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:
Regresión Lineal
Árbol de Decisión
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