Creative Commons License

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

1 Import Data and Preprocessing

1.1 Data

Click to see code

library(tidyverse)
library(limma)
library(QFeatures)
library(msqrob2)
library(plotly)
library(ggplot2)
library(data.table)
library(gridExtra)
peptidesTable <- fread("https://raw.githubusercontent.com/statOmics/PDA21/data/quantification/mouseTcell/peptidesRCB.txt")
int64 <- which(sapply(peptidesTable,class) == "integer64")
for (j in int64) peptidesTable[[j]] <- as.numeric(peptidesTable[[j]]) 

peptidesTable2 <- fread("https://raw.githubusercontent.com/statOmics/PDA21/data/quantification/mouseTcell/peptidesCRD.txt")
int64 <- which(sapply(peptidesTable2,class) == "integer64")
for (j in int64) peptidesTable2[[j]] <- as.numeric(peptidesTable2[[j]]) 

peptidesTable3 <- fread("https://raw.githubusercontent.com/statOmics/PDA21/data/quantification/mouseTcell/peptides.txt")
int64 <- which(sapply(peptidesTable3,class) == "integer64")
for (j in int64) peptidesTable3[[j]] <- as.numeric(peptidesTable3[[j]]) 

quantCols <- grep("Intensity ", names(peptidesTable))
pe <- readQFeatures(
  assayData = peptidesTable,
  fnames = 1,
  quantCols =  quantCols,
  name = "peptideRaw")
rm(peptidesTable)
gc()
##            used  (Mb) gc trigger  (Mb) limit (Mb) max used  (Mb)
## Ncells  8497634 453.9   12240530 653.8         NA 12240530 653.8
## Vcells 44722037 341.3   73581861 561.4      16384 73581855 561.4
gc()
##            used  (Mb) gc trigger  (Mb) limit (Mb) max used  (Mb)
## Ncells  8497612 453.9   12240530 653.8         NA 12240530 653.8
## Vcells 44715558 341.2   73581861 561.4      16384 73581855 561.4
quantCols2 <- grep("Intensity ", names(peptidesTable2))
pe2 <- readQFeatures(
  assayData = peptidesTable2,
  fnames = 1,
  quantCols =  quantCols2,
  name = "peptideRaw")
rm(peptidesTable2)
gc()
##            used  (Mb) gc trigger  (Mb) limit (Mb) max used  (Mb)
## Ncells  8409494 449.2   12240530 653.8         NA 12240530 653.8
## Vcells 38110740 290.8   73581861 561.4      16384 73581855 561.4
gc()
##            used  (Mb) gc trigger  (Mb) limit (Mb) max used  (Mb)
## Ncells  8409493 449.2   12240530 653.8         NA 12240530 653.8
## Vcells 38106457 290.8   73581861 561.4      16384 73581855 561.4
quantCols3 <- grep("Intensity ", names(peptidesTable3))
pe3 <- readQFeatures(
  assayData = peptidesTable3,
  fnames = 1,
  quantCols =  quantCols3,
  name = "peptideRaw")
rm(peptidesTable3)
gc()
##            used  (Mb) gc trigger  (Mb) limit (Mb) max used  (Mb)
## Ncells  8409080 449.1   12240530 653.8         NA 12240530 653.8
## Vcells 29422356 224.5   73581861 561.4      16384 73581855 561.4
gc()
##            used  (Mb) gc trigger  (Mb) limit (Mb) max used  (Mb)
## Ncells  8409005 449.1   12240530 653.8         NA 12240530 653.8
## Vcells 29417798 224.5   73581861 561.4      16384 73581855 561.4
### 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()

1.2 Preprocessing

1.2.0.1 Log-transform

Click to see code to log-transfrom the data

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

1.2.1 Filtering

Click to see details on filtering

  1. Remove peptides that map to multiple proteins

We remove PSMs that could not be mapped to a protein or that map to multiple proteins (the protein identifier contains multiple identifiers separated by a ;).

pe <- filterFeatures(
    pe, ~ Proteins != "" & ## Remove failed protein inference
        !grepl(";", Proteins)) ## Remove protein groups
## 'Proteins' found in 2 out of 2 assay(s).
pe2 <- filterFeatures(
    pe2, ~ Proteins != "" & ## Remove failed protein inference
        !grepl(";", Proteins)) ## Remove protein groups
## 'Proteins' found in 2 out of 2 assay(s).
pe3 <- filterFeatures(
    pe3, ~ Proteins != "" & ## Remove failed protein inference
        !grepl(";", Proteins)) ## Remove protein groups
## 'Proteins' found in 2 out of 2 assay(s).
  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 != "+")
## 'Reverse' found in 2 out of 2 assay(s).
pe <- filterFeatures(pe,~ Potential.contaminant != "+")
## 'Potential.contaminant' found in 2 out of 2 assay(s).
pe2 <- filterFeatures(pe2,~Reverse != "+")
## 'Reverse' found in 2 out of 2 assay(s).
pe2 <- filterFeatures(pe2,~ Potential.contaminant != "+")
## 'Potential.contaminant' found in 2 out of 2 assay(s).
pe3 <- filterFeatures(pe3,~Reverse != "+")
## 'Reverse' found in 2 out of 2 assay(s).
pe3 <- filterFeatures(pe3,~ Potential.contaminant != "+")
## 'Potential.contaminant' found in 2 out of 2 assay(s).
  1. Drop peptides that were identified in less than three sample

We keep peptides that were observed at least three times.

nObs <- 3
n <- ncol(pe[["peptideLog"]])
pNA <- (n-nObs)/n
pe <- filterNA(pe, pNA = pNA, i = "peptideLog")
nrow(pe[["peptideLog"]])
## [1] 38782
n <- ncol(pe2[["peptideLog"]])
pNA <- (n-nObs)/n
pe2 <- filterNA(pe2, pNA = pNA, i = "peptideLog")
nrow(pe2[["peptideLog"]])
## [1] 37898
n <- ncol(pe3[["peptideLog"]])
pNA <- (n-nObs)/n
pe3 <- filterNA(pe3, pNA = pNA, i = "peptideLog")
nrow(pe3[["peptideLog"]])
## [1] 43984

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

1.2.3 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.
## Aggregated: 1/1
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.
## Aggregated: 1/1
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.
## Aggregated: 1/1

1.2.4 Filtering proteins with too many missing values

We want to have at least two observed protein intensities for each group so we set the minimum number of observed values at 4. We still have to check for the observed proteins if that is the case.

For block design more clever filtering can be used. E.g. we could imply that we have both cell types in at least two animals…

nObs <- 4
n <- ncol(pe[["protein"]])
pNA <- (n-nObs)/n
pe<- filterNA(pe, pNA = pNA, i = "protein")

n <- ncol(pe2[["protein"]])
pNA <- (n-nObs)/n
pe2 <- filterNA(pe2, pNA = pNA, i = "protein")

n <- ncol(pe3[["protein"]])
pNA <- (n-nObs)/n
pe3 <- filterNA(pe3, pNA = pNA, i = "protein")

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

1.4 Modeling and inference

1.4.1 RCB analysis

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

1.4.2 RCB wrong analysis

pe <- msqrob(
  object = pe,
  i = "protein",
  formula = ~ celltype, modelColumnName = "wrongModel")

1.5 CRD analysis

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

1.5.1 Inference

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

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

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

2 Advantage of Blocking: comparison between designs

2.1 Volcano plots

Click to see code

volcanoRCB <- ggplot(
    rowData(pe[["protein"]])$celltypeTreg,
    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("RCB: \n", 
                sum(rowData(pe[["protein"]])$celltypeTreg$adjPval<0.05,na.rm=TRUE),
            " significant"))

volcanoRCBwrong <- ggplot(
    rowData(pe[["protein"]])$wrongcelltypeTreg,
    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("RCB wrong: \n", 
                sum(rowData(pe[["protein"]])$wrongcelltypeTreg$adjPval<0.05,na.rm=TRUE),
            " significant"))

volcanoCRD <- ggplot(
    rowData(pe2[["protein"]])$celltypeTreg,
    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("CRD: \n", 
                sum(rowData(pe2[["protein"]])$celltypeTreg$adjPval<0.05,na.rm=TRUE),
            " significant"))

grid.arrange(volcanoRCB,volcanoCRD, volcanoRCBwrong,ncol=2)
## Warning: Removed 453 rows containing missing values or values outside the scale range
## (`geom_point()`).
## Warning: Removed 133 rows containing missing values or values outside the scale range
## (`geom_point()`).
## Warning: Removed 55 rows containing missing values or values outside the scale range
## (`geom_point()`).

2.2 Anova table: Q7TPR4, Alpha-actinin-1

Disclaimer: the Anova analysis is only for didactical purposes. In practice we assess the hypotheses using msqrob2.

  • We illustrate the power gain of blocking using an Anova analysis on 1 protein.

  • Note, that msqrob2 will perform a similar analysis, but, it uses robust regression and it uses an empirical Bayes estimator for the variance.

prot <- "Q7TPR4"
dataHlp <- colData(pe) %>% 
  as.data.frame %>%
  mutate(intensity=assay(pe[["protein"]])[prot,],
         intensityCRD=assay(pe2[["protein"]])[prot,])

  anova(lm(intensity~ celltype + mouse, dataHlp)) 
## Analysis of Variance Table
## 
## Response: intensity
##           Df Sum Sq Mean Sq F value    Pr(>F)    
## celltype   1 5.4816  5.4816 483.074 0.0002062 ***
## mouse      3 0.4275  0.1425  12.557 0.0332459 *  
## Residuals  3 0.0340  0.0113                      
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
  anova(lm(intensity~ celltype,dataHlp))
## Analysis of Variance Table
## 
## Response: intensity
##           Df Sum Sq Mean Sq F value    Pr(>F)    
## celltype   1 5.4816  5.4816  71.264 0.0001508 ***
## Residuals  6 0.4615  0.0769                      
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
  anova(lm(intensityCRD~ celltype,dataHlp))
## Analysis of Variance Table
## 
## Response: intensityCRD
##           Df Sum Sq Mean Sq F value    Pr(>F)    
## celltype   1 5.3495  5.3495  61.763 0.0002245 ***
## Residuals  6 0.5197  0.0866                      
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

2.3 Comparison Empirical Bayes standard deviation in msqrob2

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),
sigmaRBCwrong = sapply(rowData(pe[["protein"]])$wrongModel[accessions], getSigmaPosterior),
sigmaCRD <- sapply(rowData(pe2[["protein"]])$msqrobModels[accessions], getSigmaPosterior)
)

 plotRBCvsWrong <- ggplot(data = dat, aes(sigmaRBC, sigmaRBCwrong)) +
    geom_point(alpha = 0.1, shape = 20) +
    scale_x_log10() +
    scale_y_log10() +
    geom_abline(intercept=0,slope=1)
plotCRDvsWrong <- ggplot(data = dat, aes(sigmaCRD, sigmaRBCwrong)) +
    geom_point(alpha = 0.1, shape = 20) +
    scale_x_log10() +
    scale_y_log10() +
    geom_abline(intercept=0,slope=1)
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)

grid.arrange(
  plotRBCvsWrong,
  plotCRDvsWrong,
  plotRBCvsCRD,
  nrow=2)
## Warning: Removed 316 rows containing missing values or values outside the scale range
## (`geom_point()`).
## Warning: Removed 124 rows containing missing values or values outside the scale range
## (`geom_point()`).
## Warning: Removed 351 rows containing missing values or values outside the scale range
## (`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

  • The standard deviation of the protein expression RCB where we perform a wrong analysis without considering the blocking factor according to mouse is much larger for the marjority of the proteins than that obtained with the correct analysis.

  • Indeed, when we ignore the blocking factor in the RCB design we do not remove the variability according to mouse from the analysis and the mouse effect is absorbed in the error term. The standard deviation than becomes very comparable to that observed in the completely randomised design where we could not remove the mouse effect from the analysis.

  • 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?

LS0tCnRpdGxlOiAiU3RhdGlzdGljYWwgTWV0aG9kcyBmb3IgUXVhbnRpdGF0aXZlIE1TLWJhc2VkIFByb3Rlb21pY3M6IEJsb2NraW5nIC0gV3JhcC11cCIKYXV0aG9yOiAiTGlldmVuIENsZW1lbnQiCmRhdGU6ICJbc3RhdE9taWNzXShodHRwczovL3N0YXRvbWljcy5naXRodWIuaW8pLCBHaGVudCBVbml2ZXJzaXR5IgpvdXRwdXQ6CiAgICBodG1sX2RvY3VtZW50OgogICAgICBjb2RlX2Rvd25sb2FkOiB0cnVlCiAgICAgIHRoZW1lOiBmbGF0bHkKICAgICAgdG9jOiB0cnVlCiAgICAgIHRvY19mbG9hdDogdHJ1ZQogICAgICBoaWdobGlnaHQ6IHRhbmdvCiAgICAgIG51bWJlcl9zZWN0aW9uczogdHJ1ZQogICAgcGRmX2RvY3VtZW50OgogICAgICB0b2M6IHRydWUKICAgICAgbnVtYmVyX3NlY3Rpb25zOiB0cnVlCmxpbmtjb2xvcjogYmx1ZQp1cmxjb2xvcjogYmx1ZQpjaXRlY29sb3I6IGJsdWUKCmJpYmxpb2dyYXBoeTogbXNxcm9iMi5iaWIKCi0tLQoKPGEgcmVsPSJsaWNlbnNlIiBocmVmPSJodHRwczovL2NyZWF0aXZlY29tbW9ucy5vcmcvbGljZW5zZXMvYnktbmMtc2EvNC4wIj48aW1nIGFsdD0iQ3JlYXRpdmUgQ29tbW9ucyBMaWNlbnNlIiBzdHlsZT0iYm9yZGVyLXdpZHRoOjAiIHNyYz0iaHR0cHM6Ly9pLmNyZWF0aXZlY29tbW9ucy5vcmcvbC9ieS1uYy1zYS80LjAvODh4MzEucG5nIiAvPjwvYT4KClRoaXMgaXMgcGFydCBvZiB0aGUgb25saW5lIGNvdXJzZSBbUHJvdGVvbWljcyBEYXRhIEFuYWx5c2lzIChQREEpXShodHRwczovL3N0YXRvbWljcy5naXRodWIuaW8vUERBLykKCiMgSW1wb3J0IERhdGEgYW5kIFByZXByb2Nlc3NpbmcgCgojIyBEYXRhIAoKPGRldGFpbHM+PHN1bW1hcnk+IENsaWNrIHRvIHNlZSBjb2RlICA8L3N1bW1hcnk+PHA+CgpgYGB7ciwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0KbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkobGltbWEpCmxpYnJhcnkoUUZlYXR1cmVzKQpsaWJyYXJ5KG1zcXJvYjIpCmxpYnJhcnkocGxvdGx5KQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkoZGF0YS50YWJsZSkKbGlicmFyeShncmlkRXh0cmEpCmBgYAoKYGBge3IsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9CgpwZXB0aWRlc1RhYmxlIDwtIGZyZWFkKCJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vc3RhdE9taWNzL1BEQTIxL2RhdGEvcXVhbnRpZmljYXRpb24vbW91c2VUY2VsbC9wZXB0aWRlc1JDQi50eHQiKQppbnQ2NCA8LSB3aGljaChzYXBwbHkocGVwdGlkZXNUYWJsZSxjbGFzcykgPT0gImludGVnZXI2NCIpCmZvciAoaiBpbiBpbnQ2NCkgcGVwdGlkZXNUYWJsZVtbal1dIDwtIGFzLm51bWVyaWMocGVwdGlkZXNUYWJsZVtbal1dKSAKCnBlcHRpZGVzVGFibGUyIDwtIGZyZWFkKCJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vc3RhdE9taWNzL1BEQTIxL2RhdGEvcXVhbnRpZmljYXRpb24vbW91c2VUY2VsbC9wZXB0aWRlc0NSRC50eHQiKQppbnQ2NCA8LSB3aGljaChzYXBwbHkocGVwdGlkZXNUYWJsZTIsY2xhc3MpID09ICJpbnRlZ2VyNjQiKQpmb3IgKGogaW4gaW50NjQpIHBlcHRpZGVzVGFibGUyW1tqXV0gPC0gYXMubnVtZXJpYyhwZXB0aWRlc1RhYmxlMltbal1dKSAKCnBlcHRpZGVzVGFibGUzIDwtIGZyZWFkKCJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vc3RhdE9taWNzL1BEQTIxL2RhdGEvcXVhbnRpZmljYXRpb24vbW91c2VUY2VsbC9wZXB0aWRlcy50eHQiKQppbnQ2NCA8LSB3aGljaChzYXBwbHkocGVwdGlkZXNUYWJsZTMsY2xhc3MpID09ICJpbnRlZ2VyNjQiKQpmb3IgKGogaW4gaW50NjQpIHBlcHRpZGVzVGFibGUzW1tqXV0gPC0gYXMubnVtZXJpYyhwZXB0aWRlc1RhYmxlM1tbal1dKSAKCnF1YW50Q29scyA8LSBncmVwKCJJbnRlbnNpdHkgIiwgbmFtZXMocGVwdGlkZXNUYWJsZSkpCnBlIDwtIHJlYWRRRmVhdHVyZXMoCiAgYXNzYXlEYXRhID0gcGVwdGlkZXNUYWJsZSwKICBmbmFtZXMgPSAxLAogIHF1YW50Q29scyA9ICBxdWFudENvbHMsCiAgbmFtZSA9ICJwZXB0aWRlUmF3IikKcm0ocGVwdGlkZXNUYWJsZSkKZ2MoKQpnYygpCgoKcXVhbnRDb2xzMiA8LSBncmVwKCJJbnRlbnNpdHkgIiwgbmFtZXMocGVwdGlkZXNUYWJsZTIpKQpwZTIgPC0gcmVhZFFGZWF0dXJlcygKICBhc3NheURhdGEgPSBwZXB0aWRlc1RhYmxlMiwKICBmbmFtZXMgPSAxLAogIHF1YW50Q29scyA9ICBxdWFudENvbHMyLAogIG5hbWUgPSAicGVwdGlkZVJhdyIpCnJtKHBlcHRpZGVzVGFibGUyKQpnYygpCmdjKCkKCnF1YW50Q29sczMgPC0gZ3JlcCgiSW50ZW5zaXR5ICIsIG5hbWVzKHBlcHRpZGVzVGFibGUzKSkKcGUzIDwtIHJlYWRRRmVhdHVyZXMoCiAgYXNzYXlEYXRhID0gcGVwdGlkZXNUYWJsZTMsCiAgZm5hbWVzID0gMSwKICBxdWFudENvbHMgPSAgcXVhbnRDb2xzMywKICBuYW1lID0gInBlcHRpZGVSYXciKQpybShwZXB0aWRlc1RhYmxlMykKZ2MoKQpnYygpCgojIyMgRGVzaWduCmNvbERhdGEocGUpJGNlbGx0eXBlIDwtIHN1YnN0cigKICBjb2xuYW1lcyhwZVtbInBlcHRpZGVSYXciXV0pLAogIDExLAogIDE0KSB8PgogIHVubGlzdCgpIHw+ICAKICBhcy5mYWN0b3IoKQoKY29sRGF0YShwZSkkbW91c2UgPC0gcGVbWzFdXSB8PgogIGNvbG5hbWVzKCkgfD4KICBzdHJzcGxpdChzcGxpdD0iWyBdIikgIHw+CiAgc2FwcGx5KGZ1bmN0aW9uKHgpIHhbM10pIHw+CiAgYXMuZmFjdG9yKCkKCmNvbERhdGEocGUyKSRjZWxsdHlwZSA8LSBzdWJzdHIoCiAgY29sbmFtZXMocGUyW1sicGVwdGlkZVJhdyJdXSksCiAgMTEsCiAgMTQpIHw+CiAgdW5saXN0KCkgfD4gIAogIGFzLmZhY3RvcigpCgpjb2xEYXRhKHBlMikkbW91c2UgPC0gcGUyW1sxXV0gfD4KICBjb2xuYW1lcygpIHw+CiAgc3Ryc3BsaXQoc3BsaXQ9IlsgXSIpICB8PgogIHNhcHBseShmdW5jdGlvbih4KSB4WzNdKSB8PgogIGFzLmZhY3RvcigpCgpjb2xEYXRhKHBlMykkY2VsbHR5cGUgPC0gc3Vic3RyKAogIGNvbG5hbWVzKHBlM1tbInBlcHRpZGVSYXciXV0pLAogIDExLAogIDE0KSB8PgogIHVubGlzdCgpIHw+ICAKICBhcy5mYWN0b3IoKQoKY29sRGF0YShwZTMpJG1vdXNlIDwtIHBlM1tbMV1dIHw+CiAgY29sbmFtZXMoKSB8PgogIHN0cnNwbGl0KHNwbGl0PSJbIF0iKSAgfD4KICBzYXBwbHkoZnVuY3Rpb24oeCkgeFszXSkgfD4KICBhcy5mYWN0b3IoKQpgYGAKPC9wPjwvZGVzaWduPgoKIyMgUHJlcHJvY2Vzc2luZyAKCgojIyMjIExvZy10cmFuc2Zvcm0KCjxkZXRhaWxzPjxzdW1tYXJ5PiBDbGljayB0byBzZWUgY29kZSB0byBsb2ctdHJhbnNmcm9tIHRoZSBkYXRhIDwvc3VtbWFyeT48cD4KCi0gUGVwdGlkZXMgd2l0aCB6ZXJvIGludGVuc2l0aWVzIGFyZSBtaXNzaW5nIHBlcHRpZGVzIGFuZCBzaG91bGQgYmUgcmVwcmVzZW50CndpdGggYSBgTkFgIHZhbHVlIHJhdGhlciB0aGFuIGAwYC4KCmBgYHtyfQpwZSA8LSB6ZXJvSXNOQShwZSwgInBlcHRpZGVSYXciKSAjIGNvbnZlcnQgMCB0byBOQQoKcGUyIDwtIHplcm9Jc05BKHBlMiwgInBlcHRpZGVSYXciKSAjIGNvbnZlcnQgMCB0byBOQQoKcGUzIDwtIHplcm9Jc05BKHBlMywgInBlcHRpZGVSYXciKSAjIGNvbnZlcnQgMCB0byBOQQpgYGAKCi0gTG9ndHJhbnNmb3JtIGRhdGEgd2l0aCBiYXNlIDIKCmBgYHtyfQpwZSA8LSBsb2dUcmFuc2Zvcm0ocGUsIGJhc2UgPSAyLCBpID0gInBlcHRpZGVSYXciLCBuYW1lID0gInBlcHRpZGVMb2ciKQoKcGUyIDwtIGxvZ1RyYW5zZm9ybShwZTIsIGJhc2UgPSAyLCBpID0gInBlcHRpZGVSYXciLCBuYW1lID0gInBlcHRpZGVMb2ciKQoKcGUzIDwtIGxvZ1RyYW5zZm9ybShwZTMsIGJhc2UgPSAyLCBpID0gInBlcHRpZGVSYXciLCBuYW1lID0gInBlcHRpZGVMb2ciKQpgYGAKPC9wPjwvZGV0YWlscz4KCgojIyMgRmlsdGVyaW5nCjxkZXRhaWxzPjxzdW1tYXJ5PiBDbGljayB0byBzZWUgZGV0YWlscyBvbiBmaWx0ZXJpbmcgPC9zdW1tYXJ5PjxwPgoKMS4gUmVtb3ZlIHBlcHRpZGVzIHRoYXQgbWFwIHRvIG11bHRpcGxlIHByb3RlaW5zCgpXZSByZW1vdmUgUFNNcyB0aGF0IGNvdWxkIG5vdCBiZSBtYXBwZWQgdG8gYSBwcm90ZWluIG9yIHRoYXQgbWFwCnRvIG11bHRpcGxlIHByb3RlaW5zICh0aGUgcHJvdGVpbiBpZGVudGlmaWVyIGNvbnRhaW5zIG11bHRpcGxlCmlkZW50aWZpZXJzIHNlcGFyYXRlZCBieSBhIGA7YCkuCgpgYGB7cn0KcGUgPC0gZmlsdGVyRmVhdHVyZXMoCiAgICBwZSwgfiBQcm90ZWlucyAhPSAiIiAmICMjIFJlbW92ZSBmYWlsZWQgcHJvdGVpbiBpbmZlcmVuY2UKICAgICAgICAhZ3JlcGwoIjsiLCBQcm90ZWlucykpICMjIFJlbW92ZSBwcm90ZWluIGdyb3VwcwoKcGUyIDwtIGZpbHRlckZlYXR1cmVzKAogICAgcGUyLCB+IFByb3RlaW5zICE9ICIiICYgIyMgUmVtb3ZlIGZhaWxlZCBwcm90ZWluIGluZmVyZW5jZQogICAgICAgICFncmVwbCgiOyIsIFByb3RlaW5zKSkgIyMgUmVtb3ZlIHByb3RlaW4gZ3JvdXBzCgpwZTMgPC0gZmlsdGVyRmVhdHVyZXMoCiAgICBwZTMsIH4gUHJvdGVpbnMgIT0gIiIgJiAjIyBSZW1vdmUgZmFpbGVkIHByb3RlaW4gaW5mZXJlbmNlCiAgICAgICAgIWdyZXBsKCI7IiwgUHJvdGVpbnMpKSAjIyBSZW1vdmUgcHJvdGVpbiBncm91cHMKYGBgCjIuIFJlbW92ZSByZXZlcnNlIHNlcXVlbmNlcyAoZGVjb3lzKSBhbmQgY29udGFtaW5hbnRzCgpXZSBub3cgcmVtb3ZlIHRoZSBjb250YW1pbmFudHMsIHBlcHRpZGVzIHRoYXQgbWFwIHRvIGRlY295IHNlcXVlbmNlcywgYW5kIHByb3RlaW5zCndoaWNoIHdlcmUgb25seSBpZGVudGlmaWVkIGJ5IHBlcHRpZGVzIHdpdGggbW9kaWZpY2F0aW9ucy4KCmBgYHtyfQpwZSA8LSBmaWx0ZXJGZWF0dXJlcyhwZSx+UmV2ZXJzZSAhPSAiKyIpCnBlIDwtIGZpbHRlckZlYXR1cmVzKHBlLH4gUG90ZW50aWFsLmNvbnRhbWluYW50ICE9ICIrIikKCnBlMiA8LSBmaWx0ZXJGZWF0dXJlcyhwZTIsflJldmVyc2UgIT0gIisiKQpwZTIgPC0gZmlsdGVyRmVhdHVyZXMocGUyLH4gUG90ZW50aWFsLmNvbnRhbWluYW50ICE9ICIrIikKCnBlMyA8LSBmaWx0ZXJGZWF0dXJlcyhwZTMsflJldmVyc2UgIT0gIisiKQpwZTMgPC0gZmlsdGVyRmVhdHVyZXMocGUzLH4gUG90ZW50aWFsLmNvbnRhbWluYW50ICE9ICIrIikKYGBgCgoKMy4gRHJvcCBwZXB0aWRlcyB0aGF0IHdlcmUgaWRlbnRpZmllZCBpbiBsZXNzIHRoYW4gdGhyZWUgc2FtcGxlCgpXZSBrZWVwIHBlcHRpZGVzIHRoYXQgd2VyZSBvYnNlcnZlZCBhdCBsZWFzdCB0aHJlZSB0aW1lcy4KCmBgYHtyfQpuT2JzIDwtIDMKbiA8LSBuY29sKHBlW1sicGVwdGlkZUxvZyJdXSkKcE5BIDwtIChuLW5PYnMpL24KcGUgPC0gZmlsdGVyTkEocGUsIHBOQSA9IHBOQSwgaSA9ICJwZXB0aWRlTG9nIikKbnJvdyhwZVtbInBlcHRpZGVMb2ciXV0pCgpuIDwtIG5jb2wocGUyW1sicGVwdGlkZUxvZyJdXSkKcE5BIDwtIChuLW5PYnMpL24KcGUyIDwtIGZpbHRlck5BKHBlMiwgcE5BID0gcE5BLCBpID0gInBlcHRpZGVMb2ciKQpucm93KHBlMltbInBlcHRpZGVMb2ciXV0pCgpuIDwtIG5jb2wocGUzW1sicGVwdGlkZUxvZyJdXSkKcE5BIDwtIChuLW5PYnMpL24KcGUzIDwtIGZpbHRlck5BKHBlMywgcE5BID0gcE5BLCBpID0gInBlcHRpZGVMb2ciKQpucm93KHBlM1tbInBlcHRpZGVMb2ciXV0pCmBgYAoKPC9wPjwvZGV0YWlscz4KCiMjIyBOb3JtYWxpemF0aW9uIAoKPGRldGFpbHM+PHN1bW1hcnk+IENsaWNrIHRvIHNlZSBjb2RlIHRvIG5vcm1hbGl6ZSB0aGUgZGF0YSA8L3N1bW1hcnk+PHA+CmBgYHtyfQpwZSA8LSBub3JtYWxpemUocGUsIAogICAgICAgICAgICAgICAgaSA9ICJwZXB0aWRlTG9nIiwgCiAgICAgICAgICAgICAgICBuYW1lID0gInBlcHRpZGVOb3JtIiwgCiAgICAgICAgICAgICAgICBtZXRob2QgPSAiY2VudGVyLm1lZGlhbiIpCgpwZTIgPC0gbm9ybWFsaXplKHBlMiwgCiAgICAgICAgICAgICAgICBpID0gInBlcHRpZGVMb2ciLCAKICAgICAgICAgICAgICAgIG5hbWUgPSAicGVwdGlkZU5vcm0iLCAKICAgICAgICAgICAgICAgIG1ldGhvZCA9ICJjZW50ZXIubWVkaWFuIikKCgpwZTMgPC0gbm9ybWFsaXplKHBlMywgCiAgICAgICAgICAgICAgICBpID0gInBlcHRpZGVMb2ciLCAKICAgICAgICAgICAgICAgIG5hbWUgPSAicGVwdGlkZU5vcm0iLCAKICAgICAgICAgICAgICAgIG1ldGhvZCA9ICJjZW50ZXIubWVkaWFuIikKYGBgCgo8L3A+PC9kZXRhaWxzPgoKIyMjIFN1bW1hcml6YXRpb24KCjxkZXRhaWxzPjxzdW1tYXJ5PiBDbGljayB0byBzZWUgY29kZSB0byBzdW1tYXJpemUgdGhlIGRhdGEgPC9zdW1tYXJ5PjxwPgoKYGBge3Isd2FybmluZz1GQUxTRX0KcGUgPC0gYWdncmVnYXRlRmVhdHVyZXMocGUsCiBpID0gInBlcHRpZGVOb3JtIiwKIGZjb2wgPSAiUHJvdGVpbnMiLAogbmEucm0gPSBUUlVFLAogbmFtZSA9ICJwcm90ZWluIikKCgpwZTIgPC0gYWdncmVnYXRlRmVhdHVyZXMocGUyLAogaSA9ICJwZXB0aWRlTm9ybSIsCiBmY29sID0gIlByb3RlaW5zIiwKIG5hLnJtID0gVFJVRSwKIG5hbWUgPSAicHJvdGVpbiIpCgpwZTMgPC0gYWdncmVnYXRlRmVhdHVyZXMocGUzLAogaSA9ICJwZXB0aWRlTm9ybSIsCiBmY29sID0gIlByb3RlaW5zIiwKIG5hLnJtID0gVFJVRSwKIG5hbWUgPSAicHJvdGVpbiIpCmBgYAoKIyMjIEZpbHRlcmluZyBwcm90ZWlucyB3aXRoIHRvbyBtYW55IG1pc3NpbmcgdmFsdWVzIAoKV2Ugd2FudCB0byBoYXZlIGF0IGxlYXN0IHR3byBvYnNlcnZlZCBwcm90ZWluIGludGVuc2l0aWVzIGZvciBlYWNoIGdyb3VwIHNvIHdlIHNldCB0aGUgbWluaW11bSBudW1iZXIgb2Ygb2JzZXJ2ZWQgdmFsdWVzIGF0IDQuIApXZSBzdGlsbCBoYXZlIHRvIGNoZWNrIGZvciB0aGUgb2JzZXJ2ZWQgcHJvdGVpbnMgaWYgdGhhdCBpcyB0aGUgY2FzZS4gCgpGb3IgYmxvY2sgZGVzaWduIG1vcmUgY2xldmVyIGZpbHRlcmluZyBjYW4gYmUgdXNlZC4gRS5nLiB3ZSBjb3VsZCBpbXBseSB0aGF0IHdlIGhhdmUgYm90aCBjZWxsIHR5cGVzIGluIGF0IGxlYXN0IHR3byBhbmltYWxzLi4uIAoKCmBgYHtyfQpuT2JzIDwtIDQKbiA8LSBuY29sKHBlW1sicHJvdGVpbiJdXSkKcE5BIDwtIChuLW5PYnMpL24KcGU8LSBmaWx0ZXJOQShwZSwgcE5BID0gcE5BLCBpID0gInByb3RlaW4iKQoKbiA8LSBuY29sKHBlMltbInByb3RlaW4iXV0pCnBOQSA8LSAobi1uT2JzKS9uCnBlMiA8LSBmaWx0ZXJOQShwZTIsIHBOQSA9IHBOQSwgaSA9ICJwcm90ZWluIikKCm4gPC0gbmNvbChwZTNbWyJwcm90ZWluIl1dKQpwTkEgPC0gKG4tbk9icykvbgpwZTMgPC0gZmlsdGVyTkEocGUzLCBwTkEgPSBwTkEsIGkgPSAicHJvdGVpbiIpCmBgYAoKPC9wPjwvZGV0YWlscz4KCiMjIERhdGEgRXhwbG9yYXRpb246IHdoYXQgaXMgaW1wYWN0IG9mIGJsb2NraW5nPyAKCjxkZXRhaWxzPjxzdW1tYXJ5PiBDbGljayB0byBzZWUgY29kZSA8L3N1bW1hcnk+PHA+CmBgYHtyfQpsZXZlbHMoY29sRGF0YShwZTMpJG1vdXNlKSA8LSBwYXN0ZTAoIm0iLDE6NykKbWRzT2JqMyA8LSBwbG90TURTKGFzc2F5KHBlM1tbInByb3RlaW4iXV0pLCBwbG90ID0gRkFMU0UpCm1kc09yaWcgPC0gY29sRGF0YShwZTMpICU+JQogIGFzLmRhdGEuZnJhbWUgJT4lCiAgbXV0YXRlKG1kczEgPSBtZHNPYmozJHgsCiAgICAgICAgIG1kczIgPSBtZHNPYmozJHksCiAgICAgICAgIGxhYiA9IHBhc3RlKG1vdXNlLGNlbGx0eXBlLHNlcD0iXyIpKSAlPiUKICBnZ3Bsb3QoYWVzKHggPSBtZHMxLCB5ID0gbWRzMiwgbGFiZWwgPSBsYWIsIGNvbG9yID0gY2VsbHR5cGUsIGdyb3VwID0gbW91c2UpKSArCiAgZ2VvbV90ZXh0KHNob3cubGVnZW5kID0gRkFMU0UpICsKICBnZW9tX3BvaW50KHNoYXBlID0gMjEpICsKICBnZW9tX2xpbmUoY29sb3IgPSAiYmxhY2siLCBsaW5ldHlwZSA9ICJkYXNoZWQiKSArCiAgeGxhYigKICAgIHBhc3RlMCgKICAgICAgbWRzT2JqMyRheGlzbGFiZWwsCiAgICAgICIgIiwKICAgICAgMSwgCiAgICAgICIgKCIsCiAgICAgIHBhc3RlMCgKICAgICAgICByb3VuZChtZHNPYmozJHZhci5leHBsYWluZWRbMV0gKjEwMCwwKSwKICAgICAgICAiJSIKICAgICAgICApLAogICAgICAiKSIKICAgICAgKQogICAgKSArCiAgeWxhYigKICAgIHBhc3RlMCgKICAgICAgbWRzT2JqMyRheGlzbGFiZWwsCiAgICAgICIgIiwKICAgICAgMiwgCiAgICAgICIgKCIsCiAgICAgIHBhc3RlMCgKICAgICAgICByb3VuZChtZHNPYmozJHZhci5leHBsYWluZWRbMl0gKjEwMCwwKSwKICAgICAgICAiJSIKICAgICAgICApLAogICAgICAiKSIKICAgICAgKQogICAgKSArCiAgZ2d0aXRsZSgiT3JpZ2luYWwgKFJDQikiKQoKbGV2ZWxzKGNvbERhdGEocGUpJG1vdXNlKSA8LSBwYXN0ZTAoIm0iLDE6NCkKbWRzT2JqIDwtIHBsb3RNRFMoYXNzYXkocGVbWyJwcm90ZWluIl1dKSwgcGxvdCA9IEZBTFNFKQptZHNSQ0IgPC0gY29sRGF0YShwZSkgJT4lCiAgYXMuZGF0YS5mcmFtZSAlPiUKICBtdXRhdGUobWRzMSA9IG1kc09iaiR4LAogICAgICAgICBtZHMyID0gbWRzT2JqJHksCiAgICAgICAgIGxhYiA9IHBhc3RlKG1vdXNlLGNlbGx0eXBlLHNlcD0iXyIpKSAlPiUKICBnZ3Bsb3QoYWVzKHggPSBtZHMxLCB5ID0gbWRzMiwgbGFiZWwgPSBsYWIsIGNvbG9yID0gY2VsbHR5cGUsIGdyb3VwID0gbW91c2UpKSArCiAgZ2VvbV90ZXh0KHNob3cubGVnZW5kID0gRkFMU0UpICsKICBnZW9tX3BvaW50KHNoYXBlID0gMjEpICsKICBnZW9tX2xpbmUoY29sb3IgPSAiYmxhY2siLCBsaW5ldHlwZSA9ICJkYXNoZWQiKSArCiAgeGxhYigKICAgIHBhc3RlMCgKICAgICAgbWRzT2JqJGF4aXNsYWJlbCwKICAgICAgIiAiLAogICAgICAxLCAKICAgICAgIiAoIiwKICAgICAgcGFzdGUwKAogICAgICAgIHJvdW5kKG1kc09iaiR2YXIuZXhwbGFpbmVkWzFdICoxMDAsMCksCiAgICAgICAgIiUiCiAgICAgICAgKSwKICAgICAgIikiCiAgICAgICkKICAgICkgKwogIHlsYWIoCiAgICBwYXN0ZTAoCiAgICAgIG1kc09iaiRheGlzbGFiZWwsCiAgICAgICIgIiwKICAgICAgMiwgCiAgICAgICIgKCIsCiAgICAgIHBhc3RlMCgKICAgICAgICByb3VuZChtZHNPYmokdmFyLmV4cGxhaW5lZFsyXSAqMTAwLDApLAogICAgICAgICIlIgogICAgICAgICksCiAgICAgICIpIgogICAgICApCiAgICApICsKICBnZ3RpdGxlKCJSYW5kb21pemVkIENvbXBsZXRlIEJsb2NrIChSQ0IpIikKCgpsZXZlbHMoY29sRGF0YShwZTIpJG1vdXNlKSA8LSBwYXN0ZTAoIm0iLDE6OCkKbWRzT2JqMiA8LSBwbG90TURTKGFzc2F5KHBlMltbInByb3RlaW4iXV0pLCBwbG90ID0gRkFMU0UpCm1kc0NSRCA8LSBjb2xEYXRhKHBlMikgJT4lCiAgYXMuZGF0YS5mcmFtZSAlPiUKICBtdXRhdGUobWRzMSA9IG1kc09iajIkeCwKICAgICAgICAgbWRzMiA9IG1kc09iajIkeSwKICAgICAgICAgbGFiID0gcGFzdGUobW91c2UsY2VsbHR5cGUsc2VwPSJfIikpICU+JQogIGdncGxvdChhZXMoeCA9IG1kczEsIHkgPSBtZHMyLCBsYWJlbCA9IGxhYiwgY29sb3IgPSBjZWxsdHlwZSwgZ3JvdXAgPSBtb3VzZSkpICsKICBnZW9tX3RleHQoc2hvdy5sZWdlbmQgPSBGQUxTRSkgKwogIGdlb21fcG9pbnQoc2hhcGUgPSAyMSkgKwogIHhsYWIoCiAgICBwYXN0ZTAoCiAgICAgIG1kc09iaiRheGlzbGFiZWwsCiAgICAgICIgIiwKICAgICAgMSwgCiAgICAgICIgKCIsCiAgICAgIHBhc3RlMCgKICAgICAgICByb3VuZChtZHNPYmoyJHZhci5leHBsYWluZWRbMV0gKjEwMCwwKSwKICAgICAgICAiJSIKICAgICAgICApLAogICAgICAiKSIKICAgICAgKQogICAgKSArCiAgeWxhYigKICAgIHBhc3RlMCgKICAgICAgbWRzT2JqJGF4aXNsYWJlbCwKICAgICAgIiAiLAogICAgICAyLCAKICAgICAgIiAoIiwKICAgICAgcGFzdGUwKAogICAgICAgIHJvdW5kKG1kc09iajIkdmFyLmV4cGxhaW5lZFsyXSAqMTAwLDApLAogICAgICAgICIlIgogICAgICAgICksCiAgICAgICIpIgogICAgICApCiAgICApICsKICBnZ3RpdGxlKCJDb21wbGV0ZWx5IFJhbmRvbWl6ZWQgRGVzaWduIChDUkQpIikKYGBgCjwvcD48L2RldGFpbHM+CmBgYHtyfQptZHNPcmlnCm1kc1JDQgptZHNDUkQKYGBgCgotIFdlIG9ic2VydmUgdGhhdCB0aGUgbGVhZGluZyBmb2xkIGNoYW5nZSBpcyBhY2NvcmRpbmcgdG8gbW91c2UKLSBJbiB0aGUgc2Vjb25kIGRpbWVuc2lvbiB3ZSBzZWUgYSBzZXBhcmF0aW9uIGFjY29yZGluZyB0byBjZWxsLXR5cGUgCi0gV2l0aCB0aGUgUmFuZG9taXplZCBDb21wbGV0ZSBCbG9jayBkZXNpZ24gKFJDQikgd2UgY2FuIHJlbW92ZSB0aGUgbW91c2UgZWZmZWN0IGZyb20gdGhlIGFuYWx5c2lzIQoKIyMgTW9kZWxpbmcgYW5kIGluZmVyZW5jZQoKIyMjIFJDQiBhbmFseXNpcwpgYGB7ciB3YXJuaW5nPUZBTFNFfQpwZSA8LSBtc3Fyb2IoCiAgb2JqZWN0ID0gcGUsCiAgaSA9ICJwcm90ZWluIiwKICBmb3JtdWxhID0gfiBjZWxsdHlwZSArIG1vdXNlKQpgYGAKCiMjIyBSQ0Igd3JvbmcgYW5hbHlzaXMKYGBge3Igd2FybmluZz1GQUxTRX0KcGUgPC0gbXNxcm9iKAogIG9iamVjdCA9IHBlLAogIGkgPSAicHJvdGVpbiIsCiAgZm9ybXVsYSA9IH4gY2VsbHR5cGUsIG1vZGVsQ29sdW1uTmFtZSA9ICJ3cm9uZ01vZGVsIikKYGBgCgojIyBDUkQgYW5hbHlzaXMgCmBgYHtyIHdhcm5pbmcgPSBGQUxTRX0KcGUyIDwtIG1zcXJvYigKICBvYmplY3QgPSBwZTIsCiAgaSA9ICJwcm90ZWluIiwKICBmb3JtdWxhID0gfiBjZWxsdHlwZSkKYGBgCgojIyMgSW5mZXJlbmNlIAoKYGBge3J9CmxpYnJhcnkoRXhwbG9yZU1vZGVsTWF0cml4KQpWaXN1YWxpemVEZXNpZ24oY29sRGF0YShwZSksfiBjZWxsdHlwZSArIG1vdXNlKSRwbG90bGlzdApWaXN1YWxpemVEZXNpZ24oY29sRGF0YShwZTIpLH4gY2VsbHR5cGUpJHBsb3RsaXN0CmBgYAoKCmBgYHtyfQpMIDwtIG1ha2VDb250cmFzdCgiY2VsbHR5cGVUcmVnID0gMCIsIHBhcmFtZXRlck5hbWVzID0gYygiY2VsbHR5cGVUcmVnIikpCnBlIDwtIGh5cG90aGVzaXNUZXN0KG9iamVjdCA9IHBlLCBpID0gInByb3RlaW4iLCBjb250cmFzdCA9IEwpCnBlIDwtIGh5cG90aGVzaXNUZXN0KG9iamVjdCA9IHBlLCBpID0gInByb3RlaW4iLCBjb250cmFzdCA9IEwsIG1vZGVsQ29sdW1uID0gIndyb25nTW9kZWwiLCByZXN1bHRzQ29sdW1uTmFtZVByZWZpeD0id3JvbmciKQpwZTIgPC0gaHlwb3RoZXNpc1Rlc3Qob2JqZWN0ID0gcGUyLCBpID0gInByb3RlaW4iLCBjb250cmFzdCA9IEwpCmBgYAoKIyBBZHZhbnRhZ2Ugb2YgQmxvY2tpbmc6IGNvbXBhcmlzb24gYmV0d2VlbiBkZXNpZ25zCgojIyBWb2xjYW5vIHBsb3RzCgo8ZGV0YWlscz48c3VtbWFyeT4gQ2xpY2sgdG8gc2VlIGNvZGUgPC9zdW1tYXJ5PjxwPgpgYGB7cn0Kdm9sY2Fub1JDQiA8LSBnZ3Bsb3QoCiAgICByb3dEYXRhKHBlW1sicHJvdGVpbiJdXSkkY2VsbHR5cGVUcmVnLAogICAgYWVzKHggPSBsb2dGQywgeSA9IC1sb2cxMChwdmFsKSwgY29sb3IgPSBhZGpQdmFsIDwgMC4wNSkKKSArCiAgICBnZW9tX3BvaW50KGNleCA9IDIuNSkgKwogICAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGFscGhhKGMoImJsYWNrIiwgInJlZCIpLCAwLjUpKSArCiAgICB0aGVtZV9taW5pbWFsKCkgKwogICAgZ2d0aXRsZShwYXN0ZTAoIlJDQjogXG4iLCAKICAgICAgICAgICAgICAgIHN1bShyb3dEYXRhKHBlW1sicHJvdGVpbiJdXSkkY2VsbHR5cGVUcmVnJGFkalB2YWw8MC4wNSxuYS5ybT1UUlVFKSwKICAgICAgICAgICAgIiBzaWduaWZpY2FudCIpKQoKdm9sY2Fub1JDQndyb25nIDwtIGdncGxvdCgKICAgIHJvd0RhdGEocGVbWyJwcm90ZWluIl1dKSR3cm9uZ2NlbGx0eXBlVHJlZywKICAgIGFlcyh4ID0gbG9nRkMsIHkgPSAtbG9nMTAocHZhbCksIGNvbG9yID0gYWRqUHZhbCA8IDAuMDUpCikgKwogICAgZ2VvbV9wb2ludChjZXggPSAyLjUpICsKICAgIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBhbHBoYShjKCJibGFjayIsICJyZWQiKSwgMC41KSkgKwogICAgdGhlbWVfbWluaW1hbCgpICsKICAgIGdndGl0bGUocGFzdGUwKCJSQ0Igd3Jvbmc6IFxuIiwgCiAgICAgICAgICAgICAgICBzdW0ocm93RGF0YShwZVtbInByb3RlaW4iXV0pJHdyb25nY2VsbHR5cGVUcmVnJGFkalB2YWw8MC4wNSxuYS5ybT1UUlVFKSwKICAgICAgICAgICAgIiBzaWduaWZpY2FudCIpKQoKdm9sY2Fub0NSRCA8LSBnZ3Bsb3QoCiAgICByb3dEYXRhKHBlMltbInByb3RlaW4iXV0pJGNlbGx0eXBlVHJlZywKICAgIGFlcyh4ID0gbG9nRkMsIHkgPSAtbG9nMTAocHZhbCksIGNvbG9yID0gYWRqUHZhbCA8IDAuMDUpCikgKwogICAgZ2VvbV9wb2ludChjZXggPSAyLjUpICsKICAgIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBhbHBoYShjKCJibGFjayIsICJyZWQiKSwgMC41KSkgKwogICAgdGhlbWVfbWluaW1hbCgpICsKICAgIGdndGl0bGUocGFzdGUwKCJDUkQ6IFxuIiwgCiAgICAgICAgICAgICAgICBzdW0ocm93RGF0YShwZTJbWyJwcm90ZWluIl1dKSRjZWxsdHlwZVRyZWckYWRqUHZhbDwwLjA1LG5hLnJtPVRSVUUpLAogICAgICAgICAgICAiIHNpZ25pZmljYW50IikpCmBgYAo8L3A+PC9kZXRhaWxzPgogIApgYGB7cn0KZ3JpZC5hcnJhbmdlKHZvbGNhbm9SQ0Isdm9sY2Fub0NSRCwgdm9sY2Fub1JDQndyb25nLG5jb2w9MikKYGBgCgojIyBBbm92YSB0YWJsZTogUTdUUFI0LCBBbHBoYS1hY3RpbmluLTEKCkRpc2NsYWltZXI6IHRoZSBBbm92YSBhbmFseXNpcyBpcyBvbmx5IGZvciBkaWRhY3RpY2FsIHB1cnBvc2VzLiBJbiBwcmFjdGljZSB3ZSBhc3Nlc3MgdGhlIGh5cG90aGVzZXMgdXNpbmcgbXNxcm9iMi4gCgotIFdlIGlsbHVzdHJhdGUgdGhlIHBvd2VyIGdhaW4gb2YgYmxvY2tpbmcgdXNpbmcgYW4gQW5vdmEgYW5hbHlzaXMgb24gMSBwcm90ZWluLiAKCi0gTm90ZSwgdGhhdCBtc3Fyb2IyIHdpbGwgcGVyZm9ybSBhIHNpbWlsYXIgYW5hbHlzaXMsIGJ1dCwgaXQgdXNlcyByb2J1c3QgcmVncmVzc2lvbiBhbmQgaXQgdXNlcyBhbiBlbXBpcmljYWwgQmF5ZXMgZXN0aW1hdG9yIGZvciB0aGUgdmFyaWFuY2UuCgpgYGB7cn0KcHJvdCA8LSAiUTdUUFI0IgpkYXRhSGxwIDwtIGNvbERhdGEocGUpICU+JSAKICBhcy5kYXRhLmZyYW1lICU+JQogIG11dGF0ZShpbnRlbnNpdHk9YXNzYXkocGVbWyJwcm90ZWluIl1dKVtwcm90LF0sCiAgICAgICAgIGludGVuc2l0eUNSRD1hc3NheShwZTJbWyJwcm90ZWluIl1dKVtwcm90LF0pCgogIGFub3ZhKGxtKGludGVuc2l0eX4gY2VsbHR5cGUgKyBtb3VzZSwgZGF0YUhscCkpIAogIGFub3ZhKGxtKGludGVuc2l0eX4gY2VsbHR5cGUsZGF0YUhscCkpCiAgYW5vdmEobG0oaW50ZW5zaXR5Q1JEfiBjZWxsdHlwZSxkYXRhSGxwKSkKYGBgCgojIyBDb21wYXJpc29uIEVtcGlyaWNhbCBCYXllcyBzdGFuZGFyZCBkZXZpYXRpb24gaW4gbXNxcm9iMiAKCjxkZXRhaWxzPjxzdW1tYXJ5PiBDbGljayB0byBzZWUgY29kZSA8L3N1bW1hcnk+PHA+CmBgYHtyfQphY2Nlc3Npb25zIDwtIHJvd25hbWVzKHBlW1sicHJvdGVpbiJdXSlbcm93bmFtZXMocGVbWyJwcm90ZWluIl1dKSVpbiVyb3duYW1lcyhwZTJbWyJwcm90ZWluIl1dKV0KZGF0IDwtIGRhdGEuZnJhbWUoCnNpZ21hUkJDID0gc2FwcGx5KHJvd0RhdGEocGVbWyJwcm90ZWluIl1dKSRtc3Fyb2JNb2RlbHNbYWNjZXNzaW9uc10sIGdldFNpZ21hUG9zdGVyaW9yKSwKc2lnbWFSQkN3cm9uZyA9IHNhcHBseShyb3dEYXRhKHBlW1sicHJvdGVpbiJdXSkkd3JvbmdNb2RlbFthY2Nlc3Npb25zXSwgZ2V0U2lnbWFQb3N0ZXJpb3IpLApzaWdtYUNSRCA8LSBzYXBwbHkocm93RGF0YShwZTJbWyJwcm90ZWluIl1dKSRtc3Fyb2JNb2RlbHNbYWNjZXNzaW9uc10sIGdldFNpZ21hUG9zdGVyaW9yKQopCgogcGxvdFJCQ3ZzV3JvbmcgPC0gZ2dwbG90KGRhdGEgPSBkYXQsIGFlcyhzaWdtYVJCQywgc2lnbWFSQkN3cm9uZykpICsKICAgIGdlb21fcG9pbnQoYWxwaGEgPSAwLjEsIHNoYXBlID0gMjApICsKICAgIHNjYWxlX3hfbG9nMTAoKSArCiAgICBzY2FsZV95X2xvZzEwKCkgKwogICAgZ2VvbV9hYmxpbmUoaW50ZXJjZXB0PTAsc2xvcGU9MSkKcGxvdENSRHZzV3JvbmcgPC0gZ2dwbG90KGRhdGEgPSBkYXQsIGFlcyhzaWdtYUNSRCwgc2lnbWFSQkN3cm9uZykpICsKICAgIGdlb21fcG9pbnQoYWxwaGEgPSAwLjEsIHNoYXBlID0gMjApICsKICAgIHNjYWxlX3hfbG9nMTAoKSArCiAgICBzY2FsZV95X2xvZzEwKCkgKwogICAgZ2VvbV9hYmxpbmUoaW50ZXJjZXB0PTAsc2xvcGU9MSkKcGxvdFJCQ3ZzQ1JEIDwtIGdncGxvdChkYXRhID0gZGF0LCBhZXMoc2lnbWFSQkMsIHNpZ21hQ1JEKSkgKwogICAgZ2VvbV9wb2ludChhbHBoYSA9IDAuMSwgc2hhcGUgPSAyMCkgKwogICAgc2NhbGVfeF9sb2cxMCgpICsKICAgIHNjYWxlX3lfbG9nMTAoKSArCiAgICBnZW9tX2FibGluZShpbnRlcmNlcHQ9MCxzbG9wZT0xKQpgYGAKPC9wPjwvZGV0YWlscz4KCmBgYHtyfQpncmlkLmFycmFuZ2UoCiAgcGxvdFJCQ3ZzV3JvbmcsCiAgcGxvdENSRHZzV3JvbmcsCiAgcGxvdFJCQ3ZzQ1JELAogIG5yb3c9MikKYGBgCgotIFdlIGNsZWFybHkgb2JzZXJ2ZSB0aGF0IHRoZSBzdGFuZGFyZCBkZXZpYXRpb24gb2YgdGhlIHByb3RlaW4gZXhwcmVzc2lvbiBpbiB0aGUgUkNCIGlzIHNtYWxsZXIgZm9yIHRoZSBtYWpvcml0eSBvZiB0aGUgcHJvdGVpbnMgdGhhbiB0aGF0IG9idGFpbmVkIHdpdGggdGhlIENSRAoKLSBUaGUgc3RhbmRhcmQgZGV2aWF0aW9uIG9mIHRoZSBwcm90ZWluIGV4cHJlc3Npb24gUkNCIHdoZXJlIHdlIHBlcmZvcm0gYSB3cm9uZyBhbmFseXNpcyB3aXRob3V0IGNvbnNpZGVyaW5nIHRoZSBibG9ja2luZyBmYWN0b3IgYWNjb3JkaW5nIHRvIG1vdXNlIGlzIG11Y2ggbGFyZ2VyIGZvciB0aGUgbWFyam9yaXR5IG9mIHRoZSBwcm90ZWlucyB0aGFuIHRoYXQgb2J0YWluZWQgd2l0aCB0aGUgY29ycmVjdCBhbmFseXNpcy4gCgotIEluZGVlZCwgd2hlbiB3ZSBpZ25vcmUgdGhlIGJsb2NraW5nIGZhY3RvciBpbiB0aGUgUkNCIGRlc2lnbiB3ZSBkbyBub3QgcmVtb3ZlIHRoZSB2YXJpYWJpbGl0eSBhY2NvcmRpbmcgdG8gbW91c2UgZnJvbSB0aGUgYW5hbHlzaXMgYW5kIHRoZSBtb3VzZSBlZmZlY3QgaXMgYWJzb3JiZWQgaW4gdGhlIGVycm9yIHRlcm0uIFRoZSBzdGFuZGFyZCBkZXZpYXRpb24gdGhhbiBiZWNvbWVzIHZlcnkgY29tcGFyYWJsZSB0byB0aGF0IG9ic2VydmVkIGluIHRoZSBjb21wbGV0ZWx5IHJhbmRvbWlzZWQgZGVzaWduIHdoZXJlIHdlIGNvdWxkIG5vdCByZW1vdmUgdGhlIG1vdXNlIGVmZmVjdCBmcm9tIHRoZSBhbmFseXNpcy4gCgotIFdoeSBhcmUgc29tZSBvZiB0aGUgc3RhbmRhcmQgZGV2aWF0aW9ucyBmb3IgdGhlIFJDQiB3aXRoIHRoZSBjb3JyZWN0IGFuYWx5c2lzIGxhcmdlciB0aGFuIHRoYW4gb2YgdGhlIFJDQiB3aXRoIHRoZSBpbmNvcnJlY3QgYW5hbHlzaXMgdGhhdCBpZ25vcmVkIHRoZSBtb3VzZSBibG9ja2luZyBmYWN0b3I/CgotIENhbiB5b3UgdGhpbmsgb2YgYSByZWFzb24gd2h5IGl0IHdvdWxkIG5vdCBiZSB1c2VmdWwgdG8gYmxvY2sgb24gYSBwYXJ0aWN1bGFyIGZhY3Rvcj8gCg==