library(dplyr)
library(ggplot2)
library(lattice)
library(tidyr)
library(caret)
library(randomForest)

Cargamos dataset

Eliminamos X1

ctu19=readr::read_delim(file="//home/harpo/Dropbox/ongoing-work/git-repos/deepactivelearning/datasets/ctu19_result.csv",delim = ',')
Missing column names filled in: 'X1' [1]Parsed with column specification:
cols(
  X1 = col_double(),
  InitialIp = col_character(),
  EndIP = col_character(),
  Port = col_double(),
  Proto = col_character(),
  State = col_character(),
  LabelName = col_character()
)
22184 parsing failures.
 row  col               expected actual                                                                                       file
2940 Port no trailing characters  x00d7 '//home/harpo/Dropbox/ongoing-work/git-repos/deepactivelearning/datasets/ctu19_result.csv'
2941 Port no trailing characters  x00db '//home/harpo/Dropbox/ongoing-work/git-repos/deepactivelearning/datasets/ctu19_result.csv'
2942 Port no trailing characters  x00ea '//home/harpo/Dropbox/ongoing-work/git-repos/deepactivelearning/datasets/ctu19_result.csv'
2943 Port no trailing characters  x00ff '//home/harpo/Dropbox/ongoing-work/git-repos/deepactivelearning/datasets/ctu19_result.csv'
2944 Port no trailing characters  x01d1 '//home/harpo/Dropbox/ongoing-work/git-repos/deepactivelearning/datasets/ctu19_result.csv'
.... .... ...................... ...... ..........................................................................................
See problems(...) for more details.
ctu19<-ctu19 %>% select(-X1)
ctu19 %>% head(100) #ojo que aveces si se muestra un dataframe grande en un notebok, este puede explotar ;-)

Descartamos aquellas que tengan 4 simbolos o menos.

ctu19 <- ctu19 %>% filter(nchar(State) > 4) # Version usando pipes. Fijate que a filter no necesito pasarle el dataframe como parametro.


### Version original
#ctu19 <- filter(ctu19,nchar(ctu19$State) > 4)

Otra opcion para eliminar


ctu19$State <- ctu19$State %>% substr(4,nchar(ctu19$State)) # Version usado pipes
 
### Version original
#for (i in 1:length(ctu19$State)){
#  ctu19$State[i] <- substr(ctu19$State[i],5,nchar(ctu19$State[i]))
#}

La version final del dataset quedaria asi:

ctu19 %>% head(100)

Feature Vectors

Armamos la matriz que se usara para crear los feature vectors.

size_small <- c('a','b','c','A','B','C','r','s','t','R','S','T',1,2,3)
size_medium <- c('d','e','f','D','E','F','u','v','w','U','V','W',4,5,6)
size_large <- c('g','h','i','G','H','I','x','y','z','X','Y','Z',7,8,9)

strong_per <- c('a','b','c','d','f','g','h','i')
weak_per <- c('A','B','C','D','E','F','G','H','I')
weak_nonper <- c('r','s','t','u','v','w','x','y','z')
strong_nonper <- c('R','S','T','U','V','W','X','Y','Z')
no_data <- c(1,2,3,4,5,6,7,8,9)

dur_short <- c('a','A','r','R',1,'d','D','u','U',4,'g','G','x','X',7)
dur_med <- c('b','B','s','S',2,'e','E','v','V',5,'h','H','y','Y',8)
dur_long <- c('c','C','t','T',3,'f','F','w','W',6,'i','I','z','Z',9)

time_simbol <- c('.',',','+','*',0)

Creacion de los Feature Vectors

ft0 <- c()
ft1 <- c()
ft2 <- c()
ft3 <- c()
ft4 <- c()
ft5 <- c()
ft6 <- c()
ft7 <- c()
ft8 <- c()
ft9 <- c()
for (i in 1:length(ctu19$State)){
  feature_vector <- c(0,0,0,0,0,0,0,0,0,0)
  longitud_cadena <- 0
  for (j in 1:nchar(ctu19$State[i])){
    simbolo <- substr(ctu19$State[i],j,j)
    
    if (!(is.element(simbolo,time_simbol))){
      longitud_cadena <- longitud_cadena + 1
    }
    
    if (is.element(simbolo,size_small)){
      feature_vector[8] <- feature_vector[8] + 1
    }else{
      if (is.element(simbolo,size_medium)){
        feature_vector[9] <- feature_vector[9] + 1
      } else{
        if (is.element(simbolo,size_large)){
          feature_vector[10] <- feature_vector[10] + 1
        }
      }
    }
    
    if (is.element(simbolo,dur_short)){
      feature_vector[5] <- feature_vector[5] + 1
    }else{
      if (is.element(simbolo,dur_med)){
        feature_vector[6] <- feature_vector[6] + 1
      } else{
        if (is.element(simbolo,dur_long)){
          feature_vector[7] <- feature_vector[7] + 1
        }
      }
    }
    
    if (is.element(simbolo,strong_per)){
      feature_vector[1] <- feature_vector[1] + 1
    }else{
      if (is.element(simbolo,weak_per)){
        feature_vector[2] <- feature_vector[2] + 1
      } else{
        if (is.element(simbolo,weak_nonper)){
          feature_vector[3] <- feature_vector[3] + 1
        }else{
          if (is.element(simbolo,strong_nonper)){
            feature_vector[4] <- feature_vector[4] + 1
          }
        }
      }
    }
  }
  feature_vector <- feature_vector/longitud_cadena
  feature_vector <- round(feature_vector,3)
  
  ft0 <- c(ft0,feature_vector[1])
  ft1 <- c(ft1,feature_vector[2])
  ft2 <- c(ft2,feature_vector[3])
  ft3 <- c(ft3,feature_vector[4])
  ft4 <- c(ft4,feature_vector[5])
  ft5 <- c(ft5,feature_vector[6])
  ft6 <- c(ft6,feature_vector[7])
  ft7 <- c(ft7,feature_vector[8])
  ft8 <- c(ft8,feature_vector[9])
  ft9 <- c(ft9,feature_vector[10])
}

Los integramos al ctu19

ctu19 <- cbind(ctu19,ft0)
ctu19 <- cbind(ctu19,ft1)
ctu19 <- cbind(ctu19,ft2)
ctu19 <- cbind(ctu19,ft3)
ctu19 <- cbind(ctu19,ft4)
ctu19 <- cbind(ctu19,ft5)
ctu19 <- cbind(ctu19,ft6)
ctu19 <- cbind(ctu19,ft7)
ctu19 <- cbind(ctu19,ft8)
ctu19 <- cbind(ctu19,ft9)

Eliminamos todas las columnas y dejamos solo LabelName y los Feature Vectors.

ctu19 <- ctu19 %>% select(-InitialIp,-EndIP,-Proto,-State,-Port)
ctu19$LabelName <- as.factor(ctu19$LabelName)
ctu19 %>% head(100)

Random Forest

CTU19 puro

Train 70% - Test 30%

train_data_ind <- createDataPartition(ctu19$LabelName, p = 0.7,list = FALSE)
train_data <- ctu19[train_data_ind,]
test_data <- ctu19[-train_data_ind,]
nrow(train_data)
[1] 19191
nrow(test_data)
[1] 8223

Vemos que hay un gran desvalance en el ctu19, con muchas botnets y pocas normales.

train_data %>% group_by(LabelName) %>% summarise(total=n())

Creacion del modelo

set.seed(123)

modelo_randomforest <- randomForest(LabelName~., data=train_data,na.action = na.omit)

Claramente vemos que tenemos un problema con las normales.

prediction <-predict(modelo_randomforest,test_data)
confusionMatrix(prediction, test_data$LabelName)
Confusion Matrix and Statistics

          Reference
Prediction Botnet Normal
    Botnet   5570    316
    Normal     69    162
                                         
               Accuracy : 0.9371         
                 95% CI : (0.9307, 0.943)
    No Information Rate : 0.9219         
    P-Value [Acc > NIR] : 2.79e-06       
                                         
                  Kappa : 0.4278         
                                         
 Mcnemar's Test P-Value : < 2.2e-16      
                                         
            Sensitivity : 0.9878         
            Specificity : 0.3389         
         Pos Pred Value : 0.9463         
         Neg Pred Value : 0.7013         
             Prevalence : 0.9219         
         Detection Rate : 0.9106         
   Detection Prevalence : 0.9622         
      Balanced Accuracy : 0.6633         
                                         
       'Positive' Class : Botnet         
                                         

ft3 es periocidad, ft5 y ft6 duracion y ft9 tamaño.

varImpPlot(modelo_randomforest,main = "Importancia de predictores")

CTU19 con DownSampling

down_train_data <- downSample(train_data,train_data$LabelName, list=FALSE)
down_train_data %>% group_by(LabelName) %>% summarise(total=n())
down_train_data <- down_train_data %>% select(-Class)
set.seed(123)

modelo_randomforest <- randomForest(LabelName~., data=down_train_data,na.action = na.omit)

Vemos que tiene muchos problemas con los normales, ya que etiqueto a 1045 como normales y eran botnets.

prediction <-predict(modelo_randomforest,test_data)
confusionMatrix(prediction, test_data$LabelName)
Confusion Matrix and Statistics

          Reference
Prediction Botnet Normal
    Botnet   4503     30
    Normal   1136    448
                                          
               Accuracy : 0.8094          
                 95% CI : (0.7993, 0.8192)
    No Information Rate : 0.9219          
    P-Value [Acc > NIR] : 1               
                                          
                  Kappa : 0.3574          
                                          
 Mcnemar's Test P-Value : <2e-16          
                                          
            Sensitivity : 0.7985          
            Specificity : 0.9372          
         Pos Pred Value : 0.9934          
         Neg Pred Value : 0.2828          
             Prevalence : 0.9219          
         Detection Rate : 0.7361          
   Detection Prevalence : 0.7410          
      Balanced Accuracy : 0.8679          
                                          
       'Positive' Class : Botnet          
                                          

Vemos que cambiaron los predictores mas importantes, siendo ahora el ft7 duracion, el ft9 tamaño y el ft0 periosidad.

varImpPlot(modelo_randomforest,main = "Importancia de predictores")

CTU19 con UpSampling

up_train_data <- upSample(train_data,train_data$LabelName, list=FALSE)
up_train_data %>% group_by(LabelName) %>% summarise(total=n())
up_train_data <- up_train_data %>% select(-Class)
set.seed(123)

modelo_randomforest <- randomForest(LabelName~., data=up_train_data,na.action = na.omit)

Seguimos teniendo problemas con los normales. Notamos una mejora pero vemos que por mas de que ahora usemos la misma cantidad de botnets y de normales para entrenar el modelo sigue teniendo problemas etiquetando como normales a botnets. Sin usar Sampling vemos que no se equivoca tanto con los botnets, pero esto es porque estamos entrenando y testeando con muchas botnets.

prediction <-predict(modelo_randomforest,test_data)
confusionMatrix(prediction, test_data$LabelName)
Confusion Matrix and Statistics

          Reference
Prediction Botnet Normal
    Botnet   4790     79
    Normal    849    399
                                          
               Accuracy : 0.8483          
                 95% CI : (0.8391, 0.8572)
    No Information Rate : 0.9219          
    P-Value [Acc > NIR] : 1               
                                          
                  Kappa : 0.3938          
                                          
 Mcnemar's Test P-Value : <2e-16          
                                          
            Sensitivity : 0.8494          
            Specificity : 0.8347          
         Pos Pred Value : 0.9838          
         Neg Pred Value : 0.3197          
             Prevalence : 0.9219          
         Detection Rate : 0.7831          
   Detection Prevalence : 0.7960          
      Balanced Accuracy : 0.8421          
                                          
       'Positive' Class : Botnet          
                                          
varImpPlot(modelo_randomforest,main = "Importancia de predictores")

LS0tCnRpdGxlOiAiUiBOb3RlYm9vayIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKYGBge3J9CmxpYnJhcnkoZHBseXIpCmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShsYXR0aWNlKQpsaWJyYXJ5KHRpZHlyKQpsaWJyYXJ5KGNhcmV0KQpsaWJyYXJ5KHJhbmRvbUZvcmVzdCkKYGBgCgojIENhcmdhbW9zIGRhdGFzZXQKCkVsaW1pbmFtb3MgWDEKYGBge3J9CmN0dTE5PXJlYWRyOjpyZWFkX2RlbGltKGZpbGU9Ii8vaG9tZS9oYXJwby9Ecm9wYm94L29uZ29pbmctd29yay9naXQtcmVwb3MvZGVlcGFjdGl2ZWxlYXJuaW5nL2RhdGFzZXRzL2N0dTE5X3Jlc3VsdC5jc3YiLGRlbGltID0gJywnKQpjdHUxOTwtY3R1MTkgJT4lIHNlbGVjdCgtWDEpCmN0dTE5ICU+JSBoZWFkKDEwMCkgI29qbyBxdWUgYXZlY2VzIHNpIHNlIG11ZXN0cmEgdW4gZGF0YWZyYW1lIGdyYW5kZSBlbiB1biBub3RlYm9rLCBlc3RlIHB1ZWRlIGV4cGxvdGFyIDstKQpgYGAKCgpEZXNjYXJ0YW1vcyBhcXVlbGxhcyBxdWUgdGVuZ2FuIDQgc2ltYm9sb3MgbyBtZW5vcy4KCmBgYHtyfQpjdHUxOSA8LSBjdHUxOSAlPiUgZmlsdGVyKG5jaGFyKFN0YXRlKSA+IDQpICMgVmVyc2lvbiB1c2FuZG8gcGlwZXMuIEZpamF0ZSBxdWUgYSBmaWx0ZXIgbm8gbmVjZXNpdG8gcGFzYXJsZSBlbCBkYXRhZnJhbWUgY29tbyBwYXJhbWV0cm8uCgoKIyMjIFZlcnNpb24gb3JpZ2luYWwKI2N0dTE5IDwtIGZpbHRlcihjdHUxOSxuY2hhcihjdHUxOSRTdGF0ZSkgPiA0KQoKYGBgCgpPdHJhIG9wY2lvbiBwYXJhIGVsaW1pbmFyCgpgYGB7cn0KCmN0dTE5JFN0YXRlIDwtIGN0dTE5JFN0YXRlICU+JSBzdWJzdHIoNCxuY2hhcihjdHUxOSRTdGF0ZSkpICMgVmVyc2lvbiB1c2FkbyBwaXBlcwogCiMjIyBWZXJzaW9uIG9yaWdpbmFsCiNmb3IgKGkgaW4gMTpsZW5ndGgoY3R1MTkkU3RhdGUpKXsKIyAgY3R1MTkkU3RhdGVbaV0gPC0gc3Vic3RyKGN0dTE5JFN0YXRlW2ldLDUsbmNoYXIoY3R1MTkkU3RhdGVbaV0pKQojfQoKCmBgYAoKTGEgdmVyc2lvbiBmaW5hbCBkZWwgZGF0YXNldCBxdWVkYXJpYSBhc2k6CgpgYGB7cn0KY3R1MTkgJT4lIGhlYWQoMTAwKQpgYGAKCgojIEZlYXR1cmUgVmVjdG9ycwoKQXJtYW1vcyBsYSBtYXRyaXogcXVlIHNlIHVzYXJhIHBhcmEgY3JlYXIgbG9zIGZlYXR1cmUgdmVjdG9ycy4KYGBge3J9CnNpemVfc21hbGwgPC0gYygnYScsJ2InLCdjJywnQScsJ0InLCdDJywncicsJ3MnLCd0JywnUicsJ1MnLCdUJywxLDIsMykKc2l6ZV9tZWRpdW0gPC0gYygnZCcsJ2UnLCdmJywnRCcsJ0UnLCdGJywndScsJ3YnLCd3JywnVScsJ1YnLCdXJyw0LDUsNikKc2l6ZV9sYXJnZSA8LSBjKCdnJywnaCcsJ2knLCdHJywnSCcsJ0knLCd4JywneScsJ3onLCdYJywnWScsJ1onLDcsOCw5KQoKc3Ryb25nX3BlciA8LSBjKCdhJywnYicsJ2MnLCdkJywnZicsJ2cnLCdoJywnaScpCndlYWtfcGVyIDwtIGMoJ0EnLCdCJywnQycsJ0QnLCdFJywnRicsJ0cnLCdIJywnSScpCndlYWtfbm9ucGVyIDwtIGMoJ3InLCdzJywndCcsJ3UnLCd2JywndycsJ3gnLCd5JywneicpCnN0cm9uZ19ub25wZXIgPC0gYygnUicsJ1MnLCdUJywnVScsJ1YnLCdXJywnWCcsJ1knLCdaJykKbm9fZGF0YSA8LSBjKDEsMiwzLDQsNSw2LDcsOCw5KQoKZHVyX3Nob3J0IDwtIGMoJ2EnLCdBJywncicsJ1InLDEsJ2QnLCdEJywndScsJ1UnLDQsJ2cnLCdHJywneCcsJ1gnLDcpCmR1cl9tZWQgPC0gYygnYicsJ0InLCdzJywnUycsMiwnZScsJ0UnLCd2JywnVicsNSwnaCcsJ0gnLCd5JywnWScsOCkKZHVyX2xvbmcgPC0gYygnYycsJ0MnLCd0JywnVCcsMywnZicsJ0YnLCd3JywnVycsNiwnaScsJ0knLCd6JywnWicsOSkKCnRpbWVfc2ltYm9sIDwtIGMoJy4nLCcsJywnKycsJyonLDApCmBgYAoKCkNyZWFjaW9uIGRlIGxvcyBGZWF0dXJlIFZlY3RvcnMKCmBgYHtyfQpmdDAgPC0gYygpCmZ0MSA8LSBjKCkKZnQyIDwtIGMoKQpmdDMgPC0gYygpCmZ0NCA8LSBjKCkKZnQ1IDwtIGMoKQpmdDYgPC0gYygpCmZ0NyA8LSBjKCkKZnQ4IDwtIGMoKQpmdDkgPC0gYygpCmZvciAoaSBpbiAxOmxlbmd0aChjdHUxOSRTdGF0ZSkpewogIGZlYXR1cmVfdmVjdG9yIDwtIGMoMCwwLDAsMCwwLDAsMCwwLDAsMCkKICBsb25naXR1ZF9jYWRlbmEgPC0gMAogIGZvciAoaiBpbiAxOm5jaGFyKGN0dTE5JFN0YXRlW2ldKSl7CiAgICBzaW1ib2xvIDwtIHN1YnN0cihjdHUxOSRTdGF0ZVtpXSxqLGopCiAgICAKICAgIGlmICghKGlzLmVsZW1lbnQoc2ltYm9sbyx0aW1lX3NpbWJvbCkpKXsKICAgICAgbG9uZ2l0dWRfY2FkZW5hIDwtIGxvbmdpdHVkX2NhZGVuYSArIDEKICAgIH0KICAgIAogICAgaWYgKGlzLmVsZW1lbnQoc2ltYm9sbyxzaXplX3NtYWxsKSl7CiAgICAgIGZlYXR1cmVfdmVjdG9yWzhdIDwtIGZlYXR1cmVfdmVjdG9yWzhdICsgMQogICAgfWVsc2V7CiAgICAgIGlmIChpcy5lbGVtZW50KHNpbWJvbG8sc2l6ZV9tZWRpdW0pKXsKICAgICAgICBmZWF0dXJlX3ZlY3Rvcls5XSA8LSBmZWF0dXJlX3ZlY3Rvcls5XSArIDEKICAgICAgfSBlbHNlewogICAgICAgIGlmIChpcy5lbGVtZW50KHNpbWJvbG8sc2l6ZV9sYXJnZSkpewogICAgICAgICAgZmVhdHVyZV92ZWN0b3JbMTBdIDwtIGZlYXR1cmVfdmVjdG9yWzEwXSArIDEKICAgICAgICB9CiAgICAgIH0KICAgIH0KICAgIAogICAgaWYgKGlzLmVsZW1lbnQoc2ltYm9sbyxkdXJfc2hvcnQpKXsKICAgICAgZmVhdHVyZV92ZWN0b3JbNV0gPC0gZmVhdHVyZV92ZWN0b3JbNV0gKyAxCiAgICB9ZWxzZXsKICAgICAgaWYgKGlzLmVsZW1lbnQoc2ltYm9sbyxkdXJfbWVkKSl7CiAgICAgICAgZmVhdHVyZV92ZWN0b3JbNl0gPC0gZmVhdHVyZV92ZWN0b3JbNl0gKyAxCiAgICAgIH0gZWxzZXsKICAgICAgICBpZiAoaXMuZWxlbWVudChzaW1ib2xvLGR1cl9sb25nKSl7CiAgICAgICAgICBmZWF0dXJlX3ZlY3Rvcls3XSA8LSBmZWF0dXJlX3ZlY3Rvcls3XSArIDEKICAgICAgICB9CiAgICAgIH0KICAgIH0KICAgIAogICAgaWYgKGlzLmVsZW1lbnQoc2ltYm9sbyxzdHJvbmdfcGVyKSl7CiAgICAgIGZlYXR1cmVfdmVjdG9yWzFdIDwtIGZlYXR1cmVfdmVjdG9yWzFdICsgMQogICAgfWVsc2V7CiAgICAgIGlmIChpcy5lbGVtZW50KHNpbWJvbG8sd2Vha19wZXIpKXsKICAgICAgICBmZWF0dXJlX3ZlY3RvclsyXSA8LSBmZWF0dXJlX3ZlY3RvclsyXSArIDEKICAgICAgfSBlbHNlewogICAgICAgIGlmIChpcy5lbGVtZW50KHNpbWJvbG8sd2Vha19ub25wZXIpKXsKICAgICAgICAgIGZlYXR1cmVfdmVjdG9yWzNdIDwtIGZlYXR1cmVfdmVjdG9yWzNdICsgMQogICAgICAgIH1lbHNlewogICAgICAgICAgaWYgKGlzLmVsZW1lbnQoc2ltYm9sbyxzdHJvbmdfbm9ucGVyKSl7CiAgICAgICAgICAgIGZlYXR1cmVfdmVjdG9yWzRdIDwtIGZlYXR1cmVfdmVjdG9yWzRdICsgMQogICAgICAgICAgfQogICAgICAgIH0KICAgICAgfQogICAgfQogIH0KICBmZWF0dXJlX3ZlY3RvciA8LSBmZWF0dXJlX3ZlY3Rvci9sb25naXR1ZF9jYWRlbmEKICBmZWF0dXJlX3ZlY3RvciA8LSByb3VuZChmZWF0dXJlX3ZlY3RvciwzKQogIAogIGZ0MCA8LSBjKGZ0MCxmZWF0dXJlX3ZlY3RvclsxXSkKICBmdDEgPC0gYyhmdDEsZmVhdHVyZV92ZWN0b3JbMl0pCiAgZnQyIDwtIGMoZnQyLGZlYXR1cmVfdmVjdG9yWzNdKQogIGZ0MyA8LSBjKGZ0MyxmZWF0dXJlX3ZlY3Rvcls0XSkKICBmdDQgPC0gYyhmdDQsZmVhdHVyZV92ZWN0b3JbNV0pCiAgZnQ1IDwtIGMoZnQ1LGZlYXR1cmVfdmVjdG9yWzZdKQogIGZ0NiA8LSBjKGZ0NixmZWF0dXJlX3ZlY3Rvcls3XSkKICBmdDcgPC0gYyhmdDcsZmVhdHVyZV92ZWN0b3JbOF0pCiAgZnQ4IDwtIGMoZnQ4LGZlYXR1cmVfdmVjdG9yWzldKQogIGZ0OSA8LSBjKGZ0OSxmZWF0dXJlX3ZlY3RvclsxMF0pCn0KCmBgYAoKTG9zIGludGVncmFtb3MgYWwgY3R1MTkKCmBgYHtyfQpjdHUxOSA8LSBjYmluZChjdHUxOSxmdDApCmN0dTE5IDwtIGNiaW5kKGN0dTE5LGZ0MSkKY3R1MTkgPC0gY2JpbmQoY3R1MTksZnQyKQpjdHUxOSA8LSBjYmluZChjdHUxOSxmdDMpCmN0dTE5IDwtIGNiaW5kKGN0dTE5LGZ0NCkKY3R1MTkgPC0gY2JpbmQoY3R1MTksZnQ1KQpjdHUxOSA8LSBjYmluZChjdHUxOSxmdDYpCmN0dTE5IDwtIGNiaW5kKGN0dTE5LGZ0NykKY3R1MTkgPC0gY2JpbmQoY3R1MTksZnQ4KQpjdHUxOSA8LSBjYmluZChjdHUxOSxmdDkpCmBgYAoKRWxpbWluYW1vcyB0b2RhcyBsYXMgY29sdW1uYXMgeSBkZWphbW9zIHNvbG8gTGFiZWxOYW1lIHkgbG9zIEZlYXR1cmUgVmVjdG9ycy4KYGBge3J9CmN0dTE5IDwtIGN0dTE5ICU+JSBzZWxlY3QoLUluaXRpYWxJcCwtRW5kSVAsLVByb3RvLC1TdGF0ZSwtUG9ydCkKY3R1MTkkTGFiZWxOYW1lIDwtIGFzLmZhY3RvcihjdHUxOSRMYWJlbE5hbWUpCmBgYAoKYGBge3J9CmN0dTE5ICU+JSBoZWFkKDEwMCkKYGBgCgojIFJhbmRvbSBGb3Jlc3QKCiMjIENUVTE5IHB1cm8KClRyYWluIDcwJSAtIFRlc3QgMzAlCgoKYGBge3J9CnRyYWluX2RhdGFfaW5kIDwtIGNyZWF0ZURhdGFQYXJ0aXRpb24oY3R1MTkkTGFiZWxOYW1lLCBwID0gMC43LGxpc3QgPSBGQUxTRSkKdHJhaW5fZGF0YSA8LSBjdHUxOVt0cmFpbl9kYXRhX2luZCxdCnRlc3RfZGF0YSA8LSBjdHUxOVstdHJhaW5fZGF0YV9pbmQsXQpgYGAKCmBgYHtyfQpucm93KHRyYWluX2RhdGEpCm5yb3codGVzdF9kYXRhKQpgYGAKCgpWZW1vcyBxdWUgaGF5IHVuIGdyYW4gZGVzdmFsYW5jZSBlbiBlbCBjdHUxOSwgY29uIG11Y2hhcyBib3RuZXRzIHkgcG9jYXMgbm9ybWFsZXMuCgpgYGB7cn0KdHJhaW5fZGF0YSAlPiUgZ3JvdXBfYnkoTGFiZWxOYW1lKSAlPiUgc3VtbWFyaXNlKHRvdGFsPW4oKSkKYGBgCgpDcmVhY2lvbiBkZWwgbW9kZWxvCgpgYGB7cn0Kc2V0LnNlZWQoMTIzKQoKbW9kZWxvX3JhbmRvbWZvcmVzdCA8LSByYW5kb21Gb3Jlc3QoTGFiZWxOYW1lfi4sIGRhdGE9dHJhaW5fZGF0YSxuYS5hY3Rpb24gPSBuYS5vbWl0KQoKYGBgCgpDbGFyYW1lbnRlIHZlbW9zIHF1ZSB0ZW5lbW9zIHVuIHByb2JsZW1hIGNvbiBsYXMgbm9ybWFsZXMuIAoKYGBge3J9CnByZWRpY3Rpb24gPC1wcmVkaWN0KG1vZGVsb19yYW5kb21mb3Jlc3QsdGVzdF9kYXRhKQpjb25mdXNpb25NYXRyaXgocHJlZGljdGlvbiwgdGVzdF9kYXRhJExhYmVsTmFtZSkKYGBgCgpmdDMgZXMgcGVyaW9jaWRhZCwgZnQ1IHkgZnQ2IGR1cmFjaW9uIHkgZnQ5IHRhbWHDsW8uCgpgYGB7cn0KdmFySW1wUGxvdChtb2RlbG9fcmFuZG9tZm9yZXN0LG1haW4gPSAiSW1wb3J0YW5jaWEgZGUgcHJlZGljdG9yZXMiKQpgYGAKCgojIyBDVFUxOSBjb24gRG93blNhbXBsaW5nCgoKYGBge3J9CmRvd25fdHJhaW5fZGF0YSA8LSBkb3duU2FtcGxlKHRyYWluX2RhdGEsdHJhaW5fZGF0YSRMYWJlbE5hbWUsIGxpc3Q9RkFMU0UpCmRvd25fdHJhaW5fZGF0YSAlPiUgZ3JvdXBfYnkoTGFiZWxOYW1lKSAlPiUgc3VtbWFyaXNlKHRvdGFsPW4oKSkKZG93bl90cmFpbl9kYXRhIDwtIGRvd25fdHJhaW5fZGF0YSAlPiUgc2VsZWN0KC1DbGFzcykKYGBgCgoKYGBge3J9CnNldC5zZWVkKDEyMykKCm1vZGVsb19yYW5kb21mb3Jlc3QgPC0gcmFuZG9tRm9yZXN0KExhYmVsTmFtZX4uLCBkYXRhPWRvd25fdHJhaW5fZGF0YSxuYS5hY3Rpb24gPSBuYS5vbWl0KQoKYGBgCgoKClZlbW9zIHF1ZSB0aWVuZSBtdWNob3MgcHJvYmxlbWFzIGNvbiBsb3Mgbm9ybWFsZXMsIHlhIHF1ZSBldGlxdWV0byBhIDEwNDUgY29tbyBub3JtYWxlcyB5IGVyYW4gYm90bmV0cy4gCgpgYGB7cn0KcHJlZGljdGlvbiA8LXByZWRpY3QobW9kZWxvX3JhbmRvbWZvcmVzdCx0ZXN0X2RhdGEpCmNvbmZ1c2lvbk1hdHJpeChwcmVkaWN0aW9uLCB0ZXN0X2RhdGEkTGFiZWxOYW1lKQpgYGAKClZlbW9zIHF1ZSBjYW1iaWFyb24gbG9zIHByZWRpY3RvcmVzIG1hcyBpbXBvcnRhbnRlcywgc2llbmRvIGFob3JhIGVsIGZ0NyBkdXJhY2lvbiwgZWwgZnQ5IHRhbWHDsW8geSBlbCBmdDAgcGVyaW9zaWRhZC4gCgpgYGB7cn0KdmFySW1wUGxvdChtb2RlbG9fcmFuZG9tZm9yZXN0LG1haW4gPSAiSW1wb3J0YW5jaWEgZGUgcHJlZGljdG9yZXMiKQpgYGAKCiMjIENUVTE5IGNvbiBVcFNhbXBsaW5nCgpgYGB7cn0KdXBfdHJhaW5fZGF0YSA8LSB1cFNhbXBsZSh0cmFpbl9kYXRhLHRyYWluX2RhdGEkTGFiZWxOYW1lLCBsaXN0PUZBTFNFKQp1cF90cmFpbl9kYXRhICU+JSBncm91cF9ieShMYWJlbE5hbWUpICU+JSBzdW1tYXJpc2UodG90YWw9bigpKQp1cF90cmFpbl9kYXRhIDwtIHVwX3RyYWluX2RhdGEgJT4lIHNlbGVjdCgtQ2xhc3MpCmBgYAoKCmBgYHtyfQpzZXQuc2VlZCgxMjMpCgptb2RlbG9fcmFuZG9tZm9yZXN0IDwtIHJhbmRvbUZvcmVzdChMYWJlbE5hbWV+LiwgZGF0YT11cF90cmFpbl9kYXRhLG5hLmFjdGlvbiA9IG5hLm9taXQpCgpgYGAKCgpTZWd1aW1vcyB0ZW5pZW5kbyBwcm9ibGVtYXMgY29uIGxvcyBub3JtYWxlcy4gTm90YW1vcyB1bmEgbWVqb3JhIHBlcm8gdmVtb3MgcXVlIHBvciBtYXMgZGUgcXVlIGFob3JhIHVzZW1vcyBsYSBtaXNtYSBjYW50aWRhZCBkZSBib3RuZXRzIHkgZGUgbm9ybWFsZXMgcGFyYSBlbnRyZW5hciBlbCBtb2RlbG8gc2lndWUgdGVuaWVuZG8gcHJvYmxlbWFzIGV0aXF1ZXRhbmRvIGNvbW8gbm9ybWFsZXMgYSBib3RuZXRzLiAKU2luIHVzYXIgU2FtcGxpbmcgdmVtb3MgcXVlIG5vIHNlIGVxdWl2b2NhIHRhbnRvIGNvbiBsb3MgYm90bmV0cywgcGVybyBlc3RvIGVzIHBvcnF1ZSBlc3RhbW9zIGVudHJlbmFuZG8geSB0ZXN0ZWFuZG8gY29uIG11Y2hhcyBib3RuZXRzLiAKCmBgYHtyfQpwcmVkaWN0aW9uIDwtcHJlZGljdChtb2RlbG9fcmFuZG9tZm9yZXN0LHRlc3RfZGF0YSkKY29uZnVzaW9uTWF0cml4KHByZWRpY3Rpb24sIHRlc3RfZGF0YSRMYWJlbE5hbWUpCmBgYAoKYGBge3J9CnZhckltcFBsb3QobW9kZWxvX3JhbmRvbWZvcmVzdCxtYWluID0gIkltcG9ydGFuY2lhIGRlIHByZWRpY3RvcmVzIikKYGBgCgoK