Creative Commons License

This is part of the online course Experimental Design and Data-Analysis in Label-Free Quantitative LC/MS Proteomics - A Tutorial with msqrob2 (hupo21)

Click to see libraries that are loaded

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

1 Overview

  • Sample size
  • Randomized Complete Block Designs

2 Sample size

2.1 Statistical models

  • Upon preprocessing and summarization we model the log2 transformed protein expression (\(y_{i}\)) values using a linear model

\[y_{i}= \beta_0 + \beta_1 x_{i,1} + \beta x_{i,2} + ... + \epsilon_i\]

with

  • \(\beta_0\) the intercept
  • \(\beta_j\) the slope for predictor \(x_{i,j}\)
  • \(x_{i,j}\) a continuous predictor (such as age) or a dummy variable that can take values of 0 and 1 and that will used for factors (e.g treatment: cancer, normal).

Example:

  • Estrogen Receptor Positive Breast cancer tissues from from patients treated with tamoxifen upon recurrence have been assessed in a proteomics study.
  • Half of the patients had a good outcome (or) and the other half had a poor outcome (pd).
  • The proteomes have been assessed using an LTQ-Orbitrap and the thermo output .RAW files were searched with MaxQuant (version 1.4.1.2) against the human proteome database (FASTA version 2012-09, human canonical proteome).

We model the data for a single protein using a model:

\[y_{i}= \beta_0 + \beta_{PD} x_{i,PD} + \epsilon_i\]

with \(x_{i,PD}=\begin{cases} 0& \text{good outcome}\\ 1& \text{poor outcome} \end{cases}\).

2.1.1 Read data

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/pda21/data/quantification/cancer/peptides3vs3.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")

2.1.2 Design

Click to see background and code

pe %>% colnames
## CharacterList of length 1
## [["peptideRaw"]] Intensity.OR.01 Intensity.OR.04 ... Intensity.PD.04
  • Note, that the sample names the outcome.
  • We update the colData with information on the design
colData(pe)$prognosis <-  pe[["peptideRaw"]] %>% 
  colnames %>% 
  substr(11,12) %>% 
  as.factor
  • We explore the colData
colData(pe)
## DataFrame with 6 rows and 1 column
##                 prognosis
##                  <factor>
## Intensity.OR.01        OR
## Intensity.OR.04        OR
## Intensity.OR.07        OR
## Intensity.PD.02        PD
## Intensity.PD.03        PD
## Intensity.PD.04        PD

2.1.3 Preprocessing

Click to see code for preprocessing

  1. Log-transform
  • 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. Filtering
Click to see code to filter the data

  • 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,~ 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] 22413

We keep 22413 peptides upon filtering.

  1. Normalization
pe <- normalize(pe, 
                i = "peptideLog", 
                name = "peptideNorm", 
                method = "center.median")
  1. Summarization
pe <- aggregateFeatures(pe,
  i = "peptideNorm",
  fcol = "Proteins",
  na.rm = TRUE,
  name = "protein")

2.2 Effect size?

library(ExploreModelMatrix)
VisualizeDesign(colData(pe),designFormula = ~prognosis)$plotlist
## [[1]]

\[ \begin{array}{rclcl} E[Y|OR] &=& \beta_0\\ E[Y|PD] &=& \beta_0 + \beta_{PD}\\ \log_2 FC_{PD-OR} &=& \beta_0 + \beta_{PD} - \beta_0 &=&\beta_{PD} \end{array} \]

2.3 Statistical Inference

2.3.1 Hypotheses

We want to find proteins that are differential abundant

\(\rightarrow\) use a statistical test

We typically start from the alternative hypothesis

  • \(H_1\): \(\log_2 FC_{PD-OR} \neq 0\) or \(\beta_{PD} \neq 0\)

But we can not use data to prove a hypothesis, we therefore falsify the opposite:

  • \(H_0\): \(\log_2 FC_{PD-OR} = 0\) or \(\beta_{PD} = 0\)

2.3.2 Test statistic:

  • T-test on the model parameter \(\beta_{PD}\)

\[T = \frac{\hat\beta_{PD} - 0}{\text{se}_{\hat\beta_{PD}}}\]

which follows a t-distribution under \(H_0\) if the errors are \[ \epsilon_i \text{ i.i.d. } N(0,\sigma^2) \]

  • p-value: probability to observe a t-statistic that is as extreme or more extreme that the one we observed for this protein in our sample if we would repeat the experiment and if the protein is not differentially abundant.

2.3.3 Multiple testing

  • We do this test for all proteins (typically thousands of them)

  • Adjust p-values for multiple testing using the false discovery rate \[FDR = E\left[\frac{FP}{FP + TP}\right]\]

  • Empirical Bayes variance estimation: Note, that the massive parallel data structure also allows you to stabilize the variance estimation by borrowing information across proteins!

Note, if you want to refresh some fundamental concepts of hypothesis testing:

2.4 Experiment with 3 vs 3 comparison

Click to see code to inference

pe <- msqrob(object = pe, i = "protein", formula = ~prognosis)
L <- makeContrast("prognosisPD=0", parameterNames = c("prognosisPD"))
pe <- hypothesisTest(object = pe, i = "protein", contrast = L)
volcano3x3 <- ggplot(rowData(pe[["protein"]])$prognosisPD %>% na.exclude,
                  aes(x = logFC, y = -log10(pval), color = adjPval < 0.05)) +
  geom_point(cex = 2.5) +
  scale_color_manual(values = alpha(c("black", "red"), 0.5)) + 
  theme_minimal() +
  ggtitle(paste0(sum(rowData(pe[["protein"]])$prognosisPD$adjPval<0.05,na.rm=TRUE)," proteins are found to be DA"))

volcano3x3

Upon correction for multiple testing with using the false discovery rate (FDR) method no proteins are found to be differentially expressed.

Note, that you can refresh the concept of multiple testing and FDR [here] (https://statomics.github.io/PDA21/pda_quantification_inference.html#14_Multiple_hypothesis_testing).

2.5 Experiment with a 6 vs 6 comparison

2.5.1 Read data

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/pda21/data/quantification/cancer/peptides6vs6.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")

2.5.2 Design

Click to see background and code

pe %>% colnames
## CharacterList of length 1
## [["peptideRaw"]] Intensity.OR.01 Intensity.OR.04 ... Intensity.PD.08
  • Note, that the sample names the outcome.
  • We update the colData with information on the design
colData(pe)$prognosis <-  pe[["peptideRaw"]] %>% 
  colnames %>% 
  substr(11,12) %>% 
  as.factor
  • We explore the colData
colData(pe)
## DataFrame with 12 rows and 1 column
##                 prognosis
##                  <factor>
## Intensity.OR.01        OR
## Intensity.OR.04        OR
## Intensity.OR.07        OR
## Intensity.OR.09        OR
## Intensity.OR.10        OR
## ...                   ...
## Intensity.PD.03        PD
## Intensity.PD.04        PD
## Intensity.PD.06        PD
## Intensity.PD.07        PD
## Intensity.PD.08        PD

2.5.3 Preprocessing

Click to see code for preprocessing

  1. Log-transform
  • 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. Filtering
Click to see code to filter the data

  • 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,~ 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] 25452

We keep 25452 peptides upon filtering.

  1. Normalization
pe <- normalize(pe, 
                i = "peptideLog", 
                name = "peptideNorm", 
                method = "center.median")
  1. Summarization
pe <- aggregateFeatures(pe,
  i = "peptideNorm",
  fcol = "Proteins",
  na.rm = TRUE,
  name = "protein")

2.5.4 Statistical Inference

Click to see code to inference

pe <- msqrob(object = pe, i = "protein", formula = ~prognosis)
L <- makeContrast("prognosisPD=0", parameterNames = c("prognosisPD"))
pe <- hypothesisTest(object = pe, i = "protein", contrast = L)
volcano6x6 <- ggplot(rowData(pe[["protein"]])$prognosisPD %>% na.exclude,
                  aes(x = logFC, y = -log10(pval), color = adjPval < 0.05)) +
  geom_point(cex = 2.5) +
  scale_color_manual(values = alpha(c("black", "red"), 0.5)) + 
  theme_minimal() +
  ggtitle(paste0(sum(rowData(pe[["protein"]])$prognosisPD$adjPval<0.05,na.rm=TRUE)," proteins are found to be DA"))

volcano6x6

2.6 Experiment with a 9 vs 9 comparison

2.6.1 Read data

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/pda21/data/quantification/cancer/peptides9vs9.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")

2.6.2 Design

Click to see background and code

pe %>% colnames
## CharacterList of length 1
## [["peptideRaw"]] Intensity.OR.01 Intensity.OR.04 ... Intensity.PD.11
  • Note, that the sample names the outcome.
  • We update the colData with information on the design
colData(pe)$prognosis <-  pe[["peptideRaw"]] %>% 
  colnames %>% 
  substr(11,12) %>% 
  as.factor
  • We explore the colData
colData(pe)
## DataFrame with 18 rows and 1 column
##                 prognosis
##                  <factor>
## Intensity.OR.01        OR
## Intensity.OR.04        OR
## Intensity.OR.07        OR
## Intensity.OR.09        OR
## Intensity.OR.10        OR
## ...                   ...
## Intensity.PD.07        PD
## Intensity.PD.08        PD
## Intensity.PD.09        PD
## Intensity.PD.10        PD
## Intensity.PD.11        PD

2.6.3 Preprocessing

Click to see code for preprocessing

  1. Log-transform
  • 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. Filtering
Click to see code to filter the data

  • 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,~ 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] 26696

We keep 26696 peptides upon filtering.

  1. Normalization
pe <- normalize(pe, 
                i = "peptideLog", 
                name = "peptideNorm", 
                method = "center.median")
  1. Summarization
pe <- aggregateFeatures(pe,
  i = "peptideNorm",
  fcol = "Proteins",
  na.rm = TRUE,
  name = "protein")

2.6.4 Statistical Inference

Click to see code to inference

pe <- msqrob(object = pe, i = "protein", formula = ~prognosis)
L <- makeContrast("prognosisPD=0", parameterNames = c("prognosisPD"))
pe <- hypothesisTest(object = pe, i = "protein", contrast = L)
volcano9x9 <- ggplot(rowData(pe[["protein"]])$prognosisPD %>% na.exclude,
                  aes(x = logFC, y = -log10(pval), color = adjPval < 0.05)) +
  geom_point(cex = 2.5) +
  scale_color_manual(values = alpha(c("black", "red"), 0.5)) + 
  theme_minimal() +
  ggtitle(paste0(sum(rowData(pe[["protein"]])$prognosisPD$adjPval<0.05,na.rm=TRUE)," proteins are found to be DA"))

volcano9x9

  • We have seen that the sample size is key to recover DA proteins

  • Indeed, if a protein is differentially expressed, the value of T-test depends on the effect size, the variability of the protein expression values and the sample size.

\[ T_g=\frac{\log_2 \text{FC}}{\text{se}_{\log_2 \text{FC}}} \]

\[ T_g=\frac{\widehat{\text{signal}}}{\widehat{\text{Noise}}} \]

For a two group comparison the standard error on the fold change equals

\[ \text{se}_{\log_2 \text{FC}}=\text{SD}\sqrt{\frac{1}{n_1}+\frac{1}{n_2}} \]

\(\rightarrow\) if number of bio-repeats increases we have a higher power!

3 Randomized complete block designs

\[\sigma^2= \sigma^2_{bio}+\sigma^2_\text{lab} +\sigma^2_\text{extraction} + \sigma^2_\text{run} + \ldots\]

  • Biological: fluctuations in protein level between mice, fluctations in protein level between cells, …
  • Technical: cage effect, lab effect, week effect, plasma extraction, MS-run, …

3.1 Nature methods: Points of significance - Blocking

https://www.nature.com/articles/nmeth.3005.pdf

3.2 Mouse example

Duguet et al. (2017) MCP 16(8):1416-1432. doi: 10.1074/mcp.m116.062745

  • All treatments of interest are present within block!
  • We can estimate the effect of the treatment within block!

To illustrate the power of blocking we have subsetted the data of Duguet et al. in a

  • completely randomized design with

    • four mice for which we only have measurements on the ordinary T-cells
    • four mice for which we only have measurements on the regulatory T-cells
  • randomized complete block design with four mice for which we both have

    • measurements on ordinary T-cells as well as
    • measurements on regulatory T-cells

3.2.1 Data

Click to see code

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

peptidesFile <- "https://raw.githubusercontent.com/statOmics/PDA21/data/quantification/mouseTcell/peptidesRCB.txt"
peptidesFile2 <- "https://raw.githubusercontent.com/statOmics/PDA21/data/quantification/mouseTcell/peptidesCRD.txt"
peptidesFile3 <- "https://raw.githubusercontent.com/statOmics/PDA21/data/quantification/mouseTcell/peptides.txt"

ecols <- grep("Intensity\\.", names(read.delim(peptidesFile)))
pe <- readQFeatures(
  table = peptidesFile,
  fnames = 1,
  ecol = ecols,
  name = "peptideRaw", sep="\t")

ecols2 <- grep("Intensity\\.", names(read.delim(peptidesFile2)))
pe2 <- readQFeatures(
  table = peptidesFile2,
  fnames = 1,
  ecol = ecols2,
  name = "peptideRaw", sep="\t")

ecols3 <- grep("Intensity\\.", names(read.delim(peptidesFile3)))
pe3 <- readQFeatures(
  table = peptidesFile3,
  fnames = 1,
  ecol = ecols3,
  name = "peptideRaw", sep="\t")

### Design
colData(pe)$celltype <- substr(
  colnames(pe[["peptideRaw"]]),
  11,
  14) %>%
  unlist %>%  
  as.factor

colData(pe)$mouse <- pe[[1]] %>%
  colnames %>%
  strsplit(split="[.]")  %>%
  sapply(function(x) x[3]) %>%
  as.factor

colData(pe2)$celltype <- substr(
  colnames(pe2[["peptideRaw"]]),
  11,
  14) %>%
  unlist %>%  
  as.factor

colData(pe2)$mouse <- pe2[[1]] %>%
  colnames %>%
  strsplit(split="[.]")  %>%
  sapply(function(x) x[3]) %>%
  as.factor

colData(pe3)$celltype <- substr(
  colnames(pe3[["peptideRaw"]]),
  11,
  14) %>%
  unlist %>%  
  as.factor

colData(pe3)$mouse <- pe3[[1]] %>%
  colnames %>%
  strsplit(split="[.]")  %>%
  sapply(function(x) x[3]) %>%
  as.factor

3.2.2 Preprocessing

3.2.2.1 Log-transform

Click to see code to log-transfrom 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)

rowData(pe2[["peptideRaw"]])$nNonZero <- rowSums(assay(pe2[["peptideRaw"]]) > 0)

rowData(pe3[["peptideRaw"]])$nNonZero <- rowSums(assay(pe3[["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

pe2 <- zeroIsNA(pe2, "peptideRaw") # convert 0 to NA

pe3 <- zeroIsNA(pe3, "peptideRaw") # convert 0 to NA
  • Logtransform data with base 2
pe <- logTransform(pe, base = 2, i = "peptideRaw", name = "peptideLog")

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

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

3.2.2.2 Filtering

Click to see details on 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))

pe2 <- filterFeatures(pe2, ~ Proteins %in% smallestUniqueGroups(rowData(pe2[["peptideLog"]])$Proteins))

pe3 <- filterFeatures(pe3, ~ Proteins %in% smallestUniqueGroups(rowData(pe3[["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 != "+")

pe2 <- filterFeatures(pe2,~Reverse != "+")
pe2 <- filterFeatures(pe2,~ Potential.contaminant != "+")

pe3 <- filterFeatures(pe3,~Reverse != "+")
pe3 <- filterFeatures(pe3,~ 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] 44449
pe2 <- filterFeatures(pe2,~ nNonZero >=2)
nrow(pe2[["peptideLog"]])
## [1] 43401
pe3 <- filterFeatures(pe3,~ nNonZero >=2)
nrow(pe3[["peptideLog"]])
## [1] 47431

3.2.2.3 Normalization

Click to see code to normalize the data

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

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


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

3.2.2.4 Summarization

Click to see code to summarize the data

pe <- aggregateFeatures(pe,
 i = "peptideNorm",
 fcol = "Proteins",
 na.rm = TRUE,
 name = "protein")
## Your quantitative and row data contain missing values. Please read the
## relevant section(s) in the aggregateFeatures manual page regarding the
## effects of missing values on data aggregation.
pe2 <- aggregateFeatures(pe2,
 i = "peptideNorm",
 fcol = "Proteins",
 na.rm = TRUE,
 name = "protein")
## Your quantitative and row data contain missing values. Please read the
## relevant section(s) in the aggregateFeatures manual page regarding the
## effects of missing values on data aggregation.
pe3 <- aggregateFeatures(pe3,
 i = "peptideNorm",
 fcol = "Proteins",
 na.rm = TRUE,
 name = "protein")
## Your quantitative and row data contain missing values. Please read the
## relevant section(s) in the aggregateFeatures manual page regarding the
## effects of missing values on data aggregation.

3.2.3 Data Exploration: what is impact of blocking?

Click to see code

levels(colData(pe3)$mouse) <- paste0("m",1:7)
mdsObj3 <- plotMDS(assay(pe3[["protein"]]), plot = FALSE)
mdsOrig <- colData(pe3) %>%
  as.data.frame %>%
  mutate(mds1 = mdsObj3$x,
         mds2 = mdsObj3$y,
         lab = paste(mouse,celltype,sep="_")) %>%
  ggplot(aes(x = mds1, y = mds2, label = lab, color = celltype, group = mouse)) +
  geom_text(show.legend = FALSE) +
  geom_point(shape = 21) +
  geom_line(color = "black", linetype = "dashed") +
  xlab(
    paste0(
      mdsObj3$axislabel,
      " ",
      1, 
      " (",
      paste0(
        round(mdsObj3$var.explained[1] *100,0),
        "%"
        ),
      ")"
      )
    ) +
  ylab(
    paste0(
      mdsObj3$axislabel,
      " ",
      2, 
      " (",
      paste0(
        round(mdsObj3$var.explained[2] *100,0),
        "%"
        ),
      ")"
      )
    ) +
  ggtitle("Original (RCB)")

levels(colData(pe)$mouse) <- paste0("m",1:4)
mdsObj <- plotMDS(assay(pe[["protein"]]), plot = FALSE)
mdsRCB <- colData(pe) %>%
  as.data.frame %>%
  mutate(mds1 = mdsObj$x,
         mds2 = mdsObj$y,
         lab = paste(mouse,celltype,sep="_")) %>%
  ggplot(aes(x = mds1, y = mds2, label = lab, color = celltype, group = mouse)) +
  geom_text(show.legend = FALSE) +
  geom_point(shape = 21) +
  geom_line(color = "black", linetype = "dashed") +
  xlab(
    paste0(
      mdsObj$axislabel,
      " ",
      1, 
      " (",
      paste0(
        round(mdsObj$var.explained[1] *100,0),
        "%"
        ),
      ")"
      )
    ) +
  ylab(
    paste0(
      mdsObj$axislabel,
      " ",
      2, 
      " (",
      paste0(
        round(mdsObj$var.explained[2] *100,0),
        "%"
        ),
      ")"
      )
    ) +
  ggtitle("Randomized Complete Block (RCB)")


levels(colData(pe2)$mouse) <- paste0("m",1:8)
mdsObj2 <- plotMDS(assay(pe2[["protein"]]), plot = FALSE)
mdsCRD <- colData(pe2) %>%
  as.data.frame %>%
  mutate(mds1 = mdsObj2$x,
         mds2 = mdsObj2$y,
         lab = paste(mouse,celltype,sep="_")) %>%
  ggplot(aes(x = mds1, y = mds2, label = lab, color = celltype, group = mouse)) +
  geom_text(show.legend = FALSE) +
  geom_point(shape = 21) +
  xlab(
    paste0(
      mdsObj$axislabel,
      " ",
      1, 
      " (",
      paste0(
        round(mdsObj2$var.explained[1] *100,0),
        "%"
        ),
      ")"
      )
    ) +
  ylab(
    paste0(
      mdsObj$axislabel,
      " ",
      2, 
      " (",
      paste0(
        round(mdsObj2$var.explained[2] *100,0),
        "%"
        ),
      ")"
      )
    ) +
  ggtitle("Completely Randomized Design (CRD)")

mdsOrig

mdsRCB

mdsCRD

  • We observe that the leading fold change is according to mouse

  • In the second dimension we see a separation according to cell-type

  • With the Randomized Complete Block design (RCB) we can remove the mouse effect from the analysis!

  • We can isolate the between block variability from the analysis using linear model:

    • Formula in R \[ y \sim \text{celltype} + \text{mouse} \]

    • Formula

\[ y_i = \beta_0 + \beta_\text{Treg} x_{i,\text{Treg}} + \beta_{m2}x_{i,m2} + \beta_{m3}x_{i,m3} + \beta_{m4}x_{i,m4} + \epsilon_i \]

with

  • \(x_{i,Treg}=\begin{cases} 1& \text{Treg}\\ 0& \text{Tcon} \end{cases}\)

  • \(x_{i,m2}=\begin{cases} 1& \text{m2}\\ 0& \text{otherwise} \end{cases}\)

  • \(x_{i,m3}=\begin{cases} 1& \text{m3}\\ 0& \text{otherwise} \end{cases}\)

  • \(x_{i,m4}=\begin{cases} 1& \text{m4}\\ 0& \text{otherwise} \end{cases}\)

  • Possible in msqrob2 and MSstats but not possible with Perseus!

3.3 Modeling and inference

3.3.1 RCB analysis

pe <- msqrob(
  object = pe,
  i = "protein",
  formula = ~ celltype + mouse)

3.3.2 CRD analysis

pe2 <- msqrob(
  object = pe2,
  i = "protein",
  formula = ~ celltype)

3.3.3 Estimation, effect size and inference

Effect size in RCB

library(ExploreModelMatrix)
VisualizeDesign(colData(pe),~ celltype + mouse)$plotlist
## [[1]]

Effect size in CRD

VisualizeDesign(colData(pe2),~ celltype)$plotlist
## [[1]]

Click to see code for statistical inference

L <- makeContrast("celltypeTreg = 0", parameterNames = c("celltypeTreg"))
pe <- hypothesisTest(object = pe, i = "protein", contrast = L)
pe2 <- hypothesisTest(object = pe2, i = "protein", contrast = L)

3.3.4 Comparison of results

Click to see code

3.3.5 Comparison of standard deviation

Click to see code

accessions <- rownames(pe[["protein"]])[rownames(pe[["protein"]])%in%rownames(pe2[["protein"]])]
dat <- data.frame(
sigmaRBC = sapply(rowData(pe[["protein"]])$msqrobModels[accessions], getSigmaPosterior),
sigmaCRD <- sapply(rowData(pe2[["protein"]])$msqrobModels[accessions], getSigmaPosterior)
)

plotRBCvsCRD <- ggplot(data = dat, aes(sigmaRBC, sigmaCRD)) +
    geom_point(alpha = 0.1, shape = 20) +
    scale_x_log10() +
    scale_y_log10() +
    geom_abline(intercept=0,slope=1)

  plotRBCvsCRD
## Warning: Removed 743 rows containing missing values (geom_point).

  • We clearly observe that the standard deviation of the protein expression in the RCB is smaller for the majority of the proteins than that obtained with the CRD

  • Why are some of the standard deviations for the RCB with the correct analysis larger than than of the RCB with the incorrect analysis that ignored the mouse blocking factor?

  • Can you think of a reason why it would not be useful to block on a particular factor?

LS0tCnRpdGxlOiAiIEV4cGVyaW1lbnRhbCBkZXNpZ24gY29uY2VwdHMgZm9yIGxhYmVsLWZyZWUgcHJvdGVvbWljcyBleHBlcmltZW50cyIKYXV0aG9yOiAiTGlldmVuIENsZW1lbnQiCmRhdGU6ICJbc3RhdE9taWNzXShodHRwczovL3N0YXRvbWljcy5naXRodWIuaW8pLCBHaGVudCBVbml2ZXJzaXR5IgpvdXRwdXQ6CiAgICBodG1sX2RvY3VtZW50OgogICAgICBjb2RlX2Rvd25sb2FkOiB0cnVlCiAgICAgIHRoZW1lOiBjb3NtbwogICAgICB0b2M6IHRydWUKICAgICAgdG9jX2Zsb2F0OiB0cnVlCiAgICAgIGhpZ2hsaWdodDogdGFuZ28KICAgICAgbnVtYmVyX3NlY3Rpb25zOiB0cnVlCiAgICBwZGZfZG9jdW1lbnQ6CiAgICAgIHRvYzogdHJ1ZQogICAgICBudW1iZXJfc2VjdGlvbnM6IHRydWUKbGlua2NvbG9yOiBibHVlCnVybGNvbG9yOiBibHVlCmNpdGVjb2xvcjogYmx1ZQoKYmlibGlvZ3JhcGh5OiBtc3Fyb2IyLmJpYgogICAgICAKLS0tCgo8YSByZWw9ImxpY2Vuc2UiIGhyZWY9Imh0dHBzOi8vY3JlYXRpdmVjb21tb25zLm9yZy9saWNlbnNlcy9ieS1uYy1zYS80LjAiPjxpbWcgYWx0PSJDcmVhdGl2ZSBDb21tb25zIExpY2Vuc2UiIHN0eWxlPSJib3JkZXItd2lkdGg6MCIgc3JjPSJodHRwczovL2kuY3JlYXRpdmVjb21tb25zLm9yZy9sL2J5LW5jLXNhLzQuMC84OHgzMS5wbmciIC8+PC9hPgoKVGhpcyBpcyBwYXJ0IG9mIHRoZSBvbmxpbmUgY291cnNlIFtFeHBlcmltZW50YWwgRGVzaWduIGFuZCBEYXRhLUFuYWx5c2lzIGluIExhYmVsLUZyZWUgUXVhbnRpdGF0aXZlIExDL01TIFByb3Rlb21pY3MgLSBBIFR1dG9yaWFsIHdpdGggbXNxcm9iMiAoaHVwbzIxKV0oaHR0cHM6Ly9zdGF0b21pY3MuZ2l0aHViLmlvL2h1cG8yMS8pCgoKCjxpZnJhbWUgd2lkdGg9IjU2MCIgaGVpZ2h0PSIzMTUiCnNyYz0iaHR0cHM6Ly93d3cueW91dHViZS5jb20vZW1iZWQvMW12SGFwTG1yYzQiCmZyYW1lYm9yZGVyPSIwIgpzdHlsZT0iZGlzcGxheTogYmxvY2s7IG1hcmdpbjogYXV0bzsiCmFsbG93PSJhdXRvcGxheTsgZW5jcnlwdGVkLW1lZGlhIiBhbGxvd2Z1bGxzY3JlZW4+PC9pZnJhbWU+CgoKPGRldGFpbHM+PHN1bW1hcnk+IENsaWNrIHRvIHNlZSBsaWJyYXJpZXMgdGhhdCBhcmUgbG9hZGVkIDwvc3VtbWFyeT48cD4KYGBge3IsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9CmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KGxpbW1hKQpsaWJyYXJ5KFFGZWF0dXJlcykKbGlicmFyeShtc3Fyb2IyKQpsaWJyYXJ5KHBsb3RseSkKbGlicmFyeShncmlkRXh0cmEpCmBgYAo8L3A+PC9kZXRhaWxzPgoKCiMgT3ZlcnZpZXcKCi0gU2FtcGxlIHNpemUgCi0gUmFuZG9taXplZCBDb21wbGV0ZSBCbG9jayBEZXNpZ25zIAoKIyBTYW1wbGUgc2l6ZSAKCjxpZnJhbWUgd2lkdGg9IjU2MCIgaGVpZ2h0PSIzMTUiCnNyYz0iaHR0cHM6Ly93d3cueW91dHViZS5jb20vZW1iZWQvaUlrcmZuVnhtNlkiCmZyYW1lYm9yZGVyPSIwIgpzdHlsZT0iZGlzcGxheTogYmxvY2s7IG1hcmdpbjogYXV0bzsiCmFsbG93PSJhdXRvcGxheTsgZW5jcnlwdGVkLW1lZGlhIiBhbGxvd2Z1bGxzY3JlZW4+PC9pZnJhbWU+CgojIyBTdGF0aXN0aWNhbCBtb2RlbHMgCi0gVXBvbiBwcmVwcm9jZXNzaW5nIGFuZCBzdW1tYXJpemF0aW9uIHdlIG1vZGVsIHRoZSBsb2cyIHRyYW5zZm9ybWVkIHByb3RlaW4gZXhwcmVzc2lvbiAoJHlfe2l9JCkgdmFsdWVzIHVzaW5nIGEgbGluZWFyIG1vZGVsIAoKXFt5X3tpfT0gXGJldGFfMCArIFxiZXRhXzEgeF97aSwxfSArIFxiZXRhIHhfe2ksMn0gKyAuLi4gKyBcZXBzaWxvbl9pXF0KCndpdGggCgotICRcYmV0YV8wJCB0aGUgaW50ZXJjZXB0Ci0gJFxiZXRhX2okIHRoZSBzbG9wZSBmb3IgcHJlZGljdG9yICR4X3tpLGp9JAotICR4X3tpLGp9JCBhIGNvbnRpbnVvdXMgcHJlZGljdG9yIChzdWNoIGFzIGFnZSkgb3IgYSBkdW1teSB2YXJpYWJsZSB0aGF0IGNhbiB0YWtlIHZhbHVlcyBvZiAwIGFuZCAxIGFuZCB0aGF0IHdpbGwgdXNlZCBmb3IgZmFjdG9ycyAoZS5nIHRyZWF0bWVudDogY2FuY2VyLCBub3JtYWwpLiAKCkV4YW1wbGU6IAoKLSBFc3Ryb2dlbiBSZWNlcHRvciBQb3NpdGl2ZSBCcmVhc3QgY2FuY2VyIHRpc3N1ZXMgZnJvbSBmcm9tIHBhdGllbnRzIHRyZWF0ZWQgd2l0aCB0YW1veGlmZW4gdXBvbiByZWN1cnJlbmNlIGhhdmUgYmVlbiBhc3Nlc3NlZCBpbiBhIHByb3Rlb21pY3Mgc3R1ZHkuIAotIEhhbGYgb2YgdGhlIHBhdGllbnRzIGhhZCBhIGdvb2Qgb3V0Y29tZSAob3IpIGFuZCB0aGUgb3RoZXIgaGFsZiBoYWQgYSBwb29yIG91dGNvbWUgKHBkKS4gCi0gVGhlIHByb3Rlb21lcyBoYXZlIGJlZW4gYXNzZXNzZWQgdXNpbmcgYW4gTFRRLU9yYml0cmFwIGFuZCB0aGUgdGhlcm1vIG91dHB1dCAuUkFXIGZpbGVzIHdlcmUgc2VhcmNoZWQgd2l0aCBNYXhRdWFudCAodmVyc2lvbiAxLjQuMS4yKSBhZ2FpbnN0IHRoZSBodW1hbiBwcm90ZW9tZSBkYXRhYmFzZSAoRkFTVEEgdmVyc2lvbiAyMDEyLTA5LCBodW1hbiBjYW5vbmljYWwgcHJvdGVvbWUpLgoKV2UgbW9kZWwgdGhlIGRhdGEgZm9yIGEgc2luZ2xlIHByb3RlaW4gdXNpbmcgYSBtb2RlbDoKClxbeV97aX09IFxiZXRhXzAgKyBcYmV0YV97UER9IHhfe2ksUER9ICsgXGVwc2lsb25faVxdCgp3aXRoIAokeF97aSxQRH09XGJlZ2lue2Nhc2VzfQowJiBcdGV4dHtnb29kIG91dGNvbWV9XFwKMSYgXHRleHR7cG9vciBvdXRjb21lfQpcZW5ke2Nhc2VzfSQuIAoKCiMjIyBSZWFkIGRhdGEgCgo8ZGV0YWlscz48c3VtbWFyeT4gQ2xpY2sgdG8gc2VlIGJhY2tncm91bmQgYW5kIGNvZGUgPC9zdW1tYXJ5PjxwPgoxLiBXZSB1c2UgYSBwZXB0aWRlcy50eHQgZmlsZSBmcm9tIE1TLWRhdGEgcXVhbnRpZmllZCB3aXRoIG1heHF1YW50IHRoYXQgCmNvbnRhaW5zIE1TMSBpbnRlbnNpdGllcyBzdW1tYXJpemVkIGF0IHRoZSBwZXB0aWRlIGxldmVsLiAKYGBge3J9CnBlcHRpZGVzRmlsZSA8LSAiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3N0YXRPbWljcy9wZGEyMS9kYXRhL3F1YW50aWZpY2F0aW9uL2NhbmNlci9wZXB0aWRlczN2czMudHh0IgpgYGAKCjIuIE1heHF1YW50IHN0b3JlcyB0aGUgaW50ZW5zaXR5IGRhdGEgZm9yIHRoZSBkaWZmZXJlbnQgc2FtcGxlcyBpbiBjb2x1bW5ucyB0aGF0IHN0YXJ0IHdpdGggSW50ZW5zaXR5LiBXZSBjYW4gcmV0cmVpdmUgdGhlIGNvbHVtbiBuYW1lcyB3aXRoIHRoZSBpbnRlbnNpdHkgZGF0YSB3aXRoIHRoZSBjb2RlIGJlbG93OiAKCmBgYHtyfQplY29scyA8LSBncmVwKCJJbnRlbnNpdHlcXC4iLCBuYW1lcyhyZWFkLmRlbGltKHBlcHRpZGVzRmlsZSkpKQpgYGAKCjMuIFJlYWQgdGhlIGRhdGEgYW5kIHN0b3JlIGl0IGluICBRRmVhdHVyZXMgb2JqZWN0IAoKYGBge3J9CnBlIDwtIHJlYWRRRmVhdHVyZXMoCiAgdGFibGUgPSBwZXB0aWRlc0ZpbGUsCiAgZm5hbWVzID0gMSwKICBlY29sID0gZWNvbHMsCiAgbmFtZSA9ICJwZXB0aWRlUmF3Iiwgc2VwPSJcdCIpCmBgYAo8L3A+PC9kZXRhaWxzPgoKIyMjIERlc2lnbgoKPGRldGFpbHM+PHN1bW1hcnk+IENsaWNrIHRvIHNlZSBiYWNrZ3JvdW5kIGFuZCBjb2RlIDwvc3VtbWFyeT48cD4KCmBgYHtyfSAKcGUgJT4lIGNvbG5hbWVzCmBgYAoKLSBOb3RlLCB0aGF0IHRoZSBzYW1wbGUgbmFtZXMgdGhlIG91dGNvbWUuIAotIFdlIHVwZGF0ZSB0aGUgY29sRGF0YSB3aXRoIGluZm9ybWF0aW9uIG9uIHRoZSBkZXNpZ24KCmBgYHtyfQpjb2xEYXRhKHBlKSRwcm9nbm9zaXMgPC0gIHBlW1sicGVwdGlkZVJhdyJdXSAlPiUgCiAgY29sbmFtZXMgJT4lIAogIHN1YnN0cigxMSwxMikgJT4lIAogIGFzLmZhY3RvcgpgYGAKCi0gV2UgZXhwbG9yZSB0aGUgY29sRGF0YQoKYGBge3J9CmNvbERhdGEocGUpCmBgYAoKPC9wPjwvZGV0YWlscz4KCiMjIyBQcmVwcm9jZXNzaW5nCgoKPGRldGFpbHM+PHN1bW1hcnk+IENsaWNrIHRvIHNlZSBjb2RlIGZvciBwcmVwcm9jZXNzaW5nIDwvc3VtbWFyeT48cD4KCjEuIExvZy10cmFuc2Zvcm0KCi0gV2UgY2FsY3VsYXRlIGhvdyBtYW55IG5vbiB6ZXJvIGludGVuc2l0aWVzIHdlIGhhdmUgZm9yIGVhY2ggcGVwdGlkZSBhbmQgdGhpcyBjYW4gYmUgdXNlZnVsIGZvciBmaWx0ZXJpbmcuCgpgYGB7cn0Kcm93RGF0YShwZVtbInBlcHRpZGVSYXciXV0pJG5Ob25aZXJvIDwtIHJvd1N1bXMoYXNzYXkocGVbWyJwZXB0aWRlUmF3Il1dKSA+IDApCmBgYAoKCi0gUGVwdGlkZXMgd2l0aCB6ZXJvIGludGVuc2l0aWVzIGFyZSBtaXNzaW5nIHBlcHRpZGVzIGFuZCBzaG91bGQgYmUgcmVwcmVzZW50CndpdGggYSBgTkFgIHZhbHVlIHJhdGhlciB0aGFuIGAwYC4KCmBgYHtyfQpwZSA8LSB6ZXJvSXNOQShwZSwgInBlcHRpZGVSYXciKSAjIGNvbnZlcnQgMCB0byBOQQpgYGAKCi0gTG9ndHJhbnNmb3JtIGRhdGEgd2l0aCBiYXNlIDIKCmBgYHtyfQpwZSA8LSBsb2dUcmFuc2Zvcm0ocGUsIGJhc2UgPSAyLCBpID0gInBlcHRpZGVSYXciLCBuYW1lID0gInBlcHRpZGVMb2ciKQpgYGAKCjIuIEZpbHRlcmluZwoKPGRldGFpbHM+PHN1bW1hcnk+IENsaWNrIHRvIHNlZSBjb2RlIHRvIGZpbHRlciB0aGUgZGF0YSA8L3N1bW1hcnk+PHA+CgotIEhhbmRsaW5nIG92ZXJsYXBwaW5nIHByb3RlaW4gZ3JvdXBzCgpJbiBvdXIgYXBwcm9hY2ggYSBwZXB0aWRlIGNhbiBtYXAgdG8gbXVsdGlwbGUgcHJvdGVpbnMsIGFzIGxvbmcgYXMgdGhlcmUgaXMKbm9uZSBvZiB0aGVzZSBwcm90ZWlucyBwcmVzZW50IGluIGEgc21hbGxlciBzdWJncm91cC4KCmBgYHtyfQpwZSA8LSBmaWx0ZXJGZWF0dXJlcyhwZSwgfiBQcm90ZWlucyAlaW4lIHNtYWxsZXN0VW5pcXVlR3JvdXBzKHJvd0RhdGEocGVbWyJwZXB0aWRlTG9nIl1dKSRQcm90ZWlucykpCmBgYAoKLSBSZW1vdmUgcmV2ZXJzZSBzZXF1ZW5jZXMgKGRlY295cykgYW5kIGNvbnRhbWluYW50cwoKV2Ugbm93IHJlbW92ZSB0aGUgY29udGFtaW5hbnRzLCBwZXB0aWRlcyB0aGF0IG1hcCB0byBkZWNveSBzZXF1ZW5jZXMsIGFuZCBwcm90ZWlucwp3aGljaCB3ZXJlIG9ubHkgaWRlbnRpZmllZCBieSBwZXB0aWRlcyB3aXRoIG1vZGlmaWNhdGlvbnMuCgpgYGB7cn0KcGUgPC0gZmlsdGVyRmVhdHVyZXMocGUsflJldmVyc2UgIT0gIisiKQpwZSA8LSBmaWx0ZXJGZWF0dXJlcyhwZSx+IENvbnRhbWluYW50ICE9ICIrIikKYGBgCgotIERyb3AgcGVwdGlkZXMgdGhhdCB3ZXJlIG9ubHkgaWRlbnRpZmllZCBpbiBvbmUgc2FtcGxlCgpXZSBrZWVwIHBlcHRpZGVzIHRoYXQgd2VyZSBvYnNlcnZlZCBhdCBsYXN0IHR3aWNlLgoKYGBge3J9CnBlIDwtIGZpbHRlckZlYXR1cmVzKHBlLH4gbk5vblplcm8gPj0yKQpucm93KHBlW1sicGVwdGlkZUxvZyJdXSkKYGBgCgpXZSBrZWVwIGByIG5yb3cocGVbWyJwZXB0aWRlTG9nIl1dKWAgcGVwdGlkZXMgdXBvbiBmaWx0ZXJpbmcuCgozLiBOb3JtYWxpemF0aW9uIAoKYGBge3J9CnBlIDwtIG5vcm1hbGl6ZShwZSwgCiAgICAgICAgICAgICAgICBpID0gInBlcHRpZGVMb2ciLCAKICAgICAgICAgICAgICAgIG5hbWUgPSAicGVwdGlkZU5vcm0iLCAKICAgICAgICAgICAgICAgIG1ldGhvZCA9ICJjZW50ZXIubWVkaWFuIikKYGBgCgo0LiBTdW1tYXJpemF0aW9uCgpgYGB7ciB3YXJuaW5nPUZBTFNFLG1lc3NhZ2U9RkFMU0V9CnBlIDwtIGFnZ3JlZ2F0ZUZlYXR1cmVzKHBlLAogIGkgPSAicGVwdGlkZU5vcm0iLAogIGZjb2wgPSAiUHJvdGVpbnMiLAogIG5hLnJtID0gVFJVRSwKICBuYW1lID0gInByb3RlaW4iKQpgYGAKPC9wPjwvZGV0YWlscz4KCiMjIEVmZmVjdCBzaXplPyAKCmBgYHtyfQpsaWJyYXJ5KEV4cGxvcmVNb2RlbE1hdHJpeCkKVmlzdWFsaXplRGVzaWduKGNvbERhdGEocGUpLGRlc2lnbkZvcm11bGEgPSB+cHJvZ25vc2lzKSRwbG90bGlzdApgYGAKCgpcWwpcYmVnaW57YXJyYXl9e3JjbGNsfQpFW1l8T1JdICY9JiBcYmV0YV8wXFwKRVtZfFBEXSAmPSYgXGJldGFfMCArIFxiZXRhX3tQRH1cXApcbG9nXzIgRkNfe1BELU9SfSAmPSYgIFxiZXRhXzAgKyBcYmV0YV97UER9IC0gIFxiZXRhXzAgJj0mXGJldGFfe1BEfQpcZW5ke2FycmF5fQpcXQoKCiMjIFN0YXRpc3RpY2FsIEluZmVyZW5jZQoKIyMjIEh5cG90aGVzZXMKCldlIHdhbnQgdG8gZmluZCBwcm90ZWlucyB0aGF0IGFyZSBkaWZmZXJlbnRpYWwgYWJ1bmRhbnQKCiRccmlnaHRhcnJvdyQgdXNlIGEgc3RhdGlzdGljYWwgdGVzdCAKCldlIHR5cGljYWxseSBzdGFydCBmcm9tIHRoZSBhbHRlcm5hdGl2ZSBoeXBvdGhlc2lzCgotICRIXzEkOiAkXGxvZ18yIEZDX3tQRC1PUn0gXG5lcSAwJCAgb3IgJFxiZXRhX3tQRH0gXG5lcSAwJAoKQnV0IHdlIGNhbiBub3QgdXNlIGRhdGEgdG8gcHJvdmUgYSBoeXBvdGhlc2lzLCB3ZSB0aGVyZWZvcmUgZmFsc2lmeSB0aGUgb3Bwb3NpdGU6CgotICRIXzAkOiAkXGxvZ18yIEZDX3tQRC1PUn0gPSAwJCAgb3IgJFxiZXRhX3tQRH0gPSAwJAoKIyMjIFRlc3Qgc3RhdGlzdGljOiAKCi0gVC10ZXN0IG9uIHRoZSBtb2RlbCBwYXJhbWV0ZXIgJFxiZXRhX3tQRH0kCgoKXFtUID0gXGZyYWN7XGhhdFxiZXRhX3tQRH0gLSAwfXtcdGV4dHtzZX1fe1xoYXRcYmV0YV97UER9fX1cXQoKd2hpY2ggZm9sbG93cyBhIHQtZGlzdHJpYnV0aW9uIHVuZGVyICRIXzAkIGlmIHRoZSBlcnJvcnMgYXJlIApcWwpcZXBzaWxvbl9pIFx0ZXh0eyBpLmkuZC4gfSBOKDAsXHNpZ21hXjIpClxdCgotIHAtdmFsdWU6IHByb2JhYmlsaXR5IHRvIG9ic2VydmUgYSB0LXN0YXRpc3RpYyB0aGF0IGlzIGFzIGV4dHJlbWUgb3IgbW9yZSBleHRyZW1lIHRoYXQgdGhlIG9uZSB3ZSBvYnNlcnZlZCBmb3IgdGhpcyBwcm90ZWluIGluIG91ciBzYW1wbGUgaWYgd2Ugd291bGQgcmVwZWF0IHRoZSBleHBlcmltZW50IGFuZCBpZiB0aGUgcHJvdGVpbiBpcyBub3QgZGlmZmVyZW50aWFsbHkgYWJ1bmRhbnQuCgojIyMgTXVsdGlwbGUgdGVzdGluZyAKCi0gV2UgZG8gdGhpcyB0ZXN0IGZvciBhbGwgcHJvdGVpbnMgKHR5cGljYWxseSB0aG91c2FuZHMgb2YgdGhlbSkKCi0gQWRqdXN0IHAtdmFsdWVzIGZvciBtdWx0aXBsZSB0ZXN0aW5nIHVzaW5nIHRoZSBmYWxzZSBkaXNjb3ZlcnkgcmF0ZSAKJCRGRFIgPSBFXGxlZnRbXGZyYWN7RlB9e0ZQICsgVFB9XHJpZ2h0XSQkCgotIEVtcGlyaWNhbCBCYXllcyB2YXJpYW5jZSBlc3RpbWF0aW9uOiBOb3RlLCB0aGF0IHRoZSBtYXNzaXZlIHBhcmFsbGVsIGRhdGEgc3RydWN0dXJlIGFsc28gYWxsb3dzIHlvdSB0byBzdGFiaWxpemUgdGhlIHZhcmlhbmNlIGVzdGltYXRpb24gYnkgYm9ycm93aW5nIGluZm9ybWF0aW9uIGFjcm9zcyBwcm90ZWlucyEgCgpOb3RlLCBpZiB5b3Ugd2FudCB0byByZWZyZXNoIHNvbWUgZnVuZGFtZW50YWwgY29uY2VwdHMgb2YgaHlwb3RoZXNpcyB0ZXN0aW5nOiAKCi0gW1N0YXRpc3RpY2FsIGh5cG90aGVzaXMgdGVzdGluZ10oaHR0cHM6Ly9zdGF0b21pY3MuZ2l0aHViLmlvL1BEQTIxL3BkYV9xdWFudGlmaWNhdGlvbl9pbmZlcmVuY2UuaHRtbCMxMzNfSHlwb3RoZXNpc190ZXN0aW5nOl9hX3NpbmdsZV9wcm90ZWluKQotIFtNdWx0aXBsZSBoeXBvdGhlc2lzIHRlc3RpbmddKGh0dHBzOi8vc3RhdG9taWNzLmdpdGh1Yi5pby9QREEyMS9wZGFfcXVhbnRpZmljYXRpb25faW5mZXJlbmNlLmh0bWwjMTRfTXVsdGlwbGVfaHlwb3RoZXNpc190ZXN0aW5nKQotIFtFbXBpcmljYWwgQmF5ZXMgVmFyaWFuY2UgRXN0aW1hdGlvbl0oaHR0cHM6Ly9zdGF0b21pY3MuZ2l0aHViLmlvL1BEQTIxL3BkYV9xdWFudGlmaWNhdGlvbl9pbmZlcmVuY2UuaHRtbCMxNV9Nb2RlcmF0ZWRfU3RhdGlzdGljcykKCgojIyBFeHBlcmltZW50IHdpdGggMyB2cyAzIGNvbXBhcmlzb24KCjxkZXRhaWxzPjxzdW1tYXJ5PiBDbGljayB0byBzZWUgY29kZSB0byBpbmZlcmVuY2UgPC9zdW1tYXJ5PjxwPgpgYGB7ciBtZXNzYWdlPUZBTFNFLHdhcm5pbmc9RkFMU0V9CnBlIDwtIG1zcXJvYihvYmplY3QgPSBwZSwgaSA9ICJwcm90ZWluIiwgZm9ybXVsYSA9IH5wcm9nbm9zaXMpCkwgPC0gbWFrZUNvbnRyYXN0KCJwcm9nbm9zaXNQRD0wIiwgcGFyYW1ldGVyTmFtZXMgPSBjKCJwcm9nbm9zaXNQRCIpKQpwZSA8LSBoeXBvdGhlc2lzVGVzdChvYmplY3QgPSBwZSwgaSA9ICJwcm90ZWluIiwgY29udHJhc3QgPSBMKQpgYGAKCmBgYHtyfQp2b2xjYW5vM3gzIDwtIGdncGxvdChyb3dEYXRhKHBlW1sicHJvdGVpbiJdXSkkcHJvZ25vc2lzUEQgJT4lIG5hLmV4Y2x1ZGUsCiAgICAgICAgICAgICAgICAgIGFlcyh4ID0gbG9nRkMsIHkgPSAtbG9nMTAocHZhbCksIGNvbG9yID0gYWRqUHZhbCA8IDAuMDUpKSArCiAgZ2VvbV9wb2ludChjZXggPSAyLjUpICsKICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gYWxwaGEoYygiYmxhY2siLCAicmVkIiksIDAuNSkpICsgCiAgdGhlbWVfbWluaW1hbCgpICsKICBnZ3RpdGxlKHBhc3RlMChzdW0ocm93RGF0YShwZVtbInByb3RlaW4iXV0pJHByb2dub3Npc1BEJGFkalB2YWw8MC4wNSxuYS5ybT1UUlVFKSwiIHByb3RlaW5zIGFyZSBmb3VuZCB0byBiZSBEQSIpKQpgYGAKCjwvcD48L2RldGFpbHM+CgpgYGB7cn0Kdm9sY2FubzN4MwpgYGAKClVwb24gY29ycmVjdGlvbiBmb3IgbXVsdGlwbGUgdGVzdGluZyB3aXRoIHVzaW5nIHRoZSBmYWxzZSBkaXNjb3ZlcnkgcmF0ZSAoRkRSKSBtZXRob2Qgbm8gcHJvdGVpbnMgYXJlIGZvdW5kIHRvIGJlIGRpZmZlcmVudGlhbGx5IGV4cHJlc3NlZC4gCgpOb3RlLCB0aGF0IHlvdSBjYW4gcmVmcmVzaCB0aGUgY29uY2VwdCBvZiBtdWx0aXBsZSB0ZXN0aW5nIGFuZCBGRFIgW2hlcmVdIChodHRwczovL3N0YXRvbWljcy5naXRodWIuaW8vUERBMjEvcGRhX3F1YW50aWZpY2F0aW9uX2luZmVyZW5jZS5odG1sIzE0X011bHRpcGxlX2h5cG90aGVzaXNfdGVzdGluZykuCgojIyBFeHBlcmltZW50IHdpdGggYSA2IHZzIDYgY29tcGFyaXNvbgoKIyMjIFJlYWQgZGF0YSAKCjxkZXRhaWxzPjxzdW1tYXJ5PiBDbGljayB0byBzZWUgYmFja2dyb3VuZCBhbmQgY29kZSA8L3N1bW1hcnk+PHA+CjEuIFdlIHVzZSBhIHBlcHRpZGVzLnR4dCBmaWxlIGZyb20gTVMtZGF0YSBxdWFudGlmaWVkIHdpdGggbWF4cXVhbnQgdGhhdCAKY29udGFpbnMgTVMxIGludGVuc2l0aWVzIHN1bW1hcml6ZWQgYXQgdGhlIHBlcHRpZGUgbGV2ZWwuIApgYGB7cn0KcGVwdGlkZXNGaWxlIDwtICJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vc3RhdE9taWNzL3BkYTIxL2RhdGEvcXVhbnRpZmljYXRpb24vY2FuY2VyL3BlcHRpZGVzNnZzNi50eHQiCmBgYAoKMi4gTWF4cXVhbnQgc3RvcmVzIHRoZSBpbnRlbnNpdHkgZGF0YSBmb3IgdGhlIGRpZmZlcmVudCBzYW1wbGVzIGluIGNvbHVtbm5zIHRoYXQgc3RhcnQgd2l0aCBJbnRlbnNpdHkuIFdlIGNhbiByZXRyZWl2ZSB0aGUgY29sdW1uIG5hbWVzIHdpdGggdGhlIGludGVuc2l0eSBkYXRhIHdpdGggdGhlIGNvZGUgYmVsb3c6IAoKYGBge3J9CmVjb2xzIDwtIGdyZXAoIkludGVuc2l0eVxcLiIsIG5hbWVzKHJlYWQuZGVsaW0ocGVwdGlkZXNGaWxlKSkpCmBgYAoKMy4gUmVhZCB0aGUgZGF0YSBhbmQgc3RvcmUgaXQgaW4gIFFGZWF0dXJlcyBvYmplY3QgCgpgYGB7cn0KcGUgPC0gcmVhZFFGZWF0dXJlcygKICB0YWJsZSA9IHBlcHRpZGVzRmlsZSwKICBmbmFtZXMgPSAxLAogIGVjb2wgPSBlY29scywKICBuYW1lID0gInBlcHRpZGVSYXciLCBzZXA9Ilx0IikKYGBgCjwvcD48L2RldGFpbHM+CgojIyMgRGVzaWduCgo8ZGV0YWlscz48c3VtbWFyeT4gQ2xpY2sgdG8gc2VlIGJhY2tncm91bmQgYW5kIGNvZGUgPC9zdW1tYXJ5PjxwPgoKYGBge3J9IApwZSAlPiUgY29sbmFtZXMKYGBgCgotIE5vdGUsIHRoYXQgdGhlIHNhbXBsZSBuYW1lcyB0aGUgb3V0Y29tZS4gCi0gV2UgdXBkYXRlIHRoZSBjb2xEYXRhIHdpdGggaW5mb3JtYXRpb24gb24gdGhlIGRlc2lnbgoKYGBge3J9CmNvbERhdGEocGUpJHByb2dub3NpcyA8LSAgcGVbWyJwZXB0aWRlUmF3Il1dICU+JSAKICBjb2xuYW1lcyAlPiUgCiAgc3Vic3RyKDExLDEyKSAlPiUgCiAgYXMuZmFjdG9yCmBgYAoKLSBXZSBleHBsb3JlIHRoZSBjb2xEYXRhCgpgYGB7cn0KY29sRGF0YShwZSkKYGBgCgo8L3A+PC9kZXRhaWxzPgoKIyMjIFByZXByb2Nlc3NpbmcKCgo8ZGV0YWlscz48c3VtbWFyeT4gQ2xpY2sgdG8gc2VlIGNvZGUgZm9yIHByZXByb2Nlc3NpbmcgPC9zdW1tYXJ5PjxwPgoKMS4gTG9nLXRyYW5zZm9ybQoKLSBXZSBjYWxjdWxhdGUgaG93IG1hbnkgbm9uIHplcm8gaW50ZW5zaXRpZXMgd2UgaGF2ZSBmb3IgZWFjaCBwZXB0aWRlIGFuZCB0aGlzIGNhbiBiZSB1c2VmdWwgZm9yIGZpbHRlcmluZy4KCmBgYHtyfQpyb3dEYXRhKHBlW1sicGVwdGlkZVJhdyJdXSkkbk5vblplcm8gPC0gcm93U3Vtcyhhc3NheShwZVtbInBlcHRpZGVSYXciXV0pID4gMCkKYGBgCgoKLSBQZXB0aWRlcyB3aXRoIHplcm8gaW50ZW5zaXRpZXMgYXJlIG1pc3NpbmcgcGVwdGlkZXMgYW5kIHNob3VsZCBiZSByZXByZXNlbnQKd2l0aCBhIGBOQWAgdmFsdWUgcmF0aGVyIHRoYW4gYDBgLgoKYGBge3J9CnBlIDwtIHplcm9Jc05BKHBlLCAicGVwdGlkZVJhdyIpICMgY29udmVydCAwIHRvIE5BCmBgYAoKLSBMb2d0cmFuc2Zvcm0gZGF0YSB3aXRoIGJhc2UgMgoKYGBge3J9CnBlIDwtIGxvZ1RyYW5zZm9ybShwZSwgYmFzZSA9IDIsIGkgPSAicGVwdGlkZVJhdyIsIG5hbWUgPSAicGVwdGlkZUxvZyIpCmBgYAoKMi4gRmlsdGVyaW5nCgo8ZGV0YWlscz48c3VtbWFyeT4gQ2xpY2sgdG8gc2VlIGNvZGUgdG8gZmlsdGVyIHRoZSBkYXRhIDwvc3VtbWFyeT48cD4KCi0gSGFuZGxpbmcgb3ZlcmxhcHBpbmcgcHJvdGVpbiBncm91cHMKCkluIG91ciBhcHByb2FjaCBhIHBlcHRpZGUgY2FuIG1hcCB0byBtdWx0aXBsZSBwcm90ZWlucywgYXMgbG9uZyBhcyB0aGVyZSBpcwpub25lIG9mIHRoZXNlIHByb3RlaW5zIHByZXNlbnQgaW4gYSBzbWFsbGVyIHN1Ymdyb3VwLgoKYGBge3J9CnBlIDwtIGZpbHRlckZlYXR1cmVzKHBlLCB+IFByb3RlaW5zICVpbiUgc21hbGxlc3RVbmlxdWVHcm91cHMocm93RGF0YShwZVtbInBlcHRpZGVMb2ciXV0pJFByb3RlaW5zKSkKYGBgCgotIFJlbW92ZSByZXZlcnNlIHNlcXVlbmNlcyAoZGVjb3lzKSBhbmQgY29udGFtaW5hbnRzCgpXZSBub3cgcmVtb3ZlIHRoZSBjb250YW1pbmFudHMsIHBlcHRpZGVzIHRoYXQgbWFwIHRvIGRlY295IHNlcXVlbmNlcywgYW5kIHByb3RlaW5zCndoaWNoIHdlcmUgb25seSBpZGVudGlmaWVkIGJ5IHBlcHRpZGVzIHdpdGggbW9kaWZpY2F0aW9ucy4KCmBgYHtyfQpwZSA8LSBmaWx0ZXJGZWF0dXJlcyhwZSx+UmV2ZXJzZSAhPSAiKyIpCnBlIDwtIGZpbHRlckZlYXR1cmVzKHBlLH4gQ29udGFtaW5hbnQgIT0gIisiKQpgYGAKCi0gRHJvcCBwZXB0aWRlcyB0aGF0IHdlcmUgb25seSBpZGVudGlmaWVkIGluIG9uZSBzYW1wbGUKCldlIGtlZXAgcGVwdGlkZXMgdGhhdCB3ZXJlIG9ic2VydmVkIGF0IGxhc3QgdHdpY2UuCgpgYGB7cn0KcGUgPC0gZmlsdGVyRmVhdHVyZXMocGUsfiBuTm9uWmVybyA+PTIpCm5yb3cocGVbWyJwZXB0aWRlTG9nIl1dKQpgYGAKCldlIGtlZXAgYHIgbnJvdyhwZVtbInBlcHRpZGVMb2ciXV0pYCBwZXB0aWRlcyB1cG9uIGZpbHRlcmluZy4KCjMuIE5vcm1hbGl6YXRpb24gCgpgYGB7cn0KcGUgPC0gbm9ybWFsaXplKHBlLCAKICAgICAgICAgICAgICAgIGkgPSAicGVwdGlkZUxvZyIsIAogICAgICAgICAgICAgICAgbmFtZSA9ICJwZXB0aWRlTm9ybSIsIAogICAgICAgICAgICAgICAgbWV0aG9kID0gImNlbnRlci5tZWRpYW4iKQpgYGAKCjQuIFN1bW1hcml6YXRpb24KCmBgYHtyIHdhcm5pbmc9RkFMU0UsbWVzc2FnZT1GQUxTRX0KcGUgPC0gYWdncmVnYXRlRmVhdHVyZXMocGUsCiAgaSA9ICJwZXB0aWRlTm9ybSIsCiAgZmNvbCA9ICJQcm90ZWlucyIsCiAgbmEucm0gPSBUUlVFLAogIG5hbWUgPSAicHJvdGVpbiIpCmBgYAo8L3A+PC9kZXRhaWxzPgoKIyMjIFN0YXRpc3RpY2FsIEluZmVyZW5jZQoKPGRldGFpbHM+PHN1bW1hcnk+IENsaWNrIHRvIHNlZSBjb2RlIHRvIGluZmVyZW5jZSA8L3N1bW1hcnk+PHA+CmBgYHtyIG1lc3NhZ2U9RkFMU0Usd2FybmluZz1GQUxTRX0KcGUgPC0gbXNxcm9iKG9iamVjdCA9IHBlLCBpID0gInByb3RlaW4iLCBmb3JtdWxhID0gfnByb2dub3NpcykKTCA8LSBtYWtlQ29udHJhc3QoInByb2dub3Npc1BEPTAiLCBwYXJhbWV0ZXJOYW1lcyA9IGMoInByb2dub3Npc1BEIikpCnBlIDwtIGh5cG90aGVzaXNUZXN0KG9iamVjdCA9IHBlLCBpID0gInByb3RlaW4iLCBjb250cmFzdCA9IEwpCmBgYAoKYGBge3J9CnZvbGNhbm82eDYgPC0gZ2dwbG90KHJvd0RhdGEocGVbWyJwcm90ZWluIl1dKSRwcm9nbm9zaXNQRCAlPiUgbmEuZXhjbHVkZSwKICAgICAgICAgICAgICAgICAgYWVzKHggPSBsb2dGQywgeSA9IC1sb2cxMChwdmFsKSwgY29sb3IgPSBhZGpQdmFsIDwgMC4wNSkpICsKICBnZW9tX3BvaW50KGNleCA9IDIuNSkgKwogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBhbHBoYShjKCJibGFjayIsICJyZWQiKSwgMC41KSkgKyAKICB0aGVtZV9taW5pbWFsKCkgKwogIGdndGl0bGUocGFzdGUwKHN1bShyb3dEYXRhKHBlW1sicHJvdGVpbiJdXSkkcHJvZ25vc2lzUEQkYWRqUHZhbDwwLjA1LG5hLnJtPVRSVUUpLCIgcHJvdGVpbnMgYXJlIGZvdW5kIHRvIGJlIERBIikpCmBgYAo8L3A+PC9kZXRhaWxzPgoKCmBgYHtyfQp2b2xjYW5vNng2CmBgYAoKCiMjIEV4cGVyaW1lbnQgd2l0aCBhIDkgdnMgOSBjb21wYXJpc29uCgojIyMgUmVhZCBkYXRhIAoKPGRldGFpbHM+PHN1bW1hcnk+IENsaWNrIHRvIHNlZSBiYWNrZ3JvdW5kIGFuZCBjb2RlIDwvc3VtbWFyeT48cD4KMS4gV2UgdXNlIGEgcGVwdGlkZXMudHh0IGZpbGUgZnJvbSBNUy1kYXRhIHF1YW50aWZpZWQgd2l0aCBtYXhxdWFudCB0aGF0IApjb250YWlucyBNUzEgaW50ZW5zaXRpZXMgc3VtbWFyaXplZCBhdCB0aGUgcGVwdGlkZSBsZXZlbC4gCmBgYHtyfQpwZXB0aWRlc0ZpbGUgPC0gImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9zdGF0T21pY3MvcGRhMjEvZGF0YS9xdWFudGlmaWNhdGlvbi9jYW5jZXIvcGVwdGlkZXM5dnM5LnR4dCIKYGBgCgoyLiBNYXhxdWFudCBzdG9yZXMgdGhlIGludGVuc2l0eSBkYXRhIGZvciB0aGUgZGlmZmVyZW50IHNhbXBsZXMgaW4gY29sdW1ubnMgdGhhdCBzdGFydCB3aXRoIEludGVuc2l0eS4gV2UgY2FuIHJldHJlaXZlIHRoZSBjb2x1bW4gbmFtZXMgd2l0aCB0aGUgaW50ZW5zaXR5IGRhdGEgd2l0aCB0aGUgY29kZSBiZWxvdzogCgpgYGB7cn0KZWNvbHMgPC0gZ3JlcCgiSW50ZW5zaXR5XFwuIiwgbmFtZXMocmVhZC5kZWxpbShwZXB0aWRlc0ZpbGUpKSkKYGBgCgozLiBSZWFkIHRoZSBkYXRhIGFuZCBzdG9yZSBpdCBpbiAgUUZlYXR1cmVzIG9iamVjdCAKCmBgYHtyfQpwZSA8LSByZWFkUUZlYXR1cmVzKAogIHRhYmxlID0gcGVwdGlkZXNGaWxlLAogIGZuYW1lcyA9IDEsCiAgZWNvbCA9IGVjb2xzLAogIG5hbWUgPSAicGVwdGlkZVJhdyIsIHNlcD0iXHQiKQpgYGAKPC9wPjwvZGV0YWlscz4KCiMjIyBEZXNpZ24KCjxkZXRhaWxzPjxzdW1tYXJ5PiBDbGljayB0byBzZWUgYmFja2dyb3VuZCBhbmQgY29kZSA8L3N1bW1hcnk+PHA+CgpgYGB7cn0gCnBlICU+JSBjb2xuYW1lcwpgYGAKCi0gTm90ZSwgdGhhdCB0aGUgc2FtcGxlIG5hbWVzIHRoZSBvdXRjb21lLiAKLSBXZSB1cGRhdGUgdGhlIGNvbERhdGEgd2l0aCBpbmZvcm1hdGlvbiBvbiB0aGUgZGVzaWduCgpgYGB7cn0KY29sRGF0YShwZSkkcHJvZ25vc2lzIDwtICBwZVtbInBlcHRpZGVSYXciXV0gJT4lIAogIGNvbG5hbWVzICU+JSAKICBzdWJzdHIoMTEsMTIpICU+JSAKICBhcy5mYWN0b3IKYGBgCgotIFdlIGV4cGxvcmUgdGhlIGNvbERhdGEKCmBgYHtyfQpjb2xEYXRhKHBlKQpgYGAKCjwvcD48L2RldGFpbHM+CgojIyMgUHJlcHJvY2Vzc2luZwoKCjxkZXRhaWxzPjxzdW1tYXJ5PiBDbGljayB0byBzZWUgY29kZSBmb3IgcHJlcHJvY2Vzc2luZyA8L3N1bW1hcnk+PHA+CgoxLiBMb2ctdHJhbnNmb3JtCgotIFdlIGNhbGN1bGF0ZSBob3cgbWFueSBub24gemVybyBpbnRlbnNpdGllcyB3ZSBoYXZlIGZvciBlYWNoIHBlcHRpZGUgYW5kIHRoaXMgY2FuIGJlIHVzZWZ1bCBmb3IgZmlsdGVyaW5nLgoKYGBge3J9CnJvd0RhdGEocGVbWyJwZXB0aWRlUmF3Il1dKSRuTm9uWmVybyA8LSByb3dTdW1zKGFzc2F5KHBlW1sicGVwdGlkZVJhdyJdXSkgPiAwKQpgYGAKCgotIFBlcHRpZGVzIHdpdGggemVybyBpbnRlbnNpdGllcyBhcmUgbWlzc2luZyBwZXB0aWRlcyBhbmQgc2hvdWxkIGJlIHJlcHJlc2VudAp3aXRoIGEgYE5BYCB2YWx1ZSByYXRoZXIgdGhhbiBgMGAuCgpgYGB7cn0KcGUgPC0gemVyb0lzTkEocGUsICJwZXB0aWRlUmF3IikgIyBjb252ZXJ0IDAgdG8gTkEKYGBgCgotIExvZ3RyYW5zZm9ybSBkYXRhIHdpdGggYmFzZSAyCgpgYGB7cn0KcGUgPC0gbG9nVHJhbnNmb3JtKHBlLCBiYXNlID0gMiwgaSA9ICJwZXB0aWRlUmF3IiwgbmFtZSA9ICJwZXB0aWRlTG9nIikKYGBgCgoyLiBGaWx0ZXJpbmcKCjxkZXRhaWxzPjxzdW1tYXJ5PiBDbGljayB0byBzZWUgY29kZSB0byBmaWx0ZXIgdGhlIGRhdGEgPC9zdW1tYXJ5PjxwPgoKLSBIYW5kbGluZyBvdmVybGFwcGluZyBwcm90ZWluIGdyb3VwcwoKSW4gb3VyIGFwcHJvYWNoIGEgcGVwdGlkZSBjYW4gbWFwIHRvIG11bHRpcGxlIHByb3RlaW5zLCBhcyBsb25nIGFzIHRoZXJlIGlzCm5vbmUgb2YgdGhlc2UgcHJvdGVpbnMgcHJlc2VudCBpbiBhIHNtYWxsZXIgc3ViZ3JvdXAuCgpgYGB7cn0KcGUgPC0gZmlsdGVyRmVhdHVyZXMocGUsIH4gUHJvdGVpbnMgJWluJSBzbWFsbGVzdFVuaXF1ZUdyb3Vwcyhyb3dEYXRhKHBlW1sicGVwdGlkZUxvZyJdXSkkUHJvdGVpbnMpKQpgYGAKCi0gUmVtb3ZlIHJldmVyc2Ugc2VxdWVuY2VzIChkZWNveXMpIGFuZCBjb250YW1pbmFudHMKCldlIG5vdyByZW1vdmUgdGhlIGNvbnRhbWluYW50cywgcGVwdGlkZXMgdGhhdCBtYXAgdG8gZGVjb3kgc2VxdWVuY2VzLCBhbmQgcHJvdGVpbnMKd2hpY2ggd2VyZSBvbmx5IGlkZW50aWZpZWQgYnkgcGVwdGlkZXMgd2l0aCBtb2RpZmljYXRpb25zLgoKYGBge3J9CnBlIDwtIGZpbHRlckZlYXR1cmVzKHBlLH5SZXZlcnNlICE9ICIrIikKcGUgPC0gZmlsdGVyRmVhdHVyZXMocGUsfiBDb250YW1pbmFudCAhPSAiKyIpCmBgYAoKLSBEcm9wIHBlcHRpZGVzIHRoYXQgd2VyZSBvbmx5IGlkZW50aWZpZWQgaW4gb25lIHNhbXBsZQoKV2Uga2VlcCBwZXB0aWRlcyB0aGF0IHdlcmUgb2JzZXJ2ZWQgYXQgbGFzdCB0d2ljZS4KCmBgYHtyfQpwZSA8LSBmaWx0ZXJGZWF0dXJlcyhwZSx+IG5Ob25aZXJvID49MikKbnJvdyhwZVtbInBlcHRpZGVMb2ciXV0pCmBgYAoKV2Uga2VlcCBgciBucm93KHBlW1sicGVwdGlkZUxvZyJdXSlgIHBlcHRpZGVzIHVwb24gZmlsdGVyaW5nLgoKMy4gTm9ybWFsaXphdGlvbiAKCmBgYHtyfQpwZSA8LSBub3JtYWxpemUocGUsIAogICAgICAgICAgICAgICAgaSA9ICJwZXB0aWRlTG9nIiwgCiAgICAgICAgICAgICAgICBuYW1lID0gInBlcHRpZGVOb3JtIiwgCiAgICAgICAgICAgICAgICBtZXRob2QgPSAiY2VudGVyLm1lZGlhbiIpCmBgYAoKNC4gU3VtbWFyaXphdGlvbgoKYGBge3Igd2FybmluZz1GQUxTRSxtZXNzYWdlPUZBTFNFfQpwZSA8LSBhZ2dyZWdhdGVGZWF0dXJlcyhwZSwKICBpID0gInBlcHRpZGVOb3JtIiwKICBmY29sID0gIlByb3RlaW5zIiwKICBuYS5ybSA9IFRSVUUsCiAgbmFtZSA9ICJwcm90ZWluIikKYGBgCjwvcD48L2RldGFpbHM+CgojIyMgU3RhdGlzdGljYWwgSW5mZXJlbmNlCgo8ZGV0YWlscz48c3VtbWFyeT4gQ2xpY2sgdG8gc2VlIGNvZGUgdG8gaW5mZXJlbmNlIDwvc3VtbWFyeT48cD4KYGBge3IgbWVzc2FnZT1GQUxTRSx3YXJuaW5nPUZBTFNFfQpwZSA8LSBtc3Fyb2Iob2JqZWN0ID0gcGUsIGkgPSAicHJvdGVpbiIsIGZvcm11bGEgPSB+cHJvZ25vc2lzKQpMIDwtIG1ha2VDb250cmFzdCgicHJvZ25vc2lzUEQ9MCIsIHBhcmFtZXRlck5hbWVzID0gYygicHJvZ25vc2lzUEQiKSkKcGUgPC0gaHlwb3RoZXNpc1Rlc3Qob2JqZWN0ID0gcGUsIGkgPSAicHJvdGVpbiIsIGNvbnRyYXN0ID0gTCkKYGBgCgpgYGB7cn0Kdm9sY2Fubzl4OSA8LSBnZ3Bsb3Qocm93RGF0YShwZVtbInByb3RlaW4iXV0pJHByb2dub3Npc1BEICU+JSBuYS5leGNsdWRlLAogICAgICAgICAgICAgICAgICBhZXMoeCA9IGxvZ0ZDLCB5ID0gLWxvZzEwKHB2YWwpLCBjb2xvciA9IGFkalB2YWwgPCAwLjA1KSkgKwogIGdlb21fcG9pbnQoY2V4ID0gMi41KSArCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGFscGhhKGMoImJsYWNrIiwgInJlZCIpLCAwLjUpKSArIAogIHRoZW1lX21pbmltYWwoKSArCiAgZ2d0aXRsZShwYXN0ZTAoc3VtKHJvd0RhdGEocGVbWyJwcm90ZWluIl1dKSRwcm9nbm9zaXNQRCRhZGpQdmFsPDAuMDUsbmEucm09VFJVRSksIiBwcm90ZWlucyBhcmUgZm91bmQgdG8gYmUgREEiKSkKYGBgCjwvcD48L2RldGFpbHM+CgoKYGBge3J9CnZvbGNhbm85eDkKYGBgCgotIFdlIGhhdmUgc2VlbiB0aGF0IHRoZSBzYW1wbGUgc2l6ZSBpcyBrZXkgdG8gcmVjb3ZlciBEQSBwcm90ZWlucwoKLSBJbmRlZWQsIGlmIGEgcHJvdGVpbiBpcyBkaWZmZXJlbnRpYWxseSBleHByZXNzZWQsIHRoZSB2YWx1ZSBvZiBULXRlc3QgZGVwZW5kcyBvbiB0aGUgZWZmZWN0IHNpemUsIHRoZSB2YXJpYWJpbGl0eSBvZiB0aGUgcHJvdGVpbiBleHByZXNzaW9uIHZhbHVlcyBhbmQgdGhlIHNhbXBsZSBzaXplLiAKCiQkClRfZz1cZnJhY3tcbG9nXzIgXHRleHR7RkN9fXtcdGV4dHtzZX1fe1xsb2dfMiBcdGV4dHtGQ319fQokJAoKJCQKVF9nPVxmcmFje1x3aWRlaGF0e1x0ZXh0e3NpZ25hbH19fXtcd2lkZWhhdHtcdGV4dHtOb2lzZX19fQokJAoKRm9yIGEgdHdvIGdyb3VwIGNvbXBhcmlzb24gdGhlIHN0YW5kYXJkIGVycm9yIG9uIHRoZSBmb2xkIGNoYW5nZSBlcXVhbHMKCiQkClx0ZXh0e3NlfV97XGxvZ18yIFx0ZXh0e0ZDfX09XHRleHR7U0R9XHNxcnR7XGZyYWN7MX17bl8xfStcZnJhY3sxfXtuXzJ9fQokJAoKJFxyaWdodGFycm93JCBpZiBudW1iZXIgb2YgYmlvLXJlcGVhdHMgaW5jcmVhc2VzIHdlIGhhdmUgYSBoaWdoZXIgcG93ZXIhCgoKIyBSYW5kb21pemVkIGNvbXBsZXRlIGJsb2NrIGRlc2lnbnMKCjxpZnJhbWUgd2lkdGg9IjU2MCIgaGVpZ2h0PSIzMTUiCnNyYz0iaHR0cHM6Ly93d3cueW91dHViZS5jb20vZW1iZWQvSEU0S1dmSU9WcDAiCmZyYW1lYm9yZGVyPSIwIgpzdHlsZT0iZGlzcGxheTogYmxvY2s7IG1hcmdpbjogYXV0bzsiCmFsbG93PSJhdXRvcGxheTsgZW5jcnlwdGVkLW1lZGlhIiBhbGxvd2Z1bGxzY3JlZW4+PC9pZnJhbWU+CgoKXFtcc2lnbWFeMj0gXHNpZ21hXjJfe2Jpb30rXHNpZ21hXjJfXHRleHR7bGFifSArXHNpZ21hXjJfXHRleHR7ZXh0cmFjdGlvbn0gKyBcc2lnbWFeMl9cdGV4dHtydW59ICsgXGxkb3RzXF0KCi0gQmlvbG9naWNhbDogZmx1Y3R1YXRpb25zIGluIHByb3RlaW4gbGV2ZWwgYmV0d2VlbiBtaWNlLCBmbHVjdGF0aW9ucyBpbiBwcm90ZWluIGxldmVsIGJldHdlZW4gY2VsbHMsIC4uLgotIFRlY2huaWNhbDogY2FnZSBlZmZlY3QsIGxhYiBlZmZlY3QsIHdlZWsgZWZmZWN0LCBwbGFzbWEgZXh0cmFjdGlvbiwgTVMtcnVuLCAuLi4KCiMjIE5hdHVyZSBtZXRob2RzOiBQb2ludHMgb2Ygc2lnbmlmaWNhbmNlIC0gQmxvY2tpbmcgCgpbaHR0cHM6Ly93d3cubmF0dXJlLmNvbS9hcnRpY2xlcy9ubWV0aC4zMDA1LnBkZl0oaHR0cHM6Ly93d3cubmF0dXJlLmNvbS9hcnRpY2xlcy9ubWV0aC4zMDA1LnBkZikKCgojIyBNb3VzZSBleGFtcGxlIAoKCmBgYHtyIGVjaG89RkFMU0UsIG91dC53aWR0aD0iNTAlIn0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoIi4vZmlndXJlcy9tb3VzZVRjZWxsX1JDQl9kZXNpZ24ucG5nIikKYGBgCgpEdWd1ZXQgZXQgYWwuICgyMDE3KSBNQ1AgMTYoOCk6MTQxNi0xNDMyLiBkb2k6IDEwLjEwNzQvbWNwLm0xMTYuMDYyNzQ1CgotIEFsbCB0cmVhdG1lbnRzIG9mIGludGVyZXN0IGFyZSBwcmVzZW50IHdpdGhpbiBibG9jayEKLSBXZSBjYW4gZXN0aW1hdGUgdGhlIGVmZmVjdCBvZiB0aGUgdHJlYXRtZW50IHdpdGhpbiBibG9jayEKClRvIGlsbHVzdHJhdGUgdGhlIHBvd2VyIG9mIGJsb2NraW5nIHdlIGhhdmUgc3Vic2V0dGVkIHRoZSBkYXRhIG9mIER1Z3VldCBldCBhbC4gaW4gYSAKCi0gY29tcGxldGVseSByYW5kb21pemVkIGRlc2lnbiB3aXRoIAogICAgLSBmb3VyIG1pY2UgZm9yIHdoaWNoIHdlIG9ubHkgaGF2ZSBtZWFzdXJlbWVudHMgb24gdGhlIG9yZGluYXJ5IFQtY2VsbHMgCiAgICAtIGZvdXIgbWljZSBmb3Igd2hpY2ggd2Ugb25seSBoYXZlIG1lYXN1cmVtZW50cyBvbiB0aGUgcmVndWxhdG9yeSBULWNlbGxzIAogICAgCi0gcmFuZG9taXplZCBjb21wbGV0ZSBibG9jayBkZXNpZ24gd2l0aCBmb3VyIG1pY2UgZm9yIHdoaWNoIHdlIGJvdGggaGF2ZQogICAgCiAgICAtIG1lYXN1cmVtZW50cyBvbiBvcmRpbmFyeSBULWNlbGxzIGFzIHdlbGwgYXMKICAgIC0gbWVhc3VyZW1lbnRzIG9uIHJlZ3VsYXRvcnkgVC1jZWxscwoKCgoKIyMjIERhdGEgCjxkZXRhaWxzPjxzdW1tYXJ5PiBDbGljayB0byBzZWUgY29kZSAgPC9zdW1tYXJ5PjxwPgpgYGB7ciwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0KbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkobGltbWEpCmxpYnJhcnkoUUZlYXR1cmVzKQpsaWJyYXJ5KG1zcXJvYjIpCmxpYnJhcnkocGxvdGx5KQpsaWJyYXJ5KGdyaWRFeHRyYSkKCnBlcHRpZGVzRmlsZSA8LSAiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3N0YXRPbWljcy9QREEyMS9kYXRhL3F1YW50aWZpY2F0aW9uL21vdXNlVGNlbGwvcGVwdGlkZXNSQ0IudHh0IgpwZXB0aWRlc0ZpbGUyIDwtICJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vc3RhdE9taWNzL1BEQTIxL2RhdGEvcXVhbnRpZmljYXRpb24vbW91c2VUY2VsbC9wZXB0aWRlc0NSRC50eHQiCnBlcHRpZGVzRmlsZTMgPC0gImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9zdGF0T21pY3MvUERBMjEvZGF0YS9xdWFudGlmaWNhdGlvbi9tb3VzZVRjZWxsL3BlcHRpZGVzLnR4dCIKCmVjb2xzIDwtIGdyZXAoIkludGVuc2l0eVxcLiIsIG5hbWVzKHJlYWQuZGVsaW0ocGVwdGlkZXNGaWxlKSkpCnBlIDwtIHJlYWRRRmVhdHVyZXMoCiAgdGFibGUgPSBwZXB0aWRlc0ZpbGUsCiAgZm5hbWVzID0gMSwKICBlY29sID0gZWNvbHMsCiAgbmFtZSA9ICJwZXB0aWRlUmF3Iiwgc2VwPSJcdCIpCgplY29sczIgPC0gZ3JlcCgiSW50ZW5zaXR5XFwuIiwgbmFtZXMocmVhZC5kZWxpbShwZXB0aWRlc0ZpbGUyKSkpCnBlMiA8LSByZWFkUUZlYXR1cmVzKAogIHRhYmxlID0gcGVwdGlkZXNGaWxlMiwKICBmbmFtZXMgPSAxLAogIGVjb2wgPSBlY29sczIsCiAgbmFtZSA9ICJwZXB0aWRlUmF3Iiwgc2VwPSJcdCIpCgplY29sczMgPC0gZ3JlcCgiSW50ZW5zaXR5XFwuIiwgbmFtZXMocmVhZC5kZWxpbShwZXB0aWRlc0ZpbGUzKSkpCnBlMyA8LSByZWFkUUZlYXR1cmVzKAogIHRhYmxlID0gcGVwdGlkZXNGaWxlMywKICBmbmFtZXMgPSAxLAogIGVjb2wgPSBlY29sczMsCiAgbmFtZSA9ICJwZXB0aWRlUmF3Iiwgc2VwPSJcdCIpCgojIyMgRGVzaWduCmNvbERhdGEocGUpJGNlbGx0eXBlIDwtIHN1YnN0cigKICBjb2xuYW1lcyhwZVtbInBlcHRpZGVSYXciXV0pLAogIDExLAogIDE0KSAlPiUKICB1bmxpc3QgJT4lICAKICBhcy5mYWN0b3IKCmNvbERhdGEocGUpJG1vdXNlIDwtIHBlW1sxXV0gJT4lCiAgY29sbmFtZXMgJT4lCiAgc3Ryc3BsaXQoc3BsaXQ9IlsuXSIpICAlPiUKICBzYXBwbHkoZnVuY3Rpb24oeCkgeFszXSkgJT4lCiAgYXMuZmFjdG9yCgpjb2xEYXRhKHBlMikkY2VsbHR5cGUgPC0gc3Vic3RyKAogIGNvbG5hbWVzKHBlMltbInBlcHRpZGVSYXciXV0pLAogIDExLAogIDE0KSAlPiUKICB1bmxpc3QgJT4lICAKICBhcy5mYWN0b3IKCmNvbERhdGEocGUyKSRtb3VzZSA8LSBwZTJbWzFdXSAlPiUKICBjb2xuYW1lcyAlPiUKICBzdHJzcGxpdChzcGxpdD0iWy5dIikgICU+JQogIHNhcHBseShmdW5jdGlvbih4KSB4WzNdKSAlPiUKICBhcy5mYWN0b3IKCmNvbERhdGEocGUzKSRjZWxsdHlwZSA8LSBzdWJzdHIoCiAgY29sbmFtZXMocGUzW1sicGVwdGlkZVJhdyJdXSksCiAgMTEsCiAgMTQpICU+JQogIHVubGlzdCAlPiUgIAogIGFzLmZhY3RvcgoKY29sRGF0YShwZTMpJG1vdXNlIDwtIHBlM1tbMV1dICU+JQogIGNvbG5hbWVzICU+JQogIHN0cnNwbGl0KHNwbGl0PSJbLl0iKSAgJT4lCiAgc2FwcGx5KGZ1bmN0aW9uKHgpIHhbM10pICU+JQogIGFzLmZhY3RvcgpgYGAKPC9wPjwvZGVzaWduPgoKIyMjIFByZXByb2Nlc3NpbmcgCgoKIyMjIyBMb2ctdHJhbnNmb3JtCgo8ZGV0YWlscz48c3VtbWFyeT4gQ2xpY2sgdG8gc2VlIGNvZGUgdG8gbG9nLXRyYW5zZnJvbSB0aGUgZGF0YSA8L3N1bW1hcnk+PHA+Ci0gV2UgY2FsY3VsYXRlIGhvdyBtYW55IG5vbiB6ZXJvIGludGVuc2l0aWVzIHdlIGhhdmUgZm9yIGVhY2ggcGVwdGlkZSBhbmQgdGhpcyBjYW4gYmUgdXNlZnVsIGZvciBmaWx0ZXJpbmcuCgpgYGB7cn0Kcm93RGF0YShwZVtbInBlcHRpZGVSYXciXV0pJG5Ob25aZXJvIDwtIHJvd1N1bXMoYXNzYXkocGVbWyJwZXB0aWRlUmF3Il1dKSA+IDApCgpyb3dEYXRhKHBlMltbInBlcHRpZGVSYXciXV0pJG5Ob25aZXJvIDwtIHJvd1N1bXMoYXNzYXkocGUyW1sicGVwdGlkZVJhdyJdXSkgPiAwKQoKcm93RGF0YShwZTNbWyJwZXB0aWRlUmF3Il1dKSRuTm9uWmVybyA8LSByb3dTdW1zKGFzc2F5KHBlM1tbInBlcHRpZGVSYXciXV0pID4gMCkKYGBgCgotIFBlcHRpZGVzIHdpdGggemVybyBpbnRlbnNpdGllcyBhcmUgbWlzc2luZyBwZXB0aWRlcyBhbmQgc2hvdWxkIGJlIHJlcHJlc2VudAp3aXRoIGEgYE5BYCB2YWx1ZSByYXRoZXIgdGhhbiBgMGAuCgpgYGB7cn0KcGUgPC0gemVyb0lzTkEocGUsICJwZXB0aWRlUmF3IikgIyBjb252ZXJ0IDAgdG8gTkEKCnBlMiA8LSB6ZXJvSXNOQShwZTIsICJwZXB0aWRlUmF3IikgIyBjb252ZXJ0IDAgdG8gTkEKCnBlMyA8LSB6ZXJvSXNOQShwZTMsICJwZXB0aWRlUmF3IikgIyBjb252ZXJ0IDAgdG8gTkEKYGBgCgotIExvZ3RyYW5zZm9ybSBkYXRhIHdpdGggYmFzZSAyCgpgYGB7cn0KcGUgPC0gbG9nVHJhbnNmb3JtKHBlLCBiYXNlID0gMiwgaSA9ICJwZXB0aWRlUmF3IiwgbmFtZSA9ICJwZXB0aWRlTG9nIikKCnBlMiA8LSBsb2dUcmFuc2Zvcm0ocGUyLCBiYXNlID0gMiwgaSA9ICJwZXB0aWRlUmF3IiwgbmFtZSA9ICJwZXB0aWRlTG9nIikKCnBlMyA8LSBsb2dUcmFuc2Zvcm0ocGUzLCBiYXNlID0gMiwgaSA9ICJwZXB0aWRlUmF3IiwgbmFtZSA9ICJwZXB0aWRlTG9nIikKYGBgCjwvcD48L2RldGFpbHM+CgoKIyMjIyBGaWx0ZXJpbmcKPGRldGFpbHM+PHN1bW1hcnk+IENsaWNrIHRvIHNlZSBkZXRhaWxzIG9uIGZpbHRlcmluZyA8L3N1bW1hcnk+PHA+CgoxLiBIYW5kbGluZyBvdmVybGFwcGluZyBwcm90ZWluIGdyb3VwcwoKSW4gb3VyIGFwcHJvYWNoIGEgcGVwdGlkZSBjYW4gbWFwIHRvIG11bHRpcGxlIHByb3RlaW5zLCBhcyBsb25nIGFzIHRoZXJlIGlzCm5vbmUgb2YgdGhlc2UgcHJvdGVpbnMgcHJlc2VudCBpbiBhIHNtYWxsZXIgc3ViZ3JvdXAuCgpgYGB7cn0KcGUgPC0gZmlsdGVyRmVhdHVyZXMocGUsIH4gUHJvdGVpbnMgJWluJSBzbWFsbGVzdFVuaXF1ZUdyb3Vwcyhyb3dEYXRhKHBlW1sicGVwdGlkZUxvZyJdXSkkUHJvdGVpbnMpKQoKcGUyIDwtIGZpbHRlckZlYXR1cmVzKHBlMiwgfiBQcm90ZWlucyAlaW4lIHNtYWxsZXN0VW5pcXVlR3JvdXBzKHJvd0RhdGEocGUyW1sicGVwdGlkZUxvZyJdXSkkUHJvdGVpbnMpKQoKcGUzIDwtIGZpbHRlckZlYXR1cmVzKHBlMywgfiBQcm90ZWlucyAlaW4lIHNtYWxsZXN0VW5pcXVlR3JvdXBzKHJvd0RhdGEocGUzW1sicGVwdGlkZUxvZyJdXSkkUHJvdGVpbnMpKQpgYGAKMi4gUmVtb3ZlIHJldmVyc2Ugc2VxdWVuY2VzIChkZWNveXMpIGFuZCBjb250YW1pbmFudHMKCldlIG5vdyByZW1vdmUgdGhlIGNvbnRhbWluYW50cywgcGVwdGlkZXMgdGhhdCBtYXAgdG8gZGVjb3kgc2VxdWVuY2VzLCBhbmQgcHJvdGVpbnMKd2hpY2ggd2VyZSBvbmx5IGlkZW50aWZpZWQgYnkgcGVwdGlkZXMgd2l0aCBtb2RpZmljYXRpb25zLgoKYGBge3J9CnBlIDwtIGZpbHRlckZlYXR1cmVzKHBlLH5SZXZlcnNlICE9ICIrIikKcGUgPC0gZmlsdGVyRmVhdHVyZXMocGUsfiBQb3RlbnRpYWwuY29udGFtaW5hbnQgIT0gIisiKQoKcGUyIDwtIGZpbHRlckZlYXR1cmVzKHBlMix+UmV2ZXJzZSAhPSAiKyIpCnBlMiA8LSBmaWx0ZXJGZWF0dXJlcyhwZTIsfiBQb3RlbnRpYWwuY29udGFtaW5hbnQgIT0gIisiKQoKcGUzIDwtIGZpbHRlckZlYXR1cmVzKHBlMyx+UmV2ZXJzZSAhPSAiKyIpCnBlMyA8LSBmaWx0ZXJGZWF0dXJlcyhwZTMsfiBQb3RlbnRpYWwuY29udGFtaW5hbnQgIT0gIisiKQpgYGAKCgozLiBEcm9wIHBlcHRpZGVzIHRoYXQgd2VyZSBvbmx5IGlkZW50aWZpZWQgaW4gb25lIHNhbXBsZQoKV2Uga2VlcCBwZXB0aWRlcyB0aGF0IHdlcmUgb2JzZXJ2ZWQgYXQgbGFzdCB0d2ljZS4KCmBgYHtyfQpwZSA8LSBmaWx0ZXJGZWF0dXJlcyhwZSx+IG5Ob25aZXJvID49MikKbnJvdyhwZVtbInBlcHRpZGVMb2ciXV0pCgpwZTIgPC0gZmlsdGVyRmVhdHVyZXMocGUyLH4gbk5vblplcm8gPj0yKQpucm93KHBlMltbInBlcHRpZGVMb2ciXV0pCgpwZTMgPC0gZmlsdGVyRmVhdHVyZXMocGUzLH4gbk5vblplcm8gPj0yKQpucm93KHBlM1tbInBlcHRpZGVMb2ciXV0pCmBgYAoKPC9wPjwvZGV0YWlscz4KCiMjIyMgTm9ybWFsaXphdGlvbiAKCjxkZXRhaWxzPjxzdW1tYXJ5PiBDbGljayB0byBzZWUgY29kZSB0byBub3JtYWxpemUgdGhlIGRhdGEgPC9zdW1tYXJ5PjxwPgpgYGB7cn0KcGUgPC0gbm9ybWFsaXplKHBlLCAKICAgICAgICAgICAgICAgIGkgPSAicGVwdGlkZUxvZyIsIAogICAgICAgICAgICAgICAgbmFtZSA9ICJwZXB0aWRlTm9ybSIsIAogICAgICAgICAgICAgICAgbWV0aG9kID0gImNlbnRlci5tZWRpYW4iKQoKcGUyIDwtIG5vcm1hbGl6ZShwZTIsIAogICAgICAgICAgICAgICAgaSA9ICJwZXB0aWRlTG9nIiwgCiAgICAgICAgICAgICAgICBuYW1lID0gInBlcHRpZGVOb3JtIiwgCiAgICAgICAgICAgICAgICBtZXRob2QgPSAiY2VudGVyLm1lZGlhbiIpCgoKcGUzIDwtIG5vcm1hbGl6ZShwZTMsIAogICAgICAgICAgICAgICAgaSA9ICJwZXB0aWRlTG9nIiwgCiAgICAgICAgICAgICAgICBuYW1lID0gInBlcHRpZGVOb3JtIiwgCiAgICAgICAgICAgICAgICBtZXRob2QgPSAiY2VudGVyLm1lZGlhbiIpCmBgYAoKPC9wPjwvZGV0YWlscz4KCiMjIyMgU3VtbWFyaXphdGlvbgoKPGRldGFpbHM+PHN1bW1hcnk+IENsaWNrIHRvIHNlZSBjb2RlIHRvIHN1bW1hcml6ZSB0aGUgZGF0YSA8L3N1bW1hcnk+PHA+CgpgYGB7cix3YXJuaW5nPUZBTFNFfQpwZSA8LSBhZ2dyZWdhdGVGZWF0dXJlcyhwZSwKIGkgPSAicGVwdGlkZU5vcm0iLAogZmNvbCA9ICJQcm90ZWlucyIsCiBuYS5ybSA9IFRSVUUsCiBuYW1lID0gInByb3RlaW4iKQoKCnBlMiA8LSBhZ2dyZWdhdGVGZWF0dXJlcyhwZTIsCiBpID0gInBlcHRpZGVOb3JtIiwKIGZjb2wgPSAiUHJvdGVpbnMiLAogbmEucm0gPSBUUlVFLAogbmFtZSA9ICJwcm90ZWluIikKCnBlMyA8LSBhZ2dyZWdhdGVGZWF0dXJlcyhwZTMsCiBpID0gInBlcHRpZGVOb3JtIiwKIGZjb2wgPSAiUHJvdGVpbnMiLAogbmEucm0gPSBUUlVFLAogbmFtZSA9ICJwcm90ZWluIikKYGBgCgo8L3A+PC9kZXRhaWxzPgoKIyMjIERhdGEgRXhwbG9yYXRpb246IHdoYXQgaXMgaW1wYWN0IG9mIGJsb2NraW5nPyAKCjxkZXRhaWxzPjxzdW1tYXJ5PiBDbGljayB0byBzZWUgY29kZSA8L3N1bW1hcnk+PHA+CmBgYHtyfQpsZXZlbHMoY29sRGF0YShwZTMpJG1vdXNlKSA8LSBwYXN0ZTAoIm0iLDE6NykKbWRzT2JqMyA8LSBwbG90TURTKGFzc2F5KHBlM1tbInByb3RlaW4iXV0pLCBwbG90ID0gRkFMU0UpCm1kc09yaWcgPC0gY29sRGF0YShwZTMpICU+JQogIGFzLmRhdGEuZnJhbWUgJT4lCiAgbXV0YXRlKG1kczEgPSBtZHNPYmozJHgsCiAgICAgICAgIG1kczIgPSBtZHNPYmozJHksCiAgICAgICAgIGxhYiA9IHBhc3RlKG1vdXNlLGNlbGx0eXBlLHNlcD0iXyIpKSAlPiUKICBnZ3Bsb3QoYWVzKHggPSBtZHMxLCB5ID0gbWRzMiwgbGFiZWwgPSBsYWIsIGNvbG9yID0gY2VsbHR5cGUsIGdyb3VwID0gbW91c2UpKSArCiAgZ2VvbV90ZXh0KHNob3cubGVnZW5kID0gRkFMU0UpICsKICBnZW9tX3BvaW50KHNoYXBlID0gMjEpICsKICBnZW9tX2xpbmUoY29sb3IgPSAiYmxhY2siLCBsaW5ldHlwZSA9ICJkYXNoZWQiKSArCiAgeGxhYigKICAgIHBhc3RlMCgKICAgICAgbWRzT2JqMyRheGlzbGFiZWwsCiAgICAgICIgIiwKICAgICAgMSwgCiAgICAgICIgKCIsCiAgICAgIHBhc3RlMCgKICAgICAgICByb3VuZChtZHNPYmozJHZhci5leHBsYWluZWRbMV0gKjEwMCwwKSwKICAgICAgICAiJSIKICAgICAgICApLAogICAgICAiKSIKICAgICAgKQogICAgKSArCiAgeWxhYigKICAgIHBhc3RlMCgKICAgICAgbWRzT2JqMyRheGlzbGFiZWwsCiAgICAgICIgIiwKICAgICAgMiwgCiAgICAgICIgKCIsCiAgICAgIHBhc3RlMCgKICAgICAgICByb3VuZChtZHNPYmozJHZhci5leHBsYWluZWRbMl0gKjEwMCwwKSwKICAgICAgICAiJSIKICAgICAgICApLAogICAgICAiKSIKICAgICAgKQogICAgKSArCiAgZ2d0aXRsZSgiT3JpZ2luYWwgKFJDQikiKQoKbGV2ZWxzKGNvbERhdGEocGUpJG1vdXNlKSA8LSBwYXN0ZTAoIm0iLDE6NCkKbWRzT2JqIDwtIHBsb3RNRFMoYXNzYXkocGVbWyJwcm90ZWluIl1dKSwgcGxvdCA9IEZBTFNFKQptZHNSQ0IgPC0gY29sRGF0YShwZSkgJT4lCiAgYXMuZGF0YS5mcmFtZSAlPiUKICBtdXRhdGUobWRzMSA9IG1kc09iaiR4LAogICAgICAgICBtZHMyID0gbWRzT2JqJHksCiAgICAgICAgIGxhYiA9IHBhc3RlKG1vdXNlLGNlbGx0eXBlLHNlcD0iXyIpKSAlPiUKICBnZ3Bsb3QoYWVzKHggPSBtZHMxLCB5ID0gbWRzMiwgbGFiZWwgPSBsYWIsIGNvbG9yID0gY2VsbHR5cGUsIGdyb3VwID0gbW91c2UpKSArCiAgZ2VvbV90ZXh0KHNob3cubGVnZW5kID0gRkFMU0UpICsKICBnZW9tX3BvaW50KHNoYXBlID0gMjEpICsKICBnZW9tX2xpbmUoY29sb3IgPSAiYmxhY2siLCBsaW5ldHlwZSA9ICJkYXNoZWQiKSArCiAgeGxhYigKICAgIHBhc3RlMCgKICAgICAgbWRzT2JqJGF4aXNsYWJlbCwKICAgICAgIiAiLAogICAgICAxLCAKICAgICAgIiAoIiwKICAgICAgcGFzdGUwKAogICAgICAgIHJvdW5kKG1kc09iaiR2YXIuZXhwbGFpbmVkWzFdICoxMDAsMCksCiAgICAgICAgIiUiCiAgICAgICAgKSwKICAgICAgIikiCiAgICAgICkKICAgICkgKwogIHlsYWIoCiAgICBwYXN0ZTAoCiAgICAgIG1kc09iaiRheGlzbGFiZWwsCiAgICAgICIgIiwKICAgICAgMiwgCiAgICAgICIgKCIsCiAgICAgIHBhc3RlMCgKICAgICAgICByb3VuZChtZHNPYmokdmFyLmV4cGxhaW5lZFsyXSAqMTAwLDApLAogICAgICAgICIlIgogICAgICAgICksCiAgICAgICIpIgogICAgICApCiAgICApICsKICBnZ3RpdGxlKCJSYW5kb21pemVkIENvbXBsZXRlIEJsb2NrIChSQ0IpIikKCgpsZXZlbHMoY29sRGF0YShwZTIpJG1vdXNlKSA8LSBwYXN0ZTAoIm0iLDE6OCkKbWRzT2JqMiA8LSBwbG90TURTKGFzc2F5KHBlMltbInByb3RlaW4iXV0pLCBwbG90ID0gRkFMU0UpCm1kc0NSRCA8LSBjb2xEYXRhKHBlMikgJT4lCiAgYXMuZGF0YS5mcmFtZSAlPiUKICBtdXRhdGUobWRzMSA9IG1kc09iajIkeCwKICAgICAgICAgbWRzMiA9IG1kc09iajIkeSwKICAgICAgICAgbGFiID0gcGFzdGUobW91c2UsY2VsbHR5cGUsc2VwPSJfIikpICU+JQogIGdncGxvdChhZXMoeCA9IG1kczEsIHkgPSBtZHMyLCBsYWJlbCA9IGxhYiwgY29sb3IgPSBjZWxsdHlwZSwgZ3JvdXAgPSBtb3VzZSkpICsKICBnZW9tX3RleHQoc2hvdy5sZWdlbmQgPSBGQUxTRSkgKwogIGdlb21fcG9pbnQoc2hhcGUgPSAyMSkgKwogIHhsYWIoCiAgICBwYXN0ZTAoCiAgICAgIG1kc09iaiRheGlzbGFiZWwsCiAgICAgICIgIiwKICAgICAgMSwgCiAgICAgICIgKCIsCiAgICAgIHBhc3RlMCgKICAgICAgICByb3VuZChtZHNPYmoyJHZhci5leHBsYWluZWRbMV0gKjEwMCwwKSwKICAgICAgICAiJSIKICAgICAgICApLAogICAgICAiKSIKICAgICAgKQogICAgKSArCiAgeWxhYigKICAgIHBhc3RlMCgKICAgICAgbWRzT2JqJGF4aXNsYWJlbCwKICAgICAgIiAiLAogICAgICAyLCAKICAgICAgIiAoIiwKICAgICAgcGFzdGUwKAogICAgICAgIHJvdW5kKG1kc09iajIkdmFyLmV4cGxhaW5lZFsyXSAqMTAwLDApLAogICAgICAgICIlIgogICAgICAgICksCiAgICAgICIpIgogICAgICApCiAgICApICsKICBnZ3RpdGxlKCJDb21wbGV0ZWx5IFJhbmRvbWl6ZWQgRGVzaWduIChDUkQpIikKYGBgCjwvcD48L2RldGFpbHM+CmBgYHtyfQptZHNPcmlnCm1kc1JDQgptZHNDUkQKYGBgCgotIFdlIG9ic2VydmUgdGhhdCB0aGUgbGVhZGluZyBmb2xkIGNoYW5nZSBpcyBhY2NvcmRpbmcgdG8gbW91c2UKLSBJbiB0aGUgc2Vjb25kIGRpbWVuc2lvbiB3ZSBzZWUgYSBzZXBhcmF0aW9uIGFjY29yZGluZyB0byBjZWxsLXR5cGUgCi0gV2l0aCB0aGUgUmFuZG9taXplZCBDb21wbGV0ZSBCbG9jayBkZXNpZ24gKFJDQikgd2UgY2FuIHJlbW92ZSB0aGUgbW91c2UgZWZmZWN0IGZyb20gdGhlIGFuYWx5c2lzIQoKCgoKLSBXZSBjYW4gaXNvbGF0ZSB0aGUgYmV0d2VlbiBibG9jayB2YXJpYWJpbGl0eSBmcm9tIHRoZSBhbmFseXNpcyB1c2luZyBsaW5lYXIgbW9kZWw6CiAgCiAgLSBGb3JtdWxhIGluIFIKJCQgCnkgXHNpbSBcdGV4dHtjZWxsdHlwZX0gKyBcdGV4dHttb3VzZX0KJCQKICAKICAtIEZvcm11bGEgCgokJAp5X2kgPSBcYmV0YV8wICsgXGJldGFfXHRleHR7VHJlZ30geF97aSxcdGV4dHtUcmVnfX0gKyBcYmV0YV97bTJ9eF97aSxtMn0gKyBcYmV0YV97bTN9eF97aSxtM30gKyBcYmV0YV97bTR9eF97aSxtNH0gICsgXGVwc2lsb25faQokJAoKd2l0aAoKLSAkeF97aSxUcmVnfT1cYmVnaW57Y2FzZXN9CjEmIFx0ZXh0e1RyZWd9XFwKMCYgXHRleHR7VGNvbn0KXGVuZHtjYXNlc30kCgotICR4X3tpLG0yfT1cYmVnaW57Y2FzZXN9CjEmIFx0ZXh0e20yfVxcCjAmIFx0ZXh0e290aGVyd2lzZX0KXGVuZHtjYXNlc30kCgotICR4X3tpLG0zfT1cYmVnaW57Y2FzZXN9CjEmIFx0ZXh0e20zfVxcCjAmIFx0ZXh0e290aGVyd2lzZX0KXGVuZHtjYXNlc30kCi0gJHhfe2ksbTR9PVxiZWdpbntjYXNlc30KMSYgXHRleHR7bTR9XFwKMCYgXHRleHR7b3RoZXJ3aXNlfQpcZW5ke2Nhc2VzfSQKLSBQb3NzaWJsZSBpbiBtc3Fyb2IyIGFuZCBNU3N0YXRzIGJ1dCBub3QgcG9zc2libGUgd2l0aCBQZXJzZXVzIQoKCgojIyBNb2RlbGluZyBhbmQgaW5mZXJlbmNlCgojIyMgUkNCIGFuYWx5c2lzCmBgYHtyIHdhcm5pbmc9RkFMU0V9CnBlIDwtIG1zcXJvYigKICBvYmplY3QgPSBwZSwKICBpID0gInByb3RlaW4iLAogIGZvcm11bGEgPSB+IGNlbGx0eXBlICsgbW91c2UpCmBgYAoKCiMjIyBDUkQgYW5hbHlzaXMgCmBgYHtyIHdhcm5pbmcgPSBGQUxTRX0KcGUyIDwtIG1zcXJvYigKICBvYmplY3QgPSBwZTIsCiAgaSA9ICJwcm90ZWluIiwKICBmb3JtdWxhID0gfiBjZWxsdHlwZSkKYGBgCgojIyMgRXN0aW1hdGlvbiwgZWZmZWN0IHNpemUgYW5kIGluZmVyZW5jZQoKRWZmZWN0IHNpemUgaW4gUkNCCmBgYHtyfQpsaWJyYXJ5KEV4cGxvcmVNb2RlbE1hdHJpeCkKVmlzdWFsaXplRGVzaWduKGNvbERhdGEocGUpLH4gY2VsbHR5cGUgKyBtb3VzZSkkcGxvdGxpc3QKYGBgCgpFZmZlY3Qgc2l6ZSBpbiBDUkQKYGBge3J9ClZpc3VhbGl6ZURlc2lnbihjb2xEYXRhKHBlMiksfiBjZWxsdHlwZSkkcGxvdGxpc3QKYGBgCgoKPGRldGFpbHM+PHN1bW1hcnk+IENsaWNrIHRvIHNlZSBjb2RlIGZvciBzdGF0aXN0aWNhbCBpbmZlcmVuY2UgPC9zdW1tYXJ5PjxwPgpgYGB7cn0KTCA8LSBtYWtlQ29udHJhc3QoImNlbGx0eXBlVHJlZyA9IDAiLCBwYXJhbWV0ZXJOYW1lcyA9IGMoImNlbGx0eXBlVHJlZyIpKQpwZSA8LSBoeXBvdGhlc2lzVGVzdChvYmplY3QgPSBwZSwgaSA9ICJwcm90ZWluIiwgY29udHJhc3QgPSBMKQpwZTIgPC0gaHlwb3RoZXNpc1Rlc3Qob2JqZWN0ID0gcGUyLCBpID0gInByb3RlaW4iLCBjb250cmFzdCA9IEwpCmBgYAo8L3A+PC9kZXRhaWxzPgoKIyMjIENvbXBhcmlzb24gb2YgcmVzdWx0cwoKPGRldGFpbHM+PHN1bW1hcnk+IENsaWNrIHRvIHNlZSBjb2RlIDwvc3VtbWFyeT48cD4KYGBge3Igd2FybmluZz1GQUxTRSxlY2hvPUZBTFNFfQp2b2xjYW5vUkNCIDwtIGdncGxvdCgKICAgIHJvd0RhdGEocGVbWyJwcm90ZWluIl1dKSRjZWxsdHlwZVRyZWcsCiAgICBhZXMoeCA9IGxvZ0ZDLCB5ID0gLWxvZzEwKHB2YWwpLCBjb2xvciA9IGFkalB2YWwgPCAwLjA1KQopICsKICAgIGdlb21fcG9pbnQoY2V4ID0gMi41KSArCiAgICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gYWxwaGEoYygiYmxhY2siLCAicmVkIiksIDAuNSkpICsKICAgIHRoZW1lX21pbmltYWwoKSArCiAgICBnZ3RpdGxlKHBhc3RlMCgiUkNCOiBcbiIsIAogICAgICAgICAgICAgICAgc3VtKHJvd0RhdGEocGVbWyJwcm90ZWluIl1dKSRjZWxsdHlwZVRyZWckYWRqUHZhbDwwLjA1LG5hLnJtPVRSVUUpLAogICAgICAgICAgICAiIHNpZ25pZmljYW50IikpCgp2b2xjYW5vQ1JEIDwtIGdncGxvdCgKICAgIHJvd0RhdGEocGUyW1sicHJvdGVpbiJdXSkkY2VsbHR5cGVUcmVnLAogICAgYWVzKHggPSBsb2dGQywgeSA9IC1sb2cxMChwdmFsKSwgY29sb3IgPSBhZGpQdmFsIDwgMC4wNSkKKSArCiAgICBnZW9tX3BvaW50KGNleCA9IDIuNSkgKwogICAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGFscGhhKGMoImJsYWNrIiwgInJlZCIpLCAwLjUpKSArCiAgICB0aGVtZV9taW5pbWFsKCkgKwogICAgZ2d0aXRsZShwYXN0ZTAoIkNSRDogXG4iLCAKICAgICAgICAgICAgICAgIHN1bShyb3dEYXRhKHBlMltbInByb3RlaW4iXV0pJGNlbGx0eXBlVHJlZyRhZGpQdmFsPDAuMDUsbmEucm09VFJVRSksCiAgICAgICAgICAgICIgc2lnbmlmaWNhbnQiKSkKCnhsaW1zIDwtIChyYW5nZShyb3dEYXRhKHBlW1sicHJvdGVpbiJdXSkkY2VsbHR5cGVUcmVnJGxvZ0ZDLCAKICAgICAgICAgICAgICByb3dEYXRhKHBlMltbInByb3RlaW4iXV0pJGNlbGx0eXBlVHJlZyRsb2dGQywKICAgICAgICAgICAgICBuYS5ybT1UUlVFKSAlPiUgYWJzICU+JSBtYXgpICogYygtMSwxKQp5bGltcyA8LSByYW5nZSgtbG9nMTAocm93RGF0YShwZVtbInByb3RlaW4iXV0pJGNlbGx0eXBlVHJlZyRwdmFsKSwgCiAgICAgICAgICAgICAgLWxvZzEwKHJvd0RhdGEocGUyW1sicHJvdGVpbiJdXSkkY2VsbHR5cGVUcmVnJGxvZ0ZDKSwgCiAgICAgICAgICAgICAgbmEucm09VFJVRSkKYGBgCjwvcD48L2RldGFpbHM+CiAgCmBgYHtyIHdhcm5pbmc9RkFMU0UsZWNobz1GQUxTRX0Kdm9sY2Fub1JDQiArIAogIHhsaW0oeGxpbXMpICsgCiAgeWxpbSh5bGltcykgCnZvbGNhbm9DUkQgKyAKICB4bGltKHhsaW1zKSArIAogIHlsaW0oeWxpbXMpCmBgYAoKIyMjIENvbXBhcmlzb24gb2Ygc3RhbmRhcmQgZGV2aWF0aW9uIAoKPGRldGFpbHM+PHN1bW1hcnk+IENsaWNrIHRvIHNlZSBjb2RlIDwvc3VtbWFyeT48cD4KYGBge3J9CmFjY2Vzc2lvbnMgPC0gcm93bmFtZXMocGVbWyJwcm90ZWluIl1dKVtyb3duYW1lcyhwZVtbInByb3RlaW4iXV0pJWluJXJvd25hbWVzKHBlMltbInByb3RlaW4iXV0pXQpkYXQgPC0gZGF0YS5mcmFtZSgKc2lnbWFSQkMgPSBzYXBwbHkocm93RGF0YShwZVtbInByb3RlaW4iXV0pJG1zcXJvYk1vZGVsc1thY2Nlc3Npb25zXSwgZ2V0U2lnbWFQb3N0ZXJpb3IpLApzaWdtYUNSRCA8LSBzYXBwbHkocm93RGF0YShwZTJbWyJwcm90ZWluIl1dKSRtc3Fyb2JNb2RlbHNbYWNjZXNzaW9uc10sIGdldFNpZ21hUG9zdGVyaW9yKQopCgpwbG90UkJDdnNDUkQgPC0gZ2dwbG90KGRhdGEgPSBkYXQsIGFlcyhzaWdtYVJCQywgc2lnbWFDUkQpKSArCiAgICBnZW9tX3BvaW50KGFscGhhID0gMC4xLCBzaGFwZSA9IDIwKSArCiAgICBzY2FsZV94X2xvZzEwKCkgKwogICAgc2NhbGVfeV9sb2cxMCgpICsKICAgIGdlb21fYWJsaW5lKGludGVyY2VwdD0wLHNsb3BlPTEpCmBgYAo8L3A+PC9kZXRhaWxzPgoKYGBge3J9CiAgcGxvdFJCQ3ZzQ1JECmBgYAoKLSBXZSBjbGVhcmx5IG9ic2VydmUgdGhhdCB0aGUgc3RhbmRhcmQgZGV2aWF0aW9uIG9mIHRoZSBwcm90ZWluIGV4cHJlc3Npb24gaW4gdGhlIFJDQiBpcyBzbWFsbGVyIGZvciB0aGUgbWFqb3JpdHkgb2YgdGhlIHByb3RlaW5zIHRoYW4gdGhhdCBvYnRhaW5lZCB3aXRoIHRoZSBDUkQKCi0gV2h5IGFyZSBzb21lIG9mIHRoZSBzdGFuZGFyZCBkZXZpYXRpb25zIGZvciB0aGUgUkNCIHdpdGggdGhlIGNvcnJlY3QgYW5hbHlzaXMgbGFyZ2VyIHRoYW4gdGhhbiBvZiB0aGUgUkNCIHdpdGggdGhlIGluY29ycmVjdCBhbmFseXNpcyB0aGF0IGlnbm9yZWQgdGhlIG1vdXNlIGJsb2NraW5nIGZhY3Rvcj8KCi0gQ2FuIHlvdSB0aGluayBvZiBhIHJlYXNvbiB3aHkgaXQgd291bGQgbm90IGJlIHVzZWZ1bCB0byBibG9jayBvbiBhIHBhcnRpY3VsYXIgZmFjdG9yPyAKCg==