Goal
Reduce the number of False Positive generated by the NN classifier by using a client profile.
Approach for training the classifier
Features
The cliente profile was generated using the following features
CURRENT TIME WINDOW = 1H
Total number of Requests
Ratio of NX requests (answer=NX)
ratio of MX request (query_type=“MX”)
Ratio of Reverse requests (answer=in.arpa)
Ratio of Fail Request (answer=SERVFAIL)
Ratio of the total amount of domains that answered to more than one IP (given a query, count the number of distinct IP addresses, using the answer_ip field and sum them)
Ratio of the Average amount of domains that answered to more than one (calculate the average over all the queries that have distinct IP addresses. In other words instead of sum them, just calcuate the average)
Ratio of the total amount of IP that correspond to more than one Domain name (Given an answer_ip field, count the number distinct queries for this answer_ip and sum them)
Ratio of the Average amount of IP addresses that correspond to more than one Domain name. (Calculate the average over all the answer_ip that have distinct queries. In other word, instead of sum them, just calculate the average)
Note: in all cases ratio is calculated over the Total Number of requests (Feature 1)
Training approach
The training dataset contains only those client profiles labeled as Normal with at least one request detected by the NN. That is, those profiles that contains False Positives. THe resulting training dataset contains 225 client profiles.
The table below shows the GroundTruthLabel (Label) and the (NN) Label (i.e. those profile with at least 1 NN detection are labeled as DGA). For simplitiy a third label called (profile) is added for aggregating all those GroundTruhLabels not Normal.
Separating in training and testing Sets
We used a 2x5 Cross Validation approach. The ROC metric was used for finding the best model.
ctrl_fast <- trainControl(method="cv",
repeats=2,
number=5,
summaryFunction=twoClassSummary,
verboseIter=T,
classProbs=TRUE,
allowParallel = TRUE)
70% of the dataset was used for training and a 30% for testing.
Below we show the distribution of the profile labels in the resulting 2 datasets (training,testing)
data_train %>% group_by(profile) %>% summarise(total=n())
data_test %>% group_by(profile) %>% summarise(total=n())
A randomForest classifier is training using ROC metric for finding the best Model. Prior training the training dataset was Randomly Upsampled
ctrl_fast$sampling<-"up"
rfFit<- train(train_formula,
data = data_train,
# method = "svmRadialWeights", # Radial kernel
method = "rf",
tuneLength = 5,
#tuneGrid = svmGrid,
#preProcess=c("scale","center"),
metric="ROC",
#weights = model_weights,
trControl = ctrl_fast)
rfFit
The Confusion matrix for resulting model on the Training set is shown below.
rfFit$finalModel
Call:
randomForest(x = x, y = y, mtry = param$mtry)
Type of random forest: classification
Number of trees: 500
No. of variables tried at each split: 2
OOB estimate of error rate: 4.04%
Confusion matrix:
Malicious Normal class.error
Malicious 94 5 0.05050505
Normal 3 96 0.03030303
Most relevant features
THe list of the most relevant features used by the RandomForest Classifier
varImp(rfFit, scale = F)
rf variable importance
Overall
tot_requests 19.030
ratio_avg_samedomain 18.481
ratio_detected 12.318
ratio_tot_samedomain 8.645
ratio_nx 8.596
ratio_reverse 8.122
ratio_fail 6.902
ratio_tot_sameip 4.929
ratio_avg_sameip 4.738
ratio_mx 4.383
ROC Curve Analysis:
The resulting ROC curves showed that a threshold between 0.8 and 0.9 are the best options for detecting the most of the Normal Profiles (i.e False Negatives) while keeping a good rate of True Positive (i.e. those profiles labeled asMalicious)
#plot(roc(data_test$profile,predsrprofilerobsamp$Malicious))
ggplot(cbind(predsrprofilerobsamp,class=data_test$profile),
aes(m = Normal, d = factor(class, labels=c("Normal","Malicious"),levels = c("Normal", "Malicious")))) +
geom_roc(hjust = -0.4, vjust = 1.5,colour='orange') +
theme_bw()

Evaluation on Testing File
The Confusion matrix for the resulting model on the Testing set is shown below:
predsrprofilerobsamp=predict(rfFit,data_test,type='prob')
predsrfsamp=ifelse(predsrprofilerobsamp$Malicious >=0.9,'Malicious','Normal')
cm<-confusionMatrix(predsrfsamp,data_test$profile,positive="Malicious")
cm
Confusion Matrix and Statistics
Reference
Prediction Malicious Normal
Malicious 20 0
Normal 5 42
Accuracy : 0.9254
95% CI : (0.8344, 0.9753)
No Information Rate : 0.6269
P-Value [Acc > NIR] : 2.134e-08
Kappa : 0.8337
Mcnemar's Test P-Value : 0.07364
Sensitivity : 0.8000
Specificity : 1.0000
Pos Pred Value : 1.0000
Neg Pred Value : 0.8936
Prevalence : 0.3731
Detection Rate : 0.2985
Detection Prevalence : 0.2985
Balanced Accuracy : 0.9000
'Positive' Class : Malicious
With a specificity of 1 all the Normal profiles were correctly detected by the classifier. While, the Sensitiviy value was 0.8, which means that some of the the TP profile were not detected.
Distribution of False and True Positive
data_test_predic<-cbind(data_test,profilepredclass=predsrfsamp,profilepredprob=predsrprofilerobsamp)
profile_incorrect<-data_test_predic %>% filter(profile=='Normal' & profilepredclass=='Malicious')
profile_correct<-data_test_predic %>% filter(profile=='Normal' & profilepredclass=='Normal')
histogram(~ratio_avg_samedomain,data=profile_correct,main="Distribution of Avg_samedomain for True positives ")

#histogram(~ratio_avg_samedomain,data=profile_incorrect,main="Distribution of Avg_samedomain for False positives ")
histogram(~tot_requests,data=profile_correct,main="Distribution of Total Requests for True positives ")

#histogram(~tot_requests,data=profile_incorrect,main="Distribution of Total Requests for False positives")
Per request evaluation
Despites the good results of the client profile classifier showed in the previous secctions, we need to analize it in the context of the DNS request. Therefore, a per request evaluation is performed. THe approach followed for performing such evaluation was the following:
- We considered only the profiles in the testfile
- For each profile, we select all the DNS requests and labeled accordingly.
- For a profile labeled as Normal, all DNS requests are considered Normal
- For a profile labeled as DGA we label only those request labeled as DGA by the NN. The remaining Resquests are note labeled and considered Background
a<-request_labeled %>% group_by(client,profilenum) %>% summarise(n=n())
b<-data_test %>% group_by(client,profilenum) %>% summarise(n=sum(tot_requests))
as.data.frame(c(a,b)) %>% filter(n!=n.1)
legit_labeled %>% filter(dga.class==1)
request_labeled %>% filter(dga.class==1 & GroundTruthLabel=='Normal') %>% select(profilepredclass) %>% group_by(profilepredclass) %>% summarise(n=n())
request_labeled %>% filter(dga.class==1 & GroundTruthLabel=='Normal') %>% group_by(client,profilenum) %>% summarise(n=n())
dga_data_test %>% filter(profilenum==0)
Number of labeled requests: 7688. THe labels of the requests are distributed according to the Table below.
nlabels<-request_labeled%>% filter(!is.na(GroundTruthLabel)) %>% group_by(GroundTruthLabel) %>% summarise(total=n())
nlabels
As can be see, a total of 7609 Normal request are available while the total of Not Normal requests is79
NN per request Analysis
The distribution of the GroundTruthLabel classified as DGA by the NN is showed in the Table below.
nndetected<-request_labeled %>% filter(!is.na(GroundTruthLabel) & dga.class==1) %>% group_by(GroundTruthLabel, dga.class) %>% summarise(nntotal=n())
nndetected
In this case, the total of False Positive requests from NN classifier is 3031.
Client Profile per request Analysis
The distribution of the GroundTruthLabel classified as DGA by the Profile is showed in the Table below.
profiledetected<-request_labeled %>% filter(!is.na(GroundTruthLabel) & profilepredclass=='Malicious') %>% group_by(GroundTruthLabel, profilepredclass) %>% summarise(profiletotal=n())
profiledetected
In this case, the total of False Positive requests from profile classifier is 2655. That means the profile has reduced the number of False Positive in 376.
Such results are shown in the next barplot. In gray is the total amount of Normal record, in blue the total amount of Normal requests detected as DGA by the NN. Finally, in Orange the total amount of Malicious requests according to the client profile.
results<-cbind(nlabels,profiletotal=profiledetected$profiletotal,nntotal=nndetected$nntotal)
results<-results %>% mutate(GroundTruthLabel=ifelse(GroundTruthLabel=='Normal','Normal','Not Normal'))
ggplot(as.data.frame(results))+
geom_col(aes(x=GroundTruthLabel,y=total),fill='lightgray')+
geom_col(aes(x=GroundTruthLabel,y=nntotal),fill='skyblue',alpha=0.5)+
geom_col(aes(x=GroundTruthLabel,y=profiletotal),fill='orange',alpha=0.7)+
theme_bw()

Conclusions
In the first part of the evaluation, when we were only considering the client profiles, the classifier has correctly detected all the FP (i.e. Normal profile uncorrectly detected by the NN). An Specificity of 1 and a Sensitivity of 0.8 was showed. However, the Mcnemar’s Test P-Value resulted in 0.07364, that means results should not considered significant. Perhaps more Profile labeled are going to be needed.
Regarding the per request evaluation, in general the client profile has reduced the FP of the NN by only a ~15%. In those Normal profiles detected by the NN as DGA, the profile classifier has reduced considerable the FP. However, the problem seems to be in the case of those Malicious profiles. A malicious profile can have only a few malicious requests mixed with several normal requests. In those cases, obviously the profile classifier will classify everything as Malicious, resulting in a poor performance.
The mentioned problem seems to be a dificult one, since the profile is labeled as malicious if it contains at least only one confirmed DGA requests. It doesn’t matter if there ara housands of normal requests in the same profile. Moreover, the selected features allow the RandomFOrest algorithm to detect correctly the presence of this small malicious behavior in a profile, but it knows nothing about the remaining normal requests.
Given that the presence of DGA could be small during a 1 hour time period, a possible solution could be to use smaller windows.
saving the model
LS0tCnRpdGxlOiAiV0IgcHJvZmlsZSBjbGFzc2lmaWVyIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgpgYGB7ciwgaW5jbHVkZT1GQUxTRX0KbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkoY2FyZXQpCmxpYnJhcnkoZG9NQykKbGlicmFyeShwUk9DKQpsaWJyYXJ5KHBsb3RST0MpCnJlZ2lzdGVyRG9NQyhjb3Jlcz02KQpgYGAKCmBgYHtyLCBpbmNsdWRlPUZBTFNFfQoKcHJvZmlsZXM8LXJlYWQuY3N2KCIvaG9tZS9oYXJwby9Ecm9wYm94L29uZ29pbmctd29yay9naXQtcmVwb3MvZGdhLXdiL2RhdGFzZXRzL3Byb2ZpbGUvd2JvbmUtY2xpZW50LXByb2ZpbGUtMWgtbGFzdDhkYXlzLXVuaXF1ZS12Mi1maXJzdC01MDAwMC0wNC0wOS0yMDE3LmNzdiIsaGVhZGVyID0gVCkKY2xpZW50c19wcm9maWxlczwtcmVhZHI6OnJlYWRfY3N2KCIvaG9tZS9oYXJwby9Ecm9wYm94L29uZ29pbmctd29yay9naXQtcmVwb3MvZGdhLWFsZ29zL2RhdGEvd2JvbmUtY2xpZW50LXByb2ZpbGUtMWgtbGFzdDhkYXlzLXVuaXF1ZS12NC5jc3YiKQpwcm9maWxlc19maXhlZDwtcmlnaHRfam9pbihjbGllbnRzX3Byb2ZpbGVzICU+JSB0b3Bfbig1MDAwMCkgJT4lIHNlbGVjdChjbGllbnQsdG90X2RldGVjdGVkLHJhdGlvX2RldGVjdGVkLHByb2ZpbGVudW0pLHByb2ZpbGVzLGJ5PWMoImNsaWVudCIsInByb2ZpbGVudW0iKSkgJT4lICBzZWxlY3QoLXRvdF9kZXRlY3RlZC54LC1yYXRpb19kZXRlY3RlZC54KQpwcm9maWxlc19maXhlZDwtcmlnaHRfam9pbihwcm9maWxlcyAlPiUgdG9wX24oNTAwMDApICU+JSBzZWxlY3QoY2xpZW50LExhYmVsLERldGFpbHMscHJvZmlsZW51bSksY2xpZW50c19wcm9maWxlcyxieT1jKCJjbGllbnQiLCJwcm9maWxlbnVtIikpIAoKbmFtZXMocHJvZmlsZXMpCnN1bW1hcnkoY2xpZW50c19wcm9maWxlcykKcHJvZmlsZXNfZml4ZWQKCmBgYAojIyBHb2FsCgpSZWR1Y2UgdGhlIG51bWJlciBvZiBGYWxzZSBQb3NpdGl2ZSBnZW5lcmF0ZWQgYnkgdGhlIE5OIGNsYXNzaWZpZXIgYnkgdXNpbmcgYSBjbGllbnQgcHJvZmlsZS4gIAoKIyMgQXBwcm9hY2ggZm9yIHRyYWluaW5nIHRoZSBjbGFzc2lmaWVyCgojIyMgRmVhdHVyZXMKClRoZSBjbGllbnRlIHByb2ZpbGUgd2FzIGdlbmVyYXRlZCB1c2luZyB0aGUgZm9sbG93aW5nIGZlYXR1cmVzCgoqQ1VSUkVOVCBUSU1FIFdJTkRPVyogPSAxSAoKMS4gVG90YWwgbnVtYmVyIG9mIFJlcXVlc3RzCgoyLiBSYXRpbyBvZiBOWCByZXF1ZXN0cyAoYW5zd2VyPU5YKQoKMy4gcmF0aW8gb2YgTVggcmVxdWVzdCAocXVlcnlfdHlwZT0iTVgiKQoKNC4gUmF0aW8gb2YgUmV2ZXJzZSByZXF1ZXN0cyAoYW5zd2VyPWluLmFycGEpCgo1LiBSYXRpbyBvZiBGYWlsIFJlcXVlc3QgKGFuc3dlcj1TRVJWRkFJTCkKCjYuIFJhdGlvIG9mIHRoZSB0b3RhbCBhbW91bnQgb2YgZG9tYWlucyB0aGF0IGFuc3dlcmVkIHRvIG1vcmUgdGhhbiBvbmUgSVAgKGdpdmVuIGEgcXVlcnksIGNvdW50IHRoZSBudW1iZXIgb2YgZGlzdGluY3QgSVAgYWRkcmVzc2VzLCB1c2luZyB0aGUgYW5zd2VyX2lwIGZpZWxkIGFuZCBzdW0gdGhlbSkKCjcuIFJhdGlvIG9mIHRoZSBBdmVyYWdlIGFtb3VudCBvZiBkb21haW5zIHRoYXQgYW5zd2VyZWQgdG8gbW9yZSB0aGFuIG9uZSAoY2FsY3VsYXRlIHRoZSBhdmVyYWdlIG92ZXIgYWxsIHRoZSBxdWVyaWVzIHRoYXQgaGF2ZSBkaXN0aW5jdCBJUCBhZGRyZXNzZXMuIEluIG90aGVyIHdvcmRzIGluc3RlYWQgb2Ygc3VtIHRoZW0sIGp1c3QgY2FsY3VhdGUgdGhlIGF2ZXJhZ2UpCgo4LiBSYXRpbyBvZiB0aGUgdG90YWwgYW1vdW50IG9mIElQIHRoYXQgY29ycmVzcG9uZCB0byBtb3JlIHRoYW4gb25lIERvbWFpbiBuYW1lIChHaXZlbiBhbiBhbnN3ZXJfaXAgZmllbGQsIGNvdW50IHRoZSBudW1iZXIgZGlzdGluY3QgcXVlcmllcyBmb3IgdGhpcyBhbnN3ZXJfaXAgYW5kIHN1bSB0aGVtKQoKOS4gUmF0aW8gb2YgdGhlIEF2ZXJhZ2UgYW1vdW50IG9mIElQIGFkZHJlc3NlcyB0aGF0IGNvcnJlc3BvbmQgdG8gbW9yZSB0aGFuIG9uZSBEb21haW4gbmFtZS4gKENhbGN1bGF0ZSB0aGUgYXZlcmFnZSBvdmVyIGFsbCB0aGUgYW5zd2VyX2lwIHRoYXQgaGF2ZSBkaXN0aW5jdCBxdWVyaWVzLiBJbiBvdGhlciB3b3JkLCBpbnN0ZWFkIG9mIHN1bSB0aGVtLCBqdXN0IGNhbGN1bGF0ZSB0aGUgYXZlcmFnZSkKCipOb3RlOiogaW4gYWxsIGNhc2VzIHJhdGlvIGlzIGNhbGN1bGF0ZWQgb3ZlciB0aGUgVG90YWwgTnVtYmVyIG9mIHJlcXVlc3RzIChGZWF0dXJlIDEpCgojIyMgVHJhaW5pbmcgYXBwcm9hY2gKVGhlIHRyYWluaW5nIGRhdGFzZXQgY29udGFpbnMgb25seSB0aG9zZSBjbGllbnQgcHJvZmlsZXMgbGFiZWxlZCBhcyAqTm9ybWFsKiB3aXRoIGF0IGxlYXN0IG9uZSByZXF1ZXN0IGRldGVjdGVkIGJ5IHRoZSBOTi4gVGhhdCBpcywgdGhvc2UgcHJvZmlsZXMgdGhhdCBjb250YWlucyAqRmFsc2UgUG9zaXRpdmVzKi4gVEhlIHJlc3VsdGluZyB0cmFpbmluZyBkYXRhc2V0IGNvbnRhaW5zIGByIG5yb3cocHJvZmlsZXNfbGFiZWxlZCApYCBjbGllbnQgcHJvZmlsZXMuCgpUaGUgdGFibGUgYmVsb3cgc2hvd3MgdGhlIEdyb3VuZFRydXRoTGFiZWwgKExhYmVsKSBhbmQgdGhlIChOTikgTGFiZWwgKGkuZS4gdGhvc2UgcHJvZmlsZSB3aXRoIGF0IGxlYXN0IDEgTk4gZGV0ZWN0aW9uIGFyZSBsYWJlbGVkIGFzIERHQSkuIEZvciBzaW1wbGl0aXkgYSB0aGlyZCBsYWJlbCBjYWxsZWQgKHByb2ZpbGUpIGlzIGFkZGVkIGZvciBhZ2dyZWdhdGluZyBhbGwgdGhvc2UgR3JvdW5kVHJ1aExhYmVscyBub3QgTm9ybWFsLgpgYGB7cn0KCiNwcm9maWxlc19sYWJlbGVkPC0gcHJvZmlsZXNfbGFiZWxlZCAlPiUgZmlsdGVyKExhYmVsIT0iIikgJT4lIG11dGF0ZShub3JtYWw9aWZlbHNlKGdyZXBsKCJOb3JtYWwiLExhYmVsKSwieWVzIiwibm8iKSkKcHJvZmlsZXNfbGFiZWxlZDwtIHByb2ZpbGVzX2ZpeGVkICU+JSBmaWx0ZXIoTGFiZWwhPSIiKSAlPiUgbXV0YXRlKG5uPWlmZWxzZSh0b3RfZGV0ZWN0ZWQ+PTEsIkRHQSIsIk5vcm1hbCIpKQpwcm9maWxlc19sYWJlbGVkPC0gcHJvZmlsZXNfbGFiZWxlZCAlPiUgZmlsdGVyKHRvdF9kZXRlY3RlZD4xKSAlPiUgZmlsdGVyKExhYmVsIT0iIikgJT4lIG11dGF0ZShwcm9maWxlPWlmZWxzZShncmVwbCgiTm9ybWFsIixMYWJlbCkgJiB0b3RfZGV0ZWN0ZWQ+PTEsIk5vcm1hbCIsIk1hbGljaW91cyIpKQoKcHJvZmlsZXNfbGFiZWxlZCAlPiUgc2VsZWN0KGNsaWVudCxMYWJlbCxwcm9maWxlLHRvdF9yZXF1ZXN0cyx0b3RfZGV0ZWN0ZWQscmF0aW9fZGV0ZWN0ZWQscmF0aW9fbngscmF0aW9fbXgscmF0aW9fcmV2ZXJzZSxyYXRpb190b3Rfc2FtZWRvbWFpbixyYXRpb190b3Rfc2FtZWlwLHJhdGlvX2F2Z19zYW1lZG9tYWluLHJhdGlvX2F2Z19zYW1laXApICU+JQogIGdyb3VwX2J5KHByb2ZpbGUpICU+JSBzdW1tYXJpc2Uobj1uKCkpCgpwcm9maWxlc19sYWJlbGVkICAlPiUgZ3JvdXBfYnkobm4sTGFiZWwscHJvZmlsZSkgJT4lIHN1bW1hcmlzZShuPW4oKSkKcmVhZHI6OndyaXRlX2Nzdihwcm9maWxlc19sYWJlbGVkICU+JSBmaWx0ZXIodG90X2RldGVjdGVkPjEpLHBhdGg9Ii9ob21lL2hhcnBvL0Ryb3Bib3gvb25nb2luZy13b3JrL2dpdC1yZXBvcy9kZ2Etd2IvcHJvZmlsZS1jbGFzc2lmaWVyL2RhdGEvcHJvZmlsZV90cmFpbl9kYXRhLmNzdiIpCmBgYAoKIyMgU2VwYXJhdGluZyBpbiB0cmFpbmluZyBhbmQgdGVzdGluZyBTZXRzCgpXZSB1c2VkIGEgMng1IENyb3NzIFZhbGlkYXRpb24gYXBwcm9hY2guIFRoZSBST0MgbWV0cmljIHdhcyB1c2VkICBmb3IgZmluZGluZyB0aGUgYmVzdCBtb2RlbC4KCmBgYHtyfQpjdHJsX2Zhc3QgPC0gdHJhaW5Db250cm9sKG1ldGhvZD0iY3YiLCAKICAgICAgICAgICAgICAgICAgICAgcmVwZWF0cz0yLAogICAgICAgICAgICAgICAgICAgICBudW1iZXI9NSwgCiAgICAgICAgICAgICAgICAgICAgIHN1bW1hcnlGdW5jdGlvbj10d29DbGFzc1N1bW1hcnksCiAgICAgICAgICAgICAgICAgICAgIHZlcmJvc2VJdGVyPVQsCiAgICAgICAgICAgICAgICAgICAgIGNsYXNzUHJvYnM9VFJVRSwKICAgICAgICAgICAgICAgICAgICAgYWxsb3dQYXJhbGxlbCA9IFRSVUUpICAKYGBgCgo3MCUgb2YgdGhlIGRhdGFzZXQgd2FzIHVzZWQgZm9yIHRyYWluaW5nIGFuZCBhIDMwJSBmb3IgdGVzdGluZy4KYGBge3IsIGluY2x1ZGU9RkFMU0V9CnNldC5zZWVkKDEwMDEyMSkKdHJhaW5zZXQ8LXByb2ZpbGVzX2xhYmVsZWQgCnRyYWluc2V0JHByb2ZpbGU8LWFzLmZhY3Rvcih0cmFpbnNldCRwcm9maWxlKQp0cmFpbkluZGV4IDwtIGNyZWF0ZURhdGFQYXJ0aXRpb24odHJhaW5zZXQkcHJvZmlsZSwgcD0wLjcwLCBsaXN0PUZBTFNFKQpkYXRhX3RyYWluIDwtIHRyYWluc2V0WyB0cmFpbkluZGV4LF0KZGF0YV90ZXN0IDwtICB0cmFpbnNldFstdHJhaW5JbmRleCxdCnRyYWluX2Zvcm11bGE8LWZvcm11bGEocHJvZmlsZX50b3RfcmVxdWVzdHMrcmF0aW9fbngrcmF0aW9fbXgrcmF0aW9fcmV2ZXJzZStyYXRpb19mYWlsK3JhdGlvX3RvdF9zYW1lZG9tYWluK3JhdGlvX3RvdF9zYW1laXArcmF0aW9fYXZnX3NhbWVkb21haW4rcmF0aW9fYXZnX3NhbWVpcCtyYXRpb19kZXRlY3RlZCkKYGBgCgpCZWxvdyB3ZSBzaG93IHRoZSBkaXN0cmlidXRpb24gb2YgdGhlIHByb2ZpbGUgbGFiZWxzIGluIHRoZSByZXN1bHRpbmcgMiBkYXRhc2V0cyAodHJhaW5pbmcsdGVzdGluZykKYGBge3J9CmRhdGFfdHJhaW4gJT4lIGdyb3VwX2J5KHByb2ZpbGUpICU+JSBzdW1tYXJpc2UodG90YWw9bigpKQpkYXRhX3Rlc3QgJT4lIGdyb3VwX2J5KHByb2ZpbGUpICU+JSBzdW1tYXJpc2UodG90YWw9bigpKQpgYGAKCkEgcmFuZG9tRm9yZXN0IGNsYXNzaWZpZXIgaXMgdHJhaW5pbmcgdXNpbmcgUk9DIG1ldHJpYyBmb3IgZmluZGluZyB0aGUgYmVzdCBNb2RlbC4gUHJpb3IgdHJhaW5pbmcgdGhlIHRyYWluaW5nIGRhdGFzZXQgd2FzIFJhbmRvbWx5IFVwc2FtcGxlZApgYGB7cn0KY3RybF9mYXN0JHNhbXBsaW5nPC0idXAiCnJmRml0PC0gdHJhaW4odHJhaW5fZm9ybXVsYSwKICAgICAgICAgICAgICAgZGF0YSA9IGRhdGFfdHJhaW4sCiMgICAgICAgICAgICAgICBtZXRob2QgPSAic3ZtUmFkaWFsV2VpZ2h0cyIsICAgIyBSYWRpYWwga2VybmVsCiAgICAgICAgICAgICAgIG1ldGhvZCA9ICJyZiIsCiAgICAgICAgICAgICAgIHR1bmVMZW5ndGggPSA1LAogICAgICAgICAgICAgICAjdHVuZUdyaWQgPSBzdm1HcmlkLAogICAgICAgICAgICAgICAjcHJlUHJvY2Vzcz1jKCJzY2FsZSIsImNlbnRlciIpLAogICAgICAgICAgICAgICBtZXRyaWM9IlJPQyIsCiAgICAgICAgICAgICAgICN3ZWlnaHRzID0gbW9kZWxfd2VpZ2h0cywKICAgICAgICAgICAgICAgdHJDb250cm9sID0gY3RybF9mYXN0KQoKcmZGaXQKYGBgCgpUaGUgQ29uZnVzaW9uIG1hdHJpeCBmb3IgcmVzdWx0aW5nIG1vZGVsIG9uIHRoZSAqVHJhaW5pbmcgc2V0KiBpcyBzaG93biBiZWxvdy4KYGBge3J9CnJmRml0JGZpbmFsTW9kZWwKYGBgCgojIyBNb3N0IHJlbGV2YW50IGZlYXR1cmVzClRIZSBsaXN0IG9mIHRoZSBtb3N0IHJlbGV2YW50IGZlYXR1cmVzIHVzZWQgYnkgdGhlIFJhbmRvbUZvcmVzdCBDbGFzc2lmaWVyCmBgYHtyfQp2YXJJbXAocmZGaXQsIHNjYWxlID0gRikKYGBgCgoKIyMgUk9DIEN1cnZlIEFuYWx5c2lzOgoKVGhlIHJlc3VsdGluZyBST0MgY3VydmVzIHNob3dlZCB0aGF0IGEgdGhyZXNob2xkIGJldHdlZW4gMC44IGFuZCAwLjkgYXJlIHRoZSBiZXN0IG9wdGlvbnMgZm9yIGRldGVjdGluZyB0aGUgbW9zdCBvZiB0aGUgTm9ybWFsIFByb2ZpbGVzIChpLmUgRmFsc2UgTmVnYXRpdmVzKSB3aGlsZSBrZWVwaW5nIGEgZ29vZCByYXRlIG9mIFRydWUgUG9zaXRpdmUgKGkuZS4gdGhvc2UgcHJvZmlsZXMgbGFiZWxlZCBhc01hbGljaW91cykKCmBgYHtyfQojcGxvdChyb2MoZGF0YV90ZXN0JHByb2ZpbGUscHJlZHNycHJvZmlsZXJvYnNhbXAkTWFsaWNpb3VzKSkKZ2dwbG90KGNiaW5kKHByZWRzcnByb2ZpbGVyb2JzYW1wLGNsYXNzPWRhdGFfdGVzdCRwcm9maWxlKSwgCiAgICAgICBhZXMobSA9IE5vcm1hbCwgZCA9IGZhY3RvcihjbGFzcywgbGFiZWxzPWMoIk5vcm1hbCIsIk1hbGljaW91cyIpLGxldmVscyA9IGMoIk5vcm1hbCIsICJNYWxpY2lvdXMiKSkpKSArIAogICAgZ2VvbV9yb2MoaGp1c3QgPSAtMC40LCB2anVzdCA9IDEuNSxjb2xvdXI9J29yYW5nZScpICsgCiAgdGhlbWVfYncoKQoKYGBgCgoKIyMgRXZhbHVhdGlvbiBvbiBUZXN0aW5nIEZpbGUKClRoZSBDb25mdXNpb24gbWF0cml4IGZvciB0aGUgcmVzdWx0aW5nIG1vZGVsIG9uIHRoZSAqVGVzdGluZyBzZXQqIGlzIHNob3duIGJlbG93OgoKYGBge3J9CnByZWRzcnByb2ZpbGVyb2JzYW1wPXByZWRpY3QocmZGaXQsZGF0YV90ZXN0LHR5cGU9J3Byb2InKQpwcmVkc3Jmc2FtcD1pZmVsc2UocHJlZHNycHJvZmlsZXJvYnNhbXAkTWFsaWNpb3VzID49MC45LCdNYWxpY2lvdXMnLCdOb3JtYWwnKQpjbTwtY29uZnVzaW9uTWF0cml4KHByZWRzcmZzYW1wLGRhdGFfdGVzdCRwcm9maWxlLHBvc2l0aXZlPSJNYWxpY2lvdXMiKQpjbQpgYGAKCldpdGggYSBzcGVjaWZpY2l0eSBvZiAqYHIgY20kYnlDbGFzc1syXWAqIGFsbCB0aGUgTm9ybWFsIHByb2ZpbGVzIHdlcmUgY29ycmVjdGx5IGRldGVjdGVkIGJ5IHRoZSBjbGFzc2lmaWVyLiBXaGlsZSwgdGhlICBTZW5zaXRpdml5IHZhbHVlIHdhcyAqYHIgY20kYnlDbGFzc1sxXWAqLCB3aGljaCBtZWFucyB0aGF0IHNvbWUgb2YgdGhlICB0aGUgVFAgcHJvZmlsZSB3ZXJlIG5vdCBkZXRlY3RlZC4KCiMjIERpc3RyaWJ1dGlvbiBvZiAgRmFsc2UgYW5kIFRydWUgUG9zaXRpdmUKYGBge3J9CmRhdGFfdGVzdF9wcmVkaWM8LWNiaW5kKGRhdGFfdGVzdCxwcm9maWxlcHJlZGNsYXNzPXByZWRzcmZzYW1wLHByb2ZpbGVwcmVkcHJvYj1wcmVkc3Jwcm9maWxlcm9ic2FtcCkKcHJvZmlsZV9pbmNvcnJlY3Q8LWRhdGFfdGVzdF9wcmVkaWMgJT4lIGZpbHRlcihwcm9maWxlPT0nTm9ybWFsJyAmIHByb2ZpbGVwcmVkY2xhc3M9PSdNYWxpY2lvdXMnKQpwcm9maWxlX2NvcnJlY3Q8LWRhdGFfdGVzdF9wcmVkaWMgJT4lIGZpbHRlcihwcm9maWxlPT0nTm9ybWFsJyAmIHByb2ZpbGVwcmVkY2xhc3M9PSdOb3JtYWwnKQoKaGlzdG9ncmFtKH5yYXRpb19hdmdfc2FtZWRvbWFpbixkYXRhPXByb2ZpbGVfY29ycmVjdCxtYWluPSJEaXN0cmlidXRpb24gb2YgQXZnX3NhbWVkb21haW4gZm9yIFRydWUgcG9zaXRpdmVzICIpCiNoaXN0b2dyYW0ofnJhdGlvX2F2Z19zYW1lZG9tYWluLGRhdGE9cHJvZmlsZV9pbmNvcnJlY3QsbWFpbj0iRGlzdHJpYnV0aW9uIG9mIEF2Z19zYW1lZG9tYWluIGZvciBGYWxzZSBwb3NpdGl2ZXMgIikKCmhpc3RvZ3JhbSh+dG90X3JlcXVlc3RzLGRhdGE9cHJvZmlsZV9jb3JyZWN0LG1haW49IkRpc3RyaWJ1dGlvbiBvZiBUb3RhbCBSZXF1ZXN0cyBmb3IgVHJ1ZSBwb3NpdGl2ZXMgIikKI2hpc3RvZ3JhbSh+dG90X3JlcXVlc3RzLGRhdGE9cHJvZmlsZV9pbmNvcnJlY3QsbWFpbj0iRGlzdHJpYnV0aW9uIG9mIFRvdGFsIFJlcXVlc3RzIGZvciBGYWxzZSBwb3NpdGl2ZXMiKQpgYGAKCgojIyAgUGVyIHJlcXVlc3QgZXZhbHVhdGlvbgoKRGVzcGl0ZXMgdGhlIGdvb2QgcmVzdWx0cyBvZiB0aGUgY2xpZW50IHByb2ZpbGUgY2xhc3NpZmllciBzaG93ZWQgaW4gdGhlIHByZXZpb3VzIHNlY2N0aW9ucywgd2UgbmVlZCB0byBhbmFsaXplIGl0IGluIHRoZSBjb250ZXh0IG9mIHRoZSBETlMgcmVxdWVzdC4gVGhlcmVmb3JlLCBhIHBlciByZXF1ZXN0IGV2YWx1YXRpb24gaXMgcGVyZm9ybWVkLiBUSGUgYXBwcm9hY2ggZm9sbG93ZWQgZm9yIHBlcmZvcm1pbmcgc3VjaCBldmFsdWF0aW9uIHdhcyB0aGUgZm9sbG93aW5nOgoKMS4gV2UgY29uc2lkZXJlZCBvbmx5IHRoZSBwcm9maWxlcyBpbiB0aGUgdGVzdGZpbGUKMi4gRm9yIGVhY2ggcHJvZmlsZSwgd2Ugc2VsZWN0IGFsbCB0aGUgRE5TIHJlcXVlc3RzIGFuZCBsYWJlbGVkIGFjY29yZGluZ2x5LiAKMy4gRm9yIGEgcHJvZmlsZSBsYWJlbGVkIGFzICpOb3JtYWwqLCBhbGwgRE5TIHJlcXVlc3RzIGFyZSBjb25zaWRlcmVkICpOb3JtYWwqCjQuIEZvciBhIHByb2ZpbGUgbGFiZWxlZCBhcyAqREdBKiB3ZSBsYWJlbCBvbmx5IHRob3NlIHJlcXVlc3QgbGFiZWxlZCBhcyBER0EgYnkgdGhlIE5OLiBUaGUgcmVtYWluaW5nIFJlc3F1ZXN0cyBhcmUgbm90ZSBsYWJlbGVkIGFuZCBjb25zaWRlcmVkICpCYWNrZ3JvdW5kKgoKYGBge3IsIGV2YWw9RkFMU0UsIGluY2x1ZGU9RkFMU0V9CmxpYnJhcnkobHVicmlkYXRlKQpkb21haW5zPC1yZWFkcjo6cmVhZF9jc3YoZmlsZT0iL2hvbWUvaGFycG8vRHJvcGJveC9vbmdvaW5nLXdvcmsvZ2l0LXJlcG9zL2RnYS13Yi9kYXRhc2V0cy9kYXRhYmFzZS0wOC0yMDE3LmNzdi5neiIscHJvZ3Jlc3MgPSBUKQpgYGAKCmBgYHtyLCBldmFsPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQpkYXRhX3Rlc3RfcHJlZGljICU+JSBncm91cF9ieShMYWJlbCkgJT4lIHN1bW1hcmlzZShuPW4oKSkKbm9ybWFsX2RhdGFfdGVzdDwtZGF0YV90ZXN0X3ByZWRpYyAlPiUgZmlsdGVyKGdyZXBsKCdOb3JtYWwnLExhYmVsKSkgJT4lIHNlbGVjdChjbGllbnQscHJvZmlsZW51bSxjdXJyZW50dG0scHJvZmlsZXByZWRjbGFzcyxwcm9maWxlcHJlZHByb2IuTWFsaWNpb3VzLExhYmVsKSAKbGVnaXRfbGFiZWxlZD1jKCkKZm9yIChsaW5lIGluIHNlcSgxLG5yb3cobm9ybWFsX2RhdGFfdGVzdCkpICl7CiAgICAgY2xpZW50X2lwPC1ub3JtYWxfZGF0YV90ZXN0W2xpbmUsXSRjbGllbnQKICAgICBjbGllbnRfY3VycmVudHRtPC1ub3JtYWxfZGF0YV90ZXN0W2xpbmUsXSRjdXJyZW50dG0KICAgICBjbGllbnRfcHJvZmlsZW51bTwtbm9ybWFsX2RhdGFfdGVzdFtsaW5lLF0kcHJvZmlsZW51bQogICAgIGNsaWVudF9wcm9maWxlcHJlZGNsYXNzPC1ub3JtYWxfZGF0YV90ZXN0W2xpbmUsXSRwcm9maWxlcHJlZGNsYXNzCiAgICAgY2xpZW50X3Byb2ZpbGVwcmVkcHJvYk1hbGljaW91czwtbm9ybWFsX2RhdGFfdGVzdFtsaW5lLF0kcHJvZmlsZXByZWRwcm9iLk1hbGljaW91cwogICAgIGNsaWVudF9wcm9maWxlTGFiZWw8LWRnYV9kYXRhX3Rlc3RbbGluZSxdJExhYmVsCiAgICAgbGVnaXRfbGFiZWxlZD1yYmluZChsZWdpdF9sYWJlbGVkLGRvbWFpbnMgJT4lIGZpbHRlcihjbGllbnQ9PWNsaWVudF9pcCAmIAogICAgIHRpbWVzdGFtcD5jbGllbnRfY3VycmVudHRtLW1pbnV0ZXMoNjApICYgCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHRpbWVzdGFtcCA8IGNsaWVudF9jdXJyZW50dG0gKSU+JSBtdXRhdGUoR3JvdW5kVHJ1dGhMYWJlbD0iTm9ybWFsIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwcm9maWxlbnVtPWNsaWVudF9wcm9maWxlbnVtLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHByb2ZpbGVwcmVkY2xhc3M9Y2xpZW50X3Byb2ZpbGVwcmVkY2xhc3MsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcHJvZmlsZXByZWRwcm9iTWFsaWNpb3VzPWNsaWVudF9wcm9maWxlcHJlZHByb2JNYWxpY2lvdXMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcHJvZmlsZUxhYmVsPWNsaWVudF9wcm9maWxlTGFiZWwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICApKQp9CmBgYAoKYGBge3IsIGV2YWw9RkFMU0UsIGluY2x1ZGU9RkFMU0V9CmRnYTFfZmlsZTwtcmVhZF9jc3YoZmlsZT0nL2hvbWUvaGFycG8vRHJvcGJveC9vbmdvaW5nLXdvcmsvZ2l0LXJlcG9zL2RnYS13Yi9wcm9maWxlLWNsYXNzaWZpZXIvZGF0YS8xODguOTUuNTYuMy0xLmNzdi5iejInKQpkZ2EyX2ZpbGU8LXJlYWRfY3N2KGZpbGU9Jy9ob21lL2hhcnBvL0Ryb3Bib3gvb25nb2luZy13b3JrL2dpdC1yZXBvcy9kZ2Etd2IvcHJvZmlsZS1jbGFzc2lmaWVyL2RhdGEvMjEzLjgxLjIxNC4xMzMtMS5jc3YuYnoyJykKZGdhM19maWxlPC1yZWFkX2NzdihmaWxlPScvaG9tZS9oYXJwby9Ecm9wYm94L29uZ29pbmctd29yay9naXQtcmVwb3MvZGdhLXdiL3Byb2ZpbGUtY2xhc3NpZmllci9kYXRhLzc3LjI0MC4xMDMuMi0xLmNzdi5iejInKQpkZ2E0X2ZpbGU8LXJlYWRfY3N2KGZpbGU9Jy9ob21lL2hhcnBvL0Ryb3Bib3gvb25nb2luZy13b3JrL2dpdC1yZXBvcy9kZ2Etd2IvcHJvZmlsZS1jbGFzc2lmaWVyL2RhdGEvNzcuMjQwLjEwMy4yLTIuY3N2LmJ6MicpCmRnYTVfZmlsZTwtcmVhZF9jc3YoZmlsZT0nL2hvbWUvaGFycG8vRHJvcGJveC9vbmdvaW5nLXdvcmsvZ2l0LXJlcG9zL2RnYS13Yi9wcm9maWxlLWNsYXNzaWZpZXIvZGF0YS83Ny4yNDAuMTAzLjItMy5jc3YuYnoyJykKZGdhNl9maWxlPC1yZWFkX2NzdihmaWxlPScvaG9tZS9oYXJwby9Ecm9wYm94L29uZ29pbmctd29yay9naXQtcmVwb3MvZGdhLXdiL3Byb2ZpbGUtY2xhc3NpZmllci9kYXRhLzEwLjEuMi4xNDUtMS5jc3YuYnoyJykKZGdhN19maWxlPC1yZWFkX2NzdihmaWxlPScvaG9tZS9oYXJwby9Ecm9wYm94L29uZ29pbmctd29yay9naXQtcmVwb3MvZGdhLXdiL3Byb2ZpbGUtY2xhc3NpZmllci9kYXRhLzE4OC45NS41Ni4zLTIuY3N2LmJ6MicpCmRnYThfZmlsZTwtcmVhZF9jc3YoZmlsZT0nL2hvbWUvaGFycG8vRHJvcGJveC9vbmdvaW5nLXdvcmsvZ2l0LXJlcG9zL2RnYS13Yi9wcm9maWxlLWNsYXNzaWZpZXIvZGF0YS8xODguOTUuNTYuMy0zLmNzdi5iejInKQpkb21haW5zX2RnYTwtcmJpbmQoZGdhMV9maWxlLGRnYTJfZmlsZSxkZ2EzX2ZpbGUsZGdhNF9maWxlLGRnYTVfZmlsZSxkZ2E2X2ZpbGUsZGdhN19maWxlLGRnYThfZmlsZSkgJT4lIHVuaXF1ZSgpCmBgYAoKYGBge3IsIGV2YWw9RkFMU0UsIGluY2x1ZGU9RkFMU0V9CiMgcHJvZmlsZSBkYXRhIHdpdGggbWFsaWNpb3VzIHJlcXVlc3RzCmRnYV9kYXRhX3Rlc3Q8LWRhdGFfdGVzdF9wcmVkaWMgJT4lIGZpbHRlcighZ3JlcGwoJ05vcm1hbCcsTGFiZWwpKSAlPiUgc2VsZWN0KGNsaWVudCxwcm9maWxlbnVtLGN1cnJlbnR0bSxwcm9maWxlcHJlZGNsYXNzLHByb2ZpbGVwcmVkcHJvYi5NYWxpY2lvdXMsTGFiZWwpCmRnYV9sYWJlbGVkPWMoKQpmb3IgKGxpbmUgaW4gc2VxKDEsbnJvdyhkZ2FfZGF0YV90ZXN0KSkgKXsKICAgICBjbGllbnRfaXA8LWRnYV9kYXRhX3Rlc3RbbGluZSxdJGNsaWVudAogICAgIGNsaWVudF9jdXJyZW50dG08LWRnYV9kYXRhX3Rlc3RbbGluZSxdJGN1cnJlbnR0bQogICAgIGNsaWVudF9wcm9maWxlbnVtPC1kZ2FfZGF0YV90ZXN0W2xpbmUsXSRwcm9maWxlbnVtCiAgICAgY2xpZW50X3Byb2ZpbGVwcmVkY2xhc3M8LWRnYV9kYXRhX3Rlc3RbbGluZSxdJHByb2ZpbGVwcmVkY2xhc3MKICAgICBjbGllbnRfcHJvZmlsZXByZWRwcm9iTWFsaWNpb3VzPC1kZ2FfZGF0YV90ZXN0W2xpbmUsXSRwcm9maWxlcHJlZHByb2IuTWFsaWNpb3VzCiAgICAgY2xpZW50X3Byb2ZpbGVMYWJlbDwtZGdhX2RhdGFfdGVzdFtsaW5lLF0kTGFiZWwKICAgICAKICAgICBiYWNrZ3JvdW5kPC1kb21haW5zICU+JSBmaWx0ZXIoY2xpZW50PT1jbGllbnRfaXAgJiB0aW1lc3RhbXAgPiBjbGllbnRfY3VycmVudHRtLW1pbnV0ZXMoNjApICYgdGltZXN0YW1wIDxjbGllbnRfY3VycmVudHRtKSAlPiV1bmlxdWUoKQogICAgIGRnYTwtZG9tYWluc19kZ2EgICAgJT4lIGZpbHRlcihjbGllbnQ9PWNsaWVudF9pcCAmIHRpbWVzdGFtcCA+IGNsaWVudF9jdXJyZW50dG0tbWludXRlcyg2MCkgJiB0aW1lc3RhbXAgPGNsaWVudF9jdXJyZW50dG0pIAogICAgIAogICAgIGRnYV9sYWJlbGVkPXJiaW5kKGRnYV9sYWJlbGVkLGZ1bGxfam9pbihiYWNrZ3JvdW5kLGRnYSAlPiUgc2VsZWN0KGNsaWVudCx0aW1lc3RhbXAsYW5zd2VyX2lwLEdyb3VuZFRydXRoTGFiZWwpLGJ5PWMoImNsaWVudCIsInRpbWVzdGFtcCIsImFuc3dlcl9pcCIpKQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJT4lIG11dGF0ZSgKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHByb2ZpbGVudW09Y2xpZW50X3Byb2ZpbGVudW0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwcm9maWxlcHJlZGNsYXNzPWNsaWVudF9wcm9maWxlcHJlZGNsYXNzLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcHJvZmlsZXByZWRwcm9iTWFsaWNpb3VzPWNsaWVudF9wcm9maWxlcHJlZHByb2JNYWxpY2lvdXMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwcm9maWxlTGFiZWw9Y2xpZW50X3Byb2ZpbGVMYWJlbAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICkpCn0KZGdhX2xhYmVsZWQgJT4lIGdyb3VwX2J5KEdyb3VuZFRydXRoTGFiZWwpICU+JSBzdW1tYXJpc2Uobj1uKCkpCmxlZ2l0X2xhYmVsZWQgJT4lIGdyb3VwX2J5KEdyb3VuZFRydXRoTGFiZWwpICU+JSBzdW1tYXJpc2Uobj1uKCkpCnJlcXVlc3RfbGFiZWxlZDwtcmJpbmQoZGdhX2xhYmVsZWQsbGVnaXRfbGFiZWxlZCkKYGBgCgoKYGBge3J9CmE8LXJlcXVlc3RfbGFiZWxlZCAlPiUgZ3JvdXBfYnkoY2xpZW50LHByb2ZpbGVudW0pICU+JSBzdW1tYXJpc2Uobj1uKCkpCmI8LWRhdGFfdGVzdCAlPiUgZ3JvdXBfYnkoY2xpZW50LHByb2ZpbGVudW0pICU+JSBzdW1tYXJpc2Uobj1zdW0odG90X3JlcXVlc3RzKSkKYXMuZGF0YS5mcmFtZShjKGEsYikpICU+JSBmaWx0ZXIobiE9bi4xKQoKbGVnaXRfbGFiZWxlZCAlPiUgZmlsdGVyKGRnYS5jbGFzcz09MSkKcmVxdWVzdF9sYWJlbGVkICU+JSBmaWx0ZXIoZGdhLmNsYXNzPT0xICAmIEdyb3VuZFRydXRoTGFiZWw9PSdOb3JtYWwnKSAlPiUgc2VsZWN0KHByb2ZpbGVwcmVkY2xhc3MpICU+JSBncm91cF9ieShwcm9maWxlcHJlZGNsYXNzKSAlPiUgc3VtbWFyaXNlKG49bigpKQoKCnJlcXVlc3RfbGFiZWxlZCAlPiUgZmlsdGVyKGRnYS5jbGFzcz09MSAgJiBHcm91bmRUcnV0aExhYmVsPT0nTm9ybWFsJykgICU+JSBncm91cF9ieShjbGllbnQscHJvZmlsZW51bSkgJT4lIHN1bW1hcmlzZShuPW4oKSkKZGdhX2RhdGFfdGVzdCAlPiUgZmlsdGVyKHByb2ZpbGVudW09PTApCmBgYCAgCgoKTnVtYmVyIG9mIGxhYmVsZWQgcmVxdWVzdHM6IGByIG5yb3cocmVxdWVzdF9sYWJlbGVkICU+JSBmaWx0ZXIoIWlzLm5hKEdyb3VuZFRydXRoTGFiZWwpKSlgLiBUSGUgbGFiZWxzIG9mIHRoZSByZXF1ZXN0cyBhcmUgZGlzdHJpYnV0ZWQgYWNjb3JkaW5nIHRvIHRoZSBUYWJsZSBiZWxvdy4KYGBge3J9Cm5sYWJlbHM8LXJlcXVlc3RfbGFiZWxlZCU+JSBmaWx0ZXIoIWlzLm5hKEdyb3VuZFRydXRoTGFiZWwpKSAlPiUgZ3JvdXBfYnkoR3JvdW5kVHJ1dGhMYWJlbCkgJT4lIHN1bW1hcmlzZSh0b3RhbD1uKCkpIApubGFiZWxzCmBgYApBcyBjYW4gYmUgc2VlLCBhIHRvdGFsIG9mIGByIG5yb3cocmVxdWVzdF9sYWJlbGVkICU+JSBmaWx0ZXIoR3JvdW5kVHJ1dGhMYWJlbD09J05vcm1hbCcpKWAgTm9ybWFsIHJlcXVlc3QgYXJlIGF2YWlsYWJsZSB3aGlsZSB0aGUgdG90YWwgb2YgTm90IE5vcm1hbCByZXF1ZXN0cyBpc2ByIG5yb3cocmVxdWVzdF9sYWJlbGVkICU+JSBmaWx0ZXIoR3JvdW5kVHJ1dGhMYWJlbCE9J05vcm1hbCcpKWAKCiMjIyBOTiBwZXIgcmVxdWVzdCBBbmFseXNpcwpUaGUgIGRpc3RyaWJ1dGlvbiBvZiB0aGUgR3JvdW5kVHJ1dGhMYWJlbCBjbGFzc2lmaWVkIGFzIERHQSBieSB0aGUgTk4gaXMgc2hvd2VkIGluIHRoZSBUYWJsZSBiZWxvdy4KYGBge3J9Cm5uZGV0ZWN0ZWQ8LXJlcXVlc3RfbGFiZWxlZCAlPiUgIGZpbHRlcighaXMubmEoR3JvdW5kVHJ1dGhMYWJlbCkgJiBkZ2EuY2xhc3M9PTEpICU+JSBncm91cF9ieShHcm91bmRUcnV0aExhYmVsLCBkZ2EuY2xhc3MpICU+JSBzdW1tYXJpc2Uobm50b3RhbD1uKCkpIApubmRldGVjdGVkCmBgYAoKSW4gdGhpcyBjYXNlLCB0aGUgdG90YWwgb2YgKkZhbHNlIFBvc2l0aXZlKiByZXF1ZXN0cyBmcm9tIE5OIGNsYXNzaWZpZXIgaXMgYHIgbnJvdyhyZXF1ZXN0X2xhYmVsZWQgJT4lIGZpbHRlcihHcm91bmRUcnV0aExhYmVsPT0nTm9ybWFsJyAmIGRnYS5jbGFzcz09MSkpYC4KCiMjIyBDbGllbnQgUHJvZmlsZSBwZXIgcmVxdWVzdCBBbmFseXNpcwpUaGUgIGRpc3RyaWJ1dGlvbiBvZiB0aGUgR3JvdW5kVHJ1dGhMYWJlbCBjbGFzc2lmaWVkIGFzIERHQSBieSB0aGUgUHJvZmlsZSBpcyBzaG93ZWQgaW4gdGhlIFRhYmxlIGJlbG93LgpgYGB7cn0KcHJvZmlsZWRldGVjdGVkPC1yZXF1ZXN0X2xhYmVsZWQgJT4lICBmaWx0ZXIoIWlzLm5hKEdyb3VuZFRydXRoTGFiZWwpICYgcHJvZmlsZXByZWRjbGFzcz09J01hbGljaW91cycpICU+JSBncm91cF9ieShHcm91bmRUcnV0aExhYmVsLCBwcm9maWxlcHJlZGNsYXNzKSAlPiUgc3VtbWFyaXNlKHByb2ZpbGV0b3RhbD1uKCkpIApwcm9maWxlZGV0ZWN0ZWQKYGBgCkluIHRoaXMgY2FzZSwgdGhlIHRvdGFsIG9mICpGYWxzZSBQb3NpdGl2ZSogcmVxdWVzdHMgZnJvbSBwcm9maWxlIGNsYXNzaWZpZXIgaXMgKipgciBucm93KHJlcXVlc3RfbGFiZWxlZCAlPiUgZmlsdGVyKEdyb3VuZFRydXRoTGFiZWw9PSdOb3JtYWwnICYgcHJvZmlsZXByZWRjbGFzcz09J01hbGljaW91cycpKWAqKi4gVGhhdCBtZWFucyB0aGUgcHJvZmlsZSBoYXMgcmVkdWNlZCB0aGUgbnVtYmVyIG9mICpGYWxzZSBQb3NpdGl2ZSogaW4gKipgciBucm93KHJlcXVlc3RfbGFiZWxlZCAlPiUgZmlsdGVyKEdyb3VuZFRydXRoTGFiZWw9PSdOb3JtYWwnICYgZGdhLmNsYXNzPT0xKSkgLSBucm93KHJlcXVlc3RfbGFiZWxlZCAlPiUgZmlsdGVyKEdyb3VuZFRydXRoTGFiZWw9PSdOb3JtYWwnICYgcHJvZmlsZXByZWRjbGFzcz09J01hbGljaW91cycpKWAqKi4KClN1Y2ggcmVzdWx0cyBhcmUgc2hvd24gaW4gdGhlIG5leHQgYmFycGxvdC4gSW4gZ3JheSBpcyB0aGUgdG90YWwgYW1vdW50IG9mIE5vcm1hbCByZWNvcmQsIGluIGJsdWUgdGhlIHRvdGFsIGFtb3VudCBvZiBOb3JtYWwgcmVxdWVzdHMgZGV0ZWN0ZWQgYXMgREdBIGJ5IHRoZSBOTi4gRmluYWxseSwgaW4gT3JhbmdlIHRoZSB0b3RhbCBhbW91bnQgb2YgTWFsaWNpb3VzIHJlcXVlc3RzIGFjY29yZGluZyB0byB0aGUgY2xpZW50IHByb2ZpbGUuCmBgYHtyfQpyZXN1bHRzPC1jYmluZChubGFiZWxzLHByb2ZpbGV0b3RhbD1wcm9maWxlZGV0ZWN0ZWQkcHJvZmlsZXRvdGFsLG5udG90YWw9bm5kZXRlY3RlZCRubnRvdGFsKQpyZXN1bHRzPC1yZXN1bHRzICU+JSBtdXRhdGUoR3JvdW5kVHJ1dGhMYWJlbD1pZmVsc2UoR3JvdW5kVHJ1dGhMYWJlbD09J05vcm1hbCcsJ05vcm1hbCcsJ05vdCBOb3JtYWwnKSkKZ2dwbG90KGFzLmRhdGEuZnJhbWUocmVzdWx0cykpKwogICAgZ2VvbV9jb2woYWVzKHg9R3JvdW5kVHJ1dGhMYWJlbCx5PXRvdGFsKSxmaWxsPSdsaWdodGdyYXknKSsKICAgIGdlb21fY29sKGFlcyh4PUdyb3VuZFRydXRoTGFiZWwseT1ubnRvdGFsKSxmaWxsPSdza3libHVlJyxhbHBoYT0wLjUpKwogICAgZ2VvbV9jb2woYWVzKHg9R3JvdW5kVHJ1dGhMYWJlbCx5PXByb2ZpbGV0b3RhbCksZmlsbD0nb3JhbmdlJyxhbHBoYT0wLjcpKwogICAgdGhlbWVfYncoKQpgYGAKCiMgQ29uY2x1c2lvbnMKCiogSW4gdGhlIGZpcnN0IHBhcnQgb2YgdGhlIGV2YWx1YXRpb24sIHdoZW4gd2Ugd2VyZSBvbmx5IGNvbnNpZGVyaW5nIHRoZSBjbGllbnQgcHJvZmlsZXMsIHRoZSBjbGFzc2lmaWVyIGhhcyBjb3JyZWN0bHkgZGV0ZWN0ZWQgYWxsIHRoZSBGUCAoaS5lLiBOb3JtYWwgcHJvZmlsZSB1bmNvcnJlY3RseSBkZXRlY3RlZCBieSB0aGUgTk4pLiBBbiBTcGVjaWZpY2l0eSBvZiAxIGFuZCBhIFNlbnNpdGl2aXR5IG9mIDAuOCB3YXMgc2hvd2VkLiBIb3dldmVyLCB0aGUgIE1jbmVtYXIncyBUZXN0IFAtVmFsdWUgcmVzdWx0ZWQgaW4gIDAuMDczNjQsIHRoYXQgbWVhbnMgcmVzdWx0cyBzaG91bGQgbm90IGNvbnNpZGVyZWQgc2lnbmlmaWNhbnQuIFBlcmhhcHMgbW9yZSBQcm9maWxlIGxhYmVsZWQgYXJlIGdvaW5nIHRvIGJlIG5lZWRlZC4KCiogUmVnYXJkaW5nIHRoZSBwZXIgcmVxdWVzdCBldmFsdWF0aW9uLCBpbiBnZW5lcmFsIHRoZSBjbGllbnQgcHJvZmlsZSBoYXMgcmVkdWNlZCB0aGUgKipGUCoqIG9mIHRoZSBOTiBieSBvbmx5IGEgfjE1JS4gSW4gdGhvc2UgTm9ybWFsIHByb2ZpbGVzIGRldGVjdGVkIGJ5IHRoZSBOTiBhcyBER0EsIHRoZSBwcm9maWxlIGNsYXNzaWZpZXIgaGFzIHJlZHVjZWQgY29uc2lkZXJhYmxlIHRoZSBGUC4gSG93ZXZlciwgdGhlIHByb2JsZW0gc2VlbXMgdG8gYmUgaW4gdGhlIGNhc2Ugb2YgdGhvc2UgTWFsaWNpb3VzIHByb2ZpbGVzLiBBIG1hbGljaW91cyBwcm9maWxlIGNhbiBoYXZlIG9ubHkgYSBmZXcgbWFsaWNpb3VzIHJlcXVlc3RzIG1peGVkIHdpdGggc2V2ZXJhbCBub3JtYWwgcmVxdWVzdHMuIEluIHRob3NlIGNhc2VzLCBvYnZpb3VzbHkgdGhlIHByb2ZpbGUgY2xhc3NpZmllciB3aWxsIGNsYXNzaWZ5IGV2ZXJ5dGhpbmcgYXMgTWFsaWNpb3VzLCByZXN1bHRpbmcgaW4gYSBwb29yIHBlcmZvcm1hbmNlLgoKKiBUaGUgbWVudGlvbmVkIHByb2JsZW0gc2VlbXMgdG8gYmUgYSBkaWZpY3VsdCBvbmUsIHNpbmNlIHRoZSAgcHJvZmlsZSBpcyBsYWJlbGVkIGFzIG1hbGljaW91cyBpZiBpdCBjb250YWlucyBhdCBsZWFzdCBvbmx5IG9uZSBjb25maXJtZWQgREdBIHJlcXVlc3RzLiBJdCBkb2Vzbid0IG1hdHRlciBpZiB0aGVyZSBhcmEgaG91c2FuZHMgb2Ygbm9ybWFsIHJlcXVlc3RzIGluIHRoZSBzYW1lIHByb2ZpbGUuIE1vcmVvdmVyLCB0aGUgc2VsZWN0ZWQgZmVhdHVyZXMgYWxsb3cgIHRoZSBSYW5kb21GT3Jlc3QgYWxnb3JpdGhtIHRvICBkZXRlY3QgY29ycmVjdGx5IHRoZSBwcmVzZW5jZSBvZiB0aGlzIHNtYWxsIG1hbGljaW91cyBiZWhhdmlvciBpbiBhIHByb2ZpbGUsIGJ1dCBpdCBrbm93cyBub3RoaW5nIGFib3V0IHRoZSByZW1haW5pbmcgbm9ybWFsIHJlcXVlc3RzLgoKKiAgR2l2ZW4gdGhhdCB0aGUgcHJlc2VuY2Ugb2YgREdBIGNvdWxkIGJlIHNtYWxsIGR1cmluZyBhIDEgaG91ciB0aW1lIHBlcmlvZCwgYSBwb3NzaWJsZSBzb2x1dGlvbiBjb3VsZCBiZSB0byB1c2Ugc21hbGxlciB3aW5kb3dzLgoKI3NhdmluZyB0aGUgIG1vZGVsCmBgYHtyLCBldmFsPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQoKcHJvZmlsZW1vZGVsPC1yYW5kb21Gb3Jlc3QodHJhaW5fZm9ybXVsYSxkYXRhPXRyYWluc2V0LG10cnkgPSA0KQpwcm9maWxlcHJlZGljdGlvbnM8LXByZWRpY3QocHJvZmlsZW1vZGVsLHRyYWluc2V0KQpjb25mdXNpb25NYXRyaXgodHJhaW5zZXQkcHJvZmlsZSxwcm9maWxlcHJlZGljdGlvbnMscG9zaXRpdmUgPSAnTm9ybWFsJykKc2F2ZShmaWxlID0gIn4vRHJvcGJveC9vbmdvaW5nLXdvcmsvZ2l0LXJlcG9zL2RnYS13Yi9wcm9maWxlLWNsYXNzaWZpZXIvZG9ja2VyL2FwcC9wbW9kZWwucmRhIixwcm9maWxlbW9kZWwpCmBgYAoK