# ============================================================
# NORMALIZACION GENERAL DE TABLAS PARA HTML
# ============================================================

normalizar_tabla_utf8 <- function(tabla) {

  tabla <- as.data.frame(
    tabla,
    stringsAsFactors = FALSE,
    check.names = FALSE
  )

  # Normalizar nombres de columnas
  nombres_originales <- names(tabla)

  nombres_utf8 <- iconv(
    nombres_originales,
    from = "",
    to = "UTF-8",
    sub = "byte"
  )

  # Si el sistema no reconoce la codificación de origen,
  # intentar Windows-1252 únicamente sobre los nombres fallidos.
  nombres_fallidos <- is.na(nombres_utf8)

  if (any(nombres_fallidos)) {
    nombres_utf8[nombres_fallidos] <- iconv(
      nombres_originales[nombres_fallidos],
      from = "windows-1252",
      to = "UTF-8",
      sub = "byte"
    )
  }

  nombres_utf8 <- make.unique(
    nombres_utf8,
    sep = "_"
  )

  names(tabla) <- nombres_utf8

  # Normalizar columnas de texto
  columnas_texto <- vapply(
    tabla,
    function(x) {
      is.character(x) || is.factor(x)
    },
    logical(1)
  )

  tabla[columnas_texto] <- lapply(
    tabla[columnas_texto],
    function(x) {

      x <- as.character(x)

      convertido <- iconv(
        x,
        from = "",
        to = "UTF-8",
        sub = "byte"
      )

      fallidos <- is.na(convertido) & !is.na(x)

      if (any(fallidos)) {
        convertido[fallidos] <- iconv(
          x[fallidos],
          from = "windows-1252",
          to = "UTF-8",
          sub = "byte"
        )
      }

      convertido
    }
  )

  tabla
}
Logo Universidad Pontificia Javeriana

Informe 2 RMarkdown: aptitud climática global para caña de azúcar

Universidad Pontificia Javeriana

Presentado por: Dainer Orlando Cruz Chate

Trabajo presentado al profesor: David Arango Londoño

Curso: Análisis de Datos Geográficos y Espaciales

Unidad: 2

Fecha: 27/10/2026

1 Planteamiento del caso

Un agricultor desea identificar zonas del mundo con condiciones climáticas favorables para la caña de azúcar. Se aplican dos aproximaciones complementarias:

  1. Aptitud por umbrales: temperatura media anual entre 22,5 y 28 °C y precipitación anual entre 1.500 y 3.500 mm.
  2. Analogía climática: similitud entre el ciclo mensual de temperatura y precipitación de tres sitios del Valle del Cauca y las celdas climáticas del mundo.

Alcance: el resultado representa aptitud climática de línea base, no una predicción directa de rendimiento en kg/ha. Para estimar rendimiento potencial se requieren, además, radiación, suelo, relieve, variedad, duración del ciclo, manejo, riego y un modelo de cultivo calibrado, por ejemplo DSSAT-CANEGRO, APSIM-Sugar o AquaCrop.

2 Fuentes, criterios y método

Se emplea WorldClim 2.1, climatología de línea base 1970–2000. WorldClim ofrece capas mensuales de temperatura media y precipitación a varias resoluciones. Para que el informe pueda ejecutarse en un computador convencional se utiliza resolución de 10 minutos de arco; puede cambiarse a 5 o 2,5 minutos, con mayor costo de memoria y tiempo.

Los rangos de aptitud usados en este ejercicio proceden de las instrucciones del caso. Como referencia agronómica adicional, FAO señala que la producción de caña depende de clima, suelo, variedad y manejo, y reporta condiciones cálidas y pluviometría elevada como favorables.

Fuentes principales

2.1 Herramientas espaciales y compatibilidad

El flujo de trabajo integra raster para compatibilidad con las estructuras ráster solicitadas, leaflet para mapas interactivos, tmaptools para utilidades cartográficas y geocodificación, y sf/terra para las operaciones espaciales actuales. El paquete rgdal se comprueba en el entorno por exigencia del caso estudiado, pero no se fuerza su instalación porque fue retirado de CRAN; las transformaciones y lecturas se realizan con sf y terra para asegurar compatibilidad con versiones recientes de R.

# paquetes_caso <- data.frame(
#  Paquete = c("tmaptools", "leaflet", "raster", "rgdal"),
#  Función_en_el_informe = c("Utilidades cartográficas y geocodificación",
#                            "Visualización cartográfica interactiva",
#                            "Compatibilidad y manejo de capas ráster",
#                            "Compatibilidad heredada; sustituido operativamente por sf/terra"),
#  Disponible = c(requireNamespace("tmaptools", quietly = TRUE),
#                 requireNamespace("leaflet", quietly = TRUE),
#                 requireNamespace("raster", quietly = TRUE),
#                 rgdal_disponible)
# )
# knitr::kable(paquetes_caso, caption = "Paquetes espaciales contemplados") |>
#  kableExtra::kable_styling(bootstrap_options = c("striped", "hover"), full_width = TRUE)

# Tabla de paquetes espaciales utilizados o contemplados
paquetes_caso <- data.frame(
  Paquete = c(
    "tmaptools",
    "leaflet",
    "raster",
    "rgdal"
  ),
  Funcion_en_el_informe = c(
    "Utilidades cartograficas y geocodificacion",
    "Visualizacion cartografica interactiva",
    "Compatibilidad y manejo de capas raster",
    "Compatibilidad heredada; sustituido operativamente por sf/terra"
  ),
  Disponible = c(
    requireNamespace("tmaptools", quietly = TRUE),
    requireNamespace("leaflet", quietly = TRUE),
    requireNamespace("raster", quietly = TRUE),
    isTRUE(rgdal_disponible)
  ),
  stringsAsFactors = FALSE,
  check.names = FALSE
)

# Etiquetas comprensibles para la disponibilidad
paquetes_caso$Disponible <- ifelse(
  paquetes_caso$Disponible,
  "Si",
  "No"
)

# Convertir explícitamente el contenido a UTF-8
paquetes_caso[] <- lapply(
  paquetes_caso,
  function(x) {
    if (is.character(x)) {
      iconv(
        x,
        from = "",
        to = "UTF-8",
        sub = "byte"
      )
    } else {
      x
    }
  }
)

paquetes_caso <- normalizar_tabla_utf8(paquetes_caso)

# Generar la tabla HTML

tabla_paquetes_caso <- knitr::kable(
  paquetes_caso,
  format = "html",
  col.names = c(
    "Paquete",
    "Funcion en el informe",
    "Disponible"
  ),
  caption = "Paquetes espaciales contemplados",
  align = c("l", "l", "c"),
  escape = TRUE
)

kableExtra::kable_styling(
  tabla_paquetes_caso,
  bootstrap_options = c(
    "striped",
    "hover",
    "condensed",
    "responsive"
  ),
  full_width = TRUE,
  position = "center"
)
Paquetes espaciales contemplados
Paquete Funcion en el informe Disponible
tmaptools Utilidades cartograficas y geocodificacion Si
leaflet Visualizacion cartografica interactiva Si
raster Compatibilidad y manejo de capas raster Si
rgdal Compatibilidad heredada; sustituido operativamente por sf/terra No

2.2 Descarga y preparación de datos

# Climatologías mensuales globales. geodata almacena los archivos en ruta_datos.
tmed_m <- geodata::worldclim_global(var = "tavg", res = 10, path = ruta_datos)
prec_m <- geodata::worldclim_global(var = "prec", res = 10, path = ruta_datos)

names(tmed_m) <- paste0("tmed_", sprintf("%02d", 1:12))
names(prec_m) <- paste0("prec_", sprintf("%02d", 1:12))

# Verificación automática de unidades de temperatura.
# En algunas distribuciones raster la temperatura puede estar escalada por 10.
media_prueba <- global(tmed_m[[1]], "mean", na.rm = TRUE)[1, 1]
if (is.finite(media_prueba) && media_prueba > 60) tmed_m <- tmed_m / 10

# Agregados anuales
tmed_anual <- mean(tmed_m, na.rm = TRUE)
prec_anual <- sum(prec_m, na.rm = TRUE)
names(tmed_anual) <- "temperatura_media_C"
names(prec_anual) <- "precipitacion_anual_mm"

# Conversión explícita al formato RasterLayer solicitado en la rúbrica.
# Se conserva terra para cálculos robustos y raster para interoperabilidad.
tmed_anual_raster <- raster::raster(tmed_anual)
prec_anual_raster <- raster::raster(prec_anual)

# Límites nacionales, provistos por geodata/GADM
paises <- geodata::world(path = ruta_datos)

# Control básico
stopifnot(nlyr(tmed_m) == 12, nlyr(prec_m) == 12)
resumen_datos <- data.frame(
  Característica = c("Objeto", "Filas", "Columnas", "Capas climáticas",
                     "Resolución espacial", "Extensión", "Referencia espacial",
                     "Periodo climático"),
  Valor = c("Ráster multibanda global", terra::nrow(tmed_m), terra::ncol(tmed_m),
            terra::nlyr(tmed_m) + terra::nlyr(prec_m),
            paste(round(terra::res(tmed_m), 6), collapse = " × "),
            paste(round(as.vector(terra::ext(tmed_m)), 2), collapse = ", "),
            "WGS 84 (EPSG:4326)", "1970–2000")
)

resumen_datos <- normalizar_tabla_utf8(resumen_datos)
knitr::kable(resumen_datos, caption = "Características de los datos climáticos utilizados",
             align = c("l", "l")) |>
  kableExtra::kable_styling(bootstrap_options = c("striped", "hover", "condensed"),
                            full_width = TRUE)
Características de los datos climáticos utilizados
Característica Valor
Objeto Ráster multibanda global
Filas 1080
Columnas 2160
Capas climáticas 24
Resolución espacial 0.166667 × 0.166667
Extensión -180, 180, -90, 90
Referencia espacial WGS 84 (EPSG:4326)
Periodo climático 1970–2000

3 Aptitud climática por rangos óptimos

Se calcula primero la adecuación de cada variable en escala continua de 0 a 1 mediante una función trapezoidal con tolerancia exterior. Después se obtiene un índice conservador como el mínimo entre temperatura y precipitación. Así, una limitación fuerte en una variable no queda compensada por la otra.

# VERSIÓN 5: cálculo mediante álgebra raster nativa, sin funciones personalizadas.
# Los objetos espaciales se reconstruyen en cada renderizado y no se almacenan en caché.

# Adecuación térmica trapezoidal, calculada directamente con álgebra raster.
apt_temp <- terra::ifel(
  tmed_anual < 18 | tmed_anual > 35, 0,
  terra::ifel(
    tmed_anual < 22.5,
    (tmed_anual - 18) / (22.5 - 18),
    terra::ifel(
      tmed_anual <= 28,
      1,
      (35 - tmed_anual) / (35 - 28)
    )
  )
)

# Adecuación de precipitación trapezoidal.
apt_prec <- terra::ifel(
  prec_anual < 750 | prec_anual > 5000, 0,
  terra::ifel(
    prec_anual < 1500,
    (prec_anual - 750) / (1500 - 750),
    terra::ifel(
      prec_anual <= 3500,
      1,
      (5000 - prec_anual) / (5000 - 3500)
    )
  )
)

# El operador conservador toma la menor adecuación de las dos variables.
apt_climatica <- terra::ifel(apt_temp <= apt_prec, apt_temp, apt_prec)
names(apt_temp) <- "aptitud_temperatura"
names(apt_prec) <- "aptitud_precipitacion"
names(apt_climatica) <- "aptitud_climatica"

# Controles antes de continuar.
stopifnot(inherits(apt_climatica, "SpatRaster"))
stopifnot(terra::nlyr(apt_climatica) == 1)
# rango_aptitud <- terra::minmax(apt_climatica)
# print(rango_aptitud)
# Obtener los valores mínimo y máximo
rango_aptitud <- terra::minmax(apt_climatica)

# Construir una tabla organizada
tabla_rango_aptitud <- data.frame(
  Indicador = c(
    "Valor mínimo",
    "Valor máximo"
  ),
  `Índice de aptitud climática` = c(
    as.numeric(rango_aptitud[1, 1]),
    as.numeric(rango_aptitud[2, 1])
  ),
  Interpretación = c(
    "Ausencia de aptitud climática",
    "Aptitud climática óptima"
  ),
  check.names = FALSE
)


tabla_rango_aptitud <- normalizar_tabla_utf8(tabla_rango_aptitud)
# Presentar la tabla con formato profesional
knitr::kable(
  tabla_rango_aptitud,
  caption = "Rango del índice de aptitud climática para la caña de azúcar",
  col.names = c(
    "Indicador",
    "Índice de aptitud climática",
    "Interpretación"
  ),
  digits = 2,
  align = c("l", "c", "l")
) |>
  kableExtra::kable_styling(
    bootstrap_options = c(
      "striped",
      "hover",
      "condensed",
      "responsive"
    ),
    full_width = TRUE,
    position = "center"
  ) |>
  kableExtra::column_spec(
    column = 1,
    bold = TRUE,
    color = "#123F72"
  ) |>
  kableExtra::column_spec(
    column = 2,
    bold = TRUE,
    color = "white",
    background = "#1F5B96",
    width = "4cm"
  ) |>
  kableExtra::column_spec(
    column = 3,
    width = "7cm"
  )
Rango del índice de aptitud climática para la caña de azúcar
Indicador Índice de aptitud climática Interpretación
Valor mínimo 0 Ausencia de aptitud climática
Valor máximo 1 Aptitud climática óptima
clase_aptitud <- terra::classify(
  apt_climatica,
  rcl = matrix(c(
    -Inf, 0.25, 1,
    0.25, 0.50, 2,
    0.50, 0.75, 3,
    0.75, Inf, 4
  ), ncol = 3, byrow = TRUE),
  include.lowest = TRUE,
  right = FALSE
)

levels(clase_aptitud) <- data.frame(
  value = 1:4,
  clase = c("Muy baja", "Baja", "Media", "Alta")
)

terra::writeRaster(
  c(tmed_anual, prec_anual, apt_temp, apt_prec, apt_climatica),
  file.path(ruta_resultados, "aptitud_climatica_cana.tif"),
  overwrite = TRUE
)

3.1 Mapas globales

plot(apt_climatica,
     col = hcl.colors(100, "YlGn", rev = FALSE),
     range = c(0, 1),
     main = "Aptitud climática global para caña de azúcar",
     axes = TRUE)
lines(paises, col = "grey35", lwd = 0.25)

plot(clase_aptitud,
     col = c("#d73027", "#fee08b", "#91cf60", "#1a9850"),
     main = "Clases de aptitud climática")
lines(paises, col = "grey30", lwd = 0.25)

3.2 Países con mayor superficie de aptitud alta

En lugar de escoger países subjetivamente, se calcula la superficie de celdas con índice mayor o igual a 0,75. La superficie se estima con cellSize() en km² y se resume por país.

area_alta <- ifel(apt_climatica >= 0.75, cellSize(apt_climatica, unit = "km"), NA)
area_por_pais <- terra::extract(area_alta, paises, fun = sum, na.rm = TRUE)

campo_nombre <- intersect(c("NAME_0", "COUNTRY", "NAME_ENGLI", "ADMIN"), names(paises))[1]
if (is.na(campo_nombre)) stop("No se encontró el campo de nombre de país en la capa mundial.")

ranking_paises <- data.frame(
  pais = paises[[campo_nombre]],
  area_alta_km2 = area_por_pais[, 2]
) |>
  dplyr::filter(is.finite(area_alta_km2), area_alta_km2 > 0) |>
  dplyr::arrange(dplyr::desc(area_alta_km2)) |>
  dplyr::mutate(area_alta_km2 = round(area_alta_km2, 0))

knitr::kable(head(ranking_paises, 10),
             caption = "Diez países con mayor superficie climáticamente apta") |>
  kableExtra::kable_styling(bootstrap_options = c("striped", "hover", "condensed"),
                            full_width = TRUE)
Diez países con mayor superficie climáticamente apta
NAME_0 area_alta_km2
Brazil 5761923
Democratic Republic of the Congo 1800743
Indonesia 1602374
Colombia 860486
India 681546
Peru 668296
Venezuela 645191
Bolivia 462795
Central African Republic 392362
Cameroon 366904
paises_top3_nombres <- head(ranking_paises$pais, 3)
# El objeto se reconstruye después de aplicar, si fuera necesario, el respaldo
# por aptitud máxima en el bloque de mapas.
paises_top3 <- paises[paises[[campo_nombre]] %in% paises_top3_nombres, ]

3.3 Mapas de países con mayor superficie climática apta (Top 3)

# ==============================================================
# MAPAS DE PAÍSES CON MAYOR SUPERFICIE CLIMÁTICAMENTE APTA
# ==============================================================

# 1. Validar los objetos requeridos
if (!exists("apt_climatica")) {
  stop("No existe el objeto 'apt_climatica'.")
}

if (!exists("paises")) {
  stop("No existe el objeto espacial 'paises'.")
}

if (!inherits(apt_climatica, "SpatRaster")) {
  stop("'apt_climatica' no es un objeto SpatRaster.")
}

if (!inherits(paises, "SpatVector")) {
  stop("'paises' no es un objeto SpatVector.")
}

# 2. Extraer los atributos de la capa espacial
atributos_paises <- terra::values(
  paises,
  dataframe = TRUE
)

if (
  !is.data.frame(atributos_paises) ||
  nrow(atributos_paises) == 0L
) {
  stop(
    "La capa mundial no contiene una tabla de atributos válida."
  )
}

# 3. Identificar automáticamente la columna con el nombre del país
campos_posibles <- c(
  "NAME_0",
  "COUNTRY",
  "ADMIN",
  "NAME_ENGLI",
  "NAME",
  "SOVEREIGNT"
)

campos_encontrados <- intersect(
  campos_posibles,
  names(atributos_paises)
)

if (length(campos_encontrados) > 0L) {

  campo_nombre <- campos_encontrados[1]

} else {

  columnas_texto <- names(atributos_paises)[
    vapply(
      atributos_paises,
      function(x) {
        is.character(x) || is.factor(x)
      },
      logical(1)
    )
  ]

  if (length(columnas_texto) == 0L) {
    stop(
      paste0(
        "No se identificó una columna de nombres. ",
        "Columnas disponibles: ",
        paste(names(atributos_paises), collapse = ", ")
      )
    )
  }

  cantidad_unicos <- vapply(
    atributos_paises[columnas_texto],
    function(x) {
      length(
        unique(
          stats::na.omit(
            as.character(x)
          )
        )
      )
    },
    integer(1)
  )

  campo_nombre <- columnas_texto[
    which.max(cantidad_unicos)
  ]
}

# 4. Extraer los nombres de países
nombres_paises <- trimws(
  as.character(
    atributos_paises[[campo_nombre]]
  )
)

numero_paises <- length(nombres_paises)

if (numero_paises == 0L) {
  stop(
    paste0(
      "La columna '",
      campo_nombre,
      "' no contiene nombres de países."
    )
  )
}

if (numero_paises != terra::nrow(paises)) {
  stop(
    paste0(
      "La cantidad de nombres (",
      numero_paises,
      ") no coincide con la cantidad de geometrías (",
      terra::nrow(paises),
      ")."
    )
  )
}

# 5. Construir el catálogo, conservando el índice espacial
catalogo_paises <- data.frame(
  ID = seq_along(nombres_paises),
  pais = nombres_paises,
  stringsAsFactors = FALSE
)

# 6. Calcular el área de cada celda en km²
area_celda_km2 <- terra::cellSize(
  apt_climatica,
  unit = "km"
)

# 7. Conservar el área con aptitud climática alta
area_alta <- terra::ifel(
  apt_climatica >= 0.75,
  area_celda_km2,
  NA
)

names(area_alta) <- "area_alta_km2"

# 8. Calcular cada estadística por separado
# No se utilizan funciones personalizadas multivariadas.

resultado_area <- terra::extract(
  area_alta,
  paises,
  fun = "sum",
  na.rm = TRUE
)

resultado_media <- terra::extract(
  apt_climatica,
  paises,
  fun = "mean",
  na.rm = TRUE
)

resultado_maxima <- terra::extract(
  apt_climatica,
  paises,
  fun = "max",
  na.rm = TRUE
)

# 9. Verificar y normalizar las tablas extraídas
if (ncol(resultado_area) < 2L) {
  stop("No se pudo calcular el área altamente apta por país.")
}

if (ncol(resultado_media) < 2L) {
  stop("No se pudo calcular la aptitud media por país.")
}

if (ncol(resultado_maxima) < 2L) {
  stop("No se pudo calcular la aptitud máxima por país.")
}

resultado_area <- data.frame(
  ID = as.integer(resultado_area[[1]]),
  area_alta_km2 = as.numeric(resultado_area[[2]])
)

resultado_media <- data.frame(
  ID = as.integer(resultado_media[[1]]),
  aptitud_media = as.numeric(resultado_media[[2]])
)

resultado_maxima <- data.frame(
  ID = as.integer(resultado_maxima[[1]]),
  aptitud_maxima = as.numeric(resultado_maxima[[2]])
)

# 10. Integrar los resultados
estadisticas_paises <- catalogo_paises |>
  dplyr::left_join(
    resultado_area,
    by = "ID"
  ) |>
  dplyr::left_join(
    resultado_media,
    by = "ID"
  ) |>
  dplyr::left_join(
    resultado_maxima,
    by = "ID"
  )

# Los países sin área de aptitud alta reciben cero
estadisticas_paises$area_alta_km2[
  is.na(estadisticas_paises$area_alta_km2)
] <- 0

# 11. Validación del resultado integrado
stopifnot(
  nrow(estadisticas_paises) == numero_paises,
  is.numeric(estadisticas_paises$area_alta_km2),
  is.numeric(estadisticas_paises$aptitud_media),
  is.numeric(estadisticas_paises$aptitud_maxima)
)

# 12. Conservar países con nombres y datos climáticos válidos
estadisticas_validas <- estadisticas_paises |>
  dplyr::filter(
    !is.na(pais),
    nzchar(trimws(pais)),
    is.finite(aptitud_maxima)
  ) |>
  dplyr::arrange(
    dplyr::desc(area_alta_km2),
    dplyr::desc(aptitud_media),
    dplyr::desc(aptitud_maxima)
  )

if (nrow(estadisticas_validas) == 0L) {
  stop(
    "No existen países con estadísticas climáticas válidas."
  )
}

# 13. Seleccionar los tres primeros países
top_paises <- estadisticas_validas |>
  dplyr::slice_head(
    n = min(3L, nrow(estadisticas_validas))
  )

paises_top3_nombres <- top_paises$pais
n_mapas <- nrow(top_paises)

# cat(
#  "Campo utilizado para el nombre del país:",
#  campo_nombre,
#  "\n"
# )

# cat(
#  "Países seleccionados:",
#  paste(paises_top3_nombres, collapse = ", "),
#  "\n"
# )

# Traducir los nombres para la presentación del informe
traduccion_paises <- c(
  "Brazil" = "Brasil",
  "Democratic Republic of the Congo" = "República Democrática del Congo",
  "Indonesia" = "Indonesia"
)

paises_presentacion <- ifelse(
  paises_top3_nombres %in% names(traduccion_paises),
  traduccion_paises[paises_top3_nombres],
  paises_top3_nombres
)

# Construir un recuadro HTML profesional
html_paises <- paste0(
  '<div class="recuadro-seleccion">',
  '<div class="recuadro-titulo">',
  'Países seleccionados para el análisis detallado',
  '</div>',
  '<div class="recuadro-contenido">',
  paste(
    paste0(
      '<span class="etiqueta-pais">',
      paises_presentacion,
      '</span>'
    ),
    collapse = " "
  ),
  '</div>',
  '<div class="recuadro-nota">',
  'Selección basada en la superficie estimada con aptitud climática alta.',
  '</div>',
  '</div>'
)

htmltools::HTML(html_paises)
Países seleccionados para el análisis detallado
Brasil República Democrática del Congo Indonesia
Selección basada en la superficie estimada con aptitud climática alta.
# 14. Tabla de resultados
tabla_top_paises <- top_paises |>
  dplyr::transmute(
    País = pais,
    `Área con aptitud alta (km²)` = round(
      area_alta_km2,
      0
    ),
    `Aptitud media` = round(
      aptitud_media,
      3
    ),
    `Aptitud máxima` = round(
      aptitud_maxima,
      3
    )
  )

tabla_top_paises<- normalizar_tabla_utf8(tabla_top_paises)
tabla_resultados <- knitr::kable(
  tabla_top_paises,
  caption = paste(
    "Países con mayor superficie climáticamente",
    "apta para caña de azúcar"
  ),
  align = c("l", "r", "c", "c"),
  format.args = list(
    big.mark = ".",
    decimal.mark = ","
  )
) |>
  kableExtra::kable_styling(
    bootstrap_options = c(
      "striped",
      "hover",
      "condensed",
      "responsive"
    ),
    full_width = TRUE,
    position = "center"
  )

# print(tabla_resultados)
tabla_resultados
Países con mayor superficie climáticamente apta para caña de azúcar
País Área con aptitud alta (km²) Aptitud media Aptitud máxima
Brazil 5.761.923 0,756 1
Democratic Republic of the Congo 1.800.743 0,860 1
Indonesia 1.602.374 0,904 1
# 15. Guardar la configuración gráfica
configuracion_anterior <- graphics::par(
  no.readonly = TRUE
)

# 16. Definir la disposición de los mapas
graphics::par(
  mfrow = c(n_mapas, 1),
  mar = c(3.5, 3.5, 4.5, 5.5),
  oma = c(0, 0, 2, 0)
)

# 17. Crear los mapas
for (i in seq_len(n_mapas)) {

  id_pais <- as.integer(
    top_paises$ID[i]
  )

  nombre_pais <- as.character(
    top_paises$pais[i]
  )

  if (
    !is.finite(id_pais) ||
    id_pais < 1L ||
    id_pais > terra::nrow(paises)
  ) {
    graphics::plot.new()

    graphics::text(
      0.5,
      0.5,
      paste(
        "Identificador espacial inválido para",
        nombre_pais
      )
    )

  } else {

    poligono_pais <- paises[id_pais, ]

    raster_pais <- terra::crop(
      apt_climatica,
      poligono_pais,
      snap = "out"
    )

    raster_pais <- terra::mask(
      raster_pais,
      poligono_pais
    )

    cantidad_celdas <- terra::global(
      !is.na(raster_pais),
      fun = "sum",
      na.rm = TRUE
    )[1, 1]

    if (
      is.na(cantidad_celdas) ||
      cantidad_celdas == 0
    ) {
      graphics::plot.new()

      graphics::text(
        0.5,
        0.5,
        paste(
          "No existen datos climáticos válidos para",
          nombre_pais
        )
      )

    } else {

      terra::plot(
        raster_pais,
        col = hcl.colors(
          100,
          "YlGn"
        ),
        range = c(0, 1),
        axes = TRUE,
        main = paste(
          "Aptitud climática para caña de azúcar:",
          nombre_pais
        ),
        plg = list(
          title = "Índice de aptitud",
          cex = 0.8
        )
      )

      terra::lines(
        poligono_pais,
        col = "#263238",
        lwd = 1.1
      )
    }
  }
}

# 18. Título general
graphics::mtext(
  text = "Países con mayor superficie climáticamente apta",
  side = 3,
  outer = TRUE,
  line = 0.3,
  cex = 1.15,
  font = 2,
  col = "#123f72"
)

# 19. Restaurar la configuración gráfica
graphics::par(
  configuracion_anterior
)

Interpretación. Los tres países anteriores no se fijan de antemano: son el resultado del área clasificada como alta bajo la resolución, periodo y umbrales seleccionados. La presencia de aptitud climática no implica que toda esa superficie esté disponible para agricultura; pueden existir bosques, áreas protegidas, ciudades, pendientes fuertes o suelos no aptos.

4 Sitios de referencia en el Valle del Cauca

Se usan tres puntos representativos del corredor geográfico del valle: Palmira, Candelaria y Zarzal. Son puntos urbanos de referencia, no parcelas agrícolas. Las ubicaciones se representan directamente en un mapa interactivo elaborado dentro del informe. El mapa utiliza teselas de OpenStreetMap, no requiere claves API y muestra los tres sitios con etiquetas permanentes.

sitios <- data.frame(
  sitio = c("Palmira", "Candelaria", "Zarzal"),
  longitud = c(-76.3036, -76.3482, -76.0715),
  latitud  = c(  3.5394,   3.4067,   4.3946)
)

sitios_v <- vect(sitios, geom = c("longitud", "latitud"), crs = "EPSG:4326")
sitios <- normalizar_tabla_utf8(sitios)
knitr::kable(sitios, caption = "Sitios climáticos de referencia en el Valle del Cauca") |>
  kableExtra::kable_styling(bootstrap_options = c("striped", "hover"), full_width = TRUE)
Sitios climáticos de referencia en el Valle del Cauca
sitio longitud latitud
Palmira -76.3036 3.5394
Candelaria -76.3482 3.4067
Zarzal -76.0715 4.3946
# Mapa interactivo sin dependencia de CARTO ni claves API.
# OpenStreetMap se agrega mediante su URL oficial y atribución visible.
paleta_sitios <- leaflet::colorFactor(
  palette = c("#123f72", "#c7a34b", "#2f855a"),
  domain = sitios$sitio
)

mapa_valle <- leaflet::leaflet(
  data = sitios,
  width = "100%",
  height = 560,
  options = leaflet::leafletOptions(minZoom = 7, maxZoom = 18)
) |>
  leaflet::addTiles(
    urlTemplate = "https://tile.openstreetmap.org/{z}/{x}/{y}.png",
    attribution = "&copy; OpenStreetMap contributors",
    options = leaflet::tileOptions(maxZoom = 19)
  ) |>
  leaflet::addCircleMarkers(
    lng = ~longitud,
    lat = ~latitud,
    radius = 9,
    stroke = TRUE,
    weight = 3,
    color = "#ffffff",
    fillColor = ~paleta_sitios(sitio),
    fillOpacity = 1,
    label = ~sitio,
    labelOptions = leaflet::labelOptions(
      noHide = TRUE,
      direction = "top",
      offset = c(0, -10),
      textOnly = FALSE,
      style = list(
        "font-weight" = "bold",
        "color" = "#123f72",
        "background-color" = "white",
        "border" = "1px solid #123f72",
        "padding" = "3px 6px"
      )
    ),
    popup = ~paste0(
      "<strong>", sitio, "</strong><br>",
      "Latitud: ", sprintf("%.4f", latitud), "<br>",
      "Longitud: ", sprintf("%.4f", longitud)
    )
  ) |>
  leaflet::fitBounds(
    lng1 = min(sitios$longitud) - 0.12,
    lat1 = min(sitios$latitud) - 0.12,
    lng2 = max(sitios$longitud) + 0.12,
    lat2 = max(sitios$latitud) + 0.12
  ) |>
  leaflet::addScaleBar(position = "bottomleft")

mapa_valle

4.1 Extracción climática y series mensuales

WorldClim es una climatología mensual 1970–2000. Por tanto, las gráficas muestran el ciclo promedio enero–diciembre, no una serie interanual de observaciones.

clima_mensual <- c(tmed_m, prec_m)
ext_sitios <- terra::extract(clima_mensual, sitios_v)

serie_temp <- ext_sitios[, 2:13] |>
  dplyr::mutate(sitio = sitios$sitio) |>
  tidyr::pivot_longer(-sitio, names_to = "variable", values_to = "temperatura_C") |>
  dplyr::mutate(mes = rep(1:12, times = nrow(sitios)))

serie_prec <- ext_sitios[, 14:25] |>
  dplyr::mutate(sitio = sitios$sitio) |>
  tidyr::pivot_longer(-sitio, names_to = "variable", values_to = "precipitacion_mm") |>
  dplyr::mutate(mes = rep(1:12, times = nrow(sitios)))

serie_sitios <- dplyr::left_join(
  dplyr::select(serie_temp, sitio, mes, temperatura_C),
  dplyr::select(serie_prec, sitio, mes, precipitacion_mm),
  by = c("sitio", "mes")
) |>
  dplyr::mutate(mes_nombre = factor(month.abb[mes], levels = month.abb))

serie_sitios <- normalizar_tabla_utf8(serie_sitios)
knitr::kable(serie_sitios, digits = 1,
             caption = "Climatología mensual extraída en los sitios") |>
  kableExtra::kable_styling(bootstrap_options = c("striped", "hover", "condensed"),
                            full_width = TRUE)
Climatología mensual extraída en los sitios
sitio mes temperatura_C precipitacion_mm mes_nombre
Palmira 1 22.5 92 Jan
Palmira 2 22.6 102 Feb
Palmira 3 22.7 132 Mar
Palmira 4 22.4 172 Apr
Palmira 5 22.4 146 May
Palmira 6 22.3 112 Jun
Palmira 7 22.6 61 Jul
Palmira 8 22.7 73 Aug
Palmira 9 22.6 105 Sep
Palmira 10 22.0 195 Oct
Palmira 11 21.9 173 Nov
Palmira 12 22.1 121 Dec
Candelaria 1 23.8 95 Jan
Candelaria 2 23.9 110 Feb
Candelaria 3 24.0 142 Mar
Candelaria 4 23.8 185 Apr
Candelaria 5 23.7 168 May
Candelaria 6 23.7 106 Jun
Candelaria 7 24.0 55 Jul
Candelaria 8 24.1 78 Aug
Candelaria 9 23.9 112 Sep
Candelaria 10 23.4 210 Oct
Candelaria 11 23.3 176 Nov
Candelaria 12 23.5 128 Dec
Zarzal 1 23.5 81 Jan
Zarzal 2 23.7 102 Feb
Zarzal 3 23.8 141 Mar
Zarzal 4 23.5 193 Apr
Zarzal 5 23.4 182 May
Zarzal 6 23.3 127 Jun
Zarzal 7 23.8 74 Jul
Zarzal 8 23.8 105 Aug
Zarzal 9 23.5 135 Sep
Zarzal 10 22.8 214 Oct
Zarzal 11 22.7 168 Nov
Zarzal 12 23.0 110 Dec
g_temp <- ggplot(serie_sitios, aes(mes_nombre, temperatura_C, color = sitio, group = sitio)) +
  geom_line(linewidth = 0.9) + geom_point(size = 1.8) +
  scale_color_viridis_d(option = "C", end = 0.85) +
  labs(title = "Ciclo climatológico mensual de temperatura",
       x = "Mes", y = "Temperatura media (°C)", color = "Sitio") +
  theme_minimal(base_size = 11) + theme(legend.position = "bottom")

g_prec <- ggplot(serie_sitios, aes(mes_nombre, precipitacion_mm, color = sitio, group = sitio)) +
  geom_line(linewidth = 0.9) + geom_point(size = 1.8) +
  scale_color_viridis_d(option = "D", end = 0.85) +
  labs(title = "Ciclo climatológico mensual de precipitación",
       x = "Mes", y = "Precipitación (mm/mes)", color = "Sitio") +
  theme_minimal(base_size = 11) + theme(legend.position = "bottom")

g_temp / g_prec

5 Similaridad climática global

La distancia euclidiana directa entre variables con unidades diferentes estaría dominada por la precipitación. Para evitarlo, las 24 variables mensuales se estandarizan con media 0 y desviación estándar 1. Para cada sitio se calcula:

\[ d_i = \sqrt{\sum_{j=1}^{24}(z_{ij}-z_{sj})^2} \]

Luego se transforma la distancia en una similaridad acotada entre 0 y 1:

\[ S_i = \exp(-d_i) \]

Valores cercanos a 1 representan mayor analogía climática con el sitio del Valle del Cauca.

clima_24 <- c(tmed_m, prec_m)

# Estadísticos globales por capa; se aplican los mismos parámetros a raster y sitios.
medias <- as.numeric(global(clima_24, "mean", na.rm = TRUE)[, 1])
desv <- as.numeric(global(clima_24, "sd", na.rm = TRUE)[, 1])
if (any(!is.finite(desv) | desv == 0)) stop("Hay capas sin variabilidad válida.")

clima_z <- (clima_24 - medias) / desv
sitios_24 <- as.matrix(ext_sitios[, 2:25])
sitios_z <- sweep(sweep(sitios_24, 2, medias, "-"), 2, desv, "/")

mapas_sim <- list()
for (i in seq_len(nrow(sitios))) {
  dif2 <- (clima_z - sitios_z[i, ])^2
  distancia <- sqrt(sum(dif2, na.rm = FALSE))
  similitud <- exp(-distancia)
  names(similitud) <- paste0("sim_", sitios$sitio[i])
  mapas_sim[[i]] <- similitud
}
sim_global <- rast(mapas_sim)
writeRaster(sim_global, file.path(ruta_resultados, "similaridad_sitios_valle.tif"), overwrite = TRUE)
par(mfrow = c(3, 1), mar = c(2, 3, 3, 5))
for (i in 1:3) {
  plot(sim_global[[i]], col = hcl.colors(100, "Inferno", rev = TRUE),
       range = c(0, 1), main = paste("Similaridad climática con", sitios$sitio[i]),
       axes = TRUE)
  lines(paises, col = "grey70", lwd = 0.2)
}

par(mfrow = c(1, 1))

5.1 Consenso entre sitios

El promedio resume la analogía general con los tres sitios; el mínimo es más exigente y conserva únicamente zonas semejantes a todos ellos.

sim_promedio <- mean(sim_global, na.rm = TRUE)
sim_conservadora <- terra::ifel(
  sim_global[[1]] <= sim_global[[2]] & sim_global[[1]] <= sim_global[[3]], sim_global[[1]],
  terra::ifel(sim_global[[2]] <= sim_global[[3]], sim_global[[2]], sim_global[[3]])
)
names(sim_promedio) <- "similitud_promedio"
names(sim_conservadora) <- "similitud_conservadora"

par(mfrow = c(2, 1), mar = c(2, 3, 3, 5))
plot(sim_promedio, col = hcl.colors(100, "Inferno", rev = TRUE), range = c(0, 1),
     main = "Similaridad promedio con los sitios del Valle del Cauca")
lines(paises, col = "grey70", lwd = 0.2)
plot(sim_conservadora, col = hcl.colors(100, "Inferno", rev = TRUE), range = c(0, 1),
     main = "Similaridad conservadora: mínimo entre los tres sitios")
lines(paises, col = "grey70", lwd = 0.2)

par(mfrow = c(1, 1))
# ==============================================================
# SIMILARIDAD CLIMÁTICA GLOBAL
# CHUNK AUTÓNOMO: NO DEPENDE DE OBJETOS PREVIOS EN MEMORIA
# ==============================================================

construir_similaridad_climatica <- function() {

  # ------------------------------------------------------------
  # 1. Verificar paquetes
  # ------------------------------------------------------------

  paquetes_necesarios <- c(
    "terra",
    "geodata",
    "knitr",
    "kableExtra"
  )

  paquetes_faltantes <- paquetes_necesarios[
    !vapply(
      paquetes_necesarios,
      requireNamespace,
      logical(1),
      quietly = TRUE
    )
  ]

  if (length(paquetes_faltantes) > 0L) {
    stop(
      paste0(
        "Faltan los paquetes: ",
        paste(paquetes_faltantes, collapse = ", "),
        ". Instálelos antes de renderizar."
      ),
      call. = FALSE
    )
  }

  terra::terraOptions(progress = 0)

  # ------------------------------------------------------------
  # 2. Crear las carpetas necesarias
  # ------------------------------------------------------------

  ruta_datos_local <- file.path(
    getwd(),
    "datos_clima"
  )

  ruta_resultados_local <- file.path(
    getwd(),
    "resultados_informe2"
  )

  dir.create(
    ruta_datos_local,
    recursive = TRUE,
    showWarnings = FALSE
  )

  dir.create(
    ruta_resultados_local,
    recursive = TRUE,
    showWarnings = FALSE
  )

  # ------------------------------------------------------------
  # 3. Cargar o descargar WorldClim
  # ------------------------------------------------------------

  tmed_local <- geodata::worldclim_global(
    var = "tavg",
    res = 10,
    path = ruta_datos_local
  )

  prec_local <- geodata::worldclim_global(
    var = "prec",
    res = 10,
    path = ruta_datos_local
  )

  if (!inherits(tmed_local, "SpatRaster")) {
    stop(
      "No fue posible cargar la temperatura mensual.",
      call. = FALSE
    )
  }

  if (!inherits(prec_local, "SpatRaster")) {
    stop(
      "No fue posible cargar la precipitación mensual.",
      call. = FALSE
    )
  }

  if (terra::nlyr(tmed_local) != 12L) {
    stop(
      paste0(
        "La temperatura debe contener 12 capas, pero contiene ",
        terra::nlyr(tmed_local),
        "."
      ),
      call. = FALSE
    )
  }

  if (terra::nlyr(prec_local) != 12L) {
    stop(
      paste0(
        "La precipitación debe contener 12 capas, pero contiene ",
        terra::nlyr(prec_local),
        "."
      ),
      call. = FALSE
    )
  }

  if (
    !isTRUE(
      terra::compareGeom(
        tmed_local,
        prec_local,
        stopOnError = FALSE
      )
    )
  ) {
    stop(
      paste(
        "Las capas de temperatura y precipitación",
        "no son espacialmente compatibles."
      ),
      call. = FALSE
    )
  }

  names(tmed_local) <- paste0(
    "tmed_",
    sprintf("%02d", 1:12)
  )

  names(prec_local) <- paste0(
    "prec_",
    sprintf("%02d", 1:12)
  )

  # WorldClim puede almacenar temperatura con factor 10.
  temperatura_prueba <- terra::global(
    tmed_local[[1]],
    fun = "mean",
    na.rm = TRUE
  )[1, 1]

  if (
    is.finite(temperatura_prueba) &&
    temperatura_prueba > 60
  ) {
    tmed_local <- tmed_local / 10
  }

  # ------------------------------------------------------------
  # 4. Construir las 24 capas climáticas
  # ------------------------------------------------------------

  clima_local <- c(
    tmed_local,
    prec_local
  )

  if (terra::nlyr(clima_local) != 24L) {
    stop(
      "No fue posible construir las 24 capas climáticas.",
      call. = FALSE
    )
  }

  # ------------------------------------------------------------
  # 5. Crear autónomamente los sitios del Valle del Cauca
  # ------------------------------------------------------------

  sitios_local <- data.frame(
    sitio = c(
      "Palmira",
      "Candelaria",
      "Zarzal"
    ),
    longitud = c(
      -76.3036,
      -76.3482,
      -76.0715
    ),
    latitud = c(
      3.5394,
      3.4067,
      4.3946
    ),
    stringsAsFactors = FALSE
  )

  sitios_vector <- terra::vect(
    sitios_local,
    geom = c(
      "longitud",
      "latitud"
    ),
    crs = "EPSG:4326"
  )

  # ------------------------------------------------------------
  # 6. Extraer el clima de los sitios
  # ------------------------------------------------------------

  extraccion_local <- terra::extract(
    clima_local,
    sitios_vector
  )

  if (
    !is.data.frame(extraccion_local) ||
    nrow(extraccion_local) != nrow(sitios_local) ||
    ncol(extraccion_local) != 25L
  ) {
    stop(
      paste0(
        "La extracción climática no tiene la estructura esperada. ",
        "Dimensiones obtenidas: ",
        nrow(extraccion_local),
        " × ",
        ncol(extraccion_local),
        "."
      ),
      call. = FALSE
    )
  }

  sitios_clima <- as.matrix(
    extraccion_local[
      ,
      2:25,
      drop = FALSE
    ]
  )

  storage.mode(sitios_clima) <- "numeric"

  if (any(!is.finite(sitios_clima))) {
    stop(
      "La extracción climática de los sitios contiene valores no válidos.",
      call. = FALSE
    )
  }

  # ------------------------------------------------------------
  # 7. Calcular estadísticas globales por capa
  # ------------------------------------------------------------

  medias_tabla <- terra::global(
    clima_local,
    fun = "mean",
    na.rm = TRUE
  )

  desviaciones_tabla <- terra::global(
    clima_local,
    fun = "sd",
    na.rm = TRUE
  )

  medias_locales <- as.numeric(
    medias_tabla[, 1]
  )

  desviaciones_locales <- as.numeric(
    desviaciones_tabla[, 1]
  )

  if (
    length(medias_locales) != 24L ||
    length(desviaciones_locales) != 24L
  ) {
    stop(
      "No se obtuvieron 24 medias y 24 desviaciones estándar.",
      call. = FALSE
    )
  }

  if (any(!is.finite(medias_locales))) {
    stop(
      "Existen medias climáticas no válidas.",
      call. = FALSE
    )
  }

  if (
    any(
      !is.finite(desviaciones_locales) |
      desviaciones_locales <= 0
    )
  ) {
    stop(
      "Existen capas climáticas sin variabilidad válida.",
      call. = FALSE
    )
  }

  # `terra::global()` devuelve una fila por capa.
  # Las funciones incorporadas mean y sd son adecuadas
  # para rásteres globales grandes. 

  # ------------------------------------------------------------
  # 8. Estandarizar las capas climáticas
  # ------------------------------------------------------------

  capas_estandarizadas <- vector(
    mode = "list",
    length = 24L
  )

  for (j in seq_len(24L)) {

    capas_estandarizadas[[j]] <- (
      clima_local[[j]] - medias_locales[j]
    ) / desviaciones_locales[j]
  }

  clima_estandarizado <- terra::rast(
    capas_estandarizadas
  )

  names(clima_estandarizado) <- paste0(
    "z_",
    names(clima_local)
  )

  if (terra::nlyr(clima_estandarizado) != 24L) {
    stop(
      "No fue posible estandarizar las 24 capas.",
      call. = FALSE
    )
  }

  # ------------------------------------------------------------
  # 9. Estandarizar el clima de los sitios
  # ------------------------------------------------------------

  sitios_estandarizados <- sweep(
    sitios_clima,
    MARGIN = 2,
    STATS = medias_locales,
    FUN = "-"
  )

  sitios_estandarizados <- sweep(
    sitios_estandarizados,
    MARGIN = 2,
    STATS = desviaciones_locales,
    FUN = "/"
  )

  if (any(!is.finite(sitios_estandarizados))) {
    stop(
      "La estandarización climática de los sitios produjo valores no válidos.",
      call. = FALSE
    )
  }

  # ------------------------------------------------------------
  # 10. Calcular la distancia euclidiana
  # ------------------------------------------------------------

  mapas_locales <- vector(
    mode = "list",
    length = nrow(sitios_local)
  )

  for (i in seq_len(nrow(sitios_local))) {

    # Inicializar con la primera variable
    distancia_cuadrada <- (
      clima_estandarizado[[1]] -
      sitios_estandarizados[i, 1]
    )^2

    # Acumular las otras 23 diferencias cuadráticas
    for (j in 2:24) {

      distancia_cuadrada <- distancia_cuadrada + (
        clima_estandarizado[[j]] -
        sitios_estandarizados[i, j]
      )^2
    }

    distancia_euclidiana <- sqrt(
      distancia_cuadrada
    )

    mapa_similaridad <- exp(
      -distancia_euclidiana
    )

    names(mapa_similaridad) <- paste0(
      "sim_",
      make.names(sitios_local$sitio[i])
    )

    mapas_locales[[i]] <- mapa_similaridad
  }

  # ------------------------------------------------------------
  # 11. Integrar y validar los tres mapas
  # ------------------------------------------------------------

  if (
    length(mapas_locales) != 3L ||
    !all(
      vapply(
        mapas_locales,
        inherits,
        logical(1),
        what = "SpatRaster"
      )
    )
  ) {
    stop(
      "No fue posible construir los tres mapas de similaridad.",
      call. = FALSE
    )
  }

  similitud_global <- c(
    mapas_locales[[1]],
    mapas_locales[[2]],
    mapas_locales[[3]]
  )

  names(similitud_global) <- paste0(
    "Similaridad_",
    make.names(sitios_local$sitio)
  )

  if (terra::nlyr(similitud_global) != 3L) {
    stop(
      "El resultado final no contiene tres mapas.",
      call. = FALSE
    )
  }

  rango_local <- terra::minmax(
    similitud_global
  )

  if (
    any(rango_local < 0, na.rm = TRUE) ||
    any(rango_local > 1, na.rm = TRUE)
  ) {
    stop(
      "La similaridad contiene valores fuera del intervalo 0–1.",
      call. = FALSE
    )
  }

   # ------------------------------------------------------------
  # 12. Crear y mostrar la tabla de resumen
  # ------------------------------------------------------------

  resumen_local <- data.frame(
    Sitio = sitios_local$sitio,
    `Similaridad mínima` = round(
      as.numeric(rango_local[1, ]),
      4
    ),
    `Similaridad máxima` = round(
      as.numeric(rango_local[2, ]),
      4
    ),
    check.names = FALSE,
    stringsAsFactors = FALSE
  )
  
assign(
  "resumen_similaridad",
  resumen_local,
  envir = parent.frame()
)

#  tabla_local <- knitr::kable(
#    resumen_local,
#    format = "html",
#    caption = paste(
#      "Rango de similaridad climática global",
#      "por sitio del Valle del Cauca"
#    ),
#    col.names = c(
#      "Sitio del Valle del Cauca",
#      "Similaridad mínima",
#      "Similaridad máxima"
#    ),
#    align = c("l", "c", "c"),
#    digits = 4,
#    escape = TRUE,
#    table.attr = 'class="table table-striped table-hover table-condensed"'
#  ) |>
#    kableExtra::kable_styling(
#      bootstrap_options = c(
#        "striped",
#        "hover",
#        "condensed",
#        "responsive"
#      ),
#      full_width = TRUE,
#      position = "center",
#      font_size = 14
#    ) |>
#    kableExtra::row_spec(
#      row = 0,
#      bold = TRUE,
#      color = "white",
#      background = "#123F72"
#    ) |>
#    kableExtra::column_spec(
#      column = 1,
#      bold = TRUE,
#      color = "#123F72",
#      width = "35%"
#    ) |>
#    kableExtra::column_spec(
#      column = 2,
#      width = "25%"
#    ) |>
#    kableExtra::column_spec(
#      column = 3,
#      width = "25%"
#    )

  # Emitir el HTML como contenido del informe, no como texto de consola.
#  cat(
#    as.character(tabla_local),
#    sep = "\n"
#  )

  # Separación entre la tabla y los mapas.
#  cat(
#    "\n<div style='margin-bottom: 28px;'></div>\n"
# )
  
  # ------------------------------------------------------------
  # 13. Cargar límites mundiales
  # ------------------------------------------------------------

  paises_locales <- geodata::world(
    path = ruta_datos_local
  )

  # ------------------------------------------------------------
  # 14. Graficar los mapas
  # ------------------------------------------------------------

  configuracion_anterior <- graphics::par(
    no.readonly = TRUE
  )

  graphics::par(
    mfrow = c(3, 1),
    mar = c(2.5, 3, 4, 5.5),
    oma = c(0, 0, 2, 0)
  )

  for (i in seq_len(3L)) {

    terra::plot(
      similitud_global[[i]],
      col = hcl.colors(
        100,
        palette = "Inferno",
        rev = TRUE
      ),
      range = c(0, 1),
      axes = TRUE,
      main = paste(
        "Similaridad climática global con",
        sitios_local$sitio[i]
      ),
      plg = list(
        title = "Similaridad",
        cex = 0.8
      )
    )

    terra::lines(
      paises_locales,
      col = "grey55",
      lwd = 0.25
    )
  }

  graphics::mtext(
    text = paste(
      "Analogía climática con sitios",
      "del Valle del Cauca"
    ),
    side = 3,
    outer = TRUE,
    line = 0.3,
    font = 2,
    cex = 1.15,
    col = "#123F72"
  )

  graphics::par(
    configuracion_anterior
  )

  # ------------------------------------------------------------
  # 15. Guardar resultados
  # ------------------------------------------------------------

  archivo_similaridad <- file.path(
    ruta_resultados_local,
    "similaridad_climatica_sitios_valle.tif"
  )

  terra::writeRaster(
    similitud_global,
    filename = archivo_similaridad,
    overwrite = TRUE
  )

  # ------------------------------------------------------------
  # 16. Crear objetos requeridos por chunks posteriores
  # ------------------------------------------------------------

  entorno_salida <- parent.frame()

  assign(
    "tmed_m",
    tmed_local,
    envir = entorno_salida
  )

  assign(
    "prec_m",
    prec_local,
    envir = entorno_salida
  )

  assign(
    "clima_24",
    clima_local,
    envir = entorno_salida
  )

  assign(
    "clima_z",
    clima_estandarizado,
    envir = entorno_salida
  )

  assign(
    "sitios",
    sitios_local,
    envir = entorno_salida
  )

  assign(
    "ext_sitios",
    extraccion_local,
    envir = entorno_salida
  )

  assign(
    "sitios_z",
    sitios_estandarizados,
    envir = entorno_salida
  )

  assign(
    "paises",
    paises_locales,
    envir = entorno_salida
  )

  assign(
    "sim_global",
    similitud_global,
    envir = entorno_salida
  )

  assign(
    "mapas_sim",
    mapas_locales,
    envir = entorno_salida
  )

  assign(
    "ruta_resultados",
    ruta_resultados_local,
    envir = entorno_salida
  )

  invisible(
    similitud_global
  )
}

# ÚNICA LLAMADA DEL CHUNK
# No ejecutar las líneas internas de la función por separado.
construir_similaridad_climatica()

Rango de similaridad climática global por sitio del Valle del Cauca
Sitio del Valle del Cauca Similaridad mínima Similaridad máxima
Palmira 0 1
Candelaria 0 1
Zarzal 0 1

6 Comparación de aproximaciones

La aproximación por umbrales responde: ¿dónde se cumplen simultáneamente los rangos climáticos considerados óptimos? La analogía responde: ¿dónde se parece el ciclo mensual al de los sitios de referencia? No son equivalentes: dos lugares pueden compartir promedios anuales pero diferir en la estacionalidad.

# Escala relativa de similaridad alta: percentil 90 sobre celdas terrestres.
umbral_sim <- as.numeric(global(sim_promedio, function(x) quantile(x, 0.90, na.rm = TRUE))[, 1])

# Ráster numérico de clases. Se conserva sin niveles para los cálculos.
acuerdo_num <- terra::ifel(
  apt_climatica >= 0.75 & sim_promedio >= umbral_sim, 3,
  terra::ifel(
    apt_climatica >= 0.75, 1,
    terra::ifel(sim_promedio >= umbral_sim, 2, 0)
  )
)
names(acuerdo_num) <- "codigo_acuerdo"

# Se crea una copia categórica exclusivamente para la representación gráfica.
acuerdo_plot <- acuerdo_num
levels(acuerdo_plot) <- data.frame(
  value = 0:3,
  clase = c("Ninguna", "Solo umbrales", "Solo analogía", "Coincidencia")
)

plot(
  acuerdo_plot,
  col = c("grey90", "#2c7bb6", "#fdae61", "#1a9641"),
  main = "Comparación espacial de las dos aproximaciones"
)
lines(paises, col = "grey30", lwd = 0.2)

# Resumen robusto de superficies. Se evita zonal() sobre un ráster categórico,
# porque su primera columna puede devolverse como texto según la versión de terra.
area_celda <- terra::cellSize(acuerdo_num, unit = "km")
clases_codigo <- 0:3
clases_nombre <- c("Ninguna", "Solo umbrales", "Solo analogía", "Coincidencia")

area_por_clase <- vapply(
  clases_codigo,
  function(codigo) {
    area_clase <- terra::ifel(acuerdo_num == codigo, area_celda, NA)
    valor <- terra::global(area_clase, "sum", na.rm = TRUE)[1, 1]
    if (is.na(valor)) 0 else as.numeric(valor)
  },
  numeric(1)
)

resumen_acuerdo <- data.frame(
  codigo = clases_codigo,
  clase = clases_nombre,
  area_km2 = area_por_clase
)

stopifnot(
  nrow(resumen_acuerdo) == 4L,
  is.numeric(resumen_acuerdo$area_km2),
  all(is.finite(resumen_acuerdo$area_km2)),
  all(resumen_acuerdo$area_km2 >= 0)
)

resumen_acuerdo <- normalizar_tabla_utf8(resumen_acuerdo)
knitr::kable(
  resumen_acuerdo[, c("clase", "area_km2")],
  digits = 0,
  col.names = c("Clase", "Área aproximada (km²)"),
  caption = "Superficie por tipo de acuerdo"
)
Superficie por tipo de acuerdo
Clase Área aproximada (km²)
Ninguna 115851391
Solo umbrales 14614790
Solo analogía 16788328
Coincidencia 7094662

6.1 Interpretación

  • Coincidencia: zonas prioritarias para una evaluación agroecológica posterior, porque cumplen los rangos anuales y reproducen relativamente bien el ciclo mensual de los sitios vallecaucanos.
  • Solo umbrales: el promedio anual es favorable, pero la distribución intraanual de lluvia o temperatura difiere del patrón de referencia.
  • Solo analogía: el patrón mensual se parece, aunque al menos uno de los valores anuales no alcanza el criterio estricto de aptitud alta.
  • Ninguna: baja prioridad climática bajo los supuestos adoptados.

7 Conclusiones

  1. El análisis por umbrales permite localizar de manera transparente las celdas donde la temperatura media y la precipitación anual se aproximan a los rangos óptimos definidos para el caso.
  2. El ranking de países se basa en superficie apta calculada, no en una selección visual. Los tres países y sus mapas se generan automáticamente al ejecutar el informe.
  3. La analogía climática añade información sobre estacionalidad. Por ello puede excluir zonas con promedios anuales adecuados, pero con calendarios de lluvia o temperatura muy diferentes a los del Valle del Cauca.
  4. Las áreas donde coinciden ambas aproximaciones constituyen candidatos razonables para estudios posteriores, pero no prueban rendimiento potencial en kg/ha.
  5. Antes de una decisión productiva deben incorporarse suelo, pendiente, disponibilidad de agua, radiación, riesgo de inundación o sequía, infraestructura, restricciones ambientales, variedad y manejo. La etapa siguiente debe usar un modelo de cultivo calibrado y datos locales de rendimiento.

8 Limitaciones y reproducibilidad

  • WorldClim 2.1 representa promedios de 1970–2000 y no las condiciones observadas de un año específico.
  • La resolución de 10 minutos suaviza variaciones locales, especialmente en relieves complejos.
  • Los tres puntos son referencias espaciales verificables mediante Google Maps, pero no sustituyen coordenadas de lotes o estaciones agrometeorológicas.
  • Los rangos óptimos fueron fijados por el enunciado. Los márgenes externos del índice trapezoidal son supuestos metodológicos explícitos y pueden ajustarse.
  • La similaridad depende de las variables, estandarización, métrica y transformación elegidas.
  • La selección del percentil 90 se usa solo para comparar mapas; no representa un umbral agronómico universal.
write.csv(ranking_paises,
          file.path(ruta_resultados, "ranking_paises_aptitud.csv"), row.names = FALSE)
write.csv(serie_sitios,
          file.path(ruta_resultados, "clima_mensual_sitios_valle.csv"), row.names = FALSE)
cat("Resultados guardados en:", normalizePath(ruta_resultados), "\n")
Resultados guardados en: C:\Users\dcruz\OneDrive - Consorcio Programa Servicios de Tránsito\Documentos\ElectivaUn2\resultados_informe2 

9 Referencias

sessionInfo()
R version 4.5.0 (2025-04-11 ucrt)
Platform: x86_64-w64-mingw32/x64
Running under: Windows 11 x64 (build 26100)

Matrix products: default
  LAPACK version 3.12.1

locale:
[1] LC_COLLATE=Spanish_Colombia.utf8  LC_CTYPE=Spanish_Colombia.utf8    LC_MONETARY=Spanish_Colombia.utf8
[4] LC_NUMERIC=C                      LC_TIME=Spanish_Colombia.utf8    

time zone: America/Bogota
tzcode source: internal

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
 [1] htmltools_0.5.9   patchwork_1.3.2   kableExtra_1.4.1  knitr_1.52        scales_1.4.0     
 [6] viridis_0.6.5     viridisLite_0.4.3 ggplot2_4.0.3     tidyr_1.3.2       dplyr_1.2.0      
[11] tmaptools_3.3     leaflet_2.2.3     raster_3.6-32     sp_2.2-0          sf_1.1-3         
[16] geodata_0.6-9     terra_1.9-50     

loaded via a namespace (and not attached):
 [1] gtable_0.3.6       xfun_0.56          bslib_0.10.0       htmlwidgets_1.6.4  lattice_0.22-6    
 [6] vctrs_0.7.1        tools_4.5.0        crosstalk_1.2.2    generics_0.1.4     parallel_4.5.0    
[11] tibble_3.3.1       proxy_0.4-29       pkgconfig_2.0.3    KernSmooth_2.23-26 RColorBrewer_1.1-3
[16] S7_0.2.1           lifecycle_1.0.5    compiler_4.5.0     farver_2.1.2       stringr_1.6.0     
[21] textshaping_1.0.4  codetools_0.2-20   stars_0.7-3        class_7.3-23       sass_0.4.10       
[26] yaml_2.3.12        pillar_1.11.1      jquerylib_0.1.4    classInt_0.4-11    cachem_1.1.0      
[31] lwgeom_0.2-17      abind_1.4-8        tidyselect_1.2.1   digest_0.6.39      stringi_1.8.7     
[36] purrr_1.2.1        labeling_0.4.3     fastmap_1.2.0      grid_4.5.0         cli_3.6.5         
[41] magrittr_2.0.4     XML_3.99-0.25      e1071_1.7-17       withr_3.0.2        rappdirs_0.3.4    
[46] rmarkdown_2.32     otel_0.2.0         gridExtra_2.3      evaluate_1.0.5     rlang_1.1.7       
[51] Rcpp_1.1.1         glue_1.8.0         DBI_1.3.0          xml2_1.5.2         svglite_2.2.2     
[56] rstudioapi_0.18.0  jsonlite_2.0.0     R6_2.6.1           systemfonts_1.3.1  units_1.0-1       
LS0tDQp0aXRsZTogIiINCm91dHB1dDoNCiAgaHRtbF9kb2N1bWVudDoNCiAgICB0b2M6IHRydWUNCiAgICB0b2NfZmxvYXQ6DQogICAgICBjb2xsYXBzZWQ6IGZhbHNlDQogICAgICBzbW9vdGhfc2Nyb2xsOiB0cnVlDQogICAgbnVtYmVyX3NlY3Rpb25zOiB0cnVlDQogICAgY29kZV9mb2xkaW5nOiBoaWRlDQogICAgY29kZV9kb3dubG9hZDogdHJ1ZQ0KICAgIHNlbGZfY29udGFpbmVkOiB0cnVlDQogICAgdGhlbWU6IGZsYXRseQ0KICAgIGhpZ2hsaWdodDogdGFuZ28NCiAgICBkZl9wcmludDogcGFnZWQNCmxhbmc6IGVzDQotLS0NCg0KDQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0NCmtuaXRyOjpvcHRzX2NodW5rJHNldCgNCiAgZWNobyA9IFRSVUUsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFLA0KICBmaWcuYWxpZ24gPSAiY2VudGVyIiwgZmlnLndpZHRoID0gMTEsIGZpZy5oZWlnaHQgPSA2LjUsDQogIGRwaSA9IDE1MCwgY2FjaGUgPSBGQUxTRSwgY29tbWVudCA9IE5BDQopDQpvcHRpb25zKHNjaXBlbiA9IDk5OSkNCnNldC5zZWVkKDg3NSkNCg0KcGFxdWV0ZXMgPC0gYygidGVycmEiLCAiZ2VvZGF0YSIsICJzZiIsICJzcCIsICJyYXN0ZXIiLCAibGVhZmxldCIsICJ0bWFwdG9vbHMiLA0KICAgICAgICAgICAgICAiZHBseXIiLCAidGlkeXIiLCAiZ2dwbG90MiIsICJ2aXJpZGlzIiwgInNjYWxlcyIsICJrbml0ciIsDQogICAgICAgICAgICAgICJrYWJsZUV4dHJhIiwgInBhdGNod29yayIsICJodG1sdG9vbHMiKQ0KZmFsdGFudGVzIDwtIHBhcXVldGVzWyF2YXBwbHkocGFxdWV0ZXMsIHJlcXVpcmVOYW1lc3BhY2UsIGxvZ2ljYWwoMSksIHF1aWV0bHkgPSBUUlVFKV0NCmlmIChsZW5ndGgoZmFsdGFudGVzKSA+IDApIHsNCiAgaW5zdGFsbC5wYWNrYWdlcyhmYWx0YW50ZXMsIHJlcG9zID0gImh0dHBzOi8vY2xvdWQuci1wcm9qZWN0Lm9yZyIsIGRlcGVuZGVuY2llcyA9IFRSVUUpDQp9DQojIHJnZGFsIHNlIGNvbXBydWViYSBwb3IgZXhpZ2VuY2lhIGRlbCBjYXNvLCBwZXJvIG5vIHNlIGZ1ZXJ6YSBzdSBpbnN0YWxhY2nDs246DQojIGZ1ZSBhcmNoaXZhZG8gZW4gQ1JBTiB5IGxhcyBvcGVyYWNpb25lcyBhY3R1YWxlcyBzZSBlamVjdXRhbiBjb24gc2YvdGVycmEuDQpyZ2RhbF9kaXNwb25pYmxlIDwtIHJlcXVpcmVOYW1lc3BhY2UoInJnZGFsIiwgcXVpZXRseSA9IFRSVUUpDQppbnZpc2libGUobGFwcGx5KHBhcXVldGVzLCBsaWJyYXJ5LCBjaGFyYWN0ZXIub25seSA9IFRSVUUpKQ0KDQojIENhcnBldGEgbG9jYWwgcGFyYSBkYXRvcyB5IHJlc3VsdGFkb3MgcmVwcm9kdWNpYmxlcw0KcnV0YV9kYXRvcyA8LSBmaWxlLnBhdGgoZ2V0d2QoKSwgImRhdG9zX2NsaW1hIikNCnJ1dGFfcmVzdWx0YWRvcyA8LSBmaWxlLnBhdGgoZ2V0d2QoKSwgInJlc3VsdGFkb3NfaW5mb3JtZTIiKQ0KZGlyLmNyZWF0ZShydXRhX2RhdG9zLCBzaG93V2FybmluZ3MgPSBGQUxTRSwgcmVjdXJzaXZlID0gVFJVRSkNCmRpci5jcmVhdGUocnV0YV9yZXN1bHRhZG9zLCBzaG93V2FybmluZ3MgPSBGQUxTRSwgcmVjdXJzaXZlID0gVFJVRSkNCg0KIyBFbGltaW5hciBjYWNow6kgYW50aWd1YSBkZWwgZG9jdW1lbnRvIHNpIGV4aXN0ZS4gVW5hIGNhY2jDqSBwcmV2aWEgcHVlZGUgY29udGVuZXINCiMgcHVudGVyb3MgZXh0ZXJub3MgZGUgdGVycmEgcXVlIGRlamFuIGRlIHNlciB2w6FsaWRvcyBhbCBpbmljaWFyIG90cmEgc2VzacOzbi4NCmNhY2hlX3Bvc2libGVzIDwtIGxpc3QuZGlycyhnZXR3ZCgpLCByZWN1cnNpdmUgPSBGQUxTRSwgZnVsbC5uYW1lcyA9IFRSVUUpDQpjYWNoZV9wb3NpYmxlcyA8LSBjYWNoZV9wb3NpYmxlc1tncmVwbCgiSW5mb3JtZV8yX1JNYXJrZG93bi4qX2NhY2hlJCIsIGNhY2hlX3Bvc2libGVzKV0NCmlmIChsZW5ndGgoY2FjaGVfcG9zaWJsZXMpID4gMCkgdW5saW5rKGNhY2hlX3Bvc2libGVzLCByZWN1cnNpdmUgPSBUUlVFLCBmb3JjZSA9IFRSVUUpDQpjYXQoIk1BUkNBRE9SIERFIFZFUlNJw5NOOiBJTkZPUk1FX1YxMF9NQVBBX09TTVxuIikNCmBgYA0KDQpgYGB7ciBub3JtYWxpemFjaW9uLXRhYmxhc30NCiMgPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09DQojIE5PUk1BTElaQUNJT04gR0VORVJBTCBERSBUQUJMQVMgUEFSQSBIVE1MDQojID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQ0KDQpub3JtYWxpemFyX3RhYmxhX3V0ZjggPC0gZnVuY3Rpb24odGFibGEpIHsNCg0KICB0YWJsYSA8LSBhcy5kYXRhLmZyYW1lKA0KICAgIHRhYmxhLA0KICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRSwNCiAgICBjaGVjay5uYW1lcyA9IEZBTFNFDQogICkNCg0KICAjIE5vcm1hbGl6YXIgbm9tYnJlcyBkZSBjb2x1bW5hcw0KICBub21icmVzX29yaWdpbmFsZXMgPC0gbmFtZXModGFibGEpDQoNCiAgbm9tYnJlc191dGY4IDwtIGljb252KA0KICAgIG5vbWJyZXNfb3JpZ2luYWxlcywNCiAgICBmcm9tID0gIiIsDQogICAgdG8gPSAiVVRGLTgiLA0KICAgIHN1YiA9ICJieXRlIg0KICApDQoNCiAgIyBTaSBlbCBzaXN0ZW1hIG5vIHJlY29ub2NlIGxhIGNvZGlmaWNhY2nDs24gZGUgb3JpZ2VuLA0KICAjIGludGVudGFyIFdpbmRvd3MtMTI1MiDDum5pY2FtZW50ZSBzb2JyZSBsb3Mgbm9tYnJlcyBmYWxsaWRvcy4NCiAgbm9tYnJlc19mYWxsaWRvcyA8LSBpcy5uYShub21icmVzX3V0ZjgpDQoNCiAgaWYgKGFueShub21icmVzX2ZhbGxpZG9zKSkgew0KICAgIG5vbWJyZXNfdXRmOFtub21icmVzX2ZhbGxpZG9zXSA8LSBpY29udigNCiAgICAgIG5vbWJyZXNfb3JpZ2luYWxlc1tub21icmVzX2ZhbGxpZG9zXSwNCiAgICAgIGZyb20gPSAid2luZG93cy0xMjUyIiwNCiAgICAgIHRvID0gIlVURi04IiwNCiAgICAgIHN1YiA9ICJieXRlIg0KICAgICkNCiAgfQ0KDQogIG5vbWJyZXNfdXRmOCA8LSBtYWtlLnVuaXF1ZSgNCiAgICBub21icmVzX3V0ZjgsDQogICAgc2VwID0gIl8iDQogICkNCg0KICBuYW1lcyh0YWJsYSkgPC0gbm9tYnJlc191dGY4DQoNCiAgIyBOb3JtYWxpemFyIGNvbHVtbmFzIGRlIHRleHRvDQogIGNvbHVtbmFzX3RleHRvIDwtIHZhcHBseSgNCiAgICB0YWJsYSwNCiAgICBmdW5jdGlvbih4KSB7DQogICAgICBpcy5jaGFyYWN0ZXIoeCkgfHwgaXMuZmFjdG9yKHgpDQogICAgfSwNCiAgICBsb2dpY2FsKDEpDQogICkNCg0KICB0YWJsYVtjb2x1bW5hc190ZXh0b10gPC0gbGFwcGx5KA0KICAgIHRhYmxhW2NvbHVtbmFzX3RleHRvXSwNCiAgICBmdW5jdGlvbih4KSB7DQoNCiAgICAgIHggPC0gYXMuY2hhcmFjdGVyKHgpDQoNCiAgICAgIGNvbnZlcnRpZG8gPC0gaWNvbnYoDQogICAgICAgIHgsDQogICAgICAgIGZyb20gPSAiIiwNCiAgICAgICAgdG8gPSAiVVRGLTgiLA0KICAgICAgICBzdWIgPSAiYnl0ZSINCiAgICAgICkNCg0KICAgICAgZmFsbGlkb3MgPC0gaXMubmEoY29udmVydGlkbykgJiAhaXMubmEoeCkNCg0KICAgICAgaWYgKGFueShmYWxsaWRvcykpIHsNCiAgICAgICAgY29udmVydGlkb1tmYWxsaWRvc10gPC0gaWNvbnYoDQogICAgICAgICAgeFtmYWxsaWRvc10sDQogICAgICAgICAgZnJvbSA9ICJ3aW5kb3dzLTEyNTIiLA0KICAgICAgICAgIHRvID0gIlVURi04IiwNCiAgICAgICAgICBzdWIgPSAiYnl0ZSINCiAgICAgICAgKQ0KICAgICAgfQ0KDQogICAgICBjb252ZXJ0aWRvDQogICAgfQ0KICApDQoNCiAgdGFibGENCn0NCmBgYA0KDQo8c3R5bGU+DQo6cm9vdCB7IC0tYXp1bDojMTIzZjcyOyAtLWF6dWwyOiMxZjViOTY7IC0tb3JvOiNjN2EzNGI7IC0tZ3JpczojZjRmNmY4OyB9DQpib2R5IHsgZm9udC1mYW1pbHk6ICJTZWdvZSBVSSIsIEFyaWFsLCBzYW5zLXNlcmlmOyBjb2xvcjojMjczNDQyOyBsaW5lLWhlaWdodDoxLjY1OyB9DQoubWFpbi1jb250YWluZXIgeyBtYXgtd2lkdGg6IDExODBweCAhaW1wb3J0YW50OyBtYXJnaW46YXV0bzsgfQ0KcCwgbGkgeyB0ZXh0LWFsaWduOiBqdXN0aWZ5OyB9DQpoMSwgaDIsIGgzIHsgY29sb3I6dmFyKC0tYXp1bCk7IGZvbnQtd2VpZ2h0OjY1MDsgfQ0KaDEgeyBib3JkZXItYm90dG9tOjJweCBzb2xpZCB2YXIoLS1vcm8pOyBwYWRkaW5nLWJvdHRvbTouMzVyZW07IH0NCmgyIHsgYm9yZGVyLWxlZnQ6NXB4IHNvbGlkIHZhcigtLW9ybyk7IHBhZGRpbmctbGVmdDouNjVyZW07IH0NCi5jb3ZlciB7IG1pbi1oZWlnaHQ6OTJ2aDsgZGlzcGxheTpmbGV4OyBmbGV4LWRpcmVjdGlvbjpjb2x1bW47IGp1c3RpZnktY29udGVudDpjZW50ZXI7DQogIGFsaWduLWl0ZW1zOmNlbnRlcjsgdGV4dC1hbGlnbjpjZW50ZXI7IHBhZ2UtYnJlYWstYWZ0ZXI6YWx3YXlzOyBib3JkZXItdG9wOjlweCBzb2xpZCB2YXIoLS1henVsKTsNCiAgYm9yZGVyLWJvdHRvbTo5cHggc29saWQgdmFyKC0tb3JvKTsgcGFkZGluZzozcmVtIDJyZW07IGJveC1zaXppbmc6Ym9yZGVyLWJveDsgfQ0KLmNvdmVyIGltZyB7IHdpZHRoOjMwMHB4OyBtYXgtd2lkdGg6NzAlOyBtYXJnaW4tYm90dG9tOjIuNXJlbTsgfQ0KLmNvdmVyIGgxIHsgYm9yZGVyOjA7IGZvbnQtc2l6ZToyLjM1cmVtOyB0ZXh0LWFsaWduOmNlbnRlcjsgbWFyZ2luOjFyZW0gMCAyLjJyZW07IH0NCi5jb3ZlciBwIHsgdGV4dC1hbGlnbjpjZW50ZXI7IG1hcmdpbjouMzVyZW0gMDsgZm9udC1zaXplOjEuMDhyZW07IH0NCi5jb3ZlciAubWV0YSB7IG1hcmdpbi10b3A6MnJlbTsgfQ0KLnRhYmxlIHsgd2lkdGg6MTAwJTsgfQ0KdGFibGUgeyBtYXJnaW46MS4ycmVtIGF1dG8gMnJlbTsgYm9yZGVyLWNvbGxhcHNlOmNvbGxhcHNlOyB9DQp0aGVhZCB7IGJhY2tncm91bmQ6dmFyKC0tYXp1bCk7IGNvbG9yOndoaXRlOyB9DQp0aCwgdGQgeyBwYWRkaW5nOi42NXJlbSAuOHJlbSAhaW1wb3J0YW50OyBib3JkZXI6MXB4IHNvbGlkICNkOWUwZTcgIWltcG9ydGFudDsgfQ0KdGJvZHkgdHI6bnRoLWNoaWxkKGV2ZW4pIHsgYmFja2dyb3VuZDojZjdmOWZiOyB9DQouY2FwdGlvbiB7IGNvbG9yOnZhcigtLWF6dWwpOyBmb250LXdlaWdodDo2MDA7IHRleHQtYWxpZ246Y2VudGVyICFpbXBvcnRhbnQ7IH0NCnByZSB7IGJvcmRlci1yYWRpdXM6NnB4OyB9DQouZmlndXJlLCAubGVhZmxldCB7IG1hcmdpbjoxLjRyZW0gYXV0bzsgfQ0KYmxvY2txdW90ZSB7IGJvcmRlci1sZWZ0OjVweCBzb2xpZCB2YXIoLS1vcm8pOyBiYWNrZ3JvdW5kOiNmZmZhZjA7IHBhZGRpbmc6MXJlbSAxLjJyZW07IH0NCi50b2NpZnkgeyBib3JkZXItcmFkaXVzOjhweDsgfQ0KQG1lZGlhIHByaW50IHsgLmNvdmVyIHsgbWluLWhlaWdodDo5NXZoOyB9IC50b2NpZnktd3JhcHBlciB7IGRpc3BsYXk6bm9uZTsgfSB9DQo8L3N0eWxlPg0KDQo8ZGl2IGNsYXNzPSJjb3ZlciI+DQo8aW1nIHNyYz0ibG9nb19qYXZlcmlhbmEucG5nIiBhbHQ9IkxvZ28gVW5pdmVyc2lkYWQgUG9udGlmaWNpYSBKYXZlcmlhbmEiPg0KPGgxPkluZm9ybWUgMiBSTWFya2Rvd246IGFwdGl0dWQgY2xpbcOhdGljYSBnbG9iYWwgcGFyYSBjYcOxYSBkZSBhesO6Y2FyPC9oMT4NCjxkaXYgY2xhc3M9Im1ldGEiPg0KPHA+PHN0cm9uZz5Vbml2ZXJzaWRhZCBQb250aWZpY2lhIEphdmVyaWFuYTwvc3Ryb25nPjwvcD4NCjxwPjxzdHJvbmc+UHJlc2VudGFkbyBwb3I6PC9zdHJvbmc+IERhaW5lciBPcmxhbmRvIENydXogQ2hhdGU8L3A+DQo8cD48c3Ryb25nPlRyYWJham8gcHJlc2VudGFkbyBhbCBwcm9mZXNvcjo8L3N0cm9uZz4gRGF2aWQgQXJhbmdvIExvbmRvw7FvPC9wPg0KPHA+PHN0cm9uZz5DdXJzbzo8L3N0cm9uZz4gQW7DoWxpc2lzIGRlIERhdG9zIEdlb2dyw6FmaWNvcyB5IEVzcGFjaWFsZXM8L3A+DQo8cD48c3Ryb25nPlVuaWRhZDo8L3N0cm9uZz4gMjwvcD4NCjxwPjxzdHJvbmc+RmVjaGE6PC9zdHJvbmc+IGByIGZvcm1hdChTeXMuRGF0ZSgpLCAiJWQvJW0vJVkiKWA8L3A+DQo8L2Rpdj4NCjwvZGl2Pg0KDQo8ZGl2IHN0eWxlPSJwYWdlLWJyZWFrLWFmdGVyOiBhbHdheXM7Ij48L2Rpdj4NCg0KIyBQbGFudGVhbWllbnRvIGRlbCBjYXNvDQoNClVuIGFncmljdWx0b3IgZGVzZWEgaWRlbnRpZmljYXIgem9uYXMgZGVsIG11bmRvIGNvbiBjb25kaWNpb25lcyBjbGltw6F0aWNhcyBmYXZvcmFibGVzIHBhcmEgbGEgY2HDsWEgZGUgYXrDumNhci4gU2UgYXBsaWNhbiBkb3MgYXByb3hpbWFjaW9uZXMgY29tcGxlbWVudGFyaWFzOg0KDQoxLiAqKkFwdGl0dWQgcG9yIHVtYnJhbGVzOioqIHRlbXBlcmF0dXJhIG1lZGlhIGFudWFsIGVudHJlICoqMjIsNSB5IDI4IMKwQyoqIHkgcHJlY2lwaXRhY2nDs24gYW51YWwgZW50cmUgKioxLjUwMCB5IDMuNTAwIG1tKiouDQoyLiAqKkFuYWxvZ8OtYSBjbGltw6F0aWNhOioqIHNpbWlsaXR1ZCBlbnRyZSBlbCBjaWNsbyBtZW5zdWFsIGRlIHRlbXBlcmF0dXJhIHkgcHJlY2lwaXRhY2nDs24gZGUgdHJlcyBzaXRpb3MgZGVsIFZhbGxlIGRlbCBDYXVjYSB5IGxhcyBjZWxkYXMgY2xpbcOhdGljYXMgZGVsIG11bmRvLg0KDQo+ICoqQWxjYW5jZToqKiBlbCByZXN1bHRhZG8gcmVwcmVzZW50YSBhcHRpdHVkIGNsaW3DoXRpY2EgZGUgbMOtbmVhIGJhc2UsIG5vIHVuYSBwcmVkaWNjacOzbiBkaXJlY3RhIGRlIHJlbmRpbWllbnRvIGVuIGtnL2hhLiBQYXJhIGVzdGltYXIgcmVuZGltaWVudG8gcG90ZW5jaWFsIHNlIHJlcXVpZXJlbiwgYWRlbcOhcywgcmFkaWFjacOzbiwgc3VlbG8sIHJlbGlldmUsIHZhcmllZGFkLCBkdXJhY2nDs24gZGVsIGNpY2xvLCBtYW5lam8sIHJpZWdvIHkgdW4gbW9kZWxvIGRlIGN1bHRpdm8gY2FsaWJyYWRvLCBwb3IgZWplbXBsbyBEU1NBVC1DQU5FR1JPLCBBUFNJTS1TdWdhciBvIEFxdWFDcm9wLg0KDQojIEZ1ZW50ZXMsIGNyaXRlcmlvcyB5IG3DqXRvZG8NCg0KU2UgZW1wbGVhICoqV29ybGRDbGltIDIuMSoqLCBjbGltYXRvbG9nw61hIGRlIGzDrW5lYSBiYXNlIDE5NzDigJMyMDAwLiBXb3JsZENsaW0gb2ZyZWNlIGNhcGFzIG1lbnN1YWxlcyBkZSB0ZW1wZXJhdHVyYSBtZWRpYSB5IHByZWNpcGl0YWNpw7NuIGEgdmFyaWFzIHJlc29sdWNpb25lcy4gUGFyYSBxdWUgZWwgaW5mb3JtZSBwdWVkYSBlamVjdXRhcnNlIGVuIHVuIGNvbXB1dGFkb3IgY29udmVuY2lvbmFsIHNlIHV0aWxpemEgcmVzb2x1Y2nDs24gZGUgKioxMCBtaW51dG9zIGRlIGFyY28qKjsgcHVlZGUgY2FtYmlhcnNlIGEgNSBvIDIsNSBtaW51dG9zLCBjb24gbWF5b3IgY29zdG8gZGUgbWVtb3JpYSB5IHRpZW1wby4NCg0KTG9zIHJhbmdvcyBkZSBhcHRpdHVkIHVzYWRvcyBlbiBlc3RlIGVqZXJjaWNpbyBwcm9jZWRlbiBkZSBsYXMgaW5zdHJ1Y2Npb25lcyBkZWwgY2Fzby4gQ29tbyByZWZlcmVuY2lhIGFncm9uw7NtaWNhIGFkaWNpb25hbCwgRkFPIHNlw7FhbGEgcXVlIGxhIHByb2R1Y2Npw7NuIGRlIGNhw7FhIGRlcGVuZGUgZGUgY2xpbWEsIHN1ZWxvLCB2YXJpZWRhZCB5IG1hbmVqbywgeSByZXBvcnRhIGNvbmRpY2lvbmVzIGPDoWxpZGFzIHkgcGx1dmlvbWV0csOtYSBlbGV2YWRhIGNvbW8gZmF2b3JhYmxlcy4NCg0KKipGdWVudGVzIHByaW5jaXBhbGVzKioNCg0KLSBXb3JsZENsaW0gMi4xOiA8aHR0cHM6Ly93d3cud29ybGRjbGltLm9yZy9kYXRhL3dvcmxkY2xpbTIxLmh0bWw+DQotIEZpY2ssIFMuIEUuIHkgSGlqbWFucywgUi4gSi4gKDIwMTcpLCAqV29ybGRDbGltIDIqOiA8aHR0cHM6Ly9kb2kub3JnLzEwLjEwMDIvam9jLjUwODY+DQotIEZBTywgaW5mb3JtYWNpw7NuIGdlbmVyYWwgc29icmUgY2HDsWEgZGUgYXrDumNhcjogPGh0dHBzOi8vd3d3LmZhby5vcmcvNC94NTY0OGUveDU2NDhlMGUuaHRtPg0KDQojIyBIZXJyYW1pZW50YXMgZXNwYWNpYWxlcyB5IGNvbXBhdGliaWxpZGFkDQoNCkVsIGZsdWpvIGRlIHRyYWJham8gaW50ZWdyYSBgcmFzdGVyYCBwYXJhIGNvbXBhdGliaWxpZGFkIGNvbiBsYXMgZXN0cnVjdHVyYXMgcsOhc3RlciBzb2xpY2l0YWRhcywgYGxlYWZsZXRgIHBhcmEgbWFwYXMgaW50ZXJhY3Rpdm9zLCBgdG1hcHRvb2xzYCBwYXJhIHV0aWxpZGFkZXMgY2FydG9ncsOhZmljYXMgeSBnZW9jb2RpZmljYWNpw7NuLCB5IGBzZmAvYHRlcnJhYCBwYXJhIGxhcyBvcGVyYWNpb25lcyBlc3BhY2lhbGVzIGFjdHVhbGVzLiBFbCBwYXF1ZXRlIGByZ2RhbGAgc2UgY29tcHJ1ZWJhIGVuIGVsIGVudG9ybm8gcG9yIGV4aWdlbmNpYSBkZWwgY2FzbyBlc3R1ZGlhZG8sIHBlcm8gbm8gc2UgZnVlcnphIHN1IGluc3RhbGFjacOzbiBwb3JxdWUgZnVlIHJldGlyYWRvIGRlIENSQU47IGxhcyB0cmFuc2Zvcm1hY2lvbmVzIHkgbGVjdHVyYXMgc2UgcmVhbGl6YW4gY29uIGBzZmAgeSBgdGVycmFgIHBhcmEgYXNlZ3VyYXIgY29tcGF0aWJpbGlkYWQgY29uIHZlcnNpb25lcyByZWNpZW50ZXMgZGUgUi4NCg0KYGBge3IgcGFxdWV0ZXMtY2Fzb30NCiMgcGFxdWV0ZXNfY2FzbyA8LSBkYXRhLmZyYW1lKA0KIyAgUGFxdWV0ZSA9IGMoInRtYXB0b29scyIsICJsZWFmbGV0IiwgInJhc3RlciIsICJyZ2RhbCIpLA0KIyAgRnVuY2nDs25fZW5fZWxfaW5mb3JtZSA9IGMoIlV0aWxpZGFkZXMgY2FydG9ncsOhZmljYXMgeSBnZW9jb2RpZmljYWNpw7NuIiwNCiMgICAgICAgICAgICAgICAgICAgICAgICAgICAgIlZpc3VhbGl6YWNpw7NuIGNhcnRvZ3LDoWZpY2EgaW50ZXJhY3RpdmEiLA0KIyAgICAgICAgICAgICAgICAgICAgICAgICAgICAiQ29tcGF0aWJpbGlkYWQgeSBtYW5lam8gZGUgY2FwYXMgcsOhc3RlciIsDQojICAgICAgICAgICAgICAgICAgICAgICAgICAgICJDb21wYXRpYmlsaWRhZCBoZXJlZGFkYTsgc3VzdGl0dWlkbyBvcGVyYXRpdmFtZW50ZSBwb3Igc2YvdGVycmEiKSwNCiMgIERpc3BvbmlibGUgPSBjKHJlcXVpcmVOYW1lc3BhY2UoInRtYXB0b29scyIsIHF1aWV0bHkgPSBUUlVFKSwNCiMgICAgICAgICAgICAgICAgIHJlcXVpcmVOYW1lc3BhY2UoImxlYWZsZXQiLCBxdWlldGx5ID0gVFJVRSksDQojICAgICAgICAgICAgICAgICByZXF1aXJlTmFtZXNwYWNlKCJyYXN0ZXIiLCBxdWlldGx5ID0gVFJVRSksDQojICAgICAgICAgICAgICAgICByZ2RhbF9kaXNwb25pYmxlKQ0KIyApDQojIGtuaXRyOjprYWJsZShwYXF1ZXRlc19jYXNvLCBjYXB0aW9uID0gIlBhcXVldGVzIGVzcGFjaWFsZXMgY29udGVtcGxhZG9zIikgfD4NCiMgIGthYmxlRXh0cmE6OmthYmxlX3N0eWxpbmcoYm9vdHN0cmFwX29wdGlvbnMgPSBjKCJzdHJpcGVkIiwgImhvdmVyIiksIGZ1bGxfd2lkdGggPSBUUlVFKQ0KDQojIFRhYmxhIGRlIHBhcXVldGVzIGVzcGFjaWFsZXMgdXRpbGl6YWRvcyBvIGNvbnRlbXBsYWRvcw0KcGFxdWV0ZXNfY2FzbyA8LSBkYXRhLmZyYW1lKA0KICBQYXF1ZXRlID0gYygNCiAgICAidG1hcHRvb2xzIiwNCiAgICAibGVhZmxldCIsDQogICAgInJhc3RlciIsDQogICAgInJnZGFsIg0KICApLA0KICBGdW5jaW9uX2VuX2VsX2luZm9ybWUgPSBjKA0KICAgICJVdGlsaWRhZGVzIGNhcnRvZ3JhZmljYXMgeSBnZW9jb2RpZmljYWNpb24iLA0KICAgICJWaXN1YWxpemFjaW9uIGNhcnRvZ3JhZmljYSBpbnRlcmFjdGl2YSIsDQogICAgIkNvbXBhdGliaWxpZGFkIHkgbWFuZWpvIGRlIGNhcGFzIHJhc3RlciIsDQogICAgIkNvbXBhdGliaWxpZGFkIGhlcmVkYWRhOyBzdXN0aXR1aWRvIG9wZXJhdGl2YW1lbnRlIHBvciBzZi90ZXJyYSINCiAgKSwNCiAgRGlzcG9uaWJsZSA9IGMoDQogICAgcmVxdWlyZU5hbWVzcGFjZSgidG1hcHRvb2xzIiwgcXVpZXRseSA9IFRSVUUpLA0KICAgIHJlcXVpcmVOYW1lc3BhY2UoImxlYWZsZXQiLCBxdWlldGx5ID0gVFJVRSksDQogICAgcmVxdWlyZU5hbWVzcGFjZSgicmFzdGVyIiwgcXVpZXRseSA9IFRSVUUpLA0KICAgIGlzVFJVRShyZ2RhbF9kaXNwb25pYmxlKQ0KICApLA0KICBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UsDQogIGNoZWNrLm5hbWVzID0gRkFMU0UNCikNCg0KIyBFdGlxdWV0YXMgY29tcHJlbnNpYmxlcyBwYXJhIGxhIGRpc3BvbmliaWxpZGFkDQpwYXF1ZXRlc19jYXNvJERpc3BvbmlibGUgPC0gaWZlbHNlKA0KICBwYXF1ZXRlc19jYXNvJERpc3BvbmlibGUsDQogICJTaSIsDQogICJObyINCikNCg0KIyBDb252ZXJ0aXIgZXhwbMOtY2l0YW1lbnRlIGVsIGNvbnRlbmlkbyBhIFVURi04DQpwYXF1ZXRlc19jYXNvW10gPC0gbGFwcGx5KA0KICBwYXF1ZXRlc19jYXNvLA0KICBmdW5jdGlvbih4KSB7DQogICAgaWYgKGlzLmNoYXJhY3Rlcih4KSkgew0KICAgICAgaWNvbnYoDQogICAgICAgIHgsDQogICAgICAgIGZyb20gPSAiIiwNCiAgICAgICAgdG8gPSAiVVRGLTgiLA0KICAgICAgICBzdWIgPSAiYnl0ZSINCiAgICAgICkNCiAgICB9IGVsc2Ugew0KICAgICAgeA0KICAgIH0NCiAgfQ0KKQ0KDQpwYXF1ZXRlc19jYXNvIDwtIG5vcm1hbGl6YXJfdGFibGFfdXRmOChwYXF1ZXRlc19jYXNvKQ0KDQojIEdlbmVyYXIgbGEgdGFibGEgSFRNTA0KDQp0YWJsYV9wYXF1ZXRlc19jYXNvIDwtIGtuaXRyOjprYWJsZSgNCiAgcGFxdWV0ZXNfY2FzbywNCiAgZm9ybWF0ID0gImh0bWwiLA0KICBjb2wubmFtZXMgPSBjKA0KICAgICJQYXF1ZXRlIiwNCiAgICAiRnVuY2lvbiBlbiBlbCBpbmZvcm1lIiwNCiAgICAiRGlzcG9uaWJsZSINCiAgKSwNCiAgY2FwdGlvbiA9ICJQYXF1ZXRlcyBlc3BhY2lhbGVzIGNvbnRlbXBsYWRvcyIsDQogIGFsaWduID0gYygibCIsICJsIiwgImMiKSwNCiAgZXNjYXBlID0gVFJVRQ0KKQ0KDQprYWJsZUV4dHJhOjprYWJsZV9zdHlsaW5nKA0KICB0YWJsYV9wYXF1ZXRlc19jYXNvLA0KICBib290c3RyYXBfb3B0aW9ucyA9IGMoDQogICAgInN0cmlwZWQiLA0KICAgICJob3ZlciIsDQogICAgImNvbmRlbnNlZCIsDQogICAgInJlc3BvbnNpdmUiDQogICksDQogIGZ1bGxfd2lkdGggPSBUUlVFLA0KICBwb3NpdGlvbiA9ICJjZW50ZXIiDQopDQoNCmBgYA0KDQojIyBEZXNjYXJnYSB5IHByZXBhcmFjacOzbiBkZSBkYXRvcw0KDQpgYGB7ciBkZXNjYXJnYXItZGF0b3N9DQojIENsaW1hdG9sb2fDrWFzIG1lbnN1YWxlcyBnbG9iYWxlcy4gZ2VvZGF0YSBhbG1hY2VuYSBsb3MgYXJjaGl2b3MgZW4gcnV0YV9kYXRvcy4NCnRtZWRfbSA8LSBnZW9kYXRhOjp3b3JsZGNsaW1fZ2xvYmFsKHZhciA9ICJ0YXZnIiwgcmVzID0gMTAsIHBhdGggPSBydXRhX2RhdG9zKQ0KcHJlY19tIDwtIGdlb2RhdGE6OndvcmxkY2xpbV9nbG9iYWwodmFyID0gInByZWMiLCByZXMgPSAxMCwgcGF0aCA9IHJ1dGFfZGF0b3MpDQoNCm5hbWVzKHRtZWRfbSkgPC0gcGFzdGUwKCJ0bWVkXyIsIHNwcmludGYoIiUwMmQiLCAxOjEyKSkNCm5hbWVzKHByZWNfbSkgPC0gcGFzdGUwKCJwcmVjXyIsIHNwcmludGYoIiUwMmQiLCAxOjEyKSkNCg0KIyBWZXJpZmljYWNpw7NuIGF1dG9tw6F0aWNhIGRlIHVuaWRhZGVzIGRlIHRlbXBlcmF0dXJhLg0KIyBFbiBhbGd1bmFzIGRpc3RyaWJ1Y2lvbmVzIHJhc3RlciBsYSB0ZW1wZXJhdHVyYSBwdWVkZSBlc3RhciBlc2NhbGFkYSBwb3IgMTAuDQptZWRpYV9wcnVlYmEgPC0gZ2xvYmFsKHRtZWRfbVtbMV1dLCAibWVhbiIsIG5hLnJtID0gVFJVRSlbMSwgMV0NCmlmIChpcy5maW5pdGUobWVkaWFfcHJ1ZWJhKSAmJiBtZWRpYV9wcnVlYmEgPiA2MCkgdG1lZF9tIDwtIHRtZWRfbSAvIDEwDQoNCiMgQWdyZWdhZG9zIGFudWFsZXMNCnRtZWRfYW51YWwgPC0gbWVhbih0bWVkX20sIG5hLnJtID0gVFJVRSkNCnByZWNfYW51YWwgPC0gc3VtKHByZWNfbSwgbmEucm0gPSBUUlVFKQ0KbmFtZXModG1lZF9hbnVhbCkgPC0gInRlbXBlcmF0dXJhX21lZGlhX0MiDQpuYW1lcyhwcmVjX2FudWFsKSA8LSAicHJlY2lwaXRhY2lvbl9hbnVhbF9tbSINCg0KIyBDb252ZXJzacOzbiBleHBsw61jaXRhIGFsIGZvcm1hdG8gUmFzdGVyTGF5ZXIgc29saWNpdGFkbyBlbiBsYSByw7picmljYS4NCiMgU2UgY29uc2VydmEgdGVycmEgcGFyYSBjw6FsY3Vsb3Mgcm9idXN0b3MgeSByYXN0ZXIgcGFyYSBpbnRlcm9wZXJhYmlsaWRhZC4NCnRtZWRfYW51YWxfcmFzdGVyIDwtIHJhc3Rlcjo6cmFzdGVyKHRtZWRfYW51YWwpDQpwcmVjX2FudWFsX3Jhc3RlciA8LSByYXN0ZXI6OnJhc3RlcihwcmVjX2FudWFsKQ0KDQojIEzDrW1pdGVzIG5hY2lvbmFsZXMsIHByb3Zpc3RvcyBwb3IgZ2VvZGF0YS9HQURNDQpwYWlzZXMgPC0gZ2VvZGF0YTo6d29ybGQocGF0aCA9IHJ1dGFfZGF0b3MpDQoNCiMgQ29udHJvbCBiw6FzaWNvDQpzdG9waWZub3Qobmx5cih0bWVkX20pID09IDEyLCBubHlyKHByZWNfbSkgPT0gMTIpDQpyZXN1bWVuX2RhdG9zIDwtIGRhdGEuZnJhbWUoDQogIENhcmFjdGVyw61zdGljYSA9IGMoIk9iamV0byIsICJGaWxhcyIsICJDb2x1bW5hcyIsICJDYXBhcyBjbGltw6F0aWNhcyIsDQogICAgICAgICAgICAgICAgICAgICAiUmVzb2x1Y2nDs24gZXNwYWNpYWwiLCAiRXh0ZW5zacOzbiIsICJSZWZlcmVuY2lhIGVzcGFjaWFsIiwNCiAgICAgICAgICAgICAgICAgICAgICJQZXJpb2RvIGNsaW3DoXRpY28iKSwNCiAgVmFsb3IgPSBjKCJSw6FzdGVyIG11bHRpYmFuZGEgZ2xvYmFsIiwgdGVycmE6Om5yb3codG1lZF9tKSwgdGVycmE6Om5jb2wodG1lZF9tKSwNCiAgICAgICAgICAgIHRlcnJhOjpubHlyKHRtZWRfbSkgKyB0ZXJyYTo6bmx5cihwcmVjX20pLA0KICAgICAgICAgICAgcGFzdGUocm91bmQodGVycmE6OnJlcyh0bWVkX20pLCA2KSwgY29sbGFwc2UgPSAiIMOXICIpLA0KICAgICAgICAgICAgcGFzdGUocm91bmQoYXMudmVjdG9yKHRlcnJhOjpleHQodG1lZF9tKSksIDIpLCBjb2xsYXBzZSA9ICIsICIpLA0KICAgICAgICAgICAgIldHUyA4NCAoRVBTRzo0MzI2KSIsICIxOTcw4oCTMjAwMCIpDQopDQoNCnJlc3VtZW5fZGF0b3MgPC0gbm9ybWFsaXphcl90YWJsYV91dGY4KHJlc3VtZW5fZGF0b3MpDQprbml0cjo6a2FibGUocmVzdW1lbl9kYXRvcywgY2FwdGlvbiA9ICJDYXJhY3RlcsOtc3RpY2FzIGRlIGxvcyBkYXRvcyBjbGltw6F0aWNvcyB1dGlsaXphZG9zIiwNCiAgICAgICAgICAgICBhbGlnbiA9IGMoImwiLCAibCIpKSB8Pg0KICBrYWJsZUV4dHJhOjprYWJsZV9zdHlsaW5nKGJvb3RzdHJhcF9vcHRpb25zID0gYygic3RyaXBlZCIsICJob3ZlciIsICJjb25kZW5zZWQiKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBmdWxsX3dpZHRoID0gVFJVRSkNCmBgYA0KDQojIEFwdGl0dWQgY2xpbcOhdGljYSBwb3IgcmFuZ29zIMOzcHRpbW9zDQoNClNlIGNhbGN1bGEgcHJpbWVybyBsYSBhZGVjdWFjacOzbiBkZSBjYWRhIHZhcmlhYmxlIGVuIGVzY2FsYSBjb250aW51YSBkZSAwIGEgMSBtZWRpYW50ZSB1bmEgZnVuY2nDs24gdHJhcGV6b2lkYWwgY29uIHRvbGVyYW5jaWEgZXh0ZXJpb3IuIERlc3B1w6lzIHNlIG9idGllbmUgdW4gw61uZGljZSBjb25zZXJ2YWRvciBjb21vIGVsIG3DrW5pbW8gZW50cmUgdGVtcGVyYXR1cmEgeSBwcmVjaXBpdGFjacOzbi4gQXPDrSwgdW5hIGxpbWl0YWNpw7NuIGZ1ZXJ0ZSBlbiB1bmEgdmFyaWFibGUgbm8gcXVlZGEgY29tcGVuc2FkYSBwb3IgbGEgb3RyYS4NCg0KYGBge3IgYXB0aXR1ZC1zaW4tYXBwLXY1fQ0KIyBWRVJTScOTTiA1OiBjw6FsY3VsbyBtZWRpYW50ZSDDoWxnZWJyYSByYXN0ZXIgbmF0aXZhLCBzaW4gZnVuY2lvbmVzIHBlcnNvbmFsaXphZGFzLg0KIyBMb3Mgb2JqZXRvcyBlc3BhY2lhbGVzIHNlIHJlY29uc3RydXllbiBlbiBjYWRhIHJlbmRlcml6YWRvIHkgbm8gc2UgYWxtYWNlbmFuIGVuIGNhY2jDqS4NCg0KIyBBZGVjdWFjacOzbiB0w6lybWljYSB0cmFwZXpvaWRhbCwgY2FsY3VsYWRhIGRpcmVjdGFtZW50ZSBjb24gw6FsZ2VicmEgcmFzdGVyLg0KYXB0X3RlbXAgPC0gdGVycmE6OmlmZWwoDQogIHRtZWRfYW51YWwgPCAxOCB8IHRtZWRfYW51YWwgPiAzNSwgMCwNCiAgdGVycmE6OmlmZWwoDQogICAgdG1lZF9hbnVhbCA8IDIyLjUsDQogICAgKHRtZWRfYW51YWwgLSAxOCkgLyAoMjIuNSAtIDE4KSwNCiAgICB0ZXJyYTo6aWZlbCgNCiAgICAgIHRtZWRfYW51YWwgPD0gMjgsDQogICAgICAxLA0KICAgICAgKDM1IC0gdG1lZF9hbnVhbCkgLyAoMzUgLSAyOCkNCiAgICApDQogICkNCikNCg0KIyBBZGVjdWFjacOzbiBkZSBwcmVjaXBpdGFjacOzbiB0cmFwZXpvaWRhbC4NCmFwdF9wcmVjIDwtIHRlcnJhOjppZmVsKA0KICBwcmVjX2FudWFsIDwgNzUwIHwgcHJlY19hbnVhbCA+IDUwMDAsIDAsDQogIHRlcnJhOjppZmVsKA0KICAgIHByZWNfYW51YWwgPCAxNTAwLA0KICAgIChwcmVjX2FudWFsIC0gNzUwKSAvICgxNTAwIC0gNzUwKSwNCiAgICB0ZXJyYTo6aWZlbCgNCiAgICAgIHByZWNfYW51YWwgPD0gMzUwMCwNCiAgICAgIDEsDQogICAgICAoNTAwMCAtIHByZWNfYW51YWwpIC8gKDUwMDAgLSAzNTAwKQ0KICAgICkNCiAgKQ0KKQ0KDQojIEVsIG9wZXJhZG9yIGNvbnNlcnZhZG9yIHRvbWEgbGEgbWVub3IgYWRlY3VhY2nDs24gZGUgbGFzIGRvcyB2YXJpYWJsZXMuDQphcHRfY2xpbWF0aWNhIDwtIHRlcnJhOjppZmVsKGFwdF90ZW1wIDw9IGFwdF9wcmVjLCBhcHRfdGVtcCwgYXB0X3ByZWMpDQpuYW1lcyhhcHRfdGVtcCkgPC0gImFwdGl0dWRfdGVtcGVyYXR1cmEiDQpuYW1lcyhhcHRfcHJlYykgPC0gImFwdGl0dWRfcHJlY2lwaXRhY2lvbiINCm5hbWVzKGFwdF9jbGltYXRpY2EpIDwtICJhcHRpdHVkX2NsaW1hdGljYSINCg0KIyBDb250cm9sZXMgYW50ZXMgZGUgY29udGludWFyLg0Kc3RvcGlmbm90KGluaGVyaXRzKGFwdF9jbGltYXRpY2EsICJTcGF0UmFzdGVyIikpDQpzdG9waWZub3QodGVycmE6Om5seXIoYXB0X2NsaW1hdGljYSkgPT0gMSkNCiMgcmFuZ29fYXB0aXR1ZCA8LSB0ZXJyYTo6bWlubWF4KGFwdF9jbGltYXRpY2EpDQojIHByaW50KHJhbmdvX2FwdGl0dWQpDQojIE9idGVuZXIgbG9zIHZhbG9yZXMgbcOtbmltbyB5IG3DoXhpbW8NCnJhbmdvX2FwdGl0dWQgPC0gdGVycmE6Om1pbm1heChhcHRfY2xpbWF0aWNhKQ0KDQojIENvbnN0cnVpciB1bmEgdGFibGEgb3JnYW5pemFkYQ0KdGFibGFfcmFuZ29fYXB0aXR1ZCA8LSBkYXRhLmZyYW1lKA0KICBJbmRpY2Fkb3IgPSBjKA0KICAgICJWYWxvciBtw61uaW1vIiwNCiAgICAiVmFsb3IgbcOheGltbyINCiAgKSwNCiAgYMONbmRpY2UgZGUgYXB0aXR1ZCBjbGltw6F0aWNhYCA9IGMoDQogICAgYXMubnVtZXJpYyhyYW5nb19hcHRpdHVkWzEsIDFdKSwNCiAgICBhcy5udW1lcmljKHJhbmdvX2FwdGl0dWRbMiwgMV0pDQogICksDQogIEludGVycHJldGFjacOzbiA9IGMoDQogICAgIkF1c2VuY2lhIGRlIGFwdGl0dWQgY2xpbcOhdGljYSIsDQogICAgIkFwdGl0dWQgY2xpbcOhdGljYSDDs3B0aW1hIg0KICApLA0KICBjaGVjay5uYW1lcyA9IEZBTFNFDQopDQoNCg0KdGFibGFfcmFuZ29fYXB0aXR1ZCA8LSBub3JtYWxpemFyX3RhYmxhX3V0ZjgodGFibGFfcmFuZ29fYXB0aXR1ZCkNCiMgUHJlc2VudGFyIGxhIHRhYmxhIGNvbiBmb3JtYXRvIHByb2Zlc2lvbmFsDQprbml0cjo6a2FibGUoDQogIHRhYmxhX3JhbmdvX2FwdGl0dWQsDQogIGNhcHRpb24gPSAiUmFuZ28gZGVsIMOtbmRpY2UgZGUgYXB0aXR1ZCBjbGltw6F0aWNhIHBhcmEgbGEgY2HDsWEgZGUgYXrDumNhciIsDQogIGNvbC5uYW1lcyA9IGMoDQogICAgIkluZGljYWRvciIsDQogICAgIsONbmRpY2UgZGUgYXB0aXR1ZCBjbGltw6F0aWNhIiwNCiAgICAiSW50ZXJwcmV0YWNpw7NuIg0KICApLA0KICBkaWdpdHMgPSAyLA0KICBhbGlnbiA9IGMoImwiLCAiYyIsICJsIikNCikgfD4NCiAga2FibGVFeHRyYTo6a2FibGVfc3R5bGluZygNCiAgICBib290c3RyYXBfb3B0aW9ucyA9IGMoDQogICAgICAic3RyaXBlZCIsDQogICAgICAiaG92ZXIiLA0KICAgICAgImNvbmRlbnNlZCIsDQogICAgICAicmVzcG9uc2l2ZSINCiAgICApLA0KICAgIGZ1bGxfd2lkdGggPSBUUlVFLA0KICAgIHBvc2l0aW9uID0gImNlbnRlciINCiAgKSB8Pg0KICBrYWJsZUV4dHJhOjpjb2x1bW5fc3BlYygNCiAgICBjb2x1bW4gPSAxLA0KICAgIGJvbGQgPSBUUlVFLA0KICAgIGNvbG9yID0gIiMxMjNGNzIiDQogICkgfD4NCiAga2FibGVFeHRyYTo6Y29sdW1uX3NwZWMoDQogICAgY29sdW1uID0gMiwNCiAgICBib2xkID0gVFJVRSwNCiAgICBjb2xvciA9ICJ3aGl0ZSIsDQogICAgYmFja2dyb3VuZCA9ICIjMUY1Qjk2IiwNCiAgICB3aWR0aCA9ICI0Y20iDQogICkgfD4NCiAga2FibGVFeHRyYTo6Y29sdW1uX3NwZWMoDQogICAgY29sdW1uID0gMywNCiAgICB3aWR0aCA9ICI3Y20iDQogICkNCg0KY2xhc2VfYXB0aXR1ZCA8LSB0ZXJyYTo6Y2xhc3NpZnkoDQogIGFwdF9jbGltYXRpY2EsDQogIHJjbCA9IG1hdHJpeChjKA0KICAgIC1JbmYsIDAuMjUsIDEsDQogICAgMC4yNSwgMC41MCwgMiwNCiAgICAwLjUwLCAwLjc1LCAzLA0KICAgIDAuNzUsIEluZiwgNA0KICApLCBuY29sID0gMywgYnlyb3cgPSBUUlVFKSwNCiAgaW5jbHVkZS5sb3dlc3QgPSBUUlVFLA0KICByaWdodCA9IEZBTFNFDQopDQoNCmxldmVscyhjbGFzZV9hcHRpdHVkKSA8LSBkYXRhLmZyYW1lKA0KICB2YWx1ZSA9IDE6NCwNCiAgY2xhc2UgPSBjKCJNdXkgYmFqYSIsICJCYWphIiwgIk1lZGlhIiwgIkFsdGEiKQ0KKQ0KDQp0ZXJyYTo6d3JpdGVSYXN0ZXIoDQogIGModG1lZF9hbnVhbCwgcHJlY19hbnVhbCwgYXB0X3RlbXAsIGFwdF9wcmVjLCBhcHRfY2xpbWF0aWNhKSwNCiAgZmlsZS5wYXRoKHJ1dGFfcmVzdWx0YWRvcywgImFwdGl0dWRfY2xpbWF0aWNhX2NhbmEudGlmIiksDQogIG92ZXJ3cml0ZSA9IFRSVUUNCikNCmBgYA0KDQoNCiMjIE1hcGFzIGdsb2JhbGVzDQoNCmBgYHtyIG1hcGEtZ2xvYmFsLWNvbnRpbnVvfQ0KcGxvdChhcHRfY2xpbWF0aWNhLA0KICAgICBjb2wgPSBoY2wuY29sb3JzKDEwMCwgIllsR24iLCByZXYgPSBGQUxTRSksDQogICAgIHJhbmdlID0gYygwLCAxKSwNCiAgICAgbWFpbiA9ICJBcHRpdHVkIGNsaW3DoXRpY2EgZ2xvYmFsIHBhcmEgY2HDsWEgZGUgYXrDumNhciIsDQogICAgIGF4ZXMgPSBUUlVFKQ0KbGluZXMocGFpc2VzLCBjb2wgPSAiZ3JleTM1IiwgbHdkID0gMC4yNSkNCmBgYA0KDQpgYGB7ciBtYXBhLWdsb2JhbC1jbGFzZXN9DQpwbG90KGNsYXNlX2FwdGl0dWQsDQogICAgIGNvbCA9IGMoIiNkNzMwMjciLCAiI2ZlZTA4YiIsICIjOTFjZjYwIiwgIiMxYTk4NTAiKSwNCiAgICAgbWFpbiA9ICJDbGFzZXMgZGUgYXB0aXR1ZCBjbGltw6F0aWNhIikNCmxpbmVzKHBhaXNlcywgY29sID0gImdyZXkzMCIsIGx3ZCA9IDAuMjUpDQpgYGANCg0KIyMgUGHDrXNlcyBjb24gbWF5b3Igc3VwZXJmaWNpZSBkZSBhcHRpdHVkIGFsdGENCg0KRW4gbHVnYXIgZGUgZXNjb2dlciBwYcOtc2VzIHN1YmpldGl2YW1lbnRlLCBzZSBjYWxjdWxhIGxhIHN1cGVyZmljaWUgZGUgY2VsZGFzIGNvbiDDrW5kaWNlIG1heW9yIG8gaWd1YWwgYSAwLDc1LiBMYSBzdXBlcmZpY2llIHNlIGVzdGltYSBjb24gYGNlbGxTaXplKClgIGVuIGttwrIgeSBzZSByZXN1bWUgcG9yIHBhw61zLg0KDQpgYGB7ciByYW5raW5nLXBhaXNlc30NCmFyZWFfYWx0YSA8LSBpZmVsKGFwdF9jbGltYXRpY2EgPj0gMC43NSwgY2VsbFNpemUoYXB0X2NsaW1hdGljYSwgdW5pdCA9ICJrbSIpLCBOQSkNCmFyZWFfcG9yX3BhaXMgPC0gdGVycmE6OmV4dHJhY3QoYXJlYV9hbHRhLCBwYWlzZXMsIGZ1biA9IHN1bSwgbmEucm0gPSBUUlVFKQ0KDQpjYW1wb19ub21icmUgPC0gaW50ZXJzZWN0KGMoIk5BTUVfMCIsICJDT1VOVFJZIiwgIk5BTUVfRU5HTEkiLCAiQURNSU4iKSwgbmFtZXMocGFpc2VzKSlbMV0NCmlmIChpcy5uYShjYW1wb19ub21icmUpKSBzdG9wKCJObyBzZSBlbmNvbnRyw7MgZWwgY2FtcG8gZGUgbm9tYnJlIGRlIHBhw61zIGVuIGxhIGNhcGEgbXVuZGlhbC4iKQ0KDQpyYW5raW5nX3BhaXNlcyA8LSBkYXRhLmZyYW1lKA0KICBwYWlzID0gcGFpc2VzW1tjYW1wb19ub21icmVdXSwNCiAgYXJlYV9hbHRhX2ttMiA9IGFyZWFfcG9yX3BhaXNbLCAyXQ0KKSB8Pg0KICBkcGx5cjo6ZmlsdGVyKGlzLmZpbml0ZShhcmVhX2FsdGFfa20yKSwgYXJlYV9hbHRhX2ttMiA+IDApIHw+DQogIGRwbHlyOjphcnJhbmdlKGRwbHlyOjpkZXNjKGFyZWFfYWx0YV9rbTIpKSB8Pg0KICBkcGx5cjo6bXV0YXRlKGFyZWFfYWx0YV9rbTIgPSByb3VuZChhcmVhX2FsdGFfa20yLCAwKSkNCg0Ka25pdHI6OmthYmxlKGhlYWQocmFua2luZ19wYWlzZXMsIDEwKSwNCiAgICAgICAgICAgICBjYXB0aW9uID0gIkRpZXogcGHDrXNlcyBjb24gbWF5b3Igc3VwZXJmaWNpZSBjbGltw6F0aWNhbWVudGUgYXB0YSIpIHw+DQogIGthYmxlRXh0cmE6OmthYmxlX3N0eWxpbmcoYm9vdHN0cmFwX29wdGlvbnMgPSBjKCJzdHJpcGVkIiwgImhvdmVyIiwgImNvbmRlbnNlZCIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZ1bGxfd2lkdGggPSBUUlVFKQ0KDQpwYWlzZXNfdG9wM19ub21icmVzIDwtIGhlYWQocmFua2luZ19wYWlzZXMkcGFpcywgMykNCiMgRWwgb2JqZXRvIHNlIHJlY29uc3RydXllIGRlc3B1w6lzIGRlIGFwbGljYXIsIHNpIGZ1ZXJhIG5lY2VzYXJpbywgZWwgcmVzcGFsZG8NCiMgcG9yIGFwdGl0dWQgbcOheGltYSBlbiBlbCBibG9xdWUgZGUgbWFwYXMuDQpwYWlzZXNfdG9wMyA8LSBwYWlzZXNbcGFpc2VzW1tjYW1wb19ub21icmVdXSAlaW4lIHBhaXNlc190b3AzX25vbWJyZXMsIF0NCmBgYA0KDQojIyBNYXBhcyBkZSBwYcOtc2VzIGNvbiBtYXlvciBzdXBlcmZpY2llIGNsaW3DoXRpY2EgYXB0YSAoVG9wIDMpDQoNCmBgYHtyIG1hcGFzLXBhaXNlcy10b3AzLWNvcnJlZ2lkbywgZmlnLndpZHRoPTExLCBmaWcuaGVpZ2h0PTEyLCBmaWcuYWxpZ249ImNlbnRlciJ9DQoNCiMgPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0NCiMgTUFQQVMgREUgUEHDjVNFUyBDT04gTUFZT1IgU1VQRVJGSUNJRSBDTElNw4FUSUNBTUVOVEUgQVBUQQ0KIyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQ0KDQojIDEuIFZhbGlkYXIgbG9zIG9iamV0b3MgcmVxdWVyaWRvcw0KaWYgKCFleGlzdHMoImFwdF9jbGltYXRpY2EiKSkgew0KICBzdG9wKCJObyBleGlzdGUgZWwgb2JqZXRvICdhcHRfY2xpbWF0aWNhJy4iKQ0KfQ0KDQppZiAoIWV4aXN0cygicGFpc2VzIikpIHsNCiAgc3RvcCgiTm8gZXhpc3RlIGVsIG9iamV0byBlc3BhY2lhbCAncGFpc2VzJy4iKQ0KfQ0KDQppZiAoIWluaGVyaXRzKGFwdF9jbGltYXRpY2EsICJTcGF0UmFzdGVyIikpIHsNCiAgc3RvcCgiJ2FwdF9jbGltYXRpY2EnIG5vIGVzIHVuIG9iamV0byBTcGF0UmFzdGVyLiIpDQp9DQoNCmlmICghaW5oZXJpdHMocGFpc2VzLCAiU3BhdFZlY3RvciIpKSB7DQogIHN0b3AoIidwYWlzZXMnIG5vIGVzIHVuIG9iamV0byBTcGF0VmVjdG9yLiIpDQp9DQoNCiMgMi4gRXh0cmFlciBsb3MgYXRyaWJ1dG9zIGRlIGxhIGNhcGEgZXNwYWNpYWwNCmF0cmlidXRvc19wYWlzZXMgPC0gdGVycmE6OnZhbHVlcygNCiAgcGFpc2VzLA0KICBkYXRhZnJhbWUgPSBUUlVFDQopDQoNCmlmICgNCiAgIWlzLmRhdGEuZnJhbWUoYXRyaWJ1dG9zX3BhaXNlcykgfHwNCiAgbnJvdyhhdHJpYnV0b3NfcGFpc2VzKSA9PSAwTA0KKSB7DQogIHN0b3AoDQogICAgIkxhIGNhcGEgbXVuZGlhbCBubyBjb250aWVuZSB1bmEgdGFibGEgZGUgYXRyaWJ1dG9zIHbDoWxpZGEuIg0KICApDQp9DQoNCiMgMy4gSWRlbnRpZmljYXIgYXV0b23DoXRpY2FtZW50ZSBsYSBjb2x1bW5hIGNvbiBlbCBub21icmUgZGVsIHBhw61zDQpjYW1wb3NfcG9zaWJsZXMgPC0gYygNCiAgIk5BTUVfMCIsDQogICJDT1VOVFJZIiwNCiAgIkFETUlOIiwNCiAgIk5BTUVfRU5HTEkiLA0KICAiTkFNRSIsDQogICJTT1ZFUkVJR05UIg0KKQ0KDQpjYW1wb3NfZW5jb250cmFkb3MgPC0gaW50ZXJzZWN0KA0KICBjYW1wb3NfcG9zaWJsZXMsDQogIG5hbWVzKGF0cmlidXRvc19wYWlzZXMpDQopDQoNCmlmIChsZW5ndGgoY2FtcG9zX2VuY29udHJhZG9zKSA+IDBMKSB7DQoNCiAgY2FtcG9fbm9tYnJlIDwtIGNhbXBvc19lbmNvbnRyYWRvc1sxXQ0KDQp9IGVsc2Ugew0KDQogIGNvbHVtbmFzX3RleHRvIDwtIG5hbWVzKGF0cmlidXRvc19wYWlzZXMpWw0KICAgIHZhcHBseSgNCiAgICAgIGF0cmlidXRvc19wYWlzZXMsDQogICAgICBmdW5jdGlvbih4KSB7DQogICAgICAgIGlzLmNoYXJhY3Rlcih4KSB8fCBpcy5mYWN0b3IoeCkNCiAgICAgIH0sDQogICAgICBsb2dpY2FsKDEpDQogICAgKQ0KICBdDQoNCiAgaWYgKGxlbmd0aChjb2x1bW5hc190ZXh0bykgPT0gMEwpIHsNCiAgICBzdG9wKA0KICAgICAgcGFzdGUwKA0KICAgICAgICAiTm8gc2UgaWRlbnRpZmljw7MgdW5hIGNvbHVtbmEgZGUgbm9tYnJlcy4gIiwNCiAgICAgICAgIkNvbHVtbmFzIGRpc3BvbmlibGVzOiAiLA0KICAgICAgICBwYXN0ZShuYW1lcyhhdHJpYnV0b3NfcGFpc2VzKSwgY29sbGFwc2UgPSAiLCAiKQ0KICAgICAgKQ0KICAgICkNCiAgfQ0KDQogIGNhbnRpZGFkX3VuaWNvcyA8LSB2YXBwbHkoDQogICAgYXRyaWJ1dG9zX3BhaXNlc1tjb2x1bW5hc190ZXh0b10sDQogICAgZnVuY3Rpb24oeCkgew0KICAgICAgbGVuZ3RoKA0KICAgICAgICB1bmlxdWUoDQogICAgICAgICAgc3RhdHM6Om5hLm9taXQoDQogICAgICAgICAgICBhcy5jaGFyYWN0ZXIoeCkNCiAgICAgICAgICApDQogICAgICAgICkNCiAgICAgICkNCiAgICB9LA0KICAgIGludGVnZXIoMSkNCiAgKQ0KDQogIGNhbXBvX25vbWJyZSA8LSBjb2x1bW5hc190ZXh0b1sNCiAgICB3aGljaC5tYXgoY2FudGlkYWRfdW5pY29zKQ0KICBdDQp9DQoNCiMgNC4gRXh0cmFlciBsb3Mgbm9tYnJlcyBkZSBwYcOtc2VzDQpub21icmVzX3BhaXNlcyA8LSB0cmltd3MoDQogIGFzLmNoYXJhY3RlcigNCiAgICBhdHJpYnV0b3NfcGFpc2VzW1tjYW1wb19ub21icmVdXQ0KICApDQopDQoNCm51bWVyb19wYWlzZXMgPC0gbGVuZ3RoKG5vbWJyZXNfcGFpc2VzKQ0KDQppZiAobnVtZXJvX3BhaXNlcyA9PSAwTCkgew0KICBzdG9wKA0KICAgIHBhc3RlMCgNCiAgICAgICJMYSBjb2x1bW5hICciLA0KICAgICAgY2FtcG9fbm9tYnJlLA0KICAgICAgIicgbm8gY29udGllbmUgbm9tYnJlcyBkZSBwYcOtc2VzLiINCiAgICApDQogICkNCn0NCg0KaWYgKG51bWVyb19wYWlzZXMgIT0gdGVycmE6Om5yb3cocGFpc2VzKSkgew0KICBzdG9wKA0KICAgIHBhc3RlMCgNCiAgICAgICJMYSBjYW50aWRhZCBkZSBub21icmVzICgiLA0KICAgICAgbnVtZXJvX3BhaXNlcywNCiAgICAgICIpIG5vIGNvaW5jaWRlIGNvbiBsYSBjYW50aWRhZCBkZSBnZW9tZXRyw61hcyAoIiwNCiAgICAgIHRlcnJhOjpucm93KHBhaXNlcyksDQogICAgICAiKS4iDQogICAgKQ0KICApDQp9DQoNCiMgNS4gQ29uc3RydWlyIGVsIGNhdMOhbG9nbywgY29uc2VydmFuZG8gZWwgw61uZGljZSBlc3BhY2lhbA0KY2F0YWxvZ29fcGFpc2VzIDwtIGRhdGEuZnJhbWUoDQogIElEID0gc2VxX2Fsb25nKG5vbWJyZXNfcGFpc2VzKSwNCiAgcGFpcyA9IG5vbWJyZXNfcGFpc2VzLA0KICBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UNCikNCg0KIyA2LiBDYWxjdWxhciBlbCDDoXJlYSBkZSBjYWRhIGNlbGRhIGVuIGttwrINCmFyZWFfY2VsZGFfa20yIDwtIHRlcnJhOjpjZWxsU2l6ZSgNCiAgYXB0X2NsaW1hdGljYSwNCiAgdW5pdCA9ICJrbSINCikNCg0KIyA3LiBDb25zZXJ2YXIgZWwgw6FyZWEgY29uIGFwdGl0dWQgY2xpbcOhdGljYSBhbHRhDQphcmVhX2FsdGEgPC0gdGVycmE6OmlmZWwoDQogIGFwdF9jbGltYXRpY2EgPj0gMC43NSwNCiAgYXJlYV9jZWxkYV9rbTIsDQogIE5BDQopDQoNCm5hbWVzKGFyZWFfYWx0YSkgPC0gImFyZWFfYWx0YV9rbTIiDQoNCiMgOC4gQ2FsY3VsYXIgY2FkYSBlc3RhZMOtc3RpY2EgcG9yIHNlcGFyYWRvDQojIE5vIHNlIHV0aWxpemFuIGZ1bmNpb25lcyBwZXJzb25hbGl6YWRhcyBtdWx0aXZhcmlhZGFzLg0KDQpyZXN1bHRhZG9fYXJlYSA8LSB0ZXJyYTo6ZXh0cmFjdCgNCiAgYXJlYV9hbHRhLA0KICBwYWlzZXMsDQogIGZ1biA9ICJzdW0iLA0KICBuYS5ybSA9IFRSVUUNCikNCg0KcmVzdWx0YWRvX21lZGlhIDwtIHRlcnJhOjpleHRyYWN0KA0KICBhcHRfY2xpbWF0aWNhLA0KICBwYWlzZXMsDQogIGZ1biA9ICJtZWFuIiwNCiAgbmEucm0gPSBUUlVFDQopDQoNCnJlc3VsdGFkb19tYXhpbWEgPC0gdGVycmE6OmV4dHJhY3QoDQogIGFwdF9jbGltYXRpY2EsDQogIHBhaXNlcywNCiAgZnVuID0gIm1heCIsDQogIG5hLnJtID0gVFJVRQ0KKQ0KDQojIDkuIFZlcmlmaWNhciB5IG5vcm1hbGl6YXIgbGFzIHRhYmxhcyBleHRyYcOtZGFzDQppZiAobmNvbChyZXN1bHRhZG9fYXJlYSkgPCAyTCkgew0KICBzdG9wKCJObyBzZSBwdWRvIGNhbGN1bGFyIGVsIMOhcmVhIGFsdGFtZW50ZSBhcHRhIHBvciBwYcOtcy4iKQ0KfQ0KDQppZiAobmNvbChyZXN1bHRhZG9fbWVkaWEpIDwgMkwpIHsNCiAgc3RvcCgiTm8gc2UgcHVkbyBjYWxjdWxhciBsYSBhcHRpdHVkIG1lZGlhIHBvciBwYcOtcy4iKQ0KfQ0KDQppZiAobmNvbChyZXN1bHRhZG9fbWF4aW1hKSA8IDJMKSB7DQogIHN0b3AoIk5vIHNlIHB1ZG8gY2FsY3VsYXIgbGEgYXB0aXR1ZCBtw6F4aW1hIHBvciBwYcOtcy4iKQ0KfQ0KDQpyZXN1bHRhZG9fYXJlYSA8LSBkYXRhLmZyYW1lKA0KICBJRCA9IGFzLmludGVnZXIocmVzdWx0YWRvX2FyZWFbWzFdXSksDQogIGFyZWFfYWx0YV9rbTIgPSBhcy5udW1lcmljKHJlc3VsdGFkb19hcmVhW1syXV0pDQopDQoNCnJlc3VsdGFkb19tZWRpYSA8LSBkYXRhLmZyYW1lKA0KICBJRCA9IGFzLmludGVnZXIocmVzdWx0YWRvX21lZGlhW1sxXV0pLA0KICBhcHRpdHVkX21lZGlhID0gYXMubnVtZXJpYyhyZXN1bHRhZG9fbWVkaWFbWzJdXSkNCikNCg0KcmVzdWx0YWRvX21heGltYSA8LSBkYXRhLmZyYW1lKA0KICBJRCA9IGFzLmludGVnZXIocmVzdWx0YWRvX21heGltYVtbMV1dKSwNCiAgYXB0aXR1ZF9tYXhpbWEgPSBhcy5udW1lcmljKHJlc3VsdGFkb19tYXhpbWFbWzJdXSkNCikNCg0KIyAxMC4gSW50ZWdyYXIgbG9zIHJlc3VsdGFkb3MNCmVzdGFkaXN0aWNhc19wYWlzZXMgPC0gY2F0YWxvZ29fcGFpc2VzIHw+DQogIGRwbHlyOjpsZWZ0X2pvaW4oDQogICAgcmVzdWx0YWRvX2FyZWEsDQogICAgYnkgPSAiSUQiDQogICkgfD4NCiAgZHBseXI6OmxlZnRfam9pbigNCiAgICByZXN1bHRhZG9fbWVkaWEsDQogICAgYnkgPSAiSUQiDQogICkgfD4NCiAgZHBseXI6OmxlZnRfam9pbigNCiAgICByZXN1bHRhZG9fbWF4aW1hLA0KICAgIGJ5ID0gIklEIg0KICApDQoNCiMgTG9zIHBhw61zZXMgc2luIMOhcmVhIGRlIGFwdGl0dWQgYWx0YSByZWNpYmVuIGNlcm8NCmVzdGFkaXN0aWNhc19wYWlzZXMkYXJlYV9hbHRhX2ttMlsNCiAgaXMubmEoZXN0YWRpc3RpY2FzX3BhaXNlcyRhcmVhX2FsdGFfa20yKQ0KXSA8LSAwDQoNCiMgMTEuIFZhbGlkYWNpw7NuIGRlbCByZXN1bHRhZG8gaW50ZWdyYWRvDQpzdG9waWZub3QoDQogIG5yb3coZXN0YWRpc3RpY2FzX3BhaXNlcykgPT0gbnVtZXJvX3BhaXNlcywNCiAgaXMubnVtZXJpYyhlc3RhZGlzdGljYXNfcGFpc2VzJGFyZWFfYWx0YV9rbTIpLA0KICBpcy5udW1lcmljKGVzdGFkaXN0aWNhc19wYWlzZXMkYXB0aXR1ZF9tZWRpYSksDQogIGlzLm51bWVyaWMoZXN0YWRpc3RpY2FzX3BhaXNlcyRhcHRpdHVkX21heGltYSkNCikNCg0KIyAxMi4gQ29uc2VydmFyIHBhw61zZXMgY29uIG5vbWJyZXMgeSBkYXRvcyBjbGltw6F0aWNvcyB2w6FsaWRvcw0KZXN0YWRpc3RpY2FzX3ZhbGlkYXMgPC0gZXN0YWRpc3RpY2FzX3BhaXNlcyB8Pg0KICBkcGx5cjo6ZmlsdGVyKA0KICAgICFpcy5uYShwYWlzKSwNCiAgICBuemNoYXIodHJpbXdzKHBhaXMpKSwNCiAgICBpcy5maW5pdGUoYXB0aXR1ZF9tYXhpbWEpDQogICkgfD4NCiAgZHBseXI6OmFycmFuZ2UoDQogICAgZHBseXI6OmRlc2MoYXJlYV9hbHRhX2ttMiksDQogICAgZHBseXI6OmRlc2MoYXB0aXR1ZF9tZWRpYSksDQogICAgZHBseXI6OmRlc2MoYXB0aXR1ZF9tYXhpbWEpDQogICkNCg0KaWYgKG5yb3coZXN0YWRpc3RpY2FzX3ZhbGlkYXMpID09IDBMKSB7DQogIHN0b3AoDQogICAgIk5vIGV4aXN0ZW4gcGHDrXNlcyBjb24gZXN0YWTDrXN0aWNhcyBjbGltw6F0aWNhcyB2w6FsaWRhcy4iDQogICkNCn0NCg0KIyAxMy4gU2VsZWNjaW9uYXIgbG9zIHRyZXMgcHJpbWVyb3MgcGHDrXNlcw0KdG9wX3BhaXNlcyA8LSBlc3RhZGlzdGljYXNfdmFsaWRhcyB8Pg0KICBkcGx5cjo6c2xpY2VfaGVhZCgNCiAgICBuID0gbWluKDNMLCBucm93KGVzdGFkaXN0aWNhc192YWxpZGFzKSkNCiAgKQ0KDQpwYWlzZXNfdG9wM19ub21icmVzIDwtIHRvcF9wYWlzZXMkcGFpcw0Kbl9tYXBhcyA8LSBucm93KHRvcF9wYWlzZXMpDQoNCiMgY2F0KA0KIyAgIkNhbXBvIHV0aWxpemFkbyBwYXJhIGVsIG5vbWJyZSBkZWwgcGHDrXM6IiwNCiMgIGNhbXBvX25vbWJyZSwNCiMgICJcbiINCiMgKQ0KDQojIGNhdCgNCiMgICJQYcOtc2VzIHNlbGVjY2lvbmFkb3M6IiwNCiMgIHBhc3RlKHBhaXNlc190b3AzX25vbWJyZXMsIGNvbGxhcHNlID0gIiwgIiksDQojICAiXG4iDQojICkNCg0KIyBUcmFkdWNpciBsb3Mgbm9tYnJlcyBwYXJhIGxhIHByZXNlbnRhY2nDs24gZGVsIGluZm9ybWUNCnRyYWR1Y2Npb25fcGFpc2VzIDwtIGMoDQogICJCcmF6aWwiID0gIkJyYXNpbCIsDQogICJEZW1vY3JhdGljIFJlcHVibGljIG9mIHRoZSBDb25nbyIgPSAiUmVww7pibGljYSBEZW1vY3LDoXRpY2EgZGVsIENvbmdvIiwNCiAgIkluZG9uZXNpYSIgPSAiSW5kb25lc2lhIg0KKQ0KDQpwYWlzZXNfcHJlc2VudGFjaW9uIDwtIGlmZWxzZSgNCiAgcGFpc2VzX3RvcDNfbm9tYnJlcyAlaW4lIG5hbWVzKHRyYWR1Y2Npb25fcGFpc2VzKSwNCiAgdHJhZHVjY2lvbl9wYWlzZXNbcGFpc2VzX3RvcDNfbm9tYnJlc10sDQogIHBhaXNlc190b3AzX25vbWJyZXMNCikNCg0KIyBDb25zdHJ1aXIgdW4gcmVjdWFkcm8gSFRNTCBwcm9mZXNpb25hbA0KaHRtbF9wYWlzZXMgPC0gcGFzdGUwKA0KICAnPGRpdiBjbGFzcz0icmVjdWFkcm8tc2VsZWNjaW9uIj4nLA0KICAnPGRpdiBjbGFzcz0icmVjdWFkcm8tdGl0dWxvIj4nLA0KICAnUGHDrXNlcyBzZWxlY2Npb25hZG9zIHBhcmEgZWwgYW7DoWxpc2lzIGRldGFsbGFkbycsDQogICc8L2Rpdj4nLA0KICAnPGRpdiBjbGFzcz0icmVjdWFkcm8tY29udGVuaWRvIj4nLA0KICBwYXN0ZSgNCiAgICBwYXN0ZTAoDQogICAgICAnPHNwYW4gY2xhc3M9ImV0aXF1ZXRhLXBhaXMiPicsDQogICAgICBwYWlzZXNfcHJlc2VudGFjaW9uLA0KICAgICAgJzwvc3Bhbj4nDQogICAgKSwNCiAgICBjb2xsYXBzZSA9ICIgIg0KICApLA0KICAnPC9kaXY+JywNCiAgJzxkaXYgY2xhc3M9InJlY3VhZHJvLW5vdGEiPicsDQogICdTZWxlY2Npw7NuIGJhc2FkYSBlbiBsYSBzdXBlcmZpY2llIGVzdGltYWRhIGNvbiBhcHRpdHVkIGNsaW3DoXRpY2EgYWx0YS4nLA0KICAnPC9kaXY+JywNCiAgJzwvZGl2PicNCikNCg0KaHRtbHRvb2xzOjpIVE1MKGh0bWxfcGFpc2VzKQ0KDQojIDE0LiBUYWJsYSBkZSByZXN1bHRhZG9zDQp0YWJsYV90b3BfcGFpc2VzIDwtIHRvcF9wYWlzZXMgfD4NCiAgZHBseXI6OnRyYW5zbXV0ZSgNCiAgICBQYcOtcyA9IHBhaXMsDQogICAgYMOBcmVhIGNvbiBhcHRpdHVkIGFsdGEgKGttwrIpYCA9IHJvdW5kKA0KICAgICAgYXJlYV9hbHRhX2ttMiwNCiAgICAgIDANCiAgICApLA0KICAgIGBBcHRpdHVkIG1lZGlhYCA9IHJvdW5kKA0KICAgICAgYXB0aXR1ZF9tZWRpYSwNCiAgICAgIDMNCiAgICApLA0KICAgIGBBcHRpdHVkIG3DoXhpbWFgID0gcm91bmQoDQogICAgICBhcHRpdHVkX21heGltYSwNCiAgICAgIDMNCiAgICApDQogICkNCg0KdGFibGFfdG9wX3BhaXNlczwtIG5vcm1hbGl6YXJfdGFibGFfdXRmOCh0YWJsYV90b3BfcGFpc2VzKQ0KdGFibGFfcmVzdWx0YWRvcyA8LSBrbml0cjo6a2FibGUoDQogIHRhYmxhX3RvcF9wYWlzZXMsDQogIGNhcHRpb24gPSBwYXN0ZSgNCiAgICAiUGHDrXNlcyBjb24gbWF5b3Igc3VwZXJmaWNpZSBjbGltw6F0aWNhbWVudGUiLA0KICAgICJhcHRhIHBhcmEgY2HDsWEgZGUgYXrDumNhciINCiAgKSwNCiAgYWxpZ24gPSBjKCJsIiwgInIiLCAiYyIsICJjIiksDQogIGZvcm1hdC5hcmdzID0gbGlzdCgNCiAgICBiaWcubWFyayA9ICIuIiwNCiAgICBkZWNpbWFsLm1hcmsgPSAiLCINCiAgKQ0KKSB8Pg0KICBrYWJsZUV4dHJhOjprYWJsZV9zdHlsaW5nKA0KICAgIGJvb3RzdHJhcF9vcHRpb25zID0gYygNCiAgICAgICJzdHJpcGVkIiwNCiAgICAgICJob3ZlciIsDQogICAgICAiY29uZGVuc2VkIiwNCiAgICAgICJyZXNwb25zaXZlIg0KICAgICksDQogICAgZnVsbF93aWR0aCA9IFRSVUUsDQogICAgcG9zaXRpb24gPSAiY2VudGVyIg0KICApDQoNCiMgcHJpbnQodGFibGFfcmVzdWx0YWRvcykNCnRhYmxhX3Jlc3VsdGFkb3MNCg0KIyAxNS4gR3VhcmRhciBsYSBjb25maWd1cmFjacOzbiBncsOhZmljYQ0KY29uZmlndXJhY2lvbl9hbnRlcmlvciA8LSBncmFwaGljczo6cGFyKA0KICBuby5yZWFkb25seSA9IFRSVUUNCikNCg0KIyAxNi4gRGVmaW5pciBsYSBkaXNwb3NpY2nDs24gZGUgbG9zIG1hcGFzDQpncmFwaGljczo6cGFyKA0KICBtZnJvdyA9IGMobl9tYXBhcywgMSksDQogIG1hciA9IGMoMy41LCAzLjUsIDQuNSwgNS41KSwNCiAgb21hID0gYygwLCAwLCAyLCAwKQ0KKQ0KDQojIDE3LiBDcmVhciBsb3MgbWFwYXMNCmZvciAoaSBpbiBzZXFfbGVuKG5fbWFwYXMpKSB7DQoNCiAgaWRfcGFpcyA8LSBhcy5pbnRlZ2VyKA0KICAgIHRvcF9wYWlzZXMkSURbaV0NCiAgKQ0KDQogIG5vbWJyZV9wYWlzIDwtIGFzLmNoYXJhY3RlcigNCiAgICB0b3BfcGFpc2VzJHBhaXNbaV0NCiAgKQ0KDQogIGlmICgNCiAgICAhaXMuZmluaXRlKGlkX3BhaXMpIHx8DQogICAgaWRfcGFpcyA8IDFMIHx8DQogICAgaWRfcGFpcyA+IHRlcnJhOjpucm93KHBhaXNlcykNCiAgKSB7DQogICAgZ3JhcGhpY3M6OnBsb3QubmV3KCkNCg0KICAgIGdyYXBoaWNzOjp0ZXh0KA0KICAgICAgMC41LA0KICAgICAgMC41LA0KICAgICAgcGFzdGUoDQogICAgICAgICJJZGVudGlmaWNhZG9yIGVzcGFjaWFsIGludsOhbGlkbyBwYXJhIiwNCiAgICAgICAgbm9tYnJlX3BhaXMNCiAgICAgICkNCiAgICApDQoNCiAgfSBlbHNlIHsNCg0KICAgIHBvbGlnb25vX3BhaXMgPC0gcGFpc2VzW2lkX3BhaXMsIF0NCg0KICAgIHJhc3Rlcl9wYWlzIDwtIHRlcnJhOjpjcm9wKA0KICAgICAgYXB0X2NsaW1hdGljYSwNCiAgICAgIHBvbGlnb25vX3BhaXMsDQogICAgICBzbmFwID0gIm91dCINCiAgICApDQoNCiAgICByYXN0ZXJfcGFpcyA8LSB0ZXJyYTo6bWFzaygNCiAgICAgIHJhc3Rlcl9wYWlzLA0KICAgICAgcG9saWdvbm9fcGFpcw0KICAgICkNCg0KICAgIGNhbnRpZGFkX2NlbGRhcyA8LSB0ZXJyYTo6Z2xvYmFsKA0KICAgICAgIWlzLm5hKHJhc3Rlcl9wYWlzKSwNCiAgICAgIGZ1biA9ICJzdW0iLA0KICAgICAgbmEucm0gPSBUUlVFDQogICAgKVsxLCAxXQ0KDQogICAgaWYgKA0KICAgICAgaXMubmEoY2FudGlkYWRfY2VsZGFzKSB8fA0KICAgICAgY2FudGlkYWRfY2VsZGFzID09IDANCiAgICApIHsNCiAgICAgIGdyYXBoaWNzOjpwbG90Lm5ldygpDQoNCiAgICAgIGdyYXBoaWNzOjp0ZXh0KA0KICAgICAgICAwLjUsDQogICAgICAgIDAuNSwNCiAgICAgICAgcGFzdGUoDQogICAgICAgICAgIk5vIGV4aXN0ZW4gZGF0b3MgY2xpbcOhdGljb3MgdsOhbGlkb3MgcGFyYSIsDQogICAgICAgICAgbm9tYnJlX3BhaXMNCiAgICAgICAgKQ0KICAgICAgKQ0KDQogICAgfSBlbHNlIHsNCg0KICAgICAgdGVycmE6OnBsb3QoDQogICAgICAgIHJhc3Rlcl9wYWlzLA0KICAgICAgICBjb2wgPSBoY2wuY29sb3JzKA0KICAgICAgICAgIDEwMCwNCiAgICAgICAgICAiWWxHbiINCiAgICAgICAgKSwNCiAgICAgICAgcmFuZ2UgPSBjKDAsIDEpLA0KICAgICAgICBheGVzID0gVFJVRSwNCiAgICAgICAgbWFpbiA9IHBhc3RlKA0KICAgICAgICAgICJBcHRpdHVkIGNsaW3DoXRpY2EgcGFyYSBjYcOxYSBkZSBhesO6Y2FyOiIsDQogICAgICAgICAgbm9tYnJlX3BhaXMNCiAgICAgICAgKSwNCiAgICAgICAgcGxnID0gbGlzdCgNCiAgICAgICAgICB0aXRsZSA9ICLDjW5kaWNlIGRlIGFwdGl0dWQiLA0KICAgICAgICAgIGNleCA9IDAuOA0KICAgICAgICApDQogICAgICApDQoNCiAgICAgIHRlcnJhOjpsaW5lcygNCiAgICAgICAgcG9saWdvbm9fcGFpcywNCiAgICAgICAgY29sID0gIiMyNjMyMzgiLA0KICAgICAgICBsd2QgPSAxLjENCiAgICAgICkNCiAgICB9DQogIH0NCn0NCg0KIyAxOC4gVMOtdHVsbyBnZW5lcmFsDQpncmFwaGljczo6bXRleHQoDQogIHRleHQgPSAiUGHDrXNlcyBjb24gbWF5b3Igc3VwZXJmaWNpZSBjbGltw6F0aWNhbWVudGUgYXB0YSIsDQogIHNpZGUgPSAzLA0KICBvdXRlciA9IFRSVUUsDQogIGxpbmUgPSAwLjMsDQogIGNleCA9IDEuMTUsDQogIGZvbnQgPSAyLA0KICBjb2wgPSAiIzEyM2Y3MiINCikNCg0KIyAxOS4gUmVzdGF1cmFyIGxhIGNvbmZpZ3VyYWNpw7NuIGdyw6FmaWNhDQpncmFwaGljczo6cGFyKA0KICBjb25maWd1cmFjaW9uX2FudGVyaW9yDQopDQpgYGANCg0KDQoqKkludGVycHJldGFjacOzbi4qKiBMb3MgdHJlcyBwYcOtc2VzIGFudGVyaW9yZXMgbm8gc2UgZmlqYW4gZGUgYW50ZW1hbm86IHNvbiBlbCByZXN1bHRhZG8gZGVsIMOhcmVhIGNsYXNpZmljYWRhIGNvbW8gYWx0YSBiYWpvIGxhIHJlc29sdWNpw7NuLCBwZXJpb2RvIHkgdW1icmFsZXMgc2VsZWNjaW9uYWRvcy4gTGEgcHJlc2VuY2lhIGRlIGFwdGl0dWQgY2xpbcOhdGljYSBubyBpbXBsaWNhIHF1ZSB0b2RhIGVzYSBzdXBlcmZpY2llIGVzdMOpIGRpc3BvbmlibGUgcGFyYSBhZ3JpY3VsdHVyYTsgcHVlZGVuIGV4aXN0aXIgYm9zcXVlcywgw6FyZWFzIHByb3RlZ2lkYXMsIGNpdWRhZGVzLCBwZW5kaWVudGVzIGZ1ZXJ0ZXMgbyBzdWVsb3Mgbm8gYXB0b3MuDQoNCiMgU2l0aW9zIGRlIHJlZmVyZW5jaWEgZW4gZWwgVmFsbGUgZGVsIENhdWNhDQoNClNlIHVzYW4gdHJlcyBwdW50b3MgcmVwcmVzZW50YXRpdm9zIGRlbCBjb3JyZWRvciBnZW9ncsOhZmljbyBkZWwgdmFsbGU6IFBhbG1pcmEsIENhbmRlbGFyaWEgeSBaYXJ6YWwuIFNvbiBwdW50b3MgdXJiYW5vcyBkZSByZWZlcmVuY2lhLCBubyBwYXJjZWxhcyBhZ3LDrWNvbGFzLiBMYXMgdWJpY2FjaW9uZXMgc2UgcmVwcmVzZW50YW4gZGlyZWN0YW1lbnRlIGVuIHVuIG1hcGEgaW50ZXJhY3Rpdm8gZWxhYm9yYWRvIGRlbnRybyBkZWwgaW5mb3JtZS4gRWwgbWFwYSB1dGlsaXphIHRlc2VsYXMgZGUgT3BlblN0cmVldE1hcCwgbm8gcmVxdWllcmUgY2xhdmVzIEFQSSB5IG11ZXN0cmEgbG9zIHRyZXMgc2l0aW9zIGNvbiBldGlxdWV0YXMgcGVybWFuZW50ZXMuDQoNCmBgYHtyIHNpdGlvcy12YWxsZS12MTAsIG91dC53aWR0aD0iMTAwJSJ9DQpzaXRpb3MgPC0gZGF0YS5mcmFtZSgNCiAgc2l0aW8gPSBjKCJQYWxtaXJhIiwgIkNhbmRlbGFyaWEiLCAiWmFyemFsIiksDQogIGxvbmdpdHVkID0gYygtNzYuMzAzNiwgLTc2LjM0ODIsIC03Ni4wNzE1KSwNCiAgbGF0aXR1ZCAgPSBjKCAgMy41Mzk0LCAgIDMuNDA2NywgICA0LjM5NDYpDQopDQoNCnNpdGlvc192IDwtIHZlY3Qoc2l0aW9zLCBnZW9tID0gYygibG9uZ2l0dWQiLCAibGF0aXR1ZCIpLCBjcnMgPSAiRVBTRzo0MzI2IikNCnNpdGlvcyA8LSBub3JtYWxpemFyX3RhYmxhX3V0Zjgoc2l0aW9zKQ0Ka25pdHI6OmthYmxlKHNpdGlvcywgY2FwdGlvbiA9ICJTaXRpb3MgY2xpbcOhdGljb3MgZGUgcmVmZXJlbmNpYSBlbiBlbCBWYWxsZSBkZWwgQ2F1Y2EiKSB8Pg0KICBrYWJsZUV4dHJhOjprYWJsZV9zdHlsaW5nKGJvb3RzdHJhcF9vcHRpb25zID0gYygic3RyaXBlZCIsICJob3ZlciIpLCBmdWxsX3dpZHRoID0gVFJVRSkNCg0KIyBNYXBhIGludGVyYWN0aXZvIHNpbiBkZXBlbmRlbmNpYSBkZSBDQVJUTyBuaSBjbGF2ZXMgQVBJLg0KIyBPcGVuU3RyZWV0TWFwIHNlIGFncmVnYSBtZWRpYW50ZSBzdSBVUkwgb2ZpY2lhbCB5IGF0cmlidWNpw7NuIHZpc2libGUuDQpwYWxldGFfc2l0aW9zIDwtIGxlYWZsZXQ6OmNvbG9yRmFjdG9yKA0KICBwYWxldHRlID0gYygiIzEyM2Y3MiIsICIjYzdhMzRiIiwgIiMyZjg1NWEiKSwNCiAgZG9tYWluID0gc2l0aW9zJHNpdGlvDQopDQoNCm1hcGFfdmFsbGUgPC0gbGVhZmxldDo6bGVhZmxldCgNCiAgZGF0YSA9IHNpdGlvcywNCiAgd2lkdGggPSAiMTAwJSIsDQogIGhlaWdodCA9IDU2MCwNCiAgb3B0aW9ucyA9IGxlYWZsZXQ6OmxlYWZsZXRPcHRpb25zKG1pblpvb20gPSA3LCBtYXhab29tID0gMTgpDQopIHw+DQogIGxlYWZsZXQ6OmFkZFRpbGVzKA0KICAgIHVybFRlbXBsYXRlID0gImh0dHBzOi8vdGlsZS5vcGVuc3RyZWV0bWFwLm9yZy97en0ve3h9L3t5fS5wbmciLA0KICAgIGF0dHJpYnV0aW9uID0gIiZjb3B5OyBPcGVuU3RyZWV0TWFwIGNvbnRyaWJ1dG9ycyIsDQogICAgb3B0aW9ucyA9IGxlYWZsZXQ6OnRpbGVPcHRpb25zKG1heFpvb20gPSAxOSkNCiAgKSB8Pg0KICBsZWFmbGV0OjphZGRDaXJjbGVNYXJrZXJzKA0KICAgIGxuZyA9IH5sb25naXR1ZCwNCiAgICBsYXQgPSB+bGF0aXR1ZCwNCiAgICByYWRpdXMgPSA5LA0KICAgIHN0cm9rZSA9IFRSVUUsDQogICAgd2VpZ2h0ID0gMywNCiAgICBjb2xvciA9ICIjZmZmZmZmIiwNCiAgICBmaWxsQ29sb3IgPSB+cGFsZXRhX3NpdGlvcyhzaXRpbyksDQogICAgZmlsbE9wYWNpdHkgPSAxLA0KICAgIGxhYmVsID0gfnNpdGlvLA0KICAgIGxhYmVsT3B0aW9ucyA9IGxlYWZsZXQ6OmxhYmVsT3B0aW9ucygNCiAgICAgIG5vSGlkZSA9IFRSVUUsDQogICAgICBkaXJlY3Rpb24gPSAidG9wIiwNCiAgICAgIG9mZnNldCA9IGMoMCwgLTEwKSwNCiAgICAgIHRleHRPbmx5ID0gRkFMU0UsDQogICAgICBzdHlsZSA9IGxpc3QoDQogICAgICAgICJmb250LXdlaWdodCIgPSAiYm9sZCIsDQogICAgICAgICJjb2xvciIgPSAiIzEyM2Y3MiIsDQogICAgICAgICJiYWNrZ3JvdW5kLWNvbG9yIiA9ICJ3aGl0ZSIsDQogICAgICAgICJib3JkZXIiID0gIjFweCBzb2xpZCAjMTIzZjcyIiwNCiAgICAgICAgInBhZGRpbmciID0gIjNweCA2cHgiDQogICAgICApDQogICAgKSwNCiAgICBwb3B1cCA9IH5wYXN0ZTAoDQogICAgICAiPHN0cm9uZz4iLCBzaXRpbywgIjwvc3Ryb25nPjxicj4iLA0KICAgICAgIkxhdGl0dWQ6ICIsIHNwcmludGYoIiUuNGYiLCBsYXRpdHVkKSwgIjxicj4iLA0KICAgICAgIkxvbmdpdHVkOiAiLCBzcHJpbnRmKCIlLjRmIiwgbG9uZ2l0dWQpDQogICAgKQ0KICApIHw+DQogIGxlYWZsZXQ6OmZpdEJvdW5kcygNCiAgICBsbmcxID0gbWluKHNpdGlvcyRsb25naXR1ZCkgLSAwLjEyLA0KICAgIGxhdDEgPSBtaW4oc2l0aW9zJGxhdGl0dWQpIC0gMC4xMiwNCiAgICBsbmcyID0gbWF4KHNpdGlvcyRsb25naXR1ZCkgKyAwLjEyLA0KICAgIGxhdDIgPSBtYXgoc2l0aW9zJGxhdGl0dWQpICsgMC4xMg0KICApIHw+DQogIGxlYWZsZXQ6OmFkZFNjYWxlQmFyKHBvc2l0aW9uID0gImJvdHRvbWxlZnQiKQ0KDQptYXBhX3ZhbGxlDQpgYGANCg0KIyMgRXh0cmFjY2nDs24gY2xpbcOhdGljYSB5IHNlcmllcyBtZW5zdWFsZXMNCg0KV29ybGRDbGltIGVzIHVuYSAqKmNsaW1hdG9sb2fDrWEgbWVuc3VhbCAxOTcw4oCTMjAwMCoqLiBQb3IgdGFudG8sIGxhcyBncsOhZmljYXMgbXVlc3RyYW4gZWwgY2ljbG8gcHJvbWVkaW8gZW5lcm/igJNkaWNpZW1icmUsIG5vIHVuYSBzZXJpZSBpbnRlcmFudWFsIGRlIG9ic2VydmFjaW9uZXMuDQoNCmBgYHtyIGV4dHJhZXItc2VyaWVzfQ0KY2xpbWFfbWVuc3VhbCA8LSBjKHRtZWRfbSwgcHJlY19tKQ0KZXh0X3NpdGlvcyA8LSB0ZXJyYTo6ZXh0cmFjdChjbGltYV9tZW5zdWFsLCBzaXRpb3NfdikNCg0Kc2VyaWVfdGVtcCA8LSBleHRfc2l0aW9zWywgMjoxM10gfD4NCiAgZHBseXI6Om11dGF0ZShzaXRpbyA9IHNpdGlvcyRzaXRpbykgfD4NCiAgdGlkeXI6OnBpdm90X2xvbmdlcigtc2l0aW8sIG5hbWVzX3RvID0gInZhcmlhYmxlIiwgdmFsdWVzX3RvID0gInRlbXBlcmF0dXJhX0MiKSB8Pg0KICBkcGx5cjo6bXV0YXRlKG1lcyA9IHJlcCgxOjEyLCB0aW1lcyA9IG5yb3coc2l0aW9zKSkpDQoNCnNlcmllX3ByZWMgPC0gZXh0X3NpdGlvc1ssIDE0OjI1XSB8Pg0KICBkcGx5cjo6bXV0YXRlKHNpdGlvID0gc2l0aW9zJHNpdGlvKSB8Pg0KICB0aWR5cjo6cGl2b3RfbG9uZ2VyKC1zaXRpbywgbmFtZXNfdG8gPSAidmFyaWFibGUiLCB2YWx1ZXNfdG8gPSAicHJlY2lwaXRhY2lvbl9tbSIpIHw+DQogIGRwbHlyOjptdXRhdGUobWVzID0gcmVwKDE6MTIsIHRpbWVzID0gbnJvdyhzaXRpb3MpKSkNCg0Kc2VyaWVfc2l0aW9zIDwtIGRwbHlyOjpsZWZ0X2pvaW4oDQogIGRwbHlyOjpzZWxlY3Qoc2VyaWVfdGVtcCwgc2l0aW8sIG1lcywgdGVtcGVyYXR1cmFfQyksDQogIGRwbHlyOjpzZWxlY3Qoc2VyaWVfcHJlYywgc2l0aW8sIG1lcywgcHJlY2lwaXRhY2lvbl9tbSksDQogIGJ5ID0gYygic2l0aW8iLCAibWVzIikNCikgfD4NCiAgZHBseXI6Om11dGF0ZShtZXNfbm9tYnJlID0gZmFjdG9yKG1vbnRoLmFiYlttZXNdLCBsZXZlbHMgPSBtb250aC5hYmIpKQ0KDQpzZXJpZV9zaXRpb3MgPC0gbm9ybWFsaXphcl90YWJsYV91dGY4KHNlcmllX3NpdGlvcykNCmtuaXRyOjprYWJsZShzZXJpZV9zaXRpb3MsIGRpZ2l0cyA9IDEsDQogICAgICAgICAgICAgY2FwdGlvbiA9ICJDbGltYXRvbG9nw61hIG1lbnN1YWwgZXh0cmHDrWRhIGVuIGxvcyBzaXRpb3MiKSB8Pg0KICBrYWJsZUV4dHJhOjprYWJsZV9zdHlsaW5nKGJvb3RzdHJhcF9vcHRpb25zID0gYygic3RyaXBlZCIsICJob3ZlciIsICJjb25kZW5zZWQiKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBmdWxsX3dpZHRoID0gVFJVRSkNCmBgYA0KDQpgYGB7ciBzZXJpZXMtdGllbXBvfQ0KZ190ZW1wIDwtIGdncGxvdChzZXJpZV9zaXRpb3MsIGFlcyhtZXNfbm9tYnJlLCB0ZW1wZXJhdHVyYV9DLCBjb2xvciA9IHNpdGlvLCBncm91cCA9IHNpdGlvKSkgKw0KICBnZW9tX2xpbmUobGluZXdpZHRoID0gMC45KSArIGdlb21fcG9pbnQoc2l6ZSA9IDEuOCkgKw0KICBzY2FsZV9jb2xvcl92aXJpZGlzX2Qob3B0aW9uID0gIkMiLCBlbmQgPSAwLjg1KSArDQogIGxhYnModGl0bGUgPSAiQ2ljbG8gY2xpbWF0b2zDs2dpY28gbWVuc3VhbCBkZSB0ZW1wZXJhdHVyYSIsDQogICAgICAgeCA9ICJNZXMiLCB5ID0gIlRlbXBlcmF0dXJhIG1lZGlhICjCsEMpIiwgY29sb3IgPSAiU2l0aW8iKSArDQogIHRoZW1lX21pbmltYWwoYmFzZV9zaXplID0gMTEpICsgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIpDQoNCmdfcHJlYyA8LSBnZ3Bsb3Qoc2VyaWVfc2l0aW9zLCBhZXMobWVzX25vbWJyZSwgcHJlY2lwaXRhY2lvbl9tbSwgY29sb3IgPSBzaXRpbywgZ3JvdXAgPSBzaXRpbykpICsNCiAgZ2VvbV9saW5lKGxpbmV3aWR0aCA9IDAuOSkgKyBnZW9tX3BvaW50KHNpemUgPSAxLjgpICsNCiAgc2NhbGVfY29sb3JfdmlyaWRpc19kKG9wdGlvbiA9ICJEIiwgZW5kID0gMC44NSkgKw0KICBsYWJzKHRpdGxlID0gIkNpY2xvIGNsaW1hdG9sw7NnaWNvIG1lbnN1YWwgZGUgcHJlY2lwaXRhY2nDs24iLA0KICAgICAgIHggPSAiTWVzIiwgeSA9ICJQcmVjaXBpdGFjacOzbiAobW0vbWVzKSIsIGNvbG9yID0gIlNpdGlvIikgKw0KICB0aGVtZV9taW5pbWFsKGJhc2Vfc2l6ZSA9IDExKSArIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iKQ0KDQpnX3RlbXAgLyBnX3ByZWMNCmBgYA0KDQojIFNpbWlsYXJpZGFkIGNsaW3DoXRpY2EgZ2xvYmFsDQoNCkxhIGRpc3RhbmNpYSBldWNsaWRpYW5hIGRpcmVjdGEgZW50cmUgdmFyaWFibGVzIGNvbiB1bmlkYWRlcyBkaWZlcmVudGVzIGVzdGFyw61hIGRvbWluYWRhIHBvciBsYSBwcmVjaXBpdGFjacOzbi4gUGFyYSBldml0YXJsbywgbGFzIDI0IHZhcmlhYmxlcyBtZW5zdWFsZXMgc2UgZXN0YW5kYXJpemFuIGNvbiBtZWRpYSAwIHkgZGVzdmlhY2nDs24gZXN0w6FuZGFyIDEuIFBhcmEgY2FkYSBzaXRpbyBzZSBjYWxjdWxhOg0KDQpcWw0KZF9pID0gXHNxcnR7XHN1bV97aj0xfV57MjR9KHpfe2lqfS16X3tzan0pXjJ9DQpcXQ0KDQpMdWVnbyBzZSB0cmFuc2Zvcm1hIGxhIGRpc3RhbmNpYSBlbiB1bmEgc2ltaWxhcmlkYWQgYWNvdGFkYSBlbnRyZSAwIHkgMToNCg0KXFsNClNfaSA9IFxleHAoLWRfaSkNClxdDQoNClZhbG9yZXMgY2VyY2Fub3MgYSAxIHJlcHJlc2VudGFuIG1heW9yIGFuYWxvZ8OtYSBjbGltw6F0aWNhIGNvbiBlbCBzaXRpbyBkZWwgVmFsbGUgZGVsIENhdWNhLg0KDQpgYGB7ciBzaW1pbGFyaWRhZC1nbG9iYWx9DQpjbGltYV8yNCA8LSBjKHRtZWRfbSwgcHJlY19tKQ0KDQojIEVzdGFkw61zdGljb3MgZ2xvYmFsZXMgcG9yIGNhcGE7IHNlIGFwbGljYW4gbG9zIG1pc21vcyBwYXLDoW1ldHJvcyBhIHJhc3RlciB5IHNpdGlvcy4NCm1lZGlhcyA8LSBhcy5udW1lcmljKGdsb2JhbChjbGltYV8yNCwgIm1lYW4iLCBuYS5ybSA9IFRSVUUpWywgMV0pDQpkZXN2IDwtIGFzLm51bWVyaWMoZ2xvYmFsKGNsaW1hXzI0LCAic2QiLCBuYS5ybSA9IFRSVUUpWywgMV0pDQppZiAoYW55KCFpcy5maW5pdGUoZGVzdikgfCBkZXN2ID09IDApKSBzdG9wKCJIYXkgY2FwYXMgc2luIHZhcmlhYmlsaWRhZCB2w6FsaWRhLiIpDQoNCmNsaW1hX3ogPC0gKGNsaW1hXzI0IC0gbWVkaWFzKSAvIGRlc3YNCnNpdGlvc18yNCA8LSBhcy5tYXRyaXgoZXh0X3NpdGlvc1ssIDI6MjVdKQ0Kc2l0aW9zX3ogPC0gc3dlZXAoc3dlZXAoc2l0aW9zXzI0LCAyLCBtZWRpYXMsICItIiksIDIsIGRlc3YsICIvIikNCg0KbWFwYXNfc2ltIDwtIGxpc3QoKQ0KZm9yIChpIGluIHNlcV9sZW4obnJvdyhzaXRpb3MpKSkgew0KICBkaWYyIDwtIChjbGltYV96IC0gc2l0aW9zX3pbaSwgXSleMg0KICBkaXN0YW5jaWEgPC0gc3FydChzdW0oZGlmMiwgbmEucm0gPSBGQUxTRSkpDQogIHNpbWlsaXR1ZCA8LSBleHAoLWRpc3RhbmNpYSkNCiAgbmFtZXMoc2ltaWxpdHVkKSA8LSBwYXN0ZTAoInNpbV8iLCBzaXRpb3Mkc2l0aW9baV0pDQogIG1hcGFzX3NpbVtbaV1dIDwtIHNpbWlsaXR1ZA0KfQ0Kc2ltX2dsb2JhbCA8LSByYXN0KG1hcGFzX3NpbSkNCndyaXRlUmFzdGVyKHNpbV9nbG9iYWwsIGZpbGUucGF0aChydXRhX3Jlc3VsdGFkb3MsICJzaW1pbGFyaWRhZF9zaXRpb3NfdmFsbGUudGlmIiksIG92ZXJ3cml0ZSA9IFRSVUUpDQpgYGANCg0KYGBge3IgbWFwYXMtc2ltaWxhcmlkYWQsIGZpZy5oZWlnaHQ9MTB9DQpwYXIobWZyb3cgPSBjKDMsIDEpLCBtYXIgPSBjKDIsIDMsIDMsIDUpKQ0KZm9yIChpIGluIDE6Mykgew0KICBwbG90KHNpbV9nbG9iYWxbW2ldXSwgY29sID0gaGNsLmNvbG9ycygxMDAsICJJbmZlcm5vIiwgcmV2ID0gVFJVRSksDQogICAgICAgcmFuZ2UgPSBjKDAsIDEpLCBtYWluID0gcGFzdGUoIlNpbWlsYXJpZGFkIGNsaW3DoXRpY2EgY29uIiwgc2l0aW9zJHNpdGlvW2ldKSwNCiAgICAgICBheGVzID0gVFJVRSkNCiAgbGluZXMocGFpc2VzLCBjb2wgPSAiZ3JleTcwIiwgbHdkID0gMC4yKQ0KfQ0KcGFyKG1mcm93ID0gYygxLCAxKSkNCmBgYA0KDQojIyBDb25zZW5zbyBlbnRyZSBzaXRpb3MNCg0KRWwgcHJvbWVkaW8gcmVzdW1lIGxhIGFuYWxvZ8OtYSBnZW5lcmFsIGNvbiBsb3MgdHJlcyBzaXRpb3M7IGVsIG3DrW5pbW8gZXMgbcOhcyBleGlnZW50ZSB5IGNvbnNlcnZhIMO6bmljYW1lbnRlIHpvbmFzIHNlbWVqYW50ZXMgYSB0b2RvcyBlbGxvcy4NCg0KYGBge3IgY29uc2Vuc28tc2ltaWxpdHVkfQ0Kc2ltX3Byb21lZGlvIDwtIG1lYW4oc2ltX2dsb2JhbCwgbmEucm0gPSBUUlVFKQ0Kc2ltX2NvbnNlcnZhZG9yYSA8LSB0ZXJyYTo6aWZlbCgNCiAgc2ltX2dsb2JhbFtbMV1dIDw9IHNpbV9nbG9iYWxbWzJdXSAmIHNpbV9nbG9iYWxbWzFdXSA8PSBzaW1fZ2xvYmFsW1szXV0sIHNpbV9nbG9iYWxbWzFdXSwNCiAgdGVycmE6OmlmZWwoc2ltX2dsb2JhbFtbMl1dIDw9IHNpbV9nbG9iYWxbWzNdXSwgc2ltX2dsb2JhbFtbMl1dLCBzaW1fZ2xvYmFsW1szXV0pDQopDQpuYW1lcyhzaW1fcHJvbWVkaW8pIDwtICJzaW1pbGl0dWRfcHJvbWVkaW8iDQpuYW1lcyhzaW1fY29uc2VydmFkb3JhKSA8LSAic2ltaWxpdHVkX2NvbnNlcnZhZG9yYSINCg0KcGFyKG1mcm93ID0gYygyLCAxKSwgbWFyID0gYygyLCAzLCAzLCA1KSkNCnBsb3Qoc2ltX3Byb21lZGlvLCBjb2wgPSBoY2wuY29sb3JzKDEwMCwgIkluZmVybm8iLCByZXYgPSBUUlVFKSwgcmFuZ2UgPSBjKDAsIDEpLA0KICAgICBtYWluID0gIlNpbWlsYXJpZGFkIHByb21lZGlvIGNvbiBsb3Mgc2l0aW9zIGRlbCBWYWxsZSBkZWwgQ2F1Y2EiKQ0KbGluZXMocGFpc2VzLCBjb2wgPSAiZ3JleTcwIiwgbHdkID0gMC4yKQ0KcGxvdChzaW1fY29uc2VydmFkb3JhLCBjb2wgPSBoY2wuY29sb3JzKDEwMCwgIkluZmVybm8iLCByZXYgPSBUUlVFKSwgcmFuZ2UgPSBjKDAsIDEpLA0KICAgICBtYWluID0gIlNpbWlsYXJpZGFkIGNvbnNlcnZhZG9yYTogbcOtbmltbyBlbnRyZSBsb3MgdHJlcyBzaXRpb3MiKQ0KbGluZXMocGFpc2VzLCBjb2wgPSAiZ3JleTcwIiwgbHdkID0gMC4yKQ0KcGFyKG1mcm93ID0gYygxLCAxKSkNCmBgYA0KDQoNCmBgYHtyIHNpbWlsYXJpZGFkLWF1dG9ub21hLWZpbmFsLCBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGZpZy53aWR0aD0xMiwgZmlnLmhlaWdodD0xMCwgZmlnLmFsaWduPSJjZW50ZXIifQ0KDQoNCiMgPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0NCiMgU0lNSUxBUklEQUQgQ0xJTcOBVElDQSBHTE9CQUwNCiMgQ0hVTksgQVVUw5NOT01POiBOTyBERVBFTkRFIERFIE9CSkVUT1MgUFJFVklPUyBFTiBNRU1PUklBDQojID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09DQoNCmNvbnN0cnVpcl9zaW1pbGFyaWRhZF9jbGltYXRpY2EgPC0gZnVuY3Rpb24oKSB7DQoNCiAgIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCiAgIyAxLiBWZXJpZmljYXIgcGFxdWV0ZXMNCiAgIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KICBwYXF1ZXRlc19uZWNlc2FyaW9zIDwtIGMoDQogICAgInRlcnJhIiwNCiAgICAiZ2VvZGF0YSIsDQogICAgImtuaXRyIiwNCiAgICAia2FibGVFeHRyYSINCiAgKQ0KDQogIHBhcXVldGVzX2ZhbHRhbnRlcyA8LSBwYXF1ZXRlc19uZWNlc2FyaW9zWw0KICAgICF2YXBwbHkoDQogICAgICBwYXF1ZXRlc19uZWNlc2FyaW9zLA0KICAgICAgcmVxdWlyZU5hbWVzcGFjZSwNCiAgICAgIGxvZ2ljYWwoMSksDQogICAgICBxdWlldGx5ID0gVFJVRQ0KICAgICkNCiAgXQ0KDQogIGlmIChsZW5ndGgocGFxdWV0ZXNfZmFsdGFudGVzKSA+IDBMKSB7DQogICAgc3RvcCgNCiAgICAgIHBhc3RlMCgNCiAgICAgICAgIkZhbHRhbiBsb3MgcGFxdWV0ZXM6ICIsDQogICAgICAgIHBhc3RlKHBhcXVldGVzX2ZhbHRhbnRlcywgY29sbGFwc2UgPSAiLCAiKSwNCiAgICAgICAgIi4gSW5zdMOhbGVsb3MgYW50ZXMgZGUgcmVuZGVyaXphci4iDQogICAgICApLA0KICAgICAgY2FsbC4gPSBGQUxTRQ0KICAgICkNCiAgfQ0KDQogIHRlcnJhOjp0ZXJyYU9wdGlvbnMocHJvZ3Jlc3MgPSAwKQ0KDQogICMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQogICMgMi4gQ3JlYXIgbGFzIGNhcnBldGFzIG5lY2VzYXJpYXMNCiAgIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KICBydXRhX2RhdG9zX2xvY2FsIDwtIGZpbGUucGF0aCgNCiAgICBnZXR3ZCgpLA0KICAgICJkYXRvc19jbGltYSINCiAgKQ0KDQogIHJ1dGFfcmVzdWx0YWRvc19sb2NhbCA8LSBmaWxlLnBhdGgoDQogICAgZ2V0d2QoKSwNCiAgICAicmVzdWx0YWRvc19pbmZvcm1lMiINCiAgKQ0KDQogIGRpci5jcmVhdGUoDQogICAgcnV0YV9kYXRvc19sb2NhbCwNCiAgICByZWN1cnNpdmUgPSBUUlVFLA0KICAgIHNob3dXYXJuaW5ncyA9IEZBTFNFDQogICkNCg0KICBkaXIuY3JlYXRlKA0KICAgIHJ1dGFfcmVzdWx0YWRvc19sb2NhbCwNCiAgICByZWN1cnNpdmUgPSBUUlVFLA0KICAgIHNob3dXYXJuaW5ncyA9IEZBTFNFDQogICkNCg0KICAjIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KICAjIDMuIENhcmdhciBvIGRlc2NhcmdhciBXb3JsZENsaW0NCiAgIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KICB0bWVkX2xvY2FsIDwtIGdlb2RhdGE6OndvcmxkY2xpbV9nbG9iYWwoDQogICAgdmFyID0gInRhdmciLA0KICAgIHJlcyA9IDEwLA0KICAgIHBhdGggPSBydXRhX2RhdG9zX2xvY2FsDQogICkNCg0KICBwcmVjX2xvY2FsIDwtIGdlb2RhdGE6OndvcmxkY2xpbV9nbG9iYWwoDQogICAgdmFyID0gInByZWMiLA0KICAgIHJlcyA9IDEwLA0KICAgIHBhdGggPSBydXRhX2RhdG9zX2xvY2FsDQogICkNCg0KICBpZiAoIWluaGVyaXRzKHRtZWRfbG9jYWwsICJTcGF0UmFzdGVyIikpIHsNCiAgICBzdG9wKA0KICAgICAgIk5vIGZ1ZSBwb3NpYmxlIGNhcmdhciBsYSB0ZW1wZXJhdHVyYSBtZW5zdWFsLiIsDQogICAgICBjYWxsLiA9IEZBTFNFDQogICAgKQ0KICB9DQoNCiAgaWYgKCFpbmhlcml0cyhwcmVjX2xvY2FsLCAiU3BhdFJhc3RlciIpKSB7DQogICAgc3RvcCgNCiAgICAgICJObyBmdWUgcG9zaWJsZSBjYXJnYXIgbGEgcHJlY2lwaXRhY2nDs24gbWVuc3VhbC4iLA0KICAgICAgY2FsbC4gPSBGQUxTRQ0KICAgICkNCiAgfQ0KDQogIGlmICh0ZXJyYTo6bmx5cih0bWVkX2xvY2FsKSAhPSAxMkwpIHsNCiAgICBzdG9wKA0KICAgICAgcGFzdGUwKA0KICAgICAgICAiTGEgdGVtcGVyYXR1cmEgZGViZSBjb250ZW5lciAxMiBjYXBhcywgcGVybyBjb250aWVuZSAiLA0KICAgICAgICB0ZXJyYTo6bmx5cih0bWVkX2xvY2FsKSwNCiAgICAgICAgIi4iDQogICAgICApLA0KICAgICAgY2FsbC4gPSBGQUxTRQ0KICAgICkNCiAgfQ0KDQogIGlmICh0ZXJyYTo6bmx5cihwcmVjX2xvY2FsKSAhPSAxMkwpIHsNCiAgICBzdG9wKA0KICAgICAgcGFzdGUwKA0KICAgICAgICAiTGEgcHJlY2lwaXRhY2nDs24gZGViZSBjb250ZW5lciAxMiBjYXBhcywgcGVybyBjb250aWVuZSAiLA0KICAgICAgICB0ZXJyYTo6bmx5cihwcmVjX2xvY2FsKSwNCiAgICAgICAgIi4iDQogICAgICApLA0KICAgICAgY2FsbC4gPSBGQUxTRQ0KICAgICkNCiAgfQ0KDQogIGlmICgNCiAgICAhaXNUUlVFKA0KICAgICAgdGVycmE6OmNvbXBhcmVHZW9tKA0KICAgICAgICB0bWVkX2xvY2FsLA0KICAgICAgICBwcmVjX2xvY2FsLA0KICAgICAgICBzdG9wT25FcnJvciA9IEZBTFNFDQogICAgICApDQogICAgKQ0KICApIHsNCiAgICBzdG9wKA0KICAgICAgcGFzdGUoDQogICAgICAgICJMYXMgY2FwYXMgZGUgdGVtcGVyYXR1cmEgeSBwcmVjaXBpdGFjacOzbiIsDQogICAgICAgICJubyBzb24gZXNwYWNpYWxtZW50ZSBjb21wYXRpYmxlcy4iDQogICAgICApLA0KICAgICAgY2FsbC4gPSBGQUxTRQ0KICAgICkNCiAgfQ0KDQogIG5hbWVzKHRtZWRfbG9jYWwpIDwtIHBhc3RlMCgNCiAgICAidG1lZF8iLA0KICAgIHNwcmludGYoIiUwMmQiLCAxOjEyKQ0KICApDQoNCiAgbmFtZXMocHJlY19sb2NhbCkgPC0gcGFzdGUwKA0KICAgICJwcmVjXyIsDQogICAgc3ByaW50ZigiJTAyZCIsIDE6MTIpDQogICkNCg0KICAjIFdvcmxkQ2xpbSBwdWVkZSBhbG1hY2VuYXIgdGVtcGVyYXR1cmEgY29uIGZhY3RvciAxMC4NCiAgdGVtcGVyYXR1cmFfcHJ1ZWJhIDwtIHRlcnJhOjpnbG9iYWwoDQogICAgdG1lZF9sb2NhbFtbMV1dLA0KICAgIGZ1biA9ICJtZWFuIiwNCiAgICBuYS5ybSA9IFRSVUUNCiAgKVsxLCAxXQ0KDQogIGlmICgNCiAgICBpcy5maW5pdGUodGVtcGVyYXR1cmFfcHJ1ZWJhKSAmJg0KICAgIHRlbXBlcmF0dXJhX3BydWViYSA+IDYwDQogICkgew0KICAgIHRtZWRfbG9jYWwgPC0gdG1lZF9sb2NhbCAvIDEwDQogIH0NCg0KICAjIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KICAjIDQuIENvbnN0cnVpciBsYXMgMjQgY2FwYXMgY2xpbcOhdGljYXMNCiAgIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KICBjbGltYV9sb2NhbCA8LSBjKA0KICAgIHRtZWRfbG9jYWwsDQogICAgcHJlY19sb2NhbA0KICApDQoNCiAgaWYgKHRlcnJhOjpubHlyKGNsaW1hX2xvY2FsKSAhPSAyNEwpIHsNCiAgICBzdG9wKA0KICAgICAgIk5vIGZ1ZSBwb3NpYmxlIGNvbnN0cnVpciBsYXMgMjQgY2FwYXMgY2xpbcOhdGljYXMuIiwNCiAgICAgIGNhbGwuID0gRkFMU0UNCiAgICApDQogIH0NCg0KICAjIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KICAjIDUuIENyZWFyIGF1dMOzbm9tYW1lbnRlIGxvcyBzaXRpb3MgZGVsIFZhbGxlIGRlbCBDYXVjYQ0KICAjIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQogIHNpdGlvc19sb2NhbCA8LSBkYXRhLmZyYW1lKA0KICAgIHNpdGlvID0gYygNCiAgICAgICJQYWxtaXJhIiwNCiAgICAgICJDYW5kZWxhcmlhIiwNCiAgICAgICJaYXJ6YWwiDQogICAgKSwNCiAgICBsb25naXR1ZCA9IGMoDQogICAgICAtNzYuMzAzNiwNCiAgICAgIC03Ni4zNDgyLA0KICAgICAgLTc2LjA3MTUNCiAgICApLA0KICAgIGxhdGl0dWQgPSBjKA0KICAgICAgMy41Mzk0LA0KICAgICAgMy40MDY3LA0KICAgICAgNC4zOTQ2DQogICAgKSwNCiAgICBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UNCiAgKQ0KDQogIHNpdGlvc192ZWN0b3IgPC0gdGVycmE6OnZlY3QoDQogICAgc2l0aW9zX2xvY2FsLA0KICAgIGdlb20gPSBjKA0KICAgICAgImxvbmdpdHVkIiwNCiAgICAgICJsYXRpdHVkIg0KICAgICksDQogICAgY3JzID0gIkVQU0c6NDMyNiINCiAgKQ0KDQogICMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQogICMgNi4gRXh0cmFlciBlbCBjbGltYSBkZSBsb3Mgc2l0aW9zDQogICMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiAgZXh0cmFjY2lvbl9sb2NhbCA8LSB0ZXJyYTo6ZXh0cmFjdCgNCiAgICBjbGltYV9sb2NhbCwNCiAgICBzaXRpb3NfdmVjdG9yDQogICkNCg0KICBpZiAoDQogICAgIWlzLmRhdGEuZnJhbWUoZXh0cmFjY2lvbl9sb2NhbCkgfHwNCiAgICBucm93KGV4dHJhY2Npb25fbG9jYWwpICE9IG5yb3coc2l0aW9zX2xvY2FsKSB8fA0KICAgIG5jb2woZXh0cmFjY2lvbl9sb2NhbCkgIT0gMjVMDQogICkgew0KICAgIHN0b3AoDQogICAgICBwYXN0ZTAoDQogICAgICAgICJMYSBleHRyYWNjacOzbiBjbGltw6F0aWNhIG5vIHRpZW5lIGxhIGVzdHJ1Y3R1cmEgZXNwZXJhZGEuICIsDQogICAgICAgICJEaW1lbnNpb25lcyBvYnRlbmlkYXM6ICIsDQogICAgICAgIG5yb3coZXh0cmFjY2lvbl9sb2NhbCksDQogICAgICAgICIgw5cgIiwNCiAgICAgICAgbmNvbChleHRyYWNjaW9uX2xvY2FsKSwNCiAgICAgICAgIi4iDQogICAgICApLA0KICAgICAgY2FsbC4gPSBGQUxTRQ0KICAgICkNCiAgfQ0KDQogIHNpdGlvc19jbGltYSA8LSBhcy5tYXRyaXgoDQogICAgZXh0cmFjY2lvbl9sb2NhbFsNCiAgICAgICwNCiAgICAgIDI6MjUsDQogICAgICBkcm9wID0gRkFMU0UNCiAgICBdDQogICkNCg0KICBzdG9yYWdlLm1vZGUoc2l0aW9zX2NsaW1hKSA8LSAibnVtZXJpYyINCg0KICBpZiAoYW55KCFpcy5maW5pdGUoc2l0aW9zX2NsaW1hKSkpIHsNCiAgICBzdG9wKA0KICAgICAgIkxhIGV4dHJhY2Npw7NuIGNsaW3DoXRpY2EgZGUgbG9zIHNpdGlvcyBjb250aWVuZSB2YWxvcmVzIG5vIHbDoWxpZG9zLiIsDQogICAgICBjYWxsLiA9IEZBTFNFDQogICAgKQ0KICB9DQoNCiAgIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCiAgIyA3LiBDYWxjdWxhciBlc3RhZMOtc3RpY2FzIGdsb2JhbGVzIHBvciBjYXBhDQogICMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiAgbWVkaWFzX3RhYmxhIDwtIHRlcnJhOjpnbG9iYWwoDQogICAgY2xpbWFfbG9jYWwsDQogICAgZnVuID0gIm1lYW4iLA0KICAgIG5hLnJtID0gVFJVRQ0KICApDQoNCiAgZGVzdmlhY2lvbmVzX3RhYmxhIDwtIHRlcnJhOjpnbG9iYWwoDQogICAgY2xpbWFfbG9jYWwsDQogICAgZnVuID0gInNkIiwNCiAgICBuYS5ybSA9IFRSVUUNCiAgKQ0KDQogIG1lZGlhc19sb2NhbGVzIDwtIGFzLm51bWVyaWMoDQogICAgbWVkaWFzX3RhYmxhWywgMV0NCiAgKQ0KDQogIGRlc3ZpYWNpb25lc19sb2NhbGVzIDwtIGFzLm51bWVyaWMoDQogICAgZGVzdmlhY2lvbmVzX3RhYmxhWywgMV0NCiAgKQ0KDQogIGlmICgNCiAgICBsZW5ndGgobWVkaWFzX2xvY2FsZXMpICE9IDI0TCB8fA0KICAgIGxlbmd0aChkZXN2aWFjaW9uZXNfbG9jYWxlcykgIT0gMjRMDQogICkgew0KICAgIHN0b3AoDQogICAgICAiTm8gc2Ugb2J0dXZpZXJvbiAyNCBtZWRpYXMgeSAyNCBkZXN2aWFjaW9uZXMgZXN0w6FuZGFyLiIsDQogICAgICBjYWxsLiA9IEZBTFNFDQogICAgKQ0KICB9DQoNCiAgaWYgKGFueSghaXMuZmluaXRlKG1lZGlhc19sb2NhbGVzKSkpIHsNCiAgICBzdG9wKA0KICAgICAgIkV4aXN0ZW4gbWVkaWFzIGNsaW3DoXRpY2FzIG5vIHbDoWxpZGFzLiIsDQogICAgICBjYWxsLiA9IEZBTFNFDQogICAgKQ0KICB9DQoNCiAgaWYgKA0KICAgIGFueSgNCiAgICAgICFpcy5maW5pdGUoZGVzdmlhY2lvbmVzX2xvY2FsZXMpIHwNCiAgICAgIGRlc3ZpYWNpb25lc19sb2NhbGVzIDw9IDANCiAgICApDQogICkgew0KICAgIHN0b3AoDQogICAgICAiRXhpc3RlbiBjYXBhcyBjbGltw6F0aWNhcyBzaW4gdmFyaWFiaWxpZGFkIHbDoWxpZGEuIiwNCiAgICAgIGNhbGwuID0gRkFMU0UNCiAgICApDQogIH0NCg0KICAjIGB0ZXJyYTo6Z2xvYmFsKClgIGRldnVlbHZlIHVuYSBmaWxhIHBvciBjYXBhLg0KICAjIExhcyBmdW5jaW9uZXMgaW5jb3Jwb3JhZGFzIG1lYW4geSBzZCBzb24gYWRlY3VhZGFzDQogICMgcGFyYSByw6FzdGVyZXMgZ2xvYmFsZXMgZ3JhbmRlcy4gDQoNCiAgIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCiAgIyA4LiBFc3RhbmRhcml6YXIgbGFzIGNhcGFzIGNsaW3DoXRpY2FzDQogICMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiAgY2FwYXNfZXN0YW5kYXJpemFkYXMgPC0gdmVjdG9yKA0KICAgIG1vZGUgPSAibGlzdCIsDQogICAgbGVuZ3RoID0gMjRMDQogICkNCg0KICBmb3IgKGogaW4gc2VxX2xlbigyNEwpKSB7DQoNCiAgICBjYXBhc19lc3RhbmRhcml6YWRhc1tbal1dIDwtICgNCiAgICAgIGNsaW1hX2xvY2FsW1tqXV0gLSBtZWRpYXNfbG9jYWxlc1tqXQ0KICAgICkgLyBkZXN2aWFjaW9uZXNfbG9jYWxlc1tqXQ0KICB9DQoNCiAgY2xpbWFfZXN0YW5kYXJpemFkbyA8LSB0ZXJyYTo6cmFzdCgNCiAgICBjYXBhc19lc3RhbmRhcml6YWRhcw0KICApDQoNCiAgbmFtZXMoY2xpbWFfZXN0YW5kYXJpemFkbykgPC0gcGFzdGUwKA0KICAgICJ6XyIsDQogICAgbmFtZXMoY2xpbWFfbG9jYWwpDQogICkNCg0KICBpZiAodGVycmE6Om5seXIoY2xpbWFfZXN0YW5kYXJpemFkbykgIT0gMjRMKSB7DQogICAgc3RvcCgNCiAgICAgICJObyBmdWUgcG9zaWJsZSBlc3RhbmRhcml6YXIgbGFzIDI0IGNhcGFzLiIsDQogICAgICBjYWxsLiA9IEZBTFNFDQogICAgKQ0KICB9DQoNCiAgIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCiAgIyA5LiBFc3RhbmRhcml6YXIgZWwgY2xpbWEgZGUgbG9zIHNpdGlvcw0KICAjIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQogIHNpdGlvc19lc3RhbmRhcml6YWRvcyA8LSBzd2VlcCgNCiAgICBzaXRpb3NfY2xpbWEsDQogICAgTUFSR0lOID0gMiwNCiAgICBTVEFUUyA9IG1lZGlhc19sb2NhbGVzLA0KICAgIEZVTiA9ICItIg0KICApDQoNCiAgc2l0aW9zX2VzdGFuZGFyaXphZG9zIDwtIHN3ZWVwKA0KICAgIHNpdGlvc19lc3RhbmRhcml6YWRvcywNCiAgICBNQVJHSU4gPSAyLA0KICAgIFNUQVRTID0gZGVzdmlhY2lvbmVzX2xvY2FsZXMsDQogICAgRlVOID0gIi8iDQogICkNCg0KICBpZiAoYW55KCFpcy5maW5pdGUoc2l0aW9zX2VzdGFuZGFyaXphZG9zKSkpIHsNCiAgICBzdG9wKA0KICAgICAgIkxhIGVzdGFuZGFyaXphY2nDs24gY2xpbcOhdGljYSBkZSBsb3Mgc2l0aW9zIHByb2R1am8gdmFsb3JlcyBubyB2w6FsaWRvcy4iLA0KICAgICAgY2FsbC4gPSBGQUxTRQ0KICAgICkNCiAgfQ0KDQogICMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQogICMgMTAuIENhbGN1bGFyIGxhIGRpc3RhbmNpYSBldWNsaWRpYW5hDQogICMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiAgbWFwYXNfbG9jYWxlcyA8LSB2ZWN0b3IoDQogICAgbW9kZSA9ICJsaXN0IiwNCiAgICBsZW5ndGggPSBucm93KHNpdGlvc19sb2NhbCkNCiAgKQ0KDQogIGZvciAoaSBpbiBzZXFfbGVuKG5yb3coc2l0aW9zX2xvY2FsKSkpIHsNCg0KICAgICMgSW5pY2lhbGl6YXIgY29uIGxhIHByaW1lcmEgdmFyaWFibGUNCiAgICBkaXN0YW5jaWFfY3VhZHJhZGEgPC0gKA0KICAgICAgY2xpbWFfZXN0YW5kYXJpemFkb1tbMV1dIC0NCiAgICAgIHNpdGlvc19lc3RhbmRhcml6YWRvc1tpLCAxXQ0KICAgICleMg0KDQogICAgIyBBY3VtdWxhciBsYXMgb3RyYXMgMjMgZGlmZXJlbmNpYXMgY3VhZHLDoXRpY2FzDQogICAgZm9yIChqIGluIDI6MjQpIHsNCg0KICAgICAgZGlzdGFuY2lhX2N1YWRyYWRhIDwtIGRpc3RhbmNpYV9jdWFkcmFkYSArICgNCiAgICAgICAgY2xpbWFfZXN0YW5kYXJpemFkb1tbal1dIC0NCiAgICAgICAgc2l0aW9zX2VzdGFuZGFyaXphZG9zW2ksIGpdDQogICAgICApXjINCiAgICB9DQoNCiAgICBkaXN0YW5jaWFfZXVjbGlkaWFuYSA8LSBzcXJ0KA0KICAgICAgZGlzdGFuY2lhX2N1YWRyYWRhDQogICAgKQ0KDQogICAgbWFwYV9zaW1pbGFyaWRhZCA8LSBleHAoDQogICAgICAtZGlzdGFuY2lhX2V1Y2xpZGlhbmENCiAgICApDQoNCiAgICBuYW1lcyhtYXBhX3NpbWlsYXJpZGFkKSA8LSBwYXN0ZTAoDQogICAgICAic2ltXyIsDQogICAgICBtYWtlLm5hbWVzKHNpdGlvc19sb2NhbCRzaXRpb1tpXSkNCiAgICApDQoNCiAgICBtYXBhc19sb2NhbGVzW1tpXV0gPC0gbWFwYV9zaW1pbGFyaWRhZA0KICB9DQoNCiAgIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCiAgIyAxMS4gSW50ZWdyYXIgeSB2YWxpZGFyIGxvcyB0cmVzIG1hcGFzDQogICMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiAgaWYgKA0KICAgIGxlbmd0aChtYXBhc19sb2NhbGVzKSAhPSAzTCB8fA0KICAgICFhbGwoDQogICAgICB2YXBwbHkoDQogICAgICAgIG1hcGFzX2xvY2FsZXMsDQogICAgICAgIGluaGVyaXRzLA0KICAgICAgICBsb2dpY2FsKDEpLA0KICAgICAgICB3aGF0ID0gIlNwYXRSYXN0ZXIiDQogICAgICApDQogICAgKQ0KICApIHsNCiAgICBzdG9wKA0KICAgICAgIk5vIGZ1ZSBwb3NpYmxlIGNvbnN0cnVpciBsb3MgdHJlcyBtYXBhcyBkZSBzaW1pbGFyaWRhZC4iLA0KICAgICAgY2FsbC4gPSBGQUxTRQ0KICAgICkNCiAgfQ0KDQogIHNpbWlsaXR1ZF9nbG9iYWwgPC0gYygNCiAgICBtYXBhc19sb2NhbGVzW1sxXV0sDQogICAgbWFwYXNfbG9jYWxlc1tbMl1dLA0KICAgIG1hcGFzX2xvY2FsZXNbWzNdXQ0KICApDQoNCiAgbmFtZXMoc2ltaWxpdHVkX2dsb2JhbCkgPC0gcGFzdGUwKA0KICAgICJTaW1pbGFyaWRhZF8iLA0KICAgIG1ha2UubmFtZXMoc2l0aW9zX2xvY2FsJHNpdGlvKQ0KICApDQoNCiAgaWYgKHRlcnJhOjpubHlyKHNpbWlsaXR1ZF9nbG9iYWwpICE9IDNMKSB7DQogICAgc3RvcCgNCiAgICAgICJFbCByZXN1bHRhZG8gZmluYWwgbm8gY29udGllbmUgdHJlcyBtYXBhcy4iLA0KICAgICAgY2FsbC4gPSBGQUxTRQ0KICAgICkNCiAgfQ0KDQogIHJhbmdvX2xvY2FsIDwtIHRlcnJhOjptaW5tYXgoDQogICAgc2ltaWxpdHVkX2dsb2JhbA0KICApDQoNCiAgaWYgKA0KICAgIGFueShyYW5nb19sb2NhbCA8IDAsIG5hLnJtID0gVFJVRSkgfHwNCiAgICBhbnkocmFuZ29fbG9jYWwgPiAxLCBuYS5ybSA9IFRSVUUpDQogICkgew0KICAgIHN0b3AoDQogICAgICAiTGEgc2ltaWxhcmlkYWQgY29udGllbmUgdmFsb3JlcyBmdWVyYSBkZWwgaW50ZXJ2YWxvIDDigJMxLiIsDQogICAgICBjYWxsLiA9IEZBTFNFDQogICAgKQ0KICB9DQoNCiAgICMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQogICMgMTIuIENyZWFyIHkgbW9zdHJhciBsYSB0YWJsYSBkZSByZXN1bWVuDQogICMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiAgcmVzdW1lbl9sb2NhbCA8LSBkYXRhLmZyYW1lKA0KICAgIFNpdGlvID0gc2l0aW9zX2xvY2FsJHNpdGlvLA0KICAgIGBTaW1pbGFyaWRhZCBtw61uaW1hYCA9IHJvdW5kKA0KICAgICAgYXMubnVtZXJpYyhyYW5nb19sb2NhbFsxLCBdKSwNCiAgICAgIDQNCiAgICApLA0KICAgIGBTaW1pbGFyaWRhZCBtw6F4aW1hYCA9IHJvdW5kKA0KICAgICAgYXMubnVtZXJpYyhyYW5nb19sb2NhbFsyLCBdKSwNCiAgICAgIDQNCiAgICApLA0KICAgIGNoZWNrLm5hbWVzID0gRkFMU0UsDQogICAgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFDQogICkNCiAgDQphc3NpZ24oDQogICJyZXN1bWVuX3NpbWlsYXJpZGFkIiwNCiAgcmVzdW1lbl9sb2NhbCwNCiAgZW52aXIgPSBwYXJlbnQuZnJhbWUoKQ0KKQ0KDQojICB0YWJsYV9sb2NhbCA8LSBrbml0cjo6a2FibGUoDQojICAgIHJlc3VtZW5fbG9jYWwsDQojICAgIGZvcm1hdCA9ICJodG1sIiwNCiMgICAgY2FwdGlvbiA9IHBhc3RlKA0KIyAgICAgICJSYW5nbyBkZSBzaW1pbGFyaWRhZCBjbGltw6F0aWNhIGdsb2JhbCIsDQojICAgICAgInBvciBzaXRpbyBkZWwgVmFsbGUgZGVsIENhdWNhIg0KIyAgICApLA0KIyAgICBjb2wubmFtZXMgPSBjKA0KIyAgICAgICJTaXRpbyBkZWwgVmFsbGUgZGVsIENhdWNhIiwNCiMgICAgICAiU2ltaWxhcmlkYWQgbcOtbmltYSIsDQojICAgICAgIlNpbWlsYXJpZGFkIG3DoXhpbWEiDQojICAgICksDQojICAgIGFsaWduID0gYygibCIsICJjIiwgImMiKSwNCiMgICAgZGlnaXRzID0gNCwNCiMgICAgZXNjYXBlID0gVFJVRSwNCiMgICAgdGFibGUuYXR0ciA9ICdjbGFzcz0idGFibGUgdGFibGUtc3RyaXBlZCB0YWJsZS1ob3ZlciB0YWJsZS1jb25kZW5zZWQiJw0KIyAgKSB8Pg0KIyAgICBrYWJsZUV4dHJhOjprYWJsZV9zdHlsaW5nKA0KIyAgICAgIGJvb3RzdHJhcF9vcHRpb25zID0gYygNCiMgICAgICAgICJzdHJpcGVkIiwNCiMgICAgICAgICJob3ZlciIsDQojICAgICAgICAiY29uZGVuc2VkIiwNCiMgICAgICAgICJyZXNwb25zaXZlIg0KIyAgICAgICksDQojICAgICAgZnVsbF93aWR0aCA9IFRSVUUsDQojICAgICAgcG9zaXRpb24gPSAiY2VudGVyIiwNCiMgICAgICBmb250X3NpemUgPSAxNA0KIyAgICApIHw+DQojICAgIGthYmxlRXh0cmE6OnJvd19zcGVjKA0KIyAgICAgIHJvdyA9IDAsDQojICAgICAgYm9sZCA9IFRSVUUsDQojICAgICAgY29sb3IgPSAid2hpdGUiLA0KIyAgICAgIGJhY2tncm91bmQgPSAiIzEyM0Y3MiINCiMgICAgKSB8Pg0KIyAgICBrYWJsZUV4dHJhOjpjb2x1bW5fc3BlYygNCiMgICAgICBjb2x1bW4gPSAxLA0KIyAgICAgIGJvbGQgPSBUUlVFLA0KIyAgICAgIGNvbG9yID0gIiMxMjNGNzIiLA0KIyAgICAgIHdpZHRoID0gIjM1JSINCiMgICAgKSB8Pg0KIyAgICBrYWJsZUV4dHJhOjpjb2x1bW5fc3BlYygNCiMgICAgICBjb2x1bW4gPSAyLA0KIyAgICAgIHdpZHRoID0gIjI1JSINCiMgICAgKSB8Pg0KIyAgICBrYWJsZUV4dHJhOjpjb2x1bW5fc3BlYygNCiMgICAgICBjb2x1bW4gPSAzLA0KIyAgICAgIHdpZHRoID0gIjI1JSINCiMgICAgKQ0KDQogICMgRW1pdGlyIGVsIEhUTUwgY29tbyBjb250ZW5pZG8gZGVsIGluZm9ybWUsIG5vIGNvbW8gdGV4dG8gZGUgY29uc29sYS4NCiMgIGNhdCgNCiMgICAgYXMuY2hhcmFjdGVyKHRhYmxhX2xvY2FsKSwNCiMgICAgc2VwID0gIlxuIg0KIyAgKQ0KDQogICMgU2VwYXJhY2nDs24gZW50cmUgbGEgdGFibGEgeSBsb3MgbWFwYXMuDQojICBjYXQoDQojICAgICJcbjxkaXYgc3R5bGU9J21hcmdpbi1ib3R0b206IDI4cHg7Jz48L2Rpdj5cbiINCiMgKQ0KICANCiAgIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCiAgIyAxMy4gQ2FyZ2FyIGzDrW1pdGVzIG11bmRpYWxlcw0KICAjIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQogIHBhaXNlc19sb2NhbGVzIDwtIGdlb2RhdGE6OndvcmxkKA0KICAgIHBhdGggPSBydXRhX2RhdG9zX2xvY2FsDQogICkNCg0KICAjIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KICAjIDE0LiBHcmFmaWNhciBsb3MgbWFwYXMNCiAgIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KICBjb25maWd1cmFjaW9uX2FudGVyaW9yIDwtIGdyYXBoaWNzOjpwYXIoDQogICAgbm8ucmVhZG9ubHkgPSBUUlVFDQogICkNCg0KICBncmFwaGljczo6cGFyKA0KICAgIG1mcm93ID0gYygzLCAxKSwNCiAgICBtYXIgPSBjKDIuNSwgMywgNCwgNS41KSwNCiAgICBvbWEgPSBjKDAsIDAsIDIsIDApDQogICkNCg0KICBmb3IgKGkgaW4gc2VxX2xlbigzTCkpIHsNCg0KICAgIHRlcnJhOjpwbG90KA0KICAgICAgc2ltaWxpdHVkX2dsb2JhbFtbaV1dLA0KICAgICAgY29sID0gaGNsLmNvbG9ycygNCiAgICAgICAgMTAwLA0KICAgICAgICBwYWxldHRlID0gIkluZmVybm8iLA0KICAgICAgICByZXYgPSBUUlVFDQogICAgICApLA0KICAgICAgcmFuZ2UgPSBjKDAsIDEpLA0KICAgICAgYXhlcyA9IFRSVUUsDQogICAgICBtYWluID0gcGFzdGUoDQogICAgICAgICJTaW1pbGFyaWRhZCBjbGltw6F0aWNhIGdsb2JhbCBjb24iLA0KICAgICAgICBzaXRpb3NfbG9jYWwkc2l0aW9baV0NCiAgICAgICksDQogICAgICBwbGcgPSBsaXN0KA0KICAgICAgICB0aXRsZSA9ICJTaW1pbGFyaWRhZCIsDQogICAgICAgIGNleCA9IDAuOA0KICAgICAgKQ0KICAgICkNCg0KICAgIHRlcnJhOjpsaW5lcygNCiAgICAgIHBhaXNlc19sb2NhbGVzLA0KICAgICAgY29sID0gImdyZXk1NSIsDQogICAgICBsd2QgPSAwLjI1DQogICAgKQ0KICB9DQoNCiAgZ3JhcGhpY3M6Om10ZXh0KA0KICAgIHRleHQgPSBwYXN0ZSgNCiAgICAgICJBbmFsb2fDrWEgY2xpbcOhdGljYSBjb24gc2l0aW9zIiwNCiAgICAgICJkZWwgVmFsbGUgZGVsIENhdWNhIg0KICAgICksDQogICAgc2lkZSA9IDMsDQogICAgb3V0ZXIgPSBUUlVFLA0KICAgIGxpbmUgPSAwLjMsDQogICAgZm9udCA9IDIsDQogICAgY2V4ID0gMS4xNSwNCiAgICBjb2wgPSAiIzEyM0Y3MiINCiAgKQ0KDQogIGdyYXBoaWNzOjpwYXIoDQogICAgY29uZmlndXJhY2lvbl9hbnRlcmlvcg0KICApDQoNCiAgIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCiAgIyAxNS4gR3VhcmRhciByZXN1bHRhZG9zDQogICMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiAgYXJjaGl2b19zaW1pbGFyaWRhZCA8LSBmaWxlLnBhdGgoDQogICAgcnV0YV9yZXN1bHRhZG9zX2xvY2FsLA0KICAgICJzaW1pbGFyaWRhZF9jbGltYXRpY2Ffc2l0aW9zX3ZhbGxlLnRpZiINCiAgKQ0KDQogIHRlcnJhOjp3cml0ZVJhc3RlcigNCiAgICBzaW1pbGl0dWRfZ2xvYmFsLA0KICAgIGZpbGVuYW1lID0gYXJjaGl2b19zaW1pbGFyaWRhZCwNCiAgICBvdmVyd3JpdGUgPSBUUlVFDQogICkNCg0KICAjIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KICAjIDE2LiBDcmVhciBvYmpldG9zIHJlcXVlcmlkb3MgcG9yIGNodW5rcyBwb3N0ZXJpb3Jlcw0KICAjIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQogIGVudG9ybm9fc2FsaWRhIDwtIHBhcmVudC5mcmFtZSgpDQoNCiAgYXNzaWduKA0KICAgICJ0bWVkX20iLA0KICAgIHRtZWRfbG9jYWwsDQogICAgZW52aXIgPSBlbnRvcm5vX3NhbGlkYQ0KICApDQoNCiAgYXNzaWduKA0KICAgICJwcmVjX20iLA0KICAgIHByZWNfbG9jYWwsDQogICAgZW52aXIgPSBlbnRvcm5vX3NhbGlkYQ0KICApDQoNCiAgYXNzaWduKA0KICAgICJjbGltYV8yNCIsDQogICAgY2xpbWFfbG9jYWwsDQogICAgZW52aXIgPSBlbnRvcm5vX3NhbGlkYQ0KICApDQoNCiAgYXNzaWduKA0KICAgICJjbGltYV96IiwNCiAgICBjbGltYV9lc3RhbmRhcml6YWRvLA0KICAgIGVudmlyID0gZW50b3Jub19zYWxpZGENCiAgKQ0KDQogIGFzc2lnbigNCiAgICAic2l0aW9zIiwNCiAgICBzaXRpb3NfbG9jYWwsDQogICAgZW52aXIgPSBlbnRvcm5vX3NhbGlkYQ0KICApDQoNCiAgYXNzaWduKA0KICAgICJleHRfc2l0aW9zIiwNCiAgICBleHRyYWNjaW9uX2xvY2FsLA0KICAgIGVudmlyID0gZW50b3Jub19zYWxpZGENCiAgKQ0KDQogIGFzc2lnbigNCiAgICAic2l0aW9zX3oiLA0KICAgIHNpdGlvc19lc3RhbmRhcml6YWRvcywNCiAgICBlbnZpciA9IGVudG9ybm9fc2FsaWRhDQogICkNCg0KICBhc3NpZ24oDQogICAgInBhaXNlcyIsDQogICAgcGFpc2VzX2xvY2FsZXMsDQogICAgZW52aXIgPSBlbnRvcm5vX3NhbGlkYQ0KICApDQoNCiAgYXNzaWduKA0KICAgICJzaW1fZ2xvYmFsIiwNCiAgICBzaW1pbGl0dWRfZ2xvYmFsLA0KICAgIGVudmlyID0gZW50b3Jub19zYWxpZGENCiAgKQ0KDQogIGFzc2lnbigNCiAgICAibWFwYXNfc2ltIiwNCiAgICBtYXBhc19sb2NhbGVzLA0KICAgIGVudmlyID0gZW50b3Jub19zYWxpZGENCiAgKQ0KDQogIGFzc2lnbigNCiAgICAicnV0YV9yZXN1bHRhZG9zIiwNCiAgICBydXRhX3Jlc3VsdGFkb3NfbG9jYWwsDQogICAgZW52aXIgPSBlbnRvcm5vX3NhbGlkYQ0KICApDQoNCiAgaW52aXNpYmxlKA0KICAgIHNpbWlsaXR1ZF9nbG9iYWwNCiAgKQ0KfQ0KDQojIMOaTklDQSBMTEFNQURBIERFTCBDSFVOSw0KIyBObyBlamVjdXRhciBsYXMgbMOtbmVhcyBpbnRlcm5hcyBkZSBsYSBmdW5jacOzbiBwb3Igc2VwYXJhZG8uDQpjb25zdHJ1aXJfc2ltaWxhcmlkYWRfY2xpbWF0aWNhKCkNCmBgYA0KDQpgYGB7ciB0YWJsYS1zaW1pbGFyaWRhZC1maW5hbCwgZWNobz1GQUxTRSwgcmVzdWx0cz0nYXNpcyd9DQoNCnJlc3VtZW5fc2ltaWxhcmlkYWQgPC0gbm9ybWFsaXphcl90YWJsYV91dGY4KHJlc3VtZW5fc2ltaWxhcmlkYWQpDQp0YWJsYV9zaW1pbGFyaWRhZF9odG1sIDwtIGtuaXRyOjprYWJsZSgNCiAgcmVzdW1lbl9zaW1pbGFyaWRhZCwNCiAgZm9ybWF0ID0gImh0bWwiLA0KICBjYXB0aW9uID0gcGFzdGUoDQogICAgIlJhbmdvIGRlIHNpbWlsYXJpZGFkIGNsaW3DoXRpY2EgZ2xvYmFsIiwNCiAgICAicG9yIHNpdGlvIGRlbCBWYWxsZSBkZWwgQ2F1Y2EiDQogICksDQogIGNvbC5uYW1lcyA9IGMoDQogICAgIlNpdGlvIGRlbCBWYWxsZSBkZWwgQ2F1Y2EiLA0KICAgICJTaW1pbGFyaWRhZCBtw61uaW1hIiwNCiAgICAiU2ltaWxhcmlkYWQgbcOheGltYSINCiAgKSwNCiAgYWxpZ24gPSBjKCJsIiwgImMiLCAiYyIpLA0KICBkaWdpdHMgPSA0LA0KICBlc2NhcGUgPSBUUlVFDQopIHw+DQogIGthYmxlRXh0cmE6OmthYmxlX3N0eWxpbmcoDQogICAgYm9vdHN0cmFwX29wdGlvbnMgPSBjKA0KICAgICAgInN0cmlwZWQiLA0KICAgICAgImhvdmVyIiwNCiAgICAgICJjb25kZW5zZWQiLA0KICAgICAgInJlc3BvbnNpdmUiDQogICAgKSwNCiAgICBmdWxsX3dpZHRoID0gVFJVRSwNCiAgICBwb3NpdGlvbiA9ICJjZW50ZXIiLA0KICAgIGZvbnRfc2l6ZSA9IDE0DQogICkgfD4NCiAga2FibGVFeHRyYTo6cm93X3NwZWMoDQogICAgMCwNCiAgICBib2xkID0gVFJVRSwNCiAgICBjb2xvciA9ICJ3aGl0ZSIsDQogICAgYmFja2dyb3VuZCA9ICIjMTIzRjcyIg0KICApIHw+DQogIGthYmxlRXh0cmE6OmNvbHVtbl9zcGVjKA0KICAgIDEsDQogICAgYm9sZCA9IFRSVUUsDQogICAgY29sb3IgPSAiIzEyM0Y3MiINCiAgKQ0KDQpjYXQoDQogIGFzLmNoYXJhY3Rlcih0YWJsYV9zaW1pbGFyaWRhZF9odG1sKSwNCiAgc2VwID0gIlxuIg0KKQ0KYGBgDQoNCg0KIyBDb21wYXJhY2nDs24gZGUgYXByb3hpbWFjaW9uZXMNCg0KTGEgYXByb3hpbWFjacOzbiBwb3IgdW1icmFsZXMgcmVzcG9uZGU6ICoqwr9kw7NuZGUgc2UgY3VtcGxlbiBzaW11bHTDoW5lYW1lbnRlIGxvcyByYW5nb3MgY2xpbcOhdGljb3MgY29uc2lkZXJhZG9zIMOzcHRpbW9zPyoqIExhIGFuYWxvZ8OtYSByZXNwb25kZTogKirCv2TDs25kZSBzZSBwYXJlY2UgZWwgY2ljbG8gbWVuc3VhbCBhbCBkZSBsb3Mgc2l0aW9zIGRlIHJlZmVyZW5jaWE/KiogTm8gc29uIGVxdWl2YWxlbnRlczogZG9zIGx1Z2FyZXMgcHVlZGVuIGNvbXBhcnRpciBwcm9tZWRpb3MgYW51YWxlcyBwZXJvIGRpZmVyaXIgZW4gbGEgZXN0YWNpb25hbGlkYWQuDQoNCmBgYHtyIGNvbXBhcmFjaW9uLXY3fQ0KIyBFc2NhbGEgcmVsYXRpdmEgZGUgc2ltaWxhcmlkYWQgYWx0YTogcGVyY2VudGlsIDkwIHNvYnJlIGNlbGRhcyB0ZXJyZXN0cmVzLg0KdW1icmFsX3NpbSA8LSBhcy5udW1lcmljKGdsb2JhbChzaW1fcHJvbWVkaW8sIGZ1bmN0aW9uKHgpIHF1YW50aWxlKHgsIDAuOTAsIG5hLnJtID0gVFJVRSkpWywgMV0pDQoNCiMgUsOhc3RlciBudW3DqXJpY28gZGUgY2xhc2VzLiBTZSBjb25zZXJ2YSBzaW4gbml2ZWxlcyBwYXJhIGxvcyBjw6FsY3Vsb3MuDQphY3VlcmRvX251bSA8LSB0ZXJyYTo6aWZlbCgNCiAgYXB0X2NsaW1hdGljYSA+PSAwLjc1ICYgc2ltX3Byb21lZGlvID49IHVtYnJhbF9zaW0sIDMsDQogIHRlcnJhOjppZmVsKA0KICAgIGFwdF9jbGltYXRpY2EgPj0gMC43NSwgMSwNCiAgICB0ZXJyYTo6aWZlbChzaW1fcHJvbWVkaW8gPj0gdW1icmFsX3NpbSwgMiwgMCkNCiAgKQ0KKQ0KbmFtZXMoYWN1ZXJkb19udW0pIDwtICJjb2RpZ29fYWN1ZXJkbyINCg0KIyBTZSBjcmVhIHVuYSBjb3BpYSBjYXRlZ8OzcmljYSBleGNsdXNpdmFtZW50ZSBwYXJhIGxhIHJlcHJlc2VudGFjacOzbiBncsOhZmljYS4NCmFjdWVyZG9fcGxvdCA8LSBhY3VlcmRvX251bQ0KbGV2ZWxzKGFjdWVyZG9fcGxvdCkgPC0gZGF0YS5mcmFtZSgNCiAgdmFsdWUgPSAwOjMsDQogIGNsYXNlID0gYygiTmluZ3VuYSIsICJTb2xvIHVtYnJhbGVzIiwgIlNvbG8gYW5hbG9nw61hIiwgIkNvaW5jaWRlbmNpYSIpDQopDQoNCnBsb3QoDQogIGFjdWVyZG9fcGxvdCwNCiAgY29sID0gYygiZ3JleTkwIiwgIiMyYzdiYjYiLCAiI2ZkYWU2MSIsICIjMWE5NjQxIiksDQogIG1haW4gPSAiQ29tcGFyYWNpw7NuIGVzcGFjaWFsIGRlIGxhcyBkb3MgYXByb3hpbWFjaW9uZXMiDQopDQpsaW5lcyhwYWlzZXMsIGNvbCA9ICJncmV5MzAiLCBsd2QgPSAwLjIpDQoNCiMgUmVzdW1lbiByb2J1c3RvIGRlIHN1cGVyZmljaWVzLiBTZSBldml0YSB6b25hbCgpIHNvYnJlIHVuIHLDoXN0ZXIgY2F0ZWfDs3JpY28sDQojIHBvcnF1ZSBzdSBwcmltZXJhIGNvbHVtbmEgcHVlZGUgZGV2b2x2ZXJzZSBjb21vIHRleHRvIHNlZ8O6biBsYSB2ZXJzacOzbiBkZSB0ZXJyYS4NCmFyZWFfY2VsZGEgPC0gdGVycmE6OmNlbGxTaXplKGFjdWVyZG9fbnVtLCB1bml0ID0gImttIikNCmNsYXNlc19jb2RpZ28gPC0gMDozDQpjbGFzZXNfbm9tYnJlIDwtIGMoIk5pbmd1bmEiLCAiU29sbyB1bWJyYWxlcyIsICJTb2xvIGFuYWxvZ8OtYSIsICJDb2luY2lkZW5jaWEiKQ0KDQphcmVhX3Bvcl9jbGFzZSA8LSB2YXBwbHkoDQogIGNsYXNlc19jb2RpZ28sDQogIGZ1bmN0aW9uKGNvZGlnbykgew0KICAgIGFyZWFfY2xhc2UgPC0gdGVycmE6OmlmZWwoYWN1ZXJkb19udW0gPT0gY29kaWdvLCBhcmVhX2NlbGRhLCBOQSkNCiAgICB2YWxvciA8LSB0ZXJyYTo6Z2xvYmFsKGFyZWFfY2xhc2UsICJzdW0iLCBuYS5ybSA9IFRSVUUpWzEsIDFdDQogICAgaWYgKGlzLm5hKHZhbG9yKSkgMCBlbHNlIGFzLm51bWVyaWModmFsb3IpDQogIH0sDQogIG51bWVyaWMoMSkNCikNCg0KcmVzdW1lbl9hY3VlcmRvIDwtIGRhdGEuZnJhbWUoDQogIGNvZGlnbyA9IGNsYXNlc19jb2RpZ28sDQogIGNsYXNlID0gY2xhc2VzX25vbWJyZSwNCiAgYXJlYV9rbTIgPSBhcmVhX3Bvcl9jbGFzZQ0KKQ0KDQpzdG9waWZub3QoDQogIG5yb3cocmVzdW1lbl9hY3VlcmRvKSA9PSA0TCwNCiAgaXMubnVtZXJpYyhyZXN1bWVuX2FjdWVyZG8kYXJlYV9rbTIpLA0KICBhbGwoaXMuZmluaXRlKHJlc3VtZW5fYWN1ZXJkbyRhcmVhX2ttMikpLA0KICBhbGwocmVzdW1lbl9hY3VlcmRvJGFyZWFfa20yID49IDApDQopDQoNCnJlc3VtZW5fYWN1ZXJkbyA8LSBub3JtYWxpemFyX3RhYmxhX3V0ZjgocmVzdW1lbl9hY3VlcmRvKQ0Ka25pdHI6OmthYmxlKA0KICByZXN1bWVuX2FjdWVyZG9bLCBjKCJjbGFzZSIsICJhcmVhX2ttMiIpXSwNCiAgZGlnaXRzID0gMCwNCiAgY29sLm5hbWVzID0gYygiQ2xhc2UiLCAiw4FyZWEgYXByb3hpbWFkYSAoa23CsikiKSwNCiAgY2FwdGlvbiA9ICJTdXBlcmZpY2llIHBvciB0aXBvIGRlIGFjdWVyZG8iDQopDQpgYGANCg0KIyMgSW50ZXJwcmV0YWNpw7NuDQoNCi0gKipDb2luY2lkZW5jaWE6Kiogem9uYXMgcHJpb3JpdGFyaWFzIHBhcmEgdW5hIGV2YWx1YWNpw7NuIGFncm9lY29sw7NnaWNhIHBvc3RlcmlvciwgcG9ycXVlIGN1bXBsZW4gbG9zIHJhbmdvcyBhbnVhbGVzIHkgcmVwcm9kdWNlbiByZWxhdGl2YW1lbnRlIGJpZW4gZWwgY2ljbG8gbWVuc3VhbCBkZSBsb3Mgc2l0aW9zIHZhbGxlY2F1Y2Fub3MuDQotICoqU29sbyB1bWJyYWxlczoqKiBlbCBwcm9tZWRpbyBhbnVhbCBlcyBmYXZvcmFibGUsIHBlcm8gbGEgZGlzdHJpYnVjacOzbiBpbnRyYWFudWFsIGRlIGxsdXZpYSBvIHRlbXBlcmF0dXJhIGRpZmllcmUgZGVsIHBhdHLDs24gZGUgcmVmZXJlbmNpYS4NCi0gKipTb2xvIGFuYWxvZ8OtYToqKiBlbCBwYXRyw7NuIG1lbnN1YWwgc2UgcGFyZWNlLCBhdW5xdWUgYWwgbWVub3MgdW5vIGRlIGxvcyB2YWxvcmVzIGFudWFsZXMgbm8gYWxjYW56YSBlbCBjcml0ZXJpbyBlc3RyaWN0byBkZSBhcHRpdHVkIGFsdGEuDQotICoqTmluZ3VuYToqKiBiYWphIHByaW9yaWRhZCBjbGltw6F0aWNhIGJham8gbG9zIHN1cHVlc3RvcyBhZG9wdGFkb3MuDQoNCiMgQ29uY2x1c2lvbmVzDQoNCjEuIEVsIGFuw6FsaXNpcyBwb3IgdW1icmFsZXMgcGVybWl0ZSBsb2NhbGl6YXIgZGUgbWFuZXJhIHRyYW5zcGFyZW50ZSBsYXMgY2VsZGFzIGRvbmRlIGxhIHRlbXBlcmF0dXJhIG1lZGlhIHkgbGEgcHJlY2lwaXRhY2nDs24gYW51YWwgc2UgYXByb3hpbWFuIGEgbG9zIHJhbmdvcyDDs3B0aW1vcyBkZWZpbmlkb3MgcGFyYSBlbCBjYXNvLg0KMi4gRWwgcmFua2luZyBkZSBwYcOtc2VzIHNlIGJhc2EgZW4gc3VwZXJmaWNpZSBhcHRhIGNhbGN1bGFkYSwgbm8gZW4gdW5hIHNlbGVjY2nDs24gdmlzdWFsLiBMb3MgdHJlcyBwYcOtc2VzIHkgc3VzIG1hcGFzIHNlIGdlbmVyYW4gYXV0b23DoXRpY2FtZW50ZSBhbCBlamVjdXRhciBlbCBpbmZvcm1lLg0KMy4gTGEgYW5hbG9nw61hIGNsaW3DoXRpY2EgYcOxYWRlIGluZm9ybWFjacOzbiBzb2JyZSBlc3RhY2lvbmFsaWRhZC4gUG9yIGVsbG8gcHVlZGUgZXhjbHVpciB6b25hcyBjb24gcHJvbWVkaW9zIGFudWFsZXMgYWRlY3VhZG9zLCBwZXJvIGNvbiBjYWxlbmRhcmlvcyBkZSBsbHV2aWEgbyB0ZW1wZXJhdHVyYSBtdXkgZGlmZXJlbnRlcyBhIGxvcyBkZWwgVmFsbGUgZGVsIENhdWNhLg0KNC4gTGFzIMOhcmVhcyBkb25kZSBjb2luY2lkZW4gYW1iYXMgYXByb3hpbWFjaW9uZXMgY29uc3RpdHV5ZW4gY2FuZGlkYXRvcyByYXpvbmFibGVzIHBhcmEgZXN0dWRpb3MgcG9zdGVyaW9yZXMsIHBlcm8gKipubyBwcnVlYmFuIHJlbmRpbWllbnRvIHBvdGVuY2lhbCBlbiBrZy9oYSoqLg0KNS4gQW50ZXMgZGUgdW5hIGRlY2lzacOzbiBwcm9kdWN0aXZhIGRlYmVuIGluY29ycG9yYXJzZSBzdWVsbywgcGVuZGllbnRlLCBkaXNwb25pYmlsaWRhZCBkZSBhZ3VhLCByYWRpYWNpw7NuLCByaWVzZ28gZGUgaW51bmRhY2nDs24gbyBzZXF1w61hLCBpbmZyYWVzdHJ1Y3R1cmEsIHJlc3RyaWNjaW9uZXMgYW1iaWVudGFsZXMsIHZhcmllZGFkIHkgbWFuZWpvLiBMYSBldGFwYSBzaWd1aWVudGUgZGViZSB1c2FyIHVuIG1vZGVsbyBkZSBjdWx0aXZvIGNhbGlicmFkbyB5IGRhdG9zIGxvY2FsZXMgZGUgcmVuZGltaWVudG8uDQoNCiMgTGltaXRhY2lvbmVzIHkgcmVwcm9kdWNpYmlsaWRhZA0KDQotIFdvcmxkQ2xpbSAyLjEgcmVwcmVzZW50YSBwcm9tZWRpb3MgZGUgMTk3MOKAkzIwMDAgeSBubyBsYXMgY29uZGljaW9uZXMgb2JzZXJ2YWRhcyBkZSB1biBhw7FvIGVzcGVjw61maWNvLg0KLSBMYSByZXNvbHVjacOzbiBkZSAxMCBtaW51dG9zIHN1YXZpemEgdmFyaWFjaW9uZXMgbG9jYWxlcywgZXNwZWNpYWxtZW50ZSBlbiByZWxpZXZlcyBjb21wbGVqb3MuDQotIExvcyB0cmVzIHB1bnRvcyBzb24gcmVmZXJlbmNpYXMgZXNwYWNpYWxlcyB2ZXJpZmljYWJsZXMgbWVkaWFudGUgR29vZ2xlIE1hcHMsIHBlcm8gbm8gc3VzdGl0dXllbiBjb29yZGVuYWRhcyBkZSBsb3RlcyBvIGVzdGFjaW9uZXMgYWdyb21ldGVvcm9sw7NnaWNhcy4NCi0gTG9zIHJhbmdvcyDDs3B0aW1vcyBmdWVyb24gZmlqYWRvcyBwb3IgZWwgZW51bmNpYWRvLiBMb3MgbcOhcmdlbmVzIGV4dGVybm9zIGRlbCDDrW5kaWNlIHRyYXBlem9pZGFsIHNvbiBzdXB1ZXN0b3MgbWV0b2RvbMOzZ2ljb3MgZXhwbMOtY2l0b3MgeSBwdWVkZW4gYWp1c3RhcnNlLg0KLSBMYSBzaW1pbGFyaWRhZCBkZXBlbmRlIGRlIGxhcyB2YXJpYWJsZXMsIGVzdGFuZGFyaXphY2nDs24sIG3DqXRyaWNhIHkgdHJhbnNmb3JtYWNpw7NuIGVsZWdpZGFzLg0KLSBMYSBzZWxlY2Npw7NuIGRlbCBwZXJjZW50aWwgOTAgc2UgdXNhIHNvbG8gcGFyYSBjb21wYXJhciBtYXBhczsgbm8gcmVwcmVzZW50YSB1biB1bWJyYWwgYWdyb27Ds21pY28gdW5pdmVyc2FsLg0KDQpgYGB7ciBndWFyZGFyLXJlc3VtZW59DQp3cml0ZS5jc3YocmFua2luZ19wYWlzZXMsDQogICAgICAgICAgZmlsZS5wYXRoKHJ1dGFfcmVzdWx0YWRvcywgInJhbmtpbmdfcGFpc2VzX2FwdGl0dWQuY3N2IiksIHJvdy5uYW1lcyA9IEZBTFNFKQ0Kd3JpdGUuY3N2KHNlcmllX3NpdGlvcywNCiAgICAgICAgICBmaWxlLnBhdGgocnV0YV9yZXN1bHRhZG9zLCAiY2xpbWFfbWVuc3VhbF9zaXRpb3NfdmFsbGUuY3N2IiksIHJvdy5uYW1lcyA9IEZBTFNFKQ0KY2F0KCJSZXN1bHRhZG9zIGd1YXJkYWRvcyBlbjoiLCBub3JtYWxpemVQYXRoKHJ1dGFfcmVzdWx0YWRvcyksICJcbiIpDQpgYGANCg0KIyBSZWZlcmVuY2lhcw0KDQotIEZpY2ssIFMuIEUuLCAmIEhpam1hbnMsIFIuIEouICgyMDE3KS4gV29ybGRDbGltIDI6IG5ldyAxLWttIHNwYXRpYWwgcmVzb2x1dGlvbiBjbGltYXRlIHN1cmZhY2VzIGZvciBnbG9iYWwgbGFuZCBhcmVhcy4gKkludGVybmF0aW9uYWwgSm91cm5hbCBvZiBDbGltYXRvbG9neSwgMzcqKDEyKSwgNDMwMuKAkzQzMTUuIDxodHRwczovL2RvaS5vcmcvMTAuMTAwMi9qb2MuNTA4Nj4NCi0gV29ybGRDbGltLiBIaXN0b3JpY2FsIGNsaW1hdGUgZGF0YSwgdmVyc2lvbiAyLjEsIHBlcmlvZG8gMTk3MOKAkzIwMDAuIDxodHRwczovL3d3dy53b3JsZGNsaW0ub3JnL2RhdGEvd29ybGRjbGltMjEuaHRtbD4NCi0gRm9vZCBhbmQgQWdyaWN1bHR1cmUgT3JnYW5pemF0aW9uIG9mIHRoZSBVbml0ZWQgTmF0aW9ucyAoRkFPKS4gQ3JvcCByZXF1aXJlbWVudHMgYW5kIGxpbWl0YXRpb25zLiA8aHR0cHM6Ly93d3cuZmFvLm9yZy80L3g1NjQ4ZS94NTY0OGUwZS5odG0+DQoNCmBgYHtyIGluZm9ybWFjaW9uLXNlc2lvbn0NCnNlc3Npb25JbmZvKCkNCmBgYA0K