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:

  1. 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.
  2. 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:

  1. Describir la cobertura y calidad del dataset de mercado (países, años, tipos de contrato y experiencia).
  2. Explorar cómo varía el salario según el rol, la experiencia, la modalidad de trabajo y el país.
  3. 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.
  4. Comparar, al final, el perfil y las expectativas de nuestro grupo con esta realidad de mercado.
  5. Comunicar los hallazgos con honestidad, reconociendo qué sí y qué no permiten concluir los datos.

3. Fuentes de información

3.1 Dataset de mercado

“Data Science Salaries 2025” (Kaggle, usuario arnabchaki) — republicación de datos que profesionales de IA, datos y ML reportan voluntaria y anónimamente en aijobs.net/salaries. Licencia CC0 (dominio público), datos autorreportados desde 2020.

tibble(
  Variable = c("work_year", "experience_level", "employment_type", "job_title", "salary_in_usd", "employee_residence / company_location", "remote_ratio"),
  Descripción = c(
    "Año del registro salarial (2020-2025)",
    "Nivel de experiencia: Junior, Semi-Senior, Senior, Directivo",
    "Tipo de contrato: tiempo completo, medio tiempo, contrato, freelance",
    "Cargo reportado (texto libre de la fuente original -- más de 300 variantes distintas)",
    "Salario anual, ya convertido a USD por la fuente",
    "País de residencia del empleado / país de la empresa (código ISO de 2 letras)",
    "0 = presencial, 50 = híbrido, 100 = remoto"
  )
) %>%
  # Resaltamos salary_in_usd (fila 5): es la variable protagonista del informe.
  tabla_bonita(resaltar = 5)
Variable Descripción
work_year Año del registro salarial (2020-2025)
experience_level Nivel de experiencia: Junior, Semi-Senior, Senior, Directivo
employment_type Tipo de contrato: tiempo completo, medio tiempo, contrato, freelance
job_title Cargo reportado (texto libre de la fuente original – más de 300 variantes distintas)
salary_in_usd Salario anual, ya convertido a USD por la fuente
employee_residence / company_location País de residencia del empleado / país de la empresa (código ISO de 2 letras)
remote_ratio 0 = presencial, 50 = híbrido, 100 = remoto
n_paises <- n_distinct(salarios_mercado$company_location)
n_roles <- n_distinct(salarios_mercado$job_title)

paste0(
  comma(nrow(salarios_mercado), big.mark = "."), " registros · ",
  n_paises, " países de empresa · ", n_roles, " nombres de rol distintos · años ",
  min(salarios_mercado$work_year), "-", max(salarios_mercado$work_year)
)
## [1] "105.434 registros · 92 países de empresa · 346 nombres de rol distintos · años 2020-2025"

Un dato clave antes de interpretar cualquier cifra:

salarios_mercado %>%
  count(pais_empresa, sort = TRUE) %>%
  mutate(porcentaje = round(n / sum(n) * 100, 1)) %>%
  slice_head(n = 5) %>%
  # rename(): renombra columnas solo para mostrarlas en español y presentables.
  rename(`País` = pais_empresa, `N.º de registros` = n, `%` = porcentaje) %>%
  tabla_bonita(resaltar = 1)   # Estados Unidos: concentra casi el 90% de las filas
País N.º de registros %
Estados Unidos 94834 89.9
Canadá 3987 3.8
Reino Unido 2962 2.8
Australia 378 0.4
Alemania 311 0.3

Casi el 90% de las filas son de empresas en Estados Unidos; el resto de países aporta muy pocos registros. Por eso, a lo largo del informe, la comparación se lee como mercado global (mayoritariamente EE. UU.) y no como un promedio equilibrado del mundo.

3.2 Nuestra encuesta

Para el cruce final (sección 5), usamos también la encuesta propia del curso: cada estudiante llenó su fila en un formulario compartido, ya limpia, consolidada y anonimizada (ver 1 consolidacion_limpieza_final_encuesta.R). Aquí solo usamos dos de sus columnas: el rol de interés profesional y el salario anual esperado.

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