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(
"C:/Users/EQUIPO 3/Desktop/Especializacion/Visualizacion/Visualizacion/Input/encuesta_estudiantes_consolidada_limpia.csv",
show_col_types = FALSE
)
salarios_mercado <- read_csv(
"C:/Users/EQUIPO 3/Desktop/Especializacion/Visualizacion/Visualizacion/Input/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.
LS0tDQp0aXRsZTogIkVsIG1lcmNhZG8gZ2xvYmFsIGRlIGRhdG9zLCBlbiBjaWZyYXMgcmVhbGVzIg0Kc3VidGl0bGU6ICJTYWxhcmlvcywgcm9sZXMgeSBnZW9ncmFmw61hIGRlIGxhIGluZHVzdHJpYSDigJQgVmlzdWFsaXphY2nDs24gZGUgRGF0b3MsIEtvbnJhZCBMb3JlbnoiDQphdXRob3I6ICJFc3RlZmFueSBNZWRlbGxpbiINCmRhdGU6ICJgciBmb3JtYXQoU3lzLkRhdGUoKSwgJyVkIGRlICVCIGRlICVZJylgIg0Kb3V0cHV0Og0KICBodG1sX2RvY3VtZW50Og0KICAgIHRvYzogdHJ1ZQ0KICAgIHRvY19mbG9hdDogdHJ1ZQ0KICAgIHRoZW1lOiBmbGF0bHkNCiAgICBkZl9wcmludDoga2FibGUNCiAgICBjb2RlX2ZvbGRpbmc6IGhpZGUNCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlICAgIyBhZ3JlZ2EgdW4gYm90w7NuIHBhcmEgZGVzY2FyZ2FyIGVsIC5SbWQgb3JpZ2luYWwgLS0gcmVmdWVyemEgbGEgcmVwcm9kdWNpYmlsaWRhZA0KLS0tDQoNCg0KYGBgez1odG1sfQ0KPHN0eWxlPg0KLyogVGlwb2dyYWbDrWEgZWRpdG9yaWFsIGRlbCBjdXJzbyAobWlzbWEgcXVlIGxsZXZhbiBsb3MgZ3LDoWZpY29zIHbDrWENCiAgIHNob3d0ZXh0KSB0YW1iacOpbiBwYXJhIGVsIHRleHRvIGRlbCBkb2N1bWVudG8gLS0gcXVlIHRvZG8gZWwgaW5mb3JtZQ0KICAgc2Ugc2llbnRhIGNvbW8gdW5hIHNvbGEgcGllemEsIG5vIHVuIEhUTUwgZ2Vuw6lyaWNvIGNvbiBncsOhZmljb3MgcGVnYWRvcy4gKi8NCkBpbXBvcnQgdXJsKCdodHRwczovL2ZvbnRzLmdvb2dsZWFwaXMuY29tL2NzczI/ZmFtaWx5PUZpcmErU2Fuczp3Z2h0QDQwMDs2MDA7NzAwJmRpc3BsYXk9c3dhcCcpOw0KDQpib2R5IHsgZm9udC1mYW1pbHk6ICdGaXJhIFNhbnMnLCAtYXBwbGUtc3lzdGVtLCBCbGlua01hY1N5c3RlbUZvbnQsIHNhbnMtc2VyaWY7IGNvbG9yOiAjMkIyNTMwOyB9DQpoMSwgaDIsIGgzIHsgZm9udC1mYW1pbHk6ICdGaXJhIFNhbnMnLCBzYW5zLXNlcmlmOyBjb2xvcjogIzM1MUE0RjsgfQ0KaDEgeyBib3JkZXItYm90dG9tOiAzcHggc29saWQgI0Q5MDA2QzsgcGFkZGluZy1ib3R0b206IDhweDsgbWFyZ2luLXRvcDogNDhweDsgfQ0KaDIgeyBtYXJnaW4tdG9wOiAzNnB4OyB9DQphIHsgY29sb3I6ICNEOTAwNkM7IH0NCmE6aG92ZXIgeyBjb2xvcjogIzE2OEM4NjsgfQ0KI1RPQyB7IGJvcmRlci1yYWRpdXM6IDEwcHg7IH0NCi5uYXYtcGlsbHMgPiBsaS5hY3RpdmUgPiBhLCAubmF2LXBpbGxzID4gbGkuYWN0aXZlID4gYTpob3ZlciB7IGJhY2tncm91bmQtY29sb3I6ICMzNTFBNEYgIWltcG9ydGFudDsgfQ0KY2FwdGlvbiB7IGNvbG9yOiAjNUE1NTYwOyBmb250LXN0eWxlOiBpdGFsaWM7IGNhcHRpb24tc2lkZTogdG9wOyB9DQpjb2RlIHsgY29sb3I6ICM4QTFBNUM7IGJhY2tncm91bmQ6ICNGNkVFRjI7IHBhZGRpbmc6IDFweCA1cHg7IGJvcmRlci1yYWRpdXM6IDRweDsgfQ0KdGFibGUuZGF0YVRhYmxlIHsgZm9udC1zaXplOiAwLjkycmVtOyB9DQoubGVhZmxldC1jb250YWluZXIgeyBib3JkZXItcmFkaXVzOiAxMHB4OyBiYWNrZ3JvdW5kOiAjRUFGMEY0ICFpbXBvcnRhbnQ7IH0NCjwvc3R5bGU+DQpgYGANCg0KOjo6Ojo6OiB7c3R5bGU9ImJhY2tncm91bmQ6IGxpbmVhci1ncmFkaWVudCgxMzVkZWcsICMzNTFBNEYgMCUsICNEOTAwNkMgMTAwJSk7IHBhZGRpbmc6IDI2cHggMzBweDsgYm9yZGVyLXJhZGl1czogMTRweDsgbWFyZ2luLWJvdHRvbTogMzBweDsgZGlzcGxheTogZmxleDsgYWxpZ24taXRlbXM6IGNlbnRlcjsganVzdGlmeS1jb250ZW50OiBzcGFjZS1iZXR3ZWVuOyBmbGV4LXdyYXA6IHdyYXA7IGdhcDogMThweDsifQ0KOjo6Ojoge3N0eWxlPSJtYXgtd2lkdGg6IDYyMHB4OyJ9DQo6Ojoge3N0eWxlPSJjb2xvcjogd2hpdGU7IGZvbnQtc2l6ZTogMS41NXJlbTsgZm9udC13ZWlnaHQ6IDcwMDsgbGluZS1oZWlnaHQ6IDEuMjU7In0NCkVsIG1lcmNhZG8gZ2xvYmFsIGRlIGRhdG9zLCBlbiBjaWZyYXMgcmVhbGVzDQo6OjoNCg0KOjo6IHtzdHlsZT0iY29sb3I6ICNGNERDRTk7IGZvbnQtc2l6ZTogMXJlbTsgbWFyZ2luLXRvcDogNnB4OyJ9DQpTYWxhcmlvcywgcm9sZXMgeSBnZW9ncmFmw61hIGRlIGxhIGluZHVzdHJpYSBkZSBkYXRvcyDigJQgeSwgYWwgZmluYWwsIGPDs21vIHNlIGNvbXBhcmEgbnVlc3RybyBncnVwbw0KOjo6DQo6Ojo6Og0KDQo6Ojoge3N0eWxlPSJiYWNrZ3JvdW5kOiB3aGl0ZTsgcGFkZGluZzogMTBweCAxOHB4OyBib3JkZXItcmFkaXVzOiAxMHB4OyBmbGV4LXNocmluazogMDsifQ0KPGltZyBzcmM9ImltZy9rb25yYWRfbG9nby5wbmciIHN0eWxlPSJtYXgtd2lkdGg6IDIxMHB4OyBkaXNwbGF5OiBibG9jazsiLz4NCjo6Og0KOjo6Ojo6Og0KDQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0NCiMgaW5jbHVkZT1GQUxTRTogZXN0ZSBjaHVuayBjb25maWd1cmEgZWwgZG9jdW1lbnRvIHBlcm8gbm8gYXBhcmVjZSBlbiBlbA0KIyBIVE1MIGZpbmFsIC0tIG5pIGVsIGPDs2RpZ28gbmkgbmluZ8O6biByZXN1bHRhZG8uDQojDQojIGtuaXRyOjpvcHRzX2NodW5rJHNldCgpIGNhbWJpYSBlbCBjb21wb3J0YW1pZW50byBkZSBUT0RPUyBsb3MgY2h1bmtzDQojIHF1ZSB2aWVuZW4gZGVzcHXDqXMsIGRlIHVuYSBzb2xhIHZleiwgZW4gdmV6IGRlIHJlcGV0aXIgbGEgbWlzbWEgb3BjacOzbg0KIyBjaHVuayBwb3IgY2h1bmsuDQprbml0cjo6b3B0c19jaHVuayRzZXQoDQogIGVjaG8gPSBUUlVFLCAgICAgICAgIyBtdWVzdHJhIGVsIGPDs2RpZ28gZGUgUiBlbiBlbCBIVE1MIChjb24gImNvZGVfZm9sZGluZzogaGlkZSIgYXJyaWJhLCBxdWVkYSBvY3VsdG8gcG9yIGRlZmVjdG8gcGVybyBzZSBwdWVkZSBkZXNwbGVnYXIpDQogIHdhcm5pbmcgPSBGQUxTRSwgICAgICMgbm8gbXVlc3RyYSB3YXJuaW5ncyBkZSBSIGVuIGVsIHJlcG9ydGUNCiAgbWVzc2FnZSA9IEZBTFNFLCAgICAgIyBubyBtdWVzdHJhIG1lbnNhamVzIGluZm9ybWF0aXZvcyAoY29tbyBsb3MgZGUgbGlicmFyeSgpKQ0KICBmaWcuYWxpZ24gPSAiY2VudGVyIiwNCiAgZmlnLndpZHRoID0gOCwNCiAgZmlnLmhlaWdodCA9IDUNCikNCmBgYA0KDQoNCmBgYHtyIGxpYnJlcmlhc30NCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShzY2FsZXMpICAgICAgICAgIyBmb3JtYXRlYSBuw7ptZXJvcyBjb21vIG1vbmVkYS9wb3JjZW50YWplIGVuIGxhcyBldGlxdWV0YXMgZGUgbG9zIGdyw6FmaWNvcw0KbGlicmFyeShzaG93dGV4dCkgICAgICAgIyBkaWJ1amEgZWwgdGV4dG8gZGUgbG9zIGdyw6FmaWNvcyBjb24gdGlwb2dyYWbDrWFzIGRlIEdvb2dsZSBGb250cw0KbGlicmFyeShzeXNmb250cykgICAgICAgIyByZWdpc3RyYSBsYSB0aXBvZ3JhZsOtYSBhbnRlcyBkZSBxdWUgc2hvd3RleHQgbGEgdXNlDQpsaWJyYXJ5KGNvdW50cnljb2RlKSAgICAjIHRyYWR1Y2UgY8OzZGlnb3MgZGUgcGHDrXMgKElTTyBkZSAyIGxldHJhcykgYSBub21icmVzIGxlZ2libGVzLCBlbiBlc3Bhw7FvbA0KbGlicmFyeShEVCkgICAgICAgICAgICAgIyB0YWJsYXMgSFRNTCBpbnRlcmFjdGl2YXM6IGJ1c2NhYmxlcywgb3JkZW5hYmxlcyB5IGNvbiBjb2xvciBwb3IgY2VsZGENCmxpYnJhcnkobGVhZmxldCkgICAgICAgICMgbWFwYXMgaW50ZXJhY3Rpdm9zICh6b29tLCBwYW5lbywgdG9vbHRpcHMpIGRpcmVjdG8gZW4gZWwgSFRNTA0KbGlicmFyeShzZikgICAgICAgICAgICAgIyByZXByZXNlbnRhIGxvcyBwYcOtc2VzIGNvbW8gcG9sw61nb25vcyAoZGF0b3MgZXNwYWNpYWxlcyksIHBhcmEgZWwgbWFwYQ0KbGlicmFyeShybmF0dXJhbGVhcnRoKSAgIyB0cmFlIGxvcyBwb2zDrWdvbm9zIHJlYWxlcyBkZSBsb3MgcGHDrXNlcyBkZWwgbXVuZG8NCmxpYnJhcnkocGxvdGx5KSAgICAgICAgICMgY29udmllcnRlIHVuIGdyw6FmaWNvIGRlIGdncGxvdCBlbiB1bm8gaW50ZXJhY3Rpdm8gKGhvdmVyLCB6b29tLCBwYW5uaW5nKQ0KbGlicmFyeShrYWJsZUV4dHJhKSAgICAgIyBkYSBlc3RpbG8gZWRpdG9yaWFsIGEgbGFzIHRhYmxhcyBlc3TDoXRpY2FzIChlbmNhYmV6YWRvIGNvbiBjb2xvciwgaG92ZXIsIHJlc2FsdGFyIGZpbGFzKQ0KIyBybmF0dXJhbGVhcnRoZGF0YSBzb2xvIG5lY2VzaXRhIGVzdGFyIGluc3RhbGFkbyAtLSBndWFyZGEgbG9zIGRhdG9zIHF1ZQ0KIyBybmF0dXJhbGVhcnRoOjpuZV9jb3VudHJpZXMoKSB1c2EgcG9yIGRlbnRybzsgbm8gaGFjZSBmYWx0YSBjYXJnYXJsbyBjb24gbGlicmFyeSgpLg0KDQpmdWVudGVfZWRpdG9yaWFsIDwtICJGaXJhIFNhbnMiDQpmb250X2FkZF9nb29nbGUobmFtZSA9IGZ1ZW50ZV9lZGl0b3JpYWwsIGZhbWlseSA9IGZ1ZW50ZV9lZGl0b3JpYWwpDQpzaG93dGV4dF9hdXRvKCkNCmBgYA0KDQoNCmBgYHtyIGRhdG9zfQ0KIyBSdXRhcyBSRUxBVElWQVMgYSBsYSB1YmljYWNpw7NuIGRlIGVzdGUgLlJtZCAtLSBrbml0ciBlamVjdXRhIGNhZGEgaW5mb3JtZQ0KIyBkZXNkZSBzdSBwcm9waWEgY2FycGV0YSwgYXPDrSBxdWUgIi4uLyIgc3ViZSBhICJDb2RpZ29zLyIgeSBkZSBhaMOtIGJhamFtb3MgYQ0KIyBsYXMgY2FycGV0YXMgZGUgZGF0b3MuIENvbiBlc3RvIGVsIGluZm9ybWUgY29ycmUgZW4gQ1VBTFFVSUVSIGNvbXB1dGFkb3IgcXVlDQojIHRlbmdhIGVsIHByb3llY3RvLCBubyBzb2xvIGVuIGVsIGVxdWlwbyBkb25kZSBzZSBlc2NyaWJpw7M6IHJlcHJvZHVjaWJpbGlkYWQNCiMgZGUgdmVyZGFkIChqdXN0byBlbCBwcmluY2lwaW8gcXVlIHByZWRpY2EgbGEgaW50cm9kdWNjacOzbikuDQplc3R1ZGlhbnRlcyA8LSByZWFkX2NzdigNCiAgIkM6L1VzZXJzL0VRVUlQTyAzL0Rlc2t0b3AvRXNwZWNpYWxpemFjaW9uL1Zpc3VhbGl6YWNpb24vVmlzdWFsaXphY2lvbi9JbnB1dC9lbmN1ZXN0YV9lc3R1ZGlhbnRlc19jb25zb2xpZGFkYV9saW1waWEuY3N2IiwNCiAgc2hvd19jb2xfdHlwZXMgPSBGQUxTRQ0KKQ0KDQpzYWxhcmlvc19tZXJjYWRvIDwtIHJlYWRfY3N2KA0KICAiQzovVXNlcnMvRVFVSVBPIDMvRGVza3RvcC9Fc3BlY2lhbGl6YWNpb24vVmlzdWFsaXphY2lvbi9WaXN1YWxpemFjaW9uL0lucHV0L3NhbGFyaWVzLXNlbGVjdGVkLWNvbHVtbnMuY3N2IiwNCiAgc2hvd19jb2xfdHlwZXMgPSBGQUxTRQ0KKQ0KYGBgDQoNCg0KYGBge3IgZXN0aWxvfQ0KIyBNaXNtYSBwYWxldGEgZGUgdG9kbyBlbCBjdXJzbyAoQ2xhc2UgMiBlbiBhZGVsYW50ZSkgLS0gc2UgZGVmaW5lIHVuYQ0KIyBzb2xhIHZleiBhcXXDrSB5IHNlIHJldXRpbGl6YSBlbiBjYWRhIGdyw6FmaWNvIGRlbCBpbmZvcm1lLg0KbW9yYWRvICAgPC0gIiMzNTFBNEYiDQptYWdlbnRhICA8LSAiI0Q5MDA2QyINCnR1cnF1ZXNhIDwtICIjMTY4Qzg2Ig0KZ3JpcyAgICAgPC0gIiNEOUQ2REQiDQoNCnRlbWFfY2xhc2UgPC0gdGhlbWVfbWluaW1hbChiYXNlX3NpemUgPSAxMikgKw0KICB0aGVtZSgNCiAgICB0ZXh0ID0gZWxlbWVudF90ZXh0KGZhbWlseSA9IGZ1ZW50ZV9lZGl0b3JpYWwpLCAgIyBtaXNtYSB0aXBvZ3JhZsOtYSBlZGl0b3JpYWwgZW4gVE9ET1MgbG9zIGdyw6FmaWNvcywgbm8gc29sbyBlbiB1bm8gcHVudHVhbA0KICAgIHBsb3QudGl0bGUucG9zaXRpb24gPSAicGxvdCIsDQogICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiLCBzaXplID0gMTUsIGhqdXN0ID0gMCksDQogICAgcGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTAuNSwgY29sb3IgPSAiZ3JleTM1IiwgaGp1c3QgPSAwKSwNCiAgICBwYW5lbC5ncmlkLm1pbm9yID0gZWxlbWVudF9ibGFuaygpLA0KICAgIHBsb3QuY2FwdGlvbiA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAsIGNvbG9yID0gImdyZXk0NSIsIHNpemUgPSA4LjUpLA0KICAgIHBsb3QubWFyZ2luID0gbWFyZ2luKDEwLCAyMCwgMTAsIDEwKQ0KICApDQoNCiMgQWNhYmFkbyBjb23Dum4gcGFyYSBUT0RPUyBsb3MgZ3LDoWZpY29zIGludGVyYWN0aXZvcyBkZWwgaW5mb3JtZS4gU2UgZGVmaW5lDQojIHVuYSB2ZXogeSBzZSBhcGxpY2EgYWwgZmluYWwgZGUgY2FkYSBnZ3Bsb3RseSgpIGNvbiAlPiUgZXN0aWxvX3Bsb3RseSgpLg0KIyBEb3MgY29zYXM6ICgxKSBvY3VsdGEgbGEgYmFycmEgZGUgaGVycmFtaWVudGFzIGRlIHBsb3RseSAoem9vbSwgbGFzc28uLi4pLA0KIyBxdWUgZGlzdHJhZSB5IG5vIGFwb3J0YSBhIGxhIGxlY3R1cmE7ICgyKSBmaWphIGxhIHRpcG9ncmFmw61hIGVkaXRvcmlhbCBkZWwNCiMgY3Vyc28gLS0gc2hvd3RleHQgZGlidWphIGxhIGZ1ZW50ZSBlbiBsb3MgZ3LDoWZpY29zIEVTVMOBVElDT1MsIHBlcm8gbm8gdmlhamENCiMgYSBwbG90bHksIGFzw60gcXVlIHNpbiBlc3RvIGxvcyBpbnRlcmFjdGl2b3Mgc2FsZHLDrWFuIGNvbiB1bmEgZnVlbnRlIGRpc3RpbnRhLg0KZXN0aWxvX3Bsb3RseSA8LSBmdW5jdGlvbihwKSB7DQogIHAgJT4lDQogICAgY29uZmlnKGRpc3BsYXlNb2RlQmFyID0gRkFMU0UpICU+JSAgICAgICAgICAjIGNvbmZpZygpOiBjb250cm9sYSBsb3MgYm90b25lcyBkZSBsYSBiYXJyYSBkZSBwbG90bHkNCiAgICBsYXlvdXQoZm9udCA9IGxpc3QoZmFtaWx5ID0gZnVlbnRlX2VkaXRvcmlhbCkpICMgbGF5b3V0KCk6IGFqdXN0YSBwcm9waWVkYWRlcyBnbG9iYWxlcyBkZWwgZ3LDoWZpY28sIGFxdcOtIGxhIGZ1ZW50ZQ0KfQ0KDQojIEFjYWJhZG8gZWRpdG9yaWFsIHBhcmEgbGFzIHRhYmxhcyBFU1TDgVRJQ0FTIHBlcXVlw7FhczogZW5jYWJlemFkbyBtb3JhZG8gZW4NCiMgbmVncml0YSwgZmlsYXMgY29uIGhvdmVyIHksIG9wY2lvbmFsLCB1bmEgZmlsYSByZXNhbHRhZGEgZW4gbGlsYSBzdWF2ZSBwYXJhDQojIEVORk9DQVIgbGEgYXRlbmNpw7NuIGVuIGVsIGRhdG8gcXVlIGN1ZW50YSBsYSBoaXN0b3JpYSAobGEgY2F0ZWdvcsOtYSBkb21pbmFudGUpLg0KdGFibGFfYm9uaXRhIDwtIGZ1bmN0aW9uKGRmLCByZXNhbHRhciA9IE5VTEwpIHsNCiAgayA8LSBkZiAlPiUNCiAgICBrYmwoKSAlPiUgICAjIGtibCgpOiBjb25zdHJ1eWUgbGEgdGFibGEgSFRNTCBiYXNlIChuw7ptZXJvcyBhIGxhIGRlcmVjaGEsIHRleHRvIGEgbGEgaXpxdWllcmRhKQ0KICAgIGthYmxlX3N0eWxpbmcoYm9vdHN0cmFwX29wdGlvbnMgPSBjKCJob3ZlciIsICJjb25kZW5zZWQiKSwgZnVsbF93aWR0aCA9IFRSVUUsIGZvbnRfc2l6ZSA9IDE0KSAlPiUNCiAgICByb3dfc3BlYygwLCBib2xkID0gVFJVRSwgY29sb3IgPSAid2hpdGUiLCBiYWNrZ3JvdW5kID0gbW9yYWRvKSAgIyBmaWxhIDAgPSBlbmNhYmV6YWRvDQogIGlmICghaXMubnVsbChyZXNhbHRhcikpIHsNCiAgICAjIHJvd19zcGVjKCk6IGFwbGljYSBlc3RpbG8gYSBmaWxhcyBjb25jcmV0YXM7IGFxdcOtIHJlc2FsdGFtb3MgbGEgZmlsYSBjbGF2ZS4NCiAgICBrIDwtIGsgJT4lIHJvd19zcGVjKHJlc2FsdGFyLCBiYWNrZ3JvdW5kID0gIiNGNkVFRjIiLCBib2xkID0gVFJVRSwgY29sb3IgPSBtb3JhZG8pDQogIH0NCiAgaw0KfQ0KYGBgDQoNCmBgYHtyIHByZXAtbWVyY2Fkb30NCiMgUHJlcGFyYW1vcyBlbCBkYXRhc2V0IGRlIG1lcmNhZG8gVU5BIHNvbGEgdmV6IGFxdcOtLCB5IGxvIHJldXRpbGl6YW1vcw0KIyBlbiB0b2RvIGVsIGluZm9ybWUgLS0gYXPDrSBjYWRhIHNlY2Npw7NuIG5vIHJlcGl0ZSBsYSBtaXNtYSBsaW1waWV6YS4NCnNhbGFyaW9zX21lcmNhZG8gPC0gc2FsYXJpb3NfbWVyY2FkbyAlPiUNCiAgbXV0YXRlKA0KICAgICMgQ29ycmVnaW1vcyB1biBlcnJvciB0aXBvZ3LDoWZpY28gcmVhbCBkZSBsYSBmdWVudGU6ICJEYXRhIEFuYWxpc3QiIGVzDQogICAgIyBlbCBtaXNtbyByb2wgcXVlICJEYXRhIEFuYWx5c3QiIChzb2xvIDIgZGUgMTA1LjQzNCBmaWxhcykuIFRvZG8gbG8NCiAgICAjIGRlbcOhcyBzZSBkZWphIHRhbCBjdWFsIGxsZWdhIC0tIHVuaWZpY2FyIGEgbWFubyBsb3MgY2llbnRvcyBkZQ0KICAgICMgdmFyaWFudGVzIGRlIG5vbWJyZSBkZSByb2wgKGVqLiAiRGF0YSBTY2llbnRpc3QiIHZzICJTdGFmZiBEYXRhDQogICAgIyBTY2llbnRpc3QiKSBib3JyYXLDrWEgaW5mb3JtYWNpw7NuIHJlYWwgZGUgc2VuaW9yaXR5LCBubyB1biBlcnJvci4NCiAgICBqb2JfdGl0bGUgPSBpZl9lbHNlKGpvYl90aXRsZSA9PSAiRGF0YSBBbmFsaXN0IiwgIkRhdGEgQW5hbHlzdCIsIGpvYl90aXRsZSksDQoNCiAgICAjIHJlY29kZSgpOiByZWVtcGxhemEgY2FkYSBjw7NkaWdvIGNvcnRvIHBvciB1bmEgZXRpcXVldGEgbGVnaWJsZSAtLQ0KICAgICMgbWlzbW8gdmVjdG9yLCBzb2xvIGNhbWJpYW4gbGFzIGV0aXF1ZXRhcyBkZSB0ZXh0by4NCiAgICBuaXZlbF9leHBlcmllbmNpYSA9IHJlY29kZShleHBlcmllbmNlX2xldmVsLA0KICAgICAgIkVOIiA9ICJKdW5pb3IgKEVOKSIsICJNSSIgPSAiU2VtaS1TZW5pb3IgKE1JKSIsDQogICAgICAiU0UiID0gIlNlbmlvciAoU0UpIiwgIkVYIiA9ICJEaXJlY3Rpdm8gKEVYKSINCiAgICApLA0KICAgICMgZmFjdG9yKC4uLiwgbGV2ZWxzID0gLi4uKTogZmlqYSBlbCBvcmRlbiByZWFsIGRlIHNlbmlvcml0eSBwYXJhIHF1ZQ0KICAgICMgZ3LDoWZpY29zIHkgdGFibGFzIG5vIGxvIG9yZGVuZW4gYWxmYWLDqXRpY2FtZW50ZS4NCiAgICBuaXZlbF9leHBlcmllbmNpYSA9IGZhY3RvcihuaXZlbF9leHBlcmllbmNpYSwNCiAgICAgIGxldmVscyA9IGMoIkp1bmlvciAoRU4pIiwgIlNlbWktU2VuaW9yIChNSSkiLCAiU2VuaW9yIChTRSkiLCAiRGlyZWN0aXZvIChFWCkiKQ0KICAgICksDQoNCiAgICBtb2RhbGlkYWQgPSByZWNvZGUoYXMuY2hhcmFjdGVyKHJlbW90ZV9yYXRpbyksDQogICAgICAiMCIgPSAiUHJlc2VuY2lhbCIsICI1MCIgPSAiSMOtYnJpZG8iLCAiMTAwIiA9ICJSZW1vdG8iDQogICAgKSwNCiAgICBtb2RhbGlkYWQgPSBmYWN0b3IobW9kYWxpZGFkLCBsZXZlbHMgPSBjKCJQcmVzZW5jaWFsIiwgIkjDrWJyaWRvIiwgIlJlbW90byIpKSwNCg0KICAgIHRpcG9fY29udHJhdG8gPSByZWNvZGUoZW1wbG95bWVudF90eXBlLA0KICAgICAgIkZUIiA9ICJUaWVtcG8gY29tcGxldG8iLCAiUFQiID0gIk1lZGlvIHRpZW1wbyIsDQogICAgICAiQ1QiID0gIkNvbnRyYXRvIiwgIkZMIiA9ICJGcmVlbGFuY2UiDQogICAgKSwNCg0KICAgICMgY291bnRyeWNvZGUoKTogdHJhZHVjZSB1biBjw7NkaWdvIElTTyBkZSBwYcOtcyBhIHN1IG5vbWJyZSBlbiBlc3Bhw7FvbCAtLQ0KICAgICMgZXZpdGEgbW9zdHJhcmxlIGFsIGxlY3RvciBjw7NkaWdvcyBjcnVkb3MgY29tbyAiVVMiIG8gIklOIi4NCiAgICBwYWlzX2VtcHJlc2EgPSBjb3VudHJ5Y29kZShjb21wYW55X2xvY2F0aW9uLCBvcmlnaW4gPSAiaXNvMmMiLCBkZXN0aW5hdGlvbiA9ICJjb3VudHJ5Lm5hbWUuZXMiKSwNCiAgICBwYWlzX3Jlc2lkZW5jaWEgPSBjb3VudHJ5Y29kZShlbXBsb3llZV9yZXNpZGVuY2UsIG9yaWdpbiA9ICJpc28yYyIsIGRlc3RpbmF0aW9uID0gImNvdW50cnkubmFtZS5lcyIpDQogICkNCmBgYA0KDQoNCiMgMS4gSW50cm9kdWNjacOzbg0KDQpFc3RlIGluZm9ybWUgZXhwbG9yYSwgY29uIGPDs2RpZ28gcmVhbCB5IGRhdG9zIHJlYWxlcywgZWwgbWVyY2FkbyBnbG9iYWwgZGUgcHJvZmVzaW9uYWxlcyBkZSBkYXRvczogY3XDoW50byBzZSBnYW5hLCBlbiBxdcOpIHJvbGVzLCBjb24gcXXDqSBleHBlcmllbmNpYSwgZW4gcXXDqSBtb2RhbGlkYWQgZGUgdHJhYmFqbyB5IGVuIHF1w6kgcGHDrXNlcy4gRXMgbGEgZnVlbnRlIHF1ZSB2YW1vcyBhIGV4cHJpbWlyIGEgZm9uZG8sIGNvbiB0YWJsYXMgZGluw6FtaWNhcyB5IHVuIG1hcGEgaW50ZXJhY3Rpdm8sIGFudGVzIGRlIGRhciB1biBwYXNvIG3DoXM6IGFsIGZpbmFsLCBsYSBjb250cmFzdGFtb3MgY29uIGxhcyBleHBlY3RhdGl2YXMgcHJvcGlhcyBkZSBudWVzdHJvIGdydXBvIGRlbCBjdXJzby4NCg0KUGFydGltb3MgZGUgZG9zIGZ1ZW50ZXM6DQoNCjEuICBVbiAqKmRhdGFzZXQgYWJpZXJ0byBkZSBtZXJjYWRvKiogKCJEYXRhIFNjaWVuY2UgU2FsYXJpZXMgMjAyNSIsIHbDrWEgS2FnZ2xlIOKAlCBmdWVudGUgb3JpZ2luYWw6IGFpam9icy5uZXQgU2FsYXJ5IFN1cnZleSksIGNvbiBgciBjb21tYShucm93KHNhbGFyaW9zX21lcmNhZG8pLCBiaWcubWFyayA9ICIuIilgIHNhbGFyaW9zIHJlYWxlcyByZXBvcnRhZG9zIHBvciBwcm9mZXNpb25hbGVzIGRlIGRhdG9zIGVudHJlIGByIG1pbihzYWxhcmlvc19tZXJjYWRvJHdvcmtfeWVhcilgIHkgYHIgbWF4KHNhbGFyaW9zX21lcmNhZG8kd29ya195ZWFyKWAuICoqRXN0YSBlcyBsYSBmdWVudGUgcHJvdGFnb25pc3RhIGRlbCBpbmZvcm1lLioqDQoyLiAgTnVlc3RyYSAqKmVuY3Vlc3RhIHByb3BpYSBkZWwgY3Vyc28qKiAoYHIgbnJvdyhlc3R1ZGlhbnRlcylgIHJlc3B1ZXN0YXMpLCBxdWUgc29sbyBhcGFyZWNlIGFsIGZpbmFsLCBwYXJhIGNvbXBhcmFyIGV4cGVjdGF0aXZhcyBjb250cmEgZXN0YSByZWFsaWRhZCBkZSBtZXJjYWRvLg0KDQojIDIuIE9iamV0aXZvcw0KDQoqKk9iamV0aXZvIGdlbmVyYWw6KiogZXhwbG9yYXIgZW4gcHJvZnVuZGlkYWQgZWwgbWVyY2FkbyBnbG9iYWwgZGUgZGF0b3Mg4oCUc2FsYXJpb3MsIHJvbGVzLCBleHBlcmllbmNpYSB5IGdlb2dyYWbDrWHigJQgeSBjb250cmFzdGFybG8sIGNvbW8gcGFzbyBmaW5hbCwgY29uIGxhcyBleHBlY3RhdGl2YXMgZGUgbnVlc3RybyBncnVwbywgY29tbyBjYXNvIGFwbGljYWRvIHBhcmEgYXByZW5kZXIgZWwgY2ljbG8gY29tcGxldG8gZGUgYW7DoWxpc2lzIHkgdmlzdWFsaXphY2nDs24gZGUgZGF0b3MuDQoNCioqT2JqZXRpdm9zIGVzcGVjw61maWNvczoqKg0KDQoxLiAgRGVzY3JpYmlyIGxhIGNvYmVydHVyYSB5IGNhbGlkYWQgZGVsIGRhdGFzZXQgZGUgbWVyY2FkbyAocGHDrXNlcywgYcOxb3MsIHRpcG9zIGRlIGNvbnRyYXRvIHkgZXhwZXJpZW5jaWEpLg0KMi4gIEV4cGxvcmFyIGPDs21vIHZhcsOtYSBlbCBzYWxhcmlvIHNlZ8O6biBlbCByb2wsIGxhIGV4cGVyaWVuY2lhLCBsYSBtb2RhbGlkYWQgZGUgdHJhYmFqbyB5IGVsIHBhw61zLg0KMy4gIENvbnN0cnVpciBoZXJyYW1pZW50YXMgaW50ZXJhY3RpdmFzIChtYXBhLCB0YWJsYXMgZGluw6FtaWNhcykgcXVlIHBlcm1pdGFuIGV4cGxvcmFyIGVsIG1lcmNhZG8gbcOhcyBhbGzDoSBkZSBsbyBxdWUgbXVlc3RyYSB1biBzb2xvIGdyw6FmaWNvIGVzdMOhdGljby4NCjQuICBDb21wYXJhciwgYWwgZmluYWwsIGVsIHBlcmZpbCB5IGxhcyBleHBlY3RhdGl2YXMgZGUgbnVlc3RybyBncnVwbyBjb24gZXN0YSByZWFsaWRhZCBkZSBtZXJjYWRvLg0KNS4gIENvbXVuaWNhciBsb3MgaGFsbGF6Z29zIGNvbiBob25lc3RpZGFkLCByZWNvbm9jaWVuZG8gcXXDqSBzw60geSBxdcOpIG5vIHBlcm1pdGVuIGNvbmNsdWlyIGxvcyBkYXRvcy4NCg0KIyAzLiBGdWVudGVzIGRlIGluZm9ybWFjacOzbg0KDQojIyAzLjEgRGF0YXNldCBkZSBtZXJjYWRvDQoNCiJEYXRhIFNjaWVuY2UgU2FsYXJpZXMgMjAyNSIgKEthZ2dsZSwgdXN1YXJpbyBgYXJuYWJjaGFraWApIOKAlCByZXB1YmxpY2FjacOzbiBkZSBkYXRvcyBxdWUgcHJvZmVzaW9uYWxlcyBkZSBJQSwgZGF0b3MgeSBNTCByZXBvcnRhbiB2b2x1bnRhcmlhIHkgYW7Ds25pbWFtZW50ZSBlbiBhaWpvYnMubmV0L3NhbGFyaWVzLiBMaWNlbmNpYSBDQzAgKGRvbWluaW8gcMO6YmxpY28pLCBkYXRvcyBhdXRvcnJlcG9ydGFkb3MgZGVzZGUgMjAyMC4NCg0KDQpgYGB7ciB0YWJsYS12YXJpYWJsZXMtbWVyY2Fkb30NCnRpYmJsZSgNCiAgVmFyaWFibGUgPSBjKCJ3b3JrX3llYXIiLCAiZXhwZXJpZW5jZV9sZXZlbCIsICJlbXBsb3ltZW50X3R5cGUiLCAiam9iX3RpdGxlIiwgInNhbGFyeV9pbl91c2QiLCAiZW1wbG95ZWVfcmVzaWRlbmNlIC8gY29tcGFueV9sb2NhdGlvbiIsICJyZW1vdGVfcmF0aW8iKSwNCiAgRGVzY3JpcGNpw7NuID0gYygNCiAgICAiQcOxbyBkZWwgcmVnaXN0cm8gc2FsYXJpYWwgKDIwMjAtMjAyNSkiLA0KICAgICJOaXZlbCBkZSBleHBlcmllbmNpYTogSnVuaW9yLCBTZW1pLVNlbmlvciwgU2VuaW9yLCBEaXJlY3Rpdm8iLA0KICAgICJUaXBvIGRlIGNvbnRyYXRvOiB0aWVtcG8gY29tcGxldG8sIG1lZGlvIHRpZW1wbywgY29udHJhdG8sIGZyZWVsYW5jZSIsDQogICAgIkNhcmdvIHJlcG9ydGFkbyAodGV4dG8gbGlicmUgZGUgbGEgZnVlbnRlIG9yaWdpbmFsIC0tIG3DoXMgZGUgMzAwIHZhcmlhbnRlcyBkaXN0aW50YXMpIiwNCiAgICAiU2FsYXJpbyBhbnVhbCwgeWEgY29udmVydGlkbyBhIFVTRCBwb3IgbGEgZnVlbnRlIiwNCiAgICAiUGHDrXMgZGUgcmVzaWRlbmNpYSBkZWwgZW1wbGVhZG8gLyBwYcOtcyBkZSBsYSBlbXByZXNhIChjw7NkaWdvIElTTyBkZSAyIGxldHJhcykiLA0KICAgICIwID0gcHJlc2VuY2lhbCwgNTAgPSBow61icmlkbywgMTAwID0gcmVtb3RvIg0KICApDQopICU+JQ0KICAjIFJlc2FsdGFtb3Mgc2FsYXJ5X2luX3VzZCAoZmlsYSA1KTogZXMgbGEgdmFyaWFibGUgcHJvdGFnb25pc3RhIGRlbCBpbmZvcm1lLg0KICB0YWJsYV9ib25pdGEocmVzYWx0YXIgPSA1KQ0KYGBgDQoNCg0KYGBge3Iga3Bpcy1tZXJjYWRvfQ0Kbl9wYWlzZXMgPC0gbl9kaXN0aW5jdChzYWxhcmlvc19tZXJjYWRvJGNvbXBhbnlfbG9jYXRpb24pDQpuX3JvbGVzIDwtIG5fZGlzdGluY3Qoc2FsYXJpb3NfbWVyY2FkbyRqb2JfdGl0bGUpDQoNCnBhc3RlMCgNCiAgY29tbWEobnJvdyhzYWxhcmlvc19tZXJjYWRvKSwgYmlnLm1hcmsgPSAiLiIpLCAiIHJlZ2lzdHJvcyDCtyAiLA0KICBuX3BhaXNlcywgIiBwYcOtc2VzIGRlIGVtcHJlc2EgwrcgIiwgbl9yb2xlcywgIiBub21icmVzIGRlIHJvbCBkaXN0aW50b3MgwrcgYcOxb3MgIiwNCiAgbWluKHNhbGFyaW9zX21lcmNhZG8kd29ya195ZWFyKSwgIi0iLCBtYXgoc2FsYXJpb3NfbWVyY2FkbyR3b3JrX3llYXIpDQopDQpgYGANCg0KKipVbiBkYXRvIGNsYXZlIGFudGVzIGRlIGludGVycHJldGFyIGN1YWxxdWllciBjaWZyYToqKg0KDQpgYGB7ciByZXByZXNlbnRhdGl2aWRhZC1tZXJjYWRvfQ0Kc2FsYXJpb3NfbWVyY2FkbyAlPiUNCiAgY291bnQocGFpc19lbXByZXNhLCBzb3J0ID0gVFJVRSkgJT4lDQogIG11dGF0ZShwb3JjZW50YWplID0gcm91bmQobiAvIHN1bShuKSAqIDEwMCwgMSkpICU+JQ0KICBzbGljZV9oZWFkKG4gPSA1KSAlPiUNCiAgIyByZW5hbWUoKTogcmVub21icmEgY29sdW1uYXMgc29sbyBwYXJhIG1vc3RyYXJsYXMgZW4gZXNwYcOxb2wgeSBwcmVzZW50YWJsZXMuDQogIHJlbmFtZShgUGHDrXNgID0gcGFpc19lbXByZXNhLCBgTi7CuiBkZSByZWdpc3Ryb3NgID0gbiwgYCVgID0gcG9yY2VudGFqZSkgJT4lDQogIHRhYmxhX2Jvbml0YShyZXNhbHRhciA9IDEpICAgIyBFc3RhZG9zIFVuaWRvczogY29uY2VudHJhIGNhc2kgZWwgOTAlIGRlIGxhcyBmaWxhcw0KYGBgDQoNCg0KQ2FzaSBlbCAqKjkwJSoqIGRlIGxhcyBmaWxhcyBzb24gZGUgZW1wcmVzYXMgZW4gRXN0YWRvcyBVbmlkb3M7IGVsIHJlc3RvIGRlIHBhw61zZXMgYXBvcnRhIG11eSBwb2NvcyByZWdpc3Ryb3MuIFBvciBlc28sIGEgbG8gbGFyZ28gZGVsIGluZm9ybWUsIGxhIGNvbXBhcmFjacOzbiBzZSBsZWUgY29tbyAqbWVyY2FkbyBnbG9iYWwgKG1heW9yaXRhcmlhbWVudGUgRUUuIFVVLikqIHkgbm8gY29tbyB1biBwcm9tZWRpbyBlcXVpbGlicmFkbyBkZWwgbXVuZG8uDQoNCiMjIDMuMiBOdWVzdHJhIGVuY3Vlc3RhDQoNClBhcmEgZWwgY3J1Y2UgZmluYWwgKHNlY2Npw7NuIDUpLCB1c2Ftb3MgdGFtYmnDqW4gbGEgZW5jdWVzdGEgcHJvcGlhIGRlbCBjdXJzbzogY2FkYSBlc3R1ZGlhbnRlIGxsZW7DsyBzdSBmaWxhIGVuIHVuIGZvcm11bGFyaW8gY29tcGFydGlkbywgeWEgbGltcGlhLCBjb25zb2xpZGFkYSB5IGFub25pbWl6YWRhICh2ZXIgYDEgY29uc29saWRhY2lvbl9saW1waWV6YV9maW5hbF9lbmN1ZXN0YS5SYCkuIEFxdcOtIHNvbG8gdXNhbW9zIGRvcyBkZSBzdXMgY29sdW1uYXM6IGVsIHJvbCBkZSBpbnRlcsOpcyBwcm9mZXNpb25hbCB5IGVsIHNhbGFyaW8gYW51YWwgZXNwZXJhZG8uDQoNCiMgNC4gRXhwbG9yYW5kbyBlbCBtZXJjYWRvIGdsb2JhbCBkZSBkYXRvcw0KDQojIyA0LjEgQ29iZXJ0dXJhIGRlbCBkYXRhc2V0DQoNCmBgYHtyIGNvYmVydHVyYS1leHBlcmllbmNpYX0NCnNhbGFyaW9zX21lcmNhZG8gJT4lDQogIGNvdW50KG5pdmVsX2V4cGVyaWVuY2lhKSAlPiUNCiAgbXV0YXRlKHBvcmNlbnRhamUgPSByb3VuZChuIC8gc3VtKG4pICogMTAwLCAxKSkgJT4lDQogIHJlbmFtZShgTml2ZWwgZGUgZXhwZXJpZW5jaWFgID0gbml2ZWxfZXhwZXJpZW5jaWEsIGBOLsK6IGRlIHJlZ2lzdHJvc2AgPSBuLCBgJWAgPSBwb3JjZW50YWplKSAlPiUNCiAgdGFibGFfYm9uaXRhKHJlc2FsdGFyID0gMykgICAjIFNlbmlvcjogZWwgbml2ZWwgZG9taW5hbnRlIGRlbCBkYXRhc2V0DQpgYGANCg0KYGBge3IgY29iZXJ0dXJhLWNvbnRyYXRvfQ0Kc2FsYXJpb3NfbWVyY2FkbyAlPiUNCiAgY291bnQodGlwb19jb250cmF0bywgc29ydCA9IFRSVUUpICU+JQ0KICBtdXRhdGUocG9yY2VudGFqZSA9IHJvdW5kKG4gLyBzdW0obikgKiAxMDAsIDEpKSAlPiUNCiAgcmVuYW1lKGBUaXBvIGRlIGNvbnRyYXRvYCA9IHRpcG9fY29udHJhdG8sIGBOLsK6IGRlIHJlZ2lzdHJvc2AgPSBuLCBgJWAgPSBwb3JjZW50YWplKSAlPiUNCiAgdGFibGFfYm9uaXRhKHJlc2FsdGFyID0gMSkgICAjIFRpZW1wbyBjb21wbGV0bzogZWwgOTkuNSUgZGUgbGFzIGZpbGFzDQpgYGANCg0KRWwgZGF0YXNldCBlc3TDoSBkb21pbmFkbyBwb3IgcG9zaWNpb25lcyAqKlNlbmlvcioqIHkgZGUgKip0aWVtcG8gY29tcGxldG8qKiAoOTkuNSUgZGUgbGFzIGZpbGFzKSDigJQgY3VhbHF1aWVyIGxlY3R1cmEgc29icmUgbWVkaW8gdGllbXBvLCBmcmVlbGFuY2UgbyBjb250cmF0b3MgY29ydG9zIGRlc2NhbnNhIGVuIG11eSBwb2NvcyBjYXNvcyB5IGRlYmUgdG9tYXJzZSBjb24gY2F1dGVsYS4NCg0KYGBge3IgYmFyLWV4cGVyaWVuY2lhfQ0KcF9leHBlcmllbmNpYV9uIDwtIHNhbGFyaW9zX21lcmNhZG8gJT4lDQogIGNvdW50KG5pdmVsX2V4cGVyaWVuY2lhKSAlPiUNCiAgZ2dwbG90KGFlcyh4ID0gbml2ZWxfZXhwZXJpZW5jaWEsIHkgPSBuLCBmaWxsID0gbml2ZWxfZXhwZXJpZW5jaWEpKSArDQogIGdlb21fY29sKHdpZHRoID0gMC42KSArDQogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGMoZ3JpcywgIiNCOThGQzkiLCBtYWdlbnRhLCBtb3JhZG8pLCBndWlkZSA9ICJub25lIikgKw0KICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gbGFiZWxfY29tbWEoYmlnLm1hcmsgPSAiLiIpKSArDQogIGxhYnMoDQogICAgdGl0bGUgPSAiTGEgbWF5b3LDrWEgZGUgbG9zIHJlZ2lzdHJvcyBzb24gZGUgbml2ZWwgU2VuaW9yIiwNCiAgICB4ID0gTlVMTCwgeSA9ICJOLsK6IGRlIHJlZ2lzdHJvcyINCiAgKSArDQogIHRlbWFfY2xhc2UgKw0KICB0aGVtZShwYW5lbC5ncmlkLm1ham9yLnggPSBlbGVtZW50X2JsYW5rKCkpDQoNCiMgZ2dwbG90bHkoKTogdG9tYSB1biBncsOhZmljbyBkZSBnZ3Bsb3QgeWEgY29uc3RydWlkbyB5IGxlIGFncmVnYQ0KIyBpbnRlcmFjdGl2aWRhZCAoaG92ZXIgY29uIGVsIHZhbG9yIGV4YWN0bywgem9vbSkgc2luIHJlaGFjZXIgZWwgZ3LDoWZpY28uDQpnZ3Bsb3RseShwX2V4cGVyaWVuY2lhX24sIHRvb2x0aXAgPSBjKCJ4IiwgInkiKSkgJT4lIGVzdGlsb19wbG90bHkoKQ0KYGBgDQoNCiMjIDQuMiBFdm9sdWNpw7NuIGRlbCBzYWxhcmlvIGEgdHJhdsOpcyBkZSBsb3MgYcOxb3MNCg0KYGBge3IgZXZvbHVjaW9uLWFuaW99DQpwb3JfYW5pbyA8LSBzYWxhcmlvc19tZXJjYWRvICU+JQ0KICBncm91cF9ieSh3b3JrX3llYXIpICU+JQ0KICBzdW1tYXJpc2UobWVkaWFuYSA9IG1lZGlhbihzYWxhcnlfaW5fdXNkKSwgbiA9IG4oKSwgLmdyb3VwcyA9ICJkcm9wIikNCg0KcF9ldm9sdWNpb24gPC0gZ2dwbG90KHBvcl9hbmlvLCBhZXMoeCA9IHdvcmtfeWVhciwgeSA9IG1lZGlhbmEsIHRleHQgPSBwYXN0ZTAoIkHDsW8gIiwgd29ya195ZWFyLCAiXG5NZWRpYW5hOiBVU0QgIiwgY29tbWEobWVkaWFuYSwgYmlnLm1hcmsgPSAiLiIpLCAiXG5SZWdpc3Ryb3M6ICIsIGNvbW1hKG4sIGJpZy5tYXJrID0gIi4iKSkpKSArDQogIGdlb21fbGluZShjb2xvciA9IG1vcmFkbywgbGluZXdpZHRoID0gMSkgKw0KICBnZW9tX3BvaW50KGFlcyhzaXplID0gbiksIGNvbG9yID0gbWFnZW50YSkgKw0KICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gbGFiZWxfZG9sbGFyKHByZWZpeCA9ICJVU0QgIiwgYmlnLm1hcmsgPSAiLiIpKSArDQogIHNjYWxlX3NpemVfY29udGludW91cyhyYW5nZSA9IGMoMiwgOSksIGd1aWRlID0gIm5vbmUiKSArDQogIGxhYnMoDQogICAgdGl0bGUgPSAiRWwgc2FsYXJpbyBtZWRpYW5vIGRlbCBtZXJjYWRvIHN1YmUgYcOxbyBhIGHDsW8iLA0KICAgIHN1YnRpdGxlID0gIlRhbWHDsW8gZGVsIHB1bnRvID0gbsO6bWVybyBkZSByZWdpc3Ryb3MgZXNlIGHDsW8gKDIwMjAgeSAyMDIxIHRpZW5lbiBtdXkgcG9jb3MpIiwNCiAgICB4ID0gTlVMTCwgeSA9ICJTYWxhcmlvIG1lZGlhbm8gKFVTRCkiDQogICkgKw0KICB0ZW1hX2NsYXNlDQoNCmdncGxvdGx5KHBfZXZvbHVjaW9uLCB0b29sdGlwID0gInRleHQiKSAlPiUgZXN0aWxvX3Bsb3RseSgpDQpgYGANCg0KDQoNCkxvcyBwcmltZXJvcyBhw7FvcyAoMjAyMC0yMDIxKSB0aWVuZW4gbXV5IHBvY29zIHJlZ2lzdHJvcyAoNzUgeSAyMTgsIGZyZW50ZSBhIG3DoXMgZGUgNjAuMDAwIGVuIDIwMjQpIOKAlCBsYSB0ZW5kZW5jaWEgcmVjaWVudGUgKDIwMjMtMjAyNSkgZXMgbGEgbcOhcyBjb25maWFibGUuDQoNCiMjIDQuMyBMb3Mgcm9sZXMgbcOhcyBjb211bmVzIHkgbWVqb3IgcGFnYWRvcw0KDQpgYGB7ciByb2xlcy1mcmVjdWVudGVzfQ0KdG9wX2ZyZWN1ZW50ZXMgPC0gc2FsYXJpb3NfbWVyY2FkbyAlPiUNCiAgY291bnQoam9iX3RpdGxlLCBzb3J0ID0gVFJVRSkgJT4lDQogIHNsaWNlX2hlYWQobiA9IDE1KQ0KDQpwX2ZyZWN1ZW50ZXMgPC0gZ2dwbG90KHRvcF9mcmVjdWVudGVzLCBhZXMoeCA9IG4sIHkgPSBmY3RfcmVvcmRlcihqb2JfdGl0bGUsIG4pLCB0ZXh0ID0gcGFzdGUwKGpvYl90aXRsZSwgIjogIiwgY29tbWEobiwgYmlnLm1hcmsgPSAiLiIpLCAiIHJlZ2lzdHJvcyIpKSkgKw0KICBnZW9tX2NvbChmaWxsID0gbW9yYWRvLCB3aWR0aCA9IDAuNjUpICsNCiAgc2NhbGVfeF9jb250aW51b3VzKGxhYmVscyA9IGxhYmVsX2NvbW1hKGJpZy5tYXJrID0gIi4iKSkgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIkRhdGEgU2NpZW50aXN0LCBEYXRhIEVuZ2luZWVyIHkgU29mdHdhcmUgRW5naW5lZXIgY29uY2VudHJhbiBsYSBtYXlvcsOtYSBkZSByZWdpc3Ryb3MiLA0KICAgIHggPSAiTi7CuiBkZSByZWdpc3Ryb3MiLCB5ID0gTlVMTA0KICApICsNCiAgdGVtYV9jbGFzZSArDQogIHRoZW1lKHBhbmVsLmdyaWQubWFqb3IueSA9IGVsZW1lbnRfYmxhbmsoKSkNCg0KZ2dwbG90bHkocF9mcmVjdWVudGVzLCB0b29sdGlwID0gInRleHQiKSAlPiUgZXN0aWxvX3Bsb3RseSgpDQpgYGANCg0KYGBge3Igcm9sZXMtcGFnYWRvc30NCnRvcF9wYWdhZG9zIDwtIHNhbGFyaW9zX21lcmNhZG8gJT4lDQogIGdyb3VwX2J5KGpvYl90aXRsZSkgJT4lDQogIHN1bW1hcmlzZShuID0gbigpLCBtZWRpYW5hID0gbWVkaWFuKHNhbGFyeV9pbl91c2QpLCAuZ3JvdXBzID0gImRyb3AiKSAlPiUNCiAgZmlsdGVyKG4gPj0gMTAwKSAlPiUgICMgZXhjbHV5ZSByb2xlcyBjb24gbXV5IHBvY29zIHJlZ2lzdHJvcywgZG9uZGUgbGEgbWVkaWFuYSBubyBlcyBjb25maWFibGUNCiAgc2xpY2VfbWF4KG1lZGlhbmEsIG4gPSAxNSkNCg0KcF9wYWdhZG9zIDwtIGdncGxvdCh0b3BfcGFnYWRvcywgYWVzKHggPSBtZWRpYW5hLCB5ID0gZmN0X3Jlb3JkZXIoam9iX3RpdGxlLCBtZWRpYW5hKSwgdGV4dCA9IHBhc3RlMChqb2JfdGl0bGUsICJcbk1lZGlhbmE6IFVTRCAiLCBjb21tYShtZWRpYW5hLCBiaWcubWFyayA9ICIuIiksICJcbiIsIGNvbW1hKG4sIGJpZy5tYXJrID0gIi4iKSwgIiByZWdpc3Ryb3MiKSkpICsNCiAgZ2VvbV9jb2woZmlsbCA9IHR1cnF1ZXNhLCB3aWR0aCA9IDAuNjUpICsNCiAgc2NhbGVfeF9jb250aW51b3VzKGxhYmVscyA9IGxhYmVsX2RvbGxhcihwcmVmaXggPSAiVVNEICIsIGJpZy5tYXJrID0gIi4iKSkgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIkxvcyByb2xlcyBtZWpvciBwYWdhZG9zIGVzdMOhbiBsaWdhZG9zIGEgSUEgeSBsaWRlcmF6Z28gdMOpY25pY28iLA0KICAgIHN1YnRpdGxlID0gIlNvbG8gcm9sZXMgY29uIGFsIG1lbm9zIDEwMCByZWdpc3Ryb3MiLA0KICAgIHggPSAiU2FsYXJpbyBtZWRpYW5vIChVU0QpIiwgeSA9IE5VTEwNCiAgKSArDQogIHRlbWFfY2xhc2UgKw0KICB0aGVtZShwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2JsYW5rKCkpDQoNCmdncGxvdGx5KHBfcGFnYWRvcywgdG9vbHRpcCA9ICJ0ZXh0IikgJT4lIGVzdGlsb19wbG90bHkoKQ0KYGBgDQoNCiMjIDQuNCBFbCBlZmVjdG8gZGUgbGEgZXhwZXJpZW5jaWEgeSBsYSBtb2RhbGlkYWQgZGUgdHJhYmFqbw0KDQpgYGB7ciBib3hwbG90LWV4cGVyaWVuY2lhfQ0KcF9leHBfc2FsYXJpbyA8LSBnZ3Bsb3Qoc2FsYXJpb3NfbWVyY2FkbywgYWVzKHggPSBuaXZlbF9leHBlcmllbmNpYSwgeSA9IHNhbGFyeV9pbl91c2QpKSArDQogIGdlb21fYm94cGxvdChmaWxsID0gIiNGNEYwRjYiLCBjb2xvciA9IG1vcmFkbywgb3V0bGllci5hbHBoYSA9IDAuMDgsIG91dGxpZXIuc2l6ZSA9IDEpICsNCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IGxhYmVsX2RvbGxhcihwcmVmaXggPSAiVVNEICIsIGJpZy5tYXJrID0gIi4iKSkgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIkVsIHNhbGFyaW8gc3ViZSBkZSBmb3JtYSBjb25zaXN0ZW50ZSBjb24gbGEgZXhwZXJpZW5jaWEiLA0KICAgIHggPSBOVUxMLCB5ID0gIlNhbGFyaW8gYW51YWwgKFVTRCkiDQogICkgKw0KICB0ZW1hX2NsYXNlDQoNCmdncGxvdGx5KHBfZXhwX3NhbGFyaW8sIHRvb2x0aXAgPSBjKCJ4IiwgInkiKSkgJT4lIGVzdGlsb19wbG90bHkoKQ0KYGBgDQoNCmBgYHtyIGJveHBsb3QtbW9kYWxpZGFkfQ0KcF9tb2RhbGlkYWRfc2FsYXJpbyA8LSBnZ3Bsb3Qoc2FsYXJpb3NfbWVyY2FkbywgYWVzKHggPSBtb2RhbGlkYWQsIHkgPSBzYWxhcnlfaW5fdXNkKSkgKw0KICBnZW9tX2JveHBsb3QoZmlsbCA9ICIjRjRGMEY2IiwgY29sb3IgPSB0dXJxdWVzYSwgb3V0bGllci5hbHBoYSA9IDAuMDgsIG91dGxpZXIuc2l6ZSA9IDEpICsNCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IGxhYmVsX2RvbGxhcihwcmVmaXggPSAiVVNEICIsIGJpZy5tYXJrID0gIi4iKSkgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIkxhIG1vZGFsaWRhZCBkZSB0cmFiYWpvIG5vIG1hcmNhIHVuYSBkaWZlcmVuY2lhIHNhbGFyaWFsIGNsYXJhIiwNCiAgICBzdWJ0aXRsZSA9ICJIw61icmlkbyB0aWVuZSBtdXkgcG9jb3MgcmVnaXN0cm9zICgzMDggZGUgMTA1LjQzNCkgLS0gbm8gc2FjYXIgY29uY2x1c2lvbmVzIGZ1ZXJ0ZXMgZGUgZXNhIGNhdGVnb3LDrWEiLA0KICAgIHggPSBOVUxMLCB5ID0gIlNhbGFyaW8gYW51YWwgKFVTRCkiDQogICkgKw0KICB0ZW1hX2NsYXNlDQoNCmdncGxvdGx5KHBfbW9kYWxpZGFkX3NhbGFyaW8sIHRvb2x0aXAgPSBjKCJ4IiwgInkiKSkgJT4lIGVzdGlsb19wbG90bHkoKQ0KYGBgDQoNCg0KIyMgNC41IE1hcGE6IMK/ZMOzbmRlIHNlIHBhZ2EgbWVqb3I/DQoNCmBgYHtyIG1hcGEtZGF0b3N9DQp1bWJyYWxfbiA8LSAzMCAgIyBwYcOtc2VzIGNvbiBtZW5vcyBkZSAzMCByZWdpc3Ryb3Mgbm8gbXVlc3RyYW4gbWVkaWFuYSAtLSBzZSBjb25zaWRlcmFuIGRhdG9zIGluc3VmaWNpZW50ZXMNCg0KcmVzdW1lbl9wYWlzZXMgPC0gc2FsYXJpb3NfbWVyY2FkbyAlPiUNCiAgZ3JvdXBfYnkoY29tcGFueV9sb2NhdGlvbiwgcGFpc19lbXByZXNhKSAlPiUNCiAgc3VtbWFyaXNlKG4gPSBuKCksIG1lZGlhbmEgPSBtZWRpYW4oc2FsYXJ5X2luX3VzZCksIC5ncm91cHMgPSAiZHJvcCIpDQoNCiMgbmVfY291bnRyaWVzKCk6IHRyYWUgbG9zIHBvbMOtZ29ub3MgcmVhbGVzIGRlIGxvcyBwYcOtc2VzIGRlbCBtdW5kbyBjb21vIHVuDQojIG9iamV0byBlc3BhY2lhbCAoc2YpIC0tIGVzIGxvIHF1ZSBuZWNlc2l0YSBsZWFmbGV0IHBhcmEgZGlidWphciBlbCBtYXBhLg0KbXVuZG8gPC0gbmVfY291bnRyaWVzKHNjYWxlID0gIm1lZGl1bSIsIHJldHVybmNsYXNzID0gInNmIikNCg0KIyBpc29fYTJfZWggKGVuIHZleiBkZSBpc29fYTIpOiBybmF0dXJhbGVhcnRoIGRlamEgZWwgY8OzZGlnbyBJU08gZW4gYmxhbmNvDQojICgiLTk5IikgcGFyYSBhbGd1bm9zIHBhw61zZXMgKEZyYW5jaWEsIE5vcnVlZ2EpIGVuIGxhIGNvbHVtbmEgaXNvX2EyIHBvciB1bg0KIyBwcm9ibGVtYSBjb25vY2lkbyBkZSBsYSBmdWVudGU7IGlzb19hMl9laCBlcyBsYSB2ZXJzacOzbiBxdWUgc8OtIGxvIHRyYWUgYmllbi4NCm1hcGFfZGF0b3MgPC0gbXVuZG8gJT4lDQogIGxlZnRfam9pbihyZXN1bWVuX3BhaXNlcywgYnkgPSBjKCJpc29fYTJfZWgiID0gImNvbXBhbnlfbG9jYXRpb24iKSkgJT4lDQogIG11dGF0ZSgNCiAgICBtZWRpYW5hX21vc3RyYXIgPSBpZl9lbHNlKG4gPj0gdW1icmFsX24sIG1lZGlhbmEsIE5BX3JlYWxfKSwNCiAgICBldGlxdWV0YSA9IGlmX2Vsc2UoDQogICAgICBpcy5uYShtZWRpYW5hX21vc3RyYXIpLA0KICAgICAgcGFzdGUwKG5hbWUsICI6IGRhdG9zIGluc3VmaWNpZW50ZXMiKSwNCiAgICAgIHBhc3RlMChwYWlzX2VtcHJlc2EsICI6IFVTRCAiLCBjb21tYShtZWRpYW5hX21vc3RyYXIsIGJpZy5tYXJrID0gIi4iKSwgIiAobiA9ICIsIGNvbW1hKG4sIGJpZy5tYXJrID0gIi4iKSwgIikiKQ0KICAgICkNCiAgKQ0KYGBgDQoNCmBgYHtyIG1hcGEtcmVuZGVyLCBmaWcud2lkdGg9OSwgZmlnLmhlaWdodD01LjV9DQojIGNvbG9yQmluKCk6IGVuIHZleiBkZSB1biBncmFkaWVudGUgY29udGludW8gKGRvbmRlIGNhc2kgdG9kb3MgbG9zIHBhw61zZXMgc2UNCiMgdmVuIGRlbCBtaXNtbyBsaWxhKSwgYWdydXBhIGxvcyBzYWxhcmlvcyBlbiBUUkFNT1MgY29uIGNvcnRlcyBjbGFyb3MgLS0gbXVjaG8NCiMgbcOhcyBmw6FjaWwgZGUgZGlzdGluZ3VpciBkZSB1biB2aXN0YXpvLiBDYWRhIHRyYW1vID0gdW4gdG9ubyBkZSBsYSBwYWxldGEuDQpjb3J0ZXNfbWFwYSA8LSBjKDAsIDYwMDAwLCA5MDAwMCwgMTIwMDAwLCAxNTAwMDAsIEluZikNCnBhbGV0YV9tYXBhIDwtIGNvbG9yQmluKA0KICBwYWxldHRlID0gY29sb3JSYW1wUGFsZXR0ZShjKCIjRTdEOUVDIiwgbW9yYWRvKSkobGVuZ3RoKGNvcnRlc19tYXBhKSAtIDEpLA0KICBkb21haW4gPSBtYXBhX2RhdG9zJG1lZGlhbmFfbW9zdHJhciwNCiAgYmlucyA9IGNvcnRlc19tYXBhLA0KICBuYS5jb2xvciA9ICIjREFEN0RFIiAgICMgZ3JpcyBuZXV0cm8gcGFyYSAiZGF0b3MgaW5zdWZpY2llbnRlcyIsIGRpc3RpbnRvIGRlbCBhenVsIGRlbCBvY8OpYW5vDQopDQoNCmxlYWZsZXQobWFwYV9kYXRvcywgb3B0aW9ucyA9IGxlYWZsZXRPcHRpb25zKG1pblpvb20gPSAxLjMsIGF0dHJpYnV0aW9uQ29udHJvbCA9IEZBTFNFKSkgJT4lDQogIGFkZFBvbHlnb25zKA0KICAgIGZpbGxDb2xvciA9IH5wYWxldGFfbWFwYShtZWRpYW5hX21vc3RyYXIpLA0KICAgIGZpbGxPcGFjaXR5ID0gMC45MiwNCiAgICBjb2xvciA9ICJ3aGl0ZSIsIHdlaWdodCA9IDAuNSwNCiAgICBsYWJlbCA9IH5ldGlxdWV0YSwNCiAgICBoaWdobGlnaHRPcHRpb25zID0gaGlnaGxpZ2h0T3B0aW9ucyh3ZWlnaHQgPSAyLCBjb2xvciA9IG1hZ2VudGEsIGZpbGxPcGFjaXR5ID0gMSwgYnJpbmdUb0Zyb250ID0gVFJVRSkNCiAgKSAlPiUNCiAgYWRkTGVnZW5kKA0KICAgIHBhbCA9IHBhbGV0YV9tYXBhLCB2YWx1ZXMgPSB+bWVkaWFuYV9tb3N0cmFyLA0KICAgIHRpdGxlID0gIk1lZGlhbmEgc2FsYXJpYWwgKFVTRCkiLCBwb3NpdGlvbiA9ICJib3R0b21yaWdodCIsIG9wYWNpdHkgPSAwLjk1LA0KICAgIGxhYkZvcm1hdCA9IGxhYmVsRm9ybWF0KHByZWZpeCA9ICJVU0QgIiwgYmlnLm1hcmsgPSAiLiIsIGJldHdlZW4gPSAiIOKAkyAiKQ0KICApICU+JQ0KICBzZXRWaWV3KGxuZyA9IDUsIGxhdCA9IDI1LCB6b29tID0gMS40KQ0KYGBgDQoNClNvbG8gYHIgc3VtKHJlc3VtZW5fcGFpc2VzJG4gPj0gdW1icmFsX24pYCBkZSBsb3MgYHIgbl9wYWlzZXNgIHBhw61zZXMgc3VwZXJhbiBlbCB1bWJyYWwgZGUgYHIgdW1icmFsX25gIHJlZ2lzdHJvcyAoZ3JpcyA9IGRhdG9zIGluc3VmaWNpZW50ZXMsIG5vICJzYWxhcmlvIGNlcm8iKS4gUGFzYSBlbCBjdXJzb3Igc29icmUgY3VhbHF1aWVyIHBhw61zIHBhcmEgdmVyIHN1IG1lZGlhbmEgZXhhY3RhIHkgY3XDoW50b3MgcmVnaXN0cm9zIGxhIHJlc3BhbGRhbi4NCg0KIyMgNC42IEV4cGxvcmEgbGEgdGFibGEgY29tcGxldGE6IHJvbCDDlyBleHBlcmllbmNpYQ0KDQpgYGB7ciB0YWJsYS1kaW5hbWljYX0NCnRvcF9yb2xlc190YWJsYSA8LSBzYWxhcmlvc19tZXJjYWRvICU+JQ0KICBjb3VudChqb2JfdGl0bGUsIHNvcnQgPSBUUlVFKSAlPiUNCiAgc2xpY2VfaGVhZChuID0gMTUpICU+JQ0KICBwdWxsKGpvYl90aXRsZSkNCg0Kbml2ZWxlc19leHAgPC0gbGV2ZWxzKHNhbGFyaW9zX21lcmNhZG8kbml2ZWxfZXhwZXJpZW5jaWEpDQoNCm1hdHJpel9yb2xfZXhwZXJpZW5jaWEgPC0gc2FsYXJpb3NfbWVyY2FkbyAlPiUNCiAgZmlsdGVyKGpvYl90aXRsZSAlaW4lIHRvcF9yb2xlc190YWJsYSkgJT4lDQogIGdyb3VwX2J5KGpvYl90aXRsZSwgbml2ZWxfZXhwZXJpZW5jaWEpICU+JQ0KICBzdW1tYXJpc2UobWVkaWFuYSA9IG1lZGlhbihzYWxhcnlfaW5fdXNkKSwgLmdyb3VwcyA9ICJkcm9wIikgJT4lDQogIHBpdm90X3dpZGVyKG5hbWVzX2Zyb20gPSBuaXZlbF9leHBlcmllbmNpYSwgdmFsdWVzX2Zyb20gPSBtZWRpYW5hKSAlPiUNCiAgbXV0YXRlKGpvYl90aXRsZSA9IGZhY3Rvcihqb2JfdGl0bGUsIGxldmVscyA9IHRvcF9yb2xlc190YWJsYSkpICU+JQ0KICBhcnJhbmdlKGpvYl90aXRsZSkgJT4lDQogIG11dGF0ZShqb2JfdGl0bGUgPSBhcy5jaGFyYWN0ZXIoam9iX3RpdGxlKSkgJT4lDQogIGxlZnRfam9pbihjb3VudChzYWxhcmlvc19tZXJjYWRvLCBqb2JfdGl0bGUsIG5hbWUgPSAibl9yZWdpc3Ryb3MiKSwgYnkgPSAiam9iX3RpdGxlIikgJT4lDQogIHJlbG9jYXRlKG5fcmVnaXN0cm9zLCAuYWZ0ZXIgPSBqb2JfdGl0bGUpICU+JQ0KICByZW5hbWUoUm9sID0gam9iX3RpdGxlLCBgTi4gcmVnaXN0cm9zYCA9IG5fcmVnaXN0cm9zKQ0KDQojIERUOjpkYXRhdGFibGUoKTogdGFibGEgSFRNTCBpbnRlcmFjdGl2YSAtLSBzZSBwdWVkZSBidXNjYXIsIG9yZGVuYXIgeQ0KIyBwYWdpbmFyIHNpbiBlc2NyaWJpciBuaSB1bmEgbMOtbmVhIGRlIEphdmFTY3JpcHQuDQp0YWJsYV9kdCA8LSBkYXRhdGFibGUoDQogIG1hdHJpel9yb2xfZXhwZXJpZW5jaWEsDQogIHJvd25hbWVzID0gRkFMU0UsDQogIGNvbG5hbWVzID0gYygiUm9sIiwgIk4uIHJlZ2lzdHJvcyIsIG5pdmVsZXNfZXhwKSwNCiAgb3B0aW9ucyA9IGxpc3QocGFnZUxlbmd0aCA9IDE1LCBkb20gPSAiZnRpcCIpLA0KICBjYXB0aW9uID0gIlNhbGFyaW8gbWVkaWFubyAoVVNEKSBwb3Igcm9sIHkgbml2ZWwgZGUgZXhwZXJpZW5jaWEgLS0gb3JkZW5hIHBvciBjdWFscXVpZXIgY29sdW1uYSBvIGJ1c2NhIHVuIHJvbC4iDQopDQoNCiMgZm9ybWF0U3R5bGUoKSArIHN0eWxlSW50ZXJ2YWwoKTogY29sb3JlYW4gY2FkYSBjZWxkYSBzZWfDum4gc3UgcHJvcGlvDQojIHZhbG9yLCBjb21vIHVuIGhlYXRtYXAgZGVudHJvIGRlIGxhIHRhYmxhIC0tIG1pc21hIGlkZWEgZGVsIGhlYXRtYXAgZGUNCiMgZ2dwbG90IHF1ZSB5YSB1c2Ftb3MgZW4gb3Ryb3Mgc2NyaXB0cyBkZWwgY3Vyc28sIHBlcm8gaW50ZXJhY3Rpdm8uDQpwYWxldGFfdGFibGEgPC0gY29sb3JSYW1wUGFsZXR0ZShjKCIjRkZGRkZGIiwgbWFnZW50YSkpKDIwKQ0KDQpmb3IgKGNvbHVtbmEgaW4gbml2ZWxlc19leHApIHsNCiAgY29ydGVzIDwtIHF1YW50aWxlKG1hdHJpel9yb2xfZXhwZXJpZW5jaWFbW2NvbHVtbmFdXSwgcHJvYnMgPSBzZXEoMC4wNSwgMC45NSwgbGVuZ3RoLm91dCA9IDE5KSwgbmEucm0gPSBUUlVFKQ0KICB0YWJsYV9kdCA8LSB0YWJsYV9kdCAlPiUNCiAgICBmb3JtYXRTdHlsZShjb2x1bW5hLCBiYWNrZ3JvdW5kQ29sb3IgPSBzdHlsZUludGVydmFsKGNvcnRlcywgcGFsZXRhX3RhYmxhKSkNCn0NCg0KdGFibGFfZHQgJT4lIGZvcm1hdEN1cnJlbmN5KG5pdmVsZXNfZXhwLCBjdXJyZW5jeSA9ICJVU0QgIiwgZGlnaXRzID0gMCwgbWFyayA9ICIuIikNCmBgYA0KDQojIDUuIE51ZXN0cm8gZ3J1cG8gZnJlbnRlIGFsIG1lcmNhZG8NCg0KQ29uIGVsIG1lcmNhZG8geWEgZXhwbG9yYWRvIGEgZm9uZG8sIGNlcnJhbW9zIGNvbiBsYSBwcmVndW50YSBvcmlnaW5hbCBkZWwgY3Vyc286IMK/Y8OzbW8gc2UgdWJpY2FuIGxhcyBleHBlY3RhdGl2YXMgc2FsYXJpYWxlcyBkZSBudWVzdHJvIHByb3BpbyBncnVwbyBkZW50cm8gZGUgZXN0ZSBwYW5vcmFtYT8NCg0KYGBge3Igcm9sZXMtZ3J1cG99DQpyb2xlc19ncnVwbyA8LSBlc3R1ZGlhbnRlcyAlPiUgZmlsdGVyKCFpcy5uYShwcm9mZXNpb25faW50ZXJlcykpICU+JSBkaXN0aW5jdChwcm9mZXNpb25faW50ZXJlcykgJT4lIHB1bGwocHJvZmVzaW9uX2ludGVyZXMpDQoNCm1lcmNhZG9fcmVhbCA8LSBzYWxhcmlvc19tZXJjYWRvICU+JQ0KICBmaWx0ZXIoam9iX3RpdGxlICVpbiUgcm9sZXNfZ3J1cG8pICU+JQ0KICBncm91cF9ieShqb2JfdGl0bGUpICU+JQ0KICBzdW1tYXJpc2Uobl9tZXJjYWRvID0gbigpLCBtZWRpYW5hX3JlYWwgPSBtZWRpYW4oc2FsYXJ5X2luX3VzZCksIC5ncm91cHMgPSAiZHJvcCIpICU+JQ0KICBhcnJhbmdlKGRlc2MobWVkaWFuYV9yZWFsKSkNCg0KIyBNb3N0cmFtb3MgdW5hIHZpc3RhIHJlbm9tYnJhZGE7IGVsIG9iamV0byAnbWVyY2Fkb19yZWFsJyBzZSBjb25zZXJ2YSB0YWwgY3VhbA0KIyBwb3JxdWUgbcOhcyBhYmFqbyBzZSB1bmUgKGpvaW4pIGNvbiBsYSBleHBlY3RhdGl2YSBkZWwgZ3J1cG8gcG9yICdqb2JfdGl0bGUnLg0KbWVyY2Fkb19yZWFsICU+JQ0KICByZW5hbWUoUm9sID0gam9iX3RpdGxlLCBgTi7CuiBlbiBtZXJjYWRvYCA9IG5fbWVyY2FkbywgYE1lZGlhbmEgcmVhbCAoVVNEKWAgPSBtZWRpYW5hX3JlYWwpICU+JQ0KICB0YWJsYV9ib25pdGEoKQ0KYGBgDQoNCmBgYHtyIGNvbXBhcmFjaW9ufQ0KIyBFeGNsdWltb3Mgc2FsYXJpb19pbXB1dGFkbyA9PSBUUlVFOiBlc2FzIGZpbGFzIHNlIHJlbGxlbmFyb24gY29uIGxhDQojIG1lZGlhbmEgR0xPQkFMIGVuIGVsIHNjcmlwdCBkZSBsaW1waWV6YSwgbm8gc29uIHVuYSByZXNwdWVzdGEgcmVhbA0KIyBkZSBlc2Ugcm9sIC0tIGluY2x1aXJsYXMgc2VzZ2EgbGEgbWVkaWFuYSBkZSBCdXNpbmVzcyBJbnRlbGxpZ2VuY2UNCiMgQW5hbHlzdCB5IERhdGEgQW5hbHlzdCBlc3BlY8OtZmljYW1lbnRlICh2ZXIgc2NyaXB0IDMgcGFyYSBlbCBkZXRhbGxlKS4NCmV4cGVjdGF0aXZhX2dydXBvIDwtIGVzdHVkaWFudGVzICU+JQ0KICBmaWx0ZXIoIWlzLm5hKHByb2Zlc2lvbl9pbnRlcmVzKSwgIXNhbGFyaW9faW1wdXRhZG8pICU+JQ0KICBncm91cF9ieShwcm9mZXNpb25faW50ZXJlcykgJT4lDQogIHN1bW1hcmlzZShuX2VzdHVkaWFudGVzID0gbigpLCBtZWRpYW5hX2VzcGVyYWRhID0gbWVkaWFuKHNhbGFyaW9fZXNwZXJhZG9fdXNkX2FudWFsKSwgLmdyb3VwcyA9ICJkcm9wIikNCg0KY29tcGFyYWNpb24gPC0gZXhwZWN0YXRpdmFfZ3J1cG8gJT4lDQogIGlubmVyX2pvaW4obWVyY2Fkb19yZWFsLCBieSA9IGMoInByb2Zlc2lvbl9pbnRlcmVzIiA9ICJqb2JfdGl0bGUiKSkgJT4lDQogIG11dGF0ZShicmVjaGEgPSBtZWRpYW5hX2VzcGVyYWRhIC0gbWVkaWFuYV9yZWFsLCBicmVjaGFfcGN0ID0gcm91bmQoYnJlY2hhIC8gbWVkaWFuYV9yZWFsICogMTAwLCAwKSkgJT4lDQogIGFycmFuZ2UoYnJlY2hhKQ0KDQojIFZpc3RhIHJlbm9tYnJhZGEgcGFyYSBtb3N0cmFyOyBlbCBvYmpldG8gJ2NvbXBhcmFjaW9uJyBzZSBjb25zZXJ2YSBwb3JxdWUgbG8NCiMgcmV1dGlsaXphbiBlbCBncsOhZmljbyBkZSBicmVjaGEgeSBsb3MgaGFsbGF6Z29zIGF1dG9tw6F0aWNvcyBkZSBsYSBzZWNjacOzbiA2Lg0KY29tcGFyYWNpb24gJT4lDQogIHJlbmFtZSgNCiAgICBSb2wgPSBwcm9mZXNpb25faW50ZXJlcywgYE4uwrogZ3J1cG9gID0gbl9lc3R1ZGlhbnRlcywNCiAgICBgTWVkaWFuYSBlc3BlcmFkYWAgPSBtZWRpYW5hX2VzcGVyYWRhLCBgTi7CuiBtZXJjYWRvYCA9IG5fbWVyY2FkbywNCiAgICBgTWVkaWFuYSByZWFsYCA9IG1lZGlhbmFfcmVhbCwgYEJyZWNoYSAoVVNEKWAgPSBicmVjaGEsIGBCcmVjaGEgJWAgPSBicmVjaGFfcGN0DQogICkgJT4lDQogIHRhYmxhX2Jvbml0YSgpDQpgYGANCg0KYGBge3IgYm94cGxvdC1jb21wYXJhY2lvbiwgZmlnLmhlaWdodD02fQ0Kb3JkZW5fcm9sZXMgPC0gY29tcGFyYWNpb24kcHJvZmVzaW9uX2ludGVyZXMNCg0KZGF0b3NfbWVyY2Fkb19ncmFmaWNvIDwtIHNhbGFyaW9zX21lcmNhZG8gJT4lDQogIGZpbHRlcihqb2JfdGl0bGUgJWluJSByb2xlc19ncnVwbykgJT4lDQogIG11dGF0ZShqb2JfdGl0bGUgPSBmYWN0b3Ioam9iX3RpdGxlLCBsZXZlbHMgPSBvcmRlbl9yb2xlcykpDQoNCiMgVGV4dG8gZGVsIHRvb2x0aXAgZGVsIHB1bnRvIGRlIGV4cGVjdGF0aXZhOiBhbCBwYXNhciBlbCBjdXJzb3IsIGVsIGxlY3Rvcg0KIyB2ZSBkZSB1bmEgdmV6IGxhIGV4cGVjdGF0aXZhIGRlbCBncnVwbywgbGEgbWVkaWFuYSByZWFsIHkgbGEgZGlmZXJlbmNpYS4NCmV4cGVjdGF0aXZhX2dyYWZpY28gPC0gY29tcGFyYWNpb24gJT4lDQogIG11dGF0ZSgNCiAgICBwcm9mZXNpb25faW50ZXJlcyA9IGZhY3Rvcihwcm9mZXNpb25faW50ZXJlcywgbGV2ZWxzID0gb3JkZW5fcm9sZXMpLA0KICAgIHRvb2x0aXAgPSBwYXN0ZTAoDQogICAgICBwcm9mZXNpb25faW50ZXJlcywNCiAgICAgICJcbkV4cGVjdGF0aXZhIGRlbCBncnVwbzogVVNEICIsIGNvbW1hKG1lZGlhbmFfZXNwZXJhZGEsIGJpZy5tYXJrID0gIi4iKSwNCiAgICAgICJcbk1lZGlhbmEgcmVhbCBkZWwgbWVyY2FkbzogVVNEICIsIGNvbW1hKG1lZGlhbmFfcmVhbCwgYmlnLm1hcmsgPSAiLiIpLA0KICAgICAgIlxuRGlmZXJlbmNpYTogVVNEICIsIGNvbW1hKGJyZWNoYSwgYmlnLm1hcmsgPSAiLiIpDQogICAgKQ0KICApDQoNCnBfY29tcGFyYWNpb24gPC0gZ2dwbG90KGRhdG9zX21lcmNhZG9fZ3JhZmljbywgYWVzKHggPSBqb2JfdGl0bGUsIHkgPSBzYWxhcnlfaW5fdXNkKSkgKw0KICBnZW9tX2JveHBsb3QoZmlsbCA9IGdyaXMsIGNvbG9yID0gbW9yYWRvLCBvdXRsaWVyLmFscGhhID0gMC4xNSwgb3V0bGllci5zaXplID0gMSwgd2lkdGggPSAwLjU1KSArDQogICMgRWwgcHVudG8gbWFnZW50YSBsbGV2YSB1biBhZXModGV4dCA9IC4uLik6IGFzw60gZ2dwbG90bHkgdXNhIEVTRSB0ZXh0byBjb21vDQogICMgdG9vbHRpcCwgZW4gdmV6IGRlIGxhcyBjb29yZGVuYWRhcyBjcnVkYXMuDQogIGdlb21fcG9pbnQoZGF0YSA9IGV4cGVjdGF0aXZhX2dyYWZpY28sDQogICAgICAgICAgICAgYWVzKHggPSBwcm9mZXNpb25faW50ZXJlcywgeSA9IG1lZGlhbmFfZXNwZXJhZGEsIHRleHQgPSB0b29sdGlwKSwNCiAgICAgICAgICAgICBjb2xvciA9IG1hZ2VudGEsIHNpemUgPSA0KSArDQogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBsYWJlbF9kb2xsYXIocHJlZml4ID0gIlVTRCAiLCBiaWcubWFyayA9ICIuIikpICsNCiAgY29vcmRfZmxpcCgpICsNCiAgbGFicygNCiAgICB0aXRsZSA9ICJMYSBwZXJjZXBjacOzbiBzYWxhcmlhbCBkZWwgZ3J1cG8gc2UgdWJpY2EgcG9yIGRlYmFqbyBkZWwgbWVyY2FkbyBnbG9iYWwiLA0KICAgIHN1YnRpdGxlID0gIkNhamEgPSBkaXN0cmlidWNpw7NuIHJlYWwgZGVsIG1lcmNhZG8gwrcgUHVudG8gbWFnZW50YSA9IG1lZGlhbmEgZXNwZXJhZGEgcG9yIGVsIGdydXBvIChwYXNhIGVsIGN1cnNvciBwb3Igw6lsKSIsDQogICAgeCA9IE5VTEwsIHkgPSAiU2FsYXJpbyBhbnVhbCAoVVNEKSIsDQogICAgY2FwdGlvbiA9ICJNZXJjYWRvOiB+OTAlIGRlIGxhcyBlbXByZXNhcyByZXBvcnRhZGFzIGVzdMOhbiBlbiBFRS4gVVUuIOKAlCBubyBlcyB1bmEgY29tcGFyYWNpw7NuIGRlIHBvZGVyIGFkcXVpc2l0aXZvIGxvY2FsLiINCiAgKSArDQogIHRlbWFfY2xhc2UgKw0KICB0aGVtZShwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2JsYW5rKCkpDQoNCiMgSW50ZXJhY3Rpdm86IGxhIGNhamEgY29uc2VydmEgc3UgaG92ZXIgY29uIGxvcyBjdWFydGlsZXMgKG1pbiwgUTEsIG1lZGlhbmEsDQojIFEzLCBtw6F4KSBkZWwgbWVyY2FkbyByZWFsLCB5IGVsIHB1bnRvIG1hZ2VudGEgbXVlc3RyYSBlbCB0ZXh0byBxdWUgYXJtYW1vcy4NCmdncGxvdGx5KHBfY29tcGFyYWNpb24sIHRvb2x0aXAgPSAidGV4dCIpICU+JSBlc3RpbG9fcGxvdGx5KCkNCmBgYA0KDQpgYGB7ciBicmVjaGF9DQojIFRhYmxhIHNvbG8gcGFyYSBlc3RlIGdyw6FmaWNvIChubyB0b2NhbW9zICdjb21wYXJhY2lvbicsIHF1ZSBzZSByZXV0aWxpemEgZW4NCiMgbG9zIGhhbGxhemdvcykuIE9yZGVuYW1vcyBwb3IgYnJlY2hhIHkgcHJlcGFyYW1vcyBlbCB0ZXh0byBkZWwgdG9vbHRpcC4NCmNvbXBhcmFjaW9uX2dyYWZpY28gPC0gY29tcGFyYWNpb24gJT4lDQogIG11dGF0ZSgNCiAgICBwcm9mZXNpb25faW50ZXJlcyA9IGZjdF9yZW9yZGVyKHByb2Zlc2lvbl9pbnRlcmVzLCBicmVjaGEpLA0KICAgIHRvb2x0aXAgPSBwYXN0ZTAoDQogICAgICBwcm9mZXNpb25faW50ZXJlcywNCiAgICAgICJcbkJyZWNoYTogLVVTRCAiLCBjb21tYShhYnMoYnJlY2hhKSwgYmlnLm1hcmsgPSAiLiIpLCAiICgiLCBhYnMoYnJlY2hhX3BjdCksICIlKSIsDQogICAgICAiXG5Fc3BlcmFkbzogVVNEICIsIGNvbW1hKG1lZGlhbmFfZXNwZXJhZGEsIGJpZy5tYXJrID0gIi4iKSwNCiAgICAgICIgwrcgTWVyY2FkbzogVVNEICIsIGNvbW1hKG1lZGlhbmFfcmVhbCwgYmlnLm1hcmsgPSAiLiIpDQogICAgKQ0KICApDQoNCnBfYnJlY2hhIDwtIGdncGxvdChjb21wYXJhY2lvbl9ncmFmaWNvLCBhZXMoeCA9IGJyZWNoYSwgeSA9IHByb2Zlc2lvbl9pbnRlcmVzKSkgKw0KICBnZW9tX3NlZ21lbnQoYWVzKHggPSAwLCB4ZW5kID0gYnJlY2hhLCB5ZW5kID0gcHJvZmVzaW9uX2ludGVyZXMpLCBjb2xvciA9IGdyaXMsIGxpbmV3aWR0aCA9IDQpICsNCiAgZ2VvbV9wb2ludChhZXModGV4dCA9IHRvb2x0aXApLCBjb2xvciA9IG1hZ2VudGEsIHNpemUgPSA0KSArDQogIHNjYWxlX3hfY29udGludW91cyhsYWJlbHMgPSBsYWJlbF9kb2xsYXIocHJlZml4ID0gIlVTRCAiLCBiaWcubWFyayA9ICIuIiksIGV4cGFuZCA9IGV4cGFuc2lvbihtdWx0ID0gYygwLjEsIDAuMDUpKSkgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIkVuIHRvZG9zIGxvcyByb2xlcywgbGEgcGVyY2VwY2nDs24gZGVsIGdydXBvIHNlIHViaWNhIHBvciBkZWJham8gZGVsIG1lcmNhZG8gZ2xvYmFsIiwNCiAgICBzdWJ0aXRsZSA9ICJEaWZlcmVuY2lhIGVudHJlIGxhIG1lZGlhbmEgZXNwZXJhZGEgcG9yIGVsIGdydXBvIHkgbGEgbWVkaWFuYSByZWFsIGRlbCBtZXJjYWRvIChwYXNhIGVsIGN1cnNvciBwb3IgY2FkYSBwdW50bykiLA0KICAgIHggPSAiQnJlY2hhIChVU0QvYcOxbykiLCB5ID0gTlVMTCwNCiAgICBjYXB0aW9uID0gIkJyZWNoYSA9IG1lZGlhbmEgZXNwZXJhZGEgKGVuY3Vlc3RhKSDiiJIgbWVkaWFuYSByZWFsIChtZXJjYWRvIGdsb2JhbCwgbWF5b3JpdGFyaWFtZW50ZSBFRS4gVVUuKS4iDQogICkgKw0KICB0ZW1hX2NsYXNlICsNCiAgdGhlbWUocGFuZWwuZ3JpZC5tYWpvci55ID0gZWxlbWVudF9ibGFuaygpKQ0KDQpnZ3Bsb3RseShwX2JyZWNoYSwgdG9vbHRpcCA9ICJ0ZXh0IikgJT4lIGVzdGlsb19wbG90bHkoKQ0KYGBgDQoNCiMgNi4gUHJpbmNpcGFsZXMgaGFsbGF6Z29zDQoNCmBgYHtyIGhhbGxhemdvLTEsIHJlc3VsdHM9J2FzaXMnfQ0Kcm9sX21hc19wYWdhZG8gPC0gdG9wX3BhZ2Fkb3MgJT4lIHNsaWNlX21heChtZWRpYW5hLCBuID0gMSwgd2l0aF90aWVzID0gRkFMU0UpDQpyb2xfbWFzX2NvbXVuIDwtIHRvcF9mcmVjdWVudGVzICU+JSBzbGljZV9tYXgobiwgbiA9IDEsIHdpdGhfdGllcyA9IEZBTFNFKQ0KcGFpc19tZWpvcl9wYWdhZG8gPC0gcmVzdW1lbl9wYWlzZXMgJT4lIGZpbHRlcihuID49IHVtYnJhbF9uKSAlPiUgc2xpY2VfbWF4KG1lZGlhbmEsIG4gPSAxLCB3aXRoX3RpZXMgPSBGQUxTRSkNCg0KY2F0KHBhc3RlMCgNCiAgIi0gRWwgcm9sIG3DoXMgY29tw7puIGRlbCBtZXJjYWRvIGVzICoqIiwgcm9sX21hc19jb211biRqb2JfdGl0bGUsICIqKiAoIiwNCiAgY29tbWEocm9sX21hc19jb211biRuLCBiaWcubWFyayA9ICIuIiksICIgcmVnaXN0cm9zKSwgeSBlbCBtZWpvciBwYWdhZG8gKGVudHJlIHJvbGVzIGNvbiBhbCBtZW5vcyAxMDAgcmVnaXN0cm9zKSBlcyAqKiIsDQogIHJvbF9tYXNfcGFnYWRvJGpvYl90aXRsZSwgIioqIChtZWRpYW5hIFVTRCAiLCBjb21tYShyb2xfbWFzX3BhZ2FkbyRtZWRpYW5hLCBiaWcubWFyayA9ICIuIiksICIpLlxuIg0KKSkNCg0KY2F0KHBhc3RlMCgNCiAgIi0gRW50cmUgbG9zIHBhw61zZXMgY29uIGRhdG9zIHN1ZmljaWVudGVzIChuIOKJpSAiLCB1bWJyYWxfbiwgIiksICoqIiwgcGFpc19tZWpvcl9wYWdhZG8kcGFpc19lbXByZXNhLA0KICAiKiogdGllbmUgbGEgbWVkaWFuYSBzYWxhcmlhbCBtw6FzIGFsdGEgKFVTRCAiLCBjb21tYShwYWlzX21lam9yX3BhZ2FkbyRtZWRpYW5hLCBiaWcubWFyayA9ICIuIiksICIpLlxuIg0KKSkNCg0KY2F0KHBhc3RlMCgNCiAgIi0gRWwgc2FsYXJpbyBtZWRpYW5vIGdsb2JhbCBzdWJpw7MgZGUgVVNEICIsIGNvbW1hKHBvcl9hbmlvJG1lZGlhbmFbcG9yX2FuaW8kd29ya195ZWFyID09IG1pbihwb3JfYW5pbyR3b3JrX3llYXIpXSwgYmlnLm1hcmsgPSAiLiIpLA0KICAiIGVuICIsIG1pbihwb3JfYW5pbyR3b3JrX3llYXIpLCAiIGEgVVNEICIsIGNvbW1hKHBvcl9hbmlvJG1lZGlhbmFbcG9yX2FuaW8kd29ya195ZWFyID09IG1heChwb3JfYW5pbyR3b3JrX3llYXIpXSwgYmlnLm1hcmsgPSAiLiIpLA0KICAiIGVuICIsIG1heChwb3JfYW5pbyR3b3JrX3llYXIpLCAiIChsb3MgcHJpbWVyb3MgYcOxb3MgdGllbmVuIG11eSBwb2NvcyByZWdpc3Ryb3MsIHZlciBzZWNjacOzbiA0LjIpLlxuIg0KKSkNCg0Kcm9sX21heW9yX2JyZWNoYSA8LSBjb21wYXJhY2lvbiAlPiUgc2xpY2VfbWluKGJyZWNoYSwgbiA9IDEsIHdpdGhfdGllcyA9IEZBTFNFKQ0Kcm9sX21lbm9yX2JyZWNoYSA8LSBjb21wYXJhY2lvbiAlPiUgc2xpY2VfbWF4KGJyZWNoYSwgbiA9IDEsIHdpdGhfdGllcyA9IEZBTFNFKQ0KDQpjYXQocGFzdGUwKA0KICAiLSBGcmVudGUgYWwgbWVyY2FkbywgbGEgYnJlY2hhIG3DoXMgZ3JhbmRlIGRlbCBncnVwbyBlc3TDoSBlbiAqKiIsIHJvbF9tYXlvcl9icmVjaGEkcHJvZmVzaW9uX2ludGVyZXMsICIqKiAoLSIsDQogIGxhYmVsX2RvbGxhcihwcmVmaXggPSAiVVNEICIsIGJpZy5tYXJrID0gIi4iKShhYnMocm9sX21heW9yX2JyZWNoYSRicmVjaGEpKSwNCiAgIiksIG1pZW50cmFzIHF1ZSBsYSBtw6FzIHBlcXVlw7FhIGVzdMOhIGVuICoqIiwNCiAgcm9sX21lbm9yX2JyZWNoYSRwcm9mZXNpb25faW50ZXJlcywgIioqICgtIiwNCiAgbGFiZWxfZG9sbGFyKHByZWZpeCA9ICJVU0QgIiwgYmlnLm1hcmsgPSAiLiIpKGFicyhyb2xfbWVub3JfYnJlY2hhJGJyZWNoYSkpLCAiKS5cbiINCikpDQpgYGANCg0KIyA3LiBMaW1pdGFjaW9uZXMNCg0KLSAgICoqQ29uY2VudHJhY2nDs24gZ2VvZ3LDoWZpY2EqKjogXH45MCUgZGUgbG9zIHJlZ2lzdHJvcyBzb24gZGUgZW1wcmVzYXMgZW4gRUUuIFVVLiBFbCBtYXBhIHkgbGFzIGNvbXBhcmFjaW9uZXMgcG9yIHBhw61zIHJlZmxlamFuIHNvYnJlIHRvZG8gZXNlIG1lcmNhZG8sIG5vIHVuIHByb21lZGlvIGVxdWlsaWJyYWRvIGRlbCBtdW5kby4NCi0gICAqKkF1dG9ycmVwb3J0ZSoqOiBsb3MgZGF0b3MgdmllbmVuIGRlIHBlcnNvbmFzIHF1ZSBsb3MgcmVwb3J0YW4gdm9sdW50YXJpYW1lbnRlIGVuIGFpam9icy5uZXQg4oCUIG5vIGVzIHVuIGNlbnNvIG5pIHVuYSBtdWVzdHJhIGFsZWF0b3JpYSBkZWwgbWVyY2FkbyBsYWJvcmFsIGRlIGRhdG9zLg0KLSAgICoqYGpvYl90aXRsZWAgZXMgdGV4dG8gbGlicmUqKjogbGEgZnVlbnRlIG9yaWdpbmFsIG5vIHVzYSB1bmEgbGlzdGEgY2VycmFkYSBkZSBjYXJnb3Mg4oCUIG3DoXMgZGUgMzAwIHZhcmlhbnRlcyBkaXN0aW50YXMuIFNvbG8gY29ycmVnaW1vcyB1biB0eXBvIGV2aWRlbnRlICgiRGF0YSBBbmFsaXN0IiDihpIgIkRhdGEgQW5hbHlzdCIpOyB2YXJpYW50ZXMgZGUgc2VuaW9yaXR5IChlai4gIkRhdGEgU2NpZW50aXN0IiB2cy4gIlN0YWZmIERhdGEgU2NpZW50aXN0Iikgc2UgZGVqYXJvbiBzZXBhcmFkYXMgYSBwcm9ww7NzaXRvLg0KLSAgICoqQ2F0ZWdvcsOtYXMgY29uIHBvY29zIHJlZ2lzdHJvcyoqOiBtZWRpbyB0aWVtcG8sIGZyZWVsYW5jZS9jb250cmF0byB5IG1vZGFsaWRhZCBow61icmlkYSB0aWVuZW4gbXV5IHBvY2FzIGZpbGFzICh2ZXIgc2VjY2nDs24gNC4xKSDigJQgY3VhbHF1aWVyIGxlY3R1cmEgc29icmUgZWxsYXMgZXMgbWVub3MgY29uZmlhYmxlIHF1ZSBzb2JyZSB0aWVtcG8gY29tcGxldG8vU2VuaW9yL3ByZXNlbmNpYWwtcmVtb3RvLg0KLSAgICoqU2FsYXJpbyB5YSBjb252ZXJ0aWRvIGEgVVNEIHBvciBsYSBmdWVudGUqKjogbm8gY29udHJvbGFtb3MgY29uIHF1w6kgdGFzYSBkZSBjYW1iaW8gbmkgZW4gcXXDqSBmZWNoYSBzZSBoaXpvIGVzYSBjb252ZXJzacOzbi4NCi0gICAqKkNvcnJlbGFjacOzbiBubyBpbXBsaWNhIGNhdXNhbGlkYWQqKjogcXVlIGVsIHNhbGFyaW8gc3ViYSBjb24gbGEgZXhwZXJpZW5jaWEgbyB2YXLDrWUgcG9yIHBhw61zIGRlc2NyaWJlIHVuIHBhdHLDs24gb2JzZXJ2YWRvLCBubyB1bmEgcmVsYWNpw7NuIGRlIGNhdXNhLWVmZWN0byBhaXNsYWRhIGRlIG90cm9zIGZhY3RvcmVzLg0KLSAgICoqVmFsb3JlcyBpbXB1dGFkb3MgZW4gbGEgZW5jdWVzdGEqKjogdW5hcyBwb2NhcyByZXNwdWVzdGFzIGRlIHNhbGFyaW8gZXNwZXJhZG8gcXVlIHF1ZWRhcm9uIGVuIGJsYW5jbyBzZSByZWxsZW5hcm9uIGNvbiBsYSBtZWRpYW5hIGRlbCBncnVwbyAoY29sdW1uYSBgc2FsYXJpb19pbXB1dGFkb2ApIOKAlCBzZSBleGNsdXllcm9uIGV4cGzDrWNpdGFtZW50ZSBkZSBsYSBzZWNjacOzbiA1IHBhcmEgbm8gc2VzZ2FyIGVzYSBjb21wYXJhY2nDs24gcHVudHVhbC4NCg0KIyA4LiBDb25jbHVzaW9uZXMNCg0KRWwgbWVyY2FkbyBnbG9iYWwgZGUgZGF0b3MgbXVlc3RyYSB1biBwYXRyw7NuIGNsYXJvOiBlbCBzYWxhcmlvIHN1YmUgZGUgZm9ybWEgY29uc2lzdGVudGUgY29uIGxhIGV4cGVyaWVuY2lhLCBzZSBjb25jZW50cmEgZW4gdW4gcHXDsWFkbyBkZSByb2xlcyAoRGF0YSBTY2llbnRpc3QsIERhdGEgRW5naW5lZXIsIFNvZnR3YXJlIEVuZ2luZWVyKSB5IHZhcsOtYSBmdWVydGVtZW50ZSBwb3IgcGHDrXMg4oCUIGNvbiBFc3RhZG9zIFVuaWRvcyBkb21pbmFuZG8gdGFudG8gZWwgdm9sdW1lbiBkZSBkYXRvcyBjb21vLCBlbiBnZW5lcmFsLCBsb3Mgc2FsYXJpb3MgcmVwb3J0YWRvcy4gTGEgbW9kYWxpZGFkIGRlIHRyYWJham8sIGVuIGNhbWJpbywgbm8gbXVlc3RyYSB1bmEgZGlmZXJlbmNpYSBzYWxhcmlhbCBjbGFyYS4NCg0KRnJlbnRlIGEgZXN0ZSBwYW5vcmFtYSwgbGEgcGVyY2VwY2nDs24gc2FsYXJpYWwgZGUgbnVlc3RybyBncnVwbyBzZSB1YmljYSBwb3IgZGViYWpvIGRlbCBtZXJjYWRvIGdsb2JhbCBlbiBsb3Mgcm9sZXMgZGUgaW50ZXLDqXMg4oCUIHVuYSBkaWZlcmVuY2lhIGVzcGVyYWJsZSBkYWRvIGVsIGZ1ZXJ0ZSBwZXNvIGRlIEVFLiBVVS4gZW4gbGEgZnVlbnRlLCBtw6FzIHF1ZSB1bmEgc2XDsWFsIGRlIGV4cGVjdGF0aXZhcyBlcXVpdm9jYWRhcy4NCg0KRWwgZWplcmNpY2lvIGNvbXBsZXRvIOKAlGRlc2RlIGV4cGxvcmFyIGEgZm9uZG8gdW5hIGZ1ZW50ZSBleHRlcm5hIGhhc3RhIGNvbnN0cnVpciB1biBtYXBhIHkgdGFibGFzIGludGVyYWN0aXZhcywgeSBjZXJyYXIgY29uIHVuYSBjb21wYXJhY2nDs24gaG9uZXN0YeKAlCBlcyB0YW1iacOpbiBlbCBwdW50byBkZSBwYXJ0aWRhIG1ldG9kb2zDs2dpY28gcGFyYSBlbCBwcm95ZWN0byB0cmFuc3ZlcnNhbCBkZWwgY3Vyc286IGxhIG1pc21hIGzDs2dpY2EgKHByZWd1bnRhIOKGkiBlc3RhZMOtc3RpY2Eg4oaSIHZpc3VhbGl6YWNpw7NuIOKGkiBpbnRlcnByZXRhY2nDs24g4oaSIGNvbXVuaWNhY2nDs24pIGFwbGljYSBpZ3VhbCBkZSBiaWVuIGEgY3VhbHF1aWVyIG90cm8gZGF0YXNldCBxdWUgY2FkYSBlc3R1ZGlhbnRlIGVsaWphIHBhcmEgc3UgcHJvcGlvIHByb3llY3RvIGZpbmFsLg0K