Foreword: About the Machine Learning in Medicine (MLM) project
The MLM project has been initialized in 2016 and aims to:
Encourage using Machine Learning techniques in medical research in Vietnam and
Promote the use of R statistical programming language, an open source and leading tool for practicing data science.
Introduction
How many features and which ones should be used to develop our model ? This question could be difficult, particularly when we have a very large dataset that contains hundred or thousands of variables. Feature selection can help us to identify the most relevant features for our problem.
Note that feature selection is different from Model selection, Model regularisation and dimensionality reduction. The feature selection procedure consists of selecting a subset of relevant features BEFORE fitting the models, whilst Model selection (Bayesian Model Averaging, Stepwise…) or Regularisation techniques such as boosting, Lasso or Elastic net imply modifications on the model itself. The dimensionality reduction techniques like PCA aims to generate new combinations from all features instead of excluding some of them.
Objective
In this case study X13, we will explore 12 simple filter methods of feature selection that are suported by the mlr package.
http://mlr-org.github.io/mlr-tutorial/devel/html/filter_methods/index.html
Filter methods consist of estimating a statistical measure to assign a scoring to each feature. The high ranked features will be considered more relevant and the ones with low rank could be excluded. Different statistical methods could be applied, including the simplest ones like Chi-square test, ANOVA or Kruskal-Wallis test or the scores that based on real machine learning algorithms like Information gain, gain ratio, Random Forest based variable’s importance. Those methods are often univariate and consider the feature independently, or with regard to the dependent variable.
We also introduce the Kuncheva stability index (KSI) for determining the best feature subset(s) for a binary classification problem.
Materials and method
Our experiment implies the SPECTF Heart Data Set, as described on the UCI ML database:
https://archive.ics.uci.edu/ml/datasets/SPECTF+Heart
The dataset represents a technique called “Cardiac Single Proton Emission Computed Tomography (SPECT)”. The train subset included 80 image sets and the test subset included 187 image sets. Each of the patients can be classified as normal and abnormal.
The data of these 267 SPECT image sets (patients) were processed to extract features that summarize the original SPECT images. As a result, 44 continuous feature pattern was created for each patient.Classification rule should be developed from these 44 patterns. Assuming that we already decided to apply Logistic model for constructing our classifier, and we would like to use only 15 features instead of 44. Our goal is to identify the most useful subset of 12 features from the original dataset.
Data preparation
First, we will prepare the ggplot theme for our experiment
library(tidyverse)
theme_bare <- function(base_size=8,base_family="sans"){theme_bw(base_size = base_size, base_family = base_family)+
theme(
axis.line = element_blank(),
axis.text.x = element_blank(),
axis.text.y = element_blank(),
axis.ticks = element_blank(),
axis.title.x = element_blank(),
axis.title.y = element_blank(),
legend.position = "bottom",
panel.background = element_rect(fill = NA),
panel.border = element_blank(),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
plot.margin = unit(c(0,0,0,0), "lines")
)
}
my_theme <- function(base_size =5, base_family = "sans"){
theme_bw(base_size = base_size, base_family = base_family) +
theme(
panel.grid.major = element_line(color = "gray"),
panel.grid.minor = element_blank(),
panel.background = element_rect(fill = NA),
strip.background = element_rect(fill = "#001d60", color = "#00113a", size =0.5),
strip.text = element_text(face = "bold", size = 5, color = "white"),
legend.position = "bottom",
legend.justification = "center",
legend.background = element_blank(),
legend.margin = margin(0.5,0.5,0.5,0.5)
)
}
theme_set(my_theme())
myfillcolors=c("#ff003f","#0094ff", "#ae00ff" , "#94ff00", "#ffc700","#fc1814")
First, we load the data to R:
testset=read.table("https://archive.ics.uci.edu/ml/machine-learning-databases/spect/SPECTF.test",sep = ",")%>%as_tibble()
trainset=read.table("https://archive.ics.uci.edu/ml/machine-learning-databases/spect/SPECTF.train",sep = ",")%>%as_tibble()
testset$V1<-factor(testset$V1,levels = c(1,0), labels = c("Positive", "Negative") )
trainset$V1<-factor(trainset$V1,levels = c(1,0), labels = c("Positive", "Negative") )
names(testset)=c("Class",paste(rep("F",times=44),c(1:44),sep="") )
names(trainset)=c("Class",paste(rep("F",times=44),c(1:44),sep="") )
Data visualising
trainset%>%gather(F1:F44,key="Features",value="Value")%>%ggplot(aes(x=Value,fill=Class))+geom_density(alpha=0.5)+facet_wrap(~Features,scales="free",ncol=4)+scale_fill_manual(values=myfillcolors)
trainset%>%gather(F1:F44,key="Features",value="Value")%>%ggplot(aes(x=Class,y=Value,fill=Class))+geom_boxplot(alpha=0.7)+facet_wrap(~Features,scales="free",ncol=4)+scale_fill_manual(values=myfillcolors)+coord_flip()
library(corrplot)
library(RColorBrewer)
cor_mat=as.matrix(cor(as.matrix(trainset[,-1])))
cor_mat%>%corrplot(.,order="hclust",type="lower",method="ellipse",tl.col="black", tl.srt=45,tl.cex=0.5,col=rev(brewer.pal(n=11, name="RdBu")))

cor_mat=as.matrix(cor(as.matrix(trainset[,-1])))
library(igraph)
diag(cor_mat)<-0
library(ggraph)
m=cor_mat
df=data.frame(row=rownames(m)[row(m)[upper.tri(m)]],
col=colnames(m)[col(m)[upper.tri(m)]],
corr=m[upper.tri(m)])
cdf=subset(df,abs(corr)>0.5)
names(cdf)=c("from","to","corr")
graph<-graph_from_data_frame(cdf)
ggraph(graph, layout = 'linear',circular=T)+geom_edge_arc(aes(colour = corr,width=corr),alpha=0.3)+geom_node_label(aes(label = name))+coord_fixed()+scale_edge_color_gradient(high="#ff003f",low="#0094ff")+scale_edge_width_continuous()+theme_bare()
Feature selection by 12 Filter methods
We introduce a function called “vimplot”. This function will perform a filter method of interest then extract the subset of features and plot the result.
Then we apply the function with 12 different filter methods and save the output within the objects from impvar1 to impvar12
library(FSelector)
library(mlr)
library(randomForestSRC)
library(rJava)
library(MLmetrics)
vimplot=function(data,target,method){
train.task=makeClassifTask(data=data, target=target)
imp_var=generateFilterValuesData(train.task, method=method)
fsdf=imp_var$data%>%.[,-2]
names(fsdf)=c("Feature","Value")
fsdf$Method=method
fsdf$Feature
vimp=fsdf%>%arrange(desc(Value))%>% select(Feature)
plot=fsdf%>%ggplot(aes(x=reorder(fsdf$Feature,fsdf$Value),y=Value,fill=Value,color=Value))+geom_segment(aes(x=reorder(fsdf$Feature,fsdf$Value),xend=Feature,y=0,yend=Value),size=1,show.legend = F)+geom_point(size=2,show.legend = F)+scale_x_discrete("Features")+scale_y_continuous(method)+coord_flip()+scale_fill_gradient(low="blue",high="red")+scale_color_gradient(low="blue",high="red")+ggtitle(method)
return(list(plot=plot,name=vimp))
}
#ANOVA TEST
res=vimplot(data=trainset,target="Class",method="anova.test")
impvar1 <-res$name
res$plot

# cforest.importance
res=vimplot(data=trainset,target="Class",method="cforest.importance")
impvar2 <-res$name
res$plot

#chi.squared
res=vimplot(data=trainset,target="Class",method="chi.squared")
impvar3 <-res$name
res$plot

#gain.ratio
res=vimplot(data=trainset,target="Class",method="gain.ratio")
impvar4 <-res$name
res$plot

#information.gain
res=vimplot(data=trainset,target="Class",method="information.gain")
impvar5 <-res$name
res$plot

#OneR
res=vimplot(data=trainset,target="Class",method="oneR")
## Class ~ .
## <environment: 0x00000000704520a0>
impvar6 <-res$name
res$plot

#randomForest.importance
res=vimplot(data=trainset,target="Class",method="randomForest.importance")
impvar7 <-res$name
res$plot

#relief
res=vimplot(data=trainset,target="Class",method="relief")
impvar8 <-res$name
res$plot

#symmetrical.uncertainty
res=vimplot(data=trainset,target="Class",method="symmetrical.uncertainty")
impvar9 <-res$name
res$plot

# randomForestSRC.rfsrc
res=vimplot(data=trainset,target="Class",method="randomForestSRC.rfsrc")
impvar10 <-res$name
res$plot

# variance
res=vimplot(data=trainset,target="Class",method="variance")
impvar11<-res$name
res$plot

#univariate.model.score (rpart)
res=vimplot(data=trainset,target="Class",method="univariate.model.score")
impvar12<-res$name
res$plot

Kuncheva stability index
The Kuncheva stability index is supported by OmicsMarkeR pacakge that could be installed from Bioconductor platform:
#source("https://bioconductor.org/biocLite.R")
#biocLite("OmicsMarkeR")
First, we will load the package, then apply a loop on our 12 output objects of filter methods (V1 to V12). As mentioned above, we would like to reduce the number of features from 44 to only 15 (thus excluding 29 features). Note that the subsets of 15 features might be different, depending on the filtering method in use.
The Kuncheva stability index aims to measure the consistency level between those 12 feature subsets. The result looks similar to a correlation matrix.
library("OmicsMarkeR")
v1=impvar1$Feature[1:15]
v2=impvar2$Feature[1:15]
v3=impvar3$Feature[1:15]
v4=impvar4$Feature[1:15]
v5=impvar5$Feature[1:15]
v6=impvar6$Feature[1:15]
v7=impvar7$Feature[1:15]
v8=impvar8$Feature[1:15]
v9=impvar9$Feature[1:15]
v10=impvar10$Feature[1:15]
v11=impvar11$Feature[1:15]
v12=impvar12$Feature[1:15]
mat <- matrix(NA,12,12)
varcol <- paste0("v",seq(1,12,by = 1))
varrow <- paste0("v",seq(1,12,by = 1))
for (row in 1:12){
for(col in 1:12){
command <- paste0(" mat[row,col] <- kuncheva(",
varrow[row],",",
varcol[col],",num.features = dim(trainset[,-1])[[2]])")
eval(parse(text = command))
}
}
This is the KSI matrix
colnames(mat)=c("ANOV","CFRI","CHISQ","GRT","INFG","ONER","RFI","REL","SYMU","SRC","VAR","UMS")
rownames(mat)=c("ANOV","CFRI","CHISQ","GRT","INFG","ONER","RFI","REL","SYMU","SRC","VAR","UMS")
print(mat)
## ANOV CFRI CHISQ GRT INFG ONER
## ANOV 1.0000000 0.9494253 0.7471264 0.7471264 0.7471264 0.5448276
## CFRI 0.9494253 1.0000000 0.7471264 0.7471264 0.7471264 0.5448276
## CHISQ 0.7471264 0.7471264 1.0000000 1.0000000 1.0000000 0.7977011
## GRT 0.7471264 0.7471264 1.0000000 1.0000000 1.0000000 0.7977011
## INFG 0.7471264 0.7471264 1.0000000 1.0000000 1.0000000 0.7977011
## ONER 0.5448276 0.5448276 0.7977011 0.7977011 0.7977011 1.0000000
## RFI 0.7471264 0.7471264 0.7471264 0.7471264 0.7471264 0.5448276
## REL 0.7977011 0.7977011 0.6965517 0.6965517 0.6965517 0.5954023
## SYMU 0.7471264 0.7471264 1.0000000 1.0000000 1.0000000 0.7977011
## SRC 0.7977011 0.8482759 0.7977011 0.7977011 0.7977011 0.6459770
## VAR 0.6965517 0.6965517 0.6459770 0.6459770 0.6459770 0.5448276
## UMS 0.6459770 0.6965517 0.6459770 0.6459770 0.6459770 0.5448276
## RFI REL SYMU SRC VAR UMS
## ANOV 0.7471264 0.7977011 0.7471264 0.7977011 0.6965517 0.6459770
## CFRI 0.7471264 0.7977011 0.7471264 0.8482759 0.6965517 0.6965517
## CHISQ 0.7471264 0.6965517 1.0000000 0.7977011 0.6459770 0.6459770
## GRT 0.7471264 0.6965517 1.0000000 0.7977011 0.6459770 0.6459770
## INFG 0.7471264 0.6965517 1.0000000 0.7977011 0.6459770 0.6459770
## ONER 0.5448276 0.5954023 0.7977011 0.6459770 0.5448276 0.5448276
## RFI 1.0000000 0.6459770 0.7471264 0.7977011 0.7471264 0.7471264
## REL 0.6459770 1.0000000 0.6965517 0.7471264 0.6459770 0.6459770
## SYMU 0.7471264 0.6965517 1.0000000 0.7977011 0.6459770 0.6459770
## SRC 0.7977011 0.7471264 0.7977011 1.0000000 0.6459770 0.7471264
## VAR 0.7471264 0.6459770 0.6459770 0.6459770 1.0000000 0.5448276
## UMS 0.7471264 0.6459770 0.6459770 0.7471264 0.5448276 1.0000000
Based on this consistency matrix, we can develop some graphs to visualise the coherence level between 12 feaure filtering methods:
mat%>%corrplot(.,order="hclust",type="lower",method="pie",tl.col="black", tl.srt=45,tl.cex=0.5,col=(brewer.pal(n=9, name="Reds")))

diag(mat)<-0
m=mat
df=data.frame(row=rownames(m)[row(m)[upper.tri(m)]],
col=colnames(m)[col(m)[upper.tri(m)]],
corr=m[upper.tri(m)])
df$Coherent=df$corr>=0.8
names(df)=c("from","to","corr","coherent")
cdf=subset(df,abs(corr)>=0.8)
graph1<-graph_from_data_frame(df)
graph2<-graph_from_data_frame(cdf)
ggraph(graph2, layout = 'linear',circular=T)+geom_edge_arc2(aes(colour = corr,width=corr),alpha=0.3)+geom_node_label(aes(label = name))+coord_fixed()+scale_edge_color_gradient(high="#ff003f",low="#0094ff")+scale_edge_width_continuous()+theme_bare()

ggraph(graph1, layout = 'fr')+geom_edge_density(aes(fill = coherent))+geom_edge_link(alpha=0.6)+geom_node_label(aes(label = name))+theme_bare()+coord_fixed()+scale_edge_fill_manual(values=c("#0094ff","#ff003f"))

The network revealed that the coherence level was highest among methods that based on Decision tree or Random Forest algorithms; consistency level was lower between OneR or ANOVA. The methods that shared a high value of KSI could be considered equivalent, as they would result the same features subset. Note that such result could change for each iteration, as the Random forest and permutation algorithms are random.
EVALUATING THE PERFORMANCE OF 12 LOGISTIC MODELS
The last step consists of fitting 12 logistic models that based on 12 filtered feature subsets, then evaluating the performance of each model. A good feature subset would results accurate model, as evaluated by AUC, Balanced accuracy, F1 score and balanced error rate
set.seed(123)
res <- NULL
for (i in 1:12){
train=trainset[,c(eval(parse(text = varrow[i])),"Class")]
test=testset[,c(eval(parse(text = varrow[i])),"Class")]
#Logistic regression
train.task=makeClassifTask(data=train, target="Class")
test.task =makeClassifTask(data=test, target="Class")
# Build Model
learner_logis=makeLearner("classif.logreg", predict.type="prob")
model=mlr::train(learner_logis, train.task)
# Prediction and Evaluation
pred=predict(model, test.task)
mes=list(mlr::auc,mlr::bac,mlr::ber,mlr::f1)
perf=performance(pred,measures=mes)
auc=perf[[1]]
bac=perf[[2]]
ber=perf[[3]]
f1=perf[[4]]
line=data.frame(FSmethod = i,AUC=auc,BAC=bac,BER=ber,F1=f1)
res[[i]] <- line
}
Here is the verdict:
res <- do.call(rbind,res)
res$FSmethod <- c("ANOV","CFRI","CHISQ","GRT","INFG","ONER","RFI","REL","SYMU","SRC","VAR","UMS")
res
res%>%gather(AUC:F1,key="Metric",value="Value")%>%ggplot()+geom_tile(aes(x=reorder(Metric,Value),y=reorder(FSmethod,Value),fill=Value),color="black")+geom_text(aes(x=reorder(Metric,Value),y=reorder(FSmethod,Value),label=round(Value,4)),color="black")+scale_fill_distiller(palette = "Spectral")+theme_bw()+scale_x_discrete("Metrics")+scale_y_discrete("FS methods")

res%>%gather(AUC:F1,key="Metric",value="Value")%>%ggplot()+geom_point(shape=21,color="black",size=3,aes(x=reorder(FSmethod,Value),y=Value,fill=Metric),color="black")+scale_fill_manual(values=myfillcolors)+theme_bw()+scale_x_discrete("FSmethod")+scale_y_continuous("Values")+facet_wrap(~Metric,scales="free")+coord_flip()

Based on those result, Random Forest based importance (RFI) based method were the most powerful methods for feature selection.
V(graph1)$AUC=res$AUC
ggraph(graph1, layout = 'linear',circular=T)+geom_edge_fan(alpha=0.1,aes(colour = corr,width=corr))+geom_node_point(shape=21,aes(size=AUC,fill=AUC))+geom_node_text(aes(label = name),nudge_x=,nudge_y=0.1)+theme_bare()+coord_fixed()+scale_edge_color_gradient(low="#0094ff",high="#ff003f")+scale_fill_gradient(high="red",low="gold")

V(graph1)$BAC=res$BAC
ggraph(graph1, layout = 'linear',circular=T)+geom_edge_fan(alpha=0.1,aes(colour = corr,width=corr))+geom_node_point(shape=21,aes(size=BAC,fill=BAC))+geom_node_text(aes(label = name),nudge_x=,nudge_y=0.1)+theme_bare()+coord_fixed()+scale_edge_color_gradient(low="#0094ff",high="#ff003f")+scale_fill_gradient(high="red",low="gold")

The network analysis that combines KSI and AUC/Accuracy values showed that the coherence level as measured by KSI is NOT associated to the performance as measured by AUC or BAC. This means the methods that have the same KSI value could have an equivalent performance (as they generate the same feature subset), but the KSI is not a measure of goodness of filter method, as a method with low consistency in regard to other methods could become a better one.
Conclusion
In thiscase study, we introduced different methods for filtering the features. The consistency level among features subsets could be evaluated by the Kuncheva stability index. However, this index is not a measure of the performance for each method. The Random Forest based criteria, including the variable importance, information gain and Gain ratio are very useful for selecting features.
In a next tutorial, we will discuss about the methods that modify our models instead of input data, i.e Model selection.
Reference: https://pdfs.semanticscholar.org/9c4f/006d5547cddecef588e66ab6b33e6845b33a.pdf
Contribution:
This joint tutorial was created by Viet Tran and Nam Le Dong as a part of MLM project
Viet Tran designed the data experiment. He is also the author of most functions and loops for Feature selection and Model performance evaluation.
Nam Le Dong handled the graphs, network analysis and Rmarkdown writing.
See you in the next tutorial and thank for joining us !
END
LS0tDQp0aXRsZTogIk1MTSBDYXNlIHN0dWR5IFgxMyINCnN1YnRpdGxlOiAiRmVhdHVyZSBzZWxlY3Rpb24iDQphdXRob3I6ICJWaWV0IFRyYW4sIE5hbSBMZSBEb25nIg0KZGF0ZTogIjE4IEp1bmUsIDIwMTciDQpvdXRwdXQ6IA0KICBodG1sX2RvY3VtZW50OiANCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlDQogICAgY29kZV9mb2xkaW5nOiBoaWRlDQogICAgbnVtYmVyX3NlY3Rpb25zOiB5ZXMNCiAgICB0aGVtZTogam91cm5hbA0KICAgIGRmX3ByaW50OiBwYWdlZA0KICAgIHRvYzogVFJVRQ0KICAgIHRvY19mbG9hdDogVFJVRQ0KLS0tDQoNCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQ0KDQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUsIGVycm9yID0gRkFMU0UsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFKQ0KDQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkoaWdyYXBoKQ0KbGlicmFyeShnZ3JhcGgpDQpsaWJyYXJ5KGNvcnJwbG90KQ0KbGlicmFyeShSQ29sb3JCcmV3ZXIpDQpsaWJyYXJ5KEZTZWxlY3RvcikNCmxpYnJhcnkobWxyKQ0KbGlicmFyeShyYW5kb21Gb3Jlc3RTUkMpDQpsaWJyYXJ5KHJKYXZhKQ0KbGlicmFyeShNTG1ldHJpY3MpDQpsaWJyYXJ5KCJPbWljc01hcmtlUiIpDQpgYGANCg0KIVtdKGZlYXR1cmVzZWxlY3Rpb24ucG5nKQ0KDQojIEZvcmV3b3JkOiBBYm91dCB0aGUgTWFjaGluZSBMZWFybmluZyBpbiBNZWRpY2luZSAoTUxNKSBwcm9qZWN0DQoNClRoZSBNTE0gcHJvamVjdCBoYXMgYmVlbiBpbml0aWFsaXplZCBpbiAyMDE2IGFuZCBhaW1zIHRvOiANCg0KMSkgRW5jb3VyYWdlIHVzaW5nIE1hY2hpbmUgTGVhcm5pbmcgdGVjaG5pcXVlcyBpbiBtZWRpY2FsIHJlc2VhcmNoIGluIFZpZXRuYW0gYW5kDQoNCjIpIFByb21vdGUgdGhlIHVzZSBvZiBSIHN0YXRpc3RpY2FsIHByb2dyYW1taW5nIGxhbmd1YWdlLCBhbiBvcGVuIHNvdXJjZSBhbmQgbGVhZGluZyB0b29sIGZvciBwcmFjdGljaW5nIGRhdGEgc2NpZW5jZS4NCg0KDQojIEludHJvZHVjdGlvbg0KDQpIb3cgbWFueSBmZWF0dXJlcyBhbmQgd2hpY2ggb25lcyBzaG91bGQgYmUgdXNlZCB0byBkZXZlbG9wIG91ciBtb2RlbCA/IFRoaXMgcXVlc3Rpb24gY291bGQgYmUgZGlmZmljdWx0LCBwYXJ0aWN1bGFybHkgd2hlbiB3ZSBoYXZlIGEgdmVyeSBsYXJnZSBkYXRhc2V0IHRoYXQgY29udGFpbnMgaHVuZHJlZCBvciB0aG91c2FuZHMgb2YgdmFyaWFibGVzLiBGZWF0dXJlIHNlbGVjdGlvbiBjYW4gaGVscCB1cyB0byBpZGVudGlmeSB0aGUgbW9zdCByZWxldmFudCBmZWF0dXJlcyBmb3Igb3VyIHByb2JsZW0uDQoNCk5vdGUgdGhhdCBmZWF0dXJlIHNlbGVjdGlvbiBpcyBkaWZmZXJlbnQgZnJvbSBNb2RlbCBzZWxlY3Rpb24sIE1vZGVsIHJlZ3VsYXJpc2F0aW9uIGFuZCBkaW1lbnNpb25hbGl0eSByZWR1Y3Rpb24uIFRoZSBmZWF0dXJlIHNlbGVjdGlvbiBwcm9jZWR1cmUgY29uc2lzdHMgb2Ygc2VsZWN0aW5nIGEgc3Vic2V0IG9mIHJlbGV2YW50IGZlYXR1cmVzIEJFRk9SRSBmaXR0aW5nIHRoZSBtb2RlbHMsIHdoaWxzdCBNb2RlbCBzZWxlY3Rpb24gKEJheWVzaWFuIE1vZGVsIEF2ZXJhZ2luZywgU3RlcHdpc2UuLi4pIG9yIFJlZ3VsYXJpc2F0aW9uIHRlY2huaXF1ZXMgc3VjaCBhcyBib29zdGluZywgTGFzc28gb3IgRWxhc3RpYyBuZXQgaW1wbHkgbW9kaWZpY2F0aW9ucyBvbiB0aGUgbW9kZWwgaXRzZWxmLiBUaGUgZGltZW5zaW9uYWxpdHkgcmVkdWN0aW9uIHRlY2huaXF1ZXMgbGlrZSBQQ0EgYWltcyB0byBnZW5lcmF0ZSBuZXcgY29tYmluYXRpb25zIGZyb20gYWxsIGZlYXR1cmVzIGluc3RlYWQgb2YgZXhjbHVkaW5nIHNvbWUgb2YgdGhlbS4NCg0KIyBPYmplY3RpdmUNCg0KSW4gdGhpcyBjYXNlIHN0dWR5IFgxMywgd2Ugd2lsbCBleHBsb3JlIDEyIHNpbXBsZSBmaWx0ZXIgbWV0aG9kcyBvZiBmZWF0dXJlIHNlbGVjdGlvbiB0aGF0IGFyZSBzdXBvcnRlZCBieSB0aGUgbWxyIHBhY2thZ2UuIA0KDQo8aHR0cDovL21sci1vcmcuZ2l0aHViLmlvL21sci10dXRvcmlhbC9kZXZlbC9odG1sL2ZpbHRlcl9tZXRob2RzL2luZGV4Lmh0bWw+DQoNCkZpbHRlciBtZXRob2RzIGNvbnNpc3Qgb2YgZXN0aW1hdGluZyBhIHN0YXRpc3RpY2FsIG1lYXN1cmUgdG8gYXNzaWduIGEgc2NvcmluZyB0byBlYWNoIGZlYXR1cmUuIFRoZSBoaWdoIHJhbmtlZCBmZWF0dXJlcyB3aWxsIGJlIGNvbnNpZGVyZWQgbW9yZSByZWxldmFudCBhbmQgdGhlIG9uZXMgd2l0aCBsb3cgcmFuayBjb3VsZCBiZSBleGNsdWRlZC4gRGlmZmVyZW50IHN0YXRpc3RpY2FsIG1ldGhvZHMgY291bGQgYmUgYXBwbGllZCwgaW5jbHVkaW5nIHRoZSBzaW1wbGVzdCBvbmVzIGxpa2UgQ2hpLXNxdWFyZSB0ZXN0LCBBTk9WQSBvciBLcnVza2FsLVdhbGxpcyB0ZXN0IG9yIHRoZSBzY29yZXMgdGhhdCBiYXNlZCBvbiByZWFsIG1hY2hpbmUgbGVhcm5pbmcgYWxnb3JpdGhtcyBsaWtlIEluZm9ybWF0aW9uIGdhaW4sIGdhaW4gcmF0aW8sIFJhbmRvbSBGb3Jlc3QgYmFzZWQgdmFyaWFibGUncyBpbXBvcnRhbmNlLiBUaG9zZSBtZXRob2RzIGFyZSBvZnRlbiB1bml2YXJpYXRlIGFuZCBjb25zaWRlciB0aGUgZmVhdHVyZSBpbmRlcGVuZGVudGx5LCBvciB3aXRoIHJlZ2FyZCB0byB0aGUgZGVwZW5kZW50IHZhcmlhYmxlLg0KDQpXZSBhbHNvIGludHJvZHVjZSB0aGUgS3VuY2hldmEgc3RhYmlsaXR5IGluZGV4IChLU0kpIGZvciBkZXRlcm1pbmluZyB0aGUgYmVzdCBmZWF0dXJlIHN1YnNldChzKSBmb3IgYSBiaW5hcnkgY2xhc3NpZmljYXRpb24gcHJvYmxlbS4NCg0KIyBNYXRlcmlhbHMgYW5kIG1ldGhvZA0KDQpPdXIgZXhwZXJpbWVudCBpbXBsaWVzIHRoZSBTUEVDVEYgSGVhcnQgRGF0YSBTZXQsIGFzIGRlc2NyaWJlZCBvbiB0aGUgVUNJIE1MIGRhdGFiYXNlOg0KDQo8aHR0cHM6Ly9hcmNoaXZlLmljcy51Y2kuZWR1L21sL2RhdGFzZXRzL1NQRUNURitIZWFydD4NCg0KVGhlIGRhdGFzZXQgcmVwcmVzZW50cyBhIHRlY2huaXF1ZSBjYWxsZWQgIkNhcmRpYWMgU2luZ2xlIFByb3RvbiBFbWlzc2lvbiBDb21wdXRlZCBUb21vZ3JhcGh5IChTUEVDVCkiLiBUaGUgdHJhaW4gc3Vic2V0IGluY2x1ZGVkIDgwIGltYWdlIHNldHMgYW5kIHRoZSB0ZXN0IHN1YnNldCBpbmNsdWRlZCAxODcgaW1hZ2Ugc2V0cy4gRWFjaCBvZiB0aGUgcGF0aWVudHMgY2FuIGJlIGNsYXNzaWZpZWQgYXMgbm9ybWFsIGFuZCBhYm5vcm1hbC4gDQoNClRoZSBkYXRhIG9mIHRoZXNlIDI2NyBTUEVDVCBpbWFnZSBzZXRzIChwYXRpZW50cykgd2VyZSBwcm9jZXNzZWQgdG8gZXh0cmFjdCBmZWF0dXJlcyB0aGF0IHN1bW1hcml6ZSB0aGUgb3JpZ2luYWwgU1BFQ1QgaW1hZ2VzLiBBcyBhIHJlc3VsdCwgNDQgY29udGludW91cyBmZWF0dXJlIHBhdHRlcm4gd2FzIGNyZWF0ZWQgZm9yIGVhY2ggcGF0aWVudC5DbGFzc2lmaWNhdGlvbiBydWxlIHNob3VsZCBiZSBkZXZlbG9wZWQgZnJvbSB0aGVzZSA0NCBwYXR0ZXJucy4gDQpBc3N1bWluZyB0aGF0IHdlIGFscmVhZHkgZGVjaWRlZCB0byBhcHBseSBMb2dpc3RpYyBtb2RlbCBmb3IgY29uc3RydWN0aW5nIG91ciBjbGFzc2lmaWVyLCBhbmQgd2Ugd291bGQgbGlrZSB0byB1c2Ugb25seSAxNSBmZWF0dXJlcyBpbnN0ZWFkIG9mIDQ0LiBPdXIgZ29hbCBpcyB0byBpZGVudGlmeSB0aGUgbW9zdCB1c2VmdWwgc3Vic2V0IG9mIDEyIGZlYXR1cmVzIGZyb20gdGhlIG9yaWdpbmFsIGRhdGFzZXQuDQoNCiMgRGF0YSBwcmVwYXJhdGlvbg0KDQpGaXJzdCwgd2Ugd2lsbCBwcmVwYXJlIHRoZSBnZ3Bsb3QgdGhlbWUgZm9yIG91ciBleHBlcmltZW50DQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KDQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCg0KdGhlbWVfYmFyZSA8LSBmdW5jdGlvbihiYXNlX3NpemU9OCxiYXNlX2ZhbWlseT0ic2FucyIpe3RoZW1lX2J3KGJhc2Vfc2l6ZSA9IGJhc2Vfc2l6ZSwgYmFzZV9mYW1pbHkgPSBiYXNlX2ZhbWlseSkrDQogIHRoZW1lKA0KICBheGlzLmxpbmUgPSBlbGVtZW50X2JsYW5rKCksIA0KICBheGlzLnRleHQueCA9IGVsZW1lbnRfYmxhbmsoKSwgDQogIGF4aXMudGV4dC55ID0gZWxlbWVudF9ibGFuaygpLA0KICBheGlzLnRpY2tzID0gZWxlbWVudF9ibGFuaygpLCANCiAgYXhpcy50aXRsZS54ID0gZWxlbWVudF9ibGFuaygpLCANCiAgYXhpcy50aXRsZS55ID0gZWxlbWVudF9ibGFuaygpLCANCiAgbGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIsIA0KICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9yZWN0KGZpbGwgPSBOQSksIA0KICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCksIA0KICBwYW5lbC5ncmlkLm1ham9yID0gZWxlbWVudF9ibGFuaygpLCANCiAgcGFuZWwuZ3JpZC5taW5vciA9IGVsZW1lbnRfYmxhbmsoKSwgDQogIHBsb3QubWFyZ2luID0gdW5pdChjKDAsMCwwLDApLCAibGluZXMiKQ0KKQ0KfQ0KDQoNCg0KbXlfdGhlbWUgPC0gZnVuY3Rpb24oYmFzZV9zaXplID01LCBiYXNlX2ZhbWlseSA9ICJzYW5zIil7DQogIHRoZW1lX2J3KGJhc2Vfc2l6ZSA9IGJhc2Vfc2l6ZSwgYmFzZV9mYW1pbHkgPSBiYXNlX2ZhbWlseSkgKw0KICAgIHRoZW1lKA0KICAgICAgcGFuZWwuZ3JpZC5tYWpvciA9IGVsZW1lbnRfbGluZShjb2xvciA9ICJncmF5IiksDQogICAgICBwYW5lbC5ncmlkLm1pbm9yID0gZWxlbWVudF9ibGFuaygpLA0KICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gTkEpLA0KICAgICAgc3RyaXAuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gIiMwMDFkNjAiLCBjb2xvciA9ICIjMDAxMTNhIiwgc2l6ZSA9MC41KSwNCiAgICAgIHN0cmlwLnRleHQgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiwgc2l6ZSA9IDUsIGNvbG9yID0gIndoaXRlIiksDQogICAgICBsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIiwNCiAgICAgIGxlZ2VuZC5qdXN0aWZpY2F0aW9uID0gImNlbnRlciIsDQogICAgICBsZWdlbmQuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwNCiAgICAgIGxlZ2VuZC5tYXJnaW4gPSBtYXJnaW4oMC41LDAuNSwwLjUsMC41KQ0KICAgICkNCn0NCg0KdGhlbWVfc2V0KG15X3RoZW1lKCkpDQoNCm15ZmlsbGNvbG9ycz1jKCIjZmYwMDNmIiwiIzAwOTRmZiIsICIjYWUwMGZmIiAsICIjOTRmZjAwIiwgIiNmZmM3MDAiLCIjZmMxODE0IikNCg0KDQpgYGANCg0KRmlyc3QsIHdlIGxvYWQgdGhlIGRhdGEgdG8gUjoNCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQp0ZXN0c2V0PXJlYWQudGFibGUoImh0dHBzOi8vYXJjaGl2ZS5pY3MudWNpLmVkdS9tbC9tYWNoaW5lLWxlYXJuaW5nLWRhdGFiYXNlcy9zcGVjdC9TUEVDVEYudGVzdCIsc2VwID0gIiwiKSU+JWFzX3RpYmJsZSgpDQp0cmFpbnNldD1yZWFkLnRhYmxlKCJodHRwczovL2FyY2hpdmUuaWNzLnVjaS5lZHUvbWwvbWFjaGluZS1sZWFybmluZy1kYXRhYmFzZXMvc3BlY3QvU1BFQ1RGLnRyYWluIixzZXAgPSAiLCIpJT4lYXNfdGliYmxlKCkNCg0KdGVzdHNldCRWMTwtZmFjdG9yKHRlc3RzZXQkVjEsbGV2ZWxzID0gYygxLDApLCBsYWJlbHMgPSBjKCJQb3NpdGl2ZSIsICJOZWdhdGl2ZSIpICkNCnRyYWluc2V0JFYxPC1mYWN0b3IodHJhaW5zZXQkVjEsbGV2ZWxzID0gYygxLDApLCBsYWJlbHMgPSBjKCJQb3NpdGl2ZSIsICJOZWdhdGl2ZSIpICkNCg0KbmFtZXModGVzdHNldCk9YygiQ2xhc3MiLHBhc3RlKHJlcCgiRiIsdGltZXM9NDQpLGMoMTo0NCksc2VwPSIiKSApDQpuYW1lcyh0cmFpbnNldCk9YygiQ2xhc3MiLHBhc3RlKHJlcCgiRiIsdGltZXM9NDQpLGMoMTo0NCksc2VwPSIiKSApDQoNCmBgYA0KDQojIERhdGEgdmlzdWFsaXNpbmcNCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHJlc3VsdHM9ImhpZGUiLGZpZy5zaG93ID0iaGlkZSJ9DQoNCnRyYWluc2V0JT4lZ2F0aGVyKEYxOkY0NCxrZXk9IkZlYXR1cmVzIix2YWx1ZT0iVmFsdWUiKSU+JWdncGxvdChhZXMoeD1WYWx1ZSxmaWxsPUNsYXNzKSkrZ2VvbV9kZW5zaXR5KGFscGhhPTAuNSkrZmFjZXRfd3JhcCh+RmVhdHVyZXMsc2NhbGVzPSJmcmVlIixuY29sPTQpK3NjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcz1teWZpbGxjb2xvcnMpDQoNCnRyYWluc2V0JT4lZ2F0aGVyKEYxOkY0NCxrZXk9IkZlYXR1cmVzIix2YWx1ZT0iVmFsdWUiKSU+JWdncGxvdChhZXMoeD1DbGFzcyx5PVZhbHVlLGZpbGw9Q2xhc3MpKStnZW9tX2JveHBsb3QoYWxwaGE9MC43KStmYWNldF93cmFwKH5GZWF0dXJlcyxzY2FsZXM9ImZyZWUiLG5jb2w9NCkrc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzPW15ZmlsbGNvbG9ycykrY29vcmRfZmxpcCgpDQoNCmBgYA0KDQohW10oRlMxLnBuZykNCg0KIVtdKEZTMi5wbmcpDQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KbGlicmFyeShjb3JycGxvdCkNCmxpYnJhcnkoUkNvbG9yQnJld2VyKQ0KDQpjb3JfbWF0PWFzLm1hdHJpeChjb3IoYXMubWF0cml4KHRyYWluc2V0WywtMV0pKSkNCg0KY29yX21hdCU+JWNvcnJwbG90KC4sb3JkZXI9ImhjbHVzdCIsdHlwZT0ibG93ZXIiLG1ldGhvZD0iZWxsaXBzZSIsdGwuY29sPSJibGFjayIsIHRsLnNydD00NSx0bC5jZXg9MC41LGNvbD1yZXYoYnJld2VyLnBhbChuPTExLCBuYW1lPSJSZEJ1IikpKQ0KDQpgYGANCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0UscmVzdWx0cz0iaGlkZSIsZmlnLnNob3cgPSJoaWRlIn0NCmNvcl9tYXQ9YXMubWF0cml4KGNvcihhcy5tYXRyaXgodHJhaW5zZXRbLC0xXSkpKQ0KDQpsaWJyYXJ5KGlncmFwaCkNCg0KZGlhZyhjb3JfbWF0KTwtMA0KDQpsaWJyYXJ5KGdncmFwaCkNCg0KbT1jb3JfbWF0DQoNCmRmPWRhdGEuZnJhbWUocm93PXJvd25hbWVzKG0pW3JvdyhtKVt1cHBlci50cmkobSldXSwgDQogICAgICAgICAgIGNvbD1jb2xuYW1lcyhtKVtjb2wobSlbdXBwZXIudHJpKG0pXV0sIA0KICAgICAgICAgICBjb3JyPW1bdXBwZXIudHJpKG0pXSkNCg0KY2RmPXN1YnNldChkZixhYnMoY29ycik+MC41KQ0KDQpuYW1lcyhjZGYpPWMoImZyb20iLCJ0byIsImNvcnIiKQ0KDQpncmFwaDwtZ3JhcGhfZnJvbV9kYXRhX2ZyYW1lKGNkZikNCg0KZ2dyYXBoKGdyYXBoLCBsYXlvdXQgPSAnbGluZWFyJyxjaXJjdWxhcj1UKStnZW9tX2VkZ2VfYXJjKGFlcyhjb2xvdXIgPSBjb3JyLHdpZHRoPWNvcnIpLGFscGhhPTAuMykrZ2VvbV9ub2RlX2xhYmVsKGFlcyhsYWJlbCA9IG5hbWUpKStjb29yZF9maXhlZCgpK3NjYWxlX2VkZ2VfY29sb3JfZ3JhZGllbnQoaGlnaD0iI2ZmMDAzZiIsbG93PSIjMDA5NGZmIikrc2NhbGVfZWRnZV93aWR0aF9jb250aW51b3VzKCkrdGhlbWVfYmFyZSgpDQoNCmBgYA0KDQohW10oRlMzLnBuZykNCg0KIyBGZWF0dXJlIHNlbGVjdGlvbiBieSAxMiBGaWx0ZXIgbWV0aG9kcw0KDQpXZSBpbnRyb2R1Y2UgYSBmdW5jdGlvbiBjYWxsZWQgInZpbXBsb3QiLiBUaGlzIGZ1bmN0aW9uIHdpbGwgcGVyZm9ybSBhIGZpbHRlciBtZXRob2Qgb2YgaW50ZXJlc3QgdGhlbiBleHRyYWN0IHRoZSBzdWJzZXQgb2YgZmVhdHVyZXMgYW5kIHBsb3QgdGhlIHJlc3VsdC4NCg0KVGhlbiB3ZSBhcHBseSB0aGUgZnVuY3Rpb24gd2l0aCAxMiBkaWZmZXJlbnQgZmlsdGVyIG1ldGhvZHMgYW5kIHNhdmUgdGhlIG91dHB1dCB3aXRoaW4gdGhlIG9iamVjdHMgZnJvbSBpbXB2YXIxIHRvIGltcHZhcjEyDQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KDQpsaWJyYXJ5KEZTZWxlY3RvcikNCmxpYnJhcnkobWxyKQ0KbGlicmFyeShyYW5kb21Gb3Jlc3RTUkMpDQpsaWJyYXJ5KHJKYXZhKQ0KbGlicmFyeShNTG1ldHJpY3MpDQoNCnZpbXBsb3Q9ZnVuY3Rpb24oZGF0YSx0YXJnZXQsbWV0aG9kKXsNCiAgdHJhaW4udGFzaz1tYWtlQ2xhc3NpZlRhc2soZGF0YT1kYXRhLCB0YXJnZXQ9dGFyZ2V0KQ0KICBpbXBfdmFyPWdlbmVyYXRlRmlsdGVyVmFsdWVzRGF0YSh0cmFpbi50YXNrLCBtZXRob2Q9bWV0aG9kKQ0KICBmc2RmPWltcF92YXIkZGF0YSU+JS5bLC0yXQ0KICBuYW1lcyhmc2RmKT1jKCJGZWF0dXJlIiwiVmFsdWUiKQ0KICBmc2RmJE1ldGhvZD1tZXRob2QNCiAgZnNkZiRGZWF0dXJlDQogIHZpbXA9ZnNkZiU+JWFycmFuZ2UoZGVzYyhWYWx1ZSkpJT4lIHNlbGVjdChGZWF0dXJlKQ0KICBwbG90PWZzZGYlPiVnZ3Bsb3QoYWVzKHg9cmVvcmRlcihmc2RmJEZlYXR1cmUsZnNkZiRWYWx1ZSkseT1WYWx1ZSxmaWxsPVZhbHVlLGNvbG9yPVZhbHVlKSkrZ2VvbV9zZWdtZW50KGFlcyh4PXJlb3JkZXIoZnNkZiRGZWF0dXJlLGZzZGYkVmFsdWUpLHhlbmQ9RmVhdHVyZSx5PTAseWVuZD1WYWx1ZSksc2l6ZT0xLHNob3cubGVnZW5kID0gRikrZ2VvbV9wb2ludChzaXplPTIsc2hvdy5sZWdlbmQgPSBGKStzY2FsZV94X2Rpc2NyZXRlKCJGZWF0dXJlcyIpK3NjYWxlX3lfY29udGludW91cyhtZXRob2QpK2Nvb3JkX2ZsaXAoKStzY2FsZV9maWxsX2dyYWRpZW50KGxvdz0iYmx1ZSIsaGlnaD0icmVkIikrc2NhbGVfY29sb3JfZ3JhZGllbnQobG93PSJibHVlIixoaWdoPSJyZWQiKStnZ3RpdGxlKG1ldGhvZCkNCiAgcmV0dXJuKGxpc3QocGxvdD1wbG90LG5hbWU9dmltcCkpDQogIH0NCg0KI0FOT1ZBIFRFU1QNCg0KcmVzPXZpbXBsb3QoZGF0YT10cmFpbnNldCx0YXJnZXQ9IkNsYXNzIixtZXRob2Q9ImFub3ZhLnRlc3QiKQ0KDQppbXB2YXIxIDwtcmVzJG5hbWUNCg0KcmVzJHBsb3QNCg0KIyBjZm9yZXN0LmltcG9ydGFuY2UNCg0KcmVzPXZpbXBsb3QoZGF0YT10cmFpbnNldCx0YXJnZXQ9IkNsYXNzIixtZXRob2Q9ImNmb3Jlc3QuaW1wb3J0YW5jZSIpDQoNCmltcHZhcjIgPC1yZXMkbmFtZQ0KDQpyZXMkcGxvdA0KDQojY2hpLnNxdWFyZWQNCg0KcmVzPXZpbXBsb3QoZGF0YT10cmFpbnNldCx0YXJnZXQ9IkNsYXNzIixtZXRob2Q9ImNoaS5zcXVhcmVkIikNCg0KaW1wdmFyMyA8LXJlcyRuYW1lDQoNCnJlcyRwbG90DQoNCiNnYWluLnJhdGlvDQoNCnJlcz12aW1wbG90KGRhdGE9dHJhaW5zZXQsdGFyZ2V0PSJDbGFzcyIsbWV0aG9kPSJnYWluLnJhdGlvIikNCg0KaW1wdmFyNCA8LXJlcyRuYW1lDQoNCnJlcyRwbG90DQoNCiNpbmZvcm1hdGlvbi5nYWluDQoNCnJlcz12aW1wbG90KGRhdGE9dHJhaW5zZXQsdGFyZ2V0PSJDbGFzcyIsbWV0aG9kPSJpbmZvcm1hdGlvbi5nYWluIikNCg0KaW1wdmFyNSA8LXJlcyRuYW1lDQoNCnJlcyRwbG90DQoNCiNPbmVSDQoNCnJlcz12aW1wbG90KGRhdGE9dHJhaW5zZXQsdGFyZ2V0PSJDbGFzcyIsbWV0aG9kPSJvbmVSIikNCg0KaW1wdmFyNiA8LXJlcyRuYW1lDQoNCnJlcyRwbG90DQoNCiNyYW5kb21Gb3Jlc3QuaW1wb3J0YW5jZQ0KDQpyZXM9dmltcGxvdChkYXRhPXRyYWluc2V0LHRhcmdldD0iQ2xhc3MiLG1ldGhvZD0icmFuZG9tRm9yZXN0LmltcG9ydGFuY2UiKQ0KDQppbXB2YXI3IDwtcmVzJG5hbWUNCg0KcmVzJHBsb3QNCg0KI3JlbGllZg0KDQpyZXM9dmltcGxvdChkYXRhPXRyYWluc2V0LHRhcmdldD0iQ2xhc3MiLG1ldGhvZD0icmVsaWVmIikNCg0KaW1wdmFyOCA8LXJlcyRuYW1lDQoNCnJlcyRwbG90DQoNCiNzeW1tZXRyaWNhbC51bmNlcnRhaW50eQ0KDQpyZXM9dmltcGxvdChkYXRhPXRyYWluc2V0LHRhcmdldD0iQ2xhc3MiLG1ldGhvZD0ic3ltbWV0cmljYWwudW5jZXJ0YWludHkiKQ0KDQppbXB2YXI5IDwtcmVzJG5hbWUNCg0KcmVzJHBsb3QNCg0KIyByYW5kb21Gb3Jlc3RTUkMucmZzcmMNCg0KcmVzPXZpbXBsb3QoZGF0YT10cmFpbnNldCx0YXJnZXQ9IkNsYXNzIixtZXRob2Q9InJhbmRvbUZvcmVzdFNSQy5yZnNyYyIpDQoNCmltcHZhcjEwIDwtcmVzJG5hbWUNCg0KcmVzJHBsb3QNCg0KIyB2YXJpYW5jZQ0KDQpyZXM9dmltcGxvdChkYXRhPXRyYWluc2V0LHRhcmdldD0iQ2xhc3MiLG1ldGhvZD0idmFyaWFuY2UiKQ0KDQppbXB2YXIxMTwtcmVzJG5hbWUNCg0KcmVzJHBsb3QNCg0KI3VuaXZhcmlhdGUubW9kZWwuc2NvcmUgKHJwYXJ0KQ0KDQpyZXM9dmltcGxvdChkYXRhPXRyYWluc2V0LHRhcmdldD0iQ2xhc3MiLG1ldGhvZD0idW5pdmFyaWF0ZS5tb2RlbC5zY29yZSIpDQoNCmltcHZhcjEyPC1yZXMkbmFtZQ0KDQpyZXMkcGxvdA0KDQpgYGANCg0KIyBLdW5jaGV2YSBzdGFiaWxpdHkgaW5kZXgNCg0KVGhlIEt1bmNoZXZhIHN0YWJpbGl0eSBpbmRleCBpcyBzdXBwb3J0ZWQgYnkgT21pY3NNYXJrZVIgcGFjYWtnZSB0aGF0IGNvdWxkIGJlIGluc3RhbGxlZCBmcm9tIEJpb2NvbmR1Y3RvciBwbGF0Zm9ybToNCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQoNCiNzb3VyY2UoImh0dHBzOi8vYmlvY29uZHVjdG9yLm9yZy9iaW9jTGl0ZS5SIikNCiNiaW9jTGl0ZSgiT21pY3NNYXJrZVIiKQ0KYGBgDQoNCkZpcnN0LCB3ZSB3aWxsIGxvYWQgdGhlIHBhY2thZ2UsIHRoZW4gYXBwbHkgYSBsb29wIG9uIG91ciAxMiBvdXRwdXQgb2JqZWN0cyBvZiBmaWx0ZXIgbWV0aG9kcyAoVjEgdG8gVjEyKS4gQXMgbWVudGlvbmVkIGFib3ZlLCB3ZSB3b3VsZCBsaWtlIHRvIHJlZHVjZSB0aGUgbnVtYmVyIG9mIGZlYXR1cmVzIGZyb20gNDQgdG8gb25seSAxNSAodGh1cyBleGNsdWRpbmcgMjkgZmVhdHVyZXMpLiBOb3RlIHRoYXQgdGhlIHN1YnNldHMgb2YgMTUgZmVhdHVyZXMgbWlnaHQgYmUgZGlmZmVyZW50LCBkZXBlbmRpbmcgb24gdGhlIGZpbHRlcmluZyBtZXRob2QgaW4gdXNlLiANCg0KVGhlIEt1bmNoZXZhIHN0YWJpbGl0eSBpbmRleCBhaW1zIHRvIG1lYXN1cmUgdGhlIGNvbnNpc3RlbmN5IGxldmVsIGJldHdlZW4gdGhvc2UgMTIgZmVhdHVyZSBzdWJzZXRzLiBUaGUgcmVzdWx0IGxvb2tzIHNpbWlsYXIgdG8gYSBjb3JyZWxhdGlvbiBtYXRyaXguIA0KDQpgYGB7cixtZXNzYWdlID0gRkFMU0Usd2FybmluZz1GQUxTRX0NCmxpYnJhcnkoIk9taWNzTWFya2VSIikNCg0KdjE9aW1wdmFyMSRGZWF0dXJlWzE6MTVdDQp2Mj1pbXB2YXIyJEZlYXR1cmVbMToxNV0NCnYzPWltcHZhcjMkRmVhdHVyZVsxOjE1XQ0KdjQ9aW1wdmFyNCRGZWF0dXJlWzE6MTVdDQp2NT1pbXB2YXI1JEZlYXR1cmVbMToxNV0NCnY2PWltcHZhcjYkRmVhdHVyZVsxOjE1XQ0Kdjc9aW1wdmFyNyRGZWF0dXJlWzE6MTVdDQp2OD1pbXB2YXI4JEZlYXR1cmVbMToxNV0NCnY5PWltcHZhcjkkRmVhdHVyZVsxOjE1XQ0KdjEwPWltcHZhcjEwJEZlYXR1cmVbMToxNV0NCnYxMT1pbXB2YXIxMSRGZWF0dXJlWzE6MTVdDQp2MTI9aW1wdmFyMTIkRmVhdHVyZVsxOjE1XQ0KDQptYXQgPC0gbWF0cml4KE5BLDEyLDEyKQ0KdmFyY29sIDwtIHBhc3RlMCgidiIsc2VxKDEsMTIsYnkgPSAxKSkNCnZhcnJvdyA8LSBwYXN0ZTAoInYiLHNlcSgxLDEyLGJ5ID0gMSkpDQoNCmZvciAocm93IGluIDE6MTIpew0KICBmb3IoY29sIGluIDE6MTIpew0KICAgIGNvbW1hbmQgPC0gcGFzdGUwKCIgbWF0W3Jvdyxjb2xdIDwtIGt1bmNoZXZhKCIsDQogICAgICAgICAgICAgICAgICAgICAgdmFycm93W3Jvd10sIiwiLA0KICAgICAgICAgICAgICAgICAgICAgIHZhcmNvbFtjb2xdLCIsbnVtLmZlYXR1cmVzID0gZGltKHRyYWluc2V0WywtMV0pW1syXV0pIikNCiAgICANCiAgICBldmFsKHBhcnNlKHRleHQgPSBjb21tYW5kKSkNCiAgfQ0KfQ0KDQpgYGANCg0KVGhpcyBpcyB0aGUgS1NJIG1hdHJpeA0KDQpgYGB7cixtZXNzYWdlID0gRkFMU0Usd2FybmluZz1GQUxTRX0NCg0KY29sbmFtZXMobWF0KT1jKCJBTk9WIiwiQ0ZSSSIsIkNISVNRIiwiR1JUIiwiSU5GRyIsIk9ORVIiLCJSRkkiLCJSRUwiLCJTWU1VIiwiU1JDIiwiVkFSIiwiVU1TIikNCnJvd25hbWVzKG1hdCk9YygiQU5PViIsIkNGUkkiLCJDSElTUSIsIkdSVCIsIklORkciLCJPTkVSIiwiUkZJIiwiUkVMIiwiU1lNVSIsIlNSQyIsIlZBUiIsIlVNUyIpDQoNCnByaW50KG1hdCkNCg0KYGBgDQoNCkJhc2VkIG9uIHRoaXMgY29uc2lzdGVuY3kgbWF0cml4LCB3ZSBjYW4gZGV2ZWxvcCBzb21lIGdyYXBocyB0byB2aXN1YWxpc2UgdGhlIGNvaGVyZW5jZSBsZXZlbCBiZXR3ZWVuIDEyIGZlYXVyZSBmaWx0ZXJpbmcgbWV0aG9kczoNCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQptYXQlPiVjb3JycGxvdCguLG9yZGVyPSJoY2x1c3QiLHR5cGU9Imxvd2VyIixtZXRob2Q9InBpZSIsdGwuY29sPSJibGFjayIsIHRsLnNydD00NSx0bC5jZXg9MC41LGNvbD0oYnJld2VyLnBhbChuPTksIG5hbWU9IlJlZHMiKSkpDQoNCmRpYWcobWF0KTwtMA0KDQptPW1hdA0KDQpkZj1kYXRhLmZyYW1lKHJvdz1yb3duYW1lcyhtKVtyb3cobSlbdXBwZXIudHJpKG0pXV0sIA0KICAgICAgICAgICAgICBjb2w9Y29sbmFtZXMobSlbY29sKG0pW3VwcGVyLnRyaShtKV1dLCANCiAgICAgICAgICAgICAgY29ycj1tW3VwcGVyLnRyaShtKV0pDQoNCmRmJENvaGVyZW50PWRmJGNvcnI+PTAuOA0KbmFtZXMoZGYpPWMoImZyb20iLCJ0byIsImNvcnIiLCJjb2hlcmVudCIpDQoNCmNkZj1zdWJzZXQoZGYsYWJzKGNvcnIpPj0wLjgpDQoNCmdyYXBoMTwtZ3JhcGhfZnJvbV9kYXRhX2ZyYW1lKGRmKQ0KZ3JhcGgyPC1ncmFwaF9mcm9tX2RhdGFfZnJhbWUoY2RmKQ0KDQpnZ3JhcGgoZ3JhcGgyLCBsYXlvdXQgPSAnbGluZWFyJyxjaXJjdWxhcj1UKStnZW9tX2VkZ2VfYXJjMihhZXMoY29sb3VyID0gY29ycix3aWR0aD1jb3JyKSxhbHBoYT0wLjMpK2dlb21fbm9kZV9sYWJlbChhZXMobGFiZWwgPSBuYW1lKSkrY29vcmRfZml4ZWQoKStzY2FsZV9lZGdlX2NvbG9yX2dyYWRpZW50KGhpZ2g9IiNmZjAwM2YiLGxvdz0iIzAwOTRmZiIpK3NjYWxlX2VkZ2Vfd2lkdGhfY29udGludW91cygpK3RoZW1lX2JhcmUoKQ0KDQpnZ3JhcGgoZ3JhcGgxLCBsYXlvdXQgPSAnZnInKStnZW9tX2VkZ2VfZGVuc2l0eShhZXMoZmlsbCA9IGNvaGVyZW50KSkrZ2VvbV9lZGdlX2xpbmsoYWxwaGE9MC42KStnZW9tX25vZGVfbGFiZWwoYWVzKGxhYmVsID0gbmFtZSkpK3RoZW1lX2JhcmUoKStjb29yZF9maXhlZCgpK3NjYWxlX2VkZ2VfZmlsbF9tYW51YWwodmFsdWVzPWMoIiMwMDk0ZmYiLCIjZmYwMDNmIikpDQoNCmBgYA0KDQpUaGUgbmV0d29yayByZXZlYWxlZCB0aGF0IHRoZSBjb2hlcmVuY2UgbGV2ZWwgd2FzIGhpZ2hlc3QgYW1vbmcgbWV0aG9kcyB0aGF0IGJhc2VkIG9uIERlY2lzaW9uIHRyZWUgb3IgUmFuZG9tIEZvcmVzdCBhbGdvcml0aG1zOyBjb25zaXN0ZW5jeSBsZXZlbCB3YXMgbG93ZXIgYmV0d2VlbiBPbmVSIG9yIEFOT1ZBLiBUaGUgbWV0aG9kcyB0aGF0IHNoYXJlZCBhIGhpZ2ggdmFsdWUgb2YgS1NJIGNvdWxkIGJlIGNvbnNpZGVyZWQgZXF1aXZhbGVudCwgYXMgdGhleSB3b3VsZCByZXN1bHQgdGhlIHNhbWUgZmVhdHVyZXMgc3Vic2V0LiBOb3RlIHRoYXQgc3VjaCByZXN1bHQgY291bGQgY2hhbmdlIGZvciBlYWNoIGl0ZXJhdGlvbiwgYXMgdGhlIFJhbmRvbSBmb3Jlc3QgYW5kIHBlcm11dGF0aW9uIGFsZ29yaXRobXMgYXJlIHJhbmRvbS4NCg0KIyBFVkFMVUFUSU5HIFRIRSBQRVJGT1JNQU5DRSBPRiAxMiBMT0dJU1RJQyBNT0RFTFMNCg0KVGhlIGxhc3Qgc3RlcCBjb25zaXN0cyBvZiBmaXR0aW5nIDEyIGxvZ2lzdGljIG1vZGVscyB0aGF0IGJhc2VkIG9uIDEyIGZpbHRlcmVkIGZlYXR1cmUgc3Vic2V0cywgdGhlbiBldmFsdWF0aW5nIHRoZSBwZXJmb3JtYW5jZSBvZiBlYWNoIG1vZGVsLiBBIGdvb2QgZmVhdHVyZSBzdWJzZXQgd291bGQgcmVzdWx0cyBhY2N1cmF0ZSBtb2RlbCwgYXMgZXZhbHVhdGVkIGJ5IEFVQywgQmFsYW5jZWQgYWNjdXJhY3ksIEYxIHNjb3JlIGFuZCBiYWxhbmNlZCBlcnJvciByYXRlDQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0Kc2V0LnNlZWQoMTIzKQ0KDQpyZXMgPC0gTlVMTA0KDQpmb3IgKGkgaW4gMToxMil7DQogIA0KICB0cmFpbj10cmFpbnNldFssYyhldmFsKHBhcnNlKHRleHQgPSB2YXJyb3dbaV0pKSwiQ2xhc3MiKV0NCiAgdGVzdD10ZXN0c2V0WyxjKGV2YWwocGFyc2UodGV4dCA9IHZhcnJvd1tpXSkpLCJDbGFzcyIpXQ0KICANCiAgI0xvZ2lzdGljIHJlZ3Jlc3Npb24NCiAgdHJhaW4udGFzaz1tYWtlQ2xhc3NpZlRhc2soZGF0YT10cmFpbiwgdGFyZ2V0PSJDbGFzcyIpDQogIHRlc3QudGFzayA9bWFrZUNsYXNzaWZUYXNrKGRhdGE9dGVzdCwgdGFyZ2V0PSJDbGFzcyIpDQogIA0KICAjIEJ1aWxkIE1vZGVsDQogIGxlYXJuZXJfbG9naXM9bWFrZUxlYXJuZXIoImNsYXNzaWYubG9ncmVnIiwgcHJlZGljdC50eXBlPSJwcm9iIikNCiAgbW9kZWw9bWxyOjp0cmFpbihsZWFybmVyX2xvZ2lzLCB0cmFpbi50YXNrKQ0KICANCiAgIyBQcmVkaWN0aW9uIGFuZCBFdmFsdWF0aW9uDQogIHByZWQ9cHJlZGljdChtb2RlbCwgdGVzdC50YXNrKQ0KICBtZXM9bGlzdChtbHI6OmF1YyxtbHI6OmJhYyxtbHI6OmJlcixtbHI6OmYxKQ0KICBwZXJmPXBlcmZvcm1hbmNlKHByZWQsbWVhc3VyZXM9bWVzKQ0KICBhdWM9cGVyZltbMV1dDQogIGJhYz1wZXJmW1syXV0NCiAgYmVyPXBlcmZbWzNdXQ0KICBmMT1wZXJmW1s0XV0NCiAgbGluZT1kYXRhLmZyYW1lKEZTbWV0aG9kID0gaSxBVUM9YXVjLEJBQz1iYWMsQkVSPWJlcixGMT1mMSkNCiAgcmVzW1tpXV0gPC0gbGluZQ0KfQ0KDQpgYGANCg0KSGVyZSBpcyB0aGUgdmVyZGljdDogDQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KcmVzIDwtIGRvLmNhbGwocmJpbmQscmVzKQ0KcmVzJEZTbWV0aG9kIDwtIGMoIkFOT1YiLCJDRlJJIiwiQ0hJU1EiLCJHUlQiLCJJTkZHIiwiT05FUiIsIlJGSSIsIlJFTCIsIlNZTVUiLCJTUkMiLCJWQVIiLCJVTVMiKQ0KDQpyZXMNCg0KcmVzJT4lZ2F0aGVyKEFVQzpGMSxrZXk9Ik1ldHJpYyIsdmFsdWU9IlZhbHVlIiklPiVnZ3Bsb3QoKStnZW9tX3RpbGUoYWVzKHg9cmVvcmRlcihNZXRyaWMsVmFsdWUpLHk9cmVvcmRlcihGU21ldGhvZCxWYWx1ZSksZmlsbD1WYWx1ZSksY29sb3I9ImJsYWNrIikrZ2VvbV90ZXh0KGFlcyh4PXJlb3JkZXIoTWV0cmljLFZhbHVlKSx5PXJlb3JkZXIoRlNtZXRob2QsVmFsdWUpLGxhYmVsPXJvdW5kKFZhbHVlLDQpKSxjb2xvcj0iYmxhY2siKStzY2FsZV9maWxsX2Rpc3RpbGxlcihwYWxldHRlID0gIlNwZWN0cmFsIikrdGhlbWVfYncoKStzY2FsZV94X2Rpc2NyZXRlKCJNZXRyaWNzIikrc2NhbGVfeV9kaXNjcmV0ZSgiRlMgbWV0aG9kcyIpDQoNCnJlcyU+JWdhdGhlcihBVUM6RjEsa2V5PSJNZXRyaWMiLHZhbHVlPSJWYWx1ZSIpJT4lZ2dwbG90KCkrZ2VvbV9wb2ludChzaGFwZT0yMSxjb2xvcj0iYmxhY2siLHNpemU9MyxhZXMoeD1yZW9yZGVyKEZTbWV0aG9kLFZhbHVlKSx5PVZhbHVlLGZpbGw9TWV0cmljKSxjb2xvcj0iYmxhY2siKStzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXM9bXlmaWxsY29sb3JzKSt0aGVtZV9idygpK3NjYWxlX3hfZGlzY3JldGUoIkZTbWV0aG9kIikrc2NhbGVfeV9jb250aW51b3VzKCJWYWx1ZXMiKStmYWNldF93cmFwKH5NZXRyaWMsc2NhbGVzPSJmcmVlIikrY29vcmRfZmxpcCgpDQoNCmBgYA0KDQpCYXNlZCBvbiB0aG9zZSByZXN1bHQsIFJhbmRvbSBGb3Jlc3QgYmFzZWQgaW1wb3J0YW5jZSAoUkZJKSBiYXNlZCBtZXRob2Qgd2VyZSB0aGUgbW9zdCBwb3dlcmZ1bCBtZXRob2RzIGZvciBmZWF0dXJlIHNlbGVjdGlvbi4NCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0UscmVzdWx0cz0iaGlkZSJ9DQoNClYoZ3JhcGgxKSRBVUM9cmVzJEFVQw0KDQpnZ3JhcGgoZ3JhcGgxLCBsYXlvdXQgPSAnbGluZWFyJyxjaXJjdWxhcj1UKStnZW9tX2VkZ2VfZmFuKGFscGhhPTAuMSxhZXMoY29sb3VyID0gY29ycix3aWR0aD1jb3JyKSkrZ2VvbV9ub2RlX3BvaW50KHNoYXBlPTIxLGFlcyhzaXplPUFVQyxmaWxsPUFVQykpK2dlb21fbm9kZV90ZXh0KGFlcyhsYWJlbCA9IG5hbWUpLG51ZGdlX3g9LG51ZGdlX3k9MC4xKSt0aGVtZV9iYXJlKCkrY29vcmRfZml4ZWQoKStzY2FsZV9lZGdlX2NvbG9yX2dyYWRpZW50KGxvdz0iIzAwOTRmZiIsaGlnaD0iI2ZmMDAzZiIpK3NjYWxlX2ZpbGxfZ3JhZGllbnQoaGlnaD0icmVkIixsb3c9ImdvbGQiKQ0KDQoNClYoZ3JhcGgxKSRCQUM9cmVzJEJBQw0KDQpnZ3JhcGgoZ3JhcGgxLCBsYXlvdXQgPSAnbGluZWFyJyxjaXJjdWxhcj1UKStnZW9tX2VkZ2VfZmFuKGFscGhhPTAuMSxhZXMoY29sb3VyID0gY29ycix3aWR0aD1jb3JyKSkrZ2VvbV9ub2RlX3BvaW50KHNoYXBlPTIxLGFlcyhzaXplPUJBQyxmaWxsPUJBQykpK2dlb21fbm9kZV90ZXh0KGFlcyhsYWJlbCA9IG5hbWUpLG51ZGdlX3g9LG51ZGdlX3k9MC4xKSt0aGVtZV9iYXJlKCkrY29vcmRfZml4ZWQoKStzY2FsZV9lZGdlX2NvbG9yX2dyYWRpZW50KGxvdz0iIzAwOTRmZiIsaGlnaD0iI2ZmMDAzZiIpK3NjYWxlX2ZpbGxfZ3JhZGllbnQoaGlnaD0icmVkIixsb3c9ImdvbGQiKQ0KDQoNCmBgYA0KDQpUaGUgbmV0d29yayBhbmFseXNpcyB0aGF0IGNvbWJpbmVzIEtTSSBhbmQgQVVDL0FjY3VyYWN5IHZhbHVlcyBzaG93ZWQgdGhhdCB0aGUgY29oZXJlbmNlIGxldmVsIGFzIG1lYXN1cmVkIGJ5IEtTSSBpcyBOT1QgYXNzb2NpYXRlZCB0byB0aGUgcGVyZm9ybWFuY2UgYXMgbWVhc3VyZWQgYnkgQVVDIG9yIEJBQy4gVGhpcyBtZWFucyB0aGUgbWV0aG9kcyB0aGF0IGhhdmUgdGhlIHNhbWUgS1NJIHZhbHVlIGNvdWxkIGhhdmUgYW4gZXF1aXZhbGVudCBwZXJmb3JtYW5jZSAoYXMgdGhleSBnZW5lcmF0ZSB0aGUgc2FtZSBmZWF0dXJlIHN1YnNldCksIGJ1dCB0aGUgS1NJIGlzIG5vdCBhIG1lYXN1cmUgb2YgZ29vZG5lc3Mgb2YgZmlsdGVyIG1ldGhvZCwgYXMgYSBtZXRob2Qgd2l0aCBsb3cgY29uc2lzdGVuY3kgaW4gcmVnYXJkIHRvIG90aGVyIG1ldGhvZHMgY291bGQgYmVjb21lIGEgYmV0dGVyIG9uZS4NCg0KIyBDb25jbHVzaW9uDQoNCkluIHRoaXNjYXNlIHN0dWR5LCB3ZSBpbnRyb2R1Y2VkIGRpZmZlcmVudCBtZXRob2RzIGZvciBmaWx0ZXJpbmcgdGhlIGZlYXR1cmVzLiBUaGUgY29uc2lzdGVuY3kgbGV2ZWwgYW1vbmcgZmVhdHVyZXMgc3Vic2V0cyBjb3VsZCBiZSBldmFsdWF0ZWQgYnkgdGhlIEt1bmNoZXZhIHN0YWJpbGl0eSBpbmRleC4gSG93ZXZlciwgdGhpcyBpbmRleCBpcyBub3QgYSBtZWFzdXJlIG9mIHRoZSBwZXJmb3JtYW5jZSBmb3IgZWFjaCBtZXRob2QuIFRoZSBSYW5kb20gRm9yZXN0IGJhc2VkIGNyaXRlcmlhLCBpbmNsdWRpbmcgdGhlIHZhcmlhYmxlIGltcG9ydGFuY2UsIGluZm9ybWF0aW9uIGdhaW4gYW5kIEdhaW4gcmF0aW8gYXJlIHZlcnkgdXNlZnVsIGZvciBzZWxlY3RpbmcgZmVhdHVyZXMuDQoNCkluIGEgbmV4dCB0dXRvcmlhbCwgd2Ugd2lsbCBkaXNjdXNzIGFib3V0IHRoZSBtZXRob2RzIHRoYXQgbW9kaWZ5IG91ciBtb2RlbHMgaW5zdGVhZCBvZiBpbnB1dCBkYXRhLCBpLmUgTW9kZWwgc2VsZWN0aW9uLg0KDQpSZWZlcmVuY2U6IDxodHRwczovL3BkZnMuc2VtYW50aWNzY2hvbGFyLm9yZy85YzRmLzAwNmQ1NTQ3Y2RkZWNlZjU4OGU2NmFiNmIzM2U2ODQ1YjMzYS5wZGY+DQoNCg0KIyBDb250cmlidXRpb246DQoNCipUaGlzIGpvaW50IHR1dG9yaWFsIHdhcyBjcmVhdGVkIGJ5IFZpZXQgVHJhbiBhbmQgTmFtIExlIERvbmcgYXMgYSBwYXJ0IG9mIE1MTSBwcm9qZWN0Kg0KDQoqVmlldCBUcmFuKiBkZXNpZ25lZCB0aGUgZGF0YSBleHBlcmltZW50LiBIZSBpcyBhbHNvIHRoZSBhdXRob3Igb2YgbW9zdCBmdW5jdGlvbnMgYW5kIGxvb3BzIGZvciBGZWF0dXJlIHNlbGVjdGlvbiBhbmQgTW9kZWwgcGVyZm9ybWFuY2UgZXZhbHVhdGlvbi4NCg0KKk5hbSBMZSBEb25nKiBoYW5kbGVkIHRoZSBncmFwaHMsIG5ldHdvcmsgYW5hbHlzaXMgYW5kIFJtYXJrZG93biB3cml0aW5nLg0KDQoqKlNlZSB5b3UgaW4gdGhlIG5leHQgdHV0b3JpYWwgYW5kIHRoYW5rIGZvciBqb2luaW5nIHVzICEqKg0KDQoqRU5EKg0K