projDir <- "/mnt/scratcha/bioinformatics/baller01/20200511_FernandesM_ME_crukBiSs2020"
outDirBit <- "AnaWiSce/Attempt1"
nbPcToComp <- 50

1 Differential expression and abundance between conditions

Source: Multi-sample comparisons of the OSCA book.

1.1 Motivation

A powerful use of scRNA-seq technology lies in the design of replicated multi-condition experiments to detect changes in composition or expression between conditions. For example, a researcher could use this strategy to detect changes in cell type abundance after drug treatment (Richard et al. 2018) or genetic modifications (Scialdone et al. 2016). This provides more biological insight than conventional scRNA-seq experiments involving only one biological condition, especially if we can relate population changes to specific experimental perturbations.

Differential analyses of multi-condition scRNA-seq experiments can be broadly split into two categories - differential expression (DE) and differential abundance (DA) analyses. The former tests for changes in expression between conditions for cells of the same type that are present in both conditions, while the latter tests for changes in the composition of cell types (or states, etc.) between conditions.

1.2 Setting up the data

We will use the data set comprising the 11 samples (1000 cells per sample) analysed with fastMNN and the nested list of samples.

The differential analyses in this chapter will be predicated on many of the pre-processing steps covered previously. For brevity, we will not explicitly repeat them here, only noting that we have already merged cells from all samples into the same coordinate system and clustered the merged dataset to obtain a common partitioning across all samples.

Load the SCE object:

setName <- "caron"
# Read object in:
##setSuf <- "_1kCellPerSpl"
##tmpFn <- sprintf("%s/%s/Robjects/%s_sce_nz_postDeconv%s_clustered.Rds", projDir, outDirBit, setName, setSuf)

setSuf <- "_1kCps"
tmpFn <- sprintf("%s/%s/Robjects/%s_sce_nz_postDeconv%s_Fmwbl.Rds", projDir, outDirBit, setName, setSuf)

print(tmpFn)
[1] "/mnt/scratcha/bioinformatics/baller01/20200511_FernandesM_ME_crukBiSs2020/AnaWiSce/Attempt1/Robjects/caron_sce_nz_postDeconv_1kCps_Fmwbl.Rds"
if(!file.exists(tmpFn))
{
    knitr::knit_exit()
}
sce <- readRDS(tmpFn)
sce
class: SingleCellExperiment 
dim: 12317 11000 
metadata(2): merge.info pca.info
assays(1): reconstructed
rownames(12317): ENSG00000000003 ENSG00000000457 ... ENSG00000285458
  ENSG00000285476
rowData names(1): rotation
colnames: NULL
colData names(25): Sample Barcode ... type clusters.mnn
reducedDimNames(2): corrected TSNE
altExpNames(0):

A brief inspection of the results shows clusters contain varying contributions from batches:

library(scater)
colLabels(sce) <- sce$clusters.mnn
table(colLabels(sce), sce$type)
     
      ETV6-RUNX1  HHD PBMMC PRE-T
  c1          27    1    64     0
  c10        103   48   225    80
  c11         72    1    64    14
  c12         24    0     3     8
  c13         57    1    61     0
  c14          1    1    43     3
  c15         19   54    39   207
  c16        272  278    71    16
  c17          8    6    74    49
  c18         80    2   109     8
  c19          1    0     1   319
  c2           8    5    19     8
  c20          4    0    50     7
  c21        389  141    22     0
  c22       1154  362    35     1
  c23         42   11    10   103
  c24        144   61   238    45
  c25          2    0   243     3
  c26        345   75   545    97
  c27          0    0    27     0
  c28         10   12    28    11
  c3         867  752   398   106
  c4          23    1    57     0
  c5         167  115   156   151
  c6          12   26    86   689
  c7         165   44    41    17
  c8           2    3   212    50
  c9           2    0    79     8
table(colLabels(sce), sce$Sample.Name2)
     
      ETV6-RUNX1_1 ETV6-RUNX1_2 ETV6-RUNX1_3 ETV6-RUNX1_4 HHD_1 HHD_2 PBMMC_1
  c1             1            0           10           16     0     1      15
  c10            4            6           76           17    39     9      42
  c11            1            3           14           54     0     1       1
  c12            0            0            5           19     0     0       0
  c13            1            2           12           42     1     0       2
  c14            0            0            0            1     1     0      15
  c15           15            0            1            3    34    20      21
  c16          227           20            9           16   160   118      27
  c17            4            1            2            1     6     0      59
  c18            3            0           15           62     0     2       0
  c19            0            0            1            0     0     0       1
  c2             0            3            5            0     2     3       6
  c20            0            0            3            1     0     0      11
  c21          106           82           34          167    59    82       6
  c22          426          309           96          323   133   229       8
  c23            6           21           10            5     8     3       7
  c24            1           19          104           20    54     7      40
  c25            0            0            1            1     0     0     204
  c26            3           16          277           49    57    18     109
  c27            0            0            0            0     0     0      11
  c28            0            0            1            9    11     1       3
  c3            65          420          290           92   349   403     175
  c4             0            0            9           14     0     1       1
  c5            65           54           13           35    43    72      81
  c6             8            1            2            1    18     8      60
  c7            64           43            8           50    22    22      15
  c8             0            0            1            1     3     0      45
  c9             0            0            1            1     0     0      35
     
      PBMMC_2 PBMMC_3 PRE-T_1 PRE-T_2
  c1       43       6       0       0
  c10      94      89       7      73
  c11      60       3       1      13
  c12       2       1       0       8
  c13      56       3       0       0
  c14      14      14       0       3
  c15       7      11     162      45
  c16      11      33       2      14
  c17       1      14      40       9
  c18     105       4       1       7
  c19       0       0      18     301
  c2        7       6       2       6
  c20      30       9       0       7
  c21       4      12       0       0
  c22       9      18       0       1
  c23       3       0      73      30
  c24      86     112       8      37
  c25      28      11       1       2
  c26     217     219       7      90
  c27      10       6       0       0
  c28       6      19       1      10
  c3       44     179      63      43
  c4       47       9       0       0
  c5       21      54     134      17
  c6       13      13     465     224
  c7        8      18      14       3
  c8       54     113       1      49
  c9       20      24       0       8

On the t-SNE plots below, cells colored by type or sample (‘batch of origin’). Cluster numbers are superimposed based on the median coordinate of cells assigned to that cluster.

plotTSNE(sce, colour_by="type", text_by="label")

plotTSNE(sce, colour_by="Sample.Name2")

tmpFn <- sprintf("%s/%s/Robjects/%s_sce_nz_postDeconv%s_Fmwbl2.Rds", projDir, outDirBit, setName, setSuf)
tmpList <- readRDS(tmpFn)


chosen.hvgs <- tmpList$chosen.hvgs
rescaled.mbn <- tmpList$rescaled.mbn
uncorrected <- tmpList$uncorrected
colToKeep <- c("Run", "Sample.Name", "source_name", "block", "setName", "Sample.Name2") 
colData(uncorrected) <- colData(uncorrected)[,colToKeep]
colData(uncorrected)[1:3,]
DataFrame with 3 rows and 6 columns
          Run Sample.Name source_name      block     setName Sample.Name2
  <character> <character>    <factor>   <factor> <character>  <character>
1  SRR9264343  GSM3872434  ETV6-RUNX1 ETV6-RUNX1       Caron ETV6-RUNX1_1
2  SRR9264343  GSM3872434  ETV6-RUNX1 ETV6-RUNX1       Caron ETV6-RUNX1_1
3  SRR9264343  GSM3872434  ETV6-RUNX1 ETV6-RUNX1       Caron ETV6-RUNX1_1
#--- merging ---#
library(batchelor)
set.seed(01001001)
merged <- correctExperiments(uncorrected, 
    batch=uncorrected$Sample.Name2, 
    subset.row=chosen.hvgs,
    PARAM=FastMnnParam(
        merge.order=list( list(1,2,3,4), list(9,10,11), list(5,6), list(7,8) )
    )
)

merged
class: SingleCellExperiment 
dim: 12317 11000 
metadata(2): merge.info pca.info
assays(3): reconstructed counts logcounts
rownames(12317): ENSG00000000003 ENSG00000000457 ... ENSG00000285458
  ENSG00000285476
rowData names(12): rotation ensembl_gene_id ... detected gene_sparsity
colnames: NULL
colData names(7): batch Run ... setName Sample.Name2
reducedDimNames(1): corrected
altExpNames(0):
#--- clustering ---#
g <- buildSNNGraph(merged, use.dimred="corrected")
clusters <- igraph::cluster_louvain(g)
merged$clusters.mnn <- factor(paste0("c", clusters$membership))
#colLabels(merged) <- merged$clusters.mnn

#--- dimensionality-reduction ---#
merged <- runTSNE(merged, dimred="corrected", external_neighbors=TRUE)
merged <- runUMAP(merged, dimred="corrected", external_neighbors=TRUE)

library(scater)
table(merged$clusters.mnn, merged$block)
     
      ABMMC ETV6-RUNX1  HHD PBMMC PRE-T
  c1      0       1185  283    31     1
  c10     0        208    4   232    11
  c11     0         88    7   200    34
  c12     0        890  737   420   124
  c13     0         85   42   214    66
  c2      0        142   61   230    46
  c3      0         16   52     0     0
  c4      0        257  352   157   565
  c5      0        371   83   587   713
  c6      0        339  152   239   210
  c7      0         15   16   394    74
  c8      0        403  211    77   155
  c9      0          1    0   219     1
table(merged$clusters.mnn, merged$Sample.Name2)
     
      ETV6-RUNX1_1 ETV6-RUNX1_2 ETV6-RUNX1_3 ETV6-RUNX1_4 HHD_1 HHD_2 PBMMC_1
  c1           443          312           93          337   114   169      10
  c10            5            5           41          157     1     3       3
  c11            1            3           32           52     2     5      35
  c12           55          447          294           94   292   445     186
  c13            5            4           63           13    33     9      42
  c2             1           19          102           20    54     7      35
  c3             9            4            3            0    51     1       0
  c4           221           14           13            9   203   149      91
  c5             3           19          296           53    65    18     124
  c6           127           93           25           94    59    93     133
  c7             0            0            3           12    15     1     111
  c8           130           80           35          158   111   100      38
  c9             0            0            0            1     0     0     192
     
      PBMMC_2 PBMMC_3 PRE-T_1 PRE-T_2
  c1        9      12       1       0
  c10     219      10       2       9
  c11     133      32       1      33
  c12      49     185      89      35
  c13      87      85       7      59
  c2       81     114       9      37
  c3        0       0       0       0
  c4       25      41     379     186
  c5      231     232     203     510
  c6       30      76     183      27
  c7      105     178       2      72
  c8        8      31     124      31
  c9       23       4       0       1
plotTSNE(merged, colour_by="block", text_by="clusters.mnn")

plotTSNE(merged, colour_by="Sample.Name2")

1.3 Differential expression between conditions

1.3.1  Creating pseudo-bulk samples

The most obvious differential analysis is to look for changes in expression between conditions. We perform the DE analysis separately for each label. The actual DE testing is performed on “pseudo-bulk” expression profiles (Tung et al. 2017), generated by summing counts together for all cells with the same combination of label and sample. This leverages the resolution offered by single-cell technologies to define the labels, and combines it with the statistical rigor of existing methods for DE analyses involving a small number of samples.

# Using 'label' and 'sample' as our two factors; each column of the output
# corresponds to one unique combination of these two factors.
summed <- aggregateAcrossCells(merged, 
                    id = DataFrame(
                        label=merged$clusters.mnn,
                        sample=merged$Sample.Name2
                    )
)
summed
class: SingleCellExperiment 
dim: 12317 128 
metadata(2): merge.info pca.info
assays(1): counts
rownames(12317): ENSG00000000003 ENSG00000000457 ... ENSG00000285458
  ENSG00000285476
rowData names(12): rotation ensembl_gene_id ... detected gene_sparsity
colnames: NULL
colData names(11): batch Run ... sample ncells
reducedDimNames(3): corrected TSNE UMAP
altExpNames(0):
colData(summed) %>% head(3)
DataFrame with 3 rows and 11 columns
         batch         Run Sample.Name source_name     block     setName
   <character> <character> <character>   <integer> <integer> <character>
1 ETV6-RUNX1_1  SRR9264343  GSM3872434           2         2       Caron
2 ETV6-RUNX1_2  SRR9264344  GSM3872435           2         2       Caron
3 ETV6-RUNX1_3  SRR9264345  GSM3872436           2         2       Caron
  Sample.Name2 clusters.mnn    label       sample    ncells
   <character>    <integer> <factor>  <character> <integer>
1 ETV6-RUNX1_1            1       c1 ETV6-RUNX1_1       443
2 ETV6-RUNX1_2            1       c1 ETV6-RUNX1_2       312
3 ETV6-RUNX1_3            1       c1 ETV6-RUNX1_3        93

At this point, it is worth reflecting on the motivations behind the use of pseudo-bulking:

Larger counts are more amenable to standard DE analysis pipelines designed for bulk RNA-seq data. Normalization is more straightforward and certain statistical approximations are more accurate e.g., the saddlepoint approximation for quasi-likelihood methods or normality for linear models. Collapsing cells into samples reflects the fact that our biological replication occurs at the sample level (Lun and Marioni 2017). Each sample is represented no more than once for each condition, avoiding problems from unmodelled correlations between samples. Supplying the per-cell counts directly to a DE analysis pipeline would imply that each cell is an independent biological replicate, which is not true from an experimental perspective. (A mixed effects model can handle this variance structure but involves extra statistical and computational complexity for little benefit, see Crowell et al. (2019).) Variance between cells within each sample is masked, provided it does not affect variance across (replicate) samples. This avoids penalizing DEGs that are not uniformly up- or down-regulated for all cells in all samples of one condition. Masking is generally desirable as DEGs - unlike marker genes - do not need to have low within-sample variance to be interesting, e.g., if the treatment effect is consistent across replicate populations but heterogeneous on a per-cell basis. (Of course, high per-cell variability will still result in weaker DE if it affects the variability across populations, while homogeneous per-cell responses will result in stronger DE due to a larger population-level log-fold change. These effects are also largely desirable.)

1.3.2 Performing the DE analysis

1.3.2.1  Introduction

The DE analysis will be performed using quasi-likelihood (QL) methods from the edgeR package (Robinson, McCarthy, and Smyth 2010; Chen, Lun, and Smyth 2016). This uses a negative binomial generalized linear model (NB GLM) to handle overdispersed count data in experiments with limited replication. In our case, we have biological variation with three paired replicates per condition, so edgeR (or its contemporaries) is a natural choice for the analysis.

We do not use all labels for GLM fitting as the strong DE between labels makes it difficult to compute a sensible average abundance to model the mean-dispersion trend. Moreover, label-specific batch effects would not be easily handled with a single additive term in the design matrix for the batch. Instead, we arbitrarily pick one of the labels to use for this demonstration.

labelToGet <- "c1"
current <- summed[,summed$label==labelToGet]

# Creating up a DGEList object for use in edgeR:
suppressMessages(library(edgeR))
y <- DGEList(counts(current), samples=colData(current))
y
An object of class "DGEList"
$counts
                Sample1 Sample2 Sample3 Sample4 Sample5 Sample6 Sample7 Sample8
ENSG00000000003       0       1       0       0       0       2       0       0
ENSG00000000457      17      13       4      11       3       5       0       0
ENSG00000000938       0       2       0       0       2       1       0       0
ENSG00000000971       0       0       0       0       0       0       0       0
ENSG00000001167      15      12      11      25       2      15       2       1
                Sample9 Sample10
ENSG00000000003       0        0
ENSG00000000457       0        0
ENSG00000000938       0        0
ENSG00000000971       0        0
ENSG00000001167       0        0
12312 more rows ...

$samples
         group lib.size norm.factors        batch        Run Sample.Name
Sample1      1  1062654            1 ETV6-RUNX1_1 SRR9264343  GSM3872434
Sample2      1   501796            1 ETV6-RUNX1_2 SRR9264344  GSM3872435
Sample3      1   153900            1 ETV6-RUNX1_3 SRR9264345  GSM3872436
Sample4      1   510069            1 ETV6-RUNX1_4 SRR9264346  GSM3872437
Sample5      1   339208            1        HHD_1 SRR9264347  GSM3872438
Sample6      1   590414            1        HHD_2 SRR9264348  GSM3872439
Sample7      1    15288            1      PBMMC_1 SRR9264351  GSM3872442
Sample8      1    11108            1      PBMMC_2 SRR9264353  GSM3872443
Sample9      1     9147            1      PBMMC_3 SRR9264354  GSM3872444
Sample10     1     1650            1      PRE-T_1 SRR9264349  GSM3872440
         source_name block setName Sample.Name2 clusters.mnn label       sample
Sample1            2     2   Caron ETV6-RUNX1_1            1    c1 ETV6-RUNX1_1
Sample2            2     2   Caron ETV6-RUNX1_2            1    c1 ETV6-RUNX1_2
Sample3            2     2   Caron ETV6-RUNX1_3            1    c1 ETV6-RUNX1_3
Sample4            2     2   Caron ETV6-RUNX1_4            1    c1 ETV6-RUNX1_4
Sample5            3     3   Caron        HHD_1            1    c1        HHD_1
Sample6            3     3   Caron        HHD_2            1    c1        HHD_2
Sample7            4     4   Caron      PBMMC_1            1    c1      PBMMC_1
Sample8            4     4   Caron      PBMMC_2            1    c1      PBMMC_2
Sample9            4     4   Caron      PBMMC_3            1    c1      PBMMC_3
Sample10           5     5   Caron      PRE-T_1            1    c1      PRE-T_1
         ncells
Sample1     443
Sample2     312
Sample3      93
Sample4     337
Sample5     114
Sample6     169
Sample7      10
Sample8       9
Sample9      12
Sample10      1

1.3.2.2 Pre-processing

A typical step in bulk RNA-seq data analyses is to remove samples with very low library sizes due to failed library preparation or sequencing. The very low counts in these samples can be troublesome in downstream steps such as normalization (Chapter 7) or for some statistical approximations used in the DE analysis. In our situation, this is equivalent to removing label-sample combinations that have very few or lowly-sequenced cells. The exact definition of “very low” will vary, but in this case, we remove combinations containing fewer than 20 cells (Crowell et al. 2019). Alternatively, we could apply the outlier-based strategy described in Chapter 6, but this makes the strong assumption that all label-sample combinations have similar numbers of cells that are sequenced to similar depth.

discarded <- current$ncells < 20
y <- y[,!discarded]
summary(discarded)
   Mode   FALSE    TRUE 
logical       6       4 

Another typical step in bulk RNA-seq analyses is to remove genes that are lowly expressed. This reduces computational work, improves the accuracy of mean-variance trend modelling and decreases the severity of the multiple testing correction. Genes are discarded if they are not expressed above a log-CPM threshold in a minimum number of samples (determined from the size of the smallest treatment group in the experimental design).

keep <- filterByExpr(y, group=current$source_name)
y <- y[keep,]
summary(keep)
   Mode   FALSE    TRUE 
logical    6230    6087 

Finally, we correct for composition biases by computing normalization factors with the trimmed mean of M-values method (Robinson and Oshlack 2010). We do not need the bespoke single-cell methods described in Chapter 7, as the counts for our pseudo-bulk samples are large enough to apply bulk normalization methods. (Readers should be aware that edgeR normalization factors are closely related but not the same as the size factors described elsewhere in this book.)

y <- calcNormFactors(y)
y$samples

1.3.2.3 Statistical modelling

Our aim is to test whether the log-fold change between sample groups is significantly different from zero.

design <- model.matrix(~factor(source_name), y$samples)
design
        (Intercept) factor(source_name)3
Sample1           1                    0
Sample2           1                    0
Sample3           1                    0
Sample4           1                    0
Sample5           1                    1
Sample6           1                    1
attr(,"assign")
[1] 0 1
attr(,"contrasts")
attr(,"contrasts")$`factor(source_name)`
[1] "contr.treatment"

We estimate the negative binomial (NB) dispersions with estimateDisp(). The role of the NB dispersion is to model the mean-variance trend, which is not easily accommodated by QL dispersions alone due to the quadratic nature of the NB mean-variance trend.

y <- estimateDisp(y, design)
summary(y$trended.dispersion)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
 0.1404  0.1495  0.1599  0.1908  0.1919  0.5137 

Biological coefficient of variation (BCV) for each gene as a function of the average abundance. The BCV is computed as the square root of the NB dispersion after empirical Bayes shrinkage towards the trend. Trended and common BCV estimates are shown in blue and red, respectively.

plotBCV(y)

We also estimate the quasi-likelihood dispersions with glmQLFit() (Chen, Lun, and Smyth 2016). This fits a GLM to the counts for each gene and estimates the QL dispersion from the GLM deviance. We set robust=TRUE to avoid distortions from highly variable clusters (Phipson et al. 2016). The QL dispersion models the uncertainty and variability of the per-gene variance - which is not well handled by the NB dispersions, so the two dispersion types complement each other in the final analysis.

fit <- glmQLFit(y, design, robust=TRUE)
summary(fit$var.prior)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
 0.5445  0.5837  0.6524  0.7272  0.7564  2.2279 
summary(fit$df.prior)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  1.803   6.323   6.323   6.071   6.323   6.323 

QL dispersion estimates for each gene as a function of abundance. Raw estimates (black) are shrunk towards the trend (blue) to yield squeezed estimates (red).

plotQLDisp(fit)

We test for differences in expression due to sample group using glmQLFTest(). DEGs are defined as those with non-zero log-fold changes at a false discovery rate of 5%. If very few genes are significantly DE that sample group has little effect on the transcriptome.

res <- glmQLFTest(fit, coef=ncol(design))
summary(decideTests(res))
       factor(source_name)3
Down                    132
NotSig                 5784
Up                      171
topTab <- topTags(res)$table
tmpAnnot <- rowData(current)[,c("ensembl_gene_id","Symbol")] %>% data.frame
topTab %>% tibble::rownames_to_column("ensembl_gene_id") %>%
    left_join(tmpAnnot, by="ensembl_gene_id")

1.3.2.4 Differential expression for each cluster

The steps illustrated above with cluster 0 are now repeated for each cluster:

  • Subset pseudo-bulk counts for that cluster
  • Create edgeR object with these pseudo-bulk counts
  • Pre-process
    • Remove samples with very small library size
    • Remove genes with low UMI counts
    • Correct for compositional bias
  • Perform differential expression analysis
    • Estimate negative binomial dispersion
    • Estimate quasi-likelihood dispersion
    • Test for differential expression
de.results <- list()
for (labelToGet in levels(summed$label)) {

    current <- summed[,summed$label==labelToGet]

    y <- DGEList(counts(current), samples=colData(current))

    discarded <- isOutlier(colSums(counts(current)), log=TRUE, type="lower")
    y <- y[,!discarded]
    y <- y[filterByExpr(y, group=current$source_name),]
    y <- calcNormFactors(y)

    design <- try(
        model.matrix(~factor(source_name), y$samples),
        silent=TRUE
    )
    if (is(design, "try-error") || 
        qr(design)$rank==nrow(design) ||
        qr(design)$rank < ncol(design)) 
    {
        # Skipping labels without contrasts or without 
        # enough residual d.f. to estimate the dispersion.
        next
    }

    y <- estimateDisp(y, design)
    fit <- glmQLFit(y, design)
    res <- glmQLFTest(fit, coef=ncol(design))
    de.results[[labelToGet]] <- res
}
1.3.2.4.1 Number of DEGs by cluster and direction

We examine the numbers of DEGs at a FDR of 5% for each label (i.e. cluster). In general, there seems to be very little differential expression between the on and off conditions.

summaries <- lapply(de.results, FUN=function(x) summary(decideTests(x))[,1])
sum.tab <- do.call(rbind, summaries)
#sum.tab
sum.tab[order(rownames(sum.tab)),] %>%
    as.data.frame() %>%
    tibble::rownames_to_column("Cluster") %>%
    datatable(rownames = FALSE, options = list(pageLength = 20, scrollX = TRUE))
1.3.2.4.2 List of DEGs

We now list DEGs and the number of clusters they were detected in:

degs <- lapply(de.results, FUN=function(x) rownames(topTags(x, p.value=0.05)))
common.degs <- sort(table(unlist(degs)), decreasing=TRUE)
#head(common.degs, 20)
common.degs %>%
    as.data.frame %>% 
    dplyr::rename(Gene = Var1, NbClu = Freq) %>%
    datatable(rownames = FALSE, options = list(pageLength = 20, scrollX = TRUE))
1.3.2.4.3 Number of clusters skipped

“We also list the labels that were skipped due to the absence of replicates or contrasts. If it is necessary to extract statistics in the absence of replicates, several strategies can be applied such as reducing the complexity of the model or using a predefined value for the NB dispersion. We refer readers to the edgeR user’s guide for more details.”

skippedClusters <- setdiff(unique(summed$label), names(summaries))

The number of clusters skipped is 0.

if(length(skippedClusters)>0)
{
  skippedClusters
}
grmToShowList <- vector("list", length = nlevels(merged$clusters.mnn))
names(grmToShowList) <- levels(merged$clusters.mnn)
genesToExclude <- c()
nbGeneToShow <- 20

#degs <- lapply(de.results, FUN=function(x) (topTags(x, p.value=0.05)))
degs <- lapply(de.results, FUN=function(x) (as.data.frame(topTags(x, n=nbGeneToShow))))

for( namex in levels(merged$clusters.mnn) )
{
    nbGeneToUse <- min(c(nrow(degs[[namex]]), nbGeneToShow))

    # format

    # format p value:
    tmpCol <- grep("PValue|FDR", colnames(degs[[namex]]), value=TRUE)
    degs[[namex]][,tmpCol] <- apply(degs[[namex]][,tmpCol],
                         2,
                         function(x){format(x, scientific = TRUE, digits = 1)})
    # format logFC:
    tmpCol <- c("logFC", "logCPM", "F")
    degs[[namex]][,tmpCol] <- apply(degs[[namex]][,tmpCol], 2,  function(x){round(x, 2)})
    rm(tmpCol)

    # subset data
    grmToShow <- degs[[namex]] %>%
        as.data.frame() %>%
        tibble::rownames_to_column("gene") %>%  
        arrange(FDR, desc(abs(logFC))) %>%
        filter(! gene %in% genesToExclude) %>%
        group_modify(~ head(.x, nbGeneToUse)) 
    # keep data
    grmToShow$cluster <- namex
    grmToShowList[[namex]] <- grmToShow
    # tidy
    rm(nbGeneToUse)
}
grmToShowDf <- do.call("rbind", grmToShowList)
tmpCol <- c("cluster", "gene")
grmToShowDf %>%
    select(tmpCol, setdiff(colnames(grmToShowDf), tmpCol)) %>%
    filter(gene %in% names(common.degs) & as.numeric(FDR) < 0.05) %>%
    datatable(rownames = FALSE, filter="top", options=list(scrollX = TRUE, pageLength = 15))
Note: Using an external vector in selections is ambiguous.
ℹ Use `all_of(tmpCol)` instead of `tmpCol` to silence this message.
ℹ See <https://tidyselect.r-lib.org/reference/faq-external-vector.html>.
This message is displayed once per session.

tmpBool <- as.numeric(grmToShowDf$FDR) < 0.05 
markers.to.plot <- unique(grmToShowDf[tmpBool, "gene"])
markers.to.plot <- markers.to.plot[1:5]

1.3.3  Putting it all together

Now that we have laid out the theory underlying the DE analysis, we repeat this process for each of the labels. This is conveniently done using the pseudoBulkDGE() function from scran, which will loop over all labels and apply the exact analysis described above to each label. To prepare for this, we filter out all sample-label combinations with insufficient cells.

summed.filt <- summed[,summed$ncells >= 20]

We construct a common design matrix that will be used in the analysis for each label. Recall that this matrix should have one row per unique sample (and named as such), reflecting the fact that we are modelling counts on the sample level instead of the cell level.

# Pulling out a sample-level 'targets' data.frame:
targets <- colData(merged)[!duplicated(merged$Sample.Name2),]

# Constructing the design matrix:
design <- model.matrix(~factor(source_name), data=targets)
rownames(design) <- targets$Sample.Name2

We then apply the pseudoBulkDGE() function to obtain a list of DE genes for each label. This function puts some additional effort into automatically dealing with labels that are not represented in all sample groups, for which a DE analysis between conditions is meaningless; or are not represented in a sufficient number of replicate samples to enable modelling of biological variability.

library(scran)
de.results <- pseudoBulkDGE(summed.filt, 
    sample=summed.filt$Sample.Name2,
    label=summed.filt$label,
    design=design,
    coef=ncol(design),

    # 'condition' sets the group size for filterByExpr(),
    # to perfectly mimic our previous manual analysis.
    condition=targets$source_name 
)

We examine the numbers of DEGs at a FDR of 5% for each label using the decideTestsPerLabel() function. Note that genes listed as NA were either filtered out as low-abundance genes for a given label’s analysis, or the comparison of interest was not possible for a particular label, e.g., due to lack of residual degrees of freedom or an absence of samples from both conditions.

is.de <- decideTestsPerLabel(de.results, threshold=0.05)
summarizeTestsPerLabel(is.de)
     -1    0   1    NA
c1    0    0   0 12317
c10   0    0   0 12317
c11   0    0   0 12317
c12 307 2081 183  9746
c13   0 1320   1 10996
c2    2 1826   6 10483
c3    0    0   0 12317
c4  477 6330 266  5244
c5  212 4416 219  7470
c6  528 3361 337  8091
c7    0    0   0 12317
c8  486 3446 319  8066
c9    0    0   0 12317

For each gene, we compute the percentage of cell types in which that gene is upregulated or downregulated. (Here, we consider a gene to be non-DE if it is not retained after filtering.).

# Upregulated across most cell types.
up.de <- is.de > 0 & !is.na(is.de)
head(sort(rowMeans(up.de), decreasing=TRUE), 10)
ENSG00000064886 ENSG00000066294 ENSG00000078687 ENSG00000103254 ENSG00000106018 
      0.3846154       0.3846154       0.3846154       0.3846154       0.3846154 
ENSG00000133169 ENSG00000137731 ENSG00000138650 ENSG00000143641 ENSG00000143851 
      0.3846154       0.3846154       0.3846154       0.3846154       0.3846154 
# Downregulated across cell types.
down.de <- is.de < 0 & !is.na(is.de)
head(sort(rowMeans(down.de), decreasing=TRUE), 10)
ENSG00000007312 ENSG00000065809 ENSG00000076641 ENSG00000081189 ENSG00000090238 
      0.3846154       0.3846154       0.3846154       0.3846154       0.3846154 
ENSG00000100242 ENSG00000100721 ENSG00000105369 ENSG00000115652 ENSG00000118523 
      0.3846154       0.3846154       0.3846154       0.3846154       0.3846154 

We further identify label-specific DE genes that are significant in our label of interest yet not DE in any other label. As hypothesis tests are not typically geared towards identifying genes that are not DE, we use an ad hoc approach where we consider a gene to be consistent with the null hypothesis for a label if it fails to be detected even at a generous FDR threshold of 50%.

remotely.de <- decideTestsPerLabel(de.results, threshold=0.5)
not.de <- remotely.de==0 | is.na(remotely.de)

other.labels <- setdiff(colnames(not.de), "c2")
unique.degs <- is.de[,"c2"]!=0 & rowMeans(not.de[,other.labels])==1
unique.degs <- names(which(unique.degs))

other.labels <- setdiff(colnames(not.de), "c4")
unique.degs <- is.de[,"c4"]!=0 & rowMeans(not.de[,other.labels])==1
unique.degs <- names(which(unique.degs))
# Choosing the top-ranked gene for inspection:
de.c4 <- de.results$c4
de.c4 <- de.c4[order(de.c4$PValue),]
de.c4 <- de.c4[rownames(de.c4) %in% unique.degs,]

sizeFactors(summed.filt) <- NULL
plotExpression(logNormCounts(summed.filt), 
    features=rownames(de.c4)[1],
    x="source_name", colour_by="source_name", 
    other_fields="label") + 
    facet_wrap(~label)

We also list the labels that were skipped due to the absence of replicates or contrasts. If it is necessary to extract statistics in the absence of replicates, several strategies can be applied such as reducing the complexity of the model or using a predefined value for the NB dispersion. We refer readers to the edgeR user’s guide for more details.

print(metadata(de.results)$failed)
[1] "c1"  "c10" "c11" "c3"  "c7"  "c9" 

1.4  Differential abundance between conditions

1.4.1 Overview

n a DA analysis, we test for significant changes in per-label cell abundance across conditions. This will reveal which cell types are depleted or enriched upon treatment, which is arguably just as interesting as changes in expression within each cell type. The DA analysis has a long history in flow cytometry (Finak et al. 2014; Lun, Richard, and Marioni 2017) where it is routinely used to examine the effects of different conditions on the composition of complex cell populations. By performing it here, we effectively treat scRNA-seq as a “super-FACS” technology for defining relevant subpopulations using the entire transcriptome.

We prepare for the DA analysis by quantifying the number of cells assigned to each label (or cluster).

abundances <- table(merged$clusters.mnn, merged$Sample.Name2) 
abundances <- unclass(abundances) 
head(abundances)
     
      ETV6-RUNX1_1 ETV6-RUNX1_2 ETV6-RUNX1_3 ETV6-RUNX1_4 HHD_1 HHD_2 PBMMC_1
  c1           443          312           93          337   114   169      10
  c10            5            5           41          157     1     3       3
  c11            1            3           32           52     2     5      35
  c12           55          447          294           94   292   445     186
  c13            5            4           63           13    33     9      42
  c2             1           19          102           20    54     7      35
     
      PBMMC_2 PBMMC_3 PRE-T_1 PRE-T_2
  c1        9      12       1       0
  c10     219      10       2       9
  c11     133      32       1      33
  c12      49     185      89      35
  c13      87      85       7      59
  c2       81     114       9      37

Performing the DA analysis

Our DA analysis will again be performed with the edgeR package. This allows us to take advantage of the NB GLM methods to model overdispersed count data in the presence of limited replication - except that the counts are not of reads per gene, but of cells per label (Lun, Richard, and Marioni 2017). The aim is to share information across labels to improve our estimates of the biological variability in cell abundance between replicates.

# Attaching some column metadata.
extra.info <- colData(merged)[match(colnames(abundances), merged$Sample.Name2),]
y.ab <- DGEList(abundances, samples=extra.info)
y.ab
An object of class "DGEList"
$counts
     
      ETV6-RUNX1_1 ETV6-RUNX1_2 ETV6-RUNX1_3 ETV6-RUNX1_4 HHD_1 HHD_2 PBMMC_1
  c1           443          312           93          337   114   169      10
  c10            5            5           41          157     1     3       3
  c11            1            3           32           52     2     5      35
  c12           55          447          294           94   292   445     186
  c13            5            4           63           13    33     9      42
     
      PBMMC_2 PBMMC_3 PRE-T_1 PRE-T_2
  c1        9      12       1       0
  c10     219      10       2       9
  c11     133      32       1      33
  c12      49     185      89      35
  c13      87      85       7      59
8 more rows ...

$samples
             group lib.size norm.factors        batch        Run Sample.Name
ETV6-RUNX1_1     1     1000            1 ETV6-RUNX1_1 SRR9264343  GSM3872434
ETV6-RUNX1_2     1     1000            1 ETV6-RUNX1_2 SRR9264344  GSM3872435
ETV6-RUNX1_3     1     1000            1 ETV6-RUNX1_3 SRR9264345  GSM3872436
ETV6-RUNX1_4     1     1000            1 ETV6-RUNX1_4 SRR9264346  GSM3872437
HHD_1            1     1000            1        HHD_1 SRR9264347  GSM3872438
             source_name      block setName Sample.Name2 clusters.mnn
ETV6-RUNX1_1  ETV6-RUNX1 ETV6-RUNX1   Caron ETV6-RUNX1_1           c6
ETV6-RUNX1_2  ETV6-RUNX1 ETV6-RUNX1   Caron ETV6-RUNX1_2           c8
ETV6-RUNX1_3  ETV6-RUNX1 ETV6-RUNX1   Caron ETV6-RUNX1_3           c5
ETV6-RUNX1_4  ETV6-RUNX1 ETV6-RUNX1   Caron ETV6-RUNX1_4           c8
HHD_1                HHD        HHD   Caron        HHD_1           c7
6 more rows ...

We filter out low-abundance labels as previously described. This avoids cluttering the result table with very rare subpopulations that contain only a handful of cells. For a DA analysis of cluster abundances, filtering is generally not required as most clusters will not be of low-abundance (otherwise there would not have been enough evidence to define the cluster in the first place).

keep <- filterByExpr(y.ab, group=y.ab$samples$source_name)
y.ab <- y.ab[keep,]
summary(keep)
   Mode   FALSE    TRUE 
logical       1      12 

Unlike DE analyses, we do not perform an additional normalization step with calcNormFactors(). This means that we are only normalizing based on the “library size”, i.e., the total number of cells in each sample. Any changes we detect between conditions will subsequently represent differences in the proportion of cells in each cluster. The motivation behind this decision is discussed in more detail in Section 14.4.3.

Here, the log-fold change in our model refers to the change in cell abundance between sample groups, rather than the change in gene expression.

design <- model.matrix(~factor(source_name), y.ab$samples)

We use the estimateDisp() function to estimate the NB dipersion for each cluster. We turn off the trend as we do not have enough points for its stable estimation.

y.ab <- estimateDisp(y.ab, design, trend="none")
summary(y.ab$common.dispersion)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  1.017   1.017   1.017   1.017   1.017   1.017 
plotBCV(y.ab, cex=1)

We repeat this process with the QL dispersion, again disabling the trend.

fit.ab <- glmQLFit(y.ab, design, robust=TRUE, abundance.trend=FALSE)
summary(fit.ab$var.prior)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  1.157   1.157   1.157   1.157   1.157   1.157 
summary(fit.ab$df.prior)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  23.34   23.34   23.34   24.10   25.61   25.61 
plotQLDisp(fit.ab, cex=1)

We test for differences in abundance between sample groups using glmQLFTest().

res <- glmQLFTest(fit.ab, coef=ncol(design))
summary(decideTests(res))
       factor(source_name)PRE-T
Down                          1
NotSig                       11
Up                            0
topTags(res)
Coefficient:  factor(source_name)PRE-T 
         logFC   logCPM           F      PValue         FDR
c1  -8.8893519 17.07235 18.23350328 0.000158277 0.001899324
c7   3.2601228 15.52573  4.83688825 0.035612506 0.213675037
c10 -3.2120505 15.39844  3.08504552 0.089104799 0.344105248
c4   2.1343167 16.90254  2.63766139 0.114701749 0.344105248
c5   1.9410456 17.29501  2.11215108 0.156398940 0.375357455
c12 -1.8413698 17.59928  1.71643024 0.199307039 0.398614078
c13  0.6319960 15.24543  0.22447446 0.639043019 0.809047739
c2  -0.6234366 15.46926  0.18697282 0.668505881 0.809047739
c9   0.7369656 14.42541  0.14094409 0.710044583 0.809047739
c8  -0.3779754 16.26214  0.08626566 0.770840636 0.809047739

1.4.2 Handling composition effects

1.4.2.1 Background

As mentioned above, we do not use calcNormFactors() in our default DA analysis. This normalization step assumes that most of the input features are not different between conditions. While this assumption is reasonable for most types of gene expression data, it is generally too strong for cell type abundance - most experiments consist of only a few cell types that may all change in abundance upon perturbation. Thus, our default approach is to only normalize based on the total number of cells in each sample, which means that we are effectively testing for differential proportions between conditions.

Unfortunately, the use of the total number of cells leaves us susceptible to composition effects. For example, a large increase in abundance for one cell subpopulation will introduce decreases in proportion for all other subpopulations - which is technically correct, but may be misleading if one concludes that those other subpopulations are decreasing in abundance of their own volition. If composition biases are proving problematic for interpretation of DA results, we have several avenues for removing them or mitigating their impact by leveraging a priori biological knowledge. 14.4.3.2 Assuming most labels do not change

If it is possible to assume that most labels (i.e., cell types) do not change in abundance, we can use calcNormFactors() to compute normalization factors.

y.ab2 <- calcNormFactors(y.ab)
y.ab2$samples$norm.factors
 [1] 0.7085578 0.8214020 0.8171844 1.0331737 0.8686328 0.5418219 1.7478390
 [8] 1.8674296 1.8315815 0.8196523 0.8824318

We then proceed with the remainder of the edgeR analysis, shown below in condensed format. A shift of positive log-fold changes towards zero is consistent with the removal of composition biases.

y.ab2 <- estimateDisp(y.ab2, design, trend="none")
fit.ab2 <- glmQLFit(y.ab2, design, robust=TRUE, abundance.trend=FALSE)
res2 <- glmQLFTest(fit.ab2, coef=ncol(design))
topTags(res2, n=10)
Coefficient:  factor(source_name)PRE-T 
         logFC   logCPM           F       PValue         FDR
c1  -8.9890098 17.41093 15.15115665 0.0001981979 0.002378375
c7   3.3741853 14.98811  5.92828284 0.0170168369 0.102101021
c10 -3.0663655 15.07547  3.44448555 0.0669698626 0.267879450
c4   1.9304189 17.18145  2.13638867 0.1475711494 0.366624466
c5   1.9039062 17.24607  2.08203436 0.1527601941 0.366624466
c12 -1.8537335 17.86149  1.51313953 0.2220945660 0.444189132
c2  -0.6696831 15.29312  0.21973417 0.6404568093 0.835706180
c13  0.5688409 15.03125  0.17396711 0.6776739501 0.835706180
c9   0.8099331 13.74005  0.15287008 0.6967974881 0.835706180
c8  -0.3542759 16.51826  0.06443934 0.8002325040 0.835706180
LS0tCnRpdGxlOiAiQ1JVSyBDSSBTdW1tZXIgU2Nob29sIDIwMjAgLSBpbnRyb2R1Y3Rpb24gdG8gc2luZ2xlLWNlbGwgUk5BLXNlcSBhbmFseXNpcyIKc3VidGl0bGU6ICdNdWx0aS1zYW1wbGUgY29tcGFyaXNvbnMnCgphdXRob3I6ICJTdGVwaGFuZSBCYWxsZXJlYXUsIFpleW5lcCBLYWxlbmRlciBBdGFrLCBLYXRhcnp5bmEgS2FuaWEiCm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgY29kZV9mb2xkaW5nOiBoaWRlCiAgICB0b2M6IHllcwogICAgdG9jX2Zsb2F0OiB5ZXMKICAgIG51bWJlcl9zZWN0aW9uczogdHJ1ZQogIGh0bWxfZG9jdW1lbnQ6CiAgICBkZl9wcmludDogcGFnZWQKICAgIHRvYzogeWVzCiAgICBudW1iZXJfc2VjdGlvbnM6IHRydWUKICAgIGNvZGVfZm9sZGluZzogaGlkZQogIGh0bWxfYm9vazoKICAgIGNvZGVfZm9sZGluZzogaGlkZQpwYXJhbXM6CiAgb3V0RGlyQml0OiAiQW5hV2lTY2UvQXR0ZW1wdDEiCi0tLQoKCmBgYHtyfQpwcm9qRGlyIDwtICIvbW50L3NjcmF0Y2hhL2Jpb2luZm9ybWF0aWNzL2JhbGxlcjAxLzIwMjAwNTExX0Zlcm5hbmRlc01fTUVfY3J1a0JpU3MyMDIwIgpvdXREaXJCaXQgPC0gIkFuYVdpU2NlL0F0dGVtcHQxIgpuYlBjVG9Db21wIDwtIDUwCmBgYAoKYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0UsIGVjaG89RkFMU0V9CiMgRmlyc3QsIHNldCBzb21lIHZhcmlhYmxlczoKa25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFKQpvcHRpb25zKHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRSkKc2V0LnNlZWQoMTIzKSAjIGZvciByZXByb2R1Y2liaWxpdHkKa25pdHI6Om9wdHNfY2h1bmskc2V0KGV2YWwgPSBUUlVFKSAKYGBgCgpgYGB7ciwgaW5jbHVkZT1GQUxTRSwgd2FybmluZz1GQUxTRX0Kc3VwcHJlc3NNZXNzYWdlcyhsaWJyYXJ5KGdncGxvdDIpKQpzdXBwcmVzc01lc3NhZ2VzKGxpYnJhcnkoc2NhdGVyKSkKc3VwcHJlc3NNZXNzYWdlcyhsaWJyYXJ5KHNjcmFuKSkKc3VwcHJlc3NNZXNzYWdlcyhsaWJyYXJ5KGRwbHlyKSkKc3VwcHJlc3NNZXNzYWdlcyhsaWJyYXJ5KERUKSkKZm9udHNpemUgPC0gdGhlbWUoYXhpcy50ZXh0PWVsZW1lbnRfdGV4dChzaXplPTEyKSwgYXhpcy50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT0xNikpCnNnbFBsb3RTaXplIDwtIDcKYGBgCgojIERpZmZlcmVudGlhbCBleHByZXNzaW9uIGFuZCBhYnVuZGFuY2UgYmV0d2VlbiBjb25kaXRpb25zCgpTb3VyY2U6IFtNdWx0aS1zYW1wbGUgY29tcGFyaXNvbnNdKGh0dHBzOi8vb3NjYS5iaW9jb25kdWN0b3Iub3JnL211bHRpLXNhbXBsZS1jb21wYXJpc29ucy5odG1sKSBvZiB0aGUgT1NDQSBib29rLgoKIyMgTW90aXZhdGlvbgoKQSBwb3dlcmZ1bCB1c2Ugb2Ygc2NSTkEtc2VxIHRlY2hub2xvZ3kgbGllcyBpbiB0aGUgZGVzaWduIG9mIHJlcGxpY2F0ZWQgbXVsdGktY29uZGl0aW9uIGV4cGVyaW1lbnRzIHRvIGRldGVjdCBjaGFuZ2VzIGluIGNvbXBvc2l0aW9uIG9yIGV4cHJlc3Npb24gYmV0d2VlbiBjb25kaXRpb25zLiBGb3IgZXhhbXBsZSwgYSByZXNlYXJjaGVyIGNvdWxkIHVzZSB0aGlzIHN0cmF0ZWd5IHRvIGRldGVjdCBjaGFuZ2VzIGluIGNlbGwgdHlwZSBhYnVuZGFuY2UgYWZ0ZXIgZHJ1ZyB0cmVhdG1lbnQgKFJpY2hhcmQgZXQgYWwuIDIwMTgpIG9yIGdlbmV0aWMgbW9kaWZpY2F0aW9ucyAoU2NpYWxkb25lIGV0IGFsLiAyMDE2KS4gVGhpcyBwcm92aWRlcyBtb3JlIGJpb2xvZ2ljYWwgaW5zaWdodCB0aGFuIGNvbnZlbnRpb25hbCBzY1JOQS1zZXEgZXhwZXJpbWVudHMgaW52b2x2aW5nIG9ubHkgb25lIGJpb2xvZ2ljYWwgY29uZGl0aW9uLCBlc3BlY2lhbGx5IGlmIHdlIGNhbiByZWxhdGUgcG9wdWxhdGlvbiBjaGFuZ2VzIHRvIHNwZWNpZmljIGV4cGVyaW1lbnRhbCBwZXJ0dXJiYXRpb25zLgoKRGlmZmVyZW50aWFsIGFuYWx5c2VzIG9mIG11bHRpLWNvbmRpdGlvbiBzY1JOQS1zZXEgZXhwZXJpbWVudHMgY2FuIGJlIGJyb2FkbHkgc3BsaXQgaW50byB0d28gY2F0ZWdvcmllcyAtIGRpZmZlcmVudGlhbCBleHByZXNzaW9uIChERSkgYW5kIGRpZmZlcmVudGlhbCBhYnVuZGFuY2UgKERBKSBhbmFseXNlcy4gVGhlIGZvcm1lciB0ZXN0cyBmb3IgY2hhbmdlcyBpbiBleHByZXNzaW9uIGJldHdlZW4gY29uZGl0aW9ucyBmb3IgY2VsbHMgb2YgdGhlIHNhbWUgdHlwZSB0aGF0IGFyZSBwcmVzZW50IGluIGJvdGggY29uZGl0aW9ucywgd2hpbGUgdGhlIGxhdHRlciB0ZXN0cyBmb3IgY2hhbmdlcyBpbiB0aGUgY29tcG9zaXRpb24gb2YgY2VsbCB0eXBlcyAob3Igc3RhdGVzLCBldGMuKSBiZXR3ZWVuIGNvbmRpdGlvbnMuCgojIyBTZXR0aW5nIHVwIHRoZSBkYXRhCgpXZSB3aWxsIHVzZSB0aGUgZGF0YSBzZXQgY29tcHJpc2luZyB0aGUgMTEgc2FtcGxlcyAoMTAwMCBjZWxscyBwZXIgc2FtcGxlKSBhbmFseXNlZCB3aXRoIGZhc3RNTk4gYW5kIHRoZSBuZXN0ZWQgbGlzdCBvZiBzYW1wbGVzLgoKVGhlIGRpZmZlcmVudGlhbCBhbmFseXNlcyBpbiB0aGlzIGNoYXB0ZXIgd2lsbCBiZSBwcmVkaWNhdGVkIG9uIG1hbnkgb2YgdGhlIHByZS1wcm9jZXNzaW5nIHN0ZXBzIGNvdmVyZWQgcHJldmlvdXNseS4gRm9yIGJyZXZpdHksIHdlIHdpbGwgbm90IGV4cGxpY2l0bHkgcmVwZWF0IHRoZW0gaGVyZSwgb25seSBub3RpbmcgdGhhdCB3ZSBoYXZlIGFscmVhZHkgbWVyZ2VkIGNlbGxzIGZyb20gYWxsIHNhbXBsZXMgaW50byB0aGUgc2FtZSBjb29yZGluYXRlIHN5c3RlbSBhbmQgY2x1c3RlcmVkIHRoZSBtZXJnZWQgZGF0YXNldCB0byBvYnRhaW4gYSBjb21tb24gcGFydGl0aW9uaW5nIGFjcm9zcyBhbGwgc2FtcGxlcy4KCkxvYWQgdGhlIFNDRSBvYmplY3Q6CgpgYGB7cn0Kc2V0TmFtZSA8LSAiY2Fyb24iCiMgUmVhZCBvYmplY3QgaW46CiMjc2V0U3VmIDwtICJfMWtDZWxsUGVyU3BsIgojI3RtcEZuIDwtIHNwcmludGYoIiVzLyVzL1JvYmplY3RzLyVzX3NjZV9uel9wb3N0RGVjb252JXNfY2x1c3RlcmVkLlJkcyIsIHByb2pEaXIsIG91dERpckJpdCwgc2V0TmFtZSwgc2V0U3VmKQoKc2V0U3VmIDwtICJfMWtDcHMiCnRtcEZuIDwtIHNwcmludGYoIiVzLyVzL1JvYmplY3RzLyVzX3NjZV9uel9wb3N0RGVjb252JXNfRm13YmwuUmRzIiwgcHJvakRpciwgb3V0RGlyQml0LCBzZXROYW1lLCBzZXRTdWYpCgpwcmludCh0bXBGbikKCmlmKCFmaWxlLmV4aXN0cyh0bXBGbikpCnsKCWtuaXRyOjprbml0X2V4aXQoKQp9CnNjZSA8LSByZWFkUkRTKHRtcEZuKQpzY2UKYGBgCgpBIGJyaWVmIGluc3BlY3Rpb24gb2YgdGhlIHJlc3VsdHMgc2hvd3MgY2x1c3RlcnMgY29udGFpbiB2YXJ5aW5nIGNvbnRyaWJ1dGlvbnMgZnJvbSBiYXRjaGVzOgoKYGBge3J9CmxpYnJhcnkoc2NhdGVyKQpjb2xMYWJlbHMoc2NlKSA8LSBzY2UkY2x1c3RlcnMubW5uCnRhYmxlKGNvbExhYmVscyhzY2UpLCBzY2UkdHlwZSkKCnRhYmxlKGNvbExhYmVscyhzY2UpLCBzY2UkU2FtcGxlLk5hbWUyKQpgYGAKCk9uIHRoZSB0LVNORSBwbG90cyBiZWxvdywgY2VsbHMgY29sb3JlZCBieSB0eXBlIG9yIHNhbXBsZSAoJ2JhdGNoIG9mIG9yaWdpbicpLiBDbHVzdGVyIG51bWJlcnMgYXJlIHN1cGVyaW1wb3NlZCBiYXNlZCBvbiB0aGUgbWVkaWFuIGNvb3JkaW5hdGUgb2YgY2VsbHMgYXNzaWduZWQgdG8gdGhhdCBjbHVzdGVyLiAKCmBgYHtyfQpwbG90VFNORShzY2UsIGNvbG91cl9ieT0idHlwZSIsIHRleHRfYnk9ImxhYmVsIikKcGxvdFRTTkUoc2NlLCBjb2xvdXJfYnk9IlNhbXBsZS5OYW1lMiIpCmBgYAoKCmBgYHtyfQp0bXBGbiA8LSBzcHJpbnRmKCIlcy8lcy9Sb2JqZWN0cy8lc19zY2VfbnpfcG9zdERlY29udiVzX0Ztd2JsMi5SZHMiLCBwcm9qRGlyLCBvdXREaXJCaXQsIHNldE5hbWUsIHNldFN1ZikKdG1wTGlzdCA8LSByZWFkUkRTKHRtcEZuKQoKCmNob3Nlbi5odmdzIDwtIHRtcExpc3QkY2hvc2VuLmh2Z3MKcmVzY2FsZWQubWJuIDwtIHRtcExpc3QkcmVzY2FsZWQubWJuCnVuY29ycmVjdGVkIDwtIHRtcExpc3QkdW5jb3JyZWN0ZWQKY29sVG9LZWVwIDwtIGMoIlJ1biIsICJTYW1wbGUuTmFtZSIsICJzb3VyY2VfbmFtZSIsICJibG9jayIsICJzZXROYW1lIiwgIlNhbXBsZS5OYW1lMiIpIApjb2xEYXRhKHVuY29ycmVjdGVkKSA8LSBjb2xEYXRhKHVuY29ycmVjdGVkKVssY29sVG9LZWVwXQpjb2xEYXRhKHVuY29ycmVjdGVkKVsxOjMsXQoKIy0tLSBtZXJnaW5nIC0tLSMKbGlicmFyeShiYXRjaGVsb3IpCnNldC5zZWVkKDAxMDAxMDAxKQptZXJnZWQgPC0gY29ycmVjdEV4cGVyaW1lbnRzKHVuY29ycmVjdGVkLCAKICAgIGJhdGNoPXVuY29ycmVjdGVkJFNhbXBsZS5OYW1lMiwgCiAgICBzdWJzZXQucm93PWNob3Nlbi5odmdzLAogICAgUEFSQU09RmFzdE1ublBhcmFtKAogICAgICAgIG1lcmdlLm9yZGVyPWxpc3QoIGxpc3QoMSwyLDMsNCksIGxpc3QoOSwxMCwxMSksIGxpc3QoNSw2KSwgbGlzdCg3LDgpICkKICAgICkKKQoKbWVyZ2VkCgojLS0tIGNsdXN0ZXJpbmcgLS0tIwpnIDwtIGJ1aWxkU05OR3JhcGgobWVyZ2VkLCB1c2UuZGltcmVkPSJjb3JyZWN0ZWQiKQpjbHVzdGVycyA8LSBpZ3JhcGg6OmNsdXN0ZXJfbG91dmFpbihnKQptZXJnZWQkY2x1c3RlcnMubW5uIDwtIGZhY3RvcihwYXN0ZTAoImMiLCBjbHVzdGVycyRtZW1iZXJzaGlwKSkKI2NvbExhYmVscyhtZXJnZWQpIDwtIG1lcmdlZCRjbHVzdGVycy5tbm4KCiMtLS0gZGltZW5zaW9uYWxpdHktcmVkdWN0aW9uIC0tLSMKbWVyZ2VkIDwtIHJ1blRTTkUobWVyZ2VkLCBkaW1yZWQ9ImNvcnJlY3RlZCIsIGV4dGVybmFsX25laWdoYm9ycz1UUlVFKQptZXJnZWQgPC0gcnVuVU1BUChtZXJnZWQsIGRpbXJlZD0iY29ycmVjdGVkIiwgZXh0ZXJuYWxfbmVpZ2hib3JzPVRSVUUpCgpsaWJyYXJ5KHNjYXRlcikKdGFibGUobWVyZ2VkJGNsdXN0ZXJzLm1ubiwgbWVyZ2VkJGJsb2NrKQp0YWJsZShtZXJnZWQkY2x1c3RlcnMubW5uLCBtZXJnZWQkU2FtcGxlLk5hbWUyKQoKcGxvdFRTTkUobWVyZ2VkLCBjb2xvdXJfYnk9ImJsb2NrIiwgdGV4dF9ieT0iY2x1c3RlcnMubW5uIikKcGxvdFRTTkUobWVyZ2VkLCBjb2xvdXJfYnk9IlNhbXBsZS5OYW1lMiIpCmBgYAoKIyMgRGlmZmVyZW50aWFsIGV4cHJlc3Npb24gYmV0d2VlbiBjb25kaXRpb25zCgojIyPCoENyZWF0aW5nIHBzZXVkby1idWxrIHNhbXBsZXMKClRoZSBtb3N0IG9idmlvdXMgZGlmZmVyZW50aWFsIGFuYWx5c2lzIGlzIHRvIGxvb2sgZm9yIGNoYW5nZXMgaW4gZXhwcmVzc2lvbiBiZXR3ZWVuIGNvbmRpdGlvbnMuIFdlIHBlcmZvcm0gdGhlIERFIGFuYWx5c2lzIHNlcGFyYXRlbHkgZm9yIGVhY2ggbGFiZWwuIFRoZSBhY3R1YWwgREUgdGVzdGluZyBpcyBwZXJmb3JtZWQgb24g4oCccHNldWRvLWJ1bGvigJ0gZXhwcmVzc2lvbiBwcm9maWxlcyAoVHVuZyBldCBhbC4gMjAxNyksIGdlbmVyYXRlZCBieSBzdW1taW5nIGNvdW50cyB0b2dldGhlciBmb3IgYWxsIGNlbGxzIHdpdGggdGhlIHNhbWUgY29tYmluYXRpb24gb2YgbGFiZWwgYW5kIHNhbXBsZS4gVGhpcyBsZXZlcmFnZXMgdGhlIHJlc29sdXRpb24gb2ZmZXJlZCBieSBzaW5nbGUtY2VsbCB0ZWNobm9sb2dpZXMgdG8gZGVmaW5lIHRoZSBsYWJlbHMsIGFuZCBjb21iaW5lcyBpdCB3aXRoIHRoZSBzdGF0aXN0aWNhbCByaWdvciBvZiBleGlzdGluZyBtZXRob2RzIGZvciBERSBhbmFseXNlcyBpbnZvbHZpbmcgYSBzbWFsbCBudW1iZXIgb2Ygc2FtcGxlcy4KCgpgYGB7cn0KIyBVc2luZyAnbGFiZWwnIGFuZCAnc2FtcGxlJyBhcyBvdXIgdHdvIGZhY3RvcnM7IGVhY2ggY29sdW1uIG9mIHRoZSBvdXRwdXQKIyBjb3JyZXNwb25kcyB0byBvbmUgdW5pcXVlIGNvbWJpbmF0aW9uIG9mIHRoZXNlIHR3byBmYWN0b3JzLgpzdW1tZWQgPC0gYWdncmVnYXRlQWNyb3NzQ2VsbHMobWVyZ2VkLCAKICAgIAkJCQlpZCA9IERhdGFGcmFtZSgKICAgIAkJCQkJbGFiZWw9bWVyZ2VkJGNsdXN0ZXJzLm1ubiwKICAgIAkJCQkJc2FtcGxlPW1lcmdlZCRTYW1wbGUuTmFtZTIKCQkJCQkpCikKc3VtbWVkCmNvbERhdGEoc3VtbWVkKSAlPiUgaGVhZCgzKQpgYGAKCkF0IHRoaXMgcG9pbnQsIGl0IGlzIHdvcnRoIHJlZmxlY3Rpbmcgb24gdGhlIG1vdGl2YXRpb25zIGJlaGluZCB0aGUgdXNlIG9mIHBzZXVkby1idWxraW5nOgoKTGFyZ2VyIGNvdW50cyBhcmUgbW9yZSBhbWVuYWJsZSB0byBzdGFuZGFyZCBERSBhbmFseXNpcyBwaXBlbGluZXMgZGVzaWduZWQgZm9yIGJ1bGsgUk5BLXNlcSBkYXRhLiBOb3JtYWxpemF0aW9uIGlzIG1vcmUgc3RyYWlnaHRmb3J3YXJkIGFuZCBjZXJ0YWluIHN0YXRpc3RpY2FsIGFwcHJveGltYXRpb25zIGFyZSBtb3JlIGFjY3VyYXRlIGUuZy4sIHRoZSBzYWRkbGVwb2ludCBhcHByb3hpbWF0aW9uIGZvciBxdWFzaS1saWtlbGlob29kIG1ldGhvZHMgb3Igbm9ybWFsaXR5IGZvciBsaW5lYXIgbW9kZWxzLgpDb2xsYXBzaW5nIGNlbGxzIGludG8gc2FtcGxlcyByZWZsZWN0cyB0aGUgZmFjdCB0aGF0IG91ciBiaW9sb2dpY2FsIHJlcGxpY2F0aW9uIG9jY3VycyBhdCB0aGUgc2FtcGxlIGxldmVsIChMdW4gYW5kIE1hcmlvbmkgMjAxNykuIEVhY2ggc2FtcGxlIGlzIHJlcHJlc2VudGVkIG5vIG1vcmUgdGhhbiBvbmNlIGZvciBlYWNoIGNvbmRpdGlvbiwgYXZvaWRpbmcgcHJvYmxlbXMgZnJvbSB1bm1vZGVsbGVkIGNvcnJlbGF0aW9ucyBiZXR3ZWVuIHNhbXBsZXMuIFN1cHBseWluZyB0aGUgcGVyLWNlbGwgY291bnRzIGRpcmVjdGx5IHRvIGEgREUgYW5hbHlzaXMgcGlwZWxpbmUgd291bGQgaW1wbHkgdGhhdCBlYWNoIGNlbGwgaXMgYW4gaW5kZXBlbmRlbnQgYmlvbG9naWNhbCByZXBsaWNhdGUsIHdoaWNoIGlzIG5vdCB0cnVlIGZyb20gYW4gZXhwZXJpbWVudGFsIHBlcnNwZWN0aXZlLiAoQSBtaXhlZCBlZmZlY3RzIG1vZGVsIGNhbiBoYW5kbGUgdGhpcyB2YXJpYW5jZSBzdHJ1Y3R1cmUgYnV0IGludm9sdmVzIGV4dHJhIHN0YXRpc3RpY2FsIGFuZCBjb21wdXRhdGlvbmFsIGNvbXBsZXhpdHkgZm9yIGxpdHRsZSBiZW5lZml0LCBzZWUgQ3Jvd2VsbCBldCBhbC4gKDIwMTkpLikKVmFyaWFuY2UgYmV0d2VlbiBjZWxscyB3aXRoaW4gZWFjaCBzYW1wbGUgaXMgbWFza2VkLCBwcm92aWRlZCBpdCBkb2VzIG5vdCBhZmZlY3QgdmFyaWFuY2UgYWNyb3NzIChyZXBsaWNhdGUpIHNhbXBsZXMuIFRoaXMgYXZvaWRzIHBlbmFsaXppbmcgREVHcyB0aGF0IGFyZSBub3QgdW5pZm9ybWx5IHVwLSBvciBkb3duLXJlZ3VsYXRlZCBmb3IgYWxsIGNlbGxzIGluIGFsbCBzYW1wbGVzIG9mIG9uZSBjb25kaXRpb24uIE1hc2tpbmcgaXMgZ2VuZXJhbGx5IGRlc2lyYWJsZSBhcyBERUdzIC0gdW5saWtlIG1hcmtlciBnZW5lcyAtIGRvIG5vdCBuZWVkIHRvIGhhdmUgbG93IHdpdGhpbi1zYW1wbGUgdmFyaWFuY2UgdG8gYmUgaW50ZXJlc3RpbmcsIGUuZy4sIGlmIHRoZSB0cmVhdG1lbnQgZWZmZWN0IGlzIGNvbnNpc3RlbnQgYWNyb3NzIHJlcGxpY2F0ZSBwb3B1bGF0aW9ucyBidXQgaGV0ZXJvZ2VuZW91cyBvbiBhIHBlci1jZWxsIGJhc2lzLiAoT2YgY291cnNlLCBoaWdoIHBlci1jZWxsIHZhcmlhYmlsaXR5IHdpbGwgc3RpbGwgcmVzdWx0IGluIHdlYWtlciBERSBpZiBpdCBhZmZlY3RzIHRoZSB2YXJpYWJpbGl0eSBhY3Jvc3MgcG9wdWxhdGlvbnMsIHdoaWxlIGhvbW9nZW5lb3VzIHBlci1jZWxsIHJlc3BvbnNlcyB3aWxsIHJlc3VsdCBpbiBzdHJvbmdlciBERSBkdWUgdG8gYSBsYXJnZXIgcG9wdWxhdGlvbi1sZXZlbCBsb2ctZm9sZCBjaGFuZ2UuIFRoZXNlIGVmZmVjdHMgYXJlIGFsc28gbGFyZ2VseSBkZXNpcmFibGUuKQoKYHIgI2tuaXRyOjprbml0X2V4aXQoKWAKCiMjIyBQZXJmb3JtaW5nIHRoZSBERSBhbmFseXNpcwoKIyMjI8KgSW50cm9kdWN0aW9uCgpUaGUgREUgYW5hbHlzaXMgd2lsbCBiZSBwZXJmb3JtZWQgdXNpbmcgcXVhc2ktbGlrZWxpaG9vZCAoUUwpIG1ldGhvZHMgZnJvbSB0aGUgZWRnZVIgcGFja2FnZSAoUm9iaW5zb24sIE1jQ2FydGh5LCBhbmQgU215dGggMjAxMDsgQ2hlbiwgTHVuLCBhbmQgU215dGggMjAxNikuIFRoaXMgdXNlcyBhIG5lZ2F0aXZlIGJpbm9taWFsIGdlbmVyYWxpemVkIGxpbmVhciBtb2RlbCAoTkIgR0xNKSB0byBoYW5kbGUgb3ZlcmRpc3BlcnNlZCBjb3VudCBkYXRhIGluIGV4cGVyaW1lbnRzIHdpdGggbGltaXRlZCByZXBsaWNhdGlvbi4gSW4gb3VyIGNhc2UsIHdlIGhhdmUgYmlvbG9naWNhbCB2YXJpYXRpb24gd2l0aCB0aHJlZSBwYWlyZWQgcmVwbGljYXRlcyBwZXIgY29uZGl0aW9uLCBzbyBlZGdlUiAob3IgaXRzIGNvbnRlbXBvcmFyaWVzKSBpcyBhIG5hdHVyYWwgY2hvaWNlIGZvciB0aGUgYW5hbHlzaXMuCgpXZSBkbyBub3QgdXNlIGFsbCBsYWJlbHMgZm9yIEdMTSBmaXR0aW5nIGFzIHRoZSBzdHJvbmcgREUgYmV0d2VlbiBsYWJlbHMgbWFrZXMgaXQgZGlmZmljdWx0IHRvIGNvbXB1dGUgYSBzZW5zaWJsZSBhdmVyYWdlIGFidW5kYW5jZSB0byBtb2RlbCB0aGUgbWVhbi1kaXNwZXJzaW9uIHRyZW5kLiBNb3Jlb3ZlciwgbGFiZWwtc3BlY2lmaWMgYmF0Y2ggZWZmZWN0cyB3b3VsZCBub3QgYmUgZWFzaWx5IGhhbmRsZWQgd2l0aCBhIHNpbmdsZSBhZGRpdGl2ZSB0ZXJtIGluIHRoZSBkZXNpZ24gbWF0cml4IGZvciB0aGUgYmF0Y2guIEluc3RlYWQsIHdlIGFyYml0cmFyaWx5IHBpY2sgb25lIG9mIHRoZSBsYWJlbHMgdG8gdXNlIGZvciB0aGlzIGRlbW9uc3RyYXRpb24uCgpgYGB7cn0KbGFiZWxUb0dldCA8LSAiYzEiCmN1cnJlbnQgPC0gc3VtbWVkWyxzdW1tZWQkbGFiZWw9PWxhYmVsVG9HZXRdCgojIENyZWF0aW5nIHVwIGEgREdFTGlzdCBvYmplY3QgZm9yIHVzZSBpbiBlZGdlUjoKc3VwcHJlc3NNZXNzYWdlcyhsaWJyYXJ5KGVkZ2VSKSkKeSA8LSBER0VMaXN0KGNvdW50cyhjdXJyZW50KSwgc2FtcGxlcz1jb2xEYXRhKGN1cnJlbnQpKQp5CmBgYAoKIyMjIyBQcmUtcHJvY2Vzc2luZwoKQSB0eXBpY2FsIHN0ZXAgaW4gYnVsayBSTkEtc2VxIGRhdGEgYW5hbHlzZXMgaXMgdG8gcmVtb3ZlIHNhbXBsZXMgd2l0aCB2ZXJ5IGxvdyBsaWJyYXJ5IHNpemVzIGR1ZSB0byBmYWlsZWQgbGlicmFyeSBwcmVwYXJhdGlvbiBvciBzZXF1ZW5jaW5nLiBUaGUgdmVyeSBsb3cgY291bnRzIGluIHRoZXNlIHNhbXBsZXMgY2FuIGJlIHRyb3VibGVzb21lIGluIGRvd25zdHJlYW0gc3RlcHMgc3VjaCBhcyBub3JtYWxpemF0aW9uIChDaGFwdGVyIDcpIG9yIGZvciBzb21lIHN0YXRpc3RpY2FsIGFwcHJveGltYXRpb25zIHVzZWQgaW4gdGhlIERFIGFuYWx5c2lzLiBJbiBvdXIgc2l0dWF0aW9uLCB0aGlzIGlzIGVxdWl2YWxlbnQgdG8gcmVtb3ZpbmcgbGFiZWwtc2FtcGxlIGNvbWJpbmF0aW9ucyB0aGF0IGhhdmUgdmVyeSBmZXcgb3IgbG93bHktc2VxdWVuY2VkIGNlbGxzLiBUaGUgZXhhY3QgZGVmaW5pdGlvbiBvZiDigJx2ZXJ5IGxvd+KAnSB3aWxsIHZhcnksIGJ1dCBpbiB0aGlzIGNhc2UsIHdlIHJlbW92ZSBjb21iaW5hdGlvbnMgY29udGFpbmluZyBmZXdlciB0aGFuIDIwIGNlbGxzIChDcm93ZWxsIGV0IGFsLiAyMDE5KS4gQWx0ZXJuYXRpdmVseSwgd2UgY291bGQgYXBwbHkgdGhlIG91dGxpZXItYmFzZWQgc3RyYXRlZ3kgZGVzY3JpYmVkIGluIENoYXB0ZXIgNiwgYnV0IHRoaXMgbWFrZXMgdGhlIHN0cm9uZyBhc3N1bXB0aW9uIHRoYXQgYWxsIGxhYmVsLXNhbXBsZSBjb21iaW5hdGlvbnMgaGF2ZSBzaW1pbGFyIG51bWJlcnMgb2YgY2VsbHMgdGhhdCBhcmUgc2VxdWVuY2VkIHRvIHNpbWlsYXIgZGVwdGguCgpgYGB7cn0KZGlzY2FyZGVkIDwtIGN1cnJlbnQkbmNlbGxzIDwgMjAKeSA8LSB5WywhZGlzY2FyZGVkXQpzdW1tYXJ5KGRpc2NhcmRlZCkKYGBgCgpBbm90aGVyIHR5cGljYWwgc3RlcCBpbiBidWxrIFJOQS1zZXEgYW5hbHlzZXMgaXMgdG8gcmVtb3ZlIGdlbmVzIHRoYXQgYXJlIGxvd2x5IGV4cHJlc3NlZC4gVGhpcyByZWR1Y2VzIGNvbXB1dGF0aW9uYWwgd29yaywgaW1wcm92ZXMgdGhlIGFjY3VyYWN5IG9mIG1lYW4tdmFyaWFuY2UgdHJlbmQgbW9kZWxsaW5nIGFuZCBkZWNyZWFzZXMgdGhlIHNldmVyaXR5IG9mIHRoZSBtdWx0aXBsZSB0ZXN0aW5nIGNvcnJlY3Rpb24uIEdlbmVzIGFyZSBkaXNjYXJkZWQgaWYgdGhleSBhcmUgbm90IGV4cHJlc3NlZCBhYm92ZSBhIGxvZy1DUE0gdGhyZXNob2xkIGluIGEgbWluaW11bSBudW1iZXIgb2Ygc2FtcGxlcyAoZGV0ZXJtaW5lZCBmcm9tIHRoZSBzaXplIG9mIHRoZSBzbWFsbGVzdCB0cmVhdG1lbnQgZ3JvdXAgaW4gdGhlIGV4cGVyaW1lbnRhbCBkZXNpZ24pLgoKYGBge3J9CmtlZXAgPC0gZmlsdGVyQnlFeHByKHksIGdyb3VwPWN1cnJlbnQkc291cmNlX25hbWUpCnkgPC0geVtrZWVwLF0Kc3VtbWFyeShrZWVwKQpgYGAKCkZpbmFsbHksIHdlIGNvcnJlY3QgZm9yIGNvbXBvc2l0aW9uIGJpYXNlcyBieSBjb21wdXRpbmcgbm9ybWFsaXphdGlvbiBmYWN0b3JzIHdpdGggdGhlIHRyaW1tZWQgbWVhbiBvZiBNLXZhbHVlcyBtZXRob2QgKFJvYmluc29uIGFuZCBPc2hsYWNrIDIwMTApLiBXZSBkbyBub3QgbmVlZCB0aGUgYmVzcG9rZSBzaW5nbGUtY2VsbCBtZXRob2RzIGRlc2NyaWJlZCBpbiBDaGFwdGVyIDcsIGFzIHRoZSBjb3VudHMgZm9yIG91ciBwc2V1ZG8tYnVsayBzYW1wbGVzIGFyZSBsYXJnZSBlbm91Z2ggdG8gYXBwbHkgYnVsayBub3JtYWxpemF0aW9uIG1ldGhvZHMuIChSZWFkZXJzIHNob3VsZCBiZSBhd2FyZSB0aGF0IGVkZ2VSIG5vcm1hbGl6YXRpb24gZmFjdG9ycyBhcmUgY2xvc2VseSByZWxhdGVkIGJ1dCBub3QgdGhlIHNhbWUgYXMgdGhlIHNpemUgZmFjdG9ycyBkZXNjcmliZWQgZWxzZXdoZXJlIGluIHRoaXMgYm9vay4pCgpgYGB7cn0KeSA8LSBjYWxjTm9ybUZhY3RvcnMoeSkKeSRzYW1wbGVzCmBgYAoKIyMjIyBTdGF0aXN0aWNhbCBtb2RlbGxpbmcKCk91ciBhaW0gaXMgdG8gdGVzdCB3aGV0aGVyIHRoZSBsb2ctZm9sZCBjaGFuZ2UgYmV0d2VlbiBzYW1wbGUgZ3JvdXBzIGlzIHNpZ25pZmljYW50bHkgZGlmZmVyZW50IGZyb20gemVyby4KCmBgYHtyfQpkZXNpZ24gPC0gbW9kZWwubWF0cml4KH5mYWN0b3Ioc291cmNlX25hbWUpLCB5JHNhbXBsZXMpCmRlc2lnbgpgYGAKCldlIGVzdGltYXRlIHRoZSBuZWdhdGl2ZSBiaW5vbWlhbCAoTkIpIGRpc3BlcnNpb25zIHdpdGggZXN0aW1hdGVEaXNwKCkuIFRoZSByb2xlIG9mIHRoZSBOQiBkaXNwZXJzaW9uIGlzIHRvIG1vZGVsIHRoZSBtZWFuLXZhcmlhbmNlIHRyZW5kLCB3aGljaCBpcyBub3QgZWFzaWx5IGFjY29tbW9kYXRlZCBieSBRTCBkaXNwZXJzaW9ucyBhbG9uZSBkdWUgdG8gdGhlIHF1YWRyYXRpYyBuYXR1cmUgb2YgdGhlIE5CIG1lYW4tdmFyaWFuY2UgdHJlbmQuCgpgYGB7cn0KeSA8LSBlc3RpbWF0ZURpc3AoeSwgZGVzaWduKQpzdW1tYXJ5KHkkdHJlbmRlZC5kaXNwZXJzaW9uKQpgYGAKCkJpb2xvZ2ljYWwgY29lZmZpY2llbnQgb2YgdmFyaWF0aW9uIChCQ1YpIGZvciBlYWNoIGdlbmUgYXMgYSBmdW5jdGlvbiBvZiB0aGUgYXZlcmFnZSBhYnVuZGFuY2UuIFRoZSBCQ1YgaXMgY29tcHV0ZWQgYXMgdGhlIHNxdWFyZSByb290IG9mIHRoZSBOQiBkaXNwZXJzaW9uIGFmdGVyIGVtcGlyaWNhbCBCYXllcyBzaHJpbmthZ2UgdG93YXJkcyB0aGUgdHJlbmQuIFRyZW5kZWQgYW5kIGNvbW1vbiBCQ1YgZXN0aW1hdGVzIGFyZSBzaG93biBpbiBibHVlIGFuZCByZWQsIHJlc3BlY3RpdmVseS4gCgpgYGB7cn0KcGxvdEJDVih5KQpgYGAKCldlIGFsc28gZXN0aW1hdGUgdGhlIHF1YXNpLWxpa2VsaWhvb2QgZGlzcGVyc2lvbnMgd2l0aCBnbG1RTEZpdCgpIChDaGVuLCBMdW4sIGFuZCBTbXl0aCAyMDE2KS4gVGhpcyBmaXRzIGEgR0xNIHRvIHRoZSBjb3VudHMgZm9yIGVhY2ggZ2VuZSBhbmQgZXN0aW1hdGVzIHRoZSBRTCBkaXNwZXJzaW9uIGZyb20gdGhlIEdMTSBkZXZpYW5jZS4gV2Ugc2V0IHJvYnVzdD1UUlVFIHRvIGF2b2lkIGRpc3RvcnRpb25zIGZyb20gaGlnaGx5IHZhcmlhYmxlIGNsdXN0ZXJzIChQaGlwc29uIGV0IGFsLiAyMDE2KS4gVGhlIFFMIGRpc3BlcnNpb24gbW9kZWxzIHRoZSB1bmNlcnRhaW50eSBhbmQgdmFyaWFiaWxpdHkgb2YgdGhlIHBlci1nZW5lIHZhcmlhbmNlIC0gd2hpY2ggaXMgbm90IHdlbGwgaGFuZGxlZCBieSB0aGUgTkIgZGlzcGVyc2lvbnMsIHNvIHRoZSB0d28gZGlzcGVyc2lvbiB0eXBlcyBjb21wbGVtZW50IGVhY2ggb3RoZXIgaW4gdGhlIGZpbmFsIGFuYWx5c2lzLgoKYGBge3J9CmZpdCA8LSBnbG1RTEZpdCh5LCBkZXNpZ24sIHJvYnVzdD1UUlVFKQpzdW1tYXJ5KGZpdCR2YXIucHJpb3IpCmBgYAoKYGBge3J9CnN1bW1hcnkoZml0JGRmLnByaW9yKQpgYGAKClFMIGRpc3BlcnNpb24gZXN0aW1hdGVzIGZvciBlYWNoIGdlbmUgYXMgYSBmdW5jdGlvbiBvZiBhYnVuZGFuY2UuIFJhdyBlc3RpbWF0ZXMgKGJsYWNrKSBhcmUgc2hydW5rIHRvd2FyZHMgdGhlIHRyZW5kIChibHVlKSB0byB5aWVsZCBzcXVlZXplZCBlc3RpbWF0ZXMgKHJlZCkuCgpgYGB7cn0KcGxvdFFMRGlzcChmaXQpCmBgYAoKV2UgdGVzdCBmb3IgZGlmZmVyZW5jZXMgaW4gZXhwcmVzc2lvbiBkdWUgdG8gc2FtcGxlIGdyb3VwIHVzaW5nIGdsbVFMRlRlc3QoKS4gREVHcyBhcmUgZGVmaW5lZCBhcyB0aG9zZSB3aXRoIG5vbi16ZXJvIGxvZy1mb2xkIGNoYW5nZXMgYXQgYSBmYWxzZSBkaXNjb3ZlcnkgcmF0ZSBvZiA1JS4gSWYgdmVyeSBmZXcgZ2VuZXMgYXJlIHNpZ25pZmljYW50bHkgREUgdGhhdCBzYW1wbGUgZ3JvdXAgaGFzIGxpdHRsZSBlZmZlY3Qgb24gdGhlIHRyYW5zY3JpcHRvbWUuCgpgYGB7cn0KcmVzIDwtIGdsbVFMRlRlc3QoZml0LCBjb2VmPW5jb2woZGVzaWduKSkKc3VtbWFyeShkZWNpZGVUZXN0cyhyZXMpKQpgYGAKCmBgYHtyfQp0b3BUYWIgPC0gdG9wVGFncyhyZXMpJHRhYmxlCnRtcEFubm90IDwtIHJvd0RhdGEoY3VycmVudClbLGMoImVuc2VtYmxfZ2VuZV9pZCIsIlN5bWJvbCIpXSAlPiUgZGF0YS5mcmFtZQp0b3BUYWIgJT4lIHRpYmJsZTo6cm93bmFtZXNfdG9fY29sdW1uKCJlbnNlbWJsX2dlbmVfaWQiKSAlPiUKCWxlZnRfam9pbih0bXBBbm5vdCwgYnk9ImVuc2VtYmxfZ2VuZV9pZCIpCmBgYAoKIyMjIyBEaWZmZXJlbnRpYWwgZXhwcmVzc2lvbiBmb3IgZWFjaCBjbHVzdGVyCgpUaGUgc3RlcHMgaWxsdXN0cmF0ZWQgYWJvdmUgd2l0aCBjbHVzdGVyIDAgYXJlIG5vdyByZXBlYXRlZCBmb3IgZWFjaCBjbHVzdGVyOgoKKiBTdWJzZXQgcHNldWRvLWJ1bGsgY291bnRzIGZvciB0aGF0IGNsdXN0ZXIKKiBDcmVhdGUgZWRnZVIgb2JqZWN0IHdpdGggdGhlc2UgcHNldWRvLWJ1bGsgY291bnRzCiogUHJlLXByb2Nlc3MKICAgICogUmVtb3ZlIHNhbXBsZXMgd2l0aCB2ZXJ5IHNtYWxsIGxpYnJhcnkgc2l6ZQogICAgKiBSZW1vdmUgZ2VuZXMgd2l0aCBsb3cgVU1JIGNvdW50cwogICAgKiBDb3JyZWN0IGZvciBjb21wb3NpdGlvbmFsIGJpYXMKKiBQZXJmb3JtIGRpZmZlcmVudGlhbCBleHByZXNzaW9uIGFuYWx5c2lzICAKICAgICogRXN0aW1hdGUgbmVnYXRpdmUgYmlub21pYWwgZGlzcGVyc2lvbgogICAgKiBFc3RpbWF0ZSBxdWFzaS1saWtlbGlob29kIGRpc3BlcnNpb24KICAgICogVGVzdCBmb3IgZGlmZmVyZW50aWFsIGV4cHJlc3Npb24gCgpgYGB7cn0KZGUucmVzdWx0cyA8LSBsaXN0KCkKZm9yIChsYWJlbFRvR2V0IGluIGxldmVscyhzdW1tZWQkbGFiZWwpKSB7CgoJY3VycmVudCA8LSBzdW1tZWRbLHN1bW1lZCRsYWJlbD09bGFiZWxUb0dldF0KCiAgICB5IDwtIERHRUxpc3QoY291bnRzKGN1cnJlbnQpLCBzYW1wbGVzPWNvbERhdGEoY3VycmVudCkpCgogICAgZGlzY2FyZGVkIDwtIGlzT3V0bGllcihjb2xTdW1zKGNvdW50cyhjdXJyZW50KSksIGxvZz1UUlVFLCB0eXBlPSJsb3dlciIpCiAgICB5IDwtIHlbLCFkaXNjYXJkZWRdCiAgICB5IDwtIHlbZmlsdGVyQnlFeHByKHksIGdyb3VwPWN1cnJlbnQkc291cmNlX25hbWUpLF0KICAgIHkgPC0gY2FsY05vcm1GYWN0b3JzKHkpCgogICAgZGVzaWduIDwtIHRyeSgKICAgICAgICBtb2RlbC5tYXRyaXgofmZhY3Rvcihzb3VyY2VfbmFtZSksIHkkc2FtcGxlcyksCiAgICAgICAgc2lsZW50PVRSVUUKICAgICkKICAgIGlmIChpcyhkZXNpZ24sICJ0cnktZXJyb3IiKSB8fCAKICAgICAgICBxcihkZXNpZ24pJHJhbms9PW5yb3coZGVzaWduKSB8fAogICAgICAgIHFyKGRlc2lnbikkcmFuayA8IG5jb2woZGVzaWduKSkgCiAgICB7CiAgICAgICAgIyBTa2lwcGluZyBsYWJlbHMgd2l0aG91dCBjb250cmFzdHMgb3Igd2l0aG91dCAKICAgICAgICAjIGVub3VnaCByZXNpZHVhbCBkLmYuIHRvIGVzdGltYXRlIHRoZSBkaXNwZXJzaW9uLgogICAgICAgIG5leHQKICAgIH0KCiAgICB5IDwtIGVzdGltYXRlRGlzcCh5LCBkZXNpZ24pCiAgICBmaXQgPC0gZ2xtUUxGaXQoeSwgZGVzaWduKQogICAgcmVzIDwtIGdsbVFMRlRlc3QoZml0LCBjb2VmPW5jb2woZGVzaWduKSkKICAgIGRlLnJlc3VsdHNbW2xhYmVsVG9HZXRdXSA8LSByZXMKfQpgYGAKCiMjIyMjIE51bWJlciBvZiBERUdzIGJ5IGNsdXN0ZXIgYW5kIGRpcmVjdGlvbgoKV2UgZXhhbWluZSB0aGUgbnVtYmVycyBvZiBERUdzIGF0IGEgRkRSIG9mIDUlIGZvciBlYWNoIGxhYmVsIChpLmUuIGNsdXN0ZXIpLiBJbiBnZW5lcmFsLCB0aGVyZSBzZWVtcyB0byBiZSB2ZXJ5IGxpdHRsZSBkaWZmZXJlbnRpYWwgZXhwcmVzc2lvbiBiZXR3ZWVuIHRoZSBvbiBhbmQgb2ZmIGNvbmRpdGlvbnMuCgpgYGB7cn0Kc3VtbWFyaWVzIDwtIGxhcHBseShkZS5yZXN1bHRzLCBGVU49ZnVuY3Rpb24oeCkgc3VtbWFyeShkZWNpZGVUZXN0cyh4KSlbLDFdKQpzdW0udGFiIDwtIGRvLmNhbGwocmJpbmQsIHN1bW1hcmllcykKI3N1bS50YWIKc3VtLnRhYltvcmRlcihyb3duYW1lcyhzdW0udGFiKSksXSAlPiUKCWFzLmRhdGEuZnJhbWUoKSAlPiUKCXRpYmJsZTo6cm93bmFtZXNfdG9fY29sdW1uKCJDbHVzdGVyIikgJT4lCglkYXRhdGFibGUocm93bmFtZXMgPSBGQUxTRSwgb3B0aW9ucyA9IGxpc3QocGFnZUxlbmd0aCA9IDIwLCBzY3JvbGxYID0gVFJVRSkpCmBgYAoKIyMjIyMgTGlzdCBvZiBERUdzCgpXZSBub3cgbGlzdCBERUdzIGFuZCB0aGUgbnVtYmVyIG9mIGNsdXN0ZXJzIHRoZXkgd2VyZSBkZXRlY3RlZCBpbjoKCmBgYHtyfQpkZWdzIDwtIGxhcHBseShkZS5yZXN1bHRzLCBGVU49ZnVuY3Rpb24oeCkgcm93bmFtZXModG9wVGFncyh4LCBwLnZhbHVlPTAuMDUpKSkKY29tbW9uLmRlZ3MgPC0gc29ydCh0YWJsZSh1bmxpc3QoZGVncykpLCBkZWNyZWFzaW5nPVRSVUUpCiNoZWFkKGNvbW1vbi5kZWdzLCAyMCkKY29tbW9uLmRlZ3MgJT4lCglhcy5kYXRhLmZyYW1lICU+JSAKCWRwbHlyOjpyZW5hbWUoR2VuZSA9IFZhcjEsIE5iQ2x1ID0gRnJlcSkgJT4lCglkYXRhdGFibGUocm93bmFtZXMgPSBGQUxTRSwgb3B0aW9ucyA9IGxpc3QocGFnZUxlbmd0aCA9IDIwLCBzY3JvbGxYID0gVFJVRSkpCmBgYAoKIyMjIyMgTnVtYmVyIG9mIGNsdXN0ZXJzIHNraXBwZWQKCiJXZSBhbHNvIGxpc3QgdGhlIGxhYmVscyB0aGF0IHdlcmUgc2tpcHBlZCBkdWUgdG8gdGhlIGFic2VuY2Ugb2YgcmVwbGljYXRlcyBvciBjb250cmFzdHMuIElmIGl0IGlzIG5lY2Vzc2FyeSB0byBleHRyYWN0IHN0YXRpc3RpY3MgaW4gdGhlIGFic2VuY2Ugb2YgcmVwbGljYXRlcywgc2V2ZXJhbCBzdHJhdGVnaWVzIGNhbiBiZSBhcHBsaWVkIHN1Y2ggYXMgcmVkdWNpbmcgdGhlIGNvbXBsZXhpdHkgb2YgdGhlIG1vZGVsIG9yIHVzaW5nIGEgcHJlZGVmaW5lZCB2YWx1ZSBmb3IgdGhlIE5CIGRpc3BlcnNpb24uIFdlIHJlZmVyIHJlYWRlcnMgdG8gdGhlIGVkZ2VSIHVzZXLigJlzIGd1aWRlIGZvciBtb3JlIGRldGFpbHMuIgoKYGBge3J9CnNraXBwZWRDbHVzdGVycyA8LSBzZXRkaWZmKHVuaXF1ZShzdW1tZWQkbGFiZWwpLCBuYW1lcyhzdW1tYXJpZXMpKQpgYGAKClRoZSBudW1iZXIgb2YgY2x1c3RlcnMgc2tpcHBlZCBpcyBgciBsZW5ndGgoc2tpcHBlZENsdXN0ZXJzKWAuCgpgYGB7cn0KaWYobGVuZ3RoKHNraXBwZWRDbHVzdGVycyk+MCkKewogIHNraXBwZWRDbHVzdGVycwp9CmBgYAoKYGBge3J9CmdybVRvU2hvd0xpc3QgPC0gdmVjdG9yKCJsaXN0IiwgbGVuZ3RoID0gbmxldmVscyhtZXJnZWQkY2x1c3RlcnMubW5uKSkKbmFtZXMoZ3JtVG9TaG93TGlzdCkgPC0gbGV2ZWxzKG1lcmdlZCRjbHVzdGVycy5tbm4pCmdlbmVzVG9FeGNsdWRlIDwtIGMoKQpuYkdlbmVUb1Nob3cgPC0gMjAKCiNkZWdzIDwtIGxhcHBseShkZS5yZXN1bHRzLCBGVU49ZnVuY3Rpb24oeCkgKHRvcFRhZ3MoeCwgcC52YWx1ZT0wLjA1KSkpCmRlZ3MgPC0gbGFwcGx5KGRlLnJlc3VsdHMsIEZVTj1mdW5jdGlvbih4KSAoYXMuZGF0YS5mcmFtZSh0b3BUYWdzKHgsIG49bmJHZW5lVG9TaG93KSkpKQoKZm9yKCBuYW1leCBpbiBsZXZlbHMobWVyZ2VkJGNsdXN0ZXJzLm1ubikgKQp7CgluYkdlbmVUb1VzZSA8LSBtaW4oYyhucm93KGRlZ3NbW25hbWV4XV0pLCBuYkdlbmVUb1Nob3cpKQoKCSMgZm9ybWF0CgoJIyBmb3JtYXQgcCB2YWx1ZToKCXRtcENvbCA8LSBncmVwKCJQVmFsdWV8RkRSIiwgY29sbmFtZXMoZGVnc1tbbmFtZXhdXSksIHZhbHVlPVRSVUUpCglkZWdzW1tuYW1leF1dWyx0bXBDb2xdIDwtIGFwcGx5KGRlZ3NbW25hbWV4XV1bLHRtcENvbF0sCgkJCQkJICAgICAyLAoJCQkJCSAgICAgZnVuY3Rpb24oeCl7Zm9ybWF0KHgsIHNjaWVudGlmaWMgPSBUUlVFLCBkaWdpdHMgPSAxKX0pCgkjIGZvcm1hdCBsb2dGQzoKCXRtcENvbCA8LSBjKCJsb2dGQyIsICJsb2dDUE0iLCAiRiIpCglkZWdzW1tuYW1leF1dWyx0bXBDb2xdIDwtIGFwcGx5KGRlZ3NbW25hbWV4XV1bLHRtcENvbF0sIDIsICBmdW5jdGlvbih4KXtyb3VuZCh4LCAyKX0pCglybSh0bXBDb2wpCgoJIyBzdWJzZXQgZGF0YQoJZ3JtVG9TaG93IDwtIGRlZ3NbW25hbWV4XV0gJT4lCgkJYXMuZGF0YS5mcmFtZSgpICU+JQoJCXRpYmJsZTo6cm93bmFtZXNfdG9fY29sdW1uKCJnZW5lIikgJT4lCQoJCWFycmFuZ2UoRkRSLCBkZXNjKGFicyhsb2dGQykpKSAlPiUKCQlmaWx0ZXIoISBnZW5lICVpbiUgZ2VuZXNUb0V4Y2x1ZGUpICU+JQoJCWdyb3VwX21vZGlmeSh+IGhlYWQoLngsIG5iR2VuZVRvVXNlKSkgCgkjIGtlZXAgZGF0YQoJZ3JtVG9TaG93JGNsdXN0ZXIgPC0gbmFtZXgKCWdybVRvU2hvd0xpc3RbW25hbWV4XV0gPC0gZ3JtVG9TaG93CgkjIHRpZHkKCXJtKG5iR2VuZVRvVXNlKQp9CmdybVRvU2hvd0RmIDwtIGRvLmNhbGwoInJiaW5kIiwgZ3JtVG9TaG93TGlzdCkKdG1wQ29sIDwtIGMoImNsdXN0ZXIiLCAiZ2VuZSIpCmdybVRvU2hvd0RmICU+JQoJc2VsZWN0KHRtcENvbCwgc2V0ZGlmZihjb2xuYW1lcyhncm1Ub1Nob3dEZiksIHRtcENvbCkpICU+JQoJZmlsdGVyKGdlbmUgJWluJSBuYW1lcyhjb21tb24uZGVncykgJiBhcy5udW1lcmljKEZEUikgPCAwLjA1KSAlPiUKCWRhdGF0YWJsZShyb3duYW1lcyA9IEZBTFNFLCBmaWx0ZXI9InRvcCIsIG9wdGlvbnM9bGlzdChzY3JvbGxYID0gVFJVRSwgcGFnZUxlbmd0aCA9IDE1KSkKCnRtcEJvb2wgPC0gYXMubnVtZXJpYyhncm1Ub1Nob3dEZiRGRFIpIDwgMC4wNSAKbWFya2Vycy50by5wbG90IDwtIHVuaXF1ZShncm1Ub1Nob3dEZlt0bXBCb29sLCAiZ2VuZSJdKQptYXJrZXJzLnRvLnBsb3QgPC0gbWFya2Vycy50by5wbG90WzE6NV0KYGBgCgojIyPCoFB1dHRpbmcgaXQgYWxsIHRvZ2V0aGVyCgpOb3cgdGhhdCB3ZSBoYXZlIGxhaWQgb3V0IHRoZSB0aGVvcnkgdW5kZXJseWluZyB0aGUgREUgYW5hbHlzaXMsIHdlIHJlcGVhdCB0aGlzIHByb2Nlc3MgZm9yIGVhY2ggb2YgdGhlIGxhYmVscy4gVGhpcyBpcyBjb252ZW5pZW50bHkgZG9uZSB1c2luZyB0aGUgcHNldWRvQnVsa0RHRSgpIGZ1bmN0aW9uIGZyb20gc2NyYW4sIHdoaWNoIHdpbGwgbG9vcCBvdmVyIGFsbCBsYWJlbHMgYW5kIGFwcGx5IHRoZSBleGFjdCBhbmFseXNpcyBkZXNjcmliZWQgYWJvdmUgdG8gZWFjaCBsYWJlbC4gVG8gcHJlcGFyZSBmb3IgdGhpcywgd2UgZmlsdGVyIG91dCBhbGwgc2FtcGxlLWxhYmVsIGNvbWJpbmF0aW9ucyB3aXRoIGluc3VmZmljaWVudCBjZWxscy4KCmBgYHtyfQpzdW1tZWQuZmlsdCA8LSBzdW1tZWRbLHN1bW1lZCRuY2VsbHMgPj0gMjBdCmBgYAoKV2UgY29uc3RydWN0IGEgY29tbW9uIGRlc2lnbiBtYXRyaXggdGhhdCB3aWxsIGJlIHVzZWQgaW4gdGhlIGFuYWx5c2lzIGZvciBlYWNoIGxhYmVsLiBSZWNhbGwgdGhhdCB0aGlzIG1hdHJpeCBzaG91bGQgaGF2ZSBvbmUgcm93IHBlciB1bmlxdWUgc2FtcGxlIChhbmQgbmFtZWQgYXMgc3VjaCksIHJlZmxlY3RpbmcgdGhlIGZhY3QgdGhhdCB3ZSBhcmUgbW9kZWxsaW5nIGNvdW50cyBvbiB0aGUgc2FtcGxlIGxldmVsIGluc3RlYWQgb2YgdGhlIGNlbGwgbGV2ZWwuCgpgYGB7cn0KIyBQdWxsaW5nIG91dCBhIHNhbXBsZS1sZXZlbCAndGFyZ2V0cycgZGF0YS5mcmFtZToKdGFyZ2V0cyA8LSBjb2xEYXRhKG1lcmdlZClbIWR1cGxpY2F0ZWQobWVyZ2VkJFNhbXBsZS5OYW1lMiksXQoKIyBDb25zdHJ1Y3RpbmcgdGhlIGRlc2lnbiBtYXRyaXg6CmRlc2lnbiA8LSBtb2RlbC5tYXRyaXgofmZhY3Rvcihzb3VyY2VfbmFtZSksIGRhdGE9dGFyZ2V0cykKcm93bmFtZXMoZGVzaWduKSA8LSB0YXJnZXRzJFNhbXBsZS5OYW1lMgpgYGAKCldlIHRoZW4gYXBwbHkgdGhlIHBzZXVkb0J1bGtER0UoKSBmdW5jdGlvbiB0byBvYnRhaW4gYSBsaXN0IG9mIERFIGdlbmVzIGZvciBlYWNoIGxhYmVsLiBUaGlzIGZ1bmN0aW9uIHB1dHMgc29tZSBhZGRpdGlvbmFsIGVmZm9ydCBpbnRvIGF1dG9tYXRpY2FsbHkgZGVhbGluZyB3aXRoIGxhYmVscyB0aGF0IGFyZSBub3QgcmVwcmVzZW50ZWQgaW4gYWxsIHNhbXBsZSBncm91cHMsIGZvciB3aGljaCBhIERFIGFuYWx5c2lzIGJldHdlZW4gY29uZGl0aW9ucyBpcyBtZWFuaW5nbGVzczsgb3IgYXJlIG5vdCByZXByZXNlbnRlZCBpbiBhIHN1ZmZpY2llbnQgbnVtYmVyIG9mIHJlcGxpY2F0ZSBzYW1wbGVzIHRvIGVuYWJsZSBtb2RlbGxpbmcgb2YgYmlvbG9naWNhbCB2YXJpYWJpbGl0eS4KCmBgYHtyfQpsaWJyYXJ5KHNjcmFuKQpkZS5yZXN1bHRzIDwtIHBzZXVkb0J1bGtER0Uoc3VtbWVkLmZpbHQsIAogICAgc2FtcGxlPXN1bW1lZC5maWx0JFNhbXBsZS5OYW1lMiwKICAgIGxhYmVsPXN1bW1lZC5maWx0JGxhYmVsLAogICAgZGVzaWduPWRlc2lnbiwKICAgIGNvZWY9bmNvbChkZXNpZ24pLAoKICAgICMgJ2NvbmRpdGlvbicgc2V0cyB0aGUgZ3JvdXAgc2l6ZSBmb3IgZmlsdGVyQnlFeHByKCksCiAgICAjIHRvIHBlcmZlY3RseSBtaW1pYyBvdXIgcHJldmlvdXMgbWFudWFsIGFuYWx5c2lzLgogICAgY29uZGl0aW9uPXRhcmdldHMkc291cmNlX25hbWUgCikKYGBgCgpXZSBleGFtaW5lIHRoZSBudW1iZXJzIG9mIERFR3MgYXQgYSBGRFIgb2YgNSUgZm9yIGVhY2ggbGFiZWwgdXNpbmcgdGhlIGRlY2lkZVRlc3RzUGVyTGFiZWwoKSBmdW5jdGlvbi4gTm90ZSB0aGF0IGdlbmVzIGxpc3RlZCBhcyBOQSB3ZXJlIGVpdGhlciBmaWx0ZXJlZCBvdXQgYXMgbG93LWFidW5kYW5jZSBnZW5lcyBmb3IgYSBnaXZlbiBsYWJlbOKAmXMgYW5hbHlzaXMsIG9yIHRoZSBjb21wYXJpc29uIG9mIGludGVyZXN0IHdhcyBub3QgcG9zc2libGUgZm9yIGEgcGFydGljdWxhciBsYWJlbCwgZS5nLiwgZHVlIHRvIGxhY2sgb2YgcmVzaWR1YWwgZGVncmVlcyBvZiBmcmVlZG9tIG9yIGFuIGFic2VuY2Ugb2Ygc2FtcGxlcyBmcm9tIGJvdGggY29uZGl0aW9ucy4KCmBgYHtyfQppcy5kZSA8LSBkZWNpZGVUZXN0c1BlckxhYmVsKGRlLnJlc3VsdHMsIHRocmVzaG9sZD0wLjA1KQpzdW1tYXJpemVUZXN0c1BlckxhYmVsKGlzLmRlKQpgYGAKCkZvciBlYWNoIGdlbmUsIHdlIGNvbXB1dGUgdGhlIHBlcmNlbnRhZ2Ugb2YgY2VsbCB0eXBlcyBpbiB3aGljaCB0aGF0IGdlbmUgaXMgdXByZWd1bGF0ZWQgb3IgZG93bnJlZ3VsYXRlZC4gKEhlcmUsIHdlIGNvbnNpZGVyIGEgZ2VuZSB0byBiZSBub24tREUgaWYgaXQgaXMgbm90IHJldGFpbmVkIGFmdGVyIGZpbHRlcmluZy4pLgoKYGBge3J9CiMgVXByZWd1bGF0ZWQgYWNyb3NzIG1vc3QgY2VsbCB0eXBlcy4KdXAuZGUgPC0gaXMuZGUgPiAwICYgIWlzLm5hKGlzLmRlKQpoZWFkKHNvcnQocm93TWVhbnModXAuZGUpLCBkZWNyZWFzaW5nPVRSVUUpLCAxMCkKYGBgCgpgYGB7cn0KIyBEb3ducmVndWxhdGVkIGFjcm9zcyBjZWxsIHR5cGVzLgpkb3duLmRlIDwtIGlzLmRlIDwgMCAmICFpcy5uYShpcy5kZSkKaGVhZChzb3J0KHJvd01lYW5zKGRvd24uZGUpLCBkZWNyZWFzaW5nPVRSVUUpLCAxMCkKYGBgCgpXZSBmdXJ0aGVyIGlkZW50aWZ5IGxhYmVsLXNwZWNpZmljIERFIGdlbmVzIHRoYXQgYXJlIHNpZ25pZmljYW50IGluIG91ciBsYWJlbCBvZiBpbnRlcmVzdCB5ZXQgbm90IERFIGluIGFueSBvdGhlciBsYWJlbC4gQXMgaHlwb3RoZXNpcyB0ZXN0cyBhcmUgbm90IHR5cGljYWxseSBnZWFyZWQgdG93YXJkcyBpZGVudGlmeWluZyBnZW5lcyB0aGF0IGFyZSBub3QgREUsIHdlIHVzZSBhbiBhZCBob2MgYXBwcm9hY2ggd2hlcmUgd2UgY29uc2lkZXIgYSBnZW5lIHRvIGJlIGNvbnNpc3RlbnQgd2l0aCB0aGUgbnVsbCBoeXBvdGhlc2lzIGZvciBhIGxhYmVsIGlmIGl0IGZhaWxzIHRvIGJlIGRldGVjdGVkIGV2ZW4gYXQgYSBnZW5lcm91cyBGRFIgdGhyZXNob2xkIG9mIDUwJS4KCmBgYHtyfQpyZW1vdGVseS5kZSA8LSBkZWNpZGVUZXN0c1BlckxhYmVsKGRlLnJlc3VsdHMsIHRocmVzaG9sZD0wLjUpCm5vdC5kZSA8LSByZW1vdGVseS5kZT09MCB8IGlzLm5hKHJlbW90ZWx5LmRlKQoKb3RoZXIubGFiZWxzIDwtIHNldGRpZmYoY29sbmFtZXMobm90LmRlKSwgImMyIikKdW5pcXVlLmRlZ3MgPC0gaXMuZGVbLCJjMiJdIT0wICYgcm93TWVhbnMobm90LmRlWyxvdGhlci5sYWJlbHNdKT09MQp1bmlxdWUuZGVncyA8LSBuYW1lcyh3aGljaCh1bmlxdWUuZGVncykpCgpvdGhlci5sYWJlbHMgPC0gc2V0ZGlmZihjb2xuYW1lcyhub3QuZGUpLCAiYzQiKQp1bmlxdWUuZGVncyA8LSBpcy5kZVssImM0Il0hPTAgJiByb3dNZWFucyhub3QuZGVbLG90aGVyLmxhYmVsc10pPT0xCnVuaXF1ZS5kZWdzIDwtIG5hbWVzKHdoaWNoKHVuaXF1ZS5kZWdzKSkKYGBgCgpgYGB7cn0KIyBDaG9vc2luZyB0aGUgdG9wLXJhbmtlZCBnZW5lIGZvciBpbnNwZWN0aW9uOgpkZS5jNCA8LSBkZS5yZXN1bHRzJGM0CmRlLmM0IDwtIGRlLmM0W29yZGVyKGRlLmM0JFBWYWx1ZSksXQpkZS5jNCA8LSBkZS5jNFtyb3duYW1lcyhkZS5jNCkgJWluJSB1bmlxdWUuZGVncyxdCgpzaXplRmFjdG9ycyhzdW1tZWQuZmlsdCkgPC0gTlVMTApwbG90RXhwcmVzc2lvbihsb2dOb3JtQ291bnRzKHN1bW1lZC5maWx0KSwgCiAgICBmZWF0dXJlcz1yb3duYW1lcyhkZS5jNClbMV0sCiAgICB4PSJzb3VyY2VfbmFtZSIsIGNvbG91cl9ieT0ic291cmNlX25hbWUiLCAKICAgIG90aGVyX2ZpZWxkcz0ibGFiZWwiKSArIAogICAgZmFjZXRfd3JhcCh+bGFiZWwpCmBgYAoKV2UgYWxzbyBsaXN0IHRoZSBsYWJlbHMgdGhhdCB3ZXJlIHNraXBwZWQgZHVlIHRvIHRoZSBhYnNlbmNlIG9mIHJlcGxpY2F0ZXMgb3IgY29udHJhc3RzLiBJZiBpdCBpcyBuZWNlc3NhcnkgdG8gZXh0cmFjdCBzdGF0aXN0aWNzIGluIHRoZSBhYnNlbmNlIG9mIHJlcGxpY2F0ZXMsIHNldmVyYWwgc3RyYXRlZ2llcyBjYW4gYmUgYXBwbGllZCBzdWNoIGFzIHJlZHVjaW5nIHRoZSBjb21wbGV4aXR5IG9mIHRoZSBtb2RlbCBvciB1c2luZyBhIHByZWRlZmluZWQgdmFsdWUgZm9yIHRoZSBOQiBkaXNwZXJzaW9uLiBXZSByZWZlciByZWFkZXJzIHRvIHRoZSBlZGdlUiB1c2Vy4oCZcyBndWlkZSBmb3IgbW9yZSBkZXRhaWxzLgoKYGBge3J9CnByaW50KG1ldGFkYXRhKGRlLnJlc3VsdHMpJGZhaWxlZCkKYGBgCgojI8KgRGlmZmVyZW50aWFsIGFidW5kYW5jZSBiZXR3ZWVuIGNvbmRpdGlvbnMKCiMjIyBPdmVydmlldwoKbiBhIERBIGFuYWx5c2lzLCB3ZSB0ZXN0IGZvciBzaWduaWZpY2FudCBjaGFuZ2VzIGluIHBlci1sYWJlbCBjZWxsIGFidW5kYW5jZSBhY3Jvc3MgY29uZGl0aW9ucy4gVGhpcyB3aWxsIHJldmVhbCB3aGljaCBjZWxsIHR5cGVzIGFyZSBkZXBsZXRlZCBvciBlbnJpY2hlZCB1cG9uIHRyZWF0bWVudCwgd2hpY2ggaXMgYXJndWFibHkganVzdCBhcyBpbnRlcmVzdGluZyBhcyBjaGFuZ2VzIGluIGV4cHJlc3Npb24gd2l0aGluIGVhY2ggY2VsbCB0eXBlLiBUaGUgREEgYW5hbHlzaXMgaGFzIGEgbG9uZyBoaXN0b3J5IGluIGZsb3cgY3l0b21ldHJ5IChGaW5hayBldCBhbC4gMjAxNDsgTHVuLCBSaWNoYXJkLCBhbmQgTWFyaW9uaSAyMDE3KSB3aGVyZSBpdCBpcyByb3V0aW5lbHkgdXNlZCB0byBleGFtaW5lIHRoZSBlZmZlY3RzIG9mIGRpZmZlcmVudCBjb25kaXRpb25zIG9uIHRoZSBjb21wb3NpdGlvbiBvZiBjb21wbGV4IGNlbGwgcG9wdWxhdGlvbnMuIEJ5IHBlcmZvcm1pbmcgaXQgaGVyZSwgd2UgZWZmZWN0aXZlbHkgdHJlYXQgc2NSTkEtc2VxIGFzIGEg4oCcc3VwZXItRkFDU+KAnSB0ZWNobm9sb2d5IGZvciBkZWZpbmluZyByZWxldmFudCBzdWJwb3B1bGF0aW9ucyB1c2luZyB0aGUgZW50aXJlIHRyYW5zY3JpcHRvbWUuCgpXZSBwcmVwYXJlIGZvciB0aGUgREEgYW5hbHlzaXMgYnkgcXVhbnRpZnlpbmcgdGhlIG51bWJlciBvZiBjZWxscyBhc3NpZ25lZCB0byBlYWNoIGxhYmVsIChvciBjbHVzdGVyKS4KCmBgYHtyfQphYnVuZGFuY2VzIDwtIHRhYmxlKG1lcmdlZCRjbHVzdGVycy5tbm4sIG1lcmdlZCRTYW1wbGUuTmFtZTIpIAphYnVuZGFuY2VzIDwtIHVuY2xhc3MoYWJ1bmRhbmNlcykgCmhlYWQoYWJ1bmRhbmNlcykKYGBgCgpQZXJmb3JtaW5nIHRoZSBEQSBhbmFseXNpcwoKT3VyIERBIGFuYWx5c2lzIHdpbGwgYWdhaW4gYmUgcGVyZm9ybWVkIHdpdGggdGhlIGVkZ2VSIHBhY2thZ2UuIFRoaXMgYWxsb3dzIHVzIHRvIHRha2UgYWR2YW50YWdlIG9mIHRoZSBOQiBHTE0gbWV0aG9kcyB0byBtb2RlbCBvdmVyZGlzcGVyc2VkIGNvdW50IGRhdGEgaW4gdGhlIHByZXNlbmNlIG9mIGxpbWl0ZWQgcmVwbGljYXRpb24gLSBleGNlcHQgdGhhdCB0aGUgY291bnRzIGFyZSBub3Qgb2YgcmVhZHMgcGVyIGdlbmUsIGJ1dCBvZiBjZWxscyBwZXIgbGFiZWwgKEx1biwgUmljaGFyZCwgYW5kIE1hcmlvbmkgMjAxNykuIFRoZSBhaW0gaXMgdG8gc2hhcmUgaW5mb3JtYXRpb24gYWNyb3NzIGxhYmVscyB0byBpbXByb3ZlIG91ciBlc3RpbWF0ZXMgb2YgdGhlIGJpb2xvZ2ljYWwgdmFyaWFiaWxpdHkgaW4gY2VsbCBhYnVuZGFuY2UgYmV0d2VlbiByZXBsaWNhdGVzLgoKYGBge3J9CiMgQXR0YWNoaW5nIHNvbWUgY29sdW1uIG1ldGFkYXRhLgpleHRyYS5pbmZvIDwtIGNvbERhdGEobWVyZ2VkKVttYXRjaChjb2xuYW1lcyhhYnVuZGFuY2VzKSwgbWVyZ2VkJFNhbXBsZS5OYW1lMiksXQp5LmFiIDwtIERHRUxpc3QoYWJ1bmRhbmNlcywgc2FtcGxlcz1leHRyYS5pbmZvKQp5LmFiCmBgYAoKV2UgZmlsdGVyIG91dCBsb3ctYWJ1bmRhbmNlIGxhYmVscyBhcyBwcmV2aW91c2x5IGRlc2NyaWJlZC4gVGhpcyBhdm9pZHMgY2x1dHRlcmluZyB0aGUgcmVzdWx0IHRhYmxlIHdpdGggdmVyeSByYXJlIHN1YnBvcHVsYXRpb25zIHRoYXQgY29udGFpbiBvbmx5IGEgaGFuZGZ1bCBvZiBjZWxscy4gRm9yIGEgREEgYW5hbHlzaXMgb2YgY2x1c3RlciBhYnVuZGFuY2VzLCBmaWx0ZXJpbmcgaXMgZ2VuZXJhbGx5IG5vdCByZXF1aXJlZCBhcyBtb3N0IGNsdXN0ZXJzIHdpbGwgbm90IGJlIG9mIGxvdy1hYnVuZGFuY2UgKG90aGVyd2lzZSB0aGVyZSB3b3VsZCBub3QgaGF2ZSBiZWVuIGVub3VnaCBldmlkZW5jZSB0byBkZWZpbmUgdGhlIGNsdXN0ZXIgaW4gdGhlIGZpcnN0IHBsYWNlKS4KCmBgYHtyfQprZWVwIDwtIGZpbHRlckJ5RXhwcih5LmFiLCBncm91cD15LmFiJHNhbXBsZXMkc291cmNlX25hbWUpCnkuYWIgPC0geS5hYltrZWVwLF0Kc3VtbWFyeShrZWVwKQpgYGAKClVubGlrZSBERSBhbmFseXNlcywgd2UgZG8gbm90IHBlcmZvcm0gYW4gYWRkaXRpb25hbCBub3JtYWxpemF0aW9uIHN0ZXAgd2l0aCBjYWxjTm9ybUZhY3RvcnMoKS4gVGhpcyBtZWFucyB0aGF0IHdlIGFyZSBvbmx5IG5vcm1hbGl6aW5nIGJhc2VkIG9uIHRoZSDigJxsaWJyYXJ5IHNpemXigJ0sIGkuZS4sIHRoZSB0b3RhbCBudW1iZXIgb2YgY2VsbHMgaW4gZWFjaCBzYW1wbGUuIEFueSBjaGFuZ2VzIHdlIGRldGVjdCBiZXR3ZWVuIGNvbmRpdGlvbnMgd2lsbCBzdWJzZXF1ZW50bHkgcmVwcmVzZW50IGRpZmZlcmVuY2VzIGluIHRoZSBwcm9wb3J0aW9uIG9mIGNlbGxzIGluIGVhY2ggY2x1c3Rlci4gVGhlIG1vdGl2YXRpb24gYmVoaW5kIHRoaXMgZGVjaXNpb24gaXMgZGlzY3Vzc2VkIGluIG1vcmUgZGV0YWlsIGluIFNlY3Rpb24gMTQuNC4zLgoKSGVyZSwgdGhlIGxvZy1mb2xkIGNoYW5nZSBpbiBvdXIgbW9kZWwgcmVmZXJzIHRvIHRoZSBjaGFuZ2UgaW4gY2VsbCBhYnVuZGFuY2UgYmV0d2VlbiBzYW1wbGUgZ3JvdXBzLCByYXRoZXIgdGhhbiB0aGUgY2hhbmdlIGluIGdlbmUgZXhwcmVzc2lvbi4KCmBgYHtyfQpkZXNpZ24gPC0gbW9kZWwubWF0cml4KH5mYWN0b3Ioc291cmNlX25hbWUpLCB5LmFiJHNhbXBsZXMpCmBgYAoKV2UgdXNlIHRoZSBlc3RpbWF0ZURpc3AoKSBmdW5jdGlvbiB0byBlc3RpbWF0ZSB0aGUgTkIgZGlwZXJzaW9uIGZvciBlYWNoIGNsdXN0ZXIuIFdlIHR1cm4gb2ZmIHRoZSB0cmVuZCBhcyB3ZSBkbyBub3QgaGF2ZSBlbm91Z2ggcG9pbnRzIGZvciBpdHMgc3RhYmxlIGVzdGltYXRpb24uCgpgYGB7cn0KeS5hYiA8LSBlc3RpbWF0ZURpc3AoeS5hYiwgZGVzaWduLCB0cmVuZD0ibm9uZSIpCnN1bW1hcnkoeS5hYiRjb21tb24uZGlzcGVyc2lvbikKYGBgCgpgYGB7cn0KcGxvdEJDVih5LmFiLCBjZXg9MSkKYGBgCgpXZSByZXBlYXQgdGhpcyBwcm9jZXNzIHdpdGggdGhlIFFMIGRpc3BlcnNpb24sIGFnYWluIGRpc2FibGluZyB0aGUgdHJlbmQuCgpgYGB7cn0KZml0LmFiIDwtIGdsbVFMRml0KHkuYWIsIGRlc2lnbiwgcm9idXN0PVRSVUUsIGFidW5kYW5jZS50cmVuZD1GQUxTRSkKc3VtbWFyeShmaXQuYWIkdmFyLnByaW9yKQpgYGAKCmBgYHtyfQpzdW1tYXJ5KGZpdC5hYiRkZi5wcmlvcikKYGBgCgpgYGB7cn0KcGxvdFFMRGlzcChmaXQuYWIsIGNleD0xKQpgYGAKCldlIHRlc3QgZm9yIGRpZmZlcmVuY2VzIGluIGFidW5kYW5jZSBiZXR3ZWVuIHNhbXBsZSBncm91cHMgdXNpbmcgZ2xtUUxGVGVzdCgpLgoKYGBge3J9CnJlcyA8LSBnbG1RTEZUZXN0KGZpdC5hYiwgY29lZj1uY29sKGRlc2lnbikpCnN1bW1hcnkoZGVjaWRlVGVzdHMocmVzKSkKYGBgCgpgYGB7cn0KdG9wVGFncyhyZXMpCmBgYAoKIyMjIEhhbmRsaW5nIGNvbXBvc2l0aW9uIGVmZmVjdHMKCiMjIyMgQmFja2dyb3VuZAoKQXMgbWVudGlvbmVkIGFib3ZlLCB3ZSBkbyBub3QgdXNlIGNhbGNOb3JtRmFjdG9ycygpIGluIG91ciBkZWZhdWx0IERBIGFuYWx5c2lzLiBUaGlzIG5vcm1hbGl6YXRpb24gc3RlcCBhc3N1bWVzIHRoYXQgbW9zdCBvZiB0aGUgaW5wdXQgZmVhdHVyZXMgYXJlIG5vdCBkaWZmZXJlbnQgYmV0d2VlbiBjb25kaXRpb25zLiBXaGlsZSB0aGlzIGFzc3VtcHRpb24gaXMgcmVhc29uYWJsZSBmb3IgbW9zdCB0eXBlcyBvZiBnZW5lIGV4cHJlc3Npb24gZGF0YSwgaXQgaXMgZ2VuZXJhbGx5IHRvbyBzdHJvbmcgZm9yIGNlbGwgdHlwZSBhYnVuZGFuY2UgLSBtb3N0IGV4cGVyaW1lbnRzIGNvbnNpc3Qgb2Ygb25seSBhIGZldyBjZWxsIHR5cGVzIHRoYXQgbWF5IGFsbCBjaGFuZ2UgaW4gYWJ1bmRhbmNlIHVwb24gcGVydHVyYmF0aW9uLiBUaHVzLCBvdXIgZGVmYXVsdCBhcHByb2FjaCBpcyB0byBvbmx5IG5vcm1hbGl6ZSBiYXNlZCBvbiB0aGUgdG90YWwgbnVtYmVyIG9mIGNlbGxzIGluIGVhY2ggc2FtcGxlLCB3aGljaCBtZWFucyB0aGF0IHdlIGFyZSBlZmZlY3RpdmVseSB0ZXN0aW5nIGZvciBkaWZmZXJlbnRpYWwgcHJvcG9ydGlvbnMgYmV0d2VlbiBjb25kaXRpb25zLgoKVW5mb3J0dW5hdGVseSwgdGhlIHVzZSBvZiB0aGUgdG90YWwgbnVtYmVyIG9mIGNlbGxzIGxlYXZlcyB1cyBzdXNjZXB0aWJsZSB0byBjb21wb3NpdGlvbiBlZmZlY3RzLiBGb3IgZXhhbXBsZSwgYSBsYXJnZSBpbmNyZWFzZSBpbiBhYnVuZGFuY2UgZm9yIG9uZSBjZWxsIHN1YnBvcHVsYXRpb24gd2lsbCBpbnRyb2R1Y2UgZGVjcmVhc2VzIGluIHByb3BvcnRpb24gZm9yIGFsbCBvdGhlciBzdWJwb3B1bGF0aW9ucyAtIHdoaWNoIGlzIHRlY2huaWNhbGx5IGNvcnJlY3QsIGJ1dCBtYXkgYmUgbWlzbGVhZGluZyBpZiBvbmUgY29uY2x1ZGVzIHRoYXQgdGhvc2Ugb3RoZXIgc3VicG9wdWxhdGlvbnMgYXJlIGRlY3JlYXNpbmcgaW4gYWJ1bmRhbmNlIG9mIHRoZWlyIG93biB2b2xpdGlvbi4gSWYgY29tcG9zaXRpb24gYmlhc2VzIGFyZSBwcm92aW5nIHByb2JsZW1hdGljIGZvciBpbnRlcnByZXRhdGlvbiBvZiBEQSByZXN1bHRzLCB3ZSBoYXZlIHNldmVyYWwgYXZlbnVlcyBmb3IgcmVtb3ZpbmcgdGhlbSBvciBtaXRpZ2F0aW5nIHRoZWlyIGltcGFjdCBieSBsZXZlcmFnaW5nIGEgcHJpb3JpIGJpb2xvZ2ljYWwga25vd2xlZGdlLgoxNC40LjMuMiBBc3N1bWluZyBtb3N0IGxhYmVscyBkbyBub3QgY2hhbmdlCgpJZiBpdCBpcyBwb3NzaWJsZSB0byBhc3N1bWUgdGhhdCBtb3N0IGxhYmVscyAoaS5lLiwgY2VsbCB0eXBlcykgZG8gbm90IGNoYW5nZSBpbiBhYnVuZGFuY2UsIHdlIGNhbiB1c2UgY2FsY05vcm1GYWN0b3JzKCkgdG8gY29tcHV0ZSBub3JtYWxpemF0aW9uIGZhY3RvcnMuCgpgYGB7cn0KeS5hYjIgPC0gY2FsY05vcm1GYWN0b3JzKHkuYWIpCnkuYWIyJHNhbXBsZXMkbm9ybS5mYWN0b3JzCmBgYAoKV2UgdGhlbiBwcm9jZWVkIHdpdGggdGhlIHJlbWFpbmRlciBvZiB0aGUgZWRnZVIgYW5hbHlzaXMsIHNob3duIGJlbG93IGluIGNvbmRlbnNlZCBmb3JtYXQuIEEgc2hpZnQgb2YgcG9zaXRpdmUgbG9nLWZvbGQgY2hhbmdlcyB0b3dhcmRzIHplcm8gaXMgY29uc2lzdGVudCB3aXRoIHRoZSByZW1vdmFsIG9mIGNvbXBvc2l0aW9uIGJpYXNlcy4KCmBgYHtyfQp5LmFiMiA8LSBlc3RpbWF0ZURpc3AoeS5hYjIsIGRlc2lnbiwgdHJlbmQ9Im5vbmUiKQpmaXQuYWIyIDwtIGdsbVFMRml0KHkuYWIyLCBkZXNpZ24sIHJvYnVzdD1UUlVFLCBhYnVuZGFuY2UudHJlbmQ9RkFMU0UpCnJlczIgPC0gZ2xtUUxGVGVzdChmaXQuYWIyLCBjb2VmPW5jb2woZGVzaWduKSkKdG9wVGFncyhyZXMyLCBuPTEwKQpgYGAK