Trabajo sobre los datos de Kaggle de Titanic*

          used  (Mb) gc trigger  (Mb) max used  (Mb)
Ncells 3104495 165.8    5437717 290.5  5437717 290.5
Vcells 6834011  52.2   14786712 112.9 12255457  93.6

1 Preparación de los datos

# Carga de datos
train <- read_csv("C:/Users/fede_/Desktop/Maestria/6- Enfoque Estadistico del aprendizaje/Practico Sabados/titanic_complete_train.csv")
Parsed with column specification:
cols(
  PassengerId = col_double(),
  Survived = col_double(),
  Pclass = col_double(),
  Name = col_character(),
  Sex = col_character(),
  Age = col_double(),
  SibSp = col_double(),
  Parch = col_double(),
  Ticket = col_character(),
  Fare = col_double(),
  Cabin = col_character(),
  Embarked = col_character()
)

a.Leer archivo y mostrar su estructura<br

El archivo se compone de 891 observaciones y 12 variables. La clase está en la columna Survived.

#Muestro estructura del dataset
glimpse(train)
Observations: 891
Variables: 12
$ PassengerId <dbl> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,...
$ Survived    <dbl> 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0...
$ Pclass      <dbl> 3, 1, 3, 1, 3, 3, 1, 3, 3, 2, 3, 1, 3, 3, 3, 2, 3, 2, 3, 3, 2, 2, 3, 1, 3, 3, 3, 1...
$ Name        <chr> "Braund, Mr. Owen Harris", "Cumings, Mrs. John Bradley (Florence Briggs Thayer)", ...
$ Sex         <chr> "male", "female", "female", "female", "male", "male", "male", "male", "female", "f...
$ Age         <dbl> 22.00000, 38.00000, 26.00000, 35.00000, 35.00000, 26.50759, 54.00000, 2.00000, 27....
$ SibSp       <dbl> 1, 1, 0, 1, 0, 0, 0, 3, 0, 1, 1, 0, 0, 1, 0, 0, 4, 0, 1, 0, 0, 0, 0, 0, 3, 1, 0, 3...
$ Parch       <dbl> 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 1, 0, 0, 5, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 5, 0, 2...
$ Ticket      <chr> "A/5 21171", "PC 17599", "STON/O2. 3101282", "113803", "373450", "330877", "17463"...
$ Fare        <dbl> 7.2500, 71.2833, 7.9250, 53.1000, 8.0500, 8.4583, 51.8625, 21.0750, 11.1333, 30.07...
$ Cabin       <chr> NA, "C85", NA, "C123", NA, NA, "E46", NA, NA, NA, "G6", "C103", NA, NA, NA, NA, NA...
$ Embarked    <chr> "S", "C", "S", "S", "S", "Q", "S", "S", "S", "C", "S", "S", "S", "S", "S", "S", "Q...

b. Seleccion de varaibles.

Se seleccionan las variables PassengerId , Survived , Pclass , Sex , Age , SibSp , Parch, Fare y Embarked

train %<>% # Uso el operador de magrittr que reasigna a la variable tras transformar
  select(PassengerId, Survived, Pclass, Sex,
         Age, SibSp,Parch, Fare, Embarked)

c. Transformar las variables a factor.

train %<>% # Muto las variables elegidas a factor
  mutate_at(vars(Survived, Pclass, Embarked), as_factor)
#muestro nueva estructura
glimpse(train)
Observations: 891
Variables: 9
$ PassengerId <dbl> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,...
$ Survived    <fct> 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0, 0...
$ Pclass      <fct> 3, 1, 3, 1, 3, 3, 1, 3, 3, 2, 3, 1, 3, 3, 3, 2, 3, 2, 3, 3, 2, 2, 3, 1, 3, 3, 3, 1...
$ Sex         <chr> "male", "female", "female", "female", "male", "male", "male", "male", "female", "f...
$ Age         <dbl> 22.00000, 38.00000, 26.00000, 35.00000, 35.00000, 26.50759, 54.00000, 2.00000, 27....
$ SibSp       <dbl> 1, 1, 0, 1, 0, 0, 0, 3, 0, 1, 1, 0, 0, 1, 0, 0, 4, 0, 1, 0, 0, 0, 0, 0, 3, 1, 0, 3...
$ Parch       <dbl> 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 1, 0, 0, 5, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 5, 0, 2...
$ Fare        <dbl> 7.2500, 71.2833, 7.9250, 53.1000, 8.0500, 8.4583, 51.8625, 21.0750, 11.1333, 30.07...
$ Embarked    <fct> S, C, S, S, S, Q, S, S, S, C, S, S, S, S, S, S, Q, S, S, C, S, S, Q, S, S, S, C, S...
# analisis de na,ceros y estructura.
funModeling::status(train)

Al analizar la cantidad de ceros y NA por columna vemos que la única columna con valores faltantes, en el orden del 77% de las filas, es la de Cabina. Las filas de SibSp y Parch presentan una gran cantidad de valores 0.

d. Realizar un gráfico de ggpairs para las variables Survived, Pclass, Sex, Age y Fare e interpretarlo

train %>%
  select(Survived, Pclass, Sex, Age, Fare) %>% # Elijo variables 
  ggpairs(., mapping = aes(color = Survived), legend = c(1,1)) + # Grafico ggpairs abriendo por clase en el color
  theme(axis.text.x = element_text(angle = 90, hjust = 1)) +
  theme_grey()+
  theme(legend.position = "bottom")

Figuara 1: Grafico de GGpairs de variables seleccionadas por clase.

En la figura 1 podemos analizar la distribución de clase según las variables seleccionadas. En rojo están los pasajeros no superiviventes y en azul los que lo hicieron.

Se observa que la clase mayoritaria es la de no sobreviviente.

Pclass determina una diferencia importante entre los pasajeros de la 3er clase y los demás. Por un lado son mayoriatria y por otro lado también son los que menos sobrevivieron. Ser mujer en primera o segunda clase casi aseguró la supervivencia, y si era pasajera de tercera la tasa estaba aproximadamente en el 50%. Los caballeros de primera y segunda clase tuvieron también una tasa de supervivencia alta

Sex la proporción de varones es mayor en el caso de los sobrevivientes. Sin embargo pueden estar sobrerpresentados entre los que no lograron salvar sus vidas.

Age y Price Analizando el coeficiente de correlacion de Pearson de 0.118 no es signficativo, aunque si se observa en la tercera clase la distribución por edad representa a personas más jovenes

e. Mostrar la distribución de clase (Sobrevivientes vs No Sobrevivientes)
Analicemos la distribución de la clase. La proporción de clase es de aprox 62% para los que no sobrevivieron y 38% de casos de que si sobrevivieron

# agrupo por variable target
train %>% group_by(Survived) %>% summarise(numero_casos=n(), perc = round(n() / nrow(train) *100,2))
NA

f. Datasets de entrenamiento y validación

# Separo training y validation con modelr
train_test <- train %>% resample_partition(c(train = 0.7, test = 0.3))

# Convierto en tibbles de tidyverse para construir el dataset
training <- train_test$train %>% as_tibble()
validation <- train_test$test %>% as_tibble()

# Muto a factores los campos correspondientes.
training %<>%
  mutate_at(vars(Survived, Pclass, Embarked), as_factor)

validation %<>%
  mutate_at(vars(Survived, Pclass, Embarked), as_factor)

# Uno las cuentas y proporciones por clase del dataset original con las particiones hechas--
rbind(
  train %>%
    group_by(Survived) %>%
    summarise(
      dataset = "dataset_completo",
      count = n(),
      prop = n() / nrow(train)
    ) %>%
    select(dataset, everything())
  ,
  training %>%
    group_by(Survived) %>%
    summarise(
      dataset = "training",
      count = n(),
      prop = n() / nrow(training)
    ) %>%
    select(dataset, everything())
  ,
  validation %>%
    group_by(Survived) %>%
    summarise(
      dataset = "validation",
      count = n(),
      prop = n() / nrow(validation)
    ) %>%
    select(dataset, everything())
)
NA

2 Predicciones

a. Generación del primer modelo

Genero el listado de fórmulas con los modelos que voy a utilizar cómo optativos

logit_formula <- 
  # creamos un objeto que contiene todas las fórmulas que vamos a utilizar
  formulas(.response = ~Survived,
                         modelobase = ~Pclass+Sex+Age,
                         modelo1= ~Embarked+Fare+Pclass,
                         modelo2 = ~Sex+SibSp,
                         modelo3 = ~Pclass
                          )

Creación del modelo

# Genero los modelos de regresión logística mapeando a glm las distintas fórmulas recién creadas
models <- data_frame(logit_formula) %>%
  mutate(model = names(logit_formula),
         expression = paste(logit_formula),
         mod = map(logit_formula, ~glm(.,family = 'binomial', data = training))) 

b. Interpretación de coeficientes

models %>%
  # Filtro solo el modelo base
  filter(model == "modelobase") %>%
  # Mapeo tidy de la librería broom para obtener coeficientes
  mutate(tidy = map(mod,tidy)) %>% 
  # Hago el unnest de la información obtenida
  unnest(tidy, .drop = TRUE) %>% 
  # Elijo columnas de interés
  select(term, estimate, std.error, statistic, p.value)

Todos los coeficientes resultan significativos respecto a la predicción “survived”

Los coeficientes asociados a las variables tienen signo negativo, a lo cual resulta que un incremento, en dichas variables, disminuye la probabilidad de sobrevivir

PClass Es mayor la probabilidad de no sobrevivir siendo de tercera clase que de segunda clase.
Sex La categoría basal respecto al sexo es mujer, lo cual significa que el hecho de ser hombre reduce la probabilidad de sobrevivir y si bien el coeficiente de Age es pequeño es significativo y también es negativo, por lo tanto a mayor edad disminuye la probabilidad de sobrevivir. Esto daría verosimil a la frase “Women and children first”

c. Probabilidad de supervivencia de Rose y Jack

A priori observando los cieficientes, dado que Jack es de tercera clase y hombre debería ser más probable que no sobreviva que rose. Hago el testeo:

# Construyo nuevo dataset con tribble
new_data <- tribble(~Pclass, ~Sex, ~Age, ~Name,
                   "1", "female", 17, "Rose",
                   "3", "male", 20, "Jack")


models %>%
  filter(model == "modelobase") %>%
  # Mapeo augment al nuevo modelo, usando la nueva data para obtener predicciones
  mutate(pred = map(mod, augment, newdata = new_data, type.predict = "response")) %>%
  # Hago el unnest de los resultados obtenidos del augment
  unnest(pred) %>%
  # Elijo variables
  select(Name, Sex, Pclass, Age, .fitted)

El resultado avala nuestra interpretación

3 Generación de modelos

a. Generación de 3 modelos adicionales

Se trabaja con los 3 modelos adicionales generados antes en la variable ‘logit_formula’:

Me interesa conocer todos los atributos económicos de la persona por eso genero el modelo1

modelo1= ~Embarked+Fare+Pclass

En el modelo 2 me interesa saber la relación entre genero y relaciones familiares SibSp

modelo2 = ~Sex+SibSp

En el tercer modelo me interesa sólo saber la relación con la clase económica o de pasaje y su probabilidad de supervivencia<br

modelo3 = ~Pclass

models %>%
  # Filtro modelo de interés
  filter(model != "modelo1") %>%
  # Obtengo coeficientes
  mutate(tidy = map(mod, tidy)) %>%
  # Unnest de la info de tidy
  unnest(tidy, .drop = TRUE) %>%
  # Elijo variables
  select(model, term, estimate, std.error, statistic, p.value) %>%
  # Chequeo la significatividad de los coeficientes
  mutate(is_sign = p.value <= 0.05) %>%
  filter(term != "(Intercept)")

Todos los coeficientes resultan significativos excepto Pclass2 para el modelo3 que sólo tenia la clase a la que pertenecia<br

b. Selección del mejor modelo<br

Hago elección de modelo considerando cómo mejor aquel que minimiza su valor o que es lo mismo, maximiza la deviance explicada. En este caso sería el modelo base<br


models %<>%
  # Mapeo glance para obtener medidas de performance de los modelos
  mutate(glance = map(mod, broom::glance))

models %>%
  # Unnest de estas medidas
  unnest(glance, .drop = TRUE) %>%
  # Calculo el porcentaje de deviance explicada por cada modelo
  mutate(perc_explained_dev = 1 - deviance / null.deviance) %>%
  # Elijo variables
  select(expression, null.deviance, deviance, perc_explained_dev) %>%
  # Ordeno por deviance
  arrange(deviance)

4 Evaluación del modelo

a.Curva ROC y AUC<br

Calculo preddiciones<br

# Añadimos predicciones a los datos de training
models <- models %>%
  mutate(pred = map(mod, augment, type.predict = "response"))

# Unnest de las de nuestro modelo de interés
prediction_mbase <-
  models %>%
  filter(model == "modelobase") %>%
  unnest(pred, .drop = TRUE)

Se dibuja la curva roc del mejor modelo<br


# Construyo la curva ROC
roc_mbase <-
  roc(response = prediction_mbase$Survived, predictor = prediction_mbase$.fitted)

# La grafico
ggroc(roc_mbase, size = 1.2) +
  geom_abline(slope = 1,
              intercept = 1,
              linetype = 'dashed') +
  theme_grey() +
  labs(
    title = 'Curva ROC',
    caption = paste('AUC:', round(roc_pc_sex_age$auc, 3)),
    subtitle = "Modelo usando Pclass, Sex y Age"
  )
Error in paste("AUC:", round(roc_pc_sex_age$auc, 3)) : 
  object 'roc_pc_sex_age' not found

La curva Roc esta dibujada sobre los ejes que representan la especificidad (True negative rate) y Sensitivity (True positive rates), es decir, existe un trade off entre la proporción de positivos (sobrevivientess) clasificados correctamente versus la proporcion de negativos (no sobrevivientes)

Se puede observar que nuestro modelo está por encima de la linea punteada que representa la probabilidad del azar.

b. Violin Plot


# Grafico predicciones
ggplot(prediction_pc_sex_age,
       aes(
         x = Survived,
         y = .fitted,
         group = Survived,
         fill = factor(Survived)
       )) +
  geom_violin() +
  theme_grey() +
  guides(fill = FALSE) +
  labs(title = 'Violin plot',
       subtitle = 'Modelo usando Pclass, Sex y Age',
       y = 'Predicted probability') +
  # Agrego puntos con jittering
  geom_jitter(
    shape = 16,
    size = 1,
    position = position_jitter(0.2),
    color = "black",
    alpha = .7
  )

NA

Se observa que el modelo es bastante bueno para realizar predicciones dado que para cada clase los “violines” se encuentran invertidos. A priori pareciera que 0.5 es un buen punto de corte para efectuar predicciones.

5 Elección del punto corte

a.Gráficos de métricas de performance<br

Analizamos distintas métricas a continuación, para analizar el punto de corte.

Recall o Sensitivity (Proporción de verdaderos positivosclasificados cómo tal) Accuracy (proporción de casos clasificados correctamente sobre el total de casos) Precision (proporción del total de casos clasificados como positivos clasificados correctamente).

Primero se generan las predicciones para el dataset de validación

# Obtenemos las correspondientes a nuestro mejor modelo
prediction_validation_modelobase <-
  models_val %>%
  filter(model == "modelobase") %>%
  unnest(pred, .drop = TRUE)
Error in eval(lhs, parent, parent) : object 'models_val' not found

Luego se grafica:

b. Elección del punto de corte óptimo<br

El punto de corte a elegir es en donde se cruzan precision y recall (sensitivity). La idea es predecir la mayor cantidad de supervivientes correctamente. Apróximadamente el punto de corte óptimo es de 0.48

# Me quedo con registros cuya diferencia entre sensitividad y especificidad es mínima, dado que ninguno es estrictamente 0
logit_pred %>% 
  # Función nueva de tidyr que reemplaza a spread()
  pivot_wider(names_from = term, values_from = estimate) %>% 
  arrange(-accuracy) %>% 
  filter(abs(sensitivity - specificity)==min(abs(sensitivity - specificity)) ) %>% 
  select(-c("precision","f1"))

# Idem con registros en que la accuracy es máxima
logit_pred %>% 
  pivot_wider(names_from = term, values_from = estimate) %>% 
  filter(accuracy == max(accuracy))%>% 
  select(-c("precision","f1"))

# Comparando ambos encuentro aprox. el punto de corte óptimo

Elegir el punto de corte depende de cual es el objetivo de nuestra investigación. Si nuestro foco está en los positivos queremos aumentar la sensitividad y la precisión deberia elegir el punto de corte de 0.48. En cambio si está en los negativos queremos maximizar el recall y deberia elegir el cutoff de 0.35.

Una manera de establecer el equilibrio es elegir a partir de la métrica de F Score y por lo tanto me quedaría con el punto de corte en 0.48.

# F1 Score
logit_pred %>%
  pivot_wider(names_from = term, values_from = estimate) %>%
  arrange(-f1) %>%
  head(1) %>%
  select(cutoff, f1, sensitivity, precision)

c. Matriz de confusión de validación<br

La matriz de confusión permite comparar la clase predicha contra la clase que realmente era. Predije correctamente 147 no sobrevivientes de 170 (82%) y predije correctamente 75 sobrevivientes de 98 (76%) PRedigo mejor a los no sobrevivientes que eran la clase mayoritaria

sel_cutoff = 0.480963   

# Creo el modelo nuevamente, para simplificar
modelo <-
  glm(logit_formula$modelobase, family = 'binomial', data = training)
# Agrego las predicciones al dataset de validación
table = augment(x = modelo,
                newdata = validation,
                type.predict = 'response')
# Clasifico utilizando el punto de corte
table = table %>% mutate(
  predicted_class = if_else(.fitted >= sel_cutoff, 1, 0) %>% as.factor(),
  Survived = factor(Survived)
)
# Creo la matriz de confusión
confusionMatrix(table$predicted_class, table$Survived, positive = "1")
Confusion Matrix and Statistics

          Reference
Prediction   0   1
         0 147  23
         1  23  75
                                          
               Accuracy : 0.8284          
                 95% CI : (0.7778, 0.8715)
    No Information Rate : 0.6343          
    P-Value [Acc > NIR] : 2.385e-12       
                                          
                  Kappa : 0.63            
                                          
 Mcnemar's Test P-Value : 1               
                                          
            Sensitivity : 0.7653          
            Specificity : 0.8647          
         Pos Pred Value : 0.7653          
         Neg Pred Value : 0.8647          
             Prevalence : 0.3657          
         Detection Rate : 0.2799          
   Detection Prevalence : 0.3657          
      Balanced Accuracy : 0.8150          
                                          
       'Positive' Class : 1               
                                          

6 Dataset de testeo

a. Lectura y transformación del dataset de testing<br


# Leo el dataset de test, transformo a factores y me quedo con las variables relevantes

test <- read_csv("titanic_complete_test.csv")

test %<>%
  select(PassengerId, Survived, Pclass, Sex,
         Age, SibSp, Parch, Fare, Embarked) %>%
  mutate_at(vars(Survived, Pclass, Embarked), as_factor)

head(test)

b. Clasificación de individuos<br

Clasfico a los pasajeros del dataset de test utilizando el modelo base con el punto de corte elegido

# Agrego la predicciones al dataset de testeo
table_test = augment(x = modelo,
                     newdata = test,
                     type.predict = 'response')
# Clasifico utilizando el punto de corte
table_test = table_test %>% mutate(
  predicted_class = if_else(.fitted >= sel_cutoff, 1, 0) %>% as.factor(),
  Survived = factor(Survived)
)

# Muestro la clasificación de cada persona

table_test %>%
  select(PassengerId, Pclass, Sex, Age, prob = .fitted, predicted_class) %>%
  arrange(-prob)
NA
NA

c. Matriz de confusión de test<br

Generamos la matriz de confusión del test


# Creo la matriz de confusión
confusionMatrix(table_test$predicted_class, table_test$Survived, positive = "1")$table
          Reference
Prediction   0   1
         0 212  50
         1  49 107

# Genero métricas para validación nuevamente
logit_val = map_dfr(sel_cutoff, prediction_metrics) %>%
  mutate(
    estimate = round(estimate, 3),
    dataset = "validation",
    term = as.factor(term)
  ) %>%
  pivot_wider(names_from = term, values_from = estimate) %>%
  select(-cutoff)


# Genero métricas para test
logit_test = map_dfr(sel_cutoff, prediction_metrics, table_test) %>% mutate(
  estimate = round(estimate, 3),
  dataset = "test",
  term = as.factor(term)
) %>%
  pivot_wider(names_from = term, values_from = estimate) %>%
  select(-cutoff)

# Las coloco una abajo de la otra
rbind(logit_val, logit_test)
NA

El modelo obtenidos con el dataset de validación tiene mejor performace que el que solo utiliza el dataset de training. Esto es porque optimizamos el punto de corte con el de validación. El acuracy es 6% mayor y además todas las metricas suben

LS0tDQp0aXRsZTogIjxjZW50ZXI+RW5mb3F1ZSBFc3RhZMOtc3RpY28gZGVsIEFwcmVuZGl6YWplPC9jZW50ZXI+Ig0KYXV0aG9yOiAiRmVkZXJpY28gTW9yZW5vIg0Kc3VidGl0bGU6IDxjZW50ZXI+VHJhYmFqbyBQcsOhY3RpY28gTsK6MzwvY2VudGVyPg0Kb3V0cHV0Og0KICBodG1sX25vdGVib29rOg0KICAgIG51bWJlcl9zZWN0aW9uczogeWVzDQogICAgdGhlbWU6IHNwYWNlbGFiDQogICAgdG9jOiB5ZXMNCiAgICB0b2NfY29sbGFwc2VkOiB5ZXMNCiAgICB0b2NfZGVwdGg6IDQNCiAgICB0b2NfZmxvYXQ6IHllcw0KICBodG1sX2RvY3VtZW50Og0KICAgIGRmX3ByaW50OiBwYWdlZA0KICAgIHRvYzogeWVzDQogICAgdG9jX2RlcHRoOiAnNCcNCi0tLQ0KPHN0eWxlPg0KYm9keSB7DQp0ZXh0LWFsaWduOiBqdXN0aWZ5fQ0KDQoubGlzdC1ncm91cC1pdGVtLmFjdGl2ZSwgLmxpc3QtZ3JvdXAtaXRlbS5hY3RpdmU6Zm9jdXMsIC5saXN0LWdyb3VwLWl0ZW0uYWN0aXZlOmhvdmVyIHsNCiAgICBiYWNrZ3JvdW5kLWNvbG9yOiAjREQ4RDE7DQp9DQo8L3N0eWxlPg0KDQoNCipmZWRlcmljb21vcmVubzYxM0BnbWFpbC5jb20qPGJyPg0KKlRyYWJham8gc29icmUgbG9zIGRhdG9zIGRlIEthZ2dsZSBkZSBUaXRhbmljKjxicj4NCmBgYHtyIHBhcXVldGVzLCBlY2hvPUZBTFNFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0Kcm0obGlzdD1scygpKQ0KZ2MoKQ0KbGlicmFyeShJU0xSKQ0KbGlicmFyeShtYWdyaXR0cikNCmxpYnJhcnkoR0dhbGx5KQ0KbGlicmFyeShwUk9DKQ0KbGlicmFyeShjb3dwbG90KQ0KbGlicmFyeShPbmVSKQ0KbGlicmFyeShybGFuZykNCmxpYnJhcnkoY2FyZXQpDQpzZXQuc2VlZCgxOTg4KQ0KbGlicmFyeShyc2Nvbm5lY3QpDQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkobW9kZWxyKQ0KbGlicmFyeShicm9vbSkNCmxpYnJhcnkoZ2dzY2kpDQpsaWJyYXJ5KGZ1bk1vZGVsaW5nKQ0KcnBhbCA9cGFsX3JpY2thbmRtb3J0eShwYWxldHRlID0gYygic2Nod2lmdHkiKSwgYWxwaGEgPSAxKQ0KYGBgDQoNCiMgUHJlcGFyYWNpw7NuIGRlIGxvcyBkYXRvcw0KYGBge3IgY2FyZ2EgZGVsIGRhdGFzZXR9DQojIENhcmdhIGRlIGRhdG9zDQp0cmFpbiA8LSByZWFkX2NzdigiQzovVXNlcnMvZmVkZV8vRGVza3RvcC9NYWVzdHJpYS82LSBFbmZvcXVlIEVzdGFkaXN0aWNvIGRlbCBhcHJlbmRpemFqZS9QcmFjdGljbyBTYWJhZG9zL3RpdGFuaWNfY29tcGxldGVfdHJhaW4uY3N2IikNCmBgYA0KKiphLkxlZXIgYXJjaGl2byB5IG1vc3RyYXIgc3UgZXN0cnVjdHVyYSoqPGJyDQoNCipFbCBhcmNoaXZvIHNlIGNvbXBvbmUgZGUgODkxIG9ic2VydmFjaW9uZXMgeSAxMiB2YXJpYWJsZXMuIExhIGNsYXNlIGVzdMOhIGVuIGxhIGNvbHVtbmEgU3Vydml2ZWQuKjxicj4NCmBgYHtyIGluc3BlY2Npw7NuIGRlbCBkYXRhc2V0fQ0KI011ZXN0cm8gZXN0cnVjdHVyYSBkZWwgZGF0YXNldA0KZ2xpbXBzZSh0cmFpbikNCmBgYA0KDQoqKmIuIFNlbGVjY2lvbiBkZSB2YXJhaWJsZXMuKio8YnI+IA0KDQoqU2Ugc2VsZWNjaW9uYW4gbGFzIHZhcmlhYmxlcyA8Y29kZT5QYXNzZW5nZXJJZDwvY29kZT4gLCA8Y29kZT5TdXJ2aXZlZDwvY29kZT4gLCA8Y29kZT5QY2xhc3M8L2NvZGU+ICwgPGNvZGU+U2V4PC9jb2RlPiAsIDxjb2RlPkFnZTwvY29kZT4gLCA8Y29kZT5TaWJTcDwvY29kZT4gLCA8Y29kZT5QYXJjaDwvY29kZT4sIDxjb2RlPkZhcmU8L2NvZGU+IHkgPGNvZGU+RW1iYXJrZWQ8L2NvZGU+Kjxicj4NCg0KYGBge3IgRWouMS5ifQ0KdHJhaW4gJTw+JSAjIFVzbyBlbCBvcGVyYWRvciBkZSBtYWdyaXR0ciBxdWUgcmVhc2lnbmEgYSBsYSB2YXJpYWJsZSB0cmFzIHRyYW5zZm9ybWFyDQogIHNlbGVjdChQYXNzZW5nZXJJZCwgU3Vydml2ZWQsIFBjbGFzcywgU2V4LA0KICAgICAgICAgQWdlLCBTaWJTcCxQYXJjaCwgRmFyZSwgRW1iYXJrZWQpDQpgYGANCg0KKipjLiBUcmFuc2Zvcm1hciBsYXMgdmFyaWFibGVzIGEgZmFjdG9yLioqPGJyPg0KYGBge3IgRWogY30NCnRyYWluICU8PiUgIyBNdXRvIGxhcyB2YXJpYWJsZXMgZWxlZ2lkYXMgYSBmYWN0b3INCiAgbXV0YXRlX2F0KHZhcnMoU3Vydml2ZWQsIFBjbGFzcywgRW1iYXJrZWQpLCBhc19mYWN0b3IpDQpgYGANCg0KYGBge3J9DQojbXVlc3RybyBudWV2YSBlc3RydWN0dXJhDQpnbGltcHNlKHRyYWluKQ0KYGBgDQoNCmBgYHtyIGFuYWxpemFyIGNlcm9zfQ0KIyBhbmFsaXNpcyBkZSBuYSxjZXJvcyB5IGVzdHJ1Y3R1cmEuDQpmdW5Nb2RlbGluZzo6c3RhdHVzKHRyYWluKQ0KYGBgDQoqQWwgYW5hbGl6YXIgbGEgY2FudGlkYWQgZGUgY2Vyb3MgeSBOQSBwb3IgY29sdW1uYSB2ZW1vcyBxdWUgbGEgw7puaWNhIGNvbHVtbmEgY29uIHZhbG9yZXMgZmFsdGFudGVzLCBlbiBlbCBvcmRlbiBkZWwgNzclIGRlIGxhcyBmaWxhcywgZXMgbGEgZGUgQ2FiaW5hLiBMYXMgZmlsYXMgZGUgU2liU3AgeSBQYXJjaCBwcmVzZW50YW4gdW5hIGdyYW4gY2FudGlkYWQgZGUgdmFsb3JlcyAwLiAqPGJyPg0KDQoqKmQuIFJlYWxpemFyIHVuIGdyw6FmaWNvIGRlIGdncGFpcnMgcGFyYSBsYXMgdmFyaWFibGVzIDxjb2RlPlN1cnZpdmVkPC9jb2RlPiwgPGNvZGU+UGNsYXNzPC9jb2RlPiwgPGNvZGU+U2V4PC9jb2RlPiwgPGNvZGU+QWdlPC9jb2RlPiB5IDxjb2RlPkZhcmU8L2NvZGU+IGUgaW50ZXJwcmV0YXJsbyoqPGJyPg0KDQpgYGB7cn0NCnRyYWluICU+JQ0KICBzZWxlY3QoU3Vydml2ZWQsIFBjbGFzcywgU2V4LCBBZ2UsIEZhcmUpICU+JSAjIEVsaWpvIHZhcmlhYmxlcyANCiAgZ2dwYWlycyguLCBtYXBwaW5nID0gYWVzKGNvbG9yID0gU3Vydml2ZWQpLCBsZWdlbmQgPSBjKDEsMSkpICsgIyBHcmFmaWNvIGdncGFpcnMgYWJyaWVuZG8gcG9yIGNsYXNlIGVuIGVsIGNvbG9yDQogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIGhqdXN0ID0gMSkpICsNCiAgdGhlbWVfZ3JleSgpKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIikNCmBgYA0KPGZvbnQgc2l6ZT0iMiI+PGZvbnQgY29sb3I9IiMzYTQ3NjciPioqRmlndWFyYSAxOioqPC9mb250PiBHcmFmaWNvIGRlIEdHcGFpcnMgZGUgdmFyaWFibGVzIHNlbGVjY2lvbmFkYXMgcG9yIGNsYXNlLjwvZm9udD48YnI+DQo8YnI+DQoNCipFbiBsYSBmaWd1cmEgMSBwb2RlbW9zIGFuYWxpemFyIGxhIGRpc3RyaWJ1Y2nDs24gZGUgY2xhc2Ugc2Vnw7puIGxhcyB2YXJpYWJsZXMgc2VsZWNjaW9uYWRhcy4gRW4gcm9qbyBlc3TDoW4gbG9zIHBhc2FqZXJvcyBubyBzdXBlcml2aXZlbnRlcyB5IGVuIGF6dWwgbG9zIHF1ZSBsbyBoaWNpZXJvbi4qPGJyPg0KDQoqU2Ugb2JzZXJ2YSBxdWUgbGEgY2xhc2UgbWF5b3JpdGFyaWEgZXMgbGEgZGUgbm8gc29icmV2aXZpZW50ZS4qPGJyPg0KDQoqPGNvZGU+UGNsYXNzPC9jb2RlPiBkZXRlcm1pbmEgdW5hIGRpZmVyZW5jaWEgaW1wb3J0YW50ZSBlbnRyZSBsb3MgcGFzYWplcm9zIGRlIGxhIDNlciBjbGFzZSB5IGxvcyBkZW3DoXMuIFBvciB1biBsYWRvIHNvbiBtYXlvcmlhdHJpYSB5IHBvciBvdHJvIGxhZG8gdGFtYmnDqW4gc29uIGxvcyBxdWUgbWVub3Mgc29icmV2aXZpZXJvbi4gU2VyIG11amVyIGVuIHByaW1lcmEgbyBzZWd1bmRhIGNsYXNlIGNhc2kgYXNlZ3Vyw7MgbGEgc3VwZXJ2aXZlbmNpYSwgeSBzaSBlcmEgcGFzYWplcmEgZGUgdGVyY2VyYSBsYSB0YXNhIGVzdGFiYSBhcHJveGltYWRhbWVudGUgZW4gZWwgNTAlLiBMb3MgY2FiYWxsZXJvcyBkZSBwcmltZXJhIHkgc2VndW5kYSBjbGFzZSB0dXZpZXJvbiB0YW1iacOpbiB1bmEgdGFzYSBkZSBzdXBlcnZpdmVuY2lhIGFsdGEqPGJyPg0KDQoqPGNvZGU+U2V4PC9jb2RlPiBsYSBwcm9wb3JjacOzbiBkZSB2YXJvbmVzIGVzIG1heW9yIGVuIGVsIGNhc28gZGUgbG9zIHNvYnJldml2aWVudGVzLiBTaW4gZW1iYXJnbyBwdWVkZW4gZXN0YXIgc29icmVycHJlc2VudGFkb3MgZW50cmUgbG9zIHF1ZSBubyBsb2dyYXJvbiBzYWx2YXIgc3VzIHZpZGFzLio8YnI+DQoNCg0KKjxjb2RlPkFnZTwvY29kZT4geSA8Y29kZT5QcmljZTwvY29kZT4gQW5hbGl6YW5kbyBlbCBjb2VmaWNpZW50ZSBkZSBjb3JyZWxhY2lvbiBkZSBQZWFyc29uIGRlIDAuMTE4IG5vIGVzIHNpZ25maWNhdGl2bywgYXVucXVlIHNpIHNlIG9ic2VydmEgZW4gbGEgdGVyY2VyYSBjbGFzZSBsYSBkaXN0cmlidWNpw7NuIHBvciBlZGFkIHJlcHJlc2VudGEgYSBwZXJzb25hcyBtw6FzIGpvdmVuZXMqDQoNCg0KKiplLiBNb3N0cmFyIGxhIGRpc3RyaWJ1Y2nDs24gZGUgY2xhc2UgKFNvYnJldml2aWVudGVzIHZzIE5vIFNvYnJldml2aWVudGVzKSoqPGJyPg0KKkFuYWxpY2Vtb3MgbGEgZGlzdHJpYnVjacOzbiBkZSBsYSBjbGFzZS4gIExhIHByb3BvcmNpw7NuIGRlIGNsYXNlIGVzIGRlIGFwcm94IDYyJSBwYXJhIGxvcyBxdWUgbm8gc29icmV2aXZpZXJvbiB5IDM4JSAgZGUgY2Fzb3MgZGUgcXVlIHNpIHNvYnJldml2aWVyb24qPGJyPg0KYGBge3IgdmFyaWFibGUgdGFyZ2V0fQ0KIyBhZ3J1cG8gcG9yIHZhcmlhYmxlIHRhcmdldA0KdHJhaW4gJT4lIGdyb3VwX2J5KFN1cnZpdmVkKSAlPiUgc3VtbWFyaXNlKG51bWVyb19jYXNvcz1uKCksIHBlcmMgPSByb3VuZChuKCkgLyBucm93KHRyYWluKSAqMTAwLDIpKQ0KDQpgYGANCioqZi4gRGF0YXNldHMgZGUgZW50cmVuYW1pZW50byB5IHZhbGlkYWNpw7NuKio8YnI+DQpgYGB7cn0NCiMgU2VwYXJvIHRyYWluaW5nIHkgdmFsaWRhdGlvbiBjb24gbW9kZWxyDQp0cmFpbl90ZXN0IDwtIHRyYWluICU+JSByZXNhbXBsZV9wYXJ0aXRpb24oYyh0cmFpbiA9IDAuNywgdGVzdCA9IDAuMykpDQoNCiMgQ29udmllcnRvIGVuIHRpYmJsZXMgZGUgdGlkeXZlcnNlIHBhcmEgY29uc3RydWlyIGVsIGRhdGFzZXQNCnRyYWluaW5nIDwtIHRyYWluX3Rlc3QkdHJhaW4gJT4lIGFzX3RpYmJsZSgpDQp2YWxpZGF0aW9uIDwtIHRyYWluX3Rlc3QkdGVzdCAlPiUgYXNfdGliYmxlKCkNCg0KIyBNdXRvIGEgZmFjdG9yZXMgbG9zIGNhbXBvcyBjb3JyZXNwb25kaWVudGVzLg0KdHJhaW5pbmcgJTw+JQ0KICBtdXRhdGVfYXQodmFycyhTdXJ2aXZlZCwgUGNsYXNzLCBFbWJhcmtlZCksIGFzX2ZhY3RvcikNCg0KdmFsaWRhdGlvbiAlPD4lDQogIG11dGF0ZV9hdCh2YXJzKFN1cnZpdmVkLCBQY2xhc3MsIEVtYmFya2VkKSwgYXNfZmFjdG9yKQ0KDQojIFVubyBsYXMgY3VlbnRhcyB5IHByb3BvcmNpb25lcyBwb3IgY2xhc2UgZGVsIGRhdGFzZXQgb3JpZ2luYWwgY29uIGxhcyBwYXJ0aWNpb25lcyBoZWNoYXMtLQ0KcmJpbmQoDQogIHRyYWluICU+JQ0KICAgIGdyb3VwX2J5KFN1cnZpdmVkKSAlPiUNCiAgICBzdW1tYXJpc2UoDQogICAgICBkYXRhc2V0ID0gImRhdGFzZXRfY29tcGxldG8iLA0KICAgICAgY291bnQgPSBuKCksDQogICAgICBwcm9wID0gbigpIC8gbnJvdyh0cmFpbikNCiAgICApICU+JQ0KICAgIHNlbGVjdChkYXRhc2V0LCBldmVyeXRoaW5nKCkpDQogICwNCiAgdHJhaW5pbmcgJT4lDQogICAgZ3JvdXBfYnkoU3Vydml2ZWQpICU+JQ0KICAgIHN1bW1hcmlzZSgNCiAgICAgIGRhdGFzZXQgPSAidHJhaW5pbmciLA0KICAgICAgY291bnQgPSBuKCksDQogICAgICBwcm9wID0gbigpIC8gbnJvdyh0cmFpbmluZykNCiAgICApICU+JQ0KICAgIHNlbGVjdChkYXRhc2V0LCBldmVyeXRoaW5nKCkpDQogICwNCiAgdmFsaWRhdGlvbiAlPiUNCiAgICBncm91cF9ieShTdXJ2aXZlZCkgJT4lDQogICAgc3VtbWFyaXNlKA0KICAgICAgZGF0YXNldCA9ICJ2YWxpZGF0aW9uIiwNCiAgICAgIGNvdW50ID0gbigpLA0KICAgICAgcHJvcCA9IG4oKSAvIG5yb3codmFsaWRhdGlvbikNCiAgICApICU+JQ0KICAgIHNlbGVjdChkYXRhc2V0LCBldmVyeXRoaW5nKCkpDQopDQoNCmBgYA0KDQojIFByZWRpY2Npb25lcw0KDQoqKmEuIEdlbmVyYWNpw7NuIGRlbCBwcmltZXIgbW9kZWxvKio8YnI+DQoNCipHZW5lcm8gZWwgbGlzdGFkbyBkZSBmw7NybXVsYXMgY29uIGxvcyBtb2RlbG9zIHF1ZSB2b3kgYSB1dGlsaXphciBjw7NtbyBvcHRhdGl2b3MqPGJyPg0KDQpgYGB7cn0NCmxvZ2l0X2Zvcm11bGEgPC0gDQogICMgY3JlYW1vcyB1biBvYmpldG8gcXVlIGNvbnRpZW5lIHRvZGFzIGxhcyBmw7NybXVsYXMgcXVlIHZhbW9zIGEgdXRpbGl6YXINCiAgZm9ybXVsYXMoLnJlc3BvbnNlID0gflN1cnZpdmVkLA0KICAgICAgICAgICAgICAgICAgICAgICAgIG1vZGVsb2Jhc2UgPSB+UGNsYXNzK1NleCtBZ2UsDQogICAgICAgICAgICAgICAgICAgICAgICAgbW9kZWxvMT0gfkVtYmFya2VkK0ZhcmUrUGNsYXNzLA0KICAgICAgICAgICAgICAgICAgICAgICAgIG1vZGVsbzIgPSB+U2V4K1NpYlNwLA0KICAgICAgICAgICAgICAgICAgICAgICAgIG1vZGVsbzMgPSB+UGNsYXNzDQogICAgICAgICAgICAgICAgICAgICAgICAgICkNCmBgYA0KDQoqQ3JlYWNpw7NuIGRlbCBtb2RlbG8qPGJyPg0KYGBge3J9DQojIEdlbmVybyBsb3MgbW9kZWxvcyBkZSByZWdyZXNpw7NuIGxvZ8Otc3RpY2EgbWFwZWFuZG8gYSBnbG0gbGFzIGRpc3RpbnRhcyBmw7NybXVsYXMgcmVjacOpbiBjcmVhZGFzDQptb2RlbHMgPC0gZGF0YV9mcmFtZShsb2dpdF9mb3JtdWxhKSAlPiUNCiAgbXV0YXRlKG1vZGVsID0gbmFtZXMobG9naXRfZm9ybXVsYSksDQogICAgICAgICBleHByZXNzaW9uID0gcGFzdGUobG9naXRfZm9ybXVsYSksDQogICAgICAgICBtb2QgPSBtYXAobG9naXRfZm9ybXVsYSwgfmdsbSguLGZhbWlseSA9ICdiaW5vbWlhbCcsIGRhdGEgPSB0cmFpbmluZykpKSANCmBgYA0KKipiLiBJbnRlcnByZXRhY2nDs24gZGUgY29lZmljaWVudGVzKio8YnI+DQoNCg0KYGBge3J9DQptb2RlbHMgJT4lDQogICMgRmlsdHJvIHNvbG8gZWwgbW9kZWxvIGJhc2UNCiAgZmlsdGVyKG1vZGVsID09ICJtb2RlbG9iYXNlIikgJT4lDQogICMgTWFwZW8gdGlkeSBkZSBsYSBsaWJyZXLDrWEgYnJvb20gcGFyYSBvYnRlbmVyIGNvZWZpY2llbnRlcw0KICBtdXRhdGUodGlkeSA9IG1hcChtb2QsdGlkeSkpICU+JSANCiAgIyBIYWdvIGVsIHVubmVzdCBkZSBsYSBpbmZvcm1hY2nDs24gb2J0ZW5pZGENCiAgdW5uZXN0KHRpZHksIC5kcm9wID0gVFJVRSkgJT4lIA0KICAjIEVsaWpvIGNvbHVtbmFzIGRlIGludGVyw6lzDQogIHNlbGVjdCh0ZXJtLCBlc3RpbWF0ZSwgc3RkLmVycm9yLCBzdGF0aXN0aWMsIHAudmFsdWUpDQpgYGANCipUb2RvcyBsb3MgY29lZmljaWVudGVzIHJlc3VsdGFuIHNpZ25pZmljYXRpdm9zIHJlc3BlY3RvIGEgbGEgcHJlZGljY2nDs24gInN1cnZpdmVkIio8YnI+DQoNCipMb3MgY29lZmljaWVudGVzIGFzb2NpYWRvcyBhIGxhcyB2YXJpYWJsZXMgdGllbmVuIHNpZ25vIG5lZ2F0aXZvLCBhIGxvIGN1YWwgcmVzdWx0YSBxdWUgdW4gaW5jcmVtZW50bywgZW4gZGljaGFzIHZhcmlhYmxlcywgZGlzbWludXllIGxhIHByb2JhYmlsaWRhZCBkZSBzb2JyZXZpdmlyKjxicj4NCg0KKjxjb2RlPlBDbGFzczwvY29kZT4gRXMgbWF5b3IgbGEgcHJvYmFiaWxpZGFkIGRlIG5vIHNvYnJldml2aXIgc2llbmRvIGRlIHRlcmNlcmEgY2xhc2UgcXVlIGRlIHNlZ3VuZGEgY2xhc2UuKjxicj4NCio8Y29kZT5TZXg8L2NvZGU+IExhIGNhdGVnb3LDrWEgYmFzYWwgcmVzcGVjdG8gYWwgc2V4byBlcyBtdWplciwgbG8gY3VhbCBzaWduaWZpY2EgcXVlIGVsIGhlY2hvIGRlIHNlciBob21icmUgcmVkdWNlIGxhIHByb2JhYmlsaWRhZCBkZSBzb2JyZXZpdmlyIHkgc2kgYmllbiBlbCBjb2VmaWNpZW50ZSBkZSA8Y29kZT5BZ2U8L2NvZGU+IGVzIHBlcXVlw7FvIGVzIHNpZ25pZmljYXRpdm8geSB0YW1iacOpbiBlcyBuZWdhdGl2bywgcG9yIGxvIHRhbnRvIGEgbWF5b3IgZWRhZCBkaXNtaW51eWUgbGEgcHJvYmFiaWxpZGFkIGRlIHNvYnJldml2aXIuIEVzdG8gZGFyw61hIHZlcm9zaW1pbCBhIGxhIGZyYXNlICJXb21lbiBhbmQgY2hpbGRyZW4gZmlyc3QiKjxicj4NCg0KDQoqKmMuIFByb2JhYmlsaWRhZCBkZSBzdXBlcnZpdmVuY2lhIGRlIFJvc2UgeSBKYWNrKioNCg0KKkEgcHJpb3JpIG9ic2VydmFuZG8gbG9zIGNpZWZpY2llbnRlcywgZGFkbyBxdWUgSmFjayBlcyBkZSB0ZXJjZXJhIGNsYXNlIHkgaG9tYnJlIGRlYmVyw61hIHNlciBtw6FzIHByb2JhYmxlIHF1ZSBubyBzb2JyZXZpdmEgcXVlIHJvc2UuIEhhZ28gZWwgdGVzdGVvOioNCmBgYHtyfQ0KIyBDb25zdHJ1eW8gbnVldm8gZGF0YXNldCBjb24gdHJpYmJsZQ0KbmV3X2RhdGEgPC0gdHJpYmJsZSh+UGNsYXNzLCB+U2V4LCB+QWdlLCB+TmFtZSwNCiAgICAgICAgICAgICAgICAgICAiMSIsICJmZW1hbGUiLCAxNywgIlJvc2UiLA0KICAgICAgICAgICAgICAgICAgICIzIiwgIm1hbGUiLCAyMCwgIkphY2siKQ0KDQoNCm1vZGVscyAlPiUNCiAgZmlsdGVyKG1vZGVsID09ICJtb2RlbG9iYXNlIikgJT4lDQogICMgTWFwZW8gYXVnbWVudCBhbCBudWV2byBtb2RlbG8sIHVzYW5kbyBsYSBudWV2YSBkYXRhIHBhcmEgb2J0ZW5lciBwcmVkaWNjaW9uZXMNCiAgbXV0YXRlKHByZWQgPSBtYXAobW9kLCBhdWdtZW50LCBuZXdkYXRhID0gbmV3X2RhdGEsIHR5cGUucHJlZGljdCA9ICJyZXNwb25zZSIpKSAlPiUNCiAgIyBIYWdvIGVsIHVubmVzdCBkZSBsb3MgcmVzdWx0YWRvcyBvYnRlbmlkb3MgZGVsIGF1Z21lbnQNCiAgdW5uZXN0KHByZWQpICU+JQ0KICAjIEVsaWpvIHZhcmlhYmxlcw0KICBzZWxlY3QoTmFtZSwgU2V4LCBQY2xhc3MsIEFnZSwgLmZpdHRlZCkNCmBgYA0KKkVsIHJlc3VsdGFkbyBhdmFsYSBudWVzdHJhIGludGVycHJldGFjacOzbiogDQoNCiMgIEdlbmVyYWNpw7NuIGRlIG1vZGVsb3MNCioqYS4gR2VuZXJhY2nDs24gZGUgMyBtb2RlbG9zIGFkaWNpb25hbGVzKioNCg0KKlNlIHRyYWJhamEgY29uIGxvcyAzIG1vZGVsb3MgYWRpY2lvbmFsZXMgZ2VuZXJhZG9zIGFudGVzIGVuIGxhIHZhcmlhYmxlICdsb2dpdF9mb3JtdWxhJzoqDQoNCipNZSBpbnRlcmVzYSBjb25vY2VyIHRvZG9zIGxvcyBhdHJpYnV0b3MgZWNvbsOzbWljb3MgZGUgbGEgcGVyc29uYSBwb3IgZXNvIGdlbmVybyBlbCBtb2RlbG8xKg0KDQpfX21vZGVsbzE9IH5FbWJhcmtlZCtGYXJlK1BjbGFzc19fDQoNCipFbiBlbCBtb2RlbG8gMiBtZSBpbnRlcmVzYSBzYWJlciBsYSByZWxhY2nDs24gZW50cmUgZ2VuZXJvIHkgcmVsYWNpb25lcyBmYW1pbGlhcmVzIDxjb2RlPlNpYlNwPC9jb2RlPioNCg0KX19tb2RlbG8yID0gflNleCtTaWJTcF9fDQoNCipFbiBlbCB0ZXJjZXIgbW9kZWxvIG1lIGludGVyZXNhIHPDs2xvIHNhYmVyIGxhIHJlbGFjacOzbiBjb24gbGEgY2xhc2UgZWNvbsOzbWljYSBvIGRlIHBhc2FqZSB5IHN1IHByb2JhYmlsaWRhZCBkZSBzdXBlcnZpdmVuY2lhKjxicg0KDQpfX21vZGVsbzMgPSB+UGNsYXNzX18NCg0KYGBge3J9DQptb2RlbHMgJT4lDQogICMgRmlsdHJvIG1vZGVsbyBkZSBpbnRlcsOpcw0KICBmaWx0ZXIobW9kZWwgIT0gIm1vZGVsbzEiKSAlPiUNCiAgIyBPYnRlbmdvIGNvZWZpY2llbnRlcw0KICBtdXRhdGUodGlkeSA9IG1hcChtb2QsIHRpZHkpKSAlPiUNCiAgIyBVbm5lc3QgZGUgbGEgaW5mbyBkZSB0aWR5DQogIHVubmVzdCh0aWR5LCAuZHJvcCA9IFRSVUUpICU+JQ0KICAjIEVsaWpvIHZhcmlhYmxlcw0KICBzZWxlY3QobW9kZWwsIHRlcm0sIGVzdGltYXRlLCBzdGQuZXJyb3IsIHN0YXRpc3RpYywgcC52YWx1ZSkgJT4lDQogICMgQ2hlcXVlbyBsYSBzaWduaWZpY2F0aXZpZGFkIGRlIGxvcyBjb2VmaWNpZW50ZXMNCiAgbXV0YXRlKGlzX3NpZ24gPSBwLnZhbHVlIDw9IDAuMDUpICU+JQ0KICBmaWx0ZXIodGVybSAhPSAiKEludGVyY2VwdCkiKQ0KYGBgDQoqVG9kb3MgbG9zIGNvZWZpY2llbnRlcyByZXN1bHRhbiBzaWduaWZpY2F0aXZvcyBleGNlcHRvIFBjbGFzczIgcGFyYSBlbCBtb2RlbG8zIHF1ZSBzw7NsbyB0ZW5pYSBsYSBjbGFzZSBhIGxhIHF1ZSBwZXJ0ZW5lY2lhKjxicg0KDQoqKmIuIFNlbGVjY2nDs24gZGVsIG1lam9yIG1vZGVsbyoqPGJyDQoNCipIYWdvIGVsZWNjacOzbiBkZSBtb2RlbG8gY29uc2lkZXJhbmRvIGPDs21vIG1lam9yIGFxdWVsIHF1ZSBtaW5pbWl6YSBzdSB2YWxvciBvIHF1ZSBlcyBsbyBtaXNtbywgbWF4aW1pemEgbGEgZGV2aWFuY2UgZXhwbGljYWRhLiBFbiBlc3RlIGNhc28gc2Vyw61hIGVsIG1vZGVsbyBiYXNlKjxicg0KDQpgYGB7cn0NCg0KbW9kZWxzICU8PiUNCiAgIyBNYXBlbyBnbGFuY2UgcGFyYSBvYnRlbmVyIG1lZGlkYXMgZGUgcGVyZm9ybWFuY2UgZGUgbG9zIG1vZGVsb3MNCiAgbXV0YXRlKGdsYW5jZSA9IG1hcChtb2QsIGJyb29tOjpnbGFuY2UpKQ0KDQptb2RlbHMgJT4lDQogICMgVW5uZXN0IGRlIGVzdGFzIG1lZGlkYXMNCiAgdW5uZXN0KGdsYW5jZSwgLmRyb3AgPSBUUlVFKSAlPiUNCiAgIyBDYWxjdWxvIGVsIHBvcmNlbnRhamUgZGUgZGV2aWFuY2UgZXhwbGljYWRhIHBvciBjYWRhIG1vZGVsbw0KICBtdXRhdGUocGVyY19leHBsYWluZWRfZGV2ID0gMSAtIGRldmlhbmNlIC8gbnVsbC5kZXZpYW5jZSkgJT4lDQogICMgRWxpam8gdmFyaWFibGVzDQogIHNlbGVjdChleHByZXNzaW9uLCBudWxsLmRldmlhbmNlLCBkZXZpYW5jZSwgcGVyY19leHBsYWluZWRfZGV2KSAlPiUNCiAgIyBPcmRlbm8gcG9yIGRldmlhbmNlDQogIGFycmFuZ2UoZGV2aWFuY2UpDQpgYGANCg0KIyBFdmFsdWFjacOzbiBkZWwgbW9kZWxvDQoNCioqYS5DdXJ2YSBST0MgeSBBVUMqKjxicg0KDQoNCipDYWxjdWxvIHByZWRkaWNpb25lcyo8YnINCmBgYHtyfQ0KIyBBw7FhZGltb3MgcHJlZGljY2lvbmVzIGEgbG9zIGRhdG9zIGRlIHRyYWluaW5nDQptb2RlbHMgPC0gbW9kZWxzICU+JQ0KICBtdXRhdGUocHJlZCA9IG1hcChtb2QsIGF1Z21lbnQsIHR5cGUucHJlZGljdCA9ICJyZXNwb25zZSIpKQ0KDQojIFVubmVzdCBkZSBsYXMgZGUgbnVlc3RybyBtb2RlbG8gZGUgaW50ZXLDqXMNCnByZWRpY3Rpb25fbWJhc2UgPC0NCiAgbW9kZWxzICU+JQ0KICBmaWx0ZXIobW9kZWwgPT0gIm1vZGVsb2Jhc2UiKSAlPiUNCiAgdW5uZXN0KHByZWQsIC5kcm9wID0gVFJVRSkNCmBgYA0KDQoqU2UgZGlidWphIGxhIGN1cnZhIHJvYyBkZWwgbWVqb3IgbW9kZWxvKjxicg0KDQpgYGB7ciBtZXNzYWdlPUYsIGZpZy53aWR0aD0xMH0NCg0KIyBDb25zdHJ1eW8gbGEgY3VydmEgUk9DDQpyb2NfbWJhc2UgPC0NCiAgcm9jKHJlc3BvbnNlID0gcHJlZGljdGlvbl9tYmFzZSRTdXJ2aXZlZCwgcHJlZGljdG9yID0gcHJlZGljdGlvbl9tYmFzZSQuZml0dGVkKQ0KDQojIExhIGdyYWZpY28NCmdncm9jKHJvY19tYmFzZSwgc2l6ZSA9IDEuMikgKw0KICBnZW9tX2FibGluZShzbG9wZSA9IDEsDQogICAgICAgICAgICAgIGludGVyY2VwdCA9IDEsDQogICAgICAgICAgICAgIGxpbmV0eXBlID0gJ2Rhc2hlZCcpICsNCiAgdGhlbWVfZ3JleSgpICsNCiAgbGFicygNCiAgICB0aXRsZSA9ICdDdXJ2YSBST0MnLA0KICAgIGNhcHRpb24gPSBwYXN0ZSgnQVVDOicsIHJvdW5kKHJvY19wY19zZXhfYWdlJGF1YywgMykpLA0KICAgIHN1YnRpdGxlID0gIk1vZGVsbyB1c2FuZG8gUGNsYXNzLCBTZXggeSBBZ2UiDQogICkNCg0KYGBgDQoNCipMYSBjdXJ2YSBSb2MgZXN0YSBkaWJ1amFkYSBzb2JyZSBsb3MgZWplcyBxdWUgcmVwcmVzZW50YW4gbGEgZXNwZWNpZmljaWRhZCAoVHJ1ZSBuZWdhdGl2ZSByYXRlKSB5IFNlbnNpdGl2aXR5IChUcnVlIHBvc2l0aXZlIHJhdGVzKSwgZXMgZGVjaXIsIGV4aXN0ZSB1biB0cmFkZSBvZmYgZW50cmUgbGEgcHJvcG9yY2nDs24gZGUgcG9zaXRpdm9zIChzb2JyZXZpdmllbnRlc3MpIGNsYXNpZmljYWRvcyBjb3JyZWN0YW1lbnRlIHZlcnN1cyBsYSBwcm9wb3JjaW9uIGRlIG5lZ2F0aXZvcyAobm8gc29icmV2aXZpZW50ZXMpKg0KDQoqU2UgcHVlZGUgb2JzZXJ2YXIgcXVlIG51ZXN0cm8gbW9kZWxvIGVzdMOhIHBvciBlbmNpbWEgZGUgbGEgbGluZWEgcHVudGVhZGEgcXVlIHJlcHJlc2VudGEgbGEgcHJvYmFiaWxpZGFkIGRlbCBhemFyLioNCg0KDQoqKmIuIFZpb2xpbiBQbG90KioNCg0KYGBge3IgZmlnLndpZHRoPTEwfQ0KDQojIEdyYWZpY28gcHJlZGljY2lvbmVzDQpnZ3Bsb3QocHJlZGljdGlvbl9wY19zZXhfYWdlLA0KICAgICAgIGFlcygNCiAgICAgICAgIHggPSBTdXJ2aXZlZCwNCiAgICAgICAgIHkgPSAuZml0dGVkLA0KICAgICAgICAgZ3JvdXAgPSBTdXJ2aXZlZCwNCiAgICAgICAgIGZpbGwgPSBmYWN0b3IoU3Vydml2ZWQpDQogICAgICAgKSkgKw0KICBnZW9tX3Zpb2xpbigpICsNCiAgdGhlbWVfZ3JleSgpICsNCiAgZ3VpZGVzKGZpbGwgPSBGQUxTRSkgKw0KICBsYWJzKHRpdGxlID0gJ1Zpb2xpbiBwbG90JywNCiAgICAgICBzdWJ0aXRsZSA9ICdNb2RlbG8gdXNhbmRvIFBjbGFzcywgU2V4IHkgQWdlJywNCiAgICAgICB5ID0gJ1ByZWRpY3RlZCBwcm9iYWJpbGl0eScpICsNCiAgIyBBZ3JlZ28gcHVudG9zIGNvbiBqaXR0ZXJpbmcNCiAgZ2VvbV9qaXR0ZXIoDQogICAgc2hhcGUgPSAxNiwNCiAgICBzaXplID0gMSwNCiAgICBwb3NpdGlvbiA9IHBvc2l0aW9uX2ppdHRlcigwLjIpLA0KICAgIGNvbG9yID0gImJsYWNrIiwNCiAgICBhbHBoYSA9IC43DQogICkNCiAgDQpgYGANCg0KKlNlIG9ic2VydmEgcXVlIGVsIG1vZGVsbyBlcyBiYXN0YW50ZSBidWVubyBwYXJhIHJlYWxpemFyIHByZWRpY2Npb25lcyBkYWRvIHF1ZSBwYXJhIGNhZGEgY2xhc2UgbG9zICJ2aW9saW5lcyIgc2UgZW5jdWVudHJhbiBpbnZlcnRpZG9zLiBBIHByaW9yaSBwYXJlY2llcmEgcXVlIDAuNSBlcyB1biBidWVuIHB1bnRvIGRlIGNvcnRlIHBhcmEgZWZlY3R1YXIgcHJlZGljY2lvbmVzLioNCg0KDQojIEVsZWNjacOzbiBkZWwgcHVudG8gY29ydGUNCg0KKiphLkdyw6FmaWNvcyBkZSBtw6l0cmljYXMgZGUgcGVyZm9ybWFuY2UqKjxicg0KDQoqQW5hbGl6YW1vcyBkaXN0aW50YXMgbcOpdHJpY2FzIGEgY29udGludWFjacOzbiwgcGFyYSBhbmFsaXphciBlbCBwdW50byBkZSBjb3J0ZS4qDQoNCl9fUmVjYWxsIG8gU2Vuc2l0aXZpdHlfXyAoUHJvcG9yY2nDs24gZGUgdmVyZGFkZXJvcyBwb3NpdGl2b3NjbGFzaWZpY2Fkb3MgY8OzbW8gdGFsKQ0KX19BY2N1cmFjeV9fIChwcm9wb3JjacOzbiBkZSBjYXNvcyBjbGFzaWZpY2Fkb3MgY29ycmVjdGFtZW50ZSBzb2JyZSBlbCB0b3RhbCBkZSBjYXNvcykNCl9fUHJlY2lzaW9uX18gKHByb3BvcmNpw7NuIGRlbCB0b3RhbCBkZSBjYXNvcyBjbGFzaWZpY2Fkb3MgY29tbyBwb3NpdGl2b3MgY2xhc2lmaWNhZG9zIGNvcnJlY3RhbWVudGUpLg0KDQoqUHJpbWVybyBzZSBnZW5lcmFuIGxhcyBwcmVkaWNjaW9uZXMgcGFyYSBlbCBkYXRhc2V0IGRlIHZhbGlkYWNpw7NuKg0KDQpgYGB7cn0NCiMgQcOxYWRpbW9zIHByZWRpY2Npb25lcyBwZXJvIGVzdGEgdmV6IGRlbCBkYXRhc2V0IGRlIHZhbGlkYWNpw7NuDQptb2RlbHNfdmFsIDwtIG1vZGVscyAlPiUNCiAgZmlsdGVyKG1vZGVsID09ICdtb2RlbG9iYXNlJykgJT4lDQogIG11dGF0ZShwcmVkID0gbWFwKG1vZCwgYXVnbWVudCwgbmV3ZGF0YSA9IHZhbGlkYXRpb24sIHR5cGUucHJlZGljdCA9ICJyZXNwb25zZSIpKQ0KDQojIE9idGVuZW1vcyBsYXMgY29ycmVzcG9uZGllbnRlcyBhIG51ZXN0cm8gbWVqb3IgbW9kZWxvDQpwcmVkaWN0aW9uX3ZhbGlkYXRpb25fbW9kZWxvYmFzZSA8LQ0KICBtb2RlbHNfdmFsICU+JQ0KICBmaWx0ZXIobW9kZWwgPT0gIm1vZGVsb2Jhc2UiKSAlPiUNCiAgdW5uZXN0KHByZWQsIC5kcm9wID0gVFJVRSkNCmBgYA0KDQpMdWVnbyBzZSBncmFmaWNhOg0KDQpgYGB7ciBmaWcuaGVpZ2h0PTcsIGZpZy53aWR0aD0xMCwgd2FybmluZz1GQUxTRX0NCg0KIyBGdW5jacOzbiBxdWUgY2xhc2lmaWNhIGxhcyBvYnNlcnZhY2lvbmVzIHBhcmEgZGlmZXJlbnRlcyBwdW50b3MgZGUgY29ydGUgeSBkZXZ1ZWx2ZSBtw6l0cmljYXMgZGUgcGVyZm9ybWFuY2UgYXNvY2lhZGFzDQpwcmVkaWN0aW9uX21ldHJpY3MgPC0NCiAgZnVuY3Rpb24oY3V0b2ZmLCBwcmVkaWN0aW9ucyA9IHByZWRpY3Rpb25fdmFsaWRhdGlvbl9tb2RlbG9iYXNlKSB7DQogICAgdGFibGVfcmVzIDwtIHByZWRpY3Rpb25zICU+JQ0KICAgICAgbXV0YXRlKA0KICAgICAgICBwcmVkaWN0ZWRfY2xhc3MgPSBpZl9lbHNlKC5maXR0ZWQgPiBjdXRvZmYsIDEsIDApICU+JSBhcy5mYWN0b3IoKSwNCiAgICAgICAgU3Vydml2ZWQgPSBmYWN0b3IoU3Vydml2ZWQpDQogICAgICApDQogICAgDQogICAgY29uZnVzaW9uTWF0cml4KHRhYmxlX3JlcyRwcmVkaWN0ZWRfY2xhc3MsIHRhYmxlX3JlcyRTdXJ2aXZlZCwgcG9zaXRpdmUgPSAiMSIpICU+JQ0KICAgICAgdGlkeSgpICU+JQ0KICAgICAgc2VsZWN0KHRlcm0sIGVzdGltYXRlKSAlPiUNCiAgICAgIGZpbHRlcih0ZXJtICVpbiUgYygnYWNjdXJhY3knLCAnc2Vuc2l0aXZpdHknLCAnc3BlY2lmaWNpdHknLA0KICAgICAgICAgICAgICAgICAgICAgICAgICdwcmVjaXNpb24nLCAnZjEnKSkgJT4lDQogICAgICBtdXRhdGUoY3V0b2ZmID0gY3V0b2ZmKQ0KICB9DQoNCiMgQ29uc3RydXlvIHZlY3RvciBkZSBwdW50b3MgZGUgY29ydGUgZGVzZGUgZWwgbcOtbmltbyBhbCBtw6F4aW1vIGRlIG51ZXN0cmFzIHByb2JhYmlsaWRhZGVzIGNvbiBpbnRlcnZhbG8gZGUgMC4wMDUNCmN1dG9mZnMgPSBzZXEoDQogIG1pbihwcmVkaWN0aW9uX3ZhbGlkYXRpb25fbW9kZWxvYmFzZSQuZml0dGVkKSwNCiAgbWF4KHByZWRpY3Rpb25fdmFsaWRhdGlvbl9tb2RlbG9iYXNlJC5maXR0ZWQpLA0KICAwLjAwNQ0KKQ0KDQojIFVzbyBsYSBmdW5jacOzbiBjb24gZXN0ZSB2ZWN0b3IgZGUgcHVudG9zIGRlIGNvcnRlDQpsb2dpdF9wcmVkID0gbWFwX2RmcihjdXRvZmZzLCBwcmVkaWN0aW9uX21ldHJpY3MpICU+JSBtdXRhdGUodGVybSA9IGFzLmZhY3Rvcih0ZXJtKSkNCg0KIyBHcmFmaWNvIGxhcyBtw6l0cmljYXMgZGUgcGVyZm9ybWFuY2UgZW4gZnVuY2nDs24gZGVsIHB1bnRvIGRlIGNvcnRlDQpsb2dpdF9wcmVkICU+JQ0KICBnZ3Bsb3QoLiwgYWVzKGN1dG9mZiwgZXN0aW1hdGUsIGdyb3VwID0gdGVybSwgY29sb3IgPSB0ZXJtKSkgKw0KICBnZW9tX2xpbmUoc2l6ZSA9IDEuNSkgKw0KICB0aGVtZV9taW5pbWFsKCkgKw0KICBsYWJzKHRpdGxlID0gJ0FjY3VyYWN5LCBTZW5zaXRpdml0eSwgU3BlY2lmaWNpdHkgeSBQcmVjaXNpb24nLA0KICAgICAgIHN1YnRpdGxlID0gJ01vZGVsbyB1c2FuZG8gUGNsYXNzLCBTZXggeSBBZ2UnLA0KICAgICAgIGNvbG9yID0gIiIpICsgDQogICMgRXNjYWxvIHRpY2tzIGRlIGxvcyBlamVzDQogIHNjYWxlX3hfY29udGludW91cyhicmVha3MgPSByb3VuZChzZXEoMCwgMSwgYnkgPSAwLjEpLCAyKSkgKw0KICBzY2FsZV95X2NvbnRpbnVvdXMoYnJlYWtzID0gcm91bmQoc2VxKDAsIDEsIGJ5ID0gMC4xKSwgMikpKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIikNCmBgYA0KDQoqKmIuIEVsZWNjacOzbiBkZWwgcHVudG8gZGUgY29ydGUgw7NwdGltbyoqPGJyDQoNCipFbCBwdW50byBkZSBjb3J0ZSBhIGVsZWdpciBlcyBlbiBkb25kZSBzZSBjcnV6YW4gcHJlY2lzaW9uIHkgcmVjYWxsIChzZW5zaXRpdml0eSkuIExhIGlkZWEgZXMgcHJlZGVjaXIgbGEgbWF5b3IgY2FudGlkYWQgZGUgc3VwZXJ2aXZpZW50ZXMgY29ycmVjdGFtZW50ZS4gQXByw7N4aW1hZGFtZW50ZSBlbCBwdW50byBkZSBjb3J0ZSDDs3B0aW1vIGVzIGRlIDAuNDgqDQoNCg0KYGBge3J9DQojIE1lIHF1ZWRvIGNvbiByZWdpc3Ryb3MgY3V5YSBkaWZlcmVuY2lhIGVudHJlIHNlbnNpdGl2aWRhZCB5IGVzcGVjaWZpY2lkYWQgZXMgbcOtbmltYSwgZGFkbyBxdWUgbmluZ3VubyBlcyBlc3RyaWN0YW1lbnRlIDANCmxvZ2l0X3ByZWQgJT4lIA0KICAjIEZ1bmNpw7NuIG51ZXZhIGRlIHRpZHlyIHF1ZSByZWVtcGxhemEgYSBzcHJlYWQoKQ0KICBwaXZvdF93aWRlcihuYW1lc19mcm9tID0gdGVybSwgdmFsdWVzX2Zyb20gPSBlc3RpbWF0ZSkgJT4lIA0KICBhcnJhbmdlKC1hY2N1cmFjeSkgJT4lIA0KICBmaWx0ZXIoYWJzKHNlbnNpdGl2aXR5IC0gc3BlY2lmaWNpdHkpPT1taW4oYWJzKHNlbnNpdGl2aXR5IC0gc3BlY2lmaWNpdHkpKSApICU+JSANCiAgc2VsZWN0KC1jKCJwcmVjaXNpb24iLCJmMSIpKQ0KDQojIElkZW0gY29uIHJlZ2lzdHJvcyBlbiBxdWUgbGEgYWNjdXJhY3kgZXMgbcOheGltYQ0KbG9naXRfcHJlZCAlPiUgDQogIHBpdm90X3dpZGVyKG5hbWVzX2Zyb20gPSB0ZXJtLCB2YWx1ZXNfZnJvbSA9IGVzdGltYXRlKSAlPiUgDQogIGZpbHRlcihhY2N1cmFjeSA9PSBtYXgoYWNjdXJhY3kpKSU+JSANCiAgc2VsZWN0KC1jKCJwcmVjaXNpb24iLCJmMSIpKQ0KDQojIENvbXBhcmFuZG8gYW1ib3MgZW5jdWVudHJvIGFwcm94LiBlbCBwdW50byBkZSBjb3J0ZSDDs3B0aW1vDQpgYGANCipFbGVnaXIgZWwgcHVudG8gZGUgY29ydGUgZGVwZW5kZSBkZSBjdWFsIGVzIGVsIG9iamV0aXZvIGRlIG51ZXN0cmEgaW52ZXN0aWdhY2nDs24uIFNpIG51ZXN0cm8gZm9jbyBlc3TDoSBlbiBsb3MgcG9zaXRpdm9zIHF1ZXJlbW9zIGF1bWVudGFyIGxhIHNlbnNpdGl2aWRhZCB5IGxhIHByZWNpc2nDs24gZGViZXJpYSBlbGVnaXIgZWwgcHVudG8gZGUgY29ydGUgZGUgMC40OC4gRW4gY2FtYmlvIHNpIGVzdMOhIGVuIGxvcyBuZWdhdGl2b3MgcXVlcmVtb3MgbWF4aW1pemFyIGVsIHJlY2FsbCB5IGRlYmVyaWEgZWxlZ2lyIGVsIGN1dG9mZiBkZSAwLjM1LioNCg0KDQoqVW5hIG1hbmVyYSBkZSBlc3RhYmxlY2VyIGVsIGVxdWlsaWJyaW8gZXMgZWxlZ2lyIGEgcGFydGlyIGRlIGxhIG3DqXRyaWNhIGRlIEYgU2NvcmUgeSBwb3IgbG8gdGFudG8gbWUgcXVlZGFyw61hIGNvbiBlbCBwdW50byBkZSBjb3J0ZSBlbiAwLjQ4LioNCg0KYGBge3J9DQojIEYxIFNjb3JlDQpsb2dpdF9wcmVkICU+JQ0KICBwaXZvdF93aWRlcihuYW1lc19mcm9tID0gdGVybSwgdmFsdWVzX2Zyb20gPSBlc3RpbWF0ZSkgJT4lDQogIGFycmFuZ2UoLWYxKSAlPiUNCiAgaGVhZCgxKSAlPiUNCiAgc2VsZWN0KGN1dG9mZiwgZjEsIHNlbnNpdGl2aXR5LCBwcmVjaXNpb24pDQpgYGANCg0KKipjLiBNYXRyaXogZGUgY29uZnVzacOzbiBkZSB2YWxpZGFjacOzbioqPGJyDQoNCipMYSBtYXRyaXogZGUgY29uZnVzacOzbiBwZXJtaXRlIGNvbXBhcmFyIGxhIGNsYXNlIHByZWRpY2hhIGNvbnRyYSBsYSBjbGFzZSBxdWUgcmVhbG1lbnRlIGVyYS4gUHJlZGlqZSBjb3JyZWN0YW1lbnRlIDE0NyBubyBzb2JyZXZpdmllbnRlcyBkZSAxNzAgKDgyJSkgeSBwcmVkaWplIGNvcnJlY3RhbWVudGUgIDc1IHNvYnJldml2aWVudGVzIGRlIDk4ICg3NiUpIFBSZWRpZ28gbWVqb3IgYSBsb3Mgbm8gc29icmV2aXZpZW50ZXMgcXVlIGVyYW4gbGEgY2xhc2UgbWF5b3JpdGFyaWEqDQoNCg0KYGBge3J9DQpzZWxfY3V0b2ZmID0gMC40ODA5NjMJDQoNCiMgQ3JlbyBlbCBtb2RlbG8gbnVldmFtZW50ZSwgcGFyYSBzaW1wbGlmaWNhcg0KbW9kZWxvIDwtDQogIGdsbShsb2dpdF9mb3JtdWxhJG1vZGVsb2Jhc2UsIGZhbWlseSA9ICdiaW5vbWlhbCcsIGRhdGEgPSB0cmFpbmluZykNCiMgQWdyZWdvIGxhcyBwcmVkaWNjaW9uZXMgYWwgZGF0YXNldCBkZSB2YWxpZGFjacOzbg0KdGFibGUgPSBhdWdtZW50KHggPSBtb2RlbG8sDQogICAgICAgICAgICAgICAgbmV3ZGF0YSA9IHZhbGlkYXRpb24sDQogICAgICAgICAgICAgICAgdHlwZS5wcmVkaWN0ID0gJ3Jlc3BvbnNlJykNCiMgQ2xhc2lmaWNvIHV0aWxpemFuZG8gZWwgcHVudG8gZGUgY29ydGUNCnRhYmxlID0gdGFibGUgJT4lIG11dGF0ZSgNCiAgcHJlZGljdGVkX2NsYXNzID0gaWZfZWxzZSguZml0dGVkID49IHNlbF9jdXRvZmYsIDEsIDApICU+JSBhcy5mYWN0b3IoKSwNCiAgU3Vydml2ZWQgPSBmYWN0b3IoU3Vydml2ZWQpDQopDQojIENyZW8gbGEgbWF0cml6IGRlIGNvbmZ1c2nDs24NCmNvbmZ1c2lvbk1hdHJpeCh0YWJsZSRwcmVkaWN0ZWRfY2xhc3MsIHRhYmxlJFN1cnZpdmVkLCBwb3NpdGl2ZSA9ICIxIikNCmBgYA0KDQoNCiMgRGF0YXNldCBkZSB0ZXN0ZW8NCg0KKiphLiBMZWN0dXJhIHkgdHJhbnNmb3JtYWNpw7NuIGRlbCBkYXRhc2V0IGRlIHRlc3RpbmcqKjxicg0KDQpgYGB7ciBtZXNzYWdlPUZBTFNFfQ0KDQojIExlbyBlbCBkYXRhc2V0IGRlIHRlc3QsIHRyYW5zZm9ybW8gYSBmYWN0b3JlcyB5IG1lIHF1ZWRvIGNvbiBsYXMgdmFyaWFibGVzIHJlbGV2YW50ZXMNCg0KdGVzdCA8LSByZWFkX2NzdigidGl0YW5pY19jb21wbGV0ZV90ZXN0LmNzdiIpDQoNCnRlc3QgJTw+JQ0KICBzZWxlY3QoUGFzc2VuZ2VySWQsIFN1cnZpdmVkLCBQY2xhc3MsIFNleCwNCiAgICAgICAgIEFnZSwgU2liU3AsIFBhcmNoLCBGYXJlLCBFbWJhcmtlZCkgJT4lDQogIG11dGF0ZV9hdCh2YXJzKFN1cnZpdmVkLCBQY2xhc3MsIEVtYmFya2VkKSwgYXNfZmFjdG9yKQ0KDQpoZWFkKHRlc3QpDQpgYGANCg0KKipiLiBDbGFzaWZpY2FjacOzbiBkZSBpbmRpdmlkdW9zKio8YnINCg0KKkNsYXNmaWNvIGEgbG9zIHBhc2FqZXJvcyBkZWwgZGF0YXNldCBkZSB0ZXN0IHV0aWxpemFuZG8gZWwgbW9kZWxvIGJhc2UgY29uIGVsIHB1bnRvIGRlIGNvcnRlIGVsZWdpZG8qDQoNCmBgYHtyfQ0KIyBBZ3JlZ28gbGEgcHJlZGljY2lvbmVzIGFsIGRhdGFzZXQgZGUgdGVzdGVvDQp0YWJsZV90ZXN0ID0gYXVnbWVudCh4ID0gbW9kZWxvLA0KICAgICAgICAgICAgICAgICAgICAgbmV3ZGF0YSA9IHRlc3QsDQogICAgICAgICAgICAgICAgICAgICB0eXBlLnByZWRpY3QgPSAncmVzcG9uc2UnKQ0KIyBDbGFzaWZpY28gdXRpbGl6YW5kbyBlbCBwdW50byBkZSBjb3J0ZQ0KdGFibGVfdGVzdCA9IHRhYmxlX3Rlc3QgJT4lIG11dGF0ZSgNCiAgcHJlZGljdGVkX2NsYXNzID0gaWZfZWxzZSguZml0dGVkID49IHNlbF9jdXRvZmYsIDEsIDApICU+JSBhcy5mYWN0b3IoKSwNCiAgU3Vydml2ZWQgPSBmYWN0b3IoU3Vydml2ZWQpDQopDQoNCiMgTXVlc3RybyBsYSBjbGFzaWZpY2FjacOzbiBkZSBjYWRhIHBlcnNvbmENCg0KdGFibGVfdGVzdCAlPiUNCiAgc2VsZWN0KFBhc3NlbmdlcklkLCBQY2xhc3MsIFNleCwgQWdlLCBwcm9iID0gLmZpdHRlZCwgcHJlZGljdGVkX2NsYXNzKSAlPiUNCiAgYXJyYW5nZSgtcHJvYikNCg0KDQpgYGANCg0KKipjLiBNYXRyaXogZGUgY29uZnVzacOzbiBkZSB0ZXN0Kio8YnINCg0KKkdlbmVyYW1vcyBsYSBtYXRyaXogZGUgY29uZnVzacOzbiBkZWwgdGVzdCoNCg0KYGBge3J9DQoNCiMgQ3JlbyBsYSBtYXRyaXogZGUgY29uZnVzacOzbg0KY29uZnVzaW9uTWF0cml4KHRhYmxlX3Rlc3QkcHJlZGljdGVkX2NsYXNzLCB0YWJsZV90ZXN0JFN1cnZpdmVkLCBwb3NpdGl2ZSA9ICIxIikkdGFibGUNCg0KYGBgDQoNCmBgYHtyfQ0KDQojIEdlbmVybyBtw6l0cmljYXMgcGFyYSB2YWxpZGFjacOzbiBudWV2YW1lbnRlDQpsb2dpdF92YWwgPSBtYXBfZGZyKHNlbF9jdXRvZmYsIHByZWRpY3Rpb25fbWV0cmljcykgJT4lDQogIG11dGF0ZSgNCiAgICBlc3RpbWF0ZSA9IHJvdW5kKGVzdGltYXRlLCAzKSwNCiAgICBkYXRhc2V0ID0gInZhbGlkYXRpb24iLA0KICAgIHRlcm0gPSBhcy5mYWN0b3IodGVybSkNCiAgKSAlPiUNCiAgcGl2b3Rfd2lkZXIobmFtZXNfZnJvbSA9IHRlcm0sIHZhbHVlc19mcm9tID0gZXN0aW1hdGUpICU+JQ0KICBzZWxlY3QoLWN1dG9mZikNCg0KDQojIEdlbmVybyBtw6l0cmljYXMgcGFyYSB0ZXN0DQpsb2dpdF90ZXN0ID0gbWFwX2RmcihzZWxfY3V0b2ZmLCBwcmVkaWN0aW9uX21ldHJpY3MsIHRhYmxlX3Rlc3QpICU+JSBtdXRhdGUoDQogIGVzdGltYXRlID0gcm91bmQoZXN0aW1hdGUsIDMpLA0KICBkYXRhc2V0ID0gInRlc3QiLA0KICB0ZXJtID0gYXMuZmFjdG9yKHRlcm0pDQopICU+JQ0KICBwaXZvdF93aWRlcihuYW1lc19mcm9tID0gdGVybSwgdmFsdWVzX2Zyb20gPSBlc3RpbWF0ZSkgJT4lDQogIHNlbGVjdCgtY3V0b2ZmKQ0KDQojIExhcyBjb2xvY28gdW5hIGFiYWpvIGRlIGxhIG90cmENCnJiaW5kKGxvZ2l0X3ZhbCwgbG9naXRfdGVzdCkNCg0KYGBgDQoNCipFbCBtb2RlbG8gb2J0ZW5pZG9zIGNvbiBlbCBkYXRhc2V0IGRlIHZhbGlkYWNpw7NuIHRpZW5lIG1lam9yIHBlcmZvcm1hY2UgcXVlIGVsIHF1ZSBzb2xvIHV0aWxpemEgZWwgZGF0YXNldCBkZSB0cmFpbmluZy4gRXN0byBlcyBwb3JxdWUgb3B0aW1pemFtb3MgZWwgcHVudG8gZGUgY29ydGUgY29uIGVsIGRlIHZhbGlkYWNpw7NuLiBFbCBhY3VyYWN5IGVzIDYlIG1heW9yIHkgYWRlbcOhcyB0b2RhcyBsYXMgbWV0cmljYXMgc3ViZW4qDQoNCg0KDQoNCg0KDQo=