1 Introducción

El propósito de esta actividad es analizar de manera integral la oferta de vivienda urbana para entender mejor cómo se está comportando el mercado inmobiliario y así apoyar la toma de decisiones estratégicas de la empresa. A partir de una base de datos amplia de propiedades en oferta, se busca descubrir patrones, relaciones y segmentaciones clave que ayuden a orientar los procesos de valoración, compra y venta, así como a comprender las dinámicas del mercado según la zona y el estrato.

2 Problema y preparación de los datos

2.1 Descripción del problema

Una empresa inmobiliaria líder en una gran ciudad está buscando comprender en profundidad el mercado de viviendas urbanas para tomar decisiones estratégicas más informadas. La empresa posee una base de datos extensa que contiene información detallada sobre diversas propiedades residenciales disponibles en el mercado. Se requiere realizar un análisis holístico de estos datos para identificar patrones, relaciones y segmentaciones relevantes que permitan mejorar la toma de decisiones en cuanto a la compra, venta y valoración de propiedades.

2.2 Descripción de los datos

La base de datos reúne información de viviendas residenciales que actualmente están en oferta dentro del mercado urbano. Incluye tanto variables numéricas como categóricas relacionadas con el precio, las características físicas del inmueble, su ubicación y la clasificación socioeconómica, lo que permite tener una visión más completa del comportamiento de las propiedades.

2.2.1 Fuente de datos

Los datos provienen de un proceso de levantamiento de información del mercado de vivienda en oferta y han sido consolidados en una estructura depurada y organizada, lista para su análisis estadístico multivariado.

# Instalar paquete si no está (solo la primera vez)
# install.packages("devtools")
# devtools::install_github("centro-magis/paqueteMODELOS")

library(paqueteMODELOS)

# Cargar dataset (ajusta el nombre si es diferente)
data("vivienda")

# Crear dataframe de trabajo
df_vivienda <- vivienda

df_vivienda <- df_vivienda |>
  tibble::as_tibble()

# Vista rápida
mi_tabla(
  head(df_vivienda, 5), 
  id="tabla_resumen", 
  titulo="Tabla de ofertas inmobiliarias (registros iniciales)")
Tabla 1. Tabla de ofertas inmobiliarias (registros iniciales)
id zona piso estrato preciom areaconst parqueaderos banios habitaciones tipo barrio longitud latitud
1147 Zona Oriente NA 3 250 70 1 3 6 Casa 20 de julio -76.512 3.434
1169 Zona Oriente NA 3 320 120 1 2 3 Casa 20 de julio -76.512 3.434
1350 Zona Oriente NA 3 350 220 2 2 4 Casa 20 de julio -76.515 3.436
5992 Zona Sur 02 4 400 280 3 5 3 Casa 3 de julio -76.540 3.435
1212 Zona Norte 01 5 260 90 1 2 3 Apartamento acopi -76.513 3.459

2.2.2 Variables incluidas

A nivel general, se consideran variables como:

  • Ubicación: zona, barrio, latitud, longitud.

  • Características del inmueble: área construida, número de habitaciones, baños, parqueaderos, piso.

  • Contexto socioeconómico: estrato.

  • Variable de valor: precio (valor comercial u ofertado).

  • Tipo de inmueble: casa o apartamento.

2.2.3 Calidad de datos

Antes de aplicar modelos, se revisará:

2.2.3.1 Presencia de valores faltantes.

# Total de valores faltantes en el dataset
total_na <- sum(is.na(df_vivienda))
  • Diagnóstico del total de datos faltantes: Se identifica 4275 datos faltantes en la tabla de ofertas inmobiliarias.

  • Diagnóstico de los datos faltantes por variable: A continuación se presentan los datos faltantes para cada variable.

# Conteo de NA por variable
na_count <- colSums(is.na(df_vivienda))

# Construir dataframe resumen
df_na_resumen <- data.frame(
  variable = names(na_count),
  cantidad_faltantes = as.integer(na_count),
  porcentaje_faltantes = round(100 * na_count / nrow(df_vivienda), 2)
) |>
  dplyr::arrange(desc(porcentaje_faltantes))

df_na_resumen <- df_na_resumen |>
  tibble::as_tibble()

mi_tabla(
  df_na_resumen, 
  id="tabla_resumen", 
  titulo="Registros faltante por variable en tabla de ofertas inmobiliarias")
Tabla 2. Registros faltante por variable en tabla de ofertas inmobiliarias
variable cantidad_faltantes porcentaje_faltantes
piso 2638 31.70
parqueaderos 1605 19.29
id 3 0.04
zona 3 0.04
estrato 3 0.04
areaconst 3 0.04
banios 3 0.04
habitaciones 3 0.04
tipo 3 0.04
barrio 3 0.04
longitud 3 0.04
latitud 3 0.04
preciom 2 0.02

2.2.3.2 Corrección de los datos por variable.

Se aplica una estrategia de limpieza e imputación de valores faltantes ajustada al contexto de las ofertas inmobiliarias. Primero, se normalizan las variables de texto convirtiendo las cadenas vacías en valores faltantes reales y se crean indicadores (piso_missing y parqueaderos_missing) para conservar la información sobre la ausencia de datos. Luego se eliminan los registros con valores faltantes en variables críticas como precio, área y coordenadas, por ser fundamentales para el análisis. Posteriormente, la variable piso se imputa con la categoría “Sin información” y parqueaderos se corrige utilizando la mediana por grupo (tipo y estrato), respetando así la estructura del mercado; si aún quedan faltantes, se aplica una segunda imputación global. Finalmente, las demás variables numéricas se completan con la mediana y las categóricas con una etiqueta explícita, dejando el dataset limpio, consistente y listo para aplicar técnicas multivariadas como PCA, clustering y análisis de correspondencia.

df_vivienda_clean <- df_vivienda %>%
  # 1) Normalizar strings vacíos ("" -> NA) en caracteres
  mutate(across(where(is.character), ~ na_if(str_squish(.x), ""))) %>%
  
  # 2) Crear banderas de missing para variables clave (útil en modelos)
  mutate(
    piso_missing = is.na(piso),
    parqueaderos_missing = is.na(parqueaderos)
  ) %>%
  
  # 3) Eliminar registros con NA en variables "críticas" (son muy pocos)
  # Ajusta la lista si quieres incluir/quitar campos críticos
  drop_na(preciom, areaconst, latitud, longitud) %>%
  
  # 4) Imputación de 'piso' (categórica/ordinal con NA alto)
  mutate(piso = replace_na(piso, "Sin informacion")) %>%
  
  # 5) Imputación de 'parqueaderos' por grupo (más realista)
  # Primero por tipo+estrato (si existen); si no, cae a mediana global.
  group_by(tipo, estrato) %>%
  mutate(
    parqueaderos = if_else(
      is.na(parqueaderos),
      as.numeric(median(parqueaderos, na.rm = TRUE)),
      as.numeric(parqueaderos)
    )
  ) %>%
  ungroup() %>%
  mutate(
    parqueaderos = if_else(
      is.na(parqueaderos),
      as.numeric(median(parqueaderos, na.rm = TRUE)),
      parqueaderos
    )
  ) %>%
  
  # 6) Imputación de otras numéricas con mediana global (robusta a outliers)
  mutate(across(
    where(is.numeric),
    ~ if_else(is.na(.x), median(.x, na.rm = TRUE), .x)
  )) %>%
  
  # 7) Imputación de otras categóricas
  mutate(across(
    where(is.character),
    ~ replace_na(.x, "Sin informacion")
  ))

Como verificación final, se identifican 0 datos faltantes en la tabla de ofertas inmobiliarias.

# mi_tabla(
#  head(df_vivienda_clean, 5), 
#  id="tabla_resumen", 
#  titulo="Tabla de ofertas inmobiliarias corregida (registros iniciales)")

2.2.3.3 Valores atípicos en precio y área.

Aplicando método IQR y MAD: El método IQR no detecta valores atípicos, mientras que el método robusto basado en MAD identifica un leve porcentaje de observaciones extremas en precio y área. Esto sugiere la presencia de colas largas en la distribución, fenómeno común en mercados inmobiliarios debido a la existencia de propiedades de alto valor y gran tamaño.

# Variables a evaluar
vars_out <- c("preciom", "areaconst")

# --- Función: límites IQR ---
limites_iqr <- function(x) {
  q1 <- quantile(x, 0.25, na.rm = TRUE)
  q3 <- quantile(x, 0.75, na.rm = TRUE)
  iqr <- q3 - q1
  c(li = q1 - 1.5 * iqr, ls = q3 + 1.5 * iqr)
}

# --- Función: z-score robusto (MAD) ---
z_robusto <- function(x) {
  med <- median(x, na.rm = TRUE)
  madv <- mad(x, constant = 1.4826, na.rm = TRUE)  # escala ~ sd
  (x - med) / madv
}

# Calcular límites por variable
lim_p <- limites_iqr(df_vivienda_clean$preciom)
lim_a <- limites_iqr(df_vivienda_clean$areaconst)

df_outliers <- df_vivienda_clean %>%
  mutate(
    out_preciom_iqr  = preciom < lim_p["li"] | preciom > lim_p["ls"],
    out_areaconst_iqr = areaconst < lim_a["li"] | areaconst > lim_a["ls"],
    z_preciom_rob = z_robusto(preciom),
    z_areaconst_rob = z_robusto(areaconst),
    out_preciom_mad  = abs(z_preciom_rob) > 3.5,
    out_areaconst_mad = abs(z_areaconst_rob) > 3.5
  ) %>%
  mutate(
    outlier_any = out_preciom_iqr | out_areaconst_iqr | out_preciom_mad | out_areaconst_mad
  )

# Resumen de cantidad de outliers
res_out <- tibble::tibble(
  variable = c("preciom", "areaconst"),
  n_out_iqr = c(sum(df_outliers$out_preciom_iqr, na.rm = TRUE),
               sum(df_outliers$out_areaconst_iqr, na.rm = TRUE)),
  n_out_mad = c(sum(df_outliers$out_preciom_mad, na.rm = TRUE),
               sum(df_outliers$out_areaconst_mad, na.rm = TRUE)),
  pct_out_iqr = round(100 * n_out_iqr / nrow(df_outliers), 2),
  pct_out_mad = round(100 * n_out_mad / nrow(df_outliers), 2)
)
mi_tabla(
  res_out, 
  id="tabla_resumen", 
  titulo="Resultado de los metodo IQR y MAD para valores atipicos")
Tabla 3. Resultado de los metodo IQR y MAD para valores atipicos
variable n_out_iqr n_out_mad pct_out_iqr pct_out_mad
preciom 0 537 0 6.46
areaconst 0 517 0 6.21

Aplicando diagramas de cajas y dispersión: El análisis de dispersión entre el área construida y el precio muestra la presencia de valores extremos, ligados a propiedades de mayor tamaño y valor, algo esperable por la existencia de segmentos premium. Dado que no se observan outliers claramente erróneos o inconsistentes, no se recomienda eliminar registros de forma masiva, ya que esto podría distorsionar la representación real del mercado. En su lugar, se sugiere conservar las observaciones y aplicar transformaciones logarítmicas a las variables de precio y área construida para estabilizar la varianza, disminuir la influencia de los valores extremos y fortalecer la robustez de los análisis multivariados posteriores, como PCA y clustering.

p_box1 <- ggplot(df_outliers, aes(x = "", y = preciom)) +
  geom_boxplot() +
  labs(x = NULL, y = "preciom")

p_box2 <- ggplot(df_outliers, aes(x = "", y = areaconst)) +
  geom_boxplot() +
  labs(x = NULL, y = "areaconst")

mi_figura(
  p_box1,
  id     = "figura_resumen",
  titulo = "Diagrama de caja del precio de ofertas inmobiliarias"
)
Figura 1. Diagrama de caja del precio de ofertas inmobiliarias
mi_figura(
  p_box2,
  id     = "figura_resumen",
  titulo = "Diagrama de caja del area de ofertas inmobiliarias"
)
Figura 2. Diagrama de caja del area de ofertas inmobiliarias
p_dis_prec_area <- ggplot(df_outliers, aes(x = areaconst, y = preciom)) +
  geom_point(aes(shape = outlier_any), alpha = 0.5) +
  labs(
    x = "areaconst",
    y = "preciom"
  )

mi_figura(
  p_dis_prec_area,
  id     = "figura_resumen",
  titulo = "Diagrama de dispersón del área versus precio (outliers resaltados)"
)
Figura 3. Diagrama de dispersón del área versus precio (outliers resaltados)

2.2.3.4 Necesidad de estandarización para variables en diferentes escalas.

Se procede a la estandarización de variables (z-score) y a la creación de la variables logaritmicas de precio y área.

# 1) Crear variables log de forma segura
# Usamos log1p() = log(1 + x) para evitar problemas si hay 0.
# (Si tus variables son estrictamente > 0, también puedes usar log(x).)
df_vivienda_prep <- df_vivienda_clean %>%
  mutate(
    log_preciom   = log1p(preciom),
    log_areaconst = log1p(areaconst)
  )

# 2) Seleccionar columnas numéricas a estandarizar
# Recomendación: excluir identificadores y coordenadas si no van al PCA/Clustering
cols_excluir <- intersect(names(df_vivienda_prep), c("id", "latitud", "longitud"))

num_cols <- df_vivienda_prep %>%
  select(where(is.numeric)) %>%
  select(-all_of(cols_excluir)) %>%
  names()

# 3) Estandarización (z-score): (x - media) / sd
# scale() devuelve matriz, por eso lo convertimos a numeric con as.numeric
df_vivienda_std <- df_vivienda_prep %>%
  mutate(across(
    all_of(num_cols),
    ~ as.numeric(scale(.x))
  ))

# 4) Verificación rápida: medias ~0 y sd ~1 (aprox)
verif_std <- data.frame(
  variable = num_cols,
  media = round(sapply(df_vivienda_std[num_cols], mean, na.rm = TRUE), 3),
  sd    = round(sapply(df_vivienda_std[num_cols], sd,   na.rm = TRUE), 3)
)

verif_std <- verif_std |>
  tibble::as_tibble()

mi_tabla(
  verif_std, 
  id="tabla_resumen", 
  titulo="Verificación de estandarización de variables")
Tabla 4. Verificación de estandarización de variables
variable media sd
estrato 0 1
preciom 0 1
areaconst 0 1
parqueaderos 0 1
banios 0 1
habitaciones 0 1
log_preciom 0 1
log_areaconst 0 1
df_vivienda_prep <- df_vivienda_prep |>
  tibble::as_tibble()

# Vista rápida
mi_tabla(
  head(df_vivienda_prep, 5), 
  id="tabla_resumen", 
  titulo="Tabla de ofertas inmobiliarias con datos preparados (registros iniciales)")
Tabla 5. Tabla de ofertas inmobiliarias con datos preparados (registros iniciales)
id zona piso estrato preciom areaconst parqueaderos banios habitaciones tipo barrio longitud latitud piso_missing parqueaderos_missing log_preciom log_areaconst
1147 Zona Oriente Sin informacion 3 250 70 1 3 6 Casa 20 de julio -76.512 3.434 TRUE FALSE 5.525 4.263
1169 Zona Oriente Sin informacion 3 320 120 1 2 3 Casa 20 de julio -76.512 3.434 TRUE FALSE 5.771 4.796
1350 Zona Oriente Sin informacion 3 350 220 2 2 4 Casa 20 de julio -76.515 3.436 TRUE FALSE 5.861 5.398
5992 Zona Sur 02 4 400 280 3 5 3 Casa 3 de julio -76.540 3.435 FALSE FALSE 5.994 5.638
1212 Zona Norte 01 5 260 90 1 2 3 Apartamento acopi -76.513 3.459 FALSE FALSE 5.565 4.511
df_vivienda_std <- df_vivienda_std |>
  tibble::as_tibble()

# Vista rápida
mi_tabla(
  head(df_vivienda_std, 5), 
  id="tabla_resumen", 
  titulo="Tabla de ofertas inmobiliarias con variables estandarizadas (registros iniciales)")
Tabla 6. Tabla de ofertas inmobiliarias con variables estandarizadas (registros iniciales)
id zona piso estrato preciom areaconst parqueaderos banios habitaciones tipo barrio longitud latitud piso_missing parqueaderos_missing log_preciom log_areaconst
1147 Zona Oriente Sin informacion -1.587 -0.560 -0.734 -0.668 -0.078 1.641 Casa 20 de julio -76.512 3.434 TRUE FALSE -0.477 -1.012
1169 Zona Oriente Sin informacion -1.587 -0.347 -0.384 -0.668 -0.778 -0.415 Casa 20 de julio -76.512 3.434 TRUE FALSE -0.109 -0.205
1350 Zona Oriente Sin informacion -1.587 -0.255 0.315 0.273 -0.778 0.270 Casa 20 de julio -76.515 3.436 TRUE FALSE 0.025 0.706
5992 Zona Sur 02 -0.616 -0.103 0.735 1.214 1.322 -0.415 Casa 3 de julio -76.540 3.435 FALSE FALSE 0.225 1.069
1212 Zona Norte 01 0.356 -0.529 -0.594 -0.668 -0.778 -0.415 Apartamento acopi -76.513 3.459 FALSE FALSE -0.418 -0.636

La verificación posterior a la estandarización muestra que todas las variables numéricas presentan media cercana a cero y desviación estándar cercana a uno, lo que confirma la correcta aplicación del proceso de escalamiento y garantiza la comparabilidad entre variables para los análisis multivariados posteriores.

3 Metodología del reto

El reto principal consisten en realizar un análisis integral y multidimensional de la base de datos para obtener una comprensión del mercado inmobiliario urbano. Se requiere aplicar diversas técnicas de análisis de datos, incluyendo:

3.1 Análisis de Componentes Principales (PCA)

Reducir la dimensionalidad del conjunto de datos y visualizar la estructura de las variables en componentes principales para identificar características clave que influyen en la variación de precios y oferta del mercado.

3.1.1 Selección de variables numericas para PCA

Selecciona las variables numericas para realizar el analisis:

# 1) Selección de variables numéricas para PCA
# - Excluye identificadores y coordenadas
# - Excluye versiones originales si usarás log_*
excluir <- intersect(names(df_vivienda_std), c("id", "latitud", "longitud", "preciom", "areaconst"))

df_pca <- df_vivienda_std %>%
  select(where(is.numeric)) %>%
  select(-all_of(excluir))

# Vista rápida
mi_tabla(
  data.frame(variable = names(df_pca)), 
  id="tabla_resumen", 
  titulo="Variables numericas seleccionadas para PCA")
Tabla 7. Variables numericas seleccionadas para PCA
variable
estrato
parqueaderos
banios
habitaciones
log_preciom
log_areaconst

3.1.2 Ejecución del análisis de componentes principales

Se ejecuta el análisis PCA pra determinar la importancia de los componentes:

# 2) PCA con prcomp (ya está estandarizado, por eso center/scale = FALSE)
pca_fit <- prcomp(df_pca, center = FALSE, scale. = FALSE)

# Resumen
var_explicada <- (pca_fit$sdev)^2
prop_var <- var_explicada / sum(var_explicada)
var_acum <- cumsum(prop_var)

tabla_pca <- data.frame(
  Componente = paste0("PC", seq_along(prop_var)),
  Desviacion_Estandar = pca_fit$sdev,
  Varianza_Explicada = prop_var,
  Varianza_Acumulada = var_acum
)
mi_tabla(
  tabla_pca, 
  id="tabla_resumen", 
  titulo="Importancia de componentes de acuerdo con PCA")
Tabla 8. Importancia de componentes de acuerdo con PCA
Componente Desviacion_Estandar Varianza_Explicada Varianza_Acumulada
PC1 1.916 0.612 0.612
PC2 1.106 0.204 0.816
PC3 0.685 0.078 0.894
PC4 0.553 0.051 0.945
PC5 0.487 0.039 0.984
PC6 0.308 0.016 1.000

Interpretación El análisis de Componentes Principales (PCA) evidencia que la mayor parte de la información del conjunto de datos se concentra en los primeros componentes. En particular, el PC1 explica por sí solo el 61.2% de la variabilidad, y al considerar hasta el PC4 se alcanza aproximadamente el 94.5% de la varianza acumulada. Esto indica que es posible reducir significativamente la dimensionalidad del conjunto original sin perder información relevante.

3.2 Análisis de Conglomerados

Agrupar las propiedades residenciales en segmentos homogéneos con características similares para entender las dinámicas de las ofertas específicas en diferentes partes de la ciudad y en diferentes estratos socioeconómicos.

3.2.1 Selección de variables para clustering

Selecciona las variables para realizar el analisis:

# Variables numéricas estandarizadas para clustering
excluir <- intersect(names(df_vivienda_std), c("id", "latitud", "longitud", "preciom", "areaconst"))
df_cluster <- df_vivienda_std %>%
  select(where(is.numeric)) %>%
  select(-all_of(excluir))

# Vista rápida
mi_tabla(
  data.frame(variable = names(df_cluster)), 
  id="tabla_resumen", 
  titulo="Variables numericas seleccionadas para clustering")
Tabla 9. Variables numericas seleccionadas para clustering
variable
estrato
parqueaderos
banios
habitaciones
log_preciom
log_areaconst

3.2.2 Selección de numero de clusteres por metodos del codo y silueta

Se aplica los métodos de codo y silueta para determinar el número adecuado de clusteres para el análisis.

# Método del codo (WSS)
plot_codo <- fviz_nbclust(df_cluster, kmeans, method = "wss")
mi_figura(
  plot_codo,
  id     = "figura_resumen",
  titulo = "Método del codo para selección de número de clusteres"
)
Figura 4. Método del codo para selección de número de clusteres
# Silhouette
plot_silu <- fviz_nbclust(df_cluster, kmeans, method = "silhouette")
mi_figura(
  plot_silu,
  id     = "figura_resumen",
  titulo = "Método de la silueta para selección de número de clusteres"
)
Figura 5. Método de la silueta para selección de número de clusteres

El método de la silueta indica que la mejor segmentación se logra con k = 2 clusters, ya que en este punto se obtiene el mayor ancho promedio de silueta. Por su parte, el método del codo muestra un cambio de tendencia entre k = 3 y k = 4, lo que sugiere que a partir de allí las mejoras en la compactación de los grupos son cada vez menores. Al considerar ambos enfoques y buscando un modelo claro y fácil de interpretar, se decide trabajar con k = 3 como la alternativa para segmentar la oferta inmobiliaria.

3.2.3 Segmentación de los registros según el numero de clusteres determinado

Se segmenta el set de datos de oferta inmobiliarias (corregido) con k = 3. A continuación se presenta los clusteres con la frecuencia de ofertas inmobiliarias.

set.seed(1234)

k_opt <- 3  # <-- AJUSTA según Elbow/Silhouette

km_fit <- kmeans(df_cluster, centers = k_opt, nstart = 25)

# Añadir cluster al dataframe limpio (con categóricas)
df_vivienda_cluster <- df_vivienda_clean %>%
  mutate(cluster = factor(km_fit$cluster))

tabla_clusters <- as.data.frame(table(df_vivienda_cluster$cluster))
names(tabla_clusters) <- c("cluster", "frecuencia")
# Vista rápida
mi_tabla(
  tabla_clusters, 
  id="tabla_resumen", 
  titulo="Tamaño de cada segmento")
Tabla 10. Tamaño de cada segmento
cluster frecuencia
1 2683
2 4587
3 1049

3.2.4 Perfilamiento de clusteres

Se realiza el perfilamiento de cada cluster con las variables típicas del caso con estadistiscos principales (mediana y media).

res_cluster_num <- df_vivienda_cluster %>%
  group_by(cluster) %>%
  summarise(
    n = n(),
    # Variables típicas del caso (ajusta si deseas)
    preciom_mediana = median(preciom, na.rm = TRUE),
    areaconst_mediana = median(areaconst, na.rm = TRUE),
    parqueaderos_prom = mean(parqueaderos, na.rm = TRUE),
    banios_prom = mean(banios, na.rm = TRUE),
    habitaciones_prom = mean(habitaciones, na.rm = TRUE),
    estrato_mediana = median(estrato, na.rm = TRUE)
  ) %>%
  arrange(cluster)

mi_tabla(
  res_cluster_num |> tibble::as_tibble(), 
  id="tabla_resumen", 
  titulo="Perfilamiento de cada cluster")
Tabla 11. Perfilamiento de cada cluster
cluster n preciom_mediana areaconst_mediana parqueaderos_prom banios_prom habitaciones_prom estrato_mediana
1 2683 650 227 2.679 4.264 3.804 6
2 4587 230 84 1.185 2.177 2.892 4
3 1049 380 260 1.525 4.247 6.217 4

Interpretación El análisis de clusters permitió identificar tres segmentos diferenciados en la oferta inmobiliaria: (i) un segmento premium de alto valor y estrato elevado, caracterizado por mayor área y nivel de dotación; (ii) un segmento medio–económico que concentra la mayor cantidad de viviendas, con menor tamaño y precio; y (iii) un segmento de viviendas familiares amplias, con alto número de habitaciones y área construida, pero ubicado en estratos intermedios. Esta segmentación evidencia la coexistencia de mercados con diferentes niveles de valorización y tipología habitacional.

3.3 Análisis de Correspondencia

Examinar la relación entre las variables categóricas (tipo de vivienda, zona y barrio), para identificar patrones de comportamiento de la oferta en mercado inmobiliario.

3.3.1 Analisis de distribución por tipo de inmueble, zona y barrio

A continuación se presenta la distribución del tipo de inmueble, zona y barrio de las ofertas inmobliliarias (tabla corregida).

df_cat <- df_vivienda_clean %>%
  transmute(
    tipo  = as.factor(str_squish(tipo)),
    zona  = as.factor(str_squish(zona)),
    barrio = str_squish(barrio)
  ) %>%
  mutate(
    tipo = fct_explicit_na(tipo, na_level = "Sin informacion"),
    zona = fct_explicit_na(zona, na_level = "Sin informacion"),
    barrio = ifelse(is.na(barrio) | barrio == "", "Sin informacion", barrio)
  )

# Reducir cardinalidad de barrio (Top N + Otros)
top_n <- 20  # ajusta 15-30 según tu preferencia
top_barrios <- df_cat %>%
  count(barrio, sort = TRUE) %>%
  slice_head(n = top_n) %>%
  pull(barrio)

df_cat <- df_cat %>%
  mutate(
    barrio_rec = ifelse(barrio %in% top_barrios, barrio, "Otros"),
    barrio_rec = as.factor(barrio_rec)
  )

# Verificación rápida

mi_tabla(
  df_cat %>% count(tipo, name = "frecuencia"), 
  id="tabla_resumen", 
  titulo="Distribución por tipo de inmueble")
Tabla 12. Distribución por tipo de inmueble
tipo frecuencia
Apartamento 5100
Casa 3219
mi_tabla(
  df_cat %>% count(zona, name = "frecuencia"), 
  id="tabla_resumen", 
  titulo="Distribución por zona")
Tabla 13. Distribución por zona
zona frecuencia
Zona Centro 124
Zona Norte 1920
Zona Oeste 1198
Zona Oriente 351
Zona Sur 4726
mi_tabla(
  df_cat %>% count(barrio_rec, name = "frecuencia"), 
  id="tabla_resumen", 
  titulo="Distribución por barrio")
Tabla 14. Distribución por barrio
barrio_rec frecuencia
acopi 158
aguacatal 109
brisas de los 81
caney 88
ciudad 2000 95
ciudad jardín 516
cristales 83
el caney 208
el ingenio 202
el limonar 135
el refugio 120
la flora 366
la hacienda 164
los cristales 154
normandía 154
Otros 3798
pance 409
prados del norte 126
santa teresita 262
urbanización la flora 83
valle del lili 1008

3.3.2 Ejecución del analisis de correspondencia y prueba chi-cuadrado

Se realiza el analisis cruzado entre el tipo del inmueble versus las zonas.

tab_tipo_zona <- table(df_cat$tipo, df_cat$zona)
tab_tipo_zona_wide <- as.data.frame(tab_tipo_zona) %>%
  rename(tipo = Var1, zona = Var2, n = Freq) %>%
  pivot_wider(names_from = zona, values_from = n, values_fill = 0)
mi_tabla(
  tab_tipo_zona_wide, 
  id="tabla_resumen", 
  titulo="Tabla cruzada entre el tipo de inmueble versus las zonas")
Tabla 15. Tabla cruzada entre el tipo de inmueble versus las zonas
tipo Zona Centro Zona Norte Zona Oeste Zona Oriente Zona Sur
Apartamento 24 1198 1029 62 2787
Casa 100 722 169 289 1939

Interpretación Se realiza la prueba de independencia entre el tipo de inmueble y las zonas, donde el p-value tiende a cero, lo cual rechaza la prueba. Con esto, hay una dependencia fuerte entre el tipo de inmueble y las zonas.

chisq.test(tab_tipo_zona)
## 
##  Pearson's Chi-squared test
## 
## data:  tab_tipo_zona
## X-squared = 690.93, df = 4, p-value < 2.2e-16

Tambien Se realiza el analisis cruzado entre las zonas versus los barrios.

tab_zona_barrio <- table(df_cat$zona, df_cat$barrio_rec)
tab_zona_barrio_wide <- as.data.frame(tab_zona_barrio) %>%
  rename(zona = Var1, barrio_rec = Var2, n = Freq) %>%
  pivot_wider(names_from = barrio_rec, values_from = n, values_fill = 0)
mi_tabla(
  tab_zona_barrio_wide, 
  id="tabla_resumen", 
  titulo="Tabla cruzada entre las zonas versus los barrios")
Tabla 16. Tabla cruzada entre las zonas versus los barrios
zona acopi aguacatal brisas de los caney ciudad 2000 ciudad jardín cristales el caney el ingenio el limonar el refugio la flora la hacienda los cristales normandía Otros pance prados del norte santa teresita urbanización la flora valle del lili
Zona Centro 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 123 0 1 0 0 0
Zona Norte 157 0 81 0 0 3 1 0 0 0 0 365 0 0 0 1100 0 124 2 83 4
Zona Oeste 0 108 0 0 0 0 81 0 0 0 0 0 0 154 153 447 0 1 254 0 0
Zona Oriente 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 350 1 0 0 0 0
Zona Sur 1 1 0 88 95 513 1 208 202 135 120 1 164 0 1 1778 408 0 6 0 1004

Interpretación Se realiza la prueba de independencia entre las zonas y los barrios, donde el p-value tiende a cero, lo cual rechaza la prueba. Con esto, hay una dependencia fuerte entre las zonas y los barrios.

chisq.test(tab_zona_barrio)
## 
##  Pearson's Chi-squared test
## 
## data:  tab_zona_barrio
## X-squared = 9699.9, df = 80, p-value < 2.2e-16

4 Resultados Obtenidos

Presentar gráficos, mapas y otros recursos visuales para comunicar los hallazgos de manera clara y efectiva a la dirección de la empresa.

4.1 Resultados del PCA

Interpretación de componentes principales.

A continuación, se grafica la varianza explicada para los componentes del PCA, el analisis bivariado de los componentes principales y la contribución de las variables para cada componente principal.

var_exp <- (pca_fit$sdev^2) / sum(pca_fit$sdev^2)
df_scree <- data.frame(
  componente = seq_along(var_exp),
  varianza = var_exp
)

plot_vec <- ggplot(df_scree, aes(x = componente, y = varianza)) +
  geom_line() +
  geom_point() +
  scale_x_continuous(breaks = df_scree$componente) +
  labs(
    x = "Componente principal",
    y = "Proporción de varianza explicada"
  )

mi_figura(
  plot_vec,
  id     = "figura_resumen",
  titulo = "Varianza explicada por componente"
)
Figura 6. Varianza explicada por componente
plot_pca_bi <- fviz_pca_biplot(
  pca_fit,
  col.var = "black",
  col.ind = "gray70"
)

mi_figura(
  plot_pca_bi,
  id     = "figura_resumen",
  titulo = "Analisis bivariado de componentes PC1 versus PC2 para individuos y variables"
)
Figura 7. Analisis bivariado de componentes PC1 versus PC2 para individuos y variables
# Variables que más contribuyen a PC1 y PC2
plot_con_pc1 <- fviz_contrib(pca_fit, choice = "var", axes = 1, top = 10)

mi_figura(
  plot_con_pc1,
  id     = "figura_resumen",
  titulo = "Top variables que más contribuyen a PC1"
)
Figura 8. Top variables que más contribuyen a PC1
plot_con_pc2 <- fviz_contrib(pca_fit, choice = "var", axes = 2, top = 10)

mi_figura(
  plot_con_pc2,
  id     = "figura_resumen",
  titulo = "Top variables que más contribuyen a PC2"
)
Figura 9. Top variables que más contribuyen a PC2

Interpretación general El análisis de componentes principales (PCA) permitió reducir la dimensionalidad del conjunto de variables numéricas estandarizadas, identificando un conjunto pequeño de componentes que explican la mayor parte de la variabilidad observada en la oferta inmobiliaria.

Las contribuciones de variables a las primeras componentes sugieren cuáles características del inmueble y del mercado (por ejemplo, asociadas a tamaño, dotación y nivel socioeconómico) influyen con mayor peso en la variación de precios y en la estructura general de la oferta. La visualización en el plano PC1–PC2 facilita la identificación de patrones y posibles segmentos, los cuales serán explorados posteriormente mediante análisis de conglomerados.

4.2 Resultados del Clustering

Descripción de segmentos encontrados.

Con la tabla del analisis de conglomerados, se realiza la visualización de los clusteres con los dos componentes principales del analisis PCA.

# PCA solo para visualizar (sobre el mismo df_cluster usado en kmeans)
pca_vis <- prcomp(df_cluster, center = FALSE, scale. = FALSE)

p_clusteres_PCA <- fviz_pca_ind(
  pca_vis,
  geom.ind = "point",
  habillage = df_vivienda_cluster$cluster,
  addEllipses = TRUE,
  ellipse.level = 0.95,
  alpha.ind = 0.5
)

mi_figura(
  p_clusteres_PCA,
  id     = "figura_resumen",
  titulo = "Clusters proyectados en el plano PCA con las dos componentes principales"
)
Figura 10. Clusters proyectados en el plano PCA con las dos componentes principales

Interpretación de la segmentación en plano PCA El plano PCA evidencia una segmentación coherente del mercado inmobiliario en tres grupos con perfiles diferenciados, aunque con solapamientos moderados propios de la heterogeneidad del mercado.

tab_zona <- df_vivienda_cluster %>%
  count(zona, cluster) %>%
  group_by(zona) %>%
  mutate(pct = round(100 * n / sum(n), 2)) %>%
  arrange(zona, desc(pct))

p_clusteres_zona <- ggplot(tab_zona, aes(x = zona, y = pct, fill = cluster)) +
  geom_col() +
  labs(
    x = "Zona",
    y = "% dentro de cada zona"
  ) +
  theme(axis.text.x = element_text(angle = 25, hjust = 1))

mi_figura(
  p_clusteres_zona,
  id     = "figura_resumen",
  titulo = "Composición porcentual de clusters por zona"
)
Figura 11. Composición porcentual de clusters por zona

Interpretación de los clusteres por zona La composición porcentual por zona muestra que el mercado inmobiliario presenta diferencias espaciales claras. La Zona Oeste concentra una mayor proporción de viviendas pertenecientes al cluster de mayor valorización, mientras que las Zonas Norte y Sur están principalmente dominadas por el segmento medio–económico. En contraste, las Zonas Centro y Oriente exhiben una distribución más equilibrada y diversa entre los distintos tipos de vivienda. En conjunto, estos hallazgos confirman que el comportamiento del mercado inmobiliario varía de manera importante según la ubicación geográfica.

tab_estrato <- df_vivienda_cluster %>%
  count(estrato, cluster) %>%
  group_by(estrato) %>%
  mutate(pct = round(100 * n / sum(n), 2)) %>%
  arrange(estrato, desc(pct))

p_clusteres_estrato <- ggplot(tab_estrato, aes(x = factor(estrato), y = pct, fill = cluster)) +
  geom_col() +
  labs(
    x = "Estrato",
    y = "% dentro de cada estrato"
  )

mi_figura(
  p_clusteres_estrato,
  id     = "figura_resumen",
  titulo = "Composición porcentual de clusters por estrato"
)
Figura 12. Composición porcentual de clusters por estrato

Interpretación de los clusteres por estrato La composición porcentual por estrato evidencia una clara relación entre la segmentación obtenida y el nivel socioeconómico de las viviendas. El estrato 6 está dominado por el cluster de mayor valorización, mientras que los estratos 3 y 4 concentran principalmente el segmento medio–económico. Por su parte, el estrato 5 presenta una estructura de transición entre ambos mercados. Estos resultados confirman la coherencia del modelo de clustering con la estratificación socioeconómica del mercado inmobiliario.

Interpretación general El análisis de conglomerados permitió segmentar las propiedades en 3 grupos. Los perfiles por cluster evidencian diferencias en variables asociadas al tamaño del inmueble, dotación (baños, parqueaderos, habitaciones) y nivel socioeconómico (estrato), lo que sugiere la existencia de segmentos de mercado diferenciados.

La distribución de clusters por zona y estrato revela dinámicas específicas del mercado: ciertos segmentos se concentran en determinadas zonas y estratos, lo que puede orientar estrategias de valoración, compra/venta y focalización comercial.

4.3 Resultados del Análisis de Correspondencia

Relaciones entre variables categóricas.

Con la tabla cruzada entre las zonas y los barrios, se visualiza el analisis de correspondencia de estas variables.

ca_zona_barrio <- CA(tab_zona_barrio, graph = FALSE)

p_bi_zona_barrio <- fviz_ca_biplot(
  ca_zona_barrio,
  repel = TRUE
)

mi_figura(
  p_bi_zona_barrio,
  id     = "figura_resumen",
  titulo = "Análisis de Correspondencia: Zona vs Barrio"
)
Figura 13. Análisis de Correspondencia: Zona vs Barrio

A continuación, se visualiza la contribución por zonas y barrios a la dimensión 1.

p_con_zona_dim1 <- fviz_contrib(ca_zona_barrio, choice = "row", axes = 1, top = 10)

mi_figura(
  p_con_zona_dim1,
  id     = "figura_resumen",
  titulo = "Contribución por zonas a dim 1"
)
Figura 14. Contribución por zonas a dim 1
p_con_barrio_dim1 <- fviz_contrib(ca_zona_barrio, choice = "col", axes = 1, top = 10)

mi_figura(
  p_con_barrio_dim1,
  id     = "figura_resumen",
  titulo = "Contribución por barrios a dim 1"
)
Figura 15. Contribución por barrios a dim 1

Interpretación El Análisis de Correspondencia permitió explorar asociaciones entre variables categóricas del mercado inmobiliario. Las pruebas chi-cuadrado complementarias evalúan si existe dependencia estadística entre las categorías.

En los mapas factoriales, categorías cercanas indican patrones de asociación (por ejemplo, ciertos barrios se asocian más fuertemente con zonas específicas). Las contribuciones a las dimensiones ayudan a identificar qué categorías explican la mayor parte de la variabilidad representada en el plano.

5 Conclusiones y recomendaciones

Síntesis de hallazgos clave y recomendaciones estratégicas para la empresa inmobiliaria.

5.1 Conclusiones

El mercado inmobiliario analizado presenta una estructura claramente segmentada, donde el precio y el área construida actúan como ejes dominantes de diferenciación, mientras que la ubicación geográfica y el estrato socioeconómico determinan patrones espaciales de la oferta. La empresa dispone de suficiente información para implementar estrategias comerciales diferenciadas por segmento y territorio.

5.2 Recomendaciones

  1. Implementar una estrategia comercial por segmentos

    Acciones recomendadas

    1. Diseñar portafolios específicos para:

      • Segmento premium

      • Segmento medio

      • Segmento económico

    2. Personalizar mensajes y canales de venta por segmento.

    3. Ajustar metas comerciales por tipo de cliente objetivo.

  2. Priorizar la gestión territorial del inventario

    Acciones recomendadas

    1. Concentrar captación de inmuebles de alto valor según zonas.

    2. Definir precios de referencia diferenciados por zona.

    3. Implementar campañas de marketing hiperlocal.

Mensaje ejecutivo La empresa cuenta con una estructura de datos que permite evolucionar desde un enfoque descriptivo hacia una estrategia inmobiliaria basada en analítica avanzada, donde la segmentación, el pricing inteligente y la focalización geográfica serán los principales motores de ventaja competitiva.

LS0tDQp0aXRsZTogIioqRXZhbHVhY2nDs24gZGUgb2ZlcnRhIGlubW9iaWxpYXJpYSBtZWRpYW50ZSBtb2RlbG9zIG11bHRpdmFyaWFkb3MqKiINCmF1dGhvcjogIlJhZmFlbCBKb3NlIERlbCBDYXN0aWxsbyBQYXZhamVhdSINCmRhdGU6ICIyMDI2LTAyLTAyIg0Kb3V0cHV0Og0KICBodG1sX2RvY3VtZW50Og0KICAgIHRvYzogdHJ1ZQ0KICAgIHRvY19kZXB0aDogMg0KICAgIHRvY19mbG9hdDoNCiAgICAgIGNvbGxhcHNlZDogZmFsc2UNCiAgICAgIHNtb290aF9zY3JvbGw6IHRydWUNCiAgICBudW1iZXJfc2VjdGlvbnM6IHRydWUNCiAgICB0aGVtZTogY29zbW8NCiAgICBjb2RlX2ZvbGRpbmc6IGhpZGUNCiAgICBkZl9wcmludDogcGFnZWQNCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlDQpsYW5ndWFnZToNCiAgbGFiZWw6DQogICAgZmlnOiAiRmlndXJhICINCiAgICB0YWI6ICJUYWJsYSAiDQpwYXJhbXM6DQogIHNlZWQ6IDg5NzI3OTEgI2NvZGlnbyBlc3R1ZGlhbnRlDQplZGl0b3Jfb3B0aW9uczogDQogIGNodW5rX291dHB1dF90eXBlOiBjb25zb2xlDQotLS0NCg0KYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9DQojIFJlcG9zaXRvcmlvIENSQU4gKGVzdGFibGUpDQpvcHRpb25zKHJlcG9zID0gYyhDUkFOID0gImh0dHBzOi8vY2xvdWQuci1wcm9qZWN0Lm9yZyIpKQ0KDQojIE9wY2lvbmVzIGdsb2JhbGVzIGRlIGtuaXRyICh1bmEgc29sYSB2ZXopDQprbml0cjo6b3B0c19jaHVuayRzZXQoDQogIGVjaG8gPSBUUlVFLA0KICBtZXNzYWdlID0gRkFMU0UsDQogIHdhcm5pbmcgPSBGQUxTRSwNCiAgZmlnLmFsaWduID0gImNlbnRlciIsDQogIGZpZy53aWR0aCA9IDcsDQogIGZpZy5oZWlnaHQgPSA0LjgsDQogIGRwaSA9IDE1MA0KKQ0KDQojIFNlZWQ6IHVzYSBwYXJhbXMkc2VlZCBzaSBleGlzdGU7IHNpIG5vLCB1c2EgdW5vIGZpam8NCnNlZWRfdmFsdWUgPC0gaWYgKGV4aXN0cygicGFyYW1zIikgJiYgIWlzLm51bGwocGFyYW1zJHNlZWQpKSBwYXJhbXMkc2VlZCBlbHNlIDEyMzQNCnNldC5zZWVkKHNlZWRfdmFsdWUpDQoNCiMgSW5zdGFsYXIgKHNpIGZhbHRhKSB5IGNhcmdhciBwYXF1ZXRlcw0KdXNhcl9wYXF1ZXRlcyA8LSBmdW5jdGlvbihwYXF1ZXRlcykgew0KICBmYWx0YW4gPC0gcGFxdWV0ZXNbIXZhcHBseShwYXF1ZXRlcywgcmVxdWlyZU5hbWVzcGFjZSwgRlVOLlZBTFVFID0gbG9naWNhbCgxKSwgcXVpZXRseSA9IFRSVUUpXQ0KICBpZiAobGVuZ3RoKGZhbHRhbikpIGluc3RhbGwucGFja2FnZXMoZmFsdGFuLCBkZXBlbmRlbmNpZXMgPSBUUlVFKQ0KICBpbnZpc2libGUobGFwcGx5KHBhcXVldGVzLCBsaWJyYXJ5LCBjaGFyYWN0ZXIub25seSA9IFRSVUUpKQ0KfQ0KDQojIFBhcXVldGVzIChzaW4gcmVkdW5kYW5jaWFzIGRlbCB0aWR5dmVyc2UpDQp1c2FyX3BhcXVldGVzKGMoDQogICJ0aWR5dmVyc2UiLA0KICAia25pdHIiLA0KICAia2FibGVFeHRyYSIsDQogICJncmlkRXh0cmEiLA0KICAicGF0Y2h3b3JrIiwNCiAgImJyb29tIiwNCiAgIm5vcnRlc3QiLA0KICAiYm9vdCIsDQogICJnZ2NvcnJwbG90IiwNCiAgIkdHYWxseSIsDQogICJkcGx5ciIsDQogICJ0aWR5ciIsDQogICJzdHJpbmdyIiwNCiAgImdncGxvdDIiLA0KICAiZmFjdG9leHRyYSIsDQogICJGYWN0b01pbmVSIiwNCiAgInBzeWNoIg0KKSkNCg0KIyBJbmZvIGRlbCBlbnRvcm5vICjDunRpbCBwYXJhIGRlcHVyYXIgcGF0aHMpDQpjYXQoIkRpcmVjdG9yaW8gZGUgdHJhYmFqbzpcbiIpOyBwcmludChnZXR3ZCgpKQ0KY2F0KCJcbkFyY2hpdm9zIGVuIGxhIGNhcnBldGEgcmHDrXo6XG4iKTsgcHJpbnQoZGlyKCkpDQpgYGANCg0KYGBge3IgdGFibGFzLCBpbmNsdWRlPUZBTFNFfQ0KIyBHZW5lcmFkb3IgZGUgZnVuY2nDs24gY29uIGNvbnRhZG9yIGludGVybm8gKGV2aXRhIDw8LSkNCmNyZWFyX21pX3RhYmxhIDwtIGZ1bmN0aW9uKCkgew0KICB0YWJfbiA8LSAwTA0KICANCiAgZnVuY3Rpb24oZGYsIGlkID0gTlVMTCwgdGl0dWxvID0gIiIsIGRpZ2l0cyA9IDMsDQogICAgICAgICAgIG51bV9jb2xzID0gZHBseXI6OndoZXJlKGlzLm51bWVyaWMpLA0KICAgICAgICAgICBhbGlnbiA9ICJjIiwNCiAgICAgICAgICAgYm9vdHN0cmFwX29wdGlvbnMgPSBjKCJzdHJpcGVkIiwgImhvdmVyIiwgImNvbmRlbnNlZCIsICJyZXNwb25zaXZlIikpIHsNCiAgICANCiAgICB0YWJfbiA8PC0gdGFiX24gKyAxTA0KICAgIA0KICAgICMgaWQgYXV0b23DoXRpY28gc2kgbm8gc2UgZW50cmVnYSAoc2VndXJvIHBhcmEgSFRNTCkNCiAgICBpZiAoaXMubnVsbChpZCkgfHwgIW56Y2hhcihpZCkpIHsNCiAgICAgIGlkIDwtIHBhc3RlMCgidGFiXyIsIHRhYl9uLCAiXyIsDQogICAgICAgICAgICAgICAgICAgZ3N1YigiW15hLXpBLVowLTldKyIsICJfIiwgdG9sb3dlcih0aXR1bG8pKSkNCiAgICAgIGlkIDwtIGdzdWIoIl8rIiwgIl8iLCBpZCkNCiAgICAgIGlkIDwtIGdzdWIoIl5ffF8kIiwgIiIsIGlkKQ0KICAgIH0NCiAgICANCiAgICAjIEZvcm1hdGVvIG51bcOpcmljbyAoc29sbyBjb2x1bW5hcyBzZWxlY2Npb25hZGFzKQ0KICAgIGRmX2ZtdCA8LSBkZiB8Pg0KICAgICAgZHBseXI6Om11dGF0ZShkcGx5cjo6YWNyb3NzKHt7IG51bV9jb2xzIH19LCB+IHJvdW5kKC54LCBkaWdpdHMpKSkNCiAgICANCiAgICBjYXBfaHRtbCA8LSBzcHJpbnRmKA0KICAgICAgJzxkaXYgaWQ9IiVzIiBzdHlsZT0ibWFyZ2luOjAgMCA2cHggMDsiPjxzdHJvbmc+VGFibGEgJWQuPC9zdHJvbmc+ICVzPC9kaXY+JywNCiAgICAgIGlkLCB0YWJfbiwgdGl0dWxvDQogICAgKQ0KICAgIA0KICAgIHRibF9odG1sIDwtIGRmX2ZtdCB8Pg0KICAgICAga25pdHI6OmthYmxlKGFsaWduID0gYWxpZ24sIGVzY2FwZSA9IEZBTFNFKSB8Pg0KICAgICAga2FibGVFeHRyYTo6a2FibGVfc3R5bGluZygNCiAgICAgICAgZnVsbF93aWR0aCA9IEZBTFNFLA0KICAgICAgICBib290c3RyYXBfb3B0aW9ucyA9IGJvb3RzdHJhcF9vcHRpb25zDQogICAgICApDQogICAgDQogICAga25pdHI6OmFzaXNfb3V0cHV0KHBhc3RlMChjYXBfaHRtbCwgIlxuXG4iLCB0YmxfaHRtbCkpDQogIH0NCn0NCg0KIyBGdW5jacOzbiBsaXN0YSBwYXJhIHVzYXIgZW4gdG9kbyBlbCBkb2N1bWVudG8NCm1pX3RhYmxhIDwtIGNyZWFyX21pX3RhYmxhKCkNCmBgYA0KDQpgYGB7ciBmaWd1cmFzLCBpbmNsdWRlPUZBTFNFIH0NCiMgLS0tIEZ1bmNpw7NuIGdsb2JhbCBwYXJhIGZpZ3VyYXMgLS0tDQppZiAoIWV4aXN0cygiLkZJR19OIikpIC5GSUdfTiA8PC0gMA0KDQptaV9maWd1cmEgPC0gZnVuY3Rpb24ocGxvdCwgaWQsIHRpdHVsbyl7DQogIC5GSUdfTiA8PC0gLkZJR19OICsgMQ0KICBwcmludChwbG90KSAgIyBkaWJ1amEgbGEgZmlndXJhDQogIGNhcF9odG1sIDwtIHNwcmludGYoDQogICAgJzxkaXYgaWQ9IiVzIiBzdHlsZT0idGV4dC1hbGlnbjpjZW50ZXI7IG1hcmdpbi10b3A6NnB4OyI+PHN0cm9uZz5GaWd1cmEgJWQuPC9zdHJvbmc+ICVzPC9kaXY+JywNCiAgICBpZCwgLkZJR19OLCB0aXR1bG8NCiAgKQ0KICBrbml0cjo6YXNpc19vdXRwdXQoY2FwX2h0bWwpDQp9DQpgYGANCg0KIyAqKkludHJvZHVjY2nDs24qKg0KDQpFbCBwcm9ww7NzaXRvIGRlIGVzdGEgYWN0aXZpZGFkIGVzIGFuYWxpemFyIGRlIG1hbmVyYSBpbnRlZ3JhbCBsYSBvZmVydGEgZGUgdml2aWVuZGEgdXJiYW5hIHBhcmEgZW50ZW5kZXIgbWVqb3IgY8OzbW8gc2UgZXN0w6EgY29tcG9ydGFuZG8gZWwgbWVyY2FkbyBpbm1vYmlsaWFyaW8geSBhc8OtIGFwb3lhciBsYSB0b21hIGRlIGRlY2lzaW9uZXMgZXN0cmF0w6lnaWNhcyBkZSBsYSBlbXByZXNhLiBBIHBhcnRpciBkZSB1bmEgYmFzZSBkZSBkYXRvcyBhbXBsaWEgZGUgcHJvcGllZGFkZXMgZW4gb2ZlcnRhLCBzZSBidXNjYSBkZXNjdWJyaXIgcGF0cm9uZXMsIHJlbGFjaW9uZXMgeSBzZWdtZW50YWNpb25lcyBjbGF2ZSBxdWUgYXl1ZGVuIGEgb3JpZW50YXIgbG9zIHByb2Nlc29zIGRlIHZhbG9yYWNpw7NuLCBjb21wcmEgeSB2ZW50YSwgYXPDrSBjb21vIGEgY29tcHJlbmRlciBsYXMgZGluw6FtaWNhcyBkZWwgbWVyY2FkbyBzZWfDum4gbGEgem9uYSB5IGVsIGVzdHJhdG8uDQoNCiMgKipQcm9ibGVtYSB5IHByZXBhcmFjacOzbiBkZSBsb3MgZGF0b3MqKg0KDQojIyBEZXNjcmlwY2nDs24gZGVsIHByb2JsZW1hDQoNClVuYSBlbXByZXNhIGlubW9iaWxpYXJpYSBsw61kZXIgZW4gdW5hIGdyYW4gY2l1ZGFkIGVzdMOhIGJ1c2NhbmRvIGNvbXByZW5kZXIgZW4gcHJvZnVuZGlkYWQgZWwgbWVyY2FkbyBkZSB2aXZpZW5kYXMgdXJiYW5hcyBwYXJhIHRvbWFyIGRlY2lzaW9uZXMgZXN0cmF0w6lnaWNhcyBtw6FzIGluZm9ybWFkYXMuIExhIGVtcHJlc2EgcG9zZWUgdW5hIGJhc2UgZGUgZGF0b3MgZXh0ZW5zYSBxdWUgY29udGllbmUgaW5mb3JtYWNpw7NuIGRldGFsbGFkYSBzb2JyZSBkaXZlcnNhcyBwcm9waWVkYWRlcyByZXNpZGVuY2lhbGVzIGRpc3BvbmlibGVzIGVuIGVsIG1lcmNhZG8uIFNlIHJlcXVpZXJlIHJlYWxpemFyIHVuIGFuw6FsaXNpcyBob2zDrXN0aWNvIGRlIGVzdG9zIGRhdG9zIHBhcmEgaWRlbnRpZmljYXIgcGF0cm9uZXMsIHJlbGFjaW9uZXMgeSBzZWdtZW50YWNpb25lcyByZWxldmFudGVzIHF1ZSBwZXJtaXRhbiBtZWpvcmFyIGxhIHRvbWEgZGUgZGVjaXNpb25lcyBlbiBjdWFudG8gYSBsYSBjb21wcmEsIHZlbnRhIHkgdmFsb3JhY2nDs24gZGUgcHJvcGllZGFkZXMuDQoNCiMjIERlc2NyaXBjacOzbiBkZSBsb3MgZGF0b3MNCg0KTGEgYmFzZSBkZSBkYXRvcyByZcO6bmUgaW5mb3JtYWNpw7NuIGRlIHZpdmllbmRhcyByZXNpZGVuY2lhbGVzIHF1ZSBhY3R1YWxtZW50ZSBlc3TDoW4gZW4gb2ZlcnRhIGRlbnRybyBkZWwgbWVyY2FkbyB1cmJhbm8uIEluY2x1eWUgdGFudG8gdmFyaWFibGVzIG51bcOpcmljYXMgY29tbyBjYXRlZ8OzcmljYXMgcmVsYWNpb25hZGFzIGNvbiBlbCBwcmVjaW8sIGxhcyBjYXJhY3RlcsOtc3RpY2FzIGbDrXNpY2FzIGRlbCBpbm11ZWJsZSwgc3UgdWJpY2FjacOzbiB5IGxhIGNsYXNpZmljYWNpw7NuIHNvY2lvZWNvbsOzbWljYSwgbG8gcXVlIHBlcm1pdGUgdGVuZXIgdW5hIHZpc2nDs24gbcOhcyBjb21wbGV0YSBkZWwgY29tcG9ydGFtaWVudG8gZGUgbGFzIHByb3BpZWRhZGVzLg0KDQojIyMgRnVlbnRlIGRlIGRhdG9zIA0KDQpMb3MgZGF0b3MgcHJvdmllbmVuIGRlIHVuIHByb2Nlc28gZGUgbGV2YW50YW1pZW50byBkZSBpbmZvcm1hY2nDs24gZGVsIG1lcmNhZG8gZGUgdml2aWVuZGEgZW4gb2ZlcnRhIHkgaGFuIHNpZG8gY29uc29saWRhZG9zIGVuIHVuYSBlc3RydWN0dXJhIGRlcHVyYWRhIHkgb3JnYW5pemFkYSwgbGlzdGEgcGFyYSBzdSBhbsOhbGlzaXMgZXN0YWTDrXN0aWNvIG11bHRpdmFyaWFkby4NCg0KYGBge3IgY2FyZ2EtZGF0b3MsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQojIEluc3RhbGFyIHBhcXVldGUgc2kgbm8gZXN0w6EgKHNvbG8gbGEgcHJpbWVyYSB2ZXopDQojIGluc3RhbGwucGFja2FnZXMoImRldnRvb2xzIikNCiMgZGV2dG9vbHM6Omluc3RhbGxfZ2l0aHViKCJjZW50cm8tbWFnaXMvcGFxdWV0ZU1PREVMT1MiKQ0KDQpsaWJyYXJ5KHBhcXVldGVNT0RFTE9TKQ0KDQojIENhcmdhciBkYXRhc2V0IChhanVzdGEgZWwgbm9tYnJlIHNpIGVzIGRpZmVyZW50ZSkNCmRhdGEoInZpdmllbmRhIikNCg0KIyBDcmVhciBkYXRhZnJhbWUgZGUgdHJhYmFqbw0KZGZfdml2aWVuZGEgPC0gdml2aWVuZGENCg0KZGZfdml2aWVuZGEgPC0gZGZfdml2aWVuZGEgfD4NCiAgdGliYmxlOjphc190aWJibGUoKQ0KDQojIFZpc3RhIHLDoXBpZGENCm1pX3RhYmxhKA0KICBoZWFkKGRmX3ZpdmllbmRhLCA1KSwgDQogIGlkPSJ0YWJsYV9yZXN1bWVuIiwgDQogIHRpdHVsbz0iVGFibGEgZGUgb2ZlcnRhcyBpbm1vYmlsaWFyaWFzIChyZWdpc3Ryb3MgaW5pY2lhbGVzKSIpDQpgYGANCg0KIyMjIFZhcmlhYmxlcyBpbmNsdWlkYXMNCg0KQSBuaXZlbCBnZW5lcmFsLCBzZSBjb25zaWRlcmFuIHZhcmlhYmxlcyBjb21vOg0KDQotICoqVWJpY2FjacOzbjoqKiB6b25hLCBiYXJyaW8sIGxhdGl0dWQsIGxvbmdpdHVkLg0KDQotICoqQ2FyYWN0ZXLDrXN0aWNhcyBkZWwgaW5tdWVibGU6Kiogw6FyZWEgY29uc3RydWlkYSwgbsO6bWVybyBkZSBoYWJpdGFjaW9uZXMsIGJhw7FvcywgcGFycXVlYWRlcm9zLCBwaXNvLg0KDQotICoqQ29udGV4dG8gc29jaW9lY29uw7NtaWNvOioqIGVzdHJhdG8uDQoNCi0gKipWYXJpYWJsZSBkZSB2YWxvcjoqKiBwcmVjaW8gKHZhbG9yIGNvbWVyY2lhbCB1IG9mZXJ0YWRvKS4NCg0KLSAqKlRpcG8gZGUgaW5tdWVibGU6KiogY2FzYSBvIGFwYXJ0YW1lbnRvLg0KDQojIyMgQ2FsaWRhZCBkZSBkYXRvcw0KDQpBbnRlcyBkZSBhcGxpY2FyIG1vZGVsb3MsIHNlIHJldmlzYXLDoToNCg0KIyMjIyBQcmVzZW5jaWEgZGUgdmFsb3JlcyBmYWx0YW50ZXMuDQpgYGB7ciBuYS1kaWFnbm9zdGljb30NCiMgVG90YWwgZGUgdmFsb3JlcyBmYWx0YW50ZXMgZW4gZWwgZGF0YXNldA0KdG90YWxfbmEgPC0gc3VtKGlzLm5hKGRmX3ZpdmllbmRhKSkNCmBgYA0KLSAqKkRpYWduw7NzdGljbyBkZWwgdG90YWwgZGUgZGF0b3MgZmFsdGFudGVzKio6IFNlIGlkZW50aWZpY2EgKipgciB0b3RhbF9uYWAqKiBkYXRvcyBmYWx0YW50ZXMgZW4gbGEgdGFibGEgZGUgb2ZlcnRhcyBpbm1vYmlsaWFyaWFzLg0KDQotICoqRGlhZ27Ds3N0aWNvIGRlIGxvcyBkYXRvcyBmYWx0YW50ZXMgcG9yIHZhcmlhYmxlKio6IEEgY29udGludWFjacOzbiBzZSBwcmVzZW50YW4gbG9zIGRhdG9zIGZhbHRhbnRlcyBwYXJhIGNhZGEgdmFyaWFibGUuDQpgYGB7ciBuYS1wb3ItdmFyaWFibGV9DQojIENvbnRlbyBkZSBOQSBwb3IgdmFyaWFibGUNCm5hX2NvdW50IDwtIGNvbFN1bXMoaXMubmEoZGZfdml2aWVuZGEpKQ0KDQojIENvbnN0cnVpciBkYXRhZnJhbWUgcmVzdW1lbg0KZGZfbmFfcmVzdW1lbiA8LSBkYXRhLmZyYW1lKA0KICB2YXJpYWJsZSA9IG5hbWVzKG5hX2NvdW50KSwNCiAgY2FudGlkYWRfZmFsdGFudGVzID0gYXMuaW50ZWdlcihuYV9jb3VudCksDQogIHBvcmNlbnRhamVfZmFsdGFudGVzID0gcm91bmQoMTAwICogbmFfY291bnQgLyBucm93KGRmX3ZpdmllbmRhKSwgMikNCikgfD4NCiAgZHBseXI6OmFycmFuZ2UoZGVzYyhwb3JjZW50YWplX2ZhbHRhbnRlcykpDQoNCmRmX25hX3Jlc3VtZW4gPC0gZGZfbmFfcmVzdW1lbiB8Pg0KICB0aWJibGU6OmFzX3RpYmJsZSgpDQoNCm1pX3RhYmxhKA0KICBkZl9uYV9yZXN1bWVuLCANCiAgaWQ9InRhYmxhX3Jlc3VtZW4iLCANCiAgdGl0dWxvPSJSZWdpc3Ryb3MgZmFsdGFudGUgcG9yIHZhcmlhYmxlIGVuIHRhYmxhIGRlIG9mZXJ0YXMgaW5tb2JpbGlhcmlhcyIpDQoNCmBgYA0KDQojIyMjIENvcnJlY2Npw7NuIGRlIGxvcyBkYXRvcyBwb3IgdmFyaWFibGUuDQoNClNlIGFwbGljYSB1bmEgZXN0cmF0ZWdpYSBkZSBsaW1waWV6YSBlIGltcHV0YWNpw7NuIGRlIHZhbG9yZXMgZmFsdGFudGVzIGFqdXN0YWRhIGFsIGNvbnRleHRvIGRlIGxhcyBvZmVydGFzIGlubW9iaWxpYXJpYXMuIFByaW1lcm8sIHNlIG5vcm1hbGl6YW4gbGFzIHZhcmlhYmxlcyBkZSB0ZXh0byBjb252aXJ0aWVuZG8gbGFzIGNhZGVuYXMgdmFjw61hcyBlbiB2YWxvcmVzIGZhbHRhbnRlcyByZWFsZXMgeSBzZSBjcmVhbiBpbmRpY2Fkb3JlcyAocGlzb19taXNzaW5nIHkgcGFycXVlYWRlcm9zX21pc3NpbmcpIHBhcmEgY29uc2VydmFyIGxhIGluZm9ybWFjacOzbiBzb2JyZSBsYSBhdXNlbmNpYSBkZSBkYXRvcy4gTHVlZ28gc2UgZWxpbWluYW4gbG9zIHJlZ2lzdHJvcyBjb24gdmFsb3JlcyBmYWx0YW50ZXMgZW4gdmFyaWFibGVzIGNyw610aWNhcyBjb21vIHByZWNpbywgw6FyZWEgeSBjb29yZGVuYWRhcywgcG9yIHNlciBmdW5kYW1lbnRhbGVzIHBhcmEgZWwgYW7DoWxpc2lzLiBQb3N0ZXJpb3JtZW50ZSwgbGEgdmFyaWFibGUgcGlzbyBzZSBpbXB1dGEgY29uIGxhIGNhdGVnb3LDrWEg4oCcU2luIGluZm9ybWFjacOzbuKAnSB5IHBhcnF1ZWFkZXJvcyBzZSBjb3JyaWdlIHV0aWxpemFuZG8gbGEgbWVkaWFuYSBwb3IgZ3J1cG8gKHRpcG8geSBlc3RyYXRvKSwgcmVzcGV0YW5kbyBhc8OtIGxhIGVzdHJ1Y3R1cmEgZGVsIG1lcmNhZG87IHNpIGHDum4gcXVlZGFuIGZhbHRhbnRlcywgc2UgYXBsaWNhIHVuYSBzZWd1bmRhIGltcHV0YWNpw7NuIGdsb2JhbC4gRmluYWxtZW50ZSwgbGFzIGRlbcOhcyB2YXJpYWJsZXMgbnVtw6lyaWNhcyBzZSBjb21wbGV0YW4gY29uIGxhIG1lZGlhbmEgeSBsYXMgY2F0ZWfDs3JpY2FzIGNvbiB1bmEgZXRpcXVldGEgZXhwbMOtY2l0YSwgZGVqYW5kbyBlbCBkYXRhc2V0IGxpbXBpbywgY29uc2lzdGVudGUgeSBsaXN0byBwYXJhIGFwbGljYXIgdMOpY25pY2FzIG11bHRpdmFyaWFkYXMgY29tbyBQQ0EsIGNsdXN0ZXJpbmcgeSBhbsOhbGlzaXMgZGUgY29ycmVzcG9uZGVuY2lhLg0KYGBge3IgbmEtY29ycmVjY2lvbi1vcHRpbWF9DQpkZl92aXZpZW5kYV9jbGVhbiA8LSBkZl92aXZpZW5kYSAlPiUNCiAgIyAxKSBOb3JtYWxpemFyIHN0cmluZ3MgdmFjw61vcyAoIiIgLT4gTkEpIGVuIGNhcmFjdGVyZXMNCiAgbXV0YXRlKGFjcm9zcyh3aGVyZShpcy5jaGFyYWN0ZXIpLCB+IG5hX2lmKHN0cl9zcXVpc2goLngpLCAiIikpKSAlPiUNCiAgDQogICMgMikgQ3JlYXIgYmFuZGVyYXMgZGUgbWlzc2luZyBwYXJhIHZhcmlhYmxlcyBjbGF2ZSAow7p0aWwgZW4gbW9kZWxvcykNCiAgbXV0YXRlKA0KICAgIHBpc29fbWlzc2luZyA9IGlzLm5hKHBpc28pLA0KICAgIHBhcnF1ZWFkZXJvc19taXNzaW5nID0gaXMubmEocGFycXVlYWRlcm9zKQ0KICApICU+JQ0KICANCiAgIyAzKSBFbGltaW5hciByZWdpc3Ryb3MgY29uIE5BIGVuIHZhcmlhYmxlcyAiY3LDrXRpY2FzIiAoc29uIG11eSBwb2NvcykNCiAgIyBBanVzdGEgbGEgbGlzdGEgc2kgcXVpZXJlcyBpbmNsdWlyL3F1aXRhciBjYW1wb3MgY3LDrXRpY29zDQogIGRyb3BfbmEocHJlY2lvbSwgYXJlYWNvbnN0LCBsYXRpdHVkLCBsb25naXR1ZCkgJT4lDQogIA0KICAjIDQpIEltcHV0YWNpw7NuIGRlICdwaXNvJyAoY2F0ZWfDs3JpY2Evb3JkaW5hbCBjb24gTkEgYWx0bykNCiAgbXV0YXRlKHBpc28gPSByZXBsYWNlX25hKHBpc28sICJTaW4gaW5mb3JtYWNpb24iKSkgJT4lDQogIA0KICAjIDUpIEltcHV0YWNpw7NuIGRlICdwYXJxdWVhZGVyb3MnIHBvciBncnVwbyAobcOhcyByZWFsaXN0YSkNCiAgIyBQcmltZXJvIHBvciB0aXBvK2VzdHJhdG8gKHNpIGV4aXN0ZW4pOyBzaSBubywgY2FlIGEgbWVkaWFuYSBnbG9iYWwuDQogIGdyb3VwX2J5KHRpcG8sIGVzdHJhdG8pICU+JQ0KICBtdXRhdGUoDQogICAgcGFycXVlYWRlcm9zID0gaWZfZWxzZSgNCiAgICAgIGlzLm5hKHBhcnF1ZWFkZXJvcyksDQogICAgICBhcy5udW1lcmljKG1lZGlhbihwYXJxdWVhZGVyb3MsIG5hLnJtID0gVFJVRSkpLA0KICAgICAgYXMubnVtZXJpYyhwYXJxdWVhZGVyb3MpDQogICAgKQ0KICApICU+JQ0KICB1bmdyb3VwKCkgJT4lDQogIG11dGF0ZSgNCiAgICBwYXJxdWVhZGVyb3MgPSBpZl9lbHNlKA0KICAgICAgaXMubmEocGFycXVlYWRlcm9zKSwNCiAgICAgIGFzLm51bWVyaWMobWVkaWFuKHBhcnF1ZWFkZXJvcywgbmEucm0gPSBUUlVFKSksDQogICAgICBwYXJxdWVhZGVyb3MNCiAgICApDQogICkgJT4lDQogIA0KICAjIDYpIEltcHV0YWNpw7NuIGRlIG90cmFzIG51bcOpcmljYXMgY29uIG1lZGlhbmEgZ2xvYmFsIChyb2J1c3RhIGEgb3V0bGllcnMpDQogIG11dGF0ZShhY3Jvc3MoDQogICAgd2hlcmUoaXMubnVtZXJpYyksDQogICAgfiBpZl9lbHNlKGlzLm5hKC54KSwgbWVkaWFuKC54LCBuYS5ybSA9IFRSVUUpLCAueCkNCiAgKSkgJT4lDQogIA0KICAjIDcpIEltcHV0YWNpw7NuIGRlIG90cmFzIGNhdGVnw7NyaWNhcw0KICBtdXRhdGUoYWNyb3NzKA0KICAgIHdoZXJlKGlzLmNoYXJhY3RlciksDQogICAgfiByZXBsYWNlX25hKC54LCAiU2luIGluZm9ybWFjaW9uIikNCiAgKSkNCmBgYA0KQ29tbyB2ZXJpZmljYWNpw7NuIGZpbmFsLCBzZSBpZGVudGlmaWNhbiAqKmByIHN1bShpcy5uYShkZl92aXZpZW5kYV9jbGVhbikpYCoqIGRhdG9zIGZhbHRhbnRlcyBlbiBsYSB0YWJsYSBkZSBvZmVydGFzIGlubW9iaWxpYXJpYXMuDQoNCmBgYHtyIHRhYmxhIGNvcnJlZ2lkYX0NCiMgbWlfdGFibGEoDQojICBoZWFkKGRmX3ZpdmllbmRhX2NsZWFuLCA1KSwgDQojICBpZD0idGFibGFfcmVzdW1lbiIsIA0KIyAgdGl0dWxvPSJUYWJsYSBkZSBvZmVydGFzIGlubW9iaWxpYXJpYXMgY29ycmVnaWRhIChyZWdpc3Ryb3MgaW5pY2lhbGVzKSIpDQpgYGANCg0KIyMjIyBWYWxvcmVzIGF0w61waWNvcyBlbiBwcmVjaW8geSDDoXJlYS4NCg0KKipBcGxpY2FuZG8gbcOpdG9kbyBJUVIgeSBNQUQqKjogRWwgbcOpdG9kbyBJUVIgbm8gZGV0ZWN0YSB2YWxvcmVzIGF0w61waWNvcywgbWllbnRyYXMgcXVlIGVsIG3DqXRvZG8gcm9idXN0byBiYXNhZG8gZW4gTUFEIGlkZW50aWZpY2EgdW4gbGV2ZSBwb3JjZW50YWplIGRlIG9ic2VydmFjaW9uZXMgZXh0cmVtYXMgZW4gcHJlY2lvIHkgw6FyZWEuIEVzdG8gc3VnaWVyZSBsYSBwcmVzZW5jaWEgZGUgY29sYXMgbGFyZ2FzIGVuIGxhIGRpc3RyaWJ1Y2nDs24sIGZlbsOzbWVubyBjb23Dum4gZW4gbWVyY2Fkb3MgaW5tb2JpbGlhcmlvcyBkZWJpZG8gYSBsYSBleGlzdGVuY2lhIGRlIHByb3BpZWRhZGVzIGRlIGFsdG8gdmFsb3IgeSBncmFuIHRhbWHDsW8uDQoNCmBgYHtyIG91dGxpZXJzLWRldGVjY2lvbiwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCiMgVmFyaWFibGVzIGEgZXZhbHVhcg0KdmFyc19vdXQgPC0gYygicHJlY2lvbSIsICJhcmVhY29uc3QiKQ0KDQojIC0tLSBGdW5jacOzbjogbMOtbWl0ZXMgSVFSIC0tLQ0KbGltaXRlc19pcXIgPC0gZnVuY3Rpb24oeCkgew0KICBxMSA8LSBxdWFudGlsZSh4LCAwLjI1LCBuYS5ybSA9IFRSVUUpDQogIHEzIDwtIHF1YW50aWxlKHgsIDAuNzUsIG5hLnJtID0gVFJVRSkNCiAgaXFyIDwtIHEzIC0gcTENCiAgYyhsaSA9IHExIC0gMS41ICogaXFyLCBscyA9IHEzICsgMS41ICogaXFyKQ0KfQ0KDQojIC0tLSBGdW5jacOzbjogei1zY29yZSByb2J1c3RvIChNQUQpIC0tLQ0Kel9yb2J1c3RvIDwtIGZ1bmN0aW9uKHgpIHsNCiAgbWVkIDwtIG1lZGlhbih4LCBuYS5ybSA9IFRSVUUpDQogIG1hZHYgPC0gbWFkKHgsIGNvbnN0YW50ID0gMS40ODI2LCBuYS5ybSA9IFRSVUUpICAjIGVzY2FsYSB+IHNkDQogICh4IC0gbWVkKSAvIG1hZHYNCn0NCg0KIyBDYWxjdWxhciBsw61taXRlcyBwb3IgdmFyaWFibGUNCmxpbV9wIDwtIGxpbWl0ZXNfaXFyKGRmX3ZpdmllbmRhX2NsZWFuJHByZWNpb20pDQpsaW1fYSA8LSBsaW1pdGVzX2lxcihkZl92aXZpZW5kYV9jbGVhbiRhcmVhY29uc3QpDQoNCmRmX291dGxpZXJzIDwtIGRmX3ZpdmllbmRhX2NsZWFuICU+JQ0KICBtdXRhdGUoDQogICAgb3V0X3ByZWNpb21faXFyICA9IHByZWNpb20gPCBsaW1fcFsibGkiXSB8IHByZWNpb20gPiBsaW1fcFsibHMiXSwNCiAgICBvdXRfYXJlYWNvbnN0X2lxciA9IGFyZWFjb25zdCA8IGxpbV9hWyJsaSJdIHwgYXJlYWNvbnN0ID4gbGltX2FbImxzIl0sDQogICAgel9wcmVjaW9tX3JvYiA9IHpfcm9idXN0byhwcmVjaW9tKSwNCiAgICB6X2FyZWFjb25zdF9yb2IgPSB6X3JvYnVzdG8oYXJlYWNvbnN0KSwNCiAgICBvdXRfcHJlY2lvbV9tYWQgID0gYWJzKHpfcHJlY2lvbV9yb2IpID4gMy41LA0KICAgIG91dF9hcmVhY29uc3RfbWFkID0gYWJzKHpfYXJlYWNvbnN0X3JvYikgPiAzLjUNCiAgKSAlPiUNCiAgbXV0YXRlKA0KICAgIG91dGxpZXJfYW55ID0gb3V0X3ByZWNpb21faXFyIHwgb3V0X2FyZWFjb25zdF9pcXIgfCBvdXRfcHJlY2lvbV9tYWQgfCBvdXRfYXJlYWNvbnN0X21hZA0KICApDQoNCiMgUmVzdW1lbiBkZSBjYW50aWRhZCBkZSBvdXRsaWVycw0KcmVzX291dCA8LSB0aWJibGU6OnRpYmJsZSgNCiAgdmFyaWFibGUgPSBjKCJwcmVjaW9tIiwgImFyZWFjb25zdCIpLA0KICBuX291dF9pcXIgPSBjKHN1bShkZl9vdXRsaWVycyRvdXRfcHJlY2lvbV9pcXIsIG5hLnJtID0gVFJVRSksDQogICAgICAgICAgICAgICBzdW0oZGZfb3V0bGllcnMkb3V0X2FyZWFjb25zdF9pcXIsIG5hLnJtID0gVFJVRSkpLA0KICBuX291dF9tYWQgPSBjKHN1bShkZl9vdXRsaWVycyRvdXRfcHJlY2lvbV9tYWQsIG5hLnJtID0gVFJVRSksDQogICAgICAgICAgICAgICBzdW0oZGZfb3V0bGllcnMkb3V0X2FyZWFjb25zdF9tYWQsIG5hLnJtID0gVFJVRSkpLA0KICBwY3Rfb3V0X2lxciA9IHJvdW5kKDEwMCAqIG5fb3V0X2lxciAvIG5yb3coZGZfb3V0bGllcnMpLCAyKSwNCiAgcGN0X291dF9tYWQgPSByb3VuZCgxMDAgKiBuX291dF9tYWQgLyBucm93KGRmX291dGxpZXJzKSwgMikNCikNCm1pX3RhYmxhKA0KICByZXNfb3V0LCANCiAgaWQ9InRhYmxhX3Jlc3VtZW4iLCANCiAgdGl0dWxvPSJSZXN1bHRhZG8gZGUgbG9zIG1ldG9kbyBJUVIgeSBNQUQgcGFyYSB2YWxvcmVzIGF0aXBpY29zIikNCg0KYGBgDQoNCioqQXBsaWNhbmRvIGRpYWdyYW1hcyBkZSBjYWphcyB5IGRpc3BlcnNpw7NuKio6IEVsIGFuw6FsaXNpcyBkZSBkaXNwZXJzacOzbiBlbnRyZSBlbCDDoXJlYSBjb25zdHJ1aWRhIHkgZWwgcHJlY2lvIG11ZXN0cmEgbGEgcHJlc2VuY2lhIGRlIHZhbG9yZXMgZXh0cmVtb3MsIGxpZ2Fkb3MgYSBwcm9waWVkYWRlcyBkZSBtYXlvciB0YW1hw7FvIHkgdmFsb3IsIGFsZ28gZXNwZXJhYmxlIHBvciBsYSBleGlzdGVuY2lhIGRlIHNlZ21lbnRvcyBwcmVtaXVtLiBEYWRvIHF1ZSBubyBzZSBvYnNlcnZhbiBvdXRsaWVycyBjbGFyYW1lbnRlIGVycsOzbmVvcyBvIGluY29uc2lzdGVudGVzLCBubyBzZSByZWNvbWllbmRhIGVsaW1pbmFyIHJlZ2lzdHJvcyBkZSBmb3JtYSBtYXNpdmEsIHlhIHF1ZSBlc3RvIHBvZHLDrWEgZGlzdG9yc2lvbmFyIGxhIHJlcHJlc2VudGFjacOzbiByZWFsIGRlbCBtZXJjYWRvLiBFbiBzdSBsdWdhciwgc2Ugc3VnaWVyZSBjb25zZXJ2YXIgbGFzIG9ic2VydmFjaW9uZXMgeSBhcGxpY2FyIHRyYW5zZm9ybWFjaW9uZXMgbG9nYXLDrXRtaWNhcyBhIGxhcyB2YXJpYWJsZXMgZGUgcHJlY2lvIHkgw6FyZWEgY29uc3RydWlkYSBwYXJhIGVzdGFiaWxpemFyIGxhIHZhcmlhbnphLCBkaXNtaW51aXIgbGEgaW5mbHVlbmNpYSBkZSBsb3MgdmFsb3JlcyBleHRyZW1vcyB5IGZvcnRhbGVjZXIgbGEgcm9idXN0ZXogZGUgbG9zIGFuw6FsaXNpcyBtdWx0aXZhcmlhZG9zIHBvc3RlcmlvcmVzLCBjb21vIFBDQSB5IGNsdXN0ZXJpbmcuDQoNCmBgYHtyIG91dGxpZXJzLWJveHBsb3RzLCBmaWcud2lkdGg9NCwgZmlnLmhlaWdodD0yLjh9DQpwX2JveDEgPC0gZ2dwbG90KGRmX291dGxpZXJzLCBhZXMoeCA9ICIiLCB5ID0gcHJlY2lvbSkpICsNCiAgZ2VvbV9ib3hwbG90KCkgKw0KICBsYWJzKHggPSBOVUxMLCB5ID0gInByZWNpb20iKQ0KDQpwX2JveDIgPC0gZ2dwbG90KGRmX291dGxpZXJzLCBhZXMoeCA9ICIiLCB5ID0gYXJlYWNvbnN0KSkgKw0KICBnZW9tX2JveHBsb3QoKSArDQogIGxhYnMoeCA9IE5VTEwsIHkgPSAiYXJlYWNvbnN0IikNCg0KbWlfZmlndXJhKA0KICBwX2JveDEsDQogIGlkICAgICA9ICJmaWd1cmFfcmVzdW1lbiIsDQogIHRpdHVsbyA9ICJEaWFncmFtYSBkZSBjYWphIGRlbCBwcmVjaW8gZGUgb2ZlcnRhcyBpbm1vYmlsaWFyaWFzIg0KKQ0KDQptaV9maWd1cmEoDQogIHBfYm94MiwNCiAgaWQgICAgID0gImZpZ3VyYV9yZXN1bWVuIiwNCiAgdGl0dWxvID0gIkRpYWdyYW1hIGRlIGNhamEgZGVsIGFyZWEgZGUgb2ZlcnRhcyBpbm1vYmlsaWFyaWFzIg0KKQ0KDQpgYGANCg0KYGBge3Igb3V0bGllcnMtc2NhdHRlciwgZmlnLndpZHRoPTcsIGZpZy5oZWlnaHQ9NC44fQ0KcF9kaXNfcHJlY19hcmVhIDwtIGdncGxvdChkZl9vdXRsaWVycywgYWVzKHggPSBhcmVhY29uc3QsIHkgPSBwcmVjaW9tKSkgKw0KICBnZW9tX3BvaW50KGFlcyhzaGFwZSA9IG91dGxpZXJfYW55KSwgYWxwaGEgPSAwLjUpICsNCiAgbGFicygNCiAgICB4ID0gImFyZWFjb25zdCIsDQogICAgeSA9ICJwcmVjaW9tIg0KICApDQoNCm1pX2ZpZ3VyYSgNCiAgcF9kaXNfcHJlY19hcmVhLA0KICBpZCAgICAgPSAiZmlndXJhX3Jlc3VtZW4iLA0KICB0aXR1bG8gPSAiRGlhZ3JhbWEgZGUgZGlzcGVyc8OzbiBkZWwgw6FyZWEgdmVyc3VzIHByZWNpbyAob3V0bGllcnMgcmVzYWx0YWRvcykiDQopDQpgYGANCg0KIyMjIyBOZWNlc2lkYWQgZGUgZXN0YW5kYXJpemFjacOzbiBwYXJhIHZhcmlhYmxlcyBlbiBkaWZlcmVudGVzIGVzY2FsYXMuDQoNClNlIHByb2NlZGUgYSBsYSBlc3RhbmRhcml6YWNpw7NuIGRlIHZhcmlhYmxlcyAoei1zY29yZSkgeSBhIGxhIGNyZWFjacOzbiBkZSBsYSB2YXJpYWJsZXMgbG9nYXJpdG1pY2FzIGRlIHByZWNpbyB5IMOhcmVhLg0KDQpgYGB7ciBsb2cteS1lc3RhbmRhcml6YWNpb24sIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQojIDEpIENyZWFyIHZhcmlhYmxlcyBsb2cgZGUgZm9ybWEgc2VndXJhDQojIFVzYW1vcyBsb2cxcCgpID0gbG9nKDEgKyB4KSBwYXJhIGV2aXRhciBwcm9ibGVtYXMgc2kgaGF5IDAuDQojIChTaSB0dXMgdmFyaWFibGVzIHNvbiBlc3RyaWN0YW1lbnRlID4gMCwgdGFtYmnDqW4gcHVlZGVzIHVzYXIgbG9nKHgpLikNCmRmX3ZpdmllbmRhX3ByZXAgPC0gZGZfdml2aWVuZGFfY2xlYW4gJT4lDQogIG11dGF0ZSgNCiAgICBsb2dfcHJlY2lvbSAgID0gbG9nMXAocHJlY2lvbSksDQogICAgbG9nX2FyZWFjb25zdCA9IGxvZzFwKGFyZWFjb25zdCkNCiAgKQ0KDQojIDIpIFNlbGVjY2lvbmFyIGNvbHVtbmFzIG51bcOpcmljYXMgYSBlc3RhbmRhcml6YXINCiMgUmVjb21lbmRhY2nDs246IGV4Y2x1aXIgaWRlbnRpZmljYWRvcmVzIHkgY29vcmRlbmFkYXMgc2kgbm8gdmFuIGFsIFBDQS9DbHVzdGVyaW5nDQpjb2xzX2V4Y2x1aXIgPC0gaW50ZXJzZWN0KG5hbWVzKGRmX3ZpdmllbmRhX3ByZXApLCBjKCJpZCIsICJsYXRpdHVkIiwgImxvbmdpdHVkIikpDQoNCm51bV9jb2xzIDwtIGRmX3ZpdmllbmRhX3ByZXAgJT4lDQogIHNlbGVjdCh3aGVyZShpcy5udW1lcmljKSkgJT4lDQogIHNlbGVjdCgtYWxsX29mKGNvbHNfZXhjbHVpcikpICU+JQ0KICBuYW1lcygpDQoNCiMgMykgRXN0YW5kYXJpemFjacOzbiAoei1zY29yZSk6ICh4IC0gbWVkaWEpIC8gc2QNCiMgc2NhbGUoKSBkZXZ1ZWx2ZSBtYXRyaXosIHBvciBlc28gbG8gY29udmVydGltb3MgYSBudW1lcmljIGNvbiBhcy5udW1lcmljDQpkZl92aXZpZW5kYV9zdGQgPC0gZGZfdml2aWVuZGFfcHJlcCAlPiUNCiAgbXV0YXRlKGFjcm9zcygNCiAgICBhbGxfb2YobnVtX2NvbHMpLA0KICAgIH4gYXMubnVtZXJpYyhzY2FsZSgueCkpDQogICkpDQoNCiMgNCkgVmVyaWZpY2FjacOzbiByw6FwaWRhOiBtZWRpYXMgfjAgeSBzZCB+MSAoYXByb3gpDQp2ZXJpZl9zdGQgPC0gZGF0YS5mcmFtZSgNCiAgdmFyaWFibGUgPSBudW1fY29scywNCiAgbWVkaWEgPSByb3VuZChzYXBwbHkoZGZfdml2aWVuZGFfc3RkW251bV9jb2xzXSwgbWVhbiwgbmEucm0gPSBUUlVFKSwgMyksDQogIHNkICAgID0gcm91bmQoc2FwcGx5KGRmX3ZpdmllbmRhX3N0ZFtudW1fY29sc10sIHNkLCAgIG5hLnJtID0gVFJVRSksIDMpDQopDQoNCnZlcmlmX3N0ZCA8LSB2ZXJpZl9zdGQgfD4NCiAgdGliYmxlOjphc190aWJibGUoKQ0KDQptaV90YWJsYSgNCiAgdmVyaWZfc3RkLCANCiAgaWQ9InRhYmxhX3Jlc3VtZW4iLCANCiAgdGl0dWxvPSJWZXJpZmljYWNpw7NuIGRlIGVzdGFuZGFyaXphY2nDs24gZGUgdmFyaWFibGVzIikNCg0KZGZfdml2aWVuZGFfcHJlcCA8LSBkZl92aXZpZW5kYV9wcmVwIHw+DQogIHRpYmJsZTo6YXNfdGliYmxlKCkNCg0KIyBWaXN0YSByw6FwaWRhDQptaV90YWJsYSgNCiAgaGVhZChkZl92aXZpZW5kYV9wcmVwLCA1KSwgDQogIGlkPSJ0YWJsYV9yZXN1bWVuIiwgDQogIHRpdHVsbz0iVGFibGEgZGUgb2ZlcnRhcyBpbm1vYmlsaWFyaWFzIGNvbiBkYXRvcyBwcmVwYXJhZG9zIChyZWdpc3Ryb3MgaW5pY2lhbGVzKSIpDQoNCmRmX3ZpdmllbmRhX3N0ZCA8LSBkZl92aXZpZW5kYV9zdGQgfD4NCiAgdGliYmxlOjphc190aWJibGUoKQ0KDQojIFZpc3RhIHLDoXBpZGENCm1pX3RhYmxhKA0KICBoZWFkKGRmX3ZpdmllbmRhX3N0ZCwgNSksIA0KICBpZD0idGFibGFfcmVzdW1lbiIsIA0KICB0aXR1bG89IlRhYmxhIGRlIG9mZXJ0YXMgaW5tb2JpbGlhcmlhcyBjb24gdmFyaWFibGVzIGVzdGFuZGFyaXphZGFzIChyZWdpc3Ryb3MgaW5pY2lhbGVzKSIpDQoNCmBgYA0KDQpMYSB2ZXJpZmljYWNpw7NuIHBvc3RlcmlvciBhIGxhIGVzdGFuZGFyaXphY2nDs24gbXVlc3RyYSBxdWUgdG9kYXMgbGFzIHZhcmlhYmxlcyBudW3DqXJpY2FzIHByZXNlbnRhbiBtZWRpYSBjZXJjYW5hIGEgY2VybyB5IGRlc3ZpYWNpw7NuIGVzdMOhbmRhciBjZXJjYW5hIGEgdW5vLCBsbyBxdWUgY29uZmlybWEgbGEgY29ycmVjdGEgYXBsaWNhY2nDs24gZGVsIHByb2Nlc28gZGUgZXNjYWxhbWllbnRvIHkgZ2FyYW50aXphIGxhIGNvbXBhcmFiaWxpZGFkIGVudHJlIHZhcmlhYmxlcyBwYXJhIGxvcyBhbsOhbGlzaXMgbXVsdGl2YXJpYWRvcyBwb3N0ZXJpb3Jlcy4NCg0KIyAqKk1ldG9kb2xvZ8OtYSBkZWwgcmV0byoqDQoNCkVsIHJldG8gcHJpbmNpcGFsIGNvbnNpc3RlbiBlbiByZWFsaXphciB1biBhbsOhbGlzaXMgaW50ZWdyYWwgeSBtdWx0aWRpbWVuc2lvbmFsIGRlIGxhIGJhc2UgZGUgZGF0b3MgcGFyYSBvYnRlbmVyIHVuYSBjb21wcmVuc2nDs24gZGVsIG1lcmNhZG8gaW5tb2JpbGlhcmlvIHVyYmFuby4gU2UgcmVxdWllcmUgYXBsaWNhciBkaXZlcnNhcyB0w6ljbmljYXMgZGUgYW7DoWxpc2lzIGRlIGRhdG9zLCBpbmNsdXllbmRvOg0KDQojIyBBbsOhbGlzaXMgZGUgQ29tcG9uZW50ZXMgUHJpbmNpcGFsZXMgKFBDQSkNCg0KUmVkdWNpciBsYSBkaW1lbnNpb25hbGlkYWQgZGVsIGNvbmp1bnRvIGRlIGRhdG9zIHkgdmlzdWFsaXphciBsYSBlc3RydWN0dXJhIGRlIGxhcyB2YXJpYWJsZXMgZW4gY29tcG9uZW50ZXMgcHJpbmNpcGFsZXMgcGFyYSBpZGVudGlmaWNhciBjYXJhY3RlcsOtc3RpY2FzIGNsYXZlIHF1ZSBpbmZsdXllbiBlbiBsYSB2YXJpYWNpw7NuIGRlIHByZWNpb3MgeSBvZmVydGEgZGVsIG1lcmNhZG8uDQoNCiMjIyBTZWxlY2Npw7NuIGRlIHZhcmlhYmxlcyBudW1lcmljYXMgcGFyYSBQQ0ENClNlbGVjY2lvbmEgbGFzIHZhcmlhYmxlcyBudW1lcmljYXMgcGFyYSByZWFsaXphciBlbCBhbmFsaXNpczoNCg0KYGBge3IgcGNhLXByZXAsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQojIDEpIFNlbGVjY2nDs24gZGUgdmFyaWFibGVzIG51bcOpcmljYXMgcGFyYSBQQ0ENCiMgLSBFeGNsdXllIGlkZW50aWZpY2Fkb3JlcyB5IGNvb3JkZW5hZGFzDQojIC0gRXhjbHV5ZSB2ZXJzaW9uZXMgb3JpZ2luYWxlcyBzaSB1c2Fyw6FzIGxvZ18qDQpleGNsdWlyIDwtIGludGVyc2VjdChuYW1lcyhkZl92aXZpZW5kYV9zdGQpLCBjKCJpZCIsICJsYXRpdHVkIiwgImxvbmdpdHVkIiwgInByZWNpb20iLCAiYXJlYWNvbnN0IikpDQoNCmRmX3BjYSA8LSBkZl92aXZpZW5kYV9zdGQgJT4lDQogIHNlbGVjdCh3aGVyZShpcy5udW1lcmljKSkgJT4lDQogIHNlbGVjdCgtYWxsX29mKGV4Y2x1aXIpKQ0KDQojIFZpc3RhIHLDoXBpZGENCm1pX3RhYmxhKA0KICBkYXRhLmZyYW1lKHZhcmlhYmxlID0gbmFtZXMoZGZfcGNhKSksIA0KICBpZD0idGFibGFfcmVzdW1lbiIsIA0KICB0aXR1bG89IlZhcmlhYmxlcyBudW1lcmljYXMgc2VsZWNjaW9uYWRhcyBwYXJhIFBDQSIpDQoNCmBgYA0KIyMjIEVqZWN1Y2nDs24gZGVsIGFuw6FsaXNpcyBkZSBjb21wb25lbnRlcyBwcmluY2lwYWxlcw0KDQpTZSBlamVjdXRhIGVsIGFuw6FsaXNpcyBQQ0EgcHJhIGRldGVybWluYXIgbGEgaW1wb3J0YW5jaWEgZGUgbG9zIGNvbXBvbmVudGVzOg0KYGBge3IgcGNhLWVqZWN1Y2lvbiwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCiMgMikgUENBIGNvbiBwcmNvbXAgKHlhIGVzdMOhIGVzdGFuZGFyaXphZG8sIHBvciBlc28gY2VudGVyL3NjYWxlID0gRkFMU0UpDQpwY2FfZml0IDwtIHByY29tcChkZl9wY2EsIGNlbnRlciA9IEZBTFNFLCBzY2FsZS4gPSBGQUxTRSkNCg0KIyBSZXN1bWVuDQp2YXJfZXhwbGljYWRhIDwtIChwY2FfZml0JHNkZXYpXjINCnByb3BfdmFyIDwtIHZhcl9leHBsaWNhZGEgLyBzdW0odmFyX2V4cGxpY2FkYSkNCnZhcl9hY3VtIDwtIGN1bXN1bShwcm9wX3ZhcikNCg0KdGFibGFfcGNhIDwtIGRhdGEuZnJhbWUoDQogIENvbXBvbmVudGUgPSBwYXN0ZTAoIlBDIiwgc2VxX2Fsb25nKHByb3BfdmFyKSksDQogIERlc3ZpYWNpb25fRXN0YW5kYXIgPSBwY2FfZml0JHNkZXYsDQogIFZhcmlhbnphX0V4cGxpY2FkYSA9IHByb3BfdmFyLA0KICBWYXJpYW56YV9BY3VtdWxhZGEgPSB2YXJfYWN1bQ0KKQ0KbWlfdGFibGEoDQogIHRhYmxhX3BjYSwgDQogIGlkPSJ0YWJsYV9yZXN1bWVuIiwgDQogIHRpdHVsbz0iSW1wb3J0YW5jaWEgZGUgY29tcG9uZW50ZXMgZGUgYWN1ZXJkbyBjb24gUENBIikNCmBgYA0KDQoqKkludGVycHJldGFjacOzbioqDQpFbCBhbsOhbGlzaXMgZGUgQ29tcG9uZW50ZXMgUHJpbmNpcGFsZXMgKFBDQSkgZXZpZGVuY2lhIHF1ZSBsYSBtYXlvciBwYXJ0ZSBkZSBsYSBpbmZvcm1hY2nDs24gZGVsIGNvbmp1bnRvIGRlIGRhdG9zIHNlIGNvbmNlbnRyYSBlbiBsb3MgcHJpbWVyb3MgY29tcG9uZW50ZXMuIEVuIHBhcnRpY3VsYXIsIGVsIFBDMSBleHBsaWNhIHBvciBzw60gc29sbyBlbCA2MS4yJSBkZSBsYSB2YXJpYWJpbGlkYWQsIHkgYWwgY29uc2lkZXJhciBoYXN0YSBlbCBQQzQgc2UgYWxjYW56YSBhcHJveGltYWRhbWVudGUgZWwgOTQuNSUgZGUgbGEgdmFyaWFuemEgYWN1bXVsYWRhLiBFc3RvIGluZGljYSBxdWUgZXMgcG9zaWJsZSByZWR1Y2lyIHNpZ25pZmljYXRpdmFtZW50ZSBsYSBkaW1lbnNpb25hbGlkYWQgZGVsIGNvbmp1bnRvIG9yaWdpbmFsIHNpbiBwZXJkZXIgaW5mb3JtYWNpw7NuIHJlbGV2YW50ZS4NCg0KIyMgQW7DoWxpc2lzIGRlIENvbmdsb21lcmFkb3MNCg0KQWdydXBhciBsYXMgcHJvcGllZGFkZXMgcmVzaWRlbmNpYWxlcyBlbiBzZWdtZW50b3MgaG9tb2fDqW5lb3MgY29uIGNhcmFjdGVyw61zdGljYXMgc2ltaWxhcmVzIHBhcmEgZW50ZW5kZXIgbGFzIGRpbsOhbWljYXMgZGUgbGFzIG9mZXJ0YXMgZXNwZWPDrWZpY2FzIGVuIGRpZmVyZW50ZXMgcGFydGVzIGRlIGxhIGNpdWRhZCB5IGVuIGRpZmVyZW50ZXMgZXN0cmF0b3Mgc29jaW9lY29uw7NtaWNvcy4NCg0KIyMjIFNlbGVjY2nDs24gZGUgdmFyaWFibGVzIHBhcmEgY2x1c3RlcmluZw0KDQpTZWxlY2Npb25hIGxhcyB2YXJpYWJsZXMgcGFyYSByZWFsaXphciBlbCBhbmFsaXNpczoNCmBgYHtyIGNsdXN0ZXItcHJlcCwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCiMgVmFyaWFibGVzIG51bcOpcmljYXMgZXN0YW5kYXJpemFkYXMgcGFyYSBjbHVzdGVyaW5nDQpleGNsdWlyIDwtIGludGVyc2VjdChuYW1lcyhkZl92aXZpZW5kYV9zdGQpLCBjKCJpZCIsICJsYXRpdHVkIiwgImxvbmdpdHVkIiwgInByZWNpb20iLCAiYXJlYWNvbnN0IikpDQpkZl9jbHVzdGVyIDwtIGRmX3ZpdmllbmRhX3N0ZCAlPiUNCiAgc2VsZWN0KHdoZXJlKGlzLm51bWVyaWMpKSAlPiUNCiAgc2VsZWN0KC1hbGxfb2YoZXhjbHVpcikpDQoNCiMgVmlzdGEgcsOhcGlkYQ0KbWlfdGFibGEoDQogIGRhdGEuZnJhbWUodmFyaWFibGUgPSBuYW1lcyhkZl9jbHVzdGVyKSksIA0KICBpZD0idGFibGFfcmVzdW1lbiIsIA0KICB0aXR1bG89IlZhcmlhYmxlcyBudW1lcmljYXMgc2VsZWNjaW9uYWRhcyBwYXJhIGNsdXN0ZXJpbmciKQ0KYGBgDQoNCiMjIyBTZWxlY2Npw7NuIGRlIG51bWVybyBkZSBjbHVzdGVyZXMgcG9yIG1ldG9kb3MgZGVsIGNvZG8geSBzaWx1ZXRhDQoNClNlIGFwbGljYSBsb3MgbcOpdG9kb3MgZGUgY29kbyB5IHNpbHVldGEgcGFyYSBkZXRlcm1pbmFyIGVsIG7Dum1lcm8gYWRlY3VhZG8gZGUgY2x1c3RlcmVzIHBhcmEgZWwgYW7DoWxpc2lzLg0KYGBge3IgY2x1c3Rlci1rLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBmaWcud2lkdGg9NywgZmlnLmhlaWdodD00Ljh9DQojIE3DqXRvZG8gZGVsIGNvZG8gKFdTUykNCnBsb3RfY29kbyA8LSBmdml6X25iY2x1c3QoZGZfY2x1c3Rlciwga21lYW5zLCBtZXRob2QgPSAid3NzIikNCm1pX2ZpZ3VyYSgNCiAgcGxvdF9jb2RvLA0KICBpZCAgICAgPSAiZmlndXJhX3Jlc3VtZW4iLA0KICB0aXR1bG8gPSAiTcOpdG9kbyBkZWwgY29kbyBwYXJhIHNlbGVjY2nDs24gZGUgbsO6bWVybyBkZSBjbHVzdGVyZXMiDQopDQojIFNpbGhvdWV0dGUNCnBsb3Rfc2lsdSA8LSBmdml6X25iY2x1c3QoZGZfY2x1c3Rlciwga21lYW5zLCBtZXRob2QgPSAic2lsaG91ZXR0ZSIpDQptaV9maWd1cmEoDQogIHBsb3Rfc2lsdSwNCiAgaWQgICAgID0gImZpZ3VyYV9yZXN1bWVuIiwNCiAgdGl0dWxvID0gIk3DqXRvZG8gZGUgbGEgc2lsdWV0YSBwYXJhIHNlbGVjY2nDs24gZGUgbsO6bWVybyBkZSBjbHVzdGVyZXMiDQopDQpgYGANCkVsIG3DqXRvZG8gZGUgbGEgc2lsdWV0YSBpbmRpY2EgcXVlIGxhIG1lam9yIHNlZ21lbnRhY2nDs24gc2UgbG9ncmEgY29uIGsgPSAyIGNsdXN0ZXJzLCB5YSBxdWUgZW4gZXN0ZSBwdW50byBzZSBvYnRpZW5lIGVsIG1heW9yIGFuY2hvIHByb21lZGlvIGRlIHNpbHVldGEuIFBvciBzdSBwYXJ0ZSwgZWwgbcOpdG9kbyBkZWwgY29kbyBtdWVzdHJhIHVuIGNhbWJpbyBkZSB0ZW5kZW5jaWEgZW50cmUgayA9IDMgeSBrID0gNCwgbG8gcXVlIHN1Z2llcmUgcXVlIGEgcGFydGlyIGRlIGFsbMOtIGxhcyBtZWpvcmFzIGVuIGxhIGNvbXBhY3RhY2nDs24gZGUgbG9zIGdydXBvcyBzb24gY2FkYSB2ZXogbWVub3Jlcy4gQWwgY29uc2lkZXJhciBhbWJvcyBlbmZvcXVlcyB5IGJ1c2NhbmRvIHVuIG1vZGVsbyBjbGFybyB5IGbDoWNpbCBkZSBpbnRlcnByZXRhciwgc2UgZGVjaWRlIHRyYWJhamFyIGNvbiBrID0gMyBjb21vIGxhIGFsdGVybmF0aXZhIHBhcmEgc2VnbWVudGFyIGxhIG9mZXJ0YSBpbm1vYmlsaWFyaWEuDQoNCiMjIyBTZWdtZW50YWNpw7NuIGRlIGxvcyByZWdpc3Ryb3Mgc2Vnw7puIGVsIG51bWVybyBkZSBjbHVzdGVyZXMgZGV0ZXJtaW5hZG8NCg0KU2Ugc2VnbWVudGEgZWwgc2V0IGRlIGRhdG9zIGRlIG9mZXJ0YSBpbm1vYmlsaWFyaWFzIChjb3JyZWdpZG8pIGNvbiBrID0gMy4gQSBjb250aW51YWNpw7NuIHNlIHByZXNlbnRhIGxvcyBjbHVzdGVyZXMgY29uIGxhIGZyZWN1ZW5jaWEgZGUgb2ZlcnRhcyBpbm1vYmlsaWFyaWFzLg0KYGBge3IgY2x1c3Rlci1rbWVhbnMsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpzZXQuc2VlZCgxMjM0KQ0KDQprX29wdCA8LSAzICAjIDwtLSBBSlVTVEEgc2Vnw7puIEVsYm93L1NpbGhvdWV0dGUNCg0Ka21fZml0IDwtIGttZWFucyhkZl9jbHVzdGVyLCBjZW50ZXJzID0ga19vcHQsIG5zdGFydCA9IDI1KQ0KDQojIEHDsWFkaXIgY2x1c3RlciBhbCBkYXRhZnJhbWUgbGltcGlvIChjb24gY2F0ZWfDs3JpY2FzKQ0KZGZfdml2aWVuZGFfY2x1c3RlciA8LSBkZl92aXZpZW5kYV9jbGVhbiAlPiUNCiAgbXV0YXRlKGNsdXN0ZXIgPSBmYWN0b3Ioa21fZml0JGNsdXN0ZXIpKQ0KDQp0YWJsYV9jbHVzdGVycyA8LSBhcy5kYXRhLmZyYW1lKHRhYmxlKGRmX3ZpdmllbmRhX2NsdXN0ZXIkY2x1c3RlcikpDQpuYW1lcyh0YWJsYV9jbHVzdGVycykgPC0gYygiY2x1c3RlciIsICJmcmVjdWVuY2lhIikNCiMgVmlzdGEgcsOhcGlkYQ0KbWlfdGFibGEoDQogIHRhYmxhX2NsdXN0ZXJzLCANCiAgaWQ9InRhYmxhX3Jlc3VtZW4iLCANCiAgdGl0dWxvPSJUYW1hw7FvIGRlIGNhZGEgc2VnbWVudG8iKQ0KYGBgDQoNCiMjIyBQZXJmaWxhbWllbnRvIGRlIGNsdXN0ZXJlcw0KDQpTZSByZWFsaXphIGVsIHBlcmZpbGFtaWVudG8gZGUgY2FkYSBjbHVzdGVyIGNvbiBsYXMgdmFyaWFibGVzIHTDrXBpY2FzIGRlbCBjYXNvIGNvbiBlc3RhZGlzdGlzY29zIHByaW5jaXBhbGVzIChtZWRpYW5hIHkgbWVkaWEpLg0KDQpgYGB7ciBjbHVzdGVyLXBlcmZpbC1udW1lcmljbywgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnJlc19jbHVzdGVyX251bSA8LSBkZl92aXZpZW5kYV9jbHVzdGVyICU+JQ0KICBncm91cF9ieShjbHVzdGVyKSAlPiUNCiAgc3VtbWFyaXNlKA0KICAgIG4gPSBuKCksDQogICAgIyBWYXJpYWJsZXMgdMOtcGljYXMgZGVsIGNhc28gKGFqdXN0YSBzaSBkZXNlYXMpDQogICAgcHJlY2lvbV9tZWRpYW5hID0gbWVkaWFuKHByZWNpb20sIG5hLnJtID0gVFJVRSksDQogICAgYXJlYWNvbnN0X21lZGlhbmEgPSBtZWRpYW4oYXJlYWNvbnN0LCBuYS5ybSA9IFRSVUUpLA0KICAgIHBhcnF1ZWFkZXJvc19wcm9tID0gbWVhbihwYXJxdWVhZGVyb3MsIG5hLnJtID0gVFJVRSksDQogICAgYmFuaW9zX3Byb20gPSBtZWFuKGJhbmlvcywgbmEucm0gPSBUUlVFKSwNCiAgICBoYWJpdGFjaW9uZXNfcHJvbSA9IG1lYW4oaGFiaXRhY2lvbmVzLCBuYS5ybSA9IFRSVUUpLA0KICAgIGVzdHJhdG9fbWVkaWFuYSA9IG1lZGlhbihlc3RyYXRvLCBuYS5ybSA9IFRSVUUpDQogICkgJT4lDQogIGFycmFuZ2UoY2x1c3RlcikNCg0KbWlfdGFibGEoDQogIHJlc19jbHVzdGVyX251bSB8PiB0aWJibGU6OmFzX3RpYmJsZSgpLCANCiAgaWQ9InRhYmxhX3Jlc3VtZW4iLCANCiAgdGl0dWxvPSJQZXJmaWxhbWllbnRvIGRlIGNhZGEgY2x1c3RlciIpDQoNCmBgYA0KDQoqKkludGVycHJldGFjacOzbioqDQpFbCBhbsOhbGlzaXMgZGUgY2x1c3RlcnMgcGVybWl0acOzIGlkZW50aWZpY2FyIHRyZXMgc2VnbWVudG9zIGRpZmVyZW5jaWFkb3MgZW4gbGEgb2ZlcnRhIGlubW9iaWxpYXJpYTogKGkpIHVuIHNlZ21lbnRvIHByZW1pdW0gZGUgYWx0byB2YWxvciB5IGVzdHJhdG8gZWxldmFkbywgY2FyYWN0ZXJpemFkbyBwb3IgbWF5b3Igw6FyZWEgeSBuaXZlbCBkZSBkb3RhY2nDs247IChpaSkgdW4gc2VnbWVudG8gbWVkaW/igJNlY29uw7NtaWNvIHF1ZSBjb25jZW50cmEgbGEgbWF5b3IgY2FudGlkYWQgZGUgdml2aWVuZGFzLCBjb24gbWVub3IgdGFtYcOxbyB5IHByZWNpbzsgeSAoaWlpKSB1biBzZWdtZW50byBkZSB2aXZpZW5kYXMgZmFtaWxpYXJlcyBhbXBsaWFzLCBjb24gYWx0byBuw7ptZXJvIGRlIGhhYml0YWNpb25lcyB5IMOhcmVhIGNvbnN0cnVpZGEsIHBlcm8gdWJpY2FkbyBlbiBlc3RyYXRvcyBpbnRlcm1lZGlvcy4gRXN0YSBzZWdtZW50YWNpw7NuIGV2aWRlbmNpYSBsYSBjb2V4aXN0ZW5jaWEgZGUgbWVyY2Fkb3MgY29uIGRpZmVyZW50ZXMgbml2ZWxlcyBkZSB2YWxvcml6YWNpw7NuIHkgdGlwb2xvZ8OtYSBoYWJpdGFjaW9uYWwuDQoNCiMjICBBbsOhbGlzaXMgZGUgQ29ycmVzcG9uZGVuY2lhIA0KDQpFeGFtaW5hciBsYSByZWxhY2nDs24gZW50cmUgbGFzIHZhcmlhYmxlcyBjYXRlZ8OzcmljYXMgKHRpcG8gZGUgdml2aWVuZGEsIHpvbmEgeSBiYXJyaW8pLCBwYXJhIGlkZW50aWZpY2FyIHBhdHJvbmVzIGRlIGNvbXBvcnRhbWllbnRvIGRlIGxhIG9mZXJ0YSBlbiBtZXJjYWRvIGlubW9iaWxpYXJpby4NCg0KIyMjIEFuYWxpc2lzIGRlIGRpc3RyaWJ1Y2nDs24gcG9yIHRpcG8gZGUgaW5tdWVibGUsIHpvbmEgeSBiYXJyaW8NCg0KQSBjb250aW51YWNpw7NuIHNlIHByZXNlbnRhIGxhIGRpc3RyaWJ1Y2nDs24gZGVsIHRpcG8gZGUgaW5tdWVibGUsIHpvbmEgeSBiYXJyaW8gZGUgbGFzIG9mZXJ0YXMgaW5tb2JsaWxpYXJpYXMgKHRhYmxhIGNvcnJlZ2lkYSkuDQpgYGB7ciBjYS1wcmVwLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KZGZfY2F0IDwtIGRmX3ZpdmllbmRhX2NsZWFuICU+JQ0KICB0cmFuc211dGUoDQogICAgdGlwbyAgPSBhcy5mYWN0b3Ioc3RyX3NxdWlzaCh0aXBvKSksDQogICAgem9uYSAgPSBhcy5mYWN0b3Ioc3RyX3NxdWlzaCh6b25hKSksDQogICAgYmFycmlvID0gc3RyX3NxdWlzaChiYXJyaW8pDQogICkgJT4lDQogIG11dGF0ZSgNCiAgICB0aXBvID0gZmN0X2V4cGxpY2l0X25hKHRpcG8sIG5hX2xldmVsID0gIlNpbiBpbmZvcm1hY2lvbiIpLA0KICAgIHpvbmEgPSBmY3RfZXhwbGljaXRfbmEoem9uYSwgbmFfbGV2ZWwgPSAiU2luIGluZm9ybWFjaW9uIiksDQogICAgYmFycmlvID0gaWZlbHNlKGlzLm5hKGJhcnJpbykgfCBiYXJyaW8gPT0gIiIsICJTaW4gaW5mb3JtYWNpb24iLCBiYXJyaW8pDQogICkNCg0KIyBSZWR1Y2lyIGNhcmRpbmFsaWRhZCBkZSBiYXJyaW8gKFRvcCBOICsgT3Ryb3MpDQp0b3BfbiA8LSAyMCAgIyBhanVzdGEgMTUtMzAgc2Vnw7puIHR1IHByZWZlcmVuY2lhDQp0b3BfYmFycmlvcyA8LSBkZl9jYXQgJT4lDQogIGNvdW50KGJhcnJpbywgc29ydCA9IFRSVUUpICU+JQ0KICBzbGljZV9oZWFkKG4gPSB0b3BfbikgJT4lDQogIHB1bGwoYmFycmlvKQ0KDQpkZl9jYXQgPC0gZGZfY2F0ICU+JQ0KICBtdXRhdGUoDQogICAgYmFycmlvX3JlYyA9IGlmZWxzZShiYXJyaW8gJWluJSB0b3BfYmFycmlvcywgYmFycmlvLCAiT3Ryb3MiKSwNCiAgICBiYXJyaW9fcmVjID0gYXMuZmFjdG9yKGJhcnJpb19yZWMpDQogICkNCg0KIyBWZXJpZmljYWNpw7NuIHLDoXBpZGENCg0KbWlfdGFibGEoDQogIGRmX2NhdCAlPiUgY291bnQodGlwbywgbmFtZSA9ICJmcmVjdWVuY2lhIiksIA0KICBpZD0idGFibGFfcmVzdW1lbiIsIA0KICB0aXR1bG89IkRpc3RyaWJ1Y2nDs24gcG9yIHRpcG8gZGUgaW5tdWVibGUiKQ0KDQptaV90YWJsYSgNCiAgZGZfY2F0ICU+JSBjb3VudCh6b25hLCBuYW1lID0gImZyZWN1ZW5jaWEiKSwgDQogIGlkPSJ0YWJsYV9yZXN1bWVuIiwgDQogIHRpdHVsbz0iRGlzdHJpYnVjacOzbiBwb3Igem9uYSIpDQoNCm1pX3RhYmxhKA0KICBkZl9jYXQgJT4lIGNvdW50KGJhcnJpb19yZWMsIG5hbWUgPSAiZnJlY3VlbmNpYSIpLCANCiAgaWQ9InRhYmxhX3Jlc3VtZW4iLCANCiAgdGl0dWxvPSJEaXN0cmlidWNpw7NuIHBvciBiYXJyaW8iKQ0KYGBgDQoNCiMjIyBFamVjdWNpw7NuIGRlbCBhbmFsaXNpcyBkZSBjb3JyZXNwb25kZW5jaWEgeSBwcnVlYmEgY2hpLWN1YWRyYWRvDQpTZSByZWFsaXphIGVsIGFuYWxpc2lzIGNydXphZG8gZW50cmUgZWwgdGlwbyBkZWwgaW5tdWVibGUgdmVyc3VzIGxhcyB6b25hcy4NCmBgYHtyIGNhLXRhYi10aXBvLXpvbmEsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQp0YWJfdGlwb196b25hIDwtIHRhYmxlKGRmX2NhdCR0aXBvLCBkZl9jYXQkem9uYSkNCnRhYl90aXBvX3pvbmFfd2lkZSA8LSBhcy5kYXRhLmZyYW1lKHRhYl90aXBvX3pvbmEpICU+JQ0KICByZW5hbWUodGlwbyA9IFZhcjEsIHpvbmEgPSBWYXIyLCBuID0gRnJlcSkgJT4lDQogIHBpdm90X3dpZGVyKG5hbWVzX2Zyb20gPSB6b25hLCB2YWx1ZXNfZnJvbSA9IG4sIHZhbHVlc19maWxsID0gMCkNCm1pX3RhYmxhKA0KICB0YWJfdGlwb196b25hX3dpZGUsIA0KICBpZD0idGFibGFfcmVzdW1lbiIsIA0KICB0aXR1bG89IlRhYmxhIGNydXphZGEgZW50cmUgZWwgdGlwbyBkZSBpbm11ZWJsZSB2ZXJzdXMgbGFzIHpvbmFzIikNCmBgYA0KKipJbnRlcnByZXRhY2nDs24qKiBTZSByZWFsaXphIGxhIHBydWViYSBkZSBpbmRlcGVuZGVuY2lhIGVudHJlIGVsIHRpcG8gZGUgaW5tdWVibGUgeSBsYXMgem9uYXMsIGRvbmRlIGVsIHAtdmFsdWUgdGllbmRlIGEgY2VybywgbG8gY3VhbCByZWNoYXphIGxhIHBydWViYS4gQ29uIGVzdG8sIGhheSB1bmEgZGVwZW5kZW5jaWEgZnVlcnRlIGVudHJlIGVsIHRpcG8gZGUgaW5tdWVibGUgeSBsYXMgem9uYXMuDQpgYGB7ciBjYS1jaGktdGlwby16b25hLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KY2hpc3EudGVzdCh0YWJfdGlwb196b25hKQ0KYGBgDQpUYW1iaWVuIFNlIHJlYWxpemEgZWwgYW5hbGlzaXMgY3J1emFkbyBlbnRyZSBsYXMgem9uYXMgdmVyc3VzIGxvcyBiYXJyaW9zLg0KYGBge3IgciBjYS10YWItem9uYS1iYXJyaW8sIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQp0YWJfem9uYV9iYXJyaW8gPC0gdGFibGUoZGZfY2F0JHpvbmEsIGRmX2NhdCRiYXJyaW9fcmVjKQ0KdGFiX3pvbmFfYmFycmlvX3dpZGUgPC0gYXMuZGF0YS5mcmFtZSh0YWJfem9uYV9iYXJyaW8pICU+JQ0KICByZW5hbWUoem9uYSA9IFZhcjEsIGJhcnJpb19yZWMgPSBWYXIyLCBuID0gRnJlcSkgJT4lDQogIHBpdm90X3dpZGVyKG5hbWVzX2Zyb20gPSBiYXJyaW9fcmVjLCB2YWx1ZXNfZnJvbSA9IG4sIHZhbHVlc19maWxsID0gMCkNCm1pX3RhYmxhKA0KICB0YWJfem9uYV9iYXJyaW9fd2lkZSwgDQogIGlkPSJ0YWJsYV9yZXN1bWVuIiwgDQogIHRpdHVsbz0iVGFibGEgY3J1emFkYSBlbnRyZSBsYXMgem9uYXMgdmVyc3VzIGxvcyBiYXJyaW9zIikNCmBgYA0KKipJbnRlcnByZXRhY2nDs24qKiBTZSByZWFsaXphIGxhIHBydWViYSBkZSBpbmRlcGVuZGVuY2lhIGVudHJlIGxhcyB6b25hcyB5IGxvcyBiYXJyaW9zLCBkb25kZSBlbCBwLXZhbHVlIHRpZW5kZSBhIGNlcm8sIGxvIGN1YWwgcmVjaGF6YSBsYSBwcnVlYmEuIENvbiBlc3RvLCBoYXkgdW5hIGRlcGVuZGVuY2lhIGZ1ZXJ0ZSBlbnRyZSBsYXMgem9uYXMgeSBsb3MgYmFycmlvcy4NCmBgYHtyIGNhLWNoaS16b25hLWJhcnJpbywgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCmNoaXNxLnRlc3QodGFiX3pvbmFfYmFycmlvKQ0KYGBgDQoNCiMgKipSZXN1bHRhZG9zIE9idGVuaWRvcyoqDQoNClByZXNlbnRhciBncsOhZmljb3MsIG1hcGFzIHkgb3Ryb3MgcmVjdXJzb3MgdmlzdWFsZXMgcGFyYSBjb211bmljYXIgbG9zIGhhbGxhemdvcyBkZSBtYW5lcmEgY2xhcmEgeSBlZmVjdGl2YSBhIGxhIGRpcmVjY2nDs24gZGUgbGEgZW1wcmVzYS4NCg0KIyMgUmVzdWx0YWRvcyBkZWwgUENBICANCkludGVycHJldGFjacOzbiBkZSBjb21wb25lbnRlcyBwcmluY2lwYWxlcy4NCg0KQSBjb250aW51YWNpw7NuLCBzZSBncmFmaWNhIGxhIHZhcmlhbnphIGV4cGxpY2FkYSBwYXJhIGxvcyBjb21wb25lbnRlcyBkZWwgUENBLCBlbCBhbmFsaXNpcyBiaXZhcmlhZG8gZGUgbG9zIGNvbXBvbmVudGVzIHByaW5jaXBhbGVzIHkgbGEgY29udHJpYnVjacOzbiBkZSBsYXMgdmFyaWFibGVzIHBhcmEgY2FkYSBjb21wb25lbnRlIHByaW5jaXBhbC4NCg0KYGBge3IgcGNhLXNjcmVlLCBmaWcud2lkdGg9NywgZmlnLmhlaWdodD00LjV9DQp2YXJfZXhwIDwtIChwY2FfZml0JHNkZXZeMikgLyBzdW0ocGNhX2ZpdCRzZGV2XjIpDQpkZl9zY3JlZSA8LSBkYXRhLmZyYW1lKA0KICBjb21wb25lbnRlID0gc2VxX2Fsb25nKHZhcl9leHApLA0KICB2YXJpYW56YSA9IHZhcl9leHANCikNCg0KcGxvdF92ZWMgPC0gZ2dwbG90KGRmX3NjcmVlLCBhZXMoeCA9IGNvbXBvbmVudGUsIHkgPSB2YXJpYW56YSkpICsNCiAgZ2VvbV9saW5lKCkgKw0KICBnZW9tX3BvaW50KCkgKw0KICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gZGZfc2NyZWUkY29tcG9uZW50ZSkgKw0KICBsYWJzKA0KICAgIHggPSAiQ29tcG9uZW50ZSBwcmluY2lwYWwiLA0KICAgIHkgPSAiUHJvcG9yY2nDs24gZGUgdmFyaWFuemEgZXhwbGljYWRhIg0KICApDQoNCm1pX2ZpZ3VyYSgNCiAgcGxvdF92ZWMsDQogIGlkICAgICA9ICJmaWd1cmFfcmVzdW1lbiIsDQogIHRpdHVsbyA9ICJWYXJpYW56YSBleHBsaWNhZGEgcG9yIGNvbXBvbmVudGUiDQopDQpgYGANCg0KYGBge3IgcGNhLWJpcGxvdCwgZmlnLndpZHRoPTcsIGZpZy5oZWlnaHQ9NX0NCnBsb3RfcGNhX2JpIDwtIGZ2aXpfcGNhX2JpcGxvdCgNCiAgcGNhX2ZpdCwNCiAgY29sLnZhciA9ICJibGFjayIsDQogIGNvbC5pbmQgPSAiZ3JheTcwIg0KKQ0KDQptaV9maWd1cmEoDQogIHBsb3RfcGNhX2JpLA0KICBpZCAgICAgPSAiZmlndXJhX3Jlc3VtZW4iLA0KICB0aXR1bG8gPSAiQW5hbGlzaXMgYml2YXJpYWRvIGRlIGNvbXBvbmVudGVzIFBDMSB2ZXJzdXMgUEMyIHBhcmEgaW5kaXZpZHVvcyB5IHZhcmlhYmxlcyINCikNCmBgYA0KDQpgYGB7ciBwY2EtY29udHJpYnVjaW9uZXMsIGZpZy53aWR0aD03LCBmaWcuaGVpZ2h0PTQuOH0NCiMgVmFyaWFibGVzIHF1ZSBtw6FzIGNvbnRyaWJ1eWVuIGEgUEMxIHkgUEMyDQpwbG90X2Nvbl9wYzEgPC0gZnZpel9jb250cmliKHBjYV9maXQsIGNob2ljZSA9ICJ2YXIiLCBheGVzID0gMSwgdG9wID0gMTApDQoNCm1pX2ZpZ3VyYSgNCiAgcGxvdF9jb25fcGMxLA0KICBpZCAgICAgPSAiZmlndXJhX3Jlc3VtZW4iLA0KICB0aXR1bG8gPSAiVG9wIHZhcmlhYmxlcyBxdWUgbcOhcyBjb250cmlidXllbiBhIFBDMSINCikNCg0KcGxvdF9jb25fcGMyIDwtIGZ2aXpfY29udHJpYihwY2FfZml0LCBjaG9pY2UgPSAidmFyIiwgYXhlcyA9IDIsIHRvcCA9IDEwKQ0KDQptaV9maWd1cmEoDQogIHBsb3RfY29uX3BjMiwNCiAgaWQgICAgID0gImZpZ3VyYV9yZXN1bWVuIiwNCiAgdGl0dWxvID0gIlRvcCB2YXJpYWJsZXMgcXVlIG3DoXMgY29udHJpYnV5ZW4gYSBQQzIiDQopDQpgYGANCg0KKipJbnRlcnByZXRhY2nDs24gZ2VuZXJhbCoqDQpFbCBhbsOhbGlzaXMgZGUgY29tcG9uZW50ZXMgcHJpbmNpcGFsZXMgKFBDQSkgcGVybWl0acOzIHJlZHVjaXIgbGEgZGltZW5zaW9uYWxpZGFkIGRlbCBjb25qdW50byBkZSB2YXJpYWJsZXMgbnVtw6lyaWNhcyBlc3RhbmRhcml6YWRhcywgaWRlbnRpZmljYW5kbyB1biBjb25qdW50byBwZXF1ZcOxbyBkZSBjb21wb25lbnRlcyBxdWUgZXhwbGljYW4gbGEgbWF5b3IgcGFydGUgZGUgbGEgdmFyaWFiaWxpZGFkIG9ic2VydmFkYSBlbiBsYSBvZmVydGEgaW5tb2JpbGlhcmlhLiANCg0KTGFzIGNvbnRyaWJ1Y2lvbmVzIGRlIHZhcmlhYmxlcyBhIGxhcyBwcmltZXJhcyBjb21wb25lbnRlcyBzdWdpZXJlbiBjdcOhbGVzIGNhcmFjdGVyw61zdGljYXMgZGVsIGlubXVlYmxlIHkgZGVsIG1lcmNhZG8gKHBvciBlamVtcGxvLCBhc29jaWFkYXMgYSB0YW1hw7FvLCBkb3RhY2nDs24geSBuaXZlbCBzb2Npb2Vjb27Ds21pY28pIGluZmx1eWVuIGNvbiBtYXlvciBwZXNvIGVuIGxhIHZhcmlhY2nDs24gZGUgcHJlY2lvcyB5IGVuIGxhIGVzdHJ1Y3R1cmEgZ2VuZXJhbCBkZSBsYSBvZmVydGEuIExhIHZpc3VhbGl6YWNpw7NuIGVuIGVsIHBsYW5vIFBDMeKAk1BDMiBmYWNpbGl0YSBsYSBpZGVudGlmaWNhY2nDs24gZGUgcGF0cm9uZXMgeSBwb3NpYmxlcyBzZWdtZW50b3MsIGxvcyBjdWFsZXMgc2Vyw6FuIGV4cGxvcmFkb3MgcG9zdGVyaW9ybWVudGUgbWVkaWFudGUgYW7DoWxpc2lzIGRlIGNvbmdsb21lcmFkb3MuDQoNCiMjIFJlc3VsdGFkb3MgZGVsIENsdXN0ZXJpbmcgIA0KRGVzY3JpcGNpw7NuIGRlIHNlZ21lbnRvcyBlbmNvbnRyYWRvcy4NCg0KQ29uIGxhIHRhYmxhIGRlbCBhbmFsaXNpcyBkZSBjb25nbG9tZXJhZG9zLCBzZSByZWFsaXphIGxhIHZpc3VhbGl6YWNpw7NuIGRlIGxvcyBjbHVzdGVyZXMgY29uIGxvcyBkb3MgY29tcG9uZW50ZXMgcHJpbmNpcGFsZXMgZGVsIGFuYWxpc2lzIFBDQS4NCg0KYGBge3IgY2x1c3Rlci1wbG90LCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBmaWcud2lkdGg9NywgZmlnLmhlaWdodD00Ljh9DQojIFBDQSBzb2xvIHBhcmEgdmlzdWFsaXphciAoc29icmUgZWwgbWlzbW8gZGZfY2x1c3RlciB1c2FkbyBlbiBrbWVhbnMpDQpwY2FfdmlzIDwtIHByY29tcChkZl9jbHVzdGVyLCBjZW50ZXIgPSBGQUxTRSwgc2NhbGUuID0gRkFMU0UpDQoNCnBfY2x1c3RlcmVzX1BDQSA8LSBmdml6X3BjYV9pbmQoDQogIHBjYV92aXMsDQogIGdlb20uaW5kID0gInBvaW50IiwNCiAgaGFiaWxsYWdlID0gZGZfdml2aWVuZGFfY2x1c3RlciRjbHVzdGVyLA0KICBhZGRFbGxpcHNlcyA9IFRSVUUsDQogIGVsbGlwc2UubGV2ZWwgPSAwLjk1LA0KICBhbHBoYS5pbmQgPSAwLjUNCikNCg0KbWlfZmlndXJhKA0KICBwX2NsdXN0ZXJlc19QQ0EsDQogIGlkICAgICA9ICJmaWd1cmFfcmVzdW1lbiIsDQogIHRpdHVsbyA9ICJDbHVzdGVycyBwcm95ZWN0YWRvcyBlbiBlbCBwbGFubyBQQ0EgY29uIGxhcyBkb3MgY29tcG9uZW50ZXMgcHJpbmNpcGFsZXMiDQopDQpgYGANCioqSW50ZXJwcmV0YWNpw7NuIGRlIGxhIHNlZ21lbnRhY2nDs24gZW4gcGxhbm8gUENBKiogDQpFbCBwbGFubyBQQ0EgZXZpZGVuY2lhIHVuYSBzZWdtZW50YWNpw7NuIGNvaGVyZW50ZSBkZWwgbWVyY2FkbyBpbm1vYmlsaWFyaW8gZW4gdHJlcyBncnVwb3MgY29uIHBlcmZpbGVzIGRpZmVyZW5jaWFkb3MsIGF1bnF1ZSBjb24gc29sYXBhbWllbnRvcyBtb2RlcmFkb3MgcHJvcGlvcyBkZSBsYSBoZXRlcm9nZW5laWRhZCBkZWwgbWVyY2Fkby4NCg0KYGBge3IgY2x1c3Rlci1iYXJyYXMsIGZpZy53aWR0aD03LCBmaWcuaGVpZ2h0PTQuOH0NCnRhYl96b25hIDwtIGRmX3ZpdmllbmRhX2NsdXN0ZXIgJT4lDQogIGNvdW50KHpvbmEsIGNsdXN0ZXIpICU+JQ0KICBncm91cF9ieSh6b25hKSAlPiUNCiAgbXV0YXRlKHBjdCA9IHJvdW5kKDEwMCAqIG4gLyBzdW0obiksIDIpKSAlPiUNCiAgYXJyYW5nZSh6b25hLCBkZXNjKHBjdCkpDQoNCnBfY2x1c3RlcmVzX3pvbmEgPC0gZ2dwbG90KHRhYl96b25hLCBhZXMoeCA9IHpvbmEsIHkgPSBwY3QsIGZpbGwgPSBjbHVzdGVyKSkgKw0KICBnZW9tX2NvbCgpICsNCiAgbGFicygNCiAgICB4ID0gIlpvbmEiLA0KICAgIHkgPSAiJSBkZW50cm8gZGUgY2FkYSB6b25hIg0KICApICsNCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSAyNSwgaGp1c3QgPSAxKSkNCg0KbWlfZmlndXJhKA0KICBwX2NsdXN0ZXJlc196b25hLA0KICBpZCAgICAgPSAiZmlndXJhX3Jlc3VtZW4iLA0KICB0aXR1bG8gPSAiQ29tcG9zaWNpw7NuIHBvcmNlbnR1YWwgZGUgY2x1c3RlcnMgcG9yIHpvbmEiDQopDQpgYGANCioqSW50ZXJwcmV0YWNpw7NuIGRlIGxvcyBjbHVzdGVyZXMgcG9yIHpvbmEqKg0KTGEgY29tcG9zaWNpw7NuIHBvcmNlbnR1YWwgcG9yIHpvbmEgbXVlc3RyYSBxdWUgZWwgbWVyY2FkbyBpbm1vYmlsaWFyaW8gcHJlc2VudGEgZGlmZXJlbmNpYXMgZXNwYWNpYWxlcyBjbGFyYXMuIExhIFpvbmEgT2VzdGUgY29uY2VudHJhIHVuYSBtYXlvciBwcm9wb3JjacOzbiBkZSB2aXZpZW5kYXMgcGVydGVuZWNpZW50ZXMgYWwgY2x1c3RlciBkZSBtYXlvciB2YWxvcml6YWNpw7NuLCBtaWVudHJhcyBxdWUgbGFzIFpvbmFzIE5vcnRlIHkgU3VyIGVzdMOhbiBwcmluY2lwYWxtZW50ZSBkb21pbmFkYXMgcG9yIGVsIHNlZ21lbnRvIG1lZGlv4oCTZWNvbsOzbWljby4gRW4gY29udHJhc3RlLCBsYXMgWm9uYXMgQ2VudHJvIHkgT3JpZW50ZSBleGhpYmVuIHVuYSBkaXN0cmlidWNpw7NuIG3DoXMgZXF1aWxpYnJhZGEgeSBkaXZlcnNhIGVudHJlIGxvcyBkaXN0aW50b3MgdGlwb3MgZGUgdml2aWVuZGEuIEVuIGNvbmp1bnRvLCBlc3RvcyBoYWxsYXpnb3MgY29uZmlybWFuIHF1ZSBlbCBjb21wb3J0YW1pZW50byBkZWwgbWVyY2FkbyBpbm1vYmlsaWFyaW8gdmFyw61hIGRlIG1hbmVyYSBpbXBvcnRhbnRlIHNlZ8O6biBsYSB1YmljYWNpw7NuIGdlb2dyw6FmaWNhLg0KDQpgYGB7ciBjbHVzdGVyLWJhcnJhcy1lc3RyYXRvLCBmaWcud2lkdGg9NywgZmlnLmhlaWdodD00Ljh9DQp0YWJfZXN0cmF0byA8LSBkZl92aXZpZW5kYV9jbHVzdGVyICU+JQ0KICBjb3VudChlc3RyYXRvLCBjbHVzdGVyKSAlPiUNCiAgZ3JvdXBfYnkoZXN0cmF0bykgJT4lDQogIG11dGF0ZShwY3QgPSByb3VuZCgxMDAgKiBuIC8gc3VtKG4pLCAyKSkgJT4lDQogIGFycmFuZ2UoZXN0cmF0bywgZGVzYyhwY3QpKQ0KDQpwX2NsdXN0ZXJlc19lc3RyYXRvIDwtIGdncGxvdCh0YWJfZXN0cmF0bywgYWVzKHggPSBmYWN0b3IoZXN0cmF0byksIHkgPSBwY3QsIGZpbGwgPSBjbHVzdGVyKSkgKw0KICBnZW9tX2NvbCgpICsNCiAgbGFicygNCiAgICB4ID0gIkVzdHJhdG8iLA0KICAgIHkgPSAiJSBkZW50cm8gZGUgY2FkYSBlc3RyYXRvIg0KICApDQoNCm1pX2ZpZ3VyYSgNCiAgcF9jbHVzdGVyZXNfZXN0cmF0bywNCiAgaWQgICAgID0gImZpZ3VyYV9yZXN1bWVuIiwNCiAgdGl0dWxvID0gIkNvbXBvc2ljacOzbiBwb3JjZW50dWFsIGRlIGNsdXN0ZXJzIHBvciBlc3RyYXRvIg0KKQ0KYGBgDQoqKkludGVycHJldGFjacOzbiBkZSBsb3MgY2x1c3RlcmVzIHBvciBlc3RyYXRvKioNCkxhIGNvbXBvc2ljacOzbiBwb3JjZW50dWFsIHBvciBlc3RyYXRvIGV2aWRlbmNpYSB1bmEgY2xhcmEgcmVsYWNpw7NuIGVudHJlIGxhIHNlZ21lbnRhY2nDs24gb2J0ZW5pZGEgeSBlbCBuaXZlbCBzb2Npb2Vjb27Ds21pY28gZGUgbGFzIHZpdmllbmRhcy4gRWwgZXN0cmF0byA2IGVzdMOhIGRvbWluYWRvIHBvciBlbCBjbHVzdGVyIGRlIG1heW9yIHZhbG9yaXphY2nDs24sIG1pZW50cmFzIHF1ZSBsb3MgZXN0cmF0b3MgMyB5IDQgY29uY2VudHJhbiBwcmluY2lwYWxtZW50ZSBlbCBzZWdtZW50byBtZWRpb+KAk2Vjb27Ds21pY28uIFBvciBzdSBwYXJ0ZSwgZWwgZXN0cmF0byA1IHByZXNlbnRhIHVuYSBlc3RydWN0dXJhIGRlIHRyYW5zaWNpw7NuIGVudHJlIGFtYm9zIG1lcmNhZG9zLiBFc3RvcyByZXN1bHRhZG9zIGNvbmZpcm1hbiBsYSBjb2hlcmVuY2lhIGRlbCBtb2RlbG8gZGUgY2x1c3RlcmluZyBjb24gbGEgZXN0cmF0aWZpY2FjacOzbiBzb2Npb2Vjb27Ds21pY2EgZGVsIG1lcmNhZG8gaW5tb2JpbGlhcmlvLg0KDQoqKkludGVycHJldGFjacOzbiBnZW5lcmFsKioNCkVsIGFuw6FsaXNpcyBkZSBjb25nbG9tZXJhZG9zIHBlcm1pdGnDsyBzZWdtZW50YXIgbGFzIHByb3BpZWRhZGVzIGVuIDMgZ3J1cG9zLiBMb3MgcGVyZmlsZXMgcG9yIGNsdXN0ZXIgZXZpZGVuY2lhbiBkaWZlcmVuY2lhcyBlbiB2YXJpYWJsZXMgYXNvY2lhZGFzIGFsIHRhbWHDsW8gZGVsIGlubXVlYmxlLCBkb3RhY2nDs24gKGJhw7FvcywgcGFycXVlYWRlcm9zLCBoYWJpdGFjaW9uZXMpIHkgbml2ZWwgc29jaW9lY29uw7NtaWNvIChlc3RyYXRvKSwgbG8gcXVlIHN1Z2llcmUgbGEgZXhpc3RlbmNpYSBkZSBzZWdtZW50b3MgZGUgbWVyY2FkbyBkaWZlcmVuY2lhZG9zLg0KDQpMYSBkaXN0cmlidWNpw7NuIGRlIGNsdXN0ZXJzIHBvciB6b25hIHkgZXN0cmF0byByZXZlbGEgZGluw6FtaWNhcyBlc3BlY8OtZmljYXMgZGVsIG1lcmNhZG86IGNpZXJ0b3Mgc2VnbWVudG9zIHNlIGNvbmNlbnRyYW4gZW4gZGV0ZXJtaW5hZGFzIHpvbmFzIHkgZXN0cmF0b3MsIGxvIHF1ZSBwdWVkZSBvcmllbnRhciBlc3RyYXRlZ2lhcyBkZSB2YWxvcmFjacOzbiwgY29tcHJhL3ZlbnRhIHkgZm9jYWxpemFjacOzbiBjb21lcmNpYWwuDQoNCiMjIFJlc3VsdGFkb3MgZGVsIEFuw6FsaXNpcyBkZSBDb3JyZXNwb25kZW5jaWEgIA0KUmVsYWNpb25lcyBlbnRyZSB2YXJpYWJsZXMgY2F0ZWfDs3JpY2FzLg0KDQpDb24gbGEgdGFibGEgY3J1emFkYSBlbnRyZSBsYXMgem9uYXMgeSBsb3MgYmFycmlvcywgc2UgdmlzdWFsaXphIGVsIGFuYWxpc2lzIGRlIGNvcnJlc3BvbmRlbmNpYSBkZSBlc3RhcyB2YXJpYWJsZXMuDQoNCmBgYHtyIGNhLXBsb3Qtem9uYS1iYXJyaW8sIGZpZy53aWR0aD03LCBmaWcuaGVpZ2h0PTV9DQpjYV96b25hX2JhcnJpbyA8LSBDQSh0YWJfem9uYV9iYXJyaW8sIGdyYXBoID0gRkFMU0UpDQoNCnBfYmlfem9uYV9iYXJyaW8gPC0gZnZpel9jYV9iaXBsb3QoDQogIGNhX3pvbmFfYmFycmlvLA0KICByZXBlbCA9IFRSVUUNCikNCg0KbWlfZmlndXJhKA0KICBwX2JpX3pvbmFfYmFycmlvLA0KICBpZCAgICAgPSAiZmlndXJhX3Jlc3VtZW4iLA0KICB0aXR1bG8gPSAiQW7DoWxpc2lzIGRlIENvcnJlc3BvbmRlbmNpYTogWm9uYSB2cyBCYXJyaW8iDQopDQpgYGANCkEgY29udGludWFjacOzbiwgc2UgdmlzdWFsaXphIGxhIGNvbnRyaWJ1Y2nDs24gcG9yIHpvbmFzIHkgYmFycmlvcyBhIGxhIGRpbWVuc2nDs24gMS4NCmBgYHtyIGNhLWNvbnRyaWItem9uYS1iYXJyaW8sIGZpZy53aWR0aD03LCBmaWcuaGVpZ2h0PTQuOH0NCnBfY29uX3pvbmFfZGltMSA8LSBmdml6X2NvbnRyaWIoY2Ffem9uYV9iYXJyaW8sIGNob2ljZSA9ICJyb3ciLCBheGVzID0gMSwgdG9wID0gMTApDQoNCm1pX2ZpZ3VyYSgNCiAgcF9jb25fem9uYV9kaW0xLA0KICBpZCAgICAgPSAiZmlndXJhX3Jlc3VtZW4iLA0KICB0aXR1bG8gPSAiQ29udHJpYnVjacOzbiBwb3Igem9uYXMgYSBkaW0gMSINCikNCg0KcF9jb25fYmFycmlvX2RpbTEgPC0gZnZpel9jb250cmliKGNhX3pvbmFfYmFycmlvLCBjaG9pY2UgPSAiY29sIiwgYXhlcyA9IDEsIHRvcCA9IDEwKQ0KDQptaV9maWd1cmEoDQogIHBfY29uX2JhcnJpb19kaW0xLA0KICBpZCAgICAgPSAiZmlndXJhX3Jlc3VtZW4iLA0KICB0aXR1bG8gPSAiQ29udHJpYnVjacOzbiBwb3IgYmFycmlvcyBhIGRpbSAxIg0KKQ0KYGBgDQoqKkludGVycHJldGFjacOzbioqDQpFbCBBbsOhbGlzaXMgZGUgQ29ycmVzcG9uZGVuY2lhIHBlcm1pdGnDsyBleHBsb3JhciBhc29jaWFjaW9uZXMgZW50cmUgdmFyaWFibGVzIGNhdGVnw7NyaWNhcyBkZWwgbWVyY2FkbyBpbm1vYmlsaWFyaW8uIExhcyBwcnVlYmFzIGNoaS1jdWFkcmFkbyBjb21wbGVtZW50YXJpYXMgZXZhbMO6YW4gc2kgZXhpc3RlIGRlcGVuZGVuY2lhIGVzdGFkw61zdGljYSBlbnRyZSBsYXMgY2F0ZWdvcsOtYXMuDQoNCkVuIGxvcyBtYXBhcyBmYWN0b3JpYWxlcywgY2F0ZWdvcsOtYXMgY2VyY2FuYXMgaW5kaWNhbiBwYXRyb25lcyBkZSBhc29jaWFjacOzbiAocG9yIGVqZW1wbG8sIGNpZXJ0b3MgYmFycmlvcyBzZSBhc29jaWFuIG3DoXMgZnVlcnRlbWVudGUgY29uIHpvbmFzIGVzcGVjw61maWNhcykuIExhcyBjb250cmlidWNpb25lcyBhIGxhcyBkaW1lbnNpb25lcyBheXVkYW4gYSBpZGVudGlmaWNhciBxdcOpIGNhdGVnb3LDrWFzIGV4cGxpY2FuIGxhIG1heW9yIHBhcnRlIGRlIGxhIHZhcmlhYmlsaWRhZCByZXByZXNlbnRhZGEgZW4gZWwgcGxhbm8uDQoNCiMgKipDb25jbHVzaW9uZXMgeSByZWNvbWVuZGFjaW9uZXMqKg0KDQpTw61udGVzaXMgZGUgaGFsbGF6Z29zIGNsYXZlIHkgcmVjb21lbmRhY2lvbmVzIGVzdHJhdMOpZ2ljYXMgcGFyYSBsYSBlbXByZXNhIGlubW9iaWxpYXJpYS4NCg0KIyMgQ29uY2x1c2lvbmVzIA0KDQpFbCBtZXJjYWRvIGlubW9iaWxpYXJpbyBhbmFsaXphZG8gcHJlc2VudGEgdW5hIGVzdHJ1Y3R1cmEgY2xhcmFtZW50ZSBzZWdtZW50YWRhLCBkb25kZSBlbCBwcmVjaW8geSBlbCDDoXJlYSBjb25zdHJ1aWRhIGFjdMO6YW4gY29tbyBlamVzIGRvbWluYW50ZXMgZGUgZGlmZXJlbmNpYWNpw7NuLCBtaWVudHJhcyBxdWUgbGEgdWJpY2FjacOzbiBnZW9ncsOhZmljYSB5IGVsIGVzdHJhdG8gc29jaW9lY29uw7NtaWNvIGRldGVybWluYW4gcGF0cm9uZXMgZXNwYWNpYWxlcyBkZSBsYSBvZmVydGEuIExhIGVtcHJlc2EgZGlzcG9uZSBkZSBzdWZpY2llbnRlIGluZm9ybWFjacOzbiBwYXJhIGltcGxlbWVudGFyIGVzdHJhdGVnaWFzIGNvbWVyY2lhbGVzIGRpZmVyZW5jaWFkYXMgcG9yIHNlZ21lbnRvIHkgdGVycml0b3Jpby4NCg0KIyMgUmVjb21lbmRhY2lvbmVzDQoNCjEuICBJbXBsZW1lbnRhciB1bmEgZXN0cmF0ZWdpYSBjb21lcmNpYWwgcG9yIHNlZ21lbnRvcw0KDQogICAgKipBY2Npb25lcyByZWNvbWVuZGFkYXMqKg0KICAgIGEuICBEaXNlw7FhciBwb3J0YWZvbGlvcyBlc3BlY8OtZmljb3MgcGFyYToNCiAgICANCiAgICAgICAgLSBTZWdtZW50byBwcmVtaXVtDQogICAgICAgIA0KICAgICAgICAtIFNlZ21lbnRvIG1lZGlvDQogICAgICAgIA0KICAgICAgICAtIFNlZ21lbnRvIGVjb27Ds21pY28NCiAgICANCiAgICBiLiAgUGVyc29uYWxpemFyIG1lbnNhamVzIHkgY2FuYWxlcyBkZSB2ZW50YSBwb3Igc2VnbWVudG8uDQogICAgDQogICAgYy4gIEFqdXN0YXIgbWV0YXMgY29tZXJjaWFsZXMgcG9yIHRpcG8gZGUgY2xpZW50ZSBvYmpldGl2by4NCg0KMi4gIFByaW9yaXphciBsYSBnZXN0acOzbiB0ZXJyaXRvcmlhbCBkZWwgaW52ZW50YXJpbw0KDQogICAgKipBY2Npb25lcyByZWNvbWVuZGFkYXMqKg0KICAgIGEuICBDb25jZW50cmFyIGNhcHRhY2nDs24gZGUgaW5tdWVibGVzIGRlIGFsdG8gdmFsb3Igc2Vnw7puIHpvbmFzLg0KICAgIA0KICAgIGIuICBEZWZpbmlyIHByZWNpb3MgZGUgcmVmZXJlbmNpYSBkaWZlcmVuY2lhZG9zIHBvciB6b25hLg0KICAgIA0KICAgIGMuICBJbXBsZW1lbnRhciBjYW1wYcOxYXMgZGUgbWFya2V0aW5nIGhpcGVybG9jYWwuDQogICAgDQoqKk1lbnNhamUgZWplY3V0aXZvKiogTGEgZW1wcmVzYSBjdWVudGEgY29uIHVuYSBlc3RydWN0dXJhIGRlIGRhdG9zIHF1ZSBwZXJtaXRlIGV2b2x1Y2lvbmFyIGRlc2RlIHVuIGVuZm9xdWUgZGVzY3JpcHRpdm8gaGFjaWEgdW5hIGVzdHJhdGVnaWEgaW5tb2JpbGlhcmlhIGJhc2FkYSBlbiBhbmFsw610aWNhIGF2YW56YWRhLCBkb25kZSBsYSBzZWdtZW50YWNpw7NuLCBlbCBwcmljaW5nIGludGVsaWdlbnRlIHkgbGEgZm9jYWxpemFjacOzbiBnZW9ncsOhZmljYSBzZXLDoW4gbG9zIHByaW5jaXBhbGVzIG1vdG9yZXMgZGUgdmVudGFqYSBjb21wZXRpdGl2YS4NCg==