TP : classification de spam par boosting

Introduction

# Chargement des packages nécessaires
library(kernlab)   # Pour les données spam
library(adabag)    # Pour AdaBoost
Le chargement a nécessité le package : rpart
Le chargement a nécessité le package : caret
Le chargement a nécessité le package : ggplot2

Attachement du package : ‘ggplot2’

L'objet suivant est masqué depuis ‘package:kernlab’:

    alpha

Le chargement a nécessité le package : lattice
Registered S3 method overwritten by 'data.table':
  method           from
  print.data.table     
Le chargement a nécessité le package : foreach
Le chargement a nécessité le package : doParallel
Le chargement a nécessité le package : iterators
Le chargement a nécessité le package : parallel
Registered S3 method overwritten by 'htmlwidgets':
  method           from         
  print.htmlwidget tools:rstudio
library(gbm)       # Pour Gradient Boosting
Loaded gbm 2.1.9
This version of gbm is no longer under development. Consider transitioning to gbm3, https://github.com/gbm-developers/gbm3
library(xgboost)   # Pour XGBoost
library(caret)     # Pour la gestion des données
library(dplyr)     # Pour la manipulation des données

Attachement du package : ‘dplyr’

L'objet suivant est masqué depuis ‘package:xgboost’:

    slice

Les objets suivants sont masqués depuis ‘package:stats’:

    filter, lag

Les objets suivants sont masqués depuis ‘package:base’:

    intersect, setdiff, setequal, union
library(ggplot2)   # Pour les graphiques

# Pour s'assurer que tout est bien chargé
# sessionInfo()
# Chargement des données spam
# On utilise une fonction spéciale pour charger un ensemble de données appelé "spam" 
# qui se trouve dans un package (ou bibliothèque) nommé "kernlab".
data(spam, package = "kernlab")

# Conversion de la variable cible en facteur
# Dans nos données, il y a une colonne appelée "type" qui nous dit si un email est du spam ou non.
# On convertit cette colonne en "facteur", ce qui signifie que cette colonne 
# contient des catégories (spam ou non-spam) plutôt que des nombres.
spam$type <- as.factor(spam$type)

# Affichage d'un aperçu des données
# On utilise la fonction "head" pour afficher les premières lignes de nos données. 
# Cela nous permet de voir à quoi ressemblent nos données.
head(spam)

# Séparation des données en bases d'apprentissage et de test (80%/20%) en conservant le ratio de spam
# On veut séparer nos données en deux parties : une pour entraîner nos modèles (80%) 
# et une pour tester nos modèles (20%).
# "set.seed(123)" est une commande qui garantit que si quelqu'un d'autre exécute ce code, 
# il obtiendra les mêmes résultats que nous. C'est comme si on utilisait un mot de passe pour la reproductibilité.
set.seed(123)  # Pour la reproductibilité
# On utilise une fonction pour créer des indices de séparation de nos données. 
# La fonction "createDataPartition" prend 80% des données pour l'entraînement et garde le reste pour le test.
trainIndex <- createDataPartition(spam$type, p = 0.8, list = FALSE)
# On crée la base d'apprentissage (80% des données) en utilisant les indices créés précédemment.
trainData <- spam[trainIndex, ]
# On crée la base de test (20% des données) en prenant le reste des données.
testData <- spam[-trainIndex, ]

# Vérification du ratio de spam dans les deux bases
# On utilise la fonction "prop.table" pour vérifier que le pourcentage de spam et de non-spam 
# est à peu près le même dans nos bases d'apprentissage et de test. 
# Cela nous assure que nos données sont bien équilibrées.
prop.table(table(trainData$type))

  nonspam      spam 
0.6059207 0.3940793 
prop.table(table(testData$type))

  nonspam      spam 
0.6060936 0.3939064 

Utilisation de AdaBoost

# Entraînement du modèle AdaBoost avec 10 itérations

# On utilise Sys.time() pour démarrer le chronomètre et mesurer combien de temps
# cela va prendre pour entraîner notre modèle.
start_time <- Sys.time()

set.seed(123)  # Pour la reproductibilité

# On entraîne le modèle AdaBoost.
# boosting() est une fonction qui crée un modèle d'apprentissage supervisé appelé "AdaBoost".
# type ~ . signifie qu'on utilise toutes les colonnes de notre tableau de données (trainData)
# pour prédire la colonne "type".
# data = trainData signifie qu'on utilise les données d'apprentissage.
# mfinal = 10 signifie qu'on va faire 10 itérations, ou 10 cycles d'apprentissage.
adaboost_model <- boosting(type ~ ., data = trainData, mfinal = 10)

# On arrête le chronomètre pour voir combien de temps cela a pris pour entraîner le modèle.
end_time <- Sys.time()
# On calcule la différence entre le temps de fin et le temps de début pour obtenir
# le temps total d'exécution.
adaboost_time_default <- end_time - start_time

# Calcul du taux d'erreur sur la base d'apprentissage

# On utilise predict() pour faire des prédictions sur les données d'apprentissage.
# newdata = trainData signifie qu'on fait des prédictions sur les données d'apprentissage.
train_pred <- predict(adaboost_model, newdata = trainData)
# On calcule le taux d'erreur en comparant les prédictions avec les vraies valeurs.
# train_pred$class contient les prédictions, et trainData$type contient les vraies valeurs.
# On utilise mean() pour calculer la moyenne des différences.
train_error <- mean(train_pred$class != trainData$type)

# Calcul du taux d'erreur sur la base de test

# On utilise predict() pour faire des prédictions sur les données de test.
# newdata = testData signifie qu'on fait des prédictions sur les données de test.
test_pred <- predict(adaboost_model, newdata = testData)
# On calcule le taux d'erreur en comparant les prédictions avec les vraies valeurs.
# test_pred$class contient les prédictions, et testData$type contient les vraies valeurs.
# On utilise mean() pour calculer la moyenne des différences.
test_error <- mean(test_pred$class != testData$type)

# Affichage des résultats

# On affiche le taux d'erreur sur la base d'apprentissage.
train_error
[1] 0.02688756
# Le premier nombre (0.02688756) est le taux d'erreur sur la base d'apprentissage.
# Cela signifie que le modèle a fait une erreur environ 2.69% du temps sur les données d'apprentissage.

# On affiche le taux d'erreur sur la base de test.
test_error
[1] 0.05114255
# Le deuxième nombre (0.05114255) est le taux d'erreur sur la base de test.
# Cela signifie que le modèle a fait une erreur environ 5.11% du temps sur les données de test.

# On affiche le temps total d'exécution pour l'entraînement du modèle.
adaboost_time_default
Time difference of 5.041678 secs
# Le troisième résultat (Time difference of 5.374694 secs) indique que le temps total 
# pour entraîner le modèle AdaBoost avec 10 itérations a été d'environ 5.37 secondes.
# Entraînement du modèle AdaBoost avec jusqu'à 300 itérations

start_time <- Sys.time()  # Démarrage du chronomètre
set.seed(123)  # Pour la reproductibilité
# Création du modèle AdaBoost avec 300 itérations
# mfinal = 300 signifie qu'on fait 300 cycles d'apprentissage
adaboost_model_300 <- boosting(type ~ ., data = trainData, mfinal = 300)

end_time <- Sys.time()  # Arrêt du chronomètre
# Calcul du temps total d'exécution pour entraîner le modèle
adaboost_time_300 <- end_time - start_time

# Suivi des erreurs sur la base d'apprentissage et de test

# errorevol() est une fonction qui suit l'évolution des erreurs au fil des itérations
# Elle retourne les taux d'erreur pour chaque itération
error_evol_train <- errorevol(adaboost_model_300, trainData)
error_evol_test <- errorevol(adaboost_model_300, testData)

# Affichage des résultats

# On prend l'erreur à la 300ème itération pour les données d'apprentissage
train_error_300 <- error_evol_train$error[300]
# On prend l'erreur à la 300ème itération pour les données de test
test_error_300 <- error_evol_test$error[300]

# Affichage du taux d'erreur sur les données d'apprentissage après 300 itérations
train_error_300
# Affichage du taux d'erreur sur les données de test après 300 itérations
test_error_300
# Affichage du temps total d'exécution pour entraîner le modèle avec 300 itérations
adaboost_time_300

# Le premier ensemble de résultats correspond à l'erreur d'apprentissage (0.0005431831) 
# et l'erreur de test (0.03699674) pour le modèle AdaBoost avec 300 itérations sans limitation de profondeur.
# Le temps d'exécution total est de 3.150702 minutes.

# Traçage des courbes d'erreurs

# plot() crée un graphique montrant l'évolution des erreurs
# type = "l" signifie qu'on trace une ligne
# col = "blue" signifie que la ligne est bleue pour les erreurs d'apprentissage
# ylim = c(0, 0.1) définit les limites de l'axe Y de 0 à 0.1 pour une meilleure visualisation
# ylab = "Taux d'erreur" donne un titre à l'axe Y
# xlab = "Nombre d'itérations" donne un titre à l'axe X
# main = "Évolution des erreurs avec AdaBoost (300 itérations)" donne un titre au graphique
plot(error_evol_train$error, type = "l", col = "blue", ylim = c(0, 0.1), 
     ylab = "Taux d'erreur", xlab = "Nombre d'itérations", 
     main = "Évolution des erreurs avec AdaBoost (300 itérations)")
# lines() ajoute une ligne rouge pour les erreurs de test
lines(error_evol_test$error, col = "red")
# legend() ajoute une légende au graphique pour expliquer les couleurs
legend("topright", legend = c("Erreur d'apprentissage", "Erreur de test"), 
       col = c("blue", "red"), lty = 1)

# Entraînement du modèle AdaBoost avec jusqu'à 300 itérations et limitation de la profondeur des arbres à 3

start_time <- Sys.time()
set.seed(123)
# Création du modèle AdaBoost avec 300 itérations et profondeur limitée
# control = rpart.control(maxdepth = 3) signifie qu'on limite la profondeur des arbres à 3
adaboost_model_depth3 <- boosting(type ~ ., data = trainData, mfinal = 300, 
                                  control = rpart.control(maxdepth = 3))

end_time <- Sys.time()
adaboost_time_depth3 <- end_time - start_time

# Suivi des erreurs sur la base d'apprentissage et de test avec limitation de profondeur

# Suivi des erreurs pour le modèle avec profondeur limitée
error_evol_train_depth3 <- errorevol(adaboost_model_depth3, trainData)
error_evol_test_depth3 <- errorevol(adaboost_model_depth3, testData)

# Affichage des résultats

# Erreur d'apprentissage après 300 itérations avec profondeur limitée
train_error_depth3 <- error_evol_train_depth3$error[300]
# Erreur de test après 300 itérations avec profondeur limitée
test_error_depth3 <- error_evol_test_depth3$error[300]

# Affichage des erreurs
train_error_depth3
test_error_depth3
adaboost_time_depth3

# Le deuxième ensemble de résultats correspond à l'erreur d'apprentissage (0.04019555) 
# et l'erreur de test (0.05223069) pour le modèle AdaBoost avec 300 itérations et une profondeur limitée à 3.
# Le temps d'exécution total est de 49.57228 secondes.

# Traçage des courbes d'erreurs avec limitation de profondeur

# Création du graphique pour le modèle avec profondeur limitée
plot(error_evol_train_depth3$error, type = "l", col = "blue", ylim = c(0, 0.1), 
     ylab = "Taux d'erreur", xlab = "Nombre d'itérations", 
     main = "Évolution des erreurs avec AdaBoost (300 itérations, profondeur limitée)")
# Ajout de la ligne rouge pour les erreurs de test
lines(error_evol_test_depth3$error, col = "red")
# Ajout de la légende pour expliquer les couleurs
legend("topright", legend = c("Erreur d'apprentissage (profondeur limitée)", 
       "Erreur de test (profondeur limitée)"), col = c("blue", "red"), lty = 1)

# Les graphiques montrent l'évolution des erreurs d'apprentissage et de test 
# au fil des 300 itérations. Les deux courbes permettent de visualiser 
# comment les erreurs diminuent au fur et à mesure que le modèle apprend.
#Le premier graphique illustre l'évolution des erreurs d'apprentissage et de test pour le modèle AdaBoost sur 300 itérations. On observe que l'erreur d'apprentissage (courbe bleue) diminue rapidement et atteint quasiment zéro après environ 50 itérations. Cela indique qu'AdaBoost est très efficace pour réduire l'erreur sur les données d'apprentissage. Cependant, l'erreur de test (courbe rouge) suit un schéma différent. Bien qu'elle diminue initialement, elle stagne et commence même à légèrement augmenter après environ 50 itérations. Ce phénomène suggère un surapprentissage, où le modèle devient trop complexe et commence à mémoriser le bruit dans les données d'apprentissage au lieu d'apprendre des motifs généralisables. Malgré cela, l'erreur de test reste relativement stable après cette phase initiale, montrant une certaine robustesse du modèle.

#Le second graphique montre l'évolution des erreurs pour le même modèle AdaBoost, mais cette fois avec une limitation de profondeur des arbres à trois niveaux. L'erreur d'apprentissage commence plus haut et ne descend pas aussi bas que dans le premier graphique, atteignant un plateau autour de 0.04. Cela indique que les arbres moins profonds sont moins capables de s'adapter parfaitement aux données d'apprentissage. L'erreur de test, bien qu'elle fluctue plus que dans le premier graphique, reste plus stable sans montrer une augmentation notable. Cette stabilité indique que limiter la profondeur des arbres aide à éviter le surapprentissage, bien que cela réduise également la capacité du modèle à capturer des motifs complexes, ce qui se reflète dans l'erreur d'apprentissage plus élevée.

Gradient Boosting

# Conversion de la variable cible en valeurs 0 et 1 pour le modèle gbm
# Le modèle gbm nécessite que la variable cible soit des valeurs numériques 0 et 1.
# On convertit donc "spam" en 1 et "nonspam" en 0 pour les données d'apprentissage.
trainData$type <- ifelse(trainData$type == "spam", 1, 0)
# On fait de même pour les données de test.
testData$type <- ifelse(testData$type == "spam", 1, 0)

# Entraînement du modèle Gradient Boosting avec 1000 itérations
start_time <- Sys.time()  # Démarrage du chronomètre
set.seed(123)  # Pour la reproductibilité
# On crée un modèle de Gradient Boosting en utilisant gbm().
# type ~ . signifie qu'on utilise toutes les colonnes pour prédire la colonne "type".
# data = trainData signifie qu'on utilise les données d'apprentissage.
# distribution = "bernoulli" signifie qu'on utilise une distribution de Bernoulli pour les données binaires.
# n.trees = 1000 signifie qu'on fait 1000 itérations.
# interaction.depth = 3 signifie qu'on permet aux arbres d'avoir une profondeur de 3.
# shrinkage = 0.01 est un paramètre qui contrôle la vitesse d'apprentissage.
# cv.folds = 5 signifie qu'on utilise une validation croisée avec 5 plis.
gbm_model <- gbm(type ~ ., data = trainData, distribution = "bernoulli", 
                 n.trees = 1000, interaction.depth = 3, shrinkage = 0.01, 
                 cv.folds = 5)
end_time <- Sys.time()  # Arrêt du chronomètre
# On calcule le temps total d'exécution pour entraîner le modèle.
gbm_time_default <- end_time - start_time

# Calcul des taux d'erreur d'apprentissage et de généralisation
# On utilise predict() pour faire des prédictions sur les données d'apprentissage avec 1000 arbres.
gbm_train_pred <- predict(gbm_model, newdata = trainData, n.trees = 1000, 
                          type = "response")
# On fait de même pour les données de test.
gbm_test_pred <- predict(gbm_model, newdata = testData, n.trees = 1000, 
                         type = "response")

# Conversion des prédictions en classes binaires
# On convertit les prédictions en valeurs binaires (0 ou 1).
# Si la prédiction est supérieure à 0.5, on la considère comme 1 (spam), sinon comme 0 (nonspam).
gbm_train_class <- ifelse(gbm_train_pred > 0.5, 1, 0)
gbm_test_class <- ifelse(gbm_test_pred > 0.5, 1, 0)

# Calcul des erreurs
# On compare les prédictions avec les vraies valeurs pour calculer le taux d'erreur.
gbm_train_error <- mean(gbm_train_class != trainData$type)
gbm_test_error <- mean(gbm_test_class != testData$type)

# Affichage des résultats
# On affiche le taux d'erreur sur les données d'apprentissage.
gbm_train_error
[1] 0.04644215
# Le premier nombre (0.04644215) est le taux d'erreur sur les données d'apprentissage.
# Cela signifie que le modèle a fait une erreur environ 4.64% du temps sur les données d'apprentissage.


# On affiche le taux d'erreur sur les données de test.
gbm_test_error
[1] 0.05331882
# Le deuxième nombre (0.05331882) est le taux d'erreur sur les données de test.
# Cela signifie que le modèle a fait une erreur environ 5.33% du temps sur les données de test.

# On affiche le temps total d'exécution pour entraîner le modèle.
gbm_time_default
Time difference of 30.17881 secs
# Le troisième résultat (Time difference of 25.2539 secs) indique que le temps total 
# pour entraîner le modèle Gradient Boosting avec 1000 itérations a été d'environ 25.25 secondes.


# Traçage des courbes de déviance
# gbm.perf() permet de trouver le meilleur nombre d'arbres en utilisant la validation croisée.
gbm_performance <- gbm.perf(gbm_model, method = "cv")


# Le graphique présenté montre l'évolution de la déviance de Bernoulli pour le modèle Gradient Boosting au cours de 1000 itérations. La déviance de Bernoulli est une mesure de la qualité de l'ajustement d'un modèle, utilisée notamment pour les problèmes de classification binaire. Une valeur plus faible de cette déviance indique un meilleur ajustement du modèle aux données.

# On observe que la déviance de Bernoulli diminue de manière continue tout au long des itérations, ce qui signifie que le modèle s'améliore progressivement en ajustant de mieux en mieux les données. Les courbes pour les données d'apprentissage et de validation (test) suivent une trajectoire descendante similaire, montrant une amélioration constante de la performance du modèle.

#Cependant, vers la fin des itérations, les courbes commencent à converger et deviennent presque horizontales, indiquant que l'ajustement supplémentaire offert par les nouvelles itérations devient minime. À ce point, ajouter davantage d'itérations n'améliore que très peu la performance du modèle et pourrait même entraîner un surapprentissage si le modèle commence à mémoriser le bruit dans les données d'apprentissage.

#La ligne verticale bleue en pointillés à la 1000e itération marque la fin des itérations définies pour l'entraînement du modèle. Le modèle a été configuré pour s'arrêter à cette limite, probablement en raison des contraintes de calcul ou pour éviter un surapprentissage excessif.
# Validation croisée simplifiée pour Gradient Boosting en variant les autres paramètres

# On utilise set.seed(123) pour que nos résultats soient reproductibles
set.seed(123)

# On définit les paramètres de la validation croisée
# trainControl(method = "cv", number = 3) signifie qu'on utilise une validation croisée avec 3 plis
# Cela veut dire qu'on divise les données en 3 parties et qu'on entraîne le modèle 3 fois, chaque fois en utilisant une partie différente pour tester
ctrl <- trainControl(method = "cv", number = 3)

# On définit une grille de paramètres à tester
# expand.grid() crée toutes les combinaisons possibles des paramètres qu'on veut tester
# n.trees = 1000 signifie qu'on va toujours utiliser 1000 arbres
# interaction.depth = c(1, 3, 5) signifie qu'on va tester des profondeurs de 1, 3 et 5 pour les arbres
# shrinkage = c(0.01, 0.1, 0.3) signifie qu'on va tester des vitesses d'apprentissage de 0.01, 0.1 et 0.3
# n.minobsinnode = c(10, 20) signifie qu'on va tester des minimums de 10 et 20 observations par nœud
grid <- expand.grid(n.trees = 1000, interaction.depth = c(1, 3, 5), 
                    shrinkage = c(0.01, 0.1, 0.3), n.minobsinnode = c(10, 20))

# On démarre le chronomètre
start_time <- Sys.time()

# On entraîne le modèle en utilisant la validation croisée et la grille de paramètres
# train() crée et entraîne le modèle
# type ~ . signifie qu'on utilise toutes les colonnes pour prédire la colonne "type"
# data = trainData signifie qu'on utilise les données d'apprentissage
# method = "gbm" signifie qu'on utilise le modèle de Gradient Boosting
# trControl = ctrl signifie qu'on utilise les paramètres de validation croisée définis plus tôt
# tuneGrid = grid signifie qu'on utilise la grille de paramètres définie plus tôt
# verbose = FALSE signifie qu'on ne veut pas afficher des messages détaillés pendant l'entraînement
gbm_cv <- train(type ~ ., data = trainData, method = "gbm", trControl = ctrl, 
                tuneGrid = grid, verbose = FALSE)
Avis dans train.default(x, y, weights = w, ...) :
  You are trying to do regression and your outcome only has two possible values Are you trying to do classification? If so, use a 2 level factor as your outcome column.
# On arrête le chronomètre
end_time <- Sys.time()

# On calcule le temps total d'exécution pour la validation croisée
gbm_time_cv <- end_time - start_time

# Meilleurs paramètres
# bestTune contient les meilleurs paramètres trouvés par la validation croisée
best_gbm <- gbm_cv$bestTune
best_gbm

# Entraînement du modèle avec les meilleurs paramètres

# On démarre le chronomètre
start_time <- Sys.time()

# On crée un modèle de Gradient Boosting avec les meilleurs paramètres trouvés par la validation croisée
# distribution = "bernoulli" signifie qu'on utilise une distribution de Bernoulli pour les données binaires
# cv.folds = 3 signifie qu'on utilise une validation croisée avec 3 plis
gbm_model_best <- gbm(type ~ ., data = trainData, distribution = "bernoulli", 
                      n.trees = best_gbm$n.trees, interaction.depth = best_gbm$interaction.depth, 
                      shrinkage = best_gbm$shrinkage, n.minobsinnode = best_gbm$n.minobsinnode, 
                      cv.folds = 3)

# On arrête le chronomètre
end_time <- Sys.time()

# On calcule le temps total d'exécution pour entraîner le modèle avec les meilleurs paramètres
gbm_time_best <- end_time - start_time

# Calcul des taux d'erreur d'apprentissage et de généralisation

# On utilise predict() pour faire des prédictions sur les données d'apprentissage avec les meilleurs paramètres
gbm_train_pred_best <- predict(gbm_model_best, newdata = trainData, 
                               n.trees = best_gbm$n.trees, type = "response")

# On fait de même pour les données de test
gbm_test_pred_best <- predict(gbm_model_best, newdata = testData, 
                              n.trees = best_gbm$n.trees, type = "response")

# Conversion des prédictions en classes binaires
# On convertit les prédictions en valeurs binaires (0 ou 1)
# Si la prédiction est supérieure à 0.5, on la considère comme 1 (spam), sinon comme 0 (nonspam)
gbm_train_class_best <- ifelse(gbm_train_pred_best > 0.5, 1, 0)
gbm_test_class_best <- ifelse(gbm_test_pred_best > 0.5, 1, 0)

# Calcul des erreurs
# On compare les prédictions avec les vraies valeurs pour calculer le taux d'erreur
gbm_train_error_best <- mean(gbm_train_class_best != trainData$type)
gbm_test_error_best <- mean(gbm_test_class_best != testData$type)

# Affichage des résultats
# On affiche le taux d'erreur sur les données d'apprentissage
gbm_train_error_best
[1] 0.0008147746
# Le premier nombre (0.04644215) est le taux d'erreur sur les données d'apprentissage avec les meilleurs paramètres.
# Cela signifie que le modèle a fait une erreur environ 4.64% du temps sur les données d'apprentissage.

# On affiche le taux d'erreur sur les données de test
gbm_test_error_best
[1] 0.04026115
# Le deuxième nombre (0.05331882) est le taux d'erreur sur les données de test avec les meilleurs paramètres.
# Cela signifie que le modèle a fait une erreur environ 5.33% du temps sur les données de test.

# On affiche le temps total d'exécution pour entraîner le modèle avec les meilleurs paramètres
gbm_time_best
Time difference of 29.70658 secs
# Le troisième résultat (Time difference of 25.2539 secs) indique que le temps total 
# pour entraîner le modèle Gradient Boosting avec les meilleurs paramètres a été d'environ 25.25 secondes.

XGBoost

# Préparation des données pour XGBoost

# On prépare les données pour le modèle XGBoost
# Le modèle XGBoost nécessite que la variable cible soit des valeurs numériques 0 et 1
# On convertit donc les valeurs 1 en 1 (spam) et les valeurs 0 en 0 (nonspam) pour les données d'apprentissage
trainData$type <- ifelse(trainData$type == 1, 1, 0)
# On fait de même pour les données de test
testData$type <- ifelse(testData$type == 1, 1, 0)

# On crée des matrices de données spéciales pour XGBoost
# xgb.DMatrix() est une fonction qui crée une structure de données optimisée pour XGBoost
# data = as.matrix(trainData[,-ncol(trainData)]) signifie qu'on utilise toutes les colonnes sauf la dernière
# label = trainData$type signifie qu'on utilise la colonne "type" comme étiquette (spam ou nonspam)
train_matrix <- xgb.DMatrix(data = as.matrix(trainData[,-ncol(trainData)]), label = trainData$type)
test_matrix <- xgb.DMatrix(data = as.matrix(testData[,-ncol(testData)]), label = testData$type)

# Entraînement du modèle XGBoost avec 100 itérations
start_time <- Sys.time()  # Démarrage du chronomètre
set.seed(123)  # Pour la reproductibilité
# On crée et entraîne le modèle XGBoost en utilisant xgb.train()
# data = train_matrix signifie qu'on utilise les données d'apprentissage
# nrounds = 100 signifie qu'on fait 100 itérations
# objective = "binary:logistic" signifie qu'on utilise une régression logistique binaire
# max_depth = 3 signifie qu'on permet aux arbres d'avoir une profondeur de 3
# eta = 0.1 est un paramètre qui contrôle la vitesse d'apprentissage
# watchlist = list(train = train_matrix, test = test_matrix) signifie qu'on suit les erreurs sur les données d'apprentissage et de test
# eval_metric = "logloss" signifie qu'on utilise la perte logarithmique pour évaluer les erreurs
# verbose = 0 signifie qu'on ne veut pas afficher des messages détaillés pendant l'entraînement
xgboost_model <- xgb.train(
  data = train_matrix,
  nrounds = 100,
  objective = "binary:logistic",
  max_depth = 3,
  eta = 0.1,
  watchlist = list(train = train_matrix, test = test_matrix),
  eval_metric = "logloss",
  verbose = 0
)
end_time <- Sys.time()  # Arrêt du chronomètre
# On calcule le temps total d'exécution pour entraîner le modèle
xgboost_time_default <- end_time - start_time

# Récupération des erreurs d'apprentissage et de test
# evaluation_log contient les erreurs pour chaque itération
evaluation_log <- xgboost_model$evaluation_log

# Calcul des erreurs finales
# On utilise predict() pour faire des prédictions sur les données d'apprentissage
xgboost_train_pred <- predict(xgboost_model, newdata = train_matrix)
# On fait de même pour les données de test
xgboost_test_pred <- predict(xgboost_model, newdata = test_matrix)

# Conversion des prédictions en classes binaires
# On convertit les prédictions en valeurs binaires (0 ou 1)
# Si la prédiction est supérieure à 0.5, on la considère comme 1 (spam), sinon comme 0 (nonspam)
xgboost_train_class <- ifelse(xgboost_train_pred > 0.5, 1, 0)
xgboost_test_class <- ifelse(xgboost_test_pred > 0.5, 1, 0)

# Calcul des erreurs
# On compare les prédictions avec les vraies valeurs pour calculer le taux d'erreur
xgboost_train_error <- mean(xgboost_train_class != trainData$type)
xgboost_test_error <- mean(xgboost_test_class != testData$type)

# Affichage des erreurs finales
# On affiche le taux d'erreur sur les données d'apprentissage
xgboost_train_error
[1] 0.0418251
# Le premier nombre (0.0418251) est le taux d'erreur sur les données d'apprentissage.
# Cela signifie que le modèle a fait une erreur environ 4.18% du temps sur les données d'apprentissage.

# On affiche le taux d'erreur sur les données de test
xgboost_test_error
[1] 0.05767138
# Le deuxième nombre (0.05767138) est le taux d'erreur sur les données de test.
# Cela signifie que le modèle a fait une erreur environ 5.77% du temps sur les données de test.

# On affiche le temps total d'exécution pour entraîner le modèle
xgboost_time_default
Time difference of 0.556731 secs
# Le troisième résultat (Time difference of 0.4740241 secs) indique que le temps total 
# pour entraîner le modèle XGBoost avec 100 itérations a été d'environ 0.47 secondes.

# Traçage des courbes d'erreurs
# plot() crée un graphique montrant l'évolution des erreurs
# type = "l" signifie qu'on trace une ligne
# col = "blue" signifie que la ligne est bleue pour les erreurs d'apprentissage
# ylim = c(0, max(evaluation_log$train_logloss, evaluation_log$test_logloss)) définit les limites de l'axe Y pour une meilleure visualisation
# ylab = "Log Loss" donne un titre à l'axe Y
# xlab = "Iteration" donne un titre à l'axe X
# main = "Évolution des erreurs avec XGBoost" donne un titre au graphique
plot(evaluation_log$iter, evaluation_log$train_logloss, type = "l", col = "blue", 
     ylim = c(0, max(evaluation_log$train_logloss, evaluation_log$test_logloss)), 
     ylab = "Log Loss", xlab = "Iteration", 
     main = "Évolution des erreurs avec XGBoost")
# lines() ajoute une ligne rouge pour les erreurs de test
lines(evaluation_log$iter, evaluation_log$test_logloss, col = "red")
# legend() ajoute une légende au graphique pour expliquer les couleurs
legend("topright", legend = c("Erreur d'apprentissage", "Erreur de test"), 
       col = c("blue", "red"), lty = 1)


# Visualisation de l'importance des variables
# xgb.importance() calcule l'importance de chaque variable dans le modèle
importance_matrix <- xgb.importance(model = xgboost_model)
# xgb.plot.importance() crée un graphique montrant l'importance des variables
xgb.plot.importance(importance_matrix)



# Le premier graphique montre l'évolution des erreurs d'apprentissage et de test au fil des 100 itérations. La courbe bleue représente l'erreur d'apprentissage, qui diminue rapidement et continue de baisser au fil des itérations, ce qui indique que le modèle s'adapte de mieux en mieux aux données d'entraînement. La courbe rouge, quant à elle, montre l'erreur de test, qui diminue également mais tend à stagner vers la fin des itérations. Cette stagnation suggère que le modèle atteint un point où des itérations supplémentaires n'apportent que peu ou pas d'améliorations supplémentaires sur les données de test, ce qui est un signe positif de bonne généralisation sans surapprentissage excessif.

# Le deuxième graphique montre l'importance des variables dans le modèle. Les variables les plus importantes sont celles en haut du graphique, avec "charExclamation" et "remove" étant les plus influentes. Ces variables jouent un rôle crucial dans les décisions prises par le modèle.
# On utilise set.seed(123) pour que nos résultats soient reproductibles
set.seed(123)

# On définit les paramètres de la validation croisée
# trainControl(method = "cv", number = 3) signifie qu'on utilise une validation croisée avec 3 plis
# Cela veut dire qu'on divise les données en 3 parties et qu'on entraîne le modèle 3 fois, chaque fois en utilisant une partie différente pour tester
ctrl <- trainControl(method = "cv", number = 3)

# On définit une grille de paramètres à tester
# expand.grid() crée toutes les combinaisons possibles des paramètres qu'on veut tester
# nrounds = c(100) signifie qu'on va toujours utiliser 100 itérations
# max_depth = c(3, 5, 7) signifie qu'on va tester des profondeurs de 3, 5 et 7 pour les arbres
# eta = c(0.01, 0.1, 0.3) signifie qu'on va tester des vitesses d'apprentissage de 0.01, 0.1 et 0.3
# gamma = c(0, 1) signifie qu'on va tester des valeurs de 0 et 1 pour le paramètre gamma
# colsample_bytree = c(0.5, 1) signifie qu'on va tester des échantillons de 50% et 100% pour les arbres
# min_child_weight = c(1, 5) signifie qu'on va tester des valeurs de 1 et 5 pour le paramètre min_child_weight
# subsample = c(0.5, 1) signifie qu'on va tester des échantillons de 50% et 100% pour les arbres
grid <- expand.grid(
  nrounds = c(100), 
  max_depth = c(3, 5, 7), 
  eta = c(0.01, 0.1, 0.3),
  gamma = c(0, 1),
  colsample_bytree = c(0.5, 1),
  min_child_weight = c(1, 5),
  subsample = c(0.5, 1)
)

# On démarre le chronomètre
start_time <- Sys.time()

# On entraîne le modèle en utilisant la validation croisée et la grille de paramètres
# train() crée et entraîne le modèle
# type ~ . signifie qu'on utilise toutes les colonnes pour prédire la colonne "type"
# data = trainData signifie qu'on utilise les données d'apprentissage
# method = "xgbTree" signifie qu'on utilise le modèle XGBoost
# trControl = ctrl signifie qu'on utilise les paramètres de validation croisée définis plus tôt
# tuneGrid = grid signifie qu'on utilise la grille de paramètres définie plus tôt
xgboost_cv <- train(type ~ ., data = trainData, method = "xgbTree", trControl = ctrl, tuneGrid = grid)
Avis dans train.default(x, y, weights = w, ...) :
  You are trying to do regression and your outcome only has two possible values Are you trying to do classification? If so, use a 2 level factor as your outcome column.
# On arrête le chronomètre
end_time <- Sys.time()

# On calcule le temps total d'exécution pour la validation croisée
xgboost_time_cv <- end_time - start_time

# Meilleurs paramètres
# bestTune contient les meilleurs paramètres trouvés par la validation croisée
best_xgboost <- xgboost_cv$bestTune
best_xgboost

# Entraînement du modèle avec les meilleurs paramètres

# On démarre le chronomètre
start_time <- Sys.time()

# On crée et entraîne le modèle XGBoost avec les meilleurs paramètres trouvés par la validation croisée
# data = train_matrix signifie qu'on utilise les données d'apprentissage
# nrounds = best_xgboost$nrounds signifie qu'on utilise le nombre d'itérations trouvé par la validation croisée
# objective = "binary:logistic" signifie qu'on utilise une régression logistique binaire
# max_depth = best_xgboost$max_depth signifie qu'on utilise la profondeur trouvée par la validation croisée
# eta = best_xgboost$eta signifie qu'on utilise la vitesse d'apprentissage trouvée par la validation croisée
# gamma = best_xgboost$gamma signifie qu'on utilise le gamma trouvé par la validation croisée
# colsample_bytree = best_xgboost$colsample_bytree signifie qu'on utilise l'échantillonnage trouvé par la validation croisée
# min_child_weight = best_xgboost$min_child_weight signifie qu'on utilise le poids minimum trouvé par la validation croisée
# subsample = best_xgboost$subsample signifie qu'on utilise l'échantillonnage trouvé par la validation croisée
# watchlist = list(train = train_matrix, test = test_matrix) signifie qu'on suit les erreurs sur les données d'apprentissage et de test
# eval_metric = "logloss" signifie qu'on utilise la perte logarithmique pour évaluer les erreurs
# verbose = 0 signifie qu'on ne veut pas afficher des messages détaillés pendant l'entraînement
xgboost_model_best <- xgb.train(
  data = train_matrix,
  nrounds = best_xgboost$nrounds,
  objective = "binary:logistic",
  max_depth = best_xgboost$max_depth,
  eta = best_xgboost$eta,
  gamma = best_xgboost$gamma,
  colsample_bytree = best_xgboost$colsample_bytree,
  min_child_weight = best_xgboost$min_child_weight,
  subsample = best_xgboost$subsample,
  watchlist = list(train = train_matrix, test = test_matrix),
  eval_metric = "logloss",
  verbose = 0
)

# On arrête le chronomètre
end_time <- Sys.time()

# On calcule le temps total d'exécution pour entraîner le modèle avec les meilleurs paramètres
xgboost_time_best <- end_time - start_time

# Récupération des erreurs d'apprentissage et de test
# evaluation_log_best contient les erreurs pour chaque itération
evaluation_log_best <- xgboost_model_best$evaluation_log

# Calcul des erreurs finales
# On utilise predict() pour faire des prédictions sur les données d'apprentissage
xgboost_train_pred_best <- predict(xgboost_model_best, newdata = train_matrix)
# On fait de même pour les données de test
xgboost_test_pred_best <- predict(xgboost_model_best, newdata = test_matrix)

# Conversion des prédictions en classes binaires
# On convertit les prédictions en valeurs binaires (0 ou 1)
# Si la prédiction est supérieure à 0.5, on la considère comme 1 (spam), sinon comme 0 (nonspam)
xgboost_train_class_best <- ifelse(xgboost_train_pred_best > 0.5, 1, 0)
xgboost_test_class_best <- ifelse(xgboost_test_pred_best > 0.5, 1, 0)

# Calcul des erreurs
# On compare les prédictions avec les vraies valeurs pour calculer le taux d'erreur
xgboost_train_error_best <- mean(xgboost_train_class_best != trainData$type)
xgboost_test_error_best <- mean(xgboost_test_class_best != testData$type)

# Affichage des erreurs finales
# On affiche le taux d'erreur sur les données d'apprentissage
xgboost_train_error_best
[1] 0.01195003
# Le premier nombre (0.0418251) est le taux d'erreur sur les données d'apprentissage.
# Cela signifie que le modèle a fait une erreur environ 4.18% du temps sur les données d'apprentissage.

# On affiche le taux d'erreur sur les données de test
xgboost_test_error_best
[1] 0.04026115
# Le deuxième nombre (0.05767138) est le taux d'erreur sur les données de test.
# Cela signifie que le modèle a fait une erreur environ 5.77% du temps sur les données de test.

# On affiche le temps total d'exécution pour entraîner le modèle avec les meilleurs paramètres
xgboost_time_best
Time difference of 0.8617151 secs
# Le troisième résultat (Time difference of 0.4740241 secs) indique que le temps total 
# pour entraîner le modèle XGBoost avec 100 itérations a été d'environ 0.47 secondes.

# Traçage des courbes d'erreurs
# plot() crée un graphique montrant l'évolution des erreurs
plot(evaluation_log_best$iter, evaluation_log_best$train_logloss, type = "l", col = "blue", 
     ylim = c(0, max(evaluation_log_best$train_logloss, evaluation_log_best$test_logloss)), 
     ylab = "Log Loss", xlab = "Iteration", 
     main = "Évolution des erreurs avec XGBoost (meilleurs paramètres)")
# lines() ajoute une ligne rouge pour les erreurs de test
lines(evaluation_log_best$iter, evaluation_log_best$test_logloss, col = "red")
# legend() ajoute une légende au graphique pour expliquer les couleurs
legend("topright", legend = c("Erreur d'apprentissage", "Erreur de test"), 
       col = c("blue", "red"), lty = 1)


# Visualisation de l'importance des variables
# xgb.importance() calcule l'importance de chaque variable dans le modèle
importance_matrix_best <- xgb.importance(model = xgboost_model_best)
# xgb.plot.importance() crée un graphique montrant l'importance des variables
xgb.plot.importance(importance_matrix_best)



# Le premier graphique montre l'évolution des erreurs d'apprentissage et de test au fil des 100 itérations.
# On remarque que la courbe bleue de l'erreur d'apprentissage descend plus rapidement et atteint une valeur plus basse que dans le modèle par défaut, indiquant une meilleure adaptation aux données d'entraînement. La courbe rouge de l'erreur de test, tout en diminuant également, montre une convergence plus rapide et une stabilisation à une valeur légèrement plus basse que celle du modèle par défaut, indiquant une amélioration générale dans la performance de prédiction.

# Le deuxième graphique montre l'importance des variables dans le modèle. 
# Les variables en haut du graphique sont les plus importantes pour le modèle.
#Les variables "charDollar", "charExclamation", et "capitalLong" sont parmi les plus influentes. On constate que l'importance relative des variables peut varier légèrement par rapport au modèle par défaut, ce qui reflète les ajustements effectués par le processus d'optimisation pour améliorer la performance globale
# Résultats des modèles par défaut

# On crée un tableau de données pour stocker les résultats des modèles par défaut
# data.frame() crée un tableau de données
# Model = c("AdaBoost", "Gradient Boosting", "XGBoost") signifie qu'on a trois modèles : AdaBoost, Gradient Boosting et XGBoost
# Train_Error, Test_Error et Execution_Time sont les colonnes où on va stocker les erreurs d'apprentissage, les erreurs de test et les temps d'exécution pour chaque modèle

results_default <- data.frame(
  Model = c("AdaBoost", "Gradient Boosting", "XGBoost"),
  Train_Error = c(train_error, gbm_train_error, xgboost_train_error),
  Test_Error = c(test_error, gbm_test_error, xgboost_test_error),
  Execution_Time = c(adaboost_time_default, gbm_time_default, xgboost_time_default)
)

# Résultats des modèles avec les meilleurs paramètres

# On crée un autre tableau de données pour stocker les résultats des modèles optimisés avec les meilleurs paramètres trouvés par validation croisée
# Ici, on a quatre modèles optimisés : deux versions d'AdaBoost (300 itérations et 300 itérations avec profondeur limitée), Gradient Boosting optimisé et XGBoost optimisé
results_best <- data.frame(
  Model = c("AdaBoost (300 iterations)", "AdaBoost (300 iterations, depth 3)", "Gradient Boosting (Best)", "XGBoost (Best)"),
  Train_Error = c(train_error_300, train_error_depth3, gbm_train_error_best, xgboost_train_error_best),
  Test_Error = c(test_error_300, test_error_depth3, gbm_test_error_best, xgboost_test_error_best),
  Execution_Time = c(adaboost_time_300, adaboost_time_depth3, gbm_time_best, xgboost_time_best)
)

# Affichage des résultats

# On affiche le tableau des résultats des modèles par défaut
results_default

# On affiche le tableau des résultats des modèles optimisés
results_best
  1. Modèles par défaut :

    AdaBoost a montré un bon compromis entre temps d’exécution et taux d’erreur, avec un temps de calcul modéré (~5.37 secondes) et des erreurs d’apprentissage (2.69%) et de test (5.11%) raisonnablement basses. Gradient Boosting a pris plus de temps (~25.25 secondes) et a affiché des taux d’erreur légèrement supérieurs à ceux d’AdaBoost. XGBoost, bien que très rapide (~0.47 seconde), a montré une légère augmentation de l’erreur de test (5.77%), indiquant peut-être une moindre performance en termes de généralisation par rapport à AdaBoost.

  2. Modèles optimisés :

    En optimisant les paramètres, AdaBoost avec 300 itérations a atteint une erreur de test très basse (3.70%), mais au coût d’un temps de calcul élevé (~171.17 secondes). En limitant la profondeur des arbres, AdaBoost (300 itérations, profondeur 3) a réduit le temps de calcul (~57.49 secondes) tout en maintenant une erreur de test acceptable (5.22%). Gradient Boosting optimisé a significativement amélioré ses performances, avec une erreur de test réduite à 4.03% et un temps de calcul de ~61.49 secondes. XGBoost optimisé a également montré une performance robuste, avec une erreur de test identique à celle de Gradient Boosting (4.03%) mais avec un temps de calcul beaucoup plus faible (~0.94 seconde).

En conclusion, XGBoost optimisé offre le meilleur équilibre entre performance et temps d’exécution, affichant des erreurs de test compétitives avec un temps de calcul nettement inférieur à celui des autres modèles. AdaBoost optimisé peut offrir des erreurs de test légèrement meilleures, mais à un coût de calcul beaucoup plus élevé. Gradient Boosting se situe entre les deux, offrant une bonne performance avec un temps de calcul raisonnable. Les choix finaux dépendent des contraintes de temps de calcul et des exigences de précision spécifiques à l’application.

LS0tDQp0aXRsZTogIlRQIFNUQVQ0IDogY2xhc3NpZmljYXRpb24gZGUgc3BhbSBwYXIgYm9vc3RpbmcgLSBIdWdvIENPTE9OTkEiDQpvdXRwdXQ6DQogIGh0bWxfbm90ZWJvb2s6IGRlZmF1bHQNCiAgaHRtbF9kb2N1bWVudDoNCiAgICBkZl9wcmludDogcGFnZWQNCiAgcGRmX2RvY3VtZW50OiBkZWZhdWx0DQplZGl0b3Jfb3B0aW9uczoNCiAgY2h1bmtfb3V0cHV0X3R5cGU6IGlubGluZQ0KLS0tDQoNCiMgVFAgOiBjbGFzc2lmaWNhdGlvbiBkZSBzcGFtIHBhciBib29zdGluZw0KDQojIyBJbnRyb2R1Y3Rpb24NCg0KYGBge3IgQ2VsbHVsZSAxOiBDaGFyZ2VtZW50IGRlcyBwYWNrYWdlc30NCiMgQ2hhcmdlbWVudCBkZXMgcGFja2FnZXMgbsOpY2Vzc2FpcmVzDQpsaWJyYXJ5KGtlcm5sYWIpICAgIyBQb3VyIGxlcyBkb25uw6llcyBzcGFtDQpsaWJyYXJ5KGFkYWJhZykgICAgIyBQb3VyIEFkYUJvb3N0DQpsaWJyYXJ5KGdibSkgICAgICAgIyBQb3VyIEdyYWRpZW50IEJvb3N0aW5nDQpsaWJyYXJ5KHhnYm9vc3QpICAgIyBQb3VyIFhHQm9vc3QNCmxpYnJhcnkoY2FyZXQpICAgICAjIFBvdXIgbGEgZ2VzdGlvbiBkZXMgZG9ubsOpZXMNCmxpYnJhcnkoZHBseXIpICAgICAjIFBvdXIgbGEgbWFuaXB1bGF0aW9uIGRlcyBkb25uw6llcw0KbGlicmFyeShnZ3Bsb3QyKSAgICMgUG91ciBsZXMgZ3JhcGhpcXVlcw0KDQojIFBvdXIgcydhc3N1cmVyIHF1ZSB0b3V0IGVzdCBiaWVuIGNoYXJnw6kNCiMgc2Vzc2lvbkluZm8oKQ0KYGBgDQoNCmBgYHtyIENlbGx1bGUgMjogUHLDqXBhcmF0aW9uIGRlcyBkb25uw6llc30NCiMgQ2hhcmdlbWVudCBkZXMgZG9ubsOpZXMgc3BhbQ0KIyBPbiB1dGlsaXNlIHVuZSBmb25jdGlvbiBzcMOpY2lhbGUgcG91ciBjaGFyZ2VyIHVuIGVuc2VtYmxlIGRlIGRvbm7DqWVzIGFwcGVsw6kgInNwYW0iIA0KIyBxdWkgc2UgdHJvdXZlIGRhbnMgdW4gcGFja2FnZSAob3UgYmlibGlvdGjDqHF1ZSkgbm9tbcOpICJrZXJubGFiIi4NCmRhdGEoc3BhbSwgcGFja2FnZSA9ICJrZXJubGFiIikNCg0KIyBDb252ZXJzaW9uIGRlIGxhIHZhcmlhYmxlIGNpYmxlIGVuIGZhY3RldXINCiMgRGFucyBub3MgZG9ubsOpZXMsIGlsIHkgYSB1bmUgY29sb25uZSBhcHBlbMOpZSAidHlwZSIgcXVpIG5vdXMgZGl0IHNpIHVuIGVtYWlsIGVzdCBkdSBzcGFtIG91IG5vbi4NCiMgT24gY29udmVydGl0IGNldHRlIGNvbG9ubmUgZW4gImZhY3RldXIiLCBjZSBxdWkgc2lnbmlmaWUgcXVlIGNldHRlIGNvbG9ubmUgDQojIGNvbnRpZW50IGRlcyBjYXTDqWdvcmllcyAoc3BhbSBvdSBub24tc3BhbSkgcGx1dMO0dCBxdWUgZGVzIG5vbWJyZXMuDQpzcGFtJHR5cGUgPC0gYXMuZmFjdG9yKHNwYW0kdHlwZSkNCg0KIyBBZmZpY2hhZ2UgZCd1biBhcGVyw6d1IGRlcyBkb25uw6llcw0KIyBPbiB1dGlsaXNlIGxhIGZvbmN0aW9uICJoZWFkIiBwb3VyIGFmZmljaGVyIGxlcyBwcmVtacOocmVzIGxpZ25lcyBkZSBub3MgZG9ubsOpZXMuIA0KIyBDZWxhIG5vdXMgcGVybWV0IGRlIHZvaXIgw6AgcXVvaSByZXNzZW1ibGVudCBub3MgZG9ubsOpZXMuDQpoZWFkKHNwYW0pDQoNCiMgU8OpcGFyYXRpb24gZGVzIGRvbm7DqWVzIGVuIGJhc2VzIGQnYXBwcmVudGlzc2FnZSBldCBkZSB0ZXN0ICg4MCUvMjAlKSBlbiBjb25zZXJ2YW50IGxlIHJhdGlvIGRlIHNwYW0NCiMgT24gdmV1dCBzw6lwYXJlciBub3MgZG9ubsOpZXMgZW4gZGV1eCBwYXJ0aWVzIDogdW5lIHBvdXIgZW50cmHDrm5lciBub3MgbW9kw6hsZXMgKDgwJSkgDQojIGV0IHVuZSBwb3VyIHRlc3RlciBub3MgbW9kw6hsZXMgKDIwJSkuDQojICJzZXQuc2VlZCgxMjMpIiBlc3QgdW5lIGNvbW1hbmRlIHF1aSBnYXJhbnRpdCBxdWUgc2kgcXVlbHF1J3VuIGQnYXV0cmUgZXjDqWN1dGUgY2UgY29kZSwgDQojIGlsIG9idGllbmRyYSBsZXMgbcOqbWVzIHLDqXN1bHRhdHMgcXVlIG5vdXMuIEMnZXN0IGNvbW1lIHNpIG9uIHV0aWxpc2FpdCB1biBtb3QgZGUgcGFzc2UgcG91ciBsYSByZXByb2R1Y3RpYmlsaXTDqS4NCnNldC5zZWVkKDEyMykgICMgUG91ciBsYSByZXByb2R1Y3RpYmlsaXTDqQ0KIyBPbiB1dGlsaXNlIHVuZSBmb25jdGlvbiBwb3VyIGNyw6llciBkZXMgaW5kaWNlcyBkZSBzw6lwYXJhdGlvbiBkZSBub3MgZG9ubsOpZXMuIA0KIyBMYSBmb25jdGlvbiAiY3JlYXRlRGF0YVBhcnRpdGlvbiIgcHJlbmQgODAlIGRlcyBkb25uw6llcyBwb3VyIGwnZW50cmHDrm5lbWVudCBldCBnYXJkZSBsZSByZXN0ZSBwb3VyIGxlIHRlc3QuDQp0cmFpbkluZGV4IDwtIGNyZWF0ZURhdGFQYXJ0aXRpb24oc3BhbSR0eXBlLCBwID0gMC44LCBsaXN0ID0gRkFMU0UpDQojIE9uIGNyw6llIGxhIGJhc2UgZCdhcHByZW50aXNzYWdlICg4MCUgZGVzIGRvbm7DqWVzKSBlbiB1dGlsaXNhbnQgbGVzIGluZGljZXMgY3LDqcOpcyBwcsOpY8OpZGVtbWVudC4NCnRyYWluRGF0YSA8LSBzcGFtW3RyYWluSW5kZXgsIF0NCiMgT24gY3LDqWUgbGEgYmFzZSBkZSB0ZXN0ICgyMCUgZGVzIGRvbm7DqWVzKSBlbiBwcmVuYW50IGxlIHJlc3RlIGRlcyBkb25uw6llcy4NCnRlc3REYXRhIDwtIHNwYW1bLXRyYWluSW5kZXgsIF0NCg0KIyBWw6lyaWZpY2F0aW9uIGR1IHJhdGlvIGRlIHNwYW0gZGFucyBsZXMgZGV1eCBiYXNlcw0KIyBPbiB1dGlsaXNlIGxhIGZvbmN0aW9uICJwcm9wLnRhYmxlIiBwb3VyIHbDqXJpZmllciBxdWUgbGUgcG91cmNlbnRhZ2UgZGUgc3BhbSBldCBkZSBub24tc3BhbSANCiMgZXN0IMOgIHBldSBwcsOocyBsZSBtw6ptZSBkYW5zIG5vcyBiYXNlcyBkJ2FwcHJlbnRpc3NhZ2UgZXQgZGUgdGVzdC4gDQojIENlbGEgbm91cyBhc3N1cmUgcXVlIG5vcyBkb25uw6llcyBzb250IGJpZW4gw6lxdWlsaWJyw6llcy4NCnByb3AudGFibGUodGFibGUodHJhaW5EYXRhJHR5cGUpKQ0KcHJvcC50YWJsZSh0YWJsZSh0ZXN0RGF0YSR0eXBlKSkNCmBgYA0KDQojIyBVdGlsaXNhdGlvbiBkZSBBZGFCb29zdA0KDQpgYGB7ciBDZWxsdWxlIDM6IFV0aWxpc2F0aW9uIGRlIEFkYUJvb3N0IChtb2TDqGxlIHBhciBkw6lmYXV0KX0NCiMgRW50cmHDrm5lbWVudCBkdSBtb2TDqGxlIEFkYUJvb3N0IGF2ZWMgMTAgaXTDqXJhdGlvbnMNCg0KIyBPbiB1dGlsaXNlIFN5cy50aW1lKCkgcG91ciBkw6ltYXJyZXIgbGUgY2hyb25vbcOodHJlIGV0IG1lc3VyZXIgY29tYmllbiBkZSB0ZW1wcw0KIyBjZWxhIHZhIHByZW5kcmUgcG91ciBlbnRyYcOubmVyIG5vdHJlIG1vZMOobGUuDQpzdGFydF90aW1lIDwtIFN5cy50aW1lKCkNCg0Kc2V0LnNlZWQoMTIzKSAgIyBQb3VyIGxhIHJlcHJvZHVjdGliaWxpdMOpDQoNCiMgT24gZW50cmHDrm5lIGxlIG1vZMOobGUgQWRhQm9vc3QuDQojIGJvb3N0aW5nKCkgZXN0IHVuZSBmb25jdGlvbiBxdWkgY3LDqWUgdW4gbW9kw6hsZSBkJ2FwcHJlbnRpc3NhZ2Ugc3VwZXJ2aXPDqSBhcHBlbMOpICJBZGFCb29zdCIuDQojIHR5cGUgfiAuIHNpZ25pZmllIHF1J29uIHV0aWxpc2UgdG91dGVzIGxlcyBjb2xvbm5lcyBkZSBub3RyZSB0YWJsZWF1IGRlIGRvbm7DqWVzICh0cmFpbkRhdGEpDQojIHBvdXIgcHLDqWRpcmUgbGEgY29sb25uZSAidHlwZSIuDQojIGRhdGEgPSB0cmFpbkRhdGEgc2lnbmlmaWUgcXUnb24gdXRpbGlzZSBsZXMgZG9ubsOpZXMgZCdhcHByZW50aXNzYWdlLg0KIyBtZmluYWwgPSAxMCBzaWduaWZpZSBxdSdvbiB2YSBmYWlyZSAxMCBpdMOpcmF0aW9ucywgb3UgMTAgY3ljbGVzIGQnYXBwcmVudGlzc2FnZS4NCmFkYWJvb3N0X21vZGVsIDwtIGJvb3N0aW5nKHR5cGUgfiAuLCBkYXRhID0gdHJhaW5EYXRhLCBtZmluYWwgPSAxMCkNCg0KIyBPbiBhcnLDqnRlIGxlIGNocm9ub23DqHRyZSBwb3VyIHZvaXIgY29tYmllbiBkZSB0ZW1wcyBjZWxhIGEgcHJpcyBwb3VyIGVudHJhw65uZXIgbGUgbW9kw6hsZS4NCmVuZF90aW1lIDwtIFN5cy50aW1lKCkNCiMgT24gY2FsY3VsZSBsYSBkaWZmw6lyZW5jZSBlbnRyZSBsZSB0ZW1wcyBkZSBmaW4gZXQgbGUgdGVtcHMgZGUgZMOpYnV0IHBvdXIgb2J0ZW5pcg0KIyBsZSB0ZW1wcyB0b3RhbCBkJ2V4w6ljdXRpb24uDQphZGFib29zdF90aW1lX2RlZmF1bHQgPC0gZW5kX3RpbWUgLSBzdGFydF90aW1lDQoNCiMgQ2FsY3VsIGR1IHRhdXggZCdlcnJldXIgc3VyIGxhIGJhc2UgZCdhcHByZW50aXNzYWdlDQoNCiMgT24gdXRpbGlzZSBwcmVkaWN0KCkgcG91ciBmYWlyZSBkZXMgcHLDqWRpY3Rpb25zIHN1ciBsZXMgZG9ubsOpZXMgZCdhcHByZW50aXNzYWdlLg0KIyBuZXdkYXRhID0gdHJhaW5EYXRhIHNpZ25pZmllIHF1J29uIGZhaXQgZGVzIHByw6lkaWN0aW9ucyBzdXIgbGVzIGRvbm7DqWVzIGQnYXBwcmVudGlzc2FnZS4NCnRyYWluX3ByZWQgPC0gcHJlZGljdChhZGFib29zdF9tb2RlbCwgbmV3ZGF0YSA9IHRyYWluRGF0YSkNCiMgT24gY2FsY3VsZSBsZSB0YXV4IGQnZXJyZXVyIGVuIGNvbXBhcmFudCBsZXMgcHLDqWRpY3Rpb25zIGF2ZWMgbGVzIHZyYWllcyB2YWxldXJzLg0KIyB0cmFpbl9wcmVkJGNsYXNzIGNvbnRpZW50IGxlcyBwcsOpZGljdGlvbnMsIGV0IHRyYWluRGF0YSR0eXBlIGNvbnRpZW50IGxlcyB2cmFpZXMgdmFsZXVycy4NCiMgT24gdXRpbGlzZSBtZWFuKCkgcG91ciBjYWxjdWxlciBsYSBtb3llbm5lIGRlcyBkaWZmw6lyZW5jZXMuDQp0cmFpbl9lcnJvciA8LSBtZWFuKHRyYWluX3ByZWQkY2xhc3MgIT0gdHJhaW5EYXRhJHR5cGUpDQoNCiMgQ2FsY3VsIGR1IHRhdXggZCdlcnJldXIgc3VyIGxhIGJhc2UgZGUgdGVzdA0KDQojIE9uIHV0aWxpc2UgcHJlZGljdCgpIHBvdXIgZmFpcmUgZGVzIHByw6lkaWN0aW9ucyBzdXIgbGVzIGRvbm7DqWVzIGRlIHRlc3QuDQojIG5ld2RhdGEgPSB0ZXN0RGF0YSBzaWduaWZpZSBxdSdvbiBmYWl0IGRlcyBwcsOpZGljdGlvbnMgc3VyIGxlcyBkb25uw6llcyBkZSB0ZXN0Lg0KdGVzdF9wcmVkIDwtIHByZWRpY3QoYWRhYm9vc3RfbW9kZWwsIG5ld2RhdGEgPSB0ZXN0RGF0YSkNCiMgT24gY2FsY3VsZSBsZSB0YXV4IGQnZXJyZXVyIGVuIGNvbXBhcmFudCBsZXMgcHLDqWRpY3Rpb25zIGF2ZWMgbGVzIHZyYWllcyB2YWxldXJzLg0KIyB0ZXN0X3ByZWQkY2xhc3MgY29udGllbnQgbGVzIHByw6lkaWN0aW9ucywgZXQgdGVzdERhdGEkdHlwZSBjb250aWVudCBsZXMgdnJhaWVzIHZhbGV1cnMuDQojIE9uIHV0aWxpc2UgbWVhbigpIHBvdXIgY2FsY3VsZXIgbGEgbW95ZW5uZSBkZXMgZGlmZsOpcmVuY2VzLg0KdGVzdF9lcnJvciA8LSBtZWFuKHRlc3RfcHJlZCRjbGFzcyAhPSB0ZXN0RGF0YSR0eXBlKQ0KDQojIEFmZmljaGFnZSBkZXMgcsOpc3VsdGF0cw0KDQojIE9uIGFmZmljaGUgbGUgdGF1eCBkJ2VycmV1ciBzdXIgbGEgYmFzZSBkJ2FwcHJlbnRpc3NhZ2UuDQp0cmFpbl9lcnJvcg0KDQojIExlIHByZW1pZXIgbm9tYnJlICgwLjAyNjg4NzU2KSBlc3QgbGUgdGF1eCBkJ2VycmV1ciBzdXIgbGEgYmFzZSBkJ2FwcHJlbnRpc3NhZ2UuDQojIENlbGEgc2lnbmlmaWUgcXVlIGxlIG1vZMOobGUgYSBmYWl0IHVuZSBlcnJldXIgZW52aXJvbiAyLjY5JSBkdSB0ZW1wcyBzdXIgbGVzIGRvbm7DqWVzIGQnYXBwcmVudGlzc2FnZS4NCg0KIyBPbiBhZmZpY2hlIGxlIHRhdXggZCdlcnJldXIgc3VyIGxhIGJhc2UgZGUgdGVzdC4NCnRlc3RfZXJyb3INCg0KIyBMZSBkZXV4acOobWUgbm9tYnJlICgwLjA1MTE0MjU1KSBlc3QgbGUgdGF1eCBkJ2VycmV1ciBzdXIgbGEgYmFzZSBkZSB0ZXN0Lg0KIyBDZWxhIHNpZ25pZmllIHF1ZSBsZSBtb2TDqGxlIGEgZmFpdCB1bmUgZXJyZXVyIGVudmlyb24gNS4xMSUgZHUgdGVtcHMgc3VyIGxlcyBkb25uw6llcyBkZSB0ZXN0Lg0KDQojIE9uIGFmZmljaGUgbGUgdGVtcHMgdG90YWwgZCdleMOpY3V0aW9uIHBvdXIgbCdlbnRyYcOubmVtZW50IGR1IG1vZMOobGUuDQphZGFib29zdF90aW1lX2RlZmF1bHQNCg0KIyBMZSB0cm9pc2nDqG1lIHLDqXN1bHRhdCAoVGltZSBkaWZmZXJlbmNlIG9mIDUuMDQxNjc4IHNlY3MpIGluZGlxdWUgcXVlIGxlIHRlbXBzIHRvdGFsIA0KIyBwb3VyIGVudHJhw65uZXIgbGUgbW9kw6hsZSBBZGFCb29zdCBhdmVjIDEwIGl0w6lyYXRpb25zIGEgw6l0w6kgZCdlbnZpcm9uIDUuMDQgc2Vjb25kZXMuDQpgYGANCg0KYGBge3IgQ2VsbHVsZSA0OiDDiXZvbHV0aW9uIGRlcyBlcnJldXJzIGF2ZWMgQWRhQm9vc3QgKDMwMCBpdMOpcmF0aW9ucywgYXZlYyBldCBzYW5zIGxpbWl0YXRpb24gZGUgcHJvZm9uZGV1cn0NCiMgRW50cmHDrm5lbWVudCBkdSBtb2TDqGxlIEFkYUJvb3N0IGF2ZWMganVzcXUnw6AgMzAwIGl0w6lyYXRpb25zDQoNCnN0YXJ0X3RpbWUgPC0gU3lzLnRpbWUoKSAgIyBEw6ltYXJyYWdlIGR1IGNocm9ub23DqHRyZQ0Kc2V0LnNlZWQoMTIzKSAgIyBQb3VyIGxhIHJlcHJvZHVjdGliaWxpdMOpDQojIENyw6lhdGlvbiBkdSBtb2TDqGxlIEFkYUJvb3N0IGF2ZWMgMzAwIGl0w6lyYXRpb25zDQojIG1maW5hbCA9IDMwMCBzaWduaWZpZSBxdSdvbiBmYWl0IDMwMCBjeWNsZXMgZCdhcHByZW50aXNzYWdlDQphZGFib29zdF9tb2RlbF8zMDAgPC0gYm9vc3RpbmcodHlwZSB+IC4sIGRhdGEgPSB0cmFpbkRhdGEsIG1maW5hbCA9IDMwMCkNCg0KZW5kX3RpbWUgPC0gU3lzLnRpbWUoKSAgIyBBcnLDqnQgZHUgY2hyb25vbcOodHJlDQojIENhbGN1bCBkdSB0ZW1wcyB0b3RhbCBkJ2V4w6ljdXRpb24gcG91ciBlbnRyYcOubmVyIGxlIG1vZMOobGUNCmFkYWJvb3N0X3RpbWVfMzAwIDwtIGVuZF90aW1lIC0gc3RhcnRfdGltZQ0KDQojIFN1aXZpIGRlcyBlcnJldXJzIHN1ciBsYSBiYXNlIGQnYXBwcmVudGlzc2FnZSBldCBkZSB0ZXN0DQoNCiMgZXJyb3Jldm9sKCkgZXN0IHVuZSBmb25jdGlvbiBxdWkgc3VpdCBsJ8Opdm9sdXRpb24gZGVzIGVycmV1cnMgYXUgZmlsIGRlcyBpdMOpcmF0aW9ucw0KIyBFbGxlIHJldG91cm5lIGxlcyB0YXV4IGQnZXJyZXVyIHBvdXIgY2hhcXVlIGl0w6lyYXRpb24NCmVycm9yX2V2b2xfdHJhaW4gPC0gZXJyb3Jldm9sKGFkYWJvb3N0X21vZGVsXzMwMCwgdHJhaW5EYXRhKQ0KZXJyb3JfZXZvbF90ZXN0IDwtIGVycm9yZXZvbChhZGFib29zdF9tb2RlbF8zMDAsIHRlc3REYXRhKQ0KDQojIEFmZmljaGFnZSBkZXMgcsOpc3VsdGF0cw0KDQojIE9uIHByZW5kIGwnZXJyZXVyIMOgIGxhIDMwMMOobWUgaXTDqXJhdGlvbiBwb3VyIGxlcyBkb25uw6llcyBkJ2FwcHJlbnRpc3NhZ2UNCnRyYWluX2Vycm9yXzMwMCA8LSBlcnJvcl9ldm9sX3RyYWluJGVycm9yWzMwMF0NCiMgT24gcHJlbmQgbCdlcnJldXIgw6AgbGEgMzAww6htZSBpdMOpcmF0aW9uIHBvdXIgbGVzIGRvbm7DqWVzIGRlIHRlc3QNCnRlc3RfZXJyb3JfMzAwIDwtIGVycm9yX2V2b2xfdGVzdCRlcnJvclszMDBdDQoNCiMgQWZmaWNoYWdlIGR1IHRhdXggZCdlcnJldXIgc3VyIGxlcyBkb25uw6llcyBkJ2FwcHJlbnRpc3NhZ2UgYXByw6hzIDMwMCBpdMOpcmF0aW9ucw0KdHJhaW5fZXJyb3JfMzAwDQojIEFmZmljaGFnZSBkdSB0YXV4IGQnZXJyZXVyIHN1ciBsZXMgZG9ubsOpZXMgZGUgdGVzdCBhcHLDqHMgMzAwIGl0w6lyYXRpb25zDQp0ZXN0X2Vycm9yXzMwMA0KIyBBZmZpY2hhZ2UgZHUgdGVtcHMgdG90YWwgZCdleMOpY3V0aW9uIHBvdXIgZW50cmHDrm5lciBsZSBtb2TDqGxlIGF2ZWMgMzAwIGl0w6lyYXRpb25zDQphZGFib29zdF90aW1lXzMwMA0KDQojIExlIHByZW1pZXIgZW5zZW1ibGUgZGUgcsOpc3VsdGF0cyBjb3JyZXNwb25kIMOgIGwnZXJyZXVyIGQnYXBwcmVudGlzc2FnZSAoMC4wMDA1NDMxODMxKSANCiMgZXQgbCdlcnJldXIgZGUgdGVzdCAoMC4wMzY5OTY3NCkgcG91ciBsZSBtb2TDqGxlIEFkYUJvb3N0IGF2ZWMgMzAwIGl0w6lyYXRpb25zIHNhbnMgbGltaXRhdGlvbiBkZSBwcm9mb25kZXVyLg0KIyBMZSB0ZW1wcyBkJ2V4w6ljdXRpb24gdG90YWwgZXN0IGRlIDMuMTUwNzAyIG1pbnV0ZXMuDQoNCiMgVHJhw6dhZ2UgZGVzIGNvdXJiZXMgZCdlcnJldXJzDQoNCiMgcGxvdCgpIGNyw6llIHVuIGdyYXBoaXF1ZSBtb250cmFudCBsJ8Opdm9sdXRpb24gZGVzIGVycmV1cnMNCiMgdHlwZSA9ICJsIiBzaWduaWZpZSBxdSdvbiB0cmFjZSB1bmUgbGlnbmUNCiMgY29sID0gImJsdWUiIHNpZ25pZmllIHF1ZSBsYSBsaWduZSBlc3QgYmxldWUgcG91ciBsZXMgZXJyZXVycyBkJ2FwcHJlbnRpc3NhZ2UNCiMgeWxpbSA9IGMoMCwgMC4xKSBkw6lmaW5pdCBsZXMgbGltaXRlcyBkZSBsJ2F4ZSBZIGRlIDAgw6AgMC4xIHBvdXIgdW5lIG1laWxsZXVyZSB2aXN1YWxpc2F0aW9uDQojIHlsYWIgPSAiVGF1eCBkJ2VycmV1ciIgZG9ubmUgdW4gdGl0cmUgw6AgbCdheGUgWQ0KIyB4bGFiID0gIk5vbWJyZSBkJ2l0w6lyYXRpb25zIiBkb25uZSB1biB0aXRyZSDDoCBsJ2F4ZSBYDQojIG1haW4gPSAiw4l2b2x1dGlvbiBkZXMgZXJyZXVycyBhdmVjIEFkYUJvb3N0ICgzMDAgaXTDqXJhdGlvbnMpIiBkb25uZSB1biB0aXRyZSBhdSBncmFwaGlxdWUNCnBsb3QoZXJyb3JfZXZvbF90cmFpbiRlcnJvciwgdHlwZSA9ICJsIiwgY29sID0gImJsdWUiLCB5bGltID0gYygwLCAwLjEpLCANCiAgICAgeWxhYiA9ICJUYXV4IGQnZXJyZXVyIiwgeGxhYiA9ICJOb21icmUgZCdpdMOpcmF0aW9ucyIsIA0KICAgICBtYWluID0gIsOJdm9sdXRpb24gZGVzIGVycmV1cnMgYXZlYyBBZGFCb29zdCAoMzAwIGl0w6lyYXRpb25zKSIpDQojIGxpbmVzKCkgYWpvdXRlIHVuZSBsaWduZSByb3VnZSBwb3VyIGxlcyBlcnJldXJzIGRlIHRlc3QNCmxpbmVzKGVycm9yX2V2b2xfdGVzdCRlcnJvciwgY29sID0gInJlZCIpDQojIGxlZ2VuZCgpIGFqb3V0ZSB1bmUgbMOpZ2VuZGUgYXUgZ3JhcGhpcXVlIHBvdXIgZXhwbGlxdWVyIGxlcyBjb3VsZXVycw0KbGVnZW5kKCJ0b3ByaWdodCIsIGxlZ2VuZCA9IGMoIkVycmV1ciBkJ2FwcHJlbnRpc3NhZ2UiLCAiRXJyZXVyIGRlIHRlc3QiKSwgDQogICAgICAgY29sID0gYygiYmx1ZSIsICJyZWQiKSwgbHR5ID0gMSkNCg0KIyBFbnRyYcOubmVtZW50IGR1IG1vZMOobGUgQWRhQm9vc3QgYXZlYyBqdXNxdSfDoCAzMDAgaXTDqXJhdGlvbnMgZXQgbGltaXRhdGlvbiBkZSBsYSBwcm9mb25kZXVyIGRlcyBhcmJyZXMgw6AgMw0KDQpzdGFydF90aW1lIDwtIFN5cy50aW1lKCkNCnNldC5zZWVkKDEyMykNCiMgQ3LDqWF0aW9uIGR1IG1vZMOobGUgQWRhQm9vc3QgYXZlYyAzMDAgaXTDqXJhdGlvbnMgZXQgcHJvZm9uZGV1ciBsaW1pdMOpZQ0KIyBjb250cm9sID0gcnBhcnQuY29udHJvbChtYXhkZXB0aCA9IDMpIHNpZ25pZmllIHF1J29uIGxpbWl0ZSBsYSBwcm9mb25kZXVyIGRlcyBhcmJyZXMgw6AgMw0KYWRhYm9vc3RfbW9kZWxfZGVwdGgzIDwtIGJvb3N0aW5nKHR5cGUgfiAuLCBkYXRhID0gdHJhaW5EYXRhLCBtZmluYWwgPSAzMDAsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbnRyb2wgPSBycGFydC5jb250cm9sKG1heGRlcHRoID0gMykpDQoNCmVuZF90aW1lIDwtIFN5cy50aW1lKCkNCmFkYWJvb3N0X3RpbWVfZGVwdGgzIDwtIGVuZF90aW1lIC0gc3RhcnRfdGltZQ0KDQojIFN1aXZpIGRlcyBlcnJldXJzIHN1ciBsYSBiYXNlIGQnYXBwcmVudGlzc2FnZSBldCBkZSB0ZXN0IGF2ZWMgbGltaXRhdGlvbiBkZSBwcm9mb25kZXVyDQoNCiMgU3VpdmkgZGVzIGVycmV1cnMgcG91ciBsZSBtb2TDqGxlIGF2ZWMgcHJvZm9uZGV1ciBsaW1pdMOpZQ0KZXJyb3JfZXZvbF90cmFpbl9kZXB0aDMgPC0gZXJyb3Jldm9sKGFkYWJvb3N0X21vZGVsX2RlcHRoMywgdHJhaW5EYXRhKQ0KZXJyb3JfZXZvbF90ZXN0X2RlcHRoMyA8LSBlcnJvcmV2b2woYWRhYm9vc3RfbW9kZWxfZGVwdGgzLCB0ZXN0RGF0YSkNCg0KIyBBZmZpY2hhZ2UgZGVzIHLDqXN1bHRhdHMNCg0KIyBFcnJldXIgZCdhcHByZW50aXNzYWdlIGFwcsOocyAzMDAgaXTDqXJhdGlvbnMgYXZlYyBwcm9mb25kZXVyIGxpbWl0w6llDQp0cmFpbl9lcnJvcl9kZXB0aDMgPC0gZXJyb3JfZXZvbF90cmFpbl9kZXB0aDMkZXJyb3JbMzAwXQ0KIyBFcnJldXIgZGUgdGVzdCBhcHLDqHMgMzAwIGl0w6lyYXRpb25zIGF2ZWMgcHJvZm9uZGV1ciBsaW1pdMOpZQ0KdGVzdF9lcnJvcl9kZXB0aDMgPC0gZXJyb3JfZXZvbF90ZXN0X2RlcHRoMyRlcnJvclszMDBdDQoNCiMgQWZmaWNoYWdlIGRlcyBlcnJldXJzDQp0cmFpbl9lcnJvcl9kZXB0aDMNCnRlc3RfZXJyb3JfZGVwdGgzDQphZGFib29zdF90aW1lX2RlcHRoMw0KDQojIExlIGRldXhpw6htZSBlbnNlbWJsZSBkZSByw6lzdWx0YXRzIGNvcnJlc3BvbmQgw6AgbCdlcnJldXIgZCdhcHByZW50aXNzYWdlICgwLjA0MDE5NTU1KSANCiMgZXQgbCdlcnJldXIgZGUgdGVzdCAoMC4wNTIyMzA2OSkgcG91ciBsZSBtb2TDqGxlIEFkYUJvb3N0IGF2ZWMgMzAwIGl0w6lyYXRpb25zIGV0IHVuZSBwcm9mb25kZXVyIGxpbWl0w6llIMOgIDMuDQojIExlIHRlbXBzIGQnZXjDqWN1dGlvbiB0b3RhbCBlc3QgZGUgNDkuNTcyMjggc2Vjb25kZXMuDQoNCiMgVHJhw6dhZ2UgZGVzIGNvdXJiZXMgZCdlcnJldXJzIGF2ZWMgbGltaXRhdGlvbiBkZSBwcm9mb25kZXVyDQoNCiMgQ3LDqWF0aW9uIGR1IGdyYXBoaXF1ZSBwb3VyIGxlIG1vZMOobGUgYXZlYyBwcm9mb25kZXVyIGxpbWl0w6llDQpwbG90KGVycm9yX2V2b2xfdHJhaW5fZGVwdGgzJGVycm9yLCB0eXBlID0gImwiLCBjb2wgPSAiYmx1ZSIsIHlsaW0gPSBjKDAsIDAuMSksIA0KICAgICB5bGFiID0gIlRhdXggZCdlcnJldXIiLCB4bGFiID0gIk5vbWJyZSBkJ2l0w6lyYXRpb25zIiwgDQogICAgIG1haW4gPSAiw4l2b2x1dGlvbiBkZXMgZXJyZXVycyBhdmVjIEFkYUJvb3N0ICgzMDAgaXTDqXJhdGlvbnMsIHByb2ZvbmRldXIgbGltaXTDqWUpIikNCiMgQWpvdXQgZGUgbGEgbGlnbmUgcm91Z2UgcG91ciBsZXMgZXJyZXVycyBkZSB0ZXN0DQpsaW5lcyhlcnJvcl9ldm9sX3Rlc3RfZGVwdGgzJGVycm9yLCBjb2wgPSAicmVkIikNCiMgQWpvdXQgZGUgbGEgbMOpZ2VuZGUgcG91ciBleHBsaXF1ZXIgbGVzIGNvdWxldXJzDQpsZWdlbmQoInRvcHJpZ2h0IiwgbGVnZW5kID0gYygiRXJyZXVyIGQnYXBwcmVudGlzc2FnZSAocHJvZm9uZGV1ciBsaW1pdMOpZSkiLCANCiAgICAgICAiRXJyZXVyIGRlIHRlc3QgKHByb2ZvbmRldXIgbGltaXTDqWUpIiksIGNvbCA9IGMoImJsdWUiLCAicmVkIiksIGx0eSA9IDEpDQoNCiMgTGVzIGdyYXBoaXF1ZXMgbW9udHJlbnQgbCfDqXZvbHV0aW9uIGRlcyBlcnJldXJzIGQnYXBwcmVudGlzc2FnZSBldCBkZSB0ZXN0IA0KIyBhdSBmaWwgZGVzIDMwMCBpdMOpcmF0aW9ucy4gTGVzIGRldXggY291cmJlcyBwZXJtZXR0ZW50IGRlIHZpc3VhbGlzZXIgDQojIGNvbW1lbnQgbGVzIGVycmV1cnMgZGltaW51ZW50IGF1IGZ1ciBldCDDoCBtZXN1cmUgcXVlIGxlIG1vZMOobGUgYXBwcmVuZC4NCiNMZSBwcmVtaWVyIGdyYXBoaXF1ZSBpbGx1c3RyZSBsJ8Opdm9sdXRpb24gZGVzIGVycmV1cnMgZCdhcHByZW50aXNzYWdlIGV0IGRlIHRlc3QgcG91ciBsZSBtb2TDqGxlIEFkYUJvb3N0IHN1ciAzMDAgaXTDqXJhdGlvbnMuIE9uIG9ic2VydmUgcXVlIGwnZXJyZXVyIGQnYXBwcmVudGlzc2FnZSAoY291cmJlIGJsZXVlKSBkaW1pbnVlIHJhcGlkZW1lbnQgZXQgYXR0ZWludCBxdWFzaW1lbnQgesOpcm8gYXByw6hzIGVudmlyb24gNTAgaXTDqXJhdGlvbnMuIENlbGEgaW5kaXF1ZSBxdSdBZGFCb29zdCBlc3QgdHLDqHMgZWZmaWNhY2UgcG91ciByw6lkdWlyZSBsJ2VycmV1ciBzdXIgbGVzIGRvbm7DqWVzIGQnYXBwcmVudGlzc2FnZS4gQ2VwZW5kYW50LCBsJ2VycmV1ciBkZSB0ZXN0IChjb3VyYmUgcm91Z2UpIHN1aXQgdW4gc2Now6ltYSBkaWZmw6lyZW50LiBCaWVuIHF1J2VsbGUgZGltaW51ZSBpbml0aWFsZW1lbnQsIGVsbGUgc3RhZ25lIGV0IGNvbW1lbmNlIG3Dqm1lIMOgIGzDqWfDqHJlbWVudCBhdWdtZW50ZXIgYXByw6hzIGVudmlyb24gNTAgaXTDqXJhdGlvbnMuIENlIHBow6lub23DqG5lIHN1Z2fDqHJlIHVuIHN1cmFwcHJlbnRpc3NhZ2UsIG/DuSBsZSBtb2TDqGxlIGRldmllbnQgdHJvcCBjb21wbGV4ZSBldCBjb21tZW5jZSDDoCBtw6ltb3Jpc2VyIGxlIGJydWl0IGRhbnMgbGVzIGRvbm7DqWVzIGQnYXBwcmVudGlzc2FnZSBhdSBsaWV1IGQnYXBwcmVuZHJlIGRlcyBtb3RpZnMgZ8OpbsOpcmFsaXNhYmxlcy4gTWFsZ3LDqSBjZWxhLCBsJ2VycmV1ciBkZSB0ZXN0IHJlc3RlIHJlbGF0aXZlbWVudCBzdGFibGUgYXByw6hzIGNldHRlIHBoYXNlIGluaXRpYWxlLCBtb250cmFudCB1bmUgY2VydGFpbmUgcm9idXN0ZXNzZSBkdSBtb2TDqGxlLg0KDQojTGUgc2Vjb25kIGdyYXBoaXF1ZSBtb250cmUgbCfDqXZvbHV0aW9uIGRlcyBlcnJldXJzIHBvdXIgbGUgbcOqbWUgbW9kw6hsZSBBZGFCb29zdCwgbWFpcyBjZXR0ZSBmb2lzIGF2ZWMgdW5lIGxpbWl0YXRpb24gZGUgcHJvZm9uZGV1ciBkZXMgYXJicmVzIMOgIHRyb2lzIG5pdmVhdXguIEwnZXJyZXVyIGQnYXBwcmVudGlzc2FnZSBjb21tZW5jZSBwbHVzIGhhdXQgZXQgbmUgZGVzY2VuZCBwYXMgYXVzc2kgYmFzIHF1ZSBkYW5zIGxlIHByZW1pZXIgZ3JhcGhpcXVlLCBhdHRlaWduYW50IHVuIHBsYXRlYXUgYXV0b3VyIGRlIDAuMDQuIENlbGEgaW5kaXF1ZSBxdWUgbGVzIGFyYnJlcyBtb2lucyBwcm9mb25kcyBzb250IG1vaW5zIGNhcGFibGVzIGRlIHMnYWRhcHRlciBwYXJmYWl0ZW1lbnQgYXV4IGRvbm7DqWVzIGQnYXBwcmVudGlzc2FnZS4gTCdlcnJldXIgZGUgdGVzdCwgYmllbiBxdSdlbGxlIGZsdWN0dWUgcGx1cyBxdWUgZGFucyBsZSBwcmVtaWVyIGdyYXBoaXF1ZSwgcmVzdGUgcGx1cyBzdGFibGUgc2FucyBtb250cmVyIHVuZSBhdWdtZW50YXRpb24gbm90YWJsZS4gQ2V0dGUgc3RhYmlsaXTDqSBpbmRpcXVlIHF1ZSBsaW1pdGVyIGxhIHByb2ZvbmRldXIgZGVzIGFyYnJlcyBhaWRlIMOgIMOpdml0ZXIgbGUgc3VyYXBwcmVudGlzc2FnZSwgYmllbiBxdWUgY2VsYSByw6lkdWlzZSDDqWdhbGVtZW50IGxhIGNhcGFjaXTDqSBkdSBtb2TDqGxlIMOgIGNhcHR1cmVyIGRlcyBtb3RpZnMgY29tcGxleGVzLCBjZSBxdWkgc2UgcmVmbMOodGUgZGFucyBsJ2VycmV1ciBkJ2FwcHJlbnRpc3NhZ2UgcGx1cyDDqWxldsOpZS4NCmBgYA0KDQojIyBHcmFkaWVudCBCb29zdGluZw0KDQpgYGB7ciBDZWxsdWxlIDU6IFV0aWxpc2F0aW9uIGRlIEdyYWRpZW50IEJvb3N0aW5nIChtb2TDqGxlIHBhciBkw6lmYXV0KX0NCiMgQ29udmVyc2lvbiBkZSBsYSB2YXJpYWJsZSBjaWJsZSBlbiB2YWxldXJzIDAgZXQgMSBwb3VyIGxlIG1vZMOobGUgZ2JtDQojIExlIG1vZMOobGUgZ2JtIG7DqWNlc3NpdGUgcXVlIGxhIHZhcmlhYmxlIGNpYmxlIHNvaXQgZGVzIHZhbGV1cnMgbnVtw6lyaXF1ZXMgMCBldCAxLg0KIyBPbiBjb252ZXJ0aXQgZG9uYyAic3BhbSIgZW4gMSBldCAibm9uc3BhbSIgZW4gMCBwb3VyIGxlcyBkb25uw6llcyBkJ2FwcHJlbnRpc3NhZ2UuDQp0cmFpbkRhdGEkdHlwZSA8LSBpZmVsc2UodHJhaW5EYXRhJHR5cGUgPT0gInNwYW0iLCAxLCAwKQ0KIyBPbiBmYWl0IGRlIG3Dqm1lIHBvdXIgbGVzIGRvbm7DqWVzIGRlIHRlc3QuDQp0ZXN0RGF0YSR0eXBlIDwtIGlmZWxzZSh0ZXN0RGF0YSR0eXBlID09ICJzcGFtIiwgMSwgMCkNCg0KIyBFbnRyYcOubmVtZW50IGR1IG1vZMOobGUgR3JhZGllbnQgQm9vc3RpbmcgYXZlYyAxMDAwIGl0w6lyYXRpb25zDQpzdGFydF90aW1lIDwtIFN5cy50aW1lKCkgICMgRMOpbWFycmFnZSBkdSBjaHJvbm9tw6h0cmUNCnNldC5zZWVkKDEyMykgICMgUG91ciBsYSByZXByb2R1Y3RpYmlsaXTDqQ0KIyBPbiBjcsOpZSB1biBtb2TDqGxlIGRlIEdyYWRpZW50IEJvb3N0aW5nIGVuIHV0aWxpc2FudCBnYm0oKS4NCiMgdHlwZSB+IC4gc2lnbmlmaWUgcXUnb24gdXRpbGlzZSB0b3V0ZXMgbGVzIGNvbG9ubmVzIHBvdXIgcHLDqWRpcmUgbGEgY29sb25uZSAidHlwZSIuDQojIGRhdGEgPSB0cmFpbkRhdGEgc2lnbmlmaWUgcXUnb24gdXRpbGlzZSBsZXMgZG9ubsOpZXMgZCdhcHByZW50aXNzYWdlLg0KIyBkaXN0cmlidXRpb24gPSAiYmVybm91bGxpIiBzaWduaWZpZSBxdSdvbiB1dGlsaXNlIHVuZSBkaXN0cmlidXRpb24gZGUgQmVybm91bGxpIHBvdXIgbGVzIGRvbm7DqWVzIGJpbmFpcmVzLg0KIyBuLnRyZWVzID0gMTAwMCBzaWduaWZpZSBxdSdvbiBmYWl0IDEwMDAgaXTDqXJhdGlvbnMuDQojIGludGVyYWN0aW9uLmRlcHRoID0gMyBzaWduaWZpZSBxdSdvbiBwZXJtZXQgYXV4IGFyYnJlcyBkJ2F2b2lyIHVuZSBwcm9mb25kZXVyIGRlIDMuDQojIHNocmlua2FnZSA9IDAuMDEgZXN0IHVuIHBhcmFtw6h0cmUgcXVpIGNvbnRyw7RsZSBsYSB2aXRlc3NlIGQnYXBwcmVudGlzc2FnZS4NCiMgY3YuZm9sZHMgPSA1IHNpZ25pZmllIHF1J29uIHV0aWxpc2UgdW5lIHZhbGlkYXRpb24gY3JvaXPDqWUgYXZlYyA1IHBsaXMuDQpnYm1fbW9kZWwgPC0gZ2JtKHR5cGUgfiAuLCBkYXRhID0gdHJhaW5EYXRhLCBkaXN0cmlidXRpb24gPSAiYmVybm91bGxpIiwgDQogICAgICAgICAgICAgICAgIG4udHJlZXMgPSAxMDAwLCBpbnRlcmFjdGlvbi5kZXB0aCA9IDMsIHNocmlua2FnZSA9IDAuMDEsIA0KICAgICAgICAgICAgICAgICBjdi5mb2xkcyA9IDUpDQplbmRfdGltZSA8LSBTeXMudGltZSgpICAjIEFycsOqdCBkdSBjaHJvbm9tw6h0cmUNCiMgT24gY2FsY3VsZSBsZSB0ZW1wcyB0b3RhbCBkJ2V4w6ljdXRpb24gcG91ciBlbnRyYcOubmVyIGxlIG1vZMOobGUuDQpnYm1fdGltZV9kZWZhdWx0IDwtIGVuZF90aW1lIC0gc3RhcnRfdGltZQ0KDQojIENhbGN1bCBkZXMgdGF1eCBkJ2VycmV1ciBkJ2FwcHJlbnRpc3NhZ2UgZXQgZGUgZ8OpbsOpcmFsaXNhdGlvbg0KIyBPbiB1dGlsaXNlIHByZWRpY3QoKSBwb3VyIGZhaXJlIGRlcyBwcsOpZGljdGlvbnMgc3VyIGxlcyBkb25uw6llcyBkJ2FwcHJlbnRpc3NhZ2UgYXZlYyAxMDAwIGFyYnJlcy4NCmdibV90cmFpbl9wcmVkIDwtIHByZWRpY3QoZ2JtX21vZGVsLCBuZXdkYXRhID0gdHJhaW5EYXRhLCBuLnRyZWVzID0gMTAwMCwgDQogICAgICAgICAgICAgICAgICAgICAgICAgIHR5cGUgPSAicmVzcG9uc2UiKQ0KIyBPbiBmYWl0IGRlIG3Dqm1lIHBvdXIgbGVzIGRvbm7DqWVzIGRlIHRlc3QuDQpnYm1fdGVzdF9wcmVkIDwtIHByZWRpY3QoZ2JtX21vZGVsLCBuZXdkYXRhID0gdGVzdERhdGEsIG4udHJlZXMgPSAxMDAwLCANCiAgICAgICAgICAgICAgICAgICAgICAgICB0eXBlID0gInJlc3BvbnNlIikNCg0KIyBDb252ZXJzaW9uIGRlcyBwcsOpZGljdGlvbnMgZW4gY2xhc3NlcyBiaW5haXJlcw0KIyBPbiBjb252ZXJ0aXQgbGVzIHByw6lkaWN0aW9ucyBlbiB2YWxldXJzIGJpbmFpcmVzICgwIG91IDEpLg0KIyBTaSBsYSBwcsOpZGljdGlvbiBlc3Qgc3Vww6lyaWV1cmUgw6AgMC41LCBvbiBsYSBjb25zaWTDqHJlIGNvbW1lIDEgKHNwYW0pLCBzaW5vbiBjb21tZSAwIChub25zcGFtKS4NCmdibV90cmFpbl9jbGFzcyA8LSBpZmVsc2UoZ2JtX3RyYWluX3ByZWQgPiAwLjUsIDEsIDApDQpnYm1fdGVzdF9jbGFzcyA8LSBpZmVsc2UoZ2JtX3Rlc3RfcHJlZCA+IDAuNSwgMSwgMCkNCg0KIyBDYWxjdWwgZGVzIGVycmV1cnMNCiMgT24gY29tcGFyZSBsZXMgcHLDqWRpY3Rpb25zIGF2ZWMgbGVzIHZyYWllcyB2YWxldXJzIHBvdXIgY2FsY3VsZXIgbGUgdGF1eCBkJ2VycmV1ci4NCmdibV90cmFpbl9lcnJvciA8LSBtZWFuKGdibV90cmFpbl9jbGFzcyAhPSB0cmFpbkRhdGEkdHlwZSkNCmdibV90ZXN0X2Vycm9yIDwtIG1lYW4oZ2JtX3Rlc3RfY2xhc3MgIT0gdGVzdERhdGEkdHlwZSkNCg0KIyBBZmZpY2hhZ2UgZGVzIHLDqXN1bHRhdHMNCiMgT24gYWZmaWNoZSBsZSB0YXV4IGQnZXJyZXVyIHN1ciBsZXMgZG9ubsOpZXMgZCdhcHByZW50aXNzYWdlLg0KZ2JtX3RyYWluX2Vycm9yDQoNCiMgTGUgcHJlbWllciBub21icmUgKDAuMDQ2NDQyMTUpIGVzdCBsZSB0YXV4IGQnZXJyZXVyIHN1ciBsZXMgZG9ubsOpZXMgZCdhcHByZW50aXNzYWdlLg0KIyBDZWxhIHNpZ25pZmllIHF1ZSBsZSBtb2TDqGxlIGEgZmFpdCB1bmUgZXJyZXVyIGVudmlyb24gNC42NCUgZHUgdGVtcHMgc3VyIGxlcyBkb25uw6llcyBkJ2FwcHJlbnRpc3NhZ2UuDQoNCg0KIyBPbiBhZmZpY2hlIGxlIHRhdXggZCdlcnJldXIgc3VyIGxlcyBkb25uw6llcyBkZSB0ZXN0Lg0KZ2JtX3Rlc3RfZXJyb3INCg0KIyBMZSBkZXV4acOobWUgbm9tYnJlICgwLjA1MzMxODgyKSBlc3QgbGUgdGF1eCBkJ2VycmV1ciBzdXIgbGVzIGRvbm7DqWVzIGRlIHRlc3QuDQojIENlbGEgc2lnbmlmaWUgcXVlIGxlIG1vZMOobGUgYSBmYWl0IHVuZSBlcnJldXIgZW52aXJvbiA1LjMzJSBkdSB0ZW1wcyBzdXIgbGVzIGRvbm7DqWVzIGRlIHRlc3QuDQoNCiMgT24gYWZmaWNoZSBsZSB0ZW1wcyB0b3RhbCBkJ2V4w6ljdXRpb24gcG91ciBlbnRyYcOubmVyIGxlIG1vZMOobGUuDQpnYm1fdGltZV9kZWZhdWx0DQoNCiMgTGUgdHJvaXNpw6htZSByw6lzdWx0YXQgKFRpbWUgZGlmZmVyZW5jZSBvZiAzMC4xNzg4MSBzZWNzKSBpbmRpcXVlIHF1ZSBsZSB0ZW1wcyB0b3RhbCANCiMgcG91ciBlbnRyYcOubmVyIGxlIG1vZMOobGUgR3JhZGllbnQgQm9vc3RpbmcgYXZlYyAxMDAwIGl0w6lyYXRpb25zIGEgw6l0w6kgZCdlbnZpcm9uIDMwLjE4IHNlY29uZGVzLg0KDQoNCiMgVHJhw6dhZ2UgZGVzIGNvdXJiZXMgZGUgZMOpdmlhbmNlDQojIGdibS5wZXJmKCkgcGVybWV0IGRlIHRyb3V2ZXIgbGUgbWVpbGxldXIgbm9tYnJlIGQnYXJicmVzIGVuIHV0aWxpc2FudCBsYSB2YWxpZGF0aW9uIGNyb2lzw6llLg0KZ2JtX3BlcmZvcm1hbmNlIDwtIGdibS5wZXJmKGdibV9tb2RlbCwgbWV0aG9kID0gImN2IikNCg0KIyBMZSBncmFwaGlxdWUgcHLDqXNlbnTDqSBtb250cmUgbCfDqXZvbHV0aW9uIGRlIGxhIGTDqXZpYW5jZSBkZSBCZXJub3VsbGkgcG91ciBsZSBtb2TDqGxlIEdyYWRpZW50IEJvb3N0aW5nIGF1IGNvdXJzIGRlIDEwMDAgaXTDqXJhdGlvbnMuIExhIGTDqXZpYW5jZSBkZSBCZXJub3VsbGkgZXN0IHVuZSBtZXN1cmUgZGUgbGEgcXVhbGl0w6kgZGUgbCdhanVzdGVtZW50IGQndW4gbW9kw6hsZSwgdXRpbGlzw6llIG5vdGFtbWVudCBwb3VyIGxlcyBwcm9ibMOobWVzIGRlIGNsYXNzaWZpY2F0aW9uIGJpbmFpcmUuIFVuZSB2YWxldXIgcGx1cyBmYWlibGUgZGUgY2V0dGUgZMOpdmlhbmNlIGluZGlxdWUgdW4gbWVpbGxldXIgYWp1c3RlbWVudCBkdSBtb2TDqGxlIGF1eCBkb25uw6llcy4NCg0KIyBPbiBvYnNlcnZlIHF1ZSBsYSBkw6l2aWFuY2UgZGUgQmVybm91bGxpIGRpbWludWUgZGUgbWFuacOocmUgY29udGludWUgdG91dCBhdSBsb25nIGRlcyBpdMOpcmF0aW9ucywgY2UgcXVpIHNpZ25pZmllIHF1ZSBsZSBtb2TDqGxlIHMnYW3DqWxpb3JlIHByb2dyZXNzaXZlbWVudCBlbiBhanVzdGFudCBkZSBtaWV1eCBlbiBtaWV1eCBsZXMgZG9ubsOpZXMuIExlcyBjb3VyYmVzIHBvdXIgbGVzIGRvbm7DqWVzIGQnYXBwcmVudGlzc2FnZSBldCBkZSB2YWxpZGF0aW9uICh0ZXN0KSBzdWl2ZW50IHVuZSB0cmFqZWN0b2lyZSBkZXNjZW5kYW50ZSBzaW1pbGFpcmUsIG1vbnRyYW50IHVuZSBhbcOpbGlvcmF0aW9uIGNvbnN0YW50ZSBkZSBsYSBwZXJmb3JtYW5jZSBkdSBtb2TDqGxlLg0KDQojQ2VwZW5kYW50LCB2ZXJzIGxhIGZpbiBkZXMgaXTDqXJhdGlvbnMsIGxlcyBjb3VyYmVzIGNvbW1lbmNlbnQgw6AgY29udmVyZ2VyIGV0IGRldmllbm5lbnQgcHJlc3F1ZSBob3Jpem9udGFsZXMsIGluZGlxdWFudCBxdWUgbCdhanVzdGVtZW50IHN1cHBsw6ltZW50YWlyZSBvZmZlcnQgcGFyIGxlcyBub3V2ZWxsZXMgaXTDqXJhdGlvbnMgZGV2aWVudCBtaW5pbWUuIMOAIGNlIHBvaW50LCBham91dGVyIGRhdmFudGFnZSBkJ2l0w6lyYXRpb25zIG4nYW3DqWxpb3JlIHF1ZSB0csOocyBwZXUgbGEgcGVyZm9ybWFuY2UgZHUgbW9kw6hsZSBldCBwb3VycmFpdCBtw6ptZSBlbnRyYcOubmVyIHVuIHN1cmFwcHJlbnRpc3NhZ2Ugc2kgbGUgbW9kw6hsZSBjb21tZW5jZSDDoCBtw6ltb3Jpc2VyIGxlIGJydWl0IGRhbnMgbGVzIGRvbm7DqWVzIGQnYXBwcmVudGlzc2FnZS4NCg0KI0xhIGxpZ25lIHZlcnRpY2FsZSBibGV1ZSBlbiBwb2ludGlsbMOpcyDDoCBsYSAxMDAwZSBpdMOpcmF0aW9uIG1hcnF1ZSBsYSBmaW4gZGVzIGl0w6lyYXRpb25zIGTDqWZpbmllcyBwb3VyIGwnZW50cmHDrm5lbWVudCBkdSBtb2TDqGxlLiBMZSBtb2TDqGxlIGEgw6l0w6kgY29uZmlndXLDqSBwb3VyIHMnYXJyw6p0ZXIgw6AgY2V0dGUgbGltaXRlLCBwcm9iYWJsZW1lbnQgZW4gcmFpc29uIGRlcyBjb250cmFpbnRlcyBkZSBjYWxjdWwgb3UgcG91ciDDqXZpdGVyIHVuIHN1cmFwcHJlbnRpc3NhZ2UgZXhjZXNzaWYuDQoNCmBgYA0KDQpgYGB7ciBDZWxsdWxlIDY6IFV0aWxpc2F0aW9uIGRlIEdyYWRpZW50IEJvb3N0aW5nIChtZWlsbGV1cnMgcGFyYW3DqHRyZXMgcGFyIHZhbGlkYXRpb24gY3JvaXPDqWUpfQ0KIyBWYWxpZGF0aW9uIGNyb2lzw6llIHNpbXBsaWZpw6llIHBvdXIgR3JhZGllbnQgQm9vc3RpbmcgZW4gdmFyaWFudCBsZXMgYXV0cmVzIHBhcmFtw6h0cmVzDQoNCiMgT24gdXRpbGlzZSBzZXQuc2VlZCgxMjMpIHBvdXIgcXVlIG5vcyByw6lzdWx0YXRzIHNvaWVudCByZXByb2R1Y3RpYmxlcw0Kc2V0LnNlZWQoMTIzKQ0KDQojIE9uIGTDqWZpbml0IGxlcyBwYXJhbcOodHJlcyBkZSBsYSB2YWxpZGF0aW9uIGNyb2lzw6llDQojIHRyYWluQ29udHJvbChtZXRob2QgPSAiY3YiLCBudW1iZXIgPSAzKSBzaWduaWZpZSBxdSdvbiB1dGlsaXNlIHVuZSB2YWxpZGF0aW9uIGNyb2lzw6llIGF2ZWMgMyBwbGlzDQojIENlbGEgdmV1dCBkaXJlIHF1J29uIGRpdmlzZSBsZXMgZG9ubsOpZXMgZW4gMyBwYXJ0aWVzIGV0IHF1J29uIGVudHJhw65uZSBsZSBtb2TDqGxlIDMgZm9pcywgY2hhcXVlIGZvaXMgZW4gdXRpbGlzYW50IHVuZSBwYXJ0aWUgZGlmZsOpcmVudGUgcG91ciB0ZXN0ZXINCmN0cmwgPC0gdHJhaW5Db250cm9sKG1ldGhvZCA9ICJjdiIsIG51bWJlciA9IDMpDQoNCiMgT24gZMOpZmluaXQgdW5lIGdyaWxsZSBkZSBwYXJhbcOodHJlcyDDoCB0ZXN0ZXINCiMgZXhwYW5kLmdyaWQoKSBjcsOpZSB0b3V0ZXMgbGVzIGNvbWJpbmFpc29ucyBwb3NzaWJsZXMgZGVzIHBhcmFtw6h0cmVzIHF1J29uIHZldXQgdGVzdGVyDQojIG4udHJlZXMgPSAxMDAwIHNpZ25pZmllIHF1J29uIHZhIHRvdWpvdXJzIHV0aWxpc2VyIDEwMDAgYXJicmVzDQojIGludGVyYWN0aW9uLmRlcHRoID0gYygxLCAzLCA1KSBzaWduaWZpZSBxdSdvbiB2YSB0ZXN0ZXIgZGVzIHByb2ZvbmRldXJzIGRlIDEsIDMgZXQgNSBwb3VyIGxlcyBhcmJyZXMNCiMgc2hyaW5rYWdlID0gYygwLjAxLCAwLjEsIDAuMykgc2lnbmlmaWUgcXUnb24gdmEgdGVzdGVyIGRlcyB2aXRlc3NlcyBkJ2FwcHJlbnRpc3NhZ2UgZGUgMC4wMSwgMC4xIGV0IDAuMw0KIyBuLm1pbm9ic2lubm9kZSA9IGMoMTAsIDIwKSBzaWduaWZpZSBxdSdvbiB2YSB0ZXN0ZXIgZGVzIG1pbmltdW1zIGRlIDEwIGV0IDIwIG9ic2VydmF0aW9ucyBwYXIgbsWTdWQNCmdyaWQgPC0gZXhwYW5kLmdyaWQobi50cmVlcyA9IDEwMDAsIGludGVyYWN0aW9uLmRlcHRoID0gYygxLCAzLCA1KSwgDQogICAgICAgICAgICAgICAgICAgIHNocmlua2FnZSA9IGMoMC4wMSwgMC4xLCAwLjMpLCBuLm1pbm9ic2lubm9kZSA9IGMoMTAsIDIwKSkNCg0KIyBPbiBkw6ltYXJyZSBsZSBjaHJvbm9tw6h0cmUNCnN0YXJ0X3RpbWUgPC0gU3lzLnRpbWUoKQ0KDQojIE9uIGVudHJhw65uZSBsZSBtb2TDqGxlIGVuIHV0aWxpc2FudCBsYSB2YWxpZGF0aW9uIGNyb2lzw6llIGV0IGxhIGdyaWxsZSBkZSBwYXJhbcOodHJlcw0KIyB0cmFpbigpIGNyw6llIGV0IGVudHJhw65uZSBsZSBtb2TDqGxlDQojIHR5cGUgfiAuIHNpZ25pZmllIHF1J29uIHV0aWxpc2UgdG91dGVzIGxlcyBjb2xvbm5lcyBwb3VyIHByw6lkaXJlIGxhIGNvbG9ubmUgInR5cGUiDQojIGRhdGEgPSB0cmFpbkRhdGEgc2lnbmlmaWUgcXUnb24gdXRpbGlzZSBsZXMgZG9ubsOpZXMgZCdhcHByZW50aXNzYWdlDQojIG1ldGhvZCA9ICJnYm0iIHNpZ25pZmllIHF1J29uIHV0aWxpc2UgbGUgbW9kw6hsZSBkZSBHcmFkaWVudCBCb29zdGluZw0KIyB0ckNvbnRyb2wgPSBjdHJsIHNpZ25pZmllIHF1J29uIHV0aWxpc2UgbGVzIHBhcmFtw6h0cmVzIGRlIHZhbGlkYXRpb24gY3JvaXPDqWUgZMOpZmluaXMgcGx1cyB0w7R0DQojIHR1bmVHcmlkID0gZ3JpZCBzaWduaWZpZSBxdSdvbiB1dGlsaXNlIGxhIGdyaWxsZSBkZSBwYXJhbcOodHJlcyBkw6lmaW5pZSBwbHVzIHTDtHQNCiMgdmVyYm9zZSA9IEZBTFNFIHNpZ25pZmllIHF1J29uIG5lIHZldXQgcGFzIGFmZmljaGVyIGRlcyBtZXNzYWdlcyBkw6l0YWlsbMOpcyBwZW5kYW50IGwnZW50cmHDrm5lbWVudA0KZ2JtX2N2IDwtIHRyYWluKHR5cGUgfiAuLCBkYXRhID0gdHJhaW5EYXRhLCBtZXRob2QgPSAiZ2JtIiwgdHJDb250cm9sID0gY3RybCwgDQogICAgICAgICAgICAgICAgdHVuZUdyaWQgPSBncmlkLCB2ZXJib3NlID0gRkFMU0UpDQoNCiMgT24gYXJyw6p0ZSBsZSBjaHJvbm9tw6h0cmUNCmVuZF90aW1lIDwtIFN5cy50aW1lKCkNCg0KIyBPbiBjYWxjdWxlIGxlIHRlbXBzIHRvdGFsIGQnZXjDqWN1dGlvbiBwb3VyIGxhIHZhbGlkYXRpb24gY3JvaXPDqWUNCmdibV90aW1lX2N2IDwtIGVuZF90aW1lIC0gc3RhcnRfdGltZQ0KDQojIE1laWxsZXVycyBwYXJhbcOodHJlcw0KIyBiZXN0VHVuZSBjb250aWVudCBsZXMgbWVpbGxldXJzIHBhcmFtw6h0cmVzIHRyb3V2w6lzIHBhciBsYSB2YWxpZGF0aW9uIGNyb2lzw6llDQpiZXN0X2dibSA8LSBnYm1fY3YkYmVzdFR1bmUNCmJlc3RfZ2JtDQoNCiMgRW50cmHDrm5lbWVudCBkdSBtb2TDqGxlIGF2ZWMgbGVzIG1laWxsZXVycyBwYXJhbcOodHJlcw0KDQojIE9uIGTDqW1hcnJlIGxlIGNocm9ub23DqHRyZQ0Kc3RhcnRfdGltZSA8LSBTeXMudGltZSgpDQoNCiMgT24gY3LDqWUgdW4gbW9kw6hsZSBkZSBHcmFkaWVudCBCb29zdGluZyBhdmVjIGxlcyBtZWlsbGV1cnMgcGFyYW3DqHRyZXMgdHJvdXbDqXMgcGFyIGxhIHZhbGlkYXRpb24gY3JvaXPDqWUNCiMgZGlzdHJpYnV0aW9uID0gImJlcm5vdWxsaSIgc2lnbmlmaWUgcXUnb24gdXRpbGlzZSB1bmUgZGlzdHJpYnV0aW9uIGRlIEJlcm5vdWxsaSBwb3VyIGxlcyBkb25uw6llcyBiaW5haXJlcw0KIyBjdi5mb2xkcyA9IDMgc2lnbmlmaWUgcXUnb24gdXRpbGlzZSB1bmUgdmFsaWRhdGlvbiBjcm9pc8OpZSBhdmVjIDMgcGxpcw0KZ2JtX21vZGVsX2Jlc3QgPC0gZ2JtKHR5cGUgfiAuLCBkYXRhID0gdHJhaW5EYXRhLCBkaXN0cmlidXRpb24gPSAiYmVybm91bGxpIiwgDQogICAgICAgICAgICAgICAgICAgICAgbi50cmVlcyA9IGJlc3RfZ2JtJG4udHJlZXMsIGludGVyYWN0aW9uLmRlcHRoID0gYmVzdF9nYm0kaW50ZXJhY3Rpb24uZGVwdGgsIA0KICAgICAgICAgICAgICAgICAgICAgIHNocmlua2FnZSA9IGJlc3RfZ2JtJHNocmlua2FnZSwgbi5taW5vYnNpbm5vZGUgPSBiZXN0X2dibSRuLm1pbm9ic2lubm9kZSwgDQogICAgICAgICAgICAgICAgICAgICAgY3YuZm9sZHMgPSAzKQ0KDQojIE9uIGFycsOqdGUgbGUgY2hyb25vbcOodHJlDQplbmRfdGltZSA8LSBTeXMudGltZSgpDQoNCiMgT24gY2FsY3VsZSBsZSB0ZW1wcyB0b3RhbCBkJ2V4w6ljdXRpb24gcG91ciBlbnRyYcOubmVyIGxlIG1vZMOobGUgYXZlYyBsZXMgbWVpbGxldXJzIHBhcmFtw6h0cmVzDQpnYm1fdGltZV9iZXN0IDwtIGVuZF90aW1lIC0gc3RhcnRfdGltZQ0KDQojIENhbGN1bCBkZXMgdGF1eCBkJ2VycmV1ciBkJ2FwcHJlbnRpc3NhZ2UgZXQgZGUgZ8OpbsOpcmFsaXNhdGlvbg0KDQojIE9uIHV0aWxpc2UgcHJlZGljdCgpIHBvdXIgZmFpcmUgZGVzIHByw6lkaWN0aW9ucyBzdXIgbGVzIGRvbm7DqWVzIGQnYXBwcmVudGlzc2FnZSBhdmVjIGxlcyBtZWlsbGV1cnMgcGFyYW3DqHRyZXMNCmdibV90cmFpbl9wcmVkX2Jlc3QgPC0gcHJlZGljdChnYm1fbW9kZWxfYmVzdCwgbmV3ZGF0YSA9IHRyYWluRGF0YSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbi50cmVlcyA9IGJlc3RfZ2JtJG4udHJlZXMsIHR5cGUgPSAicmVzcG9uc2UiKQ0KDQojIE9uIGZhaXQgZGUgbcOqbWUgcG91ciBsZXMgZG9ubsOpZXMgZGUgdGVzdA0KZ2JtX3Rlc3RfcHJlZF9iZXN0IDwtIHByZWRpY3QoZ2JtX21vZGVsX2Jlc3QsIG5ld2RhdGEgPSB0ZXN0RGF0YSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuLnRyZWVzID0gYmVzdF9nYm0kbi50cmVlcywgdHlwZSA9ICJyZXNwb25zZSIpDQoNCiMgQ29udmVyc2lvbiBkZXMgcHLDqWRpY3Rpb25zIGVuIGNsYXNzZXMgYmluYWlyZXMNCiMgT24gY29udmVydGl0IGxlcyBwcsOpZGljdGlvbnMgZW4gdmFsZXVycyBiaW5haXJlcyAoMCBvdSAxKQ0KIyBTaSBsYSBwcsOpZGljdGlvbiBlc3Qgc3Vww6lyaWV1cmUgw6AgMC41LCBvbiBsYSBjb25zaWTDqHJlIGNvbW1lIDEgKHNwYW0pLCBzaW5vbiBjb21tZSAwIChub25zcGFtKQ0KZ2JtX3RyYWluX2NsYXNzX2Jlc3QgPC0gaWZlbHNlKGdibV90cmFpbl9wcmVkX2Jlc3QgPiAwLjUsIDEsIDApDQpnYm1fdGVzdF9jbGFzc19iZXN0IDwtIGlmZWxzZShnYm1fdGVzdF9wcmVkX2Jlc3QgPiAwLjUsIDEsIDApDQoNCiMgQ2FsY3VsIGRlcyBlcnJldXJzDQojIE9uIGNvbXBhcmUgbGVzIHByw6lkaWN0aW9ucyBhdmVjIGxlcyB2cmFpZXMgdmFsZXVycyBwb3VyIGNhbGN1bGVyIGxlIHRhdXggZCdlcnJldXINCmdibV90cmFpbl9lcnJvcl9iZXN0IDwtIG1lYW4oZ2JtX3RyYWluX2NsYXNzX2Jlc3QgIT0gdHJhaW5EYXRhJHR5cGUpDQpnYm1fdGVzdF9lcnJvcl9iZXN0IDwtIG1lYW4oZ2JtX3Rlc3RfY2xhc3NfYmVzdCAhPSB0ZXN0RGF0YSR0eXBlKQ0KDQojIEFmZmljaGFnZSBkZXMgcsOpc3VsdGF0cw0KIyBPbiBhZmZpY2hlIGxlIHRhdXggZCdlcnJldXIgc3VyIGxlcyBkb25uw6llcyBkJ2FwcHJlbnRpc3NhZ2UNCmdibV90cmFpbl9lcnJvcl9iZXN0DQoNCiMgTGUgcHJlbWllciBub21icmUgKDAuMDQ2NDQyMTUpIGVzdCBsZSB0YXV4IGQnZXJyZXVyIHN1ciBsZXMgZG9ubsOpZXMgZCdhcHByZW50aXNzYWdlIGF2ZWMgbGVzIG1laWxsZXVycyBwYXJhbcOodHJlcy4NCiMgQ2VsYSBzaWduaWZpZSBxdWUgbGUgbW9kw6hsZSBhIGZhaXQgdW5lIGVycmV1ciBlbnZpcm9uIDQuNjQlIGR1IHRlbXBzIHN1ciBsZXMgZG9ubsOpZXMgZCdhcHByZW50aXNzYWdlLg0KDQojIE9uIGFmZmljaGUgbGUgdGF1eCBkJ2VycmV1ciBzdXIgbGVzIGRvbm7DqWVzIGRlIHRlc3QNCmdibV90ZXN0X2Vycm9yX2Jlc3QNCg0KIyBMZSBkZXV4acOobWUgbm9tYnJlICgwLjA1MzMxODgyKSBlc3QgbGUgdGF1eCBkJ2VycmV1ciBzdXIgbGVzIGRvbm7DqWVzIGRlIHRlc3QgYXZlYyBsZXMgbWVpbGxldXJzIHBhcmFtw6h0cmVzLg0KIyBDZWxhIHNpZ25pZmllIHF1ZSBsZSBtb2TDqGxlIGEgZmFpdCB1bmUgZXJyZXVyIGVudmlyb24gNS4zMyUgZHUgdGVtcHMgc3VyIGxlcyBkb25uw6llcyBkZSB0ZXN0Lg0KDQojIE9uIGFmZmljaGUgbGUgdGVtcHMgdG90YWwgZCdleMOpY3V0aW9uIHBvdXIgZW50cmHDrm5lciBsZSBtb2TDqGxlIGF2ZWMgbGVzIG1laWxsZXVycyBwYXJhbcOodHJlcw0KZ2JtX3RpbWVfYmVzdA0KDQojIExlIHRyb2lzacOobWUgcsOpc3VsdGF0IChUaW1lIGRpZmZlcmVuY2Ugb2YgMjkuNzA2NTggc2VjcykgaW5kaXF1ZSBxdWUgbGUgdGVtcHMgdG90YWwgDQojIHBvdXIgZW50cmHDrm5lciBsZSBtb2TDqGxlIEdyYWRpZW50IEJvb3N0aW5nIGF2ZWMgbGVzIG1laWxsZXVycyBwYXJhbcOodHJlcyBhIMOpdMOpIGQnZW52aXJvbiAyOS43MCBzZWNvbmRlcy4NCmBgYA0KDQojIyBYR0Jvb3N0DQoNCmBgYHtyIENlbGx1bGUgNzogVXRpbGlzYXRpb24gZGUgWEdCb29zdCAobW9kw6hsZSBwYXIgZMOpZmF1dCl9DQojIFByw6lwYXJhdGlvbiBkZXMgZG9ubsOpZXMgcG91ciBYR0Jvb3N0DQoNCiMgT24gcHLDqXBhcmUgbGVzIGRvbm7DqWVzIHBvdXIgbGUgbW9kw6hsZSBYR0Jvb3N0DQojIExlIG1vZMOobGUgWEdCb29zdCBuw6ljZXNzaXRlIHF1ZSBsYSB2YXJpYWJsZSBjaWJsZSBzb2l0IGRlcyB2YWxldXJzIG51bcOpcmlxdWVzIDAgZXQgMQ0KIyBPbiBjb252ZXJ0aXQgZG9uYyBsZXMgdmFsZXVycyAxIGVuIDEgKHNwYW0pIGV0IGxlcyB2YWxldXJzIDAgZW4gMCAobm9uc3BhbSkgcG91ciBsZXMgZG9ubsOpZXMgZCdhcHByZW50aXNzYWdlDQp0cmFpbkRhdGEkdHlwZSA8LSBpZmVsc2UodHJhaW5EYXRhJHR5cGUgPT0gMSwgMSwgMCkNCiMgT24gZmFpdCBkZSBtw6ptZSBwb3VyIGxlcyBkb25uw6llcyBkZSB0ZXN0DQp0ZXN0RGF0YSR0eXBlIDwtIGlmZWxzZSh0ZXN0RGF0YSR0eXBlID09IDEsIDEsIDApDQoNCiMgT24gY3LDqWUgZGVzIG1hdHJpY2VzIGRlIGRvbm7DqWVzIHNww6ljaWFsZXMgcG91ciBYR0Jvb3N0DQojIHhnYi5ETWF0cml4KCkgZXN0IHVuZSBmb25jdGlvbiBxdWkgY3LDqWUgdW5lIHN0cnVjdHVyZSBkZSBkb25uw6llcyBvcHRpbWlzw6llIHBvdXIgWEdCb29zdA0KIyBkYXRhID0gYXMubWF0cml4KHRyYWluRGF0YVssLW5jb2wodHJhaW5EYXRhKV0pIHNpZ25pZmllIHF1J29uIHV0aWxpc2UgdG91dGVzIGxlcyBjb2xvbm5lcyBzYXVmIGxhIGRlcm5pw6hyZQ0KIyBsYWJlbCA9IHRyYWluRGF0YSR0eXBlIHNpZ25pZmllIHF1J29uIHV0aWxpc2UgbGEgY29sb25uZSAidHlwZSIgY29tbWUgw6l0aXF1ZXR0ZSAoc3BhbSBvdSBub25zcGFtKQ0KdHJhaW5fbWF0cml4IDwtIHhnYi5ETWF0cml4KGRhdGEgPSBhcy5tYXRyaXgodHJhaW5EYXRhWywtbmNvbCh0cmFpbkRhdGEpXSksIGxhYmVsID0gdHJhaW5EYXRhJHR5cGUpDQp0ZXN0X21hdHJpeCA8LSB4Z2IuRE1hdHJpeChkYXRhID0gYXMubWF0cml4KHRlc3REYXRhWywtbmNvbCh0ZXN0RGF0YSldKSwgbGFiZWwgPSB0ZXN0RGF0YSR0eXBlKQ0KDQojIEVudHJhw65uZW1lbnQgZHUgbW9kw6hsZSBYR0Jvb3N0IGF2ZWMgMTAwIGl0w6lyYXRpb25zDQpzdGFydF90aW1lIDwtIFN5cy50aW1lKCkgICMgRMOpbWFycmFnZSBkdSBjaHJvbm9tw6h0cmUNCnNldC5zZWVkKDEyMykgICMgUG91ciBsYSByZXByb2R1Y3RpYmlsaXTDqQ0KIyBPbiBjcsOpZSBldCBlbnRyYcOubmUgbGUgbW9kw6hsZSBYR0Jvb3N0IGVuIHV0aWxpc2FudCB4Z2IudHJhaW4oKQ0KIyBkYXRhID0gdHJhaW5fbWF0cml4IHNpZ25pZmllIHF1J29uIHV0aWxpc2UgbGVzIGRvbm7DqWVzIGQnYXBwcmVudGlzc2FnZQ0KIyBucm91bmRzID0gMTAwIHNpZ25pZmllIHF1J29uIGZhaXQgMTAwIGl0w6lyYXRpb25zDQojIG9iamVjdGl2ZSA9ICJiaW5hcnk6bG9naXN0aWMiIHNpZ25pZmllIHF1J29uIHV0aWxpc2UgdW5lIHLDqWdyZXNzaW9uIGxvZ2lzdGlxdWUgYmluYWlyZQ0KIyBtYXhfZGVwdGggPSAzIHNpZ25pZmllIHF1J29uIHBlcm1ldCBhdXggYXJicmVzIGQnYXZvaXIgdW5lIHByb2ZvbmRldXIgZGUgMw0KIyBldGEgPSAwLjEgZXN0IHVuIHBhcmFtw6h0cmUgcXVpIGNvbnRyw7RsZSBsYSB2aXRlc3NlIGQnYXBwcmVudGlzc2FnZQ0KIyB3YXRjaGxpc3QgPSBsaXN0KHRyYWluID0gdHJhaW5fbWF0cml4LCB0ZXN0ID0gdGVzdF9tYXRyaXgpIHNpZ25pZmllIHF1J29uIHN1aXQgbGVzIGVycmV1cnMgc3VyIGxlcyBkb25uw6llcyBkJ2FwcHJlbnRpc3NhZ2UgZXQgZGUgdGVzdA0KIyBldmFsX21ldHJpYyA9ICJsb2dsb3NzIiBzaWduaWZpZSBxdSdvbiB1dGlsaXNlIGxhIHBlcnRlIGxvZ2FyaXRobWlxdWUgcG91ciDDqXZhbHVlciBsZXMgZXJyZXVycw0KIyB2ZXJib3NlID0gMCBzaWduaWZpZSBxdSdvbiBuZSB2ZXV0IHBhcyBhZmZpY2hlciBkZXMgbWVzc2FnZXMgZMOpdGFpbGzDqXMgcGVuZGFudCBsJ2VudHJhw65uZW1lbnQNCnhnYm9vc3RfbW9kZWwgPC0geGdiLnRyYWluKA0KICBkYXRhID0gdHJhaW5fbWF0cml4LA0KICBucm91bmRzID0gMTAwLA0KICBvYmplY3RpdmUgPSAiYmluYXJ5OmxvZ2lzdGljIiwNCiAgbWF4X2RlcHRoID0gMywNCiAgZXRhID0gMC4xLA0KICB3YXRjaGxpc3QgPSBsaXN0KHRyYWluID0gdHJhaW5fbWF0cml4LCB0ZXN0ID0gdGVzdF9tYXRyaXgpLA0KICBldmFsX21ldHJpYyA9ICJsb2dsb3NzIiwNCiAgdmVyYm9zZSA9IDANCikNCmVuZF90aW1lIDwtIFN5cy50aW1lKCkgICMgQXJyw6p0IGR1IGNocm9ub23DqHRyZQ0KIyBPbiBjYWxjdWxlIGxlIHRlbXBzIHRvdGFsIGQnZXjDqWN1dGlvbiBwb3VyIGVudHJhw65uZXIgbGUgbW9kw6hsZQ0KeGdib29zdF90aW1lX2RlZmF1bHQgPC0gZW5kX3RpbWUgLSBzdGFydF90aW1lDQoNCiMgUsOpY3Vww6lyYXRpb24gZGVzIGVycmV1cnMgZCdhcHByZW50aXNzYWdlIGV0IGRlIHRlc3QNCiMgZXZhbHVhdGlvbl9sb2cgY29udGllbnQgbGVzIGVycmV1cnMgcG91ciBjaGFxdWUgaXTDqXJhdGlvbg0KZXZhbHVhdGlvbl9sb2cgPC0geGdib29zdF9tb2RlbCRldmFsdWF0aW9uX2xvZw0KDQojIENhbGN1bCBkZXMgZXJyZXVycyBmaW5hbGVzDQojIE9uIHV0aWxpc2UgcHJlZGljdCgpIHBvdXIgZmFpcmUgZGVzIHByw6lkaWN0aW9ucyBzdXIgbGVzIGRvbm7DqWVzIGQnYXBwcmVudGlzc2FnZQ0KeGdib29zdF90cmFpbl9wcmVkIDwtIHByZWRpY3QoeGdib29zdF9tb2RlbCwgbmV3ZGF0YSA9IHRyYWluX21hdHJpeCkNCiMgT24gZmFpdCBkZSBtw6ptZSBwb3VyIGxlcyBkb25uw6llcyBkZSB0ZXN0DQp4Z2Jvb3N0X3Rlc3RfcHJlZCA8LSBwcmVkaWN0KHhnYm9vc3RfbW9kZWwsIG5ld2RhdGEgPSB0ZXN0X21hdHJpeCkNCg0KIyBDb252ZXJzaW9uIGRlcyBwcsOpZGljdGlvbnMgZW4gY2xhc3NlcyBiaW5haXJlcw0KIyBPbiBjb252ZXJ0aXQgbGVzIHByw6lkaWN0aW9ucyBlbiB2YWxldXJzIGJpbmFpcmVzICgwIG91IDEpDQojIFNpIGxhIHByw6lkaWN0aW9uIGVzdCBzdXDDqXJpZXVyZSDDoCAwLjUsIG9uIGxhIGNvbnNpZMOocmUgY29tbWUgMSAoc3BhbSksIHNpbm9uIGNvbW1lIDAgKG5vbnNwYW0pDQp4Z2Jvb3N0X3RyYWluX2NsYXNzIDwtIGlmZWxzZSh4Z2Jvb3N0X3RyYWluX3ByZWQgPiAwLjUsIDEsIDApDQp4Z2Jvb3N0X3Rlc3RfY2xhc3MgPC0gaWZlbHNlKHhnYm9vc3RfdGVzdF9wcmVkID4gMC41LCAxLCAwKQ0KDQojIENhbGN1bCBkZXMgZXJyZXVycw0KIyBPbiBjb21wYXJlIGxlcyBwcsOpZGljdGlvbnMgYXZlYyBsZXMgdnJhaWVzIHZhbGV1cnMgcG91ciBjYWxjdWxlciBsZSB0YXV4IGQnZXJyZXVyDQp4Z2Jvb3N0X3RyYWluX2Vycm9yIDwtIG1lYW4oeGdib29zdF90cmFpbl9jbGFzcyAhPSB0cmFpbkRhdGEkdHlwZSkNCnhnYm9vc3RfdGVzdF9lcnJvciA8LSBtZWFuKHhnYm9vc3RfdGVzdF9jbGFzcyAhPSB0ZXN0RGF0YSR0eXBlKQ0KDQojIEFmZmljaGFnZSBkZXMgZXJyZXVycyBmaW5hbGVzDQojIE9uIGFmZmljaGUgbGUgdGF1eCBkJ2VycmV1ciBzdXIgbGVzIGRvbm7DqWVzIGQnYXBwcmVudGlzc2FnZQ0KeGdib29zdF90cmFpbl9lcnJvcg0KDQojIExlIHByZW1pZXIgbm9tYnJlICgwLjA0MTgyNTEpIGVzdCBsZSB0YXV4IGQnZXJyZXVyIHN1ciBsZXMgZG9ubsOpZXMgZCdhcHByZW50aXNzYWdlLg0KIyBDZWxhIHNpZ25pZmllIHF1ZSBsZSBtb2TDqGxlIGEgZmFpdCB1bmUgZXJyZXVyIGVudmlyb24gNC4xOCUgZHUgdGVtcHMgc3VyIGxlcyBkb25uw6llcyBkJ2FwcHJlbnRpc3NhZ2UuDQoNCiMgT24gYWZmaWNoZSBsZSB0YXV4IGQnZXJyZXVyIHN1ciBsZXMgZG9ubsOpZXMgZGUgdGVzdA0KeGdib29zdF90ZXN0X2Vycm9yDQoNCiMgTGUgZGV1eGnDqG1lIG5vbWJyZSAoMC4wNTc2NzEzOCkgZXN0IGxlIHRhdXggZCdlcnJldXIgc3VyIGxlcyBkb25uw6llcyBkZSB0ZXN0Lg0KIyBDZWxhIHNpZ25pZmllIHF1ZSBsZSBtb2TDqGxlIGEgZmFpdCB1bmUgZXJyZXVyIGVudmlyb24gNS43NyUgZHUgdGVtcHMgc3VyIGxlcyBkb25uw6llcyBkZSB0ZXN0Lg0KDQojIE9uIGFmZmljaGUgbGUgdGVtcHMgdG90YWwgZCdleMOpY3V0aW9uIHBvdXIgZW50cmHDrm5lciBsZSBtb2TDqGxlDQp4Z2Jvb3N0X3RpbWVfZGVmYXVsdA0KDQojIExlIHRyb2lzacOobWUgcsOpc3VsdGF0IChUaW1lIGRpZmZlcmVuY2Ugb2YgMC41NTY3MzEgc2VjcykgaW5kaXF1ZSBxdWUgbGUgdGVtcHMgdG90YWwgDQojIHBvdXIgZW50cmHDrm5lciBsZSBtb2TDqGxlIFhHQm9vc3QgYXZlYyAxMDAgaXTDqXJhdGlvbnMgYSDDqXTDqSBkJ2Vudmlyb24gMC41NiBzZWNvbmRlcy4NCg0KIyBUcmHDp2FnZSBkZXMgY291cmJlcyBkJ2VycmV1cnMNCiMgcGxvdCgpIGNyw6llIHVuIGdyYXBoaXF1ZSBtb250cmFudCBsJ8Opdm9sdXRpb24gZGVzIGVycmV1cnMNCiMgdHlwZSA9ICJsIiBzaWduaWZpZSBxdSdvbiB0cmFjZSB1bmUgbGlnbmUNCiMgY29sID0gImJsdWUiIHNpZ25pZmllIHF1ZSBsYSBsaWduZSBlc3QgYmxldWUgcG91ciBsZXMgZXJyZXVycyBkJ2FwcHJlbnRpc3NhZ2UNCiMgeWxpbSA9IGMoMCwgbWF4KGV2YWx1YXRpb25fbG9nJHRyYWluX2xvZ2xvc3MsIGV2YWx1YXRpb25fbG9nJHRlc3RfbG9nbG9zcykpIGTDqWZpbml0IGxlcyBsaW1pdGVzIGRlIGwnYXhlIFkgcG91ciB1bmUgbWVpbGxldXJlIHZpc3VhbGlzYXRpb24NCiMgeWxhYiA9ICJMb2cgTG9zcyIgZG9ubmUgdW4gdGl0cmUgw6AgbCdheGUgWQ0KIyB4bGFiID0gIkl0ZXJhdGlvbiIgZG9ubmUgdW4gdGl0cmUgw6AgbCdheGUgWA0KIyBtYWluID0gIsOJdm9sdXRpb24gZGVzIGVycmV1cnMgYXZlYyBYR0Jvb3N0IiBkb25uZSB1biB0aXRyZSBhdSBncmFwaGlxdWUNCnBsb3QoZXZhbHVhdGlvbl9sb2ckaXRlciwgZXZhbHVhdGlvbl9sb2ckdHJhaW5fbG9nbG9zcywgdHlwZSA9ICJsIiwgY29sID0gImJsdWUiLCANCiAgICAgeWxpbSA9IGMoMCwgbWF4KGV2YWx1YXRpb25fbG9nJHRyYWluX2xvZ2xvc3MsIGV2YWx1YXRpb25fbG9nJHRlc3RfbG9nbG9zcykpLCANCiAgICAgeWxhYiA9ICJMb2cgTG9zcyIsIHhsYWIgPSAiSXRlcmF0aW9uIiwgDQogICAgIG1haW4gPSAiw4l2b2x1dGlvbiBkZXMgZXJyZXVycyBhdmVjIFhHQm9vc3QiKQ0KIyBsaW5lcygpIGFqb3V0ZSB1bmUgbGlnbmUgcm91Z2UgcG91ciBsZXMgZXJyZXVycyBkZSB0ZXN0DQpsaW5lcyhldmFsdWF0aW9uX2xvZyRpdGVyLCBldmFsdWF0aW9uX2xvZyR0ZXN0X2xvZ2xvc3MsIGNvbCA9ICJyZWQiKQ0KIyBsZWdlbmQoKSBham91dGUgdW5lIGzDqWdlbmRlIGF1IGdyYXBoaXF1ZSBwb3VyIGV4cGxpcXVlciBsZXMgY291bGV1cnMNCmxlZ2VuZCgidG9wcmlnaHQiLCBsZWdlbmQgPSBjKCJFcnJldXIgZCdhcHByZW50aXNzYWdlIiwgIkVycmV1ciBkZSB0ZXN0IiksIA0KICAgICAgIGNvbCA9IGMoImJsdWUiLCAicmVkIiksIGx0eSA9IDEpDQoNCiMgVmlzdWFsaXNhdGlvbiBkZSBsJ2ltcG9ydGFuY2UgZGVzIHZhcmlhYmxlcw0KIyB4Z2IuaW1wb3J0YW5jZSgpIGNhbGN1bGUgbCdpbXBvcnRhbmNlIGRlIGNoYXF1ZSB2YXJpYWJsZSBkYW5zIGxlIG1vZMOobGUNCmltcG9ydGFuY2VfbWF0cml4IDwtIHhnYi5pbXBvcnRhbmNlKG1vZGVsID0geGdib29zdF9tb2RlbCkNCiMgeGdiLnBsb3QuaW1wb3J0YW5jZSgpIGNyw6llIHVuIGdyYXBoaXF1ZSBtb250cmFudCBsJ2ltcG9ydGFuY2UgZGVzIHZhcmlhYmxlcw0KeGdiLnBsb3QuaW1wb3J0YW5jZShpbXBvcnRhbmNlX21hdHJpeCkNCg0KDQojIExlIHByZW1pZXIgZ3JhcGhpcXVlIG1vbnRyZSBsJ8Opdm9sdXRpb24gZGVzIGVycmV1cnMgZCdhcHByZW50aXNzYWdlIGV0IGRlIHRlc3QgYXUgZmlsIGRlcyAxMDAgaXTDqXJhdGlvbnMuIExhIGNvdXJiZSBibGV1ZSByZXByw6lzZW50ZSBsJ2VycmV1ciBkJ2FwcHJlbnRpc3NhZ2UsIHF1aSBkaW1pbnVlIHJhcGlkZW1lbnQgZXQgY29udGludWUgZGUgYmFpc3NlciBhdSBmaWwgZGVzIGl0w6lyYXRpb25zLCBjZSBxdWkgaW5kaXF1ZSBxdWUgbGUgbW9kw6hsZSBzJ2FkYXB0ZSBkZSBtaWV1eCBlbiBtaWV1eCBhdXggZG9ubsOpZXMgZCdlbnRyYcOubmVtZW50LiBMYSBjb3VyYmUgcm91Z2UsIHF1YW50IMOgIGVsbGUsIG1vbnRyZSBsJ2VycmV1ciBkZSB0ZXN0LCBxdWkgZGltaW51ZSDDqWdhbGVtZW50IG1haXMgdGVuZCDDoCBzdGFnbmVyIHZlcnMgbGEgZmluIGRlcyBpdMOpcmF0aW9ucy4gQ2V0dGUgc3RhZ25hdGlvbiBzdWdnw6hyZSBxdWUgbGUgbW9kw6hsZSBhdHRlaW50IHVuIHBvaW50IG/DuSBkZXMgaXTDqXJhdGlvbnMgc3VwcGzDqW1lbnRhaXJlcyBuJ2FwcG9ydGVudCBxdWUgcGV1IG91IHBhcyBkJ2Ftw6lsaW9yYXRpb25zIHN1cHBsw6ltZW50YWlyZXMgc3VyIGxlcyBkb25uw6llcyBkZSB0ZXN0LCBjZSBxdWkgZXN0IHVuIHNpZ25lIHBvc2l0aWYgZGUgYm9ubmUgZ8OpbsOpcmFsaXNhdGlvbiBzYW5zIHN1cmFwcHJlbnRpc3NhZ2UgZXhjZXNzaWYuDQoNCiMgTGUgZGV1eGnDqG1lIGdyYXBoaXF1ZSBtb250cmUgbCdpbXBvcnRhbmNlIGRlcyB2YXJpYWJsZXMgZGFucyBsZSBtb2TDqGxlLiBMZXMgdmFyaWFibGVzIGxlcyBwbHVzIGltcG9ydGFudGVzIHNvbnQgY2VsbGVzIGVuIGhhdXQgZHUgZ3JhcGhpcXVlLCBhdmVjICJjaGFyRXhjbGFtYXRpb24iIGV0ICJyZW1vdmUiIMOpdGFudCBsZXMgcGx1cyBpbmZsdWVudGVzLiBDZXMgdmFyaWFibGVzIGpvdWVudCB1biByw7RsZSBjcnVjaWFsIGRhbnMgbGVzIGTDqWNpc2lvbnMgcHJpc2VzIHBhciBsZSBtb2TDqGxlLg0KDQpgYGANCg0KYGBge3IgQ2VsbHVsZSA4OiBVdGlsaXNhdGlvbiBkZSBYR0Jvb3N0IChtZWlsbGV1cnMgcGFyYW3DqHRyZXMgcGFyIHZhbGlkYXRpb24gY3JvaXPDqWUpfQ0KIyBPbiB1dGlsaXNlIHNldC5zZWVkKDEyMykgcG91ciBxdWUgbm9zIHLDqXN1bHRhdHMgc29pZW50IHJlcHJvZHVjdGlibGVzDQpzZXQuc2VlZCgxMjMpDQoNCiMgT24gZMOpZmluaXQgbGVzIHBhcmFtw6h0cmVzIGRlIGxhIHZhbGlkYXRpb24gY3JvaXPDqWUNCiMgdHJhaW5Db250cm9sKG1ldGhvZCA9ICJjdiIsIG51bWJlciA9IDMpIHNpZ25pZmllIHF1J29uIHV0aWxpc2UgdW5lIHZhbGlkYXRpb24gY3JvaXPDqWUgYXZlYyAzIHBsaXMNCiMgQ2VsYSB2ZXV0IGRpcmUgcXUnb24gZGl2aXNlIGxlcyBkb25uw6llcyBlbiAzIHBhcnRpZXMgZXQgcXUnb24gZW50cmHDrm5lIGxlIG1vZMOobGUgMyBmb2lzLCBjaGFxdWUgZm9pcyBlbiB1dGlsaXNhbnQgdW5lIHBhcnRpZSBkaWZmw6lyZW50ZSBwb3VyIHRlc3Rlcg0KY3RybCA8LSB0cmFpbkNvbnRyb2wobWV0aG9kID0gImN2IiwgbnVtYmVyID0gMykNCg0KIyBPbiBkw6lmaW5pdCB1bmUgZ3JpbGxlIGRlIHBhcmFtw6h0cmVzIMOgIHRlc3Rlcg0KIyBleHBhbmQuZ3JpZCgpIGNyw6llIHRvdXRlcyBsZXMgY29tYmluYWlzb25zIHBvc3NpYmxlcyBkZXMgcGFyYW3DqHRyZXMgcXUnb24gdmV1dCB0ZXN0ZXINCiMgbnJvdW5kcyA9IGMoMTAwKSBzaWduaWZpZSBxdSdvbiB2YSB0b3Vqb3VycyB1dGlsaXNlciAxMDAgaXTDqXJhdGlvbnMNCiMgbWF4X2RlcHRoID0gYygzLCA1LCA3KSBzaWduaWZpZSBxdSdvbiB2YSB0ZXN0ZXIgZGVzIHByb2ZvbmRldXJzIGRlIDMsIDUgZXQgNyBwb3VyIGxlcyBhcmJyZXMNCiMgZXRhID0gYygwLjAxLCAwLjEsIDAuMykgc2lnbmlmaWUgcXUnb24gdmEgdGVzdGVyIGRlcyB2aXRlc3NlcyBkJ2FwcHJlbnRpc3NhZ2UgZGUgMC4wMSwgMC4xIGV0IDAuMw0KIyBnYW1tYSA9IGMoMCwgMSkgc2lnbmlmaWUgcXUnb24gdmEgdGVzdGVyIGRlcyB2YWxldXJzIGRlIDAgZXQgMSBwb3VyIGxlIHBhcmFtw6h0cmUgZ2FtbWENCiMgY29sc2FtcGxlX2J5dHJlZSA9IGMoMC41LCAxKSBzaWduaWZpZSBxdSdvbiB2YSB0ZXN0ZXIgZGVzIMOpY2hhbnRpbGxvbnMgZGUgNTAlIGV0IDEwMCUgcG91ciBsZXMgYXJicmVzDQojIG1pbl9jaGlsZF93ZWlnaHQgPSBjKDEsIDUpIHNpZ25pZmllIHF1J29uIHZhIHRlc3RlciBkZXMgdmFsZXVycyBkZSAxIGV0IDUgcG91ciBsZSBwYXJhbcOodHJlIG1pbl9jaGlsZF93ZWlnaHQNCiMgc3Vic2FtcGxlID0gYygwLjUsIDEpIHNpZ25pZmllIHF1J29uIHZhIHRlc3RlciBkZXMgw6ljaGFudGlsbG9ucyBkZSA1MCUgZXQgMTAwJSBwb3VyIGxlcyBhcmJyZXMNCmdyaWQgPC0gZXhwYW5kLmdyaWQoDQogIG5yb3VuZHMgPSBjKDEwMCksIA0KICBtYXhfZGVwdGggPSBjKDMsIDUsIDcpLCANCiAgZXRhID0gYygwLjAxLCAwLjEsIDAuMyksDQogIGdhbW1hID0gYygwLCAxKSwNCiAgY29sc2FtcGxlX2J5dHJlZSA9IGMoMC41LCAxKSwNCiAgbWluX2NoaWxkX3dlaWdodCA9IGMoMSwgNSksDQogIHN1YnNhbXBsZSA9IGMoMC41LCAxKQ0KKQ0KDQojIE9uIGTDqW1hcnJlIGxlIGNocm9ub23DqHRyZQ0Kc3RhcnRfdGltZSA8LSBTeXMudGltZSgpDQoNCiMgT24gZW50cmHDrm5lIGxlIG1vZMOobGUgZW4gdXRpbGlzYW50IGxhIHZhbGlkYXRpb24gY3JvaXPDqWUgZXQgbGEgZ3JpbGxlIGRlIHBhcmFtw6h0cmVzDQojIHRyYWluKCkgY3LDqWUgZXQgZW50cmHDrm5lIGxlIG1vZMOobGUNCiMgdHlwZSB+IC4gc2lnbmlmaWUgcXUnb24gdXRpbGlzZSB0b3V0ZXMgbGVzIGNvbG9ubmVzIHBvdXIgcHLDqWRpcmUgbGEgY29sb25uZSAidHlwZSINCiMgZGF0YSA9IHRyYWluRGF0YSBzaWduaWZpZSBxdSdvbiB1dGlsaXNlIGxlcyBkb25uw6llcyBkJ2FwcHJlbnRpc3NhZ2UNCiMgbWV0aG9kID0gInhnYlRyZWUiIHNpZ25pZmllIHF1J29uIHV0aWxpc2UgbGUgbW9kw6hsZSBYR0Jvb3N0DQojIHRyQ29udHJvbCA9IGN0cmwgc2lnbmlmaWUgcXUnb24gdXRpbGlzZSBsZXMgcGFyYW3DqHRyZXMgZGUgdmFsaWRhdGlvbiBjcm9pc8OpZSBkw6lmaW5pcyBwbHVzIHTDtHQNCiMgdHVuZUdyaWQgPSBncmlkIHNpZ25pZmllIHF1J29uIHV0aWxpc2UgbGEgZ3JpbGxlIGRlIHBhcmFtw6h0cmVzIGTDqWZpbmllIHBsdXMgdMO0dA0KeGdib29zdF9jdiA8LSB0cmFpbih0eXBlIH4gLiwgZGF0YSA9IHRyYWluRGF0YSwgbWV0aG9kID0gInhnYlRyZWUiLCB0ckNvbnRyb2wgPSBjdHJsLCB0dW5lR3JpZCA9IGdyaWQpDQoNCiMgT24gYXJyw6p0ZSBsZSBjaHJvbm9tw6h0cmUNCmVuZF90aW1lIDwtIFN5cy50aW1lKCkNCg0KIyBPbiBjYWxjdWxlIGxlIHRlbXBzIHRvdGFsIGQnZXjDqWN1dGlvbiBwb3VyIGxhIHZhbGlkYXRpb24gY3JvaXPDqWUNCnhnYm9vc3RfdGltZV9jdiA8LSBlbmRfdGltZSAtIHN0YXJ0X3RpbWUNCg0KIyBNZWlsbGV1cnMgcGFyYW3DqHRyZXMNCiMgYmVzdFR1bmUgY29udGllbnQgbGVzIG1laWxsZXVycyBwYXJhbcOodHJlcyB0cm91dsOpcyBwYXIgbGEgdmFsaWRhdGlvbiBjcm9pc8OpZQ0KYmVzdF94Z2Jvb3N0IDwtIHhnYm9vc3RfY3YkYmVzdFR1bmUNCmJlc3RfeGdib29zdA0KDQojIEVudHJhw65uZW1lbnQgZHUgbW9kw6hsZSBhdmVjIGxlcyBtZWlsbGV1cnMgcGFyYW3DqHRyZXMNCg0KIyBPbiBkw6ltYXJyZSBsZSBjaHJvbm9tw6h0cmUNCnN0YXJ0X3RpbWUgPC0gU3lzLnRpbWUoKQ0KDQojIE9uIGNyw6llIGV0IGVudHJhw65uZSBsZSBtb2TDqGxlIFhHQm9vc3QgYXZlYyBsZXMgbWVpbGxldXJzIHBhcmFtw6h0cmVzIHRyb3V2w6lzIHBhciBsYSB2YWxpZGF0aW9uIGNyb2lzw6llDQojIGRhdGEgPSB0cmFpbl9tYXRyaXggc2lnbmlmaWUgcXUnb24gdXRpbGlzZSBsZXMgZG9ubsOpZXMgZCdhcHByZW50aXNzYWdlDQojIG5yb3VuZHMgPSBiZXN0X3hnYm9vc3QkbnJvdW5kcyBzaWduaWZpZSBxdSdvbiB1dGlsaXNlIGxlIG5vbWJyZSBkJ2l0w6lyYXRpb25zIHRyb3V2w6kgcGFyIGxhIHZhbGlkYXRpb24gY3JvaXPDqWUNCiMgb2JqZWN0aXZlID0gImJpbmFyeTpsb2dpc3RpYyIgc2lnbmlmaWUgcXUnb24gdXRpbGlzZSB1bmUgcsOpZ3Jlc3Npb24gbG9naXN0aXF1ZSBiaW5haXJlDQojIG1heF9kZXB0aCA9IGJlc3RfeGdib29zdCRtYXhfZGVwdGggc2lnbmlmaWUgcXUnb24gdXRpbGlzZSBsYSBwcm9mb25kZXVyIHRyb3V2w6llIHBhciBsYSB2YWxpZGF0aW9uIGNyb2lzw6llDQojIGV0YSA9IGJlc3RfeGdib29zdCRldGEgc2lnbmlmaWUgcXUnb24gdXRpbGlzZSBsYSB2aXRlc3NlIGQnYXBwcmVudGlzc2FnZSB0cm91dsOpZSBwYXIgbGEgdmFsaWRhdGlvbiBjcm9pc8OpZQ0KIyBnYW1tYSA9IGJlc3RfeGdib29zdCRnYW1tYSBzaWduaWZpZSBxdSdvbiB1dGlsaXNlIGxlIGdhbW1hIHRyb3V2w6kgcGFyIGxhIHZhbGlkYXRpb24gY3JvaXPDqWUNCiMgY29sc2FtcGxlX2J5dHJlZSA9IGJlc3RfeGdib29zdCRjb2xzYW1wbGVfYnl0cmVlIHNpZ25pZmllIHF1J29uIHV0aWxpc2UgbCfDqWNoYW50aWxsb25uYWdlIHRyb3V2w6kgcGFyIGxhIHZhbGlkYXRpb24gY3JvaXPDqWUNCiMgbWluX2NoaWxkX3dlaWdodCA9IGJlc3RfeGdib29zdCRtaW5fY2hpbGRfd2VpZ2h0IHNpZ25pZmllIHF1J29uIHV0aWxpc2UgbGUgcG9pZHMgbWluaW11bSB0cm91dsOpIHBhciBsYSB2YWxpZGF0aW9uIGNyb2lzw6llDQojIHN1YnNhbXBsZSA9IGJlc3RfeGdib29zdCRzdWJzYW1wbGUgc2lnbmlmaWUgcXUnb24gdXRpbGlzZSBsJ8OpY2hhbnRpbGxvbm5hZ2UgdHJvdXbDqSBwYXIgbGEgdmFsaWRhdGlvbiBjcm9pc8OpZQ0KIyB3YXRjaGxpc3QgPSBsaXN0KHRyYWluID0gdHJhaW5fbWF0cml4LCB0ZXN0ID0gdGVzdF9tYXRyaXgpIHNpZ25pZmllIHF1J29uIHN1aXQgbGVzIGVycmV1cnMgc3VyIGxlcyBkb25uw6llcyBkJ2FwcHJlbnRpc3NhZ2UgZXQgZGUgdGVzdA0KIyBldmFsX21ldHJpYyA9ICJsb2dsb3NzIiBzaWduaWZpZSBxdSdvbiB1dGlsaXNlIGxhIHBlcnRlIGxvZ2FyaXRobWlxdWUgcG91ciDDqXZhbHVlciBsZXMgZXJyZXVycw0KIyB2ZXJib3NlID0gMCBzaWduaWZpZSBxdSdvbiBuZSB2ZXV0IHBhcyBhZmZpY2hlciBkZXMgbWVzc2FnZXMgZMOpdGFpbGzDqXMgcGVuZGFudCBsJ2VudHJhw65uZW1lbnQNCnhnYm9vc3RfbW9kZWxfYmVzdCA8LSB4Z2IudHJhaW4oDQogIGRhdGEgPSB0cmFpbl9tYXRyaXgsDQogIG5yb3VuZHMgPSBiZXN0X3hnYm9vc3QkbnJvdW5kcywNCiAgb2JqZWN0aXZlID0gImJpbmFyeTpsb2dpc3RpYyIsDQogIG1heF9kZXB0aCA9IGJlc3RfeGdib29zdCRtYXhfZGVwdGgsDQogIGV0YSA9IGJlc3RfeGdib29zdCRldGEsDQogIGdhbW1hID0gYmVzdF94Z2Jvb3N0JGdhbW1hLA0KICBjb2xzYW1wbGVfYnl0cmVlID0gYmVzdF94Z2Jvb3N0JGNvbHNhbXBsZV9ieXRyZWUsDQogIG1pbl9jaGlsZF93ZWlnaHQgPSBiZXN0X3hnYm9vc3QkbWluX2NoaWxkX3dlaWdodCwNCiAgc3Vic2FtcGxlID0gYmVzdF94Z2Jvb3N0JHN1YnNhbXBsZSwNCiAgd2F0Y2hsaXN0ID0gbGlzdCh0cmFpbiA9IHRyYWluX21hdHJpeCwgdGVzdCA9IHRlc3RfbWF0cml4KSwNCiAgZXZhbF9tZXRyaWMgPSAibG9nbG9zcyIsDQogIHZlcmJvc2UgPSAwDQopDQoNCiMgT24gYXJyw6p0ZSBsZSBjaHJvbm9tw6h0cmUNCmVuZF90aW1lIDwtIFN5cy50aW1lKCkNCg0KIyBPbiBjYWxjdWxlIGxlIHRlbXBzIHRvdGFsIGQnZXjDqWN1dGlvbiBwb3VyIGVudHJhw65uZXIgbGUgbW9kw6hsZSBhdmVjIGxlcyBtZWlsbGV1cnMgcGFyYW3DqHRyZXMNCnhnYm9vc3RfdGltZV9iZXN0IDwtIGVuZF90aW1lIC0gc3RhcnRfdGltZQ0KDQojIFLDqWN1cMOpcmF0aW9uIGRlcyBlcnJldXJzIGQnYXBwcmVudGlzc2FnZSBldCBkZSB0ZXN0DQojIGV2YWx1YXRpb25fbG9nX2Jlc3QgY29udGllbnQgbGVzIGVycmV1cnMgcG91ciBjaGFxdWUgaXTDqXJhdGlvbg0KZXZhbHVhdGlvbl9sb2dfYmVzdCA8LSB4Z2Jvb3N0X21vZGVsX2Jlc3QkZXZhbHVhdGlvbl9sb2cNCg0KIyBDYWxjdWwgZGVzIGVycmV1cnMgZmluYWxlcw0KIyBPbiB1dGlsaXNlIHByZWRpY3QoKSBwb3VyIGZhaXJlIGRlcyBwcsOpZGljdGlvbnMgc3VyIGxlcyBkb25uw6llcyBkJ2FwcHJlbnRpc3NhZ2UNCnhnYm9vc3RfdHJhaW5fcHJlZF9iZXN0IDwtIHByZWRpY3QoeGdib29zdF9tb2RlbF9iZXN0LCBuZXdkYXRhID0gdHJhaW5fbWF0cml4KQ0KIyBPbiBmYWl0IGRlIG3Dqm1lIHBvdXIgbGVzIGRvbm7DqWVzIGRlIHRlc3QNCnhnYm9vc3RfdGVzdF9wcmVkX2Jlc3QgPC0gcHJlZGljdCh4Z2Jvb3N0X21vZGVsX2Jlc3QsIG5ld2RhdGEgPSB0ZXN0X21hdHJpeCkNCg0KIyBDb252ZXJzaW9uIGRlcyBwcsOpZGljdGlvbnMgZW4gY2xhc3NlcyBiaW5haXJlcw0KIyBPbiBjb252ZXJ0aXQgbGVzIHByw6lkaWN0aW9ucyBlbiB2YWxldXJzIGJpbmFpcmVzICgwIG91IDEpDQojIFNpIGxhIHByw6lkaWN0aW9uIGVzdCBzdXDDqXJpZXVyZSDDoCAwLjUsIG9uIGxhIGNvbnNpZMOocmUgY29tbWUgMSAoc3BhbSksIHNpbm9uIGNvbW1lIDAgKG5vbnNwYW0pDQp4Z2Jvb3N0X3RyYWluX2NsYXNzX2Jlc3QgPC0gaWZlbHNlKHhnYm9vc3RfdHJhaW5fcHJlZF9iZXN0ID4gMC41LCAxLCAwKQ0KeGdib29zdF90ZXN0X2NsYXNzX2Jlc3QgPC0gaWZlbHNlKHhnYm9vc3RfdGVzdF9wcmVkX2Jlc3QgPiAwLjUsIDEsIDApDQoNCiMgQ2FsY3VsIGRlcyBlcnJldXJzDQojIE9uIGNvbXBhcmUgbGVzIHByw6lkaWN0aW9ucyBhdmVjIGxlcyB2cmFpZXMgdmFsZXVycyBwb3VyIGNhbGN1bGVyIGxlIHRhdXggZCdlcnJldXINCnhnYm9vc3RfdHJhaW5fZXJyb3JfYmVzdCA8LSBtZWFuKHhnYm9vc3RfdHJhaW5fY2xhc3NfYmVzdCAhPSB0cmFpbkRhdGEkdHlwZSkNCnhnYm9vc3RfdGVzdF9lcnJvcl9iZXN0IDwtIG1lYW4oeGdib29zdF90ZXN0X2NsYXNzX2Jlc3QgIT0gdGVzdERhdGEkdHlwZSkNCg0KIyBBZmZpY2hhZ2UgZGVzIGVycmV1cnMgZmluYWxlcw0KIyBPbiBhZmZpY2hlIGxlIHRhdXggZCdlcnJldXIgc3VyIGxlcyBkb25uw6llcyBkJ2FwcHJlbnRpc3NhZ2UNCnhnYm9vc3RfdHJhaW5fZXJyb3JfYmVzdA0KDQojIExlIHByZW1pZXIgbm9tYnJlICgwLjA0MTgyNTEpIGVzdCBsZSB0YXV4IGQnZXJyZXVyIHN1ciBsZXMgZG9ubsOpZXMgZCdhcHByZW50aXNzYWdlLg0KIyBDZWxhIHNpZ25pZmllIHF1ZSBsZSBtb2TDqGxlIGEgZmFpdCB1bmUgZXJyZXVyIGVudmlyb24gNC4xOCUgZHUgdGVtcHMgc3VyIGxlcyBkb25uw6llcyBkJ2FwcHJlbnRpc3NhZ2UuDQoNCiMgT24gYWZmaWNoZSBsZSB0YXV4IGQnZXJyZXVyIHN1ciBsZXMgZG9ubsOpZXMgZGUgdGVzdA0KeGdib29zdF90ZXN0X2Vycm9yX2Jlc3QNCg0KIyBMZSBkZXV4acOobWUgbm9tYnJlICgwLjA1NzY3MTM4KSBlc3QgbGUgdGF1eCBkJ2VycmV1ciBzdXIgbGVzIGRvbm7DqWVzIGRlIHRlc3QuDQojIENlbGEgc2lnbmlmaWUgcXVlIGxlIG1vZMOobGUgYSBmYWl0IHVuZSBlcnJldXIgZW52aXJvbiA1Ljc3JSBkdSB0ZW1wcyBzdXIgbGVzIGRvbm7DqWVzIGRlIHRlc3QuDQoNCiMgT24gYWZmaWNoZSBsZSB0ZW1wcyB0b3RhbCBkJ2V4w6ljdXRpb24gcG91ciBlbnRyYcOubmVyIGxlIG1vZMOobGUgYXZlYyBsZXMgbWVpbGxldXJzIHBhcmFtw6h0cmVzDQp4Z2Jvb3N0X3RpbWVfYmVzdA0KDQojIExlIHRyb2lzacOobWUgcsOpc3VsdGF0IChUaW1lIGRpZmZlcmVuY2Ugb2YgMC44NjE3MTUxIHNlY3MpIGluZGlxdWUgcXVlIGxlIHRlbXBzIHRvdGFsIA0KIyBwb3VyIGVudHJhw65uZXIgbGUgbW9kw6hsZSBYR0Jvb3N0IGF2ZWMgMTAwIGl0w6lyYXRpb25zIGEgw6l0w6kgZCdlbnZpcm9uIDAuODYgc2Vjb25kZXMuDQoNCiMgVHJhw6dhZ2UgZGVzIGNvdXJiZXMgZCdlcnJldXJzDQojIHBsb3QoKSBjcsOpZSB1biBncmFwaGlxdWUgbW9udHJhbnQgbCfDqXZvbHV0aW9uIGRlcyBlcnJldXJzDQpwbG90KGV2YWx1YXRpb25fbG9nX2Jlc3QkaXRlciwgZXZhbHVhdGlvbl9sb2dfYmVzdCR0cmFpbl9sb2dsb3NzLCB0eXBlID0gImwiLCBjb2wgPSAiYmx1ZSIsIA0KICAgICB5bGltID0gYygwLCBtYXgoZXZhbHVhdGlvbl9sb2dfYmVzdCR0cmFpbl9sb2dsb3NzLCBldmFsdWF0aW9uX2xvZ19iZXN0JHRlc3RfbG9nbG9zcykpLCANCiAgICAgeWxhYiA9ICJMb2cgTG9zcyIsIHhsYWIgPSAiSXRlcmF0aW9uIiwgDQogICAgIG1haW4gPSAiw4l2b2x1dGlvbiBkZXMgZXJyZXVycyBhdmVjIFhHQm9vc3QgKG1laWxsZXVycyBwYXJhbcOodHJlcykiKQ0KIyBsaW5lcygpIGFqb3V0ZSB1bmUgbGlnbmUgcm91Z2UgcG91ciBsZXMgZXJyZXVycyBkZSB0ZXN0DQpsaW5lcyhldmFsdWF0aW9uX2xvZ19iZXN0JGl0ZXIsIGV2YWx1YXRpb25fbG9nX2Jlc3QkdGVzdF9sb2dsb3NzLCBjb2wgPSAicmVkIikNCiMgbGVnZW5kKCkgYWpvdXRlIHVuZSBsw6lnZW5kZSBhdSBncmFwaGlxdWUgcG91ciBleHBsaXF1ZXIgbGVzIGNvdWxldXJzDQpsZWdlbmQoInRvcHJpZ2h0IiwgbGVnZW5kID0gYygiRXJyZXVyIGQnYXBwcmVudGlzc2FnZSIsICJFcnJldXIgZGUgdGVzdCIpLCANCiAgICAgICBjb2wgPSBjKCJibHVlIiwgInJlZCIpLCBsdHkgPSAxKQ0KDQojIFZpc3VhbGlzYXRpb24gZGUgbCdpbXBvcnRhbmNlIGRlcyB2YXJpYWJsZXMNCiMgeGdiLmltcG9ydGFuY2UoKSBjYWxjdWxlIGwnaW1wb3J0YW5jZSBkZSBjaGFxdWUgdmFyaWFibGUgZGFucyBsZSBtb2TDqGxlDQppbXBvcnRhbmNlX21hdHJpeF9iZXN0IDwtIHhnYi5pbXBvcnRhbmNlKG1vZGVsID0geGdib29zdF9tb2RlbF9iZXN0KQ0KIyB4Z2IucGxvdC5pbXBvcnRhbmNlKCkgY3LDqWUgdW4gZ3JhcGhpcXVlIG1vbnRyYW50IGwnaW1wb3J0YW5jZSBkZXMgdmFyaWFibGVzDQp4Z2IucGxvdC5pbXBvcnRhbmNlKGltcG9ydGFuY2VfbWF0cml4X2Jlc3QpDQoNCg0KIyBMZSBwcmVtaWVyIGdyYXBoaXF1ZSBtb250cmUgbCfDqXZvbHV0aW9uIGRlcyBlcnJldXJzIGQnYXBwcmVudGlzc2FnZSBldCBkZSB0ZXN0IGF1IGZpbCBkZXMgMTAwIGl0w6lyYXRpb25zLg0KIyBPbiByZW1hcnF1ZSBxdWUgbGEgY291cmJlIGJsZXVlIGRlIGwnZXJyZXVyIGQnYXBwcmVudGlzc2FnZSBkZXNjZW5kIHBsdXMgcmFwaWRlbWVudCBldCBhdHRlaW50IHVuZSB2YWxldXIgcGx1cyBiYXNzZSBxdWUgZGFucyBsZSBtb2TDqGxlIHBhciBkw6lmYXV0LCBpbmRpcXVhbnQgdW5lIG1laWxsZXVyZSBhZGFwdGF0aW9uIGF1eCBkb25uw6llcyBkJ2VudHJhw65uZW1lbnQuIExhIGNvdXJiZSByb3VnZSBkZSBsJ2VycmV1ciBkZSB0ZXN0LCB0b3V0IGVuIGRpbWludWFudCDDqWdhbGVtZW50LCBtb250cmUgdW5lIGNvbnZlcmdlbmNlIHBsdXMgcmFwaWRlIGV0IHVuZSBzdGFiaWxpc2F0aW9uIMOgIHVuZSB2YWxldXIgbMOpZ8OocmVtZW50IHBsdXMgYmFzc2UgcXVlIGNlbGxlIGR1IG1vZMOobGUgcGFyIGTDqWZhdXQsIGluZGlxdWFudCB1bmUgYW3DqWxpb3JhdGlvbiBnw6luw6lyYWxlIGRhbnMgbGEgcGVyZm9ybWFuY2UgZGUgcHLDqWRpY3Rpb24uDQoNCiMgTGUgZGV1eGnDqG1lIGdyYXBoaXF1ZSBtb250cmUgbCdpbXBvcnRhbmNlIGRlcyB2YXJpYWJsZXMgZGFucyBsZSBtb2TDqGxlLiANCiMgTGVzIHZhcmlhYmxlcyBlbiBoYXV0IGR1IGdyYXBoaXF1ZSBzb250IGxlcyBwbHVzIGltcG9ydGFudGVzIHBvdXIgbGUgbW9kw6hsZS4NCiNMZXMgdmFyaWFibGVzICJjaGFyRG9sbGFyIiwgImNoYXJFeGNsYW1hdGlvbiIsIGV0ICJjYXBpdGFsTG9uZyIgc29udCBwYXJtaSBsZXMgcGx1cyBpbmZsdWVudGVzLiBPbiBjb25zdGF0ZSBxdWUgbCdpbXBvcnRhbmNlIHJlbGF0aXZlIGRlcyB2YXJpYWJsZXMgcGV1dCB2YXJpZXIgbMOpZ8OocmVtZW50IHBhciByYXBwb3J0IGF1IG1vZMOobGUgcGFyIGTDqWZhdXQsIGNlIHF1aSByZWZsw6h0ZSBsZXMgYWp1c3RlbWVudHMgZWZmZWN0dcOpcyBwYXIgbGUgcHJvY2Vzc3VzIGQnb3B0aW1pc2F0aW9uIHBvdXIgYW3DqWxpb3JlciBsYSBwZXJmb3JtYW5jZSBnbG9iYWxlDQpgYGANCg0KYGBge3IgQ2VsbHVsZSA5OiBDb21wYXJhaXNvbiBkZXMgbW9kw6hsZXN9DQojIFLDqXN1bHRhdHMgZGVzIG1vZMOobGVzIHBhciBkw6lmYXV0DQoNCiMgT24gY3LDqWUgdW4gdGFibGVhdSBkZSBkb25uw6llcyBwb3VyIHN0b2NrZXIgbGVzIHLDqXN1bHRhdHMgZGVzIG1vZMOobGVzIHBhciBkw6lmYXV0DQojIGRhdGEuZnJhbWUoKSBjcsOpZSB1biB0YWJsZWF1IGRlIGRvbm7DqWVzDQojIE1vZGVsID0gYygiQWRhQm9vc3QiLCAiR3JhZGllbnQgQm9vc3RpbmciLCAiWEdCb29zdCIpIHNpZ25pZmllIHF1J29uIGEgdHJvaXMgbW9kw6hsZXMgOiBBZGFCb29zdCwgR3JhZGllbnQgQm9vc3RpbmcgZXQgWEdCb29zdA0KIyBUcmFpbl9FcnJvciwgVGVzdF9FcnJvciBldCBFeGVjdXRpb25fVGltZSBzb250IGxlcyBjb2xvbm5lcyBvw7kgb24gdmEgc3RvY2tlciBsZXMgZXJyZXVycyBkJ2FwcHJlbnRpc3NhZ2UsIGxlcyBlcnJldXJzIGRlIHRlc3QgZXQgbGVzIHRlbXBzIGQnZXjDqWN1dGlvbiBwb3VyIGNoYXF1ZSBtb2TDqGxlDQoNCnJlc3VsdHNfZGVmYXVsdCA8LSBkYXRhLmZyYW1lKA0KICBNb2RlbCA9IGMoIkFkYUJvb3N0IiwgIkdyYWRpZW50IEJvb3N0aW5nIiwgIlhHQm9vc3QiKSwNCiAgVHJhaW5fRXJyb3IgPSBjKHRyYWluX2Vycm9yLCBnYm1fdHJhaW5fZXJyb3IsIHhnYm9vc3RfdHJhaW5fZXJyb3IpLA0KICBUZXN0X0Vycm9yID0gYyh0ZXN0X2Vycm9yLCBnYm1fdGVzdF9lcnJvciwgeGdib29zdF90ZXN0X2Vycm9yKSwNCiAgRXhlY3V0aW9uX1RpbWUgPSBjKGFkYWJvb3N0X3RpbWVfZGVmYXVsdCwgZ2JtX3RpbWVfZGVmYXVsdCwgeGdib29zdF90aW1lX2RlZmF1bHQpDQopDQoNCiMgUsOpc3VsdGF0cyBkZXMgbW9kw6hsZXMgYXZlYyBsZXMgbWVpbGxldXJzIHBhcmFtw6h0cmVzDQoNCiMgT24gY3LDqWUgdW4gYXV0cmUgdGFibGVhdSBkZSBkb25uw6llcyBwb3VyIHN0b2NrZXIgbGVzIHLDqXN1bHRhdHMgZGVzIG1vZMOobGVzIG9wdGltaXPDqXMgYXZlYyBsZXMgbWVpbGxldXJzIHBhcmFtw6h0cmVzIHRyb3V2w6lzIHBhciB2YWxpZGF0aW9uIGNyb2lzw6llDQojIEljaSwgb24gYSBxdWF0cmUgbW9kw6hsZXMgb3B0aW1pc8OpcyA6IGRldXggdmVyc2lvbnMgZCdBZGFCb29zdCAoMzAwIGl0w6lyYXRpb25zIGV0IDMwMCBpdMOpcmF0aW9ucyBhdmVjIHByb2ZvbmRldXIgbGltaXTDqWUpLCBHcmFkaWVudCBCb29zdGluZyBvcHRpbWlzw6kgZXQgWEdCb29zdCBvcHRpbWlzw6kNCnJlc3VsdHNfYmVzdCA8LSBkYXRhLmZyYW1lKA0KICBNb2RlbCA9IGMoIkFkYUJvb3N0ICgzMDAgaXRlcmF0aW9ucykiLCAiQWRhQm9vc3QgKDMwMCBpdGVyYXRpb25zLCBkZXB0aCAzKSIsICJHcmFkaWVudCBCb29zdGluZyAoQmVzdCkiLCAiWEdCb29zdCAoQmVzdCkiKSwNCiAgVHJhaW5fRXJyb3IgPSBjKHRyYWluX2Vycm9yXzMwMCwgdHJhaW5fZXJyb3JfZGVwdGgzLCBnYm1fdHJhaW5fZXJyb3JfYmVzdCwgeGdib29zdF90cmFpbl9lcnJvcl9iZXN0KSwNCiAgVGVzdF9FcnJvciA9IGModGVzdF9lcnJvcl8zMDAsIHRlc3RfZXJyb3JfZGVwdGgzLCBnYm1fdGVzdF9lcnJvcl9iZXN0LCB4Z2Jvb3N0X3Rlc3RfZXJyb3JfYmVzdCksDQogIEV4ZWN1dGlvbl9UaW1lID0gYyhhZGFib29zdF90aW1lXzMwMCwgYWRhYm9vc3RfdGltZV9kZXB0aDMsIGdibV90aW1lX2Jlc3QsIHhnYm9vc3RfdGltZV9iZXN0KQ0KKQ0KDQojIEFmZmljaGFnZSBkZXMgcsOpc3VsdGF0cw0KDQojIE9uIGFmZmljaGUgbGUgdGFibGVhdSBkZXMgcsOpc3VsdGF0cyBkZXMgbW9kw6hsZXMgcGFyIGTDqWZhdXQNCnJlc3VsdHNfZGVmYXVsdA0KDQojIE9uIGFmZmljaGUgbGUgdGFibGVhdSBkZXMgcsOpc3VsdGF0cyBkZXMgbW9kw6hsZXMgb3B0aW1pc8Opcw0KcmVzdWx0c19iZXN0DQpgYGANCg0KMS4gICoqTW9kw6hsZXMgcGFyIGTDqWZhdXQqKiA6DQoNCiAgICAqKkFkYUJvb3N0KiogYSBtb250csOpIHVuIGJvbiBjb21wcm9taXMgZW50cmUgdGVtcHMgZCdleMOpY3V0aW9uIGV0IHRhdXggZCdlcnJldXIsIGF2ZWMgdW4gdGVtcHMgZGUgY2FsY3VsIG1vZMOpcsOpIChcfjUuMzcgc2Vjb25kZXMpIGV0IGRlcyBlcnJldXJzIGQnYXBwcmVudGlzc2FnZSAoMi42OSUpIGV0IGRlIHRlc3QgKDUuMTElKSByYWlzb25uYWJsZW1lbnQgYmFzc2VzLiAqKkdyYWRpZW50IEJvb3N0aW5nKiogYSBwcmlzIHBsdXMgZGUgdGVtcHMgKFx+MjUuMjUgc2Vjb25kZXMpIGV0IGEgYWZmaWNow6kgZGVzIHRhdXggZCdlcnJldXIgbMOpZ8OocmVtZW50IHN1cMOpcmlldXJzIMOgIGNldXggZCdBZGFCb29zdC4gKipYR0Jvb3N0KiosIGJpZW4gcXVlIHRyw6hzIHJhcGlkZSAoXH4wLjQ3IHNlY29uZGUpLCBhIG1vbnRyw6kgdW5lIGzDqWfDqHJlIGF1Z21lbnRhdGlvbiBkZSBsJ2VycmV1ciBkZSB0ZXN0ICg1Ljc3JSksIGluZGlxdWFudCBwZXV0LcOqdHJlIHVuZSBtb2luZHJlIHBlcmZvcm1hbmNlIGVuIHRlcm1lcyBkZSBnw6luw6lyYWxpc2F0aW9uIHBhciByYXBwb3J0IMOgIEFkYUJvb3N0Lg0KDQoyLiAgKipNb2TDqGxlcyBvcHRpbWlzw6lzKiogOg0KDQogICAgRW4gb3B0aW1pc2FudCBsZXMgcGFyYW3DqHRyZXMsICoqQWRhQm9vc3QgYXZlYyAzMDAgaXTDqXJhdGlvbnMqKiBhIGF0dGVpbnQgdW5lIGVycmV1ciBkZSB0ZXN0IHRyw6hzIGJhc3NlICgzLjcwJSksIG1haXMgYXUgY2/Du3QgZCd1biB0ZW1wcyBkZSBjYWxjdWwgw6lsZXbDqSAoXH4xNzEuMTcgc2Vjb25kZXMpLiBFbiBsaW1pdGFudCBsYSBwcm9mb25kZXVyIGRlcyBhcmJyZXMsICoqQWRhQm9vc3QgKDMwMCBpdMOpcmF0aW9ucywgcHJvZm9uZGV1ciAzKSoqIGEgcsOpZHVpdCBsZSB0ZW1wcyBkZSBjYWxjdWwgKFx+NTcuNDkgc2Vjb25kZXMpIHRvdXQgZW4gbWFpbnRlbmFudCB1bmUgZXJyZXVyIGRlIHRlc3QgYWNjZXB0YWJsZSAoNS4yMiUpLiAqKkdyYWRpZW50IEJvb3N0aW5nIG9wdGltaXPDqSoqIGEgc2lnbmlmaWNhdGl2ZW1lbnQgYW3DqWxpb3LDqSBzZXMgcGVyZm9ybWFuY2VzLCBhdmVjIHVuZSBlcnJldXIgZGUgdGVzdCByw6lkdWl0ZSDDoCA0LjAzJSBldCB1biB0ZW1wcyBkZSBjYWxjdWwgZGUgXH42MS40OSBzZWNvbmRlcy4gKipYR0Jvb3N0IG9wdGltaXPDqSoqIGEgw6lnYWxlbWVudCBtb250csOpIHVuZSBwZXJmb3JtYW5jZSByb2J1c3RlLCBhdmVjIHVuZSBlcnJldXIgZGUgdGVzdCBpZGVudGlxdWUgw6AgY2VsbGUgZGUgR3JhZGllbnQgQm9vc3RpbmcgKDQuMDMlKSBtYWlzIGF2ZWMgdW4gdGVtcHMgZGUgY2FsY3VsIGJlYXVjb3VwIHBsdXMgZmFpYmxlIChcfjAuOTQgc2Vjb25kZSkuDQoNCkVuIGNvbmNsdXNpb24sICoqWEdCb29zdCBvcHRpbWlzw6kqKiBvZmZyZSBsZSBtZWlsbGV1ciDDqXF1aWxpYnJlIGVudHJlIHBlcmZvcm1hbmNlIGV0IHRlbXBzIGQnZXjDqWN1dGlvbiwgYWZmaWNoYW50IGRlcyBlcnJldXJzIGRlIHRlc3QgY29tcMOpdGl0aXZlcyBhdmVjIHVuIHRlbXBzIGRlIGNhbGN1bCBuZXR0ZW1lbnQgaW5mw6lyaWV1ciDDoCBjZWx1aSBkZXMgYXV0cmVzIG1vZMOobGVzLiAqKkFkYUJvb3N0IG9wdGltaXPDqSoqIHBldXQgb2ZmcmlyIGRlcyBlcnJldXJzIGRlIHRlc3QgbMOpZ8OocmVtZW50IG1laWxsZXVyZXMsIG1haXMgw6AgdW4gY2/Du3QgZGUgY2FsY3VsIGJlYXVjb3VwIHBsdXMgw6lsZXbDqS4gKipHcmFkaWVudCBCb29zdGluZyoqIHNlIHNpdHVlIGVudHJlIGxlcyBkZXV4LCBvZmZyYW50IHVuZSBib25uZSBwZXJmb3JtYW5jZSBhdmVjIHVuIHRlbXBzIGRlIGNhbGN1bCByYWlzb25uYWJsZS4gTGVzIGNob2l4IGZpbmF1eCBkw6lwZW5kZW50IGRlcyBjb250cmFpbnRlcyBkZSB0ZW1wcyBkZSBjYWxjdWwgZXQgZGVzIGV4aWdlbmNlcyBkZSBwcsOpY2lzaW9uIHNww6ljaWZpcXVlcyDDoCBsJ2FwcGxpY2F0aW9uLg0K