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
caprino_imu_acc.csv, 4 columnas: tiempo UTC en microsegundos, aceleración X en m/s², aceleración Y en m/s², aceleración, Z en m/s². Muestreado a 200 muestras por segundo. Filtrados
caprino_imu_gyro.csv, 4 columnas: tiempo UTC en microsegundos, velocidad angular X en rad/s, velocidad angular Y en rad/s, velocidad angular Z en rad/s. Muestreado a 200 muestras por segundo. Filtrados
caprino_gps_distancia.csv, 4 columnas: tiempo UTC en microsegundos, latitud en radianes, longitud en radianes, altitud en metros. Muestreado a 5 muestras por segundo Se agrega ademas una columna con la distancia euclidean.
caprino_hours.csv: tiempo en horas, minutos y segundos. Muestreado a 5 muestras por segundo. La primera hora de este archivo coincide con el primer valor de tiempo UTC del archivo caprino_gps.csv.
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