library(readr)
library(dplyr)
library(broom)
library(stringr)
library(ggplot2)

IMBALANCE STRATEGIES

We have tried several approaches for dealing with imbalaced classes. We have used the Woodbridge (2016) LSTM network for evaluating the different imbalaced approaches.

10% SAMPLE

The following sections presents the results of the different imabalance techniques on samples of 10% of the CTU19A dataset ### Downsampling

files <- list.files(path = "../results/",pattern="results_test_imbalance-downsampling-epochs=60-endgame-maxlen=200-\\d+")
#str_replace(string = "results_test_ctu13-lstm_endgame-80-1.csv",pattern = ".*-([0-9]+)-[0-9]+.csv","\\1")
results_lstm_downsample <- lapply(files, function(x) 
                read_csv(paste("../results/",x,sep=""), col_types = cols())   
                %>% tibble::add_column(maxlen=as.integer(str_replace(string = x ,pattern = ".*=([0-9]+)-[0-9]+.csv","\\1") ))
                )
results_lstm_downsample <-do.call(rbind,results_lstm_downsample)

Upsampling

files <- list.files(path = "../results/",pattern="results_test_imbalance-upsampling-epochs=60-endgame-maxlen=200-\\d+")
#str_replace(string = "results_test_ctu13-lstm_endgame-80-1.csv",pattern = ".*-([0-9]+)-[0-9]+.csv","\\1")
results_lstm_upsample <- lapply(files, function(x) 
                read_csv(paste("../results/",x,sep=""),col_types = cols())   
                %>% tibble::add_column(maxlen=as.integer(str_replace(string = x ,pattern = ".*=([0-9]+)-[0-9]+.csv","\\1") ))
                )
results_lstm_upsample <-do.call(rbind,results_lstm_upsample)

Augmenting data (shift sequence approach)

Only normal data

files <- list.files(path = "../results/",pattern="results_test_imbalance-augmenting-epochs=60-endgame-maxlen=200-\\d+")
#str_replace(string = "results_test_ctu13-lstm_endgame-80-1.csv",pattern = ".*-([0-9]+)-[0-9]+.csv","\\1")
results_lstm_augment <- lapply(files, function(x) 
                read_csv(paste("../results/",x,sep=""),col_types = cols())   
                %>% tibble::add_column(maxlen=as.integer(str_replace(string = x ,pattern = ".*=([0-9]+)-[0-9]+.csv","\\1") ))
                )
results_lstm_augment <-do.call(rbind,results_lstm_augment)

Normal and Botnet

files <- list.files(path = "../results/",pattern="results_test_imbalance-augmenting-botnet-epochs=60-endgame-maxlen=200-\\d+")
results_lstm_augment4x <- lapply(files, function(x) 
                read_csv(paste("../results/",x,sep=""),col_types = cols())   
                %>% tibble::add_column(maxlen=as.integer(str_replace(string = x ,pattern = ".*=([0-9]+)-[0-9]+.csv","\\1") ))
                )
results_lstm_augment4x <-do.call(rbind,results_lstm_augment4x)
files %>% length()
[1] 30

Comparison

results<-rbind(results_lstm_upsample %>% tibble::add_column(imbalance="upsample"),
               results_lstm_downsample %>% tibble::add_column(imbalance="downsample"), 
               results_lstm_augment %>% tibble::add_column(imbalance="aug-norm"),
               results_lstm_augment4x %>% tibble::add_column(imbalance="aug-norm-bot"))
results %>% filter(metric %in%  c("Balanced Accuracy","F1","Sensitivity","Specificity")) %>%
  ggplot() +
  labs(title="Imbalance Strategies [30 executions]",
        subtitle="LSTM arch. according to Woodbridge et al. (2016):\nmaxlen 200, epochs 60",
        caption="Original sample contains:  ~1800 botnets and ~180 normal data points.\n
       aug-norm and aug-norm-bot use a sliding window over sequence for generate new samples. ")+
  
  geom_boxplot(aes(x=as.factor(imbalance),y=value,fill=as.factor(imbalance)),color='gray')+
  xlab("Strategy")+
  theme_bw()+
  ggdark::dark_theme_gray()+
  theme(axis.text.x = element_text(angle = 45, hjust = 1))+
  theme(legend.position="none")+
  facet_wrap(~metric, scales = "fixed")

20% SAMPLE

The following sections presents the results of the different imabalance techniques on samples of 10% of the CTU19A dataset. The 20% was choosen based on a carefull analisys of the differences between pop. and sample distribution.

Augmenting 20% botnet 2x

We apply the shift sequence data-augmentation approach for generating 2x botnet and then generate the same amount of normal data.

files <- list.files(path = "../results/",pattern="results_test_imbalance-sample20-augment-botnetx2-epochs=60-endgame-maxlen=200-\\d+")
results_lstm_augment_sample20x2 <- lapply(files, function(x) 
                read_csv(paste("../results/",x,sep=""), col_types = cols())   
                %>% tibble::add_column(maxlen=as.integer(str_replace(string = x ,pattern = ".*=([0-9]+)-[0-9]+.csv","\\1") ))
                )
results_lstm_augment_sample20x2 <-do.call(rbind,results_lstm_augment_sample20x2)
files %>% length()
[1] 30

Augmenting 20% botnet 1x

We apply the shift sequence data-augmentation approach for generating 1x botnet and then generate the same amount of normal data.

files <- list.files(path = "../results/",pattern="results_test_imbalance-sample20-augment-botnet-epochs=60-endgame-maxlen=200-\\d+")
results_lstm_augment_sample20x3 <- lapply(files, function(x) 
                read_csv(paste("../results/",x,sep=""), col_types = cols())   
                %>% tibble::add_column(maxlen=as.integer(str_replace(string = x ,pattern = ".*=([0-9]+)-[0-9]+.csv","\\1") ))
                )
results_lstm_augment_sample20x3 <-do.call(rbind,results_lstm_augment_sample20x3)
files %>% length()
[1] 30

Augmenting 20% botnet 5x

We apply the shift sequence data-augmentation approach for generating 5x botnet and then generate the same amount of normal data.

files <- list.files(path = "../results/",pattern="results_test_imbalance-sample20-augment-botnetx5-epochs=60-endgame-maxlen=200-\\d+")
results_lstm_augment_sample20x5 <- lapply(files, function(x) 
                read_csv(paste("../results/",x,sep=""), col_types = cols())   
                %>% tibble::add_column(maxlen=as.integer(str_replace(string = x ,pattern = ".*=([0-9]+)-[0-9]+.csv","\\1") ))
                )
results_lstm_augment_sample20x5 <-do.call(rbind,results_lstm_augment_sample20x5)
files %>% length()
[1] 30

Downsampling 20%

files <- list.files(path = "../results/",pattern="results_test_imbalance-sample20-downsample-botnet-epochs=60-endgame-maxlen=200-\\d+")
results_lstm_downsample_sample20 <- lapply(files, function(x) 
                read_csv(paste("../results/",x,sep=""), col_types = cols())   
                %>% tibble::add_column(maxlen=as.integer(str_replace(string = x ,pattern = ".*=([0-9]+)-[0-9]+.csv","\\1") ))
                )
results_lstm_downsample_sample20 <-do.call(rbind,results_lstm_downsample_sample20)
files %>% length()
[1] 30

Upsampling 20%

files <- list.files(path = "../results/",pattern="results_test_imbalance-sample20-upsample-botnet-epochs=60-endgame-maxlen=200-\\d+")
results_lstm_upsample_sample20 <- lapply(files, function(x) 
                read_csv(paste("../results/",x,sep=""), col_types = cols())   
                %>% tibble::add_column(maxlen=as.integer(str_replace(string = x ,pattern = ".*=([0-9]+)-[0-9]+.csv","\\1") ))
                )
results_lstm_upsample_sample20 <-do.call(rbind,results_lstm_upsample_sample20)
files %>% length()
[1] 30

Augmenting 20% botnet 2x from datasets

All previous augmenting data approaches were applied on the tokenized(keras) version of the sequences. This approach is applied directly on the dataset. THIS WAS THE APPROACH USED FOR GENERATING THE AUGMENTED train/test sets for MMCV and final CTU19A-augmented

files <- list.files(path = "../results/",pattern="results_test_imbalance-sample20-augment-dataset-2x-epochs=60-endgame-maxlen=200-\\d+")
results_lstm_augment_sample20x2_dataset <- lapply(files, function(x) 
                read_csv(paste("../results/",x,sep=""), col_types = cols())   
                %>% tibble::add_column(maxlen=as.integer(str_replace(string = x ,pattern = ".*=([0-9]+)-[0-9]+.csv","\\1") ))
                )
results_lstm_augment_sample20x2_dataset <-do.call(rbind,results_lstm_augment_sample20x2_dataset)
files %>% length()
[1] 30
results<-rbind(results_lstm_upsample_sample20 %>% tibble::add_column(imbalance="upsample-20"),
               results_lstm_downsample_sample20 %>% tibble::add_column(imbalance="downsample-20"), 
               #results_lstm_augment_sample20x2 %>% tibble::add_column(imbalance="aug-norm-bot-20x2"),
               #results_lstm_augment_sample20x3 %>% tibble::add_column(imbalance="aug-norm-bot-20x3"),
               #results_lstm_augment_sample20x5 %>% tibble::add_column(imbalance="aug-norm-bot-20x5"),
               results_lstm_augment_sample20x2_dataset %>% tibble::add_column(imbalance="aug-norm-bot-20x2-dat")
               
               
               #results_lstm_upsample %>% tibble::add_column(imbalance="upsample-10"),
               #results_lstm_downsample %>% tibble::add_column(imbalance="downsample-10"),
        
               #results_lstm_augment %>% tibble::add_column(imbalance="aug-norm-bot-10")
               
               )
results %>% filter(metric %in%  c("Balanced Accuracy","F1","Sensitivity","Specificity")) %>%
  ggplot() +
  labs(title="Imbalance Strategies [30 executions]",
        subtitle="LSTM arch. according to Woodbridge et al. (2016):\nmaxlen 200, epochs 60",
        caption="Original sample contains:  ~3600 botnets and ~400 normal data points [20% of pop.]\n
       aug-norm-bot use a sliding window over a random sequence for generate new samples.\n
       x2 refers to different number of augmented samples.\n
       No significative differences observed between upsample and aug-norm-botx2
       ")+
  
  geom_boxplot(aes(x=as.factor(imbalance),y=value,fill=as.factor(imbalance)),color='gray')+
  xlab("Strategy")+
  theme_bw()+
  ggdark::dark_theme_gray()+
  theme(axis.text.x = element_text(angle = 45, hjust = 1))+
  theme(legend.position="none")+
  facet_wrap(~metric, scales = "fixed")

NORMAL DISTRIBUTION TESTS of the MMCV results

balanced_accuracy_augment20x3<-results_lstm_augment_sample20x3 %>% filter(metric=="Balanced Accuracy") %>% select(value)
balanced_accuracy_augment20x5<-results_lstm_augment_sample20x5 %>% filter(metric=="Balanced Accuracy") %>% select(value)
balanced_accuracy_augment20x2<-results_lstm_augment_sample20x2 %>% filter(metric=="Balanced Accuracy") %>% select(value)
balanced_accuracy_upsample20<-results_lstm_upsample_sample20 %>% filter(metric=="Balanced Accuracy") %>% select(value)
balanced_accuracy_downsample20<-results_lstm_downsample_sample20 %>% filter(metric=="Balanced Accuracy") %>% select(value)
balanced_accuracy_augment20x2_dataset<-results_lstm_augment_sample20x2_dataset %>% filter(metric=="Balanced Accuracy") %>% select(value)
balanced_accuracy_augment20x2_dataset %>% select(value) %>%
  ggplot()+
  geom_histogram(aes(x=value),fill='skyblue',color='black')+
  ggdark::dark_theme_bw()

QQplots

CI

n<-balanced_accuracy_augment20x2_dataset %>% length()
mu<-mean(balanced_accuracy_augment20x2_dataset %>% unlist())
s<-sd(balanced_accuracy_augment20x2_dataset %>% unlist())
error<- qnorm(0.975)*s/sqrt(n)
c(mu+error,mu-error)
[1] 0.9541808 0.8885488

Wilcox Mann Whitney test

wilcox.test(balanced_accuracy_augment20x3 %>% unlist(),
            balanced_accuracy_augment20x2 %>% unlist()
            )

    Wilcoxon rank sum test

data:  balanced_accuracy_augment20x3 %>% unlist() and balanced_accuracy_augment20x2 %>% unlist()
W = 415, p-value = 0.6125
alternative hypothesis: true location shift is not equal to 0
augment_results<-data.frame(downsmaple=balanced_accuracy_downsample20 %>% unlist(),
           augment20x2_dataset=balanced_accuracy_augment20x2_dataset %>% unlist(),
          
           upsample=balanced_accuracy_upsample20 %>% unlist()
           #downsample=balanced_accuracy_downsample20 %>% unlist()
            
           ) %>% reshape2::melt() 
No id variables; using all as measure variables
oneway.test(value~variable,data=augment_results) %>% broom::tidy()
Multiple parameters; naming those columns num.df, den.df

MODEL SELECTION (MMCV)

LSTM

Batch 1024

files <- list.files(path = "../results/",pattern="results_test_lstm-endgame-augmented-ctu19-mccv-epochs=15-endgame-maxlen=1000-\\d+-lstm_size=\\d+-embedingdim=\\d+-dropout=[0-9.]+.csv")
results_lstm_tune <- lapply(files, function(x) 
                read_csv(paste("../results/",x,sep=""), col_types = cols())   
                %>% tibble::add_column(lstm_size=as.integer(str_replace(string = x ,pattern = ".*-lstm_size=([0-9]+)-.*.csv","\\1") ))
               %>% tibble::add_column(embedingdim=as.integer(str_replace(string = x ,pattern = ".*-embedingdim=([0-9]+)-.*.csv","\\1") ))
               %>% tibble::add_column(dropout=as.numeric(str_replace(string = x ,pattern = ".*-dropout=([0-9]+.[0-9]).csv","\\1") ))
               %>% tibble::add_column(sample=as.factor(str_replace(string = x ,pattern = ".*=1000-(\\d+)-.*.csv","\\1") ))
          
                )
results_lstm_tune <-do.call(rbind,results_lstm_tune)
results_lstm_tune %>% group_by(sample) %>% summarise(n=n())
results_lstm_tune<-results_lstm_tune %>% tidyr::unite("parameters",lstm_size:embedingdim:dropout) %>% filter(metric %in%  c("Balanced Accuracy","F1","Sensitivity","Specificity"))
numerical expression has 2 elements: only the first used
results_lstm_tune %>% ggplot()+
  geom_boxplot(aes(y=value,x=parameters,fill=parameters),color='gray')+
  labs(title="Parameters Tuning: LSTM Woodbridge (2016)",subtitle ="Parameteres: <lstm_size>_<embeding_size>_<dropout>")+
  xlab("Parameters")+
  ggdark::dark_theme_gray()+
  theme(axis.text.x = element_text(angle = 45, hjust = 1))+
  theme(legend.position="none")+
  facet_wrap(~metric)

results_lstm_tune_ordered<-results_lstm_tune %>% filter(metric=="Balanced Accuracy") %>% select(value,parameters) %>% group_by(parameters) %>%  summarise(mean=mean(value),sd=sd(value))  %>% arrange(desc(mean))
results_lstm_tune_ordered$parameters<-factor(results_lstm_tune_ordered$parameters,levels=unique(results_lstm_tune_ordered$parameters))
lstm_sd_plot<-  ggplot(aes(x=parameters,y=mean),data=results_lstm_tune_ordered)+
   geom_errorbar(aes(ymin=mean-sd, ymax=mean+sd), width=.2,color='yellow')+
   geom_point(color='red')+
  ylab("Mean Balanced Accuracy")+
    ggdark::dark_theme_bw()+
  theme(axis.text.x = element_text(angle = 45, hjust = 1))+
   theme(legend.position="none")+
   ylim(0.80,0.95)+
   labs(title="Parameters Tuning: LSTM Woodbridge (2016)",subtitle ="Balanced Accuracy: Mean and Standard Deviation\nParameteres: <lstm_size>_<embeding_size>_<dropout>",caption="batch_size=1024")
  
lstm_sd_plot

Batch 256

files <- list.files(path = "../results/",
                    pattern="results_test_lstm-endgame-augmented-ctu19-mccv-epochs=15-endgame-batch=256-maxlen=1000-\\d+-lstm_size=\\d+-embedingdim=\\d+-dropout=[0-9.]+.csv")
results_lstm_256_tune <- lapply(files, function(x) 
                read_csv(paste("../results/",x,sep=""), col_types = cols())   
                %>% tibble::add_column(lstm_size=as.integer(str_replace(string = x ,pattern = ".*-lstm_size=([0-9]+)-.*.csv","\\1") ))
               %>% tibble::add_column(embedingdim=as.integer(str_replace(string = x ,pattern = ".*-embedingdim=([0-9]+)-.*.csv","\\1") ))
               %>% tibble::add_column(dropout=as.numeric(str_replace(string = x ,pattern = ".*-dropout=([0-9]+.[0-9]).csv","\\1") ))
               %>% tibble::add_column(sample=as.factor(str_replace(string = x ,pattern = ".*=1000-(\\d+)-.*.csv","\\1") ))
          
                )
results_lstm_256_tune <-do.call(rbind,results_lstm_256_tune)
results_lstm_256_tune %>% group_by(sample) %>% summarise(n=n())
results_lstm_256_tune<-results_lstm_256_tune %>% tidyr::unite("parameters",lstm_size:embedingdim:dropout) %>% filter(metric %in%  c("Balanced Accuracy","F1","Sensitivity","Specificity"))
numerical expression has 2 elements: only the first used
results_lstm_256_tune_ordered<-results_lstm_256_tune %>% filter(metric=="Balanced Accuracy") %>% select(value,parameters) %>% group_by(parameters) %>%  summarise(mean=mean(value),sd=sd(value))  %>% arrange(desc(mean))
results_lstm_256_tune_ordered$parameters<-factor(results_lstm_256_tune_ordered$parameters,levels=unique(results_lstm_256_tune_ordered$parameters))
lstm_256_sd_plot<-  ggplot(aes(x=parameters,y=mean),data=results_lstm_256_tune_ordered)+
   geom_errorbar(aes(ymin=mean-sd, ymax=mean+sd), width=.2,color='yellow')+
   geom_point(color='red')+
  ylab("Mean Balanced Accuracy")+
    ggdark::dark_theme_bw()+
  theme(axis.text.x = element_text(angle = 45, hjust = 1))+
   theme(legend.position="none")+
   ylim(0.80,0.95)+
   labs(title="Parameters Tuning: LSTM Woodbridge (2016)",subtitle ="Balanced Accuracy: Mean and Standard Deviation\nParameteres: <lstm_size>_<embeding_size>_<dropout>",caption="batch_size=256")
  
lstm_256_sd_plot

CNN1D

Batch 1024

files <- list.files(path = "../results/",pattern="results_test_cnn1d-cacic-augmented-ctu19-mccv-epochs=15-endgame-batch=1024-maxlen=1000-\\d+-nb_filter=\\d+-kernel_size=\\d+-embedingdim=\\d+-hidden_size=\\d+.csv")
results_cnn1d_tune <- lapply(files, function(x) 
                read_csv(paste("../results/",x,sep=""), col_types = cols())   
                %>% tibble::add_column(nb_filter=as.integer(str_replace(string = x ,pattern = ".*-nb_filter=([0-9]+)-.*.csv","\\1") ))
               %>% tibble::add_column(embedingdim=as.integer(str_replace(string = x ,pattern = ".*-embedingdim=([0-9]+)-.*.csv","\\1") ))
               %>% tibble::add_column(kernel_size=as.numeric(str_replace(string = x ,pattern = ".*-kernel_size=([0-9]+)-.*.csv","\\1") ))
              %>% tibble::add_column(hidden_size=as.numeric(str_replace(string = x ,pattern = ".*-hidden_size=([0-9]+).csv","\\1") ))
              
               %>% tibble::add_column(sample=as.factor(str_replace(string = x ,pattern = ".*=1000-(\\d+)-.*.csv","\\1") ))
          
                )
results_cnn1d_tune <-do.call(rbind,results_cnn1d_tune)
results_cnn1d_tune %>% group_by(sample) %>% summarise(n=n())
results_cnn1d_tune<-results_cnn1d_tune %>% tidyr::unite("parameters",nb_filter:embedingdim:kernel_size:hidden_size) %>% filter(metric %in%  c("Balanced Accuracy","F1","Sensitivity","Specificity"))
numerical expression has 2 elements: only the first usednumerical expression has 3 elements: only the first used
results_cnn1d_tune %>%  group_by(sample) %>% summarise(n=n())
results_cnn1d_tune %>% ggplot()+
  geom_boxplot(aes(y=value,x=parameters,fill=parameters),color='gray')+
  labs(title="Parameters Tuning: CNN1D Catania (2018)",subtitle ="Parameteres: <nb_filter>_<embeding_size>_<kernel_size>_<hidden_size>",caption="batch_size=1024")+
  xlab("Parameters")+
  ggdark::dark_theme_gray()+
  theme(axis.text.x = element_text(angle = 45, hjust = 1))+
  theme(legend.position="none")+
  facet_wrap(~metric)

results_cnn1d_tune_ordered <- results_cnn1d_tune %>% filter(metric=="Balanced Accuracy") %>% select(value,parameters) %>% group_by(parameters) %>%  summarise(mean=mean(value),sd=sd(value)) %>% arrange(desc(mean))
results_cnn1d_tune_ordered$parameters<-factor(results_cnn1d_tune_ordered$parameters,levels=unique(results_cnn1d_tune_ordered$parameters))
cnn1d_sd_plot<-  ggplot(aes(x=parameters,y=mean),data=results_cnn1d_tune_ordered)+
   geom_errorbar(aes(ymin=mean-sd, ymax=mean+sd), width=.2,color='yellow')+
   geom_point(color='red')+
  ylab("Mean Balanced Accuracy")+
    ggdark::dark_theme_bw()+
  theme(axis.text.x = element_text(angle = 45, hjust = 1))+
   theme(legend.position="none")+
  theme(axis.text=element_text(size=8))+
   ylim(0.80,0.95)+
   labs(title="Parameters Tuning: CNN1D Catania (2018)",subtitle ="Balanced Accuracy: Mean and Standard Deviation\nParameteres: <nb_filter>_<embeding_size>_<kernel_size>_<hidden_size>",caption="batch_size=1024")
cnn1d_sd_plot

gridExtra::grid.arrange(lstm_sd_plot,cnn1d_sd_plot,ncol=2)

Batch 256

files <- list.files(path = "../results/",pattern="results_test_cnn1d-cacic-augmented-ctu19-mccv-epochs=15-endgame-maxlen=1000-\\d+-nb_filter=\\d+-kernel_size=\\d+-embedingdim=\\d+-hidden_size=\\d+.csv")
results_cnn1d_256_tune <- lapply(files, function(x) 
                read_csv(paste("../results/",x,sep=""), col_types = cols())   
                %>% tibble::add_column(nb_filter=as.integer(str_replace(string = x ,pattern = ".*-nb_filter=([0-9]+)-.*.csv","\\1") ))
               %>% tibble::add_column(embedingdim=as.integer(str_replace(string = x ,pattern = ".*-embedingdim=([0-9]+)-.*.csv","\\1") ))
               %>% tibble::add_column(kernel_size=as.numeric(str_replace(string = x ,pattern = ".*-kernel_size=([0-9]+)-.*.csv","\\1") ))
              %>% tibble::add_column(hidden_size=as.numeric(str_replace(string = x ,pattern = ".*-hidden_size=([0-9]+).csv","\\1") ))
              
               %>% tibble::add_column(sample=as.factor(str_replace(string = x ,pattern = ".*=1000-(\\d+)-.*.csv","\\1") ))
          
                )
results_cnn1d_256_tune <-do.call(rbind,results_cnn1d_256_tune)
results_cnn1d_256_tune %>% group_by(sample) %>% summarise(n=n())
results_cnn1d_256_tune<-results_cnn1d_256_tune %>% tidyr::unite("parameters",nb_filter:embedingdim:kernel_size:hidden_size) %>% filter(metric %in%  c("Balanced Accuracy","F1","Sensitivity","Specificity"))
numerical expression has 2 elements: only the first usednumerical expression has 3 elements: only the first used
results_cnn1d_256_tune %>%  group_by(sample) %>% summarise(n=n())
results_cnn1d_256_tune_ordered <- results_cnn1d_256_tune %>% filter(metric=="Balanced Accuracy") %>% select(value,parameters) %>% group_by(parameters) %>%  summarise(mean=mean(value),sd=sd(value)) %>% arrange(desc(mean))
results_cnn1d_256_tune_ordered$parameters<-factor(results_cnn1d_256_tune_ordered$parameters,levels=unique(results_cnn1d_256_tune_ordered$parameters))
cnn1d_256_sd_plot<-  ggplot(aes(x=parameters,y=mean),data=results_cnn1d_256_tune_ordered)+
   geom_errorbar(aes(ymin=mean-sd, ymax=mean+sd), width=.2,color='yellow')+
   geom_point(color='red')+
  ylab("Mean Balanced Accuracy")+
    ggdark::dark_theme_bw()+
  theme(axis.text.x = element_text(angle = 45, hjust = 1))+
   theme(legend.position="none")+
   theme(axis.text=element_text(size=8))+
   ylim(0.80,0.95)+
   labs(title="Parameters Tuning: CNN1D Catania (2018)",subtitle ="Balanced Accuracy: Mean and Standard Deviation\nParameteres: <nb_filter>_<embeding_size>_<kernel_size>_<hidden_size>",caption="batch_size=256")
cnn1d_256_sd_plot

ATTENTION

Batch 1024

files <- list.files(path = "../results/juanma",pattern="results_test_awc-lstm_size%5C=\\d+-embedingdim%5C=\\d+-dropout%5C=[0-9.]+-metrics-\\d+.csv")
results_att_tune <- lapply(files, function(x) 
                read_csv(paste("../results/juanma/",x,sep=""), col_types = cols())   
                %>% tibble::add_column(lstm_size=as.integer(str_replace(string = x ,pattern = ".*-lstm_size%5C=([0-9]+)-.*.csv","\\1") ))
               %>% tibble::add_column(embedingdim=as.integer(str_replace(string = x ,pattern = ".*-embedingdim%5C=([0-9]+)-.*.csv","\\1") ))
               %>% tibble::add_column(dropout=as.numeric(str_replace(string = x ,pattern = ".*-dropout%5C=([0-9]+.[0-9])-metrics-\\d+.csv","\\1") ))
               %>% tibble::add_column(sample=as.factor(str_replace(string = x ,pattern = ".*metrics-(\\d+).csv","\\1") ))
          
                )
results_att_tune <-do.call(rbind,results_att_tune)
results_att_tune %>% group_by(sample) %>% summarise(n=n())
results_att_tune<-results_att_tune %>% tidyr::unite("parameters",lstm_size:embedingdim:dropout) %>% filter(metric %in%  c("Balanced Accuracy","F1","Sensitivity","Specificity"))
numerical expression has 2 elements: only the first used
results_att_tune %>% ggplot()+
  geom_boxplot(aes(y=value,x=parameters,fill=parameters),color='gray')+
  labs(title="Parameters Tuning: Attention Yang (2016)",subtitle ="Parameters: <lstm_size>_<embeding_size>_<dropout>")+
  xlab("Parameters")+
  ggdark::dark_theme_gray()+
  theme(axis.text.x = element_text(angle = 45, hjust = 1))+
  theme(legend.position="none")+
  facet_wrap(~metric)

results_att_tune_ordered<-results_att_tune %>% filter(metric=="Balanced Accuracy") %>% select(value,parameters) %>% group_by(parameters) %>%  summarise(mean=mean(value),sd=sd(value))  %>% arrange(desc(mean))
results_att_tune_ordered$parameters<-factor(results_att_tune_ordered$parameters,levels=unique(results_att_tune_ordered$parameters))
att_sd_plot<-  ggplot(aes(x=parameters,y=mean),data=results_att_tune_ordered)+
   geom_errorbar(aes(ymin=mean-sd, ymax=mean+sd), width=.2,color='yellow')+
   geom_point(color='red')+
  ylab("Mean Balanced Accuracy")+
    ggdark::dark_theme_bw()+
  theme(axis.text.x = element_text(angle = 45, hjust = 1))+
   theme(legend.position="none")+
   ylim(0.80,0.95)+
   labs(title="Parameters Tuning: Attention Yang (2016) ",subtitle ="Balanced Accuracy: Mean and Standard Deviation\nParameteres: <lstm_size>_<embeding_size>_<dropout>",caption="batch_size=1024")
  
att_sd_plot

Batch 256

files <- list.files(path = "../results/juanma",pattern="results_test_batch_size%5C=256-awc-lstm_size%5C=\\d+-embedingdim%5C=\\d+-dropout%5C=[0-9.]+-metrics-\\d+.csv")
results_att_256_tune <- lapply(files, function(x) 
                read_csv(paste("../results/juanma/",x,sep=""), col_types = cols())   
                %>% tibble::add_column(lstm_size=as.integer(str_replace(string = x ,pattern = ".*-lstm_size%5C=([0-9]+)-.*.csv","\\1") ))
               %>% tibble::add_column(embedingdim=as.integer(str_replace(string = x ,pattern = ".*-embedingdim%5C=([0-9]+)-.*.csv","\\1") ))
               %>% tibble::add_column(dropout=as.numeric(str_replace(string = x ,pattern = ".*-dropout%5C=([0-9]+.[0-9])-metrics-\\d+.csv","\\1") ))
               %>% tibble::add_column(sample=as.factor(str_replace(string = x ,pattern = ".*metrics-(\\d+).csv","\\1") ))
          
                )
results_att_256_tune <-do.call(rbind,results_att_256_tune)
results_att_256_tune %>% group_by(sample) %>% summarise(n=n())
results_att_256_tune<-results_att_256_tune %>% tidyr::unite("parameters",lstm_size:embedingdim:dropout) %>% filter(metric %in%  c("Balanced Accuracy","F1","Sensitivity","Specificity"))
numerical expression has 2 elements: only the first used
results_att_256_tune %>% ggplot()+
  geom_boxplot(aes(y=value,x=parameters,fill=parameters),color='gray')+
  labs(title="Parameters Tuning: Attention Yang (2016)",subtitle ="Parameters: <lstm_size>_<embeding_size>_<dropout>")+
  xlab("Parameters")+
  ggdark::dark_theme_gray()+
  theme(axis.text.x = element_text(angle = 45, hjust = 1))+
  theme(legend.position="none")+
  facet_wrap(~metric)
Inverted geom defaults of fill and color/colour.
To change them back, use invert_geom_defaults().

results_att_256_tune_ordered<-results_att_256_tune %>% filter(metric=="Balanced Accuracy") %>% select(value,parameters) %>% group_by(parameters) %>%  summarise(mean=mean(value),sd=sd(value))  %>% arrange(desc(mean))
results_att_256_tune_ordered$parameters<-factor(results_att_256_tune_ordered$parameters,levels=unique(results_att_256_tune_ordered$parameters))
att_256_sd_plot<-  ggplot(aes(x=parameters,y=mean),data=results_att_256tune_ordered)+
   geom_errorbar(aes(ymin=mean-sd, ymax=mean+sd), width=.2,color='yellow')+
   geom_point(color='red')+
  ylab("Mean Balanced Accuracy")+
    ggdark::dark_theme_bw()+
  theme(axis.text.x = element_text(angle = 45, hjust = 1))+
   theme(legend.position="none")+
   ylim(0.80,0.95)+
   labs(title="Parameters Tuning: Attention Yang (2016) ",subtitle ="Balanced Accuracy: Mean and Standard Deviation\nParameteres: <lstm_size>_<embeding_size>_<dropout>",caption="batch_size=256")
  
att_256_sd_plot

Comparison

MCCV

gridplot<-gridExtra::grid.arrange(lstm_sd_plot+ theme(plot.title = element_text(size=8))
                                    + theme(plot.subtitle = element_text(size=6))
                                            
                        
                        ,lstm_256_sd_plot + theme(plot.title = element_text(size=8))
                                    + theme(plot.subtitle = element_text(size=6))
                        
                        ,att_sd_plot+ theme(plot.title = element_text(size=8))
                                    + theme(plot.subtitle = element_text(size=6))
                        
                        ,att_256_sd_plot+ theme(plot.title = element_text(size=8))
                                    + theme(plot.subtitle = element_text(size=6))
                        
                      
                        
                        ,cnn1d_sd_plot + theme(plot.title = element_text(size=8))
                                    + theme(plot.subtitle = element_text(size=6))
                        
                        ,cnn1d_256_sd_plot + theme(plot.title = element_text(size=8))
                                     + theme(plot.subtitle = element_text(size=6))
                        
                        ,ncol=6)

gridplot %>% plot()

Results Table

results_att_256_tune$model<-"att_256"
results_att_tune$model<-"att_1024"
results_cnn1d_256_tune$model<-"cnn1d_256"
results_cnn1d_tune$model<-"cnn1d_1024"  
results_lstm_256_tune$model<-"lstm_256"
results_lstm_tune$model<-"lstm"
rbind( results_att_256_tune,
results_att_tune,
results_cnn1d_256_tune,
results_cnn1d_tune,
results_lstm_256_tune,
results_lstm_tune) %>% readr::write_csv("~/sequence_classification_metrics.csv")
  

FINAL models results for CTU19A and CTU19B

Selected Models:

LSTM: batch 256 64_64_0.1 batch 1024 64_32_0.1

ATTENTION: Batch 256 32_32_0.1 Batch 1024 128_128_0.1

|CNN: Batch 256 128_128_4_256
Batch 1024 256_128_4_128

Results table (kabble)

att1_selected<-results_att_tune %>% filter(parameters=="128_128_0.1")
att1_selected$model<-"att1"
att2_selected<-results_att_256_tune %>% filter(parameters=="32_32_0.1")
att2_selected$model<-"att2"
lstm1_selected<-results_lstm_tune %>% filter(parameters=="64_32_0.1")
lstm1_selected$model<-"lstm1"
lstm2_selected<-results_lstm_256_tune %>% filter(parameters=="64_64_0.1")
lstm2_selected$model<-"lstm2"
cnn1d1_selected<-results_cnn1d_tune %>% filter(parameters=="256_128_4_128")
cnn1d1_selected$model<-"cnn1d1"
cnn1d2_selected<-results_cnn1d_256_tune %>% filter(parameters=="128_128_4_256")
cnn1d2_selected$model<-"cnn1d2"
results_final_models<-rbind( att1_selected,
       att2_selected,
       lstm1_selected,
       lstm2_selected,
       cnn1d1_selected,
       cnn1d2_selected
       ) %>% group_by(model,metric) %>% summarise(n=n(),
                                                  mean=mean(value),
                                                  sd=sd(value), 
                                                  se=sd/sqrt(n), 
                                                  ci=qt(p=0.025, df=n-1,lower.tail=F)*se)
`summarise()` has grouped output by 'model'. You can override using the `.groups` argument.
results_final_models
n<-balanced_accuracy_augment20x2_dataset %>% length()
mu<-mean(balanced_accuracy_augment20x2_dataset %>% unlist())
s<-sd(balanced_accuracy_augment20x2_dataset %>% unlist())
error<- qnorm(0.975)*s/sqrt(n)
c(mu+error,mu-error)
[1] 0.9541808 0.8885488

Selected models CI (confidence intervals) vs final results on CTU19B

#results_att_256_tune_ordered<-results_att_256_tune %>% filter(metric=="Balanced Accuracy") %>% select(value,parameters) %>% group_by(parameters) %>%  summarise(mean=mean(value),sd=sd(value))  %>% arrange(desc(mean))
#results_att_256_tune_ordered$parameters<-factor(results_att_256_tune_ordered$parameters,levels=unique(results_att_256_tune_ordered$parameters))
final_models_plot<-  ggplot(aes(x=model,y=mean),data=results_final_models)+
  facet_wrap(~metric)+
   geom_errorbar(aes(ymin=mean-ci, ymax=mean+ci), width=.2,color='yellow')+
  
   geom_point(color='red')+
   geom_point(aes(y=value), data=tbl_results_final_models, color='green')+
  
  ylab("Model")+
    ggdark::dark_theme_bw()+
  theme(axis.text.x = element_text(angle = 45, hjust = 1))+
   theme(legend.position="none")+
   #ylim(0.80,0.95)+
   labs(title="SELECTED MODELS: Variation vs. Final Results on CTU19B",subtitle ="Metrics: Mean (in red) and CI (in yellow). Green: result on CTU19B",caption="")
final_models_plot

LS0tCnRpdGxlOiAiSW1iYWxhbmNlIHN0cmF0ZWdpZXMgZXZhbHVhdGlvbiBhbmQgbW9kZWwgc2VsZWN0aW9uIgpvdXRwdXQ6IAogIGh0bWxfbm90ZWJvb2s6IAogICAgY29kZV9mb2xkaW5nOiBzaG93CiAgICB0aGVtZTogY2VydWxlYW4KLS0tCmBgYHtyfQpsaWJyYXJ5KHJlYWRyKQpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KGJyb29tKQpsaWJyYXJ5KHN0cmluZ3IpCmxpYnJhcnkoZ2dwbG90MikKYGBgCiMgSU1CQUxBTkNFIFNUUkFURUdJRVMKCldlIGhhdmUgdHJpZWQgc2V2ZXJhbCBhcHByb2FjaGVzIGZvciBkZWFsaW5nIHdpdGggaW1iYWxhY2VkIGNsYXNzZXMuIFdlIGhhdmUgdXNlZCB0aGUgV29vZGJyaWRnZSAoMjAxNikgTFNUTSBuZXR3b3JrIGZvciBldmFsdWF0aW5nIHRoZSBkaWZmZXJlbnQgaW1iYWxhY2VkIGFwcHJvYWNoZXMuIAoKIyMgMTAlIFNBTVBMRQpUaGUgZm9sbG93aW5nIHNlY3Rpb25zIHByZXNlbnRzIHRoZSByZXN1bHRzIG9mIHRoZSBkaWZmZXJlbnQgaW1hYmFsYW5jZSB0ZWNobmlxdWVzIG9uIHNhbXBsZXMgb2YgMTAlIG9mIHRoZSBDVFUxOUEgZGF0YXNldAojIyMgRG93bnNhbXBsaW5nIApgYGB7cn0KZmlsZXMgPC0gbGlzdC5maWxlcyhwYXRoID0gIi4uL3Jlc3VsdHMvIixwYXR0ZXJuPSJyZXN1bHRzX3Rlc3RfaW1iYWxhbmNlLWRvd25zYW1wbGluZy1lcG9jaHM9NjAtZW5kZ2FtZS1tYXhsZW49MjAwLVxcZCsiKQoKI3N0cl9yZXBsYWNlKHN0cmluZyA9ICJyZXN1bHRzX3Rlc3RfY3R1MTMtbHN0bV9lbmRnYW1lLTgwLTEuY3N2IixwYXR0ZXJuID0gIi4qLShbMC05XSspLVswLTldKy5jc3YiLCJcXDEiKQoKcmVzdWx0c19sc3RtX2Rvd25zYW1wbGUgPC0gbGFwcGx5KGZpbGVzLCBmdW5jdGlvbih4KSAKICAgICAgICAgICAgICAgIHJlYWRfY3N2KHBhc3RlKCIuLi9yZXN1bHRzLyIseCxzZXA9IiIpLCBjb2xfdHlwZXMgPSBjb2xzKCkpICAgCiAgICAgICAgICAgICAgICAlPiUgdGliYmxlOjphZGRfY29sdW1uKG1heGxlbj1hcy5pbnRlZ2VyKHN0cl9yZXBsYWNlKHN0cmluZyA9IHggLHBhdHRlcm4gPSAiLio9KFswLTldKyktWzAtOV0rLmNzdiIsIlxcMSIpICkpCiAgICAgICAgICAgICAgICApCgpyZXN1bHRzX2xzdG1fZG93bnNhbXBsZSA8LWRvLmNhbGwocmJpbmQscmVzdWx0c19sc3RtX2Rvd25zYW1wbGUpCgoKCmBgYAoKIyMjIFVwc2FtcGxpbmcgCmBgYHtyfQpmaWxlcyA8LSBsaXN0LmZpbGVzKHBhdGggPSAiLi4vcmVzdWx0cy8iLHBhdHRlcm49InJlc3VsdHNfdGVzdF9pbWJhbGFuY2UtdXBzYW1wbGluZy1lcG9jaHM9NjAtZW5kZ2FtZS1tYXhsZW49MjAwLVxcZCsiKQoKI3N0cl9yZXBsYWNlKHN0cmluZyA9ICJyZXN1bHRzX3Rlc3RfY3R1MTMtbHN0bV9lbmRnYW1lLTgwLTEuY3N2IixwYXR0ZXJuID0gIi4qLShbMC05XSspLVswLTldKy5jc3YiLCJcXDEiKQoKcmVzdWx0c19sc3RtX3Vwc2FtcGxlIDwtIGxhcHBseShmaWxlcywgZnVuY3Rpb24oeCkgCiAgICAgICAgICAgICAgICByZWFkX2NzdihwYXN0ZSgiLi4vcmVzdWx0cy8iLHgsc2VwPSIiKSxjb2xfdHlwZXMgPSBjb2xzKCkpICAgCiAgICAgICAgICAgICAgICAlPiUgdGliYmxlOjphZGRfY29sdW1uKG1heGxlbj1hcy5pbnRlZ2VyKHN0cl9yZXBsYWNlKHN0cmluZyA9IHggLHBhdHRlcm4gPSAiLio9KFswLTldKyktWzAtOV0rLmNzdiIsIlxcMSIpICkpCiAgICAgICAgICAgICAgICApCgpyZXN1bHRzX2xzdG1fdXBzYW1wbGUgPC1kby5jYWxsKHJiaW5kLHJlc3VsdHNfbHN0bV91cHNhbXBsZSkKCgpgYGAKCgoKCiMjIyBBdWdtZW50aW5nIGRhdGEgKHNoaWZ0IHNlcXVlbmNlIGFwcHJvYWNoKQojIyMjIE9ubHkgbm9ybWFsIGRhdGEKYGBge3J9CmZpbGVzIDwtIGxpc3QuZmlsZXMocGF0aCA9ICIuLi9yZXN1bHRzLyIscGF0dGVybj0icmVzdWx0c190ZXN0X2ltYmFsYW5jZS1hdWdtZW50aW5nLWVwb2Nocz02MC1lbmRnYW1lLW1heGxlbj0yMDAtXFxkKyIpCgojc3RyX3JlcGxhY2Uoc3RyaW5nID0gInJlc3VsdHNfdGVzdF9jdHUxMy1sc3RtX2VuZGdhbWUtODAtMS5jc3YiLHBhdHRlcm4gPSAiLiotKFswLTldKyktWzAtOV0rLmNzdiIsIlxcMSIpCgpyZXN1bHRzX2xzdG1fYXVnbWVudCA8LSBsYXBwbHkoZmlsZXMsIGZ1bmN0aW9uKHgpIAogICAgICAgICAgICAgICAgcmVhZF9jc3YocGFzdGUoIi4uL3Jlc3VsdHMvIix4LHNlcD0iIiksY29sX3R5cGVzID0gY29scygpKSAgIAogICAgICAgICAgICAgICAgJT4lIHRpYmJsZTo6YWRkX2NvbHVtbihtYXhsZW49YXMuaW50ZWdlcihzdHJfcmVwbGFjZShzdHJpbmcgPSB4ICxwYXR0ZXJuID0gIi4qPShbMC05XSspLVswLTldKy5jc3YiLCJcXDEiKSApKQogICAgICAgICAgICAgICAgKQoKcmVzdWx0c19sc3RtX2F1Z21lbnQgPC1kby5jYWxsKHJiaW5kLHJlc3VsdHNfbHN0bV9hdWdtZW50KQoKCmBgYAoKIyMjIyBOb3JtYWwgYW5kIEJvdG5ldApgYGB7cn0KZmlsZXMgPC0gbGlzdC5maWxlcyhwYXRoID0gIi4uL3Jlc3VsdHMvIixwYXR0ZXJuPSJyZXN1bHRzX3Rlc3RfaW1iYWxhbmNlLWF1Z21lbnRpbmctYm90bmV0LWVwb2Nocz02MC1lbmRnYW1lLW1heGxlbj0yMDAtXFxkKyIpCnJlc3VsdHNfbHN0bV9hdWdtZW50NHggPC0gbGFwcGx5KGZpbGVzLCBmdW5jdGlvbih4KSAKICAgICAgICAgICAgICAgIHJlYWRfY3N2KHBhc3RlKCIuLi9yZXN1bHRzLyIseCxzZXA9IiIpLGNvbF90eXBlcyA9IGNvbHMoKSkgICAKICAgICAgICAgICAgICAgICU+JSB0aWJibGU6OmFkZF9jb2x1bW4obWF4bGVuPWFzLmludGVnZXIoc3RyX3JlcGxhY2Uoc3RyaW5nID0geCAscGF0dGVybiA9ICIuKj0oWzAtOV0rKS1bMC05XSsuY3N2IiwiXFwxIikgKSkKICAgICAgICAgICAgICAgICkKCnJlc3VsdHNfbHN0bV9hdWdtZW50NHggPC1kby5jYWxsKHJiaW5kLHJlc3VsdHNfbHN0bV9hdWdtZW50NHgpCgpmaWxlcyAlPiUgbGVuZ3RoKCkKYGBgCiMjIyBDb21wYXJpc29uCmBgYHtyIGZpZy5oZWlnaHQ9NiwgZmlnLndpZHRoPTd9CgpyZXN1bHRzPC1yYmluZChyZXN1bHRzX2xzdG1fdXBzYW1wbGUgJT4lIHRpYmJsZTo6YWRkX2NvbHVtbihpbWJhbGFuY2U9InVwc2FtcGxlIiksCiAgICAgICAgICAgICAgIHJlc3VsdHNfbHN0bV9kb3duc2FtcGxlICU+JSB0aWJibGU6OmFkZF9jb2x1bW4oaW1iYWxhbmNlPSJkb3duc2FtcGxlIiksIAogICAgICAgICAgICAgICByZXN1bHRzX2xzdG1fYXVnbWVudCAlPiUgdGliYmxlOjphZGRfY29sdW1uKGltYmFsYW5jZT0iYXVnLW5vcm0iKSwKICAgICAgICAgICAgICAgcmVzdWx0c19sc3RtX2F1Z21lbnQ0eCAlPiUgdGliYmxlOjphZGRfY29sdW1uKGltYmFsYW5jZT0iYXVnLW5vcm0tYm90IikpCgoKcmVzdWx0cyAlPiUgZmlsdGVyKG1ldHJpYyAlaW4lICBjKCJCYWxhbmNlZCBBY2N1cmFjeSIsIkYxIiwiU2Vuc2l0aXZpdHkiLCJTcGVjaWZpY2l0eSIpKSAlPiUKICBnZ3Bsb3QoKSArCiAgbGFicyh0aXRsZT0iSW1iYWxhbmNlIFN0cmF0ZWdpZXMgWzMwIGV4ZWN1dGlvbnNdIiwKICAgICAgICBzdWJ0aXRsZT0iTFNUTSBhcmNoLiBhY2NvcmRpbmcgdG8gV29vZGJyaWRnZSBldCBhbC4gKDIwMTYpOlxubWF4bGVuIDIwMCwgZXBvY2hzIDYwIiwKICAgICAgICBjYXB0aW9uPSJPcmlnaW5hbCBzYW1wbGUgY29udGFpbnM6ICB+MTgwMCBib3RuZXRzIGFuZCB+MTgwIG5vcm1hbCBkYXRhIHBvaW50cy5cbgogICAgICAgYXVnLW5vcm0gYW5kIGF1Zy1ub3JtLWJvdCB1c2UgYSBzbGlkaW5nIHdpbmRvdyBvdmVyIHNlcXVlbmNlIGZvciBnZW5lcmF0ZSBuZXcgc2FtcGxlcy4gIikrCiAgCiAgZ2VvbV9ib3hwbG90KGFlcyh4PWFzLmZhY3RvcihpbWJhbGFuY2UpLHk9dmFsdWUsZmlsbD1hcy5mYWN0b3IoaW1iYWxhbmNlKSksY29sb3I9J2dyYXknKSsKICB4bGFiKCJTdHJhdGVneSIpKwogIHRoZW1lX2J3KCkrCiAgZ2dkYXJrOjpkYXJrX3RoZW1lX2dyYXkoKSsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpKSsKICB0aGVtZShsZWdlbmQucG9zaXRpb249Im5vbmUiKSsKICBmYWNldF93cmFwKH5tZXRyaWMsIHNjYWxlcyA9ICJmaXhlZCIpCmBgYAoKIyMgMjAlIFNBTVBMRQpUaGUgZm9sbG93aW5nIHNlY3Rpb25zIHByZXNlbnRzIHRoZSByZXN1bHRzIG9mIHRoZSBkaWZmZXJlbnQgaW1hYmFsYW5jZSB0ZWNobmlxdWVzIG9uIHNhbXBsZXMgb2YgMTAlIG9mIHRoZSBDVFUxOUEgZGF0YXNldC4gVGhlIDIwJSB3YXMgY2hvb3NlbiBiYXNlZCBvbiBhIGNhcmVmdWxsIGFuYWxpc3lzIG9mIHRoZSBkaWZmZXJlbmNlcyBiZXR3ZWVuIHBvcC4gYW5kIHNhbXBsZSBkaXN0cmlidXRpb24uCgojIyMgQXVnbWVudGluZyAyMCUgYm90bmV0IDJ4CldlIGFwcGx5IHRoZSBzaGlmdCBzZXF1ZW5jZSBkYXRhLWF1Z21lbnRhdGlvbiAgYXBwcm9hY2ggZm9yIGdlbmVyYXRpbmcgMnggYm90bmV0IGFuZCB0aGVuIGdlbmVyYXRlIHRoZSBzYW1lIGFtb3VudCBvZiBub3JtYWwgZGF0YS4KCmBgYHtyfQpmaWxlcyA8LSBsaXN0LmZpbGVzKHBhdGggPSAiLi4vcmVzdWx0cy8iLHBhdHRlcm49InJlc3VsdHNfdGVzdF9pbWJhbGFuY2Utc2FtcGxlMjAtYXVnbWVudC1ib3RuZXR4Mi1lcG9jaHM9NjAtZW5kZ2FtZS1tYXhsZW49MjAwLVxcZCsiKQpyZXN1bHRzX2xzdG1fYXVnbWVudF9zYW1wbGUyMHgyIDwtIGxhcHBseShmaWxlcywgZnVuY3Rpb24oeCkgCiAgICAgICAgICAgICAgICByZWFkX2NzdihwYXN0ZSgiLi4vcmVzdWx0cy8iLHgsc2VwPSIiKSwgY29sX3R5cGVzID0gY29scygpKSAgIAogICAgICAgICAgICAgICAgJT4lIHRpYmJsZTo6YWRkX2NvbHVtbihtYXhsZW49YXMuaW50ZWdlcihzdHJfcmVwbGFjZShzdHJpbmcgPSB4ICxwYXR0ZXJuID0gIi4qPShbMC05XSspLVswLTldKy5jc3YiLCJcXDEiKSApKQogICAgICAgICAgICAgICAgKQoKcmVzdWx0c19sc3RtX2F1Z21lbnRfc2FtcGxlMjB4MiA8LWRvLmNhbGwocmJpbmQscmVzdWx0c19sc3RtX2F1Z21lbnRfc2FtcGxlMjB4MikKCmZpbGVzICU+JSBsZW5ndGgoKQpgYGAKCiMjIyBBdWdtZW50aW5nIDIwJSBib3RuZXQgMXgKV2UgYXBwbHkgdGhlIHNoaWZ0IHNlcXVlbmNlIGRhdGEtYXVnbWVudGF0aW9uICBhcHByb2FjaCBmb3IgZ2VuZXJhdGluZyAxeCBib3RuZXQgYW5kIHRoZW4gZ2VuZXJhdGUgdGhlIHNhbWUgYW1vdW50IG9mIG5vcm1hbCBkYXRhLgoKYGBge3J9CmZpbGVzIDwtIGxpc3QuZmlsZXMocGF0aCA9ICIuLi9yZXN1bHRzLyIscGF0dGVybj0icmVzdWx0c190ZXN0X2ltYmFsYW5jZS1zYW1wbGUyMC1hdWdtZW50LWJvdG5ldC1lcG9jaHM9NjAtZW5kZ2FtZS1tYXhsZW49MjAwLVxcZCsiKQpyZXN1bHRzX2xzdG1fYXVnbWVudF9zYW1wbGUyMHgzIDwtIGxhcHBseShmaWxlcywgZnVuY3Rpb24oeCkgCiAgICAgICAgICAgICAgICByZWFkX2NzdihwYXN0ZSgiLi4vcmVzdWx0cy8iLHgsc2VwPSIiKSwgY29sX3R5cGVzID0gY29scygpKSAgIAogICAgICAgICAgICAgICAgJT4lIHRpYmJsZTo6YWRkX2NvbHVtbihtYXhsZW49YXMuaW50ZWdlcihzdHJfcmVwbGFjZShzdHJpbmcgPSB4ICxwYXR0ZXJuID0gIi4qPShbMC05XSspLVswLTldKy5jc3YiLCJcXDEiKSApKQogICAgICAgICAgICAgICAgKQoKcmVzdWx0c19sc3RtX2F1Z21lbnRfc2FtcGxlMjB4MyA8LWRvLmNhbGwocmJpbmQscmVzdWx0c19sc3RtX2F1Z21lbnRfc2FtcGxlMjB4MykKCmZpbGVzICU+JSBsZW5ndGgoKQpgYGAKCiMjIyBBdWdtZW50aW5nIDIwJSBib3RuZXQgNXgKV2UgYXBwbHkgdGhlIHNoaWZ0IHNlcXVlbmNlIGRhdGEtYXVnbWVudGF0aW9uICBhcHByb2FjaCBmb3IgZ2VuZXJhdGluZyA1eCBib3RuZXQgYW5kIHRoZW4gZ2VuZXJhdGUgdGhlIHNhbWUgYW1vdW50IG9mIG5vcm1hbCBkYXRhLgoKYGBge3J9CmZpbGVzIDwtIGxpc3QuZmlsZXMocGF0aCA9ICIuLi9yZXN1bHRzLyIscGF0dGVybj0icmVzdWx0c190ZXN0X2ltYmFsYW5jZS1zYW1wbGUyMC1hdWdtZW50LWJvdG5ldHg1LWVwb2Nocz02MC1lbmRnYW1lLW1heGxlbj0yMDAtXFxkKyIpCnJlc3VsdHNfbHN0bV9hdWdtZW50X3NhbXBsZTIweDUgPC0gbGFwcGx5KGZpbGVzLCBmdW5jdGlvbih4KSAKICAgICAgICAgICAgICAgIHJlYWRfY3N2KHBhc3RlKCIuLi9yZXN1bHRzLyIseCxzZXA9IiIpLCBjb2xfdHlwZXMgPSBjb2xzKCkpICAgCiAgICAgICAgICAgICAgICAlPiUgdGliYmxlOjphZGRfY29sdW1uKG1heGxlbj1hcy5pbnRlZ2VyKHN0cl9yZXBsYWNlKHN0cmluZyA9IHggLHBhdHRlcm4gPSAiLio9KFswLTldKyktWzAtOV0rLmNzdiIsIlxcMSIpICkpCiAgICAgICAgICAgICAgICApCgpyZXN1bHRzX2xzdG1fYXVnbWVudF9zYW1wbGUyMHg1IDwtZG8uY2FsbChyYmluZCxyZXN1bHRzX2xzdG1fYXVnbWVudF9zYW1wbGUyMHg1KQoKZmlsZXMgJT4lIGxlbmd0aCgpCmBgYAoKIyMjIERvd25zYW1wbGluZyAyMCUKYGBge3J9CmZpbGVzIDwtIGxpc3QuZmlsZXMocGF0aCA9ICIuLi9yZXN1bHRzLyIscGF0dGVybj0icmVzdWx0c190ZXN0X2ltYmFsYW5jZS1zYW1wbGUyMC1kb3duc2FtcGxlLWJvdG5ldC1lcG9jaHM9NjAtZW5kZ2FtZS1tYXhsZW49MjAwLVxcZCsiKQpyZXN1bHRzX2xzdG1fZG93bnNhbXBsZV9zYW1wbGUyMCA8LSBsYXBwbHkoZmlsZXMsIGZ1bmN0aW9uKHgpIAogICAgICAgICAgICAgICAgcmVhZF9jc3YocGFzdGUoIi4uL3Jlc3VsdHMvIix4LHNlcD0iIiksIGNvbF90eXBlcyA9IGNvbHMoKSkgICAKICAgICAgICAgICAgICAgICU+JSB0aWJibGU6OmFkZF9jb2x1bW4obWF4bGVuPWFzLmludGVnZXIoc3RyX3JlcGxhY2Uoc3RyaW5nID0geCAscGF0dGVybiA9ICIuKj0oWzAtOV0rKS1bMC05XSsuY3N2IiwiXFwxIikgKSkKICAgICAgICAgICAgICAgICkKCnJlc3VsdHNfbHN0bV9kb3duc2FtcGxlX3NhbXBsZTIwIDwtZG8uY2FsbChyYmluZCxyZXN1bHRzX2xzdG1fZG93bnNhbXBsZV9zYW1wbGUyMCkKCmZpbGVzICU+JSBsZW5ndGgoKQpgYGAKCgojIyMgVXBzYW1wbGluZyAyMCUKYGBge3J9CmZpbGVzIDwtIGxpc3QuZmlsZXMocGF0aCA9ICIuLi9yZXN1bHRzLyIscGF0dGVybj0icmVzdWx0c190ZXN0X2ltYmFsYW5jZS1zYW1wbGUyMC11cHNhbXBsZS1ib3RuZXQtZXBvY2hzPTYwLWVuZGdhbWUtbWF4bGVuPTIwMC1cXGQrIikKcmVzdWx0c19sc3RtX3Vwc2FtcGxlX3NhbXBsZTIwIDwtIGxhcHBseShmaWxlcywgZnVuY3Rpb24oeCkgCiAgICAgICAgICAgICAgICByZWFkX2NzdihwYXN0ZSgiLi4vcmVzdWx0cy8iLHgsc2VwPSIiKSwgY29sX3R5cGVzID0gY29scygpKSAgIAogICAgICAgICAgICAgICAgJT4lIHRpYmJsZTo6YWRkX2NvbHVtbihtYXhsZW49YXMuaW50ZWdlcihzdHJfcmVwbGFjZShzdHJpbmcgPSB4ICxwYXR0ZXJuID0gIi4qPShbMC05XSspLVswLTldKy5jc3YiLCJcXDEiKSApKQogICAgICAgICAgICAgICAgKQoKcmVzdWx0c19sc3RtX3Vwc2FtcGxlX3NhbXBsZTIwIDwtZG8uY2FsbChyYmluZCxyZXN1bHRzX2xzdG1fdXBzYW1wbGVfc2FtcGxlMjApCgpmaWxlcyAlPiUgbGVuZ3RoKCkKYGBgCgoKIyMjIEF1Z21lbnRpbmcgMjAlIGJvdG5ldCAyeCBmcm9tIGRhdGFzZXRzCkFsbCBwcmV2aW91cyBhdWdtZW50aW5nIGRhdGEgYXBwcm9hY2hlcyB3ZXJlIGFwcGxpZWQgb24gdGhlIHRva2VuaXplZChrZXJhcykgdmVyc2lvbiBvZiB0aGUgc2VxdWVuY2VzLiBUaGlzIGFwcHJvYWNoIGlzIGFwcGxpZWQgZGlyZWN0bHkgb24gdGhlIGRhdGFzZXQuIFRISVMgV0FTIFRIRSBBUFBST0FDSCBVU0VEIEZPUiBHRU5FUkFUSU5HIFRIRSBBVUdNRU5URUQgdHJhaW4vdGVzdCBzZXRzIGZvciBNTUNWIGFuZCBmaW5hbCBDVFUxOUEtYXVnbWVudGVkCgpgYGB7cn0KZmlsZXMgPC0gbGlzdC5maWxlcyhwYXRoID0gIi4uL3Jlc3VsdHMvIixwYXR0ZXJuPSJyZXN1bHRzX3Rlc3RfaW1iYWxhbmNlLXNhbXBsZTIwLWF1Z21lbnQtZGF0YXNldC0yeC1lcG9jaHM9NjAtZW5kZ2FtZS1tYXhsZW49MjAwLVxcZCsiKQpyZXN1bHRzX2xzdG1fYXVnbWVudF9zYW1wbGUyMHgyX2RhdGFzZXQgPC0gbGFwcGx5KGZpbGVzLCBmdW5jdGlvbih4KSAKICAgICAgICAgICAgICAgIHJlYWRfY3N2KHBhc3RlKCIuLi9yZXN1bHRzLyIseCxzZXA9IiIpLCBjb2xfdHlwZXMgPSBjb2xzKCkpICAgCiAgICAgICAgICAgICAgICAlPiUgdGliYmxlOjphZGRfY29sdW1uKG1heGxlbj1hcy5pbnRlZ2VyKHN0cl9yZXBsYWNlKHN0cmluZyA9IHggLHBhdHRlcm4gPSAiLio9KFswLTldKyktWzAtOV0rLmNzdiIsIlxcMSIpICkpCiAgICAgICAgICAgICAgICApCgpyZXN1bHRzX2xzdG1fYXVnbWVudF9zYW1wbGUyMHgyX2RhdGFzZXQgPC1kby5jYWxsKHJiaW5kLHJlc3VsdHNfbHN0bV9hdWdtZW50X3NhbXBsZTIweDJfZGF0YXNldCkKCmZpbGVzICU+JSBsZW5ndGgoKQpgYGAKYGBge3IgZmlnLmhlaWdodD04LCBmaWcud2lkdGg9N30KCnJlc3VsdHM8LXJiaW5kKHJlc3VsdHNfbHN0bV91cHNhbXBsZV9zYW1wbGUyMCAlPiUgdGliYmxlOjphZGRfY29sdW1uKGltYmFsYW5jZT0idXBzYW1wbGUtMjAiKSwKICAgICAgICAgICAgICAgcmVzdWx0c19sc3RtX2Rvd25zYW1wbGVfc2FtcGxlMjAgJT4lIHRpYmJsZTo6YWRkX2NvbHVtbihpbWJhbGFuY2U9ImRvd25zYW1wbGUtMjAiKSwgCiAgICAgICAgICAgICAgICNyZXN1bHRzX2xzdG1fYXVnbWVudF9zYW1wbGUyMHgyICU+JSB0aWJibGU6OmFkZF9jb2x1bW4oaW1iYWxhbmNlPSJhdWctbm9ybS1ib3QtMjB4MiIpLAogICAgICAgICAgICAgICAjcmVzdWx0c19sc3RtX2F1Z21lbnRfc2FtcGxlMjB4MyAlPiUgdGliYmxlOjphZGRfY29sdW1uKGltYmFsYW5jZT0iYXVnLW5vcm0tYm90LTIweDMiKSwKICAgICAgICAgICAgICAgI3Jlc3VsdHNfbHN0bV9hdWdtZW50X3NhbXBsZTIweDUgJT4lIHRpYmJsZTo6YWRkX2NvbHVtbihpbWJhbGFuY2U9ImF1Zy1ub3JtLWJvdC0yMHg1IiksCiAgICAgICAgICAgICAgIHJlc3VsdHNfbHN0bV9hdWdtZW50X3NhbXBsZTIweDJfZGF0YXNldCAlPiUgdGliYmxlOjphZGRfY29sdW1uKGltYmFsYW5jZT0iYXVnLW5vcm0tYm90LTIweDItZGF0IikKICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAjcmVzdWx0c19sc3RtX3Vwc2FtcGxlICU+JSB0aWJibGU6OmFkZF9jb2x1bW4oaW1iYWxhbmNlPSJ1cHNhbXBsZS0xMCIpLAogICAgICAgICAgICAgICAjcmVzdWx0c19sc3RtX2Rvd25zYW1wbGUgJT4lIHRpYmJsZTo6YWRkX2NvbHVtbihpbWJhbGFuY2U9ImRvd25zYW1wbGUtMTAiKSwKICAgICAgICAKICAgICAgICAgICAgICAgI3Jlc3VsdHNfbHN0bV9hdWdtZW50ICU+JSB0aWJibGU6OmFkZF9jb2x1bW4oaW1iYWxhbmNlPSJhdWctbm9ybS1ib3QtMTAiKQogICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgKQoKCnJlc3VsdHMgJT4lIGZpbHRlcihtZXRyaWMgJWluJSAgYygiQmFsYW5jZWQgQWNjdXJhY3kiLCJGMSIsIlNlbnNpdGl2aXR5IiwiU3BlY2lmaWNpdHkiKSkgJT4lCiAgZ2dwbG90KCkgKwogIGxhYnModGl0bGU9IkltYmFsYW5jZSBTdHJhdGVnaWVzIFszMCBleGVjdXRpb25zXSIsCiAgICAgICAgc3VidGl0bGU9IkxTVE0gYXJjaC4gYWNjb3JkaW5nIHRvIFdvb2RicmlkZ2UgZXQgYWwuICgyMDE2KTpcbm1heGxlbiAyMDAsIGVwb2NocyA2MCIsCiAgICAgICAgY2FwdGlvbj0iT3JpZ2luYWwgc2FtcGxlIGNvbnRhaW5zOiAgfjM2MDAgYm90bmV0cyBhbmQgfjQwMCBub3JtYWwgZGF0YSBwb2ludHMgWzIwJSBvZiBwb3AuXVxuCiAgICAgICBhdWctbm9ybS1ib3QgdXNlIGEgc2xpZGluZyB3aW5kb3cgb3ZlciBhIHJhbmRvbSBzZXF1ZW5jZSBmb3IgZ2VuZXJhdGUgbmV3IHNhbXBsZXMuXG4KICAgICAgIHgyIHJlZmVycyB0byBkaWZmZXJlbnQgbnVtYmVyIG9mIGF1Z21lbnRlZCBzYW1wbGVzLlxuCiAgICAgICBObyBzaWduaWZpY2F0aXZlIGRpZmZlcmVuY2VzIG9ic2VydmVkIGJldHdlZW4gdXBzYW1wbGUgYW5kIGF1Zy1ub3JtLWJvdHgyCiAgICAgICAiKSsKICAKICBnZW9tX2JveHBsb3QoYWVzKHg9YXMuZmFjdG9yKGltYmFsYW5jZSkseT12YWx1ZSxmaWxsPWFzLmZhY3RvcihpbWJhbGFuY2UpKSxjb2xvcj0nZ3JheScpKwogIHhsYWIoIlN0cmF0ZWd5IikrCiAgdGhlbWVfYncoKSsKICBnZ2Rhcms6OmRhcmtfdGhlbWVfZ3JheSgpKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkpKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0ibm9uZSIpKwogIGZhY2V0X3dyYXAofm1ldHJpYywgc2NhbGVzID0gImZpeGVkIikKYGBgCgojIyBOT1JNQUwgRElTVFJJQlVUSU9OIFRFU1RTIG9mIHRoZSBNTUNWIHJlc3VsdHMKYGBge3J9CmJhbGFuY2VkX2FjY3VyYWN5X2F1Z21lbnQyMHgzPC1yZXN1bHRzX2xzdG1fYXVnbWVudF9zYW1wbGUyMHgzICU+JSBmaWx0ZXIobWV0cmljPT0iQmFsYW5jZWQgQWNjdXJhY3kiKSAlPiUgc2VsZWN0KHZhbHVlKQpiYWxhbmNlZF9hY2N1cmFjeV9hdWdtZW50MjB4NTwtcmVzdWx0c19sc3RtX2F1Z21lbnRfc2FtcGxlMjB4NSAlPiUgZmlsdGVyKG1ldHJpYz09IkJhbGFuY2VkIEFjY3VyYWN5IikgJT4lIHNlbGVjdCh2YWx1ZSkKYmFsYW5jZWRfYWNjdXJhY3lfYXVnbWVudDIweDI8LXJlc3VsdHNfbHN0bV9hdWdtZW50X3NhbXBsZTIweDIgJT4lIGZpbHRlcihtZXRyaWM9PSJCYWxhbmNlZCBBY2N1cmFjeSIpICU+JSBzZWxlY3QodmFsdWUpCmJhbGFuY2VkX2FjY3VyYWN5X3Vwc2FtcGxlMjA8LXJlc3VsdHNfbHN0bV91cHNhbXBsZV9zYW1wbGUyMCAlPiUgZmlsdGVyKG1ldHJpYz09IkJhbGFuY2VkIEFjY3VyYWN5IikgJT4lIHNlbGVjdCh2YWx1ZSkKYmFsYW5jZWRfYWNjdXJhY3lfZG93bnNhbXBsZTIwPC1yZXN1bHRzX2xzdG1fZG93bnNhbXBsZV9zYW1wbGUyMCAlPiUgZmlsdGVyKG1ldHJpYz09IkJhbGFuY2VkIEFjY3VyYWN5IikgJT4lIHNlbGVjdCh2YWx1ZSkKYmFsYW5jZWRfYWNjdXJhY3lfYXVnbWVudDIweDJfZGF0YXNldDwtcmVzdWx0c19sc3RtX2F1Z21lbnRfc2FtcGxlMjB4Ml9kYXRhc2V0ICU+JSBmaWx0ZXIobWV0cmljPT0iQmFsYW5jZWQgQWNjdXJhY3kiKSAlPiUgc2VsZWN0KHZhbHVlKQoKCmJhbGFuY2VkX2FjY3VyYWN5X2F1Z21lbnQyMHgyX2RhdGFzZXQgJT4lIHNlbGVjdCh2YWx1ZSkgJT4lCiAgZ2dwbG90KCkrCiAgZ2VvbV9oaXN0b2dyYW0oYWVzKHg9dmFsdWUpLGZpbGw9J3NreWJsdWUnLGNvbG9yPSdibGFjaycpKwogIGdnZGFyazo6ZGFya190aGVtZV9idygpCgpgYGAKIyMjIyBRUXBsb3RzCiAKYGBge3J9CmJhbGFuY2VkX2FjY3VyYWN5X2F1Z21lbnQyMHgyX2RhdGFzZXQgJT4lCiAgZ2dwbG90KCkrCiAgZ2VvbV9xcShhZXMoc2FtcGxlPXZhbHVlKSxjb2xvcj0nb3JhbmdlJykrCiAgZ2dkYXJrOjpkYXJrX3RoZW1lX2J3KCkKCmJhbGFuY2VkX2FjY3VyYWN5X2F1Z21lbnQyMHgyX2RhdGFzZXQgJT4lIHVubGlzdCgpICU+JQogIHNoYXBpcm8udGVzdCgpCgpgYGAKCiMjIyBDSQpgYGB7cn0KbjwtYmFsYW5jZWRfYWNjdXJhY3lfYXVnbWVudDIweDJfZGF0YXNldCAlPiUgbGVuZ3RoKCkKbXU8LW1lYW4oYmFsYW5jZWRfYWNjdXJhY3lfYXVnbWVudDIweDJfZGF0YXNldCAlPiUgdW5saXN0KCkpCnM8LXNkKGJhbGFuY2VkX2FjY3VyYWN5X2F1Z21lbnQyMHgyX2RhdGFzZXQgJT4lIHVubGlzdCgpKQplcnJvcjwtIHFub3JtKDAuOTc1KSpzL3NxcnQobikKYyhtdStlcnJvcixtdS1lcnJvcikKYGBgCiMjIyBXaWxjb3ggTWFubiBXaGl0bmV5IHRlc3QKCmBgYHtyfQp3aWxjb3gudGVzdChiYWxhbmNlZF9hY2N1cmFjeV9hdWdtZW50MjB4MyAlPiUgdW5saXN0KCksCiAgICAgICAgICAgIGJhbGFuY2VkX2FjY3VyYWN5X2F1Z21lbnQyMHgyICU+JSB1bmxpc3QoKQogICAgICAgICAgICApCgphdWdtZW50X3Jlc3VsdHM8LWRhdGEuZnJhbWUoZG93bnNtYXBsZT1iYWxhbmNlZF9hY2N1cmFjeV9kb3duc2FtcGxlMjAgJT4lIHVubGlzdCgpLAogICAgICAgICAgIGF1Z21lbnQyMHgyX2RhdGFzZXQ9YmFsYW5jZWRfYWNjdXJhY3lfYXVnbWVudDIweDJfZGF0YXNldCAlPiUgdW5saXN0KCksCiAgICAgICAgICAKICAgICAgICAgICB1cHNhbXBsZT1iYWxhbmNlZF9hY2N1cmFjeV91cHNhbXBsZTIwICU+JSB1bmxpc3QoKQogICAgICAgICAgICNkb3duc2FtcGxlPWJhbGFuY2VkX2FjY3VyYWN5X2Rvd25zYW1wbGUyMCAlPiUgdW5saXN0KCkKICAgICAgICAgICAgCiAgICAgICAgICAgKSAlPiUgcmVzaGFwZTI6Om1lbHQoKSAKCm9uZXdheS50ZXN0KHZhbHVlfnZhcmlhYmxlLGRhdGE9YXVnbWVudF9yZXN1bHRzKSAlPiUgYnJvb206OnRpZHkoKQoKCmBgYAojIyBNT0RFTCBTRUxFQ1RJT04gKE1NQ1YpCiMjIExTVE0KIyMjIEJhdGNoIDEwMjQKYGBge3IgZmlnLmhlaWdodD02LCBmaWcud2lkdGg9MTB9CmZpbGVzIDwtIGxpc3QuZmlsZXMocGF0aCA9ICIuLi9yZXN1bHRzLyIscGF0dGVybj0icmVzdWx0c190ZXN0X2xzdG0tZW5kZ2FtZS1hdWdtZW50ZWQtY3R1MTktbWNjdi1lcG9jaHM9MTUtZW5kZ2FtZS1tYXhsZW49MTAwMC1cXGQrLWxzdG1fc2l6ZT1cXGQrLWVtYmVkaW5nZGltPVxcZCstZHJvcG91dD1bMC05Ll0rLmNzdiIpCgpyZXN1bHRzX2xzdG1fdHVuZSA8LSBsYXBwbHkoZmlsZXMsIGZ1bmN0aW9uKHgpIAogICAgICAgICAgICAgICAgcmVhZF9jc3YocGFzdGUoIi4uL3Jlc3VsdHMvIix4LHNlcD0iIiksIGNvbF90eXBlcyA9IGNvbHMoKSkgICAKICAgICAgICAgICAgICAgICU+JSB0aWJibGU6OmFkZF9jb2x1bW4obHN0bV9zaXplPWFzLmludGVnZXIoc3RyX3JlcGxhY2Uoc3RyaW5nID0geCAscGF0dGVybiA9ICIuKi1sc3RtX3NpemU9KFswLTldKyktLiouY3N2IiwiXFwxIikgKSkKICAgICAgICAgICAgICAgJT4lIHRpYmJsZTo6YWRkX2NvbHVtbihlbWJlZGluZ2RpbT1hcy5pbnRlZ2VyKHN0cl9yZXBsYWNlKHN0cmluZyA9IHggLHBhdHRlcm4gPSAiLiotZW1iZWRpbmdkaW09KFswLTldKyktLiouY3N2IiwiXFwxIikgKSkKICAgICAgICAgICAgICAgJT4lIHRpYmJsZTo6YWRkX2NvbHVtbihkcm9wb3V0PWFzLm51bWVyaWMoc3RyX3JlcGxhY2Uoc3RyaW5nID0geCAscGF0dGVybiA9ICIuKi1kcm9wb3V0PShbMC05XSsuWzAtOV0pLmNzdiIsIlxcMSIpICkpCiAgICAgICAgICAgICAgICU+JSB0aWJibGU6OmFkZF9jb2x1bW4oc2FtcGxlPWFzLmZhY3RvcihzdHJfcmVwbGFjZShzdHJpbmcgPSB4ICxwYXR0ZXJuID0gIi4qPTEwMDAtKFxcZCspLS4qLmNzdiIsIlxcMSIpICkpCiAgICAgICAgICAKICAgICAgICAgICAgICAgICkKCnJlc3VsdHNfbHN0bV90dW5lIDwtZG8uY2FsbChyYmluZCxyZXN1bHRzX2xzdG1fdHVuZSkKcmVzdWx0c19sc3RtX3R1bmUgJT4lIGdyb3VwX2J5KHNhbXBsZSkgJT4lIHN1bW1hcmlzZShuPW4oKSkKYGBgCgpgYGB7ciBmaWcuaGVpZ2h0PTYsIGZpZy53aWR0aD0xMH0KCnJlc3VsdHNfbHN0bV90dW5lPC1yZXN1bHRzX2xzdG1fdHVuZSAlPiUgdGlkeXI6OnVuaXRlKCJwYXJhbWV0ZXJzIixsc3RtX3NpemU6ZW1iZWRpbmdkaW06ZHJvcG91dCkgJT4lIGZpbHRlcihtZXRyaWMgJWluJSAgYygiQmFsYW5jZWQgQWNjdXJhY3kiLCJGMSIsIlNlbnNpdGl2aXR5IiwiU3BlY2lmaWNpdHkiKSkKCnJlc3VsdHNfbHN0bV90dW5lICU+JSBnZ3Bsb3QoKSsKICBnZW9tX2JveHBsb3QoYWVzKHk9dmFsdWUseD1wYXJhbWV0ZXJzLGZpbGw9cGFyYW1ldGVycyksY29sb3I9J2dyYXknKSsKICBsYWJzKHRpdGxlPSJQYXJhbWV0ZXJzIFR1bmluZzogTFNUTSBXb29kYnJpZGdlICgyMDE2KSIsc3VidGl0bGUgPSJQYXJhbWV0ZXJlczogPGxzdG1fc2l6ZT5fPGVtYmVkaW5nX3NpemU+Xzxkcm9wb3V0PiIpKwogIHhsYWIoIlBhcmFtZXRlcnMiKSsKICBnZ2Rhcms6OmRhcmtfdGhlbWVfZ3JheSgpKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkpKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0ibm9uZSIpKwogIGZhY2V0X3dyYXAofm1ldHJpYykKCmBgYAoKYGBge3J9CnJlc3VsdHNfbHN0bV90dW5lX29yZGVyZWQ8LXJlc3VsdHNfbHN0bV90dW5lICU+JSBmaWx0ZXIobWV0cmljPT0iQmFsYW5jZWQgQWNjdXJhY3kiKSAlPiUgc2VsZWN0KHZhbHVlLHBhcmFtZXRlcnMpICU+JSBncm91cF9ieShwYXJhbWV0ZXJzKSAlPiUgIHN1bW1hcmlzZShtZWFuPW1lYW4odmFsdWUpLHNkPXNkKHZhbHVlKSkgICU+JSBhcnJhbmdlKGRlc2MobWVhbikpCgpyZXN1bHRzX2xzdG1fdHVuZV9vcmRlcmVkJHBhcmFtZXRlcnM8LWZhY3RvcihyZXN1bHRzX2xzdG1fdHVuZV9vcmRlcmVkJHBhcmFtZXRlcnMsbGV2ZWxzPXVuaXF1ZShyZXN1bHRzX2xzdG1fdHVuZV9vcmRlcmVkJHBhcmFtZXRlcnMpKQoKCmxzdG1fc2RfcGxvdDwtICBnZ3Bsb3QoYWVzKHg9cGFyYW1ldGVycyx5PW1lYW4pLGRhdGE9cmVzdWx0c19sc3RtX3R1bmVfb3JkZXJlZCkrCiAgIGdlb21fZXJyb3JiYXIoYWVzKHltaW49bWVhbi1zZCwgeW1heD1tZWFuK3NkKSwgd2lkdGg9LjIsY29sb3I9J3llbGxvdycpKwogICBnZW9tX3BvaW50KGNvbG9yPSdyZWQnKSsKICB5bGFiKCJNZWFuIEJhbGFuY2VkIEFjY3VyYWN5IikrCiAgICBnZ2Rhcms6OmRhcmtfdGhlbWVfYncoKSsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpKSsKICAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJub25lIikrCiAgIHlsaW0oMC44MCwwLjk1KSsKICAgbGFicyh0aXRsZT0iUGFyYW1ldGVycyBUdW5pbmc6IExTVE0gV29vZGJyaWRnZSAoMjAxNikiLHN1YnRpdGxlID0iQmFsYW5jZWQgQWNjdXJhY3k6IE1lYW4gYW5kIFN0YW5kYXJkIERldmlhdGlvblxuUGFyYW1ldGVyZXM6IDxsc3RtX3NpemU+XzxlbWJlZGluZ19zaXplPl88ZHJvcG91dD4iLGNhcHRpb249ImJhdGNoX3NpemU9MTAyNCIpCiAgCmxzdG1fc2RfcGxvdApgYGAKCiMjIyBCYXRjaCAyNTYKYGBge3IgZmlnLmhlaWdodD02LCBmaWcud2lkdGg9MTB9CmZpbGVzIDwtIGxpc3QuZmlsZXMocGF0aCA9ICIuLi9yZXN1bHRzLyIsCiAgICAgICAgICAgICAgICAgICAgcGF0dGVybj0icmVzdWx0c190ZXN0X2xzdG0tZW5kZ2FtZS1hdWdtZW50ZWQtY3R1MTktbWNjdi1lcG9jaHM9MTUtZW5kZ2FtZS1iYXRjaD0yNTYtbWF4bGVuPTEwMDAtXFxkKy1sc3RtX3NpemU9XFxkKy1lbWJlZGluZ2RpbT1cXGQrLWRyb3BvdXQ9WzAtOS5dKy5jc3YiKQoKcmVzdWx0c19sc3RtXzI1Nl90dW5lIDwtIGxhcHBseShmaWxlcywgZnVuY3Rpb24oeCkgCiAgICAgICAgICAgICAgICByZWFkX2NzdihwYXN0ZSgiLi4vcmVzdWx0cy8iLHgsc2VwPSIiKSwgY29sX3R5cGVzID0gY29scygpKSAgIAogICAgICAgICAgICAgICAgJT4lIHRpYmJsZTo6YWRkX2NvbHVtbihsc3RtX3NpemU9YXMuaW50ZWdlcihzdHJfcmVwbGFjZShzdHJpbmcgPSB4ICxwYXR0ZXJuID0gIi4qLWxzdG1fc2l6ZT0oWzAtOV0rKS0uKi5jc3YiLCJcXDEiKSApKQogICAgICAgICAgICAgICAlPiUgdGliYmxlOjphZGRfY29sdW1uKGVtYmVkaW5nZGltPWFzLmludGVnZXIoc3RyX3JlcGxhY2Uoc3RyaW5nID0geCAscGF0dGVybiA9ICIuKi1lbWJlZGluZ2RpbT0oWzAtOV0rKS0uKi5jc3YiLCJcXDEiKSApKQogICAgICAgICAgICAgICAlPiUgdGliYmxlOjphZGRfY29sdW1uKGRyb3BvdXQ9YXMubnVtZXJpYyhzdHJfcmVwbGFjZShzdHJpbmcgPSB4ICxwYXR0ZXJuID0gIi4qLWRyb3BvdXQ9KFswLTldKy5bMC05XSkuY3N2IiwiXFwxIikgKSkKICAgICAgICAgICAgICAgJT4lIHRpYmJsZTo6YWRkX2NvbHVtbihzYW1wbGU9YXMuZmFjdG9yKHN0cl9yZXBsYWNlKHN0cmluZyA9IHggLHBhdHRlcm4gPSAiLio9MTAwMC0oXFxkKyktLiouY3N2IiwiXFwxIikgKSkKICAgICAgICAgIAogICAgICAgICAgICAgICAgKQoKcmVzdWx0c19sc3RtXzI1Nl90dW5lIDwtZG8uY2FsbChyYmluZCxyZXN1bHRzX2xzdG1fMjU2X3R1bmUpCnJlc3VsdHNfbHN0bV8yNTZfdHVuZSAlPiUgZ3JvdXBfYnkoc2FtcGxlKSAlPiUgc3VtbWFyaXNlKG49bigpKQpgYGAKCgpgYGB7cn0KCnJlc3VsdHNfbHN0bV8yNTZfdHVuZTwtcmVzdWx0c19sc3RtXzI1Nl90dW5lICU+JSB0aWR5cjo6dW5pdGUoInBhcmFtZXRlcnMiLGxzdG1fc2l6ZTplbWJlZGluZ2RpbTpkcm9wb3V0KSAlPiUgZmlsdGVyKG1ldHJpYyAlaW4lICBjKCJCYWxhbmNlZCBBY2N1cmFjeSIsIkYxIiwiU2Vuc2l0aXZpdHkiLCJTcGVjaWZpY2l0eSIpKQoKcmVzdWx0c19sc3RtXzI1Nl90dW5lX29yZGVyZWQ8LXJlc3VsdHNfbHN0bV8yNTZfdHVuZSAlPiUgZmlsdGVyKG1ldHJpYz09IkJhbGFuY2VkIEFjY3VyYWN5IikgJT4lIHNlbGVjdCh2YWx1ZSxwYXJhbWV0ZXJzKSAlPiUgZ3JvdXBfYnkocGFyYW1ldGVycykgJT4lICBzdW1tYXJpc2UobWVhbj1tZWFuKHZhbHVlKSxzZD1zZCh2YWx1ZSkpICAlPiUgYXJyYW5nZShkZXNjKG1lYW4pKQoKcmVzdWx0c19sc3RtXzI1Nl90dW5lX29yZGVyZWQkcGFyYW1ldGVyczwtZmFjdG9yKHJlc3VsdHNfbHN0bV8yNTZfdHVuZV9vcmRlcmVkJHBhcmFtZXRlcnMsbGV2ZWxzPXVuaXF1ZShyZXN1bHRzX2xzdG1fMjU2X3R1bmVfb3JkZXJlZCRwYXJhbWV0ZXJzKSkKCgpsc3RtXzI1Nl9zZF9wbG90PC0gIGdncGxvdChhZXMoeD1wYXJhbWV0ZXJzLHk9bWVhbiksZGF0YT1yZXN1bHRzX2xzdG1fMjU2X3R1bmVfb3JkZXJlZCkrCiAgIGdlb21fZXJyb3JiYXIoYWVzKHltaW49bWVhbi1zZCwgeW1heD1tZWFuK3NkKSwgd2lkdGg9LjIsY29sb3I9J3llbGxvdycpKwogICBnZW9tX3BvaW50KGNvbG9yPSdyZWQnKSsKICB5bGFiKCJNZWFuIEJhbGFuY2VkIEFjY3VyYWN5IikrCiAgICBnZ2Rhcms6OmRhcmtfdGhlbWVfYncoKSsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpKSsKICAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJub25lIikrCiAgIHlsaW0oMC44MCwwLjk1KSsKICAgbGFicyh0aXRsZT0iUGFyYW1ldGVycyBUdW5pbmc6IExTVE0gV29vZGJyaWRnZSAoMjAxNikiLHN1YnRpdGxlID0iQmFsYW5jZWQgQWNjdXJhY3k6IE1lYW4gYW5kIFN0YW5kYXJkIERldmlhdGlvblxuUGFyYW1ldGVyZXM6IDxsc3RtX3NpemU+XzxlbWJlZGluZ19zaXplPl88ZHJvcG91dD4iLGNhcHRpb249ImJhdGNoX3NpemU9MjU2IikKICAKbHN0bV8yNTZfc2RfcGxvdApgYGAKCgoKCiMjIENOTjFECiMjIyBCYXRjaCAxMDI0CmBgYHtyIGZpZy5oZWlnaHQ9NiwgZmlnLndpZHRoPTEwfQpmaWxlcyA8LSBsaXN0LmZpbGVzKHBhdGggPSAiLi4vcmVzdWx0cy8iLHBhdHRlcm49InJlc3VsdHNfdGVzdF9jbm4xZC1jYWNpYy1hdWdtZW50ZWQtY3R1MTktbWNjdi1lcG9jaHM9MTUtZW5kZ2FtZS1iYXRjaD0xMDI0LW1heGxlbj0xMDAwLVxcZCstbmJfZmlsdGVyPVxcZCsta2VybmVsX3NpemU9XFxkKy1lbWJlZGluZ2RpbT1cXGQrLWhpZGRlbl9zaXplPVxcZCsuY3N2IikKCnJlc3VsdHNfY25uMWRfdHVuZSA8LSBsYXBwbHkoZmlsZXMsIGZ1bmN0aW9uKHgpIAogICAgICAgICAgICAgICAgcmVhZF9jc3YocGFzdGUoIi4uL3Jlc3VsdHMvIix4LHNlcD0iIiksIGNvbF90eXBlcyA9IGNvbHMoKSkgICAKICAgICAgICAgICAgICAgICU+JSB0aWJibGU6OmFkZF9jb2x1bW4obmJfZmlsdGVyPWFzLmludGVnZXIoc3RyX3JlcGxhY2Uoc3RyaW5nID0geCAscGF0dGVybiA9ICIuKi1uYl9maWx0ZXI9KFswLTldKyktLiouY3N2IiwiXFwxIikgKSkKICAgICAgICAgICAgICAgJT4lIHRpYmJsZTo6YWRkX2NvbHVtbihlbWJlZGluZ2RpbT1hcy5pbnRlZ2VyKHN0cl9yZXBsYWNlKHN0cmluZyA9IHggLHBhdHRlcm4gPSAiLiotZW1iZWRpbmdkaW09KFswLTldKyktLiouY3N2IiwiXFwxIikgKSkKICAgICAgICAgICAgICAgJT4lIHRpYmJsZTo6YWRkX2NvbHVtbihrZXJuZWxfc2l6ZT1hcy5udW1lcmljKHN0cl9yZXBsYWNlKHN0cmluZyA9IHggLHBhdHRlcm4gPSAiLiota2VybmVsX3NpemU9KFswLTldKyktLiouY3N2IiwiXFwxIikgKSkKICAgICAgICAgICAgICAlPiUgdGliYmxlOjphZGRfY29sdW1uKGhpZGRlbl9zaXplPWFzLm51bWVyaWMoc3RyX3JlcGxhY2Uoc3RyaW5nID0geCAscGF0dGVybiA9ICIuKi1oaWRkZW5fc2l6ZT0oWzAtOV0rKS5jc3YiLCJcXDEiKSApKQogICAgICAgICAgICAgIAogICAgICAgICAgICAgICAlPiUgdGliYmxlOjphZGRfY29sdW1uKHNhbXBsZT1hcy5mYWN0b3Ioc3RyX3JlcGxhY2Uoc3RyaW5nID0geCAscGF0dGVybiA9ICIuKj0xMDAwLShcXGQrKS0uKi5jc3YiLCJcXDEiKSApKQogICAgICAgICAgCiAgICAgICAgICAgICAgICApCgpyZXN1bHRzX2NubjFkX3R1bmUgPC1kby5jYWxsKHJiaW5kLHJlc3VsdHNfY25uMWRfdHVuZSkKcmVzdWx0c19jbm4xZF90dW5lICU+JSBncm91cF9ieShzYW1wbGUpICU+JSBzdW1tYXJpc2Uobj1uKCkpCgpgYGAKCgpgYGB7ciBmaWcuaGVpZ2h0PTYsIGZpZy53aWR0aD0xMH0KCnJlc3VsdHNfY25uMWRfdHVuZTwtcmVzdWx0c19jbm4xZF90dW5lICU+JSB0aWR5cjo6dW5pdGUoInBhcmFtZXRlcnMiLG5iX2ZpbHRlcjplbWJlZGluZ2RpbTprZXJuZWxfc2l6ZTpoaWRkZW5fc2l6ZSkgJT4lIGZpbHRlcihtZXRyaWMgJWluJSAgYygiQmFsYW5jZWQgQWNjdXJhY3kiLCJGMSIsIlNlbnNpdGl2aXR5IiwiU3BlY2lmaWNpdHkiKSkKcmVzdWx0c19jbm4xZF90dW5lICU+JSAgZ3JvdXBfYnkoc2FtcGxlKSAlPiUgc3VtbWFyaXNlKG49bigpKQpyZXN1bHRzX2NubjFkX3R1bmUgJT4lIGdncGxvdCgpKwogIGdlb21fYm94cGxvdChhZXMoeT12YWx1ZSx4PXBhcmFtZXRlcnMsZmlsbD1wYXJhbWV0ZXJzKSxjb2xvcj0nZ3JheScpKwogIGxhYnModGl0bGU9IlBhcmFtZXRlcnMgVHVuaW5nOiBDTk4xRCBDYXRhbmlhICgyMDE4KSIsc3VidGl0bGUgPSJQYXJhbWV0ZXJlczogPG5iX2ZpbHRlcj5fPGVtYmVkaW5nX3NpemU+XzxrZXJuZWxfc2l6ZT5fPGhpZGRlbl9zaXplPiIsY2FwdGlvbj0iYmF0Y2hfc2l6ZT0xMDI0IikrCiAgeGxhYigiUGFyYW1ldGVycyIpKwogIGdnZGFyazo6ZGFya190aGVtZV9ncmF5KCkrCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSkrCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJub25lIikrCiAgZmFjZXRfd3JhcCh+bWV0cmljKQoKYGBgCgpgYGB7cn0KCnJlc3VsdHNfY25uMWRfdHVuZV9vcmRlcmVkIDwtIHJlc3VsdHNfY25uMWRfdHVuZSAlPiUgZmlsdGVyKG1ldHJpYz09IkJhbGFuY2VkIEFjY3VyYWN5IikgJT4lIHNlbGVjdCh2YWx1ZSxwYXJhbWV0ZXJzKSAlPiUgZ3JvdXBfYnkocGFyYW1ldGVycykgJT4lICBzdW1tYXJpc2UobWVhbj1tZWFuKHZhbHVlKSxzZD1zZCh2YWx1ZSkpICU+JSBhcnJhbmdlKGRlc2MobWVhbikpCgpyZXN1bHRzX2NubjFkX3R1bmVfb3JkZXJlZCRwYXJhbWV0ZXJzPC1mYWN0b3IocmVzdWx0c19jbm4xZF90dW5lX29yZGVyZWQkcGFyYW1ldGVycyxsZXZlbHM9dW5pcXVlKHJlc3VsdHNfY25uMWRfdHVuZV9vcmRlcmVkJHBhcmFtZXRlcnMpKQoKY25uMWRfc2RfcGxvdDwtICBnZ3Bsb3QoYWVzKHg9cGFyYW1ldGVycyx5PW1lYW4pLGRhdGE9cmVzdWx0c19jbm4xZF90dW5lX29yZGVyZWQpKwogICBnZW9tX2Vycm9yYmFyKGFlcyh5bWluPW1lYW4tc2QsIHltYXg9bWVhbitzZCksIHdpZHRoPS4yLGNvbG9yPSd5ZWxsb3cnKSsKICAgZ2VvbV9wb2ludChjb2xvcj0ncmVkJykrCiAgeWxhYigiTWVhbiBCYWxhbmNlZCBBY2N1cmFjeSIpKwogICAgZ2dkYXJrOjpkYXJrX3RoZW1lX2J3KCkrCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSkrCiAgIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0ibm9uZSIpKwogIHRoZW1lKGF4aXMudGV4dD1lbGVtZW50X3RleHQoc2l6ZT04KSkrCiAgIHlsaW0oMC44MCwwLjk1KSsKICAgbGFicyh0aXRsZT0iUGFyYW1ldGVycyBUdW5pbmc6IENOTjFEIENhdGFuaWEgKDIwMTgpIixzdWJ0aXRsZSA9IkJhbGFuY2VkIEFjY3VyYWN5OiBNZWFuIGFuZCBTdGFuZGFyZCBEZXZpYXRpb25cblBhcmFtZXRlcmVzOiA8bmJfZmlsdGVyPl88ZW1iZWRpbmdfc2l6ZT5fPGtlcm5lbF9zaXplPl88aGlkZGVuX3NpemU+IixjYXB0aW9uPSJiYXRjaF9zaXplPTEwMjQiKQoKY25uMWRfc2RfcGxvdApgYGAKYGBge3IgZmlnLmhlaWdodD02LCBmaWcud2lkdGg9MTJ9CmdyaWRFeHRyYTo6Z3JpZC5hcnJhbmdlKGxzdG1fc2RfcGxvdCxjbm4xZF9zZF9wbG90LG5jb2w9MikKYGBgCiMjIyBCYXRjaCAyNTYKYGBge3IgZmlnLmhlaWdodD02LCBmaWcud2lkdGg9MTB9CmZpbGVzIDwtIGxpc3QuZmlsZXMocGF0aCA9ICIuLi9yZXN1bHRzLyIscGF0dGVybj0icmVzdWx0c190ZXN0X2NubjFkLWNhY2ljLWF1Z21lbnRlZC1jdHUxOS1tY2N2LWVwb2Nocz0xNS1lbmRnYW1lLW1heGxlbj0xMDAwLVxcZCstbmJfZmlsdGVyPVxcZCsta2VybmVsX3NpemU9XFxkKy1lbWJlZGluZ2RpbT1cXGQrLWhpZGRlbl9zaXplPVxcZCsuY3N2IikKCnJlc3VsdHNfY25uMWRfMjU2X3R1bmUgPC0gbGFwcGx5KGZpbGVzLCBmdW5jdGlvbih4KSAKICAgICAgICAgICAgICAgIHJlYWRfY3N2KHBhc3RlKCIuLi9yZXN1bHRzLyIseCxzZXA9IiIpLCBjb2xfdHlwZXMgPSBjb2xzKCkpICAgCiAgICAgICAgICAgICAgICAlPiUgdGliYmxlOjphZGRfY29sdW1uKG5iX2ZpbHRlcj1hcy5pbnRlZ2VyKHN0cl9yZXBsYWNlKHN0cmluZyA9IHggLHBhdHRlcm4gPSAiLiotbmJfZmlsdGVyPShbMC05XSspLS4qLmNzdiIsIlxcMSIpICkpCiAgICAgICAgICAgICAgICU+JSB0aWJibGU6OmFkZF9jb2x1bW4oZW1iZWRpbmdkaW09YXMuaW50ZWdlcihzdHJfcmVwbGFjZShzdHJpbmcgPSB4ICxwYXR0ZXJuID0gIi4qLWVtYmVkaW5nZGltPShbMC05XSspLS4qLmNzdiIsIlxcMSIpICkpCiAgICAgICAgICAgICAgICU+JSB0aWJibGU6OmFkZF9jb2x1bW4oa2VybmVsX3NpemU9YXMubnVtZXJpYyhzdHJfcmVwbGFjZShzdHJpbmcgPSB4ICxwYXR0ZXJuID0gIi4qLWtlcm5lbF9zaXplPShbMC05XSspLS4qLmNzdiIsIlxcMSIpICkpCiAgICAgICAgICAgICAgJT4lIHRpYmJsZTo6YWRkX2NvbHVtbihoaWRkZW5fc2l6ZT1hcy5udW1lcmljKHN0cl9yZXBsYWNlKHN0cmluZyA9IHggLHBhdHRlcm4gPSAiLiotaGlkZGVuX3NpemU9KFswLTldKykuY3N2IiwiXFwxIikgKSkKICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgJT4lIHRpYmJsZTo6YWRkX2NvbHVtbihzYW1wbGU9YXMuZmFjdG9yKHN0cl9yZXBsYWNlKHN0cmluZyA9IHggLHBhdHRlcm4gPSAiLio9MTAwMC0oXFxkKyktLiouY3N2IiwiXFwxIikgKSkKICAgICAgICAgIAogICAgICAgICAgICAgICAgKQoKcmVzdWx0c19jbm4xZF8yNTZfdHVuZSA8LWRvLmNhbGwocmJpbmQscmVzdWx0c19jbm4xZF8yNTZfdHVuZSkKcmVzdWx0c19jbm4xZF8yNTZfdHVuZSAlPiUgZ3JvdXBfYnkoc2FtcGxlKSAlPiUgc3VtbWFyaXNlKG49bigpKQpgYGAKYGBge3J9CnJlc3VsdHNfY25uMWRfMjU2X3R1bmU8LXJlc3VsdHNfY25uMWRfMjU2X3R1bmUgJT4lIHRpZHlyOjp1bml0ZSgicGFyYW1ldGVycyIsbmJfZmlsdGVyOmVtYmVkaW5nZGltOmtlcm5lbF9zaXplOmhpZGRlbl9zaXplKSAlPiUgZmlsdGVyKG1ldHJpYyAlaW4lICBjKCJCYWxhbmNlZCBBY2N1cmFjeSIsIkYxIiwiU2Vuc2l0aXZpdHkiLCJTcGVjaWZpY2l0eSIpKQpyZXN1bHRzX2NubjFkXzI1Nl90dW5lICU+JSAgZ3JvdXBfYnkoc2FtcGxlKSAlPiUgc3VtbWFyaXNlKG49bigpKQpgYGAKYGBge3J9CnJlc3VsdHNfY25uMWRfMjU2X3R1bmVfb3JkZXJlZCA8LSByZXN1bHRzX2NubjFkXzI1Nl90dW5lICU+JSBmaWx0ZXIobWV0cmljPT0iQmFsYW5jZWQgQWNjdXJhY3kiKSAlPiUgc2VsZWN0KHZhbHVlLHBhcmFtZXRlcnMpICU+JSBncm91cF9ieShwYXJhbWV0ZXJzKSAlPiUgIHN1bW1hcmlzZShtZWFuPW1lYW4odmFsdWUpLHNkPXNkKHZhbHVlKSkgJT4lIGFycmFuZ2UoZGVzYyhtZWFuKSkKCnJlc3VsdHNfY25uMWRfMjU2X3R1bmVfb3JkZXJlZCRwYXJhbWV0ZXJzPC1mYWN0b3IocmVzdWx0c19jbm4xZF8yNTZfdHVuZV9vcmRlcmVkJHBhcmFtZXRlcnMsbGV2ZWxzPXVuaXF1ZShyZXN1bHRzX2NubjFkXzI1Nl90dW5lX29yZGVyZWQkcGFyYW1ldGVycykpCgpjbm4xZF8yNTZfc2RfcGxvdDwtICBnZ3Bsb3QoYWVzKHg9cGFyYW1ldGVycyx5PW1lYW4pLGRhdGE9cmVzdWx0c19jbm4xZF8yNTZfdHVuZV9vcmRlcmVkKSsKICAgZ2VvbV9lcnJvcmJhcihhZXMoeW1pbj1tZWFuLXNkLCB5bWF4PW1lYW4rc2QpLCB3aWR0aD0uMixjb2xvcj0neWVsbG93JykrCiAgIGdlb21fcG9pbnQoY29sb3I9J3JlZCcpKwogIHlsYWIoIk1lYW4gQmFsYW5jZWQgQWNjdXJhY3kiKSsKICAgIGdnZGFyazo6ZGFya190aGVtZV9idygpKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkpKwogICB0aGVtZShsZWdlbmQucG9zaXRpb249Im5vbmUiKSsKICAgdGhlbWUoYXhpcy50ZXh0PWVsZW1lbnRfdGV4dChzaXplPTgpKSsKICAgeWxpbSgwLjgwLDAuOTUpKwogICBsYWJzKHRpdGxlPSJQYXJhbWV0ZXJzIFR1bmluZzogQ05OMUQgQ2F0YW5pYSAoMjAxOCkiLHN1YnRpdGxlID0iQmFsYW5jZWQgQWNjdXJhY3k6IE1lYW4gYW5kIFN0YW5kYXJkIERldmlhdGlvblxuUGFyYW1ldGVyZXM6IDxuYl9maWx0ZXI+XzxlbWJlZGluZ19zaXplPl88a2VybmVsX3NpemU+XzxoaWRkZW5fc2l6ZT4iLGNhcHRpb249ImJhdGNoX3NpemU9MjU2IikKCmNubjFkXzI1Nl9zZF9wbG90CmBgYAoKIyMgQVRURU5USU9OCiMjIyBCYXRjaCAxMDI0CgoKCgpgYGB7ciBmaWcuaGVpZ2h0PTYsIGZpZy53aWR0aD0xMH0KZmlsZXMgPC0gbGlzdC5maWxlcyhwYXRoID0gIi4uL3Jlc3VsdHMvanVhbm1hIixwYXR0ZXJuPSJyZXN1bHRzX3Rlc3RfYXdjLWxzdG1fc2l6ZSU1Qz1cXGQrLWVtYmVkaW5nZGltJTVDPVxcZCstZHJvcG91dCU1Qz1bMC05Ll0rLW1ldHJpY3MtXFxkKy5jc3YiKQoKcmVzdWx0c19hdHRfdHVuZSA8LSBsYXBwbHkoZmlsZXMsIGZ1bmN0aW9uKHgpIAogICAgICAgICAgICAgICAgcmVhZF9jc3YocGFzdGUoIi4uL3Jlc3VsdHMvanVhbm1hLyIseCxzZXA9IiIpLCBjb2xfdHlwZXMgPSBjb2xzKCkpICAgCiAgICAgICAgICAgICAgICAlPiUgdGliYmxlOjphZGRfY29sdW1uKGxzdG1fc2l6ZT1hcy5pbnRlZ2VyKHN0cl9yZXBsYWNlKHN0cmluZyA9IHggLHBhdHRlcm4gPSAiLiotbHN0bV9zaXplJTVDPShbMC05XSspLS4qLmNzdiIsIlxcMSIpICkpCiAgICAgICAgICAgICAgICU+JSB0aWJibGU6OmFkZF9jb2x1bW4oZW1iZWRpbmdkaW09YXMuaW50ZWdlcihzdHJfcmVwbGFjZShzdHJpbmcgPSB4ICxwYXR0ZXJuID0gIi4qLWVtYmVkaW5nZGltJTVDPShbMC05XSspLS4qLmNzdiIsIlxcMSIpICkpCiAgICAgICAgICAgICAgICU+JSB0aWJibGU6OmFkZF9jb2x1bW4oZHJvcG91dD1hcy5udW1lcmljKHN0cl9yZXBsYWNlKHN0cmluZyA9IHggLHBhdHRlcm4gPSAiLiotZHJvcG91dCU1Qz0oWzAtOV0rLlswLTldKS1tZXRyaWNzLVxcZCsuY3N2IiwiXFwxIikgKSkKICAgICAgICAgICAgICAgJT4lIHRpYmJsZTo6YWRkX2NvbHVtbihzYW1wbGU9YXMuZmFjdG9yKHN0cl9yZXBsYWNlKHN0cmluZyA9IHggLHBhdHRlcm4gPSAiLiptZXRyaWNzLShcXGQrKS5jc3YiLCJcXDEiKSApKQogICAgICAgICAgCiAgICAgICAgICAgICAgICApCgpyZXN1bHRzX2F0dF90dW5lIDwtZG8uY2FsbChyYmluZCxyZXN1bHRzX2F0dF90dW5lKQpyZXN1bHRzX2F0dF90dW5lICU+JSBncm91cF9ieShzYW1wbGUpICU+JSBzdW1tYXJpc2Uobj1uKCkpCmBgYAoKYGBge3IgZmlnLmhlaWdodD02LCBmaWcud2lkdGg9MTB9CgpyZXN1bHRzX2F0dF90dW5lPC1yZXN1bHRzX2F0dF90dW5lICU+JSB0aWR5cjo6dW5pdGUoInBhcmFtZXRlcnMiLGxzdG1fc2l6ZTplbWJlZGluZ2RpbTpkcm9wb3V0KSAlPiUgZmlsdGVyKG1ldHJpYyAlaW4lICBjKCJCYWxhbmNlZCBBY2N1cmFjeSIsIkYxIiwiU2Vuc2l0aXZpdHkiLCJTcGVjaWZpY2l0eSIpKQoKcmVzdWx0c19hdHRfdHVuZSAlPiUgZ2dwbG90KCkrCiAgZ2VvbV9ib3hwbG90KGFlcyh5PXZhbHVlLHg9cGFyYW1ldGVycyxmaWxsPXBhcmFtZXRlcnMpLGNvbG9yPSdncmF5JykrCiAgbGFicyh0aXRsZT0iUGFyYW1ldGVycyBUdW5pbmc6IEF0dGVudGlvbiBZYW5nICgyMDE2KSIsc3VidGl0bGUgPSJQYXJhbWV0ZXJzOiA8bHN0bV9zaXplPl88ZW1iZWRpbmdfc2l6ZT5fPGRyb3BvdXQ+IikrCiAgeGxhYigiUGFyYW1ldGVycyIpKwogIGdnZGFyazo6ZGFya190aGVtZV9ncmF5KCkrCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSkrCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJub25lIikrCiAgZmFjZXRfd3JhcCh+bWV0cmljKQoKYGBgCgpgYGB7ciBmaWcuaGVpZ2h0PTYsIGZpZy53aWR0aD0xMH0KcmVzdWx0c19hdHRfdHVuZV9vcmRlcmVkPC1yZXN1bHRzX2F0dF90dW5lICU+JSBmaWx0ZXIobWV0cmljPT0iQmFsYW5jZWQgQWNjdXJhY3kiKSAlPiUgc2VsZWN0KHZhbHVlLHBhcmFtZXRlcnMpICU+JSBncm91cF9ieShwYXJhbWV0ZXJzKSAlPiUgIHN1bW1hcmlzZShtZWFuPW1lYW4odmFsdWUpLHNkPXNkKHZhbHVlKSkgICU+JSBhcnJhbmdlKGRlc2MobWVhbikpCgpyZXN1bHRzX2F0dF90dW5lX29yZGVyZWQkcGFyYW1ldGVyczwtZmFjdG9yKHJlc3VsdHNfYXR0X3R1bmVfb3JkZXJlZCRwYXJhbWV0ZXJzLGxldmVscz11bmlxdWUocmVzdWx0c19hdHRfdHVuZV9vcmRlcmVkJHBhcmFtZXRlcnMpKQoKCmF0dF9zZF9wbG90PC0gIGdncGxvdChhZXMoeD1wYXJhbWV0ZXJzLHk9bWVhbiksZGF0YT1yZXN1bHRzX2F0dF90dW5lX29yZGVyZWQpKwogICBnZW9tX2Vycm9yYmFyKGFlcyh5bWluPW1lYW4tc2QsIHltYXg9bWVhbitzZCksIHdpZHRoPS4yLGNvbG9yPSd5ZWxsb3cnKSsKICAgZ2VvbV9wb2ludChjb2xvcj0ncmVkJykrCiAgeWxhYigiTWVhbiBCYWxhbmNlZCBBY2N1cmFjeSIpKwogICAgZ2dkYXJrOjpkYXJrX3RoZW1lX2J3KCkrCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSkrCiAgIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0ibm9uZSIpKwogICB5bGltKDAuODAsMC45NSkrCiAgIGxhYnModGl0bGU9IlBhcmFtZXRlcnMgVHVuaW5nOiBBdHRlbnRpb24gWWFuZyAoMjAxNikgIixzdWJ0aXRsZSA9IkJhbGFuY2VkIEFjY3VyYWN5OiBNZWFuIGFuZCBTdGFuZGFyZCBEZXZpYXRpb25cblBhcmFtZXRlcmVzOiA8bHN0bV9zaXplPl88ZW1iZWRpbmdfc2l6ZT5fPGRyb3BvdXQ+IixjYXB0aW9uPSJiYXRjaF9zaXplPTEwMjQiKQogIAphdHRfc2RfcGxvdApgYGAKCgojIyMgQmF0Y2ggMjU2CgpgYGB7ciBmaWcuaGVpZ2h0PTYsIGZpZy53aWR0aD0xMH0KZmlsZXMgPC0gbGlzdC5maWxlcyhwYXRoID0gIi4uL3Jlc3VsdHMvanVhbm1hIixwYXR0ZXJuPSJyZXN1bHRzX3Rlc3RfYmF0Y2hfc2l6ZSU1Qz0yNTYtYXdjLWxzdG1fc2l6ZSU1Qz1cXGQrLWVtYmVkaW5nZGltJTVDPVxcZCstZHJvcG91dCU1Qz1bMC05Ll0rLW1ldHJpY3MtXFxkKy5jc3YiKQoKcmVzdWx0c19hdHRfMjU2X3R1bmUgPC0gbGFwcGx5KGZpbGVzLCBmdW5jdGlvbih4KSAKICAgICAgICAgICAgICAgIHJlYWRfY3N2KHBhc3RlKCIuLi9yZXN1bHRzL2p1YW5tYS8iLHgsc2VwPSIiKSwgY29sX3R5cGVzID0gY29scygpKSAgIAogICAgICAgICAgICAgICAgJT4lIHRpYmJsZTo6YWRkX2NvbHVtbihsc3RtX3NpemU9YXMuaW50ZWdlcihzdHJfcmVwbGFjZShzdHJpbmcgPSB4ICxwYXR0ZXJuID0gIi4qLWxzdG1fc2l6ZSU1Qz0oWzAtOV0rKS0uKi5jc3YiLCJcXDEiKSApKQogICAgICAgICAgICAgICAlPiUgdGliYmxlOjphZGRfY29sdW1uKGVtYmVkaW5nZGltPWFzLmludGVnZXIoc3RyX3JlcGxhY2Uoc3RyaW5nID0geCAscGF0dGVybiA9ICIuKi1lbWJlZGluZ2RpbSU1Qz0oWzAtOV0rKS0uKi5jc3YiLCJcXDEiKSApKQogICAgICAgICAgICAgICAlPiUgdGliYmxlOjphZGRfY29sdW1uKGRyb3BvdXQ9YXMubnVtZXJpYyhzdHJfcmVwbGFjZShzdHJpbmcgPSB4ICxwYXR0ZXJuID0gIi4qLWRyb3BvdXQlNUM9KFswLTldKy5bMC05XSktbWV0cmljcy1cXGQrLmNzdiIsIlxcMSIpICkpCiAgICAgICAgICAgICAgICU+JSB0aWJibGU6OmFkZF9jb2x1bW4oc2FtcGxlPWFzLmZhY3RvcihzdHJfcmVwbGFjZShzdHJpbmcgPSB4ICxwYXR0ZXJuID0gIi4qbWV0cmljcy0oXFxkKykuY3N2IiwiXFwxIikgKSkKICAgICAgICAgIAogICAgICAgICAgICAgICAgKQoKcmVzdWx0c19hdHRfMjU2X3R1bmUgPC1kby5jYWxsKHJiaW5kLHJlc3VsdHNfYXR0XzI1Nl90dW5lKQpyZXN1bHRzX2F0dF8yNTZfdHVuZSAlPiUgZ3JvdXBfYnkoc2FtcGxlKSAlPiUgc3VtbWFyaXNlKG49bigpKQpgYGAKCmBgYHtyIGZpZy5oZWlnaHQ9NiwgZmlnLndpZHRoPTEwfQoKcmVzdWx0c19hdHRfMjU2X3R1bmU8LXJlc3VsdHNfYXR0XzI1Nl90dW5lICU+JSB0aWR5cjo6dW5pdGUoInBhcmFtZXRlcnMiLGxzdG1fc2l6ZTplbWJlZGluZ2RpbTpkcm9wb3V0KSAlPiUgZmlsdGVyKG1ldHJpYyAlaW4lICBjKCJCYWxhbmNlZCBBY2N1cmFjeSIsIkYxIiwiU2Vuc2l0aXZpdHkiLCJTcGVjaWZpY2l0eSIpKQoKcmVzdWx0c19hdHRfMjU2X3R1bmUgJT4lIGdncGxvdCgpKwogIGdlb21fYm94cGxvdChhZXMoeT12YWx1ZSx4PXBhcmFtZXRlcnMsZmlsbD1wYXJhbWV0ZXJzKSxjb2xvcj0nZ3JheScpKwogIGxhYnModGl0bGU9IlBhcmFtZXRlcnMgVHVuaW5nOiBBdHRlbnRpb24gWWFuZyAoMjAxNikiLHN1YnRpdGxlID0iUGFyYW1ldGVyczogPGxzdG1fc2l6ZT5fPGVtYmVkaW5nX3NpemU+Xzxkcm9wb3V0PiIpKwogIHhsYWIoIlBhcmFtZXRlcnMiKSsKICBnZ2Rhcms6OmRhcmtfdGhlbWVfZ3JheSgpKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkpKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0ibm9uZSIpKwogIGZhY2V0X3dyYXAofm1ldHJpYykKCmBgYAoKYGBge3IgZmlnLmhlaWdodD02LCBmaWcud2lkdGg9MTB9CnJlc3VsdHNfYXR0XzI1Nl90dW5lX29yZGVyZWQ8LXJlc3VsdHNfYXR0XzI1Nl90dW5lICU+JSBmaWx0ZXIobWV0cmljPT0iQmFsYW5jZWQgQWNjdXJhY3kiKSAlPiUgc2VsZWN0KHZhbHVlLHBhcmFtZXRlcnMpICU+JSBncm91cF9ieShwYXJhbWV0ZXJzKSAlPiUgIHN1bW1hcmlzZShtZWFuPW1lYW4odmFsdWUpLHNkPXNkKHZhbHVlKSkgICU+JSBhcnJhbmdlKGRlc2MobWVhbikpCgpyZXN1bHRzX2F0dF8yNTZfdHVuZV9vcmRlcmVkJHBhcmFtZXRlcnM8LWZhY3RvcihyZXN1bHRzX2F0dF8yNTZfdHVuZV9vcmRlcmVkJHBhcmFtZXRlcnMsbGV2ZWxzPXVuaXF1ZShyZXN1bHRzX2F0dF8yNTZfdHVuZV9vcmRlcmVkJHBhcmFtZXRlcnMpKQoKCmF0dF8yNTZfc2RfcGxvdDwtICBnZ3Bsb3QoYWVzKHg9cGFyYW1ldGVycyx5PW1lYW4pLGRhdGE9cmVzdWx0c19hdHRfMjU2X3R1bmVfb3JkZXJlZCkrCiAgIGdlb21fZXJyb3JiYXIoYWVzKHltaW49bWVhbi1zZCwgeW1heD1tZWFuK3NkKSwgd2lkdGg9LjIsY29sb3I9J3llbGxvdycpKwogICBnZW9tX3BvaW50KGNvbG9yPSdyZWQnKSsKICB5bGFiKCJNZWFuIEJhbGFuY2VkIEFjY3VyYWN5IikrCiAgICBnZ2Rhcms6OmRhcmtfdGhlbWVfYncoKSsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpKSsKICAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJub25lIikrCiAgIHlsaW0oMC44MCwwLjk1KSsKICAgbGFicyh0aXRsZT0iUGFyYW1ldGVycyBUdW5pbmc6IEF0dGVudGlvbiBZYW5nICgyMDE2KSAiLHN1YnRpdGxlID0iQmFsYW5jZWQgQWNjdXJhY3k6IE1lYW4gYW5kIFN0YW5kYXJkIERldmlhdGlvblxuUGFyYW1ldGVyZXM6IDxsc3RtX3NpemU+XzxlbWJlZGluZ19zaXplPl88ZHJvcG91dD4iLGNhcHRpb249ImJhdGNoX3NpemU9MjU2IikKICAKYXR0XzI1Nl9zZF9wbG90CmBgYAoKCiMjIENvbXBhcmlzb24KIyMjIE1DQ1YKYGBge3IgZmlnLmhlaWdodD01LCBmaWcud2lkdGg9MTh9CmdyaWRwbG90PC1ncmlkRXh0cmE6OmdyaWQuYXJyYW5nZShsc3RtX3NkX3Bsb3QrIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZT04KSkKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKyB0aGVtZShwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemU9NikpCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAsbHN0bV8yNTZfc2RfcGxvdCArIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZT04KSkKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKyB0aGVtZShwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemU9NikpCiAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAsYXR0X3NkX3Bsb3QrIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZT04KSkKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKyB0aGVtZShwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemU9NikpCiAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAsYXR0XzI1Nl9zZF9wbG90KyB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemU9OCkpCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICsgdGhlbWUocGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplPTYpKQogICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgLGNubjFkX3NkX3Bsb3QgKyB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemU9OCkpCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICsgdGhlbWUocGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplPTYpKQogICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgLGNubjFkXzI1Nl9zZF9wbG90ICsgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplPTgpKQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKyB0aGVtZShwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemU9NikpCiAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAsbmNvbD02KQpncmlkcGxvdCAlPiUgcGxvdCgpCmBgYAojIyMjIFJlc3VsdHMgVGFibGUKYGBge3J9CnJlc3VsdHNfYXR0XzI1Nl90dW5lJG1vZGVsPC0iYXR0XzI1NiIKcmVzdWx0c19hdHRfdHVuZSRtb2RlbDwtImF0dF8xMDI0IgpyZXN1bHRzX2NubjFkXzI1Nl90dW5lJG1vZGVsPC0iY25uMWRfMjU2IgpyZXN1bHRzX2NubjFkX3R1bmUkbW9kZWw8LSJjbm4xZF8xMDI0IiAgCnJlc3VsdHNfbHN0bV8yNTZfdHVuZSRtb2RlbDwtImxzdG1fMjU2IgpyZXN1bHRzX2xzdG1fdHVuZSRtb2RlbDwtImxzdG0iCgpyYmluZCggcmVzdWx0c19hdHRfMjU2X3R1bmUsCnJlc3VsdHNfYXR0X3R1bmUsCnJlc3VsdHNfY25uMWRfMjU2X3R1bmUsCnJlc3VsdHNfY25uMWRfdHVuZSwKcmVzdWx0c19sc3RtXzI1Nl90dW5lLApyZXN1bHRzX2xzdG1fdHVuZSkgJT4lIHJlYWRyOjp3cml0ZV9jc3YoIn4vc2VxdWVuY2VfY2xhc3NpZmljYXRpb25fbWV0cmljcy5jc3YiKQoKICAKCmBgYAojIyMgRklOQUwgbW9kZWxzIHJlc3VsdHMgZm9yIENUVTE5QSBhbmQgQ1RVMTlCCgoqKlNlbGVjdGVkIE1vZGVsczoqKgoKKkxTVE06KgpiYXRjaCAyNTYgICA2NF82NF8wLjEKYmF0Y2ggMTAyNCAgNjRfMzJfMC4xCgoqQVRURU5USU9OOioKQmF0Y2ggMjU2ICAgIDMyXzMyXzAuMQpCYXRjaCAxMDI0IDEyOF8xMjhfMC4xCgp8KkNOTjoqCkJhdGNoIDI1NiAgMTI4XzEyOF80XzI1NgkJCkJhdGNoIDEwMjQgIDI1Nl8xMjhfNF8xMjgKCiMjIyMgUmVzdWx0cyB0YWJsZSAoa2FiYmxlKQpgYGB7ciBldmFsPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQpmaW5hbF9hdHQxPC0gcmVhZHI6OnJlYWRfY3N2KCIuLi9yZXN1bHRzL2NhY2ljMjAyMS10ZXN0LW9ubHkvcmVzdWx0c19jdHUxOWJfYXdjLWJhdGNoX3NpemU9MTAyNC1sc3RtX3NpemU9MTI4LWVtYmVkaW5nZGltPTEyOC1kcm9wb3V0PTAuMS5jc3YiLCBjb2xfdHlwZXMgPSBjb2xzKCkpCmZpbmFsX2F0dDEkbW9kZWw8LSJhdHQxIgpmaW5hbF9hdHQxJHBhcmFtZXRlcnM8LSI8MTAyNCwxMjgsMTI4LDAuMT4iCgoKZmluYWxfYXR0MjwtIHJlYWRyOjpyZWFkX2NzdigiLi4vcmVzdWx0cy9jYWNpYzIwMjEtdGVzdC1vbmx5L3Jlc3VsdHNfY3R1MTliX2F3Yy1iYXRjaF9zaXplPTI1Ni1sc3RtX3NpemU9MzItZW1iZWRpbmdkaW09MzItZHJvcG91dD0wLjEuY3N2IiwgY29sX3R5cGVzID0gY29scygpKQpmaW5hbF9hdHQyJG1vZGVsPC0iYXR0MiIKZmluYWxfYXR0MiRwYXJhbWV0ZXJzPC0iPDI1NiwzMiwzMiwwLjE+IgoKZmluYWxfbHN0bTE8LSByZWFkcjo6cmVhZF9jc3YoIi4uL3Jlc3VsdHMvY2FjaWMyMDIxLXRlc3Qtb25seS9yZXN1bHRzX3Rlc3RfbHN0bS1lbmRnYW1lLWF1Z21lbnRlZC1jdHUxOS1tY2N2LWVwb2Nocz0xNS1lbmRnYW1lLWJhdGNoPTEwMjQtbWF4bGVuPTEwMDAtZW1iZWRpbmdkaW09NjQtbHN0bV9zaXplPTMyLWRyb3BvdXQ9MC4xLXRlc3Qtb25seS5jc3YiLCBjb2xfdHlwZXMgPSBjb2xzKCkpICU+JSBmaWx0ZXIobWV0cmljICVpbiUgYygiQmFsYW5jZWQgQWNjdXJhY3kiLCAiU2Vuc2l0aXZpdHkiLCJTcGVjaWZpY2l0eSIsIkYxIikpCmZpbmFsX2xzdG0xJG1vZGVsPC0ibHN0bTEiCmZpbmFsX2xzdG0xJHBhcmFtZXRlcnM8LSI8MTAyNCw2NCwzMiwwLjE+IgoKZmluYWxfbHN0bTI8LSByZWFkcjo6cmVhZF9jc3YoIi4uL3Jlc3VsdHMvY2FjaWMyMDIxLXRlc3Qtb25seS9yZXN1bHRzX3Rlc3RfbHN0bS1lbmRnYW1lLWF1Z21lbnRlZC1jdHUxOS1tY2N2LWVwb2Nocz0xNS1lbmRnYW1lLWJhdGNoPTI1Ni1tYXhsZW49MTAwMC1lbWJlZGluZ2RpbT02NC1sc3RtX3NpemU9NjQtZHJvcG91dD0wLjEtdGVzdC1vbmx5LmNzdiIsIGNvbF90eXBlcyA9IGNvbHMoKSkgJT4lIGZpbHRlcihtZXRyaWMgJWluJSBjKCJCYWxhbmNlZCBBY2N1cmFjeSIsICJTZW5zaXRpdml0eSIsIlNwZWNpZmljaXR5IiwiRjEiKSkKCmZpbmFsX2xzdG0yJG1vZGVsPC0ibHN0bTIiCmZpbmFsX2xzdG0yJHBhcmFtZXRlcnM8LSI8MjU2LDY0LDY0LDAuMT4iCgoKCmZpbmFsX2NubjFkMTwtIHJlYWRyOjpyZWFkX2NzdigiLi4vcmVzdWx0cy9jYWNpYzIwMjEtdGVzdC1vbmx5L3Jlc3VsdHNfdGVzdF9jbm4xZC1hdWdtZW50ZWQtY3R1MTktbWNjdi1lcG9jaHM9MTUtZW5kZ2FtZS1iYXRjaD0xMDI0LW1heGxlbj0xMDAwLW5iX2ZpbHRlcj0yNTYtZW1iZWRpbmdkaW09MTI4LWtlcm5lbF9zaXplPTQtaGlkZGVuX3NpemU9MTI4LXRlc3Qtb25seS5jc3YiLCBjb2xfdHlwZXMgPSBjb2xzKCkpICU+JSBmaWx0ZXIobWV0cmljICVpbiUgYygiQmFsYW5jZWQgQWNjdXJhY3kiLCAiU2Vuc2l0aXZpdHkiLCJTcGVjaWZpY2l0eSIsIkYxIikpCmZpbmFsX2NubjFkMSRtb2RlbDwtImNubjFkMSIKZmluYWxfY25uMWQxJHBhcmFtZXRlcnM8LSI8MTAyNCwyNTYsMTI4LDQsMTI4PiIKCgoKZmluYWxfY25uMWQyPC0gcmVhZHI6OnJlYWRfY3N2KCIuLi9yZXN1bHRzL2NhY2ljMjAyMS10ZXN0LW9ubHkvcmVzdWx0c190ZXN0X2NubjFkLWF1Z21lbnRlZC1jdHUxOS1tY2N2LWVwb2Nocz0xNS1lbmRnYW1lLWJhdGNoPTI1Ni1tYXhsZW49MTAwMC1uYl9maWx0ZXI9MTI4LWVtYmVkaW5nZGltPTEyOC1rZXJuZWxfc2l6ZT00LWhpZGRlbl9zaXplPTI1Ni10ZXN0LW9ubHkuY3N2IiwgY29sX3R5cGVzID0gY29scygpKSAlPiUgZmlsdGVyKG1ldHJpYyAlaW4lIGMoIkJhbGFuY2VkIEFjY3VyYWN5IiwgIlNlbnNpdGl2aXR5IiwiU3BlY2lmaWNpdHkiLCJGMSIpKQpmaW5hbF9jbm4xZDIkbW9kZWw8LSJjbm4xZDIiCmZpbmFsX2NubjFkMiRwYXJhbWV0ZXJzPC0iPDI1NiwxMjgsMTI4LDQsMjU2PiIKCgoKbGlicmFyeShrYWJsZUV4dHJhKQp0YmxfcmVzdWx0c19maW5hbF9tb2RlbHM8LXJiaW5kKGZpbmFsX2F0dDEsCiAgICAgIGZpbmFsX2F0dDIsCiAgICAgIGZpbmFsX2xzdG0xLAogICAgICBmaW5hbF9sc3RtMiwKICAgICAgZmluYWxfY25uMWQxLAogICAgICBmaW5hbF9jbm4xZDIpCgp0YmxfcmVzdWx0c19maW5hbF9tb2RlbHMgJT4lIHRpZHlyOjpwaXZvdF93aWRlcihuYW1lc19mcm9tID0gbWV0cmljLCB2YWx1ZXNfZnJvbSA9IHZhbHVlKSAgJT4lIAogIGtibChjYXB0aW9uID0gIkZpbmFsIG1vZGVscyB0cmFpbmVkIHdpdGggQ1RVMTlBIGFuZCB0ZXN0ZWQgaW4gQ1RVMTlCIiwgYWxpZ24gPSAnYycsIGZvcm1hdCA9ICJodG1sIikgJT4lIAogIAogIAogICBjb2x1bW5fc3BlYyg1LCBib2xkID0gYygwLDEsMCwwLDEsMSksIHdpZHRoX21heCA9IDEpICU+JQogICAgY29sdW1uX3NwZWMoNiwgYm9sZCA9IGMoMSwwLDAsMCwxLDEpKSAlPiUKICBjb2x1bW5fc3BlYygzLCBib2xkID0gYygwLDEsMSwxLDAsMCkpICU+JQogIGNvbHVtbl9zcGVjKDQsIGJvbGQgPSBjKDEsMCwwLDAsMSwxKSkgJT4lCiAgCiAga2FibGVFeHRyYTo6a2FibGVfY2xhc3NpY18yKGZ1bGxfd2lkdGggPSBGLCBodG1sX2ZvbnQgPSAiQ2FtYnJpYSIpCgpgYGAKCiFbXSguLi9maW5hbC1yZXN1bHRzLi5wbmcpCmBgYHtyfQphdHQxX3NlbGVjdGVkPC1yZXN1bHRzX2F0dF90dW5lICU+JSBmaWx0ZXIocGFyYW1ldGVycz09IjEyOF8xMjhfMC4xIikKYXR0MV9zZWxlY3RlZCRtb2RlbDwtImF0dDEiCmF0dDJfc2VsZWN0ZWQ8LXJlc3VsdHNfYXR0XzI1Nl90dW5lICU+JSBmaWx0ZXIocGFyYW1ldGVycz09IjMyXzMyXzAuMSIpCmF0dDJfc2VsZWN0ZWQkbW9kZWw8LSJhdHQyIgoKbHN0bTFfc2VsZWN0ZWQ8LXJlc3VsdHNfbHN0bV90dW5lICU+JSBmaWx0ZXIocGFyYW1ldGVycz09IjY0XzMyXzAuMSIpCmxzdG0xX3NlbGVjdGVkJG1vZGVsPC0ibHN0bTEiCgpsc3RtMl9zZWxlY3RlZDwtcmVzdWx0c19sc3RtXzI1Nl90dW5lICU+JSBmaWx0ZXIocGFyYW1ldGVycz09IjY0XzY0XzAuMSIpCmxzdG0yX3NlbGVjdGVkJG1vZGVsPC0ibHN0bTIiCgoKY25uMWQxX3NlbGVjdGVkPC1yZXN1bHRzX2NubjFkX3R1bmUgJT4lIGZpbHRlcihwYXJhbWV0ZXJzPT0iMjU2XzEyOF80XzEyOCIpCmNubjFkMV9zZWxlY3RlZCRtb2RlbDwtImNubjFkMSIKCmNubjFkMl9zZWxlY3RlZDwtcmVzdWx0c19jbm4xZF8yNTZfdHVuZSAlPiUgZmlsdGVyKHBhcmFtZXRlcnM9PSIxMjhfMTI4XzRfMjU2IikKY25uMWQyX3NlbGVjdGVkJG1vZGVsPC0iY25uMWQyIgoKcmVzdWx0c19maW5hbF9tb2RlbHM8LXJiaW5kKCBhdHQxX3NlbGVjdGVkLAogICAgICAgYXR0Ml9zZWxlY3RlZCwKICAgICAgIGxzdG0xX3NlbGVjdGVkLAogICAgICAgbHN0bTJfc2VsZWN0ZWQsCiAgICAgICBjbm4xZDFfc2VsZWN0ZWQsCiAgICAgICBjbm4xZDJfc2VsZWN0ZWQKICAgICAgICkgJT4lIGdyb3VwX2J5KG1vZGVsLG1ldHJpYykgJT4lIHN1bW1hcmlzZShuPW4oKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtZWFuPW1lYW4odmFsdWUpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNkPXNkKHZhbHVlKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2U9c2Qvc3FydChuKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2k9cXQocD0wLjAyNSwgZGY9bi0xLGxvd2VyLnRhaWw9Rikqc2UpCgpyZXN1bHRzX2ZpbmFsX21vZGVscwoKbjwtYmFsYW5jZWRfYWNjdXJhY3lfYXVnbWVudDIweDJfZGF0YXNldCAlPiUgbGVuZ3RoKCkKbXU8LW1lYW4oYmFsYW5jZWRfYWNjdXJhY3lfYXVnbWVudDIweDJfZGF0YXNldCAlPiUgdW5saXN0KCkpCnM8LXNkKGJhbGFuY2VkX2FjY3VyYWN5X2F1Z21lbnQyMHgyX2RhdGFzZXQgJT4lIHVubGlzdCgpKQplcnJvcjwtIHFub3JtKDAuOTc1KSpzL3NxcnQobikKYyhtdStlcnJvcixtdS1lcnJvcikKYGBgCiMjIyBTZWxlY3RlZCBtb2RlbHMgQ0kgKGNvbmZpZGVuY2UgaW50ZXJ2YWxzKSB2cyBmaW5hbCByZXN1bHRzIG9uIENUVTE5QgpgYGB7ciBmaWcuaGVpZ2h0PTEwLCBmaWcud2lkdGg9MTB9CiNyZXN1bHRzX2F0dF8yNTZfdHVuZV9vcmRlcmVkPC1yZXN1bHRzX2F0dF8yNTZfdHVuZSAlPiUgZmlsdGVyKG1ldHJpYz09IkJhbGFuY2VkIEFjY3VyYWN5IikgJT4lIHNlbGVjdCh2YWx1ZSxwYXJhbWV0ZXJzKSAlPiUgZ3JvdXBfYnkocGFyYW1ldGVycykgJT4lICBzdW1tYXJpc2UobWVhbj1tZWFuKHZhbHVlKSxzZD1zZCh2YWx1ZSkpICAlPiUgYXJyYW5nZShkZXNjKG1lYW4pKQoKI3Jlc3VsdHNfYXR0XzI1Nl90dW5lX29yZGVyZWQkcGFyYW1ldGVyczwtZmFjdG9yKHJlc3VsdHNfYXR0XzI1Nl90dW5lX29yZGVyZWQkcGFyYW1ldGVycyxsZXZlbHM9dW5pcXVlKHJlc3VsdHNfYXR0XzI1Nl90dW5lX29yZGVyZWQkcGFyYW1ldGVycykpCgoKZmluYWxfbW9kZWxzX3Bsb3Q8LSAgZ2dwbG90KGFlcyh4PW1vZGVsLHk9bWVhbiksZGF0YT1yZXN1bHRzX2ZpbmFsX21vZGVscykrCiAgZmFjZXRfd3JhcCh+bWV0cmljKSsKICAgZ2VvbV9lcnJvcmJhcihhZXMoeW1pbj1tZWFuLWNpLCB5bWF4PW1lYW4rY2kpLCB3aWR0aD0uMixjb2xvcj0neWVsbG93JykrCiAgCiAgIGdlb21fcG9pbnQoY29sb3I9J3JlZCcpKwogICBnZW9tX3BvaW50KGFlcyh5PXZhbHVlKSwgZGF0YT10YmxfcmVzdWx0c19maW5hbF9tb2RlbHMsIGNvbG9yPSdncmVlbicpKwogIAogIHlsYWIoIk1vZGVsIikrCiAgICBnZ2Rhcms6OmRhcmtfdGhlbWVfYncoKSsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpKSsKICAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJub25lIikrCiAgICN5bGltKDAuODAsMC45NSkrCiAgIGxhYnModGl0bGU9IlNFTEVDVEVEIE1PREVMUzogVmFyaWF0aW9uIHZzLiBGaW5hbCBSZXN1bHRzIG9uIENUVTE5QiIsc3VidGl0bGUgPSJNZXRyaWNzOiBNZWFuIChpbiByZWQpIGFuZCBDSSAoaW4geWVsbG93KS4gR3JlZW46IHJlc3VsdCBvbiBDVFUxOUIiLGNhcHRpb249IiIpCmZpbmFsX21vZGVsc19wbG90CmBgYAoK