Técnicas de Machine Learning


Alessio Crisafulli Carpani

Máster en Minería de Datos e Inteligencia de Negocios
Técnicas de Machine Learning

Este trabajo está orientado a predecir una variable binaria o continua a través de diferentes algoritmos de clasificación o estimación relaccionados con árboles y otros.

En este caso, he querido explorar un dataset de RRHH desarollado por IBM. Es posible descargarlo aquí:

El objetivo de este informe es predecir el índice de deserción de los empleados, identificando también las causas que más lo influencian.

Este es un conjunto de datos ficticio creado por científicos de datos. Por esta razón espero métricas de los modelos elevadas

Kaggle Dataset

Abstracto

A lo largo de esta análisis, he analizado el conjunto de datos IBM Employee Attrition para explorar las causas principales de la deserción en una empresa. Primero, a través de un análisis exploratorio, me he asegurado que los datos esteban limpios. En esta etapa, me dí cuenta que los datos tenían un problema de desbalanceo entre las clases de la variable dependiente, para arreglar este problema he ampliado la muestra de la clase menor.

Una vez que los datos estaban listos, he empezado la modelización del árbol de decisión, bagging, random forest, gradient boosting machine (GBM) y extreme gradient boosting machine (XGBM). Para cada técnica, mi flujo de trabajo ha sido lo siguiente:

  1. Tunear los parámetros de cada modelo, con la función train del paquete caret, empleando un bucle cuando la función no permite el control de unos parámetros y utilizando computación paralela sobre tres procesadores.

  2. Entender el andamiento del ajuste para los distintos parámetros tuneados a través unos gráficos.

    • Además para el bagging y random forest he visualizado el error de out-of-bag, para entender el andamiento a medida que aumentaban las interacciones de los árboles. A través este gráfico he podido reducir la complejidad de mi modelo final.

Después haber elegido los parámetros mejores, he ajustado el modelo final, visualizando la matriz de confusión y las variables más influyentes para cada modelo.

Una vez ajustadas las técnicas de árboles, he ajustado otra vez los modelos con validación cruzada repetida esta vez, para comparar las respectivas tasa de fallo y la área abajo la curva ROC, a través de un grafo de caja y bigote incluyendo como modelo de referencia una regresión logística.

Una vez encontrado el modelo ganador, he decidido crear un grafo de barras apiladas para obtener las variables más influyentes según los distintos modelos. A partir de estas, he creado un nuevo conjunto entrenamiento/test, con el cual voy a realizar otras técnicas de aprendizaje automático:

  • Support Vector Machines, con kernel:
    • Lineal
    • Polynomial
    • Radial
  • Bagging del SVM Lineal
  • Boosting
  • Stacking de:
    • Gradient Boosting Machine
    • SVM Radial
    • XGBoost

1 Descripción de los datos

  • El conjunto de datos: 1470 observaciones, 35 variables.
  • Tipo de variables: Tenemos ambos variables categorícas y numerícas. La variable objecto de ínteres, Attrition, es una variable binaria que toma valores Yes o No, dependiendo si el empleado se ha marchado de la impresa.

Unas columnas están en escala 1-5, dale al nombre de la columna marcada en rojo para visualizar un popup con la descripción.


as.data.frame(cbind(Variable = names(data), Description = c("Edad", "Si ha dejado la empresa o no",
    "Si viaja raramente, poco, mucho por trabajo", "Salario diarío", "El departamiento de la empresa",
    "Cuanto está lejo de su casa", "Nivel de Education", "Numero de empleado", "ID",
    "Satisfacción entorno", "Género", "Salario por hora", "Participación en el trabajo",
    "Nivel de trabajo", "Rol trabajo", "Satisfacción al trabajo", "Estado civil",
    "Salario bruto mensual", "Tasa mensual", "Con cuantas empresas ha trabajado",
    "Si es mayor de 18 años", "Si trabaja más del normal", "Porcentaje aumento salario",
    "Calificación de desempeño", "Satisfacción de la relación", "Horas estandar de trabajo",
    "Retribuciones", "Total años de trabajo", "Cuantas veces ha hecho formación el año pasado",
    "Balnceo entre vida y trabajo", "Años en la empresa", "Años en este rol", "Años desde ultima promocción",
    "Años con manager actual"))) %>%
    kable(format = "html", align = c(rep("l", 2))) %>%
    row_spec(0, font_size = 14) %>%
    column_spec(1, bold = T)
Variable Description
Age Edad
Attrition Si ha dejado la empresa o no
BusinessTravel Si viaja raramente, poco, mucho por trabajo
DailyRate Salario diarío
Department El departamiento de la empresa
DistanceFromHome Cuanto está lejo de su casa
Education Nivel de Education
EducationField Numero de empleado
EmployeeCount ID
EmployeeNumber Satisfacción entorno
EnvironmentSatisfaction Género
Gender Salario por hora
HourlyRate Participación en el trabajo
JobInvolvement Nivel de trabajo
JobLevel Rol trabajo
JobRole Satisfacción al trabajo
JobSatisfaction Estado civil
MaritalStatus Salario bruto mensual
MonthlyIncome Tasa mensual
MonthlyRate Con cuantas empresas ha trabajado
NumCompaniesWorked Si es mayor de 18 años
Over18 Si trabaja más del normal
OverTime Porcentaje aumento salario
PercentSalaryHike Calificación de desempeño
PerformanceRating Satisfacción de la relación
RelationshipSatisfaction Horas estandar de trabajo
StandardHours Retribuciones
StockOptionLevel Total años de trabajo
TotalWorkingYears Cuantas veces ha hecho formación el año pasado
TrainingTimesLastYear Balnceo entre vida y trabajo
WorkLifeBalance Años en la empresa
YearsAtCompany Años en este rol
YearsInCurrentRole Años desde ultima promocción
YearsSinceLastPromotion Años con manager actual
YearsWithCurrManager Edad


Desbalanceo entre los datos: 1237 (84% de los casos) empleados no han dejado la empresa, mientras 237 (16% of casos) lo han hecho. Este desbalanceo tiene que ser ajustado, mediante unas técnicas adecuadas, para no sobreajustar el modelo.


2 Analísis Exploratorio de Datos (EDA)

2.1 Visualización de Datos

2.1.1 Variables Continuas

library(kableExtra)
library(modelsummary)

datasummary_skim(data, type = "numeric", title = "Variables Numericas") %>%
    remove_column(columns = c(2, 3)) %>%
    column_spec(1, bold = T) %>%
    kable_material() %>%
    kable_styling(bootstrap_options = c("condensed", "responsive")) %>%
    scroll_box(width = "100%", height = "400px")
Variables Numericas
Mean SD Min Median Max
Age 36.9 9.1 18.0 36.0 60.0
DailyRate 802.5 403.5 102.0 802.0 1499.0
DistanceFromHome 9.2 8.1 1.0 7.0 29.0
EmployeeCount 1.0 0.0 1.0 1.0 1.0
EmployeeNumber 1024.9 602.0 1.0 1020.5 2068.0
EnvironmentSatisfaction 2.7 1.1 1.0 3.0 4.0
HourlyRate 65.9 20.3 30.0 66.0 100.0
JobInvolvement 2.7 0.7 1.0 3.0 4.0
JobLevel 2.1 1.1 1.0 2.0 5.0
JobSatisfaction 2.7 1.1 1.0 3.0 4.0
MonthlyIncome 6502.9 4708.0 1009.0 4919.0 19999.0
MonthlyRate 14313.1 7117.8 2094.0 14235.5 26999.0
NumCompaniesWorked 2.7 2.5 0.0 2.0 9.0
PercentSalaryHike 15.2 3.7 11.0 14.0 25.0
PerformanceRating 3.2 0.4 3.0 3.0 4.0
RelationshipSatisfaction 2.7 1.1 1.0 3.0 4.0
StandardHours 80.0 0.0 80.0 80.0 80.0
StockOptionLevel 0.8 0.9 0.0 1.0 3.0
TotalWorkingYears 11.3 7.8 0.0 10.0 40.0
TrainingTimesLastYear 2.8 1.3 0.0 3.0 6.0
WorkLifeBalance 2.8 0.7 1.0 3.0 4.0
YearsAtCompany 7.0 6.1 0.0 5.0 40.0
YearsInCurrentRole 4.2 3.6 0.0 3.0 18.0
YearsSinceLastPromotion 2.2 3.2 0.0 1.0 15.0
YearsWithCurrManager 4.1 3.6 0.0 3.0 17.0
library(GGally)
ggpairs(data, mapping = aes(color = Attrition), columns = c("MonthlyIncome", "JobSatisfaction",
    "EnvironmentSatisfaction", "DistanceFromHome", "DailyRate", "Age", "PercentSalaryHike"),
    upper = list(continuous = "barDiag"), lower = list(continuous = wrap("points",
        alpha = 0.5, size = 0.1)), title = "Matriz de Visualización Variables Continuas",
    legend = 1, showStrips = T) + theme_minimal(base_size = 10, base_family = "Times") +
    theme(title = element_text(face = "bold"), legend.title = element_text(face = "bold"),
        legend.position = "bottom") + scale_fill_manual(values = c("#2E45B8", "#D6DBF5")) +
    scale_color_manual(values = c("#2E45B8", "#D6DBF5"))
Numerical Variables

Numerical Variables

Podemos notar como las variables no tienen una distribución lineal, entonces me espero que las técnicas des árboles van a funcionar bien con estos tipo de datos. También se nota que no hay separación lineal.

2.1.2 Variables Categóricas

datasummary_skim(data, type = "categorical", title = "Variables Categóricas") %>%
    column_spec(1, bold = T) %>%
    column_spec(2, italic = T) %>%
    kable_material() %>%
    kable_styling(bootstrap_options = c("condensed", "responsive")) %>%
    scroll_box(width = "100%", height = "400px")
Variables Categóricas
N %
Attrition Yes 237 16.1
No 1233 83.9
BusinessTravel Travel_Rarely 1043 71.0
Travel_Frequently 277 18.8
Non-Travel 150 10.2
Department Sales 446 30.3
Research & Development 961 65.4
Human Resources 63 4.3
Education 2 282 19.2
1 170 11.6
4 398 27.1
3 572 38.9
5 48 3.3
EducationField Life Sciences 606 41.2
Other 82 5.6
Medical 464 31.6
Marketing 159 10.8
Technical Degree 132 9.0
Human Resources 27 1.8
Gender Female 588 40.0
Male 882 60.0
JobRole Sales Executive 326 22.2
Research Scientist 292 19.9
Laboratory Technician 259 17.6
Manufacturing Director 145 9.9
Healthcare Representative 131 8.9
Manager 102 6.9
Sales Representative 83 5.6
Research Director 80 5.4
Human Resources 52 3.5
MaritalStatus Single 470 32.0
Married 673 45.8
Divorced 327 22.2
Y 1470 100.0
OverTime Yes 416 28.3
No 1054 71.7
theme_set( 
    theme_minimal(base_family = 'Times',
                                base_size = 10) +
    theme(
    legend.position = "none",
    panel.grid.minor.x = element_blank(),
    panel.grid.major.y = element_blank(),
    axis.text.x = element_text(color = "gray60", 
                                                         size = 8),
    axis.title.x = element_blank(),
    ) 
)

colors=c("#2E45B8","#D6DBF5")


library("cowplot")
plot_grid(
ggdraw() + draw_label("Categorical Variables of Data with Attrition", fontface = "bold"),

plot_grid(
    ggplot(data,aes(fill=Attrition,x=Gender)) +
    geom_bar(position="dodge2",alpha=0.8,color="black") +
    scale_fill_manual(values=colors) + coord_flip(),

    ggplot(data,aes(fill=Attrition,x=Department))+geom_bar(position="fill",alpha=0.8,color="black")+scale_fill_manual(values=colors)+coord_flip(),
    
  ggplot(data,aes(fill=Attrition, x=JobRole))+geom_bar(position="fill",alpha=0.8,color="black")+scale_fill_manual(values=colors)+coord_flip(),

    ggplot(data,aes(fill=Attrition, x=Education))+geom_bar(position="fill",alpha=0.8,color="black")+scale_fill_manual(values=colors)+coord_flip(),

    ggplot(data,aes(fill=Attrition, x=OverTime))+geom_bar(position="fill",alpha=0.8,color="black")+scale_fill_manual(values=colors)+coord_flip(),

    ggplot(data,aes(fill=Attrition, x=EducationField))+geom_bar(position="fill",alpha=0.8,color="black")+scale_fill_manual(values=colors)+coord_flip(),

    ncol = 2), 

rel_heights = c(0.1, 1, 0.2),

get_legend(ggplot(data,aes(fill=Attrition,x=Gender))+geom_bar(position="fill",alpha=0.8,color="black")+scale_fill_manual(values=colors)+coord_flip()+ theme(legend.position = "top")), 
ncol = 1)
Categorical Variables

Categorical Variables

2.2 Datos Faltantes & Duplicados

cbind(Missings = naniar::n_miss(data), Duplicados = nrow(janitor::get_dupes(data)))
r >      Missings Duplicados
r > [1,]        0          0

No hay ni datos faltantes ni filas duplicadas

  • Age, DailyRate, DistanceFromHome, HourlyRate, MonthlyRate, PercentSalaryHike no tienen outliers.
  • NumCompaniesWorked, TrainingTimesLastYear, YearsWithCurrManager, YearsInCurrentRole tienen un moderado numero de outliers.
  • MonthlyIncome, TotalWorkingYears, YearsAtCompany, YearsSinceLastPromotion tienen un largo numero de outliers.

No haré mas consideraciones en cuanto, de todas formas, los outliers no afectan los modelos de árboles que voy a plantear.

2.3 Análisis de la Correlación

Este parte de la análisis, me proporcionará evidencias sobre la correlación entre los regresores.

corr_mx = as.matrix((cor(data[, sapply(data, is.numeric)] %>%
    select(-StandardHours, -EmployeeCount))))

corrplot::corrplot(corr_mx, method = "square", type = "lower", diag = T, tl.col = "#2E45B8",
    tl.cex = 1.3, col = painter::Palette("#D6DBF5", "#4055BE", 10))
Matriz de Correlación

Matriz de Correlación

  • MonthlyIncome está muy correlada con JobLevel

  • La correlacíon entre TotalWorkingYears y JobLevel es 0.78, que también está bastante alta.

  • La variable PercentSalaryHike tiene correlacíon elevada con PerformanceRating

  • El conjunto de variables YearsSinceLastPromotion, YearsInCurrentRole, YearsWithCurrManager y YearsAtCompany, están correladas entre ellas mismas y entonces elegiré solo dos de ellas para reducir la complejidad de los árboles

Todas las demás tienen una correlacíon menor que 0.80.


3 Features Engineering

3.1 Selección de Variables

Unas variables no explican variabilidad en el modelo planteado. Estas variables son:

  1. EmployeeNumber: denota el numero de identificación del empleado.
  2. EmployeeCount: este es justo una cuenta del empleado y entonces toma siempre valor igual a 1.
  3. Over18: esta variable describe si el empleado es mayor de 18 años. Toma valor ‘Yes’ en todos los casos.
  4. StandardHours: el numero estándar de horas de trabajo por semana. Tiene valor constante de 80.

Luego, como ya dicho para reducir la complejidad de los árboles, quitaré 2, sobre 4, variables que proporcionan informaciones sobre los años de trabajo en la empresa y también quitaré MonthlyRate en cuanto ya tengo la variable DailyRate que simplemente está explicada en otra unidad. Por estas razones, quitaré estas variables de mi conjunto de datos.

data %<>%
    select(-EmployeeNumber, -EmployeeCount, -Over18, -StandardHours, -TrainingTimesLastYear,
        -YearsWithCurrManager, -YearsInCurrentRole, -MonthlyRate)

También el conjunto de datos está muy bueno para trabajar, así que no necesitaré crear otras variables a partir de las que ya tengo.

No emplearé otros algoritmos en cuanto las técnicas de árboles ya desarrollan los modelos sobre las variables más influyentes, pero enseguida implementaré otras técnicas de ML spara el conjunto con las variables más importantes.

3.2 Data Preprocessing

3.2.1 Dummies

# Obtener dummies por las variables categoricas, quitando y
datos <- fastDummies::dummy_cols(data[-2], remove_selected_columns = T)
# Añadir the y variable
datos <- cbind(Attrition = data$Attrition, datos)

3.2.2 Estandarización de Variables

La regresión logística y los algoritmos basados en árboles, como el Decision Tree, el Random Forest y el Gradient Boosting, no son sensibles a la magnitud de las variables. Por lo tanto, no es necesaria la estandarización antes de ajustar este tipo de modelos.

3.3 Entrenamiento / Prueba

Stratified Train-Test Splits

Como el dataset no tiene un numero balanceado de ejemplos por cada clase de la variable dependiente, voy a repartir los datos entre los conjunto train y test, de una manera que preserva el mismo numero de ejemplos en cada clase como el conjunto original. Este procedimiento es llamado como muestreo train-test estratificado.

Por defecto, la función caret createDataPartition, hace la estratificación de esta manera.

library(caret)
trainIndex <- createDataPartition(datos$Attrition, p = 0.8, list = FALSE, times = 1)
Train <- datos[trainIndex, ]
Test <- datos[-trainIndex, ]

rm(trainIndex)
rbind(Test = dim(Train), Train = dim(Test)) %>%
    kable(caption = "Partición en Entrenamiento y Prueba", col.names = c("Observaciones",
        "Variables"))
Partición en Entrenamiento y Prueba
Observaciones Variables
Test 1177 52
Train 293 52

No obstante, tenemos todavía que lidiar con el desbalanceo.

3.4 Desbalanceo de Datos

El paquete caret tiene una función upSample, que reajusta las frecuencias de las clases, haciendo un muestreo con reemplazo para que la distribución en cada clase sea igual.

Up-Sampling Technique

predictors = names(Train)[names(Train) != "Attrition"]

upTrain <- upSample(x = Train[, predictors], y = Train$Attrition, list = FALSE, yname = "Attrition")

upTrain %<>%
    janitor::clean_names()
Test %<>%
    janitor::clean_names()
predictors = names(upTrain)[names(upTrain) != "attrition"]

Ahora voy a comprobar los numeros de observaciones en cada clase:

knitr::kables(format = "html", list(kable(table(Train$Attrition), caption = "Imbalanced Data",
    col.names = c("Class", "N")), kable(table(upTrain$attrition), caption = "Balanced Data",
    col.names = c("Class", "N"), position = "float_right")))
Imbalanced Data
Class N
Yes 190
No 987
Balanced Data
Class N
Yes 987
No 987

4 Ajuste del Modelo

4.1 Remuestreo

Primero, para comprobar la validez de los resultados del modelo, voy a emplear validación cruzada; esta técnica me va a garantir que los resultados son independientes de la partición de los datos entre entrenamiento y prueba, a través el uso de 5 subconjuntos aleatorios. Para la reproducibilidad de estos algoritmos, he fijado la semilla de aleatorización.

set.seed(112)
control <- trainControl(method = "cv", number = 5, classProbs = TRUE, savePredictions = "all")

4.2 Modelo de Referencia

Tenemos que establecer primero un modelo de referencia para comparar si los modelos que voy a emplear aportan mejoras consistentes. Para ello, he ajustado un modelo de regresión logística.

logi <- train(attrition ~ ., data = upTrain, method = "glm", trControl = control)
# Confusion Matrix ----
source("files/ConfusionMatrix.R")
draw_confusion_matrix(confusionMatrix(logi$pred$pred, logi$pred$obs), "#D6DBF5",
    "#2E45B8")

roc(response = logi$pred$obs, predictor = logi$pred$Yes, quiet = T) %>%
    ggroc(colour = "#2E45B8", size = 0.8) + annotate("text", x = 0.08, y = 0.92,
    label = "bold(AUC): 0.86", family = "Times", parse = TRUE) + hrbrthemes::theme_ipsum(ticks = T,
    base_family = "Times", base_size = 10) + labs(title = "ROC Curve", subtitle = "Modelo de Referencia: Regression Logistica") +
    theme(legend.position = "none", panel.background = element_rect(color = "#3b454a"),
        title = element_text(face = "bold"), axis.title.y = element_text(size = 10),
        axis.text.x = element_text(face = "bold", colour = "#3D3D3D", size = 10))
Regression LogisticaRegression Logistica

Regression Logistica

Logistica <- cruzadalogistica(data = upTrain, vardep = "attrition", listconti = predictors,
    listclass = c(""), grupos = 5, sinicio = 112, )
Logistica$modelo = "Logística"

4.3 Decision Tree

4.3.1 Tuneo del Modelo

Un árbol demasiado complejo es inestable, mientras. Un árbol demasiado sencillo puede tener poca potencia predictiva (alto sesgo) o bajo valor explicativo. Así que haré un tuneo de los datos teniendo en consideración esto. En este proceso, me dí cuenta que los mejores modelos de árboles, en termine de accuracy, se encuentran tuneando solo el parámetro minbucket, que corresponde a el número de observaciones mínimas en cada nodo final. La librería caret no permite de hacer el tuneo, así que tuve que crear un bucle que me devuelva los distintos valores para cada valor de minbucket.

minbucket_ <- c()
Accuracy <- c()
Kappa <- c()
Auc <- c()

for (minbucket in seq(from = 5, to = 205, by = 10)) {
    arbolcaret <- train(factor(attrition) ~ age + daily_rate + distance_from_home +
        environment_satisfaction + hourly_rate + job_involvement + job_satisfaction +
        monthly_income + num_companies_worked + percent_salary_hike + performance_rating +
        relationship_satisfaction + stock_option_level + total_working_years + work_life_balance +
        years_at_company + years_since_last_promotion + business_travel_travel_rarely +
        business_travel_travel_frequently + business_travel_non_travel + department_sales +
        department_research_development + department_human_resources + education_2 +
        education_1 + education_4 + education_3 + education_5 + education_field_life_sciences +
        education_field_other + education_field_medical + education_field_marketing +
        education_field_technical_degree + education_field_human_resources + gender_female +
        gender_male + job_role_sales_executive + job_role_research_scientist + job_role_laboratory_technician +
        job_role_manufacturing_director + job_role_healthcare_representative + job_role_manager +
        job_role_sales_representative + job_role_research_director + job_role_human_resources +
        marital_status_single + marital_status_married + marital_status_divorced +
        over_time_yes + over_time_no, data = upTrain, method = "rpart", trControl = control,
        tuneGrid = expand.grid(cp = c(0)), control = rpart.control(minbucket = minbucket))

    confusionMatrix <- confusionMatrix(arbolcaret$pred$pred, arbolcaret$pred$obs)

    roc <- roc(response = arbolcaret$pred$obs, predictor = arbolcaret$pred$Yes)

    Acc_i <- confusionMatrix$overall[1]
    Accuracy <- append(Accuracy, Acc_i)

    K_i <- confusionMatrix$overall[2]
    Kappa <- append(Kappa, K_i)

    Auc_i <- roc$auc
    Auc <- append(Auc, Auc_i)

    minbucket_ <- append(minbucket_, minbucket)

    # \tsvMisc::progress(minbucket)
    dput("---------------")
    dput(paste0("With minbucket= ", minbucket))
    dput(paste0("Accuracy: ", round(confusionMatrix$overall[1], 3)))
    print(roc$auc)
}
arbol_results = cbind(data.frame(Accuracy, Kappa, Auc), minbucket = as.factor(c(seq(from = 5,
    to = 205, by = 10))))

# Clear cache ----
rm(Acc_i, K_i, Auc_i, minbucket, roc)
rm(Accuracy, Auc, Kappa, minbucket_)

•••>CLick to see console results

r > "---------------"\n
r > "With minbucket= 5"\n
r > "Accuracy: 0.841"\n
r > Area under the curve: 0.905 \n
r > "---------------"\n
r > "With minbucket= 15"\n
r > "Accuracy: 0.784"\n
r > Area under the curve: 0.842\n
r > "---------------"\n
r > "With minbucket= 25"\n
r > "Accuracy: 0.749"\n
r > Area under the curve: 0.813\n
r > "---------------"\n
r > "With minbucket= 35"\n
r > "Accuracy: 0.73"\n
r > Area under the curve: 0.792\n
r > "---------------"\n
r > "With minbucket= 45"
r > "Accuracy: 0.746"
r > Area under the curve: 0.806
r > "---------------"
r > "With minbucket= 55"
r > "Accuracy: 0.744"
r > Area under the curve: 0.789
r > "---------------"
r > "With minbucket= 65"
r > "Accuracy: 0.727"
r > Area under the curve: 0.788
r > "---------------"
r > "With minbucket= 75"
r > "Accuracy: 0.727"
r > Area under the curve: 0.774
r > "---------------"
r > "With minbucket= 85"
r > "Accuracy: 0.718"
r > Area under the curve: 0.75
r > "---------------"
r > "With minbucket= 95"
r > "Accuracy: 0.714"
r > Area under the curve: 0.753
r > "---------------"
r > "With minbucket= 105"
r > "Accuracy: 0.677"
r > Area under the curve: 0.71
r > "---------------"
r > "With minbucket= 115"
r > "Accuracy: 0.68"
r > Area under the curve: 0.715
r > "---------------"
r > "With minbucket= 125"
r > "Accuracy: 0.673"
r > Area under the curve: 0.701
r > "---------------"
r > "With minbucket= 135"
r > "Accuracy: 0.678"
r > Area under the curve: 0.695
r > "---------------"
r > "With minbucket= 145"
r > "Accuracy: 0.662"
r > Area under the curve: 0.688
r > "---------------"
r > "With minbucket= 155"
r > "Accuracy: 0.651"
r > Area under the curve: 0.684
r > "---------------"
r > "With minbucket= 165"
r > "Accuracy: 0.651"
r > Area under the curve: 0.664
r > "---------------"
r > "With minbucket= 175"
r > "Accuracy: 0.635"
r > Area under the curve: 0.662
r > "---------------"
r > "With minbucket= 185"
r > "Accuracy: 0.657"
r > Area under the curve: 0.674
r > "---------------"
r > "With minbucket= 195"
r > "Accuracy: 0.632"
r > Area under the curve: 0.662
r > "---------------"
r > "With minbucket= 205"
r > "Accuracy: 0.651"
r > Area under the curve: 0.675
# Plot ----
library(apexcharter)
apexchart(width = 800, ax_opts = list(chart = list(type = "line"), stroke = list(curve = "smooth"),
    grid = list(borderColor = "#e7e7e7", row = list(colors = c("#f3f3f3", "transparent"),
        opacity = 0.5)), markers = list(style = "inverted", size = 4), series = list(list(name = "Accuracy",
        data = arbol_results$Accuracy), list(name = "Kappa", data = arbol_results$Kappa),
        list(name = "AUC", data = arbol_results$Auc)), title = list(text = "Training Results",
        align = "center"), xaxis = list(categories = arbol_results$minbucket))) %>%
    ax_yaxis(labels = list(formatter = format_num(".0%"))) %>%
    ax_stroke(curve = "stepline", width = 2) %>%
    ax_colors("#4B69CE", "#B0BDE9", "#4A80CF")

Tuneo del arbol

A menor minbucket, obtendré árboles más complejos. Como podemos comprobar de este plot, para estos datos el modelo parece que se establece alrededor del valor minbucket=100. Por valores inferiores a 65 se obtendría un modelo sobreajustado. Entonces como que los resultados son parecidos, me quedo con el valor de 105 para obtener un modelo más estable y que no sea propenso a alto sesgo.

4.3.2 Plot del Mejor Árbol

# poner maxsurrogate=0 para que solo nos presente la importancia de las
# variables que efectivamente participen en el modelo
rpart(factor(attrition) ~ ., data = upTrain, minbucket = 105, method = "class", maxsurrogate = 0,
    parms = list(split = "gini")) -> arbol
library(rpart.plot)
rpart.plot(arbol, type = 4, extra = 105, nn = F, tweak = 1.7, gap = 1, space = 2,
    box.palette = "Blues")

Como podemos observar de este plot, según el árbol de decisión la variable mas influyente es OverTime_yes, es decir los empleados que trabajan muchas veces más de las horas previstas, tienden a dejar su puesto de trabajo. También, influyen mucho si el empleado trabaja desde poco tiempo a la empresa. Este se puede notar por la importancia de las variables Total Working Years y Job Level que a niveles menores, es decir el empleado trabaja desde poco tiempo a la empresa y tiene puesto de trabajo de los primeros niveles, suele marcharse.

4.3.3 Matriz de Confusión y Importancia de Variables

arbolcaret <- train(factor(attrition) ~ ., data = upTrain, method = "rpart", trControl = control,
    tuneGrid = expand.grid(cp = c(0)), control = rpart.control(minbucket = 105, maxsurrogate = 0))

source("files/ConfusionMatrix.R")
draw_confusion_matrix(confusionMatrix(arbolcaret$pred$pred, arbolcaret$pred$obs),
    "#D6DBF5", "#2E45B8")

ggplot(vip::vi(arbol) %>%
    filter(Importance > 0) %>%
    mutate(Variable = stringr::str_to_title(Variable)) %>%
    mutate(Variable = str_replace_all(Variable, "_", " ")), aes(x = reorder(Variable,
    Importance), y = Importance)) + ggchicklet::geom_chicklet(aes(fill = Importance),
    radius = grid::unit(10, "pt"), width = 1, show.legend = F) + labs(x = NULL, title = "Variable Importance Plot Arbol") +
    scale_y_continuous(position = "right") + scale_fill_gradient(low = "#D6DBF5",
    high = "#2E45B8") + coord_flip() + hrbrthemes::theme_ipsum(base_family = "Times",
    grid = "X") + theme(plot.title = element_text(hjust = 0, vjust = -0.5), plot.margin = unit(c(0,
    1, 0.5, 0.5), "cm"))
Final ModelFinal Model

Final Model

Según este árbol de decision, las variables más importantes son el OverTime_Yes, que toma mayor importancia comparada con las demás, el total de años trabajando y enseguida encontramos la satisfacción al entorno y el nivel del trabajo y el salario mensual.

El árbol planteado no mejora el modelo de referencia de regresión logística (accuracy = 0.77). Este porque un árbol solo es incline a alta varianza. Por esta razón, implementaré algoritmos de ensemble methods.

4.4 Bagging Tree

El Bootstrap aggregating (bagging) es un método que consegue reducir la varianza y el sobreajuste. El algoritmo funciona de esta manera:

Dado un conjunto de entrenamiento inicial de tamaño n, el bagging genera m nuevos conjuntos de entrenamiento, cada uno de tamaño n’, haciendo un remuestreo desde el conjunto inicial, uniformemente y con reemplazo (bootstrap sample). El muestreo con reemplazo asegura que cada bootstrap sea independiente de sus pares. Luego, los m modelos se ajustan utilizando las m muestras bootstrap y se combinan promediando el output.

Bagging process

Con estos datos, me espero que el bagging va a ser un buen modelo en cuanto mis variables no tienen separaciones lineales y hay muchas variables categorícas.

4.4.1 Tuneo del Modelo

library(doParallel)
library(tictoc)
registerDoParallel(makeCluster(3) -> cpu) 

nodesize_ <- c()
sampsize_ <- c()
Accuracy <- c()
Kappa <- c()
Auc <- c()

tic()
for (nodesize in c(20,40,60,80,100)) {
    for (sampsize in c(200,500,800,1200,1570)) {
bg <- train(data=upTrain,
                factor(attrition)~.,
                method="rf", trControl= control,
                #fijar mtry for bagging
                tuneGrid= expand.grid(mtry=c(51)), 
                ntree = 5000, 
                sampsize = sampsize, 
                nodesize = nodesize,
                #muestras con reemplazamiento
                replace = TRUE, 
                linout = FALSE) 

confusionMatrix <- confusionMatrix(
    bg$pred$pred, bg$pred$obs)

roc <- roc(response = bg$pred$obs,
                     predictor = bg$pred$Yes)

Acc_i <- confusionMatrix$overall[1]
Accuracy <- append(Accuracy, Acc_i)

K_i <- confusionMatrix$overall[2]
Kappa <- append(Kappa, K_i)

Auc_i <- roc$auc
Auc <- append(Auc, Auc_i)

nodesize_ <- append(nodesize_, nodesize)
sampsize_ <- append(sampsize_, sampsize)

dput("---------------")
dput(paste0("With nodesize= ", nodesize))
dput(paste0("With sampsize= ", sampsize))
dput(paste0("Accuracy: ",
                        round(confusionMatrix$overall[1], 3)))
print(roc$auc)
    }
}
toc()
stopCluster(cpu)

# Aggregate Metrics ----
bagging_results = cbind(
    data.frame(Accuracy, Kappa, Auc, nodesize = nodesize_, sampsize=sampsize_))

#save(bagging_results, file="bagging_results.RData")

# Clear cache ----
rm(Acc_i, K_i, Auc_i, nodesize, roc, sampsize)
rm(Accuracy, Auc, Kappa, nodesize_, sampsize_)
library(ggeconodist)

ggplot(bagging_results, aes(x = factor(nodesize), Auc)) + geom_econodist(tenth_col = "#879DDD",
    median_col = "#1D2F65", ninetieth_col = "#3252B1") + scale_y_continuous(position = "right",
    limits = range(0.7, 1)) + labs(x = "nodesize", title = "Tuning Bagging nodesize",
    subtitle = "Resultados finales bucle, parametro nodesize y comparacion con AUC") +
    theme_econodist(econ_text_col = "#3b454a", econ_plot_bg_col = "#E6EAF8", econ_grid_col = "#bbcad2",
        econ_font = "Times", light_font = "Times", bold_font = "Times") + theme(plot.title = element_text(face = "bold")) ->
    gg

grid.newpage()
left_align(gg, c("subtitle", "title", "caption")) %>%
    add_econodist_legend(econodist_legend_grob(family = "Times", tenth_col = "#879DDD",
        ninetieth_col = "#3252B1"), below = "subtitle") %>%
    grid.draw()

ggplot(bagging_results, aes(x = factor(nodesize), Accuracy)) + geom_econodist(tenth_col = "#879DDD",
    median_col = "#1D2F65", ninetieth_col = "#3252B1") + scale_y_continuous(position = "right",
    limits = range(0.7, 1)) + labs(x = "nodesize", title = "Tuning Bagging nodesize",
    subtitle = "Resultados finales bucle, parametro nodesize y comparacion con Accuracy") +
    theme_econodist(econ_text_col = "#3b454a", econ_plot_bg_col = "#E6EAF8", econ_grid_col = "#bbcad2",
        econ_font = "Times", light_font = "Times", bold_font = "Times") + theme(plot.title = element_text(face = "bold")) ->
    gg

grid.newpage()
left_align(gg, c("subtitle", "title", "caption")) %>%
    add_econodist_legend(econodist_legend_grob(family = "Times", tenth_col = "#879DDD",
        ninetieth_col = "#3252B1"), below = "subtitle") %>%
    grid.draw()
Tuning Nodesize ResultsTuning Nodesize Results

Tuning Nodesize Results

Haciendo validación cruzada, tengo que dejar un fold de tamaño (1974/5)=394, por lo tanto utilizaré (4/5)*1974=1579 observaciones training para construir el modelo, con lo cual fijare el sampsize máximo con 1578 observaciones.

Tuning Sampsize ResultsTuning Sampsize Results

Tuning Sampsize Results

Con estos gráficos de cajas y bigotes, se puede observar que los parámetros que llevan a las mejores métricas, en termines de área abajo la curva ROC y accuracy son sampsize=1570 y el valor más bajo de nodesize, que por razones de establead no elijaré. Me quedaré con un nodesize de 40.

4.4.2 Modelo final

library(randomForest)
bg <- randomForest(data = upTrain, factor(attrition) ~ ., mtry = 51, ntree = 1000,
    sampsize = 1570, nodesize = 40, replace = TRUE, importance = TRUE)

4.4.2.1 Out-Of-Bag Error

El Out-Of-Bag (OOB) es el error cometido en las observaciones que no caen en la muestra en cada iteración-árbol, y por tanto pueden ser tomados como observaciones test y sirven para observar el error cometido sobre test a medida que avanzan las iteracciones.

as.tibble(cbind(OOB = bg$err.rate[, 1], ntree = seq(1:nrow(bg$err.rate)))) %>%
    ggplot(aes(x = ntree, y = OOB)) + geom_step(aes(col = OOB)) + scale_color_continuous(low = "#2E45B8",
    high = "#D6DBF5") + hrbrthemes::theme_ipsum(base_family = "Times", base_size = 10) +
    labs(title = "Out-Of-Bag Error", subtitle = "Bagging model") + theme(title = element_text(face = "bold"),
    legend.position = "none", panel.background = element_rect(color = "#3b454a"),
    axis.title.y = element_text(size = 10), axis.text.x = element_text(face = "bold",
        colour = "#3D3D3D", size = 10))
Bagging Out-Of-Bag Error

Bagging Out-Of-Bag Error

Se puede notar como el error menor se establece alrededor de un ntree=1000.

4.4.2.2 Matriz de Confusión y Importancia de Variables

draw_confusion_matrix(confusionMatrix(bg$predicted, bg$y), "#D6DBF5", "#2E45B8")

ggplot(vip::vi(bg) %>%
    filter(Importance > 25) %>%
    mutate(Variable = stringr::str_to_title(Variable)) %>%
    mutate(Variable = str_replace_all(Variable, "_", " ")), aes(x = reorder(Variable,
    Importance), y = Importance)) + ggchicklet::geom_chicklet(aes(fill = Importance),
    radius = grid::unit(6, "pt"), width = 0.8, show.legend = F) + labs(x = NULL,
    title = "Variable Importance Plot Bagging") + scale_y_continuous(position = "right") +
    scale_fill_gradient(low = "#D6DBF5", high = "#2E45B8") + coord_flip() + hrbrthemes::theme_ipsum(base_family = "Times",
    grid = "X") + theme(plot.title = element_text(vjust = -1), plot.margin = unit(c(0,
    1, 1, 0.5), "cm"))
Bagging Final ModelBagging Final Model

Bagging Final Model

Entre las muestras de las iteracciones calculadas, en promedio la variable más influyente es MonthlyIncome, el salario bruto de los empleados. En seguida, se encuentran JobSatisfaction y EnvironmentSatisfaction, es decir los nivel de satisfacción con el trabajo y su entorno en la empresa.

Hay que tener en cuenta que con la técnica de bagging, entre los árboles un par de variables serán en todos muy parecidos. Por esto, a través del random forest podemos añadir aleatoriedad en las variables entre los diferentes árboles, y esto también podrá reducir la varianza del modelo.

4.5 Random Forest

El algoritmo Random Forest, aprovechando de las ventajas del bagging, nos va a ayudar más a la hora de seleccionar las variables, por el hecho que esta técnica incorpora dos fuentes de variabilidad, el remuestreo de observaciones y de variables utilizadas en cada modelo. De esta manera podemos generalizar más, reduciendo el sobreajuste y conservando a la vez las relaciones particulares entre los datos.

4.5.1 Tuneo del Modelo

Para tunear los parámetros haré un bucle similar a como hecho previamente en la modelización del bagging. En este caso, tendré que tunear también el parámetro mtry, el numero de variables a seleccionar cada vez. Este parámetro si lo fijamos al máximo numero de variables, corresponderá a hacer el bagging.

4.5.1.1 Tuneo del modelo

registerDoParallel(makeCluster(3) -> cpu)

nodesize_ <- c()
sampsize_ <- c()
Accuracy <- c()
Kappa <- c()
Auc <- c()

tic()
for (nodesize in c(10, 20, 30, 40, 60, 50, 80, 100)) {
    for (sampsize in c(200, 350, 500, 800, 1000, 1200, 1350, 1570)) {
        rf <- train(data = upTrain, factor(attrition) ~ ., method = "rf", trControl = control,
            tuneGrid = expand.grid(mtry = c(5, 10, 20, 30, 40, 50)), ntree = 1000,
            sampsize = sampsize, nodesize = nodesize, replace = TRUE, linout = FALSE)

        cm <- confusionMatrix(rf$pred$pred, rf$pred$obs)

        roc <- roc(response = rf$pred$obs, predictor = rf$pred$Yes)

        Acc_i <- cm$overall[1]
        Accuracy <- append(Accuracy, Acc_i)

        K_i <- cm$overall[2]
        Kappa <- append(Kappa, K_i)

        Auc_i <- roc$auc
        Auc <- append(Auc, Auc_i)

        nodesize_ <- append(nodesize_, nodesize)
        sampsize_ <- append(sampsize_, sampsize)

        dput("---------------")
        dput(paste0("With nodesize= ", nodesize))
        dput(paste0("With sampsize= ", sampsize))
        dput(paste0("Accuracy: ", round(cm$overall[1], 3)))
        rf$results[1:3]
        print(roc$auc)
    }
}
toc()  #32 min. elapsed
stopCluster(cpu)

# Aggregate Metrics ----
rf_results = cbind(data.frame(Accuracy, Kappa, Auc, nodesize = nodesize_, sampsize = sampsize_))

# save(rf_results, file='rf_results.RData')
stopCluster(cpu)
cbind(mtry = linebreak(c(45)), nodesize = c(20), sampsize = c(1570)) %>%
    kable(format = "html", align = c(rep("l", 4)), bootstrap_options = "basic", caption = "Best Tune Random Forest") %>%
    row_spec(0, monospace = T, font_size = 14)
Best Tune Random Forest
mtry nodesize sampsize
45 20 1570

He querido también visualizar las interacciones entre ellos:

library(htmltools)

# Render a bar chart with a label on the left
bar_chart <- function(
    label, width = "100%", height = "16px", 
    fill = "#00bfc4", background = NULL){
       bar <- div(style = list(
                      background = fill, 
                      width = width, 
                      height = height))
       chart <- div(style = list(
                      flexGrow = 1,
                                    marginLeft = "8px",
                                    background = background), 
                             bar)
       div(style = list(
             display = "flex", 
             alignItems = "center"), 
            label, 
            chart)
}

reactable(
rf_results %>% 
    mutate(nodesize = as_factor(nodesize),
                 sampsize = as_factor(sampsize)) %>% 
    dplyr::group_by(nodesize, sampsize) %>% 
    select(-Kappa) %>% summarize(
        Accuracy = mean(Accuracy),
        Auc = mean(Auc)),
defaultSorted = list(sampsize = "asc", 
                                         nodesize = "desc"),
pagination = FALSE, highlight = TRUE, height = 450,
showSortIcon = TRUE, bordered = TRUE,
columns = list(
    
 Accuracy = colDef(
#   format = colFormat(digits = 2),
     name = "Accuracy", 
   align = "left", 
   cell = function(value){
     width <- paste0(value/1 * 100, "%")
     bar_chart(value, 
                        width = width,
                        fill = "#2E45B8", 
                        background = "#e1e1e1")}),
 
 Auc = colDef(
     name = "AUC", 
     align = "left", 
     cell = function(value){
        width <- paste0(value/1 * 100, "%")
    bar_chart(value, 
                        width = width, 
                        fill = "#2E45B8", 
                        background = "#e1e1e1")})
    )
  )

Como podemos explorar a partir de la tabla, unos buenos parámetros serían nodesize=20 y sampsize=1570. Ahora miramos le andamiento del mtry:

# A partir del output del tuneo, he podido crear este dataframe 
tribble(
   ~mtry, ~Accuracy, ~ Kappa,
    #----|----------|--------
    5,     0.912,     0.825,
    10,    0.918,     0.836,
    15,    0.926,     0.851,
    20,    0.920,     0.841,
    25,    0.930,     0.860,
    30,    0.923,     0.845,
    35,    0.929,     0.857,
    40,    0.919,     0.838,
    45,    0.931,     0.861,
    50,    0.920,     0.840,
) -> rf_result_mtry

# Plot ----
library(apexcharter)
apexchart(
    width = 830,
        ax_opts = list(
            chart = list(type = "line"),
            stroke = list(curve = "smooth"),
            grid = list(
                borderColor = "#e7e7e7",
                row = list(
                    colors = c("#f3f3f3", "transparent"),
                    opacity = 0.5
                )
            ),
            markers = list(style = "inverted", size = 4),
            series = list(
                list(name = "Accuracy",
                         data = rf_result_mtry$Accuracy),
                list(name = "Kappa",
                         data = rf_result_mtry$Kappa)
            ),
            title = list(text = "Tuneo Random Forest",
                                     align = "center"),
            xaxis = list(categories = rf_result_mtry$mtry)
        )
    ) %>%
    ax_subtitle(
        text = "Resultados remuestreo entre los parametros tuning", 
        align = "center") %>% 
    ax_yaxis(min = 0.815, max = 0.935) %>%
    ax_stroke(curve = "stepline", width = 2) %>%
    ax_colors("#4B69CE", "#B0BDE9")

Tuneo del mtry

Observamos que un numero de variables de 45 optimiza los valores.

4.5.2 Modelo final

library(randomForest)
rf <- randomForest(data = upTrain, factor(attrition) ~ ., mtry = 45, ntree = 2000,
    sampsize = 1570, nodesize = 20, replace = TRUE, importance = TRUE)

4.5.2.1 Out-Of-Bag Error

Queremos suficientes árboles para estabilizar el error pero usando demasiados árboles es innecesariamente ineficiente.

as.tibble(cbind(OOB = rf$err.rate[, 1], ntree = seq(1:nrow(rf$err.rate)))) %>%
    ggplot(aes(x = ntree, y = OOB)) + geom_step(aes(colour = OOB)) + scale_color_continuous(low = "#2E45B8",
    high = "#D6DBF5") + hrbrthemes::theme_ipsum(base_family = "Times", base_size = 10) +
    labs(title = "Out-Of-Bag Error", subtitle = "Random Forest model") + theme(legend.position = "none",
    panel.background = element_rect(color = "#3b454a"), title = element_text(face = "bold"),
    axis.title.y = element_text(size = 10), axis.text.x = element_text(face = "bold",
        colour = "#3D3D3D", size = 10))
Random Forest Out-Of-Bag Error

Random Forest Out-Of-Bag Error

De hecho, el error se establece entre de 1000 y 1500 árboles.

4.5.2.2 Matriz de Confusión y Importancia de Variables

draw_confusion_matrix(confusionMatrix(rf$predicted, rf$y), "#D6DBF5", "#2E45B8")

ggplot(vip::vi(rf) %>%
    filter(Importance > 30) %>%
    mutate(Variable = stringr::str_to_title(Variable)) %>%
    mutate(Variable = str_replace_all(Variable, "_", " ")), aes(x = reorder(Variable,
    Importance), y = Importance)) + ggchicklet::geom_chicklet(aes(fill = Importance),
    radius = grid::unit(6, "pt"), width = 0.8, show.legend = F) + labs(x = NULL,
    title = "Variable Importance Plot Random Forest") + scale_y_continuous(position = "right") +
    scale_fill_gradient(low = "#D6DBF5", high = "#2E45B8") + coord_flip() + hrbrthemes::theme_ipsum(base_family = "Times",
    grid = "X") + theme(plot.title = element_text(vjust = -1), plot.margin = unit(c(0,
    1, 1, 0.5), "cm"))
Random Forest Final ModelRandom Forest Final Model

Random Forest Final Model

Similar con el modelo de bagging, la variable más influyente es el salario bruto de los empleados. Por la misma razón, DailyRate (la tarifa diaria de trabajo) toma mucha importancia. Encontramos también los niveles de satisfacción del trabajo y del entorno.

4.6 Gradient Boosting Machine

Los métodos que voy a emplear están basados sobre una estrategía diferente de construcción. A diferencia del random forest, que construye los modelos y luego los promedia, el gradient boosting machine construye los modelos secuencialmente. En cada iteración, se entrena un nuevo modelo de aprendizaje con respecto al error de todo el conjunto aprendido hasta ahora.

Gradient Boosting Machine construction method

4.6.1 Tuneo del Modelo

Una peculiaridad de este modelo es que podemos controlar muchos parámetros en el entrenamiento. La función modelLookup(), especificando el modelo ("gbm" en este caso) nos devuelve la lista.

library(kableExtra)
modelLookup("gbm")[2:3] %>%
    cbind(Description = linebreak(c("El número total de árboles para emplear.",
        "El número d de divisiones en cada árbol, este controla la complejidad del conjunto impulsado.",
        "Controla la rapidez con la que el algoritmo desciende hacía el gradient descent.",
        "Controla si utiliza o no una fracción de las observaciones de entrenamiento disponibles."))) %>%
    kable(format = "html", align = c(rep("l", 4)), caption = "Descripcion de los parámetros en GBM") %>%
    row_spec(0, font_size = 14) %>%
    column_spec(1, monospace = T)
Descripcion de los parámetros en GBM
parameter label Description
n.trees # Boosting Iterations El número total de árboles para emplear.
interaction.depth Max Tree Depth El número d de divisiones en cada árbol, este controla la complejidad del conjunto impulsado.
shrinkage Shrinkage Controla la rapidez con la que el algoritmo desciende hacía el gradient descent.
n.minobsinnode Min. Terminal Node Size Controla si utiliza o no una fracción de las observaciones de entrenamiento disponibles.
library(tictoc)
registerDoParallel(makeCluster(3) -> cpu)
tic()
gbm_results <- train(factor(attrition) ~ ., data = upTrain, method = "gbm", trControl = control,
    tuneGrid = expand.grid(shrinkage = c(0.01, 0.05, 0.1, 0.5, 1), n.minobsinnode = c(5,
        20, 35), n.trees = c(1000, 5000, 10000), interaction.depth = c(2)), distribution = "bernoulli",
    bag.fraction = 1, verbose = FALSE)
toc()  #17 min elapsed
stopCluster(cpu)

Podemos ver el comportamiento en ajuste del tuneo. La máxima accuracy se alcanza con estos valores:

paste0(cat("Best Hyperparameters Tuning: \n"), xfun::tree(as.list(gbm_results$bestTune)))
r > Best Hyperparameters Tuning:
r > [1] "List of 4"                       " |-n.trees          : num 10000"
r > [3] " |-interaction.depth: num 2"     " |-shrinkage        : num 1"    
r > [5] " |-n.minobsinnode   : num 35"
plot(gbm_results, output = "ggplot", "line", layout = c(3, 1), par.settings = list(superpose.line = list(lwd = 1,
    col = c("#2A788E", "#2E45B8", "#440154")), superpose.symbol = list(pch = 19,
    cex = 0.6, col = c("#2A788E", "#2E45B8", "#440154")), strip.background = list(col = "#D6DBF5")))

El numero de árboles construido es alto, pero eso nos lleva a una precisión más alta. El numero de observaciones en el nodo final puede ser bastante pequeño (minobsinnode = 20), no obstante he fijado para el modelo final el valor de 35, construyendo un modelo no demasiado complejo, con bajo sesgo y no tan alta varianza, pero más estable.

4.6.2 Modelo final

gbm <- train(factor(attrition) ~ ., data = upTrain, method = "gbm", trControl = control,
    tuneGrid = expand.grid(shrinkage = c(1), n.minobsinnode = c(35), n.trees = c(10000),
        interaction.depth = c(2)), distribution = "bernoulli", bag.fraction = 1,
    verbose = FALSE)

4.6.2.1 Matriz de Confusión y Importancia de Variables

draw_confusion_matrix(confusionMatrix(gbm$pred$pred, gbm$pred$obs), "#D6DBF5", "#2E45B8")

as_tibble(vip::vi(gbm)) %>%
    filter(Importance > 10) %>%
    mutate(Variable = stringr::str_to_title(Variable)) %>%
    mutate(Variable = str_replace_all(Variable, "_", " ")) %>%
    ggplot(aes(x = reorder(Variable, Importance), y = Importance)) + ggchicklet::geom_chicklet(aes(fill = Importance),
    radius = grid::unit(6, "pt"), width = 0.8, show.legend = F) + labs(x = NULL,
    title = "Variable Importance Plot Gradient Boosting") + scale_y_continuous(position = "right") +
    scale_fill_gradient(low = "#D6DBF5", high = "#2E45B8") + coord_flip() + hrbrthemes::theme_ipsum(base_family = "Times",
    grid = "X") + theme(plot.title = element_text(vjust = -1), plot.margin = unit(c(0,
    1, 1, 0.5), "cm"))
GBM Final ModelGBM Final Model

GBM Final Model

A partir del gráfico de importancia de variables, destaca la variable OverTime_yes y MonthlyIcome, que juntas a StockOptionLevel y DailyRate nos confirmas que las retribuciones juegan un rol importante a la hora de decidir si dejar la empresa o no.

4.7 XGBoost

4.7.1 Tuneo del Modelo

modelLookup("xgbTree")[2:3] %>%
    cbind(Description = linebreak(c("El número total de árboles para emplear",
        "Se utiliza para controlar el sobreajuste ya que un valor mayor permitirá que el modelo aprenda relaciones muy específicas para una muestra particular.",
        "Hace el modelo más robusto reduciendo los pesos en cada paso.", "Controla si utiliza o no una fracción de las observaciones de entrenamiento disponibles.",
        "Gamma especifica la reducción positiva mínima en la loss función requerida para hacer una división del nodo.",
        "Denota las columnas a remuestrar para cada árbol.", "Define la suma mínima de pesos de todas las observaciones requeridas en un child node. Por valores superiores impiden que un modelo aprenda relaciones que podrían ser muy específicas."))) %>%
    kable(format = "html", align = c(rep("l", 4)), bootstrap_options = "basic", caption = "Descripcion de los parámetros en XGBoost") %>%
    column_spec(2, width = "3.2cm") %>%
    row_spec(0, font_size = 14) %>%
    column_spec(1, monospace = T, width = "4.2cm")
Descripcion de los parámetros en XGBoost
parameter label Description
nrounds # Boosting Iterations El número total de árboles para emplear
max_depth Max Tree Depth Se utiliza para controlar el sobreajuste ya que un valor mayor permitirá que el modelo aprenda relaciones muy específicas para una muestra particular.
eta Shrinkage Hace el modelo más robusto reduciendo los pesos en cada paso.
gamma Minimum Loss Reduction Controla si utiliza o no una fracción de las observaciones de entrenamiento disponibles.
colsample_bytree Subsample Ratio of Columns Gamma especifica la reducción positiva mínima en la loss función requerida para hacer una división del nodo.
min_child_weight Minimum Sum of Instance Weight Denota las columnas a remuestrar para cada árbol.
subsample Subsample Percentage Define la suma mínima de pesos de todas las observaciones requeridas en un child node. Por valores superiores impiden que un modelo aprenda relaciones que podrían ser muy específicas.
registerDoParallel(makeCluster(3) -> cpu)
xgbm_results <- train(factor(attrition) ~ ., data = upTrain, verbose = FALSE, method = "xgbTree",
    trControl = control, tuneGrid = expand.grid(min_child_weight = c(5, 30, 50),
        eta = c(0.1, 0.05, 0.01), nrounds = c(500, 1000, 5000, 10000), max_depth = 6,
        gamma = 0, colsample_bytree = 1, subsample = 1))
stopCluster(cpu)

Para caret, estos son los parametros mejores:

paste0(cat("Best Hyperparameters Tuning: \n"), xfun::tree(as.list(xgbm_results$bestTune)))
r > Best Hyperparameters Tuning:
r > [1] "List of 7"                     " |-nrounds         : num 1000"
r > [3] " |-max_depth       : num 6"    " |-eta             : num 0.05"
r > [5] " |-gamma           : num 0"    " |-colsample_bytree: num 1"   
r > [7] " |-min_child_weight: num 5"    " |-subsample       : num 1"

Por supuesto, esta maquina nos devuelve como mejores los parámetros que maximizan la accuracy, no obstante hay que tener en cuenta la estabilidad del modelo a la hora de ajustar. Por esta razón siempre hace falta ver el andamiento del entrenamiento:

plot(xgbm_results, output = "ggplot", "line", layout = c(3, 1), par.settings = list(superpose.line = list(lwd = 1,
    col = c("#2A788E", "#2E45B8", "#440154")), superpose.symbol = list(pch = 19,
    cex = 0.6, col = c("#2A788E", "#2E45B8", "#440154")), strip.background = list(col = "#D6DBF5")))

A partir de este gráfico he decidido de quedarme con min_child_weight=35, en cuanto encuentra el compromiso mejor para estos ajustes.

4.7.2 Modelo final

xgbm <- train(factor(attrition) ~ ., data = upTrain, method = "xgbTree", trControl = control,
    tuneGrid = expand.grid(eta = c(0.05), min_child_weight = c(35), nrounds = c(10000),
        gamma = c(0), subsample = c(1), max_depth = c(6), colsample_bytree = c(1)),
    verbose = FALSE)

4.7.2.1 Matriz de Confusión y Importancia de Variables

draw_confusion_matrix(confusionMatrix(xgbm$pred$pred, xgbm$pred$obs), "#D6DBF5",
    "#2E45B8")


as_tibble(vip::vi(xgbm)) %>%
    filter(Importance > 20) %>%
    mutate(Variable = stringr::str_to_title(Variable)) %>%
    mutate(Variable = str_replace_all(Variable, "_", " ")) %>%
    ggplot(aes(x = reorder(Variable, Importance), y = Importance)) + ggchicklet::geom_chicklet(aes(fill = Importance),
    radius = grid::unit(6, "pt"), width = 0.8, show.legend = F) + labs(x = NULL,
    title = "Variable Importance Plot XGBM") + scale_y_continuous(position = "right") +
    scale_fill_gradient(low = "#D6DBF5", high = "#2E45B8") + coord_flip() + hrbrthemes::theme_ipsum(base_family = "Times",
    grid = "X") + theme(plot.title = element_text(vjust = -1), plot.margin = unit(c(0,
    1, 1, 0.5), "cm"))
XGBM Final ModelXGBM Final Model

XGBM Final Model

Aquí las variables más importantes son muy parecidas a las del GBM.


Consideraciones

En seguida, este gráfico de cajas y bigotes compara los modelos empleados hasta ahora, con los parametros mejores según cada tuneado. Para ello, en este caso, haré validación cruzada repetida. La métrica que he elegido para la comparación es la área abajo de la curva ROC (AUC).

Arbol <- cruzadaarbolbin(data = upTrain, vardep = "attrition", listconti = predictors,
    listclass = c(""), grupos = 5, sinicio = 112, repe = 5, cp = c(0), minbucket = 105)
Arbol$modelo = "Arbol"

Bagging <- cruzadarfbin(data = upTrain, vardep = "attrition", listconti = predictors,
    listclass = c(""), grupos = 5, sinicio = 112, repe = 5, nodesize = 40, sampsize = 1570,
    mtry = 51, ntree = 1000, replace = TRUE)  #49 min
Bagging$modelo = "Bagging Tree"

RandomForest <- cruzadarfbin(data = upTrain, vardep = "attrition", listconti = predictors,
    listclass = c(""), grupos = 5, sinicio = 112, repe = 5, nodesize = 20, sampsize = 1570,
    mtry = 45, ntree = 2000, replace = TRUE)
RandomForest$modelo = "Random Forest"

GradientBoosting <- cruzadagbmbin(data = upTrain, vardep = "attrition", listconti = predictors,
    listclass = c(""), grupos = 5, sinicio = 112, repe = 5, n.minobsinnode = 35,
    shrinkage = 1, n.trees = 10000, interaction.depth = 2)
GradientBoosting$modelo = "GBM"

XGBoost <- cruzadaxgbmbin(data = upTrain, vardep = "attrition", listconti = predictors,
    listclass = c(""), grupos = 5, sinicio = 1234, repe = 5, min_child_weight = 35,
    eta = 0.05, nrounds = 10000, max_depth = 6, gamma = 0, colsample_bytree = 1,
    subsample = 1)
XGBoost$modelo = "XGBoost"
best_model <- rbind(Logistica, Arbol, Bagging, RandomForest, GradientBoosting, XGBoost)

ggplot(data = best_model, aes(x = factor(modelo, levels = c("Logistica", "Arbol",
    "Bagging", "Random Forest", "GBM", "XGBoost")), auc)) + geom_econodist(width = 0.8,
    tenth_col = "#879DDD", median_col = "#1D2F65", median_point_size = 1.3, ninetieth_col = "#2E45B8") +
    hrbrthemes::theme_ipsum(grid = "XY", base_family = "Times", base_size = 10) +
    scale_y_continuous(limits = c(0.85, 1)) + labs(title = "Comparación entre técnicas de árboles",
    subtitle = "Area abajo de la curva ROC", y = "AUC", x = NULL) + theme(title = element_text(face = "bold"),
    axis.title.y = element_text(size = 10), axis.text.x = element_text(face = "bold",
        colour = "#3D3D3D", size = 10)) + theme_econodist(econ_text_col = "#3b454a",
    econ_plot_bg_col = "white", econ_grid_col = "#bbcad2", econ_font = "Times", light_font = "Times",
    bold_font = "Times") + theme(plot.title = element_text(face = "bold")) ->
    gg

grid.newpage()
left_align(gg, c("subtitle", "title", "caption")) %>%
    add_econodist_legend(econodist_legend_grob(family = "Times", tenth_col = "#879DDD",
        med_col = "#1D2F65", ninetieth_col = "#1D2F65"), below = "subtitle") %>%
    grid.draw()
Comparación AUC entre modelos

Comparación AUC entre modelos

  • Todos los métodos ensemble tienen las mejores métricas, y aportan mejores ajustes al árbol solo y a la regresion logistica.
  • El modelo ganador es el Gradient Boosting, aunque el Random Forest, y el XGboost están justo abajo.
ggplot(data = best_model %>%
    filter(modelo %in% c("Bagging", "Random Forest", "GBM", "XGBoost")), aes(x = factor(modelo,
    levels = c("Bagging", "Random Forest", "GBM", "XGBoost")), as.numeric(tasa))) +
    geom_econodist(width = 0.8, tenth_col = "#879DDD", median_col = "#1D2F65", median_point_size = 1.3,
        ninetieth_col = "#2E45B8") + hrbrthemes::theme_ipsum(grid = "XY", base_family = "Times",
    base_size = 10) + labs(title = "Comparación entre técnicas de árboles", subtitle = "Tasa de fallo",
    y = "AUC", x = NULL) + theme(title = element_text(face = "bold"), axis.title.y = element_text(size = 10),
    axis.text.x = element_text(face = "bold", colour = "#3D3D3D", size = 10)) + theme_econodist(econ_text_col = "#3b454a",
    econ_plot_bg_col = "white", econ_grid_col = "#bbcad2", econ_font = "Times", light_font = "Times",
    bold_font = "Times") + theme(plot.title = element_text(face = "bold")) ->
    gg

grid.newpage()
left_align(gg, c("subtitle", "title", "caption")) %>%
    add_econodist_legend(econodist_legend_grob(family = "Times", tenth_col = "#879DDD",
        med_col = "#1D2F65", ninetieth_col = "#1D2F65"), below = "subtitle") %>%
    grid.draw()

ggplot(data = best_model %>%
    filter(modelo %in% c("Bagging", "Random Forest", "GBM", "XGBoost")), aes(x = factor(modelo,
    levels = c("Bagging", "Random Forest", "GBM", "XGBoost")), auc)) + geom_econodist(width = 0.8,
    tenth_col = "#879DDD", median_col = "#1D2F65", median_point_size = 1.3, ninetieth_col = "#2E45B8") +
    hrbrthemes::theme_ipsum(grid = "XY", base_family = "Times", base_size = 10) +
    labs(title = "Comparación entre técnicas de árboles", subtitle = "Area abajo de la curva ROC",
        y = "AUC", x = NULL) + theme(title = element_text(face = "bold"), axis.title.y = element_text(size = 10),
    axis.text.x = element_text(face = "bold", colour = "#3D3D3D", size = 10)) + theme_econodist(econ_text_col = "#3b454a",
    econ_plot_bg_col = "white", econ_grid_col = "#bbcad2", econ_font = "Times", light_font = "Times",
    bold_font = "Times") + theme(plot.title = element_text(face = "bold")) ->
    gg

grid.newpage()
left_align(gg, c("subtitle", "title", "caption")) %>%
    add_econodist_legend(econodist_legend_grob(family = "Times", tenth_col = "#879DDD",
        med_col = "#1D2F65", ninetieth_col = "#1D2F65"), below = "subtitle") %>%
    grid.draw()
Mejores modelosMejores modelos

Mejores modelos

Con este gráfico podemos ver mejor los resultados de las iteracciones de los modelos planteados. El modelo de gradient boosting tiene un AUC de casi 0.99, con una tasa de fallo alrededor de 0.04.

Luego, para observar hasta que punto la variación del modelo influye sobre la importancia de las variables, he creado un gráfico de barras apiladas, puesto a ordenar las variables según importancia total de los modelos juntos.

library(ggchicklet)
library(vip)
inner_join(vi(xgbm), vi(gbm), by = "Variable") %>%
    inner_join(vi(rf), by = "Variable") %>%
    inner_join(vi(bg), by = "Variable") %>%
#   inner_join(vi(arbol), by= "Variable") %>% 
    rename(
        "XGBM" = Importance.x,
        "GBM" = Importance.y,
        "Random Forest" = Importance.x.x,
        "Bagging" = Importance.y.y,
#       "Tree" = Importance
    ) %>%
    mutate(
        Variable = stringr::str_to_title(Variable)) %>%
    mutate(Variable = str_replace_all(Variable, "_", " ")) %>%
    pivot_longer(!Variable,
                             names_to = "Model",
                             values_to = "Importance") %>%
    arrange(desc(Importance)) %>%
    ggplot(aes(reorder(Variable, Importance),
                         Importance,
                         group = Model, 
                         fill = Model)) +
    geom_chicklet(width = 0.8,
                                radius = grid::unit(6, "pt")) +
    scale_y_continuous(position = "right",
                                         breaks = seq(0,300, by = 50)) +
    scale_fill_manual(
        name = NULL,
        values = c(
#           "Tree" = "#D6DBF5",
            "Bagging" = "#B0B9E7",
            "Random Forest" = "#8B98D9",
            "GBM" = "#6576CB",
            "XGBM" = "#4055BE"
        )
    ) +
    guides(fill = guide_legend(nrow = 1)) +
    coord_flip() +
    labs(
        x = NULL,
        y = NULL,
        fill = NULL,
        title = "Importancia variables según cada modelo ",
        subtitle = "",
        caption = "El árbol de decision no ha sido considerado en cuanto no todas las variables entran en el modelo"
    ) +
    hrbrthemes::theme_ipsum(base_family = "Times", 
                                                    grid = "Xx",
                                                    base_size = 12) +
    theme(
        plot.title = element_text(vjust = -1),
        legend.text = element_text(size=12),
        plot.margin = unit(c(0, 1, 1, 0.5), "cm"),
        legend.position = "top",
    )

  • Podemos observar como las variable más influyentes son MonthlyIcome y OverTime_Yes, es decir las cosas cosas que influyen más los empleados a la hora de dejar una empresa son el salario mensual y si trabajan de sobra a las horas estandard.
  • La edad y los años pasados a trabajar en la empresa también son importantes. Esto es normal en cuanto hay que tener en cuenta también los que van por el jubilado.
  • Otro conjunto de variables que destacan sus influencia son las que miden el nivel de satisfacción general, es decir EnvironmentSatisfaction, JobSatisfaction y RelationshipSatisfaction.

Porque este gráfico es muy importante? Porque a partir de ahora, voy a emplear otras técnicas de machine learning a partir de este conjunto de variables más influyentes a la hora de medir el índice de deserción de los empleados. Seguímos.

Nuevo conjunto Train/Test con variables más influyentes

upTrain2 <- upTrain %>%
    select("attrition", "monthly_income", "over_time_yes", "age", "daily_rate", "environment_satisfaction",
        "distance_from_home", "job_satisfaction", "stock_option_level", "total_working_years",
        "num_companies_worked", "hourly_rate", "years_at_company", "percent_salary_hike",
        "relationship_satisfaction", "job_level", "job_involvement", "job_role_research_scientist",
        "years_since_last_promotion", "marital_status_single", "work_life_balance")

Test2 <- Test %>%
    select("attrition", "monthly_income", "over_time_yes", "age", "daily_rate", "environment_satisfaction",
        "distance_from_home", "job_satisfaction", "stock_option_level", "total_working_years",
        "num_companies_worked", "hourly_rate", "years_at_company", "percent_salary_hike",
        "relationship_satisfaction", "job_level", "job_involvement", "job_role_research_scientist",
        "years_since_last_promotion", "marital_status_single", "work_life_balance")

4.8 Support Vector Machines

4.8.1 SVM Lineal

Como hemos podido ver a partir de la EDA, no hay separacíon lineal entre los datos. Entonces, podemos ya decir que entre los SVM que voy a emplear, los con un kernel linel no van a funcionar bien con estos datos.

4.8.1.1 Tuneo del Modelo

registerDoParallel(makeCluster(3) -> cpu)
tic()
set.seed(112)  # for reproducibility
svm_lin_result <- train(factor(attrition) ~ ., data = upTrain2, method = "svmLinear",
    verbose = FALSE, trControl = control, tuneGrid = expand.grid(C = c(0.01, 0.05,
        0.1, 0.2, 0.5, 1, 2, 5, 10, 20, 50)), )
toc()  #5.84min (15.1 min con todas las variables)
stopCluster(cpu)

Visualizamos los resultados del tuneo:

cbind(svm_lin_result$bestTune, getTrainPerf(svm_lin)) %>%
    kable(format = "html", bootstrap_options = "basic", caption = "Best Tune", digits = 3)
Best Tune
C TrainAccuracy TrainKappa method
0.01 0.771 0.542 svmLinear
ggplot(svm_lin_result) + geom_line(colour = "#2E45B8", size = 0.8) + geom_point(colour = "#5670C0") +
    hrbrthemes::theme_ipsum(ticks = T, base_family = "Times", base_size = 12) + labs(title = "Tuning Results",
    subtitle = "SVM Lineal") + theme(legend.position = "none", panel.background = element_rect(color = "#3b454a"),
    title = element_text(face = "bold"), axis.title.y = element_text(size = 12),
    axis.title.x = element_text(colour = "#3D3D3D", size = 12))

4.8.1.2 Modelo Final

svm_lin <- train(factor(attrition) ~ ., data = upTrain2, method = "svmLinear", verbose = FALSE,
    trControl = control, tuneGrid = expand.grid(C = c(0.01)))
draw_confusion_matrix(confusionMatrix(svm_lin$pred$pred, svm_lin$pred$obs), "#D6DBF5",
    "#2E45B8")

roc(svm_lin$pred$obs, svm_lin$pred$Yes) %>%
    ggroc(colour = "#2E45B8", size = 0.8) + annotate("text", x = 0.08, y = 0.88,
    label = "bold(AUC): 0.827", family = "Times", parse = TRUE) + hrbrthemes::theme_ipsum(ticks = T,
    base_family = "Times", base_size = 12) + labs(title = "ROC Curve", subtitle = "SVM Lineal") +
    theme(legend.position = "none", panel.background = element_rect(color = "#3b454a"),
        title = element_text(face = "bold"), axis.title.y = element_text(size = 12),
        axis.title.x = element_text(colour = "#3D3D3D", size = 12))
SVM LinealSVM Lineal

SVM Lineal

Entre los modelos empleados hasta ahora, no aporta buenas métricas. Vamos a construir una maquina de suporte vectorial con un kernel más complejo.

4.8.2 SVM Polinomial

library(kableExtra)
modelLookup("svmPoly")[2:3] %>%
    cbind(description = linebreak(c("El grado de la fúncion polinomial", "El parametro que ajusta el kernel a la hora de dividir las predicciones",
        "El parámetro de regularización (C), la constante C del término de regularización en la formulación de Lagrange, indica a la maquina de suporte cuánto evitar la clasificación errónea de cada ejemplo de entrenamiento."))) %>%
    kable(format = "html", align = c(rep("l", 3)), caption = "Descripcion de los parámetros en SVM Polinomial") %>%
    row_spec(0, font_size = 14) %>%
    column_spec(1, width = 12, monospace = T) %>%
    column_spec(2, width = 14)
Descripcion de los parámetros en SVM Polinomial
parameter label description
degree Polynomial Degree El grado de la fúncion polinomial
scale Scale El parametro que ajusta el kernel a la hora de dividir las predicciones
C Cost El parámetro de regularización (C), la constante C del término de regularización en la formulación de Lagrange, indica a la maquina de suporte cuánto evitar la clasificación errónea de cada ejemplo de entrenamiento.

4.8.2.1 Tuneo del Modelo

registerDoParallel(makeCluster(3) -> cpu)
tic()
set.seed(112)  # for reproducibility
svm_poly_result <- train(factor(attrition) ~ ., data = upTrain2, method = "svmPoly",
    verbose = FALSE, trainControl = control, tuneGrid = expand.grid(C = c(0.01, 0.05,
        0.1, 0.2, 1), degree = c(2, 3), scale = c(0.1, 0.5, 1, 2)))
toc()  #18.45 min
stopCluster(cpu)

Esta tabla nos dice cual han sido los parámetros mejores:

cbind(svm_poly_result$bestTune, getTrainPerf(svm_poly)) %>%
    kable(format = "html", bootstrap_options = "basic", caption = "Best Tune", digits = 3)
Best Tune
degree scale C TrainAccuracy TrainKappa method
8 3 2 0.01 0.953 0.907 svmPoly
plot(svm_poly_result, output = "ggplot", "line", layout = c(2, 1), par.settings = list(superpose.line = list(lwd = 1,
    col = c("#D6DBF5", "#5670C0", "#2E45B8", "#2A788E")), superpose.symbol = list(pch = 19,
    cex = 0.6, col = c("#D6DBF5", "#5670C0", "#2E45B8", "#2A788E")), strip.background = list(col = "#D6DBF5")))

También es importante una constatación visual del tuneo. Podemos ver que la máxima accuracy se alcanza con un grado de 3, mejorando mucho el grado 2. Me parece normal como el conjunto de datos tiene muchas variables. El ajuste del kernel mejor es el 2, aunque con un polinómico de 3 grados es suficiente un valor entre 0.5 y 1

4.8.2.2 Modelo Final

svm_poly <- train(factor(attrition) ~ ., data = upTrain2, method = "svmPoly", verbose = FALSE,
    trControl = control, tuneGrid = expand.grid(C = c(0.01), degree = c(3), scale = c(2)))
draw_confusion_matrix(confusionMatrix(svm_poly$pred$pred, svm_poly$pred$obs), "#D6DBF5",
    "#2E45B8")

roc(svm_poly$pred$obs, svm_poly$pred$Yes) %>%
    ggroc(colour = "#2E45B8", size = 0.63) + annotate("text", x = 0.08, y = 0.92,
    label = "bold(AUC): 0.97", family = "Times", parse = TRUE) + hrbrthemes::theme_ipsum(ticks = T,
    base_family = "Times", base_size = 10) + labs(title = "ROC Curve", subtitle = "SVM Polinomial") +
    theme(legend.position = "none", panel.background = element_rect(color = "#3b454a"),
        title = element_text(face = "bold"), axis.title.y = element_text(size = 10),
        axis.text.x = element_text(face = "bold", colour = "#3D3D3D", size = 10))
SVM PolinomialSVM Polinomial

SVM Polinomial

La maquina de suporte vectorial con kernel polinómico mejora mucho la con kernel lineal. Con un accuracy de 0.95 y AUC de 0.97 es uno de los mejores modelos ajustados hasta ahora.

4.8.3 SVM Radial

Vamos a ajustar la última de las maquinas de suporte vectorial. Esta vez, entre los parámetros a tunear, encontramos como siempre el parámetro de regularización y sigma, que determina el alcance de una sola instancia de entrenamiento.

4.8.3.1 Tuneo del Modelo

registerDoParallel(makeCluster(3) -> cpu)
tic()
set.seed(112)  # for reproducibility
svm_rad_result <- train(factor(attrition) ~ ., data = upTrain2, method = "svmRadial",
    verbose = FALSE, trainControl = control, tuneGrid = expand.grid(C = c(0.01, 0.05,
        0.1, 0.2, 0.5, 1, 2, 5, 10, 30), sigma = c(0.01, 0.05, 0.1, 0.2, 0.5, 1,
        2, 5, 10, 30)), )
toc()  #10.71 min
stopCluster(cpu)

Estos son los parámetros mejores:

cbind(svm_rad_result$bestTune, getTrainPerf(svm_rad_result)) %>%
    kable(format = "html", bootstrap_options = "basic", caption = "Best Tune", digits = 3)
Best Tune
sigma C TrainAccuracy TrainKappa method
60 30 1 0.985 0.97 svmRadial

Ahora vemos como ha mejorado el tuneo dependiendo del valor de su sigma.

ggplot(subset(svm_rad_result$results), aes(x = sigma, y = Accuracy, col = factor(C))) +
    facet_wrap(C ~ ., ncol = 3) + xlab("Sigma") + guides(col = guide_legend(title = "C")) +
    scale_color_brewer(palette = "Paired") + geom_step() + geom_point(size = 0.8,
    shape = 3) + theme_minimal(base_family = "Times")

Se puede observar como a partir de un coste de regularización igual a 1, la maxima accuracy se puede alcanzar con un valor de sigma entre 2 y 5.

4.8.3.2 Modelo Final

set.seed(112)  # for reproducibility
svm_rad <- train(factor(attrition) ~ ., data = upTrain2, method = "svmRadial", verbose = FALSE,
    trControl = control, tuneGrid = expand.grid(C = c(30), sigma = c(1)))
draw_confusion_matrix(confusionMatrix(svm_rad$pred$pred, svm_rad$pred$obs), "#D6DBF5",
    "#2E45B8")

roc(svm_rad$pred$obs, svm_rad$pred$Yes) %>%
    ggroc(colour = "#2E45B8", size = 0.5) + annotate("text", x = 0.08, y = 0.75,
    label = "bold(AUC): 0.998", family = "Times", parse = TRUE) + hrbrthemes::theme_ipsum(ticks = T,
    base_family = "Times", base_size = 10) + labs(title = "ROC Curve", subtitle = "SVM Radial") +
    theme(legend.position = "none", panel.background = element_rect(color = "#3b454a"),
        title = element_text(face = "bold"), axis.title.y = element_text(size = 10),
        axis.text.x = element_text(face = "bold", colour = "#3D3D3D", size = 10))
SVM RadialSVM Radial

SVM Radial

Esta maquina de suporte vectorial con kernel radial alcanza las mejores métricas sobre estos datos:

  • Accuracy 0.998
  • AUC 0.998

Hay que tener en cuenta siempre que estos datos son ficticios, entonces no ha sido tan difícil encontrar métricas tan alta.

4.9 Bagging SVM

Para emplear el bagging del SVM he construido una función que me ha permitido hacer las computaciones mas rápidas a través de un bucle paralelizado que computa tantas muestras cuantas quiero, ajustando mi mejor modelo SVM lineal identificado antes y que devuelve un vector con las predicciones promediadas a partir de las filas de las distintas predicciones de los modelos sobre cada muestra.

Lo interesante de esta implementación es que puedo controlar cada parámetro de este algoritmo y también puedo sustituir el weak learner (en este caso, es el SVM lineal del paquete e1071) para realizar el bagging con otros modelos. Además la implementación en paralelo permite de realizar el algoritmo rápidamente y no pesa la cache en cuanto solo el vector final con las predicciones está guardado.

library(e1071)
library(doParallel)
library(tictoc)

BaggingSVM_Lin <- function(train, test, samples, size, cost){

registerDoParallel(makeCluster(3) -> cpu) 
tic()

predictions <- foreach(
    icount(samples),  #iterazioni
    .packages = "e1071", 
    .combine = cbind  #azione da fare per ogni iterazione
#adesso, parallel computing per velocizzare il loop
) %dopar% {
    # bootstrap sample dei dati train
    index <- sample.int(n=nrow(train), 
                                         #qui definiamo quante osservazioni ogni sample
                                         size = floor(size*nrow(train)),
                                         replace = TRUE)
    sample <- train[index, ]  
    
    # fit model per il sample
    bagged_svm <- svm(factor(attrition)~., 
                                        data=sample, 
                                        kernel ="linear",
                                        probability=TRUE,
                                        # la funzione prende valore 'cost'
                                        cost = cost)
    
    #calcoliamo adesso le predizioni sul TEST
    as.data.frame(attr(
        predict(bagged_svm,
                        newdata=test, 
                        probability=TRUE), "prob"))$Yes
}
toc() #3 sec (4)
stopCluster(cpu)

#Ritorna un vettore con le medie per fila di ogni osservazione
return(rowMeans(predictions))
}

Ahora implemento la función con 1000 submuestras, he realizado varías pruebas con diferentes dimensiones de muestras y este valor debería ser suficiente para establecer el error. El tamaño de cada submuestra es el 60% de los datos iniciales.

bagging_svm <- BaggingSVM_Lin(upTrain2, Test2, samples = 1000, size = 0.6, cost = 0.01)

Ahora comparamos el modelo final ensemble con el modelo de SVM lineal, a través de la curva ROC.

roc.list <- list(`Bagging SVM` = roc(Test2$attrition, bagging_svm), SVM = roc(svm_lin$pred$obs,
    svm_lin$pred$Yes))


ci.list <- lapply(list(`Bagging SVM` = roc(Test2$attrition, bagging_svm), SVM = roc(svm_lin$pred$obs,
    svm_lin$pred$Yes)), ci.se, specificities = seq(0, 1, l = 25))

dat.ci.list <- lapply(ci.list, function(ciobj) data.frame(x = as.numeric(rownames(ciobj)),
    lower = ciobj[, 1], upper = ciobj[, 3]))
p <- ggroc(roc.list, size = 0.7, alpha = 0.9) + scale_color_manual(name = NULL, values = c(`Bagging SVM` = "#9EA9E0",
    SVM = "#2E45B8")) + hrbrthemes::theme_ipsum(ticks = T, base_family = "Times",
    base_size = 10) + labs(title = "ROC Curve", subtitle = "Comparing ROC curves for single models and Stacking") +
    geom_abline(slope = 1, intercept = 1, linetype = "dashed", alpha = 0.7, color = "grey") +
    ylab("Sensitivity") + xlab("Specificity") + annotate("rect", xmin = 0.25, xmax = 0,
    ymin = 0, ymax = 0.25, fill = "white") + annotate("text", x = 0.13, y = 0.18,
    label = "bold(AUC): 0.81", colour = "#9EA9E0", family = "Times", parse = TRUE) +
    annotate("text", x = 0.13, y = 0.08, colour = "#2E45B8", label = "bold(AUC): 0.82",
        family = "Times", parse = TRUE) + theme(aspect.ratio = 0.5, plot.margin = unit(c(1,
    1, 0.1, 0.1), "cm"), legend.title = element_blank(), legend.text = element_text(size = 12),
    legend.position = "top", panel.background = element_rect(color = "#3b454a"),
    title = element_text(face = "bold"))

for (i in 1:2) {
    if (i == 1) {
        col = "#2E45B8"
        a = 0.2
    } else {
        col = "#2E45B8"
        a = 0.5
    }
    p <- p + geom_ribbon(data = dat.ci.list[[i]], aes(x = x, ymin = lower, ymax = upper),
        fill = col, alpha = a, inherit.aes = F)
}
p

Se puede observar que, aunque los valores de la área abajo de la curva ROC están parecidos, el modelo ensemble no aporta mejorías a el modelo simple.

4.10 Boosting

Para realizar el boosting el flujo de trabajo es parecido a los demás. Se empieza con el tuneo.

4.10.1 Tuneo del Modelo

registerDoParallel(makeCluster(3) -> cpu)
tic()
set.seed(112)  # for reproducibility
boost_result <- train(factor(attrition) ~ ., data = upTrain2, method = "AdaBoost.M1",
    verbose = FALSE, trainControl = control, tuneGrid = expand.grid(mfinal = c(3,
        5, 8, 10, 15, 30, 50, 100), maxdepth = c(1, 5, 10, 15, 30, 50), coeflearn = c("Breiman")))
toc()  #10.71 min elapsed
stopCluster(cpu)

Observamos en la tabla los resultados:

cbind(boost_result$bestTune, getTrainPerf(boost_result)) %>%
    kable(format = "html", bootstrap_options = "basic", caption = "Best Tune", digits = 3)
Best Tune
mfinal maxdepth coeflearn TrainAccuracy TrainKappa method
24 100 10 Breiman 0.966 0.932 AdaBoost.M1
plot(boost_result, output = "ggplot", "line", par.settings = list(superpose.line = list(lwd = 1,
    col = c("#D6DBF5", "#B5BFE7", "#94A4D9", "#7388CC", "#526DBE", "#3252B1")), superpose.symbol = list(pch = 19,
    cex = 0.6, col = c("#D6DBF5", "#B5BFE7", "#94A4D9", "#7388CC", "#526DBE", "#3252B1")),
    strip.background = list(col = "#D6DBF5")))

Los números de weak learners parece establecer el error alrededor de 100. Un tamaño entre 10 y 30 parece ser necesario para alcanzar la máxima accuracy.

4.10.2 Modelo Final

boost <- train(factor(attrition) ~ ., data = upTrain2, method = "AdaBoost.M1", verbose = FALSE,
    trControl = control, tuneGrid = expand.grid(mfinal = c(100), maxdepth = c(10),
        coeflearn = c("Breiman")))
draw_confusion_matrix(confusionMatrix(boost$pred$pred, boost$pred$obs), "#D6DBF5",
    "#2E45B8")

as_tibble(vip::vi(boost)) %>%
    filter(Importance > 2.5) %>%
    mutate(Variable = stringr::str_to_title(Variable)) %>%
    mutate(Variable = str_replace_all(Variable, "_", " ")) %>%
    ggplot(aes(x = reorder(Variable, Importance), y = Importance)) + ggchicklet::geom_chicklet(aes(fill = Importance),
    radius = grid::unit(6, "pt"), width = 0.8, show.legend = F) + labs(x = NULL,
    title = "Variable Importance Plot Boosting") + scale_y_continuous(position = "right") +
    scale_fill_gradient(low = "#D6DBF5", high = "#2E45B8") + coord_flip() + hrbrthemes::theme_ipsum(base_family = "Times",
    grid = "X") + theme(plot.title = element_text(vjust = -1), plot.margin = unit(c(0,
    1, 1, 0.5), "cm"))
Boosting Modelo FinalBoosting Modelo Final

Boosting Modelo Final

El modelo es muy bueno, tiene un accuracy de 0.978 y una AUC de 0.993. También nos proporciona las importancias de las variables:

  • El salario siempre está arriba las demás por importancia, aunque notamos con este conjunto restringido que los años total trabajos, los en la empresa y el numero de empresas por el cual has trabajo toman más importancia comparado a los modelos con todas las variables.

4.11 Stacking

Para realizar el stacking, he utilizado el paquete caretEnseble. Con este he tenido varios problemas al principio, sobre todo por ajustar los modelos learners con los parámetros que quería. Al final he encontrado una solución:

  • La función caretStack admite también modelos entrenados separadamente, solo si después se convierten en la clase as.caretList.
  • Para realizar el stacking es necesario establecer un control común para los varío modelos, que especifica los indexes de los folds de la validación cruzada, en cuanto tienen que ser comunes para todos los modelos.
  • Se entrenan los modelos con train como siempre, tuneando o fijando los parámetros óptimos si ya se conocen y se juntan en una lista as.caretList.
  • Al final se realiza el stacking sobre esta lista, decidiendo el algoritmo para realizar el ensemble y es posible tunearlo. Aquí también es importante especificar el indice con la respectiva variable dependiente.
library("caretEnsemble")

stck_control <- trainControl(method = "cv", number = 5, returnResamp = "all", savePredictions = "all",
    classProbs = TRUE, index = createMultiFolds(upTrain2$attrition, 5))

# Entrenamos
gbm_stck <- train(factor(attrition) ~ ., data = upTrain2, method = "gbm", trControl = stck_control,
    tuneGrid = expand.grid(shrinkage = c(1), n.minobsinnode = c(35), n.trees = c(10000),
        interaction.depth = c(2)), distribution = "bernoulli", bag.fraction = 1,
    verbose = FALSE)

svmR_stck <- train(factor(attrition) ~ ., data = upTrain2, method = "svmRadial",
    trControl = stck_control, tuneGrid = expand.grid(C = c(30), sigma = c(1)), verbose = FALSE)

xgbm_stck <- train(factor(attrition) ~ ., data = upTrain2, method = "xgbTree", trControl = stck_control,
    tuneGrid = expand.grid(eta = c(0.05), min_child_weight = c(35), nrounds = c(10000),
        gamma = c(0), subsample = c(1), max_depth = c(6), colsample_bytree = c(1)),
    verbose = FALSE)

model_list = as.caretList(list(xgbm = xgbm_stck, svm_rad = svmR_stck, gbm = gbm_stck))

# Stacking
stacking <- caretStack(model_list, method = "gbm", verbose = FALSE, metric = "ROC",
    tuneGrid = expand.grid(shrinkage = c(0.5, 1), n.minobsinnode = c(15, 35, 50),
        n.trees = c(5000, 10000), interaction.depth = c(2)), trControl = trainControl(method = "cv",
        number = 5, savePredictions = "final", classProbs = TRUE, index = createResample(upTrain2$attrition)))

Vemos una curva ROC para comparar los resultados:

list(`SVM Rad` = roc(svmR_stck$pred$obs, svmR_stck$pred$Yes), GBM = roc(gbm_stck$pred$obs,
    gbm_stck$pred$Yes), XGBM = roc(xgbm_stck$pred$obs, xgbm_stck$pred$Yes), Stacking = roc(stacking$ens_model$pred$obs,
    stacking$ens_model$pred$Yes)) %>%
    ggroc(size = 0.7, alpha = 0.9) + scale_color_manual(name = NULL, values = c(`SVM Rad` = "#2E45B8",
    GBM = "#6677CC", XGBM = "#9EA9E0", Stacking = "#D6DBF5")) + hrbrthemes::theme_ipsum(ticks = T,
    base_family = "Times", base_size = 10) + labs(title = "ROC Curve", subtitle = "Comparing ROC curves for single models and Stacking") +
    ylab("Sensitivity") + xlab("Specificity") + annotate("rect", xmin = 0.25, xmax = 0,
    ymin = 0, ymax = 0.37, fill = "white") + annotate("text", x = 0.13, y = 0.26,
    label = "bold(AUC): 0.99", colour = "#2E45B8", family = "Times", parse = TRUE) +
    annotate("text", x = 0.13, y = 0.19, colour = "#6677CC", label = "bold(AUC): 0.99",
        family = "Times", parse = TRUE) + annotate("text", x = 0.13, y = 0.12, colour = "#9EA9E0",
    label = "bold(AUC): 0.96", family = "Times", parse = TRUE) + annotate("text",
    x = 0.13, y = 0.05, colour = "#D6DBF5", label = "bold(AUC): 0.51", face = "bold",
    family = "Times", parse = TRUE) + theme(aspect.ratio = 0.5, plot.margin = unit(c(1,
    1, 0.1, 0.1), "cm"), legend.title = element_blank(), legend.text = element_text(size = 10),
    legend.position = "top", panel.background = element_rect(color = "#3b454a"),
    title = element_text(face = "bold"))

Como se puede observar de este gráfico comparativo de las curvas ROC, el stacking no ha aportado mejoras a los modelo solos.

Además se puede notar que los modelos de GBM y XGBM, esta vez ajustados por el conjunto de datos con las variables más influyentes, son más precisos de los mismos ajustados con todas las variables.

Conclusion

En conclusión, observamos este gráfico con los distintos valores de accuracy según cada técnica.

data.frame(model = c("Stacking", "Bagging SVM", "SVM Lin", "XGboost", "SVM Poli",
    "GBM", "Boosting", "SVM Rad"), accuracy = c(47, 76, 77, 92, 95, 96, 98, 99)) %>%
    apex(type = "radialBar", height = 500, aes(x = model, y = accuracy)) %>%
    ax_labs(title = "Model accuracy for ML models", subtitle = "On selected variables based on overall importance.\nClick on the bar to see results") %>%
    ax_title(style = list(fontSize = "19px")) %>%
    ax_subtitle(style = list(fontSize = "14px", color = "#BDBDBD")) %>%
    ax_colors(painter::Palette("#D6DBF5", "#3252B1", 10))

El modelo ganador ha sido el SVM Radial con un accuracy del 99% aunque el Boosting está justo detrás con 98%.

Referencías

[1] E. Alfaro, M. Gámez, and N. Garc'ia. “adabag: An R Package for Classification with Boosting and Bagging”. In: Journal of Statistical Software 54.2 (2013), pp. 1-35. URL: http://www.jstatsoft.org/v54/i02/.

[2] Alfaro, E. Gamez, Matias, Garcia, et al. adabag: Applies Multiclass AdaBoost.M1, SAMME and Bagging. R package version 4.2. 2018. URL: https://CRAN.R-project.org/package=adabag.

[3] L. Breiman, A. Cutler, A. Liaw, and M. Wiener. randomForest: Breiman and Cutler’s Random Forests for Classification and Regression. R package version 4.6-14. 2018. URL: https://www.stat.berkeley.edu/~breiman/RandomForests/.

[4] T. Chen, T. He, M. Benesty, V. Khotilovich, et al. xgboost: Extreme Gradient Boosting. R package version 1.5.2.1. 2022. URL: https://github.com/dmlc/xgboost.

[5] M. Corporation and S. Weston. doParallel: Foreach Parallel Adaptor for the parallel Package. R package version 1.0.16. 2020. URL: https://CRAN.R-project.org/package=doParallel.

[6] Z. A. Deane-Mayer and J. E. Knowles. caretEnsemble: Ensembles of Caret Models. R package version 2.0.1. 2019. URL: https://github.com/zachmayer/caretEnsemble.

[7] B. Greenwell, B. Boehmke, J. Cunningham, and G. Developers. gbm: Generalized Boosted Regression Models. R package version 2.1.8. 2020. URL: https://github.com/gbm-developers/gbm.

[8] M. Kuhn. caret: Classification and Regression Training. R package version 6.0-91. 2022. URL: https://github.com/topepo/caret/.

[9] A. Liaw and M. Wiener. “Classification and Regression by randomForest”. In: R News 2.3 (2002), pp. 18-22. URL: https://CRAN.R-project.org/doc/Rnews/.

[10] D. Meyer, E. Dimitriadou, K. Hornik, A. Weingessel, et al. e1071: Misc Functions of the Department of Statistics, Probability Theory Group (Formerly: E1071), TU Wien. R package version 1.7-9. 2021. URL: https://CRAN.R-project.org/package=e1071.

[11] X. Robin, N. Turck, A. Hainard, N. Tiberti, et al. “pROC: an open-source package for R and S+ to analyze and compare ROC curves”. In: BMC Bioinformatics 12 (2011), p. 77.

[12] X. Robin, N. Turck, A. Hainard, N. Tiberti, et al. pROC: Display and Analyze ROC Curves. R package version 1.18.0. 2021. URL: http://expasy.org/tools/pROC/.

[13] T. Therneau and B. Atkinson. rpart: Recursive Partitioning and Regression Trees. R package version 4.1-15. 2019. URL: https://CRAN.R-project.org/package=rpart.

[14] H. Wickham. ggplot2: Elegant Graphics for Data Analysis. Springer-Verlag New York, 2016. ISBN: 978-3-319-24277-4. URL: https://ggplot2.tidyverse.org.

[15] H. Wickham, W. Chang, L. Henry, T. L. Pedersen, et al. ggplot2: Create Elegant Data Visualisations Using the Grammar of Graphics. R package version 3.3.5. 2021. URL: https://CRAN.R-project.org/package=ggplot2.

LS0tCnRpdGxlOiB8CiA8aDMgc3R5bGU9ImZvbnQtd2VpZ2h0OiA5MDA7IHRleHQtYWxpZ246IGNlbnRlcjsgZm9udC1zaXplOiAxNXB0OyI+VMOpY25pY2FzIGRlIE1hY2hpbmUgTGVhcm5pbmc8L2gzPiA8YnI+CiA8aDQgc3R5bGU9ImZvbnQtc3R5bGU6IGl0YWxpYzsgZm9udC13ZWlnaHQ6IDUwMCI+QWxlc3NpbyBDcmlzYWZ1bGxpIENhcnBhbmkgPC9oND4KYXV0aG9yOiBhbGVjcmlzYUB1Y20uZXMKb3V0cHV0OgogIHJtZGZvcm1hdHM6OnJvYm9ib29rOgogICAgY29kZV9kb3dubG9hZDogdHJ1ZQogICAgc2VsZl9jb250YWluZWQ6IGZhbHNlCiAgICBudW1iZXJfc2VjdGlvbnM6IHRydWUKICAgIGhpZ2hsaWdodDogdGFuZ28KICAgIGtlZXBfbWQ6IGZhbHNlCiAgICB0b2NfZGVwdGg6IDIKICAgIGZpZ19jYXB0aW9uOiB5ZXMKICAgIGxpZ2h0Ym94OiB0cnVlCiAgICBiaWJsaW9ncmFwaHk6IGZpbGVzL2tuaXRjaXRhdGlvbnMuYmliCiAgICBjc2w6IGZpbGVzL3RoZXNpcy5jc2wKICAgIG5vY2l0ZTogfAogICAgICBAKiAgICAKICAgIGNpdGF0aW9uX3BhY2thZ2U6IG5hdGJpYgogICAgbGluay1jaXRhdGlvbnM6IHllcwogICAgbGlua2NvbG9yOiByZWQKICAgIHVybGNvbG9yOiBjeWFuCiAgICBjaXRlY29sb3I6IGJsdWUKICAgIGNzczogZmlsZXMvc3Rlc3VyYS9zdHlsZS5jc3MKICAgIGluY2x1ZGVzOgogICAgIGJlZm9yZV9ib2R5OiBmaWxlcy9zdGVzdXJhL2xvZ28uaHRtbAogICAgIGFmdGVyX2JvZHk6IGZpbGVzL3N0ZXN1cmEvZm9vdGVyLmh0bWwKLS0tCgpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0Kb3B0aW9ucyhkaWdpdHMgPSAzKQprbml0cjo6b3B0c19jaHVuayRzZXQoCglkcGkgPSAyMDAsCgllY2hvID0gVFJVRSwKCXdhcm5pbmcgPSBGQUxTRSwKCW1lc3NhZ2UgPSBGQUxTRSwKCXRpZHkgPSBUUlVFLAoJY29tbWVudCA9ICJyID4iLAoJZGlnaXRzID0gMykKCiNrbml0cjo6d3JpdGVfYmliKHg9YygiY2FyZXQiLCJnZ3Bsb3QyIiwicnBhcnQiLCAiZTEwNzEiLCAicmFuZG9tRm9yZXN0IiwgImdibSIsICJ4Z2Jvb3N0IiwgImRvUGFyYWxsZWwiLCAiYWRhYmFnIiwgImNhcmV0RW5zZW1ibGUiLCAicFJPQyIpLCAKIwkJCWZpbGUgPSAiZmlsZXMvc3Rlc3VyYS9rbml0Y2l0YXRpb25zLmJpYiIpCgpsb2FkKCJXb3Jrc3BhY2UuUkRhdGEiKQpwYWNtYW46OnBfbG9hZCh0aWR5dmVyc2UsIG1hZ3JpdHRyLCBjYXJldCwgcnBhcnQsIHJhbmRvbUZvcmVzdCwgZ2JtLCB4Z2Jvb3N0LCBrYWJsZUV4dHJhLCBwUk9DLCBkb1BhcmFsbGVsLCB0aWN0b2MpCgpkYXRhID0gcmVhZF9jc3YoIklCTS1IUi5jc3YiLAoJY29sX3R5cGVzID0gY29scygKCQlBdHRyaXRpb24gPSBjb2xfZmFjdG9yKCksCgkJR2VuZGVyID0gY29sX2ZhY3RvcigpLAoJCUVkdWNhdGlvbkZpZWxkID0gY29sX2ZhY3RvcigpLAoJCU1hcml0YWxTdGF0dXMgPSBjb2xfZmFjdG9yKCksCgkJSm9iUm9sZSA9IGNvbF9mYWN0b3IoKSwKCQlCdXNpbmVzc1RyYXZlbCA9IGNvbF9mYWN0b3IoKSwKCQlEZXBhcnRtZW50ID0gY29sX2ZhY3RvcigpLAoJCUVkdWNhdGlvbiA9IGNvbF9mYWN0b3IoKSwKCQlFZHVjYXRpb25GaWVsZCA9IGNvbF9mYWN0b3IoKSwKCQlPdmVyVGltZSA9IGNvbF9mYWN0b3IoKSkpCgpDYXRlZ29yaWNhbC5WYXJpYWJsZXMgPC0gbmFtZXMoRmlsdGVyKGlzLmZhY3RvciwgZGF0YSAlPiUgc2VsZWN0KC1BdHRyaXRpb24pKSkKTnVtZXJpYy5WYXJpYWJsZXMgPC0gbmFtZXMoRmlsdGVyKGlzLm51bWVyaWMsIGRhdGEpKQpgYGAKPGg1IHN0eWxlPSJmb250LXN0eWxlOiBpdGFsaWM7IGZvbnQtd2VpZ2h0OiA0MDA7Ij4KTcOhc3RlciBlbiBNaW5lcsOtYSBkZSBEYXRvcyBlIEludGVsaWdlbmNpYSBkZSBOZWdvY2lvcwo8L2g1Pgo8aDUgc3R5bGU9ImZvbnQtc3R5bGU6IGl0YWxpYzsgZm9udC13ZWlnaHQ6IDQwMDsiPgpUw6ljbmljYXMgZGUgTWFjaGluZSBMZWFybmluZwo8L2g1Pgo8aHIgc3R5bGU9ImJvcmRlci10b3A6IDNweCBzb2xpZCAjYmJiOyBib3JkZXItcmFkaXVzOiA1cHg7Ij4KOjo6IHsucm93fQo6Ojogey5jb2wtbWQtNH0KCkVzdGUgdHJhYmFqbyBlc3RhzIEgb3JpZW50YWRvIGEgcHJlZGVjaXIgdW5hIHZhcmlhYmxlIGJpbmFyaWEgbyBjb250aW51YSBhIHRyYXZlzIFzIGRlIGRpZmVyZW50ZXMgYWxnb3JpdG1vcyBkZSBjbGFzaWZpY2FjaW/MgW4gbyBlc3RpbWFjaW/MgW4gcmVsYWNjaW9uYWRvcyBjb24gYcyBcmJvbGVzIHkgb3Ryb3MuCgpFbiBlc3RlIGNhc28sIGhlIHF1ZXJpZG8gZXhwbG9yYXIgdW4gZGF0YXNldCBkZSAqUlJISCogZGVzYXJvbGxhZG8gcG9yICoqSUJNKiouIEVzIHBvc2libGUgZGVzY2FyZ2FybG8gYXF1w606CmBgYHtyIGRvd25sb2FkLCBlY2hvPUZBTFNFfQpzdXBwcmVzc1BhY2thZ2VTdGFydHVwTWVzc2FnZXMobGlicmFyeShkb3dubG9hZHRoaXMpKQpkYXRhICU+JSBkb3dubG9hZF90aGlzKAogICAgb3V0cHV0X25hbWUgPSAiRGF0YXNldCIsCiAgICBvdXRwdXRfZXh0ZW5zaW9uID0gIi5jc3YiLAogICAgYnV0dG9uX2xhYmVsID0gIkRlc2NhcmdhciBEYXRhIGFzIENTViIsCiAgICBidXR0b25fdHlwZSA9ICJkZWZhdWx0IiwKICAgIGhhc19pY29uID0gVFJVRSwKICAgIGljb24gPSAiZmEgZmEtc2F2ZSIpCmBgYAo6OjoKCjo6OiB7LmNvbC1tZC00fQpFbCBvYmpldGl2byBkZSBlc3RlIGluZm9ybWUgZXMgcHJlZGVjaXIgZWwgw61uZGljZSBkZSBkZXNlcmNpw7NuIGRlIGxvcyBlbXBsZWFkb3MsIGlkZW50aWZpY2FuZG8gdGFtYmnDqW4gbGFzIGNhdXNhcyBxdWUgbcOhcyBsbyBpbmZsdWVuY2lhbi4KCkVzdGUgZXMgdW4gY29uanVudG8gZGUgZGF0b3MgKmZpY3RpY2lvKiBjcmVhZG8gcG9yIGNpZW50w61maWNvcyBkZSBkYXRvcy4gUG9yIGVzdGEgcmF6w7NuIGVzcGVybyBtw6l0cmljYXMgZGUgbG9zIG1vZGVsb3MgZWxldmFkYXMKOjo6Cgo6Ojogey5jb2wtbWQtNH0KWyFbS2FnZ2xlIERhdGFzZXRdKGZpbGVzL2ltZy9LYWdnbGUucG5nICJLYWdnbGUgRGF0YXNldCIpXShodHRwczovL3d3dy5rYWdnbGUuY29tL3BhdmFuc3ViaGFzaHQvaWJtLWhyLWFuYWx5dGljcy1hdHRyaXRpb24tZGF0YXNldCkKOjo6Cjo6OgoKIyBBYnN0cmFjdG8gey19CkEgbG8gbGFyZ28gZGUgZXN0YSBhbsOhbGlzaXMsIGhlIGFuYWxpemFkbyBlbCBjb25qdW50byBkZSBkYXRvcyAqKklCTSBFbXBsb3llZSBBdHRyaXRpb24qKiBwYXJhIGV4cGxvcmFyIGxhcyBjYXVzYXMgcHJpbmNpcGFsZXMgZGUgbGEgZGVzZXJjacOzbiBlbiB1bmEgZW1wcmVzYS4gUHJpbWVybywgYSB0cmF2w6lzIGRlIHVuIGFuw6FsaXNpcyBleHBsb3JhdG9yaW8sIG1lIGhlIGFzZWd1cmFkbyBxdWUgbG9zIGRhdG9zIGVzdGViYW4gbGltcGlvcy4gCkVuIGVzdGEgZXRhcGEsIG1lIGTDrSBjdWVudGEgcXVlIGxvcyBkYXRvcyB0ZW7DrWFuIHVuIHByb2JsZW1hIGRlIGRlc2JhbGFuY2VvIGVudHJlIGxhcyBjbGFzZXMgZGUgbGEgdmFyaWFibGUgZGVwZW5kaWVudGUsIHBhcmEgYXJyZWdsYXIgZXN0ZSBwcm9ibGVtYSBoZSBhbXBsaWFkbyBsYSBtdWVzdHJhIGRlIGxhIGNsYXNlIG1lbm9yLgoKVW5hIHZleiBxdWUgbG9zIGRhdG9zIGVzdGFiYW4gbGlzdG9zLCBoZSBlbXBlemFkbyBsYSBtb2RlbGl6YWNpw7NuIGRlbCAqKsOhcmJvbCBkZSBkZWNpc2nDs24qKiwgKipiYWdnaW5nKiosICoqcmFuZG9tIGZvcmVzdCoqLCAqKmdyYWRpZW50IGJvb3N0aW5nIG1hY2hpbmUgKEdCTSkqKiB5ICoqZXh0cmVtZSBncmFkaWVudCBib29zdGluZyBtYWNoaW5lIChYR0JNKSoqLiBQYXJhIGNhZGEgdMOpY25pY2EsIG1pIGZsdWpvIGRlIHRyYWJham8gaGEgc2lkbyBsbyBzaWd1aWVudGU6CgoxLiBUdW5lYXIgbG9zIHBhcsOhbWV0cm9zIGRlIGNhZGEgbW9kZWxvLCBjb24gbGEgZnVuY2nDs24gYHRyYWluYCBkZWwgcGFxdWV0ZSBgY2FyZXRgLCBlbXBsZWFuZG8gdW4gYnVjbGUgY3VhbmRvIGxhIGZ1bmNpw7NuIG5vIHBlcm1pdGUgZWwgY29udHJvbCBkZSB1bm9zIHBhcsOhbWV0cm9zIHkgdXRpbGl6YW5kbyAqY29tcHV0YWNpw7NuIHBhcmFsZWxhKiBzb2JyZSB0cmVzIHByb2Nlc2Fkb3Jlcy4KMi4gRW50ZW5kZXIgZWwgYW5kYW1pZW50byBkZWwgYWp1c3RlIHBhcmEgbG9zIGRpc3RpbnRvcyBwYXLDoW1ldHJvcyB0dW5lYWRvcyBhIHRyYXbDqXMgdW5vcyBncsOhZmljb3MuCgogICAgICAtICAgIEFkZW3DoXMgcGFyYSBlbCBiYWdnaW5nIHkgcmFuZG9tIGZvcmVzdCBoZSB2aXN1YWxpemFkbyBlbCAqZXJyb3IgZGUgb3V0LW9mLWJhZyosIHBhcmEgZW50ZW5kZXIgZWwgYW5kYW1pZW50byBhIG1lZGlkYSBxdWUgYXVtZW50YWJhbiBsYXMgaW50ZXJhY2Npb25lcyBkZSBsb3Mgw6FyYm9sZXMuIEEgdHJhdsOpcyBlc3RlIGdyw6FmaWNvIGhlIHBvZGlkbyByZWR1Y2lyIGxhIGNvbXBsZWppZGFkIGRlIG1pIG1vZGVsbyBmaW5hbC4KCkRlc3B1w6lzIGhhYmVyIGVsZWdpZG8gbG9zIHBhcsOhbWV0cm9zIG1lam9yZXMsIGhlIGFqdXN0YWRvIGVsIG1vZGVsbyBmaW5hbCwgdmlzdWFsaXphbmRvIGxhICptYXRyaXogZGUgY29uZnVzacOzbiogeSBsYXMgKnZhcmlhYmxlcyBtw6FzIGluZmx1eWVudGVzKiBwYXJhIGNhZGEgbW9kZWxvLgoKVW5hIHZleiBhanVzdGFkYXMgbGFzIHTDqWNuaWNhcyBkZSDDoXJib2xlcywgaGUgYWp1c3RhZG8gb3RyYSB2ZXogbG9zIG1vZGVsb3MgY29uICp2YWxpZGFjacOzbiBjcnV6YWRhIHJlcGV0aWRhKiBlc3RhIHZleiwgcGFyYSBjb21wYXJhciBsYXMgcmVzcGVjdGl2YXMgKnRhc2EgZGUgZmFsbG8qIHkgbGEgKsOhcmVhIGFiYWpvIGxhIGN1cnZhIFJPQyosIGEgdHJhdsOpcyBkZSB1biBncmFmbyBkZSBjYWphIHkgYmlnb3RlIGluY2x1eWVuZG8gY29tbyBtb2RlbG8gZGUgcmVmZXJlbmNpYSB1bmEgKnJlZ3Jlc2nDs24gbG9nw61zdGljYSouCgpVbmEgdmV6IGVuY29udHJhZG8gZWwgbW9kZWxvIGdhbmFkb3IsIGhlIGRlY2lkaWRvIGNyZWFyIHVuIGdyYWZvIGRlIGJhcnJhcyBhcGlsYWRhcyBwYXJhIG9idGVuZXIgbGFzIHZhcmlhYmxlcyBtw6FzIGluZmx1eWVudGVzIHNlZ8O6biBsb3MgZGlzdGludG9zIG1vZGVsb3MuIEEgcGFydGlyIGRlIGVzdGFzLCBoZSBjcmVhZG8gdW4gbnVldm8gY29uanVudG8gKmVudHJlbmFtaWVudG8vdGVzdCosIGNvbiBlbCBjdWFsIHZveSBhIHJlYWxpemFyIG90cmFzIHTDqWNuaWNhcyBkZSBhcHJlbmRpemFqZSBhdXRvbcOhdGljbzoKCi0gKipTdXBwb3J0IFZlY3RvciBNYWNoaW5lcyoqLCBjb24ga2VybmVsOgogICAgLSAqTGluZWFsKgogICAgLSAqUG9seW5vbWlhbCoKICAgIC0gKlJhZGlhbCoKLSAqKkJhZ2dpbmcqKiBkZWwgU1ZNIExpbmVhbAotICoqQm9vc3RpbmcqKgotICoqU3RhY2tpbmcqKiBkZToKICAgIC0gKkdyYWRpZW50IEJvb3N0aW5nIE1hY2hpbmUqCiAgICAtICpTVk0gUmFkaWFsKgogICAgLSAqWEdCb29zdCoKCioqKgojIERlc2NyaXBjacOzbiBkZSBsb3MgZGF0b3MKCi0gICAqKkVsIGNvbmp1bnRvIGRlIGRhdG9zKio6IDE0NzAgb2JzZXJ2YWNpb25lcywgMzUgdmFyaWFibGVzLgotICAgKipUaXBvIGRlIHZhcmlhYmxlcyoqOiBUZW5lbW9zIGFtYm9zIHZhcmlhYmxlcyBjYXRlZ29yw61jYXMgeSBudW1lcsOtY2FzLiBMYSB2YXJpYWJsZSBvYmplY3RvIGRlIMOtbnRlcmVzLCAqQXR0cml0aW9uKiwgZXMgdW5hIHZhcmlhYmxlIGJpbmFyaWEgcXVlIHRvbWEgdmFsb3JlcyAqWWVzKiBvICpObyosIGRlcGVuZGllbmRvIHNpIGVsIGVtcGxlYWRvIHNlIGhhIG1hcmNoYWRvIGRlIGxhIGltcHJlc2EuIAoKVW5hcyBjb2x1bW5hcyBlc3TDoW4gZW4gZXNjYWxhIDEtNSwgZGFsZSBhbCBub21icmUgZGUgbGEgY29sdW1uYSBtYXJjYWRhIGVuIDx1IHN0eWxlPSJ0ZXh0LWRlY29yYXRpb24tY29sb3I6cmVkIj4gcm9qbyA8L3U+IHBhcmEgdmlzdWFsaXphciB1biBwb3B1cCBjb24gbGEgZGVzY3JpcGNpw7NuLiAKCmBgYHtyIHRhYmxlLCBlY2hvPUZBTFNFfQpsaWJyYXJ5KGh0bWx0b29scykKbGlicmFyeShyZWFjdGFibGUpCgpsaWJyYXJ5KHRpcHB5KSAKd2l0aF90b29sdGlwIDwtIGZ1bmN0aW9uKHZhbHVlLCB0b29sdGlwLCAuLi4pewoJZGl2KHN0eWxlID0gIgoJCQl0ZXh0LWRlY29yYXRpb246IHVuZGVybGluZTsKCQkJdGV4dC1kZWNvcmF0aW9uLWNvbG9yOiByZWQ7CgkJCWN1cnNvcjogaGVscCIsCgkJCXRpcHB5KHZhbHVlLCB0b29sdGlwLCAuLi4pKQp9CgpyZWFjdGFibGUoCglkYXRhICU+JSBzZWxlY3QoR2VuZGVyLCBFZHVjYXRpb24sIE1vbnRobHlJbmNvbWUsZXZlcnl0aGluZygpKSwKIyBWYXJpYWJsZXMgR3JvdXBzLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQojIFdlIGRlZmluZSBob3cgd2UgYXJlIGdvaW5nIHRvIGdyb3VwIHRoZSBpbmZvcm1hdGlvbnMgb24gdGhlIGRhdGEgd2l0aCB0aGUgbmFtZSBvZiB0aGUgZ3JvdXAgYW5kIHRoZSBjb2x1bW5zIHdpdGhpbiB0aGUgZ3JvdXAKCWNvbHVtbkdyb3VwcyA9IGxpc3QoCgkJY29sR3JvdXAoCgkJbmFtZSA9ICJEZW1vZ3LDoWZpY2FzIiwKCQljb2x1bW5zID0gYygiQWdlIiwiR2VuZGVyIiwiRWR1Y2F0aW9uIiwgIkVkdWNhdGlvbkZpZWxkIiwgIk1hcml0YWxTdGF0dXMiLCAiRGlzdGFuY2VGcm9tSG9tZSIpKSwKCQljb2xHcm91cCgKCQluYW1lID0gIlBvc2ljaW9uIiwKCQljb2x1bW5zID0gYygiRGVwYXJ0bWVudCIsIkpvYlJvbGUiLCJKb2JMZXZlbCIsIkhvdXJseVJhdGUiLCJEYWlseVJhdGUiLCAiQnVzaW5lc3NUcmF2ZWwiKSksCgkJY29sR3JvdXAoCgkJbmFtZSA9ICJFbmN1ZXN0YSBTYXRpc2ZhY2Npb24iLAoJCWNvbHVtbnMgPSBjKCJKb2JJbnZvbHZlbWVudCIsICJFbnZpcm9ubWVudFNhdGlzZmFjdGlvbiIsICJKb2JTYXRpc2ZhY3Rpb24iLCAiV29ya0xpZmVCYWxhbmNlIiwgIlJlbGF0aW9uc2hpcFNhdGlzZmFjdGlvbiIpKSwKCQljb2xHcm91cCgKCQluYW1lID0gIkVtcHJlc2EiLAoJCWNvbHVtbnMgPSBjKCJQZXJmb3JtYW5jZVJhdGluZyIsIlBlcmNlbnRTYWxhcnlIaWtlIiwiWWVhcnNBdENvbXBhbnkiLCAiWWVhcnNJbkN1cnJlbnRSb2xlIiwgIlllYXJzU2luY2VMYXN0UHJvbW90aW9uIiwgIk51bUNvbXBhbmllc1dvcmtlZCIsICJZZWFyc1dpdGhDdXJyTWFuYWdlciIsICJUb3RhbFdvcmtpbmdZZWFycyIsIlRyYWluaW5nVGltZXNMYXN0WWVhciIpKQopLAoKIyBUaGUgRmFjdG9yIEdyb3VwaW4gLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCmdyb3VwQnkgPSAiQXR0cml0aW9uIiwKZGVmYXVsdEV4cGFuZGVkID0gVCwgICAgI2V4cGFuZCBsZXZlbHM/CgojIENvbHVtbiBTcGVjaWZpY2F0aW9ucyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KZGVmYXVsdENvbERlZiA9IGNvbERlZihtaW5XaWR0aCA9IDkwLAoJCQkJCQkJCQkJCSBtYXhXaWR0aCA9IDI4MCksCmNvbHVtbnMgPSBsaXN0KAoKCVJlbGF0aW9uc2hpcFNhdGlzZmFjdGlvbiA9IGNvbERlZihoZWFkZXIgPSB3aXRoX3Rvb2x0aXAoIlJlbGF0aW9uc2hpcFNhdGlzZmFjdGlvbiIsIAoJCQkJCQkJIjxzcGFuIHN0eWxlPSdmb250LXNpemU6MTZweDsnPiAKCQkJCQkJCTxvbD48bGk+TG93PC9saT4KCQkJCQkJCTxsaT5NZWRpdW08L2xpPgoJCQkJCQkJPGxpPkhpZ2g8L2xpPgoJCQkJCQkJPGxpPlZlcnkgSGlnaDwvbGk+CgkJCQkJCQk8L29sPjwvc3Bhbj4iKSksCglKb2JTYXRpc2ZhY3Rpb24gPSBjb2xEZWYoaGVhZGVyID0gd2l0aF90b29sdGlwKCJKb2JTYXRpc2ZhY3Rpb24iLCAKCQkJCQkJCSI8c3BhbiBzdHlsZT0nZm9udC1zaXplOjE2cHg7Jz4gCgkJCQkJCQk8b2w+PGxpPkxvdzwvbGk+CgkJCQkJCQk8bGk+TWVkaXVtPC9saT4KCQkJCQkJCTxsaT5IaWdoPC9saT4KCQkJCQkJCTxsaT5WZXJ5IEhpZ2g8L2xpPgoJCQkJCQkJPC9vbD48L3NwYW4+IikpLAoJSm9iSW52b2x2ZW1lbnQgPSBjb2xEZWYoaGVhZGVyID0gd2l0aF90b29sdGlwKCJKb2JJbnZvbHZlbWVudCIsIAoJCQkJCQkJIjxzcGFuIHN0eWxlPSdmb250LXNpemU6MTZweDsnPiAKCQkJCQkJCTxvbD48bGk+TG93PC9saT4KCQkJCQkJCTxsaT5NZWRpdW08L2xpPgoJCQkJCQkJPGxpPkhpZ2g8L2xpPgoJCQkJCQkJPGxpPlZlcnkgSGlnaDwvbGk+CgkJCQkJCQk8L29sPjwvc3Bhbj4iKSksCglFbnZpcm9ubWVudFNhdGlzZmFjdGlvbiA9IGNvbERlZihoZWFkZXIgPSB3aXRoX3Rvb2x0aXAoIkVudmlyb25tZW50U2F0aXNmYWN0aW9uIiwgCgkJCQkJCQkiPHNwYW4gc3R5bGU9J2ZvbnQtc2l6ZToxNnB4Oyc+IAoJCQkJCQkJPG9sPjxsaT5Mb3c8L2xpPgoJCQkJCQkJPGxpPk1lZGl1bTwvbGk+CgkJCQkJCQk8bGk+SGlnaDwvbGk+CgkJCQkJCQk8bGk+VmVyeSBIaWdoPC9saT4KCQkJCQkJCTwvb2w+PC9zcGFuPiIpKSwKCVdvcmtMaWZlQmFsYW5jZSAgPSBjb2xEZWYoaGVhZGVyID0gd2l0aF90b29sdGlwKCJXb3JrTGlmZUJhbGFuY2UiLCAKCQkJCQkJCSI8c3BhbiBzdHlsZT0nZm9udC1zaXplOjE2cHg7Jz4gCgkJCQkJCQk8b2w+PGxpPkJhZDwvbGk+CgkJCQkJCQk8bGk+R29vZDwvbGk+CgkJCQkJCQk8bGk+QmV0dGVyPC9saT4KCQkJCQkJCTxsaT5CZXN0PC9saT4KCQkJCQkJCTwvb2w+PC9zcGFuPiIpKSwKCVBlcmZvcm1hbmNlUmF0aW5nID0gY29sRGVmKGhlYWRlciA9IHdpdGhfdG9vbHRpcCgiUGVyZm9ybWFuY2VSYXRpbmciLCAKCQkJCQkJCSI8c3BhbiBzdHlsZT0nZm9udC1zaXplOjE2cHg7Jz4gCgkJCQkJCQk8b2w+PGxpPkxvdzwvbGk+CgkJCQkJCQk8bGk+R29vZDwvbGk+CgkJCQkJCQk8bGk+RXhjZWxsZW50PC9saT4KCQkJCQkJCTxsaT5PdXRzdGFuZGluZzwvbGk+CgkJCQkJCQk8L29sPjwvc3Bhbj4iKSksCglEZXBhcnRtZW50ID0gY29sRGVmKGhlYWRlciA9IHdpdGhfdG9vbHRpcCgiRGVwYXJ0bWVudCIsICI8c3BhbiBzdHlsZT0nZm9udC1zaXplOjE2cHg7Jz4gV29ya2luZyBEZXBhcnRtZW50IEFyZWEgPC9zcGFuPiIpKSwKCUVkdWNhdGlvbiA9IGNvbERlZihoZWFkZXIgPSB3aXRoX3Rvb2x0aXAoIkVkdWNhdGlvbiIsIAoJCQkJCQkJIjxzcGFuIHN0eWxlPSdmb250LXNpemU6MTZweDsnPiAKCQkJCQkJCTxvbD48bGk+QmVsb3cgQ29sbGVnZTwvbGk+CgkJCQkJCQk8bGk+Q29sbGVnZTwvbGk+CgkJCQkJCQk8bGk+QmFjaGVsb3I8L2xpPgoJCQkJCQkJPGxpPk1hc3RlcjwvbGk+CgkJCQkJCQk8bGk+RG9jdG9yPC9saT4KCQkJCQkJCTwvb2w+PC9zcGFuPiIpKQopLAoJCQojIERlc2lnbiZTdHlsZS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQpoZWlnaHQgPSA1MDAsCgpzdHlsZSA9IGxpc3QoCglmb250RmFtaWx5ID0gIkxhdG8sIHNhbnMtc2VyaWYiLCAKCWZvbnRTaXplID0gIjE2cHgiKSwKICBoaWdobGlnaHQgPSBUUlVFLCBvdXRsaW5lZCA9IFRSVUUsCiAgc3RyaXBlZCA9IFRSVUUsIGNvbXBhY3QgPSBUUlVFLAogIGZ1bGxXaWR0aCA9IFRSVUUsIHdyYXAgPSBUUlVFLAoKdGhlbWUgPSByZWFjdGFibGVUaGVtZSgKCXRhYmxlQm9keVN0eWxlID0gbGlzdChmbGV4ID0gImF1dG8iKSwKCWJvcmRlckNvbG9yID0gIiNkZmUyZTUiLAoJc3RyaXBlZENvbG9yID0gIiNmNmY4ZmEiLAoJaGlnaGxpZ2h0Q29sb3IgPSAiI2YwZjVmOSIsCgljZWxsUGFkZGluZyA9ICI4cHggMTJweCIsCglzdHlsZSA9IGxpc3QoCgkJZm9udEZhbWlseSA9ICItYXBwbGUtc3lzdGVtLCBCbGlua01hY1N5c3RlbUZvbnQsIFNlZ29lIFVJLCBIZWx2ZXRpY2EsIEFyaWFsLCBzYW5zLXNlcmlmIiksCgloZWFkZXJTdHlsZSA9IGxpc3QoCgkJIiY6aG92ZXJbYXJpYS1zb3J0XSIgPSBsaXN0KAoJCQliYWNrZ3JvdW5kID0gImhzbCgwLCAwJSwgOTYlKSIpLAoJCSImW2FyaWEtc29ydD0nYXNjZW5kaW5nJ10sICZbYXJpYS1zb3J0PSdkZXNjZW5kaW5nJ10iID0gbGlzdCgKCQkJYmFja2dyb3VuZCA9ICJoc2woMCwgMCUsIDk2JSkiKSwKCWJvcmRlckNvbG9yID0gIiM1NTUiKQopLAoKcm93U3R5bGUgPSBKUygiZnVuY3Rpb24ocm93SW5mbyl7CiAgaWYgKHJvd0luZm8ubGV2ZWwgPiAwKSB7CiAgICByZXR1cm4geyBiYWNrZ3JvdW5kOiAnI2VlZScsIAogICAgICAgICAgICAgYm9yZGVyTGVmdDogJzIuNXB4IHNvbGlkICMyRTQ1QjgnfQogICAgfSBlbHNlIHsKICAgIHJldHVybiB7IGJvcmRlckxlZnQ6ICcycHggc29saWQgdHJhbnNwYXJlbnQnfQogICAgfQogIH0iKSwKKQpgYGAKCjxicj4KCmBgYHtyfQphcy5kYXRhLmZyYW1lKGNiaW5kKCdWYXJpYWJsZSc9bmFtZXMoZGF0YSksICdEZXNjcmlwdGlvbic9IGMoCgkiRWRhZCIsCgkiU2kgaGEgZGVqYWRvIGxhIGVtcHJlc2EgbyBubyIsCgkiU2kgdmlhamEgcmFyYW1lbnRlLCBwb2NvLCBtdWNobyBwb3IgdHJhYmFqbyIsCgkiU2FsYXJpbyBkaWFyw61vIiwKCSJFbCBkZXBhcnRhbWllbnRvIGRlIGxhIGVtcHJlc2EiLAoJIkN1YW50byBlc3TDoSBsZWpvIGRlIHN1IGNhc2EiLAoJIk5pdmVsIGRlIEVkdWNhdGlvbiIsCgkiTnVtZXJvIGRlIGVtcGxlYWRvIiwKCSJJRCIsCgkiU2F0aXNmYWNjacOzbiBlbnRvcm5vIiwKCSJHw6luZXJvIiwKCSJTYWxhcmlvIHBvciBob3JhIiwKCSJQYXJ0aWNpcGFjacOzbiBlbiBlbCB0cmFiYWpvIiwKCSJOaXZlbCBkZSB0cmFiYWpvIiwKCSJSb2wgdHJhYmFqbyIsCgkiU2F0aXNmYWNjacOzbiBhbCB0cmFiYWpvIiwKCSJFc3RhZG8gY2l2aWwiLAoJIlNhbGFyaW8gYnJ1dG8gbWVuc3VhbCIsCgkiVGFzYSBtZW5zdWFsIiwKCSJDb24gY3VhbnRhcyBlbXByZXNhcyBoYSB0cmFiYWphZG8iLAoJIlNpIGVzIG1heW9yIGRlIDE4IGHDsW9zIiwKCSJTaSB0cmFiYWphIG3DoXMgZGVsIG5vcm1hbCIsCgkiUG9yY2VudGFqZSBhdW1lbnRvIHNhbGFyaW8iLAoJIkNhbGlmaWNhY2nDs24gZGUgZGVzZW1wZcOxbyIsCgkiU2F0aXNmYWNjacOzbiBkZSBsYSByZWxhY2nDs24iLAoJIkhvcmFzIGVzdGFuZGFyIGRlIHRyYWJham8iLAoJIlJldHJpYnVjaW9uZXMiLAoJIlRvdGFsIGHDsW9zIGRlIHRyYWJham8iLAoJIkN1YW50YXMgdmVjZXMgaGEgaGVjaG8gZm9ybWFjacOzbiBlbCBhw7FvIHBhc2FkbyIsCgkiQmFsbmNlbyBlbnRyZSB2aWRhIHkgdHJhYmFqbyIsCgkiQcOxb3MgZW4gbGEgZW1wcmVzYSIsCgkiQcOxb3MgZW4gZXN0ZSByb2wiLAoJIkHDsW9zIGRlc2RlIHVsdGltYSBwcm9tb2NjacOzbiIsCgkiQcOxb3MgY29uIG1hbmFnZXIgYWN0dWFsIgoJKSkpICU+JQoJa2FibGUoZm9ybWF0ID0gImh0bWwiLCBhbGlnbiA9IGMocmVwKCJsIiwgMikpKSAlPiUgCglyb3dfc3BlYygwLCBmb250X3NpemUgPSAxNCkgJT4lCiAgY29sdW1uX3NwZWMoMSwgYm9sZCA9IFQpCmBgYAo8YnI+Cgo8ZGl2IHN0eWxlPSJiYWNrZ3JvdW5kLWNvbG9yOiNENkRCRjU7IGJvcmRlci1yYWRpdXM6IDEwcHg7IHBhZGRpbmc6IDIwcHg7Ij4KKipEZXNiYWxhbmNlbyBlbnRyZSBsb3MgZGF0b3MqKjogMTIzNyAoODQlIGRlIGxvcyBjYXNvcykgZW1wbGVhZG9zIG5vIGhhbiBkZWphZG8gbGEgZW1wcmVzYSwgbWllbnRyYXMgMjM3ICgxNiUgb2YgY2Fzb3MpIGxvIGhhbiBoZWNoby4gRXN0ZSBkZXNiYWxhbmNlbyB0aWVuZSBxdWUgc2VyIGFqdXN0YWRvLCBtZWRpYW50ZSB1bmFzIHTDqWNuaWNhcyBhZGVjdWFkYXMsIHBhcmEgbm8gc29icmVhanVzdGFyIGVsIG1vZGVsby4KPC9kaXY+CgoqKioKIyBBbmFsw61zaXMgRXhwbG9yYXRvcmlvIGRlIERhdG9zICooRURBKSoKIyMgVmlzdWFsaXphY2nDs24gZGUgRGF0b3MKIyMjIFZhcmlhYmxlcyBDb250aW51YXMKYGBge3IgbnVtX3RhYmxlfQpsaWJyYXJ5KGthYmxlRXh0cmEpCmxpYnJhcnkobW9kZWxzdW1tYXJ5KQoKZGF0YXN1bW1hcnlfc2tpbShkYXRhLCB0eXBlID0gIm51bWVyaWMiLCB0aXRsZSA9ICJWYXJpYWJsZXMgTnVtZXJpY2FzIikgJT4lIAoJcmVtb3ZlX2NvbHVtbihjb2x1bW5zID0gYygyLDMpKSAlPiUgCgljb2x1bW5fc3BlYygxLCBib2xkID0gVCkgJT4lIGthYmxlX21hdGVyaWFsKCkgJT4lIAoJa2FibGVfc3R5bGluZyhib290c3RyYXBfb3B0aW9ucyA9IGMoImNvbmRlbnNlZCIsICJyZXNwb25zaXZlIikpICU+JQogIHNjcm9sbF9ib3god2lkdGggPSAiMTAwJSIsIGhlaWdodCA9ICI0MDBweCIpIApgYGAKCmBgYHtyLCBmaWcuY2FwPSJOdW1lcmljYWwgVmFyaWFibGVzIn0KbGlicmFyeShHR2FsbHkpCmdncGFpcnMoZGF0YSwgbWFwcGluZyA9IGFlcyhjb2xvciA9IEF0dHJpdGlvbiksIAoJCQkJY29sdW1ucyA9IGMoIk1vbnRobHlJbmNvbWUiLCJKb2JTYXRpc2ZhY3Rpb24iLCAiRW52aXJvbm1lbnRTYXRpc2ZhY3Rpb24iLCAiRGlzdGFuY2VGcm9tSG9tZSIsICJEYWlseVJhdGUiLCJBZ2UiLCJQZXJjZW50U2FsYXJ5SGlrZSIpLAoJCQkJdXBwZXIgPSBsaXN0KGNvbnRpbnVvdXM9J2JhckRpYWcnKSwKCQkJCWxvd2VyID0gbGlzdChjb250aW51b3VzPXdyYXAoInBvaW50cyIsIAoJCQkJCQkJCQkJCQkJCQkJCQkgYWxwaGEgPSAwLjUsICAgCgkJCQkJCQkJCQkJCQkJCQkJCSBzaXplPTAuMSkpLAoJCQkJdGl0bGUgPSAnTWF0cml6IGRlIFZpc3VhbGl6YWNpw7NuIFZhcmlhYmxlcyBDb250aW51YXMnLCAKCQkJCWxlZ2VuZCA9IDEsIHNob3dTdHJpcHMgPSBUKSArCgl0aGVtZV9taW5pbWFsKGJhc2Vfc2l6ZSA9IDEwLCBiYXNlX2ZhbWlseSA9ICdUaW1lcycpICsKCXRoZW1lKHRpdGxlID0gZWxlbWVudF90ZXh0KGZhY2UgPSAnYm9sZCcpLAoJCWxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfdGV4dChmYWNlID0gJ2JvbGQnKSwKCQlsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIikgICsgICAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzPWMoIiMyRTQ1QjgiLCIjRDZEQkY1IikpICsgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcz1jKCIjMkU0NUI4IiwiI0Q2REJGNSIpKSAKYGBgCgoKUG9kZW1vcyBub3RhciBjb21vIGxhcyB2YXJpYWJsZXMgbm8gdGllbmVuIHVuYSBkaXN0cmlidWNpw7NuIGxpbmVhbCwgZW50b25jZXMgbWUgZXNwZXJvIHF1ZSBsYXMgdMOpY25pY2FzIGRlcyDDoXJib2xlcyB2YW4gYSBmdW5jaW9uYXIgYmllbiBjb24gZXN0b3MgdGlwbyBkZSBkYXRvcy4gVGFtYmnDqW4gc2Ugbm90YSBxdWUgbm8gaGF5IHNlcGFyYWNpw7NuIGxpbmVhbC4KCiMjIyBWYXJpYWJsZXMgQ2F0ZWfDs3JpY2FzCmBgYHtyIGNhdGVnX3RhYmxlfQpkYXRhc3VtbWFyeV9za2ltKGRhdGEsIHR5cGUgPSAiY2F0ZWdvcmljYWwiLCB0aXRsZSA9ICJWYXJpYWJsZXMgQ2F0ZWfDs3JpY2FzIikgJT4lIAoJY29sdW1uX3NwZWMoMSwgYm9sZCA9IFQpICU+JSBjb2x1bW5fc3BlYygyLCBpdGFsaWMgPSBUKSAlPiUKCWthYmxlX21hdGVyaWFsKCkgJT4lIAoJa2FibGVfc3R5bGluZyhib290c3RyYXBfb3B0aW9ucyA9IGMoImNvbmRlbnNlZCIsICJyZXNwb25zaXZlIikpICU+JQogIHNjcm9sbF9ib3god2lkdGggPSAiMTAwJSIsIGhlaWdodCA9ICI0MDBweCIpCmBgYAoKYGBge3IgY2F0ZWdfRURBLCBmaWcuY2FwPSJDYXRlZ29yaWNhbCBWYXJpYWJsZXMifQp0aGVtZV9zZXQoIAoJdGhlbWVfbWluaW1hbChiYXNlX2ZhbWlseSA9ICdUaW1lcycsCgkJCQkJCQkJYmFzZV9zaXplID0gMTApICsKCXRoZW1lKAoJbGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiLAoJcGFuZWwuZ3JpZC5taW5vci54ID0gZWxlbWVudF9ibGFuaygpLAoJcGFuZWwuZ3JpZC5tYWpvci55ID0gZWxlbWVudF9ibGFuaygpLAoJYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoY29sb3IgPSAiZ3JheTYwIiwgCgkJCQkJCQkJCQkJCQkJIHNpemUgPSA4KSwKCWF4aXMudGl0bGUueCA9IGVsZW1lbnRfYmxhbmsoKSwKCSkgCikKCmNvbG9ycz1jKCIjMkU0NUI4IiwiI0Q2REJGNSIpCgoKbGlicmFyeSgiY293cGxvdCIpCnBsb3RfZ3JpZCgKZ2dkcmF3KCkgKyBkcmF3X2xhYmVsKCJDYXRlZ29yaWNhbCBWYXJpYWJsZXMgb2YgRGF0YSB3aXRoIEF0dHJpdGlvbiIsIGZvbnRmYWNlID0gImJvbGQiKSwKCnBsb3RfZ3JpZCgKCWdncGxvdChkYXRhLGFlcyhmaWxsPUF0dHJpdGlvbix4PUdlbmRlcikpICsKCWdlb21fYmFyKHBvc2l0aW9uPSJkb2RnZTIiLGFscGhhPTAuOCxjb2xvcj0iYmxhY2siKSArCglzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXM9Y29sb3JzKSArIGNvb3JkX2ZsaXAoKSwKCglnZ3Bsb3QoZGF0YSxhZXMoZmlsbD1BdHRyaXRpb24seD1EZXBhcnRtZW50KSkrZ2VvbV9iYXIocG9zaXRpb249ImZpbGwiLGFscGhhPTAuOCxjb2xvcj0iYmxhY2siKStzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXM9Y29sb3JzKStjb29yZF9mbGlwKCksCgkKICBnZ3Bsb3QoZGF0YSxhZXMoZmlsbD1BdHRyaXRpb24sIHg9Sm9iUm9sZSkpK2dlb21fYmFyKHBvc2l0aW9uPSJmaWxsIixhbHBoYT0wLjgsY29sb3I9ImJsYWNrIikrc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzPWNvbG9ycykrY29vcmRfZmxpcCgpLAoKCWdncGxvdChkYXRhLGFlcyhmaWxsPUF0dHJpdGlvbiwgeD1FZHVjYXRpb24pKStnZW9tX2Jhcihwb3NpdGlvbj0iZmlsbCIsYWxwaGE9MC44LGNvbG9yPSJibGFjayIpK3NjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcz1jb2xvcnMpK2Nvb3JkX2ZsaXAoKSwKCglnZ3Bsb3QoZGF0YSxhZXMoZmlsbD1BdHRyaXRpb24sIHg9T3ZlclRpbWUpKStnZW9tX2Jhcihwb3NpdGlvbj0iZmlsbCIsYWxwaGE9MC44LGNvbG9yPSJibGFjayIpK3NjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcz1jb2xvcnMpK2Nvb3JkX2ZsaXAoKSwKCglnZ3Bsb3QoZGF0YSxhZXMoZmlsbD1BdHRyaXRpb24sIHg9RWR1Y2F0aW9uRmllbGQpKStnZW9tX2Jhcihwb3NpdGlvbj0iZmlsbCIsYWxwaGE9MC44LGNvbG9yPSJibGFjayIpK3NjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcz1jb2xvcnMpK2Nvb3JkX2ZsaXAoKSwKCgluY29sID0gMiksIAoKcmVsX2hlaWdodHMgPSBjKDAuMSwgMSwgMC4yKSwKCmdldF9sZWdlbmQoZ2dwbG90KGRhdGEsYWVzKGZpbGw9QXR0cml0aW9uLHg9R2VuZGVyKSkrZ2VvbV9iYXIocG9zaXRpb249ImZpbGwiLGFscGhhPTAuOCxjb2xvcj0iYmxhY2siKStzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXM9Y29sb3JzKStjb29yZF9mbGlwKCkrIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJ0b3AiKSksIApuY29sID0gMSkKYGBgCgojIyBEYXRvcyBGYWx0YW50ZXMgJiBEdXBsaWNhZG9zCmBgYHtyfQpjYmluZCgnTWlzc2luZ3MnID0gbmFuaWFyOjpuX21pc3MoZGF0YSksCidEdXBsaWNhZG9zJyA9IG5yb3coamFuaXRvcjo6Z2V0X2R1cGVzKGRhdGEpKSkKYGBgCk5vIGhheSBuaSBkYXRvcyBmYWx0YW50ZXMgbmkgZmlsYXMgZHVwbGljYWRhcwoKCi0gKkFnZSwgRGFpbHlSYXRlLCBEaXN0YW5jZUZyb21Ib21lLCBIb3VybHlSYXRlLCBNb250aGx5UmF0ZSwgUGVyY2VudFNhbGFyeUhpa2UqIG5vIHRpZW5lbiBvdXRsaWVycy4KLSAqTnVtQ29tcGFuaWVzV29ya2VkLCBUcmFpbmluZ1RpbWVzTGFzdFllYXIsIFllYXJzV2l0aEN1cnJNYW5hZ2VyLCBZZWFyc0luQ3VycmVudFJvbGUqIHRpZW5lbiB1biBtb2RlcmFkbyBudW1lcm8gZGUgb3V0bGllcnMuCi0gKk1vbnRobHlJbmNvbWUsIFRvdGFsV29ya2luZ1llYXJzLCBZZWFyc0F0Q29tcGFueSwgWWVhcnNTaW5jZUxhc3RQcm9tb3Rpb24qIHRpZW5lbiB1biBsYXJnbyBudW1lcm8gZGUgb3V0bGllcnMuCgpObyBoYXLDqSBtYXMgY29uc2lkZXJhY2lvbmVzIGVuIGN1YW50bywgZGUgdG9kYXMgZm9ybWFzLCBsb3Mgb3V0bGllcnMgbm8gYWZlY3RhbiBsb3MgbW9kZWxvcyBkZSDDoXJib2xlcyBxdWUgdm95IGEgcGxhbnRlYXIuCgojIyBBbsOhbGlzaXMgZGUgbGEgQ29ycmVsYWNpw7NuCkVzdGUgcGFydGUgZGUgbGEgYW7DoWxpc2lzLCBtZSBwcm9wb3JjaW9uYXLDoSBldmlkZW5jaWFzIHNvYnJlIGxhIGNvcnJlbGFjacOzbiBlbnRyZSBsb3MgcmVncmVzb3Jlcy4KCmBgYHtyIGNvcnIsIGZpZy5oZWlnaHQ9MTAsIGZpZy53aWR0aD0xMCwgZmlnLmNhcD0iTWF0cml6IGRlIENvcnJlbGFjacOzbiJ9CmNvcnJfbXggPSBhcy5tYXRyaXgoKGNvcihkYXRhWywgc2FwcGx5KGRhdGEsIGlzLm51bWVyaWMpXSAlPiUgc2VsZWN0KC1TdGFuZGFyZEhvdXJzLCAtRW1wbG95ZWVDb3VudCkpKSkgCgpjb3JycGxvdDo6Y29ycnBsb3QoCiBjb3JyX214LCBtZXRob2QgPSAnc3F1YXJlJywgdHlwZSA9ICdsb3dlcicsIAogZGlhZyA9IFQsIAogdGwuY29sID0gJyMyRTQ1QjgnLCB0bC5jZXggPSAxLjMsICAKIGNvbCA9IHBhaW50ZXI6OlBhbGV0dGUoIiNENkRCRjUiLCIjNDA1NUJFIiwgMTApCikKYGBgCgotICAgTW9udGhseUluY29tZSBlc3TDoSBtdXkgY29ycmVsYWRhIGNvbiBKb2JMZXZlbAotICAgTGEgY29ycmVsYWPDrW9uIGVudHJlIFRvdGFsV29ya2luZ1llYXJzIHkgSm9iTGV2ZWwgZXMgMC43OCwgcXVlIHRhbWJpw6luIGVzdMOhIGJhc3RhbnRlIGFsdGEuCi0gICBMYSB2YXJpYWJsZSBQZXJjZW50U2FsYXJ5SGlrZSB0aWVuZSBjb3JyZWxhY8Otb24gZWxldmFkYSBjb24gUGVyZm9ybWFuY2VSYXRpbmcKCi0gICBFbCBjb25qdW50byBkZSB2YXJpYWJsZXMgWWVhcnNTaW5jZUxhc3RQcm9tb3Rpb24sIFllYXJzSW5DdXJyZW50Um9sZSwgWWVhcnNXaXRoQ3Vyck1hbmFnZXIgeSBZZWFyc0F0Q29tcGFueSwgZXN0w6FuIGNvcnJlbGFkYXMgZW50cmUgZWxsYXMgbWlzbWFzIHkgZW50b25jZXMgZWxlZ2lyw6kgc29sbyBkb3MgZGUgZWxsYXMgcGFyYSByZWR1Y2lyIGxhIGNvbXBsZWppZGFkIGRlIGxvcyDDoXJib2xlcwoKVG9kYXMgbGFzIGRlbcOhcyB0aWVuZW4gdW5hIGNvcnJlbGFjw61vbiBtZW5vciBxdWUgMC44MC4KCioqKgojIEZlYXR1cmVzIEVuZ2luZWVyaW5nCiMjIFNlbGVjY2nDs24gZGUgVmFyaWFibGVzClVuYXMgdmFyaWFibGVzIG5vIGV4cGxpY2FuIHZhcmlhYmlsaWRhZCBlbiBlbCBtb2RlbG8gcGxhbnRlYWRvLiBFc3RhcyB2YXJpYWJsZXMgc29uOgoKMS4gKkVtcGxveWVlTnVtYmVyKjogZGVub3RhIGVsIG51bWVybyBkZSBpZGVudGlmaWNhY2nDs24gZGVsIGVtcGxlYWRvLgoyLiAqRW1wbG95ZWVDb3VudCo6IGVzdGUgZXMganVzdG8gdW5hIGN1ZW50YSBkZWwgZW1wbGVhZG8geSBlbnRvbmNlcyB0b21hIHNpZW1wcmUgdmFsb3IgaWd1YWwgYSAxLgozLiAqT3ZlcjE4KjogZXN0YSB2YXJpYWJsZSBkZXNjcmliZSBzaSBlbCBlbXBsZWFkbyBlcyBtYXlvciBkZSAxOCBhw7Fvcy4gVG9tYSB2YWxvciAq4oCYWWVz4oCZKiBlbiB0b2RvcyBsb3MgY2Fzb3MuCjQuICpTdGFuZGFyZEhvdXJzKjogZWwgbnVtZXJvIGVzdMOhbmRhciBkZSBob3JhcyBkZSB0cmFiYWpvIHBvciBzZW1hbmEuIFRpZW5lIHZhbG9yIGNvbnN0YW50ZSBkZSA4MC4KCkx1ZWdvLCBjb21vIHlhIGRpY2hvIHBhcmEgcmVkdWNpciBsYSBjb21wbGVqaWRhZCBkZSBsb3Mgw6FyYm9sZXMsIHF1aXRhcsOpIDIsIHNvYnJlIDQsIHZhcmlhYmxlcyBxdWUgcHJvcG9yY2lvbmFuIGluZm9ybWFjaW9uZXMgc29icmUgbG9zIGHDsW9zIGRlIHRyYWJham8gZW4gbGEgZW1wcmVzYSB5IHRhbWJpw6luIHF1aXRhcsOpICpNb250aGx5UmF0ZSogZW4gY3VhbnRvIHlhIHRlbmdvIGxhIHZhcmlhYmxlICpEYWlseVJhdGUqIHF1ZSBzaW1wbGVtZW50ZSBlc3TDoSBleHBsaWNhZGEgZW4gb3RyYSB1bmlkYWQuClBvciBlc3RhcyByYXpvbmVzLCBxdWl0YXLDqSBlc3RhcyB2YXJpYWJsZXMgZGUgbWkgY29uanVudG8gZGUgZGF0b3MuIAoKYGBge3J9CmRhdGEgJTw+JSBzZWxlY3QoLUVtcGxveWVlTnVtYmVyLC1FbXBsb3llZUNvdW50LCAtT3ZlcjE4LCAtU3RhbmRhcmRIb3VycywgLVRyYWluaW5nVGltZXNMYXN0WWVhciwgLVllYXJzV2l0aEN1cnJNYW5hZ2VyLCAtWWVhcnNJbkN1cnJlbnRSb2xlLCAtTW9udGhseVJhdGUpCmBgYAoKVGFtYmnDqW4gZWwgY29uanVudG8gZGUgZGF0b3MgZXN0w6EgbXV5IGJ1ZW5vIHBhcmEgdHJhYmFqYXIsIGFzw60gcXVlIG5vIG5lY2VzaXRhcsOpIGNyZWFyIG90cmFzIHZhcmlhYmxlcyBhIHBhcnRpciBkZSBsYXMgcXVlIHlhIHRlbmdvLgoKTm8gZW1wbGVhcsOpIG90cm9zIGFsZ29yaXRtb3MgZW4gY3VhbnRvIGxhcyB0w6ljbmljYXMgZGUgw6FyYm9sZXMgeWEgZGVzYXJyb2xsYW4gbG9zIG1vZGVsb3Mgc29icmUgbGFzIHZhcmlhYmxlcyBtw6FzIGluZmx1eWVudGVzLCBwZXJvIGVuc2VndWlkYSBpbXBsZW1lbnRhcsOpIG90cmFzIHTDqWNuaWNhcyBkZSBNTCBzcGFyYSBlbCBjb25qdW50byBjb24gbGFzIHZhcmlhYmxlcyBtw6FzIGltcG9ydGFudGVzLgoKIyMgRGF0YSBQcmVwcm9jZXNzaW5nCiMjIyBEdW1taWVzCmBgYHtyIGR1bW15fQojIE9idGVuZXIgZHVtbWllcyBwb3IgbGFzIHZhcmlhYmxlcyBjYXRlZ29yaWNhcywgcXVpdGFuZG8geQpkYXRvcyA8LSBmYXN0RHVtbWllczo6ZHVtbXlfY29scyhkYXRhWy0yXSwgcmVtb3ZlX3NlbGVjdGVkX2NvbHVtbnMgPSBUKQojIEHDsWFkaXIgdGhlIHkgdmFyaWFibGUKZGF0b3MgPC0gY2JpbmQoJ0F0dHJpdGlvbicgPSBkYXRhJEF0dHJpdGlvbiwgZGF0b3MpCmBgYAoKYGBge3IsIGV2YWw9RkFMU0UsIGluY2x1ZGU9RkFMU0V9CiNFbiBzZWd1aWRhLCB2b3kgYSBxdWl0YXIgcG9yIGNhZGEgZHVtbXksIHVuYSBjbGFzZSBlbiBjdWFudG8gZXN0YSBpbmZvcm1hY2lvbiBlc3RhIGNvbnRlbnRpZGEgZW4gbGFzIG90cmFzIGNvbHVtbmFzIGN1YW5kbyBhbWJhcyBlc3RhbiBhIDAKCmRhdG9zIDwtIHNlbGVjdCgtQnVzaW5lc3NUcmF2ZWxfVHJhdmVsX1JhcmVseSwKCQkJCQkJCQktYERlcGFydG1lbnRfSHVtYW4gUmVzb3VyY2VzYCwKCQkJCQkJCQktRWR1Y2F0aW9uXzQsCgkJCQkJCQkJLWBFZHVjYXRpb25GaWVsZF9IdW1hbiBSZXNvdXJjZXNgLAoJCQkJCQkJCS1HZW5kZXJfTWFsZSwKCQkJCQkJCQktYEpvYlJvbGVfSHVtYW4gUmVzb3VyY2VzYCwKCQkJCQkJCQktTWFyaXRhbFN0YXR1c19EaXZvcmNlZCwKCQkJCQkJCQktT3ZlclRpbWVfTm8pCmBgYAoKIyMjIEVzdGFuZGFyaXphY2nDs24gZGUgVmFyaWFibGVzCkxhICpyZWdyZXNpw7NuIGxvZ8Otc3RpY2EqIHkgbG9zIGFsZ29yaXRtb3MgYmFzYWRvcyBlbiDDoXJib2xlcywgY29tbyBlbCAqRGVjaXNpb24gVHJlZSosIGVsICpSYW5kb20gRm9yZXN0KiB5IGVsICpHcmFkaWVudCBCb29zdGluZyosIG5vIHNvbiBzZW5zaWJsZXMgYSBsYSBtYWduaXR1ZCBkZSBsYXMgdmFyaWFibGVzLiBQb3IgbG8gdGFudG8sIG5vIGVzIG5lY2VzYXJpYSBsYSBlc3RhbmRhcml6YWNpw7NuIGFudGVzIGRlIGFqdXN0YXIgZXN0ZSB0aXBvIGRlIG1vZGVsb3MuCgojIyBFbnRyZW5hbWllbnRvIC8gUHJ1ZWJhCioqU3RyYXRpZmllZCBUcmFpbi1UZXN0IFNwbGl0cyoqCgpDb21vIGVsIGRhdGFzZXQgbm8gdGllbmUgdW4gbnVtZXJvIGJhbGFuY2VhZG8gZGUgZWplbXBsb3MgcG9yIGNhZGEgY2xhc2UgZGUgbGEgdmFyaWFibGUgZGVwZW5kaWVudGUsIHZveSBhIHJlcGFydGlyIGxvcyBkYXRvcyBlbnRyZSBsb3MgY29uanVudG8gdHJhaW4geSB0ZXN0LCBkZSB1bmEgbWFuZXJhIHF1ZSBwcmVzZXJ2YSBlbCBtaXNtbyBudW1lcm8gZGUgZWplbXBsb3MgZW4gY2FkYSBjbGFzZSBjb21vIGVsIGNvbmp1bnRvIG9yaWdpbmFsLiBFc3RlIHByb2NlZGltaWVudG8gZXMgbGxhbWFkbyBjb21vICoqbXVlc3RyZW8gdHJhaW4tdGVzdCBlc3RyYXRpZmljYWRvKiouCgpQb3IgZGVmZWN0bywgbGEgZnVuY2nDs24gKmNhcmV0KiBgY3JlYXRlRGF0YVBhcnRpdGlvbmAsIGhhY2UgbGEgZXN0cmF0aWZpY2FjacOzbiBkZSBlc3RhIG1hbmVyYS4KYGBge3J9CmxpYnJhcnkoY2FyZXQpCnRyYWluSW5kZXggPC0gY3JlYXRlRGF0YVBhcnRpdGlvbihkYXRvcyRBdHRyaXRpb24sIAoJCQkJCQkJCQkJCQkJCQkJCXAgPSAuOCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxpc3QgPSBGQUxTRSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aW1lcyA9IDEpClRyYWluIDwtIGRhdG9zWyB0cmFpbkluZGV4LF0KVGVzdCAgPC0gZGF0b3NbLXRyYWluSW5kZXgsXQoKcm0odHJhaW5JbmRleCkKcmJpbmQoIlRlc3QiID0gZGltKFRyYWluKSwiVHJhaW4iID0gIGRpbShUZXN0KSkgJT4lCmthYmxlKGNhcHRpb24gPSAiUGFydGljacOzbiBlbiBFbnRyZW5hbWllbnRvIHkgUHJ1ZWJhIiwKICAgICAgY29sLm5hbWVzID0gYygiT2JzZXJ2YWNpb25lcyIsIlZhcmlhYmxlcyIpKQpgYGAKTm8gb2JzdGFudGUsIHRlbmVtb3MgdG9kYXbDrWEgcXVlIGxpZGlhciBjb24gZWwgZGVzYmFsYW5jZW8uCgojIyBEZXNiYWxhbmNlbyBkZSBEYXRvcwpFbCBwYXF1ZXRlIGBjYXJldGAgdGllbmUgdW5hIGZ1bmNpw7NuIGB1cFNhbXBsZWAsIHF1ZSByZWFqdXN0YSBsYXMgZnJlY3VlbmNpYXMgZGUgbGFzIGNsYXNlcywgaGFjaWVuZG8gdW4gbXVlc3RyZW8gY29uIHJlZW1wbGF6byBwYXJhIHF1ZSBsYSBkaXN0cmlidWNpw7NuIGVuIGNhZGEgY2xhc2Ugc2VhIGlndWFsLiAKCiFbVXAtU2FtcGxpbmcgVGVjaG5pcXVlXShmaWxlcy9pbWcvdXBzYW1wbGluZy5wbmcpCgpgYGB7ciBpbWJhbGFuY2VtZW50XzF9CnByZWRpY3RvcnMgPSBuYW1lcyhUcmFpbilbbmFtZXMoVHJhaW4pICE9ICJBdHRyaXRpb24iXQoKdXBUcmFpbiA8LSB1cFNhbXBsZSh4ID0gVHJhaW5bLHByZWRpY3RvcnNdLAogICAgICAgICAgICAgICAgICAgeSA9IFRyYWluJEF0dHJpdGlvbiwKCQkJCQkJCQkgICBsaXN0ID0gRkFMU0UsCgkJCQkJCQkJCSB5bmFtZSA9ICJBdHRyaXRpb24iKQoKdXBUcmFpbiAlPD4lIGphbml0b3I6OmNsZWFuX25hbWVzKCkKVGVzdCAlPD4lIGphbml0b3I6OmNsZWFuX25hbWVzKCkKcHJlZGljdG9ycyA9IG5hbWVzKHVwVHJhaW4pW25hbWVzKHVwVHJhaW4pICE9ICJhdHRyaXRpb24iXQpgYGAKCkFob3JhIHZveSBhIGNvbXByb2JhciBsb3MgbnVtZXJvcyBkZSBvYnNlcnZhY2lvbmVzIGVuIGNhZGEgY2xhc2U6CgpgYGB7ciBpbWJhbGFuY2VtZW50XzJ9CmtuaXRyOjprYWJsZXMoZm9ybWF0ID0gImh0bWwiLAoJbGlzdCgKa2FibGUodGFibGUoVHJhaW4kQXR0cml0aW9uKSwKCWNhcHRpb24gPSAiSW1iYWxhbmNlZCBEYXRhIiwKCWNvbC5uYW1lcyA9IGMoIkNsYXNzIiwiTiIpKSwKa2FibGUodGFibGUodXBUcmFpbiRhdHRyaXRpb24pLAoJY2FwdGlvbiA9ICJCYWxhbmNlZCBEYXRhIiwKCWNvbC5uYW1lcyA9IGMoIkNsYXNzIiwiTiIpLCAKCXBvc2l0aW9uID0gImZsb2F0X3JpZ2h0IikKICkKKQpgYGAKCioqKgojIEFqdXN0ZSBkZWwgTW9kZWxvCiMjIFJlbXVlc3RyZW8KUHJpbWVybywgcGFyYSBjb21wcm9iYXIgbGEgdmFsaWRleiBkZSBsb3MgcmVzdWx0YWRvcyBkZWwgbW9kZWxvLCB2b3kgYSBlbXBsZWFyICoqdmFsaWRhY2nDs24gY3J1emFkYSoqOyBlc3RhIHTDqWNuaWNhIG1lIHZhIGEgZ2FyYW50aXIgcXVlIGxvcyByZXN1bHRhZG9zIHNvbiBpbmRlcGVuZGllbnRlcyBkZSBsYSBwYXJ0aWNpw7NuIGRlIGxvcyBkYXRvcyBlbnRyZSBlbnRyZW5hbWllbnRvIHkgcHJ1ZWJhLCBhIHRyYXbDqXMgZWwgdXNvIGRlIDUgc3ViY29uanVudG9zIGFsZWF0b3Jpb3MuIFBhcmEgbGEgcmVwcm9kdWNpYmlsaWRhZCBkZSBlc3RvcyBhbGdvcml0bW9zLCBoZSBmaWphZG8gbGEgc2VtaWxsYSBkZSBhbGVhdG9yaXphY2nDs24uCmBgYHtyfQpzZXQuc2VlZCgxMTIpCmNvbnRyb2wgPC0gdHJhaW5Db250cm9sKAoJbWV0aG9kID0gImN2IiwgbnVtYmVyID0gNSwgCgljbGFzc1Byb2JzID0gVFJVRSwgc2F2ZVByZWRpY3Rpb25zID0gImFsbCIpIApgYGAKCiMjIE1vZGVsbyBkZSBSZWZlcmVuY2lhClRlbmVtb3MgcXVlIGVzdGFibGVjZXIgcHJpbWVybyB1biBtb2RlbG8gZGUgcmVmZXJlbmNpYSBwYXJhIGNvbXBhcmFyIHNpIGxvcyBtb2RlbG9zIHF1ZSB2b3kgYSBlbXBsZWFyIGFwb3J0YW4gbWVqb3JhcyBjb25zaXN0ZW50ZXMuIFBhcmEgZWxsbywgaGUgYWp1c3RhZG8gdW4gbW9kZWxvIGRlIHJlZ3Jlc2nDs24gbG9nw61zdGljYS4gCgpgYGB7ciBMb2dpc3RpY2EgVHJhaW4sIGZpZy5jYXA9Ik1hdHJpeiBkZSBDb25mdXNpb24gTG9naXN0aWNhIiwgZXZhbD1GQUxTRX0KbG9naSA8LSB0cmFpbihhdHRyaXRpb24gfiAuLAoJCQkJCQkgZGF0YSA9IHVwVHJhaW4sCgkJCQkJCSBtZXRob2Q9ICJnbG0iLAoJCQkJCQkgdHJDb250cm9sPSBjb250cm9sKQpgYGAKCgpgYGB7ciBsb2dpc3RpY2EsIGZpZy5zaG93PSJob2xkIiwgb3V0LndpZHRoPSI1MCUiLCBmaWcuY2FwPSJSZWdyZXNzaW9uIExvZ2lzdGljYSIsIGZpZy5zdWJjYXA9YygiTWF0cml6IGRlIENvbmZ1c2nDs24iLCAiQXJlYSB1bmRlciB0aGUgcm9jIGN1cnZlIil9CgojQ29uZnVzaW9uIE1hdHJpeCAtLS0tCnNvdXJjZSgiZmlsZXMvQ29uZnVzaW9uTWF0cml4LlIiKQpkcmF3X2NvbmZ1c2lvbl9tYXRyaXgoCgljb25mdXNpb25NYXRyaXgobG9naSRwcmVkJHByZWQsCgkJCQkJCQkJCWxvZ2kkcHJlZCRvYnMpLAoJIiNENkRCRjUiLCAiIzJFNDVCOCIpCgpyb2MocmVzcG9uc2U9bG9naSRwcmVkJG9icywKCQlwcmVkaWN0b3I9bG9naSRwcmVkJFllcywgCgkJcXVpZXQgPSBUKSAlPiUgCglnZ3JvYyhjb2xvdXIgPSAiIzJFNDVCOCIsIAoJCQkJc2l6ZSA9IDAuOCkgKyAKICBhbm5vdGF0ZSgidGV4dCIsIHg9MC4wOCwgeT0wLjkyLCAKICAJCQkJIGxhYmVsPSAiYm9sZChBVUMpOiAwLjg2IiwgCiAgICAgICAgICAgZmFtaWx5ID0gIlRpbWVzIiwgCiAgCQkJCSBwYXJzZSA9IFRSVUUpICsKCWhyYnJ0aGVtZXM6OnRoZW1lX2lwc3VtKHRpY2tzID0gVCwKIAkJYmFzZV9mYW1pbHkgPSAnVGltZXMnLAliYXNlX3NpemUgPSAxMCkgKwogIGxhYnModGl0bGUgPSAnUk9DIEN1cnZlJywKICAJCSBzdWJ0aXRsZSA9ICdNb2RlbG8gZGUgUmVmZXJlbmNpYTogUmVncmVzc2lvbiBMb2dpc3RpY2EnKSArIAoJdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gJ25vbmUnLAoJCQkJcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChjb2xvcj0nIzNiNDU0YScpLAogIAkJIAl0aXRsZSA9IGVsZW1lbnRfdGV4dChmYWNlPSdib2xkJyksCiAgCQkgCWF4aXMudGl0bGUueSA9IGVsZW1lbnRfdGV4dChzaXplPTEwKSwKICAJCSAJYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoCiAgCQkgCQlmYWNlID0gJ2JvbGQnLCBjb2xvdXIgPSAnIzNEM0QzRCcsIHNpemUgPSAxMCkpIApgYGAKCmBgYHtyLCBldmFsPUZ9CkxvZ2lzdGljYSA8LSBjcnV6YWRhbG9naXN0aWNhKGRhdGEgPSB1cFRyYWluLAogCXZhcmRlcCA9ICJhdHRyaXRpb24iLAogCWxpc3Rjb250aSA9IHByZWRpY3RvcnMsCglsaXN0Y2xhc3MgPSBjKCIiKSwKCWdydXBvcyA9IDUsCglzaW5pY2lvID0gMTEyLAopCkxvZ2lzdGljYSRtb2RlbG8gPSAiTG9nacyBc3RpY2EiCmBgYAoKIyMgRGVjaXNpb24gVHJlZQojIyMgVHVuZW8gZGVsIE1vZGVsbwoKVW4gw6FyYm9sIGRlbWFzaWFkbyBjb21wbGVqbyBlcyBpbmVzdGFibGUsIG1pZW50cmFzLiBVbiDDoXJib2wgZGVtYXNpYWRvIHNlbmNpbGxvIHB1ZWRlIHRlbmVyIHBvY2EgcG90ZW5jaWEgcHJlZGljdGl2YSAoYWx0byBzZXNnbykgbyBiYWpvIHZhbG9yIGV4cGxpY2F0aXZvLiBBc8OtIHF1ZSBoYXLDqSB1biB0dW5lbyBkZSBsb3MgZGF0b3MgdGVuaWVuZG8gZW4gY29uc2lkZXJhY2nDs24gZXN0by4KRW4gZXN0ZSBwcm9jZXNvLCBtZSBkw60gY3VlbnRhIHF1ZSBsb3MgbWVqb3JlcyBtb2RlbG9zIGRlIMOhcmJvbGVzLCBlbiB0ZXJtaW5lIGRlICphY2N1cmFjeSosIHNlIGVuY3VlbnRyYW4gdHVuZWFuZG8gc29sbyBlbCBwYXLDoW1ldHJvIGBtaW5idWNrZXRgLCBxdWUgY29ycmVzcG9uZGUgYSBlbCBuw7ptZXJvIGRlIG9ic2VydmFjaW9uZXMgbcOtbmltYXMgZW4gY2FkYSBub2RvIGZpbmFsLgpMYSBsaWJyZXLDrWEgYGNhcmV0YCBubyBwZXJtaXRlIGRlIGhhY2VyIGVsIHR1bmVvLCBhc8OtIHF1ZSB0dXZlIHF1ZSBjcmVhciB1biBidWNsZSBxdWUgbWUgZGV2dWVsdmEgbG9zIGRpc3RpbnRvcyB2YWxvcmVzIHBhcmEgY2FkYSB2YWxvciBkZSBgbWluYnVja2V0YC4KCgpgYGB7ciBUdW5pbmcgVHJlZSwgZmlnLmNhcD0iVHVuZW8gZGVsIGFyYm9sIiwgZXZhbD1GQUxTRX0KCm1pbmJ1Y2tldF8gPC0gYygpCkFjY3VyYWN5IDwtIGMoKQpLYXBwYSA8LSBjKCkKQXVjIDwtIGMoKQoKZm9yIChtaW5idWNrZXQgaW4gc2VxKGZyb20gPSA1LCB0byA9IDIwNSwgYnkgPSAxMCkpIHsKYXJib2xjYXJldCA8LSB0cmFpbigKCQlmYWN0b3IoYXR0cml0aW9uKSB+IGFnZSArIGRhaWx5X3JhdGUgKyBkaXN0YW5jZV9mcm9tX2hvbWUgKyBlbnZpcm9ubWVudF9zYXRpc2ZhY3Rpb24gKyBob3VybHlfcmF0ZSArIGpvYl9pbnZvbHZlbWVudCArIGpvYl9zYXRpc2ZhY3Rpb24gKyBtb250aGx5X2luY29tZSArIG51bV9jb21wYW5pZXNfd29ya2VkICsgcGVyY2VudF9zYWxhcnlfaGlrZSArIHBlcmZvcm1hbmNlX3JhdGluZyArICByZWxhdGlvbnNoaXBfc2F0aXNmYWN0aW9uICsgc3RvY2tfb3B0aW9uX2xldmVsICsgdG90YWxfd29ya2luZ195ZWFycyArIHdvcmtfbGlmZV9iYWxhbmNlICsgeWVhcnNfYXRfY29tcGFueSArIHllYXJzX3NpbmNlX2xhc3RfcHJvbW90aW9uICsgYnVzaW5lc3NfdHJhdmVsX3RyYXZlbF9yYXJlbHkgKyBidXNpbmVzc190cmF2ZWxfdHJhdmVsX2ZyZXF1ZW50bHkgKyBidXNpbmVzc190cmF2ZWxfbm9uX3RyYXZlbCArIGRlcGFydG1lbnRfc2FsZXMgKyBkZXBhcnRtZW50X3Jlc2VhcmNoX2RldmVsb3BtZW50ICsgZGVwYXJ0bWVudF9odW1hbl9yZXNvdXJjZXMgKyBlZHVjYXRpb25fMiArIGVkdWNhdGlvbl8xICsgZWR1Y2F0aW9uXzQgKyBlZHVjYXRpb25fMyArIGVkdWNhdGlvbl81ICsgZWR1Y2F0aW9uX2ZpZWxkX2xpZmVfc2NpZW5jZXMgKyBlZHVjYXRpb25fZmllbGRfb3RoZXIgKyBlZHVjYXRpb25fZmllbGRfbWVkaWNhbCArIGVkdWNhdGlvbl9maWVsZF9tYXJrZXRpbmcgKyBlZHVjYXRpb25fZmllbGRfdGVjaG5pY2FsX2RlZ3JlZSArIGVkdWNhdGlvbl9maWVsZF9odW1hbl9yZXNvdXJjZXMgKyBnZW5kZXJfZmVtYWxlICsgZ2VuZGVyX21hbGUgKyBqb2Jfcm9sZV9zYWxlc19leGVjdXRpdmUgKyAgam9iX3JvbGVfcmVzZWFyY2hfc2NpZW50aXN0ICsgam9iX3JvbGVfbGFib3JhdG9yeV90ZWNobmljaWFuICsgIGpvYl9yb2xlX21hbnVmYWN0dXJpbmdfZGlyZWN0b3IgKyBqb2Jfcm9sZV9oZWFsdGhjYXJlX3JlcHJlc2VudGF0aXZlICsgam9iX3JvbGVfbWFuYWdlciArIGpvYl9yb2xlX3NhbGVzX3JlcHJlc2VudGF0aXZlICsgam9iX3JvbGVfcmVzZWFyY2hfZGlyZWN0b3IgKyBqb2Jfcm9sZV9odW1hbl9yZXNvdXJjZXMgKyBtYXJpdGFsX3N0YXR1c19zaW5nbGUgKyBtYXJpdGFsX3N0YXR1c19tYXJyaWVkICsgbWFyaXRhbF9zdGF0dXNfZGl2b3JjZWQgKyBvdmVyX3RpbWVfeWVzICsgb3Zlcl90aW1lX25vLAoJCWRhdGEgPSB1cFRyYWluLAoJCW1ldGhvZCA9ICJycGFydCIsCgkJdHJDb250cm9sID0gY29udHJvbCwKCQl0dW5lR3JpZCA9IGV4cGFuZC5ncmlkKGNwID0gYygwKSksCgkJY29udHJvbCA9IHJwYXJ0LmNvbnRyb2wobWluYnVja2V0ID0gbWluYnVja2V0KQopCgkJCiAgICBjb25mdXNpb25NYXRyaXggPC0gY29uZnVzaW9uTWF0cml4KAogICAgCSAgICAgICAgICBhcmJvbGNhcmV0JHByZWQkcHJlZCwgCgkJCQkJCQkJYXJib2xjYXJldCRwcmVkJG9icykKCQkKCQlyb2MgPC0gcm9jKHJlc3BvbnNlID0gYXJib2xjYXJldCRwcmVkJG9icywKCQkJCQkJCSBwcmVkaWN0b3IgPSBhcmJvbGNhcmV0JHByZWQkWWVzKQoJCQoJCUFjY19pIDwtIGNvbmZ1c2lvbk1hdHJpeCRvdmVyYWxsWzFdCgkJQWNjdXJhY3kgPC0gYXBwZW5kKEFjY3VyYWN5LCBBY2NfaSkKCQkKCQlLX2kgPC0gY29uZnVzaW9uTWF0cml4JG92ZXJhbGxbMl0KCQlLYXBwYSA8LSBhcHBlbmQoS2FwcGEsIEtfaSkKCQkKCQlBdWNfaSA8LSByb2MkYXVjCgkJQXVjIDwtIGFwcGVuZChBdWMsIEF1Y19pKQoJCQoJCW1pbmJ1Y2tldF8gPC0gYXBwZW5kKG1pbmJ1Y2tldF8sIG1pbmJ1Y2tldCkKCQkKCQkKCQkjCXN2TWlzYzo6cHJvZ3Jlc3MobWluYnVja2V0KQoJCWRwdXQoIi0tLS0tLS0tLS0tLS0tLSIpCgkJZHB1dChwYXN0ZTAoIldpdGggbWluYnVja2V0PSAiLCBtaW5idWNrZXQpKQoJCWRwdXQocGFzdGUwKCJBY2N1cmFjeTogIiwgIAoJCQkJIHJvdW5kKGNvbmZ1c2lvbk1hdHJpeCRvdmVyYWxsWzFdLCAzKSkpCgkJcHJpbnQocm9jJGF1YykKfQphcmJvbF9yZXN1bHRzID0gY2JpbmQoCglkYXRhLmZyYW1lKEFjY3VyYWN5LCBLYXBwYSwgQXVjKSwgCgknbWluYnVja2V0JyA9IGFzLmZhY3RvcihjKAoJCXNlcShmcm9tID0gNSwgdG8gPSAyMDUsIGJ5ID0gMTApKSkpCgojIENsZWFyIGNhY2hlIC0tLS0Kcm0oQWNjX2ksIEtfaSwgQXVjX2ksIG1pbmJ1Y2tldCwgcm9jKQpybShBY2N1cmFjeSwgQXVjLCBLYXBwYSwgbWluYnVja2V0XykKYGBgCjxkZXRhaWxzPgo8c3VtbWFyeT7igKLigKLigKI+Q0xpY2sgdG8gc2VlIGNvbnNvbGUgcmVzdWx0czwvYnV0dG9uPiA8L3N1bW1hcnk+CmBgYApyID4gIi0tLS0tLS0tLS0tLS0tLSJcbgpyID4gIldpdGggbWluYnVja2V0PSA1IlxuCnIgPiAiQWNjdXJhY3k6IDAuODQxIlxuCnIgPiBBcmVhIHVuZGVyIHRoZSBjdXJ2ZTogMC45MDUgXG4KciA+ICItLS0tLS0tLS0tLS0tLS0iXG4KciA+ICJXaXRoIG1pbmJ1Y2tldD0gMTUiXG4KciA+ICJBY2N1cmFjeTogMC43ODQiXG4KciA+IEFyZWEgdW5kZXIgdGhlIGN1cnZlOiAwLjg0MlxuCnIgPiAiLS0tLS0tLS0tLS0tLS0tIlxuCnIgPiAiV2l0aCBtaW5idWNrZXQ9IDI1IlxuCnIgPiAiQWNjdXJhY3k6IDAuNzQ5IlxuCnIgPiBBcmVhIHVuZGVyIHRoZSBjdXJ2ZTogMC44MTNcbgpyID4gIi0tLS0tLS0tLS0tLS0tLSJcbgpyID4gIldpdGggbWluYnVja2V0PSAzNSJcbgpyID4gIkFjY3VyYWN5OiAwLjczIlxuCnIgPiBBcmVhIHVuZGVyIHRoZSBjdXJ2ZTogMC43OTJcbgpyID4gIi0tLS0tLS0tLS0tLS0tLSJcbgpyID4gIldpdGggbWluYnVja2V0PSA0NSIKciA+ICJBY2N1cmFjeTogMC43NDYiCnIgPiBBcmVhIHVuZGVyIHRoZSBjdXJ2ZTogMC44MDYKciA+ICItLS0tLS0tLS0tLS0tLS0iCnIgPiAiV2l0aCBtaW5idWNrZXQ9IDU1IgpyID4gIkFjY3VyYWN5OiAwLjc0NCIKciA+IEFyZWEgdW5kZXIgdGhlIGN1cnZlOiAwLjc4OQpyID4gIi0tLS0tLS0tLS0tLS0tLSIKciA+ICJXaXRoIG1pbmJ1Y2tldD0gNjUiCnIgPiAiQWNjdXJhY3k6IDAuNzI3IgpyID4gQXJlYSB1bmRlciB0aGUgY3VydmU6IDAuNzg4CnIgPiAiLS0tLS0tLS0tLS0tLS0tIgpyID4gIldpdGggbWluYnVja2V0PSA3NSIKciA+ICJBY2N1cmFjeTogMC43MjciCnIgPiBBcmVhIHVuZGVyIHRoZSBjdXJ2ZTogMC43NzQKciA+ICItLS0tLS0tLS0tLS0tLS0iCnIgPiAiV2l0aCBtaW5idWNrZXQ9IDg1IgpyID4gIkFjY3VyYWN5OiAwLjcxOCIKciA+IEFyZWEgdW5kZXIgdGhlIGN1cnZlOiAwLjc1CnIgPiAiLS0tLS0tLS0tLS0tLS0tIgpyID4gIldpdGggbWluYnVja2V0PSA5NSIKciA+ICJBY2N1cmFjeTogMC43MTQiCnIgPiBBcmVhIHVuZGVyIHRoZSBjdXJ2ZTogMC43NTMKciA+ICItLS0tLS0tLS0tLS0tLS0iCnIgPiAiV2l0aCBtaW5idWNrZXQ9IDEwNSIKciA+ICJBY2N1cmFjeTogMC42NzciCnIgPiBBcmVhIHVuZGVyIHRoZSBjdXJ2ZTogMC43MQpyID4gIi0tLS0tLS0tLS0tLS0tLSIKciA+ICJXaXRoIG1pbmJ1Y2tldD0gMTE1IgpyID4gIkFjY3VyYWN5OiAwLjY4IgpyID4gQXJlYSB1bmRlciB0aGUgY3VydmU6IDAuNzE1CnIgPiAiLS0tLS0tLS0tLS0tLS0tIgpyID4gIldpdGggbWluYnVja2V0PSAxMjUiCnIgPiAiQWNjdXJhY3k6IDAuNjczIgpyID4gQXJlYSB1bmRlciB0aGUgY3VydmU6IDAuNzAxCnIgPiAiLS0tLS0tLS0tLS0tLS0tIgpyID4gIldpdGggbWluYnVja2V0PSAxMzUiCnIgPiAiQWNjdXJhY3k6IDAuNjc4IgpyID4gQXJlYSB1bmRlciB0aGUgY3VydmU6IDAuNjk1CnIgPiAiLS0tLS0tLS0tLS0tLS0tIgpyID4gIldpdGggbWluYnVja2V0PSAxNDUiCnIgPiAiQWNjdXJhY3k6IDAuNjYyIgpyID4gQXJlYSB1bmRlciB0aGUgY3VydmU6IDAuNjg4CnIgPiAiLS0tLS0tLS0tLS0tLS0tIgpyID4gIldpdGggbWluYnVja2V0PSAxNTUiCnIgPiAiQWNjdXJhY3k6IDAuNjUxIgpyID4gQXJlYSB1bmRlciB0aGUgY3VydmU6IDAuNjg0CnIgPiAiLS0tLS0tLS0tLS0tLS0tIgpyID4gIldpdGggbWluYnVja2V0PSAxNjUiCnIgPiAiQWNjdXJhY3k6IDAuNjUxIgpyID4gQXJlYSB1bmRlciB0aGUgY3VydmU6IDAuNjY0CnIgPiAiLS0tLS0tLS0tLS0tLS0tIgpyID4gIldpdGggbWluYnVja2V0PSAxNzUiCnIgPiAiQWNjdXJhY3k6IDAuNjM1IgpyID4gQXJlYSB1bmRlciB0aGUgY3VydmU6IDAuNjYyCnIgPiAiLS0tLS0tLS0tLS0tLS0tIgpyID4gIldpdGggbWluYnVja2V0PSAxODUiCnIgPiAiQWNjdXJhY3k6IDAuNjU3IgpyID4gQXJlYSB1bmRlciB0aGUgY3VydmU6IDAuNjc0CnIgPiAiLS0tLS0tLS0tLS0tLS0tIgpyID4gIldpdGggbWluYnVja2V0PSAxOTUiCnIgPiAiQWNjdXJhY3k6IDAuNjMyIgpyID4gQXJlYSB1bmRlciB0aGUgY3VydmU6IDAuNjYyCnIgPiAiLS0tLS0tLS0tLS0tLS0tIgpyID4gIldpdGggbWluYnVja2V0PSAyMDUiCnIgPiAiQWNjdXJhY3k6IDAuNjUxIgpyID4gQXJlYSB1bmRlciB0aGUgY3VydmU6IDAuNjc1CmBgYAo8L2RldGFpbHM+CgpgYGB7ciB0cmVlX3R1bmVfcmVzdWx0LCBmaWcuY2FwPSJUdW5lbyBkZWwgYXJib2wifQojIFBsb3QgLS0tLQpsaWJyYXJ5KGFwZXhjaGFydGVyKQphcGV4Y2hhcnQoCgl3aWR0aCA9IDgwMCwKCQlheF9vcHRzID0gbGlzdCgKCQkJY2hhcnQgPSBsaXN0KHR5cGUgPSAibGluZSIpLAoJCQlzdHJva2UgPSBsaXN0KGN1cnZlID0gInNtb290aCIpLAoJCQlncmlkID0gbGlzdCgKCQkJCWJvcmRlckNvbG9yID0gIiNlN2U3ZTciLAoJCQkJcm93ID0gbGlzdCgKCQkJCQljb2xvcnMgPSBjKCIjZjNmM2YzIiwgInRyYW5zcGFyZW50IiksCgkJCQkJb3BhY2l0eSA9IDAuNQoJCQkJKQoJCQkpLAoJCQltYXJrZXJzID0gbGlzdChzdHlsZSA9ICJpbnZlcnRlZCIsIHNpemUgPSA0KSwKCQkJc2VyaWVzID0gbGlzdCgKCQkJCWxpc3QobmFtZSA9ICJBY2N1cmFjeSIsCgkJCQkJCSBkYXRhID0gYXJib2xfcmVzdWx0cyRBY2N1cmFjeSksCgkJCQlsaXN0KG5hbWUgPSAiS2FwcGEiLAoJCQkJCQkgZGF0YSA9IGFyYm9sX3Jlc3VsdHMkS2FwcGEpLAoJCQkJbGlzdChuYW1lID0gIkFVQyIsCgkJCQkJCSBkYXRhID0gYXJib2xfcmVzdWx0cyRBdWMpCgkJCSksCgkJCXRpdGxlID0gbGlzdCh0ZXh0ID0gIlRyYWluaW5nIFJlc3VsdHMiLAoJCQkJCQkJCQkgYWxpZ24gPSAiY2VudGVyIiksCgkJCXhheGlzID0gbGlzdChjYXRlZ29yaWVzID0gYXJib2xfcmVzdWx0cyRtaW5idWNrZXQpCgkJKQoJKSAlPiUKCQlheF95YXhpcyhsYWJlbHMgPSBsaXN0KGZvcm1hdHRlciA9IGZvcm1hdF9udW0oIi4wJSIpKSkgJT4lCgkJYXhfc3Ryb2tlKGN1cnZlID0gInN0ZXBsaW5lIiwgd2lkdGggPSAyKSAlPiUKCQlheF9jb2xvcnMoIiM0QjY5Q0UiLCAiI0IwQkRFOSIsICIjNEE4MENGIikKYGBgCgpBIG1lbm9yIGBtaW5idWNrZXRgLCBvYnRlbmRyw6kgw6FyYm9sZXMgbcOhcyBjb21wbGVqb3MuIApDb21vIHBvZGVtb3MgY29tcHJvYmFyIGRlIGVzdGUgcGxvdCwgcGFyYSBlc3RvcyBkYXRvcyBlbCBtb2RlbG8gcGFyZWNlIHF1ZSBzZSBlc3RhYmxlY2UgYWxyZWRlZG9yIGRlbCB2YWxvciBgbWluYnVja2V0PTEwMGAuIFBvciB2YWxvcmVzIGluZmVyaW9yZXMgYSA2NSBzZSBvYnRlbmRyw61hIHVuIG1vZGVsbyBzb2JyZWFqdXN0YWRvLiBFbnRvbmNlcyBjb21vIHF1ZSBsb3MgcmVzdWx0YWRvcyBzb24gcGFyZWNpZG9zLCBtZSBxdWVkbyBjb24gZWwgdmFsb3IgZGUgMTA1IHBhcmEgb2J0ZW5lciB1biBtb2RlbG8gbcOhcyBlc3RhYmxlIHkgcXVlIG5vIHNlYSBwcm9wZW5zbyBhIGFsdG8gc2VzZ28uCgojIyMgUGxvdCBkZWwgTWVqb3Igw4FyYm9sCmBgYHtyLCBldmFsPUZ9CiNwb25lciBtYXhzdXJyb2dhdGU9MCBwYXJhIHF1ZSBzb2xvIG5vcyBwcmVzZW50ZSBsYSBpbXBvcnRhbmNpYSBkZSBsYXMgdmFyaWFibGVzIHF1ZSBlZmVjdGl2YW1lbnRlIHBhcnRpY2lwZW4gZW4gZWwgbW9kZWxvCnJwYXJ0KGZhY3RvcihhdHRyaXRpb24pIH4gLiwgCgkJCWRhdGEgPSB1cFRyYWluLAoJCQltaW5idWNrZXQgPSAxMDUsIAoJCQltZXRob2QgPSAiY2xhc3MiLAoJCQltYXhzdXJyb2dhdGUgPSAwLAoJCQlwYXJtcyA9IGxpc3Qoc3BsaXQgPSAiZ2luaSIpKSAtPiBhcmJvbApgYGAKYGBge3IgdHJlZX0KbGlicmFyeShycGFydC5wbG90KQpycGFydC5wbG90KGFyYm9sLHR5cGUgPSA0LCBleHRyYT0xMDUsCgkJCQkJIG5uID0gRiwgdHdlYWsgPSAxLjcsIAoJCQkJCSBnYXAgPSAxLCBzcGFjZSA9IDIsCgkJCQkJIGJveC5wYWxldHRlPSAiQmx1ZXMiKSAgIApgYGAKQ29tbyBwb2RlbW9zIG9ic2VydmFyIGRlIGVzdGUgcGxvdCwgc2Vnw7puIGVsIMOhcmJvbCBkZSBkZWNpc2nDs24gbGEgdmFyaWFibGUgbWFzIGluZmx1eWVudGUgZXMgKk92ZXJUaW1lX3llcyosIGVzIGRlY2lyIGxvcyBlbXBsZWFkb3MgcXVlIHRyYWJhamFuIG11Y2hhcyB2ZWNlcyBtw6FzIGRlIGxhcyBob3JhcyBwcmV2aXN0YXMsIHRpZW5kZW4gYSBkZWphciBzdSBwdWVzdG8gZGUgdHJhYmFqby4gVGFtYmnDqW4sIGluZmx1eWVuIG11Y2hvIHNpIGVsIGVtcGxlYWRvIHRyYWJhamEgZGVzZGUgcG9jbyB0aWVtcG8gYSBsYSBlbXByZXNhLiBFc3RlIHNlIHB1ZWRlIG5vdGFyIHBvciBsYSBpbXBvcnRhbmNpYSBkZSBsYXMgdmFyaWFibGVzICpUb3RhbCBXb3JraW5nIFllYXJzKiB5ICpKb2IgTGV2ZWwqIHF1ZSBhIG5pdmVsZXMgbWVub3JlcywgZXMgZGVjaXIgZWwgZW1wbGVhZG8gdHJhYmFqYSBkZXNkZSBwb2NvIHRpZW1wbyBhIGxhIGVtcHJlc2EgeSB0aWVuZSBwdWVzdG8gZGUgdHJhYmFqbyBkZSBsb3MgcHJpbWVyb3Mgbml2ZWxlcywgc3VlbGUgbWFyY2hhcnNlLiAKCiMjIyBNYXRyaXogZGUgQ29uZnVzacOzbiB5IEltcG9ydGFuY2lhIGRlIFZhcmlhYmxlcwoKYGBge3IgdmlwLCBmaWcuc2hvdz0iaG9sZCIsIG91dC53aWR0aD0iNTAlIiwgZmlnLmNhcD0iRmluYWwgTW9kZWwiLCBmaWcuc3ViY2FwPWMoIkltcG9ydGFuY2lhIGRlIFZhcmlhYmxlcyIsIk1hdHJpeiBkZSBDb25mdXNpw7NuIil9CmFyYm9sY2FyZXQgPC0gdHJhaW4oCglmYWN0b3IoYXR0cml0aW9uKSB+IC4sCgkJCQlkYXRhID0gdXBUcmFpbiwKCQkJCW1ldGhvZCA9ICJycGFydCIsCgkJCQl0ckNvbnRyb2wgPSBjb250cm9sLAoJCQkJdHVuZUdyaWQgPSBleHBhbmQuZ3JpZCgKCQkJCQljcCA9IGMoMCkpLAoJCQkJY29udHJvbCA9IHJwYXJ0LmNvbnRyb2woCgkJCQkJbWluYnVja2V0ID0gMTA1LAoJCQkJCW1heHN1cnJvZ2F0ZSA9IDApKQoKc291cmNlKCJmaWxlcy9Db25mdXNpb25NYXRyaXguUiIpCmRyYXdfY29uZnVzaW9uX21hdHJpeCgKCWNvbmZ1c2lvbk1hdHJpeChhcmJvbGNhcmV0JHByZWQkcHJlZCwKCQkJCQkJCQkJYXJib2xjYXJldCRwcmVkJG9icyksCgknI0Q2REJGNScsICcjMkU0NUI4JykKCmdncGxvdCgKCQl2aXA6OnZpKGFyYm9sKSAlPiUKCQkJZmlsdGVyKEltcG9ydGFuY2UgPiAwKSAlPiUKCQkJbXV0YXRlKFZhcmlhYmxlID0gc3RyaW5ncjo6c3RyX3RvX3RpdGxlKFZhcmlhYmxlKSkgJT4lCgkJCW11dGF0ZShWYXJpYWJsZSA9IHN0cl9yZXBsYWNlX2FsbChWYXJpYWJsZSwgIl8iLCAiICIpKSwKCQlhZXMoeCA9IHJlb3JkZXIoVmFyaWFibGUsIEltcG9ydGFuY2UpLCB5ID0gSW1wb3J0YW5jZSkKCSkgKwoJCWdnY2hpY2tsZXQ6Omdlb21fY2hpY2tsZXQoCgkJCWFlcyhmaWxsID0gSW1wb3J0YW5jZSksCgkJCXJhZGl1cyA9IGdyaWQ6OnVuaXQoMTAsICJwdCIpLAoJCQl3aWR0aCA9IDEsCgkJCXNob3cubGVnZW5kID0gRgoJCSkgKwoJCWxhYnMoeCA9IE5VTEwsCgkJCQkgdGl0bGUgPSAiVmFyaWFibGUgSW1wb3J0YW5jZSBQbG90IEFyYm9sIikgKwoJCXNjYWxlX3lfY29udGludW91cyhwb3NpdGlvbiA9ICJyaWdodCIpICsKCQlzY2FsZV9maWxsX2dyYWRpZW50KGxvdyA9ICcjRDZEQkY1JywgaGlnaCA9ICcjMkU0NUI4JykgKwoJCWNvb3JkX2ZsaXAoKSArCgkJaHJicnRoZW1lczo6dGhlbWVfaXBzdW0oYmFzZV9mYW1pbHkgPSAnVGltZXMnLCBncmlkID0gIlgiKSArCgkJdGhlbWUoCglwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KAoJCWhqdXN0PTAsIHZqdXN0ID0gLTAuNSksCglwbG90Lm1hcmdpbiA9IHVuaXQoYygwLCAxLCAwLjUsIDAuNSksICJjbSIpCgkJKQpgYGAKClNlZ8O6biBlc3RlIMOhcmJvbCBkZSBkZWNpc2lvbiwgbGFzIHZhcmlhYmxlcyBtw6FzIGltcG9ydGFudGVzIHNvbiBlbCAqT3ZlclRpbWVfWWVzKiwgcXVlIHRvbWEgbWF5b3IgaW1wb3J0YW5jaWEgY29tcGFyYWRhIGNvbiBsYXMgZGVtw6FzLCBlbCB0b3RhbCBkZSBhw7FvcyB0cmFiYWphbmRvIHkgZW5zZWd1aWRhIGVuY29udHJhbW9zIGxhIHNhdGlzZmFjY2nDs24gYWwgZW50b3JubyB5IGVsIG5pdmVsIGRlbCB0cmFiYWpvIHkgZWwgc2FsYXJpbyBtZW5zdWFsLgoKRWwgw6FyYm9sIHBsYW50ZWFkbyBubyBtZWpvcmEgZWwgbW9kZWxvIGRlIHJlZmVyZW5jaWEgZGUgcmVncmVzacOzbiBsb2fDrXN0aWNhIChhY2N1cmFjeSA9IDAuNzcpLiBFc3RlIHBvcnF1ZSB1biDDoXJib2wgc29sbyBlcyBpbmNsaW5lIGEgYWx0YSB2YXJpYW56YS4gUG9yIGVzdGEgcmF6w7NuLCBpbXBsZW1lbnRhcsOpIGFsZ29yaXRtb3MgZGUgKmVuc2VtYmxlIG1ldGhvZHMqLgoKCgojIyBCYWdnaW5nIFRyZWUKCkVsICoqQioqb290c3RyYXAgKiphZ2cqKnJlZ2F0KippbmcqKiAoYmFnZ2luZykgZXMgdW4gbcOpdG9kbyBxdWUgY29uc2VndWUgcmVkdWNpciBsYSB2YXJpYW56YSB5IGVsIHNvYnJlYWp1c3RlLiBFbCBhbGdvcml0bW8gZnVuY2lvbmEgZGUgZXN0YSBtYW5lcmE6Cgo+IERhZG8gdW4gY29uanVudG8gZGUgZW50cmVuYW1pZW50byBpbmljaWFsIGRlIHRhbWHDsW8gKm4qLCBlbCBiYWdnaW5nIGdlbmVyYSAqbSogbnVldm9zIGNvbmp1bnRvcyBkZSBlbnRyZW5hbWllbnRvLCBjYWRhIHVubyBkZSB0YW1hw7FvICpuJyosIGhhY2llbmRvIHVuIHJlbXVlc3RyZW8gZGVzZGUgZWwgY29uanVudG8gaW5pY2lhbCwgdW5pZm9ybWVtZW50ZSB5IGNvbiByZWVtcGxhem8gKCoqYm9vdHN0cmFwIHNhbXBsZSoqKS4gRWwgbXVlc3RyZW8gY29uIHJlZW1wbGF6byBhc2VndXJhIHF1ZSBjYWRhIGJvb3RzdHJhcCBzZWEgaW5kZXBlbmRpZW50ZSBkZSBzdXMgcGFyZXMuIEx1ZWdvLCBsb3MgKm0qIG1vZGVsb3Mgc2UgYWp1c3RhbiB1dGlsaXphbmRvIGxhcyAqbSogbXVlc3RyYXMgYm9vdHN0cmFwIHkgc2UgY29tYmluYW4gcHJvbWVkaWFuZG8gZWwgb3V0cHV0LgoKIVtCYWdnaW5nIHByb2Nlc3NdKGZpbGVzL2ltZy9iYWdnaW5nLnBuZykKCkNvbiBlc3RvcyBkYXRvcywgbWUgZXNwZXJvIHF1ZSBlbCBiYWdnaW5nIHZhIGEgc2VyIHVuIGJ1ZW4gbW9kZWxvIGVuIGN1YW50byBtaXMgdmFyaWFibGVzIG5vIHRpZW5lbiBzZXBhcmFjaW9uZXMgbGluZWFsZXMgeSBoYXkgbXVjaGFzIHZhcmlhYmxlcyBjYXRlZ29yw61jYXMuIAoKIyMjIFR1bmVvIGRlbCBNb2RlbG8KCmBgYHtyIGJhZ2dfdHVuaW5nLCBldmFsPUZBTFNFfQpsaWJyYXJ5KGRvUGFyYWxsZWwpCmxpYnJhcnkodGljdG9jKQpyZWdpc3RlckRvUGFyYWxsZWwobWFrZUNsdXN0ZXIoMykgLT4gY3B1KSAKCm5vZGVzaXplXyA8LSBjKCkKc2FtcHNpemVfIDwtIGMoKQpBY2N1cmFjeSA8LSBjKCkKS2FwcGEgPC0gYygpCkF1YyA8LSBjKCkKCnRpYygpCmZvciAobm9kZXNpemUgaW4gYygyMCw0MCw2MCw4MCwxMDApKSB7Cglmb3IgKHNhbXBzaXplIGluIGMoMjAwLDUwMCw4MDAsMTIwMCwxNTcwKSkgewpiZyA8LSB0cmFpbihkYXRhPXVwVHJhaW4sCgkJCQlmYWN0b3IoYXR0cml0aW9uKX4uLAoJCQkJbWV0aG9kPSJyZiIsIHRyQ29udHJvbD0gY29udHJvbCwKCQkJCSNmaWphciBtdHJ5IGZvciBiYWdnaW5nCgkJCQl0dW5lR3JpZD0gZXhwYW5kLmdyaWQobXRyeT1jKDUxKSksIAoJCQkJbnRyZWUgPSA1MDAwLCAKCQkJCXNhbXBzaXplID0gc2FtcHNpemUsIAoJCQkJbm9kZXNpemUgPSBub2Rlc2l6ZSwKCQkJCSNtdWVzdHJhcyBjb24gcmVlbXBsYXphbWllbnRvCgkJCQlyZXBsYWNlID0gVFJVRSwgCgkJCQlsaW5vdXQgPSBGQUxTRSkgCgpjb25mdXNpb25NYXRyaXggPC0gY29uZnVzaW9uTWF0cml4KAoJYmckcHJlZCRwcmVkLCBiZyRwcmVkJG9icykKCnJvYyA8LSByb2MocmVzcG9uc2UgPSBiZyRwcmVkJG9icywKCQkJCQkgcHJlZGljdG9yID0gYmckcHJlZCRZZXMpCgpBY2NfaSA8LSBjb25mdXNpb25NYXRyaXgkb3ZlcmFsbFsxXQpBY2N1cmFjeSA8LSBhcHBlbmQoQWNjdXJhY3ksIEFjY19pKQoKS19pIDwtIGNvbmZ1c2lvbk1hdHJpeCRvdmVyYWxsWzJdCkthcHBhIDwtIGFwcGVuZChLYXBwYSwgS19pKQoKQXVjX2kgPC0gcm9jJGF1YwpBdWMgPC0gYXBwZW5kKEF1YywgQXVjX2kpCgpub2Rlc2l6ZV8gPC0gYXBwZW5kKG5vZGVzaXplXywgbm9kZXNpemUpCnNhbXBzaXplXyA8LSBhcHBlbmQoc2FtcHNpemVfLCBzYW1wc2l6ZSkKCmRwdXQoIi0tLS0tLS0tLS0tLS0tLSIpCmRwdXQocGFzdGUwKCJXaXRoIG5vZGVzaXplPSAiLCBub2Rlc2l6ZSkpCmRwdXQocGFzdGUwKCJXaXRoIHNhbXBzaXplPSAiLCBzYW1wc2l6ZSkpCmRwdXQocGFzdGUwKCJBY2N1cmFjeTogIiwKCQkJCQkJcm91bmQoY29uZnVzaW9uTWF0cml4JG92ZXJhbGxbMV0sIDMpKSkKcHJpbnQocm9jJGF1YykKCX0KfQp0b2MoKQpzdG9wQ2x1c3RlcihjcHUpCgojIEFnZ3JlZ2F0ZSBNZXRyaWNzIC0tLS0KYmFnZ2luZ19yZXN1bHRzID0gY2JpbmQoCglkYXRhLmZyYW1lKEFjY3VyYWN5LCBLYXBwYSwgQXVjLCBub2Rlc2l6ZSA9IG5vZGVzaXplXywgc2FtcHNpemU9c2FtcHNpemVfKSkKCiNzYXZlKGJhZ2dpbmdfcmVzdWx0cywgZmlsZT0iYmFnZ2luZ19yZXN1bHRzLlJEYXRhIikKCiMgQ2xlYXIgY2FjaGUgLS0tLQpybShBY2NfaSwgS19pLCBBdWNfaSwgbm9kZXNpemUsIHJvYywgc2FtcHNpemUpCnJtKEFjY3VyYWN5LCBBdWMsIEthcHBhLCBub2Rlc2l6ZV8sIHNhbXBzaXplXykKYGBgCgpgYGB7ciBiYWdnaW5nLWJveGVzXzEsICBmaWcuc2hvdz0iaG9sZCIsIG91dC53aWR0aD0iNTAlIiwgZmlnLmNhcD0iVHVuaW5nIE5vZGVzaXplIFJlc3VsdHMiLCBmaWcuc3ViY2FwPWMoIkFyZWEgVW5kZXIgdGhlIFJPQyBjdXJ2ZSIsICJBY2N1cmFjeSIpfQoKbGlicmFyeShnZ2Vjb25vZGlzdCkKCmdncGxvdChiYWdnaW5nX3Jlc3VsdHMsCgkJCSBhZXMoeD1mYWN0b3Iobm9kZXNpemUpLCBBdWMpKSArIAoJZ2VvbV9lY29ub2Rpc3QoCgkJdGVudGhfY29sID0gJyM4NzlEREQnLAoJCW1lZGlhbl9jb2wgPSAnIzFEMkY2NScsCgkJbmluZXRpZXRoX2NvbCA9ICcjMzI1MkIxJykgKwogIHNjYWxlX3lfY29udGludW91cyhwb3NpdGlvbiA9ICJyaWdodCIsIAogIAkJCQkJCQkJCSBsaW1pdHMgPSByYW5nZSgwLjcwLCAxKSkgKwogIGxhYnMoCiAgCXggPSAnbm9kZXNpemUnLAogICAgdGl0bGUgPSAiVHVuaW5nIEJhZ2dpbmcgbm9kZXNpemUiLAogIAlzdWJ0aXRsZSA9ICdSZXN1bHRhZG9zIGZpbmFsZXMgYnVjbGUsIHBhcmFtZXRybyBub2Rlc2l6ZSB5IGNvbXBhcmFjaW9uIGNvbiBBVUMnKSArCgl0aGVtZV9lY29ub2Rpc3QoZWNvbl90ZXh0X2NvbCA9ICIjM2I0NTRhIiwKICBlY29uX3Bsb3RfYmdfY29sID0gIiNFNkVBRjgiLCAKICBlY29uX2dyaWRfY29sID0gIiNiYmNhZDIiLAogIGVjb25fZm9udCA9ICJUaW1lcyIsIAogIGxpZ2h0X2ZvbnQgPSAiVGltZXMiLAogIGJvbGRfZm9udCA9ICJUaW1lcyIpICsgCgl0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGZhY2UgPSAnYm9sZCcpKSAtPiBnZwoKZ3JpZC5uZXdwYWdlKCkKbGVmdF9hbGlnbihnZywgYygic3VidGl0bGUiLCAidGl0bGUiLCAiY2FwdGlvbiIpKSAlPiUgCiAgYWRkX2Vjb25vZGlzdF9sZWdlbmQoCiAgCWVjb25vZGlzdF9sZWdlbmRfZ3JvYihmYW1pbHkgPSAnVGltZXMnLAogICAgdGVudGhfY29sID0gJyM4NzlEREQnLAoJCW5pbmV0aWV0aF9jb2wgPSAnIzMyNTJCMScpLCAKICAJYmVsb3cgPSAic3VidGl0bGUiKSAlPiUgCiAgZ3JpZC5kcmF3KCkgCgpnZ3Bsb3QoYmFnZ2luZ19yZXN1bHRzLAoJCQkgYWVzKHg9ZmFjdG9yKG5vZGVzaXplKSwgQWNjdXJhY3kpKSArIAoJZ2VvbV9lY29ub2Rpc3QodGVudGhfY29sID0gJyM4NzlEREQnLAoJCW1lZGlhbl9jb2wgPSAnIzFEMkY2NScsCgkJbmluZXRpZXRoX2NvbCA9ICcjMzI1MkIxJykgKwogIHNjYWxlX3lfY29udGludW91cyhwb3NpdGlvbiA9ICJyaWdodCIsIAogIAkJCQkJCQkJCSBsaW1pdHMgPSByYW5nZSgwLjcsIDEpKSArCiAgbGFicygKICAJeCA9ICdub2Rlc2l6ZScsCiAgICB0aXRsZSA9ICJUdW5pbmcgQmFnZ2luZyBub2Rlc2l6ZSIsCiAgCXN1YnRpdGxlID0gJ1Jlc3VsdGFkb3MgZmluYWxlcyBidWNsZSwgcGFyYW1ldHJvIG5vZGVzaXplIHkgY29tcGFyYWNpb24gY29uIEFjY3VyYWN5JykgKwoJdGhlbWVfZWNvbm9kaXN0KGVjb25fdGV4dF9jb2wgPSAiIzNiNDU0YSIsCiAgZWNvbl9wbG90X2JnX2NvbCA9ICIjRTZFQUY4IiwgCiAgZWNvbl9ncmlkX2NvbCA9ICIjYmJjYWQyIiwKICBlY29uX2ZvbnQgPSAiVGltZXMiLCAKICBsaWdodF9mb250ID0gIlRpbWVzIiwKICBib2xkX2ZvbnQgPSAiVGltZXMiKSArIAoJdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChmYWNlID0gJ2JvbGQnKSkgLT4gZ2cKCmdyaWQubmV3cGFnZSgpCmxlZnRfYWxpZ24oZ2csIGMoInN1YnRpdGxlIiwgInRpdGxlIiwgImNhcHRpb24iKSkgJT4lIAogIGFkZF9lY29ub2Rpc3RfbGVnZW5kKAogIAllY29ub2Rpc3RfbGVnZW5kX2dyb2IoZmFtaWx5ID0gJ1RpbWVzJywKICAgIHRlbnRoX2NvbCA9ICcjODc5REREJywKCQluaW5ldGlldGhfY29sID0gJyMzMjUyQjEnKSwgCiAgCWJlbG93ID0gInN1YnRpdGxlIikgJT4lIAogIGdyaWQuZHJhdygpCmBgYAoKSGFjaWVuZG8gdmFsaWRhY2nDs24gY3J1emFkYSwgdGVuZ28gcXVlIGRlamFyIHVuIGZvbGQgZGUgdGFtYcOxbyAoMTk3NC81KT0zOTQsIHBvciBsbyB0YW50byB1dGlsaXphcsOpICg0LzUpKjE5NzQ9MTU3OSBvYnNlcnZhY2lvbmVzIHRyYWluaW5nIHBhcmEgY29uc3RydWlyIGVsIG1vZGVsbywgY29uIGxvIGN1YWwgZmlqYXJlIGVsIGBzYW1wc2l6ZWAgbcOheGltbyBjb24gMTU3OCBvYnNlcnZhY2lvbmVzLgoKYGBge3IgYmFnZ2luZy1ib3hlc18yLCBlY2hvPUZBTFNFLCBmaWcuc2hvdz0iaG9sZCIsIG91dC53aWR0aD0iNTAlIiwgZmlnLmNhcD0iVHVuaW5nIFNhbXBzaXplIFJlc3VsdHMiLCBmaWcuc3ViY2FwPWMoIkFyZWEgVW5kZXIgdGhlIFJPQyBjdXJ2ZSIsICJBY2N1cmFjeSIpfQoKZ2dwbG90KGJhZ2dpbmdfcmVzdWx0cywKCQkJIGFlcyh4PWZhY3RvcihzYW1wc2l6ZSksIEF1YykpICsgCglnZW9tX2Vjb25vZGlzdCh0ZW50aF9jb2wgPSAnIzg3OURERCcsCgkJbWVkaWFuX2NvbCA9ICcjMUQyRjY1JywKCQluaW5ldGlldGhfY29sID0gJyMzMjUyQjEnKSArCiAgc2NhbGVfeV9jb250aW51b3VzKHBvc2l0aW9uID0gInJpZ2h0IiwgCiAgCQkJCQkJCQkJIGxpbWl0cyA9IHJhbmdlKDAuNzAsIDEpKSArCiAgbGFicygKICAJeCA9ICdzYW1wc2l6ZScsCiAgICB0aXRsZSA9ICJUdW5pbmcgQmFnZ2luZyBzYW1wc2l6ZSIsCiAgCXN1YnRpdGxlID0gJ1Jlc3VsdGFkb3MgZmluYWxlcyBidWNsZSwgcGFyYW1ldHJvIHNhbXBzaXplIHkgY29tcGFyYWNpb24gY29uIEFVQycpICsKCXRoZW1lX2Vjb25vZGlzdChlY29uX3RleHRfY29sID0gIiMzYjQ1NGEiLAogIGVjb25fcGxvdF9iZ19jb2wgPSAiI0U2RUFGOCIsIAogIGVjb25fZ3JpZF9jb2wgPSAiI2JiY2FkMiIsCiAgZWNvbl9mb250ID0gIlRpbWVzIiwgCiAgbGlnaHRfZm9udCA9ICJUaW1lcyIsCiAgYm9sZF9mb250ID0gIlRpbWVzIikgKyAKCXRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoZmFjZSA9ICdib2xkJykpIC0+IGdnCgpncmlkLm5ld3BhZ2UoKQpsZWZ0X2FsaWduKGdnLCBjKCJzdWJ0aXRsZSIsICJ0aXRsZSIsICJjYXB0aW9uIikpICU+JSAKICBhZGRfZWNvbm9kaXN0X2xlZ2VuZCgKICAJZWNvbm9kaXN0X2xlZ2VuZF9ncm9iKAogIAkJZmFtaWx5ID0gJ1RpbWVzJywKICAJdGVudGhfY29sID0gJyM4NzlEREQnLAoJCW5pbmV0aWV0aF9jb2wgPSAnIzMyNTJCMScpLCAKICAJYmVsb3cgPSAic3VidGl0bGUiKSAlPiUgCiAgZ3JpZC5kcmF3KCkgCgpnZ3Bsb3QoYmFnZ2luZ19yZXN1bHRzLAoJCQkgYWVzKHg9ZmFjdG9yKHNhbXBzaXplKSwgQWNjdXJhY3kpKSArIAoJZ2VvbV9lY29ub2Rpc3QoCgkJbWVkaWFuX2NvbCA9ICcjMUQyRjY1JywKCQl0ZW50aF9jb2wgPSAnIzg3OURERCcsCgkJbmluZXRpZXRoX2NvbCA9ICcjMzI1MkIxJykgKwogIHNjYWxlX3lfY29udGludW91cyhwb3NpdGlvbiA9ICJyaWdodCIsIAogIAkJCQkJCQkJCSBsaW1pdHMgPSByYW5nZSgwLjcwLCAxKSkgKwogIGxhYnMoCiAgCXggPSAnc2FtcHNpemUnLAogICAgdGl0bGUgPSAiVHVuaW5nIEJhZ2dpbmcgc2FtcHNpemUiLAogIAlzdWJ0aXRsZSA9ICdSZXN1bHRhZG9zIGZpbmFsZXMgYnVjbGUsIHBhcmFtZXRybyBzYW1wc2l6ZSB5IGNvbXBhcmFjaW9uIGNvbiBBY2N1cmFjeScpICsKCXRoZW1lX2Vjb25vZGlzdChlY29uX3RleHRfY29sID0gIiMzYjQ1NGEiLAogIGVjb25fcGxvdF9iZ19jb2wgPSAiI0U2RUFGOCIsIAogIGVjb25fZ3JpZF9jb2wgPSAiI2JiY2FkMiIsCiAgZWNvbl9mb250ID0gIlRpbWVzIiwgCiAgbGlnaHRfZm9udCA9ICJUaW1lcyIsCiAgYm9sZF9mb250ID0gIlRpbWVzIikgKyAKCXRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoZmFjZSA9ICdib2xkJykpIC0+IGdnCgpncmlkLm5ld3BhZ2UoKQpsZWZ0X2FsaWduKGdnLCBjKCJzdWJ0aXRsZSIsICJ0aXRsZSIsICJjYXB0aW9uIikpICU+JSAKICBhZGRfZWNvbm9kaXN0X2xlZ2VuZCgKICAJZWNvbm9kaXN0X2xlZ2VuZF9ncm9iKAogIAkJZmFtaWx5ID0gJ1RpbWVzJywKCQl0ZW50aF9jb2wgPSAnIzg3OURERCcsCgkJbmluZXRpZXRoX2NvbCA9ICcjMzI1MkIxJyksIAogIAliZWxvdyA9ICJzdWJ0aXRsZSIpICU+JSAKICBncmlkLmRyYXcoKSAKYGBgCgpDb24gZXN0b3MgZ3LDoWZpY29zIGRlIGNhamFzIHkgYmlnb3Rlcywgc2UgcHVlZGUgb2JzZXJ2YXIgcXVlIGxvcyBwYXLDoW1ldHJvcyBxdWUgbGxldmFuIGEgbGFzIG1lam9yZXMgbcOpdHJpY2FzLCBlbiB0ZXJtaW5lcyBkZSAqw6FyZWEgYWJham8gbGEgY3VydmEgUk9DICogeSAqYWNjdXJhY3kqIHNvbiBgc2FtcHNpemU9MTU3MGAgeSBlbCB2YWxvciBtw6FzIGJham8gZGUgYG5vZGVzaXplYCwgcXVlIHBvciByYXpvbmVzIGRlIGVzdGFibGVhZCBubyBlbGlqYXLDqS4gTWUgcXVlZGFyw6kgY29uIHVuIGBub2Rlc2l6ZWAgZGUgNDAuCgojIyMgTW9kZWxvIGZpbmFsCmBgYHtyIGJnX2ZpbmFsX21vZGVsLCBldmFsPUZBTFNFfQpsaWJyYXJ5KHJhbmRvbUZvcmVzdCkKYmcgPC0gcmFuZG9tRm9yZXN0KGRhdGE9dXBUcmFpbiwKIAkJCQlmYWN0b3IoYXR0cml0aW9uKSB+IC4sCiAJCQkJbXRyeSA9IDUxLAogCQkJCW50cmVlID0gMTAwMCwKIAkJCQlzYW1wc2l6ZSA9IDE1NzAsCiAJCQkJbm9kZXNpemUgPSA0MCwKIAkJCQlyZXBsYWNlID0gVFJVRSwKIAkJCQlpbXBvcnRhbmNlID0gVFJVRSkKYGBgCgojIyMjIE91dC1PZi1CYWcgRXJyb3IKCkVsICoqT3V0LU9mLUJhZyAoT09CKSoqIGVzIGVsIGVycm9yIGNvbWV0aWRvIGVuIGxhcyBvYnNlcnZhY2lvbmVzIHF1ZSBubyBjYWVuIGVuIGxhIG11ZXN0cmEgZW4gY2FkYSBpdGVyYWNpw7NuLcOhcmJvbCwgeSBwb3IgdGFudG8gcHVlZGVuIHNlciB0b21hZG9zIGNvbW8gb2JzZXJ2YWNpb25lcyB0ZXN0IHkgc2lydmVuIHBhcmEgb2JzZXJ2YXIgZWwgZXJyb3IgY29tZXRpZG8gc29icmUgdGVzdCBhIG1lZGlkYSBxdWUgYXZhbnphbiBsYXMgaXRlcmFjY2lvbmVzLgoKYGBge3IgYmFnZ19vb2IsIGZpZy5jYXA9IkJhZ2dpbmcgT3V0LU9mLUJhZyBFcnJvciJ9CmFzLnRpYmJsZShjYmluZCgKCSdPT0InID0gYmckZXJyLnJhdGVbLDFdLAoJJ250cmVlJyA9IHNlcSgxOm5yb3coYmckZXJyLnJhdGUpKSkpICU+JQoJZ2dwbG90KGFlcyh4ID0gbnRyZWUseSA9IE9PQikpICsgCgkJCQkgCWdlb21fc3RlcChhZXMoY29sID0gT09CKSkgKwogICAgc2NhbGVfY29sb3JfY29udGludW91cyhsb3cgPSAiIzJFNDVCOCIsIAogICAgCQkJCQkJCQkJCQloaWdoID0gIiNENkRCRjUiKSArCglocmJydGhlbWVzOjp0aGVtZV9pcHN1bSgKCQliYXNlX2ZhbWlseSA9ICdUaW1lcycsCWJhc2Vfc2l6ZSA9IDEwKSArCiAgbGFicyh0aXRsZSA9ICdPdXQtT2YtQmFnIEVycm9yJywKICAJCSBzdWJ0aXRsZSA9ICdCYWdnaW5nIG1vZGVsJykgKyB0aGVtZSgKICAJCSAJdGl0bGUgPSBlbGVtZW50X3RleHQoZmFjZT0nYm9sZCcpLAogIAkJIAlsZWdlbmQucG9zaXRpb24gPSAnbm9uZScsCiAgCQkgCXBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoY29sb3I9JyMzYjQ1NGEnKSwKICAJCSAJYXhpcy50aXRsZS55ID0gZWxlbWVudF90ZXh0KHNpemU9MTApLAogIAkJIAlheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dCgKICAJCSAJCWZhY2UgPSAnYm9sZCcsIGNvbG91ciA9ICcjM0QzRDNEJywgc2l6ZSA9IDEwKSkKYGBgCgpTZSBwdWVkZSBub3RhciBjb21vIGVsIGVycm9yIG1lbm9yIHNlIGVzdGFibGVjZSBhbHJlZGVkb3IgZGUgdW4gYG50cmVlPTEwMDBgLiAKCiMjIyMgTWF0cml6IGRlIENvbmZ1c2nDs24geSBJbXBvcnRhbmNpYSBkZSBWYXJpYWJsZXMKCmBgYHtyIGJnX2ZpbmFsLCBmaWcuc2hvdz0iaG9sZCIsIG91dC53aWR0aD0iNTAlIiwgZmlnLmNhcD0iQmFnZ2luZyBGaW5hbCBNb2RlbCIsIGZpZy5zdWJjYXA9YygiSW1wb3J0YW5jaWEgZGUgVmFyaWFibGVzIiwiTWF0cml6IGRlIENvbmZ1c2nDs24iKX0KCmRyYXdfY29uZnVzaW9uX21hdHJpeCgKCWNvbmZ1c2lvbk1hdHJpeChiZyRwcmVkaWN0ZWQsIGJnJHkpLCAKCSIjRDZEQkY1IiwgIiMyRTQ1QjgiKQoKZ2dwbG90KAoJdmlwOjp2aShiZykgJT4lCgkgZmlsdGVyKEltcG9ydGFuY2UgPiAyNSkgJT4lCgkgbXV0YXRlKAoJICBWYXJpYWJsZSA9IHN0cmluZ3I6OnN0cl90b190aXRsZShWYXJpYWJsZSkpICU+JQoJIG11dGF0ZSgKCQlWYXJpYWJsZSA9IHN0cl9yZXBsYWNlX2FsbChWYXJpYWJsZSwgIl8iLCAiICIpKSwKCWFlcyh4ID0gcmVvcmRlcihWYXJpYWJsZSwgSW1wb3J0YW5jZSksIAoJCQl5ID0gSW1wb3J0YW5jZSkpICsKCQlnZ2NoaWNrbGV0OjpnZW9tX2NoaWNrbGV0KAoJCQlhZXMoZmlsbCA9IEltcG9ydGFuY2UpLAoJCQlyYWRpdXMgPSBncmlkOjp1bml0KDYsICJwdCIpLAoJCQl3aWR0aCA9IDAuOCwKCQkJc2hvdy5sZWdlbmQgPSBGCgkJKSArCgkJbGFicyh4ID0gTlVMTCwKCQkJCSB0aXRsZSA9ICJWYXJpYWJsZSBJbXBvcnRhbmNlIFBsb3QgQmFnZ2luZyIpICsKCQlzY2FsZV95X2NvbnRpbnVvdXMocG9zaXRpb24gPSAicmlnaHQiKSArCgkJc2NhbGVfZmlsbF9ncmFkaWVudChsb3cgPSAnI0Q2REJGNScsIGhpZ2ggPSAnIzJFNDVCOCcpICsKCQljb29yZF9mbGlwKCkgKwoJCWhyYnJ0aGVtZXM6OnRoZW1lX2lwc3VtKGJhc2VfZmFtaWx5ID0gJ1RpbWVzJywgZ3JpZCA9ICJYIikgKwoJCXRoZW1lKAoJCQlwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHZqdXN0ID0gLTEpLAoJCQlwbG90Lm1hcmdpbiA9IHVuaXQoYygwLCAxLCAxLCAwLjUpLCAiY20iKSkKYGBgCgpFbnRyZSBsYXMgbXVlc3RyYXMgZGUgbGFzIGl0ZXJhY2Npb25lcyBjYWxjdWxhZGFzLCBlbiBwcm9tZWRpbyBsYSB2YXJpYWJsZSBtw6FzIGluZmx1eWVudGUgZXMgKk1vbnRobHlJbmNvbWUqLCBlbCBzYWxhcmlvIGJydXRvIGRlIGxvcyBlbXBsZWFkb3MuIEVuIHNlZ3VpZGEsIHNlIGVuY3VlbnRyYW4gKkpvYlNhdGlzZmFjdGlvbiogeSAqRW52aXJvbm1lbnRTYXRpc2ZhY3Rpb24qLCBlcyBkZWNpciBsb3Mgbml2ZWwgZGUgc2F0aXNmYWNjacOzbiBjb24gZWwgdHJhYmFqbyB5IHN1IGVudG9ybm8gZW4gbGEgZW1wcmVzYS4KCkhheSBxdWUgdGVuZXIgZW4gY3VlbnRhIHF1ZSBjb24gbGEgdMOpY25pY2EgZGUgYmFnZ2luZywgZW50cmUgbG9zIMOhcmJvbGVzIHVuIHBhciBkZSB2YXJpYWJsZXMgc2Vyw6FuIGVuIHRvZG9zIG11eSBwYXJlY2lkb3MuIFBvciBlc3RvLCBhIHRyYXbDqXMgZGVsICpyYW5kb20gZm9yZXN0KiBwb2RlbW9zIGHDsWFkaXIgYWxlYXRvcmllZGFkIGVuIGxhcyB2YXJpYWJsZXMgZW50cmUgbG9zIGRpZmVyZW50ZXMgw6FyYm9sZXMsIHkgZXN0byB0YW1iacOpbiBwb2Ryw6EgcmVkdWNpciBsYSB2YXJpYW56YSBkZWwgbW9kZWxvLgoKIyMgUmFuZG9tIEZvcmVzdAoKRWwgYWxnb3JpdG1vICoqUmFuZG9tIEZvcmVzdCoqLCBhcHJvdmVjaGFuZG8gZGUgbGFzIHZlbnRhamFzIGRlbCAqYmFnZ2luZyosIG5vcyB2YSBhIGF5dWRhciBtw6FzIGEgbGEgaG9yYSBkZSBzZWxlY2Npb25hciBsYXMgdmFyaWFibGVzLCBwb3IgZWwgaGVjaG8gcXVlIGVzdGEgdMOpY25pY2EgaW5jb3Jwb3JhIGRvcyBmdWVudGVzIGRlIHZhcmlhYmlsaWRhZCwgZWwgcmVtdWVzdHJlbyBkZSBvYnNlcnZhY2lvbmVzIHkgZGUgdmFyaWFibGVzIHV0aWxpemFkYXMgZW4gY2FkYSBtb2RlbG8uIERlIGVzdGEgbWFuZXJhIHBvZGVtb3MgZ2VuZXJhbGl6YXIgbcOhcywgcmVkdWNpZW5kbyBlbCBzb2JyZWFqdXN0ZSB5IGNvbnNlcnZhbmRvIGEgbGEgdmV6IGxhcyByZWxhY2lvbmVzIHBhcnRpY3VsYXJlcyBlbnRyZSBsb3MgZGF0b3MuCgojIyMgVHVuZW8gZGVsIE1vZGVsbwoKUGFyYSB0dW5lYXIgbG9zIHBhcsOhbWV0cm9zIGhhcsOpIHVuIGJ1Y2xlIHNpbWlsYXIgYSBjb21vIGhlY2hvIHByZXZpYW1lbnRlIGVuIGxhIG1vZGVsaXphY2nDs24gZGVsIGJhZ2dpbmcuIEVuIGVzdGUgY2FzbywgdGVuZHLDqSBxdWUgdHVuZWFyIHRhbWJpw6luIGVsIHBhcsOhbWV0cm8gYG10cnlgLCBlbCBudW1lcm8gZGUgdmFyaWFibGVzIGEgc2VsZWNjaW9uYXIgY2FkYSB2ZXouIEVzdGUgcGFyw6FtZXRybyBzaSBsbyBmaWphbW9zIGFsIG3DoXhpbW8gbnVtZXJvIGRlIHZhcmlhYmxlcywgY29ycmVzcG9uZGVyw6EgYSBoYWNlciBlbCAqYmFnZ2luZyouIAoKIyMjIyBUdW5lbyBkZWwgbW9kZWxvCgpgYGB7ciByZl90dW5lLCBldmFsPUZBTFNFfQpyZWdpc3RlckRvUGFyYWxsZWwobWFrZUNsdXN0ZXIoMykgLT4gY3B1KSAKCm5vZGVzaXplXyA8LSBjKCkKc2FtcHNpemVfIDwtIGMoKQpBY2N1cmFjeSA8LSBjKCkKS2FwcGEgPC0gYygpCkF1YyA8LSBjKCkKCnRpYygpCmZvciAobm9kZXNpemUgaW4gYygxMCwyMCwzMCw0MCw2MCw1MCw4MCwxMDApKSB7Cglmb3IgKHNhbXBzaXplIGluIGMoMjAwLDM1MCw1MDAsODAwLDEwMDAsMTIwMCwxMzUwLDE1NzApKSB7CnJmIDwtIHRyYWluKGRhdGEgPSB1cFRyYWluLAoJCQkJZmFjdG9yKGF0dHJpdGlvbil+LiwKCQkJCW1ldGhvZCA9InJmIiwgdHJDb250cm9sPSBjb250cm9sLAoJCQkJdHVuZUdyaWQgPSBleHBhbmQuZ3JpZCgKCQkJCQltdHJ5PWMoNSwxMCwyMCwzMCw0MCw1MCkpLCAKCQkJCW50cmVlID0gMTAwMCwgCgkJCQlzYW1wc2l6ZSA9IHNhbXBzaXplLCAKCQkJCW5vZGVzaXplID0gbm9kZXNpemUsCgkJCQlyZXBsYWNlID0gVFJVRSwgCgkJCQlsaW5vdXQgPSBGQUxTRSkgCgpjbSA8LSBjb25mdXNpb25NYXRyaXgoCglyZiRwcmVkJHByZWQsIHJmJHByZWQkb2JzKQoKcm9jIDwtIHJvYyhyZXNwb25zZSA9IHJmJHByZWQkb2JzLAoJCQkJCSBwcmVkaWN0b3IgPSByZiRwcmVkJFllcykKCgpBY2NfaSA8LSBjbSRvdmVyYWxsWzFdCkFjY3VyYWN5IDwtIGFwcGVuZChBY2N1cmFjeSwgQWNjX2kpCgpLX2kgPC0gY20kb3ZlcmFsbFsyXQpLYXBwYSA8LSBhcHBlbmQoS2FwcGEsIEtfaSkKCkF1Y19pIDwtIHJvYyRhdWMKQXVjIDwtIGFwcGVuZChBdWMsIEF1Y19pKQoKbm9kZXNpemVfIDwtIGFwcGVuZChub2Rlc2l6ZV8sIG5vZGVzaXplKQpzYW1wc2l6ZV8gPC0gYXBwZW5kKHNhbXBzaXplXywgc2FtcHNpemUpCgpkcHV0KCItLS0tLS0tLS0tLS0tLS0iKQpkcHV0KHBhc3RlMCgiV2l0aCBub2Rlc2l6ZT0gIiwgbm9kZXNpemUpKQpkcHV0KHBhc3RlMCgiV2l0aCBzYW1wc2l6ZT0gIiwgc2FtcHNpemUpKQpkcHV0KHBhc3RlMCgiQWNjdXJhY3k6ICIsCgkJIHJvdW5kKGNtJG92ZXJhbGxbMV0sIDMpKSkKcmYkcmVzdWx0c1sxOjNdCnByaW50KHJvYyRhdWMpCgl9Cn0KdG9jKCkgIzMyIG1pbi4gZWxhcHNlZApzdG9wQ2x1c3RlcihjcHUpCgojIEFnZ3JlZ2F0ZSBNZXRyaWNzIC0tLS0KcmZfcmVzdWx0cyA9IGNiaW5kKAoJZGF0YS5mcmFtZShBY2N1cmFjeSwgS2FwcGEsIEF1Yywgbm9kZXNpemUgPSBub2Rlc2l6ZV8sIHNhbXBzaXplPXNhbXBzaXplXykpCgojc2F2ZShyZl9yZXN1bHRzLCBmaWxlPSJyZl9yZXN1bHRzLlJEYXRhIikKc3RvcENsdXN0ZXIoY3B1KQpgYGAKCmBgYHtyfQpjYmluZChtdHJ5ID0gbGluZWJyZWFrKGMoNDUpKSwKCQkJbm9kZXNpemUgPSBjKDIwKSwKCQkJc2FtcHNpemUgPSBjKDE1NzApKSAlPiUKICAgIGthYmxlKGZvcm1hdCA9ICJodG1sIiwgYWxpZ24gPSBjKHJlcCgibCIsIDQpKSwgYm9vdHN0cmFwX29wdGlvbnMgPSAiYmFzaWMiLCAKIGNhcHRpb24gPSAiQmVzdCBUdW5lIFJhbmRvbSBGb3Jlc3QiKSAlPiUKICAgIHJvd19zcGVjKDAsIG1vbm9zcGFjZSA9IFQsCiAgICAJCQkJIGZvbnRfc2l6ZSA9IDE0KSAKYGBgCgpIZSBxdWVyaWRvIHRhbWJpw6luIHZpc3VhbGl6YXIgbGFzIGludGVyYWNjaW9uZXMgZW50cmUgZWxsb3M6IAoKYGBge3IgcmZfdGFibGUsIGVjaG89IFR9CmxpYnJhcnkoaHRtbHRvb2xzKQoKIyBSZW5kZXIgYSBiYXIgY2hhcnQgd2l0aCBhIGxhYmVsIG9uIHRoZSBsZWZ0CmJhcl9jaGFydCA8LSBmdW5jdGlvbigKCWxhYmVsLCB3aWR0aCA9ICIxMDAlIiwgaGVpZ2h0ID0gIjE2cHgiLCAKCWZpbGwgPSAiIzAwYmZjNCIsIGJhY2tncm91bmQgPSBOVUxMKXsKCSAgIGJhciA8LSBkaXYoc3R5bGUgPSBsaXN0KAoJICAgCSAgICAgICAgICAgICAgYmFja2dyb3VuZCA9IGZpbGwsIAoJICAgCSAgICAgICAgICAgICAgd2lkdGggPSB3aWR0aCwgCgkgICAJICAgICAgICAgICAgICBoZWlnaHQgPSBoZWlnaHQpKQoJICAgY2hhcnQgPC0gZGl2KHN0eWxlID0gbGlzdCgKCSAgIAkgICAgICAgICAgICAgIGZsZXhHcm93ID0gMSwKCSAgIAkJCQkJCQkJbWFyZ2luTGVmdCA9ICI4cHgiLAoJICAgCQkJCQkJCQliYWNrZ3JvdW5kID0gYmFja2dyb3VuZCksIAoJICAgCQkJCQkJIGJhcikKCSAgIGRpdihzdHlsZSA9IGxpc3QoCgkgICAJICAgICBkaXNwbGF5ID0gImZsZXgiLCAKCSAgIAkgICAgIGFsaWduSXRlbXMgPSAiY2VudGVyIiksIAoJICAgCQlsYWJlbCwgCgkgICAJCWNoYXJ0KQp9CgpyZWFjdGFibGUoCnJmX3Jlc3VsdHMgJT4lIAoJbXV0YXRlKG5vZGVzaXplID0gYXNfZmFjdG9yKG5vZGVzaXplKSwKCQkJCSBzYW1wc2l6ZSA9IGFzX2ZhY3RvcihzYW1wc2l6ZSkpICU+JSAKCWRwbHlyOjpncm91cF9ieShub2Rlc2l6ZSwgc2FtcHNpemUpICU+JSAKCXNlbGVjdCgtS2FwcGEpICU+JSBzdW1tYXJpemUoCgkJQWNjdXJhY3kgPSBtZWFuKEFjY3VyYWN5KSwKCQlBdWMgPSBtZWFuKEF1YykpLApkZWZhdWx0U29ydGVkID0gbGlzdChzYW1wc2l6ZSA9ICJhc2MiLCAKCQkJCQkJCQkJCSBub2Rlc2l6ZSA9ICJkZXNjIiksCnBhZ2luYXRpb24gPSBGQUxTRSwgaGlnaGxpZ2h0ID0gVFJVRSwgaGVpZ2h0ID0gNDUwLApzaG93U29ydEljb24gPSBUUlVFLCBib3JkZXJlZCA9IFRSVUUsCmNvbHVtbnMgPSBsaXN0KAoJCiBBY2N1cmFjeSA9IGNvbERlZigKIwlmb3JtYXQgPSBjb2xGb3JtYXQoZGlnaXRzID0gMiksCiAJIG5hbWUgPSAiQWNjdXJhY3kiLCAKICAgYWxpZ24gPSAibGVmdCIsIAogICBjZWxsID0gZnVuY3Rpb24odmFsdWUpewogICAgIHdpZHRoIDwtIHBhc3RlMCh2YWx1ZS8xICogMTAwLCAiJSIpCiAgICAgYmFyX2NoYXJ0KHZhbHVlLCAKICAgICAJCQkJCXdpZHRoID0gd2lkdGgsCiAgICAgCQkJCQlmaWxsID0gIiMyRTQ1QjgiLCAKICAgIAkJCQkJYmFja2dyb3VuZCA9ICIjZTFlMWUxIil9KSwKIAogQXVjID0gY29sRGVmKAogCSBuYW1lID0gIkFVQyIsIAogCSBhbGlnbiA9ICJsZWZ0IiwgCiAJIGNlbGwgPSBmdW5jdGlvbih2YWx1ZSl7CiAJIAl3aWR0aCA8LSBwYXN0ZTAodmFsdWUvMSAqIDEwMCwgIiUiKQogICAgYmFyX2NoYXJ0KHZhbHVlLCAKICAgIAkJCQkJd2lkdGggPSB3aWR0aCwgCiAgICAJCQkJCWZpbGwgPSAiIzJFNDVCOCIsIAogICAgCQkJCQliYWNrZ3JvdW5kID0gIiNlMWUxZTEiKX0pCiAgICApCiAgKQpgYGAKCkNvbW8gcG9kZW1vcyBleHBsb3JhciBhIHBhcnRpciBkZSBsYSB0YWJsYSwgdW5vcyBidWVub3MgcGFyw6FtZXRyb3Mgc2Vyw61hbiBgbm9kZXNpemU9MjBgIHkgYHNhbXBzaXplPTE1NzBgLiBBaG9yYSBtaXJhbW9zIGxlIGFuZGFtaWVudG8gZGVsIGBtdHJ5YDoKCmBgYHtyIHJmX210cnlfdHVuZSwgZmlnLmNhcD0iVHVuZW8gZGVsIG10cnkifQojIEEgcGFydGlyIGRlbCBvdXRwdXQgZGVsIHR1bmVvLCBoZSBwb2RpZG8gY3JlYXIgZXN0ZSBkYXRhZnJhbWUgCnRyaWJibGUoCiAgIH5tdHJ5LCB+QWNjdXJhY3ksIH4gS2FwcGEsCiAgICAjLS0tLXwtLS0tLS0tLS0tfC0tLS0tLS0tCiAgICA1LCAgICAgMC45MTIsICAgICAwLjgyNSwKICAgIDEwLCAgICAwLjkxOCwgICAgIDAuODM2LAogICAgMTUsICAgIDAuOTI2LCAgICAgMC44NTEsCiAgICAyMCwgICAgMC45MjAsICAgICAwLjg0MSwKICAgIDI1LCAgICAwLjkzMCwgICAgIDAuODYwLAogICAgMzAsICAgIDAuOTIzLCAgICAgMC44NDUsCiAgICAzNSwgICAgMC45MjksICAgICAwLjg1NywKICAgIDQwLCAgICAwLjkxOSwgICAgIDAuODM4LAogICAgNDUsICAgIDAuOTMxLCAgICAgMC44NjEsCiAgICA1MCwgICAgMC45MjAsICAgICAwLjg0MCwKKSAtPiByZl9yZXN1bHRfbXRyeQoKIyBQbG90IC0tLS0KbGlicmFyeShhcGV4Y2hhcnRlcikKYXBleGNoYXJ0KAoJd2lkdGggPSA4MzAsCgkJYXhfb3B0cyA9IGxpc3QoCgkJCWNoYXJ0ID0gbGlzdCh0eXBlID0gImxpbmUiKSwKCQkJc3Ryb2tlID0gbGlzdChjdXJ2ZSA9ICJzbW9vdGgiKSwKCQkJZ3JpZCA9IGxpc3QoCgkJCQlib3JkZXJDb2xvciA9ICIjZTdlN2U3IiwKCQkJCXJvdyA9IGxpc3QoCgkJCQkJY29sb3JzID0gYygiI2YzZjNmMyIsICJ0cmFuc3BhcmVudCIpLAoJCQkJCW9wYWNpdHkgPSAwLjUKCQkJCSkKCQkJKSwKCQkJbWFya2VycyA9IGxpc3Qoc3R5bGUgPSAiaW52ZXJ0ZWQiLCBzaXplID0gNCksCgkJCXNlcmllcyA9IGxpc3QoCgkJCQlsaXN0KG5hbWUgPSAiQWNjdXJhY3kiLAoJCQkJCQkgZGF0YSA9IHJmX3Jlc3VsdF9tdHJ5JEFjY3VyYWN5KSwKCQkJCWxpc3QobmFtZSA9ICJLYXBwYSIsCgkJCQkJCSBkYXRhID0gcmZfcmVzdWx0X210cnkkS2FwcGEpCgkJCSksCgkJCXRpdGxlID0gbGlzdCh0ZXh0ID0gIlR1bmVvIFJhbmRvbSBGb3Jlc3QiLAoJCQkJCQkJCQkgYWxpZ24gPSAiY2VudGVyIiksCgkJCXhheGlzID0gbGlzdChjYXRlZ29yaWVzID0gcmZfcmVzdWx0X210cnkkbXRyeSkKCQkpCgkpICU+JQoJYXhfc3VidGl0bGUoCgkJdGV4dCA9ICJSZXN1bHRhZG9zIHJlbXVlc3RyZW8gZW50cmUgbG9zIHBhcmFtZXRyb3MgdHVuaW5nIiwgCgkJYWxpZ24gPSAiY2VudGVyIikgJT4lIAoJYXhfeWF4aXMobWluID0gMC44MTUsIG1heCA9IDAuOTM1KSAlPiUKCWF4X3N0cm9rZShjdXJ2ZSA9ICJzdGVwbGluZSIsIHdpZHRoID0gMikgJT4lCglheF9jb2xvcnMoIiM0QjY5Q0UiLCAiI0IwQkRFOSIpCmBgYAoKT2JzZXJ2YW1vcyBxdWUgdW4gbnVtZXJvIGRlIHZhcmlhYmxlcyBkZSA0NSBvcHRpbWl6YSBsb3MgdmFsb3Jlcy4gCgojIyMgTW9kZWxvIGZpbmFsCgpgYGB7ciByZl9maW5hbF9tb2RlbCwgZXZhbD1GfQpsaWJyYXJ5KHJhbmRvbUZvcmVzdCkKcmYgPC0gcmFuZG9tRm9yZXN0KGRhdGE9dXBUcmFpbiwKIAkJCQlmYWN0b3IoYXR0cml0aW9uKSB+IC4sCiAJCQkJbXRyeSA9IDQ1LAogCQkJCW50cmVlID0gMjAwMCwKIAkJCQlzYW1wc2l6ZSA9IDE1NzAgLAogCQkJCW5vZGVzaXplID0gMjAgLAogCQkJCXJlcGxhY2UgPSBUUlVFLAogCQkJCWltcG9ydGFuY2UgPSBUUlVFKQpgYGAKCiMjIyMgT3V0LU9mLUJhZyBFcnJvcgoKUXVlcmVtb3Mgc3VmaWNpZW50ZXMgw6FyYm9sZXMgcGFyYSBlc3RhYmlsaXphciBlbCBlcnJvciBwZXJvIHVzYW5kbwpkZW1hc2lhZG9zIMOhcmJvbGVzIGVzIGlubmVjZXNhcmlhbWVudGUgaW5lZmljaWVudGUuIAoKYGBge3IgcmZfb29iLCBmaWcuY2FwPSJSYW5kb20gRm9yZXN0IE91dC1PZi1CYWcgRXJyb3IifQphcy50aWJibGUoY2JpbmQoCgknT09CJyA9IHJmJGVyci5yYXRlWywxXSwKCSdudHJlZScgPSBzZXEoMTpucm93KHJmJGVyci5yYXRlKSkpKSAlPiUKCWdncGxvdChhZXMoeCA9IG50cmVlLHkgPSBPT0IpKSArIAoJCQkJIAlnZW9tX3N0ZXAoYWVzKGNvbG91ciA9IE9PQikpICsgICAgIHNjYWxlX2NvbG9yX2NvbnRpbnVvdXMobG93ID0gIiMyRTQ1QjgiLCAKICAgIAkJCQkJCQkJCQkJaGlnaCA9ICIjRDZEQkY1IikgKwoJaHJicnRoZW1lczo6dGhlbWVfaXBzdW0oCgkJYmFzZV9mYW1pbHkgPSAnVGltZXMnLAliYXNlX3NpemUgPSAxMCkgKwogIGxhYnModGl0bGUgPSAnT3V0LU9mLUJhZyBFcnJvcicsCiAgCQkgc3VidGl0bGUgPSAnUmFuZG9tIEZvcmVzdCBtb2RlbCcpICsgCgl0aGVtZShsZWdlbmQucG9zaXRpb24gPSAnbm9uZScsCgkJCQlwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9yZWN0KGNvbG9yPScjM2I0NTRhJyksCiAgCQkgCXRpdGxlID0gZWxlbWVudF90ZXh0KGZhY2U9J2JvbGQnKSwKICAJCSAJYXhpcy50aXRsZS55ID0gZWxlbWVudF90ZXh0KHNpemU9MTApLAogIAkJIAlheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dCgKICAJCSAJCWZhY2UgPSAnYm9sZCcsIGNvbG91ciA9ICcjM0QzRDNEJywgc2l6ZSA9IDEwKSkKYGBgCgpEZSBoZWNobywgZWwgZXJyb3Igc2UgZXN0YWJsZWNlIGVudHJlIGRlIDEwMDAgeSAxNTAwIMOhcmJvbGVzLgoKIyMjIyBNYXRyaXogZGUgQ29uZnVzacOzbiB5IEltcG9ydGFuY2lhIGRlIFZhcmlhYmxlcwoKYGBge3IgcmZfZmluYWwsIGZpZy5zaG93PSJob2xkIiwgb3V0LndpZHRoPSI1MCUiLCBmaWcuY2FwPSJSYW5kb20gRm9yZXN0IEZpbmFsIE1vZGVsIiwgZmlnLnN1YmNhcD1jKCJJbXBvcnRhbmNpYSBkZSBWYXJpYWJsZXMiLCJNYXRyaXogZGUgQ29uZnVzacOzbiIpfQoKZHJhd19jb25mdXNpb25fbWF0cml4KAoJY29uZnVzaW9uTWF0cml4KHJmJHByZWRpY3RlZCwgcmYkeSksIAoJIiNENkRCRjUiLCAiIzJFNDVCOCIpCgpnZ3Bsb3QoCgl2aXA6OnZpKHJmKSAlPiUKCSBmaWx0ZXIoSW1wb3J0YW5jZSA+IDMwKSAlPiUKCSBtdXRhdGUoCgkgIFZhcmlhYmxlID0gc3RyaW5ncjo6c3RyX3RvX3RpdGxlKFZhcmlhYmxlKSkgJT4lCgkgbXV0YXRlKAoJCVZhcmlhYmxlID0gc3RyX3JlcGxhY2VfYWxsKFZhcmlhYmxlLCAiXyIsICIgIikpLAoJYWVzKHggPSByZW9yZGVyKFZhcmlhYmxlLCBJbXBvcnRhbmNlKSwgCgkJCXkgPSBJbXBvcnRhbmNlKSkgKwoJCWdnY2hpY2tsZXQ6Omdlb21fY2hpY2tsZXQoCgkJCWFlcyhmaWxsID0gSW1wb3J0YW5jZSksCgkJCXJhZGl1cyA9IGdyaWQ6OnVuaXQoNiwgInB0IiksCgkJCXdpZHRoID0gMC44LAoJCQlzaG93LmxlZ2VuZCA9IEYpICsKCQlsYWJzKAoJCSB4ID0gTlVMTCwKCQkgdGl0bGUgPSAiVmFyaWFibGUgSW1wb3J0YW5jZSBQbG90IFJhbmRvbSBGb3Jlc3QiKSArCgkJc2NhbGVfeV9jb250aW51b3VzKHBvc2l0aW9uID0gInJpZ2h0IikgKwoJCXNjYWxlX2ZpbGxfZ3JhZGllbnQobG93ID0gJyNENkRCRjUnLCAKCQkJCQkJCQkJCQkJaGlnaCA9ICcjMkU0NUI4JykgKwoJCWNvb3JkX2ZsaXAoKSArCgkJaHJicnRoZW1lczo6dGhlbWVfaXBzdW0oYmFzZV9mYW1pbHkgPSAnVGltZXMnLCAKCQkJCQkJCQkJCQkJCQlncmlkID0gIlgiKSArCgkJdGhlbWUoCgkJCXBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoCgkJCQkJCQkJCQkJCQkJCQl2anVzdCA9IC0xKSwKCQkJcGxvdC5tYXJnaW4gPSB1bml0KGMoMCwgMSwgMSwgMC41KSwgImNtIikpCmBgYAoKU2ltaWxhciBjb24gZWwgbW9kZWxvIGRlIGJhZ2dpbmcsIGxhIHZhcmlhYmxlIG3DoXMgaW5mbHV5ZW50ZSBlcyBlbCBzYWxhcmlvIGJydXRvIGRlIGxvcyBlbXBsZWFkb3MuIFBvciBsYSBtaXNtYSByYXrDs24sICpEYWlseVJhdGUqIChsYSB0YXJpZmEgZGlhcmlhIGRlIHRyYWJham8pIHRvbWEgbXVjaGEgaW1wb3J0YW5jaWEuIEVuY29udHJhbW9zIHRhbWJpw6luIGxvcyBuaXZlbGVzIGRlIHNhdGlzZmFjY2nDs24gZGVsIHRyYWJham8geSBkZWwgZW50b3Juby4KCiMjIEdyYWRpZW50IEJvb3N0aW5nIE1hY2hpbmUKCkxvcyBtw6l0b2RvcyBxdWUgdm95IGEgZW1wbGVhciBlc3TDoW4gYmFzYWRvcyBzb2JyZSB1bmEgZXN0cmF0ZWfDrWEgZGlmZXJlbnRlIGRlIGNvbnN0cnVjY2nDs24uIEEgZGlmZXJlbmNpYSBkZWwgKnJhbmRvbSBmb3Jlc3QqLCBxdWUgY29uc3RydXllIGxvcyBtb2RlbG9zIHkgbHVlZ28gbG9zIHByb21lZGlhLCBlbCAqKmdyYWRpZW50IGJvb3N0aW5nIG1hY2hpbmUqKiBjb25zdHJ1eWUgbG9zIG1vZGVsb3Mgc2VjdWVuY2lhbG1lbnRlLiBFbiBjYWRhIGl0ZXJhY2nDs24sIHNlIGVudHJlbmEgdW4gbnVldm8gbW9kZWxvIGRlIGFwcmVuZGl6YWplIGNvbiByZXNwZWN0byBhbCBlcnJvciBkZSB0b2RvIGVsIGNvbmp1bnRvIGFwcmVuZGlkbyBoYXN0YSBhaG9yYS4KCiFbR3JhZGllbnQgQm9vc3RpbmcgTWFjaGluZSBjb25zdHJ1Y3Rpb24gbWV0aG9kXShmaWxlcy9pbWcvZ2JtLnBuZykKCiMjIyBUdW5lbyBkZWwgTW9kZWxvClVuYSBwZWN1bGlhcmlkYWQgZGUgZXN0ZSBtb2RlbG8gZXMgcXVlIHBvZGVtb3MgY29udHJvbGFyIG11Y2hvcyBwYXLDoW1ldHJvcyBlbiBlbCBlbnRyZW5hbWllbnRvLiBMYSBmdW5jacOzbiBgbW9kZWxMb29rdXAoKWAsIGVzcGVjaWZpY2FuZG8gZWwgbW9kZWxvIChgImdibSJgIGVuIGVzdGUgY2Fzbykgbm9zIGRldnVlbHZlIGxhIGxpc3RhLgpgYGB7ciwgcmVzdWx0cz0nYXNpcyd9CmxpYnJhcnkoa2FibGVFeHRyYSkKbW9kZWxMb29rdXAoImdibSIpWzI6M10gICU+JSBjYmluZCgKCURlc2NyaXB0aW9uID0gbGluZWJyZWFrKGMoCgkJIkVsIG7Dum1lcm8gdG90YWwgZGUgw6FyYm9sZXMgcGFyYSBlbXBsZWFyLiIsCgkJIkVsIG7Dum1lcm8gZCBkZSBkaXZpc2lvbmVzIGVuIGNhZGEgw6FyYm9sLCBlc3RlIGNvbnRyb2xhIGxhIGNvbXBsZWppZGFkIGRlbCBjb25qdW50byBpbXB1bHNhZG8uIiwKCQkiQ29udHJvbGEgbGEgcmFwaWRleiBjb24gbGEgcXVlIGVsIGFsZ29yaXRtbyBkZXNjaWVuZGUgaGFjw61hIGVsIGdyYWRpZW50IGRlc2NlbnQuIiwKCQkiQ29udHJvbGEgc2kgdXRpbGl6YSBvIG5vIHVuYSBmcmFjY2nDs24gZGUgbGFzIG9ic2VydmFjaW9uZXMgZGUgZW50cmVuYW1pZW50byBkaXNwb25pYmxlcy4iKSkpICU+JSAKCWthYmxlKCAKCQkJZm9ybWF0ID0gImh0bWwiLAoJCQlhbGlnbiA9IGMocmVwKCdsJywgNCkpLAoJCQljYXB0aW9uID0gIkRlc2NyaXBjaW9uIGRlIGxvcyBwYXLDoW1ldHJvcyBlbiBHQk0iKSAlPiUgCglyb3dfc3BlYygwLCBmb250X3NpemUgPSAxNCkgJT4lIAoJY29sdW1uX3NwZWMoMSwgbW9ub3NwYWNlID0gVCkKYGBgCgpgYGB7ciwgZXZhbD1GQUxTRX0KbGlicmFyeSh0aWN0b2MpCnJlZ2lzdGVyRG9QYXJhbGxlbChtYWtlQ2x1c3RlcigzKSAtPiBjcHUpIAp0aWMoKQpnYm1fcmVzdWx0cyA8LSB0cmFpbigKCWZhY3RvcihhdHRyaXRpb24pfi4sCiAgIGRhdGEgPSB1cFRyYWluLAoJIG1ldGhvZCA9ICJnYm0iLAoJIHRyQ29udHJvbCA9IGNvbnRyb2wsCgkgdHVuZUdyaWQgPSBleHBhbmQuZ3JpZCgKCQkgc2hyaW5rYWdlID0gYygwLjAxLDAuMDUsMC4xLDAuNSwxKSwKCQkgbi5taW5vYnNpbm5vZGUgPSBjKDUsMjAsMzUpLAoJCSBuLnRyZWVzID0gYygxMDAwLCA1MDAwLCAxMDAwMCksCgkJIGludGVyYWN0aW9uLmRlcHRoID0gYygyKSksCgkgZGlzdHJpYnV0aW9uID0gImJlcm5vdWxsaSIsCgkgYmFnLmZyYWN0aW9uID0gMSwKCSB2ZXJib3NlID0gRkFMU0UpCnRvYygpICMxNyBtaW4gZWxhcHNlZApzdG9wQ2x1c3RlcihjcHUpCmBgYAoKUG9kZW1vcyB2ZXIgZWwgY29tcG9ydGFtaWVudG8gZW4gYWp1c3RlIGRlbCB0dW5lby4gCkxhIG3DoXhpbWEgYWNjdXJhY3kgc2UgYWxjYW56YSBjb24gZXN0b3MgdmFsb3JlczoKCmBgYHtyfQpwYXN0ZTAoCmNhdCgnQmVzdCBIeXBlcnBhcmFtZXRlcnMgVHVuaW5nOiBcbicpLAp4ZnVuOjp0cmVlKGFzLmxpc3QoZ2JtX3Jlc3VsdHMkYmVzdFR1bmUpKSkKCnBsb3QoZ2JtX3Jlc3VsdHMsIG91dHB1dD0iZ2dwbG90IiwgJ2xpbmUnLAoJCSBsYXlvdXQgPSBjKDMsMSksCgkJIHBhci5zZXR0aW5ncyA9IGxpc3QoCgkJICBzdXBlcnBvc2UubGluZSA9IGxpc3QobHdkID0gMSwKCQkgIAljb2wgPSBjKCIjMkE3ODhFIiwgIiMyRTQ1QjgiLCIjNDQwMTU0IikpLAoJCSAJc3VwZXJwb3NlLnN5bWJvbCA9IGxpc3QocGNoID0gMTksIGNleCA9IDAuNiwKCQkgIAljb2wgPSBjKCIjMkE3ODhFIiwgIiMyRTQ1QjgiLCIjNDQwMTU0IikpLAogICAgICBzdHJpcC5iYWNrZ3JvdW5kPSBsaXN0KGNvbCA9ICIjRDZEQkY1IikpKQpgYGAKCkVsIG51bWVybyBkZSDDoXJib2xlcyBjb25zdHJ1aWRvIGVzIGFsdG8sIHBlcm8gZXNvIG5vcyBsbGV2YSBhIHVuYSBwcmVjaXNpw7NuIG3DoXMgYWx0YS4gRWwgbnVtZXJvIGRlIG9ic2VydmFjaW9uZXMgZW4gZWwgbm9kbyBmaW5hbCBwdWVkZSBzZXIgYmFzdGFudGUgcGVxdWXDsW8gKGBtaW5vYnNpbm5vZGUgPSAyMGApLCBubyBvYnN0YW50ZSBoZSBmaWphZG8gcGFyYSBlbCBtb2RlbG8gZmluYWwgZWwgdmFsb3IgZGUgMzUsIGNvbnN0cnV5ZW5kbyB1biBtb2RlbG8gbm8gZGVtYXNpYWRvIGNvbXBsZWpvLCBjb24gYmFqbyBzZXNnbyB5IG5vIHRhbiBhbHRhIHZhcmlhbnphLCBwZXJvIG3DoXMgZXN0YWJsZS4KCiMjIyBNb2RlbG8gZmluYWwKCmBgYHtyIGdibV9maW5hbCBtb2RlbCwgZXZhbD1GQUxTRX0KZ2JtIDwtIHRyYWluKAoJZmFjdG9yKGF0dHJpdGlvbil+LiwKICAgZGF0YSA9IHVwVHJhaW4sCgkgbWV0aG9kID0gImdibSIsCgkgdHJDb250cm9sID0gY29udHJvbCwKCSB0dW5lR3JpZCA9IGV4cGFuZC5ncmlkKAoJCSBzaHJpbmthZ2UgPSBjKDEpLAoJCSBuLm1pbm9ic2lubm9kZSA9IGMoMzUpLAoJCSBuLnRyZWVzID0gYygxMDAwMCksCgkJIGludGVyYWN0aW9uLmRlcHRoID0gYygyKSksCgkgZGlzdHJpYnV0aW9uID0gImJlcm5vdWxsaSIsCgkgYmFnLmZyYWN0aW9uID0gMSwKCSB2ZXJib3NlID0gRkFMU0UpCmBgYAoKIyMjIyBNYXRyaXogZGUgQ29uZnVzacOzbiB5IEltcG9ydGFuY2lhIGRlIFZhcmlhYmxlcwpgYGB7ciBnYm1fZmluYWxfbW9kZWwsIGZpZy5zaG93PSJob2xkIiwgb3V0LndpZHRoPSI1MCUiLCBmaWcuY2FwPSJHQk0gRmluYWwgTW9kZWwiLCBmaWcuc3ViY2FwPWMoIkltcG9ydGFuY2lhIGRlIFZhcmlhYmxlcyIsIk1hdHJpeiBkZSBDb25mdXNpw7NuIil9CgpkcmF3X2NvbmZ1c2lvbl9tYXRyaXgoCgljb25mdXNpb25NYXRyaXgoZ2JtJHByZWQkcHJlZCwKCQkJCQkJCQkJZ2JtJHByZWQkb2JzKSwKCScjRDZEQkY1JywgJyMyRTQ1QjgnKQoKYXNfdGliYmxlKHZpcDo6dmkoZ2JtKSkgJT4lCgkgZmlsdGVyKEltcG9ydGFuY2UgPiAxMCkgJT4lCgkgbXV0YXRlKAoJICBWYXJpYWJsZSA9IHN0cmluZ3I6OnN0cl90b190aXRsZShWYXJpYWJsZSkpICU+JQoJIG11dGF0ZSgKCQlWYXJpYWJsZSA9IHN0cl9yZXBsYWNlX2FsbChWYXJpYWJsZSwgIl8iLCAiICIpKSAlPiUgCmdncGxvdChhZXMoCgl4ID0gcmVvcmRlcihWYXJpYWJsZSwgSW1wb3J0YW5jZSksIAoJeSA9IEltcG9ydGFuY2UpKSArCgkJZ2djaGlja2xldDo6Z2VvbV9jaGlja2xldCgKCQkJYWVzKGZpbGwgPSBJbXBvcnRhbmNlKSwKCQkJcmFkaXVzID0gZ3JpZDo6dW5pdCg2LCAicHQiKSwKCQkJd2lkdGggPSAwLjgsCgkJCXNob3cubGVnZW5kID0gRikgKwoJCWxhYnMoCgkJIHggPSBOVUxMLAoJCSB0aXRsZSA9ICJWYXJpYWJsZSBJbXBvcnRhbmNlIFBsb3QgR3JhZGllbnQgQm9vc3RpbmciKSArCgkJc2NhbGVfeV9jb250aW51b3VzKHBvc2l0aW9uID0gInJpZ2h0IikgKwoJCXNjYWxlX2ZpbGxfZ3JhZGllbnQobG93ID0gJyNENkRCRjUnLCAKCQkJCQkJCQkJCQkJaGlnaCA9ICcjMkU0NUI4JykgKwoJCWNvb3JkX2ZsaXAoKSArCgkJaHJicnRoZW1lczo6dGhlbWVfaXBzdW0oYmFzZV9mYW1pbHkgPSAnVGltZXMnLAoJCQkJCQkJCQkJCQkJCWdyaWQgPSAiWCIpICsKCQl0aGVtZSgKCQkJcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dCh2anVzdCA9IC0xKSwKCQkJcGxvdC5tYXJnaW4gPSB1bml0KGMoMCwgMSwgMSwgMC41KSwgImNtIikpCmBgYAoKQSBwYXJ0aXIgZGVsIGdyw6FmaWNvIGRlIGltcG9ydGFuY2lhIGRlIHZhcmlhYmxlcywgZGVzdGFjYSBsYSB2YXJpYWJsZSAqT3ZlclRpbWVfeWVzKiB5ICpNb250aGx5SWNvbWUqLCBxdWUganVudGFzIGEgKlN0b2NrT3B0aW9uTGV2ZWwqIHkgKkRhaWx5UmF0ZSogbm9zIGNvbmZpcm1hcyBxdWUgbGFzIHJldHJpYnVjaW9uZXMganVlZ2FuIHVuIHJvbCBpbXBvcnRhbnRlIGEgbGEgaG9yYSBkZSBkZWNpZGlyIHNpIGRlamFyIGxhIGVtcHJlc2EgbyBuby4gCgojIyBYR0Jvb3N0CiMjIyBUdW5lbyBkZWwgTW9kZWxvCgpgYGB7cn0KbW9kZWxMb29rdXAoInhnYlRyZWUiKVsyOjNdICU+JSBjYmluZCgKCURlc2NyaXB0aW9uID0gbGluZWJyZWFrKGMoCgkJIkVsIG7Dum1lcm8gdG90YWwgZGUgw6FyYm9sZXMgcGFyYSBlbXBsZWFyIiwKCQkiU2UgdXRpbGl6YSBwYXJhIGNvbnRyb2xhciBlbCBzb2JyZWFqdXN0ZSB5YSBxdWUgdW4gdmFsb3IgbWF5b3IgcGVybWl0aXLDoSBxdWUgZWwgbW9kZWxvIGFwcmVuZGEgcmVsYWNpb25lcyBtdXkgZXNwZWPDrWZpY2FzIHBhcmEgdW5hIG11ZXN0cmEgcGFydGljdWxhci4iLAoJCSJIYWNlIGVsIG1vZGVsbyBtw6FzIHJvYnVzdG8gcmVkdWNpZW5kbyBsb3MgcGVzb3MgZW4gY2FkYSBwYXNvLiIsCgkJIkNvbnRyb2xhIHNpIHV0aWxpemEgbyBubyB1bmEgZnJhY2Npw7NuIGRlIGxhcyBvYnNlcnZhY2lvbmVzIGRlIGVudHJlbmFtaWVudG8gZGlzcG9uaWJsZXMuIiwKCQkgIkdhbW1hIGVzcGVjaWZpY2EgbGEgcmVkdWNjacOzbiBwb3NpdGl2YSBtw61uaW1hIGVuIGxhIGxvc3MgZnVuY2nDs24gcmVxdWVyaWRhIHBhcmEgaGFjZXIgdW5hIGRpdmlzacOzbiBkZWwgbm9kby4iLAoJCSJEZW5vdGEgbGFzIGNvbHVtbmFzIGEgcmVtdWVzdHJhciBwYXJhIGNhZGEgw6FyYm9sLiIsCgkJIkRlZmluZSBsYSBzdW1hIG3DrW5pbWEgZGUgcGVzb3MgZGUgdG9kYXMgbGFzIG9ic2VydmFjaW9uZXMgcmVxdWVyaWRhcyBlbiB1biBjaGlsZCBub2RlLiBQb3IgdmFsb3JlcyBzdXBlcmlvcmVzIGltcGlkZW4gcXVlIHVuIG1vZGVsbyBhcHJlbmRhIHJlbGFjaW9uZXMgcXVlIHBvZHLDrWFuIHNlciBtdXkgZXNwZWPDrWZpY2FzLiIpKSkgJT4lIAoJa2FibGUoIAoJCQlmb3JtYXQgPSAiaHRtbCIsCgkJCWFsaWduID0gYyhyZXAoJ2wnLCA0KSksCgkJCWJvb3RzdHJhcF9vcHRpb25zID0gImJhc2ljIiwKCQkJY2FwdGlvbiA9ICJEZXNjcmlwY2lvbiBkZSBsb3MgcGFyw6FtZXRyb3MgZW4gWEdCb29zdCIpICU+JQoJY29sdW1uX3NwZWMoMiwgd2lkdGggPSAiMy4yY20iKSAlPiUgCglyb3dfc3BlYygwLCBmb250X3NpemUgPSAxNCkgJT4lIAoJY29sdW1uX3NwZWMoMSwgbW9ub3NwYWNlID0gVCwgd2lkdGggPSAiNC4yY20iKQpgYGAKCmBgYHtyIGV2YWw9RkFMU0V9CnJlZ2lzdGVyRG9QYXJhbGxlbChtYWtlQ2x1c3RlcigzKSAtPiBjcHUpIAp4Z2JtX3Jlc3VsdHMgPC0gdHJhaW4oCglmYWN0b3IoYXR0cml0aW9uKX4uLAoJZGF0YSA9IHVwVHJhaW4sIHZlcmJvc2U9RkFMU0UsCgltZXRob2QgPSAieGdiVHJlZSIsIAoJdHJDb250cm9sID0gY29udHJvbCwKCXR1bmVHcmlkID0gZXhwYW5kLmdyaWQoCgkgIG1pbl9jaGlsZF93ZWlnaHQgPSBjKDUsMzAsNTApLAoJCWV0YSA9IGMoMC4xLDAuMDUsMC4wMSksCgkJbnJvdW5kcyA9IGMoNTAwLDEwMDAsNTAwMCwxMDAwMCksCgkJbWF4X2RlcHRoID0gNiwKCQlnYW1tYSA9IDAsCgkJY29sc2FtcGxlX2J5dHJlZSA9IDEsCgkJc3Vic2FtcGxlID0gMSkKCSkKc3RvcENsdXN0ZXIoY3B1KQpgYGAKClBhcmEgYGNhcmV0YCwgZXN0b3Mgc29uIGxvcyBwYXJhbWV0cm9zIG1lam9yZXM6CmBgYHtyfQpwYXN0ZTAoY2F0KCdCZXN0IEh5cGVycGFyYW1ldGVycyBUdW5pbmc6IFxuJyksCgl4ZnVuOjp0cmVlKGFzLmxpc3QoeGdibV9yZXN1bHRzJGJlc3RUdW5lKSkpCmBgYAoKUG9yIHN1cHVlc3RvLCBlc3RhIG1hcXVpbmEgbm9zIGRldnVlbHZlIGNvbW8gbWVqb3JlcyBsb3MgcGFyw6FtZXRyb3MgcXVlIG1heGltaXphbiBsYSBhY2N1cmFjeSwgbm8gb2JzdGFudGUgaGF5IHF1ZSB0ZW5lciBlbiBjdWVudGEgbGEgZXN0YWJpbGlkYWQgZGVsIG1vZGVsbyBhIGxhIGhvcmEgZGUgYWp1c3Rhci4gUG9yIGVzdGEgcmF6w7NuIHNpZW1wcmUgaGFjZSBmYWx0YSB2ZXIgZWwgYW5kYW1pZW50byBkZWwgZW50cmVuYW1pZW50bzoKCmBgYHtyfQpwbG90KHhnYm1fcmVzdWx0cywgb3V0cHV0PSJnZ3Bsb3QiLCAnbGluZScsCgkJIGxheW91dCA9IGMoMywxKSwKCQkgcGFyLnNldHRpbmdzID0gbGlzdCgKCQkgIHN1cGVycG9zZS5saW5lID0gbGlzdChsd2QgPSAxLAoJCSAgCWNvbCA9IGMoIiMyQTc4OEUiLCAiIzJFNDVCOCIsIiM0NDAxNTQiKSksCgkJIAlzdXBlcnBvc2Uuc3ltYm9sID0gbGlzdChwY2ggPSAxOSwgY2V4ID0gMC42LAoJCSAgCWNvbCA9IGMoIiMyQTc4OEUiLCAiIzJFNDVCOCIsIiM0NDAxNTQiKSksCiAgICAgIHN0cmlwLmJhY2tncm91bmQ9IGxpc3QoY29sID0gIiNENkRCRjUiKSkpCmBgYAoKQSBwYXJ0aXIgZGUgZXN0ZSBncsOhZmljbyBoZSBkZWNpZGlkbyBkZSBxdWVkYXJtZSBjb24gYG1pbl9jaGlsZF93ZWlnaHQ9MzVgLCBlbiBjdWFudG8gZW5jdWVudHJhIGVsIGNvbXByb21pc28gbWVqb3IgcGFyYSBlc3RvcyBhanVzdGVzLgoKIyMjIE1vZGVsbyBmaW5hbAoKYGBge3IgZXZhbD1GQUxTRX0KeGdibSA8LSB0cmFpbigKCWZhY3RvcihhdHRyaXRpb24pfi4sCiAgIGRhdGEgPSB1cFRyYWluLAoJIG1ldGhvZCA9ICJ4Z2JUcmVlIiwKCSB0ckNvbnRyb2wgPSBjb250cm9sLAoJIHR1bmVHcmlkID0gZXhwYW5kLmdyaWQoCgkJIGV0YSA9IGMoMC4wNSksCgkJIG1pbl9jaGlsZF93ZWlnaHQgPSBjKDM1KSwKCQkgbnJvdW5kcyA9IGMoMTAwMDApLAoJCSBnYW1tYSA9IGMoMCksCgkJIHN1YnNhbXBsZSA9IGMoMSksCgkJIG1heF9kZXB0aCA9IGMoNiksCgkJIGNvbHNhbXBsZV9ieXRyZWUgPWMoMSkpLAoJIHZlcmJvc2UgPSBGQUxTRSkKYGBgCgoKIyMjIyBNYXRyaXogZGUgQ29uZnVzacOzbiB5IEltcG9ydGFuY2lhIGRlIFZhcmlhYmxlcwoKYGBge3IgeGdibV9maW5hbF9tb2RlbCwgZmlnLnNob3c9ImhvbGQiLCBvdXQud2lkdGg9IjUwJSIsIGZpZy5jYXA9IlhHQk0gRmluYWwgTW9kZWwiLCBmaWcuc3ViY2FwPWMoIkltcG9ydGFuY2lhIGRlIFZhcmlhYmxlcyIsIk1hdHJpeiBkZSBDb25mdXNpw7NuIil9CgpkcmF3X2NvbmZ1c2lvbl9tYXRyaXgoCgljb25mdXNpb25NYXRyaXgoeGdibSRwcmVkJHByZWQsCgkJCQkJCQkJCXhnYm0kcHJlZCRvYnMpLAoJJyNENkRCRjUnLCAnIzJFNDVCOCcpCgoKYXNfdGliYmxlKHZpcDo6dmkoeGdibSkpICU+JQoJIGZpbHRlcihJbXBvcnRhbmNlID4gMjApICU+JQoJIG11dGF0ZSgKCSAgVmFyaWFibGUgPSBzdHJpbmdyOjpzdHJfdG9fdGl0bGUoVmFyaWFibGUpKSAlPiUKCSBtdXRhdGUoCgkJVmFyaWFibGUgPSBzdHJfcmVwbGFjZV9hbGwoVmFyaWFibGUsICJfIiwgIiAiKSkgJT4lIApnZ3Bsb3QoYWVzKAoJeCA9IHJlb3JkZXIoVmFyaWFibGUsIEltcG9ydGFuY2UpLCAKCXkgPSBJbXBvcnRhbmNlKSkgKwoJCWdnY2hpY2tsZXQ6Omdlb21fY2hpY2tsZXQoCgkJCWFlcyhmaWxsID0gSW1wb3J0YW5jZSksCgkJCXJhZGl1cyA9IGdyaWQ6OnVuaXQoNiwgInB0IiksCgkJCXdpZHRoID0gMC44LAoJCQlzaG93LmxlZ2VuZCA9IEYpICsKCQlsYWJzKAoJCSB4ID0gTlVMTCwKCQkgdGl0bGUgPSAiVmFyaWFibGUgSW1wb3J0YW5jZSBQbG90IFhHQk0iKSArCgkJc2NhbGVfeV9jb250aW51b3VzKHBvc2l0aW9uID0gInJpZ2h0IikgKwoJCXNjYWxlX2ZpbGxfZ3JhZGllbnQobG93ID0gJyNENkRCRjUnLCAKCQkJCQkJCQkJCQkJaGlnaCA9ICcjMkU0NUI4JykgKwoJCWNvb3JkX2ZsaXAoKSArCgkJaHJicnRoZW1lczo6dGhlbWVfaXBzdW0oYmFzZV9mYW1pbHkgPSAnVGltZXMnLAoJCQkJCQkJCQkJCQkJCWdyaWQgPSAiWCIpICsKCQl0aGVtZSgKCQkJcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dCh2anVzdCA9IC0xKSwKCQkJcGxvdC5tYXJnaW4gPSB1bml0KGMoMCwgMSwgMSwgMC41KSwgImNtIikpCmBgYAoKQXF1w60gbGFzIHZhcmlhYmxlcyBtw6FzIGltcG9ydGFudGVzIHNvbiBtdXkgcGFyZWNpZGFzIGEgbGFzIGRlbCAqR0JNKi4KCioqKgojIyAqQ29uc2lkZXJhY2lvbmVzKiB7LX0gCgpFbiBzZWd1aWRhLCBlc3RlIGdyw6FmaWNvIGRlIGNhamFzIHkgYmlnb3RlcyBjb21wYXJhIGxvcyBtb2RlbG9zIGVtcGxlYWRvcyBoYXN0YSBhaG9yYSwgY29uIGxvcyBwYXJhbWV0cm9zIG1lam9yZXMgc2Vnw7puIGNhZGEgdHVuZWFkby4gUGFyYSBlbGxvLCBlbiBlc3RlIGNhc28sIGhhcsOpICoqdmFsaWRhY2nDs24gY3J1emFkYSByZXBldGlkYSoqLiBMYSBtw6l0cmljYSBxdWUgaGUgZWxlZ2lkbyBwYXJhIGxhIGNvbXBhcmFjacOzbiBlcyBsYSDDoXJlYSBhYmFqbyBkZSBsYSBjdXJ2YSBST0MgKCoqQVVDKiopLgoKYGBge3IgY3ZyLCBldmFsPUZBTFNFfQpBcmJvbCA8LSBjcnV6YWRhYXJib2xiaW4oCiAJZGF0YSA9IHVwVHJhaW4sCiAJdmFyZGVwID0gImF0dHJpdGlvbiIsCiAJbGlzdGNvbnRpID0gcHJlZGljdG9ycywKCWxpc3RjbGFzcyA9IGMoIiIpLAoJZ3J1cG9zID0gNSwKCXNpbmljaW8gPSAxMTIsCglyZXBlID0gNSwKCWNwID0gYygwKSwKCW1pbmJ1Y2tldCA9IDEwNSkKQXJib2wkbW9kZWxvID0gIkFyYm9sIgoKQmFnZ2luZyA8LSBjcnV6YWRhcmZiaW4oCglkYXRhID0gdXBUcmFpbiwKCXZhcmRlcCA9ICJhdHRyaXRpb24iLAoJbGlzdGNvbnRpID0gcHJlZGljdG9ycywKCWxpc3RjbGFzcyA9IGMoIiIpLAoJZ3J1cG9zID0gNSwKCXNpbmljaW8gPSAxMTIsCglyZXBlID0gNSwKCW5vZGVzaXplID0gNDAsCglzYW1wc2l6ZSA9IDE1NzAsCgltdHJ5ID0gNTEsCgludHJlZSA9IDEwMDAsCglyZXBsYWNlID0gVFJVRSkgIzQ5IG1pbgpCYWdnaW5nJG1vZGVsbyA9ICJCYWdnaW5nIFRyZWUiCgpSYW5kb21Gb3Jlc3QgPC0gY3J1emFkYXJmYmluKAoJZGF0YT11cFRyYWluLCAKCXZhcmRlcD0iYXR0cml0aW9uIiwKICBsaXN0Y29udGkgPSBwcmVkaWN0b3JzLCBsaXN0Y2xhc3M9YygiIiksCglncnVwb3MgPSA1LCBzaW5pY2lvID0gMTEyLCByZXBlID0gNSwKCW5vZGVzaXplID0gMjAsCglzYW1wc2l6ZSA9IDE1NzAsCgltdHJ5ID0gNDUsCgludHJlZSA9IDIwMDAsCglyZXBsYWNlID0gVFJVRSkKUmFuZG9tRm9yZXN0JG1vZGVsbyA9ICJSYW5kb20gRm9yZXN0IgoKR3JhZGllbnRCb29zdGluZyA8LSBjcnV6YWRhZ2JtYmluKAoJZGF0YT11cFRyYWluLCAKCXZhcmRlcD0iYXR0cml0aW9uIiwKICBsaXN0Y29udGkgPSBwcmVkaWN0b3JzLCBsaXN0Y2xhc3M9YygiIiksCglncnVwb3MgPSA1LCBzaW5pY2lvID0gMTEyLCByZXBlID0gNSwKCW4ubWlub2JzaW5ub2RlID0gMzUsCglzaHJpbmthZ2UgPSAxLAoJbi50cmVlcyA9IDEwMDAwLAoJaW50ZXJhY3Rpb24uZGVwdGggPSAyKQpHcmFkaWVudEJvb3N0aW5nJG1vZGVsbyA9ICJHQk0iCgpYR0Jvb3N0IDwtIGNydXphZGF4Z2JtYmluKAoJZGF0YT11cFRyYWluLCB2YXJkZXA9ImF0dHJpdGlvbiIsCiAgbGlzdGNvbnRpID0gcHJlZGljdG9ycywgbGlzdGNsYXNzPWMoIiIpLAoJZ3J1cG9zPTUsIHNpbmljaW89MTIzNCwgcmVwZT01LAogIG1pbl9jaGlsZF93ZWlnaHQ9MzUsIGV0YT0wLjA1LCAKCW5yb3VuZHM9MTAwMDAsIG1heF9kZXB0aD02LAogIGdhbW1hPTAsIGNvbHNhbXBsZV9ieXRyZWU9MSwgc3Vic2FtcGxlPTEpClhHQm9vc3QkbW9kZWxvPSJYR0Jvb3N0IgpgYGAKCgpgYGB7ciBmaW5hbF90cmVlLCBmaWcuY2FwPSJDb21wYXJhY2nDs24gQVVDIGVudHJlIG1vZGVsb3MifQpiZXN0X21vZGVsIDwtIHJiaW5kKExvZ2lzdGljYSwKCQkJCQkJCQkJQXJib2wsIAoJCQkJCQkJCQlCYWdnaW5nLAoJCQkJCQkJCQlSYW5kb21Gb3Jlc3QsCgkJCQkJCQkJCUdyYWRpZW50Qm9vc3RpbmcsCgkJCQkJCQkJCVhHQm9vc3QpCgpnZ3Bsb3QoZGF0YT1iZXN0X21vZGVsLCBhZXMoCgl4ID0gZmFjdG9yKG1vZGVsbywgbGV2ZWxzID0gYygKCQknTG9naXN0aWNhJywgJ0FyYm9sJywgJ0JhZ2dpbmcnLCAnUmFuZG9tIEZvcmVzdCcsICdHQk0nLCdYR0Jvb3N0JykpLCAKCWF1YykpICsKCWdlb21fZWNvbm9kaXN0KHdpZHRoID0gMC44LAoJCXRlbnRoX2NvbCA9ICcjODc5REREJywKIAkJbWVkaWFuX2NvbCA9ICcjMUQyRjY1JywKCQltZWRpYW5fcG9pbnRfc2l6ZSA9IDEuMywKIAkJbmluZXRpZXRoX2NvbCA9ICcjMkU0NUI4JykgKyAKCWhyYnJ0aGVtZXM6OnRoZW1lX2lwc3VtKAoJCWdyaWQgPSAiWFkiLAoJCWJhc2VfZmFtaWx5ID0gJ1RpbWVzJywJYmFzZV9zaXplID0gMTApICsKCXNjYWxlX3lfY29udGludW91cyhsaW1pdHMgPSBjKDAuODUsIDEpKSArCiAgbGFicyh0aXRsZSA9ICdDb21wYXJhY2nDs24gZW50cmUgdMOpY25pY2FzIGRlIMOhcmJvbGVzJywKICAJCSBzdWJ0aXRsZSA9ICdBcmVhIGFiYWpvIGRlIGxhIGN1cnZhIFJPQycsCiAgCQkgeSA9ICJBVUMiLCB4PSBOVUxMKSArIHRoZW1lKAogIAkJIAl0aXRsZSA9IGVsZW1lbnRfdGV4dChmYWNlPSdib2xkJyksCiAgCQkgCWF4aXMudGl0bGUueSA9IGVsZW1lbnRfdGV4dChzaXplPTEwKSwKICAJCSAJYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoCiAgCQkgCQlmYWNlID0gJ2JvbGQnLCBjb2xvdXIgPSAnIzNEM0QzRCcsIHNpemUgPSAxMCkpICsKICAgIHRoZW1lX2Vjb25vZGlzdChlY29uX3RleHRfY29sID0gIiMzYjQ1NGEiLCBlY29uX3Bsb3RfYmdfY29sID0gIndoaXRlIiwgZWNvbl9ncmlkX2NvbCA9ICIjYmJjYWQyIiwKICAgICAgICBlY29uX2ZvbnQgPSAiVGltZXMiLCBsaWdodF9mb250ID0gIlRpbWVzIiwgYm9sZF9mb250ID0gIlRpbWVzIikgKyB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIpKSAtPgogICAgZ2cKCmdyaWQubmV3cGFnZSgpCmxlZnRfYWxpZ24oZ2csIGMoInN1YnRpdGxlIiwgInRpdGxlIiwgImNhcHRpb24iKSkgJT4lCiAgICBhZGRfZWNvbm9kaXN0X2xlZ2VuZChlY29ub2Rpc3RfbGVnZW5kX2dyb2IoZmFtaWx5ID0gIlRpbWVzIiwgdGVudGhfY29sID0gIiM4NzlEREQiLCBtZWRfY29sID0gJyMxRDJGNjUnLAogICAgICAgIG5pbmV0aWV0aF9jb2wgPSAiIzFEMkY2NSIpLCBiZWxvdyA9ICJzdWJ0aXRsZSIpICU+JQogICAgZ3JpZC5kcmF3KCkKYGBgCgo8ZGl2IHN0eWxlPSJiYWNrZ3JvdW5kLWNvbG9yOiNENkRCRjU7IGJvcmRlci1yYWRpdXM6IDEwcHg7IHBhZGRpbmc6IDIwcHg7Ij4KLSBUb2RvcyBsb3MgbcOpdG9kb3MgKmVuc2VtYmxlKiB0aWVuZW4gbGFzIG1lam9yZXMgbcOpdHJpY2FzLCB5IGFwb3J0YW4gbWVqb3JlcyBhanVzdGVzIGFsIMOhcmJvbCBzb2xvIHkgYSBsYSByZWdyZXNpb24gbG9naXN0aWNhLgotIEVsIG1vZGVsbyBnYW5hZG9yIGVzIGVsICpHcmFkaWVudCBCb29zdGluZyosIGF1bnF1ZSBlbCAqUmFuZG9tIEZvcmVzdCosIHkgZWwgKlhHYm9vc3QqIGVzdMOhbiBqdXN0byBhYmFqby4KPC9kaXY+CgpgYGB7ciwgIGZpZy5zaG93PSJob2xkIiwgb3V0LndpZHRoPSI1MCUiLCBmaWcuY2FwPSJNZWpvcmVzIG1vZGVsb3MiLCBmaWcuc3ViY2FwPWMoIlRhc2EgZGUgZmFsbG8iLCAiQXJlYSBVbmRlciB0aGUgUk9DIGN1cnZlIil9CmdncGxvdChkYXRhPWJlc3RfbW9kZWwgJT4lIGZpbHRlcihtb2RlbG8gJWluJSBjKCdCYWdnaW5nJywnUmFuZG9tIEZvcmVzdCcsJ0dCTScsJ1hHQm9vc3QnKSksIAoJYWVzKAoJeCA9IGZhY3Rvcihtb2RlbG8sIGxldmVscyA9IGMoCgkJJ0JhZ2dpbmcnLCAnUmFuZG9tIEZvcmVzdCcsICdHQk0nLCdYR0Jvb3N0JykpLCAKCWFzLm51bWVyaWModGFzYSkpKSArCglnZW9tX2Vjb25vZGlzdCh3aWR0aCA9IDAuOCwKCQl0ZW50aF9jb2wgPSAnIzg3OURERCcsCiAJCW1lZGlhbl9jb2wgPSAnIzFEMkY2NScsCgkJbWVkaWFuX3BvaW50X3NpemUgPSAxLjMsCiAJCW5pbmV0aWV0aF9jb2wgPSAnIzJFNDVCOCcpICsgCglocmJydGhlbWVzOjp0aGVtZV9pcHN1bSgKCQlncmlkID0gIlhZIiwKCQliYXNlX2ZhbWlseSA9ICdUaW1lcycsCWJhc2Vfc2l6ZSA9IDEwKSArCgkgIGxhYnModGl0bGUgPSAnQ29tcGFyYWNpw7NuIGVudHJlIHTDqWNuaWNhcyBkZSDDoXJib2xlcycsCiAgCQkgc3VidGl0bGUgPSAnVGFzYSBkZSBmYWxsbycsCiAgCQkgeSA9ICJBVUMiLCB4PSBOVUxMKSArIHRoZW1lKAogIAkJIAl0aXRsZSA9IGVsZW1lbnRfdGV4dChmYWNlPSdib2xkJyksCiAgCQkgCWF4aXMudGl0bGUueSA9IGVsZW1lbnRfdGV4dChzaXplPTEwKSwKICAJCSAJYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoCiAgCQkgCQlmYWNlID0gJ2JvbGQnLCBjb2xvdXIgPSAnIzNEM0QzRCcsIHNpemUgPSAxMCkpICArCiAgICB0aGVtZV9lY29ub2Rpc3QoZWNvbl90ZXh0X2NvbCA9ICIjM2I0NTRhIiwgZWNvbl9wbG90X2JnX2NvbCA9ICJ3aGl0ZSIsIGVjb25fZ3JpZF9jb2wgPSAiI2JiY2FkMiIsCiAgICAgICAgZWNvbl9mb250ID0gIlRpbWVzIiwgbGlnaHRfZm9udCA9ICJUaW1lcyIsIGJvbGRfZm9udCA9ICJUaW1lcyIpICsgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiKSkgLT4KICAgIGdnCgpncmlkLm5ld3BhZ2UoKQpsZWZ0X2FsaWduKGdnLCBjKCJzdWJ0aXRsZSIsICJ0aXRsZSIsICJjYXB0aW9uIikpICU+JQogICAgYWRkX2Vjb25vZGlzdF9sZWdlbmQoZWNvbm9kaXN0X2xlZ2VuZF9ncm9iKGZhbWlseSA9ICJUaW1lcyIsIHRlbnRoX2NvbCA9ICIjODc5REREIiwgbWVkX2NvbCA9ICcjMUQyRjY1JywKICAgICAgICBuaW5ldGlldGhfY29sID0gIiMxRDJGNjUiKSwgYmVsb3cgPSAic3VidGl0bGUiKSAlPiUKICAgIGdyaWQuZHJhdygpCgpnZ3Bsb3QoZGF0YT1iZXN0X21vZGVsICU+JSBmaWx0ZXIobW9kZWxvICVpbiUgYygnQmFnZ2luZycsCgkJCQkJCQkJCSdSYW5kb20gRm9yZXN0JywKCQkJCQkJCQkJJ0dCTScsCgkJCQkJCQkJCSdYR0Jvb3N0JykpLCAKCQkJIGFlcygKCXggPSBmYWN0b3IobW9kZWxvLCBsZXZlbHMgPSBjKAoJCSdCYWdnaW5nJywgJ1JhbmRvbSBGb3Jlc3QnLCAnR0JNJywnWEdCb29zdCcpKSwgCglhdWMpKSArCglnZW9tX2Vjb25vZGlzdCh3aWR0aCA9IDAuOCwKCQl0ZW50aF9jb2wgPSAnIzg3OURERCcsCiAJCW1lZGlhbl9jb2wgPSAnIzFEMkY2NScsCgkJbWVkaWFuX3BvaW50X3NpemUgPSAxLjMsCiAJCW5pbmV0aWV0aF9jb2wgPSAnIzJFNDVCOCcpICsgCglocmJydGhlbWVzOjp0aGVtZV9pcHN1bSgKCQlncmlkID0gIlhZIiwKCQliYXNlX2ZhbWlseSA9ICdUaW1lcycsCWJhc2Vfc2l6ZSA9IDEwKSArCgkgIGxhYnModGl0bGUgPSAnQ29tcGFyYWNpw7NuIGVudHJlIHTDqWNuaWNhcyBkZSDDoXJib2xlcycsCiAgCQkgc3VidGl0bGUgPSAnQXJlYSBhYmFqbyBkZSBsYSBjdXJ2YSBST0MnLAogIAkJIHkgPSAiQVVDIiwgeD0gTlVMTCkgKyB0aGVtZSgKICAJCSAJdGl0bGUgPSBlbGVtZW50X3RleHQoZmFjZT0nYm9sZCcpLAogIAkJIAlheGlzLnRpdGxlLnkgPSBlbGVtZW50X3RleHQoc2l6ZT0xMCksCiAgCQkgCWF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KAogIAkJIAkJZmFjZSA9ICdib2xkJywgY29sb3VyID0gJyMzRDNEM0QnLCBzaXplID0gMTApKSAgKwogICAgdGhlbWVfZWNvbm9kaXN0KGVjb25fdGV4dF9jb2wgPSAiIzNiNDU0YSIsIGVjb25fcGxvdF9iZ19jb2wgPSAid2hpdGUiLCBlY29uX2dyaWRfY29sID0gIiNiYmNhZDIiLAogICAgICAgIGVjb25fZm9udCA9ICJUaW1lcyIsIGxpZ2h0X2ZvbnQgPSAiVGltZXMiLCBib2xkX2ZvbnQgPSAiVGltZXMiKSArIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIikpIC0+CiAgICBnZwoKZ3JpZC5uZXdwYWdlKCkKbGVmdF9hbGlnbihnZywgYygic3VidGl0bGUiLCAidGl0bGUiLCAiY2FwdGlvbiIpKSAlPiUKICAgIGFkZF9lY29ub2Rpc3RfbGVnZW5kKGVjb25vZGlzdF9sZWdlbmRfZ3JvYihmYW1pbHkgPSAiVGltZXMiLCB0ZW50aF9jb2wgPSAiIzg3OURERCIsIG1lZF9jb2wgPSAnIzFEMkY2NScsCiAgICAgICAgbmluZXRpZXRoX2NvbCA9ICIjMUQyRjY1IiksIGJlbG93ID0gInN1YnRpdGxlIikgJT4lCiAgICBncmlkLmRyYXcoKQoKYGBgCgpDb24gZXN0ZSBncsOhZmljbyBwb2RlbW9zIHZlciBtZWpvciBsb3MgcmVzdWx0YWRvcyBkZSBsYXMgaXRlcmFjY2lvbmVzIGRlIGxvcyBtb2RlbG9zIHBsYW50ZWFkb3MuIEVsIG1vZGVsbyBkZSAqKmdyYWRpZW50IGJvb3N0aW5nKiogdGllbmUgdW4gKkFVQyogZGUgY2FzaSAwLjk5LCBjb24gdW5hIHRhc2EgZGUgZmFsbG8gYWxyZWRlZG9yIGRlIDAuMDQuIAoKTHVlZ28sIHBhcmEgb2JzZXJ2YXIgaGFzdGEgcXVlIHB1bnRvIGxhIHZhcmlhY2nDs24gZGVsIG1vZGVsbyBpbmZsdXllIHNvYnJlIGxhIGltcG9ydGFuY2lhIGRlIGxhcyB2YXJpYWJsZXMsIGhlIGNyZWFkbyB1biBncsOhZmljbyBkZSBiYXJyYXMgYXBpbGFkYXMsIHB1ZXN0byBhIG9yZGVuYXIgbGFzIHZhcmlhYmxlcyBzZWfDum4gaW1wb3J0YW5jaWEgdG90YWwgZGUgbG9zIG1vZGVsb3MganVudG9zLgoKYGBge3Igb3ZlcmFsbF9pbXBvcnRhbmNlLCBmaWcuaGVpZ2h0PTEwfQpsaWJyYXJ5KGdnY2hpY2tsZXQpCmxpYnJhcnkodmlwKQppbm5lcl9qb2luKHZpKHhnYm0pLCB2aShnYm0pLCBieSA9ICJWYXJpYWJsZSIpICU+JQoJaW5uZXJfam9pbih2aShyZiksIGJ5ID0gIlZhcmlhYmxlIikgJT4lCglpbm5lcl9qb2luKHZpKGJnKSwgYnkgPSAiVmFyaWFibGUiKSAlPiUKIwlpbm5lcl9qb2luKHZpKGFyYm9sKSwgYnk9ICJWYXJpYWJsZSIpICU+JSAKCXJlbmFtZSgKCQkiWEdCTSIgPSBJbXBvcnRhbmNlLngsCgkJIkdCTSIgPSBJbXBvcnRhbmNlLnksCgkJIlJhbmRvbSBGb3Jlc3QiID0gSW1wb3J0YW5jZS54LngsCgkJIkJhZ2dpbmciID0gSW1wb3J0YW5jZS55LnksCiMJCSJUcmVlIiA9IEltcG9ydGFuY2UKCSkgJT4lCiAgICBtdXRhdGUoCiAgICAJVmFyaWFibGUgPSBzdHJpbmdyOjpzdHJfdG9fdGl0bGUoVmFyaWFibGUpKSAlPiUKICAgIG11dGF0ZShWYXJpYWJsZSA9IHN0cl9yZXBsYWNlX2FsbChWYXJpYWJsZSwgIl8iLCAiICIpKSAlPiUKCXBpdm90X2xvbmdlcighVmFyaWFibGUsCgkJCQkJCQkgbmFtZXNfdG8gPSAiTW9kZWwiLAoJCQkJCQkJIHZhbHVlc190byA9ICJJbXBvcnRhbmNlIikgJT4lCglhcnJhbmdlKGRlc2MoSW1wb3J0YW5jZSkpICU+JQoJZ2dwbG90KGFlcyhyZW9yZGVyKFZhcmlhYmxlLCBJbXBvcnRhbmNlKSwKCQkJCQkJIEltcG9ydGFuY2UsCgkJCQkJCSBncm91cCA9IE1vZGVsLCAKCQkJCQkJIGZpbGwgPSBNb2RlbCkpICsKCWdlb21fY2hpY2tsZXQod2lkdGggPSAwLjgsCgkJCQkJCQkJcmFkaXVzID0gZ3JpZDo6dW5pdCg2LCAicHQiKSkgKwoJc2NhbGVfeV9jb250aW51b3VzKHBvc2l0aW9uID0gInJpZ2h0IiwKCQkJCQkJCQkJCSBicmVha3MgPSBzZXEoMCwzMDAsIGJ5ID0gNTApKSArCglzY2FsZV9maWxsX21hbnVhbCgKCQluYW1lID0gTlVMTCwKCQl2YWx1ZXMgPSBjKAojCQkJIlRyZWUiID0gIiNENkRCRjUiLAoJCQkiQmFnZ2luZyIgPSAiI0IwQjlFNyIsCgkJCSJSYW5kb20gRm9yZXN0IiA9ICIjOEI5OEQ5IiwKCQkJIkdCTSIgPSAiIzY1NzZDQiIsCgkJCSJYR0JNIiA9ICIjNDA1NUJFIgoJCSkKCSkgKwoJZ3VpZGVzKGZpbGwgPSBndWlkZV9sZWdlbmQobnJvdyA9IDEpKSArCgljb29yZF9mbGlwKCkgKwoJbGFicygKCQl4ID0gTlVMTCwKCQl5ID0gTlVMTCwKCQlmaWxsID0gTlVMTCwKCQl0aXRsZSA9ICJJbXBvcnRhbmNpYSB2YXJpYWJsZXMgc2Vnw7puIGNhZGEgbW9kZWxvICIsCgkJc3VidGl0bGUgPSAiIiwKCQljYXB0aW9uID0gIkVsIMOhcmJvbCBkZSBkZWNpc2lvbiBubyBoYSBzaWRvIGNvbnNpZGVyYWRvIGVuIGN1YW50byBubyB0b2RhcyBsYXMgdmFyaWFibGVzIGVudHJhbiBlbiBlbCBtb2RlbG8iCgkpICsKCWhyYnJ0aGVtZXM6OnRoZW1lX2lwc3VtKGJhc2VfZmFtaWx5ID0gIlRpbWVzIiwgCgkJCQkJCQkJCQkJCQlncmlkID0gIlh4IiwKCQkJCQkJCQkJCQkJCWJhc2Vfc2l6ZSA9IDEyKSArCgl0aGVtZSgKCQlwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHZqdXN0ID0gLTEpLAoJCWxlZ2VuZC50ZXh0ID0gZWxlbWVudF90ZXh0KHNpemU9MTIpLAoJCXBsb3QubWFyZ2luID0gdW5pdChjKDAsIDEsIDEsIDAuNSksICJjbSIpLAoJCWxlZ2VuZC5wb3NpdGlvbiA9ICJ0b3AiLAoJKQpgYGAKCi0gUG9kZW1vcyBvYnNlcnZhciBjb21vIGxhcyB2YXJpYWJsZSBtw6FzIGluZmx1eWVudGVzIHNvbiAqKk1vbnRobHlJY29tZSoqIHkgKipPdmVyVGltZV9ZZXMqKiwgZXMgZGVjaXIgbGFzIGNvc2FzIGNvc2FzIHF1ZSBpbmZsdXllbiBtw6FzIGxvcyBlbXBsZWFkb3MgYSBsYSBob3JhIGRlIGRlamFyIHVuYSBlbXByZXNhIHNvbiBlbCBzYWxhcmlvIG1lbnN1YWwgeSBzaSB0cmFiYWphbiBkZSBzb2JyYSBhIGxhcyBob3JhcyBlc3RhbmRhcmQuCi0gTGEgZWRhZCB5IGxvcyBhw7FvcyBwYXNhZG9zIGEgdHJhYmFqYXIgZW4gbGEgZW1wcmVzYSB0YW1iacOpbiBzb24gaW1wb3J0YW50ZXMuIEVzdG8gZXMgbm9ybWFsIGVuIGN1YW50byBoYXkgcXVlIHRlbmVyIGVuIGN1ZW50YSB0YW1iacOpbiBsb3MgcXVlIHZhbiBwb3IgZWwganViaWxhZG8uIAotIE90cm8gY29uanVudG8gZGUgdmFyaWFibGVzIHF1ZSBkZXN0YWNhbiBzdXMgaW5mbHVlbmNpYSBzb24gbGFzIHF1ZSBtaWRlbiBlbCBuaXZlbCBkZSBzYXRpc2ZhY2Npw7NuIGdlbmVyYWwsIGVzIGRlY2lyICoqRW52aXJvbm1lbnRTYXRpc2ZhY3Rpb24qKiwgKipKb2JTYXRpc2ZhY3Rpb24qKiB5ICoqUmVsYXRpb25zaGlwU2F0aXNmYWN0aW9uKiouIAoKCjxkaXYgc3R5bGU9ImJhY2tncm91bmQtY29sb3I6I0Q2REJGNTsgYm9yZGVyLXJhZGl1czogMTBweDsgcGFkZGluZzogMjBweDsiPgoKKipQb3JxdWUgZXN0ZSBncsOhZmljbyBlcyBtdXkgaW1wb3J0YW50ZT8gUG9ycXVlIGEgcGFydGlyIGRlIGFob3JhLCB2b3kgYSBlbXBsZWFyIG90cmFzIHTDqWNuaWNhcyBkZSBtYWNoaW5lIGxlYXJuaW5nIGEgcGFydGlyIGRlIGVzdGUgY29uanVudG8gZGUgdmFyaWFibGVzIG3DoXMgaW5mbHV5ZW50ZXMgYSBsYSBob3JhIGRlIG1lZGlyIGVsIMOtbmRpY2UgZGUgZGVzZXJjacOzbiBkZSBsb3MgZW1wbGVhZG9zLiBTZWd1w61tb3MuKioKPC9kaXY+CgojIyMgTnVldm8gY29uanVudG8gKlRyYWluL1Rlc3QqIGNvbiB2YXJpYWJsZXMgbcOhcyBpbmZsdXllbnRlcyB7LX0KCmBgYHtyfQp1cFRyYWluMiA8LSB1cFRyYWluICU+JSBzZWxlY3QoCgkgICdhdHRyaXRpb24nLAoJICAnbW9udGhseV9pbmNvbWUnLAoJCSdvdmVyX3RpbWVfeWVzJywKCQknYWdlJywKCQknZGFpbHlfcmF0ZScsCgkJJ2Vudmlyb25tZW50X3NhdGlzZmFjdGlvbicsCgkJJ2Rpc3RhbmNlX2Zyb21faG9tZScsCgkJJ2pvYl9zYXRpc2ZhY3Rpb24nLAoJCSdzdG9ja19vcHRpb25fbGV2ZWwnLAoJCSd0b3RhbF93b3JraW5nX3llYXJzJywKCQknbnVtX2NvbXBhbmllc193b3JrZWQnLAoJCSJob3VybHlfcmF0ZSIsCgkJInllYXJzX2F0X2NvbXBhbnkiLAoJCSJwZXJjZW50X3NhbGFyeV9oaWtlIiwKCQkicmVsYXRpb25zaGlwX3NhdGlzZmFjdGlvbiIsCgkJImpvYl9sZXZlbCIsCgkJImpvYl9pbnZvbHZlbWVudCIsCgkJImpvYl9yb2xlX3Jlc2VhcmNoX3NjaWVudGlzdCIsCgkJInllYXJzX3NpbmNlX2xhc3RfcHJvbW90aW9uIiwKCQkibWFyaXRhbF9zdGF0dXNfc2luZ2xlIiwKCQkid29ya19saWZlX2JhbGFuY2UiKQoKVGVzdDIgPC0gVGVzdCAlPiUgc2VsZWN0KAoJICAnYXR0cml0aW9uJywKCSAgJ21vbnRobHlfaW5jb21lJywKCQknb3Zlcl90aW1lX3llcycsCgkJJ2FnZScsCgkJJ2RhaWx5X3JhdGUnLAoJCSdlbnZpcm9ubWVudF9zYXRpc2ZhY3Rpb24nLAoJCSdkaXN0YW5jZV9mcm9tX2hvbWUnLAoJCSdqb2Jfc2F0aXNmYWN0aW9uJywKCQknc3RvY2tfb3B0aW9uX2xldmVsJywKCQkndG90YWxfd29ya2luZ195ZWFycycsCgkJJ251bV9jb21wYW5pZXNfd29ya2VkJywKCQkiaG91cmx5X3JhdGUiLAoJCSJ5ZWFyc19hdF9jb21wYW55IiwKCQkicGVyY2VudF9zYWxhcnlfaGlrZSIsCgkJInJlbGF0aW9uc2hpcF9zYXRpc2ZhY3Rpb24iLAoJCSJqb2JfbGV2ZWwiLAoJCSJqb2JfaW52b2x2ZW1lbnQiLAoJCSJqb2Jfcm9sZV9yZXNlYXJjaF9zY2llbnRpc3QiLAoJCSJ5ZWFyc19zaW5jZV9sYXN0X3Byb21vdGlvbiIsCgkJIm1hcml0YWxfc3RhdHVzX3NpbmdsZSIsCgkJIndvcmtfbGlmZV9iYWxhbmNlIikKYGBgCgoKIyMgU3VwcG9ydCBWZWN0b3IgTWFjaGluZXMKIyMjIFNWTSBMaW5lYWwKQ29tbyBoZW1vcyBwb2RpZG8gdmVyIGEgcGFydGlyIGRlIGxhIEVEQSwgbm8gaGF5IHNlcGFyYWPDrW9uIGxpbmVhbCBlbnRyZSBsb3MgZGF0b3MuIEVudG9uY2VzLCBwb2RlbW9zIHlhIGRlY2lyIHF1ZSBlbnRyZSBsb3MgKlNWTSogcXVlIHZveSBhIGVtcGxlYXIsIGxvcyBjb24gdW4ga2VybmVsIGxpbmVsIG5vIHZhbiBhIGZ1bmNpb25hciBiaWVuIGNvbiBlc3RvcyBkYXRvcy4KCiMjIyMgVHVuZW8gZGVsIE1vZGVsbwpgYGB7ciwgZXZhbD1GQUxTRX0KcmVnaXN0ZXJEb1BhcmFsbGVsKG1ha2VDbHVzdGVyKDMpIC0+IGNwdSkgCnRpYygpCnNldC5zZWVkKDExMikgICMgZm9yIHJlcHJvZHVjaWJpbGl0eQpzdm1fbGluX3Jlc3VsdCA8LSB0cmFpbigKICBmYWN0b3IoYXR0cml0aW9uKSB+IC4sIAogIGRhdGEgPSB1cFRyYWluMiwKICBtZXRob2QgPSAic3ZtTGluZWFyIiwgICAgICAKICB2ZXJib3NlID0gRkFMU0UsCiAgdHJDb250cm9sID0gY29udHJvbCwKICB0dW5lR3JpZCA9IGV4cGFuZC5ncmlkKAogIAlDPWMoMC4wMSwwLjA1LDAuMSwwLjIsMC41LDEsMiw1LDEwLDIwLDUwKSksCikKdG9jKCkgIzUuODRtaW4gKDE1LjEgbWluIGNvbiB0b2RhcyBsYXMgdmFyaWFibGVzKQpzdG9wQ2x1c3RlcihjcHUpCmBgYAoKVmlzdWFsaXphbW9zIGxvcyByZXN1bHRhZG9zIGRlbCB0dW5lbzoKCmBgYHtyfQpjYmluZChzdm1fbGluX3Jlc3VsdCRiZXN0VHVuZSwgZ2V0VHJhaW5QZXJmKHN2bV9saW4pKSAlPiUKICAgIGthYmxlKGZvcm1hdCA9ICJodG1sIiwgYm9vdHN0cmFwX29wdGlvbnMgPSAiYmFzaWMiLAogICAgCQkJY2FwdGlvbiA9ICJCZXN0IFR1bmUiLAogICAgCQkJZGlnaXRzID0gMykgCgpnZ3Bsb3Qoc3ZtX2xpbl9yZXN1bHQpICsgZ2VvbV9saW5lKGNvbG91ciA9ICIjMkU0NUI4Iiwgc2l6ZSA9IDAuOCkgKyBnZW9tX3BvaW50KGNvbG91cj0gIiM1NjcwQzAiKSArIGhyYnJ0aGVtZXM6OnRoZW1lX2lwc3VtKHRpY2tzID0gVCwKICAgIGJhc2VfZmFtaWx5ID0gIlRpbWVzIiwgYmFzZV9zaXplID0gMTIpICsgbGFicyh0aXRsZSA9ICJUdW5pbmcgUmVzdWx0cyIsIHN1YnRpdGxlID0gIlNWTSBMaW5lYWwiKSArCiAgICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIsIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoY29sb3IgPSAiIzNiNDU0YSIpLAogICAgICAgIHRpdGxlID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIpLCBheGlzLnRpdGxlLnkgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyKSwKICAgICAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X3RleHQoY29sb3VyID0gIiMzRDNEM0QiLCBzaXplID0gMTIpKQpgYGAKCiMjIyMgTW9kZWxvIEZpbmFsCgpgYGB7ciBzdm1fbGluLCBldmFsPUZBTFNFfQpzdm1fbGluIDwtIHRyYWluKAogIGZhY3RvcihhdHRyaXRpb24pIH4gLiwgCiAgZGF0YSA9IHVwVHJhaW4yLAogIG1ldGhvZCA9ICJzdm1MaW5lYXIiLCAgICAgIAogIHZlcmJvc2UgPSBGQUxTRSwKICB0ckNvbnRyb2wgPSBjb250cm9sLAogIHR1bmVHcmlkID0gZXhwYW5kLmdyaWQoCiAgCUM9YygwLjAxKSkpCmBgYAoKYGBge3IsIGZpZy5zaG93PSJob2xkIiwgb3V0LndpZHRoPSI1MCUiLCBmaWcuY2FwPSJTVk0gTGluZWFsIiwgZmlnLnN1YmNhcD1jKCJNYXRyaXogZGUgQ29uZnVzaW9uIiwgIlJPQyBjdXJ2ZSIpfQpkcmF3X2NvbmZ1c2lvbl9tYXRyaXgoCgljb25mdXNpb25NYXRyaXgoc3ZtX2xpbiRwcmVkJHByZWQsCgkJCQkJCQkJCXN2bV9saW4kcHJlZCRvYnMpLAoJIiNENkRCRjUiLCIjMkU0NUI4IikKCnJvYyhzdm1fbGluJHByZWQkb2JzLAoJCXN2bV9saW4kcHJlZCRZZXMpICU+JQogICAgZ2dyb2MoY29sb3VyID0gIiMyRTQ1QjgiLCBzaXplID0gMC44KSArIGFubm90YXRlKCJ0ZXh0IiwgeCA9IDAuMDgsIHkgPSAwLjg4LAogICAgbGFiZWwgPSAiYm9sZChBVUMpOiAwLjgyNyIsIGZhbWlseSA9ICJUaW1lcyIsIHBhcnNlID0gVFJVRSkgKyBocmJydGhlbWVzOjp0aGVtZV9pcHN1bSh0aWNrcyA9IFQsCiAgICBiYXNlX2ZhbWlseSA9ICJUaW1lcyIsIGJhc2Vfc2l6ZSA9IDEyKSArIGxhYnModGl0bGUgPSAiUk9DIEN1cnZlIiwgc3VidGl0bGUgPSAiU1ZNIExpbmVhbCIpICsgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiLCBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9yZWN0KGNvbG9yID0gIiMzYjQ1NGEiKSwKICAgICAgICB0aXRsZSA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiKSwgYXhpcy50aXRsZS55ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiksCiAgICAgICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF90ZXh0KGNvbG91ciA9ICIjM0QzRDNEIiwgc2l6ZSA9IDEyKSkKYGBgCgpFbnRyZSBsb3MgbW9kZWxvcyBlbXBsZWFkb3MgaGFzdGEgYWhvcmEsIG5vIGFwb3J0YSBidWVuYXMgbcOpdHJpY2FzLiBWYW1vcyBhIGNvbnN0cnVpciB1bmEgKm1hcXVpbmEgZGUgc3Vwb3J0ZSB2ZWN0b3JpYWwqIGNvbiB1biBrZXJuZWwgbcOhcyBjb21wbGVqby4gCgojIyMgU1ZNIFBvbGlub21pYWwKYGBge3Igc3ZtX3BvbHlfZGVzY3J9CmxpYnJhcnkoa2FibGVFeHRyYSkKbW9kZWxMb29rdXAoInN2bVBvbHkiKVsyOjNdICU+JQogICAgY2JpbmQoZGVzY3JpcHRpb24gPSBsaW5lYnJlYWsoYygKICAgIAkiRWwgZ3JhZG8gZGUgbGEgZsO6bmNpb24gcG9saW5vbWlhbCIsCiAgICAgICJFbCBwYXJhbWV0cm8gcXVlIGFqdXN0YSBlbCBrZXJuZWwgYSBsYSBob3JhIGRlIGRpdmlkaXIgbGFzIHByZWRpY2Npb25lcyIsCiAgICAgICJFbCBwYXLDoW1ldHJvIGRlIHJlZ3VsYXJpemFjacOzbiAoQyksIGxhIGNvbnN0YW50ZSBDIGRlbCB0w6lybWlubyBkZSByZWd1bGFyaXphY2nDs24gZW4gbGEgZm9ybXVsYWNpw7NuIGRlIExhZ3JhbmdlLCBpbmRpY2EgYSBsYSBtYXF1aW5hIGRlIHN1cG9ydGUgY3XDoW50byBldml0YXIgbGEgY2xhc2lmaWNhY2nDs24gZXJyw7NuZWEgZGUgY2FkYSBlamVtcGxvIGRlIGVudHJlbmFtaWVudG8uIikpKSAlPiUKICAgIGthYmxlKGZvcm1hdCA9ICJodG1sIiwgYWxpZ24gPSBjKHJlcCgibCIsIDMpKSwgY2FwdGlvbiA9ICJEZXNjcmlwY2lvbiBkZSBsb3MgcGFyw6FtZXRyb3MgZW4gU1ZNIFBvbGlub21pYWwiKSAlPiUKICAgIHJvd19zcGVjKDAsIGZvbnRfc2l6ZSA9IDE0KSAlPiUKICAgIGNvbHVtbl9zcGVjKDEsd2lkdGggPSAxMiwgbW9ub3NwYWNlID0gVCkgJT4lIAogICAgY29sdW1uX3NwZWMoMix3aWR0aCA9IDE0KQpgYGAKCiMjIyMgVHVuZW8gZGVsIE1vZGVsbwpgYGB7ciBzdm1fcG9seV9yZXN1bHQsIGV2YWw9RkFMU0V9CnJlZ2lzdGVyRG9QYXJhbGxlbChtYWtlQ2x1c3RlcigzKSAtPiBjcHUpIAp0aWMoKQpzZXQuc2VlZCgxMTIpICAjIGZvciByZXByb2R1Y2liaWxpdHkKc3ZtX3BvbHlfcmVzdWx0IDwtIHRyYWluKAogIGZhY3RvcihhdHRyaXRpb24pfi4sIAogIGRhdGEgPSB1cFRyYWluMiwKICBtZXRob2QgPSAic3ZtUG9seSIsICAgICAgCiAgdmVyYm9zZSA9IEZBTFNFLAogIHRyYWluQ29udHJvbCA9IGNvbnRyb2wsCiAgdHVuZUdyaWQgPSBleHBhbmQuZ3JpZCgKICAJQz1jKDAuMDEsMC4wNSwwLjEsMC4yLDEpLAogIAlkZWdyZWU9YygyLDMpLAogIAlzY2FsZT1jKDAuMSwwLjUsMSwyKSkpIAp0b2MoKSAjMTguNDUgbWluCnN0b3BDbHVzdGVyKGNwdSkKYGBgCgpFc3RhIHRhYmxhIG5vcyBkaWNlIGN1YWwgaGFuIHNpZG8gbG9zIHBhcsOhbWV0cm9zIG1lam9yZXM6IApgYGB7ciBzdm1fcG9seV9iZXN0X3R1bmV9CmNiaW5kKHN2bV9wb2x5X3Jlc3VsdCRiZXN0VHVuZSwgZ2V0VHJhaW5QZXJmKHN2bV9wb2x5KSkgJT4lCiAgICBrYWJsZShmb3JtYXQgPSAiaHRtbCIsIGJvb3RzdHJhcF9vcHRpb25zID0gImJhc2ljIiwKICAgIAkJCWNhcHRpb24gPSAiQmVzdCBUdW5lIiwKICAgIAkJCWRpZ2l0cyA9IDMpIAoKcGxvdCgKCXN2bV9wb2x5X3Jlc3VsdCwKCW91dHB1dCA9ICJnZ3Bsb3QiLAoJImxpbmUiLAoJbGF5b3V0ID0gYygyLCAxKSwKCXBhci5zZXR0aW5ncyA9IGxpc3QoCgkJc3VwZXJwb3NlLmxpbmUgPSBsaXN0KAoJCQlsd2QgPSAxLAoJCQljb2wgPSBjKCIjRDZEQkY1IiwgIiM1NjcwQzAiLCAiIzJFNDVCOCIsICIjMkE3ODhFIikKCQkpLAoJCXN1cGVycG9zZS5zeW1ib2wgPSBsaXN0KAoJCQlwY2ggPSAxOSwKCQkJY2V4ID0gMC42LAoJCQljb2wgPSBjKCIjRDZEQkY1IiwgIiM1NjcwQzAiLCAiIzJFNDVCOCIsICIjMkE3ODhFIikKCQkpLAoJCXN0cmlwLmJhY2tncm91bmQgPSBsaXN0KGNvbCA9ICIjRDZEQkY1IikKCSkKKQpgYGAKClRhbWJpw6luIGVzIGltcG9ydGFudGUgdW5hIGNvbnN0YXRhY2nDs24gdmlzdWFsIGRlbCB0dW5lby4gUG9kZW1vcyB2ZXIgcXVlIGxhIG3DoXhpbWEgYWNjdXJhY3kgc2UgYWxjYW56YSBjb24gdW4gZ3JhZG8gZGUgMywgbWVqb3JhbmRvIG11Y2hvIGVsIGdyYWRvIDIuIE1lIHBhcmVjZSBub3JtYWwgY29tbyBlbCBjb25qdW50byBkZSBkYXRvcyB0aWVuZSBtdWNoYXMgdmFyaWFibGVzLiBFbCBhanVzdGUgZGVsIGtlcm5lbCBtZWpvciBlcyBlbCAyLCBhdW5xdWUgY29uIHVuIHBvbGluw7NtaWNvIGRlIDMgZ3JhZG9zIGVzIHN1ZmljaWVudGUgdW4gdmFsb3IgZW50cmUgMC41IHkgMQoKIyMjIyBNb2RlbG8gRmluYWwKYGBge3Igc3ZtX3BvbHksIGV2YWw9RkFMU0V9CnN2bV9wb2x5IDwtIHRyYWluKAogIGZhY3RvcihhdHRyaXRpb24pIH4gLiwgCiAgZGF0YSA9IHVwVHJhaW4yLAogIG1ldGhvZCA9ICJzdm1Qb2x5IiwgICAgICAKICB2ZXJib3NlID0gRkFMU0UsCiAgdHJDb250cm9sID0gY29udHJvbCwKICB0dW5lR3JpZCA9IGV4cGFuZC5ncmlkKAogIAlDPWMoMC4wMSksCiBkZWdyZWU9YygzKSwKIHNjYWxlPWMoMikpKQpgYGAKCmBgYHtyLCBmaWcuc2hvdz0iaG9sZCIsIG91dC53aWR0aD0iNTAlIiwgZmlnLmNhcD0iU1ZNIFBvbGlub21pYWwiLCBmaWcuc3ViY2FwPWMoIk1hdHJpeiBkZSBDb25mdXNpb24iLCAiUk9DIGN1cnZlIil9CmRyYXdfY29uZnVzaW9uX21hdHJpeCgKCWNvbmZ1c2lvbk1hdHJpeChzdm1fcG9seSRwcmVkJHByZWQsCgkJCQkJCQkJCXN2bV9wb2x5JHByZWQkb2JzKSwKCSIjRDZEQkY1IiwiIzJFNDVCOCIpCgpyb2Moc3ZtX3BvbHkkcHJlZCRvYnMsCgkJc3ZtX3BvbHkkcHJlZCRZZXMpICU+JQogICAgZ2dyb2MoY29sb3VyID0gIiMyRTQ1QjgiLCBzaXplID0gMC42MykgKyBhbm5vdGF0ZSgidGV4dCIsIHggPSAwLjA4LCB5ID0gMC45MiwKICAgIGxhYmVsID0gImJvbGQoQVVDKTogMC45NyIsIGZhbWlseSA9ICJUaW1lcyIsIHBhcnNlID0gVFJVRSkgKyBocmJydGhlbWVzOjp0aGVtZV9pcHN1bSh0aWNrcyA9IFQsCiAgICBiYXNlX2ZhbWlseSA9ICJUaW1lcyIsIGJhc2Vfc2l6ZSA9IDEwKSArIGxhYnModGl0bGUgPSAiUk9DIEN1cnZlIiwgc3VidGl0bGUgPSAiU1ZNIFBvbGlub21pYWwiKSArCiAgICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIsIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoY29sb3IgPSAiIzNiNDU0YSIpLAogICAgICAgIHRpdGxlID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIpLCBheGlzLnRpdGxlLnkgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSwKICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiLCBjb2xvdXIgPSAiIzNEM0QzRCIsIHNpemUgPSAxMCkpCmBgYAoKTGEgbWFxdWluYSBkZSBzdXBvcnRlIHZlY3RvcmlhbCBjb24ga2VybmVsIHBvbGluw7NtaWNvIG1lam9yYSBtdWNobyBsYSBjb24ga2VybmVsIGxpbmVhbC4gQ29uIHVuIGFjY3VyYWN5IGRlIDAuOTUgeSAqQVVDKiBkZSAwLjk3IGVzIHVubyBkZSBsb3MgbWVqb3JlcyBtb2RlbG9zIGFqdXN0YWRvcyBoYXN0YSBhaG9yYS4KCiMjIyBTVk0gUmFkaWFsClZhbW9zIGEgYWp1c3RhciBsYSDDumx0aW1hIGRlIGxhcyBtYXF1aW5hcyBkZSBzdXBvcnRlIHZlY3RvcmlhbC4gRXN0YSB2ZXosIGVudHJlIGxvcyBwYXLDoW1ldHJvcyBhIHR1bmVhciwgZW5jb250cmFtb3MgY29tbyBzaWVtcHJlIGVsIHBhcsOhbWV0cm8gZGUgcmVndWxhcml6YWNpw7NuIHkgKnNpZ21hKiwgcXVlIGRldGVybWluYSBlbCBhbGNhbmNlIGRlIHVuYSBzb2xhIGluc3RhbmNpYSBkZSBlbnRyZW5hbWllbnRvLgoKIyMjIyBUdW5lbyBkZWwgTW9kZWxvCmBgYHtyIHN2bV9yYWRfcmVzdWx0LCBldmFsPUZBTFNFfQpyZWdpc3RlckRvUGFyYWxsZWwobWFrZUNsdXN0ZXIoMykgLT4gY3B1KSAKdGljKCkKc2V0LnNlZWQoMTEyKSAgIyBmb3IgcmVwcm9kdWNpYmlsaXR5CnN2bV9yYWRfcmVzdWx0IDwtIHRyYWluKAogIGZhY3RvcihhdHRyaXRpb24pIH4gLiwgCiAgZGF0YSA9IHVwVHJhaW4yLAogIG1ldGhvZCA9ICJzdm1SYWRpYWwiLCAgICAgIAogIHZlcmJvc2UgPSBGQUxTRSwKICB0cmFpbkNvbnRyb2wgPSBjb250cm9sLAogIHR1bmVHcmlkID0gZXhwYW5kLmdyaWQoCiAgCUM9YygwLjAxLDAuMDUsMC4xLDAuMiwwLjUsMSwyLDUsMTAsMzApLAogc2lnbWE9YygwLjAxLDAuMDUsMC4xLDAuMiwwLjUsMSwyLDUsMTAsMzApKSwKKQp0b2MoKSAjMTAuNzEgbWluCnN0b3BDbHVzdGVyKGNwdSkKYGBgCgpFc3RvcyBzb24gbG9zIHBhcsOhbWV0cm9zIG1lam9yZXM6CmBgYHtyfQpjYmluZChzdm1fcmFkX3Jlc3VsdCRiZXN0VHVuZSwgCgkJCWdldFRyYWluUGVyZihzdm1fcmFkX3Jlc3VsdCkpICU+JQogICAga2FibGUoZm9ybWF0ID0gImh0bWwiLCBib290c3RyYXBfb3B0aW9ucyA9ICJiYXNpYyIsCiAgICAJCQljYXB0aW9uID0gIkJlc3QgVHVuZSIsCiAgICAJCQlkaWdpdHMgPSAzKQpgYGAKCkFob3JhIHZlbW9zIGNvbW8gaGEgbWVqb3JhZG8gZWwgdHVuZW8gZGVwZW5kaWVuZG8gZGVsIHZhbG9yIGRlIHN1ICpzaWdtYSouCgpgYGB7cn0KZ2dwbG90KHN1YnNldCgKCXN2bV9yYWRfcmVzdWx0JHJlc3VsdHMpLAphZXMoeD1zaWdtYSwgCgkJeT1BY2N1cmFjeSwKCQljb2w9ZmFjdG9yKEMpKSkgKyBmYWNldF93cmFwKEN+LiwgbmNvbCA9IDMpICsKCXhsYWIoIlNpZ21hIikgKyBndWlkZXMoY29sPWd1aWRlX2xlZ2VuZCh0aXRsZT0iQyIpKSArIHNjYWxlX2NvbG9yX2JyZXdlcihwYWxldHRlID0gIlBhaXJlZCIpICsKZ2VvbV9zdGVwKCkgKyBnZW9tX3BvaW50KHNpemU9MC44LCBzaGFwZT0zKSArCgl0aGVtZV9taW5pbWFsKGJhc2VfZmFtaWx5ID0gJ1RpbWVzJykKYGBgCgpTZSBwdWVkZSBvYnNlcnZhciBjb21vIGEgcGFydGlyIGRlIHVuIGNvc3RlIGRlIHJlZ3VsYXJpemFjacOzbiBpZ3VhbCBhIDEsIGxhIG1heGltYSBhY2N1cmFjeSBzZSBwdWVkZSBhbGNhbnphciBjb24gdW4gdmFsb3IgZGUgc2lnbWEgZW50cmUgMiB5IDUuCgojIyMjIE1vZGVsbyBGaW5hbApgYGB7ciwgZXZhbD1GQUxTRX0Kc2V0LnNlZWQoMTEyKSAgIyBmb3IgcmVwcm9kdWNpYmlsaXR5CnN2bV9yYWQgPC0gdHJhaW4oCiAgZmFjdG9yKGF0dHJpdGlvbikgfiAuLCAKICBkYXRhID0gdXBUcmFpbjIsCiAgbWV0aG9kID0gInN2bVJhZGlhbCIsICAgICAgCiAgdmVyYm9zZSA9IEZBTFNFLAogIHRyQ29udHJvbCA9IGNvbnRyb2wsCiAgdHVuZUdyaWQgPSBleHBhbmQuZ3JpZCgKICAJQz1jKDMwKSwKIHNpZ21hPWMoMSkpKQpgYGAKCmBgYHtyLCBmaWcuc2hvdz0iaG9sZCIsIG91dC53aWR0aD0iNTAlIiwgZmlnLmNhcD0iU1ZNIFJhZGlhbCIsIGZpZy5zdWJjYXA9YygiTWF0cml6IGRlIENvbmZ1c2lvbiIsICJST0MgY3VydmUiKX0KZHJhd19jb25mdXNpb25fbWF0cml4KAoJY29uZnVzaW9uTWF0cml4KHN2bV9yYWQkcHJlZCRwcmVkLAoJCQkJCQkJCQlzdm1fcmFkJHByZWQkb2JzKSwKCSIjRDZEQkY1IiwiIzJFNDVCOCIpCgpyb2Moc3ZtX3JhZCRwcmVkJG9icywKCQlzdm1fcmFkJHByZWQkWWVzKSAlPiUKICAgIGdncm9jKGNvbG91ciA9ICIjMkU0NUI4Iiwgc2l6ZSA9IDAuNSkgKwoJYW5ub3RhdGUoInRleHQiLCB4ID0gMC4wOCwgeSA9IDAuNzUsCiAgICBsYWJlbCA9ICJib2xkKEFVQyk6IDAuOTk4IiwgZmFtaWx5ID0gIlRpbWVzIiwgcGFyc2UgPSBUUlVFKSArIGhyYnJ0aGVtZXM6OnRoZW1lX2lwc3VtKHRpY2tzID0gVCwKICAgIGJhc2VfZmFtaWx5ID0gIlRpbWVzIiwgYmFzZV9zaXplID0gMTApICsgbGFicyh0aXRsZSA9ICJST0MgQ3VydmUiLCBzdWJ0aXRsZSA9ICJTVk0gUmFkaWFsIikgKwogICAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiLCBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9yZWN0KGNvbG9yID0gIiMzYjQ1NGEiKSwKICAgICAgICB0aXRsZSA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiKSwgYXhpcy50aXRsZS55ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCksCiAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiwgY29sb3VyID0gIiMzRDNEM0QiLCBzaXplID0gMTApKQpgYGAKCkVzdGEgKm1hcXVpbmEgZGUgc3Vwb3J0ZSB2ZWN0b3JpYWwgY29uIGtlcm5lbCByYWRpYWwqIGFsY2FuemEgbGFzIG1lam9yZXMgbcOpdHJpY2FzIHNvYnJlIGVzdG9zIGRhdG9zOiAKCi0gQWNjdXJhY3kgMC45OTggCi0gKkFVQyogMC45OTgKCkhheSBxdWUgdGVuZXIgZW4gY3VlbnRhIHNpZW1wcmUgcXVlIGVzdG9zIGRhdG9zIHNvbiBmaWN0aWNpb3MsIGVudG9uY2VzIG5vIGhhIHNpZG8gdGFuIGRpZsOtY2lsIGVuY29udHJhciBtw6l0cmljYXMgdGFuIGFsdGEuCgojIyBCYWdnaW5nIFNWTQoKUGFyYSBlbXBsZWFyIGVsICoqYmFnZ2luZyoqIGRlbCBTVk0gaGUgY29uc3RydWlkbyB1bmEgZnVuY2nDs24gcXVlIG1lIGhhIHBlcm1pdGlkbyBoYWNlciBsYXMgY29tcHV0YWNpb25lcyBtYXMgcsOhcGlkYXMgYSB0cmF2w6lzIGRlIHVuIGJ1Y2xlIHBhcmFsZWxpemFkbyBxdWUgY29tcHV0YSB0YW50YXMgbXVlc3RyYXMgY3VhbnRhcyBxdWllcm8sIGFqdXN0YW5kbyBtaSBtZWpvciBtb2RlbG8gU1ZNIGxpbmVhbCBpZGVudGlmaWNhZG8gYW50ZXMgeSBxdWUgZGV2dWVsdmUgdW4gdmVjdG9yIGNvbiBsYXMgcHJlZGljY2lvbmVzIHByb21lZGlhZGFzIGEgcGFydGlyIGRlIGxhcyBmaWxhcyBkZSBsYXMgZGlzdGludGFzIHByZWRpY2Npb25lcyBkZSBsb3MgbW9kZWxvcyBzb2JyZSBjYWRhIG11ZXN0cmEuCgpMbyBpbnRlcmVzYW50ZSBkZSBlc3RhIGltcGxlbWVudGFjacOzbiBlcyBxdWUgcHVlZG8gY29udHJvbGFyIGNhZGEgcGFyw6FtZXRybyBkZSBlc3RlIGFsZ29yaXRtbyB5IHRhbWJpw6luIHB1ZWRvIHN1c3RpdHVpciBlbCAqd2VhayBsZWFybmVyKiAoZW4gZXN0ZSBjYXNvLCBlcyBlbCAqU1ZNIGxpbmVhbCogZGVsIHBhcXVldGUgYGUxMDcxYCkgcGFyYSByZWFsaXphciBlbCBiYWdnaW5nIGNvbiBvdHJvcyBtb2RlbG9zLiBBZGVtw6FzIGxhIGltcGxlbWVudGFjacOzbiBlbiBwYXJhbGVsbyBwZXJtaXRlIGRlIHJlYWxpemFyIGVsIGFsZ29yaXRtbyByw6FwaWRhbWVudGUgeSBubyBwZXNhIGxhIGNhY2hlIGVuIGN1YW50byBzb2xvIGVsIHZlY3RvciBmaW5hbCBjb24gbGFzIHByZWRpY2Npb25lcyBlc3TDoSBndWFyZGFkby4gCgpgYGB7cn0KbGlicmFyeShlMTA3MSkKbGlicmFyeShkb1BhcmFsbGVsKQpsaWJyYXJ5KHRpY3RvYykKCkJhZ2dpbmdTVk1fTGluIDwtIGZ1bmN0aW9uKHRyYWluLCB0ZXN0LCBzYW1wbGVzLCBzaXplLCBjb3N0KXsKCnJlZ2lzdGVyRG9QYXJhbGxlbChtYWtlQ2x1c3RlcigzKSAtPiBjcHUpIAp0aWMoKQoKcHJlZGljdGlvbnMgPC0gZm9yZWFjaCgKCWljb3VudChzYW1wbGVzKSwgICNpdGVyYXppb25pCgkucGFja2FnZXMgPSAiZTEwNzEiLCAKCS5jb21iaW5lID0gY2JpbmQgICNhemlvbmUgZGEgZmFyZSBwZXIgb2duaSBpdGVyYXppb25lCiNhZGVzc28sIHBhcmFsbGVsIGNvbXB1dGluZyBwZXIgdmVsb2NpenphcmUgaWwgbG9vcAopICVkb3BhciUgewoJIyBib290c3RyYXAgc2FtcGxlIGRlaSBkYXRpIHRyYWluCglpbmRleCA8LSBzYW1wbGUuaW50KG49bnJvdyh0cmFpbiksIAoJCQkJCQkJCQkJICNxdWkgZGVmaW5pYW1vIHF1YW50ZSBvc3NlcnZhemlvbmkgb2duaSBzYW1wbGUKCQkJCQkJCQkJCSBzaXplID0gZmxvb3Ioc2l6ZSpucm93KHRyYWluKSksCgkJCQkJCQkJCQkgcmVwbGFjZSA9IFRSVUUpCglzYW1wbGUgPC0gdHJhaW5baW5kZXgsIF0gIAoJCgkjIGZpdCBtb2RlbCBwZXIgaWwgc2FtcGxlCgliYWdnZWRfc3ZtIDwtIHN2bShmYWN0b3IoYXR0cml0aW9uKX4uLCAKCQkJCQkJCQkJCWRhdGE9c2FtcGxlLCAKCQkJCQkJCQkJCWtlcm5lbCA9ImxpbmVhciIsCgkJCQkJCQkJCQlwcm9iYWJpbGl0eT1UUlVFLAoJCQkJCQkJCQkJIyBsYSBmdW56aW9uZSBwcmVuZGUgdmFsb3JlICdjb3N0JwoJCQkJCQkJCQkJY29zdCA9IGNvc3QpCgkKCSNjYWxjb2xpYW1vIGFkZXNzbyBsZSBwcmVkaXppb25pIHN1bCBURVNUCglhcy5kYXRhLmZyYW1lKGF0dHIoCgkJcHJlZGljdChiYWdnZWRfc3ZtLAoJCQkJCQluZXdkYXRhPXRlc3QsIAoJCQkJCQlwcm9iYWJpbGl0eT1UUlVFKSwgInByb2IiKSkkWWVzCn0KdG9jKCkgIzMgc2VjICg0KQpzdG9wQ2x1c3RlcihjcHUpCgojUml0b3JuYSB1biB2ZXR0b3JlIGNvbiBsZSBtZWRpZSBwZXIgZmlsYSBkaSBvZ25pIG9zc2VydmF6aW9uZQpyZXR1cm4ocm93TWVhbnMocHJlZGljdGlvbnMpKQp9CmBgYAoKCkFob3JhIGltcGxlbWVudG8gbGEgZnVuY2nDs24gY29uIDEwMDAgc3VibXVlc3RyYXMsIGhlIHJlYWxpemFkbyB2YXLDrWFzIHBydWViYXMgY29uIGRpZmVyZW50ZXMgZGltZW5zaW9uZXMgZGUgbXVlc3RyYXMgeSBlc3RlIHZhbG9yIGRlYmVyw61hIHNlciBzdWZpY2llbnRlIHBhcmEgZXN0YWJsZWNlciBlbCBlcnJvci4gRWwgdGFtYcOxbyBkZSBjYWRhIHN1Ym11ZXN0cmEgZXMgZWwgNjAlIGRlIGxvcyBkYXRvcyBpbmljaWFsZXMuIAoKYGBge3IsIGV2YWw9RkFMU0V9CmJhZ2dpbmdfc3ZtIDwtIEJhZ2dpbmdTVk1fTGluKAoJdXBUcmFpbjIsIFRlc3QyLCAKCXNhbXBsZXMgPSAxMDAwLAoJc2l6ZSA9IDAuNiwgCgljb3N0ID0gMC4wMSkKYGBgCgpBaG9yYSBjb21wYXJhbW9zIGVsIG1vZGVsbyBmaW5hbCAqZW5zZW1ibGUqIGNvbiBlbCBtb2RlbG8gZGUgKlNWTSogbGluZWFsLCBhIHRyYXbDqXMgZGUgbGEgY3VydmEgUk9DLgoKYGBge3IgQl9ST0MsIGV2YWw9RkFMU0V9CnJvYy5saXN0IDwtIGxpc3QoCglgQmFnZ2luZyBTVk1gID0gcm9jKFRlc3QyJGF0dHJpdGlvbiwgYmFnZ2luZ19zdm0pLAoJU1ZNID0gcm9jKHN2bV9saW4kcHJlZCRvYnMsCgkJCQkJCXN2bV9saW4kcHJlZCRZZXMpCikKCgpjaS5saXN0IDwtIGxhcHBseShsaXN0KAoJYEJhZ2dpbmcgU1ZNYCA9IHJvYyhUZXN0MiRhdHRyaXRpb24sIGJhZ2dpbmdfc3ZtKSwKCVNWTSA9IHJvYyhzdm1fbGluJHByZWQkb2JzLAoJCQkJCQlzdm1fbGluJHByZWQkWWVzKQopLApjaS5zZSwKc3BlY2lmaWNpdGllcyA9IHNlcSgwLCAxLCBsID0gMjUpKQoKZGF0LmNpLmxpc3QgPC0gbGFwcGx5KGNpLmxpc3QsIGZ1bmN0aW9uKGNpb2JqKQoJZGF0YS5mcmFtZSgKCQl4ID0gYXMubnVtZXJpYyhyb3duYW1lcyhjaW9iaikpLAoJCWxvd2VyID0gY2lvYmpbLCAxXSwKCQl1cHBlciA9IGNpb2JqWywgM10KCSkpCmBgYApgYGB7ciBCX1NWTV9ST0MsIH0KcCA8LSBnZ3JvYyhyb2MubGlzdCwKCQkJCQkgc2l6ZSA9IDAuNywgYWxwaGEgPSAwLjkpICsKCXNjYWxlX2NvbG9yX21hbnVhbChuYW1lID0gTlVMTCwKCQkJCQkJCQkJCSB2YWx1ZXMgPSBjKAoJCQkJCQkJCQkJIAlgQmFnZ2luZyBTVk1gID0gIiM5RUE5RTAiLAoJCQkJCQkJCQkJIAlTVk0gPSAiIzJFNDVCOCIpKSArCglocmJydGhlbWVzOjp0aGVtZV9pcHN1bSh0aWNrcyA9IFQsCgkJCQkJCQkJCQkJCQliYXNlX2ZhbWlseSA9ICJUaW1lcyIsCgkJCQkJCQkJCQkJCQliYXNlX3NpemUgPSAxMCkgKyBsYWJzKHRpdGxlID0gIlJPQyBDdXJ2ZSIsIHN1YnRpdGxlID0gIkNvbXBhcmluZyBST0MgY3VydmVzIGZvciBzaW5nbGUgbW9kZWxzIGFuZCBTdGFja2luZyIpICsKCWdlb21fYWJsaW5lKAoJCXNsb3BlID0gMSwKCQlpbnRlcmNlcHQgPSAxLAoJCWxpbmV0eXBlID0gImRhc2hlZCIsCgkJYWxwaGEgPSAwLjcsCgkJY29sb3IgPSAiZ3JleSIKCSkgKwoJeWxhYigiU2Vuc2l0aXZpdHkiKSArIHhsYWIoIlNwZWNpZmljaXR5IikgKyBhbm5vdGF0ZSgKCQkicmVjdCIsCgkJeG1pbiA9IDAuMjUsCgkJeG1heCA9IDAsCgkJeW1pbiA9IDAsCgkJeW1heCA9IDAuMjUsCgkJZmlsbCA9ICJ3aGl0ZSIKCSkgKyBhbm5vdGF0ZSgKCQkidGV4dCIsCgkJeCA9IDAuMTMsCgkJeSA9IDAuMTgsCgkJbGFiZWwgPSAiYm9sZChBVUMpOiAwLjgxIiwKCQljb2xvdXIgPSAiIzlFQTlFMCIsCgkJZmFtaWx5ID0gIlRpbWVzIiwKCQlwYXJzZSA9IFRSVUUKCSkgKwoJYW5ub3RhdGUoCgkJInRleHQiLAoJCXggPSAwLjEzLAoJCXkgPSAwLjA4LAoJCWNvbG91ciA9ICIjMkU0NUI4IiwKCQlsYWJlbCA9ICJib2xkKEFVQyk6IDAuODIiLAoJCWZhbWlseSA9ICJUaW1lcyIsCgkJcGFyc2UgPSBUUlVFCgkpICsgdGhlbWUoCgkJYXNwZWN0LnJhdGlvID0gMC41LAoJCXBsb3QubWFyZ2luID0gdW5pdChjKDEsCgkJCQkJCQkJCQkJCSAxLCAwLjEsIDAuMSksICJjbSIpLAoJCWxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSwKCQlsZWdlbmQudGV4dCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIpLAoJCWxlZ2VuZC5wb3NpdGlvbiA9ICJ0b3AiLAoJCXBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoY29sb3IgPSAiIzNiNDU0YSIpLAoJCXRpdGxlID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIpCgkpCgpmb3IgKGkgaW4gMToyKSB7CglpZiAoaSA9PSAxKSB7CgkJY29sID0gJyMyRTQ1QjgnCgkgIGEgPSAwLjIgfQoJZWxzZSB7CgkJY29sID0gIiMyRTQ1QjgiCgkJYSA9IDAuNX0KCXAgPC0gcCArIGdlb21fcmliYm9uKAoJCWRhdGEgPSBkYXQuY2kubGlzdFtbaV1dLAoJCWFlcyh4ID0geCwgeW1pbiA9IGxvd2VyLCB5bWF4ID0gdXBwZXIpLAoJCWZpbGwgPSBjb2wsCgkJYWxwaGEgPSBhLAoJCWluaGVyaXQuYWVzID0gRgoJKQp9CnAKYGBgCgpTZSBwdWVkZSBvYnNlcnZhciBxdWUsIGF1bnF1ZSBsb3MgdmFsb3JlcyBkZSBsYSAqw6FyZWEgYWJham8gZGUgbGEgY3VydmEgUk9DKiBlc3TDoW4gcGFyZWNpZG9zLCBlbCBtb2RlbG8gKmVuc2VtYmxlKiBubyBhcG9ydGEgbWVqb3LDrWFzIGEgZWwgbW9kZWxvIHNpbXBsZS4gIAoKCmBgYHtyLCBldmFsPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQojIEdldCBhY2N1cmFjeSAKY29uZnVzaW9uTWF0cml4KGFzLmZhY3RvcigKCWlmZWxzZShiYWdnaW5nX3N2bSA+PSAuNSwgCgkJCQkgJ1llcycsICdObycpKSwKCVRlc3QyJGF0dHJpdGlvbikKYGBgCgoKIyMgQm9vc3RpbmcKUGFyYSByZWFsaXphciBlbCAqKmJvb3N0aW5nKiogZWwgZmx1am8gZGUgdHJhYmFqbyBlcyBwYXJlY2lkbyBhIGxvcyBkZW3DoXMuIFNlIGVtcGllemEgY29uIGVsIHR1bmVvLgoKIyMjIFR1bmVvIGRlbCBNb2RlbG8KYGBge3IsIGV2YWw9RkFMU0V9CnJlZ2lzdGVyRG9QYXJhbGxlbChtYWtlQ2x1c3RlcigzKSAtPiBjcHUpIAp0aWMoKQpzZXQuc2VlZCgxMTIpICAjIGZvciByZXByb2R1Y2liaWxpdHkKYm9vc3RfcmVzdWx0IDwtIHRyYWluKAogIGZhY3RvcihhdHRyaXRpb24pfi4sIAogIGRhdGEgPSB1cFRyYWluMiwKICBtZXRob2QgPSAiQWRhQm9vc3QuTTEiLCAgICAKICB2ZXJib3NlID0gRkFMU0UsCiAgdHJhaW5Db250cm9sID0gY29udHJvbCwKICB0dW5lR3JpZCA9IGV4cGFuZC5ncmlkKAogIAltZmluYWwgPSBjKDMsNSw4LDEwLDE1LDMwLDUwLDEwMCksCiAgICBtYXhkZXB0aCA9YygxLDUsMTAsMTUsMzAsNTApLAogIAljb2VmbGVhcm49YygnQnJlaW1hbicpKSkKdG9jKCkgIzEwLjcxIG1pbiBlbGFwc2VkCnN0b3BDbHVzdGVyKGNwdSkKYGBgCgpPYnNlcnZhbW9zIGVuIGxhIHRhYmxhIGxvcyByZXN1bHRhZG9zOgpgYGB7cn0KY2JpbmQoYm9vc3RfcmVzdWx0JGJlc3RUdW5lLCBnZXRUcmFpblBlcmYoYm9vc3RfcmVzdWx0KSkgJT4lCiAgICBrYWJsZShmb3JtYXQgPSAiaHRtbCIsIGJvb3RzdHJhcF9vcHRpb25zID0gImJhc2ljIixjYXB0aW9uID0gIkJlc3QgVHVuZSIsZGlnaXRzID0gMykgCgpwbG90KGJvb3N0X3Jlc3VsdCwgCW91dHB1dCA9ICJnZ3Bsb3QiLAoJImxpbmUiLAoJcGFyLnNldHRpbmdzID0gbGlzdCgKCQlzdXBlcnBvc2UubGluZSA9IGxpc3QoCgkJCWx3ZCA9IDEsCgkJCWNvbCA9IGMoIiNENkRCRjUiLCAiI0I1QkZFNyIsICIjOTRBNEQ5IiwgIiM3Mzg4Q0MiLCAiIzUyNkRCRSIsICIjMzI1MkIxIikpLAoJCXN1cGVycG9zZS5zeW1ib2wgPSBsaXN0KAoJCQlwY2ggPSAxOSwKCQkJY2V4ID0gMC42LAoJCQljb2wgPSBjKCIjRDZEQkY1IiwgIiNCNUJGRTciLCAiIzk0QTREOSIsICIjNzM4OENDIiwgIiM1MjZEQkUiLCAiIzMyNTJCMSIpKSwKCQlzdHJpcC5iYWNrZ3JvdW5kID0gbGlzdChjb2wgPSAiI0Q2REJGNSIpKSkKYGBgCgpMb3MgbsO6bWVyb3MgZGUgKndlYWsgbGVhcm5lcnMqIHBhcmVjZSBlc3RhYmxlY2VyIGVsIGVycm9yIGFscmVkZWRvciBkZSAxMDAuIFVuIHRhbWHDsW8gZW50cmUgMTAgeSAzMCBwYXJlY2Ugc2VyIG5lY2VzYXJpbyBwYXJhIGFsY2FuemFyIGxhIG3DoXhpbWEgYWNjdXJhY3kuCgojIyMgTW9kZWxvIEZpbmFsCmBgYHtyLCBldmFsPUZBTFNFfQpib29zdCA8LSB0cmFpbigKICBmYWN0b3IoYXR0cml0aW9uKSB+IC4sIAogIGRhdGEgPSB1cFRyYWluMiwKICBtZXRob2QgPSAiQWRhQm9vc3QuTTEiLCAgICAKICB2ZXJib3NlID0gRkFMU0UsCiAgdHJDb250cm9sID0gY29udHJvbCwKICB0dW5lR3JpZCA9IGV4cGFuZC5ncmlkKAogIAltZmluYWwgPSBjKDEwMCksCiAgICBtYXhkZXB0aCA9YygxMCksCiAgCWNvZWZsZWFybj1jKCdCcmVpbWFuJykpKQpgYGAKCgpgYGB7ciwgZmlnLnNob3c9ImhvbGQiLCBvdXQud2lkdGg9IjUwJSIsIGZpZy5jYXA9IkJvb3N0aW5nIE1vZGVsbyBGaW5hbCIsIGZpZy5zdWJjYXA9YygiTWF0cml6IGRlIGNvbmZ1c2lvbiIsIkltcG9ydGFuY2lhIFZhcmlhYmxlcyIpfQoKZHJhd19jb25mdXNpb25fbWF0cml4KAogICAgY29uZnVzaW9uTWF0cml4KGJvb3N0JHByZWQkcHJlZCwKICAgIAkJCQkJCQkJYm9vc3QkcHJlZCRvYnMpLAogICAgIiNENkRCRjUiLCIjMkU0NUI4IikKCmFzX3RpYmJsZSh2aXA6OnZpKGJvb3N0KSkgJT4lCiAgICBmaWx0ZXIoSW1wb3J0YW5jZSA+IDIuNSkgJT4lCiAgICBtdXRhdGUoVmFyaWFibGUgPSBzdHJpbmdyOjpzdHJfdG9fdGl0bGUoVmFyaWFibGUpKSAlPiUKICAgIG11dGF0ZShWYXJpYWJsZSA9IHN0cl9yZXBsYWNlX2FsbChWYXJpYWJsZSwgIl8iLCAiICIpKSAlPiUKICAgIGdncGxvdChhZXMoeCA9IHJlb3JkZXIoVmFyaWFibGUsIEltcG9ydGFuY2UpLCB5ID0gSW1wb3J0YW5jZSkpICsgZ2djaGlja2xldDo6Z2VvbV9jaGlja2xldChhZXMoZmlsbCA9IEltcG9ydGFuY2UpLAogICAgcmFkaXVzID0gZ3JpZDo6dW5pdCg2LCAicHQiKSwgd2lkdGggPSAwLjgsIHNob3cubGVnZW5kID0gRikgKyBsYWJzKHggPSBOVUxMLAogICAgdGl0bGUgPSAiVmFyaWFibGUgSW1wb3J0YW5jZSBQbG90IEJvb3N0aW5nIikgKyBzY2FsZV95X2NvbnRpbnVvdXMocG9zaXRpb24gPSAicmlnaHQiKSArCiAgICBzY2FsZV9maWxsX2dyYWRpZW50KGxvdyA9ICIjRDZEQkY1IiwgaGlnaCA9ICIjMkU0NUI4IikgKyBjb29yZF9mbGlwKCkgKyBocmJydGhlbWVzOjp0aGVtZV9pcHN1bShiYXNlX2ZhbWlseSA9ICJUaW1lcyIsCiAgICBncmlkID0gIlgiKSArIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQodmp1c3QgPSAtMSksIHBsb3QubWFyZ2luID0gdW5pdChjKDAsCiAgICAxLCAxLCAwLjUpLCAiY20iKSkKYGBgCgpFbCBtb2RlbG8gZXMgbXV5IGJ1ZW5vLCB0aWVuZSB1biBhY2N1cmFjeSBkZSAwLjk3OCB5IHVuYSAqQVVDKiBkZSAwLjk5My4gVGFtYmnDqW4gbm9zIHByb3BvcmNpb25hIGxhcyBpbXBvcnRhbmNpYXMgZGUgbGFzIHZhcmlhYmxlczogCgotIEVsIHNhbGFyaW8gc2llbXByZSBlc3TDoSBhcnJpYmEgbGFzIGRlbcOhcyBwb3IgaW1wb3J0YW5jaWEsIGF1bnF1ZSBub3RhbW9zIGNvbiBlc3RlIGNvbmp1bnRvIHJlc3RyaW5naWRvIHF1ZSBsb3MgYcOxb3MgdG90YWwgdHJhYmFqb3MsIGxvcyBlbiBsYSBlbXByZXNhIHkgZWwgbnVtZXJvIGRlIGVtcHJlc2FzIHBvciBlbCBjdWFsIGhhcyB0cmFiYWpvIHRvbWFuIG3DoXMgaW1wb3J0YW5jaWEgY29tcGFyYWRvIGEgbG9zIG1vZGVsb3MgY29uIHRvZGFzIGxhcyB2YXJpYWJsZXMuCgojIyBTdGFja2luZwoKUGFyYSByZWFsaXphciBlbCAqKnN0YWNraW5nKiosIGhlIHV0aWxpemFkbyBlbCBwYXF1ZXRlIGBjYXJldEVuc2VibGVgLiBDb24gZXN0ZSBoZSB0ZW5pZG8gdmFyaW9zIHByb2JsZW1hcyBhbCBwcmluY2lwaW8sIHNvYnJlIHRvZG8gcG9yIGFqdXN0YXIgbG9zIG1vZGVsb3MgKmxlYXJuZXJzKiBjb24gbG9zIHBhcsOhbWV0cm9zIHF1ZSBxdWVyw61hLiBBbCBmaW5hbCBoZSBlbmNvbnRyYWRvIHVuYSBzb2x1Y2nDs246CgotIExhIGZ1bmNpw7NuIGBjYXJldFN0YWNrYCBhZG1pdGUgdGFtYmnDqW4gbW9kZWxvcyBlbnRyZW5hZG9zIHNlcGFyYWRhbWVudGUsIHNvbG8gc2kgZGVzcHXDqXMgc2UgY29udmllcnRlbiBlbiBsYSBjbGFzZSBgYXMuY2FyZXRMaXN0YC4KLSBQYXJhIHJlYWxpemFyIGVsICpzdGFja2luZyogZXMgbmVjZXNhcmlvIGVzdGFibGVjZXIgdW4gY29udHJvbCBjb23Dum4gcGFyYSBsb3MgdmFyw61vIG1vZGVsb3MsIHF1ZSBlc3BlY2lmaWNhIGxvcyBpbmRleGVzIGRlIGxvcyAqZm9sZHMqIGRlIGxhIHZhbGlkYWNpw7NuIGNydXphZGEsIGVuIGN1YW50byB0aWVuZW4gcXVlIHNlciBjb211bmVzIHBhcmEgdG9kb3MgbG9zIG1vZGVsb3MuIAotIFNlIGVudHJlbmFuIGxvcyBtb2RlbG9zIGNvbiBgdHJhaW5gIGNvbW8gc2llbXByZSwgdHVuZWFuZG8gbyBmaWphbmRvIGxvcyBwYXLDoW1ldHJvcyDDs3B0aW1vcyBzaSB5YSBzZSBjb25vY2VuIHkgc2UganVudGFuIGVuIHVuYSBsaXN0YSBgYXMuY2FyZXRMaXN0YC4KLSBBbCBmaW5hbCBzZSByZWFsaXphIGVsICpzdGFja2luZyogc29icmUgZXN0YSBsaXN0YSwgZGVjaWRpZW5kbyBlbCBhbGdvcml0bW8gcGFyYSByZWFsaXphciBlbCAqZW5zZW1ibGUqIHkgZXMgcG9zaWJsZSB0dW5lYXJsby4gQXF1w60gdGFtYmnDqW4gZXMgaW1wb3J0YW50ZSBlc3BlY2lmaWNhciBlbCBpbmRpY2UgY29uIGxhIHJlc3BlY3RpdmEgdmFyaWFibGUgZGVwZW5kaWVudGUuIAoKYGBge3IsIGV2YWw9RkFMU0V9CmxpYnJhcnkoImNhcmV0RW5zZW1ibGUiKQoKc3Rja19jb250cm9sIDwtIHRyYWluQ29udHJvbCgKCQltZXRob2QgPSAiY3YiLAoJCW51bWJlciA9IDUsCgkJcmV0dXJuUmVzYW1wID0gImFsbCIsCgkJc2F2ZVByZWRpY3Rpb25zID0gImFsbCIsCgkJY2xhc3NQcm9icyA9IFRSVUUsCgkJaW5kZXggPSBjcmVhdGVNdWx0aUZvbGRzKAoJCQl1cFRyYWluMiRhdHRyaXRpb24sIDUpKQoKIyBFbnRyZW5hbW9zIApnYm1fc3RjayA8LSB0cmFpbigKCQlmYWN0b3IoYXR0cml0aW9uKSB+IC4sCgkJZGF0YSA9IHVwVHJhaW4yLAoJCW1ldGhvZCA9ICJnYm0iLAoJCXRyQ29udHJvbCA9IHN0Y2tfY29udHJvbCwKCQl0dW5lR3JpZCA9IGV4cGFuZC5ncmlkKAoJCQlzaHJpbmthZ2UgPSBjKDEpLAoJCQluLm1pbm9ic2lubm9kZSA9IGMoMzUpLAoJCQluLnRyZWVzID0gYygxMDAwMCksCgkJCWludGVyYWN0aW9uLmRlcHRoID0gYygyKSksCgkJZGlzdHJpYnV0aW9uID0gImJlcm5vdWxsaSIsCgkJYmFnLmZyYWN0aW9uID0gMSwKCQl2ZXJib3NlID0gRkFMU0UpCgpzdm1SX3N0Y2sgPC0gdHJhaW4oCgkJZmFjdG9yKGF0dHJpdGlvbikgfiAuLAoJCWRhdGEgPSB1cFRyYWluMiwKCQltZXRob2QgPSAic3ZtUmFkaWFsIiwKCQl0ckNvbnRyb2wgPSBzdGNrX2NvbnRyb2wsCgkJdHVuZUdyaWQgPSBleHBhbmQuZ3JpZCgKCQkJQyA9IGMoMzApLCBzaWdtYSA9IGMoMSkpLAoJCXZlcmJvc2UgPSBGQUxTRSkKCnhnYm1fc3RjayA8LSB0cmFpbigKCWZhY3RvcihhdHRyaXRpb24pIH4gLiwKCWRhdGEgPSB1cFRyYWluMiwKCW1ldGhvZCA9ICJ4Z2JUcmVlIiwKCXRyQ29udHJvbCA9IHN0Y2tfY29udHJvbCwKCXR1bmVHcmlkID0gZXhwYW5kLmdyaWQoCgkJZXRhID0gYygwLjA1KSwKCQltaW5fY2hpbGRfd2VpZ2h0ID0gYygzNSksCgkJbnJvdW5kcyA9IGMoMTAwMDApLAoJCWdhbW1hID0gYygwKSwKCQlzdWJzYW1wbGUgPSBjKDEpLAoJCW1heF9kZXB0aCA9IGMoNiksCgkJY29sc2FtcGxlX2J5dHJlZSA9IGMoMSkpLAoJdmVyYm9zZSA9IEZBTFNFKQoKbW9kZWxfbGlzdCA9IGFzLmNhcmV0TGlzdChsaXN0KAoJInhnYm0iPXhnYm1fc3RjaywgCgkic3ZtX3JhZCI9c3ZtUl9zdGNrLAoJImdibSI9Z2JtX3N0Y2spKQoKIyBTdGFja2luZwpzdGFja2luZyA8LSBjYXJldFN0YWNrKAoJbW9kZWxfbGlzdCwKCW1ldGhvZCA9ICJnYm0iLAoJdmVyYm9zZSA9IEZBTFNFLAoJbWV0cmljPSJST0MiLAoJdHVuZUdyaWQgPSBleHBhbmQuZ3JpZCgKCQkJc2hyaW5rYWdlID0gYygwLjUsMSksCgkJCW4ubWlub2JzaW5ub2RlID0gYygxNSwzNSw1MCksCgkJCW4udHJlZXMgPSBjKDUwMDAsIDEwMDAwKSwKCQkJaW50ZXJhY3Rpb24uZGVwdGggPSBjKDIpKSwKCXRyQ29udHJvbCA9IHRyYWluQ29udHJvbCgKCQltZXRob2QgPSAnY3YnLAoJCW51bWJlciA9IDUsCgkJc2F2ZVByZWRpY3Rpb25zID0gImZpbmFsIiwKCQljbGFzc1Byb2JzID0gVFJVRSwKCQlpbmRleCA9IGNyZWF0ZVJlc2FtcGxlKHVwVHJhaW4yJGF0dHJpdGlvbikpKQpgYGAKClZlbW9zIHVuYSBjdXJ2YSBST0MgcGFyYSBjb21wYXJhciBsb3MgcmVzdWx0YWRvczogCmBgYHtyfQpsaXN0KAoJIlNWTSBSYWQiID0gcm9jKHN2bVJfc3RjayRwcmVkJG9icywKCQkJCQkJCQkJc3ZtUl9zdGNrJHByZWQkWWVzKSwKCSJHQk0iID0gcm9jKGdibV9zdGNrJHByZWQkb2JzLAoJCQkJCQkJZ2JtX3N0Y2skcHJlZCRZZXMpLAoJIlhHQk0iID0gcm9jKHhnYm1fc3RjayRwcmVkJG9icywKCQkJCQkJCSB4Z2JtX3N0Y2skcHJlZCRZZXMpLAoJIlN0YWNraW5nIiA9IHJvYyhzdGFja2luZyRlbnNfbW9kZWwkcHJlZCRvYnMsCgkJCQkJCQkJCSBzdGFja2luZyRlbnNfbW9kZWwkcHJlZCRZZXMpKSAlPiUKCWdncm9jKHNpemUgPSAwLjcsIGFscGhhID0gMC45KSArCglzY2FsZV9jb2xvcl9tYW51YWwoCgkJbmFtZSA9IE5VTEwsCgkJdmFsdWVzID0gYygKCQkJYFNWTSBSYWRgID0gIiMyRTQ1QjgiLAoJCQlHQk0gPSAiIzY2NzdDQyIsCgkJCWBYR0JNYCA9ICIjOUVBOUUwIiwKCQkJU3RhY2tpbmcgPSAiI0Q2REJGNSIKCQkpCgkpICsKCWhyYnJ0aGVtZXM6OnRoZW1lX2lwc3VtKHRpY2tzID0gVCwKCQkJCQkJCQkJCQkJCWJhc2VfZmFtaWx5ID0gIlRpbWVzIiwKCQkJCQkJCQkJCQkJCWJhc2Vfc2l6ZSA9IDEwKSArCglsYWJzKHRpdGxlID0gIlJPQyBDdXJ2ZSIsCgkJCSBzdWJ0aXRsZSA9ICJDb21wYXJpbmcgUk9DIGN1cnZlcyBmb3Igc2luZ2xlIG1vZGVscyBhbmQgU3RhY2tpbmciKSArCgl5bGFiKCJTZW5zaXRpdml0eSIpICsgeGxhYigiU3BlY2lmaWNpdHkiKSArCglhbm5vdGF0ZSgKCQkicmVjdCIsCgkJeG1pbiA9IDAuMjUsCgkJeG1heCA9IDAsCgkJeW1pbiA9IDAsCgkJeW1heCA9IDAuMzcsCgkJZmlsbCA9ICJ3aGl0ZSIKCSkgKwoJYW5ub3RhdGUoCgkJInRleHQiLAoJCXggPSAwLjEzLAoJCXkgPSAwLjI2LAoJCWxhYmVsID0gImJvbGQoQVVDKTogMC45OSIsCgkJY29sb3VyID0gIiMyRTQ1QjgiLAoJCWZhbWlseSA9ICJUaW1lcyIsCgkJcGFyc2UgPSBUUlVFCgkpICsKCWFubm90YXRlKAoJCSJ0ZXh0IiwKCQl4ID0gMC4xMywKCQl5ID0gMC4xOSwKCQljb2xvdXIgPSAiIzY2NzdDQyIsCgkJbGFiZWwgPSAiYm9sZChBVUMpOiAwLjk5IiwKCQlmYW1pbHkgPSAiVGltZXMiLAoJCXBhcnNlID0gVFJVRQoJKSArCglhbm5vdGF0ZSgKCQkidGV4dCIsCgkJeCA9IDAuMTMsCgkJeSA9IDAuMTIsCgkJY29sb3VyID0gIiM5RUE5RTAiLAoJCWxhYmVsID0gImJvbGQoQVVDKTogMC45NiIsCgkJZmFtaWx5ID0gIlRpbWVzIiwKCQlwYXJzZSA9IFRSVUUKCSkgKwoJYW5ub3RhdGUoCgkJInRleHQiLAoJCXggPSAwLjEzLAoJCXkgPSAwLjA1LAoJCWNvbG91ciA9ICIjRDZEQkY1IiwKCQlsYWJlbCA9ICJib2xkKEFVQyk6IDAuNTEiLAoJCWZhY2UgPSAnYm9sZCcsCgkJZmFtaWx5ID0gIlRpbWVzIiwKCQlwYXJzZSA9IFRSVUUKCSkgKwoJdGhlbWUoCgkJYXNwZWN0LnJhdGlvID0gMC41LAoJCXBsb3QubWFyZ2luID0gdW5pdChjKDEsIDEsIDAuMSwgMC4xKSwgImNtIiksCgkJbGVnZW5kLnRpdGxlID0gZWxlbWVudF9ibGFuaygpLAoJCWxlZ2VuZC50ZXh0ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCksCgkJbGVnZW5kLnBvc2l0aW9uID0gInRvcCIsCgkJcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChjb2xvciA9ICIjM2I0NTRhIiksCgkJdGl0bGUgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIikKCSkKYGBgCkNvbW8gc2UgcHVlZGUgb2JzZXJ2YXIgZGUgZXN0ZSBncsOhZmljbyBjb21wYXJhdGl2byBkZSBsYXMgY3VydmFzIFJPQywgZWwgc3RhY2tpbmcgbm8gaGEgYXBvcnRhZG8gbWVqb3JhcyBhIGxvcyBtb2RlbG8gc29sb3MuIAoKQWRlbcOhcyBzZSBwdWVkZSBub3RhciBxdWUgbG9zIG1vZGVsb3MgZGUgKipHQk0qKiB5ICoqWEdCTSoqLCBlc3RhIHZleiBhanVzdGFkb3MgcG9yIGVsIGNvbmp1bnRvIGRlIGRhdG9zIGNvbiBsYXMgdmFyaWFibGVzIG3DoXMgaW5mbHV5ZW50ZXMsIHNvbiBtw6FzIHByZWNpc29zIGRlIGxvcyBtaXNtb3MgYWp1c3RhZG9zIGNvbiB0b2RhcyBsYXMgdmFyaWFibGVzLgoKIyBDb25jbHVzaW9uIHstfQoKRW4gY29uY2x1c2nDs24sIG9ic2VydmFtb3MgZXN0ZSBncsOhZmljbyBjb24gbG9zIGRpc3RpbnRvcyB2YWxvcmVzIGRlICphY2N1cmFjeSogc2Vnw7puIGNhZGEgdMOpY25pY2EuCgpgYGB7cn0KZGF0YS5mcmFtZSgKCW1vZGVsID0gYygnU3RhY2tpbmcnLCAnQmFnZ2luZyBTVk0nLAoJCQkJCQknU1ZNIExpbicsICdYR2Jvb3N0JywKCQkJCQkJICdTVk0gUG9saScsICdHQk0nLCAKCQkJCQkJJ0Jvb3N0aW5nJywgJ1NWTSBSYWQnICksCglhY2N1cmFjeSA9IGMoNDcsIDc2LAoJCQkJCQkJIDc3LCA5MiwgCgkJCQkJCQkgOTUsOTYsCgkJCQkJCQkgOTgsOTkpCikgJT4lCglhcGV4KHR5cGUgPSAicmFkaWFsQmFyIiwgaGVpZ2h0ID0gNTAwLAoJCQkgYWVzKHggPSBtb2RlbCwgeSA9IGFjY3VyYWN5KSkgICU+JQoJYXhfbGFicyh0aXRsZSA9ICJNb2RlbCBhY2N1cmFjeSBmb3IgTUwgbW9kZWxzIiwKCQkJCQlzdWJ0aXRsZSA9ICJPbiBzZWxlY3RlZCB2YXJpYWJsZXMgYmFzZWQgb24gb3ZlcmFsbCBpbXBvcnRhbmNlLlxuQ2xpY2sgb24gdGhlIGJhciB0byBzZWUgcmVzdWx0cyIpICU+JQoJYXhfdGl0bGUoc3R5bGUgPSBsaXN0KGZvbnRTaXplID0gIjE5cHgiKSkgJT4lCglheF9zdWJ0aXRsZShzdHlsZSA9IGxpc3QoZm9udFNpemUgPSAiMTRweCIsIGNvbG9yID0gIiNCREJEQkQiKSkgJT4lCiAgYXhfY29sb3JzKAogIAlwYWludGVyOjpQYWxldHRlKCIjRDZEQkY1IiwgIiMzMjUyQjEiLCAxMCkpCmBgYAoKPGJyPgo8ZGl2IHN0eWxlPSJiYWNrZ3JvdW5kLWNvbG9yOiNENkRCRjU7IGJvcmRlci1yYWRpdXM6IDEwcHg7IHBhZGRpbmc6IDIwcHg7Ij4KRWwgbW9kZWxvIGdhbmFkb3IgaGEgc2lkbyBlbCAqKlNWTSBSYWRpYWwqKiBjb24gdW4gYWNjdXJhY3kgZGVsIDk5JSBhdW5xdWUgZWwgKipCb29zdGluZyoqIGVzdMOhIGp1c3RvIGRldHLDoXMgY29uIDk4JS4KPC9kaXY+CgojIFJlZmVyZW5jw61hcyB7LX0KCmBgYHtyIGdlbmVyYXRlQmlibGlvZ3JhcGh5LCByZXN1bHRzPSJhc2lzIiwgZWNobz1GQUxTRX0KbGlicmFyeSgia25pdGNpdGF0aW9ucyIpCm9wdGlvbnMoImNpdGF0aW9uX2Zvcm1hdCIgPSAicGFuZG9jIikKY2l0ZV9vcHRpb25zKHN0eWxlID0gIm1hcmtkb3duIixoeXBlcmxpbmsgPSBUUlVFKQpyZWFkLmJpYnRleCgiZmlsZXMvc3Rlc3VyYS9rbml0Y2l0YXRpb25zLmJpYiIpCmBgYCA=
 

Un Trabajo desarrollado por Alessio Crisafulli Carpani

alecrisa@ucm.es