Datos

Metodología

  1. Lectura de datos.
  2. Exploración de datos.
    2.1. Proporción de enfermos y sanos.
    2.2. Distribuciones de probabilidad.
    2.3. Correlaciones de variables predictoras.
    2.4. Tendencias por edad del paciente.
  3. Preprocesamiento de datos.
    3.1. Partición en Training y Testing.
    3.2. Normalización de variables.
    3.3. Recodificación de variable respuesta Classification (“0”: paciente sano y “1”: paciente enfermo).
    3.4. Preprocesamiento específico para implementación de redes neuronales profundas (deep learning) con bibliotecas keras y lime.
  4. Ajuste de modelos de Machine Learning - Supervised Learning:
    4.1. K-Nearest Neighbor.
    4.2. Naive Bayes.
    4.3. Regresión Logística.
    4.4. Árbol de decisión (clasificación).
    4.5. Random Forest.
    4.6. Support Vector Machine.
    4.7. Gradient Boosting.
    4.8. Deep Learning (Multilayer Perceptron).
  5. Desempeño de los modelos: comparación de Accuracy en Training y Testing.
  6. Referencias

Resultados

1. Lectura de datos

2. Análisis exploratorio

Proporción de enfermos y sanos


       0        1 
44.82759 55.17241 

Distribuciones

Correlaciones

Tendencias por edad

3. Preprocesamiento

Train y Test

  • Primero se convierte en factor la variable respuesta que está codificada como numeric. Este paso es importante para garantizar que las proporciones de cada clase (enfermo o sano) se mantengan en las bases de datos train y test.
  • Se usa la biblioteca caret con la función CreateDataPartition().
  • La partición se realizó 70-30%, para train y test, respectivamente.
  • Nota: la función implementada tiene en cuenta la proporción de la variable respuesta para el train y test, no obstante, no hace énfasis en la variabilidad de predictores (problemas de varianza igual a cero).
set.seed(1000)
library(caret)
df_seno$Classification <- as.factor(df_seno$Classification)
ind <- createDataPartition(y = df_seno$Classification, times = 1,
                           p = 0.7, list = FALSE)
df_train <- df_seno[ind, ]
df_testi <- df_seno[-ind, ]

Preprocesamiento para keras

  • Para ajustar el modelo secuencial con keras es necesario coercionar las bases de datos (data.frame) train y test a la clase matrix, de la misma manera que quedó coercionada la variable respuesta en el paso anterior.
  • En keras y muchas otras bibliotecas para Machine Learning los modelos se expresan en términos matriciales; esto debido a la eficiencia algebráica para procesamiento de información. Este requisito no es obligatorio en todos los modelos, puesto que algunos aceptan las variables declaradas como fórmula (formula = y ~ x + w + z).
# Coerción a matriz
x_train <- as.matrix(df_train[, 1:9])
x_testi <- as.matrix(df_testi[, 1:9])
# Eliminando dimensiones
dimnames(x_train) <- NULL
dimnames(x_testi) <- NULL

Normalización

  • La normalización o estandarización de variables puede ser efecutada de maneras diferentes:
    • Escalado Min-Max: \(z_i = x-mínimo_x/(máximo_x-mínimo_x)\). La variable queda acotada entre [0, 1].
    • Estandarización: \(x - \mu_x/\sigma_x\). La variable queda acotada entre [-1, 1], con \(\mu = 0\) y \(\sigma = 1\).
  • La normalización o estandarización de variables es necesaria para la ejecución de algunos algoritmos como las redes neuronales, las máquinas de soporte vectorial o la regresión logística. Este proceso es necesario cuando las unidades de las variables son diferentes, tratando de evitar el sobrepeso (mayor importancia para el modelo) en variables que posean mayor variabilidad (varianza).
  • En R se pueden implementar las funciones scale() para escalar o estandarizar las variables y la función normalize() del paquete keras permite aplicar la normalización min-max.
  • Para este ejemplo las variables serán normalizadas con la función normalize() de la biblioteca keras.
library(keras)
df_train[, 1:9] <- normalize(x_train[, 1:9])
df_testi[, 1:9] <- normalize(x_testi[, 1:9])
  • A manera de ejemplo se muestra el rango de las variables df_train. Corroborando que ninguna de las variables posee valores inferiores a cero o mayores a 1.
apply(df_train, 2, range)
     Age          BMI          Glucose      Insulin       HOMA           Leptin        Adiponectin   Resistin     
[1,] "0.02820357" "0.01555693" "0.05288169" "0.001492439" "0.0003313214" "0.005518683" "0.003125271" "0.005109257"
[2,] "0.39514376" "0.17217330" "0.73820809" "0.062640891" "0.0175414790" "0.264769237" "0.172404469" "0.260385811"
     MCP.1       Classification
[1,] "0.4829921" "0"           
[2,] "0.9979598" "1"           

Recodificación de Classification

  • Para el modelo de red neuronal profunda implementado a través de keras, es necesario convertir la variable respuesta en Dummy, en regresión se suele hablar de variables indicadoras, aunque también es conocido como one-hot encoding (codificación activa) en Machine Learning.
  • La función to_categorical() del paquete keras permite esta conversión.
y_train <- to_categorical(df_train$Classification)
y_testi <- to_categorical(df_testi$Classification)

4. Modelos

  • Se usan las bibliotecas caret, y keras para entrenar los modelos de Machine Learning.
  • A través de validación cruzada (k = 10 y repeticiones = 10) se evalúa el desempeño de los modelos.
  • La métrica a tener en cuenta para comparar los modelos es la Accuracy o precisión del modelo.
  • Todos los procedimientos con caret son paralelizados con la biblioteca doMC.

K-Nearest Neighbor

  • Características generales:
    • Uno de los algoritmos de Machine Learning más simples.
    • Fundamentado en identificar observaciones que se asemejen a las observaciones vecinas para realizar predicciones.
    • Una nueva observación se predice en función de su “similitud” con otras observaciones (“vecinos más cercanos”).
    • La “similitud” con otras observaciones es cuantificada con métricas de distancia, por ejemplo, distancia Euclidiana.
    • Pueden ser computacionalmente ineficientes y son conocidos como lazy learners (Cunningham & Delany, 2007).
    • Hiperparámetro k: número de observaciones vecinas empleadas para realizar la predicción.
    • Algunos casos de uso:
# Paralelización del proceso
library(doMC)
library(parallel)
registerDoMC(cores = detectCores())
# Submuestras y repeticiones
particiones  <- 10
repeticiones <- 10
# Definiendo hiperparámetro k
hiperparametros <- expand.grid(k = seq(1, 30, 2))
# Semillas
set.seed(123)
seeds <- vector(mode = "list", length = (particiones * repeticiones) + 1)
for (i in 1:(particiones * repeticiones)) {
  seeds[[i]] <- sample.int(1000, nrow(hiperparametros)) 
}
seeds[[(particiones * repeticiones) + 1]] <- sample.int(1000, 1)
# Control de entrenamiento
cross_val <- trainControl(
  method = "repeatedcv",
  number = particiones,
  repeats = repeticiones,
  returnResamp = "final",
  verboseIter = FALSE,
  allowParallel = TRUE,
  seeds = seeds
)
# Ajuste del modelo (entrenamiento)
set.seed(1000)
mod_knn <- train(
  Classification ~ .,
  data = df_train,
  method = "knn",
  tuneGrid = hiperparametros,
  metric = "Accuracy",
  trControl = cross_val
)
  • Evaluando el \(k\) óptimo:

  • Resultados del modelo (de mayor a menor accuracy):
  • Accuracy en testing (matriz de confusión):
Confusion Matrix and Statistics

    Predicho
Real  0  1
   0  5 10
   1  5 14
                                          
               Accuracy : 0.5588          
                 95% CI : (0.3789, 0.7281)
    No Information Rate : 0.7059          
    P-Value [Acc > NIR] : 0.9777          
                                          
                  Kappa : 0.0727          
                                          
 Mcnemar's Test P-Value : 0.3017          
                                          
            Sensitivity : 0.5833          
            Specificity : 0.5000          
         Pos Pred Value : 0.7368          
         Neg Pred Value : 0.3333          
             Prevalence : 0.7059          
         Detection Rate : 0.4118          
   Detection Prevalence : 0.5588          
      Balanced Accuracy : 0.5417          
                                          
       'Positive' Class : 1               
                                          

Naive Bayes

  • Características:
    • Se fundamenta en el cálculo de probabilidades condicionales basado en el Teorema de Bayes.
    • El algoritmo asume que existe independencia entre las variables predictoras, de ahí que su nombre sea naive (ingenuo en inglés). En la vida real esta suposición de independencia rara vez se cumple, no obstante, proporciona resultados favorables en variedad de aplicaciones.
    • Posee tres hiperparámetros:
      • usekernel: TRUE para utilizar un kernel que estime la densidad o FALSE para asumir una función de densidad gaussiana.
      • fL: fL = 1 para aplicar el factor de corrección de Laplace. De utilidad cuando se tienen eventos o conjuntos vacios, es decir, ausencia de información que impide el cálculo de probabilidades de manera correcta.
      • adjust: parámetro que hace parte de la función density() en caso de usekernel = TRUE.
    • Algunos usos:
      • Clasificación de secuencias de RNA en taxonomía bacteriana (Wang et al., 2007).
      • Selección de características para entrenamiento de Support Vector Machine (Cinelli et al., 2017).
      • Eventos adversos de fármacos con base en citas de PubMed (Wang et al., 2011).
      • Extracción de características para clasificación de texto (Sarkar et al., 2014).
      • Estimación de ubicación (geoposicionamiento) probabilística (Shauer, 2019).
# Paralelización del proceso
library(doMC)
library(parallel)
registerDoMC(cores = detectCores())
# Submuestras y repeticiones
particiones  <- 10
repeticiones <- 10
# Definiendo hiperparámetro k
hiperparametros <- data.frame(usekernel = FALSE, fL = 0 , adjust = 0)
# Semillas
set.seed(123)
seeds <- vector(mode = "list", length = (particiones * repeticiones) + 1)
for (i in 1:(particiones * repeticiones)) {
  seeds[[i]] <- sample.int(1000, nrow(hiperparametros)) 
}
seeds[[(particiones * repeticiones) + 1]] <- sample.int(1000, 1)
# Control de entrenamiento
cross_val <- trainControl(
  method = "repeatedcv",
  number = particiones,
  repeats = repeticiones,
  returnResamp = "final",
  verboseIter = FALSE,
  allowParallel = TRUE,
  seeds = seeds
)
# Ajuste del modelo (entrenamiento)
set.seed(1000)
mod_bayes <- train(
  Classification ~ .,
  data = df_train,
  method = "nb",
  tuneGrid = hiperparametros,
  metric = "Accuracy",
  trControl = cross_val
)
  • Resultados del modelo:
  • Accuracy en testing (matriz de confusión):
Confusion Matrix and Statistics

    Predicho
Real  0  1
   0  1 14
   1  4 15
                                          
               Accuracy : 0.4706          
                 95% CI : (0.2978, 0.6487)
    No Information Rate : 0.8529          
    P-Value [Acc > NIR] : 1.00000         
                                          
                  Kappa : -0.1547         
                                          
 Mcnemar's Test P-Value : 0.03389         
                                          
            Sensitivity : 0.51724         
            Specificity : 0.20000         
         Pos Pred Value : 0.78947         
         Neg Pred Value : 0.06667         
             Prevalence : 0.85294         
         Detection Rate : 0.44118         
   Detection Prevalence : 0.55882         
      Balanced Accuracy : 0.35862         
                                          
       'Positive' Class : 1               
                                          

Regresión Logística

  • Características:
    • Algoritmos de amplio uso en estadística e incorporados hoy en día como parte de la caja de herramientas para Machine Learning.
    • Hace parte de los Modelos Lineales Generalizados (GLM).
    • Permite modelar variables con distribución de errores no gaussianas o normales.
    • Aplica transformaciones sobre la variable respuesta original, tratando de linealizar la relación con las predictoras a través de una función de enlace (linkage).
    • Es aplicable a problemas binomiales y multinomiales.
    • Algunos casos de uso:
# Paralelización del proceso
library(doMC)
library(parallel)
registerDoMC(cores = detectCores())
# Submuestras y repeticiones
particiones  <- 10
repeticiones <- 10
# Definiendo hiperparámetro k
hiperparametros <- data.frame(parameter = "none")
# Semillas
set.seed(123)
seeds <- vector(mode = "list", length = (particiones * repeticiones) + 1)
for (i in 1:(particiones * repeticiones)) {
  seeds[[i]] <- sample.int(1000, nrow(hiperparametros)) 
}
seeds[[(particiones * repeticiones) + 1]] <- sample.int(1000, 1)
# Control de entrenamiento
cross_val <- trainControl(
  method = "repeatedcv",
  number = particiones,
  repeats = repeticiones,
  returnResamp = "final",
  verboseIter = FALSE,
  allowParallel = TRUE,
  seeds = seeds
)
# Ajuste del modelo (entrenamiento)
set.seed(1000)
mod_regl <- train(
  Classification ~ .,
  data = df_train,
  method = "glm",
  tuneGrid = hiperparametros,
  metric = "Accuracy",
  trControl = cross_val
)
  • Resultados del modelo:
  • Accuracy en testing (matriz de confusión):
Confusion Matrix and Statistics

    Predicho
Real  0  1
   0  8  7
   1  2 17
                                          
               Accuracy : 0.7353          
                 95% CI : (0.5564, 0.8712)
    No Information Rate : 0.7059          
    P-Value [Acc > NIR] : 0.4355          
                                          
                  Kappa : 0.4436          
                                          
 Mcnemar's Test P-Value : 0.1824          
                                          
            Sensitivity : 0.7083          
            Specificity : 0.8000          
         Pos Pred Value : 0.8947          
         Neg Pred Value : 0.5333          
             Prevalence : 0.7059          
         Detection Rate : 0.5000          
   Detection Prevalence : 0.5588          
      Balanced Accuracy : 0.7542          
                                          
       'Positive' Class : 1               
                                          

Árbol de decisión

  • Características:
    • Se fundamenta en la segmentación del espacio de predictores a través de reglas lógicas (booleanas) simples.
    • Hacen parte de los algoritmos no paramétricos.
    • Modelos de árbol pequeños son de fácil interpretación, además, son fáciles de visualizar.
    • Permiten modelar relaciones no lineales de manera sencilla.
    • Ignora facilmente las variables menos importantes.
    • A menudo presentan alta varianza en las predicciones.
    • Dentro de los algoritmos de árboles de decisión se destacan los siguientes:
    • La métrica de impureza utilizada por CART es Gini.
    • La métrica de impureza utilizada por ID3, C4.5 y C5.0 es la entropía o ganancia de información.
    • Algunos casos de uso:
# Paralelización del proceso
library(doMC)
library(parallel)
registerDoMC(cores = detectCores())

# Submuestras y repeticiones
particiones  <- 10
repeticiones <- 10

# Definiendo hiperparámetro k
hiperparametros <- data.frame(parameter = "none")

# Semillas
set.seed(123)
seeds <- vector(mode = "list", length = (particiones * repeticiones) + 1)
for (i in 1:(particiones * repeticiones)) {
  seeds[[i]] <- sample.int(1000, nrow(hiperparametros)) 
}
seeds[[(particiones * repeticiones) + 1]] <- sample.int(1000, 1)

# Control de entrenamiento
cross_val <- trainControl(
  method = "repeatedcv",
  number = particiones,
  repeats = repeticiones,
  returnResamp = "final",
  verboseIter = FALSE,
  allowParallel = TRUE,
  seeds = seeds
)

# Ajuste del modelo (entrenamiento)
set.seed(1000)
mod_c5tree <- train(
  Classification ~ .,
  data = df_train,
  method = "C5.0Tree",
  tuneGrid = hiperparametros,
  metric = "Accuracy",
  trControl = cross_val
)
  • Resultados del modelo:
  • Accuracy en testing (matriz de confusión):
Confusion Matrix and Statistics

    Predicho
Real  0  1
   0  5 10
   1  3 16
                                          
               Accuracy : 0.6176          
                 95% CI : (0.4356, 0.7783)
    No Information Rate : 0.7647          
    P-Value [Acc > NIR] : 0.98311         
                                          
                  Kappa : 0.1845          
                                          
 Mcnemar's Test P-Value : 0.09609         
                                          
            Sensitivity : 0.6154          
            Specificity : 0.6250          
         Pos Pred Value : 0.8421          
         Neg Pred Value : 0.3333          
             Prevalence : 0.7647          
         Detection Rate : 0.4706          
   Detection Prevalence : 0.5588          
      Balanced Accuracy : 0.6202          
                                          
       'Positive' Class : 1               
                                          

Random Forest

  • Características:
    • Considerado como parte de los métodos de Bagging (bootstrap aggregation) propuestos por Leo Breiman.
    • El Bagging surge como estrategía para el desequilibrio bias-varianza.
    • El término Bagging hace referencia al empleo del muestreo repetido (bootstrapping).
    • El algoritmo en lugar de ajustar un sólo arbol de decisión permite la incorporación de muchos de estos, conformando lo que se denomina como “bosque”. Se denomina “bosque aleatorio” porque selecciona aleatoriamente \(m\) predictores para la construcción de los árboles.
    • Permite obtener métricas de importancia de variables, resultando en un método viable para seleccionar predictores.
    • En R puede ser implementado a través de la biblioteca randomForest o ranger. El método ranger posee tres hiperparámetros controlables:
      • mtry: número de predictores seleccionados aleatoriamente en cada árbol.
      • min.node.size: tamaño mínimo que debe tener un nodo para ser dividido.
      • splitrule: criterio de división (por defecto gini).
    • Algunos casos de uso:
      • Bosques aleatorios para clasificación de neuroimágenes en pacientes con Alzheimer (Sarica et al., 2017).
      • Identificación de fuentes de contaminación del agua a través de Bosques Aleatorios (Roguet et al., 2018).
      • Modelado de variables espaciales y espacio-temporales con Bosques Aleatorios (Hengl et al., 2018).
      • Extracción de características con Bosques Aleatorios para análisis de expresión génica con Deep Learning (Kong & Yu, 2018).
      • Predicción de cáncer de próstata con Bosques Aleatorios (Xiao et al., 2017).
      • Bosques Aleatorios para análisis de datos genómicos (Chen & Ishwaran, 2013).
# Paralelización del proceso
library(doMC)
library(parallel)
registerDoMC(cores = detectCores())
# Submuestras y repeticiones
particiones  <- 10
repeticiones <- 10
# Definiendo hiperparámetro k
hiperparametros <- expand.grid(mtry = seq(1, 9, 1),
                               min.node.size = seq(1, 30, 2),
                               splitrule = "gini")
# Semillas
set.seed(123)
seeds <- vector(mode = "list", length = (particiones * repeticiones) + 1)
for (i in 1:(particiones * repeticiones)) {
  seeds[[i]] <- sample.int(1000, nrow(hiperparametros)) 
}
seeds[[(particiones * repeticiones) + 1]] <- sample.int(1000, 1)
# Control de entrenamiento
cross_val <- trainControl(
  method = "repeatedcv",
  number = particiones,
  repeats = repeticiones,
  returnResamp = "final",
  verboseIter = FALSE,
  allowParallel = TRUE,
  seeds = seeds
)
# Ajuste del modelo (entrenamiento)
set.seed(1000)
mod_rf <- train(
  Classification ~ .,
  data = df_train,
  method = "ranger",
  tuneGrid = hiperparametros,
  metric = "Accuracy",
  trControl = cross_val,
  num.trees = 500
)
  • El mejor modelo:

  • Resultados del modelo:
  • Accuracy en testing (matriz de confusión):
Confusion Matrix and Statistics

    Predicho
Real  0  1
   0  8  7
   1  4 15
                                          
               Accuracy : 0.6765          
                 95% CI : (0.4947, 0.8261)
    No Information Rate : 0.6471          
    P-Value [Acc > NIR] : 0.4358          
                                          
                  Kappa : 0.3297          
                                          
 Mcnemar's Test P-Value : 0.5465          
                                          
            Sensitivity : 0.6818          
            Specificity : 0.6667          
         Pos Pred Value : 0.7895          
         Neg Pred Value : 0.5333          
             Prevalence : 0.6471          
         Detection Rate : 0.4412          
   Detection Prevalence : 0.5588          
      Balanced Accuracy : 0.6742          
                                          
       'Positive' Class : 1               
                                          

Support Vector Machine (SVM)

  • Características:
    • Algoritmo propiamente de la escuela de inteligencia artificial.
    • El algoritmo se fundamenta en la búsqueda de un hiperplano en algún espacio o dimensión de características que separe “mejor” las clases.
    • El Maximal Margin Classifier está basado en el concepto de hiperplano.
    • El espacio de entrada es mapeado a una dimensión superior a través de una función kernel y es en el espacio de características trasnformadas que encuentra el hiperplano que dará como resultado la separación máxima de las clases.
    • La escalabilidad computacional suele ser compleja en grandes volúmenes de información.
    • Resultados satisfactorios se consiguen con tamaños muestrales pequeños.
    • Los hiperparámetros de la función ksvm()del paquete kernlab que implementa caret son los siguientes:
      • sigma: coeficiente del kernel radial.
      • C: penalización para margen de hiperplano.
    • Algunos casos de uso:
# Paralelización del proceso
library(doMC)
library(parallel)
registerDoMC(cores = detectCores())
# Submuestras y repeticiones
particiones  <- 10
repeticiones <- 10
# Definiendo hiperparámetro k
hiperparametros <- expand.grid(sigma = c(0.001, 0.01, seq(0.1, 1, 0.1)),
                               C = seq(1, 1000, 20))
# Semillas
set.seed(123)
seeds <- vector(mode = "list", length = (particiones * repeticiones) + 1)
for (i in 1:(particiones * repeticiones)) {
  seeds[[i]] <- sample.int(1000, nrow(hiperparametros)) 
}
seeds[[(particiones * repeticiones) + 1]] <- sample.int(1000, 1)
# Control de entrenamiento
cross_val <- trainControl(
  method = "repeatedcv",
  number = particiones,
  repeats = repeticiones,
  returnResamp = "final",
  verboseIter = FALSE,
  allowParallel = TRUE,
  seeds = seeds
)
# Ajuste del modelo (entrenamiento)
set.seed(1000)
mod_svm <- train(
  Classification ~ .,
  data = df_train,
  method = "svmRadial",
  tuneGrid = hiperparametros,
  metric = "Accuracy",
  trControl = cross_val
)
  • El mejor modelo:

  • Resultados del modelo:
  • Accuracy en testing (matriz de confusión):
Confusion Matrix and Statistics

    Predicho
Real  0  1
   0 10  5
   1  5 14
                                         
               Accuracy : 0.7059         
                 95% CI : (0.5252, 0.849)
    No Information Rate : 0.5588         
    P-Value [Acc > NIR] : 0.0582         
                                         
                  Kappa : 0.4035         
                                         
 Mcnemar's Test P-Value : 1.0000         
                                         
            Sensitivity : 0.7368         
            Specificity : 0.6667         
         Pos Pred Value : 0.7368         
         Neg Pred Value : 0.6667         
             Prevalence : 0.5588         
         Detection Rate : 0.4118         
   Detection Prevalence : 0.5588         
      Balanced Accuracy : 0.7018         
                                         
       'Positive' Class : 1              
                                         

Gradient Boosting

  • Características:
    • Hacen parte de los algoritmos de ensemble.
    • Se fundamenta en la implementación de un conjunto de modelos sencillos (weak learners) con tasas de aprendizaje condicional por el modelo anterior, es decir, que emplea la información previa para aprender de sus errores.
    • La construcción del modelo se da de forma iterativa como lo hacen otros métodos de Boosting.
    • A diferencia de Bagging el método Gradient Boosting no usa bootstrapping.
    • Tres de los algoritmos de Boosting con mayor aceptación son AdaBoost, Gradient Boosting y Stochastic Gradient Boosting.
    • En R puede ser implementado a través de la función gbm() del paquete gmb. El algoritmo tiene 6 hiperparámetros a tener en cuenta:
      • n.trees: número de iteraciones del algoritmo. Es el número de modelos que conforma el ensemble.
      • iteration.depth: complejidad de los árboles empleados como weak learner.
      • shrinkage: tasa de aprendizaje (learning rate) que controla la influencia que tiene cada modelo individual sobre el conjunto de modelos (ensemble).
      • n.minobsinnode: número mínimo de observaciones que debe tener un nodo para poder ser dividido.
      • distribution: determina la función de coste (loss function).
        • Gaussian –> para regresión.
        • Bernoulli –> para respuestas binarias.
        • Multinomial –> para respuestas multiclase.
      • bag.fraction: submuestra del conjunto de entrenamiento utilizado para ajustar los weak learner. Si el valor es igual a \(1\) se emplea el algoritmo Gradient Boosting. Por defecto la función está con valor de 0.5, implementando el algoritmo Stochastic Gradient Boosting.
      • En general, la jerarquía en modelos basados en árboles se da de la siguiente manera: \(Boosting\ > Random\ Forest\ > Bagging\ > Árboles\ simples\).
      • Son uno de los algoritmos más potentes en cuanto a capacidad predicitiva.
    • Algunos casos de uso:
      • Analítica predicitiva en medicina con Gradient Boosting (Zhang et al., 2019).
      • Predicción de efectos secundarios en tratamiento para osteoartritis a través de modelos de Boosting (Liu et al., 2018).
      • Identificación de predictores moleculares de alta eficiencia alimenticia en cerdos en crecimiento (Messad et al., 2019).
      • Modelos de Boosting en análisis de streaming data para internet de las cosas (IoT) (Kenda et al., 2019).
      • Artículo guía de Gradient Boosting (Natekin & Knoll, 2013).
# Paralelización del proceso
library(doMC)
library(parallel)
registerDoMC(cores = detectCores())
# Submuestras y repeticiones
particiones  <- 10
repeticiones <- 10
# Definiendo hiperparámetro k
hiperparametros <- expand.grid(interaction.depth = c(1, 3, 5),
                               n.trees = seq(50, 1000, 100),
                               shrinkage = c(0.0001, 0.001, 0.01, 0.1, 1),
                               n.minobsinnode = c(2, 5, 10, 15))
# Semillas
set.seed(123)
seeds <- vector(mode = "list", length = (particiones * repeticiones) + 1)
for (i in 1:(particiones * repeticiones)) {
  seeds[[i]] <- sample.int(1000, nrow(hiperparametros)) 
}
seeds[[(particiones * repeticiones) + 1]] <- sample.int(1000, 1)
# Control de entrenamiento
cross_val <- trainControl(
  method = "repeatedcv",
  number = particiones,
  repeats = repeticiones,
  returnResamp = "final",
  verboseIter = FALSE,
  allowParallel = TRUE,
  seeds = seeds
)
# Ajuste del modelo (entrenamiento)
set.seed(1000)
mod_gbm <- train(
  Classification ~ .,
  data = df_train,
  method = "gbm",
  tuneGrid = hiperparametros,
  metric = "Accuracy",
  trControl = cross_val,
  distribution = "bernoulli",
  verbose = FALSE
)
  • El mejor modelo:

  • Resultados del modelo:
  • Accuracy en testing (matriz de confusión):
Confusion Matrix and Statistics

    Predicho
Real  0  1
   0  5 10
   1  5 14
                                          
               Accuracy : 0.5588          
                 95% CI : (0.3789, 0.7281)
    No Information Rate : 0.7059          
    P-Value [Acc > NIR] : 0.9777          
                                          
                  Kappa : 0.0727          
                                          
 Mcnemar's Test P-Value : 0.3017          
                                          
            Sensitivity : 0.5833          
            Specificity : 0.5000          
         Pos Pred Value : 0.7368          
         Neg Pred Value : 0.3333          
             Prevalence : 0.7059          
         Detection Rate : 0.4118          
   Detection Prevalence : 0.5588          
      Balanced Accuracy : 0.5417          
                                          
       'Positive' Class : 1               
                                          

Deep Learning con keras

  • Características:
    • Las redes neuronales existen hace mucho tiempo como un concepto de inteligencia artificial e incluso como un algoritmo de Machine Learning.
    • Hasta cierto punto pueden ser consideradas como métodos de regresión no lineal.
    • La arquitectura de red neuronal profunda (deep learning) hace parte de las redes neuronales artificiales.
    • Visualmente se observa como un conjunto de capas de entrada (inputs) y salida (outputs).
    • Se fundamenta en la asignación de un peso o ponderación a las entradas y con una función de activación (por ejemplo sigmoidea) se produce la siguiente capa de entradas. Este proceso se repite y el conjunto de entradas conforman lo que se conoce como capas ocultas (hidden layers).
    • El método común para entrenar redes neuronales es el de propagación hacia atrás (Backpropagation). El método de Backpropagation es iterativo, recursivo y eficiente para recalcular los pesos de las entradas de la red. La actualización de los pesos es obtenida al seguir un algoritmo de optimización basado en gradientes, como el descenso de gradiente.
    • Algunas de las arquitecturas de redes neuronales más conocidas en la actualidad se mencionan a continuación:
      • Perceptrón multicapa - MLP. Consta de múltiples capas, con cada capa conectada completamente a la siguiente. Puede distinguir datos que no son linealmente separables.
      • Redes Neuronales Recurrentes - RNN. Permite conexiones entre unidades que exhiben comportamientos temporales dinámicos. De amplio uso en processamiento de lenguaje (NLP - Natural Language Processing). Aquí un ejemplo cómico de uso de RNN.
      • Redes Neuronales Convolucionales - CNN. Las redes convolucionales son variaciones de perceptrones multicapa diseñados para usar cantidades mínimas de preprocesamiento. De amplio uso en análisis de imagen, video, NLP y sistemas de recomendación.
      • Funciones de activación:
      • Además de la función de activación para el entrenamiento de redes, es necesario declarar una función matemática para la capa de salida (outputs), para problemas binarios se usa la función Sigmoide, para tareas multinomiales se utiliza la función Softmax y para problemas de regresión se usa la función lineal (identidad).
      • Una red neuronal profunda posee los siguientes componentes fundamentales:
        • Nodos y capas: determinan la complejidad de la red. Las capas se consideran densas cuando están completamente conectados todos los nodos con capas sucesivas. Más capas y nodos agregados a la red, brindarán mayor oportunidad de extraer nueva información (características). La capa de entrada está constituida por las variables predictoras originales, además de esta capa también se encuentran capas ocultas y capa de salida. Las capas ocultas (hidden layers) pueden ser consideradas como hiperparámetros sobre los cuales no hay una dirección unificada de procesamiento; la cantidad de nodos que se incorporen en estas capas estará determinado por el número de atributos. Apelando al principio de parsimonia, lo ideal es encontrar un modelo de red simple y computacionalmente óptimo. La capa de salida está determinada por el tipo de variable respuesta, para problemas de regresión se predicen valores numéricos y para clasificación se predicen probabilidades para una etiqueta o clase específica.
        • Activación: elección de la función de activación para asignación de pesos o ponderaciones a los inputs.
      • Software para deep learning con R o Python:
      • Algunos casos de uso:
        • Redes neuronales convolucionales en identificación de objetivos en sistemas de defensa (d’Acremont et al., 2019).
        • Reconocimiento facial con CNN (Yang et al., 2018).
        • Detección de pupila rugosa con CNN a través de imágenes infrarojo (Won et al., 2019).
        • Método mejorado a través de deep learning para condición corporal en vacas lecheras (Huang et al., 2019).
        • CNN para identificación automática de patologías en plantas (Boulent et al., 2019).
        • Modelación de características del suelo a través de redes neuronales para irrigación automática (Adeyemi et al., 2018).
        • Predicción del rendimiento de cultivos con deep learning (Khaki & Wang, 2019).
        • Monitoreo de cultivos de pequeños productores a través de redes neuronales artificiales usando imágenes satelitales de alta resolución espacial (Xie et al., 2019).

Idea Intuitiva de Red Neuronal Profunda

Ajuste del modelo

# Modelo secuencial: load
mod_keras <- keras_model_sequential()
# Añadiendo capas: configuración del modelo
mod_keras %>% 
  layer_dense(units = 9, activation = "relu", input_shape = c(9)) %>% 
  layer_batch_normalization() %>% 
  layer_dropout(rate = 0.2) %>%
  layer_dense(units = 9, activation = "relu") %>% 
  layer_batch_normalization() %>% 
  layer_dense(units = 9, activation = "relu") %>% 
  layer_batch_normalization() %>% 
  layer_dense(units = 2, activation = "sigmoid")
# Compilando el modelo
mod_keras %>% 
  compile(loss = "binary_crossentropy",
          optimizer = optimizer_rmsprop(),
          metrics = "accuracy")
# Ajuste del modelo
mod_nnet <- mod_keras %>% 
  fit(x_train,
      y_train,
      epochs = 200,
      batch_size = 8,
      validation_split = 0.2,
      verbose = FALSE)
  • Desempeño de la red neuronal:

  • Accuracy en testing (matriz de confusión):
Confusion Matrix and Statistics

    Predicho
Real  0  1
   0 14  1
   1  8 11
                                          
               Accuracy : 0.7353          
                 95% CI : (0.5564, 0.8712)
    No Information Rate : 0.6471          
    P-Value [Acc > NIR] : 0.1859          
                                          
                  Kappa : 0.4883          
                                          
 Mcnemar's Test P-Value : 0.0455          
                                          
            Sensitivity : 0.9167          
            Specificity : 0.6364          
         Pos Pred Value : 0.5789          
         Neg Pred Value : 0.9333          
             Prevalence : 0.3529          
         Detection Rate : 0.3235          
   Detection Prevalence : 0.5588          
      Balanced Accuracy : 0.7765          
                                          
       'Positive' Class : 1               
                                          

5. Desempeño de modelos

  • Tabla comparativa de modelos: resultados ordenanos de mayor a menor Accuracy en datos de prueba (test).
  • Comparación de accuracy de modelos (gráfico):

  • Comparación de de modelos (gráfico):

LS0tCnRpdGxlOiAiTWFjaGluZSBMZWFybmluZzogQXByZW5kaXphamUgU3VwZXJ2aXNhZG8iCnN1YnRpdGxlOiAiTW9kZWxvcyBkZSBDbGFzaWZpY2FjacOzbjogZGV0ZWNjacOzbiBkZSBjw6FuY2VyIGRlIHNlbm8iCmF1dGhvcjogIkVkaW1lciBEYXZpZCBKYXJhbWlsbG8iCm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgY3NzOiBlc3RpbG8uY3NzCiAgICB0aGVtZTogY29zbW8KICAgIGhpZ2hsaWdodDogemVuYnVybgogICAgZGZfcHJpbnQ6IHBhZ2VkCi0tLQoKYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9CmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gRkFMU0UsCiAgICAgICAgICAgICAgICAgICAgICB3YXJuaW5nID0gRkFMU0UsCiAgICAgICAgICAgICAgICAgICAgICBlcnJvciA9IEZBTFNFLAogICAgICAgICAgICAgICAgICAgICAgbWVzc2FnZSA9IEZBTFNFLAogICAgICAgICAgICAgICAgICAgICAgZmlnLmFsaWduID0gImNlbnRlciIsCiAgICAgICAgICAgICAgICAgICAgICBmaWcud2lkdGggPSA4LjUsCiAgICAgICAgICAgICAgICAgICAgICBmaWcuaGVpZ2h0ID0gNSwKICAgICAgICAgICAgICAgICAgICAgIGNvbGxhcHNlID0gVFJVRSkKYGBgCgo8Y2VudGVyPgo8aW1nIHNyYz0iaWEuZ2lmIiB3aWR0aCA9ICI1NzAiIGhlaWdodD0iMzUwIj4KPC9jZW50ZXI+CgojIERhdG9zCgotIFtGdWVudGUgLSBVQ0kgTWFjaGluZSBMZWFybmluZyBSZXBvc2l0b3J5OiBEYXRvcyBkZSBjw6FuY2VyIGRlIHNlbm8uXShodHRwczovL2FyY2hpdmUuaWNzLnVjaS5lZHUvbWwvZGF0YXNldHMvQnJlYXN0K0NhbmNlcitDb2ltYnJhKQotICoqUHJvYmxlbWE6KiogZW50cmVuYXIgbW9kZWxvcyBkZSBhcHJlbmRpemFqZSBhdXRvbcOhdGljbyAtICphcHJlbmRpemFqZSBzdXBlcnZpc2FkbyosIHBhcmEgZGV0ZWN0YXIgY8OhbmNlciBkZSBzZW5vLiBMYXMgdmFyaWFibGVzIHNvbiBjb25zdGl0dWlkYXMgY29tbyBwcmVkaWN0b3JlcyBxdWUgcHVlZGVuIHNlciBwYXJhbWV0cml6YWRhcyB5IHNlcnZpciBjb21vIGJpb21hcmNhZG9yZXMgZGUgY8OhbmNlci4KLSBWYXJpYWJsZSByZXNwdWVzdGE6CiAgICAtICoqMCoqOiBwYWNpZW50ZXMgc2FsdWRhYmxlcy4KICAgIC0gKioxKio6IHBhY2llbnRlcyBlbmZlcm1vcy4KCiMgTWV0b2RvbG9nw61hCgoxLiBMZWN0dXJhIGRlIGRhdG9zLiAgCjIuIEV4cGxvcmFjacOzbiBkZSBkYXRvcy4gIAogIDIuMS4gUHJvcG9yY2nDs24gZGUgZW5mZXJtb3MgeSBzYW5vcy4gIAogIDIuMi4gRGlzdHJpYnVjaW9uZXMgZGUgcHJvYmFiaWxpZGFkLiAgIAogIDIuMy4gQ29ycmVsYWNpb25lcyBkZSB2YXJpYWJsZXMgcHJlZGljdG9yYXMuICAKICAyLjQuIFRlbmRlbmNpYXMgcG9yIGVkYWQgZGVsIHBhY2llbnRlLiAgCjMuIFByZXByb2Nlc2FtaWVudG8gZGUgZGF0b3MuICAKICAzLjEuIFBhcnRpY2nDs24gZW4gKlRyYWluaW5nKiB5ICpUZXN0aW5nKi4gIAogIDMuMi4gTm9ybWFsaXphY2nDs24gZGUgdmFyaWFibGVzLiAgICAKICAzLjMuIFJlY29kaWZpY2FjacOzbiBkZSB2YXJpYWJsZSByZXNwdWVzdGEgKioqQ2xhc3NpZmljYXRpb24qKiogKCIwIjogcGFjaWVudGUgc2FubyB5ICIxIjogcGFjaWVudGUgZW5mZXJtbykuICAKICAzLjQuIFByZXByb2Nlc2FtaWVudG8gZXNwZWPDrWZpY28gcGFyYSBpbXBsZW1lbnRhY2nDs24gZGUgcmVkZXMgbmV1cm9uYWxlcyBwcm9mdW5kYXMgKCpkZWVwIGxlYXJuaW5nKikgY29uIGJpYmxpb3RlY2FzIGBrZXJhc2AgeSBgbGltZWAuICAgIAo0LiBBanVzdGUgZGUgbW9kZWxvcyBkZSBNYWNoaW5lIExlYXJuaW5nIC0gU3VwZXJ2aXNlZCBMZWFybmluZzogIAogIDQuMS4gSy1OZWFyZXN0IE5laWdoYm9yLiAgICAKICA0LjIuIE5haXZlIEJheWVzLiAgICAKICA0LjMuIFJlZ3Jlc2nDs24gTG9nw61zdGljYS4gICAgCiAgNC40LiDDgXJib2wgZGUgZGVjaXNpw7NuIChjbGFzaWZpY2FjacOzbikuICAgICAgCiAgNC41LiBSYW5kb20gRm9yZXN0LiAgICAKICA0LjYuIFN1cHBvcnQgVmVjdG9yIE1hY2hpbmUuICAgICAgCiAgNC43LiBHcmFkaWVudCBCb29zdGluZy4gICAgICAKICA0LjguIERlZXAgTGVhcm5pbmcgKE11bHRpbGF5ZXIgUGVyY2VwdHJvbikuICAgIAo1LiBEZXNlbXBlw7FvIGRlIGxvcyBtb2RlbG9zOiBjb21wYXJhY2nDs24gZGUgKkFjY3VyYWN5KiBlbiAqVHJhaW5pbmcqIHkgKlRlc3RpbmcqLiAgCjYuIFJlZmVyZW5jaWFzCgojIFJlc3VsdGFkb3Mgey50YWJzZXQgLnRhYnNldC1mYWRlIC50YWJzZXQtcGlsbHN9CgojIyAxLiBMZWN0dXJhIGRlIGRhdG9zCgpgYGB7cn0KbGlicmFyeShyZWFkeGwpCmxpYnJhcnkoZHBseXIpCmRmX3Nlbm8gPC0gcmVhZF94bHN4KCJEYXRvcy9DYW5jZXJfU2Vuby54bHN4IikKZGZfc2VubyRDbGFzc2lmaWNhdGlvbiA8LSBhcy5udW1lcmljKGRmX3Nlbm8kQ2xhc3NpZmljYXRpb24pLTEKZGZfc2VubwpgYGAKCiMjIDIuIEFuw6FsaXNpcyBleHBsb3JhdG9yaW8KCiMjIyBQcm9wb3JjacOzbiBkZSBlbmZlcm1vcyB5IHNhbm9zCgpgYGB7cn0KIyBQcm9wb3JjacOzbiBkZSBlbmZlcm1vcyB5IHNhbm9zCnByb3AudGFibGUodGFibGUoZGZfc2VubyRDbGFzc2lmaWNhdGlvbikpKjEwMApgYGAKCiMjIyBEaXN0cmlidWNpb25lcyAKPGNlbnRlcj4KYGBge3IsIGZpZy5oZWlnaHQ9Nn0KbGlicmFyeShSQ29sb3JCcmV3ZXIpCmNvbG9yZXMyIDwtIGJyZXdlci5wYWwobiA9IDYsIG5hbWUgPSAiU2V0MSIpW2MoMiw1KV0KbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KHRpZHlyKQpkZl9zZW5vICU+JSAKICBtdXRhdGUoQ2xhc3NpZmljYXRpb24gPSBmYWN0b3IoQ2xhc3NpZmljYXRpb24pKSAlPiUgCiAgZ2F0aGVyKGtleSA9ICJ2YXJpYWJsZSIsIHZhbHVlID0gInZhbG9yIiwgLUNsYXNzaWZpY2F0aW9uKSAlPiUgCiAgZ2dwbG90KGRhdGEgPSAuLCBhZXMoeCA9IHZhbG9yLCBmaWxsID0gQ2xhc3NpZmljYXRpb24pKSArCiAgZmFjZXRfd3JhcChmYWNldHMgPSB+dmFyaWFibGUsIHNjYWxlcyA9ICJmcmVlIikgKwogIGdlb21fZGVuc2l0eShhbHBoYSA9IDAuNykgKwogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGNvbG9yZXMyKSArCiAgbGFicyh4ID0gIiIsIHkgPSAiIiwgZmlsbCA9ICJDbGFzaWZpY2FjacOzbjoiKSArCiAgdGhlbWVfdGVzdCgpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIikKYGBgCjwvY2VudGVyPgoKIyMjIENvcnJlbGFjaW9uZXMKPGNlbnRlcj4KYGBge3IsIGZpZy53aWR0aD05LCBmaWcuaGVpZ2h0PTZ9CmxpYnJhcnkoY29ycnBsb3QpCm10eF9jb3IgPC0gY29yKGRmX3Nlbm9bLCAtMTBdKQpjb3JycGxvdChtdHhfY29yLCBtZXRob2QgPSAicGllIiwgdHlwZSA9ICJ1cHBlciIsIGRpYWcgPSBGQUxTRSwKICAgICAgICAgdGwuc3J0ID0gNDAsIHRsLmNvbCA9ICJibGFjayIsIHRsLmNleCA9IDEsCiAgICAgICAgICAgY29sID0gYnJld2VyLnBhbChuID0gNSwgbmFtZSA9ICJTcGVjdHJhbCIpLCBvcmRlciA9ICJoY2x1c3QiKQpgYGAKPC9jZW50ZXI+CgojIyMgVGVuZGVuY2lhcyBwb3IgZWRhZAo8Y2VudGVyPgpgYGB7ciwgZmlnLmhlaWdodD02LjUsIGZpZy53aWR0aD04LjV9CmRmX3Nlbm8gJT4lIAogIG11dGF0ZShDbGFzc2lmaWNhdGlvbiA9IGZhY3RvcihDbGFzc2lmaWNhdGlvbikpICU+JSAKICBnYXRoZXIoa2V5ID0gInZhcmlhYmxlIiwgdmFsdWUgPSAidmFsb3IiLCAtYyhDbGFzc2lmaWNhdGlvbiwgQWdlKSkgJT4lIAogIGdncGxvdChkYXRhID0gLiwgYWVzKHggPSBBZ2UsIHkgPSB2YWxvciwgY29sb3IgPSBDbGFzc2lmaWNhdGlvbikpICsKICBmYWNldF93cmFwKGZhY2V0cyA9IH52YXJpYWJsZSwgc2NhbGVzID0gImZyZWUiKSArCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuNikgKwogIGdlb21fc21vb3RoKHNlID0gRkFMU0UsIGx3ZCA9IDEuMikgKwogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBjb2xvcmVzMikgKwogIGxhYnMoeCA9ICJFZGFkIChhw7FvcykiLCB5ID0gIiIsIGZpbGwgPSAiQ2xhc2lmaWNhY2nDs246IikgKwogIHRoZW1lX3Rlc3QoKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIpCmBgYAo8L2NlbnRlcj4KCiMjIDMuIFByZXByb2Nlc2FtaWVudG8KCiMjIyBUcmFpbiB5IFRlc3QKCi0gUHJpbWVybyBzZSBjb252aWVydGUgZW4gYGZhY3RvcmAgbGEgdmFyaWFibGUgcmVzcHVlc3RhIHF1ZSBlc3TDoSBjb2RpZmljYWRhIGNvbW8gYG51bWVyaWNgLiBFc3RlIHBhc28gZXMgaW1wb3J0YW50ZSBwYXJhIGdhcmFudGl6YXIgcXVlIGxhcyBwcm9wb3JjaW9uZXMgZGUgY2FkYSBjbGFzZSAoZW5mZXJtbyBvIHNhbm8pIHNlIG1hbnRlbmdhbiBlbiBsYXMgYmFzZXMgZGUgZGF0b3MgdHJhaW4geSB0ZXN0LgotIFNlIHVzYSBsYSBiaWJsaW90ZWNhIGBjYXJldGAgY29uIGxhIGZ1bmNpw7NuIGBDcmVhdGVEYXRhUGFydGl0aW9uKClgLgotIExhIHBhcnRpY2nDs24gc2UgcmVhbGl6w7MgNzAtMzAlLCBwYXJhIHRyYWluIHkgdGVzdCwgcmVzcGVjdGl2YW1lbnRlLgotICoqTm90YToqKiBsYSBmdW5jacOzbiBpbXBsZW1lbnRhZGEgdGllbmUgZW4gY3VlbnRhIGxhIHByb3BvcmNpw7NuIGRlIGxhIHZhcmlhYmxlIHJlc3B1ZXN0YSBwYXJhIGVsIHRyYWluIHkgdGVzdCwgbm8gb2JzdGFudGUsIG5vIGhhY2Ugw6luZmFzaXMgZW4gbGEgdmFyaWFiaWxpZGFkIGRlIHByZWRpY3RvcmVzIChwcm9ibGVtYXMgZGUgdmFyaWFuemEgaWd1YWwgYSBjZXJvKS4KCmBgYHtyLCBlY2hvPVRSVUV9CnNldC5zZWVkKDEwMDApCmxpYnJhcnkoY2FyZXQpCmRmX3Nlbm8kQ2xhc3NpZmljYXRpb24gPC0gYXMuZmFjdG9yKGRmX3Nlbm8kQ2xhc3NpZmljYXRpb24pCmluZCA8LSBjcmVhdGVEYXRhUGFydGl0aW9uKHkgPSBkZl9zZW5vJENsYXNzaWZpY2F0aW9uLCB0aW1lcyA9IDEsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHAgPSAwLjcsIGxpc3QgPSBGQUxTRSkKZGZfdHJhaW4gPC0gZGZfc2Vub1tpbmQsIF0KZGZfdGVzdGkgPC0gZGZfc2Vub1staW5kLCBdCmBgYAoKIyMjIFByZXByb2Nlc2FtaWVudG8gcGFyYSBga2VyYXNgCgotIFBhcmEgYWp1c3RhciBlbCBtb2RlbG8gc2VjdWVuY2lhbCBjb24gYGtlcmFzYCBlcyBuZWNlc2FyaW8gY29lcmNpb25hciBsYXMgYmFzZXMgZGUgZGF0b3MgKGBkYXRhLmZyYW1lYCkgdHJhaW4geSB0ZXN0IGEgbGEgY2xhc2UgYG1hdHJpeGAsIGRlIGxhIG1pc21hIG1hbmVyYSBxdWUgcXVlZMOzIGNvZXJjaW9uYWRhIGxhIHZhcmlhYmxlIHJlc3B1ZXN0YSBlbiBlbCBwYXNvIGFudGVyaW9yLgotIEVuIGBrZXJhc2AgeSBtdWNoYXMgb3RyYXMgYmlibGlvdGVjYXMgcGFyYSBNYWNoaW5lIExlYXJuaW5nIGxvcyBtb2RlbG9zIHNlIGV4cHJlc2FuIGVuIHTDqXJtaW5vcyBtYXRyaWNpYWxlczsgZXN0byBkZWJpZG8gYSBsYSBlZmljaWVuY2lhIGFsZ2VicsOhaWNhIHBhcmEgcHJvY2VzYW1pZW50byBkZSBpbmZvcm1hY2nDs24uIEVzdGUgcmVxdWlzaXRvIG5vIGVzIG9ibGlnYXRvcmlvIGVuIHRvZG9zIGxvcyBtb2RlbG9zLCBwdWVzdG8gcXVlIGFsZ3Vub3MgYWNlcHRhbiBsYXMgdmFyaWFibGVzIGRlY2xhcmFkYXMgY29tbyBmw7NybXVsYSAoYGZvcm11bGEgPSB5IH4geCArIHcgKyB6YCkuCgpgYGB7ciwgZWNobz1UUlVFfQojIENvZXJjacOzbiBhIG1hdHJpegp4X3RyYWluIDwtIGFzLm1hdHJpeChkZl90cmFpblssIDE6OV0pCnhfdGVzdGkgPC0gYXMubWF0cml4KGRmX3Rlc3RpWywgMTo5XSkKCiMgRWxpbWluYW5kbyBkaW1lbnNpb25lcwpkaW1uYW1lcyh4X3RyYWluKSA8LSBOVUxMCmRpbW5hbWVzKHhfdGVzdGkpIDwtIE5VTEwKYGBgCgojIyMgTm9ybWFsaXphY2nDs24KCi0gTGEgW25vcm1hbGl6YWNpw7NuIG8gZXN0YW5kYXJpemFjacOzbl0oaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvRmVhdHVyZV9zY2FsaW5nKSBkZSB2YXJpYWJsZXMgcHVlZGUgc2VyIGVmZWN1dGFkYSBkZSBtYW5lcmFzIGRpZmVyZW50ZXM6CiAgICAtICoqRXNjYWxhZG8gTWluLU1heCoqOiAkel9pID0geC1tw61uaW1vX3gvKG3DoXhpbW9feC1tw61uaW1vX3gpJC4gTGEgdmFyaWFibGUgcXVlZGEgYWNvdGFkYSBlbnRyZSBbMCwgMV0uCiAgICAtICoqRXN0YW5kYXJpemFjacOzbioqOiAkeCAtIFxtdV94L1xzaWdtYV94JC4gTGEgdmFyaWFibGUgcXVlZGEgYWNvdGFkYSBlbnRyZSBbLTEsIDFdLCBjb24gJFxtdSA9IDAkIHkgJFxzaWdtYSA9IDEkLgotIExhIG5vcm1hbGl6YWNpw7NuIG8gZXN0YW5kYXJpemFjacOzbiBkZSB2YXJpYWJsZXMgZXMgbmVjZXNhcmlhIHBhcmEgbGEgZWplY3VjacOzbiBkZSBhbGd1bm9zIGFsZ29yaXRtb3MgY29tbyBsYXMgcmVkZXMgbmV1cm9uYWxlcywgbGFzIG3DoXF1aW5hcyBkZSBzb3BvcnRlIHZlY3RvcmlhbCBvIGxhIHJlZ3Jlc2nDs24gbG9nw61zdGljYS4gRXN0ZSBwcm9jZXNvIGVzIG5lY2VzYXJpbyBjdWFuZG8gbGFzIHVuaWRhZGVzIGRlIGxhcyB2YXJpYWJsZXMgc29uIGRpZmVyZW50ZXMsIHRyYXRhbmRvIGRlIGV2aXRhciBlbCBzb2JyZXBlc28gKG1heW9yIGltcG9ydGFuY2lhIHBhcmEgZWwgbW9kZWxvKSBlbiB2YXJpYWJsZXMgcXVlIHBvc2VhbiBtYXlvciB2YXJpYWJpbGlkYWQgKHZhcmlhbnphKS4KLSBFbiBSIHNlIHB1ZWRlbiBpbXBsZW1lbnRhciBsYXMgZnVuY2lvbmVzIGBzY2FsZSgpYCBwYXJhIGVzY2FsYXIgbyBlc3RhbmRhcml6YXIgbGFzIHZhcmlhYmxlcyB5IGxhIGZ1bmNpw7NuIGBub3JtYWxpemUoKWAgZGVsIHBhcXVldGUgYGtlcmFzYCBwZXJtaXRlIGFwbGljYXIgbGEgbm9ybWFsaXphY2nDs24gbWluLW1heC4KLSBQYXJhIGVzdGUgZWplbXBsbyBsYXMgdmFyaWFibGVzIHNlcsOhbiBub3JtYWxpemFkYXMgY29uIGxhIGZ1bmNpw7NuIGBub3JtYWxpemUoKWAgZGUgbGEgYmlibGlvdGVjYSBga2VyYXNgLgoKYGBge3IsIGVjaG89VFJVRX0KbGlicmFyeShrZXJhcykKZGZfdHJhaW5bLCAxOjldIDwtIG5vcm1hbGl6ZSh4X3RyYWluWywgMTo5XSkKZGZfdGVzdGlbLCAxOjldIDwtIG5vcm1hbGl6ZSh4X3Rlc3RpWywgMTo5XSkKYGBgCgotIEEgbWFuZXJhIGRlIGVqZW1wbG8gc2UgbXVlc3RyYSBlbCByYW5nbyBkZSBsYXMgdmFyaWFibGVzIGBkZl90cmFpbmAuIENvcnJvYm9yYW5kbyBxdWUgbmluZ3VuYSBkZSBsYXMgdmFyaWFibGVzIHBvc2VlIHZhbG9yZXMgaW5mZXJpb3JlcyBhIGNlcm8gbyBtYXlvcmVzIGEgMS4KCmBgYHtyLCBlY2hvPVRSVUUsIGNvbGxhcHNlPVRSVUV9CmFwcGx5KGRmX3RyYWluLCAyLCByYW5nZSkKYGBgCgojIyMgUmVjb2RpZmljYWNpw7NuIGRlIGBDbGFzc2lmaWNhdGlvbmAKCi0gUGFyYSBlbCBtb2RlbG8gZGUgcmVkIG5ldXJvbmFsIHByb2Z1bmRhIGltcGxlbWVudGFkbyBhIHRyYXbDqXMgZGUgYGtlcmFzYCwgZXMgbmVjZXNhcmlvIGNvbnZlcnRpciBsYSB2YXJpYWJsZSByZXNwdWVzdGEgZW4gWypEdW1teSpdKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL0R1bW15X3ZhcmlhYmxlXyhzdGF0aXN0aWNzKSksIGVuIHJlZ3Jlc2nDs24gc2Ugc3VlbGUgaGFibGFyIGRlIHZhcmlhYmxlcyAqaW5kaWNhZG9yYXMqLCBhdW5xdWUgdGFtYmnDqW4gZXMgY29ub2NpZG8gY29tbyBbKm9uZS1ob3QgZW5jb2RpbmcqIChjb2RpZmljYWNpw7NuIGFjdGl2YSldKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL09uZS1ob3QpIGVuIE1hY2hpbmUgTGVhcm5pbmcuCi0gTGEgZnVuY2nDs24gYHRvX2NhdGVnb3JpY2FsKClgIGRlbCBwYXF1ZXRlIGBrZXJhc2AgcGVybWl0ZSBlc3RhIGNvbnZlcnNpw7NuLgoKYGBge3IsIGVjaG89VFJVRX0KeV90cmFpbiA8LSB0b19jYXRlZ29yaWNhbChkZl90cmFpbiRDbGFzc2lmaWNhdGlvbikKeV90ZXN0aSA8LSB0b19jYXRlZ29yaWNhbChkZl90ZXN0aSRDbGFzc2lmaWNhdGlvbikKYGBgCgojIyA0LiBNb2RlbG9zCgotIFNlIHVzYW4gbGFzIGJpYmxpb3RlY2FzIGBjYXJldGAsIHkgYGtlcmFzYCBwYXJhIGVudHJlbmFyIGxvcyBtb2RlbG9zIGRlIE1hY2hpbmUgTGVhcm5pbmcuCi0gQSB0cmF2w6lzIGRlIHZhbGlkYWNpw7NuIGNydXphZGEgKGsgPSAxMCB5IHJlcGV0aWNpb25lcyA9IDEwKSBzZSBldmFsw7phIGVsIGRlc2VtcGXDsW8gZGUgbG9zIG1vZGVsb3MuCi0gTGEgbcOpdHJpY2EgYSB0ZW5lciBlbiBjdWVudGEgcGFyYSBjb21wYXJhciBsb3MgbW9kZWxvcyBlcyBsYSBgQWNjdXJhY3lgIG8gcHJlY2lzacOzbiBkZWwgbW9kZWxvLgotIFRvZG9zIGxvcyBwcm9jZWRpbWllbnRvcyBjb24gYGNhcmV0YCBzb24gcGFyYWxlbGl6YWRvcyBjb24gbGEgYmlibGlvdGVjYSBgZG9NQ2AuCgojIyMgSy1OZWFyZXN0IE5laWdoYm9yCgotICoqQ2FyYWN0ZXLDrXN0aWNhcyBnZW5lcmFsZXM6KioKICAgIC0gVW5vIGRlIGxvcyBhbGdvcml0bW9zIGRlIE1hY2hpbmUgTGVhcm5pbmcgbcOhcyBzaW1wbGVzLgogICAgLSBGdW5kYW1lbnRhZG8gZW4gaWRlbnRpZmljYXIgb2JzZXJ2YWNpb25lcyBxdWUgc2UgYXNlbWVqZW4gYSBsYXMgb2JzZXJ2YWNpb25lcyB2ZWNpbmFzIHBhcmEgcmVhbGl6YXIgcHJlZGljY2lvbmVzLgogICAgLSBVbmEgbnVldmEgb2JzZXJ2YWNpw7NuIHNlIHByZWRpY2UgZW4gZnVuY2nDs24gZGUgc3UgInNpbWlsaXR1ZCIgY29uIG90cmFzIG9ic2VydmFjaW9uZXMgKCJ2ZWNpbm9zIG3DoXMgY2VyY2Fub3MiKS4KICAgIC0gTGEgInNpbWlsaXR1ZCIgY29uIG90cmFzIG9ic2VydmFjaW9uZXMgZXMgY3VhbnRpZmljYWRhIGNvbiBtw6l0cmljYXMgZGUgZGlzdGFuY2lhLCBwb3IgZWplbXBsbywgZGlzdGFuY2lhICpFdWNsaWRpYW5hKi4KICAgIC0gUHVlZGVuIHNlciBjb21wdXRhY2lvbmFsbWVudGUgaW5lZmljaWVudGVzIHkgc29uIGNvbm9jaWRvcyBjb21vICpsYXp5IGxlYXJuZXJzKiAoW0N1bm5pbmdoYW0gJiBEZWxhbnksIDIwMDddKGh0dHBzOi8vcGRmcy5zZW1hbnRpY3NjaG9sYXIub3JnLzYwZjMvODlmMDU2YWUyNTBkODk2ZWI4MGI0MTRhOTMzNTM3ZDUxZDZjLnBkZikpLgogICAgLSBIaXBlcnBhcsOhbWV0cm8gYGtgOiBuw7ptZXJvIGRlIG9ic2VydmFjaW9uZXMgdmVjaW5hcyBlbXBsZWFkYXMgcGFyYSByZWFsaXphciBsYSBwcmVkaWNjacOzbi4KICAgIC0gQWxndW5vcyBjYXNvcyBkZSB1c286CiAgICAgICAgLSBDYXRlZ29yaXphY2nDs24gZGUgdGV4dG8gKFtKaWFuZyBldCBhbC4sIDIwMTJdKGh0dHBzOi8vd3d3LnJlc2VhcmNoZ2F0ZS5uZXQvcHVibGljYXRpb24vMjMyNDA2NTIzX0FuX0ltcHJvdmVkX2stTmVhcmVzdF9OZWlnaGJvcl9BbGdvcml0aG1fZm9yX1RleHRfQ2F0ZWdvcml6YXRpb24pKS4KICAgICAgICAtIERldGVjY2nDs24gZGUgU3BhbSBlbiBUd2l0dGVyIChbTWNDb3JkICYgQ2h1YWgsIDIwMTFdKGh0dHA6Ly93Ym94MC5jc2UubGVoaWdoLmVkdS9+Y2h1YWgvcHVibGljYXRpb25zL2F0YzExX3NwYW1fY2FtZXJhLnBkZikpLgogICAgICAgIC0gQ2xhc2lmaWNhY2nDs24gZGUgdHVtb3JlcyBjZXJlYnJhbGVzIGNvbiBpbcOhZ2VuZXMgaGlwZXItZXNwZWN0cmFsZXMgKFtGbG9yaW1iaSBldCBhbC4sIDIwMThdKGh0dHBzOi8vd3d3Lm5jYmkubmxtLm5paC5nb3YvcG1jL2FydGljbGVzL1BNQzYwNjg0NzcvKSkuCiAgICAgICAgLSBTZWxlY2Npw7NuIGRlIGNhcmFjdGVyw61zdGljYXMgLSAqRmVhdHVyZSBFbmdpbmVlcmluZyogKFtTaGVuZ2dpYW8gZXQgYWwuLCAyMDExXShodHRwczovL3d3dy5uY2JpLm5sbS5uaWguZ292L3BtYy9hcnRpY2xlcy9QTUMzMjgxMDczLykpLgoKYGBge3IsIGVjaG89VFJVRX0KIyBQYXJhbGVsaXphY2nDs24gZGVsIHByb2Nlc28KbGlicmFyeShkb01DKQpsaWJyYXJ5KHBhcmFsbGVsKQpyZWdpc3RlckRvTUMoY29yZXMgPSBkZXRlY3RDb3JlcygpKQoKIyBTdWJtdWVzdHJhcyB5IHJlcGV0aWNpb25lcwpwYXJ0aWNpb25lcyAgPC0gMTAKcmVwZXRpY2lvbmVzIDwtIDEwCgojIERlZmluaWVuZG8gaGlwZXJwYXLDoW1ldHJvIGsKaGlwZXJwYXJhbWV0cm9zIDwtIGV4cGFuZC5ncmlkKGsgPSBzZXEoMSwgMzAsIDIpKQoKIyBTZW1pbGxhcwpzZXQuc2VlZCgxMjMpCnNlZWRzIDwtIHZlY3Rvcihtb2RlID0gImxpc3QiLCBsZW5ndGggPSAocGFydGljaW9uZXMgKiByZXBldGljaW9uZXMpICsgMSkKZm9yIChpIGluIDE6KHBhcnRpY2lvbmVzICogcmVwZXRpY2lvbmVzKSkgewogIHNlZWRzW1tpXV0gPC0gc2FtcGxlLmludCgxMDAwLCBucm93KGhpcGVycGFyYW1ldHJvcykpIAp9CnNlZWRzW1socGFydGljaW9uZXMgKiByZXBldGljaW9uZXMpICsgMV1dIDwtIHNhbXBsZS5pbnQoMTAwMCwgMSkKCiMgQ29udHJvbCBkZSBlbnRyZW5hbWllbnRvCmNyb3NzX3ZhbCA8LSB0cmFpbkNvbnRyb2woCiAgbWV0aG9kID0gInJlcGVhdGVkY3YiLAogIG51bWJlciA9IHBhcnRpY2lvbmVzLAogIHJlcGVhdHMgPSByZXBldGljaW9uZXMsCiAgcmV0dXJuUmVzYW1wID0gImZpbmFsIiwKICB2ZXJib3NlSXRlciA9IEZBTFNFLAogIGFsbG93UGFyYWxsZWwgPSBUUlVFLAogIHNlZWRzID0gc2VlZHMKKQoKIyBBanVzdGUgZGVsIG1vZGVsbyAoZW50cmVuYW1pZW50bykKc2V0LnNlZWQoMTAwMCkKbW9kX2tubiA8LSB0cmFpbigKICBDbGFzc2lmaWNhdGlvbiB+IC4sCiAgZGF0YSA9IGRmX3RyYWluLAogIG1ldGhvZCA9ICJrbm4iLAogIHR1bmVHcmlkID0gaGlwZXJwYXJhbWV0cm9zLAogIG1ldHJpYyA9ICJBY2N1cmFjeSIsCiAgdHJDb250cm9sID0gY3Jvc3NfdmFsCikKYGBgCgotICoqRXZhbHVhbmRvIGVsICRrJCDDs3B0aW1vOioqCgo8Y2VudGVyPgpgYGB7cn0KZ2dwbG90KG1vZF9rbm4pICsKICBsYWJzKHggPSAiVmVjaW5vcyAoaykiLCB5ID0gIkFjY3VyYWN5IChjcm9zcyB2YWxpZGF0aW9uKSIpICsKICBnZW9tX3BvaW50KGFlcyh4ID0gbW9kX2tubiRiZXN0VHVuZVtbMV1dLCB5ID0gbWF4KG1vZF9rbm4kcmVzdWx0cyRBY2N1cmFjeSkpLAogICAgICAgICAgICAgY29sb3IgPSAicmVkIiwgcGNoID0gMTcsIHNpemUgPSAzKSArCiAgbGFicyhzdWJ0aXRsZSA9ICJLLU5lYXJlc3RzIE5laWdoYm9ycyIpICsKICB0aGVtZV90ZXN0KCkKYGBgCjwvY2VudGVyPgoKLSAqKlJlc3VsdGFkb3MgZGVsIG1vZGVsbyAoZGUgbWF5b3IgYSBtZW5vciBhY2N1cmFjeSk6KioKCmBgYHtyfQptb2Rfa25uJHJlc3VsdHMgJT4lIAogIGFycmFuZ2UoZGVzYyhBY2N1cmFjeSkpCmBgYAoKLSAqKkFjY3VyYWN5IGVuIHRlc3RpbmcgKG1hdHJpeiBkZSBjb25mdXNpw7NuKToqKgoKYGBge3J9Cm1vZF9maW5hbF9rbm4gPC0gbW9kX2tubiRmaW5hbE1vZGVsCnRlc3RfcHJlZGljX2tubiA8LSBwcmVkaWN0KG1vZF9maW5hbF9rbm4sIG5ld2RhdGEgPSBkZl90ZXN0aVssIC0xMF0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHR5cGUgPSAiY2xhc3MiKQoKY29uZnVzaW9uTWF0cml4KHRhYmxlKGRmX3Rlc3RpJENsYXNzaWZpY2F0aW9uLCB0ZXN0X3ByZWRpY19rbm4sIAogICAgICAgICAgICAgICAgICAgICAgZG5uID0gYygiUmVhbCIsICJQcmVkaWNobyIpKSwKICAgICAgICAgICAgICAgIHBvc2l0aXZlID0gIjEiKQpgYGAKCiMjIyBOYWl2ZSBCYXllcwoKLSAqKkNhcmFjdGVyw61zdGljYXM6KioKICAgIC0gU2UgZnVuZGFtZW50YSBlbiBlbCBjw6FsY3VsbyBkZSBwcm9iYWJpbGlkYWRlcyBjb25kaWNpb25hbGVzIGJhc2FkbyBlbiBlbCBbVGVvcmVtYSBkZSBCYXllcy5dKGh0dHBzOi8vZXMud2lraXBlZGlhLm9yZy93aWtpL1Rlb3JlbWFfZGVfQmF5ZXMpCiAgICAtIEVsIGFsZ29yaXRtbyBhc3VtZSBxdWUgZXhpc3RlIGluZGVwZW5kZW5jaWEgZW50cmUgbGFzIHZhcmlhYmxlcyBwcmVkaWN0b3JhcywgZGUgYWjDrSBxdWUgc3Ugbm9tYnJlIHNlYSAqbmFpdmUqIChpbmdlbnVvIGVuIGluZ2zDqXMpLiBFbiBsYSB2aWRhIHJlYWwgZXN0YSBzdXBvc2ljacOzbiBkZSBpbmRlcGVuZGVuY2lhIHJhcmEgdmV6IHNlIGN1bXBsZSwgbm8gb2JzdGFudGUsIHByb3BvcmNpb25hIHJlc3VsdGFkb3MgZmF2b3JhYmxlcyBlbiB2YXJpZWRhZCBkZSBhcGxpY2FjaW9uZXMuCiAgICAtIFBvc2VlIHRyZXMgaGlwZXJwYXLDoW1ldHJvczoKICAgICAgICAtIGB1c2VrZXJuZWxgOiBgVFJVRWAgcGFyYSB1dGlsaXphciB1biBrZXJuZWwgcXVlIGVzdGltZSBsYSBkZW5zaWRhZCBvIGBGQUxTRWAgcGFyYSBhc3VtaXIgdW5hIGZ1bmNpw7NuIGRlIGRlbnNpZGFkIGdhdXNzaWFuYS4KICAgICAgICAtIGBmTGA6IGBmTCA9IDFgIHBhcmEgYXBsaWNhciBlbCBmYWN0b3IgZGUgY29ycmVjY2nDs24gZGUgKkxhcGxhY2UqLiBEZSB1dGlsaWRhZCBjdWFuZG8gc2UgdGllbmVuIGV2ZW50b3MgbyBjb25qdW50b3MgdmFjaW9zLCBlcyBkZWNpciwgYXVzZW5jaWEgZGUgaW5mb3JtYWNpw7NuIHF1ZSBpbXBpZGUgZWwgY8OhbGN1bG8gZGUgcHJvYmFiaWxpZGFkZXMgZGUgbWFuZXJhIGNvcnJlY3RhLgogICAgICAgIC0gYGFkanVzdGA6IHBhcsOhbWV0cm8gcXVlIGhhY2UgcGFydGUgZGUgbGEgZnVuY2nDs24gYGRlbnNpdHkoKWAgZW4gY2FzbyBkZSBgdXNla2VybmVsID0gVFJVRWAuCiAgICAtIEFsZ3Vub3MgdXNvczoKICAgICAgICAtIENsYXNpZmljYWNpw7NuIGRlIHNlY3VlbmNpYXMgZGUgUk5BIGVuIHRheG9ub23DrWEgYmFjdGVyaWFuYSAoW1dhbmcgZXQgYWwuLCAyMDA3XShodHRwczovL3d3dy5uY2JpLm5sbS5uaWguZ292L3BtYy9hcnRpY2xlcy9QTUMxOTUwOTgyLykpLgogICAgICAgIC0gU2VsZWNjacOzbiBkZSBjYXJhY3RlcsOtc3RpY2FzIHBhcmEgZW50cmVuYW1pZW50byBkZSBTdXBwb3J0IFZlY3RvciBNYWNoaW5lIChbQ2luZWxsaSBldCBhbC4sIDIwMTddKGh0dHBzOi8vd3d3Lm5jYmkubmxtLm5paC5nb3YvcG1jL2FydGljbGVzL1BNQzU4NjAzODgvKSkuCiAgICAgICAgLSBFdmVudG9zIGFkdmVyc29zIGRlIGbDoXJtYWNvcyBjb24gYmFzZSBlbiBjaXRhcyBkZSBQdWJNZWQgKFtXYW5nIGV0IGFsLiwgMjAxMV0oaHR0cHM6Ly93d3cubmNiaS5ubG0ubmloLmdvdi9wbWMvYXJ0aWNsZXMvUE1DMzI0MzIwNi8pKS4KICAgICAgICAtIEV4dHJhY2Npw7NuIGRlIGNhcmFjdGVyw61zdGljYXMgcGFyYSBjbGFzaWZpY2FjacOzbiBkZSB0ZXh0byAoW1NhcmthciBldCBhbC4sIDIwMTRdKGh0dHBzOi8vd3d3Lm5jYmkubmxtLm5paC5nb3YvcG1jL2FydGljbGVzL1BNQzQ4OTcyODcvKSkuCiAgICAgICAgLSBFc3RpbWFjacOzbiBkZSB1YmljYWNpw7NuIChnZW9wb3NpY2lvbmFtaWVudG8pIHByb2JhYmlsw61zdGljYSAoW1NoYXVlciwgMjAxOV0oaHR0cHM6Ly93d3cuc2NpZW5jZWRpcmVjdC5jb20vdG9waWNzL2VuZ2luZWVyaW5nL25haXZlLWJheWVzLWNsYXNzaWZpZXIpKS4KCmBgYHtyLCBlY2hvPVRSVUV9CiMgUGFyYWxlbGl6YWNpw7NuIGRlbCBwcm9jZXNvCmxpYnJhcnkoZG9NQykKbGlicmFyeShwYXJhbGxlbCkKcmVnaXN0ZXJEb01DKGNvcmVzID0gZGV0ZWN0Q29yZXMoKSkKCiMgU3VibXVlc3RyYXMgeSByZXBldGljaW9uZXMKcGFydGljaW9uZXMgIDwtIDEwCnJlcGV0aWNpb25lcyA8LSAxMAoKIyBEZWZpbmllbmRvIGhpcGVycGFyw6FtZXRybyBrCmhpcGVycGFyYW1ldHJvcyA8LSBkYXRhLmZyYW1lKHVzZWtlcm5lbCA9IEZBTFNFLCBmTCA9IDAgLCBhZGp1c3QgPSAwKQoKIyBTZW1pbGxhcwpzZXQuc2VlZCgxMjMpCnNlZWRzIDwtIHZlY3Rvcihtb2RlID0gImxpc3QiLCBsZW5ndGggPSAocGFydGljaW9uZXMgKiByZXBldGljaW9uZXMpICsgMSkKZm9yIChpIGluIDE6KHBhcnRpY2lvbmVzICogcmVwZXRpY2lvbmVzKSkgewogIHNlZWRzW1tpXV0gPC0gc2FtcGxlLmludCgxMDAwLCBucm93KGhpcGVycGFyYW1ldHJvcykpIAp9CnNlZWRzW1socGFydGljaW9uZXMgKiByZXBldGljaW9uZXMpICsgMV1dIDwtIHNhbXBsZS5pbnQoMTAwMCwgMSkKCiMgQ29udHJvbCBkZSBlbnRyZW5hbWllbnRvCmNyb3NzX3ZhbCA8LSB0cmFpbkNvbnRyb2woCiAgbWV0aG9kID0gInJlcGVhdGVkY3YiLAogIG51bWJlciA9IHBhcnRpY2lvbmVzLAogIHJlcGVhdHMgPSByZXBldGljaW9uZXMsCiAgcmV0dXJuUmVzYW1wID0gImZpbmFsIiwKICB2ZXJib3NlSXRlciA9IEZBTFNFLAogIGFsbG93UGFyYWxsZWwgPSBUUlVFLAogIHNlZWRzID0gc2VlZHMKKQoKIyBBanVzdGUgZGVsIG1vZGVsbyAoZW50cmVuYW1pZW50bykKc2V0LnNlZWQoMTAwMCkKbW9kX2JheWVzIDwtIHRyYWluKAogIENsYXNzaWZpY2F0aW9uIH4gLiwKICBkYXRhID0gZGZfdHJhaW4sCiAgbWV0aG9kID0gIm5iIiwKICB0dW5lR3JpZCA9IGhpcGVycGFyYW1ldHJvcywKICBtZXRyaWMgPSAiQWNjdXJhY3kiLAogIHRyQ29udHJvbCA9IGNyb3NzX3ZhbAopCmBgYAoKLSAqKlJlc3VsdGFkb3MgZGVsIG1vZGVsbzoqKgoKYGBge3J9Cm1vZF9iYXllcyRyZXN1bHRzICU+JSAKICBhcnJhbmdlKGRlc2MoQWNjdXJhY3kpKQpgYGAKCi0gKipBY2N1cmFjeSBlbiB0ZXN0aW5nIChtYXRyaXogZGUgY29uZnVzacOzbik6KioKCmBgYHtyfQptb2RfZmluYWxfYmF5ZXMgPC0gbW9kX2JheWVzJGZpbmFsTW9kZWwKdGVzdF9wcmVkaWNfYmF5ZXMgPC0gcHJlZGljdChtb2RfZmluYWxfYmF5ZXMsIG5ld2RhdGEgPSBkZl90ZXN0aVssIC0xMF0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHR5cGUgPSAiY2xhc3MiKSRjbGFzcwoKY29uZnVzaW9uTWF0cml4KHRhYmxlKGRmX3Rlc3RpJENsYXNzaWZpY2F0aW9uLCB0ZXN0X3ByZWRpY19iYXllcywgCiAgICAgICAgICAgICAgICAgICAgICBkbm4gPSBjKCJSZWFsIiwgIlByZWRpY2hvIikpLAogICAgICAgICAgICAgICAgcG9zaXRpdmUgPSAiMSIpCmBgYAoKIyMjIFJlZ3Jlc2nDs24gTG9nw61zdGljYQoKLSAqKkNhcmFjdGVyw61zdGljYXM6KioKICAgIC0gQWxnb3JpdG1vcyBkZSBhbXBsaW8gdXNvIGVuIGVzdGFkw61zdGljYSBlIGluY29ycG9yYWRvcyBob3kgZW4gZMOtYSBjb21vIHBhcnRlIGRlIGxhIGNhamEgZGUgaGVycmFtaWVudGFzIHBhcmEgTWFjaGluZSBMZWFybmluZy4KICAgIC0gSGFjZSBwYXJ0ZSBkZSBsb3MgKk1vZGVsb3MgTGluZWFsZXMgR2VuZXJhbGl6YWRvcyAoR0xNKSouCiAgICAtIFBlcm1pdGUgbW9kZWxhciB2YXJpYWJsZXMgY29uIGRpc3RyaWJ1Y2nDs24gZGUgZXJyb3JlcyBubyBnYXVzc2lhbmFzIG8gbm9ybWFsZXMuCiAgICAtIEFwbGljYSB0cmFuc2Zvcm1hY2lvbmVzIHNvYnJlIGxhIHZhcmlhYmxlIHJlc3B1ZXN0YSBvcmlnaW5hbCwgIHRyYXRhbmRvIGRlIGxpbmVhbGl6YXIgbGEgcmVsYWNpw7NuIGNvbiBsYXMgcHJlZGljdG9yYXMgYSB0cmF2w6lzIGRlIHVuYSBmdW5jacOzbiBkZSBlbmxhY2UgKCpsaW5rYWdlKikuCiAgICAtIEVzIGFwbGljYWJsZSBhIHByb2JsZW1hcyBiaW5vbWlhbGVzIHkgbXVsdGlub21pYWxlcy4KICAgIC0gQWxndW5vcyBjYXNvcyBkZSB1c286CiAgICAgICAgLSBFc3R1ZGlvIGNvbXBhcmF0aXZvIGRlIHTDqWNuaWNhcyBkZSBwcmVkaWNjacOzbiBlbiBwYWNpZW50ZXMgY29uIFZJSCAoW0Jpc2FzbyBldCBhbC4sIDIwMThdKGh0dHBzOi8vd3d3Lm5jYmkubmxtLm5paC5nb3YvcG1jL2FydGljbGVzL1BNQzYxMjM5NDkvKSkuCiAgICAgICAgLSBBbsOhbGlzaXMgZGVsIHJpZXNnbyBjYXJkaW92YXNjdWxhciBjb24gZGF0b3MgY2zDrW5pY29zIChbV2VuZyBldCBhbC4sIDIwMTddKGh0dHBzOi8vam91cm5hbHMucGxvcy5vcmcvcGxvc29uZS9hcnRpY2xlP2lkPTEwLjEzNzEvam91cm5hbC5wb25lLjAxNzQ5NDQpKS4KICAgICAgICAtIERldGVjY2nDs24gZGUgYW5vbWFsw61hcyBlbiBlbCBjcmVjaW1pZW50byBmZXRhbCAoW0t1aGxlIGV0IGFsLiwgMjAxOF0oaHR0cHM6Ly93d3cubmNiaS5ubG0ubmloLmdvdi9wbWMvYXJ0aWNsZXMvUE1DNjA5NDQ0Ni8pKS4KICAgICAgICAtIENsYXNpZmljYWNpw7NuIGRlIGVuZmVybWVkYWRlcyBhIHRyYXbDqXMgZGUgYXByZW5kaXphamUgc2VtaXN1cGVydmlzYWRvIChbQ2hhaSBldCBhbC4sIDIwMThdKGh0dHBzOi8vd3d3Lm5jYmkubmxtLm5paC5nb3YvcG1jL2FydGljbGVzL1BNQzYxMTU0NDcvKSkuCiAgICAgICAgLSBBcnTDrWN1bG8gZ3XDrWE6IGVudGVuZGllbmRvIGVsIGFuw6FsaXNpcyBkZSByZWdyZXNpw7NuIGxvZ8Otc3RpY2EgKFtTcGVyYW5kZWksIDIwMTRdKGh0dHBzOi8vd3d3Lm5jYmkubmxtLm5paC5nb3YvcG1jL2FydGljbGVzL1BNQzM5MzY5NzEvKSkuCgpgYGB7ciwgZWNobz1UUlVFfQojIFBhcmFsZWxpemFjacOzbiBkZWwgcHJvY2VzbwpsaWJyYXJ5KGRvTUMpCmxpYnJhcnkocGFyYWxsZWwpCnJlZ2lzdGVyRG9NQyhjb3JlcyA9IGRldGVjdENvcmVzKCkpCgojIFN1Ym11ZXN0cmFzIHkgcmVwZXRpY2lvbmVzCnBhcnRpY2lvbmVzICA8LSAxMApyZXBldGljaW9uZXMgPC0gMTAKCiMgRGVmaW5pZW5kbyBoaXBlcnBhcsOhbWV0cm8gawpoaXBlcnBhcmFtZXRyb3MgPC0gZGF0YS5mcmFtZShwYXJhbWV0ZXIgPSAibm9uZSIpCgojIFNlbWlsbGFzCnNldC5zZWVkKDEyMykKc2VlZHMgPC0gdmVjdG9yKG1vZGUgPSAibGlzdCIsIGxlbmd0aCA9IChwYXJ0aWNpb25lcyAqIHJlcGV0aWNpb25lcykgKyAxKQpmb3IgKGkgaW4gMToocGFydGljaW9uZXMgKiByZXBldGljaW9uZXMpKSB7CiAgc2VlZHNbW2ldXSA8LSBzYW1wbGUuaW50KDEwMDAsIG5yb3coaGlwZXJwYXJhbWV0cm9zKSkgCn0Kc2VlZHNbWyhwYXJ0aWNpb25lcyAqIHJlcGV0aWNpb25lcykgKyAxXV0gPC0gc2FtcGxlLmludCgxMDAwLCAxKQoKIyBDb250cm9sIGRlIGVudHJlbmFtaWVudG8KY3Jvc3NfdmFsIDwtIHRyYWluQ29udHJvbCgKICBtZXRob2QgPSAicmVwZWF0ZWRjdiIsCiAgbnVtYmVyID0gcGFydGljaW9uZXMsCiAgcmVwZWF0cyA9IHJlcGV0aWNpb25lcywKICByZXR1cm5SZXNhbXAgPSAiZmluYWwiLAogIHZlcmJvc2VJdGVyID0gRkFMU0UsCiAgYWxsb3dQYXJhbGxlbCA9IFRSVUUsCiAgc2VlZHMgPSBzZWVkcwopCgojIEFqdXN0ZSBkZWwgbW9kZWxvIChlbnRyZW5hbWllbnRvKQpzZXQuc2VlZCgxMDAwKQptb2RfcmVnbCA8LSB0cmFpbigKICBDbGFzc2lmaWNhdGlvbiB+IC4sCiAgZGF0YSA9IGRmX3RyYWluLAogIG1ldGhvZCA9ICJnbG0iLAogIHR1bmVHcmlkID0gaGlwZXJwYXJhbWV0cm9zLAogIG1ldHJpYyA9ICJBY2N1cmFjeSIsCiAgdHJDb250cm9sID0gY3Jvc3NfdmFsCikKYGBgCgotICoqUmVzdWx0YWRvcyBkZWwgbW9kZWxvOioqCgpgYGB7cn0KbW9kX3JlZ2wkcmVzdWx0cyAlPiUgCiAgYXJyYW5nZShkZXNjKEFjY3VyYWN5KSkKYGBgCgotICoqQWNjdXJhY3kgZW4gdGVzdGluZyAobWF0cml6IGRlIGNvbmZ1c2nDs24pOioqCgpgYGB7cn0KbW9kX2ZpbmFsX3JlZ2wgPC0gbW9kX3JlZ2wkZmluYWxNb2RlbAp0ZXN0X3ByZWRpY19yZWdsIDwtIHByZWRpY3QobW9kX2ZpbmFsX3JlZ2wsIG5ld2RhdGEgPSBkZl90ZXN0aVssIC0xMF0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHR5cGUgPSAicmVzcG9uc2UiKQp0ZXN0X3ByZWRpY19yZWdsIDwtIGlmX2Vsc2UodGVzdF9wcmVkaWNfcmVnbCA8IDAuNSwgdHJ1ZSA9ICIwIiwgZmFsc2UgPSAiMSIpCgpjb25mdXNpb25NYXRyaXgodGFibGUoZGZfdGVzdGkkQ2xhc3NpZmljYXRpb24sIHRlc3RfcHJlZGljX3JlZ2wsIAogICAgICAgICAgICAgICAgICAgICAgZG5uID0gYygiUmVhbCIsICJQcmVkaWNobyIpKSwKICAgICAgICAgICAgICAgIHBvc2l0aXZlID0gIjEiKQpgYGAKCiMjIyDDgXJib2wgZGUgZGVjaXNpw7NuCgotICoqQ2FyYWN0ZXLDrXN0aWNhczoqKgogICAgLSBTZSBmdW5kYW1lbnRhIGVuIGxhIHNlZ21lbnRhY2nDs24gZGVsIGVzcGFjaW8gZGUgcHJlZGljdG9yZXMgYSB0cmF2w6lzIGRlIHJlZ2xhcyBsw7NnaWNhcyAoYm9vbGVhbmFzKSBzaW1wbGVzLgogICAgLSBIYWNlbiBwYXJ0ZSBkZSBsb3MgYWxnb3JpdG1vcyBubyBwYXJhbcOpdHJpY29zLgogICAgLSBNb2RlbG9zIGRlIMOhcmJvbCBwZXF1ZcOxb3Mgc29uIGRlIGbDoWNpbCBpbnRlcnByZXRhY2nDs24sIGFkZW3DoXMsIHNvbiBmw6FjaWxlcyBkZSB2aXN1YWxpemFyLgogICAgLSBQZXJtaXRlbiBtb2RlbGFyIHJlbGFjaW9uZXMgbm8gbGluZWFsZXMgZGUgbWFuZXJhIHNlbmNpbGxhLgogICAgLSBJZ25vcmEgZmFjaWxtZW50ZSBsYXMgdmFyaWFibGVzIG1lbm9zIGltcG9ydGFudGVzLgogICAgLSBBIG1lbnVkbyBwcmVzZW50YW4gYWx0YSB2YXJpYW56YSBlbiBsYXMgcHJlZGljY2lvbmVzLgogICAgLSBEZW50cm8gZGUgbG9zIGFsZ29yaXRtb3MgZGUgw6FyYm9sZXMgZGUgZGVjaXNpw7NuIHNlIGRlc3RhY2FuIGxvcyBzaWd1aWVudGVzOgogICAgICAgIC0gW2BDQVJUYF0oaHR0cHM6Ly9tZWRpdW0uY29tL21hY2hpbmUtbGVhcm5pbmctcmVzZWFyY2hlci9kZWNpc2lvbi10cmVlLWFsZ29yaXRobS1pbi1tYWNoaW5lLWxlYXJuaW5nLTI0OGZiN2RlODE5ZSk6IGFsZ29yaXRtbyBvcmlnaW5hbCBkZSBbTGVvIEJyZWltYW5dKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL0xlb19CcmVpbWFuKS4KICAgICAgICAtIFtgSUQzYF0oaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvSUQzX2FsZ29yaXRobSksIFtgQzQuNWBdKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL0M0LjVfYWxnb3JpdGhtKSB5IFtgQzUuMGBdKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL0M0LjVfYWxnb3JpdGhtI0ltcHJvdmVtZW50c19pbl9DNS4wLjJGU2VlNV9hbGdvcml0aG0pLiBUb2RvcyBkZXNhcnJvbGxhZG9zIHBvciBbUm9zcyBRdWlubGFuXShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9Sb3NzX1F1aW5sYW4pLgogICAgLSBMYSBtw6l0cmljYSBkZSBpbXB1cmV6YSB1dGlsaXphZGEgcG9yICpDQVJUKiBlcyBHaW5pLgogICAgLSBMYSBtw6l0cmljYSBkZSBpbXB1cmV6YSB1dGlsaXphZGEgcG9yICpJRDMsIEM0LjUgeSBDNS4wKiBlcyBsYSBlbnRyb3DDrWEgbyBnYW5hbmNpYSBkZSBpbmZvcm1hY2nDs24uCiAgICAtIEFsZ3Vub3MgY2Fzb3MgZGUgdXNvOgogICAgICAgIC0gUHJlZGljY2nDs24gZGUgZ3JpcGUgZW4gcGFjaWVudGVzIGRlIGF0ZW5jacOzbiBwcmltYXJpYSAoW1ppbW1lcm1hbiBldCBhbC4sIDIwMTZdKGh0dHBzOi8vd3d3Lm5jYmkubmxtLm5paC5nb3YvcG1jL2FydGljbGVzL1BNQzUwMzQ0NTcvKSkuCiAgICAgICAgLSDDgXJib2xlcyBkZSBkZWNpc2nDs24gYXBsaWNhZG9zIGVuIHNlcnZpY2lvcyBwc2lxdWnDoXRyaWNvcyAoW1NvbmcgJiBMdSwgMjAxNV0oaHR0cHM6Ly93d3cubmNiaS5ubG0ubmloLmdvdi9wbWMvYXJ0aWNsZXMvUE1DNDQ2Njg1Ni8pKS4KICAgICAgICAtIFByZWRpY2Npw7NuIGRlIGFjY2lkZW50ZXMgbGFib3JhbGVzIGVuIGbDoWJyaWNhIGRlIGFjZXJvIGRlIElyw6FuIChbU2hpcmFsaSBldCBhbC4sIDIwMThdKGh0dHBzOi8vd3d3Lm5jYmkubmxtLm5paC5nb3YvcHVibWVkLzMwNTgxODA1KSkuCiAgICAgICAgLSBDYW1iaW8gZW4gZWwgdXNvIGRlbCBzdWVsbyBiYXNhZG8gZW4gYWxnb3JpdG1vIENBUlQgKFtTYW5nIGV0IGFsLiwgMjAxOV0oaHR0cHM6Ly93d3cubmNiaS5ubG0ubmloLmdvdi9wbWMvYXJ0aWNsZXMvUE1DNjcwNjQxMi8pKS4KICAgICAgICAtIERldGVjY2nDs24gdGVtcHJhbmEgZGUgbWFzdGl0aXMgZW4gdmFjYXMgcHJvZHVjdG9yYXMgZGUgbGVjaGUgKFtaaGFuZyBldCBhbC4sIDIwMTVdKGh0dHA6Ly93ZWIuZWVjcy51dGsuZWR1L356emhhbmc2MS9kb2NzL3BhcGVycy8yMDE1X0VNQkNfTWFzdGl0aXMucGRmKSkuCgpgYGB7ciwgZWNobz1UUlVFfQojIFBhcmFsZWxpemFjacOzbiBkZWwgcHJvY2VzbwpsaWJyYXJ5KGRvTUMpCmxpYnJhcnkocGFyYWxsZWwpCnJlZ2lzdGVyRG9NQyhjb3JlcyA9IGRldGVjdENvcmVzKCkpCgojIFN1Ym11ZXN0cmFzIHkgcmVwZXRpY2lvbmVzCnBhcnRpY2lvbmVzICA8LSAxMApyZXBldGljaW9uZXMgPC0gMTAKCiMgRGVmaW5pZW5kbyBoaXBlcnBhcsOhbWV0cm8gawpoaXBlcnBhcmFtZXRyb3MgPC0gZGF0YS5mcmFtZShwYXJhbWV0ZXIgPSAibm9uZSIpCgojIFNlbWlsbGFzCnNldC5zZWVkKDEyMykKc2VlZHMgPC0gdmVjdG9yKG1vZGUgPSAibGlzdCIsIGxlbmd0aCA9IChwYXJ0aWNpb25lcyAqIHJlcGV0aWNpb25lcykgKyAxKQpmb3IgKGkgaW4gMToocGFydGljaW9uZXMgKiByZXBldGljaW9uZXMpKSB7CiAgc2VlZHNbW2ldXSA8LSBzYW1wbGUuaW50KDEwMDAsIG5yb3coaGlwZXJwYXJhbWV0cm9zKSkgCn0Kc2VlZHNbWyhwYXJ0aWNpb25lcyAqIHJlcGV0aWNpb25lcykgKyAxXV0gPC0gc2FtcGxlLmludCgxMDAwLCAxKQoKIyBDb250cm9sIGRlIGVudHJlbmFtaWVudG8KY3Jvc3NfdmFsIDwtIHRyYWluQ29udHJvbCgKICBtZXRob2QgPSAicmVwZWF0ZWRjdiIsCiAgbnVtYmVyID0gcGFydGljaW9uZXMsCiAgcmVwZWF0cyA9IHJlcGV0aWNpb25lcywKICByZXR1cm5SZXNhbXAgPSAiZmluYWwiLAogIHZlcmJvc2VJdGVyID0gRkFMU0UsCiAgYWxsb3dQYXJhbGxlbCA9IFRSVUUsCiAgc2VlZHMgPSBzZWVkcwopCgojIEFqdXN0ZSBkZWwgbW9kZWxvIChlbnRyZW5hbWllbnRvKQpzZXQuc2VlZCgxMDAwKQptb2RfYzV0cmVlIDwtIHRyYWluKAogIENsYXNzaWZpY2F0aW9uIH4gLiwKICBkYXRhID0gZGZfdHJhaW4sCiAgbWV0aG9kID0gIkM1LjBUcmVlIiwKICB0dW5lR3JpZCA9IGhpcGVycGFyYW1ldHJvcywKICBtZXRyaWMgPSAiQWNjdXJhY3kiLAogIHRyQ29udHJvbCA9IGNyb3NzX3ZhbAopCmBgYAoKLSAqKlJlc3VsdGFkb3MgZGVsIG1vZGVsbzoqKgoKYGBge3J9Cm1vZF9jNXRyZWUkcmVzdWx0cyAlPiUgCiAgYXJyYW5nZShkZXNjKEFjY3VyYWN5KSkKYGBgCgotICoqQWNjdXJhY3kgZW4gdGVzdGluZyAobWF0cml6IGRlIGNvbmZ1c2nDs24pOioqCgpgYGB7cn0KbW9kX2ZpbmFsX2M1dHJlZSA8LSBtb2RfYzV0cmVlJGZpbmFsTW9kZWwKdGVzdF9wcmVkaWNfYzV0cmVlIDwtIHByZWRpY3QobW9kX2ZpbmFsX2M1dHJlZSwgbmV3ZGF0YSA9IGRmX3Rlc3RpWywgLTEwXSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgdHlwZSA9ICJjbGFzcyIpCgpjb25mdXNpb25NYXRyaXgodGFibGUoZGZfdGVzdGkkQ2xhc3NpZmljYXRpb24sIHRlc3RfcHJlZGljX2M1dHJlZSwgCiAgICAgICAgICAgICAgICAgICAgICBkbm4gPSBjKCJSZWFsIiwgIlByZWRpY2hvIikpLAogICAgICAgICAgICAgICAgcG9zaXRpdmUgPSAiMSIpCmBgYAoKIyMjIFJhbmRvbSBGb3Jlc3QKCi0gKipDYXJhY3RlcsOtc3RpY2FzOioqCiAgICAtIENvbnNpZGVyYWRvIGNvbW8gcGFydGUgZGUgbG9zIG3DqXRvZG9zIGRlICpCYWdnaW5nIChib290c3RyYXAgYWdncmVnYXRpb24pKiBwcm9wdWVzdG9zIHBvciBbTGVvIEJyZWltYW5dKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL0xlb19CcmVpbWFuKS4KICAgIC0gRWwgKkJhZ2dpbmcqIHN1cmdlIGNvbW8gZXN0cmF0ZWfDrWEgcGFyYSBlbCBkZXNlcXVpbGlicmlvIFtiaWFzLXZhcmlhbnphXShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9CaWFzJUUyJTgwJTkzdmFyaWFuY2VfdHJhZGVvZmYpLgogICAgLSBFbCB0w6lybWlubyBbKkJhZ2dpbmcqXShodHRwczovL2VzLndpa2lwZWRpYS5vcmcvd2lraS9BZ3JlZ2FjaSVDMyVCM25fZGVfYm9vdHN0cmFwKSBoYWNlIHJlZmVyZW5jaWEgYWwgZW1wbGVvIGRlbCBtdWVzdHJlbyByZXBldGlkbyAoYm9vdHN0cmFwcGluZykuCiAgICAtIEVsIGFsZ29yaXRtbyBlbiBsdWdhciBkZSBhanVzdGFyIHVuIHPDs2xvIGFyYm9sIGRlIGRlY2lzacOzbiBwZXJtaXRlIGxhIGluY29ycG9yYWNpw7NuIGRlIG11Y2hvcyBkZSBlc3RvcywgY29uZm9ybWFuZG8gbG8gcXVlIHNlIGRlbm9taW5hIGNvbW8gImJvc3F1ZSIuIFNlIGRlbm9taW5hICJib3NxdWUgYWxlYXRvcmlvIiBwb3JxdWUgc2VsZWNjaW9uYSBhbGVhdG9yaWFtZW50ZSAkbSQgcHJlZGljdG9yZXMgcGFyYSBsYSBjb25zdHJ1Y2Npw7NuIGRlIGxvcyDDoXJib2xlcy4KICAgIC0gUGVybWl0ZSBvYnRlbmVyIG3DqXRyaWNhcyBkZSBpbXBvcnRhbmNpYSBkZSB2YXJpYWJsZXMsIHJlc3VsdGFuZG8gZW4gdW4gbcOpdG9kbyB2aWFibGUgcGFyYSBzZWxlY2Npb25hciBwcmVkaWN0b3Jlcy4KICAgIC0gRW4gUiBwdWVkZSBzZXIgaW1wbGVtZW50YWRvIGEgdHJhdsOpcyBkZSBsYSBiaWJsaW90ZWNhIGByYW5kb21Gb3Jlc3RgIG8gYHJhbmdlcmAuIEVsIG3DqXRvZG8gYHJhbmdlcmAgcG9zZWUgdHJlcyBoaXBlcnBhcsOhbWV0cm9zIGNvbnRyb2xhYmxlczoKICAgICAgICAtIGBtdHJ5YDogbsO6bWVybyBkZSBwcmVkaWN0b3JlcyBzZWxlY2Npb25hZG9zIGFsZWF0b3JpYW1lbnRlIGVuIGNhZGEgw6FyYm9sLgogICAgICAgIC0gYG1pbi5ub2RlLnNpemVgOiB0YW1hw7FvIG3DrW5pbW8gcXVlIGRlYmUgdGVuZXIgdW4gbm9kbyBwYXJhIHNlciBkaXZpZGlkby4KICAgICAgICAtIGBzcGxpdHJ1bGVgOiBjcml0ZXJpbyBkZSBkaXZpc2nDs24gKHBvciBkZWZlY3RvICpnaW5pKikuCiAgICAtIEFsZ3Vub3MgY2Fzb3MgZGUgdXNvOgogICAgICAgIC0gQm9zcXVlcyBhbGVhdG9yaW9zIHBhcmEgY2xhc2lmaWNhY2nDs24gZGUgbmV1cm9pbcOhZ2VuZXMgZW4gcGFjaWVudGVzIGNvbiBBbHpoZWltZXIgKFtTYXJpY2EgZXQgYWwuLCAyMDE3XShodHRwczovL3d3dy5uY2JpLm5sbS5uaWguZ292L3BtYy9hcnRpY2xlcy9QTUM1NjM1MDQ2LykpLgogICAgICAgIC0gSWRlbnRpZmljYWNpw7NuIGRlIGZ1ZW50ZXMgZGUgY29udGFtaW5hY2nDs24gZGVsIGFndWEgYSB0cmF2w6lzIGRlIEJvc3F1ZXMgQWxlYXRvcmlvcyAoW1JvZ3VldCBldCBhbC4sIDIwMThdKGh0dHBzOi8vd3d3Lm5jYmkubmxtLm5paC5nb3YvcG1jL2FydGljbGVzL1BNQzYxOTQ2NzQvKSkuCiAgICAgICAgLSBNb2RlbGFkbyBkZSB2YXJpYWJsZXMgZXNwYWNpYWxlcyB5IGVzcGFjaW8tdGVtcG9yYWxlcyBjb24gQm9zcXVlcyBBbGVhdG9yaW9zIChbSGVuZ2wgZXQgYWwuLCAyMDE4XShodHRwczovL3d3dy5uY2JpLm5sbS5uaWguZ292L3BtYy9hcnRpY2xlcy9QTUM2MTE5NDYyLykpLgogICAgICAgIC0gRXh0cmFjY2nDs24gZGUgY2FyYWN0ZXLDrXN0aWNhcyBjb24gQm9zcXVlcyBBbGVhdG9yaW9zIHBhcmEgYW7DoWxpc2lzIGRlIGV4cHJlc2nDs24gZ8OpbmljYSBjb24gRGVlcCBMZWFybmluZyAoW0tvbmcgJiBZdSwgMjAxOF0oaHR0cHM6Ly93d3cubmNiaS5ubG0ubmloLmdvdi9wbWMvYXJ0aWNsZXMvUE1DNjIyMDI4OS8pKS4KICAgICAgICAtIFByZWRpY2Npw7NuIGRlIGPDoW5jZXIgZGUgcHLDs3N0YXRhIGNvbiBCb3NxdWVzIEFsZWF0b3Jpb3MgKFtYaWFvIGV0IGFsLiwgMjAxN10oaHR0cHM6Ly93d3cubmNiaS5ubG0ubmloLmdvdi9wbWMvYXJ0aWNsZXMvUE1DNTU2Njg1NC8pKS4KICAgICAgICAtIEJvc3F1ZXMgQWxlYXRvcmlvcyBwYXJhIGFuw6FsaXNpcyBkZSBkYXRvcyBnZW7Ds21pY29zIChbQ2hlbiAmIElzaHdhcmFuLCAyMDEzXShodHRwczovL3d3dy5uY2JpLm5sbS5uaWguZ292L3BtYy9hcnRpY2xlcy9QTUMzMzg3NDg5LykpLgoKYGBge3IsIGVjaG89VFJVRX0KIyBQYXJhbGVsaXphY2nDs24gZGVsIHByb2Nlc28KbGlicmFyeShkb01DKQpsaWJyYXJ5KHBhcmFsbGVsKQpyZWdpc3RlckRvTUMoY29yZXMgPSBkZXRlY3RDb3JlcygpKQoKIyBTdWJtdWVzdHJhcyB5IHJlcGV0aWNpb25lcwpwYXJ0aWNpb25lcyAgPC0gMTAKcmVwZXRpY2lvbmVzIDwtIDEwCgojIERlZmluaWVuZG8gaGlwZXJwYXLDoW1ldHJvIGsKaGlwZXJwYXJhbWV0cm9zIDwtIGV4cGFuZC5ncmlkKG10cnkgPSBzZXEoMSwgOSwgMSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtaW4ubm9kZS5zaXplID0gc2VxKDEsIDMwLCAyKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNwbGl0cnVsZSA9ICJnaW5pIikKCiMgU2VtaWxsYXMKc2V0LnNlZWQoMTIzKQpzZWVkcyA8LSB2ZWN0b3IobW9kZSA9ICJsaXN0IiwgbGVuZ3RoID0gKHBhcnRpY2lvbmVzICogcmVwZXRpY2lvbmVzKSArIDEpCmZvciAoaSBpbiAxOihwYXJ0aWNpb25lcyAqIHJlcGV0aWNpb25lcykpIHsKICBzZWVkc1tbaV1dIDwtIHNhbXBsZS5pbnQoMTAwMCwgbnJvdyhoaXBlcnBhcmFtZXRyb3MpKSAKfQpzZWVkc1tbKHBhcnRpY2lvbmVzICogcmVwZXRpY2lvbmVzKSArIDFdXSA8LSBzYW1wbGUuaW50KDEwMDAsIDEpCgojIENvbnRyb2wgZGUgZW50cmVuYW1pZW50bwpjcm9zc192YWwgPC0gdHJhaW5Db250cm9sKAogIG1ldGhvZCA9ICJyZXBlYXRlZGN2IiwKICBudW1iZXIgPSBwYXJ0aWNpb25lcywKICByZXBlYXRzID0gcmVwZXRpY2lvbmVzLAogIHJldHVyblJlc2FtcCA9ICJmaW5hbCIsCiAgdmVyYm9zZUl0ZXIgPSBGQUxTRSwKICBhbGxvd1BhcmFsbGVsID0gVFJVRSwKICBzZWVkcyA9IHNlZWRzCikKCiMgQWp1c3RlIGRlbCBtb2RlbG8gKGVudHJlbmFtaWVudG8pCnNldC5zZWVkKDEwMDApCm1vZF9yZiA8LSB0cmFpbigKICBDbGFzc2lmaWNhdGlvbiB+IC4sCiAgZGF0YSA9IGRmX3RyYWluLAogIG1ldGhvZCA9ICJyYW5nZXIiLAogIHR1bmVHcmlkID0gaGlwZXJwYXJhbWV0cm9zLAogIG1ldHJpYyA9ICJBY2N1cmFjeSIsCiAgdHJDb250cm9sID0gY3Jvc3NfdmFsLAogIG51bS50cmVlcyA9IDUwMAopCmBgYAoKLSAqKkVsIG1lam9yIG1vZGVsbzoqKgoKPGNlbnRlcj4KYGBge3J9CmdncGxvdChtb2RfcmYsIGhpZ2hsaWdodCA9IFRSVUUpICsKICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gc2VxKDEsIDMwLCAyKSkgKwogIHRoZW1lX3Rlc3QoKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIpIApgYGAKPC9jZW50ZXI+CgpgYGB7cn0KbW9kX3JmJGJlc3RUdW5lCmBgYAoKLSAqKlJlc3VsdGFkb3MgZGVsIG1vZGVsbzoqKgoKYGBge3J9Cm1vZF9yZiRyZXN1bHRzICU+JSAKICBhcnJhbmdlKGRlc2MoQWNjdXJhY3kpKQpgYGAKCi0gKipBY2N1cmFjeSBlbiB0ZXN0aW5nIChtYXRyaXogZGUgY29uZnVzacOzbik6KioKCmBgYHtyfQp0ZXN0X3ByZWRpY19yZiA8LSBwcmVkaWN0KG1vZF9yZiwgbmV3ZGF0YSA9IGRmX3Rlc3RpWywgLTEwXSwKICAgICAgICAgICAgICAgICAgICAgICAgICB0eXBlID0gInJhdyIpCgpjb25mdXNpb25NYXRyaXgodGFibGUoZGZfdGVzdGkkQ2xhc3NpZmljYXRpb24sIHRlc3RfcHJlZGljX3JmLCAKICAgICAgICAgICAgICAgICAgICAgIGRubiA9IGMoIlJlYWwiLCAiUHJlZGljaG8iKSksCiAgICAgICAgICAgICAgICBwb3NpdGl2ZSA9ICIxIikKYGBgCgojIyMgU3VwcG9ydCBWZWN0b3IgTWFjaGluZSAoU1ZNKQoKLSAqKkNhcmFjdGVyw61zdGljYXM6KioKICAgIC0gQWxnb3JpdG1vIHByb3BpYW1lbnRlIGRlIGxhIGVzY3VlbGEgZGUgaW50ZWxpZ2VuY2lhIGFydGlmaWNpYWwuCiAgICAtIEVsIGFsZ29yaXRtbyBzZSBmdW5kYW1lbnRhIGVuIGxhIGLDunNxdWVkYSBkZSB1biAqaGlwZXJwbGFubyogZW4gYWxnw7puIGVzcGFjaW8gbyBkaW1lbnNpw7NuIGRlIGNhcmFjdGVyw61zdGljYXMgcXVlIHNlcGFyZSAibWVqb3IiIGxhcyBjbGFzZXMuCiAgICAtIEVsIFsqTWF4aW1hbCBNYXJnaW4gQ2xhc3NpZmllcipdKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL01hcmdpbl9jbGFzc2lmaWVyKSBlc3TDoSBiYXNhZG8gZW4gZWwgY29uY2VwdG8gZGUgaGlwZXJwbGFuby4KICAgIC0gRWwgZXNwYWNpbyBkZSBlbnRyYWRhIGVzIG1hcGVhZG8gYSB1bmEgZGltZW5zacOzbiBzdXBlcmlvciBhIHRyYXbDqXMgZGUgdW5hIGZ1bmNpw7NuICprZXJuZWwqIHkgZXMgZW4gZWwgZXNwYWNpbyBkZSBjYXJhY3RlcsOtc3RpY2FzIHRyYXNuZm9ybWFkYXMgcXVlIGVuY3VlbnRyYSBlbCBoaXBlcnBsYW5vIHF1ZSBkYXLDoSBjb21vIHJlc3VsdGFkbyBsYSBzZXBhcmFjacOzbiBtw6F4aW1hIGRlIGxhcyBjbGFzZXMuCiAgICAtIExhIGVzY2FsYWJpbGlkYWQgY29tcHV0YWNpb25hbCBzdWVsZSBzZXIgY29tcGxlamEgZW4gZ3JhbmRlcyB2b2zDum1lbmVzIGRlIGluZm9ybWFjacOzbi4KICAgIC0gUmVzdWx0YWRvcyBzYXRpc2ZhY3RvcmlvcyBzZSBjb25zaWd1ZW4gY29uIHRhbWHDsW9zIG11ZXN0cmFsZXMgcGVxdWXDsW9zLgogICAgLSBMb3MgaGlwZXJwYXLDoW1ldHJvcyBkZSBsYSBmdW5jacOzbiBga3N2bSgpYGRlbCBwYXF1ZXRlIGBrZXJubGFiYCBxdWUgaW1wbGVtZW50YSBgY2FyZXRgIHNvbiBsb3Mgc2lndWllbnRlczoKICAgICAgICAtIGBzaWdtYWA6IGNvZWZpY2llbnRlIGRlbCBrZXJuZWwgcmFkaWFsLgogICAgICAgIC0gYENgOiBwZW5hbGl6YWNpw7NuIHBhcmEgbWFyZ2VuIGRlIGhpcGVycGxhbm8uCiAgICAtIEFsZ3Vub3MgY2Fzb3MgZGUgdXNvOgogICAgICAgIC0gQXBsaWNhY2nDs24gZGUgU1ZNIGVuIGFuw6FsaXNpcyBnZW7Ds21pY28gZGUgY8OhbmNlciAoW0h1YW5nIGV0IGFsLiwgMjAxOF0oaHR0cHM6Ly93d3cubmNiaS5ubG0ubmloLmdvdi9wbWMvYXJ0aWNsZXMvUE1DNTgyMjE4MS8pKS4KICAgICAgICAtIFByZWRpY2Npw7NuIGRlIG1ldMOhc3RhdGlzIGVuIGPDoW5jZXIgZGUgY29sb24gYSB0cmF2w6lzIGRlIFNWTSAoW1poaSBldCBhbC4sIDIwMThdKGh0dHBzOi8vd3d3Lm5jYmkubmxtLm5paC5nb3YvcG1jL2FydGljbGVzL1BNQzU4MTk5NDAvKSkuCiAgICAgICAgLSBNZXRhYW7DoWxpc2lzIGNvbiBTVk0gcGFyYSBkYXRvcyDDs21pY29zIChbS2ltIGV0IGFsLiwgMjAxN10oaHR0cHM6Ly93d3cubmNiaS5ubG0ubmloLmdvdi9wbWMvYXJ0aWNsZXMvUE1DNTI3MDIzMy8pKS4KICAgICAgICAtIFByZWRpY2Npw7NuIGRlIG1hc3RpdGlzIHN1YmNsw61uaWNhIGNvbiBTVk0gKFtNYW1tYWRvdmEgJiBLZXNraW4sIDIwMTNdKGh0dHBzOi8vd3d3Lm5jYmkubmxtLm5paC5nb3YvcG1jL2FydGljbGVzL1BNQzM4ODYyNzgvKSkuCiAgICAgICAgLSBDYXNvcyBkZSB1c28gZGUgU1ZNIGVuIGJpb2xvZ8OtYSBjb21wdXRhY2lvbmFsIChbQmVuLUh1ciBldCBhbC4sIDIwMDhdKGh0dHBzOi8vd3d3Lm5jYmkubmxtLm5paC5nb3YvcG1jL2FydGljbGVzL1BNQzI1NDc5ODMvKSkuCgpgYGB7ciwgZWNobz1UUlVFfQojIFBhcmFsZWxpemFjacOzbiBkZWwgcHJvY2VzbwpsaWJyYXJ5KGRvTUMpCmxpYnJhcnkocGFyYWxsZWwpCnJlZ2lzdGVyRG9NQyhjb3JlcyA9IGRldGVjdENvcmVzKCkpCgojIFN1Ym11ZXN0cmFzIHkgcmVwZXRpY2lvbmVzCnBhcnRpY2lvbmVzICA8LSAxMApyZXBldGljaW9uZXMgPC0gMTAKCiMgRGVmaW5pZW5kbyBoaXBlcnBhcsOhbWV0cm8gawpoaXBlcnBhcmFtZXRyb3MgPC0gZXhwYW5kLmdyaWQoc2lnbWEgPSBjKDAuMDAxLCAwLjAxLCBzZXEoMC4xLCAxLCAwLjEpKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEMgPSBzZXEoMSwgMTAwMCwgMjApKQoKIyBTZW1pbGxhcwpzZXQuc2VlZCgxMjMpCnNlZWRzIDwtIHZlY3Rvcihtb2RlID0gImxpc3QiLCBsZW5ndGggPSAocGFydGljaW9uZXMgKiByZXBldGljaW9uZXMpICsgMSkKZm9yIChpIGluIDE6KHBhcnRpY2lvbmVzICogcmVwZXRpY2lvbmVzKSkgewogIHNlZWRzW1tpXV0gPC0gc2FtcGxlLmludCgxMDAwLCBucm93KGhpcGVycGFyYW1ldHJvcykpIAp9CnNlZWRzW1socGFydGljaW9uZXMgKiByZXBldGljaW9uZXMpICsgMV1dIDwtIHNhbXBsZS5pbnQoMTAwMCwgMSkKCiMgQ29udHJvbCBkZSBlbnRyZW5hbWllbnRvCmNyb3NzX3ZhbCA8LSB0cmFpbkNvbnRyb2woCiAgbWV0aG9kID0gInJlcGVhdGVkY3YiLAogIG51bWJlciA9IHBhcnRpY2lvbmVzLAogIHJlcGVhdHMgPSByZXBldGljaW9uZXMsCiAgcmV0dXJuUmVzYW1wID0gImZpbmFsIiwKICB2ZXJib3NlSXRlciA9IEZBTFNFLAogIGFsbG93UGFyYWxsZWwgPSBUUlVFLAogIHNlZWRzID0gc2VlZHMKKQoKIyBBanVzdGUgZGVsIG1vZGVsbyAoZW50cmVuYW1pZW50bykKc2V0LnNlZWQoMTAwMCkKbW9kX3N2bSA8LSB0cmFpbigKICBDbGFzc2lmaWNhdGlvbiB+IC4sCiAgZGF0YSA9IGRmX3RyYWluLAogIG1ldGhvZCA9ICJzdm1SYWRpYWwiLAogIHR1bmVHcmlkID0gaGlwZXJwYXJhbWV0cm9zLAogIG1ldHJpYyA9ICJBY2N1cmFjeSIsCiAgdHJDb250cm9sID0gY3Jvc3NfdmFsCikKYGBgCgotICoqRWwgbWVqb3IgbW9kZWxvOioqCgo8Y2VudGVyPgpgYGB7cn0KZ2dwbG90KG1vZF9zdm0sIGhpZ2hsaWdodCA9IFRSVUUpICsKICB0aGVtZV90ZXN0KCkgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iKSAKYGBgCjwvY2VudGVyPgoKYGBge3J9Cm1vZF9zdm0kYmVzdFR1bmUKYGBgCgotICoqUmVzdWx0YWRvcyBkZWwgbW9kZWxvOioqCgpgYGB7cn0KbW9kX3N2bSRyZXN1bHRzICU+JSAKICBhcnJhbmdlKGRlc2MoQWNjdXJhY3kpKQpgYGAKCi0gKipBY2N1cmFjeSBlbiB0ZXN0aW5nIChtYXRyaXogZGUgY29uZnVzacOzbik6KioKCmBgYHtyfQp0ZXN0X3ByZWRpY19zdm0gPC0gcHJlZGljdChtb2Rfc3ZtLCBuZXdkYXRhID0gZGZfdGVzdGlbLCAtMTBdLAogICAgICAgICAgICAgICAgICAgICAgICAgIHR5cGUgPSAicmF3IikKCmNvbmZ1c2lvbk1hdHJpeCh0YWJsZShkZl90ZXN0aSRDbGFzc2lmaWNhdGlvbiwgdGVzdF9wcmVkaWNfc3ZtLCAKICAgICAgICAgICAgICAgICAgICAgIGRubiA9IGMoIlJlYWwiLCAiUHJlZGljaG8iKSksCiAgICAgICAgICAgICAgICBwb3NpdGl2ZSA9ICIxIikKYGBgCgojIyMgR3JhZGllbnQgQm9vc3RpbmcKCi0gKipDYXJhY3RlcsOtc3RpY2FzOioqCiAgICAtIEhhY2VuIHBhcnRlIGRlIGxvcyBhbGdvcml0bW9zIGRlIFtlbnNlbWJsZV0oaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvRW5zZW1ibGVfbGVhcm5pbmcpLgogICAgLSBTZSBmdW5kYW1lbnRhIGVuIGxhIGltcGxlbWVudGFjacOzbiBkZSB1biBjb25qdW50byBkZSBtb2RlbG9zIHNlbmNpbGxvcyAoKndlYWsgbGVhcm5lcnMqKSBjb24gdGFzYXMgZGUgYXByZW5kaXphamUgY29uZGljaW9uYWwgcG9yIGVsIG1vZGVsbyBhbnRlcmlvciwgZXMgZGVjaXIsIHF1ZSBlbXBsZWEgbGEgaW5mb3JtYWNpw7NuIHByZXZpYSBwYXJhIGFwcmVuZGVyIGRlIHN1cyBlcnJvcmVzLgogICAgLSBMYSBjb25zdHJ1Y2Npw7NuIGRlbCBtb2RlbG8gc2UgZGEgZGUgZm9ybWEgaXRlcmF0aXZhIGNvbW8gbG8gaGFjZW4gb3Ryb3MgbcOpdG9kb3MgZGUgWypCb29zdGluZypdKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL0Jvb3N0aW5nXyhtYWNoaW5lX2xlYXJuaW5nKSkuCiAgICAtIEEgZGlmZXJlbmNpYSBkZSAqQmFnZ2luZyogZWwgbcOpdG9kbyBbKkdyYWRpZW50IEJvb3N0aW5nKl0oaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvR3JhZGllbnRfYm9vc3RpbmcpIG5vIHVzYSBib290c3RyYXBwaW5nLgogICAgLSBUcmVzIGRlIGxvcyBhbGdvcml0bW9zIGRlICpCb29zdGluZyogY29uIG1heW9yIGFjZXB0YWNpw7NuIHNvbiAqQWRhQm9vc3QqLCAqR3JhZGllbnQgQm9vc3RpbmcqIHkgKlN0b2NoYXN0aWMgR3JhZGllbnQgQm9vc3RpbmcqLgogICAgLSBFbiBSIHB1ZWRlIHNlciBpbXBsZW1lbnRhZG8gYSB0cmF2w6lzIGRlIGxhIGZ1bmNpw7NuIGBnYm0oKWAgZGVsIHBhcXVldGUgYGdtYmAuIEVsIGFsZ29yaXRtbyB0aWVuZSA2IGhpcGVycGFyw6FtZXRyb3MgYSB0ZW5lciBlbiBjdWVudGE6CiAgICAgICAgLSBgbi50cmVlc2A6IG7Dum1lcm8gZGUgaXRlcmFjaW9uZXMgZGVsIGFsZ29yaXRtby4gRXMgZWwgbsO6bWVybyBkZSBtb2RlbG9zIHF1ZSBjb25mb3JtYSBlbCAqZW5zZW1ibGUqLgogICAgICAgIC0gYGl0ZXJhdGlvbi5kZXB0aGA6IGNvbXBsZWppZGFkIGRlIGxvcyDDoXJib2xlcyBlbXBsZWFkb3MgY29tbyAqd2VhayBsZWFybmVyKi4KICAgICAgICAtIGBzaHJpbmthZ2VgOiB0YXNhIGRlIGFwcmVuZGl6YWplICgqbGVhcm5pbmcgcmF0ZSopIHF1ZSBjb250cm9sYSBsYSBpbmZsdWVuY2lhIHF1ZSB0aWVuZSBjYWRhIG1vZGVsbyBpbmRpdmlkdWFsIHNvYnJlIGVsIGNvbmp1bnRvIGRlIG1vZGVsb3MgKCplbnNlbWJsZSopLgogICAgICAgIC0gYG4ubWlub2JzaW5ub2RlYDogbsO6bWVybyBtw61uaW1vIGRlIG9ic2VydmFjaW9uZXMgcXVlIGRlYmUgdGVuZXIgdW4gbm9kbyBwYXJhIHBvZGVyIHNlciBkaXZpZGlkby4KICAgICAgICAtIGBkaXN0cmlidXRpb25gOiBkZXRlcm1pbmEgbGEgZnVuY2nDs24gZGUgY29zdGUgKCpsb3NzIGZ1bmN0aW9uKikuCiAgICAgICAgICAgIC0gKkdhdXNzaWFuKiAtLT4gcGFyYSByZWdyZXNpw7NuLgogICAgICAgICAgICAtICpCZXJub3VsbGkqIC0tPiBwYXJhIHJlc3B1ZXN0YXMgYmluYXJpYXMuCiAgICAgICAgICAgIC0gKk11bHRpbm9taWFsKiAtLT4gcGFyYSByZXNwdWVzdGFzIG11bHRpY2xhc2UuCiAgICAgICAgLSBgYmFnLmZyYWN0aW9uYDogc3VibXVlc3RyYSBkZWwgY29uanVudG8gZGUgZW50cmVuYW1pZW50byB1dGlsaXphZG8gcGFyYSBhanVzdGFyIGxvcyAqd2VhayBsZWFybmVyKi4gU2kgZWwgdmFsb3IgZXMgaWd1YWwgYSAkMSQgc2UgZW1wbGVhIGVsIGFsZ29yaXRtbyAqR3JhZGllbnQgQm9vc3RpbmcqLiBQb3IgZGVmZWN0byBsYSBmdW5jacOzbiBlc3TDoSBjb24gdmFsb3IgZGUgYDAuNWAsIGltcGxlbWVudGFuZG8gZWwgYWxnb3JpdG1vIGBTdG9jaGFzdGljIEdyYWRpZW50IEJvb3N0aW5nYC4KICAgICAgICAtIEVuIGdlbmVyYWwsIGxhIGplcmFycXXDrWEgZW4gbW9kZWxvcyBiYXNhZG9zIGVuIMOhcmJvbGVzIHNlIGRhIGRlIGxhIHNpZ3VpZW50ZSBtYW5lcmE6ICRCb29zdGluZ1wgPiBSYW5kb21cIEZvcmVzdFwgPiBCYWdnaW5nXCA+IMOBcmJvbGVzXCBzaW1wbGVzJC4KICAgICAgICAtIFNvbiB1bm8gZGUgbG9zIGFsZ29yaXRtb3MgbcOhcyBwb3RlbnRlcyBlbiBjdWFudG8gYSBjYXBhY2lkYWQgcHJlZGljaXRpdmEuCiAgICAtIEFsZ3Vub3MgY2Fzb3MgZGUgdXNvOgogICAgICAgIC0gQW5hbMOtdGljYSBwcmVkaWNpdGl2YSBlbiBtZWRpY2luYSBjb24gKkdyYWRpZW50IEJvb3N0aW5nKiAoW1poYW5nIGV0IGFsLiwgMjAxOV0oaHR0cHM6Ly93d3cubmNiaS5ubG0ubmloLmdvdi9wbWMvYXJ0aWNsZXMvUE1DNjUxMTU0Ni8pKS4KICAgICAgICAtIFByZWRpY2Npw7NuIGRlIGVmZWN0b3Mgc2VjdW5kYXJpb3MgZW4gdHJhdGFtaWVudG8gcGFyYSBvc3Rlb2FydHJpdGlzIGEgdHJhdsOpcyBkZSBtb2RlbG9zIGRlICpCb29zdGluZyogKFtMaXUgZXQgYWwuLCAyMDE4XShodHRwczovL3d3dy5uY2JpLm5sbS5uaWguZ292L3BtYy9hcnRpY2xlcy9QTUM2MjQ5NzMwLykpLgogICAgICAgIC0gSWRlbnRpZmljYWNpw7NuIGRlIHByZWRpY3RvcmVzIG1vbGVjdWxhcmVzIGRlIGFsdGEgZWZpY2llbmNpYSBhbGltZW50aWNpYSBlbiBjZXJkb3MgZW4gY3JlY2ltaWVudG8gKFtNZXNzYWQgZXQgYWwuLCAyMDE5XShodHRwczovL3d3dy5uY2JpLm5sbS5uaWguZ292L3BtYy9hcnRpY2xlcy9QTUM2Njk3OTA3LykpLgogICAgICAgIC0gTW9kZWxvcyBkZSAqQm9vc3RpbmcqIGVuIGFuw6FsaXNpcyBkZSBzdHJlYW1pbmcgZGF0YSBwYXJhIGludGVybmV0IGRlIGxhcyBjb3NhcyAoSW9UKSAoW0tlbmRhIGV0IGFsLiwgMjAxOV0oaHR0cHM6Ly93d3cubmNiaS5ubG0ubmloLmdvdi9wbWMvYXJ0aWNsZXMvUE1DNjUxNDk2OS8pKS4KICAgICAgICAtIEFydMOtY3VsbyBndcOtYSBkZSAqR3JhZGllbnQgQm9vc3RpbmcqIChbTmF0ZWtpbiAmIEtub2xsLCAyMDEzXShodHRwczovL3d3dy5uY2JpLm5sbS5uaWguZ292L3BtYy9hcnRpY2xlcy9QTUMzODg1ODI2LykpLgoKYGBge3IsIGVjaG89VFJVRX0KIyBQYXJhbGVsaXphY2nDs24gZGVsIHByb2Nlc28KbGlicmFyeShkb01DKQpsaWJyYXJ5KHBhcmFsbGVsKQpyZWdpc3RlckRvTUMoY29yZXMgPSBkZXRlY3RDb3JlcygpKQoKIyBTdWJtdWVzdHJhcyB5IHJlcGV0aWNpb25lcwpwYXJ0aWNpb25lcyAgPC0gMTAKcmVwZXRpY2lvbmVzIDwtIDEwCgojIERlZmluaWVuZG8gaGlwZXJwYXLDoW1ldHJvIGsKaGlwZXJwYXJhbWV0cm9zIDwtIGV4cGFuZC5ncmlkKGludGVyYWN0aW9uLmRlcHRoID0gYygxLCAzLCA1KSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG4udHJlZXMgPSBzZXEoNTAsIDEwMDAsIDEwMCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzaHJpbmthZ2UgPSBjKDAuMDAwMSwgMC4wMDEsIDAuMDEsIDAuMSwgMSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuLm1pbm9ic2lubm9kZSA9IGMoMiwgNSwgMTAsIDE1KSkKCiMgU2VtaWxsYXMKc2V0LnNlZWQoMTIzKQpzZWVkcyA8LSB2ZWN0b3IobW9kZSA9ICJsaXN0IiwgbGVuZ3RoID0gKHBhcnRpY2lvbmVzICogcmVwZXRpY2lvbmVzKSArIDEpCmZvciAoaSBpbiAxOihwYXJ0aWNpb25lcyAqIHJlcGV0aWNpb25lcykpIHsKICBzZWVkc1tbaV1dIDwtIHNhbXBsZS5pbnQoMTAwMCwgbnJvdyhoaXBlcnBhcmFtZXRyb3MpKSAKfQpzZWVkc1tbKHBhcnRpY2lvbmVzICogcmVwZXRpY2lvbmVzKSArIDFdXSA8LSBzYW1wbGUuaW50KDEwMDAsIDEpCgojIENvbnRyb2wgZGUgZW50cmVuYW1pZW50bwpjcm9zc192YWwgPC0gdHJhaW5Db250cm9sKAogIG1ldGhvZCA9ICJyZXBlYXRlZGN2IiwKICBudW1iZXIgPSBwYXJ0aWNpb25lcywKICByZXBlYXRzID0gcmVwZXRpY2lvbmVzLAogIHJldHVyblJlc2FtcCA9ICJmaW5hbCIsCiAgdmVyYm9zZUl0ZXIgPSBGQUxTRSwKICBhbGxvd1BhcmFsbGVsID0gVFJVRSwKICBzZWVkcyA9IHNlZWRzCikKCiMgQWp1c3RlIGRlbCBtb2RlbG8gKGVudHJlbmFtaWVudG8pCnNldC5zZWVkKDEwMDApCm1vZF9nYm0gPC0gdHJhaW4oCiAgQ2xhc3NpZmljYXRpb24gfiAuLAogIGRhdGEgPSBkZl90cmFpbiwKICBtZXRob2QgPSAiZ2JtIiwKICB0dW5lR3JpZCA9IGhpcGVycGFyYW1ldHJvcywKICBtZXRyaWMgPSAiQWNjdXJhY3kiLAogIHRyQ29udHJvbCA9IGNyb3NzX3ZhbCwKICBkaXN0cmlidXRpb24gPSAiYmVybm91bGxpIiwKICB2ZXJib3NlID0gRkFMU0UKKQpgYGAKCi0gKipFbCBtZWpvciBtb2RlbG86KioKCjxjZW50ZXI+CmBgYHtyLCBmaWcuaGVpZ2h0PTZ9CmdncGxvdChtb2RfZ2JtLCBoaWdobGlnaHQgPSBUUlVFKSArCiAgdGhlbWVfdGVzdCgpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIikgCmBgYAo8L2NlbnRlcj4KCmBgYHtyfQptb2RfZ2JtJGJlc3RUdW5lCmBgYAoKLSAqKlJlc3VsdGFkb3MgZGVsIG1vZGVsbzoqKgoKYGBge3J9Cm1vZF9nYm0kcmVzdWx0cyAlPiUgCiAgYXJyYW5nZShkZXNjKEFjY3VyYWN5KSkKYGBgCgotICoqQWNjdXJhY3kgZW4gdGVzdGluZyAobWF0cml6IGRlIGNvbmZ1c2nDs24pOioqCgpgYGB7cn0KdGVzdF9wcmVkaWNfZ2JtIDwtIHByZWRpY3QobW9kX2dibSwgbmV3ZGF0YSA9IGRmX3Rlc3RpWywgLTEwXSwKICAgICAgICAgICAgICAgICAgICAgICAgICB0eXBlID0gInJhdyIpCgpjb25mdXNpb25NYXRyaXgodGFibGUoZGZfdGVzdGkkQ2xhc3NpZmljYXRpb24sIHRlc3RfcHJlZGljX2dibSwgCiAgICAgICAgICAgICAgICAgICAgICBkbm4gPSBjKCJSZWFsIiwgIlByZWRpY2hvIikpLAogICAgICAgICAgICAgICAgcG9zaXRpdmUgPSAiMSIpCmBgYAoKIyMjIERlZXAgTGVhcm5pbmcgY29uIGBrZXJhc2AKCi0gKipDYXJhY3RlcsOtc3RpY2FzOioqCiAgICAtIExhcyByZWRlcyBuZXVyb25hbGVzIGV4aXN0ZW4gaGFjZSBtdWNobyB0aWVtcG8gY29tbyB1biBjb25jZXB0byBkZSBpbnRlbGlnZW5jaWEgYXJ0aWZpY2lhbCBlIGluY2x1c28gY29tbyB1biBhbGdvcml0bW8gZGUgTWFjaGluZSBMZWFybmluZy4KICAgIC0gSGFzdGEgY2llcnRvIHB1bnRvIHB1ZWRlbiBzZXIgY29uc2lkZXJhZGFzIGNvbW8gbcOpdG9kb3MgZGUgcmVncmVzacOzbiBubyBsaW5lYWwuCiAgICAtIExhIGFycXVpdGVjdHVyYSBkZSByZWQgbmV1cm9uYWwgcHJvZnVuZGEgKFtkZWVwIGxlYXJuaW5nXShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9EZWVwX2xlYXJuaW5nI0RlZXBfbmV1cmFsXyUyMG5ldHdvcmtfYXJjaGl0ZWN0dXJlcykpIGhhY2UgcGFydGUgZGUgbGFzIFtyZWRlcyBuZXVyb25hbGVzIGFydGlmaWNpYWxlc10oaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvQXJ0aWZpY2lhbF9uZXVyYWxfbmV0d29yaykuICAgIAogICAgLSBWaXN1YWxtZW50ZSBzZSBvYnNlcnZhIGNvbW8gdW4gY29uanVudG8gZGUgY2FwYXMgZGUgZW50cmFkYSAoKmlucHV0cyopIHkgc2FsaWRhICgqb3V0cHV0cyopLgogICAgLSBTZSBmdW5kYW1lbnRhIGVuIGxhIGFzaWduYWNpw7NuIGRlIHVuIHBlc28gbyBwb25kZXJhY2nDs24gYSBsYXMgZW50cmFkYXMgeSBjb24gdW5hIGZ1bmNpw7NuIGRlIGFjdGl2YWNpw7NuIChwb3IgZWplbXBsbyAqc2lnbW9pZGVhKikgc2UgcHJvZHVjZSBsYSBzaWd1aWVudGUgY2FwYSBkZSBlbnRyYWRhcy4gRXN0ZSBwcm9jZXNvIHNlIHJlcGl0ZSB5IGVsIGNvbmp1bnRvIGRlIGVudHJhZGFzIGNvbmZvcm1hbiBsbyBxdWUgc2UgY29ub2NlIGNvbW8gY2FwYXMgb2N1bHRhcyAoKmhpZGRlbiBsYXllcnMqKS4KICAgIC0gRWwgbcOpdG9kbyBjb23Dum4gcGFyYSBlbnRyZW5hciByZWRlcyBuZXVyb25hbGVzIGVzIGVsIGRlIHByb3BhZ2FjacOzbiBoYWNpYSBhdHLDoXMgKFtCYWNrcHJvcGFnYXRpb25dKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL0JhY2twcm9wYWdhdGlvbikpLiBFbCBtw6l0b2RvIGRlICpCYWNrcHJvcGFnYXRpb24qIGVzIGl0ZXJhdGl2bywgcmVjdXJzaXZvIHkgZWZpY2llbnRlIHBhcmEgcmVjYWxjdWxhciBsb3MgcGVzb3MgZGUgbGFzIGVudHJhZGFzIGRlIGxhIHJlZC4gTGEgYWN0dWFsaXphY2nDs24gZGUgbG9zIHBlc29zIGVzIG9idGVuaWRhIGFsIHNlZ3VpciB1biBhbGdvcml0bW8gZGUgb3B0aW1pemFjacOzbiBiYXNhZG8gZW4gZ3JhZGllbnRlcywgY29tbyBlbCBbZGVzY2Vuc28gZGUgZ3JhZGllbnRlXShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9HcmFkaWVudF9kZXNjZW50KS4KICAgIC0gQWxndW5hcyBkZSBsYXMgYXJxdWl0ZWN0dXJhcyBkZSByZWRlcyBuZXVyb25hbGVzIG3DoXMgY29ub2NpZGFzIGVuIGxhIGFjdHVhbGlkYWQgc2UgbWVuY2lvbmFuIGEgY29udGludWFjacOzbjoKICAgICAgICAtIFtQZXJjZXB0csOzbiBtdWx0aWNhcGEgLSBNTFBdKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL011bHRpbGF5ZXJfcGVyY2VwdHJvbikuIENvbnN0YSBkZSBtw7psdGlwbGVzIGNhcGFzLCBjb24gY2FkYSBjYXBhIGNvbmVjdGFkYSBjb21wbGV0YW1lbnRlIGEgbGEgc2lndWllbnRlLiBQdWVkZSBkaXN0aW5ndWlyIGRhdG9zIHF1ZSBubyBzb24gbGluZWFsbWVudGUgc2VwYXJhYmxlcy4KICAgICAgICAtIFtSZWRlcyBOZXVyb25hbGVzIFJlY3VycmVudGVzIC0gUk5OXShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9SZWN1cnJlbnRfbmV1cmFsX25ldHdvcmspLiBQZXJtaXRlIGNvbmV4aW9uZXMgZW50cmUgdW5pZGFkZXMgcXVlIGV4aGliZW4gY29tcG9ydGFtaWVudG9zIHRlbXBvcmFsZXMgZGluw6FtaWNvcy4gRGUgYW1wbGlvIHVzbyBlbiBwcm9jZXNzYW1pZW50byBkZSBsZW5ndWFqZSAoKk5MUCAtIE5hdHVyYWwgTGFuZ3VhZ2UgUHJvY2Vzc2luZyopLiBbQXF1w60gdW4gZWplbXBsbyBjw7NtaWNvIGRlIHVzbyBkZSBSTk5dKGh0dHBzOi8vdHdpdHRlci5jb20vRGVlcERydW1wZikuCiAgICAgICAgLSBbUmVkZXMgTmV1cm9uYWxlcyBDb252b2x1Y2lvbmFsZXMgLSBDTk5dKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL0NvbnZvbHV0aW9uYWxfbmV1cmFsX25ldHdvcmspLiBMYXMgcmVkZXMgY29udm9sdWNpb25hbGVzIHNvbiB2YXJpYWNpb25lcyBkZSBwZXJjZXB0cm9uZXMgbXVsdGljYXBhIGRpc2XDsWFkb3MgcGFyYSB1c2FyIGNhbnRpZGFkZXMgbcOtbmltYXMgZGUgcHJlcHJvY2VzYW1pZW50by4gRGUgYW1wbGlvIHVzbyBlbiBhbsOhbGlzaXMgZGUgaW1hZ2VuLCB2aWRlbywgTkxQIHkgc2lzdGVtYXMgZGUgcmVjb21lbmRhY2nDs24uCiAgICAgICAgLSBGdW5jaW9uZXMgZGUgYWN0aXZhY2nDs246CiAgICAgICAgICAgIC0gW1NpZ21vaWRlXShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9TaWdtb2lkX2Z1bmN0aW9uKS4KICAgICAgICAgICAgLSBbSGlwZXJiw7NsaWNhc10oaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvSHlwZXJib2xpY19mdW5jdGlvbikuCiAgICAgICAgICAgIC0gW1JlY3RpZmljYWRvciAtIFJlTFVdKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL1JlY3RpZmllcl8obmV1cmFsX25ldHdvcmtzKSkuIEVzIGxhIG3DoXMgcG9wdWxhciBkZXNkZSBlbCBhw7FvIDIwMTEgY3VhbmRvIHNlIGRlbW9zdHLDsyBxdWUgcGVybWl0ZSBlbCBtZWpvciBlbnRyZW5hbWllbnRvIGRlIHJlZGVzIG5ldXJvbmFsZXMuCiAgICAgICAgICAgIC0gW03DoXMgZnVuY2lvbmVzIGRlIGFjdGl2YWNpw7NuXShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9BY3RpdmF0aW9uX2Z1bmN0aW9uKS4KICAgICAgICAtIEFkZW3DoXMgZGUgbGEgZnVuY2nDs24gZGUgYWN0aXZhY2nDs24gcGFyYSBlbCBlbnRyZW5hbWllbnRvIGRlIHJlZGVzLCBlcyBuZWNlc2FyaW8gZGVjbGFyYXIgdW5hIGZ1bmNpw7NuIG1hdGVtw6F0aWNhIHBhcmEgbGEgY2FwYSBkZSBzYWxpZGEgKCpvdXRwdXRzKiksIHBhcmEgcHJvYmxlbWFzIGJpbmFyaW9zIHNlIHVzYSBsYSBmdW5jacOzbiBbKlNpZ21vaWRlKl0oaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvU2lnbW9pZF9mdW5jdGlvbiksIHBhcmEgdGFyZWFzIG11bHRpbm9taWFsZXMgc2UgdXRpbGl6YSBsYSBmdW5jacOzbiBbKlNvZnRtYXgqXShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9Tb2Z0bWF4X2Z1bmN0aW9uKSB5IHBhcmEgcHJvYmxlbWFzIGRlIHJlZ3Jlc2nDs24gc2UgdXNhIGxhIGZ1bmNpw7NuIGxpbmVhbCAoWyppZGVudGlkYWQqXShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9JZGVudGl0eV9mdW5jdGlvbikpLgogICAgICAgIC0gVW5hIHJlZCBuZXVyb25hbCBwcm9mdW5kYSBwb3NlZSBsb3Mgc2lndWllbnRlcyBjb21wb25lbnRlcyBmdW5kYW1lbnRhbGVzOgogICAgICAgICAgICAtICoqTm9kb3MgeSBjYXBhczoqKiBkZXRlcm1pbmFuIGxhIGNvbXBsZWppZGFkIGRlIGxhIHJlZC4gTGFzIGNhcGFzIHNlIGNvbnNpZGVyYW4gKmRlbnNhcyogY3VhbmRvIGVzdMOhbiBjb21wbGV0YW1lbnRlIGNvbmVjdGFkb3MgdG9kb3MgbG9zIG5vZG9zIGNvbiBjYXBhcyBzdWNlc2l2YXMuIE3DoXMgY2FwYXMgeSBub2RvcyBhZ3JlZ2Fkb3MgYSBsYSByZWQsIGJyaW5kYXLDoW4gbWF5b3Igb3BvcnR1bmlkYWQgZGUgZXh0cmFlciBudWV2YSBpbmZvcm1hY2nDs24gKGNhcmFjdGVyw61zdGljYXMpLiBMYSBjYXBhIGRlIGVudHJhZGEgZXN0w6EgY29uc3RpdHVpZGEgcG9yIGxhcyB2YXJpYWJsZXMgcHJlZGljdG9yYXMgb3JpZ2luYWxlcywgYWRlbcOhcyBkZSBlc3RhIGNhcGEgdGFtYmnDqW4gc2UgZW5jdWVudHJhbiBjYXBhcyBvY3VsdGFzIHkgY2FwYSBkZSBzYWxpZGEuIExhcyBjYXBhcyBvY3VsdGFzICgqaGlkZGVuIGxheWVycyopIHB1ZWRlbiBzZXIgY29uc2lkZXJhZGFzIGNvbW8gaGlwZXJwYXLDoW1ldHJvcyBzb2JyZSBsb3MgY3VhbGVzIG5vIGhheSB1bmEgZGlyZWNjacOzbiB1bmlmaWNhZGEgZGUgcHJvY2VzYW1pZW50bzsgbGEgY2FudGlkYWQgZGUgbm9kb3MgcXVlIHNlIGluY29ycG9yZW4gZW4gZXN0YXMgY2FwYXMgZXN0YXLDoSBkZXRlcm1pbmFkbyBwb3IgZWwgbsO6bWVybyBkZSBhdHJpYnV0b3MuIEFwZWxhbmRvIGFsIHByaW5jaXBpbyBkZSBwYXJzaW1vbmlhLCBsbyBpZGVhbCBlcyBlbmNvbnRyYXIgdW4gbW9kZWxvIGRlIHJlZCBzaW1wbGUgeSBjb21wdXRhY2lvbmFsbWVudGUgw7NwdGltby4gTGEgY2FwYSBkZSBzYWxpZGEgZXN0w6EgZGV0ZXJtaW5hZGEgcG9yIGVsIHRpcG8gZGUgdmFyaWFibGUgcmVzcHVlc3RhLCBwYXJhIHByb2JsZW1hcyBkZSByZWdyZXNpw7NuIHNlIHByZWRpY2VuIHZhbG9yZXMgbnVtw6lyaWNvcyB5IHBhcmEgY2xhc2lmaWNhY2nDs24gc2UgcHJlZGljZW4gcHJvYmFiaWxpZGFkZXMgcGFyYSB1bmEgZXRpcXVldGEgbyBjbGFzZSBlc3BlY8OtZmljYS4KICAgICAgICAgICAgLSAqKkFjdGl2YWNpw7NuOioqIGVsZWNjacOzbiBkZSBsYSBmdW5jacOzbiBkZSBhY3RpdmFjacOzbiBwYXJhIGFzaWduYWNpw7NuIGRlIHBlc29zIG8gcG9uZGVyYWNpb25lcyBhIGxvcyAqaW5wdXRzKi4KICAgICAgICAtIFNvZnR3YXJlIHBhcmEgZGVlcCBsZWFybmluZyBjb24gUiBvIFB5dGhvbjoKICAgICAgICAgICAgLSBbYG14bmV0YF0oaHR0cHM6Ly9teG5ldC5hcGFjaGUub3JnL2dldF9zdGFydGVkL3NldHVwLmh0bWwpCiAgICAgICAgICAgIC0gW2BoMm9gXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvaDJvL2luZGV4Lmh0bWwpCiAgICAgICAgICAgICAgICAtIFtQw6FnaW5hIHdlYiBoMm9dKGh0dHBzOi8vd3d3Lmgyby5haS8pCiAgICAgICAgICAgIC0gW2B0ZW5zb3JGbG93YCBlbiBSU3R1ZGlvXShodHRwczovL2Jsb2dzLnJzdHVkaW8uY29tL3RlbnNvcmZsb3cvKQogICAgICAgICAgICAtIFtga2VyYXNgXShodHRwczovL2Jsb2dzLnJzdHVkaW8uY29tL3RlbnNvcmZsb3cvcG9zdHMvMjAxOC0wMS0yNC1hbmFseXppbmctcnR3ZWV0LWRhdGEtd2l0aC1rZXJhc2Zvcm11bGEvKQogICAgICAgICAgICAgICAgLSBbYGtlcmFzYF0oaHR0cHM6Ly9rZXJhcy5pby8pCiAgICAgICAgICAgICAgICAtIFtga2VyYXNgIGVuIFJdKGh0dHBzOi8vZ2l0aHViLmNvbS9qamFsbGFpcmUvZGVlcC1sZWFybmluZy13aXRoLXItbm90ZWJvb2tzKQogICAgICAgICAgICAtIFtgZGVlcG5ldGBdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9kZWVwbmV0L2luZGV4Lmh0bWwpCiAgICAgICAgICAgIC0gW2BQeVRvcmNoYF0oaHR0cHM6Ly9weXRvcmNoLm9yZy8pCiAgICAgICAgICAgIC0gW1Byb3llY3RvIFRlbnNvckZsb3ddKGh0dHBzOi8vd3d3LnRlbnNvcmZsb3cub3JnLykKICAgICAgICAtIEFsZ3Vub3MgY2Fzb3MgZGUgdXNvOgogICAgICAgICAgICAtIFJlZGVzIG5ldXJvbmFsZXMgY29udm9sdWNpb25hbGVzIGVuIGlkZW50aWZpY2FjacOzbiBkZSBvYmpldGl2b3MgZW4gc2lzdGVtYXMgZGUgZGVmZW5zYSAoW2QnQWNyZW1vbnQgZXQgYWwuLCAyMDE5XShodHRwczovL3d3dy5uY2JpLm5sbS5uaWguZ292L3BtYy9hcnRpY2xlcy9QTUM2NTM5NzY0LykpLgogICAgICAgICAgICAtIFJlY29ub2NpbWllbnRvIGZhY2lhbCBjb24gQ05OIChbWWFuZyBldCBhbC4sIDIwMThdKGh0dHBzOi8vd3d3Lm5jYmkubmxtLm5paC5nb3YvcG1jL2FydGljbGVzL1BNQzYzMDg1NjgvKSkuCiAgICAgICAgICAgIC0gRGV0ZWNjacOzbiBkZSBwdXBpbGEgcnVnb3NhIGNvbiBDTk4gYSB0cmF2w6lzIGRlIGltw6FnZW5lcyBpbmZyYXJvam8gKFtXb24gZXQgYWwuLCAyMDE5XShodHRwczovL3d3dy5uY2JpLm5sbS5uaWguZ292L3BtYy9hcnRpY2xlcy9QTUM2NDEyNTk0LykpLgogICAgICAgICAgICAtIE3DqXRvZG8gbWVqb3JhZG8gYSB0cmF2w6lzIGRlIGRlZXAgbGVhcm5pbmcgcGFyYSBjb25kaWNpw7NuIGNvcnBvcmFsIGVuIHZhY2FzIGxlY2hlcmFzIChbSHVhbmcgZXQgYWwuLCAyMDE5XShodHRwczovL3d3dy5uY2JpLm5sbS5uaWguZ292L3BtYy9hcnRpY2xlcy9QTUM2NjgwODA4LykpLgogICAgICAgICAgICAtIENOTiBwYXJhIGlkZW50aWZpY2FjacOzbiBhdXRvbcOhdGljYSBkZSBwYXRvbG9nw61hcyBlbiBwbGFudGFzIChbQm91bGVudCBldCBhbC4sIDIwMTldKGh0dHBzOi8vd3d3Lm5jYmkubmxtLm5paC5nb3YvcG1jL2FydGljbGVzL1BNQzY2NjQwNDcvKSkuCiAgICAgICAgICAgIC0gTW9kZWxhY2nDs24gZGUgY2FyYWN0ZXLDrXN0aWNhcyBkZWwgc3VlbG8gYSB0cmF2w6lzIGRlIHJlZGVzIG5ldXJvbmFsZXMgcGFyYSBpcnJpZ2FjacOzbiBhdXRvbcOhdGljYSAoW0FkZXllbWkgZXQgYWwuLCAyMDE4XShodHRwczovL3d3dy5uY2JpLm5sbS5uaWguZ292L3BtYy9hcnRpY2xlcy9QTUM2MjEwOTc3LykpLgogICAgICAgICAgICAtIFByZWRpY2Npw7NuIGRlbCByZW5kaW1pZW50byBkZSBjdWx0aXZvcyBjb24gZGVlcCBsZWFybmluZyAoW0toYWtpICYgV2FuZywgMjAxOV0oaHR0cHM6Ly93d3cubmNiaS5ubG0ubmloLmdvdi9wbWMvYXJ0aWNsZXMvUE1DNjU0MDk0Mi8pKS4KICAgICAgICAgICAgLSBNb25pdG9yZW8gZGUgY3VsdGl2b3MgZGUgcGVxdWXDsW9zIHByb2R1Y3RvcmVzIGEgdHJhdsOpcyBkZSByZWRlcyBuZXVyb25hbGVzIGFydGlmaWNpYWxlcyB1c2FuZG8gaW3DoWdlbmVzIHNhdGVsaXRhbGVzIGRlIGFsdGEgcmVzb2x1Y2nDs24gZXNwYWNpYWwgKFtYaWUgZXQgYWwuLCAyMDE5XShodHRwczovL3d3dy5uY2JpLm5sbS5uaWguZ292L3BtYy9hcnRpY2xlcy9QTUM2NTY3MDQ5LykpLgoKIyMjIyBJZGVhIEludHVpdGl2YSBkZSBSZWQgTmV1cm9uYWwgUHJvZnVuZGEKICAgICAgICAgICAgCjxjZW50ZXI+CjxpbWcgc3JjPSJkZWVwbDMuZ2lmIiB3aWR0aCA9ICI0NzAiIGhlaWdodD0iMzIwIj48aW1nIHNyYz0iZGVlcGw0LmdpZiIgd2lkdGggPSAiNDcwIiBoZWlnaHQ9IjMyMCI+CjwvY2VudGVyPgoKIyMjIyBBanVzdGUgZGVsIG1vZGVsbwoKYGBge3IsIGVjaG89VFJVRX0KIyBNb2RlbG8gc2VjdWVuY2lhbDogbG9hZAptb2Rfa2VyYXMgPC0ga2VyYXNfbW9kZWxfc2VxdWVudGlhbCgpCgojIEHDsWFkaWVuZG8gY2FwYXM6IGNvbmZpZ3VyYWNpw7NuIGRlbCBtb2RlbG8KbW9kX2tlcmFzICU+JSAKICBsYXllcl9kZW5zZSh1bml0cyA9IDksIGFjdGl2YXRpb24gPSAicmVsdSIsIGlucHV0X3NoYXBlID0gYyg5KSkgJT4lIAogIGxheWVyX2JhdGNoX25vcm1hbGl6YXRpb24oKSAlPiUgCiAgbGF5ZXJfZHJvcG91dChyYXRlID0gMC4yKSAlPiUKICBsYXllcl9kZW5zZSh1bml0cyA9IDksIGFjdGl2YXRpb24gPSAicmVsdSIpICU+JSAKICBsYXllcl9iYXRjaF9ub3JtYWxpemF0aW9uKCkgJT4lIAogIGxheWVyX2RlbnNlKHVuaXRzID0gOSwgYWN0aXZhdGlvbiA9ICJyZWx1IikgJT4lIAogIGxheWVyX2JhdGNoX25vcm1hbGl6YXRpb24oKSAlPiUgCiAgbGF5ZXJfZGVuc2UodW5pdHMgPSAyLCBhY3RpdmF0aW9uID0gInNpZ21vaWQiKQoKIyBDb21waWxhbmRvIGVsIG1vZGVsbwptb2Rfa2VyYXMgJT4lIAogIGNvbXBpbGUobG9zcyA9ICJiaW5hcnlfY3Jvc3NlbnRyb3B5IiwKICAgICAgICAgIG9wdGltaXplciA9IG9wdGltaXplcl9ybXNwcm9wKCksCiAgICAgICAgICBtZXRyaWNzID0gImFjY3VyYWN5IikKCiMgQWp1c3RlIGRlbCBtb2RlbG8KbW9kX25uZXQgPC0gbW9kX2tlcmFzICU+JSAKICBmaXQoeF90cmFpbiwKICAgICAgeV90cmFpbiwKICAgICAgZXBvY2hzID0gMjAwLAogICAgICBiYXRjaF9zaXplID0gOCwKICAgICAgdmFsaWRhdGlvbl9zcGxpdCA9IDAuMiwKICAgICAgdmVyYm9zZSA9IEZBTFNFKQpgYGAKCi0gKipEZXNlbXBlw7FvIGRlIGxhIHJlZCBuZXVyb25hbDoqKgoKPGNlbnRlcj4KYGBge3J9CnBsb3QobW9kX25uZXQpICsKICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gY29sb3JlczIpICsKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjb2xvcmVzMikgKwogIHRoZW1lX3Rlc3QoKQpgYGAKPC9jZW50ZXI+CgotICoqQWNjdXJhY3kgZW4gdGVzdGluZyAobWF0cml6IGRlIGNvbmZ1c2nDs24pOioqCgpgYGB7cn0KdGVzdF9wcmVkaWNfbm5ldCA8LSBtb2Rfa2VyYXMgJT4lIHByZWRpY3RfY2xhc3Nlcyh4X3Rlc3RpLCBiYXRjaF9zaXplID0gMzIpCgpjb25mdXNpb25NYXRyaXgodGFibGUoZGZfdGVzdGkkQ2xhc3NpZmljYXRpb24sIHRlc3RfcHJlZGljX25uZXQsIAogICAgICAgICAgICAgICAgICAgICAgZG5uID0gYygiUmVhbCIsICJQcmVkaWNobyIpKSwKICAgICAgICAgICAgICAgIHBvc2l0aXZlID0gIjEiKQpgYGAKCiMjIDUuIERlc2VtcGXDsW8gZGUgbW9kZWxvcwoKLSAqKlRhYmxhIGNvbXBhcmF0aXZhIGRlIG1vZGVsb3M6KiogcmVzdWx0YWRvcyBvcmRlbmFub3MgZGUgbWF5b3IgYSBtZW5vciAqQWNjdXJhY3kqIGVuIGRhdG9zIGRlIHBydWViYSAoKnRlc3QqKS4KCmBgYHtyfQojIE1vZGVsbzogS05OIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KbWNvbmZ1c19rbm4gPC0gY29uZnVzaW9uTWF0cml4KHRhYmxlKGRmX3Rlc3RpJENsYXNzaWZpY2F0aW9uLCB0ZXN0X3ByZWRpY19rbm4sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkbm4gPSBjKCJSZWFsIiwgIlByZWRpY2hvIikpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcG9zaXRpdmUgPSAiMSIpCgphY2N1cl9rbm4gICAgPC0gbWNvbmZ1c19rbm5bM11bWzFdXVtbMV1dI2FjY3VyYWN5CmFjY3VyX2tubl9saSA8LSBtY29uZnVzX2tublszXVtbMV1dW1szXV0jYWNjdXJhY3kgbG93ZXIKYWNjdXJfa25uX2xzIDwtIG1jb25mdXNfa25uWzNdW1sxXV1bWzRdXSNhY2N1cmFjeSB1cHBlcgprYXBwYV9rbm4gICAgPC0gbWNvbmZ1c19rbm5bM11bWzFdXVtbMl1dI2thcHBhCnNlbnNpdF9rbm4gICA8LSBtY29uZnVzX2tubls0XVtbMV1dW1sxXV0jc2Vuc2l0aXZpdHkKc3BlY2lmX2tubiAgIDwtIG1jb25mdXNfa25uWzRdW1sxXV1bWzJdXSNTcGVjaWZpY2l0eQoKIyBNb2RlbG86IE5haXZlIEJheWVzIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KbWNvbmZ1c19uYmF5ZXMgPC0gY29uZnVzaW9uTWF0cml4KHRhYmxlKGRmX3Rlc3RpJENsYXNzaWZpY2F0aW9uLCB0ZXN0X3ByZWRpY19iYXllcywgCiAgICAgICAgICAgICAgICAgICAgICBkbm4gPSBjKCJSZWFsIiwgIlByZWRpY2hvIikpLAogICAgICAgICAgICAgICAgcG9zaXRpdmUgPSAiMSIpCgphY2N1cl9uYmF5ZXMgICAgPC0gbWNvbmZ1c19uYmF5ZXNbM11bWzFdXVtbMV1dI2FjY3VyYWN5CmFjY3VyX25iYXllc19saSA8LSBtY29uZnVzX25iYXllc1szXVtbMV1dW1szXV0jYWNjdXJhY3kgbG93ZXIKYWNjdXJfbmJheWVzX2xzIDwtIG1jb25mdXNfbmJheWVzWzNdW1sxXV1bWzRdXSNhY2N1cmFjeSB1cHBlcgprYXBwYV9uYmF5ZXMgICAgPC0gbWNvbmZ1c19uYmF5ZXNbM11bWzFdXVtbMl1dI2thcHBhCnNlbnNpdF9uYmF5ZXMgICA8LSBtY29uZnVzX25iYXllc1s0XVtbMV1dW1sxXV0jc2Vuc2l0aXZpdHkKc3BlY2lmX25iYXllcyAgIDwtIG1jb25mdXNfbmJheWVzWzRdW1sxXV1bWzJdXSNTcGVjaWZpY2l0eQoKIyBNb2RlbG86IFIuIExvZ8Otc3RpY2EgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQptY29uZnVzX3JlZ2wgPC0gY29uZnVzaW9uTWF0cml4KHRhYmxlKGRmX3Rlc3RpJENsYXNzaWZpY2F0aW9uLCB0ZXN0X3ByZWRpY19yZWdsLCAKICAgICAgICAgICAgICAgICAgICAgIGRubiA9IGMoIlJlYWwiLCAiUHJlZGljaG8iKSksCiAgICAgICAgICAgICAgICBwb3NpdGl2ZSA9ICIxIikKCmFjY3VyX3JlZ2wgICAgPC0gbWNvbmZ1c19yZWdsWzNdW1sxXV1bWzFdXSNhY2N1cmFjeQphY2N1cl9yZWdsX2xpIDwtIG1jb25mdXNfcmVnbFszXVtbMV1dW1szXV0jYWNjdXJhY3kgbG93ZXIKYWNjdXJfcmVnbF9scyA8LSBtY29uZnVzX3JlZ2xbM11bWzFdXVtbNF1dI2FjY3VyYWN5IHVwcGVyCmthcHBhX3JlZ2wgICAgPC0gbWNvbmZ1c19yZWdsWzNdW1sxXV1bWzJdXSNrYXBwYQpzZW5zaXRfcmVnbCAgIDwtIG1jb25mdXNfcmVnbFs0XVtbMV1dW1sxXV0jc2Vuc2l0aXZpdHkKc3BlY2lmX3JlZ2wgICA8LSBtY29uZnVzX3JlZ2xbNF1bWzFdXVtbMl1dI1NwZWNpZmljaXR5CgojIE1vZGVsbzogw4FyYm9sIEM1IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KbWNvbmZ1c190cmVlIDwtIGNvbmZ1c2lvbk1hdHJpeCh0YWJsZShkZl90ZXN0aSRDbGFzc2lmaWNhdGlvbiwgdGVzdF9wcmVkaWNfYzV0cmVlLCAKICAgICAgICAgICAgICAgICAgICAgIGRubiA9IGMoIlJlYWwiLCAiUHJlZGljaG8iKSksCiAgICAgICAgICAgICAgICBwb3NpdGl2ZSA9ICIxIikKCmFjY3VyX3RyZWUgICAgPC0gbWNvbmZ1c190cmVlWzNdW1sxXV1bWzFdXSNhY2N1cmFjeQphY2N1cl90cmVlX2xpIDwtIG1jb25mdXNfdHJlZVszXVtbMV1dW1szXV0jYWNjdXJhY3kgbG93ZXIKYWNjdXJfdHJlZV9scyA8LSBtY29uZnVzX3RyZWVbM11bWzFdXVtbNF1dI2FjY3VyYWN5IHVwcGVyCmthcHBhX3RyZWUgICAgPC0gbWNvbmZ1c190cmVlWzNdW1sxXV1bWzJdXSNrYXBwYQpzZW5zaXRfdHJlZSAgIDwtIG1jb25mdXNfdHJlZVs0XVtbMV1dW1sxXV0jc2Vuc2l0aXZpdHkKc3BlY2lmX3RyZWUgICA8LSBtY29uZnVzX3RyZWVbNF1bWzFdXVtbMl1dI1NwZWNpZmljaXR5CgojIE1vZGVsbzogUi4gRm9yZXN0IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KbWNvbmZ1c19yZiA8LSBjb25mdXNpb25NYXRyaXgodGFibGUoZGZfdGVzdGkkQ2xhc3NpZmljYXRpb24sIHRlc3RfcHJlZGljX3JmLCAKICAgICAgICAgICAgICAgICAgICAgIGRubiA9IGMoIlJlYWwiLCAiUHJlZGljaG8iKSksCiAgICAgICAgICAgICAgICBwb3NpdGl2ZSA9ICIxIikKCmFjY3VyX3JmICAgIDwtIG1jb25mdXNfcmZbM11bWzFdXVtbMV1dI2FjY3VyYWN5CmFjY3VyX3JmX2xpIDwtIG1jb25mdXNfcmZbM11bWzFdXVtbM11dI2FjY3VyYWN5IGxvd2VyCmFjY3VyX3JmX2xzIDwtIG1jb25mdXNfcmZbM11bWzFdXVtbNF1dI2FjY3VyYWN5IHVwcGVyCmthcHBhX3JmICAgIDwtIG1jb25mdXNfcmZbM11bWzFdXVtbMl1dI2thcHBhCnNlbnNpdF9yZiAgIDwtIG1jb25mdXNfcmZbNF1bWzFdXVtbMV1dI3NlbnNpdGl2aXR5CnNwZWNpZl9yZiAgIDwtIG1jb25mdXNfcmZbNF1bWzFdXVtbMl1dI1NwZWNpZmljaXR5CgojIE1vZGVsbzogU1ZNIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KbWNvbmZ1c19zdm0gPC0gY29uZnVzaW9uTWF0cml4KHRhYmxlKGRmX3Rlc3RpJENsYXNzaWZpY2F0aW9uLCB0ZXN0X3ByZWRpY19zdm0sIAogICAgICAgICAgICAgICAgICAgICAgZG5uID0gYygiUmVhbCIsICJQcmVkaWNobyIpKSwKICAgICAgICAgICAgICAgIHBvc2l0aXZlID0gIjEiKQoKYWNjdXJfc3ZtICAgIDwtIG1jb25mdXNfc3ZtWzNdW1sxXV1bWzFdXSNhY2N1cmFjeQphY2N1cl9zdm1fbGkgPC0gbWNvbmZ1c19zdm1bM11bWzFdXVtbM11dI2FjY3VyYWN5IGxvd2VyCmFjY3VyX3N2bV9scyA8LSBtY29uZnVzX3N2bVszXVtbMV1dW1s0XV0jYWNjdXJhY3kgdXBwZXIKa2FwcGFfc3ZtICAgIDwtIG1jb25mdXNfc3ZtWzNdW1sxXV1bWzJdXSNrYXBwYQpzZW5zaXRfc3ZtICAgPC0gbWNvbmZ1c19zdm1bNF1bWzFdXVtbMV1dI3NlbnNpdGl2aXR5CnNwZWNpZl9zdm0gICA8LSBtY29uZnVzX3N2bVs0XVtbMV1dW1syXV0jU3BlY2lmaWNpdHkKCiMgTW9kZWxvOiBHQk0gLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQptY29uZnVzX2dibSA8LSBjb25mdXNpb25NYXRyaXgodGFibGUoZGZfdGVzdGkkQ2xhc3NpZmljYXRpb24sIHRlc3RfcHJlZGljX2dibSwgCiAgICAgICAgICAgICAgICAgICAgICBkbm4gPSBjKCJSZWFsIiwgIlByZWRpY2hvIikpLAogICAgICAgICAgICAgICAgcG9zaXRpdmUgPSAiMSIpCgphY2N1cl9nYm0gICAgPC0gbWNvbmZ1c19nYm1bM11bWzFdXVtbMV1dI2FjY3VyYWN5CmFjY3VyX2dibV9saSA8LSBtY29uZnVzX2dibVszXVtbMV1dW1szXV0jYWNjdXJhY3kgbG93ZXIKYWNjdXJfZ2JtX2xzIDwtIG1jb25mdXNfZ2JtWzNdW1sxXV1bWzRdXSNhY2N1cmFjeSB1cHBlcgprYXBwYV9nYm0gICAgPC0gbWNvbmZ1c19nYm1bM11bWzFdXVtbMl1dI2thcHBhCnNlbnNpdF9nYm0gICA8LSBtY29uZnVzX2dibVs0XVtbMV1dW1sxXV0jc2Vuc2l0aXZpdHkKc3BlY2lmX2dibSAgIDwtIG1jb25mdXNfZ2JtWzRdW1sxXV1bWzJdXSNTcGVjaWZpY2l0eQoKIyBNb2RlbG86IE5ORVQgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQptY29uZnVzX25uZXQgPC0gY29uZnVzaW9uTWF0cml4KHRhYmxlKGRmX3Rlc3RpJENsYXNzaWZpY2F0aW9uLCB0ZXN0X3ByZWRpY19ubmV0LCAKICAgICAgICAgICAgICAgICAgICAgIGRubiA9IGMoIlJlYWwiLCAiUHJlZGljaG8iKSksCiAgICAgICAgICAgICAgICBwb3NpdGl2ZSA9ICIxIikKCmFjY3VyX25uZXQgICAgPC0gbWNvbmZ1c19ubmV0WzNdW1sxXV1bWzFdXSNhY2N1cmFjeQphY2N1cl9ubmV0X2xpIDwtIG1jb25mdXNfbm5ldFszXVtbMV1dW1szXV0jYWNjdXJhY3kgbG93ZXIKYWNjdXJfbm5ldF9scyA8LSBtY29uZnVzX25uZXRbM11bWzFdXVtbNF1dI2FjY3VyYWN5IHVwcGVyCmthcHBhX25uZXQgICAgPC0gbWNvbmZ1c19ubmV0WzNdW1sxXV1bWzJdXSNrYXBwYQpzZW5zaXRfbm5ldCAgIDwtIG1jb25mdXNfbm5ldFs0XVtbMV1dW1sxXV0jc2Vuc2l0aXZpdHkKc3BlY2lmX25uZXQgICA8LSBtY29uZnVzX25uZXRbNF1bWzFdXVtbMl1dI1NwZWNpZmljaXR5CgojIERhdGFmcmFtZQpkZl9jb21wYXJfbW9kIDwtIGRhdGEuZnJhbWUoCiAgTW9kZWxvID0gYygiS25uIiwgIk4uIEJheWVzIiwgIlIuIExvZ8Otc3RpY2EiLCAiw4FyYm9sLUM1IiwgIlIuIEZvcmVzdCIsICJTVk0iLAogICAgICAgICAgICAgIkdCTSIsICJOTmV0IiksCiAgQWNjdXJhY3kgPSBjKGFjY3VyX2tubiwgYWNjdXJfbmJheWVzLCBhY2N1cl9yZWdsLCBhY2N1cl90cmVlLCBhY2N1cl9yZiwKICAgICAgICAgICAgICAgYWNjdXJfc3ZtLCBhY2N1cl9nYm0sIGFjY3VyX25uZXQpLAogIExvd2VyX0FjID0gYyhhY2N1cl9rbm5fbGksIGFjY3VyX25iYXllc19saSwgYWNjdXJfcmVnbF9saSwgYWNjdXJfdHJlZV9saSwKICAgICAgICAgICAgICAgYWNjdXJfcmZfbGksIGFjY3VyX3N2bV9saSwgYWNjdXJfZ2JtX2xpLCBhY2N1cl9ubmV0X2xpKSwKICBVcHBlcl9BYyA9IGMoYWNjdXJfa25uX2xzLCBhY2N1cl9uYmF5ZXNfbHMsIGFjY3VyX3JlZ2xfbHMsIGFjY3VyX3RyZWVfbHMsCiAgICAgICAgICAgICAgIGFjY3VyX3JmX2xzLCBhY2N1cl9zdm1fbHMsIGFjY3VyX2dibV9scywgYWNjdXJfbm5ldF9scyksCiAgS2FwcGEgICAgPSBjKGthcHBhX2tubiwga2FwcGFfbmJheWVzLCBrYXBwYV9yZWdsLCBrYXBwYV90cmVlLCBrYXBwYV9yZiwKICAgICAgICAgICAgICAga2FwcGFfc3ZtLCBrYXBwYV9nYm0sIGthcHBhX25uZXQpLAogIEVzcGVjaWZpID0gYyhzcGVjaWZfa25uLCBzcGVjaWZfbmJheWVzLCBzcGVjaWZfcmVnbCwgc3BlY2lmX3RyZWUsIHNwZWNpZl9yZiwKICAgICAgICAgICAgICAgc3BlY2lmX3N2bSwgc3BlY2lmX2dibSwgc3BlY2lmX25uZXQpLAogIFNlbnNpdGl2ID0gYyhzZW5zaXRfa25uLCBzZW5zaXRfbmJheWVzLCBzZW5zaXRfcmVnbCwgc2Vuc2l0X3RyZWUsIHNlbnNpdF9yZiwKICAgICAgICAgICAgICAgc2Vuc2l0X3N2bSwgc2Vuc2l0X2dibSwgc2Vuc2l0X25uZXQpCikKCiMgSW1wcmltaWVuZG8gcmVzdWx0YWRvcwpkZl9jb21wYXJfbW9kICU+JSAKICByZW5hbWUoYEluZi4gQWNjdXJhY3lgID0gTG93ZXJfQWMsIGBTdXAuIEFjY3VyYWN5YCA9IFVwcGVyX0FjLAogICAgICAgICBFc3BlY2lmaWNpZGFkID0gRXNwZWNpZmksIFNlbnNpdGl2aWRhZCA9IFNlbnNpdGl2KSAlPiUgCiAgYXJyYW5nZShkZXNjKEFjY3VyYWN5KSkKYGBgCgotICoqQ29tcGFyYWNpw7NuIGRlIGFjY3VyYWN5IGRlIG1vZGVsb3MgKGdyw6FmaWNvKToqKgoKPGNlbnRlcj4KYGBge3J9CmRmX2NvbXBhcl9tb2QgJT4lIAogIGdncGxvdChkYXRhID0gLiwgYWVzKHggPSBNb2RlbG8sIHkgPSBBY2N1cmFjeSwgY29sb3IgPSBNb2RlbG8pKSArCiAgZ2VvbV9lcnJvcmJhcihhZXMoeW1pbiA9IExvd2VyX0FjLCB5bWF4ID0gVXBwZXJfQWMpLCB3aWR0aCA9IDAuMikgKwogIGdlb21fcG9pbnRyYW5nZShhZXMoeW1pbiA9IExvd2VyX0FjLCB5bWF4ID0gVXBwZXJfQWMpKSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gcHJvcC50YWJsZSh0YWJsZShkZl9zZW5vJENsYXNzaWZpY2F0aW9uKSlbWzJdXSwKICAgICAgICAgICAgIGNvbG9yID0gImJsYWNrIiwgbHR5ID0gMiwgbHdkID0gMC41KSArCiAgc2NhbGVfY29sb3JfYnJld2VyKHBhbGV0dGUgPSAiU2V0MSIpICsKICBsYWJzKHRpdGxlID0gIkFjY3VyYWN5IGVuIFRlc3RpbmciLAogICAgICAgY2FwdGlvbiA9ICJMw61uZWEgbmVncmEgZXMgbGEgcHJvcG9yY2nDs24gbWF5b3JpdGFyaWEgZGUgbGEgdmFyaWFibGUgb2JqZXRpdm8uIikgKwogIHRoZW1lX3Rlc3QoKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIpCmBgYAo8L2NlbnRlcj4KCi0gKipDb21wYXJhY2nDs24gZGUgZGUgbW9kZWxvcyAoZ3LDoWZpY28pOioqCgo8Y2VudGVyPgpgYGB7cn0KZGZfY29tcGFyX21vZCAlPiUgCiAgc2VsZWN0KC1jKExvd2VyX0FjLCBVcHBlcl9BYykpICU+JSAKICBnYXRoZXIoa2V5ID0gIm1lZGlkYSIsIHZhbHVlID0gInZhbG9yIiwgLU1vZGVsbykgJT4lIAogIGdncGxvdChkYXRhID0gLiwgYWVzKHggPSBtZWRpZGEsIHkgPSB2YWxvciwgY29sb3IgPSBNb2RlbG8pKSArCiAgZ2VvbV9wb2ludChzaXplID0gNCkgKwogIGdlb21fbGluZShhZXMoZ3JvdXAgPSBNb2RlbG8pKSArCiAgc2NhbGVfY29sb3JfYnJld2VyKHBhbGV0dGUgPSAiU2V0MSIpICsKICBsYWJzKHRpdGxlID0gIkNvbXBhcmFjacOzbiBkZSBtw6l0cmljYXMgZGUgbW9kZWxvcyIsCiAgICAgICBzdWJ0aXRsZSA9ICJUZXN0aW5nIikgKwogIHRoZW1lX3Rlc3QoKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIpCmBgYAo8L2NlbnRlcj4KCgojIyA2LiBSZWZlcmVuY2lhcwoKMS4gW0xpYnJvOiBNYWNoaW5lIExlYXJuaW5nIC0gTWljaGFlbCBDbGFya10oaHR0cHM6Ly9tLWNsYXJrLmdpdGh1Yi5pby9pbnRyb2R1Y3Rpb24tdG8tbWFjaGluZS1sZWFybmluZy8pCjIuIFtMaWJybzogdXNlUiEgTWFjaGluZSBMZWFybmluZyBUdXRvcmlhbF0oaHR0cHM6Ly9rb2FsYXZlcnNlLmdpdGh1Yi5pby9tYWNoaW5lLWxlYXJuaW5nLWluLVIvKQozLiBbTGlicm86IEhhbmRzLU9uIE1hY2hpbmUgTGVhcm5pbmcgd2l0aCBSXShodHRwczovL2JyYWRsZXlib2VobWtlLmdpdGh1Yi5pby9IT01MLykKNC4gW0xpYnJvOiBBbiBJbnRyb2R1Y3Rpb24gdG8gU3RhdGlzdGljYWwgTGVhcm5pbmddKGh0dHA6Ly93d3cuaW1lLnVuaWNhbXAuYnIvfmRpYXMvSW50b2R1Y3Rpb24lMjB0byUyMFN0YXRpc3RpY2FsJTIwTGVhcm5pbmcucGRmKQo1LiBbTGlicm86IFRoZSBFbGVtZW50cyBvZiBTdGF0aXN0aWNhbCBMZWFybmluZ10oaHR0cHM6Ly93ZWIuc3RhbmZvcmQuZWR1L35oYXN0aWUvUGFwZXJzL0VTTElJLnBkZikKNi4gW1JlY3Vyc28gd2ViOiBSIE1hcmtkb3duIE5vdGVib29rcyBmb3IgIkRlZXAgTGVhcm5pbmcgd2l0aCBSIl0oaHR0cHM6Ly9naXRodWIuY29tL2pqYWxsYWlyZS9kZWVwLWxlYXJuaW5nLXdpdGgtci1ub3RlYm9va3MpCjcuIFtSZWN1cnNvIHdlYjogQSB2aXN1YWwgaW50cm9kdWN0aW9uIHRvIG1hY2hpbmUgbGVhcm5pbmcuXShodHRwOi8vd3d3LnIyZDMudXMvdmlzdWFsLWludHJvLXRvLW1hY2hpbmUtbGVhcm5pbmctcGFydC0xLykKOC4gW0N1cnNvIFlvdXR1YmU6IFJvYmVydCBUaWJzaGlyYW5pICYgVHJldm9yIEhhc3RpZSAtIFN0YXRpc3RpY2FsIExlYXJuaW5nXShodHRwczovL3d3dy55b3V0dWJlLmNvbS93YXRjaD92PTVOOVYwN0VJZklnJmxpc3Q9UExPZzBuZ0h0Y3FiUFRsWnpSSEEyb2NRWnFCMURfcVo1Vik=