Creative Commons License

This is part of the online course Proteomics Data Analysis (PDA)

1 Intro: Challenges in Label-Free Quantitative Proteomics

1.1 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


1.2 Level of quantification

  • MS-based proteomics returns peptides: pieces of proteins

  • Quantification commonly required on the protein level


1.3 Label-free Quantitative Proteomics Data Analysis Workflows


2 QFeatures

2.1 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.
Conceptual representation of a `SummarizedExperiment` object.  Assays contain information on the measured omics features (rows) for different samples (columns). The `rowData` contains information on the omics features, the `colData` contains information on the samples, i.e. experimental design etc.

Conceptual representation of a SummarizedExperiment object. Assays contain information on the measured omics features (rows) for different samples (columns). The rowData contains information on the omics features, the colData contains information on the samples, i.e. experimental design etc.

  • Assays in a QFeatures object have a hierarchical relation:

    • proteins are composed of peptides,
    • themselves produced by peptide spectrum matches
    • relations between assays are tracked and recorded throughout data processing
Conceptual representation of a `QFeatures` object and the aggregative relation between different assays. Image from the [QFeatures vignette](https://rformassspectrometry.github.io/QFeatures/articles/QFeatures.html)

Conceptual representation of a QFeatures object and the aggregative relation between different assays. Image from the QFeatures vignette

3 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

3.1 Import data in R

3.1.1 Load packages

library(tidyverse)
library(limma)
library(QFeatures)
library(msqrob2)
library(plotly)
library(ggplot2)
library(cowplot)
library(gridExtra)

3.1.2 Import data from the CPTAC study

  1. 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")
  1. 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
  1. 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.

pe_all
## 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:

pe_all[[1]]
## 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):
pe_all[["peptideRaw"]]
## 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):

3.1.3 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:
colData(pe_all)
## DataFrame with 45 rows and 0 columns
pe_all[[1]] %>% colnames
##  [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
colData(pe_all)
## 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

4 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.

4.1 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.
table(na$nNArows$nNA)
## 
##    0    1    2    3    4    5    6 
## 4059  990  884  717  934  807 3075

5 Preprocessing

This section preforms preprocessing for the peptide data. This include

  • log transformation,
  • filtering and
  • summarisation of the data.

5.1 Log transform the data

pe <- logTransform(pe, base = 2, i = "peptideRaw", name = "peptideLog")

5.2 Filtering

  1. 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))
  1. 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
  1. 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.

5.3 Normalize the data using median centering

We normalize the data by substracting the sample median from every intensity for peptide \(p\) in a sample \(i\):

\[y_{ip}^\text{norm} = y_{ip} - \hat\mu_i\]

with \(\hat\mu_i\) the median intensity over all observed peptides in sample \(i\).

pe <- normalize(pe,
                i = "peptideLog",
                name = "peptideNorm",
                method = "center.median")
pe
## An instance of class QFeatures containing 3 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

5.4 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.

6 Median summarization

6.1 Preprocessing

  • We use median summarization in aggregateFeatures.
  • Note, that this is a suboptimal normalisation procedure!
  • By default robust summarization is used: fun = MsCoreUtils::robustSummary()
pe <- aggregateFeatures(pe,
  i = "peptideNorm",
  fcol = "Proteins",
  na.rm = TRUE,
  name = "proteinMedian",
  fun = matrixStats::colMedians)
## 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.
pe
## An instance of class QFeatures containing 4 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
tmp <- assay(pe[["proteinMedian"]])
colnames(tmp) <- str_replace_all(colnames(tmp), "Intensity.6","")
tmp %>%
  limma::plotMDS(col = as.numeric(colData(pe)$condition))

6.2 Data Analysis

6.2.1 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 = "proteinMedian",
             formula = ~condition,
             overwrite = TRUE)
rowData(pe[["proteinMedian"]])[, c("Proteins", ".n", "msqrobModels")]
## DataFrame with 1389 rows and 3 columns
##                                Proteins        .n       msqrobModels
##                             <character> <integer>             <list>
## O00762ups|UBE2C_HUMAN_UPS O00762ups|...         2      StatModel:rlm
## P00167ups|CYB5_HUMAN_UPS  P00167ups|...         1 StatModel:fitError
## P00441ups|SODC_HUMAN_UPS  P00441ups|...         3      StatModel:rlm
## P00709ups|LALBA_HUMAN_UPS P00709ups|...         3      StatModel:rlm
## P00915ups|CAH1_HUMAN_UPS  P00915ups|...         1 StatModel:fitError
## ...                                 ...       ...                ...
## sp|Q99258|RIB3_YEAST      sp|Q99258|...         4      StatModel:rlm
## sp|Q99260|YPT6_YEAST      sp|Q99260|...         1 StatModel:fitError
## sp|Q99287|SEY1_YEAST      sp|Q99287|...         1      StatModel:rlm
## sp|Q99383|HRP1_YEAST      sp|Q99383|...         3      StatModel:rlm
## sp|Q99385|VCX1_YEAST      sp|Q99385|...         1 StatModel:fitError

6.2.2 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[["proteinMedian"]])$msqrobModels[[1]])
## (Intercept)  conditionB 
##   -2.793005    1.541958

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 = "proteinMedian", contrast = L)

6.2.3 Plots

6.2.3.1 Volcano-plot

tmp <- rowData(pe[["proteinMedian"]])$conditionB[complete.cases(rowData(pe[["proteinMedian"]])$conditionB),]
tmp$shapes <- 16

volcanoMedian<- 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("Median: 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)))
volcanoMedian

Note, that only 2 proteins are found to be differentially abundant.

6.2.3.2 Heatmap

We first select the names of the proteins that were declared significant

sigNames <- rowData(pe[["proteinMedian"]])$conditionB %>%
  rownames_to_column("proteinMedian") %>%
  filter(adjPval<0.05) %>%
  pull(proteinMedian)
 heatmap(assay(pe[["proteinMedian"]])[sigNames, ],cexRow = 1, cexCol = 1)

 sigProteins <- rowData(pe[["proteinMedian"]])$conditionB %>%
  rownames_to_column("proteinMedian") %>%
   filter(grepl("UPS",proteinMedian)) %>%
  pull(proteinMedian)
 heatmap(assay(pe[["proteinMedian"]])[sigProteins, ], 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.

6.2.3.3 Boxplots

We create a boxplot of the log2 FC and group according to the whether a protein is spiked or not.

rowData(pe[["proteinMedian"]])$conditionB %>%
  rownames_to_column(var = "protein") %>%
  mutate(ups=grepl("UPS",protein)) %>%
  ggplot(aes(x=ups, 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 166 rows containing non-finite values (stat_boxplot).

7 Robust summarization

7.1 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.

pe
## 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.

7.2 Data Analysis

7.2.1 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)

7.2.2 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)

7.2.3 Plots

7.2.3.1 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.

7.2.3.2 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")

7.2.3.3 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).

8 Where the difference comes from

8.1 Import data from the full CPTAC study

Click to see background and code

  1. 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"
  1. 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)))
  1. Read the data and store it in QFeatures object
pe <- readQFeatures(
  table = peptidesFile,
  fnames = 1,
  ecol = ecols,
  name = "peptideRaw", sep="\t")

8.1.1 Design

Click to see background and code

pe %>% colnames
## 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)
  • We explore the colData
colData(pe)
## 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

8.1.2 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")

  1. 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))
  1. 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 != "+")
  1. 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.

8.1.3 Normalization

Click to see R-code to normalize the data

pe <- normalize(pe,
                i = "peptideLog",
                name = "peptideNorm",
                method = "center.median")


8.2 Peptide-Level view

8.2.1 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)

8.2.1.1 Median summarization

We first evaluate median summarization for protein P01031ups|CO5_HUMAN_UPS.

Click to see code to make plot

dataHlp <- pe[["peptideNorm"]][
    rowData(pe[["peptideNorm"]])$Proteins == prot,
    colData(pe)$lab=="lab3"] %>% assay

sumMedian <- data.frame(
  intensity= dataHlp
    %>% colMedians(na.rm=TRUE)
  ,
  condition= colnames(dataHlp) %>% substr(12,12) %>% as.factor )

sumMedianPlot <- sumPlot +
  geom_hline(
    data = sumMedian,
    mapping = aes(yintercept=intensity,color=condition)) +
  ggtitle("Median summarization")

sumMedianPlot
## Warning: Removed 1 rows containing missing values (geom_hline).

  • The sample medians are not a good estimate for the protein expression value.
  • Indeed, they do not account for differences in peptide effects
  • Peptides that ionize poorly are also picked up in samples with high spike-in concencentration and not in samples with low spike-in concentration
  • This introduces a bias.

8.2.1.2 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)

8.2.1.3 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.

9 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

    1. it correctly addresses the difference levels of variability in the data
    2. it avoids summarization and therefore also accounts for the difference in the number of peptides that are observed in each sample
    3. more powerful analysis
  • It has the disadvantage that

    1. protein summaries are no longer available for plotting
    2. 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
    3. 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
    4. 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.

10 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.

sessionInfo()
## 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=