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/pbz2iqcedn4kef5/wbone-client-profile-24hs-last3weeks.csv?dl=1

max_num_profiles=21
windowsize<-60*24 # 24Hs window
initial_tm<-domains$timestamp[nrow(domains)-1]
clients_profiles<-data.frame()
clients_profiles<-foreach (profilenum = 0:max_num_profiles,.combine='rbind', .multicombine=FALSE ) %dopar% {
  currenttm<-initial_tm - (minutes(windowsize)*profilenum)
  domains_window<-domains %>% filter(timestamp > currenttm -minutes(windowsize) & timestamp < currenttm)
 
   # Total amount of domans requested 
  n_requests <-domains_window %>% group_by(client) %>% 
  summarise(tot_requests=n()) #%>% arrange(desc(tot_requests))
  # Total amount of domains detected by the NN and ratio.
  n_detected<-domains_window %>% group_by(client) %>% 
  summarise(tot_detected=sum(ifelse(dga.class==1,1,0),na.rm=T))
  n_detected$ratio_detected<-n_detected$tot_detected/n_requests$tot_requests
  # Total amount of NXDOMAIN and ratio
  n_nx<-domains_window  %>% group_by(client) %>% 
  summarise(tot_nx=sum(grepl("NXDOMAIN",answer)))
  n_nx$ratio_nx<-n_nx$tot_nx/n_requests$tot_requests
  # Total amount of SERVFAIL and ratio
  n_fail<-domains_window  %>% group_by(client) %>% 
  summarise(tot_fail=sum(grepl("SERVFAIL",answer)))
  n_fail$ratio_fail<-n_fail$tot_fail/n_requests$tot_requests
  # Total amount of MX and ratio
  n_mx<-domains_window  %>%  group_by(client)  %>% 
  summarise(tot_mx=sum(query_type=="MX")) 
  n_mx$ratio_mx<-n_mx$tot_mx/n_requests$tot_requests
  
  n_reverse <- domains_window %>% group_by(client) %>%
  summarise(tot_reverse=sum(grepl("in-addr.arpa.",query))) 
  n_reverse$ratio_reverse<-n_reverse$tot_reverse/n_requests$tot_requests
    
  # domains sharing the same IP 
  all_sameip<-domains_window %>%  
  filter(query_type=="A" & answer_ip!="NXDOMAIN" & answer_ip!="SERVFAIL") %>%  
  group_by(client,answer_ip) %>% 
  summarise(tot_distquery=n_distinct(query)) 
  #The max (int) amount of requests where one IP resolved to more than one domain.
  max_sameip<-all_sameip %>% filter(tot_distquery >1) %>% group_by(client) %>% summarise(max_sameip=max(tot_distquery))
   #The avg  of requests where one IP resolved to more than one domain.
  avg_sameip<-all_sameip %>% filter(tot_distquery >1) %>% group_by(client) %>% summarise(avg_sameip=mean(tot_distquery))
  #The avg  of requests where one IP resolved to more than one domain.
  sd_sameip<-all_sameip %>% filter(tot_distquery >1) %>% group_by(client) %>% summarise(sd_sameip=sd(tot_distquery))
  #The amount of groups of same IPs that resolved to more than one domain.
  n_sameip<-all_sameip %>% filter(tot_distquery >1) %>% group_by(client) %>% summarise(tot_sameip=sum(tot_distquery))
  
  
  # The amount of domains that each of them had more than 1 IP.
  # (Fast flux)
  all_samedomain<-domains_window %>%  
  filter(query_type=="A" & answer_ip!="NXDOMAIN" & answer_ip!="SERVFAIL") %>%  
  group_by(client,query) %>% 
  summarise(tot_distdomain=n_distinct(answer_ip)) 
  
  #The max (int) amount of requests where one domain resolved to more than one IP
  max_samedomain<-all_samedomain %>% filter(tot_distdomain >1) %>% group_by(client) %>% summarise(max_samedomain=max(tot_distdomain))
  #The avg  of requests where one domain resolved to more than one IP
  avg_samedomain<-all_samedomain %>% filter(tot_distdomain >1) %>% group_by(client) %>% summarise(avg_samedomain=mean(tot_distdomain))
  #The avg  of requests where one domain resolved to more than one IP
  sd_samedomain<-all_samedomain %>% filter(tot_distdomain >1) %>% group_by(client) %>% summarise(sd_samedomain=sd(tot_distdomain))
  #The amount of groups of same domain that resolved to more than one IP
  n_samedomain<-all_samedomain %>% filter(tot_distdomain >1) %>% group_by(client) %>% summarise(tot_samedomain=sum(tot_distdomain))
  
  
  # Profile Database Creation ------
  
  client_profile<-inner_join(n_requests,n_detected,by="client") %>% 
  inner_join(n_nx,by="client") %>%
  inner_join(n_mx,by="client") %>%
  inner_join(n_fail,by="client") %>%
  inner_join(n_reverse,by="client") %>%
  full_join(max_sameip,by="client") %>%
  full_join(n_sameip,by="client") %>%
  full_join(avg_sameip,by="client") %>%
  full_join(sd_sameip,by="client")  %>%
  full_join(n_samedomain,by="client") %>%
  full_join(max_samedomain,by="client") %>%
  full_join(avg_samedomain,by="client") %>%
  full_join(sd_samedomain,by="client") 
    
  
  # replace NAs with zeros
  client_profile<-client_profile %>% mutate(max_sameip=ifelse(is.na(max_sameip),0,max_sameip),
                                            tot_sameip=ifelse(is.na(tot_sameip),0,tot_sameip),
                                            avg_sameip=ifelse(is.na(avg_sameip),0,avg_sameip),
                                            sd_sameip=ifelse(is.na(sd_sameip),0,sd_sameip)
                                            
                                              )
  client_profile<-client_profile %>% mutate(max_samedomain=ifelse(is.na(max_samedomain),0,max_samedomain),
                                            tot_samedomain=ifelse(is.na(tot_samedomain),0,tot_samedomain),
                                            avg_samedomain=ifelse(is.na(avg_samedomain),0,avg_samedomain),
                                            sd_samedomain=ifelse(is.na(sd_samedomain),0,sd_samedomain)
                                            )
  
  client_profile<-client_profile %>% mutate(ratio_tot_samedomain=tot_samedomain/tot_requests) 
  client_profile<-client_profile %>% mutate(ratio_max_samedomain=max_samedomain/tot_requests) 
  client_profile<-client_profile %>% mutate(ratio_avg_samedomain=avg_samedomain/tot_requests) 
  client_profile<-client_profile %>% mutate(ratio_sd_samedomain=sd_samedomain/tot_requests) 
  
  client_profile<-client_profile %>% mutate(ratio_tot_sameip=tot_sameip/tot_requests) 
   client_profile<-client_profile %>% mutate(ratio_max_sameip=max_sameip/tot_requests) 
   client_profile<-client_profile %>% mutate(ratio_avg_sameip=avg_sameip/tot_requests) 
   client_profile<-client_profile %>% mutate(ratio_sd_sameip=sd_sameip/tot_requests) 
 
  
  
  
   client_profile$profilenum<-profilenum
  client_profile$currenttm<-currenttm
  #clients_profiles<-rbind(clients_profiles,client_profile)
  #currenttm<-currenttm - minutes(windowsize)
  
  return(client_profile)
}

Selecting 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_24hs_last3weeks-2.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 those clients with the highest samedomains values (possible Fastflux?)
  2. Cluster n. 2 groups those clients with a value increment for sameip and samedomains (Possible DGA/Fastflux? Not really)
  3. Cluster n. 3 groups clients with high NX replies as well as some reverse queries among others. (possible DGA)
  4. Cluster n. 4 groups those clients not showing DGA or other malwaver behavior (Possible NORMAL??)
  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)
LS0tCnRpdGxlOiAiV0IgY2xpZW50IDI0IGhvdXJzIHByb2ZpbGUgZ2VuZXJhdGlvbiAobGFzdCAzIHdlZWtzKSAiCm91dHB1dDogCiAgaHRtbF9ub3RlYm9vazogCiAgICBjb2RlX2ZvbGRpbmc6IGhpZGUKICAgIHRvYzogeWVzCi0tLQoKPHN0eWxlIHR5cGU9InRleHQvY3NzIj4KCmJvZHksIHRkIHsKICAgZm9udC1zaXplOiAxNHB4Owp9CmNvZGUucnsKICBmb250LXNpemU6IDlweDsKfQpwcmUgewogIGZvbnQtc2l6ZTogOXB4Cn0KPC9zdHlsZT4KCgoKYGBge3IsIGVjaG89RkFMU0UsIGZpZy5oZWlnaHQ9MTIsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KGx1YnJpZGF0ZSkKbGlicmFyeShkb01DKQpyZWdpc3RlckRvTUMoY29yZXM9MykKZG9tYWluczwtcmVhZF9jc3YoZmlsZT0iL2hvbWUvaGFycG8vRHJvcGJveC9vbmdvaW5nLXdvcmsvZ2l0LXJlcG9zL2RnYS13Yi9kYXRhc2V0cy9kYXRhYmFzZS5jc3YiLHByb2dyZXNzID0gVCkKc3VtbWFyeShkb21haW5zKQoKICAjZG9tYWlucyAlPiUgZmlsdGVyKGlzLm5hKGRnYS5jbGFzcykpIApgYGAKCgoKCiMjIEZlYXR1cmUgR2VuZXJhdGlvbgoKRmVhdHVyZSBmaWxlIGF2YWlsYWJsZSBhdCAgaHR0cHM6Ly93d3cuZHJvcGJveC5jb20vcy9wYnoyaXFjZWRuNGtlZjUvd2JvbmUtY2xpZW50LXByb2ZpbGUtMjRocy1sYXN0M3dlZWtzLmNzdj9kbD0xCgoKYGBge3IgZmVhdHVyZSBnZW5lcmF0aW9uLCBlY2hvPVRSVUV9Cm1heF9udW1fcHJvZmlsZXM9MjEKd2luZG93c2l6ZTwtNjAqMjQgIyAyNEhzIHdpbmRvdwppbml0aWFsX3RtPC1kb21haW5zJHRpbWVzdGFtcFtucm93KGRvbWFpbnMpLTFdCmNsaWVudHNfcHJvZmlsZXM8LWRhdGEuZnJhbWUoKQoKY2xpZW50c19wcm9maWxlczwtZm9yZWFjaCAocHJvZmlsZW51bSA9IDA6bWF4X251bV9wcm9maWxlcywuY29tYmluZT0ncmJpbmQnLCAubXVsdGljb21iaW5lPUZBTFNFICkgJWRvcGFyJSB7CiAgY3VycmVudHRtPC1pbml0aWFsX3RtIC0gKG1pbnV0ZXMod2luZG93c2l6ZSkqcHJvZmlsZW51bSkKICBkb21haW5zX3dpbmRvdzwtZG9tYWlucyAlPiUgZmlsdGVyKHRpbWVzdGFtcCA+IGN1cnJlbnR0bSAtbWludXRlcyh3aW5kb3dzaXplKSAmIHRpbWVzdGFtcCA8IGN1cnJlbnR0bSkKIAogICAjIFRvdGFsIGFtb3VudCBvZiBkb21hbnMgcmVxdWVzdGVkIAogIG5fcmVxdWVzdHMgPC1kb21haW5zX3dpbmRvdyAlPiUgZ3JvdXBfYnkoY2xpZW50KSAlPiUgCiAgc3VtbWFyaXNlKHRvdF9yZXF1ZXN0cz1uKCkpICMlPiUgYXJyYW5nZShkZXNjKHRvdF9yZXF1ZXN0cykpCgogICMgVG90YWwgYW1vdW50IG9mIGRvbWFpbnMgZGV0ZWN0ZWQgYnkgdGhlIE5OIGFuZCByYXRpby4KICBuX2RldGVjdGVkPC1kb21haW5zX3dpbmRvdyAlPiUgZ3JvdXBfYnkoY2xpZW50KSAlPiUgCiAgc3VtbWFyaXNlKHRvdF9kZXRlY3RlZD1zdW0oaWZlbHNlKGRnYS5jbGFzcz09MSwxLDApLG5hLnJtPVQpKQogIG5fZGV0ZWN0ZWQkcmF0aW9fZGV0ZWN0ZWQ8LW5fZGV0ZWN0ZWQkdG90X2RldGVjdGVkL25fcmVxdWVzdHMkdG90X3JlcXVlc3RzCgoKICAjIFRvdGFsIGFtb3VudCBvZiBOWERPTUFJTiBhbmQgcmF0aW8KICBuX254PC1kb21haW5zX3dpbmRvdyAgJT4lIGdyb3VwX2J5KGNsaWVudCkgJT4lIAogIHN1bW1hcmlzZSh0b3Rfbng9c3VtKGdyZXBsKCJOWERPTUFJTiIsYW5zd2VyKSkpCiAgbl9ueCRyYXRpb19ueDwtbl9ueCR0b3Rfbngvbl9yZXF1ZXN0cyR0b3RfcmVxdWVzdHMKCiAgIyBUb3RhbCBhbW91bnQgb2YgU0VSVkZBSUwgYW5kIHJhdGlvCiAgbl9mYWlsPC1kb21haW5zX3dpbmRvdyAgJT4lIGdyb3VwX2J5KGNsaWVudCkgJT4lIAogIHN1bW1hcmlzZSh0b3RfZmFpbD1zdW0oZ3JlcGwoIlNFUlZGQUlMIixhbnN3ZXIpKSkKICBuX2ZhaWwkcmF0aW9fZmFpbDwtbl9mYWlsJHRvdF9mYWlsL25fcmVxdWVzdHMkdG90X3JlcXVlc3RzCgogICMgVG90YWwgYW1vdW50IG9mIE1YIGFuZCByYXRpbwogIG5fbXg8LWRvbWFpbnNfd2luZG93ICAlPiUgIGdyb3VwX2J5KGNsaWVudCkgICU+JSAKICBzdW1tYXJpc2UodG90X214PXN1bShxdWVyeV90eXBlPT0iTVgiKSkgCiAgbl9teCRyYXRpb19teDwtbl9teCR0b3RfbXgvbl9yZXF1ZXN0cyR0b3RfcmVxdWVzdHMKCiAgCiAgbl9yZXZlcnNlIDwtIGRvbWFpbnNfd2luZG93ICU+JSBncm91cF9ieShjbGllbnQpICU+JQogIHN1bW1hcmlzZSh0b3RfcmV2ZXJzZT1zdW0oZ3JlcGwoImluLWFkZHIuYXJwYS4iLHF1ZXJ5KSkpIAogIG5fcmV2ZXJzZSRyYXRpb19yZXZlcnNlPC1uX3JldmVyc2UkdG90X3JldmVyc2Uvbl9yZXF1ZXN0cyR0b3RfcmVxdWVzdHMKCiAgICAKICAjIGRvbWFpbnMgc2hhcmluZyB0aGUgc2FtZSBJUCAKICBhbGxfc2FtZWlwPC1kb21haW5zX3dpbmRvdyAlPiUgIAogIGZpbHRlcihxdWVyeV90eXBlPT0iQSIgJiBhbnN3ZXJfaXAhPSJOWERPTUFJTiIgJiBhbnN3ZXJfaXAhPSJTRVJWRkFJTCIpICU+JSAgCiAgZ3JvdXBfYnkoY2xpZW50LGFuc3dlcl9pcCkgJT4lIAogIHN1bW1hcmlzZSh0b3RfZGlzdHF1ZXJ5PW5fZGlzdGluY3QocXVlcnkpKSAKCiAgI1RoZSBtYXggKGludCkgYW1vdW50IG9mIHJlcXVlc3RzIHdoZXJlIG9uZSBJUCByZXNvbHZlZCB0byBtb3JlIHRoYW4gb25lIGRvbWFpbi4KICBtYXhfc2FtZWlwPC1hbGxfc2FtZWlwICU+JSBmaWx0ZXIodG90X2Rpc3RxdWVyeSA+MSkgJT4lIGdyb3VwX2J5KGNsaWVudCkgJT4lIHN1bW1hcmlzZShtYXhfc2FtZWlwPW1heCh0b3RfZGlzdHF1ZXJ5KSkKCiAgICNUaGUgYXZnICBvZiByZXF1ZXN0cyB3aGVyZSBvbmUgSVAgcmVzb2x2ZWQgdG8gbW9yZSB0aGFuIG9uZSBkb21haW4uCiAgYXZnX3NhbWVpcDwtYWxsX3NhbWVpcCAlPiUgZmlsdGVyKHRvdF9kaXN0cXVlcnkgPjEpICU+JSBncm91cF9ieShjbGllbnQpICU+JSBzdW1tYXJpc2UoYXZnX3NhbWVpcD1tZWFuKHRvdF9kaXN0cXVlcnkpKQoKICAjVGhlIGF2ZyAgb2YgcmVxdWVzdHMgd2hlcmUgb25lIElQIHJlc29sdmVkIHRvIG1vcmUgdGhhbiBvbmUgZG9tYWluLgogIHNkX3NhbWVpcDwtYWxsX3NhbWVpcCAlPiUgZmlsdGVyKHRvdF9kaXN0cXVlcnkgPjEpICU+JSBncm91cF9ieShjbGllbnQpICU+JSBzdW1tYXJpc2Uoc2Rfc2FtZWlwPXNkKHRvdF9kaXN0cXVlcnkpKQoKICAjVGhlIGFtb3VudCBvZiBncm91cHMgb2Ygc2FtZSBJUHMgdGhhdCByZXNvbHZlZCB0byBtb3JlIHRoYW4gb25lIGRvbWFpbi4KICBuX3NhbWVpcDwtYWxsX3NhbWVpcCAlPiUgZmlsdGVyKHRvdF9kaXN0cXVlcnkgPjEpICU+JSBncm91cF9ieShjbGllbnQpICU+JSBzdW1tYXJpc2UodG90X3NhbWVpcD1zdW0odG90X2Rpc3RxdWVyeSkpCiAgCiAgCiAgIyBUaGUgYW1vdW50IG9mIGRvbWFpbnMgdGhhdCBlYWNoIG9mIHRoZW0gaGFkIG1vcmUgdGhhbiAxIElQLgogICMgKEZhc3QgZmx1eCkKICBhbGxfc2FtZWRvbWFpbjwtZG9tYWluc193aW5kb3cgJT4lICAKICBmaWx0ZXIocXVlcnlfdHlwZT09IkEiICYgYW5zd2VyX2lwIT0iTlhET01BSU4iICYgYW5zd2VyX2lwIT0iU0VSVkZBSUwiKSAlPiUgIAogIGdyb3VwX2J5KGNsaWVudCxxdWVyeSkgJT4lIAogIHN1bW1hcmlzZSh0b3RfZGlzdGRvbWFpbj1uX2Rpc3RpbmN0KGFuc3dlcl9pcCkpIAogIAogICNUaGUgbWF4IChpbnQpIGFtb3VudCBvZiByZXF1ZXN0cyB3aGVyZSBvbmUgZG9tYWluIHJlc29sdmVkIHRvIG1vcmUgdGhhbiBvbmUgSVAKICBtYXhfc2FtZWRvbWFpbjwtYWxsX3NhbWVkb21haW4gJT4lIGZpbHRlcih0b3RfZGlzdGRvbWFpbiA+MSkgJT4lIGdyb3VwX2J5KGNsaWVudCkgJT4lIHN1bW1hcmlzZShtYXhfc2FtZWRvbWFpbj1tYXgodG90X2Rpc3Rkb21haW4pKQoKICAjVGhlIGF2ZyAgb2YgcmVxdWVzdHMgd2hlcmUgb25lIGRvbWFpbiByZXNvbHZlZCB0byBtb3JlIHRoYW4gb25lIElQCiAgYXZnX3NhbWVkb21haW48LWFsbF9zYW1lZG9tYWluICU+JSBmaWx0ZXIodG90X2Rpc3Rkb21haW4gPjEpICU+JSBncm91cF9ieShjbGllbnQpICU+JSBzdW1tYXJpc2UoYXZnX3NhbWVkb21haW49bWVhbih0b3RfZGlzdGRvbWFpbikpCgogICNUaGUgYXZnICBvZiByZXF1ZXN0cyB3aGVyZSBvbmUgZG9tYWluIHJlc29sdmVkIHRvIG1vcmUgdGhhbiBvbmUgSVAKICBzZF9zYW1lZG9tYWluPC1hbGxfc2FtZWRvbWFpbiAlPiUgZmlsdGVyKHRvdF9kaXN0ZG9tYWluID4xKSAlPiUgZ3JvdXBfYnkoY2xpZW50KSAlPiUgc3VtbWFyaXNlKHNkX3NhbWVkb21haW49c2QodG90X2Rpc3Rkb21haW4pKQoKICAjVGhlIGFtb3VudCBvZiBncm91cHMgb2Ygc2FtZSBkb21haW4gdGhhdCByZXNvbHZlZCB0byBtb3JlIHRoYW4gb25lIElQCiAgbl9zYW1lZG9tYWluPC1hbGxfc2FtZWRvbWFpbiAlPiUgZmlsdGVyKHRvdF9kaXN0ZG9tYWluID4xKSAlPiUgZ3JvdXBfYnkoY2xpZW50KSAlPiUgc3VtbWFyaXNlKHRvdF9zYW1lZG9tYWluPXN1bSh0b3RfZGlzdGRvbWFpbikpCiAgCiAgCiAgIyBQcm9maWxlIERhdGFiYXNlIENyZWF0aW9uIC0tLS0tLQogIAogIGNsaWVudF9wcm9maWxlPC1pbm5lcl9qb2luKG5fcmVxdWVzdHMsbl9kZXRlY3RlZCxieT0iY2xpZW50IikgJT4lIAogIGlubmVyX2pvaW4obl9ueCxieT0iY2xpZW50IikgJT4lCiAgaW5uZXJfam9pbihuX214LGJ5PSJjbGllbnQiKSAlPiUKICBpbm5lcl9qb2luKG5fZmFpbCxieT0iY2xpZW50IikgJT4lCiAgaW5uZXJfam9pbihuX3JldmVyc2UsYnk9ImNsaWVudCIpICU+JQogIGZ1bGxfam9pbihtYXhfc2FtZWlwLGJ5PSJjbGllbnQiKSAlPiUKICBmdWxsX2pvaW4obl9zYW1laXAsYnk9ImNsaWVudCIpICU+JQogIGZ1bGxfam9pbihhdmdfc2FtZWlwLGJ5PSJjbGllbnQiKSAlPiUKICBmdWxsX2pvaW4oc2Rfc2FtZWlwLGJ5PSJjbGllbnQiKSAgJT4lCiAgZnVsbF9qb2luKG5fc2FtZWRvbWFpbixieT0iY2xpZW50IikgJT4lCiAgZnVsbF9qb2luKG1heF9zYW1lZG9tYWluLGJ5PSJjbGllbnQiKSAlPiUKICBmdWxsX2pvaW4oYXZnX3NhbWVkb21haW4sYnk9ImNsaWVudCIpICU+JQogIGZ1bGxfam9pbihzZF9zYW1lZG9tYWluLGJ5PSJjbGllbnQiKSAKICAgIAogIAogICMgcmVwbGFjZSBOQXMgd2l0aCB6ZXJvcwogIGNsaWVudF9wcm9maWxlPC1jbGllbnRfcHJvZmlsZSAlPiUgbXV0YXRlKG1heF9zYW1laXA9aWZlbHNlKGlzLm5hKG1heF9zYW1laXApLDAsbWF4X3NhbWVpcCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdG90X3NhbWVpcD1pZmVsc2UoaXMubmEodG90X3NhbWVpcCksMCx0b3Rfc2FtZWlwKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhdmdfc2FtZWlwPWlmZWxzZShpcy5uYShhdmdfc2FtZWlwKSwwLGF2Z19zYW1laXApLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNkX3NhbWVpcD1pZmVsc2UoaXMubmEoc2Rfc2FtZWlwKSwwLHNkX3NhbWVpcCkKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICkKICBjbGllbnRfcHJvZmlsZTwtY2xpZW50X3Byb2ZpbGUgJT4lIG11dGF0ZShtYXhfc2FtZWRvbWFpbj1pZmVsc2UoaXMubmEobWF4X3NhbWVkb21haW4pLDAsbWF4X3NhbWVkb21haW4pLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRvdF9zYW1lZG9tYWluPWlmZWxzZShpcy5uYSh0b3Rfc2FtZWRvbWFpbiksMCx0b3Rfc2FtZWRvbWFpbiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXZnX3NhbWVkb21haW49aWZlbHNlKGlzLm5hKGF2Z19zYW1lZG9tYWluKSwwLGF2Z19zYW1lZG9tYWluKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZF9zYW1lZG9tYWluPWlmZWxzZShpcy5uYShzZF9zYW1lZG9tYWluKSwwLHNkX3NhbWVkb21haW4pCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKQogIAogIGNsaWVudF9wcm9maWxlPC1jbGllbnRfcHJvZmlsZSAlPiUgbXV0YXRlKHJhdGlvX3RvdF9zYW1lZG9tYWluPXRvdF9zYW1lZG9tYWluL3RvdF9yZXF1ZXN0cykgCiAgY2xpZW50X3Byb2ZpbGU8LWNsaWVudF9wcm9maWxlICU+JSBtdXRhdGUocmF0aW9fbWF4X3NhbWVkb21haW49bWF4X3NhbWVkb21haW4vdG90X3JlcXVlc3RzKSAKICBjbGllbnRfcHJvZmlsZTwtY2xpZW50X3Byb2ZpbGUgJT4lIG11dGF0ZShyYXRpb19hdmdfc2FtZWRvbWFpbj1hdmdfc2FtZWRvbWFpbi90b3RfcmVxdWVzdHMpIAogIGNsaWVudF9wcm9maWxlPC1jbGllbnRfcHJvZmlsZSAlPiUgbXV0YXRlKHJhdGlvX3NkX3NhbWVkb21haW49c2Rfc2FtZWRvbWFpbi90b3RfcmVxdWVzdHMpIAogIAogIGNsaWVudF9wcm9maWxlPC1jbGllbnRfcHJvZmlsZSAlPiUgbXV0YXRlKHJhdGlvX3RvdF9zYW1laXA9dG90X3NhbWVpcC90b3RfcmVxdWVzdHMpIAogICBjbGllbnRfcHJvZmlsZTwtY2xpZW50X3Byb2ZpbGUgJT4lIG11dGF0ZShyYXRpb19tYXhfc2FtZWlwPW1heF9zYW1laXAvdG90X3JlcXVlc3RzKSAKICAgY2xpZW50X3Byb2ZpbGU8LWNsaWVudF9wcm9maWxlICU+JSBtdXRhdGUocmF0aW9fYXZnX3NhbWVpcD1hdmdfc2FtZWlwL3RvdF9yZXF1ZXN0cykgCiAgIGNsaWVudF9wcm9maWxlPC1jbGllbnRfcHJvZmlsZSAlPiUgbXV0YXRlKHJhdGlvX3NkX3NhbWVpcD1zZF9zYW1laXAvdG90X3JlcXVlc3RzKSAKIAogIAogIAogIAogICBjbGllbnRfcHJvZmlsZSRwcm9maWxlbnVtPC1wcm9maWxlbnVtCiAgY2xpZW50X3Byb2ZpbGUkY3VycmVudHRtPC1jdXJyZW50dG0KICAjY2xpZW50c19wcm9maWxlczwtcmJpbmQoY2xpZW50c19wcm9maWxlcyxjbGllbnRfcHJvZmlsZSkKICAjY3VycmVudHRtPC1jdXJyZW50dG0gLSBtaW51dGVzKHdpbmRvd3NpemUpCiAgCiAgcmV0dXJuKGNsaWVudF9wcm9maWxlKQp9CmBgYAoKCmBgYHtyIHNhdmUtY3N2LCBldmFsPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQpyZWFkcjo6d3JpdGVfY3N2KGNsaWVudHNfcHJvZmlsZXMsIi4uL2RhdGEvd2JvbmUtY2xpZW50LXByb2ZpbGUtMjRocy1sYXN0M3dlZWtzLmNzdiIpCmNsaWVudHNfcHJvZmlsZXMKYGBgCgpgYGB7ciByZWFkLWNzdiwgZXZhbD1GQUxTRSwgaW5jbHVkZT1GQUxTRX0KY2xpZW50c19wcm9maWxlczwtcmVhZHI6OnJlYWRfY3N2KCIuLi9kYXRhL3dib25lLWNsaWVudC1wcm9maWxlLTI0aHMtbGFzdDN3ZWVrcy5jc3YiKQpjbGllbnRzX3Byb2ZpbGVzICU+JSBncm91cF9ieShjbGllbnQpICU+JSBzdW1tYXJpc2Uobm9mcHJvZmlsZXM9bl9kaXN0aW5jdChwcm9maWxlbnVtKSkKY2xpZW50c19wcm9maWxlcyAlPiUgZ3JvdXBfYnkoY2xpZW50KSAlPiUgc3VtbWFyaXNlKHRvdF9yZXF1ZXN0cz1zdW0odG90X3JlcXVlc3RzKSkKY2xpZW50c19wcm9maWxlcyAlPiUgZ3JvdXBfYnkoY2xpZW50KSAlPiUgc3VtbWFyaXNlKG49bigpKQpkb21haW5zICU+JSBmaWx0ZXIoY2xpZW50PT0iMS4yMDIuMjM2LjEzNiIpCmNsaWVudHNfcHJvZmlsZXMgJT4lIGZpbHRlcihjbGllbnQ9PSIxLjIwMi4yMzYuMTM2IikKYGBgCgoKYGBge3IgcGxvdC10bS1wcm9maWxlcywgZXZhbD1GQUxTRSwgaW5jbHVkZT1GQUxTRX0KCmdncGxvdChjbGllbnRzX3Byb2ZpbGVzKSsKICBnZW9tX2xpbmUoYWVzKHg9Y3VycmVudHRtLHk9cmF0aW9fZGV0ZWN0ZWQpLGNvbG9yPSdyZWQnKSsKICBnZW9tX2xpbmUoYWVzKHg9Y3VycmVudHRtLHk9cmF0aW9fbngpLGNvbG9yPSdibHVlJykrCiAgZ2VvbV9saW5lKGFlcyh4PWN1cnJlbnR0bSx5PXJhdGlvX214KSxjb2xvcj0nb3JhbmdlJykrCiAgZ2VvbV9saW5lKGFlcyh4PWN1cnJlbnR0bSx5PXJhdGlvX2ZhaWwpLGNvbG9yPSdza3libHVlJykrCiAgZ2VvbV9saW5lKGFlcyh4PWN1cnJlbnR0bSx5PXJhdGlvX3RvdF9zYW1laXApLGNvbG9yPSdncmVlbicpKwogIHhsYWIoIkRhdGV0aW1lIikreWxhYigidmFsdWUiKSsKICB0aGVtZV9idygpKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkpKwogIGZhY2V0X3dyYXAofmNsaWVudCxzY2FsZXMgPSAiZnJlZV95IikrCiAgdGhlbWUoc3RyaXAudGV4dC55ID0gZWxlbWVudF9ibGFuaygpLCBzdHJpcC50ZXh0Lng9IGVsZW1lbnRfYmxhbmsoKSxzdHJpcC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGV4dD1lbGVtZW50X3RleHQoc2l6ZT01KQogICAgICAgICkKYGBgCgoKIyMgU2VsZWN0aW5nIGZlYXR1cmVzCgpgYGB7ciBzZWxlY3RpbmcgZmVhdHVyZXN9CmNsaWVudF9wcm9maWxlX3NlbGVjdGVkX2ZlYXR1cmVzPC1jbGllbnRzX3Byb2ZpbGVzICAgJT4lIG11dGF0ZShyYXRpb19yZXF1ZXN0cz10b3RfcmVxdWVzdHMvbWF4KHRvdF9yZXF1ZXN0cykpICU+JQogICNzZWxlY3QoY2xpZW50LHRvdF9yZXF1ZXN0cywgdG90X2RldGVjdGVkLHRvdF9ueCx0b3RfbXgsdG90X2ZhaWwsdG90X3JldmVyc2UsdG90X3NhbWVkb21haW4sdG90X3NhbWVpcCkKIHNlbGVjdChjbGllbnQsdG90X3JlcXVlc3RzLHJhdGlvX3JlcXVlc3RzLHJhdGlvX2RldGVjdGVkLHJhdGlvX254LHJhdGlvX214LHJhdGlvX2ZhaWwscmF0aW9fcmV2ZXJzZSwKICAgICAgICByYXRpb190b3Rfc2FtZWRvbWFpbixyYXRpb190b3Rfc2FtZWlwLAogICAgICAgIHJhdGlvX2F2Z19zYW1lZG9tYWluLHJhdGlvX2F2Z19zYW1laXAsCiAgICAgICAgcmF0aW9fc2Rfc2FtZWRvbWFpbixyYXRpb19zZF9zYW1laXApCmNsaWVudF9wcm9maWxlX3JhdGlvPC11bmlxdWUoY2xpZW50X3Byb2ZpbGVfcmF0aW8pCiAgI3NlbGVjdChjbGllbnQsdG90X3JlcXVlc3RzLHRvdF9kZXRlY3RlZCxtYXhfc2FtZWlwLGF2Z19zYW1laXAsc2Rfc2FtZWlwLG1heF9zYW1lZG9tYWluLGF2Z19zYW1lZG9tYWluLG1heF9zYW1lZG9tYWluLHJhdGlvX254KQpjbGllbnRfcHJvZmlsZV9zZWxlY3RlZF9mZWF0dXJlcwpgYGAKCiMjIE1hdHJpeCBDb3JyZWxhdGlvbiBoZWF0bWFwCgpgYGB7ciwgZmlnLmhlaWdodD04LCBmaWcud2lkdGg9MTJ9CmxpYnJhcnkoZDNoZWF0bWFwKQpjb3JyX2NsaWVudF9wcm9maWxlX3NlbGVjdGVkX2ZlYXR1cmVzPC1jb3IoY2xpZW50X3Byb2ZpbGVfc2VsZWN0ZWRfZmVhdHVyZXNbLGMoLTEsLTIpXSx1c2U9ImNvbXBsZXRlLm9icyIpCmQzaGVhdG1hcChjb3JyX2NsaWVudF9wcm9maWxlX3NlbGVjdGVkX2ZlYXR1cmVzLHN5bW09VCxjb2xvcnMgPSAiUmVkcyIsUm93dj1ULAogICAgICAgICAgd2lkdGggPSA2MDAsaGVpZ2h0ID0gNDAwLCAKICAgICAgICAgIHlheGlzX2ZvbnRfc2l6ZSA9ICI4cHQiLAogICAgICAgICAgeGF4aXNfZm9udF9zaXplID0gIjhwdCIpCmBgYAojIyBQZXIgRmVhdHVyZSBTY2F0dGVyIHBsb3QKCmBgYHtyLCBmaWcuaGVpZ2h0PTEyfQpwYWlycyhjbGllbnRfcHJvZmlsZV9zZWxlY3RlZF9mZWF0dXJlc1ssYygtMSwtMildLGNleC5sYWJlbHMgPSAxLjYpCmBgYAoKYGBge3J9CmdncGxvdChjbGllbnRfcHJvZmlsZV9yYXRpbyxhZXMocmF0aW9fbngscmF0aW9fZGV0ZWN0ZWQpKSsKICBnZW9tX3BvaW50KGFlcyhhbHBoYT10b3RfcmVxdWVzdHMpLGNvbG9yPSdza3libHVlJykrCiAgZ2VvbV9zbW9vdGgobWV0aG9kID0gJ2xtJyxjb2xvcj0nb3JhbmdlJykrCiAgdGhlbWVfYncoKQpgYGAKYGBge3IsIGV2YWw9RkFMU0UsIGluY2x1ZGU9RkFMU0V9CiMjIENsaWVudCBmZWF0dXJlcyBoZWF0bWFwCiMjIChvbmx5IGNvbnNpZGVyaW5nIHJhdGlvcykKI2NsaWVudF9wcm9maWxlX3JhdGlvPC1jbGllbnRfcHJvZmlsZV9yYXRpbyAlPiUgbXV0YXRlKGlkPWFzLm51bWVyaWMoYXMuZmFjdG9yKGNsaWVudCkpKQoKZDNoZWF0bWFwKHQoY2xpZW50X3Byb2ZpbGVfcmF0aW9bLC0xXSksc3ltbT1GLGNvbG9ycyA9ICJSZWRzIixSb3d2PUYsCiAgICAgICAgICAjbGFiQ29sID0gdChjbGllbnRfcHJvZmlsZV9yYXRpb1ssMV0pLAogICAgICAgICAgd2lkdGggPSAxMDQwLAogICAgICAgICAgaGVpZ2h0ID0gMjUwLCAKICAgICAgICAgIHlheGlzX2ZvbnRfc2l6ZSA9ICI4cHQiLAogICAgICAgICAgeGF4aXNfZm9udF9zaXplID0gIjBwdCIsYW5pbV9kdXJhdGlvbiA9IDApCmBgYAoKIyMgS21lYW5zIGNsdXN0ZXJpbmcKCmBgYHtyfQoKa21lYW5zX21vZGVsPC1rbWVhbnMoY2xpZW50X3Byb2ZpbGVfc2VsZWN0ZWRfZmVhdHVyZXNbLGMoLTEsLTIpXSxjZW50ZXJzPTUsbnN0YXJ0PTQwKQpjbGllbnRfcHJvZmlsZV9yYXRpbzwtY2JpbmQoY2xpZW50X3Byb2ZpbGVfc2VsZWN0ZWRfZmVhdHVyZXMsY2x1c3Rlcj1hcy5mYWN0b3Ioa21lYW5zX21vZGVsJGNsdXN0ZXIpKQpjbGllbnRfcHJvZmlsZV9yYXRpbyAlPiUgZ3JvdXBfYnkoY2x1c3RlcikgJT4lIHN1bW1hcmlzZShuPW4oKSkKc2F2ZShrbWVhbnNfbW9kZWwsZmlsZT0iLi4vbW9kZWxzL2ttZWFuc19tb2RlbF8yNGhzX2xhc3Qzd2Vla3MtMi5SZGF0YSIpCmBgYAoKCiMjIFNvbWUgYmFzaWMgcGVyIGNsdXN0ZXIgaW5mb3JtYXRpb24KCmBgYHtyfQpjbHVzdGVyX3N1bW1hcnk8LXVuaXF1ZShjbGllbnRfcHJvZmlsZV9yYXRpb1ssYygtMildKSAlPiUgZ3JvdXBfYnkoY2x1c3RlcikgJT4lIHN1bW1hcmlzZShuPW4oKSxueD1tZWFuKHJhdGlvX254KSxteD1tZWFuKHJhdGlvX214KSxzYW1laXA9bWVhbihyYXRpb190b3Rfc2FtZWlwKSxzYW1lZG9tYWluPW1lYW4ocmF0aW9fdG90X3NhbWVkb21haW4pLHJldmVyc2U9bWVhbihyYXRpb19yZXZlcnNlKSxkZXRlY3RlZD1tZWFuKHJhdGlvX2RldGVjdGVkKSxyZXF1ZXN0cz1tZWFuKHJhdGlvX3JlcXVlc3RzKSxmYWlsPW1lYW4ocmF0aW9fZmFpbCksIGlwZGlzdD1uX2Rpc3RpbmN0KGNsaWVudCkpCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCmNsdXN0ZXJfc3VtbWFyeQoKZDNoZWF0bWFwKGNsdXN0ZXJfc3VtbWFyeVssYygtMSwtMiwtMTEpXSxjb2xvcnMgPSAiUmVkcyIsUm93dj1ULAogICAgICAgICAgd2lkdGggPSA3MDAsaGVpZ2h0ID0gNDAwLCAKICAgICAgICAgIHlheGlzX2ZvbnRfc2l6ZSA9ICI4cHQiLAogICAgICAgICAgeGF4aXNfZm9udF9zaXplID0gIjhwdCIpCmBgYAoKIyMgQ2x1c3RlcmluZyBSZXN1bHRzIApBY2NvcmRpbmcgdG8gY2x1c3RlcmluZyByZXN1bHRzCgoxLiAqKkNsdXN0ZXIgbi4gMSoqIGdyb3VwcyB0aG9zZSBjbGllbnRzIHdpdGggdGhlIGhpZ2hlc3QgKnNhbWVkb21haW5zKiB2YWx1ZXMgKioocG9zc2libGUgRmFzdGZsdXg/KSoqCjIuICoqQ2x1c3RlciBuLiAyKiogZ3JvdXBzIHRob3NlIGNsaWVudHMgd2l0aCBhIHZhbHVlIGluY3JlbWVudCAgZm9yICpzYW1laXAqIGFuZCAqc2FtZWRvbWFpbnMqICoqKFBvc3NpYmxlIERHQS9GYXN0Zmx1eD8gTm90IHJlYWxseSkqKgozLiAqKkNsdXN0ZXIgbi4gMyoqIGdyb3VwcyBjbGllbnRzIHdpdGggaGlnaCBOWCByZXBsaWVzIGFzIHdlbGwgYXMgc29tZSByZXZlcnNlIHF1ZXJpZXMgYW1vbmcgb3RoZXJzLiAqKihwb3NzaWJsZSBER0EpKioKNC4gKipDbHVzdGVyIG4uIDQqKiBncm91cHMgdGhvc2UgY2xpZW50cyBub3Qgc2hvd2luZyBER0Egb3Igb3RoZXIgbWFsd2F2ZXIgYmVoYXZpb3IgKiooUG9zc2libGUgTk9STUFMPz8pKioKNS4gKipDbHVzdGVyIG4uIDUqKiBncm91cHMgdGhvc2UgY2xpZW50cyB3aXRoIGhpZ2ggdmFsdWVzIGluIEZBSUwgcXVlcmllcyAqKihQb3NzaWJsZSB3aGF0Pz8pKioKCipzb21lIElQUyB0byBjaGVjayoKCioyMTMuMTkxLjEwNS4yMTAqIHNob3dzIHNvbWUgcG9zc2libGUgcmFuZG9tIGdlbmVyYXRlZCBkb21haW5zCgoqMjEzLjE5MS4xMDUuMjQyKiBTaG93cyBhIGxvdCBvZiBOWCByZWNvcmRzLiBTb21lIHF1ZXJpZXMgY29udGFpbiB0aGUga2suIFRMRCAoPz8pCgojIyBULU5TRSAyRCByZXByZXNlbnRhdGlvbiB3aXRoIGNsdXN0ZXJzIAoKKipMaW1pdGF0aW9ucyBvZiBQQ0EqKgoKUENBIGlzIGEgbGluZWFyIGFsZ29yaXRobS4gSXQgd2lsbCBub3QgYmUgYWJsZSB0byBpbnRlcnByZXQgY29tcGxleCBwb2x5bm9taWFsIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIGZlYXR1cmVzLiBPbiB0aGUgb3RoZXIgaGFuZCwgdC1TTkUgaXMgYmFzZWQgb24gcHJvYmFiaWxpdHkgZGlzdHJpYnV0aW9ucyB3aXRoIHJhbmRvbSB3YWxrIG9uIG5laWdoYm9yaG9vZCBncmFwaHMgdG8gZmluZCB0aGUgc3RydWN0dXJlIHdpdGhpbiB0aGUgZGF0YS4KCgoKCgpgYGB7cn0KdHNuZV9tb2RlbDwtUnRzbmUodW5pcXVlKGNsaWVudF9wcm9maWxlX3JhdGlvWyxjKC0yLC1uY29sKGNsaWVudF9wcm9maWxlX3JhdGlvKSldKSxwY2E9RkFMU0UsIHBjYV9jZW50ZXIgPVRSVUUsIHBjYV9zY2FsZT1UUlVFLGNoZWNrX2R1cGxpY2F0ZXM9RiwKICAgICAgICAgICAgICAgICAgcGVycGxleGl0eSA9IDQwMCwgbWF4X2l0ZXIgPSAxMDAwKQpgYGAKCgpUbyByZW1lbWJlcjogSXQgc2VlbXMgdGhhdCBpZiB3ZSBnZW5lcmF0ZSAxaCBwcm9maWxlcyB0aGUgdGhlIDJEIHJlcHJlc2VudGF0aW9uIHRlbmRzIHRvIGJlIG1vcmUgZGlmZmljdWx0IHRvIGFuYWx5emUuIFRoZSBhcmUgbWFueSBwb2ludHMgdmVyeSBzaW1pbGFyIGNvcnJlc3BvbmRpbmcgdG8gdGhlIHNhbWUgY2xpZW50IHByb2ZpbGUgYXQgZGlmZmVyZW50IHRpbWUgcGVyaW9kcy4gVXNpbmcgYSBoaWdoZXIgcGVycGxleGl0eSB2YWx1ZSBzZWVtcyB0byBzb2x2ZSB0aGF0IGlzc3VlLiAKCkVhY2ggcG9pbnQgcmVwcmVzZW50cyBhIDEgaG91ciBwcm9maWxlLiBUaGUgKipzaXplKiogb2YgYSBnaXZlbiBwb2ludC9wcm9maWxlIGNvcnJlc3BvbmRzIHRvIHRoZSByZXF1ZXN0IHJhdGlvcyBmb3IgdGhhdCBwcm9maWxlLiBGaW5hbGx5LCAgKipjb2xvcioqIHByZXNlbnRzIGRpZmZlcmVudHMgY2x1c3RlcnMuClJlbWVtYmVyIHlvdSBjYW4gc2VsZWN0L2Rlc2VsZWN0IHRoZSBjbHVzdGVyIGJ5IGNsaWNraW5nIHRoZSBsZWdlbmQuIAoKYGBge3IgcGxvdCB0bnNlIGNsdXN0ZXJpbmcsIHdhcm5pbmc9RkFMU0V9CmNsaWVudF9wcm9maWxlX3VuaXF1ZTwtdW5pcXVlKGNsaWVudF9wcm9maWxlX3JhdGlvWyxjKC0yKV0pCnRzbmVfY2xpZW50X3Byb2ZpbGU8LWRhdGEuZnJhbWUodHNuZV9tb2RlbCRZLGNsdXN0ZXI9Y2xpZW50X3Byb2ZpbGVfdW5pcXVlJGNsdXN0ZXIsY2xpZW50PWNsaWVudF9wcm9maWxlX3VuaXF1ZSRjbGllbnQscmVxdWVzdHM9CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjbGllbnRfcHJvZmlsZV91bmlxdWUkcmF0aW9fcmVxdWVzdHMpCgpzYW1wbGVpZDwtc2FtcGxlKG5yb3codHNuZV9jbGllbnRfcHJvZmlsZSksbnJvdyh0c25lX2NsaWVudF9wcm9maWxlKSkKCmc8LWdncGxvdCh0c25lX2NsaWVudF9wcm9maWxlW3NhbXBsZWlkLF0sYWVzKHg9WDEseT1YMikpKwogIGdlb21fcG9pbnQoYWVzKGNvbG9yPWFzLmZhY3RvcihjbHVzdGVyKSxzaXplPXJlcXVlc3RzLHRleHQ9cGFzdGUoImlwZGRyIixjbGllbnQpKSkrCiAgI2dlb21fcG9pbnQoYWVzKHNoYXBlPWFzaWduYWNpb24pLHNpemU9MykrCiAgeWxhYigiWDEiKSt4bGFiKCJYMiIpKwogIHRoZW1lX2NsYXNzaWMoKSsKI3NjYWxlX3NoYXBlX21hbnVhbCh2YWx1ZXM9Yyg4LDYpKSsKICAgZ3VpZGVzKGNvbG9yPUZBTFNFLGFscGhhPUZBTFNFKQpnZ3Bsb3RseShnKQoKYGBgCgojIyBEZWNpc2lvbiBUcmVlIEFuYWx5c2lzCgpgYGB7ciBkdHJlZSBhbmFsaXN5cywgZmlnLmhlaWdodD05LCBmaWcud2lkdGg9MTZ9CmxpYnJhcnkocnBhcnQucGxvdCkKbGlicmFyeShycGFydCkKbGlicmFyeShycGFydC51dGlscykKCmZvcm11bGEgPC0gYXMuZm9ybXVsYShjbHVzdGVyfi4pCnRyZWU9cnBhcnQoZm9ybXVsYSxkYXRhPWNsaWVudF9wcm9maWxlX3JhdGlvWyxjKC0xLC0yLC0zLC00KV0sY29udHJvbCA9IHJwYXJ0LmNvbnRyb2wobWluc3BsaXQ9NTAwLCBjcD0wLjAwNSx4dmFsPTEwKSxtb2RlbD1ULHg9VCx5PVQpCnJwYXJ0LnBsb3QodHJlZSwKICAgICAgICAgICBleHRyYT00LCAKICAgICAgICAgICBib3gucGFsZXR0ZT0iR25CdSIsCiAgICAgICAgICAgYnJhbmNoLmx0eT01LCBzaGFkb3cuY29sPTAsIG5uPVRSVUUsIGNleCA9MC45LHVuZGVyPVQKICAgICAgICApCnByaW50Y3AodHJlZSkKYGBgCgoKIyMgQ2xpZW50cyBpbiBjbHVzdGVyIDEKCmBgYHtyfQp0c25lX2NsaWVudF9wcm9maWxlICU+JSBmaWx0ZXIoY2x1c3Rlcj09MSkgJT4lIGdyb3VwX2J5KGNsaWVudCkgJT4lIHN1bW1hcmlzZShwcm9maWxlcz1uKCkpICU+JSBhcnJhbmdlKGRlc2MocHJvZmlsZXMpKSAlPiUgZmlsdGVyKHByb2ZpbGVzPjAgKSAgJT4lIHNlbGVjdChjbGllbnQpCmBgYAoKIyMgQ2xpZW50cyBpbiBjbHVzdGVyIDIKCmBgYHtyfQp0c25lX2NsaWVudF9wcm9maWxlICU+JSBmaWx0ZXIoY2x1c3Rlcj09MikgJT4lIGdyb3VwX2J5KGNsaWVudCkgJT4lIHN1bW1hcmlzZShwcm9maWxlcz1uKCkpICU+JSBhcnJhbmdlKGRlc2MocHJvZmlsZXMpKSAlPiUgZmlsdGVyKHByb2ZpbGVzPjApICAlPiUgc2VsZWN0KGNsaWVudCkKYGBgCgojIyBDbGllbnRzIGluIGNsdXN0ZXIgMwoKYGBge3J9CnRzbmVfY2xpZW50X3Byb2ZpbGUgJT4lIGZpbHRlcihjbHVzdGVyPT0zKSAlPiUgZ3JvdXBfYnkoY2xpZW50KSAlPiUgc3VtbWFyaXNlKHByb2ZpbGVzPW4oKSkgJT4lIGFycmFuZ2UoZGVzYyhwcm9maWxlcykpICU+JSBmaWx0ZXIocHJvZmlsZXM+MCkgICU+JSBzZWxlY3QoY2xpZW50KQpgYGAKIyMgQ2xpZW50cyBpbiBjbHVzdGVyIDQKCmBgYHtyfQp0c25lX2NsaWVudF9wcm9maWxlICU+JSBmaWx0ZXIoY2x1c3Rlcj09NCkgJT4lIGdyb3VwX2J5KGNsaWVudCkgJT4lIHN1bW1hcmlzZShwcm9maWxlcz1uKCkpICU+JSBhcnJhbmdlKGRlc2MocHJvZmlsZXMpKSAlPiUgZmlsdGVyKHByb2ZpbGVzPjApICAlPiUgc2VsZWN0KGNsaWVudCkKYGBgCgojIyBDbGllbnRzIGluIGNsdXN0ZXIgNQoKYGBge3J9CnRzbmVfY2xpZW50X3Byb2ZpbGUgJT4lIGZpbHRlcihjbHVzdGVyPT01KSAlPiUgZ3JvdXBfYnkoY2xpZW50KSAlPiUgc3VtbWFyaXNlKHByb2ZpbGVzPW4oKSkgJT4lIGFycmFuZ2UoZGVzYyhwcm9maWxlcykpICU+JSBmaWx0ZXIocHJvZmlsZXM+MCkgICU+JSBzZWxlY3QoY2xpZW50KQpgYGAKCiMjIENsaWVudHMgaW4gY2x1c3RlciA2CgpgYGB7cn0KdHNuZV9jbGllbnRfcHJvZmlsZSAlPiUgZmlsdGVyKGNsdXN0ZXI9PTYpICU+JSBncm91cF9ieShjbGllbnQpICU+JSBzdW1tYXJpc2UocHJvZmlsZXM9bigpKSAlPiUgYXJyYW5nZShkZXNjKHByb2ZpbGVzKSkgJT4lIGZpbHRlcihwcm9maWxlcz4wKSAlPiUgc2VsZWN0KGNsaWVudCkKYGBgCgpgYGB7ciwgZXZhbD1GQUxTRSwgd2FybmluZz1GQUxTRSwgaW5jbHVkZT1GQUxTRX0KIyMgUENBIDJEIHJlcHJlc2VudGF0aW9uIAoKbGlicmFyeShwbG90bHkpCnBjYTwtcHJjb21wKGNsaWVudF9wcm9maWxlX3JhdGlvWyxjKC0xLC0yLC0zLC1uY29sKGNsaWVudF9wcm9maWxlX3JhdGlvKSldLCBjZW50ZXIgPSBUUlVFLCBzY2FsZS4gPSBUUlVFKSAKCnBjYV9jbGllbnRfcHJvZmlsZTwtZGF0YS5mcmFtZShwY2EkeCxjbGllbnQ9Y2xpZW50X3Byb2ZpbGVfcmF0aW8kY2xpZW50LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGV0ZWN0ZWQ9aWZlbHNlKGNsaWVudF9wcm9maWxlX3JhdGlvJHJhdGlvX2RldGVjdGVkPjAsImRldGVjdGVkIiwibm90ZGV0ZWN0ZWQiKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJhdGlvX3JlcXVlc3RlZD1jbGllbnRfcHJvZmlsZV9yYXRpbyRyYXRpb19yZXF1ZXN0cywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlcXVlc3RlZD1jbGllbnRfcHJvZmlsZV9yYXRpbyR0b3RfcmVxdWVzdHMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjbHVzdGVyPWNsaWVudF9wcm9maWxlX3JhdGlvJGNsdXN0ZXIKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICkKCgpnPC1nZ3Bsb3QocGNhX2NsaWVudF9wcm9maWxlLGFlcyh4PVBDMyx5PVBDMikpKwogIGdlb21faml0dGVyKGFlcyhjb2xvcj1hcy5mYWN0b3IoY2x1c3RlciksdGV4dD1wYXN0ZShjbGllbnQsIjxCUj4iLHJlcXVlc3RlZCkpKSsKICAjZ2VvbV9wb2ludChhZXMoc2hhcGU9YXNpZ25hY2lvbiksc2l6ZT0zKSsKICB5bGFiKCJQQzEiKSt4bGFiKCJQQzIiKSsKICB0aGVtZV9jbGFzc2ljKCkrCiNzY2FsZV9zaGFwZV9tYW51YWwodmFsdWVzPWMoOCw2KSkrCiAgIGd1aWRlcyhjb2xvcj1GQUxTRSxhbHBoYT1GQUxTRSkKZwoKZ2dwbG90bHkoZykKYGBgCgoK