To go back to the Home page, click here.


This notebook was inspired by this post from the website TowardsDataScience. The dataset can be found here.

Our goal is twofold:

  • We want to build a predictive model able to classify a patient as infected by the covid 19 or not based on his/her blood’s data.

  • We also want to identify diseases that have symptoms similar to the Covid 19. In that way, we can recommend to medical centers to treat separately people infected by these diseases. This would also allow to avoid to build dataset with false instances (i.e. with false discoveries).

1 Data import and pre-processing

data <- read.csv('../../data/covid.csv');
data <- data.frame(data);
data$SARS.Cov.2.exam.result = 1*(data$SARS.Cov.2.exam.result == "positive")

The dataset contains many NaN values. This issue of missing values must be corrected before moving forward. We also split the dataset in two parts. The fist DataFrame will be used to build a prediction model to classify patients as infected by the SARS Cov 2 (or not) based on their blood’s data. The second part will be used to find diseases with symptoms similar to the ones caused by the SARS Cov 2.

dfDiseases = data[,append(c(2,3),seq(22,39))];
dfDiseases = dfDiseases[,c(-7,-9)];
for (disease in colnames(dfDiseases)){
  if (!(disease %in% c("SARS.Cov.2.exam.result","Patient.age.quantile"))){
    dfDiseases[dfDiseases[[disease]]=='detected',disease] = 1; 
    dfDiseases[dfDiseases[[disease]]=='',disease] = NA; 
  }
}
dfDiseases = na.omit(dfDiseases);
dfPrediction  = data[,append(c(2,3,40,42),seq(7,20))];
dfPrediction = na.omit(dfPrediction);

2 Prediction of Infection by the SARS Cov 2

2.1 Data Exploration

2.1.1 Study of Correlations

# attaching the final dataset
attach(dfPrediction)
# Correlation between variables
dfPrediction.corr = cor(dfPrediction)
dfPrediction.corr[,'SARS.Cov.2.exam.result']
##                              Patient.age.quantile 
##                                        0.15935628 
##                            SARS.Cov.2.exam.result 
##                                        1.00000000 
##                                       Neutrophils 
##                                       -0.05666059 
##                          Proteina.C.reativa.mg.dL 
##                                        0.13497681 
##                                        Hematocrit 
##                                        0.09013687 
##                                        Hemoglobin 
##                                        0.10785913 
##                                         Platelets 
##                                       -0.30087670 
##                              Mean.platelet.volume 
##                                        0.08260132 
##                                   Red.blood.Cells 
##                                        0.10400123 
##                                       Lymphocytes 
##                                        0.03645045 
## Mean.corpuscular.hemoglobin.concentrationÂ..MCHC. 
##                                        0.08351100 
##                                        Leukocytes 
##                                       -0.35832387 
##                                         Basophils 
##                                       -0.12702656 
##                 Mean.corpuscular.hemoglobin..MCH. 
##                                       -0.01176143 
##                                       Eosinophils 
##                                       -0.21243742 
##                     Mean.corpuscular.volume..MCV. 
##                                       -0.05874675 
##                                         Monocytes 
##                                        0.23547535 
##           Red.blood.cell.distribution.width..RDW. 
##                                       -0.08749211
# Correlation plot
corrplot(dfPrediction.corr, method = "square", type = "lower", tl.cex = 0.5);

We can take a first glance at the dataset and immediately identify some variables that are correlated with the target value (i.e. the variable SARS.Cov.2.exam.result).

2.1.2 Usefulness of predictors

A first visual approach to identify the most relevant variables to predict infections by the SARS Cov 2 consists in fitting a logistic model with only one variable and to plot the probability of being infected by the Covid with respect to the value of this predictor. Let us illustrate this method for some features.

# Plotting Separate probability graphs
plotting.function <- function(var,variableORrank){
  dfPrediction.sep.function = paste("SARS.Cov.2.exam.result", "~", as.character(var))
  dfPrediction.sep.glm = glm(as.formula(dfPrediction.sep.function), data = dfPrediction , family = binomial)
  cv.glm(dfPrediction,dfPrediction.sep.glm,K=10)$delta[1]
  
  dfPrediction.predicted.data <- data.frame(
    probability.of.SARS=dfPrediction.sep.glm$fitted.values,
    variable=dfPrediction[,as.character(var)])
  
  dfPrediction.predicted.data <- dfPrediction.predicted.data[
    order(dfPrediction.predicted.data$variable, decreasing=FALSE),]
  
  dfPrediction.predicted.data$rank <- 1:nrow(dfPrediction.predicted.data)
  
  ggplot(data=dfPrediction.predicted.data, aes(x= variable, y=probability.of.SARS)) +
    geom_point(aes(color=variable), size=3) +
    xlab(as.character(var)) +
    ylab("Probability of having SARS CoV-2") +
    scale_colour_gradient(low = "darkgreen", high = "darkred", na.value = NA) +
    ggtitle(coef(summary(dfPrediction.sep.glm))[,'Pr(>|z|)'])
}
plotfun1 = plotting.function("Platelets")
plotfun2 = plotting.function("Monocytes")
plotfun3 = plotting.function("Hemoglobin")
plotfun4 = plotting.function("Red.blood.cell.distribution.width..RDW.")
grid.arrange(plotfun1, plotfun2, plotfun3, plotfun4, ncol=2 , nrow = 2)

Except for platelets, all predictors have a positive correlation on the probability of having SARS CoV-2. Moreover, Platelets seems to have a large influence on the probability to be infected by the covid, while the variable Monocytes seems to be less informative.

If this method is a good way to grasp a first intuition, it remains qualitative and scaling aspects can be confusing. Let us now consider a more rigorous approach to identify relevant predictors by using the Likelihood Ratio Test.

# Likelihood Ratio Tests with H0 being the global Null
pvalue_LRTest <- function(var){
  globalnull <- glm(SARS.Cov.2.exam.result  ~ 1, data=dfPrediction);
  alternative <- glm(paste("SARS.Cov.2.exam.result", "~", as.character(var)),data=dfPrediction);
  res_lrtest <- lrtest(globalnull, alternative);
  res_lrtest$`Pr(>Chisq)`[2];
}
names_features <- colnames(dfPrediction);
names_features <- names_features[names_features != 'SARS.Cov.2.exam.result'];
pvalues <- sapply(names_features, pvalue_LRTest);
names(pvalues) <- names_features;
pvalues[order(pvalues, decreasing=FALSE)];
##                                        Leukocytes 
##                                      3.027628e-14 
##                                         Platelets 
##                                      2.737168e-10 
##                                         Monocytes 
##                                      9.840969e-07 
##                                       Eosinophils 
##                                      1.062583e-05 
##                              Patient.age.quantile 
##                                      1.013128e-03 
##                          Proteina.C.reativa.mg.dL 
##                                      5.453885e-03 
##                                         Basophils 
##                                      8.952467e-03 
##                                        Hemoglobin 
##                                      2.662828e-02 
##                                   Red.blood.Cells 
##                                      3.258267e-02 
##                                        Hematocrit 
##                                      6.416599e-02 
##           Red.blood.cell.distribution.width..RDW. 
##                                      7.241497e-02 
## Mean.corpuscular.hemoglobin.concentrationÂ..MCHC. 
##                                      8.644399e-02 
##                              Mean.platelet.volume 
##                                      8.993860e-02 
##                     Mean.corpuscular.volume..MCV. 
##                                      2.282069e-01 
##                                       Neutrophils 
##                                      2.451826e-01 
##                                       Lymphocytes 
##                                      4.549064e-01 
##                 Mean.corpuscular.hemoglobin..MCH. 
##                                      8.095195e-01

Based of the results provided by Likelihood Ratio Tests, we will only consider the predictors leading to a p-value at most equal to 0.05. So for the analysis, we will use Leukocytes, Platelets, Monocytes, Eosinophils, Patient.Age.Quantile, Proteina.C.reativa.mg.dL, Basophils, Hemoglobin and Red.blood.Cells as predictors.

2.2 Prediction with Logistic Regression

We split the dataset in two parts. Taking 80% of patients, we build a training dataset on which we will fit the parameters of our logistic model. On the remaining 20% of patients, we will evaluate the performance of our model.

# Dividing Train/Test data with 80% training dataset
sample_size <- floor(0.8 * nrow(dfPrediction))
train_ind <- sample(nrow(dfPrediction), size = sample_size)
dfPrediction.train <- as.data.frame(dfPrediction[train_ind,])
dfPrediction.test <- as.data.frame(dfPrediction[-train_ind,])

# Logistic regression considering all the variables on the target variable SARS_COV2_Result
dfPrediction.function = paste("SARS.Cov.2.exam.result", "~", "Leukocytes + Platelets + Monocytes + Eosinophils + Patient.age.quantile + Proteina.C.reativa.mg.dL + Basophils + Hemoglobin + Red.blood.Cells")
dfPrediction.glm = glm(as.formula(dfPrediction.function), data = dfPrediction.train , family = binomial)
summary(dfPrediction.glm)
## 
## Call:
## glm(formula = as.formula(dfPrediction.function), family = binomial, 
##     data = dfPrediction.train)
## 
## Deviance Residuals: 
##     Min       1Q   Median       3Q      Max  
## -2.2836  -0.3371  -0.1300  -0.0396   3.1803  
## 
## Coefficients:
##                          Estimate Std. Error z value Pr(>|z|)    
## (Intercept)              -5.05388    0.84330  -5.993 2.06e-09 ***
## Leukocytes               -2.65092    0.58280  -4.549 5.40e-06 ***
## Platelets                -0.56822    0.40671  -1.397  0.16238    
## Monocytes                 0.09540    0.19986   0.477  0.63313    
## Eosinophils              -1.21933    0.44935  -2.714  0.00666 ** 
## Patient.age.quantile      0.09785    0.04983   1.964  0.04955 *  
## Proteina.C.reativa.mg.dL  0.70595    0.32469   2.174  0.02969 *  
## Basophils                -0.44670    0.34147  -1.308  0.19082    
## Hemoglobin                0.43039    0.54724   0.786  0.43159    
## Red.blood.Cells           0.26184    0.52074   0.503  0.61509    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## (Dispersion parameter for binomial family taken to be 1)
## 
##     Null deviance: 268.33  on 335  degrees of freedom
## Residual deviance: 149.83  on 326  degrees of freedom
## AIC: 169.83
## 
## Number of Fisher Scoring iterations: 7
# 10 fold cross-validation to verify the model
cv.glm(dfPrediction.train,dfPrediction.glm,K=10)$delta[1]
## [1] 0.08234896

Let us predict the error on our test dataset.

# Predicting on test data based on training set
dfPrediction.glm.predict <- predict(dfPrediction.glm,dfPrediction.test,type = "response")
summary(dfPrediction.glm.predict)
##      Min.   1st Qu.    Median      Mean   3rd Qu.      Max. 
## 0.0000064 0.0045178 0.0402410 0.1411901 0.1388168 0.8848365
tapply(dfPrediction.glm.predict, dfPrediction.test$SARS.Cov.2.exam.result, mean)
##          0          1 
## 0.08366047 0.45539063
# Confusion matrix for threshold of 1%
dfPrediction.confusion = table(dfPrediction.test$SARS.Cov.2.exam.result, dfPrediction.glm.predict > 0.01)
rownames(dfPrediction.confusion) <- c("Predicted FALSE","Predicted TRUE");
print(dfPrediction.confusion);
##                  
##                   FALSE TRUE
##   Predicted FALSE    27   44
##   Predicted TRUE      0   13
# False negative error rate (Type II error)
dfPrediction.type2error = dfPrediction.confusion[1,1]/ (dfPrediction.confusion[1,1]+dfPrediction.confusion[2,2])
print(paste("The proportion of errors of Type II is ",as.character(dfPrediction.type2error)));
## [1] "The proportion of errors of Type II is  0.675"

Using the confusion matrix, we understand that the Type I error using our model is really small. Hence, if patient infected by the covid with our model will be classified by our model as infected with high probability.

However, Type II errors are likely. This means that our prediction should be trust only when a patient is classified as non-infected.

Now we plot below the ROC curve for our model. Let us recall that a random classifier is expected to give points lying along the diagonal (FPR = TPR). A typical method to compare the performance of classifiers is to measure the area under the ROC curve (AUC) for each of them and to keep with the one with the larger value. In practice, the AUC performs well as a general measure of predictive accuracy.

# Plotting ROCR curve
dfPrediction.ROCRpred = prediction(dfPrediction.glm.predict, dfPrediction.test$SARS.Cov.2.exam.result)
dfPrediction.ROCRperf = performance(dfPrediction.ROCRpred, "tpr", "fpr")
plot(dfPrediction.ROCRperf, colorize=TRUE, print.cutoffs.at=seq(0,1,by=0.1), text.adj=c(-0.2,1.7))

As already seen, our model is able to extract relevant insight from blood’s data of the patient. To conclude, let us plot the final results.

# Creating a dataframe with variables and predicted values of SARS results
dfPrediction.predict.dataframe <- data.frame(
  probability.of.having.SARS=dfPrediction.glm$fitted.values,
  Leukocytes=dfPrediction.train$Leukocytes,
  Patient.age.quantile = dfPrediction.train$Patient.age.quantile,
  Eosinophils = dfPrediction.train$Eosinophils,
  Monocytes = dfPrediction.train$Monocytes,
  Platelets = dfPrediction.train$Platelets,
  Proteina.C.reativa.mg.dL = dfPrediction.train$Proteina.C.reativa.mg.dL)
plot1 = ggplot(data=dfPrediction.predict.dataframe, aes(x=Patient.age.quantile, y=probability.of.having.SARS)) +
  geom_point(aes(color=Patient.age.quantile), size=4)+ theme(axis.title.y = element_text(size=9)) + theme(legend.title=element_text(size=9))
plot2 = ggplot(data=dfPrediction.predict.dataframe, aes(x=Leukocytes, y=probability.of.having.SARS)) +
  geom_point(aes(color=Leukocytes), size=4)+ theme(axis.title.y = element_text(size=9)) + theme(legend.title=element_text(size=9))
plot3 = ggplot(data=dfPrediction.predict.dataframe, aes(x=Monocytes, y=probability.of.having.SARS)) +
  geom_point(aes(color=Monocytes), size=4)+ theme(axis.title.y = element_text(size=9)) + theme(legend.title=element_text(size=9))
plot4 = ggplot(data=dfPrediction.predict.dataframe, aes(x=Eosinophils, y=probability.of.having.SARS)) +
  geom_point(aes(color=Eosinophils), size=4)+ theme(axis.title.y = element_text(size=9)) + theme(legend.title=element_text(size=9))
plot5 = ggplot(data=dfPrediction.predict.dataframe, aes(x=Platelets, y=probability.of.having.SARS)) +
  geom_point(aes(color=Platelets), size=4) + theme(axis.title.y = element_text(size=9)) + theme(legend.title=element_text(size=9))
plot6 = ggplot(data=dfPrediction.predict.dataframe, aes(x=Proteina.C.reativa.mg.dL, y=probability.of.having.SARS)) +
  geom_point(aes(color=Proteina.C.reativa.mg.dL), size=4) + theme(axis.title.y = element_text(size=9)) + theme(legend.title=element_text(size=9))
# Plotting the values
grid.arrange(plot1, plot2, plot3, plot4, plot5, plot6, ncol=2 , nrow = 3)

3 Finding disease(s) with symptoms similar to Covid

In this section, we want to find disease(s) with symptoms similar to the one of the Covid 19. Such information could be useful to decrease the rate of false positive case in hospitals and also to improve the quality of datasets provided on the covid 19.

3.1 Correlations

We have to skip Parainfluenza 2 as there are no cases for it.

dfDiseases <- dfDiseases[,colnames(dfDiseases) != 'Parainfluenza.2'];

Now let’s plot the CramerV’s correlation plot to check the correlation between categorical variables.

# CramerV Correlation to check for any correlation between catagorical variables
# Except Patient's age, all other variables are catagorical(binary)
dfDiseases.corr  = PairApply(dfDiseases[,names(dfDiseases) != "Patient.age.quantile"], CramerV, symmetric = TRUE)
# Displaying correlation with variable SARS_COV2_Result
dfDiseases.corr[,'SARS.Cov.2.exam.result']
##      SARS.Cov.2.exam.result Respiratory.Syncytial.Virus 
##                 1.000000000                 0.060107431 
##                 Influenza.A                 Influenza.B 
##                 0.034910535                 0.038396638 
##             Parainfluenza.1      Rhinovirus.Enterovirus 
##                 0.014172707                 0.151723838 
##            Coronavirus.HKU1             Parainfluenza.3 
##                 0.036826552                 0.025943102 
##    Chlamydophila.pneumoniae                  Adenovirus 
##                 0.024602623                 0.029612806 
##             Parainfluenza.4             Coronavirus229E 
##                 0.035880616                 0.008396133 
##             CoronavirusOC43             Inf.A.H1N1.2009 
##                 0.023186945                 0.084016085 
##        Bordetella.pertussis             Metapneumovirus 
##                 0.011567680                 0.030742142
# Correlation plot
corrplot(dfDiseases.corr , method = "square", type = "lower", number.cex=0.5)

Rhinovirus_OR_Enterovirus seems to be the disease the most correlated with our target (with the correlation value of 0.1517). In the next section, we conduct a statistical test to identify if this correlation is statically significant.

3.2 Likelihood Ratio Test

We perform Likelihood Ratio Tests to identify diseases that show a significant correlation with the infection status of patient by the SARS Cov 2.

pvalue_LRTest <- function(var){
  globalnull <- glm(SARS.Cov.2.exam.result  ~ 1, data=dfDiseases);
  alternative <- glm(paste("SARS.Cov.2.exam.result", "~", as.character(var)),data=dfDiseases);
  res_lrtest <- lrtest(globalnull, alternative);
  res_lrtest$`Pr(>Chisq)`[2];
}
names_features <- colnames(dfDiseases);
names_features <- names_features[ !(names_features %in% c('SARS.Cov.2.exam.result','Patient.age.quantile'))];
pvalues <- sapply(names_features, pvalue_LRTest);
names(pvalues) <- names_features;
pvalues[order(pvalues, decreasing=FALSE)];
##      Rhinovirus.Enterovirus             Inf.A.H1N1.2009 
##                2.007768e-08                1.970079e-03 
## Respiratory.Syncytial.Virus                 Influenza.B 
##                2.695813e-02                1.578475e-01 
##            Coronavirus.HKU1             Parainfluenza.4 
##                1.755593e-01                1.869221e-01 
##                 Influenza.A             Metapneumovirus 
##                1.991293e-01                2.582058e-01 
##                  Adenovirus             Parainfluenza.3 
##                2.761149e-01                3.400448e-01 
##    Chlamydophila.pneumoniae             CoronavirusOC43 
##                3.655902e-01                3.938322e-01 
##             Parainfluenza.1        Bordetella.pertussis 
##                6.022621e-01                6.705796e-01 
##             Coronavirus229E 
##                7.575288e-01

Our intuition of the previous section is confirmed by the Likelihood Ratio Test. The symptoms of Rhinovirus are close to the SARS CoV-2 virus and mostly found in the nose (Rhinovirus). Therefore, the probability of having SARS decreases as Rhinovirus is detected.

Hence hospitals should distinguish between a person with Rhinovirus and a person with COVID-19. Separating patients with Rhinovirus and Covid-19 could help to relieve covid medical groups and to end up with datasets with less false positive patients.

LS0tDQp0aXRsZTogIkxvZ2lzdGljIFJlZ3Jlc3Npb24gd2l0aCB0aGUgU0FSUyBDb3YgMiBkYXRhc2V0Ig0Kb3V0cHV0Og0KICAgIGh0bWxfZG9jdW1lbnQ6DQogICAgICBjb2RlX2Rvd25sb2FkOiB0cnVlICAgIA0KICAgICAgdGhlbWU6IGNvc21vDQogICAgICB0b2M6IHRydWUNCiAgICAgIHRvY19mbG9hdDogdHJ1ZQ0KICAgICAgaGlnaGxpZ2h0OiB0YW5nbw0KICAgICAgbnVtYmVyX3NlY3Rpb25zOiB0cnVlDQotLS0NCg0KVG8gZ28gYmFjayB0byB0aGUgSG9tZSBwYWdlLCBjbGljayBbaGVyZV0oaHR0cHM6Ly9xdWVudGluLWR1Y2hlbWluLmdpdGh1Yi5pby9FTlBDLVNEQS8pLg0KDQo8YnI+PC9icj4NCg0KVGhpcyBub3RlYm9vayB3YXMgaW5zcGlyZWQgYnkgW3RoaXMgcG9zdF0oaHR0cHM6Ly90b3dhcmRzZGF0YXNjaWVuY2UuY29tL3ByZWRpY3RpbmctdGhlLXByb2JhYmlsaXR5LW9mLXNhcnMtY292LTItcmVzdWx0LXVzaW5nLW11bHRpcGxlLWxvZ2lzdGljLXJlZ3Jlc3NpbmctaW4tci1hbmQtcHl0aG9uLTg3NGNmOTE4NWQ2MikgZnJvbSB0aGUgd2Vic2l0ZSBbVG93YXJkc0RhdGFTY2llbmNlXShodHRwczovL3Rvd2FyZHNkYXRhc2NpZW5jZS5jb20pLiBUaGUgZGF0YXNldCBjYW4gYmUgZm91bmQgW2hlcmVdKGh0dHBzOi8vd3d3LmthZ2dsZS5jb20vZWluc3RlaW5kYXRhNHUvY292aWQxOSkuIA0KDQoNCk91ciBnb2FsIGlzIHR3b2ZvbGQ6DQoNCi0gV2Ugd2FudCB0byBidWlsZCBhIHByZWRpY3RpdmUgbW9kZWwgYWJsZSB0byBjbGFzc2lmeSBhIHBhdGllbnQgYXMgaW5mZWN0ZWQgYnkgdGhlIGNvdmlkIDE5IG9yIG5vdCBiYXNlZCBvbiBoaXMvaGVyIGJsb29kJ3MgZGF0YS4NCg0KLSBXZSBhbHNvIHdhbnQgdG8gaWRlbnRpZnkgZGlzZWFzZXMgdGhhdCBoYXZlIHN5bXB0b21zIHNpbWlsYXIgdG8gdGhlIENvdmlkIDE5LiBJbiB0aGF0IHdheSwgd2UgY2FuIHJlY29tbWVuZCB0byBtZWRpY2FsIGNlbnRlcnMgdG8gdHJlYXQgc2VwYXJhdGVseSBwZW9wbGUgaW5mZWN0ZWQgYnkgdGhlc2UgZGlzZWFzZXMuIFRoaXMgd291bGQgYWxzbyBhbGxvdyB0byBhdm9pZCB0byBidWlsZCBkYXRhc2V0IHdpdGggZmFsc2UgaW5zdGFuY2VzIChpLmUuIHdpdGggZmFsc2UgZGlzY292ZXJpZXMpLg0KDQoNCiMgRGF0YSBpbXBvcnQgYW5kIHByZS1wcm9jZXNzaW5nDQoNCmBgYHtyIGluY2x1ZGU9RkFMU0V9DQpsaWJyYXJ5KCdEZXNjVG9vbHMnKQ0KbGlicmFyeSgidGlkeXZlcnNlIikNCmxpYnJhcnkoJ2NvcnJwbG90JykNCmxpYnJhcnkoJ2Jvb3QnKSAjIHRvIHVzZSBjdi5nbG0gZm9yIENyb3NzLVZhbGlkYXRpb24NCmxpYnJhcnkoJ1JPQ1InKSAjIHRvIHVzZSBwcmVkaWN0aW9uIGFuZCBwZXJmb3JtYW5jZSBmdW5jdGlvbnMgZm9yIFJPQyBjdXJ2ZXMNCmxpYnJhcnkoJ2dncGxvdDInKSAjIHRvIHVzZSB0aGUgZnVuY3Rpb24gZ2dwbG90DQpsaWJyYXJ5KCdncmlkRXh0cmEnKSAjIHRvIHVzZSB0aGUgY29tbWFuZCBncmlhZC5hcmFuZ2UgdG8gc2hvdyBtdWx0aXBsZSBwbG90cyBpbiB0aGUgc2FtZSB3aW5kb3cNCmxpYnJhcnkoJ2xtdGVzdCcpICMgdG8gdXNlIHRoZSBmdW5jdGlvbiBscnRlc3QgZm9yIGxpa2VsaWhvb2QgcmF0aW8gdGVzdHMgDQpgYGANCg0KYGBge3J9DQpkYXRhIDwtIHJlYWQuY3N2KCcuLi8uLi9kYXRhL2NvdmlkLmNzdicpOw0KZGF0YSA8LSBkYXRhLmZyYW1lKGRhdGEpOw0KZGF0YSRTQVJTLkNvdi4yLmV4YW0ucmVzdWx0ID0gMSooZGF0YSRTQVJTLkNvdi4yLmV4YW0ucmVzdWx0ID09ICJwb3NpdGl2ZSIpDQpgYGANCg0KVGhlIGRhdGFzZXQgY29udGFpbnMgbWFueSBOYU4gdmFsdWVzLiBUaGlzIGlzc3VlIG9mIG1pc3NpbmcgdmFsdWVzIG11c3QgYmUgY29ycmVjdGVkIGJlZm9yZSBtb3ZpbmcgZm9yd2FyZC4gV2UgYWxzbyBzcGxpdCB0aGUgZGF0YXNldCBpbiB0d28gcGFydHMuIFRoZSBmaXN0IERhdGFGcmFtZSB3aWxsIGJlIHVzZWQgdG8gYnVpbGQgYSBwcmVkaWN0aW9uIG1vZGVsIHRvIGNsYXNzaWZ5IHBhdGllbnRzIGFzIGluZmVjdGVkIGJ5IHRoZSBTQVJTIENvdiAyIChvciBub3QpIGJhc2VkIG9uIHRoZWlyIGJsb29kJ3MgZGF0YS4gVGhlIHNlY29uZCBwYXJ0IHdpbGwgYmUgdXNlZCB0byBmaW5kIGRpc2Vhc2VzIHdpdGggc3ltcHRvbXMgc2ltaWxhciB0byB0aGUgb25lcyBjYXVzZWQgYnkgdGhlIFNBUlMgQ292IDIuDQoNCmBgYHtyfQ0KZGZEaXNlYXNlcyA9IGRhdGFbLGFwcGVuZChjKDIsMyksc2VxKDIyLDM5KSldOw0KZGZEaXNlYXNlcyA9IGRmRGlzZWFzZXNbLGMoLTcsLTkpXTsNCmZvciAoZGlzZWFzZSBpbiBjb2xuYW1lcyhkZkRpc2Vhc2VzKSl7DQogIGlmICghKGRpc2Vhc2UgJWluJSBjKCJTQVJTLkNvdi4yLmV4YW0ucmVzdWx0IiwiUGF0aWVudC5hZ2UucXVhbnRpbGUiKSkpew0KICAgIGRmRGlzZWFzZXNbZGZEaXNlYXNlc1tbZGlzZWFzZV1dPT0nZGV0ZWN0ZWQnLGRpc2Vhc2VdID0gMTsgDQogICAgZGZEaXNlYXNlc1tkZkRpc2Vhc2VzW1tkaXNlYXNlXV09PScnLGRpc2Vhc2VdID0gTkE7IA0KICB9DQp9DQpkZkRpc2Vhc2VzID0gbmEub21pdChkZkRpc2Vhc2VzKTsNCmRmUHJlZGljdGlvbiAgPSBkYXRhWyxhcHBlbmQoYygyLDMsNDAsNDIpLHNlcSg3LDIwKSldOw0KZGZQcmVkaWN0aW9uID0gbmEub21pdChkZlByZWRpY3Rpb24pOw0KYGBgDQoNCg0KIyBQcmVkaWN0aW9uIG9mIEluZmVjdGlvbiBieSB0aGUgU0FSUyBDb3YgMg0KIyMgRGF0YSBFeHBsb3JhdGlvbg0KDQojIyMgU3R1ZHkgb2YgQ29ycmVsYXRpb25zIA0KDQpgYGB7cn0NCiMgYXR0YWNoaW5nIHRoZSBmaW5hbCBkYXRhc2V0DQphdHRhY2goZGZQcmVkaWN0aW9uKQ0KIyBDb3JyZWxhdGlvbiBiZXR3ZWVuIHZhcmlhYmxlcw0KZGZQcmVkaWN0aW9uLmNvcnIgPSBjb3IoZGZQcmVkaWN0aW9uKQ0KZGZQcmVkaWN0aW9uLmNvcnJbLCdTQVJTLkNvdi4yLmV4YW0ucmVzdWx0J10NCiMgQ29ycmVsYXRpb24gcGxvdA0KY29ycnBsb3QoZGZQcmVkaWN0aW9uLmNvcnIsIG1ldGhvZCA9ICJzcXVhcmUiLCB0eXBlID0gImxvd2VyIiwgdGwuY2V4ID0gMC41KTsNCmBgYA0KDQpXZSBjYW4gdGFrZSBhIGZpcnN0IGdsYW5jZSBhdCB0aGUgZGF0YXNldCBhbmQgaW1tZWRpYXRlbHkgaWRlbnRpZnkgc29tZSB2YXJpYWJsZXMgdGhhdCBhcmUgY29ycmVsYXRlZCB3aXRoIHRoZSB0YXJnZXQgdmFsdWUgKGkuZS4gdGhlIHZhcmlhYmxlICpTQVJTLkNvdi4yLmV4YW0ucmVzdWx0KikuDQoNCg0KIyMjIFVzZWZ1bG5lc3Mgb2YgcHJlZGljdG9ycw0KDQoNCkEgZmlyc3QgdmlzdWFsIGFwcHJvYWNoIHRvIGlkZW50aWZ5IHRoZSBtb3N0IHJlbGV2YW50IHZhcmlhYmxlcyB0byBwcmVkaWN0IGluZmVjdGlvbnMgYnkgdGhlIFNBUlMgQ292IDIgY29uc2lzdHMgaW4gZml0dGluZyBhIGxvZ2lzdGljIG1vZGVsIHdpdGggb25seSBvbmUgdmFyaWFibGUgYW5kIHRvIHBsb3QgdGhlIHByb2JhYmlsaXR5IG9mIGJlaW5nIGluZmVjdGVkIGJ5IHRoZSBDb3ZpZCB3aXRoIHJlc3BlY3QgdG8gdGhlIHZhbHVlIG9mIHRoaXMgcHJlZGljdG9yLiBMZXQgdXMgaWxsdXN0cmF0ZSB0aGlzIG1ldGhvZCBmb3Igc29tZSBmZWF0dXJlcy4NCg0KDQpgYGB7cn0NCiMgUGxvdHRpbmcgU2VwYXJhdGUgcHJvYmFiaWxpdHkgZ3JhcGhzDQpwbG90dGluZy5mdW5jdGlvbiA8LSBmdW5jdGlvbih2YXIsdmFyaWFibGVPUnJhbmspew0KICBkZlByZWRpY3Rpb24uc2VwLmZ1bmN0aW9uID0gcGFzdGUoIlNBUlMuQ292LjIuZXhhbS5yZXN1bHQiLCAifiIsIGFzLmNoYXJhY3Rlcih2YXIpKQ0KICBkZlByZWRpY3Rpb24uc2VwLmdsbSA9IGdsbShhcy5mb3JtdWxhKGRmUHJlZGljdGlvbi5zZXAuZnVuY3Rpb24pLCBkYXRhID0gZGZQcmVkaWN0aW9uICwgZmFtaWx5ID0gYmlub21pYWwpDQogIGN2LmdsbShkZlByZWRpY3Rpb24sZGZQcmVkaWN0aW9uLnNlcC5nbG0sSz0xMCkkZGVsdGFbMV0NCiAgDQogIGRmUHJlZGljdGlvbi5wcmVkaWN0ZWQuZGF0YSA8LSBkYXRhLmZyYW1lKA0KICAgIHByb2JhYmlsaXR5Lm9mLlNBUlM9ZGZQcmVkaWN0aW9uLnNlcC5nbG0kZml0dGVkLnZhbHVlcywNCiAgICB2YXJpYWJsZT1kZlByZWRpY3Rpb25bLGFzLmNoYXJhY3Rlcih2YXIpXSkNCiAgDQogIGRmUHJlZGljdGlvbi5wcmVkaWN0ZWQuZGF0YSA8LSBkZlByZWRpY3Rpb24ucHJlZGljdGVkLmRhdGFbDQogICAgb3JkZXIoZGZQcmVkaWN0aW9uLnByZWRpY3RlZC5kYXRhJHZhcmlhYmxlLCBkZWNyZWFzaW5nPUZBTFNFKSxdDQogIA0KICBkZlByZWRpY3Rpb24ucHJlZGljdGVkLmRhdGEkcmFuayA8LSAxOm5yb3coZGZQcmVkaWN0aW9uLnByZWRpY3RlZC5kYXRhKQ0KICANCiAgZ2dwbG90KGRhdGE9ZGZQcmVkaWN0aW9uLnByZWRpY3RlZC5kYXRhLCBhZXMoeD0gdmFyaWFibGUsIHk9cHJvYmFiaWxpdHkub2YuU0FSUykpICsNCiAgICBnZW9tX3BvaW50KGFlcyhjb2xvcj12YXJpYWJsZSksIHNpemU9MykgKw0KICAgIHhsYWIoYXMuY2hhcmFjdGVyKHZhcikpICsNCiAgICB5bGFiKCJQcm9iYWJpbGl0eSBvZiBoYXZpbmcgU0FSUyBDb1YtMiIpICsNCiAgICBzY2FsZV9jb2xvdXJfZ3JhZGllbnQobG93ID0gImRhcmtncmVlbiIsIGhpZ2ggPSAiZGFya3JlZCIsIG5hLnZhbHVlID0gTkEpICsNCiAgICBnZ3RpdGxlKGNvZWYoc3VtbWFyeShkZlByZWRpY3Rpb24uc2VwLmdsbSkpWywnUHIoPnx6fCknXSkNCn0NCnBsb3RmdW4xID0gcGxvdHRpbmcuZnVuY3Rpb24oIlBsYXRlbGV0cyIpDQpwbG90ZnVuMiA9IHBsb3R0aW5nLmZ1bmN0aW9uKCJNb25vY3l0ZXMiKQ0KcGxvdGZ1bjMgPSBwbG90dGluZy5mdW5jdGlvbigiSGVtb2dsb2JpbiIpDQpwbG90ZnVuNCA9IHBsb3R0aW5nLmZ1bmN0aW9uKCJSZWQuYmxvb2QuY2VsbC5kaXN0cmlidXRpb24ud2lkdGguLlJEVy4iKQ0KZ3JpZC5hcnJhbmdlKHBsb3RmdW4xLCBwbG90ZnVuMiwgcGxvdGZ1bjMsIHBsb3RmdW40LCBuY29sPTIgLCBucm93ID0gMikNCmBgYA0KDQpFeGNlcHQgZm9yIHBsYXRlbGV0cywgYWxsIHByZWRpY3RvcnMgaGF2ZSBhIHBvc2l0aXZlIGNvcnJlbGF0aW9uIG9uIHRoZSBwcm9iYWJpbGl0eSBvZiBoYXZpbmcgU0FSUyBDb1YtMi4gTW9yZW92ZXIsICpQbGF0ZWxldHMqIHNlZW1zIHRvIGhhdmUgYSBsYXJnZSBpbmZsdWVuY2Ugb24gdGhlIHByb2JhYmlsaXR5IHRvIGJlIGluZmVjdGVkIGJ5IHRoZSBjb3ZpZCwgd2hpbGUgdGhlIHZhcmlhYmxlICpNb25vY3l0ZXMqIHNlZW1zIHRvIGJlIGxlc3MgaW5mb3JtYXRpdmUuDQoNCklmIHRoaXMgbWV0aG9kIGlzIGEgZ29vZCB3YXkgdG8gZ3Jhc3AgYSBmaXJzdCBpbnR1aXRpb24sIGl0IHJlbWFpbnMgcXVhbGl0YXRpdmUgYW5kIHNjYWxpbmcgYXNwZWN0cyBjYW4gYmUgY29uZnVzaW5nLiBMZXQgdXMgbm93IGNvbnNpZGVyIGEgbW9yZSByaWdvcm91cyBhcHByb2FjaCB0byBpZGVudGlmeSByZWxldmFudCBwcmVkaWN0b3JzIGJ5IHVzaW5nIHRoZSBMaWtlbGlob29kIFJhdGlvIFRlc3QuDQoNCg0KDQpgYGB7cn0NCiMgTGlrZWxpaG9vZCBSYXRpbyBUZXN0cyB3aXRoIEgwIGJlaW5nIHRoZSBnbG9iYWwgTnVsbA0KcHZhbHVlX0xSVGVzdCA8LSBmdW5jdGlvbih2YXIpew0KICBnbG9iYWxudWxsIDwtIGdsbShTQVJTLkNvdi4yLmV4YW0ucmVzdWx0ICB+IDEsIGRhdGE9ZGZQcmVkaWN0aW9uKTsNCiAgYWx0ZXJuYXRpdmUgPC0gZ2xtKHBhc3RlKCJTQVJTLkNvdi4yLmV4YW0ucmVzdWx0IiwgIn4iLCBhcy5jaGFyYWN0ZXIodmFyKSksZGF0YT1kZlByZWRpY3Rpb24pOw0KICByZXNfbHJ0ZXN0IDwtIGxydGVzdChnbG9iYWxudWxsLCBhbHRlcm5hdGl2ZSk7DQogIHJlc19scnRlc3QkYFByKD5DaGlzcSlgWzJdOw0KfQ0KbmFtZXNfZmVhdHVyZXMgPC0gY29sbmFtZXMoZGZQcmVkaWN0aW9uKTsNCm5hbWVzX2ZlYXR1cmVzIDwtIG5hbWVzX2ZlYXR1cmVzW25hbWVzX2ZlYXR1cmVzICE9ICdTQVJTLkNvdi4yLmV4YW0ucmVzdWx0J107DQpwdmFsdWVzIDwtIHNhcHBseShuYW1lc19mZWF0dXJlcywgcHZhbHVlX0xSVGVzdCk7DQpuYW1lcyhwdmFsdWVzKSA8LSBuYW1lc19mZWF0dXJlczsNCnB2YWx1ZXNbb3JkZXIocHZhbHVlcywgZGVjcmVhc2luZz1GQUxTRSldOw0KYGBgDQoNCg0KDQpCYXNlZCBvZiB0aGUgcmVzdWx0cyBwcm92aWRlZCBieSBMaWtlbGlob29kIFJhdGlvIFRlc3RzLCAqKndlIHdpbGwgb25seSBjb25zaWRlciB0aGUgcHJlZGljdG9ycyBsZWFkaW5nIHRvIGEgcC12YWx1ZSBhdCBtb3N0IGVxdWFsIHRvIDAuMDUqKi4gU28gZm9yIHRoZSBhbmFseXNpcywgd2Ugd2lsbCB1c2UgKkxldWtvY3l0ZXMsIFBsYXRlbGV0cywgTW9ub2N5dGVzLCBFb3Npbm9waGlscywgUGF0aWVudC5BZ2UuUXVhbnRpbGUsIFByb3RlaW5hLkMucmVhdGl2YS5tZy5kTCwgQmFzb3BoaWxzLCBIZW1vZ2xvYmluKiBhbmQgKlJlZC5ibG9vZC5DZWxscyogYXMgcHJlZGljdG9ycy4NCg0KDQojIyBQcmVkaWN0aW9uIHdpdGggTG9naXN0aWMgUmVncmVzc2lvbg0KDQoNCldlIHNwbGl0IHRoZSBkYXRhc2V0IGluIHR3byBwYXJ0cy4gVGFraW5nIDgwJSBvZiBwYXRpZW50cywgd2UgYnVpbGQgYSB0cmFpbmluZyBkYXRhc2V0IG9uIHdoaWNoIHdlIHdpbGwgZml0IHRoZSBwYXJhbWV0ZXJzIG9mIG91ciBsb2dpc3RpYyBtb2RlbC4gT24gdGhlIHJlbWFpbmluZyAyMCUgb2YgcGF0aWVudHMsIHdlIHdpbGwgZXZhbHVhdGUgdGhlIHBlcmZvcm1hbmNlIG9mIG91ciBtb2RlbC4NCg0KYGBge3J9DQojIERpdmlkaW5nIFRyYWluL1Rlc3QgZGF0YSB3aXRoIDgwJSB0cmFpbmluZyBkYXRhc2V0DQpzYW1wbGVfc2l6ZSA8LSBmbG9vcigwLjggKiBucm93KGRmUHJlZGljdGlvbikpDQp0cmFpbl9pbmQgPC0gc2FtcGxlKG5yb3coZGZQcmVkaWN0aW9uKSwgc2l6ZSA9IHNhbXBsZV9zaXplKQ0KZGZQcmVkaWN0aW9uLnRyYWluIDwtIGFzLmRhdGEuZnJhbWUoZGZQcmVkaWN0aW9uW3RyYWluX2luZCxdKQ0KZGZQcmVkaWN0aW9uLnRlc3QgPC0gYXMuZGF0YS5mcmFtZShkZlByZWRpY3Rpb25bLXRyYWluX2luZCxdKQ0KDQojIExvZ2lzdGljIHJlZ3Jlc3Npb24gY29uc2lkZXJpbmcgYWxsIHRoZSB2YXJpYWJsZXMgb24gdGhlIHRhcmdldCB2YXJpYWJsZSBTQVJTX0NPVjJfUmVzdWx0DQpkZlByZWRpY3Rpb24uZnVuY3Rpb24gPSBwYXN0ZSgiU0FSUy5Db3YuMi5leGFtLnJlc3VsdCIsICJ+IiwgIkxldWtvY3l0ZXMgKyBQbGF0ZWxldHMgKyBNb25vY3l0ZXMgKyBFb3Npbm9waGlscyArIFBhdGllbnQuYWdlLnF1YW50aWxlICsgUHJvdGVpbmEuQy5yZWF0aXZhLm1nLmRMICsgQmFzb3BoaWxzICsgSGVtb2dsb2JpbiArIFJlZC5ibG9vZC5DZWxscyIpDQpkZlByZWRpY3Rpb24uZ2xtID0gZ2xtKGFzLmZvcm11bGEoZGZQcmVkaWN0aW9uLmZ1bmN0aW9uKSwgZGF0YSA9IGRmUHJlZGljdGlvbi50cmFpbiAsIGZhbWlseSA9IGJpbm9taWFsKQ0Kc3VtbWFyeShkZlByZWRpY3Rpb24uZ2xtKQ0KDQojIDEwIGZvbGQgY3Jvc3MtdmFsaWRhdGlvbiB0byB2ZXJpZnkgdGhlIG1vZGVsDQpjdi5nbG0oZGZQcmVkaWN0aW9uLnRyYWluLGRmUHJlZGljdGlvbi5nbG0sSz0xMCkkZGVsdGFbMV0NCmBgYA0KDQpMZXQgdXMgcHJlZGljdCB0aGUgZXJyb3Igb24gb3VyIHRlc3QgZGF0YXNldC4gDQoNCmBgYHtyfQ0KIyBQcmVkaWN0aW5nIG9uIHRlc3QgZGF0YSBiYXNlZCBvbiB0cmFpbmluZyBzZXQNCmRmUHJlZGljdGlvbi5nbG0ucHJlZGljdCA8LSBwcmVkaWN0KGRmUHJlZGljdGlvbi5nbG0sZGZQcmVkaWN0aW9uLnRlc3QsdHlwZSA9ICJyZXNwb25zZSIpDQpzdW1tYXJ5KGRmUHJlZGljdGlvbi5nbG0ucHJlZGljdCkNCnRhcHBseShkZlByZWRpY3Rpb24uZ2xtLnByZWRpY3QsIGRmUHJlZGljdGlvbi50ZXN0JFNBUlMuQ292LjIuZXhhbS5yZXN1bHQsIG1lYW4pDQoNCiMgQ29uZnVzaW9uIG1hdHJpeCBmb3IgdGhyZXNob2xkIG9mIDElDQpkZlByZWRpY3Rpb24uY29uZnVzaW9uID0gdGFibGUoZGZQcmVkaWN0aW9uLnRlc3QkU0FSUy5Db3YuMi5leGFtLnJlc3VsdCwgZGZQcmVkaWN0aW9uLmdsbS5wcmVkaWN0ID4gMC4wMSkNCnJvd25hbWVzKGRmUHJlZGljdGlvbi5jb25mdXNpb24pIDwtIGMoIlByZWRpY3RlZCBGQUxTRSIsIlByZWRpY3RlZCBUUlVFIik7DQpwcmludChkZlByZWRpY3Rpb24uY29uZnVzaW9uKTsNCg0KIyBGYWxzZSBuZWdhdGl2ZSBlcnJvciByYXRlIChUeXBlIElJIGVycm9yKQ0KZGZQcmVkaWN0aW9uLnR5cGUyZXJyb3IgPSBkZlByZWRpY3Rpb24uY29uZnVzaW9uWzEsMV0vIChkZlByZWRpY3Rpb24uY29uZnVzaW9uWzEsMV0rZGZQcmVkaWN0aW9uLmNvbmZ1c2lvblsyLDJdKQ0KcHJpbnQocGFzdGUoIlRoZSBwcm9wb3J0aW9uIG9mIGVycm9ycyBvZiBUeXBlIElJIGlzICIsYXMuY2hhcmFjdGVyKGRmUHJlZGljdGlvbi50eXBlMmVycm9yKSkpOw0KYGBgDQoNClVzaW5nIHRoZSBjb25mdXNpb24gbWF0cml4LCB3ZSB1bmRlcnN0YW5kIHRoYXQgdGhlIFR5cGUgSSBlcnJvciB1c2luZyBvdXIgbW9kZWwgaXMgcmVhbGx5IHNtYWxsLiBIZW5jZSwgaWYgcGF0aWVudCBpbmZlY3RlZCBieSB0aGUgY292aWQgd2l0aCBvdXIgbW9kZWwgd2lsbCBiZSBjbGFzc2lmaWVkIGJ5IG91ciBtb2RlbCBhcyBpbmZlY3RlZCB3aXRoIGhpZ2ggcHJvYmFiaWxpdHkuIA0KDQpIb3dldmVyLCBUeXBlIElJIGVycm9ycyBhcmUgbGlrZWx5LiBUaGlzIG1lYW5zIHRoYXQgb3VyIHByZWRpY3Rpb24gc2hvdWxkIGJlIHRydXN0IG9ubHkgd2hlbiBhIHBhdGllbnQgaXMgY2xhc3NpZmllZCBhcyBub24taW5mZWN0ZWQuDQoNCg0KTm93IHdlIHBsb3QgYmVsb3cgdGhlIFJPQyBjdXJ2ZSBmb3Igb3VyIG1vZGVsLiBMZXQgdXMgcmVjYWxsIHRoYXQgYSByYW5kb20gY2xhc3NpZmllciBpcyBleHBlY3RlZCB0byBnaXZlIHBvaW50cyBseWluZyBhbG9uZyB0aGUgZGlhZ29uYWwgKEZQUiA9IFRQUikuIEEgdHlwaWNhbCBtZXRob2QgdG8gY29tcGFyZSB0aGUgcGVyZm9ybWFuY2Ugb2YgY2xhc3NpZmllcnMgaXMgdG8gbWVhc3VyZSB0aGUgYXJlYSB1bmRlciB0aGUgUk9DIGN1cnZlIChBVUMpIGZvciBlYWNoIG9mIHRoZW0gYW5kIHRvIGtlZXAgd2l0aCB0aGUgb25lIHdpdGggdGhlIGxhcmdlciB2YWx1ZS4gSW4gcHJhY3RpY2UsIHRoZSBBVUMgcGVyZm9ybXMgd2VsbCBhcyBhIGdlbmVyYWwgbWVhc3VyZSBvZiBwcmVkaWN0aXZlIGFjY3VyYWN5Lg0KDQpgYGB7cn0NCiMgUGxvdHRpbmcgUk9DUiBjdXJ2ZQ0KZGZQcmVkaWN0aW9uLlJPQ1JwcmVkID0gcHJlZGljdGlvbihkZlByZWRpY3Rpb24uZ2xtLnByZWRpY3QsIGRmUHJlZGljdGlvbi50ZXN0JFNBUlMuQ292LjIuZXhhbS5yZXN1bHQpDQpkZlByZWRpY3Rpb24uUk9DUnBlcmYgPSBwZXJmb3JtYW5jZShkZlByZWRpY3Rpb24uUk9DUnByZWQsICJ0cHIiLCAiZnByIikNCnBsb3QoZGZQcmVkaWN0aW9uLlJPQ1JwZXJmLCBjb2xvcml6ZT1UUlVFLCBwcmludC5jdXRvZmZzLmF0PXNlcSgwLDEsYnk9MC4xKSwgdGV4dC5hZGo9YygtMC4yLDEuNykpDQpgYGANCg0KQXMgYWxyZWFkeSBzZWVuLCBvdXIgbW9kZWwgaXMgYWJsZSB0byBleHRyYWN0IHJlbGV2YW50IGluc2lnaHQgZnJvbSBibG9vZCdzIGRhdGEgb2YgdGhlIHBhdGllbnQuIFRvIGNvbmNsdWRlLCBsZXQgdXMgcGxvdCB0aGUgZmluYWwgcmVzdWx0cy4NCg0KYGBge3J9DQojIENyZWF0aW5nIGEgZGF0YWZyYW1lIHdpdGggdmFyaWFibGVzIGFuZCBwcmVkaWN0ZWQgdmFsdWVzIG9mIFNBUlMgcmVzdWx0cw0KZGZQcmVkaWN0aW9uLnByZWRpY3QuZGF0YWZyYW1lIDwtIGRhdGEuZnJhbWUoDQogIHByb2JhYmlsaXR5Lm9mLmhhdmluZy5TQVJTPWRmUHJlZGljdGlvbi5nbG0kZml0dGVkLnZhbHVlcywNCiAgTGV1a29jeXRlcz1kZlByZWRpY3Rpb24udHJhaW4kTGV1a29jeXRlcywNCiAgUGF0aWVudC5hZ2UucXVhbnRpbGUgPSBkZlByZWRpY3Rpb24udHJhaW4kUGF0aWVudC5hZ2UucXVhbnRpbGUsDQogIEVvc2lub3BoaWxzID0gZGZQcmVkaWN0aW9uLnRyYWluJEVvc2lub3BoaWxzLA0KICBNb25vY3l0ZXMgPSBkZlByZWRpY3Rpb24udHJhaW4kTW9ub2N5dGVzLA0KICBQbGF0ZWxldHMgPSBkZlByZWRpY3Rpb24udHJhaW4kUGxhdGVsZXRzLA0KICBQcm90ZWluYS5DLnJlYXRpdmEubWcuZEwgPSBkZlByZWRpY3Rpb24udHJhaW4kUHJvdGVpbmEuQy5yZWF0aXZhLm1nLmRMKQ0KcGxvdDEgPSBnZ3Bsb3QoZGF0YT1kZlByZWRpY3Rpb24ucHJlZGljdC5kYXRhZnJhbWUsIGFlcyh4PVBhdGllbnQuYWdlLnF1YW50aWxlLCB5PXByb2JhYmlsaXR5Lm9mLmhhdmluZy5TQVJTKSkgKw0KICBnZW9tX3BvaW50KGFlcyhjb2xvcj1QYXRpZW50LmFnZS5xdWFudGlsZSksIHNpemU9NCkrIHRoZW1lKGF4aXMudGl0bGUueSA9IGVsZW1lbnRfdGV4dChzaXplPTkpKSArIHRoZW1lKGxlZ2VuZC50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT05KSkNCnBsb3QyID0gZ2dwbG90KGRhdGE9ZGZQcmVkaWN0aW9uLnByZWRpY3QuZGF0YWZyYW1lLCBhZXMoeD1MZXVrb2N5dGVzLCB5PXByb2JhYmlsaXR5Lm9mLmhhdmluZy5TQVJTKSkgKw0KICBnZW9tX3BvaW50KGFlcyhjb2xvcj1MZXVrb2N5dGVzKSwgc2l6ZT00KSsgdGhlbWUoYXhpcy50aXRsZS55ID0gZWxlbWVudF90ZXh0KHNpemU9OSkpICsgdGhlbWUobGVnZW5kLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTkpKQ0KcGxvdDMgPSBnZ3Bsb3QoZGF0YT1kZlByZWRpY3Rpb24ucHJlZGljdC5kYXRhZnJhbWUsIGFlcyh4PU1vbm9jeXRlcywgeT1wcm9iYWJpbGl0eS5vZi5oYXZpbmcuU0FSUykpICsNCiAgZ2VvbV9wb2ludChhZXMoY29sb3I9TW9ub2N5dGVzKSwgc2l6ZT00KSsgdGhlbWUoYXhpcy50aXRsZS55ID0gZWxlbWVudF90ZXh0KHNpemU9OSkpICsgdGhlbWUobGVnZW5kLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTkpKQ0KcGxvdDQgPSBnZ3Bsb3QoZGF0YT1kZlByZWRpY3Rpb24ucHJlZGljdC5kYXRhZnJhbWUsIGFlcyh4PUVvc2lub3BoaWxzLCB5PXByb2JhYmlsaXR5Lm9mLmhhdmluZy5TQVJTKSkgKw0KICBnZW9tX3BvaW50KGFlcyhjb2xvcj1Fb3Npbm9waGlscyksIHNpemU9NCkrIHRoZW1lKGF4aXMudGl0bGUueSA9IGVsZW1lbnRfdGV4dChzaXplPTkpKSArIHRoZW1lKGxlZ2VuZC50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT05KSkNCnBsb3Q1ID0gZ2dwbG90KGRhdGE9ZGZQcmVkaWN0aW9uLnByZWRpY3QuZGF0YWZyYW1lLCBhZXMoeD1QbGF0ZWxldHMsIHk9cHJvYmFiaWxpdHkub2YuaGF2aW5nLlNBUlMpKSArDQogIGdlb21fcG9pbnQoYWVzKGNvbG9yPVBsYXRlbGV0cyksIHNpemU9NCkgKyB0aGVtZShheGlzLnRpdGxlLnkgPSBlbGVtZW50X3RleHQoc2l6ZT05KSkgKyB0aGVtZShsZWdlbmQudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9OSkpDQpwbG90NiA9IGdncGxvdChkYXRhPWRmUHJlZGljdGlvbi5wcmVkaWN0LmRhdGFmcmFtZSwgYWVzKHg9UHJvdGVpbmEuQy5yZWF0aXZhLm1nLmRMLCB5PXByb2JhYmlsaXR5Lm9mLmhhdmluZy5TQVJTKSkgKw0KICBnZW9tX3BvaW50KGFlcyhjb2xvcj1Qcm90ZWluYS5DLnJlYXRpdmEubWcuZEwpLCBzaXplPTQpICsgdGhlbWUoYXhpcy50aXRsZS55ID0gZWxlbWVudF90ZXh0KHNpemU9OSkpICsgdGhlbWUobGVnZW5kLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTkpKQ0KIyBQbG90dGluZyB0aGUgdmFsdWVzDQpncmlkLmFycmFuZ2UocGxvdDEsIHBsb3QyLCBwbG90MywgcGxvdDQsIHBsb3Q1LCBwbG90NiwgbmNvbD0yICwgbnJvdyA9IDMpDQpgYGANCg0KDQoNCiMgRmluZGluZyBkaXNlYXNlKHMpIHdpdGggc3ltcHRvbXMgc2ltaWxhciB0byBDb3ZpZA0KDQpJbiB0aGlzIHNlY3Rpb24sIHdlIHdhbnQgdG8gZmluZCBkaXNlYXNlKHMpIHdpdGggc3ltcHRvbXMgc2ltaWxhciB0byB0aGUgb25lIG9mIHRoZSBDb3ZpZCAxOS4gU3VjaCBpbmZvcm1hdGlvbiBjb3VsZCBiZSB1c2VmdWwgdG8gZGVjcmVhc2UgdGhlIHJhdGUgb2YgZmFsc2UgcG9zaXRpdmUgY2FzZSBpbiBob3NwaXRhbHMgYW5kIGFsc28gdG8gaW1wcm92ZSB0aGUgcXVhbGl0eSBvZiBkYXRhc2V0cyBwcm92aWRlZCBvbiB0aGUgY292aWQgMTkuDQoNCiMjIENvcnJlbGF0aW9ucw0KDQpXZSBoYXZlIHRvIHNraXAgUGFyYWluZmx1ZW56YSAyIGFzIHRoZXJlIGFyZSBubyBjYXNlcyBmb3IgaXQuDQoNCmBgYHtyfQ0KZGZEaXNlYXNlcyA8LSBkZkRpc2Vhc2VzWyxjb2xuYW1lcyhkZkRpc2Vhc2VzKSAhPSAnUGFyYWluZmx1ZW56YS4yJ107DQpgYGANCg0KTm93IGxldOKAmXMgcGxvdCB0aGUgQ3JhbWVyVuKAmXMgY29ycmVsYXRpb24gcGxvdCB0byBjaGVjayB0aGUgY29ycmVsYXRpb24gYmV0d2VlbiBjYXRlZ29yaWNhbCB2YXJpYWJsZXMuDQoNCg0KYGBge3J9DQojIENyYW1lclYgQ29ycmVsYXRpb24gdG8gY2hlY2sgZm9yIGFueSBjb3JyZWxhdGlvbiBiZXR3ZWVuIGNhdGFnb3JpY2FsIHZhcmlhYmxlcw0KIyBFeGNlcHQgUGF0aWVudCdzIGFnZSwgYWxsIG90aGVyIHZhcmlhYmxlcyBhcmUgY2F0YWdvcmljYWwoYmluYXJ5KQ0KZGZEaXNlYXNlcy5jb3JyICA9IFBhaXJBcHBseShkZkRpc2Vhc2VzWyxuYW1lcyhkZkRpc2Vhc2VzKSAhPSAiUGF0aWVudC5hZ2UucXVhbnRpbGUiXSwgQ3JhbWVyViwgc3ltbWV0cmljID0gVFJVRSkNCiMgRGlzcGxheWluZyBjb3JyZWxhdGlvbiB3aXRoIHZhcmlhYmxlIFNBUlNfQ09WMl9SZXN1bHQNCmRmRGlzZWFzZXMuY29yclssJ1NBUlMuQ292LjIuZXhhbS5yZXN1bHQnXQ0KIyBDb3JyZWxhdGlvbiBwbG90DQpjb3JycGxvdChkZkRpc2Vhc2VzLmNvcnIgLCBtZXRob2QgPSAic3F1YXJlIiwgdHlwZSA9ICJsb3dlciIsIG51bWJlci5jZXg9MC41KQ0KYGBgDQoNClJoaW5vdmlydXNfT1JfRW50ZXJvdmlydXMgc2VlbXMgdG8gYmUgdGhlIGRpc2Vhc2UgdGhlIG1vc3QgY29ycmVsYXRlZCB3aXRoIG91ciB0YXJnZXQgKHdpdGggdGhlIGNvcnJlbGF0aW9uIHZhbHVlIG9mIDAuMTUxNykuIEluIHRoZSBuZXh0IHNlY3Rpb24sIHdlIGNvbmR1Y3QgYSBzdGF0aXN0aWNhbCB0ZXN0IHRvIGlkZW50aWZ5IGlmIHRoaXMgY29ycmVsYXRpb24gaXMgc3RhdGljYWxseSBzaWduaWZpY2FudC4NCg0KIyMgTGlrZWxpaG9vZCBSYXRpbyBUZXN0DQoNCldlIHBlcmZvcm0gTGlrZWxpaG9vZCBSYXRpbyBUZXN0cyB0byBpZGVudGlmeSBkaXNlYXNlcyB0aGF0IHNob3cgYSBzaWduaWZpY2FudCBjb3JyZWxhdGlvbiB3aXRoIHRoZSBpbmZlY3Rpb24gc3RhdHVzIG9mIHBhdGllbnQgYnkgdGhlIFNBUlMgQ292IDIuDQoNCmBgYHtyfQ0KcHZhbHVlX0xSVGVzdCA8LSBmdW5jdGlvbih2YXIpew0KICBnbG9iYWxudWxsIDwtIGdsbShTQVJTLkNvdi4yLmV4YW0ucmVzdWx0ICB+IDEsIGRhdGE9ZGZEaXNlYXNlcyk7DQogIGFsdGVybmF0aXZlIDwtIGdsbShwYXN0ZSgiU0FSUy5Db3YuMi5leGFtLnJlc3VsdCIsICJ+IiwgYXMuY2hhcmFjdGVyKHZhcikpLGRhdGE9ZGZEaXNlYXNlcyk7DQogIHJlc19scnRlc3QgPC0gbHJ0ZXN0KGdsb2JhbG51bGwsIGFsdGVybmF0aXZlKTsNCiAgcmVzX2xydGVzdCRgUHIoPkNoaXNxKWBbMl07DQp9DQpuYW1lc19mZWF0dXJlcyA8LSBjb2xuYW1lcyhkZkRpc2Vhc2VzKTsNCm5hbWVzX2ZlYXR1cmVzIDwtIG5hbWVzX2ZlYXR1cmVzWyAhKG5hbWVzX2ZlYXR1cmVzICVpbiUgYygnU0FSUy5Db3YuMi5leGFtLnJlc3VsdCcsJ1BhdGllbnQuYWdlLnF1YW50aWxlJykpXTsNCnB2YWx1ZXMgPC0gc2FwcGx5KG5hbWVzX2ZlYXR1cmVzLCBwdmFsdWVfTFJUZXN0KTsNCm5hbWVzKHB2YWx1ZXMpIDwtIG5hbWVzX2ZlYXR1cmVzOw0KcHZhbHVlc1tvcmRlcihwdmFsdWVzLCBkZWNyZWFzaW5nPUZBTFNFKV07DQpgYGANCk91ciBpbnR1aXRpb24gb2YgdGhlIHByZXZpb3VzIHNlY3Rpb24gaXMgY29uZmlybWVkIGJ5IHRoZSBMaWtlbGlob29kIFJhdGlvIFRlc3QuIFRoZSBzeW1wdG9tcyBvZiBSaGlub3ZpcnVzIGFyZSBjbG9zZSB0byB0aGUgU0FSUyBDb1YtMiB2aXJ1cyBhbmQgbW9zdGx5IGZvdW5kIGluIHRoZSBub3NlIChSaGlub3ZpcnVzKS4gVGhlcmVmb3JlLCB0aGUgcHJvYmFiaWxpdHkgb2YgaGF2aW5nIFNBUlMgZGVjcmVhc2VzIGFzIFJoaW5vdmlydXMgaXMgZGV0ZWN0ZWQuIA0KDQpIZW5jZSBob3NwaXRhbHMgc2hvdWxkIGRpc3Rpbmd1aXNoIGJldHdlZW4gYSBwZXJzb24gd2l0aCBSaGlub3ZpcnVzIGFuZCBhIHBlcnNvbiB3aXRoIENPVklELTE5LiBTZXBhcmF0aW5nIHBhdGllbnRzIHdpdGggUmhpbm92aXJ1cyBhbmQgQ292aWQtMTkgY291bGQgaGVscCB0byByZWxpZXZlIGNvdmlkIG1lZGljYWwgZ3JvdXBzIGFuZCB0byBlbmQgdXAgd2l0aCBkYXRhc2V0cyB3aXRoIGxlc3MgZmFsc2UgcG9zaXRpdmUgcGF0aWVudHMuDQoNCg==