install.packages('tidyverse')
Installing package into ‘/cloud/lib/x86_64-pc-linux-gnu-library/4.3’
(as ‘lib’ is unspecified)
trying URL 'http://rspm/default/__linux__/focal/latest/src/contrib/tidyverse_2.0.0.tar.gz'
Content type 'application/x-gzip' length 425237 bytes (415 KB)
==================================================
downloaded 415 KB

* installing *binary* package ‘tidyverse’ ...
* DONE (tidyverse)

The downloaded source packages are in
    ‘/tmp/Rtmp3bw7Hk/downloaded_packages’
install.packages("rstatix")
Installing package into ‘/cloud/lib/x86_64-pc-linux-gnu-library/4.3’
(as ‘lib’ is unspecified)
trying URL 'http://rspm/default/__linux__/focal/latest/src/contrib/rstatix_0.7.2.tar.gz'
Content type 'application/x-gzip' length 604777 bytes (590 KB)
==================================================
downloaded 590 KB

* installing *binary* package ‘rstatix’ ...
* DONE (rstatix)

The downloaded source packages are in
    ‘/tmp/Rtmp3bw7Hk/downloaded_packages’
library(tidyverse)
── Attaching core tidyverse packages ────────────────────────────────────── tidyverse 2.0.0 ──
✔ dplyr     1.1.4     ✔ readr     2.1.4
✔ forcats   1.0.0     ✔ stringr   1.5.1
✔ ggplot2   3.4.4     ✔ tibble    3.2.1
✔ lubridate 1.9.3     ✔ tidyr     1.3.0
✔ purrr     1.0.2     ── Conflicts ──────────────────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag()    masks stats::lag()
ℹ Use the ]8;;http://conflicted.r-lib.org/conflicted package]8;; to force all conflicts to become errors
library(rstatix)

Attaching package: ‘rstatix’

The following object is masked from ‘package:stats’:

    filter

Introduccion a R

1. Vectores

Primero agregamos valores de edades

edad = c(23,26,27,28,32,34,28,29,31,25)

Luego agregamos nombres

nombres = c("Maria", "Gustavo", "Ramon", "Silvia", "Alma", "Eduardo", "Enrique", "Erick", "Hugo", "Jesus")

Para revisarlos

nombres
 [1] "Maria"   "Gustavo" "Ramon"   "Silvia"  "Alma"    "Eduardo" "Enrique" "Erick"   "Hugo"   
[10] "Jesus"  

Generación de vectores aleatorios. Genera 10 datos aleatorios en un rango entre 8,000 a 30,000.

salario = sample(8000:30000,size = 10, replace = FALSE)
salario
 [1] 17998  9212 11821 22614 29972 14395 22702 14514 14754 21666

Nota: Si replace = FALSE, entonces no hay reemplazo.

Ahora agregamos 10 datos aleatorios referentes a sexo

sexo = sample(c("H", "M"), size = 10, replace = TRUE)
sexo
 [1] "M" "H" "M" "H" "H" "H" "H" "M" "H" "M"

Tenemos diferentes variables - Cualitativas - Nominales - Ordinales - Cuantitativas - Discretas - Continuas

2. Bases de datos (data frames)

Tenemos bases de datos - Externas - Internas

Este sería un ejemplo de bases de datos internas de R

ejemplo_1 <- datasets::state.x77
head(ejemplo_1)
           Population Income Illiteracy Life Exp Murder HS Grad Frost   Area
Alabama          3615   3624        2.1    69.05   15.1    41.3    20  50708
Alaska            365   6315        1.5    69.31   11.3    66.7   152 566432
Arizona          2212   4530        1.8    70.55    7.8    58.1    15 113417
Arkansas         2110   3378        1.9    70.66   10.1    39.9    65  51945
California      21198   5114        1.1    71.71   10.3    62.6    20 156361
Colorado         2541   4884        0.7    72.06    6.8    63.9   166 103766

La forma de leer una base de datos externa, archivo separado por comas .csv, es con la función read.csv(directorio.csv)

datos1 <- read.csv("/cloud/project/Bioestadistica_R/datos_1.csv")
head(datos1) #Con la función head() se observan los primeros 6 valores del data frame
str(datos1)
'data.frame':   200 obs. of  11 variables:
 $ Clave             : chr  "A001" "A002" "A003" "A004" ...
 $ Sexo              : chr  "M" "H" "M" "H" ...
 $ Edad              : int  21 35 38 18 32 21 39 23 37 35 ...
 $ Estatus           : chr  "Control" "Caso" "Control" "Control" ...
 $ Exposicion        : chr  "Sin exposicion" "Humo de biomasa" "Fumador" "Fumador" ...
 $ Enfermedad        : chr  "Sin alteraciones" "Capacidad pulmonar alterada" "Sin alteraciones" "Sin alteraciones" ...
 $ Cotinina          : num  1 8 183.2 225.5 2.3 ...
 $ Anos.de.exposicion: int  0 5 20 4 0 3 15 5 20 32 ...
 $ Talla             : num  1.65 1.56 1.66 1.77 1.5 1.78 1.8 1.55 1.68 1.7 ...
 $ Peso              : int  88 55 68 104 56 115 98 74 99 65 ...
 $ Padres.fumadores  : chr  "No" "No" "Si" "No" ...

Para cargar la segunda base de datos

datos2 <- read_csv("/cloud/project/Bioestadistica_R/datos_2.csv")     #Aquí usamos la función read_csv del paquete tidyverse
Rows: 200 Columns: 6── Column specification ──────────────────────────────────────────────────────────────────────
Delimiter: ","
chr (1): Clave
dbl (5): Cigarros al dia, Cartuchos al dia, TNFA, CRP, PvCO2
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
head(datos2)

Creamos una base de datos a partir de los vectores realizados previamente

datos3 <- data.frame(nombres, edad, sexo, salario) 
as_tibble(datos3)

Para cambiar el nombre de una variable utilizamos rename()

rename(datos3, nombre = nombres)   # primero ponemos el (dataframe, nombre nuevo = nombre viejo)

Recordar que ejercicio2 = datos1 y ejercicio3=datos2

Revisar algunas funciones de tidyverse Función select

ejercicio5 <- datos1 %>% 
  select(Clave, Edad, Cotinina, Anos.de.exposicion, Talla, Peso)
ejercicio5

Función filter

ejercicio6 <- datos1 %>% 
  filter(Edad>=25)
ejercicio6

Función filter con variable cualitativa

ejercicio6 <- datos1 %>% 
  filter(Exposicion=="Fumador"|Exposicion=="Vapeador"|Exposicion=="Sin exposicion")
ejercicio6

Ver la función mutate para crear variables nuevas a partir de variables existentes

ejercicio2 <- datos1 %>% 
  mutate(IMC = Peso / Talla^2)
ejercicio2

Podemos cambiar el tipo de variable de cuantitativa a cualitativa con la función mutate

ejercicio2 <- ejercicio2 %>% 
  mutate(IMC_escala = case_when(IMC<18 ~ "Desnutricion",
                                IMC<25 ~ "Normopeso",
                                IMC<30 ~ "Sobrepeso",
                                IMC>=30 ~ "Obesidad"))
ejercicio2

Ahora veremos las rutas de análisis

str(ejercicio2)    
'data.frame':   200 obs. of  13 variables:
 $ Clave             : chr  "A001" "A002" "A003" "A004" ...
 $ Sexo              : chr  "M" "H" "M" "H" ...
 $ Edad              : int  21 35 38 18 32 21 39 23 37 35 ...
 $ Estatus           : chr  "Control" "Caso" "Control" "Control" ...
 $ Exposicion        : chr  "Sin exposicion" "Humo de biomasa" "Fumador" "Fumador" ...
 $ Enfermedad        : chr  "Sin alteraciones" "Capacidad pulmonar alterada" "Sin alteraciones" "Sin alteraciones" ...
 $ Cotinina          : num  1 8 183.2 225.5 2.3 ...
 $ Anos.de.exposicion: int  0 5 20 4 0 3 15 5 20 32 ...
 $ Talla             : num  1.65 1.56 1.66 1.77 1.5 1.78 1.8 1.55 1.68 1.7 ...
 $ Peso              : int  88 55 68 104 56 115 98 74 99 65 ...
 $ Padres.fumadores  : chr  "No" "No" "Si" "No" ...
 $ IMC               : num  32.3 22.6 24.7 33.2 24.9 ...
 $ IMC_escala        : chr  "Obesidad" "Normopeso" "Normopeso" "Obesidad" ...

La datos que salen es el análisis de estructura

También podemos cambiar el tipo de variable. Por ejemplo, la variable Sexo está categorizada como chr (character), pero la cambiamos a factor. Conviene cambiarlas para realizar adecuadamente el análisis estadístico, ya que las variables tipo chr no son útiles para ese análisis.

ejercicio2$Sexo <- as.factor(ejercicio2$Sexo)
ejercicio2$Estatus <- as.factor(ejercicio2$Estatus)
ejercicio2$Exposicion <- as.factor(ejercicio2$Exposicion)
ejercicio2$Enfermedad <- as.factor(ejercicio2$Enfermedad)
ejercicio2$Padres.fumadores <- as.factor(ejercicio2$Padres.fumadores)
ejercicio2$IMC_escala <- as.factor(ejercicio2$IMC_escala)
str(ejercicio2) 
'data.frame':   200 obs. of  13 variables:
 $ Clave             : chr  "A001" "A002" "A003" "A004" ...
 $ Sexo              : Factor w/ 2 levels "H","M": 2 1 2 1 2 1 1 2 1 2 ...
 $ Edad              : int  21 35 38 18 32 21 39 23 37 35 ...
 $ Estatus           : Factor w/ 2 levels "Caso","Control": 2 1 2 2 1 2 2 1 1 2 ...
 $ Exposicion        : Factor w/ 4 levels "Fumador","Humo de biomasa",..: 3 2 1 1 3 1 1 4 1 2 ...
 $ Enfermedad        : Factor w/ 5 levels "Asma","Bronquitis",..: 5 3 5 5 2 5 5 2 2 5 ...
 $ Cotinina          : num  1 8 183.2 225.5 2.3 ...
 $ Anos.de.exposicion: int  0 5 20 4 0 3 15 5 20 32 ...
 $ Talla             : num  1.65 1.56 1.66 1.77 1.5 1.78 1.8 1.55 1.68 1.7 ...
 $ Peso              : int  88 55 68 104 56 115 98 74 99 65 ...
 $ Padres.fumadores  : Factor w/ 2 levels "No","Si": 1 1 2 1 2 2 2 1 1 2 ...
 $ IMC               : num  32.3 22.6 24.7 33.2 24.9 ...
 $ IMC_escala        : Factor w/ 4 levels "Desnutricion",..: 3 2 2 3 2 3 3 3 3 2 ...

Procedemos a hacer un análisis estadístico

resultados <- ejercicio2 %>% 
  get_summary_stats()    #Solo es con el paquete rstatix
resultados

Para hacer un análisis estratificado se requiere una variable cualitativa como factor

resultados <- ejercicio2 %>% 
  group_by(Enfermedad) %>% 
  get_summary_stats()    #Solo es con el paquete rstatix
resultados

Para realizar la tarea abrimos el archivo de datos de framingham, luego seleccionamos las variables de las cuales queremos obtener las medidas de tendencia central y las medidas de dispersión utilizando el paquete rstatix

datos_framingham <- read_csv("/cloud/project/Bioestadistica_R/framingham.csv")
Rows: 4238 Columns: 16── Column specification ──────────────────────────────────────────────────────────────────────
Delimiter: ","
dbl (16): male, age, education, currentSmoker, cigsPerDay, BPMeds, prevalentStroke, preval...
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
View(datos_framingham)

str(datos_framingham)
spc_tbl_ [4,238 × 16] (S3: spec_tbl_df/tbl_df/tbl/data.frame)
 $ male           : num [1:4238] 1 0 1 0 0 0 0 0 1 1 ...
 $ age            : num [1:4238] 39 46 48 61 46 43 63 45 52 43 ...
 $ education      : num [1:4238] 4 2 1 3 3 2 1 2 1 1 ...
 $ currentSmoker  : num [1:4238] 0 0 1 1 1 0 0 1 0 1 ...
 $ cigsPerDay     : num [1:4238] 0 0 20 30 23 0 0 20 0 30 ...
 $ BPMeds         : num [1:4238] 0 0 0 0 0 0 0 0 0 0 ...
 $ prevalentStroke: num [1:4238] 0 0 0 0 0 0 0 0 0 0 ...
 $ prevalentHyp   : num [1:4238] 0 0 0 1 0 1 0 0 1 1 ...
 $ diabetes       : num [1:4238] 0 0 0 0 0 0 0 0 0 0 ...
 $ totChol        : num [1:4238] 195 250 245 225 285 228 205 313 260 225 ...
 $ sysBP          : num [1:4238] 106 121 128 150 130 ...
 $ diaBP          : num [1:4238] 70 81 80 95 84 110 71 71 89 107 ...
 $ BMI            : num [1:4238] 27 28.7 25.3 28.6 23.1 ...
 $ heartRate      : num [1:4238] 80 95 75 65 85 77 60 79 76 93 ...
 $ glucose        : num [1:4238] 77 76 70 103 85 99 85 78 79 88 ...
 $ TenYearCHD     : num [1:4238] 0 0 0 1 0 0 1 0 0 0 ...
 - attr(*, "spec")=
  .. cols(
  ..   male = col_double(),
  ..   age = col_double(),
  ..   education = col_double(),
  ..   currentSmoker = col_double(),
  ..   cigsPerDay = col_double(),
  ..   BPMeds = col_double(),
  ..   prevalentStroke = col_double(),
  ..   prevalentHyp = col_double(),
  ..   diabetes = col_double(),
  ..   totChol = col_double(),
  ..   sysBP = col_double(),
  ..   diaBP = col_double(),
  ..   BMI = col_double(),
  ..   heartRate = col_double(),
  ..   glucose = col_double(),
  ..   TenYearCHD = col_double()
  .. )
 - attr(*, "problems")=<externalptr> 
datos_framingham <- rename(datos_framingham, sexos = male)
datos_framingham

resultados_framingham <- datos_framingham %>% 
  select(age, sexos, cigsPerDay, totChol, sysBP, diaBP, BMI, heartRate, glucose) %>% 
  filter(!complete.cases(.)) %>%
  group_by(sexos) %>% 
  get_summary_stats()

View(resultados_framingham)

Ahora obtenemos el rango de manera manual calculando la diferencia entre el máximo y el mínimo.

rango_age <- max(datos_framingham$age, na.rm = TRUE) - min(datos_framingham$age, na.rm = TRUE)
rango_cigs <- max(datos_framingham$cigsPerDay, na.rm = TRUE) - min(datos_framingham$cigsPerDay, na.rm = TRUE)
rango_chol <- max(datos_framingham$totChol, na.rm = TRUE) - min(datos_framingham$totChol, na.rm = TRUE)
rango_sbp <- max(datos_framingham$sysBP, na.rm = TRUE) - min(datos_framingham$sysBP, na.rm = TRUE)
rango_dbp <- max(datos_framingham$diaBP, na.rm = TRUE) - min(datos_framingham$diaBP, na.rm = TRUE)
rango_BMI <- max(datos_framingham$BMI, na.rm = TRUE) - min(datos_framingham$BMI, na.rm = TRUE)
rango_hr <- max(datos_framingham$heartRate, na.rm = TRUE) - min(datos_framingham$heartRate, na.rm = TRUE)
rango_gluc <- max(datos_framingham$glucose, na.rm = TRUE) - min(datos_framingham$glucose, na.rm = TRUE)

rango_age
[1] 38
rango_cigs
[1] 70
rango_chol
[1] 589
rango_sbp
[1] 211.5

3. Anális descriptivo y normalidad (Sesión 3), con enfoque en el análisis de normalidad

Tener precaución para utilizar read.csvo read_csvporque el manejo de las tablas es diferente

library(tidyverse)

tarea_1 <- read.csv("/cloud/project/Bioestadistica_R/framingham.csv")
str(tarea_1)
'data.frame':   4238 obs. of  16 variables:
 $ male           : int  1 0 1 0 0 0 0 0 1 1 ...
 $ age            : int  39 46 48 61 46 43 63 45 52 43 ...
 $ education      : int  4 2 1 3 3 2 1 2 1 1 ...
 $ currentSmoker  : int  0 0 1 1 1 0 0 1 0 1 ...
 $ cigsPerDay     : int  0 0 20 30 23 0 0 20 0 30 ...
 $ BPMeds         : int  0 0 0 0 0 0 0 0 0 0 ...
 $ prevalentStroke: int  0 0 0 0 0 0 0 0 0 0 ...
 $ prevalentHyp   : int  0 0 0 1 0 1 0 0 1 1 ...
 $ diabetes       : int  0 0 0 0 0 0 0 0 0 0 ...
 $ totChol        : int  195 250 245 225 285 228 205 313 260 225 ...
 $ sysBP          : num  106 121 128 150 130 ...
 $ diaBP          : num  70 81 80 95 84 110 71 71 89 107 ...
 $ BMI            : num  27 28.7 25.3 28.6 23.1 ...
 $ heartRate      : int  80 95 75 65 85 77 60 79 76 93 ...
 $ glucose        : int  77 76 70 103 85 99 85 78 79 88 ...
 $ TenYearCHD     : int  0 0 0 1 0 0 1 0 0 0 ...

Cambiamos el tipo de variable malecomo factor

tarea_1$male <- as.factor(tarea_1$male)
str(tarea_1)
'data.frame':   4238 obs. of  16 variables:
 $ male           : Factor w/ 2 levels "0","1": 2 1 2 1 1 1 1 1 2 2 ...
 $ age            : int  39 46 48 61 46 43 63 45 52 43 ...
 $ education      : int  4 2 1 3 3 2 1 2 1 1 ...
 $ currentSmoker  : int  0 0 1 1 1 0 0 1 0 1 ...
 $ cigsPerDay     : int  0 0 20 30 23 0 0 20 0 30 ...
 $ BPMeds         : int  0 0 0 0 0 0 0 0 0 0 ...
 $ prevalentStroke: int  0 0 0 0 0 0 0 0 0 0 ...
 $ prevalentHyp   : int  0 0 0 1 0 1 0 0 1 1 ...
 $ diabetes       : int  0 0 0 0 0 0 0 0 0 0 ...
 $ totChol        : int  195 250 245 225 285 228 205 313 260 225 ...
 $ sysBP          : num  106 121 128 150 130 ...
 $ diaBP          : num  70 81 80 95 84 110 71 71 89 107 ...
 $ BMI            : num  27 28.7 25.3 28.6 23.1 ...
 $ heartRate      : int  80 95 75 65 85 77 60 79 76 93 ...
 $ glucose        : int  77 76 70 103 85 99 85 78 79 88 ...
 $ TenYearCHD     : int  0 0 0 1 0 0 1 0 0 0 ...
tarea_1 <- tarea_1 %>% 
  mutate(male = case_when(male == "0" ~ "M",
                          male == "1" ~ "H"))

str(tarea_1)
'data.frame':   4238 obs. of  16 variables:
 $ male           : chr  "H" "M" "H" "M" ...
 $ age            : int  39 46 48 61 46 43 63 45 52 43 ...
 $ education      : int  4 2 1 3 3 2 1 2 1 1 ...
 $ currentSmoker  : int  0 0 1 1 1 0 0 1 0 1 ...
 $ cigsPerDay     : int  0 0 20 30 23 0 0 20 0 30 ...
 $ BPMeds         : int  0 0 0 0 0 0 0 0 0 0 ...
 $ prevalentStroke: int  0 0 0 0 0 0 0 0 0 0 ...
 $ prevalentHyp   : int  0 0 0 1 0 1 0 0 1 1 ...
 $ diabetes       : int  0 0 0 0 0 0 0 0 0 0 ...
 $ totChol        : int  195 250 245 225 285 228 205 313 260 225 ...
 $ sysBP          : num  106 121 128 150 130 ...
 $ diaBP          : num  70 81 80 95 84 110 71 71 89 107 ...
 $ BMI            : num  27 28.7 25.3 28.6 23.1 ...
 $ heartRate      : int  80 95 75 65 85 77 60 79 76 93 ...
 $ glucose        : int  77 76 70 103 85 99 85 78 79 88 ...
 $ TenYearCHD     : int  0 0 0 1 0 0 1 0 0 0 ...
View(tarea_1)

Realizamos el análisis descriptivo

resultados_tarea_1 <- tarea_1 %>% 
  group_by(male) %>% 
  get_summary_stats(age, cigsPerDay, totChol, sysBP, diaBP, BMI, heartRate, glucose)

resultados_tarea_1 <- resultados_tarea_1 %>% 
  mutate(Rango_1 = max - min)
  
View(resultados_tarea_1)

Para crear un archivo .csv

write.csv(resultados_tarea_1, file = "resultados_tarea1")

Temas de estadística

Definir población y muestra. La muestra es el conjunto de elementos representativos de la población y se debe seleccionar aleatoriamente.

Escalas de medición: 4 niveles de escala

  • Nominal: Se utilzan nombres, símbolos o numerales, que son empleados como etiquetas. Sirve para separar por grupos. No se pueden aplicar operaciones matemáticas.

  • Ordinal: Los números o etiquetas utilizadas indican un orden. Sirve para organizar.

  • Intervalos: Establece un orden determinado. El 0 significa ausencia de valor. Se puede utilizar para operaciones matemáticas.

  • Proporción/cociente/razón: Son secuencias numéricas. Se pueden realizar operaciones matemáticas. Es la escala que permite comparar de mejor manera los datos.

Distribución de datos

  • Distribución normal
  • Distribución de Poisson
  • Distribución binomial

Asimetría de datos

  • Negativa
  • Simétrica
  • Positiva

Curtosis

  • Leptocúrtica
  • Mesocúrtica
  • Platicúrtica

4. La normalidad…

Puede ser evaluada mediante 3 modelos

Representación de los datos a partir de las medidas de tendencia central

Utilizamos el valor de la edad

ejercicio_1 <- tarea_1 %>% 
  get_summary_stats(age)

conteo <- tarea_1 %>% 
  count(age)

#mean: 49.58
#median: 49
#moda: 40

Solo con estos parámetros se podría decir que la muestra no cumple con la normalidad. Veamos su historgrama con la función hist(), del cual podemos modificar su título, las etiquetas y el color.

hist(tarea_1$age,
     main = "Histograma de edad",
     ylab = "Frecuencia",
     xlab = "Edad (años)",
     col = "salmon",
     breaks = 50)

Podemos realizar las gráficas con el paquete ggplot que permite crear gráficos más estéticos

library(ggplot)
Error in library(ggplot) : there is no package called ‘ggplot’

Para graficar el histograma

Construyamos los gráficos de densidad

Revisar la asimetría y la curtosis

library(car)
Loading required package: carData

Attaching package: ‘car’

The following object is masked from ‘package:psych’:

    logit

La curtosis ideal debería ser cercano a 0 para decir que es mesocúrtica. Es decir,

  • k = 0 -> mesocúrtica
  • k > 0 -> leptocúrtica
  • k < 0 -> platicúrtica

Para calcular la asimetría, decimos que

  • a = 0 (simétrico)
  • a > 0 (asimetría positiva)
  • a < 0 (asimetría negativa)

Para calcular la curtosis utilizamos el paquete psych

kurtosi(tarea_1$age)
[1] -0.991

Cuando el valor de curtosis es < 2 o > 2, entonces no tiene un comportamiento normal

Calculamos la curtosis y el sesgo para hombres y para mujeres

kurtosi(tarea_1$age[tarea_1$male == "H"])
[1] -0.963
kurtosi(tarea_1$age[tarea_1$male == "M"])
[1] -1.01
#Para calcular el sesgo (simetría o asimetría)
skew(tarea_1$age)
[1] 0.228
skew(tarea_1$age[tarea_1$male == "H"])
[1] 0.272
skew(tarea_1$age[tarea_1$male == "M"])
[1] 0.195

Veamos los QQ-plot

qqPlot(tarea_1$age,
       main = "QQ plot edad",
       ylab = "Edad (años)",
       xlab = "Cuantiles")
[1] 1625 3138

Combinar análisis de asimetría con histograma, y curtosis con QQ-plot. Los QQplot permiten observar el peso de las colas. Si es una distribución normal, entonces se debería de comportar como una línea recta.

Podemos realizar el QQplot estratificado por hombres y mujeres.

qqPlot(tarea_1$age[tarea_1$male == "H"],
       main = "QQ plot edad",
       ylab = "Edad (años)",
       xlab = "Cuantiles",
       col = "blue",
       col.lines = "red")

Gráficos de cajas y bigotes son importantes para el análisis

Utilizando ggplot

Pruebas de normalidad

  • Shapiro-Wilks: Se utiliza cuando n < 50 (incluso menor a 40). Cuando hay menos de 10 datos ya no se recomienda usar.
  • Kolmogorov-Smirnov: Se utiliza cuando n > 50

Para realizar la de Kolmogorov-Smirnov…Con los requisitos mínimo Tiene como \(H_0\): La distribución real y la distribución teórica son iguales o similares (normalidad) \(H_a\): La distribución real y la distribución teórica son diferentes (no normalidad)

Cuando p > 0.05 no se rechaza la \(H_0\)

OJO: ya no se usa así, se usa una mejorada

ks.test(tarea_1$age, "pnorm",
        mean = 49.6, sd = 8.57,
        alternative = "two.sided")
Warning: ties should not be present for the Kolmogorov-Smirnov test

    Asymptotic one-sample Kolmogorov-Smirnov test

data:  tarea_1$age
D = 0.08, p-value <2e-16
alternative hypothesis: two-sided

Como el el valor de p obtenido es < 0.05 (p-value < 2e-16), se rechaza la hipótesis nula y concluimos que la muestra no proviene de una distribución normal.

La prueba de Kolmogorov-Smirnov-Lilliefors es la corregida

# Se utiliza la librería de nortest
lillie.test(tarea_1$age[tarea_1$male == "H"])

    Lilliefors (Kolmogorov-Smirnov) normality test

data:  tarea_1$age[tarea_1$male == "H"]
D = 0.09, p-value <2e-16

Para hacer la de Shapiro-Wilk

shapiro.test(tarea_1$age[tarea_1$male == "H"])

    Shapiro-Wilk normality test

data:  tarea_1$age[tarea_1$male == "H"]
W = 1, p-value <2e-16
shapiro.test(tarea_1$age[tarea_1$male == "M"])

    Shapiro-Wilk normality test

data:  tarea_1$age[tarea_1$male == "M"]
W = 1, p-value <2e-16

4. Pruebas de contraste estadístico

Pruebas paramétricas (t de student y ANOVA)

Requieren cumplir varios supuestos

  • Normalidad
  • Igualdad de varianzas (Prueba de Levene): Requiere el planteamiento de las siguientes hipótesis
    • \(H_0\): la varianza de la variable es igual o semejante en casos y controles (\(p>0.05\))
    • \(H_a\): las varianzas son diferentes entre grupos (\(p<0.05\))

Las hipotesis planteadas en las pruebas de comparación de medias son:

  • \(H_0: \mu_1 = \mu_2\)
  • \(H_a: \mu_1 \neq \mu_2\)

Pruebas no paramétricas

Empecemos con las pruebas paramétricas

Cargamos nuestra base de datos

sesion_4 <- read.csv("/cloud/project/Bioestadistica_R/datos_1.csv")
View(sesion_4)

Pasos a seguir para realizar la prueba:

  1. Realizar el análisis de normalidad
  2. Análisis de igualdad varianza (Prueba de Levene)
  3. Prueba de t de student
  4. Cálculo de tamaño de efecto
  5. Conclusión

Comparemos la talla entre casos y controles. ¿Existe diferencia en esta variable entre ambos grupos?

  1. Análisis de normalidad
summary(sesion_4$Estatus) #Debe de estar en tipo factor
   Caso Control 
    100     100 

Dado que tenemos > 50 observaciones para cada grupo, utilizamos la prueba de Lilliefors. El análisis debe de ser realizado para cada grupo por separado, recordando que

\(H_0\): La distribución real y teórica son iguales ($p > 0.05) \(H_a\): La distribución real y teórica son diferentes ($p < 0.05)

lillie.test(sesion_4$Talla[sesion_4$Estatus == "Control"])

    Lilliefors (Kolmogorov-Smirnov) normality test

data:  sesion_4$Talla[sesion_4$Estatus == "Control"]
D = 0.1, p-value = 0.02
lillie.test(sesion_4$Talla[sesion_4$Estatus == "Caso"])

    Lilliefors (Kolmogorov-Smirnov) normality test

data:  sesion_4$Talla[sesion_4$Estatus == "Caso"]
D = 0.1, p-value = 0.006

Dado que \(p-value < 0.05\), se recomienda análisis no paramétrico para comparar grupos.

  1. Ahora verificamos el análisis de igualdad de varianza (Prueba de Levene) \(H_0\): Las varianzas entre grupos son iguales o similares ($p > 0.05) \(H_a\): Las varianzas entre grupos son diferentes ($p < 0.05)
leveneTest(sesion_4$Talla ~ sesion_4$Estatus,
            center = "median")
Levene's Test for Homogeneity of Variance (center = "median")
       Df F value Pr(>F)
group   1       0   0.96
      198               
leveneTest(sesion_4$Talla ~ sesion_4$Estatus,
            center = "mean")
Levene's Test for Homogeneity of Variance (center = "mean")
       Df F value Pr(>F)
group   1       0   0.99
      198               

El p-value (Pr(>F)) > 0.05, entonces se acepta la \(H_0\). Las varianzas entre grupos son iguales o similares.

  1. Ahora realizamos la prueba de T

Las hipótesis son

\(H_0: \mu_1 = \mu_2\) \(H_a: \mu_1 \neq \mu_2\)

#Si var.eq = TRUE, entonces hace la t de student normal
t.test(sesion_4$Talla ~ sesion_4$Estatus,
       alternative = "two.sided",
       conf.level = 0.95,
       var.equal = TRUE,
       paired = FALSE)

    Two Sample t-test

data:  sesion_4$Talla by sesion_4$Estatus
t = -0.6, df = 198, p-value = 0.5
alternative hypothesis: true difference in means between group Caso and group Control is not equal to 0
95 percent confidence interval:
 -0.0324  0.0164
sample estimates:
   mean in group Caso mean in group Control 
                 1.67                  1.67 
#Si var.eq = FALSE, entonces hace la corrección de Welch porque las varianzas entre grupos son diferentes
#Esta prueba es para varianzas NO iguales
t.test(sesion_4$Talla ~ sesion_4$Estatus,
       alternative = "two.sided",
       conf.level = 0.95,
       var.equal = FALSE,
       paired = FALSE)

    Welch Two Sample t-test

data:  sesion_4$Talla by sesion_4$Estatus
t = -0.6, df = 198, p-value = 0.5
alternative hypothesis: true difference in means between group Caso and group Control is not equal to 0
95 percent confidence interval:
 -0.0324  0.0164
sample estimates:
   mean in group Caso mean in group Control 
                 1.67                  1.67 
#Pacticamente esta es igual a la anterior
t.test(sesion_4$Talla ~ sesion_4$Estatus)

    Welch Two Sample t-test

data:  sesion_4$Talla by sesion_4$Estatus
t = -0.6, df = 198, p-value = 0.5
alternative hypothesis: true difference in means between group Caso and group Control is not equal to 0
95 percent confidence interval:
 -0.0324  0.0164
sample estimates:
   mean in group Caso mean in group Control 
                 1.67                  1.67 

El intervalo de confianza (IC) son -0.0324 y 0.0164

Dado que el p-valor (0.5) > 0.05, entonces no se rechaza la \(H_0\) y las medias entre ambos grupos son iguales. El cálculo del tamaño del efecto NO se aplica cuando p > 0.05, pero vamos a verificarlo

cohen.d(sesion_4$Talla, sesion_4$Estatus)
Call: cohen.d(x = sesion_4$Talla, group = sesion_4$Estatus)
Cohen d statistic of difference between two means
     lower effect upper
[1,] -0.19   0.09  0.37

Multivariate (Mahalanobis) distance between groups
[1] 0.092
r equivalent of difference between two means
data 
0.05 

Otra opción de anális usando rstatix

library(rstatix)
library(tidyverse)
var_igualdad <- sesion_4 %>% 
  levene_test(Talla ~ Estatus, center = "median")

efecto <- sesion_4 %>% 
  cohens_d(Talla ~ Estatus)

5. ANOVA (Sesión 5)

Cargamos la base de datos

Preparación de la base de datos

str(pulmonar)
'data.frame':   130 obs. of  10 variables:
 $ Clave      : chr  "P001" "P002" "P003" "P004" ...
 $ Estatus    : Factor w/ 2 levels "Case","Control": 2 2 1 2 1 2 2 2 1 1 ...
 $ Sexo       : Factor w/ 2 levels "H","M": 1 2 1 2 2 2 2 1 2 1 ...
 $ Dosis      : Factor w/ 3 levels "Clásico","Nuevo",..: 3 1 1 2 3 3 2 3 1 3 ...
 $ Edad       : int  62 55 79 69 70 63 58 50 47 88 ...
 $ Peso       : num  65.9 68.5 72.1 92.9 84.9 ...
 $ Talla      : num  2.28 2.1 1.52 2.1 1.86 1.55 1.73 2.71 1.79 1.93 ...
 $ Fun_pulm   : num  86.9 89.3 63.6 86.3 107.8 ...
 $ PvCO2      : num  13.5 20.2 28.8 19.8 42.1 ...
 $ Neutrofilos: num  8.66 7.35 23.05 7.44 21.79 ...
head(pulmonar)

Empecemos con el proceso de análisis.

  1. Primero contemos el número de datos, n.

Dado que para cada uno de los grupos su n es < 50, utilizamos la prueba de Shapiro-Wilks

p <- pulmonar %>% 
  ggplot(pulmonar)+
  geom_bar(aes(x = Dosis, y = PvCO2))
Error in `ggplot()`:
! `mapping` should be created with `aes()`.
✖ You've supplied a <data.frame> object
Backtrace:
 1. pulmonar %>% ggplot(pulmonar)
 3. ggplot2:::ggplot.default(., pulmonar)

Las hipótesis son

Dado que el análisis de normalidad indicó que el valor p de la variable PvCO2 para cada uno de los grupos fue menor que 0.05, entonces se rechaza la hipótesis nula y aceptamos la \(H_a\), que es que no provienen de una distribución normal. Ahorita omitimos que sean no normales. Para realizar la ANOVA, todos los grupos deben cumplir el supuesto de normalidad

EL siguiente paso es comprobar la igualdad de varianzas

  1. Igualdad de varianzas. Para este caso se usa la paquetería car y carData
leveneTest(pulmonar$PvCO2 ~ pulmonar$Dosis, 
           center = "median")
Levene's Test for Homogeneity of Variance (center = "median")
       Df F value Pr(>F)
group   2    0.41   0.67
      127               

Las hipótesis son - \(H_0\): Las varianzas de PvCO2 \(=\) en todos los grupos (p-value > 0.05) - \(H_a\): Las varianzas de PvCO2 \(\neq\) en al menos un grupo (no normalidad, p-value < 0.05)

Usando el paquete rstatix

pulmonar %>% 
  levene_test(PvCO2 ~ Dosis, center = "median")

Dado que el p-value es > 0.05 (p = 0.667), entonces la conclusión del analisis de igualdad de varianzas es que las varianzas son similares o iguales. Procedemos a realizas la prueba de ANOVA

  1. ANOVA Las hipótesis son

La \(H_a\) dice que hay diferencias en la media de PvCO2 entre alguno de los grupos

anova
ANOVA Table (type II tests)

  Effect DFn DFd   F    p p<.05   ges
1  Dosis   2 127 1.4 0.25       0.022

Dado que p = 0.25, que es > 0.05, entonces aceptamos la \(H_0\) y diríamos que no hay diferencia entre grupos. Haremos las pruebas post-hoc

Finalmente, realizamos la conclusión

  1. Los tratamientos no provocan una diferencia en la PvCO2 (p-value = 0.25)

ANOVA de 2 vías

El análisis consiste

1. Normalidad

pulmonar %>% 
  group_by(Dosis) %>% 
  shapiro_test(Neutrofilos)


summary(pulmonar$Estatus)
   Case Control 
     40      90 
#Para los casos n=40, usamos la de Shapiro-Wilks
shapiro_test(pulmonar$Neutrofilos[pulmonar$Estatus == "Case"])


#Para controles n=90 usamos la de lillieforce
library(nortest)
lillie.test(pulmonar$Neutrofilos[pulmonar$Estatus == "Control"])

    Lilliefors (Kolmogorov-Smirnov) normality test

data:  pulmonar$Neutrofilos[pulmonar$Estatus == "Control"]
D = 0.06, p-value = 0.6

Para el % de neutrofilos estratificado por estatus cumplen la normalidad. SIn embargo, al estratificar por el tratamiento no cumple.

normalidad_condicionada <- pulmonar %>% 
  group_by(Dosis, Estatus) %>% 
  summary() %>% 
  shapiro_test(Neutrofilos)
Error in UseMethod("select") : 
  no applicable method for 'select' applied to an object of class "table"

Cuando se condiciona el análisis de normalidad de dos factores, el % de neutrófilos cumple con el supuesto de normalidad

2. Igualdad de varianzas

La varianza de % de neutrófilos es diferente entre los grupos (Estatus). La varianza de neutrófilos es igual entre grupos (Dosis)

3. ANOVA

result_2vias <- pulmonar %>% 
  anova_test(Neutrofilos ~ Dosis*Estatus, 
             effect.size = "ges") #Eta cuadrada generalizada

Con el análisis post-hoc

ppst_2vias <-pulmonar %>% 
  tukey_hsd(Neutrofilos ~ Dosis*Estatus)

4. Conclusión Analizando los subgrupos dosis:casos se encontró que al comparar los múltiples tratamientos entre casos y controles, no hay diferencias significativas. Al realizar un análisis individual de cada factor, se detectó una gran diferencia en el factor Estatus (p < 0.01, ges = 0.66), por lo que se sospecha que las diferencias que puedan encontrarse son atribuibles al factor Estatus.

ANOVA de 2 vías de medidas repetidas

Cargamos la base de datos y convertimos las variables caracter a factor.

str(seguimiento)
'data.frame':   84 obs. of  10 variables:
 $ Clave              : chr  "C-001" "C-002" "C-004" "C-005" ...
 $ Tiempo             : Factor w/ 3 levels "Antes","Despues",..: 1 1 1 1 1 1 1 1 1 1 ...
 $ Sexo               : Factor w/ 2 levels "H","M": 2 1 2 1 2 2 1 2 1 1 ...
 $ Fumadores          : Factor w/ 2 levels "No","Si": 2 2 2 2 2 2 2 2 2 2 ...
 $ Edad.de.inicio     : int  24 21 23 19 18 15 14 21 18 23 ...
 $ Cigarros.al.día    : int  43 11 45 39 38 30 44 27 49 28 ...
 $ Padres.fumadores   : Factor w/ 2 levels "No","Si": 2 2 1 2 2 1 2 2 2 2 ...
 $ Problemas.cardiacos: Factor w/ 2 levels "No","Si": 2 1 2 1 1 2 2 1 2 2 ...
 $ CHRNA5             : Factor w/ 2 levels "No","Si": 2 2 1 2 1 2 2 1 2 1 ...
 $ rs578776           : Factor w/ 3 levels "CC","CT","TT": 2 2 3 3 1 2 2 1 3 1 ...

Hay que asegurarnos que están todos los datos completos de la base de datos. Es decir, que la n sea la misma para todos los tiempos .

Analicemos la normalidad

Los valores de Mantenimiento no provienen de una distribución normal. En este caso, no podríamos continuar con la ANOVA y tendríamos que hacer otra prueba no paramétrica. Sin embargo, vamos a continuar con la ANOVA.

Ahora realicemos la prueba de igualdad de varianzas. Prueba de Levene

Dado el valor de p > 0.05 (p-value = 0.143), podemos decir que las varianzas son iguales/similares en todos los grupos.

Planteemos la prueba de ANOVA

seguimiento %>% 
  anova_test(dv = Cigarros.al.día, 
             wid = Clave,
             within = Tiempo)
ANOVA Table (type III tests)

$ANOVA
  Effect DFn DFd    F        p p<.05   ges
1 Tiempo   2  54 40.2 2.06e-11     * 0.362

$`Mauchly's Test for Sphericity`
  Effect     W     p p<.05
1 Tiempo 0.901 0.256      

$`Sphericity Corrections`
  Effect  GGe      DF[GG]   p[GG] p[GG]<.05   HFe      DF[HF]    p[HF] p[HF]<.05
1 Tiempo 0.91 1.82, 49.12 1.4e-10         * 0.972 1.94, 52.47 3.75e-11         *

Ya que el valor de p de ANOVA es < 0.05 (p-value = 2.06e-11), podemos decir que hay diferencias entre grupos.

Para el test de esfericidad

  • \(H_0\): los datos no varian en cada nivel de tiempo (p >0.05)
  • \(H_a\): los datos varían en cada nivel de tiempo (p < 0.05)

Si no se cumple el supuesto de esfericidad por el test de Mauchly, habrá que ver los datos de la corrección de esfericidad. Habrá que considerar el valor de p de p[GG] del test de corrección de esfericidad como el que se tiene que reportar de la prueba de ANOVA.

Hasta aquí concluimos parcialmente que existe diferencia en el número de cigarros por día después de cada etapa de tratamiento.

##6. Pruebas no paramétricas

  1. Son pruebas para comparar datos con distribuciones no normales
  2. No son sensibles a datos extremos atípicos, por lo que su presencia no afectará el análisis en conjunto
  3. Fundament: ordena los datos de menor a mayor, cada dato se le asigna posición y un valor numérico (rango de 1 a n)
  • Comparaciones cuantitativas: prueba de Wilcoxon, U de Mann-Whitney, Kruskal-Wallis, Friedman
  • Comparaciones cualitativas: chi cuadrada, Fisher
str(sesion_6)
'data.frame':   200 obs. of  11 variables:
 $ Clave             : chr  "A001" "A002" "A003" "A004" ...
 $ Sexo              : chr  "M" "H" "M" "H" ...
 $ Edad              : int  21 35 38 18 32 21 39 23 37 35 ...
 $ Estatus           : chr  "Control" "Caso" "Control" "Control" ...
 $ Exposicion        : Factor w/ 4 levels "Fumador","Humo de biomasa",..: 3 2 1 1 3 1 1 4 1 2 ...
 $ Enfermedad        : chr  "Sin alteraciones" "Capacidad pulmonar alterada" "Sin alteraciones" "Sin alteraciones" ...
 $ Cotinina          : num  1 8 183.2 225.5 2.3 ...
 $ Anos.de.exposicion: int  0 5 20 4 0 3 15 5 20 32 ...
 $ Talla             : num  1.65 1.56 1.66 1.77 1.5 1.78 1.8 1.55 1.68 1.7 ...
 $ Peso              : int  88 55 68 104 56 115 98 74 99 65 ...
 $ Padres.fumadores  : chr  "No" "No" "Si" "No" ...

Hay que probar la asimetría (skew)

skew(sesion_6$Cotinina[sesion_6$Exposicion== "Sin exposicion"])
[1] 0.908
skew(sesion_6$Cotinina[sesion_6$Exposicion== "Fumador"])
[1] -0.115
skew(sesion_6$Cotinina[sesion_6$Exposicion== "Vapeador"])
[1] 1.99
skew(sesion_6$Cotinina[sesion_6$Exposicion== "Humo de biomasa"])
[1] 0.734

Asimetría < -2 o > 2 ya era una simetría muy marcada. Se espera tener una asimetría = 0 (simétricos), o cuando \(a \neq 0\) (asimétricos )

Realizamos el análisis de igualdad de varianzas

Realizamos la prueba de U de Mann-Whitney para datos no relacionados

Existe diferencia entre el grupo sin exposición vs fumadores (se report el p.adj.signif), y sin exposicion vs vapeadores.

La prueba post-hoc de Kruskal-Wallis es la siguiente

Para responder a la pregunta de qué tan grande es la diferencia, entonces calculamos el tamaño del efecto

Los niveles de cotinina varían entre el grupo sin exposición vs fumadores (p = 3.19e-16) y sin exposición vs vapeadores. Las diferencias encontradas son grandes según la prueba del tamaño del efecto.

Revisemos como se comparan entre todos los grupos en los niveles de Cotinina. Realizamos la prueba de Kruskal-Wallis

No sabemos entre que grupos hay diferencia, pero si existe porque el valor de p arrojado por la prueba de Kruskal-Wallis (p = 2.27e-32). Para saber entre que grupos hacemos la prueba post-hoc de dunn

O hacemos una prueba post-hoc

Para ver el tamaño del efecto

Pruebas cualitativas

Realicemos una tabla de contingencia (principalmente para variables cualitativas)

estatus_sexo
    Estatus
Sexo Caso Control
   H   58      58
   M   42      42

Una tabla de nx2 (Primero filas, luego columnas)

table(sesion_6$Sexo, sesion_6$Exposicion,
      dnn = c("Sexo", "Exposicion"))
    Exposicion
Sexo Fumador Humo de biomasa Sin exposicion Vapeador
   H      55              17             18       26
   M      35              20             14       15

Para realizar la prueba

chisq.test(estatus_exposicion)

    Pearson's Chi-squared test

data:  estatus_exposicion
X-squared = 3, df = 3, p-value = 0.4

cuando p > 0.05 la freucuencia de datos es igual, cuando p < 0.05 la frecuenca de datos es diferente

LS0tCnRpdGxlOiAiQmlvZXN0YWTDrXN0aWNhIGFwbGljYWRhIGEgbGEgaW52ZXN0aWdhY2nDs24iCmF1dGhvcjogIlJvbW1lbCBTw6FuY2hleiIKZGF0ZTogIjIwMjMtMTEtMjUiCm91dHB1dDoKICBodG1sX25vdGVib29rOiBkZWZhdWx0CiAgcGRmX2RvY3VtZW50OiBkZWZhdWx0CiAgaHRtbF9kb2N1bWVudDoKICAgIGRmX3ByaW50OiBwYWdlZAplZGl0b3Jfb3B0aW9uczogCiAgbWFya2Rvd246IAogICAgd3JhcDogNzIKLS0tCgpgYGB7cn0KaW5zdGFsbC5wYWNrYWdlcygndGlkeXZlcnNlJykKaW5zdGFsbC5wYWNrYWdlcygicnN0YXRpeCIpCmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KHJzdGF0aXgpCmBgYAoKIyMgSW50cm9kdWNjaW9uIGEgUgoKIyMgMS4gVmVjdG9yZXMKClByaW1lcm8gYWdyZWdhbW9zIHZhbG9yZXMgZGUgZWRhZGVzCgpgYGB7cn0KZWRhZCA9IGMoMjMsMjYsMjcsMjgsMzIsMzQsMjgsMjksMzEsMjUpCmBgYAoKTHVlZ28gYWdyZWdhbW9zIG5vbWJyZXMKCmBgYHtyfQpub21icmVzID0gYygiTWFyaWEiLCAiR3VzdGF2byIsICJSYW1vbiIsICJTaWx2aWEiLCAiQWxtYSIsICJFZHVhcmRvIiwgIkVucmlxdWUiLCAiRXJpY2siLCAiSHVnbyIsICJKZXN1cyIpCmBgYAoKUGFyYSByZXZpc2FybG9zCgpgYGB7cn0Kbm9tYnJlcwpgYGAKCkdlbmVyYWNpw7NuIGRlIHZlY3RvcmVzIGFsZWF0b3Jpb3MuIEdlbmVyYSAxMCBkYXRvcyBhbGVhdG9yaW9zIGVuIHVuCnJhbmdvIGVudHJlIDgsMDAwIGEgMzAsMDAwLgoKYGBge3J9CnNhbGFyaW8gPSBzYW1wbGUoODAwMDozMDAwMCxzaXplID0gMTAsIHJlcGxhY2UgPSBGQUxTRSkKc2FsYXJpbwpgYGAKCk5vdGE6IFNpIGByZXBsYWNlID0gRkFMU0VgLCBlbnRvbmNlcyBubyBoYXkgcmVlbXBsYXpvLgoKQWhvcmEgYWdyZWdhbW9zIDEwIGRhdG9zIGFsZWF0b3Jpb3MgcmVmZXJlbnRlcyBhIHNleG8KCmBgYHtyfQpzZXhvID0gc2FtcGxlKGMoIkgiLCAiTSIpLCBzaXplID0gMTAsIHJlcGxhY2UgPSBUUlVFKQpzZXhvCmBgYAoKVGVuZW1vcyBkaWZlcmVudGVzIHZhcmlhYmxlcyAtIEN1YWxpdGF0aXZhcyAtIE5vbWluYWxlcyAtIE9yZGluYWxlcyAtCkN1YW50aXRhdGl2YXMgLSBEaXNjcmV0YXMgLSBDb250aW51YXMKCiMjIDIuIEJhc2VzIGRlIGRhdG9zIChkYXRhIGZyYW1lcykKClRlbmVtb3MgYmFzZXMgZGUgZGF0b3MgLSBFeHRlcm5hcyAtIEludGVybmFzCgpFc3RlIHNlcsOtYSB1biBlamVtcGxvIGRlIGJhc2VzIGRlIGRhdG9zIGludGVybmFzIGRlIFIKCmBgYHtyfQplamVtcGxvXzEgPC0gZGF0YXNldHM6OnN0YXRlLng3NwpoZWFkKGVqZW1wbG9fMSkKYGBgCgpMYSBmb3JtYSBkZSBsZWVyIHVuYSBiYXNlIGRlIGRhdG9zIGV4dGVybmEsIGFyY2hpdm8gc2VwYXJhZG8gcG9yIGNvbWFzCmAuY3N2YCwgZXMgY29uIGxhIGZ1bmNpw7NuIGByZWFkLmNzdihkaXJlY3RvcmlvLmNzdilgCgpgYGB7cn0KZGF0b3MxIDwtIHJlYWQuY3N2KCIvY2xvdWQvcHJvamVjdC9CaW9lc3RhZGlzdGljYV9SL2RhdG9zXzEuY3N2IikKaGVhZChkYXRvczEpICNDb24gbGEgZnVuY2nDs24gaGVhZCgpIHNlIG9ic2VydmFuIGxvcyBwcmltZXJvcyA2IHZhbG9yZXMgZGVsIGRhdGEgZnJhbWUKc3RyKGRhdG9zMSkKYGBgCgpQYXJhIGNhcmdhciBsYSBzZWd1bmRhIGJhc2UgZGUgZGF0b3MKCmBgYHtyfQpkYXRvczIgPC0gcmVhZF9jc3YoIi9jbG91ZC9wcm9qZWN0L0Jpb2VzdGFkaXN0aWNhX1IvZGF0b3NfMi5jc3YiKSAgICAgI0FxdcOtIHVzYW1vcyBsYSBmdW5jacOzbiByZWFkX2NzdiBkZWwgcGFxdWV0ZSB0aWR5dmVyc2UKaGVhZChkYXRvczIpCmBgYAoKQ3JlYW1vcyB1bmEgYmFzZSBkZSBkYXRvcyBhIHBhcnRpciBkZSBsb3MgdmVjdG9yZXMgcmVhbGl6YWRvcwpwcmV2aWFtZW50ZQoKYGBge3J9CmRhdG9zMyA8LSBkYXRhLmZyYW1lKG5vbWJyZXMsIGVkYWQsIHNleG8sIHNhbGFyaW8pIAphc190aWJibGUoZGF0b3MzKQpgYGAKClBhcmEgY2FtYmlhciBlbCBub21icmUgZGUgdW5hIHZhcmlhYmxlIHV0aWxpemFtb3MgYHJlbmFtZSgpYAoKYGBge3J9CnJlbmFtZShkYXRvczMsIG5vbWJyZSA9IG5vbWJyZXMpICAgIyBwcmltZXJvIHBvbmVtb3MgZWwgKGRhdGFmcmFtZSwgbm9tYnJlIG51ZXZvID0gbm9tYnJlIHZpZWpvKQpgYGAKClJlY29yZGFyIHF1ZSBlamVyY2ljaW8yID0gZGF0b3MxIHkgZWplcmNpY2lvMz1kYXRvczIKClJldmlzYXIgYWxndW5hcyBmdW5jaW9uZXMgZGUgYHRpZHl2ZXJzZWAgRnVuY2nDs24gYHNlbGVjdGAKCmBgYHtyfQplamVyY2ljaW81IDwtIGRhdG9zMSAlPiUgCiAgc2VsZWN0KENsYXZlLCBFZGFkLCBDb3RpbmluYSwgQW5vcy5kZS5leHBvc2ljaW9uLCBUYWxsYSwgUGVzbykKZWplcmNpY2lvNQpgYGAKCkZ1bmNpw7NuIGBmaWx0ZXJgCgpgYGB7cn0KZWplcmNpY2lvNiA8LSBkYXRvczEgJT4lIAogIGZpbHRlcihFZGFkPj0yNSkKZWplcmNpY2lvNgpgYGAKCkZ1bmNpw7NuIGBmaWx0ZXJgIGNvbiB2YXJpYWJsZSBjdWFsaXRhdGl2YQoKYGBge3J9CmVqZXJjaWNpbzYgPC0gZGF0b3MxICU+JSAKICBmaWx0ZXIoRXhwb3NpY2lvbj09IkZ1bWFkb3IifEV4cG9zaWNpb249PSJWYXBlYWRvciJ8RXhwb3NpY2lvbj09IlNpbiBleHBvc2ljaW9uIikKZWplcmNpY2lvNgpgYGAKClZlciBsYSBmdW5jacOzbiBgbXV0YXRlYCBwYXJhIGNyZWFyIHZhcmlhYmxlcyBudWV2YXMgYSBwYXJ0aXIgZGUKdmFyaWFibGVzIGV4aXN0ZW50ZXMKCmBgYHtyfQplamVyY2ljaW8yIDwtIGRhdG9zMSAlPiUgCiAgbXV0YXRlKElNQyA9IFBlc28gLyBUYWxsYV4yKQplamVyY2ljaW8yCmBgYAoKUG9kZW1vcyBjYW1iaWFyIGVsIHRpcG8gZGUgdmFyaWFibGUgZGUgY3VhbnRpdGF0aXZhIGEgY3VhbGl0YXRpdmEgY29uIGxhCmZ1bmNpw7NuIGBtdXRhdGVgCgpgYGB7cn0KZWplcmNpY2lvMiA8LSBlamVyY2ljaW8yICU+JSAKICBtdXRhdGUoSU1DX2VzY2FsYSA9IGNhc2Vfd2hlbihJTUM8MTggfiAiRGVzbnV0cmljaW9uIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBJTUM8MjUgfiAiTm9ybW9wZXNvIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBJTUM8MzAgfiAiU29icmVwZXNvIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBJTUM+PTMwIH4gIk9iZXNpZGFkIikpCmVqZXJjaWNpbzIKYGBgCgpBaG9yYSB2ZXJlbW9zIGxhcyBydXRhcyBkZSBhbsOhbGlzaXMKCi0gICBEZXB1cmFjacOzbiBkZSBiYXNlcyBkZSBkYXRvcwogICAgLSAgIERhdG9zIHBlcmRpZG9zKGBuYS5vbWl0YCkKICAgIC0gICBGaWx0cmFyIGRhdG9zIChgZmlsdGVyYCkKICAgIC0gICBTZWxlY2Npb25hciBsYXMgdmFyaWFibGVzIGRlIHVzbyAoYHNlbGVjdGApCiAgICAtICAgQ29udmVyc2lvbmVzIC8gQ29ycmVjY2lvbmVzIChgbXV0YXRlYCkKICAgIC0gICBBbmFsaXphciBsYSBlc3RydWN0dXJhIGRlIGxvcyBkYXRvcyAoYHN0cmApCgpgYGB7cn0Kc3RyKGVqZXJjaWNpbzIpICAgIApgYGAKCkxhIGRhdG9zIHF1ZSBzYWxlbiBlcyBlbCAqKmFuw6FsaXNpcyBkZSBlc3RydWN0dXJhKioKClRhbWJpw6luIHBvZGVtb3MgY2FtYmlhciBlbCB0aXBvIGRlIHZhcmlhYmxlLiBQb3IgZWplbXBsbywgbGEgdmFyaWFibGUKKlNleG8qIGVzdMOhIGNhdGVnb3JpemFkYSBjb21vICoqY2hyKiogKGNoYXJhY3RlciksIHBlcm8gbGEgY2FtYmlhbW9zIGEKKipmYWN0b3IqKi4gQ29udmllbmUgY2FtYmlhcmxhcyBwYXJhIHJlYWxpemFyIGFkZWN1YWRhbWVudGUgZWwgYW7DoWxpc2lzCmVzdGFkw61zdGljbywgeWEgcXVlIGxhcyB2YXJpYWJsZXMgdGlwbyAqY2hyKiBubyBzb24gw7p0aWxlcyBwYXJhIGVzZQphbsOhbGlzaXMuCgpgYGB7cn0KZWplcmNpY2lvMiRTZXhvIDwtIGFzLmZhY3RvcihlamVyY2ljaW8yJFNleG8pCmVqZXJjaWNpbzIkRXN0YXR1cyA8LSBhcy5mYWN0b3IoZWplcmNpY2lvMiRFc3RhdHVzKQplamVyY2ljaW8yJEV4cG9zaWNpb24gPC0gYXMuZmFjdG9yKGVqZXJjaWNpbzIkRXhwb3NpY2lvbikKZWplcmNpY2lvMiRFbmZlcm1lZGFkIDwtIGFzLmZhY3RvcihlamVyY2ljaW8yJEVuZmVybWVkYWQpCmVqZXJjaWNpbzIkUGFkcmVzLmZ1bWFkb3JlcyA8LSBhcy5mYWN0b3IoZWplcmNpY2lvMiRQYWRyZXMuZnVtYWRvcmVzKQplamVyY2ljaW8yJElNQ19lc2NhbGEgPC0gYXMuZmFjdG9yKGVqZXJjaWNpbzIkSU1DX2VzY2FsYSkKc3RyKGVqZXJjaWNpbzIpIApgYGAKClByb2NlZGVtb3MgYSBoYWNlciB1biBhbsOhbGlzaXMgZXN0YWTDrXN0aWNvCgpgYGB7cn0KcmVzdWx0YWRvcyA8LSBlamVyY2ljaW8yICU+JSAKICBnZXRfc3VtbWFyeV9zdGF0cygpICAgICNTb2xvIGVzIGNvbiBlbCBwYXF1ZXRlIHJzdGF0aXgKcmVzdWx0YWRvcwpgYGAKClBhcmEgaGFjZXIgdW4gYW7DoWxpc2lzIGVzdHJhdGlmaWNhZG8gc2UgcmVxdWllcmUgdW5hIHZhcmlhYmxlCmN1YWxpdGF0aXZhIGNvbW8gYGZhY3RvcmAKCmBgYHtyfQpyZXN1bHRhZG9zIDwtIGVqZXJjaWNpbzIgJT4lIAogIGdyb3VwX2J5KEVuZmVybWVkYWQpICU+JSAKICBnZXRfc3VtbWFyeV9zdGF0cygpICAgICNTb2xvIGVzIGNvbiBlbCBwYXF1ZXRlIHJzdGF0aXgKcmVzdWx0YWRvcwpgYGAKClBhcmEgcmVhbGl6YXIgbGEgdGFyZWEgYWJyaW1vcyBlbCBhcmNoaXZvIGRlIGRhdG9zIGRlIGZyYW1pbmdoYW0sIGx1ZWdvCnNlbGVjY2lvbmFtb3MgbGFzIHZhcmlhYmxlcyBkZSBsYXMgY3VhbGVzIHF1ZXJlbW9zIG9idGVuZXIgbGFzIG1lZGlkYXMKZGUgdGVuZGVuY2lhIGNlbnRyYWwgeSBsYXMgbWVkaWRhcyBkZSBkaXNwZXJzacOzbiB1dGlsaXphbmRvIGVsIHBhcXVldGUKYHJzdGF0aXhgCgpgYGB7cn0KZGF0b3NfZnJhbWluZ2hhbSA8LSByZWFkX2NzdigiL2Nsb3VkL3Byb2plY3QvQmlvZXN0YWRpc3RpY2FfUi9mcmFtaW5naGFtLmNzdiIpClZpZXcoZGF0b3NfZnJhbWluZ2hhbSkKCnN0cihkYXRvc19mcmFtaW5naGFtKQoKZGF0b3NfZnJhbWluZ2hhbSA8LSByZW5hbWUoZGF0b3NfZnJhbWluZ2hhbSwgc2V4b3MgPSBtYWxlKQpkYXRvc19mcmFtaW5naGFtCgpyZXN1bHRhZG9zX2ZyYW1pbmdoYW0gPC0gZGF0b3NfZnJhbWluZ2hhbSAlPiUgCiAgc2VsZWN0KGFnZSwgc2V4b3MsIGNpZ3NQZXJEYXksIHRvdENob2wsIHN5c0JQLCBkaWFCUCwgQk1JLCBoZWFydFJhdGUsIGdsdWNvc2UpICU+JSAKICBmaWx0ZXIoIWNvbXBsZXRlLmNhc2VzKC4pKSAlPiUKICBncm91cF9ieShzZXhvcykgJT4lIAogIGdldF9zdW1tYXJ5X3N0YXRzKCkKClZpZXcocmVzdWx0YWRvc19mcmFtaW5naGFtKQpgYGAKCkFob3JhIG9idGVuZW1vcyBlbCByYW5nbyBkZSBtYW5lcmEgbWFudWFsIGNhbGN1bGFuZG8gbGEgZGlmZXJlbmNpYSBlbnRyZQplbCBtw6F4aW1vIHkgZWwgbcOtbmltby4KCmBgYHtyfQpyYW5nb19hZ2UgPC0gbWF4KGRhdG9zX2ZyYW1pbmdoYW0kYWdlLCBuYS5ybSA9IFRSVUUpIC0gbWluKGRhdG9zX2ZyYW1pbmdoYW0kYWdlLCBuYS5ybSA9IFRSVUUpCnJhbmdvX2NpZ3MgPC0gbWF4KGRhdG9zX2ZyYW1pbmdoYW0kY2lnc1BlckRheSwgbmEucm0gPSBUUlVFKSAtIG1pbihkYXRvc19mcmFtaW5naGFtJGNpZ3NQZXJEYXksIG5hLnJtID0gVFJVRSkKcmFuZ29fY2hvbCA8LSBtYXgoZGF0b3NfZnJhbWluZ2hhbSR0b3RDaG9sLCBuYS5ybSA9IFRSVUUpIC0gbWluKGRhdG9zX2ZyYW1pbmdoYW0kdG90Q2hvbCwgbmEucm0gPSBUUlVFKQpyYW5nb19zYnAgPC0gbWF4KGRhdG9zX2ZyYW1pbmdoYW0kc3lzQlAsIG5hLnJtID0gVFJVRSkgLSBtaW4oZGF0b3NfZnJhbWluZ2hhbSRzeXNCUCwgbmEucm0gPSBUUlVFKQpyYW5nb19kYnAgPC0gbWF4KGRhdG9zX2ZyYW1pbmdoYW0kZGlhQlAsIG5hLnJtID0gVFJVRSkgLSBtaW4oZGF0b3NfZnJhbWluZ2hhbSRkaWFCUCwgbmEucm0gPSBUUlVFKQpyYW5nb19CTUkgPC0gbWF4KGRhdG9zX2ZyYW1pbmdoYW0kQk1JLCBuYS5ybSA9IFRSVUUpIC0gbWluKGRhdG9zX2ZyYW1pbmdoYW0kQk1JLCBuYS5ybSA9IFRSVUUpCnJhbmdvX2hyIDwtIG1heChkYXRvc19mcmFtaW5naGFtJGhlYXJ0UmF0ZSwgbmEucm0gPSBUUlVFKSAtIG1pbihkYXRvc19mcmFtaW5naGFtJGhlYXJ0UmF0ZSwgbmEucm0gPSBUUlVFKQpyYW5nb19nbHVjIDwtIG1heChkYXRvc19mcmFtaW5naGFtJGdsdWNvc2UsIG5hLnJtID0gVFJVRSkgLSBtaW4oZGF0b3NfZnJhbWluZ2hhbSRnbHVjb3NlLCBuYS5ybSA9IFRSVUUpCgpyYW5nb19hZ2UKcmFuZ29fY2lncwpyYW5nb19jaG9sCnJhbmdvX3NicApgYGAKCiMjIDMuIEFuw6FsaXMgZGVzY3JpcHRpdm8geSBub3JtYWxpZGFkIChTZXNpw7NuIDMpLCBjb24gZW5mb3F1ZSBlbiBlbCBhbsOhbGlzaXMgZGUgbm9ybWFsaWRhZAoKVGVuZXIgcHJlY2F1Y2nDs24gcGFyYSB1dGlsaXphciBgcmVhZC5jc3ZgbyBgcmVhZF9jc3ZgcG9ycXVlIGVsIG1hbmVqbyBkZQpsYXMgdGFibGFzIGVzIGRpZmVyZW50ZQoKYGBge3J9CmxpYnJhcnkodGlkeXZlcnNlKQoKdGFyZWFfMSA8LSByZWFkLmNzdigiL2Nsb3VkL3Byb2plY3QvQmlvZXN0YWRpc3RpY2FfUi9mcmFtaW5naGFtLmNzdiIpCnN0cih0YXJlYV8xKQoKYGBgCgpDYW1iaWFtb3MgZWwgdGlwbyBkZSB2YXJpYWJsZSBgbWFsZWBjb21vIGZhY3RvcgoKYGBge3J9CnRhcmVhXzEkbWFsZSA8LSBhcy5mYWN0b3IodGFyZWFfMSRtYWxlKQpzdHIodGFyZWFfMSkKCnRhcmVhXzEgPC0gdGFyZWFfMSAlPiUgCiAgbXV0YXRlKG1hbGUgPSBjYXNlX3doZW4obWFsZSA9PSAiMCIgfiAiTSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgbWFsZSA9PSAiMSIgfiAiSCIpKQoKc3RyKHRhcmVhXzEpCgpWaWV3KHRhcmVhXzEpCmBgYAoKUmVhbGl6YW1vcyBlbCBhbsOhbGlzaXMgZGVzY3JpcHRpdm8KCmBgYHtyfQpyZXN1bHRhZG9zX3RhcmVhXzEgPC0gdGFyZWFfMSAlPiUgCiAgZ3JvdXBfYnkobWFsZSkgJT4lIAogIGdldF9zdW1tYXJ5X3N0YXRzKGFnZSwgY2lnc1BlckRheSwgdG90Q2hvbCwgc3lzQlAsIGRpYUJQLCBCTUksIGhlYXJ0UmF0ZSwgZ2x1Y29zZSkKCnJlc3VsdGFkb3NfdGFyZWFfMSA8LSByZXN1bHRhZG9zX3RhcmVhXzEgJT4lIAogIG11dGF0ZShSYW5nb18xID0gbWF4IC0gbWluKQogIApWaWV3KHJlc3VsdGFkb3NfdGFyZWFfMSkKYGBgCgpQYXJhIGNyZWFyIHVuIGFyY2hpdm8gYC5jc3ZgCgpgYGB7cn0Kd3JpdGUuY3N2KHJlc3VsdGFkb3NfdGFyZWFfMSwgZmlsZSA9ICJyZXN1bHRhZG9zX3RhcmVhMSIpCmBgYAoKIyMjIFRlbWFzIGRlIGVzdGFkw61zdGljYQoKRGVmaW5pciAqKnBvYmxhY2nDs24qKiB5ICoqbXVlc3RyYSoqLiBMYSAqbXVlc3RyYSogZXMgZWwgY29uanVudG8gZGUKZWxlbWVudG9zIHJlcHJlc2VudGF0aXZvcyBkZSBsYSBwb2JsYWNpw7NuIHkgc2UgZGViZSBzZWxlY2Npb25hcgphbGVhdG9yaWFtZW50ZS4KCkVzY2FsYXMgZGUgbWVkaWNpw7NuOiA0IG5pdmVsZXMgZGUgZXNjYWxhCgotICAgTm9taW5hbDogU2UgdXRpbHphbiBub21icmVzLCBzw61tYm9sb3MgbyBudW1lcmFsZXMsIHF1ZSBzb24gZW1wbGVhZG9zCiAgICBjb21vIGV0aXF1ZXRhcy4gU2lydmUgcGFyYSBzZXBhcmFyIHBvciBncnVwb3MuIE5vIHNlIHB1ZWRlbiBhcGxpY2FyCiAgICBvcGVyYWNpb25lcyBtYXRlbcOhdGljYXMuCgotICAgT3JkaW5hbDogTG9zIG7Dum1lcm9zIG8gZXRpcXVldGFzIHV0aWxpemFkYXMgaW5kaWNhbiB1biBvcmRlbi4gU2lydmUKICAgIHBhcmEgb3JnYW5pemFyLgoKLSAgIEludGVydmFsb3M6IEVzdGFibGVjZSB1biBvcmRlbiBkZXRlcm1pbmFkby4gRWwgMCBzaWduaWZpY2EgYXVzZW5jaWEKICAgIGRlIHZhbG9yLiBTZSBwdWVkZSB1dGlsaXphciBwYXJhIG9wZXJhY2lvbmVzIG1hdGVtw6F0aWNhcy4KCi0gICBQcm9wb3JjacOzbi9jb2NpZW50ZS9yYXrDs246IFNvbiBzZWN1ZW5jaWFzIG51bcOpcmljYXMuIFNlIHB1ZWRlbgogICAgcmVhbGl6YXIgb3BlcmFjaW9uZXMgbWF0ZW3DoXRpY2FzLiBFcyBsYSBlc2NhbGEgcXVlIHBlcm1pdGUgY29tcGFyYXIKICAgIGRlIG1lam9yIG1hbmVyYSBsb3MgZGF0b3MuCgojIyMgRGlzdHJpYnVjacOzbiBkZSBkYXRvcwoKLSAgIERpc3RyaWJ1Y2nDs24gbm9ybWFsCi0gICBEaXN0cmlidWNpw7NuIGRlIFBvaXNzb24KLSAgIERpc3RyaWJ1Y2nDs24gYmlub21pYWwKCiMjIyBBc2ltZXRyw61hIGRlIGRhdG9zCgotICAgTmVnYXRpdmEKLSAgIFNpbcOpdHJpY2EKLSAgIFBvc2l0aXZhCgojIyMgQ3VydG9zaXMKCi0gICBMZXB0b2PDunJ0aWNhCi0gICBNZXNvY8O6cnRpY2EKLSAgIFBsYXRpY8O6cnRpY2EKCiMjIDQuIExhIG5vcm1hbGlkYWQuLi4KClB1ZWRlIHNlciBldmFsdWFkYSBtZWRpYW50ZSAzIG1vZGVsb3MKCi0gICBNb2RlbG9zIGdyw6FmaWNvczogaGlzdG9ncmFtYXMgeSBxcXBsb3QKLSAgIE1lZGlhID0gTWVkaWFuYSA9IE1vZGEKLSAgIFBydWViYXMgZGUgbm9ybWFsaWRhZDoKICAgIC0gICBTaGFwaXJvLVdpbGQ6IHBhcmEgZ3J1cG9zIGNvbiAqbWVub3MgZGUgNTAgb2JzZXJ2YWNpb25lcyoKICAgIC0gICBLb2xtb2dvcm92LVNtaXJub3YvTGlsbGlmb3JzOiBwYXJhIGdydXBvcyBjb24gKm3DoXMgZGUgNTAKICAgICAgICBvYnNlcnZhY2lvbmVzKgoKIyMjIFJlcHJlc2VudGFjacOzbiBkZSBsb3MgZGF0b3MgYSBwYXJ0aXIgZGUgbGFzIG1lZGlkYXMgZGUgdGVuZGVuY2lhIGNlbnRyYWwKClV0aWxpemFtb3MgZWwgdmFsb3IgZGUgbGEgZWRhZAoKYGBge3J9CmVqZXJjaWNpb18xIDwtIHRhcmVhXzEgJT4lIAogIGdldF9zdW1tYXJ5X3N0YXRzKGFnZSkKCmNvbnRlbyA8LSB0YXJlYV8xICU+JSAKICBjb3VudChhZ2UpCgojbWVhbjogNDkuNTgKI21lZGlhbjogNDkKI21vZGE6IDQwCmBgYAoKU29sbyBjb24gZXN0b3MgcGFyw6FtZXRyb3Mgc2UgcG9kcsOtYSBkZWNpciBxdWUgbGEgbXVlc3RyYSBubyBjdW1wbGUgY29uCmxhIG5vcm1hbGlkYWQuIFZlYW1vcyBzdSBoaXN0b3JncmFtYSBjb24gbGEgZnVuY2nDs24gYGhpc3QoKWAsIGRlbCBjdWFsCnBvZGVtb3MgbW9kaWZpY2FyIHN1IHTDrXR1bG8sIGxhcyBldGlxdWV0YXMgeSBlbCBjb2xvci4KCmBgYHtyfQpoaXN0KHRhcmVhXzEkYWdlLAogICAgIG1haW4gPSAiSGlzdG9ncmFtYSBkZSBlZGFkIiwKICAgICB5bGFiID0gIkZyZWN1ZW5jaWEiLAogICAgIHhsYWIgPSAiRWRhZCAoYcOxb3MpIiwKICAgICBjb2wgPSAic2FsbW9uIiwKICAgICBicmVha3MgPSA1MCkKYGBgCgpQb2RlbW9zIHJlYWxpemFyIGxhcyBncsOhZmljYXMgY29uIGVsIHBhcXVldGUgYGdncGxvdGAgcXVlIHBlcm1pdGUgY3JlYXIKZ3LDoWZpY29zIG3DoXMgZXN0w6l0aWNvcwoKYGBge3J9CmxpYnJhcnkoZ2dwbG90MikKYGBgCgpQYXJhIGdyYWZpY2FyIGVsIGhpc3RvZ3JhbWEKCmBgYHtyfQpncmFmaWNhXzEgPC0gZ2dwbG90KHRhcmVhXzEsIGFlcyh4PWFnZSwgZmlsbCA9IG1hbGUpKSArCiAgZ2VvbV9oaXN0b2dyYW0oYmlud2lkdGggPSAxKQoKZ3JhZmljYV8xCmBgYAoKQ29uc3RydXlhbW9zIGxvcyBncsOhZmljb3MgZGUgZGVuc2lkYWQKCmBgYHtyfQpncmFmaWNhXzIgPC0gZ2dwbG90KHRhcmVhXzEsIGFlcyh4PWFnZSwgZmlsbD0gbWFsZSwgY29sb3VyID0gbWFsZSkpKwogIGdlb21fZGVuc2l0eShhbHBoYT0wLjEpKwogIHhsaW0oMzAsNzUpICsKICBsYWJzKHg9IkVkYWQiLCB5PSAiRGVuc2lkYWQiLAogICAgICAgdGl0bGUgPSAiRGVuc2lkYWQgZGUgZGF0b3MgZWRhZCIsCiAgICAgICBjYXB0aW9uID0gIlRleHRvIGRlIHBydWViYSBjb21vIHBpZSBkZSBmaWd1cmEiKSArCiAgYW5ub3RhdGUoInRleHQiLCB4ID0gNjUsIHkgPSAwLjAzNSwgbGFiZWwgPSAiU29tZSB0ZXh0IikKZ3JhZmljYV8yCmBgYAoKIyMjIFJldmlzYXIgbGEgYXNpbWV0csOtYSB5IGxhIGN1cnRvc2lzCgpgYGB7cn0KaW5zdGFsbC5wYWNrYWdlcygicHN5Y2giKQppbnN0YWxsLnBhY2thZ2VzKCJjYXIiKQppbnN0YWxsLnBhY2thZ2VzKCJub3J0ZXN0IikKbGlicmFyeShwc3ljaCkKbGlicmFyeShjYXIpICAgICAgICAjUGFxdWV0ZXLDrWEgcGFyYSBldmFsdWFyIGRpc3RyaWJ1Y2nDs24gZGUgZGF0b3MKbGlicmFyeShjYXJEYXRhKQpsaWJyYXJ5KG5vcnRlc3QpICAgICMKYGBgCgpMYSBjdXJ0b3NpcyBpZGVhbCBkZWJlcsOtYSBzZXIgY2VyY2FubyBhIDAgcGFyYSBkZWNpciBxdWUgZXMKKm1lc29jw7pydGljYSouIEVzIGRlY2lyLAoKLSAgIGsgPSAwIC1cPiBtZXNvY8O6cnRpY2EKLSAgIGsgXD4gMCAtXD4gbGVwdG9jw7pydGljYQotICAgayBcPCAwIC1cPiBwbGF0aWPDunJ0aWNhCgpQYXJhIGNhbGN1bGFyIGxhIGFzaW1ldHLDrWEsIGRlY2ltb3MgcXVlCgotICAgYSA9IDAgKHNpbcOpdHJpY28pCi0gICBhIFw+IDAgKGFzaW1ldHLDrWEgcG9zaXRpdmEpCi0gICBhIFw8IDAgKGFzaW1ldHLDrWEgbmVnYXRpdmEpCgpQYXJhIGNhbGN1bGFyIGxhIGN1cnRvc2lzIHV0aWxpemFtb3MgZWwgcGFxdWV0ZSBgcHN5Y2hgCgpgYGB7cn0Ka3VydG9zaSh0YXJlYV8xJGFnZSkKYGBgCgpDdWFuZG8gZWwgdmFsb3IgZGUgY3VydG9zaXMgZXMgXDwgMiBvIFw+IDIsIGVudG9uY2VzICoqbm8gdGllbmUgdW4gY29tcG9ydGFtaWVudG8gbm9ybWFsKioKCkNhbGN1bGFtb3MgbGEgY3VydG9zaXMgeSBlbCBzZXNnbyBwYXJhIGhvbWJyZXMgeSBwYXJhIG11amVyZXMKCmBgYHtyfQojUGFyYSBjYWxjdWxhciBsYSBjdXJ0b3NpcwprdXJ0b3NpKHRhcmVhXzEkYWdlW3RhcmVhXzEkbWFsZSA9PSAiSCJdKQprdXJ0b3NpKHRhcmVhXzEkYWdlW3RhcmVhXzEkbWFsZSA9PSAiTSJdKQoKI1BhcmEgY2FsY3VsYXIgZWwgc2VzZ28gKHNpbWV0csOtYSBvIGFzaW1ldHLDrWEpCnNrZXcodGFyZWFfMSRhZ2UpCnNrZXcodGFyZWFfMSRhZ2VbdGFyZWFfMSRtYWxlID09ICJIIl0pCnNrZXcodGFyZWFfMSRhZ2VbdGFyZWFfMSRtYWxlID09ICJNIl0pCmBgYApWZWFtb3MgbG9zIFFRLXBsb3QKCmBgYHtyfQpxcVBsb3QodGFyZWFfMSRhZ2UsCiAgICAgICBtYWluID0gIlFRIHBsb3QgZWRhZCIsCiAgICAgICB5bGFiID0gIkVkYWQgKGHDsW9zKSIsCiAgICAgICB4bGFiID0gIkN1YW50aWxlcyIpCmBgYApDb21iaW5hciBhbsOhbGlzaXMgZGUgYXNpbWV0csOtYSBjb24gaGlzdG9ncmFtYSwgeSBjdXJ0b3NpcyBjb24gUVEtcGxvdC4gTG9zIFFRcGxvdCBwZXJtaXRlbiBvYnNlcnZhciBlbCBwZXNvIGRlIGxhcyBjb2xhcy4gU2kgZXMgdW5hIGRpc3RyaWJ1Y2nDs24gbm9ybWFsLCBlbnRvbmNlcyBzZSBkZWJlcsOtYSBkZSBjb21wb3J0YXIgY29tbyB1bmEgbMOtbmVhIHJlY3RhLiAKClBvZGVtb3MgcmVhbGl6YXIgZWwgUVFwbG90IGVzdHJhdGlmaWNhZG8gcG9yIGhvbWJyZXMgeSBtdWplcmVzLiAKYGBge3J9CnFxUGxvdCh0YXJlYV8xJGFnZVt0YXJlYV8xJG1hbGUgPT0gIkgiXSwKICAgICAgIG1haW4gPSAiUVEgcGxvdCBlZGFkIiwKICAgICAgIHlsYWIgPSAiRWRhZCAoYcOxb3MpIiwKICAgICAgIHhsYWIgPSAiQ3VhbnRpbGVzIiwKICAgICAgIGNvbCA9ICJibHVlIiwKICAgICAgIGNvbC5saW5lcyA9ICJyZWQiKQpgYGAKCkdyw6FmaWNvcyBkZSBjYWphcyB5IGJpZ290ZXMgc29uIGltcG9ydGFudGVzIHBhcmEgZWwgYW7DoWxpc2lzCmBgYHtyfQpib3hwbG90KHRhcmVhXzEkYWdlKSAjcGFyYSB0b2RvcyBsb3MgZGF0b3MKYm94cGxvdCh0YXJlYV8xJGFnZSB+IHRhcmVhXzEkbWFsZSwKICAgICAgICBtYWluID0gIkNvbXBhcmFjacOzbiBkZSBlZGFkIEggdnMgTSIsCiAgICAgICAgeWxhYiA9ICJFZGFkIChhw7FvcykiLAogICAgICAgIHhsYWIgPSAiR3J1cG9zIiwKICAgICAgICBjb2wgPSBjKCJsaWdodGdyZWVuIiwgInB1cnBsZSIpKSAjZXN0cmF0aWZpY2FkbyBwb3Igc2V4bwpgYGAKVXRpbGl6YW5kbyBgZ2dwbG90YApgYGB7cn0KcCA8LSBnZ3Bsb3QodGFyZWFfMSwgYWVzKHggPSBtYWxlLCB5ID0gYWdlLCBmaWxsID0gbWFsZSkpKwogIGdlb21fYm94cGxvdCgpKwogIGxhYnMoeD0iR3J1cG9zIiwgeT0gIkVkYWQiLAogICAgICAgdGl0bGUgPSAiQ29tcGFyYWNpw7NuIGRlIGVkYWQgSCB2cyBNIiwKICAgICAgIGNhcHRpb24gPSAiVGV4dG8gZGUgcHJ1ZWJhIGNvbW8gcGllIGRlIGZpZ3VyYSIpCnAKYGBgCiMjIyBQcnVlYmFzIGRlIG5vcm1hbGlkYWQKCi0gU2hhcGlyby1XaWxrczogU2UgdXRpbGl6YSBjdWFuZG8gKm4qIDwgNTAgKGluY2x1c28gbWVub3IgYSA0MCkuIEN1YW5kbyBoYXkgbWVub3MgZGUgMTAgZGF0b3MgeWEgbm8gc2UgcmVjb21pZW5kYSB1c2FyLiAKLSBLb2xtb2dvcm92LVNtaXJub3Y6IFNlIHV0aWxpemEgY3VhbmRvICpuKiA+IDUwCgpQYXJhIHJlYWxpemFyIGxhIGRlIEtvbG1vZ29yb3YtU21pcm5vdi4uLkNvbiBsb3MgcmVxdWlzaXRvcyBtw61uaW1vClRpZW5lIGNvbW8KJEhfMCQ6IExhIGRpc3RyaWJ1Y2nDs24gcmVhbCB5IGxhIGRpc3RyaWJ1Y2nDs24gdGXDs3JpY2Egc29uICoqaWd1YWxlcyBvIHNpbWlsYXJlcyAobm9ybWFsaWRhZCkqKgokSF9hJDogTGEgZGlzdHJpYnVjacOzbiByZWFsIHkgbGEgZGlzdHJpYnVjacOzbiB0ZcOzcmljYSBzb24gKipkaWZlcmVudGVzIChubyBub3JtYWxpZGFkKSoqCgpDdWFuZG8gKnAgPiAwLjA1KiBubyBzZSByZWNoYXphIGxhICoqJEhfMCQqKgoKKipPSk86IHlhIG5vIHNlIHVzYSBhc8OtLCBzZSB1c2EgdW5hIG1lam9yYWRhKioKYGBge3J9CmtzLnRlc3QodGFyZWFfMSRhZ2UsICJwbm9ybSIsCiAgICAgICAgbWVhbiA9IDQ5LjYsIHNkID0gOC41NywKICAgICAgICBhbHRlcm5hdGl2ZSA9ICJ0d28uc2lkZWQiKQoKbWVhbih0YXJlYV8xJGFnZSkKc2QodGFyZWFfMSRhZ2UpCmBgYApDb21vIGVsIGVsIHZhbG9yIGRlICpwKiBvYnRlbmlkbyBlcyA8IDAuMDUgKHAtdmFsdWUgPCAyZS0xNiksIHNlIHJlY2hhemEgbGEgaGlww7N0ZXNpcyBudWxhIHkgY29uY2x1aW1vcyBxdWUgbGEgbXVlc3RyYSBubyBwcm92aWVuZSBkZSB1bmEgZGlzdHJpYnVjacOzbiBub3JtYWwuIAoKTGEgcHJ1ZWJhIGRlIEtvbG1vZ29yb3YtU21pcm5vdi1MaWxsaWVmb3JzIGVzIGxhIGNvcnJlZ2lkYQpgYGB7cn0KIyBTZSB1dGlsaXphIGxhIGxpYnJlcsOtYSBkZSBub3J0ZXN0CmxpbGxpZS50ZXN0KHRhcmVhXzEkYWdlW3RhcmVhXzEkbWFsZSA9PSAiSCJdKQpgYGAKUGFyYSBoYWNlciBsYSBkZSBTaGFwaXJvLVdpbGsKYGBge3J9CnNoYXBpcm8udGVzdCh0YXJlYV8xJGFnZVt0YXJlYV8xJG1hbGUgPT0gIkgiXSkKc2hhcGlyby50ZXN0KHRhcmVhXzEkYWdlW3RhcmVhXzEkbWFsZSA9PSAiTSJdKQpgYGAKCiMjIDQuIFBydWViYXMgZGUgY29udHJhc3RlIGVzdGFkw61zdGljbwoKIyMjIFBydWViYXMgcGFyYW3DqXRyaWNhcyAodCBkZSBzdHVkZW50IHkgQU5PVkEpClJlcXVpZXJlbiBjdW1wbGlyIHZhcmlvcyBzdXB1ZXN0b3MKCi0gTm9ybWFsaWRhZAotIElndWFsZGFkIGRlIHZhcmlhbnphcyAoUHJ1ZWJhIGRlIExldmVuZSk6IFJlcXVpZXJlIGVsIHBsYW50ZWFtaWVudG8gZGUgbGFzIHNpZ3VpZW50ZXMgaGlww7N0ZXNpcwogIC0gJEhfMCQ6IGxhIHZhcmlhbnphIGRlIGxhIHZhcmlhYmxlIGVzIGlndWFsIG8gc2VtZWphbnRlIGVuIGNhc29zIHkgY29udHJvbGVzICgkcD4wLjA1JCkKICAtICRIX2EkOiBsYXMgdmFyaWFuemFzIHNvbiBkaWZlcmVudGVzIGVudHJlIGdydXBvcyAoJHA8MC4wNSQpCgpMYXMgaGlwb3Rlc2lzIHBsYW50ZWFkYXMgZW4gbGFzIHBydWViYXMgZGUgY29tcGFyYWNpw7NuIGRlIG1lZGlhcyBzb246CgotICRIXzA6IFxtdV8xID0gXG11XzIkCi0gJEhfYTogXG11XzEgXG5lcSBcbXVfMiQKCgojIyBQcnVlYmFzIG5vIHBhcmFtw6l0cmljYXMKLSBTaWdub3MgZGUgV2lsY294b24KLSBVIGRlIE1hbm4tV2hpdG5leQotIEZyaWVkbWFuCi0gS3J1c2thbC1XYWxsaXMKCgpFbXBlY2Vtb3MgY29uIGxhcyBwcnVlYmFzIHBhcmFtw6l0cmljYXMKCkNhcmdhbW9zIG51ZXN0cmEgYmFzZSBkZSBkYXRvcwpgYGB7cn0Kc2VzaW9uXzQgPC0gcmVhZC5jc3YoIi9jbG91ZC9wcm9qZWN0L0Jpb2VzdGFkaXN0aWNhX1IvZGF0b3NfMS5jc3YiKQpWaWV3KHNlc2lvbl80KQpgYGAKClBhc29zIGEgc2VndWlyIHBhcmEgcmVhbGl6YXIgbGEgcHJ1ZWJhOgoKMS4gUmVhbGl6YXIgZWwgYW7DoWxpc2lzIGRlIG5vcm1hbGlkYWQKMi4gQW7DoWxpc2lzIGRlIGlndWFsZGFkIHZhcmlhbnphIChQcnVlYmEgZGUgTGV2ZW5lKQozLiBQcnVlYmEgZGUgdCBkZSBzdHVkZW50CjQuIEPDoWxjdWxvIGRlIHRhbWHDsW8gZGUgZWZlY3RvCjUuIENvbmNsdXNpw7NuCiAKCkNvbXBhcmVtb3MgbGEgdGFsbGEgZW50cmUgY2Fzb3MgeSBjb250cm9sZXMuIMK/RXhpc3RlIGRpZmVyZW5jaWEgZW4gZXN0YSB2YXJpYWJsZSBlbnRyZSBhbWJvcyBncnVwb3M/CgoxLiBBbsOhbGlzaXMgZGUgbm9ybWFsaWRhZApgYGB7cn0Kc3RyKHNlc2lvbl80KQpzZXNpb25fNCRFc3RhdHVzIDwtIGFzLmZhY3RvcihzZXNpb25fNCRFc3RhdHVzKQpzdW1tYXJ5KHNlc2lvbl80JEVzdGF0dXMpICAjU29sbyBzaXJ2ZSBwYXJhIHZhcmlhYmxlcyBjdWFudGl0YXRpdmFzIG8gZGUgdGlwbyBmYWN0b3IKCmBgYApEYWRvIHF1ZSB0ZW5lbW9zID4gNTAgb2JzZXJ2YWNpb25lcyBwYXJhIGNhZGEgZ3J1cG8sIHV0aWxpemFtb3MgbGEgcHJ1ZWJhIGRlIExpbGxpZWZvcnMuIEVsIGFuw6FsaXNpcyBkZWJlIGRlIHNlciByZWFsaXphZG8gcGFyYSBjYWRhIGdydXBvIHBvciBzZXBhcmFkbywgcmVjb3JkYW5kbyBxdWUKCiRIXzAkOiBMYSBkaXN0cmlidWNpw7NuIHJlYWwgeSB0ZcOzcmljYSBzb24gaWd1YWxlcyAoJHAgPiAwLjA1KQokSF9hJDogTGEgZGlzdHJpYnVjacOzbiByZWFsIHkgdGXDs3JpY2Egc29uIGRpZmVyZW50ZXMgKCRwIDwgMC4wNSkKCmBgYHtyfQpsaWxsaWUudGVzdChzZXNpb25fNCRUYWxsYVtzZXNpb25fNCRFc3RhdHVzID09ICJDb250cm9sIl0pCmxpbGxpZS50ZXN0KHNlc2lvbl80JFRhbGxhW3Nlc2lvbl80JEVzdGF0dXMgPT0gIkNhc28iXSkKYGBgCgpEYWRvIHF1ZSAkcC12YWx1ZSA8IDAuMDUkLCBzZSByZWNvbWllbmRhIGFuw6FsaXNpcyBubyBwYXJhbcOpdHJpY28gcGFyYSBjb21wYXJhciBncnVwb3MuCgoyLiBBaG9yYSB2ZXJpZmljYW1vcyBlbCBhbsOhbGlzaXMgZGUgaWd1YWxkYWQgZGUgdmFyaWFuemEgKFBydWViYSBkZSBMZXZlbmUpCiRIXzAkOiBMYXMgdmFyaWFuemFzIGVudHJlIGdydXBvcyBzb24gaWd1YWxlcyBvIHNpbWlsYXJlcyAoJHAgPiAwLjA1KQokSF9hJDogTGFzIHZhcmlhbnphcyBlbnRyZSBncnVwb3Mgc29uIGRpZmVyZW50ZXMgKCRwIDwgMC4wNSkKCmBgYHtyfQpsZXZlbmVUZXN0KHNlc2lvbl80JFRhbGxhIH4gc2VzaW9uXzQkRXN0YXR1cywKICAgICAgICAgICAgY2VudGVyID0gIm1lZGlhbiIpICNDb21vIG5vIHNlIGN1bXBsZSBsYSBub3JtYWxpZGFkLCBlbCBwYXLDoW1ldHJvIHF1ZSBjb3JyZXNwb25kZSBlcyBsYSBtZWRpYW5hCgpsZXZlbmVUZXN0KHNlc2lvbl80JFRhbGxhIH4gc2VzaW9uXzQkRXN0YXR1cywKICAgICAgICAgICAgY2VudGVyID0gIm1lYW4iKQpgYGAKRWwgKnAtdmFsdWUqICgqKlByKD5GKSoqKSA+IDAuMDUsIGVudG9uY2VzIHNlIGFjZXB0YSBsYSAkSF8wJC4gTGFzIHZhcmlhbnphcyBlbnRyZSBncnVwb3Mgc29uIGlndWFsZXMgbyBzaW1pbGFyZXMuCgozLiBBaG9yYSByZWFsaXphbW9zIGxhIHBydWViYSBkZSBUCgpMYXMgaGlww7N0ZXNpcyBzb24KCiRIXzA6IFxtdV8xID0gXG11XzIkCiRIX2E6IFxtdV8xIFxuZXEgXG11XzIkCgpgYGB7cn0KCiNTaSB2YXIuZXEgPSBUUlVFLCBlbnRvbmNlcyBoYWNlIGxhIHQgZGUgc3R1ZGVudCBub3JtYWwKdC50ZXN0KHNlc2lvbl80JFRhbGxhIH4gc2VzaW9uXzQkRXN0YXR1cywKICAgICAgIGFsdGVybmF0aXZlID0gInR3by5zaWRlZCIsICNwdWVkZSBzZXIgZ3JlYXRlciBvIGxlc3MsIGRlcGVuZGllbmRvIGRlIHNpIGVzIHVuYSBjb2xhCiAgICAgICBjb25mLmxldmVsID0gMC45NSwKICAgICAgIHZhci5lcXVhbCA9IFRSVUUsCiAgICAgICBwYWlyZWQgPSBGQUxTRSkKCiNTaSB2YXIuZXEgPSBGQUxTRSwgZW50b25jZXMgaGFjZSBsYSBjb3JyZWNjacOzbiBkZSBXZWxjaCBwb3JxdWUgbGFzIHZhcmlhbnphcyBlbnRyZSBncnVwb3Mgc29uIGRpZmVyZW50ZXMKI0VzdGEgcHJ1ZWJhIGVzIHBhcmEgdmFyaWFuemFzIE5PIGlndWFsZXMKdC50ZXN0KHNlc2lvbl80JFRhbGxhIH4gc2VzaW9uXzQkRXN0YXR1cywKICAgICAgIGFsdGVybmF0aXZlID0gInR3by5zaWRlZCIsCiAgICAgICBjb25mLmxldmVsID0gMC45NSwKICAgICAgIHZhci5lcXVhbCA9IEZBTFNFLAogICAgICAgcGFpcmVkID0gRkFMU0UpCgojUHLDoWN0aWNhbWVudGUgZXN0YSBlcyBpZ3VhbCBhIGxhIGFudGVyaW9yLCBjdWFuZG8gdmFyLmVxdWFsID0gRkFMU0UKdC50ZXN0KHNlc2lvbl80JFRhbGxhIH4gc2VzaW9uXzQkRXN0YXR1cykKYGBgCgpFbCBpbnRlcnZhbG8gZGUgY29uZmlhbnphIChJQykgc29uIC0wLjAzMjQgeSAwLjAxNjQKCkRhZG8gcXVlIGVsICpwLXZhbG9yKiAoMC41KSA+IDAuMDUsIGVudG9uY2VzIG5vIHNlIHJlY2hhemEgbGEgJEhfMCQgeSBsYXMgbWVkaWFzIGVudHJlIGFtYm9zIGdydXBvcyBzb24gaWd1YWxlcy4gCkVsIGPDoWxjdWxvIGRlbCB0YW1hw7FvIGRlbCBlZmVjdG8gKipOTyoqIHNlIGFwbGljYSBjdWFuZG8gcCA+IDAuMDUsIHBlcm8gdmFtb3MgYSB2ZXJpZmljYXJsbwoKYGBge3J9CmNvaGVuLmQoc2VzaW9uXzQkVGFsbGEsIHNlc2lvbl80JEVzdGF0dXMpCiNPYnNlcnZhciBlbCB2YWxvciBxdWUgZGljZSBlZmZlY3QKCiMgQ3VhbmRvCiNkIDwgMC4yOiBwZXF1ZcOxbwojZCA8IDAuODogbWVkaWFubwojZCA+IDAuODogZ3JhbmRlCmBgYApPdHJhIG9wY2nDs24gZGUgYW7DoWxpcyB1c2FuZG8gYHJzdGF0aXhgCmBgYHtyfQpsaWJyYXJ5KHJzdGF0aXgpCmxpYnJhcnkodGlkeXZlcnNlKQp2YXJfaWd1YWxkYWQgPC0gc2VzaW9uXzQgJT4lIAogIGxldmVuZV90ZXN0KFRhbGxhIH4gRXN0YXR1cywgY2VudGVyID0gIm1lZGlhbiIpCgplZmVjdG8gPC0gc2VzaW9uXzQgJT4lIAogIGNvaGVuc19kKFRhbGxhIH4gRXN0YXR1cykKYGBgCgoKIyMgNS4gQU5PVkEgKFNlc2nDs24gNSkKCkNhcmdhbW9zIGxhIGJhc2UgZGUgZGF0b3MKYGBge3J9CnB1bG1vbmFyIDwtIHJlYWQuY3N2KCIvY2xvdWQvcHJvamVjdC9CaW9lc3RhZGlzdGljYV9SL2VuZmVybWVkYWRfcHVsbW9uYXIuY3N2IikKaGVhZChwdWxtb25hcikKCiNDaGVjYXIgZWwgYXJjaGl2byBxdWUgZXN0w6kgZ3VhcmRhZG8gY29tbyBDU1YgVVRGLTgKYGBgCgpQcmVwYXJhY2nDs24gZGUgbGEgYmFzZSBkZSBkYXRvcwpgYGB7cn0Kc3RyKHB1bG1vbmFyKQpwdWxtb25hciRFc3RhdHVzIDwtIGFzLmZhY3RvcihwdWxtb25hciRFc3RhdHVzKQpwdWxtb25hciREb3NpcyA8LSBhcy5mYWN0b3IocHVsbW9uYXIkRG9zaXMpCnB1bG1vbmFyJFNleG8gPC0gYXMuZmFjdG9yKHB1bG1vbmFyJFNleG8pCgpzdHIocHVsbW9uYXIpCmhlYWQocHVsbW9uYXIpCmBgYApFbXBlY2Vtb3MgY29uIGVsIHByb2Nlc28gZGUgYW7DoWxpc2lzLiAKCjEuIFByaW1lcm8gY29udGVtb3MgZWwgbsO6bWVybyBkZSBkYXRvcywgKm4qLgpgYGB7cn0KbGlicmFyeSh0aWR5dmVyc2UpCgpwdWxtb25hciAlPiUgCiAgY291bnQoRG9zaXMpCgpgYGAKRGFkbyBxdWUgcGFyYSBjYWRhIHVubyBkZSBsb3MgZ3J1cG9zIHN1ICpuKiBlcyA8IDUwLCB1dGlsaXphbW9zIGxhIHBydWViYSBkZSBTaGFwaXJvLVdpbGtzCgpgYGB7cn0KbGlicmFyeShyc3RhdGl4KQoKIyBTb2xvIGxhIHBydWViYSBkZSBTaGFwaXJvIHB1ZWRlIGFuYWxpemFyc2UgbWVkaWFudGUgZXN0ZSBwcm9jZXNvCm5vcm1hbGlkYWQgPC0gcHVsbW9uYXIgJT4lIAogIGdyb3VwX2J5KERvc2lzKSAlPiUgCiAgc2hhcGlyb190ZXN0KFB2Q08yKSAgICAgICAjUG9kZW1vcyByZWFsaXphciBsYSBwcnVlYmEgcGFyYSBtw6FzIGRlIHVuYSB2YXJpYWJsZQoKbm9ybWFsaWRhZAoKcXFub3JtKHB1bG1vbmFyJFB2Q08yKQpxcWxpbmUocHVsbW9uYXIkUHZDTzIpCgoKYGBgCkxhcyBoaXDDs3Rlc2lzIHNvbgoKLSAkSF8wJDogTGEgZGlzdHJpYnVjacOzbiByZWFsICQ9JCBsYSBkaXN0cmlidWNpw7NuIHRlw7NyaWNhICAqKihub3JtYWxpZGFkLCBwLXZhbHVlID4gMC4wNSkqKgotICRIX2EkOiBMYSBkaXN0cmlidWNpw7NuIHJlYWwgJFxuZXEkIGxhIGRpc3RyaWJ1Y2nDs24gdGXDs3JpY2Egc29uICoqKG5vIG5vcm1hbGlkYWQsIHAtdmFsdWUgPCAwLjA1KSoqCgpEYWRvIHF1ZSBlbCBhbsOhbGlzaXMgZGUgbm9ybWFsaWRhZCBpbmRpY8OzIHF1ZSBlbCB2YWxvciAqcCogZGUgbGEgdmFyaWFibGUgKlB2Q08yKiBwYXJhIGNhZGEgdW5vIGRlIGxvcyBncnVwb3MgZnVlIG1lbm9yIHF1ZSAwLjA1LCBlbnRvbmNlcyBzZSByZWNoYXphIGxhIGhpcMOzdGVzaXMgbnVsYSB5IGFjZXB0YW1vcyBsYSAkSF9hJCwgcXVlIGVzIHF1ZSBubyBwcm92aWVuZW4gZGUgdW5hIGRpc3RyaWJ1Y2nDs24gbm9ybWFsLiBBaG9yaXRhIG9taXRpbW9zIHF1ZSBzZWFuICpubyBub3JtYWxlcyouIFBhcmEgcmVhbGl6YXIgbGEgQU5PVkEsIHRvZG9zIGxvcyBncnVwb3MgZGViZW4gY3VtcGxpciBlbCBzdXB1ZXN0byBkZSBub3JtYWxpZGFkCgpFTCBzaWd1aWVudGUgcGFzbyBlcyBjb21wcm9iYXIgbGEgaWd1YWxkYWQgZGUgdmFyaWFuemFzCgoyLiBJZ3VhbGRhZCBkZSB2YXJpYW56YXMuIFBhcmEgZXN0ZSBjYXNvIHNlIHVzYSBsYSBwYXF1ZXRlcsOtYSBgY2FyYCB5IGBjYXJEYXRhYApgYGB7cn0KbGlicmFyeShjYXIpCmxpYnJhcnkoY2FyRGF0YSkKCmxldmVuZVRlc3QocHVsbW9uYXIkUHZDTzIgfiBwdWxtb25hciREb3NpcywgCiAgICAgICAgICAgY2VudGVyID0gIm1lZGlhbiIpCgoKYGBgCkxhcyBoaXDDs3Rlc2lzIHNvbiAKLSAkSF8wJDogTGFzIHZhcmlhbnphcyBkZSBQdkNPMiAkPSQgZW4gdG9kb3MgbG9zIGdydXBvcyAqKihwLXZhbHVlID4gMC4wNSkqKgotICRIX2EkOiBMYXMgdmFyaWFuemFzIGRlIFB2Q08yICAkXG5lcSQgZW4gYWwgbWVub3MgdW4gZ3J1cG8gKioobm8gbm9ybWFsaWRhZCwgcC12YWx1ZSA8IDAuMDUpKioKClVzYW5kbyBlbCBwYXF1ZXRlIGByc3RhdGl4YApgYGB7cn0KcHVsbW9uYXIgJT4lIAogIGxldmVuZV90ZXN0KFB2Q08yIH4gRG9zaXMsIGNlbnRlciA9ICJtZWRpYW4iKQoKIyBEZjogZGVncmVlcyBvZiBmcmVlZG9tCiMgRiB2YWx1ZTogdmFsb3IgZGVsIGVzdGFkw61zdGljbyBGCiMgcDogcC12YWx1ZQpgYGAKCkRhZG8gcXVlIGVsICpwLXZhbHVlKiBlcyA+IDAuMDUgKCpwKiA9IDAuNjY3KSwgZW50b25jZXMgbGEgY29uY2x1c2nDs24gZGVsIGFuYWxpc2lzIGRlIGlndWFsZGFkIGRlIHZhcmlhbnphcyBlcyBxdWUgbGFzIHZhcmlhbnphcyBzb24gc2ltaWxhcmVzIG8gaWd1YWxlcy4gUHJvY2VkZW1vcyBhIHJlYWxpemFzIGxhIHBydWViYSBkZSBBTk9WQQoKCjMuIEFOT1ZBCkxhcyBoaXDDs3Rlc2lzIHNvbgoKLSAkSF8wOiBcbXVfMSA9IFxtdV8yID0gXG11XzMkCi0gJEhfYTogXG11XzEgXG5lcSBcbXVfMiBcbmVxIFxtdV8zJDogCgpMYSAkSF9hJCBkaWNlIHF1ZSBoYXkgZGlmZXJlbmNpYXMgZW4gbGEgbWVkaWEgZGUgUHZDTzIgZW50cmUgYWxndW5vIGRlIGxvcyBncnVwb3MgCgpgYGB7cn0KYW5vdmEgPC0gcHVsbW9uYXIgJT4lIAogIGFub3ZhX3Rlc3QoUHZDTzIgfiBEb3NpcywgZWZmZWN0LnNpemUgPSAiZ2VzIikKCmFub3ZhCmBgYApEYWRvIHF1ZSAqcCA9IDAuMjUqLCBxdWUgZXMgPiAwLjA1LCBlbnRvbmNlcyBhY2VwdGFtb3MgbGEgJEhfMCQgeSBkaXLDrWFtb3MgcXVlIG5vIGhheSBkaWZlcmVuY2lhIGVudHJlIGdydXBvcy4gSGFyZW1vcyBsYXMgcHJ1ZWJhcyBwb3N0LWhvYyAKCmBgYHtyfQphbm92YSA8LSBwdWxtb25hciAlPiUgCiAgdHVrZXlfaHNkKFB2Q08yIH4gRG9zaXMpICModmFyaWFibGUgY3VhbnRpdGF0aXZhIH4gdmFyaWFibGUgZGUgYWdydXBhbWllbnRvKQoKYW5vdmEKYGBgCgpGaW5hbG1lbnRlLCByZWFsaXphbW9zIGxhIGNvbmNsdXNpw7NuCgo1LiBMb3MgdHJhdGFtaWVudG9zIG5vIHByb3ZvY2FuIHVuYSBkaWZlcmVuY2lhIGVuIGxhIFB2Q08yIChwLXZhbHVlID0gMC4yNSkKCgojIyMgQU5PVkEgZGUgMiB2w61hcwoKRWwgYW7DoWxpc2lzIGNvbnNpc3RlCgogKioxLiBOb3JtYWxpZGFkKioKYGBge3J9CgpwdWxtb25hciAlPiUgCiAgZ3JvdXBfYnkoRG9zaXMpICU+JSAKICBzaGFwaXJvX3Rlc3QoTmV1dHJvZmlsb3MpCgpzdW1tYXJ5KHB1bG1vbmFyJEVzdGF0dXMpCgojUGFyYSBsb3MgY2Fzb3Mgbj00MCwgdXNhbW9zIGxhIGRlIFNoYXBpcm8tV2lsa3MKc2hhcGlyb190ZXN0KHB1bG1vbmFyJE5ldXRyb2ZpbG9zW3B1bG1vbmFyJEVzdGF0dXMgPT0gIkNhc2UiXSkKCgojUGFyYSBjb250cm9sZXMgbj05MCB1c2Ftb3MgbGEgZGUgbGlsbGllZm9yY2UKbGlicmFyeShub3J0ZXN0KQpsaWxsaWUudGVzdChwdWxtb25hciROZXV0cm9maWxvc1twdWxtb25hciRFc3RhdHVzID09ICJDb250cm9sIl0pCgpgYGAKUGFyYSBlbCAlIGRlIG5ldXRyb2ZpbG9zIGVzdHJhdGlmaWNhZG8gcG9yIGVzdGF0dXMgY3VtcGxlbiBsYSBub3JtYWxpZGFkLiBTSW4gZW1iYXJnbywgYWwgZXN0cmF0aWZpY2FyIHBvciBlbCB0cmF0YW1pZW50byBubyBjdW1wbGUuCgpgYGB7cn0Kbm9ybWFsaWRhZF9jb25kaWNpb25hZGEgPC0gcHVsbW9uYXIgJT4lIAogIGdyb3VwX2J5KERvc2lzLCBFc3RhdHVzKSAlPiUgCiAgc2hhcGlyb190ZXN0KE5ldXRyb2ZpbG9zKQpgYGAKCkN1YW5kbyBzZSBjb25kaWNpb25hIGVsIGFuw6FsaXNpcyBkZSBub3JtYWxpZGFkIGRlIGRvcyBmYWN0b3JlcywgZWwgJSBkZSBuZXV0csOzZmlsb3MgY3VtcGxlIGNvbiBlbCBzdXB1ZXN0byBkZSBub3JtYWxpZGFkCgogKioyLiBJZ3VhbGRhZCBkZSB2YXJpYW56YXMqKgpgYGB7cn0KcHVsbW9uYXIgJT4lIAogIGxldmVuZV90ZXN0KE5ldXRyb2ZpbG9zIH4gRXN0YXR1cywgY2VudGVyID0gIm1lYW4iKQpgYGAKYGBge3J9CnB1bG1vbmFyICU+JSAKICBsZXZlbmVfdGVzdChOZXV0cm9maWxvcyB+IERvc2lzLCBjZW50ZXIgPSAibWVkaWFuIikKYGBgCkxhIHZhcmlhbnphIGRlICUgZGUgbmV1dHLDs2ZpbG9zIGVzIGRpZmVyZW50ZSBlbnRyZSBsb3MgZ3J1cG9zIChFc3RhdHVzKS4gTGEgdmFyaWFuemEgZGUgbmV1dHLDs2ZpbG9zIGVzIGlndWFsIGVudHJlIGdydXBvcyAoRG9zaXMpIAogCiAqKjMuIEFOT1ZBKiogCmBgYHtyfQpyZXN1bHRfMnZpYXMgPC0gcHVsbW9uYXIgJT4lIAogIGFub3ZhX3Rlc3QoTmV1dHJvZmlsb3MgfiBEb3NpcypFc3RhdHVzLCAKICAgICAgICAgICAgIGVmZmVjdC5zaXplID0gImdlcyIpICNFdGEgY3VhZHJhZGEgZ2VuZXJhbGl6YWRhCmBgYAoKQ29uIGVsIGFuw6FsaXNpcyBwb3N0LWhvYwpgYGB7cn0KcHBzdF8ydmlhcyA8LXB1bG1vbmFyICU+JSAKICB0dWtleV9oc2QoTmV1dHJvZmlsb3MgfiBEb3NpcypFc3RhdHVzKQpgYGAKIAogKio0LiBDb25jbHVzacOzbioqCkFuYWxpemFuZG8gbG9zIHN1YmdydXBvcyBkb3NpczpjYXNvcyBzZSBlbmNvbnRyw7MgcXVlIGFsIGNvbXBhcmFyIGxvcyBtw7psdGlwbGVzIHRyYXRhbWllbnRvcyBlbnRyZSBjYXNvcyB5IGNvbnRyb2xlcywgbm8gaGF5IGRpZmVyZW5jaWFzIHNpZ25pZmljYXRpdmFzLiBBbCByZWFsaXphciB1biBhbsOhbGlzaXMgaW5kaXZpZHVhbCBkZSBjYWRhIGZhY3Rvciwgc2UgZGV0ZWN0w7MgdW5hIGdyYW4gZGlmZXJlbmNpYSBlbiBlbCBmYWN0b3IgKipFc3RhdHVzICoqICgqcCogPCAwLjAxLCBnZXMgPSAwLjY2KSwgcG9yIGxvIHF1ZSBzZSBzb3NwZWNoYSBxdWUgbGFzIGRpZmVyZW5jaWFzIHF1ZSBwdWVkYW4gZW5jb250cmFyc2Ugc29uIGF0cmlidWlibGVzIGFsIGZhY3RvciAqKkVzdGF0dXMqKi4KCgoKIyMjIEFOT1ZBIGRlIDIgdsOtYXMgZGUgbWVkaWRhcyByZXBldGlkYXMKCkNhcmdhbW9zIGxhIGJhc2UgZGUgZGF0b3MgeSBjb252ZXJ0aW1vcyBsYXMgdmFyaWFibGVzIGNhcmFjdGVyIGEgZmFjdG9yLiAKCmBgYHtyfQpzZWd1aW1pZW50byA8LSByZWFkLmNzdigiL2Nsb3VkL3Byb2plY3QvQmlvZXN0YWRpc3RpY2FfUi9TZWd1aW1pZW50b190ZXJhcGlhLmNzdiIpCmhlYWQoc2VndWltaWVudG8pCnN0cihzZWd1aW1pZW50bykKCnNlZ3VpbWllbnRvJFRpZW1wbyA8LSBhcy5mYWN0b3Ioc2VndWltaWVudG8kVGllbXBvKQpzZWd1aW1pZW50byRTZXhvIDwtIGFzLmZhY3RvcihzZWd1aW1pZW50byRTZXhvKQpzZWd1aW1pZW50byRGdW1hZG9yZXMgPC0gYXMuZmFjdG9yKHNlZ3VpbWllbnRvJEZ1bWFkb3JlcykKc2VndWltaWVudG8kVGllbXBvIDwtIGFzLmZhY3RvcihzZWd1aW1pZW50byRUaWVtcG8pCnNlZ3VpbWllbnRvJFBhZHJlcy5mdW1hZG9yZXMgPC0gYXMuZmFjdG9yKHNlZ3VpbWllbnRvJFBhZHJlcy5mdW1hZG9yZXMpCnNlZ3VpbWllbnRvJFByb2JsZW1hcy5jYXJkaWFjb3MgPC0gYXMuZmFjdG9yKHNlZ3VpbWllbnRvJFByb2JsZW1hcy5jYXJkaWFjb3MpCnNlZ3VpbWllbnRvJENIUk5BNSA8LSBhcy5mYWN0b3Ioc2VndWltaWVudG8kQ0hSTkE1KQpzZWd1aW1pZW50byRyczU3ODc3NiA8LSBhcy5mYWN0b3Ioc2VndWltaWVudG8kcnM1Nzg3NzYpCgpzdHIoc2VndWltaWVudG8pCmBgYApIYXkgcXVlIGFzZWd1cmFybm9zIHF1ZSBlc3TDoW4gdG9kb3MgbG9zIGRhdG9zIGNvbXBsZXRvcyBkZSBsYSBiYXNlIGRlIGRhdG9zLiBFcyBkZWNpciwgcXVlIGxhICpuKiBzZWEgbGEgbWlzbWEgcGFyYSB0b2RvcyBsb3MgdGllbXBvcyAuCgpBbmFsaWNlbW9zIGxhIG5vcm1hbGlkYWQKYGBge3J9CnJlc3VsdGFkb3NfNCA8LSBzZWd1aW1pZW50byAlPiUgCiAgZ3JvdXBfYnkoVGllbXBvKSAlPiUgCiAgc2hhcGlyb190ZXN0KENpZ2Fycm9zLmFsLmTDrWEpCgpyZXN1bHRhZG9zXzQKYGBgCkxvcyB2YWxvcmVzIGRlICoqTWFudGVuaW1pZW50byoqIG5vIHByb3ZpZW5lbiBkZSB1bmEgZGlzdHJpYnVjacOzbiBub3JtYWwuIEVuIGVzdGUgY2Fzbywgbm8gcG9kcsOtYW1vcyBjb250aW51YXIgY29uIGxhIEFOT1ZBIHkgdGVuZHLDrWFtb3MgcXVlIGhhY2VyIG90cmEgcHJ1ZWJhIG5vIHBhcmFtw6l0cmljYS4gU2luIGVtYmFyZ28sIHZhbW9zIGEgY29udGludWFyIGNvbiBsYSBBTk9WQS4KCkFob3JhIHJlYWxpY2Vtb3MgbGEgcHJ1ZWJhIGRlIGlndWFsZGFkIGRlIHZhcmlhbnphcy4gUHJ1ZWJhIGRlIExldmVuZQpgYGB7cn0Kc2VndWltaWVudG8gJT4lIAogIGxldmVuZV90ZXN0KENpZ2Fycm9zLmFsLmTDrWEgfiBUaWVtcG8sIGNlbnRlciA9ICJtZWRpYW4iKQpgYGAKRGFkbyBlbCB2YWxvciBkZSAqcCogPiAwLjA1IChwLXZhbHVlID0gMC4xNDMpLCBwb2RlbW9zIGRlY2lyIHF1ZSBsYXMgdmFyaWFuemFzIHNvbiBpZ3VhbGVzL3NpbWlsYXJlcyBlbiB0b2RvcyBsb3MgZ3J1cG9zLgoKUGxhbnRlZW1vcyBsYSBwcnVlYmEgZGUgQU5PVkEKCmBgYHtyfQpzZWd1aW1pZW50byAlPiUgCiAgYW5vdmFfdGVzdChkdiA9IENpZ2Fycm9zLmFsLmTDrWEsIAogICAgICAgICAgICAgd2lkID0gQ2xhdmUsCiAgICAgICAgICAgICB3aXRoaW4gPSBUaWVtcG8pCmBgYAoKWWEgcXVlIGVsIHZhbG9yIGRlICpwKiBkZSBBTk9WQSBlcyA8IDAuMDUgKHAtdmFsdWUgPSAyLjA2ZS0xMSksIHBvZGVtb3MgZGVjaXIgcXVlIGhheSBkaWZlcmVuY2lhcyBlbnRyZSBncnVwb3MuCgpQYXJhIGVsIHRlc3QgZGUgZXNmZXJpY2lkYWQKCi0gJEhfMCQ6IGxvcyBkYXRvcyBubyB2YXJpYW4gZW4gY2FkYSBuaXZlbCBkZSB0aWVtcG8gKHAgPjAuMDUpCi0gJEhfYSQ6IGxvcyBkYXRvcyB2YXLDrWFuIGVuIGNhZGEgbml2ZWwgZGUgdGllbXBvIChwIDwgMC4wNSkKClNpIG5vIHNlIGN1bXBsZSBlbCBzdXB1ZXN0byBkZSBlc2ZlcmljaWRhZCBwb3IgZWwgdGVzdCBkZSBNYXVjaGx5LCBoYWJyw6EgcXVlIHZlciBsb3MgZGF0b3MgZGUgbGEgY29ycmVjY2nDs24gZGUgZXNmZXJpY2lkYWQuIEhhYnLDoSBxdWUgY29uc2lkZXJhciBlbCB2YWxvciBkZSAqcCogZGUgcFtHR10gZGVsIHRlc3QgZGUgY29ycmVjY2nDs24gZGUgZXNmZXJpY2lkYWQgY29tbyBlbCBxdWUgc2UgdGllbmUgcXVlIHJlcG9ydGFyIGRlIGxhIHBydWViYSBkZSBBTk9WQS4KCkhhc3RhIGFxdcOtIGNvbmNsdWltb3MgcGFyY2lhbG1lbnRlIHF1ZSBleGlzdGUgZGlmZXJlbmNpYSBlbiBlbCBuw7ptZXJvIGRlIGNpZ2Fycm9zIHBvciBkw61hIGRlc3B1w6lzIGRlIGNhZGEgZXRhcGEgZGUgdHJhdGFtaWVudG8uCgojIzYuIFBydWViYXMgbm8gcGFyYW3DqXRyaWNhcwoKMS4gU29uIHBydWViYXMgcGFyYSBjb21wYXJhciBkYXRvcyBjb24gZGlzdHJpYnVjaW9uZXMgbm8gbm9ybWFsZXMKMi4gTm8gc29uIHNlbnNpYmxlcyBhIGRhdG9zIGV4dHJlbW9zIGF0w61waWNvcywgcG9yIGxvIHF1ZSBzdSBwcmVzZW5jaWEgbm8gYWZlY3RhcsOhIGVsIGFuw6FsaXNpcyBlbiBjb25qdW50bwozLiBGdW5kYW1lbnQ6IG9yZGVuYSBsb3MgZGF0b3MgZGUgbWVub3IgYSBtYXlvciwgIGNhZGEgZGF0byBzZSBsZSBhc2lnbmEgcG9zaWNpw7NuIHkgdW4gdmFsb3IgbnVtw6lyaWNvIChyYW5nbyBkZSAxIGEgKm4qKQoKLSBDb21wYXJhY2lvbmVzIGN1YW50aXRhdGl2YXM6IHBydWViYSBkZSBXaWxjb3hvbiwgVSBkZSBNYW5uLVdoaXRuZXksIEtydXNrYWwtV2FsbGlzLCBGcmllZG1hbgotIENvbXBhcmFjaW9uZXMgY3VhbGl0YXRpdmFzOiBjaGkgY3VhZHJhZGEsIEZpc2hlcgoKYGBge3J9CnNlc2lvbl82IDwtIHJlYWQuY3N2KCIvY2xvdWQvcHJvamVjdC9CaW9lc3RhZGlzdGljYV9SL2RhdG9zXzEuY3N2IikKc3RyKHNlc2lvbl82KQoKc2VzaW9uXzYkRXhwb3NpY2lvbiA8LSBhcy5mYWN0b3Ioc2VzaW9uXzYkRXhwb3NpY2lvbikKc3RyKHNlc2lvbl82KQpgYGAKSGF5IHF1ZSBwcm9iYXIgbGEgYXNpbWV0csOtYSAoc2tldykKYGBge3J9CmxpYnJhcnkocHN5Y2gpCnNrZXcoc2VzaW9uXzYkQ290aW5pbmFbc2VzaW9uXzYkRXhwb3NpY2lvbj09ICJTaW4gZXhwb3NpY2lvbiJdKQpza2V3KHNlc2lvbl82JENvdGluaW5hW3Nlc2lvbl82JEV4cG9zaWNpb249PSAiRnVtYWRvciJdKQpza2V3KHNlc2lvbl82JENvdGluaW5hW3Nlc2lvbl82JEV4cG9zaWNpb249PSAiVmFwZWFkb3IiXSkKc2tldyhzZXNpb25fNiRDb3RpbmluYVtzZXNpb25fNiRFeHBvc2ljaW9uPT0gIkh1bW8gZGUgYmlvbWFzYSJdKQpgYGAKQXNpbWV0csOtYSA8IC0yIG8gPiAyIHlhIGVyYSB1bmEgc2ltZXRyw61hIG11eSBtYXJjYWRhLiBTZSBlc3BlcmEgdGVuZXIgdW5hIGFzaW1ldHLDrWEgPSAwIChzaW3DqXRyaWNvcyksIG8gY3VhbmRvICRhIFxuZXEgMCQgKGFzaW3DqXRyaWNvcyApCgpSZWFsaXphbW9zIGVsIGFuw6FsaXNpcyBkZSBpZ3VhbGRhZCBkZSB2YXJpYW56YXMKYGBge3J9CnNlc2lvbl82ICU+JSAKICBsZXZlbmVfdGVzdChDb3RpbmluYSB+IEV4cG9zaWNpb24sIGNlbnRlciA9ICJtZWRpYW4iKQpgYGAKYGBge3J9CmJveHBsb3Qoc2VzaW9uXzYkQ290aW5pbmEgfiBzZXNpb25fNiRFeHBvc2ljaW9uKQoKcCA8LSBnZ3Bsb3Qoc2VzaW9uXzYsIG1hcHBpbmcgPSBhZXMoeD1FeHBvc2ljaW9uLCB5ID0gQ290aW5pbmEsIGZpbGwgPSBFeHBvc2ljaW9uKSkKcCArIGdlb21fYm94cGxvdCgpCmBgYAoKUmVhbGl6YW1vcyBsYSBwcnVlYmEgZGUgVSBkZSBNYW5uLVdoaXRuZXkgcGFyYSBkYXRvcyBubyByZWxhY2lvbmFkb3MKYGBge3J9CnNlc2lvbl82ICU+JSAKICB3aWxjb3hfdGVzdChDb3RpbmluYSB+IEV4cG9zaWNpb24sCiAgICAgICAgICAgICAgcmVmLmdyb3VwID0gIlNpbiBleHBvc2ljaW9uIiwKICAgICAgICAgICAgICBwLmFkanVzdC5tZXRob2QgPSAiYm9uZmVycm9uaSIsCiAgICAgICAgICAgICAgZXhhY3QgPSBUUlVFLAogICAgICAgICAgICAgIHBhaXJlZCA9IEZBTFNFLAogICAgICAgICAgICAgIGNvbmYubGV2ZWwgPSAwLjk1LAogICAgICAgICAgICAgIGFsdGVybmF0aXZlID0gInR3by5zaWRlZCIpCmBgYApFeGlzdGUgZGlmZXJlbmNpYSBlbnRyZSBlbCBncnVwbyBzaW4gZXhwb3NpY2nDs24gdnMgZnVtYWRvcmVzIChzZSByZXBvcnQgZWwgcC5hZGouc2lnbmlmKSwgeSBzaW4gZXhwb3NpY2lvbiB2cyB2YXBlYWRvcmVzLgoKTGEgcHJ1ZWJhIHBvc3QtaG9jIGRlIEtydXNrYWwtV2FsbGlzIGVzIGxhIHNpZ3VpZW50ZQpgYGB7cn0Kc2VzaW9uXzYgJT4lIAogIHBhaXJ3aXNlX3dpbGNveF90ZXN0KENvdGluaW5hIH4gRXhwb3NpY2lvbiwKICAgICAgICAgICAgICAgICAgICAgICBwLmFkanVzdC5tZXRob2QgPSAiYm9uZmVycm9uaSIsCiAgICAgICAgICAgICAgICAgICAgICAgcGFpcmVkID0gRkFMU0UsCiAgICAgICAgICAgICAgICAgICAgICAgY29uZi5sZXZlbCA9IDAuOTUsCiAgICAgICAgICAgICAgICAgICAgICAgYWx0ZXJuYXRpdmUgPSAidHdvLnNpZGVkIikKYGBgCgpQYXJhIHJlc3BvbmRlciBhIGxhIHByZWd1bnRhIGRlIHF1w6kgdGFuIGdyYW5kZSBlcyBsYSBkaWZlcmVuY2lhLCBlbnRvbmNlcyBjYWxjdWxhbW9zIGVsIHRhbWHDsW8gZGVsIGVmZWN0byAKYGBge3J9Cmluc3RhbGwucGFja2FnZXMoJ2NvaW4nKQpzZXNpb25fNiAlPiUgCiAgd2lsY294X2VmZnNpemUoQ290aW5pbmEgfiBFeHBvc2ljaW9uKQpgYGAKCkxvcyBuaXZlbGVzIGRlIGNvdGluaW5hIHZhcsOtYW4gZW50cmUgZWwgZ3J1cG8gc2luIGV4cG9zaWNpw7NuIHZzIGZ1bWFkb3JlcyAocCA9IDMuMTllLTE2KSB5IHNpbiBleHBvc2ljacOzbiB2cyB2YXBlYWRvcmVzLiBMYXMgZGlmZXJlbmNpYXMgZW5jb250cmFkYXMgc29uIGdyYW5kZXMgc2Vnw7puIGxhIHBydWViYSBkZWwgdGFtYcOxbyBkZWwgZWZlY3RvLgoKUmV2aXNlbW9zIGNvbW8gc2UgY29tcGFyYW4gZW50cmUgdG9kb3MgbG9zIGdydXBvcyBlbiBsb3Mgbml2ZWxlcyBkZSBDb3RpbmluYS4gUmVhbGl6YW1vcyBsYSBwcnVlYmEgZGUgS3J1c2thbC1XYWxsaXMKYGBge3J9CnNlc2lvbl82ICU+JSAKICBrcnVza2FsX3Rlc3QoQ290aW5pbmEgfiBFeHBvc2ljaW9uKQpgYGAKTm8gc2FiZW1vcyBlbnRyZSBxdWUgZ3J1cG9zIGhheSBkaWZlcmVuY2lhLCBwZXJvIHNpIGV4aXN0ZSBwb3JxdWUgZWwgdmFsb3IgZGUgcCBhcnJvamFkbyBwb3IgbGEgcHJ1ZWJhIGRlIEtydXNrYWwtV2FsbGlzIChwID0gMi4yN2UtMzIpLiBQYXJhIHNhYmVyIGVudHJlIHF1ZSBncnVwb3MgaGFjZW1vcyBsYSBwcnVlYmEgcG9zdC1ob2MgZGUgZHVubgoKYGBge3J9CnNlc2lvbl82ICU+JSAKICBkdW5uX3Rlc3QoQ290aW5pbmEgfiBFeHBvc2ljaW9uKSAKYGBgCk8gaGFjZW1vcyB1bmEgcHJ1ZWJhIHBvc3QtaG9jIApgYGB7cn0Kc2VzaW9uXzYgJT4lIAogIHBhaXJ3aXNlX3dpbGNveF90ZXN0KENvdGluaW5hIH4gRXhwb3NpY2lvbiwKICAgICAgICAgICAgICAgICAgICAgICBwLmFkanVzdC5tZXRob2QgPSAiYm9uZmVycm9uaSIsCiAgICAgICAgICAgICAgICAgICAgICAgZXhhY3QgPSBUUlVFLAogICAgICAgICAgICAgICAgICAgICAgIHBhaXJlZCA9IEZBTFNFLAogICAgICAgICAgICAgICAgICAgICAgIGNvbmYubGV2ZWwgPSAwLjk1LAogICAgICAgICAgICAgICAgICAgICAgIGFsdGVybmF0aXZlID0gInR3by5zaWRlZCIpCmBgYAoKUGFyYSB2ZXIgZWwgdGFtYcOxbyBkZWwgZWZlY3RvCmBgYHtyfQpzZXNpb25fNiAlPiUgCiAga3J1c2thbF9lZmZzaXplKENvdGluaW5hIH4gRXhwb3NpY2lvbikKYGBgCgojIyMgUHJ1ZWJhcyBjdWFsaXRhdGl2YXMKClJlYWxpY2Vtb3MgdW5hIHRhYmxhIGRlIGNvbnRpbmdlbmNpYSAocHJpbmNpcGFsbWVudGUgcGFyYSB2YXJpYWJsZXMgY3VhbGl0YXRpdmFzKQpgYGB7cn0KZXN0YXR1c19zZXhvIDwtIHRhYmxlKHNlc2lvbl82JFNleG8sIHNlc2lvbl82JEVzdGF0dXMsCiAgICAgICAgICAgICAgICAgICAgICBkbm4gPSBjKCJTZXhvIiwgIkVzdGF0dXMiKSkKZXN0YXR1c19zZXhvCmBgYApVbmEgdGFibGEgZGUgbngyIChQcmltZXJvIGZpbGFzLCBsdWVnbyBjb2x1bW5hcykKYGBge3J9CmVzdGF0dXNfZXhwb3NpY2lvbiA8LSB0YWJsZShzZXNpb25fNiRFeHBvc2ljaW9uLCBzZXNpb25fNiRFc3RhdHVzLAogICAgICBkbm4gPSBjKCJFeHBvc2ljaW9uIiwgIkVzdGF0dXMiKSkKCmV4cG9zaWNpb25fc2V4byA8LSB0YWJsZShzZXNpb25fNiRTZXhvLCBzZXNpb25fNiRFeHBvc2ljaW9uLAogICAgICBkbm4gPSBjKCJTZXhvIiwgIkV4cG9zaWNpb24iKSkKYGBgClBhcmEgcmVhbGl6YXIgbGEgcHJ1ZWJhCmBgYHtyfQpmaXNoZXIudGVzdChlc3RhdHVzX3NleG8pCmVzdGF0dXNfc2V4bwoKY2hpc3EudGVzdChlc3RhdHVzX2V4cG9zaWNpb24pCmBgYApjdWFuZG8gcCA+IDAuMDUgbGEgZnJldWN1ZW5jaWEgZGUgZGF0b3MgZXMgaWd1YWwsIGN1YW5kbyBwIDwgMC4wNSBsYSBmcmVjdWVuY2EgZGUgZGF0b3MgZXMgZGlmZXJlbnRlCg==