Autor: Fis. Juan Carlos Velázquez Díaz

Curso: Análisis de datos con lenguaje R (básico)

Instructor: Dr. Enrique Escalante Notario

Fecha: 27 de Agosto del 2026

Dataset: UCI Heart Disease

Introducción

Buscar una relación entre los datos clínicos de los pacientes que nos permita determinar una relación entre las diversas condiciones físicas de los pacientes, lo que nos permita obtener un modelo logarítmico que bajo los supuestos nos permite discriminar entre pacientes sanos y con enfermedad cardíaca 86% de acierto. Este tipo de análisis sobre los datos clínicos, se utilizan para encontrar relaciones sobre el estado de los pacientes y sus padecimientos, para en futuros casos poder diagnosticar de mejor manera a los pacientes basado en su cuadro clínico. Se utilizo en lenguaje R para la realización del análisis por su versatilidad acerbo de funciones relacionadas con la estadística. Se hizo un estudio de los problemas clínicos, junto a las variables y datos del dataset. Se realizo la limpieza y homologación de los datos, que posteriormente permitió analizar la relación de las variables que sirvieron para la implementación del modelo. El proyecto sirve para tener una primera aproximación de los datos necesarios para la detención de enfermedades cardiacas.

Planteamiento del problema.

El dataset UCI Heart Disease contiene información clínica de pacientes evaluados por posible presencia de enfermedad cardíaca. La base incluye resultados de mediciones médicas, respuestas a pruebas de esfuerzo y algunos indicadores clínicos utilizados como apoyo en la evaluación cardiovascular. La variable ahd será nuestra variable objetivo, que es la variable nos indica la presencia o no de la enfermedad cardíaca, para el análisis se exploró su interacción con variables como la edad, genero, niveles de colesterol, tipo de dolor torácico, entre otras variables. Por la naturaleza de la variable objetivo y los datos se utilizó un modelo logístico. Este análisis se hizo de manera educativa, a manera de profundizar en los pasos necesarios para realizar un análisis de datos junto con su reporte correspondiente.

Descripción del dataset

El dataset contienen 303 observaciones con 14 variables, de las cuales 3 son carácter y 11 son numéricas. La variable ahd es una variable categoría que nos indica si el paciente presenta o no la enfermedad cardíaca. Al transformar de manera adecuada las variables obtenemos que el dataset tiene 5 variables numéricas; Edad (age), presión arterial en reposo (rest_bp), Colesterol sérico en mg/dl (chol), Frecuencia cardíaca máxima alcanzada durante la prueba de esfuerzo (max_hr), depresión del segmento ST inducida por el ejercicio en comparación con el reposo (oldpeak) y número de vasos principales observados mediante fluoroscopía (ca). Se tiene 9 variables categóricas, las cuales son: Sexo del paciente (sex), tipo de dolor torácico reportado por el paciente (chest_pain), glucosa en ayunas mayor a 120 mg/dl (fbs), resultado electrocardiográfico en reposo (rest_ecg), angina inducida por ejercicio (ex_ang), pendiente del segmento ST durante el ejercicio máximo (slope), resultado asociado con talasemia (thal) y presencia de enfermedad cardíaca (ahd).

Metodología general

Se realizo un entendimiento de la relación entre los padecimientos y enfermedades de los pacientes, lo que nos permitió entender el dataset basándonos en la descripción de este. Por lo cual se homologaron los nombres de las variables, cambiando el tipo de datos de las variables a las correspondientes en la descripción del dataset, buscando los datos faltantes y modificándolos para ser más descriptivos. Lo que nos permite hacer un análisis exploratorio y visualización de los datos sobre los cuales se hará el análisis inferencial, para seleccionar las variables que utilizaremos en el modelo.

Limpieza y transformación de datos

library(tidyverse)  # Manipulación, importación y transformación de datos
library(janitor)    # Limpieza de nombres de variables y tablas
library(skimr)      # Inspección general de datos
library(stringr)    # Manejo de cadenas de texto
library(lubridate)  # Manejo de fechas
library(knitr)      # Presentación de tablas en reportes
library(GGally)     # Matrices de gráficos exploratorios
library(corrplot)   # Visualización de matrices de correlación
library(ggplot2)    # Visualización de gráficas.
library(yardstick)  # Métricas básicas para clasificación
library(broom)      # Correcta utilización de la función tidy
heart = read.csv("./Heart.csv")
heart = na.omit(heart) # Se eliminaron los datos faltantes
heart = heart |>
clean_names()
heart <- heart |> 
  mutate(across(where(is.character), str_trim)) # Elimina los espacios en blanco
heart <- heart |> 
  mutate(across(where(is.character), str_to_lower)) # Convierte los caracteres a minusculas 
heart <- heart |>   # Muta el DataFrame
    mutate(
      sex = factor( # Convierte la variable en una variable categorica, señalando los niveles y etiquetas.
        sex, 
        levels = c(0,1),
        labels = c("femenino","masculino")
        ),
      chest_pain = as.factor(chest_pain),   # Convierte la variable en una variable categorica sin especificar.
      fbs = factor(
        fbs,
        levels = c(0, 1),
        labels = c("glucosa_normal", "glucosa_alta")
        ),
      rest_ecg = factor(
        rest_ecg,
        levels = c(0, 1, 2),
        labels = c("normal", "anormal","posible_hipertrofia")
        ),
      ex_ang = factor(
        ex_ang,
        levels = c(0, 1),
        labels = c("sin_angina","con_angina")
        ),
      slope = factor(
        slope,
        levels = c(1,2,3),
        labels = c("ascendente","plana","descendente")
        ),
      thal = as.factor(thal),
      ahd = factor(
        ahd,
        levels = c("no", "yes"),
        labels = c("sin_enfermedad", "con_enfermedad")
        )
      )
heart <- heart |>
  select(-x)  #Elimina la columna x del DataFrame

El dataset contenía 5 datos faltantes, en dos de las variables, que representaban el 2% de los datos totales, por lo que al ver que no alteraría en gran media el resultado final y para no sesgar el resultado final, se optó por eliminar los datos faltantes. Modificar el tipo de variable según la descripción del dataset junto con la modificación de los datos para ser congruente con la información que contiene. Como la homologación de las variables. De esta manera el DataFrame se quedo con 297 observaciones.

Análisis exploratorio y visualización de datos

heart |> 
  select(age, rest_bp, chol, max_hr, oldpeak, ca) |>
  skim_with(
    numeric = sfl(
      rango = ~ max(.x, na.rm = TRUE) - min(.x, na.rm = TRUE),
      varianza = ~ var(.x, na.rm = TRUE)
    ),
    append = TRUE
  )()   # Muestra estadística resumida de las variables elegidas (numéricas) junto con el rango y varianza
── Data Summary ────────────────────────
                           Values                      
Name                       select(heart, age, rest_b...
Number of rows             297                         
Number of columns          6                           
_______________________                                
Column type frequency:                                 
  numeric                  6                           
________________________                               
Group variables            None                        

Estadística de resumen de las variables numéricas, como la media, mediana, cuartiles, rango, junto como un pequeño histograma.

heart |>
  select(ahd, sex, chest_pain, fbs, rest_ecg, ex_ang, slope) |>
  pivot_longer(
    cols = everything(),
    names_to = "variable",
    values_to = "categoria"
  ) |>
  count(variable, categoria, name = "frecuencia") |>
  group_by(variable) |>
  mutate(
    porcentaje = 100 * frecuencia / sum(frecuencia)
  ) |>
  ungroup()   #Tabla de frecuencia para las variables categóricas.

Tabla de frecuencias de las variables categóricas.

ggplot(heart, aes(x = ca, y = ahd)) +
  geom_boxplot() +
  labs(
    title = "Distribución del número de vasos por diagnóstico de enfermedad cardiaca",
    x = "Número de vasos",
    y = "Diagnóstico"
  ) +
  theme_minimal()

Gráfica de bigotes de ca agrupado por ahd, en donde se muestran los valores atípicos. Y se muestra que la mayoría de los pacientes sin enfermedad no presentan vasos principales.

ggplot(heart, aes(x = age, y = rest_bp)) + 
           geom_point() + 
           labs( 
                title = "Relación entre edad y la presión arterial", 
                x = "Edad", 
                 y = "Presión arterial"
             ) + geom_smooth(method = "lm", se = FALSE)

Gráfica de dispersión de rest_bp con respecto a la edad.

ggplot(heart, aes(x = chest_pain, fill = ahd)) +
 geom_bar(position = "fill") +
 facet_wrap(~ sex) +
 coord_flip() +
 labs(
  title = "AHD según tipo de dolor torácico y sexo",
  x = "Tipo de dolor torácico",
  y = "Proporción",
  fill = "Enfermedad cardiaca"
 )

Gráfica de barras de la relación entre sex, chest_pain y ahd, donde se muestran que la mayoría de los casos asintomáticos no presentan enfermedad.

heart_num <- heart |> 
             select(age, rest_bp, chol, max_hr, oldpeak, ca) |> 
             drop_na() 
correlaciones <- cor(heart_num) 
correlaciones
               age     rest_bp          chol        max_hr     oldpeak          ca
age      1.0000000  0.29047626  2.026435e-01 -3.945629e-01  0.19712262  0.36221034
rest_bp  0.2904763  1.00000000  1.315357e-01 -4.910766e-02  0.19124314  0.09795376
chol     0.2026435  0.13153571  1.000000e+00 -7.456799e-05  0.03859579  0.11594459
max_hr  -0.3945629 -0.04910766 -7.456799e-05  1.000000e+00 -0.34763997 -0.26872698
oldpeak  0.1971226  0.19124314  3.859579e-02 -3.476400e-01  1.00000000  0.29445228
ca       0.3622103  0.09795376  1.159446e-01 -2.687270e-01  0.29445228  1.00000000
heart_num <- heart |> 
             select(age, rest_bp, chol, max_hr, oldpeak, ca) |> 
             drop_na() 
correlaciones <- cor(heart_num) 
corrplot(correlaciones, method="color")

Correlación entre las variables numéricas, donde se observa que la mayoría de las correlaciones son de medias, débiles a muy débiles.

heart |> 
      select(age, rest_bp, chol, max_hr, oldpeak, ca) |> 
      pivot_longer( 
          cols = everything(), 
          names_to = "variable", 
          values_to = "valor" 
) |> 
ggplot(aes(x = variable, y = valor)) + 
geom_boxplot() + 
coord_flip() + 
labs( 
  title = "Revisión de posibles valores atípicos", 
  x = "Variable", 
  y = "Valor"
)

Gráfica de bigotes, donde se muestran los valores atípicos de las variables.

Del análisis se obtuvo que la mayoría de las variables numéricas tuvieron una relación con ahd, salvo el caso del chol, en el caso de las variables categóricas fbs es la variable que menos relación con ahd. Con los valores atípicos en todas las variables salvo age. De este análisis obtenemos que; sex, chest_pain, ex_ang, oldpeak, slope y ca son las variables que mayor relación tienen con ahd.

t.test(heart$ca)

    One Sample t-test

data:  heart$ca
t = 12.421, df = 296, p-value < 2.2e-16
alternative hypothesis: true mean is not equal to 0
95 percent confidence interval:
 0.5695421 0.7839933
sample estimates:
mean of x 
0.6767677 
t.test(ca ~ ahd, data = heart)

    Welch Two Sample t-test

data:  ca by ahd
t = -8.6736, df = 220.45, p-value = 9.252e-16
alternative hypothesis: true difference in means between group sin_enfermedad and group con_enfermedad is not equal to 0
95 percent confidence interval:
 -1.0688871 -0.6730837
sample estimates:
mean in group sin_enfermedad mean in group con_enfermedad 
                    0.275000                     1.145985 
tabla_sex_ahd <- table(heart$sex, heart$ahd) 
tabla_sex_ahd 
           
            sin_enfermedad con_enfermedad
  femenino              71             25
  masculino             89            112
chisq.test(tabla_sex_ahd) 

    Pearson's Chi-squared test with Yates' continuity correction

data:  tabla_sex_ahd
X-squared = 21.852, df = 1, p-value = 2.946e-06
prop.table(tabla_sex_ahd, margin = 1)
           
            sin_enfermedad con_enfermedad
  femenino       0.7395833      0.2604167
  masculino      0.4427861      0.5572139
tabla_chest_ahd <- table(heart$chest_pain, heart$ahd) 
tabla_chest_ahd 
              
               sin_enfermedad con_enfermedad
  asymptomatic             39            103
  nonanginal               65             18
  nontypical               40              9
  typical                  16              7
chisq.test(tabla_chest_ahd) 

    Pearson's Chi-squared test

data:  tabla_chest_ahd
X-squared = 77.276, df = 3, p-value < 2.2e-16
prop.table(tabla_chest_ahd, margin = 1)
              
               sin_enfermedad con_enfermedad
  asymptomatic      0.2746479      0.7253521
  nonanginal        0.7831325      0.2168675
  nontypical        0.8163265      0.1836735
  typical           0.6956522      0.3043478

De análisis inferencial se tomaron las variables sex, chest_pain, ex_ang, oldpeak y ca para la realización del modelo.

Modelado estadístico básico

heart <- heart |> 
  mutate(
    ahd_bin = if_else(ahd == "con_enfermedad", 1, 0)
  )
modelo_log_1 <- glm(
  ahd_bin ~    ca + chest_pain + oldpeak + slope + rest_bp + thal + sex + ex_ang + max_hr,
  data = heart,
  family = binomial
)

summary(modelo_log_1)

Call:
glm(formula = ahd_bin ~ ca + chest_pain + oldpeak + slope + rest_bp + 
    thal + sex + ex_ang + max_hr, family = binomial, data = heart)

Coefficients:
                     Estimate Std. Error z value Pr(>|z|)    
(Intercept)          -3.73679    2.19017  -1.706 0.087978 .  
ca                    1.23705    0.25663   4.820 1.43e-06 ***
chest_painnonanginal -1.97069    0.48722  -4.045 5.24e-05 ***
chest_painnontypical -0.88221    0.55158  -1.599 0.109727    
chest_paintypical    -2.17372    0.65700  -3.309 0.000938 ***
oldpeak               0.39254    0.22234   1.765 0.077487 .  
slopeplana            1.20046    0.46467   2.583 0.009781 ** 
slopedescendente      0.45136    0.89438   0.505 0.613800    
rest_bp               0.02286    0.01032   2.215 0.026757 *  
thalnormal            0.23814    0.76139   0.313 0.754458    
thalreversable        1.60117    0.74763   2.142 0.032221 *  
sexmasculino          1.43037    0.49383   2.896 0.003774 ** 
ex_angcon_angina      0.66945    0.43267   1.547 0.121804    
max_hr               -0.01497    0.01012  -1.479 0.139097    
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 409.95  on 296  degrees of freedom
Residual deviance: 195.98  on 283  degrees of freedom
AIC: 223.98

Number of Fisher Scoring iterations: 6
glance(modelo_log_1)
heart <- heart |> 
  mutate(
    prob_ahd = predict(modelo_log_1, type = "response")
  )
ggplot(heart, aes(x = prob_ahd, fill = ahd)) +
  geom_histogram(bins = 20, alpha = 0.7, position = "identity") +
  labs(
    title = "Distribución de probabilidades estimadas de AHD",
    x = "Probabilidad estimada de enfermedad cardiaca",
    y = "Frecuencia",
    fill = "AHD"
  ) +
  theme_minimal()

heart <- heart |> 
  mutate(
    pred_ahd = if_else(
      prob_ahd >= 0.5,
      "con_enfermedad",
      "sin_enfermedad"
    ),
    pred_ahd = factor(
      pred_ahd,
      levels = c("con_enfermedad", "sin_enfermedad")
    )
  )
mean(heart$ahd == heart$pred_ahd)
[1] 0.8686869

Se realizo un modelo de regresión logística sobre la variable objetivo ahd junto con las variables predictoras; sex, chest_pain, ex_ang, oldpeak, slope, rest_bp, max_hr, ca y thal. El cual tuvo un ACI de 223.98, cuyo valor mientras más bajo, determina un mejor modelado de la variable. El modelo tiene su mayor limitación en que se realizo solamente con una muestra de la población. Y una complejidad más alta por el uso de múltiples variables.

Resultados principales

Del EDA (análisis exploratorio) podemos obtener que las variables que mayor relación tiene con la variable ahd son; ca, chest_pain y sex, siendo ca la única variable numérica que tiene una alta relevancia, también se obtuvo que las variables chol y fbs no tienen casi relación con la variable. Estos mismos resultados los podemos volver a observa en el análisis inferencial, donde se muestra que tiene una mayor relación bajos las pruebas de hipótesis y los rangos de confianza. Bajo estos supuestos se seleccionaron las variables: chest_pain, oldpeak, slope, rest_bp y ca para realización el modelo, ademas se agregaron las variables thal, sex, ex_ang y max_hr para mejorar un poco el modelado. Y lograr un ACI de 223.98, que nos permitió obtener una clasificación correcta de los pacientes con y sin enfermedad de casi el 87%.

Problemas encontrados durante el desarrollo

Se tuvo que realizar una limpieza de los datos, debido a que el dataset no tenia el formato adecuado para el nombre de las variables, así como datos faltantes y los tipos de variables no adecuadas. Se necesito agregar la biblioteca “broom” debido a errores con la función tidy. La variable chol presento una complicación en la interpretación del EDA, que fue correctamente descartada en el análisis inferencial. El ajuste del modelo fue realizado de manera correcta.

Conclusión

Del dataset aprendí las consideraciones que se deben tomar poder utilizar los datos, entre la homologación de los datos y los nombres de las variables así como el tratamiento de datos faltantes. Con lo que se logro identificar que sex, chest_pain, ca y oldpeak son las variables más relevantes, información que se logro apreciar gracias al análisis exploratorio e inferencial, de manera gráfica, cuantitativa y bajo las pruebas de hipótesis. Durante el análisis exploratorio se identifico que chol y fbs no tenían relación con el modelo. Durante la realización del modelo todo el trabajo anterior tuvo un mayor sentido y se logro obtener un modelo básico que sirve como punto de partida para encontrar una forma de detectar una enfermedad, recordando que todo el análisis solo se hizo sobre una muestra de la población, así como su clasificación, por lo que seria necesario ver si con otras muestras los margenes de detección siguen siendo los mismos.

Referencias

LS0tDQp0aXRsZTogIkluZGljYWRvcmVzIGRlIHByb2JsZW1hcyBjYXJkaWFjb3MsIGJham8gZWwgYW5hbGlzaXMgZGUgZGF0b3MgZGUgcGFjaWVudGVzLiINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KLS0tDQpBdXRvcjogRmlzLiBKdWFuIENhcmxvcyBWZWzDoXpxdWV6IETDrWF6DQoNCkN1cnNvOiAgQW7DoWxpc2lzIGRlIGRhdG9zIGNvbiBsZW5ndWFqZSBSIChiw6FzaWNvKQ0KDQpJbnN0cnVjdG9yOiBEci4gRW5yaXF1ZSBFc2NhbGFudGUgTm90YXJpbw0KDQpGZWNoYTogMjcgZGUgQWdvc3RvIGRlbCAyMDI2DQoNCkRhdGFzZXQ6IFVDSSBIZWFydCBEaXNlYXNlDQoNCiMjIEludHJvZHVjY2nDs24NCg0KQnVzY2FyIHVuYSByZWxhY2nDs24gZW50cmUgbG9zIGRhdG9zIGNsw61uaWNvcyBkZSBsb3MgcGFjaWVudGVzIHF1ZSBub3MgcGVybWl0YSBkZXRlcm1pbmFyIHVuYSByZWxhY2nDs24gZW50cmUgbGFzIGRpdmVyc2FzIGNvbmRpY2lvbmVzIGbDrXNpY2FzIGRlIGxvcyBwYWNpZW50ZXMsIGxvIHF1ZSBub3MgcGVybWl0YSBvYnRlbmVyIHVuIG1vZGVsbyBsb2dhcsOtdG1pY28gcXVlIGJham8gbG9zIHN1cHVlc3RvcyBub3MgcGVybWl0ZSBkaXNjcmltaW5hciBlbnRyZSBwYWNpZW50ZXMgc2Fub3MgeSBjb24gZW5mZXJtZWRhZCBjYXJkw61hY2EgODYlIGRlIGFjaWVydG8uIEVzdGUgdGlwbyBkZSBhbsOhbGlzaXMgc29icmUgbG9zIGRhdG9zIGNsw61uaWNvcywgc2UgdXRpbGl6YW4gcGFyYSBlbmNvbnRyYXIgcmVsYWNpb25lcyBzb2JyZSBlbCBlc3RhZG8gZGUgbG9zIHBhY2llbnRlcyB5IHN1cyBwYWRlY2ltaWVudG9zLCBwYXJhIGVuIGZ1dHVyb3MgY2Fzb3MgcG9kZXIgZGlhZ25vc3RpY2FyIGRlIG1lam9yIG1hbmVyYSBhIGxvcyBwYWNpZW50ZXMgYmFzYWRvIGVuIHN1IGN1YWRybyBjbMOtbmljby4NClNlIHV0aWxpem8gZW4gbGVuZ3VhamUgUiBwYXJhIGxhIHJlYWxpemFjacOzbiBkZWwgYW7DoWxpc2lzIHBvciBzdSB2ZXJzYXRpbGlkYWQgYWNlcmJvIGRlIGZ1bmNpb25lcyByZWxhY2lvbmFkYXMgY29uIGxhIGVzdGFkw61zdGljYS4gU2UgaGl6byB1biBlc3R1ZGlvIGRlIGxvcyBwcm9ibGVtYXMgY2zDrW5pY29zLCBqdW50byBhIGxhcyB2YXJpYWJsZXMgeSBkYXRvcyBkZWwgZGF0YXNldC4gU2UgcmVhbGl6byBsYSBsaW1waWV6YSB5IGhvbW9sb2dhY2nDs24gZGUgbG9zIGRhdG9zLCBxdWUgcG9zdGVyaW9ybWVudGUgcGVybWl0acOzIGFuYWxpemFyIGxhIHJlbGFjacOzbiBkZSBsYXMgdmFyaWFibGVzIHF1ZSBzaXJ2aWVyb24gcGFyYSBsYSBpbXBsZW1lbnRhY2nDs24gZGVsIG1vZGVsby4NCkVsIHByb3llY3RvIHNpcnZlIHBhcmEgdGVuZXIgdW5hIHByaW1lcmEgYXByb3hpbWFjacOzbiBkZSBsb3MgZGF0b3MgbmVjZXNhcmlvcyBwYXJhIGxhIGRldGVuY2nDs24gZGUgZW5mZXJtZWRhZGVzIGNhcmRpYWNhcy4NCg0KIyMjIFBsYW50ZWFtaWVudG8gZGVsIHByb2JsZW1hLg0KDQpFbCBkYXRhc2V0IFVDSSBIZWFydCBEaXNlYXNlIGNvbnRpZW5lIGluZm9ybWFjacOzbiBjbMOtbmljYSBkZSBwYWNpZW50ZXMgZXZhbHVhZG9zIHBvciBwb3NpYmxlIHByZXNlbmNpYSBkZSBlbmZlcm1lZGFkIGNhcmTDrWFjYS4gTGEgYmFzZSBpbmNsdXllIHJlc3VsdGFkb3MgZGUgbWVkaWNpb25lcyBtw6lkaWNhcywgcmVzcHVlc3RhcyBhIHBydWViYXMgZGUgZXNmdWVyem8geSBhbGd1bm9zIGluZGljYWRvcmVzIGNsw61uaWNvcyB1dGlsaXphZG9zIGNvbW8gYXBveW8gZW4gbGEgZXZhbHVhY2nDs24gY2FyZGlvdmFzY3VsYXIuIExhIHZhcmlhYmxlIGFoZCBzZXLDoSBudWVzdHJhIHZhcmlhYmxlIG9iamV0aXZvLCBxdWUgZXMgbGEgdmFyaWFibGUgbm9zIGluZGljYSBsYSBwcmVzZW5jaWEgbyBubyBkZSBsYSBlbmZlcm1lZGFkIGNhcmTDrWFjYSwgcGFyYSBlbCBhbsOhbGlzaXMgc2UgZXhwbG9yw7Mgc3UgaW50ZXJhY2Npw7NuIGNvbiB2YXJpYWJsZXMgY29tbyBsYSBlZGFkLCBnZW5lcm8sIG5pdmVsZXMgZGUgY29sZXN0ZXJvbCwgdGlwbyBkZSBkb2xvciB0b3LDoWNpY28sIGVudHJlIG90cmFzIHZhcmlhYmxlcy4gUG9yIGxhIG5hdHVyYWxlemEgZGUgbGEgdmFyaWFibGUgb2JqZXRpdm8geSBsb3MgZGF0b3Mgc2UgdXRpbGl6w7MgdW4gbW9kZWxvIGxvZ8Otc3RpY28uIEVzdGUgYW7DoWxpc2lzIHNlIGhpem8gZGUgbWFuZXJhIGVkdWNhdGl2YSwgYSBtYW5lcmEgZGUgcHJvZnVuZGl6YXIgZW4gbG9zIHBhc29zIG5lY2VzYXJpb3MgcGFyYSByZWFsaXphciB1biBhbsOhbGlzaXMgZGUgZGF0b3MganVudG8gY29uIHN1IHJlcG9ydGUgY29ycmVzcG9uZGllbnRlLg0KDQojIyMgRGVzY3JpcGNpw7NuIGRlbCBkYXRhc2V0DQoNCkVsIGRhdGFzZXQgY29udGllbmVuIDMwMyBvYnNlcnZhY2lvbmVzIGNvbiAxNCB2YXJpYWJsZXMsIGRlIGxhcyBjdWFsZXMgMyBzb24gY2Fyw6FjdGVyIHkgMTEgc29uIG51bcOpcmljYXMuIExhIHZhcmlhYmxlIGFoZCBlcyB1bmEgdmFyaWFibGUgY2F0ZWdvcsOtYSBxdWUgbm9zIGluZGljYSBzaSBlbCBwYWNpZW50ZSBwcmVzZW50YSBvIG5vIGxhIGVuZmVybWVkYWQgY2FyZMOtYWNhLiBBbCB0cmFuc2Zvcm1hciBkZSBtYW5lcmEgYWRlY3VhZGEgbGFzIHZhcmlhYmxlcyBvYnRlbmVtb3MgcXVlIGVsIGRhdGFzZXQgdGllbmUgNSB2YXJpYWJsZXMgbnVtw6lyaWNhczsgRWRhZCAoYWdlKSwgcHJlc2nDs24gYXJ0ZXJpYWwgZW4gcmVwb3NvIChyZXN0X2JwKSwgQ29sZXN0ZXJvbCBzw6lyaWNvIGVuIG1nL2RsIChjaG9sKSwgRnJlY3VlbmNpYSBjYXJkw61hY2EgbcOheGltYSBhbGNhbnphZGEgZHVyYW50ZSBsYSBwcnVlYmEgZGUgZXNmdWVyem8gKG1heF9ociksIGRlcHJlc2nDs24gZGVsIHNlZ21lbnRvIFNUIGluZHVjaWRhIHBvciBlbCBlamVyY2ljaW8gZW4gY29tcGFyYWNpw7NuIGNvbiBlbCByZXBvc28gKG9sZHBlYWspIHkgbsO6bWVybyBkZSB2YXNvcyBwcmluY2lwYWxlcyBvYnNlcnZhZG9zIG1lZGlhbnRlIGZsdW9yb3Njb3DDrWEgKGNhKS4gU2UgdGllbmUgOSB2YXJpYWJsZXMgY2F0ZWfDs3JpY2FzLCBsYXMgY3VhbGVzIHNvbjogU2V4byBkZWwgcGFjaWVudGUgKHNleCksIHRpcG8gZGUgZG9sb3IgdG9yw6FjaWNvIHJlcG9ydGFkbyBwb3IgZWwgcGFjaWVudGUgKGNoZXN0X3BhaW4pLCBnbHVjb3NhIGVuIGF5dW5hcyBtYXlvciBhIDEyMCBtZy9kbCAoZmJzKSwgcmVzdWx0YWRvIGVsZWN0cm9jYXJkaW9ncsOhZmljbyBlbiByZXBvc28gKHJlc3RfZWNnKSwgYW5naW5hIGluZHVjaWRhIHBvciBlamVyY2ljaW8gKGV4X2FuZyksIHBlbmRpZW50ZSBkZWwgc2VnbWVudG8gU1QgZHVyYW50ZSBlbCBlamVyY2ljaW8gbcOheGltbyAoc2xvcGUpLCByZXN1bHRhZG8gYXNvY2lhZG8gY29uIHRhbGFzZW1pYSAodGhhbCkgeSBwcmVzZW5jaWEgZGUgZW5mZXJtZWRhZCBjYXJkw61hY2EgKGFoZCkuDQoNCiMjIyBNZXRvZG9sb2fDrWEgZ2VuZXJhbA0KDQpTZSByZWFsaXpvIHVuIGVudGVuZGltaWVudG8gZGUgbGEgcmVsYWNpw7NuIGVudHJlIGxvcyBwYWRlY2ltaWVudG9zIHkgZW5mZXJtZWRhZGVzIGRlIGxvcyBwYWNpZW50ZXMsIGxvIHF1ZSBub3MgcGVybWl0acOzIGVudGVuZGVyIGVsIGRhdGFzZXQgYmFzw6FuZG9ub3MgZW4gbGEgZGVzY3JpcGNpw7NuIGRlIGVzdGUuIFBvciBsbyBjdWFsIHNlIGhvbW9sb2dhcm9uIGxvcyBub21icmVzIGRlIGxhcyB2YXJpYWJsZXMsIGNhbWJpYW5kbyBlbCB0aXBvIGRlIGRhdG9zIGRlIGxhcyB2YXJpYWJsZXMgYSBsYXMgY29ycmVzcG9uZGllbnRlcyBlbiBsYSBkZXNjcmlwY2nDs24gZGVsIGRhdGFzZXQsIGJ1c2NhbmRvIGxvcyBkYXRvcyBmYWx0YW50ZXMgeSBtb2RpZmljw6FuZG9sb3MgcGFyYSBzZXIgbcOhcyBkZXNjcmlwdGl2b3MuIExvIHF1ZSBub3MgcGVybWl0ZSBoYWNlciB1biBhbsOhbGlzaXMgZXhwbG9yYXRvcmlvIHkgdmlzdWFsaXphY2nDs24gZGUgbG9zIGRhdG9zIHNvYnJlIGxvcyBjdWFsZXMgc2UgaGFyw6EgZWwgYW7DoWxpc2lzIGluZmVyZW5jaWFsLCBwYXJhIHNlbGVjY2lvbmFyIGxhcyB2YXJpYWJsZXMgcXVlIHV0aWxpemFyZW1vcyBlbiBlbCBtb2RlbG8uDQoNCiMjIyBMaW1waWV6YSB5IHRyYW5zZm9ybWFjacOzbiBkZSBkYXRvcw0KDQpgYGB7cn0NCmxpYnJhcnkodGlkeXZlcnNlKSAgIyBNYW5pcHVsYWNpw7NuLCBpbXBvcnRhY2nDs24geSB0cmFuc2Zvcm1hY2nDs24gZGUgZGF0b3MNCmxpYnJhcnkoamFuaXRvcikgICAgIyBMaW1waWV6YSBkZSBub21icmVzIGRlIHZhcmlhYmxlcyB5IHRhYmxhcw0KbGlicmFyeShza2ltcikgICAgICAjIEluc3BlY2Npw7NuIGdlbmVyYWwgZGUgZGF0b3MNCmxpYnJhcnkoc3RyaW5ncikgICAgIyBNYW5lam8gZGUgY2FkZW5hcyBkZSB0ZXh0bw0KbGlicmFyeShsdWJyaWRhdGUpICAjIE1hbmVqbyBkZSBmZWNoYXMNCmxpYnJhcnkoa25pdHIpICAgICAgIyBQcmVzZW50YWNpw7NuIGRlIHRhYmxhcyBlbiByZXBvcnRlcw0KbGlicmFyeShHR2FsbHkpICAgICAjIE1hdHJpY2VzIGRlIGdyw6FmaWNvcyBleHBsb3JhdG9yaW9zDQpsaWJyYXJ5KGNvcnJwbG90KSAgICMgVmlzdWFsaXphY2nDs24gZGUgbWF0cmljZXMgZGUgY29ycmVsYWNpw7NuDQpsaWJyYXJ5KGdncGxvdDIpICAgICMgVmlzdWFsaXphY2nDs24gZGUgZ3LDoWZpY2FzLg0KbGlicmFyeSh5YXJkc3RpY2spICAjIE3DqXRyaWNhcyBiw6FzaWNhcyBwYXJhIGNsYXNpZmljYWNpw7NuDQpsaWJyYXJ5KGJyb29tKSAgICAgICMgQ29ycmVjdGEgdXRpbGl6YWNpw7NuIGRlIGxhIGZ1bmNpw7NuIHRpZHkNCmBgYA0KYGBge3J9DQpoZWFydCA9IHJlYWQuY3N2KCIuL0hlYXJ0LmNzdiIpDQpgYGANCmBgYHtyfQ0KaGVhcnQgPSBuYS5vbWl0KGhlYXJ0KSAjIFNlIGVsaW1pbmFyb24gbG9zIGRhdG9zIGZhbHRhbnRlcw0KYGBgDQpgYGB7cn0NCmhlYXJ0ID0gaGVhcnQgfD4NCmNsZWFuX25hbWVzKCkNCmBgYA0KYGBge3J9DQpoZWFydCA8LSBoZWFydCB8PiANCiAgbXV0YXRlKGFjcm9zcyh3aGVyZShpcy5jaGFyYWN0ZXIpLCBzdHJfdHJpbSkpICMgRWxpbWluYSBsb3MgZXNwYWNpb3MgZW4gYmxhbmNvDQpoZWFydCA8LSBoZWFydCB8PiANCiAgbXV0YXRlKGFjcm9zcyh3aGVyZShpcy5jaGFyYWN0ZXIpLCBzdHJfdG9fbG93ZXIpKSAjIENvbnZpZXJ0ZSBsb3MgY2FyYWN0ZXJlcyBhIG1pbnVzY3VsYXMgDQpgYGANCmBgYHtyfQ0KaGVhcnQgPC0gaGVhcnQgfD4gICAjIE11dGEgZWwgRGF0YUZyYW1lDQogICAgbXV0YXRlKA0KICAgICAgc2V4ID0gZmFjdG9yKCAjIENvbnZpZXJ0ZSBsYSB2YXJpYWJsZSBlbiB1bmEgdmFyaWFibGUgY2F0ZWdvcmljYSwgc2XDsWFsYW5kbyBsb3Mgbml2ZWxlcyB5IGV0aXF1ZXRhcy4NCiAgICAgICAgc2V4LCANCiAgICAgICAgbGV2ZWxzID0gYygwLDEpLA0KICAgICAgICBsYWJlbHMgPSBjKCJmZW1lbmlubyIsIm1hc2N1bGlubyIpDQogICAgICAgICksDQogICAgICBjaGVzdF9wYWluID0gYXMuZmFjdG9yKGNoZXN0X3BhaW4pLCAgICMgQ29udmllcnRlIGxhIHZhcmlhYmxlIGVuIHVuYSB2YXJpYWJsZSBjYXRlZ29yaWNhIHNpbiBlc3BlY2lmaWNhci4NCiAgICAgIGZicyA9IGZhY3RvcigNCiAgICAgICAgZmJzLA0KICAgICAgICBsZXZlbHMgPSBjKDAsIDEpLA0KICAgICAgICBsYWJlbHMgPSBjKCJnbHVjb3NhX25vcm1hbCIsICJnbHVjb3NhX2FsdGEiKQ0KICAgICAgICApLA0KICAgICAgcmVzdF9lY2cgPSBmYWN0b3IoDQogICAgICAgIHJlc3RfZWNnLA0KICAgICAgICBsZXZlbHMgPSBjKDAsIDEsIDIpLA0KICAgICAgICBsYWJlbHMgPSBjKCJub3JtYWwiLCAiYW5vcm1hbCIsInBvc2libGVfaGlwZXJ0cm9maWEiKQ0KICAgICAgICApLA0KICAgICAgZXhfYW5nID0gZmFjdG9yKA0KICAgICAgICBleF9hbmcsDQogICAgICAgIGxldmVscyA9IGMoMCwgMSksDQogICAgICAgIGxhYmVscyA9IGMoInNpbl9hbmdpbmEiLCJjb25fYW5naW5hIikNCiAgICAgICAgKSwNCiAgICAgIHNsb3BlID0gZmFjdG9yKA0KICAgICAgICBzbG9wZSwNCiAgICAgICAgbGV2ZWxzID0gYygxLDIsMyksDQogICAgICAgIGxhYmVscyA9IGMoImFzY2VuZGVudGUiLCJwbGFuYSIsImRlc2NlbmRlbnRlIikNCiAgICAgICAgKSwNCiAgICAgIHRoYWwgPSBhcy5mYWN0b3IodGhhbCksDQogICAgICBhaGQgPSBmYWN0b3IoDQogICAgICAgIGFoZCwNCiAgICAgICAgbGV2ZWxzID0gYygibm8iLCAieWVzIiksDQogICAgICAgIGxhYmVscyA9IGMoInNpbl9lbmZlcm1lZGFkIiwgImNvbl9lbmZlcm1lZGFkIikNCiAgICAgICAgKQ0KICAgICAgKQ0KYGBgDQpgYGB7cn0NCmhlYXJ0IDwtIGhlYXJ0IHw+DQogIHNlbGVjdCgteCkgICNFbGltaW5hIGxhIGNvbHVtbmEgeCBkZWwgRGF0YUZyYW1lDQpgYGANCkVsIGRhdGFzZXQgY29udGVuw61hIDUgZGF0b3MgZmFsdGFudGVzLCBlbiBkb3MgZGUgbGFzIHZhcmlhYmxlcywgcXVlIHJlcHJlc2VudGFiYW4gZWwgMiUgZGUgbG9zIGRhdG9zIHRvdGFsZXMsIHBvciBsbyBxdWUgYWwgdmVyIHF1ZSBubyBhbHRlcmFyw61hIGVuIGdyYW4gbWVkaWEgZWwgcmVzdWx0YWRvIGZpbmFsIHkgcGFyYSBubyBzZXNnYXIgZWwgcmVzdWx0YWRvIGZpbmFsLCBzZSBvcHTDsyBwb3IgZWxpbWluYXIgbG9zIGRhdG9zIGZhbHRhbnRlcy4gTW9kaWZpY2FyIGVsIHRpcG8gZGUgdmFyaWFibGUgc2Vnw7puIGxhIGRlc2NyaXBjacOzbiBkZWwgZGF0YXNldCBqdW50byBjb24gbGEgbW9kaWZpY2FjacOzbiBkZSBsb3MgZGF0b3MgcGFyYSBzZXIgY29uZ3J1ZW50ZSBjb24gbGEgaW5mb3JtYWNpw7NuIHF1ZSBjb250aWVuZS4gQ29tbyBsYSBob21vbG9nYWNpw7NuIGRlIGxhcyB2YXJpYWJsZXMuIERlIGVzdGEgbWFuZXJhIGVsIERhdGFGcmFtZSBzZSBxdWVkbyBjb24gMjk3IG9ic2VydmFjaW9uZXMuDQoNCiMjIyBBbsOhbGlzaXMgZXhwbG9yYXRvcmlvIHkgdmlzdWFsaXphY2nDs24gZGUgZGF0b3MgDQoNCmBgYHtyfQ0KaGVhcnQgfD4gDQogIHNlbGVjdChhZ2UsIHJlc3RfYnAsIGNob2wsIG1heF9ociwgb2xkcGVhaywgY2EpIHw+DQogIHNraW1fd2l0aCgNCiAgICBudW1lcmljID0gc2ZsKA0KICAgICAgcmFuZ28gPSB+IG1heCgueCwgbmEucm0gPSBUUlVFKSAtIG1pbigueCwgbmEucm0gPSBUUlVFKSwNCiAgICAgIHZhcmlhbnphID0gfiB2YXIoLngsIG5hLnJtID0gVFJVRSkNCiAgICApLA0KICAgIGFwcGVuZCA9IFRSVUUNCiAgKSgpICAgIyBNdWVzdHJhIGVzdGFkw61zdGljYSByZXN1bWlkYSBkZSBsYXMgdmFyaWFibGVzIGVsZWdpZGFzIChudW3DqXJpY2FzKSBqdW50byBjb24gZWwgcmFuZ28geSB2YXJpYW56YQ0KYGBgDQpFc3RhZMOtc3RpY2EgZGUgcmVzdW1lbiBkZSBsYXMgdmFyaWFibGVzIG51bcOpcmljYXMsIGNvbW8gbGEgbWVkaWEsIG1lZGlhbmEsIGN1YXJ0aWxlcywgcmFuZ28sIGp1bnRvIGNvbW8gdW4gcGVxdWXDsW8gaGlzdG9ncmFtYS4NCmBgYHtyfQ0KaGVhcnQgfD4NCiAgc2VsZWN0KGFoZCwgc2V4LCBjaGVzdF9wYWluLCBmYnMsIHJlc3RfZWNnLCBleF9hbmcsIHNsb3BlKSB8Pg0KICBwaXZvdF9sb25nZXIoDQogICAgY29scyA9IGV2ZXJ5dGhpbmcoKSwNCiAgICBuYW1lc190byA9ICJ2YXJpYWJsZSIsDQogICAgdmFsdWVzX3RvID0gImNhdGVnb3JpYSINCiAgKSB8Pg0KICBjb3VudCh2YXJpYWJsZSwgY2F0ZWdvcmlhLCBuYW1lID0gImZyZWN1ZW5jaWEiKSB8Pg0KICBncm91cF9ieSh2YXJpYWJsZSkgfD4NCiAgbXV0YXRlKA0KICAgIHBvcmNlbnRhamUgPSAxMDAgKiBmcmVjdWVuY2lhIC8gc3VtKGZyZWN1ZW5jaWEpDQogICkgfD4NCiAgdW5ncm91cCgpICAgI1RhYmxhIGRlIGZyZWN1ZW5jaWEgcGFyYSBsYXMgdmFyaWFibGVzIGNhdGVnw7NyaWNhcy4NCmBgYA0KVGFibGEgZGUgZnJlY3VlbmNpYXMgZGUgbGFzIHZhcmlhYmxlcyBjYXRlZ8OzcmljYXMuDQpgYGB7cn0NCmdncGxvdChoZWFydCwgYWVzKHggPSBjYSwgeSA9IGFoZCkpICsNCiAgZ2VvbV9ib3hwbG90KCkgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIkRpc3RyaWJ1Y2nDs24gZGVsIG7Dum1lcm8gZGUgdmFzb3MgcG9yIGRpYWduw7NzdGljbyBkZSBlbmZlcm1lZGFkIGNhcmRpYWNhIiwNCiAgICB4ID0gIk7Dum1lcm8gZGUgdmFzb3MiLA0KICAgIHkgPSAiRGlhZ27Ds3N0aWNvIg0KICApICsNCiAgdGhlbWVfbWluaW1hbCgpDQpgYGANCkdyw6FmaWNhIGRlIGJpZ290ZXMgZGUgY2EgYWdydXBhZG8gcG9yIGFoZCwgZW4gZG9uZGUgc2UgbXVlc3RyYW4gbG9zIHZhbG9yZXMgYXTDrXBpY29zLiBZIHNlIG11ZXN0cmEgcXVlIGxhIG1heW9yw61hIGRlIGxvcyBwYWNpZW50ZXMgc2luIGVuZmVybWVkYWQgbm8gcHJlc2VudGFuIHZhc29zIHByaW5jaXBhbGVzLg0KYGBge3J9DQpnZ3Bsb3QoaGVhcnQsIGFlcyh4ID0gYWdlLCB5ID0gcmVzdF9icCkpICsgDQogICAgICAgICAgIGdlb21fcG9pbnQoKSArIA0KICAgICAgICAgICBsYWJzKCANCiAgICAgICAgICAgICAgICB0aXRsZSA9ICJSZWxhY2nDs24gZW50cmUgZWRhZCB5IGxhIHByZXNpw7NuIGFydGVyaWFsIiwgDQogICAgICAgICAgICAgICAgeCA9ICJFZGFkIiwgDQogICAgICAgICAgICAgICAgIHkgPSAiUHJlc2nDs24gYXJ0ZXJpYWwiDQogICAgICAgICAgICAgKSArIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsbSIsIHNlID0gRkFMU0UpDQpgYGANCkdyw6FmaWNhIGRlIGRpc3BlcnNpw7NuIGRlIHJlc3RfYnAgY29uIHJlc3BlY3RvIGEgbGEgZWRhZC4NCmBgYHtyfQ0KZ2dwbG90KGhlYXJ0LCBhZXMoeCA9IGNoZXN0X3BhaW4sIGZpbGwgPSBhaGQpKSArDQogZ2VvbV9iYXIocG9zaXRpb24gPSAiZmlsbCIpICsNCiBmYWNldF93cmFwKH4gc2V4KSArDQogY29vcmRfZmxpcCgpICsNCiBsYWJzKA0KICB0aXRsZSA9ICJBSEQgc2Vnw7puIHRpcG8gZGUgZG9sb3IgdG9yw6FjaWNvIHkgc2V4byIsDQogIHggPSAiVGlwbyBkZSBkb2xvciB0b3LDoWNpY28iLA0KICB5ID0gIlByb3BvcmNpw7NuIiwNCiAgZmlsbCA9ICJFbmZlcm1lZGFkIGNhcmRpYWNhIg0KICkNCmBgYA0KR3LDoWZpY2EgZGUgYmFycmFzIGRlIGxhIHJlbGFjacOzbiBlbnRyZSBzZXgsIGNoZXN0X3BhaW4geSBhaGQsIGRvbmRlIHNlIG11ZXN0cmFuIHF1ZSBsYSBtYXlvcsOtYSBkZSBsb3MgY2Fzb3MgYXNpbnRvbcOhdGljb3Mgbm8gcHJlc2VudGFuIGVuZmVybWVkYWQuDQpgYGB7cn0NCmhlYXJ0X251bSA8LSBoZWFydCB8PiANCiAgICAgICAgICAgICBzZWxlY3QoYWdlLCByZXN0X2JwLCBjaG9sLCBtYXhfaHIsIG9sZHBlYWssIGNhKSB8PiANCiAgICAgICAgICAgICBkcm9wX25hKCkgDQpjb3JyZWxhY2lvbmVzIDwtIGNvcihoZWFydF9udW0pIA0KY29ycmVsYWNpb25lcw0KYGBgDQpgYGB7cn0NCmhlYXJ0X251bSA8LSBoZWFydCB8PiANCiAgICAgICAgICAgICBzZWxlY3QoYWdlLCByZXN0X2JwLCBjaG9sLCBtYXhfaHIsIG9sZHBlYWssIGNhKSB8PiANCiAgICAgICAgICAgICBkcm9wX25hKCkgDQpjb3JyZWxhY2lvbmVzIDwtIGNvcihoZWFydF9udW0pIA0KY29ycnBsb3QoY29ycmVsYWNpb25lcywgbWV0aG9kPSJjb2xvciIpDQpgYGANCkNvcnJlbGFjacOzbiBlbnRyZSBsYXMgdmFyaWFibGVzIG51bcOpcmljYXMsIGRvbmRlIHNlIG9ic2VydmEgcXVlIGxhIG1heW9yw61hIGRlIGxhcyBjb3JyZWxhY2lvbmVzIHNvbiBkZSBtZWRpYXMsIGTDqWJpbGVzIGEgbXV5IGTDqWJpbGVzLg0KYGBge3J9DQpoZWFydCB8PiANCiAgICAgIHNlbGVjdChhZ2UsIHJlc3RfYnAsIGNob2wsIG1heF9ociwgb2xkcGVhaywgY2EpIHw+IA0KICAgICAgcGl2b3RfbG9uZ2VyKCANCiAgICAgICAgICBjb2xzID0gZXZlcnl0aGluZygpLCANCiAgICAgICAgICBuYW1lc190byA9ICJ2YXJpYWJsZSIsIA0KICAgICAgICAgIHZhbHVlc190byA9ICJ2YWxvciIgDQopIHw+IA0KZ2dwbG90KGFlcyh4ID0gdmFyaWFibGUsIHkgPSB2YWxvcikpICsgDQpnZW9tX2JveHBsb3QoKSArIA0KY29vcmRfZmxpcCgpICsgDQpsYWJzKCANCiAgdGl0bGUgPSAiUmV2aXNpw7NuIGRlIHBvc2libGVzIHZhbG9yZXMgYXTDrXBpY29zIiwgDQogIHggPSAiVmFyaWFibGUiLCANCiAgeSA9ICJWYWxvciINCikNCmBgYA0KR3LDoWZpY2EgZGUgYmlnb3RlcywgZG9uZGUgc2UgbXVlc3RyYW4gbG9zIHZhbG9yZXMgYXTDrXBpY29zIGRlIGxhcyB2YXJpYWJsZXMuDQoNCkRlbCBhbsOhbGlzaXMgc2Ugb2J0dXZvIHF1ZSBsYSBtYXlvcsOtYSBkZSBsYXMgdmFyaWFibGVzIG51bcOpcmljYXMgdHV2aWVyb24gdW5hIHJlbGFjacOzbiBjb24gYWhkLCBzYWx2byBlbCBjYXNvIGRlbCBjaG9sLCBlbiBlbCBjYXNvIGRlIGxhcyB2YXJpYWJsZXMgY2F0ZWfDs3JpY2FzIGZicyBlcyBsYSB2YXJpYWJsZSBxdWUgbWVub3MgcmVsYWNpw7NuIGNvbiBhaGQuIENvbiBsb3MgdmFsb3JlcyBhdMOtcGljb3MgZW4gdG9kYXMgbGFzIHZhcmlhYmxlcyBzYWx2byBhZ2UuIERlIGVzdGUgYW7DoWxpc2lzIG9idGVuZW1vcyBxdWU7IHNleCwgY2hlc3RfcGFpbiwgZXhfYW5nLCBvbGRwZWFrLCBzbG9wZSB5IGNhIHNvbiBsYXMgdmFyaWFibGVzIHF1ZSBtYXlvciByZWxhY2nDs24gdGllbmVuIGNvbiBhaGQuDQoNCmBgYHtyfQ0KdC50ZXN0KGhlYXJ0JGNhKQ0KYGBgDQpgYGB7cn0NCnQudGVzdChjYSB+IGFoZCwgZGF0YSA9IGhlYXJ0KQ0KYGBgDQpgYGB7cn0NCnRhYmxhX3NleF9haGQgPC0gdGFibGUoaGVhcnQkc2V4LCBoZWFydCRhaGQpIA0KdGFibGFfc2V4X2FoZCANCmNoaXNxLnRlc3QodGFibGFfc2V4X2FoZCkgDQpwcm9wLnRhYmxlKHRhYmxhX3NleF9haGQsIG1hcmdpbiA9IDEpDQpgYGANCmBgYHtyfQ0KdGFibGFfY2hlc3RfYWhkIDwtIHRhYmxlKGhlYXJ0JGNoZXN0X3BhaW4sIGhlYXJ0JGFoZCkgDQp0YWJsYV9jaGVzdF9haGQgDQpjaGlzcS50ZXN0KHRhYmxhX2NoZXN0X2FoZCkgDQpwcm9wLnRhYmxlKHRhYmxhX2NoZXN0X2FoZCwgbWFyZ2luID0gMSkNCmBgYA0KRGUgYW7DoWxpc2lzIGluZmVyZW5jaWFsIHNlIHRvbWFyb24gbGFzIHZhcmlhYmxlcyBzZXgsIGNoZXN0X3BhaW4sIGV4X2FuZywgb2xkcGVhayB5IGNhIHBhcmEgbGEgcmVhbGl6YWNpw7NuIGRlbCBtb2RlbG8uDQoNCiMjIyBNb2RlbGFkbyBlc3RhZMOtc3RpY28gYsOhc2ljbw0KDQpgYGB7cn0NCmhlYXJ0IDwtIGhlYXJ0IHw+IA0KICBtdXRhdGUoDQogICAgYWhkX2JpbiA9IGlmX2Vsc2UoYWhkID09ICJjb25fZW5mZXJtZWRhZCIsIDEsIDApDQogICkNCmBgYA0KYGBge3J9DQptb2RlbG9fbG9nXzEgPC0gZ2xtKA0KICBhaGRfYmluIH4gICAgY2EgKyBjaGVzdF9wYWluICsgb2xkcGVhayArIHNsb3BlICsgcmVzdF9icCArIHRoYWwgKyBzZXggKyBleF9hbmcgKyBtYXhfaHIsDQogIGRhdGEgPSBoZWFydCwNCiAgZmFtaWx5ID0gYmlub21pYWwNCikNCg0Kc3VtbWFyeShtb2RlbG9fbG9nXzEpDQpgYGANCmBgYHtyfQ0KZ2xhbmNlKG1vZGVsb19sb2dfMSkNCmBgYA0KYGBge3J9DQpoZWFydCA8LSBoZWFydCB8PiANCiAgbXV0YXRlKA0KICAgIHByb2JfYWhkID0gcHJlZGljdChtb2RlbG9fbG9nXzEsIHR5cGUgPSAicmVzcG9uc2UiKQ0KICApDQpgYGANCmBgYHtyfQ0KZ2dwbG90KGhlYXJ0LCBhZXMoeCA9IHByb2JfYWhkLCBmaWxsID0gYWhkKSkgKw0KICBnZW9tX2hpc3RvZ3JhbShiaW5zID0gMjAsIGFscGhhID0gMC43LCBwb3NpdGlvbiA9ICJpZGVudGl0eSIpICsNCiAgbGFicygNCiAgICB0aXRsZSA9ICJEaXN0cmlidWNpw7NuIGRlIHByb2JhYmlsaWRhZGVzIGVzdGltYWRhcyBkZSBBSEQiLA0KICAgIHggPSAiUHJvYmFiaWxpZGFkIGVzdGltYWRhIGRlIGVuZmVybWVkYWQgY2FyZGlhY2EiLA0KICAgIHkgPSAiRnJlY3VlbmNpYSIsDQogICAgZmlsbCA9ICJBSEQiDQogICkgKw0KICB0aGVtZV9taW5pbWFsKCkNCmBgYA0KYGBge3J9DQpoZWFydCA8LSBoZWFydCB8PiANCiAgbXV0YXRlKA0KICAgIHByZWRfYWhkID0gaWZfZWxzZSgNCiAgICAgIHByb2JfYWhkID49IDAuNSwNCiAgICAgICJjb25fZW5mZXJtZWRhZCIsDQogICAgICAic2luX2VuZmVybWVkYWQiDQogICAgKSwNCiAgICBwcmVkX2FoZCA9IGZhY3RvcigNCiAgICAgIHByZWRfYWhkLA0KICAgICAgbGV2ZWxzID0gYygiY29uX2VuZmVybWVkYWQiLCAic2luX2VuZmVybWVkYWQiKQ0KICAgICkNCiAgKQ0KYGBgDQpgYGB7cn0NCm1lYW4oaGVhcnQkYWhkID09IGhlYXJ0JHByZWRfYWhkKQ0KYGBgDQpTZSByZWFsaXpvIHVuIG1vZGVsbyBkZSAgcmVncmVzacOzbiBsb2fDrXN0aWNhIHNvYnJlIGxhIHZhcmlhYmxlIG9iamV0aXZvIGFoZCBqdW50byBjb24gbGFzIHZhcmlhYmxlcyBwcmVkaWN0b3Jhczsgc2V4LCBjaGVzdF9wYWluLCBleF9hbmcsIG9sZHBlYWssIHNsb3BlLCByZXN0X2JwLCBtYXhfaHIsIGNhIHkgdGhhbC4gRWwgY3VhbCB0dXZvIHVuIEFDSSBkZSAyMjMuOTgsIGN1eW8gdmFsb3IgbWllbnRyYXMgbcOhcyBiYWpvLCBkZXRlcm1pbmEgdW4gbWVqb3IgbW9kZWxhZG8gZGUgbGEgdmFyaWFibGUuIEVsIG1vZGVsbyB0aWVuZSBzdSBtYXlvciBsaW1pdGFjacOzbiBlbiBxdWUgc2UgcmVhbGl6byBzb2xhbWVudGUgY29uIHVuYSBtdWVzdHJhIGRlIGxhIHBvYmxhY2nDs24uIFkgdW5hIGNvbXBsZWppZGFkIG3DoXMgYWx0YSBwb3IgZWwgdXNvIGRlIG3Dumx0aXBsZXMgdmFyaWFibGVzLg0KDQojIyMgUmVzdWx0YWRvcyBwcmluY2lwYWxlcw0KDQpEZWwgRURBIChhbsOhbGlzaXMgZXhwbG9yYXRvcmlvKSBwb2RlbW9zIG9idGVuZXIgcXVlIGxhcyB2YXJpYWJsZXMgcXVlIG1heW9yIHJlbGFjacOzbiB0aWVuZSBjb24gbGEgdmFyaWFibGUgYWhkIHNvbjsgY2EsIGNoZXN0X3BhaW4geSBzZXgsIHNpZW5kbyBjYSBsYSDDum5pY2EgdmFyaWFibGUgbnVtw6lyaWNhIHF1ZSB0aWVuZSB1bmEgYWx0YSByZWxldmFuY2lhLCB0YW1iacOpbiBzZSBvYnR1dm8gcXVlIGxhcyB2YXJpYWJsZXMgY2hvbCB5IGZicyBubyB0aWVuZW4gY2FzaSByZWxhY2nDs24gY29uIGxhIHZhcmlhYmxlLiBFc3RvcyBtaXNtb3MgcmVzdWx0YWRvcyBsb3MgcG9kZW1vcyB2b2x2ZXIgYSBvYnNlcnZhIGVuIGVsIGFuw6FsaXNpcyBpbmZlcmVuY2lhbCwgZG9uZGUgc2UgbXVlc3RyYSBxdWUgdGllbmUgdW5hIG1heW9yIHJlbGFjacOzbiBiYWpvcyBsYXMgcHJ1ZWJhcyBkZSBoaXDDs3Rlc2lzIHkgbG9zIHJhbmdvcyBkZSBjb25maWFuemEuIEJham8gZXN0b3Mgc3VwdWVzdG9zIHNlIHNlbGVjY2lvbmFyb24gbGFzIHZhcmlhYmxlczogY2hlc3RfcGFpbiwgb2xkcGVhaywgc2xvcGUsIHJlc3RfYnAgeSBjYSBwYXJhIHJlYWxpemFjacOzbiBlbCBtb2RlbG8sIGFkZW1hcyBzZSBhZ3JlZ2Fyb24gbGFzIHZhcmlhYmxlcyB0aGFsLCBzZXgsIGV4X2FuZyB5IG1heF9ociBwYXJhIG1lam9yYXIgdW4gcG9jbyBlbCBtb2RlbGFkby4gWSBsb2dyYXIgdW4gDQpBQ0kgZGUgMjIzLjk4LCBxdWUgbm9zIHBlcm1pdGnDsyBvYnRlbmVyIHVuYSBjbGFzaWZpY2FjacOzbiBjb3JyZWN0YSBkZSBsb3MgcGFjaWVudGVzIGNvbiB5IHNpbiBlbmZlcm1lZGFkIGRlIGNhc2kgZWwgODclLg0KDQojIyMgUHJvYmxlbWFzIGVuY29udHJhZG9zIGR1cmFudGUgZWwgZGVzYXJyb2xsbw0KDQpTZSB0dXZvIHF1ZSByZWFsaXphciB1bmEgbGltcGllemEgZGUgbG9zIGRhdG9zLCBkZWJpZG8gYSBxdWUgZWwgZGF0YXNldCBubyB0ZW5pYSBlbCBmb3JtYXRvIGFkZWN1YWRvIHBhcmEgZWwgbm9tYnJlIGRlIGxhcyB2YXJpYWJsZXMsIGFzw60gY29tbyBkYXRvcyBmYWx0YW50ZXMgeSBsb3MgdGlwb3MgZGUgdmFyaWFibGVzIG5vIGFkZWN1YWRhcy4gU2UgbmVjZXNpdG8gYWdyZWdhciBsYSBiaWJsaW90ZWNhICJicm9vbSIgZGViaWRvIGEgZXJyb3JlcyBjb24gbGEgZnVuY2nDs24gdGlkeS4gTGEgdmFyaWFibGUgY2hvbCBwcmVzZW50byB1bmEgY29tcGxpY2FjacOzbiBlbiBsYSBpbnRlcnByZXRhY2nDs24gZGVsIEVEQSwgcXVlIGZ1ZSBjb3JyZWN0YW1lbnRlIGRlc2NhcnRhZGEgZW4gZWwgYW7DoWxpc2lzIGluZmVyZW5jaWFsLiBFbCBhanVzdGUgZGVsIG1vZGVsbyBmdWUgcmVhbGl6YWRvIGRlIG1hbmVyYSBjb3JyZWN0YS4NCg0KIyMgQ29uY2x1c2nDs24NCg0KRGVsIGRhdGFzZXQgYXByZW5kw60gbGFzIGNvbnNpZGVyYWNpb25lcyBxdWUgc2UgZGViZW4gdG9tYXIgcG9kZXIgdXRpbGl6YXIgbG9zIGRhdG9zLCBlbnRyZSBsYSBob21vbG9nYWNpw7NuIGRlIGxvcyBkYXRvcyB5IGxvcyBub21icmVzIGRlIGxhcyB2YXJpYWJsZXMgYXPDrSBjb21vIGVsIHRyYXRhbWllbnRvIGRlIGRhdG9zIGZhbHRhbnRlcy4gQ29uIGxvIHF1ZSBzZSBsb2dybyBpZGVudGlmaWNhciBxdWUgc2V4LCBjaGVzdF9wYWluLCBjYSB5IG9sZHBlYWsgc29uIGxhcyB2YXJpYWJsZXMgbcOhcyByZWxldmFudGVzLCBpbmZvcm1hY2nDs24gcXVlIHNlIGxvZ3JvIGFwcmVjaWFyIGdyYWNpYXMgYWwgYW7DoWxpc2lzIGV4cGxvcmF0b3JpbyBlIGluZmVyZW5jaWFsLCBkZSBtYW5lcmEgZ3LDoWZpY2EsIGN1YW50aXRhdGl2YSB5IGJham8gbGFzIHBydWViYXMgZGUgaGlww7N0ZXNpcy4gRHVyYW50ZSBlbCBhbsOhbGlzaXMgZXhwbG9yYXRvcmlvIHNlIGlkZW50aWZpY28gcXVlIGNob2wgeSBmYnMgbm8gdGVuw61hbiByZWxhY2nDs24gY29uIGVsIG1vZGVsby4gRHVyYW50ZSBsYSByZWFsaXphY2nDs24gZGVsIG1vZGVsbyB0b2RvIGVsIHRyYWJham8gYW50ZXJpb3IgdHV2byB1biBtYXlvciBzZW50aWRvIHkgc2UgbG9ncm8gb2J0ZW5lciB1biBtb2RlbG8gYsOhc2ljbyBxdWUgc2lydmUgY29tbyBwdW50byBkZSBwYXJ0aWRhIHBhcmEgZW5jb250cmFyIHVuYSBmb3JtYSBkZSBkZXRlY3RhciB1bmEgZW5mZXJtZWRhZCwgcmVjb3JkYW5kbyBxdWUgdG9kbyBlbCBhbsOhbGlzaXMgc29sbyBzZSBoaXpvIHNvYnJlIHVuYSBtdWVzdHJhIGRlIGxhIHBvYmxhY2nDs24sIGFzw60gY29tbyBzdSBjbGFzaWZpY2FjacOzbiwgcG9yIGxvIHF1ZSBzZXJpYSBuZWNlc2FyaW8gdmVyIHNpIGNvbiBvdHJhcyBtdWVzdHJhcyBsb3MgbWFyZ2VuZXMgZGUgZGV0ZWNjacOzbiBzaWd1ZW4gc2llbmRvIGxvcyBtaXNtb3MuDQoNCiMjIFJlZmVyZW5jaWFzDQoNCi0gR3JvbGVtdW5kLCBHLiAoMjAxNCkuIEhhbmRzLW9uIHByb2dyYW1taW5nIHdpdGggUi4gT+KAmVJlaWxseSBNZWRpYS4gaHR0cHM6Ly9yc3R1ZGlvLWVkdWNhdGlvbi5naXRodWIuaW8vaG9wci8NCi0gSHVtYmVydG8gR3V0acOpcnJleiBQLiwgUm9tw6FuIGRlIGxhIFZhcmEgUy4gKDIwMDgpIEFuw6FsaXNpcyB5IGRpc2XDsW8gZGUgZXhwZXJpbWVudG9zICgybmQgZWQuKS4gTWNHUkFXLUhJTEwvSU5URVJBTUVSSUNBTkENCg0KDQo=