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

  1. Total number of Requests

  2. Ratio of NX requests (answer=NX)

  3. ratio of MX request (query_type=“MX”)

  4. Ratio of Reverse requests (answer=in.arpa)

  5. Ratio of Fail Request (answer=SERVFAIL)

  6. 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)

  7. 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)

  8. 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)

  9. 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:

  1. We considered only the profiles in the testfile
  2. For each profile, we select all the DNS requests and labeled accordingly.
  3. For a profile labeled as Normal, all DNS requests are considered Normal
  4. 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

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