1 Import data

The scRNAseq package provides convenient access to several datasets. See the package Bioconductor page for more information.

# install BiocManager package if not installed yet.
# BiocManager is the package installer for Bioconductor software.
if (!requireNamespace("BiocManager", quietly = TRUE))
    install.packages("BiocManager")

# install packages if not yet installed.
pkgs <- c("SingleCellExperiment", "DropletUtils", "scRNAseq", "scater", "scuttle", "scran", "BiocSingular", "scDblFinder", "glmpca", "uwot")
notInstalled <- pkgs[!pkgs %in% installed.packages()[,1]]
if(length(notInstalled) > 0){
  BiocManager::install(notInstalled)
}
## 'getOption("repos")' replaces Bioconductor standard repositories, see
## '?repositories' for details
## 
## replacement repositories:
##     CRAN: https://cloud.r-project.org
## Bioconductor version 3.14 (BiocManager 1.30.16), R 4.1.2 (2021-11-01)
## Installing package(s) 'DropletUtils', 'scater', 'scuttle', 'scran',
##   'BiocSingular', 'scDblFinder', 'glmpca', 'uwot'
## also installing the dependencies 'sparseMatrixStats', 'rhdf5filters', 'R.oo', 'R.methodsS3', 'sitmo', 'RcppHNSW', 'beeswarm', 'vipor', 'DelayedMatrixStats', 'HDF5Array', 'rhdf5', 'R.utils', 'dqrng', 'beachmat', 'Rhdf5lib', 'BiocNeighbors', 'ggbeeswarm', 'viridis', 'Rtsne', 'ggrepel', 'statmod', 'bluster', 'metapod', 'ScaledMatrix', 'irlba', 'rsvd', 'xgboost', 'FNN', 'RSpectra', 'RcppAnnoy', 'RcppProgress'
## 
##   There are binary versions available but the source versions are later:
##       binary source needs_compilation
## irlba  2.3.3  2.3.4              TRUE
## uwot  0.1.10 0.1.11              TRUE
## 
## 
## The downloaded binary packages are in
##  /var/folders/24/8k48jl6d249_n_qfxwsl6xvm0000gn/T//RtmpBCpCAH/downloaded_packages
## installing the source packages 'irlba', 'uwot'
# Code below might ask you to create an ExperimentHub directory. 
# Type 'yes' and hit Enter, to allow this.
suppressPackageStartupMessages(library(scRNAseq))
sce <- MacoskoRetinaData()
## snapshotDate(): 2021-10-19
## see ?scRNAseq and browseVignettes('scRNAseq') for documentation
## loading from cache
## see ?scRNAseq and browseVignettes('scRNAseq') for documentation
## loading from cache

2 A SingleCellExperiment object

sce
## class: SingleCellExperiment 
## dim: 24658 49300 
## metadata(0):
## assays(1): counts
## rownames(24658): KITL TMTC3 ... 1110059M19RIK GM20861
## rowData names(0):
## colnames(49300): r1_GGCCGCAGTCCG r1_CTTGTGCGGGAA ... p1_TAACGCGCTCCT
##   p1_ATTCTTGTTCTT
## colData names(2): cell.id cluster
## reducedDimNames(0):
## mainExpName: NULL
## altExpNames(0):

2.1 Accessing data from a SingleCellExperiment object

Please see Figure 4.1 in OSCA for an overview of a SingleCellExperiment object.

# Data: assays
assays(sce)
## List of length 1
## names(1): counts
assays(sce)$counts[1:5, 1:5]
## 5 x 5 sparse Matrix of class "dgCMatrix"
##               r1_GGCCGCAGTCCG r1_CTTGTGCGGGAA r1_GCGCAACTGCTC r1_GATTGGGAGGCA
## KITL                        .               .               1               .
## TMTC3                       3               .               .               .
## CEP290                      1               3               .               2
## 4930430F08RIK               2               1               2               .
## 1700017N19RIK               .               .               .               .
##               r1_CCTCCTAGTTGG
## KITL                        .
## TMTC3                       2
## CEP290                      1
## 4930430F08RIK               1
## 1700017N19RIK               .
# Feature metadata: rowData
rowData(sce) # empty for now
## DataFrame with 24658 rows and 0 columns
# Cell metadata: colData
colData(sce)
## DataFrame with 49300 rows and 2 columns
##                         cell.id   cluster
##                     <character> <integer>
## r1_GGCCGCAGTCCG r1_GGCCGCAGTCCG         2
## r1_CTTGTGCGGGAA r1_CTTGTGCGGGAA         2
## r1_GCGCAACTGCTC r1_GCGCAACTGCTC         2
## r1_GATTGGGAGGCA r1_GATTGGGAGGCA         2
## r1_CCTCCTAGTTGG r1_CCTCCTAGTTGG        NA
## ...                         ...       ...
## p1_TCAAAAGCCGGG p1_TCAAAAGCCGGG        24
## p1_ATTAAGTTCCAA p1_ATTAAGTTCCAA        34
## p1_CTGTCTGAGACC p1_CTGTCTGAGACC         2
## p1_TAACGCGCTCCT p1_TAACGCGCTCCT        24
## p1_ATTCTTGTTCTT p1_ATTCTTGTTCTT        24
# Reduced dimensions: reducedDims
reducedDims(sce) # empty for now
## List of length 0
## names(0):

2.2 Creating a new SingleCellExperiment object

sceNew <- SingleCellExperiment(assays = list(counts = assays(sce)$counts))
sceNew
## class: SingleCellExperiment 
## dim: 24658 49300 
## metadata(0):
## assays(1): counts
## rownames(24658): KITL TMTC3 ... 1110059M19RIK GM20861
## rowData names(0):
## colnames(49300): r1_GGCCGCAGTCCG r1_CTTGTGCGGGAA ... p1_TAACGCGCTCCT
##   p1_ATTCTTGTTCTT
## colData names(0):
## reducedDimNames(0):
## mainExpName: NULL
## altExpNames(0):
rm(sceNew)

2.3 Storing (meta)data in a SingleCellExperiment object

fakeGeneNames <- paste0("gene", 1:nrow(sce))
rowData(sce)$fakeName <- fakeGeneNames
head(rowData(sce))
## DataFrame with 6 rows and 1 column
##                  fakeName
##               <character>
## KITL                gene1
## TMTC3               gene2
## CEP290              gene3
## 4930430F08RIK       gene4
## 1700017N19RIK       gene5
## MGAT4C              gene6
# Remove again by setting to NULL
rowData(sce)$fakeName <- NULL

assays(sce)$logCounts <- log1p(assays(sce)$counts)
assays(sce)
## List of length 2
## names(2): counts logCounts
assays(sce)$logCounts[1:5, 1:5]
## 5 x 5 sparse Matrix of class "dgCMatrix"
##               r1_GGCCGCAGTCCG r1_CTTGTGCGGGAA r1_GCGCAACTGCTC r1_GATTGGGAGGCA
## KITL                .               .               0.6931472        .       
## TMTC3               1.3862944       .               .                .       
## CEP290              0.6931472       1.3862944       .                1.098612
## 4930430F08RIK       1.0986123       0.6931472       1.0986123        .       
## 1700017N19RIK       .               .               .                .       
##               r1_CCTCCTAGTTGG
## KITL                .        
## TMTC3               1.0986123
## CEP290              0.6931472
## 4930430F08RIK       0.6931472
## 1700017N19RIK       .
assays(sce)$logCounts <- NULL

3 Filtering non-informative genes

keep <- rowSums(assays(sce)$counts > 0) > 10
table(keep)
## keep
## FALSE  TRUE 
##  6771 17887
sce <- sce[keep,]

4 Quality control

4.1 Calculate QC variables

library(scater)
## Loading required package: scuttle
## Loading required package: ggplot2
is.mito <- grepl("^MT-", rownames(sce))
sum(is.mito) # 28 mitochondrial genes
## [1] 28
df <- perCellQCMetrics(sce, subsets=list(Mito=is.mito))
## add the QC variables to sce object
colData(sce) <- cbind(colData(sce), df)
# the QC variables have now been added to the colData of our SCE object.
colData(sce)
## DataFrame with 49300 rows and 8 columns
##                         cell.id   cluster       sum  detected subsets_Mito_sum
##                     <character> <integer> <numeric> <integer>        <numeric>
## r1_GGCCGCAGTCCG r1_GGCCGCAGTCCG         2     37478      7235              427
## r1_CTTGTGCGGGAA r1_CTTGTGCGGGAA         2     32034      6921              503
## r1_GCGCAACTGCTC r1_GCGCAACTGCTC         2     28140      6390              460
## r1_GATTGGGAGGCA r1_GATTGGGAGGCA         2     20352      5727              326
## r1_CCTCCTAGTTGG r1_CCTCCTAGTTGG        NA     19550      5769              264
## ...                         ...       ...       ...       ...              ...
## p1_TCAAAAGCCGGG p1_TCAAAAGCCGGG        24       817       537               13
## p1_ATTAAGTTCCAA p1_ATTAAGTTCCAA        34       817       574               10
## p1_CTGTCTGAGACC p1_CTGTCTGAGACC         2       816       636               24
## p1_TAACGCGCTCCT p1_TAACGCGCTCCT        24       816       488               27
## p1_ATTCTTGTTCTT p1_ATTCTTGTTCTT        24       816       484               16
##                 subsets_Mito_detected subsets_Mito_percent     total
##                             <integer>            <numeric> <numeric>
## r1_GGCCGCAGTCCG                    14              1.13934     37478
## r1_CTTGTGCGGGAA                    15              1.57021     32034
## r1_GCGCAACTGCTC                    13              1.63468     28140
## r1_GATTGGGAGGCA                    11              1.60181     20352
## r1_CCTCCTAGTTGG                     9              1.35038     19550
## ...                               ...                  ...       ...
## p1_TCAAAAGCCGGG                     4              1.59119       817
## p1_ATTAAGTTCCAA                     5              1.22399       817
## p1_CTGTCTGAGACC                     7              2.94118       816
## p1_TAACGCGCTCCT                     5              3.30882       816
## p1_ATTCTTGTTCTT                     4              1.96078       816

4.2 EDA

High-quality cells should have many features expressed, and a low contribution of mitochondrial genes. Here, we see that several cells have a very low number of expressed genes, and where most of the molecules are derived from mitochondrial genes. This indicates likely damaged cells, presumably because of loss of cytoplasmic RNA from perforated cells, so we’d want to remove these for the downstream analysis.

# Number of genes vs library size
plotColData(sce, x = "sum", y="detected", colour_by="cluster") 

# Mitochondrial genes
plotColData(sce, x = "detected", y="subsets_Mito_percent")

4.3 QC using adaptive thresholds

Below, we remove cells that are outlying with respect to

  1. A low sequencing depth (number of UMIs);
  2. A low number of genes detected;
  3. A high percentage of reads from mitochondrial genes.

We remove a total of \(3423\) cells, most of which because of an outlyingly high percentage of reads from mitochondrial genes.

lowLib <- isOutlier(df$sum, type="lower", log=TRUE)
lowFeatures <- isOutlier(df$detected, type="lower", log=TRUE)
highMito <- isOutlier(df$subsets_Mito_percent, type="higher")

table(lowLib)
## lowLib
## FALSE 
## 49300
table(lowFeatures)
## lowFeatures
## FALSE  TRUE 
## 49287    13
table(highMito)
## highMito
## FALSE  TRUE 
## 45890  3410
discardCells <- (lowLib | lowFeatures | highMito)
table(discardCells)
## discardCells
## FALSE  TRUE 
## 45877  3423
colData(sce)$discardCells <- discardCells

# visualize cells to be removed
plotColData(sce, x = "detected", y="subsets_Mito_percent", colour_by = "discardCells")

4.4 Identifying and removing empty droplets

Note that the removal of cells with low sequencing depth using the adaptive threshold procedure above is a way of removing empty droplets. Other approaches are possible, e.g., removing cells by statistical testing using emtpyDrops. This does require us to specify a lower bound on the total number of UMIs, below which all cells are considered to correspond to empty droplets. This lower bound may not be trivial to derive, but the barcodeRanks function can be useful to identify an elbow/knee point.

library(DropletUtils)
bcrank <- barcodeRanks(counts(sce))

# Only showing unique points for plotting speed.
uniq <- !duplicated(bcrank$rank)
plot(bcrank$rank[uniq], bcrank$total[uniq], log="xy",
    xlab="Rank", ylab="Total UMI count", cex.lab=1.2)

abline(h=metadata(bcrank)$inflection, col="darkgreen", lty=2)
abline(h=metadata(bcrank)$knee, col="dodgerblue", lty=2)
abline(h=350, col="orange", lty=2) # picked visually myself

legend("topright", legend=c("Inflection", "Knee", "Empirical knee point"), 
        col=c("darkgreen", "dodgerblue", "orange"), lty=2, cex=1.2)

set.seed(100)
limit <- 350   
all.out <- emptyDrops(counts(sce), lower=limit, test.ambient=TRUE)
## Warning in smooth.spline(x[new.keep], y[new.keep], df = df, ...): not using
## invalid df; must have 1 < df <= n := #{unique x} = 9
# p-values for cells with total UMI count under the lower bound.
hist(all.out$PValue[all.out$Total <= limit & all.out$Total > 0],
    xlab="P-value", main="", col="grey80")

# but note that it would remove a very high number of cells
length(which(all.out$FDR <= 0.001))
## [1] 25080
# so we stick to the more lenient adaptive filtering strategy
# remove cells identified using adaptive thresholds
sce <- sce[, !colData(sce)$discardCells]

4.5 Identifying and removing doublets

We will use scDblFinder to detect doublet cells.

## perform doublet detection
library(scDblFinder)
set.seed(211103)
sampleID <- unlist(lapply(strsplit(colData(sce)$cell.id, split="_"), "[[", 1))
table(sampleID)
## sampleID
##   p1   r1   r2   r3   r4   r5   r6 
## 3942 5953 8414 5319 7015 7487 7747
sce <- scDblFinder(sce, returnType="table",
                 samples = factor(sampleID))
table(sce$scDblFinder.class)
## 
## singlet doublet 
##   41220    4657
## visualize these scores
## explore doublet score wrt original cluster labels
boxplot(log1p(sce$scDblFinder.score) ~ factor(colData(sce)$cluster, exclude=NULL))

tab <- table(sce$scDblFinder.class, sce$cluster, 
      exclude=NULL)
tab
##          
##               1     2     3     4     5     6     7     8     9    10    11
##   singlet   213   370   253    63    69   177   264   141   305   165   180
##   doublet     2     7    22     9     5    28    53     8    32    24    27
##          
##              12    13    14    15    16    17    18    19    20    21    22
##   singlet   211    47    85    51   196   304    65   111   318   172   201
##   doublet    43     3    23    21    55    67    15    14    64    75    60
##          
##              23    24    25    26    27    28    29    30    31    32    33
##   singlet   216 25026  1604  1932   472   357   464   517   412   266   658
##   doublet    43  1917   169   229   157   124   117   113    90    52   155
##          
##              34    35    36    37    38    39  <NA>
##   singlet  1431    36    42   231    56    64  3475
##   doublet   133     7     3     9     3     2   677
t(t(tab) / colSums(tab))
##          
##                     1           2           3           4           5
##   singlet 0.990697674 0.981432361 0.920000000 0.875000000 0.932432432
##   doublet 0.009302326 0.018567639 0.080000000 0.125000000 0.067567568
##          
##                     6           7           8           9          10
##   singlet 0.863414634 0.832807571 0.946308725 0.905044510 0.873015873
##   doublet 0.136585366 0.167192429 0.053691275 0.094955490 0.126984127
##          
##                    11          12          13          14          15
##   singlet 0.869565217 0.830708661 0.940000000 0.787037037 0.708333333
##   doublet 0.130434783 0.169291339 0.060000000 0.212962963 0.291666667
##          
##                    16          17          18          19          20
##   singlet 0.780876494 0.819407008 0.812500000 0.888000000 0.832460733
##   doublet 0.219123506 0.180592992 0.187500000 0.112000000 0.167539267
##          
##                    21          22          23          24          25
##   singlet 0.696356275 0.770114943 0.833976834 0.928849794 0.904681331
##   doublet 0.303643725 0.229885057 0.166023166 0.071150206 0.095318669
##          
##                    26          27          28          29          30
##   singlet 0.894030541 0.750397456 0.742203742 0.798623064 0.820634921
##   doublet 0.105969459 0.249602544 0.257796258 0.201376936 0.179365079
##          
##                    31          32          33          34          35
##   singlet 0.820717131 0.836477987 0.809348093 0.914961637 0.837209302
##   doublet 0.179282869 0.163522013 0.190651907 0.085038363 0.162790698
##          
##                    36          37          38          39        <NA>
##   singlet 0.933333333 0.962500000 0.949152542 0.969696970 0.836946050
##   doublet 0.066666667 0.037500000 0.050847458 0.030303030 0.163053950
barplot(t(t(tab) / colSums(tab))[2,],
        xlab = "Cluster", ylab = "Fraction of doublets")

# remove doublets
sce <- sce[,!sce$scDblFinder.class == "doublet"]

5 Normalization

For normalization, the size factors \(s_i\) computed here are simply scaled library sizes: \[ N_i = \sum_g Y_{gi} \] \[ s_i = N_i / \bar{N}_i \]

sce <- logNormCounts(sce)

# note we also returned log counts: see the additional logcounts assay.
sce
## class: SingleCellExperiment 
## dim: 17887 41220 
## metadata(0):
## assays(2): counts logcounts
## rownames(17887): KITL TMTC3 ... GM16012 GM21464
## rowData names(0):
## colnames(41220): r1_GGCCGCAGTCCG r1_CTTGTGCGGGAA ... p1_TAACGCGCTCCT
##   p1_ATTCTTGTTCTT
## colData names(15): cell.id cluster ... scDblFinder.cxds_score
##   sizeFactor
## reducedDimNames(0):
## mainExpName: NULL
## altExpNames(0):
# you can extract size factors using
sf <- librarySizeFactors(sce)
mean(sf) # equal to 1 due to scaling.
## [1] 1
plot(x= log(colSums(assays(sce)$counts)), 
     y=sf)

6 Feature selection

library(scran)
dec <- modelGeneVar(sce)
fitRetina <- metadata(dec)
plot(fitRetina$mean, fitRetina$var, 
     xlab="Mean of log-expression",
    ylab="Variance of log-expression")
curve(fitRetina$trend(x), col="dodgerblue", add=TRUE, lwd=2)

# get 10% highly variable genes
hvg <- getTopHVGs(dec, prop=0.1)
head(hvg)
## [1] "RHO"     "CALM1"   "MEG3"    "GNGT1"   "RPGRIP1" "SAG"
# plot these 
plot(fitRetina$mean, fitRetina$var, 
     col = c("orange", "darkseagreen3")[(names(fitRetina$mean) %in% hvg)+1],
     xlab="Mean of log-expression",
    ylab="Variance of log-expression")
curve(fitRetina$trend(x), col="dodgerblue", add=TRUE, lwd=2)
legend("topleft", 
       legend = c("Selected", "Not selected"), 
       col = c("darkseagreen3", "orange"),
       pch = 16,
       bty='n')

7 Dimensionality reduction

Note that, below, we color the cells using the known, true cell type label as defined in the metadata, to empirically evaluate the dimensionality reduction. In reality, we don’t know this yet at this stage.

7.1 The most basic DR

Just by looking at the top two genes based on our feature selection criterion, we can already see some separation according to the cell type!

colData(sce)$cluster <- as.factor(colData(sce)$cluster)
cl <- colData(sce)$cluster

par(bty='l')
plot(x = assays(sce)$counts[hvg[1],],
     y = assays(sce)$counts[hvg[2],],
     col = as.numeric(cl),
     pch = 16, cex = 1/3,
     xlab = "Most informative gene",
     ylab = "Second most informative gene",
     main = "Cells colored acc to cell type")

7.2 Linear dimensionality reduction: PCA

We are able to recover quite some structure. However, many cell populations remain obscure, and the plot is overcrowded.

set.seed(1234)
sce <- runPCA(sce, ncomponents=30, subset_row=hvg)
plotPCA(sce, colour_by = "cluster")
## Warning: Removed 3475 rows containing missing values (geom_point).

7.2.1 PCA without feature selection

set.seed(1234)
sceNoFS <- runPCA(sce, ncomponents=30, subset_row=1:nrow(sce))
plotPCA(sceNoFS, colour_by = "cluster")
## Warning: Removed 3475 rows containing missing values (geom_point).

rm(sceNoFS)

7.3 A generalization of PCA for exponential family distributions.

library(glmpca)
set.seed(211103)
poipca <- glmpca(assays(sce)$counts[hvg,],
                 L=2, fam="poi",
                 minibatch="stochastic")
reducedDim(sce, "PoiPCA") <- poipca$factors
plotReducedDim(sce, 
               dimred="PoiPCA",
               colour_by = "cluster")
## Warning: Removed 3475 rows containing missing values (geom_point).

7.4 Non-linear dimensionality reduction: UMAP

sce <- runUMAP(sce, dimred = 'PCA', external_neighbors=TRUE)
plotUMAP(sce,
         colour_by = "cluster")
## Warning: Removed 3475 rows containing missing values (geom_point).

8 Clustering

# Build a shared nearest-neighbor graph from PCA space
g <- buildSNNGraph(sce, use.dimred = 'PCA')
# Louvain clustering on the SNN graph, and add to sce
colData(sce)$label <- factor(igraph::cluster_louvain(g)$membership)

# Visualization.
plotUMAP(sce, colour_by="label")

LS0tCnRpdGxlOiAnc2NSTkEtc2VxIHdvcmtmbG93OiBNYWNvc2tvIGV0IGFsLicKYXV0aG9yOiAiS29lbiBWYW4gZGVuIEJlcmdlIgpkYXRlOiAiMTEvMTYvMjAyMCIKb3V0cHV0OiAKICBodG1sX2RvY3VtZW50OgogICAgdG9jOiB0cnVlCiAgICB0b2NfZmxvYXQ6IHRydWUKLS0tCgojIEltcG9ydCBkYXRhCgpUaGUgYHNjUk5Bc2VxYCBwYWNrYWdlIHByb3ZpZGVzIGNvbnZlbmllbnQgYWNjZXNzIHRvIHNldmVyYWwgZGF0YXNldHMuIFNlZSB0aGUgW3BhY2thZ2UgQmlvY29uZHVjdG9yIHBhZ2VdKGh0dHA6Ly9iaW9jb25kdWN0b3Iub3JnL3BhY2thZ2VzL3JlbGVhc2UvZGF0YS9leHBlcmltZW50L2h0bWwvc2NSTkFzZXEuaHRtbCkgZm9yIG1vcmUgaW5mb3JtYXRpb24uCgpgYGB7cn0KIyBpbnN0YWxsIEJpb2NNYW5hZ2VyIHBhY2thZ2UgaWYgbm90IGluc3RhbGxlZCB5ZXQuCiMgQmlvY01hbmFnZXIgaXMgdGhlIHBhY2thZ2UgaW5zdGFsbGVyIGZvciBCaW9jb25kdWN0b3Igc29mdHdhcmUuCmlmICghcmVxdWlyZU5hbWVzcGFjZSgiQmlvY01hbmFnZXIiLCBxdWlldGx5ID0gVFJVRSkpCiAgICBpbnN0YWxsLnBhY2thZ2VzKCJCaW9jTWFuYWdlciIpCgojIGluc3RhbGwgcGFja2FnZXMgaWYgbm90IHlldCBpbnN0YWxsZWQuCnBrZ3MgPC0gYygiU2luZ2xlQ2VsbEV4cGVyaW1lbnQiLCAiRHJvcGxldFV0aWxzIiwgInNjUk5Bc2VxIiwgInNjYXRlciIsICJzY3V0dGxlIiwgInNjcmFuIiwgIkJpb2NTaW5ndWxhciIsICJzY0RibEZpbmRlciIsICJnbG1wY2EiLCAidXdvdCIpCm5vdEluc3RhbGxlZCA8LSBwa2dzWyFwa2dzICVpbiUgaW5zdGFsbGVkLnBhY2thZ2VzKClbLDFdXQppZihsZW5ndGgobm90SW5zdGFsbGVkKSA+IDApewogIEJpb2NNYW5hZ2VyOjppbnN0YWxsKG5vdEluc3RhbGxlZCkKfQoKIyBDb2RlIGJlbG93IG1pZ2h0IGFzayB5b3UgdG8gY3JlYXRlIGFuIEV4cGVyaW1lbnRIdWIgZGlyZWN0b3J5LiAKIyBUeXBlICd5ZXMnIGFuZCBoaXQgRW50ZXIsIHRvIGFsbG93IHRoaXMuCnN1cHByZXNzUGFja2FnZVN0YXJ0dXBNZXNzYWdlcyhsaWJyYXJ5KHNjUk5Bc2VxKSkKc2NlIDwtIE1hY29za29SZXRpbmFEYXRhKCkKYGBgCgojIEEgYFNpbmdsZUNlbGxFeHBlcmltZW50YCBvYmplY3QKCgpgYGB7cn0Kc2NlCmBgYAoKIyMgQWNjZXNzaW5nIGRhdGEgZnJvbSBhIGBTaW5nbGVDZWxsRXhwZXJpbWVudGAgb2JqZWN0CgpQbGVhc2Ugc2VlIFtGaWd1cmUgNC4xIGluIE9TQ0FdKGh0dHA6Ly9iaW9jb25kdWN0b3Iub3JnL2Jvb2tzL3JlbGVhc2UvT1NDQS9kYXRhLWluZnJhc3RydWN0dXJlLmh0bWwpIGZvciBhbiBvdmVydmlldyBvZiBhIGBTaW5nbGVDZWxsRXhwZXJpbWVudGAgb2JqZWN0LgoKYGBge3J9CiMgRGF0YTogYXNzYXlzCmFzc2F5cyhzY2UpCmFzc2F5cyhzY2UpJGNvdW50c1sxOjUsIDE6NV0KCiMgRmVhdHVyZSBtZXRhZGF0YTogcm93RGF0YQpyb3dEYXRhKHNjZSkgIyBlbXB0eSBmb3Igbm93CgojIENlbGwgbWV0YWRhdGE6IGNvbERhdGEKY29sRGF0YShzY2UpCgojIFJlZHVjZWQgZGltZW5zaW9uczogcmVkdWNlZERpbXMKcmVkdWNlZERpbXMoc2NlKSAjIGVtcHR5IGZvciBub3cKYGBgCgojIyBDcmVhdGluZyBhIG5ldyBgU2luZ2xlQ2VsbEV4cGVyaW1lbnRgIG9iamVjdAoKYGBge3J9CnNjZU5ldyA8LSBTaW5nbGVDZWxsRXhwZXJpbWVudChhc3NheXMgPSBsaXN0KGNvdW50cyA9IGFzc2F5cyhzY2UpJGNvdW50cykpCnNjZU5ldwoKcm0oc2NlTmV3KQpgYGAKCiMjIFN0b3JpbmcgKG1ldGEpZGF0YSBpbiBhIGBTaW5nbGVDZWxsRXhwZXJpbWVudGAgb2JqZWN0CgpgYGB7cn0KZmFrZUdlbmVOYW1lcyA8LSBwYXN0ZTAoImdlbmUiLCAxOm5yb3coc2NlKSkKcm93RGF0YShzY2UpJGZha2VOYW1lIDwtIGZha2VHZW5lTmFtZXMKaGVhZChyb3dEYXRhKHNjZSkpCiMgUmVtb3ZlIGFnYWluIGJ5IHNldHRpbmcgdG8gTlVMTApyb3dEYXRhKHNjZSkkZmFrZU5hbWUgPC0gTlVMTAoKYXNzYXlzKHNjZSkkbG9nQ291bnRzIDwtIGxvZzFwKGFzc2F5cyhzY2UpJGNvdW50cykKYXNzYXlzKHNjZSkKYXNzYXlzKHNjZSkkbG9nQ291bnRzWzE6NSwgMTo1XQphc3NheXMoc2NlKSRsb2dDb3VudHMgPC0gTlVMTApgYGAKCiMgRmlsdGVyaW5nIG5vbi1pbmZvcm1hdGl2ZSBnZW5lcwoKYGBge3J9CmtlZXAgPC0gcm93U3Vtcyhhc3NheXMoc2NlKSRjb3VudHMgPiAwKSA+IDEwCnRhYmxlKGtlZXApCgpzY2UgPC0gc2NlW2tlZXAsXQpgYGAKCgojIFF1YWxpdHkgY29udHJvbAoKIyMgQ2FsY3VsYXRlIFFDIHZhcmlhYmxlcwoKYGBge3J9CmxpYnJhcnkoc2NhdGVyKQppcy5taXRvIDwtIGdyZXBsKCJeTVQtIiwgcm93bmFtZXMoc2NlKSkKc3VtKGlzLm1pdG8pICMgMjggbWl0b2Nob25kcmlhbCBnZW5lcwpkZiA8LSBwZXJDZWxsUUNNZXRyaWNzKHNjZSwgc3Vic2V0cz1saXN0KE1pdG89aXMubWl0bykpCiMjIGFkZCB0aGUgUUMgdmFyaWFibGVzIHRvIHNjZSBvYmplY3QKY29sRGF0YShzY2UpIDwtIGNiaW5kKGNvbERhdGEoc2NlKSwgZGYpCiMgdGhlIFFDIHZhcmlhYmxlcyBoYXZlIG5vdyBiZWVuIGFkZGVkIHRvIHRoZSBjb2xEYXRhIG9mIG91ciBTQ0Ugb2JqZWN0Lgpjb2xEYXRhKHNjZSkKYGBgCgojIyBFREEKCkhpZ2gtcXVhbGl0eSBjZWxscyBzaG91bGQgaGF2ZSBtYW55IGZlYXR1cmVzIGV4cHJlc3NlZCwgYW5kIGEgbG93IGNvbnRyaWJ1dGlvbiBvZiBtaXRvY2hvbmRyaWFsIGdlbmVzLiBIZXJlLCB3ZSBzZWUgdGhhdCBzZXZlcmFsIGNlbGxzIGhhdmUgYSB2ZXJ5IGxvdyBudW1iZXIgb2YgZXhwcmVzc2VkIGdlbmVzLCBhbmQgd2hlcmUgbW9zdCBvZiB0aGUgbW9sZWN1bGVzIGFyZSBkZXJpdmVkIGZyb20gbWl0b2Nob25kcmlhbCBnZW5lcy4gVGhpcyBpbmRpY2F0ZXMgbGlrZWx5IGRhbWFnZWQgY2VsbHMsIHByZXN1bWFibHkgYmVjYXVzZSBvZiBsb3NzIG9mIGN5dG9wbGFzbWljIFJOQSBmcm9tIHBlcmZvcmF0ZWQgY2VsbHMsIHNvIHdlJ2Qgd2FudCB0byByZW1vdmUgdGhlc2UgZm9yIHRoZSBkb3duc3RyZWFtIGFuYWx5c2lzLgoKYGBge3J9CiMgTnVtYmVyIG9mIGdlbmVzIHZzIGxpYnJhcnkgc2l6ZQpwbG90Q29sRGF0YShzY2UsIHggPSAic3VtIiwgeT0iZGV0ZWN0ZWQiLCBjb2xvdXJfYnk9ImNsdXN0ZXIiKSAKCiMgTWl0b2Nob25kcmlhbCBnZW5lcwpwbG90Q29sRGF0YShzY2UsIHggPSAiZGV0ZWN0ZWQiLCB5PSJzdWJzZXRzX01pdG9fcGVyY2VudCIpCmBgYAoKIyMgUUMgdXNpbmcgYWRhcHRpdmUgdGhyZXNob2xkcwoKQmVsb3csIHdlIHJlbW92ZSBjZWxscyB0aGF0IGFyZSBvdXRseWluZyB3aXRoIHJlc3BlY3QgdG8KCiAxLiBBIGxvdyBzZXF1ZW5jaW5nIGRlcHRoIChudW1iZXIgb2YgVU1Jcyk7CiAyLiBBIGxvdyBudW1iZXIgb2YgZ2VuZXMgZGV0ZWN0ZWQ7CiAzLiBBIGhpZ2ggcGVyY2VudGFnZSBvZiByZWFkcyBmcm9tIG1pdG9jaG9uZHJpYWwgZ2VuZXMuCiAKV2UgcmVtb3ZlIGEgdG90YWwgb2YgJDM0MjMkIGNlbGxzLCBtb3N0IG9mIHdoaWNoIGJlY2F1c2Ugb2YgYW4gb3V0bHlpbmdseSBoaWdoIHBlcmNlbnRhZ2Ugb2YgcmVhZHMgZnJvbSBtaXRvY2hvbmRyaWFsIGdlbmVzLgoKYGBge3J9Cmxvd0xpYiA8LSBpc091dGxpZXIoZGYkc3VtLCB0eXBlPSJsb3dlciIsIGxvZz1UUlVFKQpsb3dGZWF0dXJlcyA8LSBpc091dGxpZXIoZGYkZGV0ZWN0ZWQsIHR5cGU9Imxvd2VyIiwgbG9nPVRSVUUpCmhpZ2hNaXRvIDwtIGlzT3V0bGllcihkZiRzdWJzZXRzX01pdG9fcGVyY2VudCwgdHlwZT0iaGlnaGVyIikKCnRhYmxlKGxvd0xpYikKdGFibGUobG93RmVhdHVyZXMpCnRhYmxlKGhpZ2hNaXRvKQoKZGlzY2FyZENlbGxzIDwtIChsb3dMaWIgfCBsb3dGZWF0dXJlcyB8IGhpZ2hNaXRvKQp0YWJsZShkaXNjYXJkQ2VsbHMpCmNvbERhdGEoc2NlKSRkaXNjYXJkQ2VsbHMgPC0gZGlzY2FyZENlbGxzCgojIHZpc3VhbGl6ZSBjZWxscyB0byBiZSByZW1vdmVkCnBsb3RDb2xEYXRhKHNjZSwgeCA9ICJkZXRlY3RlZCIsIHk9InN1YnNldHNfTWl0b19wZXJjZW50IiwgY29sb3VyX2J5ID0gImRpc2NhcmRDZWxscyIpCgpgYGAKCgojIyBJZGVudGlmeWluZyBhbmQgcmVtb3ZpbmcgZW1wdHkgZHJvcGxldHMKCk5vdGUgdGhhdCB0aGUgcmVtb3ZhbCBvZiBjZWxscyB3aXRoIGxvdyBzZXF1ZW5jaW5nIGRlcHRoIHVzaW5nIHRoZSBhZGFwdGl2ZSB0aHJlc2hvbGQgcHJvY2VkdXJlIGFib3ZlIGlzIGEgd2F5IG9mIHJlbW92aW5nIGVtcHR5IGRyb3BsZXRzLiAKT3RoZXIgYXBwcm9hY2hlcyBhcmUgcG9zc2libGUsIGUuZy4sIHJlbW92aW5nIGNlbGxzIGJ5IHN0YXRpc3RpY2FsIHRlc3RpbmcgdXNpbmcgYGVtdHB5RHJvcHNgLgpUaGlzIGRvZXMgcmVxdWlyZSB1cyB0byBzcGVjaWZ5IGEgbG93ZXIgYm91bmQgb24gdGhlIHRvdGFsIG51bWJlciBvZiBVTUlzLCBiZWxvdyB3aGljaCBhbGwgY2VsbHMgYXJlIGNvbnNpZGVyZWQgdG8gY29ycmVzcG9uZCB0byBlbXB0eSBkcm9wbGV0cy4KVGhpcyBsb3dlciBib3VuZCBtYXkgbm90IGJlIHRyaXZpYWwgdG8gZGVyaXZlLCBidXQgdGhlIGBiYXJjb2RlUmFua3NgIGZ1bmN0aW9uIGNhbiBiZSB1c2VmdWwgdG8gaWRlbnRpZnkgYW4gZWxib3cva25lZSBwb2ludC4KCmBgYHtyfQpsaWJyYXJ5KERyb3BsZXRVdGlscykKYmNyYW5rIDwtIGJhcmNvZGVSYW5rcyhjb3VudHMoc2NlKSkKCiMgT25seSBzaG93aW5nIHVuaXF1ZSBwb2ludHMgZm9yIHBsb3R0aW5nIHNwZWVkLgp1bmlxIDwtICFkdXBsaWNhdGVkKGJjcmFuayRyYW5rKQpwbG90KGJjcmFuayRyYW5rW3VuaXFdLCBiY3JhbmskdG90YWxbdW5pcV0sIGxvZz0ieHkiLAogICAgeGxhYj0iUmFuayIsIHlsYWI9IlRvdGFsIFVNSSBjb3VudCIsIGNleC5sYWI9MS4yKQoKYWJsaW5lKGg9bWV0YWRhdGEoYmNyYW5rKSRpbmZsZWN0aW9uLCBjb2w9ImRhcmtncmVlbiIsIGx0eT0yKQphYmxpbmUoaD1tZXRhZGF0YShiY3JhbmspJGtuZWUsIGNvbD0iZG9kZ2VyYmx1ZSIsIGx0eT0yKQphYmxpbmUoaD0zNTAsIGNvbD0ib3JhbmdlIiwgbHR5PTIpICMgcGlja2VkIHZpc3VhbGx5IG15c2VsZgoKbGVnZW5kKCJ0b3ByaWdodCIsIGxlZ2VuZD1jKCJJbmZsZWN0aW9uIiwgIktuZWUiLCAiRW1waXJpY2FsIGtuZWUgcG9pbnQiKSwgCiAgICAgICAgY29sPWMoImRhcmtncmVlbiIsICJkb2RnZXJibHVlIiwgIm9yYW5nZSIpLCBsdHk9MiwgY2V4PTEuMikKCnNldC5zZWVkKDEwMCkKbGltaXQgPC0gMzUwICAgCmFsbC5vdXQgPC0gZW1wdHlEcm9wcyhjb3VudHMoc2NlKSwgbG93ZXI9bGltaXQsIHRlc3QuYW1iaWVudD1UUlVFKQojIHAtdmFsdWVzIGZvciBjZWxscyB3aXRoIHRvdGFsIFVNSSBjb3VudCB1bmRlciB0aGUgbG93ZXIgYm91bmQuCmhpc3QoYWxsLm91dCRQVmFsdWVbYWxsLm91dCRUb3RhbCA8PSBsaW1pdCAmIGFsbC5vdXQkVG90YWwgPiAwXSwKICAgIHhsYWI9IlAtdmFsdWUiLCBtYWluPSIiLCBjb2w9ImdyZXk4MCIpCgojIGJ1dCBub3RlIHRoYXQgaXQgd291bGQgcmVtb3ZlIGEgdmVyeSBoaWdoIG51bWJlciBvZiBjZWxscwpsZW5ndGgod2hpY2goYWxsLm91dCRGRFIgPD0gMC4wMDEpKQoKIyBzbyB3ZSBzdGljayB0byB0aGUgbW9yZSBsZW5pZW50IGFkYXB0aXZlIGZpbHRlcmluZyBzdHJhdGVneQojIHJlbW92ZSBjZWxscyBpZGVudGlmaWVkIHVzaW5nIGFkYXB0aXZlIHRocmVzaG9sZHMKc2NlIDwtIHNjZVssICFjb2xEYXRhKHNjZSkkZGlzY2FyZENlbGxzXQpgYGAKCiMjIElkZW50aWZ5aW5nIGFuZCByZW1vdmluZyBkb3VibGV0cwoKV2Ugd2lsbCB1c2UgW3NjRGJsRmluZGVyXShodHRwczovL2Jpb2NvbmR1Y3Rvci5vcmcvcGFja2FnZXMvMy4xNC9iaW9jL2h0bWwvc2NEYmxGaW5kZXIuaHRtbCkgdG8gZGV0ZWN0IGRvdWJsZXQgY2VsbHMuCgoKCmBgYHtyfQojIyBwZXJmb3JtIGRvdWJsZXQgZGV0ZWN0aW9uCmxpYnJhcnkoc2NEYmxGaW5kZXIpCnNldC5zZWVkKDIxMTEwMykKc2FtcGxlSUQgPC0gdW5saXN0KGxhcHBseShzdHJzcGxpdChjb2xEYXRhKHNjZSkkY2VsbC5pZCwgc3BsaXQ9Il8iKSwgIltbIiwgMSkpCnRhYmxlKHNhbXBsZUlEKQpzY2UgPC0gc2NEYmxGaW5kZXIoc2NlLCByZXR1cm5UeXBlPSJ0YWJsZSIsCiAgICAgICAgICAgICAgICAgc2FtcGxlcyA9IGZhY3RvcihzYW1wbGVJRCkpCnRhYmxlKHNjZSRzY0RibEZpbmRlci5jbGFzcykKCgojIyB2aXN1YWxpemUgdGhlc2Ugc2NvcmVzCiMjIGV4cGxvcmUgZG91YmxldCBzY29yZSB3cnQgb3JpZ2luYWwgY2x1c3RlciBsYWJlbHMKYm94cGxvdChsb2cxcChzY2Ukc2NEYmxGaW5kZXIuc2NvcmUpIH4gZmFjdG9yKGNvbERhdGEoc2NlKSRjbHVzdGVyLCBleGNsdWRlPU5VTEwpKQoKdGFiIDwtIHRhYmxlKHNjZSRzY0RibEZpbmRlci5jbGFzcywgc2NlJGNsdXN0ZXIsIAogICAgICBleGNsdWRlPU5VTEwpCnRhYgp0KHQodGFiKSAvIGNvbFN1bXModGFiKSkKCmJhcnBsb3QodCh0KHRhYikgLyBjb2xTdW1zKHRhYikpWzIsXSwKICAgICAgICB4bGFiID0gIkNsdXN0ZXIiLCB5bGFiID0gIkZyYWN0aW9uIG9mIGRvdWJsZXRzIikKCiMgcmVtb3ZlIGRvdWJsZXRzCnNjZSA8LSBzY2VbLCFzY2Ukc2NEYmxGaW5kZXIuY2xhc3MgPT0gImRvdWJsZXQiXQpgYGAKCgojIE5vcm1hbGl6YXRpb24KCkZvciBub3JtYWxpemF0aW9uLCB0aGUgc2l6ZSBmYWN0b3JzICRzX2kkIGNvbXB1dGVkIGhlcmUgYXJlIHNpbXBseSBzY2FsZWQgbGlicmFyeSBzaXplczoKXFsgTl9pID0gXHN1bV9nIFlfe2dpfSBcXQpcWyBzX2kgPSBOX2kgLyBcYmFye059X2kgXF0KCmBgYHtyfQpzY2UgPC0gbG9nTm9ybUNvdW50cyhzY2UpCgojIG5vdGUgd2UgYWxzbyByZXR1cm5lZCBsb2cgY291bnRzOiBzZWUgdGhlIGFkZGl0aW9uYWwgbG9nY291bnRzIGFzc2F5LgpzY2UKCiMgeW91IGNhbiBleHRyYWN0IHNpemUgZmFjdG9ycyB1c2luZwpzZiA8LSBsaWJyYXJ5U2l6ZUZhY3RvcnMoc2NlKQptZWFuKHNmKSAjIGVxdWFsIHRvIDEgZHVlIHRvIHNjYWxpbmcuCnBsb3QoeD0gbG9nKGNvbFN1bXMoYXNzYXlzKHNjZSkkY291bnRzKSksIAogICAgIHk9c2YpCmBgYAoKIyBGZWF0dXJlIHNlbGVjdGlvbgoKYGBge3J9CmxpYnJhcnkoc2NyYW4pCmRlYyA8LSBtb2RlbEdlbmVWYXIoc2NlKQpmaXRSZXRpbmEgPC0gbWV0YWRhdGEoZGVjKQpwbG90KGZpdFJldGluYSRtZWFuLCBmaXRSZXRpbmEkdmFyLCAKICAgICB4bGFiPSJNZWFuIG9mIGxvZy1leHByZXNzaW9uIiwKICAgIHlsYWI9IlZhcmlhbmNlIG9mIGxvZy1leHByZXNzaW9uIikKY3VydmUoZml0UmV0aW5hJHRyZW5kKHgpLCBjb2w9ImRvZGdlcmJsdWUiLCBhZGQ9VFJVRSwgbHdkPTIpCgojIGdldCAxMCUgaGlnaGx5IHZhcmlhYmxlIGdlbmVzCmh2ZyA8LSBnZXRUb3BIVkdzKGRlYywgcHJvcD0wLjEpCmhlYWQoaHZnKQoKIyBwbG90IHRoZXNlIApwbG90KGZpdFJldGluYSRtZWFuLCBmaXRSZXRpbmEkdmFyLCAKICAgICBjb2wgPSBjKCJvcmFuZ2UiLCAiZGFya3NlYWdyZWVuMyIpWyhuYW1lcyhmaXRSZXRpbmEkbWVhbikgJWluJSBodmcpKzFdLAogICAgIHhsYWI9Ik1lYW4gb2YgbG9nLWV4cHJlc3Npb24iLAogICAgeWxhYj0iVmFyaWFuY2Ugb2YgbG9nLWV4cHJlc3Npb24iKQpjdXJ2ZShmaXRSZXRpbmEkdHJlbmQoeCksIGNvbD0iZG9kZ2VyYmx1ZSIsIGFkZD1UUlVFLCBsd2Q9MikKbGVnZW5kKCJ0b3BsZWZ0IiwgCiAgICAgICBsZWdlbmQgPSBjKCJTZWxlY3RlZCIsICJOb3Qgc2VsZWN0ZWQiKSwgCiAgICAgICBjb2wgPSBjKCJkYXJrc2VhZ3JlZW4zIiwgIm9yYW5nZSIpLAogICAgICAgcGNoID0gMTYsCiAgICAgICBidHk9J24nKQpgYGAKCiMgRGltZW5zaW9uYWxpdHkgcmVkdWN0aW9uCgpOb3RlIHRoYXQsIGJlbG93LCB3ZSBjb2xvciB0aGUgY2VsbHMgdXNpbmcgdGhlIGtub3duLCB0cnVlIGNlbGwgdHlwZSBsYWJlbCBhcyBkZWZpbmVkIGluIHRoZSBtZXRhZGF0YSwgdG8gZW1waXJpY2FsbHkgZXZhbHVhdGUgdGhlIGRpbWVuc2lvbmFsaXR5IHJlZHVjdGlvbi4gSW4gcmVhbGl0eSwgd2UgZG9uJ3Qga25vdyB0aGlzIHlldCBhdCB0aGlzIHN0YWdlLgoKIyMgVGhlIG1vc3QgYmFzaWMgRFIKCkp1c3QgYnkgbG9va2luZyBhdCB0aGUgdG9wIHR3byBnZW5lcyBiYXNlZCBvbiBvdXIgZmVhdHVyZSBzZWxlY3Rpb24gY3JpdGVyaW9uLCB3ZSBjYW4gYWxyZWFkeSBzZWUgc29tZSBzZXBhcmF0aW9uIGFjY29yZGluZyB0byB0aGUgY2VsbCB0eXBlIQoKYGBge3J9CmNvbERhdGEoc2NlKSRjbHVzdGVyIDwtIGFzLmZhY3Rvcihjb2xEYXRhKHNjZSkkY2x1c3RlcikKY2wgPC0gY29sRGF0YShzY2UpJGNsdXN0ZXIKCnBhcihidHk9J2wnKQpwbG90KHggPSBhc3NheXMoc2NlKSRjb3VudHNbaHZnWzFdLF0sCiAgICAgeSA9IGFzc2F5cyhzY2UpJGNvdW50c1todmdbMl0sXSwKICAgICBjb2wgPSBhcy5udW1lcmljKGNsKSwKICAgICBwY2ggPSAxNiwgY2V4ID0gMS8zLAogICAgIHhsYWIgPSAiTW9zdCBpbmZvcm1hdGl2ZSBnZW5lIiwKICAgICB5bGFiID0gIlNlY29uZCBtb3N0IGluZm9ybWF0aXZlIGdlbmUiLAogICAgIG1haW4gPSAiQ2VsbHMgY29sb3JlZCBhY2MgdG8gY2VsbCB0eXBlIikKYGBgCgojIyBMaW5lYXIgZGltZW5zaW9uYWxpdHkgcmVkdWN0aW9uOiBQQ0EKCldlIGFyZSBhYmxlIHRvIHJlY292ZXIgcXVpdGUgc29tZSBzdHJ1Y3R1cmUuIApIb3dldmVyLCBtYW55IGNlbGwgcG9wdWxhdGlvbnMgcmVtYWluIG9ic2N1cmUsIGFuZCB0aGUgcGxvdCBpcyBvdmVyY3Jvd2RlZC4KCmBgYHtyfQpzZXQuc2VlZCgxMjM0KQpzY2UgPC0gcnVuUENBKHNjZSwgbmNvbXBvbmVudHM9MzAsIHN1YnNldF9yb3c9aHZnKQpwbG90UENBKHNjZSwgY29sb3VyX2J5ID0gImNsdXN0ZXIiKQpgYGAKCiMjIyBQQ0Egd2l0aG91dCBmZWF0dXJlIHNlbGVjdGlvbgoKYGBge3J9CnNldC5zZWVkKDEyMzQpCnNjZU5vRlMgPC0gcnVuUENBKHNjZSwgbmNvbXBvbmVudHM9MzAsIHN1YnNldF9yb3c9MTpucm93KHNjZSkpCnBsb3RQQ0Eoc2NlTm9GUywgY29sb3VyX2J5ID0gImNsdXN0ZXIiKQpybShzY2VOb0ZTKQpgYGAKCgojIyBBIGdlbmVyYWxpemF0aW9uIG9mIFBDQSBmb3IgZXhwb25lbnRpYWwgZmFtaWx5IGRpc3RyaWJ1dGlvbnMuCgpgYGB7ciwgZXZhbD1UUlVFfQpsaWJyYXJ5KGdsbXBjYSkKc2V0LnNlZWQoMjExMTAzKQpwb2lwY2EgPC0gZ2xtcGNhKGFzc2F5cyhzY2UpJGNvdW50c1todmcsXSwKICAgICAgICAgICAgICAgICBMPTIsIGZhbT0icG9pIiwKICAgICAgICAgICAgICAgICBtaW5pYmF0Y2g9InN0b2NoYXN0aWMiKQpyZWR1Y2VkRGltKHNjZSwgIlBvaVBDQSIpIDwtIHBvaXBjYSRmYWN0b3JzCnBsb3RSZWR1Y2VkRGltKHNjZSwgCiAgICAgICAgICAgICAgIGRpbXJlZD0iUG9pUENBIiwKICAgICAgICAgICAgICAgY29sb3VyX2J5ID0gImNsdXN0ZXIiKQpgYGAKCgoKIyMgTm9uLWxpbmVhciBkaW1lbnNpb25hbGl0eSByZWR1Y3Rpb246IFVNQVAKCmBgYHtyfQpzY2UgPC0gcnVuVU1BUChzY2UsIGRpbXJlZCA9ICdQQ0EnLCBleHRlcm5hbF9uZWlnaGJvcnM9VFJVRSkKcGxvdFVNQVAoc2NlLAogICAgICAgICBjb2xvdXJfYnkgPSAiY2x1c3RlciIpCmBgYAoKIyBDbHVzdGVyaW5nCgpgYGB7cn0KIyBCdWlsZCBhIHNoYXJlZCBuZWFyZXN0LW5laWdoYm9yIGdyYXBoIGZyb20gUENBIHNwYWNlCmcgPC0gYnVpbGRTTk5HcmFwaChzY2UsIHVzZS5kaW1yZWQgPSAnUENBJykKIyBMb3V2YWluIGNsdXN0ZXJpbmcgb24gdGhlIFNOTiBncmFwaCwgYW5kIGFkZCB0byBzY2UKY29sRGF0YShzY2UpJGxhYmVsIDwtIGZhY3RvcihpZ3JhcGg6OmNsdXN0ZXJfbG91dmFpbihnKSRtZW1iZXJzaGlwKQoKIyBWaXN1YWxpemF0aW9uLgpwbG90VU1BUChzY2UsIGNvbG91cl9ieT0ibGFiZWwiKQpgYGAK