This is part of the online course Proteomics Data Analysis (PDA)
Intro: Challenges in Label-Free Quantitative Proteomics
MS-based workflow
Peptide Characteristics
- Modifications
- Ionisation Efficiency: huge variability
- Identification
- Misidentification \(\rightarrow\) outliers
- MS\(^2\) selection on peptide abundance
- Context depending missingness
- Non-random missingness
\(\rightarrow\) Unbalanced pepide identifications across samples and messy data
Level of quantification
- MS-based proteomics returns peptides: pieces of proteins
- Quantification commonly required on the protein level
Label-free Quantitative Proteomics Data Analysis Workflows
QFeatures
Data infrastructure
- We use the
QFeatures
package that provides the infrastructure to
- store,
- process,
- manipulate and
- analyse quantitative data/features from mass spectrometry experiments.
- It is based on the
SummarizedExperiment
and MultiAssayExperiment
classes.
Background of the CPTAC Spike-In Study
This case-study is a subset of the data of the 6th study of the Clinical Proteomic Technology Assessment for Cancer (CPTAC) [5]. 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.
Five different spike-in concentrations were used: - 6A: 0.25 fmol UPS1 proteins/\(\mu L\), - 6B: 0.74 fmol UPS1 proteins/\(\mu L\), - 6C: 2.22 fmol UPS1 proteins/\(\mu L\), - 6D: 6.67 fmol UPS1 proteins/\(\mu L\) and - 6E: 20 fmol UPS1 proteins/\(\mu L\)).
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.
- After MaxQuant search with match between runs option
- 41% of all proteins are quantified in all samples
- 6.6% of all peptides are quantified in all samples
\(\rightarrow\) vast amount of missingness
Import data in R
Load packages
library(tidyverse)
library(limma)
library(QFeatures)
library(msqrob2)
library(plotly)
library(ggplot2)
library(cowplot)
library(gridExtra)
Import data from the CPTAC study
- We use a
peptides.txt
file from MS-data quantified with maxquant that contains MS1 intensities summarized at the peptide level.
peptidesFile <- "https://raw.githubusercontent.com/statOmics/PDA/data/quantification/fullCptacDatasSetNotForTutorial/peptides.txt"
download.file(url = peptidesFile, destfile = "peptides.txt")
- 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:
grep("Intensity\\.", names(read.delim("peptides.txt")), value = TRUE)
## [1] "Intensity.6A_1" "Intensity.6A_2" "Intensity.6A_3" "Intensity.6A_4"
## [5] "Intensity.6A_5" "Intensity.6A_6" "Intensity.6A_7" "Intensity.6A_8"
## [9] "Intensity.6A_9" "Intensity.6B_1" "Intensity.6B_2" "Intensity.6B_3"
## [13] "Intensity.6B_4" "Intensity.6B_5" "Intensity.6B_6" "Intensity.6B_7"
## [17] "Intensity.6B_8" "Intensity.6B_9" "Intensity.6C_1" "Intensity.6C_2"
## [21] "Intensity.6C_3" "Intensity.6C_4" "Intensity.6C_5" "Intensity.6C_6"
## [25] "Intensity.6C_7" "Intensity.6C_8" "Intensity.6C_9" "Intensity.6D_1"
## [29] "Intensity.6D_2" "Intensity.6D_3" "Intensity.6D_4" "Intensity.6D_5"
## [33] "Intensity.6D_6" "Intensity.6D_7" "Intensity.6D_8" "Intensity.6D_9"
## [37] "Intensity.6E_1" "Intensity.6E_2" "Intensity.6E_3" "Intensity.6E_4"
## [41] "Intensity.6E_5" "Intensity.6E_6" "Intensity.6E_7" "Intensity.6E_8"
## [45] "Intensity.6E_9"
(ecols <- grep("Intensity\\.", names(read.delim("peptides.txt"))))
## [1] 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152
## [20] 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171
## [39] 172 173 174 175 176 177 178
- Read the data and store it in QFeatures object
pe_all <- readQFeatures(
"peptides.txt",
fnames = 1,
ecol = ecols,
name = "peptideRaw",
sep = "\t")
The QFeatures object stored in pe
currently contains a single assay, names peptideRaw
, composed of 11466 peptides measured in 45 samples.
## An instance of class QFeatures containing 1 assays:
## [1] peptideRaw: SummarizedExperiment with 11466 rows and 45 columns
We can access the unique assay by index (i.e. 1) or by name (i.e “peptideRaw”) using the [[]]
operator, which returns an instance of class SummarizedExperiment
:
## class: SummarizedExperiment
## dim: 11466 45
## metadata(0):
## assays(1): ''
## rownames(11466): AAAAGAGGAGDSGDAVTK AAAALAGGK ... YYTVFDRDNNR
## YYTVFDRDNNRVGFAEAAR
## rowData names(143): Sequence N.term.cleavage.window ...
## Oxidation..M..site.IDs MS.MS.Count
## colnames(45): Intensity.6A_1 Intensity.6A_2 ... Intensity.6E_8
## Intensity.6E_9
## colData names(0):
## class: SummarizedExperiment
## dim: 11466 45
## metadata(0):
## assays(1): ''
## rownames(11466): AAAAGAGGAGDSGDAVTK AAAALAGGK ... YYTVFDRDNNR
## YYTVFDRDNNRVGFAEAAR
## rowData names(143): Sequence N.term.cleavage.window ...
## Oxidation..M..site.IDs MS.MS.Count
## colnames(45): Intensity.6A_1 Intensity.6A_2 ... Intensity.6E_8
## Intensity.6E_9
## colData names(0):
Explore object
- The
rowData
contains information on the features (peptides) in the assay. E.g. Sequence, protein, …
rowData(pe_all[["peptideRaw"]])[, c("Proteins","Sequence","Charges","Intensity",
"Experiment.6A_7","Experiment.6A_8","Experiment.6A_9" )]
## DataFrame with 11466 rows and 7 columns
## Proteins Sequence Charges Intensity
## <character> <character> <character> <numeric>
## AAAAGAGGAGDSGDAVTK sp|P38915|... AAAAGAGGAG... 2 1190800
## AAAALAGGK sp|Q3E792|... AAAALAGGK 2 280990000
## AAAALAGGKK sp|Q3E792|... AAAALAGGKK 2 33360000
## AAADALSDLEIK sp|P09938|... AAADALSDLE... 2 54622000
## AAADALSDLEIKDSK sp|P09938|... AAADALSDLE... 3 18910000
## ... ... ... ... ...
## YYSIYDLGNNAVGLAK sp|P07267|... YYSIYDLGNN... 2 2145900
## YYTFNGPNYNENETIR sp|Q00955|... YYTFNGPNYN... 2 5608800
## YYTITEVATR sp|P38891|... YYTITEVATR 2 13034000
## YYTVFDRDNNR P07339ups|... YYTVFDRDNN... 2 8702500
## YYTVFDRDNNRVGFAEAAR P07339ups|... YYTVFDRDNN... 3 2391100
## Experiment.6A_7 Experiment.6A_8 Experiment.6A_9
## <integer> <integer> <integer>
## AAAAGAGGAGDSGDAVTK 1 1 1
## AAAALAGGK 2 1 1
## AAAALAGGKK 1 1 1
## AAADALSDLEIK 1 1 1
## AAADALSDLEIKDSK 1 1 1
## ... ... ... ...
## YYSIYDLGNNAVGLAK NA NA NA
## YYTFNGPNYNENETIR 1 NA 1
## YYTITEVATR 1 NA NA
## YYTVFDRDNNR NA NA NA
## YYTVFDRDNNRVGFAEAAR NA NA NA
- The
colData
contains information on the samples, but is currently empty:
## DataFrame with 45 rows and 0 columns
## [1] "Intensity.6A_1" "Intensity.6A_2" "Intensity.6A_3" "Intensity.6A_4"
## [5] "Intensity.6A_5" "Intensity.6A_6" "Intensity.6A_7" "Intensity.6A_8"
## [9] "Intensity.6A_9" "Intensity.6B_1" "Intensity.6B_2" "Intensity.6B_3"
## [13] "Intensity.6B_4" "Intensity.6B_5" "Intensity.6B_6" "Intensity.6B_7"
## [17] "Intensity.6B_8" "Intensity.6B_9" "Intensity.6C_1" "Intensity.6C_2"
## [21] "Intensity.6C_3" "Intensity.6C_4" "Intensity.6C_5" "Intensity.6C_6"
## [25] "Intensity.6C_7" "Intensity.6C_8" "Intensity.6C_9" "Intensity.6D_1"
## [29] "Intensity.6D_2" "Intensity.6D_3" "Intensity.6D_4" "Intensity.6D_5"
## [33] "Intensity.6D_6" "Intensity.6D_7" "Intensity.6D_8" "Intensity.6D_9"
## [37] "Intensity.6E_1" "Intensity.6E_2" "Intensity.6E_3" "Intensity.6E_4"
## [41] "Intensity.6E_5" "Intensity.6E_6" "Intensity.6E_7" "Intensity.6E_8"
## [45] "Intensity.6E_9"
Note, that the sample names include the spike-in condition (A and B). They also end on a number:
- 1-3 is from lab 1,
- 4-6 from lab 2 and
- 7-9 from lab 3.
We can update the colData
with information on the design
pe_all$lab <- rep(rep(paste0("lab", 1:3),each = 3), 5)
pe_all$condition <- pe_all[["peptideRaw"]] %>%
colnames %>%
substr(12, 12)
pe_all$spikeConcentration <- rep(c(A = 0.25, B = 0.74, C = 2.22,
D = 6.67, E = 20),
each = 9)
- We explore the
colData
again
## DataFrame with 45 rows and 3 columns
## lab condition spikeConcentration
## <character> <character> <numeric>
## Intensity.6A_1 lab1 A 0.25
## Intensity.6A_2 lab1 A 0.25
## Intensity.6A_3 lab1 A 0.25
## Intensity.6A_4 lab2 A 0.25
## Intensity.6A_5 lab2 A 0.25
## ... ... ... ...
## Intensity.6E_5 lab2 E 20
## Intensity.6E_6 lab2 E 20
## Intensity.6E_7 lab3 E 20
## Intensity.6E_8 lab3 E 20
## Intensity.6E_9 lab3 E 20
Subset of the CPTAC study
We are going to focus on a subset of the data, specifically on conditions A and B produced by lab3:
subsetCPTAC <- pe_all$condition %in% c("A", "B") & pe_all$lab == "lab3"
pe <- pe_all[, subsetCPTAC]
pe$condition <- factor(pe$condition)
pe
## An instance of class QFeatures containing 1 assays:
## [1] peptideRaw: SummarizedExperiment with 11466 rows and 6 columns
Note that a file containing this same subset is also available from the msdata
package using the quant()
function.
Missingness
Peptides with zero intensities are missing peptides and should be represent with a NA
value rather than 0
. This can be done with the zeroIsNA()
function. We can then use nNA()
on the individual assay to compute missingness summaries:
pe <- zeroIsNA(pe, "peptideRaw")
na <- nNA(pe[[1]])
na
## $nNA
## DataFrame with 1 row and 2 columns
## nNA pNA
## <integer> <numeric>
## 1 31130 45.2497
##
## $nNArows
## DataFrame with 11466 rows and 3 columns
## name nNA pNA
## <character> <integer> <numeric>
## 1 AAAAGAGGAG... 4 66.6667
## 2 AAAALAGGK 0 0.0000
## 3 AAAALAGGKK 0 0.0000
## 4 AAADALSDLE... 0 0.0000
## 5 AAADALSDLE... 0 0.0000
## ... ... ... ...
## 11462 YYSIYDLGNN... 6 100.0000
## 11463 YYTFNGPNYN... 3 50.0000
## 11464 YYTITEVATR 4 66.6667
## 11465 YYTVFDRDNN... 6 100.0000
## 11466 YYTVFDRDNN... 6 100.0000
##
## $nNAcols
## DataFrame with 6 rows and 3 columns
## name nNA pNA
## <character> <integer> <numeric>
## 1 Intensity.... 4743 41.3658
## 2 Intensity.... 5483 47.8196
## 3 Intensity.... 5320 46.3980
## 4 Intensity.... 4721 41.1739
## 5 Intensity.... 5563 48.5174
## 6 Intensity.... 5300 46.2236
- 31130 peptides intensities, corresponding to 45%, are missing and for some peptides we do not even measure a signal in any sample.
- For each sample, the proportion fluctuates between 41.4 and 48.5%.
- The table below shows the number of peptides that have 0, 1, … and up to 6 missing values.
##
## 0 1 2 3 4 5 6
## 4059 990 884 717 934 807 3075
Preprocessing
This section preforms preprocessing for the peptide data. This include
- log transformation,
- filtering and
- summarisation of the data.
Filtering
- Handling overlapping protein groups
In our approach a peptide can map to multiple proteins, as long as there is none of these proteins present in a smaller subgroup.
pe <- filterFeatures(pe, ~ Proteins %in% smallestUniqueGroups(rowData(pe[["peptideLog"]])$Proteins))
- Remove reverse sequences (decoys) and contaminants
We now remove the contaminants and peptides that map to decoy sequences.
pe <- pe |>
filterFeatures(~ Reverse != "+") |>
filterFeatures(~ Potential.contaminant != "+")
pe
## An instance of class QFeatures containing 2 assays:
## [1] peptideRaw: SummarizedExperiment with 10678 rows and 6 columns
## [2] peptideLog: SummarizedExperiment with 10678 rows and 6 columns
- Drop peptides that were only identified in one sample
We keep peptides that were observed at last twice, i.e. those that have no more that 4 missing values
pe <- filterFeatures(pe, ~ nNA(pe[[1]])$nNArows$nNA <= 4)
pe
## An instance of class QFeatures containing 2 assays:
## [1] peptideRaw: SummarizedExperiment with 7011 rows and 6 columns
## [2] peptideLog: SummarizedExperiment with 7011 rows and 6 columns
We keep 7011 peptides upon filtering.
Explore normalized data
Upon the normalisation the density curves follow a similar distribution.
as_tibble(longFormat(pe[, , 2:3], colvars = "condition")) %>%
ggplot(aes(x = value, group = primary, colour = condition)) +
geom_density() +
facet_grid(assay ~ .) +
theme_bw()
## Warning: 'experiments' dropped; see 'metadata'
## harmonizing input:
## removing 6 sampleMap rows not in names(experiments)
## Warning: Removed 16334 rows containing non-finite values (stat_density).
We can visualize our data using a Multi Dimensional Scaling plot, eg. as provided by the limma
package.
tmp <- assay(pe[["peptideNorm"]])
colnames(tmp) <- str_replace_all(colnames(tmp), "Intensity.6","")
tmp %>%
limma::plotMDS(col = as.numeric(colData(pe)$condition))
The first axis in the plot is showing the leading log fold changes (differences on the log scale) between the samples.
We notice that the leading differences (log FC) 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.
Robust summarization
Preprocessing
- By default robust summarization is used:
fun = MsCoreUtils::robustSummary()
- Structure from QFeatures is usefull here. No need to rerun any of the previous log transformation or normalization.
pe <- aggregateFeatures(pe,
i = "peptideNorm",
fcol = "Proteins",
na.rm = TRUE,
name = "protein_robust",
fun = MsCoreUtils::robustSummary)
## 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.
Now we have both the proteinMedian and protein_robust in one QFeatures object.
## An instance of class QFeatures containing 5 assays:
## [1] peptideRaw: SummarizedExperiment with 7011 rows and 6 columns
## [2] peptideLog: SummarizedExperiment with 7011 rows and 6 columns
## [3] peptideNorm: SummarizedExperiment with 7011 rows and 6 columns
## [4] proteinMedian: SummarizedExperiment with 1389 rows and 6 columns
## [5] protein_robust: SummarizedExperiment with 1389 rows and 6 columns
tmp <- assay(pe[["protein_robust"]] )
colnames(tmp) <- str_replace_all(colnames(tmp), "Intensity.6","")
tmp %>%
limma::plotMDS(col = as.numeric(colData(pe)$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_robust", 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_robust"]])$msqrobModels[[1]])
## (Intercept) conditionB
## -2.672396 1.513682
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_robust", contrast = L)
Plots
Volcano-plot
tmp <- rowData(pe[["protein_robust"]])$conditionB[complete.cases(rowData(pe[["protein_robust"]])$conditionB),]
tmp$shapes <- 16
tmp[!grepl("UPS",rownames(tmp)) & tmp$adjPval < 0.05,]$shapes <- 12
#FP <-tmp[!grepl("UPS",rownames(tmp)) & tmp$adjPval < 0.05,]
volcanoRobust<- ggplot(tmp,
aes(x = logFC, y = -log10(pval), color = adjPval < 0.05)) +
geom_point(cex = 2.5, shape = tmp$shapes) +
#geom_point(x =FP$logFC, y = -log10(FP$pval), shape = 8, size = 4 )+
scale_color_manual(values = alpha(c("black", "red"), 0.5)) +
theme_bw() +
ggtitle(paste0("Robust: TP = ",sum(tmp$adjPval<0.05&grepl(rownames(tmp),pattern ="UPS"),na.rm=TRUE), " FP = ", sum(tmp$adjPval<0.05&!grepl(rownames(tmp),pattern ="UPS"),na.rm=TRUE)))
plot_grid(plot_grid(volcanoMedian+theme(legend.position = "none"),
volcanoRobust+theme(legend.position = "none")), get_legend(volcanoRobust + theme(legend.position = "bottom")), ncol = 1, rel_heights = c(1,0.15))
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_robust"]])$conditionB %>%
rownames_to_column("protein_robust") %>%
filter(adjPval<0.05) %>%
pull(protein_robust)
heatmap(assay(pe[["protein_robust"]])[sigNames, ],cexCol = 1)
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.
heatmaps also show difference between median and robust summarization
par(cex.main=.8)
sigProteins <- rowData(pe[["protein_robust"]])$conditionB %>%
rownames_to_column("protein_robust") %>%
filter(grepl("UPS",protein_robust)) %>%
pull(protein_robust)
heatmap(assay(pe[["protein_robust"]])[sigProteins, ], cexCol = 1,cexRow = 0.5, main = "Robust summarization")
Boxplots
We make boxplot of the log2 FC and stratify according to the whether a protein is spiked or not.
rbind(rowData(pe[["protein_robust"]])$conditionB %>%
rownames_to_column(var = "protein") %>% mutate(method = "robust"),
rowData(pe[["proteinMedian"]])$conditionB %>%
rownames_to_column(var = "protein") %>% mutate(method = "median"))%>%
mutate(ups=grepl("UPS",protein)) %>%
ggplot(aes(x=method, y =logFC, fill = ups)) +
geom_boxplot() +
theme_bw() +
geom_hline(yintercept = log2(0.74 / .25), color = "#00BFC4") +
geom_hline(yintercept = 0, color = "#F8766D")
## Warning: Removed 333 rows containing non-finite values (stat_boxplot).
Where the difference comes from
Import data from the full CPTAC study
Click to see background and code
- We use a peptides.txt file from MS-data quantified with maxquant that contains MS1 intensities summarized at the peptide level.
peptidesFile <- "https://raw.githubusercontent.com/statOmics/PDA/data/quantification/fullCptacDatasSetNotForTutorial/peptides.txt"
- 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:
ecols <- grep("Intensity\\.", names(read.delim(peptidesFile)))
- Read the data and store it in QFeatures object
pe <- readQFeatures(
table = peptidesFile,
fnames = 1,
ecol = ecols,
name = "peptideRaw", sep="\t")
Design
Click to see background and code
## CharacterList of length 1
## [["peptideRaw"]] Intensity.6A_1 Intensity.6A_2 ... Intensity.6E_9
Note, that the sample names include the spike-in condition.
They also end on a number.
- 1-3 is from lab 1,
- 4-6 from lab 2 and
- 7-9 from lab 3.
We update the colData with information on the design
colData(pe)$lab <- rep(rep(paste0("lab",1:3),each=3),5) %>% as.factor
colData(pe)$condition <- pe[["peptideRaw"]] %>% colnames %>% substr(12,12) %>% as.factor
colData(pe)$spikeConcentration <- rep(c(A = 0.25, B = 0.74, C = 2.22, D = 6.67, E = 20),each = 9)
## DataFrame with 45 rows and 3 columns
## lab condition spikeConcentration
## <factor> <factor> <numeric>
## Intensity.6A_1 lab1 A 0.25
## Intensity.6A_2 lab1 A 0.25
## Intensity.6A_3 lab1 A 0.25
## Intensity.6A_4 lab2 A 0.25
## Intensity.6A_5 lab2 A 0.25
## ... ... ... ...
## Intensity.6E_5 lab2 E 20
## Intensity.6E_6 lab2 E 20
## Intensity.6E_7 lab3 E 20
## Intensity.6E_8 lab3 E 20
## Intensity.6E_9 lab3 E 20
Preprocessing
Click to see R-code to preprocess the data
- We calculate how many non zero intensities we have for each peptide and this can be useful for filtering.
rowData(pe[["peptideRaw"]])$nNonZero <- rowSums(assay(pe[["peptideRaw"]]) > 0)
- 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
- Logtransform data with base 2
pe <- logTransform(pe, base = 2, i = "peptideRaw", name = "peptideLog")
- Handling overlapping protein groups
In our approach a peptide can map to multiple proteins, as long as there is none of these proteins present in a smaller subgroup.
pe <- filterFeatures(pe, ~ Proteins %in% smallestUniqueGroups(rowData(pe[["peptideLog"]])$Proteins))
- Remove reverse sequences (decoys) and contaminants
We now remove the contaminants, peptides that map to decoy sequences, and proteins which were only identified by peptides with modifications.
pe <- filterFeatures(pe,~Reverse != "+")
pe <- filterFeatures(pe,~ Potential.contaminant != "+")
- Drop peptides that were only identified in one sample
We keep peptides that were observed at last twice.
pe <- filterFeatures(pe,~ nNonZero >=2)
nrow(pe[["peptideLog"]])
## [1] 10478
We keep 10478 peptides upon filtering.
Normalization
Click to see R-code to normalize the data
pe <- normalize(pe,
i = "peptideLog",
name = "peptideNorm",
method = "center.median")
Peptide-Level view
Summarization
Click to see code to make plot
prot <- "P01031ups|CO5_HUMAN_UPS"
data <- pe[["peptideNorm"]][
rowData(pe[["peptideNorm"]])$Proteins == prot,
colData(pe)$lab=="lab3"] %>%
assay %>%
as.data.frame %>%
rownames_to_column(var = "peptide") %>%
gather(sample, intensity, -peptide) %>%
mutate(condition = colData(pe)[sample,"condition"]) %>%
na.exclude
sumPlot <- data %>%
ggplot(aes(x = peptide, y = intensity, color = condition, group = sample, label = condition), show.legend = FALSE) +
geom_text(show.legend = FALSE) +
theme_minimal() +
theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust = 1)) +
xlab("Peptide") +
ylab("Intensity (log2)") +
ggtitle(paste0("protein: ",prot))
Here, we will focus on the summarization of the intensities for protein P01031ups|CO5_HUMAN_UPS from Lab3 for all conditions.
sumPlot +
geom_line(linetype="dashed",alpha=.4)
Linear Model based summarization
We can use a linear peptide-level model to estimate the protein expression value while correcting for the peptide effect, i.e.
\[
y_{ip} = \beta_i^\text{sample}+\beta^{peptide}_{p} + \epsilon_{ip}
\]
Click to see code to make plot
sumMeanPepMod <- lm(intensity ~ -1 + sample + peptide,data)
sumMeanPep <- data.frame(
intensity=sumMeanPepMod$coef[grep("sample",names(sumMeanPepMod$coef))] + mean(data$intensity) - mean(sumMeanPepMod$coef[grep("sample",names(sumMeanPepMod$coef))]),
condition= names(sumMeanPepMod$coef)[grep("sample",names(sumMeanPepMod$coef))] %>% substr(18,18) %>% as.factor )
fitLmPlot <- sumPlot + geom_line(
data = data %>% mutate(fit=sumMeanPepMod$fitted.values),
mapping = aes(x=peptide, y=fit,color=condition, group=sample)) +
ggtitle("fit: ~ sample + peptide")
sumLmPlot <- sumPlot + geom_hline(
data = sumMeanPep,
mapping = aes(yintercept=intensity,color=condition)) +
ggtitle("Peptide-Level linear model Summarization")
plot_grid(plot_grid(sumMedianPlot+theme(legend.position = "none"),
sumLmPlot+theme(legend.position = "none") + ylab("")),
get_legend(sumLmPlot), ncol = 2, rel_widths = c(1,0.15))
## Warning: Removed 1 rows containing missing values (geom_hline).
By correcting for the peptide species the protein expression values are much better separated an better reflect differences in abundance induced by the spike-in condition.
Indeed, it shows that median and mean summarization that do not account for the peptide effect indeed overestimate the protein expression value in the small spike-in conditions and underestimate that in the large spike-in conditions.
Still there seem to be some issues with samples that for which the expression values are not well separated according to the spike-in condition.
A residual analysis clearly indicates potential issues:
Click to see code to make plot
resPlot <- data %>%
mutate(res=sumMeanPepMod$residuals) %>%
ggplot(aes(x = peptide, y = res, color = condition, label = condition), show.legend = FALSE) +
geom_point(shape=21) +
theme_minimal() +
theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust = 1)) +
xlab("Peptide") +
ylab("residual") +
ggtitle("residuals: ~ sample + peptide")
grid.arrange(fitLmPlot+theme(legend.position = "none"), resPlot, nrow = 1)
plot_grid(plot_grid(fitLmPlot+theme(legend.position = "none"),
sumLmPlot+theme(legend.position = "none") + ylab("")),
get_legend(sumLmPlot), ncol = 2, rel_widths = c(1,0.15))
- The residual plot shows some large outliers for peptide KIEEIAAK.
- Indeed, in the original plot the intensities for this peptide do not seem to line up very well with the concentration.
- This induces a bias in the summarization for some of the samples (e.g. for D and E)
Robust summarization using a peptide-level linear model
\[
y_{ip} = \beta_i^\text{sample}+\beta^{peptide}_{p} + \epsilon_{ip}
\]
- Ordinary least squares: estimate \(\beta\) that minimizes \[
\text{OLS}: \sum\limits_{i,p} \epsilon_{ip}^2 = \sum\limits_{i,p}(y_{ip}-\beta_i^\text{sample}-\beta_p^\text{peptide})^2
\]
We replace OLS by M-estimation with loss function \[
\sum\limits_{i,p} w_{ip}\epsilon_{ip}^2 = \sum\limits_{i,p}w_{ip}(y_{ip}-\beta_i^\text{sample}-\beta_p^\text{peptide})^2
\]
- Iteratively fit model with observation weights \(w_{ip}\) until convergence
- The weights are calculated based on standardized residuals
Click to see code to make plot
sumMeanPepRobMod <- MASS::rlm(intensity ~ -1 + sample + peptide,data)
resRobPlot <- data %>%
mutate(res = sumMeanPepRobMod$residuals,
w = sumMeanPepRobMod$w) %>%
ggplot(aes(x = peptide, y = res, color = condition, label = condition,size=w), show.legend = FALSE) +
geom_point(shape=21,size=.2) +
geom_point(shape=21) +
theme_minimal() +
theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust = 1)) +
xlab("Peptide") +
ylab("residual") +
ylim(c(-1,1)*max(abs(sumMeanPepRobMod$residuals)))
weightPlot <- qplot(
seq(-5,5,.01),
MASS::psi.huber(seq(-5,5,.01)),
geom="path") +
xlab("standardized residual") +
ylab("weight") +
theme_minimal()
grid.arrange(weightPlot,resRobPlot,nrow=1)
- We clearly see that the weights in the M-estimation procedure will down-weight errors associated with outliers for peptide KIEEIAAK.
Click to see code to make plot
sumMeanPepRob <- data.frame(
intensity=sumMeanPepRobMod$coef[grep("sample",names(sumMeanPepRobMod$coef))] + mean(data$intensity) - mean(sumMeanPepRobMod$coef[grep("sample",names(sumMeanPepRobMod$coef))]),
condition= names(sumMeanPepRobMod$coef)[grep("sample",names(sumMeanPepRobMod$coef))] %>% substr(18,18) %>% as.factor )
sumRlmPlot <- sumPlot + geom_hline(
data=sumMeanPepRob,
mapping=aes(yintercept=intensity,color=condition)) +
ggtitle("Robust")
grid.arrange(sumLmPlot + ggtitle("OLS"), sumRlmPlot, nrow = 1)
- Robust regresion results in a better separation between the protein expression values for the different samples according to their spike-in concentration.
Estimation of differential abundance using peptide level model
- Instead of summarising the data we can also directly model the data at the peptide-level.
- But, we will have to address the pseudo-replication.
\(y_{iclp}= \beta_0 + \beta_c^\text{condition} + \beta_l^\text{lab} + \beta_p^\text{peptide} + u_s^\text{sample} + \epsilon_{iclp}\)
protein-level
- \(\beta^\text{condition}_c\): spike-in condition \(c=b, \ldots, e\)
- \(\beta^\text{lab}_l\): lab effect \(l=l_2\ldots l_3\)
- \(u_{r}^\text{run}\sim N\left(0,\sigma^2_\text{run}\right) \rightarrow\) random effect addresses pseudo-replication
peptide-level
- \(\beta_{p}^\text{peptide}\): peptide effect
- \(\epsilon_{rp} \sim N\left(0,\sigma^2_{\epsilon}\right)\) within sample (run) error
DA estimates:
\(\log_2FC_{B-A}=\beta^\text{condition}_B\)
\(\log_2FC_{C-B}=\beta^\text{condition}_C - \beta^\text{condition}_B\)
Mixed peptide-level models are implemented in msqrob2
It has the advantages that
- it correctly addresses the difference levels of variability in the data
- it avoids summarization and therefore also accounts for the difference in the number of peptides that are observed in each sample
- more powerful analysis
It has the disadvantage that
- protein summaries are no longer available for plotting
- it is difficult to correctly specify the degrees of freedom for the test-statistic leading to inference that is too liberal in experiments with small sample size
- sometimes sample level random effect variance are estimated to be zero, then the pseudo-replication is not addressed leading to inference that is too liberal for these specific proteins
- they are much more difficult to disseminate to users with limited background in statistics
Hence, for this course we opted to use peptide-level models for summarization, but not for directly inferring on the differential expression at the protein-level.
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.2.1 (2022-06-23)
## Platform: x86_64-pc-linux-gnu (64-bit)
## Running under: Ubuntu 20.04.5 LTS
##
## Matrix products: default
## BLAS: /usr/lib/x86_64-linux-gnu/blas/libblas.so.3.9.0
## LAPACK: /usr/lib/x86_64-linux-gnu/lapack/liblapack.so.3.9.0
##
## locale:
## [1] LC_CTYPE=C.UTF-8 LC_NUMERIC=C LC_TIME=C.UTF-8
## [4] LC_COLLATE=C.UTF-8 LC_MONETARY=C.UTF-8 LC_MESSAGES=C.UTF-8
## [7] LC_PAPER=C.UTF-8 LC_NAME=C LC_ADDRESS=C
## [10] LC_TELEPHONE=C LC_MEASUREMENT=C.UTF-8 LC_IDENTIFICATION=C
##
## attached base packages:
## [1] stats4 stats graphics grDevices datasets utils methods
## [8] base
##
## other attached packages:
## [1] ExploreModelMatrix_1.8.0 gridExtra_2.3
## [3] cowplot_1.1.1 plotly_4.10.0
## [5] msqrob2_1.4.0 QFeatures_1.6.0
## [7] MultiAssayExperiment_1.22.0 SummarizedExperiment_1.26.1
## [9] Biobase_2.56.0 GenomicRanges_1.48.0
## [11] GenomeInfoDb_1.32.4 IRanges_2.30.1
## [13] S4Vectors_0.34.0 BiocGenerics_0.42.0
## [15] MatrixGenerics_1.8.1 matrixStats_0.62.0
## [17] limma_3.52.3 forcats_0.5.2
## [19] stringr_1.4.1 dplyr_1.0.10
## [21] purrr_0.3.4 readr_2.1.2
## [23] tidyr_1.2.1 tibble_3.1.8
## [25] ggplot2_3.3.6 tidyverse_1.3.2
##
## loaded via a namespace (and not attached):
## [1] googledrive_2.0.0 minqa_1.2.4 colorspace_2.0-3
## [4] ellipsis_0.3.2 XVector_0.36.0 fs_1.5.2
## [7] clue_0.3-61 farver_2.1.1 DT_0.25
## [10] fansi_1.0.3 lubridate_1.8.0 xml2_1.3.3
## [13] codetools_0.2-18 splines_4.2.1 cachem_1.0.6
## [16] knitr_1.40 jsonlite_1.8.0 nloptr_2.0.3
## [19] broom_1.0.1 cluster_2.1.4 dbplyr_2.2.1
## [22] shinydashboard_0.7.2 shiny_1.7.2 BiocManager_1.30.18
## [25] compiler_4.2.1 httr_1.4.4 backports_1.4.1
## [28] assertthat_0.2.1 Matrix_1.5-1 fastmap_1.1.0
## [31] lazyeval_0.2.2 gargle_1.2.1 cli_3.4.0
## [34] later_1.3.0 htmltools_0.5.3 tools_4.2.1
## [37] igraph_1.3.4 gtable_0.3.1 glue_1.6.2
## [40] GenomeInfoDbData_1.2.8 Rcpp_1.0.9 cellranger_1.1.0
## [43] jquerylib_0.1.4 vctrs_0.4.1 nlme_3.1-159
## [46] rintrojs_0.3.2 xfun_0.33 lme4_1.1-30
## [49] rvest_1.0.3 mime_0.12 lifecycle_1.0.2
## [52] renv_0.15.5 googlesheets4_1.0.1 zlibbioc_1.42.0
## [55] MASS_7.3-58.1 scales_1.2.1 promises_1.2.0.1
## [58] hms_1.1.2 ProtGenerics_1.28.0 parallel_4.2.1
## [61] AnnotationFilter_1.20.0 yaml_2.3.5 sass_0.4.2
## [64] stringi_1.7.8 highr_0.9 boot_1.3-28
## [67] BiocParallel_1.30.3 rlang_1.0.5 pkgconfig_2.0.3
## [70] bitops_1.0-7 evaluate_0.16 lattice_0.20-45
## [73] labeling_0.4.2 htmlwidgets_1.5.4 tidyselect_1.1.2
## [76] magrittr_2.0.3 R6_2.5.1 generics_0.1.3
## [79] DelayedArray_0.22.0 DBI_1.1.3 pillar_1.8.1
## [82] haven_2.5.1 withr_2.5.0 MsCoreUtils_1.8.0
## [85] RCurl_1.98-1.8 modelr_0.1.9 crayon_1.5.1
## [88] utf8_1.2.2 tzdb_0.3.0 rmarkdown_2.16
## [91] grid_4.2.1 readxl_1.4.1 data.table_1.14.2
## [94] reprex_2.0.2 digest_0.6.29 xtable_1.8-4
## [97] httpuv_1.6.6 munsell_0.5.0 viridisLite_0.4.1
## [100] bslib_0.4.0 shinyjs_2.1.0
LS0tCnRpdGxlOiAiQW5hbHlzaXMgb2YgdGhlIENQVEFDIFNwaWtlLWluIFN0dWR5IgphdXRob3I6ICJMaWV2ZW4gQ2xlbWVudCBhbmQgTGF1cmVudCBHYXR0cCIKZGF0ZTogInN0YXRPbWljcywgR2hlbnQgVW5pdmVyc2l0eSAoaHR0cHM6Ly9zdGF0b21pY3MuZ2l0aHViLmlvKSIKb3V0cHV0OgogICAgaHRtbF9kb2N1bWVudDoKICAgICAgY29kZV9kb3dubG9hZDogdHJ1ZQogICAgICB0aGVtZTogY29zbW8KICAgICAgdG9jOiB0cnVlCiAgICAgIHRvY19mbG9hdDogdHJ1ZQogICAgICBoaWdobGlnaHQ6IHRhbmdvCiAgICAgIG51bWJlcl9zZWN0aW9uczogdHJ1ZQogICAgcGRmX2RvY3VtZW50OgogICAgICB0b2M6IHRydWUKICAgICAgbnVtYmVyX3NlY3Rpb25zOiB0cnVlCmxpbmtjb2xvcjogYmx1ZQp1cmxjb2xvcjogYmx1ZQpjaXRlY29sb3I6IGJsdWUKLS0tCgo8YSByZWw9ImxpY2Vuc2UiIGhyZWY9Imh0dHBzOi8vY3JlYXRpdmVjb21tb25zLm9yZy9saWNlbnNlcy9ieS1zYS80LjAvIj48aW1nIGFsdD0iQ3JlYXRpdmUgQ29tbW9ucyBMaWNlbnNlIiBzdHlsZT0iYm9yZGVyLXdpZHRoOjAiIHNyYz0iaHR0cHM6Ly9pLmNyZWF0aXZlY29tbW9ucy5vcmcvbC9ieS1zYS80LjAvODh4MzEucG5nIiAvPjwvYT4KClRoaXMgaXMgcGFydCBvZiB0aGUgb25saW5lIGNvdXJzZSBbUHJvdGVvbWljcyBEYXRhIEFuYWx5c2lzIChQREEpXShodHRwczovL3N0YXRvbWljcy5naXRodWIuaW8vUERBLykKCgojIEludHJvOiBDaGFsbGVuZ2VzIGluIExhYmVsLUZyZWUgUXVhbnRpdGF0aXZlIFByb3Rlb21pY3MKCiMjIE1TLWJhc2VkIHdvcmtmbG93CgpgYGB7ciBlY2hvPUZBTFNFfQprbml0cjo6aW5jbHVkZV9ncmFwaGljcygiLi9maWd1cmVzL1Byb3Rlb21pY3NXb3JrZmxvdy5wbmciKQpgYGAKCi0gUGVwdGlkZSBDaGFyYWN0ZXJpc3RpY3MKCiAgLSBNb2RpZmljYXRpb25zCiAgLSBJb25pc2F0aW9uIEVmZmljaWVuY3k6IGh1Z2UgdmFyaWFiaWxpdHkKICAtIElkZW50aWZpY2F0aW9uCiAgICAtIE1pc2lkZW50aWZpY2F0aW9uICRccmlnaHRhcnJvdyQgb3V0bGllcnMKICAgIC0gTVMkXjIkIHNlbGVjdGlvbiBvbiBwZXB0aWRlIGFidW5kYW5jZQogICAgLSBDb250ZXh0IGRlcGVuZGluZyBtaXNzaW5nbmVzcwogICAgLSBOb24tcmFuZG9tIG1pc3NpbmduZXNzCgokXHJpZ2h0YXJyb3ckIFVuYmFsYW5jZWQgcGVwaWRlIGlkZW50aWZpY2F0aW9ucyBhY3Jvc3Mgc2FtcGxlcyBhbmQgbWVzc3kgZGF0YQoKLS0tCgojIyBMZXZlbCBvZiBxdWFudGlmaWNhdGlvbgoKLSBNUy1iYXNlZCBwcm90ZW9taWNzIHJldHVybnMgcGVwdGlkZXM6IHBpZWNlcyBvZiBwcm90ZWlucwoKYGBge3IgZWNobz1GQUxTRX0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoIi4vZmlndXJlcy9jaGFsbGVuZ2VzX3BlcHRpZGVzLnBuZyIpCmBgYAoKLSBRdWFudGlmaWNhdGlvbiBjb21tb25seSByZXF1aXJlZCBvbiB0aGUgcHJvdGVpbiBsZXZlbAoKYGBge3IgZWNobz1GQUxTRX0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoIi4vZmlndXJlcy9jaGFsbGVuZ2VzX3Byb3RlaW5zLnBuZyIpCmBgYAoKLS0tCgojIyBMYWJlbC1mcmVlIFF1YW50aXRhdGl2ZSBQcm90ZW9taWNzIERhdGEgQW5hbHlzaXMgV29ya2Zsb3dzCgpgYGB7ciBlY2hvPUZBTFNFfQprbml0cjo6aW5jbHVkZV9ncmFwaGljcygiLi9maWd1cmVzL3Byb3Rlb21pY3NEYXRhQW5hbHlzaXMucG5nIikKYGBgCgotLS0KCgojIFFGZWF0dXJlcwoKIyMgRGF0YSBpbmZyYXN0cnVjdHVyZQoKLSBXZSB1c2UgdGhlIGBRRmVhdHVyZXNgIHBhY2thZ2UgdGhhdCBwcm92aWRlcyB0aGUgaW5mcmFzdHJ1Y3R1cmUgdG8KICAtIHN0b3JlLAogIC0gcHJvY2VzcywKICAtIG1hbmlwdWxhdGUgYW5kCiAgLSBhbmFseXNlIHF1YW50aXRhdGl2ZSBkYXRhL2ZlYXR1cmVzIGZyb20gbWFzcyBzcGVjdHJvbWV0cnkKZXhwZXJpbWVudHMuCgotIEl0IGlzIGJhc2VkIG9uIHRoZSBgU3VtbWFyaXplZEV4cGVyaW1lbnRgIGFuZApgTXVsdGlBc3NheUV4cGVyaW1lbnRgIGNsYXNzZXMuCgpgYGB7ciBmaWcuY2FwID0gIkNvbmNlcHR1YWwgcmVwcmVzZW50YXRpb24gb2YgYSBgU3VtbWFyaXplZEV4cGVyaW1lbnRgIG9iamVjdC4gIEFzc2F5cyBjb250YWluIGluZm9ybWF0aW9uIG9uIHRoZSBtZWFzdXJlZCBvbWljcyBmZWF0dXJlcyAocm93cykgZm9yIGRpZmZlcmVudCBzYW1wbGVzIChjb2x1bW5zKS4gVGhlIGByb3dEYXRhYCBjb250YWlucyBpbmZvcm1hdGlvbiBvbiB0aGUgb21pY3MgZmVhdHVyZXMsIHRoZSBgY29sRGF0YWAgY29udGFpbnMgaW5mb3JtYXRpb24gb24gdGhlIHNhbXBsZXMsIGkuZS4gZXhwZXJpbWVudGFsIGRlc2lnbiBldGMuIiwgZWNobz1GQUxTRSwgb3V0LndpZHRoPSI4MCUifQprbml0cjo6aW5jbHVkZV9ncmFwaGljcygiLi9maWd1cmVzL1NFLnBuZyIpCmBgYAoKLSBBc3NheXMgaW4gYSBRRmVhdHVyZXMgb2JqZWN0IGhhdmUgYQpoaWVyYXJjaGljYWwgcmVsYXRpb246CgogIC0gcHJvdGVpbnMgYXJlIGNvbXBvc2VkIG9mIHBlcHRpZGVzLAogIC0gdGhlbXNlbHZlcyBwcm9kdWNlZCBieSBwZXB0aWRlIHNwZWN0cnVtIG1hdGNoZXMKICAtIHJlbGF0aW9ucyBiZXR3ZWVuIGFzc2F5cyBhcmUgdHJhY2tlZCBhbmQgcmVjb3JkZWQgdGhyb3VnaG91dCBkYXRhIHByb2Nlc3NpbmcKCmBgYHtyIGZlYXR1cmVzcGxvdCwgZmlnLmNhcCA9ICJDb25jZXB0dWFsIHJlcHJlc2VudGF0aW9uIG9mIGEgYFFGZWF0dXJlc2Agb2JqZWN0IGFuZCB0aGUgYWdncmVnYXRpdmUgcmVsYXRpb24gYmV0d2VlbiBkaWZmZXJlbnQgYXNzYXlzLiBJbWFnZSBmcm9tIHRoZSBbUUZlYXR1cmVzIHZpZ25ldHRlXShodHRwczovL3Jmb3JtYXNzc3BlY3Ryb21ldHJ5LmdpdGh1Yi5pby9RRmVhdHVyZXMvYXJ0aWNsZXMvUUZlYXR1cmVzLmh0bWwpIiwgZWNobyA9IEZBTFNFICB9CnBhcihtYXIgPSBjKDAsIDAsIDAsIDApKQpwbG90KE5BLCB4bGltID0gYygwLCAxMiksIHlsaW0gPSBjKDAsIDIwKSwKICAgICB4YXh0ID0gIm4iLCB5YXh0ID0gIm4iLAogICAgIHhsYWIgPSAiIiwgeWxhYiA9ICIiLCBidHkgPSAibiIpCmZvciAoaSBpbiAwOjcpCiAgICByZWN0KDAsIGksIDMsIGkrMSwgY29sID0gImxpZ2h0Z3JleSIsIGJvcmRlciA9ICJ3aGl0ZSIpCmZvciAoaSBpbiA4OjEyKQogICAgcmVjdCgwLCBpLCAzLCBpKzEsIGNvbCA9ICJzdGVlbGJsdWUiLCBib3JkZXIgPSAid2hpdGUiKQpmb3IgKGkgaW4gMTM6MTgpCiAgICByZWN0KDAsIGksIDMsIGkrMSwgY29sID0gIm9yYW5nZSIsIGJvcmRlciA9ICJ3aGl0ZSIpCmZvciAoaSBpbiAxOSkKICAgIHJlY3QoMCwgaSwgMywgaSsxLCBjb2wgPSAiZGFya2dyZXkiLCBib3JkZXIgPSAid2hpdGUiKQpmb3IgKGkgaW4gNTo3KQogICAgcmVjdCg1LCBpLCA4LCBpKzEsIGNvbCA9ICJsaWdodGdyZXkiLCBib3JkZXIgPSAid2hpdGUiKQpmb3IgKGkgaW4gODoxMCkKICAgIHJlY3QoNSwgaSwgOCwgaSsxLCBjb2wgPSAic3RlZWxibHVlIiwgYm9yZGVyID0gIndoaXRlIikKZm9yIChpIGluIDExOjEzKQogICAgcmVjdCg1LCBpLCA4LCBpKzEsIGNvbCA9ICJvcmFuZ2UiLCBib3JkZXIgPSAid2hpdGUiKQpmb3IgKGkgaW4gMTQpCiAgICByZWN0KDUsIGksIDgsIGkrMSwgY29sID0gImRhcmtncmV5IiwgYm9yZGVyID0gIndoaXRlIikKcmVjdCg5LCA4LCAxMiwgOCsxLCBjb2wgPSAibGlnaHRncmV5IiwgYm9yZGVyID0gIndoaXRlIikKcmVjdCg5LCA5LCAxMiwgOSsxLCBjb2wgPSAic3RlZWxibHVlIiwgYm9yZGVyID0gIndoaXRlIikKcmVjdCg5LCAxMCwgMTIsIDEwKzEsIGNvbCA9ICJvcmFuZ2UiLCBib3JkZXIgPSAid2hpdGUiKQpyZWN0KDksIDExLCAxMiwgMTErMSwgY29sID0gImRhcmtncmV5IiwgYm9yZGVyID0gIndoaXRlIikKc2VnbWVudHMoMywgOCwgNSwgOCwgbHR5ID0gImRhc2hlZCIpCnNlZ21lbnRzKDMsIDYsIDUsIDcsIGx0eSA9ICJkYXNoZWQiKQpzZWdtZW50cygzLCA0LCA1LCA2LCBsdHkgPSAiZGFzaGVkIikKc2VnbWVudHMoMywgMCwgNSwgNSwgbHR5ID0gImRhc2hlZCIpCnNlZ21lbnRzKDMsIDEwLCA1LCA5LCBsdHkgPSAiZGFzaGVkIikKc2VnbWVudHMoMywgMTEsIDUsIDEwLCBsdHkgPSAiZGFzaGVkIikKc2VnbWVudHMoMywgMTMsIDUsIDExLCBsdHkgPSAiZGFzaGVkIikKc2VnbWVudHMoMywgMTQsIDUsIDEyLCBsdHkgPSAiZGFzaGVkIikKc2VnbWVudHMoMywgMTYsIDUsIDEzLCBsdHkgPSAiZGFzaGVkIikKc2VnbWVudHMoMywgMTksIDUsIDE0LCBsdHkgPSAiZGFzaGVkIikKc2VnbWVudHMoMywgMjAsIDUsIDE1LCBsdHkgPSAiZGFzaGVkIikKc2VnbWVudHMoOCwgNSwgOSwgOCwgbHR5ID0gImRhc2hlZCIpCnNlZ21lbnRzKDgsIDgsIDksIDksIGx0eSA9ICJkYXNoZWQiKQpzZWdtZW50cyg4LCAxMSwgOSwgMTAsIGx0eSA9ICJkYXNoZWQiKQpzZWdtZW50cyg4LCAxNCwgOSwgMTEsIGx0eSA9ICJkYXNoZWQiKQpzZWdtZW50cyg4LCAxNSwgOSwgMTIsIGx0eSA9ICJkYXNoZWQiKQpgYGAKCgojIEJhY2tncm91bmQgb2YgdGhlIENQVEFDIFNwaWtlLUluIFN0dWR5CgpUaGlzIGNhc2Utc3R1ZHkgaXMgYSBzdWJzZXQgb2YgdGhlIGRhdGEgb2YgdGhlIDZ0aCBzdHVkeSBvZiB0aGUKQ2xpbmljYWwgUHJvdGVvbWljIFRlY2hub2xvZ3kgQXNzZXNzbWVudCBmb3IgQ2FuY2VyIChDUFRBQykgWzVdLiAgSW4KdGhpcyBleHBlcmltZW50LCB0aGUgYXV0aG9ycyBzcGlrZWQgdGhlIFNpZ21hIFVuaXZlcnNhbCBQcm90ZWluClN0YW5kYXJkIG1peHR1cmUgMSAoVVBTMSkgY29udGFpbmluZyA0OCBkaWZmZXJlbnQgaHVtYW4gcHJvdGVpbnMgaW4gYQpwcm90ZWluIGJhY2tncm91bmQgb2YgNjAgbmcvJFxtdSRMIFNhY2NoYXJvbXljZXMgY2VyZXZpc2lhZSBzdHJhaW4KQlk0NzQxLgoKRml2ZSBkaWZmZXJlbnQgc3Bpa2UtaW4gY29uY2VudHJhdGlvbnMgd2VyZSB1c2VkOgotIDZBOiAwLjI1IGZtb2wgVVBTMSBwcm90ZWlucy8kXG11IEwkLAotIDZCOiAwLjc0IGZtb2wgVVBTMSBwcm90ZWlucy8kXG11IEwkLAotIDZDOiAyLjIyIGZtb2wgVVBTMSBwcm90ZWlucy8kXG11IEwkLAotIDZEOiA2LjY3IGZtb2wgVVBTMSBwcm90ZWlucy8kXG11IEwkIGFuZAotIDZFOiAyMCBmbW9sIFVQUzEgcHJvdGVpbnMvJFxtdSBMJCkuCgoKV2UgbGltaXRlZCBvdXJzZWx2ZXMgdG8gdGhlIGRhdGEgb2YgTFRRLU9yYml0cmFwIFcgYXQgc2l0ZSA1Ni4gIFRoZQpkYXRhIHdlcmUgc2VhcmNoZWQgd2l0aCBNYXhRdWFudCB2ZXJzaW9uIDEuNS4yLjgsIGFuZCBkZXRhaWxlZCBzZWFyY2gKc2V0dGluZ3Mgd2VyZSBkZXNjcmliZWQgaW4gR29lbWlubmUgZXQgYWwuICgyMDE2KSBbMV0uICBUaHJlZQpyZXBsaWNhdGVzIGFyZSBhdmFpbGFibGUgZm9yIGVhY2ggY29uY2VudHJhdGlvbi4KCgpgYGB7ciBlY2hvPUZBTFNFLCBvdXQud2lkdGg9IjUwJSJ9CmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKCIuL2ZpZ3VyZXMvY3B0YWNMYXlvdXRMdWRnZXIucG5nIikKYGBgCgotIEFmdGVyIE1heFF1YW50IHNlYXJjaCB3aXRoIG1hdGNoIGJldHdlZW4gcnVucyBvcHRpb24KICAtIDQxXCUgb2YgYWxsIHByb3RlaW5zIGFyZSBxdWFudGlmaWVkIGluIGFsbCBzYW1wbGVzCiAgLSA2LjZcJSBvZiBhbGwgcGVwdGlkZXMgYXJlIHF1YW50aWZpZWQgaW4gYWxsIHNhbXBsZXMKCiRccmlnaHRhcnJvdyQgdmFzdCBhbW91bnQgb2YgbWlzc2luZ25lc3MKCiMjIEltcG9ydCBkYXRhIGluIFIKCgojIyMgTG9hZCBwYWNrYWdlcwoKYGBge3IsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9CmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KGxpbW1hKQpsaWJyYXJ5KFFGZWF0dXJlcykKbGlicmFyeShtc3Fyb2IyKQpsaWJyYXJ5KHBsb3RseSkKbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KGNvd3Bsb3QpCmxpYnJhcnkoZ3JpZEV4dHJhKQpgYGAKPC9wPjwvZGV0YWlscz4KCiMjIyBJbXBvcnQgZGF0YSBmcm9tIHRoZSBDUFRBQyBzdHVkeQoKMS4gV2UgdXNlIGEgYHBlcHRpZGVzLnR4dGAgZmlsZSBmcm9tIE1TLWRhdGEgcXVhbnRpZmllZCB3aXRoIG1heHF1YW50CiAgIHRoYXQgY29udGFpbnMgTVMxIGludGVuc2l0aWVzIHN1bW1hcml6ZWQgYXQgdGhlIHBlcHRpZGUgbGV2ZWwuCgpgYGB7cn0KcGVwdGlkZXNGaWxlIDwtICJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vc3RhdE9taWNzL1BEQS9kYXRhL3F1YW50aWZpY2F0aW9uL2Z1bGxDcHRhY0RhdGFzU2V0Tm90Rm9yVHV0b3JpYWwvcGVwdGlkZXMudHh0Igpkb3dubG9hZC5maWxlKHVybCA9IHBlcHRpZGVzRmlsZSwgZGVzdGZpbGUgPSAicGVwdGlkZXMudHh0IikKYGBgCgoyLiBNYXhxdWFudCBzdG9yZXMgdGhlIGludGVuc2l0eSBkYXRhIGZvciB0aGUgZGlmZmVyZW50IHNhbXBsZXMgaW4KICAgY29sdW1ubnMgdGhhdCBzdGFydCB3aXRoICJJbnRlbnNpdHkiLiBXZSBjYW4gcmV0cmVpdmUgdGhlIGNvbHVtbgogICBuYW1lcyB3aXRoIHRoZSBpbnRlbnNpdHkgZGF0YSB3aXRoIHRoZSBjb2RlIGJlbG93OgoKYGBge3J9CmdyZXAoIkludGVuc2l0eVxcLiIsIG5hbWVzKHJlYWQuZGVsaW0oInBlcHRpZGVzLnR4dCIpKSwgdmFsdWUgPSBUUlVFKQooZWNvbHMgPC0gZ3JlcCgiSW50ZW5zaXR5XFwuIiwgbmFtZXMocmVhZC5kZWxpbSgicGVwdGlkZXMudHh0IikpKSkKYGBgCgozLiBSZWFkIHRoZSBkYXRhIGFuZCBzdG9yZSBpdCBpbiAgUUZlYXR1cmVzIG9iamVjdAoKYGBge3J9CnBlX2FsbCA8LSByZWFkUUZlYXR1cmVzKAogICAicGVwdGlkZXMudHh0IiwKICAgZm5hbWVzID0gMSwKICAgZWNvbCA9IGVjb2xzLAogICBuYW1lID0gInBlcHRpZGVSYXciLAogICBzZXAgPSAiXHQiKQpgYGAKClRoZSBRRmVhdHVyZXMgb2JqZWN0IHN0b3JlZCBpbiBgcGVgIGN1cnJlbnRseSBjb250YWlucyBhIHNpbmdsZSBhc3NheSwKbmFtZXMgYHBlcHRpZGVSYXdgLCBjb21wb3NlZCBvZiAxMTQ2NiBwZXB0aWRlcyBtZWFzdXJlZCBpbiA0NSBzYW1wbGVzLgoKCmBgYHtyfQpwZV9hbGwKYGBgCgpXZSBjYW4gYWNjZXNzIHRoZSB1bmlxdWUgYXNzYXkgYnkgaW5kZXggKGkuZS4gMSkgb3IgYnkgbmFtZSAoaS5lCiJwZXB0aWRlUmF3IikgdXNpbmcgdGhlIGBbW11dYCBvcGVyYXRvciwgd2hpY2ggcmV0dXJucyBhbiBpbnN0YW5jZSBvZgpjbGFzcyBgU3VtbWFyaXplZEV4cGVyaW1lbnRgOgoKCmBgYHtyfQpwZV9hbGxbWzFdXQpwZV9hbGxbWyJwZXB0aWRlUmF3Il1dCmBgYAoKCiMjIyBFeHBsb3JlIG9iamVjdAoKLSBUaGUgYHJvd0RhdGFgIGNvbnRhaW5zIGluZm9ybWF0aW9uIG9uIHRoZSBmZWF0dXJlcyAocGVwdGlkZXMpIGluIHRoZQogIGFzc2F5LiBFLmcuIFNlcXVlbmNlLCBwcm90ZWluLCAuLi4KCmBgYHtyfQpyb3dEYXRhKHBlX2FsbFtbInBlcHRpZGVSYXciXV0pWywgYygiUHJvdGVpbnMiLCJTZXF1ZW5jZSIsIkNoYXJnZXMiLCJJbnRlbnNpdHkiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiRXhwZXJpbWVudC42QV83IiwiRXhwZXJpbWVudC42QV84IiwiRXhwZXJpbWVudC42QV85IiApXQpgYGAKCi0gVGhlIGBjb2xEYXRhYCBjb250YWlucyBpbmZvcm1hdGlvbiBvbiB0aGUgc2FtcGxlcywgYnV0IGlzIGN1cnJlbnRseQogIGVtcHR5OgoKYGBge3J9CmNvbERhdGEocGVfYWxsKQpgYGAKCgpgYGB7cn0KcGVfYWxsW1sxXV0gJT4lIGNvbG5hbWVzCmBgYAoKLSBOb3RlLCB0aGF0IHRoZSBzYW1wbGUgbmFtZXMgaW5jbHVkZSB0aGUgc3Bpa2UtaW4gY29uZGl0aW9uIChBIGFuZAogIEIpLiBUaGV5IGFsc28gZW5kIG9uIGEgbnVtYmVyOgoKICAtIDEtMyBpcyBmcm9tIGxhYiAxLAogIC0gNC02IGZyb20gbGFiIDIgYW5kCiAgLSA3LTkgZnJvbSBsYWIgMy4KCi0gV2UgY2FuIHVwZGF0ZSB0aGUgYGNvbERhdGFgIHdpdGggaW5mb3JtYXRpb24gb24gdGhlIGRlc2lnbgoKYGBge3J9CnBlX2FsbCRsYWIgPC0gcmVwKHJlcChwYXN0ZTAoImxhYiIsIDE6MyksZWFjaCA9IDMpLCA1KQpwZV9hbGwkY29uZGl0aW9uIDwtIHBlX2FsbFtbInBlcHRpZGVSYXciXV0gJT4lCiAgIGNvbG5hbWVzICU+JQogICBzdWJzdHIoMTIsIDEyKQpwZV9hbGwkc3Bpa2VDb25jZW50cmF0aW9uIDwtIHJlcChjKEEgPSAwLjI1LCBCID0gMC43NCwgQyA9IDIuMjIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgRCA9IDYuNjcsIEUgPSAyMCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGVhY2ggPSA5KQpgYGAKCi0gV2UgZXhwbG9yZSB0aGUgYGNvbERhdGFgIGFnYWluCgpgYGB7cn0KY29sRGF0YShwZV9hbGwpCmBgYAoKIyBTdWJzZXQgb2YgdGhlIENQVEFDIHN0dWR5CgpXZSBhcmUgZ29pbmcgdG8gZm9jdXMgb24gYSBzdWJzZXQgb2YgdGhlIGRhdGEsIHNwZWNpZmljYWxseSBvbgpjb25kaXRpb25zIEEgYW5kIEIgcHJvZHVjZWQgYnkgbGFiMzoKCmBgYHtyfQpzdWJzZXRDUFRBQyA8LSBwZV9hbGwkY29uZGl0aW9uICVpbiUgYygiQSIsICJCIikgJiBwZV9hbGwkbGFiID09ICJsYWIzIgpwZSA8LSBwZV9hbGxbLCBzdWJzZXRDUFRBQ10KcGUkY29uZGl0aW9uIDwtIGZhY3RvcihwZSRjb25kaXRpb24pCnBlCmBgYApOb3RlIHRoYXQgYSBmaWxlIGNvbnRhaW5pbmcgdGhpcyBzYW1lIHN1YnNldCBpcyBhbHNvIGF2YWlsYWJsZSBmcm9tIHRoZQpgbXNkYXRhYCBwYWNrYWdlIHVzaW5nIHRoZSBgcXVhbnQoKWAgZnVuY3Rpb24uCgojIyBNaXNzaW5nbmVzcwoKUGVwdGlkZXMgd2l0aCB6ZXJvIGludGVuc2l0aWVzIGFyZSBtaXNzaW5nIHBlcHRpZGVzIGFuZCBzaG91bGQgYmUKcmVwcmVzZW50IHdpdGggYSBgTkFgIHZhbHVlIHJhdGhlciB0aGFuIGAwYC4gVGhpcyBjYW4gYmUgZG9uZSB3aXRoIHRoZQpgemVyb0lzTkEoKWAgZnVuY3Rpb24uIFdlIGNhbiB0aGVuIHVzZSBgbk5BKClgIG9uIHRoZSBpbmRpdmlkdWFsIGFzc2F5CnRvIGNvbXB1dGUgbWlzc2luZ25lc3Mgc3VtbWFyaWVzOgoKYGBge3J9CnBlIDwtIHplcm9Jc05BKHBlLCAicGVwdGlkZVJhdyIpCm5hIDwtIG5OQShwZVtbMV1dKQpuYQpgYGAKCi0gMzExMzAgcGVwdGlkZXMgaW50ZW5zaXRpZXMsIGNvcnJlc3BvbmRpbmcgdG8gNDUlLCBhcmUgbWlzc2luZyBhbmQKICBmb3Igc29tZSBwZXB0aWRlcyB3ZSBkbyBub3QgZXZlbiBtZWFzdXJlIGEgc2lnbmFsIGluIGFueSBzYW1wbGUuCi0gRm9yIGVhY2ggc2FtcGxlLCB0aGUgcHJvcG9ydGlvbiBmbHVjdHVhdGVzIGJldHdlZW4gNDEuNCBhbmQgNDguNSUuCi0gVGhlIHRhYmxlIGJlbG93IHNob3dzIHRoZSBudW1iZXIgb2YgcGVwdGlkZXMgdGhhdCBoYXZlIDAsIDEsIC4uLiBhbmQKICB1cCB0byA2IG1pc3NpbmcgdmFsdWVzLgoKYGBge3J9CnRhYmxlKG5hJG5OQXJvd3Mkbk5BKQpgYGAKCiMgUHJlcHJvY2Vzc2luZwoKVGhpcyBzZWN0aW9uIHByZWZvcm1zIHByZXByb2Nlc3NpbmcgZm9yIHRoZSBwZXB0aWRlIGRhdGEuICBUaGlzCmluY2x1ZGUKCi0gbG9nIHRyYW5zZm9ybWF0aW9uLAotIGZpbHRlcmluZyBhbmQKLSBzdW1tYXJpc2F0aW9uIG9mIHRoZSBkYXRhLgoKIyMgTG9nIHRyYW5zZm9ybSB0aGUgZGF0YQoKYGBge3IgbG9nVHJhbnNnfQpwZSA8LSBsb2dUcmFuc2Zvcm0ocGUsIGJhc2UgPSAyLCBpID0gInBlcHRpZGVSYXciLCBuYW1lID0gInBlcHRpZGVMb2ciKQpgYGAKCiMjIEZpbHRlcmluZwoKMS4gSGFuZGxpbmcgb3ZlcmxhcHBpbmcgcHJvdGVpbiBncm91cHMKCkluIG91ciBhcHByb2FjaCBhIHBlcHRpZGUgY2FuIG1hcCB0byBtdWx0aXBsZSBwcm90ZWlucywgYXMgbG9uZyBhcyB0aGVyZSBpcwpub25lIG9mIHRoZXNlIHByb3RlaW5zIHByZXNlbnQgaW4gYSBzbWFsbGVyIHN1Ymdyb3VwLgoKYGBge3IgZmlsdGVyU21hbGxlc3RVbmlxdWV9CnBlIDwtIGZpbHRlckZlYXR1cmVzKHBlLCB+IFByb3RlaW5zICVpbiUgc21hbGxlc3RVbmlxdWVHcm91cHMocm93RGF0YShwZVtbInBlcHRpZGVMb2ciXV0pJFByb3RlaW5zKSkKYGBgCgoyLiBSZW1vdmUgcmV2ZXJzZSBzZXF1ZW5jZXMgKGRlY295cykgYW5kIGNvbnRhbWluYW50cwoKV2Ugbm93IHJlbW92ZSB0aGUgY29udGFtaW5hbnRzIGFuZCBwZXB0aWRlcyB0aGF0IG1hcCB0byBkZWNveSBzZXF1ZW5jZXMuCgpgYGB7ciBmaWx0ZXJSZXZDb250fQpwZSA8LSBwZSB8PgogIGZpbHRlckZlYXR1cmVzKH4gUmV2ZXJzZSAhPSAiKyIpIHw+CiAgZmlsdGVyRmVhdHVyZXMofiBQb3RlbnRpYWwuY29udGFtaW5hbnQgIT0gIisiKQpwZQpgYGAKCjMuIERyb3AgcGVwdGlkZXMgdGhhdCB3ZXJlIG9ubHkgaWRlbnRpZmllZCBpbiBvbmUgc2FtcGxlCgpXZSBrZWVwIHBlcHRpZGVzIHRoYXQgd2VyZSBvYnNlcnZlZCBhdCBsYXN0IHR3aWNlLCBpLmUuIHRob3NlIHRoYXQKaGF2ZSBubyBtb3JlIHRoYXQgNCBtaXNzaW5nIHZhbHVlcwoKYGBge3IgZmlsdGVyTkF9CnBlIDwtIGZpbHRlckZlYXR1cmVzKHBlLCB+IG5OQShwZVtbMV1dKSRuTkFyb3dzJG5OQSA8PSA0KQpwZQpgYGAKCldlIGtlZXAgYHIgbnJvdyhwZVtbInBlcHRpZGVMb2ciXV0pYCBwZXB0aWRlcyB1cG9uIGZpbHRlcmluZy4KCgojIyBOb3JtYWxpemUgdGhlIGRhdGEgdXNpbmcgbWVkaWFuIGNlbnRlcmluZwoKV2Ugbm9ybWFsaXplIHRoZSBkYXRhIGJ5IHN1YnN0cmFjdGluZyB0aGUgc2FtcGxlIG1lZGlhbiBmcm9tIGV2ZXJ5CmludGVuc2l0eSBmb3IgcGVwdGlkZSAkcCQgaW4gYSBzYW1wbGUgJGkkOgoKJCR5X3tpcH1eXHRleHR7bm9ybX0gPSB5X3tpcH0gLSBcaGF0XG11X2kkJAoKd2l0aCAkXGhhdFxtdV9pJCB0aGUgbWVkaWFuIGludGVuc2l0eSBvdmVyIGFsbCBvYnNlcnZlZCBwZXB0aWRlcyBpbgpzYW1wbGUgJGkkLgoKYGBge3Igbm9ybWFsaXNlfQpwZSA8LSBub3JtYWxpemUocGUsCiAgICAgICAgICAgICAgICBpID0gInBlcHRpZGVMb2ciLAogICAgICAgICAgICAgICAgbmFtZSA9ICJwZXB0aWRlTm9ybSIsCiAgICAgICAgICAgICAgICBtZXRob2QgPSAiY2VudGVyLm1lZGlhbiIpCnBlCmBgYAoKCiMjIEV4cGxvcmUgbm9ybWFsaXplZCBkYXRhCgpVcG9uIHRoZSBub3JtYWxpc2F0aW9uIHRoZSBkZW5zaXR5IGN1cnZlcyBmb2xsb3cgYSBzaW1pbGFyIGRpc3RyaWJ1dGlvbi4KCmBgYHtyIGRlbnNpdHlwbG90fQphc190aWJibGUobG9uZ0Zvcm1hdChwZVssICwgMjozXSwgY29sdmFycyA9ICJjb25kaXRpb24iKSkgJT4lCiAgICBnZ3Bsb3QoYWVzKHggPSB2YWx1ZSwgZ3JvdXAgPSBwcmltYXJ5LCBjb2xvdXIgPSBjb25kaXRpb24pKSArCiAgICBnZW9tX2RlbnNpdHkoKSArCiAgICBmYWNldF9ncmlkKGFzc2F5IH4gLikgKwogICAgdGhlbWVfYncoKQpgYGAKCldlIGNhbiB2aXN1YWxpemUgb3VyIGRhdGEgdXNpbmcgYSBNdWx0aSBEaW1lbnNpb25hbCBTY2FsaW5nIHBsb3QsCmVnLiBhcyBwcm92aWRlZCBieSB0aGUgYGxpbW1hYCBwYWNrYWdlLgoKYGBge3IgbWRzcGxvdH0KdG1wIDwtIGFzc2F5KHBlW1sicGVwdGlkZU5vcm0iXV0pCmNvbG5hbWVzKHRtcCkgPC0gc3RyX3JlcGxhY2VfYWxsKGNvbG5hbWVzKHRtcCksICJJbnRlbnNpdHkuNiIsIiIpCnRtcCAlPiUKICBsaW1tYTo6cGxvdE1EUyhjb2wgPSBhcy5udW1lcmljKGNvbERhdGEocGUpJGNvbmRpdGlvbikpCmBgYAoKVGhlIGZpcnN0IGF4aXMgaW4gdGhlIHBsb3QgaXMgc2hvd2luZyB0aGUgbGVhZGluZyBsb2cgZm9sZCBjaGFuZ2VzCihkaWZmZXJlbmNlcyBvbiB0aGUgbG9nIHNjYWxlKSBiZXR3ZWVuIHRoZSBzYW1wbGVzLgoKV2Ugbm90aWNlIHRoYXQgdGhlIGxlYWRpbmcgZGlmZmVyZW5jZXMgKGxvZyBGQykKaW4gdGhlIHBlcHRpZGUgZGF0YSBzZWVtcyB0byBiZSBkcml2ZW4gYnkgdGVjaG5pY2FsIHZhcmlhYmlsaXR5LgpJbmRlZWQsIHRoZSBzYW1wbGVzIGRvIG5vdCBzZWVtIHRvIGJlIGNsZWFybHkgc2VwYXJhdGVkIGFjY29yZGluZwp0byB0aGUgc3Bpa2UtaW4gY29uZGl0aW9uLgoKCiMgTWVkaWFuIHN1bW1hcml6YXRpb24KCiMjIFByZXByb2Nlc3NpbmcKLSBXZSB1c2UgbWVkaWFuIHN1bW1hcml6YXRpb24gaW4gYWdncmVnYXRlRmVhdHVyZXMuCi0gTm90ZSwgdGhhdCB0aGlzIGlzIGEgc3Vib3B0aW1hbCBub3JtYWxpc2F0aW9uIHByb2NlZHVyZSEKLSBCeSBkZWZhdWx0IHJvYnVzdCBzdW1tYXJpemF0aW9uIGlzIHVzZWQ6ICBgZnVuID0gTXNDb3JlVXRpbHM6OnJvYnVzdFN1bW1hcnkoKWAKCmBgYHtyLHdhcm5pbmc9RkFMU0V9CnBlIDwtIGFnZ3JlZ2F0ZUZlYXR1cmVzKHBlLAogIGkgPSAicGVwdGlkZU5vcm0iLAogIGZjb2wgPSAiUHJvdGVpbnMiLAogIG5hLnJtID0gVFJVRSwKICBuYW1lID0gInByb3RlaW5NZWRpYW4iLAogIGZ1biA9IG1hdHJpeFN0YXRzOjpjb2xNZWRpYW5zKQpwZQpgYGAKCmBgYHtyfQp0bXAgPC0gYXNzYXkocGVbWyJwcm90ZWluTWVkaWFuIl1dKQpjb2xuYW1lcyh0bXApIDwtIHN0cl9yZXBsYWNlX2FsbChjb2xuYW1lcyh0bXApLCAiSW50ZW5zaXR5LjYiLCIiKQp0bXAgJT4lCiAgbGltbWE6OnBsb3RNRFMoY29sID0gYXMubnVtZXJpYyhjb2xEYXRhKHBlKSRjb25kaXRpb24pKQpgYGAKCiMjIERhdGEgQW5hbHlzaXMKCiMjIyBFc3RpbWF0aW9uCgpXZSBtb2RlbCB0aGUgcHJvdGVpbiBsZXZlbCBleHByZXNzaW9uIHZhbHVlcyB1c2luZyBgbXNxcm9iYC4gIEJ5CmRlZmF1bHQgYG1zcXJvYjJgIGVzdGltYXRlcyB0aGUgbW9kZWwgcGFyYW1ldGVycyB1c2luZyByb2J1c3QKcmVncmVzc2lvbi4KCldlIHdpbGwgbW9kZWwgdGhlIGRhdGEgd2l0aCBhIGRpZmZlcmVudCBncm91cCBtZWFuLiAgVGhlIGdyb3VwIGlzCmluY29kZWQgaW4gdGhlIHZhcmlhYmxlIGBjb25kaXRpb25gIG9mIHRoZSBjb2xEYXRhLiAgV2UgY2FuIHNwZWNpZnkKdGhpcyBtb2RlbCBieSB1c2luZyBhIGZvcm11bGEgd2l0aCB0aGUgZmFjdG9yIGNvbmRpdGlvbiBhcyBpdHMKcHJlZGljdG9yOiBgZm9ybXVsYSA9IH5jb25kaXRpb25gLgoKTm90ZSwgdGhhdCBhIGZvcm11bGEgYWx3YXlzIHN0YXJ0cyB3aXRoIGEgc3ltYm9sICd+Jy4KCmBgYHtyIG1zcXJvYiwgd2FybmluZz1GQUxTRX0KcGUgPC0gbXNxcm9iKG9iamVjdCA9IHBlLAogICAgICAgICAgICAgaSA9ICJwcm90ZWluTWVkaWFuIiwKICAgICAgICAgICAgIGZvcm11bGEgPSB+Y29uZGl0aW9uLAogICAgICAgICAgICAgb3ZlcndyaXRlID0gVFJVRSkKYGBgCgpgYGB7cn0Kcm93RGF0YShwZVtbInByb3RlaW5NZWRpYW4iXV0pWywgYygiUHJvdGVpbnMiLCAiLm4iLCAibXNxcm9iTW9kZWxzIildCmBgYAoKCiMjIyBJbmZlcmVuY2UKCkZpcnN0LCB3ZSBleHRyYWN0IHRoZSBwYXJhbWV0ZXIgbmFtZXMgb2YgdGhlIG1vZGVsIGJ5IGxvb2tpbmcgYXQgdGhlCmZpcnN0IG1vZGVsLiAgVGhlIG1vZGVscyBhcmUgc3RvcmVkIGluIHRoZSByb3cgZGF0YSBvZiB0aGUgYXNzYXkgdW5kZXIKdGhlIGRlZmF1bHQgbmFtZSBtc3Fyb2JNb2RlbHMuCgpgYGB7cn0KZ2V0Q29lZihyb3dEYXRhKHBlW1sicHJvdGVpbk1lZGlhbiJdXSkkbXNxcm9iTW9kZWxzW1sxXV0pCmBgYAoKV2UgY2FuIGFsc28gZXhwbG9yZSB0aGUgZGVzaWduIG9mIHRoZSBtb2RlbCB0aGF0IHdlIHNwZWNpZmllZCB1c2luZyB0aGUgdGhlIHBhY2thZ2UgYEV4cGxvcmVNb2RlbE1hdHJpeGAKCmBgYHtyfQpsaWJyYXJ5KEV4cGxvcmVNb2RlbE1hdHJpeCkKVmlzdWFsaXplRGVzaWduKGNvbERhdGEocGUpLH5jb25kaXRpb24pJHBsb3RsaXN0W1sxXV0KYGBgCgpTcGlrZS1pbiBjb25kaXRpb24gYEFgIGlzIHRoZSByZWZlcmVuY2UgY2xhc3MuIFNvIHRoZSBtZWFuIGxvZzIKZXhwcmVzc2lvbiBmb3Igc2FtcGxlcyBmcm9tIGNvbmRpdGlvbiBBIGlzICcoSW50ZXJjZXB0KS4gIFRoZSBtZWFuCmxvZzIgZXhwcmVzc2lvbiBmb3Igc2FtcGxlcyBmcm9tIGNvbmRpdGlvbiBCIGlzCicoSW50ZXJjZXB0KStjb25kaXRpb25CJy4KCkhlbmNlLCB0aGUgYXZlcmFnZSBsb2cyIGZvbGQgY2hhbmdlIGJldHdlZW4gY29uZGl0aW9uIGIgYW5kIGNvbmRpdGlvbgphIGlzIG1vZGVsbGVkIHVzaW5nIHRoZSBwYXJhbWV0ZXIgJ2NvbmRpdGlvbkInLiAgVGh1cywgd2UgYXNzZXNzIHRoZQpjb250cmFzdCAnY29uZGl0aW9uQiA9IDAnIHdpdGggb3VyIHN0YXRpc3RpY2FsIHRlc3QuCgoKYGBge3J9CkwgPC0gbWFrZUNvbnRyYXN0KCJjb25kaXRpb25CPTAiLCBwYXJhbWV0ZXJOYW1lcyA9IGMoImNvbmRpdGlvbkIiKSkKcGUgPC0gaHlwb3RoZXNpc1Rlc3Qob2JqZWN0ID0gcGUsIGkgPSAicHJvdGVpbk1lZGlhbiIsIGNvbnRyYXN0ID0gTCkKYGBgCgoKIyMjIFBsb3RzCgojIyMjIFZvbGNhbm8tcGxvdAoKCmBgYHtyLHdhcm5pbmc9RkFMU0V9CnRtcCA8LSByb3dEYXRhKHBlW1sicHJvdGVpbk1lZGlhbiJdXSkkY29uZGl0aW9uQltjb21wbGV0ZS5jYXNlcyhyb3dEYXRhKHBlW1sicHJvdGVpbk1lZGlhbiJdXSkkY29uZGl0aW9uQiksXQp0bXAkc2hhcGVzIDwtIDE2Cgp2b2xjYW5vTWVkaWFuPC0gZ2dwbG90KHRtcCwKICAgICAgICAgICAgICAgICAgYWVzKHggPSBsb2dGQywgeSA9IC1sb2cxMChwdmFsKSwgY29sb3IgPSBhZGpQdmFsIDwgMC4wNSkpICsKICBnZW9tX3BvaW50KGNleCA9IDIuNSwgc2hhcGUgPSB0bXAkc2hhcGVzKSArCiAgI2dlb21fcG9pbnQoeCA9RlAkbG9nRkMsIHkgPSAtbG9nMTAoRlAkcHZhbCksIHNoYXBlID0gOCwgc2l6ZSA9IDQgKSsKICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gYWxwaGEoYygiYmxhY2siLCAicmVkIiksIDAuNSkpICsKICB0aGVtZV9idygpICsKICBnZ3RpdGxlKHBhc3RlMCgiTWVkaWFuOiBUUCA9ICIsc3VtKHRtcCRhZGpQdmFsPDAuMDUmZ3JlcGwocm93bmFtZXModG1wKSxwYXR0ZXJuID0iVVBTIiksbmEucm09VFJVRSksICIgRlAgPSAiLCBzdW0odG1wJGFkalB2YWw8MC4wNSYhZ3JlcGwocm93bmFtZXModG1wKSxwYXR0ZXJuID0iVVBTIiksbmEucm09VFJVRSkpKQp2b2xjYW5vTWVkaWFuCgpgYGAKCk5vdGUsIHRoYXQgb25seSBgciBzdW0ocm93RGF0YShwZVtbInByb3RlaW5NZWRpYW4iXV0pJGNvbmRpdGlvbkIkYWRqUHZhbCA8IDAuMDUsIG5hLnJtID0gVFJVRSlgIHByb3RlaW5zIGFyZSBmb3VuZCB0byBiZSBkaWZmZXJlbnRpYWxseSBhYnVuZGFudC4KCiMjIyMgSGVhdG1hcAoKV2UgZmlyc3Qgc2VsZWN0IHRoZSBuYW1lcyBvZiB0aGUgcHJvdGVpbnMgdGhhdCB3ZXJlIGRlY2xhcmVkIHNpZ25pZmljYW50CgpgYGB7cn0Kc2lnTmFtZXMgPC0gcm93RGF0YShwZVtbInByb3RlaW5NZWRpYW4iXV0pJGNvbmRpdGlvbkIgJT4lCiAgcm93bmFtZXNfdG9fY29sdW1uKCJwcm90ZWluTWVkaWFuIikgJT4lCiAgZmlsdGVyKGFkalB2YWw8MC4wNSkgJT4lCiAgcHVsbChwcm90ZWluTWVkaWFuKQogaGVhdG1hcChhc3NheShwZVtbInByb3RlaW5NZWRpYW4iXV0pW3NpZ05hbWVzLCBdLGNleFJvdyA9IDEsIGNleENvbCA9IDEpCgogc2lnUHJvdGVpbnMgPC0gcm93RGF0YShwZVtbInByb3RlaW5NZWRpYW4iXV0pJGNvbmRpdGlvbkIgJT4lCiAgcm93bmFtZXNfdG9fY29sdW1uKCJwcm90ZWluTWVkaWFuIikgJT4lCiAgIGZpbHRlcihncmVwbCgiVVBTIixwcm90ZWluTWVkaWFuKSkgJT4lCiAgcHVsbChwcm90ZWluTWVkaWFuKQogaGVhdG1hcChhc3NheShwZVtbInByb3RlaW5NZWRpYW4iXV0pW3NpZ1Byb3RlaW5zLCBdLCBjZXhDb2wgPSAxKQoKCmBgYAoKVGhlIG1ham9yaXR5IG9mIHRoZSBwcm90ZWlucyBhcmUgaW5kZWVkIFVQUyBwcm90ZWlucy4KMSB5ZWFzdCBwcm90ZWluIGlzIHJldHVybmVkLgpOb3RlLCB0aGF0IHRoZSB5ZWFzdCBwcm90ZWluIGluZGVlZCBzaG93cyBldmlkZW5jZSBmb3IgZGlmZmVyZW50aWFsIGFidW5kYW5jZS4KCiMjIyMgQm94cGxvdHMKCldlIGNyZWF0ZSBhIGJveHBsb3Qgb2YgdGhlIGxvZzIgRkMgYW5kIGdyb3VwIGFjY29yZGluZyB0byB0aGUgd2hldGhlciBhIHByb3RlaW4gaXMgc3Bpa2VkIG9yIG5vdC4KCmBgYHtyfQpyb3dEYXRhKHBlW1sicHJvdGVpbk1lZGlhbiJdXSkkY29uZGl0aW9uQiAlPiUKICByb3duYW1lc190b19jb2x1bW4odmFyID0gInByb3RlaW4iKSAlPiUKICBtdXRhdGUodXBzPWdyZXBsKCJVUFMiLHByb3RlaW4pKSAlPiUKICBnZ3Bsb3QoYWVzKHg9dXBzLCB5ID1sb2dGQywgZmlsbCA9IHVwcykpICsKICBnZW9tX2JveHBsb3QoKSArCiAgdGhlbWVfYncoKSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gbG9nMigwLjc0IC8gLjI1KSwgY29sb3IgPSAiIzAwQkZDNCIpICsKICAgIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDAsIGNvbG9yID0gIiNGODc2NkQiKQoKYGBgCgojIFJvYnVzdCBzdW1tYXJpemF0aW9uCgojIyBQcmVwcm9jZXNzaW5nCgotIEJ5IGRlZmF1bHQgcm9idXN0IHN1bW1hcml6YXRpb24gaXMgdXNlZDogIGBmdW4gPSBNc0NvcmVVdGlsczo6cm9idXN0U3VtbWFyeSgpYAotIFN0cnVjdHVyZSBmcm9tIFFGZWF0dXJlcyBpcyB1c2VmdWxsIGhlcmUuIE5vIG5lZWQgdG8gcmVydW4gYW55IG9mIHRoZSBwcmV2aW91cyBsb2cgdHJhbnNmb3JtYXRpb24gb3Igbm9ybWFsaXphdGlvbi4KYGBge3Isd2FybmluZz1GQUxTRX0KcGUgPC0gYWdncmVnYXRlRmVhdHVyZXMocGUsCiAgaSA9ICJwZXB0aWRlTm9ybSIsCiAgZmNvbCA9ICJQcm90ZWlucyIsCiAgbmEucm0gPSBUUlVFLAogIG5hbWUgPSAicHJvdGVpbl9yb2J1c3QiLAogIGZ1biA9IE1zQ29yZVV0aWxzOjpyb2J1c3RTdW1tYXJ5KQpgYGAKCk5vdyB3ZSBoYXZlIGJvdGggdGhlIHByb3RlaW5NZWRpYW4gYW5kIHByb3RlaW5fcm9idXN0IGluIG9uZSBRRmVhdHVyZXMgb2JqZWN0LgpgYGB7cn0KcGUKYGBgCgoKYGBge3J9CnRtcCA8LSBhc3NheShwZVtbInByb3RlaW5fcm9idXN0Il1dICkKY29sbmFtZXModG1wKSA8LSBzdHJfcmVwbGFjZV9hbGwoY29sbmFtZXModG1wKSwgIkludGVuc2l0eS42IiwiIikKdG1wICU+JQogIGxpbW1hOjpwbG90TURTKGNvbCA9IGFzLm51bWVyaWMoY29sRGF0YShwZSkkY29uZGl0aW9uKSkKYGBgCgpOb3RlIHRoYXQgdGhlIHNhbXBsZXMgdXBvbiByb2J1c3Qgc3VtbWFyaXNhdGlvbiBzaG93IGEgY2xlYXIgc2VwYXJhdGlvbiBhY2NvcmRpbmcgdG8gdGhlIHNwaWtlLWluIGNvbmRpdGlvbiBpbiB0aGUgc2Vjb25kIGRpbWVuc2lvbiBvZiB0aGUgTURTIHBsb3QuCgojIyBEYXRhIEFuYWx5c2lzCgojIyMgRXN0aW1hdGlvbgoKV2UgbW9kZWwgdGhlIHByb3RlaW4gbGV2ZWwgZXhwcmVzc2lvbiB2YWx1ZXMgdXNpbmcgYG1zcXJvYmAuCkJ5IGRlZmF1bHQgYG1zcXJvYjJgIGVzdGltYXRlcyB0aGUgbW9kZWwgcGFyYW1ldGVycyB1c2luZyByb2J1c3QgcmVncmVzc2lvbi4KCldlIHdpbGwgbW9kZWwgdGhlIGRhdGEgd2l0aCBhIGRpZmZlcmVudCBncm91cCBtZWFuLgpUaGUgZ3JvdXAgaXMgaW5jb2RlZCBpbiB0aGUgdmFyaWFibGUgYGNvbmRpdGlvbmAgb2YgdGhlIGNvbERhdGEuCldlIGNhbiBzcGVjaWZ5IHRoaXMgbW9kZWwgYnkgdXNpbmcgYSBmb3JtdWxhIHdpdGggdGhlIGZhY3RvciBjb25kaXRpb24gYXMgaXRzIHByZWRpY3RvcjoKYGZvcm11bGEgPSB+Y29uZGl0aW9uYC4KCk5vdGUsIHRoYXQgYSBmb3JtdWxhIGFsd2F5cyBzdGFydHMgd2l0aCBhIHN5bWJvbCAnficuCgpgYGB7ciwgd2FybmluZz1GQUxTRX0KcGUgPC0gbXNxcm9iKG9iamVjdCA9IHBlLCBpID0gInByb3RlaW5fcm9idXN0IiwgZm9ybXVsYSA9IH5jb25kaXRpb24pCmBgYAoKIyMjIEluZmVyZW5jZQoKRmlyc3QsIHdlIGV4dHJhY3QgdGhlIHBhcmFtZXRlciBuYW1lcyBvZiB0aGUgbW9kZWwgYnkgbG9va2luZyBhdCB0aGUgZmlyc3QgbW9kZWwuClRoZSBtb2RlbHMgYXJlIHN0b3JlZCBpbiB0aGUgcm93IGRhdGEgb2YgdGhlIGFzc2F5IHVuZGVyIHRoZSBkZWZhdWx0IG5hbWUgbXNxcm9iTW9kZWxzLgoKYGBge3J9CmdldENvZWYocm93RGF0YShwZVtbInByb3RlaW5fcm9idXN0Il1dKSRtc3Fyb2JNb2RlbHNbWzFdXSkKYGBgCgpXZSBjYW4gYWxzbyBleHBsb3JlIHRoZSBkZXNpZ24gb2YgdGhlIG1vZGVsIHRoYXQgd2Ugc3BlY2lmaWVkIHVzaW5nIHRoZSB0aGUgcGFja2FnZSBgRXhwbG9yZU1vZGVsTWF0cml4YAoKYGBge3J9CmxpYnJhcnkoRXhwbG9yZU1vZGVsTWF0cml4KQpWaXN1YWxpemVEZXNpZ24oY29sRGF0YShwZSksfmNvbmRpdGlvbikkcGxvdGxpc3RbWzFdXQpgYGAKClNwaWtlLWluIGNvbmRpdGlvbiBgQWAgaXMgdGhlIHJlZmVyZW5jZSBjbGFzcy4gU28gdGhlIG1lYW4gbG9nMiBleHByZXNzaW9uCmZvciBzYW1wbGVzIGZyb20gY29uZGl0aW9uIEEgaXMgJyhJbnRlcmNlcHQpLgpUaGUgbWVhbiBsb2cyIGV4cHJlc3Npb24gZm9yIHNhbXBsZXMgZnJvbSBjb25kaXRpb24gQiBpcyAnKEludGVyY2VwdCkrY29uZGl0aW9uQicuCkhlbmNlLCB0aGUgYXZlcmFnZSBsb2cyIGZvbGQgY2hhbmdlIGJldHdlZW4gY29uZGl0aW9uIGIgYW5kCmNvbmRpdGlvbiBhIGlzIG1vZGVsbGVkIHVzaW5nIHRoZSBwYXJhbWV0ZXIgJ2NvbmRpdGlvbkInLgpUaHVzLCB3ZSBhc3Nlc3MgdGhlIGNvbnRyYXN0ICdjb25kaXRpb25CID0gMCcgd2l0aCBvdXIgc3RhdGlzdGljYWwgdGVzdC4KCmBgYHtyfQpMIDwtIG1ha2VDb250cmFzdCgiY29uZGl0aW9uQj0wIiwgcGFyYW1ldGVyTmFtZXMgPSBjKCJjb25kaXRpb25CIikpCnBlIDwtIGh5cG90aGVzaXNUZXN0KG9iamVjdCA9IHBlLCBpID0gInByb3RlaW5fcm9idXN0IiwgY29udHJhc3QgPSBMKQpgYGAKCgojIyMgUGxvdHMKCiMjIyMgVm9sY2Fuby1wbG90CgoKYGBge3Isd2FybmluZz1GQUxTRX0KdG1wIDwtIHJvd0RhdGEocGVbWyJwcm90ZWluX3JvYnVzdCJdXSkkY29uZGl0aW9uQltjb21wbGV0ZS5jYXNlcyhyb3dEYXRhKHBlW1sicHJvdGVpbl9yb2J1c3QiXV0pJGNvbmRpdGlvbkIpLF0KCnRtcCRzaGFwZXMgPC0gMTYKdG1wWyFncmVwbCgiVVBTIixyb3duYW1lcyh0bXApKSAmIHRtcCRhZGpQdmFsIDwgMC4wNSxdJHNoYXBlcyA8LSAxMgojRlAgPC10bXBbIWdyZXBsKCJVUFMiLHJvd25hbWVzKHRtcCkpICYgdG1wJGFkalB2YWwgPCAwLjA1LF0Kdm9sY2Fub1JvYnVzdDwtIGdncGxvdCh0bXAsCiAgICAgICAgICAgICAgICAgIGFlcyh4ID0gbG9nRkMsIHkgPSAtbG9nMTAocHZhbCksIGNvbG9yID0gYWRqUHZhbCA8IDAuMDUpKSArCiAgZ2VvbV9wb2ludChjZXggPSAyLjUsIHNoYXBlID0gdG1wJHNoYXBlcykgKwogICNnZW9tX3BvaW50KHggPUZQJGxvZ0ZDLCB5ID0gLWxvZzEwKEZQJHB2YWwpLCBzaGFwZSA9IDgsIHNpemUgPSA0ICkrCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGFscGhhKGMoImJsYWNrIiwgInJlZCIpLCAwLjUpKSArCiAgdGhlbWVfYncoKSArCiAgZ2d0aXRsZShwYXN0ZTAoIlJvYnVzdDogVFAgPSAiLHN1bSh0bXAkYWRqUHZhbDwwLjA1JmdyZXBsKHJvd25hbWVzKHRtcCkscGF0dGVybiA9IlVQUyIpLG5hLnJtPVRSVUUpLCAiIEZQID0gIiwgc3VtKHRtcCRhZGpQdmFsPDAuMDUmIWdyZXBsKHJvd25hbWVzKHRtcCkscGF0dGVybiA9IlVQUyIpLG5hLnJtPVRSVUUpKSkKCgpwbG90X2dyaWQocGxvdF9ncmlkKHZvbGNhbm9NZWRpYW4rdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSwKICAgICAgICAgIHZvbGNhbm9Sb2J1c3QrdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSksIGdldF9sZWdlbmQodm9sY2Fub1JvYnVzdCArIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iKSksIG5jb2wgPSAxLCByZWxfaGVpZ2h0cyA9IGMoMSwwLjE1KSkKCmBgYAoKTm90ZSwgdGhhdCBgciBzdW0ocm93RGF0YShwZVtbInByb3RlaW5fcm9idXN0Il1dKSRjb25kaXRpb25CJGFkalB2YWwgPCAwLjA1LCBuYS5ybSA9IFRSVUUpYCBwcm90ZWlucyBhcmUgZm91bmQgdG8gYmUgZGlmZmVyZW50aWFsbHkgYWJ1bmRhbnQuCgojIyMjIEhlYXRtYXAKCldlIGZpcnN0IHNlbGVjdCB0aGUgbmFtZXMgb2YgdGhlIHByb3RlaW5zIHRoYXQgd2VyZSBkZWNsYXJlZCBzaWduZmljYW50LgoKYGBge3J9CnNpZ05hbWVzIDwtIHJvd0RhdGEocGVbWyJwcm90ZWluX3JvYnVzdCJdXSkkY29uZGl0aW9uQiAlPiUKICByb3duYW1lc190b19jb2x1bW4oInByb3RlaW5fcm9idXN0IikgJT4lCiAgZmlsdGVyKGFkalB2YWw8MC4wNSkgJT4lCiAgcHVsbChwcm90ZWluX3JvYnVzdCkKaGVhdG1hcChhc3NheShwZVtbInByb3RlaW5fcm9idXN0Il1dKVtzaWdOYW1lcywgXSxjZXhDb2wgPSAxKQoKYGBgClRoZSBtYWpvcml0eSBvZiB0aGUgcHJvdGVpbnMgYXJlIGluZGVlZCBVUFMgcHJvdGVpbnMuCjEgeWVhc3QgcHJvdGVpbiBpcyByZXR1cm5lZC4KTm90ZSwgdGhhdCB0aGUgeWVhc3QgcHJvdGVpbiBpbmRlZWQgc2hvd3MgZXZpZGVuY2UgZm9yIGRpZmZlcmVudGlhbCBhYnVuZGFuY2UuCgpoZWF0bWFwcyBhbHNvIHNob3cgZGlmZmVyZW5jZSBiZXR3ZWVuIG1lZGlhbiBhbmQgcm9idXN0IHN1bW1hcml6YXRpb24KYGBge3J9CnBhcihjZXgubWFpbj0uOCkKc2lnUHJvdGVpbnMgPC0gcm93RGF0YShwZVtbInByb3RlaW5fcm9idXN0Il1dKSRjb25kaXRpb25CICU+JQogIHJvd25hbWVzX3RvX2NvbHVtbigicHJvdGVpbl9yb2J1c3QiKSAlPiUKICAgZmlsdGVyKGdyZXBsKCJVUFMiLHByb3RlaW5fcm9idXN0KSkgJT4lCiAgcHVsbChwcm90ZWluX3JvYnVzdCkKaGVhdG1hcChhc3NheShwZVtbInByb3RlaW5fcm9idXN0Il1dKVtzaWdQcm90ZWlucywgXSwgY2V4Q29sID0gMSxjZXhSb3cgPSAwLjUsIG1haW4gPSAiUm9idXN0IHN1bW1hcml6YXRpb24iKQpgYGAKCgojIyMjIEJveHBsb3RzCgpXZSBtYWtlIGJveHBsb3Qgb2YgdGhlIGxvZzIgRkMgYW5kIHN0cmF0aWZ5IGFjY29yZGluZyB0byB0aGUgd2hldGhlciBhIHByb3RlaW4gaXMgc3Bpa2VkIG9yIG5vdC4KCmBgYHtyfQpyYmluZChyb3dEYXRhKHBlW1sicHJvdGVpbl9yb2J1c3QiXV0pJGNvbmRpdGlvbkIgJT4lCiAgcm93bmFtZXNfdG9fY29sdW1uKHZhciA9ICJwcm90ZWluIikgJT4lIG11dGF0ZShtZXRob2QgPSAicm9idXN0IiksCiAgcm93RGF0YShwZVtbInByb3RlaW5NZWRpYW4iXV0pJGNvbmRpdGlvbkIgJT4lCiAgcm93bmFtZXNfdG9fY29sdW1uKHZhciA9ICJwcm90ZWluIikgJT4lIG11dGF0ZShtZXRob2QgPSAibWVkaWFuIikpJT4lCiAgbXV0YXRlKHVwcz1ncmVwbCgiVVBTIixwcm90ZWluKSkgJT4lCiAgZ2dwbG90KGFlcyh4PW1ldGhvZCwgeSA9bG9nRkMsIGZpbGwgPSB1cHMpKSArCiAgZ2VvbV9ib3hwbG90KCkgKwogIHRoZW1lX2J3KCkgKwogIGdlb21faGxpbmUoeWludGVyY2VwdCA9IGxvZzIoMC43NCAvIC4yNSksIGNvbG9yID0gIiMwMEJGQzQiKSArCiAgICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLCBjb2xvciA9ICIjRjg3NjZEIikKCmBgYAoKIyBXaGVyZSB0aGUgZGlmZmVyZW5jZSBjb21lcyBmcm9tCgojIyBJbXBvcnQgZGF0YSBmcm9tIHRoZSBmdWxsIENQVEFDIHN0dWR5Cgo8ZGV0YWlscz48c3VtbWFyeT4gQ2xpY2sgdG8gc2VlIGJhY2tncm91bmQgYW5kIGNvZGUgPC9zdW1tYXJ5PjxwPgoxLiBXZSB1c2UgYSBwZXB0aWRlcy50eHQgZmlsZSBmcm9tIE1TLWRhdGEgcXVhbnRpZmllZCB3aXRoIG1heHF1YW50IHRoYXQKY29udGFpbnMgTVMxIGludGVuc2l0aWVzIHN1bW1hcml6ZWQgYXQgdGhlIHBlcHRpZGUgbGV2ZWwuCmBgYHtyfQpwZXB0aWRlc0ZpbGUgPC0gImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9zdGF0T21pY3MvUERBL2RhdGEvcXVhbnRpZmljYXRpb24vZnVsbENwdGFjRGF0YXNTZXROb3RGb3JUdXRvcmlhbC9wZXB0aWRlcy50eHQiCgpgYGAKCjIuIE1heHF1YW50IHN0b3JlcyB0aGUgaW50ZW5zaXR5IGRhdGEgZm9yIHRoZSBkaWZmZXJlbnQgc2FtcGxlcyBpbiBjb2x1bW5ucyB0aGF0IHN0YXJ0IHdpdGggSW50ZW5zaXR5LiBXZSBjYW4gcmV0cmVpdmUgdGhlIGNvbHVtbiBuYW1lcyB3aXRoIHRoZSBpbnRlbnNpdHkgZGF0YSB3aXRoIHRoZSBjb2RlIGJlbG93OgoKYGBge3J9CmVjb2xzIDwtIGdyZXAoIkludGVuc2l0eVxcLiIsIG5hbWVzKHJlYWQuZGVsaW0ocGVwdGlkZXNGaWxlKSkpCmBgYAoKMy4gUmVhZCB0aGUgZGF0YSBhbmQgc3RvcmUgaXQgaW4gIFFGZWF0dXJlcyBvYmplY3QKCmBgYHtyfQpwZSA8LSByZWFkUUZlYXR1cmVzKAogIHRhYmxlID0gcGVwdGlkZXNGaWxlLAogIGZuYW1lcyA9IDEsCiAgZWNvbCA9IGVjb2xzLAogIG5hbWUgPSAicGVwdGlkZVJhdyIsIHNlcD0iXHQiKQpgYGAKPC9wPjwvZGV0YWlscz4KCiMjIyBEZXNpZ24KCjxkZXRhaWxzPjxzdW1tYXJ5PiBDbGljayB0byBzZWUgYmFja2dyb3VuZCBhbmQgY29kZSA8L3N1bW1hcnk+PHA+CgpgYGB7cn0KcGUgJT4lIGNvbG5hbWVzCmBgYAoKLSBOb3RlLCB0aGF0IHRoZSBzYW1wbGUgbmFtZXMgaW5jbHVkZSB0aGUgc3Bpa2UtaW4gY29uZGl0aW9uLgotIFRoZXkgYWxzbyBlbmQgb24gYSBudW1iZXIuCgogIC0gMS0zIGlzIGZyb20gbGFiIDEsCiAgLSA0LTYgZnJvbSBsYWIgMiBhbmQKICAtIDctOSBmcm9tIGxhYiAzLgoKLSBXZSB1cGRhdGUgdGhlIGNvbERhdGEgd2l0aCBpbmZvcm1hdGlvbiBvbiB0aGUgZGVzaWduCgpgYGB7cn0KY29sRGF0YShwZSkkbGFiIDwtIHJlcChyZXAocGFzdGUwKCJsYWIiLDE6MyksZWFjaD0zKSw1KSAlPiUgYXMuZmFjdG9yCmNvbERhdGEocGUpJGNvbmRpdGlvbiA8LSBwZVtbInBlcHRpZGVSYXciXV0gJT4lIGNvbG5hbWVzICU+JSBzdWJzdHIoMTIsMTIpICU+JSBhcy5mYWN0b3IKY29sRGF0YShwZSkkc3Bpa2VDb25jZW50cmF0aW9uIDwtIHJlcChjKEEgPSAwLjI1LCBCID0gMC43NCwgQyA9IDIuMjIsIEQgPSA2LjY3LCBFID0gMjApLGVhY2ggPSA5KQpgYGAKCi0gV2UgZXhwbG9yZSB0aGUgY29sRGF0YQoKYGBge3J9CmNvbERhdGEocGUpCmBgYAoKPC9wPjwvZGV0YWlscz4KCiMjIyBQcmVwcm9jZXNzaW5nCgo8ZGV0YWlscz48c3VtbWFyeT4gQ2xpY2sgdG8gc2VlIFItY29kZSB0byBwcmVwcm9jZXNzIHRoZSBkYXRhIDwvc3VtbWFyeT48cD4KCi0gV2UgY2FsY3VsYXRlIGhvdyBtYW55IG5vbiB6ZXJvIGludGVuc2l0aWVzIHdlIGhhdmUgZm9yIGVhY2ggcGVwdGlkZSBhbmQgdGhpcyBjYW4gYmUgdXNlZnVsIGZvciBmaWx0ZXJpbmcuCgpgYGB7cn0Kcm93RGF0YShwZVtbInBlcHRpZGVSYXciXV0pJG5Ob25aZXJvIDwtIHJvd1N1bXMoYXNzYXkocGVbWyJwZXB0aWRlUmF3Il1dKSA+IDApCmBgYAoKCi0gUGVwdGlkZXMgd2l0aCB6ZXJvIGludGVuc2l0aWVzIGFyZSBtaXNzaW5nIHBlcHRpZGVzIGFuZCBzaG91bGQgYmUgcmVwcmVzZW50CndpdGggYSBgTkFgIHZhbHVlIHJhdGhlciB0aGFuIGAwYC4KCmBgYHtyfQpwZSA8LSB6ZXJvSXNOQShwZSwgInBlcHRpZGVSYXciKSAjIGNvbnZlcnQgMCB0byBOQQpgYGAKCi0gTG9ndHJhbnNmb3JtIGRhdGEgd2l0aCBiYXNlIDIKCmBgYHtyfQpwZSA8LSBsb2dUcmFuc2Zvcm0ocGUsIGJhc2UgPSAyLCBpID0gInBlcHRpZGVSYXciLCBuYW1lID0gInBlcHRpZGVMb2ciKQpgYGAKPC9wPjwvZGV0YWlscz4KCgoxLiBIYW5kbGluZyBvdmVybGFwcGluZyBwcm90ZWluIGdyb3VwcwoKSW4gb3VyIGFwcHJvYWNoIGEgcGVwdGlkZSBjYW4gbWFwIHRvIG11bHRpcGxlIHByb3RlaW5zLCBhcyBsb25nIGFzIHRoZXJlIGlzCm5vbmUgb2YgdGhlc2UgcHJvdGVpbnMgcHJlc2VudCBpbiBhIHNtYWxsZXIgc3ViZ3JvdXAuCgpgYGB7cn0KcGUgPC0gZmlsdGVyRmVhdHVyZXMocGUsIH4gUHJvdGVpbnMgJWluJSBzbWFsbGVzdFVuaXF1ZUdyb3Vwcyhyb3dEYXRhKHBlW1sicGVwdGlkZUxvZyJdXSkkUHJvdGVpbnMpKQpgYGAKCjIuIFJlbW92ZSByZXZlcnNlIHNlcXVlbmNlcyAoZGVjb3lzKSBhbmQgY29udGFtaW5hbnRzCgpXZSBub3cgcmVtb3ZlIHRoZSBjb250YW1pbmFudHMsIHBlcHRpZGVzIHRoYXQgbWFwIHRvIGRlY295IHNlcXVlbmNlcywgYW5kIHByb3RlaW5zCndoaWNoIHdlcmUgb25seSBpZGVudGlmaWVkIGJ5IHBlcHRpZGVzIHdpdGggbW9kaWZpY2F0aW9ucy4KCmBgYHtyfQpwZSA8LSBmaWx0ZXJGZWF0dXJlcyhwZSx+UmV2ZXJzZSAhPSAiKyIpCnBlIDwtIGZpbHRlckZlYXR1cmVzKHBlLH4gUG90ZW50aWFsLmNvbnRhbWluYW50ICE9ICIrIikKYGBgCgozLiBEcm9wIHBlcHRpZGVzIHRoYXQgd2VyZSBvbmx5IGlkZW50aWZpZWQgaW4gb25lIHNhbXBsZQoKV2Uga2VlcCBwZXB0aWRlcyB0aGF0IHdlcmUgb2JzZXJ2ZWQgYXQgbGFzdCB0d2ljZS4KCmBgYHtyfQpwZSA8LSBmaWx0ZXJGZWF0dXJlcyhwZSx+IG5Ob25aZXJvID49MikKbnJvdyhwZVtbInBlcHRpZGVMb2ciXV0pCmBgYAoKV2Uga2VlcCBgciBucm93KHBlW1sicGVwdGlkZUxvZyJdXSlgIHBlcHRpZGVzIHVwb24gZmlsdGVyaW5nLgo8L3A+PC9kZXRhaWxzPgoKIyMjIE5vcm1hbGl6YXRpb24KCjxkZXRhaWxzPjxzdW1tYXJ5PiBDbGljayB0byBzZWUgUi1jb2RlIHRvIG5vcm1hbGl6ZSB0aGUgZGF0YSA8L3N1bW1hcnk+PHA+CmBgYHtyfQpwZSA8LSBub3JtYWxpemUocGUsCiAgICAgICAgICAgICAgICBpID0gInBlcHRpZGVMb2ciLAogICAgICAgICAgICAgICAgbmFtZSA9ICJwZXB0aWRlTm9ybSIsCiAgICAgICAgICAgICAgICBtZXRob2QgPSAiY2VudGVyLm1lZGlhbiIpCmBgYAo8L3A+PC9kZXRhaWxzPgoKLS0tCgojIyBQZXB0aWRlLUxldmVsIHZpZXcKCiMjIyBTdW1tYXJpemF0aW9uCgoKPGRldGFpbHM+PHN1bW1hcnk+IENsaWNrIHRvIHNlZSBjb2RlIHRvIG1ha2UgcGxvdCA8L3N1bW1hcnk+PHA+CmBgYHtyIHBsb3QgPSBGQUxTRX0KCnByb3QgPC0gIlAwMTAzMXVwc3xDTzVfSFVNQU5fVVBTIgpkYXRhIDwtIHBlW1sicGVwdGlkZU5vcm0iXV1bCiAgcm93RGF0YShwZVtbInBlcHRpZGVOb3JtIl1dKSRQcm90ZWlucyA9PSBwcm90LAogIGNvbERhdGEocGUpJGxhYj09ImxhYjMiXSAlPiUKICBhc3NheSAlPiUKICBhcy5kYXRhLmZyYW1lICU+JQogIHJvd25hbWVzX3RvX2NvbHVtbih2YXIgPSAicGVwdGlkZSIpICU+JQogIGdhdGhlcihzYW1wbGUsIGludGVuc2l0eSwgLXBlcHRpZGUpICU+JQogIG11dGF0ZShjb25kaXRpb24gPSBjb2xEYXRhKHBlKVtzYW1wbGUsImNvbmRpdGlvbiJdKSAlPiUKICBuYS5leGNsdWRlCnN1bVBsb3QgPC0gZGF0YSAlPiUKICBnZ3Bsb3QoYWVzKHggPSBwZXB0aWRlLCB5ID0gaW50ZW5zaXR5LCBjb2xvciA9IGNvbmRpdGlvbiwgZ3JvdXAgPSBzYW1wbGUsIGxhYmVsID0gY29uZGl0aW9uKSwgc2hvdy5sZWdlbmQgPSBGQUxTRSkgKwogIGdlb21fdGV4dChzaG93LmxlZ2VuZCA9IEZBTFNFKSArCiAgdGhlbWVfbWluaW1hbCgpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCB2anVzdCA9IDAuNSwgaGp1c3QgPSAxKSkgKwogIHhsYWIoIlBlcHRpZGUiKSArCiAgeWxhYigiSW50ZW5zaXR5IChsb2cyKSIpICsKICBnZ3RpdGxlKHBhc3RlMCgicHJvdGVpbjogIixwcm90KSkKYGBgCjwvcD48L2RldGFpbHM+CgoKSGVyZSwgd2Ugd2lsbCBmb2N1cyBvbiB0aGUgc3VtbWFyaXphdGlvbiBvZiB0aGUgaW50ZW5zaXRpZXMgZm9yIHByb3RlaW4gYHIgcHJvdGAgZnJvbSBMYWIzIGZvciBhbGwgY29uZGl0aW9ucy4KCmBgYHtyfQpzdW1QbG90ICsKICBnZW9tX2xpbmUobGluZXR5cGU9ImRhc2hlZCIsYWxwaGE9LjQpCmBgYAoKIyMjIyBNZWRpYW4gc3VtbWFyaXphdGlvbgoKV2UgZmlyc3QgZXZhbHVhdGUgbWVkaWFuIHN1bW1hcml6YXRpb24gZm9yIHByb3RlaW4gYHIgcHJvdGAuCgo8ZGV0YWlscz48c3VtbWFyeT4gQ2xpY2sgdG8gc2VlIGNvZGUgdG8gbWFrZSBwbG90IDwvc3VtbWFyeT48cD4KYGBge3J9CmRhdGFIbHAgPC0gcGVbWyJwZXB0aWRlTm9ybSJdXVsKICAgIHJvd0RhdGEocGVbWyJwZXB0aWRlTm9ybSJdXSkkUHJvdGVpbnMgPT0gcHJvdCwKICAgIGNvbERhdGEocGUpJGxhYj09ImxhYjMiXSAlPiUgYXNzYXkKCnN1bU1lZGlhbiA8LSBkYXRhLmZyYW1lKAogIGludGVuc2l0eT0gZGF0YUhscAogICAgJT4lIGNvbE1lZGlhbnMobmEucm09VFJVRSkKICAsCiAgY29uZGl0aW9uPSBjb2xuYW1lcyhkYXRhSGxwKSAlPiUgc3Vic3RyKDEyLDEyKSAlPiUgYXMuZmFjdG9yICkKCnN1bU1lZGlhblBsb3QgPC0gc3VtUGxvdCArCiAgZ2VvbV9obGluZSgKICAgIGRhdGEgPSBzdW1NZWRpYW4sCiAgICBtYXBwaW5nID0gYWVzKHlpbnRlcmNlcHQ9aW50ZW5zaXR5LGNvbG9yPWNvbmRpdGlvbikpICsKICBnZ3RpdGxlKCJNZWRpYW4gc3VtbWFyaXphdGlvbiIpCmBgYAo8L3A+PC9kZXRhaWxzPgoKYGBge3J9CnN1bU1lZGlhblBsb3QKYGBgCgoKLSBUaGUgc2FtcGxlIG1lZGlhbnMgYXJlIG5vdCBhIGdvb2QgZXN0aW1hdGUgZm9yIHRoZSBwcm90ZWluIGV4cHJlc3Npb24gdmFsdWUuCi0gSW5kZWVkLCB0aGV5IGRvIG5vdCBhY2NvdW50IGZvciBkaWZmZXJlbmNlcyBpbiBwZXB0aWRlIGVmZmVjdHMKLSBQZXB0aWRlcyB0aGF0IGlvbml6ZSBwb29ybHkgYXJlIGFsc28gcGlja2VkIHVwIGluIHNhbXBsZXMgd2l0aCBoaWdoIHNwaWtlLWluIGNvbmNlbmNlbnRyYXRpb24gYW5kIG5vdCBpbiBzYW1wbGVzIHdpdGggbG93IHNwaWtlLWluIGNvbmNlbnRyYXRpb24KLSBUaGlzIGludHJvZHVjZXMgYSBiaWFzLgoKCiMjIyMgTGluZWFyIE1vZGVsIGJhc2VkIHN1bW1hcml6YXRpb24KCldlIGNhbiB1c2UgYSBsaW5lYXIgcGVwdGlkZS1sZXZlbCBtb2RlbCB0byBlc3RpbWF0ZSB0aGUgcHJvdGVpbiBleHByZXNzaW9uIHZhbHVlIHdoaWxlIGNvcnJlY3RpbmcgZm9yIHRoZSBwZXB0aWRlIGVmZmVjdCwgaS5lLgoKJCQKeV97aXB9ID0gXGJldGFfaV5cdGV4dHtzYW1wbGV9K1xiZXRhXntwZXB0aWRlfV97cH0gKyBcZXBzaWxvbl97aXB9CiQkCgoKPGRldGFpbHM+PHN1bW1hcnk+IENsaWNrIHRvIHNlZSBjb2RlIHRvIG1ha2UgcGxvdCA8L3N1bW1hcnk+PHA+CmBgYHtyfQpzdW1NZWFuUGVwTW9kIDwtIGxtKGludGVuc2l0eSB+IC0xICsgc2FtcGxlICsgcGVwdGlkZSxkYXRhKQoKc3VtTWVhblBlcCA8LSBkYXRhLmZyYW1lKAogIGludGVuc2l0eT1zdW1NZWFuUGVwTW9kJGNvZWZbZ3JlcCgic2FtcGxlIixuYW1lcyhzdW1NZWFuUGVwTW9kJGNvZWYpKV0gKyBtZWFuKGRhdGEkaW50ZW5zaXR5KSAtIG1lYW4oc3VtTWVhblBlcE1vZCRjb2VmW2dyZXAoInNhbXBsZSIsbmFtZXMoc3VtTWVhblBlcE1vZCRjb2VmKSldKSwKICBjb25kaXRpb249IG5hbWVzKHN1bU1lYW5QZXBNb2QkY29lZilbZ3JlcCgic2FtcGxlIixuYW1lcyhzdW1NZWFuUGVwTW9kJGNvZWYpKV0gJT4lIHN1YnN0cigxOCwxOCkgJT4lIGFzLmZhY3RvciApCgoKZml0TG1QbG90IDwtICBzdW1QbG90ICsgZ2VvbV9saW5lKAogICAgZGF0YSA9IGRhdGEgJT4lIG11dGF0ZShmaXQ9c3VtTWVhblBlcE1vZCRmaXR0ZWQudmFsdWVzKSwKICAgIG1hcHBpbmcgPSBhZXMoeD1wZXB0aWRlLCB5PWZpdCxjb2xvcj1jb25kaXRpb24sIGdyb3VwPXNhbXBsZSkpICsKICAgIGdndGl0bGUoImZpdDogfiBzYW1wbGUgKyBwZXB0aWRlIikKc3VtTG1QbG90IDwtIHN1bVBsb3QgKyBnZW9tX2hsaW5lKAogICAgZGF0YSA9IHN1bU1lYW5QZXAsCiAgICBtYXBwaW5nID0gYWVzKHlpbnRlcmNlcHQ9aW50ZW5zaXR5LGNvbG9yPWNvbmRpdGlvbikpICsKICAgIGdndGl0bGUoIlBlcHRpZGUtTGV2ZWwgbGluZWFyIG1vZGVsIFN1bW1hcml6YXRpb24iKQpgYGAKPC9wPjwvZGV0YWlscz4KCmBgYHtyfQpwbG90X2dyaWQocGxvdF9ncmlkKHN1bU1lZGlhblBsb3QrdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSwKICAgICAgICAgIHN1bUxtUGxvdCt0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpICsgeWxhYigiIikpLAogICAgICAgICAgZ2V0X2xlZ2VuZChzdW1MbVBsb3QpLCBuY29sID0gMiwgcmVsX3dpZHRocyAgPSBjKDEsMC4xNSkpCmBgYAoKLSBCeSBjb3JyZWN0aW5nIGZvciB0aGUgcGVwdGlkZSBzcGVjaWVzIHRoZSBwcm90ZWluIGV4cHJlc3Npb24gdmFsdWVzIGFyZSBtdWNoIGJldHRlciBzZXBhcmF0ZWQgYW4gYmV0dGVyIHJlZmxlY3QgZGlmZmVyZW5jZXMgaW4gYWJ1bmRhbmNlIGluZHVjZWQgYnkgdGhlIHNwaWtlLWluIGNvbmRpdGlvbi4KCi0gSW5kZWVkLCBpdCBzaG93cyB0aGF0IG1lZGlhbiBhbmQgbWVhbiBzdW1tYXJpemF0aW9uIHRoYXQgZG8gbm90IGFjY291bnQgZm9yIHRoZSBwZXB0aWRlIGVmZmVjdCBpbmRlZWQgb3ZlcmVzdGltYXRlIHRoZSBwcm90ZWluIGV4cHJlc3Npb24gdmFsdWUgaW4gdGhlIHNtYWxsIHNwaWtlLWluIGNvbmRpdGlvbnMgYW5kIHVuZGVyZXN0aW1hdGUgdGhhdCBpbiB0aGUgbGFyZ2Ugc3Bpa2UtaW4gY29uZGl0aW9ucy4KCi0gU3RpbGwgdGhlcmUgc2VlbSB0byBiZSBzb21lIGlzc3VlcyB3aXRoIHNhbXBsZXMgdGhhdCBmb3Igd2hpY2ggdGhlIGV4cHJlc3Npb24gdmFsdWVzIGFyZSBub3Qgd2VsbCBzZXBhcmF0ZWQgYWNjb3JkaW5nIHRvIHRoZSBzcGlrZS1pbiBjb25kaXRpb24uCgpBIHJlc2lkdWFsIGFuYWx5c2lzIGNsZWFybHkgaW5kaWNhdGVzIHBvdGVudGlhbCBpc3N1ZXM6Cgo8ZGV0YWlscz48c3VtbWFyeT4gQ2xpY2sgdG8gc2VlIGNvZGUgdG8gbWFrZSBwbG90IDwvc3VtbWFyeT48cD4KYGBge3J9CnJlc1Bsb3QgPC0gZGF0YSAlPiUKICBtdXRhdGUocmVzPXN1bU1lYW5QZXBNb2QkcmVzaWR1YWxzKSAlPiUKICBnZ3Bsb3QoYWVzKHggPSBwZXB0aWRlLCB5ID0gcmVzLCBjb2xvciA9IGNvbmRpdGlvbiwgbGFiZWwgPSBjb25kaXRpb24pLCBzaG93LmxlZ2VuZCA9IEZBTFNFKSArCiAgZ2VvbV9wb2ludChzaGFwZT0yMSkgKwogIHRoZW1lX21pbmltYWwoKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgdmp1c3QgPSAwLjUsIGhqdXN0ID0gMSkpICsKICB4bGFiKCJQZXB0aWRlIikgKwogIHlsYWIoInJlc2lkdWFsIikgKwogIGdndGl0bGUoInJlc2lkdWFsczogfiBzYW1wbGUgKyBwZXB0aWRlIikKYGBgCjwvcD48L2RldGFpbHM+CgpgYGB7cn0KZ3JpZC5hcnJhbmdlKGZpdExtUGxvdCt0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpLCByZXNQbG90LCBucm93ID0gMSkKCnBsb3RfZ3JpZChwbG90X2dyaWQoZml0TG1QbG90K3RoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIiksCiAgICAgICAgICBzdW1MbVBsb3QrdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSArIHlsYWIoIiIpKSwKICAgICAgICAgIGdldF9sZWdlbmQoc3VtTG1QbG90KSwgbmNvbCA9IDIsIHJlbF93aWR0aHMgID0gYygxLDAuMTUpKQoKCmBgYAoKLSBUaGUgcmVzaWR1YWwgcGxvdCBzaG93cyBzb21lIGxhcmdlIG91dGxpZXJzIGZvciBwZXB0aWRlIEtJRUVJQUFLLgotIEluZGVlZCwgaW4gdGhlIG9yaWdpbmFsIHBsb3QgdGhlIGludGVuc2l0aWVzIGZvciB0aGlzIHBlcHRpZGUgZG8gbm90IHNlZW0gdG8gbGluZSB1cCB2ZXJ5IHdlbGwgd2l0aCB0aGUgY29uY2VudHJhdGlvbi4KLSBUaGlzIGluZHVjZXMgYSBiaWFzIGluIHRoZSBzdW1tYXJpemF0aW9uIGZvciBzb21lIG9mIHRoZSBzYW1wbGVzIChlLmcuIGZvciBEIGFuZCBFKQoKIyMjIyBSb2J1c3Qgc3VtbWFyaXphdGlvbiB1c2luZyBhIHBlcHRpZGUtbGV2ZWwgbGluZWFyIG1vZGVsCgokJAp5X3tpcH0gPSBcYmV0YV9pXlx0ZXh0e3NhbXBsZX0rXGJldGFee3BlcHRpZGV9X3twfSArIFxlcHNpbG9uX3tpcH0KJCQKCgotIE9yZGluYXJ5IGxlYXN0IHNxdWFyZXM6IGVzdGltYXRlICRcYmV0YSQgdGhhdCBtaW5pbWl6ZXMKJCQKXHRleHR7T0xTfTogXHN1bVxsaW1pdHNfe2kscH0gXGVwc2lsb25fe2lwfV4yID0gXHN1bVxsaW1pdHNfe2kscH0oeV97aXB9LVxiZXRhX2leXHRleHR7c2FtcGxlfS1cYmV0YV9wXlx0ZXh0e3BlcHRpZGV9KV4yCiQkCgpXZSByZXBsYWNlIE9MUyBieSBNLWVzdGltYXRpb24gd2l0aCBsb3NzIGZ1bmN0aW9uCiQkClxzdW1cbGltaXRzX3tpLHB9IHdfe2lwfVxlcHNpbG9uX3tpcH1eMiA9IFxzdW1cbGltaXRzX3tpLHB9d197aXB9KHlfe2lwfS1cYmV0YV9pXlx0ZXh0e3NhbXBsZX0tXGJldGFfcF5cdGV4dHtwZXB0aWRlfSleMgokJAoKLSBJdGVyYXRpdmVseSBmaXQgbW9kZWwgd2l0aCBvYnNlcnZhdGlvbiB3ZWlnaHRzICR3X3tpcH0kIHVudGlsIGNvbnZlcmdlbmNlCi0gVGhlIHdlaWdodHMgYXJlIGNhbGN1bGF0ZWQgYmFzZWQgb24gc3RhbmRhcmRpemVkIHJlc2lkdWFscwoKPGRldGFpbHM+PHN1bW1hcnk+IENsaWNrIHRvIHNlZSBjb2RlIHRvIG1ha2UgcGxvdCA8L3N1bW1hcnk+PHA+CmBgYHtyfQpzdW1NZWFuUGVwUm9iTW9kIDwtIE1BU1M6OnJsbShpbnRlbnNpdHkgfiAtMSArIHNhbXBsZSArIHBlcHRpZGUsZGF0YSkKcmVzUm9iUGxvdCA8LSBkYXRhICU+JQogIG11dGF0ZShyZXMgPSBzdW1NZWFuUGVwUm9iTW9kJHJlc2lkdWFscywKICAgICAgICAgdyA9IHN1bU1lYW5QZXBSb2JNb2QkdykgJT4lCiAgZ2dwbG90KGFlcyh4ID0gcGVwdGlkZSwgeSA9IHJlcywgY29sb3IgPSBjb25kaXRpb24sIGxhYmVsID0gY29uZGl0aW9uLHNpemU9dyksIHNob3cubGVnZW5kID0gRkFMU0UpICsKICBnZW9tX3BvaW50KHNoYXBlPTIxLHNpemU9LjIpICsKICBnZW9tX3BvaW50KHNoYXBlPTIxKSArCiAgdGhlbWVfbWluaW1hbCgpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCB2anVzdCA9IDAuNSwgaGp1c3QgPSAxKSkgKwogIHhsYWIoIlBlcHRpZGUiKSArCiAgeWxhYigicmVzaWR1YWwiKSArCiAgeWxpbShjKC0xLDEpKm1heChhYnMoc3VtTWVhblBlcFJvYk1vZCRyZXNpZHVhbHMpKSkKd2VpZ2h0UGxvdCA8LSBxcGxvdCgKICBzZXEoLTUsNSwuMDEpLAogIE1BU1M6OnBzaS5odWJlcihzZXEoLTUsNSwuMDEpKSwKICBnZW9tPSJwYXRoIikgKwogIHhsYWIoInN0YW5kYXJkaXplZCByZXNpZHVhbCIpICsKICB5bGFiKCJ3ZWlnaHQiKSArCiAgdGhlbWVfbWluaW1hbCgpCmBgYAo8L3A+PC9kZXRhaWxzPgoKYGBge3J9CmdyaWQuYXJyYW5nZSh3ZWlnaHRQbG90LHJlc1JvYlBsb3QsbnJvdz0xKQpgYGAKCi0gV2UgY2xlYXJseSBzZWUgdGhhdCB0aGUgd2VpZ2h0cyBpbiB0aGUgTS1lc3RpbWF0aW9uIHByb2NlZHVyZSB3aWxsIGRvd24td2VpZ2h0IGVycm9ycyBhc3NvY2lhdGVkIHdpdGggb3V0bGllcnMgZm9yIHBlcHRpZGUgS0lFRUlBQUsuCgo8ZGV0YWlscz48c3VtbWFyeT4gQ2xpY2sgdG8gc2VlIGNvZGUgdG8gbWFrZSBwbG90IDwvc3VtbWFyeT48cD4KYGBge3J9CnN1bU1lYW5QZXBSb2IgPC0gZGF0YS5mcmFtZSgKICBpbnRlbnNpdHk9c3VtTWVhblBlcFJvYk1vZCRjb2VmW2dyZXAoInNhbXBsZSIsbmFtZXMoc3VtTWVhblBlcFJvYk1vZCRjb2VmKSldICsgbWVhbihkYXRhJGludGVuc2l0eSkgLSBtZWFuKHN1bU1lYW5QZXBSb2JNb2QkY29lZltncmVwKCJzYW1wbGUiLG5hbWVzKHN1bU1lYW5QZXBSb2JNb2QkY29lZikpXSksCiAgY29uZGl0aW9uPSBuYW1lcyhzdW1NZWFuUGVwUm9iTW9kJGNvZWYpW2dyZXAoInNhbXBsZSIsbmFtZXMoc3VtTWVhblBlcFJvYk1vZCRjb2VmKSldICU+JSBzdWJzdHIoMTgsMTgpICU+JSBhcy5mYWN0b3IgKQoKc3VtUmxtUGxvdCA8LSBzdW1QbG90ICsgZ2VvbV9obGluZSgKICAgIGRhdGE9c3VtTWVhblBlcFJvYiwKICAgIG1hcHBpbmc9YWVzKHlpbnRlcmNlcHQ9aW50ZW5zaXR5LGNvbG9yPWNvbmRpdGlvbikpICsKICAgIGdndGl0bGUoIlJvYnVzdCIpCmBgYAo8L3A+PC9kZXRhaWxzPgoKYGBge3J9CiBncmlkLmFycmFuZ2Uoc3VtTG1QbG90ICsgZ2d0aXRsZSgiT0xTIiksIHN1bVJsbVBsb3QsIG5yb3cgPSAxKQpgYGAKCi0gUm9idXN0IHJlZ3Jlc2lvbiByZXN1bHRzIGluIGEgYmV0dGVyIHNlcGFyYXRpb24gYmV0d2VlbiB0aGUgcHJvdGVpbiBleHByZXNzaW9uIHZhbHVlcyBmb3IgdGhlIGRpZmZlcmVudCBzYW1wbGVzIGFjY29yZGluZyB0byB0aGVpciBzcGlrZS1pbiBjb25jZW50cmF0aW9uLgoKCiMgRXN0aW1hdGlvbiBvZiBkaWZmZXJlbnRpYWwgYWJ1bmRhbmNlIHVzaW5nIHBlcHRpZGUgbGV2ZWwgbW9kZWwKCi0gSW5zdGVhZCBvZiBzdW1tYXJpc2luZyB0aGUgZGF0YSB3ZSBjYW4gYWxzbyBkaXJlY3RseSBtb2RlbCB0aGUgZGF0YSBhdCB0aGUgcGVwdGlkZS1sZXZlbC4KLSBCdXQsIHdlIHdpbGwgaGF2ZSB0byBhZGRyZXNzIHRoZSBwc2V1ZG8tcmVwbGljYXRpb24uCgoKJHlfe2ljbHB9PSBcYmV0YV8wICsgXGJldGFfY15cdGV4dHtjb25kaXRpb259ICsgXGJldGFfbF5cdGV4dHtsYWJ9ICsgXGJldGFfcF5cdGV4dHtwZXB0aWRlfSArIHVfc15cdGV4dHtzYW1wbGV9ICsgXGVwc2lsb25fe2ljbHB9JAoKCi0gcHJvdGVpbi1sZXZlbAoKICAtICRcYmV0YV5cdGV4dHtjb25kaXRpb259X2MkOiBzcGlrZS1pbiBjb25kaXRpb24gJGM9YiwgXGxkb3RzLCBlJAogIC0gJFxiZXRhXlx0ZXh0e2xhYn1fbCQ6IGxhYiBlZmZlY3QgJGw9bF8yXGxkb3RzIGxfMyQKICAtICR1X3tyfV5cdGV4dHtydW59XHNpbSBOXGxlZnQoMCxcc2lnbWFeMl9cdGV4dHtydW59XHJpZ2h0KSBccmlnaHRhcnJvdyQgcmFuZG9tIGVmZmVjdCBhZGRyZXNzZXMgcHNldWRvLXJlcGxpY2F0aW9uCgotIHBlcHRpZGUtbGV2ZWwKICAtICRcYmV0YV97cH1eXHRleHR7cGVwdGlkZX0kOiBwZXB0aWRlIGVmZmVjdAogIC0gJFxlcHNpbG9uX3tycH0gXHNpbSBOXGxlZnQoMCxcc2lnbWFeMl97XGVwc2lsb259XHJpZ2h0KSQgd2l0aGluIHNhbXBsZSAocnVuKSBlcnJvcgoKCi0gREEgZXN0aW1hdGVzOgoKJFxsb2dfMkZDX3tCLUF9PVxiZXRhXlx0ZXh0e2NvbmRpdGlvbn1fQiQKCiRcbG9nXzJGQ197Qy1CfT1cYmV0YV5cdGV4dHtjb25kaXRpb259X0MgLSBcYmV0YV5cdGV4dHtjb25kaXRpb259X0IkCgotIE1peGVkIHBlcHRpZGUtbGV2ZWwgbW9kZWxzIGFyZSBpbXBsZW1lbnRlZCBpbiBtc3Fyb2IyCgotIEl0IGhhcyB0aGUgYWR2YW50YWdlcyB0aGF0CgogIDEuIGl0IGNvcnJlY3RseSBhZGRyZXNzZXMgdGhlIGRpZmZlcmVuY2UgbGV2ZWxzIG9mIHZhcmlhYmlsaXR5IGluIHRoZSBkYXRhCiAgMi4gaXQgYXZvaWRzIHN1bW1hcml6YXRpb24gYW5kIHRoZXJlZm9yZSBhbHNvIGFjY291bnRzIGZvciB0aGUgZGlmZmVyZW5jZSBpbiB0aGUgbnVtYmVyIG9mIHBlcHRpZGVzIHRoYXQgYXJlIG9ic2VydmVkIGluIGVhY2ggc2FtcGxlCiAgMy4gbW9yZSBwb3dlcmZ1bCBhbmFseXNpcwoKLSBJdCBoYXMgdGhlIGRpc2FkdmFudGFnZSB0aGF0CgoKICAxLiBwcm90ZWluIHN1bW1hcmllcyBhcmUgbm8gbG9uZ2VyIGF2YWlsYWJsZSBmb3IgcGxvdHRpbmcKICAyLiBpdCBpcyBkaWZmaWN1bHQgdG8gY29ycmVjdGx5IHNwZWNpZnkgdGhlIGRlZ3JlZXMgb2YgZnJlZWRvbSBmb3IgdGhlIHRlc3Qtc3RhdGlzdGljIGxlYWRpbmcgdG8gaW5mZXJlbmNlIHRoYXQgaXMgdG9vIGxpYmVyYWwgaW4gZXhwZXJpbWVudHMgd2l0aCBzbWFsbCBzYW1wbGUgc2l6ZQogIDMuIHNvbWV0aW1lcyBzYW1wbGUgbGV2ZWwgcmFuZG9tIGVmZmVjdCB2YXJpYW5jZSBhcmUgZXN0aW1hdGVkIHRvIGJlIHplcm8sIHRoZW4gdGhlIHBzZXVkby1yZXBsaWNhdGlvbiBpcyBub3QgYWRkcmVzc2VkIGxlYWRpbmcgdG8gaW5mZXJlbmNlIHRoYXQgaXMgdG9vIGxpYmVyYWwgZm9yIHRoZXNlIHNwZWNpZmljIHByb3RlaW5zCiAgNC4gdGhleSBhcmUgbXVjaCBtb3JlIGRpZmZpY3VsdCB0byBkaXNzZW1pbmF0ZSB0byB1c2VycyB3aXRoIGxpbWl0ZWQgYmFja2dyb3VuZCBpbiBzdGF0aXN0aWNzCgpIZW5jZSwgZm9yIHRoaXMgY291cnNlIHdlIG9wdGVkIHRvIHVzZSBwZXB0aWRlLWxldmVsIG1vZGVscyBmb3Igc3VtbWFyaXphdGlvbiwgYnV0IG5vdCBmb3IgZGlyZWN0bHkgaW5mZXJyaW5nIG9uIHRoZSBkaWZmZXJlbnRpYWwgZXhwcmVzc2lvbiBhdCB0aGUgcHJvdGVpbi1sZXZlbC4KCiMgU2Vzc2lvbiBJbmZvCgpXaXRoIHJlc3BlY3QgdG8gcmVwcm9kdWNpYmlsaXR5LCBpdCBpcyBoaWdobHkgcmVjb21tZW5kZWQgdG8gaW5jbHVkZSBhCnNlc3Npb24gaW5mbyBpbiB5b3VyIHNjcmlwdCBzbyB0aGF0IHJlYWRlcnMgb2YgeW91ciBvdXRwdXQgY2FuIHNlZQp5b3VyIHBhcnRpY3VsYXIgc2V0dXAgb2YgUi4KCmBgYHtyfQpzZXNzaW9uSW5mbygpCmBgYAo=