Learning curves are a widely used diagnostic tool in machine learning for algorithms such as deep learning that learn incrementally. During training time, we evaluate model performance on both the training and hold-out validation dataset and we plot this performance for each training step (i.e. each epoch of a deep learning model or tree for an ensembled tree model). Reviewing learning curves of models during training can be used to diagnose problems with learning, such as an underfit or overfit model, as well as whether the training and validation datasets are suitably representative. In this notebook, I will illustrate to how you can use learning curves to:

  1. Diagnose model behavior such as under or overfitting
  2. Diagnose issues regarding disproportionate data representation

This notebook will demonstrate these issues with learning curve plots but does not show any code.1

Diagnosing Model Behavior

The shape and dynamics of a learning curve can be used to diagnose the behavior of a machine learning model and in turn perhaps suggest at the type of configuration changes that may be made to improve learning and/or performance. There are three common dynamics that you are likely to observe in learning curves:

We will take a closer look at each with examples. The examples will assume that we are looking at a minimizing loss metric, meaning that smaller relative scores on the y-axis indicate better performance.

Underfit learning curves

Underfitting refers to a model that has not adequately learned the training dataset to obtain a sufficiently low training error value. There are two common signals for underfitting. First, our training learning curve may show a flat line or noisy values of relatively high loss, indicating that the model was unable to learn the training dataset at all. An example of this is provided below and is common when the model does not have a suitable capacity for the complexity of the dataset.


Solution:

  1. Add more observations. You may not have enough data for the existing patterns to become strong signals.
  2. Add more features. Occasionally our model is under-fitting on the grounds that the feature items are insufficient.
  3. Reduce any regularization on the model. If you have explicit regularization parameters specified (i.e. dropout, weight regularization), remove or reduce these parameters. ℹ️
  4. Increase model capacity. Your model capacity may not be large enough to capture and learn existing signals. ℹ️


An underfit model may also be identified by a training and validation loss that are continuing to decrease at the end of the plot. This indicates that the model is capable of further learning and that the training process was halted prematurely.


Solution:

  1. Increase the number of epochs until the validation curve has stopped improving. This is a good time to crank up the epochs and add an early stopping callback to identify how many epochs are required. ℹ️
  2. If it is taking a long time to reach a minimum for the validation curve, increase the learning rate to speed up the gradient traversal and also add a callback to automatically adjust the learning rate. ℹ️

Overfit learning curves

Overfitting refers to a model that has learned the training dataset too well, including the statistical noise or random fluctuations in the training dataset.

“… fitting a more flexible model requires estimating a greater number of parameters. These more complex models can lead to a phenomenon known as overfitting the data, which essentially means they follow the errors, or noise, too closely.”2

The problem with overfitting, is that the more specialized the model becomes to training data, the less well it is able to generalize to new data, resulting in an increase in generalization error. Overfitting is apparent when:

  • the training loss continues to decrease with experience while
  • the validation loss has decreased to a minimum and has begun to increase.

However, a model that overfits is not necessarily a bad thing. In fact, it signals that the model has extracted all the signal that that particular model could learn. The issues to be concerned about with overfitting is the magnitude and the inflection point.

A model that overfits early and has a sharp “U” shape often indicates overcapacity and/or a learning rate that is too high.


Solution:

  1. Regularize how quickly the model learns by reducing the learning rate. Add a callback to automatically reduce the learning rate as the validation loss plateaus. ℹ️
  2. Regularize model capacity by reducing the number and/or size of the hidden layers. ℹ️
  3. Regularize the weights to constrain the complexity of the network. ℹ️
  4. Regularize happenstance patterns by adding dropout to minimize the chance of fitting patterns to noise in the data. ℹ️


Often, we can minimize overfitting but rarely can we completely eliminate it and still minimize our loss. The following illustrates an example where we have minimized overfitting, yet some overfitting still exists.


Solution:

  1. Add an early stopping callback to stop training once the validation curve has stopped improving. ℹ️
  2. Add restore_best_weights = TRUE to your callback so that your final model uses the weights from the epoch with the best loss score.

Optimal fit learning curves

An optimal fit is the goal of the learning algorithm. The loss of the model will almost always be lower on the training dataset than the validation dataset. This means that we should expect some gap between the train and validation loss learning curves. This gap is referred to as the generalization gap. An optimal fit is one where:

  • The plot of training loss decreases to a point of stability.
  • The plot of validation loss decreases to a point of stability.
  • The generalization gap is minimal (nearly zero in an ideal situation).

Continued training of an optimal fit will likely lead to overfitting. The example plot below demonstrates a case of an optimal fit assuming we have found a global minimum of our loss function.


Diagnosing Unrepresentative Datasets

Learning curves can also be used to diagnose properties of a dataset and whether it is relatively representative. An unrepresentative dataset means a dataset that may not capture the statistical characteristics relative to another dataset drawn from the same domain, such as between a train and a validation dataset. This can commonly occur if the number of samples in a dataset is too small or if certain characteristics are not adequately represented, relative to another dataset.

There are two common cases that could be observed; they are:

Unrepresentative train dataset

An unrepresentative training dataset means that the training dataset does not provide sufficient information to learn the problem, relative to the validation dataset used to evaluate it. This situation can be identified by a learning curve for training loss that shows improvement and similarly a learning curve for validation loss that shows improvement, but a large gap remains between both curves. This can occur when

  • The training dataset has too few examples as compared to the validation dataset.
  • Training dataset contains features with less variance than the validation dataset.


Solution:

  1. Add more observations. You may not have enough data to capture patterns present in both the training and validation data.
  2. If using CNNs incorporate data augmentation to increase feature variability in the training data. ℹ️
  3. Make sure that you are randomly sampling observations to use in your training and validation sets. If your data is ordered by some feature (i.e. neighborhood, class) then you validation data may have features not represented in your training data. ℹ️
  4. Perform cross-validation so that all your data has the opportunity to be represented in both the training and validation sets. ℹ️

Unrepresentative validation dataset

An unrepresentative validation dataset means that the validation dataset does not provide sufficient information to evaluate the ability of the model to generalize. This may occur if the validation dataset has too few examples as compared to the training dataset. This case can be identified by a learning curve for training loss that looks like a good fit (or other fits) and a learning curve for validation loss that shows noisy movements and little or no improvement.


Solution:

  1. Add more observations to your validation dataset.
  2. If you are limited on the number of observations, perform cross-validation so that all your data has the opportunity to be represented in both the training and validation sets. ℹ️


It may also be identified by a validation loss that is lower than the training loss, no matter how many training iterations you perform. In this case, it indicates that the validation dataset may be easier for the model to predict than the training dataset. This can happen for various reason but is commonly associated with:

  • Information leakage where a feature in the training data has direct ties to observations and responses in the validation data (i.e. patient ID).
  • Poor sampling procedures where duplicate observations exist in the training and validation datasets.
  • Validation dataset contains features with less variance than the training dataset.


Solution:

  1. Check to make sure duplicate observations do not exists across training and validation datasets.
  2. Check to make sure there is no information leakage across training and validation datasets.
  3. Make sure that you are randomly sampling observations to use in your training and validation sets so that feature variance is consistent across both sets. ℹ️
  4. Perform cross-validation so that all your data has the opportunity to be represented in both the training and validation sets. ℹ️

  1. Adapted from Better Deep Learning by Jason Brownlee.

  2. Page 22, An Introduction to Statistical Learning: with Applications in R, 2013.

LS0tCnRpdGxlOiAiRGlhZ25vc2luZyBNb2RlbCBQZXJmb3JtYW5jZSB3aXRoIExlYXJuaW5nIEN1cnZlcyIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKPGJyPgoKYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9CmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSwgbWVzc2FnZSA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0UpCmdncGxvdDI6OnRoZW1lX3NldChnZ3Bsb3QyOjp0aGVtZV9taW5pbWFsKCkpCgpsaWJyYXJ5KGtlcmFzKSAgICAgIyBmb3IgZGVlcCBsZWFybmluZwpsaWJyYXJ5KHRpZHl2ZXJzZSkgIyBmb3IgZHBseXIsIGdncGxvdDIsIGV0Yy4KCiMgZGF0YQppbWRiIDwtIGRhdGFzZXRfaW1kYihudW1fd29yZHMgPSAxMDAwMSkKYyhjKHJldmlld3NfdHJhaW4sIHlfdHJhaW4pLCBjKHJldmlld3NfdGVzdCwgeV90ZXN0KSkgJTwtJSBpbWRiCgojIG51bWJlciBvZiB1bmlxdWUgd29yZHMgd2lsbCBiZSB0aGUgbnVtYmVyIG9mIGZlYXR1cmVzCm5fZmVhdHVyZXMgPC0gYyhyZXZpZXdzX3RyYWluLCByZXZpZXdzX3Rlc3QpICU+JSAgCiAgdW5saXN0KCkgJT4lIAogIG1heCgpCgojIGZ1bmN0aW9uIHRvIGNyZWF0ZSAyRCB0ZW5zb3IgKGFrYSBtYXRyaXgpCnZlY3Rvcml6ZV9zZXF1ZW5jZXMgPC0gZnVuY3Rpb24oc2VxdWVuY2VzLCBkaW1lbnNpb24gPSBuX2ZlYXR1cmVzKSB7CiAgIyBDcmVhdGUgYSBtYXRyaXggb2YgMHMKICByZXN1bHRzIDwtIG1hdHJpeCgwLCBucm93ID0gbGVuZ3RoKHNlcXVlbmNlcyksIG5jb2wgPSBkaW1lbnNpb24pCgogICMgUG9wdWxhdGUgdGhlIG1hdHJpeCB3aXRoIDFzCiAgZm9yIChpIGluIHNlcV9hbG9uZyhzZXF1ZW5jZXMpKQogICAgcmVzdWx0c1tpLCBzZXF1ZW5jZXNbW2ldXV0gPC0gMQogIHJlc3VsdHMKfQoKIyBhcHBseSB0byB0cmFpbmluZyBhbmQgdGVzdCBkYXRhCnhfdHJhaW4gPC0gdmVjdG9yaXplX3NlcXVlbmNlcyhyZXZpZXdzX3RyYWluKQp4X3Rlc3QgPC0gdmVjdG9yaXplX3NlcXVlbmNlcyhyZXZpZXdzX3Rlc3QpCgpgYGAKCkxlYXJuaW5nIGN1cnZlcyBhcmUgYSB3aWRlbHkgdXNlZCBkaWFnbm9zdGljIHRvb2wgaW4gbWFjaGluZSBsZWFybmluZyBmb3IgYWxnb3JpdGhtcyBzdWNoIGFzIGRlZXAgbGVhcm5pbmcgdGhhdCBsZWFybiBpbmNyZW1lbnRhbGx5LiBEdXJpbmcgdHJhaW5pbmcgdGltZSwgd2UgZXZhbHVhdGUgbW9kZWwgcGVyZm9ybWFuY2Ugb24gYm90aCB0aGUgdHJhaW5pbmcgYW5kIGhvbGQtb3V0IHZhbGlkYXRpb24gZGF0YXNldCBhbmQgd2UgcGxvdCB0aGlzIHBlcmZvcm1hbmNlIGZvciBlYWNoIHRyYWluaW5nIHN0ZXAgKGkuZS4gZWFjaCBlcG9jaCBvZiBhIGRlZXAgbGVhcm5pbmcgbW9kZWwgb3IgdHJlZSBmb3IgYW4gZW5zZW1ibGVkIHRyZWUgbW9kZWwpLiBSZXZpZXdpbmcgbGVhcm5pbmcgY3VydmVzIG9mIG1vZGVscyBkdXJpbmcgdHJhaW5pbmcgY2FuIGJlIHVzZWQgdG8gZGlhZ25vc2UgcHJvYmxlbXMgd2l0aCBsZWFybmluZywgc3VjaCBhcyBhbiB1bmRlcmZpdCBvciBvdmVyZml0IG1vZGVsLCBhcyB3ZWxsIGFzIHdoZXRoZXIgdGhlIHRyYWluaW5nIGFuZCB2YWxpZGF0aW9uIGRhdGFzZXRzIGFyZSBzdWl0YWJseSByZXByZXNlbnRhdGl2ZS4gSW4gdGhpcyBub3RlYm9vaywgSSB3aWxsIGlsbHVzdHJhdGUgdG8gaG93IHlvdSBjYW4gdXNlIGxlYXJuaW5nIGN1cnZlcyB0bzoKCjEuIERpYWdub3NlIG1vZGVsIGJlaGF2aW9yIHN1Y2ggYXMgdW5kZXIgb3Igb3ZlcmZpdHRpbmcKMi4gRGlhZ25vc2UgaXNzdWVzIHJlZ2FyZGluZyBkaXNwcm9wb3J0aW9uYXRlIGRhdGEgcmVwcmVzZW50YXRpb24KClRoaXMgbm90ZWJvb2sgd2lsbCBkZW1vbnN0cmF0ZSB0aGVzZSBpc3N1ZXMgd2l0aCBsZWFybmluZyBjdXJ2ZSBwbG90cyBidXQgZG9lcyBub3Qgc2hvdyBhbnkgY29kZS5eW0FkYXB0ZWQgZnJvbSAgQmV0dGVyIERlZXAgTGVhcm5pbmcgYnkgSmFzb24gQnJvd25sZWUuXQoKIyMgRGlhZ25vc2luZyBNb2RlbCBCZWhhdmlvcgoKVGhlIHNoYXBlIGFuZCBkeW5hbWljcyBvZiBhIGxlYXJuaW5nIGN1cnZlIGNhbiBiZSB1c2VkIHRvIGRpYWdub3NlIHRoZSBiZWhhdmlvciBvZiBhIG1hY2hpbmUgbGVhcm5pbmcgbW9kZWwgYW5kIGluIHR1cm4gcGVyaGFwcyBzdWdnZXN0IGF0IHRoZSB0eXBlIG9mIGNvbmZpZ3VyYXRpb24gY2hhbmdlcyB0aGF0IG1heSBiZSBtYWRlIHRvIGltcHJvdmUgbGVhcm5pbmcgYW5kL29yIHBlcmZvcm1hbmNlLiBUaGVyZSBhcmUgdGhyZWUgY29tbW9uIGR5bmFtaWNzIHRoYXQgeW91IGFyZSBsaWtlbHkgdG8gb2JzZXJ2ZSBpbiBsZWFybmluZyBjdXJ2ZXM6CgoqIFVuZGVyZml0CiogT3ZlcmZpdAoqIE9wdGltYWwgRml0CgpXZSB3aWxsIHRha2UgYSBjbG9zZXIgbG9vayBhdCBlYWNoIHdpdGggZXhhbXBsZXMuIFRoZSBleGFtcGxlcyB3aWxsIGFzc3VtZSB0aGF0IHdlIGFyZSBsb29raW5nIGF0IGEgbWluaW1pemluZyBsb3NzIG1ldHJpYywgbWVhbmluZyB0aGF0IHNtYWxsZXIgcmVsYXRpdmUgc2NvcmVzIG9uIHRoZSB5LWF4aXMgaW5kaWNhdGUgYmV0dGVyIHBlcmZvcm1hbmNlLgoKIyMjIFVuZGVyZml0IGxlYXJuaW5nIGN1cnZlcwoKVW5kZXJmaXR0aW5nIHJlZmVycyB0byBhIG1vZGVsIHRoYXQgaGFzIG5vdCBhZGVxdWF0ZWx5IGxlYXJuZWQgdGhlIHRyYWluaW5nIGRhdGFzZXQgdG8gb2J0YWluIGEgc3VmZmljaWVudGx5IGxvdyB0cmFpbmluZyBlcnJvciB2YWx1ZS4gVGhlcmUgYXJlIHR3byBjb21tb24gc2lnbmFscyBmb3IgdW5kZXJmaXR0aW5nLiBGaXJzdCwgb3VyIHRyYWluaW5nIGxlYXJuaW5nIGN1cnZlIG1heSBzaG93IGEgZmxhdCBsaW5lIG9yIG5vaXN5IHZhbHVlcyBvZiByZWxhdGl2ZWx5IGhpZ2ggbG9zcywgaW5kaWNhdGluZyB0aGF0IHRoZSBtb2RlbCB3YXMgdW5hYmxlIHRvIGxlYXJuIHRoZSB0cmFpbmluZyBkYXRhc2V0IGF0IGFsbC4gQW4gZXhhbXBsZSBvZiB0aGlzIGlzIHByb3ZpZGVkIGJlbG93IGFuZCBpcyBjb21tb24gd2hlbiB0aGUgbW9kZWwgZG9lcyBub3QgaGF2ZSBhIHN1aXRhYmxlIGNhcGFjaXR5IGZvciB0aGUgY29tcGxleGl0eSBvZiB0aGUgZGF0YXNldC4KCjxicj4KCmBgYHtyLCBlY2hvPUZBTFNFfQp5X3RyYWluX3N1YiA8LSB5X3RyYWluWzEwMToyMDBdCnhfdHJhaW5fc3ViIDwtIHhfdHJhaW5bMTAxOjIwMCwgMTo1XQoKbmV0d29yayA8LSBrZXJhc19tb2RlbF9zZXF1ZW50aWFsKCkgJT4lIAogIGxheWVyX2RlbnNlKHVuaXRzID0gMSwgYWN0aXZhdGlvbiA9ICJzaWdtb2lkIiwgaW5wdXRfc2hhcGUgPSBuY29sKHhfdHJhaW5fc3ViKSkKCm5ldHdvcmsgJT4lIGNvbXBpbGUoCiAgb3B0aW1pemVyID0gInJtc3Byb3AiLAogIGxvc3MgPSAiYmluYXJ5X2Nyb3NzZW50cm9weSIKKQoKaGlzdG9yeSA8LSBuZXR3b3JrICU+JSBmaXQoCiAgeF90cmFpbl9zdWIsCiAgeV90cmFpbl9zdWIsCiAgZXBvY2hzID0gMjAsCiAgYmF0Y2hfc2l6ZSA9IDMyLAogIHZhbGlkYXRpb25fc3BsaXQgPSAwLjk5LAogIHZlcmJvc2UgPSBGQUxTRQopCmBgYAoKYGBge3IsIGVjaG89RkFMU0V9CnBsb3QoaGlzdG9yeSkgKwogIGdndGl0bGUoIkV4YW1wbGUgb2YgbGVhcm5pbmcgY3VydmUgc2hvd2luZyBhbiB1bmRlcmZpdCBtb2RlbCB0aGF0IGRvZXMgXG5ub3QgaGF2ZSBzdWZmaWNpZW50IGluZm9ybWF0aW9uIG5vciBjYXBhY2l0eSB0byBsZWFybiBhIHNpZ25hbC4iKQpgYGAKCgpfX1NvbHV0aW9uX186CgoxLiBBZGQgbW9yZSBvYnNlcnZhdGlvbnMuIFlvdSBtYXkgbm90IGhhdmUgZW5vdWdoIGRhdGEgZm9yIHRoZSBleGlzdGluZyBwYXR0ZXJucyB0byBiZWNvbWUgc3Ryb25nIHNpZ25hbHMuCjIuIEFkZCBtb3JlIGZlYXR1cmVzLiBPY2Nhc2lvbmFsbHkgb3VyIG1vZGVsIGlzIHVuZGVyLWZpdHRpbmcgb24gdGhlIGdyb3VuZHMgdGhhdCB0aGUgZmVhdHVyZSBpdGVtcyBhcmUgaW5zdWZmaWNpZW50LgozLiBSZWR1Y2UgYW55IHJlZ3VsYXJpemF0aW9uIG9uIHRoZSBtb2RlbC4gSWYgeW91IGhhdmUgZXhwbGljaXQgcmVndWxhcml6YXRpb24gcGFyYW1ldGVycyBzcGVjaWZpZWQgKGkuZS4gZHJvcG91dCwgd2VpZ2h0IHJlZ3VsYXJpemF0aW9uKSwgcmVtb3ZlIG9yIHJlZHVjZSB0aGVzZSBwYXJhbWV0ZXJzLiBb4oS577iPXShodHRwczovL3JzdHVkaW8tY29uZi0yMDIwLmdpdGh1Yi5pby9kbC1rZXJhcy10Zi9ub3RlYm9va3MvMDItaW1kYi5uYi5odG1sKQo0LiBJbmNyZWFzZSBtb2RlbCBjYXBhY2l0eS4gWW91ciBtb2RlbCBjYXBhY2l0eSBtYXkgbm90IGJlIGxhcmdlIGVub3VnaCB0byBjYXB0dXJlIGFuZCBsZWFybiBleGlzdGluZyBzaWduYWxzLiBb4oS577iPXShodHRwczovL3JzdHVkaW8tY29uZi0yMDIwLmdpdGh1Yi5pby9kbC1rZXJhcy10Zi9ub3RlYm9va3MvMDEtYW1lcy5uYi5odG1sI3VuZGVyLWNhcGFjaXR5KQoKPGJyPgoKQW4gdW5kZXJmaXQgbW9kZWwgbWF5IGFsc28gYmUgaWRlbnRpZmllZCBieSBhIHRyYWluaW5nIGFuZCB2YWxpZGF0aW9uIGxvc3MgdGhhdCBhcmUgY29udGludWluZyB0byBkZWNyZWFzZSBhdCB0aGUgZW5kIG9mIHRoZSBwbG90LiBUaGlzIGluZGljYXRlcyB0aGF0IHRoZSBtb2RlbCBpcyBjYXBhYmxlIG9mIGZ1cnRoZXIgbGVhcm5pbmcgYW5kIHRoYXQgdGhlIHRyYWluaW5nIHByb2Nlc3Mgd2FzIGhhbHRlZCBwcmVtYXR1cmVseS4KCjxicj4KCmBgYHtyLCBlY2hvPUZBTFNFfQpuZXR3b3JrIDwtIGtlcmFzX21vZGVsX3NlcXVlbnRpYWwoKSAlPiUgCiAgbGF5ZXJfZGVuc2UodW5pdHMgPSAxNiwgYWN0aXZhdGlvbiA9ICJyZWx1IiwgaW5wdXRfc2hhcGUgPSBuX2ZlYXR1cmVzKSAlPiUgCiAgbGF5ZXJfZGVuc2UodW5pdHMgPSAxNiwgYWN0aXZhdGlvbiA9ICJyZWx1IikgJT4lIAogIGxheWVyX2RlbnNlKHVuaXRzID0gMSwgYWN0aXZhdGlvbiA9ICJzaWdtb2lkIikKCm5ldHdvcmsgJT4lIGNvbXBpbGUoCiAgb3B0aW1pemVyID0gb3B0aW1pemVyX3Jtc3Byb3AobHIgPSAwLjAwMDAxKSwKICBsb3NzID0gImJpbmFyeV9jcm9zc2VudHJvcHkiCikKCmhpc3RvcnkgPC0gbmV0d29yayAlPiUgZml0KAogIHhfdHJhaW4sCiAgeV90cmFpbiwKICBlcG9jaHMgPSAyMCwKICBiYXRjaF9zaXplID0gMzIsCiAgdmFsaWRhdGlvbl9zcGxpdCA9IDAuMiwKICB2ZXJib3NlID0gRkFMU0UKKQpgYGAKCmBgYHtyLCBlY2hvPUZBTFNFfQpwbG90KGhpc3RvcnkpICsKICBnZ3RpdGxlKCJFeGFtcGxlIG9mIGxlYXJuaW5nIGN1cnZlIHNob3dpbmcgYW4gdW5kZXJmaXQgbW9kZWwgdGhhdCByZXF1aXJlcyBcbmZ1cnRoZXIgdHJhaW5pbmcuIikKYGBgCgpfX1NvbHV0aW9uX186IAoKMS4gSW5jcmVhc2UgdGhlIG51bWJlciBvZiBlcG9jaHMgdW50aWwgdGhlIHZhbGlkYXRpb24gY3VydmUgaGFzIHN0b3BwZWQgaW1wcm92aW5nLiBUaGlzIGlzIGEgZ29vZCB0aW1lIHRvIGNyYW5rIHVwIHRoZSBlcG9jaHMgYW5kIGFkZCBhbiBlYXJseSBzdG9wcGluZyBjYWxsYmFjayB0byBpZGVudGlmeSBob3cgbWFueSBlcG9jaHMgYXJlIHJlcXVpcmVkLiBb4oS577iPXShodHRwczovL3JzdHVkaW8tY29uZi0yMDIwLmdpdGh1Yi5pby9kbC1rZXJhcy10Zi9ub3RlYm9va3MvMDEtYW1lcy5uYi5odG1sI2NvbnNpZGVyYXRpb25zLXJlZ2FyZGluZy1iYXRjaC1zaXplcy1hbmQtZXBvY2hzKQoyLiBJZiBpdCBpcyB0YWtpbmcgYSBsb25nIHRpbWUgdG8gcmVhY2ggYSBtaW5pbXVtIGZvciB0aGUgdmFsaWRhdGlvbiBjdXJ2ZSwgaW5jcmVhc2UgdGhlIGxlYXJuaW5nIHJhdGUgdG8gc3BlZWQgdXAgdGhlIGdyYWRpZW50IHRyYXZlcnNhbCBhbmQgYWxzbyBhZGQgYSBjYWxsYmFjayB0byBhdXRvbWF0aWNhbGx5IGFkanVzdCB0aGUgbGVhcm5pbmcgcmF0ZS4gW+KEue+4j10oaHR0cHM6Ly9yc3R1ZGlvLWNvbmYtMjAyMC5naXRodWIuaW8vZGwta2VyYXMtdGYvbm90ZWJvb2tzLzAxLWFtZXMubmIuaHRtbCNhZGp1c3RhYmxlLWxlYXJuaW5nLXJhdGUpCgojIyMgT3ZlcmZpdCBsZWFybmluZyBjdXJ2ZXMKCk92ZXJmaXR0aW5nIHJlZmVycyB0byBhIG1vZGVsIHRoYXQgaGFzIGxlYXJuZWQgdGhlIHRyYWluaW5nIGRhdGFzZXQgdG9vIHdlbGwsIGluY2x1ZGluZyB0aGUgc3RhdGlzdGljYWwgbm9pc2Ugb3IgcmFuZG9tIGZsdWN0dWF0aW9ucyBpbiB0aGUgdHJhaW5pbmcgZGF0YXNldC4KCj4gXyIuLi4gZml0dGluZyBhIG1vcmUgZmxleGlibGUgbW9kZWwgcmVxdWlyZXMgZXN0aW1hdGluZyBhIGdyZWF0ZXIgbnVtYmVyIG9mIHBhcmFtZXRlcnMuIFRoZXNlIG1vcmUgY29tcGxleCBtb2RlbHMgY2FuIGxlYWQgdG8gYSBwaGVub21lbm9uIGtub3duIGFzIG92ZXJmaXR0aW5nIHRoZSBkYXRhLCB3aGljaCBlc3NlbnRpYWxseSBtZWFucyB0aGV5IGZvbGxvdyB0aGUgZXJyb3JzLCBvciBub2lzZSwgdG9vIGNsb3NlbHkuIl9eW1BhZ2UgMjIsIEFuIEludHJvZHVjdGlvbiB0byBTdGF0aXN0aWNhbCBMZWFybmluZzogd2l0aCBBcHBsaWNhdGlvbnMgaW4gUiwgMjAxMy5dCgpUaGUgcHJvYmxlbSB3aXRoIG92ZXJmaXR0aW5nLCBpcyB0aGF0IHRoZSBtb3JlIHNwZWNpYWxpemVkIHRoZSBtb2RlbCBiZWNvbWVzIHRvIHRyYWluaW5nIGRhdGEsIHRoZSBsZXNzIHdlbGwgaXQgaXMgYWJsZSB0byBnZW5lcmFsaXplIHRvIG5ldyBkYXRhLCByZXN1bHRpbmcgaW4gYW4gaW5jcmVhc2UgaW4gZ2VuZXJhbGl6YXRpb24gZXJyb3IuIE92ZXJmaXR0aW5nIGlzIGFwcGFyZW50IHdoZW46CgoqIHRoZSB0cmFpbmluZyBsb3NzIGNvbnRpbnVlcyB0byBkZWNyZWFzZSB3aXRoIGV4cGVyaWVuY2Ugd2hpbGUKKiB0aGUgdmFsaWRhdGlvbiBsb3NzIGhhcyBkZWNyZWFzZWQgdG8gYSBtaW5pbXVtIGFuZCBoYXMgYmVndW4gdG8gaW5jcmVhc2UuCgpIb3dldmVyLCBhIG1vZGVsIHRoYXQgb3ZlcmZpdHMgaXMgbm90IG5lY2Vzc2FyaWx5IGEgYmFkIHRoaW5nLiBJbiBmYWN0LCBpdCBzaWduYWxzIHRoYXQgdGhlIG1vZGVsIGhhcyBleHRyYWN0ZWQgYWxsIHRoZSBzaWduYWwgdGhhdCB0aGF0IHBhcnRpY3VsYXIgbW9kZWwgY291bGQgbGVhcm4uIFRoZSBpc3N1ZXMgdG8gYmUgY29uY2VybmVkIGFib3V0IHdpdGggb3ZlcmZpdHRpbmcgaXMgdGhlIF9tYWduaXR1ZGVfIGFuZCB0aGUgX2luZmxlY3Rpb24gcG9pbnRfLgoKQSBtb2RlbCB0aGF0IG92ZXJmaXRzIGVhcmx5IGFuZCBoYXMgYSBzaGFycCAiVSIgc2hhcGUgb2Z0ZW4gaW5kaWNhdGVzIG92ZXJjYXBhY2l0eSBhbmQvb3IgYSBsZWFybmluZyByYXRlIHRoYXQgaXMgdG9vIGhpZ2guCgo8YnI+CgpgYGB7ciwgZWNobz1GQUxTRX0KbmV0d29yayA8LSBrZXJhc19tb2RlbF9zZXF1ZW50aWFsKCkgJT4lIAogIGxheWVyX2RlbnNlKHVuaXRzID0gMTYsIGFjdGl2YXRpb24gPSAicmVsdSIsIGlucHV0X3NoYXBlID0gbl9mZWF0dXJlcykgJT4lIAogIGxheWVyX2RlbnNlKHVuaXRzID0gMTYsIGFjdGl2YXRpb24gPSAicmVsdSIpICU+JSAKICBsYXllcl9kZW5zZSh1bml0cyA9IDEsIGFjdGl2YXRpb24gPSAic2lnbW9pZCIpCgpuZXR3b3JrICU+JSBjb21waWxlKAogIG9wdGltaXplciA9ICJybXNwcm9wIiwKICBsb3NzID0gImJpbmFyeV9jcm9zc2VudHJvcHkiCikKCmhpc3RvcnkgPC0gbmV0d29yayAlPiUgZml0KAogIHhfdHJhaW4sCiAgeV90cmFpbiwKICBlcG9jaHMgPSAyMCwKICBiYXRjaF9zaXplID0gNTEyLAogIHZhbGlkYXRpb25fc3BsaXQgPSAwLjIsCiAgdmVyYm9zZSA9IEZBTFNFCikKYGBgCgpgYGB7ciwgZWNobz1GQUxTRX0KcGxvdChoaXN0b3J5KSArCiAgZ2d0aXRsZSgiRXhhbXBsZSBvZiBsZWFybmluZyBjdXJ2ZSBzaG93aW5nIGFuIG92ZXJmaXQgbW9kZWwgd2l0aCB0b28gbGFyZ2UgXG5vZiBhIGNhcGFjaXR5IGFuZCBsZWFybmluZyByYXRlLiIpCmBgYAoKX19Tb2x1dGlvbl9fOiAKCjEuIFJlZ3VsYXJpemUgaG93IHF1aWNrbHkgdGhlIG1vZGVsIGxlYXJucyBieSByZWR1Y2luZyB0aGUgbGVhcm5pbmcgcmF0ZS4gQWRkIGEgY2FsbGJhY2sgdG8gYXV0b21hdGljYWxseSByZWR1Y2UgdGhlIGxlYXJuaW5nIHJhdGUgYXMgdGhlIHZhbGlkYXRpb24gbG9zcyBwbGF0ZWF1cy4gW+KEue+4j10oaHR0cHM6Ly9yc3R1ZGlvLWNvbmYtMjAyMC5naXRodWIuaW8vZGwta2VyYXMtdGYvbm90ZWJvb2tzLzAyLWltZGIubmIuaHRtbCNyZWd1bGFyaXppbmctaG93LXF1aWNrbHktdGhlLW1vZGVsLWxlYXJucykKMi4gUmVndWxhcml6ZSBtb2RlbCBjYXBhY2l0eSBieSByZWR1Y2luZyB0aGUgbnVtYmVyIGFuZC9vciBzaXplIG9mIHRoZSBoaWRkZW4gbGF5ZXJzLiBb4oS577iPXShodHRwczovL3JzdHVkaW8tY29uZi0yMDIwLmdpdGh1Yi5pby9kbC1rZXJhcy10Zi9ub3RlYm9va3MvMDItaW1kYi5uYi5odG1sI3JlZ3VsYXJpemluZy1tb2RlbC1jYXBhY2l0eSkKMy4gUmVndWxhcml6ZSB0aGUgd2VpZ2h0cyB0byBjb25zdHJhaW4gdGhlIGNvbXBsZXhpdHkgb2YgdGhlIG5ldHdvcmsuIFvihLnvuI9dKGh0dHBzOi8vcnN0dWRpby1jb25mLTIwMjAuZ2l0aHViLmlvL2RsLWtlcmFzLXRmL25vdGVib29rcy8wMi1pbWRiLm5iLmh0bWwjcmVndWxhcml6aW5nLXRoZS1zaXplLW9mLXdlaWdodHMpCjQuIFJlZ3VsYXJpemUgaGFwcGVuc3RhbmNlIHBhdHRlcm5zIGJ5IGFkZGluZyBkcm9wb3V0IHRvIG1pbmltaXplIHRoZSBjaGFuY2Ugb2YgZml0dGluZyBwYXR0ZXJucyB0byBub2lzZSBpbiB0aGUgZGF0YS4gW+KEue+4j10oaHR0cHM6Ly9yc3R1ZGlvLWNvbmYtMjAyMC5naXRodWIuaW8vZGwta2VyYXMtdGYvbm90ZWJvb2tzLzAyLWltZGIubmIuaHRtbCNyZWd1bGFyaXppbmctaGFwcGVuc3RhbmNlLXBhdHRlcm5zKQoKPGJyPgoKT2Z0ZW4sIHdlIGNhbiBtaW5pbWl6ZSBvdmVyZml0dGluZyBidXQgcmFyZWx5IGNhbiB3ZSBjb21wbGV0ZWx5IGVsaW1pbmF0ZSBpdCBhbmQgc3RpbGwgbWluaW1pemUgb3VyIGxvc3MuIFRoZSBmb2xsb3dpbmcgaWxsdXN0cmF0ZXMgYW4gZXhhbXBsZSB3aGVyZSB3ZSBoYXZlIG1pbmltaXplZCBvdmVyZml0dGluZywgeWV0IHNvbWUgb3ZlcmZpdHRpbmcgc3RpbGwgZXhpc3RzLgoKPGJyPgoKYGBge3IsIGVjaG89RkFMU0V9CmRhdGFzZXQgPC0gZGF0YXNldF9ib3N0b25faG91c2luZygpCmMoYyh0cmFpbl9kYXRhLCB0cmFpbl90YXJnZXRzKSwgYyh0ZXN0X2RhdGEsIHRlc3RfdGFyZ2V0cykpICU8LSUgZGF0YXNldAptZWFuIDwtIGFwcGx5KHRyYWluX2RhdGEsIDIsIG1lYW4pCnN0ZCA8LSBhcHBseSh0cmFpbl9kYXRhLCAyLCBzZCkKdHJhaW5fZGF0YSA8LSBzY2FsZSh0cmFpbl9kYXRhLCBjZW50ZXIgPSBtZWFuLCBzY2FsZSA9IHN0ZCkKdGVzdF9kYXRhIDwtIHNjYWxlKHRlc3RfZGF0YSwgY2VudGVyID0gbWVhbiwgc2NhbGUgPSBzdGQpCgptb2RlbCA8LSBrZXJhc19tb2RlbF9zZXF1ZW50aWFsKCkgJT4lCiAgbGF5ZXJfZGVuc2UodW5pdHMgPSA2NCwgYWN0aXZhdGlvbiA9ICJyZWx1IiwgaW5wdXRfc2hhcGUgPSBkaW0odHJhaW5fZGF0YSlbWzJdXSkgJT4lCiAgbGF5ZXJfZGVuc2UodW5pdHMgPSA2NCwgYWN0aXZhdGlvbiA9ICJyZWx1IikgJT4lCiAgbGF5ZXJfZGVuc2UodW5pdHMgPSAxKQoKbW9kZWwgJT4lIGNvbXBpbGUoCiAgb3B0aW1pemVyID0gInJtc3Byb3AiLAogIGxvc3MgPSAibXNlIgogKQoKaGlzdG9yeSA8LSBtb2RlbCAlPiUgZml0KAogIHRyYWluX2RhdGEsIHRyYWluX3RhcmdldHMsCiAgdmFsaWRhdGlvbl9zcGxpdCA9IDAuMiwKICBlcG9jaHMgPSA1MCwgYmF0Y2hfc2l6ZSA9IDQsIHZlcmJvc2UgPSAwCikKYGBgCgpgYGB7ciwgZWNobz1GQUxTRX0KcGxvdChoaXN0b3J5KSArCiAgc2NhbGVfeV9jb250aW51b3VzKGxpbWl0cyA9IGMoMCwgMTAwKSkgKwogIGdndGl0bGUoIkV4YW1wbGUgb2YgbGVhcm5pbmcgY3VydmUgc2hvd2luZyBtaW5pbWFsIG92ZXJmaXR0aW5nLiIpCmBgYAoKX19Tb2x1dGlvbl9fOiAKCjEuIEFkZCBhbiBlYXJseSBzdG9wcGluZyBjYWxsYmFjayB0byBzdG9wIHRyYWluaW5nIG9uY2UgdGhlIHZhbGlkYXRpb24gY3VydmUgaGFzIHN0b3BwZWQgaW1wcm92aW5nLiBb4oS577iPXShodHRwczovL3JzdHVkaW8tY29uZi0yMDIwLmdpdGh1Yi5pby9kbC1rZXJhcy10Zi9ub3RlYm9va3MvMDEtYW1lcy5uYi5odG1sI2Vhcmx5LXN0b3BwaW5nKQoyLiBBZGQgYHJlc3RvcmVfYmVzdF93ZWlnaHRzID0gVFJVRWAgdG8geW91ciBjYWxsYmFjayBzbyB0aGF0IHlvdXIgZmluYWwgbW9kZWwgdXNlcyB0aGUgd2VpZ2h0cyBmcm9tIHRoZSBlcG9jaCB3aXRoIHRoZSBiZXN0IGxvc3Mgc2NvcmUuCgojIyMgT3B0aW1hbCBmaXQgbGVhcm5pbmcgY3VydmVzCgpBbiBvcHRpbWFsIGZpdCBpcyB0aGUgZ29hbCBvZiB0aGUgbGVhcm5pbmcgYWxnb3JpdGhtLiBUaGUgbG9zcyBvZiB0aGUgbW9kZWwgd2lsbCBhbG1vc3QgYWx3YXlzIGJlIGxvd2VyIG9uIHRoZSB0cmFpbmluZyBkYXRhc2V0IHRoYW4gdGhlIHZhbGlkYXRpb24gZGF0YXNldC4gVGhpcyBtZWFucyB0aGF0IHdlIHNob3VsZCBleHBlY3Qgc29tZSBnYXAgYmV0d2VlbiB0aGUgdHJhaW4gYW5kIHZhbGlkYXRpb24gbG9zcyBsZWFybmluZyBjdXJ2ZXMuIFRoaXMgZ2FwIGlzIHJlZmVycmVkIHRvIGFzIHRoZSBfZ2VuZXJhbGl6YXRpb24gZ2FwXy4gQW4gb3B0aW1hbCBmaXQgaXMgb25lIHdoZXJlOgoKKiBUaGUgcGxvdCBvZiB0cmFpbmluZyBsb3NzIGRlY3JlYXNlcyB0byBhIHBvaW50IG9mIHN0YWJpbGl0eS4KKiBUaGUgcGxvdCBvZiB2YWxpZGF0aW9uIGxvc3MgZGVjcmVhc2VzIHRvIGEgcG9pbnQgb2Ygc3RhYmlsaXR5LgoqIFRoZSBnZW5lcmFsaXphdGlvbiBnYXAgaXMgbWluaW1hbCAobmVhcmx5IHplcm8gaW4gYW4gaWRlYWwgc2l0dWF0aW9uKS4KCkNvbnRpbnVlZCB0cmFpbmluZyBvZiBhbiBvcHRpbWFsIGZpdCB3aWxsIGxpa2VseSBsZWFkIHRvIG92ZXJmaXR0aW5nLiBUaGUgZXhhbXBsZSBwbG90IGJlbG93IGRlbW9uc3RyYXRlcyBhIGNhc2Ugb2YgYW4gb3B0aW1hbCBmaXQgYXNzdW1pbmcgd2UgaGF2ZSBmb3VuZCBhIGdsb2JhbCBtaW5pbXVtIG9mIG91ciBsb3NzIGZ1bmN0aW9uLgoKPGJyPgoKYGBge3IsIGVjaG89RkFMU0V9CmxpYnJhcnkocnNhbXBsZSkgICAjIGZvciBkYXRhIHNwbGl0dGluZwpsaWJyYXJ5KHJlY2lwZXMpICAgIyBmb3IgZmVhdHVyZSBlbmdpbmVlcmluZwoKYW1lcyA8LSBBbWVzSG91c2luZzo6bWFrZV9hbWVzKCkKc2V0LnNlZWQoMTIzKQphbWVzX3NwbGl0IDwtIGluaXRpYWxfc3BsaXQoYW1lcywgcHJvcCA9IDAuNykKYW1lc190cmFpbiA8LSBhbmFseXNpcyhhbWVzX3NwbGl0KQphbWVzX3Rlc3QgPC0gYXNzZXNzbWVudChhbWVzX3NwbGl0KQoKYmx1ZXByaW50IDwtIHJlY2lwZShTYWxlX1ByaWNlIH4gLiwgZGF0YSA9IGFtZXNfdHJhaW4pICU+JQogIHN0ZXBfbnp2KGFsbF9ub21pbmFsKCkpICU+JQogIHN0ZXBfb3RoZXIoYWxsX25vbWluYWwoKSwgdGhyZXNob2xkID0gLjAxLCBvdGhlciA9ICJvdGhlciIpICU+JQogIHN0ZXBfaW50ZWdlcihtYXRjaGVzKCIoUXVhbHxDb25kfFFDfFF1KSQiKSkgJT4lCiAgc3RlcF9ZZW9Kb2huc29uKGFsbF9udW1lcmljKCksIC1hbGxfb3V0Y29tZXMoKSkgJT4lCiAgc3RlcF9jZW50ZXIoYWxsX251bWVyaWMoKSwgLWFsbF9vdXRjb21lcygpKSAlPiUKICBzdGVwX3NjYWxlKGFsbF9udW1lcmljKCksIC1hbGxfb3V0Y29tZXMoKSkgJT4lCiAgc3RlcF9kdW1teShhbGxfbm9taW5hbCgpLCAtYWxsX291dGNvbWVzKCksIG9uZV9ob3QgPSBUUlVFKQoKcHJlcGFyZSA8LSBwcmVwKGJsdWVwcmludCwgdHJhaW5pbmcgPSBhbWVzX3RyYWluKQpiYWtlZF90cmFpbiA8LSBiYWtlKHByZXBhcmUsIG5ld19kYXRhID0gYW1lc190cmFpbikKYmFrZWRfdGVzdCA8LSBiYWtlKHByZXBhcmUsIG5ld19kYXRhID0gYW1lc190ZXN0KQoKeF90cmFpbl9hbWVzIDwtIHNlbGVjdChiYWtlZF90cmFpbiwgLVNhbGVfUHJpY2UpICU+JSBhcy5tYXRyaXgoKQp5X3RyYWluX2FtZXMgPC0gYmFrZWRfdHJhaW4gJT4lIHB1bGwoU2FsZV9QcmljZSkKYGBgCgpgYGB7ciwgZWNobz1GQUxTRX0KbmV0d29yayA8LSBrZXJhc19tb2RlbF9zZXF1ZW50aWFsKCkgJT4lIAogIGxheWVyX2RlbnNlKHVuaXRzID0gMTAyNCwgYWN0aXZhdGlvbiA9ICJyZWx1IiwgaW5wdXRfc2hhcGUgPSBuY29sKHhfdHJhaW5fYW1lcykpICU+JSAKICBsYXllcl9kZW5zZSh1bml0cyA9IDEyOCwgYWN0aXZhdGlvbiA9ICJyZWx1IikgJT4lCiAgbGF5ZXJfZGVuc2UodW5pdHMgPSAxKSAKCm5ldHdvcmsgJT4lCiAgY29tcGlsZSgKICAgIG9wdGltaXplciA9IG9wdGltaXplcl9ybXNwcm9wKGxyID0gMC4wMSksCiAgICBsb3NzID0gIm1zbGUiCiAgKQoKaGlzdG9yeSA8LSBuZXR3b3JrICU+JSBmaXQoCiAgeF90cmFpbl9hbWVzLAogIHlfdHJhaW5fYW1lcywKICBlcG9jaHMgPSA1MCwKICBiYXRjaF9zaXplID0gMzIsCiAgdmFsaWRhdGlvbl9zcGxpdCA9IDAuMiwKICBjYWxsYmFja3MgPSBsaXN0KAogICAgICAgIGNhbGxiYWNrX2Vhcmx5X3N0b3BwaW5nKHBhdGllbmNlID0gMTAsIHJlc3RvcmVfYmVzdF93ZWlnaHRzID0gVFJVRSksCiAgICAgICAgY2FsbGJhY2tfcmVkdWNlX2xyX29uX3BsYXRlYXUoZmFjdG9yID0gMC4yLCBwYXRpZW5jZSA9IDQpCiAgICApLAogIHZlcmJvc2UgPSAwCikKYGBgCgpgYGB7ciwgZWNobz1GQUxTRX0KcGxvdChoaXN0b3J5KSArIAogIHNjYWxlX3lfbG9nMTAoKSArCiAgc2NhbGVfeF9jb250aW51b3VzKGxpbWl0cyA9IGMoMCwgbGVuZ3RoKGhpc3RvcnkkbWV0cmljcyR2YWxfbG9zcykpKSArCiAgZ2d0aXRsZSgiRXhhbXBsZSBvZiBsZWFybmluZyBjdXJ2ZSBzaG93aW5nIG5lYXIgb3B0aW1hbGl0eSBhc3N1bWluZyB3ZSBcbmhhdmUgYWRhcXVhdGVseSBtaW5pbWl6ZWQgdGhlIGxvc3Mgc2NvcmUuIikKYGBgCgojIyBEaWFnbm9zaW5nIFVucmVwcmVzZW50YXRpdmUgRGF0YXNldHMKCkxlYXJuaW5nIGN1cnZlcyBjYW4gYWxzbyBiZSB1c2VkIHRvIGRpYWdub3NlIHByb3BlcnRpZXMgb2YgYSBkYXRhc2V0IGFuZCB3aGV0aGVyIGl0IGlzIHJlbGF0aXZlbHkgcmVwcmVzZW50YXRpdmUuIEFuIHVucmVwcmVzZW50YXRpdmUgZGF0YXNldCBtZWFucyBhIGRhdGFzZXQgdGhhdCBtYXkgbm90IGNhcHR1cmUgdGhlIHN0YXRpc3RpY2FsIGNoYXJhY3RlcmlzdGljcyByZWxhdGl2ZSB0byBhbm90aGVyIGRhdGFzZXQgZHJhd24gZnJvbSB0aGUgc2FtZSBkb21haW4sIHN1Y2ggYXMgYmV0d2VlbiBhIHRyYWluIGFuZCBhIHZhbGlkYXRpb24gZGF0YXNldC4gVGhpcyBjYW4gY29tbW9ubHkgb2NjdXIgaWYgdGhlIG51bWJlciBvZiBzYW1wbGVzIGluIGEgZGF0YXNldCBpcyB0b28gc21hbGwgb3IgaWYgY2VydGFpbiBjaGFyYWN0ZXJpc3RpY3MgYXJlIG5vdCBhZGVxdWF0ZWx5IHJlcHJlc2VudGVkLCByZWxhdGl2ZSB0byBhbm90aGVyIGRhdGFzZXQuCgpUaGVyZSBhcmUgdHdvIGNvbW1vbiBjYXNlcyB0aGF0IGNvdWxkIGJlIG9ic2VydmVkOyB0aGV5IGFyZToKCiogVHJhaW5pbmcgZGF0YXNldCBpcyByZWxhdGl2ZWx5IHVucmVwcmVzZW50YXRpdmUKKiBWYWxpZGF0aW9uIGRhdGFzZXQgaXMgcmVsYXRpdmVseSB1bnJlcHJlc2VudGF0aXZlCgojIyMgVW5yZXByZXNlbnRhdGl2ZSB0cmFpbiBkYXRhc2V0CgpBbiB1bnJlcHJlc2VudGF0aXZlIHRyYWluaW5nIGRhdGFzZXQgbWVhbnMgdGhhdCB0aGUgdHJhaW5pbmcgZGF0YXNldCBkb2VzIG5vdCBwcm92aWRlIHN1ZmZpY2llbnQgaW5mb3JtYXRpb24gdG8gbGVhcm4gdGhlIHByb2JsZW0sIHJlbGF0aXZlIHRvIHRoZSB2YWxpZGF0aW9uIGRhdGFzZXQgdXNlZCB0byBldmFsdWF0ZSBpdC4gVGhpcyBzaXR1YXRpb24gY2FuIGJlIGlkZW50aWZpZWQgYnkgYSBsZWFybmluZyBjdXJ2ZSBmb3IgdHJhaW5pbmcgbG9zcyB0aGF0IHNob3dzIGltcHJvdmVtZW50IGFuZCBzaW1pbGFybHkgYSBsZWFybmluZyBjdXJ2ZSBmb3IgdmFsaWRhdGlvbiBsb3NzIHRoYXQgc2hvd3MgaW1wcm92ZW1lbnQsIGJ1dCBhIGxhcmdlIGdhcCByZW1haW5zIGJldHdlZW4gYm90aCBjdXJ2ZXMuIFRoaXMgY2FuIG9jY3VyIHdoZW4gCgoqIFRoZSB0cmFpbmluZyBkYXRhc2V0IGhhcyB0b28gZmV3IGV4YW1wbGVzIGFzIGNvbXBhcmVkIHRvIHRoZSB2YWxpZGF0aW9uIGRhdGFzZXQuCiogVHJhaW5pbmcgZGF0YXNldCBjb250YWlucyBmZWF0dXJlcyB3aXRoIGxlc3MgdmFyaWFuY2UgdGhhbiB0aGUgdmFsaWRhdGlvbiBkYXRhc2V0LgoKPGJyPgoKYGBge3IsIGVjaG89RkFMU0V9CmFtZXNfdHJhaW4gPC0gZmlsdGVyKGFtZXMsIEdyX0xpdl9BcmVhIDwgMTUwMCkKYW1lc190ZXN0IDwtIGZpbHRlcihhbWVzLCBHcl9MaXZfQXJlYSA+IDE1MDApCgpibHVlcHJpbnQgPC0gcmVjaXBlKFNhbGVfUHJpY2UgfiAuLCBkYXRhID0gYW1lc190cmFpbikgJT4lCiAgc3RlcF9uenYoYWxsX25vbWluYWwoKSkgJT4lCiAgc3RlcF9vdGhlcihhbGxfbm9taW5hbCgpLCB0aHJlc2hvbGQgPSAuMDEsIG90aGVyID0gIm90aGVyIikgJT4lCiAgc3RlcF9pbnRlZ2VyKG1hdGNoZXMoIihRdWFsfENvbmR8UUN8UXUpJCIpKSAlPiUKICBzdGVwX1llb0pvaG5zb24oYWxsX251bWVyaWMoKSwgLWFsbF9vdXRjb21lcygpKSAlPiUKICBzdGVwX2NlbnRlcihhbGxfbnVtZXJpYygpLCAtYWxsX291dGNvbWVzKCkpICU+JQogIHN0ZXBfc2NhbGUoYWxsX251bWVyaWMoKSwgLWFsbF9vdXRjb21lcygpKSAlPiUKICBzdGVwX2R1bW15KGFsbF9ub21pbmFsKCksIC1hbGxfb3V0Y29tZXMoKSwgb25lX2hvdCA9IFRSVUUpCgpwcmVwYXJlIDwtIHByZXAoYmx1ZXByaW50LCB0cmFpbmluZyA9IGFtZXNfdHJhaW4pCmJha2VkX3RyYWluIDwtIGJha2UocHJlcGFyZSwgbmV3X2RhdGEgPSBhbWVzX3RyYWluKQpiYWtlZF90ZXN0IDwtIGJha2UocHJlcGFyZSwgbmV3X2RhdGEgPSBhbWVzX3Rlc3QpCgp4X3RyYWluX2FtZXMgPC0gc2VsZWN0KGJha2VkX3RyYWluLCAtU2FsZV9QcmljZSkgJT4lIGFzLm1hdHJpeCgpCnlfdHJhaW5fYW1lcyA8LSBiYWtlZF90cmFpbiAlPiUgcHVsbChTYWxlX1ByaWNlKQoKeF90ZXN0X2FtZXMgPC0gc2VsZWN0KGJha2VkX3Rlc3QsIC1TYWxlX1ByaWNlKSAlPiUgYXMubWF0cml4KCkKeV90ZXN0X2FtZXMgPC0gYmFrZWRfdGVzdCAlPiUgcHVsbChTYWxlX1ByaWNlKQpgYGAKCmBgYHtyLCBlY2hvPUZBTFNFfQpuZXR3b3JrIDwtIGtlcmFzX21vZGVsX3NlcXVlbnRpYWwoKSAlPiUgCiAgbGF5ZXJfZGVuc2UodW5pdHMgPSAxMDI0LCBhY3RpdmF0aW9uID0gInJlbHUiLCBpbnB1dF9zaGFwZSA9IG5jb2woeF90cmFpbl9hbWVzKSkgJT4lIAogIGxheWVyX2RlbnNlKHVuaXRzID0gMTI4LCBhY3RpdmF0aW9uID0gInJlbHUiKSAlPiUKICBsYXllcl9kZW5zZSh1bml0cyA9IDEpIAoKbmV0d29yayAlPiUKICBjb21waWxlKAogICAgb3B0aW1pemVyID0gb3B0aW1pemVyX3Jtc3Byb3AobHIgPSAwLjAxKSwKICAgIGxvc3MgPSAibXNsZSIKICApCgpoaXN0b3J5IDwtIG5ldHdvcmsgJT4lIGZpdCgKICB4X3RyYWluX2FtZXMsCiAgeV90cmFpbl9hbWVzLAogIGVwb2NocyA9IDUwLAogIGJhdGNoX3NpemUgPSAzMiwKICB2YWxpZGF0aW9uX2RhdGEgPSBsaXN0KHhfdGVzdF9hbWVzLCB5X3Rlc3RfYW1lcyksCiAgY2FsbGJhY2tzID0gbGlzdCgKICAgICAgICBjYWxsYmFja19lYXJseV9zdG9wcGluZyhwYXRpZW5jZSA9IDEwLCByZXN0b3JlX2Jlc3Rfd2VpZ2h0cyA9IFRSVUUpLAogICAgICAgIGNhbGxiYWNrX3JlZHVjZV9scl9vbl9wbGF0ZWF1KGZhY3RvciA9IDAuMiwgcGF0aWVuY2UgPSA0KQogICAgKSwKICB2ZXJib3NlID0gMAopCmBgYAoKYGBge3IsIGVjaG89RkFMU0V9CnBsb3QoaGlzdG9yeSkgKyAKICBzY2FsZV95X2xvZzEwKCkgKwogIHNjYWxlX3hfY29udGludW91cyhsaW1pdHMgPSBjKDAsIGxlbmd0aChoaXN0b3J5JG1ldHJpY3MkdmFsX2xvc3MpKSkgKwogIGdndGl0bGUoIkV4YW1wbGUgb2YgbGVhcm5pbmcgY3VydmVzIHdoZXJlIHRoZSB0cmFpbmluZyBkYXRhIGRvZXMgbm90IGhhdmUgXG5pbXBvcnRhbnQgZmVhdHVyZSB2YWx1ZXMgY29udGFpbmVkIGluIHRoZSB2YWxpZGF0aW9uIGRhdGEgKGkuZS4gc3F1YXJlIFxuZmVldCBncmVhdGVyIHRoYW4gMTUwMCkuIikKYGBgCgoKX19Tb2x1dGlvbl9fOiAKCjEuIEFkZCBtb3JlIG9ic2VydmF0aW9ucy4gWW91IG1heSBub3QgaGF2ZSBlbm91Z2ggZGF0YSB0byBjYXB0dXJlIHBhdHRlcm5zIHByZXNlbnQgaW4gYm90aCB0aGUgdHJhaW5pbmcgYW5kIHZhbGlkYXRpb24gZGF0YS4KMi4gSWYgdXNpbmcgQ05OcyBpbmNvcnBvcmF0ZSBkYXRhIGF1Z21lbnRhdGlvbiB0byBpbmNyZWFzZSBmZWF0dXJlIHZhcmlhYmlsaXR5IGluIHRoZSB0cmFpbmluZyBkYXRhLiBb4oS577iPXShodHRwczovL3JzdHVkaW8tY29uZi0yMDIwLmdpdGh1Yi5pby9kbC1rZXJhcy10Zi9ub3RlYm9va3MvMDItY2F0cy12cy1kb2dzLm5iLmh0bWwjaW1hZ2UtYXVnbWVudGF0aW9uKQozLiBNYWtlIHN1cmUgdGhhdCB5b3UgYXJlIHJhbmRvbWx5IHNhbXBsaW5nIG9ic2VydmF0aW9ucyB0byB1c2UgaW4geW91ciB0cmFpbmluZyBhbmQgdmFsaWRhdGlvbiBzZXRzLiBJZiB5b3VyIGRhdGEgaXMgb3JkZXJlZCBieSBzb21lIGZlYXR1cmUgKGkuZS4gbmVpZ2hib3Job29kLCBjbGFzcykgdGhlbiB5b3UgdmFsaWRhdGlvbiBkYXRhIG1heSBoYXZlIGZlYXR1cmVzIG5vdCByZXByZXNlbnRlZCBpbiB5b3VyIHRyYWluaW5nIGRhdGEuIFvihLnvuI9dKGh0dHBzOi8vcnN0dWRpby1jb25mLTIwMjAuZ2l0aHViLmlvL2RsLWtlcmFzLXRmL25vdGVib29rcy8wMS13b3JkLWVtYmVkZGluZ3MubmIuaHRtbCNtb2RlbC10cmFpbmluZykKNC4gUGVyZm9ybSBjcm9zcy12YWxpZGF0aW9uIHNvIHRoYXQgYWxsIHlvdXIgZGF0YSBoYXMgdGhlIG9wcG9ydHVuaXR5IHRvIGJlIHJlcHJlc2VudGVkIGluIGJvdGggdGhlIHRyYWluaW5nIGFuZCB2YWxpZGF0aW9uIHNldHMuIFvihLnvuI9dKGh0dHBzOi8vcnN0dWRpby1jb25mLTIwMjAuZ2l0aHViLmlvL2RsLWtlcmFzLXRmL25vdGVib29rcy92YWxpZGF0aW9uLXByb2NlZHVyZXMubmIuaHRtbCkKCiMjIyBVbnJlcHJlc2VudGF0aXZlIHZhbGlkYXRpb24gZGF0YXNldAoKQW4gdW5yZXByZXNlbnRhdGl2ZSB2YWxpZGF0aW9uIGRhdGFzZXQgbWVhbnMgdGhhdCB0aGUgdmFsaWRhdGlvbiBkYXRhc2V0IGRvZXMgbm90IHByb3ZpZGUgc3VmZmljaWVudCBpbmZvcm1hdGlvbiB0byBldmFsdWF0ZSB0aGUgYWJpbGl0eSBvZiB0aGUgbW9kZWwgdG8gZ2VuZXJhbGl6ZS4gVGhpcyBtYXkgb2NjdXIgaWYgdGhlIHZhbGlkYXRpb24gZGF0YXNldCBoYXMgdG9vIGZldyBleGFtcGxlcyBhcyBjb21wYXJlZCB0byB0aGUgdHJhaW5pbmcgZGF0YXNldC4gVGhpcyBjYXNlIGNhbiBiZSBpZGVudGlmaWVkIGJ5IGEgbGVhcm5pbmcgY3VydmUgZm9yIHRyYWluaW5nIGxvc3MgdGhhdCBsb29rcyBsaWtlIGEgZ29vZCBmaXQgKG9yIG90aGVyIGZpdHMpIGFuZCBhIGxlYXJuaW5nIGN1cnZlIGZvciB2YWxpZGF0aW9uIGxvc3MgdGhhdCBzaG93cyBub2lzeSBtb3ZlbWVudHMgYW5kIGxpdHRsZSBvciBubyBpbXByb3ZlbWVudC4KCjxicj4KCmBgYHtyLCBlY2hvPUZBTFNFfQpuZXR3b3JrIDwtIGtlcmFzX21vZGVsX3NlcXVlbnRpYWwoKSAlPiUgCiAgbGF5ZXJfZGVuc2UodW5pdHMgPSAxMDI0LCBhY3RpdmF0aW9uID0gInJlbHUiLCBpbnB1dF9zaGFwZSA9IG5jb2woeF90cmFpbl9hbWVzKSkgJT4lIAogIGxheWVyX2RlbnNlKHVuaXRzID0gMTI4LCBhY3RpdmF0aW9uID0gInJlbHUiKSAlPiUKICBsYXllcl9kZW5zZSh1bml0cyA9IDEpIAoKbmV0d29yayAlPiUKICBjb21waWxlKAogICAgb3B0aW1pemVyID0gb3B0aW1pemVyX3Jtc3Byb3AobHIgPSAwLjAxKSwKICAgIGxvc3MgPSAibXNsZSIKICApCgpoaXN0b3J5IDwtIG5ldHdvcmsgJT4lIGZpdCgKICB4X3RyYWluX2FtZXMsCiAgeV90cmFpbl9hbWVzLAogIGVwb2NocyA9IDUwLAogIGJhdGNoX3NpemUgPSAzMiwKICB2YWxpZGF0aW9uX3NwbGl0ID0gMC4wMDEsCiAgdmVyYm9zZSA9IDAKKQpgYGAKCmBgYHtyLCBlY2hvPUZBTFNFfQphcy5kYXRhLmZyYW1lKGhpc3RvcnkpICU+JQogIGdncGxvdChhZXMoZXBvY2gsIHZhbHVlKSkgKwogIGdlb21fbGluZShhZXMoY29sb3IgPSBkYXRhKSkgKwogIGdlb21fcG9pbnQoYWVzKGZpbGwgPSBkYXRhKSwgc2hhcGUgPSAyMSkgKwogIHNjYWxlX3lfbG9nMTAoKSArCiAgZ2d0aXRsZSgiRXhhbXBsZSBvZiBsZWFybmluZyBjdXJ2ZXMgd2hlcmUgdGhlIHZhbGlkYXRpb24gZGF0YXNldCBpcyB0b28gc21hbGwgXG5yZWxhdGl2ZSB0byB0aGUgdHJhaW5pbmcgZGF0YXNldC4iKQpgYGAKCl9fU29sdXRpb25fXzogCgoxLiBBZGQgbW9yZSBvYnNlcnZhdGlvbnMgdG8geW91ciB2YWxpZGF0aW9uIGRhdGFzZXQuCjIuIElmIHlvdSBhcmUgbGltaXRlZCBvbiB0aGUgbnVtYmVyIG9mIG9ic2VydmF0aW9ucywgcGVyZm9ybSBjcm9zcy12YWxpZGF0aW9uIHNvIHRoYXQgYWxsIHlvdXIgZGF0YSBoYXMgdGhlIG9wcG9ydHVuaXR5IHRvIGJlIHJlcHJlc2VudGVkIGluIGJvdGggdGhlIHRyYWluaW5nIGFuZCB2YWxpZGF0aW9uIHNldHMuIFvihLnvuI9dKGh0dHBzOi8vcnN0dWRpby1jb25mLTIwMjAuZ2l0aHViLmlvL2RsLWtlcmFzLXRmL25vdGVib29rcy92YWxpZGF0aW9uLXByb2NlZHVyZXMubmIuaHRtbCkKCjxicj4KCkl0IG1heSBhbHNvIGJlIGlkZW50aWZpZWQgYnkgYSB2YWxpZGF0aW9uIGxvc3MgdGhhdCBpcyBsb3dlciB0aGFuIHRoZSB0cmFpbmluZyBsb3NzLCBubyBtYXR0ZXIgaG93IG1hbnkgdHJhaW5pbmcgaXRlcmF0aW9ucyB5b3UgcGVyZm9ybS4gSW4gdGhpcyBjYXNlLCBpdCBpbmRpY2F0ZXMgdGhhdCB0aGUgdmFsaWRhdGlvbiBkYXRhc2V0IG1heSBiZSBlYXNpZXIgZm9yIHRoZSBtb2RlbCB0byBwcmVkaWN0IHRoYW4gdGhlIHRyYWluaW5nIGRhdGFzZXQuIFRoaXMgY2FuIGhhcHBlbiBmb3IgdmFyaW91cyByZWFzb24gYnV0IGlzIGNvbW1vbmx5IGFzc29jaWF0ZWQgd2l0aDoKCiogSW5mb3JtYXRpb24gbGVha2FnZSB3aGVyZSBhIGZlYXR1cmUgaW4gdGhlIHRyYWluaW5nIGRhdGEgaGFzIGRpcmVjdCB0aWVzIHRvIG9ic2VydmF0aW9ucyBhbmQgcmVzcG9uc2VzIGluIHRoZSB2YWxpZGF0aW9uIGRhdGEgKGkuZS4gcGF0aWVudCBJRCkuCiogUG9vciBzYW1wbGluZyBwcm9jZWR1cmVzIHdoZXJlIGR1cGxpY2F0ZSBvYnNlcnZhdGlvbnMgZXhpc3QgaW4gdGhlIHRyYWluaW5nIGFuZCB2YWxpZGF0aW9uIGRhdGFzZXRzLgoqIFZhbGlkYXRpb24gZGF0YXNldCBjb250YWlucyBmZWF0dXJlcyB3aXRoIGxlc3MgdmFyaWFuY2UgdGhhbiB0aGUgdHJhaW5pbmcgZGF0YXNldC4KCjxicj4KCmBgYHtyLCBlY2hvPUZBTFNFfQppbmRleCA8LSBhbWVzICU+JSAKICBtdXRhdGUoSUQgPSByb3dfbnVtYmVyKCkpICU+JSAKICBmaWx0ZXIoTmVpZ2hib3Job29kID09ICJOb3J0aF9BbWVzIiwgYXMubnVtZXJpYyhPdmVyYWxsX1F1YWwpID4gNSkgJT4lCiAgcHVsbChJRCkKCmFtZXNfdHJhaW4gPC0gYW1lcwphbWVzX3Rlc3QgPC0gYW1lc1tpbmRleCwgXQoKYmx1ZXByaW50IDwtIHJlY2lwZShTYWxlX1ByaWNlIH4gLiwgZGF0YSA9IGFtZXNfdHJhaW4pICU+JQogIHN0ZXBfbnp2KGFsbF9ub21pbmFsKCkpICU+JQogIHN0ZXBfb3RoZXIoYWxsX25vbWluYWwoKSwgdGhyZXNob2xkID0gLjAxLCBvdGhlciA9ICJvdGhlciIpICU+JQogIHN0ZXBfaW50ZWdlcihtYXRjaGVzKCIoUXVhbHxDb25kfFFDfFF1KSQiKSkgJT4lCiAgc3RlcF9ZZW9Kb2huc29uKGFsbF9udW1lcmljKCksIC1hbGxfb3V0Y29tZXMoKSkgJT4lCiAgc3RlcF9jZW50ZXIoYWxsX251bWVyaWMoKSwgLWFsbF9vdXRjb21lcygpKSAlPiUKICBzdGVwX3NjYWxlKGFsbF9udW1lcmljKCksIC1hbGxfb3V0Y29tZXMoKSkgJT4lCiAgc3RlcF9kdW1teShhbGxfbm9taW5hbCgpLCAtYWxsX291dGNvbWVzKCksIG9uZV9ob3QgPSBUUlVFKQoKcHJlcGFyZSA8LSBwcmVwKGJsdWVwcmludCwgdHJhaW5pbmcgPSBhbWVzX3RyYWluKQpiYWtlZF90cmFpbiA8LSBiYWtlKHByZXBhcmUsIG5ld19kYXRhID0gYW1lc190cmFpbikKYmFrZWRfdGVzdCA8LSBiYWtlKHByZXBhcmUsIG5ld19kYXRhID0gYW1lc190ZXN0KQoKeF90cmFpbl9hbWVzIDwtIHNlbGVjdChiYWtlZF90cmFpbiwgLVNhbGVfUHJpY2UpICU+JSBhcy5tYXRyaXgoKQp5X3RyYWluX2FtZXMgPC0gYmFrZWRfdHJhaW4gJT4lIHB1bGwoU2FsZV9QcmljZSkKCnhfdGVzdF9hbWVzIDwtIHNlbGVjdChiYWtlZF90ZXN0LCAtU2FsZV9QcmljZSkgJT4lIGFzLm1hdHJpeCgpCnlfdGVzdF9hbWVzIDwtIGJha2VkX3Rlc3QgJT4lIHB1bGwoU2FsZV9QcmljZSkKYGBgCgpgYGB7ciwgZWNobz1GQUxTRX0KbmV0d29yayA8LSBrZXJhc19tb2RlbF9zZXF1ZW50aWFsKCkgJT4lIAogIGxheWVyX2RlbnNlKHVuaXRzID0gMjU2LCBhY3RpdmF0aW9uID0gInJlbHUiLCBpbnB1dF9zaGFwZSA9IG5jb2woeF90cmFpbl9hbWVzKSkgJT4lIAogIGxheWVyX2RlbnNlKHVuaXRzID0gMTI4LCBhY3RpdmF0aW9uID0gInJlbHUiKSAlPiUKICBsYXllcl9kZW5zZSh1bml0cyA9IDEpIAoKbmV0d29yayAlPiUKICBjb21waWxlKAogICAgb3B0aW1pemVyID0gb3B0aW1pemVyX3Jtc3Byb3AobHIgPSAwLjAxKSwKICAgIGxvc3MgPSAibXNsZSIKICApCgpoaXN0b3J5IDwtIG5ldHdvcmsgJT4lIGZpdCgKICB4X3RyYWluX2FtZXMsCiAgeV90cmFpbl9hbWVzLAogIGVwb2NocyA9IDEwMCwKICBiYXRjaF9zaXplID0gMzIsCiAgdmFsaWRhdGlvbl9kYXRhID0gbGlzdCh4X3Rlc3RfYW1lcywgeV90ZXN0X2FtZXMpLAogIHZlcmJvc2UgPSAwCikKYGBgCgpgYGB7ciwgZWNobz1GQUxTRX0KcGxvdChoaXN0b3J5KSArIAogIHNjYWxlX3lfbG9nMTAoKSArCiAgZ2d0aXRsZSgiRXhhbXBsZSBvZiBsZWFybmluZyBjdXJ2ZXMgd2hlcmUgdGhlIHZhbGlkYXRpb24gZGF0YXNldCBpcyBlYXNpZXIgdG8gXG5wcmVkaWN0IHRoYW4gdGhlIHRyYWluaW5nIGRhdGFzZXQgZHVlIHRvIGluZm9ybWF0aW9uIGxlYWthZ2UuIikKYGBgCgpfX1NvbHV0aW9uX186IAoKMS4gQ2hlY2sgdG8gbWFrZSBzdXJlIGR1cGxpY2F0ZSBvYnNlcnZhdGlvbnMgZG8gbm90IGV4aXN0cyBhY3Jvc3MgdHJhaW5pbmcgYW5kIHZhbGlkYXRpb24gZGF0YXNldHMuCjIuIENoZWNrIHRvIG1ha2Ugc3VyZSB0aGVyZSBpcyBubyBpbmZvcm1hdGlvbiBsZWFrYWdlIGFjcm9zcyB0cmFpbmluZyBhbmQgdmFsaWRhdGlvbiBkYXRhc2V0cy4KMy4gTWFrZSBzdXJlIHRoYXQgeW91IGFyZSByYW5kb21seSBzYW1wbGluZyBvYnNlcnZhdGlvbnMgdG8gdXNlIGluIHlvdXIgdHJhaW5pbmcgYW5kIHZhbGlkYXRpb24gc2V0cyBzbyB0aGF0IGZlYXR1cmUgdmFyaWFuY2UgaXMgY29uc2lzdGVudCBhY3Jvc3MgYm90aCBzZXRzLiBb4oS577iPXShodHRwczovL3JzdHVkaW8tY29uZi0yMDIwLmdpdGh1Yi5pby9kbC1rZXJhcy10Zi9ub3RlYm9va3MvMDEtd29yZC1lbWJlZGRpbmdzLm5iLmh0bWwjbW9kZWwtdHJhaW5pbmcpCjQuIFBlcmZvcm0gY3Jvc3MtdmFsaWRhdGlvbiBzbyB0aGF0IGFsbCB5b3VyIGRhdGEgaGFzIHRoZSBvcHBvcnR1bml0eSB0byBiZSByZXByZXNlbnRlZCBpbiBib3RoIHRoZSB0cmFpbmluZyBhbmQgdmFsaWRhdGlvbiBzZXRzLiBb4oS577iPXShodHRwczovL3JzdHVkaW8tY29uZi0yMDIwLmdpdGh1Yi5pby9kbC1rZXJhcy10Zi9ub3RlYm9va3MvdmFsaWRhdGlvbi1wcm9jZWR1cmVzLm5iLmh0bWwpCg==