Choose best dataset
We now want to find the best dataset & cell line for our subsequent analysis - that is to show the performance vs fitness correlation. The argument here is that we want to choose a boolean model dataset that has a large enough fitness value range combined with a large TP and/or MCC value range, since with smaller value ranges it would be harder to distinguish the difference of the estimated distributions of the fitness scores belonging to different performance classes (i.e. models’ fitnesses that belong to different classification groups with the metric being either the number of TPs or the MCC score).
The next summary statistics tables and box-plots will help us determine exactly which cell line and dataset to use:
cell.specific.model.data = matrix(data = NA, nrow = length(cell.lines), ncol = 11)
rownames(cell.specific.model.data) = cell.lines
colnames(cell.specific.model.data) = c("fitness range", "Min fitness",
"Max fitness", "Mean fitness", "Median fitness", "MCC range", "Min MCC",
"Max MCC", "Mean MCC", "Median MCC", "Max TPR")
for (cell.line in cell.lines) {
models.fitness = models.fitness.per.cell.line[[cell.line]]
models.mcc = models.mcc.per.cell.line[[cell.line]]
models.tp = models.tp.per.cell.line[[cell.line]]
fit.summary = unclass(summary(models.fitness))
mcc.summary = unclass(summary(models.mcc))
max.tpr = max(models.tp) / length(observed.synergies.per.cell.line[[cell.line]])
cell.specific.model.data[cell.line, "fitness range"] = fit.summary["Max."] - fit.summary["Min."]
cell.specific.model.data[cell.line, "Min fitness"] = fit.summary["Min."]
cell.specific.model.data[cell.line, "Max fitness"] = fit.summary["Max."]
cell.specific.model.data[cell.line, "Mean fitness"] = fit.summary["Mean"]
cell.specific.model.data[cell.line, "Median fitness"] = fit.summary["Median"]
cell.specific.model.data[cell.line, "MCC range"] = mcc.summary["Max."] - mcc.summary["Min."]
cell.specific.model.data[cell.line, "Min MCC"] = mcc.summary["Min."]
cell.specific.model.data[cell.line, "Max MCC"] = mcc.summary["Max."]
cell.specific.model.data[cell.line, "Mean MCC"] = mcc.summary["Mean"]
cell.specific.model.data[cell.line, "Median MCC"] = mcc.summary["Median"]
cell.specific.model.data[cell.line, "Max TPR"] = max.tpr
}
# color columns
fit.breaks = quantile(cell.specific.model.data[,"fitness range"], probs = seq(.05, .95, .05), na.rm = TRUE)
fit.colors = round(seq(255, 40, length.out = length(fit.breaks) + 1), 0) %>%
{paste0("rgb(255,", ., ",", ., ")")} # red
mcc.breaks = quantile(cell.specific.model.data[,"MCC range"], probs = seq(.05, .95, .05), na.rm = TRUE)
mcc.colors = round(seq(255, 40, length.out = length(mcc.breaks) + 1), 0) %>%
{paste0("rgb(", ., ",255,", ., ")")} # green
caption.title = "Table 1: Fitness, MCC scores and TP (True positives) for the Cell-specific model predictions across 8 Cell Lines"
datatable(data = cell.specific.model.data,
options = list(dom = "t"), # just show the table
caption = htmltools::tags$caption(caption.title, style="color:#dd4814; font-size: 18px")) %>%
formatRound(1:11, digits = 3) %>%
formatStyle(columns = c("fitness range"), backgroundColor = styleInterval(fit.breaks, fit.colors)) %>%
formatStyle(columns = c("MCC range"), backgroundColor = styleInterval(mcc.breaks, mcc.colors))
random.model.data = matrix(data = NA, nrow = length(cell.lines), ncol = 11)
rownames(random.model.data) = cell.lines
colnames(random.model.data) = c("fitness range", "Min fitness",
"Max fitness", "Mean fitness", "Median fitness", "MCC range", "Min MCC",
"Max MCC", "Mean MCC", "Median MCC", "Max TPR")
for (cell.line in cell.lines) {
models.fitness = random.models.fitness.per.cell.line[[cell.line]]
models.mcc = random.models.mcc.per.cell.line[[cell.line]]
models.tp = random.models.tp.per.cell.line[[cell.line]]
fit.summary = unclass(summary(models.fitness))
mcc.summary = unclass(summary(models.mcc))
max.tpr = max(models.tp) / length(observed.synergies.per.cell.line[[cell.line]])
random.model.data[cell.line, "fitness range"] = fit.summary["Max."] - fit.summary["Min."]
random.model.data[cell.line, "Min fitness"] = fit.summary["Min."]
random.model.data[cell.line, "Max fitness"] = fit.summary["Max."]
random.model.data[cell.line, "Mean fitness"] = fit.summary["Mean"]
random.model.data[cell.line, "Median fitness"] = fit.summary["Median"]
random.model.data[cell.line, "MCC range"] = mcc.summary["Max."] - mcc.summary["Min."]
random.model.data[cell.line, "Min MCC"] = mcc.summary["Min."]
random.model.data[cell.line, "Max MCC"] = mcc.summary["Max."]
random.model.data[cell.line, "Mean MCC"] = mcc.summary["Mean"]
random.model.data[cell.line, "Median MCC"] = mcc.summary["Median"]
random.model.data[cell.line, "Max TPR"] = max.tpr
}
# color columns
fit.breaks = quantile(random.model.data[,"fitness range"], probs = seq(.05, .95, .05), na.rm = TRUE)
fit.colors = round(seq(255, 40, length.out = length(fit.breaks) + 1), 0) %>%
{paste0("rgb(255,", ., ",", ., ")")} # red
mcc.breaks = quantile(random.model.data[,"MCC range"], probs = seq(.05, .95, .05), na.rm = TRUE)
mcc.colors = round(seq(255, 40, length.out = length(mcc.breaks) + 1), 0) %>%
{paste0("rgb(", ., ",255,", ., ")")} # green
caption.title = "Table 2: Fitness, MCC scores and TP (True positives) for the random model predictions across 8 Cell Lines"
datatable(data = random.model.data,
options = list(dom = "t"), # just show the table
caption = htmltools::tags$caption(caption.title, style="color:#dd4814; font-size: 18px")) %>%
formatRound(1:11, digits = 3) %>%
formatStyle(columns = c("fitness range"), backgroundColor = styleInterval(fit.breaks, fit.colors)) %>%
formatStyle(columns = c("MCC range"), backgroundColor = styleInterval(mcc.breaks, mcc.colors))
The below box plots compare the MCC values and fitness scores across all cell lines between the cell-specific models (trained to steady state) and the random ones (trained to proliferation):
num.of.models = nrow(random.models.stable.state)
data.list = list()
for (cell.line in cell.lines) {
cell.line.vec = as.data.frame(rep(cell.line, num.of.models), stringsAsFactors = FALSE)
models.mcc.cell.specific = remove_rownames(as.data.frame(models.mcc.per.cell.line[[cell.line]]))
models.mcc.random = remove_rownames(as.data.frame(random.models.mcc.per.cell.line[[cell.line]]))
models.fitness.cell.specific = remove_rownames(as.data.frame(models.fitness.per.cell.line[[cell.line]]))
models.fitness.random = remove_rownames(as.data.frame(random.models.fitness.per.cell.line[[cell.line]]))
data.list[[cell.line]] = bind_cols(cell.line.vec, models.mcc.cell.specific,
models.mcc.random, models.fitness.cell.specific, models.fitness.random)
}
data = bind_rows(data.list)
colnames(data) = c("cell.line", "MCC cell-specific", "MCC random",
"fitness cell-specific", "fitness random")
# Note the cell-specific models have NaN MCC values
ggboxplot(data, x = "cell.line", y = c("MCC cell-specific", "MCC random"),
palette = brewer.pal(3, "Set1"), merge = "asis",
xlab = "Cell Lines", ylab = "MCC values", add = "point", add.params = list(size = 0.5))

ggboxplot(data, x = "cell.line", y = c("fitness cell-specific", "fitness random"),
palette = brewer.pal(3, "Set1"), merge = "asis",
xlab = "Cell Lines", ylab = "Fitness values", add = "point", add.params = list(size = 0.5))

So, from the two tables and the two box plots above we conclude that:
- In general, the random models offer a larger range of fitness values when their stable states are matched against the steady state of each particular cell line. This is something we expected since these models weren’t fitted to a specific cell-line steady state (meaning that they weren’t chosen from the
Gitsbe
module as the 3 best from each simulation that match that steady state as best as possible) but rather to a more generic state of proliferation. Thus, they represent a set of models with larger variation in terms of structure (boolean model equations) compared to the cell-specific generated ones.
- The cell-specific models have always a larger maximum fitness and median value compared to the random ones for each respective cell line.
- In each cell line (with the expection of SW620) there are always cell-specific models that show better performance than the random ones (have a higher MCC value)
All in all, we will use the random models data, contrasted to the steady state of the A498
cell line (see Table 2). This dataset has the largest fitness and MCC value range (second largest TPR value as well) combined in both tables above.
best.cell.line = "A498"
fit = random.models.fitness.per.cell.line[[best.cell.line]]
tp = random.models.tp.per.cell.line[[best.cell.line]]
mcc = random.models.mcc.per.cell.line[[best.cell.line]]
Statistical Analysis
Data preprocessing
Firstly, we filter the data by finding the unique models - those that have strictly different boolean equations. Then, we take a random sample out of these (while stabilizing the seed number for reproducibility purposes):
# For reproducibility
set.seed(0)
sample.size = 1000
unique.models = rownames(unique(random.models.link.operator))
unique.models.sample = sample(unique.models, size = sample.size)
fit.unique = fit[names(fit) %in% unique.models.sample]
tp.unique = tp[names(tp) %in% unique.models.sample]
mcc.unique = mcc[names(mcc) %in% unique.models.sample]
df = as.data.frame(cbind(fit.unique, mcc.unique, tp.unique))
Note that the fitness and MCC score are continuous variables while the number of true positives (TP) is discrete.
Correlation Plots
Then, we check if the our data is normally distributed (using the Shapiro-Wilk test for normality and the Q-Q plots):
ggqqplot(data = df, x = "fit.unique", ylab = "Fitness")

ggqqplot(data = df, x = "mcc.unique", ylab = "MMC")

shapiro.test(x = sample(fit.unique))
Shapiro-Wilk normality test
data: sample(fit.unique)
W = 0.99266, p-value = 7.375e-05
shapiro.test(x = sample(mcc.unique))
Shapiro-Wilk normality test
data: sample(mcc.unique)
W = 0.91827, p-value < 2.2e-16
From the above results we observe that both the fitness and MCC scores are surely not normally distributed (with statistical significance). Thus, we will use non-parametric correlation scores, namely the Spearman and Kendall rank-based correlation tests, to check the correlation between the two continuous variables (models fitness values and their corresponding MCC score):
ggscatter(df, x = "mcc.unique", y = "fit.unique",
title = "Fitness vs Performance (MCC) - Spearman Correlation",
add = "reg.line", conf.int = TRUE,
cor.coef = TRUE, cor.coeff.args = list(method = "spearman", label.x.npc = 0.7, label.y.npc = 1),
xlab = "MCC scores", ylab = "Fitness Values")

ggscatter(df, x = "mcc.unique", y = "fit.unique",
title = "Fitness vs Performance (MCC) - Kendall Correlation",
add = "reg.line", conf.int = TRUE,
cor.coef = TRUE, cor.coeff.args = list(method = "kendall", label.x.npc = 0.7, label.y.npc = 1),
xlab = "MCC scores", ylab = "Fitness Values")

From the correlation plots above, we observe a weak/small positive correlation between performance and fitness to the steady state.
To assess the correlation between the number of TP predictions of the models (discrete variable) and the models fitness (continuous variable), we construct a predictor of the categorical variable from the continuous variable: if the resulting classifier has a high degree of fit we can conclude the two variables share a relationship and are indeed correlated. Since there are more than 2 TP classes (values) in the dataset, we will use Multinomial Logistic Regression and fit log-linear models via neural networks:
model.classifier = multinom(data = df, formula = tp.unique ~ fit.unique)
# weights: 18 (10 variable)
initial value 1791.759469
iter 10 value 1361.771381
iter 20 value 1356.825440
iter 30 value 1356.792829
iter 40 value 1356.765176
final value 1356.764853
converged
pseudo.r2.measures = pR2(model.classifier)
fitting null model for pseudo-r2
# weights: 12 (5 variable)
initial value 1791.759469
iter 10 value 1415.698062
final value 1415.682090
converged
pseudo.r2.measures["McFadden"]
McFadden
0.04161756
To measure the goodness-of-fit for our model, there are several measures proposed for logistic regression. We emphasize on the McFaddens’s pseudo-\(R^2\) measure for our classifier, for which a value of \(0.2-0.4\) would indicate an excellent fit [source]. Since we found less, we can also assume that there is only a weak/small positive correlation between the number of TPs and the fitness score of the models.
Next, we will next proceed with a more elaborate analysis, where the models will be split to different performance classes (TP or MCC score-derived) and the statistical correlation between the individual groups will be tested (with regards to their fitness scores).
TP-class vs fitness
First, some box and density plots to see practically what and where is the difference between the models fitness values belonging to different TP-classes:
# Box plots
ggboxplot(df, x = "tp.unique", y = "fit.unique", color = "tp.unique",
palette = usefun:::colors.100[1:length(unique(tp.unique))],
xlab = "True Positives (TP)", ylab = "Fitness values")

# Density Plots
densities = list()
for (tp.num in sort(unique(tp.unique))) {
x = df %>%
filter(tp.unique == tp.num) %>%
select_at(.vars = c("fit.unique"))
den = density(x$fit.unique)
densities[[paste0(tp.num, " (", den$n, ")")]] = den
}
make_multiple_density_plot(densities, legend.title = "TP classes (#models)",
title = "Density Estimation", x.axis.label = "Fitness score")

As we can see from the above plots, there is positive correlation between the classes that predicted 0,1 and 2 TP synergies as well as between the 0 TP class and the 3,4,5-TP classes.
Mathematically, we show that the group distributions are indeed different using the Kruskal-Wallis Rank Sum Test:
# Hypothesis testing: Are the location parameters of the distribution of x the same in each group?
kruskal.test(x = df[,"fit.unique"], g = df[, "tp.unique"])
Kruskal-Wallis rank sum test
data: df[, "fit.unique"] and df[, "tp.unique"]
Kruskal-Wallis chi-squared = 108.06, df = 5, p-value < 2.2e-16
As the p-value is less than the significance level \(0.05\), we can conclude that there are significant differences between the different groups of fitness values. To see exactly which pair of groups are different we perform pairwise Wilcoxon rank sum tests and we draw a heatmap of the each test’s p-value:
res = pairwise.wilcox.test(x = df[,"fit.unique"], g = df[, "tp.unique"], p.adjust.method = "BH")
p.value.mat = res$p.value
col_fun = colorRamp2(breaks = c(0, 0.05, 0.5, 1), c("green", "white", "orange", "red"))
ht = Heatmap(matrix = p.value.mat, cluster_rows = FALSE, cluster_columns = FALSE,
na_col = "white", name = "p-value", col = col_fun, column_names_rot = 0,
row_title = "TP", row_title_rot = 0, row_names_side = "left",
column_title = "P-values (Pairwise Wilcoxon tests) - TP-classified fitnesses", column_title_gp = gpar(fontsize = 20),
cell_fun = function(j, i, x, y, width, height, fill) {
if (!is.na(p.value.mat[i,j]))
grid.text(sprintf("%.6f", p.value.mat[i, j]), x, y, gp = gpar(fontsize = 16))
})
draw(ht)

As we can see above there is significant difference between groups of fitness values belonging to models that predicted \(0\), \(1\) and \(2\) TP synergies, but not between these and the larger performant groups (\(TP=3,4,5\)) - with the exception of the group with \(TP=2\).
Note that the number of true positive predictions is an one-dimensional metric and by excluding the TN, FP and FNs we have a less-informant (and arguably incorrect) picture of the models performance classification. That’s why we will proceed with the more balanced MCC score for classifing the models fitnesses to different performance groups.
MCC-class vs fitness
Firstly, we perform a univariate k-means clustering to split the models MCC values to different classes and plot the data histogram. The number of MCC classes to split the data can be arbitrarily chosen and so we chose one less than the maximum number of TPs predicted:
num.of.mcc.classes = 5
mcc.class.ids = 1:num.of.mcc.classes
# find the clusters
res = Ckmeans.1d.dp(x = df[,"mcc.unique"], k = num.of.mcc.classes)
mcc.class.id = res$cluster
df = cbind(df, mcc.class.id)
plot_mcc_classes_hist(df[,"mcc.unique"], df[,"mcc.class.id"],
num.of.mcc.classes, mcc.class.ids)

Then we show some box and density plots to see practically what and where is the difference between the models fitness values belonging to different MCC-classes:
# Box plots
# if you want to add p-values on the boxplot
#mcc.class.id.cmps = list(c("1", "2"), c("1", "3"), c("3", "5"))
ggboxplot(df, x = "mcc.class.id", y = "fit.unique", color = "mcc.class.id",
palette = usefun:::colors.100[1:num.of.mcc.classes],
xlab = "MCC class", ylab = "Fitness values")

# + stat_compare_means(comparisons = mcc.class.id.cmps) + stat_compare_means()
# Density Plots
densities = list()
for (id in mcc.class.ids) {
x = df %>%
filter(mcc.class.id == id) %>%
select_at(.vars = c("fit.unique"))
den = density(x$fit.unique)
densities[[paste0(id, " (", res$size[id], ")")]] = den
}
make_multiple_density_plot(densities, legend.title = "MCC classes (#models)",
title = "Density Estimation", x.axis.label = "Fitness score")

As we can see from the above plots, there is positive correlation between the distribution of MCC scores in each class and the respective fitness scores, though not between the 3rd class and the 4th or 5th (it’s negative).
Note also that the 5th class has a lot less models than the rest. Mathematically, we show that the group distributions are indeed different using the Kruskal-Wallis Rank Sum Test:
# Hypothesis testing: Are the location parameters of the distribution of x the same in each group?
kruskal.test(x = df[,"fit.unique"], g = df[, "mcc.class.id"])
Kruskal-Wallis rank sum test
data: df[, "fit.unique"] and df[, "mcc.class.id"]
Kruskal-Wallis chi-squared = 135.52, df = 4, p-value < 2.2e-16
As the p-value is less than the significance level \(0.05\), we can conclude that there are significant differences between the different groups of fitness values.
Next, to show that most of the groups are statistically different, we perform pairwise Wilcoxon rank sum tests and draw a heatmap of the each test’s p-value:
pair.res = pairwise.wilcox.test(x = df[,"fit.unique"], g = df[, "mcc.class.id"], p.adjust.method = "BH")
p.value.mat = pair.res$p.value
# same: compare_means(fit.unique ~ mcc.class.id, data = df)
col_fun = colorRamp2(breaks = c(0, 0.05, 0.5, 1), c("green", "white", "orange", "red"))
ht = Heatmap(matrix = p.value.mat, cluster_rows = FALSE, cluster_columns = FALSE,
na_col = "white", name = "p-value", col = col_fun, column_names_rot = 0,
row_title = "MCC class id", row_title_rot = 90, row_names_side = "left",
column_title = "P-values (Pairwise Wilcoxon tests) - MCC-classified fitnesses", column_title_gp = gpar(fontsize = 20),
cell_fun = function(j, i, x, y, width, height, fill) {
if (!is.na(p.value.mat[i,j]))
grid.text(sprintf("%.6f", p.value.mat[i, j]), x, y, gp = gpar(fontsize = 20))
})
draw(ht)

LS0tCnRpdGxlOiAiQXRvcG8gUGVyZm9ybWFuY2UgdnMgRml0bmVzcyBNb2RlbCBBbmFseXNpcyIKYXV0aG9yOiAiW0pvaG4gWm9ib2xhc10oaHR0cHM6Ly9naXRodWIuY29tL2JibG9kZm9uKSIKZGF0ZTogIkxhc3QgdXBkYXRlZDogYHIgZm9ybWF0KFN5cy50aW1lKCksICclZCAlQiwgJVknKWAiCm91dHB1dDoKICBodG1sX2RvY3VtZW50OgogICAgY3NzOiBjc3Mvc3R5bGUuY3NzCiAgICB0aGVtZTogdW5pdGVkCiAgICB0b2M6IHRydWUKICAgIHRvY19mbG9hdDoKICAgICAgY29sbGFwc2VkOiBmYWxzZQogICAgICBzbW9vdGhfc2Nyb2xsOiB0cnVlCiAgICBudW1iZXJfc2VjdGlvbnM6IGZhbHNlCiAgICBjb2RlX2ZvbGRpbmc6IGhpZGUKICAgIGNvZGVfZG93bmxvYWQ6IHRydWUKLS0tCgpgYGB7ciBSZW5kZXIgY29tbWFuZCwgZXZhbD1GQUxTRSwgaW5jbHVkZT1GQUxTRX0KI3JtYXJrZG93bjo6cmVuZGVyKGlucHV0ID0gIi4vcGVyZm9ybWFuY2VfdnNfZml0bmVzcy5SbWQiLCBvdXRwdXRfZm9ybWF0ID0gImh0bWxfZG9jdW1lbnQiLCBvdXRwdXRfZGlyID0gIi4uLy4uL2RvY3MvYXRvcG8vY2VsbC1saW5lcy0yNTAwLyIpCmBgYAoKIyMgSW50cm8gey19CgpUaGUgcHVycG9zZSBvZiB0aGlzIGFuYWx5c2lzIGlzIHRvIGZpbmQgYSBjb3JyZWxhdGlvbiBiZXR3ZWVuIHRoZSBib29sZWFuIG1vZGVscwoqKmZpdG5lc3MgdG8gYSBzdGVhZHkgc3RhdGUgYWN0aXZpdHkgcHJvZmlsZSoqIGFuZCB0aGVpciAqKnBlcmZvcm1hbmNlKiogaW4gdGVybXMgb2YgdGhlCm51bWJlciBvZiAqVHJ1ZSBQb3NpdGl2ZSogKFRQKSBzeW5lcmdpZXMgcHJlZGljdGVkIGFuZC9vciB0aGUgb3ZlcmFsbCAqTUNDIHNjb3JlKgooTWF0dGhld3MgQ29ycmVsYXRpb24gQ29lZmZpY2llbnQgc2NvcmUpLiBXZSB3YW50IHRvIHNob3cgdGhhdCAqKmEgY2xvc2VyIGZpdG5lc3MgCnRvIHRoZSBzdGVhZHkgc3RhdGUgc3VnZ2VzdHMgbW9yZSBwcmVkaWN0aXZlIG1vZGVscyoqLCBjb3Jyb2JvcmF0aW5nIHRodXMgb3VyIHByb29mIG9mIGNvbmNlcHQgb2YgdXNpbmcgYW4gZW5zZW1ibGUtYmFzZWQgYXBwcm9hY2ggd2hlcmUgbW9kZWxzIGFyZSB0cmFpbmVkIHRvd2FyZHMgYSAKc3BlY2lmaWMgc3RlYWR5IHN0YXRlIHNpZ25hbGluZyBwYXR0ZXJuIGZvciBkcnVnIGNvbWJpbmF0aW9uIHByZWRpY3Rpb25zLgoKVGhlIGJvb2xlYW4gbW9kZWwgZGF0YXNldHMgd2Ugd2lsbCB1c2UgYXJlIGluIHRvdGFsICQ5JDogb25lIGZvciBlYWNoIGNlbGwgbGluZSAKb2YgaW50ZXJlc3QgKDggY2VsbCBsaW5lcykgd2hlcmUgdGhlIG1vZGVscyB3ZXJlICoqZml0dGVkIHRvIGEgc3BlY2lmaWMgc3RlYWR5IHN0YXRlKiogaW4gZWFjaCAKY2FzZSBhbmQgb25lIGZvciB0aGUgc28tY2FsbGVkICoqcmFuZG9tIG1vZGVscyoqIHdoaWNoIHdlcmUgZ2VuZXJhdGVkICpyYW5kb21seSogaW4gCnRoZSBzZW5zZSB0aGF0IHdlcmUgZml0dGVkIG9ubHkgdG8gYSBwcm9saWZlcmF0aW9uIHN0YXRlIChzaW11bGF0aW9ucyB3ZXJlIGRvbmUgdXNpbmcgCnRoZSBEcnVnTG9naWNzIHNvZnR3YXJlIG1vZHVsZXMgYEdpdHNiZWAgYW5kIGBEcmFibWVgKS4KCkVhY2ggYm9vbGVhbiBtb2RlbCBkYXRhc2V0IGNvbnN0aXR1ZXMgb2Y6CgotIFRoZSAqKm1vZGVsIHByZWRpY3Rpb25zKiogZmlsZSB3aGljaCBoYXMgZm9yIGVhY2ggbW9kZWwgdGhlIHByZWRpY3Rpb24gZm9yIAplYWNoIGRydWcgY29tYmluYXRpb24gdGVzdGVkICgqMCogPSBubyBzeW5lcmd5IHByZWRpY3RlZCwgKjEqID0gc3luZXJneSAKcHJlZGljdGVkLCAqTkEqID0gY291bGRuJ3QgZmluZCBzdGFibGUgc3RhdGVzIGluIGVpdGhlciB0aGUgZHJ1ZyBjb21iaW5hdGlvbiAKaW5oaWJpdGVkIG1vZGVsIG9yIGluIGFueSBvZiB0aGUgdHdvIHNpbmdsZS1kcnVnIGluaGliaXRlZCBtb2RlbHMpCi0gVGhlICoqbW9kZWxzIHN0YWJsZSBzdGF0ZSoqIChvbmUgcGVyIG1vZGVsKS4gQSAqKmZpdG5lc3Mgc2NvcmUqKgpmb3IgZWFjaCBtb2RlbCBjYW4gZWFzaWx5IGJlIGNhbGN1bGF0ZWQgdGhlbiBieSBtYXRjaGluZyB0aGUgbW9kZWwncyBzdGFibGUgCnN0YXRlICh3aGljaCBpcyBzb21ldGhpbmcgaW5oZXJlbnQgaW4gdGhlIGJvb2xlYW4ncyBtb2RlbCBzdHJ1Y3R1cmUsIGEgdW5pcXVlIApmaXhwb2ludCBhdHRyYWN0b3IpIHdpdGggdGhlIHN0ZWFkeSBzdGF0ZSBvZiBpbnRlcmVzdCwgbm9kZSBwZXIgbm9kZS4KQSAqKmhpZ2hlciBmaXRuZXNzIHNjb3JlKiogd291bGQgbWVhbiBhIGJldHRlciBtYXRjaCBvZiBhIG1vZGVsJ3MgCnN0YWJsZSBzdGF0ZSB0byB0aGUgY2VsbCBsaW5lIGRlcml2ZWQgc3RlYWR5IHN0YXRlIChhIHBlcmZlY3QgbWF0Y2ggd291bGQgcmVzdWx0IAppbiBhIGZpdG5lc3Mgb2YgMSkuCi0gVGhlICoqbW9kZWxzIGxpbmsgb3BlcmF0b3JzKiogd2hpY2ggaXMgYSByZXByZXNlbnRhdGlvbiBvZiB0aGUgYm9vbGVhbiBlcXVhdGlvbnMKb2YgZWFjaCBtb2RlbC4gRWFjaCBib29sZWFuIGVxdWF0aW9uIGlzIGluIHRoZSBmb3JtOiAqKlRhcmdldCAqPSAoQWN0aXZhdG9yIE9SIEFjdGl2YXRvciBPUi4uLikgQU5EIE5PVCAoSW5oaWJpdG9yIE9SIEluaGliaXRvciBPUi4uLikqKiBhbmQgdGhlIGRpZmZlcmVuY2UgYmV0d2VlbiB0aGUgbW9kZWxzIGNhbiBiZSBmb3VuZCBpbiB0aGUgCipsaW5rIG9wZXJhdG9yKiAoKjEqID0gJ09SIE5PVCcsICowKiA9ICdBTkQgTk9UJywgb3IgYWJzZW50KSB3aGljaCBoYXMgYmVlbiAKbXV0YXRlZCAoY2hhbmdlZCkgdGhyb3VnaCB0aGUgZ2VuZXRpYyBhbGdvcml0aG0gaW4gYEdpdHNiZWAuIE5vdGUgdGhhdCB0aGUgZXF1YXRpb25zIHRoYXQgZG8gCm5vdCBoYXZlIGxpbmsgb3BlcmF0b3JzIGFyZSAqdGhlIHNhbWUgZm9yIGV2ZXJ5IG1vZGVsKiBhbmQgYXJlIHRodXMgZGlzY2FyZGVkLgotIFRoZSAqKm9ic2VydmVkIHN5bmVyZ2llcyoqIGZpbGUgd2hpY2ggbGlzdHMgdGhlIGRydWcgY29tYmluYXRpb25zIHRoYXQgd2VyZSAKb2JzZXJ2ZWQgYXMgc3luZXJnaXN0aWMgZm9yIGVhY2ggY2VsbCBsaW5lLgotIFRoZSAqKnN0ZWFkeSBzdGF0ZSoqIGZpbGUgd2hpY2ggbGlzdHMgdGhlIG5ldHdvcmsgbm9kZXMgKHByb3RlaW4sIGdlbmUsIGNvbXBsZXhlcwpuYW1lcywgZXRjKSBhbmQgdGhlaXIgYWN0aXZpdHkgdmFsdWUgKDAgb3IgMSwgcmVwcmVzZW50aW5nIGFuIGluaGliaXRlZCBvciBhY3RpdmUKbm9kZSByZXNwZWN0aXZlbHkpLiAKVGhpcyBpbnB1dCBpcyBwcm92aWRlZCBwZXIgY2VsbCBsaW5lIGFuZCBub3QgZm9yIHRoZSByYW5kb20gbW9kZWxzIHNpbmNlIHRoZXkgYXJlIGp1c3QgdHJhaW5lZCB0byBhIHByb2ZpbGVyYXRpb24gc3RhdGUuCgojIyBJbnB1dCB7LX0KCkxvYWRpbmcgbGlicmFyaWVzOgpgYGB7ciBMb2FkIGxpYnJhcmllcywgbWVzc2FnZSA9IEZBTFNFfQpsaWJyYXJ5KERUKQpsaWJyYXJ5KGdncHVicikKbGlicmFyeShlbWJhKQpsaWJyYXJ5KHVzZWZ1bikKbGlicmFyeShubmV0KQpsaWJyYXJ5KHBzY2wpCmxpYnJhcnkoQ29tcGxleEhlYXRtYXApCmxpYnJhcnkoY2lyY2xpemUpCmxpYnJhcnkoZHBseXIpCmxpYnJhcnkodGliYmxlKQpsaWJyYXJ5KENrbWVhbnMuMWQuZHApCmxpYnJhcnkoUkNvbG9yQnJld2VyKQpgYGAKCkZpcnN0IHdlIGxvYWQgdGhlIGNlbGwtc3BlY2lmaWMgaW5wdXQgZGF0YToKYGBge3IgQ2VsbC1zcGVjaWZpYyBJbnB1dCwgY2FjaGU9VFJVRX0KIyBDZWxsIExpbmVzCmNlbGwubGluZXMgPSBjKCJBNDk4IiwgIkFHUyIsICJEVTE0NSIsICJjb2xvMjA1IiwgIlNXNjIwIiwgIlNGMjk1IiwgIlVBQ0M2MiIsICJNREEtTUItNDY4IikKCmNlbGwubGluZS5kaXJzID0gc2FwcGx5KGNlbGwubGluZXMsIGZ1bmN0aW9uKGNlbGwubGluZSkgewogIHBhc3RlMChnZXR3ZCgpLCAiLyIsIGNlbGwubGluZSkKfSkKCiMgTW9kZWwgcHJlZGljdGlvbnMKbW9kZWwucHJlZGljdGlvbnMuZmlsZXMgPSBzYXBwbHkoY2VsbC5saW5lLmRpcnMsIGZ1bmN0aW9uKGNlbGwubGluZS5kaXIpIHsKICBwYXN0ZTAoY2VsbC5saW5lLmRpciwgIi9tb2RlbF9wcmVkaWN0aW9ucyIpCn0pCgptb2RlbC5wcmVkaWN0aW9ucy5wZXIuY2VsbC5saW5lID0gbGFwcGx5KG1vZGVsLnByZWRpY3Rpb25zLmZpbGVzLCAKICBmdW5jdGlvbihmaWxlKSB7CiAgICBnZXRfbW9kZWxfcHJlZGljdGlvbnMoZmlsZSkKICB9CikKCiMgT2JzZXJ2ZWQgc3luZXJnaWVzCm9ic2VydmVkLnN5bmVyZ2llcy5maWxlcyA9IHNhcHBseShjZWxsLmxpbmUuZGlycywgZnVuY3Rpb24oY2VsbC5saW5lLmRpcikgewogIHBhc3RlMChjZWxsLmxpbmUuZGlyLCAiL29ic2VydmVkX3N5bmVyZ2llcyIpCn0pCgpvYnNlcnZlZC5zeW5lcmdpZXMucGVyLmNlbGwubGluZSA9IGxhcHBseShvYnNlcnZlZC5zeW5lcmdpZXMuZmlsZXMsIAogIGZ1bmN0aW9uKGZpbGUpIHsKICAgIGdldF9vYnNlcnZlZF9zeW5lcmdpZXMoZmlsZSkKICB9CikKCiMgTW9kZWxzIFN0YWJsZSBTdGF0ZSAoMSBwZXIgbW9kZWwpCm1vZGVscy5zdGFibGUuc3RhdGUuZmlsZXMgPSBzYXBwbHkoY2VsbC5saW5lLmRpcnMsIGZ1bmN0aW9uKGNlbGwubGluZS5kaXIpIHsKICBwYXN0ZTAoY2VsbC5saW5lLmRpciwgIi9tb2RlbHNfc3RhYmxlX3N0YXRlIikKfSkKCm1vZGVscy5zdGFibGUuc3RhdGUucGVyLmNlbGwubGluZSA9IGxhcHBseShtb2RlbHMuc3RhYmxlLnN0YXRlLmZpbGVzLAogIGZ1bmN0aW9uKGZpbGUpIHsKICAgIGFzLm1hdHJpeChyZWFkLnRhYmxlKGZpbGUsIGNoZWNrLm5hbWVzID0gRkFMU0UpKQogIH0KKQoKIyBNb2RlbHMgTGluayBPcGVyYXRvcnMKbW9kZWxzLmxpbmsub3BlcmF0b3IuZmlsZXMgPSBzYXBwbHkoY2VsbC5saW5lLmRpcnMsIGZ1bmN0aW9uKGNlbGwubGluZS5kaXIpIHsKICBwYXN0ZTAoY2VsbC5saW5lLmRpciwgIi9tb2RlbHNfZXF1YXRpb25zIikKfSkKCm1vZGVscy5saW5rLm9wZXJhdG9ycy5wZXIuY2VsbC5saW5lID0gbGFwcGx5KG1vZGVscy5saW5rLm9wZXJhdG9yLmZpbGVzLAogIGZ1bmN0aW9uKGZpbGUpIHsKICAgIGFzLm1hdHJpeChyZWFkLnRhYmxlKGZpbGUsIGNoZWNrLm5hbWVzID0gRkFMU0UpKQogIH0KKQoKIyB0aGUgbm9kZSBuYW1lcyB1c2VkIGluIG91ciBhbmFseXNpcwpub2RlLm5hbWVzID0gY29sbmFtZXMobW9kZWxzLnN0YWJsZS5zdGF0ZS5wZXIuY2VsbC5saW5lW1sxXV0pCgojIFN0ZWFkeSBTdGF0ZXMKc3RlYWR5LnN0YXRlLmZpbGVzID0gc2FwcGx5KGNlbGwubGluZS5kaXJzLCBmdW5jdGlvbihjZWxsLmxpbmUuZGlyKSB7CiAgcGFzdGUwKGNlbGwubGluZS5kaXIsICIvc3RlYWR5X3N0YXRlIikKfSkKCnN0ZWFkeS5zdGF0ZS5wZXIuY2VsbC5saW5lID0gbGFwcGx5KHN0ZWFkeS5zdGF0ZS5maWxlcywKICBmdW5jdGlvbihmaWxlKSB7CiAgICBzcy5kZiA9IHJlYWQudGFibGUoZmlsZSwgc2VwID0gIlx0Iiwgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFKQogICAgc3RlYWR5LnN0YXRlID0gc3MuZGZbLDJdCiAgICBuYW1lcyhzdGVhZHkuc3RhdGUpID0gc3MuZGZbLDFdCiAgICAKICAgICMgY2hhbmdlIHZhbHVlIHRvIE5BIGZvciBub2RlcyBmb3Igd2hpY2ggdGhlcmUgd2FzIG5vIGFjdGl2aXR5IGZvdW5kIChkYXNoKQogICAgc3RlYWR5LnN0YXRlW3N0ZWFkeS5zdGF0ZSA9PSAiLSJdID0gTkEKICAgIAogICAgIyBrZWVwIG9ubHkgdGhlIG5vZGVzIHRoYXQgYXJlIGluY2x1ZGVkIGluIHRoZSBhbmFseXNpcwogICAgc3RlYWR5LnN0YXRlID0gcHJ1bmVfYW5kX3Jlb3JkZXJfdmVjdG9yKHN0ZWFkeS5zdGF0ZSwgbm9kZS5uYW1lcykKICAgIAogICAgIyByZXR1cm4gYW4gaW50ZWdlciB2ZWN0b3Igc2luY2UgdGhlIGFjdGl2aXR5IHZhbHVlcyBhcmUgYmluYXJpemVkICgwLDEpCiAgICByZXR1cm4oc2FwcGx5KHN0ZWFkeS5zdGF0ZSwgYXMuaW50ZWdlcikpCiAgfQopCmBgYAoKVGhlIHJhbmRvbSBtb2RlbCBpbnB1dCBkYXRhOgpgYGB7ciBSYW5kb20gbW9kZWwgSW5wdXR9CnJhbmRvbS5kaXIgPSBwYXN0ZTAoZ2V0d2QoKSwgIi9yYW5kb20iKQpyYW5kb20ubW9kZWwucHJlZGljdGlvbnMgPSBnZXRfbW9kZWxfcHJlZGljdGlvbnMocGFzdGUwKHJhbmRvbS5kaXIsICIvbW9kZWxfcHJlZGljdGlvbnMiKSkKCnJhbmRvbS5tb2RlbHMuc3RhYmxlLnN0YXRlID0gYXMubWF0cml4KAogIHJlYWQudGFibGUoZmlsZSA9IHBhc3RlMChyYW5kb20uZGlyLCAiL21vZGVsc19zdGFibGVfc3RhdGUiKSwgY2hlY2submFtZXMgPSBGQUxTRSkKKQoKcmFuZG9tLm1vZGVscy5saW5rLm9wZXJhdG9yID0KICBhcy5tYXRyaXgocmVhZC50YWJsZShmaWxlID0gcGFzdGUwKHJhbmRvbS5kaXIsICIvbW9kZWxzX2VxdWF0aW9ucyIpLCBjaGVjay5uYW1lcyA9IEZBTFNFKSkKYGBgCgojIyBNb2RlbCBBbmFseXNpcyB7LX0KCkluIG9yZGVyIHRvIGZpbmQgdGhlIG51bWJlciBvZiB0cnVlIHBvc2l0aXZlIChUUCkgcHJlZGljdGVkIHN5bmVyZ2llcywgTUNDIHNjb3JlcyBhbmQgZml0bmVzcyBzY29yZXMgZm9yIGVhY2ggb2YgdGhlIG1vZGVscyBpbiBlYWNoIG9mIHRoZSA5IGRhdGFzZXRzLCB3ZSB1c2UgZnVuY3Rpb25zIGZyb20gdGhlIFtlbWJhXShodHRwczovL2dpdGh1Yi5jb20vYmJsb2Rmb24vZW1iYSkgUiBwYWNrYWdlLgoKIyMjIENlbGwtc3BlY2lmaWMgey19CgpXZSBmaW5kIHRoZSBNQ0MsIFRQIGFuZCBmaXRuZXNzIHZhbHVlcyBmb3IgZWFjaCBtb2RlbCBwZXIgY2VsbCBsaW5lIChub3RlCnRoYXQgZWFjaCBtb2RlbCdzIHN0YWJsZSBzdGF0ZSBpbiBhIHNwZWNpZmljIGNlbGwgbGluZSBpcyBtYXRjaGVkIGFnYWluc3QgdGhlIApzdGVhZHkgc3RhdGUgZnJvbSB0aGF0IGNlbGwgbGluZSk6CmBgYHtyIENlbGwtc3BlY2lmaWMgTW9kZWxzIFRQICsgTUNDICsgZml0bmVzcyBwZXIgY2VsbCBsaW5lfQptb2RlbHMudHAucGVyLmNlbGwubGluZSA9IGxpc3QoKQptb2RlbHMubWNjLnBlci5jZWxsLmxpbmUgPSBsaXN0KCkKbW9kZWxzLmZpdG5lc3MucGVyLmNlbGwubGluZSA9IGxpc3QoKQoKZm9yIChjZWxsLmxpbmUgaW4gY2VsbC5saW5lcykgewogIG1vZGVsLnByZWRpY3Rpb25zID0gbW9kZWwucHJlZGljdGlvbnMucGVyLmNlbGwubGluZVtbY2VsbC5saW5lXV0KICBvYnNlcnZlZC5zeW5lcmdpZXMgPSBvYnNlcnZlZC5zeW5lcmdpZXMucGVyLmNlbGwubGluZVtbY2VsbC5saW5lXV0KICBudW1iZXIub2YuZHJ1Zy5jb21iLnRlc3RlZCA9IG5jb2wobW9kZWwucHJlZGljdGlvbnMucGVyLmNlbGwubGluZVtbY2VsbC5saW5lXV0pCiAgCiAgIyBTcGxpdCBtb2RlbC5wcmVkaWN0aW9ucyB0byBwb3NpdGl2ZSAob2JzZXJ2ZWQpIGFuZCBuZWdhdGl2ZSAobm9uLW9ic2VydmVkKSByZXN1bHRzCiAgb2JzZXJ2ZWQubW9kZWwucHJlZGljdGlvbnMgPQogICAgZ2V0X29ic2VydmVkX21vZGVsX3ByZWRpY3Rpb25zKG1vZGVsLnByZWRpY3Rpb25zLCBvYnNlcnZlZC5zeW5lcmdpZXMpCiAgdW5vYnNlcnZlZC5tb2RlbC5wcmVkaWN0aW9ucyA9CiAgICBnZXRfdW5vYnNlcnZlZF9tb2RlbF9wcmVkaWN0aW9ucyhtb2RlbC5wcmVkaWN0aW9ucywgb2JzZXJ2ZWQuc3luZXJnaWVzKQogIAogICMgQ291bnQgdGhlIHByZWRpY3Rpb25zIG9mIHRoZSBvYnNlcnZlZCBzeW5lcmdpZXMgcGVyIG1vZGVsIChUUCkKICBtb2RlbHMudHAucGVyLmNlbGwubGluZVtbY2VsbC5saW5lXV0gPSBjYWxjdWxhdGVfbW9kZWxzX3N5bmVyZ2llc190cChvYnNlcnZlZC5tb2RlbC5wcmVkaWN0aW9ucykKICAKICAjIENhbGN1bGF0ZSBNYXR0aGV3cyBDb3JyZWxhdGlvbiBDb2VmZmljaWVudCAoTUNDKSBmb3IgZXZlcnkgbW9kZWwKICBtb2RlbHMubWNjLnBlci5jZWxsLmxpbmVbW2NlbGwubGluZV1dID0gCiAgICBjYWxjdWxhdGVfbW9kZWxzX21jYyhvYnNlcnZlZC5tb2RlbC5wcmVkaWN0aW9ucywKICAgICAgICAgICAgICAgICAgICAgICAgIHVub2JzZXJ2ZWQubW9kZWwucHJlZGljdGlvbnMsCiAgICAgICAgICAgICAgICAgICAgICAgICBudW1iZXIub2YuZHJ1Zy5jb21iLnRlc3RlZCkKICAKICAjIEZpdG5lc3MgcGVyIG1vZGVsIGNvbnRyYXN0ZWQgdG8gc3RlYWR5IHN0YXRlIGZyb20gY2VsbCBsaW5lCiAgbW9kZWxzLmZpdG5lc3MucGVyLmNlbGwubGluZVtbY2VsbC5saW5lXV0gPSAKICAgIGFwcGx5KG1vZGVscy5zdGFibGUuc3RhdGUucGVyLmNlbGwubGluZVtbY2VsbC5saW5lXV0sIDEsIGdldF9wZXJjZW50YWdlX29mX21hdGNoZXMsIAogICAgICAgICAgc3RlYWR5LnN0YXRlLnBlci5jZWxsLmxpbmVbW2NlbGwubGluZV1dKQp9CmBgYAoKIyMjIFJhbmRvbSBNb2RlbHMgey19CgpOZXh0LCB3ZSBmaW5kIHRoZSBNQ0MsIFRQIGFuZCBmaXRuZXNzIHZhbHVlcyBmb3IgZWFjaCByYW5kb20gbW9kZWwgcGVyIGNlbGwgbGluZSAKKG5vdGUgdGhhdCBlYWNoIHJhbmRvbSBtb2RlbCdzIHN0YWJsZSBzdGF0ZSBpbiBhIHNwZWNpZmljIGNlbGwgbGluZSBpcyBtYXRjaGVkIGFnYWluc3QgCnRoZSBzdGVhZHkgc3RhdGUgZnJvbSB0aGF0IGNlbGwgbGluZSBhbmQgdGhhdCB0aGUgcmFuZG9tIG1vZGVscycgc3RhYmxlIHN0YXRlIGRhdGEKZG9lcyBub3QgY2hhbmdlIHBlciBjZWxsIGxpbmUsIGkuZS4gc2FtZSBgcmFuZG9tLm1vZGVscy5zdGFibGUuc3RhdGVgIG9iamVjdCk6CmBgYHtyIFJhbmRvbSBNb2RlbHMgVFAgKyBNQ0MgKyBmaXRuZXNzIHBlciBjZWxsIGxpbmV9CnJhbmRvbS5tb2RlbHMubWNjLnBlci5jZWxsLmxpbmUgPSBsaXN0KCkKcmFuZG9tLm1vZGVscy50cC5wZXIuY2VsbC5saW5lID0gbGlzdCgpCnJhbmRvbS5tb2RlbHMuZml0bmVzcy5wZXIuY2VsbC5saW5lID0gbGlzdCgpCgpmb3IgKGNlbGwubGluZSBpbiBjZWxsLmxpbmVzKSB7CiAgb2JzZXJ2ZWQuc3luZXJnaWVzID0gb2JzZXJ2ZWQuc3luZXJnaWVzLnBlci5jZWxsLmxpbmVbW2NlbGwubGluZV1dCiAgbnVtYmVyLm9mLmRydWcuY29tYi50ZXN0ZWQgPSBuY29sKHJhbmRvbS5tb2RlbC5wcmVkaWN0aW9ucykKICAKICAjIFNwbGl0IG1vZGVsLnByZWRpY3Rpb25zIHRvIHBvc2l0aXZlIChvYnNlcnZlZCkgYW5kIG5lZ2F0aXZlIChub24tb2JzZXJ2ZWQpIHJlc3VsdHMKICBvYnNlcnZlZC5tb2RlbC5wcmVkaWN0aW9ucyA9CiAgICBnZXRfb2JzZXJ2ZWRfbW9kZWxfcHJlZGljdGlvbnMocmFuZG9tLm1vZGVsLnByZWRpY3Rpb25zLCBvYnNlcnZlZC5zeW5lcmdpZXMpCiAgdW5vYnNlcnZlZC5tb2RlbC5wcmVkaWN0aW9ucyA9CiAgICBnZXRfdW5vYnNlcnZlZF9tb2RlbF9wcmVkaWN0aW9ucyhyYW5kb20ubW9kZWwucHJlZGljdGlvbnMsIG9ic2VydmVkLnN5bmVyZ2llcykKICAKICAgIyBDb3VudCB0aGUgcHJlZGljdGlvbnMgb2YgdGhlIG9ic2VydmVkIHN5bmVyZ2llcyBwZXIgbW9kZWwgKFRQKQogIHJhbmRvbS5tb2RlbHMudHAucGVyLmNlbGwubGluZVtbY2VsbC5saW5lXV0gPSAKICAgIGNhbGN1bGF0ZV9tb2RlbHNfc3luZXJnaWVzX3RwKG9ic2VydmVkLm1vZGVsLnByZWRpY3Rpb25zKQogIAogICMgQ2FsY3VsYXRlIE1hdHRoZXdzIENvcnJlbGF0aW9uIENvZWZmaWNpZW50IChNQ0MpIGZvciBldmVyeSBtb2RlbAogIHJhbmRvbS5tb2RlbHMubWNjLnBlci5jZWxsLmxpbmVbW2NlbGwubGluZV1dID0gCiAgICBjYWxjdWxhdGVfbW9kZWxzX21jYyhvYnNlcnZlZC5tb2RlbC5wcmVkaWN0aW9ucywKICAgICAgICAgICAgICAgICAgICAgICAgIHVub2JzZXJ2ZWQubW9kZWwucHJlZGljdGlvbnMsCiAgICAgICAgICAgICAgICAgICAgICAgICBudW1iZXIub2YuZHJ1Zy5jb21iLnRlc3RlZCkKICAKICAjIEZpdG5lc3MgcGVyIG1vZGVsIGNvbnRyYXN0ZWQgdG8gc3RlYWR5IHN0YXRlIGZyb20gY2VsbCBsaW5lCiAgcmFuZG9tLm1vZGVscy5maXRuZXNzLnBlci5jZWxsLmxpbmVbW2NlbGwubGluZV1dID0gCiAgICBhcHBseShyYW5kb20ubW9kZWxzLnN0YWJsZS5zdGF0ZSwgMSwgZ2V0X3BlcmNlbnRhZ2Vfb2ZfbWF0Y2hlcywgCiAgICAgICAgICBzdGVhZHkuc3RhdGUucGVyLmNlbGwubGluZVtbY2VsbC5saW5lXV0pCn0KYGBgCgojIyBDaG9vc2UgYmVzdCBkYXRhc2V0IHstfQoKV2Ugbm93IHdhbnQgdG8gKipmaW5kIHRoZSBiZXN0IGRhdGFzZXQgJiBjZWxsIGxpbmUqKiBmb3Igb3VyIHN1YnNlcXVlbnQgYW5hbHlzaXMgLSB0aGF0IGlzIHRvIHNob3cgdGhlIHBlcmZvcm1hbmNlIHZzIGZpdG5lc3MgY29ycmVsYXRpb24uIApUaGUgYXJndW1lbnQgaGVyZSBpcyB0aGF0IHdlIHdhbnQgdG8gY2hvb3NlIGEgYm9vbGVhbiBtb2RlbCBkYXRhc2V0IHRoYXQgaGFzIGEgKipsYXJnZSBlbm91Z2ggZml0bmVzcyB2YWx1ZSByYW5nZSoqIGNvbWJpbmVkIHdpdGggYSBsYXJnZSAqKlRQIGFuZC9vciBNQ0MgdmFsdWUgcmFuZ2UqKiwgc2luY2Ugd2l0aCBzbWFsbGVyIHZhbHVlIHJhbmdlcyBpdCB3b3VsZCBiZSBoYXJkZXIgdG8gZGlzdGluZ3Vpc2ggdGhlIGRpZmZlcmVuY2Ugb2YgdGhlIGVzdGltYXRlZCBkaXN0cmlidXRpb25zIG9mIHRoZSBmaXRuZXNzIHNjb3JlcyBiZWxvbmdpbmcgdG8gZGlmZmVyZW50IHBlcmZvcm1hbmNlIGNsYXNzZXMgKGkuZS4gbW9kZWxzJyBmaXRuZXNzZXMgdGhhdCBiZWxvbmcgdG8gZGlmZmVyZW50IGNsYXNzaWZpY2F0aW9uIGdyb3VwcyB3aXRoIHRoZSBtZXRyaWMgYmVpbmcgZWl0aGVyIHRoZSBudW1iZXIgb2YgVFBzIG9yIHRoZSBNQ0Mgc2NvcmUpLgoKVGhlIG5leHQgc3VtbWFyeSBzdGF0aXN0aWNzIHRhYmxlcyBhbmQgYm94LXBsb3RzIHdpbGwgaGVscCB1cyBkZXRlcm1pbmUgZXhhY3RseSB3aGljaCBjZWxsIGxpbmUgYW5kIGRhdGFzZXQKdG8gdXNlOgpgYGB7ciBDZWxsLXNwZWNpZmljIE1vZGVscyBUUCArIE1DQyArIGZpdG5lc3MgcGVyIGNlbGwgbGluZSBzdGF0c30KY2VsbC5zcGVjaWZpYy5tb2RlbC5kYXRhID0gbWF0cml4KGRhdGEgPSBOQSwgbnJvdyA9IGxlbmd0aChjZWxsLmxpbmVzKSwgbmNvbCA9IDExKQpyb3duYW1lcyhjZWxsLnNwZWNpZmljLm1vZGVsLmRhdGEpID0gY2VsbC5saW5lcwpjb2xuYW1lcyhjZWxsLnNwZWNpZmljLm1vZGVsLmRhdGEpID0gYygiZml0bmVzcyByYW5nZSIsICJNaW4gZml0bmVzcyIsIAogICJNYXggZml0bmVzcyIsICJNZWFuIGZpdG5lc3MiLCAiTWVkaWFuIGZpdG5lc3MiLCAiTUNDIHJhbmdlIiwgIk1pbiBNQ0MiLCAKICAiTWF4IE1DQyIsICJNZWFuIE1DQyIsICJNZWRpYW4gTUNDIiwgIk1heCBUUFIiKQoKZm9yIChjZWxsLmxpbmUgaW4gY2VsbC5saW5lcykgewogIG1vZGVscy5maXRuZXNzID0gbW9kZWxzLmZpdG5lc3MucGVyLmNlbGwubGluZVtbY2VsbC5saW5lXV0KICBtb2RlbHMubWNjID0gbW9kZWxzLm1jYy5wZXIuY2VsbC5saW5lW1tjZWxsLmxpbmVdXQogIG1vZGVscy50cCA9IG1vZGVscy50cC5wZXIuY2VsbC5saW5lW1tjZWxsLmxpbmVdXQogIAogIGZpdC5zdW1tYXJ5ID0gdW5jbGFzcyhzdW1tYXJ5KG1vZGVscy5maXRuZXNzKSkKICBtY2Muc3VtbWFyeSA9IHVuY2xhc3Moc3VtbWFyeShtb2RlbHMubWNjKSkKICBtYXgudHByID0gbWF4KG1vZGVscy50cCkgLyBsZW5ndGgob2JzZXJ2ZWQuc3luZXJnaWVzLnBlci5jZWxsLmxpbmVbW2NlbGwubGluZV1dKQogIAogIGNlbGwuc3BlY2lmaWMubW9kZWwuZGF0YVtjZWxsLmxpbmUsICJmaXRuZXNzIHJhbmdlIl0gPSBmaXQuc3VtbWFyeVsiTWF4LiJdIC0gZml0LnN1bW1hcnlbIk1pbi4iXQogIGNlbGwuc3BlY2lmaWMubW9kZWwuZGF0YVtjZWxsLmxpbmUsICJNaW4gZml0bmVzcyJdID0gZml0LnN1bW1hcnlbIk1pbi4iXQogIGNlbGwuc3BlY2lmaWMubW9kZWwuZGF0YVtjZWxsLmxpbmUsICJNYXggZml0bmVzcyJdID0gZml0LnN1bW1hcnlbIk1heC4iXQogIGNlbGwuc3BlY2lmaWMubW9kZWwuZGF0YVtjZWxsLmxpbmUsICJNZWFuIGZpdG5lc3MiXSA9IGZpdC5zdW1tYXJ5WyJNZWFuIl0KICBjZWxsLnNwZWNpZmljLm1vZGVsLmRhdGFbY2VsbC5saW5lLCAiTWVkaWFuIGZpdG5lc3MiXSA9IGZpdC5zdW1tYXJ5WyJNZWRpYW4iXQogIAogIGNlbGwuc3BlY2lmaWMubW9kZWwuZGF0YVtjZWxsLmxpbmUsICJNQ0MgcmFuZ2UiXSA9IG1jYy5zdW1tYXJ5WyJNYXguIl0gLSBtY2Muc3VtbWFyeVsiTWluLiJdCiAgY2VsbC5zcGVjaWZpYy5tb2RlbC5kYXRhW2NlbGwubGluZSwgIk1pbiBNQ0MiXSA9IG1jYy5zdW1tYXJ5WyJNaW4uIl0KICBjZWxsLnNwZWNpZmljLm1vZGVsLmRhdGFbY2VsbC5saW5lLCAiTWF4IE1DQyJdID0gbWNjLnN1bW1hcnlbIk1heC4iXQogIGNlbGwuc3BlY2lmaWMubW9kZWwuZGF0YVtjZWxsLmxpbmUsICJNZWFuIE1DQyJdID0gbWNjLnN1bW1hcnlbIk1lYW4iXQogIGNlbGwuc3BlY2lmaWMubW9kZWwuZGF0YVtjZWxsLmxpbmUsICJNZWRpYW4gTUNDIl0gPSBtY2Muc3VtbWFyeVsiTWVkaWFuIl0KICAKICBjZWxsLnNwZWNpZmljLm1vZGVsLmRhdGFbY2VsbC5saW5lLCAiTWF4IFRQUiJdID0gbWF4LnRwcgp9CgojIGNvbG9yIGNvbHVtbnMKZml0LmJyZWFrcyA9IHF1YW50aWxlKGNlbGwuc3BlY2lmaWMubW9kZWwuZGF0YVssImZpdG5lc3MgcmFuZ2UiXSwgcHJvYnMgPSBzZXEoLjA1LCAuOTUsIC4wNSksIG5hLnJtID0gVFJVRSkKZml0LmNvbG9ycyA9IHJvdW5kKHNlcSgyNTUsIDQwLCBsZW5ndGgub3V0ID0gbGVuZ3RoKGZpdC5icmVha3MpICsgMSksIDApICU+JQogIHtwYXN0ZTAoInJnYigyNTUsIiwgLiwgIiwiLCAuLCAiKSIpfSAjIHJlZAptY2MuYnJlYWtzID0gcXVhbnRpbGUoY2VsbC5zcGVjaWZpYy5tb2RlbC5kYXRhWywiTUNDIHJhbmdlIl0sIHByb2JzID0gc2VxKC4wNSwgLjk1LCAuMDUpLCBuYS5ybSA9IFRSVUUpCm1jYy5jb2xvcnMgPSByb3VuZChzZXEoMjU1LCA0MCwgbGVuZ3RoLm91dCA9IGxlbmd0aChtY2MuYnJlYWtzKSArIDEpLCAwKSAlPiUKICB7cGFzdGUwKCJyZ2IoIiwgLiwgIiwyNTUsIiwgLiwgIikiKX0gIyBncmVlbgoKY2FwdGlvbi50aXRsZSA9ICJUYWJsZSAxOiBGaXRuZXNzLCBNQ0Mgc2NvcmVzIGFuZCBUUCAoVHJ1ZSBwb3NpdGl2ZXMpIGZvciB0aGUgQ2VsbC1zcGVjaWZpYyBtb2RlbCBwcmVkaWN0aW9ucyBhY3Jvc3MgOCBDZWxsIExpbmVzIgpkYXRhdGFibGUoZGF0YSA9IGNlbGwuc3BlY2lmaWMubW9kZWwuZGF0YSwgCiAgICAgICAgICBvcHRpb25zID0gbGlzdChkb20gPSAidCIpLCAjIGp1c3Qgc2hvdyB0aGUgdGFibGUKICAgICAgICAgIGNhcHRpb24gPSBodG1sdG9vbHM6OnRhZ3MkY2FwdGlvbihjYXB0aW9uLnRpdGxlLCBzdHlsZT0iY29sb3I6I2RkNDgxNDsgZm9udC1zaXplOiAxOHB4IikpICU+JSAKICBmb3JtYXRSb3VuZCgxOjExLCBkaWdpdHMgPSAzKSAlPiUKICBmb3JtYXRTdHlsZShjb2x1bW5zID0gYygiZml0bmVzcyByYW5nZSIpLCBiYWNrZ3JvdW5kQ29sb3IgPSBzdHlsZUludGVydmFsKGZpdC5icmVha3MsIGZpdC5jb2xvcnMpKSAlPiUKICBmb3JtYXRTdHlsZShjb2x1bW5zID0gYygiTUNDIHJhbmdlIiksIGJhY2tncm91bmRDb2xvciA9IHN0eWxlSW50ZXJ2YWwobWNjLmJyZWFrcywgbWNjLmNvbG9ycykpCmBgYAoKYGBge3IgUmFuZG9tIE1vZGVscyBUUCArIE1DQyArIGZpdG5lc3MgcGVyIGNlbGwgbGluZSBzdGF0c30KcmFuZG9tLm1vZGVsLmRhdGEgPSBtYXRyaXgoZGF0YSA9IE5BLCBucm93ID0gbGVuZ3RoKGNlbGwubGluZXMpLCBuY29sID0gMTEpCnJvd25hbWVzKHJhbmRvbS5tb2RlbC5kYXRhKSA9IGNlbGwubGluZXMKY29sbmFtZXMocmFuZG9tLm1vZGVsLmRhdGEpID0gYygiZml0bmVzcyByYW5nZSIsICJNaW4gZml0bmVzcyIsIAogICJNYXggZml0bmVzcyIsICJNZWFuIGZpdG5lc3MiLCAiTWVkaWFuIGZpdG5lc3MiLCAiTUNDIHJhbmdlIiwgIk1pbiBNQ0MiLCAKICAiTWF4IE1DQyIsICJNZWFuIE1DQyIsICJNZWRpYW4gTUNDIiwgIk1heCBUUFIiKQoKZm9yIChjZWxsLmxpbmUgaW4gY2VsbC5saW5lcykgewogIG1vZGVscy5maXRuZXNzID0gcmFuZG9tLm1vZGVscy5maXRuZXNzLnBlci5jZWxsLmxpbmVbW2NlbGwubGluZV1dCiAgbW9kZWxzLm1jYyA9IHJhbmRvbS5tb2RlbHMubWNjLnBlci5jZWxsLmxpbmVbW2NlbGwubGluZV1dCiAgbW9kZWxzLnRwID0gcmFuZG9tLm1vZGVscy50cC5wZXIuY2VsbC5saW5lW1tjZWxsLmxpbmVdXQogIAogIGZpdC5zdW1tYXJ5ID0gdW5jbGFzcyhzdW1tYXJ5KG1vZGVscy5maXRuZXNzKSkKICBtY2Muc3VtbWFyeSA9IHVuY2xhc3Moc3VtbWFyeShtb2RlbHMubWNjKSkKICBtYXgudHByID0gbWF4KG1vZGVscy50cCkgLyBsZW5ndGgob2JzZXJ2ZWQuc3luZXJnaWVzLnBlci5jZWxsLmxpbmVbW2NlbGwubGluZV1dKQogIAogIHJhbmRvbS5tb2RlbC5kYXRhW2NlbGwubGluZSwgImZpdG5lc3MgcmFuZ2UiXSA9IGZpdC5zdW1tYXJ5WyJNYXguIl0gLSBmaXQuc3VtbWFyeVsiTWluLiJdCiAgcmFuZG9tLm1vZGVsLmRhdGFbY2VsbC5saW5lLCAiTWluIGZpdG5lc3MiXSA9IGZpdC5zdW1tYXJ5WyJNaW4uIl0KICByYW5kb20ubW9kZWwuZGF0YVtjZWxsLmxpbmUsICJNYXggZml0bmVzcyJdID0gZml0LnN1bW1hcnlbIk1heC4iXQogIHJhbmRvbS5tb2RlbC5kYXRhW2NlbGwubGluZSwgIk1lYW4gZml0bmVzcyJdID0gZml0LnN1bW1hcnlbIk1lYW4iXQogIHJhbmRvbS5tb2RlbC5kYXRhW2NlbGwubGluZSwgIk1lZGlhbiBmaXRuZXNzIl0gPSBmaXQuc3VtbWFyeVsiTWVkaWFuIl0KICAKICByYW5kb20ubW9kZWwuZGF0YVtjZWxsLmxpbmUsICJNQ0MgcmFuZ2UiXSA9IG1jYy5zdW1tYXJ5WyJNYXguIl0gLSBtY2Muc3VtbWFyeVsiTWluLiJdCiAgcmFuZG9tLm1vZGVsLmRhdGFbY2VsbC5saW5lLCAiTWluIE1DQyJdID0gbWNjLnN1bW1hcnlbIk1pbi4iXQogIHJhbmRvbS5tb2RlbC5kYXRhW2NlbGwubGluZSwgIk1heCBNQ0MiXSA9IG1jYy5zdW1tYXJ5WyJNYXguIl0KICByYW5kb20ubW9kZWwuZGF0YVtjZWxsLmxpbmUsICJNZWFuIE1DQyJdID0gbWNjLnN1bW1hcnlbIk1lYW4iXQogIHJhbmRvbS5tb2RlbC5kYXRhW2NlbGwubGluZSwgIk1lZGlhbiBNQ0MiXSA9IG1jYy5zdW1tYXJ5WyJNZWRpYW4iXQogIAogIHJhbmRvbS5tb2RlbC5kYXRhW2NlbGwubGluZSwgIk1heCBUUFIiXSA9IG1heC50cHIKfQoKIyBjb2xvciBjb2x1bW5zCmZpdC5icmVha3MgPSBxdWFudGlsZShyYW5kb20ubW9kZWwuZGF0YVssImZpdG5lc3MgcmFuZ2UiXSwgcHJvYnMgPSBzZXEoLjA1LCAuOTUsIC4wNSksIG5hLnJtID0gVFJVRSkKZml0LmNvbG9ycyA9IHJvdW5kKHNlcSgyNTUsIDQwLCBsZW5ndGgub3V0ID0gbGVuZ3RoKGZpdC5icmVha3MpICsgMSksIDApICU+JQogIHtwYXN0ZTAoInJnYigyNTUsIiwgLiwgIiwiLCAuLCAiKSIpfSAjIHJlZAptY2MuYnJlYWtzID0gcXVhbnRpbGUocmFuZG9tLm1vZGVsLmRhdGFbLCJNQ0MgcmFuZ2UiXSwgcHJvYnMgPSBzZXEoLjA1LCAuOTUsIC4wNSksIG5hLnJtID0gVFJVRSkKbWNjLmNvbG9ycyA9IHJvdW5kKHNlcSgyNTUsIDQwLCBsZW5ndGgub3V0ID0gbGVuZ3RoKG1jYy5icmVha3MpICsgMSksIDApICU+JQogIHtwYXN0ZTAoInJnYigiLCAuLCAiLDI1NSwiLCAuLCAiKSIpfSAjIGdyZWVuCgpjYXB0aW9uLnRpdGxlID0gIlRhYmxlIDI6IEZpdG5lc3MsIE1DQyBzY29yZXMgYW5kIFRQIChUcnVlIHBvc2l0aXZlcykgZm9yIHRoZSByYW5kb20gbW9kZWwgcHJlZGljdGlvbnMgYWNyb3NzIDggQ2VsbCBMaW5lcyIKZGF0YXRhYmxlKGRhdGEgPSByYW5kb20ubW9kZWwuZGF0YSwgCiAgICAgICAgICBvcHRpb25zID0gbGlzdChkb20gPSAidCIpLCAjIGp1c3Qgc2hvdyB0aGUgdGFibGUKICAgICAgICAgIGNhcHRpb24gPSBodG1sdG9vbHM6OnRhZ3MkY2FwdGlvbihjYXB0aW9uLnRpdGxlLCBzdHlsZT0iY29sb3I6I2RkNDgxNDsgZm9udC1zaXplOiAxOHB4IikpICU+JSAKICBmb3JtYXRSb3VuZCgxOjExLCBkaWdpdHMgPSAzKSAlPiUKICBmb3JtYXRTdHlsZShjb2x1bW5zID0gYygiZml0bmVzcyByYW5nZSIpLCBiYWNrZ3JvdW5kQ29sb3IgPSBzdHlsZUludGVydmFsKGZpdC5icmVha3MsIGZpdC5jb2xvcnMpKSAlPiUKICBmb3JtYXRTdHlsZShjb2x1bW5zID0gYygiTUNDIHJhbmdlIiksIGJhY2tncm91bmRDb2xvciA9IHN0eWxlSW50ZXJ2YWwobWNjLmJyZWFrcywgbWNjLmNvbG9ycykpCmBgYAoKVGhlIGJlbG93IGJveCBwbG90cyBjb21wYXJlIHRoZSBNQ0MgdmFsdWVzIGFuZCBmaXRuZXNzIHNjb3JlcyBhY3Jvc3MgYWxsIGNlbGwgbGluZXMgYmV0d2Vlbgp0aGUgY2VsbC1zcGVjaWZpYyBtb2RlbHMgKHRyYWluZWQgdG8gc3RlYWR5IHN0YXRlKSBhbmQgdGhlIHJhbmRvbSBvbmVzICh0cmFpbmVkIHRvIHByb2xpZmVyYXRpb24pOgoKYGBge3IgQ29tYmluZSBkYXRhIGludG8gb25lIGRhdGEgZnJhbWV9Cm51bS5vZi5tb2RlbHMgPSBucm93KHJhbmRvbS5tb2RlbHMuc3RhYmxlLnN0YXRlKQpkYXRhLmxpc3QgPSBsaXN0KCkKCmZvciAoY2VsbC5saW5lIGluIGNlbGwubGluZXMpIHsKICBjZWxsLmxpbmUudmVjID0gYXMuZGF0YS5mcmFtZShyZXAoY2VsbC5saW5lLCBudW0ub2YubW9kZWxzKSwgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFKQogIG1vZGVscy5tY2MuY2VsbC5zcGVjaWZpYyA9IHJlbW92ZV9yb3duYW1lcyhhcy5kYXRhLmZyYW1lKG1vZGVscy5tY2MucGVyLmNlbGwubGluZVtbY2VsbC5saW5lXV0pKQogIG1vZGVscy5tY2MucmFuZG9tICAgICAgICA9IHJlbW92ZV9yb3duYW1lcyhhcy5kYXRhLmZyYW1lKHJhbmRvbS5tb2RlbHMubWNjLnBlci5jZWxsLmxpbmVbW2NlbGwubGluZV1dKSkKICAKICBtb2RlbHMuZml0bmVzcy5jZWxsLnNwZWNpZmljID0gcmVtb3ZlX3Jvd25hbWVzKGFzLmRhdGEuZnJhbWUobW9kZWxzLmZpdG5lc3MucGVyLmNlbGwubGluZVtbY2VsbC5saW5lXV0pKQogIG1vZGVscy5maXRuZXNzLnJhbmRvbSAgICAgICAgPSByZW1vdmVfcm93bmFtZXMoYXMuZGF0YS5mcmFtZShyYW5kb20ubW9kZWxzLmZpdG5lc3MucGVyLmNlbGwubGluZVtbY2VsbC5saW5lXV0pKQogIAogIGRhdGEubGlzdFtbY2VsbC5saW5lXV0gPSBiaW5kX2NvbHMoY2VsbC5saW5lLnZlYywgbW9kZWxzLm1jYy5jZWxsLnNwZWNpZmljLCAKICAgIG1vZGVscy5tY2MucmFuZG9tLCBtb2RlbHMuZml0bmVzcy5jZWxsLnNwZWNpZmljLCBtb2RlbHMuZml0bmVzcy5yYW5kb20pCn0KCmRhdGEgPSBiaW5kX3Jvd3MoZGF0YS5saXN0KQpjb2xuYW1lcyhkYXRhKSA9IGMoImNlbGwubGluZSIsICJNQ0MgY2VsbC1zcGVjaWZpYyIsICJNQ0MgcmFuZG9tIiwgCiAgICAgICAgICAgICAgICAgICAiZml0bmVzcyBjZWxsLXNwZWNpZmljIiwgImZpdG5lc3MgcmFuZG9tIikKYGBgCgpgYGB7ciBCb3hwbG90czogTUNDIGFuZCBmaXRuZXNzIHZhbHVlcyBiZXR3ZWVuIGNlbGwtc3BlY2lmaWMgYW5kIHJhbmRvbSBtb2RlbHMsIGZpZy53aWR0aD05LCB3YXJuaW5nPUZBTFNFLCBjYWNoZT1UUlVFfQojIE5vdGUgdGhlIGNlbGwtc3BlY2lmaWMgbW9kZWxzIGhhdmUgTmFOIE1DQyB2YWx1ZXMKZ2dib3hwbG90KGRhdGEsIHggPSAiY2VsbC5saW5lIiwgeSA9IGMoIk1DQyBjZWxsLXNwZWNpZmljIiwgIk1DQyByYW5kb20iKSwKICAgICAgICAgIHBhbGV0dGUgPSBicmV3ZXIucGFsKDMsICJTZXQxIiksIG1lcmdlID0gImFzaXMiLAogICAgICAgICAgeGxhYiA9ICJDZWxsIExpbmVzIiwgeWxhYiA9ICJNQ0MgdmFsdWVzIiwgYWRkID0gInBvaW50IiwgYWRkLnBhcmFtcyA9IGxpc3Qoc2l6ZSA9IDAuNSkpCmdnYm94cGxvdChkYXRhLCB4ID0gImNlbGwubGluZSIsIHkgPSBjKCJmaXRuZXNzIGNlbGwtc3BlY2lmaWMiLCAiZml0bmVzcyByYW5kb20iKSwKICAgICAgICAgIHBhbGV0dGUgPSBicmV3ZXIucGFsKDMsICJTZXQxIiksIG1lcmdlID0gImFzaXMiLAogICAgICAgICAgeGxhYiA9ICJDZWxsIExpbmVzIiwgeWxhYiA9ICJGaXRuZXNzIHZhbHVlcyIsIGFkZCA9ICJwb2ludCIsIGFkZC5wYXJhbXMgPSBsaXN0KHNpemUgPSAwLjUpKQpgYGAKClNvLCBmcm9tIHRoZSB0d28gdGFibGVzIGFuZCB0aGUgdHdvIGJveCBwbG90cyBhYm92ZSB3ZSBjb25jbHVkZSB0aGF0OgoKPGRpdiBjbGFzcz0iYmx1ZS1ib3giPgotIEluIGdlbmVyYWwsIHRoZSAqKnJhbmRvbSBtb2RlbHMgb2ZmZXIgYSBsYXJnZXIgcmFuZ2Ugb2YgCmZpdG5lc3MgdmFsdWVzKiogd2hlbiB0aGVpciBzdGFibGUgc3RhdGVzIGFyZSBtYXRjaGVkIGFnYWluc3QgdGhlIHN0ZWFkeSBzdGF0ZSAKb2YgZWFjaCBwYXJ0aWN1bGFyIGNlbGwgbGluZS4gClRoaXMgaXMgc29tZXRoaW5nIHdlIGV4cGVjdGVkIHNpbmNlIHRoZXNlIG1vZGVscyB3ZXJlbid0IGZpdHRlZCB0byBhIApzcGVjaWZpYyBjZWxsLWxpbmUgc3RlYWR5IHN0YXRlIChtZWFuaW5nIHRoYXQgdGhleSB3ZXJlbid0IGNob3NlbiBmcm9tIHRoZSAKYEdpdHNiZWAgbW9kdWxlIGFzIHRoZSAzIGJlc3QgZnJvbSBlYWNoIHNpbXVsYXRpb24gdGhhdCBtYXRjaCB0aGF0IHN0ZWFkeSBzdGF0ZSAKYXMgYmVzdCBhcyBwb3NzaWJsZSkgYnV0IHJhdGhlciB0byBhIG1vcmUgZ2VuZXJpYyBzdGF0ZSBvZiBwcm9saWZlcmF0aW9uLiAKVGh1cywgdGhleSByZXByZXNlbnQgYSBzZXQgb2YgbW9kZWxzIHdpdGggbGFyZ2VyIHZhcmlhdGlvbiBpbiB0ZXJtcyBvZiAKc3RydWN0dXJlIChib29sZWFuIG1vZGVsIGVxdWF0aW9ucykgY29tcGFyZWQgdG8gdGhlIGNlbGwtc3BlY2lmaWMgZ2VuZXJhdGVkIG9uZXMuCi0gKipUaGUgY2VsbC1zcGVjaWZpYyBtb2RlbHMgaGF2ZSBhbHdheXMgYSBsYXJnZXIgbWF4aW11bSBmaXRuZXNzIGFuZCBtZWRpYW4gdmFsdWUqKiAKY29tcGFyZWQgdG8gdGhlIHJhbmRvbSBvbmVzIGZvciBlYWNoIHJlc3BlY3RpdmUgY2VsbCBsaW5lLgotIEluIGVhY2ggY2VsbCBsaW5lICh3aXRoIHRoZSBleHBlY3Rpb24gb2YgU1c2MjApICoqdGhlcmUgYXJlIGFsd2F5cyBjZWxsLXNwZWNpZmljIAptb2RlbHMgdGhhdCBzaG93IGJldHRlciBwZXJmb3JtYW5jZSB0aGFuIHRoZSByYW5kb20gb25lcyAoaGF2ZSBhIGhpZ2hlciBNQ0MgdmFsdWUpKioKPC9kaXY+CjwvYnI+Cgo8ZGl2IGNsYXNzPSJvcmFuZ2UtYm94Ij4KQWxsIGluIGFsbCwgd2Ugd2lsbCB1c2UgdGhlICoqcmFuZG9tIG1vZGVscyBkYXRhKiosIGNvbnRyYXN0ZWQgdG8gdGhlIHN0ZWFkeSAKc3RhdGUgb2YgdGhlIGBBNDk4YCBjZWxsIGxpbmUgKHNlZSBUYWJsZSAyKS4gVGhpcyBkYXRhc2V0IGhhcyB0aGUgbGFyZ2VzdCAKZml0bmVzcyBhbmQgTUNDIHZhbHVlIHJhbmdlIChzZWNvbmQgbGFyZ2VzdCBUUFIgdmFsdWUgYXMgd2VsbCkgY29tYmluZWQgaW4gYm90aCAKdGFibGVzIGFib3ZlLgo8L2Rpdj4KPC9icj4KCmBgYHtyIFNhdmUgYmVzdCBkYXRhc2V0fQpiZXN0LmNlbGwubGluZSA9ICJBNDk4IgoKZml0ID0gcmFuZG9tLm1vZGVscy5maXRuZXNzLnBlci5jZWxsLmxpbmVbW2Jlc3QuY2VsbC5saW5lXV0KdHAgID0gcmFuZG9tLm1vZGVscy50cC5wZXIuY2VsbC5saW5lW1tiZXN0LmNlbGwubGluZV1dCm1jYyA9IHJhbmRvbS5tb2RlbHMubWNjLnBlci5jZWxsLmxpbmVbW2Jlc3QuY2VsbC5saW5lXV0KYGBgCgojIyBTdGF0aXN0aWNhbCBBbmFseXNpcyB7LX0KCiMjIyBEYXRhIHByZXByb2Nlc3Npbmcgey19CgpGaXJzdGx5LCB3ZSBmaWx0ZXIgdGhlIGRhdGEgYnkgZmluZGluZyB0aGUgKip1bmlxdWUgbW9kZWxzKiogLSB0aG9zZSB0aGF0IGhhdmUgCnN0cmljdGx5IGRpZmZlcmVudCBib29sZWFuIGVxdWF0aW9ucy4gVGhlbiwgd2UgdGFrZSBhIHJhbmRvbSBzYW1wbGUgb3V0IG9mIHRoZXNlIAood2hpbGUgc3RhYmlsaXppbmcgdGhlIHNlZWQgbnVtYmVyIGZvciByZXByb2R1Y2liaWxpdHkgcHVycG9zZXMpOgpgYGB7ciBTYW1wbGUgYmVzdCBkYXRhc2V0fQojIEZvciByZXByb2R1Y2liaWxpdHkKc2V0LnNlZWQoMCkKc2FtcGxlLnNpemUgPSAxMDAwCgp1bmlxdWUubW9kZWxzID0gcm93bmFtZXModW5pcXVlKHJhbmRvbS5tb2RlbHMubGluay5vcGVyYXRvcikpCnVuaXF1ZS5tb2RlbHMuc2FtcGxlID0gc2FtcGxlKHVuaXF1ZS5tb2RlbHMsIHNpemUgPSBzYW1wbGUuc2l6ZSkKCmZpdC51bmlxdWUgPSBmaXRbbmFtZXMoZml0KSAlaW4lIHVuaXF1ZS5tb2RlbHMuc2FtcGxlXQp0cC51bmlxdWUgID0gdHBbbmFtZXModHApICVpbiUgdW5pcXVlLm1vZGVscy5zYW1wbGVdCm1jYy51bmlxdWUgPSBtY2NbbmFtZXMobWNjKSAlaW4lIHVuaXF1ZS5tb2RlbHMuc2FtcGxlXQoKZGYgPSBhcy5kYXRhLmZyYW1lKGNiaW5kKGZpdC51bmlxdWUsIG1jYy51bmlxdWUsIHRwLnVuaXF1ZSkpCmBgYAoKTm90ZSB0aGF0IHRoZSAqKmZpdG5lc3MgYW5kIE1DQyBzY29yZSBhcmUgY29udGludW91cyB2YXJpYWJsZXMgd2hpbGUgdGhlIG51bWJlciAKb2YgdHJ1ZSBwb3NpdGl2ZXMgKFRQKSBpcyBkaXNjcmV0ZSoqLgoKIyMjIENvcnJlbGF0aW9uIFBsb3RzIHstfQoKVGhlbiwgd2UgY2hlY2sgaWYgdGhlIG91ciBkYXRhIGlzIG5vcm1hbGx5IGRpc3RyaWJ1dGVkICh1c2luZyB0aGUgU2hhcGlyby1XaWxrCnRlc3QgZm9yIG5vcm1hbGl0eSBhbmQgdGhlIFEtUSBwbG90cyk6CmBgYHtyIE5vcm1hbGl0eSB0ZXN0aW5nLCBjb21tZW50PSIifQpnZ3FxcGxvdChkYXRhID0gZGYsIHggPSAiZml0LnVuaXF1ZSIsIHlsYWIgPSAiRml0bmVzcyIpCmdncXFwbG90KGRhdGEgPSBkZiwgeCA9ICJtY2MudW5pcXVlIiwgeWxhYiA9ICJNTUMiKQoKc2hhcGlyby50ZXN0KHggPSBzYW1wbGUoZml0LnVuaXF1ZSkpCnNoYXBpcm8udGVzdCh4ID0gc2FtcGxlKG1jYy51bmlxdWUpKQpgYGAKCkZyb20gdGhlIGFib3ZlIHJlc3VsdHMgd2Ugb2JzZXJ2ZSB0aGF0IGJvdGggdGhlIGZpdG5lc3MgYW5kIE1DQyBzY29yZXMgYXJlIHN1cmVseQpub3Qgbm9ybWFsbHkgZGlzdHJpYnV0ZWQgKHdpdGggc3RhdGlzdGljYWwgc2lnbmlmaWNhbmNlKS4KVGh1cywgd2Ugd2lsbCB1c2UgKipub24tcGFyYW1ldHJpYyBjb3JyZWxhdGlvbiBzY29yZXMsIG5hbWVseSB0aGUgU3BlYXJtYW4gYW5kIEtlbmRhbGwgcmFuay1iYXNlZCBjb3JyZWxhdGlvbiB0ZXN0cyoqLAp0byBjaGVjayB0aGUgY29ycmVsYXRpb24gYmV0d2VlbiB0aGUgdHdvIGNvbnRpbnVvdXMgdmFyaWFibGVzIChtb2RlbHMgZml0bmVzcyAKdmFsdWVzIGFuZCB0aGVpciBjb3JyZXNwb25kaW5nIE1DQyBzY29yZSk6CmBgYHtyIFJhbmsgY29ycmVsYXRpb24gcGxvdHM6IEZpdG5lc3MgdnMgTUNDfQpnZ3NjYXR0ZXIoZGYsIHggPSAibWNjLnVuaXF1ZSIsIHkgPSAiZml0LnVuaXF1ZSIsIAogICAgICAgICAgdGl0bGUgPSAiRml0bmVzcyB2cyBQZXJmb3JtYW5jZSAoTUNDKSAtIFNwZWFybWFuIENvcnJlbGF0aW9uIiwKICAgICAgICAgIGFkZCA9ICJyZWcubGluZSIsIGNvbmYuaW50ID0gVFJVRSwgCiAgICAgICAgICBjb3IuY29lZiA9IFRSVUUsIGNvci5jb2VmZi5hcmdzID0gbGlzdChtZXRob2QgPSAic3BlYXJtYW4iLCBsYWJlbC54Lm5wYyA9IDAuNywgbGFiZWwueS5ucGMgPSAxKSwKICAgICAgICAgIHhsYWIgPSAiTUNDIHNjb3JlcyIsIHlsYWIgPSAiRml0bmVzcyBWYWx1ZXMiKQpnZ3NjYXR0ZXIoZGYsIHggPSAibWNjLnVuaXF1ZSIsIHkgPSAiZml0LnVuaXF1ZSIsIAogICAgICAgICAgdGl0bGUgPSAiRml0bmVzcyB2cyBQZXJmb3JtYW5jZSAoTUNDKSAtIEtlbmRhbGwgQ29ycmVsYXRpb24iLAogICAgICAgICAgYWRkID0gInJlZy5saW5lIiwgY29uZi5pbnQgPSBUUlVFLCAKICAgICAgICAgIGNvci5jb2VmID0gVFJVRSwgY29yLmNvZWZmLmFyZ3MgPSBsaXN0KG1ldGhvZCA9ICJrZW5kYWxsIiwgbGFiZWwueC5ucGMgPSAwLjcsIGxhYmVsLnkubnBjID0gMSksCiAgICAgICAgICB4bGFiID0gIk1DQyBzY29yZXMiLCB5bGFiID0gIkZpdG5lc3MgVmFsdWVzIikKYGBgCgpGcm9tIHRoZSBjb3JyZWxhdGlvbiBwbG90cyBhYm92ZSwgd2Ugb2JzZXJ2ZSBhICoqd2Vhay9zbWFsbCBwb3NpdGl2ZSBjb3JyZWxhdGlvbiBiZXR3ZWVuCnBlcmZvcm1hbmNlIGFuZCBmaXRuZXNzIHRvIHRoZSBzdGVhZHkgc3RhdGUqKi4KClRvIGFzc2VzcyB0aGUgKipjb3JyZWxhdGlvbiBiZXR3ZWVuIHRoZSBudW1iZXIgb2YgVFAgcHJlZGljdGlvbnMgb2YgdGhlIG1vZGVscyAKKGRpc2NyZXRlIHZhcmlhYmxlKSBhbmQgdGhlIG1vZGVscyBmaXRuZXNzIChjb250aW51b3VzIHZhcmlhYmxlKSoqLCB3ZSBjb25zdHJ1Y3QgYSAKcHJlZGljdG9yIG9mIHRoZSBjYXRlZ29yaWNhbCB2YXJpYWJsZSBmcm9tIHRoZSBjb250aW51b3VzIHZhcmlhYmxlOiBpZiB0aGUgCnJlc3VsdGluZyBjbGFzc2lmaWVyIGhhcyBhICoqaGlnaCBkZWdyZWUgb2YgZml0Kiogd2UgY2FuIGNvbmNsdWRlIHRoZSB0d28gdmFyaWFibGVzIApzaGFyZSBhIHJlbGF0aW9uc2hpcCBhbmQgYXJlIGluZGVlZCBjb3JyZWxhdGVkLiBTaW5jZSB0aGVyZSBhcmUgbW9yZSB0aGFuIDIgVFAKY2xhc3NlcyAodmFsdWVzKSBpbiB0aGUgZGF0YXNldCwgd2Ugd2lsbCB1c2UgKipNdWx0aW5vbWlhbCBMb2dpc3RpYyBSZWdyZXNzaW9uKioKYW5kIGZpdCBsb2ctbGluZWFyIG1vZGVscyB2aWEgbmV1cmFsIG5ldHdvcmtzOgoKYGBge3IgTXVsdGlub21pYWwgTG9naXN0aWMgUmVncmVzc2lvbjogVFAgfiBGaXRuZXNzLCBjb21tZW50PSIifQptb2RlbC5jbGFzc2lmaWVyID0gbXVsdGlub20oZGF0YSA9IGRmLCBmb3JtdWxhID0gdHAudW5pcXVlIH4gZml0LnVuaXF1ZSkKcHNldWRvLnIyLm1lYXN1cmVzID0gcFIyKG1vZGVsLmNsYXNzaWZpZXIpCnBzZXVkby5yMi5tZWFzdXJlc1siTWNGYWRkZW4iXQpgYGAKCmBgYHtyIFRlc3QgcHJlZGljdGlvbiBhY2N1cmFjeSBvbiBzYW1lIGRhdGFzZXQsIGV2YWw9RkFMU0UsIGluY2x1ZGU9RkFMU0V9CmxpYnJhcnkoY2FyZXQpCgphcHJlZGljdGlvbnMgPSBwcmVkaWN0KG9iamVjdCA9IG1vZGVsLmNsYXNzaWZpZXIsIG5ld2RhdGEgPSBkZiR0cC51bmlxdWUsIHR5cGUgPSAiY2xhc3MiKQpwb3N0UmVzYW1wbGUocHJlZCA9IHByZWRpY3Rpb25zLCBvYnMgPSBmYWN0b3IoZGYkdHAudW5pcXVlKSkKYGBgCgpUbyBtZWFzdXJlIHRoZSBnb29kbmVzcy1vZi1maXQgZm9yIG91ciBtb2RlbCwgdGhlcmUgYXJlIHNldmVyYWwgbWVhc3VyZXMgcHJvcG9zZWQgCmZvciBsb2dpc3RpYyByZWdyZXNzaW9uLiBXZSBlbXBoYXNpemUgb24gdGhlIE1jRmFkZGVucydzIHBzZXVkby0kUl4yJCBtZWFzdXJlIApmb3Igb3VyIGNsYXNzaWZpZXIsIGZvciB3aGljaCBhIHZhbHVlIG9mICQwLjItMC40JCB3b3VsZCBpbmRpY2F0ZSBhbiBleGNlbGxlbnQgZml0IFtbc291cmNlXShodHRwczovL3N0YXRzLnN0YWNrZXhjaGFuZ2UuY29tL3F1ZXN0aW9ucy84MjEwNS9tY2ZhZGRlbnMtcHNldWRvLXIyLWludGVycHJldGF0aW9uKV0uClNpbmNlIHdlIGZvdW5kIGxlc3MsIHdlIGNhbiBhbHNvIGFzc3VtZSB0aGF0IHRoZXJlIGlzIG9ubHkgYSAqKndlYWsvc21hbGwgcG9zaXRpdmUgCmNvcnJlbGF0aW9uIGJldHdlZW4gdGhlIG51bWJlciBvZiBUUHMgYW5kIHRoZSBmaXRuZXNzIHNjb3JlIG9mIHRoZSBtb2RlbHMqKi4KCk5leHQsIHdlIHdpbGwgbmV4dCBwcm9jZWVkIHdpdGggYSBtb3JlIGVsYWJvcmF0ZSBhbmFseXNpcywgd2hlcmUgdGhlIG1vZGVscyB3aWxsIGJlIHNwbGl0IHRvIGRpZmZlcmVudCAKcGVyZm9ybWFuY2UgY2xhc3NlcyAoVFAgb3IgTUNDIHNjb3JlLWRlcml2ZWQpIGFuZCB0aGUgc3RhdGlzdGljYWwgY29ycmVsYXRpb24gYmV0d2VlbiB0aGUgCmluZGl2aWR1YWwgZ3JvdXBzIHdpbGwgYmUgdGVzdGVkICh3aXRoIHJlZ2FyZHMgdG8gdGhlaXIgZml0bmVzcyBzY29yZXMpLgoKIyMjIFRQLWNsYXNzIHZzIGZpdG5lc3Mgey19CgpGaXJzdCwgc29tZSBib3ggYW5kIGRlbnNpdHkgcGxvdHMgdG8gc2VlIHByYWN0aWNhbGx5ICoqd2hhdCBhbmQgd2hlcmUgaXMgdGhlIGRpZmZlcmVuY2UgCmJldHdlZW4gdGhlIG1vZGVscyBmaXRuZXNzIHZhbHVlcyBiZWxvbmdpbmcgdG8gZGlmZmVyZW50IFRQLWNsYXNzZXMqKjoKYGBge3IgVFAtY2xhc3NpZmljYXRpb246IFBsb3RzfQojIEJveCBwbG90cwpnZ2JveHBsb3QoZGYsIHggPSAidHAudW5pcXVlIiwgeSA9ICJmaXQudW5pcXVlIiwgY29sb3IgPSAidHAudW5pcXVlIiwKICAgICAgICAgIHBhbGV0dGUgPSB1c2VmdW46Ojpjb2xvcnMuMTAwWzE6bGVuZ3RoKHVuaXF1ZSh0cC51bmlxdWUpKV0sCiAgICAgICAgICB4bGFiID0gIlRydWUgUG9zaXRpdmVzIChUUCkiLCB5bGFiID0gIkZpdG5lc3MgdmFsdWVzIikKCiMgRGVuc2l0eSBQbG90cwpkZW5zaXRpZXMgPSBsaXN0KCkKZm9yICh0cC5udW0gaW4gc29ydCh1bmlxdWUodHAudW5pcXVlKSkpIHsKICB4ID0gZGYgJT4lCiAgICBmaWx0ZXIodHAudW5pcXVlID09IHRwLm51bSkgJT4lCiAgICBzZWxlY3RfYXQoLnZhcnMgPSBjKCJmaXQudW5pcXVlIikpCiAgZGVuID0gZGVuc2l0eSh4JGZpdC51bmlxdWUpCiAgZGVuc2l0aWVzW1twYXN0ZTAodHAubnVtLCAiICgiLCBkZW4kbiwgIikiKV1dID0gZGVuCn0KCm1ha2VfbXVsdGlwbGVfZGVuc2l0eV9wbG90KGRlbnNpdGllcywgbGVnZW5kLnRpdGxlID0gIlRQIGNsYXNzZXMgKCNtb2RlbHMpIiwKICAgICAgICB0aXRsZSA9ICJEZW5zaXR5IEVzdGltYXRpb24iLCB4LmF4aXMubGFiZWwgPSAiRml0bmVzcyBzY29yZSIpCmBgYAoKPGRpdiBjbGFzcz0iZ3JlZW4tYm94Ij4KQXMgd2UgY2FuIHNlZSBmcm9tIHRoZSBhYm92ZSBwbG90cywgdGhlcmUgaXMgcG9zaXRpdmUgY29ycmVsYXRpb24gYmV0d2VlbiB0aGUgCmNsYXNzZXMgdGhhdCBwcmVkaWN0ZWQgMCwxIGFuZCAyIFRQIHN5bmVyZ2llcyBhcyB3ZWxsIGFzIGJldHdlZW4gdGhlIDAgVFAgY2xhc3MKYW5kIHRoZSAzLDQsNS1UUCBjbGFzc2VzLgo8L2Rpdj4KPC9icj4KCk1hdGhlbWF0aWNhbGx5LCB3ZSBzaG93IHRoYXQgKip0aGUgZ3JvdXAgZGlzdHJpYnV0aW9ucyBhcmUgaW5kZWVkIGRpZmZlcmVudCB1c2luZyB0aGUgS3J1c2thbC1XYWxsaXMgUmFuayBTdW0gVGVzdCoqOgoKYGBge3IgVFAtY2xhc3NpZmljYXRpb246IEtydXNrYWwgVGVzdCwgY29tbWVudD0iIn0KIyBIeXBvdGhlc2lzIHRlc3Rpbmc6IEFyZSB0aGUgbG9jYXRpb24gcGFyYW1ldGVycyBvZiB0aGUgZGlzdHJpYnV0aW9uIG9mIHggdGhlIHNhbWUgaW4gZWFjaCBncm91cD8Ka3J1c2thbC50ZXN0KHggPSBkZlssImZpdC51bmlxdWUiXSwgZyA9IGRmWywgInRwLnVuaXF1ZSJdKQpgYGAKCkFzIHRoZSBwLXZhbHVlIGlzIGxlc3MgdGhhbiB0aGUgc2lnbmlmaWNhbmNlIGxldmVsICQwLjA1JCwgd2UgY2FuIGNvbmNsdWRlIAp0aGF0IHRoZXJlIGFyZSBzaWduaWZpY2FudCBkaWZmZXJlbmNlcyBiZXR3ZWVuIHRoZSBkaWZmZXJlbnQgZ3JvdXBzIG9mIGZpdG5lc3MgCnZhbHVlcy4gVG8gc2VlIGV4YWN0bHkgd2hpY2ggcGFpciBvZiBncm91cHMgYXJlIGRpZmZlcmVudCAqKndlIHBlcmZvcm0gcGFpcndpc2UKV2lsY294b24gcmFuayBzdW0gdGVzdHMgYW5kIHdlIGRyYXcgYSBoZWF0bWFwIG9mIHRoZSBlYWNoIHRlc3QncyBwLXZhbHVlKio6CmBgYHtyIFRQLWNsYXNzaWZpY2F0aW9uOiBQYWlyd2lzZSBXaWxjb3ggVGVzdHMsIHdhcm5pbmc9RkFMU0V9CnJlcyA9IHBhaXJ3aXNlLndpbGNveC50ZXN0KHggPSBkZlssImZpdC51bmlxdWUiXSwgZyA9IGRmWywgInRwLnVuaXF1ZSJdLCBwLmFkanVzdC5tZXRob2QgPSAiQkgiKQpwLnZhbHVlLm1hdCA9IHJlcyRwLnZhbHVlCmBgYAoKYGBge3IgVFAtY2xhc3NpZmljYXRpb246IHAtdmFsdWUgSGVhdG1hcCBvZiBQYWlyd2lzZSBXaWxjb3ggVGVzdHMsIHdhcm5pbmc9RkFMU0UsIGZpZy53aWR0aD05LCBkcGk9MzAwfQpjb2xfZnVuID0gY29sb3JSYW1wMihicmVha3MgPSBjKDAsIDAuMDUsIDAuNSwgMSksIGMoImdyZWVuIiwgIndoaXRlIiwgIm9yYW5nZSIsICJyZWQiKSkKaHQgPSBIZWF0bWFwKG1hdHJpeCA9IHAudmFsdWUubWF0LCBjbHVzdGVyX3Jvd3MgPSBGQUxTRSwgY2x1c3Rlcl9jb2x1bW5zID0gRkFMU0UsCiAgbmFfY29sID0gIndoaXRlIiwgbmFtZSA9ICJwLXZhbHVlIiwgY29sID0gY29sX2Z1biwgY29sdW1uX25hbWVzX3JvdCA9IDAsCiAgcm93X3RpdGxlID0gIlRQIiwgcm93X3RpdGxlX3JvdCA9IDAsIHJvd19uYW1lc19zaWRlID0gImxlZnQiLAogIGNvbHVtbl90aXRsZSA9ICJQLXZhbHVlcyAoUGFpcndpc2UgV2lsY294b24gdGVzdHMpIC0gVFAtY2xhc3NpZmllZCBmaXRuZXNzZXMiLCBjb2x1bW5fdGl0bGVfZ3AgPSBncGFyKGZvbnRzaXplID0gMjApLAogIGNlbGxfZnVuID0gZnVuY3Rpb24oaiwgaSwgeCwgeSwgd2lkdGgsIGhlaWdodCwgZmlsbCkgewogICAgaWYgKCFpcy5uYShwLnZhbHVlLm1hdFtpLGpdKSkKICAgICAgICBncmlkLnRleHQoc3ByaW50ZigiJS42ZiIsIHAudmFsdWUubWF0W2ksIGpdKSwgeCwgeSwgZ3AgPSBncGFyKGZvbnRzaXplID0gMTYpKQp9KQpkcmF3KGh0KQpgYGAKCkFzIHdlIGNhbiBzZWUgYWJvdmUgdGhlcmUgaXMgc2lnbmlmaWNhbnQgZGlmZmVyZW5jZSBiZXR3ZWVuIGdyb3VwcyBvZiBmaXRuZXNzCnZhbHVlcyBiZWxvbmdpbmcgdG8gbW9kZWxzIHRoYXQgcHJlZGljdGVkICQwJCwgJDEkIGFuZCAkMiQgVFAgc3luZXJnaWVzLCBidXQKbm90IGJldHdlZW4gdGhlc2UgYW5kIHRoZSBsYXJnZXIgcGVyZm9ybWFudCBncm91cHMgKCRUUD0zLDQsNSQpIC0gd2l0aCB0aGUgZXhjZXB0aW9uCm9mIHRoZSBncm91cCB3aXRoICRUUD0yJC4gCgpOb3RlIHRoYXQgdGhlICoqbnVtYmVyIG9mIHRydWUgcG9zaXRpdmUgcHJlZGljdGlvbnMgaXMgYW4gb25lLWRpbWVuc2lvbmFsIG1ldHJpYyoqIGFuZCBieSAKZXhjbHVkaW5nIHRoZSBUTiwgRlAgYW5kIEZOcyB3ZSBoYXZlIGEgbGVzcy1pbmZvcm1hbnQgKGFuZCBhcmd1YWJseSBpbmNvcnJlY3QpIApwaWN0dXJlIG9mIHRoZSBtb2RlbHMgcGVyZm9ybWFuY2UgY2xhc3NpZmljYXRpb24uIApUaGF0J3Mgd2h5IHdlIHdpbGwgcHJvY2VlZCB3aXRoIHRoZSBtb3JlIGJhbGFuY2VkIE1DQyBzY29yZSBmb3IgY2xhc3NpZmluZyB0aGUgbW9kZWxzIGZpdG5lc3NlcyAKdG8gZGlmZmVyZW50IHBlcmZvcm1hbmNlIGdyb3Vwcy4KCiMjIyBNQ0MtY2xhc3MgdnMgZml0bmVzcyB7LX0KCkZpcnN0bHksIHdlIHBlcmZvcm0gYSAqdW5pdmFyaWF0ZSBrLW1lYW5zIGNsdXN0ZXJpbmcqIHRvIAoqKnNwbGl0IHRoZSBtb2RlbHMgTUNDIHZhbHVlcyB0byBkaWZmZXJlbnQgY2xhc3NlcyoqIGFuZCBwbG90IHRoZSBkYXRhIGhpc3RvZ3JhbS4KVGhlICoqbnVtYmVyIG9mIE1DQyBjbGFzc2VzKiogdG8gc3BsaXQgdGhlIGRhdGEgY2FuIGJlIGFyYml0cmFyaWx5IGNob3NlbiBhbmQgc28Kd2UgY2hvc2Ugb25lIGxlc3MgdGhhbiB0aGUgbWF4aW11bSBudW1iZXIgb2YgVFBzIHByZWRpY3RlZDoKYGBge3IgTUNDLWNsYXNzaWZpY2F0aW9uOiBGaW5kIHRoZSBjbHVzdGVyc30KbnVtLm9mLm1jYy5jbGFzc2VzID0gNQptY2MuY2xhc3MuaWRzID0gMTpudW0ub2YubWNjLmNsYXNzZXMKCiMgZmluZCB0aGUgY2x1c3RlcnMKcmVzID0gQ2ttZWFucy4xZC5kcCh4ID0gZGZbLCJtY2MudW5pcXVlIl0sIGsgPSBudW0ub2YubWNjLmNsYXNzZXMpCm1jYy5jbGFzcy5pZCA9IHJlcyRjbHVzdGVyCmRmID0gY2JpbmQoZGYsIG1jYy5jbGFzcy5pZCkKCnBsb3RfbWNjX2NsYXNzZXNfaGlzdChkZlssIm1jYy51bmlxdWUiXSwgZGZbLCJtY2MuY2xhc3MuaWQiXSwKICAgICAgICAgICAgICAgICAgICAgIG51bS5vZi5tY2MuY2xhc3NlcywgbWNjLmNsYXNzLmlkcykKYGBgCgpUaGVuIHdlIHNob3cgc29tZSBib3ggYW5kIGRlbnNpdHkgcGxvdHMgdG8gc2VlIHByYWN0aWNhbGx5ICoqd2hhdCBhbmQgd2hlcmUgaXMgCnRoZSBkaWZmZXJlbmNlIGJldHdlZW4gdGhlIG1vZGVscyBmaXRuZXNzIHZhbHVlcyBiZWxvbmdpbmcgdG8gZGlmZmVyZW50IE1DQy1jbGFzc2VzKio6CmBgYHtyIE1DQy1jbGFzc2lmaWNhdGlvbjogUGxvdHN9CiMgQm94IHBsb3RzCiMgaWYgeW91IHdhbnQgdG8gYWRkIHAtdmFsdWVzIG9uIHRoZSBib3hwbG90CiNtY2MuY2xhc3MuaWQuY21wcyA9IGxpc3QoYygiMSIsICIyIiksIGMoIjEiLCAiMyIpLCBjKCIzIiwgIjUiKSkKZ2dib3hwbG90KGRmLCB4ID0gIm1jYy5jbGFzcy5pZCIsIHkgPSAiZml0LnVuaXF1ZSIsIGNvbG9yID0gIm1jYy5jbGFzcy5pZCIsCiAgICAgICAgICBwYWxldHRlID0gdXNlZnVuOjo6Y29sb3JzLjEwMFsxOm51bS5vZi5tY2MuY2xhc3Nlc10sCiAgICAgICAgICB4bGFiID0gIk1DQyBjbGFzcyIsIHlsYWIgPSAiRml0bmVzcyB2YWx1ZXMiKQojICsgc3RhdF9jb21wYXJlX21lYW5zKGNvbXBhcmlzb25zID0gbWNjLmNsYXNzLmlkLmNtcHMpICsgc3RhdF9jb21wYXJlX21lYW5zKCkKCiMgRGVuc2l0eSBQbG90cwpkZW5zaXRpZXMgPSBsaXN0KCkKZm9yIChpZCBpbiBtY2MuY2xhc3MuaWRzKSB7CiAgeCA9IGRmICU+JQogICAgZmlsdGVyKG1jYy5jbGFzcy5pZCA9PSBpZCkgJT4lCiAgICBzZWxlY3RfYXQoLnZhcnMgPSBjKCJmaXQudW5pcXVlIikpCiAgZGVuID0gZGVuc2l0eSh4JGZpdC51bmlxdWUpCiAgZGVuc2l0aWVzW1twYXN0ZTAoaWQsICIgKCIsIHJlcyRzaXplW2lkXSwgIikiKV1dID0gZGVuCn0KCm1ha2VfbXVsdGlwbGVfZGVuc2l0eV9wbG90KGRlbnNpdGllcywgbGVnZW5kLnRpdGxlID0gIk1DQyBjbGFzc2VzICgjbW9kZWxzKSIsCiAgICAgICAgdGl0bGUgPSAiRGVuc2l0eSBFc3RpbWF0aW9uIiwgeC5heGlzLmxhYmVsID0gIkZpdG5lc3Mgc2NvcmUiKQpgYGAKCjxkaXYgY2xhc3M9ImdyZWVuLWJveCI+CkFzIHdlIGNhbiBzZWUgZnJvbSB0aGUgYWJvdmUgcGxvdHMsICoqdGhlcmUgaXMgcG9zaXRpdmUgY29ycmVsYXRpb24gYmV0d2VlbiB0aGUgCmRpc3RyaWJ1dGlvbiBvZiBNQ0Mgc2NvcmVzIGluIGVhY2ggY2xhc3MgYW5kIHRoZSByZXNwZWN0aXZlIGZpdG5lc3Mgc2NvcmVzKiosIAp0aG91Z2ggbm90IGJldHdlZW4gdGhlIDNyZCBjbGFzcyBhbmQgdGhlIDR0aCBvciA1dGggKGl0J3MgbmVnYXRpdmUpLiA8L2Rpdj48L2JyPgpOb3RlIGFsc28gdGhhdCB0aGUgNXRoIGNsYXNzIGhhcyBhIGxvdCBsZXNzIG1vZGVscyB0aGFuIHRoZSByZXN0LgpNYXRoZW1hdGljYWxseSwgd2Ugc2hvdyB0aGF0ICoqdGhlIGdyb3VwIGRpc3RyaWJ1dGlvbnMgYXJlIGluZGVlZCBkaWZmZXJlbnQgCnVzaW5nIHRoZSBLcnVza2FsLVdhbGxpcyBSYW5rIFN1bSBUZXN0Kio6CmBgYHtyIE1DQy1jbGFzc2lmaWNhdGlvbjogS3J1c2thbCBUZXN0LCBjb21tZW50PSIifQojIEh5cG90aGVzaXMgdGVzdGluZzogQXJlIHRoZSBsb2NhdGlvbiBwYXJhbWV0ZXJzIG9mIHRoZSBkaXN0cmlidXRpb24gb2YgeCB0aGUgc2FtZSBpbiBlYWNoIGdyb3VwPwprcnVza2FsLnRlc3QoeCA9IGRmWywiZml0LnVuaXF1ZSJdLCBnID0gZGZbLCAibWNjLmNsYXNzLmlkIl0pCmBgYAoKQXMgdGhlIHAtdmFsdWUgaXMgbGVzcyB0aGFuIHRoZSBzaWduaWZpY2FuY2UgbGV2ZWwgJDAuMDUkLCB3ZSBjYW4gY29uY2x1ZGUgCnRoYXQgdGhlcmUgYXJlIHNpZ25pZmljYW50IGRpZmZlcmVuY2VzIGJldHdlZW4gdGhlIGRpZmZlcmVudCBncm91cHMgb2YgZml0bmVzcyAKdmFsdWVzLgoKTmV4dCwgdG8gc2hvdyB0aGF0IG1vc3Qgb2YgdGhlIGdyb3VwcyBhcmUgc3RhdGlzdGljYWxseSBkaWZmZXJlbnQsICoqd2UgcGVyZm9ybSAKcGFpcndpc2UgV2lsY294b24gcmFuayBzdW0gdGVzdHMgYW5kIGRyYXcgYSBoZWF0bWFwIG9mIHRoZSBlYWNoIHRlc3TigJlzIHAtdmFsdWUqKjoKYGBge3IgTUNDLWNsYXNzaWZpY2F0aW9uOiBQYWlyd2lzZSBXaWxjb3ggVGVzdHMsIHdhcm5pbmc9RkFMU0V9CnBhaXIucmVzID0gcGFpcndpc2Uud2lsY294LnRlc3QoeCA9IGRmWywiZml0LnVuaXF1ZSJdLCBnID0gZGZbLCAibWNjLmNsYXNzLmlkIl0sIHAuYWRqdXN0Lm1ldGhvZCA9ICJCSCIpCnAudmFsdWUubWF0ID0gcGFpci5yZXMkcC52YWx1ZQojIHNhbWU6IGNvbXBhcmVfbWVhbnMoZml0LnVuaXF1ZSB+IG1jYy5jbGFzcy5pZCwgZGF0YSA9IGRmKQpgYGAKCmBgYHtyIE1DQy1jbGFzc2lmaWNhdGlvbjogcC12YWx1ZSBIZWF0bWFwIG9mIFBhaXJ3aXNlIFdpbGNveCBUZXN0cywgd2FybmluZz1GQUxTRSwgZmlnLndpZHRoPTksIGRwaT0zMDB9CmNvbF9mdW4gPSBjb2xvclJhbXAyKGJyZWFrcyA9IGMoMCwgMC4wNSwgMC41LCAxKSwgYygiZ3JlZW4iLCAid2hpdGUiLCAib3JhbmdlIiwgInJlZCIpKQpodCA9IEhlYXRtYXAobWF0cml4ID0gcC52YWx1ZS5tYXQsIGNsdXN0ZXJfcm93cyA9IEZBTFNFLCBjbHVzdGVyX2NvbHVtbnMgPSBGQUxTRSwKICBuYV9jb2wgPSAid2hpdGUiLCBuYW1lID0gInAtdmFsdWUiLCBjb2wgPSBjb2xfZnVuLCBjb2x1bW5fbmFtZXNfcm90ID0gMCwKICByb3dfdGl0bGUgPSAiTUNDIGNsYXNzIGlkIiwgcm93X3RpdGxlX3JvdCA9IDkwLCByb3dfbmFtZXNfc2lkZSA9ICJsZWZ0IiwKICBjb2x1bW5fdGl0bGUgPSAiUC12YWx1ZXMgKFBhaXJ3aXNlIFdpbGNveG9uIHRlc3RzKSAtIE1DQy1jbGFzc2lmaWVkIGZpdG5lc3NlcyIsIGNvbHVtbl90aXRsZV9ncCA9IGdwYXIoZm9udHNpemUgPSAyMCksCiAgY2VsbF9mdW4gPSBmdW5jdGlvbihqLCBpLCB4LCB5LCB3aWR0aCwgaGVpZ2h0LCBmaWxsKSB7CiAgICBpZiAoIWlzLm5hKHAudmFsdWUubWF0W2ksal0pKQogICAgICAgIGdyaWQudGV4dChzcHJpbnRmKCIlLjZmIiwgcC52YWx1ZS5tYXRbaSwgal0pLCB4LCB5LCBncCA9IGdwYXIoZm9udHNpemUgPSAyMCkpCn0pCmRyYXcoaHQpCmBgYAoKIyMgUiBzZXNzaW9uIGluZm8gey19CgpgYGB7ciBzZXNzaW9uIGluZm8sIGNvbW1lbnQ9IiJ9CnhmdW46OnNlc3Npb25faW5mbygpCmBgYAo=