Hablamos la clase pasada de extender las capacidades de R, utilizando herramientas adicionales para diferentes propositos que vienen en forma de “paquetes”. Empleamos la análogia de que “R” es como un celular nuevo, el cual tiene ciertas funcionalidades que vienen por defecto, pero que además nosotros podemos agregar más funciones a nuestros celulares a través de la descarga de aplicaciones desde “app store” o “google play”. En el caso del siguiente análisis, utilizaremos las aplicaciones “ggplot2”, “dplyr” y “skimr”, que agregaran nuevas herramientas para utilizar en nuestro código a través del concepto de funciones en programación.

library(ggplot2)
library(dplyr)
library(skimr)

En esta clase utilizaremos como ejemplo la data “mpg” que viene cargada con el paquete “ggplot2”. Esta data contiene información sobre la eficiencia de combustible de distintos modelos de autos y sus caracteristicas.

Inspeccionando los datos

Es bueno entender antes de cualquier análisis, la data con la que contamos y para esto es útil la función dplyr::glimpse(). Por función entenderemos un proceso que toma un input o entrada, como un dataframe (data rectangular), luego ejecuta una operación sobre este input en que se pueden ajustar ciertos aspectos para controlar la operación y finalmente recibimos un output o resultado.

A la función dplyr::glimpse() le entregamos como argumento nuestra data rectangular y nos arroja información acerca del tipo de variables (columnas) y algunas de las observaciones de cada una de estas.

glimpse(mpg)
Observations: 234
Variables: 11
$ manufacturer <chr> "audi", "audi", "audi", "audi", "audi", "audi", "audi", "audi", "audi", "audi...
$ model        <chr> "a4", "a4", "a4", "a4", "a4", "a4", "a4", "a4 quattro", "a4 quattro", "a4 qua...
$ displ        <dbl> 1.8, 1.8, 2.0, 2.0, 2.8, 2.8, 3.1, 1.8, 1.8, 2.0, 2.0, 2.8, 2.8, 3.1, 3.1, 2....
$ year         <int> 1999, 1999, 2008, 2008, 1999, 1999, 2008, 1999, 1999, 2008, 2008, 1999, 1999,...
$ cyl          <int> 4, 4, 4, 4, 6, 6, 6, 4, 4, 4, 4, 6, 6, 6, 6, 6, 6, 8, 8, 8, 8, 8, 8, 8, 8, 8,...
$ trans        <chr> "auto(l5)", "manual(m5)", "manual(m6)", "auto(av)", "auto(l5)", "manual(m5)",...
$ drv          <chr> "f", "f", "f", "f", "f", "f", "f", "4", "4", "4", "4", "4", "4", "4", "4", "4...
$ cty          <int> 18, 21, 20, 21, 16, 18, 18, 18, 16, 20, 19, 15, 17, 17, 15, 15, 17, 16, 14, 1...
$ hwy          <int> 29, 29, 31, 30, 26, 26, 27, 26, 25, 28, 27, 25, 25, 25, 25, 24, 25, 23, 20, 1...
$ fl           <chr> "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p", "p...
$ class        <chr> "compact", "compact", "compact", "compact", "compact", "compact", "compact", ...

Otra función útil para la exploración de nuestros datos es skimr::skim(), esta toma como input una data rectangular (dataframe) y nos entrega distintas medida de información, en un formato rectangular, de cada una de las variables como el valor mínimo, máximo, valores distintos, cantidad de observaciones con valores perdidos, entre otras.

skim(mpg)
Skim summary statistics
 n obs: 234 
 n variables: 11 

-- Variable type:character -----------------------------------------------------
     variable missing complete   n min max empty n_unique
        class       0      234 234   3  10     0        7
          drv       0      234 234   1   1     0        3
           fl       0      234 234   1   1     0        5
 manufacturer       0      234 234   4  10     0       15
        model       0      234 234   2  22     0       38
        trans       0      234 234   8  10     0       10

-- Variable type:integer -------------------------------------------------------
 variable missing complete   n    mean   sd   p0  p25    p50  p75 p100     hist
      cty       0      234 234   16.86 4.26    9   14   17     19   35 ▅▇▇▇▁▁▁▁
      cyl       0      234 234    5.89 1.61    4    4    6      8    8 ▇▁▁▇▁▁▁▇
      hwy       0      234 234   23.44 5.95   12   18   24     27   44 ▃▇▃▇▅▁▁▁
     year       0      234 234 2003.5  4.51 1999 1999 2003.5 2008 2008 ▇▁▁▁▁▁▁▇

-- Variable type:numeric -------------------------------------------------------
 variable missing complete   n mean   sd  p0 p25 p50 p75 p100     hist
    displ       0      234 234 3.47 1.29 1.6 2.4 3.3 4.6    7 ▇▇▅▅▅▃▂▁

La primera visualización

Empecemos con nuestra primera visualización en R, veamos la relación que existe entre la capacidad de desplazamiento (displ) de los vehculos con respecto a las millas rendidas en carretera (hwy).

ggplot(data = mpg) + 
  geom_point(mapping = aes(x = displ, y = hwy))

¿Alguien me puede contar lo que nos dice este gráfico? Es importante aprender a leer los gráficos, si bien la ventaja de revelar patrones es indudable, no es mágica tampoco en el sentido de que por ver el gráfico ya lo vemos todo.

A medida que aumenta la capacidad de desplazamiento (displ), la cual se mide en litros, la eficiencia en carretera de los autos, medida en millas por galon, disminuye. En términos simples, observando estas dos variables y dejando todo lo demás constante, mayores motores muestran una menor eficiencia en carretera.

Ahora, para lograr visualizar esto, ¿que necesito? Los ingredientes mínimos para crear un gráfico son los siguientes:

  1. Datos.
  2. Vincular las variables que queremos representar a los aspectos visuales del gráfico.
  3. A lo menos una capa que contenga una representación geometrica de los datos (puntos, líneas, barras, etcétera)

El primer punto es evidente, el segundo trata de mapear variables del conjunto de datos relevantes para el gráfico a ciertos atributos esteticos. Alguno de los atributos más comunes son:

En nuestro primer gráfico utilizamos solo dos atributos estéticos: el eje x e y. Podemos agregar más información a nuestro gráfico vinculando atributos estéticos adicionales a variables contenidas en los datos.

ggplot(data = mpg) +
  geom_point(mapping = aes(x = displ, y = hwy, colour = class))

Una de las ventajas de ggplot2 es que permite construir gráficos utilizando especificaciones mínimas que junto a configuraciones prestablecidas, nos facilitan el trabajo de definir todos los aspectos posibles de un gráfico. Por ejemplo, en el gráfico de arriba ggplot2 automaticamente crea la leyenda con la información de que color es cada nivel de la variable clase. Esto es posible gracias a las especificaciones internas de la data que hay debajo de cada gráfico:

(representacion_interna <- mpg %>% 
  select(displ, hwy, class) %>% 
  left_join(mpg %>% 
            distinct(class) %>% 
            mutate(colour = c("red", "brown", 
                              "green", "blue", 
                              "calypso", "purple", 
                              "yellow")),
  by = "class") %>% 
  select(-class) %>% 
  rename(x = displ,
         y = hwy))

Obviando los detalles del código de arriba, así es como ggplot2 mapea las variables que queremos gráficar de nuestra base (mpg) a las propiedades visuales (x, y, colour).

(diccionario_colores <- mpg %>% 
                            distinct(class) %>% 
                            mutate(colour = c("red", "brown", 
                                              "green", "blue", 
                                              "calypso", "purple", 
                                              "yellow")))

Podemos seguir incorporando información en nuestro gráfico mapeando otras variables de la base mpg a otros atributos estéticos dentro de la función aes(), por ejemplo, el número de cilindros que tienen los vehículos a través del atributo shape.

ggplot(data = mpg) +
  geom_point(mapping = aes(x = displ, y = hwy, colour = class, shape = as.factor(cyl)))

Sin embargo, acá nos vemos enfrentado a un trade-off entre cantidad de información y capacidad de comprensión. Tratar de abarcar demasiados aspectos visuales en un solo gráfico tiene un costo, que es la dificultad de comunicar al introducir mayor complejidad en nuestra visualización.

Facetas

Una forma para comparar distintos grupos de datos, entendamos estos grupos como una data tabular separada en varíos conjuntos por los distintos valores de una variable categórica, por ejemplo, los cuatros subconjuntos de datos que obtenemos al separar la data mpg por los autos que tienen igual número de cilindros indicados en la variable cyl.

lapply(split(mpg, mpg$cyl), head, n = 3)
$`4`

$`5`

$`6`

$`8`
NA

En vez de utilizar cuatro atributos estéticos y empezar a mermar la capacidad de comprensión del gráfico por causa del aumento de complejidad de información, podemos dejar solo tres de estos atributos pero generar, para comparar, el mismo gráfico en distintos subconjuntos de datos. Esta técnica en ggplot2 se conoce por facets y respecto al ejemplo anterior, generamos un gráfico para cada uno de los distintos grupos de datos al separar mpg por los valores unicos presentes en la variable cyl.

ggplot(data = mpg, mapping = aes(x = displ, y = hwy, colour = class)) +
  geom_point() +
  facet_wrap(~ cyl)

Ahora tenemos un nuevo componente que podemos agregar a la sintaxis de nuestro código. Abstrayendo todo lo que hemos visto hasta ahora:

#ggplot(data = mis_datos, mapping = aes(x, y, ...)) +
#  geom_<NAME>() +
#  facet_wrap(~ var_categorica)

Distintas representaciones geométricas: geom_<REPRESENTACION>

En los gráficos anteriores hemos representado nuestros datos solo a tráves de puntos. ¿Cómo incorporamos otras representaciones geometricas para visualizar datos?

El paquete ggplot2 trabaja a tráves de capas de representación gráficas sobre datos, cada una de estas capas requieres de los tres ingredientes mínimos nombrados arriba: datos, mapeo de variables a atributos estéticos y la representación geométrica (o geom).

Anteriormente, hemos trabajado con una capa visual de puntos pero podemos utilizar otro tipo de representaciones geométricas en la construcción de estas capas.

ggplot(data = mpg) +
  geom_bar(mapping = aes(x = drv))

Si observamos bien el código del gráfico de barra, solo empleamos un atributo estético comparado con los dos atributos mínimos que utilizamos para crear el gráfico de puntos. ¿Cómo se explica esto? Cada representación geométrica tiene asociado un conjunto de atributos estéticos que pueden o no tener similitudes con otros geom.

?geom_bar

Otros aspecto interesante de mencionar con respecto a la relación geom y mapeo de variables desde los datos a los atributos estéticos, es la diferencia entre sintaxis como especificación de código valido por el paquete ggplot2 y la semántica visual producida por el gráfico creado. Una sintaxis válida en ggplot2 que se traduce en un gráfico, ¿implica que este tenga semántica visual?

ggplot(data = mpg) +
  geom_line(aes(x = displ, y = hwy, linetype = as.factor(cyl), colour = as.factor(cyl)))

Para gráficar una línea se necesitan tener las coordenadas de cada punto, es decir los atributos estéticos x e y, que son utilizados por geom_line para unir cada una de estas coordenadas en base al eje x de izquierda a derecha. Esta representación es ideal para visualizar la evolución de una variable a tráves del tiempo. Sin embargo, en el gráfico de arriba, vemos que una sintaxis válida no es el único requisito para crear un gráfico semanticamente valido.

Transformaciones estadísticas y geoms

Analicemos un aspecto de ggplot2 con el gráfico de barra. Imaginemos que tenemos la cantidad de animales que hay en cierta comunidad:

animales_agrupado <- tribble(
                        ~ animal,   ~ num_obs,
                         "Perro",           203,
                          "Gato",           142,
                       "Pajaros",           108
             
)

Con la función tribble podemos crear una data rectangular (datafame o tibble) nosotros mismos. Esta se construye visualmente como esta arriba, indicando los nombres de las columnas con la cola de chancho ~ (alt + 126 en el teclado númerico) y luego agregando las observaciones por fila indentandolas para que queden alineadas bajo la columna que pertenecen. ¡No olvidemos separar cada argumento de la función tribble con una coma!

Nuestra data de juguete animales_agrupado, este tipo de data la llamaremos así porque es mínima y de ejemplo, representa el número de perros, gatos y pajaros que hay en un edificio. Esto es perfecto para gráficar con barras, sin embargo, es útil destacar que ya contamos cuantos animales hay de cada uno.

La capa geom_bar implica una transformación estadistica sobre nuestros datos para representar visualmente estos. Esta transformación es la de contar, geom_bar cuenta el número de observaciones que hay en la data y la representa con barras, pero en animales_agrupado los datos ya fueron contados. Esta es la razón de que utilizamos el argumento stat dentro de geom_bar con el valor “identity” que se lee como NO aplicar transformación sobre los datos.

ggplot(data = animales_agrupado) +
  geom_bar(mapping = aes(x = animal, y = num_obs),
           stat = "identity")

Para entender mejor lo anterior, creeemos otra data sin el número de observaciones (animales) ya agrupados. Utiliziaremos ahora la función tibble (sin la r), que es para crear data rectangular por columnas, para crear una data de solo una columna y 453 filas. Nos ayudamos en esta tarea de la función rep que repite en este caso el nombre del tipo de animal las veces que le indiquemos en el segundo argumento.

animales_no_agrupados <- tibble(
    animales = c(rep("Perro", 203),  # una sola columna con el nombre "animales".
                 rep("Gato", 142),      # que se construye concatenando "c()" 
                 rep("Pajaros", 108))   # tres vectores
)

Ahora que creamos nuestra data animales_no_agrupados podemos ver como quedo:

animales_no_agrupados

Ahora crearemos un gráfico de barra utilizando la data animales_no_agrupados, pero esta vez, no fijaremos el argumento stat igual a “Identity”. Es más, solo tenemos una columna animales, por lo que no podemos fijar el eje y a diferencia del gráfico anterior, en que teníamos una columna num_obs con el total de animales. Por debajo, geom_bar cuenta las observaciones y agrega la transformación de conteo que representa el total de animales por categoría al atributo estético eje y.

ggplot(data = animales_no_agrupados) +
  geom_bar(mapping = aes(x = animales))

Más adelante veremos otras representaciones geometricas que tienen asociadas transformaciones estadisticas sobre nuestros datos para crear representaciones visuales. Estas son computadas por las funciones del paquete ggplot2, pero lo importante es saber que transformar la data para crear medidas de resumen es una idea poderosa en visualización.

Más de una capa gráfica

Hablamos de que los ingredientes mínimos para crear un gráfico son los siguientes:

  1. Datos.
  2. Vincular las variables que queremos representar a los aspectos visuales del gráfico.
  3. A lo menos una capa que contenga una representación geometrica de los datos (puntos, líneas, barras, etcétera)

Estos ingredientes conforman un gráfico de una sola capa. Una de las caracteristicas de ggplot2 es que permite construir gráficos a tráves de capas. Esto significa que podemos agregar estos requisitos mínimos nombrados arriba más de una vez e ir componiendo gráficos por capas.

Voy a crear dos representaciones geometricas distintas pero a partir de los mismos datos mapeados de igual forma.

ggplot(data = mpg) +
  geom_point(mapping = aes(x = displ, y = hwy, colour = as.factor(cyl))) +
  ggtitle("Representación geométrica de puntos.")

ggplot(data = mpg) + 
  geom_smooth(mapping = aes(x = displ, y = hwy, colour = as.factor(cyl))) +
  ggtitle("Representación geométrica smooth: estima un modelo \n(función matématica) con los datos.")

¿Podemos juntar estas dos representaciones en un solo gráfico de dos capas?

ggplot(data = mpg) +
  geom_point(mapping = aes(x = displ, y = hwy, colour = as.factor(cyl))) +
  geom_smooth(mapping = aes(x = displ, y = hwy, colour = as.factor(cyl))) +
  ggtitle("Las dos representaciones en un mismo gráfico!")

Acá vemos un aspecto interesante de la sintaxis de ggplot2. La función con la que partimos cada gráfico ggplot(), tiene la propiedad de que todo lo especificado dentro de esta función se aplica como configuración básica de las capas del gráfico. En nuestro ejemplo de arriba, cada capa gráfica, geom_point y geom_smooth, heredan el argumento data = mpg. Es más, el mapeo de las variable es exactamente el mismo en ambas capas, por lo que lo podemos establecer tambien el argumento mapping como configuración inicial en la función ggplot() y ahorrarnos tipeo:

ggplot(data = mpg, mapping = aes(x = displ, y = hwy, colour = as.factor(cyl))) +
  geom_point() +  # 1era capa (esto es un comentario)
  geom_smooth() +  # 2da capa (la consola lo ignora y me sirve para resaltar puntos
  ggtitle("Mismo gráfico de dos representaciones geométricas de arriba!")

Si queremos gráficar representaciones geometricas de la misma data, no es necesario especificar el argumento data = mpg en cada capa o geom_nombre. De forma contraria, podemos sobreescribir la configuración básica, especificando una fuente de datos distintas en alguna de las capas, logrando de esta forma combinar datos desde distintas fuentes en un solo gráfico.

Crearé un dataframe de dos filas y cuatro columnas, con nombre “displ”, “hwy”, “cyl” y “etiqueta” representando el auto de los súpersonicos y el de los picapiedras para agregar al resto de los autos.

pica_supersonicos <- tibble(
  displ = c(7, 0),
  hwy = c(60, 5),
  cyl = c(0, 20),
  etiqueta = c("supersonicos", "picapiedras")
)
pica_supersonicos

Ahora crearemos el mismo gráfico de puntos, le agregaremos una capa adicional de puntos pero con otra fuente de datos, con las mismas variables, pero otros datos, el modelo de autos de los supersonicos y los picapiedras. Incrementaremos el tamaño de los autos de los supersonicos y los picapiedras para que sea más fácil identificarlos, esto lo hacemos con un parametro fijo size = 4, por fijo me refiero que no se vincula a una variable de la data, de hecho, al fijar los parametros a una constante, lo hago afuera de la función aes().

ggplot(data = mpg, mapping = aes(x = displ, y = hwy, colour = as.factor(cyl))) +
  geom_point() +
  geom_point(data = pica_supersonicos, size = 4) +
  ggtitle("Un gráfico de dos capas de puntos representando\ndos fuentes distintas de datos!")

Y así podemos ir agregando capaz, por ejemplo, agreguemos una tercera para crear una etiqueta para los puntos del auto de los picapiedras y los supersonicos.

ggplot(data = mpg, mapping = aes(x = displ, y = hwy, colour = as.factor(cyl))) +
  geom_point() +
  geom_point(data = pica_supersonicos, size = 4) +
  geom_text(mapping = aes(x = displ, y = hwy, label = etiqueta), data = pica_supersonicos,
            size = 2, nudge_y = 2) +
  ggtitle("Un gráfico de tres capas que ilustra el arte\nde componer por capas visuales!")

LS0tDQp0aXRsZTogIkNsYXNlIDM6IEludHJvZHVjY2nDs24gYSBsYSB2aXN1YWxpemFjacOzbiINCm91dHB1dDogDQogIGh0bWxfbm90ZWJvb2s6IA0KICAgIGhpZ2hsaWdodDoga2F0ZQ0KICAgIHRoZW1lOiBjZXJ1bGVhbg0KICAgIHRvYzogeWVzDQotLS0NCg0KSGFibGFtb3MgbGEgY2xhc2UgcGFzYWRhIGRlIGV4dGVuZGVyIGxhcyBjYXBhY2lkYWRlcyBkZSBSLCB1dGlsaXphbmRvIGhlcnJhbWllbnRhcyBhZGljaW9uYWxlcyBwYXJhIGRpZmVyZW50ZXMgcHJvcG9zaXRvcyBxdWUgdmllbmVuIGVuIGZvcm1hIGRlICJwYXF1ZXRlcyIuIEVtcGxlYW1vcyBsYSBhbsOhbG9naWEgZGUgcXVlICJSIiBlcyBjb21vIHVuIGNlbHVsYXIgbnVldm8sIGVsIGN1YWwgdGllbmUgY2llcnRhcyBmdW5jaW9uYWxpZGFkZXMgcXVlIHZpZW5lbiBwb3IgZGVmZWN0bywgcGVybyBxdWUgYWRlbcOhcyBub3NvdHJvcyBwb2RlbW9zIGFncmVnYXIgbcOhcyBmdW5jaW9uZXMgYSBudWVzdHJvcyBjZWx1bGFyZXMgYSB0cmF2w6lzIGRlIGxhIGRlc2NhcmdhIGRlIGFwbGljYWNpb25lcyBkZXNkZSAiYXBwIHN0b3JlIiBvICJnb29nbGUgcGxheSIuIEVuIGVsIGNhc28gZGVsIHNpZ3VpZW50ZSBhbsOhbGlzaXMsIHV0aWxpemFyZW1vcyBsYXMgYXBsaWNhY2lvbmVzICJnZ3Bsb3QyIiwgImRwbHlyIiB5ICJza2ltciIsIHF1ZSBhZ3JlZ2FyYW4gbnVldmFzIGhlcnJhbWllbnRhcyBwYXJhIHV0aWxpemFyIGVuIG51ZXN0cm8gY8OzZGlnbyBhIHRyYXbDqXMgZGVsIGNvbmNlcHRvIGRlIGZ1bmNpb25lcyBlbiBwcm9ncmFtYWNpw7NuLg0KDQpgYGB7ciwgbWVzc2FnZSA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0V9DQpsaWJyYXJ5KGdncGxvdDIpDQpsaWJyYXJ5KGRwbHlyKQ0KbGlicmFyeShza2ltcikNCmBgYA0KDQpFbiBlc3RhIGNsYXNlIHV0aWxpemFyZW1vcyBjb21vIGVqZW1wbG8gbGEgZGF0YSAibXBnIiBxdWUgdmllbmUgY2FyZ2FkYSBjb24gZWwgcGFxdWV0ZSAiZ2dwbG90MiIuIEVzdGEgZGF0YSBjb250aWVuZSBpbmZvcm1hY2nDs24gc29icmUgbGEgZWZpY2llbmNpYSBkZSBjb21idXN0aWJsZSBkZSBkaXN0aW50b3MgbW9kZWxvcyBkZSBhdXRvcyB5IHN1cyBjYXJhY3RlcmlzdGljYXMuDQoNCiMjIEluc3BlY2Npb25hbmRvIGxvcyBkYXRvcw0KICANCkVzIGJ1ZW5vIGVudGVuZGVyIGFudGVzIGRlIGN1YWxxdWllciBhbsOhbGlzaXMsIGxhIGRhdGEgY29uIGxhIHF1ZSBjb250YW1vcyB5IHBhcmEgZXN0byBlcyDDunRpbCBsYSBmdW5jacOzbiBkcGx5cjo6Z2xpbXBzZSgpLiBQb3IgZnVuY2nDs24gZW50ZW5kZXJlbW9zIHVuIHByb2Nlc28gcXVlIHRvbWEgdW4gaW5wdXQgbyBlbnRyYWRhLCBjb21vIHVuIGRhdGFmcmFtZSAoZGF0YSByZWN0YW5ndWxhciksIGx1ZWdvIGVqZWN1dGEgdW5hIG9wZXJhY2nDs24gc29icmUgZXN0ZSBpbnB1dCBlbiBxdWUgc2UgcHVlZGVuIGFqdXN0YXIgY2llcnRvcyBhc3BlY3RvcyBwYXJhIGNvbnRyb2xhciBsYSBvcGVyYWNpw7NuIHkgZmluYWxtZW50ZSByZWNpYmltb3MgdW4gb3V0cHV0IG8gcmVzdWx0YWRvLg0KDQpBIGxhIGZ1bmNpw7NuIGRwbHlyOjpnbGltcHNlKCkgbGUgZW50cmVnYW1vcyBjb21vIGFyZ3VtZW50byBudWVzdHJhIGRhdGEgcmVjdGFuZ3VsYXIgeSBub3MgYXJyb2phIGluZm9ybWFjacOzbiBhY2VyY2EgZGVsIHRpcG8gZGUgdmFyaWFibGVzIChjb2x1bW5hcykgeSBhbGd1bmFzIGRlIGxhcyBvYnNlcnZhY2lvbmVzIGRlIGNhZGEgdW5hIGRlIGVzdGFzLg0KDQoNCmBgYHtyfQ0KZ2xpbXBzZShtcGcpDQpgYGANCg0KT3RyYSBmdW5jacOzbiDDunRpbCBwYXJhIGxhIGV4cGxvcmFjacOzbiBkZSBudWVzdHJvcyBkYXRvcyBlcyBza2ltcjo6c2tpbSgpLCBlc3RhIHRvbWEgY29tbyBpbnB1dCB1bmEgZGF0YSByZWN0YW5ndWxhciAoZGF0YWZyYW1lKSB5IG5vcyBlbnRyZWdhIGRpc3RpbnRhcyBtZWRpZGEgZGUgaW5mb3JtYWNpw7NuLCBlbiB1biBmb3JtYXRvIHJlY3Rhbmd1bGFyLCBkZSBjYWRhIHVuYSBkZSBsYXMgdmFyaWFibGVzIGNvbW8gZWwgdmFsb3IgbcOtbmltbywgbcOheGltbywgdmFsb3JlcyBkaXN0aW50b3MsIGNhbnRpZGFkIGRlIG9ic2VydmFjaW9uZXMgY29uIHZhbG9yZXMgcGVyZGlkb3MsIGVudHJlIG90cmFzLg0KDQpgYGB7cn0NCnNraW0obXBnKQ0KYGBgDQoNCiMjIExhIHByaW1lcmEgdmlzdWFsaXphY2nDs24NCg0KRW1wZWNlbW9zIGNvbiBudWVzdHJhIHByaW1lcmEgdmlzdWFsaXphY2nDs24gZW4gUiwgdmVhbW9zIGxhIHJlbGFjacOzbiBxdWUgZXhpc3RlIGVudHJlIGxhIGNhcGFjaWRhZCBkZSBkZXNwbGF6YW1pZW50byAoZGlzcGwpIGRlIGxvcyB2ZWhjdWxvcyBjb24gcmVzcGVjdG8gYSBsYXMgbWlsbGFzIHJlbmRpZGFzIGVuIGNhcnJldGVyYSAoaHd5KS4NCg0KYGBge3IsIGVjaG8gPSBUUlVFfQ0KZ2dwbG90KGRhdGEgPSBtcGcpICsgDQogIGdlb21fcG9pbnQobWFwcGluZyA9IGFlcyh4ID0gZGlzcGwsIHkgPSBod3kpKQ0KYGBgDQoNCsK/QWxndWllbiBtZSBwdWVkZSBjb250YXIgbG8gcXVlIG5vcyBkaWNlIGVzdGUgZ3LDoWZpY28/IEVzIGltcG9ydGFudGUgYXByZW5kZXIgYSBsZWVyIGxvcyBncsOhZmljb3MsIHNpIGJpZW4gbGEgdmVudGFqYSBkZSByZXZlbGFyIHBhdHJvbmVzIGVzIGluZHVkYWJsZSwgbm8gZXMgbcOhZ2ljYSB0YW1wb2NvIGVuIGVsIHNlbnRpZG8gZGUgcXVlIHBvciB2ZXIgZWwgZ3LDoWZpY28geWEgbG8gdmVtb3MgdG9kby4NCg0KKkEgbWVkaWRhIHF1ZSBhdW1lbnRhIGxhIGNhcGFjaWRhZCBkZSBkZXNwbGF6YW1pZW50byAoZGlzcGwpLCBsYSBjdWFsIHNlIG1pZGUgZW4gbGl0cm9zLCBsYSBlZmljaWVuY2lhIGVuIGNhcnJldGVyYSBkZSBsb3MgYXV0b3MsIG1lZGlkYSBlbiBtaWxsYXMgcG9yIGdhbG9uLCBkaXNtaW51eWUqLiBFbiB0w6lybWlub3Mgc2ltcGxlcywgb2JzZXJ2YW5kbyBlc3RhcyBkb3MgdmFyaWFibGVzIHkgZGVqYW5kbyB0b2RvIGxvIGRlbcOhcyBjb25zdGFudGUsIG1heW9yZXMgbW90b3JlcyBtdWVzdHJhbiB1bmEgbWVub3IgZWZpY2llbmNpYSBlbiBjYXJyZXRlcmEuDQoNCkFob3JhLCBwYXJhIGxvZ3JhciB2aXN1YWxpemFyIGVzdG8sIMK/cXVlIG5lY2VzaXRvPyBMb3MgaW5ncmVkaWVudGVzIG3DrW5pbW9zIHBhcmEgY3JlYXIgdW4gZ3LDoWZpY28gc29uIGxvcyBzaWd1aWVudGVzOg0KDQoxLiBEYXRvcy4NCjIuIFZpbmN1bGFyIGxhcyB2YXJpYWJsZXMgcXVlIHF1ZXJlbW9zIHJlcHJlc2VudGFyIGEgbG9zIGFzcGVjdG9zIHZpc3VhbGVzIGRlbCBncsOhZmljby4NCjMuIEEgbG8gbWVub3MgdW5hIGNhcGEgcXVlIGNvbnRlbmdhIHVuYSByZXByZXNlbnRhY2nDs24gZ2VvbWV0cmljYSBkZSBsb3MgZGF0b3MgKHB1bnRvcywgbMOtbmVhcywgYmFycmFzLCBldGPDqXRlcmEpDQoNCkVsIHByaW1lciBwdW50byBlcyBldmlkZW50ZSwgZWwgc2VndW5kbyB0cmF0YSBkZSBtYXBlYXIgdmFyaWFibGVzIGRlbCBjb25qdW50byBkZSBkYXRvcyByZWxldmFudGVzIHBhcmEgZWwgZ3LDoWZpY28gYSBjaWVydG9zIGF0cmlidXRvcyBlc3RldGljb3MuIEFsZ3VubyBkZSBsb3MgYXRyaWJ1dG9zIG3DoXMgY29tdW5lcyBzb246DQoNCi0gZWplIHgNCi0gZWplIHkNCi0gY29sb3INCi0gdGFtYcOxbw0KLSBmb3JtYQ0KDQpFbiBudWVzdHJvIHByaW1lciBncsOhZmljbyB1dGlsaXphbW9zIHNvbG8gZG9zIGF0cmlidXRvcyBlc3TDqXRpY29zOiBlbCBlamUgeCBlIHkuIFBvZGVtb3MgYWdyZWdhciBtw6FzIGluZm9ybWFjacOzbiBhIG51ZXN0cm8gZ3LDoWZpY28gdmluY3VsYW5kbyBhdHJpYnV0b3MgZXN0w6l0aWNvcyBhZGljaW9uYWxlcyBhIHZhcmlhYmxlcyBjb250ZW5pZGFzIGVuIGxvcyBkYXRvcy4NCg0KYGBge3IsIGVjaG8gPSBUUlVFfQ0KZ2dwbG90KGRhdGEgPSBtcGcpICsNCiAgZ2VvbV9wb2ludChtYXBwaW5nID0gYWVzKHggPSBkaXNwbCwgeSA9IGh3eSwgY29sb3VyID0gY2xhc3MpKQ0KYGBgDQoNClVuYSBkZSBsYXMgdmVudGFqYXMgZGUgKipnZ3Bsb3QyKiogZXMgcXVlIHBlcm1pdGUgY29uc3RydWlyIGdyw6FmaWNvcyB1dGlsaXphbmRvIGVzcGVjaWZpY2FjaW9uZXMgbcOtbmltYXMgcXVlIGp1bnRvIGEgY29uZmlndXJhY2lvbmVzIHByZXN0YWJsZWNpZGFzLCBub3MgZmFjaWxpdGFuIGVsIHRyYWJham8gZGUgZGVmaW5pciB0b2RvcyBsb3MgYXNwZWN0b3MgcG9zaWJsZXMgZGUgdW4gZ3LDoWZpY28uIFBvciBlamVtcGxvLCBlbiBlbCBncsOhZmljbyBkZSBhcnJpYmEgKipnZ3Bsb3QyKiogYXV0b21hdGljYW1lbnRlIGNyZWEgbGEgbGV5ZW5kYSBjb24gbGEgaW5mb3JtYWNpw7NuIGRlIHF1ZSBjb2xvciBlcyBjYWRhIG5pdmVsIGRlIGxhIHZhcmlhYmxlIGNsYXNlLiBFc3RvIGVzIHBvc2libGUgZ3JhY2lhcyBhIGxhcyBlc3BlY2lmaWNhY2lvbmVzIGludGVybmFzIGRlIGxhIGRhdGEgcXVlIGhheSBkZWJham8gZGUgY2FkYSBncsOhZmljbzoNCg0KYGBge3J9DQoocmVwcmVzZW50YWNpb25faW50ZXJuYSA8LSBtcGcgJT4lIA0KICBzZWxlY3QoZGlzcGwsIGh3eSwgY2xhc3MpICU+JSANCiAgbGVmdF9qb2luKG1wZyAlPiUgDQogICAgICAgICAgICBkaXN0aW5jdChjbGFzcykgJT4lIA0KICAgICAgICAgICAgbXV0YXRlKGNvbG91ciA9IGMoInJlZCIsICJicm93biIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImdyZWVuIiwgImJsdWUiLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJjYWx5cHNvIiwgInB1cnBsZSIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgInllbGxvdyIpKSwNCiAgYnkgPSAiY2xhc3MiKSAlPiUgDQogIHNlbGVjdCgtY2xhc3MpICU+JSANCiAgcmVuYW1lKHggPSBkaXNwbCwNCiAgICAgICAgIHkgPSBod3kpKQ0KYGBgDQoNCk9idmlhbmRvIGxvcyBkZXRhbGxlcyBkZWwgY8OzZGlnbyBkZSBhcnJpYmEsIGFzw60gZXMgY29tbyAqKmdncGxvdDIqKiBtYXBlYSBsYXMgdmFyaWFibGVzIHF1ZSBxdWVyZW1vcyBncsOhZmljYXIgZGUgbnVlc3RyYSBiYXNlIChtcGcpIGEgbGFzIHByb3BpZWRhZGVzIHZpc3VhbGVzICh4LCB5LCBjb2xvdXIpLg0KDQoNCmBgYHtyfQ0KKGRpY2Npb25hcmlvX2NvbG9yZXMgPC0gbXBnICU+JSANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBkaXN0aW5jdChjbGFzcykgJT4lIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIG11dGF0ZShjb2xvdXIgPSBjKCJyZWQiLCAiYnJvd24iLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiZ3JlZW4iLCAiYmx1ZSIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJjYWx5cHNvIiwgInB1cnBsZSIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJ5ZWxsb3ciKSkpDQpgYGANCg0KUG9kZW1vcyBzZWd1aXIgaW5jb3Jwb3JhbmRvIGluZm9ybWFjacOzbiBlbiBudWVzdHJvIGdyw6FmaWNvIG1hcGVhbmRvIG90cmFzIHZhcmlhYmxlcyBkZSBsYSBiYXNlICoqbXBnKiogYSBvdHJvcyBhdHJpYnV0b3MgZXN0w6l0aWNvcyBkZW50cm8gZGUgbGEgZnVuY2nDs24gKmFlcygpKiwgcG9yIGVqZW1wbG8sIGVsIG7Dum1lcm8gZGUgY2lsaW5kcm9zIHF1ZSB0aWVuZW4gbG9zIHZlaMOtY3Vsb3MgYSB0cmF2w6lzIGRlbCBhdHJpYnV0byAqc2hhcGUqLg0KDQpgYGB7ciBlY2hvPVRSVUUsIGZpZy5hbGlnbj0nY2VudGVyJ30NCmdncGxvdChkYXRhID0gbXBnKSArDQogIGdlb21fcG9pbnQobWFwcGluZyA9IGFlcyh4ID0gZGlzcGwsIHkgPSBod3ksIGNvbG91ciA9IGNsYXNzLCBzaGFwZSA9IGFzLmZhY3RvcihjeWwpKSkNCmBgYA0KDQpTaW4gZW1iYXJnbywgYWPDoSBub3MgdmVtb3MgZW5mcmVudGFkbyBhIHVuICp0cmFkZS1vZmYqIGVudHJlIGNhbnRpZGFkIGRlIGluZm9ybWFjacOzbiB5IGNhcGFjaWRhZCBkZSBjb21wcmVuc2nDs24uIFRyYXRhciBkZSBhYmFyY2FyIGRlbWFzaWFkb3MgYXNwZWN0b3MgdmlzdWFsZXMgZW4gdW4gc29sbyBncsOhZmljbyB0aWVuZSB1biBjb3N0bywgcXVlIGVzIGxhIGRpZmljdWx0YWQgZGUgY29tdW5pY2FyIGFsIGludHJvZHVjaXIgbWF5b3IgY29tcGxlamlkYWQgZW4gbnVlc3RyYSB2aXN1YWxpemFjacOzbi4gDQoNCg0KIyMgRmFjZXRhcw0KDQpVbmEgZm9ybWEgcGFyYSBjb21wYXJhciBkaXN0aW50b3MgZ3J1cG9zIGRlIGRhdG9zLCBlbnRlbmRhbW9zIGVzdG9zIGdydXBvcyBjb21vIHVuYSBkYXRhIHRhYnVsYXIgc2VwYXJhZGEgZW4gdmFyw61vcyBjb25qdW50b3MgcG9yIGxvcyBkaXN0aW50b3MgdmFsb3JlcyBkZSB1bmEgdmFyaWFibGUgY2F0ZWfDs3JpY2EsIHBvciBlamVtcGxvLCBsb3MgY3VhdHJvcyBzdWJjb25qdW50b3MgZGUgZGF0b3MgcXVlIG9idGVuZW1vcyBhbCBzZXBhcmFyIGxhIGRhdGEgKiptcGcqKiBwb3IgbG9zIGF1dG9zIHF1ZSB0aWVuZW4gaWd1YWwgbsO6bWVybyBkZSBjaWxpbmRyb3MgaW5kaWNhZG9zIGVuIGxhIHZhcmlhYmxlICpjeWwqLg0KDQoNCmBgYHtyfQ0KbGFwcGx5KHNwbGl0KG1wZywgbXBnJGN5bCksIGhlYWQsIG4gPSAzKQ0KYGBgDQoNCkVuIHZleiBkZSB1dGlsaXphciBjdWF0cm8gYXRyaWJ1dG9zIGVzdMOpdGljb3MgeSBlbXBlemFyIGEgbWVybWFyIGxhIGNhcGFjaWRhZCBkZSBjb21wcmVuc2nDs24gZGVsIGdyw6FmaWNvIHBvciBjYXVzYSBkZWwgYXVtZW50byBkZSBjb21wbGVqaWRhZCBkZSBpbmZvcm1hY2nDs24sIHBvZGVtb3MgZGVqYXIgc29sbyB0cmVzIGRlIGVzdG9zIGF0cmlidXRvcyBwZXJvIGdlbmVyYXIsIHBhcmEgY29tcGFyYXIsIGVsIG1pc21vIGdyw6FmaWNvIGVuIGRpc3RpbnRvcyBzdWJjb25qdW50b3MgZGUgZGF0b3MuIEVzdGEgdMOpY25pY2EgZW4gKipnZ3Bsb3QyKiogc2UgY29ub2NlIHBvciAqZmFjZXRzKiB5IHJlc3BlY3RvIGFsIGVqZW1wbG8gYW50ZXJpb3IsIGdlbmVyYW1vcyB1biBncsOhZmljbyBwYXJhIGNhZGEgdW5vIGRlIGxvcyBkaXN0aW50b3MgZ3J1cG9zIGRlIGRhdG9zIGFsIHNlcGFyYXIgKiptcGcqKiBwb3IgbG9zIHZhbG9yZXMgdW5pY29zIHByZXNlbnRlcyBlbiBsYSB2YXJpYWJsZSAqY3lsKi4NCg0KDQpgYGB7cn0NCmdncGxvdChkYXRhID0gbXBnLCBtYXBwaW5nID0gYWVzKHggPSBkaXNwbCwgeSA9IGh3eSwgY29sb3VyID0gY2xhc3MpKSArDQogIGdlb21fcG9pbnQoKSArDQogIGZhY2V0X3dyYXAofiBjeWwpDQpgYGANCg0KQWhvcmEgdGVuZW1vcyB1biBudWV2byBjb21wb25lbnRlIHF1ZSBwb2RlbW9zIGFncmVnYXIgYSBsYSBzaW50YXhpcyBkZSBudWVzdHJvIGPDs2RpZ28uIEFic3RyYXllbmRvIHRvZG8gbG8gcXVlIGhlbW9zIHZpc3RvIGhhc3RhIGFob3JhOg0KDQpgYGB7ciwgZXZhbCA9IEZBTFNFfQ0KI2dncGxvdChkYXRhID0gbWlzX2RhdG9zLCBtYXBwaW5nID0gYWVzKHgsIHksIC4uLikpICsNCiMgIGdlb21fPE5BTUU+KCkgKw0KIyAgZmFjZXRfd3JhcCh+IHZhcl9jYXRlZ29yaWNhKQ0KYGBgDQoNCiMjIERpc3RpbnRhcyByZXByZXNlbnRhY2lvbmVzIGdlb23DqXRyaWNhczogZ2VvbV9cPFJFUFJFU0VOVEFDSU9OXD4NCg0KRW4gbG9zIGdyw6FmaWNvcyBhbnRlcmlvcmVzIGhlbW9zIHJlcHJlc2VudGFkbyBudWVzdHJvcyBkYXRvcyBzb2xvIGEgdHLDoXZlcyBkZSANCnB1bnRvcy4gwr9Dw7NtbyBpbmNvcnBvcmFtb3Mgb3RyYXMgcmVwcmVzZW50YWNpb25lcyBnZW9tZXRyaWNhcyBwYXJhIHZpc3VhbGl6YXINCmRhdG9zPw0KDQpFbCBwYXF1ZXRlICoqZ2dwbG90MioqIHRyYWJhamEgYSB0csOhdmVzIGRlIGNhcGFzIGRlIHJlcHJlc2VudGFjacOzbiBncsOhZmljYXMgc29icmUgZGF0b3MsIGNhZGEgdW5hIGRlIGVzdGFzIGNhcGFzIHJlcXVpZXJlcyBkZSBsb3MgdHJlcyBpbmdyZWRpZW50ZXMgbcOtbmltb3Mgbm9tYnJhZG9zIGFycmliYTogZGF0b3MsIG1hcGVvIGRlIHZhcmlhYmxlcyBhIGF0cmlidXRvcyBlc3TDqXRpY29zIHkgbGEgcmVwcmVzZW50YWNpw7NuIGdlb23DqXRyaWNhIChvIGdlb20pLg0KDQpBbnRlcmlvcm1lbnRlLCBoZW1vcyB0cmFiYWphZG8gY29uIHVuYSBjYXBhIHZpc3VhbCBkZSBwdW50b3MgcGVybw0KcG9kZW1vcyB1dGlsaXphciBvdHJvIHRpcG8gZGUgcmVwcmVzZW50YWNpb25lcyBnZW9tw6l0cmljYXMgZW4gbGEgY29uc3RydWNjacOzbiBkZSBlc3RhcyBjYXBhcy4NCmBgYHtyLCBlY2hvID0gVFJVRX0NCmdncGxvdChkYXRhID0gbXBnKSArDQogIGdlb21fYmFyKG1hcHBpbmcgPSBhZXMoeCA9IGRydikpDQpgYGANCg0KU2kgb2JzZXJ2YW1vcyBiaWVuIGVsIGPDs2RpZ28gZGVsIGdyw6FmaWNvIGRlIGJhcnJhLCBzb2xvIGVtcGxlYW1vcyB1biBhdHJpYnV0byBlc3TDqXRpY28gY29tcGFyYWRvIGNvbiBsb3MgZG9zIGF0cmlidXRvcyBtw61uaW1vcyBxdWUgdXRpbGl6YW1vcyBwYXJhIGNyZWFyIGVsIGdyw6FmaWNvIGRlIHB1bnRvcy4gwr9Dw7NtbyBzZSBleHBsaWNhIGVzdG8/ICoqQ2FkYSByZXByZXNlbnRhY2nDs24gZ2VvbcOpdHJpY2EgdGllbmUgYXNvY2lhZG8gdW4gY29uanVudG8gZGUgYXRyaWJ1dG9zIGVzdMOpdGljb3MqKiBxdWUgcHVlZGVuIG8gbm8gdGVuZXIgc2ltaWxpdHVkZXMgY29uIG90cm9zICoqZ2VvbSoqLg0KDQpgYGB7ciwgZXZhbCA9IEZBTFNFfQ0KP2dlb21fYmFyDQpgYGANCg0KDQpPdHJvcyBhc3BlY3RvIGludGVyZXNhbnRlIGRlIG1lbmNpb25hciBjb24gcmVzcGVjdG8gYSBsYSByZWxhY2nDs24gKipnZW9tKiogeSBtYXBlbyBkZSB2YXJpYWJsZXMgZGVzZGUgbG9zIGRhdG9zIGEgbG9zIGF0cmlidXRvcyBlc3TDqXRpY29zLCBlcyBsYSBkaWZlcmVuY2lhIGVudHJlIHNpbnRheGlzIGNvbW8gZXNwZWNpZmljYWNpw7NuIGRlIGPDs2RpZ28gdmFsaWRvIHBvciBlbCBwYXF1ZXRlIGdncGxvdDIgeSBsYSBzZW3DoW50aWNhIHZpc3VhbCBwcm9kdWNpZGEgcG9yIGVsIGdyw6FmaWNvIGNyZWFkby4gVW5hIHNpbnRheGlzIHbDoWxpZGEgZW4gZ2dwbG90MiBxdWUgc2UgdHJhZHVjZSBlbiB1biBncsOhZmljbywgwr9pbXBsaWNhIHF1ZSBlc3RlIHRlbmdhIHNlbcOhbnRpY2EgdmlzdWFsPw0KDQpgYGB7ciwgZWNobyA9IFRSVUV9DQpnZ3Bsb3QoZGF0YSA9IG1wZykgKw0KICBnZW9tX2xpbmUoYWVzKHggPSBkaXNwbCwgeSA9IGh3eSwgbGluZXR5cGUgPSBhcy5mYWN0b3IoY3lsKSwgY29sb3VyID0gYXMuZmFjdG9yKGN5bCkpKQ0KYGBgDQoNClBhcmEgZ3LDoWZpY2FyIHVuYSBsw61uZWEgc2UgbmVjZXNpdGFuIHRlbmVyIGxhcyBjb29yZGVuYWRhcyBkZSBjYWRhIHB1bnRvLCBlcyBkZWNpciANCmxvcyBhdHJpYnV0b3MgZXN0w6l0aWNvcyB4IGUgeSwgcXVlIHNvbiB1dGlsaXphZG9zIHBvciAqZ2VvbV9saW5lKiBwYXJhIHVuaXIgY2FkYSANCnVuYSBkZSBlc3RhcyBjb29yZGVuYWRhcyBlbiBiYXNlIGFsIGVqZSB4IGRlIGl6cXVpZXJkYSBhIGRlcmVjaGEuIEVzdGEgcmVwcmVzZW50YWNpw7NuDQplcyBpZGVhbCBwYXJhIHZpc3VhbGl6YXIgbGEgZXZvbHVjacOzbiBkZSB1bmEgdmFyaWFibGUgYSB0csOhdmVzIGRlbCB0aWVtcG8uIFNpbiBlbWJhcmdvLCANCmVuIGVsIGdyw6FmaWNvIGRlIGFycmliYSwgdmVtb3MgcXVlIHVuYSBzaW50YXhpcyB2w6FsaWRhIG5vIGVzIGVsIMO6bmljbyByZXF1aXNpdG8gcGFyYQ0KY3JlYXIgdW4gZ3LDoWZpY28gc2VtYW50aWNhbWVudGUgdmFsaWRvLg0KDQoNCiMjIyBUcmFuc2Zvcm1hY2lvbmVzIGVzdGFkw61zdGljYXMgeSBnZW9tcw0KDQpBbmFsaWNlbW9zIHVuIGFzcGVjdG8gZGUgKipnZ3Bsb3QyKiogY29uIGVsIGdyw6FmaWNvIGRlIGJhcnJhLiBJbWFnaW5lbW9zIHF1ZSB0ZW5lbW9zIGxhIGNhbnRpZGFkIGRlIGFuaW1hbGVzIHF1ZSBoYXkgZW4gY2llcnRhIGNvbXVuaWRhZDogDQoNCmBgYHtyfQ0KYW5pbWFsZXNfYWdydXBhZG8gPC0gdHJpYmJsZSgNCiAgICAgICAgICAgICAgICAgICAgICAgIH4gYW5pbWFsLCAgIH4gbnVtX29icywNCiAgICAgICAgICAgICAgICAgICAgICAgICAiUGVycm8iLCAgICAgICAgICAgMjAzLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAiR2F0byIsICAgICAgICAgICAxNDIsDQogICAgICAgICAgICAgICAgICAgICAgICJQYWphcm9zIiwgICAgICAgICAgIDEwOA0KICAgICAgICAgICAgIA0KKQ0KYGBgDQoNCkNvbiBsYSBmdW5jacOzbiAqdHJpYmJsZSogcG9kZW1vcyBjcmVhciB1bmEgZGF0YSByZWN0YW5ndWxhciAoZGF0YWZhbWUgbyB0aWJibGUpIG5vc290cm9zIG1pc21vcy4gRXN0YSBzZSBjb25zdHJ1eWUgdmlzdWFsbWVudGUgY29tbyBlc3RhIGFycmliYSwgaW5kaWNhbmRvIGxvcyBub21icmVzIGRlIGxhcyBjb2x1bW5hcyBjb24gbGEgY29sYSBkZSBjaGFuY2hvICoqfioqIChhbHQgKyAxMjYgZW4gZWwgdGVjbGFkbyBuw7ptZXJpY28pIHkgbHVlZ28gYWdyZWdhbmRvIGxhcyBvYnNlcnZhY2lvbmVzICoqcG9yIGZpbGEqKiBpbmRlbnRhbmRvbGFzIHBhcmEgcXVlIHF1ZWRlbiBhbGluZWFkYXMgYmFqbyBsYSBjb2x1bW5hIHF1ZSBwZXJ0ZW5lY2VuLiDCoU5vIG9sdmlkZW1vcyBzZXBhcmFyIGNhZGEgYXJndW1lbnRvIGRlIGxhIGZ1bmNpw7NuICp0cmliYmxlKiBjb24gdW5hIGNvbWEhDQoNCk51ZXN0cmEgZGF0YSBkZSBqdWd1ZXRlICphbmltYWxlc19hZ3J1cGFkbyosIGVzdGUgdGlwbyBkZSBkYXRhIGxhIGxsYW1hcmVtb3MgYXPDrSBwb3JxdWUgZXMgbcOtbmltYSB5IGRlIGVqZW1wbG8sIHJlcHJlc2VudGEgZWwgbsO6bWVybyBkZSBwZXJyb3MsIGdhdG9zIHkgcGFqYXJvcyBxdWUgaGF5IGVuIHVuIGVkaWZpY2lvLiBFc3RvIGVzIHBlcmZlY3RvIHBhcmEgZ3LDoWZpY2FyIGNvbiBiYXJyYXMsIHNpbiBlbWJhcmdvLCBlcyDDunRpbCBkZXN0YWNhciBxdWUgeWEgY29udGFtb3MgY3VhbnRvcyBhbmltYWxlcyBoYXkgZGUgY2FkYSB1bm8uDQoNCkxhIGNhcGEgKipnZW9tX2JhcioqIGltcGxpY2EgdW5hIHRyYW5zZm9ybWFjacOzbiBlc3RhZGlzdGljYSBzb2JyZSBudWVzdHJvcyBkYXRvcyBwYXJhIHJlcHJlc2VudGFyIHZpc3VhbG1lbnRlIGVzdG9zLiBFc3RhIHRyYW5zZm9ybWFjacOzbiBlcyBsYSBkZSAqKmNvbnRhcioqLCAqKmdlb21fYmFyKiogY3VlbnRhIGVsIG7Dum1lcm8gZGUgb2JzZXJ2YWNpb25lcyBxdWUgaGF5IGVuIGxhIGRhdGEgeSBsYSByZXByZXNlbnRhIGNvbiBiYXJyYXMsIHBlcm8gZW4gKmFuaW1hbGVzX2FncnVwYWRvKiBsb3MgZGF0b3MgeWEgZnVlcm9uIGNvbnRhZG9zLiBFc3RhIGVzIGxhIHJhesOzbiBkZSBxdWUgdXRpbGl6YW1vcyBlbCBhcmd1bWVudG8gKnN0YXQqIGRlbnRybyBkZSAqKmdlb21fYmFyKiogY29uIGVsIHZhbG9yICJpZGVudGl0eSIgcXVlIHNlIGxlZSBjb21vICpOTyBhcGxpY2FyIHRyYW5zZm9ybWFjacOzbiBzb2JyZSBsb3MgZGF0b3MqLg0KDQoNCg0KYGBge3IsIGVjaG8gPSBUUlVFfQ0KZ2dwbG90KGRhdGEgPSBhbmltYWxlc19hZ3J1cGFkbykgKw0KICBnZW9tX2JhcihtYXBwaW5nID0gYWVzKHggPSBhbmltYWwsIHkgPSBudW1fb2JzKSwNCiAgICAgICAgICAgc3RhdCA9ICJpZGVudGl0eSIpDQpgYGANCg0KUGFyYSBlbnRlbmRlciBtZWpvciBsbyBhbnRlcmlvciwgY3JlZWVtb3Mgb3RyYSBkYXRhIHNpbiBlbCBuw7ptZXJvIGRlIG9ic2VydmFjaW9uZXMgKGFuaW1hbGVzKSB5YSBhZ3J1cGFkb3MuIFV0aWxpemlhcmVtb3MgYWhvcmEgbGEgZnVuY2nDs24gKnRpYmJsZSogKHNpbiBsYSByKSwgcXVlIGVzIHBhcmEgY3JlYXIgZGF0YSByZWN0YW5ndWxhciBwb3IgY29sdW1uYXMsIHBhcmEgY3JlYXIgdW5hIGRhdGEgZGUgc29sbyB1bmEgY29sdW1uYSB5IGByIDIwMyArIDE0MiArIDEwOGAgZmlsYXMuIE5vcyBheXVkYW1vcyBlbiBlc3RhIHRhcmVhIGRlIGxhIGZ1bmNpw7NuICoqcmVwKiogcXVlIHJlcGl0ZSBlbiBlc3RlIGNhc28gZWwgbm9tYnJlIGRlbCB0aXBvIGRlIGFuaW1hbCBsYXMgdmVjZXMgcXVlIGxlIGluZGlxdWVtb3MgZW4gZWwgc2VndW5kbyBhcmd1bWVudG8uDQoNCmBgYHtyfQ0KYW5pbWFsZXNfbm9fYWdydXBhZG9zIDwtIHRpYmJsZSgNCiAgICBhbmltYWxlcyA9IGMocmVwKCJQZXJybyIsIDIwMyksICAjIHVuYSBzb2xhIGNvbHVtbmEgY29uIGVsIG5vbWJyZSAiYW5pbWFsZXMiLg0KICAgICAgICAgICAgICAgICByZXAoIkdhdG8iLCAxNDIpLCAgICAgICMgcXVlIHNlIGNvbnN0cnV5ZSBjb25jYXRlbmFuZG8gImMoKSIgDQogICAgICAgICAgICAgICAgIHJlcCgiUGFqYXJvcyIsIDEwOCkpICAgIyB0cmVzIHZlY3RvcmVzDQopDQpgYGANCg0KQWhvcmEgcXVlIGNyZWFtb3MgbnVlc3RyYSBkYXRhICphbmltYWxlc19ub19hZ3J1cGFkb3MqIHBvZGVtb3MgdmVyIGNvbW8gcXVlZG86DQoNCmBgYHtyfQ0KYW5pbWFsZXNfbm9fYWdydXBhZG9zDQpgYGANCg0KQWhvcmEgY3JlYXJlbW9zIHVuIGdyw6FmaWNvIGRlIGJhcnJhIHV0aWxpemFuZG8gbGEgZGF0YSAqYW5pbWFsZXNfbm9fYWdydXBhZG9zKiwgcGVybyBlc3RhIHZleiwgbm8gZmlqYXJlbW9zIGVsIGFyZ3VtZW50byAqc3RhdCogaWd1YWwgYSAiSWRlbnRpdHkiLiBFcyBtw6FzLCBzb2xvIHRlbmVtb3MgdW5hIGNvbHVtbmEgKmFuaW1hbGVzKiwgcG9yIGxvIHF1ZSBubyBwb2RlbW9zIGZpamFyIGVsIGVqZSB5IGEgZGlmZXJlbmNpYSBkZWwgZ3LDoWZpY28gYW50ZXJpb3IsIGVuIHF1ZSB0ZW7DrWFtb3MgdW5hIGNvbHVtbmEgKm51bV9vYnMqIGNvbiBlbCB0b3RhbCBkZSBhbmltYWxlcy4gUG9yIGRlYmFqbywgKipnZW9tX2JhcioqIGN1ZW50YSBsYXMgb2JzZXJ2YWNpb25lcyB5IGFncmVnYSBsYSB0cmFuc2Zvcm1hY2nDs24gZGUgY29udGVvIHF1ZSByZXByZXNlbnRhIGVsIHRvdGFsIGRlIGFuaW1hbGVzIHBvciBjYXRlZ29yw61hIGFsIGF0cmlidXRvIGVzdMOpdGljbyBlamUgeS4NCg0KYGBge3IsIGVjaG8gPSBUUlVFfQ0KZ2dwbG90KGRhdGEgPSBhbmltYWxlc19ub19hZ3J1cGFkb3MpICsNCiAgZ2VvbV9iYXIobWFwcGluZyA9IGFlcyh4ID0gYW5pbWFsZXMpKQ0KYGBgDQoNCk3DoXMgYWRlbGFudGUgdmVyZW1vcyBvdHJhcyByZXByZXNlbnRhY2lvbmVzIGdlb21ldHJpY2FzIHF1ZSB0aWVuZW4gYXNvY2lhZGFzIHRyYW5zZm9ybWFjaW9uZXMgZXN0YWRpc3RpY2FzIHNvYnJlIG51ZXN0cm9zIGRhdG9zIHBhcmEgY3JlYXIgcmVwcmVzZW50YWNpb25lcyB2aXN1YWxlcy4gRXN0YXMgc29uIGNvbXB1dGFkYXMgcG9yIGxhcyBmdW5jaW9uZXMgZGVsIHBhcXVldGUgKipnZ3Bsb3QyKiosIHBlcm8gbG8gaW1wb3J0YW50ZSBlcyBzYWJlciBxdWUgdHJhbnNmb3JtYXIgbGEgZGF0YSBwYXJhIGNyZWFyIG1lZGlkYXMgZGUgcmVzdW1lbiBlcyB1bmEgaWRlYSBwb2Rlcm9zYSBlbiB2aXN1YWxpemFjacOzbi4NCg0KIyMgTcOhcyBkZSB1bmEgY2FwYSBncsOhZmljYQ0KDQpIYWJsYW1vcyBkZSBxdWUgbG9zIGluZ3JlZGllbnRlcyBtw61uaW1vcyBwYXJhIGNyZWFyIHVuIGdyw6FmaWNvIHNvbiBsb3Mgc2lndWllbnRlczoNCg0KMS4gRGF0b3MuDQoyLiBWaW5jdWxhciBsYXMgdmFyaWFibGVzIHF1ZSBxdWVyZW1vcyByZXByZXNlbnRhciBhIGxvcyBhc3BlY3RvcyB2aXN1YWxlcyBkZWwgZ3LDoWZpY28uDQozLiBBIGxvIG1lbm9zIHVuYSBjYXBhIHF1ZSBjb250ZW5nYSB1bmEgcmVwcmVzZW50YWNpw7NuIGdlb21ldHJpY2EgZGUgbG9zIGRhdG9zIChwdW50b3MsIGzDrW5lYXMsIGJhcnJhcywgZXRjw6l0ZXJhKQ0KDQoNCkVzdG9zIGluZ3JlZGllbnRlcyBjb25mb3JtYW4gdW4gZ3LDoWZpY28gZGUgdW5hIHNvbGEgY2FwYS4gVW5hIGRlIGxhcyBjYXJhY3RlcmlzdGljYXMgZGUgKipnZ3Bsb3QyKiogZXMgcXVlIHBlcm1pdGUgY29uc3RydWlyIGdyw6FmaWNvcyBhIHRyw6F2ZXMgZGUgY2FwYXMuIEVzdG8gc2lnbmlmaWNhIHF1ZSBwb2RlbW9zIGFncmVnYXIgZXN0b3MgcmVxdWlzaXRvcyBtw61uaW1vcyBub21icmFkb3MgYXJyaWJhIG3DoXMgZGUgdW5hIHZleiBlIGlyIGNvbXBvbmllbmRvIGdyw6FmaWNvcyBwb3IgY2FwYXMuDQoNClZveSBhIGNyZWFyIGRvcyByZXByZXNlbnRhY2lvbmVzIGdlb21ldHJpY2FzIGRpc3RpbnRhcyBwZXJvIGEgcGFydGlyIGRlIGxvcyBtaXNtb3MgZGF0b3MgbWFwZWFkb3MgZGUgaWd1YWwgZm9ybWEuDQoNCmBgYHtyLCBlY2hvID0gVFJVRX0NCmdncGxvdChkYXRhID0gbXBnKSArDQogIGdlb21fcG9pbnQobWFwcGluZyA9IGFlcyh4ID0gZGlzcGwsIHkgPSBod3ksIGNvbG91ciA9IGFzLmZhY3RvcihjeWwpKSkgKw0KICBnZ3RpdGxlKCJSZXByZXNlbnRhY2nDs24gZ2VvbcOpdHJpY2EgZGUgcHVudG9zLiIpDQpgYGANCg0KDQpgYGB7ciwgZWNobyA9IFRSVUV9DQpnZ3Bsb3QoZGF0YSA9IG1wZykgKyANCiAgZ2VvbV9zbW9vdGgobWFwcGluZyA9IGFlcyh4ID0gZGlzcGwsIHkgPSBod3ksIGNvbG91ciA9IGFzLmZhY3RvcihjeWwpKSkgKw0KICBnZ3RpdGxlKCJSZXByZXNlbnRhY2nDs24gZ2VvbcOpdHJpY2Egc21vb3RoOiBlc3RpbWEgdW4gbW9kZWxvIFxuKGZ1bmNpw7NuIG1hdMOpbWF0aWNhKSBjb24gbG9zIGRhdG9zLiIpDQpgYGANCg0Kwr9Qb2RlbW9zIGp1bnRhciBlc3RhcyBkb3MgcmVwcmVzZW50YWNpb25lcyBlbiB1biBzb2xvIGdyw6FmaWNvIGRlIGRvcyBjYXBhcz8NCg0KYGBge3IsIGVjaG8gPSBUUlVFfQ0KZ2dwbG90KGRhdGEgPSBtcGcpICsNCiAgZ2VvbV9wb2ludChtYXBwaW5nID0gYWVzKHggPSBkaXNwbCwgeSA9IGh3eSwgY29sb3VyID0gYXMuZmFjdG9yKGN5bCkpKSArDQogIGdlb21fc21vb3RoKG1hcHBpbmcgPSBhZXMoeCA9IGRpc3BsLCB5ID0gaHd5LCBjb2xvdXIgPSBhcy5mYWN0b3IoY3lsKSkpICsNCiAgZ2d0aXRsZSgiTGFzIGRvcyByZXByZXNlbnRhY2lvbmVzIGVuIHVuIG1pc21vIGdyw6FmaWNvISIpDQpgYGANCg0KQWPDoSB2ZW1vcyB1biBhc3BlY3RvIGludGVyZXNhbnRlIGRlIGxhIHNpbnRheGlzIGRlICoqZ2dwbG90MioqLiBMYSBmdW5jacOzbiBjb24gbGEgcXVlIHBhcnRpbW9zIGNhZGEgZ3LDoWZpY28gKmdncGxvdCgpKiwgdGllbmUgbGEgcHJvcGllZGFkIGRlIHF1ZSB0b2RvIGxvIGVzcGVjaWZpY2FkbyBkZW50cm8gZGUgZXN0YSBmdW5jacOzbiBzZSBhcGxpY2EgY29tbyBjb25maWd1cmFjacOzbiBiw6FzaWNhIGRlIGxhcyBjYXBhcyBkZWwgZ3LDoWZpY28uIEVuIG51ZXN0cm8gZWplbXBsbyBkZSBhcnJpYmEsIGNhZGEgY2FwYSBncsOhZmljYSwgKmdlb21fcG9pbnQqIHkgKmdlb21fc21vb3RoKiwgaGVyZWRhbiBlbCBhcmd1bWVudG8gYGRhdGEgPSBtcGdgLiBFcyBtw6FzLCBlbCBtYXBlbyBkZSBsYXMgdmFyaWFibGUgZXMgZXhhY3RhbWVudGUgZWwgbWlzbW8gZW4gYW1iYXMgY2FwYXMsIHBvciBsbyBxdWUgbG8gcG9kZW1vcyBlc3RhYmxlY2VyIHRhbWJpZW4gZWwgYXJndW1lbnRvIGBtYXBwaW5nYCBjb21vIGNvbmZpZ3VyYWNpw7NuIGluaWNpYWwgZW4gbGEgZnVuY2nDs24gKmdncGxvdCgpKiB5IGFob3JyYXJub3MgdGlwZW86DQoNCmBgYHtyfQ0KZ2dwbG90KGRhdGEgPSBtcGcsIG1hcHBpbmcgPSBhZXMoeCA9IGRpc3BsLCB5ID0gaHd5LCBjb2xvdXIgPSBhcy5mYWN0b3IoY3lsKSkpICsNCiAgZ2VvbV9wb2ludCgpICsgICMgMWVyYSBjYXBhIChlc3RvIGVzIHVuIGNvbWVudGFyaW8pDQogIGdlb21fc21vb3RoKCkgKyAgIyAyZGEgY2FwYSAobGEgY29uc29sYSBsbyBpZ25vcmEgeSBtZSBzaXJ2ZSBwYXJhIHJlc2FsdGFyIHB1bnRvcw0KICBnZ3RpdGxlKCJNaXNtbyBncsOhZmljbyBkZSBkb3MgcmVwcmVzZW50YWNpb25lcyBnZW9tw6l0cmljYXMgZGUgYXJyaWJhISIpDQpgYGANCg0KDQpTaSBxdWVyZW1vcyBncsOhZmljYXIgcmVwcmVzZW50YWNpb25lcyBnZW9tZXRyaWNhcyBkZSBsYSBtaXNtYSBkYXRhLCBubyBlcyBuZWNlc2FyaW8gZXNwZWNpZmljYXIgZWwgYXJndW1lbnRvIGBkYXRhID0gbXBnYCBlbiBjYWRhIGNhcGEgbyAqZ2VvbV9ub21icmUqLiBEZSBmb3JtYSBjb250cmFyaWEsIHBvZGVtb3Mgc29icmVlc2NyaWJpciBsYSBjb25maWd1cmFjacOzbiBiw6FzaWNhLCBlc3BlY2lmaWNhbmRvIHVuYSBmdWVudGUgZGUgZGF0b3MgZGlzdGludGFzIGVuIGFsZ3VuYSBkZSBsYXMgY2FwYXMsIGxvZ3JhbmRvIGRlIGVzdGEgZm9ybWEgY29tYmluYXIgZGF0b3MgZGVzZGUgZGlzdGludGFzIGZ1ZW50ZXMgZW4gdW4gc29sbyBncsOhZmljby4NCg0KQ3JlYXLDqSB1biBkYXRhZnJhbWUgZGUgZG9zIGZpbGFzIHkgY3VhdHJvIGNvbHVtbmFzLCBjb24gbm9tYnJlICJkaXNwbCIsICJod3kiLCAiY3lsIiB5ICJldGlxdWV0YSIgcmVwcmVzZW50YW5kbyBlbCBhdXRvIGRlIGxvcyBzw7pwZXJzb25pY29zIHkgZWwgZGUgbG9zIHBpY2FwaWVkcmFzIHBhcmEgYWdyZWdhciBhbCByZXN0byBkZSBsb3MgYXV0b3MuDQoNCmBgYHtyLCBlY2hvID0gRkFMU0V9DQp1cmxfc3VwZXIgPC0gImh0dHBzOi8vcmVwdWJsaWNhLmd0L3dwLWNvbnRlbnQvdXBsb2Fkcy8yMDE4LzA4L2F1dG8tc3VwZXJzJUMzJUIzbmljb3MuanBnIg0KYGBgDQoNCg0KDQoNCmBgYHtyLCBlY2hvPVRSVUV9DQpwaWNhX3N1cGVyc29uaWNvcyA8LSB0aWJibGUoDQogIGRpc3BsID0gYyg3LCAwKSwNCiAgaHd5ID0gYyg2MCwgNSksDQogIGN5bCA9IGMoMCwgMjApLA0KICBldGlxdWV0YSA9IGMoInN1cGVyc29uaWNvcyIsICJwaWNhcGllZHJhcyIpDQopDQoNCnBpY2Ffc3VwZXJzb25pY29zDQpgYGANCg0KQWhvcmEgY3JlYXJlbW9zIGVsIG1pc21vIGdyw6FmaWNvIGRlIHB1bnRvcywgbGUgYWdyZWdhcmVtb3MgdW5hIGNhcGEgYWRpY2lvbmFsIGRlIHB1bnRvcyBwZXJvIGNvbiBvdHJhIGZ1ZW50ZSBkZSBkYXRvcywgY29uIGxhcyBtaXNtYXMgdmFyaWFibGVzLCBwZXJvIG90cm9zIGRhdG9zLCBlbCBtb2RlbG8gZGUgYXV0b3MgZGUgbG9zIHN1cGVyc29uaWNvcyB5IGxvcyBwaWNhcGllZHJhcy4gSW5jcmVtZW50YXJlbW9zIGVsIHRhbWHDsW8gZGUgbG9zIGF1dG9zIGRlIGxvcyBzdXBlcnNvbmljb3MgeSBsb3MgcGljYXBpZWRyYXMgcGFyYSBxdWUgc2VhIG3DoXMgZsOhY2lsIGlkZW50aWZpY2FybG9zLCBlc3RvIGxvIGhhY2Vtb3MgY29uIHVuIHBhcmFtZXRybyBmaWpvIGBzaXplID0gNGAsIHBvciBmaWpvIG1lIHJlZmllcm8gcXVlIG5vIHNlIHZpbmN1bGEgYSB1bmEgdmFyaWFibGUgZGUgbGEgZGF0YSwgZGUgaGVjaG8sIGFsIGZpamFyIGxvcyBwYXJhbWV0cm9zIGEgdW5hIGNvbnN0YW50ZSwgbG8gaGFnbyBhZnVlcmEgZGUgbGEgZnVuY2nDs24gKmFlcygpKi4NCg0KYGBge3IsIGVjaG8gPSBUUlVFfQ0KZ2dwbG90KGRhdGEgPSBtcGcsIG1hcHBpbmcgPSBhZXMoeCA9IGRpc3BsLCB5ID0gaHd5LCBjb2xvdXIgPSBhcy5mYWN0b3IoY3lsKSkpICsNCiAgZ2VvbV9wb2ludCgpICsNCiAgZ2VvbV9wb2ludChkYXRhID0gcGljYV9zdXBlcnNvbmljb3MsIHNpemUgPSA0KSArDQogIGdndGl0bGUoIlVuIGdyw6FmaWNvIGRlIGRvcyBjYXBhcyBkZSBwdW50b3MgcmVwcmVzZW50YW5kb1xuZG9zIGZ1ZW50ZXMgZGlzdGludGFzIGRlIGRhdG9zISIpDQpgYGANCg0KDQpZIGFzw60gcG9kZW1vcyBpciBhZ3JlZ2FuZG8gY2FwYXosIHBvciBlamVtcGxvLCBhZ3JlZ3VlbW9zIHVuYSB0ZXJjZXJhIHBhcmEgY3JlYXIgdW5hIGV0aXF1ZXRhIHBhcmEgbG9zIHB1bnRvcyBkZWwgYXV0byBkZSBsb3MgcGljYXBpZWRyYXMgeSBsb3Mgc3VwZXJzb25pY29zLg0KDQpgYGB7ciwgZWNobyA9IFRSVUV9DQpnZ3Bsb3QoZGF0YSA9IG1wZywgbWFwcGluZyA9IGFlcyh4ID0gZGlzcGwsIHkgPSBod3ksIGNvbG91ciA9IGFzLmZhY3RvcihjeWwpKSkgKw0KICBnZW9tX3BvaW50KCkgKw0KICBnZW9tX3BvaW50KGRhdGEgPSBwaWNhX3N1cGVyc29uaWNvcywgc2l6ZSA9IDQpICsNCiAgZ2VvbV90ZXh0KG1hcHBpbmcgPSBhZXMoeCA9IGRpc3BsLCB5ID0gaHd5LCBsYWJlbCA9IGV0aXF1ZXRhKSwgZGF0YSA9IHBpY2Ffc3VwZXJzb25pY29zLA0KICAgICAgICAgICAgc2l6ZSA9IDIsIG51ZGdlX3kgPSAyKSArDQogIGdndGl0bGUoIlVuIGdyw6FmaWNvIGRlIHRyZXMgY2FwYXMgcXVlIGlsdXN0cmEgZWwgYXJ0ZVxuZGUgY29tcG9uZXIgcG9yIGNhcGFzIHZpc3VhbGVzISIpDQpgYGANCg0KDQoNCg0KDQoNCg==