library(readr)
library(dplyr)
library(lubridate)
library(ggplot2)
library(plotly)
#library(ggmap)
#library(catboost)

rad2deg <- function(rad) {(rad * 180) / (pi)}
deg2rad <- function(deg) {(deg * pi) / (180)}


# Datasets originales
#caprino_hours<-read.csv("data/caprino_hours.csv.gz",header = F)
#caprino_imu_gyros<-read.csv("data/caprino_imu_gyro.csv.gz", header=F)
#caprino_imu_acc<-read.csv("data/caprino_imu_acc.csv.gz", header=F)
#caprino_gps<-read.csv("data/caprino_gps.csv",header=F)

# Datasets nuevos
caprino_hours<-read.csv("data/caprino_hours.csv.gz",header = F)
caprino_imu_gyros<-read.csv("data/caprino_imu_gyro.csv.gz", header=F)
caprino_imu_acc<-read.csv("data/caprino_imu_acc.csv.gz", header=F)
caprino_gps<-read.csv("data/caprino_gps_distancia.csv",header=F) #INCLUYE UNA SEXTA COLUMNA CON la distancia euclidea
caprino_gps<-caprino_gps %>% select(-V7)

caprino_imu_acc %>% nrow()
[1] 463703
caprino_imu_gyros %>% nrow()
[1] 463703
caprino_gps %>% nrow()
[1] 231767
caprino_hours %>% nrow()
[1] 231767

Datos de los archivos

Construccion del Dataset

caprino_data<-cbind(caprino_imu_acc,caprino_imu_gyros %>% select(-V1))

caprino_data<-cbind(caprino_data[seq(2,nrow(caprino_data),2),] %>% head(nrow(caprino_gps)-1),  caprino_gps[2:nrow(caprino_gps),] ) 
names(caprino_data)<-c("imu_ts",
                       "accX",
                       "accY",
                       "accZ",
                       "gyroX",
                       "gyroY",
                       "gyroZ",
                       "gps_hour",
                       "gps_ts",
                       "lat",
                       "long",
                       "alt",
                       "distance" 
                        )
#caprino_data %>% mutate(diff=imu_ts-gps_ts) %>% select(diff)
caprino_data<-caprino_data %>% mutate(activity=ifelse(hms(gps_hour)>hms("8:56:00") & hms(gps_hour)<hms("10:38:00"),"RP",
                                       
                                        ifelse(hms(gps_hour)>=hms("10:38:00") & hms(gps_hour)<hms("11:38:00"),"W",
                                        ifelse(hms(gps_hour)>=hms("11:38:00") & hms(gps_hour)<hms("12:05:00"),"G",
                                        ifelse(hms(gps_hour)>=hms("12:05:00") & hms(gps_hour)<hms("15:48:00"),"RF",
                                        ifelse(hms(gps_hour)>=hms("17:30:00") & hms(gps_hour)<hms("20:33:00"),"G",
                                        ifelse(hms(gps_hour)>=hms("20:50:00") & hms(gps_hour)<hms("21:31:00"),"W",
                                        ifelse(hms(gps_hour)>=hms("21:33:00") & hms(gps_hour)<hms("21:50:00"),"RP",NA)
                                        )))))
                                        )
                        
                        )

caprino_data <-caprino_data %>% mutate(id=cut(as.POSIXct(hms(caprino_data$gps_hour),origin="1960-01-01",tz="GMT"),breaks="1 min")) 
caprino_data %>% head(10)

Tomamos muestras aleatorias de 1 minuto de duracion para sobre las cuales despues queremos predecir el comportamiento

#readr::write_csv(caprino_data,"data/caprino_data_sliced_distance.csv")
#set.seed(21092019)
#set.seed(21092022)
#set.seed(07092020)
#set.seed(01092020)
minute_slice<-as.vector(t(caprino_data %>% group_by(id) %>% summarise(n=n()) %>% select(id)))
trainidx<-sample(1:length(minute_slice),(length(minute_slice)*70)/100.0)
data_train<-caprino_data %>% filter( id %in% minute_slice[trainidx])
data_test<-caprino_data %>% filter( id %in% (minute_slice[-trainidx]))
data_train%>% group_by(id) %>% summarise(n=n())
data_test%>% group_by(id) %>% summarise(n=n())
data_train %>% filter(!is.na(activity)) %>% group_by(activity) %>% summarise(n=n()) 
NA

Aplicamos Bag-of-features.

presupuesto

Consideramos cada muestra como un vector de 5 dimensiones. La idea es aplicar un algoritmo de clustering sobre estos vectores (accX,accY,accZ,GyroX,gyroY). Una vez encontrado los centroides, para cada muestra de 1 se construye un histograma de los vectores que han caido en cada uno de estos clusters. Este histograma es el que se utiliza para entregar el algoritmo.

La hipotesis de aplicar este metodo es que aquellas muestras que pertenecen a una misma actividad tendran valores similares. Los cuales han sido representado mediantes estos histogramas.

Aplicamos k-means con 90 centroides sobre el conjunto de entrenamiento.

Se utilizan todos los sensores y la distancia.

centroids<-90
cluster_features<-c('accX','accY','accZ','gyroX','gyroY','gyroZ','distance')

## Sampling considering time slices 
  minute_slice <-
    as.vector(t(
      caprino_data %>% group_by(id) %>% summarise(n = n()) %>% select(id)
    ))
  trainidx <-
    sample(1:length(minute_slice), (length(minute_slice) * 70) / 100.0)
  data_train <- caprino_data %>% filter(id %in% minute_slice[trainidx])
  data_test <-
    caprino_data %>% filter(id %in% (minute_slice[-trainidx]))
  
  
  # Kmeans on trainset
  cluster_results <-
    kmeans(
      x = data_train %>% select(cluster_features),
      centers = centroids,
      nstart = 10
    )
  data_train_cluster <-
    cbind(data_train, cluster = as.integer(cluster_results$cluster)) %>% group_by(activity, cluster)
  
  cluster_results<-cluster_results$centers %>% 
    as.data.frame() %>% 
    tibble::rownames_to_column(var = "cluster")

Aplicamos KNN con K=3 en el conjunto de entrenamiento.

  # KNN using centroids from kmeans for testset
  
  cluster_results_test <-class::knn(train=cluster_results %>% select(-cluster),
                                    test=data_test %>% select(cluster_features)
                                    ,cl = as.integer(cluster_results$cluster),
                                    k=3 )
  data_test_cluster <-
    cbind(data_test, cluster = as.integer(cluster_results_test)) %>% group_by(activity, cluster)
  
  # Create histograms for bag-of-features and label each new datapoint 
  ## For trainset
  data_train_cluster_labeled <- data_train_cluster   %>%
    group_by(id, cluster) %>% summarise(n = n()) %>%
    reshape2::dcast(id ~ cluster, fill = 0) %>%
    inner_join(data_train_cluster %>% ungroup() %>%
                 select(id, activity)  %>%
                 unique()  ,
               by = "id") %>% filter(!is.na(activity))
  
  data_cluster_histogram<-data.frame(matrix(0L,nrow=nrow(data_train_cluster_labeled),ncol=90))
  names(data_cluster_histogram)[1:90]<-seq(1,90)
  data_cluster_histogram[names(data_train_cluster_labeled)]<-data_train_cluster_labeled
  data_train_cluster_labeled<-data_cluster_histogram
  
  ## For testset
  data_test_cluster_labeled <- data_test_cluster   %>% 
    group_by(id, cluster) %>% summarise(n = n()) %>%
    reshape2::dcast(id ~ cluster, fill = 0) %>%
    inner_join(data_test_cluster %>%  ungroup() %>%
                 select(id, activity)  %>%
                 unique() ,
               by = "id")  %>% filter(!is.na(activity))
  
  data_cluster_histogram<-data.frame(matrix(0L,nrow=nrow(data_test_cluster_labeled),ncol=90))
  names(data_cluster_histogram)[1:90]<-seq(1,90)
  data_cluster_histogram[names(data_test_cluster_labeled)]<-data_test_cluster_labeled
  data_test_cluster_labeled<-data_cluster_histogram

Total de actividades en Train

data_train_cluster_labeled %>% group_by(activity) %>% summarise(total=n())

Total de actividades en Test

data_test_cluster_labeled %>% group_by(activity) %>% summarise(total=n())

Aplicamos 3x5 CV para elegir la mejor version del modelo

Probamos Random Forest

rf_grid <-  expand.grid(.mtry=c(5)) 
rfFit <- caret::train(
               x= data_train_cluster_labeled %>% select(-id,-activity),
               y= data_train_cluster_labeled$activity,
              
               method = "rf",
  
               #tuneLength=5, 
               tuneGrid=rf_grid,
               
               verbose=2,
       
               trControl = ctrl_fast,
               ntree=200)
rfFit$results

Seleccionamos el modelo

selected_model<-rfFit

Evaluacion de los resultados de CV (CV Report)

predictions_cv<-selected_model$pred %>% select(Resample,pred,obs)

predictions_cv_cm <- predictions_cv %>% group_by(Resample) %>%
  group_modify(
    ~ as.data.frame(
      caret::confusionMatrix(
        data = .x$pred,
        reference = .x$obs,
        mode = 'everything'
      )$byClass
    ) %>%
      select(Precision, Recall, F1,) %>% 
      tibble::rownames_to_column(var = "activity")
  )

predictions_cv_results<- predictions_cv_cm %>% 
  group_by(activity) %>% 
  summarise(
    precision_mean=mean(Precision),
    precision_se=sd(Precision)/sqrt(selected_model$control$number*selected_model$control$repeats),
    precision_conf_inf=mean(Precision)-(precision_se*1.96),
    precision_conf_sup=mean(Precision)+(precision_se*1.96),
    recall_mean=mean(Recall),
    recall_se=sd(Recall)/sqrt(selected_model$control$number*selected_model$control$repeats),
    recall_conf_inf=mean(Recall)-(recall_se*1.96),
    recall_conf_sup=mean(Recall)+(recall_se*1.96),
    F1_mean=mean(F1),
    F1_se=sd(F1)/sqrt(selected_model$control$number*selected_model$control$repeats),
    F1_conf_inf=mean(F1)-(F1_se*1.96),
    F1_conf_sup=mean(F1)+(F1_se*1.96)

)
predictions_cv_cm_plot<-predictions_cv_cm %>% reshape2::melt() %>%
  ggplot()+
  facet_wrap(~variable)+
  ylim(0.6,1)+
  geom_boxplot(aes(x=activity,y=value,fill=activity))+
  labs(title=paste0(selected_model$method," for activity prediction [CV] Report"),
                    subtitle="[3x5 CV]")+
  ggdark::dark_theme_gray()+
  theme(axis.text.x = element_text(angle = 45, vjust = 0.5, hjust=1))+
      theme(legend.position = "none") 
   
predictions_cv_cm

predictions_cv_cm_plot

predictions_cv_results
prediction_cv_cor<-predictions_cv_cm %>% select(activity,Recall,-Resample) %>% tidyr::pivot_wider(names_from = activity, values_from = Recall) %>% ungroup() %>% select(-Resample) %>% cor(method = "spearman")
Adding missing grouping variables: `Resample`
library(d3heatmap)
d3heatmap(prediction_cv_cor,colors = "Blues",cexRow = 0.8, cexCol = 0.8)

#predictions_cv_results_error  %>% select(activity,Sensitivity_sd,Specificity_sd,F1_sd,BalancedAccuracy_sd)
#predictions_cv_results  %>% select(activity,Sensitivity_mean,Specificity_mean,F1_mean,BalancedAccuracy_mean) %>% reshape2::melt() %>%
  
  
#  ggplot()+
#  facet_wrap(~variable)+
#  geom_col(aes(x=activity,y=value,fill=activity))+
#  ggdark::dark_theme_grey()+
#  theme(axis.text.x = element_text(angle = 45, vjust = 0.5, hjust=1))+
#  theme(legend.position = "none") 

Evaluacion sobre el conjunto de test (Test Report)

Matriz de Confusion

data_test_prediction<-predict(selected_model,data_test_cluster_labeled %>% select(-id))#,type="prob")
cm<-caret::confusionMatrix(data_test_prediction,as.factor(data_test_cluster_labeled$activity),mode='everything')
cm
Confusion Matrix and Statistics

          Reference
Prediction  G RF RP  W
        G  45  9  0  2
        RF  9 58  6  0
        RP  1  2 33  1
        W   1  0  0 32

Overall Statistics
                                          
               Accuracy : 0.8442          
                 95% CI : (0.7862, 0.8916)
    No Information Rate : 0.3467          
    P-Value [Acc > NIR] : < 2.2e-16       
                                          
                  Kappa : 0.786           
                                          
 Mcnemar's Test P-Value : NA              

Statistics by Class:

                     Class: G Class: RF Class: RP Class: W
Sensitivity            0.8036    0.8406    0.8462   0.9143
Specificity            0.9231    0.8846    0.9750   0.9939
Pos Pred Value         0.8036    0.7945    0.8919   0.9697
Neg Pred Value         0.9231    0.9127    0.9630   0.9819
Precision              0.8036    0.7945    0.8919   0.9697
Recall                 0.8036    0.8406    0.8462   0.9143
F1                     0.8036    0.8169    0.8684   0.9412
Prevalence             0.2814    0.3467    0.1960   0.1759
Detection Rate         0.2261    0.2915    0.1658   0.1608
Detection Prevalence   0.2814    0.3668    0.1859   0.1658
Balanced Accuracy      0.8633    0.8626    0.9106   0.9541
test_data<-cm$byClass %>% as.data.frame() %>% tibble::rownames_to_column(var='activity') %>%
  select(activity,Precision, Recall, F1) %>% reshape2::melt()

predictions_cv_cm_plot+
  labs(title=paste0(selected_model$method," for activity prediction [CV+Test] Report"),
                    subtitle="[3x5 CV]")+
    ylim(0.6,1)+
  geom_point(data=test_data,aes(x=activity,y=value),fill="darkgreen",color="white",shape=23,size=2)+
    theme(legend.position = "none") 

Metricas Macro y Micro



macro_metrics<-(apply(cm$byClass,2,sum)/4) [c(5,6,7)]

s=table(data_test_prediction,as.factor(data_test_cluster_labeled$activity))

Precision_micro <-  (sum(diag(s)) / sum(apply(s,1, sum)))
Recall_micro <- (sum(diag(s)) / sum(apply(s,2, sum)))

micro_metrics <- c( 
  (sum(diag(s)) / sum(apply(s,1, sum))),
 (sum(diag(s)) / sum(apply(s,2, sum))),
 2 * ((Precision_micro*Recall_micro)/(Precision_micro+Recall_micro)))

mm<-cbind(c(macro_metrics),c(micro_metrics))


library(gridExtra)
data.frame(macro=mm[,1],micro=mm[,2])
apply(cm$table,2,sum)
 G RF RP  W 
56 69 39 35 

Matriz de confusion (plot)

len_data_test_prediction = length(data_test_prediction)
reshape2::melt(table(data_test_prediction,as.factor(data_test_cluster_labeled$activity))) %>% mutate(value=(value)) %>%
  ggplot(aes(x=data_test_prediction,y=Var2))+
  geom_tile(aes(fill=value), colour = "white") + 
   geom_text(aes(label = sprintf("%1.2f", value)), color='black',vjust = 1)+
  scale_fill_gradient(low = "white", high = "red")+
  xlab(" Predicted Activity ")+ylab(" Actual Activity")+
  theme_bw()+ theme(legend.position = "none")


#ggsave(filename = "/home/harpo/Dropbox/ongoing-work/publicaciones/CAI2019-JAIIO/confusion-matrix.png",width = 8, height = 4)

G vs RF



GvsRFmatrix <-
  cbind(data_test_cluster_labeled, predictions = data_test_prediction) %>% select(activity, predictions) %>% filter(activity %in% c("G", "RF")) %>% filter(predictions %in% c("G", "RF"))

GvsRF_recall <-
  caret::confusionMatrix(
    factor(GvsRFmatrix$predictions),
    factor(GvsRFmatrix$activity),
    mode = 'everything',
    positive = "G"
  )$byClass %>% as.data.frame() %>% tibble::rownames_to_column(var = "metric") %>% filter(metric == "Recall" | metric == "Specificity") %>% mutate(activities="GvsRF")

GvsRF_recall

RF vs RP


RFvsRPmatrix <-
  cbind(data_test_cluster_labeled, predictions = data_test_prediction) %>% select(activity, predictions) %>% filter(activity %in% c("RF", "RP")) %>% filter(predictions %in% c("RF", "RP"))

RFvsRP_recall <-
  caret::confusionMatrix(
    factor(RFvsRPmatrix$predictions),
    factor(RFvsRPmatrix$activity),
    mode = 'everything',
    positive = "RF"
  )$byClass %>% as.data.frame() %>% tibble::rownames_to_column(var = "metric") %>% filter(metric == "Recall" | metric == "Specificity") %>% mutate(activities="RFvsRP")

RFvsRP_recall

G vs RP


GvsRPmatrix <-
  cbind(data_test_cluster_labeled, predictions = data_test_prediction) %>% select(activity, predictions) %>% filter(activity %in% c("G", "RP")) %>% filter(predictions %in% c("G", "RP"))

GvsRP_recall <-
  caret::confusionMatrix(
    factor(GvsRPmatrix$predictions),
    factor(GvsRPmatrix$activity),
    mode = 'everything',
    positive = "G"
  )$byClass %>% as.data.frame() %>% tibble::rownames_to_column(var = "metric") %>% filter(metric == "Recall" | metric == "Specificity") %>% mutate(activities="GvsRP")

GvsRP_recall

Session Info

sessionInfo()
R version 3.6.3 (2020-02-29)
Platform: x86_64-pc-linux-gnu (64-bit)
Running under: Ubuntu 18.04.5 LTS

Matrix products: default
BLAS:   /usr/lib/x86_64-linux-gnu/blas/libblas.so.3.7.1
LAPACK: /usr/lib/x86_64-linux-gnu/lapack/liblapack.so.3.7.1

locale:
 [1] LC_CTYPE=es_AR.UTF-8       LC_NUMERIC=C               LC_TIME=es_AR.UTF-8        LC_COLLATE=es_AR.UTF-8    
 [5] LC_MONETARY=es_AR.UTF-8    LC_MESSAGES=es_AR.UTF-8    LC_PAPER=es_AR.UTF-8       LC_NAME=C                 
 [9] LC_ADDRESS=C               LC_TELEPHONE=C             LC_MEASUREMENT=es_AR.UTF-8 LC_IDENTIFICATION=C       

attached base packages:
[1] parallel  stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
 [1] d3heatmap_0.6.1.3 gridExtra_2.3     doMC_1.3.6        iterators_1.0.12  foreach_1.4.7     caret_6.0-86      lattice_0.20-41  
 [8] plotly_4.9.2      ggplot2_3.2.1     lubridate_1.7.4   dplyr_1.0.0       readr_1.3.1      

loaded via a namespace (and not attached):
 [1] Rcpp_1.0.5           tidyr_1.0.0          png_0.1-7            class_7.3-17         packrat_0.5.0        assertthat_0.2.1    
 [7] digest_0.6.25        ipred_0.9-9          R6_2.4.0             plyr_1.8.4           ggdark_0.2.1         stats4_3.6.3        
[13] e1071_1.7-2          evaluate_0.14        httr_1.4.2           pillar_1.4.3         rlang_0.4.7          lazyeval_0.2.2      
[19] rstudioapi_0.11      data.table_1.12.4    rpart_4.1-15         Matrix_1.2-18        rmarkdown_2.3        labeling_0.3        
[25] splines_3.6.3        gower_0.2.1          stringr_1.4.0        htmlwidgets_1.5.1    munsell_0.5.0        tinytex_0.16        
[31] compiler_3.6.3       xfun_0.10            base64enc_0.1-3      pkgconfig_2.0.3      htmltools_0.5.0      nnet_7.3-14         
[37] tidyselect_1.1.0     tibble_3.0.1         prodlim_2018.04.18   codetools_0.2-16     randomForest_4.6-14  fansi_0.4.0         
[43] viridisLite_0.3.0    crayon_1.3.4         withr_2.2.0          MASS_7.3-52          recipes_0.1.10       ModelMetrics_1.2.2.2
[49] grid_3.6.3           nlme_3.1-149         jsonlite_1.7.1       gtable_0.3.0         lifecycle_0.2.0      magrittr_1.5        
[55] pROC_1.16.2          scales_1.0.0         cli_2.0.2            stringi_1.4.3        reshape2_1.4.3       timeDate_3043.102   
[61] ellipsis_0.3.1       generics_0.0.2       vctrs_0.3.2          lava_1.6.6           RColorBrewer_1.1-2   tools_3.6.3         
[67] glue_1.4.1           purrr_0.3.3          hms_0.5.3            yaml_2.2.0           survival_3.1-12      colorspace_1.4-1    
[73] knitr_1.25          
LS0tCnRpdGxlOiAiRGF0YXNldCBHYW5hZG8gQ2FwcmlubyAyOC85LzIwIgpvdXRwdXQ6IAogIGh0bWxfbm90ZWJvb2s6IAogICAgY29kZV9mb2xkaW5nOiBzaG93Ci0tLQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpsaWJyYXJ5KHJlYWRyKQpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KGx1YnJpZGF0ZSkKbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KHBsb3RseSkKI2xpYnJhcnkoZ2dtYXApCiNsaWJyYXJ5KGNhdGJvb3N0KQoKcmFkMmRlZyA8LSBmdW5jdGlvbihyYWQpIHsocmFkICogMTgwKSAvIChwaSl9CmRlZzJyYWQgPC0gZnVuY3Rpb24oZGVnKSB7KGRlZyAqIHBpKSAvICgxODApfQoKCiMgRGF0YXNldHMgb3JpZ2luYWxlcwojY2Fwcmlub19ob3VyczwtcmVhZC5jc3YoImRhdGEvY2Fwcmlub19ob3Vycy5jc3YuZ3oiLGhlYWRlciA9IEYpCiNjYXByaW5vX2ltdV9neXJvczwtcmVhZC5jc3YoImRhdGEvY2Fwcmlub19pbXVfZ3lyby5jc3YuZ3oiLCBoZWFkZXI9RikKI2NhcHJpbm9faW11X2FjYzwtcmVhZC5jc3YoImRhdGEvY2Fwcmlub19pbXVfYWNjLmNzdi5neiIsIGhlYWRlcj1GKQojY2Fwcmlub19ncHM8LXJlYWQuY3N2KCJkYXRhL2NhcHJpbm9fZ3BzLmNzdiIsaGVhZGVyPUYpCgojIERhdGFzZXRzIG51ZXZvcwpjYXByaW5vX2hvdXJzPC1yZWFkLmNzdigiZGF0YS9jYXByaW5vX2hvdXJzLmNzdi5neiIsaGVhZGVyID0gRikKY2Fwcmlub19pbXVfZ3lyb3M8LXJlYWQuY3N2KCJkYXRhL2NhcHJpbm9faW11X2d5cm8uY3N2Lmd6IiwgaGVhZGVyPUYpCmNhcHJpbm9faW11X2FjYzwtcmVhZC5jc3YoImRhdGEvY2Fwcmlub19pbXVfYWNjLmNzdi5neiIsIGhlYWRlcj1GKQpjYXByaW5vX2dwczwtcmVhZC5jc3YoImRhdGEvY2Fwcmlub19ncHNfZGlzdGFuY2lhLmNzdiIsaGVhZGVyPUYpICNJTkNMVVlFIFVOQSBTRVhUQSBDT0xVTU5BIENPTiBsYSBkaXN0YW5jaWEgZXVjbGlkZWEKY2Fwcmlub19ncHM8LWNhcHJpbm9fZ3BzICU+JSBzZWxlY3QoLVY3KQoKY2Fwcmlub19pbXVfYWNjICU+JSBucm93KCkKY2Fwcmlub19pbXVfZ3lyb3MgJT4lIG5yb3coKQpjYXByaW5vX2dwcyAlPiUgbnJvdygpCmNhcHJpbm9faG91cnMgJT4lIG5yb3coKQoKCmBgYAoKCiMjIyBEYXRvcyBkZSBsb3MgYXJjaGl2b3MgCiogY2Fwcmlub19pbXVfYWNjLmNzdiwgNCBjb2x1bW5hczogdGllbXBvIFVUQyBlbiBtaWNyb3NlZ3VuZG9zLAphY2VsZXJhY2nDs24gWCBlbiBtL3PCsiwgYWNlbGVyYWNpw7NuIFkgZW4gbS9zwrIsIGFjZWxlcmFjacOzbiwgWiBlbiBtL3PCsi4KTXVlc3RyZWFkbyBhIDIwMCBtdWVzdHJhcyBwb3Igc2VndW5kby4gRmlsdHJhZG9zCgoqIGNhcHJpbm9faW11X2d5cm8uY3N2LCA0IGNvbHVtbmFzOiB0aWVtcG8gVVRDIGVuIG1pY3Jvc2VndW5kb3MsCnZlbG9jaWRhZCBhbmd1bGFyIFggZW4gcmFkL3MsIHZlbG9jaWRhZCBhbmd1bGFyIFkgZW4gcmFkL3MsIHZlbG9jaWRhZAphbmd1bGFyIFogZW4gcmFkL3MuIE11ZXN0cmVhZG8gYSAyMDAgbXVlc3RyYXMgcG9yIHNlZ3VuZG8uIEZpbHRyYWRvcwoKKiBjYXByaW5vX2dwc19kaXN0YW5jaWEuY3N2LCA0IGNvbHVtbmFzOiB0aWVtcG8gVVRDIGVuIG1pY3Jvc2VndW5kb3MsIGxhdGl0dWQgZW4KcmFkaWFuZXMsIGxvbmdpdHVkIGVuIHJhZGlhbmVzLCBhbHRpdHVkIGVuIG1ldHJvcy4gTXVlc3RyZWFkbyBhIDUKbXVlc3RyYXMgcG9yIHNlZ3VuZG8gU2UgYWdyZWdhIGFkZW1hcyB1bmEgY29sdW1uYSBjb24gbGEgZGlzdGFuY2lhIGV1Y2xpZGVhbi4KCiogY2Fwcmlub19ob3Vycy5jc3Y6IHRpZW1wbyBlbiBob3JhcywgbWludXRvcyB5IHNlZ3VuZG9zLiBNdWVzdHJlYWRvIGEKNSBtdWVzdHJhcyBwb3Igc2VndW5kby4gTGEgcHJpbWVyYSBob3JhIGRlIGVzdGUgYXJjaGl2byBjb2luY2lkZSBjb24KZWwgcHJpbWVyIHZhbG9yIGRlIHRpZW1wbyBVVEMgZGVsIGFyY2hpdm8gY2Fwcmlub19ncHMuY3N2LgoKCgojIyBDb25zdHJ1Y2Npb24gZGVsIERhdGFzZXQKCmBgYHtyfQpjYXByaW5vX2RhdGE8LWNiaW5kKGNhcHJpbm9faW11X2FjYyxjYXByaW5vX2ltdV9neXJvcyAlPiUgc2VsZWN0KC1WMSkpCgpjYXByaW5vX2RhdGE8LWNiaW5kKGNhcHJpbm9fZGF0YVtzZXEoMixucm93KGNhcHJpbm9fZGF0YSksMiksXSAlPiUgaGVhZChucm93KGNhcHJpbm9fZ3BzKS0xKSwgIGNhcHJpbm9fZ3BzWzI6bnJvdyhjYXByaW5vX2dwcyksXSApIApuYW1lcyhjYXByaW5vX2RhdGEpPC1jKCJpbXVfdHMiLAogICAgICAgICAgICAgICAgICAgICAgICJhY2NYIiwKICAgICAgICAgICAgICAgICAgICAgICAiYWNjWSIsCiAgICAgICAgICAgICAgICAgICAgICAgImFjY1oiLAogICAgICAgICAgICAgICAgICAgICAgICJneXJvWCIsCiAgICAgICAgICAgICAgICAgICAgICAgImd5cm9ZIiwKICAgICAgICAgICAgICAgICAgICAgICAiZ3lyb1oiLAogICAgICAgICAgICAgICAgICAgICAgICJncHNfaG91ciIsCiAgICAgICAgICAgICAgICAgICAgICAgImdwc190cyIsCiAgICAgICAgICAgICAgICAgICAgICAgImxhdCIsCiAgICAgICAgICAgICAgICAgICAgICAgImxvbmciLAogICAgICAgICAgICAgICAgICAgICAgICJhbHQiLAogICAgICAgICAgICAgICAgICAgICAgICJkaXN0YW5jZSIgCiAgICAgICAgICAgICAgICAgICAgICAgICkKI2NhcHJpbm9fZGF0YSAlPiUgbXV0YXRlKGRpZmY9aW11X3RzLWdwc190cykgJT4lIHNlbGVjdChkaWZmKQpjYXByaW5vX2RhdGE8LWNhcHJpbm9fZGF0YSAlPiUgbXV0YXRlKGFjdGl2aXR5PWlmZWxzZShobXMoZ3BzX2hvdXIpPmhtcygiODo1NjowMCIpICYgaG1zKGdwc19ob3VyKTxobXMoIjEwOjM4OjAwIiksIlJQIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZmVsc2UoaG1zKGdwc19ob3VyKT49aG1zKCIxMDozODowMCIpICYgaG1zKGdwc19ob3VyKTxobXMoIjExOjM4OjAwIiksIlciLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWZlbHNlKGhtcyhncHNfaG91cik+PWhtcygiMTE6Mzg6MDAiKSAmIGhtcyhncHNfaG91cik8aG1zKCIxMjowNTowMCIpLCJHIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmZWxzZShobXMoZ3BzX2hvdXIpPj1obXMoIjEyOjA1OjAwIikgJiBobXMoZ3BzX2hvdXIpPGhtcygiMTU6NDg6MDAiKSwiUkYiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWZlbHNlKGhtcyhncHNfaG91cik+PWhtcygiMTc6MzA6MDAiKSAmIGhtcyhncHNfaG91cik8aG1zKCIyMDozMzowMCIpLCJHIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmZWxzZShobXMoZ3BzX2hvdXIpPj1obXMoIjIwOjUwOjAwIikgJiBobXMoZ3BzX2hvdXIpPGhtcygiMjE6MzE6MDAiKSwiVyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZmVsc2UoaG1zKGdwc19ob3VyKT49aG1zKCIyMTozMzowMCIpICYgaG1zKGdwc19ob3VyKTxobXMoIjIxOjUwOjAwIiksIlJQIixOQSkKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICkpKSkpCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICApCgpjYXByaW5vX2RhdGEgPC1jYXByaW5vX2RhdGEgJT4lIG11dGF0ZShpZD1jdXQoYXMuUE9TSVhjdChobXMoY2Fwcmlub19kYXRhJGdwc19ob3VyKSxvcmlnaW49IjE5NjAtMDEtMDEiLHR6PSJHTVQiKSxicmVha3M9IjEgbWluIikpIApjYXByaW5vX2RhdGEgJT4lIGhlYWQoMTApCmBgYAogIApgYGB7ciBldmFsPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQpzYW1wbGVfcmVzdWx0c190ZXN0PC1kYXRhLmZyYW1lKCkKZm9yIChpIGluIHNlcSgxLDMwKSl7CiAgbWludXRlX3NsaWNlPC1hcy52ZWN0b3IodChjYXByaW5vX2RhdGEgJT4lIGdyb3VwX2J5KGlkKSAlPiUgc3VtbWFyaXNlKG49bigpKSAlPiUgc2VsZWN0KGlkKSkpCiAgdHJhaW5pZHg8LXNhbXBsZSgxOmxlbmd0aChtaW51dGVfc2xpY2UpLChsZW5ndGgobWludXRlX3NsaWNlKSo3MCkvMTAwLjApCiAgZGF0YV90cmFpbjwtY2Fwcmlub19kYXRhICU+JSBmaWx0ZXIoIGlkICVpbiUgbWludXRlX3NsaWNlW3RyYWluaWR4XSkKICBkYXRhX3Rlc3Q8LWNhcHJpbm9fZGF0YSAlPiUgZmlsdGVyKCBpZCAlaW4lIChtaW51dGVfc2xpY2VbLXRyYWluaWR4XSkpCiAgZGF0YV90cmFpbjwtZGF0YV90cmFpbiAlPiUgc2VsZWN0ICgtaW11X3RzLC1ncHNfaG91ciwtaWQsLWxvbmcsLWxhdCwtYWx0LCAtZ3BzX3RzLC1hY3Rpdml0eSkKICBkYXRhX3Rlc3Q8LWRhdGFfdGVzdCAlPiUgc2VsZWN0ICgtaW11X3RzLC1ncHNfaG91ciwtaWQsLWxvbmcsLWxhdCwtYWx0LCAtZ3BzX3RzLC1hY3Rpdml0eSkKIAogIHJlc3VsdHNfdHJhaW48LWRhdGFfdHJhaW4gJT4lIHNraW1yOjpza2ltKCkgJT4lIHNraW1yOjp5YW5rKCJudW1lcmljIikgICU+JSBzZWxlY3QobWVhbixzZCkgJT4lIGFzLmRhdGEuZnJhbWUoKQogIHJlc3VsdHNfdHJhaW4kcmVwZXRpdGlvbjwtaQogIHJlc3VsdHNfdHJhaW4kdmFyaWFibGVfbjwtbmFtZXMoZGF0YV90cmFpbikKICBzYW1wbGVfcmVzdWx0c190cmFpbjwtcmJpbmQoc2FtcGxlX3Jlc3VsdHNfdHJhaW4scmVzdWx0c190cmFpbikKICAKICAKICByZXN1bHRzX3Rlc3Q8LWRhdGFfdGVzdCAlPiUgc2tpbXI6OnNraW0oKSAlPiUgc2tpbXI6OnlhbmsoIm51bWVyaWMiKSAgJT4lIHNlbGVjdChtZWFuLHNkKSAlPiUgYXMuZGF0YS5mcmFtZSgpCiAgcmVzdWx0c190ZXN0JHJlcGV0aXRpb248LWkKICByZXN1bHRzX3Rlc3QkdmFyaWFibGVfbjwtbmFtZXMoZGF0YV90cmFpbikKICBzYW1wbGVfcmVzdWx0c190ZXN0PC1yYmluZChzYW1wbGVfcmVzdWx0c190ZXN0LHJlc3VsdHNfdGVzdCkKICAKfQpzZXRyYWluPC1zYW1wbGVfcmVzdWx0c190cmFpbiAlPiUgZ3JvdXBfYnkodmFyaWFibGVfbikgJT4lIHN1bW1hcmlzZShncmFuZF9tZWFuPW1lYW4obWVhbiksc2U9c2QobWVhbikpCgpzZXR0ZXN0PC1zYW1wbGVfcmVzdWx0c190ZXN0ICU+JSBncm91cF9ieSh2YXJpYWJsZV9uKSAlPiUgc3VtbWFyaXNlKGdyYW5kX21lYW5fdD1tZWFuKG1lYW4pLHNlX3Q9c2QobWVhbikpCgoKY2JpbmQoc2V0cmFpbixzZXR0ZXN0KQoKc2FtcGxlX3Jlc3VsdHNfdHJhaW4gJT4lIGdncGxvdCgpKwogIGdlb21faGlzdG9ncmFtKGFlcyh4PXNkKSkrCiAgZmFjZXRfd3JhcCh+dmFyaWFibGVfbixzY2FsZXMgPSAnZnJlZScpCmBgYAoKIyBUb21hbW9zIG11ZXN0cmFzIGFsZWF0b3JpYXMgZGUgMSBtaW51dG8gZGUgZHVyYWNpb24gcGFyYSBzb2JyZSBsYXMgY3VhbGVzIGRlc3B1ZXMgcXVlcmVtb3MgcHJlZGVjaXIgZWwgY29tcG9ydGFtaWVudG8KCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CiNyZWFkcjo6d3JpdGVfY3N2KGNhcHJpbm9fZGF0YSwiZGF0YS9jYXByaW5vX2RhdGFfc2xpY2VkX2Rpc3RhbmNlLmNzdiIpCiNzZXQuc2VlZCgyMTA5MjAxOSkKI3NldC5zZWVkKDIxMDkyMDIyKQojc2V0LnNlZWQoMDcwOTIwMjApCiNzZXQuc2VlZCgwMTA5MjAyMCkKbWludXRlX3NsaWNlPC1hcy52ZWN0b3IodChjYXByaW5vX2RhdGEgJT4lIGdyb3VwX2J5KGlkKSAlPiUgc3VtbWFyaXNlKG49bigpKSAlPiUgc2VsZWN0KGlkKSkpCnRyYWluaWR4PC1zYW1wbGUoMTpsZW5ndGgobWludXRlX3NsaWNlKSwobGVuZ3RoKG1pbnV0ZV9zbGljZSkqNzApLzEwMC4wKQpkYXRhX3RyYWluPC1jYXByaW5vX2RhdGEgJT4lIGZpbHRlciggaWQgJWluJSBtaW51dGVfc2xpY2VbdHJhaW5pZHhdKQpkYXRhX3Rlc3Q8LWNhcHJpbm9fZGF0YSAlPiUgZmlsdGVyKCBpZCAlaW4lIChtaW51dGVfc2xpY2VbLXRyYWluaWR4XSkpCmRhdGFfdHJhaW4lPiUgZ3JvdXBfYnkoaWQpICU+JSBzdW1tYXJpc2Uobj1uKCkpCmRhdGFfdGVzdCU+JSBncm91cF9ieShpZCkgJT4lIHN1bW1hcmlzZShuPW4oKSkKZGF0YV90cmFpbiAlPiUgZmlsdGVyKCFpcy5uYShhY3Rpdml0eSkpICU+JSBncm91cF9ieShhY3Rpdml0eSkgJT4lIHN1bW1hcmlzZShuPW4oKSkgCgpgYGAKCiMgQXBsaWNhbW9zIEJhZy1vZi1mZWF0dXJlcy4KIyMgcHJlc3VwdWVzdG8KCiBDb25zaWRlcmFtb3MgY2FkYSBtdWVzdHJhIGNvbW8gdW4gdmVjdG9yIGRlIDUgZGltZW5zaW9uZXMuIExhIGlkZWEgZXMgYXBsaWNhciB1biBhbGdvcml0bW8gZGUgY2x1c3RlcmluZyBzb2JyZSBlc3RvcyB2ZWN0b3JlcyAoYWNjWCxhY2NZLGFjY1osR3lyb1gsZ3lyb1kpLiBVbmEgdmV6IGVuY29udHJhZG8gbG9zIGNlbnRyb2lkZXMsIHBhcmEgY2FkYSBtdWVzdHJhIGRlIDEgc2UgY29uc3RydXllIHVuIGhpc3RvZ3JhbWEgZGUgbG9zIHZlY3RvcmVzIHF1ZSBoYW4gY2FpZG8gZW4gY2FkYSB1bm8gZGUgZXN0b3MgY2x1c3RlcnMuIEVzdGUgaGlzdG9ncmFtYSBlcyBlbCBxdWUgc2UgdXRpbGl6YSBwYXJhIGVudHJlZ2FyIGVsIGFsZ29yaXRtby4KCkxhIGhpcG90ZXNpcyBkZSBhcGxpY2FyIGVzdGUgbWV0b2RvIGVzIHF1ZSBhcXVlbGxhcyBtdWVzdHJhcyBxdWUgcGVydGVuZWNlbiBhIHVuYSBtaXNtYSBhY3RpdmlkYWQgdGVuZHJhbiB2YWxvcmVzIHNpbWlsYXJlcy4gTG9zIGN1YWxlcyBoYW4gc2lkbyByZXByZXNlbnRhZG8gbWVkaWFudGVzIGVzdG9zIGhpc3RvZ3JhbWFzLiAKCiMjIEFwbGljYW1vcyBrLW1lYW5zIGNvbiA5MCBjZW50cm9pZGVzIHNvYnJlIGVsIGNvbmp1bnRvIGRlIGVudHJlbmFtaWVudG8uIApTZSB1dGlsaXphbiB0b2RvcyBsb3Mgc2Vuc29yZXMgeSBsYSBkaXN0YW5jaWEuCgpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpjZW50cm9pZHM8LTkwCmNsdXN0ZXJfZmVhdHVyZXM8LWMoJ2FjY1gnLCdhY2NZJywnYWNjWicsJ2d5cm9YJywnZ3lyb1knLCdneXJvWicsJ2Rpc3RhbmNlJykKCiMjIFNhbXBsaW5nIGNvbnNpZGVyaW5nIHRpbWUgc2xpY2VzIAogIG1pbnV0ZV9zbGljZSA8LQogICAgYXMudmVjdG9yKHQoCiAgICAgIGNhcHJpbm9fZGF0YSAlPiUgZ3JvdXBfYnkoaWQpICU+JSBzdW1tYXJpc2UobiA9IG4oKSkgJT4lIHNlbGVjdChpZCkKICAgICkpCiAgdHJhaW5pZHggPC0KICAgIHNhbXBsZSgxOmxlbmd0aChtaW51dGVfc2xpY2UpLCAobGVuZ3RoKG1pbnV0ZV9zbGljZSkgKiA3MCkgLyAxMDAuMCkKICBkYXRhX3RyYWluIDwtIGNhcHJpbm9fZGF0YSAlPiUgZmlsdGVyKGlkICVpbiUgbWludXRlX3NsaWNlW3RyYWluaWR4XSkKICBkYXRhX3Rlc3QgPC0KICAgIGNhcHJpbm9fZGF0YSAlPiUgZmlsdGVyKGlkICVpbiUgKG1pbnV0ZV9zbGljZVstdHJhaW5pZHhdKSkKICAKICAKICAjIEttZWFucyBvbiB0cmFpbnNldAogIGNsdXN0ZXJfcmVzdWx0cyA8LQogICAga21lYW5zKAogICAgICB4ID0gZGF0YV90cmFpbiAlPiUgc2VsZWN0KGNsdXN0ZXJfZmVhdHVyZXMpLAogICAgICBjZW50ZXJzID0gY2VudHJvaWRzLAogICAgICBuc3RhcnQgPSAxMAogICAgKQogIGRhdGFfdHJhaW5fY2x1c3RlciA8LQogICAgY2JpbmQoZGF0YV90cmFpbiwgY2x1c3RlciA9IGFzLmludGVnZXIoY2x1c3Rlcl9yZXN1bHRzJGNsdXN0ZXIpKSAlPiUgZ3JvdXBfYnkoYWN0aXZpdHksIGNsdXN0ZXIpCiAgCiAgY2x1c3Rlcl9yZXN1bHRzPC1jbHVzdGVyX3Jlc3VsdHMkY2VudGVycyAlPiUgCiAgICBhcy5kYXRhLmZyYW1lKCkgJT4lIAogICAgdGliYmxlOjpyb3duYW1lc190b19jb2x1bW4odmFyID0gImNsdXN0ZXIiKQpgYGAKIyMjIEFwbGljYW1vcyBLTk4gY29uIEs9MyBlbiBlbCBjb25qdW50byBkZSBlbnRyZW5hbWllbnRvLgpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQogICMgS05OIHVzaW5nIGNlbnRyb2lkcyBmcm9tIGttZWFucyBmb3IgdGVzdHNldAogIAogIGNsdXN0ZXJfcmVzdWx0c190ZXN0IDwtY2xhc3M6Omtubih0cmFpbj1jbHVzdGVyX3Jlc3VsdHMgJT4lIHNlbGVjdCgtY2x1c3RlciksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRlc3Q9ZGF0YV90ZXN0ICU+JSBzZWxlY3QoY2x1c3Rlcl9mZWF0dXJlcykKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLGNsID0gYXMuaW50ZWdlcihjbHVzdGVyX3Jlc3VsdHMkY2x1c3RlciksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGs9MyApCiAgZGF0YV90ZXN0X2NsdXN0ZXIgPC0KICAgIGNiaW5kKGRhdGFfdGVzdCwgY2x1c3RlciA9IGFzLmludGVnZXIoY2x1c3Rlcl9yZXN1bHRzX3Rlc3QpKSAlPiUgZ3JvdXBfYnkoYWN0aXZpdHksIGNsdXN0ZXIpCiAgCiAgIyBDcmVhdGUgaGlzdG9ncmFtcyBmb3IgYmFnLW9mLWZlYXR1cmVzIGFuZCBsYWJlbCBlYWNoIG5ldyBkYXRhcG9pbnQgCiAgIyMgRm9yIHRyYWluc2V0CiAgZGF0YV90cmFpbl9jbHVzdGVyX2xhYmVsZWQgPC0gZGF0YV90cmFpbl9jbHVzdGVyICAgJT4lCiAgICBncm91cF9ieShpZCwgY2x1c3RlcikgJT4lIHN1bW1hcmlzZShuID0gbigpKSAlPiUKICAgIHJlc2hhcGUyOjpkY2FzdChpZCB+IGNsdXN0ZXIsIGZpbGwgPSAwKSAlPiUKICAgIGlubmVyX2pvaW4oZGF0YV90cmFpbl9jbHVzdGVyICU+JSB1bmdyb3VwKCkgJT4lCiAgICAgICAgICAgICAgICAgc2VsZWN0KGlkLCBhY3Rpdml0eSkgICU+JQogICAgICAgICAgICAgICAgIHVuaXF1ZSgpICAsCiAgICAgICAgICAgICAgIGJ5ID0gImlkIikgJT4lIGZpbHRlcighaXMubmEoYWN0aXZpdHkpKQogIAogIGRhdGFfY2x1c3Rlcl9oaXN0b2dyYW08LWRhdGEuZnJhbWUobWF0cml4KDBMLG5yb3c9bnJvdyhkYXRhX3RyYWluX2NsdXN0ZXJfbGFiZWxlZCksbmNvbD05MCkpCiAgbmFtZXMoZGF0YV9jbHVzdGVyX2hpc3RvZ3JhbSlbMTo5MF08LXNlcSgxLDkwKQogIGRhdGFfY2x1c3Rlcl9oaXN0b2dyYW1bbmFtZXMoZGF0YV90cmFpbl9jbHVzdGVyX2xhYmVsZWQpXTwtZGF0YV90cmFpbl9jbHVzdGVyX2xhYmVsZWQKICBkYXRhX3RyYWluX2NsdXN0ZXJfbGFiZWxlZDwtZGF0YV9jbHVzdGVyX2hpc3RvZ3JhbQogIAogICMjIEZvciB0ZXN0c2V0CiAgZGF0YV90ZXN0X2NsdXN0ZXJfbGFiZWxlZCA8LSBkYXRhX3Rlc3RfY2x1c3RlciAgICU+JSAKICAgIGdyb3VwX2J5KGlkLCBjbHVzdGVyKSAlPiUgc3VtbWFyaXNlKG4gPSBuKCkpICU+JQogICAgcmVzaGFwZTI6OmRjYXN0KGlkIH4gY2x1c3RlciwgZmlsbCA9IDApICU+JQogICAgaW5uZXJfam9pbihkYXRhX3Rlc3RfY2x1c3RlciAlPiUgIHVuZ3JvdXAoKSAlPiUKICAgICAgICAgICAgICAgICBzZWxlY3QoaWQsIGFjdGl2aXR5KSAgJT4lCiAgICAgICAgICAgICAgICAgdW5pcXVlKCkgLAogICAgICAgICAgICAgICBieSA9ICJpZCIpICAlPiUgZmlsdGVyKCFpcy5uYShhY3Rpdml0eSkpCiAgCiAgZGF0YV9jbHVzdGVyX2hpc3RvZ3JhbTwtZGF0YS5mcmFtZShtYXRyaXgoMEwsbnJvdz1ucm93KGRhdGFfdGVzdF9jbHVzdGVyX2xhYmVsZWQpLG5jb2w9OTApKQogIG5hbWVzKGRhdGFfY2x1c3Rlcl9oaXN0b2dyYW0pWzE6OTBdPC1zZXEoMSw5MCkKICBkYXRhX2NsdXN0ZXJfaGlzdG9ncmFtW25hbWVzKGRhdGFfdGVzdF9jbHVzdGVyX2xhYmVsZWQpXTwtZGF0YV90ZXN0X2NsdXN0ZXJfbGFiZWxlZAogIGRhdGFfdGVzdF9jbHVzdGVyX2xhYmVsZWQ8LWRhdGFfY2x1c3Rlcl9oaXN0b2dyYW0KYGBgCiMgVG90YWwgZGUgYWN0aXZpZGFkZXMgZW4gVHJhaW4KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KZGF0YV90cmFpbl9jbHVzdGVyX2xhYmVsZWQgJT4lIGdyb3VwX2J5KGFjdGl2aXR5KSAlPiUgc3VtbWFyaXNlKHRvdGFsPW4oKSkKYGBgCiMgVG90YWwgZGUgYWN0aXZpZGFkZXMgZW4gVGVzdApgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpkYXRhX3Rlc3RfY2x1c3Rlcl9sYWJlbGVkICU+JSBncm91cF9ieShhY3Rpdml0eSkgJT4lIHN1bW1hcmlzZSh0b3RhbD1uKCkpCmBgYAoKIyMgQXBsaWNhbW9zIDN4NSBDViBwYXJhIGVsZWdpciBsYSBtZWpvciB2ZXJzaW9uIGRlbCBtb2RlbG8KYGBge3IgaW5jbHVkZT1GQUxTRX0KbGlicmFyeShjYXJldCkKbGlicmFyeShkb01DKQpyZWdpc3RlckRvTUMoY29yZXM9NikKY3RybF9mYXN0IDwtIHRyYWluQ29udHJvbChtZXRob2Q9InJlcGVhdGVkY3YiLCAKICAgICAgICAgICAgICAgICAgICAgcmVwZWF0cz0zLAogICAgICAgICAgICAgICAgICAgICBudW1iZXI9MTAsIAogICAgICAgICAgICAgICAgICAgICByZXR1cm5SZXNhbXAgPSAnZmluYWwnLAogICAgICAgICAgICAgICAgICAgICBzYXZlUHJlZGljdGlvbnMgPSAnZmluYWwnLAogICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgdmVyYm9zZUl0ZXI9RiwKICAgICAgICAgICAgICAgICAgICAgI3N1bW1hcnlGdW5jdGlvbiA9IHByU3VtbWFyeSwKICAgICAgICAgICAgICAgICAgICAgY2xhc3NQcm9icz1UUlVFLAogICAgICAgICAgICAgICAgICAgICBhbGxvd1BhcmFsbGVsID0gVAogICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICApICAKYGBgCiMjIFByb2JhbW9zIFJhbmRvbSBGb3Jlc3QKYGBge3J9CnJmX2dyaWQgPC0gIGV4cGFuZC5ncmlkKC5tdHJ5PWMoNSkpIApyZkZpdCA8LSBjYXJldDo6dHJhaW4oCiAgICAgICAgICAgICAgIHg9IGRhdGFfdHJhaW5fY2x1c3Rlcl9sYWJlbGVkICU+JSBzZWxlY3QoLWlkLC1hY3Rpdml0eSksCiAgICAgICAgICAgICAgIHk9IGRhdGFfdHJhaW5fY2x1c3Rlcl9sYWJlbGVkJGFjdGl2aXR5LAogICAgICAgICAgICAgIAogICAgICAgICAgICAgICBtZXRob2QgPSAicmYiLAogIAogICAgICAgICAgICAgICAjdHVuZUxlbmd0aD01LCAKICAgICAgICAgICAgICAgdHVuZUdyaWQ9cmZfZ3JpZCwKICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgIHZlcmJvc2U9MiwKICAgICAgIAogICAgICAgICAgICAgICB0ckNvbnRyb2wgPSBjdHJsX2Zhc3QsCiAgICAgICAgICAgICAgIG50cmVlPTIwMCkKcmZGaXQkcmVzdWx0cwpgYGAKPCEtLSAjIyBQcm9iYW1vcyBjYXRib29zdCAtLT4KYGBge3IgZXZhbD1GQUxTRSwgaW5jbHVkZT1GQUxTRX0KY2F0Ym9vc3RGaXQgPC0gY2FyZXQ6OnRyYWluKAogICAgICAgICAgICAgICB4PSBkYXRhX3RyYWluX2NsdXN0ZXJfbGFiZWxlZCAlPiUgc2VsZWN0KC1pZCwtYWN0aXZpdHkpLAogICAgICAgICAgICAgICB5PSBkYXRhX3RyYWluX2NsdXN0ZXJfbGFiZWxlZCRhY3Rpdml0eSwKICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgbWV0aG9kID0gY2F0Ym9vc3QuY2FyZXQsCiAgCiAgICAgICAgICAgICAgIHR1bmVMZW5ndGg9NSwKICAgICAgICAgICAgICAgdmVyYm9zZT0yLAogICAgICAgCiAgICAgICAgICAgICAgIHRyQ29udHJvbCA9IGN0cmxfZmFzdCkKCmBgYAoKPCEtLSAjIyBQcm9iYW1vcyBnbG1uZXQgLS0+CmBgYHtyIGV2YWw9RkFMU0UsIGluY2x1ZGU9RkFMU0V9CmdsbW5ldEZpdCA8LSBjYXJldDo6dHJhaW4oCiAgICAgICAgICAgICAgIHg9IGRhdGFfdHJhaW5fY2x1c3Rlcl9sYWJlbGVkICU+JSBzZWxlY3QoLWlkLC1hY3Rpdml0eSksCiAgICAgICAgICAgICAgIHk9IGRhdGFfdHJhaW5fY2x1c3Rlcl9sYWJlbGVkJGFjdGl2aXR5LAogICAgICAgICAgICAgIAogICAgICAgICAgICAgICBtZXRob2QgPSAiZ2xtbmV0IiwKICAKICAgICAgICAgICAgICAgdHVuZUxlbmd0aD01LAogICAgICAgICAgICAgICB2ZXJib3NlPTIsCiAgICAgICAKICAgICAgICAgICAgICAgdHJDb250cm9sID0gY3RybF9mYXN0KQoKYGBgCjwhLS0gIyMgbW9kZWxvcyBvYnRlbmlkb3MgLS0+CmBgYHtyIGV2YWw9RkFMU0UsIGluY2x1ZGU9RkFMU0V9CnJmX2N2X3Jlc3VsdHM8LWNiaW5kKHJlc2FtcGxlPXJmRml0JHJlc2FtcGxlLG1vZGVsPSJyZiIpCmNhdGJvb3N0X2N2X3Jlc3VsdHM8LWNiaW5kKHJlc2FtcGxlPWNhdGJvb3N0Rml0JHJlc2FtcGxlLG1vZGVsPSJjYXRib29zdCIpCmdsbW5ldF9jdl9yZXN1bHRzPC1jYmluZChyZXNhbXBsZT1nbG1uZXRGaXQkcmVzYW1wbGUsbW9kZWw9ImdsbW5ldCIpCgpjdl9yZXN1bHRzPC1yYmluZChyZl9jdl9yZXN1bHRzLGNhdGJvb3N0X2N2X3Jlc3VsdHMsZ2xtbmV0X2N2X3Jlc3VsdHMpCgpjdl9yZXN1bHRzICU+JSBnZ3Bsb3QoKSsKICBnZW9tX2JveHBsb3QoYWVzKHg9bW9kZWwseT1yZXNhbXBsZS5BY2N1cmFjeSxmaWxsPW1vZGVsKSkrCiAgICBnZW9tX3BvaW50KGFlcyh4PW1vZGVsLHk9cmVzYW1wbGUuQWNjdXJhY3kpLGNvbG9yPSdyZWQnLGFscGhhPTAuNSkrCiAgZ2dkYXJrOjpkYXJrX3RoZW1lX2dyYXkoKSArCiAgeWxpbSgwLjcsIDEpKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIHZqdXN0ID0gMC41LCBoanVzdD0xKSkrCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSAKCm9uZXdheS50ZXN0KHJlc2FtcGxlLkFjY3VyYWN5fm1vZGVsLGRhdGE9Y3ZfcmVzdWx0cyAlPiUgc2VsZWN0KHJlc2FtcGxlLkFjY3VyYWN5LG1vZGVsKSkgJT4lIGJyb29tOjp0aWR5KCkKCmN2X3Jlc3VsdHMgJT4lIGdyb3VwX2J5KG1vZGVsKSAlPiUgc3VtbWFyaXNlKG1lYW49bWVhbihyZXNhbXBsZS5BY2N1cmFjeSksc2Q9c2QocmVzYW1wbGUuQWNjdXJhY3kpKQpgYGAKCgojIyBTZWxlY2Npb25hbW9zIGVsIG1vZGVsbwpgYGB7cn0Kc2VsZWN0ZWRfbW9kZWw8LXJmRml0CiNzYXZlKHJmRml0LGZpbGUgPSAiam91cm5hbC1wb2xhY28vcmZfbW9kZWxfam91cm5hbF9wb2xhY28yMDIwIikKI3dyaXRlX2NzdihjYXByaW5vX2RhdGEscGF0aD0iam91cm5hbC1wb2xhY28vY2Fwcmlub19kYXRhX3NsaWNlZC5jc3YiKQojd3JpdGVfY3N2KGRhdGFfdHJhaW5fY2x1c3Rlcl9sYWJlbGVkLHBhdGg9ImpvdXJuYWwtcG9sYWNvL3RyYWluc2V0LmNzdiIpCiN3cml0ZV9jc3YoZGF0YV90ZXN0X2NsdXN0ZXJfbGFiZWxlZCxwYXRoPSJqb3VybmFsLXBvbGFjby90ZXN0c2V0LmNzdiIpCgpgYGAKCiMgRXZhbHVhY2lvbiBkZSBsb3MgcmVzdWx0YWRvcyBkZSBDViAoQ1YgUmVwb3J0KQpgYGB7ciBmaWcuaGVpZ2h0PTMsIGZpZy53aWR0aD04LCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpwcmVkaWN0aW9uc19jdjwtc2VsZWN0ZWRfbW9kZWwkcHJlZCAlPiUgc2VsZWN0KFJlc2FtcGxlLHByZWQsb2JzKQoKcHJlZGljdGlvbnNfY3ZfY20gPC0gcHJlZGljdGlvbnNfY3YgJT4lIGdyb3VwX2J5KFJlc2FtcGxlKSAlPiUKICBncm91cF9tb2RpZnkoCiAgICB+IGFzLmRhdGEuZnJhbWUoCiAgICAgIGNhcmV0Ojpjb25mdXNpb25NYXRyaXgoCiAgICAgICAgZGF0YSA9IC54JHByZWQsCiAgICAgICAgcmVmZXJlbmNlID0gLngkb2JzLAogICAgICAgIG1vZGUgPSAnZXZlcnl0aGluZycKICAgICAgKSRieUNsYXNzCiAgICApICU+JQogICAgICBzZWxlY3QoUHJlY2lzaW9uLCBSZWNhbGwsIEYxLCkgJT4lIAogICAgICB0aWJibGU6OnJvd25hbWVzX3RvX2NvbHVtbih2YXIgPSAiYWN0aXZpdHkiKQogICkKCnByZWRpY3Rpb25zX2N2X3Jlc3VsdHM8LSBwcmVkaWN0aW9uc19jdl9jbSAlPiUgCiAgZ3JvdXBfYnkoYWN0aXZpdHkpICU+JSAKICBzdW1tYXJpc2UoCiAgICBwcmVjaXNpb25fbWVhbj1tZWFuKFByZWNpc2lvbiksCiAgICBwcmVjaXNpb25fc2U9c2QoUHJlY2lzaW9uKS9zcXJ0KHNlbGVjdGVkX21vZGVsJGNvbnRyb2wkbnVtYmVyKnNlbGVjdGVkX21vZGVsJGNvbnRyb2wkcmVwZWF0cyksCiAgICBwcmVjaXNpb25fY29uZl9pbmY9bWVhbihQcmVjaXNpb24pLShwcmVjaXNpb25fc2UqMS45NiksCiAgICBwcmVjaXNpb25fY29uZl9zdXA9bWVhbihQcmVjaXNpb24pKyhwcmVjaXNpb25fc2UqMS45NiksCiAgICByZWNhbGxfbWVhbj1tZWFuKFJlY2FsbCksCiAgICByZWNhbGxfc2U9c2QoUmVjYWxsKS9zcXJ0KHNlbGVjdGVkX21vZGVsJGNvbnRyb2wkbnVtYmVyKnNlbGVjdGVkX21vZGVsJGNvbnRyb2wkcmVwZWF0cyksCiAgICByZWNhbGxfY29uZl9pbmY9bWVhbihSZWNhbGwpLShyZWNhbGxfc2UqMS45NiksCiAgICByZWNhbGxfY29uZl9zdXA9bWVhbihSZWNhbGwpKyhyZWNhbGxfc2UqMS45NiksCiAgICBGMV9tZWFuPW1lYW4oRjEpLAogICAgRjFfc2U9c2QoRjEpL3NxcnQoc2VsZWN0ZWRfbW9kZWwkY29udHJvbCRudW1iZXIqc2VsZWN0ZWRfbW9kZWwkY29udHJvbCRyZXBlYXRzKSwKICAgIEYxX2NvbmZfaW5mPW1lYW4oRjEpLShGMV9zZSoxLjk2KSwKICAgIEYxX2NvbmZfc3VwPW1lYW4oRjEpKyhGMV9zZSoxLjk2KQoKKQpwcmVkaWN0aW9uc19jdl9jbV9wbG90PC1wcmVkaWN0aW9uc19jdl9jbSAlPiUgcmVzaGFwZTI6Om1lbHQoKSAlPiUKICBnZ3Bsb3QoKSsKICBmYWNldF93cmFwKH52YXJpYWJsZSkrCiAgeWxpbSgwLjYsMSkrCiAgZ2VvbV9ib3hwbG90KGFlcyh4PWFjdGl2aXR5LHk9dmFsdWUsZmlsbD1hY3Rpdml0eSkpKwogIGxhYnModGl0bGU9cGFzdGUwKHNlbGVjdGVkX21vZGVsJG1ldGhvZCwiIGZvciBhY3Rpdml0eSBwcmVkaWN0aW9uIFtDVl0gUmVwb3J0IiksCiAgICAgICAgICAgICAgICAgICAgc3VidGl0bGU9IlszeDUgQ1ZdIikrCiAgZ2dkYXJrOjpkYXJrX3RoZW1lX2dyYXkoKSsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCB2anVzdCA9IDAuNSwgaGp1c3Q9MSkpKwogICAgICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpIAogICAKcHJlZGljdGlvbnNfY3ZfY20KCnByZWRpY3Rpb25zX2N2X2NtX3Bsb3QKcHJlZGljdGlvbnNfY3ZfcmVzdWx0cwpgYGAKCmBgYHtyfQpwcmVkaWN0aW9uX2N2X2NvcjwtcHJlZGljdGlvbnNfY3ZfY20gJT4lIHNlbGVjdChhY3Rpdml0eSxSZWNhbGwsLVJlc2FtcGxlKSAlPiUgdGlkeXI6OnBpdm90X3dpZGVyKG5hbWVzX2Zyb20gPSBhY3Rpdml0eSwgdmFsdWVzX2Zyb20gPSBSZWNhbGwpICU+JSB1bmdyb3VwKCkgJT4lIHNlbGVjdCgtUmVzYW1wbGUpICU+JSBjb3IobWV0aG9kID0gInNwZWFybWFuIikKbGlicmFyeShkM2hlYXRtYXApCmQzaGVhdG1hcChwcmVkaWN0aW9uX2N2X2Nvcixjb2xvcnMgPSAiQmx1ZXMiLGNleFJvdyA9IDAuOCwgY2V4Q29sID0gMC44KQpgYGAKCmBgYHtyfQoKI3ByZWRpY3Rpb25zX2N2X3Jlc3VsdHNfZXJyb3IgICU+JSBzZWxlY3QoYWN0aXZpdHksU2Vuc2l0aXZpdHlfc2QsU3BlY2lmaWNpdHlfc2QsRjFfc2QsQmFsYW5jZWRBY2N1cmFjeV9zZCkKI3ByZWRpY3Rpb25zX2N2X3Jlc3VsdHMgICU+JSBzZWxlY3QoYWN0aXZpdHksU2Vuc2l0aXZpdHlfbWVhbixTcGVjaWZpY2l0eV9tZWFuLEYxX21lYW4sQmFsYW5jZWRBY2N1cmFjeV9tZWFuKSAlPiUgcmVzaGFwZTI6Om1lbHQoKSAlPiUKICAKICAKIyAgZ2dwbG90KCkrCiMgIGZhY2V0X3dyYXAofnZhcmlhYmxlKSsKIyAgZ2VvbV9jb2woYWVzKHg9YWN0aXZpdHkseT12YWx1ZSxmaWxsPWFjdGl2aXR5KSkrCiMgIGdnZGFyazo6ZGFya190aGVtZV9ncmV5KCkrCiMgIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIHZqdXN0ID0gMC41LCBoanVzdD0xKSkrCiMgIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikgCmBgYAoKCmBgYHtyIGV2YWw9RkFMU0UsIGluY2x1ZGU9RkFMU0V9CnZhckltcChzZWxlY3RlZF9tb2RlbCkKYGBgCgojIyBFdmFsdWFjaW9uIHNvYnJlIGVsIGNvbmp1bnRvIGRlIHRlc3QgKFRlc3QgUmVwb3J0KQpNYXRyaXogZGUgQ29uZnVzaW9uCmBgYHtyfQpkYXRhX3Rlc3RfcHJlZGljdGlvbjwtcHJlZGljdChzZWxlY3RlZF9tb2RlbCxkYXRhX3Rlc3RfY2x1c3Rlcl9sYWJlbGVkICU+JSBzZWxlY3QoLWlkKSkjLHR5cGU9InByb2IiKQpjbTwtY2FyZXQ6OmNvbmZ1c2lvbk1hdHJpeChkYXRhX3Rlc3RfcHJlZGljdGlvbixhcy5mYWN0b3IoZGF0YV90ZXN0X2NsdXN0ZXJfbGFiZWxlZCRhY3Rpdml0eSksbW9kZT0nZXZlcnl0aGluZycpCmNtCmBgYApgYGB7ciBmaWcuaGVpZ2h0PTMsIGZpZy53aWR0aD04LCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQp0ZXN0X2RhdGE8LWNtJGJ5Q2xhc3MgJT4lIGFzLmRhdGEuZnJhbWUoKSAlPiUgdGliYmxlOjpyb3duYW1lc190b19jb2x1bW4odmFyPSdhY3Rpdml0eScpICU+JQogIHNlbGVjdChhY3Rpdml0eSxQcmVjaXNpb24sIFJlY2FsbCwgRjEpICU+JSByZXNoYXBlMjo6bWVsdCgpCgpwcmVkaWN0aW9uc19jdl9jbV9wbG90KwogIGxhYnModGl0bGU9cGFzdGUwKHNlbGVjdGVkX21vZGVsJG1ldGhvZCwiIGZvciBhY3Rpdml0eSBwcmVkaWN0aW9uIFtDVitUZXN0XSBSZXBvcnQiKSwKICAgICAgICAgICAgICAgICAgICBzdWJ0aXRsZT0iWzN4NSBDVl0iKSsKICAgIHlsaW0oMC42LDEpKwogIGdlb21fcG9pbnQoZGF0YT10ZXN0X2RhdGEsYWVzKHg9YWN0aXZpdHkseT12YWx1ZSksZmlsbD0iZGFya2dyZWVuIixjb2xvcj0id2hpdGUiLHNoYXBlPTIzLHNpemU9MikrCiAgICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpIAoKYGBgCgojIyBNZXRyaWNhcyBNYWNybyB5IE1pY3JvCmBgYHtyfQoKCm1hY3JvX21ldHJpY3M8LShhcHBseShjbSRieUNsYXNzLDIsc3VtKS80KSBbYyg1LDYsNyldCgpzPXRhYmxlKGRhdGFfdGVzdF9wcmVkaWN0aW9uLGFzLmZhY3RvcihkYXRhX3Rlc3RfY2x1c3Rlcl9sYWJlbGVkJGFjdGl2aXR5KSkKClByZWNpc2lvbl9taWNybyA8LSAgKHN1bShkaWFnKHMpKSAvIHN1bShhcHBseShzLDEsIHN1bSkpKQpSZWNhbGxfbWljcm8gPC0gKHN1bShkaWFnKHMpKSAvIHN1bShhcHBseShzLDIsIHN1bSkpKQoKbWljcm9fbWV0cmljcyA8LSBjKCAKICAoc3VtKGRpYWcocykpIC8gc3VtKGFwcGx5KHMsMSwgc3VtKSkpLAogKHN1bShkaWFnKHMpKSAvIHN1bShhcHBseShzLDIsIHN1bSkpKSwKIDIgKiAoKFByZWNpc2lvbl9taWNybypSZWNhbGxfbWljcm8pLyhQcmVjaXNpb25fbWljcm8rUmVjYWxsX21pY3JvKSkpCgptbTwtY2JpbmQoYyhtYWNyb19tZXRyaWNzKSxjKG1pY3JvX21ldHJpY3MpKQoKCmxpYnJhcnkoZ3JpZEV4dHJhKQpkYXRhLmZyYW1lKG1hY3JvPW1tWywxXSxtaWNybz1tbVssMl0pCmBgYApgYGB7cn0KYXBwbHkoY20kdGFibGUsMixzdW0pCmBgYAoKIyMgTWF0cml6IGRlIGNvbmZ1c2lvbiAocGxvdCkKYGBge3J9Cmxlbl9kYXRhX3Rlc3RfcHJlZGljdGlvbiA9IGxlbmd0aChkYXRhX3Rlc3RfcHJlZGljdGlvbikKcmVzaGFwZTI6Om1lbHQodGFibGUoZGF0YV90ZXN0X3ByZWRpY3Rpb24sYXMuZmFjdG9yKGRhdGFfdGVzdF9jbHVzdGVyX2xhYmVsZWQkYWN0aXZpdHkpKSkgJT4lIG11dGF0ZSh2YWx1ZT0odmFsdWUpKSAlPiUKICBnZ3Bsb3QoYWVzKHg9ZGF0YV90ZXN0X3ByZWRpY3Rpb24seT1WYXIyKSkrCiAgZ2VvbV90aWxlKGFlcyhmaWxsPXZhbHVlKSwgY29sb3VyID0gIndoaXRlIikgKyAKICAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IHNwcmludGYoIiUxLjJmIiwgdmFsdWUpKSwgY29sb3I9J2JsYWNrJyx2anVzdCA9IDEpKwogIHNjYWxlX2ZpbGxfZ3JhZGllbnQobG93ID0gIndoaXRlIiwgaGlnaCA9ICJyZWQiKSsKICB4bGFiKCIgUHJlZGljdGVkIEFjdGl2aXR5ICIpK3lsYWIoIiBBY3R1YWwgQWN0aXZpdHkiKSsKICB0aGVtZV9idygpKyB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpCgojZ2dzYXZlKGZpbGVuYW1lID0gIi9ob21lL2hhcnBvL0Ryb3Bib3gvb25nb2luZy13b3JrL3B1YmxpY2FjaW9uZXMvQ0FJMjAxOS1KQUlJTy9jb25mdXNpb24tbWF0cml4LnBuZyIsd2lkdGggPSA4LCBoZWlnaHQgPSA0KQoKCmBgYAoKCiMjIyMgRyB2cyBSRgpgYGB7cn0KCgpHdnNSRm1hdHJpeCA8LQogIGNiaW5kKGRhdGFfdGVzdF9jbHVzdGVyX2xhYmVsZWQsIHByZWRpY3Rpb25zID0gZGF0YV90ZXN0X3ByZWRpY3Rpb24pICU+JSBzZWxlY3QoYWN0aXZpdHksIHByZWRpY3Rpb25zKSAlPiUgZmlsdGVyKGFjdGl2aXR5ICVpbiUgYygiRyIsICJSRiIpKSAlPiUgZmlsdGVyKHByZWRpY3Rpb25zICVpbiUgYygiRyIsICJSRiIpKQoKR3ZzUkZfcmVjYWxsIDwtCiAgY2FyZXQ6OmNvbmZ1c2lvbk1hdHJpeCgKICAgIGZhY3RvcihHdnNSRm1hdHJpeCRwcmVkaWN0aW9ucyksCiAgICBmYWN0b3IoR3ZzUkZtYXRyaXgkYWN0aXZpdHkpLAogICAgbW9kZSA9ICdldmVyeXRoaW5nJywKICAgIHBvc2l0aXZlID0gIkciCiAgKSRieUNsYXNzICU+JSBhcy5kYXRhLmZyYW1lKCkgJT4lIHRpYmJsZTo6cm93bmFtZXNfdG9fY29sdW1uKHZhciA9ICJtZXRyaWMiKSAlPiUgZmlsdGVyKG1ldHJpYyA9PSAiUmVjYWxsIiB8IG1ldHJpYyA9PSAiU3BlY2lmaWNpdHkiKSAlPiUgbXV0YXRlKGFjdGl2aXRpZXM9Ikd2c1JGIikKCkd2c1JGX3JlY2FsbApgYGAKIyMjIyBSRiB2cyBSUApgYGB7cn0KClJGdnNSUG1hdHJpeCA8LQogIGNiaW5kKGRhdGFfdGVzdF9jbHVzdGVyX2xhYmVsZWQsIHByZWRpY3Rpb25zID0gZGF0YV90ZXN0X3ByZWRpY3Rpb24pICU+JSBzZWxlY3QoYWN0aXZpdHksIHByZWRpY3Rpb25zKSAlPiUgZmlsdGVyKGFjdGl2aXR5ICVpbiUgYygiUkYiLCAiUlAiKSkgJT4lIGZpbHRlcihwcmVkaWN0aW9ucyAlaW4lIGMoIlJGIiwgIlJQIikpCgpSRnZzUlBfcmVjYWxsIDwtCiAgY2FyZXQ6OmNvbmZ1c2lvbk1hdHJpeCgKICAgIGZhY3RvcihSRnZzUlBtYXRyaXgkcHJlZGljdGlvbnMpLAogICAgZmFjdG9yKFJGdnNSUG1hdHJpeCRhY3Rpdml0eSksCiAgICBtb2RlID0gJ2V2ZXJ5dGhpbmcnLAogICAgcG9zaXRpdmUgPSAiUkYiCiAgKSRieUNsYXNzICU+JSBhcy5kYXRhLmZyYW1lKCkgJT4lIHRpYmJsZTo6cm93bmFtZXNfdG9fY29sdW1uKHZhciA9ICJtZXRyaWMiKSAlPiUgZmlsdGVyKG1ldHJpYyA9PSAiUmVjYWxsIiB8IG1ldHJpYyA9PSAiU3BlY2lmaWNpdHkiKSAlPiUgbXV0YXRlKGFjdGl2aXRpZXM9IlJGdnNSUCIpCgpSRnZzUlBfcmVjYWxsCmBgYAoKIyMjIyBHIHZzIFJQCmBgYHtyfQoKR3ZzUlBtYXRyaXggPC0KICBjYmluZChkYXRhX3Rlc3RfY2x1c3Rlcl9sYWJlbGVkLCBwcmVkaWN0aW9ucyA9IGRhdGFfdGVzdF9wcmVkaWN0aW9uKSAlPiUgc2VsZWN0KGFjdGl2aXR5LCBwcmVkaWN0aW9ucykgJT4lIGZpbHRlcihhY3Rpdml0eSAlaW4lIGMoIkciLCAiUlAiKSkgJT4lIGZpbHRlcihwcmVkaWN0aW9ucyAlaW4lIGMoIkciLCAiUlAiKSkKCkd2c1JQX3JlY2FsbCA8LQogIGNhcmV0Ojpjb25mdXNpb25NYXRyaXgoCiAgICBmYWN0b3IoR3ZzUlBtYXRyaXgkcHJlZGljdGlvbnMpLAogICAgZmFjdG9yKEd2c1JQbWF0cml4JGFjdGl2aXR5KSwKICAgIG1vZGUgPSAnZXZlcnl0aGluZycsCiAgICBwb3NpdGl2ZSA9ICJHIgogICkkYnlDbGFzcyAlPiUgYXMuZGF0YS5mcmFtZSgpICU+JSB0aWJibGU6OnJvd25hbWVzX3RvX2NvbHVtbih2YXIgPSAibWV0cmljIikgJT4lIGZpbHRlcihtZXRyaWMgPT0gIlJlY2FsbCIgfCBtZXRyaWMgPT0gIlNwZWNpZmljaXR5IikgJT4lIG11dGF0ZShhY3Rpdml0aWVzPSJHdnNSUCIpCgpHdnNSUF9yZWNhbGwKYGBgCiMgU2Vzc2lvbiBJbmZvCmBgYHtyfQpzZXNzaW9uSW5mbygpCmBgYAoK