Professor: Evandro de Barros Costa
Aluno: Jairo Raphael Moreira Correia de Souza.
O objetivo desse relatório técnico é explorar a prática de técnicas de aprendizagem de máquina em um dataset contendo dados métricas de software de projetos open source. Para realização do experimento foi utilizada a Linguagem R juntamente do auxilio de algumas bibliotecas disponíveis no repositório [RCRAN] (https://cran.r-project.org/).
O relatório está estruturado da seguinte forma:
- Introdução e Descrição do Dataset;
- Preparação dos Dados;
- Construção do Modelo;
- Resultados;
- Conclusão.
Introdução
O Objetivo geral do estudo é gerar um modelo de predição utilizando métricas de software capaz de detectar bugs em alterações de códigos em funções na linguagem C. O Modelo irá utilizar diferentes técnicas de aprendizagem de máquina com o intuito de gerar um modelo preditivo capaz de prever algum possível bug em futuras alterações nesses projetos. Portanto, a grande contribuição do modelo é de evitar um maior esforço de correções em fases de desenvolvimento críticas através da predição de bugs a partir de alterações de trechos de código durante a fase de desenvolvimento.
O Dataset contém métricas de software coletadas via [API Understand] (https://scitools.com/feature/metrics/) de Funções de projetos open source presentes no Github escrito na linguagem C. Segue os projetos Utilizados:
- Glibc - The GNU C Library is the standard system C library for all GNU systems
- Httpd - Web Server
- Kernel - Kernel Linux
- OpenVPN - Mozilla OpenVPN
- Xen - Virtual Machine Monitor (VMM)
As métricas de software são divididas em 3 categorias:
Métricas de Complexidade: São métricas relacionadas a complexidade do código. Ex: A média da complexidade ciclomática para todas as funções ou métodos aninhados.
Métricas de Tamanho: São métricas relacionadas ao tamanho do software. Ex: Quantidade de linhas de código de uma função específica.
Métricas de Orientação á Objetos: São métricas relacionadas ao paradigma de orientação á objetos. Ex: Número de classes e métodos.
Obs: As métricas de orientação a objetos não estão presentes no dataset do estudo, visto que a linguagem C não é uma linguagem orientada a objetos.
Na tabela abaixo, cada linha representa uma função do projeto contendo os valores das respectivas métricas de software. As diferentes métricas são exibidas da primeira a 27º coluna. Já na 28º coluna está presente a classificação daquela função, ou seja, se a mesma possui um bug (VULNERABLE) ou não (NEUTRAL).
Preparação dos dados
Nessa fase os dados são tratados e preparados antes da utilização dos mesmo nos modelos de aprendizagem de máquina. Vale ressaltar que essa é uma das principais fases de construção de um modelo, visto que os algoritmos já estão prontos e funcionam diretamente de acordo com os inputs apresentados. Portanto, a qualidade dos dados influencia diretamente a qualidade do modelo.
Seleção de Features:
Em todo estudo é importante realizar uma seleção das features mais relevantes, caso haja possibilidade, com o intuito de obter diversas vantagens como:
- Diminuição do tempo de treinamento;
- Simplificação do modelo que deve levar uma maior interpretabilidade pelos especialistas da área de neǵocio;
- Aumento da generalização dos dados. Sabe-se que em modelos de predição devemos evitar uma generalização/especialização do modelo. Portanto é um grande desafio encontra esse trade-off entre um modelo que não seja especifico um um determinado conjunto de treinamento, além de não ser muito generalista a ponto de não conseguir classificar corretamente os dados.
Durante a fase de preparação, é realizada uma tarefa de classificação dos valores individuais de cada feature. Os valores estão relacionados ao seu grau de importância de acordo com o método escolhido. Os Métodos escolhidos foram:
- Chi Squared;
- Information Gain.
Basicamente os dois testes acimas consistem em determinar a dependência entre duas variáveis. Nesse caso, analisar qual variável dependente (classificação do bug) está mais relacionada com a variabel independente (métricas de software).
Através da Tabela abaixo é possíveis visualizar os valores de cada feature no dataset do estudo. Os dois algoritmos: Information Gain e Chi Squared classificaram a métrica AltCountLineCode (Linhas de código alteradas) contendo a maioria das informações sobre a variável-alvo affected com um valor de 0.38 e 0.77 respectivamente.
features <- makeClassifTask(id = deparse(substitute(input_sample)), input_sample, colnames(input_sample)[28] )
featuresSelection = generateFilterValuesData(features, method = c("information.gain", "chi.squared"))
featuresSelection$data
Também é possivel avaliar a distribuição dos resultados dos métodos de seleção de features supracitados através de boxplots:
par(mfrow=c(1,2))
for(i in 3:4) {
boxplot(featuresSelection$data[i], main=names(featuresSelection$data)[i])
}

Outra maneira de apresentar a distribuição é através de gráfico de barras - barplots. Na imagem abaixo o eixo x corresponde as features utilizadas no dataset e o eixo y ao seu grau de importância respectivamente. Como pode ser visto as variáveis. AltCountLineCode e CountLineCode tiveram resultados melhores que as demais variáveis.
plotFilterValues(featuresSelection)

Balanceamento dos Dados
Outro passo importante na fase de preparação dos dados está relacionada ao balanceamento dos dados. Essa fase gera algumas controvérsias na literatura, visto que alguns autores defendem que o balanceamento pode gerar um viés definitivo e permanente ao dataset, enquanto outros enfatizam que a exclusão de dados deve ser evitada. Porém, o nosso estudo está presente em um cenário de detecção de anomalias. Portanto, em cenários onde a detecção de anomalias é crucial como transações fraudulentas nos bancos, identificação de doenças raras, etc… os dados devem ser balanceados. Nessa situação, o não balanceamento dos dado em um modelo preditivo desenvolvido com algoritmos de aprendizado convencional pode ser tendencioso e impreciso.
Para avaliar tal impacto, abaixo será apresentado um gráfico contendo a distribuição das classes do dois tipos de conjunto de dados. Na imagem é apresentado dois gráficos de barras que consistem em dados balanceados e não balanceados. Os dados balanceados possuem o numero de classes iguais, ou seja, temos as mesma quantidade de linhas de métricas de softwares de funções com bugs e sem bugs respectivamente. Já nos dados não balanceados o notamos uma menor existência de classes relacionadas a não existência de bugs em funções de código em C nos projetos analisados.
input_balanced <- read.csv(file = "/home/r4ph/R/machine-learning-ufal/datasets/vulnerability/balanced/glibc_data_balanced.csv", stringsAsFactors = FALSE)
input_unbalanced <- read.csv(file = "/home/r4ph/R/machine-learning-ufal/datasets/vulnerability/unbalanced/glibc_data.csv", stringsAsFactors = FALSE)
# Criando Histogramas
par(mfrow=c(1,2))
barplot(table(input_balanced$Affected), main = "Balanced")
barplot(table(input_unbalanced$Affected), main = "Unbalanced")

Construção do Modelo
O primeiro passo concistem em importar packages que serão utilizadas no repositório [RCRAN] (https://cran.r-project.org/). Aqui são importadas packages relacionadas aos modelos de aprendizagem de máquina utlizados (arvores de decisão, regras, etc).
suppressMessages(library(RWeka))
suppressMessages(library(e1071))
suppressMessages(library(gmodels))
suppressMessages(library(C50))
suppressMessages(library(caret))
suppressMessages(library(irr))
suppressMessages(library(randomForest))
suppressMessages(library(mlr))
suppressMessages(library(evaluate))
suppressMessages(library(FSelector))
O segundo passo consistem em utilizar funções auxiliares que irão calcular a efetividade do nosso modelo. Nosso modelo será treinado utilizando o método de K-fold Cross Validation e a sua efetividade será demonstrada através de uma matriz de confusão (confusion matrix).
A técnica validação cruzada em k-fold (K-fold Cross Validation), consiste em dividir o conjunto total de dados em k subconjuntos mutuamente exclusivos do mesmo tamanho e, a partir disto, um subconjunto é utilizado para teste e os k-1 restantes são utilizados para estimação dos parâmetros. Esse processo será realizado 10 vezes durante o nosso estudo (k = 10). No final das 10 iterações será calculada a acurácia do modelo com o intuito de obter uma medida mais confiável.
Já a Matriz de confusão (Confusion Matrix) tem como objetivo calcular e representar a performance do algoritmo. Onde cada linha linha da matriz representa as instâncias em uma classe prevista, enquanto cada coluna representa as instâncias em uma classe real. Através da matriz de confusão nós podemos calcular diferentes medidas relacionadas ao grau de acertos e erros do nosso modelo.
# Function to calculate precision
precision <- function(tp, fp) {
precision <- tp / (tp + fp)
return(precision)
}
# Function to calculate recall
recall <- function(tp, fn) {
recall <- tp / (tp + fn)
return(recall)
}
# Function to calculate F-measure
f_measure <- function(tp, fp, fn) {
f_measure <-
(2 * precision(tp, fp) * recall(tp, fn)) / (recall(tp, fn) + precision(tp, fp))
return(f_measure)
}
# Function to calculate true_positive, true_negative, false_positive, false_negative
measures <- function(test, pred) {
true_positive <- 0
true_negative <- 0
false_positive <- 0
false_negative <- 0
for (i in 1:length(pred)) {
if (test[i] == 'VULNERABLE' && pred[i] == 'VULNERABLE') {
true_positive <- true_positive + 1
} else if (test[i] == 'NEUTRAL' && pred[i] == 'NEUTRAL') {
true_negative <- true_negative + 1
} else if (test[i] == 'NEUTRAL' && pred[i] == 'VULNERABLE') {
false_negative <- false_negative + 1
} else if (test[i] == 'VULNERABLE' && pred[i] == 'NEUTRAL') {
false_positive <- false_positive + 1
}
}
measures <-
c(
precision(true_positive, false_positive),
recall(true_positive, false_negative),
f_measure(true_positive, false_positive, false_negative)
)
return(measures)
}
Nesse trecho abaixo são criadas as funções auxiliares contendo os Algoritmos e Técnicas de Machine Learning Utilizadas no Projeto, são eles:
- J48[Arvore de Decisão]
- NaiveBayes
- SVM
- OneR [Regras]
- RandomForest [Arvores de Decisão]
- C50 [Arvore de Decisão]
As funções abaixo realizam o treinamento (train) e testes do nosso modelo (model) de detecção de bugs baseados em Metricas de Software. Lembrando que o treinamento e testes são utilizadas as técnicas de K-fold cross validation com 10 folds.
# Techiniques
executeJ48 <- function(dataset, folds) {
results <- lapply(folds, function(x) {
train <- dataset[-x,]
test <- dataset[x,]
model <- J48(train$Affected ~ ., data = train)
pred <- predict(model, test)
results <- measures(test$Affected, pred)
return(results)
})
}
executeNaiveBayes <- function(dataset, folds) {
results <- lapply(folds, function(x) {
train <- dataset[-x,]
test <- dataset[x,]
model <- naiveBayes(train, train$Affected, laplace = 1)
pred <- predict(model, test)
results <- measures(test$Affected, pred)
return(results)
})
}
executeSVM <- function(dataset, folds) {
results <- lapply(folds, function(x) {
train <- dataset[-x, ]
test <- dataset[x, ]
model <- svm(train$Affected ~ ., data = train)
pred <- predict(model, test)
results <- measures(test$Affected, pred)
return(results)
})
}
executeOneR <- function(dataset, folds) {
results <- lapply(folds, function(x) {
train <- dataset[-x, ]
test <- dataset[x, ]
model <- OneR(train$Affected ~ ., data = train)
pred <- predict(model, test)
results <- measures(test$Affected, pred)
return(results)
})
}
executeRandomForest <- function(dataset, folds) {
results <- lapply(folds, function(x) {
train <- dataset[-x, ]
test <- dataset[x, ]
model <- randomForest(train$Affected ~ ., data = train)
pred <- predict(model, test)
results <- measures(test$Affected, pred)
return(results)
})
}
executeC50 <- function(dataset, folds) {
results <- lapply(folds, function(x) {
train <- dataset[-x, ]
test <- dataset[x, ]
model <- C5.0(train$Affected ~ ., data = train)
pred <- predict(model, test)
results <- measures(test$Affected, pred)
return(results)
})
}
#Aux function to create the data frame
createDf <- function (){
results <<-
data.frame(
Project = character(),
Algorithm = character(),
Precision = character(),
Recall = character(),
"F-Measure" = character()
)
}
#Function to store the results in a data frame
finalResults <- function(resultsAlgo, project, algo){
results <<-
rbind(
results,
data.frame(
"Project" = project,
"Algorithm" = algo,
"Precision" = median(sapply(resultsAlgo, "[[", 1)),
"Recall" = median(sapply(resultsAlgo, "[[", 2)),
"F-Measure" = median(sapply(resultsAlgo, "[[", 3))
)
)
}
Por Último, temos o procesamento dos algoritmos de aprendizagem de máquina em todos os projetos coletados.
filenames = list.files(path = "/home/r4ph/R/machine-learning-ufal/datasets/vulnerability/balanced",
full.names = TRUE,
recursive = TRUE)
projects <- c("GlibC", "Httpd", "Kernel", "Mozilla", "Xen")
setwd("/home/r4ph/R/machine-learning-ufal/datasets/vulnerability/results/")
#Create data frame:
createDf()
#Apply Results Balanced
for (i in 1:length(filenames)) {
cat("Writting ")
dataset <- read.csv(filenames[i])
#project <- strsplit(filenames, split = '/')[[i]][9]
folds <- createFolds(dataset[1:27], k = 10, returnTrain = TRUE)
rows <- nrow(dataset)
cat(paste0("Input: ", rows, " rows in project... ", projects[i], "\n"))
#Results C50
finalResults(executeC50(dataset, folds), projects[i], "C50")
#Results Bayes
finalResults(executeNaiveBayes(dataset, folds), projects[i], "NaiveBayes")
#Algorithm SVM
finalResults(executeSVM(dataset, folds), projects[i], "SVM")
#Jr48
finalResults(executeJ48(dataset, folds), projects[i], "Jr48")
#OneR
finalResults(executeOneR(dataset, folds), projects[i], "OneR")
#RandomForest
finalResults(executeRandomForest(dataset, folds), projects[i], "RandomForest")
}
Resultados Alcançados
A tabela abaixo contém os resultados dos modelos avaliados. Na 2ª coluna são apresentados os projetos. Na 3ª são apresentados os algoritmos. Já nas colunas 4ª,5ª e 6ª colunas são apresentados os valores referentes a precisão, recall e f-measure.
A precisão é a taxa de instâncias relevantes entre as instâncias recuperadas, enquanto recall é a taxa de instâncias relevantes que foram recuperadas sobre o total quantidade de instâncias relevantes. Tanto a precisão quanto o recall são baseadas em um entendimento e medida de relevância. Já a f-measure consiste em calcular a média harmônica entre as duas.
resultsUnbal <- read.csv(file = "/home/r4ph/R/machine-learning-ufal/datasets/vulnerability/results/ml_balanced.csv", stringsAsFactors = FALSE)
resultsUnbal
Segue abaixo um gráfico de barras contendo os resultados descritos na tabela acima. O resultados demonstram que dentre todos os algoritmos analisados o técnica de classificação Random Forest obteve os melhores resultados com o f-measure variando em torno de 0.73 e 0.83 em todos os projetos, apesar do algoritmo Naive Bayes possui um pico de 0.95 no projeto Mozilla. Porém tal score é um caso isolado que não foi demonstrado nos outros projetos.
ggplot(resultsUnbal) +
geom_bar(aes(x = Algorithm, y = F.Measure, fill = Project, group = Project), position = "dodge", stat = "identity") +
geom_text( aes(x = Algorithm, y = F.Measure, label = round(F.Measure,2), group = Project),
check_overlap = TRUE, position = position_dodge(width = 1), vjust = -0.5, size = 2)

A tabela abaixo contém os resultados utilizando a técnica de seleção de dados Information Gain presentes na seção de tratamento de dados (#2) mantendo somente as features com um grau/valor de importância acima de 0.2.
resultsFeatures <- read.csv(file = "/home/r4ph/R/machine-learning-ufal/datasets/vulnerability/results/ml_features.csv", stringsAsFactors = FALSE)
resultsFeatures
Segue abaixo um gráfico de barras contendo os resultados descritos na tabela acima somente para as features que tiveram o valor de ganho de informação acima do threshold (0.2). O resultados revelam que dentre todos os algoritmos analisados o técnica de classificação Random Forest obteve os melhores resultados com o f-measure variando em torno de 0.71 e 0.88 em todos os projetos.
Um finding que deve ser ressaltado é de que utilizando a técnica de seleção de features, alguns algoritmos tiveram seus resultados melhorados em relação aos resultados anteriores (sem a técnica). Curiosamente, os algoritmos que tiveram um ligeira melhora são as técnicas relacionadas a árvore de decisão, porém os outros algoritmos como SVM e NaiveBayes tiveram um pequena redução da efetividade em termos gerais.
Essa pequena melhoria relacionada as técnicas baseadas em árvores está diretamente ligadadas as mesmas utilizaram ganho de informação (entropia) dentro dos seus algoritmos durante a fase de treinamento.
ggplot(resultsFeatures) +
geom_bar(aes(x = Algorithm, y = F.Measure, fill = Project, group = Project), position = "dodge", stat = "identity") +
geom_text( aes(x = Algorithm, y = F.Measure, label = round(F.Measure,2), group = Project),
check_overlap = TRUE, position = position_dodge(width = 1), vjust = -0.5, size = 2)

Conclusão
Técnicas de predição de bugs são de grande importância na área de engenharia de software, visto que podem levar a uma diminuição de custos e esforços por desenvolvedores, gerentes e envolvidos no ciclo de desenvolvimento. O presente trabalho procurou avaliar diferentes algoritmos de aprendizagem de máquina em métricas de software presentes em funções da linguagem C em 5 projetos.
Os resultados relevam que o modelo Ramdom Forest superou os demais dentre os projetos analisados. Esses resultados são similares a trabalhos anteriores presentes na literatura [1] que também utilizam métricas de software como input. Já em relação aos resultados dos algoritmos entre utilizando técnicas de seleção obtivemos uma ligeira melhora nos algoritmos que estão relacionados a técnica de arvores de decisão.
LS0tCnRpdGxlOiAiTWFjaGluZSBMZWFybmluZyBQcm9qZWN0IC0gVUZBTCAyMDE3LjIiCm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KCiMjIyMqKlByb2Zlc3NvcioqOiBFdmFuZHJvIGRlIEJhcnJvcyBDb3N0YQojIyMjKipBbHVubyoqOiBKYWlybyBSYXBoYWVsIE1vcmVpcmEgQ29ycmVpYSBkZSBTb3V6YS4KCk8gb2JqZXRpdm8gZGVzc2UgcmVsYXTDs3JpbyB0w6ljbmljbyDDqSBleHBsb3JhciBhIHByw6F0aWNhIGRlIHTDqWNuaWNhcyBkZSBhcHJlbmRpemFnZW0gZGUgbcOhcXVpbmEgZW0gdW0gZGF0YXNldCBjb250ZW5kbyBkYWRvcyBtw6l0cmljYXMgZGUgc29mdHdhcmUgZGUgcHJvamV0b3Mgb3BlbiBzb3VyY2UuIFBhcmEgcmVhbGl6YcOnw6NvIGRvIGV4cGVyaW1lbnRvIGZvaSB1dGlsaXphZGEgYSBMaW5ndWFnZW0gUiBqdW50YW1lbnRlIGRvIGF1eGlsaW8gZGUgYWxndW1hcyBiaWJsaW90ZWNhcyBkaXNwb27DrXZlaXMgbm8gcmVwb3NpdMOzcmlvIFtSQ1JBTl0gKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnLykuIAoKTyByZWxhdMOzcmlvIGVzdMOhIGVzdHJ1dHVyYWRvIGRhIHNlZ3VpbnRlIGZvcm1hOiAKCi0gSW50cm9kdcOnw6NvIGUgRGVzY3Jpw6fDo28gZG8gRGF0YXNldDsKLSBQcmVwYXJhw6fDo28gZG9zIERhZG9zOwotIENvbnN0cnXDp8OjbyBkbyBNb2RlbG87Ci0gUmVzdWx0YWRvczsKLSBDb25jbHVzw6NvLgoKIyMjSW50cm9kdcOnw6NvCgpPIE9iamV0aXZvIGdlcmFsIGRvIGVzdHVkbyDDqSBnZXJhciB1bSBtb2RlbG8gZGUgcHJlZGnDp8OjbyB1dGlsaXphbmRvIG3DqXRyaWNhcyBkZSBzb2Z0d2FyZSBjYXBheiBkZSBkZXRlY3RhciBidWdzIGVtIGFsdGVyYcOnw7VlcyBkZSBjw7NkaWdvcyBlbSBmdW7Dp8O1ZXMgbmEgbGluZ3VhZ2VtIEMuIE8gTW9kZWxvIGlyw6EgdXRpbGl6YXIgZGlmZXJlbnRlcyB0w6ljbmljYXMgZGUgYXByZW5kaXphZ2VtIGRlIG3DoXF1aW5hIGNvbSBvIGludHVpdG8gZGUgZ2VyYXIgdW0gbW9kZWxvIHByZWRpdGl2byBjYXBheiBkZSBwcmV2ZXIgYWxndW0gcG9zc8OtdmVsIGJ1ZyBlbSBmdXR1cmFzIGFsdGVyYcOnw7VlcyBuZXNzZXMgcHJvamV0b3MuIFBvcnRhbnRvLCBhIGdyYW5kZSBjb250cmlidWnDp8OjbyBkbyBtb2RlbG8gw6kgZGUgZXZpdGFyIHVtIG1haW9yIGVzZm9yw6dvIGRlIGNvcnJlw6fDtWVzIGVtIGZhc2VzIGRlIGRlc2Vudm9sdmltZW50byBjcsOtdGljYXMgYXRyYXbDqXMgZGEgcHJlZGnDp8OjbyBkZSBidWdzIGEgcGFydGlyIGRlIGFsdGVyYcOnw7VlcyBkZSB0cmVjaG9zIGRlIGPDs2RpZ28gZHVyYW50ZSBhIGZhc2UgZGUgZGVzZW52b2x2aW1lbnRvLgoKTyBEYXRhc2V0IGNvbnTDqW0gbcOpdHJpY2FzIGRlIHNvZnR3YXJlIGNvbGV0YWRhcyB2aWEgW0FQSSBVbmRlcnN0YW5kXSAoaHR0cHM6Ly9zY2l0b29scy5jb20vZmVhdHVyZS9tZXRyaWNzLykgZGUgRnVuw6fDtWVzIGRlIHByb2pldG9zIG9wZW4gc291cmNlIHByZXNlbnRlcyBubyBHaXRodWIgZXNjcml0byBuYSBsaW5ndWFnZW0gQy4gU2VndWUgb3MgcHJvamV0b3MgVXRpbGl6YWRvczoKCi0gW0dsaWJjXShodHRwczovL2dpdGh1Yi5jb20vbGF0dGVyYS9nbGliYykgLSBUaGUgR05VIEMgTGlicmFyeSBpcyB0aGUgc3RhbmRhcmQgc3lzdGVtIEMgbGlicmFyeSBmb3IgYWxsIEdOVSBzeXN0ZW1zCi0gW0h0dHBkXShodHRwczovL2dpdGh1Yi5jb20vYXBhY2hlL2h0dHBkKSAtIFdlYiBTZXJ2ZXIKLSBbS2VybmVsXShodHRwczovL2dpdGh1Yi5jb20vdG9ydmFsZHMvbGludXgpIC0gS2VybmVsIExpbnV4Ci0gW09wZW5WUE5dKGh0dHBzOi8vZ2l0aHViLmNvbS9tb3ppbGxhL29wZW52cG4pIC0gTW96aWxsYSBPcGVuVlBOCi0gW1hlbl0oaHR0cHM6Ly9naXRodWIuY29tL3hlbi1wcm9qZWN0L3hlbikgLSBWaXJ0dWFsIE1hY2hpbmUgTW9uaXRvciAoVk1NKQoKCkFzIG3DqXRyaWNhcyBkZSBzb2Z0d2FyZSBzw6NvIGRpdmlkaWRhcyBlbSAzIGNhdGVnb3JpYXM6CgotIE3DqXRyaWNhcyBkZSBDb21wbGV4aWRhZGU6IFPDo28gbcOpdHJpY2FzIHJlbGFjaW9uYWRhcyBhIGNvbXBsZXhpZGFkZSBkbyBjw7NkaWdvLiBFeDogQSBtw6lkaWEgZGEgY29tcGxleGlkYWRlIGNpY2xvbcOhdGljYSBwYXJhIHRvZGFzIGFzIGZ1bsOnw7VlcyBvdSBtw6l0b2RvcyBhbmluaGFkb3MuCgotIE3DqXRyaWNhcyBkZSBUYW1hbmhvOiBTw6NvIG3DqXRyaWNhcyByZWxhY2lvbmFkYXMgYW8gdGFtYW5obyBkbyBzb2Z0d2FyZS4gRXg6IFF1YW50aWRhZGUgZGUgbGluaGFzIGRlIGPDs2RpZ28gZGUgdW1hIGZ1bsOnw6NvIGVzcGVjw61maWNhLgoKLSBNw6l0cmljYXMgZGUgT3JpZW50YcOnw6NvIMOhIE9iamV0b3M6IFPDo28gbcOpdHJpY2FzIHJlbGFjaW9uYWRhcyBhbyBwYXJhZGlnbWEgZGUgb3JpZW50YcOnw6NvIMOhIG9iamV0b3MuIEV4OiBOw7ptZXJvIGRlIGNsYXNzZXMgZSBtw6l0b2Rvcy4KCioqT2JzKio6IEFzIG3DqXRyaWNhcyBkZSBvcmllbnRhw6fDo28gYSBvYmpldG9zIG7Do28gZXN0w6NvIHByZXNlbnRlcyBubyBkYXRhc2V0IGRvIGVzdHVkbywgdmlzdG8gcXVlIGEgbGluZ3VhZ2VtIEMgbsOjbyDDqSB1bWEgbGluZ3VhZ2VtIG9yaWVudGFkYSBhIG9iamV0b3MuCgpOYSB0YWJlbGEgYWJhaXhvLCBjYWRhIGxpbmhhIHJlcHJlc2VudGEgdW1hIGZ1bsOnw6NvIGRvIHByb2pldG8gY29udGVuZG8gb3MgdmFsb3JlcyBkYXMgcmVzcGVjdGl2YXMgbcOpdHJpY2FzIGRlIHNvZnR3YXJlLiBBcyBkaWZlcmVudGVzIG3DqXRyaWNhcyBzw6NvIGV4aWJpZGFzIGRhIHByaW1laXJhIGEgMjfCuiBjb2x1bmEuIErDoSBuYSAyOMK6IGNvbHVuYSBlc3TDoSBwcmVzZW50ZSBhIGNsYXNzaWZpY2HDp8OjbyBkYXF1ZWxhIGZ1bsOnw6NvLCBvdSBzZWphLCBzZSBhIG1lc21hIHBvc3N1aSB1bSBidWcgKCoqVlVMTkVSQUJMRSoqKSBvdSBuw6NvICgqKk5FVVRSQUwqKikuCgpgYGB7ciwgZWNobz1GQUxTRX0KaW5wdXRfc2FtcGxlIDwtIHJlYWQuY3N2KGZpbGUgPSAiL2hvbWUvcjRwaC9SL21hY2hpbmUtbGVhcm5pbmctdWZhbC9kYXRhc2V0cy92dWxuZXJhYmlsaXR5L2JhbGFuY2VkL2dsaWJjX2RhdGFfYmFsYW5jZWQuY3N2Iiwgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFKQpoZWFkKGlucHV0X3NhbXBsZSkKCmBgYAoKIyMjUHJlcGFyYcOnw6NvIGRvcyBkYWRvcwoKTmVzc2EgZmFzZSBvcyBkYWRvcyBzw6NvIHRyYXRhZG9zIGUgcHJlcGFyYWRvcyBhbnRlcyBkYSB1dGlsaXphw6fDo28gZG9zIG1lc21vIG5vcyBtb2RlbG9zIGRlIGFwcmVuZGl6YWdlbSBkZSBtw6FxdWluYS4gVmFsZSByZXNzYWx0YXIgcXVlIGVzc2Egw6kgdW1hIGRhcyBwcmluY2lwYWlzIGZhc2VzIGRlIGNvbnN0cnXDp8OjbyBkZSB1bSBtb2RlbG8sIHZpc3RvIHF1ZSBvcyBhbGdvcml0bW9zIGrDoSBlc3TDo28gcHJvbnRvcyBlIGZ1bmNpb25hbSBkaXJldGFtZW50ZSBkZSBhY29yZG8gY29tIG9zIGlucHV0cyBhcHJlc2VudGFkb3MuIFBvcnRhbnRvLCBhIHF1YWxpZGFkZSBkb3MgZGFkb3MgaW5mbHVlbmNpYSBkaXJldGFtZW50ZSBhIHF1YWxpZGFkZSBkbyBtb2RlbG8uCgojIyMjU2VsZcOnw6NvIGRlIEZlYXR1cmVzOgoKRW0gdG9kbyBlc3R1ZG8gw6kgaW1wb3J0YW50ZSByZWFsaXphciB1bWEgc2VsZcOnw6NvIGRhcyBmZWF0dXJlcyBtYWlzIHJlbGV2YW50ZXMsIGNhc28gaGFqYSBwb3NzaWJpbGlkYWRlLCBjb20gbyBpbnR1aXRvIGRlIG9idGVyIGRpdmVyc2FzIHZhbnRhZ2VucyBjb21vOgoKLSBEaW1pbnVpw6fDo28gZG8gdGVtcG8gZGUgdHJlaW5hbWVudG87Ci0gU2ltcGxpZmljYcOnw6NvIGRvIG1vZGVsbyBxdWUgZGV2ZSBsZXZhciB1bWEgbWFpb3IgaW50ZXJwcmV0YWJpbGlkYWRlIHBlbG9zIGVzcGVjaWFsaXN0YXMgZGEgw6FyZWEgZGUgbmXHtW9jaW87Ci0gQXVtZW50byBkYSBnZW5lcmFsaXphw6fDo28gZG9zIGRhZG9zLiBTYWJlLXNlIHF1ZSBlbSBtb2RlbG9zIGRlIHByZWRpw6fDo28gZGV2ZW1vcyBldml0YXIgdW1hIGdlbmVyYWxpemHDp8Ojby9lc3BlY2lhbGl6YcOnw6NvIGRvIG1vZGVsby4gUG9ydGFudG8gw6kgdW0gZ3JhbmRlIGRlc2FmaW8gZW5jb250cmEgZXNzZSAqdHJhZGUtb2ZmKiBlbnRyZSB1bSBtb2RlbG8gcXVlIG7Do28gc2VqYSBlc3BlY2lmaWNvIHVtIHVtIGRldGVybWluYWRvIGNvbmp1bnRvIGRlIHRyZWluYW1lbnRvLCBhbMOpbSBkZSBuw6NvIHNlciBtdWl0byBnZW5lcmFsaXN0YSBhIHBvbnRvIGRlIG7Do28gY29uc2VndWlyIGNsYXNzaWZpY2FyIGNvcnJldGFtZW50ZSBvcyBkYWRvcy4KCkR1cmFudGUgYSBmYXNlIGRlIHByZXBhcmHDp8Ojbywgw6kgcmVhbGl6YWRhIHVtYSB0YXJlZmEgZGUgY2xhc3NpZmljYcOnw6NvIGRvcyB2YWxvcmVzIGluZGl2aWR1YWlzIGRlIGNhZGEgZmVhdHVyZS4gT3MgdmFsb3JlcyBlc3TDo28gcmVsYWNpb25hZG9zIGFvIHNldSBncmF1IGRlIGltcG9ydMOibmNpYSBkZSBhY29yZG8gY29tIG8gbcOpdG9kbyBlc2NvbGhpZG8uIE9zIE3DqXRvZG9zIGVzY29saGlkb3MgZm9yYW06CgotICpDaGkgU3F1YXJlZCo7Ci0gKkluZm9ybWF0aW9uIEdhaW4qLgoKQmFzaWNhbWVudGUgb3MgZG9pcyB0ZXN0ZXMgYWNpbWFzIGNvbnNpc3RlbSBlbSBkZXRlcm1pbmFyIGEgZGVwZW5kw6puY2lhIGVudHJlIGR1YXMgdmFyacOhdmVpcy4gTmVzc2UgY2FzbywgYW5hbGlzYXIgcXVhbCB2YXJpw6F2ZWwgZGVwZW5kZW50ZSAoY2xhc3NpZmljYcOnw6NvIGRvIGJ1ZykgZXN0w6EgbWFpcyByZWxhY2lvbmFkYSBjb20gYSB2YXJpYWJlbCBpbmRlcGVuZGVudGUgKG3DqXRyaWNhcyBkZSBzb2Z0d2FyZSkuCgpBdHJhdsOpcyBkYSBUYWJlbGEgYWJhaXhvIMOpIHBvc3PDrXZlaXMgdmlzdWFsaXphciBvcyB2YWxvcmVzIGRlIGNhZGEgZmVhdHVyZSBubyBkYXRhc2V0IGRvIGVzdHVkby4gT3MgZG9pcyBhbGdvcml0bW9zOiAqSW5mb3JtYXRpb24gR2FpbiogZSAqQ2hpIFNxdWFyZWQqIGNsYXNzaWZpY2FyYW0gYSBtw6l0cmljYSAqQWx0Q291bnRMaW5lQ29kZSogKExpbmhhcyBkZSBjw7NkaWdvIGFsdGVyYWRhcykgY29udGVuZG8gYSBtYWlvcmlhIGRhcyBpbmZvcm1hw6fDtWVzIHNvYnJlIGEgdmFyacOhdmVsLWFsdm8gKmFmZmVjdGVkKiBjb20gdW0gdmFsb3IgZGUgMC4zOCBlIDAuNzcgcmVzcGVjdGl2YW1lbnRlLgoKYGBge3J9CmZlYXR1cmVzIDwtIG1ha2VDbGFzc2lmVGFzayhpZCA9IGRlcGFyc2Uoc3Vic3RpdHV0ZShpbnB1dF9zYW1wbGUpKSwgaW5wdXRfc2FtcGxlLCBjb2xuYW1lcyhpbnB1dF9zYW1wbGUpWzI4XSApCmZlYXR1cmVzU2VsZWN0aW9uID0gZ2VuZXJhdGVGaWx0ZXJWYWx1ZXNEYXRhKGZlYXR1cmVzLCBtZXRob2QgPSBjKCJpbmZvcm1hdGlvbi5nYWluIiwgImNoaS5zcXVhcmVkIikpCmZlYXR1cmVzU2VsZWN0aW9uJGRhdGEKYGBgCgpUYW1iw6ltIMOpIHBvc3NpdmVsIGF2YWxpYXIgYSBkaXN0cmlidWnDp8OjbyBkb3MgcmVzdWx0YWRvcyBkb3MgbcOpdG9kb3MgZGUgc2VsZcOnw6NvIGRlIGZlYXR1cmVzIHN1cHJhY2l0YWRvcyBhdHJhdsOpcyBkZSAqYm94cGxvdHMqOgoKYGBge3J9CnBhcihtZnJvdz1jKDEsMikpCmZvcihpIGluIDM6NCkgewoJYm94cGxvdChmZWF0dXJlc1NlbGVjdGlvbiRkYXRhW2ldLCBtYWluPW5hbWVzKGZlYXR1cmVzU2VsZWN0aW9uJGRhdGEpW2ldKQp9CmBgYAoKT3V0cmEgbWFuZWlyYSBkZSBhcHJlc2VudGFyIGEgZGlzdHJpYnVpw6fDo28gw6kgYXRyYXbDqXMgZGUgZ3LDoWZpY28gZGUgYmFycmFzIC0gKmJhcnBsb3RzKi4gTmEgaW1hZ2VtIGFiYWl4byBvIGVpeG8geCBjb3JyZXNwb25kZSBhcyBmZWF0dXJlcyB1dGlsaXphZGFzIG5vIGRhdGFzZXQgZSBvIGVpeG8geSBhbyBzZXUgZ3JhdSBkZSBpbXBvcnTDom5jaWEgcmVzcGVjdGl2YW1lbnRlLiBDb21vIHBvZGUgc2VyIHZpc3RvIGFzIHZhcmnDoXZlaXMuIEFsdENvdW50TGluZUNvZGUgZSBDb3VudExpbmVDb2RlIHRpdmVyYW0gcmVzdWx0YWRvcyBtZWxob3JlcyBxdWUgYXMgZGVtYWlzIHZhcmnDoXZlaXMuCgpgYGB7cn0KcGxvdEZpbHRlclZhbHVlcyhmZWF0dXJlc1NlbGVjdGlvbikKYGBgCgoKIyMjIyBCYWxhbmNlYW1lbnRvIGRvcyBEYWRvcwoKT3V0cm8gcGFzc28gaW1wb3J0YW50ZSBuYSBmYXNlIGRlIHByZXBhcmHDp8OjbyBkb3MgZGFkb3MgZXN0w6EgcmVsYWNpb25hZGEgYW8gYmFsYW5jZWFtZW50byBkb3MgZGFkb3MuIEVzc2EgZmFzZSBnZXJhIGFsZ3VtYXMgY29udHJvdsOpcnNpYXMgbmEgbGl0ZXJhdHVyYSwgdmlzdG8gcXVlIGFsZ3VucyBhdXRvcmVzIGRlZmVuZGVtIHF1ZSBvIGJhbGFuY2VhbWVudG8gcG9kZSBnZXJhciB1bSB2acOpcyBkZWZpbml0aXZvIGUgcGVybWFuZW50ZSBhbyBkYXRhc2V0LCBlbnF1YW50byBvdXRyb3MgZW5mYXRpemFtIHF1ZSBhIGV4Y2x1c8OjbyBkZSBkYWRvcyBkZXZlIHNlciBldml0YWRhLiBQb3LDqW0sIG8gbm9zc28gZXN0dWRvIGVzdMOhIHByZXNlbnRlIGVtIHVtIGNlbsOhcmlvIGRlIGRldGVjw6fDo28gZGUgYW5vbWFsaWFzLiBQb3J0YW50bywgZW0gY2Vuw6FyaW9zIG9uZGUgYSBkZXRlY8Onw6NvIGRlIGFub21hbGlhcyDDqSBjcnVjaWFsIGNvbW8gdHJhbnNhw6fDtWVzIGZyYXVkdWxlbnRhcyBub3MgYmFuY29zLCBpZGVudGlmaWNhw6fDo28gZGUgZG9lbsOnYXMgcmFyYXMsIGV0Yy4uLiBvcyBkYWRvcyBkZXZlbSBzZXIgYmFsYW5jZWFkb3MuIE5lc3NhIHNpdHVhw6fDo28sIG8gbsOjbyBiYWxhbmNlYW1lbnRvIGRvcyBkYWRvIGVtIHVtIG1vZGVsbyBwcmVkaXRpdm8gZGVzZW52b2x2aWRvIGNvbSBhbGdvcml0bW9zIGRlIGFwcmVuZGl6YWRvIGNvbnZlbmNpb25hbCBwb2RlIHNlciB0ZW5kZW5jaW9zbyBlIGltcHJlY2lzby4gCgpQYXJhIGF2YWxpYXIgdGFsIGltcGFjdG8sIGFiYWl4byBzZXLDoSBhcHJlc2VudGFkbyB1bSBncsOhZmljbyBjb250ZW5kbyBhIGRpc3RyaWJ1acOnw6NvIGRhcyBjbGFzc2VzIGRvIGRvaXMgdGlwb3MgZGUgY29uanVudG8gZGUgZGFkb3MuIE5hIGltYWdlbSDDqSBhcHJlc2VudGFkbyBkb2lzIGdyw6FmaWNvcyBkZSBiYXJyYXMgcXVlIGNvbnNpc3RlbSBlbSBkYWRvcyBiYWxhbmNlYWRvcyBlIG7Do28gYmFsYW5jZWFkb3MuIE9zIGRhZG9zIGJhbGFuY2VhZG9zIHBvc3N1ZW0gbyBudW1lcm8gZGUgY2xhc3NlcyBpZ3VhaXMsIG91IHNlamEsIHRlbW9zIGFzIG1lc21hIHF1YW50aWRhZGUgZGUgbGluaGFzIGRlIG3DqXRyaWNhcyBkZSBzb2Z0d2FyZXMgZGUgZnVuw6fDtWVzIGNvbSBidWdzIGUgc2VtIGJ1Z3MgcmVzcGVjdGl2YW1lbnRlLiBKw6Egbm9zIGRhZG9zIG7Do28gYmFsYW5jZWFkb3MgbyBub3RhbW9zIHVtYSBtZW5vciBleGlzdMOqbmNpYSBkZSBjbGFzc2VzIHJlbGFjaW9uYWRhcyBhIG7Do28gZXhpc3TDqm5jaWEgZGUgYnVncyBlbSBmdW7Dp8O1ZXMgZGUgY8OzZGlnbyBlbSBDIG5vcyBwcm9qZXRvcyBhbmFsaXNhZG9zLgoKCgpgYGB7cn0KaW5wdXRfYmFsYW5jZWQgPC0gcmVhZC5jc3YoZmlsZSA9ICIvaG9tZS9yNHBoL1IvbWFjaGluZS1sZWFybmluZy11ZmFsL2RhdGFzZXRzL3Z1bG5lcmFiaWxpdHkvYmFsYW5jZWQvZ2xpYmNfZGF0YV9iYWxhbmNlZC5jc3YiLCBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UpCmlucHV0X3VuYmFsYW5jZWQgPC0gcmVhZC5jc3YoZmlsZSA9ICIvaG9tZS9yNHBoL1IvbWFjaGluZS1sZWFybmluZy11ZmFsL2RhdGFzZXRzL3Z1bG5lcmFiaWxpdHkvdW5iYWxhbmNlZC9nbGliY19kYXRhLmNzdiIsIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRSkKIyBDcmlhbmRvIEhpc3RvZ3JhbWFzCnBhcihtZnJvdz1jKDEsMikpCmJhcnBsb3QodGFibGUoaW5wdXRfYmFsYW5jZWQkQWZmZWN0ZWQpLCBtYWluID0gIkJhbGFuY2VkIikKYmFycGxvdCh0YWJsZShpbnB1dF91bmJhbGFuY2VkJEFmZmVjdGVkKSwgbWFpbiA9ICJVbmJhbGFuY2VkIikKCgpgYGAKCiMjIyBDb25zdHJ1w6fDo28gZG8gTW9kZWxvCgpPIHByaW1laXJvIHBhc3NvIGNvbmNpc3RlbSBlbSBpbXBvcnRhciBwYWNrYWdlcyBxdWUgc2Vyw6NvIHV0aWxpemFkYXMgbm8gcmVwb3NpdMOzcmlvIFtSQ1JBTl0gKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnLykuIEFxdWkgc8OjbyBpbXBvcnRhZGFzIHBhY2thZ2VzIHJlbGFjaW9uYWRhcyBhb3MgbW9kZWxvcyBkZSBhcHJlbmRpemFnZW0gZGUgbcOhcXVpbmEgdXRsaXphZG9zIChhcnZvcmVzIGRlIGRlY2lzw6NvLCByZWdyYXMsIGV0YykuCgpgYGB7cn0Kc3VwcHJlc3NNZXNzYWdlcyhsaWJyYXJ5KFJXZWthKSkKc3VwcHJlc3NNZXNzYWdlcyhsaWJyYXJ5KGUxMDcxKSkKc3VwcHJlc3NNZXNzYWdlcyhsaWJyYXJ5KGdtb2RlbHMpKQpzdXBwcmVzc01lc3NhZ2VzKGxpYnJhcnkoQzUwKSkKc3VwcHJlc3NNZXNzYWdlcyhsaWJyYXJ5KGNhcmV0KSkKc3VwcHJlc3NNZXNzYWdlcyhsaWJyYXJ5KGlycikpCnN1cHByZXNzTWVzc2FnZXMobGlicmFyeShyYW5kb21Gb3Jlc3QpKQpzdXBwcmVzc01lc3NhZ2VzKGxpYnJhcnkobWxyKSkKc3VwcHJlc3NNZXNzYWdlcyhsaWJyYXJ5KGV2YWx1YXRlKSkKc3VwcHJlc3NNZXNzYWdlcyhsaWJyYXJ5KEZTZWxlY3RvcikpCmBgYAoKTyBzZWd1bmRvIHBhc3NvIGNvbnNpc3RlbSBlbSB1dGlsaXphciBmdW7Dp8O1ZXMgYXV4aWxpYXJlcyBxdWUgaXLDo28gY2FsY3VsYXIgYSBlZmV0aXZpZGFkZSBkbyBub3NzbyBtb2RlbG8uIE5vc3NvIG1vZGVsbyBzZXLDoSB0cmVpbmFkbyB1dGlsaXphbmRvIG8gbcOpdG9kbyBkZSAqSy1mb2xkIENyb3NzIFZhbGlkYXRpb24qIGUgYSBzdWEgZWZldGl2aWRhZGUgc2Vyw6EgZGVtb25zdHJhZGEgYXRyYXbDqXMgZGUgdW1hIG1hdHJpeiBkZSBjb25mdXPDo28gKCpjb25mdXNpb24gbWF0cml4KikuCgpBIHTDqWNuaWNhIHZhbGlkYcOnw6NvIGNydXphZGEgZW0gKmstZm9sZCogKCpLLWZvbGQgQ3Jvc3MgVmFsaWRhdGlvbiopLCBjb25zaXN0ZSBlbSBkaXZpZGlyIG8gY29uanVudG8gdG90YWwgZGUgZGFkb3MgZW0gayBzdWJjb25qdW50b3MgbXV0dWFtZW50ZSBleGNsdXNpdm9zIGRvIG1lc21vIHRhbWFuaG8gZSwgYSBwYXJ0aXIgZGlzdG8sIHVtIHN1YmNvbmp1bnRvIMOpIHV0aWxpemFkbyBwYXJhIHRlc3RlIGUgb3Mgay0xIHJlc3RhbnRlcyBzw6NvIHV0aWxpemFkb3MgcGFyYSBlc3RpbWHDp8OjbyBkb3MgcGFyw6JtZXRyb3MuIEVzc2UgcHJvY2Vzc28gc2Vyw6EgcmVhbGl6YWRvIDEwIHZlemVzIGR1cmFudGUgbyBub3NzbyBlc3R1ZG8gKCprID0gMTAqKS4gTm8gZmluYWwgZGFzIDEwIGl0ZXJhw6fDtWVzIHNlcsOhIGNhbGN1bGFkYSBhIGFjdXLDoWNpYSBkbyBtb2RlbG8gY29tIG8gaW50dWl0byBkZSBvYnRlciB1bWEgbWVkaWRhIG1haXMgY29uZmnDoXZlbC4KCkrDoSBhIE1hdHJpeiBkZSBjb25mdXPDo28gKCpDb25mdXNpb24gTWF0cml4KikgdGVtIGNvbW8gb2JqZXRpdm8gY2FsY3VsYXIgZSByZXByZXNlbnRhciBhIHBlcmZvcm1hbmNlIGRvIGFsZ29yaXRtby4gT25kZSBjYWRhIGxpbmhhIGxpbmhhIGRhIG1hdHJpeiByZXByZXNlbnRhIGFzIGluc3TDom5jaWFzIGVtIHVtYSBjbGFzc2UgcHJldmlzdGEsIGVucXVhbnRvIGNhZGEgY29sdW5hIHJlcHJlc2VudGEgYXMgaW5zdMOibmNpYXMgZW0gdW1hIGNsYXNzZSByZWFsLiBBdHJhdsOpcyBkYSBtYXRyaXogZGUgY29uZnVzw6NvIG7Ds3MgcG9kZW1vcyBjYWxjdWxhciBkaWZlcmVudGVzIG1lZGlkYXMgcmVsYWNpb25hZGFzIGFvIGdyYXUgZGUgYWNlcnRvcyBlIGVycm9zIGRvIG5vc3NvIG1vZGVsby4KCiAgCmBgYHtyfQoKIyBGdW5jdGlvbiB0byBjYWxjdWxhdGUgcHJlY2lzaW9uCnByZWNpc2lvbiA8LSBmdW5jdGlvbih0cCwgZnApIHsKICBwcmVjaXNpb24gPC0gdHAgLyAodHAgKyBmcCkKICAKICByZXR1cm4ocHJlY2lzaW9uKQp9CgojIEZ1bmN0aW9uIHRvIGNhbGN1bGF0ZSByZWNhbGwKcmVjYWxsIDwtIGZ1bmN0aW9uKHRwLCBmbikgewogIHJlY2FsbCA8LSB0cCAvICh0cCArIGZuKQogIAogIHJldHVybihyZWNhbGwpCn0KCiMgRnVuY3Rpb24gdG8gY2FsY3VsYXRlIEYtbWVhc3VyZQpmX21lYXN1cmUgPC0gZnVuY3Rpb24odHAsIGZwLCBmbikgewogIGZfbWVhc3VyZSA8LQogICAgKDIgKiBwcmVjaXNpb24odHAsIGZwKSAqIHJlY2FsbCh0cCwgZm4pKSAvIChyZWNhbGwodHAsIGZuKSArIHByZWNpc2lvbih0cCwgZnApKQogIAogIHJldHVybihmX21lYXN1cmUpCn0KCiMgRnVuY3Rpb24gdG8gY2FsY3VsYXRlIHRydWVfcG9zaXRpdmUsIHRydWVfbmVnYXRpdmUsIGZhbHNlX3Bvc2l0aXZlLCBmYWxzZV9uZWdhdGl2ZQptZWFzdXJlcyA8LSBmdW5jdGlvbih0ZXN0LCBwcmVkKSB7CiAgdHJ1ZV9wb3NpdGl2ZSA8LSAwCiAgdHJ1ZV9uZWdhdGl2ZSA8LSAwCiAgZmFsc2VfcG9zaXRpdmUgPC0gMAogIGZhbHNlX25lZ2F0aXZlIDwtIDAKICAKICBmb3IgKGkgaW4gMTpsZW5ndGgocHJlZCkpIHsKICAgIGlmICh0ZXN0W2ldID09ICdWVUxORVJBQkxFJyAmJiBwcmVkW2ldID09ICdWVUxORVJBQkxFJykgewogICAgICB0cnVlX3Bvc2l0aXZlIDwtIHRydWVfcG9zaXRpdmUgKyAxCiAgICB9IGVsc2UgaWYgKHRlc3RbaV0gPT0gJ05FVVRSQUwnICYmIHByZWRbaV0gPT0gJ05FVVRSQUwnKSB7CiAgICAgIHRydWVfbmVnYXRpdmUgPC0gdHJ1ZV9uZWdhdGl2ZSArIDEKICAgIH0gZWxzZSBpZiAodGVzdFtpXSA9PSAnTkVVVFJBTCcgJiYgcHJlZFtpXSA9PSAnVlVMTkVSQUJMRScpIHsKICAgICAgZmFsc2VfbmVnYXRpdmUgPC0gZmFsc2VfbmVnYXRpdmUgKyAxCiAgICB9IGVsc2UgaWYgKHRlc3RbaV0gPT0gJ1ZVTE5FUkFCTEUnICYmIHByZWRbaV0gPT0gJ05FVVRSQUwnKSB7CiAgICAgIGZhbHNlX3Bvc2l0aXZlIDwtIGZhbHNlX3Bvc2l0aXZlICsgMQogICAgfQogIH0KICAKICBtZWFzdXJlcyA8LQogICAgYygKICAgICAgcHJlY2lzaW9uKHRydWVfcG9zaXRpdmUsIGZhbHNlX3Bvc2l0aXZlKSwKICAgICAgcmVjYWxsKHRydWVfcG9zaXRpdmUsIGZhbHNlX25lZ2F0aXZlKSwKICAgICAgZl9tZWFzdXJlKHRydWVfcG9zaXRpdmUsIGZhbHNlX3Bvc2l0aXZlLCBmYWxzZV9uZWdhdGl2ZSkKICAgICkKICAKICByZXR1cm4obWVhc3VyZXMpCn0KYGBgCgpOZXNzZSB0cmVjaG8gYWJhaXhvIHPDo28gY3JpYWRhcyBhcyBmdW7Dp8O1ZXMgYXV4aWxpYXJlcyBjb250ZW5kbyBvcyBBbGdvcml0bW9zIGUgVMOpY25pY2FzIGRlIE1hY2hpbmUgTGVhcm5pbmcgVXRpbGl6YWRhcyBubyBQcm9qZXRvLCBzw6NvIGVsZXM6CgotIEo0OFtBcnZvcmUgZGUgRGVjaXPDo29dCi0gTmFpdmVCYXllcyAKLSBTVk0KLSBPbmVSIFtSZWdyYXNdCi0gUmFuZG9tRm9yZXN0IFtBcnZvcmVzIGRlIERlY2lzw6NvXQotIEM1MCBbQXJ2b3JlIGRlIERlY2lzw6NvXQoKQXMgZnVuw6fDtWVzIGFiYWl4byByZWFsaXphbSBvIHRyZWluYW1lbnRvICh0cmFpbikgZSB0ZXN0ZXMgZG8gbm9zc28gbW9kZWxvIChtb2RlbCkgZGUgZGV0ZWPDp8OjbyBkZSBidWdzIGJhc2VhZG9zIGVtIE1ldHJpY2FzIGRlIFNvZnR3YXJlLiBMZW1icmFuZG8gcXVlIG8gdHJlaW5hbWVudG8gZSB0ZXN0ZXMgc8OjbyB1dGlsaXphZGFzIGFzIHTDqWNuaWNhcyBkZSBLLWZvbGQgY3Jvc3MgdmFsaWRhdGlvbiBjb20gMTAgZm9sZHMuCgpgYGB7cn0KCiMgVGVjaGluaXF1ZXMKZXhlY3V0ZUo0OCA8LSBmdW5jdGlvbihkYXRhc2V0LCBmb2xkcykgewogIHJlc3VsdHMgPC0gbGFwcGx5KGZvbGRzLCBmdW5jdGlvbih4KSB7CiAgICB0cmFpbiA8LSBkYXRhc2V0Wy14LF0KICAgIHRlc3QgPC0gZGF0YXNldFt4LF0KICAgIG1vZGVsIDwtIEo0OCh0cmFpbiRBZmZlY3RlZCB+IC4sIGRhdGEgPSB0cmFpbikKICAgIHByZWQgPC0gcHJlZGljdChtb2RlbCwgdGVzdCkKICAgIHJlc3VsdHMgPC0gbWVhc3VyZXModGVzdCRBZmZlY3RlZCwgcHJlZCkKICAgIAogICAgcmV0dXJuKHJlc3VsdHMpCiAgfSkKICAKfQoKZXhlY3V0ZU5haXZlQmF5ZXMgPC0gZnVuY3Rpb24oZGF0YXNldCwgZm9sZHMpIHsKICByZXN1bHRzIDwtIGxhcHBseShmb2xkcywgZnVuY3Rpb24oeCkgewogICAgdHJhaW4gPC0gZGF0YXNldFsteCxdCiAgICB0ZXN0IDwtIGRhdGFzZXRbeCxdCiAgICBtb2RlbCA8LSBuYWl2ZUJheWVzKHRyYWluLCB0cmFpbiRBZmZlY3RlZCwgbGFwbGFjZSA9IDEpCiAgICBwcmVkIDwtIHByZWRpY3QobW9kZWwsIHRlc3QpCiAgICAKICAgIHJlc3VsdHMgPC0gbWVhc3VyZXModGVzdCRBZmZlY3RlZCwgcHJlZCkKICAgIAogICAgcmV0dXJuKHJlc3VsdHMpCiAgfSkKICAKfQoKZXhlY3V0ZVNWTSA8LSBmdW5jdGlvbihkYXRhc2V0LCBmb2xkcykgewogIHJlc3VsdHMgPC0gbGFwcGx5KGZvbGRzLCBmdW5jdGlvbih4KSB7CiAgICB0cmFpbiA8LSBkYXRhc2V0Wy14LCBdCiAgICB0ZXN0IDwtIGRhdGFzZXRbeCwgXQogICAgbW9kZWwgPC0gc3ZtKHRyYWluJEFmZmVjdGVkIH4gLiwgZGF0YSA9IHRyYWluKQogICAgcHJlZCA8LSBwcmVkaWN0KG1vZGVsLCB0ZXN0KQogICAgCiAgICByZXN1bHRzIDwtIG1lYXN1cmVzKHRlc3QkQWZmZWN0ZWQsIHByZWQpCiAgICAKICAgIHJldHVybihyZXN1bHRzKQogIH0pCiAgCn0KCmV4ZWN1dGVPbmVSIDwtIGZ1bmN0aW9uKGRhdGFzZXQsIGZvbGRzKSB7CiAgcmVzdWx0cyA8LSBsYXBwbHkoZm9sZHMsIGZ1bmN0aW9uKHgpIHsKICAgIHRyYWluIDwtIGRhdGFzZXRbLXgsIF0KICAgIHRlc3QgPC0gZGF0YXNldFt4LCBdCiAgICBtb2RlbCA8LSBPbmVSKHRyYWluJEFmZmVjdGVkIH4gLiwgZGF0YSA9IHRyYWluKQogICAgcHJlZCA8LSBwcmVkaWN0KG1vZGVsLCB0ZXN0KQogICAgCiAgICByZXN1bHRzIDwtIG1lYXN1cmVzKHRlc3QkQWZmZWN0ZWQsIHByZWQpCiAgICAKICAgIHJldHVybihyZXN1bHRzKQogIH0pCiAgCn0KCgpleGVjdXRlUmFuZG9tRm9yZXN0IDwtIGZ1bmN0aW9uKGRhdGFzZXQsIGZvbGRzKSB7CiAgcmVzdWx0cyA8LSBsYXBwbHkoZm9sZHMsIGZ1bmN0aW9uKHgpIHsKICAgIHRyYWluIDwtIGRhdGFzZXRbLXgsIF0KICAgIHRlc3QgPC0gZGF0YXNldFt4LCBdCiAgICBtb2RlbCA8LSByYW5kb21Gb3Jlc3QodHJhaW4kQWZmZWN0ZWQgfiAuLCBkYXRhID0gdHJhaW4pCiAgICBwcmVkIDwtIHByZWRpY3QobW9kZWwsIHRlc3QpCiAgICAKICAgIHJlc3VsdHMgPC0gbWVhc3VyZXModGVzdCRBZmZlY3RlZCwgcHJlZCkKICAgIAogICAgcmV0dXJuKHJlc3VsdHMpCiAgfSkKfQoKCmV4ZWN1dGVDNTAgPC0gZnVuY3Rpb24oZGF0YXNldCwgZm9sZHMpIHsKICByZXN1bHRzIDwtIGxhcHBseShmb2xkcywgZnVuY3Rpb24oeCkgewogICAgdHJhaW4gPC0gZGF0YXNldFsteCwgXQogICAgdGVzdCA8LSBkYXRhc2V0W3gsIF0KICAgIG1vZGVsIDwtIEM1LjAodHJhaW4kQWZmZWN0ZWQgfiAuLCBkYXRhID0gdHJhaW4pCiAgICBwcmVkIDwtIHByZWRpY3QobW9kZWwsIHRlc3QpCiAgICByZXN1bHRzIDwtIG1lYXN1cmVzKHRlc3QkQWZmZWN0ZWQsIHByZWQpCiAgICByZXR1cm4ocmVzdWx0cykKICB9KQp9CiAgCiNBdXggZnVuY3Rpb24gdG8gY3JlYXRlIHRoZSBkYXRhIGZyYW1lCmNyZWF0ZURmIDwtIGZ1bmN0aW9uICgpewogIHJlc3VsdHMgPDwtCiAgICBkYXRhLmZyYW1lKAogICAgICBQcm9qZWN0ID0gY2hhcmFjdGVyKCksCiAgICAgIEFsZ29yaXRobSA9IGNoYXJhY3RlcigpLAogICAgICBQcmVjaXNpb24gPSBjaGFyYWN0ZXIoKSwKICAgICAgUmVjYWxsID0gY2hhcmFjdGVyKCksCiAgICAgICJGLU1lYXN1cmUiID0gY2hhcmFjdGVyKCkKICAgICkKfQoKI0Z1bmN0aW9uIHRvIHN0b3JlIHRoZSByZXN1bHRzIGluIGEgZGF0YSBmcmFtZQpmaW5hbFJlc3VsdHMgPC0gZnVuY3Rpb24ocmVzdWx0c0FsZ28sIHByb2plY3QsIGFsZ28pewogIHJlc3VsdHMgPDwtCiAgICByYmluZCgKICAgICAgcmVzdWx0cywKICAgICAgZGF0YS5mcmFtZSgKICAgICAgICAiUHJvamVjdCIgPSBwcm9qZWN0LAogICAgICAgICJBbGdvcml0aG0iID0gYWxnbywKICAgICAgICAiUHJlY2lzaW9uIiA9IG1lZGlhbihzYXBwbHkocmVzdWx0c0FsZ28sICJbWyIsIDEpKSwKICAgICAgICAiUmVjYWxsIiA9IG1lZGlhbihzYXBwbHkocmVzdWx0c0FsZ28sICJbWyIsIDIpKSwKICAgICAgICAiRi1NZWFzdXJlIiA9IG1lZGlhbihzYXBwbHkocmVzdWx0c0FsZ28sICJbWyIsIDMpKQogICAgICApCiAgICApCn0KCgpgYGAKClBvciDDmmx0aW1vLCB0ZW1vcyBvIHByb2Nlc2FtZW50byBkb3MgYWxnb3JpdG1vcyBkZSBhcHJlbmRpemFnZW0gZGUgbcOhcXVpbmEgZW0gdG9kb3Mgb3MgcHJvamV0b3MgY29sZXRhZG9zLgoKYGBge3J9CmZpbGVuYW1lcyA9IGxpc3QuZmlsZXMocGF0aCA9ICIvaG9tZS9yNHBoL1IvbWFjaGluZS1sZWFybmluZy11ZmFsL2RhdGFzZXRzL3Z1bG5lcmFiaWxpdHkvYmFsYW5jZWQiLAogICAgICAgICAgICAgICAgICAgICAgIGZ1bGwubmFtZXMgPSBUUlVFLAogICAgICAgICAgICAgICAgICAgICAgIHJlY3Vyc2l2ZSA9IFRSVUUpCgpwcm9qZWN0cyA8LSBjKCJHbGliQyIsICJIdHRwZCIsICJLZXJuZWwiLCAiTW96aWxsYSIsICJYZW4iKQpzZXR3ZCgiL2hvbWUvcjRwaC9SL21hY2hpbmUtbGVhcm5pbmctdWZhbC9kYXRhc2V0cy92dWxuZXJhYmlsaXR5L3Jlc3VsdHMvIikKCiNDcmVhdGUgZGF0YSBmcmFtZToKY3JlYXRlRGYoKQoKI0FwcGx5IFJlc3VsdHMgQmFsYW5jZWQKZm9yIChpIGluIDE6bGVuZ3RoKGZpbGVuYW1lcykpIHsKICAKICBjYXQoIldyaXR0aW5nICIpCiAgZGF0YXNldCA8LSByZWFkLmNzdihmaWxlbmFtZXNbaV0pCiAgI3Byb2plY3QgPC0gc3Ryc3BsaXQoZmlsZW5hbWVzLCBzcGxpdCA9ICcvJylbW2ldXVs5XQogIGZvbGRzIDwtIGNyZWF0ZUZvbGRzKGRhdGFzZXRbMToyN10sIGsgPSAxMCwgcmV0dXJuVHJhaW4gPSBUUlVFKQogIAogIHJvd3MgPC0gbnJvdyhkYXRhc2V0KQogIGNhdChwYXN0ZTAoIklucHV0OiAiLCByb3dzLCAiIHJvd3MgaW4gcHJvamVjdC4uLiAiLCBwcm9qZWN0c1tpXSwgIlxuIikpCiAgCiAgI1Jlc3VsdHMgQzUwCiAgZmluYWxSZXN1bHRzKGV4ZWN1dGVDNTAoZGF0YXNldCwgZm9sZHMpLCBwcm9qZWN0c1tpXSwgIkM1MCIpCiAgI1Jlc3VsdHMgQmF5ZXMKICBmaW5hbFJlc3VsdHMoZXhlY3V0ZU5haXZlQmF5ZXMoZGF0YXNldCwgZm9sZHMpLCBwcm9qZWN0c1tpXSwgIk5haXZlQmF5ZXMiKQogICNBbGdvcml0aG0gU1ZNCiAgZmluYWxSZXN1bHRzKGV4ZWN1dGVTVk0oZGF0YXNldCwgZm9sZHMpLCBwcm9qZWN0c1tpXSwgIlNWTSIpCiAgI0pyNDgKICBmaW5hbFJlc3VsdHMoZXhlY3V0ZUo0OChkYXRhc2V0LCBmb2xkcyksIHByb2plY3RzW2ldLCAiSnI0OCIpCiAgI09uZVIKICBmaW5hbFJlc3VsdHMoZXhlY3V0ZU9uZVIoZGF0YXNldCwgZm9sZHMpLCBwcm9qZWN0c1tpXSwgIk9uZVIiKQogICNSYW5kb21Gb3Jlc3QKICBmaW5hbFJlc3VsdHMoZXhlY3V0ZVJhbmRvbUZvcmVzdChkYXRhc2V0LCBmb2xkcyksIHByb2plY3RzW2ldLCAiUmFuZG9tRm9yZXN0IikKICAKfQoKYGBgCgoKIyMjUmVzdWx0YWRvcyBBbGNhbsOnYWRvcwoKQSB0YWJlbGEgYWJhaXhvIGNvbnTDqW0gb3MgcmVzdWx0YWRvcyBkb3MgbW9kZWxvcyBhdmFsaWFkb3MuIE5hIDLCqiBjb2x1bmEgc8OjbyBhcHJlc2VudGFkb3Mgb3MgcHJvamV0b3MuIE5hIDPCqiBzw6NvIGFwcmVzZW50YWRvcyBvcyBhbGdvcml0bW9zLiBKw6EgbmFzIGNvbHVuYXMgNMKqLDXCqiBlIDbCqiBjb2x1bmFzIHPDo28gYXByZXNlbnRhZG9zIG9zIHZhbG9yZXMgcmVmZXJlbnRlcyBhIHByZWNpc8OjbywgcmVjYWxsIGUgZi1tZWFzdXJlLgoKQSBwcmVjaXPDo28gw6kgYSB0YXhhIGRlIGluc3TDom5jaWFzIHJlbGV2YW50ZXMgZW50cmUgYXMgaW5zdMOibmNpYXMgcmVjdXBlcmFkYXMsIGVucXVhbnRvIHJlY2FsbCDDqSBhIHRheGEgZGUgaW5zdMOibmNpYXMgcmVsZXZhbnRlcyBxdWUgZm9yYW0gcmVjdXBlcmFkYXMgc29icmUgbyB0b3RhbCBxdWFudGlkYWRlIGRlIGluc3TDom5jaWFzIHJlbGV2YW50ZXMuIFRhbnRvIGEgcHJlY2lzw6NvIHF1YW50byBvIHJlY2FsbCBzw6NvIGJhc2VhZGFzIGVtIHVtIGVudGVuZGltZW50byBlIG1lZGlkYSBkZSByZWxldsOibmNpYS4gSsOhIGEgZi1tZWFzdXJlIGNvbnNpc3RlIGVtIGNhbGN1bGFyIGEgbcOpZGlhIGhhcm3DtG5pY2EgZW50cmUgYXMgZHVhcy4KCmBgYHtyfQpyZXN1bHRzVW5iYWwgPC0gcmVhZC5jc3YoZmlsZSA9ICIvaG9tZS9yNHBoL1IvbWFjaGluZS1sZWFybmluZy11ZmFsL2RhdGFzZXRzL3Z1bG5lcmFiaWxpdHkvcmVzdWx0cy9tbF9iYWxhbmNlZC5jc3YiLCBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UpCnJlc3VsdHNVbmJhbApgYGAKClNlZ3VlIGFiYWl4byB1bSBncsOhZmljbyBkZSBiYXJyYXMgY29udGVuZG8gb3MgcmVzdWx0YWRvcyBkZXNjcml0b3MgbmEgdGFiZWxhIGFjaW1hLiBPIHJlc3VsdGFkb3MgZGVtb25zdHJhbSBxdWUgZGVudHJlIHRvZG9zIG9zIGFsZ29yaXRtb3MgYW5hbGlzYWRvcyBvIHTDqWNuaWNhIGRlIGNsYXNzaWZpY2HDp8OjbyAqUmFuZG9tIEZvcmVzdCogb2J0ZXZlIG9zIG1lbGhvcmVzIHJlc3VsdGFkb3MgY29tIG8gZi1tZWFzdXJlIHZhcmlhbmRvIGVtIHRvcm5vIGRlIDAuNzMgZSAwLjgzIGVtIHRvZG9zIG9zIHByb2pldG9zLCBhcGVzYXIgZG8gYWxnb3JpdG1vIE5haXZlIEJheWVzIHBvc3N1aSB1bSBwaWNvIGRlIDAuOTUgbm8gcHJvamV0byBNb3ppbGxhLiBQb3LDqW0gdGFsICpzY29yZSogw6kgdW0gY2FzbyBpc29sYWRvIHF1ZSBuw6NvIGZvaSBkZW1vbnN0cmFkbyBub3Mgb3V0cm9zIHByb2pldG9zLgoKYGBge3J9CmdncGxvdChyZXN1bHRzVW5iYWwpICsKICBnZW9tX2JhcihhZXMoeCA9IEFsZ29yaXRobSwgeSA9IEYuTWVhc3VyZSwgZmlsbCA9IFByb2plY3QsIGdyb3VwID0gUHJvamVjdCksIHBvc2l0aW9uID0gImRvZGdlIiwgc3RhdCA9ICJpZGVudGl0eSIpICsKICBnZW9tX3RleHQoIGFlcyh4ID0gQWxnb3JpdGhtLCB5ID0gRi5NZWFzdXJlLCBsYWJlbCA9IHJvdW5kKEYuTWVhc3VyZSwyKSwgZ3JvdXAgPSBQcm9qZWN0KSwKICAgICAgICAgICAgIGNoZWNrX292ZXJsYXAgPSBUUlVFLCBwb3NpdGlvbiA9IHBvc2l0aW9uX2RvZGdlKHdpZHRoID0gMSksIHZqdXN0ID0gLTAuNSwgc2l6ZSA9IDIpCmBgYAoKCkEgdGFiZWxhIGFiYWl4byBjb250w6ltIG9zIHJlc3VsdGFkb3MgdXRpbGl6YW5kbyBhIHTDqWNuaWNhIGRlIHNlbGXDp8OjbyBkZSBkYWRvcyAqSW5mb3JtYXRpb24gR2FpbiogcHJlc2VudGVzIG5hIHNlw6fDo28gZGUgdHJhdGFtZW50byBkZSBkYWRvcyAoIzIpIG1hbnRlbmRvIHNvbWVudGUgYXMgKmZlYXR1cmVzKiBjb20gdW0gZ3JhdS92YWxvciBkZSBpbXBvcnTDom5jaWEgYWNpbWEgZGUgMC4yLgoKCmBgYHtyfQpyZXN1bHRzRmVhdHVyZXMgPC0gcmVhZC5jc3YoZmlsZSA9ICIvaG9tZS9yNHBoL1IvbWFjaGluZS1sZWFybmluZy11ZmFsL2RhdGFzZXRzL3Z1bG5lcmFiaWxpdHkvcmVzdWx0cy9tbF9mZWF0dXJlcy5jc3YiLCBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UpCnJlc3VsdHNGZWF0dXJlcwpgYGAKClNlZ3VlIGFiYWl4byB1bSBncsOhZmljbyBkZSBiYXJyYXMgY29udGVuZG8gb3MgcmVzdWx0YWRvcyBkZXNjcml0b3MgbmEgdGFiZWxhIGFjaW1hIHNvbWVudGUgcGFyYSBhcyBmZWF0dXJlcyBxdWUgdGl2ZXJhbSBvIHZhbG9yIGRlIGdhbmhvIGRlIGluZm9ybWHDp8OjbyBhY2ltYSBkbyAqdGhyZXNob2xkKiAoMC4yKS4gTyByZXN1bHRhZG9zIHJldmVsYW0gcXVlIGRlbnRyZSB0b2RvcyBvcyBhbGdvcml0bW9zIGFuYWxpc2Fkb3MgbyB0w6ljbmljYSBkZSBjbGFzc2lmaWNhw6fDo28gKlJhbmRvbSBGb3Jlc3QqIG9idGV2ZSBvcyBtZWxob3JlcyByZXN1bHRhZG9zIGNvbSBvIGYtbWVhc3VyZSB2YXJpYW5kbyBlbSB0b3JubyBkZSAwLjcxIGUgMC44OCBlbSB0b2RvcyBvcyBwcm9qZXRvcy4KClVtIGZpbmRpbmcgcXVlIGRldmUgc2VyIHJlc3NhbHRhZG8gw6kgZGUgcXVlIHV0aWxpemFuZG8gYSB0w6ljbmljYSBkZSBzZWxlw6fDo28gZGUgZmVhdHVyZXMsIGFsZ3VucyBhbGdvcml0bW9zIHRpdmVyYW0gc2V1cyByZXN1bHRhZG9zIG1lbGhvcmFkb3MgZW0gcmVsYcOnw6NvIGFvcyByZXN1bHRhZG9zIGFudGVyaW9yZXMgKHNlbSBhIHTDqWNuaWNhKS4gQ3VyaW9zYW1lbnRlLCBvcyBhbGdvcml0bW9zIHF1ZSB0aXZlcmFtIHVtIGxpZ2VpcmEgbWVsaG9yYSBzw6NvIGFzIHTDqWNuaWNhcyByZWxhY2lvbmFkYXMgYSDDoXJ2b3JlIGRlIGRlY2lzw6NvLCBwb3LDqW0gb3Mgb3V0cm9zIGFsZ29yaXRtb3MgY29tbyBTVk0gZSBOYWl2ZUJheWVzIHRpdmVyYW0gdW0gcGVxdWVuYSByZWR1w6fDo28gZGEgZWZldGl2aWRhZGUgZW0gdGVybW9zIGdlcmFpcy4KCkVzc2EgcGVxdWVuYSBtZWxob3JpYSByZWxhY2lvbmFkYSBhcyB0w6ljbmljYXMgYmFzZWFkYXMgZW0gw6Fydm9yZXMgZXN0w6EgZGlyZXRhbWVudGUgbGlnYWRhZGFzIGFzIG1lc21hcyB1dGlsaXphcmFtIGdhbmhvIGRlIGluZm9ybWHDp8OjbyAoZW50cm9waWEpIGRlbnRybyBkb3Mgc2V1cyBhbGdvcml0bW9zIGR1cmFudGUgYSBmYXNlIGRlIHRyZWluYW1lbnRvLgoKYGBge3J9CmdncGxvdChyZXN1bHRzRmVhdHVyZXMpICsKICBnZW9tX2JhcihhZXMoeCA9IEFsZ29yaXRobSwgeSA9IEYuTWVhc3VyZSwgZmlsbCA9IFByb2plY3QsIGdyb3VwID0gUHJvamVjdCksIHBvc2l0aW9uID0gImRvZGdlIiwgc3RhdCA9ICJpZGVudGl0eSIpICsKICBnZW9tX3RleHQoIGFlcyh4ID0gQWxnb3JpdGhtLCB5ID0gRi5NZWFzdXJlLCBsYWJlbCA9IHJvdW5kKEYuTWVhc3VyZSwyKSwgZ3JvdXAgPSBQcm9qZWN0KSwKICAgICAgICAgICAgIGNoZWNrX292ZXJsYXAgPSBUUlVFLCBwb3NpdGlvbiA9IHBvc2l0aW9uX2RvZGdlKHdpZHRoID0gMSksIHZqdXN0ID0gLTAuNSwgc2l6ZSA9IDIpCmBgYAoKCgojIyMgQ29uY2x1c8OjbwoKVMOpY25pY2FzIGRlIHByZWRpw6fDo28gZGUgYnVncyBzw6NvIGRlIGdyYW5kZSBpbXBvcnTDom5jaWEgbmEgw6FyZWEgZGUgZW5nZW5oYXJpYSBkZSBzb2Z0d2FyZSwgdmlzdG8gcXVlIHBvZGVtIGxldmFyIGEgdW1hIGRpbWludWnDp8OjbyBkZSBjdXN0b3MgZSBlc2ZvcsOnb3MgcG9yIGRlc2Vudm9sdmVkb3JlcywgZ2VyZW50ZXMgZSBlbnZvbHZpZG9zIG5vIGNpY2xvIGRlIGRlc2Vudm9sdmltZW50by4gTyBwcmVzZW50ZSB0cmFiYWxobyBwcm9jdXJvdSBhdmFsaWFyIGRpZmVyZW50ZXMgYWxnb3JpdG1vcyBkZSBhcHJlbmRpemFnZW0gZGUgbcOhcXVpbmEgZW0gbcOpdHJpY2FzIGRlIHNvZnR3YXJlIHByZXNlbnRlcyBlbSBmdW7Dp8O1ZXMgZGEgbGluZ3VhZ2VtIEMgZW0gNSBwcm9qZXRvcy4gCgpPcyByZXN1bHRhZG9zIHJlbGV2YW0gcXVlIG8gbW9kZWxvICpSYW1kb20gRm9yZXN0KiBzdXBlcm91IG9zIGRlbWFpcyBkZW50cmUgb3MgcHJvamV0b3MgYW5hbGlzYWRvcy4gRXNzZXMgcmVzdWx0YWRvcyBzw6NvIHNpbWlsYXJlcyBhIHRyYWJhbGhvcyBhbnRlcmlvcmVzIHByZXNlbnRlcyBuYSBsaXRlcmF0dXJhIFsxXSBxdWUgdGFtYsOpbSB1dGlsaXphbSBtw6l0cmljYXMgZGUgc29mdHdhcmUgY29tbyBpbnB1dC4gSsOhIGVtIHJlbGHDp8OjbyBhb3MgcmVzdWx0YWRvcyBkb3MgYWxnb3JpdG1vcyBlbnRyZSB1dGlsaXphbmRvIHTDqWNuaWNhcyBkZSBzZWxlw6fDo28gb2J0aXZlbW9zIHVtYSBsaWdlaXJhIG1lbGhvcmEgbm9zIGFsZ29yaXRtb3MgcXVlIGVzdMOjbyByZWxhY2lvbmFkb3MgYSB0w6ljbmljYSBkZSBhcnZvcmVzIGRlIGRlY2lzw6NvLgoKCiMjIyBSZWZlcsOqbmNpYXMKClsxXVJ1Y2hpa2EgTWFsaG90cmEuIDIwMTUuIEEgc3lzdGVtYXRpYyByZXZpZXcgb2YgbWFjaGluZSBsZWFybmluZyB0ZWNobmlxdWVzIGZvciBzb2Z0d2FyZSBmYXVsdCBwcmVkaWN0aW9uLiBBcHBsLiBTb2Z0IENvbXB1dC4gMjcsIEMgKEZlYnJ1YXJ5IDIwMTUpLCA1MDQtNTE4LiBET0k9aHR0cDovL2R4LmRvaS5vcmcvMTAuMTAxNi9qLmFzb2MuMjAxNC4xMS4wMjMKClsyXVdpdHRlbiwgSS4gSC47IEZyYW5rLCBFLjsgSGFsbCwgTS4gQS4gRGF0YSBNaW5pbmc6IFByYXRpY2FsIE1hY2hpbmUgTGVhcm5pbmcgVG9vbHMgYW5kIFRlY2huaXF1ZXMuCkVsc2V2aWVyLiAyMDExCgpbM11GaXNjaGV0dGksIFQuIERhdGEgQW5hbHlzaXMgd2l0aCBSLiBQYWNrdCBQdWJsaXNoaW5nLiAyMDE1CgpbNF0gKGh0dHBzOi8vbWxyLW9yZy5naXRodWIuaW8vbWxyL2FydGljbGVzL3R1dG9yaWFsL2RldmVsL2ZlYXR1cmVfc2VsZWN0aW9uLmh0bWwpCgpbNV0gKGh0dHBzOi8vbWF0bG9mZi53b3JkcHJlc3MuY29tLzIwMTUvMDkvMjkvdW5iYWxhbmNlZC1kYXRhLWlzLWEtcHJvYmxlbS1uby1iYWxhbmNlZC1kYXRhLWlzLXdvcnNlLykKCls2XSAoaHR0cHM6Ly93d3cucmRvY3VtZW50YXRpb24ub3JnLykKCg==