Este conjunto de datos fue obtenido de Kaggle. Haga clic aquí para verlo.

Librerias a utilizar

# Cargar Las Librerías
library(tidyverse)
library(caret)
library(randomForest)
library(rpart)
library(tree)
library(DataExplorer)
library(plotly)
df <- read_csv("Salary_Data_Based_country_and_race.csv")
df$...1<-NULL
fix(df)
# Observamos la cabecera de nuestros datos. 
head(df)

Veamos la estructura de los datos.

glimpse(df)
Rows: 6,704
Columns: 8
$ Age                 <dbl> 32, 28, 45, 36, 52, 29, 42, 31, 26, 38, 29, 48, 35, 40, 27, 44, 33, 39, 25, 51, 34, 47…
$ Gender              <chr> "Male", "Female", "Male", "Female", "Male", "Male", "Female", "Male", "Female", "Male"…
$ Education_Level     <chr> "Bachelor's", "Master's", "PhD", "Bachelor's", "Master's", "Bachelor's", "Master's", "…
$ Job_Title           <chr> "Software Engineer", "Data Analyst", "Senior Manager", "Sales Associate", "Director", …
$ Years_of_Experience <dbl> 5, 3, 15, 7, 20, 2, 12, 4, 1, 10, 3, 18, 6, 14, 2, 16, 7, 12, 0, 22, 5, 19, 2, 9, 13, …
$ Salary              <dbl> 90000, 65000, 150000, 60000, 200000, 55000, 120000, 80000, 45000, 110000, 75000, 14000…
$ Country             <chr> "UK", "USA", "Canada", "USA", "USA", "USA", "USA", "China", "China", "Australia", "UK"…
$ Race                <chr> "White", "Hispanic", "White", "Hispanic", "Asian", "Hispanic", "Asian", "Korean", "Chi…

Realizamos un análisis para cuantificar el porcentaje de valores faltantes (NA) en nuestros datos. Esto nos permitirá identificar la integridad de nuestra muestra y evaluar la calidad de la información disponible.

plot_missing(df)

He considerado la opción de eliminar los valores faltantes debido a que su cantidad es relativamente baja en relación con el total de datos..

df<-na.omit(df)
colSums(is.na(df))
                Age              Gender     Education_Level           Job_Title Years_of_Experience 
                  0                   0                   0                   0                   0 
             Salary             Country                Race 
                  0                   0                   0 

Para mejorar la manipulación y el análisis de nuestras variables, hemos realizado la conversión de aquellas que estaban en formato caracter a formato Factor.

var_fac<-c("Gender", "Education_Level","Job_Title","Country","Race")
df <- df %>%
  mutate(across(all_of(var_fac), as.factor))

He detectado que la variable “Education_Level” contiene valores similares pero con diferentes nombres. Para lograr una representación más coherente y evitar duplicados en nuestro conjunto de datos, he realizado un proceso de unificación de dichos valores.

df$Education_Level <- recode(
 df$Education_Level,
  "Bachelor's Degree" = "Bachelor's",
  "PhD" = "phD",
  "Master's Degree" = "Master's"
)

Después de la unificación de los valores en la variable “Education_Level”, he realizado un conteo para conocer la distribución de los diferentes niveles de educación presentes en nuestro conjunto de datos. A continuación, se muestra una tabla con el número de registros asociados a cada nivel de educación:

Conteo<-fct_count(df$Education_Level)
print(Conteo)

Hemos agrupado los valores de “Education_Level” y calculado el promedio del salario y la desviación estándar para cada grupo. A continuación, se presentan los resultados:.


df %>% 
  group_by(Education_Level) %>% 
  summarise(
    Salario_Promedio = mean(Salary),
    Salario_desvE = sd(Salary)
  )

A continuación, presento un histograma para cada una de las variables numéricas en el conjunto de datos:

plot_histogram(df)

Explorando la Distribución de los Datos Mediante un Gráfico QQ-Plot

plot_qq(df)

Examinando un Resumen Estadístico de los Datos.

summary(df)
      Age           Gender        Education_Level                     Job_Title    Years_of_Experience
 Min.   :21.00   Female:3013   Bachelor's :3021   Software Engineer        : 518   Min.   : 0.000     
 1st Qu.:28.00   Male  :3671   High School: 448   Data Scientist           : 453   1st Qu.: 3.000     
 Median :32.00   Other :  14   Master's   :1860   Software Engineer Manager: 376   Median : 7.000     
 Mean   :33.62                 phD        :1369   Data Analyst             : 363   Mean   : 8.095     
 3rd Qu.:38.00                                    Senior Project Engineer  : 318   3rd Qu.:12.000     
 Max.   :62.00                                    Product Manager          : 313   Max.   :34.000     
                                                  (Other)                  :4357                      
     Salary            Country             Race     
 Min.   :   350   Australia:1336   White     :1962  
 1st Qu.: 70000   Canada   :1325   Asian     :1603  
 Median :115000   China    :1343   Korean    : 457  
 Mean   :115329   UK       :1335   Australian: 452  
 3rd Qu.:160000   USA      :1359   Chinese   : 444  
 Max.   :250000                    Black     : 437  
                                   (Other)   :1343  

Realizaremos un Análisis Exploratorio de Datos (EDA) del conjunto de datos.

Representación gráfica que visualiza la proporción de género según la raza.

mis_colores <- c("#F31559", "#78C1F3", "#9460b5", "#3ca2a2", "#ff348e", "#2a6a66",
                 "#db8fd7", "#fcff6c", "#00bfff", "#ffaf00", "#FF5733", "#00FF00",
                 "#FFD700", "#6A5ACD", "#FF1493")

a <- ggplot(df, aes(x = reorder(Race, -table(Race)[Race]), fill = Gender)) +
  geom_bar(position = "dodge") +
  theme_minimal() +
  scale_fill_manual(values = mis_colores) +
  coord_flip() +
  labs(title = "Proporción De Género Por Raza", y = "Conteo", x = "Raza")

print(a)

Realicemos una visualización de las frecuencias de los valores presentes en la variable “Género”.

b <- ggplot(df, aes(x = reorder(Gender, -table(Gender)[Gender]))) +
  geom_bar(aes(fill = Gender)) +
  geom_text(stat = "count", aes(label = ..count..), vjust = -0.5) +
  theme_minimal() +
  labs(title = "Frecuencia de Géneros", x = "Género", y = "Conteo") + 
  scale_fill_manual(values = mis_colores)

print(b)

Realizaremos un conteo de las observaciones por Nivel de Educación..

c <- ggplot(df, aes(x = reorder(Education_Level, -table(Education_Level)[Education_Level]))) +
  geom_bar(aes(fill = Education_Level)) +
  geom_text(stat = "count", aes(label = ..count..), vjust = -0.5) +
  theme_minimal() +
  labs(title = "Frecuencia de Educación", x = "Educación", y = "Conteo") + 
  scale_fill_manual(values = mis_colores)

print(c)

Examinemos cómo el rango de la variable Educación afecta al Salario.

 d <- ggplot(df, aes(x = reorder(Education_Level, Salary, median), y = Salary, fill = Education_Level)) +
  geom_boxplot(color = "black") + 
  labs(title = "Educación vs Salario",
       x = "Educación", y = "Salario") +
  theme_minimal() + scale_fill_manual(values = mis_colores)

ggplotly(d)
NA

Ahora procederemos a analizar cómo se distribuye la variable “Género” dentro de los diferentes niveles de educación.


 df_percentaje<-df %>%
  group_by(Education_Level, Gender) %>%
  tally() %>%
  mutate(Porcentaje = n / sum(n))

e <- ggplot(df_percentaje, aes(x = reorder(Education_Level, Porcentaje, median), y = Porcentaje, fill = Gender)) +
  geom_bar(stat = "identity", position = "dodge") +
  geom_text(aes(label = scales::percent(Porcentaje)), position = position_dodge(width = 0.9), vjust = -0.5) +
  theme_minimal() +
  labs(title = "Porcentaje de Género por Nivel de Educación",
       x = "Nivel de Educación", y = "Porcentaje") + scale_fill_manual(values = mis_colores)

print(e)

A continuación, realizaremos una observación de la variable “Salario” con respecto al género. Analizaremos cómo se distribuye el salario en función de los distintos géneros presentes en nuestro conjunto de datos.

f <- ggplot(df, aes(x = reorder(Gender, Salary, median), y = Salary, fill = Gender)) +
  geom_boxplot(color = "black") +
  scale_fill_manual(values = mis_colores) +
  labs(title = "Gráfico de Caja Género vs Salario",
       x = "Género", y = "Salario") +
  theme_minimal()

ggplotly(f)
NA
NA

A continuación, procederemos a realizar una visualización de cómo se distribuye el salario en diferentes países. Esto nos permitirá analizar las diferencias salariales entre los distintos países presentes en nuestro conjunto de datos..

g <- ggplot(df, aes(x = reorder(Country, Salary, median), y = Salary, fill = Education_Level)) +
  geom_boxplot(color = "black") +
  labs(title = "Gráfico de Cajas Países vs Salario",
       x = "País", y = "Salario") +
  theme_minimal() +
  scale_fill_manual(values = mis_colores)

print(g)

Ante la observación del gráfico anterior, donde se muestra que el promedio del salario es igual en distintos países, surge la duda acerca de la veracidad de los datos. Sin embargo, con el fin de continuar con el análisis de datos a modo de práctica, procederemos a seguir explorando y visualizando la información disponible.

A continuación, presentamos la distribución del salario basada en la variable “Raza”. Este gráfico nos permite visualizar cómo se distribuyen los salarios entre las diferentes categorías de raza en nuestro conjunto de datos. Es importante destacar que este análisis tiene fines puramente exploratorios y nos ayuda a identificar posibles patrones o tendencias relacionadas con la variable de interés.

h <- ggplot(df, aes(x = reorder(Race, Salary, median), y = Salary, fill = Race)) +
  geom_boxplot(color = "black") +
  labs(title = "Gráfico de Cajas Raza vs Salario",
       x = "Raza", y = "Salario") +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1)) +
  scale_fill_manual(values = mis_colores)

ggplotly(h)
NA

Para realizar una generalización de la edad y estudiar la distribución del salario con respecto a esta variable, he realizado cortes equitativos del rango de edad. Esto nos permite agrupar a las personas en diferentes categorías de edad para facilitar el análisis y la visualización de cómo se distribuyen los salarios en cada grupo.

df$Age_Interva <- cut(df$Age, breaks = c(21, 31, 41, 51, 62), include.lowest = TRUE)

table(df$Age_Interva)

[21,31] (31,41] (41,51] (51,62] 
   3256    2223    1060     159 
i <- ggplot(df, aes(x = reorder(Age_Interva, Salary, median), y = Salary, fill = Age_Interva)) +
  geom_boxplot(color = "black") +
  labs(title = "Edad en intervalos vs Salario",
       x = "Intervalos de Edad", y = "Salario") +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1)) +
  scale_fill_manual(values = mis_colores)

ggplotly(i)
NA

A continuación, procedemos a realizar un gráfico de puntos para explorar la correlación entre la variable “salario” y “años de experiencia”.

j<- ggplot(df, aes(x = Salary, y = Years_of_Experience, color = Education_Level)) +
  geom_point() +
  theme_minimal() + 
  labs(title = "Salario vs Años de Experiencia",
       x = "Salario", y = "Años de Experiencia" ) + scale_fill_manual(values = mis_colores)
ggplotly(j)

A continuación, procedemos a realizar un gráfico de puntos para visualizar la relación entre el salario y los años de experiencia, utilizando el color para representar los géneros. Este tipo de gráfico nos permitirá observar cómo se distribuyen los salarios y la experiencia en función de los diferentes géneros, lo que nos ayudará a identificar posibles patrones o diferencias entre ellos.

k<- ggplot(df, aes(x = Salary, y = Years_of_Experience, color = Gender)) +
  geom_point() +
  theme_minimal() + 
  labs(title = "Salario vs Años de Experiencia",
       x = "Salario", y = "Años de Experiencia" ) + scale_fill_manual(values = mis_colores)
ggplotly(k)

A continuación, presentamos un gráfico de barras que muestra las profesiones con los salarios más altos. Para ello, hemos ordenado las profesiones de mayor a menor salario y hemos destacado las primeras posiciones para identificar cuáles son las más remuneradas.

# Paso 1: Calcular el percentil 75 del salario
percentil_75 <- quantile(df$Salary, 0.75)

# Paso 2: Filtrar el marco de datos para quedarte con las profesiones mejor pagadas
df_filtrado <- df %>% 
  filter(Salary > percentil_75)

# Paso 3: Ordenar el marco de datos por el salario de forma descendente
df_filtrado <- df_filtrado %>% 
  arrange(desc(Salary))


l <- ggplot(top_15_titulos, aes(x = reorder(Job_Title, -Salario_promedio), y = Salario_promedio, fill = Job_Title)) +
  geom_bar(stat = "identity") +
  theme(axis.text.x = element_text(angle = 45, hjust = 1)) +
  labs(title = "Top 15 Títulos con Mejor Salario Promedio",
       x = "Título del Trabajo",
       y = "Salario Promedio") +
  guides(fill = FALSE)

print(l)

NA
NA

A continuación, presentamos un gráfico de correlación que nos permitirá visualizar las relaciones entre las variables de nuestro conjunto de datos.

df2<-df
# Converimos las variables categóricas a valores numéricos
df2$Gender <- as.numeric(factor(df$Gender))
df2$Education_Level <- as.numeric(factor(df$Education_Level))
df2$Job_Title <- as.numeric(factor(df$Job_Title))
df2$Country <- as.numeric(factor(df$Country))
df2$Race <- as.numeric(factor(df$Race))
df2$Age_Interva<-as.numeric(factor(df$Age_Interva))

#Creamos el Grafico de correlacion
corr_matrix <- cor(df2)

# Visualizamos el gráfico de correlación con ajustes personalizados
m<-corrplot(corr_matrix, method = "color", type = "upper", 
         tl.cex = 0.7, addcolor = TRUE,
         col = colorRampPalette(c("#9460b5", "white", "#F31559"))(100),
         cl.lim = c(-1, 1),
         is.corr = TRUE,
         addCoef.col = "black",
         number.cex = 0.8,
         tl.col = "black",
         diag = FALSE,
         tl.srt = 45,
         addgrid.col = "black") 

Procedemos a realizar una evaluacion de distintos modelos

En esta sección, llevaremos a cabo una evaluación de varios modelos para predecir el salario en función de las variables disponibles en nuestro conjunto de datos. Los modelos que utilizaremos son los siguientes:

  1. Regresión Lineal

  2. Árbol de Decisión

  3. Bosques Aleatorios

Para cada modelo, realizaremos una partición de nuestros datos en conjuntos de entrenamiento y prueba, entrenaremos el modelo en el conjunto de entrenamiento y evaluaremos su rendimiento en el conjunto de prueba. Utilizaremos diferentes métricas de evaluación.

A continuación, presentaremos los resultados de la evaluación para cada modelo, lo que nos permitirá tomar decisiones informadas sobre cuál modelo es el más adecuado para nuestras necesidades de predicción de salarios.

set.seed(42)
partition_indexes <- createDataPartition(df2$Salary, p = 0.7, list = FALSE)

X_train <- df2[partition_indexes, -which(names(df2) == "Salary")]
X_test <- df2[-partition_indexes, -which(names(df2) == "Salary")]
y_train <- df2$Salary[partition_indexes]
y_test <- df2$Salary[-partition_indexes]

models <- list(
  lm_model = lm(Salary ~ ., data = df2),
  tree_model = rpart(Salary ~ ., data = df2),
  rf_model = randomForest(Salary ~ ., data = df2, ntree = 350)
)

results <- data.frame(
  Model_Name = character(),
  Mean_Absolute_Error_MAE = numeric(),
  Mean_Absolute_Percentage_Error_MAPE = numeric(),
  Mean_Squared_Error_MSE = numeric(),
  Root_Mean_Squared_Error_RMSE = numeric(),
  R2_score = numeric(),
  stringsAsFactors = FALSE
)

for (i in 1:length(models)) {
  model <- models[[i]]
  model_name <- names(models)[i]
  
  # Hacer las predicciones con cada modelo en el conjunto de prueba (X_test)
  y_pred <- predict(model, newdata = X_test)
  
  # Calcular las métricas de evaluación para el modelo actual
  mae <- mean(abs(y_test - y_pred))
  mape <- mean(abs((y_test - y_pred) / y_test)) * 100
  mse <- mean((y_test - y_pred)^2)
  rmse <- sqrt(mse)
  r2 <- cor(y_test, y_pred)^2
  
  # Agregar los resultados al data frame 'results'
  results <- rbind(results, data.frame(
    Model_Name = model_name,
    Mean_Absolute_Error_MAE = mae,
    Mean_Absolute_Percentage_Error_MAPE = mape,
    Mean_Squared_Error_MSE = mse,
    Root_Mean_Squared_Error_RMSE = rmse,
    R2_score = r2
  ))
}

print(results)
NA

Para evaluar la eficacia de cada modelo, graficaremos el “Error Medio Absoluto” (MAE) obtenido en la evaluación de cada uno de ellos. El MAE es una métrica que nos indica el promedio de las diferencias absolutas entre las predicciones del modelo y los valores reales.Cuanto menor sea el MAE, mejor será el rendimiento del modelo en términos de precisión de las predicciones. \[ MAE = \frac{1}{n} \sum |y_{pred} - y_{real}| \]

# Crear el gráfico de barras con ggplot2
n <- ggplot(results, aes(x = Model_Name, y = Mean_Absolute_Error_MAE, fill = Model_Name)) +
  geom_bar(stat = 'identity') +
  labs(title = '(MAE) por modelo', x = 'Modelos', y = 'MAE') +
  theme(axis.text.x = element_text(angle = 45, hjust = 1)) + scale_fill_manual(values = mis_colores) + theme_minimal()

# Mostrar el gráfico estático
print(n)

Para evaluar la eficacia de cada modelo, Graficaremos graficar el coeficiente R cuadrado (R2) de cada modelo. El coeficiente R2 es una medida que indica cuánta varianza de la variable dependiente (en nuestro caso, el salario) es explicada por el modelo. Cuanto más cercano a 1 sea el valor de R2, mejor será la capacidad del modelo para explicar la variabilidad de los datos.

o<-ggplot(results, aes(x = Model_Name, y = R2_score, fill = Model_Name)) +
  geom_bar(stat = "identity") +
  labs(title = "R-squared por modelo", x = "Modelos", y = "R-squared") +
  theme(axis.text.x = element_text(angle = 45, hjust = 1)) +
  scale_fill_manual(values = mis_colores) + theme_minimal()
print(o)

Tras llevar a cabo una evaluación de los diferentes modelos, se ha destacado que el modelo de Bosques Aleatorios ha sobresalido por su alto rendimiento y precisión predictiva. Ahora, para profundizar en su desempeño, observaremos detalladamente las métricas obtenidas por este modelo, lo cual nos permitirá medir su eficacia en la predicción del salario basándose en las variables independientes consideradas.

# Obtenemos los errores del modelo de bosque aleatorio
rf_errors <- models$rf_model$mse

# Creamos un data frame para la curva de aprendizaje
curve_data <- data.frame(trees = seq_along(rf_errors), Error = rf_errors)

# Creamos el gráfico de curva de aprendizaje con ggplot2
ggplot(curve_data, aes(x = trees, y = Error)) +
  geom_line() +
  labs(title = "Curva de Aprendizaje - Bosque Aleatorio",
       x = "Número de Árboles",
       y = "Error") +
  theme_minimal()


# Creamos un data frame con las predicciones y valores reales
comparison_data <- data.frame(Valor_Real = y_test, Prediccion = y_pred_rf)

# Creamos el gráfico de dispersión con ggplot2
ggplot(comparison_data, aes(x = Valor_Real, y = Prediccion)) +
  geom_point() +
  geom_abline(intercept = 0, slope = 1, color = "red") +
  labs(title = "Comparación de Predicciones - Bosque Aleatorio",
       x = "Valor Real",
       y = "Predicción") +
  theme_minimal()

NA
NA

En conclusión, este análisis exploratorio de datos ha proporcionado una visión general de las relaciones y patrones presentes en el conjunto de datos estudiado. Se ha evidenciado la influencia de variables como el género, nivel de educación, experiencia laboral y país de origen en el salario de los individuos.

Se han identificado algunas relaciones significativas, como la tendencia creciente entre salario y años de experiencia, así como las diferencias en los salarios promedio entre distintos niveles de educación. También se ha destacado la presencia de valores atípicos que pueden tener un impacto en la interpretación de los resultados.

Es importante mencionar que se han identificado algunas inconsistencias en los datos, como la similitud en los salarios promedio entre diferentes países, lo cual sugiere la posibilidad de datos erróneos o sesgos en la muestra.

El modelo de Bosques Aleatorios ha demostrado ser el más eficaz en términos de rendimiento y precisión predictiva.

En resumen, este análisis exploratorio de datos proporciona una base sólida para futuros estudios y análisis más profundos. Se deben considerar las conjeturas y observaciones realizadas como hipótesis iniciales, y se invita a realizar análisis más detallados y rigurosos para obtener conclusiones más sólidas. El proceso de exploración de datos es continuo y dinámico, lo que nos permite seguir descubriendo y comprendiendo nuevas relaciones y patrones en los datos en futuras investigaciones.

¡Gracias por llegar hasta acá no dudes en dejar tu comentario para ayudarme a seguir prendiendo!

LS0tDQp0aXRsZTogIkRhdG9zIERlIFNhbGFyaW9zIg0Kb3V0cHV0OiANCiAgaHRtbF9ub3RlYm9vazoNCiAgICANCi0tLQ0KDQpFc3RlIGNvbmp1bnRvIGRlIGRhdG9zIGZ1ZSBvYnRlbmlkbyBkZSBLYWdnbGUuIFsqKkhhZ2EgY2xpYyBhcXXDrSoqXShodHRwczovL3d3dy5rYWdnbGUuY29tL2RhdGFzZXRzL3N1ZGhlZXJwMjE0NzIzNC9zYWxhcnktZGF0YXNldC1iYXNlZC1vbi1jb3VudHJ5LWFuZC1yYWNlKSBwYXJhIHZlcmxvLg0KDQoqKkxpYnJlcmlhcyBhIHV0aWxpemFyKioNCmBgYHtyLG1lc3NhZ2U9RkFMU0Usd2FybmluZz1GQUxTRX0NCiMgQ2FyZ2FyIExhcyBMaWJyZXLDrWFzDQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkoY2FyZXQpDQpsaWJyYXJ5KHJhbmRvbUZvcmVzdCkNCmxpYnJhcnkocnBhcnQpDQpsaWJyYXJ5KHRyZWUpDQpsaWJyYXJ5KERhdGFFeHBsb3JlcikNCmxpYnJhcnkocGxvdGx5KQ0KYGBgDQoNCmBgYHtyLHdhcm5pbmc9RkFMU0UsbWVzc2FnZT1GQUxTRX0NCmRmIDwtIHJlYWRfY3N2KCJTYWxhcnlfRGF0YV9CYXNlZF9jb3VudHJ5X2FuZF9yYWNlLmNzdiIpI0NhcmdhbW9zIGVsIFNldCBkZSBkYXRvcw0KZGYkLi4uMTwtTlVMTCNCb3JyYW1vcyBsYSBwcmltZXJhIGNvbHVtbmENCmZpeChkZikjb2JzZXJ2YW1vcyBlbCBkZiANCmBgYA0KDQoNCmBgYHtyfQ0KIyBPYnNlcnZhbW9zIGxhIGNhYmVjZXJhIGRlIG51ZXN0cm9zIGRhdG9zLiANCmhlYWQoZGYpDQpgYGANCioqVmVhbW9zIGxhIGVzdHJ1Y3R1cmEgZGUgbG9zIGRhdG9zLioqDQpgYGB7cn0NCmdsaW1wc2UoZGYpI0Rlc2NyaWJlIG51ZXN0cm8gZGYNCmBgYA0KKipSZWFsaXphbW9zIHVuIGFuw6FsaXNpcyBwYXJhIGN1YW50aWZpY2FyIGVsIHBvcmNlbnRhamUgZGUgdmFsb3JlcyBmYWx0YW50ZXMgKE5BKSBlbiBudWVzdHJvcyBkYXRvcy4gRXN0byBub3MgcGVybWl0aXLDoSBpZGVudGlmaWNhciBsYSBpbnRlZ3JpZGFkIGRlIG51ZXN0cmEgbXVlc3RyYSB5IGV2YWx1YXIgbGEgY2FsaWRhZCBkZSBsYSBpbmZvcm1hY2nDs24gZGlzcG9uaWJsZS4gKioNCmBgYHtyfQ0KcGxvdF9taXNzaW5nKGRmKSNPYnNlcnZhbW9zIGVsIHBvcmNlbnRhamUgZGUgdmFsb3JlcyBmYWx0YW50ZXMNCmBgYA0KKipIZSBjb25zaWRlcmFkbyBsYSBvcGNpw7NuIGRlIGVsaW1pbmFyIGxvcyB2YWxvcmVzIGZhbHRhbnRlcyBkZWJpZG8gYSBxdWUgc3UgY2FudGlkYWQgZXMgcmVsYXRpdmFtZW50ZSBiYWphIGVuIHJlbGFjacOzbiBjb24gZWwgdG90YWwgZGUgZGF0b3MuLioqDQpgYGB7cix3YXJuaW5nPUZBTFNFLG1lc3NhZ2U9RkFMU0V9DQpkZjwtbmEub21pdChkZikjQm9ycmFtb3MgbG9zIHZhbG9yZXMgTkENCmNvbFN1bXMoaXMubmEoZGYpKSNWZXJpZmljYW1vcyBxdWUgbG9zIHZhbG9yZXMgTkEgaGF5YW4gc2lkbyBib3JyYWRvcyBkZSBkZg0KYGBgDQoqKlBhcmEgbWVqb3JhciBsYSBtYW5pcHVsYWNpw7NuIHkgZWwgYW7DoWxpc2lzIGRlIG51ZXN0cmFzIHZhcmlhYmxlcywgaGVtb3MgcmVhbGl6YWRvIGxhIGNvbnZlcnNpw7NuIGRlIGFxdWVsbGFzIHF1ZSBlc3RhYmFuIGVuIGZvcm1hdG8gY2FyYWN0ZXIgYSBmb3JtYXRvIEZhY3Rvci4qKg0KYGBge3J9DQp2YXJfZmFjPC1jKCJHZW5kZXIiLCAiRWR1Y2F0aW9uX0xldmVsIiwiSm9iX1RpdGxlIiwiQ291bnRyeSIsIlJhY2UiKQ0KZGYgPC0gZGYgJT4lDQogIG11dGF0ZShhY3Jvc3MoYWxsX29mKHZhcl9mYWMpLCBhcy5mYWN0b3IpKQ0KYGBgDQoqKkhlIGRldGVjdGFkbyBxdWUgbGEgdmFyaWFibGUgIkVkdWNhdGlvbl9MZXZlbCIgY29udGllbmUgdmFsb3JlcyBzaW1pbGFyZXMgcGVybyBjb24gZGlmZXJlbnRlcyBub21icmVzLiBQYXJhIGxvZ3JhciB1bmEgcmVwcmVzZW50YWNpw7NuIG3DoXMgY29oZXJlbnRlIHkgZXZpdGFyIGR1cGxpY2Fkb3MgZW4gbnVlc3RybyBjb25qdW50byBkZSBkYXRvcywgaGUgcmVhbGl6YWRvIHVuIHByb2Nlc28gZGUgdW5pZmljYWNpw7NuIGRlIGRpY2hvcyB2YWxvcmVzLioqDQoNCmBgYHtyfQ0KZGYkRWR1Y2F0aW9uX0xldmVsIDwtIHJlY29kZSgNCiBkZiRFZHVjYXRpb25fTGV2ZWwsDQogICJCYWNoZWxvcidzIERlZ3JlZSIgPSAiQmFjaGVsb3IncyIsDQogICJQaEQiID0gInBoRCIsDQogICJNYXN0ZXIncyBEZWdyZWUiID0gIk1hc3RlcidzIg0KKQ0KDQpgYGANCioqRGVzcHXDqXMgZGUgbGEgdW5pZmljYWNpw7NuIGRlIGxvcyB2YWxvcmVzIGVuIGxhIHZhcmlhYmxlICJFZHVjYXRpb25fTGV2ZWwiLCBoZSByZWFsaXphZG8gdW4gY29udGVvIHBhcmEgY29ub2NlciBsYSBkaXN0cmlidWNpw7NuIGRlIGxvcyBkaWZlcmVudGVzIG5pdmVsZXMgZGUgZWR1Y2FjacOzbiBwcmVzZW50ZXMgZW4gbnVlc3RybyBjb25qdW50byBkZSBkYXRvcy4gQSBjb250aW51YWNpw7NuLCBzZSBtdWVzdHJhIHVuYSB0YWJsYSBjb24gZWwgbsO6bWVybyBkZSByZWdpc3Ryb3MgYXNvY2lhZG9zIGEgY2FkYSBuaXZlbCBkZSBlZHVjYWNpw7NuOiAqKg0KYGBge3J9DQpDb250ZW88LWZjdF9jb3VudChkZiRFZHVjYXRpb25fTGV2ZWwpDQpwcmludChDb250ZW8pDQpgYGANCioqSGVtb3MgYWdydXBhZG8gbG9zIHZhbG9yZXMgZGUgIkVkdWNhdGlvbl9MZXZlbCIgeSBjYWxjdWxhZG8gZWwgcHJvbWVkaW8gZGVsIHNhbGFyaW8geSBsYSBkZXN2aWFjacOzbiBlc3TDoW5kYXIgcGFyYSBjYWRhIGdydXBvLiBBIGNvbnRpbnVhY2nDs24sIHNlIHByZXNlbnRhbiBsb3MgcmVzdWx0YWRvczouKioNCmBgYHtyfQ0KDQpkZiAlPiUgDQogIGdyb3VwX2J5KEVkdWNhdGlvbl9MZXZlbCkgJT4lIA0KICBzdW1tYXJpc2UoDQogICAgU2FsYXJpb19Qcm9tZWRpbyA9IG1lYW4oU2FsYXJ5KSwNCiAgICBTYWxhcmlvX2Rlc3ZFID0gc2QoU2FsYXJ5KQ0KICApDQpgYGANCioqQSBjb250aW51YWNpw7NuLCBwcmVzZW50byB1biBoaXN0b2dyYW1hIHBhcmEgY2FkYSB1bmEgZGUgbGFzIHZhcmlhYmxlcyBudW3DqXJpY2FzIGVuIGVsIGNvbmp1bnRvIGRlIGRhdG9zOioqDQpgYGB7cix3YXJuaW5nPUZBTFNFfQ0KcGxvdF9oaXN0b2dyYW0oZGYpDQpgYGANCioqRXhwbG9yYW5kbyBsYSBEaXN0cmlidWNpw7NuIGRlIGxvcyBEYXRvcyBNZWRpYW50ZSB1biBHcsOhZmljbyBRUS1QbG90KioNCmBgYHtyfQ0KcGxvdF9xcShkZikNCmBgYA0KKipFeGFtaW5hbmRvIHVuIFJlc3VtZW4gRXN0YWTDrXN0aWNvIGRlIGxvcyBEYXRvcy4qKg0KYGBge3J9DQpzdW1tYXJ5KGRmKSAjUmVzdW1lbiBkZSBlc3RhZGlzdGljb3MgZGVsIGRmDQpgYGANCiMgUmVhbGl6YXJlbW9zIHVuIEFuw6FsaXNpcyBFeHBsb3JhdG9yaW8gZGUgRGF0b3MgKEVEQSkgZGVsIGNvbmp1bnRvIGRlIGRhdG9zLg0KDQoqKlJlcHJlc2VudGFjacOzbiBncsOhZmljYSBxdWUgdmlzdWFsaXphIGxhIHByb3BvcmNpw7NuIGRlIGfDqW5lcm8gc2Vnw7puIGxhIHJhemEuKiogDQpgYGB7cix3YXJuaW5nPUZBTFNFfQ0KbWlzX2NvbG9yZXMgPC0gYygiI0YzMTU1OSIsICIjNzhDMUYzIiwgIiM5NDYwYjUiLCAiIzNjYTJhMiIsICIjZmYzNDhlIiwgIiMyYTZhNjYiLA0KICAgICAgICAgICAgICAgICAiI2RiOGZkNyIsICIjZmNmZjZjIiwgIiMwMGJmZmYiLCAiI2ZmYWYwMCIsICIjRkY1NzMzIiwgIiMwMEZGMDAiLA0KICAgICAgICAgICAgICAgICAiI0ZGRDcwMCIsICIjNkE1QUNEIiwgIiNGRjE0OTMiKQ0KDQphIDwtIGdncGxvdChkZiwgYWVzKHggPSByZW9yZGVyKFJhY2UsIC10YWJsZShSYWNlKVtSYWNlXSksIGZpbGwgPSBHZW5kZXIpKSArDQogIGdlb21fYmFyKHBvc2l0aW9uID0gImRvZGdlIikgKw0KICB0aGVtZV9taW5pbWFsKCkgKw0KICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBtaXNfY29sb3JlcykgKw0KICBjb29yZF9mbGlwKCkgKw0KICBsYWJzKHRpdGxlID0gIlByb3BvcmNpw7NuIERlIEfDqW5lcm8gUG9yIFJhemEiLCB5ID0gIkNvbnRlbyIsIHggPSAiUmF6YSIpDQoNCnByaW50KGEpDQpgYGANCg0KKipSZWFsaWNlbW9zIHVuYSB2aXN1YWxpemFjacOzbiBkZSBsYXMgZnJlY3VlbmNpYXMgZGUgbG9zIHZhbG9yZXMgcHJlc2VudGVzIGVuIGxhIHZhcmlhYmxlICJHw6luZXJvIi4qKg0KYGBge3Isd2FybmluZz1GQUxTRX0NCmIgPC0gZ2dwbG90KGRmLCBhZXMoeCA9IHJlb3JkZXIoR2VuZGVyLCAtdGFibGUoR2VuZGVyKVtHZW5kZXJdKSkpICsNCiAgZ2VvbV9iYXIoYWVzKGZpbGwgPSBHZW5kZXIpKSArDQogIGdlb21fdGV4dChzdGF0ID0gImNvdW50IiwgYWVzKGxhYmVsID0gLi5jb3VudC4uKSwgdmp1c3QgPSAtMC41KSArDQogIHRoZW1lX21pbmltYWwoKSArDQogIGxhYnModGl0bGUgPSAiRnJlY3VlbmNpYSBkZSBHw6luZXJvcyIsIHggPSAiR8OpbmVybyIsIHkgPSAiQ29udGVvIikgKyANCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gbWlzX2NvbG9yZXMpDQoNCnByaW50KGIpDQoNCmBgYA0KDQoqKlJlYWxpemFyZW1vcyB1biBjb250ZW8gZGUgbGFzIG9ic2VydmFjaW9uZXMgcG9yIE5pdmVsIGRlIEVkdWNhY2nDs24uLioqDQpgYGB7cix3YXJuaW5nPUZBTFNFfQ0KYyA8LSBnZ3Bsb3QoZGYsIGFlcyh4ID0gcmVvcmRlcihFZHVjYXRpb25fTGV2ZWwsIC10YWJsZShFZHVjYXRpb25fTGV2ZWwpW0VkdWNhdGlvbl9MZXZlbF0pKSkgKw0KICBnZW9tX2JhcihhZXMoZmlsbCA9IEVkdWNhdGlvbl9MZXZlbCkpICsNCiAgZ2VvbV90ZXh0KHN0YXQgPSAiY291bnQiLCBhZXMobGFiZWwgPSAuLmNvdW50Li4pLCB2anVzdCA9IC0wLjUpICsNCiAgdGhlbWVfbWluaW1hbCgpICsNCiAgbGFicyh0aXRsZSA9ICJGcmVjdWVuY2lhIGRlIEVkdWNhY2nDs24iLCB4ID0gIkVkdWNhY2nDs24iLCB5ID0gIkNvbnRlbyIpICsgDQogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IG1pc19jb2xvcmVzKQ0KDQpwcmludChjKQ0KDQpgYGANCg0KKipFeGFtaW5lbW9zIGPDs21vIGVsIHJhbmdvIGRlIGxhIHZhcmlhYmxlIEVkdWNhY2nDs24gYWZlY3RhIGFsIFNhbGFyaW8uKiogDQpgYGB7cix3YXJuaW5nPUZBTFNFLG1lc3NhZ2U9RkFMU0V9DQogZCA8LSBnZ3Bsb3QoZGYsIGFlcyh4ID0gcmVvcmRlcihFZHVjYXRpb25fTGV2ZWwsIFNhbGFyeSwgbWVkaWFuKSwgeSA9IFNhbGFyeSwgZmlsbCA9IEVkdWNhdGlvbl9MZXZlbCkpICsNCiAgZ2VvbV9ib3hwbG90KGNvbG9yID0gImJsYWNrIikgKyANCiAgbGFicyh0aXRsZSA9ICJFZHVjYWNpw7NuIHZzIFNhbGFyaW8iLA0KICAgICAgIHggPSAiRWR1Y2FjacOzbiIsIHkgPSAiU2FsYXJpbyIpICsNCiAgdGhlbWVfbWluaW1hbCgpICsgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gbWlzX2NvbG9yZXMpDQoNCmdncGxvdGx5KGQpDQoNCmBgYA0KDQoqKkFob3JhIHByb2NlZGVyZW1vcyBhIGFuYWxpemFyIGPDs21vIHNlIGRpc3RyaWJ1eWUgbGEgdmFyaWFibGUgIkfDqW5lcm8iIGRlbnRybyBkZSBsb3MgZGlmZXJlbnRlcyBuaXZlbGVzIGRlIGVkdWNhY2nDs24uKioNCg0KYGBge3Isd2FybmluZz1GQUxTRSxtZXNzYWdlPUZBTFNFfQ0KDQogZGZfcGVyY2VudGFqZTwtZGYgJT4lDQogIGdyb3VwX2J5KEVkdWNhdGlvbl9MZXZlbCwgR2VuZGVyKSAlPiUNCiAgdGFsbHkoKSAlPiUNCiAgbXV0YXRlKFBvcmNlbnRhamUgPSBuIC8gc3VtKG4pKQ0KDQplIDwtIGdncGxvdChkZl9wZXJjZW50YWplLCBhZXMoeCA9IHJlb3JkZXIoRWR1Y2F0aW9uX0xldmVsLCBQb3JjZW50YWplLCBtZWRpYW4pLCB5ID0gUG9yY2VudGFqZSwgZmlsbCA9IEdlbmRlcikpICsNCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIHBvc2l0aW9uID0gImRvZGdlIikgKw0KICBnZW9tX3RleHQoYWVzKGxhYmVsID0gc2NhbGVzOjpwZXJjZW50KFBvcmNlbnRhamUpKSwgcG9zaXRpb24gPSBwb3NpdGlvbl9kb2RnZSh3aWR0aCA9IDAuOSksIHZqdXN0ID0gLTAuNSkgKw0KICB0aGVtZV9taW5pbWFsKCkgKw0KICBsYWJzKHRpdGxlID0gIlBvcmNlbnRhamUgZGUgR8OpbmVybyBwb3IgTml2ZWwgZGUgRWR1Y2FjacOzbiIsDQogICAgICAgeCA9ICJOaXZlbCBkZSBFZHVjYWNpw7NuIiwgeSA9ICJQb3JjZW50YWplIikgKyBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBtaXNfY29sb3JlcykNCg0KcHJpbnQoZSkNCg0KYGBgDQoNCioqQSBjb250aW51YWNpw7NuLCByZWFsaXphcmVtb3MgdW5hIG9ic2VydmFjacOzbiBkZSBsYSB2YXJpYWJsZSAiU2FsYXJpbyIgY29uIHJlc3BlY3RvIGFsIGfDqW5lcm8uIEFuYWxpemFyZW1vcyBjw7NtbyBzZSBkaXN0cmlidXllIGVsIHNhbGFyaW8gZW4gZnVuY2nDs24gZGUgbG9zIGRpc3RpbnRvcyBnw6luZXJvcyBwcmVzZW50ZXMgZW4gbnVlc3RybyBjb25qdW50byBkZSBkYXRvcy4qKiANCmBgYHtyLHdhcm5pbmc9RkFMU0UsbWVzc2FnZT1GQUxTRX0NCmYgPC0gZ2dwbG90KGRmLCBhZXMoeCA9IHJlb3JkZXIoR2VuZGVyLCBTYWxhcnksIG1lZGlhbiksIHkgPSBTYWxhcnksIGZpbGwgPSBHZW5kZXIpKSArDQogIGdlb21fYm94cGxvdChjb2xvciA9ICJibGFjayIpICsNCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gbWlzX2NvbG9yZXMpICsNCiAgbGFicyh0aXRsZSA9ICJHcsOhZmljbyBkZSBDYWphIEfDqW5lcm8gdnMgU2FsYXJpbyIsDQogICAgICAgeCA9ICJHw6luZXJvIiwgeSA9ICJTYWxhcmlvIikgKw0KICB0aGVtZV9taW5pbWFsKCkNCg0KZ2dwbG90bHkoZikNCg0KDQpgYGANCg0KKipBIGNvbnRpbnVhY2nDs24sIHByb2NlZGVyZW1vcyBhIHJlYWxpemFyIHVuYSB2aXN1YWxpemFjacOzbiBkZSBjw7NtbyBzZSBkaXN0cmlidXllIGVsIHNhbGFyaW8gZW4gZGlmZXJlbnRlcyBwYcOtc2VzLiBFc3RvIG5vcyBwZXJtaXRpcsOhIGFuYWxpemFyIGxhcyBkaWZlcmVuY2lhcyBzYWxhcmlhbGVzIGVudHJlIGxvcyBkaXN0aW50b3MgcGHDrXNlcyBwcmVzZW50ZXMgZW4gbnVlc3RybyBjb25qdW50byBkZSBkYXRvcy4uKioNCmBgYHtyLHdhcm5pbmc9RkFMU0UsbWVzc2FnZT1GQUxTRX0NCmcgPC0gZ2dwbG90KGRmLCBhZXMoeCA9IHJlb3JkZXIoQ291bnRyeSwgU2FsYXJ5LCBtZWRpYW4pLCB5ID0gU2FsYXJ5LCBmaWxsID0gRWR1Y2F0aW9uX0xldmVsKSkgKw0KICBnZW9tX2JveHBsb3QoY29sb3IgPSAiYmxhY2siKSArDQogIGxhYnModGl0bGUgPSAiR3LDoWZpY28gZGUgQ2FqYXMgUGHDrXNlcyB2cyBTYWxhcmlvIiwNCiAgICAgICB4ID0gIlBhw61zIiwgeSA9ICJTYWxhcmlvIikgKw0KICB0aGVtZV9taW5pbWFsKCkgKw0KICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBtaXNfY29sb3JlcykNCg0KcHJpbnQoZykNCg0KYGBgDQoNCkFudGUgbGEgb2JzZXJ2YWNpw7NuIGRlbCBncsOhZmljbyBhbnRlcmlvciwgZG9uZGUgc2UgbXVlc3RyYSBxdWUgZWwgcHJvbWVkaW8gZGVsIHNhbGFyaW8gZXMgaWd1YWwgZW4gZGlzdGludG9zIHBhw61zZXMsIHN1cmdlIGxhIGR1ZGEgYWNlcmNhIGRlIGxhIHZlcmFjaWRhZCBkZSBsb3MgZGF0b3MuIFNpbiBlbWJhcmdvLCBjb24gZWwgZmluIGRlIGNvbnRpbnVhciBjb24gZWwgYW7DoWxpc2lzIGRlIGRhdG9zIGEgbW9kbyBkZSBwcsOhY3RpY2EsIHByb2NlZGVyZW1vcyBhIHNlZ3VpciBleHBsb3JhbmRvIHkgdmlzdWFsaXphbmRvIGxhIGluZm9ybWFjacOzbiBkaXNwb25pYmxlLg0KDQoqKkEgY29udGludWFjacOzbiwgcHJlc2VudGFtb3MgbGEgZGlzdHJpYnVjacOzbiBkZWwgc2FsYXJpbyBiYXNhZGEgZW4gbGEgdmFyaWFibGUgIlJhemEiLiBFc3RlIGdyw6FmaWNvIG5vcyBwZXJtaXRlIHZpc3VhbGl6YXIgY8OzbW8gc2UgZGlzdHJpYnV5ZW4gbG9zIHNhbGFyaW9zIGVudHJlIGxhcyBkaWZlcmVudGVzIGNhdGVnb3LDrWFzIGRlIHJhemEgZW4gbnVlc3RybyBjb25qdW50byBkZSBkYXRvcy4gRXMgaW1wb3J0YW50ZSBkZXN0YWNhciBxdWUgZXN0ZSBhbsOhbGlzaXMgdGllbmUgZmluZXMgcHVyYW1lbnRlIGV4cGxvcmF0b3Jpb3MgeSBub3MgYXl1ZGEgYSBpZGVudGlmaWNhciBwb3NpYmxlcyBwYXRyb25lcyBvIHRlbmRlbmNpYXMgcmVsYWNpb25hZGFzIGNvbiBsYSB2YXJpYWJsZSBkZSBpbnRlcsOpcy4qKg0KYGBge3Isd2FybmluZz1GQUxTRSxtZXNzYWdlPUZBTFNFfQ0KaCA8LSBnZ3Bsb3QoZGYsIGFlcyh4ID0gcmVvcmRlcihSYWNlLCBTYWxhcnksIG1lZGlhbiksIHkgPSBTYWxhcnksIGZpbGwgPSBSYWNlKSkgKw0KICBnZW9tX2JveHBsb3QoY29sb3IgPSAiYmxhY2siKSArDQogIGxhYnModGl0bGUgPSAiR3LDoWZpY28gZGUgQ2FqYXMgUmF6YSB2cyBTYWxhcmlvIiwNCiAgICAgICB4ID0gIlJhemEiLCB5ID0gIlNhbGFyaW8iKSArDQogIHRoZW1lX21pbmltYWwoKSArDQogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkpICsNCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gbWlzX2NvbG9yZXMpDQoNCmdncGxvdGx5KGgpDQoNCmBgYA0KKipQYXJhIHJlYWxpemFyIHVuYSBnZW5lcmFsaXphY2nDs24gZGUgbGEgZWRhZCB5IGVzdHVkaWFyIGxhIGRpc3RyaWJ1Y2nDs24gZGVsIHNhbGFyaW8gY29uIHJlc3BlY3RvIGEgZXN0YSB2YXJpYWJsZSwgaGUgcmVhbGl6YWRvIGNvcnRlcyBlcXVpdGF0aXZvcyBkZWwgcmFuZ28gZGUgZWRhZC4gRXN0byBub3MgcGVybWl0ZSBhZ3J1cGFyIGEgbGFzIHBlcnNvbmFzIGVuIGRpZmVyZW50ZXMgY2F0ZWdvcsOtYXMgZGUgZWRhZCBwYXJhIGZhY2lsaXRhciBlbCBhbsOhbGlzaXMgeSBsYSB2aXN1YWxpemFjacOzbiBkZSBjw7NtbyBzZSBkaXN0cmlidXllbiBsb3Mgc2FsYXJpb3MgZW4gY2FkYSBncnVwby4qKg0KDQpgYGB7cix3YXJuaW5nPUZBTFNFLG1lc3NhZ2U9RkFMU0V9DQpkZiRBZ2VfSW50ZXJ2YSA8LSBjdXQoZGYkQWdlLCBicmVha3MgPSBjKDIxLCAzMSwgNDEsIDUxLCA2MiksIGluY2x1ZGUubG93ZXN0ID0gVFJVRSkNCg0KdGFibGUoZGYkQWdlX0ludGVydmEpDQoNCmkgPC0gZ2dwbG90KGRmLCBhZXMoeCA9IHJlb3JkZXIoQWdlX0ludGVydmEsIFNhbGFyeSwgbWVkaWFuKSwgeSA9IFNhbGFyeSwgZmlsbCA9IEFnZV9JbnRlcnZhKSkgKw0KICBnZW9tX2JveHBsb3QoY29sb3IgPSAiYmxhY2siKSArDQogIGxhYnModGl0bGUgPSAiRWRhZCBlbiBpbnRlcnZhbG9zIHZzIFNhbGFyaW8iLA0KICAgICAgIHggPSAiSW50ZXJ2YWxvcyBkZSBFZGFkIiwgeSA9ICJTYWxhcmlvIikgKw0KICB0aGVtZV9taW5pbWFsKCkgKw0KICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpKSArDQogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IG1pc19jb2xvcmVzKQ0KDQpnZ3Bsb3RseShpKQ0KDQpgYGANCg0KKipBIGNvbnRpbnVhY2nDs24sIHByb2NlZGVtb3MgYSByZWFsaXphciB1biBncsOhZmljbyBkZSBwdW50b3MgcGFyYSBleHBsb3JhciBsYSBjb3JyZWxhY2nDs24gZW50cmUgbGEgdmFyaWFibGUgInNhbGFyaW8iIHkgImHDsW9zIGRlIGV4cGVyaWVuY2lhIi4qKg0KYGBge3Isd2FybmluZz1GQUxTRSxtZXNzYWdlPUZBTFNFfQ0KajwtIGdncGxvdChkZiwgYWVzKHggPSBTYWxhcnksIHkgPSBZZWFyc19vZl9FeHBlcmllbmNlLCBjb2xvciA9IEVkdWNhdGlvbl9MZXZlbCkpICsNCiAgZ2VvbV9wb2ludCgpICsNCiAgdGhlbWVfbWluaW1hbCgpICsgDQogIGxhYnModGl0bGUgPSAiU2FsYXJpbyB2cyBBw7FvcyBkZSBFeHBlcmllbmNpYSIsDQogICAgICAgeCA9ICJTYWxhcmlvIiwgeSA9ICJBw7FvcyBkZSBFeHBlcmllbmNpYSIgKSArIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IG1pc19jb2xvcmVzKQ0KZ2dwbG90bHkoaikNCmBgYA0KDQoqKkEgY29udGludWFjacOzbiwgcHJvY2VkZW1vcyBhIHJlYWxpemFyIHVuIGdyw6FmaWNvIGRlIHB1bnRvcyBwYXJhIHZpc3VhbGl6YXIgbGEgcmVsYWNpw7NuIGVudHJlIGVsIHNhbGFyaW8geSBsb3MgYcOxb3MgZGUgZXhwZXJpZW5jaWEsIHV0aWxpemFuZG8gZWwgY29sb3IgcGFyYSByZXByZXNlbnRhciBsb3MgZ8OpbmVyb3MuIEVzdGUgdGlwbyBkZSBncsOhZmljbyBub3MgcGVybWl0aXLDoSBvYnNlcnZhciBjw7NtbyBzZSBkaXN0cmlidXllbiBsb3Mgc2FsYXJpb3MgeSBsYSBleHBlcmllbmNpYSBlbiBmdW5jacOzbiBkZSBsb3MgZGlmZXJlbnRlcyBnw6luZXJvcywgbG8gcXVlIG5vcyBheXVkYXLDoSBhIGlkZW50aWZpY2FyIHBvc2libGVzIHBhdHJvbmVzIG8gZGlmZXJlbmNpYXMgZW50cmUgZWxsb3MuKioNCmBgYHtyLHdhcm5pbmc9RkFMU0UsbWVzc2FnZT1GQUxTRX0NCms8LSBnZ3Bsb3QoZGYsIGFlcyh4ID0gU2FsYXJ5LCB5ID0gWWVhcnNfb2ZfRXhwZXJpZW5jZSwgY29sb3IgPSBHZW5kZXIpKSArDQogIGdlb21fcG9pbnQoKSArDQogIHRoZW1lX21pbmltYWwoKSArIA0KICBsYWJzKHRpdGxlID0gIlNhbGFyaW8gdnMgQcOxb3MgZGUgRXhwZXJpZW5jaWEiLA0KICAgICAgIHggPSAiU2FsYXJpbyIsIHkgPSAiQcOxb3MgZGUgRXhwZXJpZW5jaWEiICkgKyBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBtaXNfY29sb3JlcykNCmdncGxvdGx5KGspDQpgYGANCg0KKipBIGNvbnRpbnVhY2nDs24sIHByZXNlbnRhbW9zIHVuIGdyw6FmaWNvIGRlIGJhcnJhcyBxdWUgbXVlc3RyYSBsYXMgcHJvZmVzaW9uZXMgY29uIGxvcyBzYWxhcmlvcyBtw6FzIGFsdG9zLiBQYXJhIGVsbG8sIGhlbW9zIG9yZGVuYWRvIGxhcyBwcm9mZXNpb25lcyBkZSBtYXlvciBhIG1lbm9yIHNhbGFyaW8geSBoZW1vcyBkZXN0YWNhZG8gbGFzIHByaW1lcmFzIHBvc2ljaW9uZXMgcGFyYSBpZGVudGlmaWNhciBjdcOhbGVzIHNvbiBsYXMgbcOhcyByZW11bmVyYWRhcy4qKg0KYGBge3Isd2FybmluZz1GQUxTRSxtZXNzYWdlPUZBTFNFfQ0KIyBQYXNvIDE6IENhbGN1bGFyIGVsIHBlcmNlbnRpbCA3NSBkZWwgc2FsYXJpbw0KcGVyY2VudGlsXzc1IDwtIHF1YW50aWxlKGRmJFNhbGFyeSwgMC43NSkNCg0KIyBQYXNvIDI6IEZpbHRyYXIgZWwgbWFyY28gZGUgZGF0b3MgcGFyYSBxdWVkYXJ0ZSBjb24gbGFzIHByb2Zlc2lvbmVzIG1lam9yIHBhZ2FkYXMNCmRmX2ZpbHRyYWRvIDwtIGRmICU+JSANCiAgZmlsdGVyKFNhbGFyeSA+IHBlcmNlbnRpbF83NSkNCg0KIyBQYXNvIDM6IE9yZGVuYXIgZWwgbWFyY28gZGUgZGF0b3MgcG9yIGVsIHNhbGFyaW8gZGUgZm9ybWEgZGVzY2VuZGVudGUNCmRmX2ZpbHRyYWRvIDwtIGRmX2ZpbHRyYWRvICU+JSANCiAgYXJyYW5nZShkZXNjKFNhbGFyeSkpDQoNCg0KbCA8LSBnZ3Bsb3QodG9wXzE1X3RpdHVsb3MsIGFlcyh4ID0gcmVvcmRlcihKb2JfVGl0bGUsIC1TYWxhcmlvX3Byb21lZGlvKSwgeSA9IFNhbGFyaW9fcHJvbWVkaW8sIGZpbGwgPSBKb2JfVGl0bGUpKSArDQogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiKSArDQogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkpICsNCiAgbGFicyh0aXRsZSA9ICJUb3AgMTUgVMOtdHVsb3MgY29uIE1lam9yIFNhbGFyaW8gUHJvbWVkaW8iLA0KICAgICAgIHggPSAiVMOtdHVsbyBkZWwgVHJhYmFqbyIsDQogICAgICAgeSA9ICJTYWxhcmlvIFByb21lZGlvIikgKw0KICBndWlkZXMoZmlsbCA9IEZBTFNFKQ0KDQpwcmludChsKQ0KDQoNCmBgYA0KDQoqKkEgY29udGludWFjacOzbiwgcHJlc2VudGFtb3MgdW4gZ3LDoWZpY28gZGUgY29ycmVsYWNpw7NuIHF1ZSBub3MgcGVybWl0aXLDoSB2aXN1YWxpemFyIGxhcyByZWxhY2lvbmVzIGVudHJlIGxhcyB2YXJpYWJsZXMgZGUgbnVlc3RybyBjb25qdW50byBkZSBkYXRvcy4qKiANCmBgYHtyLHdhcm5pbmc9RkFMU0UsbWVzc2FnZT1GQUxTRX0NCmRmMjwtZGYNCiMgQ29udmVyaW1vcyBsYXMgdmFyaWFibGVzIGNhdGVnw7NyaWNhcyBhIHZhbG9yZXMgbnVtw6lyaWNvcw0KZGYyJEdlbmRlciA8LSBhcy5udW1lcmljKGZhY3RvcihkZiRHZW5kZXIpKQ0KZGYyJEVkdWNhdGlvbl9MZXZlbCA8LSBhcy5udW1lcmljKGZhY3RvcihkZiRFZHVjYXRpb25fTGV2ZWwpKQ0KZGYyJEpvYl9UaXRsZSA8LSBhcy5udW1lcmljKGZhY3RvcihkZiRKb2JfVGl0bGUpKQ0KZGYyJENvdW50cnkgPC0gYXMubnVtZXJpYyhmYWN0b3IoZGYkQ291bnRyeSkpDQpkZjIkUmFjZSA8LSBhcy5udW1lcmljKGZhY3RvcihkZiRSYWNlKSkNCmRmMiRBZ2VfSW50ZXJ2YTwtYXMubnVtZXJpYyhmYWN0b3IoZGYkQWdlX0ludGVydmEpKQ0KDQojQ3JlYW1vcyBlbCBHcmFmaWNvIGRlIGNvcnJlbGFjaW9uDQpjb3JyX21hdHJpeCA8LSBjb3IoZGYyKQ0KDQojIFZpc3VhbGl6YW1vcyBlbCBncsOhZmljbyBkZSBjb3JyZWxhY2nDs24gY29uIGFqdXN0ZXMgcGVyc29uYWxpemFkb3MNCm08LWNvcnJwbG90KGNvcnJfbWF0cml4LCBtZXRob2QgPSAiY29sb3IiLCB0eXBlID0gInVwcGVyIiwgDQogICAgICAgICB0bC5jZXggPSAwLjcsIGFkZGNvbG9yID0gVFJVRSwNCiAgICAgICAgIGNvbCA9IGNvbG9yUmFtcFBhbGV0dGUoYygiIzk0NjBiNSIsICJ3aGl0ZSIsICIjRjMxNTU5IikpKDEwMCksDQogICAgICAgICBjbC5saW0gPSBjKC0xLCAxKSwNCiAgICAgICAgIGlzLmNvcnIgPSBUUlVFLA0KICAgICAgICAgYWRkQ29lZi5jb2wgPSAiYmxhY2siLA0KICAgICAgICAgbnVtYmVyLmNleCA9IDAuOCwNCiAgICAgICAgIHRsLmNvbCA9ICJibGFjayIsDQogICAgICAgICBkaWFnID0gRkFMU0UsDQogICAgICAgICB0bC5zcnQgPSA0NSwNCiAgICAgICAgIGFkZGdyaWQuY29sID0gImJsYWNrIikgDQoNCmBgYA0KDQoNCg0KIyBQcm9jZWRlbW9zIGEgcmVhbGl6YXIgdW5hIGV2YWx1YWNpb24gZGUgZGlzdGludG9zIG1vZGVsb3MNCkVuIGVzdGEgc2VjY2nDs24sIGxsZXZhcmVtb3MgYSBjYWJvIHVuYSBldmFsdWFjacOzbiBkZSB2YXJpb3MgbW9kZWxvcyBwYXJhIHByZWRlY2lyIGVsIHNhbGFyaW8gZW4gZnVuY2nDs24gZGUgbGFzIHZhcmlhYmxlcyBkaXNwb25pYmxlcyBlbiBudWVzdHJvIGNvbmp1bnRvIGRlIGRhdG9zLiBMb3MgbW9kZWxvcyBxdWUgdXRpbGl6YXJlbW9zIHNvbiBsb3Mgc2lndWllbnRlczoNCg0KMS4gUmVncmVzacOzbiBMaW5lYWwNCg0KMi4gw4FyYm9sIGRlIERlY2lzacOzbg0KDQozLiBCb3NxdWVzIEFsZWF0b3Jpb3MNCg0KUGFyYSBjYWRhIG1vZGVsbywgcmVhbGl6YXJlbW9zIHVuYSBwYXJ0aWNpw7NuIGRlIG51ZXN0cm9zIGRhdG9zIGVuIGNvbmp1bnRvcyBkZSBlbnRyZW5hbWllbnRvIHkgcHJ1ZWJhLCBlbnRyZW5hcmVtb3MgZWwgbW9kZWxvIGVuIGVsIGNvbmp1bnRvIGRlIGVudHJlbmFtaWVudG8geSBldmFsdWFyZW1vcyBzdSByZW5kaW1pZW50byBlbiBlbCBjb25qdW50byBkZSBwcnVlYmEuIFV0aWxpemFyZW1vcyBkaWZlcmVudGVzIG3DqXRyaWNhcyBkZSBldmFsdWFjacOzbi4NCg0KQSBjb250aW51YWNpw7NuLCBwcmVzZW50YXJlbW9zIGxvcyByZXN1bHRhZG9zIGRlIGxhIGV2YWx1YWNpw7NuIHBhcmEgY2FkYSBtb2RlbG8sIGxvIHF1ZSBub3MgcGVybWl0aXLDoSB0b21hciBkZWNpc2lvbmVzIGluZm9ybWFkYXMgc29icmUgY3XDoWwgbW9kZWxvIGVzIGVsIG3DoXMgYWRlY3VhZG8gcGFyYSBudWVzdHJhcyBuZWNlc2lkYWRlcyBkZSBwcmVkaWNjacOzbiBkZSBzYWxhcmlvcy4NCg0KYGBge3J9DQpzZXQuc2VlZCg0MikNCnBhcnRpdGlvbl9pbmRleGVzIDwtIGNyZWF0ZURhdGFQYXJ0aXRpb24oZGYyJFNhbGFyeSwgcCA9IDAuNywgbGlzdCA9IEZBTFNFKQ0KDQpYX3RyYWluIDwtIGRmMltwYXJ0aXRpb25faW5kZXhlcywgLXdoaWNoKG5hbWVzKGRmMikgPT0gIlNhbGFyeSIpXQ0KWF90ZXN0IDwtIGRmMlstcGFydGl0aW9uX2luZGV4ZXMsIC13aGljaChuYW1lcyhkZjIpID09ICJTYWxhcnkiKV0NCnlfdHJhaW4gPC0gZGYyJFNhbGFyeVtwYXJ0aXRpb25faW5kZXhlc10NCnlfdGVzdCA8LSBkZjIkU2FsYXJ5Wy1wYXJ0aXRpb25faW5kZXhlc10NCg0KbW9kZWxzIDwtIGxpc3QoDQogIGxtX21vZGVsID0gbG0oU2FsYXJ5IH4gLiwgZGF0YSA9IGRmMiksDQogIHRyZWVfbW9kZWwgPSBycGFydChTYWxhcnkgfiAuLCBkYXRhID0gZGYyKSwNCiAgcmZfbW9kZWwgPSByYW5kb21Gb3Jlc3QoU2FsYXJ5IH4gLiwgZGF0YSA9IGRmMiwgbnRyZWUgPSAzNTApDQopDQoNCnJlc3VsdHMgPC0gZGF0YS5mcmFtZSgNCiAgTW9kZWxfTmFtZSA9IGNoYXJhY3RlcigpLA0KICBNZWFuX0Fic29sdXRlX0Vycm9yX01BRSA9IG51bWVyaWMoKSwNCiAgTWVhbl9BYnNvbHV0ZV9QZXJjZW50YWdlX0Vycm9yX01BUEUgPSBudW1lcmljKCksDQogIE1lYW5fU3F1YXJlZF9FcnJvcl9NU0UgPSBudW1lcmljKCksDQogIFJvb3RfTWVhbl9TcXVhcmVkX0Vycm9yX1JNU0UgPSBudW1lcmljKCksDQogIFIyX3Njb3JlID0gbnVtZXJpYygpLA0KICBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UNCikNCg0KZm9yIChpIGluIDE6bGVuZ3RoKG1vZGVscykpIHsNCiAgbW9kZWwgPC0gbW9kZWxzW1tpXV0NCiAgbW9kZWxfbmFtZSA8LSBuYW1lcyhtb2RlbHMpW2ldDQogIA0KICAjIEhhY2VyIGxhcyBwcmVkaWNjaW9uZXMgY29uIGNhZGEgbW9kZWxvIGVuIGVsIGNvbmp1bnRvIGRlIHBydWViYSAoWF90ZXN0KQ0KICB5X3ByZWQgPC0gcHJlZGljdChtb2RlbCwgbmV3ZGF0YSA9IFhfdGVzdCkNCiAgDQogICMgQ2FsY3VsYXIgbGFzIG3DqXRyaWNhcyBkZSBldmFsdWFjacOzbiBwYXJhIGVsIG1vZGVsbyBhY3R1YWwNCiAgbWFlIDwtIG1lYW4oYWJzKHlfdGVzdCAtIHlfcHJlZCkpDQogIG1hcGUgPC0gbWVhbihhYnMoKHlfdGVzdCAtIHlfcHJlZCkgLyB5X3Rlc3QpKSAqIDEwMA0KICBtc2UgPC0gbWVhbigoeV90ZXN0IC0geV9wcmVkKV4yKQ0KICBybXNlIDwtIHNxcnQobXNlKQ0KICByMiA8LSBjb3IoeV90ZXN0LCB5X3ByZWQpXjINCiAgDQogICMgQWdyZWdhciBsb3MgcmVzdWx0YWRvcyBhbCBkYXRhIGZyYW1lICdyZXN1bHRzJw0KICByZXN1bHRzIDwtIHJiaW5kKHJlc3VsdHMsIGRhdGEuZnJhbWUoDQogICAgTW9kZWxfTmFtZSA9IG1vZGVsX25hbWUsDQogICAgTWVhbl9BYnNvbHV0ZV9FcnJvcl9NQUUgPSBtYWUsDQogICAgTWVhbl9BYnNvbHV0ZV9QZXJjZW50YWdlX0Vycm9yX01BUEUgPSBtYXBlLA0KICAgIE1lYW5fU3F1YXJlZF9FcnJvcl9NU0UgPSBtc2UsDQogICAgUm9vdF9NZWFuX1NxdWFyZWRfRXJyb3JfUk1TRSA9IHJtc2UsDQogICAgUjJfc2NvcmUgPSByMg0KICApKQ0KfQ0KDQpwcmludChyZXN1bHRzKQ0KDQpgYGANCg0KKipQYXJhIGV2YWx1YXIgbGEgZWZpY2FjaWEgZGUgY2FkYSBtb2RlbG8sIGdyYWZpY2FyZW1vcyBlbCAiRXJyb3IgTWVkaW8gQWJzb2x1dG8iIChNQUUpIG9idGVuaWRvIGVuIGxhIGV2YWx1YWNpw7NuIGRlIGNhZGEgdW5vIGRlIGVsbG9zLiBFbCBNQUUgZXMgdW5hIG3DqXRyaWNhIHF1ZSBub3MgaW5kaWNhIGVsIHByb21lZGlvIGRlIGxhcyBkaWZlcmVuY2lhcyBhYnNvbHV0YXMgZW50cmUgbGFzIHByZWRpY2Npb25lcyBkZWwgbW9kZWxvIHkgbG9zIHZhbG9yZXMgcmVhbGVzLkN1YW50byBtZW5vciBzZWEgZWwgTUFFLCBtZWpvciBzZXLDoSBlbCByZW5kaW1pZW50byBkZWwgbW9kZWxvIGVuIHTDqXJtaW5vcyBkZSBwcmVjaXNpw7NuIGRlIGxhcyBwcmVkaWNjaW9uZXMuKiogDQokJCBNQUUgPSBcZnJhY3sxfXtufSBcc3VtIHx5X3twcmVkfSAtIHlfe3JlYWx9fCAkJA0KYGBge3Isd2FybmluZz1GQUxTRSxtZXNzYWdlPUZBTFNFfQ0KIyBDcmVhciBlbCBncsOhZmljbyBkZSBiYXJyYXMgY29uIGdncGxvdDINCm4gPC0gZ2dwbG90KHJlc3VsdHMsIGFlcyh4ID0gTW9kZWxfTmFtZSwgeSA9IE1lYW5fQWJzb2x1dGVfRXJyb3JfTUFFLCBmaWxsID0gTW9kZWxfTmFtZSkpICsNCiAgZ2VvbV9iYXIoc3RhdCA9ICdpZGVudGl0eScpICsNCiAgbGFicyh0aXRsZSA9ICcoTUFFKSBwb3IgbW9kZWxvJywgeCA9ICdNb2RlbG9zJywgeSA9ICdNQUUnKSArDQogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkpICsgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gbWlzX2NvbG9yZXMpICsgdGhlbWVfbWluaW1hbCgpDQoNCiMgTW9zdHJhciBlbCBncsOhZmljbyBlc3TDoXRpY28NCnByaW50KG4pDQpgYGANCg0KKipQYXJhIGV2YWx1YXIgbGEgZWZpY2FjaWEgZGUgY2FkYSBtb2RlbG8sIEdyYWZpY2FyZW1vcyBncmFmaWNhciBlbCBjb2VmaWNpZW50ZSBSIGN1YWRyYWRvIChSMikgZGUgY2FkYSBtb2RlbG8uIEVsIGNvZWZpY2llbnRlIFIyIGVzIHVuYSBtZWRpZGEgcXVlIGluZGljYSBjdcOhbnRhIHZhcmlhbnphIGRlIGxhIHZhcmlhYmxlIGRlcGVuZGllbnRlIChlbiBudWVzdHJvIGNhc28sIGVsIHNhbGFyaW8pIGVzIGV4cGxpY2FkYSBwb3IgZWwgbW9kZWxvLiBDdWFudG8gbcOhcyBjZXJjYW5vIGEgMSBzZWEgZWwgdmFsb3IgZGUgUjIsIG1lam9yIHNlcsOhIGxhIGNhcGFjaWRhZCBkZWwgbW9kZWxvIHBhcmEgZXhwbGljYXIgbGEgdmFyaWFiaWxpZGFkIGRlIGxvcyBkYXRvcy4qKg0KYGBge3Isd2FybmluZz1GQUxTRSxtZXNzYWdlPUZBTFNFfQ0KbzwtZ2dwbG90KHJlc3VsdHMsIGFlcyh4ID0gTW9kZWxfTmFtZSwgeSA9IFIyX3Njb3JlLCBmaWxsID0gTW9kZWxfTmFtZSkpICsNCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIpICsNCiAgbGFicyh0aXRsZSA9ICJSLXNxdWFyZWQgcG9yIG1vZGVsbyIsIHggPSAiTW9kZWxvcyIsIHkgPSAiUi1zcXVhcmVkIikgKw0KICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpKSArDQogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IG1pc19jb2xvcmVzKSArIHRoZW1lX21pbmltYWwoKQ0KcHJpbnQobykNCmBgYA0KKipUcmFzIGxsZXZhciBhIGNhYm8gdW5hICBldmFsdWFjacOzbiBkZSBsb3MgZGlmZXJlbnRlcyBtb2RlbG9zLCBzZSBoYSBkZXN0YWNhZG8gcXVlIGVsIG1vZGVsbyBkZSBCb3NxdWVzIEFsZWF0b3Jpb3MgaGEgc29icmVzYWxpZG8gcG9yIHN1IGFsdG8gcmVuZGltaWVudG8geSBwcmVjaXNpw7NuIHByZWRpY3RpdmEuIEFob3JhLCBwYXJhIHByb2Z1bmRpemFyIGVuIHN1IGRlc2VtcGXDsW8sIG9ic2VydmFyZW1vcyBkZXRhbGxhZGFtZW50ZSBsYXMgbcOpdHJpY2FzIG9idGVuaWRhcyBwb3IgZXN0ZSBtb2RlbG8sIGxvIGN1YWwgbm9zIHBlcm1pdGlyw6EgbWVkaXIgc3UgZWZpY2FjaWEgZW4gbGEgcHJlZGljY2nDs24gZGVsIHNhbGFyaW8gYmFzw6FuZG9zZSBlbiBsYXMgdmFyaWFibGVzIGluZGVwZW5kaWVudGVzIGNvbnNpZGVyYWRhcy4qKiANCmBgYHtyfQ0KIyBPYnRlbmVtb3MgbG9zIGVycm9yZXMgZGVsIG1vZGVsbyBkZSBib3NxdWUgYWxlYXRvcmlvDQpyZl9lcnJvcnMgPC0gbW9kZWxzJHJmX21vZGVsJG1zZQ0KDQojIENyZWFtb3MgdW4gZGF0YSBmcmFtZSBwYXJhIGxhIGN1cnZhIGRlIGFwcmVuZGl6YWplDQpjdXJ2ZV9kYXRhIDwtIGRhdGEuZnJhbWUodHJlZXMgPSBzZXFfYWxvbmcocmZfZXJyb3JzKSwgRXJyb3IgPSByZl9lcnJvcnMpDQoNCiMgQ3JlYW1vcyBlbCBncsOhZmljbyBkZSBjdXJ2YSBkZSBhcHJlbmRpemFqZSBjb24gZ2dwbG90Mg0KZ2dwbG90KGN1cnZlX2RhdGEsIGFlcyh4ID0gdHJlZXMsIHkgPSBFcnJvcikpICsNCiAgZ2VvbV9saW5lKCkgKw0KICBsYWJzKHRpdGxlID0gIkN1cnZhIGRlIEFwcmVuZGl6YWplIC0gQm9zcXVlIEFsZWF0b3JpbyIsDQogICAgICAgeCA9ICJOw7ptZXJvIGRlIMOBcmJvbGVzIiwNCiAgICAgICB5ID0gIkVycm9yIikgKw0KICB0aGVtZV9taW5pbWFsKCkNCg0KIyBDcmVhbW9zIHVuIGRhdGEgZnJhbWUgY29uIGxhcyBwcmVkaWNjaW9uZXMgeSB2YWxvcmVzIHJlYWxlcw0KY29tcGFyaXNvbl9kYXRhIDwtIGRhdGEuZnJhbWUoVmFsb3JfUmVhbCA9IHlfdGVzdCwgUHJlZGljY2lvbiA9IHlfcHJlZF9yZikNCg0KIyBDcmVhbW9zIGVsIGdyw6FmaWNvIGRlIGRpc3BlcnNpw7NuIGNvbiBnZ3Bsb3QyDQpnZ3Bsb3QoY29tcGFyaXNvbl9kYXRhLCBhZXMoeCA9IFZhbG9yX1JlYWwsIHkgPSBQcmVkaWNjaW9uKSkgKw0KICBnZW9tX3BvaW50KCkgKw0KICBnZW9tX2FibGluZShpbnRlcmNlcHQgPSAwLCBzbG9wZSA9IDEsIGNvbG9yID0gInJlZCIpICsNCiAgbGFicyh0aXRsZSA9ICJDb21wYXJhY2nDs24gZGUgUHJlZGljY2lvbmVzIC0gQm9zcXVlIEFsZWF0b3JpbyIsDQogICAgICAgeCA9ICJWYWxvciBSZWFsIiwNCiAgICAgICB5ID0gIlByZWRpY2Npw7NuIikgKw0KICB0aGVtZV9taW5pbWFsKCkNCg0KDQpgYGANCg0KRW4gY29uY2x1c2nDs24sIGVzdGUgYW7DoWxpc2lzIGV4cGxvcmF0b3JpbyBkZSBkYXRvcyBoYSBwcm9wb3JjaW9uYWRvIHVuYSB2aXNpw7NuIGdlbmVyYWwgZGUgbGFzIHJlbGFjaW9uZXMgeSBwYXRyb25lcyBwcmVzZW50ZXMgZW4gZWwgY29uanVudG8gZGUgZGF0b3MgZXN0dWRpYWRvLiBTZSBoYSBldmlkZW5jaWFkbyBsYSBpbmZsdWVuY2lhIGRlIHZhcmlhYmxlcyBjb21vIGVsIGfDqW5lcm8sIG5pdmVsIGRlIGVkdWNhY2nDs24sIGV4cGVyaWVuY2lhIGxhYm9yYWwgeSBwYcOtcyBkZSBvcmlnZW4gZW4gZWwgc2FsYXJpbyBkZSBsb3MgaW5kaXZpZHVvcy4NCg0KU2UgaGFuIGlkZW50aWZpY2FkbyBhbGd1bmFzIHJlbGFjaW9uZXMgc2lnbmlmaWNhdGl2YXMsIGNvbW8gbGEgdGVuZGVuY2lhIGNyZWNpZW50ZSBlbnRyZSBzYWxhcmlvIHkgYcOxb3MgZGUgZXhwZXJpZW5jaWEsIGFzw60gY29tbyBsYXMgZGlmZXJlbmNpYXMgZW4gbG9zIHNhbGFyaW9zIHByb21lZGlvIGVudHJlIGRpc3RpbnRvcyBuaXZlbGVzIGRlIGVkdWNhY2nDs24uIFRhbWJpw6luIHNlIGhhIGRlc3RhY2FkbyBsYSBwcmVzZW5jaWEgZGUgdmFsb3JlcyBhdMOtcGljb3MgcXVlIHB1ZWRlbiB0ZW5lciB1biBpbXBhY3RvIGVuIGxhIGludGVycHJldGFjacOzbiBkZSBsb3MgcmVzdWx0YWRvcy4NCg0KRXMgaW1wb3J0YW50ZSBtZW5jaW9uYXIgcXVlIHNlIGhhbiBpZGVudGlmaWNhZG8gYWxndW5hcyBpbmNvbnNpc3RlbmNpYXMgZW4gbG9zIGRhdG9zLCBjb21vIGxhIHNpbWlsaXR1ZCBlbiBsb3Mgc2FsYXJpb3MgcHJvbWVkaW8gZW50cmUgZGlmZXJlbnRlcyBwYcOtc2VzLCBsbyBjdWFsIHN1Z2llcmUgbGEgcG9zaWJpbGlkYWQgZGUgZGF0b3MgZXJyw7NuZW9zIG8gc2VzZ29zIGVuIGxhIG11ZXN0cmEuDQoNCkVsIG1vZGVsbyBkZSBCb3NxdWVzIEFsZWF0b3Jpb3MgaGEgZGVtb3N0cmFkbyBzZXIgZWwgbcOhcyBlZmljYXogZW4gdMOpcm1pbm9zIGRlIHJlbmRpbWllbnRvIHkgcHJlY2lzacOzbiBwcmVkaWN0aXZhLg0KDQpFbiByZXN1bWVuLCBlc3RlIGFuw6FsaXNpcyBleHBsb3JhdG9yaW8gZGUgZGF0b3MgcHJvcG9yY2lvbmEgdW5hIGJhc2Ugc8OzbGlkYSBwYXJhIGZ1dHVyb3MgZXN0dWRpb3MgeSBhbsOhbGlzaXMgbcOhcyBwcm9mdW5kb3MuIFNlIGRlYmVuIGNvbnNpZGVyYXIgbGFzIGNvbmpldHVyYXMgeSBvYnNlcnZhY2lvbmVzIHJlYWxpemFkYXMgY29tbyBoaXDDs3Rlc2lzIGluaWNpYWxlcywgeSBzZSBpbnZpdGEgYSByZWFsaXphciBhbsOhbGlzaXMgbcOhcyBkZXRhbGxhZG9zIHkgcmlndXJvc29zIHBhcmEgb2J0ZW5lciBjb25jbHVzaW9uZXMgbcOhcyBzw7NsaWRhcy4gRWwgcHJvY2VzbyBkZSBleHBsb3JhY2nDs24gZGUgZGF0b3MgZXMgY29udGludW8geSBkaW7DoW1pY28sIGxvIHF1ZSBub3MgcGVybWl0ZSBzZWd1aXIgZGVzY3VicmllbmRvIHkgY29tcHJlbmRpZW5kbyBudWV2YXMgcmVsYWNpb25lcyB5IHBhdHJvbmVzIGVuIGxvcyBkYXRvcyBlbiBmdXR1cmFzIGludmVzdGlnYWNpb25lcy4NCg0KDQoqKsKhR3JhY2lhcyBwb3IgbGxlZ2FyIGhhc3RhIGFjw6Egbm8gZHVkZXMgZW4gZGVqYXIgdHUgY29tZW50YXJpbyBwYXJhIGF5dWRhcm1lIGEgc2VndWlyIHByZW5kaWVuZG8hKiog