El mercado global de datos, en cifras reales
Salarios, roles y geografía de la industria de datos — y, al final,
cómo se compara nuestro grupo
library(tidyverse)
library(scales) # formatea números como moneda/porcentaje en las etiquetas de los gráficos
library(showtext) # dibuja el texto de los gráficos con tipografías de Google Fonts
library(sysfonts) # registra la tipografía antes de que showtext la use
library(countrycode) # traduce códigos de país (ISO de 2 letras) a nombres legibles, en español
library(DT) # tablas HTML interactivas: buscables, ordenables y con color por celda
library(leaflet) # mapas interactivos (zoom, paneo, tooltips) directo en el HTML
library(sf) # representa los países como polígonos (datos espaciales), para el mapa
library(rnaturalearth) # trae los polígonos reales de los países del mundo
library(plotly) # convierte un gráfico de ggplot en uno interactivo (hover, zoom, panning)
library(kableExtra) # da estilo editorial a las tablas estáticas (encabezado con color, hover, resaltar filas)
# rnaturalearthdata solo necesita estar instalado -- guarda los datos que
# rnaturalearth::ne_countries() usa por dentro; no hace falta cargarlo con library().
fuente_editorial <- "Fira Sans"
font_add_google(name = fuente_editorial, family = fuente_editorial)
showtext_auto()
# Rutas RELATIVAS a la ubicación de este .Rmd -- knitr ejecuta cada informe
# desde su propia carpeta, así que "../" sube a "Codigos/" y de ahí bajamos a
# las carpetas de datos. Con esto el informe corre en CUALQUIER computador que
# tenga el proyecto, no solo en el equipo donde se escribió: reproducibilidad
# de verdad (justo el principio que predica la introducción).
estudiantes <- read_csv(
"../Clase 2 - preparar datos de estudiantes/output/encuesta_estudiantes_consolidada_limpia.csv",
show_col_types = FALSE
)
salarios_mercado <- read_csv(
"../Complementos y recursos/Datos de salarios 2025/salaries-selected-columns.csv",
show_col_types = FALSE
)
# Misma paleta de todo el curso (Clase 2 en adelante) -- se define una
# sola vez aquí y se reutiliza en cada gráfico del informe.
morado <- "#351A4F"
magenta <- "#D9006C"
turquesa <- "#168C86"
gris <- "#D9D6DD"
tema_clase <- theme_minimal(base_size = 12) +
theme(
text = element_text(family = fuente_editorial), # misma tipografía editorial en TODOS los gráficos, no solo en uno puntual
plot.title.position = "plot",
plot.title = element_text(face = "bold", size = 15, hjust = 0),
plot.subtitle = element_text(size = 10.5, color = "grey35", hjust = 0),
panel.grid.minor = element_blank(),
plot.caption = element_text(hjust = 0, color = "grey45", size = 8.5),
plot.margin = margin(10, 20, 10, 10)
)
# Acabado común para TODOS los gráficos interactivos del informe. Se define
# una vez y se aplica al final de cada ggplotly() con %>% estilo_plotly().
# Dos cosas: (1) oculta la barra de herramientas de plotly (zoom, lasso...),
# que distrae y no aporta a la lectura; (2) fija la tipografía editorial del
# curso -- showtext dibuja la fuente en los gráficos ESTÁTICOS, pero no viaja
# a plotly, así que sin esto los interactivos saldrían con una fuente distinta.
estilo_plotly <- function(p) {
p %>%
config(displayModeBar = FALSE) %>% # config(): controla los botones de la barra de plotly
layout(font = list(family = fuente_editorial)) # layout(): ajusta propiedades globales del gráfico, aquí la fuente
}
# Acabado editorial para las tablas ESTÁTICAS pequeñas: encabezado morado en
# negrita, filas con hover y, opcional, una fila resaltada en lila suave para
# ENFOCAR la atención en el dato que cuenta la historia (la categoría dominante).
tabla_bonita <- function(df, resaltar = NULL) {
k <- df %>%
kbl() %>% # kbl(): construye la tabla HTML base (números a la derecha, texto a la izquierda)
kable_styling(bootstrap_options = c("hover", "condensed"), full_width = TRUE, font_size = 14) %>%
row_spec(0, bold = TRUE, color = "white", background = morado) # fila 0 = encabezado
if (!is.null(resaltar)) {
# row_spec(): aplica estilo a filas concretas; aquí resaltamos la fila clave.
k <- k %>% row_spec(resaltar, background = "#F6EEF2", bold = TRUE, color = morado)
}
k
}
# Preparamos el dataset de mercado UNA sola vez aquí, y lo reutilizamos
# en todo el informe -- así cada sección no repite la misma limpieza.
salarios_mercado <- salarios_mercado %>%
mutate(
# Corregimos un error tipográfico real de la fuente: "Data Analist" es
# el mismo rol que "Data Analyst" (solo 2 de 105.434 filas). Todo lo
# demás se deja tal cual llega -- unificar a mano los cientos de
# variantes de nombre de rol (ej. "Data Scientist" vs "Staff Data
# Scientist") borraría información real de seniority, no un error.
job_title = if_else(job_title == "Data Analist", "Data Analyst", job_title),
# recode(): reemplaza cada código corto por una etiqueta legible --
# mismo vector, solo cambian las etiquetas de texto.
nivel_experiencia = recode(experience_level,
"EN" = "Junior (EN)", "MI" = "Semi-Senior (MI)",
"SE" = "Senior (SE)", "EX" = "Directivo (EX)"
),
# factor(..., levels = ...): fija el orden real de seniority para que
# gráficos y tablas no lo ordenen alfabéticamente.
nivel_experiencia = factor(nivel_experiencia,
levels = c("Junior (EN)", "Semi-Senior (MI)", "Senior (SE)", "Directivo (EX)")
),
modalidad = recode(as.character(remote_ratio),
"0" = "Presencial", "50" = "Híbrido", "100" = "Remoto"
),
modalidad = factor(modalidad, levels = c("Presencial", "Híbrido", "Remoto")),
tipo_contrato = recode(employment_type,
"FT" = "Tiempo completo", "PT" = "Medio tiempo",
"CT" = "Contrato", "FL" = "Freelance"
),
# countrycode(): traduce un código ISO de país a su nombre en español --
# evita mostrarle al lector códigos crudos como "US" o "IN".
pais_empresa = countrycode(company_location, origin = "iso2c", destination = "country.name.es"),
pais_residencia = countrycode(employee_residence, origin = "iso2c", destination = "country.name.es")
)
1. Introducción
Este informe explora, con código real y datos reales, el mercado
global de profesionales de datos: cuánto se gana, en qué roles, con qué
experiencia, en qué modalidad de trabajo y en qué países. Es la fuente
que vamos a exprimir a fondo, con tablas dinámicas y un mapa
interactivo, antes de dar un paso más: al final, la contrastamos con las
expectativas propias de nuestro grupo del curso.
Partimos de dos fuentes:
- Un dataset abierto de mercado (“Data Science
Salaries 2025”, vía Kaggle — fuente original: aijobs.net Salary Survey),
con 105.434 salarios reales reportados por profesionales de datos entre
2020 y 2025. Esta es la fuente protagonista del
informe.
- Nuestra encuesta propia del curso (53 respuestas),
que solo aparece al final, para comparar expectativas contra esta
realidad de mercado.
2. Objetivos
Objetivo general: explorar en profundidad el mercado
global de datos —salarios, roles, experiencia y geografía— y
contrastarlo, como paso final, con las expectativas de nuestro grupo,
como caso aplicado para aprender el ciclo completo de análisis y
visualización de datos.
Objetivos específicos:
- Describir la cobertura y calidad del dataset de mercado (países,
años, tipos de contrato y experiencia).
- Explorar cómo varía el salario según el rol, la experiencia, la
modalidad de trabajo y el país.
- Construir herramientas interactivas (mapa, tablas dinámicas) que
permitan explorar el mercado más allá de lo que muestra un solo gráfico
estático.
- Comparar, al final, el perfil y las expectativas de nuestro grupo
con esta realidad de mercado.
- Comunicar los hallazgos con honestidad, reconociendo qué sí y qué no
permiten concluir los datos.
4. Explorando el mercado global de datos
4.1 Cobertura del dataset
salarios_mercado %>%
count(nivel_experiencia) %>%
mutate(porcentaje = round(n / sum(n) * 100, 1)) %>%
rename(`Nivel de experiencia` = nivel_experiencia, `N.º de registros` = n, `%` = porcentaje) %>%
tabla_bonita(resaltar = 3) # Senior: el nivel dominante del dataset
|
Nivel de experiencia
|
N.º de registros
|
%
|
|
Junior (EN)
|
9938
|
9.4
|
|
Semi-Senior (MI)
|
31802
|
30.2
|
|
Senior (SE)
|
61396
|
58.2
|
|
Directivo (EX)
|
2298
|
2.2
|
salarios_mercado %>%
count(tipo_contrato, sort = TRUE) %>%
mutate(porcentaje = round(n / sum(n) * 100, 1)) %>%
rename(`Tipo de contrato` = tipo_contrato, `N.º de registros` = n, `%` = porcentaje) %>%
tabla_bonita(resaltar = 1) # Tiempo completo: el 99.5% de las filas
|
Tipo de contrato
|
N.º de registros
|
%
|
|
Tiempo completo
|
104865
|
99.5
|
|
Medio tiempo
|
285
|
0.3
|
|
Contrato
|
268
|
0.3
|
|
Freelance
|
16
|
0.0
|
El dataset está dominado por posiciones Senior y de
tiempo completo (99.5% de las filas) — cualquier
lectura sobre medio tiempo, freelance o contratos cortos descansa en muy
pocos casos y debe tomarse con cautela.
p_experiencia_n <- salarios_mercado %>%
count(nivel_experiencia) %>%
ggplot(aes(x = nivel_experiencia, y = n, fill = nivel_experiencia)) +
geom_col(width = 0.6) +
scale_fill_manual(values = c(gris, "#B98FC9", magenta, morado), guide = "none") +
scale_y_continuous(labels = label_comma(big.mark = ".")) +
labs(
title = "La mayoría de los registros son de nivel Senior",
x = NULL, y = "N.º de registros"
) +
tema_clase +
theme(panel.grid.major.x = element_blank())
# ggplotly(): toma un gráfico de ggplot ya construido y le agrega
# interactividad (hover con el valor exacto, zoom) sin rehacer el gráfico.
ggplotly(p_experiencia_n, tooltip = c("x", "y")) %>% estilo_plotly()
4.2 Evolución del salario a través de los años
por_anio <- salarios_mercado %>%
group_by(work_year) %>%
summarise(mediana = median(salary_in_usd), n = n(), .groups = "drop")
p_evolucion <- ggplot(por_anio, aes(x = work_year, y = mediana, text = paste0("Año ", work_year, "\nMediana: USD ", comma(mediana, big.mark = "."), "\nRegistros: ", comma(n, big.mark = ".")))) +
geom_line(color = morado, linewidth = 1) +
geom_point(aes(size = n), color = magenta) +
scale_y_continuous(labels = label_dollar(prefix = "USD ", big.mark = ".")) +
scale_size_continuous(range = c(2, 9), guide = "none") +
labs(
title = "El salario mediano del mercado sube año a año",
subtitle = "Tamaño del punto = número de registros ese año (2020 y 2021 tienen muy pocos)",
x = NULL, y = "Salario mediano (USD)"
) +
tema_clase
ggplotly(p_evolucion, tooltip = "text") %>% estilo_plotly()
Los primeros años (2020-2021) tienen muy pocos registros (75 y 218,
frente a más de 60.000 en 2024) — la tendencia reciente (2023-2025) es
la más confiable.
4.3 Los roles más comunes y mejor pagados
top_frecuentes <- salarios_mercado %>%
count(job_title, sort = TRUE) %>%
slice_head(n = 15)
p_frecuentes <- ggplot(top_frecuentes, aes(x = n, y = fct_reorder(job_title, n), text = paste0(job_title, ": ", comma(n, big.mark = "."), " registros"))) +
geom_col(fill = morado, width = 0.65) +
scale_x_continuous(labels = label_comma(big.mark = ".")) +
labs(
title = "Data Scientist, Data Engineer y Software Engineer concentran la mayoría de registros",
x = "N.º de registros", y = NULL
) +
tema_clase +
theme(panel.grid.major.y = element_blank())
ggplotly(p_frecuentes, tooltip = "text") %>% estilo_plotly()
top_pagados <- salarios_mercado %>%
group_by(job_title) %>%
summarise(n = n(), mediana = median(salary_in_usd), .groups = "drop") %>%
filter(n >= 100) %>% # excluye roles con muy pocos registros, donde la mediana no es confiable
slice_max(mediana, n = 15)
p_pagados <- ggplot(top_pagados, aes(x = mediana, y = fct_reorder(job_title, mediana), text = paste0(job_title, "\nMediana: USD ", comma(mediana, big.mark = "."), "\n", comma(n, big.mark = "."), " registros"))) +
geom_col(fill = turquesa, width = 0.65) +
scale_x_continuous(labels = label_dollar(prefix = "USD ", big.mark = ".")) +
labs(
title = "Los roles mejor pagados están ligados a IA y liderazgo técnico",
subtitle = "Solo roles con al menos 100 registros",
x = "Salario mediano (USD)", y = NULL
) +
tema_clase +
theme(panel.grid.major.y = element_blank())
ggplotly(p_pagados, tooltip = "text") %>% estilo_plotly()
4.4 El efecto de la experiencia y la modalidad de trabajo
p_exp_salario <- ggplot(salarios_mercado, aes(x = nivel_experiencia, y = salary_in_usd)) +
geom_boxplot(fill = "#F4F0F6", color = morado, outlier.alpha = 0.08, outlier.size = 1) +
scale_y_continuous(labels = label_dollar(prefix = "USD ", big.mark = ".")) +
labs(
title = "El salario sube de forma consistente con la experiencia",
x = NULL, y = "Salario anual (USD)"
) +
tema_clase
ggplotly(p_exp_salario, tooltip = c("x", "y")) %>% estilo_plotly()
p_modalidad_salario <- ggplot(salarios_mercado, aes(x = modalidad, y = salary_in_usd)) +
geom_boxplot(fill = "#F4F0F6", color = turquesa, outlier.alpha = 0.08, outlier.size = 1) +
scale_y_continuous(labels = label_dollar(prefix = "USD ", big.mark = ".")) +
labs(
title = "La modalidad de trabajo no marca una diferencia salarial clara",
subtitle = "Híbrido tiene muy pocos registros (308 de 105.434) -- no sacar conclusiones fuertes de esa categoría",
x = NULL, y = "Salario anual (USD)"
) +
tema_clase
ggplotly(p_modalidad_salario, tooltip = c("x", "y")) %>% estilo_plotly()
4.5 Mapa: ¿dónde se paga mejor?
umbral_n <- 30 # países con menos de 30 registros no muestran mediana -- se consideran datos insuficientes
resumen_paises <- salarios_mercado %>%
group_by(company_location, pais_empresa) %>%
summarise(n = n(), mediana = median(salary_in_usd), .groups = "drop")
# ne_countries(): trae los polígonos reales de los países del mundo como un
# objeto espacial (sf) -- es lo que necesita leaflet para dibujar el mapa.
mundo <- ne_countries(scale = "medium", returnclass = "sf")
# iso_a2_eh (en vez de iso_a2): rnaturalearth deja el código ISO en blanco
# ("-99") para algunos países (Francia, Noruega) en la columna iso_a2 por un
# problema conocido de la fuente; iso_a2_eh es la versión que sí lo trae bien.
mapa_datos <- mundo %>%
left_join(resumen_paises, by = c("iso_a2_eh" = "company_location")) %>%
mutate(
mediana_mostrar = if_else(n >= umbral_n, mediana, NA_real_),
etiqueta = if_else(
is.na(mediana_mostrar),
paste0(name, ": datos insuficientes"),
paste0(pais_empresa, ": USD ", comma(mediana_mostrar, big.mark = "."), " (n = ", comma(n, big.mark = "."), ")")
)
)
# colorBin(): en vez de un gradiente continuo (donde casi todos los países se
# ven del mismo lila), agrupa los salarios en TRAMOS con cortes claros -- mucho
# más fácil de distinguir de un vistazo. Cada tramo = un tono de la paleta.
cortes_mapa <- c(0, 60000, 90000, 120000, 150000, Inf)
paleta_mapa <- colorBin(
palette = colorRampPalette(c("#E7D9EC", morado))(length(cortes_mapa) - 1),
domain = mapa_datos$mediana_mostrar,
bins = cortes_mapa,
na.color = "#DAD7DE" # gris neutro para "datos insuficientes", distinto del azul del océano
)
leaflet(mapa_datos, options = leafletOptions(minZoom = 1.3, attributionControl = FALSE)) %>%
addPolygons(
fillColor = ~paleta_mapa(mediana_mostrar),
fillOpacity = 0.92,
color = "white", weight = 0.5,
label = ~etiqueta,
highlightOptions = highlightOptions(weight = 2, color = magenta, fillOpacity = 1, bringToFront = TRUE)
) %>%
addLegend(
pal = paleta_mapa, values = ~mediana_mostrar,
title = "Mediana salarial (USD)", position = "bottomright", opacity = 0.95,
labFormat = labelFormat(prefix = "USD ", big.mark = ".", between = " – ")
) %>%
setView(lng = 5, lat = 25, zoom = 1.4)
Solo 27 de los 92 países superan el umbral de 30 registros (gris =
datos insuficientes, no “salario cero”). Pasa el cursor sobre cualquier
país para ver su mediana exacta y cuántos registros la respaldan.
4.6 Explora la tabla completa: rol × experiencia
top_roles_tabla <- salarios_mercado %>%
count(job_title, sort = TRUE) %>%
slice_head(n = 15) %>%
pull(job_title)
niveles_exp <- levels(salarios_mercado$nivel_experiencia)
matriz_rol_experiencia <- salarios_mercado %>%
filter(job_title %in% top_roles_tabla) %>%
group_by(job_title, nivel_experiencia) %>%
summarise(mediana = median(salary_in_usd), .groups = "drop") %>%
pivot_wider(names_from = nivel_experiencia, values_from = mediana) %>%
mutate(job_title = factor(job_title, levels = top_roles_tabla)) %>%
arrange(job_title) %>%
mutate(job_title = as.character(job_title)) %>%
left_join(count(salarios_mercado, job_title, name = "n_registros"), by = "job_title") %>%
relocate(n_registros, .after = job_title) %>%
rename(Rol = job_title, `N. registros` = n_registros)
# DT::datatable(): tabla HTML interactiva -- se puede buscar, ordenar y
# paginar sin escribir ni una línea de JavaScript.
tabla_dt <- datatable(
matriz_rol_experiencia,
rownames = FALSE,
colnames = c("Rol", "N. registros", niveles_exp),
options = list(pageLength = 15, dom = "ftip"),
caption = "Salario mediano (USD) por rol y nivel de experiencia -- ordena por cualquier columna o busca un rol."
)
# formatStyle() + styleInterval(): colorean cada celda según su propio
# valor, como un heatmap dentro de la tabla -- misma idea del heatmap de
# ggplot que ya usamos en otros scripts del curso, pero interactivo.
paleta_tabla <- colorRampPalette(c("#FFFFFF", magenta))(20)
for (columna in niveles_exp) {
cortes <- quantile(matriz_rol_experiencia[[columna]], probs = seq(0.05, 0.95, length.out = 19), na.rm = TRUE)
tabla_dt <- tabla_dt %>%
formatStyle(columna, backgroundColor = styleInterval(cortes, paleta_tabla))
}
tabla_dt %>% formatCurrency(niveles_exp, currency = "USD ", digits = 0, mark = ".")
5. Nuestro grupo frente al mercado
Con el mercado ya explorado a fondo, cerramos con la pregunta
original del curso: ¿cómo se ubican las expectativas salariales de
nuestro propio grupo dentro de este panorama?
roles_grupo <- estudiantes %>% filter(!is.na(profesion_interes)) %>% distinct(profesion_interes) %>% pull(profesion_interes)
mercado_real <- salarios_mercado %>%
filter(job_title %in% roles_grupo) %>%
group_by(job_title) %>%
summarise(n_mercado = n(), mediana_real = median(salary_in_usd), .groups = "drop") %>%
arrange(desc(mediana_real))
# Mostramos una vista renombrada; el objeto 'mercado_real' se conserva tal cual
# porque más abajo se une (join) con la expectativa del grupo por 'job_title'.
mercado_real %>%
rename(Rol = job_title, `N.º en mercado` = n_mercado, `Mediana real (USD)` = mediana_real) %>%
tabla_bonita()
|
Rol
|
N.º en mercado
|
Mediana real (USD)
|
|
Machine Learning Engineer
|
7169
|
188000.0
|
|
AI Engineer
|
1128
|
165800.0
|
|
Data Scientist
|
14938
|
150000.0
|
|
Analytics Engineer
|
1566
|
145000.0
|
|
Data Engineer
|
12392
|
140000.0
|
|
Business Intelligence Analyst
|
538
|
108191.0
|
|
Data Analyst
|
10026
|
98974.5
|
|
Data Manager
|
825
|
97693.0
|
# Excluimos salario_imputado == TRUE: esas filas se rellenaron con la
# mediana GLOBAL en el script de limpieza, no son una respuesta real
# de ese rol -- incluirlas sesga la mediana de Business Intelligence
# Analyst y Data Analyst específicamente (ver script 3 para el detalle).
expectativa_grupo <- estudiantes %>%
filter(!is.na(profesion_interes), !salario_imputado) %>%
group_by(profesion_interes) %>%
summarise(n_estudiantes = n(), mediana_esperada = median(salario_esperado_usd_anual), .groups = "drop")
comparacion <- expectativa_grupo %>%
inner_join(mercado_real, by = c("profesion_interes" = "job_title")) %>%
mutate(brecha = mediana_esperada - mediana_real, brecha_pct = round(brecha / mediana_real * 100, 0)) %>%
arrange(brecha)
# Vista renombrada para mostrar; el objeto 'comparacion' se conserva porque lo
# reutilizan el gráfico de brecha y los hallazgos automáticos de la sección 6.
comparacion %>%
rename(
Rol = profesion_interes, `N.º grupo` = n_estudiantes,
`Mediana esperada` = mediana_esperada, `N.º mercado` = n_mercado,
`Mediana real` = mediana_real, `Brecha (USD)` = brecha, `Brecha %` = brecha_pct
) %>%
tabla_bonita()
|
Rol
|
N.º grupo
|
Mediana esperada
|
N.º mercado
|
Mediana real
|
Brecha (USD)
|
Brecha %
|
|
Machine Learning Engineer
|
3
|
75000
|
7169
|
188000.0
|
-113000.0
|
-60
|
|
Data Scientist
|
8
|
68500
|
14938
|
150000.0
|
-81500.0
|
-54
|
|
Analytics Engineer
|
3
|
64000
|
1566
|
145000.0
|
-81000.0
|
-56
|
|
AI Engineer
|
1
|
88000
|
1128
|
165800.0
|
-77800.0
|
-47
|
|
Data Engineer
|
4
|
67000
|
12392
|
140000.0
|
-73000.0
|
-52
|
|
Business Intelligence Analyst
|
11
|
40000
|
538
|
108191.0
|
-68191.0
|
-63
|
|
Data Analyst
|
16
|
34500
|
10026
|
98974.5
|
-64474.5
|
-65
|
|
Data Manager
|
3
|
70000
|
825
|
97693.0
|
-27693.0
|
-28
|
orden_roles <- comparacion$profesion_interes
datos_mercado_grafico <- salarios_mercado %>%
filter(job_title %in% roles_grupo) %>%
mutate(job_title = factor(job_title, levels = orden_roles))
# Texto del tooltip del punto de expectativa: al pasar el cursor, el lector
# ve de una vez la expectativa del grupo, la mediana real y la diferencia.
expectativa_grafico <- comparacion %>%
mutate(
profesion_interes = factor(profesion_interes, levels = orden_roles),
tooltip = paste0(
profesion_interes,
"\nExpectativa del grupo: USD ", comma(mediana_esperada, big.mark = "."),
"\nMediana real del mercado: USD ", comma(mediana_real, big.mark = "."),
"\nDiferencia: USD ", comma(brecha, big.mark = ".")
)
)
p_comparacion <- ggplot(datos_mercado_grafico, aes(x = job_title, y = salary_in_usd)) +
geom_boxplot(fill = gris, color = morado, outlier.alpha = 0.15, outlier.size = 1, width = 0.55) +
# El punto magenta lleva un aes(text = ...): así ggplotly usa ESE texto como
# tooltip, en vez de las coordenadas crudas.
geom_point(data = expectativa_grafico,
aes(x = profesion_interes, y = mediana_esperada, text = tooltip),
color = magenta, size = 4) +
scale_y_continuous(labels = label_dollar(prefix = "USD ", big.mark = ".")) +
coord_flip() +
labs(
title = "La percepción salarial del grupo se ubica por debajo del mercado global",
subtitle = "Caja = distribución real del mercado · Punto magenta = mediana esperada por el grupo (pasa el cursor por él)",
x = NULL, y = "Salario anual (USD)",
caption = "Mercado: ~90% de las empresas reportadas están en EE. UU. — no es una comparación de poder adquisitivo local."
) +
tema_clase +
theme(panel.grid.major.y = element_blank())
# Interactivo: la caja conserva su hover con los cuartiles (min, Q1, mediana,
# Q3, máx) del mercado real, y el punto magenta muestra el texto que armamos.
ggplotly(p_comparacion, tooltip = "text") %>% estilo_plotly()
# Tabla solo para este gráfico (no tocamos 'comparacion', que se reutiliza en
# los hallazgos). Ordenamos por brecha y preparamos el texto del tooltip.
comparacion_grafico <- comparacion %>%
mutate(
profesion_interes = fct_reorder(profesion_interes, brecha),
tooltip = paste0(
profesion_interes,
"\nBrecha: -USD ", comma(abs(brecha), big.mark = "."), " (", abs(brecha_pct), "%)",
"\nEsperado: USD ", comma(mediana_esperada, big.mark = "."),
" · Mercado: USD ", comma(mediana_real, big.mark = ".")
)
)
p_brecha <- ggplot(comparacion_grafico, aes(x = brecha, y = profesion_interes)) +
geom_segment(aes(x = 0, xend = brecha, yend = profesion_interes), color = gris, linewidth = 4) +
geom_point(aes(text = tooltip), color = magenta, size = 4) +
scale_x_continuous(labels = label_dollar(prefix = "USD ", big.mark = "."), expand = expansion(mult = c(0.1, 0.05))) +
labs(
title = "En todos los roles, la percepción del grupo se ubica por debajo del mercado global",
subtitle = "Diferencia entre la mediana esperada por el grupo y la mediana real del mercado (pasa el cursor por cada punto)",
x = "Brecha (USD/año)", y = NULL,
caption = "Brecha = mediana esperada (encuesta) − mediana real (mercado global, mayoritariamente EE. UU.)."
) +
tema_clase +
theme(panel.grid.major.y = element_blank())
ggplotly(p_brecha, tooltip = "text") %>% estilo_plotly()
6. Principales hallazgos
rol_mas_pagado <- top_pagados %>% slice_max(mediana, n = 1, with_ties = FALSE)
rol_mas_comun <- top_frecuentes %>% slice_max(n, n = 1, with_ties = FALSE)
pais_mejor_pagado <- resumen_paises %>% filter(n >= umbral_n) %>% slice_max(mediana, n = 1, with_ties = FALSE)
cat(paste0(
"- El rol más común del mercado es **", rol_mas_comun$job_title, "** (",
comma(rol_mas_comun$n, big.mark = "."), " registros), y el mejor pagado (entre roles con al menos 100 registros) es **",
rol_mas_pagado$job_title, "** (mediana USD ", comma(rol_mas_pagado$mediana, big.mark = "."), ").\n"
))
- El rol más común del mercado es Data Scientist
(14.938 registros), y el mejor pagado (entre roles con al menos 100
registros) es Engineering Manager (mediana USD
240.000).
cat(paste0(
"- Entre los países con datos suficientes (n ≥ ", umbral_n, "), **", pais_mejor_pagado$pais_empresa,
"** tiene la mediana salarial más alta (USD ", comma(pais_mejor_pagado$mediana, big.mark = "."), ").\n"
))
- Entre los países con datos suficientes (n ≥ 30), Estados
Unidos tiene la mediana salarial más alta (USD 151.968).
cat(paste0(
"- El salario mediano global subió de USD ", comma(por_anio$mediana[por_anio$work_year == min(por_anio$work_year)], big.mark = "."),
" en ", min(por_anio$work_year), " a USD ", comma(por_anio$mediana[por_anio$work_year == max(por_anio$work_year)], big.mark = "."),
" en ", max(por_anio$work_year), " (los primeros años tienen muy pocos registros, ver sección 4.2).\n"
))
- El salario mediano global subió de USD 79.833 en 2020 a USD 145.200
en 2025 (los primeros años tienen muy pocos registros, ver sección
4.2).
rol_mayor_brecha <- comparacion %>% slice_min(brecha, n = 1, with_ties = FALSE)
rol_menor_brecha <- comparacion %>% slice_max(brecha, n = 1, with_ties = FALSE)
cat(paste0(
"- Frente al mercado, la brecha más grande del grupo está en **", rol_mayor_brecha$profesion_interes, "** (-",
label_dollar(prefix = "USD ", big.mark = ".")(abs(rol_mayor_brecha$brecha)),
"), mientras que la más pequeña está en **",
rol_menor_brecha$profesion_interes, "** (-",
label_dollar(prefix = "USD ", big.mark = ".")(abs(rol_menor_brecha$brecha)), ").\n"
))
- Frente al mercado, la brecha más grande del grupo está en
Machine Learning Engineer (-USD 113.000), mientras que
la más pequeña está en Data Manager (-USD 27.693).
7. Limitaciones
- Concentración geográfica: ~90% de los registros son
de empresas en EE. UU. El mapa y las comparaciones por país reflejan
sobre todo ese mercado, no un promedio equilibrado del mundo.
- Autorreporte: los datos vienen de personas que los
reportan voluntariamente en aijobs.net — no es un censo ni una muestra
aleatoria del mercado laboral de datos.
job_title es texto libre: la fuente
original no usa una lista cerrada de cargos — más de 300 variantes
distintas. Solo corregimos un typo evidente (“Data Analist” → “Data
Analyst”); variantes de seniority (ej. “Data Scientist” vs. “Staff Data
Scientist”) se dejaron separadas a propósito.
- Categorías con pocos registros: medio tiempo,
freelance/contrato y modalidad híbrida tienen muy pocas filas (ver
sección 4.1) — cualquier lectura sobre ellas es menos confiable que
sobre tiempo completo/Senior/presencial-remoto.
- Salario ya convertido a USD por la fuente: no
controlamos con qué tasa de cambio ni en qué fecha se hizo esa
conversión.
- Correlación no implica causalidad: que el salario
suba con la experiencia o varíe por país describe un patrón observado,
no una relación de causa-efecto aislada de otros factores.
- Valores imputados en la encuesta: unas pocas
respuestas de salario esperado que quedaron en blanco se rellenaron con
la mediana del grupo (columna
salario_imputado) — se
excluyeron explícitamente de la sección 5 para no sesgar esa comparación
puntual.
8. Conclusiones
El mercado global de datos muestra un patrón claro: el salario sube
de forma consistente con la experiencia, se concentra en un puñado de
roles (Data Scientist, Data Engineer, Software Engineer) y varía
fuertemente por país — con Estados Unidos dominando tanto el volumen de
datos como, en general, los salarios reportados. La modalidad de
trabajo, en cambio, no muestra una diferencia salarial clara.
Frente a este panorama, la percepción salarial de nuestro grupo se
ubica por debajo del mercado global en los roles de interés — una
diferencia esperable dado el fuerte peso de EE. UU. en la fuente, más
que una señal de expectativas equivocadas.
El ejercicio completo —desde explorar a fondo una fuente externa
hasta construir un mapa y tablas interactivas, y cerrar con una
comparación honesta— es también el punto de partida metodológico para el
proyecto transversal del curso: la misma lógica (pregunta → estadística
→ visualización → interpretación → comunicación) aplica igual de bien a
cualquier otro dataset que cada estudiante elija para su propio proyecto
final.
LS0tCnRpdGxlOiAiRWwgbWVyY2FkbyBnbG9iYWwgZGUgZGF0b3MsIGVuIGNpZnJhcyByZWFsZXMiCnN1YnRpdGxlOiAiU2FsYXJpb3MsIHJvbGVzIHkgZ2VvZ3JhZsOtYSBkZSBsYSBpbmR1c3RyaWEg4oCUIFZpc3VhbGl6YWNpw7NuIGRlIERhdG9zLCBLb25yYWQgTG9yZW56IgphdXRob3I6ICJDYXJsb3MgRGFuaWVsIEFtb3J0ZWd1aSBGZXJyZXIiCmRhdGU6ICJgciBmb3JtYXQoU3lzLkRhdGUoKSwgJyVkIGRlICVCIGRlICVZJylgIgpvdXRwdXQ6CiAgaHRtbF9kb2N1bWVudDoKICAgIHRvYzogdHJ1ZQogICAgdG9jX2Zsb2F0OiB0cnVlCiAgICB0aGVtZTogZmxhdGx5CiAgICBkZl9wcmludDoga2FibGUKICAgIGNvZGVfZm9sZGluZzogaGlkZQogICAgY29kZV9kb3dubG9hZDogdHJ1ZSAgICMgYWdyZWdhIHVuIGJvdMOzbiBwYXJhIGRlc2NhcmdhciBlbCAuUm1kIG9yaWdpbmFsIC0tIHJlZnVlcnphIGxhIHJlcHJvZHVjaWJpbGlkYWQKLS0tCgpgYGB7PWh0bWx9CjxzdHlsZT4KLyogVGlwb2dyYWbDrWEgZWRpdG9yaWFsIGRlbCBjdXJzbyAobWlzbWEgcXVlIGxsZXZhbiBsb3MgZ3LDoWZpY29zIHbDrWEKICAgc2hvd3RleHQpIHRhbWJpw6luIHBhcmEgZWwgdGV4dG8gZGVsIGRvY3VtZW50byAtLSBxdWUgdG9kbyBlbCBpbmZvcm1lCiAgIHNlIHNpZW50YSBjb21vIHVuYSBzb2xhIHBpZXphLCBubyB1biBIVE1MIGdlbsOpcmljbyBjb24gZ3LDoWZpY29zIHBlZ2Fkb3MuICovCkBpbXBvcnQgdXJsKCdodHRwczovL2ZvbnRzLmdvb2dsZWFwaXMuY29tL2NzczI/ZmFtaWx5PUZpcmErU2Fuczp3Z2h0QDQwMDs2MDA7NzAwJmRpc3BsYXk9c3dhcCcpOwoKYm9keSB7IGZvbnQtZmFtaWx5OiAnRmlyYSBTYW5zJywgLWFwcGxlLXN5c3RlbSwgQmxpbmtNYWNTeXN0ZW1Gb250LCBzYW5zLXNlcmlmOyBjb2xvcjogIzJCMjUzMDsgfQpoMSwgaDIsIGgzIHsgZm9udC1mYW1pbHk6ICdGaXJhIFNhbnMnLCBzYW5zLXNlcmlmOyBjb2xvcjogIzM1MUE0RjsgfQpoMSB7IGJvcmRlci1ib3R0b206IDNweCBzb2xpZCAjRDkwMDZDOyBwYWRkaW5nLWJvdHRvbTogOHB4OyBtYXJnaW4tdG9wOiA0OHB4OyB9CmgyIHsgbWFyZ2luLXRvcDogMzZweDsgfQphIHsgY29sb3I6ICNEOTAwNkM7IH0KYTpob3ZlciB7IGNvbG9yOiAjMTY4Qzg2OyB9CiNUT0MgeyBib3JkZXItcmFkaXVzOiAxMHB4OyB9Ci5uYXYtcGlsbHMgPiBsaS5hY3RpdmUgPiBhLCAubmF2LXBpbGxzID4gbGkuYWN0aXZlID4gYTpob3ZlciB7IGJhY2tncm91bmQtY29sb3I6ICMzNTFBNEYgIWltcG9ydGFudDsgfQpjYXB0aW9uIHsgY29sb3I6ICM1QTU1NjA7IGZvbnQtc3R5bGU6IGl0YWxpYzsgY2FwdGlvbi1zaWRlOiB0b3A7IH0KY29kZSB7IGNvbG9yOiAjOEExQTVDOyBiYWNrZ3JvdW5kOiAjRjZFRUYyOyBwYWRkaW5nOiAxcHggNXB4OyBib3JkZXItcmFkaXVzOiA0cHg7IH0KdGFibGUuZGF0YVRhYmxlIHsgZm9udC1zaXplOiAwLjkycmVtOyB9Ci5sZWFmbGV0LWNvbnRhaW5lciB7IGJvcmRlci1yYWRpdXM6IDEwcHg7IGJhY2tncm91bmQ6ICNFQUYwRjQgIWltcG9ydGFudDsgfQo8L3N0eWxlPgpgYGAKCjo6Ojo6Ojoge3N0eWxlPSJiYWNrZ3JvdW5kOiBsaW5lYXItZ3JhZGllbnQoMTM1ZGVnLCAjMzUxQTRGIDAlLCAjRDkwMDZDIDEwMCUpOyBwYWRkaW5nOiAyNnB4IDMwcHg7IGJvcmRlci1yYWRpdXM6IDE0cHg7IG1hcmdpbi1ib3R0b206IDMwcHg7IGRpc3BsYXk6IGZsZXg7IGFsaWduLWl0ZW1zOiBjZW50ZXI7IGp1c3RpZnktY29udGVudDogc3BhY2UtYmV0d2VlbjsgZmxleC13cmFwOiB3cmFwOyBnYXA6IDE4cHg7In0KOjo6Ojoge3N0eWxlPSJtYXgtd2lkdGg6IDYyMHB4OyJ9Cjo6OiB7c3R5bGU9ImNvbG9yOiB3aGl0ZTsgZm9udC1zaXplOiAxLjU1cmVtOyBmb250LXdlaWdodDogNzAwOyBsaW5lLWhlaWdodDogMS4yNTsifQpFbCBtZXJjYWRvIGdsb2JhbCBkZSBkYXRvcywgZW4gY2lmcmFzIHJlYWxlcwo6OjoKCjo6OiB7c3R5bGU9ImNvbG9yOiAjRjREQ0U5OyBmb250LXNpemU6IDFyZW07IG1hcmdpbi10b3A6IDZweDsifQpTYWxhcmlvcywgcm9sZXMgeSBnZW9ncmFmw61hIGRlIGxhIGluZHVzdHJpYSBkZSBkYXRvcyDigJQgeSwgYWwgZmluYWwsIGPDs21vIHNlIGNvbXBhcmEgbnVlc3RybyBncnVwbwo6OjoKOjo6OjoKCjo6OiB7c3R5bGU9ImJhY2tncm91bmQ6IHdoaXRlOyBwYWRkaW5nOiAxMHB4IDE4cHg7IGJvcmRlci1yYWRpdXM6IDEwcHg7IGZsZXgtc2hyaW5rOiAwOyJ9CjxpbWcgc3JjPSJpbWcva29ucmFkX2xvZ28ucG5nIiBzdHlsZT0ibWF4LXdpZHRoOiAyMTBweDsgZGlzcGxheTogYmxvY2s7Ii8+Cjo6Ogo6Ojo6Ojo6CgpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0KIyBpbmNsdWRlPUZBTFNFOiBlc3RlIGNodW5rIGNvbmZpZ3VyYSBlbCBkb2N1bWVudG8gcGVybyBubyBhcGFyZWNlIGVuIGVsCiMgSFRNTCBmaW5hbCAtLSBuaSBlbCBjw7NkaWdvIG5pIG5pbmfDum4gcmVzdWx0YWRvLgojCiMga25pdHI6Om9wdHNfY2h1bmskc2V0KCkgY2FtYmlhIGVsIGNvbXBvcnRhbWllbnRvIGRlIFRPRE9TIGxvcyBjaHVua3MKIyBxdWUgdmllbmVuIGRlc3B1w6lzLCBkZSB1bmEgc29sYSB2ZXosIGVuIHZleiBkZSByZXBldGlyIGxhIG1pc21hIG9wY2nDs24KIyBjaHVuayBwb3IgY2h1bmsuCmtuaXRyOjpvcHRzX2NodW5rJHNldCgKICBlY2hvID0gVFJVRSwgICAgICAgICMgbXVlc3RyYSBlbCBjw7NkaWdvIGRlIFIgZW4gZWwgSFRNTCAoY29uICJjb2RlX2ZvbGRpbmc6IGhpZGUiIGFycmliYSwgcXVlZGEgb2N1bHRvIHBvciBkZWZlY3RvIHBlcm8gc2UgcHVlZGUgZGVzcGxlZ2FyKQogIHdhcm5pbmcgPSBGQUxTRSwgICAgICMgbm8gbXVlc3RyYSB3YXJuaW5ncyBkZSBSIGVuIGVsIHJlcG9ydGUKICBtZXNzYWdlID0gRkFMU0UsICAgICAjIG5vIG11ZXN0cmEgbWVuc2FqZXMgaW5mb3JtYXRpdm9zIChjb21vIGxvcyBkZSBsaWJyYXJ5KCkpCiAgZmlnLmFsaWduID0gImNlbnRlciIsCiAgZmlnLndpZHRoID0gOCwKICBmaWcuaGVpZ2h0ID0gNQopCmBgYAoKYGBge3IgbGlicmVyaWFzfQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShzY2FsZXMpICAgICAgICAgIyBmb3JtYXRlYSBuw7ptZXJvcyBjb21vIG1vbmVkYS9wb3JjZW50YWplIGVuIGxhcyBldGlxdWV0YXMgZGUgbG9zIGdyw6FmaWNvcwpsaWJyYXJ5KHNob3d0ZXh0KSAgICAgICAjIGRpYnVqYSBlbCB0ZXh0byBkZSBsb3MgZ3LDoWZpY29zIGNvbiB0aXBvZ3JhZsOtYXMgZGUgR29vZ2xlIEZvbnRzCmxpYnJhcnkoc3lzZm9udHMpICAgICAgICMgcmVnaXN0cmEgbGEgdGlwb2dyYWbDrWEgYW50ZXMgZGUgcXVlIHNob3d0ZXh0IGxhIHVzZQpsaWJyYXJ5KGNvdW50cnljb2RlKSAgICAjIHRyYWR1Y2UgY8OzZGlnb3MgZGUgcGHDrXMgKElTTyBkZSAyIGxldHJhcykgYSBub21icmVzIGxlZ2libGVzLCBlbiBlc3Bhw7FvbApsaWJyYXJ5KERUKSAgICAgICAgICAgICAjIHRhYmxhcyBIVE1MIGludGVyYWN0aXZhczogYnVzY2FibGVzLCBvcmRlbmFibGVzIHkgY29uIGNvbG9yIHBvciBjZWxkYQpsaWJyYXJ5KGxlYWZsZXQpICAgICAgICAjIG1hcGFzIGludGVyYWN0aXZvcyAoem9vbSwgcGFuZW8sIHRvb2x0aXBzKSBkaXJlY3RvIGVuIGVsIEhUTUwKbGlicmFyeShzZikgICAgICAgICAgICAgIyByZXByZXNlbnRhIGxvcyBwYcOtc2VzIGNvbW8gcG9sw61nb25vcyAoZGF0b3MgZXNwYWNpYWxlcyksIHBhcmEgZWwgbWFwYQpsaWJyYXJ5KHJuYXR1cmFsZWFydGgpICAjIHRyYWUgbG9zIHBvbMOtZ29ub3MgcmVhbGVzIGRlIGxvcyBwYcOtc2VzIGRlbCBtdW5kbwpsaWJyYXJ5KHBsb3RseSkgICAgICAgICAjIGNvbnZpZXJ0ZSB1biBncsOhZmljbyBkZSBnZ3Bsb3QgZW4gdW5vIGludGVyYWN0aXZvIChob3Zlciwgem9vbSwgcGFubmluZykKbGlicmFyeShrYWJsZUV4dHJhKSAgICAgIyBkYSBlc3RpbG8gZWRpdG9yaWFsIGEgbGFzIHRhYmxhcyBlc3TDoXRpY2FzIChlbmNhYmV6YWRvIGNvbiBjb2xvciwgaG92ZXIsIHJlc2FsdGFyIGZpbGFzKQojIHJuYXR1cmFsZWFydGhkYXRhIHNvbG8gbmVjZXNpdGEgZXN0YXIgaW5zdGFsYWRvIC0tIGd1YXJkYSBsb3MgZGF0b3MgcXVlCiMgcm5hdHVyYWxlYXJ0aDo6bmVfY291bnRyaWVzKCkgdXNhIHBvciBkZW50cm87IG5vIGhhY2UgZmFsdGEgY2FyZ2FybG8gY29uIGxpYnJhcnkoKS4KCmZ1ZW50ZV9lZGl0b3JpYWwgPC0gIkZpcmEgU2FucyIKZm9udF9hZGRfZ29vZ2xlKG5hbWUgPSBmdWVudGVfZWRpdG9yaWFsLCBmYW1pbHkgPSBmdWVudGVfZWRpdG9yaWFsKQpzaG93dGV4dF9hdXRvKCkKYGBgCgpgYGB7ciBkYXRvc30KIyBSdXRhcyBSRUxBVElWQVMgYSBsYSB1YmljYWNpw7NuIGRlIGVzdGUgLlJtZCAtLSBrbml0ciBlamVjdXRhIGNhZGEgaW5mb3JtZQojIGRlc2RlIHN1IHByb3BpYSBjYXJwZXRhLCBhc8OtIHF1ZSAiLi4vIiBzdWJlIGEgIkNvZGlnb3MvIiB5IGRlIGFow60gYmFqYW1vcyBhCiMgbGFzIGNhcnBldGFzIGRlIGRhdG9zLiBDb24gZXN0byBlbCBpbmZvcm1lIGNvcnJlIGVuIENVQUxRVUlFUiBjb21wdXRhZG9yIHF1ZQojIHRlbmdhIGVsIHByb3llY3RvLCBubyBzb2xvIGVuIGVsIGVxdWlwbyBkb25kZSBzZSBlc2NyaWJpw7M6IHJlcHJvZHVjaWJpbGlkYWQKIyBkZSB2ZXJkYWQgKGp1c3RvIGVsIHByaW5jaXBpbyBxdWUgcHJlZGljYSBsYSBpbnRyb2R1Y2Npw7NuKS4KZXN0dWRpYW50ZXMgPC0gcmVhZF9jc3YoCiAgIi4uL0NsYXNlIDIgLSBwcmVwYXJhciBkYXRvcyBkZSBlc3R1ZGlhbnRlcy9vdXRwdXQvZW5jdWVzdGFfZXN0dWRpYW50ZXNfY29uc29saWRhZGFfbGltcGlhLmNzdiIsCiAgc2hvd19jb2xfdHlwZXMgPSBGQUxTRQopCgpzYWxhcmlvc19tZXJjYWRvIDwtIHJlYWRfY3N2KAogICIuLi9Db21wbGVtZW50b3MgeSByZWN1cnNvcy9EYXRvcyBkZSBzYWxhcmlvcyAyMDI1L3NhbGFyaWVzLXNlbGVjdGVkLWNvbHVtbnMuY3N2IiwKICBzaG93X2NvbF90eXBlcyA9IEZBTFNFCikKYGBgCgpgYGB7ciBlc3RpbG99CiMgTWlzbWEgcGFsZXRhIGRlIHRvZG8gZWwgY3Vyc28gKENsYXNlIDIgZW4gYWRlbGFudGUpIC0tIHNlIGRlZmluZSB1bmEKIyBzb2xhIHZleiBhcXXDrSB5IHNlIHJldXRpbGl6YSBlbiBjYWRhIGdyw6FmaWNvIGRlbCBpbmZvcm1lLgptb3JhZG8gICA8LSAiIzM1MUE0RiIKbWFnZW50YSAgPC0gIiNEOTAwNkMiCnR1cnF1ZXNhIDwtICIjMTY4Qzg2IgpncmlzICAgICA8LSAiI0Q5RDZERCIKCnRlbWFfY2xhc2UgPC0gdGhlbWVfbWluaW1hbChiYXNlX3NpemUgPSAxMikgKwogIHRoZW1lKAogICAgdGV4dCA9IGVsZW1lbnRfdGV4dChmYW1pbHkgPSBmdWVudGVfZWRpdG9yaWFsKSwgICMgbWlzbWEgdGlwb2dyYWbDrWEgZWRpdG9yaWFsIGVuIFRPRE9TIGxvcyBncsOhZmljb3MsIG5vIHNvbG8gZW4gdW5vIHB1bnR1YWwKICAgIHBsb3QudGl0bGUucG9zaXRpb24gPSAicGxvdCIsCiAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIsIHNpemUgPSAxNSwgaGp1c3QgPSAwKSwKICAgIHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwLjUsIGNvbG9yID0gImdyZXkzNSIsIGhqdXN0ID0gMCksCiAgICBwYW5lbC5ncmlkLm1pbm9yID0gZWxlbWVudF9ibGFuaygpLAogICAgcGxvdC5jYXB0aW9uID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMCwgY29sb3IgPSAiZ3JleTQ1Iiwgc2l6ZSA9IDguNSksCiAgICBwbG90Lm1hcmdpbiA9IG1hcmdpbigxMCwgMjAsIDEwLCAxMCkKICApCgojIEFjYWJhZG8gY29tw7puIHBhcmEgVE9ET1MgbG9zIGdyw6FmaWNvcyBpbnRlcmFjdGl2b3MgZGVsIGluZm9ybWUuIFNlIGRlZmluZQojIHVuYSB2ZXogeSBzZSBhcGxpY2EgYWwgZmluYWwgZGUgY2FkYSBnZ3Bsb3RseSgpIGNvbiAlPiUgZXN0aWxvX3Bsb3RseSgpLgojIERvcyBjb3NhczogKDEpIG9jdWx0YSBsYSBiYXJyYSBkZSBoZXJyYW1pZW50YXMgZGUgcGxvdGx5ICh6b29tLCBsYXNzby4uLiksCiMgcXVlIGRpc3RyYWUgeSBubyBhcG9ydGEgYSBsYSBsZWN0dXJhOyAoMikgZmlqYSBsYSB0aXBvZ3JhZsOtYSBlZGl0b3JpYWwgZGVsCiMgY3Vyc28gLS0gc2hvd3RleHQgZGlidWphIGxhIGZ1ZW50ZSBlbiBsb3MgZ3LDoWZpY29zIEVTVMOBVElDT1MsIHBlcm8gbm8gdmlhamEKIyBhIHBsb3RseSwgYXPDrSBxdWUgc2luIGVzdG8gbG9zIGludGVyYWN0aXZvcyBzYWxkcsOtYW4gY29uIHVuYSBmdWVudGUgZGlzdGludGEuCmVzdGlsb19wbG90bHkgPC0gZnVuY3Rpb24ocCkgewogIHAgJT4lCiAgICBjb25maWcoZGlzcGxheU1vZGVCYXIgPSBGQUxTRSkgJT4lICAgICAgICAgICMgY29uZmlnKCk6IGNvbnRyb2xhIGxvcyBib3RvbmVzIGRlIGxhIGJhcnJhIGRlIHBsb3RseQogICAgbGF5b3V0KGZvbnQgPSBsaXN0KGZhbWlseSA9IGZ1ZW50ZV9lZGl0b3JpYWwpKSAjIGxheW91dCgpOiBhanVzdGEgcHJvcGllZGFkZXMgZ2xvYmFsZXMgZGVsIGdyw6FmaWNvLCBhcXXDrSBsYSBmdWVudGUKfQoKIyBBY2FiYWRvIGVkaXRvcmlhbCBwYXJhIGxhcyB0YWJsYXMgRVNUw4FUSUNBUyBwZXF1ZcOxYXM6IGVuY2FiZXphZG8gbW9yYWRvIGVuCiMgbmVncml0YSwgZmlsYXMgY29uIGhvdmVyIHksIG9wY2lvbmFsLCB1bmEgZmlsYSByZXNhbHRhZGEgZW4gbGlsYSBzdWF2ZSBwYXJhCiMgRU5GT0NBUiBsYSBhdGVuY2nDs24gZW4gZWwgZGF0byBxdWUgY3VlbnRhIGxhIGhpc3RvcmlhIChsYSBjYXRlZ29yw61hIGRvbWluYW50ZSkuCnRhYmxhX2Jvbml0YSA8LSBmdW5jdGlvbihkZiwgcmVzYWx0YXIgPSBOVUxMKSB7CiAgayA8LSBkZiAlPiUKICAgIGtibCgpICU+JSAgICMga2JsKCk6IGNvbnN0cnV5ZSBsYSB0YWJsYSBIVE1MIGJhc2UgKG7Dum1lcm9zIGEgbGEgZGVyZWNoYSwgdGV4dG8gYSBsYSBpenF1aWVyZGEpCiAgICBrYWJsZV9zdHlsaW5nKGJvb3RzdHJhcF9vcHRpb25zID0gYygiaG92ZXIiLCAiY29uZGVuc2VkIiksIGZ1bGxfd2lkdGggPSBUUlVFLCBmb250X3NpemUgPSAxNCkgJT4lCiAgICByb3dfc3BlYygwLCBib2xkID0gVFJVRSwgY29sb3IgPSAid2hpdGUiLCBiYWNrZ3JvdW5kID0gbW9yYWRvKSAgIyBmaWxhIDAgPSBlbmNhYmV6YWRvCiAgaWYgKCFpcy5udWxsKHJlc2FsdGFyKSkgewogICAgIyByb3dfc3BlYygpOiBhcGxpY2EgZXN0aWxvIGEgZmlsYXMgY29uY3JldGFzOyBhcXXDrSByZXNhbHRhbW9zIGxhIGZpbGEgY2xhdmUuCiAgICBrIDwtIGsgJT4lIHJvd19zcGVjKHJlc2FsdGFyLCBiYWNrZ3JvdW5kID0gIiNGNkVFRjIiLCBib2xkID0gVFJVRSwgY29sb3IgPSBtb3JhZG8pCiAgfQogIGsKfQpgYGAKCmBgYHtyIHByZXAtbWVyY2Fkb30KIyBQcmVwYXJhbW9zIGVsIGRhdGFzZXQgZGUgbWVyY2FkbyBVTkEgc29sYSB2ZXogYXF1w60sIHkgbG8gcmV1dGlsaXphbW9zCiMgZW4gdG9kbyBlbCBpbmZvcm1lIC0tIGFzw60gY2FkYSBzZWNjacOzbiBubyByZXBpdGUgbGEgbWlzbWEgbGltcGllemEuCnNhbGFyaW9zX21lcmNhZG8gPC0gc2FsYXJpb3NfbWVyY2FkbyAlPiUKICBtdXRhdGUoCiAgICAjIENvcnJlZ2ltb3MgdW4gZXJyb3IgdGlwb2dyw6FmaWNvIHJlYWwgZGUgbGEgZnVlbnRlOiAiRGF0YSBBbmFsaXN0IiBlcwogICAgIyBlbCBtaXNtbyByb2wgcXVlICJEYXRhIEFuYWx5c3QiIChzb2xvIDIgZGUgMTA1LjQzNCBmaWxhcykuIFRvZG8gbG8KICAgICMgZGVtw6FzIHNlIGRlamEgdGFsIGN1YWwgbGxlZ2EgLS0gdW5pZmljYXIgYSBtYW5vIGxvcyBjaWVudG9zIGRlCiAgICAjIHZhcmlhbnRlcyBkZSBub21icmUgZGUgcm9sIChlai4gIkRhdGEgU2NpZW50aXN0IiB2cyAiU3RhZmYgRGF0YQogICAgIyBTY2llbnRpc3QiKSBib3JyYXLDrWEgaW5mb3JtYWNpw7NuIHJlYWwgZGUgc2VuaW9yaXR5LCBubyB1biBlcnJvci4KICAgIGpvYl90aXRsZSA9IGlmX2Vsc2Uoam9iX3RpdGxlID09ICJEYXRhIEFuYWxpc3QiLCAiRGF0YSBBbmFseXN0Iiwgam9iX3RpdGxlKSwKCiAgICAjIHJlY29kZSgpOiByZWVtcGxhemEgY2FkYSBjw7NkaWdvIGNvcnRvIHBvciB1bmEgZXRpcXVldGEgbGVnaWJsZSAtLQogICAgIyBtaXNtbyB2ZWN0b3IsIHNvbG8gY2FtYmlhbiBsYXMgZXRpcXVldGFzIGRlIHRleHRvLgogICAgbml2ZWxfZXhwZXJpZW5jaWEgPSByZWNvZGUoZXhwZXJpZW5jZV9sZXZlbCwKICAgICAgIkVOIiA9ICJKdW5pb3IgKEVOKSIsICJNSSIgPSAiU2VtaS1TZW5pb3IgKE1JKSIsCiAgICAgICJTRSIgPSAiU2VuaW9yIChTRSkiLCAiRVgiID0gIkRpcmVjdGl2byAoRVgpIgogICAgKSwKICAgICMgZmFjdG9yKC4uLiwgbGV2ZWxzID0gLi4uKTogZmlqYSBlbCBvcmRlbiByZWFsIGRlIHNlbmlvcml0eSBwYXJhIHF1ZQogICAgIyBncsOhZmljb3MgeSB0YWJsYXMgbm8gbG8gb3JkZW5lbiBhbGZhYsOpdGljYW1lbnRlLgogICAgbml2ZWxfZXhwZXJpZW5jaWEgPSBmYWN0b3Iobml2ZWxfZXhwZXJpZW5jaWEsCiAgICAgIGxldmVscyA9IGMoIkp1bmlvciAoRU4pIiwgIlNlbWktU2VuaW9yIChNSSkiLCAiU2VuaW9yIChTRSkiLCAiRGlyZWN0aXZvIChFWCkiKQogICAgKSwKCiAgICBtb2RhbGlkYWQgPSByZWNvZGUoYXMuY2hhcmFjdGVyKHJlbW90ZV9yYXRpbyksCiAgICAgICIwIiA9ICJQcmVzZW5jaWFsIiwgIjUwIiA9ICJIw61icmlkbyIsICIxMDAiID0gIlJlbW90byIKICAgICksCiAgICBtb2RhbGlkYWQgPSBmYWN0b3IobW9kYWxpZGFkLCBsZXZlbHMgPSBjKCJQcmVzZW5jaWFsIiwgIkjDrWJyaWRvIiwgIlJlbW90byIpKSwKCiAgICB0aXBvX2NvbnRyYXRvID0gcmVjb2RlKGVtcGxveW1lbnRfdHlwZSwKICAgICAgIkZUIiA9ICJUaWVtcG8gY29tcGxldG8iLCAiUFQiID0gIk1lZGlvIHRpZW1wbyIsCiAgICAgICJDVCIgPSAiQ29udHJhdG8iLCAiRkwiID0gIkZyZWVsYW5jZSIKICAgICksCgogICAgIyBjb3VudHJ5Y29kZSgpOiB0cmFkdWNlIHVuIGPDs2RpZ28gSVNPIGRlIHBhw61zIGEgc3Ugbm9tYnJlIGVuIGVzcGHDsW9sIC0tCiAgICAjIGV2aXRhIG1vc3RyYXJsZSBhbCBsZWN0b3IgY8OzZGlnb3MgY3J1ZG9zIGNvbW8gIlVTIiBvICJJTiIuCiAgICBwYWlzX2VtcHJlc2EgPSBjb3VudHJ5Y29kZShjb21wYW55X2xvY2F0aW9uLCBvcmlnaW4gPSAiaXNvMmMiLCBkZXN0aW5hdGlvbiA9ICJjb3VudHJ5Lm5hbWUuZXMiKSwKICAgIHBhaXNfcmVzaWRlbmNpYSA9IGNvdW50cnljb2RlKGVtcGxveWVlX3Jlc2lkZW5jZSwgb3JpZ2luID0gImlzbzJjIiwgZGVzdGluYXRpb24gPSAiY291bnRyeS5uYW1lLmVzIikKICApCmBgYAoKIyAxLiBJbnRyb2R1Y2Npw7NuCgpFc3RlIGluZm9ybWUgZXhwbG9yYSwgY29uIGPDs2RpZ28gcmVhbCB5IGRhdG9zIHJlYWxlcywgZWwgbWVyY2FkbyBnbG9iYWwgZGUgcHJvZmVzaW9uYWxlcyBkZSBkYXRvczogY3XDoW50byBzZSBnYW5hLCBlbiBxdcOpIHJvbGVzLCBjb24gcXXDqSBleHBlcmllbmNpYSwgZW4gcXXDqSBtb2RhbGlkYWQgZGUgdHJhYmFqbyB5IGVuIHF1w6kgcGHDrXNlcy4gRXMgbGEgZnVlbnRlIHF1ZSB2YW1vcyBhIGV4cHJpbWlyIGEgZm9uZG8sIGNvbiB0YWJsYXMgZGluw6FtaWNhcyB5IHVuIG1hcGEgaW50ZXJhY3Rpdm8sIGFudGVzIGRlIGRhciB1biBwYXNvIG3DoXM6IGFsIGZpbmFsLCBsYSBjb250cmFzdGFtb3MgY29uIGxhcyBleHBlY3RhdGl2YXMgcHJvcGlhcyBkZSBudWVzdHJvIGdydXBvIGRlbCBjdXJzby4KClBhcnRpbW9zIGRlIGRvcyBmdWVudGVzOgoKMS4gIFVuICoqZGF0YXNldCBhYmllcnRvIGRlIG1lcmNhZG8qKiAoIkRhdGEgU2NpZW5jZSBTYWxhcmllcyAyMDI1IiwgdsOtYSBLYWdnbGUg4oCUIGZ1ZW50ZSBvcmlnaW5hbDogYWlqb2JzLm5ldCBTYWxhcnkgU3VydmV5KSwgY29uIGByIGNvbW1hKG5yb3coc2FsYXJpb3NfbWVyY2FkbyksIGJpZy5tYXJrID0gIi4iKWAgc2FsYXJpb3MgcmVhbGVzIHJlcG9ydGFkb3MgcG9yIHByb2Zlc2lvbmFsZXMgZGUgZGF0b3MgZW50cmUgYHIgbWluKHNhbGFyaW9zX21lcmNhZG8kd29ya195ZWFyKWAgeSBgciBtYXgoc2FsYXJpb3NfbWVyY2FkbyR3b3JrX3llYXIpYC4gKipFc3RhIGVzIGxhIGZ1ZW50ZSBwcm90YWdvbmlzdGEgZGVsIGluZm9ybWUuKioKMi4gIE51ZXN0cmEgKiplbmN1ZXN0YSBwcm9waWEgZGVsIGN1cnNvKiogKGByIG5yb3coZXN0dWRpYW50ZXMpYCByZXNwdWVzdGFzKSwgcXVlIHNvbG8gYXBhcmVjZSBhbCBmaW5hbCwgcGFyYSBjb21wYXJhciBleHBlY3RhdGl2YXMgY29udHJhIGVzdGEgcmVhbGlkYWQgZGUgbWVyY2Fkby4KCiMgMi4gT2JqZXRpdm9zCgoqKk9iamV0aXZvIGdlbmVyYWw6KiogZXhwbG9yYXIgZW4gcHJvZnVuZGlkYWQgZWwgbWVyY2FkbyBnbG9iYWwgZGUgZGF0b3Mg4oCUc2FsYXJpb3MsIHJvbGVzLCBleHBlcmllbmNpYSB5IGdlb2dyYWbDrWHigJQgeSBjb250cmFzdGFybG8sIGNvbW8gcGFzbyBmaW5hbCwgY29uIGxhcyBleHBlY3RhdGl2YXMgZGUgbnVlc3RybyBncnVwbywgY29tbyBjYXNvIGFwbGljYWRvIHBhcmEgYXByZW5kZXIgZWwgY2ljbG8gY29tcGxldG8gZGUgYW7DoWxpc2lzIHkgdmlzdWFsaXphY2nDs24gZGUgZGF0b3MuCgoqKk9iamV0aXZvcyBlc3BlY8OtZmljb3M6KioKCjEuICBEZXNjcmliaXIgbGEgY29iZXJ0dXJhIHkgY2FsaWRhZCBkZWwgZGF0YXNldCBkZSBtZXJjYWRvIChwYcOtc2VzLCBhw7FvcywgdGlwb3MgZGUgY29udHJhdG8geSBleHBlcmllbmNpYSkuCjIuICBFeHBsb3JhciBjw7NtbyB2YXLDrWEgZWwgc2FsYXJpbyBzZWfDum4gZWwgcm9sLCBsYSBleHBlcmllbmNpYSwgbGEgbW9kYWxpZGFkIGRlIHRyYWJham8geSBlbCBwYcOtcy4KMy4gIENvbnN0cnVpciBoZXJyYW1pZW50YXMgaW50ZXJhY3RpdmFzIChtYXBhLCB0YWJsYXMgZGluw6FtaWNhcykgcXVlIHBlcm1pdGFuIGV4cGxvcmFyIGVsIG1lcmNhZG8gbcOhcyBhbGzDoSBkZSBsbyBxdWUgbXVlc3RyYSB1biBzb2xvIGdyw6FmaWNvIGVzdMOhdGljby4KNC4gIENvbXBhcmFyLCBhbCBmaW5hbCwgZWwgcGVyZmlsIHkgbGFzIGV4cGVjdGF0aXZhcyBkZSBudWVzdHJvIGdydXBvIGNvbiBlc3RhIHJlYWxpZGFkIGRlIG1lcmNhZG8uCjUuICBDb211bmljYXIgbG9zIGhhbGxhemdvcyBjb24gaG9uZXN0aWRhZCwgcmVjb25vY2llbmRvIHF1w6kgc8OtIHkgcXXDqSBubyBwZXJtaXRlbiBjb25jbHVpciBsb3MgZGF0b3MuCgojIDMuIEZ1ZW50ZXMgZGUgaW5mb3JtYWNpw7NuCgojIyAzLjEgRGF0YXNldCBkZSBtZXJjYWRvCgoiRGF0YSBTY2llbmNlIFNhbGFyaWVzIDIwMjUiIChLYWdnbGUsIHVzdWFyaW8gYGFybmFiY2hha2lgKSDigJQgcmVwdWJsaWNhY2nDs24gZGUgZGF0b3MgcXVlIHByb2Zlc2lvbmFsZXMgZGUgSUEsIGRhdG9zIHkgTUwgcmVwb3J0YW4gdm9sdW50YXJpYSB5IGFuw7NuaW1hbWVudGUgZW4gYWlqb2JzLm5ldC9zYWxhcmllcy4gTGljZW5jaWEgQ0MwIChkb21pbmlvIHDDumJsaWNvKSwgZGF0b3MgYXV0b3JyZXBvcnRhZG9zIGRlc2RlIDIwMjAuCgpgYGB7ciB0YWJsYS12YXJpYWJsZXMtbWVyY2Fkb30KdGliYmxlKAogIFZhcmlhYmxlID0gYygid29ya195ZWFyIiwgImV4cGVyaWVuY2VfbGV2ZWwiLCAiZW1wbG95bWVudF90eXBlIiwgImpvYl90aXRsZSIsICJzYWxhcnlfaW5fdXNkIiwgImVtcGxveWVlX3Jlc2lkZW5jZSAvIGNvbXBhbnlfbG9jYXRpb24iLCAicmVtb3RlX3JhdGlvIiksCiAgRGVzY3JpcGNpw7NuID0gYygKICAgICJBw7FvIGRlbCByZWdpc3RybyBzYWxhcmlhbCAoMjAyMC0yMDI1KSIsCiAgICAiTml2ZWwgZGUgZXhwZXJpZW5jaWE6IEp1bmlvciwgU2VtaS1TZW5pb3IsIFNlbmlvciwgRGlyZWN0aXZvIiwKICAgICJUaXBvIGRlIGNvbnRyYXRvOiB0aWVtcG8gY29tcGxldG8sIG1lZGlvIHRpZW1wbywgY29udHJhdG8sIGZyZWVsYW5jZSIsCiAgICAiQ2FyZ28gcmVwb3J0YWRvICh0ZXh0byBsaWJyZSBkZSBsYSBmdWVudGUgb3JpZ2luYWwgLS0gbcOhcyBkZSAzMDAgdmFyaWFudGVzIGRpc3RpbnRhcykiLAogICAgIlNhbGFyaW8gYW51YWwsIHlhIGNvbnZlcnRpZG8gYSBVU0QgcG9yIGxhIGZ1ZW50ZSIsCiAgICAiUGHDrXMgZGUgcmVzaWRlbmNpYSBkZWwgZW1wbGVhZG8gLyBwYcOtcyBkZSBsYSBlbXByZXNhIChjw7NkaWdvIElTTyBkZSAyIGxldHJhcykiLAogICAgIjAgPSBwcmVzZW5jaWFsLCA1MCA9IGjDrWJyaWRvLCAxMDAgPSByZW1vdG8iCiAgKQopICU+JQogICMgUmVzYWx0YW1vcyBzYWxhcnlfaW5fdXNkIChmaWxhIDUpOiBlcyBsYSB2YXJpYWJsZSBwcm90YWdvbmlzdGEgZGVsIGluZm9ybWUuCiAgdGFibGFfYm9uaXRhKHJlc2FsdGFyID0gNSkKYGBgCgpgYGB7ciBrcGlzLW1lcmNhZG99Cm5fcGFpc2VzIDwtIG5fZGlzdGluY3Qoc2FsYXJpb3NfbWVyY2FkbyRjb21wYW55X2xvY2F0aW9uKQpuX3JvbGVzIDwtIG5fZGlzdGluY3Qoc2FsYXJpb3NfbWVyY2FkbyRqb2JfdGl0bGUpCgpwYXN0ZTAoCiAgY29tbWEobnJvdyhzYWxhcmlvc19tZXJjYWRvKSwgYmlnLm1hcmsgPSAiLiIpLCAiIHJlZ2lzdHJvcyDCtyAiLAogIG5fcGFpc2VzLCAiIHBhw61zZXMgZGUgZW1wcmVzYSDCtyAiLCBuX3JvbGVzLCAiIG5vbWJyZXMgZGUgcm9sIGRpc3RpbnRvcyDCtyBhw7FvcyAiLAogIG1pbihzYWxhcmlvc19tZXJjYWRvJHdvcmtfeWVhciksICItIiwgbWF4KHNhbGFyaW9zX21lcmNhZG8kd29ya195ZWFyKQopCmBgYAoKKipVbiBkYXRvIGNsYXZlIGFudGVzIGRlIGludGVycHJldGFyIGN1YWxxdWllciBjaWZyYToqKgoKYGBge3IgcmVwcmVzZW50YXRpdmlkYWQtbWVyY2Fkb30Kc2FsYXJpb3NfbWVyY2FkbyAlPiUKICBjb3VudChwYWlzX2VtcHJlc2EsIHNvcnQgPSBUUlVFKSAlPiUKICBtdXRhdGUocG9yY2VudGFqZSA9IHJvdW5kKG4gLyBzdW0obikgKiAxMDAsIDEpKSAlPiUKICBzbGljZV9oZWFkKG4gPSA1KSAlPiUKICAjIHJlbmFtZSgpOiByZW5vbWJyYSBjb2x1bW5hcyBzb2xvIHBhcmEgbW9zdHJhcmxhcyBlbiBlc3Bhw7FvbCB5IHByZXNlbnRhYmxlcy4KICByZW5hbWUoYFBhw61zYCA9IHBhaXNfZW1wcmVzYSwgYE4uwrogZGUgcmVnaXN0cm9zYCA9IG4sIGAlYCA9IHBvcmNlbnRhamUpICU+JQogIHRhYmxhX2Jvbml0YShyZXNhbHRhciA9IDEpICAgIyBFc3RhZG9zIFVuaWRvczogY29uY2VudHJhIGNhc2kgZWwgOTAlIGRlIGxhcyBmaWxhcwpgYGAKCkNhc2kgZWwgKio5MCUqKiBkZSBsYXMgZmlsYXMgc29uIGRlIGVtcHJlc2FzIGVuIEVzdGFkb3MgVW5pZG9zOyBlbCByZXN0byBkZSBwYcOtc2VzIGFwb3J0YSBtdXkgcG9jb3MgcmVnaXN0cm9zLiBQb3IgZXNvLCBhIGxvIGxhcmdvIGRlbCBpbmZvcm1lLCBsYSBjb21wYXJhY2nDs24gc2UgbGVlIGNvbW8gKm1lcmNhZG8gZ2xvYmFsIChtYXlvcml0YXJpYW1lbnRlIEVFLiBVVS4pKiB5IG5vIGNvbW8gdW4gcHJvbWVkaW8gZXF1aWxpYnJhZG8gZGVsIG11bmRvLgoKIyMgMy4yIE51ZXN0cmEgZW5jdWVzdGEKClBhcmEgZWwgY3J1Y2UgZmluYWwgKHNlY2Npw7NuIDUpLCB1c2Ftb3MgdGFtYmnDqW4gbGEgZW5jdWVzdGEgcHJvcGlhIGRlbCBjdXJzbzogY2FkYSBlc3R1ZGlhbnRlIGxsZW7DsyBzdSBmaWxhIGVuIHVuIGZvcm11bGFyaW8gY29tcGFydGlkbywgeWEgbGltcGlhLCBjb25zb2xpZGFkYSB5IGFub25pbWl6YWRhICh2ZXIgYDEgY29uc29saWRhY2lvbl9saW1waWV6YV9maW5hbF9lbmN1ZXN0YS5SYCkuIEFxdcOtIHNvbG8gdXNhbW9zIGRvcyBkZSBzdXMgY29sdW1uYXM6IGVsIHJvbCBkZSBpbnRlcsOpcyBwcm9mZXNpb25hbCB5IGVsIHNhbGFyaW8gYW51YWwgZXNwZXJhZG8uCgojIDQuIEV4cGxvcmFuZG8gZWwgbWVyY2FkbyBnbG9iYWwgZGUgZGF0b3MKCiMjIDQuMSBDb2JlcnR1cmEgZGVsIGRhdGFzZXQKCmBgYHtyIGNvYmVydHVyYS1leHBlcmllbmNpYX0Kc2FsYXJpb3NfbWVyY2FkbyAlPiUKICBjb3VudChuaXZlbF9leHBlcmllbmNpYSkgJT4lCiAgbXV0YXRlKHBvcmNlbnRhamUgPSByb3VuZChuIC8gc3VtKG4pICogMTAwLCAxKSkgJT4lCiAgcmVuYW1lKGBOaXZlbCBkZSBleHBlcmllbmNpYWAgPSBuaXZlbF9leHBlcmllbmNpYSwgYE4uwrogZGUgcmVnaXN0cm9zYCA9IG4sIGAlYCA9IHBvcmNlbnRhamUpICU+JQogIHRhYmxhX2Jvbml0YShyZXNhbHRhciA9IDMpICAgIyBTZW5pb3I6IGVsIG5pdmVsIGRvbWluYW50ZSBkZWwgZGF0YXNldApgYGAKCmBgYHtyIGNvYmVydHVyYS1jb250cmF0b30Kc2FsYXJpb3NfbWVyY2FkbyAlPiUKICBjb3VudCh0aXBvX2NvbnRyYXRvLCBzb3J0ID0gVFJVRSkgJT4lCiAgbXV0YXRlKHBvcmNlbnRhamUgPSByb3VuZChuIC8gc3VtKG4pICogMTAwLCAxKSkgJT4lCiAgcmVuYW1lKGBUaXBvIGRlIGNvbnRyYXRvYCA9IHRpcG9fY29udHJhdG8sIGBOLsK6IGRlIHJlZ2lzdHJvc2AgPSBuLCBgJWAgPSBwb3JjZW50YWplKSAlPiUKICB0YWJsYV9ib25pdGEocmVzYWx0YXIgPSAxKSAgICMgVGllbXBvIGNvbXBsZXRvOiBlbCA5OS41JSBkZSBsYXMgZmlsYXMKYGBgCgpFbCBkYXRhc2V0IGVzdMOhIGRvbWluYWRvIHBvciBwb3NpY2lvbmVzICoqU2VuaW9yKiogeSBkZSAqKnRpZW1wbyBjb21wbGV0byoqICg5OS41JSBkZSBsYXMgZmlsYXMpIOKAlCBjdWFscXVpZXIgbGVjdHVyYSBzb2JyZSBtZWRpbyB0aWVtcG8sIGZyZWVsYW5jZSBvIGNvbnRyYXRvcyBjb3J0b3MgZGVzY2Fuc2EgZW4gbXV5IHBvY29zIGNhc29zIHkgZGViZSB0b21hcnNlIGNvbiBjYXV0ZWxhLgoKYGBge3IgYmFyLWV4cGVyaWVuY2lhfQpwX2V4cGVyaWVuY2lhX24gPC0gc2FsYXJpb3NfbWVyY2FkbyAlPiUKICBjb3VudChuaXZlbF9leHBlcmllbmNpYSkgJT4lCiAgZ2dwbG90KGFlcyh4ID0gbml2ZWxfZXhwZXJpZW5jaWEsIHkgPSBuLCBmaWxsID0gbml2ZWxfZXhwZXJpZW5jaWEpKSArCiAgZ2VvbV9jb2wod2lkdGggPSAwLjYpICsKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjKGdyaXMsICIjQjk4RkM5IiwgbWFnZW50YSwgbW9yYWRvKSwgZ3VpZGUgPSAibm9uZSIpICsKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gbGFiZWxfY29tbWEoYmlnLm1hcmsgPSAiLiIpKSArCiAgbGFicygKICAgIHRpdGxlID0gIkxhIG1heW9yw61hIGRlIGxvcyByZWdpc3Ryb3Mgc29uIGRlIG5pdmVsIFNlbmlvciIsCiAgICB4ID0gTlVMTCwgeSA9ICJOLsK6IGRlIHJlZ2lzdHJvcyIKICApICsKICB0ZW1hX2NsYXNlICsKICB0aGVtZShwYW5lbC5ncmlkLm1ham9yLnggPSBlbGVtZW50X2JsYW5rKCkpCgojIGdncGxvdGx5KCk6IHRvbWEgdW4gZ3LDoWZpY28gZGUgZ2dwbG90IHlhIGNvbnN0cnVpZG8geSBsZSBhZ3JlZ2EKIyBpbnRlcmFjdGl2aWRhZCAoaG92ZXIgY29uIGVsIHZhbG9yIGV4YWN0bywgem9vbSkgc2luIHJlaGFjZXIgZWwgZ3LDoWZpY28uCmdncGxvdGx5KHBfZXhwZXJpZW5jaWFfbiwgdG9vbHRpcCA9IGMoIngiLCAieSIpKSAlPiUgZXN0aWxvX3Bsb3RseSgpCmBgYAoKIyMgNC4yIEV2b2x1Y2nDs24gZGVsIHNhbGFyaW8gYSB0cmF2w6lzIGRlIGxvcyBhw7FvcwoKYGBge3IgZXZvbHVjaW9uLWFuaW99CnBvcl9hbmlvIDwtIHNhbGFyaW9zX21lcmNhZG8gJT4lCiAgZ3JvdXBfYnkod29ya195ZWFyKSAlPiUKICBzdW1tYXJpc2UobWVkaWFuYSA9IG1lZGlhbihzYWxhcnlfaW5fdXNkKSwgbiA9IG4oKSwgLmdyb3VwcyA9ICJkcm9wIikKCnBfZXZvbHVjaW9uIDwtIGdncGxvdChwb3JfYW5pbywgYWVzKHggPSB3b3JrX3llYXIsIHkgPSBtZWRpYW5hLCB0ZXh0ID0gcGFzdGUwKCJBw7FvICIsIHdvcmtfeWVhciwgIlxuTWVkaWFuYTogVVNEICIsIGNvbW1hKG1lZGlhbmEsIGJpZy5tYXJrID0gIi4iKSwgIlxuUmVnaXN0cm9zOiAiLCBjb21tYShuLCBiaWcubWFyayA9ICIuIikpKSkgKwogIGdlb21fbGluZShjb2xvciA9IG1vcmFkbywgbGluZXdpZHRoID0gMSkgKwogIGdlb21fcG9pbnQoYWVzKHNpemUgPSBuKSwgY29sb3IgPSBtYWdlbnRhKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IGxhYmVsX2RvbGxhcihwcmVmaXggPSAiVVNEICIsIGJpZy5tYXJrID0gIi4iKSkgKwogIHNjYWxlX3NpemVfY29udGludW91cyhyYW5nZSA9IGMoMiwgOSksIGd1aWRlID0gIm5vbmUiKSArCiAgbGFicygKICAgIHRpdGxlID0gIkVsIHNhbGFyaW8gbWVkaWFubyBkZWwgbWVyY2FkbyBzdWJlIGHDsW8gYSBhw7FvIiwKICAgIHN1YnRpdGxlID0gIlRhbWHDsW8gZGVsIHB1bnRvID0gbsO6bWVybyBkZSByZWdpc3Ryb3MgZXNlIGHDsW8gKDIwMjAgeSAyMDIxIHRpZW5lbiBtdXkgcG9jb3MpIiwKICAgIHggPSBOVUxMLCB5ID0gIlNhbGFyaW8gbWVkaWFubyAoVVNEKSIKICApICsKICB0ZW1hX2NsYXNlCgpnZ3Bsb3RseShwX2V2b2x1Y2lvbiwgdG9vbHRpcCA9ICJ0ZXh0IikgJT4lIGVzdGlsb19wbG90bHkoKQpgYGAKCkxvcyBwcmltZXJvcyBhw7FvcyAoMjAyMC0yMDIxKSB0aWVuZW4gbXV5IHBvY29zIHJlZ2lzdHJvcyAoNzUgeSAyMTgsIGZyZW50ZSBhIG3DoXMgZGUgNjAuMDAwIGVuIDIwMjQpIOKAlCBsYSB0ZW5kZW5jaWEgcmVjaWVudGUgKDIwMjMtMjAyNSkgZXMgbGEgbcOhcyBjb25maWFibGUuCgojIyA0LjMgTG9zIHJvbGVzIG3DoXMgY29tdW5lcyB5IG1lam9yIHBhZ2Fkb3MKCmBgYHtyIHJvbGVzLWZyZWN1ZW50ZXN9CnRvcF9mcmVjdWVudGVzIDwtIHNhbGFyaW9zX21lcmNhZG8gJT4lCiAgY291bnQoam9iX3RpdGxlLCBzb3J0ID0gVFJVRSkgJT4lCiAgc2xpY2VfaGVhZChuID0gMTUpCgpwX2ZyZWN1ZW50ZXMgPC0gZ2dwbG90KHRvcF9mcmVjdWVudGVzLCBhZXMoeCA9IG4sIHkgPSBmY3RfcmVvcmRlcihqb2JfdGl0bGUsIG4pLCB0ZXh0ID0gcGFzdGUwKGpvYl90aXRsZSwgIjogIiwgY29tbWEobiwgYmlnLm1hcmsgPSAiLiIpLCAiIHJlZ2lzdHJvcyIpKSkgKwogIGdlb21fY29sKGZpbGwgPSBtb3JhZG8sIHdpZHRoID0gMC42NSkgKwogIHNjYWxlX3hfY29udGludW91cyhsYWJlbHMgPSBsYWJlbF9jb21tYShiaWcubWFyayA9ICIuIikpICsKICBsYWJzKAogICAgdGl0bGUgPSAiRGF0YSBTY2llbnRpc3QsIERhdGEgRW5naW5lZXIgeSBTb2Z0d2FyZSBFbmdpbmVlciBjb25jZW50cmFuIGxhIG1heW9yw61hIGRlIHJlZ2lzdHJvcyIsCiAgICB4ID0gIk4uwrogZGUgcmVnaXN0cm9zIiwgeSA9IE5VTEwKICApICsKICB0ZW1hX2NsYXNlICsKICB0aGVtZShwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2JsYW5rKCkpCgpnZ3Bsb3RseShwX2ZyZWN1ZW50ZXMsIHRvb2x0aXAgPSAidGV4dCIpICU+JSBlc3RpbG9fcGxvdGx5KCkKYGBgCgpgYGB7ciByb2xlcy1wYWdhZG9zfQp0b3BfcGFnYWRvcyA8LSBzYWxhcmlvc19tZXJjYWRvICU+JQogIGdyb3VwX2J5KGpvYl90aXRsZSkgJT4lCiAgc3VtbWFyaXNlKG4gPSBuKCksIG1lZGlhbmEgPSBtZWRpYW4oc2FsYXJ5X2luX3VzZCksIC5ncm91cHMgPSAiZHJvcCIpICU+JQogIGZpbHRlcihuID49IDEwMCkgJT4lICAjIGV4Y2x1eWUgcm9sZXMgY29uIG11eSBwb2NvcyByZWdpc3Ryb3MsIGRvbmRlIGxhIG1lZGlhbmEgbm8gZXMgY29uZmlhYmxlCiAgc2xpY2VfbWF4KG1lZGlhbmEsIG4gPSAxNSkKCnBfcGFnYWRvcyA8LSBnZ3Bsb3QodG9wX3BhZ2Fkb3MsIGFlcyh4ID0gbWVkaWFuYSwgeSA9IGZjdF9yZW9yZGVyKGpvYl90aXRsZSwgbWVkaWFuYSksIHRleHQgPSBwYXN0ZTAoam9iX3RpdGxlLCAiXG5NZWRpYW5hOiBVU0QgIiwgY29tbWEobWVkaWFuYSwgYmlnLm1hcmsgPSAiLiIpLCAiXG4iLCBjb21tYShuLCBiaWcubWFyayA9ICIuIiksICIgcmVnaXN0cm9zIikpKSArCiAgZ2VvbV9jb2woZmlsbCA9IHR1cnF1ZXNhLCB3aWR0aCA9IDAuNjUpICsKICBzY2FsZV94X2NvbnRpbnVvdXMobGFiZWxzID0gbGFiZWxfZG9sbGFyKHByZWZpeCA9ICJVU0QgIiwgYmlnLm1hcmsgPSAiLiIpKSArCiAgbGFicygKICAgIHRpdGxlID0gIkxvcyByb2xlcyBtZWpvciBwYWdhZG9zIGVzdMOhbiBsaWdhZG9zIGEgSUEgeSBsaWRlcmF6Z28gdMOpY25pY28iLAogICAgc3VidGl0bGUgPSAiU29sbyByb2xlcyBjb24gYWwgbWVub3MgMTAwIHJlZ2lzdHJvcyIsCiAgICB4ID0gIlNhbGFyaW8gbWVkaWFubyAoVVNEKSIsIHkgPSBOVUxMCiAgKSArCiAgdGVtYV9jbGFzZSArCiAgdGhlbWUocGFuZWwuZ3JpZC5tYWpvci55ID0gZWxlbWVudF9ibGFuaygpKQoKZ2dwbG90bHkocF9wYWdhZG9zLCB0b29sdGlwID0gInRleHQiKSAlPiUgZXN0aWxvX3Bsb3RseSgpCmBgYAoKIyMgNC40IEVsIGVmZWN0byBkZSBsYSBleHBlcmllbmNpYSB5IGxhIG1vZGFsaWRhZCBkZSB0cmFiYWpvCgpgYGB7ciBib3hwbG90LWV4cGVyaWVuY2lhfQpwX2V4cF9zYWxhcmlvIDwtIGdncGxvdChzYWxhcmlvc19tZXJjYWRvLCBhZXMoeCA9IG5pdmVsX2V4cGVyaWVuY2lhLCB5ID0gc2FsYXJ5X2luX3VzZCkpICsKICBnZW9tX2JveHBsb3QoZmlsbCA9ICIjRjRGMEY2IiwgY29sb3IgPSBtb3JhZG8sIG91dGxpZXIuYWxwaGEgPSAwLjA4LCBvdXRsaWVyLnNpemUgPSAxKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IGxhYmVsX2RvbGxhcihwcmVmaXggPSAiVVNEICIsIGJpZy5tYXJrID0gIi4iKSkgKwogIGxhYnMoCiAgICB0aXRsZSA9ICJFbCBzYWxhcmlvIHN1YmUgZGUgZm9ybWEgY29uc2lzdGVudGUgY29uIGxhIGV4cGVyaWVuY2lhIiwKICAgIHggPSBOVUxMLCB5ID0gIlNhbGFyaW8gYW51YWwgKFVTRCkiCiAgKSArCiAgdGVtYV9jbGFzZQoKZ2dwbG90bHkocF9leHBfc2FsYXJpbywgdG9vbHRpcCA9IGMoIngiLCAieSIpKSAlPiUgZXN0aWxvX3Bsb3RseSgpCmBgYAoKYGBge3IgYm94cGxvdC1tb2RhbGlkYWR9CnBfbW9kYWxpZGFkX3NhbGFyaW8gPC0gZ2dwbG90KHNhbGFyaW9zX21lcmNhZG8sIGFlcyh4ID0gbW9kYWxpZGFkLCB5ID0gc2FsYXJ5X2luX3VzZCkpICsKICBnZW9tX2JveHBsb3QoZmlsbCA9ICIjRjRGMEY2IiwgY29sb3IgPSB0dXJxdWVzYSwgb3V0bGllci5hbHBoYSA9IDAuMDgsIG91dGxpZXIuc2l6ZSA9IDEpICsKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gbGFiZWxfZG9sbGFyKHByZWZpeCA9ICJVU0QgIiwgYmlnLm1hcmsgPSAiLiIpKSArCiAgbGFicygKICAgIHRpdGxlID0gIkxhIG1vZGFsaWRhZCBkZSB0cmFiYWpvIG5vIG1hcmNhIHVuYSBkaWZlcmVuY2lhIHNhbGFyaWFsIGNsYXJhIiwKICAgIHN1YnRpdGxlID0gIkjDrWJyaWRvIHRpZW5lIG11eSBwb2NvcyByZWdpc3Ryb3MgKDMwOCBkZSAxMDUuNDM0KSAtLSBubyBzYWNhciBjb25jbHVzaW9uZXMgZnVlcnRlcyBkZSBlc2EgY2F0ZWdvcsOtYSIsCiAgICB4ID0gTlVMTCwgeSA9ICJTYWxhcmlvIGFudWFsIChVU0QpIgogICkgKwogIHRlbWFfY2xhc2UKCmdncGxvdGx5KHBfbW9kYWxpZGFkX3NhbGFyaW8sIHRvb2x0aXAgPSBjKCJ4IiwgInkiKSkgJT4lIGVzdGlsb19wbG90bHkoKQpgYGAKCiMjIDQuNSBNYXBhOiDCv2TDs25kZSBzZSBwYWdhIG1lam9yPwoKYGBge3IgbWFwYS1kYXRvc30KdW1icmFsX24gPC0gMzAgICMgcGHDrXNlcyBjb24gbWVub3MgZGUgMzAgcmVnaXN0cm9zIG5vIG11ZXN0cmFuIG1lZGlhbmEgLS0gc2UgY29uc2lkZXJhbiBkYXRvcyBpbnN1ZmljaWVudGVzCgpyZXN1bWVuX3BhaXNlcyA8LSBzYWxhcmlvc19tZXJjYWRvICU+JQogIGdyb3VwX2J5KGNvbXBhbnlfbG9jYXRpb24sIHBhaXNfZW1wcmVzYSkgJT4lCiAgc3VtbWFyaXNlKG4gPSBuKCksIG1lZGlhbmEgPSBtZWRpYW4oc2FsYXJ5X2luX3VzZCksIC5ncm91cHMgPSAiZHJvcCIpCgojIG5lX2NvdW50cmllcygpOiB0cmFlIGxvcyBwb2zDrWdvbm9zIHJlYWxlcyBkZSBsb3MgcGHDrXNlcyBkZWwgbXVuZG8gY29tbyB1bgojIG9iamV0byBlc3BhY2lhbCAoc2YpIC0tIGVzIGxvIHF1ZSBuZWNlc2l0YSBsZWFmbGV0IHBhcmEgZGlidWphciBlbCBtYXBhLgptdW5kbyA8LSBuZV9jb3VudHJpZXMoc2NhbGUgPSAibWVkaXVtIiwgcmV0dXJuY2xhc3MgPSAic2YiKQoKIyBpc29fYTJfZWggKGVuIHZleiBkZSBpc29fYTIpOiBybmF0dXJhbGVhcnRoIGRlamEgZWwgY8OzZGlnbyBJU08gZW4gYmxhbmNvCiMgKCItOTkiKSBwYXJhIGFsZ3Vub3MgcGHDrXNlcyAoRnJhbmNpYSwgTm9ydWVnYSkgZW4gbGEgY29sdW1uYSBpc29fYTIgcG9yIHVuCiMgcHJvYmxlbWEgY29ub2NpZG8gZGUgbGEgZnVlbnRlOyBpc29fYTJfZWggZXMgbGEgdmVyc2nDs24gcXVlIHPDrSBsbyB0cmFlIGJpZW4uCm1hcGFfZGF0b3MgPC0gbXVuZG8gJT4lCiAgbGVmdF9qb2luKHJlc3VtZW5fcGFpc2VzLCBieSA9IGMoImlzb19hMl9laCIgPSAiY29tcGFueV9sb2NhdGlvbiIpKSAlPiUKICBtdXRhdGUoCiAgICBtZWRpYW5hX21vc3RyYXIgPSBpZl9lbHNlKG4gPj0gdW1icmFsX24sIG1lZGlhbmEsIE5BX3JlYWxfKSwKICAgIGV0aXF1ZXRhID0gaWZfZWxzZSgKICAgICAgaXMubmEobWVkaWFuYV9tb3N0cmFyKSwKICAgICAgcGFzdGUwKG5hbWUsICI6IGRhdG9zIGluc3VmaWNpZW50ZXMiKSwKICAgICAgcGFzdGUwKHBhaXNfZW1wcmVzYSwgIjogVVNEICIsIGNvbW1hKG1lZGlhbmFfbW9zdHJhciwgYmlnLm1hcmsgPSAiLiIpLCAiIChuID0gIiwgY29tbWEobiwgYmlnLm1hcmsgPSAiLiIpLCAiKSIpCiAgICApCiAgKQpgYGAKCmBgYHtyIG1hcGEtcmVuZGVyLCBmaWcud2lkdGg9OSwgZmlnLmhlaWdodD01LjV9CiMgY29sb3JCaW4oKTogZW4gdmV6IGRlIHVuIGdyYWRpZW50ZSBjb250aW51byAoZG9uZGUgY2FzaSB0b2RvcyBsb3MgcGHDrXNlcyBzZQojIHZlbiBkZWwgbWlzbW8gbGlsYSksIGFncnVwYSBsb3Mgc2FsYXJpb3MgZW4gVFJBTU9TIGNvbiBjb3J0ZXMgY2xhcm9zIC0tIG11Y2hvCiMgbcOhcyBmw6FjaWwgZGUgZGlzdGluZ3VpciBkZSB1biB2aXN0YXpvLiBDYWRhIHRyYW1vID0gdW4gdG9ubyBkZSBsYSBwYWxldGEuCmNvcnRlc19tYXBhIDwtIGMoMCwgNjAwMDAsIDkwMDAwLCAxMjAwMDAsIDE1MDAwMCwgSW5mKQpwYWxldGFfbWFwYSA8LSBjb2xvckJpbigKICBwYWxldHRlID0gY29sb3JSYW1wUGFsZXR0ZShjKCIjRTdEOUVDIiwgbW9yYWRvKSkobGVuZ3RoKGNvcnRlc19tYXBhKSAtIDEpLAogIGRvbWFpbiA9IG1hcGFfZGF0b3MkbWVkaWFuYV9tb3N0cmFyLAogIGJpbnMgPSBjb3J0ZXNfbWFwYSwKICBuYS5jb2xvciA9ICIjREFEN0RFIiAgICMgZ3JpcyBuZXV0cm8gcGFyYSAiZGF0b3MgaW5zdWZpY2llbnRlcyIsIGRpc3RpbnRvIGRlbCBhenVsIGRlbCBvY8OpYW5vCikKCmxlYWZsZXQobWFwYV9kYXRvcywgb3B0aW9ucyA9IGxlYWZsZXRPcHRpb25zKG1pblpvb20gPSAxLjMsIGF0dHJpYnV0aW9uQ29udHJvbCA9IEZBTFNFKSkgJT4lCiAgYWRkUG9seWdvbnMoCiAgICBmaWxsQ29sb3IgPSB+cGFsZXRhX21hcGEobWVkaWFuYV9tb3N0cmFyKSwKICAgIGZpbGxPcGFjaXR5ID0gMC45MiwKICAgIGNvbG9yID0gIndoaXRlIiwgd2VpZ2h0ID0gMC41LAogICAgbGFiZWwgPSB+ZXRpcXVldGEsCiAgICBoaWdobGlnaHRPcHRpb25zID0gaGlnaGxpZ2h0T3B0aW9ucyh3ZWlnaHQgPSAyLCBjb2xvciA9IG1hZ2VudGEsIGZpbGxPcGFjaXR5ID0gMSwgYnJpbmdUb0Zyb250ID0gVFJVRSkKICApICU+JQogIGFkZExlZ2VuZCgKICAgIHBhbCA9IHBhbGV0YV9tYXBhLCB2YWx1ZXMgPSB+bWVkaWFuYV9tb3N0cmFyLAogICAgdGl0bGUgPSAiTWVkaWFuYSBzYWxhcmlhbCAoVVNEKSIsIHBvc2l0aW9uID0gImJvdHRvbXJpZ2h0Iiwgb3BhY2l0eSA9IDAuOTUsCiAgICBsYWJGb3JtYXQgPSBsYWJlbEZvcm1hdChwcmVmaXggPSAiVVNEICIsIGJpZy5tYXJrID0gIi4iLCBiZXR3ZWVuID0gIiDigJMgIikKICApICU+JQogIHNldFZpZXcobG5nID0gNSwgbGF0ID0gMjUsIHpvb20gPSAxLjQpCmBgYAoKU29sbyBgciBzdW0ocmVzdW1lbl9wYWlzZXMkbiA+PSB1bWJyYWxfbilgIGRlIGxvcyBgciBuX3BhaXNlc2AgcGHDrXNlcyBzdXBlcmFuIGVsIHVtYnJhbCBkZSBgciB1bWJyYWxfbmAgcmVnaXN0cm9zIChncmlzID0gZGF0b3MgaW5zdWZpY2llbnRlcywgbm8gInNhbGFyaW8gY2VybyIpLiBQYXNhIGVsIGN1cnNvciBzb2JyZSBjdWFscXVpZXIgcGHDrXMgcGFyYSB2ZXIgc3UgbWVkaWFuYSBleGFjdGEgeSBjdcOhbnRvcyByZWdpc3Ryb3MgbGEgcmVzcGFsZGFuLgoKIyMgNC42IEV4cGxvcmEgbGEgdGFibGEgY29tcGxldGE6IHJvbCDDlyBleHBlcmllbmNpYQoKYGBge3IgdGFibGEtZGluYW1pY2F9CnRvcF9yb2xlc190YWJsYSA8LSBzYWxhcmlvc19tZXJjYWRvICU+JQogIGNvdW50KGpvYl90aXRsZSwgc29ydCA9IFRSVUUpICU+JQogIHNsaWNlX2hlYWQobiA9IDE1KSAlPiUKICBwdWxsKGpvYl90aXRsZSkKCm5pdmVsZXNfZXhwIDwtIGxldmVscyhzYWxhcmlvc19tZXJjYWRvJG5pdmVsX2V4cGVyaWVuY2lhKQoKbWF0cml6X3JvbF9leHBlcmllbmNpYSA8LSBzYWxhcmlvc19tZXJjYWRvICU+JQogIGZpbHRlcihqb2JfdGl0bGUgJWluJSB0b3Bfcm9sZXNfdGFibGEpICU+JQogIGdyb3VwX2J5KGpvYl90aXRsZSwgbml2ZWxfZXhwZXJpZW5jaWEpICU+JQogIHN1bW1hcmlzZShtZWRpYW5hID0gbWVkaWFuKHNhbGFyeV9pbl91c2QpLCAuZ3JvdXBzID0gImRyb3AiKSAlPiUKICBwaXZvdF93aWRlcihuYW1lc19mcm9tID0gbml2ZWxfZXhwZXJpZW5jaWEsIHZhbHVlc19mcm9tID0gbWVkaWFuYSkgJT4lCiAgbXV0YXRlKGpvYl90aXRsZSA9IGZhY3Rvcihqb2JfdGl0bGUsIGxldmVscyA9IHRvcF9yb2xlc190YWJsYSkpICU+JQogIGFycmFuZ2Uoam9iX3RpdGxlKSAlPiUKICBtdXRhdGUoam9iX3RpdGxlID0gYXMuY2hhcmFjdGVyKGpvYl90aXRsZSkpICU+JQogIGxlZnRfam9pbihjb3VudChzYWxhcmlvc19tZXJjYWRvLCBqb2JfdGl0bGUsIG5hbWUgPSAibl9yZWdpc3Ryb3MiKSwgYnkgPSAiam9iX3RpdGxlIikgJT4lCiAgcmVsb2NhdGUobl9yZWdpc3Ryb3MsIC5hZnRlciA9IGpvYl90aXRsZSkgJT4lCiAgcmVuYW1lKFJvbCA9IGpvYl90aXRsZSwgYE4uIHJlZ2lzdHJvc2AgPSBuX3JlZ2lzdHJvcykKCiMgRFQ6OmRhdGF0YWJsZSgpOiB0YWJsYSBIVE1MIGludGVyYWN0aXZhIC0tIHNlIHB1ZWRlIGJ1c2Nhciwgb3JkZW5hciB5CiMgcGFnaW5hciBzaW4gZXNjcmliaXIgbmkgdW5hIGzDrW5lYSBkZSBKYXZhU2NyaXB0Lgp0YWJsYV9kdCA8LSBkYXRhdGFibGUoCiAgbWF0cml6X3JvbF9leHBlcmllbmNpYSwKICByb3duYW1lcyA9IEZBTFNFLAogIGNvbG5hbWVzID0gYygiUm9sIiwgIk4uIHJlZ2lzdHJvcyIsIG5pdmVsZXNfZXhwKSwKICBvcHRpb25zID0gbGlzdChwYWdlTGVuZ3RoID0gMTUsIGRvbSA9ICJmdGlwIiksCiAgY2FwdGlvbiA9ICJTYWxhcmlvIG1lZGlhbm8gKFVTRCkgcG9yIHJvbCB5IG5pdmVsIGRlIGV4cGVyaWVuY2lhIC0tIG9yZGVuYSBwb3IgY3VhbHF1aWVyIGNvbHVtbmEgbyBidXNjYSB1biByb2wuIgopCgojIGZvcm1hdFN0eWxlKCkgKyBzdHlsZUludGVydmFsKCk6IGNvbG9yZWFuIGNhZGEgY2VsZGEgc2Vnw7puIHN1IHByb3BpbwojIHZhbG9yLCBjb21vIHVuIGhlYXRtYXAgZGVudHJvIGRlIGxhIHRhYmxhIC0tIG1pc21hIGlkZWEgZGVsIGhlYXRtYXAgZGUKIyBnZ3Bsb3QgcXVlIHlhIHVzYW1vcyBlbiBvdHJvcyBzY3JpcHRzIGRlbCBjdXJzbywgcGVybyBpbnRlcmFjdGl2by4KcGFsZXRhX3RhYmxhIDwtIGNvbG9yUmFtcFBhbGV0dGUoYygiI0ZGRkZGRiIsIG1hZ2VudGEpKSgyMCkKCmZvciAoY29sdW1uYSBpbiBuaXZlbGVzX2V4cCkgewogIGNvcnRlcyA8LSBxdWFudGlsZShtYXRyaXpfcm9sX2V4cGVyaWVuY2lhW1tjb2x1bW5hXV0sIHByb2JzID0gc2VxKDAuMDUsIDAuOTUsIGxlbmd0aC5vdXQgPSAxOSksIG5hLnJtID0gVFJVRSkKICB0YWJsYV9kdCA8LSB0YWJsYV9kdCAlPiUKICAgIGZvcm1hdFN0eWxlKGNvbHVtbmEsIGJhY2tncm91bmRDb2xvciA9IHN0eWxlSW50ZXJ2YWwoY29ydGVzLCBwYWxldGFfdGFibGEpKQp9Cgp0YWJsYV9kdCAlPiUgZm9ybWF0Q3VycmVuY3kobml2ZWxlc19leHAsIGN1cnJlbmN5ID0gIlVTRCAiLCBkaWdpdHMgPSAwLCBtYXJrID0gIi4iKQpgYGAKCiMgNS4gTnVlc3RybyBncnVwbyBmcmVudGUgYWwgbWVyY2FkbwoKQ29uIGVsIG1lcmNhZG8geWEgZXhwbG9yYWRvIGEgZm9uZG8sIGNlcnJhbW9zIGNvbiBsYSBwcmVndW50YSBvcmlnaW5hbCBkZWwgY3Vyc286IMK/Y8OzbW8gc2UgdWJpY2FuIGxhcyBleHBlY3RhdGl2YXMgc2FsYXJpYWxlcyBkZSBudWVzdHJvIHByb3BpbyBncnVwbyBkZW50cm8gZGUgZXN0ZSBwYW5vcmFtYT8KCmBgYHtyIHJvbGVzLWdydXBvfQpyb2xlc19ncnVwbyA8LSBlc3R1ZGlhbnRlcyAlPiUgZmlsdGVyKCFpcy5uYShwcm9mZXNpb25faW50ZXJlcykpICU+JSBkaXN0aW5jdChwcm9mZXNpb25faW50ZXJlcykgJT4lIHB1bGwocHJvZmVzaW9uX2ludGVyZXMpCgptZXJjYWRvX3JlYWwgPC0gc2FsYXJpb3NfbWVyY2FkbyAlPiUKICBmaWx0ZXIoam9iX3RpdGxlICVpbiUgcm9sZXNfZ3J1cG8pICU+JQogIGdyb3VwX2J5KGpvYl90aXRsZSkgJT4lCiAgc3VtbWFyaXNlKG5fbWVyY2FkbyA9IG4oKSwgbWVkaWFuYV9yZWFsID0gbWVkaWFuKHNhbGFyeV9pbl91c2QpLCAuZ3JvdXBzID0gImRyb3AiKSAlPiUKICBhcnJhbmdlKGRlc2MobWVkaWFuYV9yZWFsKSkKCiMgTW9zdHJhbW9zIHVuYSB2aXN0YSByZW5vbWJyYWRhOyBlbCBvYmpldG8gJ21lcmNhZG9fcmVhbCcgc2UgY29uc2VydmEgdGFsIGN1YWwKIyBwb3JxdWUgbcOhcyBhYmFqbyBzZSB1bmUgKGpvaW4pIGNvbiBsYSBleHBlY3RhdGl2YSBkZWwgZ3J1cG8gcG9yICdqb2JfdGl0bGUnLgptZXJjYWRvX3JlYWwgJT4lCiAgcmVuYW1lKFJvbCA9IGpvYl90aXRsZSwgYE4uwrogZW4gbWVyY2Fkb2AgPSBuX21lcmNhZG8sIGBNZWRpYW5hIHJlYWwgKFVTRClgID0gbWVkaWFuYV9yZWFsKSAlPiUKICB0YWJsYV9ib25pdGEoKQpgYGAKCmBgYHtyIGNvbXBhcmFjaW9ufQojIEV4Y2x1aW1vcyBzYWxhcmlvX2ltcHV0YWRvID09IFRSVUU6IGVzYXMgZmlsYXMgc2UgcmVsbGVuYXJvbiBjb24gbGEKIyBtZWRpYW5hIEdMT0JBTCBlbiBlbCBzY3JpcHQgZGUgbGltcGllemEsIG5vIHNvbiB1bmEgcmVzcHVlc3RhIHJlYWwKIyBkZSBlc2Ugcm9sIC0tIGluY2x1aXJsYXMgc2VzZ2EgbGEgbWVkaWFuYSBkZSBCdXNpbmVzcyBJbnRlbGxpZ2VuY2UKIyBBbmFseXN0IHkgRGF0YSBBbmFseXN0IGVzcGVjw61maWNhbWVudGUgKHZlciBzY3JpcHQgMyBwYXJhIGVsIGRldGFsbGUpLgpleHBlY3RhdGl2YV9ncnVwbyA8LSBlc3R1ZGlhbnRlcyAlPiUKICBmaWx0ZXIoIWlzLm5hKHByb2Zlc2lvbl9pbnRlcmVzKSwgIXNhbGFyaW9faW1wdXRhZG8pICU+JQogIGdyb3VwX2J5KHByb2Zlc2lvbl9pbnRlcmVzKSAlPiUKICBzdW1tYXJpc2Uobl9lc3R1ZGlhbnRlcyA9IG4oKSwgbWVkaWFuYV9lc3BlcmFkYSA9IG1lZGlhbihzYWxhcmlvX2VzcGVyYWRvX3VzZF9hbnVhbCksIC5ncm91cHMgPSAiZHJvcCIpCgpjb21wYXJhY2lvbiA8LSBleHBlY3RhdGl2YV9ncnVwbyAlPiUKICBpbm5lcl9qb2luKG1lcmNhZG9fcmVhbCwgYnkgPSBjKCJwcm9mZXNpb25faW50ZXJlcyIgPSAiam9iX3RpdGxlIikpICU+JQogIG11dGF0ZShicmVjaGEgPSBtZWRpYW5hX2VzcGVyYWRhIC0gbWVkaWFuYV9yZWFsLCBicmVjaGFfcGN0ID0gcm91bmQoYnJlY2hhIC8gbWVkaWFuYV9yZWFsICogMTAwLCAwKSkgJT4lCiAgYXJyYW5nZShicmVjaGEpCgojIFZpc3RhIHJlbm9tYnJhZGEgcGFyYSBtb3N0cmFyOyBlbCBvYmpldG8gJ2NvbXBhcmFjaW9uJyBzZSBjb25zZXJ2YSBwb3JxdWUgbG8KIyByZXV0aWxpemFuIGVsIGdyw6FmaWNvIGRlIGJyZWNoYSB5IGxvcyBoYWxsYXpnb3MgYXV0b23DoXRpY29zIGRlIGxhIHNlY2Npw7NuIDYuCmNvbXBhcmFjaW9uICU+JQogIHJlbmFtZSgKICAgIFJvbCA9IHByb2Zlc2lvbl9pbnRlcmVzLCBgTi7CuiBncnVwb2AgPSBuX2VzdHVkaWFudGVzLAogICAgYE1lZGlhbmEgZXNwZXJhZGFgID0gbWVkaWFuYV9lc3BlcmFkYSwgYE4uwrogbWVyY2Fkb2AgPSBuX21lcmNhZG8sCiAgICBgTWVkaWFuYSByZWFsYCA9IG1lZGlhbmFfcmVhbCwgYEJyZWNoYSAoVVNEKWAgPSBicmVjaGEsIGBCcmVjaGEgJWAgPSBicmVjaGFfcGN0CiAgKSAlPiUKICB0YWJsYV9ib25pdGEoKQpgYGAKCmBgYHtyIGJveHBsb3QtY29tcGFyYWNpb24sIGZpZy5oZWlnaHQ9Nn0Kb3JkZW5fcm9sZXMgPC0gY29tcGFyYWNpb24kcHJvZmVzaW9uX2ludGVyZXMKCmRhdG9zX21lcmNhZG9fZ3JhZmljbyA8LSBzYWxhcmlvc19tZXJjYWRvICU+JQogIGZpbHRlcihqb2JfdGl0bGUgJWluJSByb2xlc19ncnVwbykgJT4lCiAgbXV0YXRlKGpvYl90aXRsZSA9IGZhY3Rvcihqb2JfdGl0bGUsIGxldmVscyA9IG9yZGVuX3JvbGVzKSkKCiMgVGV4dG8gZGVsIHRvb2x0aXAgZGVsIHB1bnRvIGRlIGV4cGVjdGF0aXZhOiBhbCBwYXNhciBlbCBjdXJzb3IsIGVsIGxlY3RvcgojIHZlIGRlIHVuYSB2ZXogbGEgZXhwZWN0YXRpdmEgZGVsIGdydXBvLCBsYSBtZWRpYW5hIHJlYWwgeSBsYSBkaWZlcmVuY2lhLgpleHBlY3RhdGl2YV9ncmFmaWNvIDwtIGNvbXBhcmFjaW9uICU+JQogIG11dGF0ZSgKICAgIHByb2Zlc2lvbl9pbnRlcmVzID0gZmFjdG9yKHByb2Zlc2lvbl9pbnRlcmVzLCBsZXZlbHMgPSBvcmRlbl9yb2xlcyksCiAgICB0b29sdGlwID0gcGFzdGUwKAogICAgICBwcm9mZXNpb25faW50ZXJlcywKICAgICAgIlxuRXhwZWN0YXRpdmEgZGVsIGdydXBvOiBVU0QgIiwgY29tbWEobWVkaWFuYV9lc3BlcmFkYSwgYmlnLm1hcmsgPSAiLiIpLAogICAgICAiXG5NZWRpYW5hIHJlYWwgZGVsIG1lcmNhZG86IFVTRCAiLCBjb21tYShtZWRpYW5hX3JlYWwsIGJpZy5tYXJrID0gIi4iKSwKICAgICAgIlxuRGlmZXJlbmNpYTogVVNEICIsIGNvbW1hKGJyZWNoYSwgYmlnLm1hcmsgPSAiLiIpCiAgICApCiAgKQoKcF9jb21wYXJhY2lvbiA8LSBnZ3Bsb3QoZGF0b3NfbWVyY2Fkb19ncmFmaWNvLCBhZXMoeCA9IGpvYl90aXRsZSwgeSA9IHNhbGFyeV9pbl91c2QpKSArCiAgZ2VvbV9ib3hwbG90KGZpbGwgPSBncmlzLCBjb2xvciA9IG1vcmFkbywgb3V0bGllci5hbHBoYSA9IDAuMTUsIG91dGxpZXIuc2l6ZSA9IDEsIHdpZHRoID0gMC41NSkgKwogICMgRWwgcHVudG8gbWFnZW50YSBsbGV2YSB1biBhZXModGV4dCA9IC4uLik6IGFzw60gZ2dwbG90bHkgdXNhIEVTRSB0ZXh0byBjb21vCiAgIyB0b29sdGlwLCBlbiB2ZXogZGUgbGFzIGNvb3JkZW5hZGFzIGNydWRhcy4KICBnZW9tX3BvaW50KGRhdGEgPSBleHBlY3RhdGl2YV9ncmFmaWNvLAogICAgICAgICAgICAgYWVzKHggPSBwcm9mZXNpb25faW50ZXJlcywgeSA9IG1lZGlhbmFfZXNwZXJhZGEsIHRleHQgPSB0b29sdGlwKSwKICAgICAgICAgICAgIGNvbG9yID0gbWFnZW50YSwgc2l6ZSA9IDQpICsKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gbGFiZWxfZG9sbGFyKHByZWZpeCA9ICJVU0QgIiwgYmlnLm1hcmsgPSAiLiIpKSArCiAgY29vcmRfZmxpcCgpICsKICBsYWJzKAogICAgdGl0bGUgPSAiTGEgcGVyY2VwY2nDs24gc2FsYXJpYWwgZGVsIGdydXBvIHNlIHViaWNhIHBvciBkZWJham8gZGVsIG1lcmNhZG8gZ2xvYmFsIiwKICAgIHN1YnRpdGxlID0gIkNhamEgPSBkaXN0cmlidWNpw7NuIHJlYWwgZGVsIG1lcmNhZG8gwrcgUHVudG8gbWFnZW50YSA9IG1lZGlhbmEgZXNwZXJhZGEgcG9yIGVsIGdydXBvIChwYXNhIGVsIGN1cnNvciBwb3Igw6lsKSIsCiAgICB4ID0gTlVMTCwgeSA9ICJTYWxhcmlvIGFudWFsIChVU0QpIiwKICAgIGNhcHRpb24gPSAiTWVyY2FkbzogfjkwJSBkZSBsYXMgZW1wcmVzYXMgcmVwb3J0YWRhcyBlc3TDoW4gZW4gRUUuIFVVLiDigJQgbm8gZXMgdW5hIGNvbXBhcmFjacOzbiBkZSBwb2RlciBhZHF1aXNpdGl2byBsb2NhbC4iCiAgKSArCiAgdGVtYV9jbGFzZSArCiAgdGhlbWUocGFuZWwuZ3JpZC5tYWpvci55ID0gZWxlbWVudF9ibGFuaygpKQoKIyBJbnRlcmFjdGl2bzogbGEgY2FqYSBjb25zZXJ2YSBzdSBob3ZlciBjb24gbG9zIGN1YXJ0aWxlcyAobWluLCBRMSwgbWVkaWFuYSwKIyBRMywgbcOheCkgZGVsIG1lcmNhZG8gcmVhbCwgeSBlbCBwdW50byBtYWdlbnRhIG11ZXN0cmEgZWwgdGV4dG8gcXVlIGFybWFtb3MuCmdncGxvdGx5KHBfY29tcGFyYWNpb24sIHRvb2x0aXAgPSAidGV4dCIpICU+JSBlc3RpbG9fcGxvdGx5KCkKYGBgCgpgYGB7ciBicmVjaGF9CiMgVGFibGEgc29sbyBwYXJhIGVzdGUgZ3LDoWZpY28gKG5vIHRvY2Ftb3MgJ2NvbXBhcmFjaW9uJywgcXVlIHNlIHJldXRpbGl6YSBlbgojIGxvcyBoYWxsYXpnb3MpLiBPcmRlbmFtb3MgcG9yIGJyZWNoYSB5IHByZXBhcmFtb3MgZWwgdGV4dG8gZGVsIHRvb2x0aXAuCmNvbXBhcmFjaW9uX2dyYWZpY28gPC0gY29tcGFyYWNpb24gJT4lCiAgbXV0YXRlKAogICAgcHJvZmVzaW9uX2ludGVyZXMgPSBmY3RfcmVvcmRlcihwcm9mZXNpb25faW50ZXJlcywgYnJlY2hhKSwKICAgIHRvb2x0aXAgPSBwYXN0ZTAoCiAgICAgIHByb2Zlc2lvbl9pbnRlcmVzLAogICAgICAiXG5CcmVjaGE6IC1VU0QgIiwgY29tbWEoYWJzKGJyZWNoYSksIGJpZy5tYXJrID0gIi4iKSwgIiAoIiwgYWJzKGJyZWNoYV9wY3QpLCAiJSkiLAogICAgICAiXG5Fc3BlcmFkbzogVVNEICIsIGNvbW1hKG1lZGlhbmFfZXNwZXJhZGEsIGJpZy5tYXJrID0gIi4iKSwKICAgICAgIiDCtyBNZXJjYWRvOiBVU0QgIiwgY29tbWEobWVkaWFuYV9yZWFsLCBiaWcubWFyayA9ICIuIikKICAgICkKICApCgpwX2JyZWNoYSA8LSBnZ3Bsb3QoY29tcGFyYWNpb25fZ3JhZmljbywgYWVzKHggPSBicmVjaGEsIHkgPSBwcm9mZXNpb25faW50ZXJlcykpICsKICBnZW9tX3NlZ21lbnQoYWVzKHggPSAwLCB4ZW5kID0gYnJlY2hhLCB5ZW5kID0gcHJvZmVzaW9uX2ludGVyZXMpLCBjb2xvciA9IGdyaXMsIGxpbmV3aWR0aCA9IDQpICsKICBnZW9tX3BvaW50KGFlcyh0ZXh0ID0gdG9vbHRpcCksIGNvbG9yID0gbWFnZW50YSwgc2l6ZSA9IDQpICsKICBzY2FsZV94X2NvbnRpbnVvdXMobGFiZWxzID0gbGFiZWxfZG9sbGFyKHByZWZpeCA9ICJVU0QgIiwgYmlnLm1hcmsgPSAiLiIpLCBleHBhbmQgPSBleHBhbnNpb24obXVsdCA9IGMoMC4xLCAwLjA1KSkpICsKICBsYWJzKAogICAgdGl0bGUgPSAiRW4gdG9kb3MgbG9zIHJvbGVzLCBsYSBwZXJjZXBjacOzbiBkZWwgZ3J1cG8gc2UgdWJpY2EgcG9yIGRlYmFqbyBkZWwgbWVyY2FkbyBnbG9iYWwiLAogICAgc3VidGl0bGUgPSAiRGlmZXJlbmNpYSBlbnRyZSBsYSBtZWRpYW5hIGVzcGVyYWRhIHBvciBlbCBncnVwbyB5IGxhIG1lZGlhbmEgcmVhbCBkZWwgbWVyY2FkbyAocGFzYSBlbCBjdXJzb3IgcG9yIGNhZGEgcHVudG8pIiwKICAgIHggPSAiQnJlY2hhIChVU0QvYcOxbykiLCB5ID0gTlVMTCwKICAgIGNhcHRpb24gPSAiQnJlY2hhID0gbWVkaWFuYSBlc3BlcmFkYSAoZW5jdWVzdGEpIOKIkiBtZWRpYW5hIHJlYWwgKG1lcmNhZG8gZ2xvYmFsLCBtYXlvcml0YXJpYW1lbnRlIEVFLiBVVS4pLiIKICApICsKICB0ZW1hX2NsYXNlICsKICB0aGVtZShwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2JsYW5rKCkpCgpnZ3Bsb3RseShwX2JyZWNoYSwgdG9vbHRpcCA9ICJ0ZXh0IikgJT4lIGVzdGlsb19wbG90bHkoKQpgYGAKCiMgNi4gUHJpbmNpcGFsZXMgaGFsbGF6Z29zCgpgYGB7ciBoYWxsYXpnby0xLCByZXN1bHRzPSdhc2lzJ30Kcm9sX21hc19wYWdhZG8gPC0gdG9wX3BhZ2Fkb3MgJT4lIHNsaWNlX21heChtZWRpYW5hLCBuID0gMSwgd2l0aF90aWVzID0gRkFMU0UpCnJvbF9tYXNfY29tdW4gPC0gdG9wX2ZyZWN1ZW50ZXMgJT4lIHNsaWNlX21heChuLCBuID0gMSwgd2l0aF90aWVzID0gRkFMU0UpCnBhaXNfbWVqb3JfcGFnYWRvIDwtIHJlc3VtZW5fcGFpc2VzICU+JSBmaWx0ZXIobiA+PSB1bWJyYWxfbikgJT4lIHNsaWNlX21heChtZWRpYW5hLCBuID0gMSwgd2l0aF90aWVzID0gRkFMU0UpCgpjYXQocGFzdGUwKAogICItIEVsIHJvbCBtw6FzIGNvbcO6biBkZWwgbWVyY2FkbyBlcyAqKiIsIHJvbF9tYXNfY29tdW4kam9iX3RpdGxlLCAiKiogKCIsCiAgY29tbWEocm9sX21hc19jb211biRuLCBiaWcubWFyayA9ICIuIiksICIgcmVnaXN0cm9zKSwgeSBlbCBtZWpvciBwYWdhZG8gKGVudHJlIHJvbGVzIGNvbiBhbCBtZW5vcyAxMDAgcmVnaXN0cm9zKSBlcyAqKiIsCiAgcm9sX21hc19wYWdhZG8kam9iX3RpdGxlLCAiKiogKG1lZGlhbmEgVVNEICIsIGNvbW1hKHJvbF9tYXNfcGFnYWRvJG1lZGlhbmEsIGJpZy5tYXJrID0gIi4iKSwgIikuXG4iCikpCgpjYXQocGFzdGUwKAogICItIEVudHJlIGxvcyBwYcOtc2VzIGNvbiBkYXRvcyBzdWZpY2llbnRlcyAobiDiiaUgIiwgdW1icmFsX24sICIpLCAqKiIsIHBhaXNfbWVqb3JfcGFnYWRvJHBhaXNfZW1wcmVzYSwKICAiKiogdGllbmUgbGEgbWVkaWFuYSBzYWxhcmlhbCBtw6FzIGFsdGEgKFVTRCAiLCBjb21tYShwYWlzX21lam9yX3BhZ2FkbyRtZWRpYW5hLCBiaWcubWFyayA9ICIuIiksICIpLlxuIgopKQoKY2F0KHBhc3RlMCgKICAiLSBFbCBzYWxhcmlvIG1lZGlhbm8gZ2xvYmFsIHN1YmnDsyBkZSBVU0QgIiwgY29tbWEocG9yX2FuaW8kbWVkaWFuYVtwb3JfYW5pbyR3b3JrX3llYXIgPT0gbWluKHBvcl9hbmlvJHdvcmtfeWVhcildLCBiaWcubWFyayA9ICIuIiksCiAgIiBlbiAiLCBtaW4ocG9yX2FuaW8kd29ya195ZWFyKSwgIiBhIFVTRCAiLCBjb21tYShwb3JfYW5pbyRtZWRpYW5hW3Bvcl9hbmlvJHdvcmtfeWVhciA9PSBtYXgocG9yX2FuaW8kd29ya195ZWFyKV0sIGJpZy5tYXJrID0gIi4iKSwKICAiIGVuICIsIG1heChwb3JfYW5pbyR3b3JrX3llYXIpLCAiIChsb3MgcHJpbWVyb3MgYcOxb3MgdGllbmVuIG11eSBwb2NvcyByZWdpc3Ryb3MsIHZlciBzZWNjacOzbiA0LjIpLlxuIgopKQoKcm9sX21heW9yX2JyZWNoYSA8LSBjb21wYXJhY2lvbiAlPiUgc2xpY2VfbWluKGJyZWNoYSwgbiA9IDEsIHdpdGhfdGllcyA9IEZBTFNFKQpyb2xfbWVub3JfYnJlY2hhIDwtIGNvbXBhcmFjaW9uICU+JSBzbGljZV9tYXgoYnJlY2hhLCBuID0gMSwgd2l0aF90aWVzID0gRkFMU0UpCgpjYXQocGFzdGUwKAogICItIEZyZW50ZSBhbCBtZXJjYWRvLCBsYSBicmVjaGEgbcOhcyBncmFuZGUgZGVsIGdydXBvIGVzdMOhIGVuICoqIiwgcm9sX21heW9yX2JyZWNoYSRwcm9mZXNpb25faW50ZXJlcywgIioqICgtIiwKICBsYWJlbF9kb2xsYXIocHJlZml4ID0gIlVTRCAiLCBiaWcubWFyayA9ICIuIikoYWJzKHJvbF9tYXlvcl9icmVjaGEkYnJlY2hhKSksCiAgIiksIG1pZW50cmFzIHF1ZSBsYSBtw6FzIHBlcXVlw7FhIGVzdMOhIGVuICoqIiwKICByb2xfbWVub3JfYnJlY2hhJHByb2Zlc2lvbl9pbnRlcmVzLCAiKiogKC0iLAogIGxhYmVsX2RvbGxhcihwcmVmaXggPSAiVVNEICIsIGJpZy5tYXJrID0gIi4iKShhYnMocm9sX21lbm9yX2JyZWNoYSRicmVjaGEpKSwgIikuXG4iCikpCmBgYAoKIyA3LiBMaW1pdGFjaW9uZXMKCi0gICAqKkNvbmNlbnRyYWNpw7NuIGdlb2dyw6FmaWNhKio6IFx+OTAlIGRlIGxvcyByZWdpc3Ryb3Mgc29uIGRlIGVtcHJlc2FzIGVuIEVFLiBVVS4gRWwgbWFwYSB5IGxhcyBjb21wYXJhY2lvbmVzIHBvciBwYcOtcyByZWZsZWphbiBzb2JyZSB0b2RvIGVzZSBtZXJjYWRvLCBubyB1biBwcm9tZWRpbyBlcXVpbGlicmFkbyBkZWwgbXVuZG8uCi0gICAqKkF1dG9ycmVwb3J0ZSoqOiBsb3MgZGF0b3MgdmllbmVuIGRlIHBlcnNvbmFzIHF1ZSBsb3MgcmVwb3J0YW4gdm9sdW50YXJpYW1lbnRlIGVuIGFpam9icy5uZXQg4oCUIG5vIGVzIHVuIGNlbnNvIG5pIHVuYSBtdWVzdHJhIGFsZWF0b3JpYSBkZWwgbWVyY2FkbyBsYWJvcmFsIGRlIGRhdG9zLgotICAgKipgam9iX3RpdGxlYCBlcyB0ZXh0byBsaWJyZSoqOiBsYSBmdWVudGUgb3JpZ2luYWwgbm8gdXNhIHVuYSBsaXN0YSBjZXJyYWRhIGRlIGNhcmdvcyDigJQgbcOhcyBkZSAzMDAgdmFyaWFudGVzIGRpc3RpbnRhcy4gU29sbyBjb3JyZWdpbW9zIHVuIHR5cG8gZXZpZGVudGUgKCJEYXRhIEFuYWxpc3QiIOKGkiAiRGF0YSBBbmFseXN0Iik7IHZhcmlhbnRlcyBkZSBzZW5pb3JpdHkgKGVqLiAiRGF0YSBTY2llbnRpc3QiIHZzLiAiU3RhZmYgRGF0YSBTY2llbnRpc3QiKSBzZSBkZWphcm9uIHNlcGFyYWRhcyBhIHByb3DDs3NpdG8uCi0gICAqKkNhdGVnb3LDrWFzIGNvbiBwb2NvcyByZWdpc3Ryb3MqKjogbWVkaW8gdGllbXBvLCBmcmVlbGFuY2UvY29udHJhdG8geSBtb2RhbGlkYWQgaMOtYnJpZGEgdGllbmVuIG11eSBwb2NhcyBmaWxhcyAodmVyIHNlY2Npw7NuIDQuMSkg4oCUIGN1YWxxdWllciBsZWN0dXJhIHNvYnJlIGVsbGFzIGVzIG1lbm9zIGNvbmZpYWJsZSBxdWUgc29icmUgdGllbXBvIGNvbXBsZXRvL1Nlbmlvci9wcmVzZW5jaWFsLXJlbW90by4KLSAgICoqU2FsYXJpbyB5YSBjb252ZXJ0aWRvIGEgVVNEIHBvciBsYSBmdWVudGUqKjogbm8gY29udHJvbGFtb3MgY29uIHF1w6kgdGFzYSBkZSBjYW1iaW8gbmkgZW4gcXXDqSBmZWNoYSBzZSBoaXpvIGVzYSBjb252ZXJzacOzbi4KLSAgICoqQ29ycmVsYWNpw7NuIG5vIGltcGxpY2EgY2F1c2FsaWRhZCoqOiBxdWUgZWwgc2FsYXJpbyBzdWJhIGNvbiBsYSBleHBlcmllbmNpYSBvIHZhcsOtZSBwb3IgcGHDrXMgZGVzY3JpYmUgdW4gcGF0csOzbiBvYnNlcnZhZG8sIG5vIHVuYSByZWxhY2nDs24gZGUgY2F1c2EtZWZlY3RvIGFpc2xhZGEgZGUgb3Ryb3MgZmFjdG9yZXMuCi0gICAqKlZhbG9yZXMgaW1wdXRhZG9zIGVuIGxhIGVuY3Vlc3RhKio6IHVuYXMgcG9jYXMgcmVzcHVlc3RhcyBkZSBzYWxhcmlvIGVzcGVyYWRvIHF1ZSBxdWVkYXJvbiBlbiBibGFuY28gc2UgcmVsbGVuYXJvbiBjb24gbGEgbWVkaWFuYSBkZWwgZ3J1cG8gKGNvbHVtbmEgYHNhbGFyaW9faW1wdXRhZG9gKSDigJQgc2UgZXhjbHV5ZXJvbiBleHBsw61jaXRhbWVudGUgZGUgbGEgc2VjY2nDs24gNSBwYXJhIG5vIHNlc2dhciBlc2EgY29tcGFyYWNpw7NuIHB1bnR1YWwuCgojIDguIENvbmNsdXNpb25lcwoKRWwgbWVyY2FkbyBnbG9iYWwgZGUgZGF0b3MgbXVlc3RyYSB1biBwYXRyw7NuIGNsYXJvOiBlbCBzYWxhcmlvIHN1YmUgZGUgZm9ybWEgY29uc2lzdGVudGUgY29uIGxhIGV4cGVyaWVuY2lhLCBzZSBjb25jZW50cmEgZW4gdW4gcHXDsWFkbyBkZSByb2xlcyAoRGF0YSBTY2llbnRpc3QsIERhdGEgRW5naW5lZXIsIFNvZnR3YXJlIEVuZ2luZWVyKSB5IHZhcsOtYSBmdWVydGVtZW50ZSBwb3IgcGHDrXMg4oCUIGNvbiBFc3RhZG9zIFVuaWRvcyBkb21pbmFuZG8gdGFudG8gZWwgdm9sdW1lbiBkZSBkYXRvcyBjb21vLCBlbiBnZW5lcmFsLCBsb3Mgc2FsYXJpb3MgcmVwb3J0YWRvcy4gTGEgbW9kYWxpZGFkIGRlIHRyYWJham8sIGVuIGNhbWJpbywgbm8gbXVlc3RyYSB1bmEgZGlmZXJlbmNpYSBzYWxhcmlhbCBjbGFyYS4KCkZyZW50ZSBhIGVzdGUgcGFub3JhbWEsIGxhIHBlcmNlcGNpw7NuIHNhbGFyaWFsIGRlIG51ZXN0cm8gZ3J1cG8gc2UgdWJpY2EgcG9yIGRlYmFqbyBkZWwgbWVyY2FkbyBnbG9iYWwgZW4gbG9zIHJvbGVzIGRlIGludGVyw6lzIOKAlCB1bmEgZGlmZXJlbmNpYSBlc3BlcmFibGUgZGFkbyBlbCBmdWVydGUgcGVzbyBkZSBFRS4gVVUuIGVuIGxhIGZ1ZW50ZSwgbcOhcyBxdWUgdW5hIHNlw7FhbCBkZSBleHBlY3RhdGl2YXMgZXF1aXZvY2FkYXMuCgpFbCBlamVyY2ljaW8gY29tcGxldG8g4oCUZGVzZGUgZXhwbG9yYXIgYSBmb25kbyB1bmEgZnVlbnRlIGV4dGVybmEgaGFzdGEgY29uc3RydWlyIHVuIG1hcGEgeSB0YWJsYXMgaW50ZXJhY3RpdmFzLCB5IGNlcnJhciBjb24gdW5hIGNvbXBhcmFjacOzbiBob25lc3Rh4oCUIGVzIHRhbWJpw6luIGVsIHB1bnRvIGRlIHBhcnRpZGEgbWV0b2RvbMOzZ2ljbyBwYXJhIGVsIHByb3llY3RvIHRyYW5zdmVyc2FsIGRlbCBjdXJzbzogbGEgbWlzbWEgbMOzZ2ljYSAocHJlZ3VudGEg4oaSIGVzdGFkw61zdGljYSDihpIgdmlzdWFsaXphY2nDs24g4oaSIGludGVycHJldGFjacOzbiDihpIgY29tdW5pY2FjacOzbikgYXBsaWNhIGlndWFsIGRlIGJpZW4gYSBjdWFscXVpZXIgb3RybyBkYXRhc2V0IHF1ZSBjYWRhIGVzdHVkaWFudGUgZWxpamEgcGFyYSBzdSBwcm9waW8gcHJveWVjdG8gZmluYWwuCg==