Territorio y seguridad en Bogotá: un análisis de las denuncias por UPZ
Una exploración de las características demográficas y territoriales y su relación con las denuncias por delito en Bogotá
library(tidyverse)      # incluye dplyr, tidyr, readr, purrr y ggplot2
library(scales)
library(showtext)
library(sysfonts)
library(sf)
library(plotly)
library(kableExtra)
library(jsonlite)

fuente_editorial <- "Fira Sans"
font_add_google(name = fuente_editorial, family = fuente_editorial)
showtext_auto()
# 1. cargar datos
upz <- read_csv(file.path(ruta_input, "upz_variables.csv"), show_col_types = FALSE)

# 1.1 . Explorar Data set UPZ

# Dimensiones de la base
dim(upz)

# Nombres de las columnas
names(upz)

# glimpse(): ver TODAS las columnas, su tipo y los primeros valores en una pantalla.
glimpse(upz)

# Primeras observaciones
head(upz)

# Las columnas están agrupadas por prefijo. starts_with() selecciona por prefijo:
upz %>% select(upz_nombre, starts_with("tasa_")) %>% head()   # tasas de delitos /10.000 hab
upz %>% select(upz_nombre, starts_with("min_"))  %>% head()   # minutos caminando a servicios

# 1.2 validar faltantes en Dataset

faltantes <- upz |>
  summarise(across(everything(),
                   ~ sum(is.na(.x)))) |>
  pivot_longer(
    cols = everything(),
    names_to = "variable",
    values_to = "faltantes"
  ) |>
  arrange(desc(faltantes))

faltantes
## 2. cargar ´paleta de colores 
#--- Paleta y tema del curso (los mismos de todos los scripts) --------------
morado   <- "#351A4F"
magenta  <- "#D9006C"
turquesa <- "#168C86"
gris     <- "#D9D6DD"

tema_clase <- theme_minimal(base_size = 12) +
  theme(
    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)
  )


estilo_plotly <- function(p) {
  p %>%
    config(displayModeBar = FALSE) %>%          
    layout(font = list(family = fuente_editorial)) 
}

tabla_bonita <- function(df, resaltar = NULL) {
  k <- df %>%
    kbl() %>%   
    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)) {
    k <- k %>% row_spec(resaltar, background = "#F6EEF2", bold = TRUE, color = morado)
  }
  k
}

# --- Segmentación poblacional: nombres y colores (se usan en todo el informe) ---
grupos  <- c("Población baja", "Población media", "Población alta")
colores <- c("Población baja"  = "#F8766D",
             "Población media" = "#F2B84B",
             "Población alta"  = "#4C9F70")

# --- Formato de números y porcentajes (estilo colombiano) ---
num <- function(x, ...) scales::number(x, big.mark = ".", decimal.mark = ",", ...)
pc  <- function(x) scales::percent(x, accuracy = 0.1, decimal.mark = ",")

# --- Piezas de estilo plotly que se repetían en varios gráficos ---
hover_plotly <- list(bgcolor = morado, font = list(color = "white", size = 13), align = "left")

# Parte un título largo en varias líneas (plotly no baja de línea solo y el título se corta)
partir_titulo <- function(texto, ancho = 75) paste(strwrap(texto, width = ancho), collapse = "<br>")

titulo_plotly <- function(texto, ...) {
  c(list(text = texto, x = 0.01, xanchor = "left", y = 0.97, yanchor = "top",
         font = list(size = 18, color = morado)), list(...))
}

menu_plotly <- function(botones, x = 0, y = 1.04) {
  list(type = "dropdown", direction = "down", active = 0, showactive = TRUE,
       x = x, xanchor = "left", y = y, yanchor = "bottom", buttons = botones,
       bgcolor = "white", bordercolor = gris, font = list(color = morado, size = 13))
}

# --- Caja de comentarios debajo del gráfico (la usan 4.3 y 4.4) ---
js_comentarios <- "
function(el, x, datos) {
  var caja = document.createElement('div');
  caja.style.cssText = 'width:100%;box-sizing:border-box;padding:14px 18px;margin-top:6px;border-top:2px solid #D9D6DD;font-size:14px;line-height:1.5;color:#333;';
  el.parentNode.insertBefore(caja, el.nextSibling);
  var actual = -1;

  function pintar(i) {
    if (i === actual) return;
    actual = i;
    caja.innerHTML = '';
    var ul = document.createElement('ul');
    ul.style.cssText = 'margin:0;padding-left:20px;';
    [].concat(datos.comentarios[i]).forEach(function(t) {
      var li = document.createElement('li');
      li.style.marginBottom = '4px';
      li.innerHTML = t;
      ul.appendChild(li);
    });
    caja.appendChild(ul);
  }

  // devuelve el número de vista según qué traza de inicio está visible (datos.inicios = traza inicial de cada vista)
  function vista() {
    var d = el.data, ini = datos.inicios;
    for (var i = 0; i < ini.length; i++) {
      if (d[ini[i]] && d[ini[i]].visible === true) return i;
    }
    return 0;
  }

  pintar(0);
  el.on('plotly_afterplot', function() { pintar(vista()); });
}
"

1. Introducción

En este informe se analizan las 112 UPZ de Bogotá, con el propósito de explorar las características demográficas y territoriales de cada una, así como su relación con las denuncias registradas entre enero y julio de 2023. Además, se busca identificar posibles relaciones entre la composición de la población, las características del territorio y la cantidad y las tasas de denuncias asociadas a diferentes tipos de delito. De esta manera, se pretende reconocer patrones y diferencias entre las UPZ que permitan comprender mejor el comportamiento territorial de las denuncias en la ciudad.

Preguntas que guían el análisis

  1. ¿Segmentar las UPZ por tamaño de población permite comparaciones más justas?
  2. ¿Cómo varían las tasas de denuncias según el grupo de población y qué UPZ son atípicas?
  3. ¿Qué delitos predominan en cada grupo y cambia su peso entre grupos?
  4. ¿Cómo se relaciona la composición por edad con las tasas de delito y esa relación cambia según el grupo?
  5. ¿Dónde se ubican las UPZ con mayores tasas y cómo se ven las segmentaciones en el territorio?

Variables seleccionadas y por qué

Se usan 14 variables agrupadas en tres bloques, porque cada bloque responde a una parte de las preguntas:

Bloque Variables Por qué se eligen
Territorial area_km2, poblacion Describen el tamaño y la población de cada UPZ. poblacion es la base de la segmentación (K-means) y del cálculo de tasas.
Demográfico pct_0_14, pct_15_29, pct_30_59 (Se genera esta variable), pct_60_mas Permiten ver si la estructura por edades se asocia con más o menos denuncias.
Delitos casos_total, tasa_total, hurto_total (suma de 7 tipos de hurto), lesiones, homicidios, violencia_intrafamiliar y sus tasas Son lo que se quiere explicar. Se usan tasas por cada 10.000 habitantes para comparar UPZ de distinto tamaño.

Nota: importante indicar que las cifras son denuncias registradas, no delitos ocurridos. Por eso se interpretan como asociaciones entre UPZ y no como causas.

2. Objetivos

Objetivo general: Analizar la relación entre las características demográficas y territoriales de las 112 UPZ de Bogotá y la cantidad y las tasas de denuncias registradas por diferentes tipos de delito, con el fin de identificar patrones y diferencias territoriales durante el periodo de estudio.

Objetivos específicos:

  1. Segmentar las UPZ de Bogotá a partir de sus variables como la población, la composición por grupos de edad, el área y la densidad poblacional.

  2. Describir y comparar la cantidad y las tasas de denuncias correspondientes a diferentes tipos de delito, identificando las UPZ que presentan mayores y menores registros.

  3. Explorar las posibles relaciones entre las características poblacionales y territoriales de las UPZ y el comportamiento de las denuncias, mediante herramientas estadísticas y visualizaciones que permitan identificar patrones y diferencias espaciales.

3. Fuentes de información

3.1 Dataset de estudio UPZ Bogotá

3.1.1 Fuentes

  1. Delitos: Secretaría Distrital de Seguridad, Convivencia y Justicia. Población y viviendas: proyecciones DANE (CNPV 2018).

  2. Luminarias: Datos Abiertos Bogotá. Encuesta Multipropósito 2021: DANE.

3.1.2 Variables analisis de estudio

# Las 8 variables de estudio con su descripción (una sola vez: también se usa en la tabla descriptiva)
descripcion_variables <- c(
  area_km2         = "Área territorial de la UPZ en kilómetros cuadrados",
  poblacion        = "Cantidad de habitantes de la UPZ",
  densidad_hab_km2 = "Densidad de habitantes por kilómetro cuadrado",
  pct_0_14         = "Porcentaje de población entre 0 y 14 años",
  pct_15_29        = "Porcentaje de población entre 15 y 29 años",
  pct_60_mas       = "Porcentaje de población mas de 60 años",
  casos_total      = "Cantidad total de denuncias registradas",
  tasa_total       = "Tasa total de denuncias por cada 10.000 habitantes"
)

variables <- names(descripcion_variables)

tibble(
  Variable    = variables,
  Descripcion = unname(descripcion_variables)
) %>%
  tabla_bonita(resaltar = c(1,2))
Variable Descripcion
area_km2 Área territorial de la UPZ en kilómetros cuadrados
poblacion Cantidad de habitantes de la UPZ
densidad_hab_km2 Densidad de habitantes por kilómetro cuadrado
pct_0_14 Porcentaje de población entre 0 y 14 años
pct_15_29 Porcentaje de población entre 15 y 29 años
pct_60_mas Porcentaje de población mas de 60 años
casos_total Cantidad total de denuncias registradas
tasa_total Tasa total de denuncias por cada 10.000 habitantes

Datos importantes del dataset a tener en cuenta

n_upz <- n_distinct(upz$upz_cod)

poblacion_total <- sum(upz$poblacion, na.rm = TRUE)

area_total <- sum(upz$area_km2, na.rm = TRUE)

total_denuncias <- sum(upz$casos_total, na.rm = TRUE)

paste0(
  n_upz, " UPZ analizadas · ",
  comma(poblacion_total, big.mark = "."), " habitantes · ",
  comma(round(area_total, 2), big.mark = "."), " km² de extensión · ",
  comma(total_denuncias, big.mark = "."), " denuncias registradas","  Periodo: enero-julio de 2023"
)
## [1] "112 UPZ analizadas · 7.936.532 habitantes · 415 km² de extensión · 162.835 denuncias registradas  Periodo: enero-julio de 2023"

Bogotá en cifras: una mirada estadística a las UPZ:

# "variables" viene de la tabla de variables (sección 3.1.2)

tabla_descriptiva <- data.frame(
  Media = sapply(upz[variables], mean, na.rm = TRUE),
  Mediana = sapply(upz[variables], median, na.rm = TRUE),
  Desviacion = sapply(upz[variables], sd, na.rm = TRUE),
  Minimo = sapply(upz[variables], min, na.rm = TRUE),
  Maximo = sapply(upz[variables], max, na.rm = TRUE)
)

# Redondear las estadísticas a 1 decimal
tabla_descriptiva[] <- lapply(tabla_descriptiva, round, digits = 1)

# Mostrar tabla
tabla_descriptiva %>%
  tabla_bonita(resaltar = c(1, 2))
Media Mediana Desviacion Minimo Maximo
area_km2 3.7 3.5 1.7 0.9 9.3
poblacion 70861.9 54102.5 58406.4 30.0 305907.0
densidad_hab_km2 19119.4 20180.2 10801.9 9.4 46156.9
pct_0_14 17.3 16.7 4.8 7.4 31.0
pct_15_29 24.1 24.0 4.5 14.8 51.9
pct_60_mas 15.7 14.6 5.1 7.5 29.9
casos_total 1453.9 1222.0 1023.1 16.0 5441.0
tasa_total 503.6 192.5 1263.4 64.5 11333.3

4. Analisis poblacional y territorial de las upz

4.1 ¿La segmentación de las poblaciones permite realizar un análisis más preciso y profundo de la información?

p_poblacion_area <- ggplot(upz, aes(x = area_km2, y = poblacion)) +
  geom_point(
    color = "#6B4C8A",
    size = 2.5,
    alpha = 0.7,
    aes(text = paste0(
      "UPZ: ", upz_nombre,
      "<br>Área: ", round(area_km2, 2), " km²",
      "<br>Población: ",
      format(round(poblacion), big.mark = ".", decimal.mark = ",",
             scientific = FALSE)
    ))
  ) +
  geom_smooth(
    method = "lm",
    se = FALSE,
    color = "#D17A22"
  ) +
  scale_y_continuous(
    labels = scales::label_comma(
      big.mark = ".",
      decimal.mark = ","
    )
  ) +
  labs(
    x = "Área territorial (km²)",
    y = "Población (habitantes)"
  ) +
  tema_clase +
  theme(
    panel.grid.major.x = element_blank()
  )

ggplotly(p_poblacion_area, tooltip = "text") %>%
  estilo_plotly() %>%
  layout(
    title = titulo_plotly(
      paste0(
        "<b>", partir_titulo("El área y la población se relacionan de manera moderada: algunas UPZ pequeñas tienen más población que otras más grandes"), "</b>",
        "<br><sup>Cada punto representa una UPZ. Pasa el cursor sobre cada punto para consultar sus cifras.</sup>"
      ),
      xref = "container"
    ),
    margin = list(t = 125)
  )

Al revisar los datos, se evidencia que la población presenta una diferencia bastante amplia entre el valor mínimo y el máximo. Por esta razón, considero necesario dividir las UPZ en tres grupos: población baja, población media y población alta. Esto permitirá realizar comparaciones entre UPZ que tengan características poblacionales similares y analizar de una mejor manera las tasas de denuncias, teniendo en cuenta que estas pueden variar bastante dependiendo de la cantidad de habitantes de cada territorio.

4.2 ¿Cómo varían las tasas de denuncias según el grupo de población y qué UPZ son atípicas?

Teniendo en cuenta el hallazgo anterior y generada la segmentación por medio de K-Means se evidencia lo siguiente.

Estadisticas descriptivas segmentación generada

# K-means # modelo de clusters
set.seed(123)

modelo <- kmeans(
  scale(upz["poblacion"]),
  centers = 3,
  nstart = 25
)

# Identificar el orden de los grupos según su población promedio
orden_grupos <- order(modelo$centers[, 1])

# Asignar las categorías a cada UPZ (los nombres de los grupos se definen en el chunk "estilo")
upz <- upz %>%
  mutate(
    grupo_poblacion = factor(
      grupos[match(modelo$cluster, orden_grupos)],
      levels = grupos
    )
  )
# Variables derivadas que se usan en 4.3, 4.4 y el mapa (se calculan una sola vez)
cols_hurto <- c("casos_hurto_personas", "casos_hurto_residencias", "casos_hurto_automotor",
                "casos_hurto_bicicletas", "casos_hurto_comercio", "casos_hurto_celulares",
                "casos_hurto_motos")

upz <- upz %>%
  mutate(
    casos_hurto_total = rowSums(across(all_of(cols_hurto))),
    tasa_hurto_total  = casos_hurto_total / poblacion * 10000,
    pct_30_59         = 100 - (pct_0_14 + pct_15_29 + pct_60_mas)   # lo que falta para llegar a 100 %
  )
k_atipico <- 1.5   # límite atípico: Q3 + 1,5 × IQR
k_extremo <- 3     # límite extremo: Q3 + 3 × IQR

# Agregar n a las etiquetas (grupo_poblacion ya es factor ordenado desde el K-means)
upz <- upz %>%
  group_by(grupo_poblacion) %>%
  mutate(
    # todo se calcula con las tasas reales (sin log)
    q1_g = quantile(tasa_total, 0.25),
    q3_g = quantile(tasa_total, 0.75),
    iqr_g = q3_g - q1_g,
    lim_atipico = q3_g + k_atipico * iqr_g,
    lim_extremo = q3_g + k_extremo * iqr_g,
    es_atipico  = tasa_total > lim_atipico | tasa_total < q1_g - k_atipico * iqr_g,
    es_extremo  = tasa_total > lim_extremo | tasa_total < q1_g - k_extremo * iqr_g,
    etiqueta = paste0(grupo_poblacion, "<br>(n = ", n(), ")")
  ) %>%
  ungroup() %>%
  mutate(
    etiqueta = factor(etiqueta, levels = unique(etiqueta[order(grupo_poblacion)])),
    pos = as.numeric(etiqueta),
    texto = paste0(
      "<b>", upz_nombre, "</b>",
      "<br>Localidad: ", localidad,
      "<br>Población: ", scales::comma(poblacion),
      "<br>Tasa total: ", round(tasa_total, 1),
      ifelse(es_extremo, "<br><i>Atípico extremo</i>", "<br><i>Atípico</i>")
    )
  )

# límites por grupo (para que los revises)
upz %>% distinct(grupo_poblacion, q1_g, q3_g, iqr_g, lim_atipico, lim_extremo)%>%
  tabla_bonita(resaltar = c())
grupo_poblacion q1_g q3_g iqr_g lim_atipico lim_extremo
Población baja 163.1525 479.1225 315.97 953.0775 1427.033
Población media 106.4600 192.2100 85.75 320.8350 449.460
Población alta 113.9600 138.5400 24.58 175.4100 212.280
rgba <- function(col, a) { m <- col2rgb(col); sprintf("rgba(%d,%d,%d,%.2f)", m[1], m[2], m[3], a) }

g <- plot_ly()

# 1. cajas: los bigotes terminan en el límite superior/inferior (no en el máximo)
#    los puntos propios de la caja se ocultan; los atípicos que se ven son los de abajo
for (gr in levels(upz$grupo_poblacion)) {
  d <- upz %>% filter(grupo_poblacion == gr)
  g <- g %>% add_trace(
    data = d, type = "box",
    x = ~pos, y = ~tasa_total, width = 0.6,
    fillcolor = rgba(colores[[gr]], 0.5),
    line = list(color = "grey20", width = 1.5),
    boxpoints = "outliers",
    marker = list(opacity = 0, size = 1),   # puntos de la caja invisibles
    quartilemethod = "linear",
    hoveron = "boxes",                      # el cuadro del mouse solo sale sobre la caja
    hoverinfo = "y", showlegend = FALSE
  )
}

# 2. atípicos (negros)
set.seed(123)
negros <- upz %>% filter(es_atipico, !es_extremo) %>%
  mutate(xj = pos + runif(n(), -0.07, 0.07))

g <- g %>% add_trace(
  data = negros, type = "scatter", mode = "markers",
  x = ~xj, y = ~tasa_total,
  marker = list(color = "grey20", size = 7, opacity = 0.75),
  hovertext = ~texto, hoverinfo = "text", showlegend = FALSE
)

# 3. atípicos extremos (rojos)
rojos <- upz %>% filter(es_extremo) %>%
  mutate(xj = pos + runif(n(), -0.07, 0.07))

g <- g %>% add_trace(
  data = rojos, type = "scatter", mode = "markers",
  x = ~xj, y = ~tasa_total,
  marker = list(color = "red", size = 10, opacity = 0.9),
  hovertext = ~texto, hoverinfo = "text", showlegend = FALSE
)

# 4. escala log solo para la vista
marcas <- c(50, 100, 200, 500, 1000, 2000, 5000, 10000)
etiquetas_x <- levels(upz$etiqueta)

g <- g %>%
  layout(
    title = list(
      text = paste0("<b>", partir_titulo("Poblacion baja tiene los atipicos extremos, aumentando la tasa de delitos, Poblacion media y alta son homogéneas"), "</b>",
                    "<br><sup>Método K-means · puntos negros = atípicos (> Q3 + 1,5 × IQR) · ",
                    "puntos rojos = atípicos extremos (> Q3 + 3 × IQR) · pasa el cursor para ver los valores</sup>"),
      x = 0.01, xanchor = "left"
    ),
    xaxis = list(title = "Grupo de población", showgrid = FALSE, zeroline = FALSE,
                 tickvals = seq_along(etiquetas_x), ticktext = etiquetas_x,
                 range = c(0.4, length(etiquetas_x) + 0.6)),
    yaxis = list(title = "Tasa total de delitos (escala log)", type = "log",
                 tickvals = marcas, ticktext = format(marcas, big.mark = ".", scientific = FALSE),
                 hoverformat = ".1f"),
    hoverlabel = list(align = "left"),
    margin = list(t = 125)
  ) %>%
  estilo_plotly()

g

En las poblaciones media y alta no se identifican valores atípicos que puedan afectar significativamente el análisis de correlación entre el tamaño de la población y las tasas de delito. Sin embargo, en la población baja se identifican varios valores atipicos extremos, como se muestran en la tabla a continuacion.

Estos valores merecen especial atención, ya que podrían influir en los resultados del análisis de correlación y afectar la interpretación de la relación entre el tamaño de la población y las tasas de delito.

4.3 ¿Qué delitos predominan en cada grupo y cambia su peso entre grupos?

Tabla de UPZ con valores atípicos en las tasas de denuncias por delitos.

# 1. El hurto total y su tasa (por 10.000 hab.) se calculan en el chunk "preparar_datos"

# 2. Funciones de apoyo ----
orden_delitos <- c("Hurto", "Lesiones", "Homicidios", "Violencia intrafamiliar")

resumir <- function(datos) {
  datos %>%
    summarise(
      poblacion  = sum(poblacion),
      Hurto      = sum(casos_hurto_total),
      Lesiones   = sum(casos_lesiones),
      Homicidios = sum(casos_homicidios),
      `Violencia intrafamiliar` = sum(casos_violencia_intrafamiliar)
    ) %>%
    pivot_longer(all_of(orden_delitos), names_to = "delito", values_to = "casos") %>%
    mutate(tasa = casos / poblacion * 10000)
}

expandir <- function(d, rango, n_pts = 40) {
  p <- d[rep(seq_len(nrow(d)), each = n_pts), ]
  p$x <- rep(seq(0, rango, length.out = n_pts), times = nrow(d))
  p
}

# 3. Vista 1: población baja con vs sin atípicos extremos ----
# es_extremo ya viene calculado en el boxplot (Q3 + 3 × IQR, con tasas reales y por segmentación)
baja_con <- upz %>% filter(grupo_poblacion == "Población baja")

atipicos <- baja_con %>%
  filter(es_extremo) %>%
  arrange(desc(tasa_total))

n_atip   <- nrow(atipicos)
baja_sin <- baja_con %>% filter(!(upz_cod %in% atipicos$upz_cod))

comp <- resumir(baja_con) %>%
  select(delito, casos_con = casos, tasa_con = tasa) %>%
  left_join(resumir(baja_sin) %>% select(delito, casos_sin = casos, tasa_sin = tasa), by = "delito") %>%
  mutate(
    dif_casos = ifelse(casos_con == 0, 0, casos_sin / casos_con - 1),
    es_max    = casos_con == max(casos_con),
    etiqueta  = ifelse(es_max, paste0(delito, "  ★"), delito),
    texto = paste0(
      "<b>", delito, "</b>",
      "<br>Con atípicos extremos: ", num(casos_con), " casos · tasa ", num(tasa_con, accuracy = 0.1),
      "<br>Sin atípicos extremos: ", num(casos_sin), " casos · tasa ", num(tasa_sin, accuracy = 0.1),
      "<br>Cambio en casos: ", pc(dif_casos)
    )
  ) %>%
  arrange(desc(casos_con))

rango_baja  <- max(comp$casos_con) * 1.1
puntos_baja <- expandir(comp, rango_baja)

prom_con <- mean(baja_con$tasa_total);   prom_sin <- mean(baja_sin$tasa_total)
med_con  <- median(baja_con$tasa_total); med_sin  <- median(baja_sin$tasa_total)
pct_pob_ext <- sum(atipicos$poblacion) / sum(baja_con$poblacion)

#3.1 tabla de atipicos extremos

tabla_extremos <- atipicos %>%      # ya viene ordenada de mayor a menor tasa
  transmute(
    `#`          = row_number(),
    UPZ          = upz_nombre,
    Localidad    = localidad,
    Población    = num(poblacion),
    `Casos totales` = num(casos_total),
    `Tasa total (por 10.000 hab.)` = num(tasa_total, accuracy = 0.1),
    `Veces la mediana del grupo`   = paste0(num(tasa_total / median(baja_con$tasa_total),
                                                accuracy = 0.1), "x")
  )

tabla_bonita(tabla_extremos)
# UPZ Localidad Población Casos totales Tasa total (por 10.000 hab.) Veces la mediana del grupo
1 El Mochuelo Ciudad Bolívar 30 34 11.333,3 43,5x
2 Aeropuerto El Dorado Fontibón 883 545 6.172,1 23,7x
3 Parque Simon Bolivar - Can Teusaquillo 3.323 958 2.882,9 11,1x
4 Zona Industrial Puente Aranda 6.666 1.573 2.359,7 9,1x
5 Las Nieves Santa Fe 17.203 4.033 2.344,4 9,0x
6 Parque Salitre Barrios Unidos 1.442 233 1.615,8 6,2x
7 Sagrado Corazon Santa Fe 11.247 1.707 1.517,7 5,8x
# 4. Vistas 2 y 3: población media y alta ----
preparar <- function(g) {
  resumir(upz %>% filter(grupo_poblacion == g)) %>%
    mutate(
      pct      = casos / sum(casos),
      es_max   = casos == max(casos),
      etiqueta = ifelse(es_max, paste0(delito, "  ★"), delito),
      texto = paste0("<b>", delito, "</b>",
                     "<br>Casos: ", num(casos), " (", pc(pct), " del total)",
                     "<br>Tasa: ", num(tasa, accuracy = 0.1), " por 10.000 hab.")
    ) %>%
    arrange(desc(casos))
}

d_media <- preparar("Población media")
d_alta  <- preparar("Población alta")

stopifnot(
  sum(baja_con$casos_hurto_total) == comp$casos_con[comp$delito == "Hurto"],
  sum(baja_sin$casos_hurto_total) == comp$casos_sin[comp$delito == "Hurto"],
  nrow(baja_con) - nrow(baja_sin) == n_atip,
  sum(d_media$casos) == sum(upz$casos_total[upz$grupo_poblacion == "Población media"]),
  sum(d_alta$casos)  == sum(upz$casos_total[upz$grupo_poblacion == "Población alta"])
)

rango_media <- max(d_media$casos) * 1.1
rango_alta  <- max(d_alta$casos) * 1.1

puntos_media <- expandir(d_media, rango_media)
puntos_alta  <- expandir(d_alta, rango_alta)

# 5. COMENTARIOS ----
# >>> AQUÍ ESCRIBES TU COMENTARIO PARA CADA VISTA (puede ser tan largo como quieras) <<<
# Se muestra completo, de lado a lado, solo cuando eliges esa vista en el menú.
# Déjalo "" si no quieres agregar nada.
mis_comentarios <- c(
  "Población baja: con vs sin atípicos" = "Si se quitan los 7 atípicos extremos no genera un cambio de ranking entre delitos en la población baja, pero lo que sí cambia son las tasas de delitos y los promedios, en donde los casos de hurto bajan de tasa 288.5 a 261.5 por cada 10.000 habitantes, este cambio en la tasa pasa debido que estas UPZ tienen muy pocas personas y muchas más denuncias, en los otros delitos, se presenta variación pero no es tan considerable como el resultado de Hurto",
  "Población media"                     = "Frente a población baja, la tasa de hurto baja a menos de la mitad (288,5 > 122,4). Además, aquí no hay valores atípicos: las UPZ se parecen bastante entre sí, con tasas totales que van de 72 a 312.",
  "Población alta"                      = "Lo que sí cambia es el peso de la violencia intrafamiliar: pasa de 6,6 % en población baja a 19,9 % aquí, y su tasa 23,9 es la más alta de los tres grupos, siendos solo 5 UPZ."
)

mostrar_automatico <- TRUE   # FALSE = muestra solo tus comentarios

vistas <- names(mis_comentarios)

# vista 1
top_b <- comp %>% filter(es_max)
nombres_atip <- paste0(atipicos$upz_nombre, " (", num(atipicos$poblacion), " hab.)", collapse = ", ")
pct_top_con  <- top_b$casos_con / sum(comp$casos_con)
pct_top_sin  <- top_b$casos_sin / sum(comp$casos_sin)

comentario_baja <- c(
  if (mostrar_automatico) c(
    paste0("<b>Atípicos extremos retirados (", n_atip, "):</b> ", nombres_atip,
           ". Juntos tienen solo el ", pc(pct_pob_ext), " de la población del grupo."),
    paste0("<b>Efecto en los totales:</b> el ", tolower(top_b$delito), " pasa de ",
           num(top_b$casos_con), " a ", num(top_b$casos_sin), " casos (", pc(top_b$dif_casos),
           ") y su tasa de ", num(top_b$tasa_con, accuracy = 0.1), " a ", num(top_b$tasa_sin, accuracy = 0.1), "."),
    paste0("<b>Lo que no cambia:</b> el ", tolower(top_b$delito), " sigue siendo el delito más frecuente (",
           pc(pct_top_con), " del total con atípicos y ", pc(pct_top_sin), " sin ellos)."),
    paste0("<b>Promedio vs mediana de la tasa por UPZ:</b> el promedio pasa de ", num(prom_con, accuracy = 0.1),
           " a ", num(prom_sin, accuracy = 0.1), " (", pc(prom_sin / prom_con - 1),
           "), mientras la mediana pasa de ", num(med_con, accuracy = 0.1), " a ",
           num(med_sin, accuracy = 0.1), " (", pc(med_sin / med_con - 1), ").")
  ),
  if (nzchar(mis_comentarios[[vistas[1]]])) paste0("<b>Comentario:</b> ", mis_comentarios[[vistas[1]]])
)

# vistas 2 y 3
comentario_grupo <- function(g, d) {
  top <- d[1, ]; seg <- d[2, ]; ult <- d[nrow(d), ]
  n_upz <- sum(upz$grupo_poblacion == g)
  c(
    if (mostrar_automatico) c(
      paste0("<b>Contexto:</b> ", n_upz, " UPZ y ", num(top$poblacion), " habitantes en este grupo."),
      paste0("<b>Más frecuente:</b> ", tolower(top$delito), ", con ", num(top$casos),
             " casos (", pc(top$pct), " del total) y una tasa de ", num(top$tasa, accuracy = 0.1),
             " por cada 10.000 habitantes."),
      paste0("<b>Le sigue:</b> ", tolower(seg$delito), ", con ", num(seg$casos),
             " casos (", pc(seg$pct), ")."),
      paste0("<b>Menos frecuente:</b> ", tolower(ult$delito), ", con ", num(ult$casos),
             " casos (", pc(ult$pct), ").")
    ),
    if (nzchar(mis_comentarios[[g]])) paste0("<b>Comentario:</b> ", mis_comentarios[[g]])
  )
}

comentarios <- list(comentario_baja,
                    comentario_grupo("Población media", d_media),
                    comentario_grupo("Población alta",  d_alta))

# 6. Gráfico ----
op <- function(d) ifelse(d$es_max, 1, 0.45)   # el delito más frecuente queda resaltado
invisible_marker <- list(color = "rgba(0,0,0,0)", size = 12)

# vistas 2 y 3: barras + puntos invisibles para el cuadro del mouse (misma estructura, cambian datos y color)
barras_vista <- function(fig, d, puntos, nombre, color) {
  fig %>%
    add_trace(data = d, type = "bar", orientation = "h", name = nombre,
              x = ~casos, y = ~etiqueta,
              marker = list(color = color, opacity = op(d)),
              hoverinfo = "skip", visible = FALSE, showlegend = FALSE) %>%
    add_trace(data = puntos, type = "scatter", mode = "markers",
              x = ~x, y = ~etiqueta, marker = invisible_marker,
              hovertext = ~texto, hoverinfo = "text", visible = FALSE, showlegend = FALSE)
}

fig <- plot_ly() %>%
  # --- vista 1 (trazas 1-3)
  add_trace(data = comp, type = "bar", orientation = "h", name = "Con atípicos extremos",
            x = ~casos_con, y = ~etiqueta,
            marker = list(color = magenta, opacity = op(comp)),
            hoverinfo = "skip", visible = TRUE, showlegend = TRUE) %>%
  add_trace(data = comp, type = "bar", orientation = "h", name = "Sin atípicos extremos",
            x = ~casos_sin, y = ~etiqueta,
            marker = list(color = morado, opacity = op(comp)),
            hoverinfo = "skip", visible = TRUE, showlegend = TRUE) %>%
  add_trace(data = puntos_baja, type = "scatter", mode = "markers",
            x = ~x, y = ~etiqueta, marker = invisible_marker,
            hovertext = ~texto, hoverinfo = "text", visible = TRUE, showlegend = FALSE) %>%
  # --- vista 2: población media (trazas 4-5) y vista 3: población alta (trazas 6-7)
  barras_vista(d_media, puntos_media, "Población media", turquesa) %>%
  barras_vista(d_alta,  puntos_alta,  "Población alta",  morado)

titulos <- c(paste0("Delitos Población baja: con y sin los ", n_atip, " valores atípicos extremos, Hurto: Delito que predomina con y sin los ", n_atip, " atípicos extremos."),
             "Delitos en población media, Hurto contiene el 80% de los casos.",
             "Delitos en población alta, Hurto predomina, pero violencia intrafamiliar presenta 20% de los casos siendo solo 5 UPZ.")
subtitulos <- c("Cantidad de casos · ★ y color fuerte = el delito que más se presenta · pasa el cursor para ver el detalle",
                "Cantidad de casos · ★ y color fuerte = el delito que más se presenta · pasa el cursor para ver casos y tasa",
                "Cantidad de casos · ★ y color fuerte = el delito que más se presenta · pasa el cursor para ver casos y tasa")
rangos  <- c(rango_baja, rango_media, rango_alta)
ordenes <- list(comp$etiqueta, d_media$etiqueta, d_alta$etiqueta)

titulo <- function(i) paste0("<b>", partir_titulo(titulos[i]), "</b><br><sup>", subtitulos[i], "</sup>")

# 7 trazas: 1-3 vista 1, 4-5 vista 2, 6-7 vista 3
trazas_por_vista <- c(3, 2, 2)
visibilidad <- function(i) rep(seq_along(vistas) == i, times = trazas_por_vista)

botones <- lapply(seq_along(vistas), function(i) {
  list(
    method = "update",
    label  = vistas[i],
    args   = list(
      list(visible = visibilidad(i)),
      list("title.text"          = titulo(i),
           "xaxis.range"         = list(0, rangos[i]),
           "yaxis.categoryarray" = rev(ordenes[[i]]))
    )
  )
})

fig <- fig %>%
  layout(
    height = 520,
    title  = titulo_plotly(titulo(1)),
    xaxis  = list(title = "Cantidad de casos", range = c(0, rangos[1]),
                  gridcolor = "#EEEBF0", zeroline = FALSE),
    yaxis  = list(title = "", categoryorder = "array",
                  categoryarray = rev(ordenes[[1]]), tickfont = list(size = 13)),
    barmode = "group",
    bargap  = 0.3,
    hovermode     = "closest",
    hoverdistance = 40,
    hoverlabel = hover_plotly,
    updatemenus = list(menu_plotly(botones)),
    legend = list(orientation = "h", x = 1, xanchor = "right", y = 1.04, yanchor = "bottom"),
    margin = list(t = 140, b = 70, l = 10, r = 30),
    showlegend = TRUE
  ) %>%
  estilo_plotly()

# 7. Caja de comentarios de lado a lado (debajo del gráfico) ----
# Se cambia sola según la segmentación elegida en el menú (js_comentarios está en el chunk "estilo")
fig <- htmlwidgets::onRender(
  fig, js_comentarios,
  data = list(comentarios = comentarios,
              inicios = c(0, cumsum(trazas_por_vista)[-length(trazas_por_vista)]))   # traza inicial de cada vista
)

fig

4.4 ¿Cómo se relaciona la composición por edad con las tasas de delito y esa relación cambia según el grupo poblacional?

# 1. Variables: grupos de edad y delitos (hurto total y pct_30_59 se crean en el chunk "preparar_datos") ----

edades <- c(pct_0_14   = "0–14 años",
            pct_15_29  = "15–29 años",
            pct_30_59  = "30–59 años",
            pct_60_mas = "60 años o más")

delitos_corr <- c(tasa_hurto_total            = "Hurto",
                  tasa_lesiones               = "Lesiones",
                  tasa_homicidios             = "Homicidios",
                  tasa_violencia_intrafamiliar = "Violencia intrafamiliar")

# 2. Correlación de Spearman por segmentación ----
cor_spearman <- function(x, y) {
  r <- tryCatch(suppressWarnings(cor.test(x, y, method = "spearman", exact = FALSE)),
                error = function(e) NULL)
  if (is.null(r)) c(rho = NA_real_, p = NA_real_)
  else c(rho = unname(r$estimate), p = r$p.value)
}

fuerza <- function(r) {
  a <- abs(r)
  ifelse(is.na(r), "sin dato",
  ifelse(a < 0.2, "muy débil",
  ifelse(a < 0.4, "débil",
  ifelse(a < 0.6, "moderada",
  ifelse(a < 0.8, "fuerte", "muy fuerte")))))
}

correlaciones <- expand_grid(grupo = grupos, vd = names(delitos_corr), ve = names(edades)) %>%
  mutate(
    r      = pmap(list(grupo, vd, ve), function(g, vd, ve) {
               s <- upz[upz$grupo_poblacion == g, ]
               cor_spearman(s[[ve]], s[[vd]])
             }),
    rho    = map_dbl(r, ~ .x[["rho"]]),
    p      = map_dbl(r, ~ .x[["p"]]),
    n      = map_int(grupo, ~ sum(upz$grupo_poblacion == .x)),
    delito = unname(delitos_corr[vd]),
    edad   = unname(edades[ve])
  ) %>%
  select(grupo, delito, edad, rho, p, n) %>%
  mutate(
    signif = !is.na(p) & p < 0.05,
    etiqueta_celda = case_when(
      is.na(rho) ~ "",
      signif     ~ paste0("<b>", num(rho, accuracy = 0.01), "*</b>"),
      TRUE       ~ num(rho, accuracy = 0.01)
    ),
    texto = paste0(
      "<b>", delito, " · ", edad, "</b>",
      "<br>Correlación (Spearman): ", num(rho, accuracy = 0.01),
      "<br>Relación ", fuerza(rho), ifelse(rho >= 0, " positiva", " negativa"),
      "<br>p-valor: ", ifelse(p < 0.001, "< 0,001", num(p, accuracy = 0.001)),
      "<br>", ifelse(signif, "Estadísticamente significativa", "No significativa"),
      "<br>UPZ analizadas: ", n
    )
  )

# 3. COMENTARIOS ----
# >>> AQUÍ ESCRIBES TU COMENTARIO PARA CADA POBLACIÓN (puede ser tan largo como quieras) <<<
# Se muestra completo, de lado a lado, solo cuando eliges esa población en el menú.
# Déjalo "" si no quieres agregar nada.
mis_comentarios <- c(
  "Población baja"  = "El hurto es menor donde hay más niños y niñas, mayor donde predomina la población de 30–59 años. La violencia intrafamiliar y lesiones suben con más población de 15–29 años y baja con más población de 30–59., estas asociaciones son generales de las UPZ en este grupo poblacional, pero no asegura que la edad genere el aumento en las denuncias.",
  "Población media" = "es donde se generan las relaciones mas fuertes, donde si aumentan las personas de 60 años, las denuncias en hurto aumentan, por el contrario los homicidios y la violencia intrafamiliar baja en los grupos de igual o mas de 30 años, en contraparte, aumenta en los jovenes estos dos delitos. importante aclarar que Relación no es causalidad",
  "Población alta"  = "Solo tiene 5 UPZ, y esto puede generar cambios drasticos es las correlaciones con solo aumentar en una UPZ, por tal motivo es importante indicar en los jovenes el hurto es bajo y la violencia intrafamiliar cumple el mismo patron en el grupo de 30 - 59 años, aunque se debe validar mas al detalle estos datos, debido que no se cuenta con datos estables que puedan generalizar."
)

mostrar_automatico <- FALSE #ALSE = muestra solo tus comentarios

comentario_corr <- function(g) {
  d <- correlaciones %>% filter(grupo == g, !is.na(rho))
  n <- d$n[1]
  pos <- d %>% slice_max(rho, n = 1, with_ties = FALSE)
  neg <- d %>% slice_min(rho, n = 1, with_ties = FALSE)

  c(
    if (mostrar_automatico) c(
      paste0("<b>Cómo leerlo:</b> ", n, " UPZ en este grupo. Cada celda es la correlación de Spearman ",
             "entre el % de población de ese grupo de edad y la tasa del delito (por 10.000 habitantes). ",
             "Va de -1 a 1; las celdas en negrita con * son estadísticamente significativas (p < 0,05)."),
      if (pos$rho > 0) paste0("<b>Relación positiva más fuerte:</b> ", tolower(pos$delito), " con ", pos$edad,
             " (", num(pos$rho, accuracy = 0.01), "): las UPZ con más población de esa edad tienden a tener tasas más altas."),
      if (neg$rho < 0) paste0("<b>Relación negativa más fuerte:</b> ", tolower(neg$delito), " con ", neg$edad,
             " (", num(neg$rho, accuracy = 0.01), "): las UPZ con más población de esa edad tienden a tener tasas más bajas."),
      paste0("<b>Relaciones significativas:</b> ", sum(d$signif), " de ", nrow(d), "."),
      if (n < 10) paste0("<b>Ojo:</b> con solo ", n, " UPZ las correlaciones son poco confiables ",
             "(basta con que una UPZ cambie para que el resultado cambie). Tómalas solo como referencia."),
      paste0("<b>Recuerda:</b> correlación no es causalidad; muestra que dos cosas suben o bajan juntas, ",
             "no que un grupo de edad cause el delito. Además los cuatro grupos de edad suman 100 %, ",
             "por eso 30–59 años suele moverse al revés que los demás.")
    ),
    if (nzchar(mis_comentarios[[g]])) paste0("<b>Comentarios:</b> ", mis_comentarios[[g]])
  )
}

comentarios <- lapply(grupos, comentario_corr)

# 4. Gráfico: mapa de calor con menú ----
a_matriz <- function(d, col) {
  d %>%
    select(delito, edad, all_of(col)) %>%
    pivot_wider(names_from = edad, values_from = all_of(col)) %>%
    arrange(match(delito, delitos_corr)) %>%
    select(all_of(unname(edades))) %>%
    as.matrix()
}

fig <- plot_ly()

for (i in seq_along(grupos)) {
  d <- correlaciones %>% filter(grupo == grupos[i])
  fig <- fig %>% add_trace(
    type = "heatmap",
    x = unname(edades), y = unname(delitos_corr),
    z = a_matriz(d, "rho"), text = a_matriz(d, "texto"),
    hoverinfo = "text", xgap = 3, ygap = 3,
    zmin = -1, zmax = 1,
    colorscale = list(c(0, turquesa), c(0.5, "#FFFFFF"), c(1, magenta)),
    colorbar = list(title = "Correlación<br>(Spearman)", len = 0.8, thickness = 14,
                    tickvals = c(-1, -0.5, 0, 0.5, 1)),
    showscale = TRUE, visible = (i == 1)
  )
}

# número escrito dentro de cada celda (en negrita si es significativa)
anotaciones_celdas <- function(g) {
  d <- correlaciones %>% filter(grupo == g)
  lapply(seq_len(nrow(d)), function(k) list(
    x = d$edad[k], y = d$delito[k], xref = "x", yref = "y",
    text = d$etiqueta_celda[k], showarrow = FALSE,
    font = list(size = 15,
                color = ifelse(!is.na(d$rho[k]) && abs(d$rho[k]) > 0.55, "white", "#333333"))
  ))
}

titulo <- function(i) {
  paste0("<b>", partir_titulo("En poblaciones jóvenes la violencia intrafamiliar tiende a subir y el hurto a bajar; esto aplica a población baja y media"), "</b>",
         "<br><sup>", grupos[i], " · Correlación de Spearman entre el % de población por edad y la tasa de delitos de cada UPZ",
         " · * = significativa (p < 0,05)</sup>")
}

botones <- lapply(seq_along(grupos), function(i) {
  list(
    method = "update",
    label  = grupos[i],
    args   = list(
      list(visible = seq_along(grupos) == i),
      list("title.text" = titulo(i),
           annotations  = anotaciones_celdas(grupos[i]))
    )
  )
})

fig <- fig %>%
  layout(
    height = 520,
    title  = titulo_plotly(titulo(1)),
    xaxis = list(title = "Grupo de edad (% de habitantes de la UPZ)", side = "bottom"),
    yaxis = list(title = "", autorange = "reversed", tickfont = list(size = 13), automargin = TRUE),
    annotations = anotaciones_celdas(grupos[1]),
    hoverlabel = hover_plotly,
    updatemenus = list(menu_plotly(botones)),
    margin = list(t = 140, b = 80, l = 10, r = 30)
  ) %>%
  estilo_plotly()

# 5. Caja de comentarios de lado a lado (debajo del gráfico) ----
# (js_comentarios está en el chunk "estilo")
fig <- htmlwidgets::onRender(
  fig, js_comentarios,
  data = list(comentarios = comentarios, inicios = seq_along(grupos) - 1)   # una traza por grupo
)

fig

Conclusión de correlación

En esta seccion se mantienen los casos atipicos extremos en el analisis, aunque son asociaciones entre UPZ, no explican por qué ocurren y no significan que la edad cause el delito, para mayor analisis seria importante tener la data por denuncias realizadas por edad, junto con el delito a denunciar, de esta manera se podria comprobar positiva o negativamente la correlación.

4.5 ¿Dónde se ubican las UPZ con mayores tasas y cómo se ven las segmentaciones en el territorio?

# mapa bogota upz
ruta_shp <- file.path(ruta_input, "shp", "upz_bogota.shp")
upz_shp <- st_read(ruta_shp, quiet = TRUE)
loc_shp <- st_read(ruta_shp, quiet = TRUE)  # recortadas al área urbana

# 1. Geometría + datos de cada UPZ ----
mapa <- upz_shp %>%
  select(upz_cod) %>%
  left_join(
    upz %>% select(upz_cod, upz_nombre, localidad, grupo_poblacion, poblacion,
                   casos_total, tasa_total,
                   casos_hurto_total, tasa_hurto_total,
                   casos_lesiones, tasa_lesiones,
                   casos_homicidios, tasa_homicidios,
                   casos_violencia_intrafamiliar, tasa_violencia_intrafamiliar),
    by = "upz_cod"
  ) %>%
  mutate(id_upz = as.character(upz_cod))

stopifnot("Hay UPZ sin segmentación: corre primero el chunk de K-means" = !anyNA(mapa$grupo_poblacion))

# se aligera la geometría para que el HTML pese poco
mapa <- mapa %>%
  st_make_valid() %>%
  st_transform(3116) %>%
  st_simplify(dTolerance = 30, preserveTopology = TRUE) %>%
  st_transform(4326)

# límites exactos de Bogotá para que el mapa llene el espacio
bb  <- st_bbox(mapa)
pad <- 0.005
rango_lon <- c(unname(bb["xmin"]) - pad, unname(bb["xmax"]) + pad)
rango_lat <- c(unname(bb["ymin"]) - pad, unname(bb["ymax"]) + pad)

a_geojson <- function(x) {
  tmp <- tempfile(fileext = ".geojson")
  st_write(x[, "id_upz"], tmp, driver = "GeoJSON", quiet = TRUE)
  out <- read_json(tmp)
  unlink(tmp)
  out
}

# 2. Segmentación y delitos ----
n_grupo <- sapply(grupos, function(g) sum(mapa$grupo_poblacion == g))

delitos_mapa <- list(
  "Total de delitos"        = c("casos_total", "tasa_total"),
  "Hurto"                   = c("casos_hurto_total", "tasa_hurto_total"),
  "Lesiones"                = c("casos_lesiones", "tasa_lesiones"),
  "Homicidios"              = c("casos_homicidios", "tasa_homicidios"),
  "Violencia intrafamiliar" = c("casos_violencia_intrafamiliar", "tasa_violencia_intrafamiliar")
)

crear_texto <- function(d, delito) {
  v <- delitos_mapa[[delito]]
  paste0(
    "<b>", d$upz_nombre, "</b>",
    "<br>Localidad: ", d$localidad,
    "<br>Segmentación: ", d$grupo_poblacion,
    "<br>Población: ", num(d$poblacion),
    "<br>Total de delitos: ", num(d$casos_total), " casos · tasa ", num(d$tasa_total, accuracy = 0.1),
    if (delito != "Total de delitos")
      paste0("<br><b>", delito, ":</b> ", num(d[[v[1]]]), " casos · tasa ", num(d[[v[2]]], accuracy = 0.1))
    else ""
  )
}

datos_g <- lapply(grupos, function(g) mapa %>% filter(grupo_poblacion == g))
textos  <- lapply(names(delitos_mapa), function(dl) {
  lapply(datos_g, function(dg) crear_texto(st_drop_geometry(dg), dl))
})
names(textos) <- names(delitos_mapa)

# 3. Mapa: base gris (todas las UPZ) + una capa por segmentación ----
alto_mapa <- 950

fig <- plot_ly(height = alto_mapa) %>%
  add_trace(
    type = "choropleth", name = "base",                       # <<< NUEVO: nombre de la traza
    geojson = a_geojson(mapa), featureidkey = "properties.id_upz",
    locations = mapa$id_upz, z = rep(1, nrow(mapa)), zmin = 0, zmax = 1,
    colorscale = list(c(0, gris), c(1, gris)), showscale = FALSE,
    marker = list(line = list(color = "white", width = 0.5)),
    hoverinfo = "skip"
  )

for (i in seq_along(grupos)) {
  dg <- datos_g[[i]]
  fig <- fig %>% add_trace(
    type = "choropleth", name = grupos[i],                    # <<< NUEVO: la tabla lee este nombre
    geojson = a_geojson(dg), featureidkey = "properties.id_upz",
    locations = dg$id_upz, z = rep(1, nrow(dg)), zmin = 0, zmax = 1,
    colorscale = list(c(0, colores[[grupos[i]]]), c(1, colores[[grupos[i]]])), showscale = FALSE,
    marker = list(line = list(color = "white", width = 0.8)),
    text = textos[["Total de delitos"]][[i]], hoverinfo = "text"
  )
}

# 4. Selectores ----
botones_seg <- lapply(c("Todas", grupos), function(s) {
  list(method = "restyle",
       label  = if (s == "Todas") paste0("Todas (", sum(n_grupo), " UPZ)") else paste0(s, " (", n_grupo[[s]], " UPZ)"),
       args   = list(list(visible = (grupos == s) | (s == "Todas")), c(1, 2, 3)))
})

botones_delito <- lapply(names(delitos_mapa), function(dl) {
  list(method = "restyle", label = dl,
       args   = list(list(text = lapply(textos[[dl]], as.list)), c(1, 2, 3)))
})

leyenda <- paste0(
  paste0("<span style='color:", colores, "'>■</span> ", grupos, " (", n_grupo, ")", collapse = "      "),
  "      <span style='color:#BDBAC4'>■</span> UPZ no seleccionadas"
)

# 5. Diseño del mapa ----
fig <- fig %>%
  layout(
    height = alto_mapa,
    title = titulo_plotly(
      paste0("<b>Mapa de las UPZ de Bogotá según segmentación poblacional</b>",
             "<br><sup>Elige la segmentación y el delito · pasa el cursor sobre cada UPZ · ",
             "tasas por cada 10.000 habitantes</sup>"),
      xref = "container"
    ),
    geo = list(
      projection = list(type = "mercator"),
      lonaxis = list(range = rango_lon),
      lataxis = list(range = rango_lat),
      visible = FALSE, bgcolor = "rgba(0,0,0,0)",
      domain = list(x = c(0, 1), y = c(0, 1))
    ),
    hoverlabel = hover_plotly,
    updatemenus = list(
      menu_plotly(botones_seg,    x = 0,    y = 1.02),
      menu_plotly(botones_delito, x = 0.42, y = 1.02)
    ),
    annotations = list(
      list(text = "Segmentación poblacional", x = 0, y = 1.10, xref = "paper", yref = "paper",
           xanchor = "left", yanchor = "bottom", showarrow = FALSE, font = list(size = 12, color = "grey40")),
      list(text = "Delito (en el cuadro del mouse)", x = 0.42, y = 1.10, xref = "paper", yref = "paper",
           xanchor = "left", yanchor = "bottom", showarrow = FALSE, font = list(size = 12, color = "grey40")),
      list(text = leyenda, x = 0, y = -0.02, xref = "paper", yref = "paper",
           xanchor = "left", yanchor = "top", showarrow = FALSE, font = list(size = 13, color = "grey25"))
    ),
    margin = list(t = 140, b = 50, l = 0, r = 0)
  ) %>%
  estilo_plotly()
# 6. datos del ranking ----
top_n <- 15   # cuántas UPZ muestra la tabla

# es_extremo ya viene calculado en el boxplot (Q3 + 3 × IQR, tasas reales, por segmentación)
ranking_datos <- upz %>%
  transmute(
    upz_nombre,
    grupo_poblacion = as.character(grupo_poblacion),
    area_km2, poblacion,
    tasa_hurto_total, tasa_lesiones, tasa_homicidios, tasa_violencia_intrafamiliar, tasa_total,
    es_extremo
  )

cfg <- list(
  morado = morado, magenta = magenta, gris = gris,
  top_n = top_n, grupos = grupos, colores = as.list(colores),
  n_ext = sum(ranking_datos$es_extremo),
  n_cero_hom = sum(ranking_datos$tasa_homicidios == 0)
)

# 7. tabla ranking conectada (en los dos sentidos) con el selector de segmentación del mapa ----
js_ranking <- r"---(
function(el, x, datos) {
  var reg = JSON.parse(datos.registros);
  var cfg = JSON.parse(datos.cfg);

  function f(v, dec) {
    return new Intl.NumberFormat('es-CO', {minimumFractionDigits: dec, maximumFractionDigits: dec}).format(v);
  }
  function esc(s) {
    return String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
  }

  var cols = [
    {k: 'upz_nombre',                   t: 'UPZ',                         num: false},
    {k: 'grupo_poblacion',              t: 'Segmentación',                num: false},
    {k: 'area_km2',                     t: 'Área (km²)',                  num: true, d: 2},
    {k: 'poblacion',                    t: 'Población',                   num: true, d: 0},
    {k: 'tasa_hurto_total',             t: 'Tasa hurto',                  num: true, d: 1},
    {k: 'tasa_lesiones',                t: 'Tasa lesiones',               num: true, d: 1},
    {k: 'tasa_homicidios',              t: 'Tasa homicidios',             num: true, d: 2},
    {k: 'tasa_violencia_intrafamiliar', t: 'Tasa violencia intrafamiliar', num: true, d: 1},
    {k: 'tasa_total',                   t: 'Tasa total',                  num: true, d: 1}
  ];
  var ordenables = cols.filter(function(c) { return c.num; });
  var estado = {k: 'tasa_total', dir: 'desc', firma: null};

  // estilo (mismo look que tabla_bonita: encabezado morado, fila resaltada rosada, hover)
  var css = document.createElement('style');
  css.textContent =
    '.rk-cont{margin-top:14px;width:100%;box-sizing:border-box;padding:0 10px;font-family:inherit;color:#333}' +
    '.rk-titulo{font-size:18px;color:' + cfg.morado + ';line-height:1.4;margin-bottom:8px}' +
    '.rk-titulo span{font-size:13px;color:#555}' +
    '.rk-ctrl{display:flex;gap:16px;flex-wrap:wrap;align-items:center;margin-bottom:10px;font-size:13px;color:#555}' +
    '.rk-ctrl select{padding:5px 8px;border:1px solid ' + cfg.gris + ';border-radius:4px;color:' + cfg.morado + ';background:#fff;font-size:13px}' +
    '.rk-wrap{overflow-x:auto}' +
    '.rk-tabla{width:100%;border-collapse:collapse;font-size:14px}' +
    '.rk-tabla th{background:' + cfg.morado + ';color:#fff;font-weight:bold;padding:7px 9px;text-align:left;white-space:nowrap}' +
    '.rk-tabla th.rk-num{text-align:right}' +
    '.rk-tabla th.rk-ord{cursor:pointer}' +
    '.rk-tabla th.rk-ord:hover{filter:brightness(1.25)}' +
    '.rk-tabla td{padding:6px 9px;border-top:1px solid #ddd}' +
    '.rk-tabla td.rk-num{text-align:right;white-space:nowrap}' +
    '.rk-tabla td.rk-pos{text-align:center;font-weight:bold;color:' + cfg.morado + ';width:42px}' +
    '.rk-tabla tbody tr:hover{background:#f5f5f5}' +
    '.rk-tabla tbody tr.rk-primero{background:#F6EEF2;font-weight:bold;color:' + cfg.morado + '}' +
    '.rk-tabla tbody tr.rk-ext td{border-top:2px solid ' + cfg.magenta + ';border-bottom:2px solid ' + cfg.magenta + '}' +
    '.rk-tabla tbody tr.rk-ext td:first-child{border-left:2px solid ' + cfg.magenta + '}' +
    '.rk-tabla tbody tr.rk-ext td:last-child{border-right:2px solid ' + cfg.magenta + '}' +
    '.rk-nota{margin-top:12px;font-size:13px;line-height:1.5;color:#444;border-top:2px solid ' + cfg.gris + ';padding-top:10px}' +
    '.rk-nota ul{margin:6px 0 0 0;padding-left:20px}' +
    '.rk-nota li{margin-bottom:4px}' +
    '.rk-sw{display:inline-block;width:22px;height:11px;vertical-align:middle;margin-right:4px;box-sizing:border-box}' +
    '.rk-sw-ext{border:2px solid ' + cfg.magenta + '}' +
    '.rk-sw-prim{background:#F6EEF2;border:1px solid ' + cfg.gris + '}';
  document.head.appendChild(css);

  // estructura
  var cont = document.createElement('div');
  cont.className = 'rk-cont';
  el.parentNode.insertBefore(cont, el.nextSibling);

  var titulo = document.createElement('div');
  titulo.className = 'rk-titulo';
  cont.appendChild(titulo);

  var ctrl = document.createElement('div');
  ctrl.className = 'rk-ctrl';

  // <<< NUEVO: cuántas UPZ tiene cada segmentación (para el selector)
  var conteo = {};
  reg.forEach(function(r) { conteo[r.grupo_poblacion] = (conteo[r.grupo_poblacion] || 0) + 1; });

  ctrl.innerHTML =
    // <<< NUEVO: selector de segmentación dentro de la tabla
    '<label>Segmentación&nbsp;<select class="rk-sel-seg">' +
    '<option value="Todas">Todas (' + reg.length + ' UPZ)</option>' +
    cfg.grupos.map(function(g) { return '<option value="' + g + '">' + g + ' (' + (conteo[g] || 0) + ' UPZ)</option>'; }).join('') +
    '</select></label>' +
    '<label>Ordenar por&nbsp;<select class="rk-sel-k">' +
    ordenables.map(function(c) { return '<option value="' + c.k + '">' + c.t + '</option>'; }).join('') +
    '</select></label>' +
    '<label>Orden&nbsp;<select class="rk-sel-dir">' +
    '<option value="desc">De mayor a menor</option><option value="asc">De menor a mayor</option>' +
    '</select></label>' +
    '<span>También puedes hacer clic en el encabezado de una columna.</span>';
  cont.appendChild(ctrl);
  var selSeg = ctrl.querySelector('.rk-sel-seg');   // <<< NUEVO
  var selK = ctrl.querySelector('.rk-sel-k');
  var selDir = ctrl.querySelector('.rk-sel-dir');
  selK.value = estado.k;
  selDir.value = estado.dir;

  var wrap = document.createElement('div');
  wrap.className = 'rk-wrap';
  cont.appendChild(wrap);

  var nota = document.createElement('div');
  nota.className = 'rk-nota';
  nota.innerHTML =
    '<b>Cómo leer esta tabla</b><ul>' +
    '<li><span class="rk-sw rk-sw-ext"></span><b>Borde magenta:</b> atípico extremo, es decir, una UPZ cuya tasa total supera Q3 + 3 × IQR dentro de la segmentación poblacional generada (hoy son ' + cfg.n_ext + '). Se resalta siempre que aparezca en el ranking.</li>' +
    '<li>Las tasas de delitos son por cada 10.000 habitantes.</li></ul>';
  cont.appendChild(nota);

  // segmentaciones visibles en el mapa
  function seleccion() {
    var s = [];
    (el.data || []).forEach(function(t) {
      if (cfg.grupos.indexOf(t.name) >= 0 && t.visible !== false && t.visible !== 'legendonly') s.push(t.name);
    });
    return s;
  }

  // <<< NUEVO: tabla -> mapa: muestra solo la segmentación elegida y actualiza el menú del mapa
  function aplicarAlMapa(valor) {
    if (typeof Plotly === 'undefined') return;
    var vis = cfg.grupos.map(function(g) { return valor === 'Todas' || g === valor; });
    var idx = cfg.grupos.map(function(g) {
      for (var i = 0; i < el.data.length; i++) { if (el.data[i].name === g) return i; }
      return -1;
    });
    if (idx.indexOf(-1) >= 0) return;
    var pos = (valor === 'Todas') ? 0 : cfg.grupos.indexOf(valor) + 1;
    Plotly.restyle(el, {visible: vis}, idx).then(function() {
      return Plotly.relayout(el, {'updatemenus[0].active': pos});
    });
  }

  function render(forzar) {
    var sel = seleccion();
    selSeg.value = (sel.length === 1) ? sel[0] : 'Todas';   // <<< NUEVO: mapa -> tabla (sincroniza el selector)
    var firma = sel.join('|') + '#' + estado.k + '#' + estado.dir;
    if (!forzar && firma === estado.firma) return;
    estado.firma = firma;

    var d = reg.filter(function(r) { return sel.indexOf(r.grupo_poblacion) >= 0; });
    var sgn = estado.dir === 'desc' ? -1 : 1;
    d.sort(function(a, b) {
      var dif = (a[estado.k] - b[estado.k]) * sgn;
      return dif !== 0 ? dif : a.upz_nombre.localeCompare(b.upz_nombre, 'es');
    });
    var top = d.slice(0, cfg.top_n);

    var col = ordenables.filter(function(c) { return c.k === estado.k; })[0];
    var txtSel = (sel.length === cfg.grupos.length) ? 'todas las segmentaciones'
               : (sel.length ? sel.join(', ').toLowerCase() : 'ninguna segmentación');
    titulo.innerHTML = '<b>Ranking de UPZ</b> · Top ' + top.length + ' de ' + d.length + ' UPZ · ' + txtSel +
      '<br><span>Ordenado por <b>' + col.t.toLowerCase() + '</b> ' +
      (estado.dir === 'desc' ? '(de mayor a menor)' : '(de menor a mayor)') + '</span>';

    var h = '<table class="rk-tabla"><thead><tr><th class="rk-num" style="text-align:center">#</th>';
    cols.forEach(function(c) {
      var flecha = (c.k === estado.k) ? (estado.dir === 'desc' ? ' ▼' : ' ▲') : '';
      h += '<th class="' + (c.num ? 'rk-num rk-ord' : '') + '"' + (c.num ? ' data-k="' + c.k + '"' : '') + '>' + c.t + flecha + '</th>';
    });
    h += '</tr></thead><tbody>';
    top.forEach(function(r, i) {
      var clases = (i === 0 ? 'rk-primero ' : '') + (r.es_extremo ? 'rk-ext' : '');
      h += '<tr class="' + clases + '"><td class="rk-pos">' + (i + 1) + '</td>';
      cols.forEach(function(c) {
        if (c.k === 'grupo_poblacion') {
          h += '<td><span style="color:' + (cfg.colores[r.grupo_poblacion] || '#999') + '">■</span> ' + esc(r.grupo_poblacion) + '</td>';
        } else if (c.num) {
          h += '<td class="rk-num">' + f(r[c.k], c.d) + '</td>';
        } else {
          h += '<td>' + esc(r[c.k]) + '</td>';
        }
      });
      h += '</tr>';
    });
    h += '</tbody></table>';
    wrap.innerHTML = h;
  }

  // <<< NUEVO: al cambiar la segmentación en la tabla, se mueve el mapa
  selSeg.addEventListener('change', function() { aplicarAlMapa(selSeg.value); render(true); });
  selK.addEventListener('change', function() { estado.k = selK.value; render(true); });
  selDir.addEventListener('change', function() { estado.dir = selDir.value; render(true); });
  wrap.addEventListener('click', function(e) {
    var th = e.target.closest ? e.target.closest('th.rk-ord') : null;
    if (!th) return;
    var k = th.getAttribute('data-k');
    if (estado.k === k) { estado.dir = (estado.dir === 'desc' ? 'asc' : 'desc'); }
    else { estado.k = k; estado.dir = 'desc'; }
    selK.value = estado.k;
    selDir.value = estado.dir;
    render(true);
  });

  render(true);
  el.on('plotly_afterplot', function() { render(false); });
}
)---"

fig <- htmlwidgets::onRender(
  fig, js_ranking,
  data = list(
    registros = as.character(toJSON(ranking_datos, dataframe = "rows", auto_unbox = TRUE, digits = NA)),
    cfg       = as.character(toJSON(cfg, auto_unbox = TRUE))
  )
)

fig
# Cifras de la sección de Hallazgos: se calculan aquí y el texto las llama con `r ...`,
# así el informe se actualiza solo si cambian los datos, la semilla o los umbrales.
pct <- function(x) paste0(num(100 * x, accuracy = 0.1), " %")

# Correlación (Spearman) de la matriz del apartado 4.4, con el signo menos tipográfico
rho_txt <- function(g, d_, e_) {
  r <- correlaciones$rho[correlaciones$grupo == g & correlaciones$delito == d_ & correlaciones$edad == e_]
  sub("-", "−", num(r, accuracy = 0.01))
}

# Por grupo: cantidad de UPZ y peso de cada delito (sobre hurto + lesiones + homicidios + violencia intrafamiliar)
por_grupo <- upz %>%
  group_by(grupo_poblacion) %>%
  summarise(
    n         = n(),
    delitos4  = sum(casos_hurto_total + casos_lesiones + casos_homicidios + casos_violencia_intrafamiliar),
    pct_hurto = sum(casos_hurto_total) / delitos4,
    pct_vif   = sum(casos_violencia_intrafamiliar) / delitos4,
    .groups = "drop"
  ) %>%
  split(.$grupo_poblacion)

g_baja  <- por_grupo[["Población baja"]]
g_media <- por_grupo[["Población media"]]
g_alta  <- por_grupo[["Población alta"]]

# Ranking: composición del top de mayores y de menores tasas totales
top_may <- upz %>% slice_max(tasa_total, n = top_n, with_ties = FALSE)
top_men <- upz %>% slice_min(tasa_total, n = top_n, with_ties = FALSE)
n_may_baja  <- sum(top_may$grupo_poblacion == "Población baja")
n_men_media <- sum(top_men$grupo_poblacion == "Población media")

# Alerta: si cambian los datos y alguna afirmación cualitativa del texto deja de ser cierta, el tejido avisa
stopifnot(
  "Los atípicos extremos ya no están solo en población baja: revisa el texto de Hallazgos" =
    all(upz$grupo_poblacion[upz$es_extremo] == "Población baja"),
  "El top de mayores tasas ya no es todo de población baja: revisa el texto de Hallazgos" =
    n_may_baja == top_n,
  "En el top de menores tasas ya no predomina población media: revisa el texto de Hallazgos" =
    n_men_media == max(table(top_men$grupo_poblacion))
)

5 Hallazgos

  • Se evidencia que la segmentación mediante K-means permite dividir la población de Bogotá de una manera más acorde con la cantidad de habitantes. Sin embargo, el grupo de población alta queda conformado por solo 5 UPZ. Esto permite plantear que, en futuros análisis, se estudie esta segmentación de manera individual por UPZ.

  • Los atípicos extremos se presentan únicamente en el grupo de población baja. Según el análisis, estos generan un aumento considerable en la tasa total de delitos. Al analizar con mayor detalle los resultados, se observa que su efecto se concentra principalmente en el delito de hurto. Sin embargo, es importante tener presente esta información para estudiar estas 7 UPZ y conocer los motivos detrás de la cantidad de denuncias registradas.

  • El hurto predomina en los tres grupos poblacionales, pero pesa menos a medida que crece la población: 88,3 % en población baja, 80,4 % en media y 70,7 % en alta. Lo contrario pasa con la violencia intrafamiliar, que sube de 6,6 % a 12,0 % y 19,9 %.

  • En el ranking top 15 de mayores tasas de denuncias totales, solo se evidencian UPZ categorizadas como “poblacion baja”, pero cuando se valida al top 15 de menores tasas de denuncias totales, predomina poblacion media con 9 UPZ de 15, siendo estas el 25,7 % de toda las categorizadas por poblacion media (35 UPZ).

  • En población baja y media, más población joven se asocia con más violencia intrafamiliar y menos hurto, por ejemplo, en poblacion media, en el grupo 0–14 años con violencia intrafamiliar la correlacion es ρ = 0,78 y con hurto ρ = −0,77. Son asociaciones entre UPZ, no causas.

6 Limitaciones

  • Se identifica una oportunidad de mejora en los datos disponibles: contar con un mayor desglose de las denuncias por edad y tipo de delito permitiría, como se mencionó anteriormente, realizar un análisis de correlación más detallado entre estas variables.

  • El periodo de análisis de las denuncias y las tasas comprende únicamente 7 meses, dejando por fuera los últimos meses del año. Estos meses podrían ser relevantes, considerando que corresponden a una época en la que se generan más interacciones entre los habitantes de Bogotá. Esto plantea una pregunta: ¿se mantendrían las tendencias observadas durante esos 5 meses o se presentarían aumentos o disminuciones considerables en las tasas de denuncias por delitos?

7 Conclusion

La seguridad en las UPZ, según lo validado en el análisis, presenta varias observaciones a tener en cuenta. Una es que, entre más población, más casos de denuncias; sin embargo, esto no aplica del todo cuando se evidencian atípicos extremos, en los cuales la población era mucho menor. Además, teniendo en cuenta la población, tampoco es viable decir que el área se relaciona directamente con la cantidad de personas que habitan la UPZ, debido a que hay casos en los que una UPZ es pequeña en comparación con otras más grandes. Sí se puede indicar que las denuncias por hurto predominan a nivel general y por segmento poblacional.

Con respecto a las correlaciones, es necesario entender que en estos casos se buscó generar una asociación, que directamente no indica causalidad. Para poder afirmar estas relaciones, es necesario contar con el detalle de la edad y de las denuncias generadas por cada delito. Es decir, para un primer análisis el informe es muy útil, aunque abre la puerta a un mayor detalle y entendimiento, como por ejemplo en las UPZ atípicas extremas en tasa total de delitos.

LS0tDQp0aXRsZTogIkJvZ290w6EgYmFqbyBsYSBsdXBhOiBwb2JsYWNpw7NuLCB0ZXJyaXRvcmlvIHkgZGVudW5jaWFzIGVuIGxhcyBVUFoiDQpzdWJ0aXRsZTogIsK/Q8OzbW8gc2UgcmVsYWNpb25hbiBsYSBjb21wb3NpY2nDs24gcG9yIHJhbmdvcyBkZSBlZGFkIGRlIGxhIHBvYmxhY2nDs24geSBlbCDDoXJlYSB0ZXJyaXRvcmlhbCBkZSBsYXMgVVBaIGNvbiBsYSBjYW50aWRhZCB5IGxhIHRhc2EgZGUgZGVudW5jaWFzIHBvciBkZWxpdG8gZW4gQm9nb3TDoT8iDQphdXRob3I6ICJKZWlzb24gRGlheiAtIFJvYmluc29uIEdpcmFsZG8iDQpkYXRlOiAiYHIgZm9ybWF0KFN5cy5EYXRlKCksICclZCBkZSAlQiBkZSAlWScpYCINCm91dHB1dDoNCiAgaHRtbF9kb2N1bWVudDoNCiAgICB0b2M6IHRydWUNCiAgICB0b2NfZmxvYXQ6IHRydWUNCiAgICB0aGVtZTogZmxhdGx5DQogICAgZGZfcHJpbnQ6IGthYmxlDQogICAgY29kZV9mb2xkaW5nOiBoaWRlDQogICAgY29kZV9kb3dubG9hZDogdHJ1ZSAgICMgYWdyZWdhIHVuIGJvdMOzbiBwYXJhIGRlc2NhcmdhciBlbCAuUm1kIG9yaWdpbmFsIC0tIHJlZnVlcnphIGxhIHJlcHJvZHVjaWJpbGlkYWQNCi0tLQ0KDQpgYGB7PWh0bWx9DQo8c3R5bGU+DQovKiBUaXBvZ3JhZsOtYSBlZGl0b3JpYWwgZGVsIGN1cnNvICAqLw0KQGltcG9ydCB1cmwoJ2h0dHBzOi8vZm9udHMuZ29vZ2xlYXBpcy5jb20vY3NzMj9mYW1pbHk9RmlyYStTYW5zOndnaHRANDAwOzYwMDs3MDAmZGlzcGxheT1zd2FwJyk7DQoNCmJvZHkgeyBmb250LWZhbWlseTogJ0ZpcmEgU2FucycsIC1hcHBsZS1zeXN0ZW0sIEJsaW5rTWFjU3lzdGVtRm9udCwgc2Fucy1zZXJpZjsgY29sb3I6ICMyQjI1MzA7IH0NCmgxLCBoMiwgaDMgeyBmb250LWZhbWlseTogJ0ZpcmEgU2FucycsIHNhbnMtc2VyaWY7IGNvbG9yOiAjMzUxQTRGOyB9DQpoMSB7IGJvcmRlci1ib3R0b206IDNweCBzb2xpZCAjRDkwMDZDOyBwYWRkaW5nLWJvdHRvbTogOHB4OyBtYXJnaW4tdG9wOiA0OHB4OyB9DQpoMiB7IG1hcmdpbi10b3A6IDM2cHg7IH0NCmEgeyBjb2xvcjogI0Q5MDA2QzsgfQ0KYTpob3ZlciB7IGNvbG9yOiAjMTY4Qzg2OyB9DQojVE9DIHsgYm9yZGVyLXJhZGl1czogMTBweDsgfQ0KLm5hdi1waWxscyA+IGxpLmFjdGl2ZSA+IGEsIC5uYXYtcGlsbHMgPiBsaS5hY3RpdmUgPiBhOmhvdmVyIHsgYmFja2dyb3VuZC1jb2xvcjogIzM1MUE0RiAhaW1wb3J0YW50OyB9DQpjYXB0aW9uIHsgY29sb3I6ICM1QTU1NjA7IGZvbnQtc3R5bGU6IGl0YWxpYzsgY2FwdGlvbi1zaWRlOiB0b3A7IH0NCmNvZGUgeyBjb2xvcjogIzhBMUE1QzsgYmFja2dyb3VuZDogI0Y2RUVGMjsgcGFkZGluZzogMXB4IDVweDsgYm9yZGVyLXJhZGl1czogNHB4OyB9DQp0YWJsZS5kYXRhVGFibGUgeyBmb250LXNpemU6IDAuOTJyZW07IH0NCi5sZWFmbGV0LWNvbnRhaW5lciB7IGJvcmRlci1yYWRpdXM6IDEwcHg7IGJhY2tncm91bmQ6ICNFQUYwRjQgIWltcG9ydGFudDsgfQ0KPC9zdHlsZT4NCmBgYA0KDQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0NCmtuaXRyOjpvcHRzX2NodW5rJHNldCgNCiAgZWNobyA9IFRSVUUsDQogIHdhcm5pbmcgPSBGQUxTRSwNCiAgbWVzc2FnZSA9IEZBTFNFLA0KICBmaWcuYWxpZ24gPSAiY2VudGVyIiwNCiAgZmlnLndpZHRoID0gOCwNCiAgZmlnLmhlaWdodCA9IDUNCikNCg0KIyBDYXJwZXRhIGRlIGRhdG9zIChydXRhIHJlbGF0aXZhOiBsYSBjYXJwZXRhICJpbnB1dCIgZGViZSBlc3RhciBqdW50byBhbCAuUm1kKQ0KcnV0YV9pbnB1dCA8LSAiQzovVXNlcnMvVXNlci9EZXNrdG9wL2lucHV0LyINCmBgYA0KDQo8ZGl2IHN0eWxlPSJiYWNrZ3JvdW5kOiBsaW5lYXItZ3JhZGllbnQoMTM1ZGVnLCAjMzUxQTRGIDAlLCAjRDkwMDZDIDEwMCUpOyBwYWRkaW5nOiAyOHB4IDM0cHg7IGJvcmRlci1yYWRpdXM6IDE2cHg7IG1hcmdpbi1ib3R0b206IDMwcHg7IGRpc3BsYXk6IGZsZXg7IGFsaWduLWl0ZW1zOiBjZW50ZXI7IGp1c3RpZnktY29udGVudDogc3BhY2UtYmV0d2VlbjsgZmxleC13cmFwOiB3cmFwOyBnYXA6IDIycHg7IGJveC1zaGFkb3c6IDAgNnB4IDE4cHggcmdiYSg1MywgMjYsIDc5LCAwLjI1KTsiPg0KPGRpdiBzdHlsZT0iZmxleDogMSAxIDM4MHB4OyBtaW4td2lkdGg6IDA7IGNvbG9yOiB3aGl0ZTsiPg0KPGRpdiBzdHlsZT0iZm9udC1zaXplOiAxLjdyZW07IGZvbnQtd2VpZ2h0OiA3MDA7IGxpbmUtaGVpZ2h0OiAxLjI1OyI+VGVycml0b3JpbyB5IHNlZ3VyaWRhZCBlbiBCb2dvdMOhOiB1biBhbsOhbGlzaXMgZGUgbGFzIGRlbnVuY2lhcyBwb3IgVVBaPC9kaXY+DQo8ZGl2IHN0eWxlPSJjb2xvcjogI0Y0RENFOTsgZm9udC1zaXplOiAxLjAycmVtOyBsaW5lLWhlaWdodDogMS40NTsgbWFyZ2luLXRvcDogOHB4OyI+VW5hIGV4cGxvcmFjacOzbiBkZSBsYXMgY2FyYWN0ZXLDrXN0aWNhcyBkZW1vZ3LDoWZpY2FzIHkgdGVycml0b3JpYWxlcyB5IHN1IHJlbGFjacOzbiBjb24gbGFzIGRlbnVuY2lhcyBwb3IgZGVsaXRvIGVuIEJvZ290w6E8L2Rpdj4NCjwvZGl2Pg0KPGRpdiBzdHlsZT0iYmFja2dyb3VuZDogd2hpdGU7IHBhZGRpbmc6IDEycHggMThweDsgYm9yZGVyLXJhZGl1czogMTJweDsgZmxleDogMCAwIGF1dG87IGJveC1zaGFkb3c6IDAgMnB4IDhweCByZ2JhKDAsIDAsIDAsIDAuMTgpOyI+DQo8ZGl2IHJvbGU9ImltZyIgYXJpYS1sYWJlbD0iTG9nbyBLb25yYWQgTG9yZW56IEZ1bmRhY2nDs24gVW5pdmVyc2l0YXJpYSIgc3R5bGU9IndpZHRoOiAxODBweDsgaGVpZ2h0OiA5MnB4OyBiYWNrZ3JvdW5kOiB1cmwoJ2ByIGtuaXRyOjppbWFnZV91cmkoZmlsZS5wYXRoKHJ1dGFfaW5wdXQsICdsb2dvIFVuaXZlcnNpZGFkLnBuZycpKWAnKSBjZW50ZXIgLyBjb3ZlciBuby1yZXBlYXQ7Ij48L2Rpdj4NCjwvZGl2Pg0KPC9kaXY+DQoNCmBgYHtyIGxpYnJlcmlhc30NCmxpYnJhcnkodGlkeXZlcnNlKSAgICAgICMgaW5jbHV5ZSBkcGx5ciwgdGlkeXIsIHJlYWRyLCBwdXJyciB5IGdncGxvdDINCmxpYnJhcnkoc2NhbGVzKQ0KbGlicmFyeShzaG93dGV4dCkNCmxpYnJhcnkoc3lzZm9udHMpDQpsaWJyYXJ5KHNmKQ0KbGlicmFyeShwbG90bHkpDQpsaWJyYXJ5KGthYmxlRXh0cmEpDQpsaWJyYXJ5KGpzb25saXRlKQ0KDQpmdWVudGVfZWRpdG9yaWFsIDwtICJGaXJhIFNhbnMiDQpmb250X2FkZF9nb29nbGUobmFtZSA9IGZ1ZW50ZV9lZGl0b3JpYWwsIGZhbWlseSA9IGZ1ZW50ZV9lZGl0b3JpYWwpDQpzaG93dGV4dF9hdXRvKCkNCmBgYA0KDQpgYGB7ciBkYXRvcywgcmVzdWx0cz0naGlkZSd9DQojIDEuIGNhcmdhciBkYXRvcw0KdXB6IDwtIHJlYWRfY3N2KGZpbGUucGF0aChydXRhX2lucHV0LCAidXB6X3ZhcmlhYmxlcy5jc3YiKSwgc2hvd19jb2xfdHlwZXMgPSBGQUxTRSkNCg0KIyAxLjEgLiBFeHBsb3JhciBEYXRhIHNldCBVUFoNCg0KIyBEaW1lbnNpb25lcyBkZSBsYSBiYXNlDQpkaW0odXB6KQ0KDQojIE5vbWJyZXMgZGUgbGFzIGNvbHVtbmFzDQpuYW1lcyh1cHopDQoNCiMgZ2xpbXBzZSgpOiB2ZXIgVE9EQVMgbGFzIGNvbHVtbmFzLCBzdSB0aXBvIHkgbG9zIHByaW1lcm9zIHZhbG9yZXMgZW4gdW5hIHBhbnRhbGxhLg0KZ2xpbXBzZSh1cHopDQoNCiMgUHJpbWVyYXMgb2JzZXJ2YWNpb25lcw0KaGVhZCh1cHopDQoNCiMgTGFzIGNvbHVtbmFzIGVzdMOhbiBhZ3J1cGFkYXMgcG9yIHByZWZpam8uIHN0YXJ0c193aXRoKCkgc2VsZWNjaW9uYSBwb3IgcHJlZmlqbzoNCnVweiAlPiUgc2VsZWN0KHVwel9ub21icmUsIHN0YXJ0c193aXRoKCJ0YXNhXyIpKSAlPiUgaGVhZCgpICAgIyB0YXNhcyBkZSBkZWxpdG9zIC8xMC4wMDAgaGFiDQp1cHogJT4lIHNlbGVjdCh1cHpfbm9tYnJlLCBzdGFydHNfd2l0aCgibWluXyIpKSAgJT4lIGhlYWQoKSAgICMgbWludXRvcyBjYW1pbmFuZG8gYSBzZXJ2aWNpb3MNCg0KIyAxLjIgdmFsaWRhciBmYWx0YW50ZXMgZW4gRGF0YXNldA0KDQpmYWx0YW50ZXMgPC0gdXB6IHw+DQogIHN1bW1hcmlzZShhY3Jvc3MoZXZlcnl0aGluZygpLA0KICAgICAgICAgICAgICAgICAgIH4gc3VtKGlzLm5hKC54KSkpKSB8Pg0KICBwaXZvdF9sb25nZXIoDQogICAgY29scyA9IGV2ZXJ5dGhpbmcoKSwNCiAgICBuYW1lc190byA9ICJ2YXJpYWJsZSIsDQogICAgdmFsdWVzX3RvID0gImZhbHRhbnRlcyINCiAgKSB8Pg0KICBhcnJhbmdlKGRlc2MoZmFsdGFudGVzKSkNCg0KZmFsdGFudGVzDQpgYGANCg0KYGBge3IgZXN0aWxvfQ0KIyMgMi4gY2FyZ2FyIMK0cGFsZXRhIGRlIGNvbG9yZXMgDQojLS0tIFBhbGV0YSB5IHRlbWEgZGVsIGN1cnNvIChsb3MgbWlzbW9zIGRlIHRvZG9zIGxvcyBzY3JpcHRzKSAtLS0tLS0tLS0tLS0tLQ0KbW9yYWRvICAgPC0gIiMzNTFBNEYiDQptYWdlbnRhICA8LSAiI0Q5MDA2QyINCnR1cnF1ZXNhIDwtICIjMTY4Qzg2Ig0KZ3JpcyAgICAgPC0gIiNEOUQ2REQiDQoNCnRlbWFfY2xhc2UgPC0gdGhlbWVfbWluaW1hbChiYXNlX3NpemUgPSAxMikgKw0KICB0aGVtZSgNCiAgICBwbG90LnRpdGxlLnBvc2l0aW9uID0gInBsb3QiLA0KICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiwgc2l6ZSA9IDE1LCBoanVzdCA9IDApLA0KICAgIHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwLjUsIGNvbG9yID0gImdyZXkzNSIsIGhqdXN0ID0gMCksDQogICAgcGFuZWwuZ3JpZC5taW5vciA9IGVsZW1lbnRfYmxhbmsoKSwNCiAgICBwbG90LmNhcHRpb24gPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLCBjb2xvciA9ICJncmV5NDUiLCBzaXplID0gOC41KSwNCiAgICBwbG90Lm1hcmdpbiA9IG1hcmdpbigxMCwgMjAsIDEwLCAxMCkNCiAgKQ0KDQoNCmVzdGlsb19wbG90bHkgPC0gZnVuY3Rpb24ocCkgew0KICBwICU+JQ0KICAgIGNvbmZpZyhkaXNwbGF5TW9kZUJhciA9IEZBTFNFKSAlPiUgICAgICAgICAgDQogICAgbGF5b3V0KGZvbnQgPSBsaXN0KGZhbWlseSA9IGZ1ZW50ZV9lZGl0b3JpYWwpKSANCn0NCg0KdGFibGFfYm9uaXRhIDwtIGZ1bmN0aW9uKGRmLCByZXNhbHRhciA9IE5VTEwpIHsNCiAgayA8LSBkZiAlPiUNCiAgICBrYmwoKSAlPiUgICANCiAgICBrYWJsZV9zdHlsaW5nKGJvb3RzdHJhcF9vcHRpb25zID0gYygiaG92ZXIiLCAiY29uZGVuc2VkIiksIGZ1bGxfd2lkdGggPSBUUlVFLCBmb250X3NpemUgPSAxNCkgJT4lDQogICAgcm93X3NwZWMoMCwgYm9sZCA9IFRSVUUsIGNvbG9yID0gIndoaXRlIiwgYmFja2dyb3VuZCA9IG1vcmFkbykgICMgZmlsYSAwID0gZW5jYWJlemFkbw0KICBpZiAoIWlzLm51bGwocmVzYWx0YXIpKSB7DQogICAgayA8LSBrICU+JSByb3dfc3BlYyhyZXNhbHRhciwgYmFja2dyb3VuZCA9ICIjRjZFRUYyIiwgYm9sZCA9IFRSVUUsIGNvbG9yID0gbW9yYWRvKQ0KICB9DQogIGsNCn0NCg0KIyAtLS0gU2VnbWVudGFjacOzbiBwb2JsYWNpb25hbDogbm9tYnJlcyB5IGNvbG9yZXMgKHNlIHVzYW4gZW4gdG9kbyBlbCBpbmZvcm1lKSAtLS0NCmdydXBvcyAgPC0gYygiUG9ibGFjacOzbiBiYWphIiwgIlBvYmxhY2nDs24gbWVkaWEiLCAiUG9ibGFjacOzbiBhbHRhIikNCmNvbG9yZXMgPC0gYygiUG9ibGFjacOzbiBiYWphIiAgPSAiI0Y4NzY2RCIsDQogICAgICAgICAgICAgIlBvYmxhY2nDs24gbWVkaWEiID0gIiNGMkI4NEIiLA0KICAgICAgICAgICAgICJQb2JsYWNpw7NuIGFsdGEiICA9ICIjNEM5RjcwIikNCg0KIyAtLS0gRm9ybWF0byBkZSBuw7ptZXJvcyB5IHBvcmNlbnRhamVzIChlc3RpbG8gY29sb21iaWFubykgLS0tDQpudW0gPC0gZnVuY3Rpb24oeCwgLi4uKSBzY2FsZXM6Om51bWJlcih4LCBiaWcubWFyayA9ICIuIiwgZGVjaW1hbC5tYXJrID0gIiwiLCAuLi4pDQpwYyAgPC0gZnVuY3Rpb24oeCkgc2NhbGVzOjpwZXJjZW50KHgsIGFjY3VyYWN5ID0gMC4xLCBkZWNpbWFsLm1hcmsgPSAiLCIpDQoNCiMgLS0tIFBpZXphcyBkZSBlc3RpbG8gcGxvdGx5IHF1ZSBzZSByZXBldMOtYW4gZW4gdmFyaW9zIGdyw6FmaWNvcyAtLS0NCmhvdmVyX3Bsb3RseSA8LSBsaXN0KGJnY29sb3IgPSBtb3JhZG8sIGZvbnQgPSBsaXN0KGNvbG9yID0gIndoaXRlIiwgc2l6ZSA9IDEzKSwgYWxpZ24gPSAibGVmdCIpDQoNCiMgUGFydGUgdW4gdMOtdHVsbyBsYXJnbyBlbiB2YXJpYXMgbMOtbmVhcyAocGxvdGx5IG5vIGJhamEgZGUgbMOtbmVhIHNvbG8geSBlbCB0w610dWxvIHNlIGNvcnRhKQ0KcGFydGlyX3RpdHVsbyA8LSBmdW5jdGlvbih0ZXh0bywgYW5jaG8gPSA3NSkgcGFzdGUoc3Ryd3JhcCh0ZXh0bywgd2lkdGggPSBhbmNobyksIGNvbGxhcHNlID0gIjxicj4iKQ0KDQp0aXR1bG9fcGxvdGx5IDwtIGZ1bmN0aW9uKHRleHRvLCAuLi4pIHsNCiAgYyhsaXN0KHRleHQgPSB0ZXh0bywgeCA9IDAuMDEsIHhhbmNob3IgPSAibGVmdCIsIHkgPSAwLjk3LCB5YW5jaG9yID0gInRvcCIsDQogICAgICAgICBmb250ID0gbGlzdChzaXplID0gMTgsIGNvbG9yID0gbW9yYWRvKSksIGxpc3QoLi4uKSkNCn0NCg0KbWVudV9wbG90bHkgPC0gZnVuY3Rpb24oYm90b25lcywgeCA9IDAsIHkgPSAxLjA0KSB7DQogIGxpc3QodHlwZSA9ICJkcm9wZG93biIsIGRpcmVjdGlvbiA9ICJkb3duIiwgYWN0aXZlID0gMCwgc2hvd2FjdGl2ZSA9IFRSVUUsDQogICAgICAgeCA9IHgsIHhhbmNob3IgPSAibGVmdCIsIHkgPSB5LCB5YW5jaG9yID0gImJvdHRvbSIsIGJ1dHRvbnMgPSBib3RvbmVzLA0KICAgICAgIGJnY29sb3IgPSAid2hpdGUiLCBib3JkZXJjb2xvciA9IGdyaXMsIGZvbnQgPSBsaXN0KGNvbG9yID0gbW9yYWRvLCBzaXplID0gMTMpKQ0KfQ0KDQojIC0tLSBDYWphIGRlIGNvbWVudGFyaW9zIGRlYmFqbyBkZWwgZ3LDoWZpY28gKGxhIHVzYW4gNC4zIHkgNC40KSAtLS0NCmpzX2NvbWVudGFyaW9zIDwtICINCmZ1bmN0aW9uKGVsLCB4LCBkYXRvcykgew0KICB2YXIgY2FqYSA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoJ2RpdicpOw0KICBjYWphLnN0eWxlLmNzc1RleHQgPSAnd2lkdGg6MTAwJTtib3gtc2l6aW5nOmJvcmRlci1ib3g7cGFkZGluZzoxNHB4IDE4cHg7bWFyZ2luLXRvcDo2cHg7Ym9yZGVyLXRvcDoycHggc29saWQgI0Q5RDZERDtmb250LXNpemU6MTRweDtsaW5lLWhlaWdodDoxLjU7Y29sb3I6IzMzMzsnOw0KICBlbC5wYXJlbnROb2RlLmluc2VydEJlZm9yZShjYWphLCBlbC5uZXh0U2libGluZyk7DQogIHZhciBhY3R1YWwgPSAtMTsNCg0KICBmdW5jdGlvbiBwaW50YXIoaSkgew0KICAgIGlmIChpID09PSBhY3R1YWwpIHJldHVybjsNCiAgICBhY3R1YWwgPSBpOw0KICAgIGNhamEuaW5uZXJIVE1MID0gJyc7DQogICAgdmFyIHVsID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgndWwnKTsNCiAgICB1bC5zdHlsZS5jc3NUZXh0ID0gJ21hcmdpbjowO3BhZGRpbmctbGVmdDoyMHB4Oyc7DQogICAgW10uY29uY2F0KGRhdG9zLmNvbWVudGFyaW9zW2ldKS5mb3JFYWNoKGZ1bmN0aW9uKHQpIHsNCiAgICAgIHZhciBsaSA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoJ2xpJyk7DQogICAgICBsaS5zdHlsZS5tYXJnaW5Cb3R0b20gPSAnNHB4JzsNCiAgICAgIGxpLmlubmVySFRNTCA9IHQ7DQogICAgICB1bC5hcHBlbmRDaGlsZChsaSk7DQogICAgfSk7DQogICAgY2FqYS5hcHBlbmRDaGlsZCh1bCk7DQogIH0NCg0KICAvLyBkZXZ1ZWx2ZSBlbCBuw7ptZXJvIGRlIHZpc3RhIHNlZ8O6biBxdcOpIHRyYXphIGRlIGluaWNpbyBlc3TDoSB2aXNpYmxlIChkYXRvcy5pbmljaW9zID0gdHJhemEgaW5pY2lhbCBkZSBjYWRhIHZpc3RhKQ0KICBmdW5jdGlvbiB2aXN0YSgpIHsNCiAgICB2YXIgZCA9IGVsLmRhdGEsIGluaSA9IGRhdG9zLmluaWNpb3M7DQogICAgZm9yICh2YXIgaSA9IDA7IGkgPCBpbmkubGVuZ3RoOyBpKyspIHsNCiAgICAgIGlmIChkW2luaVtpXV0gJiYgZFtpbmlbaV1dLnZpc2libGUgPT09IHRydWUpIHJldHVybiBpOw0KICAgIH0NCiAgICByZXR1cm4gMDsNCiAgfQ0KDQogIHBpbnRhcigwKTsNCiAgZWwub24oJ3Bsb3RseV9hZnRlcnBsb3QnLCBmdW5jdGlvbigpIHsgcGludGFyKHZpc3RhKCkpOyB9KTsNCn0NCiINCmBgYA0KDQojIDEuIEludHJvZHVjY2nDs24NCg0KRW4gZXN0ZSBpbmZvcm1lIHNlIGFuYWxpemFuIGxhcyAxMTIgVVBaIGRlIEJvZ290w6EsIGNvbiBlbCBwcm9ww7NzaXRvIGRlIGV4cGxvcmFyIGxhcyBjYXJhY3RlcsOtc3RpY2FzIGRlbW9ncsOhZmljYXMgeSB0ZXJyaXRvcmlhbGVzIGRlIGNhZGEgdW5hLCBhc8OtIGNvbW8gc3UgcmVsYWNpw7NuIGNvbiBsYXMgZGVudW5jaWFzIHJlZ2lzdHJhZGFzIGVudHJlIGVuZXJvIHkganVsaW8gZGUgMjAyMy4gQWRlbcOhcywgc2UgYnVzY2EgaWRlbnRpZmljYXIgcG9zaWJsZXMgcmVsYWNpb25lcyBlbnRyZSBsYSBjb21wb3NpY2nDs24gZGUgbGEgcG9ibGFjacOzbiwgbGFzIGNhcmFjdGVyw61zdGljYXMgZGVsIHRlcnJpdG9yaW8geSBsYSBjYW50aWRhZCB5IGxhcyB0YXNhcyBkZSBkZW51bmNpYXMgYXNvY2lhZGFzIGEgZGlmZXJlbnRlcyB0aXBvcyBkZSBkZWxpdG8uIERlIGVzdGEgbWFuZXJhLCBzZSBwcmV0ZW5kZSByZWNvbm9jZXIgcGF0cm9uZXMgeSBkaWZlcmVuY2lhcyBlbnRyZSBsYXMgVVBaIHF1ZSBwZXJtaXRhbiBjb21wcmVuZGVyIG1lam9yIGVsIGNvbXBvcnRhbWllbnRvIHRlcnJpdG9yaWFsIGRlIGxhcyBkZW51bmNpYXMgZW4gbGEgY2l1ZGFkLg0KDQojIyMgUHJlZ3VudGFzIHF1ZSBndcOtYW4gZWwgYW7DoWxpc2lzDQoNCjEuICoqwr9TZWdtZW50YXIgbGFzIFVQWiBwb3IgdGFtYcOxbyBkZSBwb2JsYWNpw7NuIHBlcm1pdGUgY29tcGFyYWNpb25lcyBtw6FzIGp1c3Rhcz8qKg0KMi4gKirCv0PDs21vIHZhcsOtYW4gbGFzIHRhc2FzIGRlIGRlbnVuY2lhcyBzZWfDum4gZWwgZ3J1cG8gZGUgcG9ibGFjacOzbiB5IHF1w6kgVVBaIHNvbiBhdMOtcGljYXM/KioNCjMuICoqwr9RdcOpIGRlbGl0b3MgcHJlZG9taW5hbiBlbiBjYWRhIGdydXBvIHkgY2FtYmlhIHN1IHBlc28gZW50cmUgZ3J1cG9zPyoqDQo0LiAqKsK/Q8OzbW8gc2UgcmVsYWNpb25hIGxhIGNvbXBvc2ljacOzbiBwb3IgZWRhZCBjb24gbGFzIHRhc2FzIGRlIGRlbGl0byB5IGVzYSByZWxhY2nDs24gY2FtYmlhIHNlZ8O6biBlbCBncnVwbz8qKiANCjUuICoqwr9Ew7NuZGUgc2UgdWJpY2FuIGxhcyBVUFogY29uIG1heW9yZXMgdGFzYXMgeSBjw7NtbyBzZSB2ZW4gbGFzIHNlZ21lbnRhY2lvbmVzIGVuIGVsIHRlcnJpdG9yaW8/KioNCg0KIyMjIFZhcmlhYmxlcyBzZWxlY2Npb25hZGFzIHkgcG9yIHF1w6kNCg0KU2UgdXNhbiAxNCB2YXJpYWJsZXMgYWdydXBhZGFzIGVuIHRyZXMgYmxvcXVlcywgcG9ycXVlIGNhZGEgYmxvcXVlIHJlc3BvbmRlIGEgdW5hIHBhcnRlIGRlIGxhcyBwcmVndW50YXM6DQoNCnwgQmxvcXVlIHwgVmFyaWFibGVzIHwgUG9yIHF1w6kgc2UgZWxpZ2VuIHwNCnwtLS18LS0tfC0tLXwNCnwgKipUZXJyaXRvcmlhbCoqIHwgYGFyZWFfa20yYCwgYHBvYmxhY2lvbmAgfCBEZXNjcmliZW4gZWwgdGFtYcOxbyB5IGxhIHBvYmxhY2nDs24gZGUgY2FkYSBVUFouIGBwb2JsYWNpb25gIGVzIGxhIGJhc2UgZGUgbGEgc2VnbWVudGFjacOzbiAoSy1tZWFucykgeSBkZWwgY8OhbGN1bG8gZGUgdGFzYXMuIHwNCnwgKipEZW1vZ3LDoWZpY28qKiB8IGBwY3RfMF8xNGAsIGBwY3RfMTVfMjlgLCBgcGN0XzMwXzU5YCAoU2UgZ2VuZXJhIGVzdGEgdmFyaWFibGUpLCBgcGN0XzYwX21hc2AgfCBQZXJtaXRlbiB2ZXIgc2kgbGEgZXN0cnVjdHVyYSBwb3IgZWRhZGVzIHNlIGFzb2NpYSBjb24gbcOhcyBvIG1lbm9zIGRlbnVuY2lhcy4gfA0KfCAqKkRlbGl0b3MqKiB8IGBjYXNvc190b3RhbGAsIGB0YXNhX3RvdGFsYCwgYGh1cnRvX3RvdGFsYCAoc3VtYSBkZSA3IHRpcG9zIGRlIGh1cnRvKSwgYGxlc2lvbmVzYCwgYGhvbWljaWRpb3NgLCBgdmlvbGVuY2lhX2ludHJhZmFtaWxpYXJgIHkgc3VzIHRhc2FzIHwgU29uIGxvIHF1ZSBzZSBxdWllcmUgZXhwbGljYXIuIFNlIHVzYW4gKip0YXNhcyBwb3IgY2FkYSAxMC4wMDAgaGFiaXRhbnRlcyoqIHBhcmEgY29tcGFyYXIgVVBaIGRlIGRpc3RpbnRvIHRhbWHDsW8uIHwNCg0KPiAqKk5vdGE6KiogaW1wb3J0YW50ZSBpbmRpY2FyIHF1ZSBsYXMgY2lmcmFzIHNvbiBkZW51bmNpYXMgcmVnaXN0cmFkYXMsIG5vIGRlbGl0b3Mgb2N1cnJpZG9zLiBQb3IgZXNvIHNlIGludGVycHJldGFuIGNvbW8gYXNvY2lhY2lvbmVzIGVudHJlIFVQWiB5IG5vIGNvbW8gY2F1c2FzLg0KDQojIDIuIE9iamV0aXZvcw0KDQoqKk9iamV0aXZvIGdlbmVyYWw6KiogQW5hbGl6YXIgbGEgcmVsYWNpw7NuIGVudHJlIGxhcyBjYXJhY3RlcsOtc3RpY2FzIGRlbW9ncsOhZmljYXMgeSB0ZXJyaXRvcmlhbGVzIGRlIGxhcyAxMTIgVVBaIGRlIEJvZ290w6EgeSBsYSBjYW50aWRhZCB5IGxhcyB0YXNhcyBkZSBkZW51bmNpYXMgcmVnaXN0cmFkYXMgcG9yIGRpZmVyZW50ZXMgdGlwb3MgZGUgZGVsaXRvLCBjb24gZWwgZmluIGRlIGlkZW50aWZpY2FyIHBhdHJvbmVzIHkgZGlmZXJlbmNpYXMgdGVycml0b3JpYWxlcyBkdXJhbnRlIGVsIHBlcmlvZG8gZGUgZXN0dWRpby4NCg0KKipPYmpldGl2b3MgZXNwZWPDrWZpY29zOioqDQoNCjEuIFNlZ21lbnRhciBsYXMgVVBaIGRlIEJvZ290w6EgYSBwYXJ0aXIgZGUgc3VzIHZhcmlhYmxlcyBjb21vIGxhIHBvYmxhY2nDs24sIGxhIGNvbXBvc2ljacOzbiBwb3IgZ3J1cG9zIGRlIGVkYWQsIGVsIMOhcmVhIHkgbGEgZGVuc2lkYWQgcG9ibGFjaW9uYWwuDQoNCjIuIERlc2NyaWJpciB5IGNvbXBhcmFyIGxhIGNhbnRpZGFkIHkgbGFzIHRhc2FzIGRlIGRlbnVuY2lhcyBjb3JyZXNwb25kaWVudGVzIGEgZGlmZXJlbnRlcyB0aXBvcyBkZSBkZWxpdG8sIGlkZW50aWZpY2FuZG8gbGFzIFVQWiBxdWUgcHJlc2VudGFuIG1heW9yZXMgeSBtZW5vcmVzIHJlZ2lzdHJvcy4NCg0KMy4gRXhwbG9yYXIgbGFzIHBvc2libGVzIHJlbGFjaW9uZXMgZW50cmUgbGFzIGNhcmFjdGVyw61zdGljYXMgcG9ibGFjaW9uYWxlcyB5IHRlcnJpdG9yaWFsZXMgZGUgbGFzIFVQWiB5IGVsIGNvbXBvcnRhbWllbnRvIGRlIGxhcyBkZW51bmNpYXMsIG1lZGlhbnRlIGhlcnJhbWllbnRhcyBlc3RhZMOtc3RpY2FzIHkgdmlzdWFsaXphY2lvbmVzIHF1ZSBwZXJtaXRhbiBpZGVudGlmaWNhciBwYXRyb25lcyB5IGRpZmVyZW5jaWFzIGVzcGFjaWFsZXMuDQoNCiMgMy4gRnVlbnRlcyBkZSBpbmZvcm1hY2nDs24NCg0KIyMgMy4xIERhdGFzZXQgZGUgZXN0dWRpbyBVUFogQm9nb3TDoQ0KDQojIyMgMy4xLjEgRnVlbnRlcw0KDQoxLiBEZWxpdG9zOiBTZWNyZXRhcsOtYSBEaXN0cml0YWwgZGUgU2VndXJpZGFkLCBDb252aXZlbmNpYSB5IEp1c3RpY2lhLiBQb2JsYWNpw7NuIHkgdml2aWVuZGFzOiBwcm95ZWNjaW9uZXMNCkRBTkUgKENOUFYgMjAxOCkuIA0KDQoyLiBMdW1pbmFyaWFzOiBEYXRvcyBBYmllcnRvcyBCb2dvdMOhLiBFbmN1ZXN0YSBNdWx0aXByb3DDs3NpdG8gMjAyMTogREFORS4NCg0KIyMjIDMuMS4yIFZhcmlhYmxlcyBhbmFsaXNpcyBkZSBlc3R1ZGlvDQoNCmBgYHtyIHRhYmxhLXZhcmlhYmxlcy11cHp9DQojIExhcyA4IHZhcmlhYmxlcyBkZSBlc3R1ZGlvIGNvbiBzdSBkZXNjcmlwY2nDs24gKHVuYSBzb2xhIHZlejogdGFtYmnDqW4gc2UgdXNhIGVuIGxhIHRhYmxhIGRlc2NyaXB0aXZhKQ0KZGVzY3JpcGNpb25fdmFyaWFibGVzIDwtIGMoDQogIGFyZWFfa20yICAgICAgICAgPSAiw4FyZWEgdGVycml0b3JpYWwgZGUgbGEgVVBaIGVuIGtpbMOzbWV0cm9zIGN1YWRyYWRvcyIsDQogIHBvYmxhY2lvbiAgICAgICAgPSAiQ2FudGlkYWQgZGUgaGFiaXRhbnRlcyBkZSBsYSBVUFoiLA0KICBkZW5zaWRhZF9oYWJfa20yID0gIkRlbnNpZGFkIGRlIGhhYml0YW50ZXMgcG9yIGtpbMOzbWV0cm8gY3VhZHJhZG8iLA0KICBwY3RfMF8xNCAgICAgICAgID0gIlBvcmNlbnRhamUgZGUgcG9ibGFjacOzbiBlbnRyZSAwIHkgMTQgYcOxb3MiLA0KICBwY3RfMTVfMjkgICAgICAgID0gIlBvcmNlbnRhamUgZGUgcG9ibGFjacOzbiBlbnRyZSAxNSB5IDI5IGHDsW9zIiwNCiAgcGN0XzYwX21hcyAgICAgICA9ICJQb3JjZW50YWplIGRlIHBvYmxhY2nDs24gbWFzIGRlIDYwIGHDsW9zIiwNCiAgY2Fzb3NfdG90YWwgICAgICA9ICJDYW50aWRhZCB0b3RhbCBkZSBkZW51bmNpYXMgcmVnaXN0cmFkYXMiLA0KICB0YXNhX3RvdGFsICAgICAgID0gIlRhc2EgdG90YWwgZGUgZGVudW5jaWFzIHBvciBjYWRhIDEwLjAwMCBoYWJpdGFudGVzIg0KKQ0KDQp2YXJpYWJsZXMgPC0gbmFtZXMoZGVzY3JpcGNpb25fdmFyaWFibGVzKQ0KDQp0aWJibGUoDQogIFZhcmlhYmxlICAgID0gdmFyaWFibGVzLA0KICBEZXNjcmlwY2lvbiA9IHVubmFtZShkZXNjcmlwY2lvbl92YXJpYWJsZXMpDQopICU+JQ0KICB0YWJsYV9ib25pdGEocmVzYWx0YXIgPSBjKDEsMikpDQpgYGANCg0KKipEYXRvcyBpbXBvcnRhbnRlcyBkZWwgZGF0YXNldCBhIHRlbmVyIGVuIGN1ZW50YSoqDQoNCmBgYHtyIHJlc3VtZW4tZ2VuZXJhbH0NCm5fdXB6IDwtIG5fZGlzdGluY3QodXB6JHVwel9jb2QpDQoNCnBvYmxhY2lvbl90b3RhbCA8LSBzdW0odXB6JHBvYmxhY2lvbiwgbmEucm0gPSBUUlVFKQ0KDQphcmVhX3RvdGFsIDwtIHN1bSh1cHokYXJlYV9rbTIsIG5hLnJtID0gVFJVRSkNCg0KdG90YWxfZGVudW5jaWFzIDwtIHN1bSh1cHokY2Fzb3NfdG90YWwsIG5hLnJtID0gVFJVRSkNCg0KcGFzdGUwKA0KICBuX3VweiwgIiBVUFogYW5hbGl6YWRhcyDCtyAiLA0KICBjb21tYShwb2JsYWNpb25fdG90YWwsIGJpZy5tYXJrID0gIi4iKSwgIiBoYWJpdGFudGVzIMK3ICIsDQogIGNvbW1hKHJvdW5kKGFyZWFfdG90YWwsIDIpLCBiaWcubWFyayA9ICIuIiksICIga23CsiBkZSBleHRlbnNpw7NuIMK3ICIsDQogIGNvbW1hKHRvdGFsX2RlbnVuY2lhcywgYmlnLm1hcmsgPSAiLiIpLCAiIGRlbnVuY2lhcyByZWdpc3RyYWRhcyIsIiAgUGVyaW9kbzogZW5lcm8tanVsaW8gZGUgMjAyMyINCikNCmBgYA0KDQoqKkJvZ290w6EgZW4gY2lmcmFzOiB1bmEgbWlyYWRhIGVzdGFkw61zdGljYSBhIGxhcyBVUFo6KioNCg0KYGBge3IgdGFibGEtZGVzY3JpcHRpdmF9DQojICJ2YXJpYWJsZXMiIHZpZW5lIGRlIGxhIHRhYmxhIGRlIHZhcmlhYmxlcyAoc2VjY2nDs24gMy4xLjIpDQoNCnRhYmxhX2Rlc2NyaXB0aXZhIDwtIGRhdGEuZnJhbWUoDQogIE1lZGlhID0gc2FwcGx5KHVwelt2YXJpYWJsZXNdLCBtZWFuLCBuYS5ybSA9IFRSVUUpLA0KICBNZWRpYW5hID0gc2FwcGx5KHVwelt2YXJpYWJsZXNdLCBtZWRpYW4sIG5hLnJtID0gVFJVRSksDQogIERlc3ZpYWNpb24gPSBzYXBwbHkodXB6W3ZhcmlhYmxlc10sIHNkLCBuYS5ybSA9IFRSVUUpLA0KICBNaW5pbW8gPSBzYXBwbHkodXB6W3ZhcmlhYmxlc10sIG1pbiwgbmEucm0gPSBUUlVFKSwNCiAgTWF4aW1vID0gc2FwcGx5KHVwelt2YXJpYWJsZXNdLCBtYXgsIG5hLnJtID0gVFJVRSkNCikNCg0KIyBSZWRvbmRlYXIgbGFzIGVzdGFkw61zdGljYXMgYSAxIGRlY2ltYWwNCnRhYmxhX2Rlc2NyaXB0aXZhW10gPC0gbGFwcGx5KHRhYmxhX2Rlc2NyaXB0aXZhLCByb3VuZCwgZGlnaXRzID0gMSkNCg0KIyBNb3N0cmFyIHRhYmxhDQp0YWJsYV9kZXNjcmlwdGl2YSAlPiUNCiAgdGFibGFfYm9uaXRhKHJlc2FsdGFyID0gYygxLCAyKSkNCmBgYA0KDQojIDQuIEFuYWxpc2lzIHBvYmxhY2lvbmFsIHkgdGVycml0b3JpYWwgZGUgbGFzIHVweg0KDQojIyA0LjEgwr9MYSBzZWdtZW50YWNpw7NuIGRlIGxhcyBwb2JsYWNpb25lcyBwZXJtaXRlIHJlYWxpemFyIHVuIGFuw6FsaXNpcyBtw6FzIHByZWNpc28geSBwcm9mdW5kbyBkZSBsYSBpbmZvcm1hY2nDs24/DQoNCmBgYHtyIGhpc3RvLXBvYmxhY2lvbn0NCnBfcG9ibGFjaW9uX2FyZWEgPC0gZ2dwbG90KHVweiwgYWVzKHggPSBhcmVhX2ttMiwgeSA9IHBvYmxhY2lvbikpICsNCiAgZ2VvbV9wb2ludCgNCiAgICBjb2xvciA9ICIjNkI0QzhBIiwNCiAgICBzaXplID0gMi41LA0KICAgIGFscGhhID0gMC43LA0KICAgIGFlcyh0ZXh0ID0gcGFzdGUwKA0KICAgICAgIlVQWjogIiwgdXB6X25vbWJyZSwNCiAgICAgICI8YnI+w4FyZWE6ICIsIHJvdW5kKGFyZWFfa20yLCAyKSwgIiBrbcKyIiwNCiAgICAgICI8YnI+UG9ibGFjacOzbjogIiwNCiAgICAgIGZvcm1hdChyb3VuZChwb2JsYWNpb24pLCBiaWcubWFyayA9ICIuIiwgZGVjaW1hbC5tYXJrID0gIiwiLA0KICAgICAgICAgICAgIHNjaWVudGlmaWMgPSBGQUxTRSkNCiAgICApKQ0KICApICsNCiAgZ2VvbV9zbW9vdGgoDQogICAgbWV0aG9kID0gImxtIiwNCiAgICBzZSA9IEZBTFNFLA0KICAgIGNvbG9yID0gIiNEMTdBMjIiDQogICkgKw0KICBzY2FsZV95X2NvbnRpbnVvdXMoDQogICAgbGFiZWxzID0gc2NhbGVzOjpsYWJlbF9jb21tYSgNCiAgICAgIGJpZy5tYXJrID0gIi4iLA0KICAgICAgZGVjaW1hbC5tYXJrID0gIiwiDQogICAgKQ0KICApICsNCiAgbGFicygNCiAgICB4ID0gIsOBcmVhIHRlcnJpdG9yaWFsIChrbcKyKSIsDQogICAgeSA9ICJQb2JsYWNpw7NuIChoYWJpdGFudGVzKSINCiAgKSArDQogIHRlbWFfY2xhc2UgKw0KICB0aGVtZSgNCiAgICBwYW5lbC5ncmlkLm1ham9yLnggPSBlbGVtZW50X2JsYW5rKCkNCiAgKQ0KDQpnZ3Bsb3RseShwX3BvYmxhY2lvbl9hcmVhLCB0b29sdGlwID0gInRleHQiKSAlPiUNCiAgZXN0aWxvX3Bsb3RseSgpICU+JQ0KICBsYXlvdXQoDQogICAgdGl0bGUgPSB0aXR1bG9fcGxvdGx5KA0KICAgICAgcGFzdGUwKA0KICAgICAgICAiPGI+IiwgcGFydGlyX3RpdHVsbygiRWwgw6FyZWEgeSBsYSBwb2JsYWNpw7NuIHNlIHJlbGFjaW9uYW4gZGUgbWFuZXJhIG1vZGVyYWRhOiBhbGd1bmFzIFVQWiBwZXF1ZcOxYXMgdGllbmVuIG3DoXMgcG9ibGFjacOzbiBxdWUgb3RyYXMgbcOhcyBncmFuZGVzIiksICI8L2I+IiwNCiAgICAgICAgIjxicj48c3VwPkNhZGEgcHVudG8gcmVwcmVzZW50YSB1bmEgVVBaLiBQYXNhIGVsIGN1cnNvciBzb2JyZSBjYWRhIHB1bnRvIHBhcmEgY29uc3VsdGFyIHN1cyBjaWZyYXMuPC9zdXA+Ig0KICAgICAgKSwNCiAgICAgIHhyZWYgPSAiY29udGFpbmVyIg0KICAgICksDQogICAgbWFyZ2luID0gbGlzdCh0ID0gMTI1KQ0KICApDQpgYGANCg0KDQpBbCByZXZpc2FyIGxvcyBkYXRvcywgc2UgZXZpZGVuY2lhIHF1ZSBsYSBwb2JsYWNpw7NuIHByZXNlbnRhIHVuYSBkaWZlcmVuY2lhIGJhc3RhbnRlIGFtcGxpYSBlbnRyZSBlbCB2YWxvciBtw61uaW1vIHkgZWwgbcOheGltby4gUG9yIGVzdGEgcmF6w7NuLCBjb25zaWRlcm8gbmVjZXNhcmlvIGRpdmlkaXIgbGFzIFVQWiBlbiB0cmVzIGdydXBvczogcG9ibGFjacOzbiBiYWphLCBwb2JsYWNpw7NuIG1lZGlhIHkgcG9ibGFjacOzbiBhbHRhLiBFc3RvIHBlcm1pdGlyw6EgcmVhbGl6YXIgY29tcGFyYWNpb25lcyBlbnRyZSBVUFogcXVlIHRlbmdhbiBjYXJhY3RlcsOtc3RpY2FzIHBvYmxhY2lvbmFsZXMgc2ltaWxhcmVzIHkgYW5hbGl6YXIgZGUgdW5hIG1lam9yIG1hbmVyYSBsYXMgdGFzYXMgZGUgZGVudW5jaWFzLCB0ZW5pZW5kbyBlbiBjdWVudGEgcXVlIGVzdGFzIHB1ZWRlbiB2YXJpYXIgYmFzdGFudGUgZGVwZW5kaWVuZG8gZGUgbGEgY2FudGlkYWQgZGUgaGFiaXRhbnRlcyBkZSBjYWRhIHRlcnJpdG9yaW8uDQoNCiMjIDQuMiDCv0PDs21vIHZhcsOtYW4gbGFzIHRhc2FzIGRlIGRlbnVuY2lhcyBzZWfDum4gZWwgZ3J1cG8gZGUgcG9ibGFjacOzbiB5IHF1w6kgVVBaIHNvbiBhdMOtcGljYXM/DQoNClRlbmllbmRvIGVuIGN1ZW50YSBlbCBoYWxsYXpnbyBhbnRlcmlvciB5IGdlbmVyYWRhIGxhIHNlZ21lbnRhY2nDs24gcG9yIG1lZGlvIGRlIEstTWVhbnMgc2UgZXZpZGVuY2lhIGxvIHNpZ3VpZW50ZS4NCg0KKipFc3RhZGlzdGljYXMgZGVzY3JpcHRpdmFzIHNlZ21lbnRhY2nDs24gZ2VuZXJhZGEqKg0KDQpgYGB7ciBnZW5lcmFjaW9uX3NlZ21lbnRhY2lvbn0NCg0KIyBLLW1lYW5zICMgbW9kZWxvIGRlIGNsdXN0ZXJzDQpzZXQuc2VlZCgxMjMpDQoNCm1vZGVsbyA8LSBrbWVhbnMoDQogIHNjYWxlKHVwelsicG9ibGFjaW9uIl0pLA0KICBjZW50ZXJzID0gMywNCiAgbnN0YXJ0ID0gMjUNCikNCg0KIyBJZGVudGlmaWNhciBlbCBvcmRlbiBkZSBsb3MgZ3J1cG9zIHNlZ8O6biBzdSBwb2JsYWNpw7NuIHByb21lZGlvDQpvcmRlbl9ncnVwb3MgPC0gb3JkZXIobW9kZWxvJGNlbnRlcnNbLCAxXSkNCg0KIyBBc2lnbmFyIGxhcyBjYXRlZ29yw61hcyBhIGNhZGEgVVBaIChsb3Mgbm9tYnJlcyBkZSBsb3MgZ3J1cG9zIHNlIGRlZmluZW4gZW4gZWwgY2h1bmsgImVzdGlsbyIpDQp1cHogPC0gdXB6ICU+JQ0KICBtdXRhdGUoDQogICAgZ3J1cG9fcG9ibGFjaW9uID0gZmFjdG9yKA0KICAgICAgZ3J1cG9zW21hdGNoKG1vZGVsbyRjbHVzdGVyLCBvcmRlbl9ncnVwb3MpXSwNCiAgICAgIGxldmVscyA9IGdydXBvcw0KICAgICkNCiAgKQ0KYGBgDQoNCmBgYHtyIHByZXBhcmFyX2RhdG9zfQ0KIyBWYXJpYWJsZXMgZGVyaXZhZGFzIHF1ZSBzZSB1c2FuIGVuIDQuMywgNC40IHkgZWwgbWFwYSAoc2UgY2FsY3VsYW4gdW5hIHNvbGEgdmV6KQ0KY29sc19odXJ0byA8LSBjKCJjYXNvc19odXJ0b19wZXJzb25hcyIsICJjYXNvc19odXJ0b19yZXNpZGVuY2lhcyIsICJjYXNvc19odXJ0b19hdXRvbW90b3IiLA0KICAgICAgICAgICAgICAgICJjYXNvc19odXJ0b19iaWNpY2xldGFzIiwgImNhc29zX2h1cnRvX2NvbWVyY2lvIiwgImNhc29zX2h1cnRvX2NlbHVsYXJlcyIsDQogICAgICAgICAgICAgICAgImNhc29zX2h1cnRvX21vdG9zIikNCg0KdXB6IDwtIHVweiAlPiUNCiAgbXV0YXRlKA0KICAgIGNhc29zX2h1cnRvX3RvdGFsID0gcm93U3VtcyhhY3Jvc3MoYWxsX29mKGNvbHNfaHVydG8pKSksDQogICAgdGFzYV9odXJ0b190b3RhbCAgPSBjYXNvc19odXJ0b190b3RhbCAvIHBvYmxhY2lvbiAqIDEwMDAwLA0KICAgIHBjdF8zMF81OSAgICAgICAgID0gMTAwIC0gKHBjdF8wXzE0ICsgcGN0XzE1XzI5ICsgcGN0XzYwX21hcykgICAjIGxvIHF1ZSBmYWx0YSBwYXJhIGxsZWdhciBhIDEwMCAlDQogICkNCmBgYA0KDQpgYGB7ciBnZW5lcmFjaW9uX2dyYWZpY299DQprX2F0aXBpY28gPC0gMS41ICAgIyBsw61taXRlIGF0w61waWNvOiBRMyArIDEsNSDDlyBJUVINCmtfZXh0cmVtbyA8LSAzICAgICAjIGzDrW1pdGUgZXh0cmVtbzogUTMgKyAzIMOXIElRUg0KDQojIEFncmVnYXIgbiBhIGxhcyBldGlxdWV0YXMgKGdydXBvX3BvYmxhY2lvbiB5YSBlcyBmYWN0b3Igb3JkZW5hZG8gZGVzZGUgZWwgSy1tZWFucykNCnVweiA8LSB1cHogJT4lDQogIGdyb3VwX2J5KGdydXBvX3BvYmxhY2lvbikgJT4lDQogIG11dGF0ZSgNCiAgICAjIHRvZG8gc2UgY2FsY3VsYSBjb24gbGFzIHRhc2FzIHJlYWxlcyAoc2luIGxvZykNCiAgICBxMV9nID0gcXVhbnRpbGUodGFzYV90b3RhbCwgMC4yNSksDQogICAgcTNfZyA9IHF1YW50aWxlKHRhc2FfdG90YWwsIDAuNzUpLA0KICAgIGlxcl9nID0gcTNfZyAtIHExX2csDQogICAgbGltX2F0aXBpY28gPSBxM19nICsga19hdGlwaWNvICogaXFyX2csDQogICAgbGltX2V4dHJlbW8gPSBxM19nICsga19leHRyZW1vICogaXFyX2csDQogICAgZXNfYXRpcGljbyAgPSB0YXNhX3RvdGFsID4gbGltX2F0aXBpY28gfCB0YXNhX3RvdGFsIDwgcTFfZyAtIGtfYXRpcGljbyAqIGlxcl9nLA0KICAgIGVzX2V4dHJlbW8gID0gdGFzYV90b3RhbCA+IGxpbV9leHRyZW1vIHwgdGFzYV90b3RhbCA8IHExX2cgLSBrX2V4dHJlbW8gKiBpcXJfZywNCiAgICBldGlxdWV0YSA9IHBhc3RlMChncnVwb19wb2JsYWNpb24sICI8YnI+KG4gPSAiLCBuKCksICIpIikNCiAgKSAlPiUNCiAgdW5ncm91cCgpICU+JQ0KICBtdXRhdGUoDQogICAgZXRpcXVldGEgPSBmYWN0b3IoZXRpcXVldGEsIGxldmVscyA9IHVuaXF1ZShldGlxdWV0YVtvcmRlcihncnVwb19wb2JsYWNpb24pXSkpLA0KICAgIHBvcyA9IGFzLm51bWVyaWMoZXRpcXVldGEpLA0KICAgIHRleHRvID0gcGFzdGUwKA0KICAgICAgIjxiPiIsIHVwel9ub21icmUsICI8L2I+IiwNCiAgICAgICI8YnI+TG9jYWxpZGFkOiAiLCBsb2NhbGlkYWQsDQogICAgICAiPGJyPlBvYmxhY2nDs246ICIsIHNjYWxlczo6Y29tbWEocG9ibGFjaW9uKSwNCiAgICAgICI8YnI+VGFzYSB0b3RhbDogIiwgcm91bmQodGFzYV90b3RhbCwgMSksDQogICAgICBpZmVsc2UoZXNfZXh0cmVtbywgIjxicj48aT5BdMOtcGljbyBleHRyZW1vPC9pPiIsICI8YnI+PGk+QXTDrXBpY288L2k+IikNCiAgICApDQogICkNCg0KIyBsw61taXRlcyBwb3IgZ3J1cG8gKHBhcmEgcXVlIGxvcyByZXZpc2VzKQ0KdXB6ICU+JSBkaXN0aW5jdChncnVwb19wb2JsYWNpb24sIHExX2csIHEzX2csIGlxcl9nLCBsaW1fYXRpcGljbywgbGltX2V4dHJlbW8pJT4lDQogIHRhYmxhX2Jvbml0YShyZXNhbHRhciA9IGMoKSkNCg0KcmdiYSA8LSBmdW5jdGlvbihjb2wsIGEpIHsgbSA8LSBjb2wycmdiKGNvbCk7IHNwcmludGYoInJnYmEoJWQsJWQsJWQsJS4yZikiLCBtWzFdLCBtWzJdLCBtWzNdLCBhKSB9DQoNCmcgPC0gcGxvdF9seSgpDQoNCiMgMS4gY2FqYXM6IGxvcyBiaWdvdGVzIHRlcm1pbmFuIGVuIGVsIGzDrW1pdGUgc3VwZXJpb3IvaW5mZXJpb3IgKG5vIGVuIGVsIG3DoXhpbW8pDQojICAgIGxvcyBwdW50b3MgcHJvcGlvcyBkZSBsYSBjYWphIHNlIG9jdWx0YW47IGxvcyBhdMOtcGljb3MgcXVlIHNlIHZlbiBzb24gbG9zIGRlIGFiYWpvDQpmb3IgKGdyIGluIGxldmVscyh1cHokZ3J1cG9fcG9ibGFjaW9uKSkgew0KICBkIDwtIHVweiAlPiUgZmlsdGVyKGdydXBvX3BvYmxhY2lvbiA9PSBncikNCiAgZyA8LSBnICU+JSBhZGRfdHJhY2UoDQogICAgZGF0YSA9IGQsIHR5cGUgPSAiYm94IiwNCiAgICB4ID0gfnBvcywgeSA9IH50YXNhX3RvdGFsLCB3aWR0aCA9IDAuNiwNCiAgICBmaWxsY29sb3IgPSByZ2JhKGNvbG9yZXNbW2dyXV0sIDAuNSksDQogICAgbGluZSA9IGxpc3QoY29sb3IgPSAiZ3JleTIwIiwgd2lkdGggPSAxLjUpLA0KICAgIGJveHBvaW50cyA9ICJvdXRsaWVycyIsDQogICAgbWFya2VyID0gbGlzdChvcGFjaXR5ID0gMCwgc2l6ZSA9IDEpLCAgICMgcHVudG9zIGRlIGxhIGNhamEgaW52aXNpYmxlcw0KICAgIHF1YXJ0aWxlbWV0aG9kID0gImxpbmVhciIsDQogICAgaG92ZXJvbiA9ICJib3hlcyIsICAgICAgICAgICAgICAgICAgICAgICMgZWwgY3VhZHJvIGRlbCBtb3VzZSBzb2xvIHNhbGUgc29icmUgbGEgY2FqYQ0KICAgIGhvdmVyaW5mbyA9ICJ5Iiwgc2hvd2xlZ2VuZCA9IEZBTFNFDQogICkNCn0NCg0KIyAyLiBhdMOtcGljb3MgKG5lZ3JvcykNCnNldC5zZWVkKDEyMykNCm5lZ3JvcyA8LSB1cHogJT4lIGZpbHRlcihlc19hdGlwaWNvLCAhZXNfZXh0cmVtbykgJT4lDQogIG11dGF0ZSh4aiA9IHBvcyArIHJ1bmlmKG4oKSwgLTAuMDcsIDAuMDcpKQ0KDQpnIDwtIGcgJT4lIGFkZF90cmFjZSgNCiAgZGF0YSA9IG5lZ3JvcywgdHlwZSA9ICJzY2F0dGVyIiwgbW9kZSA9ICJtYXJrZXJzIiwNCiAgeCA9IH54aiwgeSA9IH50YXNhX3RvdGFsLA0KICBtYXJrZXIgPSBsaXN0KGNvbG9yID0gImdyZXkyMCIsIHNpemUgPSA3LCBvcGFjaXR5ID0gMC43NSksDQogIGhvdmVydGV4dCA9IH50ZXh0bywgaG92ZXJpbmZvID0gInRleHQiLCBzaG93bGVnZW5kID0gRkFMU0UNCikNCg0KIyAzLiBhdMOtcGljb3MgZXh0cmVtb3MgKHJvam9zKQ0Kcm9qb3MgPC0gdXB6ICU+JSBmaWx0ZXIoZXNfZXh0cmVtbykgJT4lDQogIG11dGF0ZSh4aiA9IHBvcyArIHJ1bmlmKG4oKSwgLTAuMDcsIDAuMDcpKQ0KDQpnIDwtIGcgJT4lIGFkZF90cmFjZSgNCiAgZGF0YSA9IHJvam9zLCB0eXBlID0gInNjYXR0ZXIiLCBtb2RlID0gIm1hcmtlcnMiLA0KICB4ID0gfnhqLCB5ID0gfnRhc2FfdG90YWwsDQogIG1hcmtlciA9IGxpc3QoY29sb3IgPSAicmVkIiwgc2l6ZSA9IDEwLCBvcGFjaXR5ID0gMC45KSwNCiAgaG92ZXJ0ZXh0ID0gfnRleHRvLCBob3ZlcmluZm8gPSAidGV4dCIsIHNob3dsZWdlbmQgPSBGQUxTRQ0KKQ0KDQojIDQuIGVzY2FsYSBsb2cgc29sbyBwYXJhIGxhIHZpc3RhDQptYXJjYXMgPC0gYyg1MCwgMTAwLCAyMDAsIDUwMCwgMTAwMCwgMjAwMCwgNTAwMCwgMTAwMDApDQpldGlxdWV0YXNfeCA8LSBsZXZlbHModXB6JGV0aXF1ZXRhKQ0KDQpnIDwtIGcgJT4lDQogIGxheW91dCgNCiAgICB0aXRsZSA9IGxpc3QoDQogICAgICB0ZXh0ID0gcGFzdGUwKCI8Yj4iLCBwYXJ0aXJfdGl0dWxvKCJQb2JsYWNpb24gYmFqYSB0aWVuZSBsb3MgYXRpcGljb3MgZXh0cmVtb3MsIGF1bWVudGFuZG8gbGEgdGFzYSBkZSBkZWxpdG9zLCBQb2JsYWNpb24gbWVkaWEgeSBhbHRhIHNvbiBob21vZ8OpbmVhcyIpLCAiPC9iPiIsDQogICAgICAgICAgICAgICAgICAgICI8YnI+PHN1cD5Nw6l0b2RvIEstbWVhbnMgwrcgcHVudG9zIG5lZ3JvcyA9IGF0w61waWNvcyAoPiBRMyArIDEsNSDDlyBJUVIpIMK3ICIsDQogICAgICAgICAgICAgICAgICAgICJwdW50b3Mgcm9qb3MgPSBhdMOtcGljb3MgZXh0cmVtb3MgKD4gUTMgKyAzIMOXIElRUikgwrcgcGFzYSBlbCBjdXJzb3IgcGFyYSB2ZXIgbG9zIHZhbG9yZXM8L3N1cD4iKSwNCiAgICAgIHggPSAwLjAxLCB4YW5jaG9yID0gImxlZnQiDQogICAgKSwNCiAgICB4YXhpcyA9IGxpc3QodGl0bGUgPSAiR3J1cG8gZGUgcG9ibGFjacOzbiIsIHNob3dncmlkID0gRkFMU0UsIHplcm9saW5lID0gRkFMU0UsDQogICAgICAgICAgICAgICAgIHRpY2t2YWxzID0gc2VxX2Fsb25nKGV0aXF1ZXRhc194KSwgdGlja3RleHQgPSBldGlxdWV0YXNfeCwNCiAgICAgICAgICAgICAgICAgcmFuZ2UgPSBjKDAuNCwgbGVuZ3RoKGV0aXF1ZXRhc194KSArIDAuNikpLA0KICAgIHlheGlzID0gbGlzdCh0aXRsZSA9ICJUYXNhIHRvdGFsIGRlIGRlbGl0b3MgKGVzY2FsYSBsb2cpIiwgdHlwZSA9ICJsb2ciLA0KICAgICAgICAgICAgICAgICB0aWNrdmFscyA9IG1hcmNhcywgdGlja3RleHQgPSBmb3JtYXQobWFyY2FzLCBiaWcubWFyayA9ICIuIiwgc2NpZW50aWZpYyA9IEZBTFNFKSwNCiAgICAgICAgICAgICAgICAgaG92ZXJmb3JtYXQgPSAiLjFmIiksDQogICAgaG92ZXJsYWJlbCA9IGxpc3QoYWxpZ24gPSAibGVmdCIpLA0KICAgIG1hcmdpbiA9IGxpc3QodCA9IDEyNSkNCiAgKSAlPiUNCiAgZXN0aWxvX3Bsb3RseSgpDQoNCmcNCmBgYA0KDQpFbiBsYXMgcG9ibGFjaW9uZXMgbWVkaWEgeSBhbHRhIG5vIHNlIGlkZW50aWZpY2FuIHZhbG9yZXMgYXTDrXBpY29zIHF1ZSBwdWVkYW4gYWZlY3RhciBzaWduaWZpY2F0aXZhbWVudGUgZWwgYW7DoWxpc2lzIGRlIGNvcnJlbGFjacOzbiBlbnRyZSBlbCB0YW1hw7FvIGRlIGxhIHBvYmxhY2nDs24geSBsYXMgdGFzYXMgZGUgZGVsaXRvLiBTaW4gZW1iYXJnbywgZW4gbGEgcG9ibGFjacOzbiBiYWphIHNlIGlkZW50aWZpY2FuIHZhcmlvcyB2YWxvcmVzIGF0aXBpY29zIGV4dHJlbW9zLCBjb21vIHNlIG11ZXN0cmFuIGVuIGxhIHRhYmxhIGEgY29udGludWFjaW9uLg0KDQpFc3RvcyB2YWxvcmVzIG1lcmVjZW4gZXNwZWNpYWwgYXRlbmNpw7NuLCB5YSBxdWUgcG9kcsOtYW4gaW5mbHVpciBlbiBsb3MgcmVzdWx0YWRvcyBkZWwgYW7DoWxpc2lzIGRlIGNvcnJlbGFjacOzbiB5IGFmZWN0YXIgbGEgaW50ZXJwcmV0YWNpw7NuIGRlIGxhIHJlbGFjacOzbiBlbnRyZSBlbCB0YW1hw7FvIGRlIGxhIHBvYmxhY2nDs24geSBsYXMgdGFzYXMgZGUgZGVsaXRvLg0KDQojIyA0LjMgwr9RdcOpIGRlbGl0b3MgcHJlZG9taW5hbiBlbiBjYWRhIGdydXBvIHkgY2FtYmlhIHN1IHBlc28gZW50cmUgZ3J1cG9zPw0KDQoqKlRhYmxhIGRlIFVQWiBjb24gdmFsb3JlcyBhdMOtcGljb3MgZW4gbGFzIHRhc2FzIGRlIGRlbnVuY2lhcyBwb3IgZGVsaXRvcy4qKg0KDQpgYGB7ciBncmFmaWNvX2JhcnJhc19kZWxpdG9zX3hfc2VnbWVudGFjaW9ufQ0KIyAxLiBFbCBodXJ0byB0b3RhbCB5IHN1IHRhc2EgKHBvciAxMC4wMDAgaGFiLikgc2UgY2FsY3VsYW4gZW4gZWwgY2h1bmsgInByZXBhcmFyX2RhdG9zIg0KDQojIDIuIEZ1bmNpb25lcyBkZSBhcG95byAtLS0tDQpvcmRlbl9kZWxpdG9zIDwtIGMoIkh1cnRvIiwgIkxlc2lvbmVzIiwgIkhvbWljaWRpb3MiLCAiVmlvbGVuY2lhIGludHJhZmFtaWxpYXIiKQ0KDQpyZXN1bWlyIDwtIGZ1bmN0aW9uKGRhdG9zKSB7DQogIGRhdG9zICU+JQ0KICAgIHN1bW1hcmlzZSgNCiAgICAgIHBvYmxhY2lvbiAgPSBzdW0ocG9ibGFjaW9uKSwNCiAgICAgIEh1cnRvICAgICAgPSBzdW0oY2Fzb3NfaHVydG9fdG90YWwpLA0KICAgICAgTGVzaW9uZXMgICA9IHN1bShjYXNvc19sZXNpb25lcyksDQogICAgICBIb21pY2lkaW9zID0gc3VtKGNhc29zX2hvbWljaWRpb3MpLA0KICAgICAgYFZpb2xlbmNpYSBpbnRyYWZhbWlsaWFyYCA9IHN1bShjYXNvc192aW9sZW5jaWFfaW50cmFmYW1pbGlhcikNCiAgICApICU+JQ0KICAgIHBpdm90X2xvbmdlcihhbGxfb2Yob3JkZW5fZGVsaXRvcyksIG5hbWVzX3RvID0gImRlbGl0byIsIHZhbHVlc190byA9ICJjYXNvcyIpICU+JQ0KICAgIG11dGF0ZSh0YXNhID0gY2Fzb3MgLyBwb2JsYWNpb24gKiAxMDAwMCkNCn0NCg0KZXhwYW5kaXIgPC0gZnVuY3Rpb24oZCwgcmFuZ28sIG5fcHRzID0gNDApIHsNCiAgcCA8LSBkW3JlcChzZXFfbGVuKG5yb3coZCkpLCBlYWNoID0gbl9wdHMpLCBdDQogIHAkeCA8LSByZXAoc2VxKDAsIHJhbmdvLCBsZW5ndGgub3V0ID0gbl9wdHMpLCB0aW1lcyA9IG5yb3coZCkpDQogIHANCn0NCg0KIyAzLiBWaXN0YSAxOiBwb2JsYWNpw7NuIGJhamEgY29uIHZzIHNpbiBhdMOtcGljb3MgZXh0cmVtb3MgLS0tLQ0KIyBlc19leHRyZW1vIHlhIHZpZW5lIGNhbGN1bGFkbyBlbiBlbCBib3hwbG90IChRMyArIDMgw5cgSVFSLCBjb24gdGFzYXMgcmVhbGVzIHkgcG9yIHNlZ21lbnRhY2nDs24pDQpiYWphX2NvbiA8LSB1cHogJT4lIGZpbHRlcihncnVwb19wb2JsYWNpb24gPT0gIlBvYmxhY2nDs24gYmFqYSIpDQoNCmF0aXBpY29zIDwtIGJhamFfY29uICU+JQ0KICBmaWx0ZXIoZXNfZXh0cmVtbykgJT4lDQogIGFycmFuZ2UoZGVzYyh0YXNhX3RvdGFsKSkNCg0Kbl9hdGlwICAgPC0gbnJvdyhhdGlwaWNvcykNCmJhamFfc2luIDwtIGJhamFfY29uICU+JSBmaWx0ZXIoISh1cHpfY29kICVpbiUgYXRpcGljb3MkdXB6X2NvZCkpDQoNCmNvbXAgPC0gcmVzdW1pcihiYWphX2NvbikgJT4lDQogIHNlbGVjdChkZWxpdG8sIGNhc29zX2NvbiA9IGNhc29zLCB0YXNhX2NvbiA9IHRhc2EpICU+JQ0KICBsZWZ0X2pvaW4ocmVzdW1pcihiYWphX3NpbikgJT4lIHNlbGVjdChkZWxpdG8sIGNhc29zX3NpbiA9IGNhc29zLCB0YXNhX3NpbiA9IHRhc2EpLCBieSA9ICJkZWxpdG8iKSAlPiUNCiAgbXV0YXRlKA0KICAgIGRpZl9jYXNvcyA9IGlmZWxzZShjYXNvc19jb24gPT0gMCwgMCwgY2Fzb3Nfc2luIC8gY2Fzb3NfY29uIC0gMSksDQogICAgZXNfbWF4ICAgID0gY2Fzb3NfY29uID09IG1heChjYXNvc19jb24pLA0KICAgIGV0aXF1ZXRhICA9IGlmZWxzZShlc19tYXgsIHBhc3RlMChkZWxpdG8sICIgIOKYhSIpLCBkZWxpdG8pLA0KICAgIHRleHRvID0gcGFzdGUwKA0KICAgICAgIjxiPiIsIGRlbGl0bywgIjwvYj4iLA0KICAgICAgIjxicj5Db24gYXTDrXBpY29zIGV4dHJlbW9zOiAiLCBudW0oY2Fzb3NfY29uKSwgIiBjYXNvcyDCtyB0YXNhICIsIG51bSh0YXNhX2NvbiwgYWNjdXJhY3kgPSAwLjEpLA0KICAgICAgIjxicj5TaW4gYXTDrXBpY29zIGV4dHJlbW9zOiAiLCBudW0oY2Fzb3Nfc2luKSwgIiBjYXNvcyDCtyB0YXNhICIsIG51bSh0YXNhX3NpbiwgYWNjdXJhY3kgPSAwLjEpLA0KICAgICAgIjxicj5DYW1iaW8gZW4gY2Fzb3M6ICIsIHBjKGRpZl9jYXNvcykNCiAgICApDQogICkgJT4lDQogIGFycmFuZ2UoZGVzYyhjYXNvc19jb24pKQ0KDQpyYW5nb19iYWphICA8LSBtYXgoY29tcCRjYXNvc19jb24pICogMS4xDQpwdW50b3NfYmFqYSA8LSBleHBhbmRpcihjb21wLCByYW5nb19iYWphKQ0KDQpwcm9tX2NvbiA8LSBtZWFuKGJhamFfY29uJHRhc2FfdG90YWwpOyAgIHByb21fc2luIDwtIG1lYW4oYmFqYV9zaW4kdGFzYV90b3RhbCkNCm1lZF9jb24gIDwtIG1lZGlhbihiYWphX2NvbiR0YXNhX3RvdGFsKTsgbWVkX3NpbiAgPC0gbWVkaWFuKGJhamFfc2luJHRhc2FfdG90YWwpDQpwY3RfcG9iX2V4dCA8LSBzdW0oYXRpcGljb3MkcG9ibGFjaW9uKSAvIHN1bShiYWphX2NvbiRwb2JsYWNpb24pDQoNCiMzLjEgdGFibGEgZGUgYXRpcGljb3MgZXh0cmVtb3MNCg0KdGFibGFfZXh0cmVtb3MgPC0gYXRpcGljb3MgJT4lICAgICAgIyB5YSB2aWVuZSBvcmRlbmFkYSBkZSBtYXlvciBhIG1lbm9yIHRhc2ENCiAgdHJhbnNtdXRlKA0KICAgIGAjYCAgICAgICAgICA9IHJvd19udW1iZXIoKSwNCiAgICBVUFogICAgICAgICAgPSB1cHpfbm9tYnJlLA0KICAgIExvY2FsaWRhZCAgICA9IGxvY2FsaWRhZCwNCiAgICBQb2JsYWNpw7NuICAgID0gbnVtKHBvYmxhY2lvbiksDQogICAgYENhc29zIHRvdGFsZXNgID0gbnVtKGNhc29zX3RvdGFsKSwNCiAgICBgVGFzYSB0b3RhbCAocG9yIDEwLjAwMCBoYWIuKWAgPSBudW0odGFzYV90b3RhbCwgYWNjdXJhY3kgPSAwLjEpLA0KICAgIGBWZWNlcyBsYSBtZWRpYW5hIGRlbCBncnVwb2AgICA9IHBhc3RlMChudW0odGFzYV90b3RhbCAvIG1lZGlhbihiYWphX2NvbiR0YXNhX3RvdGFsKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFjY3VyYWN5ID0gMC4xKSwgIngiKQ0KICApDQoNCnRhYmxhX2Jvbml0YSh0YWJsYV9leHRyZW1vcykNCg0KIyA0LiBWaXN0YXMgMiB5IDM6IHBvYmxhY2nDs24gbWVkaWEgeSBhbHRhIC0tLS0NCnByZXBhcmFyIDwtIGZ1bmN0aW9uKGcpIHsNCiAgcmVzdW1pcih1cHogJT4lIGZpbHRlcihncnVwb19wb2JsYWNpb24gPT0gZykpICU+JQ0KICAgIG11dGF0ZSgNCiAgICAgIHBjdCAgICAgID0gY2Fzb3MgLyBzdW0oY2Fzb3MpLA0KICAgICAgZXNfbWF4ICAgPSBjYXNvcyA9PSBtYXgoY2Fzb3MpLA0KICAgICAgZXRpcXVldGEgPSBpZmVsc2UoZXNfbWF4LCBwYXN0ZTAoZGVsaXRvLCAiICDimIUiKSwgZGVsaXRvKSwNCiAgICAgIHRleHRvID0gcGFzdGUwKCI8Yj4iLCBkZWxpdG8sICI8L2I+IiwNCiAgICAgICAgICAgICAgICAgICAgICI8YnI+Q2Fzb3M6ICIsIG51bShjYXNvcyksICIgKCIsIHBjKHBjdCksICIgZGVsIHRvdGFsKSIsDQogICAgICAgICAgICAgICAgICAgICAiPGJyPlRhc2E6ICIsIG51bSh0YXNhLCBhY2N1cmFjeSA9IDAuMSksICIgcG9yIDEwLjAwMCBoYWIuIikNCiAgICApICU+JQ0KICAgIGFycmFuZ2UoZGVzYyhjYXNvcykpDQp9DQoNCmRfbWVkaWEgPC0gcHJlcGFyYXIoIlBvYmxhY2nDs24gbWVkaWEiKQ0KZF9hbHRhICA8LSBwcmVwYXJhcigiUG9ibGFjacOzbiBhbHRhIikNCg0Kc3RvcGlmbm90KA0KICBzdW0oYmFqYV9jb24kY2Fzb3NfaHVydG9fdG90YWwpID09IGNvbXAkY2Fzb3NfY29uW2NvbXAkZGVsaXRvID09ICJIdXJ0byJdLA0KICBzdW0oYmFqYV9zaW4kY2Fzb3NfaHVydG9fdG90YWwpID09IGNvbXAkY2Fzb3Nfc2luW2NvbXAkZGVsaXRvID09ICJIdXJ0byJdLA0KICBucm93KGJhamFfY29uKSAtIG5yb3coYmFqYV9zaW4pID09IG5fYXRpcCwNCiAgc3VtKGRfbWVkaWEkY2Fzb3MpID09IHN1bSh1cHokY2Fzb3NfdG90YWxbdXB6JGdydXBvX3BvYmxhY2lvbiA9PSAiUG9ibGFjacOzbiBtZWRpYSJdKSwNCiAgc3VtKGRfYWx0YSRjYXNvcykgID09IHN1bSh1cHokY2Fzb3NfdG90YWxbdXB6JGdydXBvX3BvYmxhY2lvbiA9PSAiUG9ibGFjacOzbiBhbHRhIl0pDQopDQoNCnJhbmdvX21lZGlhIDwtIG1heChkX21lZGlhJGNhc29zKSAqIDEuMQ0KcmFuZ29fYWx0YSAgPC0gbWF4KGRfYWx0YSRjYXNvcykgKiAxLjENCg0KcHVudG9zX21lZGlhIDwtIGV4cGFuZGlyKGRfbWVkaWEsIHJhbmdvX21lZGlhKQ0KcHVudG9zX2FsdGEgIDwtIGV4cGFuZGlyKGRfYWx0YSwgcmFuZ29fYWx0YSkNCg0KIyA1LiBDT01FTlRBUklPUyAtLS0tDQojID4+PiBBUVXDjSBFU0NSSUJFUyBUVSBDT01FTlRBUklPIFBBUkEgQ0FEQSBWSVNUQSAocHVlZGUgc2VyIHRhbiBsYXJnbyBjb21vIHF1aWVyYXMpIDw8PA0KIyBTZSBtdWVzdHJhIGNvbXBsZXRvLCBkZSBsYWRvIGEgbGFkbywgc29sbyBjdWFuZG8gZWxpZ2VzIGVzYSB2aXN0YSBlbiBlbCBtZW7Dui4NCiMgRMOpamFsbyAiIiBzaSBubyBxdWllcmVzIGFncmVnYXIgbmFkYS4NCm1pc19jb21lbnRhcmlvcyA8LSBjKA0KICAiUG9ibGFjacOzbiBiYWphOiBjb24gdnMgc2luIGF0w61waWNvcyIgPSAiU2kgc2UgcXVpdGFuIGxvcyA3IGF0w61waWNvcyBleHRyZW1vcyBubyBnZW5lcmEgdW4gY2FtYmlvIGRlIHJhbmtpbmcgZW50cmUgZGVsaXRvcyBlbiBsYSBwb2JsYWNpw7NuIGJhamEsIHBlcm8gbG8gcXVlIHPDrSBjYW1iaWEgc29uIGxhcyB0YXNhcyBkZSBkZWxpdG9zIHkgbG9zIHByb21lZGlvcywgZW4gZG9uZGUgbG9zIGNhc29zIGRlIGh1cnRvIGJhamFuIGRlIHRhc2EgMjg4LjUgYSAyNjEuNSBwb3IgY2FkYSAxMC4wMDAgaGFiaXRhbnRlcywgZXN0ZSBjYW1iaW8gZW4gbGEgdGFzYSBwYXNhIGRlYmlkbyBxdWUgZXN0YXMgVVBaIHRpZW5lbiBtdXkgcG9jYXMgcGVyc29uYXMgeSBtdWNoYXMgbcOhcyBkZW51bmNpYXMsIGVuIGxvcyBvdHJvcyBkZWxpdG9zLCBzZSBwcmVzZW50YSB2YXJpYWNpw7NuIHBlcm8gbm8gZXMgdGFuIGNvbnNpZGVyYWJsZSBjb21vIGVsIHJlc3VsdGFkbyBkZSBIdXJ0byIsDQogICJQb2JsYWNpw7NuIG1lZGlhIiAgICAgICAgICAgICAgICAgICAgID0gIkZyZW50ZSBhIHBvYmxhY2nDs24gYmFqYSwgbGEgdGFzYSBkZSBodXJ0byBiYWphIGEgbWVub3MgZGUgbGEgbWl0YWQgKDI4OCw1ID4gMTIyLDQpLiBBZGVtw6FzLCBhcXXDrSBubyBoYXkgdmFsb3JlcyBhdMOtcGljb3M6IGxhcyBVUFogc2UgcGFyZWNlbiBiYXN0YW50ZSBlbnRyZSBzw60sIGNvbiB0YXNhcyB0b3RhbGVzIHF1ZSB2YW4gZGUgNzIgYSAzMTIuIiwNCiAgIlBvYmxhY2nDs24gYWx0YSIgICAgICAgICAgICAgICAgICAgICAgPSAiTG8gcXVlIHPDrSBjYW1iaWEgZXMgZWwgcGVzbyBkZSBsYSB2aW9sZW5jaWEgaW50cmFmYW1pbGlhcjogcGFzYSBkZSA2LDYgJSBlbiBwb2JsYWNpw7NuIGJhamEgYSAxOSw5ICUgYXF1w60sIHkgc3UgdGFzYSAyMyw5IGVzIGxhIG3DoXMgYWx0YSBkZSBsb3MgdHJlcyBncnVwb3MsIHNpZW5kb3Mgc29sbyA1IFVQWi4iDQopDQoNCm1vc3RyYXJfYXV0b21hdGljbyA8LSBUUlVFICAgIyBGQUxTRSA9IG11ZXN0cmEgc29sbyB0dXMgY29tZW50YXJpb3MNCg0KdmlzdGFzIDwtIG5hbWVzKG1pc19jb21lbnRhcmlvcykNCg0KIyB2aXN0YSAxDQp0b3BfYiA8LSBjb21wICU+JSBmaWx0ZXIoZXNfbWF4KQ0Kbm9tYnJlc19hdGlwIDwtIHBhc3RlMChhdGlwaWNvcyR1cHpfbm9tYnJlLCAiICgiLCBudW0oYXRpcGljb3MkcG9ibGFjaW9uKSwgIiBoYWIuKSIsIGNvbGxhcHNlID0gIiwgIikNCnBjdF90b3BfY29uICA8LSB0b3BfYiRjYXNvc19jb24gLyBzdW0oY29tcCRjYXNvc19jb24pDQpwY3RfdG9wX3NpbiAgPC0gdG9wX2IkY2Fzb3Nfc2luIC8gc3VtKGNvbXAkY2Fzb3Nfc2luKQ0KDQpjb21lbnRhcmlvX2JhamEgPC0gYygNCiAgaWYgKG1vc3RyYXJfYXV0b21hdGljbykgYygNCiAgICBwYXN0ZTAoIjxiPkF0w61waWNvcyBleHRyZW1vcyByZXRpcmFkb3MgKCIsIG5fYXRpcCwgIik6PC9iPiAiLCBub21icmVzX2F0aXAsDQogICAgICAgICAgICIuIEp1bnRvcyB0aWVuZW4gc29sbyBlbCAiLCBwYyhwY3RfcG9iX2V4dCksICIgZGUgbGEgcG9ibGFjacOzbiBkZWwgZ3J1cG8uIiksDQogICAgcGFzdGUwKCI8Yj5FZmVjdG8gZW4gbG9zIHRvdGFsZXM6PC9iPiBlbCAiLCB0b2xvd2VyKHRvcF9iJGRlbGl0byksICIgcGFzYSBkZSAiLA0KICAgICAgICAgICBudW0odG9wX2IkY2Fzb3NfY29uKSwgIiBhICIsIG51bSh0b3BfYiRjYXNvc19zaW4pLCAiIGNhc29zICgiLCBwYyh0b3BfYiRkaWZfY2Fzb3MpLA0KICAgICAgICAgICAiKSB5IHN1IHRhc2EgZGUgIiwgbnVtKHRvcF9iJHRhc2FfY29uLCBhY2N1cmFjeSA9IDAuMSksICIgYSAiLCBudW0odG9wX2IkdGFzYV9zaW4sIGFjY3VyYWN5ID0gMC4xKSwgIi4iKSwNCiAgICBwYXN0ZTAoIjxiPkxvIHF1ZSBubyBjYW1iaWE6PC9iPiBlbCAiLCB0b2xvd2VyKHRvcF9iJGRlbGl0byksICIgc2lndWUgc2llbmRvIGVsIGRlbGl0byBtw6FzIGZyZWN1ZW50ZSAoIiwNCiAgICAgICAgICAgcGMocGN0X3RvcF9jb24pLCAiIGRlbCB0b3RhbCBjb24gYXTDrXBpY29zIHkgIiwgcGMocGN0X3RvcF9zaW4pLCAiIHNpbiBlbGxvcykuIiksDQogICAgcGFzdGUwKCI8Yj5Qcm9tZWRpbyB2cyBtZWRpYW5hIGRlIGxhIHRhc2EgcG9yIFVQWjo8L2I+IGVsIHByb21lZGlvIHBhc2EgZGUgIiwgbnVtKHByb21fY29uLCBhY2N1cmFjeSA9IDAuMSksDQogICAgICAgICAgICIgYSAiLCBudW0ocHJvbV9zaW4sIGFjY3VyYWN5ID0gMC4xKSwgIiAoIiwgcGMocHJvbV9zaW4gLyBwcm9tX2NvbiAtIDEpLA0KICAgICAgICAgICAiKSwgbWllbnRyYXMgbGEgbWVkaWFuYSBwYXNhIGRlICIsIG51bShtZWRfY29uLCBhY2N1cmFjeSA9IDAuMSksICIgYSAiLA0KICAgICAgICAgICBudW0obWVkX3NpbiwgYWNjdXJhY3kgPSAwLjEpLCAiICgiLCBwYyhtZWRfc2luIC8gbWVkX2NvbiAtIDEpLCAiKS4iKQ0KICApLA0KICBpZiAobnpjaGFyKG1pc19jb21lbnRhcmlvc1tbdmlzdGFzWzFdXV0pKSBwYXN0ZTAoIjxiPkNvbWVudGFyaW86PC9iPiAiLCBtaXNfY29tZW50YXJpb3NbW3Zpc3Rhc1sxXV1dKQ0KKQ0KDQojIHZpc3RhcyAyIHkgMw0KY29tZW50YXJpb19ncnVwbyA8LSBmdW5jdGlvbihnLCBkKSB7DQogIHRvcCA8LSBkWzEsIF07IHNlZyA8LSBkWzIsIF07IHVsdCA8LSBkW25yb3coZCksIF0NCiAgbl91cHogPC0gc3VtKHVweiRncnVwb19wb2JsYWNpb24gPT0gZykNCiAgYygNCiAgICBpZiAobW9zdHJhcl9hdXRvbWF0aWNvKSBjKA0KICAgICAgcGFzdGUwKCI8Yj5Db250ZXh0bzo8L2I+ICIsIG5fdXB6LCAiIFVQWiB5ICIsIG51bSh0b3AkcG9ibGFjaW9uKSwgIiBoYWJpdGFudGVzIGVuIGVzdGUgZ3J1cG8uIiksDQogICAgICBwYXN0ZTAoIjxiPk3DoXMgZnJlY3VlbnRlOjwvYj4gIiwgdG9sb3dlcih0b3AkZGVsaXRvKSwgIiwgY29uICIsIG51bSh0b3AkY2Fzb3MpLA0KICAgICAgICAgICAgICIgY2Fzb3MgKCIsIHBjKHRvcCRwY3QpLCAiIGRlbCB0b3RhbCkgeSB1bmEgdGFzYSBkZSAiLCBudW0odG9wJHRhc2EsIGFjY3VyYWN5ID0gMC4xKSwNCiAgICAgICAgICAgICAiIHBvciBjYWRhIDEwLjAwMCBoYWJpdGFudGVzLiIpLA0KICAgICAgcGFzdGUwKCI8Yj5MZSBzaWd1ZTo8L2I+ICIsIHRvbG93ZXIoc2VnJGRlbGl0byksICIsIGNvbiAiLCBudW0oc2VnJGNhc29zKSwNCiAgICAgICAgICAgICAiIGNhc29zICgiLCBwYyhzZWckcGN0KSwgIikuIiksDQogICAgICBwYXN0ZTAoIjxiPk1lbm9zIGZyZWN1ZW50ZTo8L2I+ICIsIHRvbG93ZXIodWx0JGRlbGl0byksICIsIGNvbiAiLCBudW0odWx0JGNhc29zKSwNCiAgICAgICAgICAgICAiIGNhc29zICgiLCBwYyh1bHQkcGN0KSwgIikuIikNCiAgICApLA0KICAgIGlmIChuemNoYXIobWlzX2NvbWVudGFyaW9zW1tnXV0pKSBwYXN0ZTAoIjxiPkNvbWVudGFyaW86PC9iPiAiLCBtaXNfY29tZW50YXJpb3NbW2ddXSkNCiAgKQ0KfQ0KDQpjb21lbnRhcmlvcyA8LSBsaXN0KGNvbWVudGFyaW9fYmFqYSwNCiAgICAgICAgICAgICAgICAgICAgY29tZW50YXJpb19ncnVwbygiUG9ibGFjacOzbiBtZWRpYSIsIGRfbWVkaWEpLA0KICAgICAgICAgICAgICAgICAgICBjb21lbnRhcmlvX2dydXBvKCJQb2JsYWNpw7NuIGFsdGEiLCAgZF9hbHRhKSkNCg0KIyA2LiBHcsOhZmljbyAtLS0tDQpvcCA8LSBmdW5jdGlvbihkKSBpZmVsc2UoZCRlc19tYXgsIDEsIDAuNDUpICAgIyBlbCBkZWxpdG8gbcOhcyBmcmVjdWVudGUgcXVlZGEgcmVzYWx0YWRvDQppbnZpc2libGVfbWFya2VyIDwtIGxpc3QoY29sb3IgPSAicmdiYSgwLDAsMCwwKSIsIHNpemUgPSAxMikNCg0KIyB2aXN0YXMgMiB5IDM6IGJhcnJhcyArIHB1bnRvcyBpbnZpc2libGVzIHBhcmEgZWwgY3VhZHJvIGRlbCBtb3VzZSAobWlzbWEgZXN0cnVjdHVyYSwgY2FtYmlhbiBkYXRvcyB5IGNvbG9yKQ0KYmFycmFzX3Zpc3RhIDwtIGZ1bmN0aW9uKGZpZywgZCwgcHVudG9zLCBub21icmUsIGNvbG9yKSB7DQogIGZpZyAlPiUNCiAgICBhZGRfdHJhY2UoZGF0YSA9IGQsIHR5cGUgPSAiYmFyIiwgb3JpZW50YXRpb24gPSAiaCIsIG5hbWUgPSBub21icmUsDQogICAgICAgICAgICAgIHggPSB+Y2Fzb3MsIHkgPSB+ZXRpcXVldGEsDQogICAgICAgICAgICAgIG1hcmtlciA9IGxpc3QoY29sb3IgPSBjb2xvciwgb3BhY2l0eSA9IG9wKGQpKSwNCiAgICAgICAgICAgICAgaG92ZXJpbmZvID0gInNraXAiLCB2aXNpYmxlID0gRkFMU0UsIHNob3dsZWdlbmQgPSBGQUxTRSkgJT4lDQogICAgYWRkX3RyYWNlKGRhdGEgPSBwdW50b3MsIHR5cGUgPSAic2NhdHRlciIsIG1vZGUgPSAibWFya2VycyIsDQogICAgICAgICAgICAgIHggPSB+eCwgeSA9IH5ldGlxdWV0YSwgbWFya2VyID0gaW52aXNpYmxlX21hcmtlciwNCiAgICAgICAgICAgICAgaG92ZXJ0ZXh0ID0gfnRleHRvLCBob3ZlcmluZm8gPSAidGV4dCIsIHZpc2libGUgPSBGQUxTRSwgc2hvd2xlZ2VuZCA9IEZBTFNFKQ0KfQ0KDQpmaWcgPC0gcGxvdF9seSgpICU+JQ0KICAjIC0tLSB2aXN0YSAxICh0cmF6YXMgMS0zKQ0KICBhZGRfdHJhY2UoZGF0YSA9IGNvbXAsIHR5cGUgPSAiYmFyIiwgb3JpZW50YXRpb24gPSAiaCIsIG5hbWUgPSAiQ29uIGF0w61waWNvcyBleHRyZW1vcyIsDQogICAgICAgICAgICB4ID0gfmNhc29zX2NvbiwgeSA9IH5ldGlxdWV0YSwNCiAgICAgICAgICAgIG1hcmtlciA9IGxpc3QoY29sb3IgPSBtYWdlbnRhLCBvcGFjaXR5ID0gb3AoY29tcCkpLA0KICAgICAgICAgICAgaG92ZXJpbmZvID0gInNraXAiLCB2aXNpYmxlID0gVFJVRSwgc2hvd2xlZ2VuZCA9IFRSVUUpICU+JQ0KICBhZGRfdHJhY2UoZGF0YSA9IGNvbXAsIHR5cGUgPSAiYmFyIiwgb3JpZW50YXRpb24gPSAiaCIsIG5hbWUgPSAiU2luIGF0w61waWNvcyBleHRyZW1vcyIsDQogICAgICAgICAgICB4ID0gfmNhc29zX3NpbiwgeSA9IH5ldGlxdWV0YSwNCiAgICAgICAgICAgIG1hcmtlciA9IGxpc3QoY29sb3IgPSBtb3JhZG8sIG9wYWNpdHkgPSBvcChjb21wKSksDQogICAgICAgICAgICBob3ZlcmluZm8gPSAic2tpcCIsIHZpc2libGUgPSBUUlVFLCBzaG93bGVnZW5kID0gVFJVRSkgJT4lDQogIGFkZF90cmFjZShkYXRhID0gcHVudG9zX2JhamEsIHR5cGUgPSAic2NhdHRlciIsIG1vZGUgPSAibWFya2VycyIsDQogICAgICAgICAgICB4ID0gfngsIHkgPSB+ZXRpcXVldGEsIG1hcmtlciA9IGludmlzaWJsZV9tYXJrZXIsDQogICAgICAgICAgICBob3ZlcnRleHQgPSB+dGV4dG8sIGhvdmVyaW5mbyA9ICJ0ZXh0IiwgdmlzaWJsZSA9IFRSVUUsIHNob3dsZWdlbmQgPSBGQUxTRSkgJT4lDQogICMgLS0tIHZpc3RhIDI6IHBvYmxhY2nDs24gbWVkaWEgKHRyYXphcyA0LTUpIHkgdmlzdGEgMzogcG9ibGFjacOzbiBhbHRhICh0cmF6YXMgNi03KQ0KICBiYXJyYXNfdmlzdGEoZF9tZWRpYSwgcHVudG9zX21lZGlhLCAiUG9ibGFjacOzbiBtZWRpYSIsIHR1cnF1ZXNhKSAlPiUNCiAgYmFycmFzX3Zpc3RhKGRfYWx0YSwgIHB1bnRvc19hbHRhLCAgIlBvYmxhY2nDs24gYWx0YSIsICBtb3JhZG8pDQoNCnRpdHVsb3MgPC0gYyhwYXN0ZTAoIkRlbGl0b3MgUG9ibGFjacOzbiBiYWphOiBjb24geSBzaW4gbG9zICIsIG5fYXRpcCwgIiB2YWxvcmVzIGF0w61waWNvcyBleHRyZW1vcywgSHVydG86IERlbGl0byBxdWUgcHJlZG9taW5hIGNvbiB5IHNpbiBsb3MgIiwgbl9hdGlwLCAiIGF0w61waWNvcyBleHRyZW1vcy4iKSwNCiAgICAgICAgICAgICAiRGVsaXRvcyBlbiBwb2JsYWNpw7NuIG1lZGlhLCBIdXJ0byBjb250aWVuZSBlbCA4MCUgZGUgbG9zIGNhc29zLiIsDQogICAgICAgICAgICAgIkRlbGl0b3MgZW4gcG9ibGFjacOzbiBhbHRhLCBIdXJ0byBwcmVkb21pbmEsIHBlcm8gdmlvbGVuY2lhIGludHJhZmFtaWxpYXIgcHJlc2VudGEgMjAlIGRlIGxvcyBjYXNvcyBzaWVuZG8gc29sbyA1IFVQWi4iKQ0Kc3VidGl0dWxvcyA8LSBjKCJDYW50aWRhZCBkZSBjYXNvcyDCtyDimIUgeSBjb2xvciBmdWVydGUgPSBlbCBkZWxpdG8gcXVlIG3DoXMgc2UgcHJlc2VudGEgwrcgcGFzYSBlbCBjdXJzb3IgcGFyYSB2ZXIgZWwgZGV0YWxsZSIsDQogICAgICAgICAgICAgICAgIkNhbnRpZGFkIGRlIGNhc29zIMK3IOKYhSB5IGNvbG9yIGZ1ZXJ0ZSA9IGVsIGRlbGl0byBxdWUgbcOhcyBzZSBwcmVzZW50YSDCtyBwYXNhIGVsIGN1cnNvciBwYXJhIHZlciBjYXNvcyB5IHRhc2EiLA0KICAgICAgICAgICAgICAgICJDYW50aWRhZCBkZSBjYXNvcyDCtyDimIUgeSBjb2xvciBmdWVydGUgPSBlbCBkZWxpdG8gcXVlIG3DoXMgc2UgcHJlc2VudGEgwrcgcGFzYSBlbCBjdXJzb3IgcGFyYSB2ZXIgY2Fzb3MgeSB0YXNhIikNCnJhbmdvcyAgPC0gYyhyYW5nb19iYWphLCByYW5nb19tZWRpYSwgcmFuZ29fYWx0YSkNCm9yZGVuZXMgPC0gbGlzdChjb21wJGV0aXF1ZXRhLCBkX21lZGlhJGV0aXF1ZXRhLCBkX2FsdGEkZXRpcXVldGEpDQoNCnRpdHVsbyA8LSBmdW5jdGlvbihpKSBwYXN0ZTAoIjxiPiIsIHBhcnRpcl90aXR1bG8odGl0dWxvc1tpXSksICI8L2I+PGJyPjxzdXA+Iiwgc3VidGl0dWxvc1tpXSwgIjwvc3VwPiIpDQoNCiMgNyB0cmF6YXM6IDEtMyB2aXN0YSAxLCA0LTUgdmlzdGEgMiwgNi03IHZpc3RhIDMNCnRyYXphc19wb3JfdmlzdGEgPC0gYygzLCAyLCAyKQ0KdmlzaWJpbGlkYWQgPC0gZnVuY3Rpb24oaSkgcmVwKHNlcV9hbG9uZyh2aXN0YXMpID09IGksIHRpbWVzID0gdHJhemFzX3Bvcl92aXN0YSkNCg0KYm90b25lcyA8LSBsYXBwbHkoc2VxX2Fsb25nKHZpc3RhcyksIGZ1bmN0aW9uKGkpIHsNCiAgbGlzdCgNCiAgICBtZXRob2QgPSAidXBkYXRlIiwNCiAgICBsYWJlbCAgPSB2aXN0YXNbaV0sDQogICAgYXJncyAgID0gbGlzdCgNCiAgICAgIGxpc3QodmlzaWJsZSA9IHZpc2liaWxpZGFkKGkpKSwNCiAgICAgIGxpc3QoInRpdGxlLnRleHQiICAgICAgICAgID0gdGl0dWxvKGkpLA0KICAgICAgICAgICAieGF4aXMucmFuZ2UiICAgICAgICAgPSBsaXN0KDAsIHJhbmdvc1tpXSksDQogICAgICAgICAgICJ5YXhpcy5jYXRlZ29yeWFycmF5IiA9IHJldihvcmRlbmVzW1tpXV0pKQ0KICAgICkNCiAgKQ0KfSkNCg0KZmlnIDwtIGZpZyAlPiUNCiAgbGF5b3V0KA0KICAgIGhlaWdodCA9IDUyMCwNCiAgICB0aXRsZSAgPSB0aXR1bG9fcGxvdGx5KHRpdHVsbygxKSksDQogICAgeGF4aXMgID0gbGlzdCh0aXRsZSA9ICJDYW50aWRhZCBkZSBjYXNvcyIsIHJhbmdlID0gYygwLCByYW5nb3NbMV0pLA0KICAgICAgICAgICAgICAgICAgZ3JpZGNvbG9yID0gIiNFRUVCRjAiLCB6ZXJvbGluZSA9IEZBTFNFKSwNCiAgICB5YXhpcyAgPSBsaXN0KHRpdGxlID0gIiIsIGNhdGVnb3J5b3JkZXIgPSAiYXJyYXkiLA0KICAgICAgICAgICAgICAgICAgY2F0ZWdvcnlhcnJheSA9IHJldihvcmRlbmVzW1sxXV0pLCB0aWNrZm9udCA9IGxpc3Qoc2l6ZSA9IDEzKSksDQogICAgYmFybW9kZSA9ICJncm91cCIsDQogICAgYmFyZ2FwICA9IDAuMywNCiAgICBob3Zlcm1vZGUgICAgID0gImNsb3Nlc3QiLA0KICAgIGhvdmVyZGlzdGFuY2UgPSA0MCwNCiAgICBob3ZlcmxhYmVsID0gaG92ZXJfcGxvdGx5LA0KICAgIHVwZGF0ZW1lbnVzID0gbGlzdChtZW51X3Bsb3RseShib3RvbmVzKSksDQogICAgbGVnZW5kID0gbGlzdChvcmllbnRhdGlvbiA9ICJoIiwgeCA9IDEsIHhhbmNob3IgPSAicmlnaHQiLCB5ID0gMS4wNCwgeWFuY2hvciA9ICJib3R0b20iKSwNCiAgICBtYXJnaW4gPSBsaXN0KHQgPSAxNDAsIGIgPSA3MCwgbCA9IDEwLCByID0gMzApLA0KICAgIHNob3dsZWdlbmQgPSBUUlVFDQogICkgJT4lDQogIGVzdGlsb19wbG90bHkoKQ0KDQojIDcuIENhamEgZGUgY29tZW50YXJpb3MgZGUgbGFkbyBhIGxhZG8gKGRlYmFqbyBkZWwgZ3LDoWZpY28pIC0tLS0NCiMgU2UgY2FtYmlhIHNvbGEgc2Vnw7puIGxhIHNlZ21lbnRhY2nDs24gZWxlZ2lkYSBlbiBlbCBtZW7DuiAoanNfY29tZW50YXJpb3MgZXN0w6EgZW4gZWwgY2h1bmsgImVzdGlsbyIpDQpmaWcgPC0gaHRtbHdpZGdldHM6Om9uUmVuZGVyKA0KICBmaWcsIGpzX2NvbWVudGFyaW9zLA0KICBkYXRhID0gbGlzdChjb21lbnRhcmlvcyA9IGNvbWVudGFyaW9zLA0KICAgICAgICAgICAgICBpbmljaW9zID0gYygwLCBjdW1zdW0odHJhemFzX3Bvcl92aXN0YSlbLWxlbmd0aCh0cmF6YXNfcG9yX3Zpc3RhKV0pKSAgICMgdHJhemEgaW5pY2lhbCBkZSBjYWRhIHZpc3RhDQopDQoNCmZpZw0KYGBgDQoNCiMjIDQuNCDCv0PDs21vIHNlIHJlbGFjaW9uYSBsYSBjb21wb3NpY2nDs24gcG9yIGVkYWQgY29uIGxhcyB0YXNhcyBkZSBkZWxpdG8geSBlc2EgcmVsYWNpw7NuIGNhbWJpYSBzZWfDum4gZWwgZ3J1cG8gcG9ibGFjaW9uYWw/DQoNCg0KYGBge3IgZ3JhZmljb19jb3JyZWxhY2lvbl9ncnVwb3NfZWRhZGVzX3ZzX2RlbGl0b3NfeF9zZWdtZW50YWNpb25fcG9ibGFjaW9uYWx9DQojIDEuIFZhcmlhYmxlczogZ3J1cG9zIGRlIGVkYWQgeSBkZWxpdG9zIChodXJ0byB0b3RhbCB5IHBjdF8zMF81OSBzZSBjcmVhbiBlbiBlbCBjaHVuayAicHJlcGFyYXJfZGF0b3MiKSAtLS0tDQoNCmVkYWRlcyA8LSBjKHBjdF8wXzE0ICAgPSAiMOKAkzE0IGHDsW9zIiwNCiAgICAgICAgICAgIHBjdF8xNV8yOSAgPSAiMTXigJMyOSBhw7FvcyIsDQogICAgICAgICAgICBwY3RfMzBfNTkgID0gIjMw4oCTNTkgYcOxb3MiLA0KICAgICAgICAgICAgcGN0XzYwX21hcyA9ICI2MCBhw7FvcyBvIG3DoXMiKQ0KDQpkZWxpdG9zX2NvcnIgPC0gYyh0YXNhX2h1cnRvX3RvdGFsICAgICAgICAgICAgPSAiSHVydG8iLA0KICAgICAgICAgICAgICAgICAgdGFzYV9sZXNpb25lcyAgICAgICAgICAgICAgID0gIkxlc2lvbmVzIiwNCiAgICAgICAgICAgICAgICAgIHRhc2FfaG9taWNpZGlvcyAgICAgICAgICAgICA9ICJIb21pY2lkaW9zIiwNCiAgICAgICAgICAgICAgICAgIHRhc2FfdmlvbGVuY2lhX2ludHJhZmFtaWxpYXIgPSAiVmlvbGVuY2lhIGludHJhZmFtaWxpYXIiKQ0KDQojIDIuIENvcnJlbGFjacOzbiBkZSBTcGVhcm1hbiBwb3Igc2VnbWVudGFjacOzbiAtLS0tDQpjb3Jfc3BlYXJtYW4gPC0gZnVuY3Rpb24oeCwgeSkgew0KICByIDwtIHRyeUNhdGNoKHN1cHByZXNzV2FybmluZ3MoY29yLnRlc3QoeCwgeSwgbWV0aG9kID0gInNwZWFybWFuIiwgZXhhY3QgPSBGQUxTRSkpLA0KICAgICAgICAgICAgICAgIGVycm9yID0gZnVuY3Rpb24oZSkgTlVMTCkNCiAgaWYgKGlzLm51bGwocikpIGMocmhvID0gTkFfcmVhbF8sIHAgPSBOQV9yZWFsXykNCiAgZWxzZSBjKHJobyA9IHVubmFtZShyJGVzdGltYXRlKSwgcCA9IHIkcC52YWx1ZSkNCn0NCg0KZnVlcnphIDwtIGZ1bmN0aW9uKHIpIHsNCiAgYSA8LSBhYnMocikNCiAgaWZlbHNlKGlzLm5hKHIpLCAic2luIGRhdG8iLA0KICBpZmVsc2UoYSA8IDAuMiwgIm11eSBkw6liaWwiLA0KICBpZmVsc2UoYSA8IDAuNCwgImTDqWJpbCIsDQogIGlmZWxzZShhIDwgMC42LCAibW9kZXJhZGEiLA0KICBpZmVsc2UoYSA8IDAuOCwgImZ1ZXJ0ZSIsICJtdXkgZnVlcnRlIikpKSkpDQp9DQoNCmNvcnJlbGFjaW9uZXMgPC0gZXhwYW5kX2dyaWQoZ3J1cG8gPSBncnVwb3MsIHZkID0gbmFtZXMoZGVsaXRvc19jb3JyKSwgdmUgPSBuYW1lcyhlZGFkZXMpKSAlPiUNCiAgbXV0YXRlKA0KICAgIHIgICAgICA9IHBtYXAobGlzdChncnVwbywgdmQsIHZlKSwgZnVuY3Rpb24oZywgdmQsIHZlKSB7DQogICAgICAgICAgICAgICBzIDwtIHVwelt1cHokZ3J1cG9fcG9ibGFjaW9uID09IGcsIF0NCiAgICAgICAgICAgICAgIGNvcl9zcGVhcm1hbihzW1t2ZV1dLCBzW1t2ZF1dKQ0KICAgICAgICAgICAgIH0pLA0KICAgIHJobyAgICA9IG1hcF9kYmwociwgfiAueFtbInJobyJdXSksDQogICAgcCAgICAgID0gbWFwX2RibChyLCB+IC54W1sicCJdXSksDQogICAgbiAgICAgID0gbWFwX2ludChncnVwbywgfiBzdW0odXB6JGdydXBvX3BvYmxhY2lvbiA9PSAueCkpLA0KICAgIGRlbGl0byA9IHVubmFtZShkZWxpdG9zX2NvcnJbdmRdKSwNCiAgICBlZGFkICAgPSB1bm5hbWUoZWRhZGVzW3ZlXSkNCiAgKSAlPiUNCiAgc2VsZWN0KGdydXBvLCBkZWxpdG8sIGVkYWQsIHJobywgcCwgbikgJT4lDQogIG11dGF0ZSgNCiAgICBzaWduaWYgPSAhaXMubmEocCkgJiBwIDwgMC4wNSwNCiAgICBldGlxdWV0YV9jZWxkYSA9IGNhc2Vfd2hlbigNCiAgICAgIGlzLm5hKHJobykgfiAiIiwNCiAgICAgIHNpZ25pZiAgICAgfiBwYXN0ZTAoIjxiPiIsIG51bShyaG8sIGFjY3VyYWN5ID0gMC4wMSksICIqPC9iPiIpLA0KICAgICAgVFJVRSAgICAgICB+IG51bShyaG8sIGFjY3VyYWN5ID0gMC4wMSkNCiAgICApLA0KICAgIHRleHRvID0gcGFzdGUwKA0KICAgICAgIjxiPiIsIGRlbGl0bywgIiDCtyAiLCBlZGFkLCAiPC9iPiIsDQogICAgICAiPGJyPkNvcnJlbGFjacOzbiAoU3BlYXJtYW4pOiAiLCBudW0ocmhvLCBhY2N1cmFjeSA9IDAuMDEpLA0KICAgICAgIjxicj5SZWxhY2nDs24gIiwgZnVlcnphKHJobyksIGlmZWxzZShyaG8gPj0gMCwgIiBwb3NpdGl2YSIsICIgbmVnYXRpdmEiKSwNCiAgICAgICI8YnI+cC12YWxvcjogIiwgaWZlbHNlKHAgPCAwLjAwMSwgIjwgMCwwMDEiLCBudW0ocCwgYWNjdXJhY3kgPSAwLjAwMSkpLA0KICAgICAgIjxicj4iLCBpZmVsc2Uoc2lnbmlmLCAiRXN0YWTDrXN0aWNhbWVudGUgc2lnbmlmaWNhdGl2YSIsICJObyBzaWduaWZpY2F0aXZhIiksDQogICAgICAiPGJyPlVQWiBhbmFsaXphZGFzOiAiLCBuDQogICAgKQ0KICApDQoNCiMgMy4gQ09NRU5UQVJJT1MgLS0tLQ0KIyA+Pj4gQVFVw40gRVNDUklCRVMgVFUgQ09NRU5UQVJJTyBQQVJBIENBREEgUE9CTEFDScOTTiAocHVlZGUgc2VyIHRhbiBsYXJnbyBjb21vIHF1aWVyYXMpIDw8PA0KIyBTZSBtdWVzdHJhIGNvbXBsZXRvLCBkZSBsYWRvIGEgbGFkbywgc29sbyBjdWFuZG8gZWxpZ2VzIGVzYSBwb2JsYWNpw7NuIGVuIGVsIG1lbsO6Lg0KIyBEw6lqYWxvICIiIHNpIG5vIHF1aWVyZXMgYWdyZWdhciBuYWRhLg0KbWlzX2NvbWVudGFyaW9zIDwtIGMoDQogICJQb2JsYWNpw7NuIGJhamEiICA9ICJFbCBodXJ0byBlcyBtZW5vciBkb25kZSBoYXkgbcOhcyBuacOxb3MgeSBuacOxYXMsIG1heW9yIGRvbmRlIHByZWRvbWluYSBsYSBwb2JsYWNpw7NuIGRlIDMw4oCTNTkgYcOxb3MuIExhIHZpb2xlbmNpYSBpbnRyYWZhbWlsaWFyIHkgbGVzaW9uZXMgc3ViZW4gY29uIG3DoXMgcG9ibGFjacOzbiBkZSAxNeKAkzI5IGHDsW9zIHkgYmFqYSBjb24gbcOhcyBwb2JsYWNpw7NuIGRlIDMw4oCTNTkuLCBlc3RhcyBhc29jaWFjaW9uZXMgc29uIGdlbmVyYWxlcyBkZSBsYXMgVVBaIGVuIGVzdGUgZ3J1cG8gcG9ibGFjaW9uYWwsIHBlcm8gbm8gYXNlZ3VyYSBxdWUgbGEgZWRhZCBnZW5lcmUgZWwgYXVtZW50byBlbiBsYXMgZGVudW5jaWFzLiIsDQogICJQb2JsYWNpw7NuIG1lZGlhIiA9ICJlcyBkb25kZSBzZSBnZW5lcmFuIGxhcyByZWxhY2lvbmVzIG1hcyBmdWVydGVzLCBkb25kZSBzaSBhdW1lbnRhbiBsYXMgcGVyc29uYXMgZGUgNjAgYcOxb3MsIGxhcyBkZW51bmNpYXMgZW4gaHVydG8gYXVtZW50YW4sIHBvciBlbCBjb250cmFyaW8gbG9zIGhvbWljaWRpb3MgeSBsYSB2aW9sZW5jaWEgaW50cmFmYW1pbGlhciBiYWphIGVuIGxvcyBncnVwb3MgZGUgaWd1YWwgbyBtYXMgZGUgMzAgYcOxb3MsIGVuIGNvbnRyYXBhcnRlLCBhdW1lbnRhIGVuIGxvcyBqb3ZlbmVzIGVzdG9zIGRvcyBkZWxpdG9zLiBpbXBvcnRhbnRlIGFjbGFyYXIgcXVlIFJlbGFjacOzbiBubyBlcyBjYXVzYWxpZGFkIiwNCiAgIlBvYmxhY2nDs24gYWx0YSIgID0gIlNvbG8gdGllbmUgNSBVUFosIHkgZXN0byBwdWVkZSBnZW5lcmFyIGNhbWJpb3MgZHJhc3RpY29zIGVzIGxhcyBjb3JyZWxhY2lvbmVzIGNvbiBzb2xvIGF1bWVudGFyIGVuIHVuYSBVUFosIHBvciB0YWwgbW90aXZvIGVzIGltcG9ydGFudGUgaW5kaWNhciBlbiBsb3Mgam92ZW5lcyBlbCBodXJ0byBlcyBiYWpvIHkgbGEgdmlvbGVuY2lhIGludHJhZmFtaWxpYXIgY3VtcGxlIGVsIG1pc21vIHBhdHJvbiBlbiBlbCBncnVwbyBkZSAzMCAtIDU5IGHDsW9zLCBhdW5xdWUgc2UgZGViZSB2YWxpZGFyIG1hcyBhbCBkZXRhbGxlIGVzdG9zIGRhdG9zLCBkZWJpZG8gcXVlIG5vIHNlIGN1ZW50YSBjb24gZGF0b3MgZXN0YWJsZXMgcXVlIHB1ZWRhbiBnZW5lcmFsaXphci4iDQopDQoNCm1vc3RyYXJfYXV0b21hdGljbyA8LSBGQUxTRSAjQUxTRSA9IG11ZXN0cmEgc29sbyB0dXMgY29tZW50YXJpb3MNCg0KY29tZW50YXJpb19jb3JyIDwtIGZ1bmN0aW9uKGcpIHsNCiAgZCA8LSBjb3JyZWxhY2lvbmVzICU+JSBmaWx0ZXIoZ3J1cG8gPT0gZywgIWlzLm5hKHJobykpDQogIG4gPC0gZCRuWzFdDQogIHBvcyA8LSBkICU+JSBzbGljZV9tYXgocmhvLCBuID0gMSwgd2l0aF90aWVzID0gRkFMU0UpDQogIG5lZyA8LSBkICU+JSBzbGljZV9taW4ocmhvLCBuID0gMSwgd2l0aF90aWVzID0gRkFMU0UpDQoNCiAgYygNCiAgICBpZiAobW9zdHJhcl9hdXRvbWF0aWNvKSBjKA0KICAgICAgcGFzdGUwKCI8Yj5Dw7NtbyBsZWVybG86PC9iPiAiLCBuLCAiIFVQWiBlbiBlc3RlIGdydXBvLiBDYWRhIGNlbGRhIGVzIGxhIGNvcnJlbGFjacOzbiBkZSBTcGVhcm1hbiAiLA0KICAgICAgICAgICAgICJlbnRyZSBlbCAlIGRlIHBvYmxhY2nDs24gZGUgZXNlIGdydXBvIGRlIGVkYWQgeSBsYSB0YXNhIGRlbCBkZWxpdG8gKHBvciAxMC4wMDAgaGFiaXRhbnRlcykuICIsDQogICAgICAgICAgICAgIlZhIGRlIC0xIGEgMTsgbGFzIGNlbGRhcyBlbiBuZWdyaXRhIGNvbiAqIHNvbiBlc3RhZMOtc3RpY2FtZW50ZSBzaWduaWZpY2F0aXZhcyAocCA8IDAsMDUpLiIpLA0KICAgICAgaWYgKHBvcyRyaG8gPiAwKSBwYXN0ZTAoIjxiPlJlbGFjacOzbiBwb3NpdGl2YSBtw6FzIGZ1ZXJ0ZTo8L2I+ICIsIHRvbG93ZXIocG9zJGRlbGl0byksICIgY29uICIsIHBvcyRlZGFkLA0KICAgICAgICAgICAgICIgKCIsIG51bShwb3MkcmhvLCBhY2N1cmFjeSA9IDAuMDEpLCAiKTogbGFzIFVQWiBjb24gbcOhcyBwb2JsYWNpw7NuIGRlIGVzYSBlZGFkIHRpZW5kZW4gYSB0ZW5lciB0YXNhcyBtw6FzIGFsdGFzLiIpLA0KICAgICAgaWYgKG5lZyRyaG8gPCAwKSBwYXN0ZTAoIjxiPlJlbGFjacOzbiBuZWdhdGl2YSBtw6FzIGZ1ZXJ0ZTo8L2I+ICIsIHRvbG93ZXIobmVnJGRlbGl0byksICIgY29uICIsIG5lZyRlZGFkLA0KICAgICAgICAgICAgICIgKCIsIG51bShuZWckcmhvLCBhY2N1cmFjeSA9IDAuMDEpLCAiKTogbGFzIFVQWiBjb24gbcOhcyBwb2JsYWNpw7NuIGRlIGVzYSBlZGFkIHRpZW5kZW4gYSB0ZW5lciB0YXNhcyBtw6FzIGJhamFzLiIpLA0KICAgICAgcGFzdGUwKCI8Yj5SZWxhY2lvbmVzIHNpZ25pZmljYXRpdmFzOjwvYj4gIiwgc3VtKGQkc2lnbmlmKSwgIiBkZSAiLCBucm93KGQpLCAiLiIpLA0KICAgICAgaWYgKG4gPCAxMCkgcGFzdGUwKCI8Yj5Pam86PC9iPiBjb24gc29sbyAiLCBuLCAiIFVQWiBsYXMgY29ycmVsYWNpb25lcyBzb24gcG9jbyBjb25maWFibGVzICIsDQogICAgICAgICAgICAgIihiYXN0YSBjb24gcXVlIHVuYSBVUFogY2FtYmllIHBhcmEgcXVlIGVsIHJlc3VsdGFkbyBjYW1iaWUpLiBUw7NtYWxhcyBzb2xvIGNvbW8gcmVmZXJlbmNpYS4iKSwNCiAgICAgIHBhc3RlMCgiPGI+UmVjdWVyZGE6PC9iPiBjb3JyZWxhY2nDs24gbm8gZXMgY2F1c2FsaWRhZDsgbXVlc3RyYSBxdWUgZG9zIGNvc2FzIHN1YmVuIG8gYmFqYW4ganVudGFzLCAiLA0KICAgICAgICAgICAgICJubyBxdWUgdW4gZ3J1cG8gZGUgZWRhZCBjYXVzZSBlbCBkZWxpdG8uIEFkZW3DoXMgbG9zIGN1YXRybyBncnVwb3MgZGUgZWRhZCBzdW1hbiAxMDAgJSwgIiwNCiAgICAgICAgICAgICAicG9yIGVzbyAzMOKAkzU5IGHDsW9zIHN1ZWxlIG1vdmVyc2UgYWwgcmV2w6lzIHF1ZSBsb3MgZGVtw6FzLiIpDQogICAgKSwNCiAgICBpZiAobnpjaGFyKG1pc19jb21lbnRhcmlvc1tbZ11dKSkgcGFzdGUwKCI8Yj5Db21lbnRhcmlvczo8L2I+ICIsIG1pc19jb21lbnRhcmlvc1tbZ11dKQ0KICApDQp9DQoNCmNvbWVudGFyaW9zIDwtIGxhcHBseShncnVwb3MsIGNvbWVudGFyaW9fY29ycikNCg0KIyA0LiBHcsOhZmljbzogbWFwYSBkZSBjYWxvciBjb24gbWVuw7ogLS0tLQ0KYV9tYXRyaXogPC0gZnVuY3Rpb24oZCwgY29sKSB7DQogIGQgJT4lDQogICAgc2VsZWN0KGRlbGl0bywgZWRhZCwgYWxsX29mKGNvbCkpICU+JQ0KICAgIHBpdm90X3dpZGVyKG5hbWVzX2Zyb20gPSBlZGFkLCB2YWx1ZXNfZnJvbSA9IGFsbF9vZihjb2wpKSAlPiUNCiAgICBhcnJhbmdlKG1hdGNoKGRlbGl0bywgZGVsaXRvc19jb3JyKSkgJT4lDQogICAgc2VsZWN0KGFsbF9vZih1bm5hbWUoZWRhZGVzKSkpICU+JQ0KICAgIGFzLm1hdHJpeCgpDQp9DQoNCmZpZyA8LSBwbG90X2x5KCkNCg0KZm9yIChpIGluIHNlcV9hbG9uZyhncnVwb3MpKSB7DQogIGQgPC0gY29ycmVsYWNpb25lcyAlPiUgZmlsdGVyKGdydXBvID09IGdydXBvc1tpXSkNCiAgZmlnIDwtIGZpZyAlPiUgYWRkX3RyYWNlKA0KICAgIHR5cGUgPSAiaGVhdG1hcCIsDQogICAgeCA9IHVubmFtZShlZGFkZXMpLCB5ID0gdW5uYW1lKGRlbGl0b3NfY29yciksDQogICAgeiA9IGFfbWF0cml6KGQsICJyaG8iKSwgdGV4dCA9IGFfbWF0cml6KGQsICJ0ZXh0byIpLA0KICAgIGhvdmVyaW5mbyA9ICJ0ZXh0IiwgeGdhcCA9IDMsIHlnYXAgPSAzLA0KICAgIHptaW4gPSAtMSwgem1heCA9IDEsDQogICAgY29sb3JzY2FsZSA9IGxpc3QoYygwLCB0dXJxdWVzYSksIGMoMC41LCAiI0ZGRkZGRiIpLCBjKDEsIG1hZ2VudGEpKSwNCiAgICBjb2xvcmJhciA9IGxpc3QodGl0bGUgPSAiQ29ycmVsYWNpw7NuPGJyPihTcGVhcm1hbikiLCBsZW4gPSAwLjgsIHRoaWNrbmVzcyA9IDE0LA0KICAgICAgICAgICAgICAgICAgICB0aWNrdmFscyA9IGMoLTEsIC0wLjUsIDAsIDAuNSwgMSkpLA0KICAgIHNob3dzY2FsZSA9IFRSVUUsIHZpc2libGUgPSAoaSA9PSAxKQ0KICApDQp9DQoNCiMgbsO6bWVybyBlc2NyaXRvIGRlbnRybyBkZSBjYWRhIGNlbGRhIChlbiBuZWdyaXRhIHNpIGVzIHNpZ25pZmljYXRpdmEpDQphbm90YWNpb25lc19jZWxkYXMgPC0gZnVuY3Rpb24oZykgew0KICBkIDwtIGNvcnJlbGFjaW9uZXMgJT4lIGZpbHRlcihncnVwbyA9PSBnKQ0KICBsYXBwbHkoc2VxX2xlbihucm93KGQpKSwgZnVuY3Rpb24oaykgbGlzdCgNCiAgICB4ID0gZCRlZGFkW2tdLCB5ID0gZCRkZWxpdG9ba10sIHhyZWYgPSAieCIsIHlyZWYgPSAieSIsDQogICAgdGV4dCA9IGQkZXRpcXVldGFfY2VsZGFba10sIHNob3dhcnJvdyA9IEZBTFNFLA0KICAgIGZvbnQgPSBsaXN0KHNpemUgPSAxNSwNCiAgICAgICAgICAgICAgICBjb2xvciA9IGlmZWxzZSghaXMubmEoZCRyaG9ba10pICYmIGFicyhkJHJob1trXSkgPiAwLjU1LCAid2hpdGUiLCAiIzMzMzMzMyIpKQ0KICApKQ0KfQ0KDQp0aXR1bG8gPC0gZnVuY3Rpb24oaSkgew0KICBwYXN0ZTAoIjxiPiIsIHBhcnRpcl90aXR1bG8oIkVuIHBvYmxhY2lvbmVzIGrDs3ZlbmVzIGxhIHZpb2xlbmNpYSBpbnRyYWZhbWlsaWFyIHRpZW5kZSBhIHN1YmlyIHkgZWwgaHVydG8gYSBiYWphcjsgZXN0byBhcGxpY2EgYSBwb2JsYWNpw7NuIGJhamEgeSBtZWRpYSIpLCAiPC9iPiIsDQogICAgICAgICAiPGJyPjxzdXA+IiwgZ3J1cG9zW2ldLCAiIMK3IENvcnJlbGFjacOzbiBkZSBTcGVhcm1hbiBlbnRyZSBlbCAlIGRlIHBvYmxhY2nDs24gcG9yIGVkYWQgeSBsYSB0YXNhIGRlIGRlbGl0b3MgZGUgY2FkYSBVUFoiLA0KICAgICAgICAgIiDCtyAqID0gc2lnbmlmaWNhdGl2YSAocCA8IDAsMDUpPC9zdXA+IikNCn0NCg0KYm90b25lcyA8LSBsYXBwbHkoc2VxX2Fsb25nKGdydXBvcyksIGZ1bmN0aW9uKGkpIHsNCiAgbGlzdCgNCiAgICBtZXRob2QgPSAidXBkYXRlIiwNCiAgICBsYWJlbCAgPSBncnVwb3NbaV0sDQogICAgYXJncyAgID0gbGlzdCgNCiAgICAgIGxpc3QodmlzaWJsZSA9IHNlcV9hbG9uZyhncnVwb3MpID09IGkpLA0KICAgICAgbGlzdCgidGl0bGUudGV4dCIgPSB0aXR1bG8oaSksDQogICAgICAgICAgIGFubm90YXRpb25zICA9IGFub3RhY2lvbmVzX2NlbGRhcyhncnVwb3NbaV0pKQ0KICAgICkNCiAgKQ0KfSkNCg0KZmlnIDwtIGZpZyAlPiUNCiAgbGF5b3V0KA0KICAgIGhlaWdodCA9IDUyMCwNCiAgICB0aXRsZSAgPSB0aXR1bG9fcGxvdGx5KHRpdHVsbygxKSksDQogICAgeGF4aXMgPSBsaXN0KHRpdGxlID0gIkdydXBvIGRlIGVkYWQgKCUgZGUgaGFiaXRhbnRlcyBkZSBsYSBVUFopIiwgc2lkZSA9ICJib3R0b20iKSwNCiAgICB5YXhpcyA9IGxpc3QodGl0bGUgPSAiIiwgYXV0b3JhbmdlID0gInJldmVyc2VkIiwgdGlja2ZvbnQgPSBsaXN0KHNpemUgPSAxMyksIGF1dG9tYXJnaW4gPSBUUlVFKSwNCiAgICBhbm5vdGF0aW9ucyA9IGFub3RhY2lvbmVzX2NlbGRhcyhncnVwb3NbMV0pLA0KICAgIGhvdmVybGFiZWwgPSBob3Zlcl9wbG90bHksDQogICAgdXBkYXRlbWVudXMgPSBsaXN0KG1lbnVfcGxvdGx5KGJvdG9uZXMpKSwNCiAgICBtYXJnaW4gPSBsaXN0KHQgPSAxNDAsIGIgPSA4MCwgbCA9IDEwLCByID0gMzApDQogICkgJT4lDQogIGVzdGlsb19wbG90bHkoKQ0KDQojIDUuIENhamEgZGUgY29tZW50YXJpb3MgZGUgbGFkbyBhIGxhZG8gKGRlYmFqbyBkZWwgZ3LDoWZpY28pIC0tLS0NCiMgKGpzX2NvbWVudGFyaW9zIGVzdMOhIGVuIGVsIGNodW5rICJlc3RpbG8iKQ0KZmlnIDwtIGh0bWx3aWRnZXRzOjpvblJlbmRlcigNCiAgZmlnLCBqc19jb21lbnRhcmlvcywNCiAgZGF0YSA9IGxpc3QoY29tZW50YXJpb3MgPSBjb21lbnRhcmlvcywgaW5pY2lvcyA9IHNlcV9hbG9uZyhncnVwb3MpIC0gMSkgICAjIHVuYSB0cmF6YSBwb3IgZ3J1cG8NCikNCg0KZmlnDQpgYGANCg0KKipDb25jbHVzacOzbiBkZSBjb3JyZWxhY2nDs24qKg0KDQpFbiBlc3RhIHNlY2Npb24gc2UgbWFudGllbmVuIGxvcyBjYXNvcyBhdGlwaWNvcyBleHRyZW1vcyBlbiBlbCBhbmFsaXNpcywgYXVucXVlIHNvbiBhc29jaWFjaW9uZXMgZW50cmUgVVBaLCBubyBleHBsaWNhbiBwb3IgcXXDqSBvY3VycmVuIHkgbm8gc2lnbmlmaWNhbiBxdWUgbGEgZWRhZCBjYXVzZSBlbCBkZWxpdG8sIHBhcmEgbWF5b3IgYW5hbGlzaXMgc2VyaWEgaW1wb3J0YW50ZSB0ZW5lciBsYSBkYXRhIHBvciBkZW51bmNpYXMgcmVhbGl6YWRhcyBwb3IgZWRhZCwganVudG8gY29uIGVsIGRlbGl0byBhIGRlbnVuY2lhciwgZGUgZXN0YSBtYW5lcmEgc2UgcG9kcmlhIGNvbXByb2JhciBwb3NpdGl2YSBvIG5lZ2F0aXZhbWVudGUgbGEgY29ycmVsYWNpw7NuLg0KDQojIyA0LjUgwr9Ew7NuZGUgc2UgdWJpY2FuIGxhcyBVUFogY29uIG1heW9yZXMgdGFzYXMgeSBjw7NtbyBzZSB2ZW4gbGFzIHNlZ21lbnRhY2lvbmVzIGVuIGVsIHRlcnJpdG9yaW8/DQoNCmBgYHtyIE1hcGF9DQojIG1hcGEgYm9nb3RhIHVweg0KcnV0YV9zaHAgPC0gZmlsZS5wYXRoKHJ1dGFfaW5wdXQsICJzaHAiLCAidXB6X2JvZ290YS5zaHAiKQ0KdXB6X3NocCA8LSBzdF9yZWFkKHJ1dGFfc2hwLCBxdWlldCA9IFRSVUUpDQpsb2Nfc2hwIDwtIHN0X3JlYWQocnV0YV9zaHAsIHF1aWV0ID0gVFJVRSkgICMgcmVjb3J0YWRhcyBhbCDDoXJlYSB1cmJhbmENCg0KIyAxLiBHZW9tZXRyw61hICsgZGF0b3MgZGUgY2FkYSBVUFogLS0tLQ0KbWFwYSA8LSB1cHpfc2hwICU+JQ0KICBzZWxlY3QodXB6X2NvZCkgJT4lDQogIGxlZnRfam9pbigNCiAgICB1cHogJT4lIHNlbGVjdCh1cHpfY29kLCB1cHpfbm9tYnJlLCBsb2NhbGlkYWQsIGdydXBvX3BvYmxhY2lvbiwgcG9ibGFjaW9uLA0KICAgICAgICAgICAgICAgICAgIGNhc29zX3RvdGFsLCB0YXNhX3RvdGFsLA0KICAgICAgICAgICAgICAgICAgIGNhc29zX2h1cnRvX3RvdGFsLCB0YXNhX2h1cnRvX3RvdGFsLA0KICAgICAgICAgICAgICAgICAgIGNhc29zX2xlc2lvbmVzLCB0YXNhX2xlc2lvbmVzLA0KICAgICAgICAgICAgICAgICAgIGNhc29zX2hvbWljaWRpb3MsIHRhc2FfaG9taWNpZGlvcywNCiAgICAgICAgICAgICAgICAgICBjYXNvc192aW9sZW5jaWFfaW50cmFmYW1pbGlhciwgdGFzYV92aW9sZW5jaWFfaW50cmFmYW1pbGlhciksDQogICAgYnkgPSAidXB6X2NvZCINCiAgKSAlPiUNCiAgbXV0YXRlKGlkX3VweiA9IGFzLmNoYXJhY3Rlcih1cHpfY29kKSkNCg0Kc3RvcGlmbm90KCJIYXkgVVBaIHNpbiBzZWdtZW50YWNpw7NuOiBjb3JyZSBwcmltZXJvIGVsIGNodW5rIGRlIEstbWVhbnMiID0gIWFueU5BKG1hcGEkZ3J1cG9fcG9ibGFjaW9uKSkNCg0KIyBzZSBhbGlnZXJhIGxhIGdlb21ldHLDrWEgcGFyYSBxdWUgZWwgSFRNTCBwZXNlIHBvY28NCm1hcGEgPC0gbWFwYSAlPiUNCiAgc3RfbWFrZV92YWxpZCgpICU+JQ0KICBzdF90cmFuc2Zvcm0oMzExNikgJT4lDQogIHN0X3NpbXBsaWZ5KGRUb2xlcmFuY2UgPSAzMCwgcHJlc2VydmVUb3BvbG9neSA9IFRSVUUpICU+JQ0KICBzdF90cmFuc2Zvcm0oNDMyNikNCg0KIyBsw61taXRlcyBleGFjdG9zIGRlIEJvZ290w6EgcGFyYSBxdWUgZWwgbWFwYSBsbGVuZSBlbCBlc3BhY2lvDQpiYiAgPC0gc3RfYmJveChtYXBhKQ0KcGFkIDwtIDAuMDA1DQpyYW5nb19sb24gPC0gYyh1bm5hbWUoYmJbInhtaW4iXSkgLSBwYWQsIHVubmFtZShiYlsieG1heCJdKSArIHBhZCkNCnJhbmdvX2xhdCA8LSBjKHVubmFtZShiYlsieW1pbiJdKSAtIHBhZCwgdW5uYW1lKGJiWyJ5bWF4Il0pICsgcGFkKQ0KDQphX2dlb2pzb24gPC0gZnVuY3Rpb24oeCkgew0KICB0bXAgPC0gdGVtcGZpbGUoZmlsZWV4dCA9ICIuZ2VvanNvbiIpDQogIHN0X3dyaXRlKHhbLCAiaWRfdXB6Il0sIHRtcCwgZHJpdmVyID0gIkdlb0pTT04iLCBxdWlldCA9IFRSVUUpDQogIG91dCA8LSByZWFkX2pzb24odG1wKQ0KICB1bmxpbmsodG1wKQ0KICBvdXQNCn0NCg0KIyAyLiBTZWdtZW50YWNpw7NuIHkgZGVsaXRvcyAtLS0tDQpuX2dydXBvIDwtIHNhcHBseShncnVwb3MsIGZ1bmN0aW9uKGcpIHN1bShtYXBhJGdydXBvX3BvYmxhY2lvbiA9PSBnKSkNCg0KZGVsaXRvc19tYXBhIDwtIGxpc3QoDQogICJUb3RhbCBkZSBkZWxpdG9zIiAgICAgICAgPSBjKCJjYXNvc190b3RhbCIsICJ0YXNhX3RvdGFsIiksDQogICJIdXJ0byIgICAgICAgICAgICAgICAgICAgPSBjKCJjYXNvc19odXJ0b190b3RhbCIsICJ0YXNhX2h1cnRvX3RvdGFsIiksDQogICJMZXNpb25lcyIgICAgICAgICAgICAgICAgPSBjKCJjYXNvc19sZXNpb25lcyIsICJ0YXNhX2xlc2lvbmVzIiksDQogICJIb21pY2lkaW9zIiAgICAgICAgICAgICAgPSBjKCJjYXNvc19ob21pY2lkaW9zIiwgInRhc2FfaG9taWNpZGlvcyIpLA0KICAiVmlvbGVuY2lhIGludHJhZmFtaWxpYXIiID0gYygiY2Fzb3NfdmlvbGVuY2lhX2ludHJhZmFtaWxpYXIiLCAidGFzYV92aW9sZW5jaWFfaW50cmFmYW1pbGlhciIpDQopDQoNCmNyZWFyX3RleHRvIDwtIGZ1bmN0aW9uKGQsIGRlbGl0bykgew0KICB2IDwtIGRlbGl0b3NfbWFwYVtbZGVsaXRvXV0NCiAgcGFzdGUwKA0KICAgICI8Yj4iLCBkJHVwel9ub21icmUsICI8L2I+IiwNCiAgICAiPGJyPkxvY2FsaWRhZDogIiwgZCRsb2NhbGlkYWQsDQogICAgIjxicj5TZWdtZW50YWNpw7NuOiAiLCBkJGdydXBvX3BvYmxhY2lvbiwNCiAgICAiPGJyPlBvYmxhY2nDs246ICIsIG51bShkJHBvYmxhY2lvbiksDQogICAgIjxicj5Ub3RhbCBkZSBkZWxpdG9zOiAiLCBudW0oZCRjYXNvc190b3RhbCksICIgY2Fzb3MgwrcgdGFzYSAiLCBudW0oZCR0YXNhX3RvdGFsLCBhY2N1cmFjeSA9IDAuMSksDQogICAgaWYgKGRlbGl0byAhPSAiVG90YWwgZGUgZGVsaXRvcyIpDQogICAgICBwYXN0ZTAoIjxicj48Yj4iLCBkZWxpdG8sICI6PC9iPiAiLCBudW0oZFtbdlsxXV1dKSwgIiBjYXNvcyDCtyB0YXNhICIsIG51bShkW1t2WzJdXV0sIGFjY3VyYWN5ID0gMC4xKSkNCiAgICBlbHNlICIiDQogICkNCn0NCg0KZGF0b3NfZyA8LSBsYXBwbHkoZ3J1cG9zLCBmdW5jdGlvbihnKSBtYXBhICU+JSBmaWx0ZXIoZ3J1cG9fcG9ibGFjaW9uID09IGcpKQ0KdGV4dG9zICA8LSBsYXBwbHkobmFtZXMoZGVsaXRvc19tYXBhKSwgZnVuY3Rpb24oZGwpIHsNCiAgbGFwcGx5KGRhdG9zX2csIGZ1bmN0aW9uKGRnKSBjcmVhcl90ZXh0byhzdF9kcm9wX2dlb21ldHJ5KGRnKSwgZGwpKQ0KfSkNCm5hbWVzKHRleHRvcykgPC0gbmFtZXMoZGVsaXRvc19tYXBhKQ0KDQojIDMuIE1hcGE6IGJhc2UgZ3JpcyAodG9kYXMgbGFzIFVQWikgKyB1bmEgY2FwYSBwb3Igc2VnbWVudGFjacOzbiAtLS0tDQphbHRvX21hcGEgPC0gOTUwDQoNCmZpZyA8LSBwbG90X2x5KGhlaWdodCA9IGFsdG9fbWFwYSkgJT4lDQogIGFkZF90cmFjZSgNCiAgICB0eXBlID0gImNob3JvcGxldGgiLCBuYW1lID0gImJhc2UiLCAgICAgICAgICAgICAgICAgICAgICAgIyA8PDwgTlVFVk86IG5vbWJyZSBkZSBsYSB0cmF6YQ0KICAgIGdlb2pzb24gPSBhX2dlb2pzb24obWFwYSksIGZlYXR1cmVpZGtleSA9ICJwcm9wZXJ0aWVzLmlkX3VweiIsDQogICAgbG9jYXRpb25zID0gbWFwYSRpZF91cHosIHogPSByZXAoMSwgbnJvdyhtYXBhKSksIHptaW4gPSAwLCB6bWF4ID0gMSwNCiAgICBjb2xvcnNjYWxlID0gbGlzdChjKDAsIGdyaXMpLCBjKDEsIGdyaXMpKSwgc2hvd3NjYWxlID0gRkFMU0UsDQogICAgbWFya2VyID0gbGlzdChsaW5lID0gbGlzdChjb2xvciA9ICJ3aGl0ZSIsIHdpZHRoID0gMC41KSksDQogICAgaG92ZXJpbmZvID0gInNraXAiDQogICkNCg0KZm9yIChpIGluIHNlcV9hbG9uZyhncnVwb3MpKSB7DQogIGRnIDwtIGRhdG9zX2dbW2ldXQ0KICBmaWcgPC0gZmlnICU+JSBhZGRfdHJhY2UoDQogICAgdHlwZSA9ICJjaG9yb3BsZXRoIiwgbmFtZSA9IGdydXBvc1tpXSwgICAgICAgICAgICAgICAgICAgICMgPDw8IE5VRVZPOiBsYSB0YWJsYSBsZWUgZXN0ZSBub21icmUNCiAgICBnZW9qc29uID0gYV9nZW9qc29uKGRnKSwgZmVhdHVyZWlka2V5ID0gInByb3BlcnRpZXMuaWRfdXB6IiwNCiAgICBsb2NhdGlvbnMgPSBkZyRpZF91cHosIHogPSByZXAoMSwgbnJvdyhkZykpLCB6bWluID0gMCwgem1heCA9IDEsDQogICAgY29sb3JzY2FsZSA9IGxpc3QoYygwLCBjb2xvcmVzW1tncnVwb3NbaV1dXSksIGMoMSwgY29sb3Jlc1tbZ3J1cG9zW2ldXV0pKSwgc2hvd3NjYWxlID0gRkFMU0UsDQogICAgbWFya2VyID0gbGlzdChsaW5lID0gbGlzdChjb2xvciA9ICJ3aGl0ZSIsIHdpZHRoID0gMC44KSksDQogICAgdGV4dCA9IHRleHRvc1tbIlRvdGFsIGRlIGRlbGl0b3MiXV1bW2ldXSwgaG92ZXJpbmZvID0gInRleHQiDQogICkNCn0NCg0KIyA0LiBTZWxlY3RvcmVzIC0tLS0NCmJvdG9uZXNfc2VnIDwtIGxhcHBseShjKCJUb2RhcyIsIGdydXBvcyksIGZ1bmN0aW9uKHMpIHsNCiAgbGlzdChtZXRob2QgPSAicmVzdHlsZSIsDQogICAgICAgbGFiZWwgID0gaWYgKHMgPT0gIlRvZGFzIikgcGFzdGUwKCJUb2RhcyAoIiwgc3VtKG5fZ3J1cG8pLCAiIFVQWikiKSBlbHNlIHBhc3RlMChzLCAiICgiLCBuX2dydXBvW1tzXV0sICIgVVBaKSIpLA0KICAgICAgIGFyZ3MgICA9IGxpc3QobGlzdCh2aXNpYmxlID0gKGdydXBvcyA9PSBzKSB8IChzID09ICJUb2RhcyIpKSwgYygxLCAyLCAzKSkpDQp9KQ0KDQpib3RvbmVzX2RlbGl0byA8LSBsYXBwbHkobmFtZXMoZGVsaXRvc19tYXBhKSwgZnVuY3Rpb24oZGwpIHsNCiAgbGlzdChtZXRob2QgPSAicmVzdHlsZSIsIGxhYmVsID0gZGwsDQogICAgICAgYXJncyAgID0gbGlzdChsaXN0KHRleHQgPSBsYXBwbHkodGV4dG9zW1tkbF1dLCBhcy5saXN0KSksIGMoMSwgMiwgMykpKQ0KfSkNCg0KbGV5ZW5kYSA8LSBwYXN0ZTAoDQogIHBhc3RlMCgiPHNwYW4gc3R5bGU9J2NvbG9yOiIsIGNvbG9yZXMsICInPuKWoDwvc3Bhbj4gIiwgZ3J1cG9zLCAiICgiLCBuX2dydXBvLCAiKSIsIGNvbGxhcHNlID0gIiAgICAgICIpLA0KICAiICAgICAgPHNwYW4gc3R5bGU9J2NvbG9yOiNCREJBQzQnPuKWoDwvc3Bhbj4gVVBaIG5vIHNlbGVjY2lvbmFkYXMiDQopDQoNCiMgNS4gRGlzZcOxbyBkZWwgbWFwYSAtLS0tDQpmaWcgPC0gZmlnICU+JQ0KICBsYXlvdXQoDQogICAgaGVpZ2h0ID0gYWx0b19tYXBhLA0KICAgIHRpdGxlID0gdGl0dWxvX3Bsb3RseSgNCiAgICAgIHBhc3RlMCgiPGI+TWFwYSBkZSBsYXMgVVBaIGRlIEJvZ290w6Egc2Vnw7puIHNlZ21lbnRhY2nDs24gcG9ibGFjaW9uYWw8L2I+IiwNCiAgICAgICAgICAgICAiPGJyPjxzdXA+RWxpZ2UgbGEgc2VnbWVudGFjacOzbiB5IGVsIGRlbGl0byDCtyBwYXNhIGVsIGN1cnNvciBzb2JyZSBjYWRhIFVQWiDCtyAiLA0KICAgICAgICAgICAgICJ0YXNhcyBwb3IgY2FkYSAxMC4wMDAgaGFiaXRhbnRlczwvc3VwPiIpLA0KICAgICAgeHJlZiA9ICJjb250YWluZXIiDQogICAgKSwNCiAgICBnZW8gPSBsaXN0KA0KICAgICAgcHJvamVjdGlvbiA9IGxpc3QodHlwZSA9ICJtZXJjYXRvciIpLA0KICAgICAgbG9uYXhpcyA9IGxpc3QocmFuZ2UgPSByYW5nb19sb24pLA0KICAgICAgbGF0YXhpcyA9IGxpc3QocmFuZ2UgPSByYW5nb19sYXQpLA0KICAgICAgdmlzaWJsZSA9IEZBTFNFLCBiZ2NvbG9yID0gInJnYmEoMCwwLDAsMCkiLA0KICAgICAgZG9tYWluID0gbGlzdCh4ID0gYygwLCAxKSwgeSA9IGMoMCwgMSkpDQogICAgKSwNCiAgICBob3ZlcmxhYmVsID0gaG92ZXJfcGxvdGx5LA0KICAgIHVwZGF0ZW1lbnVzID0gbGlzdCgNCiAgICAgIG1lbnVfcGxvdGx5KGJvdG9uZXNfc2VnLCAgICB4ID0gMCwgICAgeSA9IDEuMDIpLA0KICAgICAgbWVudV9wbG90bHkoYm90b25lc19kZWxpdG8sIHggPSAwLjQyLCB5ID0gMS4wMikNCiAgICApLA0KICAgIGFubm90YXRpb25zID0gbGlzdCgNCiAgICAgIGxpc3QodGV4dCA9ICJTZWdtZW50YWNpw7NuIHBvYmxhY2lvbmFsIiwgeCA9IDAsIHkgPSAxLjEwLCB4cmVmID0gInBhcGVyIiwgeXJlZiA9ICJwYXBlciIsDQogICAgICAgICAgIHhhbmNob3IgPSAibGVmdCIsIHlhbmNob3IgPSAiYm90dG9tIiwgc2hvd2Fycm93ID0gRkFMU0UsIGZvbnQgPSBsaXN0KHNpemUgPSAxMiwgY29sb3IgPSAiZ3JleTQwIikpLA0KICAgICAgbGlzdCh0ZXh0ID0gIkRlbGl0byAoZW4gZWwgY3VhZHJvIGRlbCBtb3VzZSkiLCB4ID0gMC40MiwgeSA9IDEuMTAsIHhyZWYgPSAicGFwZXIiLCB5cmVmID0gInBhcGVyIiwNCiAgICAgICAgICAgeGFuY2hvciA9ICJsZWZ0IiwgeWFuY2hvciA9ICJib3R0b20iLCBzaG93YXJyb3cgPSBGQUxTRSwgZm9udCA9IGxpc3Qoc2l6ZSA9IDEyLCBjb2xvciA9ICJncmV5NDAiKSksDQogICAgICBsaXN0KHRleHQgPSBsZXllbmRhLCB4ID0gMCwgeSA9IC0wLjAyLCB4cmVmID0gInBhcGVyIiwgeXJlZiA9ICJwYXBlciIsDQogICAgICAgICAgIHhhbmNob3IgPSAibGVmdCIsIHlhbmNob3IgPSAidG9wIiwgc2hvd2Fycm93ID0gRkFMU0UsIGZvbnQgPSBsaXN0KHNpemUgPSAxMywgY29sb3IgPSAiZ3JleTI1IikpDQogICAgKSwNCiAgICBtYXJnaW4gPSBsaXN0KHQgPSAxNDAsIGIgPSA1MCwgbCA9IDAsIHIgPSAwKQ0KICApICU+JQ0KICBlc3RpbG9fcGxvdGx5KCkNCmBgYA0KDQpgYGB7ciBnZW5lcmFjaW9uX3Jhbmtpbmd9DQojIDYuIGRhdG9zIGRlbCByYW5raW5nIC0tLS0NCnRvcF9uIDwtIDE1ICAgIyBjdcOhbnRhcyBVUFogbXVlc3RyYSBsYSB0YWJsYQ0KDQojIGVzX2V4dHJlbW8geWEgdmllbmUgY2FsY3VsYWRvIGVuIGVsIGJveHBsb3QgKFEzICsgMyDDlyBJUVIsIHRhc2FzIHJlYWxlcywgcG9yIHNlZ21lbnRhY2nDs24pDQpyYW5raW5nX2RhdG9zIDwtIHVweiAlPiUNCiAgdHJhbnNtdXRlKA0KICAgIHVwel9ub21icmUsDQogICAgZ3J1cG9fcG9ibGFjaW9uID0gYXMuY2hhcmFjdGVyKGdydXBvX3BvYmxhY2lvbiksDQogICAgYXJlYV9rbTIsIHBvYmxhY2lvbiwNCiAgICB0YXNhX2h1cnRvX3RvdGFsLCB0YXNhX2xlc2lvbmVzLCB0YXNhX2hvbWljaWRpb3MsIHRhc2FfdmlvbGVuY2lhX2ludHJhZmFtaWxpYXIsIHRhc2FfdG90YWwsDQogICAgZXNfZXh0cmVtbw0KICApDQoNCmNmZyA8LSBsaXN0KA0KICBtb3JhZG8gPSBtb3JhZG8sIG1hZ2VudGEgPSBtYWdlbnRhLCBncmlzID0gZ3JpcywNCiAgdG9wX24gPSB0b3BfbiwgZ3J1cG9zID0gZ3J1cG9zLCBjb2xvcmVzID0gYXMubGlzdChjb2xvcmVzKSwNCiAgbl9leHQgPSBzdW0ocmFua2luZ19kYXRvcyRlc19leHRyZW1vKSwNCiAgbl9jZXJvX2hvbSA9IHN1bShyYW5raW5nX2RhdG9zJHRhc2FfaG9taWNpZGlvcyA9PSAwKQ0KKQ0KDQojIDcuIHRhYmxhIHJhbmtpbmcgY29uZWN0YWRhIChlbiBsb3MgZG9zIHNlbnRpZG9zKSBjb24gZWwgc2VsZWN0b3IgZGUgc2VnbWVudGFjacOzbiBkZWwgbWFwYSAtLS0tDQpqc19yYW5raW5nIDwtIHIiLS0tKA0KZnVuY3Rpb24oZWwsIHgsIGRhdG9zKSB7DQogIHZhciByZWcgPSBKU09OLnBhcnNlKGRhdG9zLnJlZ2lzdHJvcyk7DQogIHZhciBjZmcgPSBKU09OLnBhcnNlKGRhdG9zLmNmZyk7DQoNCiAgZnVuY3Rpb24gZih2LCBkZWMpIHsNCiAgICByZXR1cm4gbmV3IEludGwuTnVtYmVyRm9ybWF0KCdlcy1DTycsIHttaW5pbXVtRnJhY3Rpb25EaWdpdHM6IGRlYywgbWF4aW11bUZyYWN0aW9uRGlnaXRzOiBkZWN9KS5mb3JtYXQodik7DQogIH0NCiAgZnVuY3Rpb24gZXNjKHMpIHsNCiAgICByZXR1cm4gU3RyaW5nKHMpLnJlcGxhY2UoLyYvZywgJyZhbXA7JykucmVwbGFjZSgvPC9nLCAnJmx0OycpLnJlcGxhY2UoLz4vZywgJyZndDsnKTsNCiAgfQ0KDQogIHZhciBjb2xzID0gWw0KICAgIHtrOiAndXB6X25vbWJyZScsICAgICAgICAgICAgICAgICAgIHQ6ICdVUFonLCAgICAgICAgICAgICAgICAgICAgICAgICBudW06IGZhbHNlfSwNCiAgICB7azogJ2dydXBvX3BvYmxhY2lvbicsICAgICAgICAgICAgICB0OiAnU2VnbWVudGFjacOzbicsICAgICAgICAgICAgICAgIG51bTogZmFsc2V9LA0KICAgIHtrOiAnYXJlYV9rbTInLCAgICAgICAgICAgICAgICAgICAgIHQ6ICfDgXJlYSAoa23CsiknLCAgICAgICAgICAgICAgICAgIG51bTogdHJ1ZSwgZDogMn0sDQogICAge2s6ICdwb2JsYWNpb24nLCAgICAgICAgICAgICAgICAgICAgdDogJ1BvYmxhY2nDs24nLCAgICAgICAgICAgICAgICAgICBudW06IHRydWUsIGQ6IDB9LA0KICAgIHtrOiAndGFzYV9odXJ0b190b3RhbCcsICAgICAgICAgICAgIHQ6ICdUYXNhIGh1cnRvJywgICAgICAgICAgICAgICAgICBudW06IHRydWUsIGQ6IDF9LA0KICAgIHtrOiAndGFzYV9sZXNpb25lcycsICAgICAgICAgICAgICAgIHQ6ICdUYXNhIGxlc2lvbmVzJywgICAgICAgICAgICAgICBudW06IHRydWUsIGQ6IDF9LA0KICAgIHtrOiAndGFzYV9ob21pY2lkaW9zJywgICAgICAgICAgICAgIHQ6ICdUYXNhIGhvbWljaWRpb3MnLCAgICAgICAgICAgICBudW06IHRydWUsIGQ6IDJ9LA0KICAgIHtrOiAndGFzYV92aW9sZW5jaWFfaW50cmFmYW1pbGlhcicsIHQ6ICdUYXNhIHZpb2xlbmNpYSBpbnRyYWZhbWlsaWFyJywgbnVtOiB0cnVlLCBkOiAxfSwNCiAgICB7azogJ3Rhc2FfdG90YWwnLCAgICAgICAgICAgICAgICAgICB0OiAnVGFzYSB0b3RhbCcsICAgICAgICAgICAgICAgICAgbnVtOiB0cnVlLCBkOiAxfQ0KICBdOw0KICB2YXIgb3JkZW5hYmxlcyA9IGNvbHMuZmlsdGVyKGZ1bmN0aW9uKGMpIHsgcmV0dXJuIGMubnVtOyB9KTsNCiAgdmFyIGVzdGFkbyA9IHtrOiAndGFzYV90b3RhbCcsIGRpcjogJ2Rlc2MnLCBmaXJtYTogbnVsbH07DQoNCiAgLy8gZXN0aWxvIChtaXNtbyBsb29rIHF1ZSB0YWJsYV9ib25pdGE6IGVuY2FiZXphZG8gbW9yYWRvLCBmaWxhIHJlc2FsdGFkYSByb3NhZGEsIGhvdmVyKQ0KICB2YXIgY3NzID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgnc3R5bGUnKTsNCiAgY3NzLnRleHRDb250ZW50ID0NCiAgICAnLnJrLWNvbnR7bWFyZ2luLXRvcDoxNHB4O3dpZHRoOjEwMCU7Ym94LXNpemluZzpib3JkZXItYm94O3BhZGRpbmc6MCAxMHB4O2ZvbnQtZmFtaWx5OmluaGVyaXQ7Y29sb3I6IzMzM30nICsNCiAgICAnLnJrLXRpdHVsb3tmb250LXNpemU6MThweDtjb2xvcjonICsgY2ZnLm1vcmFkbyArICc7bGluZS1oZWlnaHQ6MS40O21hcmdpbi1ib3R0b206OHB4fScgKw0KICAgICcucmstdGl0dWxvIHNwYW57Zm9udC1zaXplOjEzcHg7Y29sb3I6IzU1NX0nICsNCiAgICAnLnJrLWN0cmx7ZGlzcGxheTpmbGV4O2dhcDoxNnB4O2ZsZXgtd3JhcDp3cmFwO2FsaWduLWl0ZW1zOmNlbnRlcjttYXJnaW4tYm90dG9tOjEwcHg7Zm9udC1zaXplOjEzcHg7Y29sb3I6IzU1NX0nICsNCiAgICAnLnJrLWN0cmwgc2VsZWN0e3BhZGRpbmc6NXB4IDhweDtib3JkZXI6MXB4IHNvbGlkICcgKyBjZmcuZ3JpcyArICc7Ym9yZGVyLXJhZGl1czo0cHg7Y29sb3I6JyArIGNmZy5tb3JhZG8gKyAnO2JhY2tncm91bmQ6I2ZmZjtmb250LXNpemU6MTNweH0nICsNCiAgICAnLnJrLXdyYXB7b3ZlcmZsb3cteDphdXRvfScgKw0KICAgICcucmstdGFibGF7d2lkdGg6MTAwJTtib3JkZXItY29sbGFwc2U6Y29sbGFwc2U7Zm9udC1zaXplOjE0cHh9JyArDQogICAgJy5yay10YWJsYSB0aHtiYWNrZ3JvdW5kOicgKyBjZmcubW9yYWRvICsgJztjb2xvcjojZmZmO2ZvbnQtd2VpZ2h0OmJvbGQ7cGFkZGluZzo3cHggOXB4O3RleHQtYWxpZ246bGVmdDt3aGl0ZS1zcGFjZTpub3dyYXB9JyArDQogICAgJy5yay10YWJsYSB0aC5yay1udW17dGV4dC1hbGlnbjpyaWdodH0nICsNCiAgICAnLnJrLXRhYmxhIHRoLnJrLW9yZHtjdXJzb3I6cG9pbnRlcn0nICsNCiAgICAnLnJrLXRhYmxhIHRoLnJrLW9yZDpob3ZlcntmaWx0ZXI6YnJpZ2h0bmVzcygxLjI1KX0nICsNCiAgICAnLnJrLXRhYmxhIHRke3BhZGRpbmc6NnB4IDlweDtib3JkZXItdG9wOjFweCBzb2xpZCAjZGRkfScgKw0KICAgICcucmstdGFibGEgdGQucmstbnVte3RleHQtYWxpZ246cmlnaHQ7d2hpdGUtc3BhY2U6bm93cmFwfScgKw0KICAgICcucmstdGFibGEgdGQucmstcG9ze3RleHQtYWxpZ246Y2VudGVyO2ZvbnQtd2VpZ2h0OmJvbGQ7Y29sb3I6JyArIGNmZy5tb3JhZG8gKyAnO3dpZHRoOjQycHh9JyArDQogICAgJy5yay10YWJsYSB0Ym9keSB0cjpob3ZlcntiYWNrZ3JvdW5kOiNmNWY1ZjV9JyArDQogICAgJy5yay10YWJsYSB0Ym9keSB0ci5yay1wcmltZXJve2JhY2tncm91bmQ6I0Y2RUVGMjtmb250LXdlaWdodDpib2xkO2NvbG9yOicgKyBjZmcubW9yYWRvICsgJ30nICsNCiAgICAnLnJrLXRhYmxhIHRib2R5IHRyLnJrLWV4dCB0ZHtib3JkZXItdG9wOjJweCBzb2xpZCAnICsgY2ZnLm1hZ2VudGEgKyAnO2JvcmRlci1ib3R0b206MnB4IHNvbGlkICcgKyBjZmcubWFnZW50YSArICd9JyArDQogICAgJy5yay10YWJsYSB0Ym9keSB0ci5yay1leHQgdGQ6Zmlyc3QtY2hpbGR7Ym9yZGVyLWxlZnQ6MnB4IHNvbGlkICcgKyBjZmcubWFnZW50YSArICd9JyArDQogICAgJy5yay10YWJsYSB0Ym9keSB0ci5yay1leHQgdGQ6bGFzdC1jaGlsZHtib3JkZXItcmlnaHQ6MnB4IHNvbGlkICcgKyBjZmcubWFnZW50YSArICd9JyArDQogICAgJy5yay1ub3Rhe21hcmdpbi10b3A6MTJweDtmb250LXNpemU6MTNweDtsaW5lLWhlaWdodDoxLjU7Y29sb3I6IzQ0NDtib3JkZXItdG9wOjJweCBzb2xpZCAnICsgY2ZnLmdyaXMgKyAnO3BhZGRpbmctdG9wOjEwcHh9JyArDQogICAgJy5yay1ub3RhIHVse21hcmdpbjo2cHggMCAwIDA7cGFkZGluZy1sZWZ0OjIwcHh9JyArDQogICAgJy5yay1ub3RhIGxpe21hcmdpbi1ib3R0b206NHB4fScgKw0KICAgICcucmstc3d7ZGlzcGxheTppbmxpbmUtYmxvY2s7d2lkdGg6MjJweDtoZWlnaHQ6MTFweDt2ZXJ0aWNhbC1hbGlnbjptaWRkbGU7bWFyZ2luLXJpZ2h0OjRweDtib3gtc2l6aW5nOmJvcmRlci1ib3h9JyArDQogICAgJy5yay1zdy1leHR7Ym9yZGVyOjJweCBzb2xpZCAnICsgY2ZnLm1hZ2VudGEgKyAnfScgKw0KICAgICcucmstc3ctcHJpbXtiYWNrZ3JvdW5kOiNGNkVFRjI7Ym9yZGVyOjFweCBzb2xpZCAnICsgY2ZnLmdyaXMgKyAnfSc7DQogIGRvY3VtZW50LmhlYWQuYXBwZW5kQ2hpbGQoY3NzKTsNCg0KICAvLyBlc3RydWN0dXJhDQogIHZhciBjb250ID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgnZGl2Jyk7DQogIGNvbnQuY2xhc3NOYW1lID0gJ3JrLWNvbnQnOw0KICBlbC5wYXJlbnROb2RlLmluc2VydEJlZm9yZShjb250LCBlbC5uZXh0U2libGluZyk7DQoNCiAgdmFyIHRpdHVsbyA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoJ2RpdicpOw0KICB0aXR1bG8uY2xhc3NOYW1lID0gJ3JrLXRpdHVsbyc7DQogIGNvbnQuYXBwZW5kQ2hpbGQodGl0dWxvKTsNCg0KICB2YXIgY3RybCA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoJ2RpdicpOw0KICBjdHJsLmNsYXNzTmFtZSA9ICdyay1jdHJsJzsNCg0KICAvLyA8PDwgTlVFVk86IGN1w6FudGFzIFVQWiB0aWVuZSBjYWRhIHNlZ21lbnRhY2nDs24gKHBhcmEgZWwgc2VsZWN0b3IpDQogIHZhciBjb250ZW8gPSB7fTsNCiAgcmVnLmZvckVhY2goZnVuY3Rpb24ocikgeyBjb250ZW9bci5ncnVwb19wb2JsYWNpb25dID0gKGNvbnRlb1tyLmdydXBvX3BvYmxhY2lvbl0gfHwgMCkgKyAxOyB9KTsNCg0KICBjdHJsLmlubmVySFRNTCA9DQogICAgLy8gPDw8IE5VRVZPOiBzZWxlY3RvciBkZSBzZWdtZW50YWNpw7NuIGRlbnRybyBkZSBsYSB0YWJsYQ0KICAgICc8bGFiZWw+U2VnbWVudGFjacOzbiZuYnNwOzxzZWxlY3QgY2xhc3M9InJrLXNlbC1zZWciPicgKw0KICAgICc8b3B0aW9uIHZhbHVlPSJUb2RhcyI+VG9kYXMgKCcgKyByZWcubGVuZ3RoICsgJyBVUFopPC9vcHRpb24+JyArDQogICAgY2ZnLmdydXBvcy5tYXAoZnVuY3Rpb24oZykgeyByZXR1cm4gJzxvcHRpb24gdmFsdWU9IicgKyBnICsgJyI+JyArIGcgKyAnICgnICsgKGNvbnRlb1tnXSB8fCAwKSArICcgVVBaKTwvb3B0aW9uPic7IH0pLmpvaW4oJycpICsNCiAgICAnPC9zZWxlY3Q+PC9sYWJlbD4nICsNCiAgICAnPGxhYmVsPk9yZGVuYXIgcG9yJm5ic3A7PHNlbGVjdCBjbGFzcz0icmstc2VsLWsiPicgKw0KICAgIG9yZGVuYWJsZXMubWFwKGZ1bmN0aW9uKGMpIHsgcmV0dXJuICc8b3B0aW9uIHZhbHVlPSInICsgYy5rICsgJyI+JyArIGMudCArICc8L29wdGlvbj4nOyB9KS5qb2luKCcnKSArDQogICAgJzwvc2VsZWN0PjwvbGFiZWw+JyArDQogICAgJzxsYWJlbD5PcmRlbiZuYnNwOzxzZWxlY3QgY2xhc3M9InJrLXNlbC1kaXIiPicgKw0KICAgICc8b3B0aW9uIHZhbHVlPSJkZXNjIj5EZSBtYXlvciBhIG1lbm9yPC9vcHRpb24+PG9wdGlvbiB2YWx1ZT0iYXNjIj5EZSBtZW5vciBhIG1heW9yPC9vcHRpb24+JyArDQogICAgJzwvc2VsZWN0PjwvbGFiZWw+JyArDQogICAgJzxzcGFuPlRhbWJpw6luIHB1ZWRlcyBoYWNlciBjbGljIGVuIGVsIGVuY2FiZXphZG8gZGUgdW5hIGNvbHVtbmEuPC9zcGFuPic7DQogIGNvbnQuYXBwZW5kQ2hpbGQoY3RybCk7DQogIHZhciBzZWxTZWcgPSBjdHJsLnF1ZXJ5U2VsZWN0b3IoJy5yay1zZWwtc2VnJyk7ICAgLy8gPDw8IE5VRVZPDQogIHZhciBzZWxLID0gY3RybC5xdWVyeVNlbGVjdG9yKCcucmstc2VsLWsnKTsNCiAgdmFyIHNlbERpciA9IGN0cmwucXVlcnlTZWxlY3RvcignLnJrLXNlbC1kaXInKTsNCiAgc2VsSy52YWx1ZSA9IGVzdGFkby5rOw0KICBzZWxEaXIudmFsdWUgPSBlc3RhZG8uZGlyOw0KDQogIHZhciB3cmFwID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgnZGl2Jyk7DQogIHdyYXAuY2xhc3NOYW1lID0gJ3JrLXdyYXAnOw0KICBjb250LmFwcGVuZENoaWxkKHdyYXApOw0KDQogIHZhciBub3RhID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgnZGl2Jyk7DQogIG5vdGEuY2xhc3NOYW1lID0gJ3JrLW5vdGEnOw0KICBub3RhLmlubmVySFRNTCA9DQogICAgJzxiPkPDs21vIGxlZXIgZXN0YSB0YWJsYTwvYj48dWw+JyArDQogICAgJzxsaT48c3BhbiBjbGFzcz0icmstc3cgcmstc3ctZXh0Ij48L3NwYW4+PGI+Qm9yZGUgbWFnZW50YTo8L2I+IGF0w61waWNvIGV4dHJlbW8sIGVzIGRlY2lyLCB1bmEgVVBaIGN1eWEgdGFzYSB0b3RhbCBzdXBlcmEgUTMgKyAzIMOXIElRUiBkZW50cm8gZGUgbGEgc2VnbWVudGFjacOzbiBwb2JsYWNpb25hbCBnZW5lcmFkYSAoaG95IHNvbiAnICsgY2ZnLm5fZXh0ICsgJykuIFNlIHJlc2FsdGEgc2llbXByZSBxdWUgYXBhcmV6Y2EgZW4gZWwgcmFua2luZy48L2xpPicgKw0KICAgICc8bGk+TGFzIHRhc2FzIGRlIGRlbGl0b3Mgc29uIHBvciBjYWRhIDEwLjAwMCBoYWJpdGFudGVzLjwvbGk+PC91bD4nOw0KICBjb250LmFwcGVuZENoaWxkKG5vdGEpOw0KDQogIC8vIHNlZ21lbnRhY2lvbmVzIHZpc2libGVzIGVuIGVsIG1hcGENCiAgZnVuY3Rpb24gc2VsZWNjaW9uKCkgew0KICAgIHZhciBzID0gW107DQogICAgKGVsLmRhdGEgfHwgW10pLmZvckVhY2goZnVuY3Rpb24odCkgew0KICAgICAgaWYgKGNmZy5ncnVwb3MuaW5kZXhPZih0Lm5hbWUpID49IDAgJiYgdC52aXNpYmxlICE9PSBmYWxzZSAmJiB0LnZpc2libGUgIT09ICdsZWdlbmRvbmx5Jykgcy5wdXNoKHQubmFtZSk7DQogICAgfSk7DQogICAgcmV0dXJuIHM7DQogIH0NCg0KICAvLyA8PDwgTlVFVk86IHRhYmxhIC0+IG1hcGE6IG11ZXN0cmEgc29sbyBsYSBzZWdtZW50YWNpw7NuIGVsZWdpZGEgeSBhY3R1YWxpemEgZWwgbWVuw7ogZGVsIG1hcGENCiAgZnVuY3Rpb24gYXBsaWNhckFsTWFwYSh2YWxvcikgew0KICAgIGlmICh0eXBlb2YgUGxvdGx5ID09PSAndW5kZWZpbmVkJykgcmV0dXJuOw0KICAgIHZhciB2aXMgPSBjZmcuZ3J1cG9zLm1hcChmdW5jdGlvbihnKSB7IHJldHVybiB2YWxvciA9PT0gJ1RvZGFzJyB8fCBnID09PSB2YWxvcjsgfSk7DQogICAgdmFyIGlkeCA9IGNmZy5ncnVwb3MubWFwKGZ1bmN0aW9uKGcpIHsNCiAgICAgIGZvciAodmFyIGkgPSAwOyBpIDwgZWwuZGF0YS5sZW5ndGg7IGkrKykgeyBpZiAoZWwuZGF0YVtpXS5uYW1lID09PSBnKSByZXR1cm4gaTsgfQ0KICAgICAgcmV0dXJuIC0xOw0KICAgIH0pOw0KICAgIGlmIChpZHguaW5kZXhPZigtMSkgPj0gMCkgcmV0dXJuOw0KICAgIHZhciBwb3MgPSAodmFsb3IgPT09ICdUb2RhcycpID8gMCA6IGNmZy5ncnVwb3MuaW5kZXhPZih2YWxvcikgKyAxOw0KICAgIFBsb3RseS5yZXN0eWxlKGVsLCB7dmlzaWJsZTogdmlzfSwgaWR4KS50aGVuKGZ1bmN0aW9uKCkgew0KICAgICAgcmV0dXJuIFBsb3RseS5yZWxheW91dChlbCwgeyd1cGRhdGVtZW51c1swXS5hY3RpdmUnOiBwb3N9KTsNCiAgICB9KTsNCiAgfQ0KDQogIGZ1bmN0aW9uIHJlbmRlcihmb3J6YXIpIHsNCiAgICB2YXIgc2VsID0gc2VsZWNjaW9uKCk7DQogICAgc2VsU2VnLnZhbHVlID0gKHNlbC5sZW5ndGggPT09IDEpID8gc2VsWzBdIDogJ1RvZGFzJzsgICAvLyA8PDwgTlVFVk86IG1hcGEgLT4gdGFibGEgKHNpbmNyb25pemEgZWwgc2VsZWN0b3IpDQogICAgdmFyIGZpcm1hID0gc2VsLmpvaW4oJ3wnKSArICcjJyArIGVzdGFkby5rICsgJyMnICsgZXN0YWRvLmRpcjsNCiAgICBpZiAoIWZvcnphciAmJiBmaXJtYSA9PT0gZXN0YWRvLmZpcm1hKSByZXR1cm47DQogICAgZXN0YWRvLmZpcm1hID0gZmlybWE7DQoNCiAgICB2YXIgZCA9IHJlZy5maWx0ZXIoZnVuY3Rpb24ocikgeyByZXR1cm4gc2VsLmluZGV4T2Yoci5ncnVwb19wb2JsYWNpb24pID49IDA7IH0pOw0KICAgIHZhciBzZ24gPSBlc3RhZG8uZGlyID09PSAnZGVzYycgPyAtMSA6IDE7DQogICAgZC5zb3J0KGZ1bmN0aW9uKGEsIGIpIHsNCiAgICAgIHZhciBkaWYgPSAoYVtlc3RhZG8ua10gLSBiW2VzdGFkby5rXSkgKiBzZ247DQogICAgICByZXR1cm4gZGlmICE9PSAwID8gZGlmIDogYS51cHpfbm9tYnJlLmxvY2FsZUNvbXBhcmUoYi51cHpfbm9tYnJlLCAnZXMnKTsNCiAgICB9KTsNCiAgICB2YXIgdG9wID0gZC5zbGljZSgwLCBjZmcudG9wX24pOw0KDQogICAgdmFyIGNvbCA9IG9yZGVuYWJsZXMuZmlsdGVyKGZ1bmN0aW9uKGMpIHsgcmV0dXJuIGMuayA9PT0gZXN0YWRvLms7IH0pWzBdOw0KICAgIHZhciB0eHRTZWwgPSAoc2VsLmxlbmd0aCA9PT0gY2ZnLmdydXBvcy5sZW5ndGgpID8gJ3RvZGFzIGxhcyBzZWdtZW50YWNpb25lcycNCiAgICAgICAgICAgICAgIDogKHNlbC5sZW5ndGggPyBzZWwuam9pbignLCAnKS50b0xvd2VyQ2FzZSgpIDogJ25pbmd1bmEgc2VnbWVudGFjacOzbicpOw0KICAgIHRpdHVsby5pbm5lckhUTUwgPSAnPGI+UmFua2luZyBkZSBVUFo8L2I+IMK3IFRvcCAnICsgdG9wLmxlbmd0aCArICcgZGUgJyArIGQubGVuZ3RoICsgJyBVUFogwrcgJyArIHR4dFNlbCArDQogICAgICAnPGJyPjxzcGFuPk9yZGVuYWRvIHBvciA8Yj4nICsgY29sLnQudG9Mb3dlckNhc2UoKSArICc8L2I+ICcgKw0KICAgICAgKGVzdGFkby5kaXIgPT09ICdkZXNjJyA/ICcoZGUgbWF5b3IgYSBtZW5vciknIDogJyhkZSBtZW5vciBhIG1heW9yKScpICsgJzwvc3Bhbj4nOw0KDQogICAgdmFyIGggPSAnPHRhYmxlIGNsYXNzPSJyay10YWJsYSI+PHRoZWFkPjx0cj48dGggY2xhc3M9InJrLW51bSIgc3R5bGU9InRleHQtYWxpZ246Y2VudGVyIj4jPC90aD4nOw0KICAgIGNvbHMuZm9yRWFjaChmdW5jdGlvbihjKSB7DQogICAgICB2YXIgZmxlY2hhID0gKGMuayA9PT0gZXN0YWRvLmspID8gKGVzdGFkby5kaXIgPT09ICdkZXNjJyA/ICcg4pa8JyA6ICcg4payJykgOiAnJzsNCiAgICAgIGggKz0gJzx0aCBjbGFzcz0iJyArIChjLm51bSA/ICdyay1udW0gcmstb3JkJyA6ICcnKSArICciJyArIChjLm51bSA/ICcgZGF0YS1rPSInICsgYy5rICsgJyInIDogJycpICsgJz4nICsgYy50ICsgZmxlY2hhICsgJzwvdGg+JzsNCiAgICB9KTsNCiAgICBoICs9ICc8L3RyPjwvdGhlYWQ+PHRib2R5Pic7DQogICAgdG9wLmZvckVhY2goZnVuY3Rpb24ociwgaSkgew0KICAgICAgdmFyIGNsYXNlcyA9IChpID09PSAwID8gJ3JrLXByaW1lcm8gJyA6ICcnKSArIChyLmVzX2V4dHJlbW8gPyAncmstZXh0JyA6ICcnKTsNCiAgICAgIGggKz0gJzx0ciBjbGFzcz0iJyArIGNsYXNlcyArICciPjx0ZCBjbGFzcz0icmstcG9zIj4nICsgKGkgKyAxKSArICc8L3RkPic7DQogICAgICBjb2xzLmZvckVhY2goZnVuY3Rpb24oYykgew0KICAgICAgICBpZiAoYy5rID09PSAnZ3J1cG9fcG9ibGFjaW9uJykgew0KICAgICAgICAgIGggKz0gJzx0ZD48c3BhbiBzdHlsZT0iY29sb3I6JyArIChjZmcuY29sb3Jlc1tyLmdydXBvX3BvYmxhY2lvbl0gfHwgJyM5OTknKSArICciPuKWoDwvc3Bhbj4gJyArIGVzYyhyLmdydXBvX3BvYmxhY2lvbikgKyAnPC90ZD4nOw0KICAgICAgICB9IGVsc2UgaWYgKGMubnVtKSB7DQogICAgICAgICAgaCArPSAnPHRkIGNsYXNzPSJyay1udW0iPicgKyBmKHJbYy5rXSwgYy5kKSArICc8L3RkPic7DQogICAgICAgIH0gZWxzZSB7DQogICAgICAgICAgaCArPSAnPHRkPicgKyBlc2MocltjLmtdKSArICc8L3RkPic7DQogICAgICAgIH0NCiAgICAgIH0pOw0KICAgICAgaCArPSAnPC90cj4nOw0KICAgIH0pOw0KICAgIGggKz0gJzwvdGJvZHk+PC90YWJsZT4nOw0KICAgIHdyYXAuaW5uZXJIVE1MID0gaDsNCiAgfQ0KDQogIC8vIDw8PCBOVUVWTzogYWwgY2FtYmlhciBsYSBzZWdtZW50YWNpw7NuIGVuIGxhIHRhYmxhLCBzZSBtdWV2ZSBlbCBtYXBhDQogIHNlbFNlZy5hZGRFdmVudExpc3RlbmVyKCdjaGFuZ2UnLCBmdW5jdGlvbigpIHsgYXBsaWNhckFsTWFwYShzZWxTZWcudmFsdWUpOyByZW5kZXIodHJ1ZSk7IH0pOw0KICBzZWxLLmFkZEV2ZW50TGlzdGVuZXIoJ2NoYW5nZScsIGZ1bmN0aW9uKCkgeyBlc3RhZG8uayA9IHNlbEsudmFsdWU7IHJlbmRlcih0cnVlKTsgfSk7DQogIHNlbERpci5hZGRFdmVudExpc3RlbmVyKCdjaGFuZ2UnLCBmdW5jdGlvbigpIHsgZXN0YWRvLmRpciA9IHNlbERpci52YWx1ZTsgcmVuZGVyKHRydWUpOyB9KTsNCiAgd3JhcC5hZGRFdmVudExpc3RlbmVyKCdjbGljaycsIGZ1bmN0aW9uKGUpIHsNCiAgICB2YXIgdGggPSBlLnRhcmdldC5jbG9zZXN0ID8gZS50YXJnZXQuY2xvc2VzdCgndGgucmstb3JkJykgOiBudWxsOw0KICAgIGlmICghdGgpIHJldHVybjsNCiAgICB2YXIgayA9IHRoLmdldEF0dHJpYnV0ZSgnZGF0YS1rJyk7DQogICAgaWYgKGVzdGFkby5rID09PSBrKSB7IGVzdGFkby5kaXIgPSAoZXN0YWRvLmRpciA9PT0gJ2Rlc2MnID8gJ2FzYycgOiAnZGVzYycpOyB9DQogICAgZWxzZSB7IGVzdGFkby5rID0gazsgZXN0YWRvLmRpciA9ICdkZXNjJzsgfQ0KICAgIHNlbEsudmFsdWUgPSBlc3RhZG8uazsNCiAgICBzZWxEaXIudmFsdWUgPSBlc3RhZG8uZGlyOw0KICAgIHJlbmRlcih0cnVlKTsNCiAgfSk7DQoNCiAgcmVuZGVyKHRydWUpOw0KICBlbC5vbigncGxvdGx5X2FmdGVycGxvdCcsIGZ1bmN0aW9uKCkgeyByZW5kZXIoZmFsc2UpOyB9KTsNCn0NCiktLS0iDQoNCmZpZyA8LSBodG1sd2lkZ2V0czo6b25SZW5kZXIoDQogIGZpZywganNfcmFua2luZywNCiAgZGF0YSA9IGxpc3QoDQogICAgcmVnaXN0cm9zID0gYXMuY2hhcmFjdGVyKHRvSlNPTihyYW5raW5nX2RhdG9zLCBkYXRhZnJhbWUgPSAicm93cyIsIGF1dG9fdW5ib3ggPSBUUlVFLCBkaWdpdHMgPSBOQSkpLA0KICAgIGNmZyAgICAgICA9IGFzLmNoYXJhY3Rlcih0b0pTT04oY2ZnLCBhdXRvX3VuYm94ID0gVFJVRSkpDQogICkNCikNCg0KZmlnDQpgYGANCg0KYGBge3IgZGF0b3NfaGFsbGF6Z29zfQ0KIyBDaWZyYXMgZGUgbGEgc2VjY2nDs24gZGUgSGFsbGF6Z29zOiBzZSBjYWxjdWxhbiBhcXXDrSB5IGVsIHRleHRvIGxhcyBsbGFtYSBjb24gYHIgLi4uYCwNCiMgYXPDrSBlbCBpbmZvcm1lIHNlIGFjdHVhbGl6YSBzb2xvIHNpIGNhbWJpYW4gbG9zIGRhdG9zLCBsYSBzZW1pbGxhIG8gbG9zIHVtYnJhbGVzLg0KcGN0IDwtIGZ1bmN0aW9uKHgpIHBhc3RlMChudW0oMTAwICogeCwgYWNjdXJhY3kgPSAwLjEpLCAiICUiKQ0KDQojIENvcnJlbGFjacOzbiAoU3BlYXJtYW4pIGRlIGxhIG1hdHJpeiBkZWwgYXBhcnRhZG8gNC40LCBjb24gZWwgc2lnbm8gbWVub3MgdGlwb2dyw6FmaWNvDQpyaG9fdHh0IDwtIGZ1bmN0aW9uKGcsIGRfLCBlXykgew0KICByIDwtIGNvcnJlbGFjaW9uZXMkcmhvW2NvcnJlbGFjaW9uZXMkZ3J1cG8gPT0gZyAmIGNvcnJlbGFjaW9uZXMkZGVsaXRvID09IGRfICYgY29ycmVsYWNpb25lcyRlZGFkID09IGVfXQ0KICBzdWIoIi0iLCAi4oiSIiwgbnVtKHIsIGFjY3VyYWN5ID0gMC4wMSkpDQp9DQoNCiMgUG9yIGdydXBvOiBjYW50aWRhZCBkZSBVUFogeSBwZXNvIGRlIGNhZGEgZGVsaXRvIChzb2JyZSBodXJ0byArIGxlc2lvbmVzICsgaG9taWNpZGlvcyArIHZpb2xlbmNpYSBpbnRyYWZhbWlsaWFyKQ0KcG9yX2dydXBvIDwtIHVweiAlPiUNCiAgZ3JvdXBfYnkoZ3J1cG9fcG9ibGFjaW9uKSAlPiUNCiAgc3VtbWFyaXNlKA0KICAgIG4gICAgICAgICA9IG4oKSwNCiAgICBkZWxpdG9zNCAgPSBzdW0oY2Fzb3NfaHVydG9fdG90YWwgKyBjYXNvc19sZXNpb25lcyArIGNhc29zX2hvbWljaWRpb3MgKyBjYXNvc192aW9sZW5jaWFfaW50cmFmYW1pbGlhciksDQogICAgcGN0X2h1cnRvID0gc3VtKGNhc29zX2h1cnRvX3RvdGFsKSAvIGRlbGl0b3M0LA0KICAgIHBjdF92aWYgICA9IHN1bShjYXNvc192aW9sZW5jaWFfaW50cmFmYW1pbGlhcikgLyBkZWxpdG9zNCwNCiAgICAuZ3JvdXBzID0gImRyb3AiDQogICkgJT4lDQogIHNwbGl0KC4kZ3J1cG9fcG9ibGFjaW9uKQ0KDQpnX2JhamEgIDwtIHBvcl9ncnVwb1tbIlBvYmxhY2nDs24gYmFqYSJdXQ0KZ19tZWRpYSA8LSBwb3JfZ3J1cG9bWyJQb2JsYWNpw7NuIG1lZGlhIl1dDQpnX2FsdGEgIDwtIHBvcl9ncnVwb1tbIlBvYmxhY2nDs24gYWx0YSJdXQ0KDQojIFJhbmtpbmc6IGNvbXBvc2ljacOzbiBkZWwgdG9wIGRlIG1heW9yZXMgeSBkZSBtZW5vcmVzIHRhc2FzIHRvdGFsZXMNCnRvcF9tYXkgPC0gdXB6ICU+JSBzbGljZV9tYXgodGFzYV90b3RhbCwgbiA9IHRvcF9uLCB3aXRoX3RpZXMgPSBGQUxTRSkNCnRvcF9tZW4gPC0gdXB6ICU+JSBzbGljZV9taW4odGFzYV90b3RhbCwgbiA9IHRvcF9uLCB3aXRoX3RpZXMgPSBGQUxTRSkNCm5fbWF5X2JhamEgIDwtIHN1bSh0b3BfbWF5JGdydXBvX3BvYmxhY2lvbiA9PSAiUG9ibGFjacOzbiBiYWphIikNCm5fbWVuX21lZGlhIDwtIHN1bSh0b3BfbWVuJGdydXBvX3BvYmxhY2lvbiA9PSAiUG9ibGFjacOzbiBtZWRpYSIpDQoNCiMgQWxlcnRhOiBzaSBjYW1iaWFuIGxvcyBkYXRvcyB5IGFsZ3VuYSBhZmlybWFjacOzbiBjdWFsaXRhdGl2YSBkZWwgdGV4dG8gZGVqYSBkZSBzZXIgY2llcnRhLCBlbCB0ZWppZG8gYXZpc2ENCnN0b3BpZm5vdCgNCiAgIkxvcyBhdMOtcGljb3MgZXh0cmVtb3MgeWEgbm8gZXN0w6FuIHNvbG8gZW4gcG9ibGFjacOzbiBiYWphOiByZXZpc2EgZWwgdGV4dG8gZGUgSGFsbGF6Z29zIiA9DQogICAgYWxsKHVweiRncnVwb19wb2JsYWNpb25bdXB6JGVzX2V4dHJlbW9dID09ICJQb2JsYWNpw7NuIGJhamEiKSwNCiAgIkVsIHRvcCBkZSBtYXlvcmVzIHRhc2FzIHlhIG5vIGVzIHRvZG8gZGUgcG9ibGFjacOzbiBiYWphOiByZXZpc2EgZWwgdGV4dG8gZGUgSGFsbGF6Z29zIiA9DQogICAgbl9tYXlfYmFqYSA9PSB0b3BfbiwNCiAgIkVuIGVsIHRvcCBkZSBtZW5vcmVzIHRhc2FzIHlhIG5vIHByZWRvbWluYSBwb2JsYWNpw7NuIG1lZGlhOiByZXZpc2EgZWwgdGV4dG8gZGUgSGFsbGF6Z29zIiA9DQogICAgbl9tZW5fbWVkaWEgPT0gbWF4KHRhYmxlKHRvcF9tZW4kZ3J1cG9fcG9ibGFjaW9uKSkNCikNCmBgYA0KDQojIDUgSGFsbGF6Z29zDQoNCi0gU2UgZXZpZGVuY2lhIHF1ZSBsYSBzZWdtZW50YWNpw7NuIG1lZGlhbnRlIEstbWVhbnMgcGVybWl0ZSBkaXZpZGlyIGxhIHBvYmxhY2nDs24gZGUgQm9nb3TDoSBkZSB1bmEgbWFuZXJhIG3DoXMgYWNvcmRlIGNvbiBsYSBjYW50aWRhZCBkZSBoYWJpdGFudGVzLiBTaW4gZW1iYXJnbywgZWwgZ3J1cG8gZGUgcG9ibGFjacOzbiBhbHRhIHF1ZWRhIGNvbmZvcm1hZG8gcG9yIHNvbG8gYHIgZ19hbHRhJG5gIFVQWi4gRXN0byBwZXJtaXRlIHBsYW50ZWFyIHF1ZSwgZW4gZnV0dXJvcyBhbsOhbGlzaXMsIHNlIGVzdHVkaWUgZXN0YSBzZWdtZW50YWNpw7NuIGRlIG1hbmVyYSBpbmRpdmlkdWFsIHBvciBVUFouDQoNCi0gTG9zIGF0w61waWNvcyBleHRyZW1vcyBzZSBwcmVzZW50YW4gw7puaWNhbWVudGUgZW4gZWwgZ3J1cG8gZGUgcG9ibGFjacOzbiBiYWphLiBTZWfDum4gZWwgYW7DoWxpc2lzLCBlc3RvcyBnZW5lcmFuIHVuIGF1bWVudG8gY29uc2lkZXJhYmxlIGVuIGxhIHRhc2EgdG90YWwgZGUgZGVsaXRvcy4gQWwgYW5hbGl6YXIgY29uIG1heW9yIGRldGFsbGUgbG9zIHJlc3VsdGFkb3MsIHNlIG9ic2VydmEgcXVlIHN1IGVmZWN0byBzZSBjb25jZW50cmEgcHJpbmNpcGFsbWVudGUgZW4gZWwgZGVsaXRvIGRlIGh1cnRvLiBTaW4gZW1iYXJnbywgZXMgaW1wb3J0YW50ZSB0ZW5lciBwcmVzZW50ZSBlc3RhIGluZm9ybWFjacOzbiBwYXJhIGVzdHVkaWFyIGVzdGFzIGByIG5fYXRpcGAgVVBaIHkgY29ub2NlciBsb3MgbW90aXZvcyBkZXRyw6FzIGRlIGxhIGNhbnRpZGFkIGRlIGRlbnVuY2lhcyByZWdpc3RyYWRhcy4NCg0KLSBFbCBodXJ0byBwcmVkb21pbmEgZW4gbG9zIHRyZXMgZ3J1cG9zIHBvYmxhY2lvbmFsZXMsIHBlcm8gcGVzYSBtZW5vcyBhIG1lZGlkYSBxdWUgY3JlY2UgbGEgcG9ibGFjacOzbjogYHIgcGN0KGdfYmFqYSRwY3RfaHVydG8pYCBlbiBwb2JsYWNpw7NuIGJhamEsIGByIHBjdChnX21lZGlhJHBjdF9odXJ0bylgIGVuIG1lZGlhIHkgYHIgcGN0KGdfYWx0YSRwY3RfaHVydG8pYCBlbiBhbHRhLiBMbyBjb250cmFyaW8gcGFzYSBjb24gbGEgdmlvbGVuY2lhIGludHJhZmFtaWxpYXIsIHF1ZSBzdWJlIGRlIGByIHBjdChnX2JhamEkcGN0X3ZpZilgIGEgYHIgcGN0KGdfbWVkaWEkcGN0X3ZpZilgIHkgYHIgcGN0KGdfYWx0YSRwY3RfdmlmKWAuDQoNCi0gRW4gZWwgcmFua2luZyB0b3AgYHIgdG9wX25gIGRlIG1heW9yZXMgdGFzYXMgZGUgZGVudW5jaWFzIHRvdGFsZXMsIHNvbG8gc2UgZXZpZGVuY2lhbiBVUFogY2F0ZWdvcml6YWRhcyBjb21vICJwb2JsYWNpb24gYmFqYSIsIHBlcm8gY3VhbmRvIHNlIHZhbGlkYSBhbCB0b3AgYHIgdG9wX25gIGRlIG1lbm9yZXMgdGFzYXMgZGUgZGVudW5jaWFzIHRvdGFsZXMsIHByZWRvbWluYSBwb2JsYWNpb24gbWVkaWEgY29uIGByIG5fbWVuX21lZGlhYCBVUFogZGUgYHIgdG9wX25gLCBzaWVuZG8gZXN0YXMgZWwgYHIgcGN0KG5fbWVuX21lZGlhIC8gZ19tZWRpYSRuKWAgZGUgdG9kYSBsYXMgY2F0ZWdvcml6YWRhcyBwb3IgcG9ibGFjaW9uIG1lZGlhIChgciBnX21lZGlhJG5gIFVQWikuDQoNCi0gRW4gcG9ibGFjacOzbiBiYWphIHkgbWVkaWEsIG3DoXMgcG9ibGFjacOzbiBqb3ZlbiBzZSBhc29jaWEgY29uIG3DoXMgdmlvbGVuY2lhIGludHJhZmFtaWxpYXIgeSBtZW5vcyBodXJ0bywgcG9yIGVqZW1wbG8sIGVuIHBvYmxhY2lvbiBtZWRpYSwgZW4gZWwgZ3J1cG8gMOKAkzE0IGHDsW9zIGNvbiB2aW9sZW5jaWEgaW50cmFmYW1pbGlhciBsYSBjb3JyZWxhY2lvbiBlcyDPgSA9IGByIHJob190eHQoIlBvYmxhY2nDs24gbWVkaWEiLCAiVmlvbGVuY2lhIGludHJhZmFtaWxpYXIiLCAiMOKAkzE0IGHDsW9zIilgIHkgY29uIGh1cnRvIM+BID0gYHIgcmhvX3R4dCgiUG9ibGFjacOzbiBtZWRpYSIsICJIdXJ0byIsICIw4oCTMTQgYcOxb3MiKWAuIFNvbiBhc29jaWFjaW9uZXMgZW50cmUgVVBaLCBubyBjYXVzYXMuDQoNCiMgNiBMaW1pdGFjaW9uZXMNCg0KLSBTZSBpZGVudGlmaWNhIHVuYSBvcG9ydHVuaWRhZCBkZSBtZWpvcmEgZW4gbG9zIGRhdG9zIGRpc3BvbmlibGVzOiBjb250YXIgY29uIHVuIG1heW9yIGRlc2dsb3NlIGRlIGxhcyBkZW51bmNpYXMgcG9yIGVkYWQgeSB0aXBvIGRlIGRlbGl0byBwZXJtaXRpcsOtYSwgY29tbyBzZSBtZW5jaW9uw7MgYW50ZXJpb3JtZW50ZSwgcmVhbGl6YXIgdW4gYW7DoWxpc2lzIGRlIGNvcnJlbGFjacOzbiBtw6FzIGRldGFsbGFkbyBlbnRyZSBlc3RhcyB2YXJpYWJsZXMuDQoNCi0gRWwgcGVyaW9kbyBkZSBhbsOhbGlzaXMgZGUgbGFzIGRlbnVuY2lhcyB5IGxhcyB0YXNhcyBjb21wcmVuZGUgw7puaWNhbWVudGUgNyBtZXNlcywgZGVqYW5kbyBwb3IgZnVlcmEgbG9zIMO6bHRpbW9zIG1lc2VzIGRlbCBhw7FvLiBFc3RvcyBtZXNlcyBwb2Ryw61hbiBzZXIgcmVsZXZhbnRlcywgY29uc2lkZXJhbmRvIHF1ZSBjb3JyZXNwb25kZW4gYSB1bmEgw6lwb2NhIGVuIGxhIHF1ZSBzZSBnZW5lcmFuIG3DoXMgaW50ZXJhY2Npb25lcyBlbnRyZSBsb3MgaGFiaXRhbnRlcyBkZSBCb2dvdMOhLiBFc3RvIHBsYW50ZWEgdW5hIHByZWd1bnRhOiDCv3NlIG1hbnRlbmRyw61hbiBsYXMgdGVuZGVuY2lhcyBvYnNlcnZhZGFzIGR1cmFudGUgZXNvcyA1IG1lc2VzIG8gc2UgcHJlc2VudGFyw61hbiBhdW1lbnRvcyBvIGRpc21pbnVjaW9uZXMgY29uc2lkZXJhYmxlcyBlbiBsYXMgdGFzYXMgZGUgZGVudW5jaWFzIHBvciBkZWxpdG9zPw0KDQojIDcgQ29uY2x1c2lvbg0KDQpMYSBzZWd1cmlkYWQgZW4gbGFzIFVQWiwgc2Vnw7puIGxvIHZhbGlkYWRvIGVuIGVsIGFuw6FsaXNpcywgcHJlc2VudGEgdmFyaWFzIG9ic2VydmFjaW9uZXMgYSB0ZW5lciBlbiBjdWVudGEuIFVuYSBlcyBxdWUsIGVudHJlIG3DoXMgcG9ibGFjacOzbiwgbcOhcyBjYXNvcyBkZSBkZW51bmNpYXM7IHNpbiBlbWJhcmdvLCBlc3RvIG5vIGFwbGljYSBkZWwgdG9kbyBjdWFuZG8gc2UgZXZpZGVuY2lhbiBhdMOtcGljb3MgZXh0cmVtb3MsIGVuIGxvcyBjdWFsZXMgbGEgcG9ibGFjacOzbiBlcmEgbXVjaG8gbWVub3IuIEFkZW3DoXMsIHRlbmllbmRvIGVuIGN1ZW50YSBsYSBwb2JsYWNpw7NuLCB0YW1wb2NvIGVzIHZpYWJsZSBkZWNpciBxdWUgZWwgw6FyZWEgc2UgcmVsYWNpb25hIGRpcmVjdGFtZW50ZSBjb24gbGEgY2FudGlkYWQgZGUgcGVyc29uYXMgcXVlIGhhYml0YW4gbGEgVVBaLCBkZWJpZG8gYSBxdWUgaGF5IGNhc29zIGVuIGxvcyBxdWUgdW5hIFVQWiBlcyBwZXF1ZcOxYSBlbiBjb21wYXJhY2nDs24gY29uIG90cmFzIG3DoXMgZ3JhbmRlcy4gU8OtIHNlIHB1ZWRlIGluZGljYXIgcXVlIGxhcyBkZW51bmNpYXMgcG9yIGh1cnRvIHByZWRvbWluYW4gYSBuaXZlbCBnZW5lcmFsIHkgcG9yIHNlZ21lbnRvIHBvYmxhY2lvbmFsLg0KDQpDb24gcmVzcGVjdG8gYSBsYXMgY29ycmVsYWNpb25lcywgZXMgbmVjZXNhcmlvIGVudGVuZGVyIHF1ZSBlbiBlc3RvcyBjYXNvcyBzZSBidXNjw7MgZ2VuZXJhciB1bmEgYXNvY2lhY2nDs24sIHF1ZSBkaXJlY3RhbWVudGUgbm8gaW5kaWNhIGNhdXNhbGlkYWQuIFBhcmEgcG9kZXIgYWZpcm1hciBlc3RhcyByZWxhY2lvbmVzLCBlcyBuZWNlc2FyaW8gY29udGFyIGNvbiBlbCBkZXRhbGxlIGRlIGxhIGVkYWQgeSBkZSBsYXMgZGVudW5jaWFzIGdlbmVyYWRhcyBwb3IgY2FkYSBkZWxpdG8uIEVzIGRlY2lyLCBwYXJhIHVuIHByaW1lciBhbsOhbGlzaXMgZWwgaW5mb3JtZSBlcyBtdXkgw7p0aWwsIGF1bnF1ZSBhYnJlIGxhIHB1ZXJ0YSBhIHVuIG1heW9yIGRldGFsbGUgeSBlbnRlbmRpbWllbnRvLCBjb21vIHBvciBlamVtcGxvIGVuIGxhcyBVUFogYXTDrXBpY2FzIGV4dHJlbWFzIGVuIHRhc2EgdG90YWwgZGUgZGVsaXRvcy4=