Introducción
Como parte de la investigación a la librería ggplot2 de R, se
consultó el artículo “Una breve introducción a ggplot2” de José Ramón
Berrendero (Universidad Autónoma de Madrid), quien introdujo de manera
puntual y clara a esta librería. Los puntos introductorios que se dan a
conocer con este artículo, sirven para descomponer el análisis y se
expone a continuación.
La librería ggplot2 de R es un sistema organizado de visualización de
datos. Forma parte del conjunto de librerías llamado tidyverse. En este
documento se introduce su uso, principalmente a través de ejemplos.
Los elementos necesarios para representar un gráfico con ggplot2 son
los siguientes:
Un data frame que contiene los datos que se quieren
visualizar.
Los aesthetics, es decir, una lista de relaciones entre las
variables del fichero de datos y determinados aspectos del gráfico (como
por ejemplo coordenadas, formas o colores).
Los geoms, que especifican los elementos geométricos (puntos,
líneas, círculos, etc.) que se van a representar.
Normalmente estos elementos se van añadiendo de forma consecutiva en
distintas capas (layers). Para añadir una nueva capa se usa el signo +.
La estructura general del código para obtener un gráfico es esta:
ggplot(data = 'nombre del fichero de datos') + geom_nombre1(aes(aesthetics1=var1, aesthetics2=var2, ...)) + geom_nombre2(...)
El comando ggplot se usa para generar el sistema de coordenadas (por
defecto, rectangulares) y posteriormente vamos añadiendo los geoms con
sus correspondientes aesthetics. En principio los aesthetics se pueden
asignar individualmente para cada geom.
Considerando estos elementos es que iniciaremos nuestra
investigación. Se van a contemplar varios puntos sobre la librería
ggplot2, como su historia, funciones principales, descripción de las
capas que conforman un gráfico y su utilización, ejemplos de uso, y
graficaremos el histograma de cada una de las variables numéricas del
dataset Iris.
Desarrollo
La librería ggplot2 se ha consolidado como una de las librerías más
robustas y ampliamente utilizadas para la visualización de datos en el
lenguaje de programación R. Formando parte integral del ecosistema
tidyverse, esta herramienta ofrece a los usuarios la posibilidad de
crear gráficos complejos de forma intuitiva y eficiente. En esta
investigación, se abordará la historia de ggplot2, sus funciones más
destacadas, la estructura de capas que compone un gráfico y se
proporcionarán ejemplos prácticos para demostrar su utilidad en el
contexto del análisis de datos.
1. Historia de la librería
La librería ggplot2 fue desarrollada por Hadley Wickham en 2005
durante sus estudios de doctorado en Estadística. Wickham se inspiró en
el concepto de la Gramática de los Gráficos (“Grammar of Graphics”) de
Leland Wilkinson, una teoría que sistematiza la construcción de gráficos
estadísticos mediante la división en componentes fundamentales, tales
como datos, geometrías y estéticas. Esta gramática aporta un marco
coherente para la creación y descripción de visualizaciones, lo que
permitió a Wickham concebir ggplot2 no solo como una herramienta para
generar gráficos, sino también como un medio para mejorar la comprensión
y comunicación de datos a través de visualizaciones claras y efectivas
(Wickham, 2016).
Desde su lanzamiento inicial, ggplot2 ha evolucionado hasta
convertirse en un estándar para la visualización de datos en R,
apreciada por su flexibilidad, capacidad de personalización y el apoyo
de una comunidad activa. Al ser una librería de código abierto, ha
experimentado numerosas actualizaciones y expansiones, manteniendo su
relevancia tanto en ámbitos académicos como en el sector industrial.
2. Funciones principales
La filosofía de ggplot2 se fundamenta en la gramática de los
gráficos, lo que implica que los gráficos se construyen mediante la
combinación de capas sucesivas, que incluyen datos, geometrías (geoms),
estéticas (aesthetics), facetas, y otros elementos. A continuación, se
detallan algunas de las funciones más representativas de la
librería:
- ggplot(): Es la función central utilizada para iniciar un gráfico,
estableciendo el conjunto de datos y las estéticas globales, como los
ejes x e y.
ggplot(data = mtcars, aes(x = wt, y = mpg))
- geom_*: Las funciones geométricas, como geom_point(), geom_line(),
geom_bar(), etc., se emplean para añadir elementos visuales que
representan los datos. Cada función geométrica produce una forma visual
específica, como puntos, líneas o barras.
ggplot(mtcars, aes(x = wt, y = mpg)) + geom_point()
- aes(): La función aes() se utiliza para asignar variables del
conjunto de datos a propiedades visuales del gráfico, como la posición
en los ejes, el color, el tamaño, etc.
ggplot(mtcars, aes(x = wt, y = mpg, color = factor(cyl))) + geom_point()
- facet_*: Las funciones de facetado, como facet_wrap() y
facet_grid(), permiten crear subgráficos dentro de un gráfico principal,
basados en los valores de una o más variables, lo cual es útil para
comparar diferentes subconjuntos de datos.
ggplot(mtcars, aes(x = wt, y = mpg)) + geom_point() + facet_wrap(\~ cyl)
- labs() y ggtitle(): Estas funciones son utilizadas para añadir
etiquetas y títulos a los gráficos, mejorando así su claridad y
presentación.
ggplot(mtcars, aes(x = wt, y = mpg)) + geom_point() + ggtitle("Relación entre Peso y Consumo de Combustible")
- theme(): La función theme() ofrece la posibilidad de personalizar la
apariencia del gráfico, permitiendo modificar elementos como los ej es,
los textos y el fondo.
ggplot(mtcars, aes(x = wt, y = mpg)) + geom_point() + theme_minimal()
scale_*(): Ajusta las escalas y mapeos estéticos, como colores,
tamaños, y forma.
coord_*(): Modifica el sistema de coordenadas del gráfico. Útil
para cambiar la perspectiva, ajustar la proporción, o transformar
ejes.
Estas funciones permiten construir gráficos desde lo más simple hasta
lo más complejo, personalizando cada aspecto del gráfico para ajustarlo
a las necesidades de la visualización.
4. Ejemplos de uso
Esta herramienta de visualización facilita la creación de gráficos
complejos a partir de datos almacenados en marcos de datos. Dicha
herramienta, tiene una gran variedad de funciones para utilizar y
seleccionar las variables a representar y ajustar la apariencia de los
gráficos. Este paquete es efectivo en situaciones que se trabaja con
datos estructurados, como por ejemplo, cuando cada variable que se va a
utilizar tiene una columna y cada observación tiene una fila. Crear
visualizaciones precisas y eficientes será más sencillo si los datos
están bien organizados.
Una vez escritos todos los códigos necesarios en R para cargar la
información precisa del análisis que se deseaba en primera instancia, se
debe de ejecutar el codigo ggplot(data =, mapping = aes ()) + que es
útil para diferentes tipos de gráficos (). Según Nallar (n.d.) “usamos
ggplot() y data para indicar a partir de qué datos se debe crear la
gráfica. Luego, aes() (aesthetic) para seleccionar las variables a
graficar y como presentarlas, e.g. ejes x e y o características como
tamaño, forma, color, etc.”
Ejemplo 1:
ggplot(data = data, aes(x = shannon, y = evenness_camargo)) +
geom_point()
Figura 1
Nallar, E. C., PhD. (n.d.). 6 Visualización de datos usando ggplot2 |
Diseño experimental y análisis de datos. https://www.castrolab.org/teaching/data_analysis/visualizacion-de-datos-usando-ggplot2.html
Con respecto al grafico anterior se debe de saber que cualquier
parámetro que se indique en esta función cuando configura un gráfico con
ggplot(), se aplica a todas las capas del gráfico como se explicó
anteriormente. Esto incluye mapeos estéticos como las variables para los
ejes x e y que se definen con aes(). Por lo tanto, todas las capas
(geoms) que se agreguen se considerarán cualquier ajuste hecho en
ggplot(). Por otro lado, las funciones geométricas también se pueden
utilizar para establecer parámetros específicos para cada capa. Estos
parámetros solo afectan la capa en cuestión, sin alterar las
configuraciones globales de ggplot(). Es de suma importancia tener en
cuenta que el uso del signo + nos ayuda o permite agregar nuevas capas o
cambiar las configuraciones de ggplot2. Como se puede observar con el
ejemplo anterior, este símbolo se coloca al final de cada línea que
contiene una función y se utiliza para encadenar varias funciones.
Ejemplo 2:
ggplot(data = data, aes(x = geo_loc_name, y = observed)) +
geom_boxplot()
Los boxplots o gráficos de caja son útiles para poder visualizar la
distribución de los datos de acuerdo con una variable o condición de
interés
Figura 2
Nallar, E. C., PhD. (n.d.). 6 Visualización de datos usando ggplot2 |
Diseño experimental y análisis de datos. https://www.castrolab.org/teaching/data_analysis/visualizacion-de-datos-usando-ggplot2.html
Es importante saber que se puede utilizar geom_boxplot para generar
un diagrama de cajas (box plot) en ggplot2 si tiene un frame de datos
con una variable numérica. Este codigo nos ayuda específicamente a crear
gráficos de caja que muestran la distribución de un conjunto de datos
con medianas, cuartiles y valores posibles atípicos. Para hacerlo, al
construir el gráfico, se debe especificar la variable numérica dentro de
la función aes(), también conocida como mapeo estético. Un aspecto
importante de tener en cuenta a la hora de graficar geom_boxplot() de
ggplot2 es que según Coder, (2024) “alternativamente puedes establecer x
=”“. Esto eliminará los valores del eje X y hará la caja más estrecha.”
Estas y muchas alternativas existentes nos ayudan a crear el gráfico
personalizada considerando todas nuestras preferencias respecto a cómo
queremos que sea el resultado final.
Como se puede observar ggplot2 es una herramienta útil para la
visualización de datos en R que permite crear gráficos complejos y
personalizados a partir de marcos de datos. Su diseño, en el que cada
columna representa una variable y cada fila una observación, destaca su
capacidad para manejar datos estructurados de manera eficiente. La
herramienta facilita la creación de visualizaciones precisas y efectivas
al permitir la selección, representación y modificación de
variables.
5. Histograma sobre las variables numéricas del dataset
Iris
Se procede a graficar el histograma de cada una de las variables
numéricas del dataset Iris usando ggplot2.
Instalar paquetes.
Install.packages(“ggplot2”).
Install.packages(“tidyr”).
Cargar las librerías necesarias.
tidyr se emplea para convertir el dataset iris a un formato largo (o
“long format”), que es especialmente útil cuando se desea crear gráficos
faceteados o comparar múltiples variables.
pivot_longer convierte el dataset iris de un formato ancho a un
formato largo. En el código, se especifica que todas las columnas
excepto ‘Species’ deben ser transformadas en dos columnas: Variable
(contendrá el nombre de la variable original) y Value (contendrá los
valores correspondientes).
- Graficar histogramas para cada variable numérica con
personalizaciones.
ggplot(iris_long, aes(x = Value, fill = Species)) +
geom_histogram(binwidth = 0.3, color = "black", alpha = 0.7, position = "dodge") +
facet_wrap(~Variable, scales = "free_x") +
labs(
title = "Variables numéricas dataset Iris",
x = "Valor",
y = "Frecuencia"
) +
theme_bw(base_size = 14, base_family = "Times New Roman") +
theme( plot.title = element_text(color = "purple", size = 16, face = "bold", hjust = 0.5),
axis.title.x = element_text(color = "purple", size = 14, face = "bold"),
axis.title.y = element_text(color = "purple", size = 14, face = "bold"),
strip.background = element_rect(fill = "black"),
strip.text = element_text(color = "white", size = 12, face = "bold")
) +
geom_density(aes(x = Value), color = "turquoise", size = 1, alpha = 0.6)

El resultado será una serie de histogramas que muestran la
distribución de las cuatro variables numéricas en el dataset iris.
LS0tDQp0aXRsZTogIkxpYnJlcsOtYSBnZ3Bsb3QyIg0Kb3V0cHV0OiANCiAgaHRtbF9ub3RlYm9vazogZGVmYXVsdA0KICBwZGZfZG9jdW1lbnQ6IGRlZmF1bHQNCiAgaHRtbF9kb2N1bWVudDogDQogICAgZGZfcHJpbnQ6IHBhZ2VkDQogIHdvcmRfZG9jdW1lbnQ6IGRlZmF1bHQNCi0tLQ0KDQoqKkFyY2hpdm8gcHVibGljYWRvOioqIDxodHRwczovL3JwdWJzLmNvbS9zZWJhc2NqLzEyMTU4MDU+DQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQoqKlRlY25vbMOzZ2ljbyBkZSBDb3N0YSBSaWNhKioNCg0KKipDdXJzbzoqKiBJbnRyb2R1Y2Npw7NuIGEgbGEgUHJvZ3JhbWFjacOzbiBjb24gUi4NCg0KKipUcmFiYWpvIGRlIEludmVzdGlnYWNpw7NuIDI6KiogTGlicmVyw61hIGdncGxvdDINCg0KKipQcm9mZXNvcjoqKiBMdWlzIEZlcm5hbmRvIENhc3Rybw0KDQoqKkVzdHVkaWFudGVzOioqDQoNCk1hcmljcnV6IFZhcmdhcyBSYW3DrXJleg0KDQpMYXVyYSBSb2Ryw61ndWV6IFZhcmdhcw0KDQpNYXLDrWEgR2FicmllbGEgU2V2ZXJpbm8gQ2FsZGVyw7NuDQoNClNlYmFzdGnDoW4gQ2FiZXphcyBKaW3DqW5leg0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyAqKkludHJvZHVjY2nDs24qKg0KDQpDb21vIHBhcnRlIGRlIGxhIGludmVzdGlnYWNpw7NuIGEgbGEgbGlicmVyw61hIGdncGxvdDIgZGUgUiwgc2UgY29uc3VsdMOzIGVsIGFydMOtY3VsbyDigJxVbmEgYnJldmUgaW50cm9kdWNjacOzbiBhIGdncGxvdDLigJ0gZGUgSm9zw6kgUmFtw7NuIEJlcnJlbmRlcm8gKFVuaXZlcnNpZGFkIEF1dMOzbm9tYSBkZSBNYWRyaWQpLCBxdWllbiBpbnRyb2R1am8gZGUgbWFuZXJhIHB1bnR1YWwgeSBjbGFyYSBhIGVzdGEgbGlicmVyw61hLiBMb3MgcHVudG9zIGludHJvZHVjdG9yaW9zIHF1ZSBzZSBkYW4gYSBjb25vY2VyIGNvbiBlc3RlIGFydMOtY3Vsbywgc2lydmVuIHBhcmEgZGVzY29tcG9uZXIgZWwgYW7DoWxpc2lzIHkgc2UgZXhwb25lIGEgY29udGludWFjacOzbi4NCg0KTGEgbGlicmVyw61hIGdncGxvdDIgZGUgUiBlcyB1biBzaXN0ZW1hIG9yZ2FuaXphZG8gZGUgdmlzdWFsaXphY2nDs24gZGUgZGF0b3MuIEZvcm1hIHBhcnRlIGRlbCBjb25qdW50byBkZSBsaWJyZXLDrWFzIGxsYW1hZG8gdGlkeXZlcnNlLiBFbiBlc3RlIGRvY3VtZW50byBzZSBpbnRyb2R1Y2Ugc3UgdXNvLCBwcmluY2lwYWxtZW50ZSBhIHRyYXbDqXMgZGUgZWplbXBsb3MuDQoNCkxvcyBlbGVtZW50b3MgbmVjZXNhcmlvcyBwYXJhIHJlcHJlc2VudGFyIHVuIGdyw6FmaWNvIGNvbiBnZ3Bsb3QyIHNvbiBsb3Mgc2lndWllbnRlczoNCg0KLSAgIFVuIGRhdGEgZnJhbWUgcXVlIGNvbnRpZW5lIGxvcyBkYXRvcyBxdWUgc2UgcXVpZXJlbiB2aXN1YWxpemFyLg0KDQotICAgTG9zIGFlc3RoZXRpY3MsIGVzIGRlY2lyLCB1bmEgbGlzdGEgZGUgcmVsYWNpb25lcyBlbnRyZSBsYXMgdmFyaWFibGVzIGRlbCBmaWNoZXJvIGRlIGRhdG9zIHkgZGV0ZXJtaW5hZG9zIGFzcGVjdG9zIGRlbCBncsOhZmljbyAoY29tbyBwb3IgZWplbXBsbyBjb29yZGVuYWRhcywgZm9ybWFzIG8gY29sb3JlcykuDQoNCi0gICBMb3MgZ2VvbXMsIHF1ZSBlc3BlY2lmaWNhbiBsb3MgZWxlbWVudG9zIGdlb23DqXRyaWNvcyAocHVudG9zLCBsw61uZWFzLCBjw61yY3Vsb3MsIGV0Yy4pIHF1ZSBzZSB2YW4gYSByZXByZXNlbnRhci4NCg0KTm9ybWFsbWVudGUgZXN0b3MgZWxlbWVudG9zIHNlIHZhbiBhw7FhZGllbmRvIGRlIGZvcm1hIGNvbnNlY3V0aXZhIGVuIGRpc3RpbnRhcyBjYXBhcyAobGF5ZXJzKS4gUGFyYSBhw7FhZGlyIHVuYSBudWV2YSBjYXBhIHNlIHVzYSBlbCBzaWdubyArLiBMYSBlc3RydWN0dXJhIGdlbmVyYWwgZGVsIGPDs2RpZ28gcGFyYSBvYnRlbmVyIHVuIGdyw6FmaWNvIGVzIGVzdGE6DQoNCmBgYCByDQpnZ3Bsb3QoZGF0YSA9ICdub21icmUgZGVsIGZpY2hlcm8gZGUgZGF0b3MnKSArIGdlb21fbm9tYnJlMShhZXMoYWVzdGhldGljczE9dmFyMSwgYWVzdGhldGljczI9dmFyMiwgLi4uKSkgKyBnZW9tX25vbWJyZTIoLi4uKQ0KYGBgDQoNCkVsIGNvbWFuZG8gZ2dwbG90IHNlIHVzYSBwYXJhIGdlbmVyYXIgZWwgc2lzdGVtYSBkZSBjb29yZGVuYWRhcyAocG9yIGRlZmVjdG8sIHJlY3Rhbmd1bGFyZXMpIHkgcG9zdGVyaW9ybWVudGUgdmFtb3MgYcOxYWRpZW5kbyBsb3MgZ2VvbXMgY29uIHN1cyBjb3JyZXNwb25kaWVudGVzIGFlc3RoZXRpY3MuIEVuIHByaW5jaXBpbyBsb3MgYWVzdGhldGljcyBzZSBwdWVkZW4gYXNpZ25hciBpbmRpdmlkdWFsbWVudGUgcGFyYSBjYWRhIGdlb20uDQoNCkNvbnNpZGVyYW5kbyBlc3RvcyBlbGVtZW50b3MgZXMgcXVlIGluaWNpYXJlbW9zIG51ZXN0cmEgaW52ZXN0aWdhY2nDs24uIFNlIHZhbiBhIGNvbnRlbXBsYXIgdmFyaW9zIHB1bnRvcyBzb2JyZSBsYSBsaWJyZXLDrWEgZ2dwbG90MiwgY29tbyBzdSBoaXN0b3JpYSwgZnVuY2lvbmVzIHByaW5jaXBhbGVzLCBkZXNjcmlwY2nDs24gZGUgbGFzIGNhcGFzIHF1ZSBjb25mb3JtYW4gdW4gZ3LDoWZpY28geSBzdSB1dGlsaXphY2nDs24sIGVqZW1wbG9zIGRlIHVzbywgeSBncmFmaWNhcmVtb3MgZWwgaGlzdG9ncmFtYSBkZSBjYWRhIHVuYSBkZSBsYXMgdmFyaWFibGVzIG51bcOpcmljYXMgZGVsIGRhdGFzZXQgSXJpcy4NCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMgKipEZXNhcnJvbGxvKioNCg0KTGEgbGlicmVyw61hIGdncGxvdDIgc2UgaGEgY29uc29saWRhZG8gY29tbyB1bmEgZGUgbGFzIGxpYnJlcsOtYXMgbcOhcyByb2J1c3RhcyB5IGFtcGxpYW1lbnRlIHV0aWxpemFkYXMgcGFyYSBsYSB2aXN1YWxpemFjacOzbiBkZSBkYXRvcyBlbiBlbCBsZW5ndWFqZSBkZSBwcm9ncmFtYWNpw7NuIFIuIEZvcm1hbmRvIHBhcnRlIGludGVncmFsIGRlbCBlY29zaXN0ZW1hIHRpZHl2ZXJzZSwgZXN0YSBoZXJyYW1pZW50YSBvZnJlY2UgYSBsb3MgdXN1YXJpb3MgbGEgcG9zaWJpbGlkYWQgZGUgY3JlYXIgZ3LDoWZpY29zIGNvbXBsZWpvcyBkZSBmb3JtYSBpbnR1aXRpdmEgeSBlZmljaWVudGUuIEVuIGVzdGEgaW52ZXN0aWdhY2nDs24sIHNlIGFib3JkYXLDoSBsYSBoaXN0b3JpYSBkZSBnZ3Bsb3QyLCBzdXMgZnVuY2lvbmVzIG3DoXMgZGVzdGFjYWRhcywgbGEgZXN0cnVjdHVyYSBkZSBjYXBhcyBxdWUgY29tcG9uZSB1biBncsOhZmljbyB5IHNlIHByb3BvcmNpb25hcsOhbiBlamVtcGxvcyBwcsOhY3RpY29zIHBhcmEgZGVtb3N0cmFyIHN1IHV0aWxpZGFkIGVuIGVsIGNvbnRleHRvIGRlbCBhbsOhbGlzaXMgZGUgZGF0b3MuDQoNCiMjICoqMS4gSGlzdG9yaWEgZGUgbGEgbGlicmVyw61hKioNCg0KTGEgbGlicmVyw61hIGdncGxvdDIgZnVlIGRlc2Fycm9sbGFkYSBwb3IgSGFkbGV5IFdpY2toYW0gZW4gMjAwNSBkdXJhbnRlIHN1cyBlc3R1ZGlvcyBkZSBkb2N0b3JhZG8gZW4gRXN0YWTDrXN0aWNhLiBXaWNraGFtIHNlIGluc3BpcsOzIGVuIGVsIGNvbmNlcHRvIGRlIGxhIEdyYW3DoXRpY2EgZGUgbG9zIEdyw6FmaWNvcyAoIkdyYW1tYXIgb2YgR3JhcGhpY3MiKSBkZSBMZWxhbmQgV2lsa2luc29uLCB1bmEgdGVvcsOtYSBxdWUgc2lzdGVtYXRpemEgbGEgY29uc3RydWNjacOzbiBkZSBncsOhZmljb3MgZXN0YWTDrXN0aWNvcyBtZWRpYW50ZSBsYSBkaXZpc2nDs24gZW4gY29tcG9uZW50ZXMgZnVuZGFtZW50YWxlcywgdGFsZXMgY29tbyBkYXRvcywgZ2VvbWV0csOtYXMgeSBlc3TDqXRpY2FzLiBFc3RhIGdyYW3DoXRpY2EgYXBvcnRhIHVuIG1hcmNvIGNvaGVyZW50ZSBwYXJhIGxhIGNyZWFjacOzbiB5IGRlc2NyaXBjacOzbiBkZSB2aXN1YWxpemFjaW9uZXMsIGxvIHF1ZSBwZXJtaXRpw7MgYSBXaWNraGFtIGNvbmNlYmlyIGdncGxvdDIgbm8gc29sbyBjb21vIHVuYSBoZXJyYW1pZW50YSBwYXJhIGdlbmVyYXIgZ3LDoWZpY29zLCBzaW5vIHRhbWJpw6luIGNvbW8gdW4gbWVkaW8gcGFyYSBtZWpvcmFyIGxhIGNvbXByZW5zacOzbiB5IGNvbXVuaWNhY2nDs24gZGUgZGF0b3MgYSB0cmF2w6lzIGRlIHZpc3VhbGl6YWNpb25lcyBjbGFyYXMgeSBlZmVjdGl2YXMgKFdpY2toYW0sIDIwMTYpLg0KDQpEZXNkZSBzdSBsYW56YW1pZW50byBpbmljaWFsLCBnZ3Bsb3QyIGhhIGV2b2x1Y2lvbmFkbyBoYXN0YSBjb252ZXJ0aXJzZSBlbiB1biBlc3TDoW5kYXIgcGFyYSBsYSB2aXN1YWxpemFjacOzbiBkZSBkYXRvcyBlbiBSLCBhcHJlY2lhZGEgcG9yIHN1IGZsZXhpYmlsaWRhZCwgY2FwYWNpZGFkIGRlIHBlcnNvbmFsaXphY2nDs24geSBlbCBhcG95byBkZSB1bmEgY29tdW5pZGFkIGFjdGl2YS4gQWwgc2VyIHVuYSBsaWJyZXLDrWEgZGUgY8OzZGlnbyBhYmllcnRvLCBoYSBleHBlcmltZW50YWRvIG51bWVyb3NhcyBhY3R1YWxpemFjaW9uZXMgeSBleHBhbnNpb25lcywgbWFudGVuaWVuZG8gc3UgcmVsZXZhbmNpYSB0YW50byBlbiDDoW1iaXRvcyBhY2Fkw6ltaWNvcyBjb21vIGVuIGVsIHNlY3RvciBpbmR1c3RyaWFsLg0KDQojIyAqKjIuIEZ1bmNpb25lcyBwcmluY2lwYWxlcyoqDQoNCkxhIGZpbG9zb2bDrWEgZGUgZ2dwbG90MiBzZSBmdW5kYW1lbnRhIGVuIGxhIGdyYW3DoXRpY2EgZGUgbG9zIGdyw6FmaWNvcywgbG8gcXVlIGltcGxpY2EgcXVlIGxvcyBncsOhZmljb3Mgc2UgY29uc3RydXllbiBtZWRpYW50ZSBsYSBjb21iaW5hY2nDs24gZGUgY2FwYXMgc3VjZXNpdmFzLCBxdWUgaW5jbHV5ZW4gZGF0b3MsIGdlb21ldHLDrWFzIChnZW9tcyksIGVzdMOpdGljYXMgKGFlc3RoZXRpY3MpLCBmYWNldGFzLCB5IG90cm9zIGVsZW1lbnRvcy4gQSBjb250aW51YWNpw7NuLCBzZSBkZXRhbGxhbiBhbGd1bmFzIGRlIGxhcyBmdW5jaW9uZXMgbcOhcyByZXByZXNlbnRhdGl2YXMgZGUgbGEgbGlicmVyw61hOg0KDQoxLiAgZ2dwbG90KCk6IEVzIGxhIGZ1bmNpw7NuIGNlbnRyYWwgdXRpbGl6YWRhIHBhcmEgaW5pY2lhciB1biBncsOhZmljbywgZXN0YWJsZWNpZW5kbyBlbCBjb25qdW50byBkZSBkYXRvcyB5IGxhcyBlc3TDqXRpY2FzIGdsb2JhbGVzLCBjb21vIGxvcyBlamVzIHggZSB5Lg0KDQpgYGB7cn0NCmdncGxvdChkYXRhID0gbXRjYXJzLCBhZXMoeCA9IHd0LCB5ID0gbXBnKSkNCmBgYA0KDQoyLiAgZ2VvbVxfXCo6IExhcyBmdW5jaW9uZXMgZ2VvbcOpdHJpY2FzLCBjb21vIGdlb21fcG9pbnQoKSwgZ2VvbV9saW5lKCksIGdlb21fYmFyKCksIGV0Yy4sIHNlIGVtcGxlYW4gcGFyYSBhw7FhZGlyIGVsZW1lbnRvcyB2aXN1YWxlcyBxdWUgcmVwcmVzZW50YW4gbG9zIGRhdG9zLiBDYWRhIGZ1bmNpw7NuIGdlb23DqXRyaWNhIHByb2R1Y2UgdW5hIGZvcm1hIHZpc3VhbCBlc3BlY8OtZmljYSwgY29tbyBwdW50b3MsIGzDrW5lYXMgbyBiYXJyYXMuDQoNCmBgYHtyfQ0KZ2dwbG90KG10Y2FycywgYWVzKHggPSB3dCwgeSA9IG1wZykpICsgZ2VvbV9wb2ludCgpDQpgYGANCg0KMy4gIGFlcygpOiBMYSBmdW5jacOzbiBhZXMoKSBzZSB1dGlsaXphIHBhcmEgYXNpZ25hciB2YXJpYWJsZXMgZGVsIGNvbmp1bnRvIGRlIGRhdG9zIGEgcHJvcGllZGFkZXMgdmlzdWFsZXMgZGVsIGdyw6FmaWNvLCBjb21vIGxhIHBvc2ljacOzbiBlbiBsb3MgZWplcywgZWwgY29sb3IsIGVsIHRhbWHDsW8sIGV0Yy4NCg0KYGBge3J9DQpnZ3Bsb3QobXRjYXJzLCBhZXMoeCA9IHd0LCB5ID0gbXBnLCBjb2xvciA9IGZhY3RvcihjeWwpKSkgKyBnZW9tX3BvaW50KCkNCmBgYA0KDQo0LiAgZmFjZXRcX1wqOiBMYXMgZnVuY2lvbmVzIGRlIGZhY2V0YWRvLCBjb21vIGZhY2V0X3dyYXAoKSB5IGZhY2V0X2dyaWQoKSwgcGVybWl0ZW4gY3JlYXIgc3ViZ3LDoWZpY29zIGRlbnRybyBkZSB1biBncsOhZmljbyBwcmluY2lwYWwsIGJhc2Fkb3MgZW4gbG9zIHZhbG9yZXMgZGUgdW5hIG8gbcOhcyB2YXJpYWJsZXMsIGxvIGN1YWwgZXMgw7p0aWwgcGFyYSBjb21wYXJhciBkaWZlcmVudGVzIHN1YmNvbmp1bnRvcyBkZSBkYXRvcy4NCg0KYGBge3J9DQpnZ3Bsb3QobXRjYXJzLCBhZXMoeCA9IHd0LCB5ID0gbXBnKSkgKyBnZW9tX3BvaW50KCkgKyBmYWNldF93cmFwKFx+IGN5bCkNCmBgYA0KDQo1LiAgbGFicygpIHkgZ2d0aXRsZSgpOiBFc3RhcyBmdW5jaW9uZXMgc29uIHV0aWxpemFkYXMgcGFyYSBhw7FhZGlyIGV0aXF1ZXRhcyB5IHTDrXR1bG9zIGEgbG9zIGdyw6FmaWNvcywgbWVqb3JhbmRvIGFzw60gc3UgY2xhcmlkYWQgeSBwcmVzZW50YWNpw7NuLg0KDQpgYGB7cn0NCmdncGxvdChtdGNhcnMsIGFlcyh4ID0gd3QsIHkgPSBtcGcpKSArIGdlb21fcG9pbnQoKSArIGdndGl0bGUoIlJlbGFjacOzbiBlbnRyZSBQZXNvIHkgQ29uc3VtbyBkZSBDb21idXN0aWJsZSIpDQpgYGANCg0KNi4gIHRoZW1lKCk6IExhIGZ1bmNpw7NuIHRoZW1lKCkgb2ZyZWNlIGxhIHBvc2liaWxpZGFkIGRlIHBlcnNvbmFsaXphciBsYSBhcGFyaWVuY2lhIGRlbCBncsOhZmljbywgcGVybWl0aWVuZG8gbW9kaWZpY2FyIGVsZW1lbnRvcyBjb21vIGxvcyBlaiBlcywgbG9zIHRleHRvcyB5IGVsIGZvbmRvLg0KDQpgYGB7cn0NCmdncGxvdChtdGNhcnMsIGFlcyh4ID0gd3QsIHkgPSBtcGcpKSArIGdlb21fcG9pbnQoKSArIHRoZW1lX21pbmltYWwoKQ0KYGBgDQoNCjcuICBzY2FsZVxfXCooKTogQWp1c3RhIGxhcyBlc2NhbGFzIHkgbWFwZW9zIGVzdMOpdGljb3MsIGNvbW8gY29sb3JlcywgdGFtYcOxb3MsIHkgZm9ybWEuDQoNCjguICBjb29yZFxfXCooKTogTW9kaWZpY2EgZWwgc2lzdGVtYSBkZSBjb29yZGVuYWRhcyBkZWwgZ3LDoWZpY28uIMOadGlsIHBhcmEgY2FtYmlhciBsYSBwZXJzcGVjdGl2YSwgYWp1c3RhciBsYSBwcm9wb3JjacOzbiwgbyB0cmFuc2Zvcm1hciBlamVzLg0KDQpFc3RhcyBmdW5jaW9uZXMgcGVybWl0ZW4gY29uc3RydWlyIGdyw6FmaWNvcyBkZXNkZSBsbyBtw6FzIHNpbXBsZSBoYXN0YSBsbyBtw6FzIGNvbXBsZWpvLCBwZXJzb25hbGl6YW5kbyBjYWRhIGFzcGVjdG8gZGVsIGdyw6FmaWNvIHBhcmEgYWp1c3RhcmxvIGEgbGFzIG5lY2VzaWRhZGVzIGRlIGxhIHZpc3VhbGl6YWNpw7NuLg0KDQojIyAqKjMuIERlc2NyaXBjacOzbiBkZSBsYXMgY2FwYXMgcXVlIGNvbmZvcm1hbiB1biBncsOhZmljbyB5IHN1IHV0aWxpemFjacOzbioqDQoNCkxhIGNvbnN0cnVjY2nDs24gZGUgZ3LDoWZpY29zIGVuIGdncGxvdDIgc2UgYmFzYSBlbiBsYSBhZGljacOzbiBkZSBtw7psdGlwbGVzIGNhcGFzLCBjYWRhIHVuYSBkZSBsYXMgY3VhbGVzIGNvbnRyaWJ1eWUgY29uIHVuIGVsZW1lbnRvIGVzcGVjw61maWNvIGFsIGdyw6FmaWNvIGZpbmFsOg0KDQotICAgKipDYXBhIGRlIERhdG9zIChkYXRhKToqKiBFc3RhIGNhcGEgY29ycmVzcG9uZGUgYWwgY29uanVudG8gZGUgZGF0b3MgdXRpbGl6YWRvIHBhcmEgY29uc3RydWlyIGVsIGdyw6FmaWNvLiBTZSBlc3BlY2lmaWNhIGVuIGxhIGZ1bmNpw7NuIGdncGxvdCgpIG8gZGlyZWN0YW1lbnRlIGVuIGxhcyBmdW5jaW9uZXMgZ2VvbcOpdHJpY2FzLg0KDQotICAgKipDYXBhIGRlIEVzdMOpdGljYSAoYWVzKToqKiBEZXRlcm1pbmEgY8OzbW8gc2UgYXNpZ25hbiBsYXMgdmFyaWFibGVzIGEgbGFzIHByb3BpZWRhZGVzIHZpc3VhbGVzIGRlbCBncsOhZmljbywgdGFsZXMgY29tbyBsYSBwb3NpY2nDs24gKGVqZXMgeCBlIHkpLCBlbCBjb2xvciwgZWwgdGFtYcOxbywgbGEgZm9ybWEsIGV0Yy4gRXN0YSBjYXBhIHNlIGRlZmluZSBkZW50cm8gZGUgZ2dwbG90KCkgbyBzZSBwdWVkZSBhw7FhZGlyIGEgbml2ZWwgZGUgY2FwYSBnZW9tw6l0cmljYS4NCg0KLSAgICoqQ2FwYSBHZW9tw6l0cmljYSAoZ2VvbSk6KiogUmVwcmVzZW50YSBsYSBmb3JtYSB2aXN1YWwgZGUgbG9zIGRhdG9zIGVuIGVsIGdyw6FmaWNvLiBDYWRhIHRpcG8gZGUgZ3LDoWZpY28gKHB1bnRvcywgYmFycmFzLCBsw61uZWFzLCBldGMuKSBjb3JyZXNwb25kZSBhIHVuYSBmdW5jacOzbiBnZW9tw6l0cmljYSBlc3BlY8OtZmljYSAoZ2VvbV9wb2ludCgpLCBnZW9tX2JhcigpLCBnZW9tX2xpbmUoKSwgZXRjLikuDQoNCi0gICAqKkNhcGEgZGUgRXNjYWxhcyAoc2NhbGVcXyk6KiogQWp1c3RhIGPDs21vIHNlIHJlcHJlc2VudGFuIGxhcyBhc2lnbmFjaW9uZXMgZXN0w6l0aWNhcywgY29tbyBsYSBlc2NhbGEgZGUgY29sb3JlcywgdGFtYcOxb3MsIG8gZWplcy4gUGVybWl0ZSBwZXJzb25hbGl6YXIgbGEgdHJhbnNmb3JtYWNpw7NuIHkgbG9zIGzDrW1pdGVzIGRlIGxvcyBkYXRvcyB2aXN1YWxpemFkb3MNCg0KLSAgICoqQ2FwYSBFc3RhZMOtc3RpY2EgKHN0YXQpOioqIGdncGxvdDIgaW5jb3Jwb3JhIGZ1bmNpb25lcyBlc3RhZMOtc3RpY2FzIHByZWRldGVybWluYWRhcyBxdWUgcGVybWl0ZW4gY2FsY3VsYXIgeSByZXByZXNlbnRhciB2aXN1YWxtZW50ZSBlc3RhZMOtc3RpY2FzIHNvYnJlIGxvcyBkYXRvcy4gQXVucXVlIG11Y2hhcyBmdW5jaW9uZXMgZ2VvbcOpdHJpY2FzIHRpZW5lbiB1bmEgZnVuY2nDs24gZXN0YWTDrXN0aWNhIHBvciBkZWZlY3RvLCBlc3RhIHB1ZWRlIHNlciBtb2RpZmljYWRhIHNlZ8O6biBsYXMgbmVjZXNpZGFkZXMuDQoNCi0gICAqKkNhcGEgZGUgRmFjZXRhZG8gKGZhY2V0KToqKiBGYWNpbGl0YSBsYSBjcmVhY2nDs24gZGUgZ3LDoWZpY29zIGRpdmlkaWRvcyBlbiBtw7psdGlwbGVzIHBhbmVsZXMgc2Vnw7puIGxvcyB2YWxvcmVzIGRlIHVuYSBvIG3DoXMgdmFyaWFibGVzLCBsbyBxdWUgYXl1ZGEgYSBjb21wYXJhciBkaXN0aW50b3Mgc3ViY29uanVudG9zIGRlIGRhdG9zLg0KDQotICAgKipDYXBhIGRlIENvb3JkZW5hZGFzIChjb29yZCk6KiogRGVmaW5lIGVsIHNpc3RlbWEgZGUgY29vcmRlbmFkYXMgZGVsIGdyw6FmaWNvLiBFbCBzaXN0ZW1hIHByZWRldGVybWluYWRvIGVzIGVsIGNhcnRlc2lhbm8gKGNvb3JkX2NhcnRlc2lhbigpKSwgYXVucXVlIHRhbWJpw6luIHNlIHB1ZWRlbiB1c2FyIGNvb3JkZW5hZGFzIHBvbGFyZXMgKGNvb3JkX3BvbGFyKCkpLCBlbnRyZSBvdHJhcyBvcGNpb25lcy4NCg0KLSAgICoqQ2FwYSBkZSBFdGlxdWV0YXMgeSBUw610dWxvcyAobGFicygpIHkgZ2d0aXRsZSgpKToqKiBBw7FhZGUgbyBtb2RpZmljYSBsYXMgZXRpcXVldGFzIGRlbCBncsOhZmljbywgY29tbyBsb3MgdMOtdHVsb3MgZGUgbG9zIGVqZXMsIGVsIHTDrXR1bG8gcHJpbmNpcGFsIGRlbCBncsOhZmljbywgeSBsYXMgZXRpcXVldGFzIGRlIGxhcyBsZXllbmRhcy4NCg0KLSAgICoqQ2FwYSBkZSBUZW1hICh0aGVtZSk6KiogUGVybWl0ZSBsYSBwZXJzb25hbGl6YWNpw7NuIGRlIGVsZW1lbnRvcyBkZWwgZ3LDoWZpY28gcXVlIG5vIGVzdMOhbiByZWxhY2lvbmFkb3MgZGlyZWN0YW1lbnRlIGNvbiBsb3MgZGF0b3MsIGNvbW8gZm9uZG9zLCBib3JkZXMgeSB0ZXh0b3MgZGUgbG9zIGVqZXMuIEV4aXN0ZW4gdGVtYXMgcHJlZGVmaW5pZG9zIGNvbW8gdGhlbWVfbWluaW1hbCgpIG8gdGhlbWVfY2xhc3NpYygpLCBxdWUgb2ZyZWNlbiBlc3RpbG9zIGdyw6FmaWNvcyBxdWUgcHVlZGVuIGFqdXN0YXJzZSBhw7puIG3DoXMuDQoNCiMjIyAqKlJlc3VtZW4gZGVsIFByb2Nlc28qKg0KDQpBbCBjb25zdHJ1aXIgdW4gZ3LDoWZpY28gZW4gZ2dwbG90MiwgZ2VuZXJhbG1lbnRlIHNpZ3VlcyBlc3RlIHByb2Nlc286DQoNCjEuICBEZWZpbmUgbG9zIGRhdG9zIHkgbGFzIGFzaWduYWNpb25lcyBlc3TDqXRpY2FzIChnZ3Bsb3QoKSB5IGFlcygpKS4NCg0KMi4gIEHDsWFkZSBnZW9tZXRyw61hcyBwYXJhIHZpc3VhbGl6YXIgbG9zIGRhdG9zIChnZW9tXF9cKikuDQoNCjMuICBBanVzdGEgZXNjYWxhcyB5IGNvb3JkZW5hZGFzIHNpIGVzIG5lY2VzYXJpbyAoc2NhbGVcXyosIGNvb3JkXF8qKS4NCg0KNC4gIEHDsWFkZSBmYWNldGFzIHNpIGRlc2VhcyBkaXZpZGlyIGVsIGdyw6FmaWNvIGVuIG3Dumx0aXBsZXMgc3ViZ3LDoWZpY29zIChmYWNldFxfXCopLg0KDQo1LiAgUGVyc29uYWxpemEgZXRpcXVldGFzIHkgdMOtdHVsb3MgKGxhYnMoKSkuDQoNCjYuICBBanVzdGEgZWwgdGVtYSBkZWwgZ3LDoWZpY28gKHRoZW1lXF9cKikuDQoNCkVzdGFzIGNhcGFzIHB1ZWRlbiBzZXIgY29tYmluYWRhcyBkZSBkaWZlcmVudGVzIG1hbmVyYXMgcGFyYSBjcmVhciBncsOhZmljb3MgYWx0YW1lbnRlIHBlcnNvbmFsaXphZG9zIHkgY29tcGxlam9zIGVuIGdncGxvdDIuDQoNCiMjICoqNC4gRWplbXBsb3MgZGUgdXNvKioNCg0KRXN0YSBoZXJyYW1pZW50YSBkZSB2aXN1YWxpemFjacOzbiBmYWNpbGl0YSBsYSBjcmVhY2nDs24gZGUgZ3LDoWZpY29zIGNvbXBsZWpvcyBhIHBhcnRpciBkZSBkYXRvcyBhbG1hY2VuYWRvcyBlbiBtYXJjb3MgZGUgZGF0b3MuIERpY2hhIGhlcnJhbWllbnRhLCB0aWVuZSB1bmEgZ3JhbiB2YXJpZWRhZCBkZSBmdW5jaW9uZXMgcGFyYSB1dGlsaXphciB5IHNlbGVjY2lvbmFyIGxhcyB2YXJpYWJsZXMgYSByZXByZXNlbnRhciB5IGFqdXN0YXIgbGEgYXBhcmllbmNpYSBkZSBsb3MgZ3LDoWZpY29zLiBFc3RlIHBhcXVldGUgZXMgZWZlY3Rpdm8gZW4gc2l0dWFjaW9uZXMgcXVlIHNlIHRyYWJhamEgY29uIGRhdG9zIGVzdHJ1Y3R1cmFkb3MsIGNvbW8gcG9yIGVqZW1wbG8sIGN1YW5kbyBjYWRhIHZhcmlhYmxlIHF1ZSBzZSB2YSBhIHV0aWxpemFyIHRpZW5lIHVuYSBjb2x1bW5hIHkgY2FkYSBvYnNlcnZhY2nDs24gdGllbmUgdW5hIGZpbGEuIENyZWFyIHZpc3VhbGl6YWNpb25lcyBwcmVjaXNhcyB5IGVmaWNpZW50ZXMgc2Vyw6EgbcOhcyBzZW5jaWxsbyBzaSBsb3MgZGF0b3MgZXN0w6FuIGJpZW4gb3JnYW5pemFkb3MuDQoNClVuYSB2ZXogZXNjcml0b3MgdG9kb3MgbG9zIGPDs2RpZ29zIG5lY2VzYXJpb3MgZW4gUiBwYXJhIGNhcmdhciBsYSBpbmZvcm1hY2nDs24gcHJlY2lzYSBkZWwgYW7DoWxpc2lzIHF1ZSBzZSBkZXNlYWJhIGVuIHByaW1lcmEgaW5zdGFuY2lhLCBzZSBkZWJlIGRlIGVqZWN1dGFyIGVsIGNvZGlnbyBnZ3Bsb3QoZGF0YSA9LCBtYXBwaW5nID0gYWVzICgpKSArIHF1ZSBlcyDDunRpbCBwYXJhIGRpZmVyZW50ZXMgdGlwb3MgZGUgZ3LDoWZpY29zICgpLiBTZWfDum4gTmFsbGFyIChuLmQuKSDigJx1c2Ftb3MgZ2dwbG90KCkgeSBkYXRhIHBhcmEgaW5kaWNhciBhIHBhcnRpciBkZSBxdcOpIGRhdG9zIHNlIGRlYmUgY3JlYXIgbGEgZ3LDoWZpY2EuIEx1ZWdvLCBhZXMoKSAoYWVzdGhldGljKSBwYXJhIHNlbGVjY2lvbmFyIGxhcyB2YXJpYWJsZXMgYSBncmFmaWNhciB5IGNvbW8gcHJlc2VudGFybGFzLCBlLmcuIGVqZXMgeCBlIHkgbyBjYXJhY3RlcsOtc3RpY2FzIGNvbW8gdGFtYcOxbywgZm9ybWEsIGNvbG9yLCBldGMu4oCdDQoNCioqRWplbXBsbyAxOioqDQoNCipnZ3Bsb3QoZGF0YSA9IGRhdGEsIGFlcyh4ID0gc2hhbm5vbiwgeSA9IGV2ZW5uZXNzX2NhbWFyZ28pKSArIGdlb21fcG9pbnQoKSoNCg0KIVsqKkZpZ3VyYSAxKipdKEZpZ3VyYSUyMDEucG5nKQ0KDQpOYWxsYXIsIEUuIEMuLCBQaEQuIChuLmQuKS4gNiBWaXN1YWxpemFjacOzbiBkZSBkYXRvcyB1c2FuZG8gZ2dwbG90MiBcfCBEaXNlw7FvIGV4cGVyaW1lbnRhbCB5IGFuw6FsaXNpcyBkZSBkYXRvcy4gPGh0dHBzOi8vd3d3LmNhc3Ryb2xhYi5vcmcvdGVhY2hpbmcvZGF0YV9hbmFseXNpcy92aXN1YWxpemFjaW9uLWRlLWRhdG9zLXVzYW5kby1nZ3Bsb3QyLmh0bWw+DQoNCkNvbiByZXNwZWN0byBhbCBncmFmaWNvIGFudGVyaW9yIHNlIGRlYmUgZGUgc2FiZXIgcXVlIGN1YWxxdWllciBwYXLDoW1ldHJvIHF1ZSBzZSBpbmRpcXVlIGVuIGVzdGEgZnVuY2nDs24gY3VhbmRvIGNvbmZpZ3VyYSB1biBncsOhZmljbyBjb24gZ2dwbG90KCksIHNlIGFwbGljYSBhIHRvZGFzIGxhcyBjYXBhcyBkZWwgZ3LDoWZpY28gY29tbyBzZSBleHBsaWPDsyBhbnRlcmlvcm1lbnRlLiBFc3RvIGluY2x1eWUgbWFwZW9zIGVzdMOpdGljb3MgY29tbyBsYXMgdmFyaWFibGVzIHBhcmEgbG9zIGVqZXMgeCBlIHkgcXVlIHNlIGRlZmluZW4gY29uIGFlcygpLiBQb3IgbG8gdGFudG8sIHRvZGFzIGxhcyBjYXBhcyAoZ2VvbXMpIHF1ZSBzZSBhZ3JlZ3VlbiBzZSBjb25zaWRlcmFyw6FuIGN1YWxxdWllciBhanVzdGUgaGVjaG8gZW4gZ2dwbG90KCkuIFBvciBvdHJvIGxhZG8sIGxhcyBmdW5jaW9uZXMgZ2VvbcOpdHJpY2FzIHRhbWJpw6luIHNlIHB1ZWRlbiB1dGlsaXphciBwYXJhIGVzdGFibGVjZXIgcGFyw6FtZXRyb3MgZXNwZWPDrWZpY29zIHBhcmEgY2FkYSBjYXBhLiBFc3RvcyBwYXLDoW1ldHJvcyBzb2xvIGFmZWN0YW4gbGEgY2FwYSBlbiBjdWVzdGnDs24sIHNpbiBhbHRlcmFyIGxhcyBjb25maWd1cmFjaW9uZXMgZ2xvYmFsZXMgZGUgZ2dwbG90KCkuIEVzIGRlIHN1bWEgaW1wb3J0YW5jaWEgdGVuZXIgZW4gY3VlbnRhIHF1ZSBlbCB1c28gZGVsIHNpZ25vICsgbm9zIGF5dWRhIG8gcGVybWl0ZSBhZ3JlZ2FyIG51ZXZhcyBjYXBhcyBvIGNhbWJpYXIgbGFzIGNvbmZpZ3VyYWNpb25lcyBkZSBnZ3Bsb3QyLiBDb21vIHNlIHB1ZWRlIG9ic2VydmFyIGNvbiBlbCBlamVtcGxvIGFudGVyaW9yLCBlc3RlIHPDrW1ib2xvIHNlIGNvbG9jYSBhbCBmaW5hbCBkZSBjYWRhIGzDrW5lYSBxdWUgY29udGllbmUgdW5hIGZ1bmNpw7NuIHkgc2UgdXRpbGl6YSBwYXJhIGVuY2FkZW5hciB2YXJpYXMgZnVuY2lvbmVzLg0KDQoqKkVqZW1wbG8gMjoqKg0KDQoqZ2dwbG90KGRhdGEgPSBkYXRhLCBhZXMoeCA9IGdlb19sb2NfbmFtZSwgeSA9IG9ic2VydmVkKSkgK1wNCmdlb21fYm94cGxvdCgpKg0KDQpMb3MgYm94cGxvdHMgbyBncsOhZmljb3MgZGUgY2FqYSBzb24gw7p0aWxlcyBwYXJhIHBvZGVyIHZpc3VhbGl6YXIgbGEgZGlzdHJpYnVjacOzbiBkZSBsb3MgZGF0b3MgZGUgYWN1ZXJkbyBjb24gdW5hIHZhcmlhYmxlIG8gY29uZGljacOzbiBkZSBpbnRlcsOpcw0KDQohWyoqRmlndXJhIDIqKl0oRmlndXJhJTIwMi5wbmcpDQoNCk5hbGxhciwgRS4gQy4sIFBoRC4gKG4uZC4pLiA2IFZpc3VhbGl6YWNpw7NuIGRlIGRhdG9zIHVzYW5kbyBnZ3Bsb3QyIFx8IERpc2XDsW8gZXhwZXJpbWVudGFsIHkgYW7DoWxpc2lzIGRlIGRhdG9zLiA8aHR0cHM6Ly93d3cuY2FzdHJvbGFiLm9yZy90ZWFjaGluZy9kYXRhX2FuYWx5c2lzL3Zpc3VhbGl6YWNpb24tZGUtZGF0b3MtdXNhbmRvLWdncGxvdDIuaHRtbD4NCg0KRXMgaW1wb3J0YW50ZSBzYWJlciBxdWUgc2UgcHVlZGUgdXRpbGl6YXIgZ2VvbV9ib3hwbG90IHBhcmEgZ2VuZXJhciB1biBkaWFncmFtYSBkZSBjYWphcyAoYm94IHBsb3QpIGVuIGdncGxvdDIgc2kgdGllbmUgdW4gZnJhbWUgZGUgZGF0b3MgY29uIHVuYSB2YXJpYWJsZSBudW3DqXJpY2EuIEVzdGUgY29kaWdvIG5vcyBheXVkYSBlc3BlY8OtZmljYW1lbnRlIGEgY3JlYXIgZ3LDoWZpY29zIGRlIGNhamEgcXVlIG11ZXN0cmFuIGxhIGRpc3RyaWJ1Y2nDs24gZGUgdW4gY29uanVudG8gZGUgZGF0b3MgY29uIG1lZGlhbmFzLCBjdWFydGlsZXMgeSB2YWxvcmVzIHBvc2libGVzIGF0w61waWNvcy4gUGFyYSBoYWNlcmxvLCBhbCBjb25zdHJ1aXIgZWwgZ3LDoWZpY28sIHNlIGRlYmUgZXNwZWNpZmljYXIgbGEgdmFyaWFibGUgbnVtw6lyaWNhIGRlbnRybyBkZSBsYSBmdW5jacOzbiBhZXMoKSwgdGFtYmnDqW4gY29ub2NpZGEgY29tbyBtYXBlbyBlc3TDqXRpY28uIFVuIGFzcGVjdG8gaW1wb3J0YW50ZSBkZSB0ZW5lciBlbiBjdWVudGEgYSBsYSBob3JhIGRlIGdyYWZpY2FyIGdlb21fYm94cGxvdCgpIGRlIGdncGxvdDIgZXMgcXVlIHNlZ8O6biBDb2RlciwgKDIwMjQpIOKAnGFsdGVybmF0aXZhbWVudGUgcHVlZGVzIGVzdGFibGVjZXIgeCA9ICIiLiBFc3RvIGVsaW1pbmFyw6EgbG9zIHZhbG9yZXMgZGVsIGVqZSBYIHkgaGFyw6EgbGEgY2FqYSBtw6FzIGVzdHJlY2hhLuKAnSBFc3RhcyB5IG11Y2hhcyBhbHRlcm5hdGl2YXMgZXhpc3RlbnRlcyBub3MgYXl1ZGFuIGEgY3JlYXIgZWwgZ3LDoWZpY28gcGVyc29uYWxpemFkYSBjb25zaWRlcmFuZG8gdG9kYXMgbnVlc3RyYXMgcHJlZmVyZW5jaWFzIHJlc3BlY3RvIGEgY8OzbW8gcXVlcmVtb3MgcXVlIHNlYSBlbCByZXN1bHRhZG8gZmluYWwuDQoNCkNvbW8gc2UgcHVlZGUgb2JzZXJ2YXIgZ2dwbG90MiBlcyB1bmEgaGVycmFtaWVudGEgw7p0aWwgcGFyYSBsYSB2aXN1YWxpemFjacOzbiBkZSBkYXRvcyBlbiBSIHF1ZSBwZXJtaXRlIGNyZWFyIGdyw6FmaWNvcyBjb21wbGVqb3MgeSBwZXJzb25hbGl6YWRvcyBhIHBhcnRpciBkZSBtYXJjb3MgZGUgZGF0b3MuIFN1IGRpc2XDsW8sIGVuIGVsIHF1ZSBjYWRhIGNvbHVtbmEgcmVwcmVzZW50YSB1bmEgdmFyaWFibGUgeSBjYWRhIGZpbGEgdW5hIG9ic2VydmFjacOzbiwgZGVzdGFjYSBzdSBjYXBhY2lkYWQgcGFyYSBtYW5lamFyIGRhdG9zIGVzdHJ1Y3R1cmFkb3MgZGUgbWFuZXJhIGVmaWNpZW50ZS4gTGEgaGVycmFtaWVudGEgZmFjaWxpdGEgbGEgY3JlYWNpw7NuIGRlIHZpc3VhbGl6YWNpb25lcyBwcmVjaXNhcyB5IGVmZWN0aXZhcyBhbCBwZXJtaXRpciBsYSBzZWxlY2Npw7NuLCByZXByZXNlbnRhY2nDs24geSBtb2RpZmljYWNpw7NuIGRlIHZhcmlhYmxlcy4NCg0KIyMgKio1LiBIaXN0b2dyYW1hIHNvYnJlIGxhcyB2YXJpYWJsZXMgbnVtw6lyaWNhcyBkZWwgZGF0YXNldCBJcmlzKioNCg0KU2UgcHJvY2VkZSBhIGdyYWZpY2FyIGVsIGhpc3RvZ3JhbWEgZGUgY2FkYSB1bmEgZGUgbGFzIHZhcmlhYmxlcyBudW3DqXJpY2FzIGRlbCBkYXRhc2V0IElyaXMgdXNhbmRvIGdncGxvdDIuDQoNCjEuICBJbnN0YWxhciBwYXF1ZXRlcy4NCg0KMi4gIEluc3RhbGwucGFja2FnZXMoImdncGxvdDIiKS4NCg0KMy4gIEluc3RhbGwucGFja2FnZXMoInRpZHlyIikuDQoNCjQuICBDYXJnYXIgbGFzIGxpYnJlcsOtYXMgbmVjZXNhcmlhcy4NCg0KYGBge3J9DQpsaWJyYXJ5KGdncGxvdDIpIA0KDQpsaWJyYXJ5KHRpZHlyKQ0KYGBgDQoNCnRpZHlyIHNlIGVtcGxlYSBwYXJhIGNvbnZlcnRpciBlbCBkYXRhc2V0IGlyaXMgYSB1biBmb3JtYXRvIGxhcmdvIChvICJsb25nIGZvcm1hdCIpLCBxdWUgZXMgZXNwZWNpYWxtZW50ZSDDunRpbCBjdWFuZG8gc2UgZGVzZWEgY3JlYXIgZ3LDoWZpY29zIGZhY2V0ZWFkb3MgbyBjb21wYXJhciBtw7psdGlwbGVzIHZhcmlhYmxlcy4NCg0KYGBge3J9DQppcmlzX2xvbmcgPC0gcGl2b3RfbG9uZ2VyKGlyaXMsIGNvbHMgPSAtU3BlY2llcywgbmFtZXNfdG8gPSAiVmFyaWFibGUiLCB2YWx1ZXNfdG8gPSAiVmFsdWUiKSANCmBgYA0KDQpwaXZvdF9sb25nZXIgY29udmllcnRlIGVsIGRhdGFzZXQgaXJpcyBkZSB1biBmb3JtYXRvIGFuY2hvIGEgdW4gZm9ybWF0byBsYXJnby4gRW4gZWwgY8OzZGlnbywgc2UgZXNwZWNpZmljYSBxdWUgdG9kYXMgbGFzIGNvbHVtbmFzIGV4Y2VwdG8gJ1NwZWNpZXMnIGRlYmVuIHNlciB0cmFuc2Zvcm1hZGFzIGVuIGRvcyBjb2x1bW5hczogVmFyaWFibGUgKGNvbnRlbmRyw6EgZWwgbm9tYnJlIGRlIGxhIHZhcmlhYmxlIG9yaWdpbmFsKSB5IFZhbHVlIChjb250ZW5kcsOhIGxvcyB2YWxvcmVzIGNvcnJlc3BvbmRpZW50ZXMpLg0KDQo1LiAgR3JhZmljYXIgaGlzdG9ncmFtYXMgcGFyYSBjYWRhIHZhcmlhYmxlIG51bcOpcmljYSBjb24gcGVyc29uYWxpemFjaW9uZXMuDQoNCmBgYHtyIEMzLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBwYWdlZC5wcmludD1UUlVFfQ0KZ2dwbG90KGlyaXNfbG9uZywgYWVzKHggPSBWYWx1ZSwgZmlsbCA9IFNwZWNpZXMpKSArICANCiAgZ2VvbV9oaXN0b2dyYW0oYmlud2lkdGggPSAwLjMsIGNvbG9yID0gImJsYWNrIiwgYWxwaGEgPSAwLjcsIHBvc2l0aW9uID0gImRvZGdlIikgKyANCiAgZmFjZXRfd3JhcCh+VmFyaWFibGUsIHNjYWxlcyA9ICJmcmVlX3giKSArIA0KICBsYWJzKA0KICAgIHRpdGxlID0gIlZhcmlhYmxlcyBudW3DqXJpY2FzIGRhdGFzZXQgSXJpcyIsIA0KICAgIHggPSAiVmFsb3IiLCANCiAgICB5ID0gIkZyZWN1ZW5jaWEiDQogICkgKyANCiAgdGhlbWVfYncoYmFzZV9zaXplID0gMTQsIGJhc2VfZmFtaWx5ID0gIlRpbWVzIE5ldyBSb21hbiIpICsgDQogIHRoZW1lKCBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGNvbG9yID0gInB1cnBsZSIsIHNpemUgPSAxNiwgZmFjZSA9ICJib2xkIiwgaGp1c3QgPSAwLjUpLA0KICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfdGV4dChjb2xvciA9ICJwdXJwbGUiLCBzaXplID0gMTQsIGZhY2UgPSAiYm9sZCIpLA0KICAgIGF4aXMudGl0bGUueSA9IGVsZW1lbnRfdGV4dChjb2xvciA9ICJwdXJwbGUiLCBzaXplID0gMTQsIGZhY2UgPSAiYm9sZCIpLA0KICAgIHN0cmlwLmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoZmlsbCA9ICJibGFjayIpLA0KICAgIHN0cmlwLnRleHQgPSBlbGVtZW50X3RleHQoY29sb3IgPSAid2hpdGUiLCBzaXplID0gMTIsIGZhY2UgPSAiYm9sZCIpDQogICkgKyANCiAgZ2VvbV9kZW5zaXR5KGFlcyh4ID0gVmFsdWUpLCBjb2xvciA9ICJ0dXJxdW9pc2UiLCBzaXplID0gMSwgYWxwaGEgPSAwLjYpDQoNCmBgYA0KDQohW10oSGlzdG9ncmFtYS5qZmlmKQ0KDQpFbCByZXN1bHRhZG8gc2Vyw6EgdW5hIHNlcmllIGRlIGhpc3RvZ3JhbWFzIHF1ZSBtdWVzdHJhbiBsYSBkaXN0cmlidWNpw7NuIGRlIGxhcyBjdWF0cm8gdmFyaWFibGVzIG51bcOpcmljYXMgZW4gZWwgZGF0YXNldCBpcmlzLg0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyAqKkNvbmNsdXNpw7NuKioNCg0KRW4gY29uY2x1c2nDs24sIGdncGxvdDIgZXMgdW5hIGhlcnJhbWllbnRhIGZ1bmRhbWVudGFsIGVuIGVsIGFuw6FsaXNpcyB5IHZpc3VhbGl6YWNpw7NuIGRlIGRhdG9zIGVuIFIsIGRlYmlkbyBhIHN1IGVzdHJ1Y3R1cmEgYmFzYWRhIGVuIGNhcGFzIHkgc3UgZ3JhbiB2ZXJzYXRpbGlkYWQuIERlc2RlIHN1IGNyZWFjacOzbiwgaGEgc2lkbyBvYmpldG8gZGUgY29udGludWFzIG1lam9yYXMsIGxvIHF1ZSBoYSBwZXJtaXRpZG8gcXVlIHNpZ2Egc2llbmRvIHJlbGV2YW50ZSB0YW50byBwYXJhIHVzdWFyaW9zIG5vdmF0b3MgY29tbyBwYXJhIGV4cGVydG9zIGVuIGNpZW5jaWEgZGUgZGF0b3MuIEVsIGRvbWluaW8gZGUgc3UgZ3JhbcOhdGljYSBkZSBncsOhZmljb3MgYnJpbmRhIGEgbG9zIGFuYWxpc3RhcyB5IGNpZW50w61maWNvcyBkZSBkYXRvcyB1bmEgcG9kZXJvc2EgaGVycmFtaWVudGEgcGFyYSBleHBsb3JhciwgdmlzdWFsaXphciB5IGNvbXVuaWNhciBkYXRvcyBkZSBtYW5lcmEgZWZlY3RpdmEuDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojICoqQmlibGlvZ3JhZsOtYSoqDQoNCkJlcnJlbmRlcm8sIEouIFIuIChuLmQuKS4gVW5hIGJyZXZlIGludHJvZHVjY2nDs24gYSBnZ3Bsb3QyLiBVbml2ZXJzaWRhZCBBdXTDs25vbWEgZGUgTWFkcmlkLiA8aHR0cHM6Ly92ZXJzby5tYXQudWFtLmVzL35qb3Nlci5iZXJyZW5kZXJvL1IvaW50cm9nZ3Bsb3QyLmh0bWw+DQoNCldpY2toYW0sIEguICgyMDE2KS4gZ2dwbG90MjogRWxlZ2FudCBHcmFwaGljcyBmb3IgRGF0YSBBbmFseXNpcy4gU3ByaW5nZXItVmVybGFnIE5ldyBZb3JrLg0KDQpOYWxsYXIsIEUuIEMuLCBQaEQuIChuLmQuKS4gNiBWaXN1YWxpemFjacOzbiBkZSBkYXRvcyB1c2FuZG8gZ2dwbG90MiBcfCBEaXNlw7FvIGV4cGVyaW1lbnRhbCB5IGFuw6FsaXNpcyBkZSBkYXRvcy4gPGh0dHBzOi8vd3d3LmNhc3Ryb2xhYi5vcmcvdGVhY2hpbmcvZGF0YV9hbmFseXNpcy92aXN1YWxpemFjaW9uLWRlLWRhdG9zLXVzYW5kby1nZ3Bsb3QyLmh0bWw+DQoNCkNvZGVyLCBSLiAoMjAyNCwgSmFudWFyeSA0KS4gQm94IHBsb3QgZW4gZ2dwbG90Mi4gUiBDSEFSVFMgXHwgVW5hIENvbGVjY2nDs24gRGUgR3LDoWZpY29zIEhlY2hvcyBDb24gRWwgTGVuZ3VhamUgRGUgUHJvZ3JhbWFjacOzbiBSLiA8aHR0cHM6Ly9yLWNoYXJ0cy5jb20vZXMvZGlzdHJpYnVjaW9uL2JveC1wbG90LWdncGxvdDIvPg0K