Presentación

En el presente proyecto se analizarán algunos comentarios hechos a través de la red social Twitter por los usuarios de la banca en México, específicamente, los clientes de Citibanamex, en el periodo comprendido del 21 al 30 de diciembre de 2021. Lo anteior, debido a que dicha red social solo permite que se extraigan datos de los últimos 6-9 días. Se extrajeron tweets de la cuenta @ContactoCitibmx, que es uno de los canales de atención a clientes que tiene dicha entidad financiera para interactuar con sus clientes.

Este análisis es de minería de texto, por lo que, la mayoría del trabajo mostrará técnicas estadísticas descriptivas y exploratorias.

Los datos se pueden extraer directamente desde twitter, sin embargo, por comodidad y para seguir el presete trabajo, también, se encuentran en mi repositorio de github junto con el código de esta publicación.


Contenido

  • Presentación
  • Contenido
  • Set up
  • Extracción de la información
  • Tokenizacion y limpieza de texto
  • Análisis exploratorio
    • Distribución temporal de los tweets
    • Frecuencia de palabras
    • Nube de palabras
  • Relación entre palabras
  • Análisis de sentimientos
  • Conclusiones


Set up

Iniciamos configurando las opciones generales que vamos a requerir para el desarrollo de este proyecto.

knitr::opts_chunk$set(echo = TRUE,
                      warning = FALSE,
                      message = FALSE,
                      fig.align = "center"
                      )

paquetes <- c("rtweet", "tidyverse", "lubridate", "wordcloud", "RColorBrewer", "tidytext", "igraph", "ggraph", "knitr", "DT", "kableExtra", "tm", "reshape2")

instalados <- paquetes %in% installed.packages()

if(sum(instalados == FALSE) > 0) {
  install.packages(paquetes[!instalados])
}

lapply(paquetes, require, character.only = TRUE)

 

Extracción de de información

La información de los tweets se pueden descargar directamente con apoyo de la función search_tweets contenida en el paquete rtweet:

# se extraen los tweets
ds <- Map(
  "search_tweets",
  q = "@Citibanamex OR Banamex OR Citibanamex OR banamex OR @banamex",
  n = 5000,
  parse = TRUE,
  include_rts = FALSE,
  lang = "es"
)

# unir los datos
ds <- do_call_rbind(ds)

# se seleccionan columnas
ds <- ds %>% 
  select("screen_name", 
         "created_at", 
         "status_id", 
         "text",
         "user_id",
         "verified",
         "location")

# se guarda el archivo
write_csv(ds, "tweets.csv")
# se cargan los datos previamente guardados
tweets <- read_csv("tweets.csv")

# se quitan las respuestas de la institución, ya que nos interesan solo los comentarios
# de los clientes
tweets <- tweets %>% 
  filter(screen_name !="Citibanamex" & screen_name !="ContactoCitibmx") %>% 
  select(c(created_at, text))

Se muestra un fragmento de los tweets extraidos:

DT::datatable(head(tweets, 20),
              rownames = FALSE,
              options = list(
                pageLength = 5,
                scrollX = TRUE))

 

Tokenizacion y limpieza de texto

En este proyecto, el proceso de limpieza consiste en eliminar del texto todas aquellas palabras, signos, números, abreviaturas, etc., que no aportan información importante.

Además, para poder seguir adelante es necesario tener la información de los comentarios (columna text), en palabras individuales para poder obtener, entre otras cosas sus frecuencias para saber cuáles son las más mencionadas por los usuarios.

source("limpieza.R")

tweets.tkns <- tweets %>% 
  mutate(texto_tokenizado = map(.x = text,
                                .f = limpiar_tokenizar))

tweets.tkns %>% 
  slice(1) %>% 
  select(texto_tokenizado) %>%
  pull()
## [[1]]
##  [1] "citibanamex" "que"         "les"         "pasa"        "estoy"      
##  [6] "muy"         "molesta"     "tienen"      "acaso"       "ejecutivos" 
## [11] "para"        "atender"     "audio"       "matico"      "llevo"      
## [16] "semana"      "tratando"    "que"         "ser"         "humano"     
## [21] "atienda"     "voy"         "sucursal"    "dice"        "ejecutivo"  
## [26] "que"         "duda"        "solo"        "puede"       "ser"        
## [31] "resuelta"    "audiomatico" "dice"        "que"         "hay"        
## [36] "agentes"
tweets.tkns <- tweets.tkns %>% 
  select(-text) %>%
  unnest()

tweets.tkns <- tweets.tkns %>% 
  rename(token = texto_tokenizado)

tweets.tkns %>% 
  head(10) %>% 
  kbl( 
      align = "c",
      caption = "Tokenización",
      ) %>%
  kable_paper("hover", full_width = F)
Tokenización
created_at token
2021-12-30 23:41:04 citibanamex
2021-12-30 23:41:04 que
2021-12-30 23:41:04 les
2021-12-30 23:41:04 pasa
2021-12-30 23:41:04 estoy
2021-12-30 23:41:04 muy
2021-12-30 23:41:04 molesta
2021-12-30 23:41:04 tienen
2021-12-30 23:41:04 acaso
2021-12-30 23:41:04 ejecutivos

Como los comentarios están en el idioma Español, se utilizará el paquete tm para trabajar con las llamadas stopwords, que son palabras que no aportan valor al análisis como los artículos, conectores, etc.

custom_stop_words <- bind_rows(
                               tibble(word = tm::stopwords("spanish"),
                                          lexicon = "custom"))
tweets.tkns <- tweets.tkns %>% 
  rename(word = token,
         fecha = created_at)

tweets.tkns <- anti_join(x = tweets.tkns,
                    y = custom_stop_words,
                    by = "word")

tweets.tkns %>% 
  head(10) %>% 
  kbl( 
      align = "c",
      caption = "Tokenización",
      ) %>%
  kable_paper("hover", full_width = F)
Tokenización
fecha word
2021-12-30 23:41:04 citibanamex
2021-12-30 23:41:04 pasa
2021-12-30 23:41:04 molesta
2021-12-30 23:41:04 acaso
2021-12-30 23:41:04 ejecutivos
2021-12-30 23:41:04 atender
2021-12-30 23:41:04 audio
2021-12-30 23:41:04 matico
2021-12-30 23:41:04 llevo
2021-12-30 23:41:04 semana

El resultado de la limpieza y tokenización es un marco de datos de 32,211 filas y 2 columnas:

dim(tweets.tkns)
## [1] 32211     2

 

Análisis exploratorio

A continuación, se crearán algunas gráficas que faciliten el análisis exploratorio tanto del comportamiento de los usuarios como del contenido de sus comentarios. Lo anterior, nos dará una primer idea de lo que transmitieron en sus tweets.

Distribución temporal de los tweets

Veamos la distribución de los tweets para ver su evolución en el tiempo durante los 9 días de actividad con la que contamos

tweets.tkns %>% 
  ggplot(aes(x = as.Date(fecha))) +
  geom_histogram(position = "identity", 
                 bins = 20, 
                 show.legend = FALSE, 
                 fill = "steelblue",
                 color = "firebrick") +
  scale_y_continuous(labels = scales::comma) +
  scale_x_date(date_labels = "%d-%m-%Y", date_breaks = "1 day") +
  theme(axis.text.x = element_text(angle = 90, size = 8)) +
  labs(title = "Distribución Tweets por Día", x = "fecha de publicación", y = "número de tweets") 

En esta primer gráfica se aprecia que los últimos cuatro días del año 2021 hubo una mayor actividad de los usuarios, siendo el día 28 el de mayor cantidad de mensajes.

tweets.tkns %>% 
  ggplot(aes(x = as.POSIXct(fecha))) +
  geom_histogram(position = "identity", 
                 bins = 20, 
                 show.legend = FALSE, 
                 fill = "steelblue",
                 color = "firebrick") +
  scale_y_continuous(labels = scales::comma) +
  scale_x_datetime(date_labels = "%d-%m-%Y %H", date_breaks = "6 hours") +
   theme(axis.text.x = element_text(angle = 90, size = 8)) +
  labs(title = "Distribución Tweets: cada seis horas",
       x = "fecha de publicación",
       y = "número de tweets") 

En esta segunda gráfica se muestra la actividad de los usuarios cada seis horas. En ella se observa que en la segunda parte del rango de días analizado la actividad es menor solo que en los últimos 4 días después de las 18:00 horas la actividad crece bastante.

 

Frecuencia de palabras

Veamos cuáles son las palabras más utilizadas por los usuarios en sus tweets.

count(tweets.tkns,
      word,
      sort = TRUE) %>% 
  head(10) %>% 
  kbl( 
      align = "c",
      caption = "Tokenización",
      ) %>%
  kable_styling("hover", full_width = F)
Tokenización
word n
citibanamex 2149
banamex 629
tarjeta 390
banco 245
cuenta 201
hacer 178
dinero 171
solo 150
sucursal 150
bbva 149
tweets.tkns %>% 
  count(word) %>% 
  top_n(10, n) %>%
  arrange(desc(n)) %>% 
  ggplot(aes(x = reorder(word, n), y = n)) + 
  geom_col(fill = "steelblue", color = "firebrick") + 
  theme_bw() + 
  labs(y = "", x = "") + 
  theme(legend.position = "none") +
  scale_y_continuous(labels = scales::comma) +
  coord_flip() +
  labs(title = "Top 10: Palabras más frecuentes", 
       y = "Frecuencia")

No es de extrañar que las palabras más frecuentes tengan que ver con el nombre de la institución bancaria a la que pertenecen los usuarios cuyos tweets descargamos. También, por las demás palabras parece ser que los comentarios de los usuarios tienen que ver con señalamientos referentes a tarjetas, cuentas, banco, sucursal, etc.

 

Nube de palabras

Otra forma de ver las palabras más frecuentes y más utilizada en temas de análisis de texto es la Word Cloud o nube de palabras, la cual es una representación gráfica de la frecuencia y cuyas palabras más importantes (repetidas) tienen mayor tamaño.

count(tweets.tkns, word) %>%
  with(wordcloud(words = word,
                 freq = n,
                 max.words = 400,
                 scale = c(3,1),
                 rot.per = 0.3,
                 random.order = FALSE,
                 colors = brewer.pal(6, "Dark2")))

 

Relación entre palabras

Se han hecho descripciones del texto considerando palabras de forma individual que nos han mostrado las más utilizadas por los usuarios, sin embargo, para hacer una descripción un poco más completa y realista es conveniente analizar las palabras en forma conjunta. Lo siguiente que se hará es calcular y visualizar las relaciones entre dos palabras (bigramas).

bigramas <- tweets %>% 
  mutate(texto = limpiar(text)) %>%
           select(text) %>%
           unnest_tokens(input = text, 
                         output = "bigrama",
                         token = "ngrams",
                         n = 2, 
                         drop = TRUE)

bigramas  %>% 
  count(bigrama, sort = TRUE) %>% 
  head(10) %>% 
  kbl( 
      align = "c",
      caption = "Tokenización",
      ) %>%
  kable_styling("hover", full_width = F)
Tokenización
bigrama n
https t.co 693
que no 196
no me 165
y no 153
de citibanamex 152
en el 151
de la 146
en la 136
que me 128
de banamex 126

Se quitan manualmente algunas palabras que no aportan valor al análisis:

bigramas <- filter(bigramas, 
                   bigrama!="https t.co")

En el resumen anterior, se aprecia que los bigramas más frecuentes están formados por stopwords, por lo que, se eliminarán por no aportar valor al análisis.

bigramas_separados <- bigramas %>% 
  separate(bigrama, c("palabra1", "palabra2"), sep = " ")

head(bigramas_separados)
# Filtrado de los bigramas que contienen alguna stopword
bigramas_separados <- bigramas_separados  %>%
  filter(!palabra1 %in% custom_stop_words$word) %>%
  filter(!palabra2 %in% custom_stop_words$word)

# Unión de las palabras para formar de nuevo los bigramas
bigramas <- bigramas_separados %>%
            unite(bigrama, palabra1, palabra2, sep = " ")

bigramas %>% 
  count(bigrama, sort = TRUE) %>%
  head(20) %>% 
  kbl( 
      align = "c",
      caption = "Tokenización",
      ) %>%
  kable_styling("hover", full_width = F)
Tokenización
bigrama n
almendrayek citibanamex 77
tarjeta citibanamex 66
citibanamex https 56
citibanamex contactocitibmx 44
citibanamex si 40
banco azteca 38
tarjeta banamex 38
citibanamex hola 36
pésimo servicio 32
contactocitibmx citibanamex 30
citibanamex gracias 25
puedo hacer 23
msi https 22
citibanamex condusefmx 21
pesos 10 21
2021 https 19
pesos https 19
preventa citibanamex 19
centro citibanamex 18
mejor fin 18

Una forma más visual e informativa de analizar las relaciones entre las palabras es mediante el uso de redes.

graph <- bigramas %>%
         separate(bigrama, c("palabra1", "palabra2"), sep = " ") %>% 
         count(palabra1, palabra2, sort = TRUE) %>%
         filter(n > 18) %>%
  graph_from_data_frame(directed = FALSE)

set.seed(123)

plot(graph, vertex.label.font = 3,
     vertex.label.color = "black",
     vertex.label.cex = 0.9, edge.color = "gray85")

Podemos apreciar que dentro del top 10 de los bigramas están:

  • tarjeta citibanamex
  • banco azteca
  • pésimo servicio

Probablemente, las quejas tienen que ver con alguna tarjeta de crédito, con la atención al público tanto en sucursal, vía telefónica y online.

 

Análisis de sentimientos

En esta última parte del trabajo se hará el análisis de sentimientos, es decir, se clasificarán las palabras contenidas en los tweets en una de las siguientes dos categorías: positiva o negativa, además, utilizando el lexicon nrc se podrán también clasificar las palabras en los siguientes sentimientos: confianza, ira, disgusto, asombro, alegría, miedo, tristeza y premonición.

El archivo de sentimientos se puede descargar del repositorio github del proyecto 7PartidasDigital elaborado por la Universidad de Valladolid.

source("get_sentiments.R")
sentimientos <- readRDS(file = "sentimientos.rds")

tweets.tkns <- tweets.tkns %>% 
  rename(palabra = word)

Veamos la cantidad de palabras clasificadas como positivas y negativas de todos los tweets

tweets.tkns %>%
  right_join(get_sentiments("nrc")) %>%
  filter(!is.na(sentimiento) & sentimiento == "positivo" | sentimiento == "negativo") %>%
  count(sentimiento, sort = TRUE) %>% 
  kable( 
      format = "html",
      digits = 0,
      caption = "Sentimientos Positivos - Negativos",
      format.args = list(big.mark = ","),
      table.attr = "style='width:50%;'") %>%
  kable_styling(bootstrap_options = c("striped", "hover"))
Sentimientos Positivos - Negativos
sentimiento n
positivo 3,960
negativo 3,824
tweets.tkns %>%
  right_join(get_sentiments("nrc")) %>%
  filter(!is.na(sentimiento)& sentimiento == "positivo" | sentimiento == "negativo") %>% 
  count(palabra, sentimiento, sort = TRUE) %>%
  acast(palabra ~ sentimiento, value.var = "n", fill = 0) %>%
  comparison.cloud(colors = c("red","blue"), 
                   max.words = 300,
                   title.size = 2)

En el cuadro anterior, se aprecia que se detectó casi la misma cantidad de palabras negativas que positivas. Ahora, se muestra el detalle (top 15) de las palabras en cada categoría.

tweets.tkns %>%
  right_join(get_sentiments("nrc")) %>%
  filter(!is.na(sentimiento)& sentimiento == "positivo" | sentimiento == "negativo") %>% 
  count(palabra, sentimiento, short = TRUE) %>% 
  group_by(sentimiento) %>% 
  top_n(15) %>% 
  ungroup() %>% 
  mutate(palabra = reorder(palabra, n)) %>% 
  ggplot(aes(palabra, n, fill = sentimiento)) +
  geom_col(show.legend = FALSE) +
  scale_fill_manual(values = c("darkred", "steelblue")) +
  geom_text(aes(label = n), hjust = 1.2, color = "white") +
  facet_wrap(~sentimiento, scales = "free_y") +
  coord_flip() +
  xlab(NULL) +
  labs(title = "Detalle de Sentimientos")

En las gráficas anteriores, se aprecian las palabras que los usuarios capturaron en sus tweets y que el algoritmo los clasificó como negativos o positivos. Las palabras negativas parece que tienen más que ver con reclamos y quejas de los usuarios como robo, mal, peor, cancelar, problema, entre otras.

Ahora, veamos el conteo de los demás sentimientos para darnos una idea del ánimo de los usuarios cuando escribieron sus tweets.

tweets.tkns %>%
  right_join(get_sentiments("nrc")) %>%
  filter(!is.na(sentimiento)) %>%
  count(sentimiento, sort = TRUE) %>% 
  kable( 
      format = "html",
      digits = 0,
      caption = "Resumen de Sentimientos",
      format.args = list(big.mark = ","),
      table.attr = "style='width:50%;'") %>%
  kable_styling(bootstrap_options = c("striped", "hover"))
Resumen de Sentimientos
sentimiento n
positivo 3,960
negativo 3,824
confianza 2,857
miedo 1,945
tristeza 1,678
premonición 1,674
ira 1,610
disgusto 1,271
alegría 1,234
asombro 941

Gráficamente:

tweets.tkns %>%
  right_join(get_sentiments("nrc")) %>%
  filter(!is.na(sentimiento)) %>%
  group_by(sentimiento) %>%
  count(sentimiento, sort = TRUE) %>% 
  mutate(sentimiento = reorder(factor(sentimiento), n)) %>%
  ggplot(aes(reorder(sentimiento,n), n, fill = sentimiento)) +
  geom_col(show.legend = FALSE) +
  geom_text(aes(label = format(n, big.mark = ",")), hjust = 1.2, color = "black") +
  coord_flip() +
  xlab(NULL) +
  labs(title = "Detalle de Emociones")

Bajando más el análisis para conocer las palabras contenidas en cada emoción:

tweets.tkns %>%
  right_join(get_sentiments("nrc")) %>%
  filter(!is.na(sentimiento)) %>% 
  count(palabra, sentimiento, short = TRUE) %>% 
  group_by(sentimiento) %>% 
  top_n(15) %>% 
  ungroup() %>% 
  mutate(palabra = reorder(palabra, n)) %>% 
  ggplot(aes(palabra, n, fill = sentimiento)) +
  geom_col(show.legend = FALSE) +
  geom_text(aes(label = n), hjust = 1.2, color = "white") +
  facet_wrap(~sentimiento, scales = "free_y") +
  coord_flip() +
  xlab(NULL) +
  labs(title = "Detalle de Emociones")

Hay algunas palabras que se clasifican en más de una emoción como, por ejemplo, dinero que se encuentra en las categorías de alegría, asombro e ira. Lo anterior, porque depende mucho del contexto de la palabra.

tweets.tkns %>%
  right_join(get_sentiments("nrc")) %>%
  count(palabra, sentimiento, sort = TRUE) %>% 
  filter(!is.na(sentimiento), sentimiento != "positivo", sentimiento != "negativo") %>%
  acast(palabra ~ sentimiento, value.var = "n", fill = 0) %>%
  comparison.cloud(title.size = 1.5)

Conclusiones

Después de haber realizado este pequeño análisis podemos concluir que los usuarios de la institución bancaria en cuestión se suelen quejar por temas relacionados con errores en algunas aplicaciones, se sinten robados, pésimo o mal servicio, cancelaciones y con algunos otros problemas que han tenido como clientes.

El resultado de hacer un análisis de emociones nos indica que la mayoría de los usuarios que comentaron en su cuenta de twitter tenían emociones negativas como miedo, tristeza, ira y disgusto. Lo anterior, porque fueron las emociones con mayor frecuencia, sin embargo, al clasificar todas las palabras en positivas y negativas la proporción es casi la misma.

Hay que aclarar que depende del lexicon o diccionario utilizado los resultados serán distintos.

Con este corto trabajo podemos darnos una idea de lo que el equipo de atención a clientes del banco podría hacer para disminuir los reclamos o quejas como, por ejemplo, realizar encuestas a los clientes que van directamente a sucursal y enviar encuestas vía correo electrónico después de usar algún cajero automático o directamente en su página web. Actualmente, hay empresas que se dedican a proporcionar ese servicio y ya solo le entregan los resultados al cliente, el cual con su equipo o área de analítica se encarga de procesar dicha información para la toma de decisiones.

LS0tDQp0aXRsZTogIkFuYWxpc2lzIGRlIFR3ZWV0cyBVc3VhcmlvcyBDaXRpYmFuYW1leCINCnN1YnRpdGxlOiAiUHJveWVjdG86IFByb2Nlc2FtaWVudG8gZGVsIExlbmd1YWplIE5hdHVyYWwiDQphdXRob3I6ICJBbGVqYW5kcm8gUm9qYXMgTW9yZW5vIg0KZGF0ZTogIjQvMS8yMDIyIg0Kb3V0cHV0Og0KICBodG1sX2RvY3VtZW50Og0KICAgIGNvZGVfZm9sZGluZzogc2hvdw0KICAgIGNvZGVfZG93bmxvYWQ6IHRydWUNCiAgICBkZl9wcmludDogcGFnZWQNCiAgICB0aGVtZTogc3BhY2VsYWINCiAgICB0b2M6IHRydWUNCiAgICB0b2NfZmxvYXQ6IHRydWUNCmVkaXRvcl9vcHRpb25zOg0KICBjaHVua19vdXRwdXRfdHlwZTogY29uc29sZQ0KLS0tDQoNCjxicj4NCg0KIyA8c3BhbiBzdHlsZT0iY29sb3I6cmdiKDAsIDAsIDIwNSkiPlByZXNlbnRhY2nDs248L3NwYW4+DQoNCjxkaXYgY2xhc3M9dGV4dC1qdXN0aWZ5Pg0KRW4gZWwgcHJlc2VudGUgcHJveWVjdG8gc2UgYW5hbGl6YXLDoW4gYWxndW5vcyBjb21lbnRhcmlvcyBoZWNob3MgYSB0cmF2w6lzIGRlIGxhIHJlZCBzb2NpYWwgKipUd2l0dGVyKiogcG9yIGxvcyB1c3VhcmlvcyBkZSBsYSBiYW5jYSBlbiBNw6l4aWNvLCBlc3BlY8OtZmljYW1lbnRlLCBsb3MgY2xpZW50ZXMgZGUgKipDaXRpYmFuYW1leCoqLCBlbiBlbCBwZXJpb2RvIGNvbXByZW5kaWRvIGRlbCAyMSBhbCAzMCBkZSBkaWNpZW1icmUgZGUgMjAyMS4gTG8gYW50ZWlvciwgZGViaWRvIGEgcXVlIGRpY2hhIHJlZCBzb2NpYWwgc29sbyBwZXJtaXRlIHF1ZSBzZSBleHRyYWlnYW4gZGF0b3MgZGUgbG9zIMO6bHRpbW9zIDYtOSBkw61hcy4gU2UgZXh0cmFqZXJvbiB0d2VldHMgZGUgbGEgY3VlbnRhIEBDb250YWN0b0NpdGlibXgsIHF1ZSBlcyB1bm8gZGUgbG9zIGNhbmFsZXMgZGUgYXRlbmNpw7NuIGEgY2xpZW50ZXMgcXVlIHRpZW5lIGRpY2hhIGVudGlkYWQgZmluYW5jaWVyYSBwYXJhIGludGVyYWN0dWFyIGNvbiBzdXMgY2xpZW50ZXMuDQoNCkVzdGUgYW7DoWxpc2lzIGVzIGRlIG1pbmVyw61hIGRlIHRleHRvLCBwb3IgbG8gcXVlLCBsYSBtYXlvcsOtYSBkZWwgdHJhYmFqbyBtb3N0cmFyw6EgdMOpY25pY2FzIGVzdGFkw61zdGljYXMgZGVzY3JpcHRpdmFzIHkgZXhwbG9yYXRvcmlhcy4NCg0KTG9zIGRhdG9zIHNlIHB1ZWRlbiBleHRyYWVyIGRpcmVjdGFtZW50ZSBkZXNkZSB0d2l0dGVyLCBzaW4gZW1iYXJnbywgcG9yIGNvbW9kaWRhZCB5IHBhcmEgc2VndWlyIGVsIHByZXNldGUgdHJhYmFqbywgdGFtYmnDqW4sIHNlIGVuY3VlbnRyYW4gZW4gbWkgcmVwb3NpdG9yaW8gZGUgW2dpdGh1Yl0oaHR0cHM6Ly9naXRodWIuY29tL2Fyb2phc21vci9RdWVqYXNibm14KSBqdW50byBjb24gZWwgY8OzZGlnbyBkZSBlc3RhIHB1YmxpY2FjacOzbi4NCjwvZGl2Pg0KDQo8YnI+DQoNCiMgPHNwYW4gc3R5bGU9ImNvbG9yOnJnYigwLCAwLCAyMDUpIj5Db250ZW5pZG88L3NwYW4+DQoNCiogUHJlc2VudGFjacOzbg0KKiBDb250ZW5pZG8NCiogU2V0IHVwDQoqIEV4dHJhY2Npw7NuIGRlIGxhIGluZm9ybWFjacOzbg0KKiBUb2tlbml6YWNpb24geSBsaW1waWV6YSBkZSB0ZXh0bw0KKiBBbsOhbGlzaXMgZXhwbG9yYXRvcmlvDQogICArIERpc3RyaWJ1Y2nDs24gdGVtcG9yYWwgZGUgbG9zIHR3ZWV0cw0KICAgKyBGcmVjdWVuY2lhIGRlIHBhbGFicmFzDQogICArIE51YmUgZGUgcGFsYWJyYXMNCiogUmVsYWNpw7NuIGVudHJlIHBhbGFicmFzDQoqIEFuw6FsaXNpcyBkZSBzZW50aW1pZW50b3MNCiogQ29uY2x1c2lvbmVzDQoNCjxicj4NCg0KIyA8c3BhbiBzdHlsZT0iY29sb3I6cmdiKDAsIDAsIDIwNSkiPlNldCB1cDwvc3Bhbj4NCg0KSW5pY2lhbW9zIGNvbmZpZ3VyYW5kbyBsYXMgb3BjaW9uZXMgZ2VuZXJhbGVzIHF1ZSB2YW1vcyBhIHJlcXVlcmlyIHBhcmEgZWwgZGVzYXJyb2xsbyBkZSBlc3RlIHByb3llY3RvLg0KDQpgYGB7ciBzZXR1cCwgbWVzc2FnZT1GQUxTRSwgY29tbWVudD0iIiwgd2FybmluZz1GQUxTRSwgcmVzdWx0cz0naGlkZSd9DQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUsDQogICAgICAgICAgICAgICAgICAgICAgd2FybmluZyA9IEZBTFNFLA0KICAgICAgICAgICAgICAgICAgICAgIG1lc3NhZ2UgPSBGQUxTRSwNCiAgICAgICAgICAgICAgICAgICAgICBmaWcuYWxpZ24gPSAiY2VudGVyIg0KICAgICAgICAgICAgICAgICAgICAgICkNCg0KcGFxdWV0ZXMgPC0gYygicnR3ZWV0IiwgInRpZHl2ZXJzZSIsICJsdWJyaWRhdGUiLCAid29yZGNsb3VkIiwgIlJDb2xvckJyZXdlciIsICJ0aWR5dGV4dCIsICJpZ3JhcGgiLCAiZ2dyYXBoIiwgImtuaXRyIiwgIkRUIiwgImthYmxlRXh0cmEiLCAidG0iLCAicmVzaGFwZTIiKQ0KDQppbnN0YWxhZG9zIDwtIHBhcXVldGVzICVpbiUgaW5zdGFsbGVkLnBhY2thZ2VzKCkNCg0KaWYoc3VtKGluc3RhbGFkb3MgPT0gRkFMU0UpID4gMCkgew0KICBpbnN0YWxsLnBhY2thZ2VzKHBhcXVldGVzWyFpbnN0YWxhZG9zXSkNCn0NCg0KbGFwcGx5KHBhcXVldGVzLCByZXF1aXJlLCBjaGFyYWN0ZXIub25seSA9IFRSVUUpDQpgYGANCg0KJm5ic3A7DQoNCiMgPHNwYW4gc3R5bGU9ImNvbG9yOnJnYigwLCAwLCAyMDUpIj5FeHRyYWNjacOzbiBkZSBkZSBpbmZvcm1hY2nDs248L3NwYW4+DQoNCkxhIGluZm9ybWFjacOzbiBkZSBsb3MgdHdlZXRzIHNlIHB1ZWRlbiBkZXNjYXJnYXIgZGlyZWN0YW1lbnRlIGNvbiBhcG95byBkZSBsYSBmdW5jacOzbiAqKnNlYXJjaF90d2VldHMqKiBjb250ZW5pZGEgZW4gZWwgcGFxdWV0ZSAqKnJ0d2VldCoqOg0KDQpgYGB7ciBldmFsPUZBTFNFfQ0KIyBzZSBleHRyYWVuIGxvcyB0d2VldHMNCmRzIDwtIE1hcCgNCiAgInNlYXJjaF90d2VldHMiLA0KICBxID0gIkBDaXRpYmFuYW1leCBPUiBCYW5hbWV4IE9SIENpdGliYW5hbWV4IE9SIGJhbmFtZXggT1IgQGJhbmFtZXgiLA0KICBuID0gNTAwMCwNCiAgcGFyc2UgPSBUUlVFLA0KICBpbmNsdWRlX3J0cyA9IEZBTFNFLA0KICBsYW5nID0gImVzIg0KKQ0KDQojIHVuaXIgbG9zIGRhdG9zDQpkcyA8LSBkb19jYWxsX3JiaW5kKGRzKQ0KDQojIHNlIHNlbGVjY2lvbmFuIGNvbHVtbmFzDQpkcyA8LSBkcyAlPiUgDQogIHNlbGVjdCgic2NyZWVuX25hbWUiLCANCiAgICAgICAgICJjcmVhdGVkX2F0IiwgDQogICAgICAgICAic3RhdHVzX2lkIiwgDQogICAgICAgICAidGV4dCIsDQogICAgICAgICAidXNlcl9pZCIsDQogICAgICAgICAidmVyaWZpZWQiLA0KICAgICAgICAgImxvY2F0aW9uIikNCg0KIyBzZSBndWFyZGEgZWwgYXJjaGl2bw0Kd3JpdGVfY3N2KGRzLCAidHdlZXRzLmNzdiIpDQpgYGANCg0KYGBge3J9DQojIHNlIGNhcmdhbiBsb3MgZGF0b3MgcHJldmlhbWVudGUgZ3VhcmRhZG9zDQp0d2VldHMgPC0gcmVhZF9jc3YoInR3ZWV0cy5jc3YiKQ0KDQojIHNlIHF1aXRhbiBsYXMgcmVzcHVlc3RhcyBkZSBsYSBpbnN0aXR1Y2nDs24sIHlhIHF1ZSBub3MgaW50ZXJlc2FuIHNvbG8gbG9zIGNvbWVudGFyaW9zDQojIGRlIGxvcyBjbGllbnRlcw0KdHdlZXRzIDwtIHR3ZWV0cyAlPiUgDQogIGZpbHRlcihzY3JlZW5fbmFtZSAhPSJDaXRpYmFuYW1leCIgJiBzY3JlZW5fbmFtZSAhPSJDb250YWN0b0NpdGlibXgiKSAlPiUgDQogIHNlbGVjdChjKGNyZWF0ZWRfYXQsIHRleHQpKQ0KDQpgYGANCg0KU2UgbXVlc3RyYSB1biBmcmFnbWVudG8gZGUgbG9zIHR3ZWV0cyBleHRyYWlkb3M6DQoNCmBgYHtyfQ0KRFQ6OmRhdGF0YWJsZShoZWFkKHR3ZWV0cywgMjApLA0KICAgICAgICAgICAgICByb3duYW1lcyA9IEZBTFNFLA0KICAgICAgICAgICAgICBvcHRpb25zID0gbGlzdCgNCiAgICAgICAgICAgICAgICBwYWdlTGVuZ3RoID0gNSwNCiAgICAgICAgICAgICAgICBzY3JvbGxYID0gVFJVRSkpDQoNCmBgYA0KDQombmJzcDsNCg0KIyA8c3BhbiBzdHlsZT0iY29sb3I6cmdiKDAsIDAsIDIwNSkiPlRva2VuaXphY2lvbiB5IGxpbXBpZXphIGRlIHRleHRvPC9zcGFuPg0KDQpFbiBlc3RlIHByb3llY3RvLCBlbCBwcm9jZXNvIGRlIGxpbXBpZXphIGNvbnNpc3RlIGVuIGVsaW1pbmFyIGRlbCB0ZXh0byB0b2RhcyBhcXVlbGxhcyBwYWxhYnJhcywgc2lnbm9zLCBuw7ptZXJvcywgYWJyZXZpYXR1cmFzLCBldGMuLCBxdWUgbm8gYXBvcnRhbiBpbmZvcm1hY2nDs24gaW1wb3J0YW50ZS4NCg0KQWRlbcOhcywgcGFyYSBwb2RlciBzZWd1aXIgYWRlbGFudGUgZXMgbmVjZXNhcmlvIHRlbmVyIGxhIGluZm9ybWFjacOzbiBkZSBsb3MgY29tZW50YXJpb3MgKGNvbHVtbmEgdGV4dCksIGVuIHBhbGFicmFzIGluZGl2aWR1YWxlcyBwYXJhIHBvZGVyIG9idGVuZXIsIGVudHJlIG90cmFzIGNvc2FzIHN1cyBmcmVjdWVuY2lhcyBwYXJhIHNhYmVyIGN1w6FsZXMgc29uIGxhcyBtw6FzIG1lbmNpb25hZGFzIHBvciBsb3MgdXN1YXJpb3MuDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0V9DQpzb3VyY2UoImxpbXBpZXphLlIiKQ0KDQp0d2VldHMudGtucyA8LSB0d2VldHMgJT4lIA0KICBtdXRhdGUodGV4dG9fdG9rZW5pemFkbyA9IG1hcCgueCA9IHRleHQsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC5mID0gbGltcGlhcl90b2tlbml6YXIpKQ0KDQp0d2VldHMudGtucyAlPiUgDQogIHNsaWNlKDEpICU+JSANCiAgc2VsZWN0KHRleHRvX3Rva2VuaXphZG8pICU+JQ0KICBwdWxsKCkNCg0KdHdlZXRzLnRrbnMgPC0gdHdlZXRzLnRrbnMgJT4lIA0KICBzZWxlY3QoLXRleHQpICU+JQ0KICB1bm5lc3QoKQ0KDQp0d2VldHMudGtucyA8LSB0d2VldHMudGtucyAlPiUgDQogIHJlbmFtZSh0b2tlbiA9IHRleHRvX3Rva2VuaXphZG8pDQoNCnR3ZWV0cy50a25zICU+JSANCiAgaGVhZCgxMCkgJT4lIA0KICBrYmwoIA0KICAgICAgYWxpZ24gPSAiYyIsDQogICAgICBjYXB0aW9uID0gIlRva2VuaXphY2nDs24iLA0KICAgICAgKSAlPiUNCiAga2FibGVfcGFwZXIoImhvdmVyIiwgZnVsbF93aWR0aCA9IEYpDQoNCmBgYA0KDQpDb21vIGxvcyBjb21lbnRhcmlvcyBlc3TDoW4gZW4gZWwgaWRpb21hIEVzcGHDsW9sLCBzZSB1dGlsaXphcsOhIGVsIHBhcXVldGUgKip0bSoqIHBhcmEgdHJhYmFqYXIgY29uIGxhcyBsbGFtYWRhcyBzdG9wd29yZHMsIHF1ZSBzb24gcGFsYWJyYXMgcXVlIG5vIGFwb3J0YW4gdmFsb3IgYWwgYW7DoWxpc2lzIGNvbW8gbG9zIGFydMOtY3Vsb3MsIGNvbmVjdG9yZXMsIGV0Yy4NCg0KYGBge3IgbWVzc2FnZT1GQUxTRX0NCmN1c3RvbV9zdG9wX3dvcmRzIDwtIGJpbmRfcm93cygNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aWJibGUod29yZCA9IHRtOjpzdG9wd29yZHMoInNwYW5pc2giKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxleGljb24gPSAiY3VzdG9tIikpDQp0d2VldHMudGtucyA8LSB0d2VldHMudGtucyAlPiUgDQogIHJlbmFtZSh3b3JkID0gdG9rZW4sDQogICAgICAgICBmZWNoYSA9IGNyZWF0ZWRfYXQpDQoNCnR3ZWV0cy50a25zIDwtIGFudGlfam9pbih4ID0gdHdlZXRzLnRrbnMsDQogICAgICAgICAgICAgICAgICAgIHkgPSBjdXN0b21fc3RvcF93b3JkcywNCiAgICAgICAgICAgICAgICAgICAgYnkgPSAid29yZCIpDQoNCnR3ZWV0cy50a25zICU+JSANCiAgaGVhZCgxMCkgJT4lIA0KICBrYmwoIA0KICAgICAgYWxpZ24gPSAiYyIsDQogICAgICBjYXB0aW9uID0gIlRva2VuaXphY2nDs24iLA0KICAgICAgKSAlPiUNCiAga2FibGVfcGFwZXIoImhvdmVyIiwgZnVsbF93aWR0aCA9IEYpDQoNCmBgYA0KDQpFbCByZXN1bHRhZG8gZGUgbGEgbGltcGllemEgeSB0b2tlbml6YWNpw7NuIGVzIHVuIG1hcmNvIGRlIGRhdG9zIGRlIDMyLDIxMSBmaWxhcyB5IDIgY29sdW1uYXM6DQoNCmBgYHtyfQ0KZGltKHR3ZWV0cy50a25zKQ0KYGBgDQoNCiZuYnNwOw0KDQojIDxzcGFuIHN0eWxlPSJjb2xvcjpyZ2IoMCwgMCwgMjA1KSI+QW7DoWxpc2lzIGV4cGxvcmF0b3Jpbzwvc3Bhbj4NCg0KQSBjb250aW51YWNpw7NuLCBzZSBjcmVhcsOhbiBhbGd1bmFzIGdyw6FmaWNhcyBxdWUgZmFjaWxpdGVuIGVsIGFuw6FsaXNpcyBleHBsb3JhdG9yaW8gdGFudG8gZGVsIGNvbXBvcnRhbWllbnRvIGRlIGxvcyB1c3VhcmlvcyBjb21vIGRlbCBjb250ZW5pZG8gZGUgc3VzIGNvbWVudGFyaW9zLiBMbyBhbnRlcmlvciwgbm9zIGRhcsOhIHVuYSBwcmltZXIgaWRlYSBkZSBsbyBxdWUgdHJhbnNtaXRpZXJvbiBlbiBzdXMgdHdlZXRzLg0KDQojIyA8c3BhbiBzdHlsZT0iY29sb3I6cmdiKDAsIDAsIDIwNSkiPkRpc3RyaWJ1Y2nDs24gdGVtcG9yYWwgZGUgbG9zIHR3ZWV0czwvc3Bhbj4NCg0KVmVhbW9zIGxhIGRpc3RyaWJ1Y2nDs24gZGUgbG9zIHR3ZWV0cyBwYXJhIHZlciBzdSBldm9sdWNpw7NuIGVuIGVsIHRpZW1wbyBkdXJhbnRlIGxvcyA5IGTDrWFzIGRlIGFjdGl2aWRhZCBjb24gbGEgcXVlIGNvbnRhbW9zDQoNCmBgYHtyfQ0KdHdlZXRzLnRrbnMgJT4lIA0KICBnZ3Bsb3QoYWVzKHggPSBhcy5EYXRlKGZlY2hhKSkpICsNCiAgZ2VvbV9oaXN0b2dyYW0ocG9zaXRpb24gPSAiaWRlbnRpdHkiLCANCiAgICAgICAgICAgICAgICAgYmlucyA9IDIwLCANCiAgICAgICAgICAgICAgICAgc2hvdy5sZWdlbmQgPSBGQUxTRSwgDQogICAgICAgICAgICAgICAgIGZpbGwgPSAic3RlZWxibHVlIiwNCiAgICAgICAgICAgICAgICAgY29sb3IgPSAiZmlyZWJyaWNrIikgKw0KICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjpjb21tYSkgKw0KICBzY2FsZV94X2RhdGUoZGF0ZV9sYWJlbHMgPSAiJWQtJW0tJVkiLCBkYXRlX2JyZWFrcyA9ICIxIGRheSIpICsNCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgc2l6ZSA9IDgpKSArDQogIGxhYnModGl0bGUgPSAiRGlzdHJpYnVjacOzbiBUd2VldHMgcG9yIETDrWEiLCB4ID0gImZlY2hhIGRlIHB1YmxpY2FjacOzbiIsIHkgPSAibsO6bWVybyBkZSB0d2VldHMiKSANCg0KYGBgDQoNCkVuIGVzdGEgcHJpbWVyIGdyw6FmaWNhIHNlIGFwcmVjaWEgcXVlIGxvcyDDumx0aW1vcyBjdWF0cm8gZMOtYXMgZGVsIGHDsW8gMjAyMSBodWJvIHVuYSBtYXlvciBhY3RpdmlkYWQgZGUgbG9zIHVzdWFyaW9zLCBzaWVuZG8gZWwgZMOtYSAyOCBlbCBkZSBtYXlvciBjYW50aWRhZCBkZSBtZW5zYWplcy4NCg0KYGBge3J9DQp0d2VldHMudGtucyAlPiUgDQogIGdncGxvdChhZXMoeCA9IGFzLlBPU0lYY3QoZmVjaGEpKSkgKw0KICBnZW9tX2hpc3RvZ3JhbShwb3NpdGlvbiA9ICJpZGVudGl0eSIsIA0KICAgICAgICAgICAgICAgICBiaW5zID0gMjAsIA0KICAgICAgICAgICAgICAgICBzaG93LmxlZ2VuZCA9IEZBTFNFLCANCiAgICAgICAgICAgICAgICAgZmlsbCA9ICJzdGVlbGJsdWUiLA0KICAgICAgICAgICAgICAgICBjb2xvciA9ICJmaXJlYnJpY2siKSArDQogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBzY2FsZXM6OmNvbW1hKSArDQogIHNjYWxlX3hfZGF0ZXRpbWUoZGF0ZV9sYWJlbHMgPSAiJWQtJW0tJVkgJUgiLCBkYXRlX2JyZWFrcyA9ICI2IGhvdXJzIikgKw0KICAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgc2l6ZSA9IDgpKSArDQogIGxhYnModGl0bGUgPSAiRGlzdHJpYnVjacOzbiBUd2VldHM6IGNhZGEgc2VpcyBob3JhcyIsDQogICAgICAgeCA9ICJmZWNoYSBkZSBwdWJsaWNhY2nDs24iLA0KICAgICAgIHkgPSAibsO6bWVybyBkZSB0d2VldHMiKSANCg0KYGBgDQoNCjxkaXYgY2xhc3M9dGV4dC1qdXN0aWZ5Pg0KRW4gZXN0YSBzZWd1bmRhIGdyw6FmaWNhIHNlIG11ZXN0cmEgbGEgYWN0aXZpZGFkIGRlIGxvcyB1c3VhcmlvcyBjYWRhIHNlaXMgaG9yYXMuIEVuIGVsbGEgc2Ugb2JzZXJ2YSBxdWUgZW4gbGEgc2VndW5kYSBwYXJ0ZSBkZWwgcmFuZ28gZGUgZMOtYXMgYW5hbGl6YWRvIGxhIGFjdGl2aWRhZCBlcyBtZW5vciBzb2xvIHF1ZSBlbiBsb3Mgw7psdGltb3MgNCBkw61hcyBkZXNwdcOpcyBkZSBsYXMgMTg6MDAgaG9yYXMgbGEgYWN0aXZpZGFkIGNyZWNlIGJhc3RhbnRlLg0KPC9kaXY+DQoNCiZuYnNwOw0KDQojIyA8c3BhbiBzdHlsZT0iY29sb3I6cmdiKDAsIDAsIDIwNSkiPkZyZWN1ZW5jaWEgZGUgcGFsYWJyYXM8L3NwYW4+DQoNClZlYW1vcyBjdcOhbGVzIHNvbiBsYXMgcGFsYWJyYXMgbcOhcyB1dGlsaXphZGFzIHBvciBsb3MgdXN1YXJpb3MgZW4gc3VzIHR3ZWV0cy4NCg0KYGBge3J9DQpjb3VudCh0d2VldHMudGtucywNCiAgICAgIHdvcmQsDQogICAgICBzb3J0ID0gVFJVRSkgJT4lIA0KICBoZWFkKDEwKSAlPiUgDQogIGtibCggDQogICAgICBhbGlnbiA9ICJjIiwNCiAgICAgIGNhcHRpb24gPSAiVG9rZW5pemFjacOzbiIsDQogICAgICApICU+JQ0KICBrYWJsZV9zdHlsaW5nKCJob3ZlciIsIGZ1bGxfd2lkdGggPSBGKQ0KDQpgYGANCg0KYGBge3J9DQp0d2VldHMudGtucyAlPiUgDQogIGNvdW50KHdvcmQpICU+JSANCiAgdG9wX24oMTAsIG4pICU+JQ0KICBhcnJhbmdlKGRlc2MobikpICU+JSANCiAgZ2dwbG90KGFlcyh4ID0gcmVvcmRlcih3b3JkLCBuKSwgeSA9IG4pKSArIA0KICBnZW9tX2NvbChmaWxsID0gInN0ZWVsYmx1ZSIsIGNvbG9yID0gImZpcmVicmljayIpICsgDQogIHRoZW1lX2J3KCkgKyANCiAgbGFicyh5ID0gIiIsIHggPSAiIikgKyANCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSArDQogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBzY2FsZXM6OmNvbW1hKSArDQogIGNvb3JkX2ZsaXAoKSArDQogIGxhYnModGl0bGUgPSAiVG9wIDEwOiBQYWxhYnJhcyBtw6FzIGZyZWN1ZW50ZXMiLCANCiAgICAgICB5ID0gIkZyZWN1ZW5jaWEiKQ0KDQpgYGANCg0KPGRpdiBjbGFzcz10ZXh0LWp1c3RpZnk+DQpObyBlcyBkZSBleHRyYcOxYXIgcXVlIGxhcyBwYWxhYnJhcyBtw6FzIGZyZWN1ZW50ZXMgdGVuZ2FuIHF1ZSB2ZXIgY29uIGVsIG5vbWJyZSBkZSBsYSBpbnN0aXR1Y2nDs24gYmFuY2FyaWEgYSBsYSBxdWUgcGVydGVuZWNlbiBsb3MgdXN1YXJpb3MgY3V5b3MgdHdlZXRzIGRlc2NhcmdhbW9zLiBUYW1iacOpbiwgcG9yIGxhcyBkZW3DoXMgcGFsYWJyYXMgcGFyZWNlIHNlciBxdWUgbG9zIGNvbWVudGFyaW9zIGRlIGxvcyB1c3VhcmlvcyB0aWVuZW4gcXVlIHZlciBjb24gc2XDsWFsYW1pZW50b3MgcmVmZXJlbnRlcyBhIHRhcmpldGFzLCBjdWVudGFzLCBiYW5jbywgc3VjdXJzYWwsIGV0Yy4NCjwvZGl2Pg0KDQombmJzcDsNCg0KIyMgPHNwYW4gc3R5bGU9ImNvbG9yOnJnYigwLCAwLCAyMDUpIj5OdWJlIGRlIHBhbGFicmFzPC9zcGFuPg0KDQpPdHJhIGZvcm1hIGRlIHZlciBsYXMgcGFsYWJyYXMgbcOhcyBmcmVjdWVudGVzIHkgbcOhcyB1dGlsaXphZGEgZW4gdGVtYXMgZGUgYW7DoWxpc2lzIGRlIHRleHRvIGVzIGxhIFdvcmQgQ2xvdWQgbyBudWJlIGRlIHBhbGFicmFzLCBsYSBjdWFsIGVzIHVuYSByZXByZXNlbnRhY2nDs24gZ3LDoWZpY2EgZGUgbGEgZnJlY3VlbmNpYSB5IGN1eWFzIHBhbGFicmFzIG3DoXMgaW1wb3J0YW50ZXMgKHJlcGV0aWRhcykgdGllbmVuIG1heW9yIHRhbWHDsW8uDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0V9DQpjb3VudCh0d2VldHMudGtucywgd29yZCkgJT4lDQogIHdpdGgod29yZGNsb3VkKHdvcmRzID0gd29yZCwNCiAgICAgICAgICAgICAgICAgZnJlcSA9IG4sDQogICAgICAgICAgICAgICAgIG1heC53b3JkcyA9IDQwMCwNCiAgICAgICAgICAgICAgICAgc2NhbGUgPSBjKDMsMSksDQogICAgICAgICAgICAgICAgIHJvdC5wZXIgPSAwLjMsDQogICAgICAgICAgICAgICAgIHJhbmRvbS5vcmRlciA9IEZBTFNFLA0KICAgICAgICAgICAgICAgICBjb2xvcnMgPSBicmV3ZXIucGFsKDYsICJEYXJrMiIpKSkNCg0KYGBgDQoNCiZuYnNwOw0KDQojIDxzcGFuIHN0eWxlPSJjb2xvcjpyZ2IoMCwgMCwgMjA1KSI+UmVsYWNpw7NuIGVudHJlIHBhbGFicmFzPC9zcGFuPg0KDQpTZSBoYW4gaGVjaG8gZGVzY3JpcGNpb25lcyBkZWwgdGV4dG8gY29uc2lkZXJhbmRvIHBhbGFicmFzIGRlIGZvcm1hIGluZGl2aWR1YWwgcXVlIG5vcyBoYW4gbW9zdHJhZG8gbGFzIG3DoXMgdXRpbGl6YWRhcyBwb3IgbG9zIHVzdWFyaW9zLCBzaW4gZW1iYXJnbywgcGFyYSBoYWNlciB1bmEgZGVzY3JpcGNpw7NuIHVuIHBvY28gbcOhcyBjb21wbGV0YSB5IHJlYWxpc3RhIGVzIGNvbnZlbmllbnRlIGFuYWxpemFyIGxhcyBwYWxhYnJhcyBlbiBmb3JtYSBjb25qdW50YS4gTG8gc2lndWllbnRlIHF1ZSBzZSBoYXLDoSBlcyBjYWxjdWxhciB5IHZpc3VhbGl6YXIgbGFzIHJlbGFjaW9uZXMgZW50cmUgZG9zIHBhbGFicmFzIChiaWdyYW1hcykuDQoNCmBgYHtyfQ0KYmlncmFtYXMgPC0gdHdlZXRzICU+JSANCiAgbXV0YXRlKHRleHRvID0gbGltcGlhcih0ZXh0KSkgJT4lDQogICAgICAgICAgIHNlbGVjdCh0ZXh0KSAlPiUNCiAgICAgICAgICAgdW5uZXN0X3Rva2VucyhpbnB1dCA9IHRleHQsIA0KICAgICAgICAgICAgICAgICAgICAgICAgIG91dHB1dCA9ICJiaWdyYW1hIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICB0b2tlbiA9ICJuZ3JhbXMiLA0KICAgICAgICAgICAgICAgICAgICAgICAgIG4gPSAyLCANCiAgICAgICAgICAgICAgICAgICAgICAgICBkcm9wID0gVFJVRSkNCg0KYmlncmFtYXMgICU+JSANCiAgY291bnQoYmlncmFtYSwgc29ydCA9IFRSVUUpICU+JSANCiAgaGVhZCgxMCkgJT4lIA0KICBrYmwoIA0KICAgICAgYWxpZ24gPSAiYyIsDQogICAgICBjYXB0aW9uID0gIlRva2VuaXphY2nDs24iLA0KICAgICAgKSAlPiUNCiAga2FibGVfc3R5bGluZygiaG92ZXIiLCBmdWxsX3dpZHRoID0gRikNCg0KYGBgDQoNClNlIHF1aXRhbiBtYW51YWxtZW50ZSBhbGd1bmFzIHBhbGFicmFzIHF1ZSBubyBhcG9ydGFuIHZhbG9yIGFsIGFuw6FsaXNpczoNCg0KYGBge3J9DQpiaWdyYW1hcyA8LSBmaWx0ZXIoYmlncmFtYXMsIA0KICAgICAgICAgICAgICAgICAgIGJpZ3JhbWEhPSJodHRwcyB0LmNvIikNCg0KYGBgDQoNCkVuIGVsIHJlc3VtZW4gYW50ZXJpb3IsIHNlIGFwcmVjaWEgcXVlIGxvcyBiaWdyYW1hcyBtw6FzIGZyZWN1ZW50ZXMgZXN0w6FuIGZvcm1hZG9zIHBvciBzdG9wd29yZHMsIHBvciBsbyBxdWUsIHNlIGVsaW1pbmFyw6FuIHBvciBubyBhcG9ydGFyIHZhbG9yIGFsIGFuw6FsaXNpcy4NCg0KYGBge3J9DQpiaWdyYW1hc19zZXBhcmFkb3MgPC0gYmlncmFtYXMgJT4lIA0KICBzZXBhcmF0ZShiaWdyYW1hLCBjKCJwYWxhYnJhMSIsICJwYWxhYnJhMiIpLCBzZXAgPSAiICIpDQoNCmhlYWQoYmlncmFtYXNfc2VwYXJhZG9zKQ0KDQojIEZpbHRyYWRvIGRlIGxvcyBiaWdyYW1hcyBxdWUgY29udGllbmVuIGFsZ3VuYSBzdG9wd29yZA0KYmlncmFtYXNfc2VwYXJhZG9zIDwtIGJpZ3JhbWFzX3NlcGFyYWRvcyAgJT4lDQogIGZpbHRlcighcGFsYWJyYTEgJWluJSBjdXN0b21fc3RvcF93b3JkcyR3b3JkKSAlPiUNCiAgZmlsdGVyKCFwYWxhYnJhMiAlaW4lIGN1c3RvbV9zdG9wX3dvcmRzJHdvcmQpDQoNCiMgVW5pw7NuIGRlIGxhcyBwYWxhYnJhcyBwYXJhIGZvcm1hciBkZSBudWV2byBsb3MgYmlncmFtYXMNCmJpZ3JhbWFzIDwtIGJpZ3JhbWFzX3NlcGFyYWRvcyAlPiUNCiAgICAgICAgICAgIHVuaXRlKGJpZ3JhbWEsIHBhbGFicmExLCBwYWxhYnJhMiwgc2VwID0gIiAiKQ0KDQpiaWdyYW1hcyAlPiUgDQogIGNvdW50KGJpZ3JhbWEsIHNvcnQgPSBUUlVFKSAlPiUNCiAgaGVhZCgyMCkgJT4lIA0KICBrYmwoIA0KICAgICAgYWxpZ24gPSAiYyIsDQogICAgICBjYXB0aW9uID0gIlRva2VuaXphY2nDs24iLA0KICAgICAgKSAlPiUNCiAga2FibGVfc3R5bGluZygiaG92ZXIiLCBmdWxsX3dpZHRoID0gRikNCg0KYGBgDQoNClVuYSBmb3JtYSBtw6FzIHZpc3VhbCBlIGluZm9ybWF0aXZhIGRlIGFuYWxpemFyIGxhcyByZWxhY2lvbmVzIGVudHJlIGxhcyBwYWxhYnJhcyBlcyBtZWRpYW50ZSBlbCB1c28gZGUgcmVkZXMuDQoNCmBgYHtyfQ0KZ3JhcGggPC0gYmlncmFtYXMgJT4lDQogICAgICAgICBzZXBhcmF0ZShiaWdyYW1hLCBjKCJwYWxhYnJhMSIsICJwYWxhYnJhMiIpLCBzZXAgPSAiICIpICU+JSANCiAgICAgICAgIGNvdW50KHBhbGFicmExLCBwYWxhYnJhMiwgc29ydCA9IFRSVUUpICU+JQ0KICAgICAgICAgZmlsdGVyKG4gPiAxOCkgJT4lDQogIGdyYXBoX2Zyb21fZGF0YV9mcmFtZShkaXJlY3RlZCA9IEZBTFNFKQ0KDQpzZXQuc2VlZCgxMjMpDQoNCnBsb3QoZ3JhcGgsIHZlcnRleC5sYWJlbC5mb250ID0gMywNCiAgICAgdmVydGV4LmxhYmVsLmNvbG9yID0gImJsYWNrIiwNCiAgICAgdmVydGV4LmxhYmVsLmNleCA9IDAuOSwgZWRnZS5jb2xvciA9ICJncmF5ODUiKQ0KDQpgYGANCg0KUG9kZW1vcyBhcHJlY2lhciBxdWUgZGVudHJvIGRlbCB0b3AgMTAgZGUgbG9zIGJpZ3JhbWFzIGVzdMOhbjoNCg0KKiAqKnRhcmpldGEgY2l0aWJhbmFtZXgqKg0KKiAqKmJhbmNvIGF6dGVjYSoqDQoqICoqcMOpc2ltbyBzZXJ2aWNpbyoqDQoNClByb2JhYmxlbWVudGUsIGxhcyBxdWVqYXMgdGllbmVuIHF1ZSB2ZXIgY29uIGFsZ3VuYSB0YXJqZXRhIGRlIGNyw6lkaXRvLCBjb24gbGEgYXRlbmNpw7NuIGFsIHDDumJsaWNvIHRhbnRvIGVuIHN1Y3Vyc2FsLCB2w61hIHRlbGVmw7NuaWNhIHkgb25saW5lLg0KDQombmJzcDsNCg0KIyA8c3BhbiBzdHlsZT0iY29sb3I6cmdiKDAsIDAsIDIwNSkiPkFuw6FsaXNpcyBkZSBzZW50aW1pZW50b3M8L3NwYW4+DQoNCjxkaXYgY2xhc3M9dGV4dC1qdXN0aWZ5Pg0KRW4gZXN0YSDDumx0aW1hIHBhcnRlIGRlbCB0cmFiYWpvIHNlIGhhcsOhIGVsIGFuw6FsaXNpcyBkZSBzZW50aW1pZW50b3MsIGVzIGRlY2lyLCBzZSBjbGFzaWZpY2Fyw6FuIGxhcyBwYWxhYnJhcyBjb250ZW5pZGFzIGVuIGxvcyB0d2VldHMgZW4gdW5hIGRlIGxhcyBzaWd1aWVudGVzIGRvcyBjYXRlZ29yw61hczogcG9zaXRpdmEgbyBuZWdhdGl2YSwgYWRlbcOhcywgdXRpbGl6YW5kbyBlbCBsZXhpY29uICoqbnJjKiogc2UgcG9kcsOhbiB0YW1iacOpbiBjbGFzaWZpY2FyIGxhcyBwYWxhYnJhcyBlbiBsb3Mgc2lndWllbnRlcyBzZW50aW1pZW50b3M6IGNvbmZpYW56YSwgaXJhLCBkaXNndXN0bywgYXNvbWJybywgYWxlZ3LDrWEsIG1pZWRvLCB0cmlzdGV6YSB5IHByZW1vbmljacOzbi4NCg0KRWwgYXJjaGl2byBkZSBzZW50aW1pZW50b3Mgc2UgcHVlZGUgZGVzY2FyZ2FyIGRlbCByZXBvc2l0b3JpbyBbZ2l0aHViXShodHRwczovL2dpdGh1Yi5jb20vN1BhcnRpZGFzRGlnaXRhbC9BbmFUZXh0L3RyZWUvbWFzdGVyL2RhdG9zL2RpY2Npb25hcmlvcykgZGVsIHByb3llY3RvICoqN1BhcnRpZGFzRGlnaXRhbCoqIGVsYWJvcmFkbyBwb3IgbGEgVW5pdmVyc2lkYWQgZGUgVmFsbGFkb2xpZC4NCjwvZGl2Pg0KDQpgYGB7ciBtZXNzYWdlPUZBTFNFfQ0Kc291cmNlKCJnZXRfc2VudGltZW50cy5SIikNCnNlbnRpbWllbnRvcyA8LSByZWFkUkRTKGZpbGUgPSAic2VudGltaWVudG9zLnJkcyIpDQoNCnR3ZWV0cy50a25zIDwtIHR3ZWV0cy50a25zICU+JSANCiAgcmVuYW1lKHBhbGFicmEgPSB3b3JkKQ0KDQpgYGANCg0KVmVhbW9zIGxhIGNhbnRpZGFkIGRlIHBhbGFicmFzIGNsYXNpZmljYWRhcyBjb21vIHBvc2l0aXZhcyB5IG5lZ2F0aXZhcyBkZSB0b2RvcyBsb3MgdHdlZXRzDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0V9DQp0d2VldHMudGtucyAlPiUNCiAgcmlnaHRfam9pbihnZXRfc2VudGltZW50cygibnJjIikpICU+JQ0KICBmaWx0ZXIoIWlzLm5hKHNlbnRpbWllbnRvKSAmIHNlbnRpbWllbnRvID09ICJwb3NpdGl2byIgfCBzZW50aW1pZW50byA9PSAibmVnYXRpdm8iKSAlPiUNCiAgY291bnQoc2VudGltaWVudG8sIHNvcnQgPSBUUlVFKSAlPiUgDQogIGthYmxlKCANCiAgICAgIGZvcm1hdCA9ICJodG1sIiwNCiAgICAgIGRpZ2l0cyA9IDAsDQogICAgICBjYXB0aW9uID0gIlNlbnRpbWllbnRvcyBQb3NpdGl2b3MgLSBOZWdhdGl2b3MiLA0KICAgICAgZm9ybWF0LmFyZ3MgPSBsaXN0KGJpZy5tYXJrID0gIiwiKSwNCiAgICAgIHRhYmxlLmF0dHIgPSAic3R5bGU9J3dpZHRoOjUwJTsnIikgJT4lDQogIGthYmxlX3N0eWxpbmcoYm9vdHN0cmFwX29wdGlvbnMgPSBjKCJzdHJpcGVkIiwgImhvdmVyIikpDQoNCmBgYA0KDQpgYGB7ciBtZXNzYWdlPUZBTFNFfQ0KdHdlZXRzLnRrbnMgJT4lDQogIHJpZ2h0X2pvaW4oZ2V0X3NlbnRpbWVudHMoIm5yYyIpKSAlPiUNCiAgZmlsdGVyKCFpcy5uYShzZW50aW1pZW50bykmIHNlbnRpbWllbnRvID09ICJwb3NpdGl2byIgfCBzZW50aW1pZW50byA9PSAibmVnYXRpdm8iKSAlPiUgDQogIGNvdW50KHBhbGFicmEsIHNlbnRpbWllbnRvLCBzb3J0ID0gVFJVRSkgJT4lDQogIGFjYXN0KHBhbGFicmEgfiBzZW50aW1pZW50bywgdmFsdWUudmFyID0gIm4iLCBmaWxsID0gMCkgJT4lDQogIGNvbXBhcmlzb24uY2xvdWQoY29sb3JzID0gYygicmVkIiwiYmx1ZSIpLCANCiAgICAgICAgICAgICAgICAgICBtYXgud29yZHMgPSAzMDAsDQogICAgICAgICAgICAgICAgICAgdGl0bGUuc2l6ZSA9IDIpDQoNCmBgYA0KDQpFbiBlbCBjdWFkcm8gYW50ZXJpb3IsIHNlIGFwcmVjaWEgcXVlIHNlIGRldGVjdMOzIGNhc2kgbGEgbWlzbWEgY2FudGlkYWQgZGUgcGFsYWJyYXMgbmVnYXRpdmFzIHF1ZSBwb3NpdGl2YXMuIEFob3JhLCBzZSBtdWVzdHJhIGVsIGRldGFsbGUgKHRvcCAxNSkgZGUgbGFzIHBhbGFicmFzIGVuIGNhZGEgY2F0ZWdvcsOtYS4NCg0KYGBge3IgbWVzc2FnZT1GQUxTRX0NCnR3ZWV0cy50a25zICU+JQ0KICByaWdodF9qb2luKGdldF9zZW50aW1lbnRzKCJucmMiKSkgJT4lDQogIGZpbHRlcighaXMubmEoc2VudGltaWVudG8pJiBzZW50aW1pZW50byA9PSAicG9zaXRpdm8iIHwgc2VudGltaWVudG8gPT0gIm5lZ2F0aXZvIikgJT4lIA0KICBjb3VudChwYWxhYnJhLCBzZW50aW1pZW50bywgc2hvcnQgPSBUUlVFKSAlPiUgDQogIGdyb3VwX2J5KHNlbnRpbWllbnRvKSAlPiUgDQogIHRvcF9uKDE1KSAlPiUgDQogIHVuZ3JvdXAoKSAlPiUgDQogIG11dGF0ZShwYWxhYnJhID0gcmVvcmRlcihwYWxhYnJhLCBuKSkgJT4lIA0KICBnZ3Bsb3QoYWVzKHBhbGFicmEsIG4sIGZpbGwgPSBzZW50aW1pZW50bykpICsNCiAgZ2VvbV9jb2woc2hvdy5sZWdlbmQgPSBGQUxTRSkgKw0KICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjKCJkYXJrcmVkIiwgInN0ZWVsYmx1ZSIpKSArDQogIGdlb21fdGV4dChhZXMobGFiZWwgPSBuKSwgaGp1c3QgPSAxLjIsIGNvbG9yID0gIndoaXRlIikgKw0KICBmYWNldF93cmFwKH5zZW50aW1pZW50bywgc2NhbGVzID0gImZyZWVfeSIpICsNCiAgY29vcmRfZmxpcCgpICsNCiAgeGxhYihOVUxMKSArDQogIGxhYnModGl0bGUgPSAiRGV0YWxsZSBkZSBTZW50aW1pZW50b3MiKQ0KDQpgYGANCg0KRW4gbGFzIGdyw6FmaWNhcyBhbnRlcmlvcmVzLCBzZSBhcHJlY2lhbiBsYXMgcGFsYWJyYXMgcXVlIGxvcyB1c3VhcmlvcyBjYXB0dXJhcm9uIGVuIHN1cyB0d2VldHMgeSBxdWUgZWwgYWxnb3JpdG1vIGxvcyBjbGFzaWZpY8OzIGNvbW8gbmVnYXRpdm9zIG8gcG9zaXRpdm9zLiBMYXMgcGFsYWJyYXMgbmVnYXRpdmFzIHBhcmVjZSBxdWUgdGllbmVuIG3DoXMgcXVlIHZlciBjb24gcmVjbGFtb3MgeSBxdWVqYXMgZGUgbG9zIHVzdWFyaW9zIGNvbW8gcm9ibywgbWFsLCBwZW9yLCBjYW5jZWxhciwgcHJvYmxlbWEsIGVudHJlIG90cmFzLg0KDQpBaG9yYSwgdmVhbW9zIGVsIGNvbnRlbyBkZSBsb3MgZGVtw6FzIHNlbnRpbWllbnRvcyBwYXJhIGRhcm5vcyB1bmEgaWRlYSBkZWwgw6FuaW1vIGRlIGxvcyB1c3VhcmlvcyBjdWFuZG8gZXNjcmliaWVyb24gc3VzIHR3ZWV0cy4NCg0KYGBge3IgbWVzc2FnZT1GQUxTRX0NCnR3ZWV0cy50a25zICU+JQ0KICByaWdodF9qb2luKGdldF9zZW50aW1lbnRzKCJucmMiKSkgJT4lDQogIGZpbHRlcighaXMubmEoc2VudGltaWVudG8pKSAlPiUNCiAgY291bnQoc2VudGltaWVudG8sIHNvcnQgPSBUUlVFKSAlPiUgDQogIGthYmxlKCANCiAgICAgIGZvcm1hdCA9ICJodG1sIiwNCiAgICAgIGRpZ2l0cyA9IDAsDQogICAgICBjYXB0aW9uID0gIlJlc3VtZW4gZGUgU2VudGltaWVudG9zIiwNCiAgICAgIGZvcm1hdC5hcmdzID0gbGlzdChiaWcubWFyayA9ICIsIiksDQogICAgICB0YWJsZS5hdHRyID0gInN0eWxlPSd3aWR0aDo1MCU7JyIpICU+JQ0KICBrYWJsZV9zdHlsaW5nKGJvb3RzdHJhcF9vcHRpb25zID0gYygic3RyaXBlZCIsICJob3ZlciIpKQ0KDQpgYGANCg0KR3LDoWZpY2FtZW50ZToNCg0KYGBge3IgbWVzc2FnZT1GQUxTRX0NCnR3ZWV0cy50a25zICU+JQ0KICByaWdodF9qb2luKGdldF9zZW50aW1lbnRzKCJucmMiKSkgJT4lDQogIGZpbHRlcighaXMubmEoc2VudGltaWVudG8pKSAlPiUNCiAgZ3JvdXBfYnkoc2VudGltaWVudG8pICU+JQ0KICBjb3VudChzZW50aW1pZW50bywgc29ydCA9IFRSVUUpICU+JSANCiAgbXV0YXRlKHNlbnRpbWllbnRvID0gcmVvcmRlcihmYWN0b3Ioc2VudGltaWVudG8pLCBuKSkgJT4lDQogIGdncGxvdChhZXMocmVvcmRlcihzZW50aW1pZW50byxuKSwgbiwgZmlsbCA9IHNlbnRpbWllbnRvKSkgKw0KICBnZW9tX2NvbChzaG93LmxlZ2VuZCA9IEZBTFNFKSArDQogIGdlb21fdGV4dChhZXMobGFiZWwgPSBmb3JtYXQobiwgYmlnLm1hcmsgPSAiLCIpKSwgaGp1c3QgPSAxLjIsIGNvbG9yID0gImJsYWNrIikgKw0KICBjb29yZF9mbGlwKCkgKw0KICB4bGFiKE5VTEwpICsNCiAgbGFicyh0aXRsZSA9ICJEZXRhbGxlIGRlIEVtb2Npb25lcyIpDQoNCmBgYA0KDQpCYWphbmRvIG3DoXMgZWwgYW7DoWxpc2lzIHBhcmEgY29ub2NlciBsYXMgcGFsYWJyYXMgY29udGVuaWRhcyBlbiBjYWRhIGVtb2Npw7NuOg0KDQpgYGB7ciBtZXNzYWdlPUZBTFNFfQ0KdHdlZXRzLnRrbnMgJT4lDQogIHJpZ2h0X2pvaW4oZ2V0X3NlbnRpbWVudHMoIm5yYyIpKSAlPiUNCiAgZmlsdGVyKCFpcy5uYShzZW50aW1pZW50bykpICU+JSANCiAgY291bnQocGFsYWJyYSwgc2VudGltaWVudG8sIHNob3J0ID0gVFJVRSkgJT4lIA0KICBncm91cF9ieShzZW50aW1pZW50bykgJT4lIA0KICB0b3BfbigxNSkgJT4lIA0KICB1bmdyb3VwKCkgJT4lIA0KICBtdXRhdGUocGFsYWJyYSA9IHJlb3JkZXIocGFsYWJyYSwgbikpICU+JSANCiAgZ2dwbG90KGFlcyhwYWxhYnJhLCBuLCBmaWxsID0gc2VudGltaWVudG8pKSArDQogIGdlb21fY29sKHNob3cubGVnZW5kID0gRkFMU0UpICsNCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IG4pLCBoanVzdCA9IDEuMiwgY29sb3IgPSAid2hpdGUiKSArDQogIGZhY2V0X3dyYXAofnNlbnRpbWllbnRvLCBzY2FsZXMgPSAiZnJlZV95IikgKw0KICBjb29yZF9mbGlwKCkgKw0KICB4bGFiKE5VTEwpICsNCiAgbGFicyh0aXRsZSA9ICJEZXRhbGxlIGRlIEVtb2Npb25lcyIpDQpgYGANCg0KSGF5IGFsZ3VuYXMgcGFsYWJyYXMgcXVlIHNlIGNsYXNpZmljYW4gZW4gbcOhcyBkZSB1bmEgZW1vY2nDs24gY29tbywgcG9yIGVqZW1wbG8sIGRpbmVybyBxdWUgc2UgZW5jdWVudHJhIGVuIGxhcyBjYXRlZ29yw61hcyBkZSBhbGVncsOtYSwgYXNvbWJybyBlIGlyYS4gTG8gYW50ZXJpb3IsIHBvcnF1ZSBkZXBlbmRlIG11Y2hvIGRlbCBjb250ZXh0byBkZSBsYSBwYWxhYnJhLg0KDQpgYGB7ciBtZXNzYWdlPUZBTFNFfQ0KdHdlZXRzLnRrbnMgJT4lDQogIHJpZ2h0X2pvaW4oZ2V0X3NlbnRpbWVudHMoIm5yYyIpKSAlPiUNCiAgY291bnQocGFsYWJyYSwgc2VudGltaWVudG8sIHNvcnQgPSBUUlVFKSAlPiUgDQogIGZpbHRlcighaXMubmEoc2VudGltaWVudG8pLCBzZW50aW1pZW50byAhPSAicG9zaXRpdm8iLCBzZW50aW1pZW50byAhPSAibmVnYXRpdm8iKSAlPiUNCiAgYWNhc3QocGFsYWJyYSB+IHNlbnRpbWllbnRvLCB2YWx1ZS52YXIgPSAibiIsIGZpbGwgPSAwKSAlPiUNCiAgY29tcGFyaXNvbi5jbG91ZCh0aXRsZS5zaXplID0gMS41KQ0KDQpgYGANCg0KIyA8c3BhbiBzdHlsZT0iY29sb3I6cmdiKDAsIDAsIDIwNSkiPkNvbmNsdXNpb25lczwvc3Bhbj4NCg0KPGRpdiBjbGFzcz10ZXh0LWp1c3RpZnk+DQpEZXNwdcOpcyBkZSBoYWJlciByZWFsaXphZG8gZXN0ZSBwZXF1ZcOxbyBhbsOhbGlzaXMgcG9kZW1vcyBjb25jbHVpciBxdWUgbG9zIHVzdWFyaW9zIGRlIGxhIGluc3RpdHVjacOzbiBiYW5jYXJpYSBlbiBjdWVzdGnDs24gc2Ugc3VlbGVuIHF1ZWphciBwb3IgdGVtYXMgcmVsYWNpb25hZG9zIGNvbiBlcnJvcmVzIGVuIGFsZ3VuYXMgYXBsaWNhY2lvbmVzLCBzZSBzaW50ZW4gcm9iYWRvcywgcMOpc2ltbyBvIG1hbCBzZXJ2aWNpbywgY2FuY2VsYWNpb25lcyB5IGNvbiBhbGd1bm9zIG90cm9zIHByb2JsZW1hcyBxdWUgaGFuIHRlbmlkbyBjb21vIGNsaWVudGVzLg0KDQpFbCByZXN1bHRhZG8gZGUgaGFjZXIgdW4gYW7DoWxpc2lzIGRlIGVtb2Npb25lcyBub3MgaW5kaWNhIHF1ZSBsYSBtYXlvcsOtYSBkZSBsb3MgdXN1YXJpb3MgcXVlIGNvbWVudGFyb24gZW4gc3UgY3VlbnRhIGRlIHR3aXR0ZXIgdGVuw61hbiBlbW9jaW9uZXMgbmVnYXRpdmFzIGNvbW8gbWllZG8sIHRyaXN0ZXphLCBpcmEgeSBkaXNndXN0by4gTG8gYW50ZXJpb3IsIHBvcnF1ZSBmdWVyb24gbGFzIGVtb2Npb25lcyBjb24gbWF5b3IgZnJlY3VlbmNpYSwgc2luIGVtYmFyZ28sIGFsIGNsYXNpZmljYXIgdG9kYXMgbGFzIHBhbGFicmFzIGVuIHBvc2l0aXZhcyB5IG5lZ2F0aXZhcyBsYSBwcm9wb3JjacOzbiBlcyBjYXNpIGxhIG1pc21hLg0KDQpIYXkgcXVlIGFjbGFyYXIgcXVlIGRlcGVuZGUgZGVsIGxleGljb24gbyBkaWNjaW9uYXJpbyB1dGlsaXphZG8gbG9zIHJlc3VsdGFkb3Mgc2Vyw6FuIGRpc3RpbnRvcy4NCg0KQ29uIGVzdGUgY29ydG8gdHJhYmFqbyBwb2RlbW9zIGRhcm5vcyB1bmEgaWRlYSBkZSBsbyBxdWUgZWwgZXF1aXBvIGRlIGF0ZW5jacOzbiBhIGNsaWVudGVzIGRlbCBiYW5jbyBwb2Ryw61hIGhhY2VyIHBhcmEgZGlzbWludWlyIGxvcyByZWNsYW1vcyBvIHF1ZWphcyBjb21vLCBwb3IgZWplbXBsbywgcmVhbGl6YXIgZW5jdWVzdGFzIGEgbG9zIGNsaWVudGVzIHF1ZSB2YW4gZGlyZWN0YW1lbnRlIGEgc3VjdXJzYWwgeSBlbnZpYXIgZW5jdWVzdGFzIHbDrWEgY29ycmVvIGVsZWN0csOzbmljbyBkZXNwdcOpcyBkZSB1c2FyIGFsZ8O6biBjYWplcm8gYXV0b23DoXRpY28gbyBkaXJlY3RhbWVudGUgZW4gc3UgcMOhZ2luYSB3ZWIuIEFjdHVhbG1lbnRlLCBoYXkgZW1wcmVzYXMgcXVlIHNlIGRlZGljYW4gYSBwcm9wb3JjaW9uYXIgZXNlIHNlcnZpY2lvIHkgeWEgc29sbyBsZSBlbnRyZWdhbiBsb3MgcmVzdWx0YWRvcyBhbCBjbGllbnRlLCBlbCBjdWFsIGNvbiBzdSBlcXVpcG8gbyDDoXJlYSBkZSBhbmFsw610aWNhIHNlIGVuY2FyZ2EgZGUgcHJvY2VzYXIgZGljaGEgaW5mb3JtYWNpw7NuIHBhcmEgbGEgdG9tYSBkZSBkZWNpc2lvbmVzLg0KDQo8L2Rpdj4NCg0KDQoNCg0KDQo=