1. Introducción



El análisis de redes sociales es una herramienta útil para varios fines. Por ejemplo, este puede ser de ayuda para determinar qué tan bien funciona la relación con el público, o con determinados segmentos de él, puede servir también para medir el impacto de alguna determinada campaña o conocer la opinión respecto a alguna temática de interés. A través de la recopilación de los datos de cuentas y temáticas podemos utilizar distintos algoritmos para medir, por ejemplo, el sentir de las personas con respecto a un tema, las palabras más comunes que utiliza una determinada entidad, los discursos que lo hacen único e incluso crear grupos de temáticas.

En los capítulos 1 y 3 de mi curso de procesamiento del lenguaje natural (PLN) se explica cómo el mineo de texto, como parte del PLN, nos puede ayudar a destilar accionables útiles desde texto con el uso distintas técnicas. Es así que este cuaderno pretende presentar un corto caso de estudio, introductorio al análisis de texto, utilizando la API de Twitter para R.

2. Creación de una cuenta Developer en twitter

Para poder comenzar este análisis es preciso que creemos una cuenta de desarrollador (Developer) en Twitter. A continuación se explican los pasos.

  1. Visitar la página https://developer.twitter.com/ y hacer clic en la opción apply de la esquina superior derecha:


  1. Aquí escogemos la opción apply for a developer account (se nos pedirá el acceso a nuestra cuenta de twitter):


  1. Una vez aquí completaremos todos los datos que se nos pide acerca de los propósitos de uso. Usualmente se coloca que se utilizará la API con fines de investigación. Al finalizar aceptaremos los términos y condiciones.




  1. En este punto se nos pedirá validar nnuestro e-mail y esperar por el resultado de aprobación del proceso. Este tarda alrededor de dos días.




  1. Una vez que recibamos el correo de confirmación crearemos una app para poder utilizar la API con R. Para ello, ingresaremos a la página de nuestra cuenta de desarrollador (https://developer.twitter.com/en/portal/dashboard) y daremos clic en la opción Create project.


  1. Completamos la información acerca de nuestra API en los cuatro pasos solicitados.








  1. En el lado derecho de nuestro dashboard estará disponible nuestro nuevo proyecto, al dar clic en este, seleccionaremos el ícono de llave de la app que creamos para obtener nuestras credenciales.




  1. En este punto damos clic en View Keys y copiamos las credenciales.


  1. Finalmente, crearemos un ambiente de trabajo para guardar nuestras credenciales, con el objetivo de no llamarlas explícitamente cada vez que trabajemos con ellas:
# Nombre de la aplicación
appname = "NaturalLanguageData"
key = "XXXXX"
secret = "XXXXX"

# Token de conexión
twitter_token = rtweet::create_token(app = appname,
                                      consumer_key = key,
                                      consumer_secret = secret)

# Guardado del token
home_directory <- path.expand("C:/Users/hugo-/Downloads/MasterUtilitiesR/TwitterToken")
file_name <- file.path(home_directory,"twitter_token.rds")
saveRDS(twitter_token, file = file_name)

3. Extracción de tweets a través de R

Antes de comenzar, no debemos olvidar cargar nuestras credenciales (twitter_token):

# Cargado del token
home_directory = path.expand("C:/Users/hugo-/Downloads/MasterUtilitiesR/TwitterToken")
file_name = file.path(home_directory,"twitter_token.rds")
twitter_token = readRDS(file = file_name)

Para este caso de estudio utilizaremos las siguientes librerías:

  • rtweet: Conexión a la API de Twitter para la extracción de datos.
  • tidyverse: Manipulación, limpieza y visualización de datos estructurados.
  • tidytext: Manipulación de datos de texto.
  • waffle: Gráficos de “waffle” (como al alternativaa los gráficos de pastel).
  • tm: Funciones para limpieza y procesamiento de corpus texto.
  • stringi: Funciones de manipulación de texto.
  • stopwords: Palabras vacías en varios idiomas.
  • wordcloud2: Visualización de nubes de palabras.
  • udpipe: Librería para realizar varias tareas de NLP en varios idiomas.
  • syuzhet: Librería dedicada al análisis de sentimientos en varios idiomas.
  • parallel: Librería que habilita la paralelización de procesos, muy útil con syuzhet.
# Carga de librerías
library(rtweet)
library(tidyverse)
package 㤼㸱tidyverse㤼㸲 was built under R version 4.0.2Registered S3 methods overwritten by 'dbplyr':
  method         from
  print.tbl_lazy     
  print.tbl_sql      
-- Attaching packages --------------------------------------- tidyverse 1.3.0 --
v ggplot2 3.3.1     v purrr   0.3.4
v tibble  3.0.1     v dplyr   1.0.0
v tidyr   1.1.0     v stringr 1.4.0
v readr   1.3.1     v forcats 0.5.0
-- Conflicts ------------------------------------------ tidyverse_conflicts() --
x dplyr::filter()  masks stats::filter()
x purrr::flatten() masks rtweet::flatten()
x dplyr::lag()     masks stats::lag()
library(tidytext)
package 㤼㸱tidytext㤼㸲 was built under R version 4.0.2
library(waffle)
package 㤼㸱waffle㤼㸲 was built under R version 4.0.2
library(tm)
package 㤼㸱tm㤼㸲 was built under R version 4.0.2Loading required package: NLP

Attaching package: 㤼㸱NLP㤼㸲

The following object is masked from 㤼㸱package:ggplot2㤼㸲:

    annotate
library(stopwords)
package 㤼㸱stopwords㤼㸲 was built under R version 4.0.2
Attaching package: 㤼㸱stopwords㤼㸲

The following object is masked from 㤼㸱package:tm㤼㸲:

    stopwords
library(wordcloud2)
Registered S3 method overwritten by 'htmlwidgets':
  method           from         
  print.htmlwidget tools:rstudio
library(syuzhet)
package 㤼㸱syuzhet㤼㸲 was built under R version 4.0.2
Attaching package: 㤼㸱syuzhet㤼㸲

The following object is masked from 㤼㸱package:rtweet㤼㸲:

    get_tokens
library(stringi)
library(parallel)
library(udpipe)
package 㤼㸱udpipe㤼㸲 was built under R version 4.0.2Registered S3 method overwritten by 'data.table':
  method           from
  print.data.table     

Si queremos observar las tendencias en Twitter, simplemente debemos utilizar la función trends_available. En nuestro caso, filtraremos aquellas disponibles para Ecuador.

# Función de codificación geográfica 
trends_available() %>% filter(countryCode=="EC")
# Tendencias para el "woeid" de Ecuador
get_trends(woeid = 23424801)

Como se puede observar, una de las tendencias en Ecuador es #justiciaparaRobertoMalta, hashtag al cual podemos hechar un vistazo en Twitter:



Para extraer datos de este #hashtag utilizaremos la función search_tweets, especificando la ubicación del Ecuador, que se incluyan retweets y que los tweets estén en español:

# Extraer data de un hashtag o de un tema
tweets_ec = search_tweets(q = "#justiciaparaRobertoMalta", 
                          n = 10000, geocode = "-0.220102,-78.5119250,100mi", 
                          include_rts = T, lang="es")

Downloading [>----------------------------------------]   2%
Downloading [>----------------------------------------]   3%
Downloading [=>---------------------------------------]   4%
Downloading [=>---------------------------------------]   5%
Downloading [=>---------------------------------------]   6%
tweets_ec %>% head()

Así también, podemos extraer los tweets desde cuentas de usuario con la función get_timeline. Esta extraerá máximo los últimos 3200 tweets de la cuenta. En nuestro caso de estudio, utilizaremos los datos de la Fiscalía General del Estado (FGE):



# Extraer data de un usuario
user_tweets_ec = get_timeline(user = "@FiscaliaEcuador", n = 3200, lang="es")
saveRDS(user_tweets_ec, "CE_FGE/tweets_FGE.RDS")
user_tweets_ec %>% head()

4. Limpieza de datos

Cuando tratamos con datos de Twitter, la primera tarea que debemos llevar a cabo es la identificación de tweets orgánicos (originales), retweets y respuestas. Para esto, los datos recogidos por la API de twitter tienen las columnas is_retweet y reply_to_status_id.

# Tweets orgánicos
user_tweets_ec_organic = user_tweets_ec %>% filter(is_retweet==F, is.na(reply_to_status_id))

# Retweets
user_tweets_ec_retweets = user_tweets_ec %>% filter(is_retweet==T)

# Respuestas
user_tweets_ec_replies = user_tweets_ec %>% filter(!is.na(reply_to_status_id))

En los tweets orgánicos vamos a remover hipervínculos, menciones (@) y puntuación a través de las funciones str_replace_all, removeNumbers y removePunctuation y stri_trans_general con la finalidad de analizar únicamente los textos.

# Limpieza de textos
user_tweets_ec_organic = user_tweets_ec_organic %>% 
  mutate(text=str_replace_all(text, "https\\S*", "")) %>% # hipervínculos
  mutate(text=str_replace_all(text, "@\\S*", "")) %>% # menciones
  mutate(text=str_replace_all(text, "[\r\n\t]", "")) %>% # separadores
  mutate(text=removeNumbers(text)) %>% # números
  mutate(text=removePunctuation(text)) %>%  # puntuacion
  mutate(text=str_squish(text))

Acto seguido removeremos palabras vacías y tokenizaremos por palabras usando las funciones stopwords y unnest_tokens. Las palabras vacías son aquellas que no son útiles para el análisis y usualmente incluyen artículos, pronombres, preposiciones, etc. y la función stopwords nos ayuda a cargar distintos diccionarios de palabras vacías. La tokenización en cambio se refiere a la estructuración de los datos de texto en filas, donde cada fila será un token (tal token puede ser una palabra, un n-grama, una oración, etc.). Estos, entre varios otros conceptos son de útil conocimiento para tareas del procesamiento del lenguaje natural. Tales definiciones las podrás encontrar en el capítulo 2 de mi curso.

# Palabras vacías
stopwords_snow = stopwords("es", source = "snowball")
stopwords_iso = stopwords("es", source = "stopwords-iso")
stopwords_ntlk = stopwords("es", source = "nltk")
# Conteo de palabras
tweets = user_tweets_ec_organic %>%
  select(text) %>%
  unnest_tokens(token, text, to_lower = F)
tweets = tweets %>%
  filter(!token %in% c(stopwords_ntlk))

Una tarea adicional que se lleva a cabo en este tipo de análisis es el stemming o la lematización. Estos son métodos para reducir una palabra a su raíz o morfema con el objetivo de analizar variaciones de una palabra como una sola.

# Descarga de modelo preentrenado udpipe 
#udpipe::udpipe_download_model('spanish') # Descomentar al ejecutar por primera vez
model = udpipe_load_model("spanish-gsd-ud-2.4-190531.udpipe")
tweets_ann = as_tibble(udpipe_annotate(model, tweets$token))
# Stemming
tweets = tweets_ann %>% 
  select(token, lemma) %>% 
  filter(!is.na(lemma))

Debido a que al traer a la raíz se podrían generar otras palabras vacías que estaban conjugadas, realizaremos este procedimiento nuevamente y pasaremos a minúsculas las palabras:

# Conteo de palabras
tweets = tweets %>%
  mutate(lemma=tolower(lemma)) %>% 
  filter(!lemma %in% c(stopwords_ntlk))

Veamos el resultado de esta limpieza y procesamiento de los datos:

tweets %>% head(7)

5. Análisis descriptivo

Una vez que tenemos los datos extraídos limpios, realizaremos algunos análisis descriptivos para extraer conclusiones acerca del manejo de la cuenta de Twitter de la FGE:

Top tweets por conteo de likes

# Top de tweets por conteo de likes
user_tweets_ec = user_tweets_ec %>% arrange(desc(favorite_count))
user_tweets_ec %>% head(5) %>% select(text)

Top tweets por conteo de retweets

# Top de tweets por conteo de retweets
user_tweets_ec = user_tweets_ec %>% arrange(-retweet_count)
user_tweets_ec %>% head(5) %>% select(text)

Composición de los tweets

# Composición de los tweets
CountTweets = data.frame(tipo=c("orgánico","retweets","respuestas"),
                         conteo=c(nrow(user_tweets_ec_organic), 
                                  nrow(user_tweets_ec_retweets), 
                                  nrow(user_tweets_ec_replies)))
CountTweets = CountTweets %>% 
  mutate(porcentaje=round(conteo/sum(conteo)*100,0)) %>% 
  arrange(desc(conteo))
CountTweets
# Gráfico de la composición de tweets
w_vec = CountTweets$porcentaje
names(w_vec) = CountTweets$tipo
waffle(w_vec, rows = 10, title = 'FGE: Tweets por origen')

Evolución temporal de los tweets por día

# Gráfico de la evolución de tweets
ts_plot(user_tweets_ec, by="day", color="darkred") +
  labs(x = "Fecha", y = NULL, title = "Frecuencia de los tweets de la FGE", 
       subtitle = "Conteo de tweets agregado por día", caption = "Fuente: Twitter") +
  theme_bw()

Fuente de publicación de los tweets

# Fuente de los tweets
user_tweets_ec_sources = user_tweets_ec %>% 
  group_by(source) %>%
  summarize(conteo=n()) %>% 
  mutate(porcentaje=round(conteo/sum(conteo)*100,0)) %>% 
  arrange(desc(conteo))
`summarise()` ungrouping output (override with `.groups` argument)
user_tweets_ec_sources
# Gráfico de la fuente de los tweets
w_vec2 = user_tweets_ec_sources$porcentaje
names(w_vec2) = user_tweets_ec_sources$source
waffle(w_vec2, rows = 10, title = 'FGE: Tweets por fuente')

Hashtags más comunes

# Hashtags más comunes
data.frame(text=unlist(user_tweets_ec_organic$hashtags)) %>% 
  count(text, sort = TRUE) %>%
  top_n(15) %>%
  mutate(text = reorder(text, n)) %>%
  ggplot(aes(x = text, y = n)) +
  geom_col() +
  xlab(NULL) +
  coord_flip() +
  labs(y = "Frecuencia",
       x = "Hashtags",
       title = "Hashtags más frecuentes en la cuenta de Twitter de la FGE",
       subtitle = "Tweets orgánicos de la FGE")
Selecting by n

# Hashtags más comunes
data.frame(text=unlist(user_tweets_ec_organic$hashtags)) %>% 
  count(text, sort = TRUE) %>%
  mutate(text = reorder(text, n)) %>%
  select(word=text, freq=n) %>% 
  wordcloud2()

Palabras más usadas en los tweets

# Palabras más usadas
tweets %>% 
  count(lemma, sort = TRUE) %>%
  top_n(15) %>%
  mutate(lemma = reorder(lemma, n)) %>%
  ggplot(aes(x = lemma, y = n)) +
  geom_col() +
  xlab(NULL) +
  coord_flip() +
  labs(y = "Frecuencia",
       x = "Palabras",
       title = "Palabras más frecuentes en la cuenta de Twitter de la FGE")
Selecting by n

# Palabras más usadas
tweets %>% 
  count(lemma, sort = TRUE) %>%
  mutate(lemma = reorder(lemma, n)) %>%
  select(word=lemma, freq=n) %>% 
  wordcloud2()

6. Análisis de sentimientos

Cuando tenemos datos de texto podemos realizar además análisis de sentimientos. Acorde a Robinson y Silge (2019), cuando el ser humano lee un texto, entiende la intención emocional de una palabra para inferir si una porción de texto es positiva o negativa, o incluso podría reconocer miedo o disgusto. Este mismo acercamiento lo podemos realizar a través de técnicas de mineo de texto, de las cuales existen muchas alternativas.

En este caso de estudio usaremos una técnica de diccionarios y unigramas basada en el trabajo de Saif Mohammad y Peter Turney. A tal diccionario se lo llama “nrc” y lo que busca es etiquetar cada palabra en una de 10 emociones a través de un algoritmo de búsqueda intensiva. Este análisis se lo realiza a través de la función get_nrc_sentiment, optimizándola al paralelizarlo con el número de núcleos que tenga nuestro CPU.

# Creación del ambiente de paralelización
cl = makeCluster(detectCores()-1)
clusterExport(cl = cl, c("get_sentiment", "get_sent_values", "get_nrc_sentiment", "get_nrc_values", "parLapply"))

# Análisis de sentimientos
tweet_sentiment_nrc = get_nrc_sentiment(tweets$lemma,language = "spanish", cl=cl)
`filter_()` is deprecated as of dplyr 0.7.0.
Please use `filter()` instead.
See vignette('programming') for more help
This warning is displayed once every 8 hours.
Call `lifecycle::last_warnings()` to see where this warning was generated.
stopCluster(cl)

# Etiquetado de sentimientos
tweet_sentiment_nrc = cbind(tweets, tweet_sentiment_nrc)
tweet_sentiment_nrc %>% filter(rowSums(tweet_sentiment_nrc[,-c(1,2)]) > 0) %>% head()

Gráficamente, los sentimientos más recurrentes en los tweets de la FGE son:

# Frecuencia de sentimientos
sentimentscores = data.frame(colSums(tweet_sentiment_nrc %>% filter(lemma!="general") %>% 
                                       select(-token,-lemma)))
names(sentimentscores) = "Score"
sentimentscores = cbind("sentiment"=rownames(sentimentscores),sentimentscores)
rownames(sentimentscores) = NULL
sentimentscores = sentimentscores %>% 
  mutate(sentiment = recode(sentiment, 
                            "anger"="enfado",
                            "anticipation"="anticipación",
                            "disgust"="disgusto",
                            "fear"="miedo",
                            "joy"="alegría",
                            "negative"="negativo",
                            "positive"="positivo",
                            "sadness"="tristeza",
                            "surprise"="sorpresa",
                            "trust"="confianza"))
ggplot(data=sentimentscores,aes(x=sentiment,y=Score))+
  geom_bar(aes(fill=sentiment),stat = "identity")+
  xlab("Sentimientos")+ylab("Scores")+
  ggtitle("Sentimientos totales basados en scores")+
  theme(axis.text.x = element_text(angle=90),
        legend.position = "none")

Se puede notar en este gráfico que el sentimiento más frecuente en los tweets de la FGE es negativo (específicamente, el miedo), sus palabras asociadas se pueden ver gráficamente como:

tweet_sentiment_nrc %>% 
  filter(fear > 0) %>% 
  select(lemma) %>% 
  count(lemma) %>% 
  select(word=lemma, freq=n) %>% 
  wordcloud2()

7. Conclusión

En este corto caso de estudio se han mostrado muchas de las utilidades que nos presenta el PLN para el análisis de tweets. ¡Es tu turno ahora de demostrar lo aprendido y realizar inferencia sobre el análisis de alguno de los datasets que te será entregado!

8. Bibliografía

Kwartler, T. (2017), Text mining in practice with R.

Silge, J. & Robinson, D. (2017), Text Mining with R, a tidy approach.

LS0tDQp0aXRsZTogIkludHJvZHVjY2nDs24gYWwgYW7DoWxpc2lzIGRlIHJlZGVzIHNvY2lhbGVzIHVzYW5kbyBQcm9jZXNhbWllbnRvIGRlbCBMZW5ndWFqZSBOYXR1cmFsIg0Kc3VidGl0bGU6ICJDYXNvIHByw6FjdGljbzogTGEgRmlzY2Fsw61hIEdlbmVyYWwgZGVsIEVzdGFkbyBlbiBUd2l0dGVyIg0KYXV0aG9yOiBIdWdvIFBvcnJhcw0Kb3V0cHV0OiANCiAgaHRtbF9ub3RlYm9vazoNCiAgICBjc3M6IEVzdGlsb3MuY3NzDQogICAgdG9jOiB0cnVlDQogICAgdG9jX2RlcHRoOiAyDQogICAgdG9jX2Zsb2F0Og0KICAgICAgY29sbGFwc2VkOiB0cnVlDQogICAgICBzbW9vdGhfc2Nyb2xsOiBmYWxzZQ0KYmlibGlvZ3JhcGh5OiBCaWJsaW9ncmFmaWEuYmliDQpjc2w6IGNlcGFsLnhtbA0Kbm9jaXRlOiB8DQogIEBLd2FydGxlcjIwMTcsIEBTaWxnZTIwMTcNCi0tLQ0KDQojIDEuIEludHJvZHVjY2nDs24NCg0KPGJyPjwvYnI+DQo8Y2VudGVyPjxhPjxpbWcgd2lkdGg9IjgwJSIgc3JjPSJmaWdzL3R3aXR0ZXJfZGV2ZWxvcGVyL3JlZGVzLXNvY2lhbGVzLnBuZyI+PC9hPjwvY2VudGVyPg0KPGJyPjwvYnI+DQoNCkVsIGFuw6FsaXNpcyBkZSByZWRlcyBzb2NpYWxlcyBlcyB1bmEgaGVycmFtaWVudGEgw7p0aWwgcGFyYSB2YXJpb3MgZmluZXMuIFBvciBlamVtcGxvLCBlc3RlIHB1ZWRlIHNlciBkZSBheXVkYSBwYXJhIGRldGVybWluYXIgcXXDqSB0YW4gYmllbiBmdW5jaW9uYSBsYSByZWxhY2nDs24gY29uIGVsIHDDumJsaWNvLCBvIGNvbiBkZXRlcm1pbmFkb3Mgc2VnbWVudG9zIGRlIMOpbCwgcHVlZGUgc2VydmlyIHRhbWJpw6luIHBhcmEgbWVkaXIgZWwgaW1wYWN0byBkZSBhbGd1bmEgZGV0ZXJtaW5hZGEgY2FtcGHDsWEgbyBjb25vY2VyIGxhIG9waW5pw7NuIHJlc3BlY3RvIGEgYWxndW5hIHRlbcOhdGljYSBkZSBpbnRlcsOpcy4gQSB0cmF2w6lzIGRlIGxhIHJlY29waWxhY2nDs24gZGUgbG9zIGRhdG9zIGRlIGN1ZW50YXMgeSB0ZW3DoXRpY2FzIHBvZGVtb3MgdXRpbGl6YXIgZGlzdGludG9zIGFsZ29yaXRtb3MgcGFyYSBtZWRpciwgcG9yIGVqZW1wbG8sIGVsIHNlbnRpciBkZSBsYXMgcGVyc29uYXMgY29uIHJlc3BlY3RvIGEgdW4gdGVtYSwgbGFzIHBhbGFicmFzIG3DoXMgY29tdW5lcyBxdWUgdXRpbGl6YSB1bmEgZGV0ZXJtaW5hZGEgZW50aWRhZCwgbG9zIGRpc2N1cnNvcyBxdWUgbG8gaGFjZW4gw7puaWNvIGUgaW5jbHVzbyBjcmVhciBncnVwb3MgZGUgdGVtw6F0aWNhcy4NCg0KRW4gbG9zIGNhcMOtdHVsb3MgWzFdKGh0dHBzOi8vcnB1YnMuY29tL2h1Z29wb3JyYXMvbmxwX2NhcGl0dWxvMykgeSBbM10oaHR0cHM6Ly9ycHVicy5jb20vaHVnb3BvcnJhcy9ubHBfY2FwaXR1bG8zKSBkZSBtaSBjdXJzbyBkZSBwcm9jZXNhbWllbnRvIGRlbCBsZW5ndWFqZSBuYXR1cmFsIChQTE4pIHNlIGV4cGxpY2EgY8OzbW8gZWwgbWluZW8gZGUgdGV4dG8sIGNvbW8gcGFydGUgZGVsIFBMTiwgbm9zIHB1ZWRlIGF5dWRhciBhIGRlc3RpbGFyIGFjY2lvbmFibGVzIMO6dGlsZXMgZGVzZGUgdGV4dG8gY29uIGVsIHVzbyBkaXN0aW50YXMgdMOpY25pY2FzLiBFcyBhc8OtIHF1ZSBlc3RlIGN1YWRlcm5vIHByZXRlbmRlIHByZXNlbnRhciB1biBjb3J0byBjYXNvIGRlIGVzdHVkaW8sIGludHJvZHVjdG9yaW8gYWwgYW7DoWxpc2lzIGRlIHRleHRvLCB1dGlsaXphbmRvIGxhIEFQSSBkZSBUd2l0dGVyIHBhcmEgUi4NCg0KIyAyLiBDcmVhY2nDs24gZGUgdW5hIGN1ZW50YSBEZXZlbG9wZXIgZW4gdHdpdHRlcg0KDQpQYXJhIHBvZGVyIGNvbWVuemFyIGVzdGUgYW7DoWxpc2lzIGVzIHByZWNpc28gcXVlIGNyZWVtb3MgdW5hIGN1ZW50YSBkZSBkZXNhcnJvbGxhZG9yIChEZXZlbG9wZXIpIGVuIFR3aXR0ZXIuIEEgY29udGludWFjacOzbiBzZSBleHBsaWNhbiBsb3MgcGFzb3MuDQoNCmEuIFZpc2l0YXIgbGEgcMOhZ2luYSBodHRwczovL2RldmVsb3Blci50d2l0dGVyLmNvbS8geSBoYWNlciBjbGljIGVuIGxhIG9wY2nDs24gKiphcHBseSoqIGRlIGxhIGVzcXVpbmEgc3VwZXJpb3IgZGVyZWNoYToNCg0KPGJyPjwvYnI+DQo8Y2VudGVyPjxhPjxpbWcgd2lkdGg9IjgwJSIgc3JjPSJmaWdzL3R3aXR0ZXJfZGV2ZWxvcGVyL3N0ZXAxLnBuZyI+PC9hPjwvY2VudGVyPg0KPGJyPjwvYnI+DQoNCmIuIEFxdcOtIGVzY29nZW1vcyBsYSBvcGNpw7NuICoqYXBwbHkgZm9yIGEgZGV2ZWxvcGVyIGFjY291bnQqKiAoc2Ugbm9zIHBlZGlyw6EgZWwgYWNjZXNvIGEgbnVlc3RyYSBjdWVudGEgZGUgdHdpdHRlcik6DQoNCjxicj48L2JyPg0KPGNlbnRlcj48YT48aW1nIHdpZHRoPSI4MCUiIHNyYz0iZmlncy90d2l0dGVyX2RldmVsb3Blci9zdGVwMi5wbmciPjwvYT48L2NlbnRlcj4NCjxicj48L2JyPg0KDQpjLiBVbmEgdmV6IGFxdcOtIGNvbXBsZXRhcmVtb3MgdG9kb3MgbG9zIGRhdG9zIHF1ZSBzZSBub3MgcGlkZSBhY2VyY2EgZGUgbG9zIHByb3DDs3NpdG9zIGRlIHVzby4gVXN1YWxtZW50ZSBzZSBjb2xvY2EgcXVlIHNlIHV0aWxpemFyw6EgbGEgQVBJIGNvbiBmaW5lcyBkZSBpbnZlc3RpZ2FjacOzbi4gQWwgZmluYWxpemFyIGFjZXB0YXJlbW9zIGxvcyB0w6lybWlub3MgeSBjb25kaWNpb25lcy4NCg0KPGJyPjwvYnI+DQo8Y2VudGVyPjxhPjxpbWcgd2lkdGg9IjgwJSIgc3JjPSJmaWdzL3R3aXR0ZXJfZGV2ZWxvcGVyL3N0ZXAzLnBuZyI+PC9hPjwvY2VudGVyPg0KPGJyPjwvYnI+DQoNCjxicj48L2JyPg0KPGNlbnRlcj48YT48aW1nIHdpZHRoPSI4MCUiIHNyYz0iZmlncy90d2l0dGVyX2RldmVsb3Blci9zdGVwNC5wbmciPjwvYT48L2NlbnRlcj4NCjxicj48L2JyPg0KDQpkLiBFbiBlc3RlIHB1bnRvIHNlIG5vcyBwZWRpcsOhIHZhbGlkYXIgbm51ZXN0cm8gZS1tYWlsIHkgZXNwZXJhciBwb3IgZWwgcmVzdWx0YWRvIGRlIGFwcm9iYWNpw7NuIGRlbCBwcm9jZXNvLiBFc3RlIHRhcmRhIGFscmVkZWRvciBkZSBkb3MgZMOtYXMuDQoNCjxicj48L2JyPg0KPGNlbnRlcj48YT48aW1nIHdpZHRoPSI4MCUiIHNyYz0iZmlncy90d2l0dGVyX2RldmVsb3Blci9zdGVwNS5wbmciPjwvYT48L2NlbnRlcj4NCjxicj48L2JyPg0KDQo8YnI+PC9icj4NCjxjZW50ZXI+PGE+PGltZyB3aWR0aD0iODAlIiBzcmM9ImZpZ3MvdHdpdHRlcl9kZXZlbG9wZXIvc3RlcDYucG5nIj48L2E+PC9jZW50ZXI+DQo8YnI+PC9icj4NCg0KZS4gVW5hIHZleiBxdWUgcmVjaWJhbW9zIGVsIGNvcnJlbyBkZSBjb25maXJtYWNpw7NuIGNyZWFyZW1vcyB1bmEgKiphcHAqKiBwYXJhIHBvZGVyIHV0aWxpemFyIGxhIEFQSSBjb24gUi4gUGFyYSBlbGxvLCBpbmdyZXNhcmVtb3MgYSBsYSBww6FnaW5hIGRlIG51ZXN0cmEgY3VlbnRhIGRlIGRlc2Fycm9sbGFkb3IgKGh0dHBzOi8vZGV2ZWxvcGVyLnR3aXR0ZXIuY29tL2VuL3BvcnRhbC9kYXNoYm9hcmQpIHkgZGFyZW1vcyBjbGljIGVuIGxhIG9wY2nDs24gKipDcmVhdGUgcHJvamVjdCoqLg0KDQo8YnI+PC9icj4NCjxjZW50ZXI+PGE+PGltZyB3aWR0aD0iODAlIiBzcmM9ImZpZ3MvdHdpdHRlcl9kZXZlbG9wZXIvc3RlcDgucG5nIj48L2E+PC9jZW50ZXI+DQo8YnI+PC9icj4NCg0KZi4gQ29tcGxldGFtb3MgbGEgaW5mb3JtYWNpw7NuIGFjZXJjYSBkZSBudWVzdHJhIEFQSSBlbiBsb3MgY3VhdHJvIHBhc29zIHNvbGljaXRhZG9zLg0KDQo8YnI+PC9icj4NCjxjZW50ZXI+PGE+PGltZyB3aWR0aD0iODAlIiBzcmM9ImZpZ3MvdHdpdHRlcl9kZXZlbG9wZXIvc3RlcDkucG5nIj48L2E+PC9jZW50ZXI+DQo8YnI+PC9icj4NCg0KPGJyPjwvYnI+DQo8Y2VudGVyPjxhPjxpbWcgd2lkdGg9IjgwJSIgc3JjPSJmaWdzL3R3aXR0ZXJfZGV2ZWxvcGVyL3N0ZXAxMC5wbmciPjwvYT48L2NlbnRlcj4NCjxicj48L2JyPg0KDQo8YnI+PC9icj4NCjxjZW50ZXI+PGE+PGltZyB3aWR0aD0iODAlIiBzcmM9ImZpZ3MvdHdpdHRlcl9kZXZlbG9wZXIvc3RlcDExLnBuZyI+PC9hPjwvY2VudGVyPg0KPGJyPjwvYnI+DQoNCjxicj48L2JyPg0KPGNlbnRlcj48YT48aW1nIHdpZHRoPSI4MCUiIHNyYz0iZmlncy90d2l0dGVyX2RldmVsb3Blci9zdGVwMTIucG5nIj48L2E+PC9jZW50ZXI+DQo8YnI+PC9icj4NCg0KZy4gRW4gZWwgbGFkbyBkZXJlY2hvIGRlIG51ZXN0cm8gZGFzaGJvYXJkIGVzdGFyw6EgZGlzcG9uaWJsZSBudWVzdHJvIG51ZXZvIHByb3llY3RvLCBhbCBkYXIgY2xpYyBlbiBlc3RlLCBzZWxlY2Npb25hcmVtb3MgZWwgw61jb25vIGRlIGxsYXZlIGRlIGxhIGFwcCBxdWUgY3JlYW1vcyBwYXJhIG9idGVuZXIgbnVlc3RyYXMgY3JlZGVuY2lhbGVzLg0KDQo8YnI+PC9icj4NCjxjZW50ZXI+PGE+PGltZyB3aWR0aD0iODAlIiBzcmM9ImZpZ3MvdHdpdHRlcl9kZXZlbG9wZXIvc3RlcDEzLnBuZyI+PC9hPjwvY2VudGVyPg0KPGJyPjwvYnI+DQoNCjxicj48L2JyPg0KPGNlbnRlcj48YT48aW1nIHdpZHRoPSI4MCUiIHNyYz0iZmlncy90d2l0dGVyX2RldmVsb3Blci9zdGVwMTQucG5nIj48L2E+PC9jZW50ZXI+DQo8YnI+PC9icj4NCg0KaC4gRW4gZXN0ZSBwdW50byBkYW1vcyBjbGljIGVuICoqVmlldyBLZXlzKiogeSBjb3BpYW1vcyBsYXMgY3JlZGVuY2lhbGVzLg0KDQo8YnI+PC9icj4NCjxjZW50ZXI+PGE+PGltZyB3aWR0aD0iODAlIiBzcmM9ImZpZ3MvdHdpdHRlcl9kZXZlbG9wZXIvc3RlcDE1LnBuZyI+PC9hPjwvY2VudGVyPg0KPGJyPjwvYnI+DQoNCmkuIEZpbmFsbWVudGUsIGNyZWFyZW1vcyB1biBhbWJpZW50ZSBkZSB0cmFiYWpvIHBhcmEgZ3VhcmRhciBudWVzdHJhcyBjcmVkZW5jaWFsZXMsIGNvbiBlbCBvYmpldGl2byBkZSBubyBsbGFtYXJsYXMgZXhwbMOtY2l0YW1lbnRlIGNhZGEgdmV6IHF1ZSB0cmFiYWplbW9zIGNvbiBlbGxhczoNCg0KYGBge3IgZXZhbD1GQUxTRX0NCiMgTm9tYnJlIGRlIGxhIGFwbGljYWNpw7NuDQphcHBuYW1lID0gIk5hdHVyYWxMYW5ndWFnZURhdGEiDQprZXkgPSAiWFhYWFgiDQpzZWNyZXQgPSAiWFhYWFgiDQoNCiMgVG9rZW4gZGUgY29uZXhpw7NuDQp0d2l0dGVyX3Rva2VuID0gcnR3ZWV0OjpjcmVhdGVfdG9rZW4oYXBwID0gYXBwbmFtZSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29uc3VtZXJfa2V5ID0ga2V5LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb25zdW1lcl9zZWNyZXQgPSBzZWNyZXQpDQoNCiMgR3VhcmRhZG8gZGVsIHRva2VuDQpob21lX2RpcmVjdG9yeSA8LSBwYXRoLmV4cGFuZCgiQzovVXNlcnMvaHVnby0vRG93bmxvYWRzL01hc3RlclV0aWxpdGllc1IvVHdpdHRlclRva2VuIikNCmZpbGVfbmFtZSA8LSBmaWxlLnBhdGgoaG9tZV9kaXJlY3RvcnksInR3aXR0ZXJfdG9rZW4ucmRzIikNCnNhdmVSRFModHdpdHRlcl90b2tlbiwgZmlsZSA9IGZpbGVfbmFtZSkNCmBgYA0KDQoNCiMgMy4gRXh0cmFjY2nDs24gZGUgdHdlZXRzIGEgdHJhdsOpcyBkZSBSDQoNCkFudGVzIGRlIGNvbWVuemFyLCBubyBkZWJlbW9zIG9sdmlkYXIgY2FyZ2FyIG51ZXN0cmFzIGNyZWRlbmNpYWxlcyAodHdpdHRlcl90b2tlbik6DQoNCmBgYHtyfQ0KIyBDYXJnYWRvIGRlbCB0b2tlbg0KaG9tZV9kaXJlY3RvcnkgPSBwYXRoLmV4cGFuZCgiQzovVXNlcnMvaHVnby0vRG93bmxvYWRzL01hc3RlclV0aWxpdGllc1IvVHdpdHRlclRva2VuIikNCmZpbGVfbmFtZSA9IGZpbGUucGF0aChob21lX2RpcmVjdG9yeSwidHdpdHRlcl90b2tlbi5yZHMiKQ0KdHdpdHRlcl90b2tlbiA9IHJlYWRSRFMoZmlsZSA9IGZpbGVfbmFtZSkNCmBgYA0KDQpQYXJhIGVzdGUgY2FzbyBkZSBlc3R1ZGlvIHV0aWxpemFyZW1vcyBsYXMgc2lndWllbnRlcyBsaWJyZXLDrWFzOg0KDQorIHJ0d2VldDogQ29uZXhpw7NuIGEgbGEgQVBJIGRlIFR3aXR0ZXIgcGFyYSBsYSBleHRyYWNjacOzbiBkZSBkYXRvcy4NCisgdGlkeXZlcnNlOiBNYW5pcHVsYWNpw7NuLCBsaW1waWV6YSB5IHZpc3VhbGl6YWNpw7NuIGRlIGRhdG9zIGVzdHJ1Y3R1cmFkb3MuDQorIHRpZHl0ZXh0OiBNYW5pcHVsYWNpw7NuIGRlIGRhdG9zIGRlIHRleHRvLg0KKyB3YWZmbGU6IEdyw6FmaWNvcyBkZSAid2FmZmxlIiAoY29tbyBhbCBhbHRlcm5hdGl2YWEgbG9zIGdyw6FmaWNvcyBkZSBwYXN0ZWwpLg0KKyB0bTogRnVuY2lvbmVzIHBhcmEgbGltcGllemEgeSBwcm9jZXNhbWllbnRvIGRlIGNvcnB1cyB0ZXh0by4NCisgc3RyaW5naTogRnVuY2lvbmVzIGRlIG1hbmlwdWxhY2nDs24gZGUgdGV4dG8uDQorIHN0b3B3b3JkczogUGFsYWJyYXMgdmFjw61hcyBlbiB2YXJpb3MgaWRpb21hcy4NCisgd29yZGNsb3VkMjogVmlzdWFsaXphY2nDs24gZGUgbnViZXMgZGUgcGFsYWJyYXMuDQorIHVkcGlwZTogTGlicmVyw61hIHBhcmEgcmVhbGl6YXIgdmFyaWFzIHRhcmVhcyBkZSBOTFAgZW4gdmFyaW9zIGlkaW9tYXMuDQorIHN5dXpoZXQ6IExpYnJlcsOtYSBkZWRpY2FkYSBhbCBhbsOhbGlzaXMgZGUgc2VudGltaWVudG9zIGVuIHZhcmlvcyBpZGlvbWFzLg0KKyBwYXJhbGxlbDogTGlicmVyw61hIHF1ZSBoYWJpbGl0YSBsYSBwYXJhbGVsaXphY2nDs24gZGUgcHJvY2Vzb3MsIG11eSDDunRpbCBjb24gc3l1emhldC4NCg0KYGBge3J9DQojIENhcmdhIGRlIGxpYnJlcsOtYXMNCmxpYnJhcnkocnR3ZWV0KQ0KbGlicmFyeSh0aWR5dmVyc2UpDQpsaWJyYXJ5KHRpZHl0ZXh0KQ0KbGlicmFyeSh3YWZmbGUpDQpsaWJyYXJ5KHRtKQ0KbGlicmFyeShzdG9wd29yZHMpDQpsaWJyYXJ5KHdvcmRjbG91ZDIpDQpsaWJyYXJ5KHN5dXpoZXQpDQpsaWJyYXJ5KHN0cmluZ2kpDQpsaWJyYXJ5KHBhcmFsbGVsKQ0KbGlicmFyeSh1ZHBpcGUpDQpgYGANCg0KU2kgcXVlcmVtb3Mgb2JzZXJ2YXIgbGFzIHRlbmRlbmNpYXMgZW4gVHdpdHRlciwgc2ltcGxlbWVudGUgZGViZW1vcyB1dGlsaXphciBsYSBmdW5jacOzbiAqdHJlbmRzX2F2YWlsYWJsZSouIEVuIG51ZXN0cm8gY2FzbywgZmlsdHJhcmVtb3MgYXF1ZWxsYXMgZGlzcG9uaWJsZXMgcGFyYSBFY3VhZG9yLg0KDQpgYGB7cn0NCiMgRnVuY2nDs24gZGUgY29kaWZpY2FjacOzbiBnZW9ncsOhZmljYSANCnRyZW5kc19hdmFpbGFibGUoKSAlPiUgZmlsdGVyKGNvdW50cnlDb2RlPT0iRUMiKQ0KYGBgDQoNCmBgYHtyfQ0KIyBUZW5kZW5jaWFzIHBhcmEgZWwgIndvZWlkIiBkZSBFY3VhZG9yDQpnZXRfdHJlbmRzKHdvZWlkID0gMjM0MjQ4MDEpDQpgYGANCg0KQ29tbyBzZSBwdWVkZSBvYnNlcnZhciwgdW5hIGRlIGxhcyB0ZW5kZW5jaWFzIGVuIEVjdWFkb3IgZXMgI2p1c3RpY2lhcGFyYVJvYmVydG9NYWx0YSwgaGFzaHRhZyBhbCBjdWFsIHBvZGVtb3MgaGVjaGFyIHVuIHZpc3Rhem8gZW4gVHdpdHRlcjoNCg0KPGJyPjwvYnI+DQo8Y2VudGVyPjxhPjxpbWcgd2lkdGg9IjEwMCUiIHNyYz0iZmlncy90d2l0dGVyX2RldmVsb3Blci90cmVuZHNFY3VhZG9yMTQwOTIwLnBuZyI+PC9hPjwvY2VudGVyPg0KPGJyPjwvYnI+DQoNClBhcmEgZXh0cmFlciBkYXRvcyBkZSBlc3RlICNoYXNodGFnIHV0aWxpemFyZW1vcyBsYSBmdW5jacOzbiAqc2VhcmNoX3R3ZWV0cyosIGVzcGVjaWZpY2FuZG8gbGEgdWJpY2FjacOzbiBkZWwgRWN1YWRvciwgcXVlIHNlIGluY2x1eWFuIHJldHdlZXRzIHkgcXVlIGxvcyB0d2VldHMgZXN0w6luIGVuIGVzcGHDsW9sOg0KDQpgYGB7cn0NCiMgRXh0cmFlciBkYXRhIGRlIHVuIGhhc2h0YWcgbyBkZSB1biB0ZW1hDQp0d2VldHNfZWMgPSBzZWFyY2hfdHdlZXRzKHEgPSAiI2p1c3RpY2lhcGFyYVJvYmVydG9NYWx0YSIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICBuID0gMTAwMDAsIGdlb2NvZGUgPSAiLTAuMjIwMTAyLC03OC41MTE5MjUwLDEwMG1pIiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgIGluY2x1ZGVfcnRzID0gVCwgbGFuZz0iZXMiKQ0KdHdlZXRzX2VjICU+JSBoZWFkKCkNCmBgYA0KDQpBc8OtIHRhbWJpw6luLCBwb2RlbW9zIGV4dHJhZXIgbG9zIHR3ZWV0cyBkZXNkZSBjdWVudGFzIGRlIHVzdWFyaW8gY29uIGxhIGZ1bmNpw7NuICoqZ2V0X3RpbWVsaW5lKiouIEVzdGEgZXh0cmFlcsOhIG3DoXhpbW8gbG9zIMO6bHRpbW9zIDMyMDAgdHdlZXRzIGRlIGxhIGN1ZW50YS4gRW4gbnVlc3RybyBjYXNvIGRlIGVzdHVkaW8sIHV0aWxpemFyZW1vcyBsb3MgZGF0b3MgZGUgbGEgKipGaXNjYWzDrWEgR2VuZXJhbCBkZWwgRXN0YWRvKiogKEZHRSk6DQoNCjxicj48L2JyPg0KPGNlbnRlcj48YT48aW1nIHdpZHRoPSIxMDAlIiBzcmM9ImZpZ3MvdHdpdHRlcl9kZXZlbG9wZXIvdHdpdHRlckZHRS5wbmciPjwvYT48L2NlbnRlcj4NCjxicj48L2JyPg0KDQpgYGB7cn0NCiMgRXh0cmFlciBkYXRhIGRlIHVuIHVzdWFyaW8NCnVzZXJfdHdlZXRzX2VjID0gZ2V0X3RpbWVsaW5lKHVzZXIgPSAiQEZpc2NhbGlhRWN1YWRvciIsIG4gPSAzMjAwLCBsYW5nPSJlcyIpDQpzYXZlUkRTKHVzZXJfdHdlZXRzX2VjLCAiQ0VfRkdFL3R3ZWV0c19GR0UuUkRTIikNCnVzZXJfdHdlZXRzX2VjICU+JSBoZWFkKCkNCmBgYA0KDQojIDQuIExpbXBpZXphIGRlIGRhdG9zDQoNCkN1YW5kbyB0cmF0YW1vcyBjb24gZGF0b3MgZGUgVHdpdHRlciwgbGEgcHJpbWVyYSB0YXJlYSBxdWUgZGViZW1vcyBsbGV2YXIgYSBjYWJvIGVzIGxhIGlkZW50aWZpY2FjacOzbiBkZSB0d2VldHMgb3Jnw6FuaWNvcyAob3JpZ2luYWxlcyksIHJldHdlZXRzIHkgcmVzcHVlc3Rhcy4gUGFyYSBlc3RvLCBsb3MgZGF0b3MgcmVjb2dpZG9zIHBvciBsYSBBUEkgZGUgdHdpdHRlciB0aWVuZW4gbGFzIGNvbHVtbmFzICppc19yZXR3ZWV0KiB5ICpyZXBseV90b19zdGF0dXNfaWQqLg0KDQpgYGB7cn0NCiMgVHdlZXRzIG9yZ8Ohbmljb3MNCnVzZXJfdHdlZXRzX2VjX29yZ2FuaWMgPSB1c2VyX3R3ZWV0c19lYyAlPiUgZmlsdGVyKGlzX3JldHdlZXQ9PUYsIGlzLm5hKHJlcGx5X3RvX3N0YXR1c19pZCkpDQoNCiMgUmV0d2VldHMNCnVzZXJfdHdlZXRzX2VjX3JldHdlZXRzID0gdXNlcl90d2VldHNfZWMgJT4lIGZpbHRlcihpc19yZXR3ZWV0PT1UKQ0KDQojIFJlc3B1ZXN0YXMNCnVzZXJfdHdlZXRzX2VjX3JlcGxpZXMgPSB1c2VyX3R3ZWV0c19lYyAlPiUgZmlsdGVyKCFpcy5uYShyZXBseV90b19zdGF0dXNfaWQpKQ0KYGBgDQoNCkVuIGxvcyB0d2VldHMgb3Jnw6FuaWNvcyB2YW1vcyBhIHJlbW92ZXIgaGlwZXJ2w61uY3Vsb3MsIG1lbmNpb25lcyAoQCkgeSBwdW50dWFjacOzbiBhIHRyYXbDqXMgZGUgbGFzIGZ1bmNpb25lcyAqc3RyX3JlcGxhY2VfYWxsKiwgKnJlbW92ZU51bWJlcnMqIHkgKnJlbW92ZVB1bmN0dWF0aW9uKiB5ICpzdHJpX3RyYW5zX2dlbmVyYWwqIGNvbiBsYSBmaW5hbGlkYWQgZGUgYW5hbGl6YXIgw7puaWNhbWVudGUgbG9zIHRleHRvcy4NCg0KYGBge3J9DQojIExpbXBpZXphIGRlIHRleHRvcw0KdXNlcl90d2VldHNfZWNfb3JnYW5pYyA9IHVzZXJfdHdlZXRzX2VjX29yZ2FuaWMgJT4lIA0KICBtdXRhdGUodGV4dD1zdHJfcmVwbGFjZV9hbGwodGV4dCwgImh0dHBzXFxTKiIsICIiKSkgJT4lICMgaGlwZXJ2w61uY3Vsb3MNCiAgbXV0YXRlKHRleHQ9c3RyX3JlcGxhY2VfYWxsKHRleHQsICJAXFxTKiIsICIiKSkgJT4lICMgbWVuY2lvbmVzDQogIG11dGF0ZSh0ZXh0PXN0cl9yZXBsYWNlX2FsbCh0ZXh0LCAiW1xyXG5cdF0iLCAiIikpICU+JSAjIHNlcGFyYWRvcmVzDQogIG11dGF0ZSh0ZXh0PXJlbW92ZU51bWJlcnModGV4dCkpICU+JSAjIG7Dum1lcm9zDQogIG11dGF0ZSh0ZXh0PXJlbW92ZVB1bmN0dWF0aW9uKHRleHQpKSAlPiUgICMgcHVudHVhY2lvbg0KICBtdXRhdGUodGV4dD1zdHJfc3F1aXNoKHRleHQpKQ0KYGBgDQoNCkFjdG8gc2VndWlkbyByZW1vdmVyZW1vcyAqKnBhbGFicmFzIHZhY8OtYXMqKiB5ICoqdG9rZW5pemFyZW1vcyoqIHBvciBwYWxhYnJhcyB1c2FuZG8gbGFzIGZ1bmNpb25lcyAqc3RvcHdvcmRzKiB5ICp1bm5lc3RfdG9rZW5zKi4gTGFzIHBhbGFicmFzIHZhY8OtYXMgc29uIGFxdWVsbGFzIHF1ZSBubyBzb24gw7p0aWxlcyBwYXJhIGVsIGFuw6FsaXNpcyB5IHVzdWFsbWVudGUgaW5jbHV5ZW4gYXJ0w61jdWxvcywgcHJvbm9tYnJlcywgcHJlcG9zaWNpb25lcywgZXRjLiB5IGxhIGZ1bmNpw7NuICpzdG9wd29yZHMqIG5vcyBheXVkYSBhIGNhcmdhciBkaXN0aW50b3MgZGljY2lvbmFyaW9zIGRlIHBhbGFicmFzIHZhY8OtYXMuIExhIHRva2VuaXphY2nDs24gZW4gY2FtYmlvIHNlIHJlZmllcmUgYSBsYSBlc3RydWN0dXJhY2nDs24gZGUgbG9zIGRhdG9zIGRlIHRleHRvIGVuIGZpbGFzLCBkb25kZSBjYWRhIGZpbGEgc2Vyw6EgdW4gdG9rZW4gKHRhbCB0b2tlbiBwdWVkZSBzZXIgdW5hIHBhbGFicmEsIHVuIG4tZ3JhbWEsIHVuYSBvcmFjacOzbiwgZXRjLikuIEVzdG9zLCBlbnRyZSB2YXJpb3Mgb3Ryb3MgY29uY2VwdG9zIHNvbiBkZSDDunRpbCBjb25vY2ltaWVudG8gcGFyYSB0YXJlYXMgZGVsIHByb2Nlc2FtaWVudG8gZGVsIGxlbmd1YWplIG5hdHVyYWwuIFRhbGVzIGRlZmluaWNpb25lcyBsYXMgcG9kcsOhcyBlbmNvbnRyYXIgZW4gZWwgY2Fww610dWxvIFsyXShodHRwczovL3JwdWJzLmNvbS9odWdvcG9ycmFzL25scF9jYXBpdHVsbzNfMikgZGUgbWkgY3Vyc28uDQoNCmBgYHtyfQ0KIyBQYWxhYnJhcyB2YWPDrWFzDQpzdG9wd29yZHNfc25vdyA9IHN0b3B3b3JkcygiZXMiLCBzb3VyY2UgPSAic25vd2JhbGwiKQ0Kc3RvcHdvcmRzX2lzbyA9IHN0b3B3b3JkcygiZXMiLCBzb3VyY2UgPSAic3RvcHdvcmRzLWlzbyIpDQpzdG9wd29yZHNfbnRsayA9IHN0b3B3b3JkcygiZXMiLCBzb3VyY2UgPSAibmx0ayIpDQojIENvbnRlbyBkZSBwYWxhYnJhcw0KdHdlZXRzID0gdXNlcl90d2VldHNfZWNfb3JnYW5pYyAlPiUNCiAgc2VsZWN0KHRleHQpICU+JQ0KICB1bm5lc3RfdG9rZW5zKHRva2VuLCB0ZXh0LCB0b19sb3dlciA9IEYpDQp0d2VldHMgPSB0d2VldHMgJT4lDQogIGZpbHRlcighdG9rZW4gJWluJSBjKHN0b3B3b3Jkc19udGxrKSkNCmBgYA0KDQpVbmEgdGFyZWEgYWRpY2lvbmFsIHF1ZSBzZSBsbGV2YSBhIGNhYm8gZW4gZXN0ZSB0aXBvIGRlIGFuw6FsaXNpcyBlcyBlbCBzdGVtbWluZyBvIGxhIGxlbWF0aXphY2nDs24uIEVzdG9zIHNvbiBtw6l0b2RvcyBwYXJhIHJlZHVjaXIgdW5hIHBhbGFicmEgYSBzdSByYcOteiBvIG1vcmZlbWEgY29uIGVsIG9iamV0aXZvIGRlIGFuYWxpemFyIHZhcmlhY2lvbmVzIGRlIHVuYSBwYWxhYnJhIGNvbW8gdW5hIHNvbGEuDQoNCmBgYHtyfQ0KIyBEZXNjYXJnYSBkZSBtb2RlbG8gcHJlZW50cmVuYWRvIHVkcGlwZSANCiN1ZHBpcGU6OnVkcGlwZV9kb3dubG9hZF9tb2RlbCgnc3BhbmlzaCcpICMgRGVzY29tZW50YXIgYWwgZWplY3V0YXIgcG9yIHByaW1lcmEgdmV6DQptb2RlbCA9IHVkcGlwZV9sb2FkX21vZGVsKCJzcGFuaXNoLWdzZC11ZC0yLjQtMTkwNTMxLnVkcGlwZSIpDQp0d2VldHNfYW5uID0gYXNfdGliYmxlKHVkcGlwZV9hbm5vdGF0ZShtb2RlbCwgdHdlZXRzJHRva2VuKSkNCiMgU3RlbW1pbmcNCnR3ZWV0cyA9IHR3ZWV0c19hbm4gJT4lIA0KICBzZWxlY3QodG9rZW4sIGxlbW1hKSAlPiUgDQogIGZpbHRlcighaXMubmEobGVtbWEpKQ0KYGBgDQoNCkRlYmlkbyBhIHF1ZSBhbCB0cmFlciBhIGxhIHJhw616IHNlIHBvZHLDrWFuIGdlbmVyYXIgb3RyYXMgcGFsYWJyYXMgdmFjw61hcyBxdWUgZXN0YWJhbiBjb25qdWdhZGFzLCByZWFsaXphcmVtb3MgZXN0ZSBwcm9jZWRpbWllbnRvIG51ZXZhbWVudGUgeSBwYXNhcmVtb3MgYSBtaW7DunNjdWxhcyBsYXMgcGFsYWJyYXM6DQoNCmBgYHtyfQ0KIyBDb250ZW8gZGUgcGFsYWJyYXMNCnR3ZWV0cyA9IHR3ZWV0cyAlPiUNCiAgbXV0YXRlKGxlbW1hPXRvbG93ZXIobGVtbWEpKSAlPiUgDQogIGZpbHRlcighbGVtbWEgJWluJSBjKHN0b3B3b3Jkc19udGxrKSkNCmBgYA0KDQpWZWFtb3MgZWwgcmVzdWx0YWRvIGRlIGVzdGEgbGltcGllemEgeSBwcm9jZXNhbWllbnRvIGRlIGxvcyBkYXRvczoNCg0KYGBge3J9DQp0d2VldHMgJT4lIGhlYWQoNykNCmBgYA0KDQojIDUuIEFuw6FsaXNpcyBkZXNjcmlwdGl2bw0KDQpVbmEgdmV6IHF1ZSB0ZW5lbW9zIGxvcyBkYXRvcyBleHRyYcOtZG9zIGxpbXBpb3MsIHJlYWxpemFyZW1vcyBhbGd1bm9zIGFuw6FsaXNpcyBkZXNjcmlwdGl2b3MgcGFyYSBleHRyYWVyIGNvbmNsdXNpb25lcyBhY2VyY2EgZGVsIG1hbmVqbyBkZSBsYSBjdWVudGEgZGUgVHdpdHRlciBkZSBsYSBGR0U6DQoNCioqVG9wIHR3ZWV0cyBwb3IgY29udGVvIGRlIGxpa2VzKioNCg0KYGBge3J9DQojIFRvcCBkZSB0d2VldHMgcG9yIGNvbnRlbyBkZSBsaWtlcw0KdXNlcl90d2VldHNfZWMgPSB1c2VyX3R3ZWV0c19lYyAlPiUgYXJyYW5nZShkZXNjKGZhdm9yaXRlX2NvdW50KSkNCnVzZXJfdHdlZXRzX2VjICU+JSBoZWFkKDUpICU+JSBzZWxlY3QodGV4dCkNCmBgYA0KDQoqKlRvcCB0d2VldHMgcG9yIGNvbnRlbyBkZSByZXR3ZWV0cyoqDQoNCmBgYHtyfQ0KIyBUb3AgZGUgdHdlZXRzIHBvciBjb250ZW8gZGUgcmV0d2VldHMNCnVzZXJfdHdlZXRzX2VjID0gdXNlcl90d2VldHNfZWMgJT4lIGFycmFuZ2UoLXJldHdlZXRfY291bnQpDQp1c2VyX3R3ZWV0c19lYyAlPiUgaGVhZCg1KSAlPiUgc2VsZWN0KHRleHQpDQpgYGANCg0KKipDb21wb3NpY2nDs24gZGUgbG9zIHR3ZWV0cyoqDQoNCmBgYHtyfQ0KIyBDb21wb3NpY2nDs24gZGUgbG9zIHR3ZWV0cw0KQ291bnRUd2VldHMgPSBkYXRhLmZyYW1lKHRpcG89Yygib3Jnw6FuaWNvIiwicmV0d2VldHMiLCJyZXNwdWVzdGFzIiksDQogICAgICAgICAgICAgICAgICAgICAgICAgY29udGVvPWMobnJvdyh1c2VyX3R3ZWV0c19lY19vcmdhbmljKSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbnJvdyh1c2VyX3R3ZWV0c19lY19yZXR3ZWV0cyksIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5yb3codXNlcl90d2VldHNfZWNfcmVwbGllcykpKQ0KQ291bnRUd2VldHMgPSBDb3VudFR3ZWV0cyAlPiUgDQogIG11dGF0ZShwb3JjZW50YWplPXJvdW5kKGNvbnRlby9zdW0oY29udGVvKSoxMDAsMCkpICU+JSANCiAgYXJyYW5nZShkZXNjKGNvbnRlbykpDQpDb3VudFR3ZWV0cw0KYGBgDQoNCmBgYHtyfQ0KIyBHcsOhZmljbyBkZSBsYSBjb21wb3NpY2nDs24gZGUgdHdlZXRzDQp3X3ZlYyA9IENvdW50VHdlZXRzJHBvcmNlbnRhamUNCm5hbWVzKHdfdmVjKSA9IENvdW50VHdlZXRzJHRpcG8NCndhZmZsZSh3X3ZlYywgcm93cyA9IDEwLCB0aXRsZSA9ICdGR0U6IFR3ZWV0cyBwb3Igb3JpZ2VuJykNCmBgYA0KDQoqKkV2b2x1Y2nDs24gdGVtcG9yYWwgZGUgbG9zIHR3ZWV0cyBwb3IgZMOtYSoqDQoNCmBgYHtyfQ0KIyBHcsOhZmljbyBkZSBsYSBldm9sdWNpw7NuIGRlIHR3ZWV0cw0KdHNfcGxvdCh1c2VyX3R3ZWV0c19lYywgYnk9ImRheSIsIGNvbG9yPSJkYXJrcmVkIikgKw0KICBsYWJzKHggPSAiRmVjaGEiLCB5ID0gTlVMTCwgdGl0bGUgPSAiRnJlY3VlbmNpYSBkZSBsb3MgdHdlZXRzIGRlIGxhIEZHRSIsIA0KICAgICAgIHN1YnRpdGxlID0gIkNvbnRlbyBkZSB0d2VldHMgYWdyZWdhZG8gcG9yIGTDrWEiLCBjYXB0aW9uID0gIkZ1ZW50ZTogVHdpdHRlciIpICsNCiAgdGhlbWVfYncoKQ0KYGBgDQoNCioqRnVlbnRlIGRlIHB1YmxpY2FjacOzbiBkZSBsb3MgdHdlZXRzKioNCg0KYGBge3J9DQojIEZ1ZW50ZSBkZSBsb3MgdHdlZXRzDQp1c2VyX3R3ZWV0c19lY19zb3VyY2VzID0gdXNlcl90d2VldHNfZWMgJT4lIA0KICBncm91cF9ieShzb3VyY2UpICU+JQ0KICBzdW1tYXJpemUoY29udGVvPW4oKSkgJT4lIA0KICBtdXRhdGUocG9yY2VudGFqZT1yb3VuZChjb250ZW8vc3VtKGNvbnRlbykqMTAwLDApKSAlPiUgDQogIGFycmFuZ2UoZGVzYyhjb250ZW8pKQ0KdXNlcl90d2VldHNfZWNfc291cmNlcw0KYGBgDQoNCmBgYHtyfQ0KIyBHcsOhZmljbyBkZSBsYSBmdWVudGUgZGUgbG9zIHR3ZWV0cw0Kd192ZWMyID0gdXNlcl90d2VldHNfZWNfc291cmNlcyRwb3JjZW50YWplDQpuYW1lcyh3X3ZlYzIpID0gdXNlcl90d2VldHNfZWNfc291cmNlcyRzb3VyY2UNCndhZmZsZSh3X3ZlYzIsIHJvd3MgPSAxMCwgdGl0bGUgPSAnRkdFOiBUd2VldHMgcG9yIGZ1ZW50ZScpDQpgYGANCg0KKipIYXNodGFncyBtw6FzIGNvbXVuZXMqKg0KDQpgYGB7cn0NCiMgSGFzaHRhZ3MgbcOhcyBjb211bmVzDQpkYXRhLmZyYW1lKHRleHQ9dW5saXN0KHVzZXJfdHdlZXRzX2VjX29yZ2FuaWMkaGFzaHRhZ3MpKSAlPiUgDQogIGNvdW50KHRleHQsIHNvcnQgPSBUUlVFKSAlPiUNCiAgdG9wX24oMTUpICU+JQ0KICBtdXRhdGUodGV4dCA9IHJlb3JkZXIodGV4dCwgbikpICU+JQ0KICBnZ3Bsb3QoYWVzKHggPSB0ZXh0LCB5ID0gbikpICsNCiAgZ2VvbV9jb2woKSArDQogIHhsYWIoTlVMTCkgKw0KICBjb29yZF9mbGlwKCkgKw0KICBsYWJzKHkgPSAiRnJlY3VlbmNpYSIsDQogICAgICAgeCA9ICJIYXNodGFncyIsDQogICAgICAgdGl0bGUgPSAiSGFzaHRhZ3MgbcOhcyBmcmVjdWVudGVzIGVuIGxhIGN1ZW50YSBkZSBUd2l0dGVyIGRlIGxhIEZHRSIsDQogICAgICAgc3VidGl0bGUgPSAiVHdlZXRzIG9yZ8Ohbmljb3MgZGUgbGEgRkdFIikNCmBgYA0KDQpgYGB7cn0NCiMgSGFzaHRhZ3MgbcOhcyBjb211bmVzDQpkYXRhLmZyYW1lKHRleHQ9dW5saXN0KHVzZXJfdHdlZXRzX2VjX29yZ2FuaWMkaGFzaHRhZ3MpKSAlPiUgDQogIGNvdW50KHRleHQsIHNvcnQgPSBUUlVFKSAlPiUNCiAgbXV0YXRlKHRleHQgPSByZW9yZGVyKHRleHQsIG4pKSAlPiUNCiAgc2VsZWN0KHdvcmQ9dGV4dCwgZnJlcT1uKSAlPiUgDQogIHdvcmRjbG91ZDIoKQ0KYGBgDQoNCioqUGFsYWJyYXMgbcOhcyB1c2FkYXMgZW4gbG9zIHR3ZWV0cyoqDQoNCmBgYHtyfQ0KIyBQYWxhYnJhcyBtw6FzIHVzYWRhcw0KdHdlZXRzICU+JSANCiAgY291bnQobGVtbWEsIHNvcnQgPSBUUlVFKSAlPiUNCiAgdG9wX24oMTUpICU+JQ0KICBtdXRhdGUobGVtbWEgPSByZW9yZGVyKGxlbW1hLCBuKSkgJT4lDQogIGdncGxvdChhZXMoeCA9IGxlbW1hLCB5ID0gbikpICsNCiAgZ2VvbV9jb2woKSArDQogIHhsYWIoTlVMTCkgKw0KICBjb29yZF9mbGlwKCkgKw0KICBsYWJzKHkgPSAiRnJlY3VlbmNpYSIsDQogICAgICAgeCA9ICJQYWxhYnJhcyIsDQogICAgICAgdGl0bGUgPSAiUGFsYWJyYXMgbcOhcyBmcmVjdWVudGVzIGVuIGxhIGN1ZW50YSBkZSBUd2l0dGVyIGRlIGxhIEZHRSIpDQpgYGANCg0KYGBge3J9DQojIFBhbGFicmFzIG3DoXMgdXNhZGFzDQp0d2VldHMgJT4lIA0KICBjb3VudChsZW1tYSwgc29ydCA9IFRSVUUpICU+JQ0KICBtdXRhdGUobGVtbWEgPSByZW9yZGVyKGxlbW1hLCBuKSkgJT4lDQogIHNlbGVjdCh3b3JkPWxlbW1hLCBmcmVxPW4pICU+JSANCiAgd29yZGNsb3VkMigpDQpgYGANCg0KIyA2LiBBbsOhbGlzaXMgZGUgc2VudGltaWVudG9zDQoNCkN1YW5kbyB0ZW5lbW9zIGRhdG9zIGRlIHRleHRvIHBvZGVtb3MgcmVhbGl6YXIgYWRlbcOhcyBhbsOhbGlzaXMgZGUgc2VudGltaWVudG9zLiBBY29yZGUgYSBSb2JpbnNvbiB5IFNpbGdlICgyMDE5KSwgY3VhbmRvIGVsIHNlciBodW1hbm8gbGVlIHVuIHRleHRvLCBlbnRpZW5kZSBsYSBpbnRlbmNpw7NuIGVtb2Npb25hbCBkZSB1bmEgcGFsYWJyYSBwYXJhIGluZmVyaXIgc2kgdW5hIHBvcmNpw7NuIGRlIHRleHRvIGVzIHBvc2l0aXZhIG8gbmVnYXRpdmEsIG8gaW5jbHVzbyBwb2Ryw61hIHJlY29ub2NlciBtaWVkbyBvIGRpc2d1c3RvLiBFc3RlIG1pc21vIGFjZXJjYW1pZW50byBsbyBwb2RlbW9zIHJlYWxpemFyIGEgdHJhdsOpcyBkZSB0w6ljbmljYXMgZGUgbWluZW8gZGUgdGV4dG8sIGRlIGxhcyBjdWFsZXMgZXhpc3RlbiBtdWNoYXMgYWx0ZXJuYXRpdmFzLg0KDQpFbiBlc3RlIGNhc28gZGUgZXN0dWRpbyB1c2FyZW1vcyB1bmEgdMOpY25pY2EgZGUgZGljY2lvbmFyaW9zIHkgdW5pZ3JhbWFzIGJhc2FkYSBlbiBlbCB0cmFiYWpvIGRlIFtTYWlmIE1vaGFtbWFkIHkgUGV0ZXIgVHVybmV5XShodHRwOi8vc2FpZm1vaGFtbWFkLmNvbS9XZWJQYWdlcy9OUkMtRW1vdGlvbi1MZXhpY29uLmh0bSkuIEEgdGFsIGRpY2Npb25hcmlvIHNlIGxvIGxsYW1hICJucmMiIHkgbG8gcXVlIGJ1c2NhIGVzIGV0aXF1ZXRhciBjYWRhIHBhbGFicmEgZW4gdW5hIGRlIDEwIGVtb2Npb25lcyBhIHRyYXbDqXMgZGUgdW4gYWxnb3JpdG1vIGRlIGLDunNxdWVkYSBpbnRlbnNpdmEuIEVzdGUgYW7DoWxpc2lzIHNlIGxvIHJlYWxpemEgYSB0cmF2w6lzIGRlIGxhIGZ1bmNpw7NuICpnZXRfbnJjX3NlbnRpbWVudCosIG9wdGltaXrDoW5kb2xhIGFsIHBhcmFsZWxpemFybG8gY29uIGVsIG7Dum1lcm8gZGUgbsO6Y2xlb3MgcXVlIHRlbmdhIG51ZXN0cm8gQ1BVLg0KDQpgYGB7cn0NCiMgQ3JlYWNpw7NuIGRlbCBhbWJpZW50ZSBkZSBwYXJhbGVsaXphY2nDs24NCmNsID0gbWFrZUNsdXN0ZXIoZGV0ZWN0Q29yZXMoKS0xKQ0KY2x1c3RlckV4cG9ydChjbCA9IGNsLCBjKCJnZXRfc2VudGltZW50IiwgImdldF9zZW50X3ZhbHVlcyIsICJnZXRfbnJjX3NlbnRpbWVudCIsICJnZXRfbnJjX3ZhbHVlcyIsICJwYXJMYXBwbHkiKSkNCg0KIyBBbsOhbGlzaXMgZGUgc2VudGltaWVudG9zDQp0d2VldF9zZW50aW1lbnRfbnJjID0gZ2V0X25yY19zZW50aW1lbnQodHdlZXRzJGxlbW1hLGxhbmd1YWdlID0gInNwYW5pc2giLCBjbD1jbCkNCnN0b3BDbHVzdGVyKGNsKQ0KDQojIEV0aXF1ZXRhZG8gZGUgc2VudGltaWVudG9zDQp0d2VldF9zZW50aW1lbnRfbnJjID0gY2JpbmQodHdlZXRzLCB0d2VldF9zZW50aW1lbnRfbnJjKQ0KdHdlZXRfc2VudGltZW50X25yYyAlPiUgZmlsdGVyKHJvd1N1bXModHdlZXRfc2VudGltZW50X25yY1ssLWMoMSwyKV0pID4gMCkgJT4lIGhlYWQoKQ0KYGBgDQoNCkdyw6FmaWNhbWVudGUsIGxvcyBzZW50aW1pZW50b3MgbcOhcyByZWN1cnJlbnRlcyBlbiBsb3MgdHdlZXRzIGRlIGxhIEZHRSBzb246DQoNCmBgYHtyfQ0KIyBGcmVjdWVuY2lhIGRlIHNlbnRpbWllbnRvcw0Kc2VudGltZW50c2NvcmVzID0gZGF0YS5mcmFtZShjb2xTdW1zKHR3ZWV0X3NlbnRpbWVudF9ucmMgJT4lIGZpbHRlcihsZW1tYSE9ImdlbmVyYWwiKSAlPiUgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZWxlY3QoLXRva2VuLC1sZW1tYSkpKQ0KbmFtZXMoc2VudGltZW50c2NvcmVzKSA9ICJTY29yZSINCnNlbnRpbWVudHNjb3JlcyA9IGNiaW5kKCJzZW50aW1lbnQiPXJvd25hbWVzKHNlbnRpbWVudHNjb3Jlcyksc2VudGltZW50c2NvcmVzKQ0Kcm93bmFtZXMoc2VudGltZW50c2NvcmVzKSA9IE5VTEwNCnNlbnRpbWVudHNjb3JlcyA9IHNlbnRpbWVudHNjb3JlcyAlPiUgDQogIG11dGF0ZShzZW50aW1lbnQgPSByZWNvZGUoc2VudGltZW50LCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAiYW5nZXIiPSJlbmZhZG8iLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICJhbnRpY2lwYXRpb24iPSJhbnRpY2lwYWNpw7NuIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAiZGlzZ3VzdCI9ImRpc2d1c3RvIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAiZmVhciI9Im1pZWRvIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAiam95Ij0iYWxlZ3LDrWEiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICJuZWdhdGl2ZSI9Im5lZ2F0aXZvIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAicG9zaXRpdmUiPSJwb3NpdGl2byIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgInNhZG5lc3MiPSJ0cmlzdGV6YSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgInN1cnByaXNlIj0ic29ycHJlc2EiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICJ0cnVzdCI9ImNvbmZpYW56YSIpKQ0KYGBgDQoNCg0KYGBge3J9DQpnZ3Bsb3QoZGF0YT1zZW50aW1lbnRzY29yZXMsYWVzKHg9c2VudGltZW50LHk9U2NvcmUpKSsNCiAgZ2VvbV9iYXIoYWVzKGZpbGw9c2VudGltZW50KSxzdGF0ID0gImlkZW50aXR5IikrDQogIHhsYWIoIlNlbnRpbWllbnRvcyIpK3lsYWIoIlNjb3JlcyIpKw0KICBnZ3RpdGxlKCJTZW50aW1pZW50b3MgdG90YWxlcyBiYXNhZG9zIGVuIHNjb3JlcyIpKw0KICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZT05MCksDQogICAgICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikNCmBgYA0KDQpTZSBwdWVkZSBub3RhciBlbiBlc3RlIGdyw6FmaWNvIHF1ZSBlbCBzZW50aW1pZW50byBtw6FzIGZyZWN1ZW50ZSBlbiBsb3MgdHdlZXRzIGRlIGxhIEZHRSBlcyBuZWdhdGl2byAoZXNwZWPDrWZpY2FtZW50ZSwgZWwgbWllZG8pLCBzdXMgcGFsYWJyYXMgYXNvY2lhZGFzIHNlIHB1ZWRlbiB2ZXIgZ3LDoWZpY2FtZW50ZSBjb21vOg0KDQpgYGB7cn0NCnR3ZWV0X3NlbnRpbWVudF9ucmMgJT4lIA0KICBmaWx0ZXIoZmVhciA+IDApICU+JSANCiAgc2VsZWN0KGxlbW1hKSAlPiUgDQogIGNvdW50KGxlbW1hKSAlPiUgDQogIHNlbGVjdCh3b3JkPWxlbW1hLCBmcmVxPW4pICU+JSANCiAgd29yZGNsb3VkMigpDQpgYGANCg0KDQojIDcuIENvbmNsdXNpw7NuDQoNCkVuIGVzdGUgY29ydG8gY2FzbyBkZSBlc3R1ZGlvIHNlIGhhbiBtb3N0cmFkbyBtdWNoYXMgZGUgbGFzIHV0aWxpZGFkZXMgcXVlIG5vcyBwcmVzZW50YSBlbCBQTE4gcGFyYSBlbCBhbsOhbGlzaXMgZGUgdHdlZXRzLiDCoUVzIHR1IHR1cm5vIGFob3JhIGRlIGRlbW9zdHJhciBsbyBhcHJlbmRpZG8geSByZWFsaXphciBpbmZlcmVuY2lhIHNvYnJlIGVsIGFuw6FsaXNpcyBkZSBhbGd1bm8gZGUgbG9zIGRhdGFzZXRzIHF1ZSB0ZSBzZXLDoSBlbnRyZWdhZG8hDQoNCmBgYHtyIGluY2x1ZGU9RkFMU0UsIGV2YWw9RkFMU0V9DQpsaWJyYXJ5KHJ0d2VldCkNCnR3ZWV0c19sZW5pbiA9IGdldF90aW1lbGluZSh1c2VyID0gIkBMZW5pbiIsIG4gPSAzMjAwLCBsYW5nPSJlcyIpDQp0d2VldHNfbGFzc28gPSBnZXRfdGltZWxpbmUodXNlciA9ICJATGFzc29HdWlsbGVybW8iLCBuID0gMzIwMCwgbGFuZz0iZXMiKQ0KdHdlZXRzX2FiZGFsYSA9IGdldF90aW1lbGluZSh1c2VyID0gIkBhYmRhbGFidWNhcmFtIiwgbiA9IDMyMDAsIGxhbmc9ImVzIikNCnNhdmVSRFModHdlZXRzX2xlbmluLCAiQ0VfRkdFL3R3ZWV0c19sZW5pbi5SRFMiKQ0Kc2F2ZVJEUyh0d2VldHNfbGFzc28sICJDRV9GR0UvdHdlZXRzX2xhc3NvLlJEUyIpDQpzYXZlUkRTKHR3ZWV0c19hYmRhbGEsICJDRV9GR0UvdHdlZXRzX2FiZGFsYS5SRFMiKQ0KYGBgDQoNCg0KIyA4LiBCaWJsaW9ncmFmw61hDQo=