Análise Multivariada - Atividade 03

Análise de Agrupamentos (resumo)

O que é?

  • Grupo de técnicas multivariadas cuja finalidade principal é agregar objetos com base nas características que eles possuem.

  • Classifica objetos de modo que cada objeto é semelhante aos outros no agrupamento com base em um conjunto de características escolhidas. Os agrupamentos resultantes de objetos devem então exibir:

  1. Elevada homogeneidade interna (dentro dos agrupamentos);
  2. Elevada heterogeneidade externa (entre agrupamentos).
  • Assim, se a classificação for bem sucedida, teremos graficamente:
  1. Os objetos dentro de um mesmo agrupamentos estarão próximos;
  2. Objetos em diferentes agrupamentos estarão distantes.
  • Geralmente envolve pelo menos três passos:
  1. O primeiro é a medida de alguma forma de similaridade ou associação entre as entidades para determinar quantos grupos realmente existem na amostra.
  2. O segundo passo é o real processo de agrupamento, onde entidades são particionadas em grupos (agrupamentos).
  3. O último passo é estabelecer o perfil das pessoas ou variáveis para determinar sua composição.

Desenvolvimento conceitual

Os papéis mais comuns que a análise de agrupamentos pode desempenhar em desenvolvimento conceitual incluem os seguintes:

  • Redução de dados: A análise de agrupamentos pode realizar esse procedimento de redução de dados objetivamente pela redução da informação de uma população inteira ou de uma amostra para a informação sobre subgrupos específicos e menores.

  • Geração de hipóteses: A análise de agrupamentos também é útil quando um pesquisador deseja desenvolver hipóteses relativas à natureza dos dados ou examinar hipóteses previamente estabelecidas.

Advertências

Em qualquer uso da análise de agrupamentos o pesquisador deve ter especial cuidado para garantir que forte suporte conceitual anteceda a aplicação da técnica.

  1. A análise de agrupamentos é descritiva, não-teórica e não-inferencial;

  2. Não tem base estatística sobre a qual esboçar inferências de uma amostra para uma população;

  3. Muitos clamam que é apenas uma técnica exploratória;

  4. Nada garante soluções únicas, já que a pertinência a um agrupamento para qualquer número de soluções depende de muitos elementos do procedimento. Muitas soluções diferentes podem ser obtidas pela variação de um ou mais elementos;

  5. Se possível, a análise de agrupamentos deve ser aplicada a partir de um modo confirmatório, usando-a para identificar grupos que já têm uma fundamentação conceitual estabelecida quanto à existência dos mesmos.

  6. A análise de agrupamentos sempre criará agrupamentos, independentemente da existência real de alguma estrutura nos dados;

  7. A solução de agrupamentos não é generalizável, pois é totalmente dependente das variáveis usadas como base para a medida de similaridade.

Análise Fatorial versus Análise de Agrupamentos

São semelhantes em seu objetivo de avaliar estrutura inerente aos dados.

  1. Análise Fatorial
    1. agrega objetos;
    2. faz os agrupamentos com base em padrões de variação (correlação) nos dados.
  2. Análise de Agrupamentos
    1. está prioritariamente interessada em agregar variáveis;
    2. faz agregados baseados em distância (proximidade).

Como funciona?

  • A análise de agrupamentos executa uma tarefa inata a todos os indivíduos – reconhecimento de padrões e agrupamento.
  • O objetivo principal é definir a estrutura dos dados colocando as observações mais parecidas em grupos. Para conseguir isso, devemos tratar de três questões básicas:
  1. Como medir a similaridade?
  2. Como formar os agrupamentos?
  3. Quantos grupos formar?

Análise descritiva

library(pacman)
library(rstatix)
library(e1071)
library(readxl)
library(tidyverse)
library(dplyr)
library(kableExtra)
library(knitr)
library(ggplot2)
library(RColorBrewer)
library(corrplot)
library(ggcorrplot)
library(data.table)
library(cluster)
pacman::p_load(knitr, captioner, bundesligR, stringr)

table_nums <- captioner::captioner(prefix = "Tabela")
tab.1_cap <- table_nums(name = "tab_1", 
                         caption = "Características básicas do dataset credit")
tab.2_cap <- table_nums(name = "tab_2", 
                         caption = "Descrições das variáveis do dataset credit")

tab.3_cap <- table_nums(name = "tab_3", 
                         caption = "Amostra do dataset credit")

tab.4_cap <- table_nums(name = "tab_4", 
                         caption = "Estatísticas resumo das variáveis quantitativas do dataset credit")

tab.5_cap <- table_nums(name = "tab_5", 
                         caption = "Frequências relativas da variável SEXO")

tab.6_cap <- table_nums(name = "tab_6", 
                         caption = "Frequências relativas da variável ESCOLARIDADE")

tab.7_cap <- table_nums(name = "tab_7", 
                         caption = "Frequências relativas da variável ESTADO CIVIL")

tab.8_cap <- table_nums(name = "tab_8", 
                         caption = "Frequências relativas da variável PAG_PADRAO")

tab.9_cap <- table_nums(name = "tab_9", 
                         caption = "Coeficientes de assimetria das variáveis quantitativas")

A Análise Descritiva é a fase inicial do processo de estudo dos dados coletados, em que são utilizados métodos de Estatística Descritiva para organizar, resumir, descrever e comparar aspectos importantes de um conjunto de variáveis. A identificação de anomalias e e dados dispersos também faz parte desse tipo de análise.

Sobre o dataset

Foi utilizado o dataset default of credit card clients.xls, retirado do Machine Learning Repository da University of California, Irvine (UCI).

O dataset foi nomeado credit e contém dados pessoais e bancários de consumidores em Taiwan, abrangendo o período de abril a setembro de 2005.

Tendo por base esses dados, o comportamento dos consumidores foi classificado em regular ou irregular, a fim de prever fraudes de crédito.

Após a leitura dos dados, os nomes das variáveis foram modificados para facilitar a compreensão e posterior manipulação dos dados.

#Leitura dos dados
# credit<- read_excel("E:\\Multivariada\\Atividade 03\\default of credit card clients.xls",
#                     col_types = c("text", "numeric", "numeric","numeric", "numeric", 
#                                   "numeric", "numeric", "numeric", "numeric", "numeric", 
#                                   "numeric", "numeric", "numeric", "numeric", "numeric", 
#                                   "numeric","numeric","numeric","numeric","numeric",
#                                   "numeric","numeric","numeric","numeric","text"))

#Alterando nomes das variáveis 

# colnames(credit)<-credit[1,]
# credit<-credit[-1,]
# colnames(credit) <- c("ID",
#                       "LIMITE",
#                       "SEXO",
#                       "ESCOLARIDADE",
#                       "ESTADO CIVIL",
#                       "IDADE",
#                       "HIST_SET2005",
#                       "HIST_AGO2005",
#                       "HIST_JUL2005",
#                       "HIST_JUN2005",
#                       "HIST_MAI2005",
#                       "HIST_ABR2005",
#                       "EXT_SET2005",
#                       "EXT_AGO2005",
#                       "EXT_JUL2005",
#                       "EXT_JUN2005",
#                       "EXT_MAI2005",
#                       "EXT_ABR2005",
#                       "PAG_SET2005",
#                       "PAG_AGO2005",
#                       "PAG_JUL2005",
#                       "PAG_JUN2005",
#                       "PAG_MAI2005",
#                       "PAG_ABR2005",
#                       "PAG_PADRAO")

# library(data.table)
# fwrite(credit, file = "credit_clean.csv")

credit<- read.csv("C:\\Users\\Victoria Vargas\\Downloads\\ATIVIDADE 3\\credit_clean.csv")

Características básicas


Tabela 1: Características básicas do dataset credit


caract_basic <- data.frame(NAs = sum(is.na(credit)), Observações = nrow(credit), Variáveis = ncol(credit), Duplicidades = anyDuplicated.data.frame(credit))

knitr::kable(caract_basic)%>% kable_styling(position = "center")
NAs Observações Variáveis Duplicidades
0 30000 25 0

De acordo com a Tabela 1:

  • O dataset possui 30.000 observações e 25 variáveis;
  • Não possui NAs e nem linhas duplicadas.

Descrição do dataset


Tabela 2: Descrições das variáveis do dataset credit


colunas<- c(colnames(credit)[1:6],"HIST_SET2005 a HIST_ABR2005","EXT_SET2005 a EXT_ABR2005","PAG_SET2005 a PAG_ABR2005","PAG_PADRAO")
descricao <- data.frame(Variável = colunas,
                        Tipo = c("Texto","Inteiro","Categórica","Categórica","Categórica","Inteiro","Categórica","Inteiro","Inteiro","Binária"),
                        Descrição = c("Id da observação.",
                                      "Valor do crédito concedido (em Novo dólar taiwanês): inclui tanto o crédito individual ao consumidor quanto o crédito familiar (complementar).",
                                      "Sexo (1= masculino; 2 = feminino).",
                                      "Escolaridade (1 = pós-graduação; 2 = universidade; 3 = ensino médio; 0, 4, 5, 6 = outros).",
                                      "Estado civil (1 = casado; 2 = solteiro; 3 = divorciado; 0 = outros).",
                                      "Idade em anos.",
                                      "Histórico de pagamentos anteriores. Acompanha o registros de pagamentos mensais de abril a setembro de 2005 através da seguinte escala:  -2: Sem consumo; -1: pago integralmente; 0: O uso de crédito rotativo; 1 = atraso no pagamento por um mês; 2 = atraso no pagamento por dois meses; ... ; 8 = atraso no pagamento por oito meses; 9 = atraso no pagamento por nove meses ou mais.",
                                      "Valor do extrato da conta (em Novo dólar taiwanês).  Acompanha o valor do extrato da conta de abril a setembro de 2005.",
                                      "Valor dos pagamentos anteriores (em Novo dólar taiwanês). Acompanha o valor pago de abril a setembro de 2005",
                                      "Comportamento do cliente quanto aos gastos e pagamentos (0 = irregular; 1 = regular)"))

knitr::kable(descricao)%>% kable_styling(position = "center")
Variável Tipo Descrição
ID Texto Id da observação.
LIMITE Inteiro Valor do crédito concedido (em Novo dólar taiwanês): inclui tanto o crédito individual ao consumidor quanto o crédito familiar (complementar).
SEXO Categórica Sexo (1= masculino; 2 = feminino).
ESCOLARIDADE Categórica Escolaridade (1 = pós-graduação; 2 = universidade; 3 = ensino médio; 0, 4, 5, 6 = outros).
ESTADO.CIVIL Categórica Estado civil (1 = casado; 2 = solteiro; 3 = divorciado; 0 = outros).
IDADE Inteiro Idade em anos.
HIST_SET2005 a HIST_ABR2005 Categórica Histórico de pagamentos anteriores. Acompanha o registros de pagamentos mensais de abril a setembro de 2005 através da seguinte escala: -2: Sem consumo; -1: pago integralmente; 0: O uso de crédito rotativo; 1 = atraso no pagamento por um mês; 2 = atraso no pagamento por dois meses; … ; 8 = atraso no pagamento por oito meses; 9 = atraso no pagamento por nove meses ou mais.
EXT_SET2005 a EXT_ABR2005 Inteiro Valor do extrato da conta (em Novo dólar taiwanês). Acompanha o valor do extrato da conta de abril a setembro de 2005.
PAG_SET2005 a PAG_ABR2005 Inteiro Valor dos pagamentos anteriores (em Novo dólar taiwanês). Acompanha o valor pago de abril a setembro de 2005
PAG_PADRAO Binária Comportamento do cliente quanto aos gastos e pagamentos (0 = irregular; 1 = regular)

Uma amostra do dataset pode ser visualizada abaixo:


Tabela 3: Amostra do dataset credit


knitr::kable(head(credit))%>% kable_styling(position = "center")
ID LIMITE SEXO ESCOLARIDADE ESTADO.CIVIL IDADE HIST_SET2005 HIST_AGO2005 HIST_JUL2005 HIST_JUN2005 HIST_MAI2005 HIST_ABR2005 EXT_SET2005 EXT_AGO2005 EXT_JUL2005 EXT_JUN2005 EXT_MAI2005 EXT_ABR2005 PAG_SET2005 PAG_AGO2005 PAG_JUL2005 PAG_JUN2005 PAG_MAI2005 PAG_ABR2005 PAG_PADRAO
1 20000 2 2 1 24 2 2 -1 -1 -2 -2 3913 3102 689 0 0 0 0 689 0 0 0 0 1
2 120000 2 2 2 26 -1 2 0 0 0 2 2682 1725 2682 3272 3455 3261 0 1000 1000 1000 0 2000 1
3 90000 2 2 2 34 0 0 0 0 0 0 29239 14027 13559 14331 14948 15549 1518 1500 1000 1000 1000 5000 0
4 50000 2 2 1 37 0 0 0 0 0 0 46990 48233 49291 28314 28959 29547 2000 2019 1200 1100 1069 1000 0
5 50000 1 2 1 57 -1 0 -1 0 0 0 8617 5670 35835 20940 19146 19131 2000 36681 10000 9000 689 679 0
6 50000 1 1 2 37 0 0 0 0 0 0 64400 57069 57608 19394 19619 20024 2500 1815 657 1000 1000 800 0

Estatísticas resumo (variáveis quantitativas)

O cálculo das estatísticas resumo foi feito para as variáveis quantitativas.


Tabela 4: Estatísticas resumo das variáveis quantitativas do dataset credit


knitr::kable(get_summary_stats(credit[,c(2,6,13:24)]) [,-c(2,8,9,12,13)])%>% kable_styling(position = "center")
variable min max median q1 q3 mean sd
EXT_ABR2005 -339603 961664 17071.0 1256.00 49198.25 38871.760 59554.108
EXT_AGO2005 -69777 983931 21200.0 2984.75 64006.25 49179.075 71173.769
EXT_JUL2005 -157264 1664089 20088.5 2666.25 60164.75 47013.155 69349.387
EXT_JUN2005 -170000 891586 19052.0 2326.75 54506.00 43262.949 64332.856
EXT_MAI2005 -81334 927171 18104.5 1763.00 50190.50 40311.401 60797.156
EXT_SET2005 -165580 964511 22381.5 3558.75 67091.00 51223.331 73635.861
IDADE 21 79 34.0 28.00 41.00 35.486 9.218
LIMITE 10000 1000000 140000.0 50000.00 240000.00 167484.323 129747.662
PAG_ABR2005 0 528666 1500.0 117.75 4000.00 5215.503 17777.466
PAG_AGO2005 0 1684259 2009.0 833.00 5000.00 5921.164 23040.870
PAG_JUL2005 0 896040 1800.0 390.00 4505.00 5225.682 17606.961
PAG_JUN2005 0 621000 1500.0 296.00 4013.25 4826.077 15666.160
PAG_MAI2005 0 426529 1500.0 252.50 4031.50 4799.388 15278.306
PAG_SET2005 0 873552 2100.0 1000.00 5006.00 5663.580 16563.280
  • Para a variável IDADE, temos que as idades mínima e máxima observadas são 21 e 79 anos, respectivamente. A mediana é 39 anos, ou seja, 50% dos clientes observados possuem idade menor ou igual a 39 anos.

  • Para a variável LIMITE, temos que o valor mínimo e máximo de crédito concedido são NT$10.000 e NT$1.000.000 , respectivamente. A mediana é NT$140.000, ou seja, 50% dos clientes observados foram concedidos um valor em crédito menor ou igual a NT$140.000.

  • As medianas das variáveis PAG_SET2005 a PAG_ABR2005 assumem valores na faixa NT$1500-NT$2100, ou seja, 50% dos clientes observados fizeram um pagamento entre NT$1500 e NT$2100.

  • As medianas das variáveis EXT_SET2005 a EXT_ABR2005 assumem valores na faixa NT$17071-NT$22381.5, ou seja, 50% dos extratos de clientes observados possuem valor entre NT$17071 e NT$22381.5.

  • Observem que, pelo comportamento mediano, podemos perceber que muitos dos clientes possuem um valor de extrato superior ao valor de pagamento.

Tabelas de frequências relativas

Foram feitas tabelas de frequências relativas para as variáveis categóricas relativas a características pessoais (SEXO, ESCOLARIDADE e ESTADO CIVIL) e para a variável binária (PAG_PADRAO).


Tabela 5: Frequências relativas da variável SEXO


sexo <- data.frame(round((table(credit$SEXO)/nrow(credit))*100,2))
colnames(sexo) <- c("SEXO","Frequência relativa (%)")
knitr::kable(sexo)%>% kable_styling(position = "center")
SEXO Frequência relativa (%)
1 39.63
2 60.37
  • 39.63% dos clientes são do sexo masculino
  • 60.37% dos clientes são do sexo feminino

Tabela 6: Frequências relativas da variável ESCOLARIDADE


escolaridade <- data.frame(round((table(credit$ESCOLARIDADE)/nrow(credit))*100,2))
colnames(escolaridade) <- c("ESCOLARIDADE","Frequência relativa (%)")
knitr::kable(escolaridade)%>% kable_styling(position = "center")
ESCOLARIDADE Frequência relativa (%)
0 0.05
1 35.28
2 46.77
3 16.39
4 0.41
5 0.93
6 0.17
  • 46.77% dos clientes possuem graduação
  • 35.28% dos clientes possuem pós-graduação
  • 16.39% dos clientes possuem somente ensino médio
  • 1.56% dos clientes estão em outras faixas de escolaridade

Tabela 7: Frequências relativas da variável ESTADO CIVIL


# est_civil <- data.frame(round((table(credit$`ESTADO CIVIL`)/nrow(credit))*100,2))
# colnames(est_civil) <- c("ESTADO CIVIL","Frequência relativa (%)")
# knitr::kable(est_civil)%>% kable_styling(position = "center")



est_civil <- data.frame(round((table(credit$ESTADO.CIVIL)/nrow(credit))*100,2))
colnames(est_civil) <- c("ESTADO CIVIL","Frequência relativa (%)")
knitr::kable(est_civil)%>% kable_styling(position = "center")
ESTADO CIVIL Frequência relativa (%)
0 0.18
1 45.53
2 53.21
3 1.08
  • 53.21% dos clientes são solteiros
  • 45.53 % dos clientes são casados
  • 1.08% dos clientes são divorciados
  • 0.18% dos clientes possuem outro estado civil

Tabela 8: Frequências relativas da variável PAG_PADRAO


pag_padr <-data.frame(round((table(credit$PAG_PADRAO)/nrow(credit))*100,2))
colnames(pag_padr) <- c("PAG_PADRAO","Frequência relativa (%)")
knitr::kable(pag_padr)%>% kable_styling(position = "center")
PAG_PADRAO Frequência relativa (%)
0 77.88
1 22.12
  • Foi detectado comportamento irregular em 77.88% dos clientes observados
  • Foi detectado comportamento regular em 22.12% dos clientes observados

Gráficos de barras (variáveis de histórico de pagamento)

par(mfrow=c(3,2))

histset2005<- data.frame(table(credit$HIST_SET2005))
colnames(histset2005)<- c("HIST_SET2005","Frequência")

histago2005<- data.frame(table(credit$HIST_AGO2005))
colnames(histago2005)<- c("HIST_AGO2005","Frequência")

histjul2005<- data.frame(table(credit$HIST_JUL2005))
colnames(histjul2005)<- c("HIST_JUL2005","Frequência")

histjun2005<- data.frame(table(credit$HIST_JUN2005))
colnames(histjun2005)<- c("HIST_JUN2005","Frequência")

histmai2005<- data.frame(table(credit$HIST_MAI2005))
colnames(histmai2005)<- c("HIST_MAI2005","Frequência")

histabr2005<- data.frame(table(credit$HIST_ABR2005))
colnames(histabr2005)<- c("HIST_ABR2005","Frequência")

ggplot(data=histset2005, aes(x=HIST_SET2005, y=Frequência)) +
  geom_bar(stat="identity", color="blue", fill="white")+
  geom_text(aes(label=Frequência), vjust=-0.3, color="black", size=3.5)+
  labs(x="\nHIST_SET2005",y="Frequência\n", title="Frequências absolutas - HIST_SET2005\n")+
  theme_bw()

ggplot(data=histago2005, aes(x=HIST_AGO2005, y=Frequência)) +
  geom_bar(stat="identity", color="blue", fill="white")+
  geom_text(aes(label=Frequência), vjust=-0.3, color="black", size=3.5)+
  labs(x="\nHIST_AGO2005",y="Frequência\n", title="Frequências absolutas - HIST_AGO2005\n")+
  theme_bw()

ggplot(data=histjul2005, aes(x=HIST_JUL2005, y=Frequência)) +
  geom_bar(stat="identity", color="blue", fill="white")+
  geom_text(aes(label=Frequência), vjust=-0.3, color="black", size=3.5)+
  labs(x="\nHIST_JUL2005",y="Frequência\n", title="Frequências absolutas - HIST_JUL2005\n")+
  theme_bw()

ggplot(data=histjun2005, aes(x=HIST_JUN2005, y=Frequência)) +
  geom_bar(stat="identity", color="blue", fill="white")+
  geom_text(aes(label=Frequência), vjust=-0.3, color="black", size=3.5)+
  labs(x="\nHIST_JUN2005",y="Frequência\n", title="Frequências absolutas - HIST_JUN2005\n")+
  theme_bw()

ggplot(data=histmai2005, aes(x=HIST_MAI2005, y=Frequência)) +
  geom_bar(stat="identity", color="blue", fill="white")+
  geom_text(aes(label=Frequência), vjust=-0.3, color="black", size=3.5)+
  labs(x="\nHIST_MAI2005",y="Frequência\n", title="Frequências absolutas - HIST_MAI2005\n")+
  theme_bw()

ggplot(data=histabr2005, aes(x=HIST_ABR2005, y=Frequência)) +
  geom_bar(stat="identity", color="blue", fill="white")+
  geom_text(aes(label=Frequência), vjust=-0.3, color="black", size=3.5)+
  labs(x="\nHIST_ABR2005",y="Frequência\n", title="Frequências absolutas - HIST_ABR2005\n")+
  theme_bw()

- Podemos observar que, para os meses entre abril e setembro de 2005, o histórico de pagamentos apresenta em maior frequência o uso de credito rotativo (0).

Coeficientes de assimetria

A Tabela 9 contém os coeficientes de assimetria para as variáveis quantitativas do dataset.

A assimetria negativa indica que a média dos dados é menor que a mediana e, portanto, que a distribuição dos dados é assimétrica à esquerda. Já a assimetria positiva indica que a média dos dados é maior que a mediana e, portanto, que a distribuição dos dados é assimétrica à direita.


Tabela 9: Coeficientes de assimetria das variáveis quantitativas


knitr::kable(apply(credit[,c(2,6,13:24)],2, function(x) round(skewness(x),2)),col.names = "Coeficiente de assimetria")%>% kable_styling(position = "center")
Coeficiente de assimetria
LIMITE 0.99
IDADE 0.73
EXT_SET2005 2.66
EXT_AGO2005 2.70
EXT_JUL2005 3.09
EXT_JUN2005 2.82
EXT_MAI2005 2.88
EXT_ABR2005 2.85
PAG_SET2005 14.67
PAG_AGO2005 30.45
PAG_JUL2005 17.21
PAG_JUN2005 12.90
PAG_MAI2005 11.13
PAG_ABR2005 10.64

De acordo com a Tabela 9, temos que todas as variáveis quantitativas são assimétricas à direita.

Correlograma

O correlograma abaixo mostra os coeficientes de correlação de Pearson para todas as variáveis quantitativas do dataset.

corr <- cor(credit[,c(2,6,13:24)]) #Matriz de correlação
ggcorrplot(corr,outline.color = "white",type = "lower",lab = TRUE,digits = 2, lab_size = 3)

  • Temos que as maiores correlações ocorreram entre os valores dos extratos de abril a setembro de 2005, como destacado pela área em vermelho.

Análise de Agrupamento (Clustering)

tab.10_cap <- table_nums(name = "tab_10", 
                         caption = "Intervalos de classificação da silhueta média (Kaufman e Rousseeuw, 1989)")

tab.11_cap <- table_nums(name = "tab_11", 
                         caption = "Experimento 1 - Medoids da solução com k=3")


tab.12_cap <- table_nums(name = "tab_12", 
                         caption = "Experimento 1 - Matriz de confusão para a solução com k=2")

tab.13_cap <- table_nums(name = "tab_13", 
                         caption = "Experimento 2 - Medoids da solução com k=4")


tab.14_cap <- table_nums(name = "tab_13", 
                         caption = "Experimento 2 - Matriz de confusão para a solução com k=2")

tab.15_cap <- table_nums(name = "tab_15", 
                         caption = " Experimento 3 - Medoids da solução com k=2")


tab.16_cap <- table_nums(name = "tab_16", 
                         caption = "Experimento 3 - Matriz de confusão para a solução com k=2")


tab.17_cap <- table_nums(name = "tab_17", 
                         caption = " Experimento 4 - Medoids da solução com k=3")

tab.18_cap <- table_nums(name = "tab_18", 
                         caption = " Experimento 4 - Percentual de acerto para a solução com k=2")

Depois da análise descritiva, demos início ao agrupamento, ou clustering, dos dados.

Três questões básicas devem ser abordadas em um problema de agrupamento (Hair e Babin, 2018):

(1) Como medir a similaridade?

o primeiro passo da tarefa de agrupamento consiste na escolha de como a similaridade/dissimilaridade entre as observações será medida. A dissimilaridade nada mais é que a distância entre as observações.

(2) Como formar os agrupamentos?

O segundo passo consiste na escolha do algoritmo de agrupamento, que dará origem aos grupos de acordo com as dissimilaridades obtidas no passo 1.

(3) Quantos grupos formar?

Uma vez obtido um agrupamento (solução), é de suma importância avaliá- lo e verificar se, de fato, representa bem a estrutura dos dados. Esse tipo de avaliação pode ser feita mediante a aplicação de índices de validação. Logo, o terceiro e último passo consiste em e avaliar e comparar as soluções produzidas no segundo. Nesse trabalho usaremos o índice de silhueta, mais especificamente, a silhueta média como um indicador de qualidade do agrupamento.


Tabela 10: Intervalos de classificação da silhueta média (Kaufman e Rousseeuw, 1989)


sil_tabela <- data.frame(a=c("0.71-1","0.51 − 0.70","0.26 − 0.50","≤ 0.25"),
                         d=c("Estrutura forte encontrada nos dados",
                             "Estrutura razoável encontrada nos dados",
                              "Estrutura fraca, possivelmente artificial; Avaliar a aplicação de outros algoritmos nos dados",
                              "Não foi encontrada estrutura substancial nos dados"))


colnames(sil_tabela) <- c("Valor da silhueta média","Descrição")
knitr::kable(sil_tabela)%>% kable_styling(position = "center")
Valor da silhueta média Descrição
0.71-1 Estrutura forte encontrada nos dados
0.51 − 0.70 Estrutura razoável encontrada nos dados
0.26 − 0.50 Estrutura fraca, possivelmente artificial; Avaliar a aplicação de outros algoritmos nos dados
≤ 0.25 Não foi encontrada estrutura substancial nos dados

Distância de Gower + PAM

As distâncias Euclidiana e Manhattan são comumente utilizadas, porém, ambas são aplicáveis somente para dados numéricos e, como já vimos, o dataset credit possui variáveis categóricas e numéricas.

Levando em consideração essa particularidade do dataset, primeiramente utilizamos a distância de Gower, uma métrica que pode ser usada para calcular a distância entre duas observações cujos atributos/variáveis são um misto de valores categóricos e quantitativos.

A distância de Gower foi empregada através da função daisy do pacote cluster. Com a matriz de distâncias obtida, o algoritmo de agrupamento PAM foi aplicado variando o número de grupos k entre 2 e 5. A qualidade dos agrupamentos foi verificada através da silhueta média das soluções.

Foram realizados dois experimentos diferentes considerando a combinação da distância de Gower e do algoritmo PAM:

  1. EXPERIMENTO 1: A distância foi calculada para todas as variáveis do dataset, com exceção de ID e PAG_PADRAO;
  2. EXPERIMENTO 2: A distância foi calculada usando todas as variáveis numéricas e as variáveis categóricas HIST_SET2005 a HIST_ABR2005, mantendo a exclusão das variáveis ID e PAG_PADRAO.

O resultado do EXPERIMENTO 1 pode ser visualizado no gráfico abaixo:

# credit$HIST_SET2005<- as.factor(credit$HIST_SET2005)
# credit$HIST_AGO2005<- as.factor(credit$HIST_AGO2005)
# credit$HIST_JUL2005<- as.factor(credit$HIST_JUL2005)
# credit$HIST_JUN2005<- as.factor(credit$HIST_JUN2005)
# credit$HIST_MAI2005<- as.factor(credit$HIST_MAI2005)
# credit$HIST_ABR2005<- as.factor(credit$HIST_ABR2005)
# credit$SEXO<-as.factor(credit$SEXO)
# credit$ESCOLARIDADE<-as.factor(credit$ESCOLARIDADE)
# credit$ESTADO.CIVIL<-as.factor(credit$ESTADO.CIVIL)

#-------------------- Com todas as variáveis -----------------------------------

# gower_df <- daisy(credit[,-c(1,25)],
#                   metric = "gower" )


# saveRDS(gower_df, file = "C:\\Users\\Victoria Vargas\\Downloads\\ATIVIDADE 3\\credit_gowerdf_complete.rds")


# rm(list = ls())
# gc()
# .rs.restartR()
# 
# library(cluster)
# library(data.table)
# 
# gower_df<-readRDS("C:\\Users\\Victoria Vargas\\Downloads\\ATIVIDADE 3\\credit_gowerdf_complete.rds", refhook = NULL)
# 


# i=2
# pam_clusters <- pam(gower_df,diss = TRUE, k = i)
# save(pam_clusters,file="C:\\Users\\Victoria Vargas\\Downloads\\ATIVIDADE 3\\doisgrupos_gower_complete.Rdata")
# dois_grupos <- data.frame(c(as.vector(pam_clusters$silinfo$avg.width),as.vector(pam_clusters$clustering)))
# fwrite(dois_grupos, file = "C:\\Users\\Victoria Vargas\\Downloads\\ATIVIDADE 3\\doisgrupos_gower_complete.csv")

# rm(list=c("pam_clusters","dois_grupos"))
# gc()


# i=3
# pam_clusters <- pam(gower_df,diss = TRUE, k = i)
# save(pam_clusters,file="C:\\Users\\Victoria Vargas\\Downloads\\ATIVIDADE 3\\tresgrupos_gower_complete.Rdata")
# tres_grupos <-data.frame(c(as.vector(pam_clusters$silinfo$avg.width),as.vector(pam_clusters$clustering)))
# fwrite(tres_grupos, file = "C:\\Users\\Victoria Vargas\\Downloads\\ATIVIDADE 3\\tresgrupos_gower_complete.csv")


# rm(list=c("pam_clusters","tres_grupos"))
# gc()


# i=4
# pam_clusters <- pam(gower_df,diss = TRUE, k = i)
# save(pam_clusters,file="C:\\Users\\Victoria Vargas\\Downloads\\ATIVIDADE 3\\quatrogrupos_gower_complete.Rdata")
# quatro_grupos <- data.frame(c(as.vector(pam_clusters$silinfo$avg.width),as.vector(pam_clusters$clustering)))
# fwrite(quatro_grupos, file = "C:\\Users\\Victoria Vargas\\Downloads\\ATIVIDADE 3\\quatrogrupos_gower_complete.csv")
# 
# rm(list=c("pam_clusters","quatro_grupos"))
# gc()

# i=5
# pam_clusters <- pam(gower_df,diss = TRUE, k = i)
# save(pam_clusters,file="C:\\Users\\Victoria Vargas\\Downloads\\ATIVIDADE 3\\cincogrupos_gower_complete.Rdata")
# cinco_grupos <- data.frame(c(as.vector(pam_clusters$silinfo$avg.width),as.vector(pam_clusters$clustering)))
# fwrite(cinco_grupos, file = "C:\\Users\\Victoria Vargas\\Downloads\\ATIVIDADE 3\\cincogrupos_gower_complete.csv")

# rm(list = ls())
# gc()
# .rs.restartR()

#--------------------- Sem sexo, escolaridade e estado civil -------------------------


# library(cluster)
# library(data.table)

# credit<- read.csv("C:\\Users\\Victoria Vargas\\Downloads\\ATIVIDADE 3\\credit_clean.csv")
# credit$HIST_SET2005<- as.factor(credit$HIST_SET2005)
# credit$HIST_AGO2005<- as.factor(credit$HIST_AGO2005)
# credit$HIST_JUL2005<- as.factor(credit$HIST_JUL2005)
# credit$HIST_JUN2005<- as.factor(credit$HIST_JUN2005)
# credit$HIST_MAI2005<- as.factor(credit$HIST_MAI2005)
# credit$HIST_ABR2005<- as.factor(credit$HIST_ABR2005)



# gower_df <- daisy(credit[,-c(1,3:5,25)],
#                     metric = "gower" )


# saveRDS(gower_df, file = "C:\\Users\\Victoria Vargas\\Downloads\\ATIVIDADE 3\\credit_gowerdf_notcomplete.rds")

# rm(list = ls())
# gc()
# .rs.restartR()

# library(cluster)
# library(data.table)

# gower_df<-readRDS("C:\\Users\\Victoria Vargas\\Downloads\\ATIVIDADE 3\\credit_gowerdf_notcomplete.rds", refhook = NULL)



# i=2
# pam_clusters <- pam(gower_df,diss = TRUE, k = i)
# save(pam_clusters,file="C:\\Users\\Victoria Vargas\\Downloads\\ATIVIDADE 3\\doisgrupos_gower_notcomplete.Rdata")
# dois_grupos <- data.frame(c(as.vector(pam_clusters$silinfo$avg.width),as.vector(pam_clusters$clustering)))
# fwrite(dois_grupos, file = "C:\\Users\\Victoria Vargas\\Downloads\\ATIVIDADE 3\\doisgrupos_gower_notcomplete.csv")

# rm(list=c("pam_clusters","dois_grupos"))
# gc()


# i=3
# pam_clusters <- pam(gower_df,diss = TRUE, k = i)
# save(pam_clusters,file="C:\\Users\\Victoria Vargas\\Downloads\\ATIVIDADE 3\\tresgrupos_gower_notcomplete.Rdata")
# tres_grupos <-data.frame(c(as.vector(pam_clusters$silinfo$avg.width),as.vector(pam_clusters$clustering)))
# fwrite(tres_grupos, file = "C:\\Users\\Victoria Vargas\\Downloads\\ATIVIDADE 3\\tresgrupos_gower_notcomplete.csv")


# rm(list=c("pam_clusters","tres_grupos"))
# gc()


# i=4
# pam_clusters <- pam(gower_df,diss = TRUE, k = i)
# save(pam_clusters,file="C:\\Users\\Victoria Vargas\\Downloads\\ATIVIDADE 3\\quatrogrupos_gower_notcomplete.Rdata")
# quatro_grupos <- data.frame(c(as.vector(pam_clusters$silinfo$avg.width),as.vector(pam_clusters$clustering)))
# fwrite(quatro_grupos, file = "C:\\Users\\Victoria Vargas\\Downloads\\ATIVIDADE 3\\quatrogrupos_gower_notcomplete.csv")


# rm(list=c("pam_clusters","quatro_grupos"))
# gc()

# i=5
# pam_clusters <- pam(gower_df,diss = TRUE, k = i)
# save(pam_clusters,file="C:\\Users\\Victoria Vargas\\Downloads\\ATIVIDADE 3\\cincogrupos_gower_notcomplete.Rdata")
# cinco_grupos <- data.frame(c(as.vector(pam_clusters$silinfo$avg.width),as.vector(pam_clusters$clustering)))
# fwrite(cinco_grupos, file = "C:\\Users\\Victoria Vargas\\Downloads\\ATIVIDADE 3\\cincogrupos_gower_notcomplete.csv")

# rm(list = ls())
# gc()
# .rs.restartR()


dois_grupos<- read.csv("C:\\Users\\Victoria Vargas\\Downloads\\ATIVIDADE 3\\doisgrupos_gower_complete.csv")
tres_grupos <- read.csv("C:\\Users\\Victoria Vargas\\Downloads\\ATIVIDADE 3\\tresgrupos_gower_complete.csv")
quatro_grupos <- read.csv("C:\\Users\\Victoria Vargas\\Downloads\\ATIVIDADE 3\\quatrogrupos_gower_complete.csv")
cinco_grupos <- read.csv("C:\\Users\\Victoria Vargas\\Downloads\\ATIVIDADE 3\\cincogrupos_gower_complete.csv")


silhueta <- cbind.data.frame (grupos=as.factor(1:5),silhueta=c(0, dois_grupos[1,],tres_grupos[1,],quatro_grupos[1,],cinco_grupos[1,]))


ggplot(data=silhueta, aes(x=grupos, y=silhueta, group=1, label=as.character(round(silhueta,2)))) +
  geom_line()+
  geom_point()+
  geom_text(hjust=0, vjust=-1.5)+
  labs(x="\nGrupos",y="Valor da silhueta média\n", title="Experimento 1 - Valor da silhueta média de acordo com o número de grupos k\n")+
  scale_y_continuous(breaks = seq(0,0.6,by=0.1), limits= c(0,0.6))+
  theme_classic()

De acordo com os valores da silhueta média, no Experimento 1 a melhor solução utilizando a distância de Gower em conjunto ao algoritmo PAM é para \(k=3\) grupos.

Com a solução do PAM para \(k=3\) grupos, tentamos interpretar o comportamento de cada cluster com a ajuda dos medoids da solução.


Tabela 11: Experimento 1 - Medoids da solução com k=3


load("C:\\Users\\Victoria Vargas\\Downloads\\ATIVIDADE 3\\tresgrupos_gower_complete.Rdata")

knitr::kable(credit[pam_clusters$medoids, ])%>% kable_styling(position = "center")
ID LIMITE SEXO ESCOLARIDADE ESTADO.CIVIL IDADE HIST_SET2005 HIST_AGO2005 HIST_JUL2005 HIST_JUN2005 HIST_MAI2005 HIST_ABR2005 EXT_SET2005 EXT_AGO2005 EXT_JUL2005 EXT_JUN2005 EXT_MAI2005 EXT_ABR2005 PAG_SET2005 PAG_AGO2005 PAG_JUL2005 PAG_JUN2005 PAG_MAI2005 PAG_ABR2005 PAG_PADRAO
3893 3893 170000 2 1 1 36 -1 -1 -1 -1 -1 -1 1218 2434 1537 2156 1898 2656 2434 1537 2156 1898 2656 1626 0
26299 26299 80000 2 2 2 34 0 0 0 0 0 0 64553 52484 41048 31786 29105 28294 1784 1874 2000 1011 1100 1154 0
17641 17641 200000 2 1 2 34 -2 -2 -2 -2 -2 -2 0 0 0 0 0 0 0 0 0 0 0 0 1
  • Na tabela acima, cada linha contém a observação de credit escolhida como medoid na solução ótima (\(k=3\)).

  • A partir desta tabela, podemos inferir que os clientes pertencentes ao Cluster 1 (linha 1) têm as seguintes características representativas (de acordo com o medoid): o limite do crédito concedido é de NT$170k, são do sexo feminino, com pós graduação completa, casadas, idade média de 36 anos, com o histórico de pagamentos de abril a setembro de 2015 pago integralmente, com o valor do extrato da conta desse mesmo período variando entre 1218 a 2656 Novo dólar taiwanês, o valor dos pagamentos do período abordado entre NT$1626 e NT$2656, e possuem comportamento irregular de gastos e pagamentos.

  • Observe que nem todos os clientes serão exatamente assim; os medoides são apenas uma representantes do cluster abordado.

  • Interpretações similares podem ser feitas para os outros clusters. Podemos notar que o segundo cluster aparenta ser de pessoas em que o extrato é bem maior que os pagamentos dos mesmos meses, com limite de NT$80k, com ensino médio inferior aos outros clusters (universidade), usando crédito rotativo e com comportamento irregular. Enquanto o terceiro, possui um comportamento regular, um limite médio de NT$200k e não apresentam consumos nos meses estudados, nem extratos e nem pagamentos.

Para a solução com \(k=2\) grupos, comparamos o agrupamento obtido no Experimento 1 com a coluna PAG_PADRAO presente no dataset credit, obtendo os seguintes resultados:


Tabela 12: Experimento 1 - Matriz de confusão para a solução com k=2


aux <- dois_grupos
aux<- as.character(aux[-1,])
#table(aux)
# 2 aparece em maior quantidade, logo, corresponde ao grupo 0 da base original
aux [which(aux=="2")] <- "0"


# correspondencia <- data.frame(PAG_PADRAO = sum(as.character(credit$PAG_PADRAO) == aux)/30000)
# 
# correspondencia <- round(correspondencia*100,2)
# colnames(correspondencia) <- c(" Exp1 - Acertos (%)")
# knitr::kable(correspondencia)%>% kable_styling(position = "center")



#Import required library
library(caret)

#Creates vectors having data points
expected_value <- factor(aux)
predicted_value <- factor(credit$PAG_PADRAO)

#Creating confusion matrix
example <- confusionMatrix(data=predicted_value, reference = expected_value)

#Display results 
knitr::kable(example$table)%>% kable_styling(position = "center")
0 1
0 15636 7728
1 4631 2005
  • Na solução de agrupamento obtida, os objetos foram alocados aos clusters 1 e 2. Por possuir mais observações, tal qual o grupo 0 da base original, transformou-se o “2” em “0”.
  • Ao compararmos a variável PAG_PADRAO com a solução do agrupamento para \(k=2\) do Experimento 1, temos que a acurácia foi 58.8%.
  • De acordo com a tabela, o agrupamento obtido alocou um objeto pertencente ao grupo 0 originalmente ao grupo 0 do agrupamento em 15636 casos e alocou um objeto do grupo 0 ao grupo 1 em 4631 casos.
  • O agrupamento obtido alocou um objeto pertencente ao grupo 1 originalmente ao grupo 0 do agrupamento em 7728 casos, e alocou um objeto do grupo 1 ao grupo 1 do agrupamento em 2005 casos.

Quanto ao EXPERIMENTO 2, seus resultados podem ser visualizados no gráfico abaixo:

dois_grupos<- read.csv("C:\\Users\\Victoria Vargas\\Downloads\\ATIVIDADE 3\\doisgrupos_gower_notcomplete.csv")
tres_grupos <- read.csv("C:\\Users\\Victoria Vargas\\Downloads\\ATIVIDADE 3\\tresgrupos_gower_notcomplete.csv")
quatro_grupos <- read.csv("C:\\Users\\Victoria Vargas\\Downloads\\ATIVIDADE 3\\quatrogrupos_gower_notcomplete.csv")
cinco_grupos <- read.csv("C:\\Users\\Victoria Vargas\\Downloads\\ATIVIDADE 3\\cincogrupos_gower_notcomplete.csv")


silhueta <- cbind.data.frame (grupos=as.factor(1:5),silhueta=c(0, dois_grupos[1,],tres_grupos[1,],quatro_grupos[1,],cinco_grupos[1,]))


ggplot(data=silhueta, aes(x=grupos, y=silhueta, group=1, label=as.character(round(silhueta,2)))) +
  geom_line()+
  geom_point()+
  geom_text(hjust=0, vjust=-1.5)+
  labs(x="\nGrupos",y="Valor da silhueta média\n", title="Experimento 2 - Valor da silhueta média de acordo com o número de grupos k\n")+
  scale_y_continuous(breaks = seq(0,0.6,by=0.1), limits= c(0,0.6))+
  theme_classic()

De acordo com os valores da silhueta média, no Experimento 2 a melhor solução utilizando a distância de Gower em conjunto ao algoritmo PAM é para \(k=4\) grupos.

Com a solução do PAM para \(k=4\) grupos, tentamos interpretar o comportamento de cada cluster com a ajuda dos medoids da solução.


Tabela 13: Experimento 2 - Medoids da solução com k=4


load("C:\\Users\\Victoria Vargas\\Downloads\\ATIVIDADE 3\\quatrogrupos_gower_notcomplete.Rdata")

knitr::kable(credit[pam_clusters$medoids, ])%>% kable_styling(position = "center")
ID LIMITE SEXO ESCOLARIDADE ESTADO.CIVIL IDADE HIST_SET2005 HIST_AGO2005 HIST_JUL2005 HIST_JUN2005 HIST_MAI2005 HIST_ABR2005 EXT_SET2005 EXT_AGO2005 EXT_JUL2005 EXT_JUN2005 EXT_MAI2005 EXT_ABR2005 PAG_SET2005 PAG_AGO2005 PAG_JUL2005 PAG_JUN2005 PAG_MAI2005 PAG_ABR2005 PAG_PADRAO
15930 15930 60000 2 2 1 32 2 2 2 2 2 2 20524 21584 22437 22771 23099 23583 1700 1500 1000 1000 1000 0 1
29556 29556 100000 1 1 1 33 0 0 0 0 0 0 49415 50851 49528 50604 40966 40308 2284 1961 2037 1434 1469 1483 0
14857 14857 210000 2 2 1 35 -1 -1 -1 -1 -1 -1 1338 1508 1409 1549 2454 946 1508 1409 1549 2454 946 1525 0
13471 13471 200000 1 1 1 36 -2 -2 -2 -2 -2 -2 0 0 0 0 0 0 0 0 0 0 0 0 1
  • As características dos clientes do grupo/cluster 1 são: limite do crédito concedido de NT$60k, sexo feminino, escolaridade graduação completa, casados, idade média de 32 anos, atraso médio de 2 meses em todos os pagamentos dos meses abordados, extratos entre NT$20k e NT$24k, pagamentos entre NT$1k e NT$1.7k com exceção de abril, que apresenta valor 0 e comportamento regular.

  • No segundo grupo/cluster, as principais características são: limite de 100k, sexo masculino, casados, com idade média de 33 anos, pós graduação completa, usando crédito rotativo em todos os meses abordados, extratos variando de NT$40k a NT$51k, pagamentos entre NT$1.4k e NT$2.3k e comportamento irregular.

  • O terceiro cluster têm como características: valor limite do crédito concedido de NT$210k, mulheres casadas, graduação completa, com 35 anos, pagamentos pagos integralmente,com extratos e pagamentos entre NT$956 e NT$2454 e comportamento irregular.

  • O quarto cluster possui características similares ao terceiro cluster do experimento 1, com exceção do perfil demográfico: o sexo é masculino, estado civil casado e idade 36 anos.

Novamente foi feita a comparação entre a solução com \(k=2\) grupos e a coluna PAG_PADRAO presente no dataset credit, obtendo a seguinte matriz de confusão:


Tabela 18:


aux <- dois_grupos
aux<- as.character(aux[-1,])
# table(aux)
# 2 aparece em maior quantidade, logo, corresponde ao grupo 0 da base original
aux [which(aux=="2")] <- "0"


# correspondencia <- data.frame(PAG_PADRAO = sum(as.character(credit$PAG_PADRAO) == aux)/30000)
# 
# correspondencia <- round(correspondencia*100,2)
# colnames(correspondencia) <- c(" Exp1 - Acertos (%)")
# knitr::kable(correspondencia)%>% kable_styling(position = "center")



#Creates vectors having data points
expected_value <- factor(aux)
predicted_value <- factor(credit$PAG_PADRAO)

#Creating confusion matrix
example <- confusionMatrix(data=predicted_value, reference = expected_value)

#Display results 
knitr::kable(example$table)%>% kable_styling(position = "center")
0 1
0 14998 8366
1 4517 2119
  • Na solução de agrupamento obtida, os objetos foram alocados aos clusters 1 e 2. Por possuir mais observações, tal qual o grupo 0 da base original, transformou-se o “2” em “0”.
  • Ao compararmos a variável PAG_PADRAO com a solução do agrupamento para \(k=2\) do Experimento 2, temos que a acurácia foi de 57.06%.
  • De acordo com a tabela, o agrupamento obtido alocou um objeto pertencente ao grupo 0 originalmente ao grupo 0 do agrupamento em 14998 casos e alocou um objeto do grupo 0 ao grupo 1 em 4517 casos.
  • O agrupamento obtido alocou um objeto pertencente ao grupo 1 originalmente ao grupo 0 do agrupamento em 8366 casos, e alocou um objeto do grupo 1 ao grupo 1 do agrupamento em 2119 casos.

Distância Euclidiana + PAM

Como os resultados obtidos utilizando a distância de Gower em conjunto ao algoritmo PAM não foram satisfatórios, isto é, a análise de qualidade segundo a silhueta média não indicou o número “correto” de grupos, optamos por realizar um terceiro experimento, dessa vez usando a distância Euclidiana.

Comentamos anteriormente que a distância euclidiana deve ser usada somente com variáveis quantitativas, portanto, utilizamos neste experimento somente as variáveis quantitativas do dataset:

  • LIMITE
  • IDADE
  • EXT_SET2005
  • EXT_AGO2005
  • EXT_JUL2005
  • EXT_JUN2005
  • EXT_MAI2005
  • EXT_ABR2005
  • PAG_SET2005
  • PAG_AGO2005
  • PAG_JUL2005
  • PAG_JUN2005
  • PAG_MAI2005
  • PAG_ABR2005

Previamente ao cálculo das distâncias, essas variáveis foram padronizadas com o auxílio da função scale.

A matriz de distância foi obtida pela função distances do pacote distances.

Com a matriz de distâncias euclidiana em mãos, o Experimento 3 consistiu em aplicar o algoritmo de agrupamento PAM variando o número de grupos k entre 2 e 5. A qualidade dos agrupamentos foi verificada através da silhueta média das soluções.

# library(distances)
# credit_padronizado <- credit[,c(2,6,13:24)]
# credit_padronizado<- data.frame(apply(credit_padronizado,2,scale))
# euclidean_df <- distances(credit_padronizado)
# 
# 
# i=2
# pam_clusters <- pam(euclidean_df,diss = TRUE, k = i)
# save(pam_clusters,file="C:\\Users\\Victoria Vargas\\Downloads\\ATIVIDADE 3\\doisgrupos_euclidean.Rdata")
# dois_grupos <- data.frame(c(as.vector(pam_clusters$silinfo$avg.width),as.vector(pam_clusters$clustering)))
# fwrite(dois_grupos, file = "C:\\Users\\Josh\\Desktop\\VICTÓRIA VARGAS\\doisgrupos_euclidean.csv")


# i=3
# pam_clusters <- pam(euclidean_df,diss = TRUE, k = i)
# save(pam_clusters,file="C:\\Users\\Josh\\Desktop\\VICTÓRIA VARGAS\\tresgrupos_euclidean.Rdata")
# tres_grupos <-data.frame(c(as.vector(pam_clusters$silinfo$avg.width),as.vector(pam_clusters$clustering)))
# fwrite(tres_grupos, file = "C:\\Users\\Josh\\Desktop\\VICTÓRIA VARGAS\\tresgrupos_euclidean.csv")
# 


# i=4
# pam_clusters <- pam(euclidean_df,diss = TRUE, k = i)
# save(pam_clusters,file="C:\\Users\\Josh\\Desktop\\VICTÓRIA VARGAS\\quatrogrupos_euclidean.Rdata")
# quatro_grupos <- data.frame(c(as.vector(pam_clusters$silinfo$avg.width),as.vector(pam_clusters$clustering)))
# fwrite(quatro_grupos, file = "C:\\Users\\Josh\\Desktop\\VICTÓRIA VARGAS\\quatrogrupos_euclidean.csv")


# i=5
# pam_clusters <- pam(euclidean_df,diss = TRUE, k = i)
# save(pam_clusters,file="C:\\Users\\Josh\\Desktop\\VICTÓRIA VARGAS\\cincogrupos_euclidean.Rdata")
# cinco_grupos <- data.frame(c(as.vector(pam_clusters$silinfo$avg.width),as.vector(pam_clusters$clustering)))
# fwrite(cinco_grupos, file = "C:\\Users\\Josh\\Desktop\\VICTÓRIA VARGAS\\cincogrupos_euclidean.csv")




dois_grupos<- read.csv("C:\\Users\\Victoria Vargas\\Downloads\\ATIVIDADE 3\\doisgrupos_euclidean.csv")
tres_grupos <- read.csv("C:\\Users\\Victoria Vargas\\Downloads\\ATIVIDADE 3\\tresgrupos_euclidean.csv")
quatro_grupos <- read.csv("C:\\Users\\Victoria Vargas\\Downloads\\ATIVIDADE 3\\quatrogrupos_euclidean.csv")
cinco_grupos <- read.csv("C:\\Users\\Victoria Vargas\\Downloads\\ATIVIDADE 3\\cincogrupos_euclidean.csv")



silhueta <- cbind.data.frame (grupos=as.factor(1:5),silhueta=c(0, dois_grupos[1,],tres_grupos[1,],quatro_grupos[1,],cinco_grupos[1,]))


ggplot(data=silhueta, aes(x=grupos, y=silhueta, group=1, label=as.character(round(silhueta,2)))) +
  geom_line()+
  geom_point()+
  geom_text(hjust=0, vjust=-1.5)+
  labs(x="\nGrupos",y="Valor da silhueta média\n", title="Experimento 3 - Valor da silhueta média de acordo com o número de grupos k\n")+
  scale_y_continuous(breaks = seq(0,0.6,by=0.1), limits= c(0,0.6))+
  theme_classic()

Note que, de acordo com os valores da silhueta média, a melhor solução utilizando a distância de Euclidiana em conjunto ao algoritmo PAM é para \(k=2\) grupos, o número “correto” de grupos do dataset credit.

Com a solução do PAM para \(k=2\) grupos, tentamos interpretar o comportamento de cada cluster com a ajuda dos medoids da solução.


Tabela 14: Experimento 3 - Medoids da solução com k=2


load("C:\\Users\\Victoria Vargas\\Downloads\\ATIVIDADE 3\\doisgrupos_euclidean.Rdata")

knitr::kable(credit[pam_2g$medoids, ])%>% kable_styling(position = "center")
ID LIMITE SEXO ESCOLARIDADE ESTADO.CIVIL IDADE HIST_SET2005 HIST_AGO2005 HIST_JUL2005 HIST_JUN2005 HIST_MAI2005 HIST_ABR2005 EXT_SET2005 EXT_AGO2005 EXT_JUL2005 EXT_JUN2005 EXT_MAI2005 EXT_ABR2005 PAG_SET2005 PAG_AGO2005 PAG_JUL2005 PAG_JUN2005 PAG_MAI2005 PAG_ABR2005 PAG_PADRAO
14580 14580 140000 2 2 1 34 0 0 0 0 0 0 15550 17780 18226 19915 20891 21542 2500 1500 2000 1300 1000 1500 0
27252 27252 230000 1 3 2 34 0 0 0 0 0 0 137122 138268 127797 130490 110153 112253 6700 6500 6513 4601 5000 5000 0
  • De acordo com a tabela, os clientes pertencentes ao grupo/cluster 1 têm as seguintes características: limite de NT$140k, mulheres casadas com graduação completa, idade média de 34 anos, utilizadoras de crédito rotativo em todos os meses, extratos entre NT$15k e NT$21k, diminuindo seus valores com o passar dos meses, pagamentos entre NT$1k e NT$2.5k e com comportamento irregular.

  • Os clientes do segundo grupo/cluster diferem são em geral homens solteiros, com limite de NT$230k, ensino médio completo, com mesma idade média, também utilizadores de crédito rotativo em todos os meses, extratos entre NT$110k e NT$140k, pagamentos entre NT$4.5k e NT$7k, e também apresentam comportamento irregular.

Verificando a matriz de confusão para a solução com \(k=2\), obtemos:


Tabela 15: Experimento 3 - Matriz de confusão para a solução com k=2


aux <- dois_grupos
aux<- as.character(aux[-1,])

# table(aux)
# 1 aparece em maior quantidade, logo, corresponde ao grupo 0 da base original
aux [which(aux=="1")] <- "0"
aux [which(aux=="2")] <- "1"

# correspondencia <- data.frame(PAG_PADRAO = sum(as.character(credit$PAG_PADRAO) == aux)/30000)
# 
# correspondencia <- round(correspondencia*100,2)
# colnames(correspondencia) <- c(" Exp1 - Acertos (%)")
# knitr::kable(correspondencia)%>% kable_styling(position = "center")



#Creates vectors having data points
expected_value <- factor(aux)
predicted_value <- factor(credit$PAG_PADRAO)

#Creating confusion matrix
example <- confusionMatrix(data=predicted_value, reference = expected_value)

#Display results 
knitr::kable(example$table)%>% kable_styling(position = "center")
0 1
0 18424 4940
1 5442 1194
  • Na solução de agrupamento obtida, os objetos foram alocados aos clusters 1 e 2. Por possuir mais observações, tal qual o grupo 0 da base original, transformou-se o “1” em “0”. Logo, o “2” teve que ser transformado em “1”.
  • Ao compararmos a variável PAG_PADRAO com a solução do agrupamento para \(k=2\) do Experimento 2, temos que a acurácia foi de 65.39%.
  • De acordo com a tabela, o agrupamento obtido alocou um objeto pertencente ao grupo 0 originalmente ao grupo 0 do agrupamento em 18424 casos e alocou um objeto do grupo 0 ao grupo 1 em 5442 casos.
  • O agrupamento obtido alocou um objeto pertencente ao grupo 1 originalmente ao grupo 0 do agrupamento em 4940 casos, e alocou um objeto do grupo 1 ao grupo 1 do agrupamento em 1194 casos.

Distância Euclidiana + CLARA

Por fim, o Experimento 4 também utilizou a distância Euclidiana, mas agora em conjunto ao algoritmo CLARA.

Comentamos anteriormente que a distância euclidiana deve ser usada somente com variáveis quantitativas, portanto, utilizamos neste experimento somente as variáveis quantitativas do dataset:

  • LIMITE
  • IDADE
  • EXT_SET2005
  • EXT_AGO2005
  • EXT_JUL2005
  • EXT_JUN2005
  • EXT_MAI2005
  • EXT_ABR2005
  • PAG_SET2005
  • PAG_AGO2005
  • PAG_JUL2005
  • PAG_JUN2005
  • PAG_MAI2005
  • PAG_ABR2005

Previamente ao cálculo das distâncias, essas variáveis foram padronizadas com o auxílio da função scale.

A matriz de distância foi obtida pela função distances do pacote distances.

Com a matriz de distâncias euclidiana em mãos, o Experimento 4 consistiu em aplicar o algoritmo de agrupamento CLARA variando o número de grupos k entre 2 e 5. A qualidade dos agrupamentos foi verificada através da silhueta média das soluções.

# i=2
# clara_clusters <- clara(credit[,c(2,6,13:24)], k=i, samples = 50,sampsize=1000, pamLike = TRUE, stand = FALSE)
# save(clara_clusters,file="C:\\Users\\Victoria Vargas\\Downloads\\ATIVIDADE 3\\doisgrupos_euclidean_clara.Rdata")
# dois_grupos <- data.frame(c(as.vector(clara_clusters$silinfo$avg.width),as.vector(clara_clusters$clustering)))
# fwrite(dois_grupos, file = "C:\\Users\\Victoria Vargas\\Downloads\\ATIVIDADE 3\\doisgrupos_euclidean_clara.csv")
# 
# rm(list=c("clara_clusters","dois_grupos"))
# gc()
# 
# i=3
# clara_clusters <- clara(credit[,c(2,6,13:24)], k=i, samples = 50,sampsize=1000, pamLike = TRUE, stand = FALSE)
# save(clara_clusters,file="C:\\Users\\Victoria Vargas\\Downloads\\ATIVIDADE 3\\tresgrupos_euclidean_clara.Rdata")
# tres_grupos <- data.frame(c(as.vector(clara_clusters$silinfo$avg.width),as.vector(clara_clusters$clustering)))
# fwrite(tres_grupos, file = "C:\\Users\\Victoria Vargas\\Downloads\\ATIVIDADE 3\\tresgrupos_euclidean_clara.csv")
# 
# rm(list=c("clara_clusters","tres_grupos"))
# gc()
# 
# i=4
# clara_clusters <- clara(credit[,c(2,6,13:24)], k=i, samples = 50,sampsize=1000, pamLike = TRUE, stand = FALSE)
# save(clara_clusters,file="C:\\Users\\Victoria Vargas\\Downloads\\ATIVIDADE 3\\quatrogrupos_euclidean_clara.Rdata")
# tres_grupos <- data.frame(c(as.vector(clara_clusters$silinfo$avg.width),as.vector(clara_clusters$clustering)))
# fwrite(quatro_grupos, file = "C:\\Users\\Victoria Vargas\\Downloads\\ATIVIDADE 3\\quatrogrupos_euclidean_clara.csv")
# 
# rm(list=c("clara_clusters","quatro_grupos"))
# gc()
# 
# i=5
# clara_clusters <- clara(credit[,c(2,6,13:24)], k=i, samples = 50,sampsize=1000, pamLike = TRUE, stand = FALSE)
# save(clara_clusters,file="C:\\Users\\Victoria Vargas\\Downloads\\ATIVIDADE 3\\cincogrupos_euclidean_clara.Rdata")
# cinco_grupos <- data.frame(c(as.vector(clara_clusters$silinfo$avg.width),as.vector(clara_clusters$clustering)))
# fwrite(cinco_grupos, file = "C:\\Users\\Victoria Vargas\\Downloads\\ATIVIDADE 3\\cincogrupos_euclidean_clara.csv")
# 
# rm(list=c("clara_clusters","cinco_grupos"))
# gc()
# 
# rm(list = ls())
# gc()
# .rs.restartR()

dois_grupos<- read.csv("C:\\Users\\Victoria Vargas\\Downloads\\ATIVIDADE 3\\doisgrupos_euclidean_clara.csv")
tres_grupos <- read.csv("C:\\Users\\Victoria Vargas\\Downloads\\ATIVIDADE 3\\tresgrupos_euclidean_clara.csv")
quatro_grupos <- read.csv("C:\\Users\\Victoria Vargas\\Downloads\\ATIVIDADE 3\\quatrogrupos_euclidean_clara.csv")
cinco_grupos <- read.csv("C:\\Users\\Victoria Vargas\\Downloads\\ATIVIDADE 3\\cincogrupos_euclidean_clara.csv")


silhueta <- cbind.data.frame (grupos=as.factor(1:5),silhueta=c(0, dois_grupos[1,],tres_grupos[1,],quatro_grupos[1,],cinco_grupos[1,]))


ggplot(data=silhueta, aes(x=grupos, y=silhueta, group=1, label=as.character(round(silhueta,2)))) +
  geom_line()+
  geom_point()+
  geom_text(hjust=0, vjust=-1.5)+
  labs(x="\nGrupos",y="Valor da silhueta média\n", title="Experimento 4 - Valor da silhueta média de acordo com o número de grupos k\n")+
  scale_y_continuous(breaks = seq(0,0.6,by=0.1), limits= c(0,0.6))+
  theme_classic()

Note que, de acordo com os valores da silhueta média, a melhor solução utilizando a distância de Euclidiana em conjunto ao algoritmo CLARA é para \(k=3\) grupos.

Com a solução do CLARA para \(k=3\) grupos, tentamos interpretar o comportamento de cada cluster com a ajuda dos medoids da solução.


Tabela 16: Experimento 4 - Medoids da solução com k=3


load("C:\\Users\\Victoria Vargas\\Downloads\\ATIVIDADE 3\\tresgrupos_euclidean_clara.Rdata")

knitr::kable(credit[clara_clusters$i.med, ])%>% kable_styling(position = "center")
ID LIMITE SEXO ESCOLARIDADE ESTADO.CIVIL IDADE HIST_SET2005 HIST_AGO2005 HIST_JUL2005 HIST_JUN2005 HIST_MAI2005 HIST_ABR2005 EXT_SET2005 EXT_AGO2005 EXT_JUL2005 EXT_JUN2005 EXT_MAI2005 EXT_ABR2005 PAG_SET2005 PAG_AGO2005 PAG_JUL2005 PAG_JUN2005 PAG_MAI2005 PAG_ABR2005 PAG_PADRAO
22435 22435 60000 2 3 2 45 0 0 0 0 0 0 26350 23908 24215 17728 18087 17730 1413 1500 1173 532 597 469 0
19731 19731 200000 2 3 1 39 -1 0 0 0 0 0 145687 146519 144315 130807 124688 122518 7627 4631 5066 5033 5007 5125 0
14890 14890 260000 1 2 2 30 0 0 0 0 0 0 7426 6887 7757 8172 8407 8681 2000 1757 2000 2000 2000 2000 0
  • Grupo/cluster 1: Limite de NT$60k, mulheres casadas com ensino médio completo e idade média de 39 anos, utilizando crédito rotativo em todos os meses de análise, extratos entre NT$17k e NT$27k, pagamentos por volta de NT$500 até junho e com média de NT$1.3k nos meses restantes e com comportamento irregular.

  • Grupo/cluster 2: Limite de NT$200k, mulheres solteiras com ensino médio completo e idade média de 45 anos, utilizando crédito rotativo com exceção do mês de setembro, extratos entre NT$12k e NT$15k, pagamentos entre NT$4k e NT$8k e com comportamento irregular.

  • Grupo/cluster 3: Limite de NT$260k, homens casados com graduação completa e idade média de 30 anos, utilizando crédito rotativo em todos os meses de análise, extratos entre NT$6k e NT$9k, pagamentos iguais a NT$2k em todos os meses com exceção de agosto, e com comportamento irregular.

Verificando a matriz de confusão para a solução com \(k=2\), obtemos:


Tabela 17: Experimento 4 - Percentual de acerto para a solução com k=2


aux <- dois_grupos
aux<- as.character(aux[-1,])

# table(aux)
# aux [which(aux=="1")] <- "0"
# aux [which(aux=="2")] <- "1"

aux [which(aux=="2")] <- "0"
# correspondencia <- data.frame(PAG_PADRAO = sum(as.character(credit$PAG_PADRAO) == aux)/30000)
# 
# correspondencia <- round(correspondencia*100,2)
# colnames(correspondencia) <- c(" Exp1 - Acertos (%)")
# knitr::kable(correspondencia)%>% kable_styling(position = "center")



#
#Creates vectors having data points
expected_value <- factor(aux)
predicted_value <- factor(credit$PAG_PADRAO)

#Creating confusion matrix
example <- confusionMatrix(data=predicted_value, reference = expected_value)

#Display results 
knitr::kable(example$table)%>% kable_styling(position = "center")
0 1
0 11515 11849
1 2162 4474
  • Na solução de agrupamento obtida, os objetos foram alocados aos clusters 1 e 2. Por possuir mais observações, tal qual o grupo 0 da base original, transformou-se o “1” em “0”. Logo, o “2” teve que ser transformado em “1”.Nessa configuração, ao compararmos a variável PAG_PADRAO com a solução do agrupamento para \(k=2\) do Experimento 2, temos que a acurácia foi de 46.7%. Então, de forma a maximizar a acurácia, optamos por transformar o cluster “2” em “0”, mantendo o cluster 1, obtendo uma acurácia de 53.3%.
  • De acordo com a tabela, o agrupamento obtido alocou um objeto pertencente ao grupo 0 originalmente ao grupo 0 do agrupamento em 11515 casos e alocou um objeto do grupo 0 ao grupo 1 em 2162 casos.
  • O agrupamento obtido alocou um objeto pertencente ao grupo 1 originalmente ao grupo 0 do agrupamento em 11849 casos, e alocou um objeto do grupo 1 ao grupo 1 do agrupamento em 4474 casos.

Conclusão

  • De acordo com as acurácias de cada método, o que teve maior porcentagem foi o da distância Euclidiana + PAM, e também, o que apresentou valores de silhueta média mais divergentes.

  • Todos os métodos utilizados tiveram mais 50% de acurácia.

  • O experimento 2 foi o que teve os valores de silhueta média mais altos e mais pariformes entre si.

  • Com as melhores soluções de acordo com cada método, conseguimos utilizar os medoids para traçar o perfil representativo de cada cluster obtido.

Limitações

  • Como o dataset possui muitas observações, ao optar por utilizar a distância de Gower foi necessário calcular uma matriz de distância de grandes dimensões (30.000 x 30.000), o que demandou um poder de processamento mais elevado.

  • Foi utilizado um computador com as seguintes características para conduzir o cálculo dessa matriz e em seguida, utilizá-la como dado de entrada nos demais algoritmos:

Processador i5-9400f 4.0GHz Memória RAM 16Gb DDR4 2666 Mhz SSD 240 Gb

  • A cada etapa do trabalho foi necessário salvar os outputs das funções para posterior uso, a fim de economizar tempo e garantir o que foi feito.

  • Para arquivos grandes, as funções fwrite do pacote data.table e saveRDS foram utilizadas para agilizar o salvamento.

  • Para os experimentos envolvendo distância euclidiana, a função distances foi empregada e, por ser otimizada, não ocorreram dificuldades quanto à utilização de memória.

  • Foi cogitado a visualização dos clusters utilizando o T-Sne, entretanto, a função Rtsne do pacote Rtsne não aceita objetos produzidos pela função distances. A visualização pela matriz de Gower seria possível, mas o acesso ao computador utilizado nos cálculos era limitado e os computadores disponíveis nesse momento do trabalho não possuíam poder de processamento suficiente sequer para a leitura do arquivo.

Referências

  1. L. Kaufman and P. J. Rousseeuw. Finding Groups in Data - An Introduction to Clusters Analysis. Wiley-Interscience Publication, 1989.

  2. W. B. Hair and B. A. R. Babin. Multivariate Data Analysis. Cengage, 8th edition, 2018.

LS0tDQp0aXRsZTogIkFuw6FsaXNlIE11bHRpdmFyaWFkYSAtIEF0aXZpZGFkZSAwMyINCmF1dGhvcjogIlZpY3TDs3JpYSBWYXJnYXMiDQpvdXRwdXQ6IA0KICBybWRmb3JtYXRzOjpyb2JvYm9vazoNCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlDQogICAgY29kZV9mb2xkaW5nOiBoaWRlIA0KICAgIA0KLS0tDQoNCiMgQW7DoWxpc2UgZGUgQWdydXBhbWVudG9zIChyZXN1bW8pIA0KDQojIyBPIHF1ZSDDqT8gDQoNCi0gR3J1cG8gZGUgdMOpY25pY2FzIG11bHRpdmFyaWFkYXMgY3VqYSBmaW5hbGlkYWRlIHByaW5jaXBhbCDDqSBhZ3JlZ2FyIG9iamV0b3MgY29tIGJhc2UgbmFzIGNhcmFjdGVyw61zdGljYXMgcXVlIGVsZXMgcG9zc3VlbS4NCg0KLSBDbGFzc2lmaWNhIG9iamV0b3MgZGUgbW9kbyBxdWUgY2FkYSBvYmpldG8gw6kgc2VtZWxoYW50ZSBhb3Mgb3V0cm9zIG5vIGFncnVwYW1lbnRvIGNvbSBiYXNlIGVtIHVtIGNvbmp1bnRvIGRlIGNhcmFjdGVyw61zdGljYXMgZXNjb2xoaWRhcy4gT3MgYWdydXBhbWVudG9zIHJlc3VsdGFudGVzIGRlIG9iamV0b3MgZGV2ZW0gZW50w6NvIGV4aWJpcjoNCg0KKDEpIEVsZXZhZGEgaG9tb2dlbmVpZGFkZSBpbnRlcm5hIChkZW50cm8gZG9zIGFncnVwYW1lbnRvcyk7ICANCigyKSBFbGV2YWRhIGhldGVyb2dlbmVpZGFkZSBleHRlcm5hIChlbnRyZSBhZ3J1cGFtZW50b3MpLiANCg0KLSBBc3NpbSwgc2UgYSBjbGFzc2lmaWNhw6fDo28gZm9yIGJlbSBzdWNlZGlkYSwgdGVyZW1vcyBncmFmaWNhbWVudGU6DQooMSkgT3Mgb2JqZXRvcyBkZW50cm8gZGUgdW0gbWVzbW8gYWdydXBhbWVudG9zIGVzdGFyw6NvIHByw7N4aW1vczsNCigyKSBPYmpldG9zIGVtIGRpZmVyZW50ZXMgYWdydXBhbWVudG9zIGVzdGFyw6NvIGRpc3RhbnRlcy4NCg0KLSBHZXJhbG1lbnRlIGVudm9sdmUgcGVsbyBtZW5vcyB0csOqcyBwYXNzb3M6DQooMSkgTyBwcmltZWlybyDDqSBhIG1lZGlkYSBkZSBhbGd1bWEgZm9ybWEgZGUgc2ltaWxhcmlkYWRlIG91IGFzc29jaWHDp8OjbyBlbnRyZSBhcyBlbnRpZGFkZXMgcGFyYSBkZXRlcm1pbmFyIHF1YW50b3MgZ3J1cG9zIHJlYWxtZW50ZSBleGlzdGVtIG5hIGFtb3N0cmEuIA0KKDIpIE8gc2VndW5kbyBwYXNzbyDDqSBvIHJlYWwgcHJvY2Vzc28gZGUgYWdydXBhbWVudG8sIG9uZGUgZW50aWRhZGVzIHPDo28gcGFydGljaW9uYWRhcyBlbSBncnVwb3MgKGFncnVwYW1lbnRvcykuIA0KKDMpIE8gw7psdGltbyBwYXNzbyDDqSBlc3RhYmVsZWNlciBvIHBlcmZpbCBkYXMgcGVzc29hcyBvdSB2YXJpw6F2ZWlzIHBhcmEgZGV0ZXJtaW5hciBzdWEgY29tcG9zacOnw6NvLiANCg0KIyMjICBEZXNlbnZvbHZpbWVudG8gY29uY2VpdHVhbCANCg0KT3MgcGFww6lpcyBtYWlzIGNvbXVucyBxdWUgYSBhbsOhbGlzZSBkZSBhZ3J1cGFtZW50b3MgcG9kZSBkZXNlbXBlbmhhciBlbSBkZXNlbnZvbHZpbWVudG8gY29uY2VpdHVhbCBpbmNsdWVtIG9zIHNlZ3VpbnRlczoNCg0KLSAqKlJlZHXDp8OjbyBkZSBkYWRvczoqKiBBIGFuw6FsaXNlIGRlIGFncnVwYW1lbnRvcyBwb2RlIHJlYWxpemFyIGVzc2UgcHJvY2VkaW1lbnRvIGRlIHJlZHXDp8OjbyBkZSBkYWRvcyBvYmpldGl2YW1lbnRlIHBlbGEgcmVkdcOnw6NvIGRhIGluZm9ybWHDp8OjbyBkZSB1bWEgcG9wdWxhw6fDo28gaW50ZWlyYSBvdSBkZSB1bWEgYW1vc3RyYSBwYXJhIGEgaW5mb3JtYcOnw6NvIHNvYnJlIHN1YmdydXBvcyBlc3BlY8OtZmljb3MgZSBtZW5vcmVzLg0KDQotICoqR2VyYcOnw6NvIGRlIGhpcMOzdGVzZXM6KiogIEEgYW7DoWxpc2UgZGUgYWdydXBhbWVudG9zIHRhbWLDqW0gw6kgw7p0aWwgcXVhbmRvIHVtIHBlc3F1aXNhZG9yIGRlc2VqYSBkZXNlbnZvbHZlciBoaXDDs3Rlc2VzIHJlbGF0aXZhcyDDoCBuYXR1cmV6YSBkb3MgZGFkb3Mgb3UgZXhhbWluYXIgaGlww7N0ZXNlcyBwcmV2aWFtZW50ZSBlc3RhYmVsZWNpZGFzLg0KDQojIyMgQWR2ZXJ0w6puY2lhcyANCg0KPiBFbSBxdWFscXVlciB1c28gZGEgYW7DoWxpc2UgZGUgYWdydXBhbWVudG9zIG8gcGVzcXVpc2Fkb3IgZGV2ZSB0ZXIgZXNwZWNpYWwgY3VpZGFkbyBwYXJhIGdhcmFudGlyIHF1ZSBmb3J0ZSBzdXBvcnRlIGNvbmNlaXR1YWwgYW50ZWNlZGEgYSBhcGxpY2HDp8OjbyBkYSB0w6ljbmljYS4NCg0KKDEpIEEgYW7DoWxpc2UgZGUgYWdydXBhbWVudG9zIMOpIGRlc2NyaXRpdmEsIG7Do28tdGXDs3JpY2EgZSBuw6NvLWluZmVyZW5jaWFsOyANCg0KKDIpIE7Do28gdGVtIGJhc2UgZXN0YXTDrXN0aWNhIHNvYnJlIGEgcXVhbCBlc2Jvw6dhciBpbmZlcsOqbmNpYXMgZGUgdW1hIGFtb3N0cmEgcGFyYSB1bWEgcG9wdWxhw6fDo287DQoNCigzKSBNdWl0b3MgY2xhbWFtIHF1ZSDDqSBhcGVuYXMgdW1hIHTDqWNuaWNhIGV4cGxvcmF0w7NyaWE7DQoNCig0KSBOYWRhIGdhcmFudGUgc29sdcOnw7VlcyDDum5pY2FzLCBqw6EgcXVlIGEgcGVydGluw6puY2lhIGEgdW0gYWdydXBhbWVudG8gcGFyYSBxdWFscXVlciBuw7ptZXJvIGRlIHNvbHXDp8O1ZXMgZGVwZW5kZSBkZSBtdWl0b3MgZWxlbWVudG9zIGRvIHByb2NlZGltZW50by4gTXVpdGFzIHNvbHXDp8O1ZXMgZGlmZXJlbnRlcyBwb2RlbSBzZXIgb2J0aWRhcyBwZWxhIHZhcmlhw6fDo28gZGUgdW0gb3UgbWFpcyBlbGVtZW50b3M7IA0KDQooNSkgU2UgcG9zc8OtdmVsLCBhIGFuw6FsaXNlIGRlIGFncnVwYW1lbnRvcyBkZXZlIHNlciBhcGxpY2FkYSBhIHBhcnRpciBkZSB1bSBtb2RvIGNvbmZpcm1hdMOzcmlvLCB1c2FuZG8tYSBwYXJhIGlkZW50aWZpY2FyIGdydXBvcyBxdWUgasOhIHTDqm0gdW1hIGZ1bmRhbWVudGHDp8OjbyBjb25jZWl0dWFsIGVzdGFiZWxlY2lkYSBxdWFudG8gw6AgZXhpc3TDqm5jaWEgZG9zIG1lc21vcy4gDQoNCig2KSBBIGFuw6FsaXNlIGRlIGFncnVwYW1lbnRvcyBzZW1wcmUgY3JpYXLDoSBhZ3J1cGFtZW50b3MsIGluZGVwZW5kZW50ZW1lbnRlIGRhIGV4aXN0w6puY2lhIHJlYWwgZGUgYWxndW1hIGVzdHJ1dHVyYSBub3MgZGFkb3M7DQoNCig3KSBBIHNvbHXDp8OjbyBkZSBhZ3J1cGFtZW50b3MgbsOjbyDDqSBnZW5lcmFsaXrDoXZlbCwgcG9pcyDDqSB0b3RhbG1lbnRlIGRlcGVuZGVudGUgZGFzIHZhcmnDoXZlaXMgdXNhZGFzIGNvbW8gYmFzZSBwYXJhIGEgbWVkaWRhIGRlIHNpbWlsYXJpZGFkZS4NCg0KDQojIyMgQW7DoWxpc2UgRmF0b3JpYWwgdmVyc3VzIEFuw6FsaXNlIGRlIEFncnVwYW1lbnRvcw0KDQpTw6NvIHNlbWVsaGFudGVzIGVtIHNldSBvYmpldGl2byBkZSBhdmFsaWFyIGVzdHJ1dHVyYSBpbmVyZW50ZSBhb3MgZGFkb3MuIA0KDQooMSkgKipBbsOhbGlzZSBGYXRvcmlhbCoqDQogICAgKGEpIGFncmVnYSBvYmpldG9zOw0KICAgIChiKSBmYXogb3MgYWdydXBhbWVudG9zIGNvbSBiYXNlIGVtIHBhZHLDtWVzIGRlIHZhcmlhw6fDo28gKGNvcnJlbGHDp8Ojbykgbm9zIGRhZG9zLg0KICAgIA0KKDIpICoqQW7DoWxpc2UgZGUgQWdydXBhbWVudG9zKiogDQogICAgKGEpIGVzdMOhIHByaW9yaXRhcmlhbWVudGUgaW50ZXJlc3NhZGEgZW0gYWdyZWdhciB2YXJpw6F2ZWlzOw0KICAgIChiKSBmYXogYWdyZWdhZG9zIGJhc2VhZG9zIGVtIGRpc3TDom5jaWEgKHByb3hpbWlkYWRlKS4NCg0KDQoNCiMjIENvbW8gZnVuY2lvbmE/DQoNCi0gQSBhbsOhbGlzZSBkZSBhZ3J1cGFtZW50b3MgZXhlY3V0YSB1bWEgdGFyZWZhIGluYXRhIGEgdG9kb3Mgb3MgaW5kaXbDrWR1b3Mg4oCTIHJlY29uaGVjaW1lbnRvIGRlIHBhZHLDtWVzIGUgYWdydXBhbWVudG8uDQotIE8gb2JqZXRpdm8gcHJpbmNpcGFsIMOpIGRlZmluaXIgYSBlc3RydXR1cmEgZG9zIGRhZG9zIGNvbG9jYW5kbyBhcyBvYnNlcnZhw6fDtWVzIG1haXMgcGFyZWNpZGFzIGVtIGdydXBvcy4gUGFyYSBjb25zZWd1aXIgaXNzbywgZGV2ZW1vcyB0cmF0YXIgZGUgdHLDqnMgcXVlc3TDtWVzIGLDoXNpY2FzOg0KDQooMSkgQ29tbyBtZWRpciBhIHNpbWlsYXJpZGFkZT8NCigyKSBDb21vIGZvcm1hciBvcyBhZ3J1cGFtZW50b3M/DQooMykgUXVhbnRvcyBncnVwb3MgZm9ybWFyPyANCg0KDQojIEFuw6FsaXNlIGRlc2NyaXRpdmEgDQoNCmBgYHtyIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9DQpsaWJyYXJ5KHBhY21hbikNCmxpYnJhcnkocnN0YXRpeCkNCmxpYnJhcnkoZTEwNzEpDQpsaWJyYXJ5KHJlYWR4bCkNCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShkcGx5cikNCmxpYnJhcnkoa2FibGVFeHRyYSkNCmxpYnJhcnkoa25pdHIpDQpsaWJyYXJ5KGdncGxvdDIpDQpsaWJyYXJ5KFJDb2xvckJyZXdlcikNCmxpYnJhcnkoY29ycnBsb3QpDQpsaWJyYXJ5KGdnY29ycnBsb3QpDQpsaWJyYXJ5KGRhdGEudGFibGUpDQpsaWJyYXJ5KGNsdXN0ZXIpDQpwYWNtYW46OnBfbG9hZChrbml0ciwgY2FwdGlvbmVyLCBidW5kZXNsaWdSLCBzdHJpbmdyKQ0KDQp0YWJsZV9udW1zIDwtIGNhcHRpb25lcjo6Y2FwdGlvbmVyKHByZWZpeCA9ICJUYWJlbGEiKQ0KdGFiLjFfY2FwIDwtIHRhYmxlX251bXMobmFtZSA9ICJ0YWJfMSIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgIGNhcHRpb24gPSAiQ2FyYWN0ZXLDrXN0aWNhcyBiw6FzaWNhcyBkbyBkYXRhc2V0IGNyZWRpdCIpDQp0YWIuMl9jYXAgPC0gdGFibGVfbnVtcyhuYW1lID0gInRhYl8yIiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgY2FwdGlvbiA9ICJEZXNjcmnDp8O1ZXMgZGFzIHZhcmnDoXZlaXMgZG8gZGF0YXNldCBjcmVkaXQiKQ0KDQp0YWIuM19jYXAgPC0gdGFibGVfbnVtcyhuYW1lID0gInRhYl8zIiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgY2FwdGlvbiA9ICJBbW9zdHJhIGRvIGRhdGFzZXQgY3JlZGl0IikNCg0KdGFiLjRfY2FwIDwtIHRhYmxlX251bXMobmFtZSA9ICJ0YWJfNCIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgIGNhcHRpb24gPSAiRXN0YXTDrXN0aWNhcyByZXN1bW8gZGFzIHZhcmnDoXZlaXMgcXVhbnRpdGF0aXZhcyBkbyBkYXRhc2V0IGNyZWRpdCIpDQoNCnRhYi41X2NhcCA8LSB0YWJsZV9udW1zKG5hbWUgPSAidGFiXzUiLCANCiAgICAgICAgICAgICAgICAgICAgICAgICBjYXB0aW9uID0gIkZyZXF1w6puY2lhcyByZWxhdGl2YXMgZGEgdmFyacOhdmVsIFNFWE8iKQ0KDQp0YWIuNl9jYXAgPC0gdGFibGVfbnVtcyhuYW1lID0gInRhYl82IiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgY2FwdGlvbiA9ICJGcmVxdcOqbmNpYXMgcmVsYXRpdmFzIGRhIHZhcmnDoXZlbCBFU0NPTEFSSURBREUiKQ0KDQp0YWIuN19jYXAgPC0gdGFibGVfbnVtcyhuYW1lID0gInRhYl83IiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgY2FwdGlvbiA9ICJGcmVxdcOqbmNpYXMgcmVsYXRpdmFzIGRhIHZhcmnDoXZlbCBFU1RBRE8gQ0lWSUwiKQ0KDQp0YWIuOF9jYXAgPC0gdGFibGVfbnVtcyhuYW1lID0gInRhYl84IiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgY2FwdGlvbiA9ICJGcmVxdcOqbmNpYXMgcmVsYXRpdmFzIGRhIHZhcmnDoXZlbCBQQUdfUEFEUkFPIikNCg0KdGFiLjlfY2FwIDwtIHRhYmxlX251bXMobmFtZSA9ICJ0YWJfOSIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgIGNhcHRpb24gPSAiQ29lZmljaWVudGVzIGRlIGFzc2ltZXRyaWEgZGFzIHZhcmnDoXZlaXMgcXVhbnRpdGF0aXZhcyIpDQoNCg0KYGBgDQoNCg0KQSBBbsOhbGlzZSBEZXNjcml0aXZhIMOpIGEgZmFzZSBpbmljaWFsIGRvIHByb2Nlc3NvIGRlIGVzdHVkbyBkb3MgZGFkb3MgY29sZXRhZG9zLCBlbSBxdWUgDQpzw6NvIHV0aWxpemFkb3MgIG3DqXRvZG9zIGRlIEVzdGF0w61zdGljYSBEZXNjcml0aXZhIHBhcmEgb3JnYW5pemFyLCByZXN1bWlyLCBkZXNjcmV2ZXIgZSBjb21wYXJhciBhc3BlY3RvcyBpbXBvcnRhbnRlcyBkZSB1bSBjb25qdW50byBkZSB2YXJpw6F2ZWlzLiBBIGlkZW50aWZpY2HDp8OjbyBkZSBhbm9tYWxpYXMgZSBlIGRhZG9zIGRpc3BlcnNvcyB0YW1iw6ltIGZheiBwYXJ0ZSBkZXNzZSB0aXBvIGRlIGFuw6FsaXNlLg0KDQojIyBTb2JyZSBvIGRhdGFzZXQgDQoNCkZvaSB1dGlsaXphZG8gbyBkYXRhc2V0ICpkZWZhdWx0IG9mIGNyZWRpdCBjYXJkIGNsaWVudHMueGxzKiwgcmV0aXJhZG8gZG8gDQpbTWFjaGluZSBMZWFybmluZyBSZXBvc2l0b3J5IGRhIFVuaXZlcnNpdHkgb2YgQ2FsaWZvcm5pYSwgSXJ2aW5lIChVQ0kpXShodHRwOi8vYXJjaGl2ZS5pY3MudWNpLmVkdS9tbC9kYXRhc2V0cy9kZWZhdWx0K29mK2NyZWRpdCtjYXJkK2NsaWVudHMpLg0KDQpPIGRhdGFzZXQgZm9pIG5vbWVhZG8gKmNyZWRpdCogZSBjb250w6ltIGRhZG9zIHBlc3NvYWlzIGUgYmFuY8OhcmlvcyBkZSBjb25zdW1pZG9yZXMgZW0gVGFpd2FuLCAgYWJyYW5nZW5kbyBvIHBlcsOtb2RvIGRlIGFicmlsIGEgc2V0ZW1icm8gZGUgMjAwNS4gDQoNClRlbmRvIHBvciBiYXNlIGVzc2VzIGRhZG9zLCBvIGNvbXBvcnRhbWVudG8gZG9zIGNvbnN1bWlkb3JlcyBmb2kgY2xhc3NpZmljYWRvIGVtIHJlZ3VsYXIgb3UgaXJyZWd1bGFyLCBhIGZpbSBkZSBwcmV2ZXIgZnJhdWRlcyBkZSBjcsOpZGl0by4gDQoNCkFww7NzIGEgIGxlaXR1cmEgZG9zIGRhZG9zLCBvcyBub21lcyBkYXMgdmFyacOhdmVpcyBmb3JhbSBtb2RpZmljYWRvcyBwYXJhIGZhY2lsaXRhciBhIGNvbXByZWVuc8OjbyBlIHBvc3RlcmlvciBtYW5pcHVsYcOnw6NvIGRvcyBkYWRvcy4NCg0KYGBge3IsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9DQojTGVpdHVyYSBkb3MgZGFkb3MNCiMgY3JlZGl0PC0gcmVhZF9leGNlbCgiRTpcXE11bHRpdmFyaWFkYVxcQXRpdmlkYWRlIDAzXFxkZWZhdWx0IG9mIGNyZWRpdCBjYXJkIGNsaWVudHMueGxzIiwNCiMgICAgICAgICAgICAgICAgICAgICBjb2xfdHlwZXMgPSBjKCJ0ZXh0IiwgIm51bWVyaWMiLCAibnVtZXJpYyIsIm51bWVyaWMiLCAibnVtZXJpYyIsIA0KIyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIm51bWVyaWMiLCAibnVtZXJpYyIsICJudW1lcmljIiwgIm51bWVyaWMiLCAibnVtZXJpYyIsIA0KIyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIm51bWVyaWMiLCAibnVtZXJpYyIsICJudW1lcmljIiwgIm51bWVyaWMiLCAibnVtZXJpYyIsIA0KIyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIm51bWVyaWMiLCJudW1lcmljIiwibnVtZXJpYyIsIm51bWVyaWMiLCJudW1lcmljIiwNCiMgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJudW1lcmljIiwibnVtZXJpYyIsIm51bWVyaWMiLCJudW1lcmljIiwidGV4dCIpKQ0KDQojQWx0ZXJhbmRvIG5vbWVzIGRhcyB2YXJpw6F2ZWlzIA0KDQojIGNvbG5hbWVzKGNyZWRpdCk8LWNyZWRpdFsxLF0NCiMgY3JlZGl0PC1jcmVkaXRbLTEsXQ0KIyBjb2xuYW1lcyhjcmVkaXQpIDwtIGMoIklEIiwNCiMgICAgICAgICAgICAgICAgICAgICAgICJMSU1JVEUiLA0KIyAgICAgICAgICAgICAgICAgICAgICAgIlNFWE8iLA0KIyAgICAgICAgICAgICAgICAgICAgICAgIkVTQ09MQVJJREFERSIsDQojICAgICAgICAgICAgICAgICAgICAgICAiRVNUQURPIENJVklMIiwNCiMgICAgICAgICAgICAgICAgICAgICAgICJJREFERSIsDQojICAgICAgICAgICAgICAgICAgICAgICAiSElTVF9TRVQyMDA1IiwNCiMgICAgICAgICAgICAgICAgICAgICAgICJISVNUX0FHTzIwMDUiLA0KIyAgICAgICAgICAgICAgICAgICAgICAgIkhJU1RfSlVMMjAwNSIsDQojICAgICAgICAgICAgICAgICAgICAgICAiSElTVF9KVU4yMDA1IiwNCiMgICAgICAgICAgICAgICAgICAgICAgICJISVNUX01BSTIwMDUiLA0KIyAgICAgICAgICAgICAgICAgICAgICAgIkhJU1RfQUJSMjAwNSIsDQojICAgICAgICAgICAgICAgICAgICAgICAiRVhUX1NFVDIwMDUiLA0KIyAgICAgICAgICAgICAgICAgICAgICAgIkVYVF9BR08yMDA1IiwNCiMgICAgICAgICAgICAgICAgICAgICAgICJFWFRfSlVMMjAwNSIsDQojICAgICAgICAgICAgICAgICAgICAgICAiRVhUX0pVTjIwMDUiLA0KIyAgICAgICAgICAgICAgICAgICAgICAgIkVYVF9NQUkyMDA1IiwNCiMgICAgICAgICAgICAgICAgICAgICAgICJFWFRfQUJSMjAwNSIsDQojICAgICAgICAgICAgICAgICAgICAgICAiUEFHX1NFVDIwMDUiLA0KIyAgICAgICAgICAgICAgICAgICAgICAgIlBBR19BR08yMDA1IiwNCiMgICAgICAgICAgICAgICAgICAgICAgICJQQUdfSlVMMjAwNSIsDQojICAgICAgICAgICAgICAgICAgICAgICAiUEFHX0pVTjIwMDUiLA0KIyAgICAgICAgICAgICAgICAgICAgICAgIlBBR19NQUkyMDA1IiwNCiMgICAgICAgICAgICAgICAgICAgICAgICJQQUdfQUJSMjAwNSIsDQojICAgICAgICAgICAgICAgICAgICAgICAiUEFHX1BBRFJBTyIpDQoNCiMgbGlicmFyeShkYXRhLnRhYmxlKQ0KIyBmd3JpdGUoY3JlZGl0LCBmaWxlID0gImNyZWRpdF9jbGVhbi5jc3YiKQ0KDQpjcmVkaXQ8LSByZWFkLmNzdigiQzpcXFVzZXJzXFxWaWN0b3JpYSBWYXJnYXNcXERvd25sb2Fkc1xcQVRJVklEQURFIDNcXGNyZWRpdF9jbGVhbi5jc3YiKQ0KDQpgYGANCg0KDQojIyBDYXJhY3RlcsOtc3RpY2FzIGLDoXNpY2FzDQoNCg0KLS0tDQoNCjxkaXYgYWxpZ249ImNlbnRlciI+DQogICBgciB0YWJsZV9udW1zKCd0YWJfMScpYA0KPC9kaXY+DQoNCi0tLQ0KDQoNCmBgYHtyfQ0KY2FyYWN0X2Jhc2ljIDwtIGRhdGEuZnJhbWUoTkFzID0gc3VtKGlzLm5hKGNyZWRpdCkpLCBPYnNlcnZhw6fDtWVzID0gbnJvdyhjcmVkaXQpLCBWYXJpw6F2ZWlzID0gbmNvbChjcmVkaXQpLCBEdXBsaWNpZGFkZXMgPSBhbnlEdXBsaWNhdGVkLmRhdGEuZnJhbWUoY3JlZGl0KSkNCg0Ka25pdHI6OmthYmxlKGNhcmFjdF9iYXNpYyklPiUga2FibGVfc3R5bGluZyhwb3NpdGlvbiA9ICJjZW50ZXIiKQ0KYGBgDQoNCkRlIGFjb3JkbyBjb20gYSBUYWJlbGEgMTogDQoNCiogTyBkYXRhc2V0IHBvc3N1aSAzMC4wMDAgb2JzZXJ2YcOnw7VlcyBlIDI1IHZhcmnDoXZlaXM7DQoqIE7Do28gcG9zc3VpIE5BcyBlIG5lbSBsaW5oYXMgZHVwbGljYWRhcy4NCg0KDQoNCiMjIERlc2NyacOnw6NvIGRvIGRhdGFzZXQNCg0KLS0tDQoNCjxkaXYgYWxpZ249ImNlbnRlciI+DQogICBgciB0YWJsZV9udW1zKCd0YWJfMicpYA0KPC9kaXY+DQoNCi0tLQ0KDQpgYGB7cn0NCg0KY29sdW5hczwtIGMoY29sbmFtZXMoY3JlZGl0KVsxOjZdLCJISVNUX1NFVDIwMDUgYSBISVNUX0FCUjIwMDUiLCJFWFRfU0VUMjAwNSBhIEVYVF9BQlIyMDA1IiwiUEFHX1NFVDIwMDUgYSBQQUdfQUJSMjAwNSIsIlBBR19QQURSQU8iKQ0KZGVzY3JpY2FvIDwtIGRhdGEuZnJhbWUoVmFyacOhdmVsID0gY29sdW5hcywNCiAgICAgICAgICAgICAgICAgICAgICAgIFRpcG8gPSBjKCJUZXh0byIsIkludGVpcm8iLCJDYXRlZ8OzcmljYSIsIkNhdGVnw7NyaWNhIiwiQ2F0ZWfDs3JpY2EiLCJJbnRlaXJvIiwiQ2F0ZWfDs3JpY2EiLCJJbnRlaXJvIiwiSW50ZWlybyIsIkJpbsOhcmlhIiksDQogICAgICAgICAgICAgICAgICAgICAgICBEZXNjcmnDp8OjbyA9IGMoIklkIGRhIG9ic2VydmHDp8Ojby4iLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiVmFsb3IgZG8gY3LDqWRpdG8gY29uY2VkaWRvIChlbSBOb3ZvIGTDs2xhciB0YWl3YW7DqnMpOiBpbmNsdWkgdGFudG8gbyBjcsOpZGl0byBpbmRpdmlkdWFsIGFvIGNvbnN1bWlkb3IgcXVhbnRvIG8gY3LDqWRpdG8gZmFtaWxpYXIgKGNvbXBsZW1lbnRhcikuIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIlNleG8gKDE9IG1hc2N1bGlubzsgMiA9IGZlbWluaW5vKS4iLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiRXNjb2xhcmlkYWRlICgxID0gcMOzcy1ncmFkdWHDp8OjbzsgMiA9IHVuaXZlcnNpZGFkZTsgMyA9IGVuc2lubyBtw6lkaW87IDAsIDQsIDUsIDYgPSBvdXRyb3MpLiIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJFc3RhZG8gY2l2aWwgKDEgPSBjYXNhZG87IDIgPSBzb2x0ZWlybzsgMyA9IGRpdm9yY2lhZG87IDAgPSBvdXRyb3MpLiIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJJZGFkZSBlbSBhbm9zLiIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJIaXN0w7NyaWNvIGRlIHBhZ2FtZW50b3MgYW50ZXJpb3Jlcy4gQWNvbXBhbmhhIG8gcmVnaXN0cm9zIGRlIHBhZ2FtZW50b3MgbWVuc2FpcyBkZSBhYnJpbCBhIHNldGVtYnJvIGRlIDIwMDUgYXRyYXbDqXMgZGEgc2VndWludGUgZXNjYWxhOiAgLTI6IFNlbSBjb25zdW1vOyAtMTogcGFnbyBpbnRlZ3JhbG1lbnRlOyAwOiBPIHVzbyBkZSBjcsOpZGl0byByb3RhdGl2bzsgMSA9IGF0cmFzbyBubyBwYWdhbWVudG8gcG9yIHVtIG3DqnM7IDIgPSBhdHJhc28gbm8gcGFnYW1lbnRvIHBvciBkb2lzIG1lc2VzOyAuLi4gOyA4ID0gYXRyYXNvIG5vIHBhZ2FtZW50byBwb3Igb2l0byBtZXNlczsgOSA9IGF0cmFzbyBubyBwYWdhbWVudG8gcG9yIG5vdmUgbWVzZXMgb3UgbWFpcy4iLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiVmFsb3IgZG8gZXh0cmF0byBkYSBjb250YSAoZW0gTm92byBkw7NsYXIgdGFpd2Fuw6pzKS4gIEFjb21wYW5oYSBvIHZhbG9yIGRvIGV4dHJhdG8gZGEgY29udGEgZGUgYWJyaWwgYSBzZXRlbWJybyBkZSAyMDA1LiIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJWYWxvciBkb3MgcGFnYW1lbnRvcyBhbnRlcmlvcmVzIChlbSBOb3ZvIGTDs2xhciB0YWl3YW7DqnMpLiBBY29tcGFuaGEgbyB2YWxvciBwYWdvIGRlIGFicmlsIGEgc2V0ZW1icm8gZGUgMjAwNSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJDb21wb3J0YW1lbnRvIGRvIGNsaWVudGUgcXVhbnRvIGFvcyBnYXN0b3MgZSBwYWdhbWVudG9zICgwID0gaXJyZWd1bGFyOyAxID0gcmVndWxhcikiKSkNCg0Ka25pdHI6OmthYmxlKGRlc2NyaWNhbyklPiUga2FibGVfc3R5bGluZyhwb3NpdGlvbiA9ICJjZW50ZXIiKQ0KDQpgYGANCg0KVW1hIGFtb3N0cmEgZG8gZGF0YXNldCBwb2RlIHNlciB2aXN1YWxpemFkYSBhYmFpeG86DQoNCi0tLQ0KDQo8ZGl2IGFsaWduPSJjZW50ZXIiPg0KICAgYHIgdGFibGVfbnVtcygndGFiXzMnKWANCjwvZGl2Pg0KDQotLS0NCg0KYGBge3J9DQprbml0cjo6a2FibGUoaGVhZChjcmVkaXQpKSU+JSBrYWJsZV9zdHlsaW5nKHBvc2l0aW9uID0gImNlbnRlciIpDQpgYGANCg0KDQojIyBFc3RhdMOtc3RpY2FzIHJlc3VtbyAodmFyacOhdmVpcyBxdWFudGl0YXRpdmFzKQ0KDQpPIGPDoWxjdWxvIGRhcyBlc3RhdMOtc3RpY2FzIHJlc3VtbyBmb2kgZmVpdG8gcGFyYSBhcyB2YXJpw6F2ZWlzIHF1YW50aXRhdGl2YXMuDQoNCi0tLQ0KDQo8ZGl2IGFsaWduPSJjZW50ZXIiPg0KICAgYHIgdGFibGVfbnVtcygndGFiXzQnKWANCjwvZGl2Pg0KDQotLS0NCg0KYGBge3IgfQ0Ka25pdHI6OmthYmxlKGdldF9zdW1tYXJ5X3N0YXRzKGNyZWRpdFssYygyLDYsMTM6MjQpXSkgWywtYygyLDgsOSwxMiwxMyldKSU+JSBrYWJsZV9zdHlsaW5nKHBvc2l0aW9uID0gImNlbnRlciIpDQpgYGANCg0KLSBQYXJhIGEgdmFyacOhdmVsICoqSURBREUqKiwgdGVtb3MgcXVlIGFzIGlkYWRlcyBtw61uaW1hIGUgbcOheGltYSBvYnNlcnZhZGFzIHPDo28gMjEgZSA3OSBhbm9zLCByZXNwZWN0aXZhbWVudGUuIA0KQSBtZWRpYW5hIMOpIDM5IGFub3MsIG91IHNlamEsIDUwJSBkb3MgY2xpZW50ZXMgb2JzZXJ2YWRvcyBwb3NzdWVtIGlkYWRlIG1lbm9yIG91IGlndWFsIGEgMzkgYW5vcy4NCg0KLSBQYXJhIGEgdmFyacOhdmVsICoqTElNSVRFKiosIHRlbW9zIHF1ZSBvIHZhbG9yIG3DrW5pbW8gZSBtw6F4aW1vIGRlIGNyw6lkaXRvIGNvbmNlZGlkbyBzw6NvIE5UXCQxMC4wMDAgZSBOVFwkMS4wMDAuMDAwICwgcmVzcGVjdGl2YW1lbnRlLiBBIG1lZGlhbmEgw6kgTlRcJDE0MC4wMDAsIG91IHNlamEsIDUwJSBkb3MgY2xpZW50ZXMgb2JzZXJ2YWRvcyBmb3JhbSBjb25jZWRpZG9zIHVtIHZhbG9yIGVtIGNyw6lkaXRvIG1lbm9yIG91IGlndWFsIGEgTlRcJDE0MC4wMDAuDQoNCi0gQXMgbWVkaWFuYXMgZGFzIHZhcmnDoXZlaXMgKipQQUdfU0VUMjAwNSoqIGEgKipQQUdfQUJSMjAwNSoqIGFzc3VtZW0gdmFsb3JlcyBuYSBmYWl4YSBOVFwkMTUwMC1OVFwkMjEwMCwgb3Ugc2VqYSwgNTAlIGRvcyBjbGllbnRlcyBvYnNlcnZhZG9zIGZpemVyYW0gdW0gcGFnYW1lbnRvIGVudHJlIE5UXCQxNTAwIGUgTlRcJDIxMDAuDQoNCi0gQXMgbWVkaWFuYXMgZGFzIHZhcmnDoXZlaXMgKipFWFRfU0VUMjAwNSoqIGEgKipFWFRfQUJSMjAwNSoqIGFzc3VtZW0gdmFsb3JlcyBuYSBmYWl4YSBOVFwkMTcwNzEtTlRcJDIyMzgxLjUsIG91IHNlamEsIDUwJSBkb3MgZXh0cmF0b3MgZGUgY2xpZW50ZXMgb2JzZXJ2YWRvcyBwb3NzdWVtIHZhbG9yIGVudHJlIE5UXCQxNzA3MSBlIE5UXCQyMjM4MS41Lg0KDQotIE9ic2VydmVtIHF1ZSwgcGVsbyBjb21wb3J0YW1lbnRvIG1lZGlhbm8sIHBvZGVtb3MgcGVyY2ViZXIgcXVlIG11aXRvcyBkb3MgY2xpZW50ZXMgcG9zc3VlbSB1bSB2YWxvciBkZSBleHRyYXRvIHN1cGVyaW9yIGFvIHZhbG9yIGRlIHBhZ2FtZW50by4gDQoNCiMjIFRhYmVsYXMgZGUgZnJlcXXDqm5jaWFzIHJlbGF0aXZhcyANCg0KRm9yYW0gZmVpdGFzIHRhYmVsYXMgZGUgZnJlcXXDqm5jaWFzIHJlbGF0aXZhcyBwYXJhIGFzIHZhcmnDoXZlaXMgY2F0ZWfDs3JpY2FzIHJlbGF0aXZhcyBhIGNhcmFjdGVyw61zdGljYXMgcGVzc29haXMgKFNFWE8sIEVTQ09MQVJJREFERSBlIEVTVEFETyBDSVZJTCkgZSBwYXJhIGEgdmFyacOhdmVsIGJpbsOhcmlhIChQQUdfUEFEUkFPKS4NCg0KLS0tDQoNCjxkaXYgYWxpZ249ImNlbnRlciI+DQogICBgciB0YWJsZV9udW1zKCd0YWJfNScpYA0KPC9kaXY+DQoNCi0tLQ0KYGBge3J9DQpzZXhvIDwtIGRhdGEuZnJhbWUocm91bmQoKHRhYmxlKGNyZWRpdCRTRVhPKS9ucm93KGNyZWRpdCkpKjEwMCwyKSkNCmNvbG5hbWVzKHNleG8pIDwtIGMoIlNFWE8iLCJGcmVxdcOqbmNpYSByZWxhdGl2YSAoJSkiKQ0Ka25pdHI6OmthYmxlKHNleG8pJT4lIGthYmxlX3N0eWxpbmcocG9zaXRpb24gPSAiY2VudGVyIikNCmBgYA0KDQoNCi0gMzkuNjMlIGRvcyBjbGllbnRlcyBzw6NvIGRvIHNleG8gbWFzY3VsaW5vDQotIDYwLjM3JSBkb3MgY2xpZW50ZXMgc8OjbyBkbyBzZXhvIGZlbWluaW5vIA0KDQoNCg0KLS0tDQoNCjxkaXYgYWxpZ249ImNlbnRlciI+DQogICBgciB0YWJsZV9udW1zKCd0YWJfNicpYA0KPC9kaXY+DQoNCi0tLQ0KYGBge3J9DQplc2NvbGFyaWRhZGUgPC0gZGF0YS5mcmFtZShyb3VuZCgodGFibGUoY3JlZGl0JEVTQ09MQVJJREFERSkvbnJvdyhjcmVkaXQpKSoxMDAsMikpDQpjb2xuYW1lcyhlc2NvbGFyaWRhZGUpIDwtIGMoIkVTQ09MQVJJREFERSIsIkZyZXF1w6puY2lhIHJlbGF0aXZhICglKSIpDQprbml0cjo6a2FibGUoZXNjb2xhcmlkYWRlKSU+JSBrYWJsZV9zdHlsaW5nKHBvc2l0aW9uID0gImNlbnRlciIpDQpgYGANCg0KDQotIDQ2Ljc3JSBkb3MgY2xpZW50ZXMgcG9zc3VlbSBncmFkdWHDp8OjbyANCi0gMzUuMjglIGRvcyBjbGllbnRlcyBwb3NzdWVtIHDDs3MtZ3JhZHVhw6fDo28gDQotIDE2LjM5JSBkb3MgY2xpZW50ZXMgcG9zc3VlbSBzb21lbnRlIGVuc2lubyBtw6lkaW8gDQotIDEuNTYlIGRvcyBjbGllbnRlcyBlc3TDo28gZW0gb3V0cmFzIGZhaXhhcyBkZSBlc2NvbGFyaWRhZGUNCg0KDQotLS0NCg0KPGRpdiBhbGlnbj0iY2VudGVyIj4NCiAgIGByIHRhYmxlX251bXMoJ3RhYl83JylgDQo8L2Rpdj4NCg0KLS0tDQpgYGB7cn0NCiMgZXN0X2NpdmlsIDwtIGRhdGEuZnJhbWUocm91bmQoKHRhYmxlKGNyZWRpdCRgRVNUQURPIENJVklMYCkvbnJvdyhjcmVkaXQpKSoxMDAsMikpDQojIGNvbG5hbWVzKGVzdF9jaXZpbCkgPC0gYygiRVNUQURPIENJVklMIiwiRnJlcXXDqm5jaWEgcmVsYXRpdmEgKCUpIikNCiMga25pdHI6OmthYmxlKGVzdF9jaXZpbCklPiUga2FibGVfc3R5bGluZyhwb3NpdGlvbiA9ICJjZW50ZXIiKQ0KDQoNCg0KZXN0X2NpdmlsIDwtIGRhdGEuZnJhbWUocm91bmQoKHRhYmxlKGNyZWRpdCRFU1RBRE8uQ0lWSUwpL25yb3coY3JlZGl0KSkqMTAwLDIpKQ0KY29sbmFtZXMoZXN0X2NpdmlsKSA8LSBjKCJFU1RBRE8gQ0lWSUwiLCJGcmVxdcOqbmNpYSByZWxhdGl2YSAoJSkiKQ0Ka25pdHI6OmthYmxlKGVzdF9jaXZpbCklPiUga2FibGVfc3R5bGluZyhwb3NpdGlvbiA9ICJjZW50ZXIiKQ0KYGBgDQoNCg0KLQk1My4yMSUgZG9zIGNsaWVudGVzIHPDo28gc29sdGVpcm9zDQotIDQ1LjUzICUgZG9zIGNsaWVudGVzIHPDo28gY2FzYWRvcw0KLQkxLjA4JSBkb3MgY2xpZW50ZXMgc8OjbyBkaXZvcmNpYWRvcw0KLSAwLjE4JSBkb3MgY2xpZW50ZXMgcG9zc3VlbSBvdXRybyBlc3RhZG8gY2l2aWwNCg0KLS0tDQoNCjxkaXYgYWxpZ249ImNlbnRlciI+DQogICBgciB0YWJsZV9udW1zKCd0YWJfOCcpYA0KPC9kaXY+DQoNCi0tLQ0KYGBge3J9DQpwYWdfcGFkciA8LWRhdGEuZnJhbWUocm91bmQoKHRhYmxlKGNyZWRpdCRQQUdfUEFEUkFPKS9ucm93KGNyZWRpdCkpKjEwMCwyKSkNCmNvbG5hbWVzKHBhZ19wYWRyKSA8LSBjKCJQQUdfUEFEUkFPIiwiRnJlcXXDqm5jaWEgcmVsYXRpdmEgKCUpIikNCmtuaXRyOjprYWJsZShwYWdfcGFkciklPiUga2FibGVfc3R5bGluZyhwb3NpdGlvbiA9ICJjZW50ZXIiKQ0KYGBgDQoNCi0gRm9pIGRldGVjdGFkbyBjb21wb3J0YW1lbnRvIGlycmVndWxhciBlbSA3Ny44OCUgZG9zIGNsaWVudGVzIG9ic2VydmFkb3MNCi0gRm9pIGRldGVjdGFkbyBjb21wb3J0YW1lbnRvIHJlZ3VsYXIgZW0gMjIuMTIlIGRvcyBjbGllbnRlcyBvYnNlcnZhZG9zIA0KDQoNCiMjIEdyw6FmaWNvcyBkZSBiYXJyYXMgKHZhcmnDoXZlaXMgZGUgaGlzdMOzcmljbyBkZSBwYWdhbWVudG8pDQoNCmBgYHtyfQ0KDQpwYXIobWZyb3c9YygzLDIpKQ0KDQpoaXN0c2V0MjAwNTwtIGRhdGEuZnJhbWUodGFibGUoY3JlZGl0JEhJU1RfU0VUMjAwNSkpDQpjb2xuYW1lcyhoaXN0c2V0MjAwNSk8LSBjKCJISVNUX1NFVDIwMDUiLCJGcmVxdcOqbmNpYSIpDQoNCmhpc3RhZ28yMDA1PC0gZGF0YS5mcmFtZSh0YWJsZShjcmVkaXQkSElTVF9BR08yMDA1KSkNCmNvbG5hbWVzKGhpc3RhZ28yMDA1KTwtIGMoIkhJU1RfQUdPMjAwNSIsIkZyZXF1w6puY2lhIikNCg0KaGlzdGp1bDIwMDU8LSBkYXRhLmZyYW1lKHRhYmxlKGNyZWRpdCRISVNUX0pVTDIwMDUpKQ0KY29sbmFtZXMoaGlzdGp1bDIwMDUpPC0gYygiSElTVF9KVUwyMDA1IiwiRnJlcXXDqm5jaWEiKQ0KDQpoaXN0anVuMjAwNTwtIGRhdGEuZnJhbWUodGFibGUoY3JlZGl0JEhJU1RfSlVOMjAwNSkpDQpjb2xuYW1lcyhoaXN0anVuMjAwNSk8LSBjKCJISVNUX0pVTjIwMDUiLCJGcmVxdcOqbmNpYSIpDQoNCmhpc3RtYWkyMDA1PC0gZGF0YS5mcmFtZSh0YWJsZShjcmVkaXQkSElTVF9NQUkyMDA1KSkNCmNvbG5hbWVzKGhpc3RtYWkyMDA1KTwtIGMoIkhJU1RfTUFJMjAwNSIsIkZyZXF1w6puY2lhIikNCg0KaGlzdGFicjIwMDU8LSBkYXRhLmZyYW1lKHRhYmxlKGNyZWRpdCRISVNUX0FCUjIwMDUpKQ0KY29sbmFtZXMoaGlzdGFicjIwMDUpPC0gYygiSElTVF9BQlIyMDA1IiwiRnJlcXXDqm5jaWEiKQ0KDQpnZ3Bsb3QoZGF0YT1oaXN0c2V0MjAwNSwgYWVzKHg9SElTVF9TRVQyMDA1LCB5PUZyZXF1w6puY2lhKSkgKw0KICBnZW9tX2JhcihzdGF0PSJpZGVudGl0eSIsIGNvbG9yPSJibHVlIiwgZmlsbD0id2hpdGUiKSsNCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbD1GcmVxdcOqbmNpYSksIHZqdXN0PS0wLjMsIGNvbG9yPSJibGFjayIsIHNpemU9My41KSsNCiAgbGFicyh4PSJcbkhJU1RfU0VUMjAwNSIseT0iRnJlcXXDqm5jaWFcbiIsIHRpdGxlPSJGcmVxdcOqbmNpYXMgYWJzb2x1dGFzIC0gSElTVF9TRVQyMDA1XG4iKSsNCiAgdGhlbWVfYncoKQ0KDQoNCg0KZ2dwbG90KGRhdGE9aGlzdGFnbzIwMDUsIGFlcyh4PUhJU1RfQUdPMjAwNSwgeT1GcmVxdcOqbmNpYSkpICsNCiAgZ2VvbV9iYXIoc3RhdD0iaWRlbnRpdHkiLCBjb2xvcj0iYmx1ZSIsIGZpbGw9IndoaXRlIikrDQogIGdlb21fdGV4dChhZXMobGFiZWw9RnJlcXXDqm5jaWEpLCB2anVzdD0tMC4zLCBjb2xvcj0iYmxhY2siLCBzaXplPTMuNSkrDQogIGxhYnMoeD0iXG5ISVNUX0FHTzIwMDUiLHk9IkZyZXF1w6puY2lhXG4iLCB0aXRsZT0iRnJlcXXDqm5jaWFzIGFic29sdXRhcyAtIEhJU1RfQUdPMjAwNVxuIikrDQogIHRoZW1lX2J3KCkNCg0KZ2dwbG90KGRhdGE9aGlzdGp1bDIwMDUsIGFlcyh4PUhJU1RfSlVMMjAwNSwgeT1GcmVxdcOqbmNpYSkpICsNCiAgZ2VvbV9iYXIoc3RhdD0iaWRlbnRpdHkiLCBjb2xvcj0iYmx1ZSIsIGZpbGw9IndoaXRlIikrDQogIGdlb21fdGV4dChhZXMobGFiZWw9RnJlcXXDqm5jaWEpLCB2anVzdD0tMC4zLCBjb2xvcj0iYmxhY2siLCBzaXplPTMuNSkrDQogIGxhYnMoeD0iXG5ISVNUX0pVTDIwMDUiLHk9IkZyZXF1w6puY2lhXG4iLCB0aXRsZT0iRnJlcXXDqm5jaWFzIGFic29sdXRhcyAtIEhJU1RfSlVMMjAwNVxuIikrDQogIHRoZW1lX2J3KCkNCg0KZ2dwbG90KGRhdGE9aGlzdGp1bjIwMDUsIGFlcyh4PUhJU1RfSlVOMjAwNSwgeT1GcmVxdcOqbmNpYSkpICsNCiAgZ2VvbV9iYXIoc3RhdD0iaWRlbnRpdHkiLCBjb2xvcj0iYmx1ZSIsIGZpbGw9IndoaXRlIikrDQogIGdlb21fdGV4dChhZXMobGFiZWw9RnJlcXXDqm5jaWEpLCB2anVzdD0tMC4zLCBjb2xvcj0iYmxhY2siLCBzaXplPTMuNSkrDQogIGxhYnMoeD0iXG5ISVNUX0pVTjIwMDUiLHk9IkZyZXF1w6puY2lhXG4iLCB0aXRsZT0iRnJlcXXDqm5jaWFzIGFic29sdXRhcyAtIEhJU1RfSlVOMjAwNVxuIikrDQogIHRoZW1lX2J3KCkNCg0KDQpnZ3Bsb3QoZGF0YT1oaXN0bWFpMjAwNSwgYWVzKHg9SElTVF9NQUkyMDA1LCB5PUZyZXF1w6puY2lhKSkgKw0KICBnZW9tX2JhcihzdGF0PSJpZGVudGl0eSIsIGNvbG9yPSJibHVlIiwgZmlsbD0id2hpdGUiKSsNCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbD1GcmVxdcOqbmNpYSksIHZqdXN0PS0wLjMsIGNvbG9yPSJibGFjayIsIHNpemU9My41KSsNCiAgbGFicyh4PSJcbkhJU1RfTUFJMjAwNSIseT0iRnJlcXXDqm5jaWFcbiIsIHRpdGxlPSJGcmVxdcOqbmNpYXMgYWJzb2x1dGFzIC0gSElTVF9NQUkyMDA1XG4iKSsNCiAgdGhlbWVfYncoKQ0KDQpnZ3Bsb3QoZGF0YT1oaXN0YWJyMjAwNSwgYWVzKHg9SElTVF9BQlIyMDA1LCB5PUZyZXF1w6puY2lhKSkgKw0KICBnZW9tX2JhcihzdGF0PSJpZGVudGl0eSIsIGNvbG9yPSJibHVlIiwgZmlsbD0id2hpdGUiKSsNCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbD1GcmVxdcOqbmNpYSksIHZqdXN0PS0wLjMsIGNvbG9yPSJibGFjayIsIHNpemU9My41KSsNCiAgbGFicyh4PSJcbkhJU1RfQUJSMjAwNSIseT0iRnJlcXXDqm5jaWFcbiIsIHRpdGxlPSJGcmVxdcOqbmNpYXMgYWJzb2x1dGFzIC0gSElTVF9BQlIyMDA1XG4iKSsNCiAgdGhlbWVfYncoKQ0KICANCmBgYA0KLSBQb2RlbW9zIG9ic2VydmFyIHF1ZSwgcGFyYSBvcyBtZXNlcyBlbnRyZSBhYnJpbCBlIHNldGVtYnJvIGRlIDIwMDUsIG8gaGlzdMOzcmljbyBkZSBwYWdhbWVudG9zIGFwcmVzZW50YSBlbSBtYWlvciBmcmVxdcOqbmNpYSBvIHVzbyBkZSBjcmVkaXRvIHJvdGF0aXZvICgwKS4gDQoNCg0KIyMgQ29lZmljaWVudGVzIGRlIGFzc2ltZXRyaWEgDQoNCkEgVGFiZWxhIDkgY29udMOpbSBvcyBjb2VmaWNpZW50ZXMgZGUgYXNzaW1ldHJpYSBwYXJhIGFzIHZhcmnDoXZlaXMgcXVhbnRpdGF0aXZhcyBkbyBkYXRhc2V0Lg0KDQpBIGFzc2ltZXRyaWEgbmVnYXRpdmEgaW5kaWNhIHF1ZSBhIG3DqWRpYSBkb3MgZGFkb3Mgw6kgbWVub3IgcXVlIGEgbWVkaWFuYSBlLCBwb3J0YW50bywgcXVlIGEgZGlzdHJpYnVpw6fDo28gZG9zIGRhZG9zIMOpIGFzc2ltw6l0cmljYSDDoCBlc3F1ZXJkYS4gSsOhIGEgYXNzaW1ldHJpYSBwb3NpdGl2YSBpbmRpY2EgcXVlIGEgbcOpZGlhIGRvcyBkYWRvcyDDqSBtYWlvciBxdWUgYSBtZWRpYW5hIGUsIHBvcnRhbnRvLCBxdWUgYSBkaXN0cmlidWnDp8OjbyBkb3MgZGFkb3Mgw6kgYXNzaW3DqXRyaWNhIMOgIGRpcmVpdGEuIA0KDQoNCi0tLQ0KDQo8ZGl2IGFsaWduPSJjZW50ZXIiPg0KICAgYHIgdGFibGVfbnVtcygndGFiXzknKWANCjwvZGl2Pg0KDQotLS0NCg0KDQpgYGB7ciB9DQprbml0cjo6a2FibGUoYXBwbHkoY3JlZGl0WyxjKDIsNiwxMzoyNCldLDIsIGZ1bmN0aW9uKHgpIHJvdW5kKHNrZXduZXNzKHgpLDIpKSxjb2wubmFtZXMgPSAiQ29lZmljaWVudGUgZGUgYXNzaW1ldHJpYSIpJT4lIGthYmxlX3N0eWxpbmcocG9zaXRpb24gPSAiY2VudGVyIikNCmBgYA0KDQoNCkRlIGFjb3JkbyBjb20gYSBUYWJlbGEgOSwgdGVtb3MgcXVlIHRvZGFzIGFzIHZhcmnDoXZlaXMgcXVhbnRpdGF0aXZhcyBzw6NvIGFzc2ltw6l0cmljYXMgw6AgZGlyZWl0YS4NCg0KDQoNCiMjIENvcnJlbG9ncmFtYQ0KDQpPIGNvcnJlbG9ncmFtYSBhYmFpeG8gbW9zdHJhIG9zIGNvZWZpY2llbnRlcyBkZSBjb3JyZWxhw6fDo28gZGUgUGVhcnNvbiBwYXJhIHRvZGFzIGFzIHZhcmnDoXZlaXMgcXVhbnRpdGF0aXZhcyBkbyBkYXRhc2V0Lg0KDQoNCmBgYHtyIH0NCmNvcnIgPC0gY29yKGNyZWRpdFssYygyLDYsMTM6MjQpXSkgI01hdHJpeiBkZSBjb3JyZWxhw6fDo28NCmdnY29ycnBsb3QoY29ycixvdXRsaW5lLmNvbG9yID0gIndoaXRlIix0eXBlID0gImxvd2VyIixsYWIgPSBUUlVFLGRpZ2l0cyA9IDIsIGxhYl9zaXplID0gMykNCmBgYA0KDQotIFRlbW9zIHF1ZSBhcyBtYWlvcmVzIGNvcnJlbGHDp8O1ZXMgb2NvcnJlcmFtIGVudHJlIG9zIHZhbG9yZXMgZG9zIGV4dHJhdG9zIGRlIGFicmlsIGEgc2V0ZW1icm8gZGUgMjAwNSwgY29tbyBkZXN0YWNhZG8gcGVsYSDDoXJlYSBlbSB2ZXJtZWxoby4gDQoNCg0KDQoNCiMgQW7DoWxpc2UgZGUgQWdydXBhbWVudG8gKENsdXN0ZXJpbmcpDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQoNCnRhYi4xMF9jYXAgPC0gdGFibGVfbnVtcyhuYW1lID0gInRhYl8xMCIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgIGNhcHRpb24gPSAiSW50ZXJ2YWxvcyBkZSBjbGFzc2lmaWNhw6fDo28gZGEgc2lsaHVldGEgbcOpZGlhIChLYXVmbWFuIGUgUm91c3NlZXV3LCAxOTg5KSIpDQoNCnRhYi4xMV9jYXAgPC0gdGFibGVfbnVtcyhuYW1lID0gInRhYl8xMSIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgIGNhcHRpb24gPSAiRXhwZXJpbWVudG8gMSAtIE1lZG9pZHMgZGEgc29sdcOnw6NvIGNvbSBrPTMiKQ0KDQoNCnRhYi4xMl9jYXAgPC0gdGFibGVfbnVtcyhuYW1lID0gInRhYl8xMiIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgIGNhcHRpb24gPSAiRXhwZXJpbWVudG8gMSAtIE1hdHJpeiBkZSBjb25mdXPDo28gcGFyYSBhIHNvbHXDp8OjbyBjb20gaz0yIikNCg0KdGFiLjEzX2NhcCA8LSB0YWJsZV9udW1zKG5hbWUgPSAidGFiXzEzIiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgY2FwdGlvbiA9ICJFeHBlcmltZW50byAyIC0gTWVkb2lkcyBkYSBzb2x1w6fDo28gY29tIGs9NCIpDQoNCg0KdGFiLjE0X2NhcCA8LSB0YWJsZV9udW1zKG5hbWUgPSAidGFiXzEzIiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgY2FwdGlvbiA9ICJFeHBlcmltZW50byAyIC0gTWF0cml6IGRlIGNvbmZ1c8OjbyBwYXJhIGEgc29sdcOnw6NvIGNvbSBrPTIiKQ0KDQp0YWIuMTVfY2FwIDwtIHRhYmxlX251bXMobmFtZSA9ICJ0YWJfMTUiLCANCiAgICAgICAgICAgICAgICAgICAgICAgICBjYXB0aW9uID0gIiBFeHBlcmltZW50byAzIC0gTWVkb2lkcyBkYSBzb2x1w6fDo28gY29tIGs9MiIpDQoNCg0KdGFiLjE2X2NhcCA8LSB0YWJsZV9udW1zKG5hbWUgPSAidGFiXzE2IiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgY2FwdGlvbiA9ICJFeHBlcmltZW50byAzIC0gTWF0cml6IGRlIGNvbmZ1c8OjbyBwYXJhIGEgc29sdcOnw6NvIGNvbSBrPTIiKQ0KDQoNCnRhYi4xN19jYXAgPC0gdGFibGVfbnVtcyhuYW1lID0gInRhYl8xNyIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgIGNhcHRpb24gPSAiIEV4cGVyaW1lbnRvIDQgLSBNZWRvaWRzIGRhIHNvbHXDp8OjbyBjb20gaz0zIikNCg0KdGFiLjE4X2NhcCA8LSB0YWJsZV9udW1zKG5hbWUgPSAidGFiXzE4IiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgY2FwdGlvbiA9ICIgRXhwZXJpbWVudG8gNCAtIFBlcmNlbnR1YWwgZGUgYWNlcnRvIHBhcmEgYSBzb2x1w6fDo28gY29tIGs9MiIpDQoNCg0KYGBgDQoNCg0KRGVwb2lzIGRhIGFuw6FsaXNlIGRlc2NyaXRpdmEsIGRlbW9zIGluw61jaW8gYW8gYWdydXBhbWVudG8sIG91IGNsdXN0ZXJpbmcsIGRvcyBkYWRvcy4NCg0KVHLDqnMgcXVlc3TDtWVzIGLDoXNpY2FzIGRldmVtIHNlciBhYm9yZGFkYXMgZW0gdW0gcHJvYmxlbWEgZGUgYWdydXBhbWVudG8gKEhhaXIgZSBCYWJpbiwgMjAxOCk6DQoNCioqKDEpIENvbW8gbWVkaXIgYSBzaW1pbGFyaWRhZGU/KioNCg0KbyBwcmltZWlybyBwYXNzbyBkYSB0YXJlZmEgZGUgYWdydXBhbWVudG8gY29uc2lzdGUgbmEgZXNjb2xoYSBkZSBjb21vIGEgc2ltaWxhcmlkYWRlL2Rpc3NpbWlsYXJpZGFkZSBlbnRyZSBhcyBvYnNlcnZhw6fDtWVzIHNlcsOhIG1lZGlkYS4gQSBkaXNzaW1pbGFyaWRhZGUgbmFkYSBtYWlzIMOpIHF1ZSBhIGRpc3TDom5jaWEgZW50cmUgYXMgb2JzZXJ2YcOnw7Vlcy4NCg0KKiooMikgQ29tbyBmb3JtYXIgb3MgYWdydXBhbWVudG9zPyoqDQoNCk8gc2VndW5kbyBwYXNzbyBjb25zaXN0ZSBuYSBlc2NvbGhhIGRvIGFsZ29yaXRtbyBkZSBhZ3J1cGFtZW50bywgcXVlIGRhcsOhIG9yaWdlbSBhb3MgZ3J1cG9zIGRlIGFjb3JkbyBjb20gYXMgZGlzc2ltaWxhcmlkYWRlcyBvYnRpZGFzIG5vIHBhc3NvIDEuDQoNCioqKDMpIFF1YW50b3MgZ3J1cG9zIGZvcm1hcj8qKg0KDQpVbWEgdmV6IG9idGlkbyB1bSBhZ3J1cGFtZW50byAoc29sdcOnw6NvKSwgw6kgZGUgc3VtYSBpbXBvcnTDom5jaWEgYXZhbGnDoS0NCmxvIGUgdmVyaWZpY2FyIHNlLCBkZSBmYXRvLCByZXByZXNlbnRhIGJlbSBhIGVzdHJ1dHVyYSBkb3MgZGFkb3MuIA0KRXNzZSB0aXBvIGRlIGF2YWxpYcOnw6NvIHBvZGUgc2VyIGZlaXRhIG1lZGlhbnRlIGEgYXBsaWNhw6fDo28gZGUgw61uZGljZXMgZGUgdmFsaWRhw6fDo28uDQpMb2dvLCBvIHRlcmNlaXJvIGUgw7psdGltbyBwYXNzbyBjb25zaXN0ZSBlbQ0KZSBhdmFsaWFyIGUgY29tcGFyYXIgYXMgc29sdcOnw7VlcyBwcm9kdXppZGFzIG5vIHNlZ3VuZG8uIA0KTmVzc2UgdHJhYmFsaG8gdXNhcmVtb3MgbyDDrW5kaWNlDQpkZSBzaWxodWV0YSwgbWFpcyBlc3BlY2lmaWNhbWVudGUsIGEgc2lsaHVldGEgbcOpZGlhIGNvbW8gdW0gaW5kaWNhZG9yIGRlIHF1YWxpZGFkZSBkbyBhZ3J1cGFtZW50by4gDQoNCg0KDQotLS0NCg0KPGRpdiBhbGlnbj0iY2VudGVyIj4NCiAgIGByIHRhYmxlX251bXMoJ3RhYl8xMCcpYA0KPC9kaXY+DQoNCi0tLQ0KYGBge3J9DQpzaWxfdGFiZWxhIDwtIGRhdGEuZnJhbWUoYT1jKCIwLjcxLTEiLCIwLjUxIOKIkiAwLjcwIiwiMC4yNiDiiJIgMC41MCIsIuKJpCAwLjI1IiksDQogICAgICAgICAgICAgICAgICAgICAgICAgZD1jKCJFc3RydXR1cmEgZm9ydGUgZW5jb250cmFkYSBub3MgZGFkb3MiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiRXN0cnV0dXJhIHJhem/DoXZlbCBlbmNvbnRyYWRhIG5vcyBkYWRvcyIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiRXN0cnV0dXJhIGZyYWNhLCBwb3NzaXZlbG1lbnRlIGFydGlmaWNpYWw7IEF2YWxpYXIgYSBhcGxpY2HDp8OjbyBkZSBvdXRyb3MgYWxnb3JpdG1vcyBub3MgZGFkb3MiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIk7Do28gZm9pIGVuY29udHJhZGEgZXN0cnV0dXJhIHN1YnN0YW5jaWFsIG5vcyBkYWRvcyIpKQ0KDQoNCmNvbG5hbWVzKHNpbF90YWJlbGEpIDwtIGMoIlZhbG9yIGRhIHNpbGh1ZXRhIG3DqWRpYSIsIkRlc2NyacOnw6NvIikNCmtuaXRyOjprYWJsZShzaWxfdGFiZWxhKSU+JSBrYWJsZV9zdHlsaW5nKHBvc2l0aW9uID0gImNlbnRlciIpDQoNCmBgYA0KDQoNCiMjIERpc3TDom5jaWEgZGUgR293ZXIgKyBQQU0gDQoNCg0KQXMgZGlzdMOibmNpYXMgRXVjbGlkaWFuYSBlIE1hbmhhdHRhbiBzw6NvIGNvbXVtZW50ZSB1dGlsaXphZGFzLCBwb3LDqW0sIGFtYmFzIHPDo28gYXBsaWPDoXZlaXMgc29tZW50ZSBwYXJhIGRhZG9zIG51bcOpcmljb3MgZSwgY29tbyBqw6Egdmltb3MsIG8gZGF0YXNldCAqY3JlZGl0KiBwb3NzdWkgdmFyacOhdmVpcyBjYXRlZ8OzcmljYXMgZSBudW3DqXJpY2FzLiANCg0KTGV2YW5kbyBlbSBjb25zaWRlcmHDp8OjbyBlc3NhIHBhcnRpY3VsYXJpZGFkZSBkbyBkYXRhc2V0LCBwcmltZWlyYW1lbnRlIHV0aWxpemFtb3MgYSAqKmRpc3TDom5jaWEgZGUgR293ZXIqKiwgdW1hIG3DqXRyaWNhIHF1ZSBwb2RlIHNlciB1c2FkYSBwYXJhIGNhbGN1bGFyIGEgZGlzdMOibmNpYSBlbnRyZSBkdWFzIG9ic2VydmHDp8O1ZXMgY3Vqb3MgYXRyaWJ1dG9zL3ZhcmnDoXZlaXMgc8OjbyB1bSBtaXN0byBkZSB2YWxvcmVzIGNhdGVnw7NyaWNvcyBlIHF1YW50aXRhdGl2b3MuIA0KDQpBIGRpc3TDom5jaWEgZGUgR293ZXIgZm9pIGVtcHJlZ2FkYSBhdHJhdsOpcyBkYSBmdW7Dp8OjbyAqZGFpc3kqIGRvIHBhY290ZSAqY2x1c3RlciouIENvbSBhIG1hdHJpeiBkZSBkaXN0w6JuY2lhcyBvYnRpZGEsIG8gYWxnb3JpdG1vIGRlIGFncnVwYW1lbnRvICoqUEFNKiogZm9pIGFwbGljYWRvIHZhcmlhbmRvIG8gbsO6bWVybyBkZSBncnVwb3MgKiprIGVudHJlIDIgZSA1KiouIEEgcXVhbGlkYWRlIGRvcyBhZ3J1cGFtZW50b3MgZm9pIHZlcmlmaWNhZGEgYXRyYXbDqXMgZGEgc2lsaHVldGEgbcOpZGlhIGRhcyBzb2x1w6fDtWVzLiANCg0KRm9yYW0gcmVhbGl6YWRvcyBkb2lzIGV4cGVyaW1lbnRvcyBkaWZlcmVudGVzIGNvbnNpZGVyYW5kbyBhIGNvbWJpbmHDp8OjbyBkYSBkaXN0w6JuY2lhIGRlIEdvd2VyIGUgZG8gYWxnb3JpdG1vIFBBTTogDQoNCjEuICoqRVhQRVJJTUVOVE8gMToqKiBBIGRpc3TDom5jaWEgZm9pIGNhbGN1bGFkYSAqKnBhcmEgdG9kYXMgYXMgdmFyacOhdmVpcyBkbyBkYXRhc2V0KiosIGNvbSBleGNlw6fDo28gZGUgKklEKiBlICpQQUdfUEFEUkFPKjsNCjIuICoqRVhQRVJJTUVOVE8gMjoqKiBBIGRpc3TDom5jaWEgZm9pIGNhbGN1bGFkYSB1c2FuZG8gKip0b2RhcyBhcyB2YXJpw6F2ZWlzIG51bcOpcmljYXMqKiBlIGFzIHZhcmnDoXZlaXMgY2F0ZWfDs3JpY2FzICoqSElTVF9TRVQyMDA1IGEgSElTVF9BQlIyMDA1KiosIG1hbnRlbmRvIGEgZXhjbHVzw6NvIGRhcyB2YXJpw6F2ZWlzICpJRCogZSAqUEFHX1BBRFJBTyouDQogDQpPIHJlc3VsdGFkbyBkbyAqKkVYUEVSSU1FTlRPIDEqKiBwb2RlIHNlciB2aXN1YWxpemFkbyBubyBncsOhZmljbyBhYmFpeG86DQoNCmBgYHtyfQ0KDQojIGNyZWRpdCRISVNUX1NFVDIwMDU8LSBhcy5mYWN0b3IoY3JlZGl0JEhJU1RfU0VUMjAwNSkNCiMgY3JlZGl0JEhJU1RfQUdPMjAwNTwtIGFzLmZhY3RvcihjcmVkaXQkSElTVF9BR08yMDA1KQ0KIyBjcmVkaXQkSElTVF9KVUwyMDA1PC0gYXMuZmFjdG9yKGNyZWRpdCRISVNUX0pVTDIwMDUpDQojIGNyZWRpdCRISVNUX0pVTjIwMDU8LSBhcy5mYWN0b3IoY3JlZGl0JEhJU1RfSlVOMjAwNSkNCiMgY3JlZGl0JEhJU1RfTUFJMjAwNTwtIGFzLmZhY3RvcihjcmVkaXQkSElTVF9NQUkyMDA1KQ0KIyBjcmVkaXQkSElTVF9BQlIyMDA1PC0gYXMuZmFjdG9yKGNyZWRpdCRISVNUX0FCUjIwMDUpDQojIGNyZWRpdCRTRVhPPC1hcy5mYWN0b3IoY3JlZGl0JFNFWE8pDQojIGNyZWRpdCRFU0NPTEFSSURBREU8LWFzLmZhY3RvcihjcmVkaXQkRVNDT0xBUklEQURFKQ0KIyBjcmVkaXQkRVNUQURPLkNJVklMPC1hcy5mYWN0b3IoY3JlZGl0JEVTVEFETy5DSVZJTCkNCg0KIy0tLS0tLS0tLS0tLS0tLS0tLS0tIENvbSB0b2RhcyBhcyB2YXJpw6F2ZWlzIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMgZ293ZXJfZGYgPC0gZGFpc3koY3JlZGl0WywtYygxLDI1KV0sDQojICAgICAgICAgICAgICAgICAgIG1ldHJpYyA9ICJnb3dlciIgKQ0KDQoNCiMgc2F2ZVJEUyhnb3dlcl9kZiwgZmlsZSA9ICJDOlxcVXNlcnNcXFZpY3RvcmlhIFZhcmdhc1xcRG93bmxvYWRzXFxBVElWSURBREUgM1xcY3JlZGl0X2dvd2VyZGZfY29tcGxldGUucmRzIikNCg0KDQojIHJtKGxpc3QgPSBscygpKQ0KIyBnYygpDQojIC5ycy5yZXN0YXJ0UigpDQojIA0KIyBsaWJyYXJ5KGNsdXN0ZXIpDQojIGxpYnJhcnkoZGF0YS50YWJsZSkNCiMgDQojIGdvd2VyX2RmPC1yZWFkUkRTKCJDOlxcVXNlcnNcXFZpY3RvcmlhIFZhcmdhc1xcRG93bmxvYWRzXFxBVElWSURBREUgM1xcY3JlZGl0X2dvd2VyZGZfY29tcGxldGUucmRzIiwgcmVmaG9vayA9IE5VTEwpDQojIA0KDQoNCiMgaT0yDQojIHBhbV9jbHVzdGVycyA8LSBwYW0oZ293ZXJfZGYsZGlzcyA9IFRSVUUsIGsgPSBpKQ0KIyBzYXZlKHBhbV9jbHVzdGVycyxmaWxlPSJDOlxcVXNlcnNcXFZpY3RvcmlhIFZhcmdhc1xcRG93bmxvYWRzXFxBVElWSURBREUgM1xcZG9pc2dydXBvc19nb3dlcl9jb21wbGV0ZS5SZGF0YSIpDQojIGRvaXNfZ3J1cG9zIDwtIGRhdGEuZnJhbWUoYyhhcy52ZWN0b3IocGFtX2NsdXN0ZXJzJHNpbGluZm8kYXZnLndpZHRoKSxhcy52ZWN0b3IocGFtX2NsdXN0ZXJzJGNsdXN0ZXJpbmcpKSkNCiMgZndyaXRlKGRvaXNfZ3J1cG9zLCBmaWxlID0gIkM6XFxVc2Vyc1xcVmljdG9yaWEgVmFyZ2FzXFxEb3dubG9hZHNcXEFUSVZJREFERSAzXFxkb2lzZ3J1cG9zX2dvd2VyX2NvbXBsZXRlLmNzdiIpDQoNCiMgcm0obGlzdD1jKCJwYW1fY2x1c3RlcnMiLCJkb2lzX2dydXBvcyIpKQ0KIyBnYygpDQoNCg0KIyBpPTMNCiMgcGFtX2NsdXN0ZXJzIDwtIHBhbShnb3dlcl9kZixkaXNzID0gVFJVRSwgayA9IGkpDQojIHNhdmUocGFtX2NsdXN0ZXJzLGZpbGU9IkM6XFxVc2Vyc1xcVmljdG9yaWEgVmFyZ2FzXFxEb3dubG9hZHNcXEFUSVZJREFERSAzXFx0cmVzZ3J1cG9zX2dvd2VyX2NvbXBsZXRlLlJkYXRhIikNCiMgdHJlc19ncnVwb3MgPC1kYXRhLmZyYW1lKGMoYXMudmVjdG9yKHBhbV9jbHVzdGVycyRzaWxpbmZvJGF2Zy53aWR0aCksYXMudmVjdG9yKHBhbV9jbHVzdGVycyRjbHVzdGVyaW5nKSkpDQojIGZ3cml0ZSh0cmVzX2dydXBvcywgZmlsZSA9ICJDOlxcVXNlcnNcXFZpY3RvcmlhIFZhcmdhc1xcRG93bmxvYWRzXFxBVElWSURBREUgM1xcdHJlc2dydXBvc19nb3dlcl9jb21wbGV0ZS5jc3YiKQ0KDQoNCiMgcm0obGlzdD1jKCJwYW1fY2x1c3RlcnMiLCJ0cmVzX2dydXBvcyIpKQ0KIyBnYygpDQoNCg0KIyBpPTQNCiMgcGFtX2NsdXN0ZXJzIDwtIHBhbShnb3dlcl9kZixkaXNzID0gVFJVRSwgayA9IGkpDQojIHNhdmUocGFtX2NsdXN0ZXJzLGZpbGU9IkM6XFxVc2Vyc1xcVmljdG9yaWEgVmFyZ2FzXFxEb3dubG9hZHNcXEFUSVZJREFERSAzXFxxdWF0cm9ncnVwb3NfZ293ZXJfY29tcGxldGUuUmRhdGEiKQ0KIyBxdWF0cm9fZ3J1cG9zIDwtIGRhdGEuZnJhbWUoYyhhcy52ZWN0b3IocGFtX2NsdXN0ZXJzJHNpbGluZm8kYXZnLndpZHRoKSxhcy52ZWN0b3IocGFtX2NsdXN0ZXJzJGNsdXN0ZXJpbmcpKSkNCiMgZndyaXRlKHF1YXRyb19ncnVwb3MsIGZpbGUgPSAiQzpcXFVzZXJzXFxWaWN0b3JpYSBWYXJnYXNcXERvd25sb2Fkc1xcQVRJVklEQURFIDNcXHF1YXRyb2dydXBvc19nb3dlcl9jb21wbGV0ZS5jc3YiKQ0KIyANCiMgcm0obGlzdD1jKCJwYW1fY2x1c3RlcnMiLCJxdWF0cm9fZ3J1cG9zIikpDQojIGdjKCkNCg0KIyBpPTUNCiMgcGFtX2NsdXN0ZXJzIDwtIHBhbShnb3dlcl9kZixkaXNzID0gVFJVRSwgayA9IGkpDQojIHNhdmUocGFtX2NsdXN0ZXJzLGZpbGU9IkM6XFxVc2Vyc1xcVmljdG9yaWEgVmFyZ2FzXFxEb3dubG9hZHNcXEFUSVZJREFERSAzXFxjaW5jb2dydXBvc19nb3dlcl9jb21wbGV0ZS5SZGF0YSIpDQojIGNpbmNvX2dydXBvcyA8LSBkYXRhLmZyYW1lKGMoYXMudmVjdG9yKHBhbV9jbHVzdGVycyRzaWxpbmZvJGF2Zy53aWR0aCksYXMudmVjdG9yKHBhbV9jbHVzdGVycyRjbHVzdGVyaW5nKSkpDQojIGZ3cml0ZShjaW5jb19ncnVwb3MsIGZpbGUgPSAiQzpcXFVzZXJzXFxWaWN0b3JpYSBWYXJnYXNcXERvd25sb2Fkc1xcQVRJVklEQURFIDNcXGNpbmNvZ3J1cG9zX2dvd2VyX2NvbXBsZXRlLmNzdiIpDQoNCiMgcm0obGlzdCA9IGxzKCkpDQojIGdjKCkNCiMgLnJzLnJlc3RhcnRSKCkNCg0KIy0tLS0tLS0tLS0tLS0tLS0tLS0tLSBTZW0gc2V4bywgZXNjb2xhcmlkYWRlIGUgZXN0YWRvIGNpdmlsIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KDQojIGxpYnJhcnkoY2x1c3RlcikNCiMgbGlicmFyeShkYXRhLnRhYmxlKQ0KDQojIGNyZWRpdDwtIHJlYWQuY3N2KCJDOlxcVXNlcnNcXFZpY3RvcmlhIFZhcmdhc1xcRG93bmxvYWRzXFxBVElWSURBREUgM1xcY3JlZGl0X2NsZWFuLmNzdiIpDQojIGNyZWRpdCRISVNUX1NFVDIwMDU8LSBhcy5mYWN0b3IoY3JlZGl0JEhJU1RfU0VUMjAwNSkNCiMgY3JlZGl0JEhJU1RfQUdPMjAwNTwtIGFzLmZhY3RvcihjcmVkaXQkSElTVF9BR08yMDA1KQ0KIyBjcmVkaXQkSElTVF9KVUwyMDA1PC0gYXMuZmFjdG9yKGNyZWRpdCRISVNUX0pVTDIwMDUpDQojIGNyZWRpdCRISVNUX0pVTjIwMDU8LSBhcy5mYWN0b3IoY3JlZGl0JEhJU1RfSlVOMjAwNSkNCiMgY3JlZGl0JEhJU1RfTUFJMjAwNTwtIGFzLmZhY3RvcihjcmVkaXQkSElTVF9NQUkyMDA1KQ0KIyBjcmVkaXQkSElTVF9BQlIyMDA1PC0gYXMuZmFjdG9yKGNyZWRpdCRISVNUX0FCUjIwMDUpDQoNCg0KDQojIGdvd2VyX2RmIDwtIGRhaXN5KGNyZWRpdFssLWMoMSwzOjUsMjUpXSwNCiMgICAgICAgICAgICAgICAgICAgICBtZXRyaWMgPSAiZ293ZXIiICkNCg0KDQojIHNhdmVSRFMoZ293ZXJfZGYsIGZpbGUgPSAiQzpcXFVzZXJzXFxWaWN0b3JpYSBWYXJnYXNcXERvd25sb2Fkc1xcQVRJVklEQURFIDNcXGNyZWRpdF9nb3dlcmRmX25vdGNvbXBsZXRlLnJkcyIpDQoNCiMgcm0obGlzdCA9IGxzKCkpDQojIGdjKCkNCiMgLnJzLnJlc3RhcnRSKCkNCg0KIyBsaWJyYXJ5KGNsdXN0ZXIpDQojIGxpYnJhcnkoZGF0YS50YWJsZSkNCg0KIyBnb3dlcl9kZjwtcmVhZFJEUygiQzpcXFVzZXJzXFxWaWN0b3JpYSBWYXJnYXNcXERvd25sb2Fkc1xcQVRJVklEQURFIDNcXGNyZWRpdF9nb3dlcmRmX25vdGNvbXBsZXRlLnJkcyIsIHJlZmhvb2sgPSBOVUxMKQ0KDQoNCg0KIyBpPTINCiMgcGFtX2NsdXN0ZXJzIDwtIHBhbShnb3dlcl9kZixkaXNzID0gVFJVRSwgayA9IGkpDQojIHNhdmUocGFtX2NsdXN0ZXJzLGZpbGU9IkM6XFxVc2Vyc1xcVmljdG9yaWEgVmFyZ2FzXFxEb3dubG9hZHNcXEFUSVZJREFERSAzXFxkb2lzZ3J1cG9zX2dvd2VyX25vdGNvbXBsZXRlLlJkYXRhIikNCiMgZG9pc19ncnVwb3MgPC0gZGF0YS5mcmFtZShjKGFzLnZlY3RvcihwYW1fY2x1c3RlcnMkc2lsaW5mbyRhdmcud2lkdGgpLGFzLnZlY3RvcihwYW1fY2x1c3RlcnMkY2x1c3RlcmluZykpKQ0KIyBmd3JpdGUoZG9pc19ncnVwb3MsIGZpbGUgPSAiQzpcXFVzZXJzXFxWaWN0b3JpYSBWYXJnYXNcXERvd25sb2Fkc1xcQVRJVklEQURFIDNcXGRvaXNncnVwb3NfZ293ZXJfbm90Y29tcGxldGUuY3N2IikNCg0KIyBybShsaXN0PWMoInBhbV9jbHVzdGVycyIsImRvaXNfZ3J1cG9zIikpDQojIGdjKCkNCg0KDQojIGk9Mw0KIyBwYW1fY2x1c3RlcnMgPC0gcGFtKGdvd2VyX2RmLGRpc3MgPSBUUlVFLCBrID0gaSkNCiMgc2F2ZShwYW1fY2x1c3RlcnMsZmlsZT0iQzpcXFVzZXJzXFxWaWN0b3JpYSBWYXJnYXNcXERvd25sb2Fkc1xcQVRJVklEQURFIDNcXHRyZXNncnVwb3NfZ293ZXJfbm90Y29tcGxldGUuUmRhdGEiKQ0KIyB0cmVzX2dydXBvcyA8LWRhdGEuZnJhbWUoYyhhcy52ZWN0b3IocGFtX2NsdXN0ZXJzJHNpbGluZm8kYXZnLndpZHRoKSxhcy52ZWN0b3IocGFtX2NsdXN0ZXJzJGNsdXN0ZXJpbmcpKSkNCiMgZndyaXRlKHRyZXNfZ3J1cG9zLCBmaWxlID0gIkM6XFxVc2Vyc1xcVmljdG9yaWEgVmFyZ2FzXFxEb3dubG9hZHNcXEFUSVZJREFERSAzXFx0cmVzZ3J1cG9zX2dvd2VyX25vdGNvbXBsZXRlLmNzdiIpDQoNCg0KIyBybShsaXN0PWMoInBhbV9jbHVzdGVycyIsInRyZXNfZ3J1cG9zIikpDQojIGdjKCkNCg0KDQojIGk9NA0KIyBwYW1fY2x1c3RlcnMgPC0gcGFtKGdvd2VyX2RmLGRpc3MgPSBUUlVFLCBrID0gaSkNCiMgc2F2ZShwYW1fY2x1c3RlcnMsZmlsZT0iQzpcXFVzZXJzXFxWaWN0b3JpYSBWYXJnYXNcXERvd25sb2Fkc1xcQVRJVklEQURFIDNcXHF1YXRyb2dydXBvc19nb3dlcl9ub3Rjb21wbGV0ZS5SZGF0YSIpDQojIHF1YXRyb19ncnVwb3MgPC0gZGF0YS5mcmFtZShjKGFzLnZlY3RvcihwYW1fY2x1c3RlcnMkc2lsaW5mbyRhdmcud2lkdGgpLGFzLnZlY3RvcihwYW1fY2x1c3RlcnMkY2x1c3RlcmluZykpKQ0KIyBmd3JpdGUocXVhdHJvX2dydXBvcywgZmlsZSA9ICJDOlxcVXNlcnNcXFZpY3RvcmlhIFZhcmdhc1xcRG93bmxvYWRzXFxBVElWSURBREUgM1xccXVhdHJvZ3J1cG9zX2dvd2VyX25vdGNvbXBsZXRlLmNzdiIpDQoNCg0KIyBybShsaXN0PWMoInBhbV9jbHVzdGVycyIsInF1YXRyb19ncnVwb3MiKSkNCiMgZ2MoKQ0KDQojIGk9NQ0KIyBwYW1fY2x1c3RlcnMgPC0gcGFtKGdvd2VyX2RmLGRpc3MgPSBUUlVFLCBrID0gaSkNCiMgc2F2ZShwYW1fY2x1c3RlcnMsZmlsZT0iQzpcXFVzZXJzXFxWaWN0b3JpYSBWYXJnYXNcXERvd25sb2Fkc1xcQVRJVklEQURFIDNcXGNpbmNvZ3J1cG9zX2dvd2VyX25vdGNvbXBsZXRlLlJkYXRhIikNCiMgY2luY29fZ3J1cG9zIDwtIGRhdGEuZnJhbWUoYyhhcy52ZWN0b3IocGFtX2NsdXN0ZXJzJHNpbGluZm8kYXZnLndpZHRoKSxhcy52ZWN0b3IocGFtX2NsdXN0ZXJzJGNsdXN0ZXJpbmcpKSkNCiMgZndyaXRlKGNpbmNvX2dydXBvcywgZmlsZSA9ICJDOlxcVXNlcnNcXFZpY3RvcmlhIFZhcmdhc1xcRG93bmxvYWRzXFxBVElWSURBREUgM1xcY2luY29ncnVwb3NfZ293ZXJfbm90Y29tcGxldGUuY3N2IikNCg0KIyBybShsaXN0ID0gbHMoKSkNCiMgZ2MoKQ0KIyAucnMucmVzdGFydFIoKQ0KDQoNCmRvaXNfZ3J1cG9zPC0gcmVhZC5jc3YoIkM6XFxVc2Vyc1xcVmljdG9yaWEgVmFyZ2FzXFxEb3dubG9hZHNcXEFUSVZJREFERSAzXFxkb2lzZ3J1cG9zX2dvd2VyX2NvbXBsZXRlLmNzdiIpDQp0cmVzX2dydXBvcyA8LSByZWFkLmNzdigiQzpcXFVzZXJzXFxWaWN0b3JpYSBWYXJnYXNcXERvd25sb2Fkc1xcQVRJVklEQURFIDNcXHRyZXNncnVwb3NfZ293ZXJfY29tcGxldGUuY3N2IikNCnF1YXRyb19ncnVwb3MgPC0gcmVhZC5jc3YoIkM6XFxVc2Vyc1xcVmljdG9yaWEgVmFyZ2FzXFxEb3dubG9hZHNcXEFUSVZJREFERSAzXFxxdWF0cm9ncnVwb3NfZ293ZXJfY29tcGxldGUuY3N2IikNCmNpbmNvX2dydXBvcyA8LSByZWFkLmNzdigiQzpcXFVzZXJzXFxWaWN0b3JpYSBWYXJnYXNcXERvd25sb2Fkc1xcQVRJVklEQURFIDNcXGNpbmNvZ3J1cG9zX2dvd2VyX2NvbXBsZXRlLmNzdiIpDQoNCg0Kc2lsaHVldGEgPC0gY2JpbmQuZGF0YS5mcmFtZSAoZ3J1cG9zPWFzLmZhY3RvcigxOjUpLHNpbGh1ZXRhPWMoMCwgZG9pc19ncnVwb3NbMSxdLHRyZXNfZ3J1cG9zWzEsXSxxdWF0cm9fZ3J1cG9zWzEsXSxjaW5jb19ncnVwb3NbMSxdKSkNCg0KDQpnZ3Bsb3QoZGF0YT1zaWxodWV0YSwgYWVzKHg9Z3J1cG9zLCB5PXNpbGh1ZXRhLCBncm91cD0xLCBsYWJlbD1hcy5jaGFyYWN0ZXIocm91bmQoc2lsaHVldGEsMikpKSkgKw0KICBnZW9tX2xpbmUoKSsNCiAgZ2VvbV9wb2ludCgpKw0KICBnZW9tX3RleHQoaGp1c3Q9MCwgdmp1c3Q9LTEuNSkrDQogIGxhYnMoeD0iXG5HcnVwb3MiLHk9IlZhbG9yIGRhIHNpbGh1ZXRhIG3DqWRpYVxuIiwgdGl0bGU9IkV4cGVyaW1lbnRvIDEgLSBWYWxvciBkYSBzaWxodWV0YSBtw6lkaWEgZGUgYWNvcmRvIGNvbSBvIG7Dum1lcm8gZGUgZ3J1cG9zIGtcbiIpKw0KICBzY2FsZV95X2NvbnRpbnVvdXMoYnJlYWtzID0gc2VxKDAsMC42LGJ5PTAuMSksIGxpbWl0cz0gYygwLDAuNikpKw0KICB0aGVtZV9jbGFzc2ljKCkNCg0KYGBgDQoNCkRlIGFjb3JkbyBjb20gb3MgdmFsb3JlcyBkYSBzaWxodWV0YSBtw6lkaWEsIG5vIEV4cGVyaW1lbnRvIDEgYSBtZWxob3Igc29sdcOnw6NvIHV0aWxpemFuZG8gYSBkaXN0w6JuY2lhIGRlIEdvd2VyIGVtIGNvbmp1bnRvIGFvIGFsZ29yaXRtbyBQQU0gw6kgcGFyYSAkaz0zJCBncnVwb3MuDQoNCg0KQ29tIGEgc29sdcOnw6NvIGRvIFBBTSBwYXJhICRrPTMkIGdydXBvcywgdGVudGFtb3MgaW50ZXJwcmV0YXIgbyBjb21wb3J0YW1lbnRvIGRlIGNhZGEgY2x1c3RlciBjb20gYSBhanVkYSBkb3MgbWVkb2lkcyBkYSBzb2x1w6fDo28uICANCg0KLS0tDQoNCjxkaXYgYWxpZ249ImNlbnRlciI+DQogICBgciB0YWJsZV9udW1zKCd0YWJfMTEnKWANCjwvZGl2Pg0KDQotLS0NCmBgYHtyfQ0KDQpsb2FkKCJDOlxcVXNlcnNcXFZpY3RvcmlhIFZhcmdhc1xcRG93bmxvYWRzXFxBVElWSURBREUgM1xcdHJlc2dydXBvc19nb3dlcl9jb21wbGV0ZS5SZGF0YSIpDQoNCmtuaXRyOjprYWJsZShjcmVkaXRbcGFtX2NsdXN0ZXJzJG1lZG9pZHMsIF0pJT4lIGthYmxlX3N0eWxpbmcocG9zaXRpb24gPSAiY2VudGVyIikNCg0KDQpgYGANCg0KDQoNCi0gTmEgdGFiZWxhIGFjaW1hLCBjYWRhIGxpbmhhIGNvbnTDqW0gYSBvYnNlcnZhw6fDo28gZGUgKmNyZWRpdCogZXNjb2xoaWRhIGNvbW8gKm1lZG9pZCogbmEgc29sdcOnw6NvIMOzdGltYSAoJGs9MyQpLg0KDQotIEEgcGFydGlyIGRlc3RhIHRhYmVsYSwgcG9kZW1vcyBpbmZlcmlyIHF1ZSBvcyBjbGllbnRlcyBwZXJ0ZW5jZW50ZXMNCmFvIENsdXN0ZXIgMSAobGluaGEgMSkgdMOqbSBhcyBzZWd1aW50ZXMgY2FyYWN0ZXLDrXN0aWNhcyByZXByZXNlbnRhdGl2YXMgKGRlIGFjb3JkbyBjb20gbyBtZWRvaWQpOg0KbyBsaW1pdGUgZG8gY3LDqWRpdG8gY29uY2VkaWRvIMOpIGRlIE5UXCQxNzBrLA0Kc8OjbyBkbyBzZXhvIGZlbWluaW5vLCBjb20gcMOzcyBncmFkdWHDp8OjbyBjb21wbGV0YSwgY2FzYWRhcywgaWRhZGUgbcOpZGlhIGRlIDM2IGFub3MsIA0KY29tIG8gaGlzdMOzcmljbyBkZSBwYWdhbWVudG9zIGRlIGFicmlsIGEgc2V0ZW1icm8gZGUgMjAxNSBwYWdvIGludGVncmFsbWVudGUsIA0KY29tIG8gdmFsb3IgZG8gZXh0cmF0byBkYSBjb250YSBkZXNzZSBtZXNtbyBwZXLDrW9kbyB2YXJpYW5kbyBlbnRyZSAxMjE4IGEgMjY1NiBOb3ZvIA0KZMOzbGFyIHRhaXdhbsOqcywgbyB2YWxvciBkb3MgcGFnYW1lbnRvcyBkbyBwZXLDrW9kbyBhYm9yZGFkbyBlbnRyZSAgTlRcJDE2MjYgZSBOVFwkMjY1NiwgZSBwb3NzdWVtIA0KY29tcG9ydGFtZW50byBpcnJlZ3VsYXIgZGUgZ2FzdG9zIGUgcGFnYW1lbnRvcy4NCg0KLSBPYnNlcnZlIHF1ZSBuZW0gdG9kb3Mgb3MgY2xpZW50ZXMgc2Vyw6NvIGV4YXRhbWVudGUgYXNzaW07IG9zIG1lZG9pZGVzIHPDo28gYXBlbmFzIHVtYSByZXByZXNlbnRhbnRlcyBkbyBjbHVzdGVyIGFib3JkYWRvLg0KDQotIEludGVycHJldGHDp8O1ZXMgc2ltaWxhcmVzIHBvZGVtIHNlciBmZWl0YXMgcGFyYSBvcyBvdXRyb3MgY2x1c3RlcnMuIFBvZGVtb3Mgbm90YXIgcXVlIG8NCnNlZ3VuZG8gY2x1c3RlciBhcGFyZW50YSBzZXIgZGUgcGVzc29hcyBlbSBxdWUgbyBleHRyYXRvIMOpIGJlbSBtYWlvciBxdWUgb3MgcGFnYW1lbnRvcyBkb3MNCm1lc21vcyBtZXNlcywgY29tIGxpbWl0ZSBkZSBOVFwkODBrLCBjb20gZW5zaW5vIG3DqWRpbyBpbmZlcmlvciBhb3Mgb3V0cm9zIGNsdXN0ZXJzICh1bml2ZXJzaWRhZGUpLCANCnVzYW5kbyBjcsOpZGl0byByb3RhdGl2byBlIGNvbSBjb21wb3J0YW1lbnRvIGlycmVndWxhci4gRW5xdWFudG8gbyB0ZXJjZWlybywNCnBvc3N1aSB1bSBjb21wb3J0YW1lbnRvIHJlZ3VsYXIsIHVtIGxpbWl0ZSBtw6lkaW8gZGUgTlRcJDIwMGsgZSANCm7Do28gYXByZXNlbnRhbSBjb25zdW1vcyBub3MgbWVzZXMgZXN0dWRhZG9zLCBuZW0gZXh0cmF0b3MgZSBuZW0gcGFnYW1lbnRvcy4NCg0KDQoNClBhcmEgYSBzb2x1w6fDo28gY29tICRrPTIkIGdydXBvcywgY29tcGFyYW1vcyBvIGFncnVwYW1lbnRvIG9idGlkbyBubyBFeHBlcmltZW50byAxIGNvbSBhIGNvbHVuYSAqKlBBR19QQURSQU8qKiBwcmVzZW50ZSBubyBkYXRhc2V0ICpjcmVkaXQqLCBvYnRlbmRvIG9zIHNlZ3VpbnRlcyByZXN1bHRhZG9zOiANCg0KLS0tDQoNCjxkaXYgYWxpZ249ImNlbnRlciI+DQogICBgciB0YWJsZV9udW1zKCd0YWJfMTInKWANCjwvZGl2Pg0KDQotLS0NCg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCg0KYXV4IDwtIGRvaXNfZ3J1cG9zDQphdXg8LSBhcy5jaGFyYWN0ZXIoYXV4Wy0xLF0pDQojdGFibGUoYXV4KQ0KIyAyIGFwYXJlY2UgZW0gbWFpb3IgcXVhbnRpZGFkZSwgbG9nbywgY29ycmVzcG9uZGUgYW8gZ3J1cG8gMCBkYSBiYXNlIG9yaWdpbmFsDQphdXggW3doaWNoKGF1eD09IjIiKV0gPC0gIjAiDQoNCg0KIyBjb3JyZXNwb25kZW5jaWEgPC0gZGF0YS5mcmFtZShQQUdfUEFEUkFPID0gc3VtKGFzLmNoYXJhY3RlcihjcmVkaXQkUEFHX1BBRFJBTykgPT0gYXV4KS8zMDAwMCkNCiMgDQojIGNvcnJlc3BvbmRlbmNpYSA8LSByb3VuZChjb3JyZXNwb25kZW5jaWEqMTAwLDIpDQojIGNvbG5hbWVzKGNvcnJlc3BvbmRlbmNpYSkgPC0gYygiIEV4cDEgLSBBY2VydG9zICglKSIpDQojIGtuaXRyOjprYWJsZShjb3JyZXNwb25kZW5jaWEpJT4lIGthYmxlX3N0eWxpbmcocG9zaXRpb24gPSAiY2VudGVyIikNCg0KDQoNCiNJbXBvcnQgcmVxdWlyZWQgbGlicmFyeQ0KbGlicmFyeShjYXJldCkNCg0KI0NyZWF0ZXMgdmVjdG9ycyBoYXZpbmcgZGF0YSBwb2ludHMNCmV4cGVjdGVkX3ZhbHVlIDwtIGZhY3RvcihhdXgpDQpwcmVkaWN0ZWRfdmFsdWUgPC0gZmFjdG9yKGNyZWRpdCRQQUdfUEFEUkFPKQ0KDQojQ3JlYXRpbmcgY29uZnVzaW9uIG1hdHJpeA0KZXhhbXBsZSA8LSBjb25mdXNpb25NYXRyaXgoZGF0YT1wcmVkaWN0ZWRfdmFsdWUsIHJlZmVyZW5jZSA9IGV4cGVjdGVkX3ZhbHVlKQ0KDQojRGlzcGxheSByZXN1bHRzIA0Ka25pdHI6OmthYmxlKGV4YW1wbGUkdGFibGUpJT4lIGthYmxlX3N0eWxpbmcocG9zaXRpb24gPSAiY2VudGVyIikNCg0KDQoNCg0KYGBgDQoNCi0gTmEgc29sdcOnw6NvIGRlIGFncnVwYW1lbnRvIG9idGlkYSwgb3Mgb2JqZXRvcyBmb3JhbSBhbG9jYWRvcyBhb3MgY2x1c3RlcnMgMSBlIDIuIFBvciBwb3NzdWlyIG1haXMgb2JzZXJ2YcOnw7VlcywNCnRhbCBxdWFsIG8gZ3J1cG8gMCBkYSBiYXNlIG9yaWdpbmFsLCB0cmFuc2Zvcm1vdS1zZSBvICIyIiBlbSAiMCIuDQotIEFvIGNvbXBhcmFybW9zIGEgdmFyacOhdmVsICoqUEFHX1BBRFJBTyoqIGNvbSBhIHNvbHXDp8OjbyBkbyBhZ3J1cGFtZW50byBwYXJhICRrPTIkIGRvIEV4cGVyaW1lbnRvIDEsIHRlbW9zIHF1ZSBhIGFjdXLDoWNpYSBmb2kgNTguOCUuDQotIERlIGFjb3JkbyBjb20gYSB0YWJlbGEsIG8gYWdydXBhbWVudG8gb2J0aWRvIGFsb2NvdSB1bSBvYmpldG8gcGVydGVuY2VudGUgYW8gZ3J1cG8gMCBvcmlnaW5hbG1lbnRlIGFvIGdydXBvIDAgZG8gYWdydXBhbWVudG8gZW0gMTU2MzYgY2Fzb3MgZSBhbG9jb3UgdW0gb2JqZXRvIGRvIGdydXBvIDAgYW8gZ3J1cG8gMSBlbSA0NjMxIGNhc29zLiANCi0gIE8gYWdydXBhbWVudG8gb2J0aWRvIGFsb2NvdSB1bSBvYmpldG8gcGVydGVuY2VudGUgYW8gZ3J1cG8gMSBvcmlnaW5hbG1lbnRlIGFvIGdydXBvIDAgZG8gYWdydXBhbWVudG8gZW0gNzcyOCBjYXNvcywgZSBhbG9jb3UgdW0gb2JqZXRvIGRvIGdydXBvIDEgYW8gZ3J1cG8gMSBkbyBhZ3J1cGFtZW50byBlbSAyMDA1IGNhc29zLg0KDQpRdWFudG8gYW8gKipFWFBFUklNRU5UTyAyKiosIHNldXMgcmVzdWx0YWRvcyBwb2RlbSBzZXIgdmlzdWFsaXphZG9zIG5vIGdyw6FmaWNvIGFiYWl4bzoNCg0KYGBge3J9DQpkb2lzX2dydXBvczwtIHJlYWQuY3N2KCJDOlxcVXNlcnNcXFZpY3RvcmlhIFZhcmdhc1xcRG93bmxvYWRzXFxBVElWSURBREUgM1xcZG9pc2dydXBvc19nb3dlcl9ub3Rjb21wbGV0ZS5jc3YiKQ0KdHJlc19ncnVwb3MgPC0gcmVhZC5jc3YoIkM6XFxVc2Vyc1xcVmljdG9yaWEgVmFyZ2FzXFxEb3dubG9hZHNcXEFUSVZJREFERSAzXFx0cmVzZ3J1cG9zX2dvd2VyX25vdGNvbXBsZXRlLmNzdiIpDQpxdWF0cm9fZ3J1cG9zIDwtIHJlYWQuY3N2KCJDOlxcVXNlcnNcXFZpY3RvcmlhIFZhcmdhc1xcRG93bmxvYWRzXFxBVElWSURBREUgM1xccXVhdHJvZ3J1cG9zX2dvd2VyX25vdGNvbXBsZXRlLmNzdiIpDQpjaW5jb19ncnVwb3MgPC0gcmVhZC5jc3YoIkM6XFxVc2Vyc1xcVmljdG9yaWEgVmFyZ2FzXFxEb3dubG9hZHNcXEFUSVZJREFERSAzXFxjaW5jb2dydXBvc19nb3dlcl9ub3Rjb21wbGV0ZS5jc3YiKQ0KDQoNCnNpbGh1ZXRhIDwtIGNiaW5kLmRhdGEuZnJhbWUgKGdydXBvcz1hcy5mYWN0b3IoMTo1KSxzaWxodWV0YT1jKDAsIGRvaXNfZ3J1cG9zWzEsXSx0cmVzX2dydXBvc1sxLF0scXVhdHJvX2dydXBvc1sxLF0sY2luY29fZ3J1cG9zWzEsXSkpDQoNCg0KZ2dwbG90KGRhdGE9c2lsaHVldGEsIGFlcyh4PWdydXBvcywgeT1zaWxodWV0YSwgZ3JvdXA9MSwgbGFiZWw9YXMuY2hhcmFjdGVyKHJvdW5kKHNpbGh1ZXRhLDIpKSkpICsNCiAgZ2VvbV9saW5lKCkrDQogIGdlb21fcG9pbnQoKSsNCiAgZ2VvbV90ZXh0KGhqdXN0PTAsIHZqdXN0PS0xLjUpKw0KICBsYWJzKHg9IlxuR3J1cG9zIix5PSJWYWxvciBkYSBzaWxodWV0YSBtw6lkaWFcbiIsIHRpdGxlPSJFeHBlcmltZW50byAyIC0gVmFsb3IgZGEgc2lsaHVldGEgbcOpZGlhIGRlIGFjb3JkbyBjb20gbyBuw7ptZXJvIGRlIGdydXBvcyBrXG4iKSsNCiAgc2NhbGVfeV9jb250aW51b3VzKGJyZWFrcyA9IHNlcSgwLDAuNixieT0wLjEpLCBsaW1pdHM9IGMoMCwwLjYpKSsNCiAgdGhlbWVfY2xhc3NpYygpDQogIA0KICANCg0KYGBgDQoNCg0KRGUgYWNvcmRvIGNvbSBvcyB2YWxvcmVzIGRhIHNpbGh1ZXRhIG3DqWRpYSwgbm8gRXhwZXJpbWVudG8gMiBhIG1lbGhvciBzb2x1w6fDo28gdXRpbGl6YW5kbyBhIGRpc3TDom5jaWEgZGUgR293ZXIgZW0gY29uanVudG8gYW8gYWxnb3JpdG1vIFBBTSDDqSBwYXJhICRrPTQkIGdydXBvcy4NCg0KQ29tIGEgc29sdcOnw6NvIGRvIFBBTSBwYXJhICRrPTQkIGdydXBvcywgdGVudGFtb3MgaW50ZXJwcmV0YXIgbyBjb21wb3J0YW1lbnRvIGRlIGNhZGEgY2x1c3RlciBjb20gYSBhanVkYSBkb3MgbWVkb2lkcyBkYSBzb2x1w6fDo28uIA0KDQoNCi0tLQ0KDQo8ZGl2IGFsaWduPSJjZW50ZXIiPg0KICAgYHIgdGFibGVfbnVtcygndGFiXzEzJylgDQo8L2Rpdj4NCg0KLS0tDQpgYGB7cn0NCmxvYWQoIkM6XFxVc2Vyc1xcVmljdG9yaWEgVmFyZ2FzXFxEb3dubG9hZHNcXEFUSVZJREFERSAzXFxxdWF0cm9ncnVwb3NfZ293ZXJfbm90Y29tcGxldGUuUmRhdGEiKQ0KDQprbml0cjo6a2FibGUoY3JlZGl0W3BhbV9jbHVzdGVycyRtZWRvaWRzLCBdKSU+JSBrYWJsZV9zdHlsaW5nKHBvc2l0aW9uID0gImNlbnRlciIpDQoNCmBgYA0KDQotIEFzIGNhcmFjdGVyw61zdGljYXMgZG9zIGNsaWVudGVzIGRvIGdydXBvL2NsdXN0ZXIgMSBzw6NvOiBsaW1pdGUgZG8gY3LDqWRpdG8gY29uY2VkaWRvIGRlIE5UXCQ2MGssIHNleG8gZmVtaW5pbm8sIGVzY29sYXJpZGFkZSBncmFkdWHDp8OjbyBjb21wbGV0YSwgY2FzYWRvcywgaWRhZGUgbcOpZGlhIGRlIDMyIGFub3MsDQphdHJhc28gbcOpZGlvIGRlIDIgbWVzZXMgZW0gdG9kb3Mgb3MgcGFnYW1lbnRvcyBkb3MgbWVzZXMgYWJvcmRhZG9zLCBleHRyYXRvcyBlbnRyZSBOVFwkMjBrIGUgTlRcJDI0aywNCnBhZ2FtZW50b3MgZW50cmUgTlRcJDFrIGUgTlRcJDEuN2sgY29tIGV4Y2XDp8OjbyBkZSBhYnJpbCwgcXVlIGFwcmVzZW50YSB2YWxvciAwIGUgY29tcG9ydGFtZW50byByZWd1bGFyLg0KDQotIE5vIHNlZ3VuZG8gZ3J1cG8vY2x1c3RlciwgYXMgcHJpbmNpcGFpcyBjYXJhY3RlcsOtc3RpY2FzIHPDo286IGxpbWl0ZSBkZSAxMDBrLCBzZXhvIG1hc2N1bGlubywgY2FzYWRvcywgY29tDQppZGFkZSBtw6lkaWEgZGUgMzMgYW5vcywgcMOzcyBncmFkdWHDp8OjbyBjb21wbGV0YSwgdXNhbmRvIGNyw6lkaXRvIHJvdGF0aXZvIGVtIHRvZG9zIG9zIG1lc2VzIGFib3JkYWRvcywNCmV4dHJhdG9zIHZhcmlhbmRvIGRlIE5UXCQ0MGsgYSBOVFwkNTFrLCBwYWdhbWVudG9zIGVudHJlIE5UXCQxLjRrIGUgTlRcJDIuM2sgZSBjb21wb3J0YW1lbnRvIGlycmVndWxhci4NCg0KLSBPIHRlcmNlaXJvIGNsdXN0ZXIgdMOqbSBjb21vIGNhcmFjdGVyw61zdGljYXM6IHZhbG9yIGxpbWl0ZSBkbyBjcsOpZGl0byBjb25jZWRpZG8gZGUgTlRcJDIxMGssIG11bGhlcmVzIGNhc2FkYXMsDQpncmFkdWHDp8OjbyBjb21wbGV0YSwgY29tIDM1IGFub3MsIHBhZ2FtZW50b3MgcGFnb3MgaW50ZWdyYWxtZW50ZSxjb20gZXh0cmF0b3MgZSBwYWdhbWVudG9zIGVudHJlIE5UXCQ5NTYgZSANCk5UXCQyNDU0IGUgY29tcG9ydGFtZW50byBpcnJlZ3VsYXIuDQoNCi0gTyBxdWFydG8gY2x1c3RlciBwb3NzdWkgY2FyYWN0ZXLDrXN0aWNhcyBzaW1pbGFyZXMgYW8gdGVyY2Vpcm8gY2x1c3RlciBkbyBleHBlcmltZW50byAxLCBjb20gZXhjZcOnw6NvDQpkbyBwZXJmaWwgZGVtb2dyw6FmaWNvOiBvIHNleG8gw6kgbWFzY3VsaW5vLCBlc3RhZG8gY2l2aWwgY2FzYWRvIGUgaWRhZGUgMzYgYW5vcy4NCg0KDQoNCk5vdmFtZW50ZSBmb2kgZmVpdGEgYSBjb21wYXJhw6fDo28gZW50cmUgYSBzb2x1w6fDo28gY29tICRrPTIkIGdydXBvcyBlIGEgY29sdW5hICoqUEFHX1BBRFJBTyoqIHByZXNlbnRlIG5vIGRhdGFzZXQgKmNyZWRpdCosIG9idGVuZG8gYSBzZWd1aW50ZSBtYXRyaXogZGUgY29uZnVzw6NvOiANCg0KLS0tDQoNCjxkaXYgYWxpZ249ImNlbnRlciI+DQogICBgciB0YWJsZV9udW1zKCd0YWJfMTQnKWANCjwvZGl2Pg0KDQotLS0NCg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCg0KYXV4IDwtIGRvaXNfZ3J1cG9zDQphdXg8LSBhcy5jaGFyYWN0ZXIoYXV4Wy0xLF0pDQojIHRhYmxlKGF1eCkNCiMgMiBhcGFyZWNlIGVtIG1haW9yIHF1YW50aWRhZGUsIGxvZ28sIGNvcnJlc3BvbmRlIGFvIGdydXBvIDAgZGEgYmFzZSBvcmlnaW5hbA0KYXV4IFt3aGljaChhdXg9PSIyIildIDwtICIwIg0KDQoNCiMgY29ycmVzcG9uZGVuY2lhIDwtIGRhdGEuZnJhbWUoUEFHX1BBRFJBTyA9IHN1bShhcy5jaGFyYWN0ZXIoY3JlZGl0JFBBR19QQURSQU8pID09IGF1eCkvMzAwMDApDQojIA0KIyBjb3JyZXNwb25kZW5jaWEgPC0gcm91bmQoY29ycmVzcG9uZGVuY2lhKjEwMCwyKQ0KIyBjb2xuYW1lcyhjb3JyZXNwb25kZW5jaWEpIDwtIGMoIiBFeHAxIC0gQWNlcnRvcyAoJSkiKQ0KIyBrbml0cjo6a2FibGUoY29ycmVzcG9uZGVuY2lhKSU+JSBrYWJsZV9zdHlsaW5nKHBvc2l0aW9uID0gImNlbnRlciIpDQoNCg0KDQojQ3JlYXRlcyB2ZWN0b3JzIGhhdmluZyBkYXRhIHBvaW50cw0KZXhwZWN0ZWRfdmFsdWUgPC0gZmFjdG9yKGF1eCkNCnByZWRpY3RlZF92YWx1ZSA8LSBmYWN0b3IoY3JlZGl0JFBBR19QQURSQU8pDQoNCiNDcmVhdGluZyBjb25mdXNpb24gbWF0cml4DQpleGFtcGxlIDwtIGNvbmZ1c2lvbk1hdHJpeChkYXRhPXByZWRpY3RlZF92YWx1ZSwgcmVmZXJlbmNlID0gZXhwZWN0ZWRfdmFsdWUpDQoNCiNEaXNwbGF5IHJlc3VsdHMgDQprbml0cjo6a2FibGUoZXhhbXBsZSR0YWJsZSklPiUga2FibGVfc3R5bGluZyhwb3NpdGlvbiA9ICJjZW50ZXIiKQ0KDQoNCg0KDQpgYGANCg0KLSBOYSBzb2x1w6fDo28gZGUgYWdydXBhbWVudG8gb2J0aWRhLCBvcyBvYmpldG9zIGZvcmFtIGFsb2NhZG9zIGFvcyBjbHVzdGVycyAxIGUgMi4gUG9yIHBvc3N1aXIgbWFpcyBvYnNlcnZhw6fDtWVzLA0KdGFsIHF1YWwgbyBncnVwbyAwIGRhIGJhc2Ugb3JpZ2luYWwsIHRyYW5zZm9ybW91LXNlIG8gIjIiIGVtICIwIi4NCi0gQW8gY29tcGFyYXJtb3MgYSB2YXJpw6F2ZWwgKipQQUdfUEFEUkFPKiogY29tIGEgc29sdcOnw6NvIGRvIGFncnVwYW1lbnRvIHBhcmEgJGs9MiQgZG8gRXhwZXJpbWVudG8gMiwgdGVtb3MgcXVlIGEgYWN1csOhY2lhICBmb2kgZGUgNTcuMDYlLg0KLSBEZSBhY29yZG8gY29tIGEgdGFiZWxhLCBvIGFncnVwYW1lbnRvIG9idGlkbyBhbG9jb3UgdW0gb2JqZXRvIHBlcnRlbmNlbnRlIGFvIGdydXBvIDAgb3JpZ2luYWxtZW50ZSBhbyBncnVwbyAwIGRvIGFncnVwYW1lbnRvIGVtIDE0OTk4IGNhc29zIGUgYWxvY291IHVtIG9iamV0byBkbyBncnVwbyAwIGFvIGdydXBvIDEgZW0gNDUxNyBjYXNvcy4gDQotICBPIGFncnVwYW1lbnRvIG9idGlkbyBhbG9jb3UgdW0gb2JqZXRvIHBlcnRlbmNlbnRlIGFvIGdydXBvIDEgb3JpZ2luYWxtZW50ZSBhbyBncnVwbyAwIGRvIGFncnVwYW1lbnRvIGVtIDgzNjYgY2Fzb3MsIGUgYWxvY291IHVtIG9iamV0byBkbyBncnVwbyAxIGFvIGdydXBvIDEgZG8gYWdydXBhbWVudG8gZW0gMjExOSBjYXNvcy4NCg0KDQoNCiMjIERpc3TDom5jaWEgRXVjbGlkaWFuYSArIFBBTSANCg0KQ29tbyBvcyByZXN1bHRhZG9zIG9idGlkb3MgdXRpbGl6YW5kbyBhIGRpc3TDom5jaWEgZGUgR293ZXIgZW0gY29uanVudG8gYW8gYWxnb3JpdG1vIFBBTSBuw6NvIGZvcmFtIHNhdGlzZmF0w7NyaW9zLCBpc3RvIMOpLCBhIGFuw6FsaXNlIGRlIHF1YWxpZGFkZSBzZWd1bmRvIGEgc2lsaHVldGEgbcOpZGlhIG7Do28gaW5kaWNvdSBvIG7Dum1lcm8gImNvcnJldG8iIGRlIGdydXBvcywgb3B0YW1vcyBwb3IgcmVhbGl6YXIgdW0gdGVyY2Vpcm8gZXhwZXJpbWVudG8sIGRlc3NhIHZleiB1c2FuZG8gYSAqKmRpc3TDom5jaWEgRXVjbGlkaWFuYSoqLiANCg0KQ29tZW50YW1vcyBhbnRlcmlvcm1lbnRlIHF1ZSBhIGRpc3TDom5jaWEgZXVjbGlkaWFuYSBkZXZlIHNlciB1c2FkYSBzb21lbnRlIGNvbSB2YXJpw6F2ZWlzIHF1YW50aXRhdGl2YXMsIHBvcnRhbnRvLCB1dGlsaXphbW9zIG5lc3RlIGV4cGVyaW1lbnRvIHNvbWVudGUgYXMgdmFyacOhdmVpcyBxdWFudGl0YXRpdmFzIGRvIGRhdGFzZXQ6IA0KDQotICpMSU1JVEUqDQotICpJREFERSoNCi0gKkVYVF9TRVQyMDA1KiANCi0gKkVYVF9BR08yMDA1KiANCi0gKkVYVF9KVUwyMDA1KiANCi0gKkVYVF9KVU4yMDA1Kg0KLSAqRVhUX01BSTIwMDUqICANCi0gKkVYVF9BQlIyMDA1KiANCi0gKlBBR19TRVQyMDA1KiAgDQotICpQQUdfQUdPMjAwNSogDQotICpQQUdfSlVMMjAwNSogDQotICpQQUdfSlVOMjAwNSogIA0KLSAqUEFHX01BSTIwMDUqIA0KLSAqUEFHX0FCUjIwMDUqDQoNClByZXZpYW1lbnRlIGFvIGPDoWxjdWxvIGRhcyBkaXN0w6JuY2lhcywgZXNzYXMgdmFyacOhdmVpcyBmb3JhbSBwYWRyb25pemFkYXMgY29tIG8gYXV4w61saW8gZGEgZnVuw6fDo28gKnNjYWxlKi4NCg0KQSBtYXRyaXogZGUgZGlzdMOibmNpYSBmb2kgb2J0aWRhIHBlbGEgZnVuw6fDo28gKmRpc3RhbmNlcyogZG8gcGFjb3RlICpkaXN0YW5jZXMqLiANCg0KQ29tIGEgbWF0cml6IGRlIGRpc3TDom5jaWFzIGV1Y2xpZGlhbmEgZW0gbcOjb3MsIG8gRXhwZXJpbWVudG8gMyBjb25zaXN0aXUgZW0gYXBsaWNhciBvIGFsZ29yaXRtbyBkZSBhZ3J1cGFtZW50byAqKlBBTSoqIHZhcmlhbmRvIG8gbsO6bWVybyBkZSBncnVwb3MgKiprIGVudHJlIDIgZSA1KiouIEEgcXVhbGlkYWRlIGRvcyBhZ3J1cGFtZW50b3MgZm9pIHZlcmlmaWNhZGEgYXRyYXbDqXMgZGEgc2lsaHVldGEgbcOpZGlhIGRhcyBzb2x1w6fDtWVzLiANCg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCiMgbGlicmFyeShkaXN0YW5jZXMpDQojIGNyZWRpdF9wYWRyb25pemFkbyA8LSBjcmVkaXRbLGMoMiw2LDEzOjI0KV0NCiMgY3JlZGl0X3BhZHJvbml6YWRvPC0gZGF0YS5mcmFtZShhcHBseShjcmVkaXRfcGFkcm9uaXphZG8sMixzY2FsZSkpDQojIGV1Y2xpZGVhbl9kZiA8LSBkaXN0YW5jZXMoY3JlZGl0X3BhZHJvbml6YWRvKQ0KIyANCiMgDQojIGk9Mg0KIyBwYW1fY2x1c3RlcnMgPC0gcGFtKGV1Y2xpZGVhbl9kZixkaXNzID0gVFJVRSwgayA9IGkpDQojIHNhdmUocGFtX2NsdXN0ZXJzLGZpbGU9IkM6XFxVc2Vyc1xcVmljdG9yaWEgVmFyZ2FzXFxEb3dubG9hZHNcXEFUSVZJREFERSAzXFxkb2lzZ3J1cG9zX2V1Y2xpZGVhbi5SZGF0YSIpDQojIGRvaXNfZ3J1cG9zIDwtIGRhdGEuZnJhbWUoYyhhcy52ZWN0b3IocGFtX2NsdXN0ZXJzJHNpbGluZm8kYXZnLndpZHRoKSxhcy52ZWN0b3IocGFtX2NsdXN0ZXJzJGNsdXN0ZXJpbmcpKSkNCiMgZndyaXRlKGRvaXNfZ3J1cG9zLCBmaWxlID0gIkM6XFxVc2Vyc1xcSm9zaFxcRGVza3RvcFxcVklDVMOTUklBIFZBUkdBU1xcZG9pc2dydXBvc19ldWNsaWRlYW4uY3N2IikNCg0KDQojIGk9Mw0KIyBwYW1fY2x1c3RlcnMgPC0gcGFtKGV1Y2xpZGVhbl9kZixkaXNzID0gVFJVRSwgayA9IGkpDQojIHNhdmUocGFtX2NsdXN0ZXJzLGZpbGU9IkM6XFxVc2Vyc1xcSm9zaFxcRGVza3RvcFxcVklDVMOTUklBIFZBUkdBU1xcdHJlc2dydXBvc19ldWNsaWRlYW4uUmRhdGEiKQ0KIyB0cmVzX2dydXBvcyA8LWRhdGEuZnJhbWUoYyhhcy52ZWN0b3IocGFtX2NsdXN0ZXJzJHNpbGluZm8kYXZnLndpZHRoKSxhcy52ZWN0b3IocGFtX2NsdXN0ZXJzJGNsdXN0ZXJpbmcpKSkNCiMgZndyaXRlKHRyZXNfZ3J1cG9zLCBmaWxlID0gIkM6XFxVc2Vyc1xcSm9zaFxcRGVza3RvcFxcVklDVMOTUklBIFZBUkdBU1xcdHJlc2dydXBvc19ldWNsaWRlYW4uY3N2IikNCiMgDQoNCg0KIyBpPTQNCiMgcGFtX2NsdXN0ZXJzIDwtIHBhbShldWNsaWRlYW5fZGYsZGlzcyA9IFRSVUUsIGsgPSBpKQ0KIyBzYXZlKHBhbV9jbHVzdGVycyxmaWxlPSJDOlxcVXNlcnNcXEpvc2hcXERlc2t0b3BcXFZJQ1TDk1JJQSBWQVJHQVNcXHF1YXRyb2dydXBvc19ldWNsaWRlYW4uUmRhdGEiKQ0KIyBxdWF0cm9fZ3J1cG9zIDwtIGRhdGEuZnJhbWUoYyhhcy52ZWN0b3IocGFtX2NsdXN0ZXJzJHNpbGluZm8kYXZnLndpZHRoKSxhcy52ZWN0b3IocGFtX2NsdXN0ZXJzJGNsdXN0ZXJpbmcpKSkNCiMgZndyaXRlKHF1YXRyb19ncnVwb3MsIGZpbGUgPSAiQzpcXFVzZXJzXFxKb3NoXFxEZXNrdG9wXFxWSUNUw5NSSUEgVkFSR0FTXFxxdWF0cm9ncnVwb3NfZXVjbGlkZWFuLmNzdiIpDQoNCg0KIyBpPTUNCiMgcGFtX2NsdXN0ZXJzIDwtIHBhbShldWNsaWRlYW5fZGYsZGlzcyA9IFRSVUUsIGsgPSBpKQ0KIyBzYXZlKHBhbV9jbHVzdGVycyxmaWxlPSJDOlxcVXNlcnNcXEpvc2hcXERlc2t0b3BcXFZJQ1TDk1JJQSBWQVJHQVNcXGNpbmNvZ3J1cG9zX2V1Y2xpZGVhbi5SZGF0YSIpDQojIGNpbmNvX2dydXBvcyA8LSBkYXRhLmZyYW1lKGMoYXMudmVjdG9yKHBhbV9jbHVzdGVycyRzaWxpbmZvJGF2Zy53aWR0aCksYXMudmVjdG9yKHBhbV9jbHVzdGVycyRjbHVzdGVyaW5nKSkpDQojIGZ3cml0ZShjaW5jb19ncnVwb3MsIGZpbGUgPSAiQzpcXFVzZXJzXFxKb3NoXFxEZXNrdG9wXFxWSUNUw5NSSUEgVkFSR0FTXFxjaW5jb2dydXBvc19ldWNsaWRlYW4uY3N2IikNCg0KDQoNCg0KZG9pc19ncnVwb3M8LSByZWFkLmNzdigiQzpcXFVzZXJzXFxWaWN0b3JpYSBWYXJnYXNcXERvd25sb2Fkc1xcQVRJVklEQURFIDNcXGRvaXNncnVwb3NfZXVjbGlkZWFuLmNzdiIpDQp0cmVzX2dydXBvcyA8LSByZWFkLmNzdigiQzpcXFVzZXJzXFxWaWN0b3JpYSBWYXJnYXNcXERvd25sb2Fkc1xcQVRJVklEQURFIDNcXHRyZXNncnVwb3NfZXVjbGlkZWFuLmNzdiIpDQpxdWF0cm9fZ3J1cG9zIDwtIHJlYWQuY3N2KCJDOlxcVXNlcnNcXFZpY3RvcmlhIFZhcmdhc1xcRG93bmxvYWRzXFxBVElWSURBREUgM1xccXVhdHJvZ3J1cG9zX2V1Y2xpZGVhbi5jc3YiKQ0KY2luY29fZ3J1cG9zIDwtIHJlYWQuY3N2KCJDOlxcVXNlcnNcXFZpY3RvcmlhIFZhcmdhc1xcRG93bmxvYWRzXFxBVElWSURBREUgM1xcY2luY29ncnVwb3NfZXVjbGlkZWFuLmNzdiIpDQoNCg0KDQpzaWxodWV0YSA8LSBjYmluZC5kYXRhLmZyYW1lIChncnVwb3M9YXMuZmFjdG9yKDE6NSksc2lsaHVldGE9YygwLCBkb2lzX2dydXBvc1sxLF0sdHJlc19ncnVwb3NbMSxdLHF1YXRyb19ncnVwb3NbMSxdLGNpbmNvX2dydXBvc1sxLF0pKQ0KDQoNCmdncGxvdChkYXRhPXNpbGh1ZXRhLCBhZXMoeD1ncnVwb3MsIHk9c2lsaHVldGEsIGdyb3VwPTEsIGxhYmVsPWFzLmNoYXJhY3Rlcihyb3VuZChzaWxodWV0YSwyKSkpKSArDQogIGdlb21fbGluZSgpKw0KICBnZW9tX3BvaW50KCkrDQogIGdlb21fdGV4dChoanVzdD0wLCB2anVzdD0tMS41KSsNCiAgbGFicyh4PSJcbkdydXBvcyIseT0iVmFsb3IgZGEgc2lsaHVldGEgbcOpZGlhXG4iLCB0aXRsZT0iRXhwZXJpbWVudG8gMyAtIFZhbG9yIGRhIHNpbGh1ZXRhIG3DqWRpYSBkZSBhY29yZG8gY29tIG8gbsO6bWVybyBkZSBncnVwb3Mga1xuIikrDQogIHNjYWxlX3lfY29udGludW91cyhicmVha3MgPSBzZXEoMCwwLjYsYnk9MC4xKSwgbGltaXRzPSBjKDAsMC42KSkrDQogIHRoZW1lX2NsYXNzaWMoKQ0KDQoNCg0KYGBgDQoNCg0KTm90ZSBxdWUsIGRlIGFjb3JkbyBjb20gb3MgdmFsb3JlcyBkYSBzaWxodWV0YSBtw6lkaWEsIGEgbWVsaG9yIHNvbHXDp8OjbyB1dGlsaXphbmRvIGEgZGlzdMOibmNpYSBkZSBFdWNsaWRpYW5hIGVtIGNvbmp1bnRvIGFvIGFsZ29yaXRtbyBQQU0gw6kgcGFyYSAkaz0yJCBncnVwb3MsIG8gbsO6bWVybyAiY29ycmV0byIgZGUgZ3J1cG9zIGRvIGRhdGFzZXQgKmNyZWRpdCouDQoNCg0KQ29tIGEgc29sdcOnw6NvIGRvIFBBTSBwYXJhICRrPTIkIGdydXBvcywgdGVudGFtb3MgaW50ZXJwcmV0YXIgbyBjb21wb3J0YW1lbnRvIGRlIGNhZGEgY2x1c3RlciBjb20gYSBhanVkYSBkb3MgbWVkb2lkcyBkYSBzb2x1w6fDo28uIA0KDQotLS0NCg0KPGRpdiBhbGlnbj0iY2VudGVyIj4NCiAgIGByIHRhYmxlX251bXMoJ3RhYl8xNScpYA0KPC9kaXY+DQoNCi0tLQ0KYGBge3J9DQpsb2FkKCJDOlxcVXNlcnNcXFZpY3RvcmlhIFZhcmdhc1xcRG93bmxvYWRzXFxBVElWSURBREUgM1xcZG9pc2dydXBvc19ldWNsaWRlYW4uUmRhdGEiKQ0KDQprbml0cjo6a2FibGUoY3JlZGl0W3BhbV8yZyRtZWRvaWRzLCBdKSU+JSBrYWJsZV9zdHlsaW5nKHBvc2l0aW9uID0gImNlbnRlciIpDQoNCmBgYA0KDQotIERlIGFjb3JkbyBjb20gYSB0YWJlbGEsIG9zIGNsaWVudGVzIHBlcnRlbmNlbnRlcyBhbyBncnVwby9jbHVzdGVyIDEgdMOqbSBhcyBzZWd1aW50ZXMgY2FyYWN0ZXLDrXN0aWNhczogbGltaXRlIGRlIA0KTlRcJDE0MGssIG11bGhlcmVzIGNhc2FkYXMgY29tIGdyYWR1YcOnw6NvIGNvbXBsZXRhLCBpZGFkZSBtw6lkaWEgZGUgMzQgYW5vcywgdXRpbGl6YWRvcmFzIGRlIGNyw6lkaXRvIA0Kcm90YXRpdm8gZW0gdG9kb3Mgb3MgbWVzZXMsIGV4dHJhdG9zIGVudHJlIE5UXCQxNWsgZSBOVFwkMjFrLCBkaW1pbnVpbmRvIHNldXMgdmFsb3JlcyBjb20gbyBwYXNzYXIgZG9zIA0KbWVzZXMsIHBhZ2FtZW50b3MgZW50cmUgTlRcJDFrIGUgTlRcJDIuNWsgZSBjb20gY29tcG9ydGFtZW50byBpcnJlZ3VsYXIuDQoNCi0gT3MgY2xpZW50ZXMgZG8gc2VndW5kbyBncnVwby9jbHVzdGVyIGRpZmVyZW0gc8OjbyBlbSBnZXJhbCBob21lbnMgc29sdGVpcm9zLCBjb20gbGltaXRlIGRlIE5UXCQyMzBrLCBlbnNpbm8gbcOpZGlvIGNvbXBsZXRvLA0KY29tIG1lc21hIGlkYWRlIG3DqWRpYSwgdGFtYsOpbSB1dGlsaXphZG9yZXMgZGUgY3LDqWRpdG8gcm90YXRpdm8gZW0gdG9kb3Mgb3MgbWVzZXMsIGV4dHJhdG9zIGVudHJlIA0KTlRcJDExMGsgZSBOVFwkMTQwaywgcGFnYW1lbnRvcyBlbnRyZSBOVFwkNC41ayBlIE5UXCQ3aywgZSB0YW1iw6ltIGFwcmVzZW50YW0gY29tcG9ydGFtZW50byBpcnJlZ3VsYXIuDQoNCg0KVmVyaWZpY2FuZG8gYSBtYXRyaXogZGUgY29uZnVzw6NvIHBhcmEgYSBzb2x1w6fDo28gY29tICRrPTIkLCBvYnRlbW9zOiANCg0KDQoNCi0tLQ0KDQo8ZGl2IGFsaWduPSJjZW50ZXIiPg0KICAgYHIgdGFibGVfbnVtcygndGFiXzE2JylgDQo8L2Rpdj4NCg0KLS0tDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KDQphdXggPC0gZG9pc19ncnVwb3MNCmF1eDwtIGFzLmNoYXJhY3RlcihhdXhbLTEsXSkNCg0KIyB0YWJsZShhdXgpDQojIDEgYXBhcmVjZSBlbSBtYWlvciBxdWFudGlkYWRlLCBsb2dvLCBjb3JyZXNwb25kZSBhbyBncnVwbyAwIGRhIGJhc2Ugb3JpZ2luYWwNCmF1eCBbd2hpY2goYXV4PT0iMSIpXSA8LSAiMCINCmF1eCBbd2hpY2goYXV4PT0iMiIpXSA8LSAiMSINCg0KIyBjb3JyZXNwb25kZW5jaWEgPC0gZGF0YS5mcmFtZShQQUdfUEFEUkFPID0gc3VtKGFzLmNoYXJhY3RlcihjcmVkaXQkUEFHX1BBRFJBTykgPT0gYXV4KS8zMDAwMCkNCiMgDQojIGNvcnJlc3BvbmRlbmNpYSA8LSByb3VuZChjb3JyZXNwb25kZW5jaWEqMTAwLDIpDQojIGNvbG5hbWVzKGNvcnJlc3BvbmRlbmNpYSkgPC0gYygiIEV4cDEgLSBBY2VydG9zICglKSIpDQojIGtuaXRyOjprYWJsZShjb3JyZXNwb25kZW5jaWEpJT4lIGthYmxlX3N0eWxpbmcocG9zaXRpb24gPSAiY2VudGVyIikNCg0KDQoNCiNDcmVhdGVzIHZlY3RvcnMgaGF2aW5nIGRhdGEgcG9pbnRzDQpleHBlY3RlZF92YWx1ZSA8LSBmYWN0b3IoYXV4KQ0KcHJlZGljdGVkX3ZhbHVlIDwtIGZhY3RvcihjcmVkaXQkUEFHX1BBRFJBTykNCg0KI0NyZWF0aW5nIGNvbmZ1c2lvbiBtYXRyaXgNCmV4YW1wbGUgPC0gY29uZnVzaW9uTWF0cml4KGRhdGE9cHJlZGljdGVkX3ZhbHVlLCByZWZlcmVuY2UgPSBleHBlY3RlZF92YWx1ZSkNCg0KI0Rpc3BsYXkgcmVzdWx0cyANCmtuaXRyOjprYWJsZShleGFtcGxlJHRhYmxlKSU+JSBrYWJsZV9zdHlsaW5nKHBvc2l0aW9uID0gImNlbnRlciIpDQoNCg0KDQoNCmBgYA0KDQotIE5hIHNvbHXDp8OjbyBkZSBhZ3J1cGFtZW50byBvYnRpZGEsIG9zIG9iamV0b3MgZm9yYW0gYWxvY2Fkb3MgYW9zIGNsdXN0ZXJzIDEgZSAyLiBQb3IgcG9zc3VpciBtYWlzIG9ic2VydmHDp8O1ZXMsDQp0YWwgcXVhbCBvIGdydXBvIDAgZGEgYmFzZSBvcmlnaW5hbCwgdHJhbnNmb3Jtb3Utc2UgbyAiMSIgZW0gIjAiLiBMb2dvLCBvICIyIiB0ZXZlIHF1ZSBzZXIgdHJhbnNmb3JtYWRvIGVtICIxIi4NCi0gQW8gY29tcGFyYXJtb3MgYSB2YXJpw6F2ZWwgKipQQUdfUEFEUkFPKiogY29tIGEgc29sdcOnw6NvIGRvIGFncnVwYW1lbnRvIHBhcmEgJGs9MiQgZG8gRXhwZXJpbWVudG8gMiwgdGVtb3MgcXVlIGEgYWN1csOhY2lhICBmb2kgZGUgNjUuMzklLg0KLSBEZSBhY29yZG8gY29tIGEgdGFiZWxhLCBvIGFncnVwYW1lbnRvIG9idGlkbyBhbG9jb3UgdW0gb2JqZXRvIHBlcnRlbmNlbnRlIGFvIGdydXBvIDAgb3JpZ2luYWxtZW50ZSBhbyBncnVwbyAwIGRvIGFncnVwYW1lbnRvIGVtIDE4NDI0IGNhc29zIGUgYWxvY291IHVtIG9iamV0byBkbyBncnVwbyAwIGFvIGdydXBvIDEgZW0gNTQ0MiBjYXNvcy4gDQotICBPIGFncnVwYW1lbnRvIG9idGlkbyBhbG9jb3UgdW0gb2JqZXRvIHBlcnRlbmNlbnRlIGFvIGdydXBvIDEgb3JpZ2luYWxtZW50ZSBhbyBncnVwbyAwIGRvIGFncnVwYW1lbnRvIGVtIDQ5NDAgY2Fzb3MsIGUgYWxvY291IHVtIG9iamV0byBkbyBncnVwbyAxIGFvIGdydXBvIDEgZG8gYWdydXBhbWVudG8gZW0gMTE5NCBjYXNvcy4NCg0KDQoNCiMjIERpc3TDom5jaWEgRXVjbGlkaWFuYSArIENMQVJBDQoNClBvciBmaW0sIG8gRXhwZXJpbWVudG8gNCB0YW1iw6ltIHV0aWxpem91IGEgZGlzdMOibmNpYSBFdWNsaWRpYW5hLCBtYXMgYWdvcmEgZW0gY29uanVudG8gYW8gYWxnb3JpdG1vIENMQVJBLiANCg0KQ29tZW50YW1vcyBhbnRlcmlvcm1lbnRlIHF1ZSBhIGRpc3TDom5jaWEgZXVjbGlkaWFuYSBkZXZlIHNlciB1c2FkYSBzb21lbnRlIGNvbSB2YXJpw6F2ZWlzIHF1YW50aXRhdGl2YXMsIHBvcnRhbnRvLCB1dGlsaXphbW9zIG5lc3RlIGV4cGVyaW1lbnRvIHNvbWVudGUgYXMgdmFyacOhdmVpcyBxdWFudGl0YXRpdmFzIGRvIGRhdGFzZXQ6IA0KDQotICpMSU1JVEUqDQotICpJREFERSoNCi0gKkVYVF9TRVQyMDA1KiANCi0gKkVYVF9BR08yMDA1KiANCi0gKkVYVF9KVUwyMDA1KiANCi0gKkVYVF9KVU4yMDA1Kg0KLSAqRVhUX01BSTIwMDUqICANCi0gKkVYVF9BQlIyMDA1KiANCi0gKlBBR19TRVQyMDA1KiAgDQotICpQQUdfQUdPMjAwNSogDQotICpQQUdfSlVMMjAwNSogDQotICpQQUdfSlVOMjAwNSogIA0KLSAqUEFHX01BSTIwMDUqIA0KLSAqUEFHX0FCUjIwMDUqDQoNClByZXZpYW1lbnRlIGFvIGPDoWxjdWxvIGRhcyBkaXN0w6JuY2lhcywgZXNzYXMgdmFyacOhdmVpcyBmb3JhbSBwYWRyb25pemFkYXMgY29tIG8gYXV4w61saW8gZGEgZnVuw6fDo28gKnNjYWxlKi4NCg0KQSBtYXRyaXogZGUgZGlzdMOibmNpYSBmb2kgb2J0aWRhIHBlbGEgZnVuw6fDo28gKmRpc3RhbmNlcyogZG8gcGFjb3RlICpkaXN0YW5jZXMqLiANCg0KQ29tIGEgbWF0cml6IGRlIGRpc3TDom5jaWFzIGV1Y2xpZGlhbmEgZW0gbcOjb3MsIG8gRXhwZXJpbWVudG8gNCBjb25zaXN0aXUgZW0gYXBsaWNhciBvIGFsZ29yaXRtbyBkZSBhZ3J1cGFtZW50byAqKkNMQVJBKiogdmFyaWFuZG8gbyBuw7ptZXJvIGRlIGdydXBvcyAqKmsgZW50cmUgMiBlIDUqKi4gQSBxdWFsaWRhZGUgZG9zIGFncnVwYW1lbnRvcyBmb2kgdmVyaWZpY2FkYSBhdHJhdsOpcyBkYSBzaWxodWV0YSBtw6lkaWEgZGFzIHNvbHXDp8O1ZXMuIA0KDQoNCmBgYHtyfQ0KDQojIGk9Mg0KIyBjbGFyYV9jbHVzdGVycyA8LSBjbGFyYShjcmVkaXRbLGMoMiw2LDEzOjI0KV0sIGs9aSwgc2FtcGxlcyA9IDUwLHNhbXBzaXplPTEwMDAsIHBhbUxpa2UgPSBUUlVFLCBzdGFuZCA9IEZBTFNFKQ0KIyBzYXZlKGNsYXJhX2NsdXN0ZXJzLGZpbGU9IkM6XFxVc2Vyc1xcVmljdG9yaWEgVmFyZ2FzXFxEb3dubG9hZHNcXEFUSVZJREFERSAzXFxkb2lzZ3J1cG9zX2V1Y2xpZGVhbl9jbGFyYS5SZGF0YSIpDQojIGRvaXNfZ3J1cG9zIDwtIGRhdGEuZnJhbWUoYyhhcy52ZWN0b3IoY2xhcmFfY2x1c3RlcnMkc2lsaW5mbyRhdmcud2lkdGgpLGFzLnZlY3RvcihjbGFyYV9jbHVzdGVycyRjbHVzdGVyaW5nKSkpDQojIGZ3cml0ZShkb2lzX2dydXBvcywgZmlsZSA9ICJDOlxcVXNlcnNcXFZpY3RvcmlhIFZhcmdhc1xcRG93bmxvYWRzXFxBVElWSURBREUgM1xcZG9pc2dydXBvc19ldWNsaWRlYW5fY2xhcmEuY3N2IikNCiMgDQojIHJtKGxpc3Q9YygiY2xhcmFfY2x1c3RlcnMiLCJkb2lzX2dydXBvcyIpKQ0KIyBnYygpDQojIA0KIyBpPTMNCiMgY2xhcmFfY2x1c3RlcnMgPC0gY2xhcmEoY3JlZGl0WyxjKDIsNiwxMzoyNCldLCBrPWksIHNhbXBsZXMgPSA1MCxzYW1wc2l6ZT0xMDAwLCBwYW1MaWtlID0gVFJVRSwgc3RhbmQgPSBGQUxTRSkNCiMgc2F2ZShjbGFyYV9jbHVzdGVycyxmaWxlPSJDOlxcVXNlcnNcXFZpY3RvcmlhIFZhcmdhc1xcRG93bmxvYWRzXFxBVElWSURBREUgM1xcdHJlc2dydXBvc19ldWNsaWRlYW5fY2xhcmEuUmRhdGEiKQ0KIyB0cmVzX2dydXBvcyA8LSBkYXRhLmZyYW1lKGMoYXMudmVjdG9yKGNsYXJhX2NsdXN0ZXJzJHNpbGluZm8kYXZnLndpZHRoKSxhcy52ZWN0b3IoY2xhcmFfY2x1c3RlcnMkY2x1c3RlcmluZykpKQ0KIyBmd3JpdGUodHJlc19ncnVwb3MsIGZpbGUgPSAiQzpcXFVzZXJzXFxWaWN0b3JpYSBWYXJnYXNcXERvd25sb2Fkc1xcQVRJVklEQURFIDNcXHRyZXNncnVwb3NfZXVjbGlkZWFuX2NsYXJhLmNzdiIpDQojIA0KIyBybShsaXN0PWMoImNsYXJhX2NsdXN0ZXJzIiwidHJlc19ncnVwb3MiKSkNCiMgZ2MoKQ0KIyANCiMgaT00DQojIGNsYXJhX2NsdXN0ZXJzIDwtIGNsYXJhKGNyZWRpdFssYygyLDYsMTM6MjQpXSwgaz1pLCBzYW1wbGVzID0gNTAsc2FtcHNpemU9MTAwMCwgcGFtTGlrZSA9IFRSVUUsIHN0YW5kID0gRkFMU0UpDQojIHNhdmUoY2xhcmFfY2x1c3RlcnMsZmlsZT0iQzpcXFVzZXJzXFxWaWN0b3JpYSBWYXJnYXNcXERvd25sb2Fkc1xcQVRJVklEQURFIDNcXHF1YXRyb2dydXBvc19ldWNsaWRlYW5fY2xhcmEuUmRhdGEiKQ0KIyB0cmVzX2dydXBvcyA8LSBkYXRhLmZyYW1lKGMoYXMudmVjdG9yKGNsYXJhX2NsdXN0ZXJzJHNpbGluZm8kYXZnLndpZHRoKSxhcy52ZWN0b3IoY2xhcmFfY2x1c3RlcnMkY2x1c3RlcmluZykpKQ0KIyBmd3JpdGUocXVhdHJvX2dydXBvcywgZmlsZSA9ICJDOlxcVXNlcnNcXFZpY3RvcmlhIFZhcmdhc1xcRG93bmxvYWRzXFxBVElWSURBREUgM1xccXVhdHJvZ3J1cG9zX2V1Y2xpZGVhbl9jbGFyYS5jc3YiKQ0KIyANCiMgcm0obGlzdD1jKCJjbGFyYV9jbHVzdGVycyIsInF1YXRyb19ncnVwb3MiKSkNCiMgZ2MoKQ0KIyANCiMgaT01DQojIGNsYXJhX2NsdXN0ZXJzIDwtIGNsYXJhKGNyZWRpdFssYygyLDYsMTM6MjQpXSwgaz1pLCBzYW1wbGVzID0gNTAsc2FtcHNpemU9MTAwMCwgcGFtTGlrZSA9IFRSVUUsIHN0YW5kID0gRkFMU0UpDQojIHNhdmUoY2xhcmFfY2x1c3RlcnMsZmlsZT0iQzpcXFVzZXJzXFxWaWN0b3JpYSBWYXJnYXNcXERvd25sb2Fkc1xcQVRJVklEQURFIDNcXGNpbmNvZ3J1cG9zX2V1Y2xpZGVhbl9jbGFyYS5SZGF0YSIpDQojIGNpbmNvX2dydXBvcyA8LSBkYXRhLmZyYW1lKGMoYXMudmVjdG9yKGNsYXJhX2NsdXN0ZXJzJHNpbGluZm8kYXZnLndpZHRoKSxhcy52ZWN0b3IoY2xhcmFfY2x1c3RlcnMkY2x1c3RlcmluZykpKQ0KIyBmd3JpdGUoY2luY29fZ3J1cG9zLCBmaWxlID0gIkM6XFxVc2Vyc1xcVmljdG9yaWEgVmFyZ2FzXFxEb3dubG9hZHNcXEFUSVZJREFERSAzXFxjaW5jb2dydXBvc19ldWNsaWRlYW5fY2xhcmEuY3N2IikNCiMgDQojIHJtKGxpc3Q9YygiY2xhcmFfY2x1c3RlcnMiLCJjaW5jb19ncnVwb3MiKSkNCiMgZ2MoKQ0KIyANCiMgcm0obGlzdCA9IGxzKCkpDQojIGdjKCkNCiMgLnJzLnJlc3RhcnRSKCkNCg0KZG9pc19ncnVwb3M8LSByZWFkLmNzdigiQzpcXFVzZXJzXFxWaWN0b3JpYSBWYXJnYXNcXERvd25sb2Fkc1xcQVRJVklEQURFIDNcXGRvaXNncnVwb3NfZXVjbGlkZWFuX2NsYXJhLmNzdiIpDQp0cmVzX2dydXBvcyA8LSByZWFkLmNzdigiQzpcXFVzZXJzXFxWaWN0b3JpYSBWYXJnYXNcXERvd25sb2Fkc1xcQVRJVklEQURFIDNcXHRyZXNncnVwb3NfZXVjbGlkZWFuX2NsYXJhLmNzdiIpDQpxdWF0cm9fZ3J1cG9zIDwtIHJlYWQuY3N2KCJDOlxcVXNlcnNcXFZpY3RvcmlhIFZhcmdhc1xcRG93bmxvYWRzXFxBVElWSURBREUgM1xccXVhdHJvZ3J1cG9zX2V1Y2xpZGVhbl9jbGFyYS5jc3YiKQ0KY2luY29fZ3J1cG9zIDwtIHJlYWQuY3N2KCJDOlxcVXNlcnNcXFZpY3RvcmlhIFZhcmdhc1xcRG93bmxvYWRzXFxBVElWSURBREUgM1xcY2luY29ncnVwb3NfZXVjbGlkZWFuX2NsYXJhLmNzdiIpDQoNCg0Kc2lsaHVldGEgPC0gY2JpbmQuZGF0YS5mcmFtZSAoZ3J1cG9zPWFzLmZhY3RvcigxOjUpLHNpbGh1ZXRhPWMoMCwgZG9pc19ncnVwb3NbMSxdLHRyZXNfZ3J1cG9zWzEsXSxxdWF0cm9fZ3J1cG9zWzEsXSxjaW5jb19ncnVwb3NbMSxdKSkNCg0KDQpnZ3Bsb3QoZGF0YT1zaWxodWV0YSwgYWVzKHg9Z3J1cG9zLCB5PXNpbGh1ZXRhLCBncm91cD0xLCBsYWJlbD1hcy5jaGFyYWN0ZXIocm91bmQoc2lsaHVldGEsMikpKSkgKw0KICBnZW9tX2xpbmUoKSsNCiAgZ2VvbV9wb2ludCgpKw0KICBnZW9tX3RleHQoaGp1c3Q9MCwgdmp1c3Q9LTEuNSkrDQogIGxhYnMoeD0iXG5HcnVwb3MiLHk9IlZhbG9yIGRhIHNpbGh1ZXRhIG3DqWRpYVxuIiwgdGl0bGU9IkV4cGVyaW1lbnRvIDQgLSBWYWxvciBkYSBzaWxodWV0YSBtw6lkaWEgZGUgYWNvcmRvIGNvbSBvIG7Dum1lcm8gZGUgZ3J1cG9zIGtcbiIpKw0KICBzY2FsZV95X2NvbnRpbnVvdXMoYnJlYWtzID0gc2VxKDAsMC42LGJ5PTAuMSksIGxpbWl0cz0gYygwLDAuNikpKw0KICB0aGVtZV9jbGFzc2ljKCkNCg0KDQpgYGANCg0KTm90ZSBxdWUsIGRlIGFjb3JkbyBjb20gb3MgdmFsb3JlcyBkYSBzaWxodWV0YSBtw6lkaWEsIGEgbWVsaG9yIHNvbHXDp8OjbyB1dGlsaXphbmRvIGEgZGlzdMOibmNpYSBkZSBFdWNsaWRpYW5hIGVtIGNvbmp1bnRvIGFvIGFsZ29yaXRtbyBDTEFSQSDDqSBwYXJhICRrPTMkIGdydXBvcy4NCg0KQ29tIGEgc29sdcOnw6NvIGRvIENMQVJBIHBhcmEgJGs9MyQgZ3J1cG9zLCB0ZW50YW1vcyBpbnRlcnByZXRhciBvIGNvbXBvcnRhbWVudG8gZGUgY2FkYSBjbHVzdGVyIGNvbSBhIGFqdWRhIGRvcyBtZWRvaWRzIGRhIHNvbHXDp8Ojby4gDQoNCi0tLQ0KDQo8ZGl2IGFsaWduPSJjZW50ZXIiPg0KICAgYHIgdGFibGVfbnVtcygndGFiXzE3JylgDQo8L2Rpdj4NCg0KLS0tDQpgYGB7cn0NCmxvYWQoIkM6XFxVc2Vyc1xcVmljdG9yaWEgVmFyZ2FzXFxEb3dubG9hZHNcXEFUSVZJREFERSAzXFx0cmVzZ3J1cG9zX2V1Y2xpZGVhbl9jbGFyYS5SZGF0YSIpDQoNCmtuaXRyOjprYWJsZShjcmVkaXRbY2xhcmFfY2x1c3RlcnMkaS5tZWQsIF0pJT4lIGthYmxlX3N0eWxpbmcocG9zaXRpb24gPSAiY2VudGVyIikNCg0KYGBgDQoNCi0gR3J1cG8vY2x1c3RlciAxOiBMaW1pdGUgZGUgTlRcJDYwaywgbXVsaGVyZXMgY2FzYWRhcyBjb20gZW5zaW5vIG3DqWRpbyBjb21wbGV0byBlIGlkYWRlIG3DqWRpYSBkZSAzOSBhbm9zLA0KdXRpbGl6YW5kbyBjcsOpZGl0byByb3RhdGl2byBlbSB0b2RvcyBvcyBtZXNlcyBkZSBhbsOhbGlzZSwgZXh0cmF0b3MgZW50cmUgIE5UXCQxN2sgZSAgTlRcJDI3aywgcGFnYW1lbnRvcyANCnBvciB2b2x0YSBkZSBOVFwkNTAwIGF0w6kganVuaG8gZSBjb20gbcOpZGlhIGRlIE5UXCQxLjNrIG5vcyBtZXNlcyByZXN0YW50ZXMgZSBjb20gY29tcG9ydGFtZW50byBpcnJlZ3VsYXIuDQoNCi0gR3J1cG8vY2x1c3RlciAyOiBMaW1pdGUgZGUgIE5UXCQyMDBrLCBtdWxoZXJlcyBzb2x0ZWlyYXMgY29tIGVuc2lubyBtw6lkaW8gY29tcGxldG8gZSBpZGFkZSBtw6lkaWEgZGUgNDUgYW5vcywNCnV0aWxpemFuZG8gY3LDqWRpdG8gcm90YXRpdm8gY29tIGV4Y2XDp8OjbyBkbyBtw6pzIGRlIHNldGVtYnJvLCBleHRyYXRvcyBlbnRyZSAgTlRcJDEyayBlICBOVFwkMTVrLCBwYWdhbWVudG9zIA0KZW50cmUgIE5UXCQ0ayBlICBOVFwkOGsgZSBjb20gY29tcG9ydGFtZW50byBpcnJlZ3VsYXIuDQoNCi0gR3J1cG8vY2x1c3RlciAzOiBMaW1pdGUgZGUgIE5UXCQyNjBrLCBob21lbnMgY2FzYWRvcyBjb20gZ3JhZHVhw6fDo28gY29tcGxldGEgZSBpZGFkZSBtw6lkaWEgZGUgMzAgYW5vcywgdXRpbGl6YW5kbyBjcsOpZGl0byByb3RhdGl2byAgZW0gdG9kb3Mgb3MgbWVzZXMgZGUgYW7DoWxpc2UsIGV4dHJhdG9zIGVudHJlICBOVFwkNmsgZSAgTlRcJDlrLCBwYWdhbWVudG9zIA0KaWd1YWlzIGEgIE5UXCQyayBlbSB0b2RvcyBvcyBtZXNlcyBjb20gZXhjZcOnw6NvIGRlIGFnb3N0bywgZSBjb20gY29tcG9ydGFtZW50byBpcnJlZ3VsYXIuDQoNCg0KDQoNClZlcmlmaWNhbmRvIGEgbWF0cml6IGRlIGNvbmZ1c8OjbyBwYXJhIGEgc29sdcOnw6NvIGNvbSAkaz0yJCwgb2J0ZW1vczogDQoNCi0tLQ0KDQo8ZGl2IGFsaWduPSJjZW50ZXIiPg0KICAgYHIgdGFibGVfbnVtcygndGFiXzE4JylgDQo8L2Rpdj4NCg0KLS0tDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KDQphdXggPC0gZG9pc19ncnVwb3MNCmF1eDwtIGFzLmNoYXJhY3RlcihhdXhbLTEsXSkNCg0KIyB0YWJsZShhdXgpDQojIGF1eCBbd2hpY2goYXV4PT0iMSIpXSA8LSAiMCINCiMgYXV4IFt3aGljaChhdXg9PSIyIildIDwtICIxIg0KDQphdXggW3doaWNoKGF1eD09IjIiKV0gPC0gIjAiDQojIGNvcnJlc3BvbmRlbmNpYSA8LSBkYXRhLmZyYW1lKFBBR19QQURSQU8gPSBzdW0oYXMuY2hhcmFjdGVyKGNyZWRpdCRQQUdfUEFEUkFPKSA9PSBhdXgpLzMwMDAwKQ0KIyANCiMgY29ycmVzcG9uZGVuY2lhIDwtIHJvdW5kKGNvcnJlc3BvbmRlbmNpYSoxMDAsMikNCiMgY29sbmFtZXMoY29ycmVzcG9uZGVuY2lhKSA8LSBjKCIgRXhwMSAtIEFjZXJ0b3MgKCUpIikNCiMga25pdHI6OmthYmxlKGNvcnJlc3BvbmRlbmNpYSklPiUga2FibGVfc3R5bGluZyhwb3NpdGlvbiA9ICJjZW50ZXIiKQ0KDQoNCg0KIw0KI0NyZWF0ZXMgdmVjdG9ycyBoYXZpbmcgZGF0YSBwb2ludHMNCmV4cGVjdGVkX3ZhbHVlIDwtIGZhY3RvcihhdXgpDQpwcmVkaWN0ZWRfdmFsdWUgPC0gZmFjdG9yKGNyZWRpdCRQQUdfUEFEUkFPKQ0KDQojQ3JlYXRpbmcgY29uZnVzaW9uIG1hdHJpeA0KZXhhbXBsZSA8LSBjb25mdXNpb25NYXRyaXgoZGF0YT1wcmVkaWN0ZWRfdmFsdWUsIHJlZmVyZW5jZSA9IGV4cGVjdGVkX3ZhbHVlKQ0KDQojRGlzcGxheSByZXN1bHRzIA0Ka25pdHI6OmthYmxlKGV4YW1wbGUkdGFibGUpJT4lIGthYmxlX3N0eWxpbmcocG9zaXRpb24gPSAiY2VudGVyIikNCg0KDQoNCg0KYGBgDQoNCi0gTmEgc29sdcOnw6NvIGRlIGFncnVwYW1lbnRvIG9idGlkYSwgb3Mgb2JqZXRvcyBmb3JhbSBhbG9jYWRvcyBhb3MgY2x1c3RlcnMgMSBlIDIuIFBvciBwb3NzdWlyIG1haXMgb2JzZXJ2YcOnw7VlcywNCnRhbCBxdWFsIG8gZ3J1cG8gMCBkYSBiYXNlIG9yaWdpbmFsLCB0cmFuc2Zvcm1vdS1zZSBvICIxIiBlbSAiMCIuIExvZ28sIG8gIjIiIHRldmUgcXVlIHNlciB0cmFuc2Zvcm1hZG8gZW0gIjEiLk5lc3NhIGNvbmZpZ3VyYcOnw6NvLCBhbyBjb21wYXJhcm1vcyBhIHZhcmnDoXZlbCAqKlBBR19QQURSQU8qKiBjb20gYSBzb2x1w6fDo28gZG8gYWdydXBhbWVudG8gcGFyYSAkaz0yJCBkbyBFeHBlcmltZW50byAyLCB0ZW1vcyBxdWUgYSBhY3Vyw6FjaWEgIGZvaSBkZSA0Ni43JS4gRW50w6NvLCBkZSBmb3JtYSBhIG1heGltaXphciBhIGFjdXLDoWNpYSwgb3B0YW1vcyBwb3IgdHJhbnNmb3JtYXIgbyBjbHVzdGVyICIyIiBlbSAiMCIsIG1hbnRlbmRvIG8gY2x1c3RlciAxLCBvYnRlbmRvIHVtYSBhY3Vyw6FjaWEgZGUgNTMuMyUuDQotIERlIGFjb3JkbyBjb20gYSB0YWJlbGEsIG8gYWdydXBhbWVudG8gb2J0aWRvIGFsb2NvdSB1bSBvYmpldG8gcGVydGVuY2VudGUgYW8gZ3J1cG8gMCBvcmlnaW5hbG1lbnRlIGFvIGdydXBvIDAgZG8gYWdydXBhbWVudG8gZW0gMTE1MTUgY2Fzb3MgZSBhbG9jb3UgdW0gb2JqZXRvIGRvIGdydXBvIDAgYW8gZ3J1cG8gMSBlbSAyMTYyIGNhc29zLiANCi0gIE8gYWdydXBhbWVudG8gb2J0aWRvIGFsb2NvdSB1bSBvYmpldG8gcGVydGVuY2VudGUgYW8gZ3J1cG8gMSBvcmlnaW5hbG1lbnRlIGFvIGdydXBvIDAgZG8gYWdydXBhbWVudG8gZW0gMTE4NDkgY2Fzb3MsIGUgYWxvY291IHVtIG9iamV0byBkbyBncnVwbyAxIGFvIGdydXBvIDEgZG8gYWdydXBhbWVudG8gZW0gNDQ3NCBjYXNvcy4NCg0KDQoNCiMgQ29uY2x1c8Ojbw0KDQotIERlIGFjb3JkbyBjb20gYXMgYWN1csOhY2lhcyBkZSBjYWRhIG3DqXRvZG8sIG8gcXVlIHRldmUgbWFpb3IgcG9yY2VudGFnZW0gZm9pIG8gZGEgDQpkaXN0w6JuY2lhIEV1Y2xpZGlhbmEgKyBQQU0sIGUgdGFtYsOpbSwgbyBxdWUgYXByZXNlbnRvdSB2YWxvcmVzIGRlIHNpbGh1ZXRhIG3DqWRpYSBtYWlzIGRpdmVyZ2VudGVzLg0KDQotIFRvZG9zIG9zIG3DqXRvZG9zIHV0aWxpemFkb3MgdGl2ZXJhbSBtYWlzIDUwJSBkZSBhY3Vyw6FjaWEuDQoNCi0gTyBleHBlcmltZW50byAyIGZvaSBvIHF1ZSB0ZXZlIG9zIHZhbG9yZXMgZGUgc2lsaHVldGEgbcOpZGlhIG1haXMgYWx0b3MgZSBtYWlzIHBhcmlmb3JtZXMgZW50cmUgc2kuDQoNCi0gQ29tIGFzIG1lbGhvcmVzIHNvbHXDp8O1ZXMgZGUgYWNvcmRvIGNvbSBjYWRhIG3DqXRvZG8sIGNvbnNlZ3VpbW9zIHV0aWxpemFyIG9zIG1lZG9pZHMgcGFyYSB0cmHDp2FyIG8gcGVyZmlsIHJlcHJlc2VudGF0aXZvIGRlIGNhZGEgY2x1c3RlciBvYnRpZG8uDQoNCiMgTGltaXRhw6fDtWVzIA0KDQotIENvbW8gbyBkYXRhc2V0IHBvc3N1aSBtdWl0YXMgb2JzZXJ2YcOnw7VlcywgYW8gb3B0YXIgcG9yIHV0aWxpemFyIGEgZGlzdMOibmNpYSBkZSBHb3dlciBmb2kgbmVjZXNzw6FyaW8gY2FsY3VsYXIgdW1hIG1hdHJpeiBkZSBkaXN0w6JuY2lhIGRlIGdyYW5kZXMgZGltZW5zw7VlcyAoMzAuMDAwIHggMzAuMDAwKSwgbyBxdWUgZGVtYW5kb3UgdW0gcG9kZXIgZGUgcHJvY2Vzc2FtZW50byBtYWlzIGVsZXZhZG8uIA0KDQotIEZvaSB1dGlsaXphZG8gdW0gY29tcHV0YWRvciBjb20gYXMgc2VndWludGVzIGNhcmFjdGVyw61zdGljYXMgcGFyYSBjb25kdXppciBvIGPDoWxjdWxvIGRlc3NhIG1hdHJpeiBlIGVtIHNlZ3VpZGEsIHV0aWxpesOhLWxhIGNvbW8gZGFkbyBkZSBlbnRyYWRhIG5vcyBkZW1haXMgYWxnb3JpdG1vczoNCg0KPiBQcm9jZXNzYWRvciBpNS05NDAwZiA0LjBHSHoNCj4gTWVtw7NyaWEgUkFNIDE2R2IgRERSNCAyNjY2IE1oeg0KPiBTU0QgMjQwIEdiDQoNCi0gQSBjYWRhIGV0YXBhIGRvIHRyYWJhbGhvIGZvaSBuZWNlc3PDoXJpbyBzYWx2YXIgb3MgKm91dHB1dHMqIGRhcyBmdW7Dp8O1ZXMgcGFyYSBwb3N0ZXJpb3IgdXNvLCBhIGZpbSBkZSBlY29ub21pemFyIHRlbXBvIGUgZ2FyYW50aXIgbyBxdWUgZm9pIGZlaXRvLg0KDQotIFBhcmEgYXJxdWl2b3MgZ3JhbmRlcywgYXMgZnVuw6fDtWVzICpmd3JpdGUqICBkbyBwYWNvdGUgKmRhdGEudGFibGUqIGUgKnNhdmVSRFMqIGZvcmFtIHV0aWxpemFkYXMgcGFyYSBhZ2lsaXphciBvIHNhbHZhbWVudG8uIA0KDQotIFBhcmEgb3MgZXhwZXJpbWVudG9zIGVudm9sdmVuZG8gZGlzdMOibmNpYSBldWNsaWRpYW5hLCBhIGZ1bsOnw6NvICpkaXN0YW5jZXMqIGZvaSBlbXByZWdhZGEgZSwgcG9yIHNlciBvdGltaXphZGEsIG7Do28gb2NvcnJlcmFtIGRpZmljdWxkYWRlcyBxdWFudG8gw6AgdXRpbGl6YcOnw6NvIGRlIG1lbcOzcmlhLiANCg0KLSBGb2kgY29naXRhZG8gYSB2aXN1YWxpemHDp8OjbyBkb3MgY2x1c3RlcnMgdXRpbGl6YW5kbyBvIFQtU25lLCBlbnRyZXRhbnRvLCBhIGZ1bsOnw6NvICAqUnRzbmUqIGRvIHBhY290ZSAqUnRzbmUqIA0KbsOjbyBhY2VpdGEgb2JqZXRvcyBwcm9kdXppZG9zIHBlbGEgZnVuw6fDo28gKmRpc3RhbmNlcyouIEEgdmlzdWFsaXphw6fDo28gcGVsYSBtYXRyaXogZGUgR293ZXIgc2VyaWEgcG9zc8OtdmVsLCBtYXMgbyBhY2Vzc28gYW8gY29tcHV0YWRvciB1dGlsaXphZG8gbm9zIGPDoWxjdWxvcyBlcmEgbGltaXRhZG8gZSBvcyBjb21wdXRhZG9yZXMgZGlzcG9uw612ZWlzIG5lc3NlIG1vbWVudG8gZG8gdHJhYmFsaG8gbsOjbyBwb3NzdcOtYW0gcG9kZXIgZGUgcHJvY2Vzc2FtZW50byBzdWZpY2llbnRlIHNlcXVlciBwYXJhIGEgbGVpdHVyYSBkbyBhcnF1aXZvLiANCg0KDQojIFJlZmVyw6puY2lhcyANCg0KMS4gTC4gS2F1Zm1hbiBhbmQgUC4gSi4gUm91c3NlZXV3LiBGaW5kaW5nIEdyb3VwcyBpbiBEYXRhIC0gQW4gSW50cm9kdWN0aW9uIHRvIENsdXN0ZXJzIEFuYWx5c2lzLiBXaWxleS1JbnRlcnNjaWVuY2UgUHVibGljYXRpb24sIDE5ODkuDQoNCjIuIFcuIEIuIEhhaXIgYW5kIEIuIEEuIFIuIEJhYmluLiBNdWx0aXZhcmlhdGUgRGF0YSBBbmFseXNpcy4gQ2VuZ2FnZSwgOHRoIGVkaXRpb24sIDIwMTguDQo=