timestamp                      client             query            query_type         answer_ip          domain_l1          domain_l2        
 Min.   :2017-02-20 23:12:13   Length:10000000    Length:10000000    Length:10000000    Length:10000000    Length:10000000    Length:10000000   
 1st Qu.:2017-03-07 19:37:18   Class :character   Class :character   Class :character   Class :character   Class :character   Class :character  
 Median :2017-03-08 22:39:36   Mode  :character   Mode  :character   Mode  :character   Mode  :character   Mode  :character   Mode  :character  
 Mean   :2017-03-09 07:33:45                                                                                                                    
 3rd Qu.:2017-03-11 12:45:21                                                                                                                    
 Max.   :2017-03-15 07:16:55                                                                                                                    
                                                                                                                                                
      ttl               answer          dga.probability    dga.class     
 Min.   :0.000e+00   Length:10000000    Min.   :0        Min.   :0       
 1st Qu.:0.000e+00   Class :character   1st Qu.:0        1st Qu.:0       
 Median :3.000e+01   Mode  :character   Median :0        Median :0       
 Mean   :4.872e+03                      Mean   :0        Mean   :0       
 3rd Qu.:3.000e+02                      3rd Qu.:0        3rd Qu.:0       
 Max.   :2.147e+09                      Max.   :1        Max.   :1       
                                        NA's   :388724   NA's   :388724  

Feature Generation

Feature file available at https://www.dropbox.com/s/vl9y5p32e2umxu1/wbone-client-profile-1h-last8days.csv?dl=1

summary(clients_profiles)
    client           tot_requests      tot_detected     ratio_detected       tot_nx           ratio_nx           tot_mx        
 Length:15870       Min.   :    1.0   Min.   :  0.000   Min.   :0.0000   Min.   :    0.0   Min.   :0.00000   Min.   :  0.0000  
 Class :character   1st Qu.:    2.0   1st Qu.:  0.000   1st Qu.:0.0000   1st Qu.:    0.0   1st Qu.:0.00000   1st Qu.:  0.0000  
 Mode  :character   Median :    6.0   Median :  0.000   Median :0.0000   Median :    0.0   Median :0.00000   Median :  0.0000  
                    Mean   :  516.7   Mean   :  3.822   Mean   :0.0082   Mean   :  149.8   Mean   :0.10165   Mean   :  0.6547  
                    3rd Qu.:   43.0   3rd Qu.:  0.000   3rd Qu.:0.0000   3rd Qu.:    3.0   3rd Qu.:0.07692   3rd Qu.:  0.0000  
                    Max.   :61787.0   Max.   :609.000   Max.   :1.0000   Max.   :54037.0   Max.   :1.00000   Max.   :170.0000  
    ratio_mx           tot_fail         ratio_fail       tot_reverse      ratio_reverse       max_sameip         tot_sameip     
 Min.   :0.000000   Min.   :    0.0   Min.   :0.00000   Min.   :   0.00   Min.   :0.00000   Min.   :   0.000   Min.   :   0.00  
 1st Qu.:0.000000   1st Qu.:    0.0   1st Qu.:0.00000   1st Qu.:   0.00   1st Qu.:0.00000   1st Qu.:   0.000   1st Qu.:   0.00  
 Median :0.000000   Median :    0.0   Median :0.00000   Median :   0.00   Median :0.00000   Median :   0.000   Median :   0.00  
 Mean   :0.002088   Mean   :  105.5   Mean   :0.03087   Mean   :  15.35   Mean   :0.02532   Mean   :   4.256   Mean   :  55.59  
 3rd Qu.:0.000000   3rd Qu.:    0.0   3rd Qu.:0.00000   3rd Qu.:   0.00   3rd Qu.:0.00000   3rd Qu.:   2.000   3rd Qu.:   2.00  
 Max.   :1.000000   Max.   :12510.0   Max.   :1.00000   Max.   :2177.00   Max.   :1.00000   Max.   :1610.000   Max.   :5692.00  
   avg_sameip        sd_sameip       tot_samedomain    max_samedomain   avg_samedomain  sd_samedomain    ratio_tot_samedomain
 Min.   : 0.0000   Min.   : 0.0000   Min.   :   0.00   Min.   : 0.000   Min.   :0.000   Min.   :0.0000   Min.   :0.0000      
 1st Qu.: 0.0000   1st Qu.: 0.0000   1st Qu.:   0.00   1st Qu.: 0.000   1st Qu.:0.000   1st Qu.:0.0000   1st Qu.:0.0000      
 Median : 0.0000   Median : 0.0000   Median :   0.00   Median : 0.000   Median :0.000   Median :0.0000   Median :0.0000      
 Mean   : 0.7989   Mean   : 0.4673   Mean   :  68.87   Mean   : 2.177   Mean   :1.158   Mean   :0.2819   Mean   :0.1419      
 3rd Qu.: 2.0000   3rd Qu.: 0.0000   3rd Qu.:   7.00   3rd Qu.: 3.000   3rd Qu.:2.444   3rd Qu.:0.4754   3rd Qu.:0.2208      
 Max.   :41.7500   Max.   :70.1429   Max.   :5310.00   Max.   :84.000   Max.   :9.000   Max.   :5.6569   Max.   :1.0000      
 ratio_max_samedomain ratio_avg_samedomain ratio_sd_samedomain ratio_tot_sameip  ratio_max_sameip   ratio_avg_sameip    ratio_sd_sameip   
 Min.   :0.00000      Min.   :0.00000      Min.   :0.0000000   Min.   :0.00000   Min.   :0.000000   Min.   :0.0000000   Min.   :0.000000  
 1st Qu.:0.00000      1st Qu.:0.00000      1st Qu.:0.0000000   1st Qu.:0.00000   1st Qu.:0.000000   1st Qu.:0.0000000   1st Qu.:0.000000  
 Median :0.00000      Median :0.00000      Median :0.0000000   Median :0.00000   Median :0.000000   Median :0.0000000   Median :0.000000  
 Mean   :0.08588      Mean   :0.08116      Mean   :0.0037496   Mean   :0.03802   Mean   :0.020587   Mean   :0.0181529   Mean   :0.001514  
 3rd Qu.:0.05039      3rd Qu.:0.03571      3rd Qu.:0.0001406   3rd Qu.:0.02563   3rd Qu.:0.005566   3rd Qu.:0.0006352   3rd Qu.:0.000000  
 Max.   :1.00000      Max.   :1.00000      Max.   :0.3535534   Max.   :1.00000   Max.   :1.000000   Max.   :1.0000000   Max.   :0.489536  
   profilenum      currenttm                  
 Min.   :  0.0   Min.   :2017-03-07 07:16:51  
 1st Qu.: 59.0   1st Qu.:2017-03-08 20:16:51  
 Median :113.0   Median :2017-03-10 14:16:51  
 Mean   :105.8   Mean   :2017-03-10 21:25:51  
 3rd Qu.:155.0   3rd Qu.:2017-03-12 20:16:51  
 Max.   :192.0   Max.   :2017-03-15 07:16:51  

Selecting features

client_profile_selected_features<-clients_profiles   %>% mutate(ratio_requests=tot_requests/max(tot_requests)) %>%
  #select(client,tot_requests, tot_detected,tot_nx,tot_mx,tot_fail,tot_reverse,tot_samedomain,tot_sameip)
 select(client,tot_requests,ratio_requests,ratio_detected,ratio_nx,ratio_mx,ratio_fail,ratio_reverse,
        ratio_tot_samedomain,ratio_tot_sameip,
        ratio_avg_samedomain,ratio_avg_sameip,
        ratio_sd_samedomain,ratio_sd_sameip)
client_profile_ratio<-unique(client_profile_ratio)
  #select(client,tot_requests,tot_detected,max_sameip,avg_sameip,sd_sameip,max_samedomain,avg_samedomain,max_samedomain,ratio_nx)
client_profile_selected_features

Matrix Correlation heatmap

library(d3heatmap)
corr_client_profile_selected_features<-cor(client_profile_selected_features[,c(-1,-2)],use="complete.obs")
d3heatmap(corr_client_profile_selected_features,symm=T,colors = "Reds",Rowv=T,
          width = 600,height = 400, 
          yaxis_font_size = "8pt",
          xaxis_font_size = "8pt")

Per Feature Scatter plot

pairs(client_profile_selected_features[,c(-1,-2)],cex.labels = 1.6)

ggplot(client_profile_ratio,aes(ratio_nx,ratio_detected))+
  geom_point(aes(alpha=tot_requests),color='skyblue')+
  geom_smooth(method = 'lm',color='orange')+
  theme_bw()

Kmeans clustering

kmeans_model<-kmeans(client_profile_selected_features[,c(-1,-2)],centers=5,nstart=40)
client_profile_ratio<-cbind(client_profile_selected_features,cluster=as.factor(kmeans_model$cluster))
client_profile_ratio %>% group_by(cluster) %>% summarise(n=n())
save(kmeans_model,file="../models/kmeans_model_1h_last8days.Rdata")

Some basic per cluster information

cluster_summary<-unique(client_profile_ratio[,c(-2)]) %>% group_by(cluster) %>% summarise(n=n(),nx=mean(ratio_nx),mx=mean(ratio_mx),sameip=mean(ratio_tot_sameip),samedomain=mean(ratio_tot_samedomain),reverse=mean(ratio_reverse),detected=mean(ratio_detected),requests=mean(ratio_requests),fail=mean(ratio_fail), ipdist=n_distinct(client))
                                                                                         
cluster_summary
d3heatmap(cluster_summary[,c(-1,-2,-11)],colors = "Reds",Rowv=T,
          width = 700,height = 400, 
          yaxis_font_size = "8pt",
          xaxis_font_size = "8pt")

Clustering Results

According to clustering results

  1. Cluster n. 1 groups clients with high NX replies as well as some reverse queries among others. (possible DGA)
  2. Cluster n. 2 groups those clients not showing DGA or other malwaver behavior (Possible NORMAL??)
  3. Cluster n. 3 groups those clients with the highest samedomains values (possible Fastflux?)
  4. Cluster n. 4 groups those clients with a value increment for sameip and samedomains (Possible DGA/Fastflux? Not really)
  5. Cluster n. 5 groups those clients with high values in FAIL queries (Possible what??)

some IPS to check

213.191.105.210 shows some possible random generated domains

213.191.105.242 Shows a lot of NX records. Some queries contain the kk. TLD (??)

T-NSE 2D representation with clusters

Limitations of PCA

PCA is a linear algorithm. It will not be able to interpret complex polynomial relationship between features. On the other hand, t-SNE is based on probability distributions with random walk on neighborhood graphs to find the structure within the data.

tsne_model<-Rtsne(unique(client_profile_ratio[,c(-2,-ncol(client_profile_ratio))]),pca=FALSE, pca_center =TRUE, pca_scale=TRUE,check_duplicates=F,
                  perplexity = 400, max_iter = 1000)

To remember: It seems that if we generate 1h profiles the the 2D representation tends to be more difficult to analyze. The are many points very similar corresponding to the same client profile at different time periods. Using a higher perplexity value seems to solve that issue.

Each point represents a 1 hour profile. The size of a given point/profile corresponds to the request ratios for that profile. Finally, color presents differents clusters. Remember you can select/deselect the cluster by clicking the legend.

client_profile_unique<-unique(client_profile_ratio[,c(-2)])
tsne_client_profile<-data.frame(tsne_model$Y,cluster=client_profile_unique$cluster,client=client_profile_unique$client,requests=
                                  client_profile_unique$ratio_requests)
sampleid<-sample(nrow(tsne_client_profile),nrow(tsne_client_profile))
g<-ggplot(tsne_client_profile[sampleid,],aes(x=X1,y=X2))+
  geom_point(aes(color=as.factor(cluster),size=requests,text=paste("ipddr",client)))+
  #geom_point(aes(shape=asignacion),size=3)+
  ylab("X1")+xlab("X2")+
  theme_classic()+
#scale_shape_manual(values=c(8,6))+
   guides(color=FALSE,alpha=FALSE)
ggplotly(g)

Decision Tree Analysis

library(rpart.plot)
library(rpart)
library(rpart.utils)

formula <- as.formula(cluster~.)
tree=rpart(formula,data=client_profile_ratio[,c(-1,-2,-3,-4)],control = rpart.control(minsplit=500, cp=0.005,xval=10),model=T,x=T,y=T)
rpart.plot(tree,
           extra=4, 
           box.palette="GnBu",
           branch.lty=5, shadow.col=0, nn=TRUE, cex =0.9,under=T
        )
printcp(tree)

Clients in cluster 1

tsne_client_profile %>% filter(cluster==1) %>% group_by(client) %>% summarise(profiles=n()) %>% arrange(desc(profiles)) %>% filter(profiles>0 )  %>% select(client)

Clients in cluster 2

tsne_client_profile %>% filter(cluster==2) %>% group_by(client) %>% summarise(profiles=n()) %>% arrange(desc(profiles)) %>% filter(profiles>0)  %>% select(client)

Clients in cluster 3

tsne_client_profile %>% filter(cluster==3) %>% group_by(client) %>% summarise(profiles=n()) %>% arrange(desc(profiles)) %>% filter(profiles>0)  %>% select(client)

Clients in cluster 4

tsne_client_profile %>% filter(cluster==4) %>% group_by(client) %>% summarise(profiles=n()) %>% arrange(desc(profiles)) %>% filter(profiles>0)  %>% select(client)

Clients in cluster 5

tsne_client_profile %>% filter(cluster==5) %>% group_by(client) %>% summarise(profiles=n()) %>% arrange(desc(profiles)) %>% filter(profiles>0)  %>% select(client)

Clients in cluster 6

tsne_client_profile %>% filter(cluster==6) %>% group_by(client) %>% summarise(profiles=n()) %>% arrange(desc(profiles)) %>% filter(profiles>0) %>% select(client)
LS0tCnRpdGxlOiAiV0IgY2xpZW50IDEgaG91ciBwcm9maWxlIGdlbmVyYXRpb24gKGxhc3QgOCBkYXlzKSAiCm91dHB1dDogCiAgaHRtbF9ub3RlYm9vazogCiAgICBjb2RlX2ZvbGRpbmc6IGhpZGUKICAgIHRvYzogeWVzCi0tLQoKPHN0eWxlIHR5cGU9InRleHQvY3NzIj4KCmJvZHksIHRkIHsKICAgZm9udC1zaXplOiAxNHB4Owp9CmNvZGUucnsKICBmb250LXNpemU6IDlweDsKfQpwcmUgewogIGZvbnQtc2l6ZTogOXB4Cn0KPC9zdHlsZT4KCgoKYGBge3IsIGVjaG89RkFMU0UsIGZpZy5oZWlnaHQ9MTIsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KGx1YnJpZGF0ZSkKbGlicmFyeShkb01DKQpyZWdpc3RlckRvTUMoY29yZXM9MykKZG9tYWluczwtcmVhZF9jc3YoZmlsZT0iL2hvbWUvaGFycG8vRHJvcGJveC9vbmdvaW5nLXdvcmsvZ2l0LXJlcG9zL2RnYS13Yi9kYXRhc2V0cy9kYXRhYmFzZS5jc3YiLHByb2dyZXNzID0gVCkKc3VtbWFyeShkb21haW5zKQoKICAjZG9tYWlucyAlPiUgZmlsdGVyKGlzLm5hKGRnYS5jbGFzcykpIApgYGAKCgoKCiMjIEZlYXR1cmUgR2VuZXJhdGlvbgoKRmVhdHVyZSBmaWxlIGF2YWlsYWJsZSBhdCAgaHR0cHM6Ly93d3cuZHJvcGJveC5jb20vcy92bDl5NXAzMmUydW14dTEvd2JvbmUtY2xpZW50LXByb2ZpbGUtMWgtbGFzdDhkYXlzLmNzdj9kbD0xCgoKYGBge3IgZmVhdHVyZSBnZW5lcmF0aW9uLCBlY2hvPVRSVUV9Cm1heF9udW1fcHJvZmlsZXM9MTkyCndpbmRvd3NpemU8LTYwKjEgIyAyNEhzIHdpbmRvdwppbml0aWFsX3RtPC1kb21haW5zJHRpbWVzdGFtcFtucm93KGRvbWFpbnMpLTFdCmNsaWVudHNfcHJvZmlsZXM8LWRhdGEuZnJhbWUoKQoKY2xpZW50c19wcm9maWxlczwtZm9yZWFjaCAocHJvZmlsZW51bSA9IDA6bWF4X251bV9wcm9maWxlcywuY29tYmluZT0ncmJpbmQnLCAubXVsdGljb21iaW5lPUZBTFNFICkgJWRvcGFyJSB7CiAgY3VycmVudHRtPC1pbml0aWFsX3RtIC0gKG1pbnV0ZXMod2luZG93c2l6ZSkqcHJvZmlsZW51bSkKICBkb21haW5zX3dpbmRvdzwtZG9tYWlucyAlPiUgZmlsdGVyKHRpbWVzdGFtcCA+IGN1cnJlbnR0bSAtbWludXRlcyh3aW5kb3dzaXplKSAmIHRpbWVzdGFtcCA8IGN1cnJlbnR0bSkKIAogICAjIFRvdGFsIGFtb3VudCBvZiBkb21hbnMgcmVxdWVzdGVkIAogIG5fcmVxdWVzdHMgPC1kb21haW5zX3dpbmRvdyAlPiUgZ3JvdXBfYnkoY2xpZW50KSAlPiUgCiAgc3VtbWFyaXNlKHRvdF9yZXF1ZXN0cz1uKCkpICMlPiUgYXJyYW5nZShkZXNjKHRvdF9yZXF1ZXN0cykpCgogICMgVG90YWwgYW1vdW50IG9mIGRvbWFpbnMgZGV0ZWN0ZWQgYnkgdGhlIE5OIGFuZCByYXRpby4KICBuX2RldGVjdGVkPC1kb21haW5zX3dpbmRvdyAlPiUgZ3JvdXBfYnkoY2xpZW50KSAlPiUgCiAgc3VtbWFyaXNlKHRvdF9kZXRlY3RlZD1zdW0oaWZlbHNlKGRnYS5jbGFzcz09MSwxLDApLG5hLnJtPVQpKQogIG5fZGV0ZWN0ZWQkcmF0aW9fZGV0ZWN0ZWQ8LW5fZGV0ZWN0ZWQkdG90X2RldGVjdGVkL25fcmVxdWVzdHMkdG90X3JlcXVlc3RzCgoKICAjIFRvdGFsIGFtb3VudCBvZiBOWERPTUFJTiBhbmQgcmF0aW8KICBuX254PC1kb21haW5zX3dpbmRvdyAgJT4lIGdyb3VwX2J5KGNsaWVudCkgJT4lIAogIHN1bW1hcmlzZSh0b3Rfbng9c3VtKGdyZXBsKCJOWERPTUFJTiIsYW5zd2VyKSkpCiAgbl9ueCRyYXRpb19ueDwtbl9ueCR0b3Rfbngvbl9yZXF1ZXN0cyR0b3RfcmVxdWVzdHMKCiAgIyBUb3RhbCBhbW91bnQgb2YgU0VSVkZBSUwgYW5kIHJhdGlvCiAgbl9mYWlsPC1kb21haW5zX3dpbmRvdyAgJT4lIGdyb3VwX2J5KGNsaWVudCkgJT4lIAogIHN1bW1hcmlzZSh0b3RfZmFpbD1zdW0oZ3JlcGwoIlNFUlZGQUlMIixhbnN3ZXIpKSkKICBuX2ZhaWwkcmF0aW9fZmFpbDwtbl9mYWlsJHRvdF9mYWlsL25fcmVxdWVzdHMkdG90X3JlcXVlc3RzCgogICMgVG90YWwgYW1vdW50IG9mIE1YIGFuZCByYXRpbwogIG5fbXg8LWRvbWFpbnNfd2luZG93ICAlPiUgIGdyb3VwX2J5KGNsaWVudCkgICU+JSAKICBzdW1tYXJpc2UodG90X214PXN1bShxdWVyeV90eXBlPT0iTVgiKSkgCiAgbl9teCRyYXRpb19teDwtbl9teCR0b3RfbXgvbl9yZXF1ZXN0cyR0b3RfcmVxdWVzdHMKCiAgCiAgbl9yZXZlcnNlIDwtIGRvbWFpbnNfd2luZG93ICU+JSBncm91cF9ieShjbGllbnQpICU+JQogIHN1bW1hcmlzZSh0b3RfcmV2ZXJzZT1zdW0oZ3JlcGwoImluLWFkZHIuYXJwYS4iLHF1ZXJ5KSkpIAogIG5fcmV2ZXJzZSRyYXRpb19yZXZlcnNlPC1uX3JldmVyc2UkdG90X3JldmVyc2Uvbl9yZXF1ZXN0cyR0b3RfcmVxdWVzdHMKCiAgICAKICAjIGRvbWFpbnMgc2hhcmluZyB0aGUgc2FtZSBJUCAKICBhbGxfc2FtZWlwPC1kb21haW5zX3dpbmRvdyAlPiUgIAogIGZpbHRlcihxdWVyeV90eXBlPT0iQSIgJiBhbnN3ZXJfaXAhPSJOWERPTUFJTiIgJiBhbnN3ZXJfaXAhPSJTRVJWRkFJTCIpICU+JSAgCiAgZ3JvdXBfYnkoY2xpZW50LGFuc3dlcl9pcCkgJT4lIAogIHN1bW1hcmlzZSh0b3RfZGlzdHF1ZXJ5PW5fZGlzdGluY3QocXVlcnkpKSAKCiAgI1RoZSBtYXggKGludCkgYW1vdW50IG9mIHJlcXVlc3RzIHdoZXJlIG9uZSBJUCByZXNvbHZlZCB0byBtb3JlIHRoYW4gb25lIGRvbWFpbi4KICBtYXhfc2FtZWlwPC1hbGxfc2FtZWlwICU+JSBmaWx0ZXIodG90X2Rpc3RxdWVyeSA+MSkgJT4lIGdyb3VwX2J5KGNsaWVudCkgJT4lIHN1bW1hcmlzZShtYXhfc2FtZWlwPW1heCh0b3RfZGlzdHF1ZXJ5KSkKCiAgICNUaGUgYXZnICBvZiByZXF1ZXN0cyB3aGVyZSBvbmUgSVAgcmVzb2x2ZWQgdG8gbW9yZSB0aGFuIG9uZSBkb21haW4uCiAgYXZnX3NhbWVpcDwtYWxsX3NhbWVpcCAlPiUgZmlsdGVyKHRvdF9kaXN0cXVlcnkgPjEpICU+JSBncm91cF9ieShjbGllbnQpICU+JSBzdW1tYXJpc2UoYXZnX3NhbWVpcD1tZWFuKHRvdF9kaXN0cXVlcnkpKQoKICAjVGhlIGF2ZyAgb2YgcmVxdWVzdHMgd2hlcmUgb25lIElQIHJlc29sdmVkIHRvIG1vcmUgdGhhbiBvbmUgZG9tYWluLgogIHNkX3NhbWVpcDwtYWxsX3NhbWVpcCAlPiUgZmlsdGVyKHRvdF9kaXN0cXVlcnkgPjEpICU+JSBncm91cF9ieShjbGllbnQpICU+JSBzdW1tYXJpc2Uoc2Rfc2FtZWlwPXNkKHRvdF9kaXN0cXVlcnkpKQoKICAjVGhlIGFtb3VudCBvZiBncm91cHMgb2Ygc2FtZSBJUHMgdGhhdCByZXNvbHZlZCB0byBtb3JlIHRoYW4gb25lIGRvbWFpbi4KICBuX3NhbWVpcDwtYWxsX3NhbWVpcCAlPiUgZmlsdGVyKHRvdF9kaXN0cXVlcnkgPjEpICU+JSBncm91cF9ieShjbGllbnQpICU+JSBzdW1tYXJpc2UodG90X3NhbWVpcD1zdW0odG90X2Rpc3RxdWVyeSkpCiAgCiAgCiAgIyBUaGUgYW1vdW50IG9mIGRvbWFpbnMgdGhhdCBlYWNoIG9mIHRoZW0gaGFkIG1vcmUgdGhhbiAxIElQLgogICMgKEZhc3QgZmx1eCkKICBhbGxfc2FtZWRvbWFpbjwtZG9tYWluc193aW5kb3cgJT4lICAKICBmaWx0ZXIocXVlcnlfdHlwZT09IkEiICYgYW5zd2VyX2lwIT0iTlhET01BSU4iICYgYW5zd2VyX2lwIT0iU0VSVkZBSUwiKSAlPiUgIAogIGdyb3VwX2J5KGNsaWVudCxxdWVyeSkgJT4lIAogIHN1bW1hcmlzZSh0b3RfZGlzdGRvbWFpbj1uX2Rpc3RpbmN0KGFuc3dlcl9pcCkpIAogIAogICNUaGUgbWF4IChpbnQpIGFtb3VudCBvZiByZXF1ZXN0cyB3aGVyZSBvbmUgZG9tYWluIHJlc29sdmVkIHRvIG1vcmUgdGhhbiBvbmUgSVAKICBtYXhfc2FtZWRvbWFpbjwtYWxsX3NhbWVkb21haW4gJT4lIGZpbHRlcih0b3RfZGlzdGRvbWFpbiA+MSkgJT4lIGdyb3VwX2J5KGNsaWVudCkgJT4lIHN1bW1hcmlzZShtYXhfc2FtZWRvbWFpbj1tYXgodG90X2Rpc3Rkb21haW4pKQoKICAjVGhlIGF2ZyAgb2YgcmVxdWVzdHMgd2hlcmUgb25lIGRvbWFpbiByZXNvbHZlZCB0byBtb3JlIHRoYW4gb25lIElQCiAgYXZnX3NhbWVkb21haW48LWFsbF9zYW1lZG9tYWluICU+JSBmaWx0ZXIodG90X2Rpc3Rkb21haW4gPjEpICU+JSBncm91cF9ieShjbGllbnQpICU+JSBzdW1tYXJpc2UoYXZnX3NhbWVkb21haW49bWVhbih0b3RfZGlzdGRvbWFpbikpCgogICNUaGUgYXZnICBvZiByZXF1ZXN0cyB3aGVyZSBvbmUgZG9tYWluIHJlc29sdmVkIHRvIG1vcmUgdGhhbiBvbmUgSVAKICBzZF9zYW1lZG9tYWluPC1hbGxfc2FtZWRvbWFpbiAlPiUgZmlsdGVyKHRvdF9kaXN0ZG9tYWluID4xKSAlPiUgZ3JvdXBfYnkoY2xpZW50KSAlPiUgc3VtbWFyaXNlKHNkX3NhbWVkb21haW49c2QodG90X2Rpc3Rkb21haW4pKQoKICAjVGhlIGFtb3VudCBvZiBncm91cHMgb2Ygc2FtZSBkb21haW4gdGhhdCByZXNvbHZlZCB0byBtb3JlIHRoYW4gb25lIElQCiAgbl9zYW1lZG9tYWluPC1hbGxfc2FtZWRvbWFpbiAlPiUgZmlsdGVyKHRvdF9kaXN0ZG9tYWluID4xKSAlPiUgZ3JvdXBfYnkoY2xpZW50KSAlPiUgc3VtbWFyaXNlKHRvdF9zYW1lZG9tYWluPXN1bSh0b3RfZGlzdGRvbWFpbikpCiAgCiAgCiAgIyBQcm9maWxlIERhdGFiYXNlIENyZWF0aW9uIC0tLS0tLQogIAogIGNsaWVudF9wcm9maWxlPC1pbm5lcl9qb2luKG5fcmVxdWVzdHMsbl9kZXRlY3RlZCxieT0iY2xpZW50IikgJT4lIAogIGlubmVyX2pvaW4obl9ueCxieT0iY2xpZW50IikgJT4lCiAgaW5uZXJfam9pbihuX214LGJ5PSJjbGllbnQiKSAlPiUKICBpbm5lcl9qb2luKG5fZmFpbCxieT0iY2xpZW50IikgJT4lCiAgaW5uZXJfam9pbihuX3JldmVyc2UsYnk9ImNsaWVudCIpICU+JQogIGZ1bGxfam9pbihtYXhfc2FtZWlwLGJ5PSJjbGllbnQiKSAlPiUKICBmdWxsX2pvaW4obl9zYW1laXAsYnk9ImNsaWVudCIpICU+JQogIGZ1bGxfam9pbihhdmdfc2FtZWlwLGJ5PSJjbGllbnQiKSAlPiUKICBmdWxsX2pvaW4oc2Rfc2FtZWlwLGJ5PSJjbGllbnQiKSAgJT4lCiAgZnVsbF9qb2luKG5fc2FtZWRvbWFpbixieT0iY2xpZW50IikgJT4lCiAgZnVsbF9qb2luKG1heF9zYW1lZG9tYWluLGJ5PSJjbGllbnQiKSAlPiUKICBmdWxsX2pvaW4oYXZnX3NhbWVkb21haW4sYnk9ImNsaWVudCIpICU+JQogIGZ1bGxfam9pbihzZF9zYW1lZG9tYWluLGJ5PSJjbGllbnQiKSAKICAgIAogIAogICMgcmVwbGFjZSBOQXMgd2l0aCB6ZXJvcwogIGNsaWVudF9wcm9maWxlPC1jbGllbnRfcHJvZmlsZSAlPiUgbXV0YXRlKG1heF9zYW1laXA9aWZlbHNlKGlzLm5hKG1heF9zYW1laXApLDAsbWF4X3NhbWVpcCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdG90X3NhbWVpcD1pZmVsc2UoaXMubmEodG90X3NhbWVpcCksMCx0b3Rfc2FtZWlwKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhdmdfc2FtZWlwPWlmZWxzZShpcy5uYShhdmdfc2FtZWlwKSwwLGF2Z19zYW1laXApLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNkX3NhbWVpcD1pZmVsc2UoaXMubmEoc2Rfc2FtZWlwKSwwLHNkX3NhbWVpcCkKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICkKICBjbGllbnRfcHJvZmlsZTwtY2xpZW50X3Byb2ZpbGUgJT4lIG11dGF0ZShtYXhfc2FtZWRvbWFpbj1pZmVsc2UoaXMubmEobWF4X3NhbWVkb21haW4pLDAsbWF4X3NhbWVkb21haW4pLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRvdF9zYW1lZG9tYWluPWlmZWxzZShpcy5uYSh0b3Rfc2FtZWRvbWFpbiksMCx0b3Rfc2FtZWRvbWFpbiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXZnX3NhbWVkb21haW49aWZlbHNlKGlzLm5hKGF2Z19zYW1lZG9tYWluKSwwLGF2Z19zYW1lZG9tYWluKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZF9zYW1lZG9tYWluPWlmZWxzZShpcy5uYShzZF9zYW1lZG9tYWluKSwwLHNkX3NhbWVkb21haW4pCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKQogIAogIGNsaWVudF9wcm9maWxlPC1jbGllbnRfcHJvZmlsZSAlPiUgbXV0YXRlKHJhdGlvX3RvdF9zYW1lZG9tYWluPXRvdF9zYW1lZG9tYWluL3RvdF9yZXF1ZXN0cykgCiAgY2xpZW50X3Byb2ZpbGU8LWNsaWVudF9wcm9maWxlICU+JSBtdXRhdGUocmF0aW9fbWF4X3NhbWVkb21haW49bWF4X3NhbWVkb21haW4vdG90X3JlcXVlc3RzKSAKICBjbGllbnRfcHJvZmlsZTwtY2xpZW50X3Byb2ZpbGUgJT4lIG11dGF0ZShyYXRpb19hdmdfc2FtZWRvbWFpbj1hdmdfc2FtZWRvbWFpbi90b3RfcmVxdWVzdHMpIAogIGNsaWVudF9wcm9maWxlPC1jbGllbnRfcHJvZmlsZSAlPiUgbXV0YXRlKHJhdGlvX3NkX3NhbWVkb21haW49c2Rfc2FtZWRvbWFpbi90b3RfcmVxdWVzdHMpIAogIAogIGNsaWVudF9wcm9maWxlPC1jbGllbnRfcHJvZmlsZSAlPiUgbXV0YXRlKHJhdGlvX3RvdF9zYW1laXA9dG90X3NhbWVpcC90b3RfcmVxdWVzdHMpIAogICBjbGllbnRfcHJvZmlsZTwtY2xpZW50X3Byb2ZpbGUgJT4lIG11dGF0ZShyYXRpb19tYXhfc2FtZWlwPW1heF9zYW1laXAvdG90X3JlcXVlc3RzKSAKICAgY2xpZW50X3Byb2ZpbGU8LWNsaWVudF9wcm9maWxlICU+JSBtdXRhdGUocmF0aW9fYXZnX3NhbWVpcD1hdmdfc2FtZWlwL3RvdF9yZXF1ZXN0cykgCiAgIGNsaWVudF9wcm9maWxlPC1jbGllbnRfcHJvZmlsZSAlPiUgbXV0YXRlKHJhdGlvX3NkX3NhbWVpcD1zZF9zYW1laXAvdG90X3JlcXVlc3RzKSAKIAogIAogIAogIAogICBjbGllbnRfcHJvZmlsZSRwcm9maWxlbnVtPC1wcm9maWxlbnVtCiAgY2xpZW50X3Byb2ZpbGUkY3VycmVudHRtPC1jdXJyZW50dG0KICAjY2xpZW50c19wcm9maWxlczwtcmJpbmQoY2xpZW50c19wcm9maWxlcyxjbGllbnRfcHJvZmlsZSkKICAjY3VycmVudHRtPC1jdXJyZW50dG0gLSBtaW51dGVzKHdpbmRvd3NpemUpCiAgCiAgcmV0dXJuKGNsaWVudF9wcm9maWxlKQp9CnN1bW1hcnkoY2xpZW50c19wcm9maWxlcykKYGBgCgoKYGBge3Igc2F2ZS1jc3YsIGV2YWw9RkFMU0UsIGluY2x1ZGU9RkFMU0V9CnJlYWRyOjp3cml0ZV9jc3YoY2xpZW50c19wcm9maWxlcywiLi4vZGF0YS93Ym9uZS1jbGllbnQtcHJvZmlsZS0xaC1sYXN0OGRheXMuY3N2IikKY2xpZW50c19wcm9maWxlcwpgYGAKCmBgYHtyIHJlYWQtY3N2LCBldmFsPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQpjbGllbnRzX3Byb2ZpbGVzPC1yZWFkcjo6cmVhZF9jc3YoIi4uL2RhdGEvd2JvbmUtY2xpZW50LXByb2ZpbGUtMjRocy1sYXN0M3dlZWtzLmNzdiIpCmNsaWVudHNfcHJvZmlsZXMgJT4lIGdyb3VwX2J5KGNsaWVudCkgJT4lIHN1bW1hcmlzZShub2Zwcm9maWxlcz1uX2Rpc3RpbmN0KHByb2ZpbGVudW0pKQpjbGllbnRzX3Byb2ZpbGVzICU+JSBncm91cF9ieShjbGllbnQpICU+JSBzdW1tYXJpc2UodG90X3JlcXVlc3RzPXN1bSh0b3RfcmVxdWVzdHMpKQpjbGllbnRzX3Byb2ZpbGVzICU+JSBncm91cF9ieShjbGllbnQpICU+JSBzdW1tYXJpc2Uobj1uKCkpCmRvbWFpbnMgJT4lIGZpbHRlcihjbGllbnQ9PSIxLjIwMi4yMzYuMTM2IikKY2xpZW50c19wcm9maWxlcyAlPiUgZmlsdGVyKGNsaWVudD09IjEuMjAyLjIzNi4xMzYiKQpgYGAKCgpgYGB7ciBwbG90LXRtLXByb2ZpbGVzLCBldmFsPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQoKZ2dwbG90KGNsaWVudHNfcHJvZmlsZXMpKwogIGdlb21fbGluZShhZXMoeD1jdXJyZW50dG0seT1yYXRpb19kZXRlY3RlZCksY29sb3I9J3JlZCcpKwogIGdlb21fbGluZShhZXMoeD1jdXJyZW50dG0seT1yYXRpb19ueCksY29sb3I9J2JsdWUnKSsKICBnZW9tX2xpbmUoYWVzKHg9Y3VycmVudHRtLHk9cmF0aW9fbXgpLGNvbG9yPSdvcmFuZ2UnKSsKICBnZW9tX2xpbmUoYWVzKHg9Y3VycmVudHRtLHk9cmF0aW9fZmFpbCksY29sb3I9J3NreWJsdWUnKSsKICBnZW9tX2xpbmUoYWVzKHg9Y3VycmVudHRtLHk9cmF0aW9fdG90X3NhbWVpcCksY29sb3I9J2dyZWVuJykrCiAgeGxhYigiRGF0ZXRpbWUiKSt5bGFiKCJ2YWx1ZSIpKwogIHRoZW1lX2J3KCkrCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSkrCiAgZmFjZXRfd3JhcCh+Y2xpZW50LHNjYWxlcyA9ICJmcmVlX3kiKSsKICB0aGVtZShzdHJpcC50ZXh0LnkgPSBlbGVtZW50X2JsYW5rKCksIHN0cmlwLnRleHQueD0gZWxlbWVudF9ibGFuaygpLHN0cmlwLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50ZXh0PWVsZW1lbnRfdGV4dChzaXplPTUpCiAgICAgICAgKQpgYGAKCgojIyBTZWxlY3RpbmcgZmVhdHVyZXMKCmBgYHtyIHNlbGVjdGluZyBmZWF0dXJlc30KY2xpZW50X3Byb2ZpbGVfc2VsZWN0ZWRfZmVhdHVyZXM8LWNsaWVudHNfcHJvZmlsZXMgICAlPiUgbXV0YXRlKHJhdGlvX3JlcXVlc3RzPXRvdF9yZXF1ZXN0cy9tYXgodG90X3JlcXVlc3RzKSkgJT4lCiAgI3NlbGVjdChjbGllbnQsdG90X3JlcXVlc3RzLCB0b3RfZGV0ZWN0ZWQsdG90X254LHRvdF9teCx0b3RfZmFpbCx0b3RfcmV2ZXJzZSx0b3Rfc2FtZWRvbWFpbix0b3Rfc2FtZWlwKQogc2VsZWN0KGNsaWVudCx0b3RfcmVxdWVzdHMscmF0aW9fcmVxdWVzdHMscmF0aW9fZGV0ZWN0ZWQscmF0aW9fbngscmF0aW9fbXgscmF0aW9fZmFpbCxyYXRpb19yZXZlcnNlLAogICAgICAgIHJhdGlvX3RvdF9zYW1lZG9tYWluLHJhdGlvX3RvdF9zYW1laXAsCiAgICAgICAgcmF0aW9fYXZnX3NhbWVkb21haW4scmF0aW9fYXZnX3NhbWVpcCwKICAgICAgICByYXRpb19zZF9zYW1lZG9tYWluLHJhdGlvX3NkX3NhbWVpcCkKY2xpZW50X3Byb2ZpbGVfcmF0aW88LXVuaXF1ZShjbGllbnRfcHJvZmlsZV9yYXRpbykKICAjc2VsZWN0KGNsaWVudCx0b3RfcmVxdWVzdHMsdG90X2RldGVjdGVkLG1heF9zYW1laXAsYXZnX3NhbWVpcCxzZF9zYW1laXAsbWF4X3NhbWVkb21haW4sYXZnX3NhbWVkb21haW4sbWF4X3NhbWVkb21haW4scmF0aW9fbngpCmNsaWVudF9wcm9maWxlX3NlbGVjdGVkX2ZlYXR1cmVzCmBgYAoKIyMgTWF0cml4IENvcnJlbGF0aW9uIGhlYXRtYXAKCmBgYHtyLCBmaWcuaGVpZ2h0PTgsIGZpZy53aWR0aD0xMn0KbGlicmFyeShkM2hlYXRtYXApCmNvcnJfY2xpZW50X3Byb2ZpbGVfc2VsZWN0ZWRfZmVhdHVyZXM8LWNvcihjbGllbnRfcHJvZmlsZV9zZWxlY3RlZF9mZWF0dXJlc1ssYygtMSwtMildLHVzZT0iY29tcGxldGUub2JzIikKZDNoZWF0bWFwKGNvcnJfY2xpZW50X3Byb2ZpbGVfc2VsZWN0ZWRfZmVhdHVyZXMsc3ltbT1ULGNvbG9ycyA9ICJSZWRzIixSb3d2PVQsCiAgICAgICAgICB3aWR0aCA9IDYwMCxoZWlnaHQgPSA0MDAsIAogICAgICAgICAgeWF4aXNfZm9udF9zaXplID0gIjhwdCIsCiAgICAgICAgICB4YXhpc19mb250X3NpemUgPSAiOHB0IikKYGBgCiMjIFBlciBGZWF0dXJlIFNjYXR0ZXIgcGxvdAoKYGBge3IsIGZpZy5oZWlnaHQ9MTJ9CnBhaXJzKGNsaWVudF9wcm9maWxlX3NlbGVjdGVkX2ZlYXR1cmVzWyxjKC0xLC0yKV0sY2V4LmxhYmVscyA9IDEuNikKYGBgCgpgYGB7cn0KZ2dwbG90KGNsaWVudF9wcm9maWxlX3JhdGlvLGFlcyhyYXRpb19ueCxyYXRpb19kZXRlY3RlZCkpKwogIGdlb21fcG9pbnQoYWVzKGFscGhhPXRvdF9yZXF1ZXN0cyksY29sb3I9J3NreWJsdWUnKSsKICBnZW9tX3Ntb290aChtZXRob2QgPSAnbG0nLGNvbG9yPSdvcmFuZ2UnKSsKICB0aGVtZV9idygpCmBgYApgYGB7ciwgZXZhbD1GQUxTRSwgaW5jbHVkZT1GQUxTRX0KIyMgQ2xpZW50IGZlYXR1cmVzIGhlYXRtYXAKIyMgKG9ubHkgY29uc2lkZXJpbmcgcmF0aW9zKQojY2xpZW50X3Byb2ZpbGVfcmF0aW88LWNsaWVudF9wcm9maWxlX3JhdGlvICU+JSBtdXRhdGUoaWQ9YXMubnVtZXJpYyhhcy5mYWN0b3IoY2xpZW50KSkpCgpkM2hlYXRtYXAodChjbGllbnRfcHJvZmlsZV9yYXRpb1ssLTFdKSxzeW1tPUYsY29sb3JzID0gIlJlZHMiLFJvd3Y9RiwKICAgICAgICAgICNsYWJDb2wgPSB0KGNsaWVudF9wcm9maWxlX3JhdGlvWywxXSksCiAgICAgICAgICB3aWR0aCA9IDEwNDAsCiAgICAgICAgICBoZWlnaHQgPSAyNTAsIAogICAgICAgICAgeWF4aXNfZm9udF9zaXplID0gIjhwdCIsCiAgICAgICAgICB4YXhpc19mb250X3NpemUgPSAiMHB0IixhbmltX2R1cmF0aW9uID0gMCkKYGBgCgojIyBLbWVhbnMgY2x1c3RlcmluZwoKYGBge3J9CgprbWVhbnNfbW9kZWw8LWttZWFucyhjbGllbnRfcHJvZmlsZV9zZWxlY3RlZF9mZWF0dXJlc1ssYygtMSwtMildLGNlbnRlcnM9NSxuc3RhcnQ9NDApCmNsaWVudF9wcm9maWxlX3JhdGlvPC1jYmluZChjbGllbnRfcHJvZmlsZV9zZWxlY3RlZF9mZWF0dXJlcyxjbHVzdGVyPWFzLmZhY3RvcihrbWVhbnNfbW9kZWwkY2x1c3RlcikpCmNsaWVudF9wcm9maWxlX3JhdGlvICU+JSBncm91cF9ieShjbHVzdGVyKSAlPiUgc3VtbWFyaXNlKG49bigpKQpzYXZlKGttZWFuc19tb2RlbCxmaWxlPSIuLi9tb2RlbHMva21lYW5zX21vZGVsXzFoX2xhc3Q4ZGF5cy5SZGF0YSIpCmBgYAoKCiMjIFNvbWUgYmFzaWMgcGVyIGNsdXN0ZXIgaW5mb3JtYXRpb24KCmBgYHtyfQpjbHVzdGVyX3N1bW1hcnk8LXVuaXF1ZShjbGllbnRfcHJvZmlsZV9yYXRpb1ssYygtMildKSAlPiUgZ3JvdXBfYnkoY2x1c3RlcikgJT4lIHN1bW1hcmlzZShuPW4oKSxueD1tZWFuKHJhdGlvX254KSxteD1tZWFuKHJhdGlvX214KSxzYW1laXA9bWVhbihyYXRpb190b3Rfc2FtZWlwKSxzYW1lZG9tYWluPW1lYW4ocmF0aW9fdG90X3NhbWVkb21haW4pLHJldmVyc2U9bWVhbihyYXRpb19yZXZlcnNlKSxkZXRlY3RlZD1tZWFuKHJhdGlvX2RldGVjdGVkKSxyZXF1ZXN0cz1tZWFuKHJhdGlvX3JlcXVlc3RzKSxmYWlsPW1lYW4ocmF0aW9fZmFpbCksIGlwZGlzdD1uX2Rpc3RpbmN0KGNsaWVudCkpCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCmNsdXN0ZXJfc3VtbWFyeQoKZDNoZWF0bWFwKGNsdXN0ZXJfc3VtbWFyeVssYygtMSwtMiwtMTEpXSxjb2xvcnMgPSAiUmVkcyIsUm93dj1ULAogICAgICAgICAgd2lkdGggPSA3MDAsaGVpZ2h0ID0gNDAwLCAKICAgICAgICAgIHlheGlzX2ZvbnRfc2l6ZSA9ICI4cHQiLAogICAgICAgICAgeGF4aXNfZm9udF9zaXplID0gIjhwdCIpCmBgYAoKIyMgQ2x1c3RlcmluZyBSZXN1bHRzIApBY2NvcmRpbmcgdG8gY2x1c3RlcmluZyByZXN1bHRzCgoxLiAqKkNsdXN0ZXIgbi4gMSoqIGdyb3VwcyBjbGllbnRzIHdpdGggaGlnaCBOWCByZXBsaWVzIGFzIHdlbGwgYXMgc29tZSByZXZlcnNlIHF1ZXJpZXMgYW1vbmcgb3RoZXJzLiAqKihwb3NzaWJsZSBER0EpKioKMi4gKipDbHVzdGVyIG4uIDIqKiBncm91cHMgdGhvc2UgY2xpZW50cyBub3Qgc2hvd2luZyBER0Egb3Igb3RoZXIgbWFsd2F2ZXIgYmVoYXZpb3IgKiooUG9zc2libGUgTk9STUFMPz8pKioKMy4gKipDbHVzdGVyIG4uIDMqKiBncm91cHMgdGhvc2UgY2xpZW50cyB3aXRoIHRoZSBoaWdoZXN0ICpzYW1lZG9tYWlucyogdmFsdWVzICoqKHBvc3NpYmxlIEZhc3RmbHV4PykqKgo0LiAqKkNsdXN0ZXIgbi4gNCoqIGdyb3VwcyB0aG9zZSBjbGllbnRzIHdpdGggYSB2YWx1ZSBpbmNyZW1lbnQgIGZvciAqc2FtZWlwKiBhbmQgKnNhbWVkb21haW5zKiAqKihQb3NzaWJsZSBER0EvRmFzdGZsdXg/IE5vdCByZWFsbHkpKioKNS4gKipDbHVzdGVyIG4uIDUqKiBncm91cHMgdGhvc2UgY2xpZW50cyB3aXRoIGhpZ2ggdmFsdWVzIGluIEZBSUwgcXVlcmllcyAqKihQb3NzaWJsZSB3aGF0Pz8pKioKCipzb21lIElQUyB0byBjaGVjayoKCioyMTMuMTkxLjEwNS4yMTAqIHNob3dzIHNvbWUgcG9zc2libGUgcmFuZG9tIGdlbmVyYXRlZCBkb21haW5zCgoqMjEzLjE5MS4xMDUuMjQyKiBTaG93cyBhIGxvdCBvZiBOWCByZWNvcmRzLiBTb21lIHF1ZXJpZXMgY29udGFpbiB0aGUga2suIFRMRCAoPz8pCgojIyBULU5TRSAyRCByZXByZXNlbnRhdGlvbiB3aXRoIGNsdXN0ZXJzIAoKKipMaW1pdGF0aW9ucyBvZiBQQ0EqKgoKUENBIGlzIGEgbGluZWFyIGFsZ29yaXRobS4gSXQgd2lsbCBub3QgYmUgYWJsZSB0byBpbnRlcnByZXQgY29tcGxleCBwb2x5bm9taWFsIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIGZlYXR1cmVzLiBPbiB0aGUgb3RoZXIgaGFuZCwgdC1TTkUgaXMgYmFzZWQgb24gcHJvYmFiaWxpdHkgZGlzdHJpYnV0aW9ucyB3aXRoIHJhbmRvbSB3YWxrIG9uIG5laWdoYm9yaG9vZCBncmFwaHMgdG8gZmluZCB0aGUgc3RydWN0dXJlIHdpdGhpbiB0aGUgZGF0YS4KCgoKCgpgYGB7cn0KdHNuZV9tb2RlbDwtUnRzbmUodW5pcXVlKGNsaWVudF9wcm9maWxlX3JhdGlvWyxjKC0yLC1uY29sKGNsaWVudF9wcm9maWxlX3JhdGlvKSldKSxwY2E9RkFMU0UsIHBjYV9jZW50ZXIgPVRSVUUsIHBjYV9zY2FsZT1UUlVFLGNoZWNrX2R1cGxpY2F0ZXM9RiwKICAgICAgICAgICAgICAgICAgcGVycGxleGl0eSA9IDQwMCwgbWF4X2l0ZXIgPSAxMDAwKQpgYGAKCgpUbyByZW1lbWJlcjogSXQgc2VlbXMgdGhhdCBpZiB3ZSBnZW5lcmF0ZSAxaCBwcm9maWxlcyB0aGUgdGhlIDJEIHJlcHJlc2VudGF0aW9uIHRlbmRzIHRvIGJlIG1vcmUgZGlmZmljdWx0IHRvIGFuYWx5emUuIFRoZSBhcmUgbWFueSBwb2ludHMgdmVyeSBzaW1pbGFyIGNvcnJlc3BvbmRpbmcgdG8gdGhlIHNhbWUgY2xpZW50IHByb2ZpbGUgYXQgZGlmZmVyZW50IHRpbWUgcGVyaW9kcy4gVXNpbmcgYSBoaWdoZXIgcGVycGxleGl0eSB2YWx1ZSBzZWVtcyB0byBzb2x2ZSB0aGF0IGlzc3VlLiAKCkVhY2ggcG9pbnQgcmVwcmVzZW50cyBhIDEgaG91ciBwcm9maWxlLiBUaGUgKipzaXplKiogb2YgYSBnaXZlbiBwb2ludC9wcm9maWxlIGNvcnJlc3BvbmRzIHRvIHRoZSByZXF1ZXN0IHJhdGlvcyBmb3IgdGhhdCBwcm9maWxlLiBGaW5hbGx5LCAgKipjb2xvcioqIHByZXNlbnRzIGRpZmZlcmVudHMgY2x1c3RlcnMuClJlbWVtYmVyIHlvdSBjYW4gc2VsZWN0L2Rlc2VsZWN0IHRoZSBjbHVzdGVyIGJ5IGNsaWNraW5nIHRoZSBsZWdlbmQuIAoKYGBge3IgcGxvdCB0bnNlIGNsdXN0ZXJpbmcsIHdhcm5pbmc9RkFMU0V9CmNsaWVudF9wcm9maWxlX3VuaXF1ZTwtdW5pcXVlKGNsaWVudF9wcm9maWxlX3JhdGlvWyxjKC0yKV0pCnRzbmVfY2xpZW50X3Byb2ZpbGU8LWRhdGEuZnJhbWUodHNuZV9tb2RlbCRZLGNsdXN0ZXI9Y2xpZW50X3Byb2ZpbGVfdW5pcXVlJGNsdXN0ZXIsY2xpZW50PWNsaWVudF9wcm9maWxlX3VuaXF1ZSRjbGllbnQscmVxdWVzdHM9CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjbGllbnRfcHJvZmlsZV91bmlxdWUkcmF0aW9fcmVxdWVzdHMpCgpzYW1wbGVpZDwtc2FtcGxlKG5yb3codHNuZV9jbGllbnRfcHJvZmlsZSksbnJvdyh0c25lX2NsaWVudF9wcm9maWxlKSkKCmc8LWdncGxvdCh0c25lX2NsaWVudF9wcm9maWxlW3NhbXBsZWlkLF0sYWVzKHg9WDEseT1YMikpKwogIGdlb21fcG9pbnQoYWVzKGNvbG9yPWFzLmZhY3RvcihjbHVzdGVyKSxzaXplPXJlcXVlc3RzLHRleHQ9cGFzdGUoImlwZGRyIixjbGllbnQpKSkrCiAgI2dlb21fcG9pbnQoYWVzKHNoYXBlPWFzaWduYWNpb24pLHNpemU9MykrCiAgeWxhYigiWDEiKSt4bGFiKCJYMiIpKwogIHRoZW1lX2NsYXNzaWMoKSsKI3NjYWxlX3NoYXBlX21hbnVhbCh2YWx1ZXM9Yyg4LDYpKSsKICAgZ3VpZGVzKGNvbG9yPUZBTFNFLGFscGhhPUZBTFNFKQpnZ3Bsb3RseShnKQoKYGBgCgojIyBEZWNpc2lvbiBUcmVlIEFuYWx5c2lzCgpgYGB7ciBkdHJlZSBhbmFsaXN5cywgZmlnLmhlaWdodD05LCBmaWcud2lkdGg9MTZ9CmxpYnJhcnkocnBhcnQucGxvdCkKbGlicmFyeShycGFydCkKbGlicmFyeShycGFydC51dGlscykKCmZvcm11bGEgPC0gYXMuZm9ybXVsYShjbHVzdGVyfi4pCnRyZWU9cnBhcnQoZm9ybXVsYSxkYXRhPWNsaWVudF9wcm9maWxlX3JhdGlvWyxjKC0xLC0yLC0zLC00KV0sY29udHJvbCA9IHJwYXJ0LmNvbnRyb2wobWluc3BsaXQ9NTAwLCBjcD0wLjAwNSx4dmFsPTEwKSxtb2RlbD1ULHg9VCx5PVQpCnJwYXJ0LnBsb3QodHJlZSwKICAgICAgICAgICBleHRyYT00LCAKICAgICAgICAgICBib3gucGFsZXR0ZT0iR25CdSIsCiAgICAgICAgICAgYnJhbmNoLmx0eT01LCBzaGFkb3cuY29sPTAsIG5uPVRSVUUsIGNleCA9MC45LHVuZGVyPVQKICAgICAgICApCnByaW50Y3AodHJlZSkKYGBgCgoKIyMgQ2xpZW50cyBpbiBjbHVzdGVyIDEKCmBgYHtyfQp0c25lX2NsaWVudF9wcm9maWxlICU+JSBmaWx0ZXIoY2x1c3Rlcj09MSkgJT4lIGdyb3VwX2J5KGNsaWVudCkgJT4lIHN1bW1hcmlzZShwcm9maWxlcz1uKCkpICU+JSBhcnJhbmdlKGRlc2MocHJvZmlsZXMpKSAlPiUgZmlsdGVyKHByb2ZpbGVzPjAgKSAgJT4lIHNlbGVjdChjbGllbnQpCmBgYAoKIyMgQ2xpZW50cyBpbiBjbHVzdGVyIDIKCmBgYHtyfQp0c25lX2NsaWVudF9wcm9maWxlICU+JSBmaWx0ZXIoY2x1c3Rlcj09MikgJT4lIGdyb3VwX2J5KGNsaWVudCkgJT4lIHN1bW1hcmlzZShwcm9maWxlcz1uKCkpICU+JSBhcnJhbmdlKGRlc2MocHJvZmlsZXMpKSAlPiUgZmlsdGVyKHByb2ZpbGVzPjApICAlPiUgc2VsZWN0KGNsaWVudCkKYGBgCgojIyBDbGllbnRzIGluIGNsdXN0ZXIgMwoKYGBge3J9CnRzbmVfY2xpZW50X3Byb2ZpbGUgJT4lIGZpbHRlcihjbHVzdGVyPT0zKSAlPiUgZ3JvdXBfYnkoY2xpZW50KSAlPiUgc3VtbWFyaXNlKHByb2ZpbGVzPW4oKSkgJT4lIGFycmFuZ2UoZGVzYyhwcm9maWxlcykpICU+JSBmaWx0ZXIocHJvZmlsZXM+MCkgICU+JSBzZWxlY3QoY2xpZW50KQpgYGAKIyMgQ2xpZW50cyBpbiBjbHVzdGVyIDQKCmBgYHtyfQp0c25lX2NsaWVudF9wcm9maWxlICU+JSBmaWx0ZXIoY2x1c3Rlcj09NCkgJT4lIGdyb3VwX2J5KGNsaWVudCkgJT4lIHN1bW1hcmlzZShwcm9maWxlcz1uKCkpICU+JSBhcnJhbmdlKGRlc2MocHJvZmlsZXMpKSAlPiUgZmlsdGVyKHByb2ZpbGVzPjApICAlPiUgc2VsZWN0KGNsaWVudCkKYGBgCgojIyBDbGllbnRzIGluIGNsdXN0ZXIgNQoKYGBge3J9CnRzbmVfY2xpZW50X3Byb2ZpbGUgJT4lIGZpbHRlcihjbHVzdGVyPT01KSAlPiUgZ3JvdXBfYnkoY2xpZW50KSAlPiUgc3VtbWFyaXNlKHByb2ZpbGVzPW4oKSkgJT4lIGFycmFuZ2UoZGVzYyhwcm9maWxlcykpICU+JSBmaWx0ZXIocHJvZmlsZXM+MCkgICU+JSBzZWxlY3QoY2xpZW50KQpgYGAKCiMjIENsaWVudHMgaW4gY2x1c3RlciA2CgpgYGB7cn0KdHNuZV9jbGllbnRfcHJvZmlsZSAlPiUgZmlsdGVyKGNsdXN0ZXI9PTYpICU+JSBncm91cF9ieShjbGllbnQpICU+JSBzdW1tYXJpc2UocHJvZmlsZXM9bigpKSAlPiUgYXJyYW5nZShkZXNjKHByb2ZpbGVzKSkgJT4lIGZpbHRlcihwcm9maWxlcz4wKSAlPiUgc2VsZWN0KGNsaWVudCkKYGBgCgpgYGB7ciwgZXZhbD1GQUxTRSwgd2FybmluZz1GQUxTRSwgaW5jbHVkZT1GQUxTRX0KIyMgUENBIDJEIHJlcHJlc2VudGF0aW9uIAoKbGlicmFyeShwbG90bHkpCnBjYTwtcHJjb21wKGNsaWVudF9wcm9maWxlX3JhdGlvWyxjKC0xLC0yLC0zLC1uY29sKGNsaWVudF9wcm9maWxlX3JhdGlvKSldLCBjZW50ZXIgPSBUUlVFLCBzY2FsZS4gPSBUUlVFKSAKCnBjYV9jbGllbnRfcHJvZmlsZTwtZGF0YS5mcmFtZShwY2EkeCxjbGllbnQ9Y2xpZW50X3Byb2ZpbGVfcmF0aW8kY2xpZW50LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGV0ZWN0ZWQ9aWZlbHNlKGNsaWVudF9wcm9maWxlX3JhdGlvJHJhdGlvX2RldGVjdGVkPjAsImRldGVjdGVkIiwibm90ZGV0ZWN0ZWQiKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJhdGlvX3JlcXVlc3RlZD1jbGllbnRfcHJvZmlsZV9yYXRpbyRyYXRpb19yZXF1ZXN0cywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlcXVlc3RlZD1jbGllbnRfcHJvZmlsZV9yYXRpbyR0b3RfcmVxdWVzdHMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjbHVzdGVyPWNsaWVudF9wcm9maWxlX3JhdGlvJGNsdXN0ZXIKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICkKCgpnPC1nZ3Bsb3QocGNhX2NsaWVudF9wcm9maWxlLGFlcyh4PVBDMyx5PVBDMikpKwogIGdlb21faml0dGVyKGFlcyhjb2xvcj1hcy5mYWN0b3IoY2x1c3RlciksdGV4dD1wYXN0ZShjbGllbnQsIjxCUj4iLHJlcXVlc3RlZCkpKSsKICAjZ2VvbV9wb2ludChhZXMoc2hhcGU9YXNpZ25hY2lvbiksc2l6ZT0zKSsKICB5bGFiKCJQQzEiKSt4bGFiKCJQQzIiKSsKICB0aGVtZV9jbGFzc2ljKCkrCiNzY2FsZV9zaGFwZV9tYW51YWwodmFsdWVzPWMoOCw2KSkrCiAgIGd1aWRlcyhjb2xvcj1GQUxTRSxhbHBoYT1GQUxTRSkKZwoKZ2dwbG90bHkoZykKYGBgCgoK