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.
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=