1: Carga de datos

1.A: Lectura y analisis de estructura del archivo

Procedemos a cargar el archivo de entrada y verificar la cantidad de variables y casos. Para ello tenemos que descomprimir el archivo antes de cargalo, y tabmien especificar el encoding correcto (UTF-8) para la carga correcta de caracteres especiales.

Debido a que la carga de los ids cuesta mucho mas memoria que todo el resto y no proporciona informacion de utilidad para el analisis se descarta de entrada.

# carga libreria tidyverse
suppressPackageStartupMessages(library(tidyverse))

#carga de datos
ar_properties <- read.csv(unz('ar_properties.zip', 'ar_properties.csv'), row.names = NULL, stringsAsFactors = TRUE, encoding = "UTF-8") %>% select( -c('id') )

#verificaion de casos y variables
head(ar_properties)
nrow(ar_properties)
[1] 388891

1.B: Preprocesado y filtrado

Cargamos la libreria tidyverse para realizar los filtrados, y procedemos a extraer los casos de interes:

# Filtrado de los casos deseados
ar_properties <- ar_properties %>% filter(
    l1             == 'Argentina' &
    l2             == 'Capital Federal' &
    currency       == 'USD' &
    operation_type == 'Venta' &
    property_type  %in% c('Casa', 'Departamento', 'PH'))

1.C: Preprocesado y filtrado

Una vez filtrados los casos, seleccionamos las variables deseadas y reajustamos los niveles de las variables de categoria (descartamos las categorias que ya no figuran en nuestros datos). Verificamos nuevamente la cantidad de registros y variables que quedan

# Seleccion de las variables de interes
ar_properties <- ar_properties %>% select(
  l3,
  rooms,
  bedrooms,
  bathrooms,
  surface_total,
  surface_covered,
  price,
  property_type )

# eliminacion de categorias en desuso
ar_properties <- droplevels(ar_properties)

#verificacion de casos y variables
nrow(ar_properties)
[1] 61905
ncol(ar_properties)
[1] 8

2: Analisis exploratorio (I)

2.A: Analisis de valores distintos y faltantes

calculamos la cantidad de valores distintos y faltantes, y los analizamos:

# calculamos la cantidad de valores distintos
uniques.values <- apply(ar_properties, 2, n_distinct)
as.data.frame(uniques.values)

# calculamos la cantidad de valores faltantes
missing.values <- colSums(is.na(ar_properties))
as.data.frame(missing.values)

Curiosamente, notamos que para el campo l3 que corresponde al barrio porteño, hay 57 valores distintos (58 si tomamos en cuenta NA). Esto se contradice con los 48 barrios porteños existentes, por lo que analizamos los barrios inesperados:

barrios.portenos.officiales <- c("Agronomía", "Almagro", "Balvanera", "Barracas", "Belgrano", "Boedo", "Caballito", "Chacarita", "Coghlan", "Colegiales", "Constitución", "Flores", "Floresta", "Boca", "Paternal", "Liniers", "Mataderos", "Monserrat", "Monte Castro", "Pompeya", "Nuñez", "Palermo", "Parque Avellaneda", "Parque Chacabuco", "Parque Chas", "Parque Patricios", "Puerto Madero", "Recoleta", "Retiro", "Saavedra", "San Cristobal", "San Nicolás", "San Telmo", "Velez Sarsfield", "Versalles", "Villa Crespo", "Villa del Parque", "Villa Devoto", "Villa General Mitre", "Villa Lugano", "Villa Luro", "Villa Ortuzar", "Villa Pueyrredón", "Villa Real", "Villa Riachuelo", "Villa Santa Rita", "Villa Soldati", "Villa Urquiza")
sort(setdiff(unique(ar_properties$l3), barrios.portenos.officiales))
[1] "Abasto"               "Barrio Norte"         "Catalinas"            "Centro / Microcentro"
[5] "Congreso"             "Las Cañitas"          "Once"                 "Parque Centenario"   
[9] "Tribunales"          

Observamos como los barrios ‘extra’ corresponden a denominaciones no-oficiales de ciertas regions de la CABA.

2.B: Matriz de correlacion

Para el calculo de la matriz de correlacion, necesitamos poder extraer las variables del tipo numerico. Dado que esta funcion sera utilizada en pasos posteriores, será de utilidad crear una funcion que permita automatizar esta funcion; y ademas agregamos la opcion para seleccionar columnas especificas en lugar de todas las numericas. Luego procedemos a calcular la correlacion de las variables numericas.

# funcion para obtener las columnas especificadas, o las numericas en caso de no especificarse
get.numeric.columns <- function(df, columns.to.get=NULL) {
    if (is.null(columns.to.get))
        columns.to.get <- colnames(df)[unlist(lapply(df, is.numeric))]

    return(columns.to.get)
}

# calculamos la correlacion de las variables numericas, descartando los casos incompletos
cor(ar_properties[,get.numeric.columns(ar_properties)], use="complete.obs", method="pearson")
                     rooms   bedrooms  bathrooms surface_total surface_covered      price
rooms           1.00000000 0.92138719 0.61335026    0.06828238      0.07468335 0.48748747
bedrooms        0.92138719 1.00000000 0.61578024    0.06746895      0.07206826 0.43221753
bathrooms       0.61335026 0.61578024 1.00000000    0.06234262      0.06777010 0.59904254
surface_total   0.06828238 0.06746895 0.06234262    1.00000000      0.69656225 0.05095265
surface_covered 0.07468335 0.07206826 0.06777010    0.69656225      1.00000000 0.06257960
price           0.48748747 0.43221753 0.59904254    0.05095265      0.06257960 1.00000000

3: Preparacion de datos

3.A: Eliminacion de variables altamente correlacionadas

Dado que las variables rooms y bedrooms presentan alta correlacion, procedemos a eliminar una de ellas, la de mayor cantaidad de faltantes (bedrooms)

# eliminacion de la variable `bedrooms`
ar_properties <- ar_properties %>% select( -c('bedrooms') )

3.B: Filtrado de los casos incompletos

Filtramos los casos incompletos y validamos la cantidad de variables y casos

# eliminacion de la variable `bedrooms`
ar_properties <- ar_properties[complete.cases(ar_properties),]

#Validamos la cantidad de casos y variables
nrow(ar_properties)
[1] 51210
ncol(ar_properties)
[1] 7

4: Analisis exploratorio (II)

4.A: Obtencion de estadisticas generales

Obtenemos estadisticas de la variable precio. Generamos funciones que reutilizaremos mas tarde.


# Funcion para generar estadisticas con la media incluida
summary.with.mean <- function(data)
{
    summary <- summary(data)
    summary['mean'] = mean(data)
    return( summary )
}

# Caluclamos las estadisticas
summary.with.mean(ar_properties$price)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max.    mean 
   6000  119000  170000  251577  270000 6000000  251577 
# Y realizamos un histograma
ggplot(ar_properties, aes(x = price)) + geom_histogram()

Notamos que hay un gran rango de precios, y que la mayoria de los valores se concentran en los valores mas bajos. Por ese motivo, repetimos el histograma pero utiliando una escala logaritmica para poder observar mejor la distribucion.

ggplot(ar_properties, aes(x = price)) + geom_histogram(alpha=0.5, position="identity", aes(y = ..density..)) + scale_x_log10() + geom_density(alpha=0.5)

4.B: Obtencion de estadisticas por tipo de propiedad

Realizamos el mismo analisis anterior, pero segmentado por tipo de propiedad.

# Calculamos las estadisticas, discriminando por tipo de propiedad
tapply(ar_properties$price, ar_properties$property_type, summary.with.mean)
$Casa
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max.    mean 
  20000  235000  335000  434189  490000 5000000  434189 

$Departamento
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max.    mean 
   6000  115000  164000  246856  260000 6000000  246856 

$PH
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max.    mean 
  32000  137000  190000  218747  270000 1500000  218747 
# Realizamos el histograma, discriminando por tipo de propiedad
ggplot(ar_properties, aes(x=price, fill=property_type)) +
    geom_histogram(alpha=0.5, position="identity", aes(y = ..density..)) +
    geom_density(alpha=0.5) +
    scale_x_log10()

4.C: Boxplots

Graficamos los boxplots del precio por cada tipo de propiedad. Siguiendo la misma logica, escalamos el eje de precios logaritmicamente.

# Graficamos los boxplots
ggplot(ar_properties, aes(x=property_type, y=price, fill=property_type)) +
    geom_boxplot() +
    scale_y_log10()

Obervamos como los precios tienen una tendencia del tipo Casa > PH > depto (ya obervada tambien previamente en los histogramas)

4.D: Correlograma

suppressPackageStartupMessages(library(GGally))

# Graficamos el correlograma
ggcorr(ar_properties[,get.numeric.columns(ar_properties)], method = c("everything", "pearson")) 

5: Outliers

Analizamos como se componen los precios en los extremos superiores e inferiores de la distribucion

# Cortes de precios para los cuantiles en los extremos
quant.inf <- quantile(ar_properties$price, probs=seq(0, 0.005, length.out = 6) )
quant.sup <- quantile(ar_properties$price, probs=seq(.995, 1, length.out = 6) )

quant.inf
   0%  0.1%  0.2%  0.3%  0.4%  0.5% 
 6000 35000 45000 49900 53000 55000 
quant.sup
  99.5%   99.6%   99.7%   99.8%   99.9%    100% 
1979550 2300000 2500000 2879100 3500000 6000000 

Analizando los resultados de zonaProp, la propiedad mas barata en venta en capital federal esta por sobre 50.000 dolares. Combinando este dato con la informacion de los cuartiles obtenida previamente, podemos asegurar que descartar los valores inferiores a U$S 50.000 seria descartar menos del 0.5% de las muestras. En cuanto a valores superiores, se prosiguió a descartar el mismo porcentage de corte que para las muestras inferiores.


ar_properties.sin.outliers = ar_properties %>% filter(
    price > quant.inf['0.5%'] & price < quant.sup['99.5%'] )

Como caso de interes, tambien filtramos los outliers utilizando la tecnica de isolation tree, utilizando las variables de interes y filtrando una cantidad demejante de muestras, pero utilizando como criterio su puntaje de anomalia

# Cargamos la libreria necesaria
suppressPackageStartupMessages(library("solitude"))

# iniciamos el algoritmo utilizando solamente las variables que vamos a correlacionar
iso <- solitude::isolationForest$new()
iso$fit(ar_properties %>% select( c("rooms", "price", "surface_total") ) )
Building Isolation Forest ... done
Computing depth of terminal nodes ... 
done
ar_properties.sin.outliers.isolationtree <- ar_properties

# Usamos los resultados para filtras las muestras mas anomalas, y luego descartamos esta columna
ar_properties.sin.outliers.isolationtree$anomalyScore = iso$scores$anomaly_score
ar_properties.sin.outliers.isolationtree <- ar_properties.sin.outliers.isolationtree %>% filter( anomalyScore < 0.6 ) %>% select( -c("anomalyScore") )

6: Analisis exploratorios (III)

# Calculamos las estadisticas, discriminando por tipo de propiedad
tapply(ar_properties.sin.outliers$price, ar_properties.sin.outliers$property_type, summary.with.mean)
$Casa
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max.    mean 
  62000  235000  330000  397974  485000 1950000  397974 

$Departamento
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max.    mean 
  55400  115000  164000  234475  260000 1970000  234475 

$PH
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max.    mean 
  58000  138000  195000  219302  272750 1500000  219302 
# Realizamos el histograma, discriminando por tipo de propiedad
ggplot(ar_properties.sin.outliers, aes(x=price, fill=property_type)) +
    geom_histogram(alpha=0.5, position="identity", aes(y = ..density..)) +
    geom_density(alpha=0.5) +
    scale_x_log10()


# Graficamos los boxplots
ggplot(ar_properties.sin.outliers, aes(x=property_type, y=price, fill=property_type)) +
    geom_boxplot() +
    scale_y_log10()


# Graficamos el correlograma
ggcorr(ar_properties.sin.outliers[,get.numeric.columns(ar_properties.sin.outliers)], method = c("everything", "pearson"))

Se puede observar (con especial detalle en los boxplots) como gran cantidad de los outliers desaparecieron. Cabe destacar que esta es una tecnica un poco ingenua para la eliminacion de los mismos, ya que no considera la posibilidad de outliers debido a la relacion de las variables entre sí, y solamente descarta los extremos en cada dimension (en este caso, la dimension precio)

7: Modelo lineal

7.A: Generacion del modelo lineal
#calculamos los modelos lineales indicados
linear.model.surface <- lm(price ~ surface_total, data = ar_properties.sin.outliers)
linear.model.rooms <- lm(price ~ rooms, data = ar_properties.sin.outliers)
7.B: Descripcion de los modelos lineales

superficie vs precio

summary(linear.model.surface)

Call:
lm(formula = price ~ surface_total, data = ar_properties.sin.outliers)

Residuals:
     Min       1Q   Median       3Q      Max 
-2103818  -119487   -69187    30879  1727990 

Coefficients:
               Estimate Std. Error t value Pr(>|t|)    
(Intercept)   237800.77     972.82  244.45   <2e-16 ***
surface_total     16.51       1.18   13.99   <2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 217300 on 50630 degrees of freedom
Multiple R-squared:  0.003851,  Adjusted R-squared:  0.003832 
F-statistic: 195.8 on 1 and 50630 DF,  p-value: < 2.2e-16

ambientes vs precio

summary(linear.model.rooms)

Call:
lm(formula = price ~ rooms, data = ar_properties.sin.outliers)

Residuals:
     Min       1Q   Median       3Q      Max 
-2239135   -84697   -26697    32289  1692289 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)    
(Intercept)  -9332.7     1809.4  -5.158 2.51e-07 ***
rooms        89014.6      580.8 153.272  < 2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 179900 on 50630 degrees of freedom
Multiple R-squared:  0.3169,    Adjusted R-squared:  0.3169 
F-statistic: 2.349e+04 on 1 and 50630 DF,  p-value: < 2.2e-16
7.B (bis): Descripcion de los modelos lineales - isolation tree

Verificamos que la cantidad de casos en ambos filtrados sea semejante


nrow(ar_properties.sin.outliers)
[1] 50632
nrow(ar_properties.sin.outliers.isolationtree)
[1] 50981

Analizamos los modelos:

superficie vs precio

summary(lm(price ~ surface_total, data = ar_properties.sin.outliers.isolationtree))

Call:
lm(formula = price ~ surface_total, data = ar_properties.sin.outliers.isolationtree)

Residuals:
     Min       1Q   Median       3Q      Max 
-2303976   -45505   -17164    19403  2630776 

Coefficients:
              Estimate Std. Error t value Pr(>|t|)    
(Intercept)   33868.30    1252.81   27.03   <2e-16 ***
surface_total  2339.93      10.68  219.06   <2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 180900 on 50979 degrees of freedom
Multiple R-squared:  0.4849,    Adjusted R-squared:  0.4849 
F-statistic: 4.799e+04 on 1 and 50979 DF,  p-value: < 2.2e-16

ambientes vs precio

summary(lm(price ~ rooms, data = ar_properties.sin.outliers.isolationtree))

Call:
lm(formula = price ~ rooms, data = ar_properties.sin.outliers.isolationtree)

Residuals:
    Min      1Q  Median      3Q     Max 
-909837  -91525  -28961   38139 3030147 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)    
(Intercept) -38467.3     2166.3  -17.76   <2e-16 ***
rooms       101664.1      700.5  145.13   <2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 212000 on 50979 degrees of freedom
Multiple R-squared:  0.2924,    Adjusted R-squared:  0.2924 
F-statistic: 2.106e+04 on 1 and 50979 DF,  p-value: < 2.2e-16

Y realizamos el correlograma para esta version de filtrado

ggcorr(ar_properties.sin.outliers.isolationtree[,get.numeric.columns(ar_properties.sin.outliers.isolationtree)], method = c("everything", "pearson"))

7.C: Analisis / conclusiones
filtrado por extremos

Se puede observar en los valores de R-squared (mayor en el caso de ambiente-precio) que la correlacion es mas fuete para esta variable (ya se podia observar este efecto en el correlograma). Tambien observamos como los residuos son menores para este modelo lineal. Dado estos motivos, el modelo de precio-ambiente parece ser un mejor predictor de precio que el de sueprficie-ambiente.

filtrado por isolation tree

Cuando el filtrado se realiza utilizando otra tecnica, se observa como los resultados anteriores se invierten: el modelo de area-precio se vuelve un mejor predictor en este caso; pero ademas se potencia, dado que este modelo (filtrado por isolation tree, y modelando area vs precio) es la que expresa una mayor correlacion que todos los otros casos; producto de haber filtrado no solo los valores extremos, sino tambien usando tecnicas mas avanzadas para detectar outliers de valores intermedios, que habrian sesgado el modelo a uno menor performante que el de ambientes vs precio (esto se observa claramente en los dispersogramas a continuacion)


ggplot(ar_properties.sin.outliers, aes(x=rooms, y=price)) + geom_point(color="red") + geom_smooth(method='lm') + ggtitle("Precio vs Ambientes - filtrado ingenuo")



ggplot(ar_properties.sin.outliers, aes(x=surface_total, y=price)) + geom_point(color="red") + geom_smooth(method='lm') + ggtitle("Precio vs Area - filtrado ingenuo")



ggplot(ar_properties.sin.outliers.isolationtree, aes(x=rooms, y=price)) + geom_point(color="green") + geom_smooth(method='lm') + ggtitle("Precio vs Ambientes - filtrado por isolation tree")



ggplot(ar_properties.sin.outliers.isolationtree, aes(x=surface_total, y=price)) + geom_point(color="green") + geom_smooth(method='lm') + ggtitle("Precio vs Area - filtrado por isolation tree")

LS0tDQp0aXRsZTogIlRyYWJham8gUHJhY3RpY28gMSINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KLS0tDQoNCg0KIyMjIDE6IENhcmdhIGRlIGRhdG9zDQoNCiMjIyMgMS5BOiBMZWN0dXJhIHkgYW5hbGlzaXMgZGUgZXN0cnVjdHVyYSBkZWwgYXJjaGl2bw0KDQpQcm9jZWRlbW9zIGEgY2FyZ2FyIGVsIGFyY2hpdm8gZGUgZW50cmFkYSB5IHZlcmlmaWNhciBsYSBjYW50aWRhZCBkZSB2YXJpYWJsZXMgeSBjYXNvcy4NClBhcmEgZWxsbyB0ZW5lbW9zIHF1ZSBkZXNjb21wcmltaXIgZWwgYXJjaGl2byBhbnRlcyBkZSBjYXJnYWxvLCB5IHRhYm1pZW4gZXNwZWNpZmljYXIgZWwgZW5jb2RpbmcgY29ycmVjdG8gKGBVVEYtOGApIHBhcmEgbGEgY2FyZ2EgY29ycmVjdGEgZGUgY2FyYWN0ZXJlcyBlc3BlY2lhbGVzLg0KDQpEZWJpZG8gYSBxdWUgbGEgY2FyZ2EgZGUgbG9zIGBpZGBzIGN1ZXN0YSBtdWNobyBtYXMgbWVtb3JpYSBxdWUgdG9kbyBlbCByZXN0byB5IG5vIHByb3BvcmNpb25hIGluZm9ybWFjaW9uIGRlIHV0aWxpZGFkIHBhcmEgZWwgYW5hbGlzaXMgc2UgZGVzY2FydGEgZGUgZW50cmFkYS4NCg0KYGBge3J9DQojIGNhcmdhIGxpYnJlcmlhIHRpZHl2ZXJzZQ0Kc3VwcHJlc3NQYWNrYWdlU3RhcnR1cE1lc3NhZ2VzKGxpYnJhcnkodGlkeXZlcnNlKSkNCg0KI2NhcmdhIGRlIGRhdG9zDQphcl9wcm9wZXJ0aWVzIDwtIHJlYWQuY3N2KHVueignYXJfcHJvcGVydGllcy56aXAnLCAnYXJfcHJvcGVydGllcy5jc3YnKSwgcm93Lm5hbWVzID0gTlVMTCwgc3RyaW5nc0FzRmFjdG9ycyA9IFRSVUUsIGVuY29kaW5nID0gIlVURi04IikgJT4lIHNlbGVjdCggLWMoJ2lkJykgKQ0KDQojdmVyaWZpY2Fpb24gZGUgY2Fzb3MgeSB2YXJpYWJsZXMNCmhlYWQoYXJfcHJvcGVydGllcykNCm5yb3coYXJfcHJvcGVydGllcykNCmBgYA0KDQoNCiMjIyMgMS5COiBQcmVwcm9jZXNhZG8geSBmaWx0cmFkbw0KDQpDYXJnYW1vcyBsYSBsaWJyZXJpYSB0aWR5dmVyc2UgcGFyYSByZWFsaXphciBsb3MgZmlsdHJhZG9zLCB5IHByb2NlZGVtb3MgYSBleHRyYWVyIGxvcyBjYXNvcyBkZSBpbnRlcmVzOg0KDQpgYGB7cn0NCiMgRmlsdHJhZG8gZGUgbG9zIGNhc29zIGRlc2VhZG9zDQphcl9wcm9wZXJ0aWVzIDwtIGFyX3Byb3BlcnRpZXMgJT4lIGZpbHRlcigNCiAgICBsMSAgICAgICAgICAgICA9PSAnQXJnZW50aW5hJyAmDQogICAgbDIgICAgICAgICAgICAgPT0gJ0NhcGl0YWwgRmVkZXJhbCcgJg0KICAgIGN1cnJlbmN5ICAgICAgID09ICdVU0QnICYNCiAgICBvcGVyYXRpb25fdHlwZSA9PSAnVmVudGEnICYNCiAgICBwcm9wZXJ0eV90eXBlICAlaW4lIGMoJ0Nhc2EnLCAnRGVwYXJ0YW1lbnRvJywgJ1BIJykpDQpgYGANCg0KDQojIyMjIDEuQzogUHJlcHJvY2VzYWRvIHkgZmlsdHJhZG8NCg0KVW5hIHZleiBmaWx0cmFkb3MgbG9zIGNhc29zLCBzZWxlY2Npb25hbW9zIGxhcyB2YXJpYWJsZXMgZGVzZWFkYXMgeSByZWFqdXN0YW1vcyBsb3Mgbml2ZWxlcyBkZSBsYXMgdmFyaWFibGVzIGRlIGNhdGVnb3JpYSAoZGVzY2FydGFtb3MgbGFzIGNhdGVnb3JpYXMgcXVlIHlhIG5vIGZpZ3VyYW4gZW4gbnVlc3Ryb3MgZGF0b3MpLiBWZXJpZmljYW1vcyBudWV2YW1lbnRlIGxhIGNhbnRpZGFkIGRlIHJlZ2lzdHJvcyB5IHZhcmlhYmxlcyBxdWUgcXVlZGFuDQoNCmBgYHtyfQ0KIyBTZWxlY2Npb24gZGUgbGFzIHZhcmlhYmxlcyBkZSBpbnRlcmVzDQphcl9wcm9wZXJ0aWVzIDwtIGFyX3Byb3BlcnRpZXMgJT4lIHNlbGVjdCgNCiAgbDMsDQogIHJvb21zLA0KICBiZWRyb29tcywNCiAgYmF0aHJvb21zLA0KICBzdXJmYWNlX3RvdGFsLA0KICBzdXJmYWNlX2NvdmVyZWQsDQogIHByaWNlLA0KICBwcm9wZXJ0eV90eXBlICkNCg0KIyBlbGltaW5hY2lvbiBkZSBjYXRlZ29yaWFzIGVuIGRlc3Vzbw0KYXJfcHJvcGVydGllcyA8LSBkcm9wbGV2ZWxzKGFyX3Byb3BlcnRpZXMpDQoNCiN2ZXJpZmljYWNpb24gZGUgY2Fzb3MgeSB2YXJpYWJsZXMNCm5yb3coYXJfcHJvcGVydGllcykNCm5jb2woYXJfcHJvcGVydGllcykNCmBgYA0KDQoNCiMjIyAyOiBBbmFsaXNpcyBleHBsb3JhdG9yaW8gKEkpDQoNCiMjIyMgMi5BOiBBbmFsaXNpcyBkZSB2YWxvcmVzIGRpc3RpbnRvcyB5IGZhbHRhbnRlcw0KDQpjYWxjdWxhbW9zIGxhIGNhbnRpZGFkIGRlIHZhbG9yZXMgZGlzdGludG9zIHkgZmFsdGFudGVzLCB5IGxvcyBhbmFsaXphbW9zOg0KDQpgYGB7cn0NCiMgY2FsY3VsYW1vcyBsYSBjYW50aWRhZCBkZSB2YWxvcmVzIGRpc3RpbnRvcw0KdW5pcXVlcy52YWx1ZXMgPC0gYXBwbHkoYXJfcHJvcGVydGllcywgMiwgbl9kaXN0aW5jdCkNCmFzLmRhdGEuZnJhbWUodW5pcXVlcy52YWx1ZXMpDQoNCiMgY2FsY3VsYW1vcyBsYSBjYW50aWRhZCBkZSB2YWxvcmVzIGZhbHRhbnRlcw0KbWlzc2luZy52YWx1ZXMgPC0gY29sU3Vtcyhpcy5uYShhcl9wcm9wZXJ0aWVzKSkNCmFzLmRhdGEuZnJhbWUobWlzc2luZy52YWx1ZXMpDQpgYGANCg0KQ3VyaW9zYW1lbnRlLCBub3RhbW9zIHF1ZSBwYXJhIGVsIGNhbXBvICoqbDMqKiBxdWUgY29ycmVzcG9uZGUgYWwgYmFycmlvIHBvcnRlw7FvLCBoYXkgNTcgdmFsb3JlcyBkaXN0aW50b3MgKDU4IHNpIHRvbWFtb3MgZW4gY3VlbnRhIE5BKS4gRXN0byBzZSBjb250cmFkaWNlIGNvbiBsb3MgNDggYmFycmlvcyBwb3J0ZcOxb3MgZXhpc3RlbnRlcywgcG9yIGxvIHF1ZSBhbmFsaXphbW9zIGxvcyBiYXJyaW9zIGluZXNwZXJhZG9zOg0KDQpgYGB7cn0NCmJhcnJpb3MucG9ydGVub3Mub2ZmaWNpYWxlcyA8LSBjKCJBZ3Jvbm9tw61hIiwgIkFsbWFncm8iLCAiQmFsdmFuZXJhIiwgIkJhcnJhY2FzIiwgIkJlbGdyYW5vIiwgIkJvZWRvIiwgIkNhYmFsbGl0byIsICJDaGFjYXJpdGEiLCAiQ29naGxhbiIsICJDb2xlZ2lhbGVzIiwgIkNvbnN0aXR1Y2nDs24iLCAiRmxvcmVzIiwgIkZsb3Jlc3RhIiwgIkJvY2EiLCAiUGF0ZXJuYWwiLCAiTGluaWVycyIsICJNYXRhZGVyb3MiLCAiTW9uc2VycmF0IiwgIk1vbnRlIENhc3RybyIsICJQb21wZXlhIiwgIk51w7FleiIsICJQYWxlcm1vIiwgIlBhcnF1ZSBBdmVsbGFuZWRhIiwgIlBhcnF1ZSBDaGFjYWJ1Y28iLCAiUGFycXVlIENoYXMiLCAiUGFycXVlIFBhdHJpY2lvcyIsICJQdWVydG8gTWFkZXJvIiwgIlJlY29sZXRhIiwgIlJldGlybyIsICJTYWF2ZWRyYSIsICJTYW4gQ3Jpc3RvYmFsIiwgIlNhbiBOaWNvbMOhcyIsICJTYW4gVGVsbW8iLCAiVmVsZXogU2Fyc2ZpZWxkIiwgIlZlcnNhbGxlcyIsICJWaWxsYSBDcmVzcG8iLCAiVmlsbGEgZGVsIFBhcnF1ZSIsICJWaWxsYSBEZXZvdG8iLCAiVmlsbGEgR2VuZXJhbCBNaXRyZSIsICJWaWxsYSBMdWdhbm8iLCAiVmlsbGEgTHVybyIsICJWaWxsYSBPcnR1emFyIiwgIlZpbGxhIFB1ZXlycmVkw7NuIiwgIlZpbGxhIFJlYWwiLCAiVmlsbGEgUmlhY2h1ZWxvIiwgIlZpbGxhIFNhbnRhIFJpdGEiLCAiVmlsbGEgU29sZGF0aSIsICJWaWxsYSBVcnF1aXphIikNCnNvcnQoc2V0ZGlmZih1bmlxdWUoYXJfcHJvcGVydGllcyRsMyksIGJhcnJpb3MucG9ydGVub3Mub2ZmaWNpYWxlcykpDQpgYGANCg0KT2JzZXJ2YW1vcyBjb21vIGxvcyBiYXJyaW9zICdleHRyYScgY29ycmVzcG9uZGVuIGEgZGVub21pbmFjaW9uZXMgbm8tb2ZpY2lhbGVzIGRlIGNpZXJ0YXMgcmVnaW9ucyBkZSBsYSBDQUJBLg0KDQoNCiMjIyMgMi5COiBNYXRyaXogZGUgY29ycmVsYWNpb24NCg0KUGFyYSBlbCBjYWxjdWxvIGRlIGxhIG1hdHJpeiBkZSBjb3JyZWxhY2lvbiwgbmVjZXNpdGFtb3MgcG9kZXIgZXh0cmFlciBsYXMgdmFyaWFibGVzIGRlbCB0aXBvIG51bWVyaWNvLiBEYWRvIHF1ZSBlc3RhIGZ1bmNpb24gc2VyYSB1dGlsaXphZGEgZW4gcGFzb3MgcG9zdGVyaW9yZXMsIHNlcsOhIGRlIHV0aWxpZGFkIGNyZWFyIHVuYSBmdW5jaW9uIHF1ZSBwZXJtaXRhIGF1dG9tYXRpemFyIGVzdGEgZnVuY2lvbjsgeSBhZGVtYXMgYWdyZWdhbW9zIGxhIG9wY2lvbiBwYXJhIHNlbGVjY2lvbmFyIGNvbHVtbmFzIGVzcGVjaWZpY2FzIGVuIGx1Z2FyIGRlIHRvZGFzIGxhcyBudW1lcmljYXMuIEx1ZWdvIHByb2NlZGVtb3MgYSBjYWxjdWxhciBsYSBjb3JyZWxhY2lvbiBkZSBsYXMgdmFyaWFibGVzIG51bWVyaWNhcy4NCg0KYGBge3J9DQojIGZ1bmNpb24gcGFyYSBvYnRlbmVyIGxhcyBjb2x1bW5hcyBlc3BlY2lmaWNhZGFzLCBvIGxhcyBudW1lcmljYXMgZW4gY2FzbyBkZSBubyBlc3BlY2lmaWNhcnNlDQpnZXQubnVtZXJpYy5jb2x1bW5zIDwtIGZ1bmN0aW9uKGRmLCBjb2x1bW5zLnRvLmdldD1OVUxMKSB7DQogICAgaWYgKGlzLm51bGwoY29sdW1ucy50by5nZXQpKQ0KICAgICAgICBjb2x1bW5zLnRvLmdldCA8LSBjb2xuYW1lcyhkZilbdW5saXN0KGxhcHBseShkZiwgaXMubnVtZXJpYykpXQ0KDQogICAgcmV0dXJuKGNvbHVtbnMudG8uZ2V0KQ0KfQ0KDQojIGNhbGN1bGFtb3MgbGEgY29ycmVsYWNpb24gZGUgbGFzIHZhcmlhYmxlcyBudW1lcmljYXMsIGRlc2NhcnRhbmRvIGxvcyBjYXNvcyBpbmNvbXBsZXRvcw0KY29yKGFyX3Byb3BlcnRpZXNbLGdldC5udW1lcmljLmNvbHVtbnMoYXJfcHJvcGVydGllcyldLCB1c2U9ImNvbXBsZXRlLm9icyIsIG1ldGhvZD0icGVhcnNvbiIpDQpgYGANCg0KDQojIyMgMzogUHJlcGFyYWNpb24gZGUgZGF0b3MNCg0KIyMjIyAzLkE6IEVsaW1pbmFjaW9uIGRlIHZhcmlhYmxlcyBhbHRhbWVudGUgY29ycmVsYWNpb25hZGFzDQoNCkRhZG8gcXVlIGxhcyB2YXJpYWJsZXMgYHJvb21zYCB5IGBiZWRyb29tc2AgcHJlc2VudGFuIGFsdGEgY29ycmVsYWNpb24sIHByb2NlZGVtb3MgYSBlbGltaW5hciB1bmEgZGUgZWxsYXMsIGxhIGRlIG1heW9yIGNhbnRhaWRhZCBkZSBmYWx0YW50ZXMgKGBiZWRyb29tc2ApDQoNCmBgYHtyfQ0KIyBlbGltaW5hY2lvbiBkZSBsYSB2YXJpYWJsZSBgYmVkcm9vbXNgDQphcl9wcm9wZXJ0aWVzIDwtIGFyX3Byb3BlcnRpZXMgJT4lIHNlbGVjdCggLWMoJ2JlZHJvb21zJykgKQ0KYGBgDQoNCg0KIyMjIyAzLkI6IEZpbHRyYWRvIGRlIGxvcyBjYXNvcyBpbmNvbXBsZXRvcw0KDQpGaWx0cmFtb3MgbG9zIGNhc29zIGluY29tcGxldG9zIHkgdmFsaWRhbW9zIGxhIGNhbnRpZGFkIGRlIHZhcmlhYmxlcyB5IGNhc29zDQoNCmBgYHtyfQ0KIyBlbGltaW5hY2lvbiBkZSBsYSB2YXJpYWJsZSBgYmVkcm9vbXNgDQphcl9wcm9wZXJ0aWVzIDwtIGFyX3Byb3BlcnRpZXNbY29tcGxldGUuY2FzZXMoYXJfcHJvcGVydGllcyksXQ0KDQojVmFsaWRhbW9zIGxhIGNhbnRpZGFkIGRlIGNhc29zIHkgdmFyaWFibGVzDQpucm93KGFyX3Byb3BlcnRpZXMpDQpuY29sKGFyX3Byb3BlcnRpZXMpDQpgYGANCg0KDQojIyMgNDogQW5hbGlzaXMgZXhwbG9yYXRvcmlvIChJSSkNCg0KIyMjIyA0LkE6IE9idGVuY2lvbiBkZSBlc3RhZGlzdGljYXMgZ2VuZXJhbGVzDQoNCk9idGVuZW1vcyBlc3RhZGlzdGljYXMgZGUgbGEgdmFyaWFibGUgcHJlY2lvLiBHZW5lcmFtb3MgZnVuY2lvbmVzIHF1ZSByZXV0aWxpemFyZW1vcyBtYXMgdGFyZGUuDQoNCmBgYHtyfQ0KIyBGdW5jaW9uIHBhcmEgZ2VuZXJhciBlc3RhZGlzdGljYXMgY29uIGxhIG1lZGlhIGluY2x1aWRhDQpzdW1tYXJ5LndpdGgubWVhbiA8LSBmdW5jdGlvbihkYXRhKQ0Kew0KICAgIHN1bW1hcnkgPC0gc3VtbWFyeShkYXRhKQ0KICAgIHN1bW1hcnlbJ21lYW4nXSA9IG1lYW4oZGF0YSkNCiAgICByZXR1cm4oIHN1bW1hcnkgKQ0KfQ0KDQojIENhbHVjbGFtb3MgbGFzIGVzdGFkaXN0aWNhcw0Kc3VtbWFyeS53aXRoLm1lYW4oYXJfcHJvcGVydGllcyRwcmljZSkNCg0KIyBZIHJlYWxpemFtb3MgdW4gaGlzdG9ncmFtYQ0KZ2dwbG90KGFyX3Byb3BlcnRpZXMsIGFlcyh4ID0gcHJpY2UpKSArIGdlb21faGlzdG9ncmFtKCkNCmBgYA0KDQpOb3RhbW9zIHF1ZSBoYXkgdW4gZ3JhbiByYW5nbyBkZSBwcmVjaW9zLCB5IHF1ZSBsYSBtYXlvcmlhIGRlIGxvcyB2YWxvcmVzIHNlIGNvbmNlbnRyYW4gZW4gbG9zIHZhbG9yZXMgbWFzIGJham9zLiBQb3IgZXNlIG1vdGl2bywgcmVwZXRpbW9zIGVsIGhpc3RvZ3JhbWEgcGVybyB1dGlsaWFuZG8gdW5hIGVzY2FsYSBsb2dhcml0bWljYSBwYXJhIHBvZGVyIG9ic2VydmFyIG1lam9yIGxhIGRpc3RyaWJ1Y2lvbi4NCg0KYGBge3J9DQpnZ3Bsb3QoYXJfcHJvcGVydGllcywgYWVzKHggPSBwcmljZSkpICsgZ2VvbV9oaXN0b2dyYW0oYWxwaGE9MC41LCBwb3NpdGlvbj0iaWRlbnRpdHkiLCBhZXMoeSA9IC4uZGVuc2l0eS4uKSkgKyBzY2FsZV94X2xvZzEwKCkgKyBnZW9tX2RlbnNpdHkoYWxwaGE9MC41KQ0KYGBgDQoNCiMjIyMgNC5COiBPYnRlbmNpb24gZGUgZXN0YWRpc3RpY2FzIHBvciB0aXBvIGRlIHByb3BpZWRhZA0KDQpSZWFsaXphbW9zIGVsIG1pc21vIGFuYWxpc2lzIGFudGVyaW9yLCBwZXJvIHNlZ21lbnRhZG8gcG9yIHRpcG8gZGUgcHJvcGllZGFkLg0KDQpgYGB7cn0NCiMgQ2FsY3VsYW1vcyBsYXMgZXN0YWRpc3RpY2FzLCBkaXNjcmltaW5hbmRvIHBvciB0aXBvIGRlIHByb3BpZWRhZA0KdGFwcGx5KGFyX3Byb3BlcnRpZXMkcHJpY2UsIGFyX3Byb3BlcnRpZXMkcHJvcGVydHlfdHlwZSwgc3VtbWFyeS53aXRoLm1lYW4pDQoNCiMgUmVhbGl6YW1vcyBlbCBoaXN0b2dyYW1hLCBkaXNjcmltaW5hbmRvIHBvciB0aXBvIGRlIHByb3BpZWRhZA0KZ2dwbG90KGFyX3Byb3BlcnRpZXMsIGFlcyh4PXByaWNlLCBmaWxsPXByb3BlcnR5X3R5cGUpKSArDQogICAgZ2VvbV9oaXN0b2dyYW0oYWxwaGE9MC41LCBwb3NpdGlvbj0iaWRlbnRpdHkiLCBhZXMoeSA9IC4uZGVuc2l0eS4uKSkgKw0KICAgIGdlb21fZGVuc2l0eShhbHBoYT0wLjUpICsNCiAgICBzY2FsZV94X2xvZzEwKCkNCmBgYA0KDQoNCg0KIyMjIyA0LkM6IEJveHBsb3RzDQoNCkdyYWZpY2Ftb3MgbG9zIGJveHBsb3RzIGRlbCBwcmVjaW8gcG9yIGNhZGEgdGlwbyBkZSBwcm9waWVkYWQuIFNpZ3VpZW5kbyBsYSBtaXNtYSBsb2dpY2EsIGVzY2FsYW1vcyBlbCBlamUgZGUgcHJlY2lvcyBsb2dhcml0bWljYW1lbnRlLg0KDQpgYGB7cn0NCiMgR3JhZmljYW1vcyBsb3MgYm94cGxvdHMNCmdncGxvdChhcl9wcm9wZXJ0aWVzLCBhZXMoeD1wcm9wZXJ0eV90eXBlLCB5PXByaWNlLCBmaWxsPXByb3BlcnR5X3R5cGUpKSArDQogICAgZ2VvbV9ib3hwbG90KCkgKw0KICAgIHNjYWxlX3lfbG9nMTAoKQ0KYGBgDQoNCg0KT2JlcnZhbW9zIGNvbW8gbG9zIHByZWNpb3MgdGllbmVuIHVuYSB0ZW5kZW5jaWEgZGVsIHRpcG8gQ2FzYSA+IFBIID4gZGVwdG8gKHlhIG9iZXJ2YWRhIHRhbWJpZW4gcHJldmlhbWVudGUgZW4gbG9zIGhpc3RvZ3JhbWFzKQ0KDQoNCiMjIyMgNC5EOiBDb3JyZWxvZ3JhbWENCg0KYGBge3J9DQpzdXBwcmVzc1BhY2thZ2VTdGFydHVwTWVzc2FnZXMobGlicmFyeShHR2FsbHkpKQ0KDQojIEdyYWZpY2Ftb3MgZWwgY29ycmVsb2dyYW1hDQpnZ2NvcnIoYXJfcHJvcGVydGllc1ssZ2V0Lm51bWVyaWMuY29sdW1ucyhhcl9wcm9wZXJ0aWVzKV0sIG1ldGhvZCA9IGMoImV2ZXJ5dGhpbmciLCAicGVhcnNvbiIpKSANCmBgYA0KDQojIyMjIDU6IE91dGxpZXJzDQoNCkFuYWxpemFtb3MgY29tbyBzZSBjb21wb25lbiBsb3MgcHJlY2lvcyBlbiBsb3MgZXh0cmVtb3Mgc3VwZXJpb3JlcyBlIGluZmVyaW9yZXMgZGUgbGEgZGlzdHJpYnVjaW9uDQoNCmBgYHtyfQ0KIyBDb3J0ZXMgZGUgcHJlY2lvcyBwYXJhIGxvcyBjdWFudGlsZXMgZW4gbG9zIGV4dHJlbW9zDQpxdWFudC5pbmYgPC0gcXVhbnRpbGUoYXJfcHJvcGVydGllcyRwcmljZSwgcHJvYnM9c2VxKDAsIDAuMDA1LCBsZW5ndGgub3V0ID0gNikgKQ0KcXVhbnQuc3VwIDwtIHF1YW50aWxlKGFyX3Byb3BlcnRpZXMkcHJpY2UsIHByb2JzPXNlcSguOTk1LCAxLCBsZW5ndGgub3V0ID0gNikgKQ0KDQpxdWFudC5pbmYNCnF1YW50LnN1cA0KYGBgDQoNCkFuYWxpemFuZG8gbG9zIHJlc3VsdGFkb3MgZGUgem9uYVByb3AsIGxhIHByb3BpZWRhZCBtYXMgYmFyYXRhIGVuIHZlbnRhIGVuIGNhcGl0YWwgZmVkZXJhbCBlc3RhIHBvciBzb2JyZSA1MC4wMDAgZG9sYXJlcy4gQ29tYmluYW5kbyBlc3RlIGRhdG8gY29uIGxhIGluZm9ybWFjaW9uIGRlIGxvcyBjdWFydGlsZXMgb2J0ZW5pZGEgcHJldmlhbWVudGUsIHBvZGVtb3MgYXNlZ3VyYXIgcXVlIGRlc2NhcnRhciBsb3MgdmFsb3JlcyBpbmZlcmlvcmVzIGEgVSRTIDUwLjAwMCBzZXJpYSBkZXNjYXJ0YXIgbWVub3MgZGVsIDAuNSUgZGUgbGFzIG11ZXN0cmFzLiBFbiBjdWFudG8gYSB2YWxvcmVzIHN1cGVyaW9yZXMsIHNlIHByb3NpZ3Vpw7MgYSBkZXNjYXJ0YXIgZWwgbWlzbW8gcG9yY2VudGFnZSBkZSBjb3J0ZSBxdWUgcGFyYSBsYXMgbXVlc3RyYXMgaW5mZXJpb3Jlcy4NCg0KYGBge3J9DQphcl9wcm9wZXJ0aWVzLnNpbi5vdXRsaWVycyA9IGFyX3Byb3BlcnRpZXMgJT4lIGZpbHRlcigNCiAgICBwcmljZSA+IHF1YW50LmluZlsnMC41JSddICYgcHJpY2UgPCBxdWFudC5zdXBbJzk5LjUlJ10gKQ0KYGBgDQoNCkNvbW8gY2FzbyBkZSBpbnRlcmVzLCB0YW1iaWVuIGZpbHRyYW1vcyBsb3Mgb3V0bGllcnMgdXRpbGl6YW5kbyBsYSB0ZWNuaWNhIGRlICppc29sYXRpb24gdHJlZSosIHV0aWxpemFuZG8gbGFzIHZhcmlhYmxlcyBkZSBpbnRlcmVzIHkgZmlsdHJhbmRvIHVuYSBjYW50aWRhZCBkZW1lamFudGUgZGUgbXVlc3RyYXMsIHBlcm8gdXRpbGl6YW5kbyBjb21vIGNyaXRlcmlvIHN1IHB1bnRhamUgZGUgKmFub21hbGlhKg0KDQpgYGB7cn0NCiMgQ2FyZ2Ftb3MgbGEgbGlicmVyaWEgbmVjZXNhcmlhDQpzdXBwcmVzc1BhY2thZ2VTdGFydHVwTWVzc2FnZXMobGlicmFyeSgic29saXR1ZGUiKSkNCg0KIyBpbmljaWFtb3MgZWwgYWxnb3JpdG1vIHV0aWxpemFuZG8gc29sYW1lbnRlIGxhcyB2YXJpYWJsZXMgcXVlIHZhbW9zIGEgY29ycmVsYWNpb25hcg0KaXNvIDwtIHNvbGl0dWRlOjppc29sYXRpb25Gb3Jlc3QkbmV3KCkNCmlzbyRmaXQoYXJfcHJvcGVydGllcyAlPiUgc2VsZWN0KCBjKCJyb29tcyIsICJwcmljZSIsICJzdXJmYWNlX3RvdGFsIikgKSApDQphcl9wcm9wZXJ0aWVzLnNpbi5vdXRsaWVycy5pc29sYXRpb250cmVlIDwtIGFyX3Byb3BlcnRpZXMNCg0KIyBVc2Ftb3MgbG9zIHJlc3VsdGFkb3MgcGFyYSBmaWx0cmFzIGxhcyBtdWVzdHJhcyBtYXMgYW5vbWFsYXMsIHkgbHVlZ28gZGVzY2FydGFtb3MgZXN0YSBjb2x1bW5hDQphcl9wcm9wZXJ0aWVzLnNpbi5vdXRsaWVycy5pc29sYXRpb250cmVlJGFub21hbHlTY29yZSA9IGlzbyRzY29yZXMkYW5vbWFseV9zY29yZQ0KYXJfcHJvcGVydGllcy5zaW4ub3V0bGllcnMuaXNvbGF0aW9udHJlZSA8LSBhcl9wcm9wZXJ0aWVzLnNpbi5vdXRsaWVycy5pc29sYXRpb250cmVlICU+JSBmaWx0ZXIoIGFub21hbHlTY29yZSA8IDAuNiApICU+JSBzZWxlY3QoIC1jKCJhbm9tYWx5U2NvcmUiKSApDQpgYGANCg0KIyMjIyA2OiBBbmFsaXNpcyBleHBsb3JhdG9yaW9zIChJSUkpDQoNCmBgYHtyfQ0KIyBDYWxjdWxhbW9zIGxhcyBlc3RhZGlzdGljYXMsIGRpc2NyaW1pbmFuZG8gcG9yIHRpcG8gZGUgcHJvcGllZGFkDQp0YXBwbHkoYXJfcHJvcGVydGllcy5zaW4ub3V0bGllcnMkcHJpY2UsIGFyX3Byb3BlcnRpZXMuc2luLm91dGxpZXJzJHByb3BlcnR5X3R5cGUsIHN1bW1hcnkud2l0aC5tZWFuKQ0KDQojIFJlYWxpemFtb3MgZWwgaGlzdG9ncmFtYSwgZGlzY3JpbWluYW5kbyBwb3IgdGlwbyBkZSBwcm9waWVkYWQNCmdncGxvdChhcl9wcm9wZXJ0aWVzLnNpbi5vdXRsaWVycywgYWVzKHg9cHJpY2UsIGZpbGw9cHJvcGVydHlfdHlwZSkpICsNCiAgICBnZW9tX2hpc3RvZ3JhbShhbHBoYT0wLjUsIHBvc2l0aW9uPSJpZGVudGl0eSIsIGFlcyh5ID0gLi5kZW5zaXR5Li4pKSArDQogICAgZ2VvbV9kZW5zaXR5KGFscGhhPTAuNSkgKw0KICAgIHNjYWxlX3hfbG9nMTAoKQ0KDQojIEdyYWZpY2Ftb3MgbG9zIGJveHBsb3RzDQpnZ3Bsb3QoYXJfcHJvcGVydGllcy5zaW4ub3V0bGllcnMsIGFlcyh4PXByb3BlcnR5X3R5cGUsIHk9cHJpY2UsIGZpbGw9cHJvcGVydHlfdHlwZSkpICsNCiAgICBnZW9tX2JveHBsb3QoKSArDQogICAgc2NhbGVfeV9sb2cxMCgpDQoNCiMgR3JhZmljYW1vcyBlbCBjb3JyZWxvZ3JhbWENCmdnY29ycihhcl9wcm9wZXJ0aWVzLnNpbi5vdXRsaWVyc1ssZ2V0Lm51bWVyaWMuY29sdW1ucyhhcl9wcm9wZXJ0aWVzLnNpbi5vdXRsaWVycyldLCBtZXRob2QgPSBjKCJldmVyeXRoaW5nIiwgInBlYXJzb24iKSkNCmBgYA0KDQpTZSBwdWVkZSBvYnNlcnZhciAoY29uIGVzcGVjaWFsIGRldGFsbGUgZW4gbG9zIGJveHBsb3RzKSBjb21vIGdyYW4gY2FudGlkYWQgZGUgbG9zIG91dGxpZXJzIGRlc2FwYXJlY2llcm9uLiBDYWJlIGRlc3RhY2FyIHF1ZSBlc3RhIGVzIHVuYSB0ZWNuaWNhIHVuIHBvY28gaW5nZW51YSBwYXJhIGxhIGVsaW1pbmFjaW9uIGRlIGxvcyBtaXNtb3MsIHlhIHF1ZSBubyBjb25zaWRlcmEgbGEgcG9zaWJpbGlkYWQgZGUgb3V0bGllcnMgZGViaWRvIGEgbGEgcmVsYWNpb24gZGUgbGFzIHZhcmlhYmxlcyBlbnRyZSBzw60sIHkgc29sYW1lbnRlIGRlc2NhcnRhIGxvcyBleHRyZW1vcyBlbiBjYWRhIGRpbWVuc2lvbiAoZW4gZXN0ZSBjYXNvLCBsYSBkaW1lbnNpb24gYHByZWNpb2ApDQoNCiMjIyMgNzogTW9kZWxvIGxpbmVhbA0KDQojIyMjIyA3LkE6IEdlbmVyYWNpb24gZGVsIG1vZGVsbyBsaW5lYWwNCg0KYGBge3J9DQojY2FsY3VsYW1vcyBsb3MgbW9kZWxvcyBsaW5lYWxlcyBpbmRpY2Fkb3MNCmxpbmVhci5tb2RlbC5zdXJmYWNlIDwtIGxtKHByaWNlIH4gc3VyZmFjZV90b3RhbCwgZGF0YSA9IGFyX3Byb3BlcnRpZXMuc2luLm91dGxpZXJzKQ0KbGluZWFyLm1vZGVsLnJvb21zIDwtIGxtKHByaWNlIH4gcm9vbXMsIGRhdGEgPSBhcl9wcm9wZXJ0aWVzLnNpbi5vdXRsaWVycykNCmBgYA0KDQojIyMjIyA3LkI6IERlc2NyaXBjaW9uIGRlIGxvcyBtb2RlbG9zIGxpbmVhbGVzDQoNCnN1cGVyZmljaWUgdnMgcHJlY2lvDQpgYGB7cn0NCnN1bW1hcnkobGluZWFyLm1vZGVsLnN1cmZhY2UpDQpgYGANCg0KYW1iaWVudGVzIHZzIHByZWNpbw0KYGBge3J9DQpzdW1tYXJ5KGxpbmVhci5tb2RlbC5yb29tcykNCmBgYA0KDQojIyMjIyA3LkIgKGJpcyk6IERlc2NyaXBjaW9uIGRlIGxvcyBtb2RlbG9zIGxpbmVhbGVzIC0gaXNvbGF0aW9uIHRyZWUNCg0KDQpWZXJpZmljYW1vcyBxdWUgbGEgY2FudGlkYWQgZGUgY2Fzb3MgZW4gYW1ib3MgZmlsdHJhZG9zIHNlYSBzZW1lamFudGUNCmBgYHtyfQ0KbnJvdyhhcl9wcm9wZXJ0aWVzLnNpbi5vdXRsaWVycykNCm5yb3coYXJfcHJvcGVydGllcy5zaW4ub3V0bGllcnMuaXNvbGF0aW9udHJlZSkNCmBgYA0KDQpBbmFsaXphbW9zIGxvcyBtb2RlbG9zOg0KDQpzdXBlcmZpY2llIHZzIHByZWNpbw0KYGBge3J9DQpzdW1tYXJ5KGxtKHByaWNlIH4gc3VyZmFjZV90b3RhbCwgZGF0YSA9IGFyX3Byb3BlcnRpZXMuc2luLm91dGxpZXJzLmlzb2xhdGlvbnRyZWUpKQ0KYGBgDQoNCmFtYmllbnRlcyB2cyBwcmVjaW8NCmBgYHtyfQ0Kc3VtbWFyeShsbShwcmljZSB+IHJvb21zLCBkYXRhID0gYXJfcHJvcGVydGllcy5zaW4ub3V0bGllcnMuaXNvbGF0aW9udHJlZSkpDQpgYGANCg0KWSByZWFsaXphbW9zIGVsIGNvcnJlbG9ncmFtYSBwYXJhIGVzdGEgdmVyc2lvbiBkZSBmaWx0cmFkbw0KYGBge3J9DQpnZ2NvcnIoYXJfcHJvcGVydGllcy5zaW4ub3V0bGllcnMuaXNvbGF0aW9udHJlZVssZ2V0Lm51bWVyaWMuY29sdW1ucyhhcl9wcm9wZXJ0aWVzLnNpbi5vdXRsaWVycy5pc29sYXRpb250cmVlKV0sIG1ldGhvZCA9IGMoImV2ZXJ5dGhpbmciLCAicGVhcnNvbiIpKQ0KYGBgDQoNCg0KDQojIyMjIyA3LkM6IEFuYWxpc2lzIC8gY29uY2x1c2lvbmVzDQoNCiMjIyMjIyAqKmZpbHRyYWRvIHBvciBleHRyZW1vcyoqDQpTZSBwdWVkZSBvYnNlcnZhciBlbiBsb3MgdmFsb3JlcyBkZSBSLXNxdWFyZWQgKG1heW9yIGVuIGVsIGNhc28gZGUgYW1iaWVudGUtcHJlY2lvKSBxdWUgbGEgY29ycmVsYWNpb24gZXMgbWFzIGZ1ZXRlIHBhcmEgZXN0YSB2YXJpYWJsZSAoeWEgc2UgcG9kaWEgb2JzZXJ2YXIgZXN0ZSBlZmVjdG8gZW4gZWwgY29ycmVsb2dyYW1hKS4gVGFtYmllbiBvYnNlcnZhbW9zIGNvbW8gbG9zIHJlc2lkdW9zIHNvbiBtZW5vcmVzIHBhcmEgZXN0ZSBtb2RlbG8gbGluZWFsLiBEYWRvIGVzdG9zIG1vdGl2b3MsIGVsIG1vZGVsbyBkZSBwcmVjaW8tYW1iaWVudGUgcGFyZWNlIHNlciB1biBtZWpvciBwcmVkaWN0b3IgZGUgcHJlY2lvIHF1ZSBlbCBkZSBzdWVwcmZpY2llLWFtYmllbnRlLg0KDQoNCiMjIyMjIyAqKmZpbHRyYWRvIHBvciAqaXNvbGF0aW9uIHRyZWUqKioNCkN1YW5kbyBlbCBmaWx0cmFkbyBzZSByZWFsaXphIHV0aWxpemFuZG8gb3RyYSB0ZWNuaWNhLCBzZSBvYnNlcnZhIGNvbW8gbG9zIHJlc3VsdGFkb3MgYW50ZXJpb3JlcyBzZSBpbnZpZXJ0ZW46IGVsIG1vZGVsbyBkZSBhcmVhLXByZWNpbyBzZSB2dWVsdmUgdW4gbWVqb3IgcHJlZGljdG9yIGVuIGVzdGUgY2FzbzsgcGVybyBhZGVtYXMgc2UgcG90ZW5jaWEsIGRhZG8gcXVlIGVzdGUgbW9kZWxvIChmaWx0cmFkbyBwb3IgKmlzb2xhdGlvbiB0cmVlKiwgeSBtb2RlbGFuZG8gYXJlYSB2cyBwcmVjaW8pIGVzIGxhIHF1ZSBleHByZXNhIHVuYSBtYXlvciBjb3JyZWxhY2lvbiBxdWUgdG9kb3MgbG9zIG90cm9zIGNhc29zOyBwcm9kdWN0byBkZSBoYWJlciBmaWx0cmFkbyBubyBzb2xvIGxvcyB2YWxvcmVzIGV4dHJlbW9zLCBzaW5vIHRhbWJpZW4gdXNhbmRvIHRlY25pY2FzIG1hcyBhdmFuemFkYXMgcGFyYSBkZXRlY3RhciAqb3V0bGllcnMqIGRlIHZhbG9yZXMgaW50ZXJtZWRpb3MsIHF1ZSBoYWJyaWFuIHNlc2dhZG8gZWwgbW9kZWxvIGEgdW5vIG1lbm9yIHBlcmZvcm1hbnRlIHF1ZSBlbCBkZSBhbWJpZW50ZXMgdnMgcHJlY2lvIChlc3RvIHNlIG9ic2VydmEgY2xhcmFtZW50ZSBlbiBsb3MgZGlzcGVyc29ncmFtYXMgYSBjb250aW51YWNpb24pDQoNCmBgYHtyfQ0KZ2dwbG90KGFyX3Byb3BlcnRpZXMuc2luLm91dGxpZXJzLCBhZXMoeD1yb29tcywgeT1wcmljZSkpICsgZ2VvbV9wb2ludChjb2xvcj0icmVkIikgKyBnZW9tX3Ntb290aChtZXRob2Q9J2xtJykgKyBnZ3RpdGxlKCJQcmVjaW8gdnMgQW1iaWVudGVzIC0gZmlsdHJhZG8gaW5nZW51byIpDQoNCmdncGxvdChhcl9wcm9wZXJ0aWVzLnNpbi5vdXRsaWVycywgYWVzKHg9c3VyZmFjZV90b3RhbCwgeT1wcmljZSkpICsgZ2VvbV9wb2ludChjb2xvcj0icmVkIikgKyBnZW9tX3Ntb290aChtZXRob2Q9J2xtJykgKyBnZ3RpdGxlKCJQcmVjaW8gdnMgQXJlYSAtIGZpbHRyYWRvIGluZ2VudW8iKQ0KDQpnZ3Bsb3QoYXJfcHJvcGVydGllcy5zaW4ub3V0bGllcnMuaXNvbGF0aW9udHJlZSwgYWVzKHg9cm9vbXMsIHk9cHJpY2UpKSArIGdlb21fcG9pbnQoY29sb3I9ImdyZWVuIikgKyBnZW9tX3Ntb290aChtZXRob2Q9J2xtJykgKyBnZ3RpdGxlKCJQcmVjaW8gdnMgQW1iaWVudGVzIC0gZmlsdHJhZG8gcG9yIGlzb2xhdGlvbiB0cmVlIikNCg0KZ2dwbG90KGFyX3Byb3BlcnRpZXMuc2luLm91dGxpZXJzLmlzb2xhdGlvbnRyZWUsIGFlcyh4PXN1cmZhY2VfdG90YWwsIHk9cHJpY2UpKSArIGdlb21fcG9pbnQoY29sb3I9ImdyZWVuIikgKyBnZW9tX3Ntb290aChtZXRob2Q9J2xtJykgKyBnZ3RpdGxlKCJQcmVjaW8gdnMgQXJlYSAtIGZpbHRyYWRvIHBvciBpc29sYXRpb24gdHJlZSIpDQpgYGANCg0K