Sat May 8 23:34:35 2021

library(readr)
library(dplyr)
library(stringr)
library(purrr)
library(ggplot2)
library(skimr)

All the analysis is done for sequence length = 1000

seq_maxlen=1000

Helper Functions

Efron’s similarity function

library(randomForest)
library(tidyr)
library(dplyr)
efron_simil<-function(train,prec_len){
  train<-train %>% select(1:prec_len)
  predictor_order<-sample(1:prec_len,prec_len)
  train_permuted<-train[,predictor_order]
  names(train_permuted)<-names(train)
  train_permuted$dataset<- "random"
  train$dataset<- "original"
  train<-rbind(train_permuted,train)
  train_model<-randomForest::randomForest(x=train[,1:prec_len],
  y=as.factor(train$dataset))
}

PCA 2D Visualization function

source("../../deepseq/config.R") # required for using deepseq #remove opt$dataset from config to use
source("../../deepseq/preprocess.R") # required for padding function
# ctu19_filtered_first4removed.csv was generated in other notebook ctu19-analysis.rmd 
ctu19_first4removed<-read_csv("../datasets/ctu19_filtered_first4removed.csv")

tokenize <- function(data){
  sequencel<-sapply(data,function(x)  strsplit(x,split=""))
  x_data <- lapply(sequencel,function(x) sapply(x,function(x) tokens[[x]]))
  }

create_padded_seq<-function(data,maxlen=seq_maxlen){
tokenized_seq <- tokenize(data$State)
padded_seq<-pad_sequences_fast(unname(tokenized_seq),maxlen=maxlen,padding='post', truncating='post')
padded_seq<-data.frame(seq=padded_seq,label=data$LabelName,source=data$source, id=data$id)
}


create_pca<-function(data,maxlen=seq_maxlen)
{  
pca<-prcomp(data[,1:maxlen],center=TRUE,scale.=TRUE)
  summary(pca)
  pca_data<-data.frame(pca$x,label=data$label,source=data$source, id=data$id)
  pca_plot<-ggplot(pca_data,aes(x=PC1,y=PC2))+
    geom_point(aes(color=label),alpha=0.5,size=0.1)+
    ylim(c(-40,40))+
    xlim(c(-80,80))+
    ggdark::dark_theme_bw()
pca_plot
}

PCA 2D of CTU19

padded_seq<-create_padded_seq(ctu19_first4removed,maxlen=seq_maxlen)
pca_plot<-create_pca(padded_seq,maxlen=seq_maxlen)
pca_plot 

#create_pca(padded_seq %>% filter(source=="2017-05-02_kali-normal.pcap.tsv"))
#create_pca(padded_seq %>% filter(source=="2014-01-31_capture-win7.pcap.tsv"))
#create_pca(padded_seq %>% filter(source=="2014-03-12_capture-win3.pcap.tsv"))
#create_pca(padded_seq %>% filter(source=="2013-11-25_capture-win7-2.pcap.tsv"))
#create_pca(padded_seq %>% filter(source=="2015-03-24_capture1-only-dns.pcap.tsv"))

PCA 2D of CTU19 and the 19 data sources.

pca_plot + facet_wrap(~source) + labs(title="PCA vizualization of the CTU19", subtitle="Different sources", caption="Sequences were tokenized and padded using deepseq framework with a maxlen=1000")

gridExtra::grid.arrange(pca_plot  + labs(title="PCA vizualization of the CTU19", subtitle="Full Dataset and different sources"),
                        pca_plot + theme(legend.position = "none") + facet_wrap(~source) +labs(caption="Sequences were tokenized and padded using deepseq framework with a maxlen=1000"),ncol=2)

Analysis of CTU19 for building CTU19A(train) and CTU19B(test)

A subset of CTU19 data sources is removed for building a test set. The resulting CTU19A and CTU19B. Datasets with less than 3/4 sequences are also removed.

CTU19B_datasets<-c( #"2017-05-02_kali-normal.pcap.tsv",
                    "2014-01-31_capture-win7.pcap.tsv",
                    "2014-01-25_capture_win3.pcap.tsv",
                    "2013-08-20_capture-win2.pcap.tsv",
                   "2017-04-25_win-normal.pcap.tsv",
                   "2014-02-10_capture-win3.pcap.tsv",
                   "2013-11-25_capture-win7-2.pcap.tsv",
                   "2013-12-17_capture1.pcap.tsv"
                )
# Removed because number of sequences  <3
CTU19_removed<-c('2014-02-07_capture-win3.pcap.tsv',
'2015-03-24_capture1-only-dns.pcap.tsv',    
'2017-07-03_capture-win2.pcap.tsv')
CTU19A_seq<-padded_seq %>% filter( ! source %in%  CTU19B_datasets )
CTU19A_seq<-CTU19A_seq %>% filter( ! source %in%  CTU19_removed )
CTU19B_seq<-padded_seq %>% filter(  source %in%  CTU19B_datasets )
CTU19A_seq$set<-"CTU19A"
CTU19B_seq$set<-"CTU19B"

Checking whether CTU19A and CTU19B include the correct number of datasets

CTU19A_seq %>% group_by(source) %>% summarize(n=n())
CTU19B_seq %>% group_by(source) %>% summarize(n=n())

The new CTU19A and CTU19B are visualizad using PCA.

CTU19A_nrow<-CTU19A_seq %>% nrow()
gridExtra::grid.arrange(create_pca(CTU19A_seq)+
                          labs(title="CTU19A",subtitle=paste(
                                                             " Botnet:", 
                                                             CTU19A_seq %>% filter(label =="Botnet") %>% nrow,
                                                             " Normal:", 
                                                             CTU19A_seq %>% filter(label !="Botnet") %>% nrow )
                               ),
                          create_pca(CTU19B_seq)+
                          labs(title="CTU19B",subtitle=paste(
                                                             " Botnet:", 
                                                             CTU19B_seq %>% filter(label =="Botnet") %>% nrow,
                                                             " Normal:", 
                                                             CTU19B_seq %>% filter(label !="Botnet") %>% nrow )
                               
                               
                            )
                          )

Create Train/test (CTU19) csv files

CTU19A_seq %>% group_by(label) %>% summarize(n=n())
CTU19A_csv <- inner_join(ctu19_first4removed,CTU19A_seq %>% select(id), by="id")
CTU19B_csv <- inner_join(ctu19_first4removed,CTU19B_seq %>% select(id), by="id")
readr::write_csv(CTU19A_csv,path="../datasets/samples/CTU19A-first4-removed.csv")
readr::write_csv(CTU19B_csv,path="../datasets/samples/CTU19B-first4-removed.csv")
CTU19A_csv %>% group_by(source) %>% summarize(n=n())
CTU19B_csv %>% group_by(source) %>% summarize(n=n())

Select Sample Size

We apply Efron’s algorithm for selecting different sample proportions and then classify the remaining CTU19 as original or random. Ideally the 100% of the remaining CTU19 should be considered as original.

PCA 2D visualization for comparing CTU19 and a 20% sample.

 CTU19A_split<-rsample::initial_split(CTU19A_seq,prop =0.2, strata=NULL)
    CTU19A_seq_sample<-training(CTU19A_split)
    CTU19A_seq_sample_test<-testing(CTU19A_split)
    gridExtra::grid.arrange(create_pca(CTU19A_seq_sample)+labs(title="20% Sample")
                            ,create_pca(CTU19A_seq)+labs(title="CTU19A Population"))

    
CTU19A_seq_sample %>% group_by(source) %>% summarise(n=n())
CTU19A_seq %>% group_by(source) %>% summarise(n=n()) %>% arrange(n)

Create 30 samples using pseudo Monte Carlo CV

Shuffle the dataset and pick ~4000 for training and ~1000 for test. Repeat 30 times.

Checking if all train samples include the 9 sources from CTU19A

samples_files %>% group_by(source,sample) %>% summarise(n=n()) %>% group_by(sample) %>% summarise(sources=n())
`summarise()` has grouped output by 'source'. You can override using the `.groups` argument.

Checking if all test samples include the 9 sources from CTU19A

samples_files %>% group_by(source,sample) %>% summarise(n=n()) %>% group_by(sample) %>% summarise(sources=n())
`summarise()` has grouped output by 'source'. You can override using the `.groups` argument.

Resulting datasets and samples

The final version of CTU19A and CTU19B as well as the 10% and 20% samples are located here

LS0tCnRpdGxlOiAiQ1RVMTkgc3BsaXQgYW5kIHNhbXBsaW5nICIKb3V0cHV0OiAKICBodG1sX25vdGVib29rOiAKICAgIGNvZGVfZm9sZGluZzogc2hvdwogICAgdGhlbWU6IGNlcnVsZWFuCi0tLQpgciBkYXRlKClgCgpgYGB7cn0KbGlicmFyeShyZWFkcikKbGlicmFyeShkcGx5cikKbGlicmFyeShzdHJpbmdyKQpsaWJyYXJ5KHB1cnJyKQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkoc2tpbXIpCgpgYGAKCiMjIEFsbCB0aGUgYW5hbHlzaXMgaXMgZG9uZSBmb3Igc2VxdWVuY2UgbGVuZ3RoID0gMTAwMApgYGB7cn0Kc2VxX21heGxlbj0xMDAwCmBgYAoKIyMgSGVscGVyIEZ1bmN0aW9ucwojIyMgRWZyb24ncyBzaW1pbGFyaXR5IGZ1bmN0aW9uCmBgYHtyfQpsaWJyYXJ5KHJhbmRvbUZvcmVzdCkKbGlicmFyeSh0aWR5cikKbGlicmFyeShkcGx5cikKCmVmcm9uX3NpbWlsPC1mdW5jdGlvbih0cmFpbixwcmVjX2xlbil7CiAgdHJhaW48LXRyYWluICU+JSBzZWxlY3QoMTpwcmVjX2xlbikKICBwcmVkaWN0b3Jfb3JkZXI8LXNhbXBsZSgxOnByZWNfbGVuLHByZWNfbGVuKQogIHRyYWluX3Blcm11dGVkPC10cmFpblsscHJlZGljdG9yX29yZGVyXQogIG5hbWVzKHRyYWluX3Blcm11dGVkKTwtbmFtZXModHJhaW4pCiAgdHJhaW5fcGVybXV0ZWQkZGF0YXNldDwtICJyYW5kb20iCiAgdHJhaW4kZGF0YXNldDwtICJvcmlnaW5hbCIKICB0cmFpbjwtcmJpbmQodHJhaW5fcGVybXV0ZWQsdHJhaW4pCiAgdHJhaW5fbW9kZWw8LXJhbmRvbUZvcmVzdDo6cmFuZG9tRm9yZXN0KHg9dHJhaW5bLDE6cHJlY19sZW5dLAogIHk9YXMuZmFjdG9yKHRyYWluJGRhdGFzZXQpKQp9CgpgYGAKIyMjIFBDQSAyRCBWaXN1YWxpemF0aW9uIGZ1bmN0aW9uCmBgYHtyfQpzb3VyY2UoIi4uLy4uL2RlZXBzZXEvY29uZmlnLlIiKSAjIHJlcXVpcmVkIGZvciB1c2luZyBkZWVwc2VxICNyZW1vdmUgb3B0JGRhdGFzZXQgZnJvbSBjb25maWcgdG8gdXNlCnNvdXJjZSgiLi4vLi4vZGVlcHNlcS9wcmVwcm9jZXNzLlIiKSAjIHJlcXVpcmVkIGZvciBwYWRkaW5nIGZ1bmN0aW9uCiMgY3R1MTlfZmlsdGVyZWRfZmlyc3Q0cmVtb3ZlZC5jc3Ygd2FzIGdlbmVyYXRlZCBpbiBvdGhlciBub3RlYm9vayBjdHUxOS1hbmFseXNpcy5ybWQgCmN0dTE5X2ZpcnN0NHJlbW92ZWQ8LXJlYWRfY3N2KCIuLi9kYXRhc2V0cy9jdHUxOV9maWx0ZXJlZF9maXJzdDRyZW1vdmVkLmNzdiIpCgp0b2tlbml6ZSA8LSBmdW5jdGlvbihkYXRhKXsKICBzZXF1ZW5jZWw8LXNhcHBseShkYXRhLGZ1bmN0aW9uKHgpICBzdHJzcGxpdCh4LHNwbGl0PSIiKSkKICB4X2RhdGEgPC0gbGFwcGx5KHNlcXVlbmNlbCxmdW5jdGlvbih4KSBzYXBwbHkoeCxmdW5jdGlvbih4KSB0b2tlbnNbW3hdXSkpCiAgfQoKY3JlYXRlX3BhZGRlZF9zZXE8LWZ1bmN0aW9uKGRhdGEsbWF4bGVuPXNlcV9tYXhsZW4pewp0b2tlbml6ZWRfc2VxIDwtIHRva2VuaXplKGRhdGEkU3RhdGUpCnBhZGRlZF9zZXE8LXBhZF9zZXF1ZW5jZXNfZmFzdCh1bm5hbWUodG9rZW5pemVkX3NlcSksbWF4bGVuPW1heGxlbixwYWRkaW5nPSdwb3N0JywgdHJ1bmNhdGluZz0ncG9zdCcpCnBhZGRlZF9zZXE8LWRhdGEuZnJhbWUoc2VxPXBhZGRlZF9zZXEsbGFiZWw9ZGF0YSRMYWJlbE5hbWUsc291cmNlPWRhdGEkc291cmNlLCBpZD1kYXRhJGlkKQp9CgoKY3JlYXRlX3BjYTwtZnVuY3Rpb24oZGF0YSxtYXhsZW49c2VxX21heGxlbikKeyAgCnBjYTwtcHJjb21wKGRhdGFbLDE6bWF4bGVuXSxjZW50ZXI9VFJVRSxzY2FsZS49VFJVRSkKICBzdW1tYXJ5KHBjYSkKICBwY2FfZGF0YTwtZGF0YS5mcmFtZShwY2EkeCxsYWJlbD1kYXRhJGxhYmVsLHNvdXJjZT1kYXRhJHNvdXJjZSwgaWQ9ZGF0YSRpZCkKICBwY2FfcGxvdDwtZ2dwbG90KHBjYV9kYXRhLGFlcyh4PVBDMSx5PVBDMikpKwogICAgZ2VvbV9wb2ludChhZXMoY29sb3I9bGFiZWwpLGFscGhhPTAuNSxzaXplPTAuMSkrCiAgICB5bGltKGMoLTQwLDQwKSkrCiAgICB4bGltKGMoLTgwLDgwKSkrCiAgICBnZ2Rhcms6OmRhcmtfdGhlbWVfYncoKQpwY2FfcGxvdAp9CmBgYAoKIyMgUENBIDJEIG9mIENUVTE5CmBgYHtyfQpwYWRkZWRfc2VxPC1jcmVhdGVfcGFkZGVkX3NlcShjdHUxOV9maXJzdDRyZW1vdmVkLG1heGxlbj1zZXFfbWF4bGVuKQpwY2FfcGxvdDwtY3JlYXRlX3BjYShwYWRkZWRfc2VxLG1heGxlbj1zZXFfbWF4bGVuKQpwY2FfcGxvdCAKI2NyZWF0ZV9wY2EocGFkZGVkX3NlcSAlPiUgZmlsdGVyKHNvdXJjZT09IjIwMTctMDUtMDJfa2FsaS1ub3JtYWwucGNhcC50c3YiKSkKI2NyZWF0ZV9wY2EocGFkZGVkX3NlcSAlPiUgZmlsdGVyKHNvdXJjZT09IjIwMTQtMDEtMzFfY2FwdHVyZS13aW43LnBjYXAudHN2IikpCiNjcmVhdGVfcGNhKHBhZGRlZF9zZXEgJT4lIGZpbHRlcihzb3VyY2U9PSIyMDE0LTAzLTEyX2NhcHR1cmUtd2luMy5wY2FwLnRzdiIpKQojY3JlYXRlX3BjYShwYWRkZWRfc2VxICU+JSBmaWx0ZXIoc291cmNlPT0iMjAxMy0xMS0yNV9jYXB0dXJlLXdpbjctMi5wY2FwLnRzdiIpKQojY3JlYXRlX3BjYShwYWRkZWRfc2VxICU+JSBmaWx0ZXIoc291cmNlPT0iMjAxNS0wMy0yNF9jYXB0dXJlMS1vbmx5LWRucy5wY2FwLnRzdiIpKQpgYGAKCgojIyBQQ0EgMkQgb2YgQ1RVMTkgYW5kIHRoZSAxOSBkYXRhIHNvdXJjZXMuCgoKYGBge3IgZmlnLndpZHRoPTE0fQpwY2FfcGxvdCArIGZhY2V0X3dyYXAofnNvdXJjZSkgKyBsYWJzKHRpdGxlPSJQQ0Egdml6dWFsaXphdGlvbiBvZiB0aGUgQ1RVMTkiLCBzdWJ0aXRsZT0iRGlmZmVyZW50IHNvdXJjZXMiLCBjYXB0aW9uPSJTZXF1ZW5jZXMgd2VyZSB0b2tlbml6ZWQgYW5kIHBhZGRlZCB1c2luZyBkZWVwc2VxIGZyYW1ld29yayB3aXRoIGEgbWF4bGVuPTEwMDAiKQoKZ3JpZEV4dHJhOjpncmlkLmFycmFuZ2UocGNhX3Bsb3QgICsgbGFicyh0aXRsZT0iUENBIHZpenVhbGl6YXRpb24gb2YgdGhlIENUVTE5Iiwgc3VidGl0bGU9IkZ1bGwgRGF0YXNldCBhbmQgZGlmZmVyZW50IHNvdXJjZXMiKSwKICAgICAgICAgICAgICAgICAgICAgICAgcGNhX3Bsb3QgKyB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpICsgZmFjZXRfd3JhcCh+c291cmNlKSArbGFicyhjYXB0aW9uPSJTZXF1ZW5jZXMgd2VyZSB0b2tlbml6ZWQgYW5kIHBhZGRlZCB1c2luZyBkZWVwc2VxIGZyYW1ld29yayB3aXRoIGEgbWF4bGVuPTEwMDAiKSxuY29sPTIpCmBgYAoKIyMgQW5hbHlzaXMgb2YgQ1RVMTkgZm9yIGJ1aWxkaW5nIENUVTE5QSh0cmFpbikgYW5kIENUVTE5Qih0ZXN0KQoKQSBzdWJzZXQgb2YgQ1RVMTkgZGF0YSBzb3VyY2VzIGlzIHJlbW92ZWQgZm9yIGJ1aWxkaW5nIGEgdGVzdCBzZXQuClRoZSByZXN1bHRpbmcgQ1RVMTlBIGFuZCBDVFUxOUIuIERhdGFzZXRzIHdpdGggbGVzcyB0aGFuIDMvNCBzZXF1ZW5jZXMgYXJlIGFsc28gcmVtb3ZlZC4KYGBge3J9CgpDVFUxOUJfZGF0YXNldHM8LWMoICMiMjAxNy0wNS0wMl9rYWxpLW5vcm1hbC5wY2FwLnRzdiIsCiAgICAgICAgICAgICAgICAgICAgIjIwMTQtMDEtMzFfY2FwdHVyZS13aW43LnBjYXAudHN2IiwKICAgICAgICAgICAgICAgICAgICAiMjAxNC0wMS0yNV9jYXB0dXJlX3dpbjMucGNhcC50c3YiLAogICAgICAgICAgICAgICAgICAgICIyMDEzLTA4LTIwX2NhcHR1cmUtd2luMi5wY2FwLnRzdiIsCiAgICAgICAgICAgICAgICAgICAiMjAxNy0wNC0yNV93aW4tbm9ybWFsLnBjYXAudHN2IiwKICAgICAgICAgICAgICAgICAgICIyMDE0LTAyLTEwX2NhcHR1cmUtd2luMy5wY2FwLnRzdiIsCiAgICAgICAgICAgICAgICAgICAiMjAxMy0xMS0yNV9jYXB0dXJlLXdpbjctMi5wY2FwLnRzdiIsCiAgICAgICAgICAgICAgICAgICAiMjAxMy0xMi0xN19jYXB0dXJlMS5wY2FwLnRzdiIKICAgICAgICAgICAgICAgICkKIyBSZW1vdmVkIGJlY2F1c2UgbnVtYmVyIG9mIHNlcXVlbmNlcyAgPDMKQ1RVMTlfcmVtb3ZlZDwtYygnMjAxNC0wMi0wN19jYXB0dXJlLXdpbjMucGNhcC50c3YnLAonMjAxNS0wMy0yNF9jYXB0dXJlMS1vbmx5LWRucy5wY2FwLnRzdicsCQonMjAxNy0wNy0wM19jYXB0dXJlLXdpbjIucGNhcC50c3YnKQoKQ1RVMTlBX3NlcTwtcGFkZGVkX3NlcSAlPiUgZmlsdGVyKCAhIHNvdXJjZSAlaW4lICBDVFUxOUJfZGF0YXNldHMgKQpDVFUxOUFfc2VxPC1DVFUxOUFfc2VxICU+JSBmaWx0ZXIoICEgc291cmNlICVpbiUgIENUVTE5X3JlbW92ZWQgKQpDVFUxOUJfc2VxPC1wYWRkZWRfc2VxICU+JSBmaWx0ZXIoICBzb3VyY2UgJWluJSAgQ1RVMTlCX2RhdGFzZXRzICkKQ1RVMTlBX3NlcSRzZXQ8LSJDVFUxOUEiCkNUVTE5Ql9zZXEkc2V0PC0iQ1RVMTlCIgpgYGAKCiMjIyBDaGVja2luZyB3aGV0aGVyIENUVTE5QSBhbmQgQ1RVMTlCIGluY2x1ZGUgdGhlIGNvcnJlY3QgbnVtYmVyIG9mIGRhdGFzZXRzCmBgYHtyfQpDVFUxOUFfc2VxICU+JSBncm91cF9ieShzb3VyY2UpICU+JSBzdW1tYXJpemUobj1uKCkpCkNUVTE5Ql9zZXEgJT4lIGdyb3VwX2J5KHNvdXJjZSkgJT4lIHN1bW1hcml6ZShuPW4oKSkKCmBgYAoKIyMjIFRoZSBuZXcgQ1RVMTlBIGFuZCBDVFUxOUIgYXJlIHZpc3VhbGl6YWQgdXNpbmcgUENBLgoKYGBge3J9CkNUVTE5QV9ucm93PC1DVFUxOUFfc2VxICU+JSBucm93KCkKZ3JpZEV4dHJhOjpncmlkLmFycmFuZ2UoY3JlYXRlX3BjYShDVFUxOUFfc2VxKSsKICAgICAgICAgICAgICAgICAgICAgICAgICBsYWJzKHRpdGxlPSJDVFUxOUEiLHN1YnRpdGxlPXBhc3RlKAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIiBCb3RuZXQ6IiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBDVFUxOUFfc2VxICU+JSBmaWx0ZXIobGFiZWwgPT0iQm90bmV0IikgJT4lIG5yb3csCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiIE5vcm1hbDoiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIENUVTE5QV9zZXEgJT4lIGZpbHRlcihsYWJlbCAhPSJCb3RuZXQiKSAlPiUgbnJvdyApCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICApLAogICAgICAgICAgICAgICAgICAgICAgICAgIGNyZWF0ZV9wY2EoQ1RVMTlCX3NlcSkrCiAgICAgICAgICAgICAgICAgICAgICAgICAgbGFicyh0aXRsZT0iQ1RVMTlCIixzdWJ0aXRsZT1wYXN0ZSgKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICIgQm90bmV0OiIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgQ1RVMTlCX3NlcSAlPiUgZmlsdGVyKGxhYmVsID09IkJvdG5ldCIpICU+JSBucm93LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIiBOb3JtYWw6IiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBDVFUxOUJfc2VxICU+JSBmaWx0ZXIobGFiZWwgIT0iQm90bmV0IikgJT4lIG5yb3cgKQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICAgICAgICAgICAgICApCgpgYGAKCiMjIyBDcmVhdGUgVHJhaW4vdGVzdCAoQ1RVMTkpIGNzdiBmaWxlcyAKYGBge3J9CkNUVTE5QV9zZXEgJT4lIGdyb3VwX2J5KGxhYmVsKSAlPiUgc3VtbWFyaXplKG49bigpKQpDVFUxOUFfY3N2IDwtIGlubmVyX2pvaW4oY3R1MTlfZmlyc3Q0cmVtb3ZlZCxDVFUxOUFfc2VxICU+JSBzZWxlY3QoaWQpLCBieT0iaWQiKQpDVFUxOUJfY3N2IDwtIGlubmVyX2pvaW4oY3R1MTlfZmlyc3Q0cmVtb3ZlZCxDVFUxOUJfc2VxICU+JSBzZWxlY3QoaWQpLCBieT0iaWQiKQpyZWFkcjo6d3JpdGVfY3N2KENUVTE5QV9jc3YscGF0aD0iLi4vZGF0YXNldHMvc2FtcGxlcy9DVFUxOUEtZmlyc3Q0LXJlbW92ZWQuY3N2IikKcmVhZHI6OndyaXRlX2NzdihDVFUxOUJfY3N2LHBhdGg9Ii4uL2RhdGFzZXRzL3NhbXBsZXMvQ1RVMTlCLWZpcnN0NC1yZW1vdmVkLmNzdiIpCgpDVFUxOUFfY3N2ICU+JSBncm91cF9ieShzb3VyY2UpICU+JSBzdW1tYXJpemUobj1uKCkpCkNUVTE5Ql9jc3YgJT4lIGdyb3VwX2J5KHNvdXJjZSkgJT4lIHN1bW1hcml6ZShuPW4oKSkKYGBgCgojIyBTZWxlY3QgU2FtcGxlIFNpemUKV2UgYXBwbHkgRWZyb24ncyBhbGdvcml0aG0gZm9yIHNlbGVjdGluZyBkaWZmZXJlbnQgc2FtcGxlIHByb3BvcnRpb25zIGFuZCB0aGVuIGNsYXNzaWZ5IHRoZSByZW1haW5pbmcgQ1RVMTkgYXMgYG9yaWdpbmFsYCBvciBgcmFuZG9tYC4gSWRlYWxseSB0aGUgMTAwJSBvZiB0aGUgcmVtYWluaW5nIENUVTE5IHNob3VsZCBiZSBjb25zaWRlcmVkIGFzIGBvcmlnaW5hbGAuCgoKYGBge3J9CmxpYnJhcnkoZG9NQykKbGlicmFyeShyc2FtcGxlKQpyZWdpc3RlckRvTUMoY29yZXM9MTEpCgpwcm9wX3Jlc3VsdHM8LWxpc3QoKQpmb3IgKHByb3BvcnRpb24gaW4gYygwLjEsMC4yLDAuMywwLjQsMC41KSl7CiAgcGFydGlhbF9yZXN1bHRzPC1jKCkKIyAgZm9yIChyZXBldGl0aW9uIGluIDE6MTApewogIHJlc3VsdHMgPC0gZm9yZWFjaChyZXBldGl0aW9uID0gMToyMiwgLmNvbWJpbmUgPSByYmluZCkgJWRvcGFyJSB7CiAgICBDVFUxOUFfc3BsaXQ8LXJzYW1wbGU6OmluaXRpYWxfc3BsaXQoQ1RVMTlBX3NlcSxwcm9wID1wcm9wb3J0aW9uLCBzdHJhdGE9c291cmNlKQogICAgQ1RVMTlBX3NlcV9zYW1wbGU8LXRyYWluaW5nKENUVTE5QV9zcGxpdCkKICAgIENUVTE5QV9zZXFfc2FtcGxlX3Rlc3Q8LXRlc3RpbmcoQ1RVMTlBX3NwbGl0KQogICAgQ1RVMTlBX3NlcV9zYW1wbGUgJT4lIG5yb3coKQogICAgCiAgICBtPC1lZnJvbl9zaW1pbChDVFUxOUFfc2VxX3NhbXBsZSxzZXFfbWF4bGVuKQogICAgcHJlZGljdGlvbnNfcHJvYjwtcHJlZGljdChtLCBDVFUxOUFfc2VxX3NhbXBsZV90ZXN0LCB0eXBlPSJwcm9iIikKICAgIHBhcnRpYWxfcmVzdWx0czwtYyhwYXJ0aWFsX3Jlc3VsdHMsbWVhbihwcmVkaWN0aW9uc19wcm9iWywxXSkpCiAgfQogIHByb3BfcmVzdWx0czwtcmJpbmQocHJvcF9yZXN1bHRzLGMobWVhbihyZXN1bHRzKSxzZChyZXN1bHRzKSkpCn0KZGF0YS5mcmFtZShtZWFuPXByb3BfcmVzdWx0c1ssMV0gJT4lIHVubGlzdCgpLHNkPXByb3BfcmVzdWx0c1ssMl0gJT4lIHVubGlzdCgpLCBpZD1zZXEoMTAsNTAsMTApKSAlPiUgZ2dwbG90KGFlcyh4PWlkLHk9bWVhbikpKwogICBnZW9tX3BvaW50KGNvbG9yPSdyZWQnKSArCiAgeWxhYigiUHJvYmFiaWxpdHkgW21lYW4gYW5kIHNkXSIpKwogIHhsYWIoIlBlcmNlbnRhZ2Ugb2YgdGhlIHBvcHVsYXRpb24gc2FtcGxlZCIpKwogIGxhYnModGl0bGU9IkVmcm9uJ3MgQWxnb3JpdGhtIGZvciBTYW1wbGUgU2ltaWxhcml0eSIsc3VidGl0bGU9IkEgUmFuZG9tIEZvcmVzdCBvbiBzYW1wbGUgZGF0YSBpcyB1c2VkIHRvIGNhbGN1bGF0ZSB0aGUgc2ltaWxhcml0eSBcbnRvIHRoZSBDVFUxOUEgaW4gdGVybXMgb2YgdGhlICdvcmlnaW5hbCcgY2xhc3MgcHJvYmFiaWxpdHkuIikrCiAgZ2VvbV9lcnJvcmJhcihhZXMoeW1pbj1tZWFuLXNkLCB5bWF4PW1lYW4rc2QpLCB3aWR0aD0uMixjb2xvcj0neWVsbG93JykrCiAgZ2dkYXJrOjpkYXJrX3RoZW1lX2J3KCkKIApgYGAKCiMjIyBQQ0EgMkQgdmlzdWFsaXphdGlvbiBmb3IgY29tcGFyaW5nIENUVTE5IGFuZCBhIDIwJSBzYW1wbGUuCmBgYHtyfQogQ1RVMTlBX3NwbGl0PC1yc2FtcGxlOjppbml0aWFsX3NwbGl0KENUVTE5QV9zZXEscHJvcCA9MC4yLCBzdHJhdGE9TlVMTCkKICAgIENUVTE5QV9zZXFfc2FtcGxlPC10cmFpbmluZyhDVFUxOUFfc3BsaXQpCiAgICBDVFUxOUFfc2VxX3NhbXBsZV90ZXN0PC10ZXN0aW5nKENUVTE5QV9zcGxpdCkKICAgIGdyaWRFeHRyYTo6Z3JpZC5hcnJhbmdlKGNyZWF0ZV9wY2EoQ1RVMTlBX3NlcV9zYW1wbGUpK2xhYnModGl0bGU9IjIwJSBTYW1wbGUiKQogICAgICAgICAgICAgICAgICAgICAgICAgICAgLGNyZWF0ZV9wY2EoQ1RVMTlBX3NlcSkrbGFicyh0aXRsZT0iQ1RVMTlBIFBvcHVsYXRpb24iKSkKICAgIApDVFUxOUFfc2VxX3NhbXBsZSAlPiUgZ3JvdXBfYnkoc291cmNlKSAlPiUgc3VtbWFyaXNlKG49bigpKQpDVFUxOUFfc2VxICU+JSBncm91cF9ieShzb3VyY2UpICU+JSBzdW1tYXJpc2Uobj1uKCkpICU+JSBhcnJhbmdlKG4pCgpgYGAKCiMjIENyZWF0ZSAzMCBzYW1wbGVzIHVzaW5nIHBzZXVkbyBNb250ZSBDYXJsbyBDVgpTaHVmZmxlIHRoZSBkYXRhc2V0IGFuZCBwaWNrIH40MDAwIGZvciB0cmFpbmluZyBhbmQgfjEwMDAgZm9yIHRlc3QuIFJlcGVhdCAzMCB0aW1lcy4KYGBge3J9CndvcmtkaXI9Ii9ob21lL2dhYi9kZ2Etd2Itci9kYXRhc2V0cy9zYW1wbGVzLyIKcmVzYW1wbGVfbWNfY3Y8LWZ1bmN0aW9uKHgpewogIHRyYWluX2luZCA8LSBzYW1wbGUoc2VxX2xlbihucm93KHgpKSwgc2l6ZSA9IG5yb3coeCkpCiAgI3RyYWluPC14W3RyYWluX2luZFsxOjIwMDBdLF0KICAjdGVzdDwteFt0cmFpbl9pbmRbMjAwMTozMDAwXSxdCiAgdHJhaW48LXhbdHJhaW5faW5kWzE6NDAwMF0sXQogIHRlc3Q8LXhbdHJhaW5faW5kWzQwMDE6NTAwMF0sXQogIHJldHVybiAobGlzdCh0cmFpbj10cmFpbix0ZXN0PXRlc3QpKQp9CmZvciAoaXRlciBpbiBzZXEoMzApKXsKICAgIHJlc2FtcGxlX21jX2N2X2RhdGFzZXRzPC1yZXNhbXBsZV9tY19jdihDVFUxOUFfY3N2KQogICAgcmVhZHI6OndyaXRlX2NzdihyZXNhbXBsZV9tY19jdl9kYXRhc2V0cyR0cmFpbixwYXRoPXBhc3RlMCh3b3JrZGlyLCJzYW1wbGUyMC1DVFUxOUEtbWMtdHJhaW4tIixpdGVyLCIuY3N2IikpCiAgICByZWFkcjo6d3JpdGVfY3N2KHJlc2FtcGxlX21jX2N2X2RhdGFzZXRzJHRlc3QscGF0aD1wYXN0ZTAod29ya2Rpciwic2FtcGxlMjAtQ1RVMTlBLW1jLXRlc3QtIixpdGVyLCIuY3N2IikpCn0KCkNUVTE5QV9zYW1wbGU8LXJlYWRyOjpyZWFkX2NzdigiLi4vZGF0YXNldHMvc2FtcGxlcy9zYW1wbGUyMC1DVFUxOUEtbWMtdHJhaW4tNi5jc3YiKQpDVFUxOUFfc2FtcGxlX3BhZGRlZF9zZXE8LWNyZWF0ZV9wYWRkZWRfc2VxKENUVTE5QV9zYW1wbGUsbWF4bGVuPXNlcV9tYXhsZW4pCnBjYV9wbG90X0NUVTE5QV9zYW1wbGVfcGFkZGVkX3NlcTwtY3JlYXRlX3BjYShDVFUxOUFfc2FtcGxlX3BhZGRlZF9zZXEsbWF4bGVuPXNlcV9tYXhsZW4pCgpDVFUxOUFfc2FtcGxlICU+JSBncm91cF9ieShzb3VyY2UpICU+JSBzdW1tYXJpc2Uobj1uKCkpCkNUVTE5QV9zYW1wbGUgJT4lIGdyb3VwX2J5KExhYmVsTmFtZSkgJT4lIHN1bW1hcmlzZShuPW4oKSkKcGNhX3Bsb3RfQ1RVMTlBX3NhbXBsZV9wYWRkZWRfc2VxK2xhYnModGl0bGU9IlBDQSBvZiBhIHRyYWluaW5nIHNhbXBsZSB3aXRoIDIwJSBvZiB0aGUgQ1RVMTlBIikKCgpgYGAKCiMjIyBDaGVja2luZyBpZiBhbGwgdHJhaW4gc2FtcGxlcyBpbmNsdWRlIHRoZSA5IHNvdXJjZXMgZnJvbSBDVFUxOUEKYGBge3J9CmZpbGVzIDwtIGxpc3QuZmlsZXMocGF0aCA9IHdvcmtkaXIscGF0dGVybj0ic2FtcGxlMjAtQ1RVMTlBLW1jLXRyYWluLVxcZCsiKQpzYW1wbGVzX2ZpbGVzIDwtIGxhcHBseShmaWxlcywgZnVuY3Rpb24oeCkgCiAgICAgICAgICAgICAgICByZWFkX2NzdihwYXN0ZSh3b3JrZGlyLHgsc2VwPSIiKSkgICAKICAgICAgICAgICAgICAgICU+JSB0aWJibGU6OmFkZF9jb2x1bW4oc2FtcGxlPWFzLmludGVnZXIoc3RyX3JlcGxhY2Uoc3RyaW5nID0geCAscGF0dGVybiA9ICJzYW1wbGUyMC1DVFUxOUEtbWMtdHJhaW4tKFxcZCspLmNzdiIsIlxcMSIpICkpCiAgICAgICAgICAgICAgICApCnNhbXBsZXNfZmlsZXMgPC1kby5jYWxsKHJiaW5kLHNhbXBsZXNfZmlsZXMpCnNhbXBsZXNfZmlsZXMgJT4lIGdyb3VwX2J5KHNvdXJjZSxzYW1wbGUpICU+JSBzdW1tYXJpc2Uobj1uKCkpICU+JSBncm91cF9ieShzYW1wbGUpICU+JSBzdW1tYXJpc2Uoc291cmNlcz1uKCkpCmBgYAoKIyMjIENoZWNraW5nIGlmIGFsbCB0ZXN0IHNhbXBsZXMgaW5jbHVkZSB0aGUgOSBzb3VyY2VzIGZyb20gQ1RVMTlBCmBgYHtyfQpmaWxlcyA8LSBsaXN0LmZpbGVzKHBhdGggPSB3b3JrZGlyLHBhdHRlcm49InNhbXBsZTIwLUNUVTE5QS1tYy10ZXN0LVxcZCsiKQpzYW1wbGVzX2ZpbGVzIDwtIGxhcHBseShmaWxlcywgZnVuY3Rpb24oeCkgCiAgICAgICAgICAgICAgICByZWFkX2NzdihwYXN0ZSh3b3JrZGlyLHgsc2VwPSIiKSkgICAKICAgICAgICAgICAgICAgICU+JSB0aWJibGU6OmFkZF9jb2x1bW4oc2FtcGxlPWFzLmludGVnZXIoc3RyX3JlcGxhY2Uoc3RyaW5nID0geCAscGF0dGVybiA9ICJzYW1wbGUyMC1DVFUxOUEtbWMtdGVzdC0oXFxkKykuY3N2IiwiXFwxIikgKSkKICAgICAgICAgICAgICAgICkKc2FtcGxlc19maWxlcyA8LWRvLmNhbGwocmJpbmQsc2FtcGxlc19maWxlcykKc2FtcGxlc19maWxlcyAlPiUgZ3JvdXBfYnkoc291cmNlLHNhbXBsZSkgJT4lIHN1bW1hcmlzZShuPW4oKSkgJT4lIGdyb3VwX2J5KHNhbXBsZSkgJT4lIHN1bW1hcmlzZShzb3VyY2VzPW4oKSkKYGBgCgojIyMgUmVzdWx0aW5nIGRhdGFzZXRzIGFuZCBzYW1wbGVzCgpUaGUgZmluYWwgdmVyc2lvbiBvZiBDVFUxOUEgYW5kIENUVTE5QiBhcyB3ZWxsIGFzIHRoZSAxMCUgYW5kIDIwJSBzYW1wbGVzIGFyZSBsb2NhdGVkIFtoZXJlXShodHRwczovL3d3dy5kcm9wYm94LmNvbS9zaC9pamllcGEzbDh6eHk4bHUvQUFCUERGam85ZU1OUHJhXzNTZlBaeWVOYT9kbD0wKQ==