
This is part of the online course Proteomics Data Analysis
(PDA)
Background
This case-study is a subset of the data of the 6th study of the
Clinical Proteomic Technology Assessment for Cancer (CPTAC). In this
experiment, the authors spiked the Sigma Universal Protein Standard
mixture 1 (UPS1) containing 48 different human proteins in a protein
background of 60 ng/\(\mu\)L
Saccharomyces cerevisiae strain BY4741. Two different spike-in
concentrations were used: 6A (0.25 fmol UPS1 proteins/\(\mu\)L) and 6B (0.74 fmol UPS1
proteins/\(\mu\)L) [5]. We limited
ourselves to the data of LTQ-Orbitrap W at site 56. The data were
searched with MaxQuant version 1.5.2.8, and detailed search settings
were described in Goeminne et al. (2016) [1]. Three replicates are
available for each concentration.
Data
We first import the data from peptideRaws.txt file. This is the file
containing your peptideRaw-level intensities. For a MaxQuant search [6],
this peptideRaws.txt file can be found by default in the
“path_to_raw_files/combined/txt/” folder from the MaxQuant output, with
“path_to_raw_files” the folder where the raw files were saved. In this
vignette, we use a MaxQuant peptideRaws file which is a subset of the
cptac study. This data is available in the msdata
package.
To import the data we use the QFeatures
package.
We generate the object peptideRawFile with the path to the
peptideRaws.txt file. Using the grepEcols
function, we find
the columns that contain the expression data of the peptideRaws in the
peptideRaws.txt file.
- Load libraries
library(tidyverse)
library(limma)
library(QFeatures)
library(msqrob2)
library(plotly)
library(data.table)
library(pcaMethods)
- We use a peptides.txt file from MS-data quantified with maxquant
that contains MS1 intensities summarized at the peptide level.
peptidesTable <- fread("https://raw.githubusercontent.com/statOmics/SGA2020/data/quantification/cptacAvsB_lab3/peptides.txt")
int64 <- which(sapply(peptidesTable,class) == "integer64")
for (j in int64) peptidesTable[[j]] <- as.numeric(peptidesTable[[j]])
- Maxquant stores the intensity data for the different samples in
columnns that start with Intensity. We can retreive the column names
with the intensity data with the code below:
quantCols <- grep("Intensity ", names(peptidesTable))
- Read the data and store it in QFeatures object, the first column of
the peptides file contains the peptide sequence, which is a unique
identifier for the peptide. We will use this column as the rownames of
the assay data
fnames = 1
.
pe <- readQFeatures(
assayData = peptidesTable,
fnames = 1,
quantCols = quantCols,
name = "peptideRaw")
## Checking arguments.
## Loading data as a 'SummarizedExperiment' object.
## Formatting sample annotations (colData).
## Formatting data as a 'QFeatures' object.
## Setting assay rownames.
## used (Mb) gc trigger (Mb) limit (Mb) max used (Mb)
## Ncells 8266137 441.5 16546607 883.7 NA 16546607 883.7
## Vcells 15993940 122.1 33213111 253.4 16384 28324276 216.1
## used (Mb) gc trigger (Mb) limit (Mb) max used (Mb)
## Ncells 8266088 441.5 16546607 883.7 NA 16546607 883.7
## Vcells 15991717 122.1 33213111 253.4 16384 28324276 216.1
## CharacterList of length 1
## [["peptideRaw"]] Intensity 6A_7 Intensity 6A_8 ... Intensity 6B_9
In the following code chunk, we can extract the spikein condition
from the raw file name.
cond <- which(
strsplit(colnames(pe)[[1]][1], split = "")[[1]] == "A") # find where condition is stored
colData(pe)$condition <- substr(colnames(pe), cond, cond) |>
unlist() |>
as.factor()
Peptides with zero intensities are missing peptides and should be
represent with a NA
value rather than 0
.
pe <- zeroIsNA(pe, "peptideRaw") # convert 0 to NA
Data exploration
45% of all peptide intensities are missing and for some peptides we
do not even measure a signal in any sample.
Preprocessing
This section preforms preprocessing for the peptide data. This
include
- log transformation,
- filtering and
- summarisation of the data.
Filtering
- Handling overlapping protein groups
We remove peptides that could not be mapped to a protein or that map
to multiple proteins (the protein identifier contains multiple
identifiers separated by a ;
).
pe <- filterFeatures(
pe, ~ Proteins != "" & ## Remove failed protein inference
!grepl(";", Proteins)) ## Remove protein groups
## 'Proteins' found in 2 out of 2 assay(s).
- Remove reverse sequences (decoys) and contaminants
We now remove the contaminants and peptides that map to decoy
sequences.
pe <- filterFeatures(pe,~Reverse != "+")
## 'Reverse' found in 2 out of 2 assay(s).
pe <- filterFeatures(pe,~ Potential.contaminant != "+")
## 'Potential.contaminant' found in 2 out of 2 assay(s).
- Drop peptides that were identified in less than three sample.
We tolerate the following proportion of NAs: pNA = (n-3)/n.
nObs <- 3
n <- ncol(pe[["peptideLog"]])
pNA <- (n-nObs)/n
pe <- filterNA(pe, pNA = pNA, i = "peptideLog")
nrow(pe[["peptideLog"]])
## [1] 5910
We keep 5910 peptides upon filtering.
Explore normalized
data
Upon the normalisation the density curves are nicely registered
pe[["peptideNorm"]] |>
assay() |>
as.data.frame() |>
gather(sample, intensity) |>
mutate(condition = colData(pe)[sample,"condition"]) |>
ggplot(aes(x = intensity,group = sample,color = condition)) +
geom_density() +
theme_minimal()
## Warning: Removed 4568 rows containing non-finite outside the scale range
## (`stat_density()`).

We can visualize our data using a PCA plot eg. as provided by Nipals
from the pcaMethods
package, that can handle missing
data.
plotPCA <- function (pe, assayName, varName=NULL)
{
require(pcaMethods)
if (!is.null(varName)) varName <- as.character(varName)
#extract assayData and transpose
dat <- pe[[assayName]] |>
assay() |>
t()
# Replace NaN values to NA (often occur upon median summarisation)
dat[is.nan(dat)] <- NA
# Run Nipals
pc <- pca(dat, method = "nipals")
# Make data frame for plotting
df <- merge(scores(pc), colData(pe), by = 0)
if (!is.null(varName)) return(
ggplot(df, aes(PC1, PC2, col = !!sym(varName))) +
geom_point() +
xlab(paste0("PC1 (", round(pc@R2[1] * 100, 1), "%)")) +
ylab(paste0("PC2 (", round(pc@R2[2] * 100, 1), "%)")) +
theme_minimal()
) else return(
ggplot(df, aes(PC1, PC2)) +
geom_point() +
xlab(paste0("PC1 (", round(pc@R2[1] * 100, 1), "%)")) +
ylab(paste0("PC2 (", round(pc@R2[2] * 100, 1), "%)")) +
theme_minimal()
)
}
plotPCA(pe, "peptideNorm", "condition")

The first axis in the plot is showing the leading variability (log
fold changes, differences on a log scale) between the samples.
We notice that the leading differences in the peptide data seems to
be driven by technical variability. Indeed, the samples do not seem to
be clearly separated according to the spike-in condition.
Summarization to
protein level
- By default robust summarization is used:
fun = MsCoreUtils::robustSummary()
pe <- aggregateFeatures(pe,
i = "peptideNorm",
fcol = "Proteins",
na.rm = TRUE,
name = "protein")
## Your quantitative and row data contain missing values. Please read the
## relevant section(s) in the aggregateFeatures manual page regarding the
## effects of missing values on data aggregation.
## Aggregated: 1/1
Filter proteins that
contain many missing values
We want to have at least 4 observed proteins so that most proteins
have at least 2 observations in each group.
So we tolerate a proportion of (n-4)/n NAs.
nObs <- 4
n <- ncol(pe[["protein"]])
pNA <- (n-nObs)/n
pe <- filterNA(pe, pNA = pNA, i = "protein")
plotPCA(pe, "protein", "condition")
Note that the samples upon robust summarisation show a clear separation
according to the spike-in condition in the second dimension of the MDS
plot.
Data Analysis
Estimation
We model the protein level expression values using
msqrob
. By default msqrob2
estimates the model
parameters using robust regression.
We will model the data with a different group mean. The group is
incoded in the variable condition
of the colData. We can
specify this model by using a formula with the factor condition as its
predictor: formula = ~condition
.
Note, that a formula always starts with a symbol ‘~’.
pe <- msqrob(object = pe, i = "protein", formula = ~condition)
Inference
First, we extract the parameter names of the model by looking at the
first model. The models are stored in the row data of the assay under
the default name msqrobModels.
getCoef(rowData(pe[["protein"]])$msqrobModels[[1]])
## (Intercept) conditionB
## -2.659614 1.502650
We can also explore the design of the model that we specified using
the the package ExploreModelMatrix
library(ExploreModelMatrix)
VisualizeDesign(colData(pe),~condition)$plotlist[[1]]

Spike-in condition A
is the reference class. So the mean
log2 expression for samples from condition A is ‘(Intercept). The mean
log2 expression for samples from condition B is’(Intercept)+conditionB’.
Hence, the average log2 fold change between condition b and condition a
is modelled using the parameter ‘conditionB’. Thus, we assess the
contrast ‘conditionB = 0’ with our statistical test.
L <- makeContrast("conditionB=0", parameterNames = c("conditionB"))
pe <- hypothesisTest(object = pe, i = "protein", contrast = L)
Plots
Volcano-plot
volcano <- ggplot(rowData(pe[["protein"]])$conditionB,
aes(x = logFC, y = -log10(pval), color = adjPval < 0.05)) +
geom_point(cex = 2.5) +
scale_color_manual(values = alpha(c("black", "red"), 0.5)) + theme_minimal()
volcano

Note, that 20 proteins are found to be differentially abundant.
Heatmap
We first select the names of the proteins that were declared
signficant.
sigNames <- rowData(pe[["protein"]])$conditionB %>%
rownames_to_column("protein") %>%
filter(adjPval<0.05) %>%
pull(protein)
heatmap(assay(pe[["protein"]])[sigNames, ])

The majority of the proteins are indeed UPS proteins. 1 yeast protein
is returned. Note, that the yeast protein indeed shows evidence for
differential abundance.
Boxplots
We make boxplot of the log2 FC and stratify according to the whether
a protein is spiked or not.
rowData(pe[["protein"]])$conditionB %>%
rownames_to_column(var = "protein") %>%
ggplot(aes(x=grepl("UPS",protein),y=logFC)) +
geom_boxplot() +
xlab("UPS") +
geom_segment(
x = 1.5,
xend = 2.5,
y = log2(0.74/0.25),
yend = log2(0.74/0.25),
colour="red") +
geom_segment(
x = 0.5,
xend = 1.5,
y = 0,
yend = 0,
colour="red") +
annotate(
"text",
x = c(1,2),
y = c(0,log2(0.74/0.25))+.1,
label = c(
"log2 FC Ecoli = 0",
paste0("log2 FC UPS = ",round(log2(0.74/0.25),2))
),
colour = "red")
## Warning: Removed 30 rows containing non-finite outside the scale range
## (`stat_boxplot()`).

What do you observe?
Detail plots
We first extract the normalized peptideRaw expression values for a
particular protein.
for (protName in sigNames)
{
pePlot <- pe[protName, , c("peptideNorm","protein")]
pePlotDf <- data.frame(longForm(pePlot))
pePlotDf$assay <- factor(pePlotDf$assay,
levels = c("peptideNorm", "protein"))
pePlotDf$condition <- as.factor(colData(pePlot)[pePlotDf$colname, "condition"])
# plotting
p1 <- ggplot(data = pePlotDf,
aes(x = colname, y = value, group = rowname)) +
geom_line() +
geom_point() +
theme(axis.text.x = element_text(angle = 70, hjust = 1, vjust = 0.5)) +
facet_grid(~assay) +
ggtitle(protName)
print(p1)
# plotting 2
p2 <- ggplot(pePlotDf, aes(x = colname, y = value, fill = condition)) +
geom_boxplot(outlier.shape = NA) +
geom_point(
position = position_jitter(width = .1),
aes(shape = rowname)) +
scale_shape_manual(values = 1:nrow(pePlotDf)) +
labs(title = protName, x = "sample", y = "peptide intensity (log2)") +
theme(axis.text.x = element_text(angle = 70, hjust = 1, vjust = 0.5)) +
facet_grid(~assay)
print(p2)
}








































Note, that the yeast protein is only covered by 3 peptides. Only one
peptide is picked up in condition A. This peptide is also only once
observed in spike-in condition B. This puts a considerable burden upon
the inference and could be avoided by more stringent filtering.
Session Info
With respect to reproducibility, it is highly recommended to include
a session info in your script so that readers of your output can see
your particular setup of R.
## R version 4.4.0 RC (2024-04-16 r86468)
## Platform: aarch64-apple-darwin20
## Running under: macOS 15.5
##
## Matrix products: default
## BLAS: /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/lib/libRblas.0.dylib
## LAPACK: /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/lib/libRlapack.dylib; LAPACK version 3.12.0
##
## locale:
## [1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8
##
## time zone: Europe/London
## tzcode source: internal
##
## attached base packages:
## [1] stats4 stats graphics grDevices utils datasets methods
## [8] base
##
## other attached packages:
## [1] ExploreModelMatrix_1.16.0 pcaMethods_1.96.0
## [3] data.table_1.17.6 plotly_4.10.4
## [5] msqrob2_1.15.1 QFeatures_1.19.3
## [7] MultiAssayExperiment_1.34.0 SummarizedExperiment_1.34.0
## [9] Biobase_2.64.0 GenomicRanges_1.56.1
## [11] GenomeInfoDb_1.40.1 IRanges_2.38.1
## [13] S4Vectors_0.42.1 BiocGenerics_0.54.0
## [15] generics_0.1.3 MatrixGenerics_1.16.0
## [17] matrixStats_1.4.1 limma_3.60.6
## [19] lubridate_1.9.3 forcats_1.0.0
## [21] stringr_1.5.1 dplyr_1.1.4
## [23] purrr_1.0.2 readr_2.1.5
## [25] tidyr_1.3.1 tibble_3.2.1
## [27] ggplot2_3.5.1 tidyverse_2.0.0
##
## loaded via a namespace (and not attached):
## [1] rlang_1.1.4 magrittr_2.0.3 shinydashboard_0.7.2
## [4] clue_0.3-65 compiler_4.4.0 vctrs_0.6.5
## [7] reshape2_1.4.4 ProtGenerics_1.36.0 pkgconfig_2.0.3
## [10] crayon_1.5.3 fastmap_1.2.0 XVector_0.44.0
## [13] fontawesome_0.5.2 labeling_0.4.3 utf8_1.2.4
## [16] promises_1.3.0 rmarkdown_2.28 tzdb_0.4.0
## [19] UCSC.utils_1.0.0 nloptr_2.1.1 bit_4.5.0
## [22] xfun_0.47 zlibbioc_1.50.0 cachem_1.1.0
## [25] jsonlite_1.8.9 later_1.3.2 highr_0.11
## [28] DelayedArray_0.30.1 BiocParallel_1.38.0 parallel_4.4.0
## [31] cluster_2.1.6 R6_2.5.1 bslib_0.8.0
## [34] stringi_1.8.4 boot_1.3-31 jquerylib_0.1.4
## [37] Rcpp_1.0.13-1 bookdown_0.40 knitr_1.48
## [40] BiocBaseUtils_1.10.0 httpuv_1.6.15 Matrix_1.7-0
## [43] splines_4.4.0 igraph_2.0.3 timechange_0.3.0
## [46] tidyselect_1.2.1 rstudioapi_0.16.0 abind_1.4-8
## [49] yaml_2.3.10 codetools_0.2-20 lattice_0.22-6
## [52] plyr_1.8.9 shiny_1.9.1 withr_3.0.1
## [55] evaluate_1.0.0 pillar_1.9.0 DT_0.33
## [58] shinyjs_2.1.0 hms_1.1.3 munsell_0.5.1
## [61] scales_1.3.0 minqa_1.2.8 xtable_1.8-4
## [64] glue_1.8.0 lazyeval_0.2.2 tools_4.4.0
## [67] lme4_1.1-35.5 cowplot_1.1.3 grid_4.4.0
## [70] MsCoreUtils_1.16.1 colorspace_2.1-1 nlme_3.1-166
## [73] GenomeInfoDbData_1.2.12 cli_3.6.3 fansi_1.0.6
## [76] S4Arrays_1.4.1 viridisLite_0.4.2 AnnotationFilter_1.28.0
## [79] gtable_0.3.5 rintrojs_0.3.4 sass_0.4.9
## [82] digest_0.6.37 SparseArray_1.4.8 farver_2.1.2
## [85] htmlwidgets_1.6.4 htmltools_0.5.8.1 lifecycle_1.0.4
## [88] httr_1.4.7 mime_0.12 statmod_1.5.0
## [91] bit64_4.5.2 MASS_7.3-61
LS0tCnRpdGxlOiAiSW50cm9kdWN0aW9uIHRvIHByb3Rlb21pY3MgZGF0YSBhbmFseXNpczogcm9idXN0IHN1bW1hcml6YXRpb24iCmF1dGhvcjogIkxpZXZlbiBDbGVtZW50IgpkYXRlOiAic3RhdE9taWNzLCBHaGVudCBVbml2ZXJzaXR5IChodHRwczovL3N0YXRvbWljcy5naXRodWIuaW8pIgpvdXRwdXQ6CiAgICBodG1sX2RvY3VtZW50OgogICAgICBjb2RlX2Rvd25sb2FkOiB0cnVlCiAgICAgIHRoZW1lOiBmbGF0bHkKICAgICAgdG9jOiB0cnVlCiAgICAgIHRvY19mbG9hdDogdHJ1ZQogICAgICBoaWdobGlnaHQ6IHRhbmdvCiAgICAgIG51bWJlcl9zZWN0aW9uczogdHJ1ZQogICAgcGRmX2RvY3VtZW50OgogICAgICB0b2M6IHRydWUKICAgICAgbnVtYmVyX3NlY3Rpb25zOiB0cnVlCmxpbmtjb2xvcjogYmx1ZQp1cmxjb2xvcjogYmx1ZQpjaXRlY29sb3I6IGJsdWUKCmJpYmxpb2dyYXBoeTogbXNxcm9iMi5iaWIKCi0tLQoKPGEgcmVsPSJsaWNlbnNlIiBocmVmPSJodHRwczovL2NyZWF0aXZlY29tbW9ucy5vcmcvbGljZW5zZXMvYnktbmMtc2EvNC4wIj48aW1nIGFsdD0iQ3JlYXRpdmUgQ29tbW9ucyBMaWNlbnNlIiBzdHlsZT0iYm9yZGVyLXdpZHRoOjAiIHNyYz0iaHR0cHM6Ly9pLmNyZWF0aXZlY29tbW9ucy5vcmcvbC9ieS1uYy1zYS80LjAvODh4MzEucG5nIiAvPjwvYT4KClRoaXMgaXMgcGFydCBvZiB0aGUgb25saW5lIGNvdXJzZSBbUHJvdGVvbWljcyBEYXRhIEFuYWx5c2lzIChQREEpXShodHRwczovL3N0YXRvbWljcy5naXRodWIuaW8vUERBLykKCiMgQmFja2dyb3VuZApUaGlzIGNhc2Utc3R1ZHkgaXMgYSBzdWJzZXQgb2YgdGhlIGRhdGEgb2YgdGhlIDZ0aCBzdHVkeSBvZiB0aGUgQ2xpbmljYWwKUHJvdGVvbWljIFRlY2hub2xvZ3kgQXNzZXNzbWVudCBmb3IgQ2FuY2VyIChDUFRBQykuCkluIHRoaXMgZXhwZXJpbWVudCwgdGhlIGF1dGhvcnMgc3Bpa2VkIHRoZSBTaWdtYSBVbml2ZXJzYWwgUHJvdGVpbiBTdGFuZGFyZAptaXh0dXJlIDEgKFVQUzEpIGNvbnRhaW5pbmcgNDggZGlmZmVyZW50IGh1bWFuIHByb3RlaW5zIGluIGEgcHJvdGVpbiBiYWNrZ3JvdW5kCm9mIDYwIG5nLyRcbXUkTCBTYWNjaGFyb215Y2VzIGNlcmV2aXNpYWUgc3RyYWluIEJZNDc0MS4KVHdvIGRpZmZlcmVudCBzcGlrZS1pbiBjb25jZW50cmF0aW9ucyB3ZXJlIHVzZWQ6CjZBICgwLjI1IGZtb2wgVVBTMSBwcm90ZWlucy8kXG11JEwpIGFuZCA2QiAoMC43NCBmbW9sIFVQUzEgcHJvdGVpbnMvJFxtdSRMKSBbNV0uCldlIGxpbWl0ZWQgb3Vyc2VsdmVzIHRvIHRoZSBkYXRhIG9mIExUUS1PcmJpdHJhcCBXIGF0IHNpdGUgNTYuClRoZSBkYXRhIHdlcmUgc2VhcmNoZWQgd2l0aCBNYXhRdWFudCB2ZXJzaW9uIDEuNS4yLjgsIGFuZApkZXRhaWxlZCBzZWFyY2ggc2V0dGluZ3Mgd2VyZSBkZXNjcmliZWQgaW4gR29lbWlubmUgZXQgYWwuICgyMDE2KSBbMV0uClRocmVlIHJlcGxpY2F0ZXMgYXJlIGF2YWlsYWJsZSBmb3IgZWFjaCBjb25jZW50cmF0aW9uLgoKCiMgRGF0YQoKV2UgZmlyc3QgaW1wb3J0IHRoZSBkYXRhIGZyb20gcGVwdGlkZVJhd3MudHh0IGZpbGUuIFRoaXMgaXMgdGhlIGZpbGUgY29udGFpbmluZwp5b3VyIHBlcHRpZGVSYXctbGV2ZWwgaW50ZW5zaXRpZXMuIEZvciBhIE1heFF1YW50IHNlYXJjaCBbNl0sCnRoaXMgcGVwdGlkZVJhd3MudHh0IGZpbGUgY2FuIGJlIGZvdW5kIGJ5IGRlZmF1bHQgaW4gdGhlCiJwYXRoX3RvX3Jhd19maWxlcy9jb21iaW5lZC90eHQvIiBmb2xkZXIgZnJvbSB0aGUgTWF4UXVhbnQgb3V0cHV0LAp3aXRoICJwYXRoX3RvX3Jhd19maWxlcyIgdGhlIGZvbGRlciB3aGVyZSB0aGUgcmF3IGZpbGVzIHdlcmUgc2F2ZWQuCkluIHRoaXMgdmlnbmV0dGUsIHdlIHVzZSBhIE1heFF1YW50IHBlcHRpZGVSYXdzIGZpbGUgd2hpY2ggaXMgYSBzdWJzZXQKb2YgdGhlIGNwdGFjIHN0dWR5LiBUaGlzIGRhdGEgaXMgYXZhaWxhYmxlIGluIHRoZSBgbXNkYXRhYCBwYWNrYWdlLgpUbyBpbXBvcnQgdGhlIGRhdGEgd2UgdXNlIHRoZSBgUUZlYXR1cmVzYCBwYWNrYWdlLgoKV2UgZ2VuZXJhdGUgdGhlIG9iamVjdCBwZXB0aWRlUmF3RmlsZSB3aXRoIHRoZSBwYXRoIHRvIHRoZSBwZXB0aWRlUmF3cy50eHQgZmlsZS4KVXNpbmcgdGhlIGBncmVwRWNvbHNgIGZ1bmN0aW9uLCB3ZSBmaW5kIHRoZSBjb2x1bW5zIHRoYXQgY29udGFpbiB0aGUgZXhwcmVzc2lvbgpkYXRhIG9mIHRoZSBwZXB0aWRlUmF3cyBpbiB0aGUgcGVwdGlkZVJhd3MudHh0IGZpbGUuCgoxLiBMb2FkIGxpYnJhcmllcyAKCmBgYHtyLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShsaW1tYSkKbGlicmFyeShRRmVhdHVyZXMpCmxpYnJhcnkobXNxcm9iMikKbGlicmFyeShwbG90bHkpCmxpYnJhcnkoZGF0YS50YWJsZSkKbGlicmFyeShwY2FNZXRob2RzKQpgYGAKCjIuIFdlIHVzZSBhIHBlcHRpZGVzLnR4dCBmaWxlIGZyb20gTVMtZGF0YSBxdWFudGlmaWVkIHdpdGggbWF4cXVhbnQgdGhhdCAKY29udGFpbnMgTVMxIGludGVuc2l0aWVzIHN1bW1hcml6ZWQgYXQgdGhlIHBlcHRpZGUgbGV2ZWwuIAoKYGBge3J9CnBlcHRpZGVzVGFibGUgPC0gZnJlYWQoImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9zdGF0T21pY3MvU0dBMjAyMC9kYXRhL3F1YW50aWZpY2F0aW9uL2NwdGFjQXZzQl9sYWIzL3BlcHRpZGVzLnR4dCIpCmludDY0IDwtIHdoaWNoKHNhcHBseShwZXB0aWRlc1RhYmxlLGNsYXNzKSA9PSAiaW50ZWdlcjY0IikKZm9yIChqIGluIGludDY0KSBwZXB0aWRlc1RhYmxlW1tqXV0gPC0gYXMubnVtZXJpYyhwZXB0aWRlc1RhYmxlW1tqXV0pICAgICAgCmBgYAoKMy4gTWF4cXVhbnQgc3RvcmVzIHRoZSBpbnRlbnNpdHkgZGF0YSBmb3IgdGhlIGRpZmZlcmVudCBzYW1wbGVzIGluIGNvbHVtbm5zIHRoYXQgc3RhcnQgd2l0aCBJbnRlbnNpdHkuIFdlIGNhbiByZXRyZWl2ZSB0aGUgY29sdW1uIG5hbWVzIHdpdGggdGhlIGludGVuc2l0eSBkYXRhIHdpdGggdGhlIGNvZGUgYmVsb3c6IAoKYGBge3J9CnF1YW50Q29scyA8LSBncmVwKCJJbnRlbnNpdHkgIiwgbmFtZXMocGVwdGlkZXNUYWJsZSkpCmBgYAoKNC4gUmVhZCB0aGUgZGF0YSBhbmQgc3RvcmUgaXQgaW4gIFFGZWF0dXJlcyBvYmplY3QsIHRoZSBmaXJzdCBjb2x1bW4gb2YgdGhlIHBlcHRpZGVzIGZpbGUgY29udGFpbnMgdGhlIHBlcHRpZGUgc2VxdWVuY2UsIHdoaWNoIGlzIGEgdW5pcXVlIGlkZW50aWZpZXIgZm9yIHRoZSBwZXB0aWRlLiBXZSB3aWxsIHVzZSB0aGlzIGNvbHVtbiBhcyB0aGUgcm93bmFtZXMgb2YgdGhlIGFzc2F5IGRhdGEgYGZuYW1lcyA9IDFgLiAKCmBgYHtyfQpwZSA8LSByZWFkUUZlYXR1cmVzKAogIGFzc2F5RGF0YSA9IHBlcHRpZGVzVGFibGUsCiAgZm5hbWVzID0gMSwKICBxdWFudENvbHMgPSAgcXVhbnRDb2xzLAogIG5hbWUgPSAicGVwdGlkZVJhdyIpCnJtKHBlcHRpZGVzVGFibGUpCmdjKCkKZ2MoKQoKY29sbmFtZXMocGUpCmBgYAoKSW4gdGhlIGZvbGxvd2luZyBjb2RlIGNodW5rLCB3ZSBjYW4gZXh0cmFjdCB0aGUgc3Bpa2VpbiBjb25kaXRpb24gZnJvbSB0aGUgcmF3IGZpbGUgbmFtZS4KCmBgYHtyfQpjb25kIDwtIHdoaWNoKAogIHN0cnNwbGl0KGNvbG5hbWVzKHBlKVtbMV1dWzFdLCBzcGxpdCA9ICIiKVtbMV1dID09ICJBIikgIyBmaW5kIHdoZXJlIGNvbmRpdGlvbiBpcyBzdG9yZWQKCmNvbERhdGEocGUpJGNvbmRpdGlvbiA8LSBzdWJzdHIoY29sbmFtZXMocGUpLCBjb25kLCBjb25kKSB8PgogIHVubGlzdCgpIHw+ICAKICBhcy5mYWN0b3IoKQpgYGAKCgoKUGVwdGlkZXMgd2l0aCB6ZXJvIGludGVuc2l0aWVzIGFyZSBtaXNzaW5nIHBlcHRpZGVzIGFuZCBzaG91bGQgYmUgcmVwcmVzZW50CndpdGggYSBgTkFgIHZhbHVlIHJhdGhlciB0aGFuIGAwYC4KYGBge3J9CnBlIDwtIHplcm9Jc05BKHBlLCAicGVwdGlkZVJhdyIpICMgY29udmVydCAwIHRvIE5BCmBgYAoKCiMjIERhdGEgZXhwbG9yYXRpb24KCmByIGZvcm1hdChtZWFuKGlzLm5hKGFzc2F5KHBlW1sicGVwdGlkZVJhdyJdXSkpKSoxMDAsZGlnaXRzPTIpYCUgb2YgYWxsIHBlcHRpZGUKaW50ZW5zaXRpZXMgYXJlIG1pc3NpbmcgYW5kIGZvciBzb21lIHBlcHRpZGVzIHdlIGRvIG5vdCBldmVuIG1lYXN1cmUgYSBzaWduYWwKaW4gYW55IHNhbXBsZS4KCgojIFByZXByb2Nlc3NpbmcKClRoaXMgc2VjdGlvbiBwcmVmb3JtcyBwcmVwcm9jZXNzaW5nIGZvciB0aGUgcGVwdGlkZSBkYXRhLiAKVGhpcyBpbmNsdWRlIAoKLSBsb2cgdHJhbnNmb3JtYXRpb24sIAotIGZpbHRlcmluZyBhbmQgCi0gc3VtbWFyaXNhdGlvbiBvZiB0aGUgZGF0YS4KCiMjIExvZyB0cmFuc2Zvcm0gdGhlIGRhdGEKCmBgYHtyfQpwZSA8LSBsb2dUcmFuc2Zvcm0ocGUsIGJhc2UgPSAyLCBpID0gInBlcHRpZGVSYXciLCBuYW1lID0gInBlcHRpZGVMb2ciKQpgYGAKCiMjIEZpbHRlcmluZwoKMS4gSGFuZGxpbmcgb3ZlcmxhcHBpbmcgcHJvdGVpbiBncm91cHMKCldlIHJlbW92ZSBwZXB0aWRlcyB0aGF0IGNvdWxkIG5vdCBiZSBtYXBwZWQgdG8gYSBwcm90ZWluIG9yIHRoYXQgbWFwIHRvIG11bHRpcGxlIHByb3RlaW5zICh0aGUgcHJvdGVpbiBpZGVudGlmaWVyIGNvbnRhaW5zIG11bHRpcGxlIGlkZW50aWZpZXJzIHNlcGFyYXRlZCBieSBhIGA7YCkuCgpgYGB7cn0KcGUgPC0gZmlsdGVyRmVhdHVyZXMoCiAgICBwZSwgfiBQcm90ZWlucyAhPSAiIiAmICMjIFJlbW92ZSBmYWlsZWQgcHJvdGVpbiBpbmZlcmVuY2UKICAgICAgICAhZ3JlcGwoIjsiLCBQcm90ZWlucykpICMjIFJlbW92ZSBwcm90ZWluIGdyb3VwcwpgYGAKCjIuIFJlbW92ZSByZXZlcnNlIHNlcXVlbmNlcyAoZGVjb3lzKSBhbmQgY29udGFtaW5hbnRzCgpXZSBub3cgcmVtb3ZlIHRoZSBjb250YW1pbmFudHMgYW5kIHBlcHRpZGVzIHRoYXQgbWFwIHRvIGRlY295IHNlcXVlbmNlcy4KCmBgYHtyfQpwZSA8LSBmaWx0ZXJGZWF0dXJlcyhwZSx+UmV2ZXJzZSAhPSAiKyIpCnBlIDwtIGZpbHRlckZlYXR1cmVzKHBlLH4gUG90ZW50aWFsLmNvbnRhbWluYW50ICE9ICIrIikKYGBgCgozLiBEcm9wIHBlcHRpZGVzIHRoYXQgd2VyZSBpZGVudGlmaWVkIGluIGxlc3MgdGhhbiB0aHJlZSBzYW1wbGUuIAoKV2UgdG9sZXJhdGUgdGhlIGZvbGxvd2luZyBwcm9wb3J0aW9uIG9mIE5BczogcE5BID0gKG4tMykvbi4gCgpgYGB7cn0Kbk9icyA8LSAzCm4gPC0gbmNvbChwZVtbInBlcHRpZGVMb2ciXV0pCnBOQSA8LSAobi1uT2JzKS9uCnBlIDwtIGZpbHRlck5BKHBlLCBwTkEgPSBwTkEsIGkgPSAicGVwdGlkZUxvZyIpCm5yb3cocGVbWyJwZXB0aWRlTG9nIl1dKQpgYGAKCldlIGtlZXAgYHIgbnJvdyhwZVtbInBlcHRpZGVMb2ciXV0pYCBwZXB0aWRlcyB1cG9uIGZpbHRlcmluZy4KCgojIyBOb3JtYWxpemUgdGhlIGRhdGEgdXNpbmcgbWVkaWFuIGNlbnRlcmluZyAKCldlIG5vcm1hbGl6ZSB0aGUgZGF0YSBieSBzdWJzdHJhY3RpbmcgdGhlIHNhbXBsZSBtZWRpYW4gZnJvbSBldmVyeSBpbnRlbnNpdHkgZm9yIHBlcHRpZGUgJHAkICBpbiBhIHNhbXBsZSAkaSQ6IAoKJCR5X3tpcH1eXHRleHR7bm9ybX0gPSB5X3tpcH0gLSBcaGF0XG11X2kkJCAKCndpdGggJFxoYXRcbXVfaSQgdGhlIG1lZGlhbiBpbnRlbnNpdHkgb3ZlciBhbGwgb2JzZXJ2ZWQgcGVwdGlkZXMgaW4gc2FtcGxlICRpJC4KCmBgYHtyfQpwZSA8LSBub3JtYWxpemUocGUsIAogICAgICAgICAgICAgICAgaSA9ICJwZXB0aWRlTG9nIiwgCiAgICAgICAgICAgICAgICBuYW1lID0gInBlcHRpZGVOb3JtIiwgCiAgICAgICAgICAgICAgICBtZXRob2QgPSAiY2VudGVyLm1lZGlhbiIpCmBgYAoKCiMjIEV4cGxvcmUgIG5vcm1hbGl6ZWQgZGF0YQoKVXBvbiB0aGUgbm9ybWFsaXNhdGlvbiB0aGUgZGVuc2l0eSBjdXJ2ZXMgYXJlIG5pY2VseSByZWdpc3RlcmVkCgpgYGB7cn0KcGVbWyJwZXB0aWRlTm9ybSJdXSB8PgogIGFzc2F5KCkgfD4KICBhcy5kYXRhLmZyYW1lKCkgfD4KICBnYXRoZXIoc2FtcGxlLCBpbnRlbnNpdHkpIHw+IAogIG11dGF0ZShjb25kaXRpb24gPSBjb2xEYXRhKHBlKVtzYW1wbGUsImNvbmRpdGlvbiJdKSB8PgogIGdncGxvdChhZXMoeCA9IGludGVuc2l0eSxncm91cCA9IHNhbXBsZSxjb2xvciA9IGNvbmRpdGlvbikpICsgCiAgZ2VvbV9kZW5zaXR5KCkgKwogIHRoZW1lX21pbmltYWwoKQpgYGAKCldlIGNhbiB2aXN1YWxpemUgb3VyIGRhdGEgdXNpbmcgYSBQQ0EgcGxvdAplZy4gYXMgcHJvdmlkZWQgYnkgTmlwYWxzIGZyb20gdGhlICBgcGNhTWV0aG9kc2AgcGFja2FnZSwgdGhhdCBjYW4gaGFuZGxlIG1pc3NpbmcgZGF0YS4KCmBgYHtyfQpwbG90UENBIDwtIGZ1bmN0aW9uIChwZSwgYXNzYXlOYW1lLCB2YXJOYW1lPU5VTEwpIAp7CiAgICByZXF1aXJlKHBjYU1ldGhvZHMpCiAgICBpZiAoIWlzLm51bGwodmFyTmFtZSkpIHZhck5hbWUgPC0gYXMuY2hhcmFjdGVyKHZhck5hbWUpCiAgICAjZXh0cmFjdCBhc3NheURhdGEgYW5kIHRyYW5zcG9zZSAKICAgIGRhdCA8LSBwZVtbYXNzYXlOYW1lXV0gfD4gCiAgICAgIGFzc2F5KCkgfD4gCiAgICAgIHQoKQogICAgIyBSZXBsYWNlIE5hTiB2YWx1ZXMgdG8gTkEgKG9mdGVuIG9jY3VyIHVwb24gbWVkaWFuIHN1bW1hcmlzYXRpb24pCiAgICBkYXRbaXMubmFuKGRhdCldIDwtIE5BCiAgICAjIFJ1biBOaXBhbHMKICAgIHBjIDwtIHBjYShkYXQsIG1ldGhvZCA9ICJuaXBhbHMiKQogICAgIyBNYWtlIGRhdGEgZnJhbWUgZm9yIHBsb3R0aW5nCiAgICBkZiA8LSBtZXJnZShzY29yZXMocGMpLCBjb2xEYXRhKHBlKSwgYnkgPSAwKQogICAgaWYgKCFpcy5udWxsKHZhck5hbWUpKSByZXR1cm4oCiAgICAgIGdncGxvdChkZiwgYWVzKFBDMSwgUEMyLCBjb2wgPSAhIXN5bSh2YXJOYW1lKSkpICsKICAgICAgZ2VvbV9wb2ludCgpICsgCiAgICAgIHhsYWIocGFzdGUwKCJQQzEgKCIsIHJvdW5kKHBjQFIyWzFdICogMTAwLCAxKSwgIiUpIikpICsgCiAgICAgIHlsYWIocGFzdGUwKCJQQzIgKCIsIHJvdW5kKHBjQFIyWzJdICogMTAwLCAxKSwgIiUpIikpICsKICAgICAgdGhlbWVfbWluaW1hbCgpCiAgICApIGVsc2UgcmV0dXJuKAogICAgICBnZ3Bsb3QoZGYsIGFlcyhQQzEsIFBDMikpICsKICAgICAgZ2VvbV9wb2ludCgpICsgCiAgICAgIHhsYWIocGFzdGUwKCJQQzEgKCIsIHJvdW5kKHBjQFIyWzFdICogMTAwLCAxKSwgIiUpIikpICsgCiAgICAgIHlsYWIocGFzdGUwKCJQQzIgKCIsIHJvdW5kKHBjQFIyWzJdICogMTAwLCAxKSwgIiUpIikpICsKICAgICAgdGhlbWVfbWluaW1hbCgpCiAgICApCn0KcGxvdFBDQShwZSwgInBlcHRpZGVOb3JtIiwgImNvbmRpdGlvbiIpCmBgYAoKVGhlIGZpcnN0IGF4aXMgaW4gdGhlIHBsb3QgaXMgc2hvd2luZyB0aGUgbGVhZGluZyB2YXJpYWJpbGl0eSAobG9nIGZvbGQgY2hhbmdlcywgZGlmZmVyZW5jZXMgb24gYSBsb2cgc2NhbGUpIGJldHdlZW4gdGhlIHNhbXBsZXMuCgpXZSBub3RpY2UgdGhhdCB0aGUgbGVhZGluZyBkaWZmZXJlbmNlcyAKaW4gdGhlIHBlcHRpZGUgZGF0YSBzZWVtcyB0byBiZSBkcml2ZW4gYnkgdGVjaG5pY2FsIHZhcmlhYmlsaXR5LgpJbmRlZWQsIHRoZSBzYW1wbGVzIGRvIG5vdCBzZWVtIHRvIGJlIGNsZWFybHkgc2VwYXJhdGVkIGFjY29yZGluZwp0byB0aGUgc3Bpa2UtaW4gY29uZGl0aW9uLgoKCiMjIFN1bW1hcml6YXRpb24gdG8gcHJvdGVpbiBsZXZlbAoKLSBCeSBkZWZhdWx0IHJvYnVzdCBzdW1tYXJpemF0aW9uIGlzIHVzZWQ6ICBgZnVuID0gTXNDb3JlVXRpbHM6OnJvYnVzdFN1bW1hcnkoKWAKCmBgYHtyLHdhcm5pbmc9RkFMU0V9CnBlIDwtIGFnZ3JlZ2F0ZUZlYXR1cmVzKHBlLAogIGkgPSAicGVwdGlkZU5vcm0iLAogIGZjb2wgPSAiUHJvdGVpbnMiLAogIG5hLnJtID0gVFJVRSwKICBuYW1lID0gInByb3RlaW4iKQpgYGAKCiMjIEZpbHRlciBwcm90ZWlucyB0aGF0IGNvbnRhaW4gbWFueSBtaXNzaW5nIHZhbHVlcyAKCldlIHdhbnQgdG8gaGF2ZSBhdCBsZWFzdCA0IG9ic2VydmVkIHByb3RlaW5zIHNvIHRoYXQgbW9zdCBwcm90ZWlucyBoYXZlIGF0IGxlYXN0IDIgb2JzZXJ2YXRpb25zIGluIGVhY2ggZ3JvdXAuICAKU28gd2UgdG9sZXJhdGUgYSBwcm9wb3J0aW9uIG9mIChuLTQpL24gTkFzLiAKCgpgYGB7cn0Kbk9icyA8LSA0Cm4gPC0gbmNvbChwZVtbInByb3RlaW4iXV0pCnBOQSA8LSAobi1uT2JzKS9uCnBlIDwtIGZpbHRlck5BKHBlLCBwTkEgPSBwTkEsIGkgPSAicHJvdGVpbiIpCmBgYAoKCmBgYHtyfQpwbG90UENBKHBlLCAicHJvdGVpbiIsICJjb25kaXRpb24iKQpgYGAKTm90ZSB0aGF0IHRoZSBzYW1wbGVzIHVwb24gcm9idXN0IHN1bW1hcmlzYXRpb24gc2hvdyBhIGNsZWFyIHNlcGFyYXRpb24gYWNjb3JkaW5nIHRvIHRoZSBzcGlrZS1pbiBjb25kaXRpb24gaW4gdGhlIHNlY29uZCBkaW1lbnNpb24gb2YgdGhlIE1EUyBwbG90LgoKIyBEYXRhIEFuYWx5c2lzCgojIyBFc3RpbWF0aW9uCgpXZSBtb2RlbCB0aGUgcHJvdGVpbiBsZXZlbCBleHByZXNzaW9uIHZhbHVlcyB1c2luZyBgbXNxcm9iYC4KQnkgZGVmYXVsdCBgbXNxcm9iMmAgZXN0aW1hdGVzIHRoZSBtb2RlbCBwYXJhbWV0ZXJzIHVzaW5nIHJvYnVzdCByZWdyZXNzaW9uLgoKV2Ugd2lsbCBtb2RlbCB0aGUgZGF0YSB3aXRoIGEgZGlmZmVyZW50IGdyb3VwIG1lYW4uIApUaGUgZ3JvdXAgaXMgaW5jb2RlZCBpbiB0aGUgdmFyaWFibGUgYGNvbmRpdGlvbmAgb2YgdGhlIGNvbERhdGEuIApXZSBjYW4gc3BlY2lmeSB0aGlzIG1vZGVsIGJ5IHVzaW5nIGEgZm9ybXVsYSB3aXRoIHRoZSBmYWN0b3IgY29uZGl0aW9uIGFzIGl0cyBwcmVkaWN0b3I6IApgZm9ybXVsYSA9IH5jb25kaXRpb25gLgoKTm90ZSwgdGhhdCBhIGZvcm11bGEgYWx3YXlzIHN0YXJ0cyB3aXRoIGEgc3ltYm9sICd+Jy4KCmBgYHtyLCB3YXJuaW5nPUZBTFNFfQpwZSA8LSBtc3Fyb2Iob2JqZWN0ID0gcGUsIGkgPSAicHJvdGVpbiIsIGZvcm11bGEgPSB+Y29uZGl0aW9uKQpgYGAKCiMjIEluZmVyZW5jZQoKRmlyc3QsIHdlIGV4dHJhY3QgdGhlIHBhcmFtZXRlciBuYW1lcyBvZiB0aGUgbW9kZWwgYnkgbG9va2luZyBhdCB0aGUgZmlyc3QgbW9kZWwuIApUaGUgbW9kZWxzIGFyZSBzdG9yZWQgaW4gdGhlIHJvdyBkYXRhIG9mIHRoZSBhc3NheSB1bmRlciB0aGUgZGVmYXVsdCBuYW1lIG1zcXJvYk1vZGVscy4gCgpgYGB7cn0KZ2V0Q29lZihyb3dEYXRhKHBlW1sicHJvdGVpbiJdXSkkbXNxcm9iTW9kZWxzW1sxXV0pCmBgYAoKV2UgY2FuIGFsc28gZXhwbG9yZSB0aGUgZGVzaWduIG9mIHRoZSBtb2RlbCB0aGF0IHdlIHNwZWNpZmllZCB1c2luZyB0aGUgdGhlIHBhY2thZ2UgYEV4cGxvcmVNb2RlbE1hdHJpeGAgCgpgYGB7cn0KbGlicmFyeShFeHBsb3JlTW9kZWxNYXRyaXgpClZpc3VhbGl6ZURlc2lnbihjb2xEYXRhKHBlKSx+Y29uZGl0aW9uKSRwbG90bGlzdFtbMV1dCmBgYAoKU3Bpa2UtaW4gY29uZGl0aW9uIGBBYCBpcyB0aGUgcmVmZXJlbmNlIGNsYXNzLiBTbyB0aGUgbWVhbiBsb2cyIGV4cHJlc3Npb24KZm9yIHNhbXBsZXMgZnJvbSBjb25kaXRpb24gQSBpcyAnKEludGVyY2VwdCkuClRoZSBtZWFuIGxvZzIgZXhwcmVzc2lvbiBmb3Igc2FtcGxlcyBmcm9tIGNvbmRpdGlvbiBCIGlzICcoSW50ZXJjZXB0KStjb25kaXRpb25CJy4KSGVuY2UsIHRoZSBhdmVyYWdlIGxvZzIgZm9sZCBjaGFuZ2UgYmV0d2VlbiBjb25kaXRpb24gYiBhbmQKY29uZGl0aW9uIGEgaXMgbW9kZWxsZWQgdXNpbmcgdGhlIHBhcmFtZXRlciAnY29uZGl0aW9uQicuClRodXMsIHdlIGFzc2VzcyB0aGUgY29udHJhc3QgJ2NvbmRpdGlvbkIgPSAwJyB3aXRoIG91ciBzdGF0aXN0aWNhbCB0ZXN0LgoKYGBge3J9CkwgPC0gbWFrZUNvbnRyYXN0KCJjb25kaXRpb25CPTAiLCBwYXJhbWV0ZXJOYW1lcyA9IGMoImNvbmRpdGlvbkIiKSkKcGUgPC0gaHlwb3RoZXNpc1Rlc3Qob2JqZWN0ID0gcGUsIGkgPSAicHJvdGVpbiIsIGNvbnRyYXN0ID0gTCkKYGBgCgoKIyMgUGxvdHMKCiMjIyBWb2xjYW5vLXBsb3QKCgpgYGB7cix3YXJuaW5nPUZBTFNFfQp2b2xjYW5vIDwtIGdncGxvdChyb3dEYXRhKHBlW1sicHJvdGVpbiJdXSkkY29uZGl0aW9uQiwKICAgICAgICAgICAgICAgICAgYWVzKHggPSBsb2dGQywgeSA9IC1sb2cxMChwdmFsKSwgY29sb3IgPSBhZGpQdmFsIDwgMC4wNSkpICsKICBnZW9tX3BvaW50KGNleCA9IDIuNSkgKwogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBhbHBoYShjKCJibGFjayIsICJyZWQiKSwgMC41KSkgKyB0aGVtZV9taW5pbWFsKCkKdm9sY2FubwpgYGAKCk5vdGUsIHRoYXQgYHIgc3VtKHJvd0RhdGEocGVbWyJwcm90ZWluIl1dKSRjb25kaXRpb25CJGFkalB2YWwgPCAwLjA1LCBuYS5ybSA9IFRSVUUpYCBwcm90ZWlucyBhcmUgZm91bmQgdG8gYmUgZGlmZmVyZW50aWFsbHkgYWJ1bmRhbnQuCgojIyMgSGVhdG1hcAoKV2UgZmlyc3Qgc2VsZWN0IHRoZSBuYW1lcyBvZiB0aGUgcHJvdGVpbnMgdGhhdCB3ZXJlIGRlY2xhcmVkIHNpZ25maWNhbnQuCgpgYGB7cn0Kc2lnTmFtZXMgPC0gcm93RGF0YShwZVtbInByb3RlaW4iXV0pJGNvbmRpdGlvbkIgJT4lCiAgcm93bmFtZXNfdG9fY29sdW1uKCJwcm90ZWluIikgJT4lCiAgZmlsdGVyKGFkalB2YWw8MC4wNSkgJT4lCiAgcHVsbChwcm90ZWluKQpoZWF0bWFwKGFzc2F5KHBlW1sicHJvdGVpbiJdXSlbc2lnTmFtZXMsIF0pCmBgYAoKVGhlIG1ham9yaXR5IG9mIHRoZSBwcm90ZWlucyBhcmUgaW5kZWVkIFVQUyBwcm90ZWlucy4gCjEgeWVhc3QgcHJvdGVpbiBpcyByZXR1cm5lZC4gCk5vdGUsIHRoYXQgdGhlIHllYXN0IHByb3RlaW4gaW5kZWVkIHNob3dzIGV2aWRlbmNlIGZvciBkaWZmZXJlbnRpYWwgYWJ1bmRhbmNlLiAKCiMjIyBCb3hwbG90cwoKV2UgbWFrZSBib3hwbG90IG9mIHRoZSBsb2cyIEZDIGFuZCBzdHJhdGlmeSBhY2NvcmRpbmcgdG8gdGhlIHdoZXRoZXIgYSBwcm90ZWluIGlzIHNwaWtlZCBvciBub3QuCgpgYGB7cn0Kcm93RGF0YShwZVtbInByb3RlaW4iXV0pJGNvbmRpdGlvbkIgJT4lCiAgcm93bmFtZXNfdG9fY29sdW1uKHZhciA9ICJwcm90ZWluIikgJT4lCiAgZ2dwbG90KGFlcyh4PWdyZXBsKCJVUFMiLHByb3RlaW4pLHk9bG9nRkMpKSArCiAgZ2VvbV9ib3hwbG90KCkgKwogIHhsYWIoIlVQUyIpICsKICBnZW9tX3NlZ21lbnQoCiAgICB4ID0gMS41LAogICAgeGVuZCA9IDIuNSwKICAgIHkgPSBsb2cyKDAuNzQvMC4yNSksCiAgICB5ZW5kID0gbG9nMigwLjc0LzAuMjUpLAogICAgY29sb3VyPSJyZWQiKSArCiAgZ2VvbV9zZWdtZW50KAogICAgeCA9IDAuNSwKICAgIHhlbmQgPSAxLjUsCiAgICB5ID0gMCwKICAgIHllbmQgPSAwLAogICAgY29sb3VyPSJyZWQiKSArCiAgYW5ub3RhdGUoCiAgICAidGV4dCIsCiAgICB4ID0gYygxLDIpLAogICAgeSA9IGMoMCxsb2cyKDAuNzQvMC4yNSkpKy4xLAogICAgbGFiZWwgPSBjKAogICAgICAibG9nMiBGQyBFY29saSA9IDAiLAogICAgICBwYXN0ZTAoImxvZzIgRkMgVVBTID0gIixyb3VuZChsb2cyKDAuNzQvMC4yNSksMikpCiAgICAgICksCiAgICBjb2xvdXIgPSAicmVkIikKYGBgCgpXaGF0IGRvIHlvdSBvYnNlcnZlPwoKIyMjIERldGFpbCBwbG90cwoKV2UgZmlyc3QgZXh0cmFjdCB0aGUgbm9ybWFsaXplZCBwZXB0aWRlUmF3IGV4cHJlc3Npb24gdmFsdWVzIGZvciBhIHBhcnRpY3VsYXIgcHJvdGVpbi4gIAoKCmBgYHtyLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQpmb3IgKHByb3ROYW1lIGluIHNpZ05hbWVzKQp7CnBlUGxvdCA8LSBwZVtwcm90TmFtZSwgLCBjKCJwZXB0aWRlTm9ybSIsInByb3RlaW4iKV0KcGVQbG90RGYgPC0gZGF0YS5mcmFtZShsb25nRm9ybShwZVBsb3QpKQpwZVBsb3REZiRhc3NheSA8LSBmYWN0b3IocGVQbG90RGYkYXNzYXksCiAgICAgICAgICAgICAgICAgICAgICAgIGxldmVscyA9IGMoInBlcHRpZGVOb3JtIiwgInByb3RlaW4iKSkKcGVQbG90RGYkY29uZGl0aW9uIDwtIGFzLmZhY3Rvcihjb2xEYXRhKHBlUGxvdClbcGVQbG90RGYkY29sbmFtZSwgImNvbmRpdGlvbiJdKQoKIyBwbG90dGluZwpwMSA8LSBnZ3Bsb3QoZGF0YSA9IHBlUGxvdERmLAogICAgICAgYWVzKHggPSBjb2xuYW1lLCB5ID0gdmFsdWUsIGdyb3VwID0gcm93bmFtZSkpICsKICAgIGdlb21fbGluZSgpICsgCiAgICBnZW9tX3BvaW50KCkgKyAgCiAgICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDcwLCBoanVzdCA9IDEsIHZqdXN0ID0gMC41KSkgKwogICAgZmFjZXRfZ3JpZCh+YXNzYXkpICsgCiAgICBnZ3RpdGxlKHByb3ROYW1lKQpwcmludChwMSkKCiMgcGxvdHRpbmcgMgpwMiA8LSBnZ3Bsb3QocGVQbG90RGYsIGFlcyh4ID0gY29sbmFtZSwgeSA9IHZhbHVlLCBmaWxsID0gY29uZGl0aW9uKSkgKwogIGdlb21fYm94cGxvdChvdXRsaWVyLnNoYXBlID0gTkEpICsgCiAgZ2VvbV9wb2ludCgKICAgIHBvc2l0aW9uID0gcG9zaXRpb25faml0dGVyKHdpZHRoID0gLjEpLAogICAgYWVzKHNoYXBlID0gcm93bmFtZSkpICsKICBzY2FsZV9zaGFwZV9tYW51YWwodmFsdWVzID0gMTpucm93KHBlUGxvdERmKSkgKwogIGxhYnModGl0bGUgPSBwcm90TmFtZSwgeCA9ICJzYW1wbGUiLCB5ID0gInBlcHRpZGUgaW50ZW5zaXR5IChsb2cyKSIpICsgCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA3MCwgaGp1c3QgPSAxLCB2anVzdCA9IDAuNSkpICsKICBmYWNldF9ncmlkKH5hc3NheSkKcHJpbnQocDIpCn0KYGBgCgpOb3RlLCB0aGF0IHRoZSB5ZWFzdCBwcm90ZWluIGlzIG9ubHkgY292ZXJlZCBieSAzIHBlcHRpZGVzLiAKT25seSBvbmUgcGVwdGlkZSBpcyBwaWNrZWQgdXAgaW4gY29uZGl0aW9uIEEuIApUaGlzIHBlcHRpZGUgaXMgYWxzbyBvbmx5IG9uY2Ugb2JzZXJ2ZWQgaW4gc3Bpa2UtaW4gY29uZGl0aW9uIEIuIApUaGlzIHB1dHMgYSBjb25zaWRlcmFibGUgYnVyZGVuIHVwb24gdGhlIGluZmVyZW5jZSBhbmQgY291bGQgYmUgYXZvaWRlZCBieSBtb3JlIHN0cmluZ2VudCBmaWx0ZXJpbmcuIAoKIyBTZXNzaW9uIEluZm8KCldpdGggcmVzcGVjdCB0byByZXByb2R1Y2liaWxpdHksIGl0IGlzIGhpZ2hseSByZWNvbW1lbmRlZCB0byBpbmNsdWRlIGEgc2Vzc2lvbiBpbmZvIGluIHlvdXIgc2NyaXB0IHNvIHRoYXQgcmVhZGVycyBvZiB5b3VyIG91dHB1dCBjYW4gc2VlIHlvdXIgcGFydGljdWxhciBzZXR1cCBvZiBSLiAKCmBgYHtyfQpzZXNzaW9uSW5mbygpCmBgYAo=