Como ya mencionamos, otra forma de analizar datos adicional al uso de estadística descriptiva e inferencial, es la de visualizar información mediante gráficos.

Antes de iniciar, carguemos nuestras librerías:

library(tidyverse)
library(lubridate)

Adicionalmente, importemos nuestro dataframe de sesiones web que se encuentra en el link https://raw.githubusercontent.com/jsaraujo5081/clasesBI/main/sesiones_web.csv.

df_sesiones <- read_csv(file = "https://raw.githubusercontent.com/jsaraujo5081/clasesBI/main/sesiones_web.csv",
                          col_names = TRUE)

-- Column specification -------------------------------------------------------
cols(
  Usuario_IP = col_character(),
  Fecha = col_date(format = ""),
  DiaSemana = col_character(),
  Paginas = col_double(),
  Duracion_Min = col_double(),
  Num_Sesion = col_double(),
  Tipo_Dispositivo = col_character(),
  Dispositivo = col_character(),
  Navegador = col_character(),
  TipoFuente_Analisis = col_character(),
  Hora = col_double(),
  HoraEC = col_double()
)

Y como complemento importamos también una tabla que se encuentra en el link https://drive.google.com/file/d/1LbfeWrzKJzxGRZffBt6jr5zier3ddpVJ/view?usp=sharing, y que contiene la geolocalización de distintas IP alrededor del mundo.

Para esta importación descarguemos el archivo existente en el link y luego, definiendo el directorio pertienente donde se aloja este archivo importémoslo usando la funcion readRDS.

setwd("C:/Users/jsara/OneDrive/Documentos/UHemisferios/BI 2021-2/Datasets") #Este comando depende de cada persona y de donde fue guardado el archivo descargado
df_geo <- readRDS(file = "usuarios_geo(1).rds")

Los archivos RDS como el que vemos aquí, son aquellos que guardan objetos propios de R para su uso posterior.

Ahora bien, antes de iniciar con los gráficos aprendamos a usar una nueva función del tidyverse, equivalente a la familia de lookup (buscar) de Excel, que nos permitirá unir dos tablas sobre la base de un campo de referencia común: el inner_join. Para usarla, veamos en primer lugar nuestras tablas, e identifiquemos el campo que debemos usar para la unión.

View(df_sesiones)
View(df_geo)

Resulta evidente que en df_sesiones y en df_geo los campos comunes son “Usuario_IP” y “Usuario”, respectivamente. Por tanto, usémoslos como referencia para la unión creando un nuevo objeto df_ses_geo.

df_ses_geo <- df_sesiones %>%
  inner_join(df_geo,
             by = c("Usuario_IP" = "Usuario"))

Veamos la estructura de nuestra tabla resultante, y verifiquemos que unimos de forma adecuada.

glimpse(df_ses_geo)
Rows: 174,313
Columns: 19
$ Usuario_IP          <chr> "1.0.149.137", "1.0.197.59", "1.0.197.59", "1.0.21~
$ Fecha               <date> 2020-06-15, 2020-04-27, 2020-04-27, 2020-02-23, 2~
$ DiaSemana           <chr> "1.Lu", "1.Lu", "1.Lu", "7.Do", "3.Mi", "2.Ma", "2~
$ Paginas             <dbl> 2, 1, 1, 1, 1, 1, 1, 1, 1, 4, 2, 1, 2, 1, 1, 1, 2,~
$ Duracion_Min        <dbl> 0.003888900, 0.119444450, 0.119444450, 1.277129633~
$ Num_Sesion          <dbl> 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, 3, 1, 1, 1, 1,~
$ Tipo_Dispositivo    <chr> "Escritorio", "Movil", "Movil", "Movil", "Escritor~
$ Dispositivo         <chr> "PC", "Android", "Android", "iPhone", "PC", "Mac",~
$ Navegador           <chr> "Google Chrome", "Google Chrome", "Google Chrome",~
$ TipoFuente_Analisis <chr> "SearchEngine", "SearchEngine", "SearchEngine", "S~
$ Hora                <dbl> 8, 7, 7, 15, 12, 21, 17, 14, 18, 6, 19, 20, 20, 16~
$ HoraEC              <dbl> 20, 19, 19, 3, 0, 9, 3, 0, 3, 15, 5, 5, 6, 1, 1, 4~
$ ZIP                 <chr> "91130", "84160", "84160", "80130", "10200", "1020~
$ Ciudad              <chr> "Khuan Don", "Kanchanadit", "Kanchanadit", "Ron Ph~
$ CiudadRegion        <chr> "Khuan Don.Satun", "Kanchanadit.Surat Thani", "Kan~
$ Region              <chr> "Satun", "Surat Thani", "Surat Thani", "Nakhon Si ~
$ Pais                <chr> "Tailandia", "Tailandia", "Tailandia", "Tailandia"~
$ Lat                 <dbl> 6.78758, 9.16611, 9.16611, 8.17911, 13.75000, 13.7~
$ Lon                 <dbl> 100.0780, 99.4701, 99.4701, 99.8542, 100.5170, 100~

PUNTOS EXTRAS: adicional al inner_join existen otras funciones similares como son left_join, right_join y full_join. El primer estudiante que envíe por correo electrónico una explicación con las diferencias entre éstas recibirá 5 puntos para la evaluación final.

Empecemos entonces con nuestras visualizaciones usando este data frame con datos de las sesiones y de geolocalización.

1. Gramática de gráficos

Para empezar, entendamos lo que es la gramática de gráficos, para lo cual usaremos una sub-librería del tidyverse llamada ggplot2.

Desde la perspectiva de este curso, entenderemos un gráfico es un objeto (así como lo son los data frames, los vectores y las variables) que posee como principal característica una estructura aditiva definida conforme lo que llamamos gramática de gráficos.

Como primer componente de dicha estructura están los DATOS con los que se va a trabajar. En el caso que estamos estudiando, los datos se corresponden con nuestro data frame df_ses_geo. Construyamos nuestro primer gráfico graf_01 identificando este componente y usando para esto la función ggplot y veamoslo en nuestra área de visualización.

graf_01 <- ggplot(data = df_ses_geo)
graf_01

Como se puede apreciar, nuestro gráfico todavía no tiene nada que “ver”. Agreguemos entonces el siguiente componente que son las ESTÉTICAS, las cuales toman variables en los datos y nos van a permitir evidenciar características específicas de los gráficos (por ejemplo, los ejes). Para esto, usemos el parámetro adicional “mapping” de la función ggplot, y creemos un nuevo objeto graf_02.

graf_02 <- ggplot(data = df_ses_geo,
                  mapping = aes(x = HoraEC))
graf_02

Hemos conseguido, “ver” algo nuevo ahora como es la estética del eje horizontal “x”. Lo que tenemos que tenemos que agregar ahora es el contenido del gráfico, lo cual viene representado específicamente por lo que se conoce como la GEOMETRÍA. Usemos entonces como ejemplo una función que nos permita visualizar un conteo de casos de las sesiones por hora aplicando la función geom_bar, a fin de saber cuales son las horas de Ecuador con mayor tráfico web. Creemos el gráfico nuevo a partir del anterior.

graf_02 + geom_bar()

Una vez que tenemos el gráfico deseado, el último componente relevante de la gramática es el de los FORMATOS, lo cual se asocia a todos aquellos elementos que permiten una visualización más agradable para el usuario final (colores, tipos de letra, nombres, etc.).

Creemos entonces un nuevo gráfico graf_03 que incluya también la capa de formato. En concreto, quisiéramos lo siguiente:

graf_03 <- graf_02 +
  geom_bar(fill = "blue",
           color = "darkblue") +
  xlab("Hora Ecuador") +
  ylab("Conteo") +
  ggtitle("Comportamiento de sesiones por hora") +
  theme_classic()

graf_03

NA

Hemos hecho nuestro primer gráfico, sobre la base de la lógica dada por la gramática de gráficos. Notemos que los formatos son el último punto que hemos tomado en cuenta, pues antes que eso debemos siempre preocuparnos de saber qué datos son los que voy a analizar, cómo los quiero visualizar, y qu+e tipo de gráfico necesito.

Empecemos entonces a ver algunos tipos de gráficos que tenemos disponibles.

2. Gráficos Univariables

2.1 Barras

Al momento de entender la gramática, hemos ya visto la sintaxis de los gráficos de barra, los cuales nos permiten conocer el conteo de casos que existen para una variable en específico.

Volvamos a repasar esto, intentando conocer cuál la provincia de Ecuador que tiene más sesiones. Creemos un objeto llamado bar_prov. No nos preocupemos por ahora de los formatos, sino de los otros componentes.

df_ecu <- df_ses_geo %>%
  filter(Pais == "Ecuador")

bar_prov <- ggplot(data = df_ecu,
                     mapping = aes(x = Region)) +
  geom_bar()

bar_prov

Arreglemos los formatos, de forma que al menos:

  • Los nombres de las provincias se encuentren de forma vertical.
  • No existan nombres de ejes.
  • El gráfico tenga título.
bar_prov <- ggplot(data = df_ecu,
                     mapping = aes(x = Region)) +
  geom_bar() +
  xlab("") +
  ylab("") +
  ggtitle("Sesiones por provincia (EC)") +
  theme(axis.text.x = element_text(angle = 90))

bar_prov

Notamos que los nombres en algunos casos están equivocados, podriamos corregir esto quitando la expresión “Provincia de[l]” usando la función conocida str_remove. También podríamos sustituir los símbolos extraños como guiones por espacios, y arreglar “Manab??”.

df_ecu <- df_ses_geo %>%
  filter(Pais == "Ecuador") %>%
  mutate(Region = str_remove(Region, "Provincia de[l]{0,1} ")) %>%
  mutate(Region = str_replace(Region, "-", " ")) %>%
  mutate(Region = str_replace(Region, "\\?\\?", "i"))

bar_prov <- ggplot(data = df_ecu,
                     mapping = aes(x = Region)) +
  geom_bar(fill = "tomato",
           color = "red") +
  xlab("") +
  ylab("") +
  ggtitle("Sesiones por provincia (EC)") +
  theme_classic() +
  theme(axis.text.x = element_text(angle = 90))

bar_prov

2.2 Histograma

Los histogramas cumplen la misma función que las barras, con la diferencia que los primeros sirven para visualizar variables numéricas decimales y sus respectivos conteos.

Construyamos un histograma que nos permita visualizar el comportamiento de la duración de sesiones. Utilizemos ahora la función geom_histogram para crear el objeto hist_dur. Recordemos que no tiene sentido ver duraciones de 0, por lo que las podemos quitar.

df_dur <- df_ses_geo %>%
  filter(Duracion_Min > 0)

hist_dur <- ggplot(data = df_dur,
                   mapping = aes(x = Duracion_Min)) +
  geom_histogram()

hist_dur

Apliquemos ahora formatos que incluyan al menos:

  • Título del gráfico.
  • Nombres de eje x pero no del vertical.
  • Fondo del gráfico limpio o en blanco.
  • Escala del eje x en logaritmo para una mejor visualización.
hist_dur <- ggplot(data = df_dur,
                   mapping = aes(x = Duracion_Min)) +
  geom_histogram(fill = "yellow",
                 color = "goldenrod") +
  xlab("Duración en Minutos (log)") +
  ylab("") +
  ggtitle("Distribución de Duración de Sesiones") +
  theme_classic() +
  scale_x_log10()

hist_dur

2.3 Densidad

En muchas ocasiones, los histogramas puedes llegar a generar confusión dada la gran cantidad de barras que se generan, por lo que suele emplearse un gráfico de densidad a fin de mostrar de una manera simplificada el comportamiento de una variable.

Como muestra de esto, veamos nuevamente la distribución de la duración de las sesiones, pero ahora cambiemos la geometría a geom_density en un objeto llamado dens_dur.

dens_dur <- ggplot(data = df_dur,
                   mapping = aes(x = Duracion_Min)) +
  geom_density()

dens_dur

Apliquemos nuevamente formatos para una mejor visualización:

dens_dur <- ggplot(data = df_dur,
                   mapping = aes(x = Duracion_Min)) +
  geom_density(fill = "yellow",
               color = "goldenrod") +
  xlab("Duración en Minutos (log)") +
  ylab("") +
  ggtitle("Distribución de Duración de Sesiones") +
  theme_classic() +
  scale_x_log10()

dens_dur

Notemos que la información que entrega el histograma y la densidad, y que está asociada al comportamiento de la variable “Duracion_Min” es equivalnete. Por lo que este gráfico resulta una alternativa válida, en caso de estimar conveniente por parte del analista.

2.4 Cajones (con bigotes)

Otro gráfico muy usado a nivel de análisis de una variable, es el cajón con bigotes, En el cual podemos visualizar de forma inmediata, medidas de centralidad y de posición de una variable, así como también los potenciales datos atípicos que existan.

Sigamos estudiando la duración de las sesiones, pero en esta ocasión hagamos un cajón de la duración para aquellas sesiones cuyo país sea Estados Unidos. Utilicemos la función geom_boxplot y creemos el objeto box_dur.

df_eeuu <- df_ses_geo %>%
  filter(Pais == "Estados Unidos") %>%
  filter(Duracion_Min > 0)

box_dur <- ggplot(data = df_eeuu,
                   mapping = aes(x = Duracion_Min)) +
  geom_boxplot()

box_dur

Como se puede evidenciar, los potenciales casos atípicos, generan confusión, por lo que sería preferible quitarlos y volver a graficar. Apliquemos la regla de exclusión de 1.5 rangos intercuartiles para quitarlos de nuestro cajón, recordando que un valor x es outlier si

\[ x > cuartil_3 + 1.5*IQR \]

df_eeuu <- df_ses_geo %>%
  filter(Pais == "Estados Unidos") %>%
  filter(Duracion_Min > 0) %>%
  filter(Duracion_Min <= quantile(Duracion_Min,0.75) + 1.5*IQR(Duracion_Min))

box_dur <- ggplot(data = df_eeuu,
                   mapping = aes(x = Duracion_Min)) +
  geom_boxplot()

box_dur

Terminemos entonces con el formato.

box_dur <- ggplot(data = df_eeuu,
                   mapping = aes(x = Duracion_Min)) +
  geom_boxplot(fill = "orange",
               color = "black") +
  xlab("Duración en Minutos") +
  ggtitle("Duración de Sesiones de Estados Unidos") +
  theme_classic() +
  theme(axis.text.y = element_blank())

box_dur

3. Gráficos Bivariables

Una vez que hemos desarrollado visualizaciones con una sola variable, pasemos ahora a estudiar los gráficos de dos variables. Este tipo de gráficos son muy útiles para relacionar el comportamiento de atributos específicos y ser el primer paso para un análisis de causalidad, así como para otro tipo de análisis como las segmentaciones.

3.1 Dispersión

Los diagramas de dispersión, comúnmente denominados gráficos “de puntos”, permiten visualizar pares de coordenadas \(x,y\) donde cada uno de éstos representa un registro dentro de mis datos.

Para ejemplificar, estudiemos el comportamiento de las páginas visitadas con la duración de las sesiones, filtrando solamente las primeras sesiones de cada usuario de Italia y Alemania, y excluyendo también aquellas con duraciones de 0. Usemos para esto la geometría geom_point.

df_disp <- df_ses_geo %>%
  filter(Num_Sesion == 1) %>%
  filter(Pais == "Alemania" | Pais == "Italia") %>%
  filter(Duracion_Min > 0)

disp_durpag <- ggplot(data = df_disp,
                      mapping = aes(x = Duracion_Min,
                                    y = Paginas)) +
  geom_point()

disp_durpag

De forma, general se puede avidenciar que gran parte de los datos se encuentran en un intervalo de tiempo entre 0 y 1 minuto y con páginas ebtre 1 y 15. Para mejorar la visibilidad de la información usemos un reescalamiento, pero esta vez solamente ajustemos el rango de los ejes con las funciones xlim y ylim.

disp_durpag <- ggplot(data = df_disp,
                      mapping = aes(x = Duracion_Min,
                                    y = Paginas)) +
  geom_point() +
  ylim(c(0,10)) +
  xlim(c(0,1))

disp_durpag

Pongamos ahora los formatos que necesitemos a nuestro gráfico.

disp_durpag <- ggplot(data = df_disp,
                      mapping = aes(x = Duracion_Min,
                                    y = Paginas)) +
  geom_point(color = "tomato",
             alpha = 0.75) + #Este parametro permite dar transparencia
  ylim(c(0,10)) +
  xlim(c(0,1)) +
  ylab("Paginas visitadas") +
  xlab("Duración en Minutos") +
  ggtitle("Paginas vs. Duración de Sesiones") +
  theme_classic()

disp_durpag

A nivel general, se evidencia que, salvo unos pocos casos, existe una relación positiva entre las dos variables. A menor duración, mayores páginas visitadas. Intente como experto de negocio encontrar una explicación a este comportamiento.

3.2 Curva de suavizamiento

Así como vimos en el gráfico anterior, al parece existiría una relación negativa entre las variables Paginas y Duracion_Min. Sin embargo, para un ojo poco entrenado esto podría no ser tan evidente. Por esta razón existen las curvas de suaviamiento incorporando una nueva geometría al gráfico mediante geom_smooth.

smooth_durpag <- ggplot(data = df_disp,
                      mapping = aes(x = Duracion_Min,
                                    y = Paginas)) +
  geom_point(color = "tomato",
             alpha = 0.75) +
  ylim(c(0,10)) +
  xlim(c(0,1)) +
  ylab("Paginas visitadas") +
  xlab("Duración en Minutos") +
  ggtitle("Paginas vs. Duración de Sesiones") +
  theme_classic() +
  geom_smooth(method = "lm")

smooth_durpag

El parámetro method utilizado hace referencia a que queremos un ajuste mediante un modelo lineal (lm por sus siglas en inglés).

En efecto, notemos que existe una leve relación inversa entre las variables, sin embargo, esto parece aún no ser suficientemente claro. Para facilitar aún más la visualización incorporemos un parámetro adicional a esta geometría llamado formula tal que la variable páginas esté dada en función del logaritmo de la duración.

\[ Paginas = f(log(Duracion_{Min})) \]

smooth_durpag <- ggplot(data = df_disp,
                      mapping = aes(x = Duracion_Min,
                                    y = Paginas)) +
  geom_point(color = "tomato",
             alpha = 0.75) +
  ylim(c(0,10)) +
  xlim(c(0,1)) +
  ylab("Paginas visitadas") +
  xlab("Duración en Minutos") +
  ggtitle("Paginas vs. Duración de Sesiones") +
  theme_classic() +
  geom_smooth(method = "lm",
              formula = y ~ log(x))
smooth_durpag

3.3 Columnas

Un gráfico que resulta muy útil para estudiar una variable numérica y su relación con otra que no lo es (texto, factor), es el gráfico de columnas. Un punto de cuidado aquí es que no debemos confundir las barras de las columnas. Las primeras se utilizan para visualizar la frecuencia o distribución de una sola variable, mientras que las segundas miden el comperamiento de una variable, con relación a otra.

Para entender de mejor forma, supongamos que deseamos conocer la duración promedio en segundos de las primeras sesiones, y como esta duración cambia en función del dispositivo utilizado. PAra esto usamos la geometría dada por la función geom_col, y antes creamos una tabla que resuma la métrica que deseamos visualizar.

df_durdisp <- df_ses_geo %>%
  filter(Num_Sesion == 1) %>%
  filter(Duracion_Min > 0) %>%
  group_by(Dispositivo) %>%
  summarise(Duracion_Prom = mean(Duracion_Min)*60) %>%
  ungroup()

col_durdisp <- ggplot(data = df_durdisp,
                      mapping = aes(x = Dispositivo,
                                    y = Duracion_Prom)) +
  geom_col()

col_durdisp

Resulta interesante observar que las personas que utilizan Android y PC, mantienen sesiones entre un 25% y un 30% más largas en duración que aquellos con dispositivos Apple.

Pongamos ahora el formato que nos facilite la visualización del gráfico obtenido.

col_durdisp <- ggplot(data = df_durdisp,
                      mapping = aes(x = Dispositivo,
                                    y = Duracion_Prom)) +
  geom_col(fill = "purple",
           alpha = 0.5,
           color = "purple") +
  xlab("Dispositivo") +
  ylab("Duración Promedio (en segundos)") +
  ggtitle("Duración vs Dispositivo en Sesiones") +
  theme_gray()

col_durdisp

3.4 Cajones (con bigotes)

Ya vimos que los cajones con bigotes permiten conocer la distribución y las principales medidas de centralidad y posición de una variable, si ahora añadimos la estética “y”, podemos además comparar las distribuciones de una variable con relación a otra.

Continuando ocn el ejemplo anterior, dados otros resultados que hemos obtenido previamente, podría discutirse que la duración de las sesiones puede estar sesgada o presentar valores atípicos, por lo que la conclusión de que la duración es diferente por dispositivo no es suficientemente robusta. Para comprobar esto, usemos cajones con bigotes, donde excluyamos los datos atípicos, como ya lo hicimos previamente.

df_durdisp2 <- df_ses_geo %>%
  filter(Num_Sesion == 1) %>%
  filter(Duracion_Min > 0) %>%
  filter(Duracion_Min <= quantile(Duracion_Min,0.75 + 1.5*IQR(Duracion_Min))) %>%
  mutate(Duracion_Seg = Duracion_Min*60)

box_durdisp <- ggplot(data = df_durdisp2,
                      mapping = aes(x = Dispositivo,
                                    y = Duracion_Seg)) +
  geom_boxplot()

box_durdisp

Mejoremos nuestra visualización con un escalamiento de l´+imites en el eje y formatos.

box_durdisp <- ggplot(data = df_durdisp2,
                      mapping = aes(x = Dispositivo,
                                    y = Duracion_Seg)) +
  geom_boxplot(fill = "goldenrod1",
               color = "goldenrod4") +
  ylim(c(0,20)) +
  ylab("Duracion (Segundos)") +
  xlab("Dispositivo") +
  ggtitle("Comportamiento de la Duración por Dispositivo") +
  theme_classic()

box_durdisp

¿Cree que la concslusión alcanzada previamente por las columnas cambia a la luz de este gráfico?

3.5 Líneas de series temporales

El uso de líneas en gráficos de datos, suele ser mal entendido y sobreutilizado para visualizar cosas que no deberían representarse por esta geometría. La naturaleza de una línea es la de continuidad por lo que su uso se restrinje a aquellas variables con una naturaleza “de flujo”. El tipo de variable que mejor cumple esta característica es justamente la de fechas, pues es la forma de presentar el flujo temporal.

Como un primer ejemplo, veamos el número de sesiones que se han tenido en nuestros datos por fecha. Usemos la función geom_line.

df_sesfecha <- df_ses_geo %>%
  count(Fecha)

line_ses <- ggplot(data = df_sesfecha,
                   mapping = aes(x = Fecha,
                                 y = n)) +
  geom_line()

line_ses

NA

A partir del segundo semestre de 2019 se evidencia un incremento importante a nivel de sesiones registradas, pasando de un promedio diario de aproximadamente 150 a uno de 300. Completemos este gráfico con un formato adecuado.

line_ses <- ggplot(data = df_sesfecha,
                   mapping = aes(x = Fecha,
                                 y = n)) +
  geom_line(color = "lightgreen",
            size = 1) + #Este parametro permite definir el grosor de la linea
  ylab("# Sesiones") +
  xlab("") +
  ggtitle("Evolución de Sesiones") +
  theme_dark()

line_ses

Hagamos otro ejemplo, pero ahora en vez de estudiar las sesiones por fecha, veamos la duración promedio de sesiones en segundos, pero agrupado por mes y año.

df_durmes <- df_ses_geo %>%
  mutate(Periodo = year(Fecha)) %>%
  mutate(Mes = month(Fecha)) %>%
  mutate(PeriodoMes = Periodo*100 + Mes) %>%
  group_by(PeriodoMes) %>%
  summarise(Duracion_Prom = mean(Duracion_Min)*60) %>%
  ungroup() %>%
  arrange(PeriodoMes)

line_durmes <- ggplot(data = df_durmes,
                      mapping = aes(x = PeriodoMes,
                                    y = Duracion_Prom)) +
  geom_line()

line_durmes

Notemos que en la gráfica resultante, existe un espacio con valores de PeriodoMes sin sentido (i.e. 201930). Esto sucede porque R entiende esta variable como numérica e intenta completarla al momento de graficar. Para arreglar esto incluyamos la estética group con un valor de 1, lo cual nos permite consolidar los valores y no considerar este autocompletado; y además definamos la variable como factor al momento de graficarla para que no se asuma que es de tipo numérica.

line_durmes <- ggplot(data = df_durmes,
                      mapping = aes(x = as.factor(PeriodoMes),
                                    y = Duracion_Prom,
                                    group = 1)) +
  geom_line()

line_durmes

Finalicemos con el formato para una visualización adecuada.

line_durmes <- ggplot(data = df_durmes,
                      mapping = aes(x = as.factor(PeriodoMes),
                                    y = Duracion_Prom,
                                    group = 1)) +
  geom_line(color = "darkred",
            size = 0.75,
            linetype = 2) + #Este parametro me permite escoger el tipo de linea a utilizar
  ylab("Duración Promedio de Sesiones") +
  xlab("PeriodoMes") +
  ggtitle("Evolución Mensual de la Duración") +
  theme_classic() +
  theme(axis.text.x = element_text(angle = 90))

line_durmes

4. Gráficos Multivariables

Usando gráficos en coordenadas cartesianas $ x,y $ igualmente podemos visualizar más de dos variables. Para esto, es importante hacer uso del componente de ESTETICAS como iremos viendo en los siguientes casos.

4.1 Densidades

Recordemos el ejemplo del gráfico de distribución que habíamos visto en la sección de una variable,

dens_dur

Nos interesa ahora ver si la distribución difiere por tipo de dispositivo (Escritorio, Móvil). Para esto, volvamos a hacer este gráfico pero ahora usemos como ESTETICA el relleno (fill) y ya no solamente como parámetro.

dens_dur2 <- ggplot(data = df_dur,
                   mapping = aes(x = Duracion_Min,
                                 fill = Tipo_Dispositivo)) +
  geom_density(color = "goldenrod") +
  xlab("Duración en Minutos (log)") +
  ylab("") +
  ggtitle("Distribución de Duración de Sesiones") +
  theme_classic() +
  scale_x_log10()

dens_dur2

Mejoremos este gráfico adicionando como formatos un grado de transparencia del 50%, y un parámetro adicional a la geometríca llamado “bw” (ancho de banda) con un valor de 0.25.

dens_dur2 <- ggplot(data = df_dur,
                   mapping = aes(x = Duracion_Min,
                                 fill = Tipo_Dispositivo)) +
  geom_density(color = "goldenrod",
               alpha = 0.5,
               bw = 0.25) +
  xlab("Duración en Minutos (log)") +
  ylab("") +
  ggtitle("Distribución de Duración de Sesiones") +
  theme_classic() +
  scale_x_log10()

dens_dur2

Al parecer la duración de las sesiones en móvil es levemente inferior a la de aquellas que se dan por dispositivos de escritorio.

Como ejercicio, pruebe con otros anchos de banda menores y mayores al mostrado para ver qué efecto tienen en su gráfico.

4.2 Lineas de series de tiempo

Sobre la base de usar como estética el parámetro fill en el caso anterior, veamos ahora el uso de color para hacer una línea de series de tiempo del número de primeras sesiones pero diferenciando por fuente.

df_sesfecha_f <- df_ses_geo %>%
  filter(Num_Sesion == 1) %>%
  count(Fecha,
        TipoFuente_Analisis)

line_ses2 <- ggplot(data = df_sesfecha_f,
                   mapping = aes(x = Fecha,
                                 y = n,
                                 color = TipoFuente_Analisis)) +
  geom_line()

line_ses2

El gráfico obtenido resulta un tanto confuso en vista de que existen muchas fuentes. Mejoremos esto, añadiendo una transparencia del 75% en las líneas y adicionando una geometría de suavizamiento con parámetro method de “loess”. Incorpore además otros formatos que estime adecuados.

line_ses2 <- ggplot(data = df_sesfecha_f,
                   mapping = aes(x = Fecha,
                                 y = n,
                                 color = TipoFuente_Analisis)) +
  geom_line(alpha = 0.25) +
  geom_smooth(method = "loess") +
  ylab("# Sesiones") +
  xlab("") +
  labs(color = "Fuente") + #Esta función permite cambiar el nombre de la leyenda
  ggtitle("Evoluciones de Sesiones por Fuente") +
  theme_classic() +
  theme(legend.position = "top")

line_ses2

¿Qué puede inferir respecto a las fuentes de las sesiones, siendo usted un experto de negocio?

Así como fill y color, otros parámetros que hemos visto en los gráficos precedentes igualmente pueden transformarse en ESTETICAS a fin de representar alguna otra variable. Como ejercicio intente probar el uso de estéticas adicionales en gráficos de puntos y de columnas.

4.3 Mapas de calor

Los mapas de calor son representaciones espaciales de dos variables que permiten visualizar zonas de interés definidas en base a una tercera variable.

Para entender mejor su funcionamiento, supongamos que ahora queremos conocer la cantidad de sesiones por día de la semana y hora (del usuario). Para mayor facilidad, creemos una nueva variable llamada Grupo_Hora donde las opciones sean:

  • 1.Madrugada (entre las 0 y las 5 horas)
  • 2.Manana (6-10)
  • 3.MedioDia (11-15)
  • 4.Tarde (16-20)
  • 5.Noche (21-23)

Usemos además la geometría geom_tile y como estética nueva fill.

df_sestime <- df_ses_geo %>%
  mutate(Grupo_Hora = ifelse(Hora <= 5,
                             "1.Madrugada",
                             ifelse(Hora <= 10,
                                    "2.Manana",
                                    ifelse(Hora <= 15,
                                           "3.MedioDia",
                                           ifelse(Hora <= 20,
                                                  "4.Tarde",
                                                  "5.Noche"))))) %>%
  count(DiaSemana,
         Grupo_Hora)

map_sestime <- ggplot(data = df_sestime,
                      mapping = aes(x = DiaSemana,
                                    y = Grupo_Hora,
                                    fill = n)) +
  geom_tile()

map_sestime

Para una mejor visualización, controlemos el color de relleno de forma que sea rojo para el caso de pocas sesiones, y verde para el de muchas; además que estos colores sigan un gradiente. Usemos una función adicional llamada scale_fill_gradiente. Aprovechemos y adicionemos todos los formatos que veamos conveniente.

map_sestime <- ggplot(data = df_sestime,
                      mapping = aes(x = DiaSemana,
                                    y = Grupo_Hora,
                                    fill = n)) +
  geom_tile() +
  scale_fill_gradient(low = "red", high = "green") +
  ylab("Momento del Día") +
  xlab("Dia de la Semana") +
  labs(fill = "# Sesiones") +
  ggtitle("Sesiones por Día y Hora") +
  theme_minimal()

map_sestime

Con nuestro mapa de calor podemos ver que los lunes al medio día hay mayor tráfico. Así mismo, en las noches y madrugadas de todos los días hay poco tráfico, y en las tardes de los fines de semana el tráfico es moderado.

4.4 Mapas geográficos

Una de las aplicaciones más interesantes de los mapas de calor hace referencia a su uso para análisis geolocalizado.

Una pregunta recurrente que podría salir de este conjunto de datos es saber cuál es la ciudad de Ecuador con mayor número de sesiones, y para esto podemos usar una base de datos específica de ggplot llamada map_data.

df_mapa_ec <- map_data(map = "world", region = "Ecuador")
view(df_mapa_ec)

Visualicemos en primera instancia nuestro mapa de Ecuador usando la geometría geom_polygon.

mapa_sesec <- ggplot(data = df_mapa_ec,
                     mapping = aes(x = long,
                                   y = lat,
                                   group = group)) +
  geom_polygon()

mapa_sesec

Apliquemos ciertos formatos, que hagan que nuestro mapa se vea mejor;

mapa_sesec <- ggplot(data = df_mapa_ec,
                     mapping = aes(x = long,
                                   y = lat,
                                   group = group)) +
  geom_polygon(fill = "lightblue",
               color = "gray50",
               alpha = 0.5) +
  theme_void() #Borra todo lo asociado a ejes y fondos

mapa_sesec

Ahora bien, sobre este mapa vamos a montar otra geometria que nos muestre puntos en las ciudades donde hay sesiones web, y donde el color de cada punto corresponda a un criterio de muchas (verde) o pocas (rojo) sesiones, de forma equivalente al mapa de calor de la sección anterior. Para esto vamos a hacer lo siguiente:

  • En primer lugar construimos un data frame que contenga solo el total de sesiones en las ciudades de Ecuador, así como sus respectivas latitudes y longitudes.
df_geo_ec <- df_ses_geo %>%
  filter(Pais == "Ecuador") %>%
  group_by(Ciudad) %>%
  summarise(Sesiones = n(),
            lat = mean(Lat),
            long = mean(Lon))
  • Una vez tenemos esto, añadimos al mapa que ya habíamos hecho previamente.
mapa_sesec <- ggplot(data = df_mapa_ec,
                     mapping = aes(x = long,
                                   y = lat,
                                   group = group)) +
  geom_polygon(fill = "lightblue",
               color = "gray50",
               alpha = 0.5) +
  theme_void() +
  geom_point(data = df_geo_ec,
             mapping = aes(x = long,
                           y = lat,
                           group = 1,
                           color = Sesiones),
             size = 1.5) +
  scale_color_gradient(low = "red", high = "green")

mapa_sesec

Notemos que hay muchas ciudades con tonalidades rojas, lo cual hace que se pierda información. Usemos el parámetro trans para corregir esto dando un reescalamiento logarítmico a los colores.

mapa_sesec <- ggplot(data = df_mapa_ec,
                     mapping = aes(x = long,
                                   y = lat,
                                   group = group)) +
  geom_polygon(fill = "lightblue",
               color = "gray50",
               alpha = 0.5) +
  theme_void() +
  geom_point(data = df_geo_ec,
             mapping = aes(x = long,
                           y = lat,
                           group = 1,
                           color = Sesiones),
             size = 1.5) +
  scale_color_gradient(low = "red", high = "green", trans = "log")

mapa_sesec

PUNTOS EXTRAS: Recibirá 10 puntos extras, el primer estudiante que remita el script con un mapa equivalente al hecho en este documento, pero ahora para las ciudades de Estados Unidos, y excluyendo del mapa las subregiones de Alaska y Hawaii.

4.5 Etiquetas

Un aspecto importante que facilita la visualización de gráficos es la inclusión de etiquetas que guíen y faciliten el entendimiento. En este sentido, las etiquetas deben entenderse como una GEOMETRÍA adicional dentro de la ¨gramática de los gráficos.

Recordemos entonces el gráfico de columnas que realizamos para evaluar la duración promedio por dispositivo.

col_durdisp

Incluyamos ahora etiquetas para que muestren el valor de cada columna, para esto usemos una estética adicional dada por label y la función de geometría geom_label.

etiq_dur_disp <- ggplot(data = df_durdisp,
                      mapping = aes(x = Dispositivo,
                                    y = Duracion_Prom,
                                    label = Duracion_Prom)) +
  geom_col(fill = "purple",
           alpha = 0.5,
           color = "purple") +
  xlab("Dispositivo") +
  ylab("Duración Promedio (en segundos)") +
  ggtitle("Duración vs Dispositivo en Sesiones") +
  theme_gray() +
  geom_label()

etiq_dur_disp

Ajustemos el formato de estas etiquetas tal que:

  • Los valores estén redondeados al primer decimal.
  • Tengan un fondo con un color adecuado.
  • Tengan un color de letra adecuado.
  • Tengan un tamaño tal que sean facilmente visibles.
etiq_dur_disp <- ggplot(data = df_durdisp,
                      mapping = aes(x = Dispositivo,
                                    y = Duracion_Prom,
                                    label = round(Duracion_Prom,1))) +
  geom_col(fill = "purple",
           alpha = 0.5,
           color = "purple") +
  xlab("Dispositivo") +
  ylab("Duración Promedio (en segundos)") +
  ggtitle("Duración vs Dispositivo en Sesiones") +
  theme_gray() +
  geom_label(fill = "purple",
            color = "white",
            size = 3)

etiq_dur_disp

Como otro ejemplo de etiquetas, tomemos nuestro mapa de calor de dias de la semana y horas.

map_sestime

Incorporemos las etiquetas con los valores alcanzados en cada cuadrante y eliminemos a su vez la leyenda pues resultaría irrelevante.

etiq_sestime <- ggplot(data = df_sestime,
                       mapping = aes(x = DiaSemana,
                                     y = Grupo_Hora,
                                     fill = n,
                                     label = n)) +
  geom_tile() +
  scale_fill_gradient(low = "red", high = "green") +
  ylab("Momento del Día") +
  xlab("Dia de la Semana") +
  labs(fill = "# Sesiones") +
  ggtitle("Sesiones por Día y Hora") +
  theme_minimal() +
  geom_label() +
  theme(legend.position = "none")

etiq_sestime

Finalmente, y como un ejemplo más desafiante, en nuestro mapa georeferenciado de Ecuador, pongamos etiquetas a las top 5 ciudades en número de sesiones, que incluyan tanto el nombre de la ciudad como su cantidad. Coloquemos esta etiqueta a la derecha de cada punto.

df_geo_ec2 <- df_geo_ec %>%
  arrange(-Sesiones) %>%
  top_n(5,Sesiones)

etiq_sesec <- ggplot(data = df_mapa_ec,
                     mapping = aes(x = long,
                                   y = lat,
                                   group = group)) +
  geom_polygon(fill = "lightblue",
               color = "gray50",
               alpha = 0.5) +
  theme_void() +
  geom_point(data = df_geo_ec,
             mapping = aes(x = long,
                           y = lat,
                           group = 1,
                           color = Sesiones),
             size = 1.5) +
  scale_color_gradient(low = "red", high = "green", trans = "log") +
  geom_label(data = df_geo_ec2,
             mapping = aes(x = long,
                           y = lat,
                           group = 1,
                           label = paste(Ciudad,", ",Sesiones, sep = "")),
             fill = "white",
             size = 2,
             hjust = -0.15)
  

etiq_sesec

LS0tDQp0aXRsZTogIlZpc3VhbGl6YWNpw7NuIG1lZGlhbnRlIGdyw6FmaWNvcyINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KLS0tDQoNCkNvbW8geWEgbWVuY2lvbmFtb3MsIG90cmEgZm9ybWEgZGUgYW5hbGl6YXIgZGF0b3MgYWRpY2lvbmFsIGFsIHVzbyBkZSBlc3RhZMOtc3RpY2EgZGVzY3JpcHRpdmEgZSBpbmZlcmVuY2lhbCwgZXMgbGEgZGUgdmlzdWFsaXphciBpbmZvcm1hY2nDs24gbWVkaWFudGUgZ3LDoWZpY29zLg0KDQpBbnRlcyBkZSBpbmljaWFyLCBjYXJndWVtb3MgbnVlc3RyYXMgbGlicmVyw61hczoNCmBgYHtyfQ0KbGlicmFyeSh0aWR5dmVyc2UpDQpsaWJyYXJ5KGx1YnJpZGF0ZSkNCmBgYA0KDQpBZGljaW9uYWxtZW50ZSwgaW1wb3J0ZW1vcyBudWVzdHJvIGRhdGFmcmFtZSBkZSBzZXNpb25lcyB3ZWIgcXVlIHNlIGVuY3VlbnRyYSBlbiBlbCBsaW5rIGh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9qc2FyYXVqbzUwODEvY2xhc2VzQkkvbWFpbi9zZXNpb25lc193ZWIuY3N2Lg0KYGBge3J9DQpkZl9zZXNpb25lcyA8LSByZWFkX2NzdihmaWxlID0gImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9qc2FyYXVqbzUwODEvY2xhc2VzQkkvbWFpbi9zZXNpb25lc193ZWIuY3N2IiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgY29sX25hbWVzID0gVFJVRSkNCmBgYA0KDQpZIGNvbW8gY29tcGxlbWVudG8gaW1wb3J0YW1vcyB0YW1iacOpbiB1bmEgdGFibGEgcXVlIHNlIGVuY3VlbnRyYSBlbiBlbCBsaW5rIA0KaHR0cHM6Ly9kcml2ZS5nb29nbGUuY29tL2ZpbGUvZC8xTGJmZVdyektKenhHUlpmZkJ0NmpyNXppZXIzZGRwVkovdmlldz91c3A9c2hhcmluZywgeSBxdWUgY29udGllbmUgbGEgZ2VvbG9jYWxpemFjacOzbiBkZSBkaXN0aW50YXMgSVAgYWxyZWRlZG9yIGRlbCBtdW5kby4NCg0KUGFyYSBlc3RhIGltcG9ydGFjacOzbiBkZXNjYXJndWVtb3MgZWwgYXJjaGl2byBleGlzdGVudGUgZW4gZWwgbGluayB5IGx1ZWdvLCBkZWZpbmllbmRvIGVsIGRpcmVjdG9yaW8gcGVydGllbmVudGUgZG9uZGUgc2UgYWxvamEgZXN0ZSBhcmNoaXZvIGltcG9ydMOpbW9zbG8gdXNhbmRvIGxhIGZ1bmNpb24gKnJlYWRSRFMqLg0KYGBge3J9DQpzZXR3ZCgiQzovVXNlcnMvanNhcmEvT25lRHJpdmUvRG9jdW1lbnRvcy9VSGVtaXNmZXJpb3MvQkkgMjAyMS0yL0RhdGFzZXRzIikgI0VzdGUgY29tYW5kbyBkZXBlbmRlIGRlIGNhZGEgcGVyc29uYSB5IGRlIGRvbmRlIGZ1ZSBndWFyZGFkbyBlbCBhcmNoaXZvIGRlc2NhcmdhZG8NCmRmX2dlbyA8LSByZWFkUkRTKGZpbGUgPSAidXN1YXJpb3NfZ2VvKDEpLnJkcyIpDQpgYGANCg0KTG9zIGFyY2hpdm9zIFJEUyBjb21vIGVsIHF1ZSB2ZW1vcyBhcXXDrSwgc29uIGFxdWVsbG9zIHF1ZSBndWFyZGFuIG9iamV0b3MgcHJvcGlvcyBkZSBSIHBhcmEgc3UgdXNvIHBvc3Rlcmlvci4NCg0KQWhvcmEgYmllbiwgYW50ZXMgZGUgaW5pY2lhciBjb24gbG9zIGdyw6FmaWNvcyBhcHJlbmRhbW9zIGEgdXNhciB1bmEgbnVldmEgZnVuY2nDs24gZGVsICoqdGlkeXZlcnNlKiosIGVxdWl2YWxlbnRlIGEgbGEgZmFtaWxpYSBkZSAqbG9va3VwKiAoKmJ1c2NhciopIGRlIEV4Y2VsLCBxdWUgbm9zIHBlcm1pdGlyw6EgdW5pciBkb3MgdGFibGFzIHNvYnJlIGxhIGJhc2UgZGUgdW4gY2FtcG8gZGUgcmVmZXJlbmNpYSBjb23Dum46IGVsICppbm5lcl9qb2luKi4gUGFyYSB1c2FybGEsIHZlYW1vcyBlbiBwcmltZXIgbHVnYXIgbnVlc3RyYXMgdGFibGFzLCBlIGlkZW50aWZpcXVlbW9zIGVsIGNhbXBvIHF1ZSBkZWJlbW9zIHVzYXIgcGFyYSBsYSB1bmnDs24uDQpgYGB7cn0NClZpZXcoZGZfc2VzaW9uZXMpDQpWaWV3KGRmX2dlbykNCmBgYA0KDQpSZXN1bHRhIGV2aWRlbnRlIHF1ZSBlbiAqZGZfc2VzaW9uZXMqIHkgZW4gKmRmX2dlbyogbG9zIGNhbXBvcyBjb211bmVzIHNvbiAiVXN1YXJpb19JUCIgeSAiVXN1YXJpbyIsIHJlc3BlY3RpdmFtZW50ZS4gUG9yIHRhbnRvLCB1c8OpbW9zbG9zIGNvbW8gcmVmZXJlbmNpYSBwYXJhIGxhIHVuacOzbiBjcmVhbmRvIHVuIG51ZXZvIG9iamV0byAqZGZfc2VzX2dlbyouDQpgYGB7cn0NCmRmX3Nlc19nZW8gPC0gZGZfc2VzaW9uZXMgJT4lDQogIGlubmVyX2pvaW4oZGZfZ2VvLA0KICAgICAgICAgICAgIGJ5ID0gYygiVXN1YXJpb19JUCIgPSAiVXN1YXJpbyIpKQ0KYGBgDQoNClZlYW1vcyBsYSBlc3RydWN0dXJhIGRlIG51ZXN0cmEgdGFibGEgcmVzdWx0YW50ZSwgeSB2ZXJpZmlxdWVtb3MgcXVlIHVuaW1vcyBkZSBmb3JtYSBhZGVjdWFkYS4NCmBgYHtyfQ0KZ2xpbXBzZShkZl9zZXNfZ2VvKQ0KYGBgDQpQVU5UT1MgRVhUUkFTOiBhZGljaW9uYWwgYWwgKmlubmVyX2pvaW4qIGV4aXN0ZW4gb3RyYXMgZnVuY2lvbmVzIHNpbWlsYXJlcyBjb21vIHNvbiAqbGVmdF9qb2luKiwgKnJpZ2h0X2pvaW4qIHkgKmZ1bGxfam9pbiouIEVsIHByaW1lciBlc3R1ZGlhbnRlIHF1ZSBlbnbDrWUgcG9yIGNvcnJlbyBlbGVjdHLDs25pY28gdW5hIGV4cGxpY2FjacOzbiBjb24gbGFzIGRpZmVyZW5jaWFzIGVudHJlIMOpc3RhcyByZWNpYmlyw6EgNSBwdW50b3MgcGFyYSBsYSBldmFsdWFjacOzbiBmaW5hbC4NCg0KRW1wZWNlbW9zIGVudG9uY2VzIGNvbiBudWVzdHJhcyB2aXN1YWxpemFjaW9uZXMgdXNhbmRvIGVzdGUgZGF0YSBmcmFtZSBjb24gZGF0b3MgZGUgbGFzIHNlc2lvbmVzIHkgZGUgZ2VvbG9jYWxpemFjacOzbi4NCg0KIyMgMS4gR3JhbcOhdGljYSBkZSBncsOhZmljb3MNCg0KUGFyYSBlbXBlemFyLCBlbnRlbmRhbW9zICBsbyBxdWUgZXMgbGEgKmdyYW3DoXRpY2EgZGUgZ3LDoWZpY29zKiwgcGFyYSBsbyBjdWFsIHVzYXJlbW9zIHVuYSBzdWItbGlicmVyw61hIGRlbCB0aWR5dmVyc2UgbGxhbWFkYSAqKmdncGxvdDIqKi4NCg0KRGVzZGUgbGEgcGVyc3BlY3RpdmEgZGUgZXN0ZSBjdXJzbywgZW50ZW5kZXJlbW9zIHVuIGdyw6FmaWNvIGVzIHVuIG9iamV0byAoYXPDrSBjb21vIGxvIHNvbiBsb3MgZGF0YSBmcmFtZXMsIGxvcyB2ZWN0b3JlcyB5IGxhcyB2YXJpYWJsZXMpIHF1ZSBwb3NlZSBjb21vIHByaW5jaXBhbCBjYXJhY3RlcsOtc3RpY2EgdW5hIGVzdHJ1Y3R1cmEgYWRpdGl2YSBkZWZpbmlkYSBjb25mb3JtZSBsbyBxdWUgbGxhbWFtb3MgKmdyYW3DoXRpY2EgZGUgZ3LDoWZpY29zKi4NCg0KQ29tbyBwcmltZXIgY29tcG9uZW50ZSBkZSBkaWNoYSBlc3RydWN0dXJhIGVzdMOhbiBsb3MgKipEQVRPUyoqIGNvbiBsb3MgcXVlIHNlIHZhIGEgdHJhYmFqYXIuIEVuIGVsIGNhc28gcXVlIGVzdGFtb3MgZXN0dWRpYW5kbywgbG9zIGRhdG9zIHNlIGNvcnJlc3BvbmRlbiBjb24gbnVlc3RybyBkYXRhIGZyYW1lICpkZl9zZXNfZ2VvKi4gQ29uc3RydXlhbW9zIG51ZXN0cm8gcHJpbWVyIGdyw6FmaWNvICpncmFmXzAxKiBpZGVudGlmaWNhbmRvIGVzdGUgY29tcG9uZW50ZSB5IHVzYW5kbyBwYXJhIGVzdG8gbGEgZnVuY2nDs24gKmdncGxvdCogeSB2ZWFtb3NsbyBlbiBudWVzdHJhIMOhcmVhIGRlIHZpc3VhbGl6YWNpw7NuLg0KDQpgYGB7cn0NCmdyYWZfMDEgPC0gZ2dwbG90KGRhdGEgPSBkZl9zZXNfZ2VvKQ0KZ3JhZl8wMQ0KYGBgDQoNCkNvbW8gc2UgcHVlZGUgYXByZWNpYXIsIG51ZXN0cm8gZ3LDoWZpY28gdG9kYXbDrWEgbm8gdGllbmUgbmFkYSBxdWUgInZlciIuIEFncmVndWVtb3MgZW50b25jZXMgZWwgc2lndWllbnRlIGNvbXBvbmVudGUgcXVlIHNvbiBsYXMgKipFU1TDiVRJQ0FTKiosIGxhcyBjdWFsZXMgdG9tYW4gdmFyaWFibGVzIGVuIGxvcyBkYXRvcyB5IG5vcyB2YW4gYSBwZXJtaXRpciBldmlkZW5jaWFyIGNhcmFjdGVyw61zdGljYXMgZXNwZWPDrWZpY2FzIGRlIGxvcyBncsOhZmljb3MgKHBvciBlamVtcGxvLCBsb3MgZWplcykuIFBhcmEgZXN0bywgdXNlbW9zIGVsIHBhcsOhbWV0cm8gYWRpY2lvbmFsICJtYXBwaW5nIiBkZSBsYSBmdW5jacOzbiAqZ2dwbG90KiwgeSBjcmVlbW9zIHVuIG51ZXZvIG9iamV0byAqZ3JhZl8wMiouIA0KYGBge3J9DQpncmFmXzAyIDwtIGdncGxvdChkYXRhID0gZGZfc2VzX2dlbywNCiAgICAgICAgICAgICAgICAgIG1hcHBpbmcgPSBhZXMoeCA9IEhvcmFFQykpDQpncmFmXzAyDQpgYGANCg0KSGVtb3MgY29uc2VndWlkbywgInZlciIgYWxnbyBudWV2byBhaG9yYSBjb21vIGVzIGxhIGVzdMOpdGljYSBkZWwgZWplIGhvcml6b250YWwgIngiLiBMbyBxdWUgdGVuZW1vcyBxdWUgdGVuZW1vcyBxdWUgYWdyZWdhciBhaG9yYSBlcyBlbCBjb250ZW5pZG8gZGVsIGdyw6FmaWNvLCBsbyBjdWFsIHZpZW5lIHJlcHJlc2VudGFkbyBlc3BlY8OtZmljYW1lbnRlIHBvciBsbyBxdWUgc2UgY29ub2NlIGNvbW8gbGEgKipHRU9NRVRSw41BKiouIFVzZW1vcyBlbnRvbmNlcyBjb21vIGVqZW1wbG8gdW5hIGZ1bmNpw7NuIHF1ZSBub3MgcGVybWl0YSB2aXN1YWxpemFyIHVuIGNvbnRlbyBkZSBjYXNvcyBkZSBsYXMgc2VzaW9uZXMgcG9yIGhvcmEgYXBsaWNhbmRvIGxhIGZ1bmNpw7NuICpnZW9tX2JhciosIGEgZmluIGRlIHNhYmVyIGN1YWxlcyBzb24gbGFzIGhvcmFzIGRlIEVjdWFkb3IgY29uIG1heW9yIHRyw6FmaWNvIHdlYi4gQ3JlZW1vcyBlbCBncsOhZmljbyBudWV2byBhIHBhcnRpciBkZWwgYW50ZXJpb3IuDQpgYGB7cn0NCmdyYWZfMDIgKyBnZW9tX2JhcigpDQpgYGANClVuYSB2ZXogcXVlIHRlbmVtb3MgZWwgZ3LDoWZpY28gZGVzZWFkbywgZWwgw7psdGltbyBjb21wb25lbnRlIHJlbGV2YW50ZSBkZSBsYSAqZ3JhbcOhdGljYSogZXMgZWwgZGUgbG9zICoqRk9STUFUT1MqKiwgbG8gY3VhbCBzZSBhc29jaWEgYSB0b2RvcyBhcXVlbGxvcyBlbGVtZW50b3MgcXVlIHBlcm1pdGVuIHVuYSB2aXN1YWxpemFjacOzbiBtw6FzIGFncmFkYWJsZSBwYXJhIGVsIHVzdWFyaW8gZmluYWwgKGNvbG9yZXMsIHRpcG9zIGRlIGxldHJhLCBub21icmVzLCBldGMuKS4NCg0KQ3JlZW1vcyBlbnRvbmNlcyB1biBudWV2byBncsOhZmljbyAqZ3JhZl8wMyogcXVlIGluY2x1eWEgdGFtYmnDqW4gbGEgY2FwYSBkZSBmb3JtYXRvLiBFbiBjb25jcmV0bywgcXVpc2nDqXJhbW9zIGxvIHNpZ3VpZW50ZToNCg0KKiBFbCBjb2xvciBkZSByZWxsZW5vIGRlIGxhcyBiYXJyYXMgcXVlIHNlYSBhenVsLg0KKiBFbCBjb2xvciBkZSBsw61uZWEgZGUgbGFzIGJhcnJhcyBxdWUgc2VhIGF6dWwgb3NjdXJvLg0KKiBMb3Mgbm9tYnJlcyBkZSBsb3MgZWplcyBzZWEgIkhvcmEgRWN1YWRvciIgeSAiQ29udGVvIiwgc2Vnw7puIGNvcnJlc3BvbmRhLg0KKiBFbCBmb25kbyBkZWwgZ3LDoWZpY28gbm8gc2VhIGdyaXMgc2lubyBtw6FzIHNpbXBsZS4NCiogRWwgdMOtdHVsbyBkZWwgZ3LDoWZpY28gc2VhICJDb21wb3J0YW1pZW50byBkZSBzZXNpb25lcyBwb3IgaG9yYSIuDQoNCmBgYHtyfQ0KZ3JhZl8wMyA8LSBncmFmXzAyICsNCiAgZ2VvbV9iYXIoZmlsbCA9ICJibHVlIiwNCiAgICAgICAgICAgY29sb3IgPSAiZGFya2JsdWUiKSArDQogIHhsYWIoIkhvcmEgRWN1YWRvciIpICsNCiAgeWxhYigiQ29udGVvIikgKw0KICBnZ3RpdGxlKCJDb21wb3J0YW1pZW50byBkZSBzZXNpb25lcyBwb3IgaG9yYSIpICsNCiAgdGhlbWVfY2xhc3NpYygpDQoNCmdyYWZfMDMNCmBgYA0KDQpIZW1vcyBoZWNobyBudWVzdHJvIHByaW1lciBncsOhZmljbywgc29icmUgbGEgYmFzZSBkZSBsYSBsw7NnaWNhIGRhZGEgcG9yIGxhICpncmFtw6F0aWNhIGRlIGdyw6FmaWNvcyouIE5vdGVtb3MgcXVlIGxvcyBmb3JtYXRvcyBzb24gZWwgw7psdGltbyBwdW50byBxdWUgaGVtb3MgdG9tYWRvIGVuIGN1ZW50YSwgcHVlcyBhbnRlcyBxdWUgZXNvIGRlYmVtb3Mgc2llbXByZSBwcmVvY3VwYXJub3MgZGUgc2FiZXIgcXXDqSBkYXRvcyBzb24gbG9zIHF1ZSB2b3kgYSBhbmFsaXphciwgY8OzbW8gbG9zIHF1aWVybyB2aXN1YWxpemFyLCB5IHF1K2UgdGlwbyBkZSBncsOhZmljbyBuZWNlc2l0by4NCg0KRW1wZWNlbW9zIGVudG9uY2VzIGEgdmVyIGFsZ3Vub3MgdGlwb3MgZGUgZ3LDoWZpY29zIHF1ZSB0ZW5lbW9zIGRpc3BvbmlibGVzLg0KDQojIyAyLiBHcsOhZmljb3MgVW5pdmFyaWFibGVzDQoNCiMjIyAyLjEgQmFycmFzDQoNCkFsIG1vbWVudG8gZGUgZW50ZW5kZXIgbGEgKmdyYW3DoXRpY2EqLCBoZW1vcyB5YSB2aXN0byBsYSBzaW50YXhpcyBkZSBsb3MgZ3LDoWZpY29zIGRlIGJhcnJhLCBsb3MgY3VhbGVzIG5vcyBwZXJtaXRlbiBjb25vY2VyIGVsIGNvbnRlbyBkZSBjYXNvcyBxdWUgZXhpc3RlbiBwYXJhIHVuYSB2YXJpYWJsZSBlbiBlc3BlY8OtZmljby4NCg0KVm9sdmFtb3MgYSByZXBhc2FyIGVzdG8sIGludGVudGFuZG8gY29ub2NlciBjdcOhbCBsYSBwcm92aW5jaWEgZGUgRWN1YWRvciBxdWUgdGllbmUgbcOhcyBzZXNpb25lcy4gQ3JlZW1vcyB1biBvYmpldG8gbGxhbWFkbyAqYmFyX3Byb3YqLiBObyBub3MgcHJlb2N1cGVtb3MgcG9yIGFob3JhIGRlIGxvcyBmb3JtYXRvcywgc2lubyBkZSBsb3Mgb3Ryb3MgY29tcG9uZW50ZXMuDQpgYGB7cn0NCmRmX2VjdSA8LSBkZl9zZXNfZ2VvICU+JQ0KICBmaWx0ZXIoUGFpcyA9PSAiRWN1YWRvciIpDQoNCmJhcl9wcm92IDwtIGdncGxvdChkYXRhID0gZGZfZWN1LA0KICAgICAgICAgICAgICAgICAgICAgbWFwcGluZyA9IGFlcyh4ID0gUmVnaW9uKSkgKw0KICBnZW9tX2JhcigpDQoNCmJhcl9wcm92DQpgYGANCkFycmVnbGVtb3MgbG9zIGZvcm1hdG9zLCBkZSBmb3JtYSBxdWUgYWwgbWVub3M6DQoNCiogTG9zIG5vbWJyZXMgZGUgbGFzIHByb3ZpbmNpYXMgc2UgZW5jdWVudHJlbiBkZSBmb3JtYSB2ZXJ0aWNhbC4NCiogTm8gZXhpc3RhbiBub21icmVzIGRlIGVqZXMuDQoqIEVsIGdyw6FmaWNvIHRlbmdhIHTDrXR1bG8uDQoNCmBgYHtyfQ0KYmFyX3Byb3YgPC0gZ2dwbG90KGRhdGEgPSBkZl9lY3UsDQogICAgICAgICAgICAgICAgICAgICBtYXBwaW5nID0gYWVzKHggPSBSZWdpb24pKSArDQogIGdlb21fYmFyKCkgKw0KICB4bGFiKCIiKSArDQogIHlsYWIoIiIpICsNCiAgZ2d0aXRsZSgiU2VzaW9uZXMgcG9yIHByb3ZpbmNpYSAoRUMpIikgKw0KICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwKSkNCg0KYmFyX3Byb3YNCmBgYA0KTm90YW1vcyBxdWUgbG9zIG5vbWJyZXMgZW4gYWxndW5vcyBjYXNvcyBlc3TDoW4gZXF1aXZvY2Fkb3MsIHBvZHJpYW1vcyBjb3JyZWdpciBlc3RvIHF1aXRhbmRvIGxhIGV4cHJlc2nDs24gIlByb3ZpbmNpYSBkZVtsXSAiIHVzYW5kbyBsYSBmdW5jacOzbiBjb25vY2lkYSAqc3RyX3JlbW92ZSouIFRhbWJpw6luIHBvZHLDrWFtb3Mgc3VzdGl0dWlyIGxvcyBzw61tYm9sb3MgZXh0cmHDsW9zIGNvbW8gZ3Vpb25lcyBwb3IgZXNwYWNpb3MsIHkgYXJyZWdsYXIgIk1hbmFiPz8iLg0KDQpgYGB7cn0NCmRmX2VjdSA8LSBkZl9zZXNfZ2VvICU+JQ0KICBmaWx0ZXIoUGFpcyA9PSAiRWN1YWRvciIpICU+JQ0KICBtdXRhdGUoUmVnaW9uID0gc3RyX3JlbW92ZShSZWdpb24sICJQcm92aW5jaWEgZGVbbF17MCwxfSAiKSkgJT4lDQogIG11dGF0ZShSZWdpb24gPSBzdHJfcmVwbGFjZShSZWdpb24sICItIiwgIiAiKSkgJT4lDQogIG11dGF0ZShSZWdpb24gPSBzdHJfcmVwbGFjZShSZWdpb24sICJcXD9cXD8iLCAiaSIpKQ0KDQpiYXJfcHJvdiA8LSBnZ3Bsb3QoZGF0YSA9IGRmX2VjdSwNCiAgICAgICAgICAgICAgICAgICAgIG1hcHBpbmcgPSBhZXMoeCA9IFJlZ2lvbikpICsNCiAgZ2VvbV9iYXIoZmlsbCA9ICJ0b21hdG8iLA0KICAgICAgICAgICBjb2xvciA9ICJyZWQiKSArDQogIHhsYWIoIiIpICsNCiAgeWxhYigiIikgKw0KICBnZ3RpdGxlKCJTZXNpb25lcyBwb3IgcHJvdmluY2lhIChFQykiKSArDQogIHRoZW1lX2NsYXNzaWMoKSArDQogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTApKQ0KDQpiYXJfcHJvdg0KYGBgDQoNCiMjIyAyLjIgSGlzdG9ncmFtYQ0KDQpMb3MgaGlzdG9ncmFtYXMgY3VtcGxlbiBsYSBtaXNtYSBmdW5jacOzbiBxdWUgbGFzIGJhcnJhcywgY29uIGxhIGRpZmVyZW5jaWEgcXVlIGxvcyBwcmltZXJvcyBzaXJ2ZW4gcGFyYSB2aXN1YWxpemFyIHZhcmlhYmxlcyBudW3DqXJpY2FzIGRlY2ltYWxlcyB5IHN1cyByZXNwZWN0aXZvcyBjb250ZW9zLg0KDQpDb25zdHJ1eWFtb3MgdW4gaGlzdG9ncmFtYSBxdWUgbm9zIHBlcm1pdGEgdmlzdWFsaXphciBlbCBjb21wb3J0YW1pZW50byBkZSBsYSBkdXJhY2nDs24gZGUgc2VzaW9uZXMuIFV0aWxpemVtb3MgYWhvcmEgbGEgZnVuY2nDs24gKmdlb21faGlzdG9ncmFtKiBwYXJhIGNyZWFyIGVsIG9iamV0byAqaGlzdF9kdXIqLiBSZWNvcmRlbW9zIHF1ZSBubyB0aWVuZSBzZW50aWRvIHZlciBkdXJhY2lvbmVzIGRlIDAsIHBvciBsbyBxdWUgbGFzIHBvZGVtb3MgcXVpdGFyLg0KYGBge3J9DQpkZl9kdXIgPC0gZGZfc2VzX2dlbyAlPiUNCiAgZmlsdGVyKER1cmFjaW9uX01pbiA+IDApDQoNCmhpc3RfZHVyIDwtIGdncGxvdChkYXRhID0gZGZfZHVyLA0KICAgICAgICAgICAgICAgICAgIG1hcHBpbmcgPSBhZXMoeCA9IER1cmFjaW9uX01pbikpICsNCiAgZ2VvbV9oaXN0b2dyYW0oKQ0KDQpoaXN0X2R1cg0KYGBgDQpBcGxpcXVlbW9zIGFob3JhIGZvcm1hdG9zIHF1ZSBpbmNsdXlhbiBhbCBtZW5vczoNCg0KKiBUw610dWxvIGRlbCBncsOhZmljby4NCiogTm9tYnJlcyBkZSBlamUgeCBwZXJvIG5vIGRlbCB2ZXJ0aWNhbC4NCiogRm9uZG8gZGVsIGdyw6FmaWNvIGxpbXBpbyBvIGVuIGJsYW5jby4NCiogRXNjYWxhIGRlbCBlamUgeCBlbiBsb2dhcml0bW8gcGFyYSB1bmEgbWVqb3IgdmlzdWFsaXphY2nDs24uDQoNCmBgYHtyfQ0KaGlzdF9kdXIgPC0gZ2dwbG90KGRhdGEgPSBkZl9kdXIsDQogICAgICAgICAgICAgICAgICAgbWFwcGluZyA9IGFlcyh4ID0gRHVyYWNpb25fTWluKSkgKw0KICBnZW9tX2hpc3RvZ3JhbShmaWxsID0gInllbGxvdyIsDQogICAgICAgICAgICAgICAgIGNvbG9yID0gImdvbGRlbnJvZCIpICsNCiAgeGxhYigiRHVyYWNpw7NuIGVuIE1pbnV0b3MgKGxvZykiKSArDQogIHlsYWIoIiIpICsNCiAgZ2d0aXRsZSgiRGlzdHJpYnVjacOzbiBkZSBEdXJhY2nDs24gZGUgU2VzaW9uZXMiKSArDQogIHRoZW1lX2NsYXNzaWMoKSArDQogIHNjYWxlX3hfbG9nMTAoKQ0KDQpoaXN0X2R1cg0KYGBgDQoNCiMjIyAyLjMgRGVuc2lkYWQNCg0KRW4gbXVjaGFzIG9jYXNpb25lcywgbG9zIGhpc3RvZ3JhbWFzIHB1ZWRlcyBsbGVnYXIgYSBnZW5lcmFyIGNvbmZ1c2nDs24gZGFkYSBsYSBncmFuIGNhbnRpZGFkIGRlIGJhcnJhcyBxdWUgc2UgZ2VuZXJhbiwgcG9yIGxvIHF1ZSBzdWVsZSBlbXBsZWFyc2UgdW4gZ3LDoWZpY28gZGUgZGVuc2lkYWQgYSBmaW4gZGUgbW9zdHJhciBkZSB1bmEgbWFuZXJhIHNpbXBsaWZpY2FkYSBlbCBjb21wb3J0YW1pZW50byBkZSB1bmEgdmFyaWFibGUuDQoNCkNvbW8gbXVlc3RyYSBkZSBlc3RvLCB2ZWFtb3MgbnVldmFtZW50ZSBsYSBkaXN0cmlidWNpw7NuIGRlIGxhIGR1cmFjacOzbiBkZSBsYXMgc2VzaW9uZXMsIHBlcm8gYWhvcmEgY2FtYmllbW9zIGxhIGdlb21ldHLDrWEgYSAqZ2VvbV9kZW5zaXR5KiBlbiB1biBvYmpldG8gbGxhbWFkbyAqZGVuc19kdXIqLg0KYGBge3J9DQpkZW5zX2R1ciA8LSBnZ3Bsb3QoZGF0YSA9IGRmX2R1ciwNCiAgICAgICAgICAgICAgICAgICBtYXBwaW5nID0gYWVzKHggPSBEdXJhY2lvbl9NaW4pKSArDQogIGdlb21fZGVuc2l0eSgpDQoNCmRlbnNfZHVyDQpgYGANCkFwbGlxdWVtb3MgbnVldmFtZW50ZSBmb3JtYXRvcyBwYXJhIHVuYSBtZWpvciB2aXN1YWxpemFjacOzbjoNCmBgYHtyfQ0KZGVuc19kdXIgPC0gZ2dwbG90KGRhdGEgPSBkZl9kdXIsDQogICAgICAgICAgICAgICAgICAgbWFwcGluZyA9IGFlcyh4ID0gRHVyYWNpb25fTWluKSkgKw0KICBnZW9tX2RlbnNpdHkoZmlsbCA9ICJ5ZWxsb3ciLA0KICAgICAgICAgICAgICAgY29sb3IgPSAiZ29sZGVucm9kIikgKw0KICB4bGFiKCJEdXJhY2nDs24gZW4gTWludXRvcyAobG9nKSIpICsNCiAgeWxhYigiIikgKw0KICBnZ3RpdGxlKCJEaXN0cmlidWNpw7NuIGRlIER1cmFjacOzbiBkZSBTZXNpb25lcyIpICsNCiAgdGhlbWVfY2xhc3NpYygpICsNCiAgc2NhbGVfeF9sb2cxMCgpDQoNCmRlbnNfZHVyDQpgYGANCk5vdGVtb3MgcXVlIGxhIGluZm9ybWFjacOzbiBxdWUgZW50cmVnYSBlbCBoaXN0b2dyYW1hIHkgbGEgZGVuc2lkYWQsIHkgcXVlIGVzdMOhIGFzb2NpYWRhIGFsIGNvbXBvcnRhbWllbnRvIGRlIGxhIHZhcmlhYmxlICJEdXJhY2lvbl9NaW4iIGVzIGVxdWl2YWxuZXRlLiBQb3IgbG8gcXVlIGVzdGUgZ3LDoWZpY28gcmVzdWx0YSB1bmEgYWx0ZXJuYXRpdmEgdsOhbGlkYSwgZW4gY2FzbyBkZSBlc3RpbWFyIGNvbnZlbmllbnRlIHBvciBwYXJ0ZSBkZWwgYW5hbGlzdGEuDQoNCiMjIyAyLjQgQ2Fqb25lcyAoY29uIGJpZ290ZXMpDQoNCk90cm8gZ3LDoWZpY28gbXV5IHVzYWRvIGEgbml2ZWwgZGUgYW7DoWxpc2lzIGRlIHVuYSB2YXJpYWJsZSwgZXMgZWwgY2Fqw7NuIGNvbiBiaWdvdGVzLCBFbiBlbCBjdWFsIHBvZGVtb3MgdmlzdWFsaXphciBkZSBmb3JtYSBpbm1lZGlhdGEsIG1lZGlkYXMgZGUgY2VudHJhbGlkYWQgeSBkZSBwb3NpY2nDs24gZGUgdW5hIHZhcmlhYmxlLCBhc8OtIGNvbW8gdGFtYmnDqW4gbG9zIHBvdGVuY2lhbGVzIGRhdG9zIGF0w61waWNvcyBxdWUgZXhpc3Rhbi4NCg0KU2lnYW1vcyBlc3R1ZGlhbmRvIGxhIGR1cmFjacOzbiBkZSBsYXMgc2VzaW9uZXMsIHBlcm8gZW4gZXN0YSBvY2FzacOzbiBoYWdhbW9zIHVuIGNhasOzbiBkZSBsYSBkdXJhY2nDs24gcGFyYSBhcXVlbGxhcyBzZXNpb25lcyBjdXlvIHBhw61zIHNlYSBFc3RhZG9zIFVuaWRvcy4gVXRpbGljZW1vcyBsYSBmdW5jacOzbiAqZ2VvbV9ib3hwbG90KiB5IGNyZWVtb3MgZWwgb2JqZXRvICpib3hfZHVyKi4NCg0KYGBge3J9DQpkZl9lZXV1IDwtIGRmX3Nlc19nZW8gJT4lDQogIGZpbHRlcihQYWlzID09ICJFc3RhZG9zIFVuaWRvcyIpICU+JQ0KICBmaWx0ZXIoRHVyYWNpb25fTWluID4gMCkNCg0KYm94X2R1ciA8LSBnZ3Bsb3QoZGF0YSA9IGRmX2VldXUsDQogICAgICAgICAgICAgICAgICAgbWFwcGluZyA9IGFlcyh4ID0gRHVyYWNpb25fTWluKSkgKw0KICBnZW9tX2JveHBsb3QoKQ0KDQpib3hfZHVyDQpgYGANCkNvbW8gc2UgcHVlZGUgZXZpZGVuY2lhciwgbG9zIHBvdGVuY2lhbGVzIGNhc29zIGF0w61waWNvcywgZ2VuZXJhbiBjb25mdXNpw7NuLCBwb3IgbG8gcXVlIHNlcsOtYSBwcmVmZXJpYmxlIHF1aXRhcmxvcyB5IHZvbHZlciBhIGdyYWZpY2FyLiBBcGxpcXVlbW9zIGxhIHJlZ2xhIGRlIGV4Y2x1c2nDs24gZGUgMS41IHJhbmdvcyBpbnRlcmN1YXJ0aWxlcyBwYXJhIHF1aXRhcmxvcyBkZSBudWVzdHJvIGNhasOzbiwgcmVjb3JkYW5kbyBxdWUgdW4gdmFsb3IgKngqIGVzIG91dGxpZXIgc2kgDQoNCiQkIHggPiBjdWFydGlsXzMgKyAxLjUqSVFSICAkJA0KYGBge3J9DQpkZl9lZXV1IDwtIGRmX3Nlc19nZW8gJT4lDQogIGZpbHRlcihQYWlzID09ICJFc3RhZG9zIFVuaWRvcyIpICU+JQ0KICBmaWx0ZXIoRHVyYWNpb25fTWluID4gMCkgJT4lDQogIGZpbHRlcihEdXJhY2lvbl9NaW4gPD0gcXVhbnRpbGUoRHVyYWNpb25fTWluLDAuNzUpICsgMS41KklRUihEdXJhY2lvbl9NaW4pKQ0KDQpib3hfZHVyIDwtIGdncGxvdChkYXRhID0gZGZfZWV1dSwNCiAgICAgICAgICAgICAgICAgICBtYXBwaW5nID0gYWVzKHggPSBEdXJhY2lvbl9NaW4pKSArDQogIGdlb21fYm94cGxvdCgpDQoNCmJveF9kdXINCmBgYA0KVGVybWluZW1vcyBlbnRvbmNlcyBjb24gZWwgZm9ybWF0by4NCg0KYGBge3J9DQpib3hfZHVyIDwtIGdncGxvdChkYXRhID0gZGZfZWV1dSwNCiAgICAgICAgICAgICAgICAgICBtYXBwaW5nID0gYWVzKHggPSBEdXJhY2lvbl9NaW4pKSArDQogIGdlb21fYm94cGxvdChmaWxsID0gIm9yYW5nZSIsDQogICAgICAgICAgICAgICBjb2xvciA9ICJibGFjayIpICsNCiAgeGxhYigiRHVyYWNpw7NuIGVuIE1pbnV0b3MiKSArDQogIGdndGl0bGUoIkR1cmFjacOzbiBkZSBTZXNpb25lcyBkZSBFc3RhZG9zIFVuaWRvcyIpICsNCiAgdGhlbWVfY2xhc3NpYygpICsNCiAgdGhlbWUoYXhpcy50ZXh0LnkgPSBlbGVtZW50X2JsYW5rKCkpDQoNCmJveF9kdXINCmBgYA0KDQojIyAzLiBHcsOhZmljb3MgQml2YXJpYWJsZXMNCg0KVW5hIHZleiBxdWUgaGVtb3MgZGVzYXJyb2xsYWRvIHZpc3VhbGl6YWNpb25lcyBjb24gdW5hIHNvbGEgdmFyaWFibGUsIHBhc2Vtb3MgYWhvcmEgYSBlc3R1ZGlhciBsb3MgZ3LDoWZpY29zIGRlIGRvcyB2YXJpYWJsZXMuIEVzdGUgdGlwbyBkZSBncsOhZmljb3Mgc29uIG11eSDDunRpbGVzIHBhcmEgcmVsYWNpb25hciBlbCBjb21wb3J0YW1pZW50byBkZSBhdHJpYnV0b3MgZXNwZWPDrWZpY29zIHkgc2VyIGVsIHByaW1lciBwYXNvIHBhcmEgdW4gYW7DoWxpc2lzIGRlIGNhdXNhbGlkYWQsIGFzw60gY29tbyBwYXJhIG90cm8gdGlwbyBkZSBhbsOhbGlzaXMgY29tbyBsYXMgc2VnbWVudGFjaW9uZXMuDQoNCiMjIyAzLjEgRGlzcGVyc2nDs24NCg0KTG9zIGRpYWdyYW1hcyBkZSBkaXNwZXJzacOzbiwgY29tw7pubWVudGUgZGVub21pbmFkb3MgZ3LDoWZpY29zICJkZSBwdW50b3MiLCBwZXJtaXRlbiB2aXN1YWxpemFyIHBhcmVzIGRlIGNvb3JkZW5hZGFzICR4LHkkIGRvbmRlIGNhZGEgdW5vIGRlIMOpc3RvcyByZXByZXNlbnRhIHVuIHJlZ2lzdHJvIGRlbnRybyBkZSBtaXMgZGF0b3MuDQoNClBhcmEgZWplbXBsaWZpY2FyLCBlc3R1ZGllbW9zIGVsIGNvbXBvcnRhbWllbnRvIGRlIGxhcyBww6FnaW5hcyB2aXNpdGFkYXMgY29uIGxhIGR1cmFjacOzbiBkZSBsYXMgc2VzaW9uZXMsIGZpbHRyYW5kbyBzb2xhbWVudGUgbGFzIHByaW1lcmFzIHNlc2lvbmVzIGRlIGNhZGEgdXN1YXJpbyBkZSBJdGFsaWEgeSBBbGVtYW5pYSwgeSBleGNsdXllbmRvIHRhbWJpw6luIGFxdWVsbGFzIGNvbiBkdXJhY2lvbmVzIGRlIDAuIFVzZW1vcyBwYXJhIGVzdG8gbGEgZ2VvbWV0csOtYSAqZ2VvbV9wb2ludCouDQoNCmBgYHtyfQ0KZGZfZGlzcCA8LSBkZl9zZXNfZ2VvICU+JQ0KICBmaWx0ZXIoTnVtX1Nlc2lvbiA9PSAxKSAlPiUNCiAgZmlsdGVyKFBhaXMgPT0gIkFsZW1hbmlhIiB8IFBhaXMgPT0gIkl0YWxpYSIpICU+JQ0KICBmaWx0ZXIoRHVyYWNpb25fTWluID4gMCkNCg0KZGlzcF9kdXJwYWcgPC0gZ2dwbG90KGRhdGEgPSBkZl9kaXNwLA0KICAgICAgICAgICAgICAgICAgICAgIG1hcHBpbmcgPSBhZXMoeCA9IER1cmFjaW9uX01pbiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHkgPSBQYWdpbmFzKSkgKw0KICBnZW9tX3BvaW50KCkNCg0KZGlzcF9kdXJwYWcNCmBgYA0KRGUgZm9ybWEsIGdlbmVyYWwgc2UgcHVlZGUgYXZpZGVuY2lhciBxdWUgZ3JhbiBwYXJ0ZSBkZSBsb3MgZGF0b3Mgc2UgZW5jdWVudHJhbiBlbiB1biBpbnRlcnZhbG8gZGUgdGllbXBvIGVudHJlIDAgeSAxIG1pbnV0byB5IGNvbiBww6FnaW5hcyBlYnRyZSAxIHkgMTUuIFBhcmEgbWVqb3JhciBsYSB2aXNpYmlsaWRhZCBkZSBsYSBpbmZvcm1hY2nDs24gdXNlbW9zIHVuIHJlZXNjYWxhbWllbnRvLCBwZXJvIGVzdGEgdmV6IHNvbGFtZW50ZSBhanVzdGVtb3MgZWwgcmFuZ28gZGUgbG9zIGVqZXMgY29uIGxhcyBmdW5jaW9uZXMgKnhsaW0qIHkgKnlsaW0qLg0KDQpgYGB7cn0NCmRpc3BfZHVycGFnIDwtIGdncGxvdChkYXRhID0gZGZfZGlzcCwNCiAgICAgICAgICAgICAgICAgICAgICBtYXBwaW5nID0gYWVzKHggPSBEdXJhY2lvbl9NaW4sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB5ID0gUGFnaW5hcykpICsNCiAgZ2VvbV9wb2ludCgpICsNCiAgeWxpbShjKDAsMTApKSArDQogIHhsaW0oYygwLDEpKQ0KDQpkaXNwX2R1cnBhZw0KYGBgDQoNClBvbmdhbW9zIGFob3JhIGxvcyBmb3JtYXRvcyBxdWUgbmVjZXNpdGVtb3MgYSBudWVzdHJvIGdyw6FmaWNvLg0KYGBge3J9DQpkaXNwX2R1cnBhZyA8LSBnZ3Bsb3QoZGF0YSA9IGRmX2Rpc3AsDQogICAgICAgICAgICAgICAgICAgICAgbWFwcGluZyA9IGFlcyh4ID0gRHVyYWNpb25fTWluLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeSA9IFBhZ2luYXMpKSArDQogIGdlb21fcG9pbnQoY29sb3IgPSAidG9tYXRvIiwNCiAgICAgICAgICAgICBhbHBoYSA9IDAuNzUpICsgI0VzdGUgcGFyYW1ldHJvIHBlcm1pdGUgZGFyIHRyYW5zcGFyZW5jaWENCiAgeWxpbShjKDAsMTApKSArDQogIHhsaW0oYygwLDEpKSArDQogIHlsYWIoIlBhZ2luYXMgdmlzaXRhZGFzIikgKw0KICB4bGFiKCJEdXJhY2nDs24gZW4gTWludXRvcyIpICsNCiAgZ2d0aXRsZSgiUGFnaW5hcyB2cy4gRHVyYWNpw7NuIGRlIFNlc2lvbmVzIikgKw0KICB0aGVtZV9jbGFzc2ljKCkNCg0KZGlzcF9kdXJwYWcNCmBgYA0KQSBuaXZlbCBnZW5lcmFsLCBzZSBldmlkZW5jaWEgcXVlLCBzYWx2byB1bm9zIHBvY29zIGNhc29zLCBleGlzdGUgdW5hIHJlbGFjacOzbiBwb3NpdGl2YSBlbnRyZSBsYXMgZG9zIHZhcmlhYmxlcy4gQSBtZW5vciBkdXJhY2nDs24sIG1heW9yZXMgcMOhZ2luYXMgdmlzaXRhZGFzLiBJbnRlbnRlIGNvbW8gZXhwZXJ0byBkZSBuZWdvY2lvIGVuY29udHJhciB1bmEgZXhwbGljYWNpw7NuIGEgZXN0ZSBjb21wb3J0YW1pZW50by4NCg0KIyMjIDMuMiBDdXJ2YSBkZSBzdWF2aXphbWllbnRvDQoNCkFzw60gY29tbyB2aW1vcyBlbiBlbCBncsOhZmljbyBhbnRlcmlvciwgYWwgcGFyZWNlIGV4aXN0aXLDrWEgdW5hIHJlbGFjacOzbiBuZWdhdGl2YSBlbnRyZSBsYXMgdmFyaWFibGVzICpQYWdpbmFzKiB5ICpEdXJhY2lvbl9NaW4qLiBTaW4gZW1iYXJnbywgcGFyYSB1biBvam8gcG9jbyBlbnRyZW5hZG8gZXN0byBwb2Ryw61hIG5vIHNlciB0YW4gZXZpZGVudGUuIFBvciBlc3RhIHJhesOzbiBleGlzdGVuIGxhcyBjdXJ2YXMgZGUgc3VhdmlhbWllbnRvIGluY29ycG9yYW5kbyB1bmEgbnVldmEgZ2VvbWV0csOtYSBhbCBncsOhZmljbyBtZWRpYW50ZSAqZ2VvbV9zbW9vdGgqLg0KDQpgYGB7cn0NCnNtb290aF9kdXJwYWcgPC0gZ2dwbG90KGRhdGEgPSBkZl9kaXNwLA0KICAgICAgICAgICAgICAgICAgICAgIG1hcHBpbmcgPSBhZXMoeCA9IER1cmFjaW9uX01pbiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHkgPSBQYWdpbmFzKSkgKw0KICBnZW9tX3BvaW50KGNvbG9yID0gInRvbWF0byIsDQogICAgICAgICAgICAgYWxwaGEgPSAwLjc1KSArDQogIHlsaW0oYygwLDEwKSkgKw0KICB4bGltKGMoMCwxKSkgKw0KICB5bGFiKCJQYWdpbmFzIHZpc2l0YWRhcyIpICsNCiAgeGxhYigiRHVyYWNpw7NuIGVuIE1pbnV0b3MiKSArDQogIGdndGl0bGUoIlBhZ2luYXMgdnMuIER1cmFjacOzbiBkZSBTZXNpb25lcyIpICsNCiAgdGhlbWVfY2xhc3NpYygpICsNCiAgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxtIikNCg0Kc21vb3RoX2R1cnBhZw0KYGBgDQpFbCBwYXLDoW1ldHJvICptZXRob2QqIHV0aWxpemFkbyBoYWNlIHJlZmVyZW5jaWEgYSBxdWUgcXVlcmVtb3MgdW4gYWp1c3RlIG1lZGlhbnRlIHVuIG1vZGVsbyBsaW5lYWwgKGxtIHBvciBzdXMgc2lnbGFzIGVuIGluZ2zDqXMpLg0KDQpFbiBlZmVjdG8sIG5vdGVtb3MgcXVlIGV4aXN0ZSB1bmEgbGV2ZSByZWxhY2nDs24gaW52ZXJzYSBlbnRyZSBsYXMgdmFyaWFibGVzLCBzaW4gZW1iYXJnbywgZXN0byBwYXJlY2UgYcO6biBubyBzZXIgc3VmaWNpZW50ZW1lbnRlIGNsYXJvLiBQYXJhIGZhY2lsaXRhciBhw7puIG3DoXMgbGEgdmlzdWFsaXphY2nDs24gaW5jb3Jwb3JlbW9zIHVuIHBhcsOhbWV0cm8gYWRpY2lvbmFsIGEgZXN0YSBnZW9tZXRyw61hIGxsYW1hZG8gKmZvcm11bGEqIHRhbCBxdWUgbGEgdmFyaWFibGUgcMOhZ2luYXMgZXN0w6kgZGFkYSBlbiBmdW5jacOzbiBkZWwgbG9nYXJpdG1vIGRlIGxhIGR1cmFjacOzbi4NCg0KJCQgUGFnaW5hcyA9IGYobG9nKER1cmFjaW9uX3tNaW59KSkgJCQNCg0KYGBge3J9DQpzbW9vdGhfZHVycGFnIDwtIGdncGxvdChkYXRhID0gZGZfZGlzcCwNCiAgICAgICAgICAgICAgICAgICAgICBtYXBwaW5nID0gYWVzKHggPSBEdXJhY2lvbl9NaW4sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB5ID0gUGFnaW5hcykpICsNCiAgZ2VvbV9wb2ludChjb2xvciA9ICJ0b21hdG8iLA0KICAgICAgICAgICAgIGFscGhhID0gMC43NSkgKw0KICB5bGltKGMoMCwxMCkpICsNCiAgeGxpbShjKDAsMSkpICsNCiAgeWxhYigiUGFnaW5hcyB2aXNpdGFkYXMiKSArDQogIHhsYWIoIkR1cmFjacOzbiBlbiBNaW51dG9zIikgKw0KICBnZ3RpdGxlKCJQYWdpbmFzIHZzLiBEdXJhY2nDs24gZGUgU2VzaW9uZXMiKSArDQogIHRoZW1lX2NsYXNzaWMoKSArDQogIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsbSIsDQogICAgICAgICAgICAgIGZvcm11bGEgPSB5IH4gbG9nKHgpKQ0Kc21vb3RoX2R1cnBhZw0KYGBgDQojIyMgMy4zIENvbHVtbmFzDQoNClVuIGdyw6FmaWNvIHF1ZSByZXN1bHRhIG11eSDDunRpbCBwYXJhIGVzdHVkaWFyIHVuYSB2YXJpYWJsZSBudW3DqXJpY2EgeSBzdSByZWxhY2nDs24gY29uIG90cmEgcXVlIG5vIGxvIGVzICh0ZXh0bywgZmFjdG9yKSwgZXMgZWwgZ3LDoWZpY28gZGUgY29sdW1uYXMuIFVuIHB1bnRvIGRlIGN1aWRhZG8gYXF1w60gZXMgcXVlIG5vIGRlYmVtb3MgY29uZnVuZGlyIGxhcyBiYXJyYXMgZGUgbGFzIGNvbHVtbmFzLiBMYXMgcHJpbWVyYXMgc2UgdXRpbGl6YW4gcGFyYSB2aXN1YWxpemFyIGxhIGZyZWN1ZW5jaWEgbyBkaXN0cmlidWNpw7NuIGRlIHVuYSBzb2xhIHZhcmlhYmxlLCBtaWVudHJhcyBxdWUgbGFzIHNlZ3VuZGFzIG1pZGVuIGVsIGNvbXBlcmFtaWVudG8gZGUgdW5hIHZhcmlhYmxlLCBjb24gcmVsYWNpw7NuIGEgb3RyYS4NCg0KUGFyYSBlbnRlbmRlciBkZSBtZWpvciBmb3JtYSwgc3Vwb25nYW1vcyBxdWUgZGVzZWFtb3MgY29ub2NlciBsYSBkdXJhY2nDs24gcHJvbWVkaW8gZW4gc2VndW5kb3MgZGUgbGFzIHByaW1lcmFzIHNlc2lvbmVzLCB5IGNvbW8gZXN0YSBkdXJhY2nDs24gY2FtYmlhIGVuIGZ1bmNpw7NuIGRlbCBkaXNwb3NpdGl2byB1dGlsaXphZG8uIFBBcmEgZXN0byB1c2Ftb3MgbGEgZ2VvbWV0csOtYSBkYWRhIHBvciBsYSBmdW5jacOzbiAqZ2VvbV9jb2wqLCB5IGFudGVzIGNyZWFtb3MgdW5hIHRhYmxhIHF1ZSByZXN1bWEgbGEgbcOpdHJpY2EgcXVlIGRlc2VhbW9zIHZpc3VhbGl6YXIuDQoNCmBgYHtyfQ0KZGZfZHVyZGlzcCA8LSBkZl9zZXNfZ2VvICU+JQ0KICBmaWx0ZXIoTnVtX1Nlc2lvbiA9PSAxKSAlPiUNCiAgZmlsdGVyKER1cmFjaW9uX01pbiA+IDApICU+JQ0KICBncm91cF9ieShEaXNwb3NpdGl2bykgJT4lDQogIHN1bW1hcmlzZShEdXJhY2lvbl9Qcm9tID0gbWVhbihEdXJhY2lvbl9NaW4pKjYwKSAlPiUNCiAgdW5ncm91cCgpDQoNCmNvbF9kdXJkaXNwIDwtIGdncGxvdChkYXRhID0gZGZfZHVyZGlzcCwNCiAgICAgICAgICAgICAgICAgICAgICBtYXBwaW5nID0gYWVzKHggPSBEaXNwb3NpdGl2bywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHkgPSBEdXJhY2lvbl9Qcm9tKSkgKw0KICBnZW9tX2NvbCgpDQoNCmNvbF9kdXJkaXNwDQpgYGANClJlc3VsdGEgaW50ZXJlc2FudGUgb2JzZXJ2YXIgcXVlIGxhcyBwZXJzb25hcyBxdWUgdXRpbGl6YW4gQW5kcm9pZCB5IFBDLCBtYW50aWVuZW4gc2VzaW9uZXMgZW50cmUgdW4gMjUlIHkgdW4gMzAlIG3DoXMgbGFyZ2FzIGVuIGR1cmFjacOzbiBxdWUgYXF1ZWxsb3MgY29uIGRpc3Bvc2l0aXZvcyBBcHBsZS4NCg0KUG9uZ2Ftb3MgYWhvcmEgZWwgZm9ybWF0byBxdWUgbm9zIGZhY2lsaXRlIGxhIHZpc3VhbGl6YWNpw7NuIGRlbCBncsOhZmljbyBvYnRlbmlkby4NCmBgYHtyfQ0KY29sX2R1cmRpc3AgPC0gZ2dwbG90KGRhdGEgPSBkZl9kdXJkaXNwLA0KICAgICAgICAgICAgICAgICAgICAgIG1hcHBpbmcgPSBhZXMoeCA9IERpc3Bvc2l0aXZvLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeSA9IER1cmFjaW9uX1Byb20pKSArDQogIGdlb21fY29sKGZpbGwgPSAicHVycGxlIiwNCiAgICAgICAgICAgYWxwaGEgPSAwLjUsDQogICAgICAgICAgIGNvbG9yID0gInB1cnBsZSIpICsNCiAgeGxhYigiRGlzcG9zaXRpdm8iKSArDQogIHlsYWIoIkR1cmFjacOzbiBQcm9tZWRpbyAoZW4gc2VndW5kb3MpIikgKw0KICBnZ3RpdGxlKCJEdXJhY2nDs24gdnMgRGlzcG9zaXRpdm8gZW4gU2VzaW9uZXMiKSArDQogIHRoZW1lX2dyYXkoKQ0KDQpjb2xfZHVyZGlzcA0KYGBgDQojIyMgMy40IENham9uZXMgKGNvbiBiaWdvdGVzKQ0KDQpZYSB2aW1vcyBxdWUgbG9zIGNham9uZXMgY29uIGJpZ290ZXMgcGVybWl0ZW4gY29ub2NlciBsYSBkaXN0cmlidWNpw7NuIHkgbGFzIHByaW5jaXBhbGVzIG1lZGlkYXMgZGUgY2VudHJhbGlkYWQgeSBwb3NpY2nDs24gZGUgdW5hIHZhcmlhYmxlLCBzaSBhaG9yYSBhw7FhZGltb3MgbGEgZXN0w6l0aWNhICJ5IiwgcG9kZW1vcyBhZGVtw6FzIGNvbXBhcmFyIGxhcyBkaXN0cmlidWNpb25lcyBkZSB1bmEgdmFyaWFibGUgY29uIHJlbGFjacOzbiBhIG90cmEuDQoNCkNvbnRpbnVhbmRvIG9jbiBlbCBlamVtcGxvIGFudGVyaW9yLCBkYWRvcyBvdHJvcyByZXN1bHRhZG9zIHF1ZSBoZW1vcyBvYnRlbmlkbyBwcmV2aWFtZW50ZSwgcG9kcsOtYSBkaXNjdXRpcnNlIHF1ZSBsYSBkdXJhY2nDs24gZGUgbGFzIHNlc2lvbmVzIHB1ZWRlIGVzdGFyIHNlc2dhZGEgbyBwcmVzZW50YXIgdmFsb3JlcyBhdMOtcGljb3MsIHBvciBsbyBxdWUgbGEgY29uY2x1c2nDs24gZGUgcXVlIGxhIGR1cmFjacOzbiBlcyBkaWZlcmVudGUgcG9yIGRpc3Bvc2l0aXZvIG5vIGVzIHN1ZmljaWVudGVtZW50ZSByb2J1c3RhLiBQYXJhIGNvbXByb2JhciBlc3RvLCB1c2Vtb3MgY2Fqb25lcyBjb24gYmlnb3RlcywgZG9uZGUgZXhjbHV5YW1vcyBsb3MgZGF0b3MgYXTDrXBpY29zLCBjb21vIHlhIGxvIGhpY2ltb3MgcHJldmlhbWVudGUuDQoNCmBgYHtyfQ0KZGZfZHVyZGlzcDIgPC0gZGZfc2VzX2dlbyAlPiUNCiAgZmlsdGVyKE51bV9TZXNpb24gPT0gMSkgJT4lDQogIGZpbHRlcihEdXJhY2lvbl9NaW4gPiAwKSAlPiUNCiAgZmlsdGVyKER1cmFjaW9uX01pbiA8PSBxdWFudGlsZShEdXJhY2lvbl9NaW4sMC43NSArIDEuNSpJUVIoRHVyYWNpb25fTWluKSkpICU+JQ0KICBtdXRhdGUoRHVyYWNpb25fU2VnID0gRHVyYWNpb25fTWluKjYwKQ0KDQpib3hfZHVyZGlzcCA8LSBnZ3Bsb3QoZGF0YSA9IGRmX2R1cmRpc3AyLA0KICAgICAgICAgICAgICAgICAgICAgIG1hcHBpbmcgPSBhZXMoeCA9IERpc3Bvc2l0aXZvLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeSA9IER1cmFjaW9uX1NlZykpICsNCiAgZ2VvbV9ib3hwbG90KCkNCg0KYm94X2R1cmRpc3ANCmBgYA0KTWVqb3JlbW9zIG51ZXN0cmEgdmlzdWFsaXphY2nDs24gY29uIHVuIGVzY2FsYW1pZW50byBkZSBswrQraW1pdGVzIGVuIGVsIGVqZSB5IGZvcm1hdG9zLg0KDQpgYGB7cn0NCmJveF9kdXJkaXNwIDwtIGdncGxvdChkYXRhID0gZGZfZHVyZGlzcDIsDQogICAgICAgICAgICAgICAgICAgICAgbWFwcGluZyA9IGFlcyh4ID0gRGlzcG9zaXRpdm8sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB5ID0gRHVyYWNpb25fU2VnKSkgKw0KICBnZW9tX2JveHBsb3QoZmlsbCA9ICJnb2xkZW5yb2QxIiwNCiAgICAgICAgICAgICAgIGNvbG9yID0gImdvbGRlbnJvZDQiKSArDQogIHlsaW0oYygwLDIwKSkgKw0KICB5bGFiKCJEdXJhY2lvbiAoU2VndW5kb3MpIikgKw0KICB4bGFiKCJEaXNwb3NpdGl2byIpICsNCiAgZ2d0aXRsZSgiQ29tcG9ydGFtaWVudG8gZGUgbGEgRHVyYWNpw7NuIHBvciBEaXNwb3NpdGl2byIpICsNCiAgdGhlbWVfY2xhc3NpYygpDQoNCmJveF9kdXJkaXNwDQpgYGANCsK/Q3JlZSBxdWUgbGEgY29uY3NsdXNpw7NuIGFsY2FuemFkYSBwcmV2aWFtZW50ZSBwb3IgbGFzIGNvbHVtbmFzIGNhbWJpYSBhIGxhIGx1eiBkZSBlc3RlIGdyw6FmaWNvPw0KDQojIyMgMy41IEzDrW5lYXMgZGUgc2VyaWVzIHRlbXBvcmFsZXMNCg0KRWwgdXNvIGRlIGzDrW5lYXMgZW4gZ3LDoWZpY29zIGRlIGRhdG9zLCBzdWVsZSBzZXIgbWFsIGVudGVuZGlkbyB5IHNvYnJldXRpbGl6YWRvIHBhcmEgdmlzdWFsaXphciBjb3NhcyBxdWUgbm8gZGViZXLDrWFuIHJlcHJlc2VudGFyc2UgcG9yIGVzdGEgZ2VvbWV0csOtYS4gTGEgbmF0dXJhbGV6YSBkZSB1bmEgbMOtbmVhIGVzIGxhIGRlIGNvbnRpbnVpZGFkIHBvciBsbyBxdWUgc3UgdXNvIHNlIHJlc3RyaW5qZSBhIGFxdWVsbGFzIHZhcmlhYmxlcyBjb24gdW5hIG5hdHVyYWxlemEgImRlIGZsdWpvIi4gRWwgdGlwbyBkZSB2YXJpYWJsZSBxdWUgbWVqb3IgY3VtcGxlIGVzdGEgY2FyYWN0ZXLDrXN0aWNhIGVzIGp1c3RhbWVudGUgbGEgZGUgZmVjaGFzLCBwdWVzIGVzIGxhIGZvcm1hIGRlIHByZXNlbnRhciBlbCBmbHVqbyB0ZW1wb3JhbC4NCg0KQ29tbyB1biBwcmltZXIgZWplbXBsbywgdmVhbW9zIGVsIG7Dum1lcm8gZGUgc2VzaW9uZXMgcXVlIHNlIGhhbiB0ZW5pZG8gZW4gbnVlc3Ryb3MgZGF0b3MgcG9yIGZlY2hhLiBVc2Vtb3MgbGEgZnVuY2nDs24gKmdlb21fbGluZSouDQpgYGB7cn0NCmRmX3Nlc2ZlY2hhIDwtIGRmX3Nlc19nZW8gJT4lDQogIGNvdW50KEZlY2hhKQ0KDQpsaW5lX3NlcyA8LSBnZ3Bsb3QoZGF0YSA9IGRmX3Nlc2ZlY2hhLA0KICAgICAgICAgICAgICAgICAgIG1hcHBpbmcgPSBhZXMoeCA9IEZlY2hhLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeSA9IG4pKSArDQogIGdlb21fbGluZSgpDQoNCmxpbmVfc2VzDQpgYGANCkEgcGFydGlyIGRlbCBzZWd1bmRvIHNlbWVzdHJlIGRlIDIwMTkgc2UgZXZpZGVuY2lhIHVuIGluY3JlbWVudG8gaW1wb3J0YW50ZSBhIG5pdmVsIGRlIHNlc2lvbmVzIHJlZ2lzdHJhZGFzLCBwYXNhbmRvIGRlIHVuIHByb21lZGlvIGRpYXJpbyBkZSBhcHJveGltYWRhbWVudGUgMTUwIGEgdW5vIGRlIDMwMC4gQ29tcGxldGVtb3MgZXN0ZSBncsOhZmljbyBjb24gdW4gZm9ybWF0byBhZGVjdWFkby4NCg0KYGBge3J9DQpsaW5lX3NlcyA8LSBnZ3Bsb3QoZGF0YSA9IGRmX3Nlc2ZlY2hhLA0KICAgICAgICAgICAgICAgICAgIG1hcHBpbmcgPSBhZXMoeCA9IEZlY2hhLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeSA9IG4pKSArDQogIGdlb21fbGluZShjb2xvciA9ICJsaWdodGdyZWVuIiwNCiAgICAgICAgICAgIHNpemUgPSAxKSArICNFc3RlIHBhcmFtZXRybyBwZXJtaXRlIGRlZmluaXIgZWwgZ3Jvc29yIGRlIGxhIGxpbmVhDQogIHlsYWIoIiMgU2VzaW9uZXMiKSArDQogIHhsYWIoIiIpICsNCiAgZ2d0aXRsZSgiRXZvbHVjacOzbiBkZSBTZXNpb25lcyIpICsNCiAgdGhlbWVfZGFyaygpDQoNCmxpbmVfc2VzDQpgYGANCkhhZ2Ftb3Mgb3RybyBlamVtcGxvLCBwZXJvIGFob3JhIGVuIHZleiBkZSBlc3R1ZGlhciBsYXMgc2VzaW9uZXMgcG9yIGZlY2hhLCB2ZWFtb3MgbGEgZHVyYWNpw7NuIHByb21lZGlvIGRlIHNlc2lvbmVzIGVuIHNlZ3VuZG9zLCBwZXJvIGFncnVwYWRvIHBvciBtZXMgeSBhw7FvLg0KDQpgYGB7cn0NCmRmX2R1cm1lcyA8LSBkZl9zZXNfZ2VvICU+JQ0KICBtdXRhdGUoUGVyaW9kbyA9IHllYXIoRmVjaGEpKSAlPiUNCiAgbXV0YXRlKE1lcyA9IG1vbnRoKEZlY2hhKSkgJT4lDQogIG11dGF0ZShQZXJpb2RvTWVzID0gUGVyaW9kbyoxMDAgKyBNZXMpICU+JQ0KICBncm91cF9ieShQZXJpb2RvTWVzKSAlPiUNCiAgc3VtbWFyaXNlKER1cmFjaW9uX1Byb20gPSBtZWFuKER1cmFjaW9uX01pbikqNjApICU+JQ0KICB1bmdyb3VwKCkgJT4lDQogIGFycmFuZ2UoUGVyaW9kb01lcykNCg0KbGluZV9kdXJtZXMgPC0gZ2dwbG90KGRhdGEgPSBkZl9kdXJtZXMsDQogICAgICAgICAgICAgICAgICAgICAgbWFwcGluZyA9IGFlcyh4ID0gUGVyaW9kb01lcywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHkgPSBEdXJhY2lvbl9Qcm9tKSkgKw0KICBnZW9tX2xpbmUoKQ0KDQpsaW5lX2R1cm1lcw0KYGBgDQpOb3RlbW9zIHF1ZSBlbiBsYSBncsOhZmljYSByZXN1bHRhbnRlLCBleGlzdGUgdW4gZXNwYWNpbyBjb24gdmFsb3JlcyBkZSBQZXJpb2RvTWVzIHNpbiBzZW50aWRvIChpLmUuIDIwMTkzMCkuIEVzdG8gc3VjZWRlIHBvcnF1ZSBSIGVudGllbmRlIGVzdGEgdmFyaWFibGUgY29tbyBudW3DqXJpY2EgZSBpbnRlbnRhIGNvbXBsZXRhcmxhIGFsIG1vbWVudG8gZGUgZ3JhZmljYXIuIFBhcmEgYXJyZWdsYXIgZXN0byBpbmNsdXlhbW9zIGxhIGVzdMOpdGljYSAqZ3JvdXAqIGNvbiB1biB2YWxvciBkZSAxLCBsbyBjdWFsIG5vcyBwZXJtaXRlIGNvbnNvbGlkYXIgbG9zIHZhbG9yZXMgeSBubyBjb25zaWRlcmFyIGVzdGUgYXV0b2NvbXBsZXRhZG87IHkgYWRlbcOhcyBkZWZpbmFtb3MgbGEgdmFyaWFibGUgY29tbyBmYWN0b3IgYWwgbW9tZW50byBkZSBncmFmaWNhcmxhIHBhcmEgcXVlIG5vIHNlIGFzdW1hIHF1ZSBlcyBkZSB0aXBvIG51bcOpcmljYS4NCg0KYGBge3J9DQpsaW5lX2R1cm1lcyA8LSBnZ3Bsb3QoZGF0YSA9IGRmX2R1cm1lcywNCiAgICAgICAgICAgICAgICAgICAgICBtYXBwaW5nID0gYWVzKHggPSBhcy5mYWN0b3IoUGVyaW9kb01lcyksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB5ID0gRHVyYWNpb25fUHJvbSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdyb3VwID0gMSkpICsNCiAgZ2VvbV9saW5lKCkNCg0KbGluZV9kdXJtZXMNCmBgYA0KRmluYWxpY2Vtb3MgY29uIGVsIGZvcm1hdG8gcGFyYSB1bmEgdmlzdWFsaXphY2nDs24gYWRlY3VhZGEuDQoNCmBgYHtyfQ0KbGluZV9kdXJtZXMgPC0gZ2dwbG90KGRhdGEgPSBkZl9kdXJtZXMsDQogICAgICAgICAgICAgICAgICAgICAgbWFwcGluZyA9IGFlcyh4ID0gYXMuZmFjdG9yKFBlcmlvZG9NZXMpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeSA9IER1cmFjaW9uX1Byb20sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBncm91cCA9IDEpKSArDQogIGdlb21fbGluZShjb2xvciA9ICJkYXJrcmVkIiwNCiAgICAgICAgICAgIHNpemUgPSAwLjc1LA0KICAgICAgICAgICAgbGluZXR5cGUgPSAyKSArICNFc3RlIHBhcmFtZXRybyBtZSBwZXJtaXRlIGVzY29nZXIgZWwgdGlwbyBkZSBsaW5lYSBhIHV0aWxpemFyDQogIHlsYWIoIkR1cmFjacOzbiBQcm9tZWRpbyBkZSBTZXNpb25lcyIpICsNCiAgeGxhYigiUGVyaW9kb01lcyIpICsNCiAgZ2d0aXRsZSgiRXZvbHVjacOzbiBNZW5zdWFsIGRlIGxhIER1cmFjacOzbiIpICsNCiAgdGhlbWVfY2xhc3NpYygpICsNCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCkpDQoNCmxpbmVfZHVybWVzDQpgYGANCg0KIyMgNC4gR3LDoWZpY29zIE11bHRpdmFyaWFibGVzDQoNClVzYW5kbyBncsOhZmljb3MgZW4gY29vcmRlbmFkYXMgY2FydGVzaWFuYXMgJCB4LHkgJCBpZ3VhbG1lbnRlIHBvZGVtb3MgdmlzdWFsaXphciBtw6FzIGRlIGRvcyB2YXJpYWJsZXMuIFBhcmEgZXN0bywgZXMgaW1wb3J0YW50ZSBoYWNlciB1c28gZGVsIGNvbXBvbmVudGUgZGUgKipFU1RFVElDQVMqKiBjb21vIGlyZW1vcyB2aWVuZG8gZW4gbG9zIHNpZ3VpZW50ZXMgY2Fzb3MuIA0KDQojIyMgNC4xIERlbnNpZGFkZXMNCg0KUmVjb3JkZW1vcyBlbCBlamVtcGxvIGRlbCBncsOhZmljbyBkZSBkaXN0cmlidWNpw7NuIHF1ZSBoYWLDrWFtb3MgdmlzdG8gZW4gbGEgc2VjY2nDs24gZGUgdW5hIHZhcmlhYmxlLCANCg0KYGBge3J9DQpkZW5zX2R1cg0KYGBgDQpOb3MgaW50ZXJlc2EgYWhvcmEgdmVyIHNpIGxhIGRpc3RyaWJ1Y2nDs24gZGlmaWVyZSBwb3IgdGlwbyBkZSBkaXNwb3NpdGl2byAoRXNjcml0b3JpbywgTcOzdmlsKS4gUGFyYSBlc3RvLCB2b2x2YW1vcyBhIGhhY2VyIGVzdGUgZ3LDoWZpY28gcGVybyBhaG9yYSB1c2Vtb3MgY29tbyAqKkVTVEVUSUNBKiogZWwgcmVsbGVubyAoZmlsbCkgeSB5YSBubyBzb2xhbWVudGUgY29tbyBwYXLDoW1ldHJvLg0KDQpgYGB7cn0NCmRlbnNfZHVyMiA8LSBnZ3Bsb3QoZGF0YSA9IGRmX2R1ciwNCiAgICAgICAgICAgICAgICAgICBtYXBwaW5nID0gYWVzKHggPSBEdXJhY2lvbl9NaW4sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmaWxsID0gVGlwb19EaXNwb3NpdGl2bykpICsNCiAgZ2VvbV9kZW5zaXR5KGNvbG9yID0gImdvbGRlbnJvZCIpICsNCiAgeGxhYigiRHVyYWNpw7NuIGVuIE1pbnV0b3MgKGxvZykiKSArDQogIHlsYWIoIiIpICsNCiAgZ2d0aXRsZSgiRGlzdHJpYnVjacOzbiBkZSBEdXJhY2nDs24gZGUgU2VzaW9uZXMiKSArDQogIHRoZW1lX2NsYXNzaWMoKSArDQogIHNjYWxlX3hfbG9nMTAoKQ0KDQpkZW5zX2R1cjINCmBgYA0KTWVqb3JlbW9zIGVzdGUgZ3LDoWZpY28gYWRpY2lvbmFuZG8gY29tbyBmb3JtYXRvcyB1biBncmFkbyBkZSB0cmFuc3BhcmVuY2lhIGRlbCA1MCUsIHkgdW4gcGFyw6FtZXRybyBhZGljaW9uYWwgYSBsYSBnZW9tZXRyw61jYSBsbGFtYWRvICJidyIgKGFuY2hvIGRlIGJhbmRhKSBjb24gdW4gdmFsb3IgZGUgMC4yNS4gDQoNCmBgYHtyfQ0KZGVuc19kdXIyIDwtIGdncGxvdChkYXRhID0gZGZfZHVyLA0KICAgICAgICAgICAgICAgICAgIG1hcHBpbmcgPSBhZXMoeCA9IER1cmFjaW9uX01pbiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZpbGwgPSBUaXBvX0Rpc3Bvc2l0aXZvKSkgKw0KICBnZW9tX2RlbnNpdHkoY29sb3IgPSAiZ29sZGVucm9kIiwNCiAgICAgICAgICAgICAgIGFscGhhID0gMC41LA0KICAgICAgICAgICAgICAgYncgPSAwLjI1KSArDQogIHhsYWIoIkR1cmFjacOzbiBlbiBNaW51dG9zIChsb2cpIikgKw0KICB5bGFiKCIiKSArDQogIGdndGl0bGUoIkRpc3RyaWJ1Y2nDs24gZGUgRHVyYWNpw7NuIGRlIFNlc2lvbmVzIikgKw0KICB0aGVtZV9jbGFzc2ljKCkgKw0KICBzY2FsZV94X2xvZzEwKCkNCg0KZGVuc19kdXIyDQpgYGANCkFsIHBhcmVjZXIgbGEgZHVyYWNpw7NuIGRlIGxhcyBzZXNpb25lcyBlbiBtw7N2aWwgZXMgbGV2ZW1lbnRlIGluZmVyaW9yIGEgbGEgZGUgYXF1ZWxsYXMgcXVlIHNlIGRhbiBwb3IgZGlzcG9zaXRpdm9zIGRlIGVzY3JpdG9yaW8uDQoNCkNvbW8gZWplcmNpY2lvLCBwcnVlYmUgY29uIG90cm9zIGFuY2hvcyBkZSBiYW5kYSBtZW5vcmVzIHkgbWF5b3JlcyBhbCBtb3N0cmFkbyBwYXJhIHZlciBxdcOpIGVmZWN0byB0aWVuZW4gZW4gc3UgZ3LDoWZpY28uDQoNCiMjIyA0LjIgTGluZWFzIGRlIHNlcmllcyBkZSB0aWVtcG8NCg0KU29icmUgbGEgYmFzZSBkZSB1c2FyIGNvbW8gZXN0w6l0aWNhIGVsIHBhcsOhbWV0cm8gKmZpbGwqIGVuIGVsIGNhc28gYW50ZXJpb3IsIHZlYW1vcyBhaG9yYSBlbCB1c28gZGUgKmNvbG9yKiBwYXJhIGhhY2VyIHVuYSBsw61uZWEgZGUgc2VyaWVzIGRlIHRpZW1wbyBkZWwgbsO6bWVybyBkZSBwcmltZXJhcyBzZXNpb25lcyBwZXJvIGRpZmVyZW5jaWFuZG8gcG9yIGZ1ZW50ZS4NCg0KYGBge3J9DQpkZl9zZXNmZWNoYV9mIDwtIGRmX3Nlc19nZW8gJT4lDQogIGZpbHRlcihOdW1fU2VzaW9uID09IDEpICU+JQ0KICBjb3VudChGZWNoYSwNCiAgICAgICAgVGlwb0Z1ZW50ZV9BbmFsaXNpcykNCg0KbGluZV9zZXMyIDwtIGdncGxvdChkYXRhID0gZGZfc2VzZmVjaGFfZiwNCiAgICAgICAgICAgICAgICAgICBtYXBwaW5nID0gYWVzKHggPSBGZWNoYSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHkgPSBuLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sb3IgPSBUaXBvRnVlbnRlX0FuYWxpc2lzKSkgKw0KICBnZW9tX2xpbmUoKQ0KDQpsaW5lX3NlczINCmBgYA0KDQpFbCBncsOhZmljbyBvYnRlbmlkbyByZXN1bHRhIHVuIHRhbnRvIGNvbmZ1c28gZW4gdmlzdGEgZGUgcXVlIGV4aXN0ZW4gbXVjaGFzIGZ1ZW50ZXMuIE1lam9yZW1vcyBlc3RvLCBhw7FhZGllbmRvIHVuYSB0cmFuc3BhcmVuY2lhIGRlbCA3NSUgZW4gbGFzIGzDrW5lYXMgeSBhZGljaW9uYW5kbyB1bmEgZ2VvbWV0csOtYSBkZSBzdWF2aXphbWllbnRvIGNvbiBwYXLDoW1ldHJvICptZXRob2QqIGRlICJsb2VzcyIuIEluY29ycG9yZSBhZGVtw6FzIG90cm9zIGZvcm1hdG9zIHF1ZSBlc3RpbWUgYWRlY3VhZG9zLg0KDQpgYGB7cn0NCmxpbmVfc2VzMiA8LSBnZ3Bsb3QoZGF0YSA9IGRmX3Nlc2ZlY2hhX2YsDQogICAgICAgICAgICAgICAgICAgbWFwcGluZyA9IGFlcyh4ID0gRmVjaGEsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB5ID0gbiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbG9yID0gVGlwb0Z1ZW50ZV9BbmFsaXNpcykpICsNCiAgZ2VvbV9saW5lKGFscGhhID0gMC4yNSkgKw0KICBnZW9tX3Ntb290aChtZXRob2QgPSAibG9lc3MiKSArDQogIHlsYWIoIiMgU2VzaW9uZXMiKSArDQogIHhsYWIoIiIpICsNCiAgbGFicyhjb2xvciA9ICJGdWVudGUiKSArICNFc3RhIGZ1bmNpw7NuIHBlcm1pdGUgY2FtYmlhciBlbCBub21icmUgZGUgbGEgbGV5ZW5kYQ0KICBnZ3RpdGxlKCJFdm9sdWNpb25lcyBkZSBTZXNpb25lcyBwb3IgRnVlbnRlIikgKw0KICB0aGVtZV9jbGFzc2ljKCkgKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAidG9wIikNCg0KbGluZV9zZXMyDQpgYGANCg0Kwr9RdcOpIHB1ZWRlIGluZmVyaXIgcmVzcGVjdG8gYSBsYXMgZnVlbnRlcyBkZSBsYXMgc2VzaW9uZXMsIHNpZW5kbyB1c3RlZCB1biBleHBlcnRvIGRlIG5lZ29jaW8/DQoNCkFzw60gY29tbyAqZmlsbCogeSAqY29sb3IqLCBvdHJvcyBwYXLDoW1ldHJvcyBxdWUgaGVtb3MgdmlzdG8gZW4gbG9zIGdyw6FmaWNvcyBwcmVjZWRlbnRlcyBpZ3VhbG1lbnRlIHB1ZWRlbiB0cmFuc2Zvcm1hcnNlIGVuICoqRVNURVRJQ0FTKiogYSBmaW4gZGUgcmVwcmVzZW50YXIgYWxndW5hIG90cmEgdmFyaWFibGUuIENvbW8gZWplcmNpY2lvIGludGVudGUgcHJvYmFyIGVsIHVzbyBkZSBlc3TDqXRpY2FzIGFkaWNpb25hbGVzIGVuIGdyw6FmaWNvcyBkZSBwdW50b3MgeSBkZSBjb2x1bW5hcy4NCg0KIyMjIDQuMyBNYXBhcyBkZSBjYWxvcg0KDQpMb3MgbWFwYXMgZGUgY2Fsb3Igc29uIHJlcHJlc2VudGFjaW9uZXMgZXNwYWNpYWxlcyBkZSBkb3MgdmFyaWFibGVzIHF1ZSBwZXJtaXRlbiB2aXN1YWxpemFyIHpvbmFzIGRlIGludGVyw6lzIGRlZmluaWRhcyBlbiBiYXNlIGEgdW5hIHRlcmNlcmEgdmFyaWFibGUuDQoNClBhcmEgZW50ZW5kZXIgbWVqb3Igc3UgZnVuY2lvbmFtaWVudG8sIHN1cG9uZ2Ftb3MgcXVlIGFob3JhIHF1ZXJlbW9zIGNvbm9jZXIgbGEgY2FudGlkYWQgZGUgc2VzaW9uZXMgcG9yIGTDrWEgZGUgbGEgc2VtYW5hIHkgaG9yYSAoZGVsIHVzdWFyaW8pLiBQYXJhIG1heW9yIGZhY2lsaWRhZCwgY3JlZW1vcyB1bmEgbnVldmEgdmFyaWFibGUgbGxhbWFkYSAqR3J1cG9fSG9yYSogZG9uZGUgbGFzIG9wY2lvbmVzIHNlYW46DQoNCiogMS5NYWRydWdhZGEgKGVudHJlIGxhcyAwIHkgbGFzIDUgaG9yYXMpDQoqIDIuTWFuYW5hICg2LTEwKQ0KKiAzLk1lZGlvRGlhICgxMS0xNSkNCiogNC5UYXJkZSAoMTYtMjApDQoqIDUuTm9jaGUgKDIxLTIzKQ0KDQpVc2Vtb3MgYWRlbcOhcyBsYSBnZW9tZXRyw61hICpnZW9tX3RpbGUqIHkgY29tbyBlc3TDqXRpY2EgbnVldmEgKmZpbGwqLg0KDQpgYGB7cn0NCmRmX3Nlc3RpbWUgPC0gZGZfc2VzX2dlbyAlPiUNCiAgbXV0YXRlKEdydXBvX0hvcmEgPSBpZmVsc2UoSG9yYSA8PSA1LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiMS5NYWRydWdhZGEiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZmVsc2UoSG9yYSA8PSAxMCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICIyLk1hbmFuYSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZmVsc2UoSG9yYSA8PSAxNSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiMy5NZWRpb0RpYSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWZlbHNlKEhvcmEgPD0gMjAsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICI0LlRhcmRlIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIjUuTm9jaGUiKSkpKSkgJT4lDQogIGNvdW50KERpYVNlbWFuYSwNCiAgICAgICAgIEdydXBvX0hvcmEpDQoNCm1hcF9zZXN0aW1lIDwtIGdncGxvdChkYXRhID0gZGZfc2VzdGltZSwNCiAgICAgICAgICAgICAgICAgICAgICBtYXBwaW5nID0gYWVzKHggPSBEaWFTZW1hbmEsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB5ID0gR3J1cG9fSG9yYSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZpbGwgPSBuKSkgKw0KICBnZW9tX3RpbGUoKQ0KDQptYXBfc2VzdGltZQ0KYGBgDQpQYXJhIHVuYSBtZWpvciB2aXN1YWxpemFjacOzbiwgY29udHJvbGVtb3MgZWwgY29sb3IgZGUgcmVsbGVubyBkZSBmb3JtYSBxdWUgc2VhIHJvam8gcGFyYSBlbCBjYXNvIGRlIHBvY2FzIHNlc2lvbmVzLCB5IHZlcmRlIHBhcmEgZWwgZGUgbXVjaGFzOyBhZGVtw6FzIHF1ZSBlc3RvcyBjb2xvcmVzIHNpZ2FuIHVuIGdyYWRpZW50ZS4gVXNlbW9zIHVuYSBmdW5jacOzbiBhZGljaW9uYWwgbGxhbWFkYSAqc2NhbGVfZmlsbF9ncmFkaWVudGUqLiBBcHJvdmVjaGVtb3MgeSBhZGljaW9uZW1vcyB0b2RvcyBsb3MgZm9ybWF0b3MgcXVlIHZlYW1vcyBjb252ZW5pZW50ZS4NCg0KYGBge3J9DQptYXBfc2VzdGltZSA8LSBnZ3Bsb3QoZGF0YSA9IGRmX3Nlc3RpbWUsDQogICAgICAgICAgICAgICAgICAgICAgbWFwcGluZyA9IGFlcyh4ID0gRGlhU2VtYW5hLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeSA9IEdydXBvX0hvcmEsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmaWxsID0gbikpICsNCiAgZ2VvbV90aWxlKCkgKw0KICBzY2FsZV9maWxsX2dyYWRpZW50KGxvdyA9ICJyZWQiLCBoaWdoID0gImdyZWVuIikgKw0KICB5bGFiKCJNb21lbnRvIGRlbCBEw61hIikgKw0KICB4bGFiKCJEaWEgZGUgbGEgU2VtYW5hIikgKw0KICBsYWJzKGZpbGwgPSAiIyBTZXNpb25lcyIpICsNCiAgZ2d0aXRsZSgiU2VzaW9uZXMgcG9yIETDrWEgeSBIb3JhIikgKw0KICB0aGVtZV9taW5pbWFsKCkNCg0KbWFwX3Nlc3RpbWUNCmBgYA0KQ29uIG51ZXN0cm8gbWFwYSBkZSBjYWxvciBwb2RlbW9zIHZlciBxdWUgbG9zIGx1bmVzIGFsIG1lZGlvIGTDrWEgaGF5IG1heW9yIHRyw6FmaWNvLiBBc8OtIG1pc21vLCBlbiBsYXMgbm9jaGVzIHkgbWFkcnVnYWRhcyBkZSB0b2RvcyBsb3MgZMOtYXMgaGF5IHBvY28gdHLDoWZpY28sIHkgZW4gbGFzIHRhcmRlcyBkZSBsb3MgZmluZXMgZGUgc2VtYW5hIGVsIHRyw6FmaWNvIGVzIG1vZGVyYWRvLiANCg0KIyMjIDQuNCBNYXBhcyBnZW9ncsOhZmljb3MNCg0KVW5hIGRlIGxhcyBhcGxpY2FjaW9uZXMgbcOhcyBpbnRlcmVzYW50ZXMgZGUgbG9zIG1hcGFzIGRlIGNhbG9yIGhhY2UgcmVmZXJlbmNpYSBhIHN1IHVzbyBwYXJhIGFuw6FsaXNpcyBnZW9sb2NhbGl6YWRvLiANCg0KVW5hIHByZWd1bnRhIHJlY3VycmVudGUgcXVlIHBvZHLDrWEgc2FsaXIgZGUgZXN0ZSBjb25qdW50byBkZSBkYXRvcyBlcyBzYWJlciBjdcOhbCBlcyBsYSBjaXVkYWQgZGUgRWN1YWRvciBjb24gbWF5b3IgbsO6bWVybyBkZSBzZXNpb25lcywgeSBwYXJhIGVzdG8gcG9kZW1vcyB1c2FyIHVuYSBiYXNlIGRlIGRhdG9zIGVzcGVjw61maWNhIGRlICoqZ2dwbG90KiogbGxhbWFkYSAqbWFwX2RhdGEqLg0KDQpgYGB7cn0NCmRmX21hcGFfZWMgPC0gbWFwX2RhdGEobWFwID0gIndvcmxkIiwgcmVnaW9uID0gIkVjdWFkb3IiKQ0KdmlldyhkZl9tYXBhX2VjKQ0KYGBgDQoNClZpc3VhbGljZW1vcyBlbiBwcmltZXJhIGluc3RhbmNpYSBudWVzdHJvIG1hcGEgZGUgRWN1YWRvciB1c2FuZG8gbGEgZ2VvbWV0csOtYSAqZ2VvbV9wb2x5Z29uKi4NCmBgYHtyfQ0KbWFwYV9zZXNlYyA8LSBnZ3Bsb3QoZGF0YSA9IGRmX21hcGFfZWMsDQogICAgICAgICAgICAgICAgICAgICBtYXBwaW5nID0gYWVzKHggPSBsb25nLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB5ID0gbGF0LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBncm91cCA9IGdyb3VwKSkgKw0KICBnZW9tX3BvbHlnb24oKQ0KDQptYXBhX3Nlc2VjDQpgYGANCkFwbGlxdWVtb3MgY2llcnRvcyBmb3JtYXRvcywgcXVlIGhhZ2FuIHF1ZSBudWVzdHJvIG1hcGEgc2UgdmVhIG1lam9yOw0KYGBge3J9DQptYXBhX3Nlc2VjIDwtIGdncGxvdChkYXRhID0gZGZfbWFwYV9lYywNCiAgICAgICAgICAgICAgICAgICAgIG1hcHBpbmcgPSBhZXMoeCA9IGxvbmcsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHkgPSBsYXQsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdyb3VwID0gZ3JvdXApKSArDQogIGdlb21fcG9seWdvbihmaWxsID0gImxpZ2h0Ymx1ZSIsDQogICAgICAgICAgICAgICBjb2xvciA9ICJncmF5NTAiLA0KICAgICAgICAgICAgICAgYWxwaGEgPSAwLjUpICsNCiAgdGhlbWVfdm9pZCgpICNCb3JyYSB0b2RvIGxvIGFzb2NpYWRvIGEgZWplcyB5IGZvbmRvcw0KDQptYXBhX3Nlc2VjDQpgYGANCkFob3JhIGJpZW4sIHNvYnJlIGVzdGUgbWFwYSB2YW1vcyBhIG1vbnRhciBvdHJhIGdlb21ldHJpYSBxdWUgbm9zIG11ZXN0cmUgcHVudG9zIGVuIGxhcyBjaXVkYWRlcyBkb25kZSBoYXkgc2VzaW9uZXMgd2ViLCB5IGRvbmRlIGVsIGNvbG9yIGRlIGNhZGEgcHVudG8gY29ycmVzcG9uZGEgYSB1biBjcml0ZXJpbyBkZSBtdWNoYXMgKHZlcmRlKSBvIHBvY2FzIChyb2pvKSBzZXNpb25lcywgZGUgZm9ybWEgZXF1aXZhbGVudGUgYWwgbWFwYSBkZSBjYWxvciBkZSBsYSBzZWNjacOzbiBhbnRlcmlvci4gUGFyYSBlc3RvIHZhbW9zIGEgaGFjZXIgbG8gc2lndWllbnRlOg0KDQoqIEVuIHByaW1lciBsdWdhciBjb25zdHJ1aW1vcyB1biBkYXRhIGZyYW1lIHF1ZSBjb250ZW5nYSBzb2xvIGVsIHRvdGFsIGRlIHNlc2lvbmVzIGVuIGxhcyBjaXVkYWRlcyBkZSBFY3VhZG9yLCBhc8OtIGNvbW8gc3VzIHJlc3BlY3RpdmFzIGxhdGl0dWRlcyB5IGxvbmdpdHVkZXMuDQpgYGB7cn0NCmRmX2dlb19lYyA8LSBkZl9zZXNfZ2VvICU+JQ0KICBmaWx0ZXIoUGFpcyA9PSAiRWN1YWRvciIpICU+JQ0KICBncm91cF9ieShDaXVkYWQpICU+JQ0KICBzdW1tYXJpc2UoU2VzaW9uZXMgPSBuKCksDQogICAgICAgICAgICBsYXQgPSBtZWFuKExhdCksDQogICAgICAgICAgICBsb25nID0gbWVhbihMb24pKQ0KYGBgDQoNCiogVW5hIHZleiB0ZW5lbW9zIGVzdG8sIGHDsWFkaW1vcyBhbCBtYXBhIHF1ZSB5YSBoYWLDrWFtb3MgaGVjaG8gcHJldmlhbWVudGUuDQoNCmBgYHtyfQ0KbWFwYV9zZXNlYyA8LSBnZ3Bsb3QoZGF0YSA9IGRmX21hcGFfZWMsDQogICAgICAgICAgICAgICAgICAgICBtYXBwaW5nID0gYWVzKHggPSBsb25nLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB5ID0gbGF0LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBncm91cCA9IGdyb3VwKSkgKw0KICBnZW9tX3BvbHlnb24oZmlsbCA9ICJsaWdodGJsdWUiLA0KICAgICAgICAgICAgICAgY29sb3IgPSAiZ3JheTUwIiwNCiAgICAgICAgICAgICAgIGFscGhhID0gMC41KSArDQogIHRoZW1lX3ZvaWQoKSArDQogIGdlb21fcG9pbnQoZGF0YSA9IGRmX2dlb19lYywNCiAgICAgICAgICAgICBtYXBwaW5nID0gYWVzKHggPSBsb25nLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgeSA9IGxhdCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGdyb3VwID0gMSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbG9yID0gU2VzaW9uZXMpLA0KICAgICAgICAgICAgIHNpemUgPSAxLjUpICsNCiAgc2NhbGVfY29sb3JfZ3JhZGllbnQobG93ID0gInJlZCIsIGhpZ2ggPSAiZ3JlZW4iKQ0KDQptYXBhX3Nlc2VjDQpgYGANCk5vdGVtb3MgcXVlIGhheSBtdWNoYXMgY2l1ZGFkZXMgY29uIHRvbmFsaWRhZGVzIHJvamFzLCBsbyBjdWFsIGhhY2UgcXVlIHNlIHBpZXJkYSBpbmZvcm1hY2nDs24uIFVzZW1vcyBlbCBwYXLDoW1ldHJvICp0cmFucyogcGFyYSBjb3JyZWdpciBlc3RvIGRhbmRvIHVuIHJlZXNjYWxhbWllbnRvIGxvZ2Fyw610bWljbyBhIGxvcyBjb2xvcmVzLg0KDQpgYGB7cn0NCm1hcGFfc2VzZWMgPC0gZ2dwbG90KGRhdGEgPSBkZl9tYXBhX2VjLA0KICAgICAgICAgICAgICAgICAgICAgbWFwcGluZyA9IGFlcyh4ID0gbG9uZywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeSA9IGxhdCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZ3JvdXAgPSBncm91cCkpICsNCiAgZ2VvbV9wb2x5Z29uKGZpbGwgPSAibGlnaHRibHVlIiwNCiAgICAgICAgICAgICAgIGNvbG9yID0gImdyYXk1MCIsDQogICAgICAgICAgICAgICBhbHBoYSA9IDAuNSkgKw0KICB0aGVtZV92b2lkKCkgKw0KICBnZW9tX3BvaW50KGRhdGEgPSBkZl9nZW9fZWMsDQogICAgICAgICAgICAgbWFwcGluZyA9IGFlcyh4ID0gbG9uZywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHkgPSBsYXQsDQogICAgICAgICAgICAgICAgICAgICAgICAgICBncm91cCA9IDEsDQogICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xvciA9IFNlc2lvbmVzKSwNCiAgICAgICAgICAgICBzaXplID0gMS41KSArDQogIHNjYWxlX2NvbG9yX2dyYWRpZW50KGxvdyA9ICJyZWQiLCBoaWdoID0gImdyZWVuIiwgdHJhbnMgPSAibG9nIikNCg0KbWFwYV9zZXNlYw0KYGBgDQoqKlBVTlRPUyBFWFRSQVMqKjogUmVjaWJpcsOhICoqMTAgcHVudG9zIGV4dHJhcyoqLCBlbCBwcmltZXIgZXN0dWRpYW50ZSBxdWUgcmVtaXRhIGVsIHNjcmlwdCBjb24gdW4gbWFwYSBlcXVpdmFsZW50ZSBhbCBoZWNobyBlbiBlc3RlIGRvY3VtZW50bywgcGVybyBhaG9yYSBwYXJhIGxhcyBjaXVkYWRlcyBkZSBFc3RhZG9zIFVuaWRvcywgeSBleGNsdXllbmRvIGRlbCBtYXBhIGxhcyAqc3VicmVnaW9uZXMqIGRlIEFsYXNrYSB5IEhhd2FpaS4NCg0KIyMjIDQuNSBFdGlxdWV0YXMNCg0KVW4gYXNwZWN0byBpbXBvcnRhbnRlIHF1ZSBmYWNpbGl0YSBsYSB2aXN1YWxpemFjacOzbiBkZSBncsOhZmljb3MgZXMgbGEgaW5jbHVzacOzbiBkZSBldGlxdWV0YXMgcXVlIGd1w61lbiB5IGZhY2lsaXRlbiBlbCBlbnRlbmRpbWllbnRvLiBFbiBlc3RlIHNlbnRpZG8sIGxhcyBldGlxdWV0YXMgZGViZW4gZW50ZW5kZXJzZSBjb21vIHVuYSAqKkdFT01FVFLDjUEqKiBhZGljaW9uYWwgZGVudHJvIGRlIGxhIMKoZ3JhbcOhdGljYSBkZSBsb3MgZ3LDoWZpY29zLg0KDQpSZWNvcmRlbW9zIGVudG9uY2VzIGVsIGdyw6FmaWNvIGRlIGNvbHVtbmFzIHF1ZSByZWFsaXphbW9zIHBhcmEgZXZhbHVhciBsYSBkdXJhY2nDs24gcHJvbWVkaW8gcG9yIGRpc3Bvc2l0aXZvLg0KYGBge3J9DQpjb2xfZHVyZGlzcA0KYGBgDQpJbmNsdXlhbW9zIGFob3JhIGV0aXF1ZXRhcyBwYXJhIHF1ZSBtdWVzdHJlbiBlbCB2YWxvciBkZSBjYWRhIGNvbHVtbmEsIHBhcmEgZXN0byB1c2Vtb3MgdW5hIGVzdMOpdGljYSBhZGljaW9uYWwgZGFkYSBwb3IgKmxhYmVsKiB5IGxhIGZ1bmNpw7NuIGRlIGdlb21ldHLDrWEgKmdlb21fbGFiZWwqLg0KDQpgYGB7cn0NCmV0aXFfZHVyX2Rpc3AgPC0gZ2dwbG90KGRhdGEgPSBkZl9kdXJkaXNwLA0KICAgICAgICAgICAgICAgICAgICAgIG1hcHBpbmcgPSBhZXMoeCA9IERpc3Bvc2l0aXZvLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeSA9IER1cmFjaW9uX1Byb20sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsYWJlbCA9IER1cmFjaW9uX1Byb20pKSArDQogIGdlb21fY29sKGZpbGwgPSAicHVycGxlIiwNCiAgICAgICAgICAgYWxwaGEgPSAwLjUsDQogICAgICAgICAgIGNvbG9yID0gInB1cnBsZSIpICsNCiAgeGxhYigiRGlzcG9zaXRpdm8iKSArDQogIHlsYWIoIkR1cmFjacOzbiBQcm9tZWRpbyAoZW4gc2VndW5kb3MpIikgKw0KICBnZ3RpdGxlKCJEdXJhY2nDs24gdnMgRGlzcG9zaXRpdm8gZW4gU2VzaW9uZXMiKSArDQogIHRoZW1lX2dyYXkoKSArDQogIGdlb21fbGFiZWwoKQ0KDQpldGlxX2R1cl9kaXNwDQpgYGANCkFqdXN0ZW1vcyBlbCBmb3JtYXRvIGRlIGVzdGFzIGV0aXF1ZXRhcyB0YWwgcXVlOg0KDQoqIExvcyB2YWxvcmVzIGVzdMOpbiByZWRvbmRlYWRvcyBhbCBwcmltZXIgZGVjaW1hbC4NCiogVGVuZ2FuIHVuIGZvbmRvIGNvbiB1biBjb2xvciBhZGVjdWFkby4NCiogVGVuZ2FuIHVuIGNvbG9yIGRlIGxldHJhIGFkZWN1YWRvLg0KKiBUZW5nYW4gdW4gdGFtYcOxbyB0YWwgcXVlIHNlYW4gZmFjaWxtZW50ZSB2aXNpYmxlcy4NCg0KYGBge3J9DQpldGlxX2R1cl9kaXNwIDwtIGdncGxvdChkYXRhID0gZGZfZHVyZGlzcCwNCiAgICAgICAgICAgICAgICAgICAgICBtYXBwaW5nID0gYWVzKHggPSBEaXNwb3NpdGl2bywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHkgPSBEdXJhY2lvbl9Qcm9tLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFiZWwgPSByb3VuZChEdXJhY2lvbl9Qcm9tLDEpKSkgKw0KICBnZW9tX2NvbChmaWxsID0gInB1cnBsZSIsDQogICAgICAgICAgIGFscGhhID0gMC41LA0KICAgICAgICAgICBjb2xvciA9ICJwdXJwbGUiKSArDQogIHhsYWIoIkRpc3Bvc2l0aXZvIikgKw0KICB5bGFiKCJEdXJhY2nDs24gUHJvbWVkaW8gKGVuIHNlZ3VuZG9zKSIpICsNCiAgZ2d0aXRsZSgiRHVyYWNpw7NuIHZzIERpc3Bvc2l0aXZvIGVuIFNlc2lvbmVzIikgKw0KICB0aGVtZV9ncmF5KCkgKw0KICBnZW9tX2xhYmVsKGZpbGwgPSAicHVycGxlIiwNCiAgICAgICAgICAgIGNvbG9yID0gIndoaXRlIiwNCiAgICAgICAgICAgIHNpemUgPSAzKQ0KDQpldGlxX2R1cl9kaXNwDQpgYGANCkNvbW8gb3RybyBlamVtcGxvIGRlIGV0aXF1ZXRhcywgdG9tZW1vcyBudWVzdHJvIG1hcGEgZGUgY2Fsb3IgZGUgZGlhcyBkZSBsYSBzZW1hbmEgeSBob3Jhcy4NCg0KYGBge3J9DQptYXBfc2VzdGltZQ0KYGBgDQpJbmNvcnBvcmVtb3MgbGFzIGV0aXF1ZXRhcyBjb24gbG9zIHZhbG9yZXMgYWxjYW56YWRvcyBlbiBjYWRhIGN1YWRyYW50ZSB5IGVsaW1pbmVtb3MgYSBzdSB2ZXogbGEgbGV5ZW5kYSBwdWVzIHJlc3VsdGFyw61hIGlycmVsZXZhbnRlLg0KDQpgYGB7cn0NCmV0aXFfc2VzdGltZSA8LSBnZ3Bsb3QoZGF0YSA9IGRmX3Nlc3RpbWUsDQogICAgICAgICAgICAgICAgICAgICAgIG1hcHBpbmcgPSBhZXMoeCA9IERpYVNlbWFuYSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB5ID0gR3J1cG9fSG9yYSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmaWxsID0gbiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsYWJlbCA9IG4pKSArDQogIGdlb21fdGlsZSgpICsNCiAgc2NhbGVfZmlsbF9ncmFkaWVudChsb3cgPSAicmVkIiwgaGlnaCA9ICJncmVlbiIpICsNCiAgeWxhYigiTW9tZW50byBkZWwgRMOtYSIpICsNCiAgeGxhYigiRGlhIGRlIGxhIFNlbWFuYSIpICsNCiAgbGFicyhmaWxsID0gIiMgU2VzaW9uZXMiKSArDQogIGdndGl0bGUoIlNlc2lvbmVzIHBvciBEw61hIHkgSG9yYSIpICsNCiAgdGhlbWVfbWluaW1hbCgpICsNCiAgZ2VvbV9sYWJlbCgpICsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKQ0KDQpldGlxX3Nlc3RpbWUNCmBgYA0KRmluYWxtZW50ZSwgeSBjb21vIHVuIGVqZW1wbG8gbcOhcyBkZXNhZmlhbnRlLCBlbiBudWVzdHJvIG1hcGEgZ2VvcmVmZXJlbmNpYWRvIGRlIEVjdWFkb3IsIHBvbmdhbW9zIGV0aXF1ZXRhcyBhIGxhcyB0b3AgNSBjaXVkYWRlcyBlbiBuw7ptZXJvIGRlIHNlc2lvbmVzLCBxdWUgaW5jbHV5YW4gdGFudG8gZWwgbm9tYnJlIGRlIGxhIGNpdWRhZCBjb21vIHN1IGNhbnRpZGFkLiBDb2xvcXVlbW9zIGVzdGEgZXRpcXVldGEgYSBsYSBkZXJlY2hhIGRlIGNhZGEgcHVudG8uIA0KDQpgYGB7cn0NCmRmX2dlb19lYzIgPC0gZGZfZ2VvX2VjICU+JQ0KICBhcnJhbmdlKC1TZXNpb25lcykgJT4lDQogIHRvcF9uKDUsU2VzaW9uZXMpDQoNCmV0aXFfc2VzZWMgPC0gZ2dwbG90KGRhdGEgPSBkZl9tYXBhX2VjLA0KICAgICAgICAgICAgICAgICAgICAgbWFwcGluZyA9IGFlcyh4ID0gbG9uZywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeSA9IGxhdCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZ3JvdXAgPSBncm91cCkpICsNCiAgZ2VvbV9wb2x5Z29uKGZpbGwgPSAibGlnaHRibHVlIiwNCiAgICAgICAgICAgICAgIGNvbG9yID0gImdyYXk1MCIsDQogICAgICAgICAgICAgICBhbHBoYSA9IDAuNSkgKw0KICB0aGVtZV92b2lkKCkgKw0KICBnZW9tX3BvaW50KGRhdGEgPSBkZl9nZW9fZWMsDQogICAgICAgICAgICAgbWFwcGluZyA9IGFlcyh4ID0gbG9uZywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHkgPSBsYXQsDQogICAgICAgICAgICAgICAgICAgICAgICAgICBncm91cCA9IDEsDQogICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xvciA9IFNlc2lvbmVzKSwNCiAgICAgICAgICAgICBzaXplID0gMS41KSArDQogIHNjYWxlX2NvbG9yX2dyYWRpZW50KGxvdyA9ICJyZWQiLCBoaWdoID0gImdyZWVuIiwgdHJhbnMgPSAibG9nIikgKw0KICBnZW9tX2xhYmVsKGRhdGEgPSBkZl9nZW9fZWMyLA0KICAgICAgICAgICAgIG1hcHBpbmcgPSBhZXMoeCA9IGxvbmcsDQogICAgICAgICAgICAgICAgICAgICAgICAgICB5ID0gbGF0LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgZ3JvdXAgPSAxLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFiZWwgPSBwYXN0ZShDaXVkYWQsIiwgIixTZXNpb25lcywgc2VwID0gIiIpKSwNCiAgICAgICAgICAgICBmaWxsID0gIndoaXRlIiwNCiAgICAgICAgICAgICBzaXplID0gMiwNCiAgICAgICAgICAgICBoanVzdCA9IC0wLjE1KQ0KICANCg0KZXRpcV9zZXNlYw0KYGBgDQoNCg==