1 Import Data and Preprocessing

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

1.2 Preprocessing

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

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

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

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

1.5 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.6 Modeling and inference

1.6.1 RCB analysis

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

1.6.2 RCB wrong analysis

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

1.7 CRD analysis

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

1.7.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 777 rows containing missing values (geom_point).
## Warning: Removed 382 rows containing missing values (geom_point).
## Warning: Removed 262 rows containing missing values (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)) 
  anova(lm(intensity~ celltype,dataHlp))
  anova(lm(intensityCRD~ celltype,dataHlp))

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 730 rows containing missing values (geom_point).
## Warning: Removed 397 rows containing missing values (geom_point).
## 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

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

LS0tCnRpdGxlOiAiU3RhdGlzdGljYWwgTWV0aG9kcyBmb3IgUXVhbnRpdGF0aXZlIE1TLWJhc2VkIFByb3Rlb21pY3M6IEJsb2NraW5nIC0gV3JhcC11cCIKYXV0aG9yOiAiTGlldmVuIENsZW1lbnQiCmRhdGU6ICJbc3RhdE9taWNzXShodHRwczovL3N0YXRvbWljcy5naXRodWIuaW8pLCBHaGVudCBVbml2ZXJzaXR5IgpvdXRwdXQ6CiAgICBodG1sX2RvY3VtZW50OgogICAgICBjb2RlX2Rvd25sb2FkOiB0cnVlCiAgICAgIHRoZW1lOiBjb3NtbwogICAgICB0b2M6IHRydWUKICAgICAgdG9jX2Zsb2F0OiB0cnVlCiAgICAgIGhpZ2hsaWdodDogdGFuZ28KICAgICAgbnVtYmVyX3NlY3Rpb25zOiB0cnVlCiAgICBwZGZfZG9jdW1lbnQ6CiAgICAgIHRvYzogdHJ1ZQogICAgICBudW1iZXJfc2VjdGlvbnM6IHRydWUKbGlua2NvbG9yOiBibHVlCnVybGNvbG9yOiBibHVlCmNpdGVjb2xvcjogYmx1ZQoKYmlibGlvZ3JhcGh5OiBtc3Fyb2IyLmJpYgoKLS0tCgoKIyBJbXBvcnQgRGF0YSBhbmQgUHJlcHJvY2Vzc2luZyAKCiMjIERhdGEgCjxkZXRhaWxzPjxzdW1tYXJ5PiBDbGljayB0byBzZWUgY29kZSAgPC9zdW1tYXJ5PjxwPgpgYGB7ciwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0KbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkobGltbWEpCmxpYnJhcnkoUUZlYXR1cmVzKQpsaWJyYXJ5KG1zcXJvYjIpCmxpYnJhcnkocGxvdGx5KQpsaWJyYXJ5KGdyaWRFeHRyYSkKCnBlcHRpZGVzRmlsZSA8LSAiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3N0YXRPbWljcy9QREEyMS9kYXRhL3F1YW50aWZpY2F0aW9uL21vdXNlVGNlbGwvcGVwdGlkZXNSQ0IudHh0IgpwZXB0aWRlc0ZpbGUyIDwtICJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vc3RhdE9taWNzL1BEQTIxL2RhdGEvcXVhbnRpZmljYXRpb24vbW91c2VUY2VsbC9wZXB0aWRlc0NSRC50eHQiCnBlcHRpZGVzRmlsZTMgPC0gImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9zdGF0T21pY3MvUERBMjEvZGF0YS9xdWFudGlmaWNhdGlvbi9tb3VzZVRjZWxsL3BlcHRpZGVzLnR4dCIKCmVjb2xzIDwtIGdyZXAoIkludGVuc2l0eVxcLiIsIG5hbWVzKHJlYWQuZGVsaW0ocGVwdGlkZXNGaWxlKSkpCnBlIDwtIHJlYWRRRmVhdHVyZXMoCiAgdGFibGUgPSBwZXB0aWRlc0ZpbGUsCiAgZm5hbWVzID0gMSwKICBlY29sID0gZWNvbHMsCiAgbmFtZSA9ICJwZXB0aWRlUmF3Iiwgc2VwPSJcdCIpCgplY29sczIgPC0gZ3JlcCgiSW50ZW5zaXR5XFwuIiwgbmFtZXMocmVhZC5kZWxpbShwZXB0aWRlc0ZpbGUyKSkpCnBlMiA8LSByZWFkUUZlYXR1cmVzKAogIHRhYmxlID0gcGVwdGlkZXNGaWxlMiwKICBmbmFtZXMgPSAxLAogIGVjb2wgPSBlY29sczIsCiAgbmFtZSA9ICJwZXB0aWRlUmF3Iiwgc2VwPSJcdCIpCgplY29sczMgPC0gZ3JlcCgiSW50ZW5zaXR5XFwuIiwgbmFtZXMocmVhZC5kZWxpbShwZXB0aWRlc0ZpbGUzKSkpCnBlMyA8LSByZWFkUUZlYXR1cmVzKAogIHRhYmxlID0gcGVwdGlkZXNGaWxlMywKICBmbmFtZXMgPSAxLAogIGVjb2wgPSBlY29sczMsCiAgbmFtZSA9ICJwZXB0aWRlUmF3Iiwgc2VwPSJcdCIpCgojIyMgRGVzaWduCmNvbERhdGEocGUpJGNlbGx0eXBlIDwtIHN1YnN0cigKICBjb2xuYW1lcyhwZVtbInBlcHRpZGVSYXciXV0pLAogIDExLAogIDE0KSAlPiUKICB1bmxpc3QgJT4lICAKICBhcy5mYWN0b3IKCmNvbERhdGEocGUpJG1vdXNlIDwtIHBlW1sxXV0gJT4lCiAgY29sbmFtZXMgJT4lCiAgc3Ryc3BsaXQoc3BsaXQ9IlsuXSIpICAlPiUKICBzYXBwbHkoZnVuY3Rpb24oeCkgeFszXSkgJT4lCiAgYXMuZmFjdG9yCgpjb2xEYXRhKHBlMikkY2VsbHR5cGUgPC0gc3Vic3RyKAogIGNvbG5hbWVzKHBlMltbInBlcHRpZGVSYXciXV0pLAogIDExLAogIDE0KSAlPiUKICB1bmxpc3QgJT4lICAKICBhcy5mYWN0b3IKCmNvbERhdGEocGUyKSRtb3VzZSA8LSBwZTJbWzFdXSAlPiUKICBjb2xuYW1lcyAlPiUKICBzdHJzcGxpdChzcGxpdD0iWy5dIikgICU+JQogIHNhcHBseShmdW5jdGlvbih4KSB4WzNdKSAlPiUKICBhcy5mYWN0b3IKCmNvbERhdGEocGUzKSRjZWxsdHlwZSA8LSBzdWJzdHIoCiAgY29sbmFtZXMocGUzW1sicGVwdGlkZVJhdyJdXSksCiAgMTEsCiAgMTQpICU+JQogIHVubGlzdCAlPiUgIAogIGFzLmZhY3RvcgoKY29sRGF0YShwZTMpJG1vdXNlIDwtIHBlM1tbMV1dICU+JQogIGNvbG5hbWVzICU+JQogIHN0cnNwbGl0KHNwbGl0PSJbLl0iKSAgJT4lCiAgc2FwcGx5KGZ1bmN0aW9uKHgpIHhbM10pICU+JQogIGFzLmZhY3RvcgpgYGAKPC9wPjwvZGVzaWduPgoKIyMgUHJlcHJvY2Vzc2luZyAKCgojIyMgTG9nLXRyYW5zZm9ybQoKPGRldGFpbHM+PHN1bW1hcnk+IENsaWNrIHRvIHNlZSBjb2RlIHRvIGxvZy10cmFuc2Zyb20gdGhlIGRhdGEgPC9zdW1tYXJ5PjxwPgotIFdlIGNhbGN1bGF0ZSBob3cgbWFueSBub24gemVybyBpbnRlbnNpdGllcyB3ZSBoYXZlIGZvciBlYWNoIHBlcHRpZGUgYW5kIHRoaXMgY2FuIGJlIHVzZWZ1bCBmb3IgZmlsdGVyaW5nLgoKYGBge3J9CnJvd0RhdGEocGVbWyJwZXB0aWRlUmF3Il1dKSRuTm9uWmVybyA8LSByb3dTdW1zKGFzc2F5KHBlW1sicGVwdGlkZVJhdyJdXSkgPiAwKQoKcm93RGF0YShwZTJbWyJwZXB0aWRlUmF3Il1dKSRuTm9uWmVybyA8LSByb3dTdW1zKGFzc2F5KHBlMltbInBlcHRpZGVSYXciXV0pID4gMCkKCnJvd0RhdGEocGUzW1sicGVwdGlkZVJhdyJdXSkkbk5vblplcm8gPC0gcm93U3Vtcyhhc3NheShwZTNbWyJwZXB0aWRlUmF3Il1dKSA+IDApCmBgYAoKLSBQZXB0aWRlcyB3aXRoIHplcm8gaW50ZW5zaXRpZXMgYXJlIG1pc3NpbmcgcGVwdGlkZXMgYW5kIHNob3VsZCBiZSByZXByZXNlbnQKd2l0aCBhIGBOQWAgdmFsdWUgcmF0aGVyIHRoYW4gYDBgLgoKYGBge3J9CnBlIDwtIHplcm9Jc05BKHBlLCAicGVwdGlkZVJhdyIpICMgY29udmVydCAwIHRvIE5BCgpwZTIgPC0gemVyb0lzTkEocGUyLCAicGVwdGlkZVJhdyIpICMgY29udmVydCAwIHRvIE5BCgpwZTMgPC0gemVyb0lzTkEocGUzLCAicGVwdGlkZVJhdyIpICMgY29udmVydCAwIHRvIE5BCmBgYAoKLSBMb2d0cmFuc2Zvcm0gZGF0YSB3aXRoIGJhc2UgMgoKYGBge3J9CnBlIDwtIGxvZ1RyYW5zZm9ybShwZSwgYmFzZSA9IDIsIGkgPSAicGVwdGlkZVJhdyIsIG5hbWUgPSAicGVwdGlkZUxvZyIpCgpwZTIgPC0gbG9nVHJhbnNmb3JtKHBlMiwgYmFzZSA9IDIsIGkgPSAicGVwdGlkZVJhdyIsIG5hbWUgPSAicGVwdGlkZUxvZyIpCgpwZTMgPC0gbG9nVHJhbnNmb3JtKHBlMywgYmFzZSA9IDIsIGkgPSAicGVwdGlkZVJhdyIsIG5hbWUgPSAicGVwdGlkZUxvZyIpCmBgYAo8L3A+PC9kZXRhaWxzPgoKCiMjIyBGaWx0ZXJpbmcKPGRldGFpbHM+PHN1bW1hcnk+IENsaWNrIHRvIHNlZSBkZXRhaWxzIG9uIGZpbHRlcmluZyA8L3N1bW1hcnk+PHA+CgoxLiBIYW5kbGluZyBvdmVybGFwcGluZyBwcm90ZWluIGdyb3VwcwoKSW4gb3VyIGFwcHJvYWNoIGEgcGVwdGlkZSBjYW4gbWFwIHRvIG11bHRpcGxlIHByb3RlaW5zLCBhcyBsb25nIGFzIHRoZXJlIGlzCm5vbmUgb2YgdGhlc2UgcHJvdGVpbnMgcHJlc2VudCBpbiBhIHNtYWxsZXIgc3ViZ3JvdXAuCgpgYGB7cn0KcGUgPC0gZmlsdGVyRmVhdHVyZXMocGUsIH4gUHJvdGVpbnMgJWluJSBzbWFsbGVzdFVuaXF1ZUdyb3Vwcyhyb3dEYXRhKHBlW1sicGVwdGlkZUxvZyJdXSkkUHJvdGVpbnMpKQoKcGUyIDwtIGZpbHRlckZlYXR1cmVzKHBlMiwgfiBQcm90ZWlucyAlaW4lIHNtYWxsZXN0VW5pcXVlR3JvdXBzKHJvd0RhdGEocGUyW1sicGVwdGlkZUxvZyJdXSkkUHJvdGVpbnMpKQoKcGUzIDwtIGZpbHRlckZlYXR1cmVzKHBlMywgfiBQcm90ZWlucyAlaW4lIHNtYWxsZXN0VW5pcXVlR3JvdXBzKHJvd0RhdGEocGUzW1sicGVwdGlkZUxvZyJdXSkkUHJvdGVpbnMpKQpgYGAKMi4gUmVtb3ZlIHJldmVyc2Ugc2VxdWVuY2VzIChkZWNveXMpIGFuZCBjb250YW1pbmFudHMKCldlIG5vdyByZW1vdmUgdGhlIGNvbnRhbWluYW50cywgcGVwdGlkZXMgdGhhdCBtYXAgdG8gZGVjb3kgc2VxdWVuY2VzLCBhbmQgcHJvdGVpbnMKd2hpY2ggd2VyZSBvbmx5IGlkZW50aWZpZWQgYnkgcGVwdGlkZXMgd2l0aCBtb2RpZmljYXRpb25zLgoKYGBge3J9CnBlIDwtIGZpbHRlckZlYXR1cmVzKHBlLH5SZXZlcnNlICE9ICIrIikKcGUgPC0gZmlsdGVyRmVhdHVyZXMocGUsfiBQb3RlbnRpYWwuY29udGFtaW5hbnQgIT0gIisiKQoKcGUyIDwtIGZpbHRlckZlYXR1cmVzKHBlMix+UmV2ZXJzZSAhPSAiKyIpCnBlMiA8LSBmaWx0ZXJGZWF0dXJlcyhwZTIsfiBQb3RlbnRpYWwuY29udGFtaW5hbnQgIT0gIisiKQoKcGUzIDwtIGZpbHRlckZlYXR1cmVzKHBlMyx+UmV2ZXJzZSAhPSAiKyIpCnBlMyA8LSBmaWx0ZXJGZWF0dXJlcyhwZTMsfiBQb3RlbnRpYWwuY29udGFtaW5hbnQgIT0gIisiKQpgYGAKMy4gRHJvcCBwZXB0aWRlcyB0aGF0IHdlcmUgb25seSBpZGVudGlmaWVkIGluIG9uZSBzYW1wbGUKCldlIGtlZXAgcGVwdGlkZXMgdGhhdCB3ZXJlIG9ic2VydmVkIGF0IGxhc3QgdHdpY2UuCgpgYGB7cn0KcGUgPC0gZmlsdGVyRmVhdHVyZXMocGUsfm5Ob25aZXJvID49IDIpCm5yb3cocGVbWyJwZXB0aWRlTG9nIl1dKQoKCnBlMiA8LSBmaWx0ZXJGZWF0dXJlcyhwZTIsfm5Ob25aZXJvID49IDIpCm5yb3cocGUyW1sicGVwdGlkZUxvZyJdXSkKCgpwZTMgPC0gZmlsdGVyRmVhdHVyZXMocGUzLH5uTm9uWmVybyA+PSAyKQpucm93KHBlM1tbInBlcHRpZGVMb2ciXV0pCmBgYAoKPC9wPjwvZGV0YWlscz4KCiMjIE5vcm1hbGl6YXRpb24gCgo8ZGV0YWlscz48c3VtbWFyeT4gQ2xpY2sgdG8gc2VlIGNvZGUgdG8gbm9ybWFsaXplIHRoZSBkYXRhIDwvc3VtbWFyeT48cD4KYGBge3J9CnBlIDwtIG5vcm1hbGl6ZShwZSwgCiAgICAgICAgICAgICAgICBpID0gInBlcHRpZGVMb2ciLCAKICAgICAgICAgICAgICAgIG5hbWUgPSAicGVwdGlkZU5vcm0iLCAKICAgICAgICAgICAgICAgIG1ldGhvZCA9ICJjZW50ZXIubWVkaWFuIikKCnBlMiA8LSBub3JtYWxpemUocGUyLCAKICAgICAgICAgICAgICAgIGkgPSAicGVwdGlkZUxvZyIsIAogICAgICAgICAgICAgICAgbmFtZSA9ICJwZXB0aWRlTm9ybSIsIAogICAgICAgICAgICAgICAgbWV0aG9kID0gImNlbnRlci5tZWRpYW4iKQoKCnBlMyA8LSBub3JtYWxpemUocGUzLCAKICAgICAgICAgICAgICAgIGkgPSAicGVwdGlkZUxvZyIsIAogICAgICAgICAgICAgICAgbmFtZSA9ICJwZXB0aWRlTm9ybSIsIAogICAgICAgICAgICAgICAgbWV0aG9kID0gImNlbnRlci5tZWRpYW4iKQpgYGAKCjwvcD48L2RldGFpbHM+CgojIyBTdW1tYXJpemF0aW9uCgo8ZGV0YWlscz48c3VtbWFyeT4gQ2xpY2sgdG8gc2VlIGNvZGUgdG8gc3VtbWFyaXplIHRoZSBkYXRhIDwvc3VtbWFyeT48cD4KCmBgYHtyLHdhcm5pbmc9RkFMU0V9CnBlIDwtIGFnZ3JlZ2F0ZUZlYXR1cmVzKHBlLAogaSA9ICJwZXB0aWRlTm9ybSIsCiBmY29sID0gIlByb3RlaW5zIiwKIG5hLnJtID0gVFJVRSwKIG5hbWUgPSAicHJvdGVpbiIpCgoKcGUyIDwtIGFnZ3JlZ2F0ZUZlYXR1cmVzKHBlMiwKIGkgPSAicGVwdGlkZU5vcm0iLAogZmNvbCA9ICJQcm90ZWlucyIsCiBuYS5ybSA9IFRSVUUsCiBuYW1lID0gInByb3RlaW4iKQoKcGUzIDwtIGFnZ3JlZ2F0ZUZlYXR1cmVzKHBlMywKIGkgPSAicGVwdGlkZU5vcm0iLAogZmNvbCA9ICJQcm90ZWlucyIsCiBuYS5ybSA9IFRSVUUsCiBuYW1lID0gInByb3RlaW4iKQpgYGAKCjwvcD48L2RldGFpbHM+CgojIyBEYXRhIEV4cGxvcmF0aW9uOiB3aGF0IGlzIGltcGFjdCBvZiBibG9ja2luZz8gCgo8ZGV0YWlscz48c3VtbWFyeT4gQ2xpY2sgdG8gc2VlIGNvZGUgPC9zdW1tYXJ5PjxwPgpgYGB7cn0KbGV2ZWxzKGNvbERhdGEocGUzKSRtb3VzZSkgPC0gcGFzdGUwKCJtIiwxOjcpCm1kc09iajMgPC0gcGxvdE1EUyhhc3NheShwZTNbWyJwcm90ZWluIl1dKSwgcGxvdCA9IEZBTFNFKQptZHNPcmlnIDwtIGNvbERhdGEocGUzKSAlPiUKICBhcy5kYXRhLmZyYW1lICU+JQogIG11dGF0ZShtZHMxID0gbWRzT2JqMyR4LAogICAgICAgICBtZHMyID0gbWRzT2JqMyR5LAogICAgICAgICBsYWIgPSBwYXN0ZShtb3VzZSxjZWxsdHlwZSxzZXA9Il8iKSkgJT4lCiAgZ2dwbG90KGFlcyh4ID0gbWRzMSwgeSA9IG1kczIsIGxhYmVsID0gbGFiLCBjb2xvciA9IGNlbGx0eXBlLCBncm91cCA9IG1vdXNlKSkgKwogIGdlb21fdGV4dChzaG93LmxlZ2VuZCA9IEZBTFNFKSArCiAgZ2VvbV9wb2ludChzaGFwZSA9IDIxKSArCiAgZ2VvbV9saW5lKGNvbG9yID0gImJsYWNrIiwgbGluZXR5cGUgPSAiZGFzaGVkIikgKwogIHhsYWIoCiAgICBwYXN0ZTAoCiAgICAgIG1kc09iajMkYXhpc2xhYmVsLAogICAgICAiICIsCiAgICAgIDEsIAogICAgICAiICgiLAogICAgICBwYXN0ZTAoCiAgICAgICAgcm91bmQobWRzT2JqMyR2YXIuZXhwbGFpbmVkWzFdICoxMDAsMCksCiAgICAgICAgIiUiCiAgICAgICAgKSwKICAgICAgIikiCiAgICAgICkKICAgICkgKwogIHlsYWIoCiAgICBwYXN0ZTAoCiAgICAgIG1kc09iajMkYXhpc2xhYmVsLAogICAgICAiICIsCiAgICAgIDIsIAogICAgICAiICgiLAogICAgICBwYXN0ZTAoCiAgICAgICAgcm91bmQobWRzT2JqMyR2YXIuZXhwbGFpbmVkWzJdICoxMDAsMCksCiAgICAgICAgIiUiCiAgICAgICAgKSwKICAgICAgIikiCiAgICAgICkKICAgICkgKwogIGdndGl0bGUoIk9yaWdpbmFsIChSQ0IpIikKCmxldmVscyhjb2xEYXRhKHBlKSRtb3VzZSkgPC0gcGFzdGUwKCJtIiwxOjQpCm1kc09iaiA8LSBwbG90TURTKGFzc2F5KHBlW1sicHJvdGVpbiJdXSksIHBsb3QgPSBGQUxTRSkKbWRzUkNCIDwtIGNvbERhdGEocGUpICU+JQogIGFzLmRhdGEuZnJhbWUgJT4lCiAgbXV0YXRlKG1kczEgPSBtZHNPYmokeCwKICAgICAgICAgbWRzMiA9IG1kc09iaiR5LAogICAgICAgICBsYWIgPSBwYXN0ZShtb3VzZSxjZWxsdHlwZSxzZXA9Il8iKSkgJT4lCiAgZ2dwbG90KGFlcyh4ID0gbWRzMSwgeSA9IG1kczIsIGxhYmVsID0gbGFiLCBjb2xvciA9IGNlbGx0eXBlLCBncm91cCA9IG1vdXNlKSkgKwogIGdlb21fdGV4dChzaG93LmxlZ2VuZCA9IEZBTFNFKSArCiAgZ2VvbV9wb2ludChzaGFwZSA9IDIxKSArCiAgZ2VvbV9saW5lKGNvbG9yID0gImJsYWNrIiwgbGluZXR5cGUgPSAiZGFzaGVkIikgKwogIHhsYWIoCiAgICBwYXN0ZTAoCiAgICAgIG1kc09iaiRheGlzbGFiZWwsCiAgICAgICIgIiwKICAgICAgMSwgCiAgICAgICIgKCIsCiAgICAgIHBhc3RlMCgKICAgICAgICByb3VuZChtZHNPYmokdmFyLmV4cGxhaW5lZFsxXSAqMTAwLDApLAogICAgICAgICIlIgogICAgICAgICksCiAgICAgICIpIgogICAgICApCiAgICApICsKICB5bGFiKAogICAgcGFzdGUwKAogICAgICBtZHNPYmokYXhpc2xhYmVsLAogICAgICAiICIsCiAgICAgIDIsIAogICAgICAiICgiLAogICAgICBwYXN0ZTAoCiAgICAgICAgcm91bmQobWRzT2JqJHZhci5leHBsYWluZWRbMl0gKjEwMCwwKSwKICAgICAgICAiJSIKICAgICAgICApLAogICAgICAiKSIKICAgICAgKQogICAgKSArCiAgZ2d0aXRsZSgiUmFuZG9taXplZCBDb21wbGV0ZSBCbG9jayAoUkNCKSIpCgoKbGV2ZWxzKGNvbERhdGEocGUyKSRtb3VzZSkgPC0gcGFzdGUwKCJtIiwxOjgpCm1kc09iajIgPC0gcGxvdE1EUyhhc3NheShwZTJbWyJwcm90ZWluIl1dKSwgcGxvdCA9IEZBTFNFKQptZHNDUkQgPC0gY29sRGF0YShwZTIpICU+JQogIGFzLmRhdGEuZnJhbWUgJT4lCiAgbXV0YXRlKG1kczEgPSBtZHNPYmoyJHgsCiAgICAgICAgIG1kczIgPSBtZHNPYmoyJHksCiAgICAgICAgIGxhYiA9IHBhc3RlKG1vdXNlLGNlbGx0eXBlLHNlcD0iXyIpKSAlPiUKICBnZ3Bsb3QoYWVzKHggPSBtZHMxLCB5ID0gbWRzMiwgbGFiZWwgPSBsYWIsIGNvbG9yID0gY2VsbHR5cGUsIGdyb3VwID0gbW91c2UpKSArCiAgZ2VvbV90ZXh0KHNob3cubGVnZW5kID0gRkFMU0UpICsKICBnZW9tX3BvaW50KHNoYXBlID0gMjEpICsKICB4bGFiKAogICAgcGFzdGUwKAogICAgICBtZHNPYmokYXhpc2xhYmVsLAogICAgICAiICIsCiAgICAgIDEsIAogICAgICAiICgiLAogICAgICBwYXN0ZTAoCiAgICAgICAgcm91bmQobWRzT2JqMiR2YXIuZXhwbGFpbmVkWzFdICoxMDAsMCksCiAgICAgICAgIiUiCiAgICAgICAgKSwKICAgICAgIikiCiAgICAgICkKICAgICkgKwogIHlsYWIoCiAgICBwYXN0ZTAoCiAgICAgIG1kc09iaiRheGlzbGFiZWwsCiAgICAgICIgIiwKICAgICAgMiwgCiAgICAgICIgKCIsCiAgICAgIHBhc3RlMCgKICAgICAgICByb3VuZChtZHNPYmoyJHZhci5leHBsYWluZWRbMl0gKjEwMCwwKSwKICAgICAgICAiJSIKICAgICAgICApLAogICAgICAiKSIKICAgICAgKQogICAgKSArCiAgZ2d0aXRsZSgiQ29tcGxldGVseSBSYW5kb21pemVkIERlc2lnbiAoQ1JEKSIpCmBgYAo8L3A+PC9kZXRhaWxzPgpgYGB7cn0KbWRzT3JpZwptZHNSQ0IKbWRzQ1JECmBgYAoKLSBXZSBvYnNlcnZlIHRoYXQgdGhlIGxlYWRpbmcgZm9sZCBjaGFuZ2UgaXMgYWNjb3JkaW5nIHRvIG1vdXNlCi0gSW4gdGhlIHNlY29uZCBkaW1lbnNpb24gd2Ugc2VlIGEgc2VwYXJhdGlvbiBhY2NvcmRpbmcgdG8gY2VsbC10eXBlIAotIFdpdGggdGhlIFJhbmRvbWl6ZWQgQ29tcGxldGUgQmxvY2sgZGVzaWduIChSQ0IpIHdlIGNhbiByZW1vdmUgdGhlIG1vdXNlIGVmZmVjdCBmcm9tIHRoZSBhbmFseXNpcyEKCiMjIE1vZGVsaW5nIGFuZCBpbmZlcmVuY2UKCiMjIyBSQ0IgYW5hbHlzaXMKYGBge3Igd2FybmluZz1GQUxTRX0KcGUgPC0gbXNxcm9iKAogIG9iamVjdCA9IHBlLAogIGkgPSAicHJvdGVpbiIsCiAgZm9ybXVsYSA9IH4gY2VsbHR5cGUgKyBtb3VzZSkKYGBgCgojIyMgUkNCIHdyb25nIGFuYWx5c2lzCmBgYHtyIHdhcm5pbmc9RkFMU0V9CnBlIDwtIG1zcXJvYigKICBvYmplY3QgPSBwZSwKICBpID0gInByb3RlaW4iLAogIGZvcm11bGEgPSB+IGNlbGx0eXBlLCBtb2RlbENvbHVtbk5hbWUgPSAid3JvbmdNb2RlbCIpCmBgYAoKIyMgQ1JEIGFuYWx5c2lzIApgYGB7ciB3YXJuaW5nID0gRkFMU0V9CnBlMiA8LSBtc3Fyb2IoCiAgb2JqZWN0ID0gcGUyLAogIGkgPSAicHJvdGVpbiIsCiAgZm9ybXVsYSA9IH4gY2VsbHR5cGUpCmBgYAoKIyMjIEluZmVyZW5jZSAKCmBgYHtyfQpsaWJyYXJ5KEV4cGxvcmVNb2RlbE1hdHJpeCkKVmlzdWFsaXplRGVzaWduKGNvbERhdGEocGUpLH4gY2VsbHR5cGUgKyBtb3VzZSkkcGxvdGxpc3QKVmlzdWFsaXplRGVzaWduKGNvbERhdGEocGUyKSx+IGNlbGx0eXBlKSRwbG90bGlzdApgYGAKCgpgYGB7cn0KTCA8LSBtYWtlQ29udHJhc3QoImNlbGx0eXBlVHJlZyA9IDAiLCBwYXJhbWV0ZXJOYW1lcyA9IGMoImNlbGx0eXBlVHJlZyIpKQpwZSA8LSBoeXBvdGhlc2lzVGVzdChvYmplY3QgPSBwZSwgaSA9ICJwcm90ZWluIiwgY29udHJhc3QgPSBMKQpwZSA8LSBoeXBvdGhlc2lzVGVzdChvYmplY3QgPSBwZSwgaSA9ICJwcm90ZWluIiwgY29udHJhc3QgPSBMLCBtb2RlbENvbHVtbiA9ICJ3cm9uZ01vZGVsIiwgcmVzdWx0c0NvbHVtbk5hbWVQcmVmaXg9Indyb25nIikKcGUyIDwtIGh5cG90aGVzaXNUZXN0KG9iamVjdCA9IHBlMiwgaSA9ICJwcm90ZWluIiwgY29udHJhc3QgPSBMKQpgYGAKCiMgQWR2YW50YWdlIG9mIEJsb2NraW5nOiBjb21wYXJpc29uIGJldHdlZW4gZGVzaWducwoKIyMgVm9sY2FubyBwbG90cwoKPGRldGFpbHM+PHN1bW1hcnk+IENsaWNrIHRvIHNlZSBjb2RlIDwvc3VtbWFyeT48cD4KYGBge3J9CnZvbGNhbm9SQ0IgPC0gZ2dwbG90KAogICAgcm93RGF0YShwZVtbInByb3RlaW4iXV0pJGNlbGx0eXBlVHJlZywKICAgIGFlcyh4ID0gbG9nRkMsIHkgPSAtbG9nMTAocHZhbCksIGNvbG9yID0gYWRqUHZhbCA8IDAuMDUpCikgKwogICAgZ2VvbV9wb2ludChjZXggPSAyLjUpICsKICAgIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBhbHBoYShjKCJibGFjayIsICJyZWQiKSwgMC41KSkgKwogICAgdGhlbWVfbWluaW1hbCgpICsKICAgIGdndGl0bGUocGFzdGUwKCJSQ0I6IFxuIiwgCiAgICAgICAgICAgICAgICBzdW0ocm93RGF0YShwZVtbInByb3RlaW4iXV0pJGNlbGx0eXBlVHJlZyRhZGpQdmFsPDAuMDUsbmEucm09VFJVRSksCiAgICAgICAgICAgICIgc2lnbmlmaWNhbnQiKSkKCnZvbGNhbm9SQ0J3cm9uZyA8LSBnZ3Bsb3QoCiAgICByb3dEYXRhKHBlW1sicHJvdGVpbiJdXSkkd3JvbmdjZWxsdHlwZVRyZWcsCiAgICBhZXMoeCA9IGxvZ0ZDLCB5ID0gLWxvZzEwKHB2YWwpLCBjb2xvciA9IGFkalB2YWwgPCAwLjA1KQopICsKICAgIGdlb21fcG9pbnQoY2V4ID0gMi41KSArCiAgICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gYWxwaGEoYygiYmxhY2siLCAicmVkIiksIDAuNSkpICsKICAgIHRoZW1lX21pbmltYWwoKSArCiAgICBnZ3RpdGxlKHBhc3RlMCgiUkNCIHdyb25nOiBcbiIsIAogICAgICAgICAgICAgICAgc3VtKHJvd0RhdGEocGVbWyJwcm90ZWluIl1dKSR3cm9uZ2NlbGx0eXBlVHJlZyRhZGpQdmFsPDAuMDUsbmEucm09VFJVRSksCiAgICAgICAgICAgICIgc2lnbmlmaWNhbnQiKSkKCnZvbGNhbm9DUkQgPC0gZ2dwbG90KAogICAgcm93RGF0YShwZTJbWyJwcm90ZWluIl1dKSRjZWxsdHlwZVRyZWcsCiAgICBhZXMoeCA9IGxvZ0ZDLCB5ID0gLWxvZzEwKHB2YWwpLCBjb2xvciA9IGFkalB2YWwgPCAwLjA1KQopICsKICAgIGdlb21fcG9pbnQoY2V4ID0gMi41KSArCiAgICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gYWxwaGEoYygiYmxhY2siLCAicmVkIiksIDAuNSkpICsKICAgIHRoZW1lX21pbmltYWwoKSArCiAgICBnZ3RpdGxlKHBhc3RlMCgiQ1JEOiBcbiIsIAogICAgICAgICAgICAgICAgc3VtKHJvd0RhdGEocGUyW1sicHJvdGVpbiJdXSkkY2VsbHR5cGVUcmVnJGFkalB2YWw8MC4wNSxuYS5ybT1UUlVFKSwKICAgICAgICAgICAgIiBzaWduaWZpY2FudCIpKQpgYGAKPC9wPjwvZGV0YWlscz4KICAKYGBge3J9CmdyaWQuYXJyYW5nZSh2b2xjYW5vUkNCLHZvbGNhbm9DUkQsIHZvbGNhbm9SQ0J3cm9uZyxuY29sPTIpCmBgYAoKIyMgQW5vdmEgdGFibGU6IFE3VFBSNCwgQWxwaGEtYWN0aW5pbi0xCgpEaXNjbGFpbWVyOiB0aGUgQW5vdmEgYW5hbHlzaXMgaXMgb25seSBmb3IgZGlkYWN0aWNhbCBwdXJwb3Nlcy4gSW4gcHJhY3RpY2Ugd2UgYXNzZXNzIHRoZSBoeXBvdGhlc2VzIHVzaW5nIG1zcXJvYjIuIAoKLSBXZSBpbGx1c3RyYXRlIHRoZSBwb3dlciBnYWluIG9mIGJsb2NraW5nIHVzaW5nIGFuIEFub3ZhIGFuYWx5c2lzIG9uIDEgcHJvdGVpbi4gCgotIE5vdGUsIHRoYXQgbXNxcm9iMiB3aWxsIHBlcmZvcm0gYSBzaW1pbGFyIGFuYWx5c2lzLCBidXQsIGl0IHVzZXMgcm9idXN0IHJlZ3Jlc3Npb24gYW5kIGl0IHVzZXMgYW4gZW1waXJpY2FsIEJheWVzIGVzdGltYXRvciBmb3IgdGhlIHZhcmlhbmNlLgoKYGBge3J9CnByb3QgPC0gIlE3VFBSNCIKZGF0YUhscCA8LSBjb2xEYXRhKHBlKSAlPiUgCiAgYXMuZGF0YS5mcmFtZSAlPiUKICBtdXRhdGUoaW50ZW5zaXR5PWFzc2F5KHBlW1sicHJvdGVpbiJdXSlbcHJvdCxdLAogICAgICAgICBpbnRlbnNpdHlDUkQ9YXNzYXkocGUyW1sicHJvdGVpbiJdXSlbcHJvdCxdKQoKICBhbm92YShsbShpbnRlbnNpdHl+IGNlbGx0eXBlICsgbW91c2UsIGRhdGFIbHApKSAKICBhbm92YShsbShpbnRlbnNpdHl+IGNlbGx0eXBlLGRhdGFIbHApKQogIGFub3ZhKGxtKGludGVuc2l0eUNSRH4gY2VsbHR5cGUsZGF0YUhscCkpCmBgYAoKIyMgQ29tcGFyaXNvbiBFbXBpcmljYWwgQmF5ZXMgc3RhbmRhcmQgZGV2aWF0aW9uIGluIG1zcXJvYjIgCgo8ZGV0YWlscz48c3VtbWFyeT4gQ2xpY2sgdG8gc2VlIGNvZGUgPC9zdW1tYXJ5PjxwPgpgYGB7cn0KYWNjZXNzaW9ucyA8LSByb3duYW1lcyhwZVtbInByb3RlaW4iXV0pW3Jvd25hbWVzKHBlW1sicHJvdGVpbiJdXSklaW4lcm93bmFtZXMocGUyW1sicHJvdGVpbiJdXSldCmRhdCA8LSBkYXRhLmZyYW1lKApzaWdtYVJCQyA9IHNhcHBseShyb3dEYXRhKHBlW1sicHJvdGVpbiJdXSkkbXNxcm9iTW9kZWxzW2FjY2Vzc2lvbnNdLCBnZXRTaWdtYVBvc3RlcmlvciksCnNpZ21hUkJDd3JvbmcgPSBzYXBwbHkocm93RGF0YShwZVtbInByb3RlaW4iXV0pJHdyb25nTW9kZWxbYWNjZXNzaW9uc10sIGdldFNpZ21hUG9zdGVyaW9yKSwKc2lnbWFDUkQgPC0gc2FwcGx5KHJvd0RhdGEocGUyW1sicHJvdGVpbiJdXSkkbXNxcm9iTW9kZWxzW2FjY2Vzc2lvbnNdLCBnZXRTaWdtYVBvc3RlcmlvcikKKQoKIHBsb3RSQkN2c1dyb25nIDwtIGdncGxvdChkYXRhID0gZGF0LCBhZXMoc2lnbWFSQkMsIHNpZ21hUkJDd3JvbmcpKSArCiAgICBnZW9tX3BvaW50KGFscGhhID0gMC4xLCBzaGFwZSA9IDIwKSArCiAgICBzY2FsZV94X2xvZzEwKCkgKwogICAgc2NhbGVfeV9sb2cxMCgpICsKICAgIGdlb21fYWJsaW5lKGludGVyY2VwdD0wLHNsb3BlPTEpCnBsb3RDUkR2c1dyb25nIDwtIGdncGxvdChkYXRhID0gZGF0LCBhZXMoc2lnbWFDUkQsIHNpZ21hUkJDd3JvbmcpKSArCiAgICBnZW9tX3BvaW50KGFscGhhID0gMC4xLCBzaGFwZSA9IDIwKSArCiAgICBzY2FsZV94X2xvZzEwKCkgKwogICAgc2NhbGVfeV9sb2cxMCgpICsKICAgIGdlb21fYWJsaW5lKGludGVyY2VwdD0wLHNsb3BlPTEpCnBsb3RSQkN2c0NSRCA8LSBnZ3Bsb3QoZGF0YSA9IGRhdCwgYWVzKHNpZ21hUkJDLCBzaWdtYUNSRCkpICsKICAgIGdlb21fcG9pbnQoYWxwaGEgPSAwLjEsIHNoYXBlID0gMjApICsKICAgIHNjYWxlX3hfbG9nMTAoKSArCiAgICBzY2FsZV95X2xvZzEwKCkgKwogICAgZ2VvbV9hYmxpbmUoaW50ZXJjZXB0PTAsc2xvcGU9MSkKYGBgCjwvcD48L2RldGFpbHM+CgpgYGB7cn0KZ3JpZC5hcnJhbmdlKAogIHBsb3RSQkN2c1dyb25nLAogIHBsb3RDUkR2c1dyb25nLAogIHBsb3RSQkN2c0NSRCwKICBucm93PTIpCmBgYAoKLSBXZSBjbGVhcmx5IG9ic2VydmUgdGhhdCB0aGUgc3RhbmRhcmQgZGV2aWF0aW9uIG9mIHRoZSBwcm90ZWluIGV4cHJlc3Npb24gaW4gdGhlIFJDQiBpcyBzbWFsbGVyIGZvciB0aGUgbWFqb3JpdHkgb2YgdGhlIHByb3RlaW5zIHRoYW4gdGhhdCBvYnRhaW5lZCB3aXRoIHRoZSBDUkQKCi0gVGhlIHN0YW5kYXJkIGRldmlhdGlvbiBvZiB0aGUgcHJvdGVpbiBleHByZXNzaW9uIFJDQiB3aGVyZSB3ZSBwZXJmb3JtIGEgd3JvbmcgYW5hbHlzaXMgd2l0aG91dCBjb25zaWRlcmluZyB0aGUgYmxvY2tpbmcgZmFjdG9yIGFjY29yZGluZyB0byBtb3VzZSBpcyBtdWNoIGxhcmdlciBmb3IgdGhlIG1hcmpvcml0eSBvZiB0aGUgcHJvdGVpbnMgdGhhbiB0aGF0IG9idGFpbmVkIHdpdGggdGhlIGNvcnJlY3QgYW5hbHlzaXMuIAoKLSBJbmRlZWQsIHdoZW4gd2UgaWdub3JlIHRoZSBibG9ja2luZyBmYWN0b3IgaW4gdGhlIFJDQiBkZXNpZ24gd2UgZG8gbm90IHJlbW92ZSB0aGUgdmFyaWFiaWxpdHkgYWNjb3JkaW5nIHRvIG1vdXNlIGZyb20gdGhlIGFuYWx5c2lzIGFuZCB0aGUgbW91c2UgZWZmZWN0IGlzIGFic29yYmVkIGluIHRoZSBlcnJvciB0ZXJtLiBUaGUgc3RhbmRhcmQgZGV2aWF0aW9uIHRoYW4gYmVjb21lcyB2ZXJ5IGNvbXBhcmFibGUgdG8gdGhhdCBvYnNlcnZlZCBpbiB0aGUgY29tcGxldGVseSByYW5kb21pc2VkIGRlc2lnbiB3aGVyZSB3ZSBjb3VsZCBub3QgcmVtb3ZlIHRoZSBtb3VzZSBlZmZlY3QgZnJvbSB0aGUgYW5hbHlzaXMuIAoKLSBXaHkgYXJlIHNvbWUgb2YgdGhlIHN0YW5kYXJkIGRldmlhdGlvbnMgZm9yIHRoZSBSQ0Igd2l0aCB0aGUgY29ycmVjdCBhbmFseXNpcyBsYXJnZXIgdGhhbiB0aGFuIG9mIHRoZSBSQ0Igd2l0aCB0aGUgaW5jb3JyZWN0IGFuYWx5c2lzIHRoYXQgaWdub3JlZCB0aGUgbW91c2UgYmxvY2tpbmcgZmFjdG9yPwoKLSBDYW4geW91IHRoaW5rIG9mIGEgcmVhc29uIHdoeSBpdCB3b3VsZCBub3QgYmUgdXNlZnVsIHRvIGJsb2NrIG9uIGEgcGFydGljdWxhciBmYWN0b3I/IAo=