Objectives

This notebook will demonstrate how to:

  • Read Cell Ranger data into R
  • Filter to cells using emptyDropsCellRanger()
  • Calculate quality control measures on scRNA-seq data
  • Remove likely compromised cells with miQC()
  • Normalize expression data across cells
  • Calculate and plot reduced dimension representations of expression data (PCA, UMAP)

In this notebook, we will review basic processing for single-cell RNA-seq data, starting with the output from Cell Ranger, and proceeding through filtering, quality control, normalization, and dimension reduction. We will perform these tasks using tools from the Bioconductor project, in particular SingleCellExperiment objects and functions that work with those objects. Much of the material in this notebook is directly inspired by, and draws heavily on, material presented in the book Orchestrating Single Cell Analysis with Bioconductor (OSCA).

Single-cell roadmap: Overview
Single-cell roadmap: Overview

The data we will use for this notebook is derived from a human glioblastoma specimen. The sample was processed by 10x Genomics using a 3’ RNA kit (v3.1), sequenced, and quantified with Cell Ranger 6.0. Further details about the sample and processing can be found on the 10x website.

Set Up

To start, we will load some of the libraries we will need later, and set a random number seed for reproducibility.

# Load libraries

# Plotting functions
library(ggplot2)

# The main class we will use for Single Cell data
library(SingleCellExperiment)
Loading required package: SummarizedExperiment
Loading required package: MatrixGenerics
Loading required package: matrixStats

Attaching package: 'MatrixGenerics'
The following objects are masked from 'package:matrixStats':

    colAlls, colAnyNAs, colAnys, colAvgsPerRowSet, colCollapse,
    colCounts, colCummaxs, colCummins, colCumprods, colCumsums,
    colDiffs, colIQRDiffs, colIQRs, colLogSumExps, colMadDiffs,
    colMads, colMaxs, colMeans2, colMedians, colMins, colOrderStats,
    colProds, colQuantiles, colRanges, colRanks, colSdDiffs, colSds,
    colSums2, colTabulates, colVarDiffs, colVars, colWeightedMads,
    colWeightedMeans, colWeightedMedians, colWeightedSds,
    colWeightedVars, rowAlls, rowAnyNAs, rowAnys, rowAvgsPerColSet,
    rowCollapse, rowCounts, rowCummaxs, rowCummins, rowCumprods,
    rowCumsums, rowDiffs, rowIQRDiffs, rowIQRs, rowLogSumExps,
    rowMadDiffs, rowMads, rowMaxs, rowMeans2, rowMedians, rowMins,
    rowOrderStats, rowProds, rowQuantiles, rowRanges, rowRanks,
    rowSdDiffs, rowSds, rowSums2, rowTabulates, rowVarDiffs, rowVars,
    rowWeightedMads, rowWeightedMeans, rowWeightedMedians,
    rowWeightedSds, rowWeightedVars
Loading required package: GenomicRanges
Loading required package: stats4
Loading required package: BiocGenerics

Attaching package: 'BiocGenerics'
The following objects are masked from 'package:stats':

    IQR, mad, sd, var, xtabs
The following objects are masked from 'package:base':

    anyDuplicated, aperm, append, as.data.frame, basename, cbind,
    colnames, dirname, do.call, duplicated, eval, evalq, Filter, Find,
    get, grep, grepl, intersect, is.unsorted, lapply, Map, mapply,
    match, mget, order, paste, pmax, pmax.int, pmin, pmin.int,
    Position, rank, rbind, Reduce, rownames, sapply, setdiff, table,
    tapply, union, unique, unsplit, which.max, which.min
Loading required package: S4Vectors

Attaching package: 'S4Vectors'
The following object is masked from 'package:utils':

    findMatches
The following objects are masked from 'package:base':

    expand.grid, I, unname
Loading required package: IRanges
Loading required package: GenomeInfoDb
Loading required package: Biobase
Welcome to Bioconductor

    Vignettes contain introductory material; view with
    'browseVignettes()'. To cite Bioconductor, see
    'citation("Biobase")', and for packages 'citation("pkgname")'.

Attaching package: 'Biobase'
The following object is masked from 'package:MatrixGenerics':

    rowMedians
The following objects are masked from 'package:matrixStats':

    anyMissing, rowMedians
Warning: replacing previous import 'S4Arrays::makeNindexFromArrayViewport' by
'DelayedArray::makeNindexFromArrayViewport' when loading 'SummarizedExperiment'
# Setting the seed for reproducibility
set.seed(12345)

Directories and files

Before we get too far, we like to define the input and output files that the notebook will use near the top of the document. While you might not know the names of all of the files you will need or create output files when you start an analysis, we have found it helpful to keep all file and directory names in a single place near the top of the document. This makes it easier for somebody coming to the code later to quickly see what files are needed as input and what will be produced as output. More often than not, that somebody is you!

The gene expression data were processed to create a gene-by-cell expression matrix of counts for using Cell Ranger 6.0. We have provided the raw data directory, raw_feature_bc_matrix, which is usually produced by Cell Ranger and placed in its outs directory. This directory usually contains three files: - barcodes.tsv.gz, a table of the cell barcodes that 10x uses, corresponding to the columns of the count matrix. - features.tsv.gz, a table of the features (genes in this case) for which expression was quantified. This will usually also include a bit of metadata about the features, including gene symbols (if the features are genes) and the type of data they represent (e.g., gene expression or antibody capture). - matrix.mtx.gz, The counts themselves, stored in a sparse “Matrix Exchange” format.

Cell Ranger will also export these data in a single HDF5 format file with a .h5 extension, which can also be imported with the same commands we will use below. However, we have found that processing large .h5 files is often much less efficient in R, so we prefer to start with the matrix files when possible. In particular, we would not recommend working with .h5 files for raw data; the filtering steps we will use below can sometimes take hours when using those files as input.

We will also need a table of mitochondrial genes, which we have stored in the data/reference/ directory.

Finally, we will set up the our output directory, creating it if it does not yet exist, and define the name for the files we will save after all of our initial processing is complete.

# Inputs --------------------------------------
# main data directory
data_dir <- file.path("data", "glioblastoma-10x")

# Path to the Cell Ranger matrix file
raw_matrix_dir <- file.path(data_dir, "raw_feature_bc_matrix")

# reference data directory
ref_dir <- file.path("data", "reference")

# Path to mitochondrial genes table
mito_file <- file.path(ref_dir, "hs_mitochondrial_genes.tsv")
# Outputs ------------------------------------

# Directory and file to save output
normalized_dir <- file.path(data_dir, "normalized")

# create the directory if it does not exist
fs::dir_create(normalized_dir)

# output RDS file for normalized data
output_sce_file <- file.path(normalized_dir,
                             "glioblastoma_normalized_sce.rds")

Reading Cell Ranger data

Single-cell roadmap: Preprocess and Import
Single-cell roadmap: Preprocess and Import

Whether the 10x Cell Ranger data is in Matrix Exchange format or in an HDF5 file, we can use the read10xCounts() function from the DropletUtils package to read the data into R and create a SingleCellExperiment object. (Though again, we do not recommend using the .h5 file if you can avoid it, especially for raw (unfiltered) data.)

If you used something other than Cell Ranger to process the raw data, you would need to use a different function to read it in and create the SingleCellExperiment object. Some of these functions for other common data formats are discussed in [Chapter 3 of OSCA] (http://bioconductor.org/books/3.16/OSCA.intro/getting-scrna-seq-datasets.html#reading-counts-into-r).

# read SCE from matrix directory
raw_sce <- DropletUtils::read10xCounts(
  raw_matrix_dir,
  col.names = TRUE # ensure barcodes are set as column names in the SCE object
)
Warning: replacing previous import 'S4Arrays::makeNindexFromArrayViewport' by
'DelayedArray::makeNindexFromArrayViewport' when loading 'HDF5Array'

Let’s look at the contents of the object after reading it in:

# view SCE object
raw_sce
class: SingleCellExperiment 
dim: 36601 734492 
metadata(1): Samples
assays(1): counts
rownames(36601): ENSG00000243485 ENSG00000237613 ... ENSG00000278817
  ENSG00000277196
rowData names(3): ID Symbol Type
colnames(734492): AAACCCAAGAAACCCA-1 AAACCCAAGAAATTCG-1 ...
  TTTGTTGTCTTTCTAG-1 TTTGTTGTCTTTGCAT-1
colData names(2): Sample Barcode
reducedDimNames(0):
mainExpName: NULL
altExpNames(0):

We can see from this summary that this SingleCellExperiment (SCE) object contains 36,601 rows, which correspond to the features (genes) that were analyzed, and 734,492 columns, which correspond to the possible barcode tags that were used in the experiment. Note that not all of these barcode tags will have been used, and many of the features may never have been seen either. One of our first steps will be to filter out barcodes that were never seen, or that may have only been seen in a droplet that did not contain a cell (an “empty droplet”).

Structure of the SingleCellExperiment object

In addition to the main counts matrix, listed as an assay in the SCE summary above, the SCE object can contain a number of other tables and matrices, each stored in a “slot” with a particular format. The overall structure of the object can be seen in the figure below, which comes from an OSCA Introduction chapter.

Structure of a SingleCellExperiment object
Structure of a SingleCellExperiment object

We have just mentioned the main assay slot, which contains full matrices of data (such as transcript counts) with each row a feature and each column a cell. There are also a couple of tables for metadata, and a slot to store reduced-dimension representations (e.g., PCA and/or UMAP) of the expression data.

We’ll start with the rowData slot, which is a table of metadata for each feature in our object. For now that contains the contents of the features.tsv.gz file that we discussed earlier. If we had read the data from something other than Cell Ranger output, we might have different contents, but each row would still correspond to a single feature of the SCE object.

Let’s look at this table, extracting it from the SCE object with the rowData() function and using head() to view only the first 6 rows.

# view rowData (features)
head(rowData(raw_sce))
DataFrame with 6 rows and 3 columns
                             ID      Symbol            Type
                    <character> <character>     <character>
ENSG00000243485 ENSG00000243485 MIR1302-2HG Gene Expression
ENSG00000237613 ENSG00000237613     FAM138A Gene Expression
ENSG00000186092 ENSG00000186092       OR4F5 Gene Expression
ENSG00000238009 ENSG00000238009  AL627309.1 Gene Expression
ENSG00000239945 ENSG00000239945  AL627309.3 Gene Expression
ENSG00000239906 ENSG00000239906  AL627309.2 Gene Expression

You can see that this table includes an ID for each feature, which is usually the Ensembl gene ID, as well as the corresponding gene symbol in the Symbol column. Finally there is a column for Type, which in this case is always “Gene Expression”, as all of the features in this data set are genes. If there were another modality of data that had been assayed in this experiment, there might be other values in this column, such as “Antibody Capture” for CITE-seq experiments.

The second slot is the colData table, which now corresponds to the barcodes.tsv.gz file, containing one row per cell barcode, or, more generally, one row per column of the counts assay. We can look at this table using the colData() function (and head() again to prevent printing the whole table):

# view colData (cell barcodes)
head(colData(raw_sce))
DataFrame with 6 rows and 2 columns
                                   Sample            Barcode
                              <character>        <character>
AAACCCAAGAAACCCA-1 data/glioblastoma-10.. AAACCCAAGAAACCCA-1
AAACCCAAGAAATTCG-1 data/glioblastoma-10.. AAACCCAAGAAATTCG-1
AAACCCAAGAACTGAT-1 data/glioblastoma-10.. AAACCCAAGAACTGAT-1
AAACCCAAGAAGAACG-1 data/glioblastoma-10.. AAACCCAAGAAGAACG-1
AAACCCAAGAAGCGCT-1 data/glioblastoma-10.. AAACCCAAGAAGCGCT-1
AAACCCAAGAAGGATG-1 data/glioblastoma-10.. AAACCCAAGAAGGATG-1

Here we see that there are currently two columns:

  • the Sample column has the path of the file that we read in (you may not see the whole path in this display); this should be identical in all rows from a single sample.
  • the Barcode column contains the sequence that was used to identify each potential droplet for sequencing (and a numeric tag, in this case). These will be unique within a sample.

As we proceed to calculate per-cell statistics, we will be adding new data to this table.

Quality control and filtering

Single-cell roadmap: QC, Filter, and Normalize
Single-cell roadmap: QC, Filter, and Normalize

Filtering empty droplets

Most of the barcodes in any given 10x experiment will not be seen at all, so our first step can be to filter this raw data to only the cells where there is at least one transcript that was counted with that barcode.

To do this, we will use the colSums() function to quickly add up all the counts that correspond to each possible cell barcode, then filter our raw_sce down to just those columns where there are non-zero total counts. We will need to extract the counts matrix from our SCE object, which we can do using the counts() function, conveniently enough.

# sum columns from counts matrix
barcode_counts <- colSums(counts(raw_sce))

# filter SCE object to only rows with counts > 0
raw_sce <- raw_sce[, which(barcode_counts > 0)]

Now we can look at how our SCE object has changed:

raw_sce
class: SingleCellExperiment 
dim: 36601 462027 
metadata(1): Samples
assays(1): counts
rownames(36601): ENSG00000243485 ENSG00000237613 ... ENSG00000278817
  ENSG00000277196
rowData names(3): ID Symbol Type
colnames(462027): AAACCCAAGAAACCCA-1 AAACCCAAGAAATTCG-1 ...
  TTTGTTGTCTTTCTAG-1 TTTGTTGTCTTTGCAT-1
colData names(2): Sample Barcode
reducedDimNames(0):
mainExpName: NULL
altExpNames(0):

But barcodes with zero counts are not the only ones that correspond to droplets without cells in them! Even if a droplet does not have a cell in it, there will often be spurious reads from RNA sequences that were present in the extracellular solution, whether from the original sample or from cells that were damaged during single-cell library preparation.

We could identify these barcodes simply as those with low transcript counts. Or, we can be a bit more clever! We can look at the transcript counts from the lowest-count droplets to create an expected distribution of transcripts in droplets that don’t contain cells. Then we can test each droplet to determine whether or not its transcript distribution deviates from that expectation. If it does, then we have pretty good evidence that there is a cell in there.

This test was first proposed by Lun et al. (2019) and implemented as emptyDrops() in the DropletUtils package. This method was then adopted, with some modifications, as the default cell filtering method used by Cell Ranger. Here we will use the emptyDropsCellRanger() function to perform filtering that more closely matches the Cell Ranger implementation.

# create a table of statistics using emptyDropsCellRanger
droplet_df <- DropletUtils::emptyDropsCellRanger(raw_sce)

Most values in this table are NA, because individual statistics were not calculated for the low-count droplets that were used to generate the background distribution. (Most droplets don’t have cells, so this makes some sense!)

We can look at just the rows without NA values by selected the ones where the FDR (which we will use again soon), is not NA.

# view rows where FDR is not `NA`
droplet_df[!is.na(droplet_df$FDR), ]
DataFrame with 2176 rows and 5 columns
                       Total   LogProb     PValue   Limited         FDR
                   <integer> <numeric>  <numeric> <logical>   <numeric>
AAACCCACATGTGTCA-1     15622        NA         NA        NA   0.0000000
AAACCCAGTGGTAATA-1       589  -1614.42  0.6597340     FALSE   0.7664609
AAACGAAAGAAACTAC-1       507  -1343.92  0.9993001     FALSE   1.0000000
AAACGAAAGAGAACCC-1     19899        NA         NA        NA   0.0000000
AAACGAATCCCTTCCC-1       881  -2262.85  0.0237976     FALSE   0.0311013
...                      ...       ...        ...       ...         ...
TTTGGTTTCCCGGTAG-1       867  -2361.80 0.00009999      TRUE 0.000139922
TTTGGTTTCCGGACTG-1      1245  -2840.48 0.04199580     FALSE 0.054297601
TTTGGTTTCTGTCCCA-1       770  -2046.57 0.05659434     FALSE 0.072654445
TTTGTTGGTCAGGCAA-1      1011  -2876.13 0.00009999      TRUE 0.000139922
TTTGTTGTCAGATTGC-1      9987        NA         NA        NA 0.000000000

You will notice that some cells with high counts also have NA values for many statistics. In those cases, NA values are actually present because of the high counts - emptyDropsCellRanger() automatically assumed cells were present, so they were also not tested.

Now we can filter our raw_sce object by column to only keep the cells with a small FDR: those that are quite unlikely to be empty droplets.

# filter droplets using `which` to prevent NA trouble
cells_to_retain <- which(droplet_df$FDR < 0.01)
filtered_sce <- raw_sce[, cells_to_retain]

How many cells do we have now?

filtered_sce
class: SingleCellExperiment 
dim: 36601 1626 
metadata(1): Samples
assays(1): counts
rownames(36601): ENSG00000243485 ENSG00000237613 ... ENSG00000278817
  ENSG00000277196
rowData names(3): ID Symbol Type
colnames(1626): AAACCCACATGTGTCA-1 AAACGAAAGAGAACCC-1 ...
  TTTGTTGGTCAGGCAA-1 TTTGTTGTCAGATTGC-1
colData names(2): Sample Barcode
reducedDimNames(0):
mainExpName: NULL
altExpNames(0):

Additional quality control

In addition to filtering out empty droplets, we also will want to filter out cells that may have been damaged during library preparation. These will often be characterized by a high proportion of mitochondrial transcripts and a smaller overall number of unique transcripts. When a cell ruptures, cytoplasmic transcripts will leak out, but mitochondrial transcripts, still protected by the mitochondrial membrane, may remain. As a consequence, there will be an over-abundance of mitochondrial reads, and fewer unique transcripts expressed.

Our first step then, is create a vector of the mitochondrial genes that are present in our dataset. The mitochondrial file we defined during setup (mito_file) is a TSV file containing all of the human mitochondrial genes with additional annotation information for each gene, such as the gene location and alternative names. (For more detail on the steps we took to create this file, you can look at one of our setup notebooks)

All we need now is the gene_id, and only for the genes that are present in our SCE, so we will do some filtering with dplyr to pull out a vector with just those ids.

# read in a table of mitochondrial genes and extract ids
mito_genes <- readr::read_tsv(mito_file) |>
  # filter to only the genes that are found in our dataset
  dplyr::filter(gene_id %in% rownames(filtered_sce)) |>
  # create a vector from the gene_id column
  dplyr::pull(gene_id)
Rows: 37 Columns: 13
── Column specification ────────────────────────────────────────────────────────
Delimiter: "\t"
chr (9): gene_id, gene_name, seqnames, strand, gene_biotype, seq_coord_syste...
dbl (4): start, end, width, entrezid

ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.

Calculating summary QC statistics

We can now use the scuttle function addPerCellQC() to calculate some statistics based on the counts matrix, which will be added to the colData table.

In addition to calculating statistics like the total read count for each cell and the number of transcripts that are detected, we can also calculate those statistics for defined subsets of genes. In this case, we will use our mito_genes vector to define a subset called mito. The mito name is important in that it is the name that will be expected by a later function. (We could define more subsets, but for now this one will do.)

filtered_sce <- scuttle::addPerCellQC(filtered_sce,
                                      subsets = list(mito = mito_genes))

Now we can look at the colData to see what was added:

head(colData(filtered_sce))
DataFrame with 6 rows and 8 columns
                                   Sample            Barcode       sum
                              <character>        <character> <numeric>
AAACCCACATGTGTCA-1 data/glioblastoma-10.. AAACCCACATGTGTCA-1     15622
AAACGAAAGAGAACCC-1 data/glioblastoma-10.. AAACGAAAGAGAACCC-1     19899
AAACGCTAGATTAGAC-1 data/glioblastoma-10.. AAACGCTAGATTAGAC-1      2858
AAACGCTGTACGCTAT-1 data/glioblastoma-10.. AAACGCTGTACGCTAT-1     31363
AAAGAACAGTACAGCG-1 data/glioblastoma-10.. AAAGAACAGTACAGCG-1     12636
AAAGGATAGATTGTGA-1 data/glioblastoma-10.. AAAGGATAGATTGTGA-1     13489
                    detected subsets_mito_sum subsets_mito_detected
                   <integer>        <numeric>             <integer>
AAACCCACATGTGTCA-1      2822             1699                    13
AAACGAAAGAGAACCC-1      3503             1141                    11
AAACGCTAGATTAGAC-1      1367              108                    10
AAACGCTGTACGCTAT-1      4354             1887                    13
AAAGAACAGTACAGCG-1      2780             1008                    11
AAAGGATAGATTGTGA-1      2533             1145                    13
                   subsets_mito_percent     total
                              <numeric> <numeric>
AAACCCACATGTGTCA-1             10.87569     15622
AAACGAAAGAGAACCC-1              5.73396     19899
AAACGCTAGATTAGAC-1              3.77887      2858
AAACGCTGTACGCTAT-1              6.01664     31363
AAAGAACAGTACAGCG-1              7.97721     12636
AAAGGATAGATTGTGA-1              8.48840     13489

We can also plot some of these statistics, here using the plotMetrics() function from the miQC package to plot the percent of reads that are mitochondrial (the subsets_mito_percent column) against the number of unique genes detected (the detected column) for each cell.

# use miQC::plotMetrics()
miQC::plotMetrics(filtered_sce) + theme_bw()

We can see that there is a range of mitochondrial percentages, and it does also seem that cells with high percentages of mitochondrial genes don’t seem to contain very many unique genes.

How do we filter with this information? One option is to define a cutoff for the mitochondrial percentage above which we call a cell compromised and exclude it from further analysis. However, choosing that cutoff can be a bit fraught, as the expected percentage of mitochondrial reads can vary depending on the cell type and library preparation methods. So it might be nice to have a method to determine that cutoff from the data itself.

Filtering compromised cells

Determining mitochondrial cutoffs is exactly what the miQC package does (Hippen et al. 2021)! In truth, it does something possibly even a bit better: it fits a mixture model to the data that consists of distributions of healthy cells and compromised cells. Then we can calculate whether each cell is more likely to belong to the healthy or compromised distribution. We can then exclude the cells that are more likely to be compromised.

To use miQC, we first fit a model to the data in our SCE object:

# fit the miQC model
miqc_model <- miQC::mixtureModel(filtered_sce)

Now we can plot the model results using the plotModel() function to see how it corresponds to our data. We should expect to see two fit lines:

  • One line will correspond the the “healthy” cells and should show little to no relationship between the number of unique genes detected and the mitochondrial percentage.
  • By contrast, the line that corresponds to “compromised” cells will show a negative relationship between the number of unique genes detected and the mitochondrial percentage.

This plot will also show, for each cell, the posterior probability that the cell is derived from the compromised distribution; a higher score indicates that a cell is more likely to be compromised.

It is also critical to note that this model can and does fail at times. Plotting the results as we have done here is not a step to skip. Always look at your data!

# plot the miQC model
miQC::plotModel(filtered_sce, miqc_model) +
  theme_bw()

We can now filter our data based on the probability compromised as calculated from the model. But before we do that, we might want to quickly plot to see what would be filtered out with a given cutoff, using the plotFiltering() function. The default is to exclude cells that have a posterior probability of 0.75 or greater of being compromised. We stick with that default, but for clarity, we will also include it in our code!

# look at miQC filtering
miQC::plotFiltering(filtered_sce, miqc_model,
                    posterior_cutoff = 0.75) +
  theme_bw()

In this case, the line between the cells to be kept and those that will be removed seems to correspond to a mitochondrial percentage of about 12.5%, but note that this will not always be constant. The cutoff point can vary for different numbers of unique genes within a sample, and it will certainly vary among samples!

At this point, we can perform the actual filtering using the filterCells() function, giving us a further filtered SCE object.

# perform miQC filtering
qcfiltered_sce <- miQC::filterCells(filtered_sce,
                                    model = miqc_model)
Removing 387 out of 1626 cells.

One more filter: unique gene count

While the miQC filtering is pretty good, you may have noticed that it still left some cells that had very low numbers of unique genes. While these cells may not be compromised, the information from them is also not likely to be useful, so we will filter those as well. We will only keep cells that have at least 200 unique genes.

# filter cells by unique gene count (`detected`)
qcfiltered_sce <- qcfiltered_sce[, which(qcfiltered_sce$detected >= 200)]
qcfiltered_sce
class: SingleCellExperiment 
dim: 36601 1233 
metadata(1): Samples
assays(1): counts
rownames(36601): ENSG00000243485 ENSG00000237613 ... ENSG00000278817
  ENSG00000277196
rowData names(3): ID Symbol Type
colnames(1233): AAACCCACATGTGTCA-1 AAACGAAAGAGAACCC-1 ...
  TTTGGTTTCATCTACT-1 TTTGTTGTCAGATTGC-1
colData names(9): Sample Barcode ... total prob_compromised
reducedDimNames(0):
mainExpName: NULL
altExpNames(0):

Normalization

Now that we have done our filtering, we can start analyzing the expression counts for the remaining cells.

The next step at this point is to convert the raw counts into a measure that accounts for differences in sequencing depth between cells, and to convert the distribution of expression values from the skewed distribution we expect to see in raw counts to one that is more normally distributed.

We will do this using functions from the scran and scuttle packages. The procedure we will use here is derived from the OSCA chapter on normalization. The idea is that the varying expression patterns that different cell types exhibit will affect the scaling factors that we would apply. To account for that variation, we first do a rough clustering of cells by their expression with scran::quickCluster(), then use that clustering to calculate the scaling factor for each cell within the clusters using scran::computeSumFactors(). Finally, we apply the scaling factor to the expression values for each cell and calculate the log-scaled expression values using the scuttle::logNormCounts() function.

# Perform rough clustering
qclust <- scran::quickCluster(qcfiltered_sce)

# use clusters to compute scaling factors and add to SCE object
qcfiltered_sce <- scran::computeSumFactors(qcfiltered_sce,
                                           clusters = qclust)

# perform normalization using scaling factors
# and save as a new SCE object
normalized_sce <- scuttle::logNormCounts(qcfiltered_sce)

This creates a new “assay” in the normalized_sce object, logcounts, which contains the normalized count values for each cell and gene. (The data here are not actually the log of the counts, since we also applied the scaling factors, but that name is used for historical reasons.)

Let’s take a look:

normalized_sce
class: SingleCellExperiment 
dim: 36601 1233 
metadata(1): Samples
assays(2): counts logcounts
rownames(36601): ENSG00000243485 ENSG00000237613 ... ENSG00000278817
  ENSG00000277196
rowData names(3): ID Symbol Type
colnames(1233): AAACCCACATGTGTCA-1 AAACGAAAGAGAACCC-1 ...
  TTTGGTTTCATCTACT-1 TTTGTTGTCAGATTGC-1
colData names(10): Sample Barcode ... prob_compromised sizeFactor
reducedDimNames(0):
mainExpName: NULL
altExpNames(0):

Dimension reduction

Single-cell roadmap: Dimension reduction
Single-cell roadmap: Dimension reduction

Now that we have normalized expression values, we would like to produce some reduced-dimension representations of the data. These will allow us to perform some downstream calculations more quickly, reduce some of the noise in the data, and allow us to visualize overall relationships among cells more easily (though with many caveats!).

Selecting highly variable genes

While we could calculate the reduced dimensions using all of the genes that we have assayed, in practice most of the genes will have very little variation in expression, so doing so will not provide much additional signal. Reducing the number of genes we include will also speed up some of the calculations.

To identify the most variable genes, we will use functions from the scran package. The first function, modelGeneVar(), attempts to divide the variation observed for each gene into a biological and technical component, with the intuition that genes with lower mean expression tend to have lower variance for purely technical reasons. We then provide the modelGeneVar() output to the getTopHVGs() function to identify the genes with the highest biological variation, which is what we are most interested in.

# identify 2000 genes
num_genes <- 2000

# model variance, partitioning into biological and technical variation
gene_variance <- scran::modelGeneVar(normalized_sce)

# get the most variable genes
hv_genes <- scran::getTopHVGs(gene_variance,
                              n = num_genes)

The result is a vector of gene ids (ordered from most to least variable):

head(hv_genes)
[1] "ENSG00000118785" "ENSG00000130203" "ENSG00000204287" "ENSG00000275302"
[5] "ENSG00000204389" "ENSG00000019582"
ENSG00000118785
ENSG00000130203
ENSG00000204287
ENSG00000275302
ENSG00000204389
ENSG00000019582

Principal components analysis

Now that we have selected the genes we would like to use for the reduced-dimension representations of the expression data, we can start to calculate them. First we will use the scater::runPCA() function to calculate the principal components from the expression matrix. This representation is fast and fairly robust, but the result is still quite multidimensional. We want keep a fair number of components (dimensions) in order to accurately represent the variation in the data, but doing so means that plotting only a few of these dimensions (in 2D) is not likely to provide a full view of the data.

The default number of components is 50, which we will stick with, but let’s enter it manually just for the record.

# calculate and save PCA results
normalized_sce <- scater::runPCA(
  normalized_sce,
  ncomponents = 50, # how many components to keep
  subset_row = hv_genes # use only the variable genes we chose
)

These reduced-dimension results will be stored in a reducedDim slot in the SCE object. We can see the names of the reducedDims that we have by looking at the object summary:

normalized_sce
class: SingleCellExperiment 
dim: 36601 1233 
metadata(1): Samples
assays(2): counts logcounts
rownames(36601): ENSG00000243485 ENSG00000237613 ... ENSG00000278817
  ENSG00000277196
rowData names(3): ID Symbol Type
colnames(1233): AAACCCACATGTGTCA-1 AAACGAAAGAGAACCC-1 ...
  TTTGGTTTCATCTACT-1 TTTGTTGTCAGATTGC-1
colData names(10): Sample Barcode ... prob_compromised sizeFactor
reducedDimNames(1): PCA
mainExpName: NULL
altExpNames(0):

If we want to extract the PCA results, we can do that with the reducedDim() function: Note that for these reduced-dimensionality matrices, the rows are the cells and the columns are the PC dimensions.

# extract the PCA matrix
pca_matrix <- reducedDim(normalized_sce, "PCA")

# look at the shape of the matrix
dim(pca_matrix)
[1] 1233   50

UMAP

Finally, we will calculate a UMAP (Uniform Manifold Approximation and Projection) representation of our data. This is a machine-learning-based method that is useful for performing dimensionality reduction suitable for visualization. It’s goal is to provide a representation of the data in two dimensions (typically, more are possible) that preserves as much of the distance relationships among cells as possible. While this does make for visually appealing and useful plots, it is important not to overinterpret the results! In particular, while you will often see some apparent clustering of cells in the resulting output, those clusters may not be particularly valid, and the spacing within or between clusters may not reflect true distances.

In many ways this is analogous to the problem of projecting a map of the earth onto a flat surface; any choice will result in some distortions. However, with UMAP, we rarely know exactly what choices were made and what distortions might have resulted. The UMAP coordinates themselves should never be used for downstream analysis.

Since the UMAP procedure would be slow to calculate with the full data, so the runUMAP() function first calculates a PCA matrix and then uses that to calculate the UMAP. Since we already have a PCA matrix, we will tell the function use that instead of recalculating it.

normalized_sce <- scater::runUMAP(normalized_sce,
                                  dimred = "PCA")

As before, we could extract the UMAP matrix from our SCE object with the reducedDim() function. We can also visualize the UMAP results using the plotReducedDim() function.

# plot the UMAP
scater::plotReducedDim(normalized_sce,
                       "UMAP",
                       # color by the most variable gene
                       color_by = hv_genes[1])

Unsupervised clustering

As a final analysis step at this stage, we will return to the PCA results to perform unsupervised clustering. Here we will use a graph-based clustering method, which starts by identifying cells that are close together in the multidimensional space. It then identifies “communities” of highly connected cells, and breaks them apart by regions of lower connection.

There are a number of algorithms that can perform this clustering, each with parameters that can affect how many clusters are identified and which cells belong to each cluster. It is also worth noting that these clusters may or may not correspond to “cell types” by whatever definition you might prefer to use. Interpretation of these clusters, or other measures of cell type, are something that will require more careful and likely more customized analysis.

We will perform our clustering using the bluster package, which can perform many different types of clustering. As mentioned earlier, we are using “graph” clustering, which we define using the NNGraphParam() function. Within that are a number of further options, such as the weighting used for building the network graph and the algorithm used for dividing the graph into clusters.

Modifying these parameters can result in quite different cluster assignments! For the clustering below we will use Jaccard weighting and Louvain clustering, which correspond more closely to the default methods used by Seurat than the default parameters. It is also worth noting that the the cluster assignments are somewhat stochastic. In particular, the names/numbers of the clusters can be quite inconsistent between runs!

# perform graph-based clustering
nn_clusters <- bluster::clusterRows(
  pca_matrix,
  bluster::NNGraphParam(
    # number of neighbors to use in network graph
    k = 20,
    # weighting scheme for building the network graph
    # default is "rank"
    type = "jaccard",
    # cluster detection algorithm
    # default is "walktrap"
    cluster.fun = "louvain"
  )
)

We can save the cluster assignments back into the colData of the SCE object with a little shortcut: the $ followed by the name of the new column we want to add.

# save clusters to SCE colData
normalized_sce$nn_cluster <- nn_clusters

Now we can plot the UMAP again, this time colored by the cluster assignments that we just created. Here rather than the general plotReducedDim() function, we will use plotUMAP(), which is exactly the same, except it always plots from the reducedDim slot named UMAP, so we can skip that argument.

# plot UMAP with assigned clusters
scater::plotUMAP(normalized_sce,
                 color_by = "nn_cluster")

What do you see in these results?

What would you want to do next?

Save SCE object for later

We will now save our filtered and normalized object, including the dimension reduction and clustering results to an RDS file, using the file path that we defined at the start of the notebook. If we were to want to return to this data, we could load this file directly into a new R session and not have to repeat the processing that we have done up to this point.

The data in these objects tends to be quite large, but very compressible. To save space on disk (at the expense of time), we will make sure that the data is compressed internally before writing it out to a file. Note that the file we write is still going to be an .rds file with no additional extension. (Further note: The base R function saveRDS() uses compression by default, but the tidyverse function readr::write_rds() does not.)

# write RDS file with compression
readr::write_rds(normalized_sce, file = output_sce_file, compress = "gz")
LS0tCnRpdGxlOiAiUmVhZGluZywgZmlsdGVyaW5nLCBhbmQgbm9ybWFsaXppbmcgc2NSTkEtc2VxIGRhdGEiCmF1dGhvcjogRGF0YSBMYWIgZm9yIEFMU0YKZGF0ZTogMjAyMwpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazoKICAgIHRvYzogdHJ1ZQogICAgdG9jX2Zsb2F0OiB0cnVlCi0tLQoKIyMgT2JqZWN0aXZlcwoKVGhpcyBub3RlYm9vayB3aWxsIGRlbW9uc3RyYXRlIGhvdyB0bzoKCi0gUmVhZCBDZWxsIFJhbmdlciBkYXRhIGludG8gUgotIEZpbHRlciB0byBjZWxscyB1c2luZyBgZW1wdHlEcm9wc0NlbGxSYW5nZXIoKWAKLSBDYWxjdWxhdGUgcXVhbGl0eSBjb250cm9sIG1lYXN1cmVzIG9uIHNjUk5BLXNlcSBkYXRhCi0gUmVtb3ZlIGxpa2VseSBjb21wcm9taXNlZCBjZWxscyB3aXRoIGBtaVFDKClgCi0gTm9ybWFsaXplIGV4cHJlc3Npb24gZGF0YSBhY3Jvc3MgY2VsbHMKLSBDYWxjdWxhdGUgYW5kIHBsb3QgcmVkdWNlZCBkaW1lbnNpb24gcmVwcmVzZW50YXRpb25zIG9mIGV4cHJlc3Npb24gZGF0YSAoUENBLCBVTUFQKQoKLS0tCgpJbiB0aGlzIG5vdGVib29rLCB3ZSB3aWxsIHJldmlldyBiYXNpYyBwcm9jZXNzaW5nIGZvciBzaW5nbGUtY2VsbCBSTkEtc2VxIGRhdGEsIHN0YXJ0aW5nIHdpdGggdGhlIG91dHB1dCBmcm9tIENlbGwgUmFuZ2VyLCBhbmQgcHJvY2VlZGluZyB0aHJvdWdoIGZpbHRlcmluZywgcXVhbGl0eSBjb250cm9sLCBub3JtYWxpemF0aW9uLCBhbmQgZGltZW5zaW9uIHJlZHVjdGlvbi4gV2Ugd2lsbCBwZXJmb3JtIHRoZXNlIHRhc2tzIHVzaW5nIHRvb2xzIGZyb20gdGhlIFtCaW9jb25kdWN0b3IgcHJvamVjdF0oaHR0cHM6Ly9iaW9jb25kdWN0b3Iub3JnKSwgaW4gcGFydGljdWxhciBbYFNpbmdsZUNlbGxFeHBlcmltZW50YCBvYmplY3RzXShodHRwczovL2Jpb2NvbmR1Y3Rvci5vcmcvcGFja2FnZXMvcmVsZWFzZS9iaW9jL2h0bWwvU2luZ2xlQ2VsbEV4cGVyaW1lbnQuaHRtbCkgYW5kIGZ1bmN0aW9ucyB0aGF0IHdvcmsgd2l0aCB0aG9zZSBvYmplY3RzLgpNdWNoIG9mIHRoZSBtYXRlcmlhbCBpbiB0aGlzIG5vdGVib29rIGlzIGRpcmVjdGx5IGluc3BpcmVkIGJ5LCBhbmQgZHJhd3MgaGVhdmlseSBvbiwgbWF0ZXJpYWwgcHJlc2VudGVkIGluIHRoZSBib29rIFtfT3JjaGVzdHJhdGluZyBTaW5nbGUgQ2VsbCBBbmFseXNpcyB3aXRoIEJpb2NvbmR1Y3Rvcl8gKE9TQ0EpXShodHRwOi8vYmlvY29uZHVjdG9yLm9yZy9ib29rcy8zLjE2L09TQ0EvKS4KCiFbU2luZ2xlLWNlbGwgcm9hZG1hcDogT3ZlcnZpZXddKGRpYWdyYW1zL3JvYWRtYXBfc2luZ2xlX292ZXJ2aWV3LnBuZykKClRoZSBkYXRhIHdlIHdpbGwgdXNlIGZvciB0aGlzIG5vdGVib29rIGlzIGRlcml2ZWQgZnJvbSBhIGh1bWFuIGdsaW9ibGFzdG9tYSBzcGVjaW1lbi4KVGhlIHNhbXBsZSB3YXMgcHJvY2Vzc2VkIGJ5IDEweCBHZW5vbWljcyB1c2luZyBhIDMnIFJOQSBraXQgKHYzLjEpLCBzZXF1ZW5jZWQsIGFuZCBxdWFudGlmaWVkIHdpdGggQ2VsbCBSYW5nZXIgNi4wLgpGdXJ0aGVyIGRldGFpbHMgYWJvdXQgdGhlIHNhbXBsZSBhbmQgcHJvY2Vzc2luZyBjYW4gYmUgZm91bmQgb24gdGhlIFsxMHggd2Vic2l0ZV0oaHR0cHM6Ly93d3cuMTB4Z2Vub21pY3MuY29tL3Jlc291cmNlcy9kYXRhc2V0cy8yLWstc29ydGVkLWNlbGxzLWZyb20taHVtYW4tZ2xpb2JsYXN0b21hLW11bHRpZm9ybWUtMy12LTMtMS0zLTEtc3RhbmRhcmQtNi0wLTApLgoKCiMjIFNldCBVcAoKVG8gc3RhcnQsIHdlIHdpbGwgbG9hZCBzb21lIG9mIHRoZSBsaWJyYXJpZXMgd2Ugd2lsbCBuZWVkIGxhdGVyLCBhbmQgc2V0IGEgcmFuZG9tIG51bWJlciBzZWVkIGZvciByZXByb2R1Y2liaWxpdHkuCgpgYGB7ciBzZXR1cH0KIyBMb2FkIGxpYnJhcmllcwoKIyBQbG90dGluZyBmdW5jdGlvbnMKbGlicmFyeShnZ3Bsb3QyKQoKIyBUaGUgbWFpbiBjbGFzcyB3ZSB3aWxsIHVzZSBmb3IgU2luZ2xlIENlbGwgZGF0YQpsaWJyYXJ5KFNpbmdsZUNlbGxFeHBlcmltZW50KQoKIyBTZXR0aW5nIHRoZSBzZWVkIGZvciByZXByb2R1Y2liaWxpdHkKc2V0LnNlZWQoMTIzNDUpCmBgYAoKIyMjIERpcmVjdG9yaWVzIGFuZCBmaWxlcwoKQmVmb3JlIHdlIGdldCB0b28gZmFyLCB3ZSBsaWtlIHRvIGRlZmluZSB0aGUgaW5wdXQgYW5kIG91dHB1dCBmaWxlcyB0aGF0IHRoZSBub3RlYm9vayB3aWxsIHVzZSBuZWFyIHRoZSB0b3Agb2YgdGhlIGRvY3VtZW50LgpXaGlsZSB5b3UgbWlnaHQgbm90IGtub3cgdGhlIG5hbWVzIG9mIGFsbCBvZiB0aGUgZmlsZXMgeW91IHdpbGwgbmVlZCBvciBjcmVhdGUgb3V0cHV0IGZpbGVzIHdoZW4geW91IHN0YXJ0IGFuIGFuYWx5c2lzLCB3ZSBoYXZlIGZvdW5kIGl0IGhlbHBmdWwgdG8ga2VlcCBhbGwgZmlsZSBhbmQgZGlyZWN0b3J5IG5hbWVzIGluIGEgc2luZ2xlIHBsYWNlIG5lYXIgdGhlIHRvcCBvZiB0aGUgZG9jdW1lbnQuClRoaXMgbWFrZXMgaXQgZWFzaWVyIGZvciBzb21lYm9keSBjb21pbmcgdG8gdGhlIGNvZGUgbGF0ZXIgdG8gcXVpY2tseSBzZWUgd2hhdCBmaWxlcyBhcmUgbmVlZGVkIGFzIGlucHV0IGFuZCB3aGF0IHdpbGwgYmUgcHJvZHVjZWQgYXMgb3V0cHV0LgpNb3JlIG9mdGVuIHRoYW4gbm90LCB0aGF0IHNvbWVib2R5IGlzIHlvdSEKClRoZSBnZW5lIGV4cHJlc3Npb24gZGF0YSB3ZXJlIHByb2Nlc3NlZCB0byBjcmVhdGUgYSBnZW5lLWJ5LWNlbGwgZXhwcmVzc2lvbiBtYXRyaXggb2YgY291bnRzIGZvciB1c2luZyBDZWxsIFJhbmdlciA2LjAuCldlIGhhdmUgcHJvdmlkZWQgdGhlIHJhdyBkYXRhIGRpcmVjdG9yeSwgYHJhd19mZWF0dXJlX2JjX21hdHJpeGAsIHdoaWNoIGlzIHVzdWFsbHkgcHJvZHVjZWQgYnkgQ2VsbCBSYW5nZXIgYW5kIHBsYWNlZCBpbiBpdHMgYG91dHNgIGRpcmVjdG9yeS4KVGhpcyBkaXJlY3RvcnkgdXN1YWxseSBjb250YWlucyB0aHJlZSBmaWxlczoKLSBgYmFyY29kZXMudHN2Lmd6YCwgYSB0YWJsZSBvZiB0aGUgY2VsbCBiYXJjb2RlcyB0aGF0IDEweCB1c2VzLCBjb3JyZXNwb25kaW5nIHRvIHRoZSBjb2x1bW5zIG9mIHRoZSBjb3VudCBtYXRyaXguCi0gYGZlYXR1cmVzLnRzdi5nemAsIGEgdGFibGUgb2YgdGhlIGZlYXR1cmVzIChnZW5lcyBpbiB0aGlzIGNhc2UpIGZvciB3aGljaCBleHByZXNzaW9uIHdhcyBxdWFudGlmaWVkLgpUaGlzIHdpbGwgdXN1YWxseSBhbHNvIGluY2x1ZGUgYSBiaXQgb2YgbWV0YWRhdGEgYWJvdXQgdGhlIGZlYXR1cmVzLCBpbmNsdWRpbmcgZ2VuZSBzeW1ib2xzIChpZiB0aGUgZmVhdHVyZXMgYXJlIGdlbmVzKSBhbmQgdGhlIHR5cGUgb2YgZGF0YSB0aGV5IHJlcHJlc2VudCAoZS5nLiwgZ2VuZSBleHByZXNzaW9uIG9yIGFudGlib2R5IGNhcHR1cmUpLgotIGBtYXRyaXgubXR4Lmd6YCwgVGhlIGNvdW50cyB0aGVtc2VsdmVzLCBzdG9yZWQgaW4gYSBzcGFyc2UgWyJNYXRyaXggRXhjaGFuZ2UiIGZvcm1hdF0oaHR0cHM6Ly9tYXRoLm5pc3QuZ292L01hdHJpeE1hcmtldC9mb3JtYXRzLmh0bWwpLgoKQ2VsbCBSYW5nZXIgd2lsbCBhbHNvIGV4cG9ydCB0aGVzZSBkYXRhIGluIGEgc2luZ2xlIGBIREY1YCBmb3JtYXQgZmlsZSB3aXRoIGEgYC5oNWAgZXh0ZW5zaW9uLCB3aGljaCBjYW4gYWxzbyBiZSBpbXBvcnRlZCB3aXRoIHRoZSBzYW1lIGNvbW1hbmRzIHdlIHdpbGwgdXNlIGJlbG93LgpIb3dldmVyLCB3ZSBoYXZlIGZvdW5kIHRoYXQgcHJvY2Vzc2luZyBsYXJnZSBgLmg1YCBmaWxlcyBpcyBvZnRlbiBfbXVjaF8gbGVzcyBlZmZpY2llbnQgaW4gUiwgc28gd2UgcHJlZmVyIHRvIHN0YXJ0IHdpdGggdGhlIG1hdHJpeCBmaWxlcyB3aGVuIHBvc3NpYmxlLgpJbiBwYXJ0aWN1bGFyLCB3ZSB3b3VsZCBub3QgcmVjb21tZW5kIHdvcmtpbmcgd2l0aCBgLmg1YCBmaWxlcyBmb3IgcmF3IGRhdGE7IHRoZSBmaWx0ZXJpbmcgc3RlcHMgd2Ugd2lsbCB1c2UgYmVsb3cgY2FuIHNvbWV0aW1lcyB0YWtlIGhvdXJzIHdoZW4gdXNpbmcgdGhvc2UgZmlsZXMgYXMgaW5wdXQuCgpXZSB3aWxsIGFsc28gbmVlZCBhIHRhYmxlIG9mIG1pdG9jaG9uZHJpYWwgZ2VuZXMsIHdoaWNoIHdlIGhhdmUgc3RvcmVkIGluIHRoZSBgZGF0YS9yZWZlcmVuY2UvYCBkaXJlY3RvcnkuCgpGaW5hbGx5LCB3ZSB3aWxsIHNldCB1cCB0aGUgb3VyIG91dHB1dCBkaXJlY3RvcnksIGNyZWF0aW5nIGl0IGlmIGl0IGRvZXMgbm90IHlldCBleGlzdCwgYW5kIGRlZmluZSB0aGUgbmFtZSBmb3IgdGhlIGZpbGVzIHdlIHdpbGwgc2F2ZSBhZnRlciBhbGwgb2Ygb3VyIGluaXRpYWwgcHJvY2Vzc2luZyBpcyBjb21wbGV0ZS4KCmBgYHtyIGlucHV0cywgbGl2ZT1UUlVFfQojIElucHV0cyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQojIG1haW4gZGF0YSBkaXJlY3RvcnkKZGF0YV9kaXIgPC0gZmlsZS5wYXRoKCJkYXRhIiwgImdsaW9ibGFzdG9tYS0xMHgiKQoKIyBQYXRoIHRvIHRoZSBDZWxsIFJhbmdlciBtYXRyaXggZmlsZQpyYXdfbWF0cml4X2RpciA8LSBmaWxlLnBhdGgoZGF0YV9kaXIsICJyYXdfZmVhdHVyZV9iY19tYXRyaXgiKQoKIyByZWZlcmVuY2UgZGF0YSBkaXJlY3RvcnkKcmVmX2RpciA8LSBmaWxlLnBhdGgoImRhdGEiLCAicmVmZXJlbmNlIikKCiMgUGF0aCB0byBtaXRvY2hvbmRyaWFsIGdlbmVzIHRhYmxlCm1pdG9fZmlsZSA8LSBmaWxlLnBhdGgocmVmX2RpciwgImhzX21pdG9jaG9uZHJpYWxfZ2VuZXMudHN2IikKYGBgCgpgYGB7ciBvdXRwdXRzfQojIE91dHB1dHMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCgojIERpcmVjdG9yeSBhbmQgZmlsZSB0byBzYXZlIG91dHB1dApub3JtYWxpemVkX2RpciA8LSBmaWxlLnBhdGgoZGF0YV9kaXIsICJub3JtYWxpemVkIikKCiMgY3JlYXRlIHRoZSBkaXJlY3RvcnkgaWYgaXQgZG9lcyBub3QgZXhpc3QKZnM6OmRpcl9jcmVhdGUobm9ybWFsaXplZF9kaXIpCgojIG91dHB1dCBSRFMgZmlsZSBmb3Igbm9ybWFsaXplZCBkYXRhCm91dHB1dF9zY2VfZmlsZSA8LSBmaWxlLnBhdGgobm9ybWFsaXplZF9kaXIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImdsaW9ibGFzdG9tYV9ub3JtYWxpemVkX3NjZS5yZHMiKQpgYGAKCgojIyBSZWFkaW5nIENlbGwgUmFuZ2VyIGRhdGEKCiFbU2luZ2xlLWNlbGwgcm9hZG1hcDogUHJlcHJvY2VzcyBhbmQgSW1wb3J0XShkaWFncmFtcy9yb2FkbWFwX3NpbmdsZV9wcmVwcm9jZXNzLnBuZykKCldoZXRoZXIgdGhlIDEweCBDZWxsIFJhbmdlciBkYXRhIGlzIGluIE1hdHJpeCBFeGNoYW5nZSBmb3JtYXQgb3IgaW4gYW4gSERGNSBmaWxlLCB3ZSBjYW4gdXNlIHRoZSBgcmVhZDEweENvdW50cygpYCBmdW5jdGlvbiBmcm9tIHRoZSBgRHJvcGxldFV0aWxzYCBwYWNrYWdlIHRvIHJlYWQgdGhlIGRhdGEgaW50byBSIGFuZCBjcmVhdGUgYSBgU2luZ2xlQ2VsbEV4cGVyaW1lbnRgIG9iamVjdC4KKFRob3VnaCBhZ2Fpbiwgd2UgZG8gbm90IHJlY29tbWVuZCB1c2luZyB0aGUgYC5oNWAgZmlsZSBpZiB5b3UgY2FuIGF2b2lkIGl0LCBfZXNwZWNpYWxseV8gZm9yIHJhdyAodW5maWx0ZXJlZCkgZGF0YS4pCgpJZiB5b3UgdXNlZCBzb21ldGhpbmcgb3RoZXIgdGhhbiBDZWxsIFJhbmdlciB0byBwcm9jZXNzIHRoZSByYXcgZGF0YSwgeW91IHdvdWxkIG5lZWQgdG8gdXNlIGEgZGlmZmVyZW50IGZ1bmN0aW9uIHRvIHJlYWQgaXQgaW4gYW5kIGNyZWF0ZSB0aGUgYFNpbmdsZUNlbGxFeHBlcmltZW50YCBvYmplY3QuClNvbWUgb2YgdGhlc2UgZnVuY3Rpb25zIGZvciBvdGhlciBjb21tb24gZGF0YSBmb3JtYXRzIGFyZSBkaXNjdXNzZWQgaW4gW0NoYXB0ZXIgMyBvZiBPU0NBXSAoaHR0cDovL2Jpb2NvbmR1Y3Rvci5vcmcvYm9va3MvMy4xNi9PU0NBLmludHJvL2dldHRpbmctc2NybmEtc2VxLWRhdGFzZXRzLmh0bWwjcmVhZGluZy1jb3VudHMtaW50by1yKS4KCmBgYHtyIHJlYWQgU0NFLCBsaXZlPVRSVUV9CiMgcmVhZCBTQ0UgZnJvbSBtYXRyaXggZGlyZWN0b3J5CnJhd19zY2UgPC0gRHJvcGxldFV0aWxzOjpyZWFkMTB4Q291bnRzKAogIHJhd19tYXRyaXhfZGlyLAogIGNvbC5uYW1lcyA9IFRSVUUgIyBlbnN1cmUgYmFyY29kZXMgYXJlIHNldCBhcyBjb2x1bW4gbmFtZXMgaW4gdGhlIFNDRSBvYmplY3QKKQpgYGAKCkxldCdzIGxvb2sgYXQgdGhlIGNvbnRlbnRzIG9mIHRoZSBvYmplY3QgYWZ0ZXIgcmVhZGluZyBpdCBpbjoKCmBgYHtyIHZpZXcgU0NFLCBsaXZlPVRSVUV9CiMgdmlldyBTQ0Ugb2JqZWN0CnJhd19zY2UKYGBgCgpXZSBjYW4gc2VlIGZyb20gdGhpcyBzdW1tYXJ5IHRoYXQgdGhpcyBgU2luZ2xlQ2VsbEV4cGVyaW1lbnRgIChTQ0UpIG9iamVjdCBjb250YWlucyAzNiw2MDEgcm93cywgd2hpY2ggY29ycmVzcG9uZCB0byB0aGUgZmVhdHVyZXMgKGdlbmVzKSB0aGF0IHdlcmUgYW5hbHl6ZWQsIGFuZCA3MzQsNDkyIGNvbHVtbnMsIHdoaWNoIGNvcnJlc3BvbmQgdG8gdGhlIHBvc3NpYmxlIGJhcmNvZGUgdGFncyB0aGF0IHdlcmUgdXNlZCBpbiB0aGUgZXhwZXJpbWVudC4KTm90ZSB0aGF0IG5vdCBhbGwgb2YgdGhlc2UgYmFyY29kZSB0YWdzIHdpbGwgaGF2ZSBiZWVuIHVzZWQsIGFuZCBtYW55IG9mIHRoZSBmZWF0dXJlcyBtYXkgbmV2ZXIgaGF2ZSBiZWVuIHNlZW4gZWl0aGVyLgpPbmUgb2Ygb3VyIGZpcnN0IHN0ZXBzIHdpbGwgYmUgdG8gZmlsdGVyIG91dCBiYXJjb2RlcyB0aGF0IHdlcmUgbmV2ZXIgc2Vlbiwgb3IgdGhhdCBtYXkgaGF2ZSBvbmx5IGJlZW4gc2VlbiBpbiBhIGRyb3BsZXQgdGhhdCBkaWQgbm90IGNvbnRhaW4gYSBjZWxsIChhbiAiZW1wdHkgZHJvcGxldCIpLgoKIyMjIFN0cnVjdHVyZSBvZiB0aGUgYFNpbmdsZUNlbGxFeHBlcmltZW50YCBvYmplY3QKCkluIGFkZGl0aW9uIHRvIHRoZSBtYWluIGBjb3VudHNgIG1hdHJpeCwgbGlzdGVkIGFzIGFuIGBhc3NheWAgaW4gdGhlIFNDRSBzdW1tYXJ5IGFib3ZlLCB0aGUgU0NFIG9iamVjdCBjYW4gY29udGFpbiBhIG51bWJlciBvZiBvdGhlciB0YWJsZXMgYW5kIG1hdHJpY2VzLCBlYWNoIHN0b3JlZCBpbiBhICJzbG90IiB3aXRoIGEgcGFydGljdWxhciBmb3JtYXQuClRoZSBvdmVyYWxsIHN0cnVjdHVyZSBvZiB0aGUgb2JqZWN0IGNhbiBiZSBzZWVuIGluIHRoZSBmaWd1cmUgYmVsb3csIHdoaWNoIGNvbWVzIGZyb20gYW4gW09TQ0EgSW50cm9kdWN0aW9uIGNoYXB0ZXJdKGh0dHA6Ly9iaW9jb25kdWN0b3Iub3JnL2Jvb2tzLzMuMTYvT1NDQS5pbnRyby90aGUtc2luZ2xlY2VsbGV4cGVyaW1lbnQtY2xhc3MuaHRtbCkuCgohW1N0cnVjdHVyZSBvZiBhIFNpbmdsZUNlbGxFeHBlcmltZW50IG9iamVjdF0oZGlhZ3JhbXMvU2luZ2xlQ2VsbEV4cGVyaW1lbnQucG5nKQoKV2UgaGF2ZSBqdXN0IG1lbnRpb25lZCB0aGUgbWFpbiBgYXNzYXlgIHNsb3QsIHdoaWNoIGNvbnRhaW5zIGZ1bGwgbWF0cmljZXMgb2YgZGF0YSAoc3VjaCBhcyB0cmFuc2NyaXB0IGNvdW50cykgd2l0aCBlYWNoIHJvdyBhIGZlYXR1cmUgYW5kIGVhY2ggY29sdW1uIGEgY2VsbC4KVGhlcmUgYXJlIGFsc28gYSBjb3VwbGUgb2YgdGFibGVzIGZvciBtZXRhZGF0YSwgYW5kIGEgc2xvdCB0byBzdG9yZSByZWR1Y2VkLWRpbWVuc2lvbiByZXByZXNlbnRhdGlvbnMgKGUuZy4sIFBDQSBhbmQvb3IgVU1BUCkgb2YgdGhlIGV4cHJlc3Npb24gZGF0YS4KCldlJ2xsIHN0YXJ0IHdpdGggdGhlIGByb3dEYXRhYCBzbG90LCB3aGljaCBpcyBhIHRhYmxlIG9mIG1ldGFkYXRhIGZvciBlYWNoIGZlYXR1cmUgaW4gb3VyIG9iamVjdC4KRm9yIG5vdyB0aGF0IGNvbnRhaW5zIHRoZSBjb250ZW50cyBvZiB0aGUgYGZlYXR1cmVzLnRzdi5nemAgZmlsZSB0aGF0IHdlIGRpc2N1c3NlZCBlYXJsaWVyLgpJZiB3ZSBoYWQgcmVhZCB0aGUgZGF0YSBmcm9tIHNvbWV0aGluZyBvdGhlciB0aGFuIENlbGwgUmFuZ2VyIG91dHB1dCwgd2UgbWlnaHQgaGF2ZSBkaWZmZXJlbnQgY29udGVudHMsIGJ1dCBlYWNoIHJvdyB3b3VsZCBzdGlsbCBjb3JyZXNwb25kIHRvIGEgc2luZ2xlIGZlYXR1cmUgb2YgdGhlIFNDRSBvYmplY3QuCgpMZXQncyBsb29rIGF0IHRoaXMgdGFibGUsIGV4dHJhY3RpbmcgaXQgZnJvbSB0aGUgU0NFIG9iamVjdCB3aXRoIHRoZSBgcm93RGF0YSgpYCBmdW5jdGlvbiBhbmQgdXNpbmcgYGhlYWQoKWAgdG8gdmlldyBvbmx5IHRoZSBmaXJzdCA2IHJvd3MuCgpgYGB7ciByb3dkYXRhfQojIHZpZXcgcm93RGF0YSAoZmVhdHVyZXMpCmhlYWQocm93RGF0YShyYXdfc2NlKSkKYGBgCgpZb3UgY2FuIHNlZSB0aGF0IHRoaXMgdGFibGUgaW5jbHVkZXMgYW4gYElEYCBmb3IgZWFjaCBmZWF0dXJlLCB3aGljaCBpcyB1c3VhbGx5IHRoZSBFbnNlbWJsIGdlbmUgSUQsIGFzIHdlbGwgYXMgdGhlIGNvcnJlc3BvbmRpbmcgZ2VuZSBzeW1ib2wgaW4gdGhlIGBTeW1ib2xgIGNvbHVtbi4KRmluYWxseSB0aGVyZSBpcyBhIGNvbHVtbiBmb3IgYFR5cGVgLCB3aGljaCBpbiB0aGlzIGNhc2UgaXMgYWx3YXlzICJHZW5lIEV4cHJlc3Npb24iLCBhcyBhbGwgb2YgdGhlIGZlYXR1cmVzIGluIHRoaXMgZGF0YSBzZXQgYXJlIGdlbmVzLgpJZiB0aGVyZSB3ZXJlIGFub3RoZXIgbW9kYWxpdHkgb2YgZGF0YSB0aGF0IGhhZCBiZWVuIGFzc2F5ZWQgaW4gdGhpcyBleHBlcmltZW50LCB0aGVyZSBtaWdodCBiZSBvdGhlciB2YWx1ZXMgaW4gdGhpcyBjb2x1bW4sIHN1Y2ggYXMgIkFudGlib2R5IENhcHR1cmUiIGZvciBDSVRFLXNlcSBleHBlcmltZW50cy4KClRoZSBzZWNvbmQgc2xvdCBpcyB0aGUgYGNvbERhdGFgIHRhYmxlLCB3aGljaCBub3cgY29ycmVzcG9uZHMgdG8gdGhlIGBiYXJjb2Rlcy50c3YuZ3pgIGZpbGUsIGNvbnRhaW5pbmcgb25lIHJvdyBwZXIgY2VsbCBiYXJjb2RlLCBvciwgbW9yZSBnZW5lcmFsbHksIG9uZSByb3cgcGVyIGNvbHVtbiBvZiB0aGUgYGNvdW50c2AgYXNzYXkuCldlIGNhbiBsb29rIGF0IHRoaXMgdGFibGUgdXNpbmcgdGhlIGBjb2xEYXRhKClgIGZ1bmN0aW9uIChhbmQgYGhlYWQoKWAgYWdhaW4gdG8gcHJldmVudCBwcmludGluZyB0aGUgd2hvbGUgdGFibGUpOgoKYGBge3IgY29sZGF0YX0KIyB2aWV3IGNvbERhdGEgKGNlbGwgYmFyY29kZXMpCmhlYWQoY29sRGF0YShyYXdfc2NlKSkKYGBgCgpIZXJlIHdlIHNlZSB0aGF0IHRoZXJlIGFyZSBjdXJyZW50bHkgdHdvIGNvbHVtbnM6CgotIHRoZSBgU2FtcGxlYCBjb2x1bW4gaGFzIHRoZSBwYXRoIG9mIHRoZSBmaWxlIHRoYXQgd2UgcmVhZCBpbiAoeW91IG1heSBub3Qgc2VlIHRoZSB3aG9sZSBwYXRoIGluIHRoaXMgZGlzcGxheSk7IHRoaXMgc2hvdWxkIGJlIGlkZW50aWNhbCBpbiBhbGwgcm93cyBmcm9tIGEgc2luZ2xlIHNhbXBsZS4KLSB0aGUgYEJhcmNvZGVgIGNvbHVtbiBjb250YWlucyB0aGUgc2VxdWVuY2UgdGhhdCB3YXMgdXNlZCB0byBpZGVudGlmeSBlYWNoIHBvdGVudGlhbCBkcm9wbGV0IGZvciBzZXF1ZW5jaW5nIChhbmQgYSBudW1lcmljIHRhZywgaW4gdGhpcyBjYXNlKS4KVGhlc2Ugd2lsbCBiZSB1bmlxdWUgd2l0aGluIGEgc2FtcGxlLgoKQXMgd2UgcHJvY2VlZCB0byBjYWxjdWxhdGUgcGVyLWNlbGwgc3RhdGlzdGljcywgd2Ugd2lsbCBiZSBhZGRpbmcgbmV3IGRhdGEgdG8gdGhpcyB0YWJsZS4KCiMjIFF1YWxpdHkgY29udHJvbCBhbmQgZmlsdGVyaW5nCgohW1NpbmdsZS1jZWxsIHJvYWRtYXA6IFFDLCBGaWx0ZXIsIGFuZCBOb3JtYWxpemVdKGRpYWdyYW1zL3JvYWRtYXBfc2luZ2xlX3FjX25vcm0ucG5nKQoKIyMjIEZpbHRlcmluZyBlbXB0eSBkcm9wbGV0cwoKTW9zdCBvZiB0aGUgYmFyY29kZXMgaW4gYW55IGdpdmVuIDEweCBleHBlcmltZW50IHdpbGwgbm90IGJlIHNlZW4gYXQgYWxsLCBzbyBvdXIgZmlyc3Qgc3RlcCBjYW4gYmUgdG8gZmlsdGVyIHRoaXMgcmF3IGRhdGEgdG8gb25seSB0aGUgY2VsbHMgd2hlcmUgdGhlcmUgaXMgYXQgbGVhc3Qgb25lIHRyYW5zY3JpcHQgdGhhdCB3YXMgY291bnRlZCB3aXRoIHRoYXQgYmFyY29kZS4KClRvIGRvIHRoaXMsIHdlIHdpbGwgdXNlIHRoZSBgY29sU3VtcygpYCBmdW5jdGlvbiB0byBxdWlja2x5IGFkZCB1cCBhbGwgdGhlIGNvdW50cyB0aGF0IGNvcnJlc3BvbmQgdG8gZWFjaCBwb3NzaWJsZSBjZWxsIGJhcmNvZGUsIHRoZW4gZmlsdGVyIG91ciBgcmF3X3NjZWAgZG93biB0byBqdXN0IHRob3NlIGNvbHVtbnMgd2hlcmUgdGhlcmUgYXJlIG5vbi16ZXJvIHRvdGFsIGNvdW50cy4KV2Ugd2lsbCBuZWVkIHRvIGV4dHJhY3QgdGhlIGBjb3VudHNgIG1hdHJpeCBmcm9tIG91ciBTQ0Ugb2JqZWN0LCB3aGljaCB3ZSBjYW4gZG8gdXNpbmcgdGhlIGBjb3VudHMoKWAgZnVuY3Rpb24sIGNvbnZlbmllbnRseSBlbm91Z2guCgpgYGB7ciByZW1vdmUgemVyb3MsIGxpdmU9VFJVRX0KIyBzdW0gY29sdW1ucyBmcm9tIGNvdW50cyBtYXRyaXgKYmFyY29kZV9jb3VudHMgPC0gY29sU3Vtcyhjb3VudHMocmF3X3NjZSkpCgojIGZpbHRlciBTQ0Ugb2JqZWN0IHRvIG9ubHkgcm93cyB3aXRoIGNvdW50cyA+IDAKcmF3X3NjZSA8LSByYXdfc2NlWywgd2hpY2goYmFyY29kZV9jb3VudHMgPiAwKV0KYGBgCgpOb3cgd2UgY2FuIGxvb2sgYXQgaG93IG91ciBTQ0Ugb2JqZWN0IGhhcyBjaGFuZ2VkOgoKYGBge3IgemVyby1maWx0ZXJlZCBTQ0V9CnJhd19zY2UKYGBgCgpCdXQgYmFyY29kZXMgd2l0aCB6ZXJvIGNvdW50cyBhcmUgbm90IHRoZSBvbmx5IG9uZXMgdGhhdCBjb3JyZXNwb25kIHRvIGRyb3BsZXRzIHdpdGhvdXQgY2VsbHMgaW4gdGhlbSEKRXZlbiBpZiBhIGRyb3BsZXQgZG9lcyBub3QgaGF2ZSBhIGNlbGwgaW4gaXQsIHRoZXJlIHdpbGwgb2Z0ZW4gYmUgc3B1cmlvdXMgcmVhZHMgZnJvbSBSTkEgc2VxdWVuY2VzIHRoYXQgd2VyZSBwcmVzZW50IGluIHRoZSBleHRyYWNlbGx1bGFyIHNvbHV0aW9uLCB3aGV0aGVyIGZyb20gdGhlIG9yaWdpbmFsIHNhbXBsZSBvciBmcm9tIGNlbGxzIHRoYXQgd2VyZSBkYW1hZ2VkIGR1cmluZyBzaW5nbGUtY2VsbCBsaWJyYXJ5IHByZXBhcmF0aW9uLgoKV2UgY291bGQgaWRlbnRpZnkgdGhlc2UgYmFyY29kZXMgc2ltcGx5IGFzIHRob3NlIHdpdGggbG93IHRyYW5zY3JpcHQgY291bnRzLgpPciwgd2UgY2FuIGJlIGEgYml0IG1vcmUgY2xldmVyIQpXZSBjYW4gbG9vayBhdCB0aGUgdHJhbnNjcmlwdCBjb3VudHMgX2Zyb21fIHRoZSBsb3dlc3QtY291bnQgZHJvcGxldHMgdG8gY3JlYXRlIGFuIGV4cGVjdGVkIGRpc3RyaWJ1dGlvbiBvZiB0cmFuc2NyaXB0cyBpbiBkcm9wbGV0cyB0aGF0IGRvbid0IGNvbnRhaW4gY2VsbHMuClRoZW4gd2UgY2FuIHRlc3QgZWFjaCBkcm9wbGV0IHRvIGRldGVybWluZSB3aGV0aGVyIG9yIG5vdCBpdHMgdHJhbnNjcmlwdCBkaXN0cmlidXRpb24gZGV2aWF0ZXMgZnJvbSB0aGF0IGV4cGVjdGF0aW9uLgpJZiBpdCBkb2VzLCB0aGVuIHdlIGhhdmUgcHJldHR5IGdvb2QgZXZpZGVuY2UgdGhhdCB0aGVyZSBfaXNfIGEgY2VsbCBpbiB0aGVyZS4KClRoaXMgdGVzdCB3YXMgZmlyc3QgcHJvcG9zZWQgYnkgW0x1biBfZXQgYWwuXyAoMjAxOSldKGh0dHBzOi8vZG9pLm9yZy8xMC4xMTg2L3MxMzA1OS0wMTktMTY2Mi15KSBhbmQgaW1wbGVtZW50ZWQgYXMgYGVtcHR5RHJvcHMoKWAgaW4gdGhlIGBEcm9wbGV0VXRpbHNgIHBhY2thZ2UuClRoaXMgbWV0aG9kIHdhcyB0aGVuIGFkb3B0ZWQsIHdpdGggc29tZSBtb2RpZmljYXRpb25zLCBhcyB0aGUgZGVmYXVsdCBjZWxsIGZpbHRlcmluZyBtZXRob2QgdXNlZCBieSBDZWxsIFJhbmdlci4KSGVyZSB3ZSB3aWxsIHVzZSB0aGUgW2BlbXB0eURyb3BzQ2VsbFJhbmdlcigpYCBmdW5jdGlvbl0oaHR0cHM6Ly9yZHJyLmlvL2dpdGh1Yi9NYXJpb25pTGFiL0Ryb3BsZXRVdGlscy9tYW4vZW1wdHlEcm9wc0NlbGxSYW5nZXIuaHRtbCkgdG8gcGVyZm9ybSBmaWx0ZXJpbmcgdGhhdCBtb3JlIGNsb3NlbHkgbWF0Y2hlcyB0aGUgQ2VsbCBSYW5nZXIgaW1wbGVtZW50YXRpb24uCgoKYGBge3IgY2FsY3VsYXRlIGRyb3BsZXQgc3RhdHMsIGxpdmU9VFJVRX0KIyBjcmVhdGUgYSB0YWJsZSBvZiBzdGF0aXN0aWNzIHVzaW5nIGVtcHR5RHJvcHNDZWxsUmFuZ2VyCmRyb3BsZXRfZGYgPC0gRHJvcGxldFV0aWxzOjplbXB0eURyb3BzQ2VsbFJhbmdlcihyYXdfc2NlKQpgYGAKCk1vc3QgdmFsdWVzIGluIHRoaXMgdGFibGUgYXJlIGBOQWAsIGJlY2F1c2UgaW5kaXZpZHVhbCBzdGF0aXN0aWNzIHdlcmUgbm90IGNhbGN1bGF0ZWQgZm9yIHRoZSBsb3ctY291bnQgZHJvcGxldHMgdGhhdCB3ZXJlIHVzZWQgdG8gZ2VuZXJhdGUgdGhlIGJhY2tncm91bmQgZGlzdHJpYnV0aW9uLgooTW9zdCBkcm9wbGV0cyBkb24ndCBoYXZlIGNlbGxzLCBzbyB0aGlzIG1ha2VzIHNvbWUgc2Vuc2UhKQoKV2UgY2FuIGxvb2sgYXQganVzdCB0aGUgcm93cyB3aXRob3V0IGBOQWAgdmFsdWVzIGJ5IHNlbGVjdGVkIHRoZSBvbmVzIHdoZXJlIHRoZSBGRFIgKHdoaWNoIHdlIHdpbGwgdXNlIGFnYWluIHNvb24pLCBpcyBub3QgYE5BYC4KCmBgYHtyIGRyb3BsZXQgc3RhdHN9CiMgdmlldyByb3dzIHdoZXJlIEZEUiBpcyBub3QgYE5BYApkcm9wbGV0X2RmWyFpcy5uYShkcm9wbGV0X2RmJEZEUiksIF0KYGBgCllvdSB3aWxsIG5vdGljZSB0aGF0IHNvbWUgY2VsbHMgd2l0aCBoaWdoIGNvdW50cyBhbHNvIGhhdmUgYE5BYCB2YWx1ZXMgZm9yIG1hbnkgc3RhdGlzdGljcy4KSW4gdGhvc2UgY2FzZXMsIGBOQWAgdmFsdWVzIGFyZSBhY3R1YWxseSBwcmVzZW50IF9iZWNhdXNlXyBvZiB0aGUgaGlnaCBjb3VudHMgLSBgZW1wdHlEcm9wc0NlbGxSYW5nZXIoKWAgYXV0b21hdGljYWxseSBhc3N1bWVkIGNlbGxzIHdlcmUgcHJlc2VudCwgc28gdGhleSB3ZXJlIGFsc28gbm90IHRlc3RlZC4KCk5vdyB3ZSBjYW4gZmlsdGVyIG91ciBgcmF3X3NjZWAgb2JqZWN0IF9ieSBjb2x1bW5fIHRvIG9ubHkga2VlcCB0aGUgY2VsbHMgd2l0aCBhIHNtYWxsIEZEUjogdGhvc2UgdGhhdCBhcmUgcXVpdGUgdW5saWtlbHkgdG8gYmUgZW1wdHkgZHJvcGxldHMuCgpgYGB7ciBmaWx0ZXIgZW1wdHlkcm9wcywgbGl2ZT1UUlVFfQojIGZpbHRlciBkcm9wbGV0cyB1c2luZyBgd2hpY2hgIHRvIHByZXZlbnQgTkEgdHJvdWJsZQpjZWxsc190b19yZXRhaW4gPC0gd2hpY2goZHJvcGxldF9kZiRGRFIgPCAwLjAxKQpmaWx0ZXJlZF9zY2UgPC0gcmF3X3NjZVssIGNlbGxzX3RvX3JldGFpbl0KYGBgCgpIb3cgbWFueSBjZWxscyBkbyB3ZSBoYXZlIG5vdz8KCmBgYHtyIGZpbHRlcmVkIHN1bW1hcnl9CmZpbHRlcmVkX3NjZQpgYGAKCiMjIyBBZGRpdGlvbmFsIHF1YWxpdHkgY29udHJvbAoKSW4gYWRkaXRpb24gdG8gZmlsdGVyaW5nIG91dCBlbXB0eSBkcm9wbGV0cywgd2UgYWxzbyB3aWxsIHdhbnQgdG8gZmlsdGVyIG91dCBjZWxscyB0aGF0IG1heSBoYXZlIGJlZW4gZGFtYWdlZCBkdXJpbmcgbGlicmFyeSBwcmVwYXJhdGlvbi4KVGhlc2Ugd2lsbCBvZnRlbiBiZSBjaGFyYWN0ZXJpemVkIGJ5IGEgaGlnaCBwcm9wb3J0aW9uIG9mIG1pdG9jaG9uZHJpYWwgdHJhbnNjcmlwdHMgYW5kIGEgc21hbGxlciBvdmVyYWxsIG51bWJlciBvZiB1bmlxdWUgdHJhbnNjcmlwdHMuCldoZW4gYSBjZWxsIHJ1cHR1cmVzLCBjeXRvcGxhc21pYyB0cmFuc2NyaXB0cyB3aWxsIGxlYWsgb3V0LCBidXQgbWl0b2Nob25kcmlhbCB0cmFuc2NyaXB0cywgc3RpbGwgcHJvdGVjdGVkIGJ5IHRoZSBtaXRvY2hvbmRyaWFsIG1lbWJyYW5lLCBtYXkgcmVtYWluLgpBcyBhIGNvbnNlcXVlbmNlLCB0aGVyZSB3aWxsIGJlIGFuIG92ZXItYWJ1bmRhbmNlIG9mIG1pdG9jaG9uZHJpYWwgcmVhZHMsIGFuZCBmZXdlciB1bmlxdWUgdHJhbnNjcmlwdHMgZXhwcmVzc2VkLgoKT3VyIGZpcnN0IHN0ZXAgdGhlbiwgaXMgY3JlYXRlIGEgdmVjdG9yIG9mIHRoZSBtaXRvY2hvbmRyaWFsIGdlbmVzIHRoYXQgYXJlIHByZXNlbnQgaW4gb3VyIGRhdGFzZXQuClRoZSBtaXRvY2hvbmRyaWFsIGZpbGUgd2UgZGVmaW5lZCBkdXJpbmcgc2V0dXAgKGBtaXRvX2ZpbGVgKSBpcyBhIFRTViBmaWxlIGNvbnRhaW5pbmcgYWxsIG9mIHRoZSBodW1hbiBtaXRvY2hvbmRyaWFsIGdlbmVzIHdpdGggYWRkaXRpb25hbCBhbm5vdGF0aW9uIGluZm9ybWF0aW9uIGZvciBlYWNoIGdlbmUsIHN1Y2ggYXMgdGhlIGdlbmUgbG9jYXRpb24gYW5kIGFsdGVybmF0aXZlIG5hbWVzLgooRm9yIG1vcmUgZGV0YWlsIG9uIHRoZSBzdGVwcyB3ZSB0b29rIHRvIGNyZWF0ZSB0aGlzIGZpbGUsIHlvdSBjYW4gbG9vayBhdCBbb25lIG9mIG91ciBzZXR1cCBub3RlYm9va3NdKGh0dHBzOi8vZ2l0aHViLmNvbS9BbGV4c0xlbW9uYWRlL3RyYWluaW5nLW1vZHVsZXMvYmxvYi9tYXN0ZXIvc2NSTkEtc2VxLWFkdmFuY2VkL3NldHVwL21pdG9fZ2VuZV9saXN0cy5SbWQpKQoKQWxsIHdlIG5lZWQgbm93IGlzIHRoZSBgZ2VuZV9pZGAsIGFuZCBvbmx5IGZvciB0aGUgZ2VuZXMgdGhhdCBhcmUgcHJlc2VudCBpbiBvdXIgU0NFLCBzbyB3ZSB3aWxsIGRvIHNvbWUgZmlsdGVyaW5nIHdpdGggYGRwbHlyYCB0byBwdWxsIG91dCBhIHZlY3RvciB3aXRoIGp1c3QgdGhvc2UgaWRzLgoKYGBge3IgZ2V0IG1pdG9jaG9uZHJpYWwgZ2VuZXN9CiMgcmVhZCBpbiBhIHRhYmxlIG9mIG1pdG9jaG9uZHJpYWwgZ2VuZXMgYW5kIGV4dHJhY3QgaWRzCm1pdG9fZ2VuZXMgPC0gcmVhZHI6OnJlYWRfdHN2KG1pdG9fZmlsZSkgfD4KICAjIGZpbHRlciB0byBvbmx5IHRoZSBnZW5lcyB0aGF0IGFyZSBmb3VuZCBpbiBvdXIgZGF0YXNldAogIGRwbHlyOjpmaWx0ZXIoZ2VuZV9pZCAlaW4lIHJvd25hbWVzKGZpbHRlcmVkX3NjZSkpIHw+CiAgIyBjcmVhdGUgYSB2ZWN0b3IgZnJvbSB0aGUgZ2VuZV9pZCBjb2x1bW4KICBkcGx5cjo6cHVsbChnZW5lX2lkKQpgYGAKCiMjIyBDYWxjdWxhdGluZyBzdW1tYXJ5IFFDIHN0YXRpc3RpY3MKCldlIGNhbiBub3cgdXNlIHRoZSBgc2N1dHRsZWAgZnVuY3Rpb24gYGFkZFBlckNlbGxRQygpYCB0byBjYWxjdWxhdGUgc29tZSBzdGF0aXN0aWNzIGJhc2VkIG9uIHRoZSBjb3VudHMgbWF0cml4LCB3aGljaCB3aWxsIGJlIGFkZGVkIHRvIHRoZSBgY29sRGF0YWAgdGFibGUuCgpJbiBhZGRpdGlvbiB0byBjYWxjdWxhdGluZyBzdGF0aXN0aWNzIGxpa2UgdGhlIHRvdGFsIHJlYWQgY291bnQgZm9yIGVhY2ggY2VsbCBhbmQgdGhlIG51bWJlciBvZiB0cmFuc2NyaXB0cyB0aGF0IGFyZSBkZXRlY3RlZCwgd2UgY2FuIGFsc28gY2FsY3VsYXRlIHRob3NlIHN0YXRpc3RpY3MgZm9yIGRlZmluZWQgc3Vic2V0cyBvZiBnZW5lcy4KSW4gdGhpcyBjYXNlLCB3ZSB3aWxsIHVzZSBvdXIgYG1pdG9fZ2VuZXNgIHZlY3RvciB0byBkZWZpbmUgYSBzdWJzZXQgY2FsbGVkIGBtaXRvYC4KVGhlIGBtaXRvYCBuYW1lIGlzIGltcG9ydGFudCBpbiB0aGF0IGl0IGlzIHRoZSBuYW1lIHRoYXQgd2lsbCBiZSBleHBlY3RlZCBieSBhIGxhdGVyIGZ1bmN0aW9uLgooV2UgY291bGQgZGVmaW5lIG1vcmUgc3Vic2V0cywgYnV0IGZvciBub3cgdGhpcyBvbmUgd2lsbCBkby4pCgpgYGB7ciBwZXIgY2VsbCBRQywgbGl2ZT1UUlVFfQpmaWx0ZXJlZF9zY2UgPC0gc2N1dHRsZTo6YWRkUGVyQ2VsbFFDKGZpbHRlcmVkX3NjZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdWJzZXRzID0gbGlzdChtaXRvID0gbWl0b19nZW5lcykpCmBgYAoKTm93IHdlIGNhbiBsb29rIGF0IHRoZSBjb2xEYXRhIHRvIHNlZSB3aGF0IHdhcyBhZGRlZDoKCmBgYHtyIHZpZXcgY29sRGF0YSBzdGF0c30KaGVhZChjb2xEYXRhKGZpbHRlcmVkX3NjZSkpCmBgYAoKV2UgY2FuIGFsc28gcGxvdCBzb21lIG9mIHRoZXNlIHN0YXRpc3RpY3MsIGhlcmUgdXNpbmcgdGhlIGBwbG90TWV0cmljcygpYCBmdW5jdGlvbiBmcm9tIHRoZSBgbWlRQ2AgcGFja2FnZSB0byBwbG90IHRoZSBwZXJjZW50IG9mIHJlYWRzIHRoYXQgYXJlIG1pdG9jaG9uZHJpYWwgKHRoZSBgc3Vic2V0c19taXRvX3BlcmNlbnRgIGNvbHVtbikgYWdhaW5zdCB0aGUgbnVtYmVyIG9mIHVuaXF1ZSBnZW5lcyBkZXRlY3RlZCAodGhlIGBkZXRlY3RlZGAgY29sdW1uKSBmb3IgZWFjaCBjZWxsLgoKYGBge3IgbWlRQyBwbG90TWV0cmljc30KIyB1c2UgbWlRQzo6cGxvdE1ldHJpY3MoKQptaVFDOjpwbG90TWV0cmljcyhmaWx0ZXJlZF9zY2UpICsgdGhlbWVfYncoKQpgYGAKCldlIGNhbiBzZWUgdGhhdCB0aGVyZSBpcyBhIHJhbmdlIG9mIG1pdG9jaG9uZHJpYWwgcGVyY2VudGFnZXMsIGFuZCBpdCBkb2VzIGFsc28gc2VlbSB0aGF0IGNlbGxzIHdpdGggaGlnaCBwZXJjZW50YWdlcyBvZiBtaXRvY2hvbmRyaWFsIGdlbmVzIGRvbid0IHNlZW0gdG8gY29udGFpbiB2ZXJ5IG1hbnkgdW5pcXVlIGdlbmVzLgoKSG93IGRvIHdlIGZpbHRlciB3aXRoIHRoaXMgaW5mb3JtYXRpb24/Ck9uZSBvcHRpb24gaXMgdG8gZGVmaW5lIGEgY3V0b2ZmIGZvciB0aGUgbWl0b2Nob25kcmlhbCBwZXJjZW50YWdlIGFib3ZlIHdoaWNoIHdlIGNhbGwgYSBjZWxsIGNvbXByb21pc2VkIGFuZCBleGNsdWRlIGl0IGZyb20gZnVydGhlciBhbmFseXNpcy4KSG93ZXZlciwgY2hvb3NpbmcgdGhhdCBjdXRvZmYgY2FuIGJlIGEgYml0IGZyYXVnaHQsIGFzIHRoZSBleHBlY3RlZCBwZXJjZW50YWdlIG9mIG1pdG9jaG9uZHJpYWwgcmVhZHMgY2FuIHZhcnkgZGVwZW5kaW5nIG9uIHRoZSBjZWxsIHR5cGUgYW5kIGxpYnJhcnkgcHJlcGFyYXRpb24gbWV0aG9kcy4KU28gaXQgbWlnaHQgYmUgbmljZSB0byBoYXZlIGEgbWV0aG9kIHRvIGRldGVybWluZSB0aGF0IGN1dG9mZiBmcm9tIHRoZSBkYXRhIGl0c2VsZi4KCiMjIyBGaWx0ZXJpbmcgY29tcHJvbWlzZWQgY2VsbHMKCkRldGVybWluaW5nIG1pdG9jaG9uZHJpYWwgY3V0b2ZmcyBpcyBleGFjdGx5IHdoYXQgdGhlIGBtaVFDYCBwYWNrYWdlIGRvZXMgKFtIaXBwZW4gX2V0IGFsLl8gMjAyMV0oaHR0cHM6Ly9kb2kub3JnLzEwLjEzNzEvam91cm5hbC5wY2JpLjEwMDkyOTApKSEKSW4gdHJ1dGgsIGl0IGRvZXMgc29tZXRoaW5nIHBvc3NpYmx5IGV2ZW4gYSBiaXQgYmV0dGVyOiBpdCBmaXRzIGEgbWl4dHVyZSBtb2RlbCB0byB0aGUgZGF0YSB0aGF0IGNvbnNpc3RzIG9mIGRpc3RyaWJ1dGlvbnMgb2YgaGVhbHRoeSBjZWxscyBhbmQgY29tcHJvbWlzZWQgY2VsbHMuClRoZW4gd2UgY2FuIGNhbGN1bGF0ZSB3aGV0aGVyIGVhY2ggY2VsbCBpcyBtb3JlIGxpa2VseSB0byBiZWxvbmcgdG8gdGhlIGhlYWx0aHkgb3IgY29tcHJvbWlzZWQgZGlzdHJpYnV0aW9uLgpXZSBjYW4gdGhlbiBleGNsdWRlIHRoZSBjZWxscyB0aGF0IGFyZSBtb3JlIGxpa2VseSB0byBiZSBjb21wcm9taXNlZC4KClRvIHVzZSBgbWlRQ2AsIHdlIGZpcnN0IGZpdCBhIG1vZGVsIHRvIHRoZSBkYXRhIGluIG91ciBTQ0Ugb2JqZWN0OgoKYGBge3IgbWlRQyBtb2RlbCwgbGl2ZT1UUlVFfQojIGZpdCB0aGUgbWlRQyBtb2RlbAptaXFjX21vZGVsIDwtIG1pUUM6Om1peHR1cmVNb2RlbChmaWx0ZXJlZF9zY2UpCmBgYAoKTm93IHdlIGNhbiBwbG90IHRoZSBtb2RlbCByZXN1bHRzIHVzaW5nIHRoZSBgcGxvdE1vZGVsKClgIGZ1bmN0aW9uIHRvIHNlZSBob3cgaXQgY29ycmVzcG9uZHMgdG8gb3VyIGRhdGEuCldlIHNob3VsZCBleHBlY3QgdG8gc2VlIHR3byBmaXQgbGluZXM6CgotIE9uZSBsaW5lIHdpbGwgY29ycmVzcG9uZCB0aGUgdGhlICJoZWFsdGh5IiBjZWxscyBhbmQgc2hvdWxkIHNob3cgbGl0dGxlIHRvIG5vIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIHRoZSBudW1iZXIgb2YgdW5pcXVlIGdlbmVzIGRldGVjdGVkIGFuZCB0aGUgbWl0b2Nob25kcmlhbCBwZXJjZW50YWdlLgotIEJ5IGNvbnRyYXN0LCB0aGUgbGluZSB0aGF0IGNvcnJlc3BvbmRzIHRvICJjb21wcm9taXNlZCIgY2VsbHMgd2lsbCBzaG93IGEgbmVnYXRpdmUgcmVsYXRpb25zaGlwIGJldHdlZW4gdGhlIG51bWJlciBvZiB1bmlxdWUgZ2VuZXMgZGV0ZWN0ZWQgYW5kIHRoZSBtaXRvY2hvbmRyaWFsIHBlcmNlbnRhZ2UuCgpUaGlzIHBsb3Qgd2lsbCBhbHNvIHNob3csIGZvciBlYWNoIGNlbGwsIHRoZSBwb3N0ZXJpb3IgcHJvYmFiaWxpdHkgdGhhdCB0aGUgY2VsbCBpcyBkZXJpdmVkIGZyb20gdGhlIGNvbXByb21pc2VkIGRpc3RyaWJ1dGlvbjsgYSBoaWdoZXIgc2NvcmUgaW5kaWNhdGVzIHRoYXQgYSBjZWxsIGlzIG1vcmUgbGlrZWx5IHRvIGJlIGNvbXByb21pc2VkLgoKSXQgaXMgYWxzbyBjcml0aWNhbCB0byBub3RlIHRoYXQgdGhpcyBtb2RlbCBjYW4gX2FuZCBkb2VzXyBmYWlsIGF0IHRpbWVzLgpQbG90dGluZyB0aGUgcmVzdWx0cyBhcyB3ZSBoYXZlIGRvbmUgaGVyZSBpcyBub3QgYSBzdGVwIHRvIHNraXAuCioqQWx3YXlzIGxvb2sgYXQgeW91ciBkYXRhISoqCgpgYGB7ciBtaVFDIHBsb3RNb2RlbCwgbGl2ZT1UUlVFfQojIHBsb3QgdGhlIG1pUUMgbW9kZWwKbWlRQzo6cGxvdE1vZGVsKGZpbHRlcmVkX3NjZSwgbWlxY19tb2RlbCkgKwogIHRoZW1lX2J3KCkKYGBgCgpXZSBjYW4gbm93IGZpbHRlciBvdXIgZGF0YSBiYXNlZCBvbiB0aGUgcHJvYmFiaWxpdHkgY29tcHJvbWlzZWQgYXMgY2FsY3VsYXRlZCBmcm9tIHRoZSBtb2RlbC4KQnV0IGJlZm9yZSB3ZSBkbyB0aGF0LCB3ZSBtaWdodCB3YW50IHRvIHF1aWNrbHkgcGxvdCB0byBzZWUgd2hhdCB3b3VsZCBiZSBmaWx0ZXJlZCBvdXQgd2l0aCBhIGdpdmVuIGN1dG9mZiwgdXNpbmcgdGhlIGBwbG90RmlsdGVyaW5nKClgIGZ1bmN0aW9uLgpUaGUgZGVmYXVsdCBpcyB0byBleGNsdWRlIGNlbGxzIHRoYXQgaGF2ZSBhIHBvc3RlcmlvciBwcm9iYWJpbGl0eSBvZiAwLjc1IG9yIGdyZWF0ZXIgb2YgYmVpbmcgY29tcHJvbWlzZWQuCldlIHN0aWNrIHdpdGggdGhhdCBkZWZhdWx0LCBidXQgZm9yIGNsYXJpdHksIHdlIHdpbGwgYWxzbyBpbmNsdWRlIGl0IGluIG91ciBjb2RlIQoKCmBgYHtyIG1pUUMgcGxvdEZpbHRlcmluZ30KIyBsb29rIGF0IG1pUUMgZmlsdGVyaW5nCm1pUUM6OnBsb3RGaWx0ZXJpbmcoZmlsdGVyZWRfc2NlLCBtaXFjX21vZGVsLAogICAgICAgICAgICAgICAgICAgIHBvc3Rlcmlvcl9jdXRvZmYgPSAwLjc1KSArCiAgdGhlbWVfYncoKQpgYGAKCkluIHRoaXMgY2FzZSwgdGhlIGxpbmUgYmV0d2VlbiB0aGUgY2VsbHMgdG8gYmUga2VwdCBhbmQgdGhvc2UgdGhhdCB3aWxsIGJlIHJlbW92ZWQgc2VlbXMgdG8gY29ycmVzcG9uZCB0byBhIG1pdG9jaG9uZHJpYWwgcGVyY2VudGFnZSBvZiBhYm91dCAxMi41JSwgYnV0IG5vdGUgdGhhdCB0aGlzIHdpbGwgbm90IGFsd2F5cyBiZSBjb25zdGFudC4KVGhlIGN1dG9mZiBwb2ludCBjYW4gdmFyeSBmb3IgZGlmZmVyZW50IG51bWJlcnMgb2YgdW5pcXVlIGdlbmVzIHdpdGhpbiBhIHNhbXBsZSwgYW5kIGl0IHdpbGwgY2VydGFpbmx5IHZhcnkgYW1vbmcgc2FtcGxlcyEKCkF0IHRoaXMgcG9pbnQsIHdlIGNhbiBwZXJmb3JtIHRoZSBhY3R1YWwgZmlsdGVyaW5nIHVzaW5nIHRoZSBgZmlsdGVyQ2VsbHMoKWAgZnVuY3Rpb24sIGdpdmluZyB1cyBhIGZ1cnRoZXIgZmlsdGVyZWQgU0NFIG9iamVjdC4KCmBgYHtyIG1pUUMgZmlsdGVyY2VsbHMsIGxpdmU9VFJVRX0KIyBwZXJmb3JtIG1pUUMgZmlsdGVyaW5nCnFjZmlsdGVyZWRfc2NlIDwtIG1pUUM6OmZpbHRlckNlbGxzKGZpbHRlcmVkX3NjZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbW9kZWwgPSBtaXFjX21vZGVsKQpgYGAKCiMjIyMgT25lIG1vcmUgZmlsdGVyOiB1bmlxdWUgZ2VuZSBjb3VudAoKV2hpbGUgdGhlIG1pUUMgZmlsdGVyaW5nIGlzIHByZXR0eSBnb29kLCB5b3UgbWF5IGhhdmUgbm90aWNlZCB0aGF0IGl0IHN0aWxsIGxlZnQgc29tZSBjZWxscyB0aGF0IGhhZCB2ZXJ5IGxvdyBudW1iZXJzIG9mIHVuaXF1ZSBnZW5lcy4KV2hpbGUgdGhlc2UgY2VsbHMgbWF5IG5vdCBiZSBjb21wcm9taXNlZCwgdGhlIGluZm9ybWF0aW9uIGZyb20gdGhlbSBpcyBhbHNvIG5vdCBsaWtlbHkgdG8gYmUgdXNlZnVsLCBzbyB3ZSB3aWxsIGZpbHRlciB0aG9zZSBhcyB3ZWxsLgpXZSB3aWxsIG9ubHkga2VlcCBjZWxscyB0aGF0IGhhdmUgYXQgbGVhc3QgMjAwIHVuaXF1ZSBnZW5lcy4KCmBgYHtyIHVuaXF1ZSBjdXRvZmYsIGxpdmU9VFJVRX0KIyBmaWx0ZXIgY2VsbHMgYnkgdW5pcXVlIGdlbmUgY291bnQgKGBkZXRlY3RlZGApCnFjZmlsdGVyZWRfc2NlIDwtIHFjZmlsdGVyZWRfc2NlWywgd2hpY2gocWNmaWx0ZXJlZF9zY2UkZGV0ZWN0ZWQgPj0gMjAwKV0KcWNmaWx0ZXJlZF9zY2UKYGBgCgoKIyMgTm9ybWFsaXphdGlvbgoKTm93IHRoYXQgd2UgaGF2ZSBkb25lIG91ciBmaWx0ZXJpbmcsIHdlIGNhbiBzdGFydCBhbmFseXppbmcgdGhlIGV4cHJlc3Npb24gY291bnRzIGZvciB0aGUgcmVtYWluaW5nIGNlbGxzLgoKVGhlIG5leHQgc3RlcCBhdCB0aGlzIHBvaW50IGlzIHRvIGNvbnZlcnQgdGhlIHJhdyBjb3VudHMgaW50byBhIG1lYXN1cmUgdGhhdCBhY2NvdW50cyBmb3IgZGlmZmVyZW5jZXMgaW4gc2VxdWVuY2luZyBkZXB0aCBiZXR3ZWVuIGNlbGxzLCBhbmQgdG8gY29udmVydCB0aGUgZGlzdHJpYnV0aW9uIG9mIGV4cHJlc3Npb24gdmFsdWVzIGZyb20gdGhlIHNrZXdlZCBkaXN0cmlidXRpb24gd2UgZXhwZWN0IHRvIHNlZSBpbiByYXcgY291bnRzIHRvIG9uZSB0aGF0IGlzIG1vcmUgbm9ybWFsbHkgZGlzdHJpYnV0ZWQuCgpXZSB3aWxsIGRvIHRoaXMgdXNpbmcgZnVuY3Rpb25zIGZyb20gdGhlIGBzY3JhbmAgYW5kIGBzY3V0dGxlYCBwYWNrYWdlcy4KVGhlIHByb2NlZHVyZSB3ZSB3aWxsIHVzZSBoZXJlIGlzIGRlcml2ZWQgZnJvbSB0aGUgW09TQ0EgY2hhcHRlciBvbiBub3JtYWxpemF0aW9uXShodHRwOi8vYmlvY29uZHVjdG9yLm9yZy9ib29rcy8zLjE2L09TQ0EuYmFzaWMvbm9ybWFsaXphdGlvbi5odG1sI25vcm1hbGl6YXRpb24tYnktZGVjb252b2x1dGlvbikuClRoZSBpZGVhIGlzIHRoYXQgdGhlIHZhcnlpbmcgZXhwcmVzc2lvbiBwYXR0ZXJucyB0aGF0IGRpZmZlcmVudCBjZWxsIHR5cGVzIGV4aGliaXQgd2lsbCBhZmZlY3QgdGhlIHNjYWxpbmcgZmFjdG9ycyB0aGF0IHdlIHdvdWxkIGFwcGx5LgpUbyBhY2NvdW50IGZvciB0aGF0IHZhcmlhdGlvbiwgd2UgZmlyc3QgZG8gYSByb3VnaCBjbHVzdGVyaW5nIG9mIGNlbGxzIGJ5IHRoZWlyIGV4cHJlc3Npb24gd2l0aCBgc2NyYW46OnF1aWNrQ2x1c3RlcigpYCwgdGhlbiB1c2UgdGhhdCBjbHVzdGVyaW5nIHRvIGNhbGN1bGF0ZSB0aGUgc2NhbGluZyBmYWN0b3IgZm9yIGVhY2ggY2VsbCB3aXRoaW4gdGhlIGNsdXN0ZXJzIHVzaW5nIGBzY3Jhbjo6Y29tcHV0ZVN1bUZhY3RvcnMoKWAuCkZpbmFsbHksIHdlIGFwcGx5IHRoZSBzY2FsaW5nIGZhY3RvciB0byB0aGUgZXhwcmVzc2lvbiB2YWx1ZXMgZm9yIGVhY2ggY2VsbCBhbmQgY2FsY3VsYXRlIHRoZSBsb2ctc2NhbGVkIGV4cHJlc3Npb24gdmFsdWVzIHVzaW5nIHRoZSBgc2N1dHRsZTo6bG9nTm9ybUNvdW50cygpYCBmdW5jdGlvbi4KCmBgYHtyIG5vcm1hbGl6YXRpb24sIGxpdmU9VFJVRX0KIyBQZXJmb3JtIHJvdWdoIGNsdXN0ZXJpbmcKcWNsdXN0IDwtIHNjcmFuOjpxdWlja0NsdXN0ZXIocWNmaWx0ZXJlZF9zY2UpCgojIHVzZSBjbHVzdGVycyB0byBjb21wdXRlIHNjYWxpbmcgZmFjdG9ycyBhbmQgYWRkIHRvIFNDRSBvYmplY3QKcWNmaWx0ZXJlZF9zY2UgPC0gc2NyYW46OmNvbXB1dGVTdW1GYWN0b3JzKHFjZmlsdGVyZWRfc2NlLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2x1c3RlcnMgPSBxY2x1c3QpCgojIHBlcmZvcm0gbm9ybWFsaXphdGlvbiB1c2luZyBzY2FsaW5nIGZhY3RvcnMKIyBhbmQgc2F2ZSBhcyBhIG5ldyBTQ0Ugb2JqZWN0Cm5vcm1hbGl6ZWRfc2NlIDwtIHNjdXR0bGU6OmxvZ05vcm1Db3VudHMocWNmaWx0ZXJlZF9zY2UpCmBgYAoKVGhpcyBjcmVhdGVzIGEgbmV3ICJhc3NheSIgaW4gdGhlIGBub3JtYWxpemVkX3NjZWAgb2JqZWN0LCBgbG9nY291bnRzYCwgd2hpY2ggY29udGFpbnMgdGhlIG5vcm1hbGl6ZWQgY291bnQgdmFsdWVzIGZvciBlYWNoIGNlbGwgYW5kIGdlbmUuCihUaGUgZGF0YSBoZXJlIGFyZSBub3QgX2FjdHVhbGx5XyB0aGUgbG9nIG9mIHRoZSBjb3VudHMsIHNpbmNlIHdlIGFsc28gYXBwbGllZCB0aGUgc2NhbGluZyBmYWN0b3JzLCBidXQgdGhhdCBuYW1lIGlzIHVzZWQgZm9yIGhpc3RvcmljYWwgcmVhc29ucy4pCgpMZXQncyB0YWtlIGEgbG9vazoKCmBgYHtyIG5vcm1hbGl6ZWQgc2NlfQpub3JtYWxpemVkX3NjZQpgYGAKCiMjIERpbWVuc2lvbiByZWR1Y3Rpb24KCiFbU2luZ2xlLWNlbGwgcm9hZG1hcDogRGltZW5zaW9uIHJlZHVjdGlvbl0oZGlhZ3JhbXMvcm9hZG1hcF9zaW5nbGVfZGltZW5zaW9uX3JlZHVjdGlvbi5wbmcpCgpOb3cgdGhhdCB3ZSBoYXZlIG5vcm1hbGl6ZWQgZXhwcmVzc2lvbiB2YWx1ZXMsIHdlIHdvdWxkIGxpa2UgdG8gcHJvZHVjZSBzb21lIHJlZHVjZWQtZGltZW5zaW9uIHJlcHJlc2VudGF0aW9ucyBvZiB0aGUgZGF0YS4KVGhlc2Ugd2lsbCBhbGxvdyB1cyB0byBwZXJmb3JtIHNvbWUgZG93bnN0cmVhbSBjYWxjdWxhdGlvbnMgbW9yZSBxdWlja2x5LCByZWR1Y2Ugc29tZSBvZiB0aGUgbm9pc2UgaW4gdGhlIGRhdGEsIGFuZCBhbGxvdyB1cyB0byB2aXN1YWxpemUgb3ZlcmFsbCByZWxhdGlvbnNoaXBzIGFtb25nIGNlbGxzIG1vcmUgZWFzaWx5ICh0aG91Z2ggd2l0aCBtYW55IGNhdmVhdHMhKS4KCiMjIyBTZWxlY3RpbmcgaGlnaGx5IHZhcmlhYmxlIGdlbmVzCgpXaGlsZSB3ZSBjb3VsZCBjYWxjdWxhdGUgdGhlIHJlZHVjZWQgZGltZW5zaW9ucyB1c2luZyBhbGwgb2YgdGhlIGdlbmVzIHRoYXQgd2UgaGF2ZSBhc3NheWVkLCBpbiBwcmFjdGljZSBtb3N0IG9mIHRoZSBnZW5lcyB3aWxsIGhhdmUgdmVyeSBsaXR0bGUgdmFyaWF0aW9uIGluIGV4cHJlc3Npb24sIHNvIGRvaW5nIHNvIHdpbGwgbm90IHByb3ZpZGUgbXVjaCBhZGRpdGlvbmFsIHNpZ25hbC4KUmVkdWNpbmcgdGhlIG51bWJlciBvZiBnZW5lcyB3ZSBpbmNsdWRlIHdpbGwgYWxzbyBzcGVlZCB1cCBzb21lIG9mIHRoZSBjYWxjdWxhdGlvbnMuCgpUbyBpZGVudGlmeSB0aGUgbW9zdCB2YXJpYWJsZSBnZW5lcywgd2Ugd2lsbCB1c2UgZnVuY3Rpb25zIGZyb20gdGhlIGBzY3JhbmAgcGFja2FnZS4KVGhlIGZpcnN0IGZ1bmN0aW9uLCBgbW9kZWxHZW5lVmFyKClgLCBhdHRlbXB0cyB0byBkaXZpZGUgdGhlIHZhcmlhdGlvbiBvYnNlcnZlZCBmb3IgZWFjaCBnZW5lIGludG8gYSBiaW9sb2dpY2FsIGFuZCB0ZWNobmljYWwgY29tcG9uZW50LCB3aXRoIHRoZSBpbnR1aXRpb24gdGhhdCBnZW5lcyB3aXRoIGxvd2VyIG1lYW4gZXhwcmVzc2lvbiB0ZW5kIHRvIGhhdmUgbG93ZXIgdmFyaWFuY2UgZm9yIHB1cmVseSB0ZWNobmljYWwgcmVhc29ucy4KV2UgdGhlbiBwcm92aWRlIHRoZSBgbW9kZWxHZW5lVmFyKClgIG91dHB1dCB0byB0aGUgYGdldFRvcEhWR3MoKWAgZnVuY3Rpb24gdG8gaWRlbnRpZnkgdGhlIGdlbmVzIHdpdGggdGhlIGhpZ2hlc3QgX2Jpb2xvZ2ljYWxfIHZhcmlhdGlvbiwgd2hpY2ggaXMgd2hhdCB3ZSBhcmUgbW9zdCBpbnRlcmVzdGVkIGluLgoKYGBge3Igc2VsZWN0IEhWR3N9CiMgaWRlbnRpZnkgMjAwMCBnZW5lcwpudW1fZ2VuZXMgPC0gMjAwMAoKIyBtb2RlbCB2YXJpYW5jZSwgcGFydGl0aW9uaW5nIGludG8gYmlvbG9naWNhbCBhbmQgdGVjaG5pY2FsIHZhcmlhdGlvbgpnZW5lX3ZhcmlhbmNlIDwtIHNjcmFuOjptb2RlbEdlbmVWYXIobm9ybWFsaXplZF9zY2UpCgojIGdldCB0aGUgbW9zdCB2YXJpYWJsZSBnZW5lcwpodl9nZW5lcyA8LSBzY3Jhbjo6Z2V0VG9wSFZHcyhnZW5lX3ZhcmlhbmNlLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuID0gbnVtX2dlbmVzKQpgYGAKClRoZSByZXN1bHQgaXMgYSB2ZWN0b3Igb2YgZ2VuZSBpZHMgKG9yZGVyZWQgZnJvbSBtb3N0IHRvIGxlYXN0IHZhcmlhYmxlKToKCmBgYHtyIHZpZXcgSFZHc30KaGVhZChodl9nZW5lcykKYGBgCgojIyMgUHJpbmNpcGFsIGNvbXBvbmVudHMgYW5hbHlzaXMKCk5vdyB0aGF0IHdlIGhhdmUgc2VsZWN0ZWQgdGhlIGdlbmVzIHdlIHdvdWxkIGxpa2UgdG8gdXNlIGZvciB0aGUgcmVkdWNlZC1kaW1lbnNpb24gcmVwcmVzZW50YXRpb25zIG9mIHRoZSBleHByZXNzaW9uIGRhdGEsIHdlIGNhbiBzdGFydCB0byBjYWxjdWxhdGUgdGhlbS4KRmlyc3Qgd2Ugd2lsbCB1c2UgdGhlIGBzY2F0ZXI6OnJ1blBDQSgpYCBmdW5jdGlvbiB0byBjYWxjdWxhdGUgdGhlIHByaW5jaXBhbCBjb21wb25lbnRzIGZyb20gdGhlIGV4cHJlc3Npb24gbWF0cml4LgpUaGlzIHJlcHJlc2VudGF0aW9uIGlzIGZhc3QgYW5kIGZhaXJseSByb2J1c3QsIGJ1dCB0aGUgcmVzdWx0IGlzIHN0aWxsIHF1aXRlIG11bHRpZGltZW5zaW9uYWwuCldlIHdhbnQga2VlcCBhIGZhaXIgbnVtYmVyIG9mIGNvbXBvbmVudHMgKGRpbWVuc2lvbnMpIGluIG9yZGVyIHRvIGFjY3VyYXRlbHkgcmVwcmVzZW50IHRoZSB2YXJpYXRpb24gaW4gdGhlIGRhdGEsIGJ1dCBkb2luZyBzbyBtZWFucyB0aGF0IHBsb3R0aW5nIG9ubHkgYSBmZXcgb2YgdGhlc2UgZGltZW5zaW9ucyAoaW4gMkQpIGlzIG5vdCBsaWtlbHkgdG8gcHJvdmlkZSBhIGZ1bGwgdmlldyBvZiB0aGUgZGF0YS4KClRoZSBkZWZhdWx0IG51bWJlciBvZiBjb21wb25lbnRzIGlzIDUwLCB3aGljaCB3ZSB3aWxsIHN0aWNrIHdpdGgsIGJ1dCBsZXQncyBlbnRlciBpdCBtYW51YWxseSBqdXN0IGZvciB0aGUgcmVjb3JkLgoKYGBge3IgcnVuUENBLCBsaXZlPVRSVUV9CiMgY2FsY3VsYXRlIGFuZCBzYXZlIFBDQSByZXN1bHRzCm5vcm1hbGl6ZWRfc2NlIDwtIHNjYXRlcjo6cnVuUENBKAogIG5vcm1hbGl6ZWRfc2NlLAogIG5jb21wb25lbnRzID0gNTAsICMgaG93IG1hbnkgY29tcG9uZW50cyB0byBrZWVwCiAgc3Vic2V0X3JvdyA9IGh2X2dlbmVzICMgdXNlIG9ubHkgdGhlIHZhcmlhYmxlIGdlbmVzIHdlIGNob3NlCikKYGBgCgpUaGVzZSByZWR1Y2VkLWRpbWVuc2lvbiByZXN1bHRzIHdpbGwgYmUgc3RvcmVkIGluIGEgYHJlZHVjZWREaW1gIHNsb3QgaW4gdGhlIFNDRSBvYmplY3QuCldlIGNhbiBzZWUgdGhlIG5hbWVzIG9mIHRoZSBgcmVkdWNlZERpbWBzIHRoYXQgd2UgaGF2ZSBieSBsb29raW5nIGF0IHRoZSBvYmplY3Qgc3VtbWFyeToKCmBgYHtyIHZpZXcgcmVkdWNlZCBkaW1lbnNpb25zfQpub3JtYWxpemVkX3NjZQpgYGAKCklmIHdlIHdhbnQgdG8gZXh0cmFjdCB0aGUgUENBIHJlc3VsdHMsIHdlIGNhbiBkbyB0aGF0IHdpdGggdGhlIGByZWR1Y2VkRGltKClgIGZ1bmN0aW9uOgpOb3RlIHRoYXQgZm9yIHRoZXNlIHJlZHVjZWQtZGltZW5zaW9uYWxpdHkgbWF0cmljZXMsIHRoZSByb3dzIGFyZSB0aGUgY2VsbHMgYW5kIHRoZSBjb2x1bW5zIGFyZSB0aGUgUEMgZGltZW5zaW9ucy4KCmBgYHtyIGdldFJlZHVjZWREaW19CiMgZXh0cmFjdCB0aGUgUENBIG1hdHJpeApwY2FfbWF0cml4IDwtIHJlZHVjZWREaW0obm9ybWFsaXplZF9zY2UsICJQQ0EiKQoKIyBsb29rIGF0IHRoZSBzaGFwZSBvZiB0aGUgbWF0cml4CmRpbShwY2FfbWF0cml4KQpgYGAKCiMjIyBVTUFQCgpGaW5hbGx5LCB3ZSB3aWxsIGNhbGN1bGF0ZSBhIFVNQVAgKFVuaWZvcm0gTWFuaWZvbGQgQXBwcm94aW1hdGlvbiBhbmQgUHJvamVjdGlvbikgcmVwcmVzZW50YXRpb24gb2Ygb3VyIGRhdGEuClRoaXMgaXMgYSBtYWNoaW5lLWxlYXJuaW5nLWJhc2VkIG1ldGhvZCB0aGF0IGlzIHVzZWZ1bCBmb3IgcGVyZm9ybWluZyBkaW1lbnNpb25hbGl0eSByZWR1Y3Rpb24gc3VpdGFibGUgZm9yIHZpc3VhbGl6YXRpb24uCkl0J3MgZ29hbCBpcyB0byBwcm92aWRlIGEgcmVwcmVzZW50YXRpb24gb2YgdGhlIGRhdGEgaW4gdHdvIGRpbWVuc2lvbnMgKHR5cGljYWxseSwgbW9yZSBhcmUgcG9zc2libGUpIHRoYXQgcHJlc2VydmVzIGFzIG11Y2ggb2YgdGhlIGRpc3RhbmNlIHJlbGF0aW9uc2hpcHMgYW1vbmcgY2VsbHMgYXMgcG9zc2libGUuCldoaWxlIHRoaXMgZG9lcyBtYWtlIGZvciB2aXN1YWxseSBhcHBlYWxpbmcgYW5kIHVzZWZ1bCBwbG90cywgaXQgaXMgaW1wb3J0YW50IG5vdCB0byBvdmVyaW50ZXJwcmV0IHRoZSByZXN1bHRzIQpJbiBwYXJ0aWN1bGFyLCB3aGlsZSB5b3Ugd2lsbCBvZnRlbiBzZWUgc29tZSBhcHBhcmVudCBjbHVzdGVyaW5nIG9mIGNlbGxzIGluIHRoZSByZXN1bHRpbmcgb3V0cHV0LCB0aG9zZSBjbHVzdGVycyBtYXkgbm90IGJlIHBhcnRpY3VsYXJseSB2YWxpZCwgYW5kIHRoZSBzcGFjaW5nIHdpdGhpbiBvciBiZXR3ZWVuIGNsdXN0ZXJzIG1heSBub3QgcmVmbGVjdCB0cnVlIGRpc3RhbmNlcy4KCkluIG1hbnkgd2F5cyB0aGlzIGlzIGFuYWxvZ291cyB0byB0aGUgcHJvYmxlbSBvZiBwcm9qZWN0aW5nIGEgbWFwIG9mIHRoZSBlYXJ0aCBvbnRvIGEgZmxhdCBzdXJmYWNlOyBhbnkgY2hvaWNlIHdpbGwgcmVzdWx0IGluIHNvbWUgZGlzdG9ydGlvbnMuCkhvd2V2ZXIsIHdpdGggVU1BUCwgd2UgcmFyZWx5IGtub3cgZXhhY3RseSB3aGF0IGNob2ljZXMgd2VyZSBtYWRlIGFuZCB3aGF0IGRpc3RvcnRpb25zIG1pZ2h0IGhhdmUgcmVzdWx0ZWQuClRoZSBVTUFQIGNvb3JkaW5hdGVzIHRoZW1zZWx2ZXMgc2hvdWxkIG5ldmVyIGJlIHVzZWQgZm9yIGRvd25zdHJlYW0gYW5hbHlzaXMuCgpTaW5jZSB0aGUgVU1BUCBwcm9jZWR1cmUgd291bGQgYmUgc2xvdyB0byBjYWxjdWxhdGUgd2l0aCB0aGUgZnVsbCBkYXRhLCBzbyB0aGUgYHJ1blVNQVAoKWAgZnVuY3Rpb24gZmlyc3QgY2FsY3VsYXRlcyBhIFBDQSBtYXRyaXggYW5kIHRoZW4gdXNlcyBfdGhhdF8gdG8gY2FsY3VsYXRlIHRoZSBVTUFQLgpTaW5jZSB3ZSBhbHJlYWR5IGhhdmUgYSBQQ0EgbWF0cml4LCB3ZSB3aWxsIHRlbGwgdGhlIGZ1bmN0aW9uIHVzZSB0aGF0IGluc3RlYWQgb2YgcmVjYWxjdWxhdGluZyBpdC4KCmBgYHtyIHJ1blVNQVAsIGxpdmU9VFJVRX0Kbm9ybWFsaXplZF9zY2UgPC0gc2NhdGVyOjpydW5VTUFQKG5vcm1hbGl6ZWRfc2NlLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGltcmVkID0gIlBDQSIpCmBgYAoKQXMgYmVmb3JlLCB3ZSBjb3VsZCBleHRyYWN0IHRoZSBVTUFQIG1hdHJpeCBmcm9tIG91ciBTQ0Ugb2JqZWN0IHdpdGggdGhlIGByZWR1Y2VkRGltKClgIGZ1bmN0aW9uLgpXZSBjYW4gYWxzbyB2aXN1YWxpemUgdGhlIFVNQVAgcmVzdWx0cyB1c2luZyB0aGUgYHBsb3RSZWR1Y2VkRGltKClgIGZ1bmN0aW9uLgoKYGBge3IgcGxvdFJlZHVjZWREaW0sIGxpdmU9VFJVRX0KIyBwbG90IHRoZSBVTUFQCnNjYXRlcjo6cGxvdFJlZHVjZWREaW0obm9ybWFsaXplZF9zY2UsCiAgICAgICAgICAgICAgICAgICAgICAgIlVNQVAiLAogICAgICAgICAgICAgICAgICAgICAgICMgY29sb3IgYnkgdGhlIG1vc3QgdmFyaWFibGUgZ2VuZQogICAgICAgICAgICAgICAgICAgICAgIGNvbG9yX2J5ID0gaHZfZ2VuZXNbMV0pCmBgYAoKCiMjIFVuc3VwZXJ2aXNlZCBjbHVzdGVyaW5nCgpBcyBhIGZpbmFsIGFuYWx5c2lzIHN0ZXAgYXQgdGhpcyBzdGFnZSwgd2Ugd2lsbCByZXR1cm4gdG8gdGhlIFBDQSByZXN1bHRzIHRvIHBlcmZvcm0gdW5zdXBlcnZpc2VkIGNsdXN0ZXJpbmcuCkhlcmUgd2Ugd2lsbCB1c2UgYSBncmFwaC1iYXNlZCBjbHVzdGVyaW5nIG1ldGhvZCwgd2hpY2ggc3RhcnRzIGJ5IGlkZW50aWZ5aW5nIGNlbGxzIHRoYXQgYXJlIGNsb3NlIHRvZ2V0aGVyIGluIHRoZSBtdWx0aWRpbWVuc2lvbmFsIHNwYWNlLgpJdCB0aGVuIGlkZW50aWZpZXMgImNvbW11bml0aWVzIiBvZiBoaWdobHkgY29ubmVjdGVkIGNlbGxzLCBhbmQgYnJlYWtzIHRoZW0gYXBhcnQgYnkgcmVnaW9ucyBvZiBsb3dlciBjb25uZWN0aW9uLgoKVGhlcmUgYXJlIGEgbnVtYmVyIG9mIGFsZ29yaXRobXMgdGhhdCBjYW4gcGVyZm9ybSB0aGlzIGNsdXN0ZXJpbmcsIGVhY2ggd2l0aCBwYXJhbWV0ZXJzIHRoYXQgY2FuIGFmZmVjdCBob3cgbWFueSBjbHVzdGVycyBhcmUgaWRlbnRpZmllZCBhbmQgd2hpY2ggY2VsbHMgYmVsb25nIHRvIGVhY2ggY2x1c3Rlci4KSXQgaXMgYWxzbyB3b3J0aCBub3RpbmcgdGhhdCB0aGVzZSBjbHVzdGVycyBtYXkgb3IgbWF5IG5vdCBjb3JyZXNwb25kIHRvICJjZWxsIHR5cGVzIiBieSB3aGF0ZXZlciBkZWZpbml0aW9uIHlvdSBtaWdodCBwcmVmZXIgdG8gdXNlLgpJbnRlcnByZXRhdGlvbiBvZiB0aGVzZSBjbHVzdGVycywgb3Igb3RoZXIgbWVhc3VyZXMgb2YgY2VsbCB0eXBlLCBhcmUgc29tZXRoaW5nIHRoYXQgd2lsbCByZXF1aXJlIG1vcmUgY2FyZWZ1bCBhbmQgbGlrZWx5IG1vcmUgY3VzdG9taXplZCBhbmFseXNpcy4KCldlIHdpbGwgcGVyZm9ybSBvdXIgY2x1c3RlcmluZyB1c2luZyB0aGUgYGJsdXN0ZXJgIHBhY2thZ2UsIHdoaWNoIGNhbiBwZXJmb3JtIG1hbnkgZGlmZmVyZW50IHR5cGVzIG9mIGNsdXN0ZXJpbmcuCkFzIG1lbnRpb25lZCBlYXJsaWVyLCB3ZSBhcmUgdXNpbmcgImdyYXBoIiBjbHVzdGVyaW5nLCB3aGljaCB3ZSBkZWZpbmUgdXNpbmcgdGhlIGBOTkdyYXBoUGFyYW0oKWAgZnVuY3Rpb24uCldpdGhpbiB0aGF0IGFyZSBhIG51bWJlciBvZiBmdXJ0aGVyIG9wdGlvbnMsIHN1Y2ggYXMgdGhlIHdlaWdodGluZyB1c2VkIGZvciBidWlsZGluZyB0aGUgbmV0d29yayBncmFwaCBhbmQgdGhlIGFsZ29yaXRobSB1c2VkIGZvciBkaXZpZGluZyB0aGUgZ3JhcGggaW50byBjbHVzdGVycy4KCk1vZGlmeWluZyB0aGVzZSBwYXJhbWV0ZXJzIGNhbiByZXN1bHQgaW4gcXVpdGUgZGlmZmVyZW50IGNsdXN0ZXIgYXNzaWdubWVudHMhCkZvciB0aGUgY2x1c3RlcmluZyBiZWxvdyB3ZSB3aWxsIHVzZSBKYWNjYXJkIHdlaWdodGluZyBhbmQgTG91dmFpbiBjbHVzdGVyaW5nLCB3aGljaCBjb3JyZXNwb25kIG1vcmUgY2xvc2VseSB0byB0aGUgZGVmYXVsdCBtZXRob2RzIHVzZWQgYnkgYFNldXJhdGAgdGhhbiB0aGUgZGVmYXVsdCBwYXJhbWV0ZXJzLgpJdCBpcyBhbHNvIHdvcnRoIG5vdGluZyB0aGF0IHRoZSB0aGUgY2x1c3RlciBhc3NpZ25tZW50cyBhcmUgc29tZXdoYXQgc3RvY2hhc3RpYy4KSW4gcGFydGljdWxhciwgdGhlIG5hbWVzL251bWJlcnMgb2YgdGhlIGNsdXN0ZXJzIGNhbiBiZSBxdWl0ZSBpbmNvbnNpc3RlbnQgYmV0d2VlbiBydW5zIQoKCmBgYHtyIGNsdXN0ZXJpbmcsIGxpdmU9VFJVRX0KIyBwZXJmb3JtIGdyYXBoLWJhc2VkIGNsdXN0ZXJpbmcKbm5fY2x1c3RlcnMgPC0gYmx1c3Rlcjo6Y2x1c3RlclJvd3MoCiAgcGNhX21hdHJpeCwKICBibHVzdGVyOjpOTkdyYXBoUGFyYW0oCiAgICAjIG51bWJlciBvZiBuZWlnaGJvcnMgdG8gdXNlIGluIG5ldHdvcmsgZ3JhcGgKICAgIGsgPSAyMCwKICAgICMgd2VpZ2h0aW5nIHNjaGVtZSBmb3IgYnVpbGRpbmcgdGhlIG5ldHdvcmsgZ3JhcGgKICAgICMgZGVmYXVsdCBpcyAicmFuayIKICAgIHR5cGUgPSAiamFjY2FyZCIsCiAgICAjIGNsdXN0ZXIgZGV0ZWN0aW9uIGFsZ29yaXRobQogICAgIyBkZWZhdWx0IGlzICJ3YWxrdHJhcCIKICAgIGNsdXN0ZXIuZnVuID0gImxvdXZhaW4iCiAgKQopCmBgYAoKV2UgY2FuIHNhdmUgdGhlIGNsdXN0ZXIgYXNzaWdubWVudHMgYmFjayBpbnRvIHRoZSBgY29sRGF0YWAgb2YgdGhlIFNDRSBvYmplY3Qgd2l0aCBhIGxpdHRsZSBzaG9ydGN1dDogdGhlIGAkYCBmb2xsb3dlZCBieSB0aGUgbmFtZSBvZiB0aGUgbmV3IGNvbHVtbiB3ZSB3YW50IHRvIGFkZC4KCmBgYHtyIGFkZCBjbHVzdGVycyB0byBTQ0UsIGxpdmU9VFJVRX0KIyBzYXZlIGNsdXN0ZXJzIHRvIFNDRSBjb2xEYXRhCm5vcm1hbGl6ZWRfc2NlJG5uX2NsdXN0ZXIgPC0gbm5fY2x1c3RlcnMKYGBgCgpOb3cgd2UgY2FuIHBsb3QgdGhlIFVNQVAgYWdhaW4sIHRoaXMgdGltZSBjb2xvcmVkIGJ5IHRoZSBjbHVzdGVyIGFzc2lnbm1lbnRzIHRoYXQgd2UganVzdCBjcmVhdGVkLgpIZXJlIHJhdGhlciB0aGFuIHRoZSBnZW5lcmFsIGBwbG90UmVkdWNlZERpbSgpYCBmdW5jdGlvbiwgd2Ugd2lsbCB1c2UgYHBsb3RVTUFQKClgLCB3aGljaCBpcyBleGFjdGx5IHRoZSBzYW1lLCBleGNlcHQgaXQgYWx3YXlzIHBsb3RzIGZyb20gdGhlIGByZWR1Y2VkRGltYCBzbG90IG5hbWVkIGBVTUFQYCwgc28gd2UgY2FuIHNraXAgdGhhdCBhcmd1bWVudC4KCmBgYHtyIHBsb3QgY2x1c3RlcnMsIGxpdmU9VFJVRX0KIyBwbG90IFVNQVAgd2l0aCBhc3NpZ25lZCBjbHVzdGVycwpzY2F0ZXI6OnBsb3RVTUFQKG5vcm1hbGl6ZWRfc2NlLAogICAgICAgICAgICAgICAgIGNvbG9yX2J5ID0gIm5uX2NsdXN0ZXIiKQpgYGAKCldoYXQgZG8geW91IHNlZSBpbiB0aGVzZSByZXN1bHRzPwoKV2hhdCB3b3VsZCB5b3Ugd2FudCB0byBkbyBuZXh0PwoKIyMgU2F2ZSBTQ0Ugb2JqZWN0IGZvciBsYXRlcgoKV2Ugd2lsbCBub3cgc2F2ZSBvdXIgZmlsdGVyZWQgYW5kIG5vcm1hbGl6ZWQgb2JqZWN0LCBpbmNsdWRpbmcgdGhlIGRpbWVuc2lvbiByZWR1Y3Rpb24gYW5kIGNsdXN0ZXJpbmcgcmVzdWx0cyB0byBhbiBgUkRTYCBmaWxlLCB1c2luZyB0aGUgZmlsZSBwYXRoIHRoYXQgd2UgZGVmaW5lZCBhdCB0aGUgc3RhcnQgb2YgdGhlIG5vdGVib29rLgpJZiB3ZSB3ZXJlIHRvIHdhbnQgdG8gcmV0dXJuIHRvIHRoaXMgZGF0YSwgd2UgY291bGQgbG9hZCB0aGlzIGZpbGUgZGlyZWN0bHkgaW50byBhIG5ldyBSIHNlc3Npb24gYW5kIG5vdCBoYXZlIHRvIHJlcGVhdCB0aGUgcHJvY2Vzc2luZyB0aGF0IHdlIGhhdmUgZG9uZSB1cCB0byB0aGlzIHBvaW50LgoKVGhlIGRhdGEgaW4gdGhlc2Ugb2JqZWN0cyB0ZW5kcyB0byBiZSBxdWl0ZSBsYXJnZSwgYnV0IHZlcnkgY29tcHJlc3NpYmxlLgpUbyBzYXZlIHNwYWNlIG9uIGRpc2sgKGF0IHRoZSBleHBlbnNlIG9mIHRpbWUpLCB3ZSB3aWxsIG1ha2Ugc3VyZSB0aGF0IHRoZSBkYXRhIGlzIGNvbXByZXNzZWQgaW50ZXJuYWxseSBiZWZvcmUgd3JpdGluZyBpdCBvdXQgdG8gYSBmaWxlLgpOb3RlIHRoYXQgdGhlIGZpbGUgd2Ugd3JpdGUgaXMgc3RpbGwgZ29pbmcgdG8gYmUgYW4gYC5yZHNgIGZpbGUgd2l0aCBubyBhZGRpdGlvbmFsIGV4dGVuc2lvbi4KKEZ1cnRoZXIgbm90ZTogVGhlIGJhc2UgUiBmdW5jdGlvbiBgc2F2ZVJEUygpYCB1c2VzIGNvbXByZXNzaW9uIGJ5IGRlZmF1bHQsIGJ1dCB0aGUgYHRpZHl2ZXJzZWAgZnVuY3Rpb24gYHJlYWRyOjp3cml0ZV9yZHMoKWAgZG9lcyBub3QuKQoKYGBge3Igc2F2ZSBTQ0UsIGxpdmU9VFJVRX0KIyB3cml0ZSBSRFMgZmlsZSB3aXRoIGNvbXByZXNzaW9uCnJlYWRyOjp3cml0ZV9yZHMobm9ybWFsaXplZF9zY2UsIGZpbGUgPSBvdXRwdXRfc2NlX2ZpbGUsIGNvbXByZXNzID0gImd6IikKYGBgCgoKIyMgUHJpbnQgc2Vzc2lvbiBpbmZvCgpBcyBpcyBvdXIgaGFiaXQgYXQgdGhlIERhdGEgTGFiLCB3ZSB3aWxsIHNhdmUgaW5mb3JtYXRpb24gYWJvdXQgdGhlIGNvbXB1dGluZyBlbnZpcm9ubWVudCwgdGhlIHBhY2thZ2VzIHdlIGhhdmUgdXNlZCBpbiB0aGlzIG5vdGVib29rLCBhbmQgdGhlaXIgdmVyc2lvbnMgdXNpbmcgdGhlIGBzZXNzaW9uSW5mbygpYCBjb21tYW5kLgoKYGBge3Igc2Vzc2lvbiBpbmZvfQpzZXNzaW9uSW5mbygpCmBgYAoK