1 PREAMBLE

1.1 Purpose of this session

This file describes the different steps to perform the first part of the single cell RNAseq data analysis training course for the EBAII n1 2024, covering these steps :

  • Load a single cell raw counts matrix

  • Remove barcodes from empty droplets

  • Esimate and remove ambient RNA contamination (“soup”)

1.2 An important notice

  • These early (but mandatory) steps of the analysis are not covered by the renowned, higher-level analysis frameworks like Seurat nor scran/scater.

  • Thus, we will write/use a certain amount of lines of code, some of which of a higher complexity for R beginners.

  • Newcomer, do not fear !

    • While you are welcome to try writing some by yourself, depending on your R knowledge and self-confidence in it …

    • … you do not have to know how these code work …

    • … thus you should not feel ashamed copy-pasting the pre-written code hereafter …

    • … but in both cases, please try your best understanding what these code do, and even more, why !

    • In this purpose, you may use whatever help you can by searching.

1.3 What to run ?

  • This training presentation, written in Rmarkdown, contains many chunks ( = code blocks)

  • You do not have to run them all !

  • Chunks follow a color code :

  • Chunks you are expected to run :

    ## Example of chunk to run
    getwd()

    Show output

    [1] "/shared/projects/2422_ebaii_n1/atelier_scrnaseq/RMD/Preproc.1"


  • Chunks you are NOT expected to run (we will demo them). You may run them but if you are late afterwards, you’ll be the one to blame !

    ## Example of chunk to NOT run
    getwd()

    Show output

    [1] "/shared/projects/2422_ebaii_n1/atelier_scrnaseq/RMD/Preproc.1"


  • Additional exercises / questions beyond the scope of the training course, you may try if you feel bored

    ## Example of chunk to play with but beyond the scope
    getwd()

    Show output

    [1] "/shared/projects/2422_ebaii_n1/atelier_scrnaseq/RMD/Preproc.1"


  • Questions we would like you to answer during the training course

    What's the Answer to Life ? The Universe ? Everything ??


  • Answers to these

    cat("The Answer to the Great Question... is ... 
    Forty-two. Six by nine : forty-two. That's it. 
    That's all there is. (I always thought something was 
    fundamentally wrong with the universe.)")

    Show output

    The Answer to the Great Question... is ... 
    Forty-two. Six by nine : forty-two. That's it. 
    That's all there is. (I always thought something was 
    fundamentally wrong with the universe.)


2 Start Rstudio



3 Warm-up

  • We set common parameters we will use throughout this session :
## Set your project name
project_name <- "ebaii_sc_teachers"  # Do not copy-paste this ! It's MY project ! Put yours !!

## Seed for the RNG
my_seed <- 1337L

## Empty droplets max p-value
max_p <- 1E-03


4 Prepare the data structure

  • During the current and remaining training sessions, we will work on different data objects, some retrieved from external resources, some we will create.

  • In order to reuse these for further steps, or other sessions, we need to store them, thus create a directory structure on the cluster.

  • We will create this structure in your project folder (the one you registered at the IFB Core, in which you stored your own data for the tutoring).

  • To ensure that you will always use the same structure for your work, the code hereafter has been writen so that you just have to define a single variable that corresponds to your project name.

    • In my case, as a teacher for the SC training, it is ebaii_sc_teachers.

4.1 Main directory

  • Create the training course directory
## Prepare the path
TD_dir <- paste0("/shared/projects/", project_name, "/SC_TD")

## Create the root directory
dir.create(path = TD_dir, recursive = TRUE)

## Print the root directory on-screen
print(TD_dir)
[1] "/shared/projects/ebaii_sc_teachers/SC_TD"

4.2 Current session

## Create the session (Preproc.1) directory
session_dir <- paste0(TD_dir, "/01_Preproc.1")
dir.create(path = session_dir, recursive = TRUE)

## Print the session directory on-screen
print(session_dir)
[1] "/shared/projects/ebaii_sc_teachers/SC_TD/01_Preproc.1"

4.3 Input data directory

## Create the INPUT data directory
input_dir <- paste0(session_dir, "/DATA")
dir.create(path = input_dir, recursive = TRUE)

## Print the input directory on-screen
print(input_dir)
[1] "/shared/projects/ebaii_sc_teachers/SC_TD/01_Preproc.1/DATA"

4.4 Output results directory

## Create the OUTPUT data directory
output_dir <- paste0(session_dir, "/RESULTS")
dir.create(path = output_dir, recursive = TRUE)

## Print the output directory on-screen
print(output_dir)
[1] "/shared/projects/ebaii_sc_teachers/SC_TD/01_Preproc.1/RESULTS"



Note : Actually, for this session we won’t export any result, so creating this RESULTS folder was just for practice.


Now, the we have everything needed to :

  • load data (input_dir)

  • work on it (in memory)

  • save the results we will generate (output_dir).



5 Load a 10X Cell Ranger raw count matrix

5.1 The dataset

  • The data we will work on is hosted on Zenodo. To ease its retrieval, we can define its ID as a variable

    ## This is actually to help me updating this training without hassle
    zen_id <- "14034221"
  • Here will we train ourselves to load into R a single cell RNAseq data produced by 10X’s Cell Ranger.

  • We will work with a public dataset provided by the manufacturer, that consists in ~ 10,000 PBMC (peripheral bone marrow cells) from a human donor (reduced to 1/5th).

  • The experiment was performed with the 3’ capture kit v3.

  • The analysis was performed with Cell Ranger v3, mapping on the GRCh38-2020-A manufacturer reference.

  • The data consists into the typical 10x 3-files structure, hosted in a Zenodo respository (Id : 14034221)

    • barcodes.tsv.gz : the list of putative droplet barcodes
    • features.tsv.gz : the list of features (genes)
    • matrix.mtx.gz : the feature x barcode expression count matrix

5.2 Download data

  • The code chunk here after is written to :

    • let you download the data files from

      • Zenodo by default

      • a local backup if backup is set to TRUE (if we loose the internet connection)

    • if the desired data is already available locally :

      • load this rather than download

      • force a new-download if force is set to TRUE

### Named files (will be used later on !)
mtx_file <- "matrix.mtx.gz"
features_file <- "features.tsv.gz"
barcodes_file <- "barcodes.tsv.gz"
summary_file <- "pbmc_10k_v3_summary.html"

## Filename(s) to retrieve
toget_files <- c(mtx_file,
                 features_file,
                 barcodes_file,
                 summary_file)

## Folder to store retrieved files
local_folder <- input_dir

## Use local backup ?
backup <- FALSE
if(backup) message("Using local backup !")

## Force download ?
force <- FALSE
if(force) message("Forcing (re)download !")

### Define remote folder
remote_folder <- if (backup) "/shared/projects/2422_ebaii_n1/atelier_scrnaseq/TD/BACKUP/10X/" else paste0("https://zenodo.org/records/", zen_id, "/files/")

### Reconstruct the input paths
remote_path <- paste0(remote_folder, "/", toget_files)

### Reconstruct the output paths
local_path <- paste0(local_folder, "/", toget_files)

## Retrieve files (if they don't exist), in loop
for (tg in seq_along(toget_files)) {
  ## If the file does not locally exist
  if (!file.exists(local_path[tg]) | force) {
    ## Retrieve data
    if(backup) {
      file.copy(from = remote_path[tg],
                to = local_path[tg])
    } else {
      download.file(url = remote_path[tg], 
                    destfile = local_path[tg])
    }
    ## Check if downloaded files exist locally
    if(file.exists(local_path[tg])) message("\tOK")
  } else message(paste0(toget_files[tg], " already downloaded !"))
}
matrix.mtx.gz already downloaded !
features.tsv.gz already downloaded !
barcodes.tsv.gz already downloaded !
pbmc_10k_v3_summary.html already downloaded !

5.3 Load into R

  • Load the 10X data files into R :

    How ?
    • A good starter
    ## https://lmddgtfy.net/?q=load%2010x%20data%20in%20R
    • Back to your local source
    ## Reading the function help page
    ?Seurat::Read10X




    ## Loading unfiltered 10X matrix
    scmat <- Seurat::Read10X(data.dir = input_dir)


  • However, we don’t know what the loaded object actually looks like in R…

  • Questions :

    • Do you know a way to …
    ... know what IS the type of object that was created ?


    ## To know the type of an R object : methods::is()
    ?methods::is
    ## Get the type
    methods::is(scmat)

    Show output

    [1] "dgCMatrix"     "CsparseMatrix" "dsparseMatrix" "generalMatrix"
    [5] "AnyMatrix"     "V3Matrix"      "dMatrix"       "sparseMatrix" 
    [9] "Matrix"       



    ... observe the STRucture of the resulting object ?


    ## To get a basic structure description : utils::str()
    ?utils::str
    ## Get the basic structure
    utils::str(scmat)

    Show output

    Formal class 'dgCMatrix' [package "Matrix"] with 6 slots
      ..@ i       : int [1:1718404] 7922 18295 23126 24791 31764 32866 13690 17058 2511 33306 ...
      ..@ p       : int [1:147457] 0 0 0 0 0 0 6 7 8 8 ...
      ..@ Dim     : int [1:2] 33694 147456
      ..@ Dimnames:List of 2
      .. ..$ : chr [1:33694] "RP11-34P13.3" "FAM138A" "OR4F5" "RP11-34P13.7" ...
      .. ..$ : chr [1:147456] "TCCCGATAGCCTTGAT-1" "AGATCTGAGACTGTAA-1" "GCTTGAAGTGCGAAAC-1" "ATCATGGGTATTCGTG-1" ...
      ..@ x       : num [1:1718404] 1 1 1 1 1 1 1 1 1 1 ...
      ..@ factors : list()


5.4 Features cleanup

Seurat does not like special underscores (_) in feature names …

We will have to :

  • control if our dataset contains some ?

    ## Check feature names with underscore(s)
    ### Here we use base::grep that allows to look for character strings (x) 
    ### containing a defined set of character(s) (pattern), returning their 
    ### position (value = FALSE) or value (value = TRUE)
    base::grep(pattern = '_', 
         x = rownames(scmat), 
         value = TRUE)

    Show output

     [1] "RP11-442N24__B.1" "RP11-99J16__A.2"  "RP11-59D5__B.2"   "RP11-445L13__B.3"
     [5] "RP11-544L8__B.4"  "XXyac-YX65C7_A.2" "XXyac-YX65C7_A.3" "RP11-524D16__A.3"
     [9] "XX-DJ76P10__A.2"  "RP11-1157N2__B.2" "RP1-213J1P__B.1"  "RP1-213J1P__B.2" 
    [13] "RP4-633O19__A.1"  "RP4-754E20__A.5"  "CTA-280A3__B.2"  


  • if so, clean it

    ## Clean features : replacing underscores by dashes
    base::rownames(scmat) <- base::gsub(
      pattern = "_", 
      replacement = "-", 
      x = rownames(scmat))
  • control the efficiency of our cleaning

    ## Post-cleanup control
    grep(pattern = "_", 
         x = rownames(scmat), 
         value = TRUE)

    Show output

    character(0)




6 Empty droplets

  • While a large part of our barcodes contain no count at all, barcodes with at least one total count may not mandatorily correspond to a real cell, due to ambient RNA.

  • Here we want to identify empty droplets, then remove them from the matrix.

6.1 Detection

  • To detect the margins of “true” cells versus empty droplets, we will rely on the kneeplot : a plot of UMI counts per barcode, in function of their increasingly ordered rank.

  • Thus, to perform the kneeplot, we need to rank the barcodes thanks to their total counts.

6.1.1 Ranking barcodes

  • We will use the DropletUtils package

    ## Read the function help page
    ?DropletUtils::barcodeRanks
    ## Generate the rank statistics
    bc_rank <- DropletUtils::barcodeRanks(scmat)
  • Check the ranking

    ## Show a summary of the ranks (ordered for display convenience)
    print(bc_rank[order(bc_rank$rank),])
    DataFrame with 147456 rows and 3 columns
                            rank     total    fitted
                       <numeric> <integer> <numeric>
    ATCATGGCAGCCAATT-1         1     21195        NA
    GGGCATCGTAGCTTGT-1         2     18193        NA
    AGTGGGATCTTAACCT-1         3     16966        NA
    GTCGGGTGTGCAGTAG-1         4     16383        NA
    TTAGGCAAGCCGCCTA-1         5     15406        NA
    ...                      ...       ...       ...
    TTGAACGTCAGAGGTG-1    100998         0        NA
    ACGATGTAGTTGTCGT-1    100998         0        NA
    GTCAAGTTCAGTTGAC-1    100998         0        NA
    TCGCGAGGTTGCGCAC-1    100998         0        NA
    TATCTCAGTTACCAGT-1    100998         0        NA


6.1.2 Kneeplot

Now, we can draw our kneeplot :

## Plot
graphics::plot(bc_rank$rank, 
               bc_rank$total + 1, 
               log = "xy", 
               xlab = "Barcode rank", 
               ylab = "Total UMIs", 
               col = "black", 
               pch = ".", 
               cex = 5, 
               main = "Kneeplot")



  • Question :

    Looking at the kneeplot, can you roughly predict how many barcodes will be kept as cells (ie, as non-empty droplets) ?


    ## . The "cliff" in the kneeplot is at a 
    ##  rank value close to ~ 1,000 counts.

Then, we can use these ranks to identify “true” cells :

## Read the function help page
?DropletUtils::emptyDrops
## Set the RNG seed
set.seed(my_seed)

## Identify empty droplets
bc_rank2 <- DropletUtils::emptyDrops(scmat)

We can show a summary of ordered barcodes (qualified on top) :

## Show a summary of the qualified ranks (ordered for display convenience)
print(bc_rank2[order(bc_rank2$Total, decreasing = TRUE),])
Show output
DataFrame with 147456 rows and 5 columns
                       Total   LogProb    PValue   Limited       FDR
                   <integer> <numeric> <numeric> <logical> <numeric>
ATCATGGCAGCCAATT-1     21195  -13101.3 9.999e-05      TRUE         0
GGGCATCGTAGCTTGT-1     18193  -12055.5 9.999e-05      TRUE         0
AGTGGGATCTTAACCT-1     16966  -11594.8 9.999e-05      TRUE         0
GTCGGGTGTGCAGTAG-1     16383  -11994.6 9.999e-05      TRUE         0
TTAGGCAAGCCGCCTA-1     15406  -10687.9 9.999e-05      TRUE         0
...                      ...       ...       ...       ...       ...
TTGAACGTCAGAGGTG-1         0        NA        NA        NA        NA
ACGATGTAGTTGTCGT-1         0        NA        NA        NA        NA
GTCAAGTTCAGTTGAC-1         0        NA        NA        NA        NA
TCGCGAGGTTGCGCAC-1         0        NA        NA        NA        NA
TATCTCAGTTACCAGT-1         0        NA        NA        NA        NA


6.1.3 Selection

Barcodes considered as empty have no p-value (FDR = NA)

## Creating a validation column (empty droplets have NA as p-value)
bc_rank2$VALID <- !is.na(bc_rank2$FDR) 

## Quantify "real cells"
table(bc_rank2$VALID)
Show output

 FALSE   TRUE 
146397   1059 


We can control the stringency by setting a FDR-adjusted p-value threshold

## We've set the max p-value to 1E-03
bc_rank2$VALID[bc_rank2$FDR >= max_p] <- FALSE

## Control
table(bc_rank2$VALID)
Show output

 FALSE   TRUE 
146581    875 


Now, we can display our true cells on the kneeplot :

## Plot
graphics::plot(bc_rank$rank, 
               bc_rank$total+1, 
               log = "xy", 
               xlab = "Barcode rank", 
               ylab = "Total UMIs", 
               col = ifelse(bc_rank2$VALID, "red", "black"), 
               pch = ".", 
               cex = ifelse(bc_rank2$VALID, 7, 3), 
               main = paste0("Kneeplot (", 
                             length(which(bc_rank2$VALID)), 
                             " barcodes kept)"))
Show plot


  • Question :

    Is there something unexpected for the retained "TRUE" cells in this kneeplot ?


    ## . You may observe that the selection of "true" cells (red)
    ##   does not strictly correspond to a "cut" in the kneeplot
    ##   descending curve.
    ##
    ## . This is because emptyDrops does not only "follow" the curve
    ##   to determine a threshold corresponding to a "minimal count" 
    ##   value to consider a barcode as a cell, but also performs a 
    ##   statistical analysis for each barcode, considering how much 
    ##   its expression profile ressembles the one of other cells, 
    ##   even with a very different (lower) global level of expression.



6.2 Removal

We just need to restrict our count matrix to “true” cells.

  • How many do we have at the moment ?

    dim(scmat)

    Show output

    [1]  33694 147456


  • Filter empty droplets out :

    ## Restrict to valid barcodes
    scmat_cells <- scmat[, bc_rank2$VALID]
    
    ## Control
    dim(scmat_cells)

    Show output

    [1] 33694   875




7 Ambient RNA

  • The counts measured in this matrix of (now considered “true”) single cells does not reflects the measurement of these cells expression only

  • Due to other over-lysed / dying cells in the emulsion, we also measured fraction of ambient RNA that adds up to the “real” expression

  • We may estimate it by observing counts existing at a minimal level across cells that greatly differ in their expression profile

7.1 Estimate the “soup”

SoupX can estimate the rate and composition of ambient RNA, in several ways :

  • Manual mode : providing a list of features expected to reflect the soup (genes expressed at high levels in a majority of the expected cell types)

  • Automatic modes, using the empty droplets-filtered raw count matrix …

    • … along with the unfiltered matrix (ie, containing all quantified droplets). This is the most efficient mode.

    • solely : this is less efficient, but useful when the unfiltered matrix is not available.

## Reading the function help page
?SC.helper::SoupX_auto

Let’s run SoupX :

## In case of error :
# install.packages("irlba", type="source", force=TRUE)

## SoupX
soup_frac <- SC.helper::SoupX_auto(
  scmat_filt = scmat_cells, 
  scmat_raw = scmat)
Show output

Soup-contributing features (Top 20) :


|       |       est| counts|
|:------|---------:|------:|
|MALAT1 | 0.0321703|  17473|
|B2M    | 0.0194002|  10537|
|TMSB4X | 0.0165850|   9008|
|EEF1A1 | 0.0129267|   7021|
|RPL21  | 0.0103251|   5608|
|RPS27  | 0.0100269|   5446|
|RPL13  | 0.0091542|   4972|
|RPL13A | 0.0087252|   4739|
|RPL10  | 0.0082852|   4500|
|RPLP1  | 0.0079225|   4303|
|RPL34  | 0.0078801|   4280|
|RPS12  | 0.0076481|   4154|
|RPS6   | 0.0075211|   4085|
|RPS18  | 0.0074530|   4048|
|RPS27A | 0.0073093|   3970|
|RPS2   | 0.0072965|   3963|
|RPS3A  | 0.0072909|   3960|
|RPL32  | 0.0072744|   3951|
|RPL41  | 0.0067865|   3686|
|RPL11  | 0.0063446|   3446|


NOTE : We can observe MALAT1 as the top first gene.


What’s the estimated fraction of soup in our counts ?

cat("Soup fraction : ", soup_frac)
Show output
Soup fraction :  0.054


  • Question :

    Should we try to remove such an amount of ambient RNA ?


    The best way to know is to try it out !



7.2 Remove the “soup”

SoupX can remove the soup (subtract counts for identified soup genes to all barcodes/cells) using different methods :

  • Automatically (safer)

  • Specifying a fraction of counts to remove.

    • Pros :

      • By removing X % of reads :

        • if soup was effectively present (at a rate <= X), this will mostly affect soup genes first.

        • If not, it will decrease counts globally, thus have negligible effect in differences between cells / cell types.

    • Cons :

      • Counts are already rare in droplet-based single cell technologies… Removing them “by default” makes them even more rare

      • If actual soup rate is higher than the defined rate X to remove, this mode will remain uneffective

## Run SoupX in "removal" mode
scmat_unsoup <- SC.helper::SoupX_auto(
  scmat_filt = scmat_cells, 
  scmat_raw = scmat, 
  return_object = TRUE)
Show output
Counts BEFORE SoupX :  3766216 
Show output
Counts AFTER SoupX :  3563040 
## We can remove the barcode matrix to free some RAM
rm(scmat)


7.3 Visualize the “soup” removal

  • Now, we want to characterize the effect of the removal of the ambient RNA, by comparing the pre- and post- soupX matrices.

  • In this purpose, we will construct a Seurat object from each of these matrices, that we will describe, then represent graphically.

  • But how to create a Seurat object from a count matrix ?

## Reading the function help page
?Seurat::CreateSeuratObject




7.3.1 Create and describe the objects

7.3.1.1 Before SoupX

  • Create the Seurat object
## Create the Seurat object for unsouped cells
sobj <- Seurat::CreateSeuratObject(
  counts = scmat_cells, 
  project = "PBMC10K_BeforeSoupX")
  • Describe

    • We will use a function prepared for you : SC.helper::SeuratObject_descriptor
## Descibe the obtained Seurat object
SC.helper::SeuratObject_descriptor(sobj = sobj)
Show output
OBJECT VERSION :    5.0.2 
PROJECT :   [PBMC10K_BeforeSoupX] 

[ASSAYS]
   ASSAY 1 :    [RNA] [ACTIVE] 
      SLOT/LAYER 1 :    [counts]    Dims:[33694 x 875]  Range:[0.00-724.00] 
         Counts :   3766216 
         Sparsity : 96.14275% 
Show output
      SLOT/LAYER 2 :    [data]  Dims:[0 x 0] 
         Sparsity : NaN% 
Show output
      SLOT/LAYER 3 :    [scale.data]    Dims:[0 x 0] 

[DIMREDS]

[BARCODES METADATA]
orig.ident             Freq
--------------------  -----
PBMC10K_BeforeSoupX     875
NA                        0

 nCount_RNA 
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
    116    3069    3957    4304    5014   21195 

 nFeature_RNA 
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
     89    1052    1242    1300    1484    3752 


7.3.1.2 After SoupX

  • Create the Seurat object
## Create the Seurat object for unsouped cells
sobj_unsoup <- Seurat::CreateSeuratObject(
  counts = scmat_unsoup, 
  project = "PBMC10K_AfterSoupX")
  • Describe
## Describe the new object content
SC.helper::SeuratObject_descriptor(sobj = sobj_unsoup)
Show output
OBJECT VERSION :    5.0.2 
PROJECT :   [PBMC10K_AfterSoupX] 

[ASSAYS]
   ASSAY 1 :    [RNA] [ACTIVE] 
      SLOT/LAYER 1 :    [counts]    Dims:[33694 x 875]  Range:[0.00-699.00] 
         Counts :   3563040 
         Sparsity : 96.35223% 
Show output
      SLOT/LAYER 2 :    [data]  Dims:[0 x 0] 
         Sparsity : NaN% 
Show output
      SLOT/LAYER 3 :    [scale.data]    Dims:[0 x 0] 

[DIMREDS]

[BARCODES METADATA]
orig.ident            Freq
-------------------  -----
PBMC10K_AfterSoupX     875
NA                       0

 nCount_RNA 
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
    113    2912    3735    4072    4754   19797 

 nFeature_RNA 
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
     88    1002    1175    1229    1400    3435 





  • Questions :

    Can you describe the difference (especially for counts and sparsity) ?
    
    Is it expected ?


    ## . Total counts, max value, counts per cell and number of
    ##   expressed features per cell, all have decreased.
    ##
    ## . The sparsity level has slightly increased (removing the soup
    ##   obliterated all counts for some features in some cells).
    ##
    ## . All of this is absolutely expected.


What are the features most affected by the soup removal ?

## Compute the fraction matrix (PRE / POST)
##  NOTE : we have 0 counts in the divider, 
##  so we increment both matrix by +1 !
cont_frac <- (scmat_cells + 1) / (scmat_unsoup + 1)

## Fraction of counts decreased by soup removal, per feature (ordered)
feat_frac <- sort(sparseMatrixStats::rowMeans2(cont_frac), decreasing = TRUE)

### Display Top 5 features
utils::head(feat_frac, decreasing = TRUE)
Show output
     LYZ   S100A9   S100A8  HLA-DRA     CD74     CST3 
1.478310 1.453317 1.384649 1.343360 1.279586 1.204137 
## Removing objects to free some RAM
rm(cont_frac, feat_frac)


## Remove unused objects to free some RAM
rm(scmat_cells, scmat_unsoup)


7.3.2 Plot the “top 1” feature

  • We coded a small R function to help you visualize in 2D your data, even at such a very early step of the analysis.

  • This function will analyze your data (as a Seurat object) in the shadows with default values, and generate a 2D representation called UMAP.

  • As default values are used, they should fit most cases. However, do not fully trust by default its results as a pure ground truth, as your data may not fit these defaults… That’s why the function is nammed “QnD_viz”, as in “Quick and dirty visualization”.

## Reading the function help page
?SC.helper::QnD_viz

7.3.2.1 Before SoupX

## Top1 feature 
umap_pre <- SC.helper::QnD_viz(
  sobj = sobj, 
  features = "LYZ",
  return_plot = TRUE)
Show plot


7.3.2.2 After SoupX

## Top1 feature 
umap_post <- SC.helper::QnD_viz(
  sobj = sobj_unsoup, 
  features = "LYZ",
  return_plot = TRUE)
Show plot




Merging plots for ease of use :

## Using the patchwork package to merge plots (and ggplot2 to add titles)
patchwork::wrap_plots(
  list(
    umap_pre & ggplot2::ggtitle(label = "LYZ before SoupX"), 
    umap_post & ggplot2::ggtitle(label = "LYZ adter SoupX")), 
  nrow = 1)
Show plot


  • Question

    Can you compare the two plots :
      - for the LYZ expression globally ?
      - for the LYZ expression across the 2-D space ?
      - about the space topologies ?


    ## . The level expression measured in cells with smaller expression
    ##   before SoupX has gone to almost none after : the ambient expression
    ##   of this gene has efficiently been removed.
    ##
    ## . Hopefully, the cluster(s) with higher expression remain(s) high.
    ##
    ## . The expression scale upper bound after SoupX is higher than before ?! 
    ##   But we REMOVED some counts ?! While being counter-intuitive, this is
    ##   a positive consequence of the ambient removal on the scaling of
    ##   barcodes expression (see later). 
    ##
    ## . The global topology of clusters remains close (but not identical).







Beyond :

  1. Save the sobj R object on disk.
## A single object : use RDS format with saveRDS()
saveRDS(object = sobj, file = paste0(output_dir, "/sobj.RDS"))
  1. Save both the sobj and sobj_unsoup objects in a single archive on disk.
## Multiple objects : use Rdata format with save()
save(list = c(sobj, sobj_unsoup), file = paste0(output_dir, "/sobjects.RDA"))
  1. Same as 1. but use the “bzip2” compression algorithm
## Compress with bzip2
saveRDS(object = sobj, file = paste0(output_dir, "/sobj.RDS"), compress = "bzip2")







8 Rsession

utils::sessionInfo()
Show output
R version 4.4.1 (2024-06-14)
Platform: x86_64-conda-linux-gnu
Running under: Ubuntu 20.04.6 LTS

Matrix products: default
BLAS/LAPACK: /shared/ifbstor1/software/miniconda/envs/r-4.4.1/lib/libopenblasp-r0.3.27.so;  LAPACK version 3.12.0

locale:
 [1] LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C              
 [3] LC_TIME=en_US.UTF-8        LC_COLLATE=en_US.UTF-8    
 [5] LC_MONETARY=en_US.UTF-8    LC_MESSAGES=en_US.UTF-8   
 [7] LC_PAPER=en_US.UTF-8       LC_NAME=C                 
 [9] LC_ADDRESS=C               LC_TELEPHONE=C            
[11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C       

time zone: Europe/Paris
tzcode source: system (glibc)

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

loaded via a namespace (and not attached):
  [1] matrixStats_1.4.1           spatstat.sparse_3.1-0      
  [3] SC.helper_0.0.6             httr_1.4.7                 
  [5] RColorBrewer_1.1-3          doParallel_1.0.17          
  [7] alabaster.base_1.4.1        tools_4.4.1                
  [9] sctransform_0.4.1           backports_1.5.0            
 [11] utf8_1.2.4                  R6_2.5.1                   
 [13] HDF5Array_1.32.0            lazyeval_0.2.2             
 [15] uwot_0.2.2                  rhdf5filters_1.16.0        
 [17] GetoptLong_1.0.5            withr_3.0.1                
 [19] sp_2.1-4                    gridExtra_2.3              
 [21] progressr_0.14.0            cli_3.6.3                  
 [23] Biobase_2.64.0              spatstat.explore_3.3-2     
 [25] fastDummies_1.7.4           labeling_0.4.3             
 [27] alabaster.se_1.4.1          sass_0.4.9                 
 [29] Seurat_5.1.0                spatstat.data_3.1-2        
 [31] ggridges_0.5.6              pbapply_1.7-2              
 [33] foreign_0.8-86              R.utils_2.12.3             
 [35] parallelly_1.38.0           limma_3.60.6               
 [37] rstudioapi_0.17.0           RSQLite_2.3.7              
 [39] generics_0.1.3              shape_1.4.6.1              
 [41] ica_1.0-3                   spatstat.random_3.3-2      
 [43] dplyr_1.1.4                 Matrix_1.7-1               
 [45] fansi_1.0.6                 S4Vectors_0.42.1           
 [47] abind_1.4-8                 R.methodsS3_1.8.2          
 [49] lifecycle_1.0.4             SoupX_1.6.2                
 [51] yaml_2.3.10                 edgeR_4.2.2                
 [53] SummarizedExperiment_1.34.0 BiocFileCache_2.12.0       
 [55] rhdf5_2.48.0                SparseArray_1.4.8          
 [57] Rtsne_0.17                  grid_4.4.1                 
 [59] blob_1.2.4                  promises_1.3.0             
 [61] dqrng_0.4.1                 ExperimentHub_2.12.0       
 [63] crayon_1.5.3                miniUI_0.1.1.1             
 [65] lattice_0.22-6              beachmat_2.20.0            
 [67] cowplot_1.1.3               KEGGREST_1.44.0            
 [69] pillar_1.9.0                knitr_1.48                 
 [71] ComplexHeatmap_2.20.0       metapod_1.12.0             
 [73] GenomicRanges_1.56.2        rjson_0.2.21               
 [75] future.apply_1.11.2         codetools_0.2-20           
 [77] leiden_0.4.3.1              glue_1.8.0                 
 [79] spatstat.univar_3.0-1       data.table_1.16.2          
 [81] gypsum_1.0.1                vctrs_0.6.5                
 [83] png_0.1-8                   spam_2.11-0                
 [85] gtable_0.3.5                cachem_1.1.0               
 [87] xfun_0.48                   S4Arrays_1.4.1             
 [89] mime_0.12                   DropletUtils_1.24.0        
 [91] survival_3.7-0              SingleCellExperiment_1.26.0
 [93] iterators_1.0.14            statmod_1.5.0              
 [95] bluster_1.14.0              fitdistrplus_1.2-1         
 [97] ROCR_1.0-11                 nlme_3.1-165               
 [99] bit64_4.5.2                 alabaster.ranges_1.4.1     
[101] filelock_1.0.3              RcppAnnoy_0.0.22           
[103] GenomeInfoDb_1.40.1         bslib_0.8.0                
[105] irlba_2.3.5.1               KernSmooth_2.23-24         
[107] rpart_4.1.23                colorspace_2.1-1           
[109] BiocGenerics_0.50.0         DBI_1.2.3                  
[111] Hmisc_5.1-3                 celldex_1.14.0             
[113] nnet_7.3-19                 tidyselect_1.2.1           
[115] curl_5.2.3                  bit_4.5.0                  
[117] compiler_4.4.1              httr2_1.0.1                
[119] htmlTable_2.4.2             BiocNeighbors_1.22.0       
[121] DelayedArray_0.30.1         plotly_4.10.4              
[123] checkmate_2.3.1             scales_1.3.0               
[125] lmtest_0.9-40               rappdirs_0.3.3             
[127] stringr_1.5.1               digest_0.6.37              
[129] goftest_1.2-3               spatstat.utils_3.1-0       
[131] alabaster.matrix_1.4.1      rmarkdown_2.28             
[133] XVector_0.44.0              htmltools_0.5.8.1          
[135] pkgconfig_2.0.3             base64enc_0.1-3            
[137] SingleR_2.6.0               sparseMatrixStats_1.16.0   
[139] MatrixGenerics_1.16.0       dbplyr_2.5.0               
[141] highr_0.11                  fastmap_1.2.0              
[143] rlang_1.1.4                 GlobalOptions_0.1.2        
[145] htmlwidgets_1.6.4           UCSC.utils_1.0.0           
[147] shiny_1.9.1                 DelayedMatrixStats_1.26.0  
[149] farver_2.1.2                jquerylib_0.1.4            
[151] zoo_1.8-12                  jsonlite_1.8.9             
[153] BiocParallel_1.38.0         R.oo_1.26.0                
[155] BiocSingular_1.20.0         magrittr_2.0.3             
[157] Formula_1.2-5               scuttle_1.14.0             
[159] GenomeInfoDbData_1.2.12     dotCall64_1.2              
[161] patchwork_1.3.0             Rhdf5lib_1.26.0            
[163] munsell_0.5.1               Rcpp_1.0.13                
[165] reticulate_1.39.0           alabaster.schemas_1.4.0    
[167] stringi_1.8.4               zlibbioc_1.50.0            
[169] MASS_7.3-61                 AnnotationHub_3.12.0       
[171] plyr_1.8.9                  parallel_4.4.1             
[173] listenv_0.9.1               ggrepel_0.9.6              
[175] deldir_2.0-4                Biostrings_2.72.1          
[177] splines_4.4.1               tensor_1.5                 
[179] circlize_0.4.16             locfit_1.5-9.9             
[181] igraph_2.1.1                spatstat.geom_3.3-3        
[183] RcppHNSW_0.6.0              reshape2_1.4.4             
[185] stats4_4.4.1                ScaledMatrix_1.12.0        
[187] BiocVersion_3.19.1          evaluate_1.0.1             
[189] SeuratObject_5.0.2          BiocManager_1.30.25        
[191] scran_1.32.0                foreach_1.5.2              
[193] httpuv_1.6.15               RANN_2.6.2                 
[195] tidyr_1.3.1                 purrr_1.0.2                
[197] polyclip_1.10-7             future_1.34.0              
[199] clue_0.3-65                 scattermore_1.2            
[201] ggplot2_3.5.1               rsvd_1.0.5                 
[203] xtable_1.8-4                RSpectra_0.16-2            
[205] later_1.3.2                 viridisLite_0.4.2          
[207] tibble_3.2.1                memoise_2.0.1              
[209] AnnotationDbi_1.66.0        IRanges_2.38.1             
[211] cluster_2.1.6               globals_0.16.3             
LS0tCnRpdGxlOiAiPENFTlRFUj5FQkFJSSBuMSAyMDI0IDogU0lOR0xFIENFTEwgQU5BTFlTSVMgVFJBSU5JTkc8QlI+IDxCPlBSRVBST0NFU1NJTkcgKEkpPC9CPjxCUj5Mb2FkIGEgbWF0cml4LCBlbXB0eSBkcm9wbGV0cyBhbmQgYW1iaWVudCBSTkEgZmlsdGVyaW5nPC9DRU5URVI+IgpkYXRlOiAiMjAyNC0xMS0xNy4yMiIKYXV0aG9yOiAiRUJBSUkgbjEgc2NSTkFzZXEgVGVhbSIKb3V0cHV0OgogIGh0bWxfZG9jdW1lbnQ6IAogICAgYmFja2dyb3VuZDogYmxhY2sKICAgIGZpZ19oZWlnaHQ6IDYKICAgIGZpZ193aWR0aDogOAogICAgaGlnaGxpZ2h0OiB0YW5nbyAgIyMgVGhlbWUgZm9yIHRoZSBjb2RlIGNodW5rcwogICAgbnVtYmVyX3NlY3Rpb25zOiB0cnVlICAjIyBBZGRzIG51bWJlciB0byBoZWFkZXJzIChzZWN0aW9ucykKICAgIHRoZW1lOiBmbGF0bHkgICMjIENTUyB0aGVtZSBmb3IgdGhlIEhUTUwgcGFnZQogICAgdG9jOiB0cnVlICAjIyBBZGRzIGEgdGFibGUgb2YgY29udGVudAogICAgdG9jX2Zsb2F0OiAgIyMgVE9DIG9wdGlvbnMKICAgICAgY29sbGFwc2VkOiB0cnVlICAjIyBCeSBkZWZhdWx0LCB0aGUgVE9DIGlzIGZvbGRlZAogICAgICBzbW9vdGhfc2Nyb2xsOiB0cnVlICMjIFNtb290aCBzY3JvbGwgb2YgdGhlIEhUTUwgcGFnZQogICAgc2VsZl9jb250YWluZWQ6IHRydWUgIyMgSW5jbHVkZXMgYWxsIHBsb3RzL2ltYWdlcyB3aXRoaW4gdGhlIEhUTUwKICAgIGNvZGVfZG93bmxvYWQ6IHRydWUgIyMgQWRkcyBhIGJ1dHRvbiB0byBkb3dubG9hZCB0aGUgUm1kCiAgICBjb2RlX2ZvbGRpbmc6IHNob3cKICAgIHRodW1ibmFpbHM6IGZhbHNlCiAgICBsaWdodGJveDogdHJ1ZQogICAgZmlnX2NhcHRpb246IGZhbHNlCiAgICBnYWxsZXJ5OiB0cnVlCiAgICB1c2VfYm9va2Rvd246IHRydWUKYWx3YXlzX2FsbG93X2h0bWw6IHRydWUgIyMgQWxsb3cgcGxhaW4gSFRNTCBjb2RlIGluIHRoZSBSbWQKZWRpdG9yX29wdGlvbnM6IAogIG1hcmtkb3duOiAKICAgIHdyYXA6IDcyCi0tLQoKPCEtLSBBZGQgdGhlIFJvc2NvZmYgYmFubmVyIC0tPgoKYGBge2NzcywgZWNobyA9IEZBTFNFfQpib2R5IHsKICBiYWNrZ3JvdW5kLWltYWdlOiB1cmwoJ2ViYWlpX2Jhbm5lci5wbmcnKTsKICBiYWNrZ3JvdW5kLXJlcGVhdDogbm8tcmVwZWF0OwogIGJhY2tncm91bmQtc2l6ZTogMTAwJTsKfQoKZGl2IHsKICBiYWNrZ3JvdW5kLWNvbG9yOiByZ2JhKDI1NSwgMjU1LCAyNTUsIDAuMzUpICAgLyogMzUlIG9wYXF1ZSB3aGl0ZSAqLzsKICBwYWRkaW5nOiAwLjI1ZW07Cn0KYGBgCgo8IS0tIEFsbG93cyB0byBoaWRlIHRoZSBUT0MgYnkgZGVmYXVsdCwgZGlzcGxheSBpdCB3aXRoIGEgYnV0dG9uLCBtb3ZlIGl0IHRvIHRoZSByaWdodCBvciBsZWZ0IG9mIHRoZSBwYWdlIC0tPgoKYHIgSG1pc2M6OmhpZGluZ1RPQyhidXR0b25MYWJlbCA9ICdTaG93IFRPQycsIGhpZGRlbiA9IFRSVUUsIHRvY1NpZGUgPSAnbGVmdCcsIGJ1dHRvblNpZGU9J2xlZnQnLCBwb3NDb2xsYXBzZSA9ICdtYXJnaW4nLCBsZXZlbHMgPSAzKWAKCmBgYHtyIGtuaXRfc2V0dXAsIGVjaG8gPSBGQUxTRX0KIyBvcHRpb25zKHdpZHRoID0gNjApOwprbml0cjo6b3B0c19jaHVuayRzZXQoCiAgZWNobyA9IFRSVUUsICAgICAgICAjIFByaW50IHRoZSBjb2RlCiAgZXZhbCA9IFRSVUUsICAgICAgICAjIFJ1biBjb21tYW5kIGxpbmVzCiAgbWVzc2FnZSA9IEZBTFNFLCAgICAjIFByaW50IG1lc3NhZ2VzCiAgcHJvbXB0ID0gRkFMU0UsICAgICAjIERvIG5vdCBkaXNwbGF5IHByb21wdAogIGNvbW1lbnQgPSBOQSwgICAgICAgIyBObyBjb21tZW50cyBvbiB0aGlzIHNlY3Rpb24KICB3YXJuaW5nID0gRkFMU0UsICAgICMgRGlzcGxheSB3YXJuaW5ncwogIHRpZHkgPSBGQUxTRSwKICBmaWcuYWxpZ249ImNlbnRlciIsIAogICMgcmVzdWx0cyA9ICdoaWRlJywKICB3aWR0aCA9IDEwMCAgICAgICAjIE51bWJlciBvZiBjaGFyYWN0ZXJzIHBlciBsaW5lCikKYGBgCgo8IS0tIEhvb2sgdG8gaGFuZGxlIG91dHB1dCBmb2xkaW5nIC0tPgoKYGBge3Iga25pdF9ob29rLCBlY2hvID0gRkFMU0V9Cmhvb2tzID0ga25pdHI6OmtuaXRfaG9va3MkZ2V0KCkKaG9va19mb2xkYWJsZSA9IGZ1bmN0aW9uKHR5cGUpIHsKICBmb3JjZSh0eXBlKQogIGZ1bmN0aW9uKHgsIG9wdGlvbnMpIHsKICAgIHJlcyA9IGhvb2tzW1t0eXBlXV0oeCwgb3B0aW9ucykKICAgIAogICAgaWYgKGlzRkFMU0Uob3B0aW9uc1tbcGFzdGUwKCJmb2xkLiIsIHR5cGUpXV0pKSByZXR1cm4ocmVzKQogICAgCiAgICBwYXN0ZTAoCiAgICAgICI8ZGV0YWlscz48c3VtbWFyeT5TaG93ICIsIHR5cGUsICI8L3N1bW1hcnk+XG5cbiIsCiAgICAgIHJlcywKICAgICAgIlxuXG48L2RldGFpbHM+IgogICAgKQogIH0KfQprbml0cjo6a25pdF9ob29rcyRzZXQoCiAgb3V0cHV0ID0gaG9va19mb2xkYWJsZSgib3V0cHV0IiksCiAgcGxvdCA9IGhvb2tfZm9sZGFibGUoInBsb3QiKQopCmBgYAoKPCEtLSBDU1MgdG8gY29sb3IgY2h1bmtzIGFuZCBvdXRwdXRzIC0tPgoKYGBge2NzcywgZWNobz1GQUxTRX0KLm5vdHJ1biB7CiAgYmFja2dyb3VuZC1jb2xvcjogbGlnaHRncmV5ICFpbXBvcnRhbnQ7CiAgYm9yZGVyOiAzcHggc29saWQgYmxhY2sgIWltcG9ydGFudDsKfQoubm90cnVubyB7CiAgYmFja2dyb3VuZC1jb2xvcjogbGlnaHRncmV5ICFpbXBvcnRhbnQ7CiAgY29sb3IgOiBibGFjayAhaW1wb3J0YW50Owp9Ci5xdWVzdGlvbiB7CiAgYmFja2dyb3VuZC1jb2xvcjogYXF1YW1hcmluZSAhaW1wb3J0YW50OwogIGNvbG9yIDogYmxhY2sgIWltcG9ydGFudDsKICBib3JkZXI6IDNweCBzb2xpZCBsaW1lZ3JlZW4gIWltcG9ydGFudDsKfQoucXVlc3Rpb25vIHsKICBiYWNrZ3JvdW5kLWNvbG9yOiBhcXVhbWFyaW5lICFpbXBvcnRhbnQ7CiAgY29sb3IgOiBibGFjayAhaW1wb3J0YW50Owp9Ci5hbnN3ZXIgewogIGJhY2tncm91bmQtY29sb3I6IG5hdmFqb3doaXRlICFpbXBvcnRhbnQ7CiAgYm9yZGVyOiAzcHggc29saWQgYnJvd24gIWltcG9ydGFudDsKfQouYW5zd2VybyB7CiAgYmFja2dyb3VuZC1jb2xvcjogbmF2YWpvd2hpdGUgIWltcG9ydGFudDsKICBjb2xvciA6IGJsYWNrICFpbXBvcnRhbnQ7Cn0KLmJleW9uZCB7CiAgYmFja2dyb3VuZC1jb2xvcjogdmlvbGV0ICFpbXBvcnRhbnQ7CiAgYm9yZGVyOiAzcHggc29saWQgcHVycGxlICFpbXBvcnRhbnQ7Cn0KLmJleW9uZG8gewogIGJhY2tncm91bmQtY29sb3I6IHZpb2xldCAhaW1wb3J0YW50OwogIGNvbG9yIDogYmxhY2sgIWltcG9ydGFudDsKfQpgYGAKCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQoKLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCgojIFBSRUFNQkxFCgojIyBQdXJwb3NlIG9mIHRoaXMgc2Vzc2lvbgoKVGhpcyBmaWxlIGRlc2NyaWJlcyB0aGUgZGlmZmVyZW50IHN0ZXBzIHRvIHBlcmZvcm0gdGhlICoqZmlyc3QgcGFydCoqIG9mCnRoZSAqKnNpbmdsZSBjZWxsIFJOQXNlcSoqIGRhdGEgYW5hbHlzaXMgdHJhaW5pbmcgY291cnNlIGZvciB0aGUgKipFQkFJSQpuMSAyMDI0KiosIGNvdmVyaW5nIHRoZXNlIHN0ZXBzIDoKCi0gICAqKkxvYWQqKiBhIHNpbmdsZSBjZWxsIHJhdyBjb3VudHMgKiptYXRyaXgqKgoKLSAgICoqUmVtb3ZlKiogYmFyY29kZXMgZnJvbSAqKmVtcHR5IGRyb3BsZXRzKioKCi0gICAqKkVzaW1hdGUqKiBhbmQgcmVtb3ZlICoqYW1iaWVudCBSTkEqKiBjb250YW1pbmF0aW9uICooInNvdXAiKSoKCiMjIEFuIGltcG9ydGFudCBub3RpY2UKCjxjZW50ZXI+IVtdKGd5cm8ucG5nKTwvY2VudGVyPgoKLSAgIFRoZXNlIGVhcmx5IChidXQgbWFuZGF0b3J5KSBzdGVwcyBvZiB0aGUgYW5hbHlzaXMgYXJlIG5vdCBjb3ZlcmVkIGJ5CiAgICB0aGUgcmVub3duZWQsIGhpZ2hlci1sZXZlbCBhbmFseXNpcyBmcmFtZXdvcmtzIGxpa2UgKipTZXVyYXQqKiBub3IKICAgICoqc2NyYW4vc2NhdGVyKiouCiAgICAKLSAgIFRodXMsIHdlIHdpbGwgd3JpdGUvdXNlIGEgY2VydGFpbiBhbW91bnQgb2YgKipsaW5lcyBvZiBjb2RlKiosIHNvbWUKICAgIG9mIHdoaWNoIG9mIGEgaGlnaGVyIGNvbXBsZXhpdHkgZm9yIFIgYmVnaW5uZXJzLgogICAgCi0gICAqKk5ld2NvbWVyLCBkbyBub3QgZmVhcioqICEKCiAgICAtICAgV2hpbGUgeW91IGFyZSAqKndlbGNvbWUgdG8gdHJ5Kiogd3JpdGluZyBzb21lIGJ5IHlvdXJzZWxmLAogICAgICAgIGRlcGVuZGluZyBvbiB5b3VyIFIga25vd2xlZGdlIGFuZCBzZWxmLWNvbmZpZGVuY2UgaW4gaXQgLi4uCiAgICAgICAgCiAgICAtICAgLi4uIHlvdSBkbyBub3QgaGF2ZSB0byBrbm93IFsqKmhvdyoqXXsudW5kZXJsaW5lfSB0aGVzZSBjb2RlCiAgICAgICAgd29yayAuLi4KICAgICAgICAKICAgIC0gICAuLi4gdGh1cyB5b3UgKipzaG91bGQgbm90IGZlZWwgYXNoYW1lZCoqIGNvcHktcGFzdGluZyB0aGUKICAgICAgICBwcmUtd3JpdHRlbiBjb2RlIGhlcmVhZnRlciAuLi4KICAgICAgICAKICAgIC0gICAuLi4gYnV0IGluIGJvdGggY2FzZXMsIHBsZWFzZSB0cnkgKip5b3VyIGJlc3QgdW5kZXJzdGFuZGluZyoqCiAgICAgICAgWyoqd2hhdCoqXXsudW5kZXJsaW5lfSB0aGVzZSBjb2RlIGRvLCBhbmQgZXZlbiBtb3JlLAogICAgICAgIFsqKndoeSoqXXsudW5kZXJsaW5lfSAhCiAgICAgICAgCiAgICAtICAgSW4gdGhpcyBwdXJwb3NlLCB5b3UgbWF5IHVzZSB3aGF0ZXZlciAqKmhlbHAqKiB5b3UgY2FuICoqYnkqKgogICAgICAgICoqc2VhcmNoaW5nKiouCgojIyBXaGF0IHRvIHJ1biA/CgotICAgVGhpcyB0cmFpbmluZyBwcmVzZW50YXRpb24sIHdyaXR0ZW4gaW4gUm1hcmtkb3duLCBjb250YWlucyAqKm1hbnkqKiBjaHVua3MgKCA9IGNvZGUgYmxvY2tzKQoKLSAgIFlvdSAqKmRvIG5vdCBoYXZlIHRvKiogcnVuIHRoZW0gYWxsICEKCi0gICBDaHVua3MgZm9sbG93IGEgKipjb2xvciBjb2RlKiogOgoKICAtICAgQ2h1bmtzIHlvdSBhcmUgZXhwZWN0ZWQgdG8gcnVuIDoKCiAgICAgIGBgYHtyIGNodW5rX3J1bn0KICAgICAgIyMgRXhhbXBsZSBvZiBjaHVuayB0byBydW4KICAgICAgZ2V0d2QoKQogICAgICBgYGAKICAgICAgCiAgICAgIDxicj4KCiAgLSAgIENodW5rcyB5b3UgYXJlICoqTk9UKiogZXhwZWN0ZWQgdG8gcnVuICh3ZSB3aWxsIGRlbW8gdGhlbSkuIFlvdSBtYXkgcnVuIHRoZW0gYnV0IGlmIHlvdSBhcmUgbGF0ZSBhZnRlcndhcmRzLCBfeW91XydsbCBiZSB0aGUgb25lIHRvIGJsYW1lICEKCiAgICAgIGBgYHtyIGNodW5rX2RlbW8sIGNsYXNzLnNvdXJjZT0ibm90cnVuIiwgY2xhc3Mub3V0cHV0PSJub3RydW5vIn0KICAgICAgIyMgRXhhbXBsZSBvZiBjaHVuayB0byBOT1QgcnVuCiAgICAgIGdldHdkKCkKICAgICAgYGBgCgogICAgICA8YnI+CgogIC0gICAqKkFkZGl0aW9uYWwqKiBleGVyY2lzZXMgLyBxdWVzdGlvbnMgYmV5b25kIHRoZSBzY29wZSBvZiB0aGUgdHJhaW5pbmcgCiAgY291cnNlLCB5b3UgbWF5IHRyeSBpZiB5b3UgZmVlbCBib3JlZAoKICAgICAgYGBge3IgY2h1bmtfYmV5b25kLCBjbGFzcy5zb3VyY2U9ImJleW9uZCIsIGNsYXNzLm91dHB1dD0iYmV5b25kbyJ9CiAgICAgICMjIEV4YW1wbGUgb2YgY2h1bmsgdG8gcGxheSB3aXRoIGJ1dCBiZXlvbmQgdGhlIHNjb3BlCiAgICAgIGdldHdkKCkKICAgICAgYGBgCgogICAgICA8YnI+CgogIC0gICAqKlF1ZXN0aW9ucyoqIHdlIHdvdWxkIGxpa2UgeW91IHRvIGFuc3dlciBkdXJpbmcgdGhlIHRyYWluaW5nIGNvdXJzZQoKICAgICAgYGBge3IgY2h1bmtfcXVlc3Rpb24sIGNsYXNzLnNvdXJjZT0icXVlc3Rpb24iLCBldmFsID0gRkFMU0V9CiAgICAgIFdoYXQncyB0aGUgQW5zd2VyIHRvIExpZmUgPyBUaGUgVW5pdmVyc2UgPyBFdmVyeXRoaW5nID8/CiAgICAgIGBgYAoKICAgICAgPGJyPgoKICAtICAgKipBbnN3ZXJzKiogdG8gdGhlc2UKCiAgICAgIGBgYHtyIGNodW5rX2Fuc3dlciwgY2xhc3Muc291cmNlID0gYygiZm9sZC1oaWRlIiwgImFuc3dlciIpLCBjbGFzcy5vdXRwdXQ9ImFuc3dlcm8ifQogICAgICBjYXQoIlRoZSBBbnN3ZXIgdG8gdGhlIEdyZWF0IFF1ZXN0aW9uLi4uIGlzIC4uLiAKICAgICAgRm9ydHktdHdvLiBTaXggYnkgbmluZSA6IGZvcnR5LXR3by4gVGhhdCdzIGl0LiAKICAgICAgVGhhdCdzIGFsbCB0aGVyZSBpcy4gKEkgYWx3YXlzIHRob3VnaHQgc29tZXRoaW5nIHdhcyAKICAgICAgZnVuZGFtZW50YWxseSB3cm9uZyB3aXRoIHRoZSB1bml2ZXJzZS4pIikKICAgICAgYGBgCgotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQoKIyBTdGFydCBSc3R1ZGlvCgotICAgVXNpbmcgdGhlIFtPcGVuT25EZW1hbmQgY2hlYXQKICAgIHNoZWV0XShodHRwczovL2lmYi1lbGl4aXJmci5naXRodWIuaW8vRUJBSUkvMjAyMy9lYmFpaW4xL1NpbmdsZUNlbGwvMjAyNF9URF9PcGVuT25EZW1hbmQuaHRtbCl7dGFyZ2V0PSJfYmxhbmsifSwKICAgIGNvbm5lY3QgdG8gdGhlIFtPcGVuT25EZW1hbmQKICAgIHBvcnRhbF0oaHR0cHM6Ly9vbmRlbWFuZC5jbHVzdGVyLmZyYW5jZS1iaW9pbmZvcm1hdGlxdWUuZnIpe3RhcmdldD0iX2JsYW5rIn0gYW5kCiAgICAqKmNyZWF0ZSBhIFJzdHVkaW8gc2Vzc2lvbioqIHdpdGggdGhlIHJpZ2h0IHJlc291cmNlIHJlcXVpcmVtZW50cy4KCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQoKLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCgojIFdhcm0tdXAKCi0gICBXZSBzZXQgKipjb21tb24gcGFyYW1ldGVycyoqIHdlIHdpbGwgdXNlIHRocm91Z2hvdXQgdGhpcyBzZXNzaW9uIDoKCmBgYHtyIHNldHBhcmFtfQojIyBTZXQgeW91ciBwcm9qZWN0IG5hbWUKcHJvamVjdF9uYW1lIDwtICJlYmFpaV9zY190ZWFjaGVycyIgICMgRG8gbm90IGNvcHktcGFzdGUgdGhpcyAhIEl0J3MgTVkgcHJvamVjdCAhIFB1dCB5b3VycyAhIQoKIyMgU2VlZCBmb3IgdGhlIFJORwpteV9zZWVkIDwtIDEzMzdMCgojIyBFbXB0eSBkcm9wbGV0cyBtYXggcC12YWx1ZQptYXhfcCA8LSAxRS0wMwpgYGAKCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQoKLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCgojIFByZXBhcmUgdGhlIGRhdGEgc3RydWN0dXJlCgotICAgRHVyaW5nIHRoZSBjdXJyZW50IGFuZCByZW1haW5pbmcgdHJhaW5pbmcgc2Vzc2lvbnMsIHdlIHdpbGwgd29yayBvbgogICAgZGlmZmVyZW50ICoqZGF0YSoqICoqb2JqZWN0cyoqLCBzb21lIHJldHJpZXZlZCBmcm9tICoqZXh0ZXJuYWwqKgogICAgcmVzb3VyY2VzLCBzb21lIHdlIHdpbGwgKipjcmVhdGUqKi4KICAgIAotICAgSW4gb3JkZXIgdG8gcmV1c2UgdGhlc2UgZm9yIGZ1cnRoZXIgc3RlcHMsIG9yIG90aGVyIHNlc3Npb25zLCB3ZQogICAgbmVlZCB0byAqKnN0b3JlKiogdGhlbSwgdGh1cyBjcmVhdGUgYSAqKmRpcmVjdG9yeSBzdHJ1Y3R1cmUqKiBvbiB0aGUKICAgIGNsdXN0ZXIuCiAgICAKLSAgIFdlIHdpbGwgY3JlYXRlIHRoaXMgc3RydWN0dXJlIGluICoqeW91ciBwcm9qZWN0KiogZm9sZGVyICh0aGUgb25lCiAgICB5b3UgcmVnaXN0ZXJlZCBhdCB0aGUgSUZCIENvcmUsIGluIHdoaWNoIHlvdSBzdG9yZWQgeW91ciBvd24gZGF0YQogICAgZm9yIHRoZSB0dXRvcmluZykuCiAgICAKLSAgIFRvIGVuc3VyZSB0aGF0ICp5b3UqIHdpbGwgYWx3YXlzIHVzZSB0aGUgc2FtZSBzdHJ1Y3R1cmUgZm9yICp5b3VyKgogICAgd29yaywgdGhlIGNvZGUgaGVyZWFmdGVyIGhhcyBiZWVuIHdyaXRlbiBzbyB0aGF0IHlvdSBqdXN0IGhhdmUgdG8KICAgIGRlZmluZSBhIHNpbmdsZSAqKnZhcmlhYmxlKiogdGhhdCBjb3JyZXNwb25kcyB0byB5b3VyIHByb2plY3QgbmFtZS4KICAgIAogICAgLSAgIEluICpteSogY2FzZSwgYXMgYSB0ZWFjaGVyIGZvciB0aGUgU0MgdHJhaW5pbmcsIGl0IGlzCiAgICAgICAgKipgZWJhaWlfc2NfdGVhY2hlcnNgKiouCgojIyBNYWluIGRpcmVjdG9yeQoKLSAgIENyZWF0ZSB0aGUgdHJhaW5pbmcgY291cnNlIGRpcmVjdG9yeQoKYGBge3IgbWFpbmRpciwgZm9sZC5vdXRwdXQgPSBGQUxTRX0KIyMgUHJlcGFyZSB0aGUgcGF0aApURF9kaXIgPC0gcGFzdGUwKCIvc2hhcmVkL3Byb2plY3RzLyIsIHByb2plY3RfbmFtZSwgIi9TQ19URCIpCgojIyBDcmVhdGUgdGhlIHJvb3QgZGlyZWN0b3J5CmRpci5jcmVhdGUocGF0aCA9IFREX2RpciwgcmVjdXJzaXZlID0gVFJVRSkKCiMjIFByaW50IHRoZSByb290IGRpcmVjdG9yeSBvbi1zY3JlZW4KcHJpbnQoVERfZGlyKQpgYGAKCiMjIEN1cnJlbnQgc2Vzc2lvbgoKYGBge3Igc2Vzc2lvbmRpciwgZm9sZC5vdXRwdXQgPSBGQUxTRX0KIyMgQ3JlYXRlIHRoZSBzZXNzaW9uIChQcmVwcm9jLjEpIGRpcmVjdG9yeQpzZXNzaW9uX2RpciA8LSBwYXN0ZTAoVERfZGlyLCAiLzAxX1ByZXByb2MuMSIpCmRpci5jcmVhdGUocGF0aCA9IHNlc3Npb25fZGlyLCByZWN1cnNpdmUgPSBUUlVFKQoKIyMgUHJpbnQgdGhlIHNlc3Npb24gZGlyZWN0b3J5IG9uLXNjcmVlbgpwcmludChzZXNzaW9uX2RpcikKYGBgCgojIyBJbnB1dCBkYXRhIGRpcmVjdG9yeQoKYGBge3IgaW5kaXIsIGZvbGQub3V0cHV0PUZBTFNFfQojIyBDcmVhdGUgdGhlIElOUFVUIGRhdGEgZGlyZWN0b3J5CmlucHV0X2RpciA8LSBwYXN0ZTAoc2Vzc2lvbl9kaXIsICIvREFUQSIpCmRpci5jcmVhdGUocGF0aCA9IGlucHV0X2RpciwgcmVjdXJzaXZlID0gVFJVRSkKCiMjIFByaW50IHRoZSBpbnB1dCBkaXJlY3Rvcnkgb24tc2NyZWVuCnByaW50KGlucHV0X2RpcikKYGBgCgojIyBPdXRwdXQgcmVzdWx0cyBkaXJlY3RvcnkKCmBgYHtyIG91dGRpciwgZm9sZC5vdXRwdXQ9RkFMU0V9CiMjIENyZWF0ZSB0aGUgT1VUUFVUIGRhdGEgZGlyZWN0b3J5Cm91dHB1dF9kaXIgPC0gcGFzdGUwKHNlc3Npb25fZGlyLCAiL1JFU1VMVFMiKQpkaXIuY3JlYXRlKHBhdGggPSBvdXRwdXRfZGlyLCByZWN1cnNpdmUgPSBUUlVFKQoKIyMgUHJpbnQgdGhlIG91dHB1dCBkaXJlY3Rvcnkgb24tc2NyZWVuCnByaW50KG91dHB1dF9kaXIpCmBgYAoKPGJyPjxicj4KClsqKk5vdGUgOioqXXsudW5kZXJsaW5lfSAqQWN0dWFsbHksIGZvciB0aGlzIHNlc3Npb24gd2Ugd29uJ3QgZXhwb3J0CmFueSByZXN1bHQsIHNvIGNyZWF0aW5nIHRoaXMgKipgUkVTVUxUU2AqKiBmb2xkZXIgd2FzIGp1c3QgZm9yCnByYWN0aWNlLioKCjxicj4KCk5vdywgdGhlIHdlIGhhdmUgZXZlcnl0aGluZyBuZWVkZWQgdG8gOgoKLSAgICoqbG9hZCoqIGRhdGEgKGBpbnB1dF9kaXJgKQoKLSAgICoqd29yayoqIG9uIGl0IF8oaW4gbWVtb3J5KV8KCi0gICAqKnNhdmUqKiB0aGUgcmVzdWx0cyB3ZSB3aWxsIGdlbmVyYXRlIChgb3V0cHV0X2RpcmApLgoKLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCgotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KCiMgTG9hZCBhIDEwWCBDZWxsIFJhbmdlciByYXcgY291bnQgbWF0cml4CgojIyBUaGUgZGF0YXNldAoKLSAgIFRoZSBkYXRhIHdlIHdpbGwgd29yayBvbiBpcyBob3N0ZWQgb24gW1plbm9kb10oaHR0cHM6Ly96ZW5vZG8ub3JnKXt0YXJnZXQ9Il9ibGFuayJ9LiBUbyBlYXNlIGl0cyByZXRyaWV2YWwsIHdlIGNhbiBkZWZpbmUgaXRzIElEIGFzIGEgdmFyaWFibGUKCiAgICBgYGB7ciB6aWR9CiAgICAjIyBUaGlzIGlzIGFjdHVhbGx5IHRvIGhlbHAgbWUgdXBkYXRpbmcgdGhpcyB0cmFpbmluZyB3aXRob3V0IGhhc3NsZQogICAgemVuX2lkIDwtICIxNDAzNDIyMSIKICAgIGBgYAoKLSAgIEhlcmUgd2lsbCB3ZSB0cmFpbiBvdXJzZWx2ZXMgdG8gbG9hZCBpbnRvIFIgYSBzaW5nbGUgY2VsbCBSTkFzZXEKICAgIGRhdGEgcHJvZHVjZWQgYnkgMTBYJ3MgKipDZWxsIFJhbmdlcioqLgogICAgCi0gICBXZSB3aWxsIHdvcmsgd2l0aCBhIHB1YmxpYyBkYXRhc2V0IHByb3ZpZGVkIGJ5IHRoZSBtYW51ZmFjdHVyZXIsCiAgICB0aGF0IGNvbnNpc3RzIGluIFx+ICoqMTAsMDAwIFBCTUMqKiAocGVyaXBoZXJhbCBib25lIG1hcnJvdyBjZWxscykKICAgIGZyb20gYSBodW1hbiBkb25vciAocmVkdWNlZCB0byAxLzV0aCkuCiAgICAKLSAgIFRoZSBleHBlcmltZW50IHdhcyBwZXJmb3JtZWQgd2l0aCB0aGUgKiozJyBjYXB0dXJlIGtpdCB2MyoqLgoKLSAgIFRoZSBhbmFseXNpcyB3YXMgcGVyZm9ybWVkIHdpdGggKipDZWxsIFJhbmdlciB2MyoqLCBtYXBwaW5nIG9uIHRoZQogICAgKipHUkNoMzgtMjAyMC1BIG1hbnVmYWN0dXJlcioqIHJlZmVyZW5jZS4KICAgIAotICAgVGhlIGRhdGEgY29uc2lzdHMgaW50byB0aGUgdHlwaWNhbCAxMHggMy1maWxlcyBzdHJ1Y3R1cmUsIGhvc3RlZCBpbgogICAgYSBaZW5vZG8gcmVzcG9zaXRvcnkgKElkIDoKICAgIFtgciB6ZW5faWRgXShodHRwczovL3plbm9kby5vcmcvcmVjb3Jkcy9gciB6ZW5faWRgICJaZW5vZG8gUHJlcHJvYy4xIil7dGFyZ2V0PSJfYmxhbmsifSkKICAgIAogICAgLSAgIGBiYXJjb2Rlcy50c3YuZ3pgIDogdGhlIGxpc3Qgb2YgcHV0YXRpdmUgZHJvcGxldCAqKmJhcmNvZGVzKioKICAgIC0gICBgZmVhdHVyZXMudHN2Lmd6YCA6IHRoZSBsaXN0IG9mICoqZmVhdHVyZXMqKiAoZ2VuZXMpCiAgICAtICAgYG1hdHJpeC5tdHguZ3pgIDogdGhlICoqZmVhdHVyZSoqIHggKipiYXJjb2RlKiogZXhwcmVzc2lvbiBjb3VudAogICAgICAgICoqbWF0cml4KioKCiMjIERvd25sb2FkIGRhdGEKCi0gICBUaGUgY29kZSBjaHVuayBoZXJlIGFmdGVyIGlzIHdyaXR0ZW4gdG8gOgogICAgCiAgICAtICAgbGV0IHlvdSBkb3dubG9hZCB0aGUgZGF0YSBmaWxlcyBmcm9tIAogICAgCiAgICAgICAgLSAgIFplbm9kbyBieSBkZWZhdWx0CiAgICAgICAgCiAgICAgICAgLSAgIGEgbG9jYWwgYmFja3VwIGlmIGBiYWNrdXBgIGlzIHNldCB0byBgVFJVRWAgKGlmIHdlIGxvb3NlIHRoZSBpbnRlcm5ldCBjb25uZWN0aW9uKQogICAgICAgIAogICAgLSAgIGlmIHRoZSBkZXNpcmVkIGRhdGEgaXMgYWxyZWFkeSBhdmFpbGFibGUgbG9jYWxseSA6CiAgICAKICAgICAgICAtICAgbG9hZCB0aGlzIHJhdGhlciB0aGFuIGRvd25sb2FkCiAgICAgICAgCiAgICAgICAgLSAgIGZvcmNlIGEgbmV3LWRvd25sb2FkIGlmIGBmb3JjZWAgaXMgc2V0IHRvIGBUUlVFYAogICAgCmBgYHtyIGRsemVuMTB4LCBtZXNzYWdlID0gVFJVRSwgZWNobyA9IFRSVUV9CiMjIyBOYW1lZCBmaWxlcyAod2lsbCBiZSB1c2VkIGxhdGVyIG9uICEpCm10eF9maWxlIDwtICJtYXRyaXgubXR4Lmd6IgpmZWF0dXJlc19maWxlIDwtICJmZWF0dXJlcy50c3YuZ3oiCmJhcmNvZGVzX2ZpbGUgPC0gImJhcmNvZGVzLnRzdi5neiIKc3VtbWFyeV9maWxlIDwtICJwYm1jXzEwa192M19zdW1tYXJ5Lmh0bWwiCgojIyBGaWxlbmFtZShzKSB0byByZXRyaWV2ZQp0b2dldF9maWxlcyA8LSBjKG10eF9maWxlLAogICAgICAgICAgICAgICAgIGZlYXR1cmVzX2ZpbGUsCiAgICAgICAgICAgICAgICAgYmFyY29kZXNfZmlsZSwKICAgICAgICAgICAgICAgICBzdW1tYXJ5X2ZpbGUpCgojIyBGb2xkZXIgdG8gc3RvcmUgcmV0cmlldmVkIGZpbGVzCmxvY2FsX2ZvbGRlciA8LSBpbnB1dF9kaXIKCiMjIFVzZSBsb2NhbCBiYWNrdXAgPwpiYWNrdXAgPC0gRkFMU0UKaWYoYmFja3VwKSBtZXNzYWdlKCJVc2luZyBsb2NhbCBiYWNrdXAgISIpCgojIyBGb3JjZSBkb3dubG9hZCA/CmZvcmNlIDwtIEZBTFNFCmlmKGZvcmNlKSBtZXNzYWdlKCJGb3JjaW5nIChyZSlkb3dubG9hZCAhIikKCiMjIyBEZWZpbmUgcmVtb3RlIGZvbGRlcgpyZW1vdGVfZm9sZGVyIDwtIGlmIChiYWNrdXApICIvc2hhcmVkL3Byb2plY3RzLzI0MjJfZWJhaWlfbjEvYXRlbGllcl9zY3JuYXNlcS9URC9CQUNLVVAvMTBYLyIgZWxzZSBwYXN0ZTAoImh0dHBzOi8vemVub2RvLm9yZy9yZWNvcmRzLyIsIHplbl9pZCwgIi9maWxlcy8iKQoKIyMjIFJlY29uc3RydWN0IHRoZSBpbnB1dCBwYXRocwpyZW1vdGVfcGF0aCA8LSBwYXN0ZTAocmVtb3RlX2ZvbGRlciwgIi8iLCB0b2dldF9maWxlcykKCiMjIyBSZWNvbnN0cnVjdCB0aGUgb3V0cHV0IHBhdGhzCmxvY2FsX3BhdGggPC0gcGFzdGUwKGxvY2FsX2ZvbGRlciwgIi8iLCB0b2dldF9maWxlcykKCiMjIFJldHJpZXZlIGZpbGVzIChpZiB0aGV5IGRvbid0IGV4aXN0KSwgaW4gbG9vcApmb3IgKHRnIGluIHNlcV9hbG9uZyh0b2dldF9maWxlcykpIHsKICAjIyBJZiB0aGUgZmlsZSBkb2VzIG5vdCBsb2NhbGx5IGV4aXN0CiAgaWYgKCFmaWxlLmV4aXN0cyhsb2NhbF9wYXRoW3RnXSkgfCBmb3JjZSkgewogICAgIyMgUmV0cmlldmUgZGF0YQogICAgaWYoYmFja3VwKSB7CiAgICAgIGZpbGUuY29weShmcm9tID0gcmVtb3RlX3BhdGhbdGddLAogICAgICAgICAgICAgICAgdG8gPSBsb2NhbF9wYXRoW3RnXSkKICAgIH0gZWxzZSB7CiAgICAgIGRvd25sb2FkLmZpbGUodXJsID0gcmVtb3RlX3BhdGhbdGddLCAKICAgICAgICAgICAgICAgICAgICBkZXN0ZmlsZSA9IGxvY2FsX3BhdGhbdGddKQogICAgfQogICAgIyMgQ2hlY2sgaWYgZG93bmxvYWRlZCBmaWxlcyBleGlzdCBsb2NhbGx5CiAgICBpZihmaWxlLmV4aXN0cyhsb2NhbF9wYXRoW3RnXSkpIG1lc3NhZ2UoIlx0T0siKQogIH0gZWxzZSBtZXNzYWdlKHBhc3RlMCh0b2dldF9maWxlc1t0Z10sICIgYWxyZWFkeSBkb3dubG9hZGVkICEiKSkKfQpgYGAKCiMjIExvYWQgaW50byBSCgotICAgKipMb2FkKiogdGhlICoqMTBYKiogKipkYXRhKiogZmlsZXMgaW50byBSIDoKCiAgICBgYGB7ciBxX1JlYWQxMFgsIGV2YWwgPSBGQUxTRSwgY2xhc3Muc291cmNlPSJxdWVzdGlvbiIsIGV2YWwgPSBGQUxTRX0KICAgIEhvdyA/CiAgICBgYGAKCiAgICAtICAgQSBnb29kIHN0YXJ0ZXIKICAgIAogICAgYGBge3IgYV9SZWFkMTBYMSwgY2xhc3Muc291cmNlID0gYygiZm9sZC1oaWRlIiwgImFuc3dlciIpLCBldmFsID0gRkFMU0V9CiAgICAjIyBodHRwczovL2xtZGRndGZ5Lm5ldC8/cT1sb2FkJTIwMTB4JTIwZGF0YSUyMGluJTIwUgogICAgYGBgCiAgICAKICAgIC0gICBCYWNrIHRvIHlvdXIgbG9jYWwgc291cmNlCiAgICAKICAgIGBgYHtyIGFfUmVhZDEwWDIsIGNsYXNzLnNvdXJjZSA9IGMoImZvbGQtaGlkZSIsICJhbnN3ZXIiKSwgZXZhbCA9IEZBTFNFfQogICAgIyMgUmVhZGluZyB0aGUgZnVuY3Rpb24gaGVscCBwYWdlCiAgICA/U2V1cmF0OjpSZWFkMTBYCiAgICBgYGAKICAgIAogICAgPGJyPjxicj48YnI+CiAgICAKICAgIGBgYHtyIGxvYWQxMHh9CiAgICAjIyBMb2FkaW5nIHVuZmlsdGVyZWQgMTBYIG1hdHJpeAogICAgc2NtYXQgPC0gU2V1cmF0OjpSZWFkMTBYKGRhdGEuZGlyID0gaW5wdXRfZGlyKQogICAgYGBgCiAgICAKICAgIDxicj4KCi0gICBIb3dldmVyLCB3ZSBkb24ndCBrbm93IHdoYXQgdGhlIGxvYWRlZCBvYmplY3QgYWN0dWFsbHkgbG9va3MgbGlrZSBpbiBSLi4uCgotICAgKipRdWVzdGlvbnMqKiA6IAoKICAgIC0gICBEbyB5b3Uga25vdyBhIHdheSB0byAuLi4KICAgIAogICAgYGBge3IgcV9pcywgY2xhc3Muc291cmNlPSJxdWVzdGlvbiIsIGV2YWwgPSBGQUxTRX0KICAgIC4uLiBrbm93IHdoYXQgSVMgdGhlIHR5cGUgb2Ygb2JqZWN0IHRoYXQgd2FzIGNyZWF0ZWQgPwogICAgYGBgCiAgICAKICAgIDxicj4KCiAgICBgYGB7ciBhX2lzLCBjbGFzcy5zb3VyY2UgPSBjKCJmb2xkLWhpZGUiLCAiYW5zd2VyIiksIGV2YWwgPSBGQUxTRX0KICAgICMjIFRvIGtub3cgdGhlIHR5cGUgb2YgYW4gUiBvYmplY3QgOiBtZXRob2RzOjppcygpCiAgICA/bWV0aG9kczo6aXMKICAgIGBgYAogICAgCiAgICBgYGB7ciBnb2lzLCBjbGFzcy5zb3VyY2UgPSAiZm9sZC1oaWRlIn0KICAgICMjIEdldCB0aGUgdHlwZQogICAgbWV0aG9kczo6aXMoc2NtYXQpCiAgICBgYGAKICAgIAogICAgPGJyPjxicj4KICAgIAogICAgYGBge3IgcV9zdHIsIGNsYXNzLnNvdXJjZT0icXVlc3Rpb24iLCBldmFsID0gRkFMU0V9CiAgICAuLi4gb2JzZXJ2ZSB0aGUgU1RSdWN0dXJlIG9mIHRoZSByZXN1bHRpbmcgb2JqZWN0ID8KICAgIGBgYAogICAgCiAgICA8YnI+CiAgICAKICAgIGBgYHtyIGFfc3RyLCBjbGFzcy5zb3VyY2UgPSBjKCJmb2xkLWhpZGUiLCAiYW5zd2VyIiksIGV2YWwgPSBGQUxTRX0KICAgICMjIFRvIGdldCBhIGJhc2ljIHN0cnVjdHVyZSBkZXNjcmlwdGlvbiA6IHV0aWxzOjpzdHIoKQogICAgP3V0aWxzOjpzdHIKICAgIGBgYAogICAgCiAgICBgYGB7ciBnbyBzdHIsIGNsYXNzLnNvdXJjZSA9ICJmb2xkLWhpZGUifQogICAgIyMgR2V0IHRoZSBiYXNpYyBzdHJ1Y3R1cmUKICAgIHV0aWxzOjpzdHIoc2NtYXQpCiAgICBgYGAKCjxicj4KCiMjIEZlYXR1cmVzIGNsZWFudXAKClNldXJhdCBkb2VzIG5vdCBsaWtlIHNwZWNpYWwgKip1bmRlcnNjb3JlcyoqIChgX2ApIGluIGZlYXR1cmUgbmFtZXMgLi4uCgpXZSB3aWxsIGhhdmUgdG8gOgoKLSAgICoqY29udHJvbCoqIGlmIG91ciBkYXRhc2V0IGNvbnRhaW5zIHNvbWUgPwogICAgCiAgICBgYGB7ciB1c19jaGVjazF9CiAgICAjIyBDaGVjayBmZWF0dXJlIG5hbWVzIHdpdGggdW5kZXJzY29yZShzKQogICAgIyMjIEhlcmUgd2UgdXNlIGJhc2U6OmdyZXAgdGhhdCBhbGxvd3MgdG8gbG9vayBmb3IgY2hhcmFjdGVyIHN0cmluZ3MgKHgpIAogICAgIyMjIGNvbnRhaW5pbmcgYSBkZWZpbmVkIHNldCBvZiBjaGFyYWN0ZXIocykgKHBhdHRlcm4pLCByZXR1cm5pbmcgdGhlaXIgCiAgICAjIyMgcG9zaXRpb24gKHZhbHVlID0gRkFMU0UpIG9yIHZhbHVlICh2YWx1ZSA9IFRSVUUpCiAgICBiYXNlOjpncmVwKHBhdHRlcm4gPSAnXycsIAogICAgICAgICB4ID0gcm93bmFtZXMoc2NtYXQpLCAKICAgICAgICAgdmFsdWUgPSBUUlVFKQogICAgYGBgCiAgICAKICAgIDxicj4KICAgIAotICAgaWYgc28sICoqY2xlYW4qKiBpdAoKICAgIGBgYHtyIHVzX2NsZWFufQogICAgIyMgQ2xlYW4gZmVhdHVyZXMgOiByZXBsYWNpbmcgdW5kZXJzY29yZXMgYnkgZGFzaGVzCiAgICBiYXNlOjpyb3duYW1lcyhzY21hdCkgPC0gYmFzZTo6Z3N1YigKICAgICAgcGF0dGVybiA9ICJfIiwgCiAgICAgIHJlcGxhY2VtZW50ID0gIi0iLCAKICAgICAgeCA9IHJvd25hbWVzKHNjbWF0KSkKICAgIGBgYAoKLSAgIGNvbnRyb2wgdGhlIGVmZmljaWVuY3kgb2Ygb3VyIGNsZWFuaW5nCiAgICAKICAgIGBgYHtyIHVzX2NoZWsyLCBjbGFzcy5zb3VyY2U9Im5vdHJ1biIsIGNsYXNzLm91dHB1dD0ibm90cnVubyJ9CiAgICAjIyBQb3N0LWNsZWFudXAgY29udHJvbAogICAgZ3JlcChwYXR0ZXJuID0gIl8iLCAKICAgICAgICAgeCA9IHJvd25hbWVzKHNjbWF0KSwgCiAgICAgICAgIHZhbHVlID0gVFJVRSkKICAgIGBgYAoKPGJyPgoKLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCgotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KCgojIEVtcHR5IGRyb3BsZXRzCgotICAgV2hpbGUgYSBsYXJnZSBwYXJ0IG9mIG91ciBiYXJjb2RlcyBjb250YWluICoqbm8gY291bnQqKiBhdCBhbGwsIGJhcmNvZGVzIHdpdGggYXQgbGVhc3Qgb25lIHRvdGFsIGNvdW50ICoqbWF5IG5vdCoqIG1hbmRhdG9yaWx5IGNvcnJlc3BvbmQgdG8gYSAqKnJlYWwgY2VsbCoqLCBkdWUgdG8gYW1iaWVudCBSTkEuCgotICAgSGVyZSB3ZSB3YW50IHRvICoqaWRlbnRpZnkqKiBlbXB0eSBkcm9wbGV0cywgdGhlbiAqKnJlbW92ZSoqIHRoZW0gZnJvbSB0aGUgbWF0cml4LgoKIyMgRGV0ZWN0aW9uCgotICAgVG8gZGV0ZWN0IHRoZSBtYXJnaW5zIG9mICJ0cnVlIiBjZWxscyB2ZXJzdXMgZW1wdHkgZHJvcGxldHMsIHdlIHdpbGwKcmVseSBvbiB0aGUgKiprbmVlcGxvdCoqIDogYSBwbG90IG9mICoqVU1JIGNvdW50cyBwZXIgYmFyY29kZSoqLCBpbiBmdW5jdGlvbiBvZiB0aGVpcgppbmNyZWFzaW5nbHkgKipvcmRlcmVkIHJhbmsqKi4KCi0gICBUaHVzLCB0byBwZXJmb3JtIHRoZSBrbmVlcGxvdCwgd2UgbmVlZCB0byAqKnJhbmsgdGhlIGJhcmNvZGVzKiogCnRoYW5rcyB0byB0aGVpciB0b3RhbCBjb3VudHMuCgojIyMgUmFua2luZyBiYXJjb2RlcwoKLSAgIFdlIHdpbGwgdXNlIHRoZSBbKipEcm9wbGV0VXRpbHMqKl0oaHR0cHM6Ly9iaW9jb25kdWN0b3Iub3JnL3BhY2thZ2VzL3JlbGVhc2UvYmlvYy9odG1sL0Ryb3BsZXRVdGlscy5odG1sKXt0YXJnZXQ9Il9ibGFuayJ9IHBhY2thZ2UKCiAgICBgYGB7ciBoX2JhcmNvZGVSYW5rcywgZXZhbCA9IEZBTFNFfQogICAgIyMgUmVhZCB0aGUgZnVuY3Rpb24gaGVscCBwYWdlCiAgICA/RHJvcGxldFV0aWxzOjpiYXJjb2RlUmFua3MKICAgIGBgYAogICAgCiAgICBgYGB7ciBiY3JhbmtzfQogICAgIyMgR2VuZXJhdGUgdGhlIHJhbmsgc3RhdGlzdGljcwogICAgYmNfcmFuayA8LSBEcm9wbGV0VXRpbHM6OmJhcmNvZGVSYW5rcyhzY21hdCkKICAgIGBgYAoKLSAgIENoZWNrIHRoZSByYW5raW5nCgogICAgYGBge3IgYmNyYW5rX3RvcCwgY2xhc3Muc291cmNlPSJub3RydW4iLCBjbGFzcy5vdXRwdXQ9Im5vdHJ1bm8iLCBmb2xkLm91dHB1dCA9IEZBTFNFfQogICAgIyMgU2hvdyBhIHN1bW1hcnkgb2YgdGhlIHJhbmtzIChvcmRlcmVkIGZvciBkaXNwbGF5IGNvbnZlbmllbmNlKQogICAgcHJpbnQoYmNfcmFua1tvcmRlcihiY19yYW5rJHJhbmspLF0pCiAgICBgYGAKICAgIAo8YnI+CgojIyMgS25lZXBsb3QKCk5vdywgd2UgY2FuICoqZHJhdyoqIG91ciBrbmVlcGxvdCA6CgpgYGB7ciBrbmVlcGxvdDEsIGNsYXNzLnNvdXJjZT0ibm90cnVuIiwgY2xhc3Mub3V0cHV0PSJub3RydW5vIiwgZm9sZC5wbG90ID0gRkFMU0V9CiMjIFBsb3QKZ3JhcGhpY3M6OnBsb3QoYmNfcmFuayRyYW5rLCAKICAgICAgICAgICAgICAgYmNfcmFuayR0b3RhbCArIDEsIAogICAgICAgICAgICAgICBsb2cgPSAieHkiLCAKICAgICAgICAgICAgICAgeGxhYiA9ICJCYXJjb2RlIHJhbmsiLCAKICAgICAgICAgICAgICAgeWxhYiA9ICJUb3RhbCBVTUlzIiwgCiAgICAgICAgICAgICAgIGNvbCA9ICJibGFjayIsIAogICAgICAgICAgICAgICBwY2ggPSAiLiIsIAogICAgICAgICAgICAgICBjZXggPSA1LCAKICAgICAgICAgICAgICAgbWFpbiA9ICJLbmVlcGxvdCIpCmBgYAoKPGJyPjxicj4KCi0gICAqKlF1ZXN0aW9uKiogOiAKICAgIAogICAgYGBge3IgcV9rbmVlLCBjbGFzcy5zb3VyY2U9InF1ZXN0aW9uIiwgZXZhbCA9IEZBTFNFfQogICAgTG9va2luZyBhdCB0aGUga25lZXBsb3QsIGNhbiB5b3Ugcm91Z2hseSBwcmVkaWN0IGhvdyBtYW55IGJhcmNvZGVzIHdpbGwgYmUga2VwdCBhcyBjZWxscyAoaWUsIGFzIG5vbi1lbXB0eSBkcm9wbGV0cykgPwogICAgYGBgCiAgICAKICAgIDxicj4KICAgIAogICAgYGBge3IgYV9rbmVlLCBjbGFzcy5zb3VyY2UgPSBjKCJmb2xkLWhpZGUiLCAiYW5zd2VyIiksIGV2YWwgPSBGQUxTRX0KICAgICMjIC4gVGhlICJjbGlmZiIgaW4gdGhlIGtuZWVwbG90IGlzIGF0IGEgCiAgICAjIyAgcmFuayB2YWx1ZSBjbG9zZSB0byB+IDEsMDAwIGNvdW50cy4KICAgIGBgYAogICAgClRoZW4sIHdlIGNhbiB1c2UgdGhlc2UgcmFua3MgdG8gKippZGVudGlmeSAidHJ1ZSIgY2VsbHMqKiA6CgpgYGB7ciBoX2VtcHR5RHJvcHMsIGNsYXNzLnNvdXJjZT0ibm90cnVuIiwgY2xhc3Mub3V0cHV0PSJub3RydW5vIiwgZXZhbCA9IEZBTFNFfQojIyBSZWFkIHRoZSBmdW5jdGlvbiBoZWxwIHBhZ2UKP0Ryb3BsZXRVdGlsczo6ZW1wdHlEcm9wcwpgYGAKCmBgYHtyIGVtcHR5ZHJvcHMxfQojIyBTZXQgdGhlIFJORyBzZWVkCnNldC5zZWVkKG15X3NlZWQpCgojIyBJZGVudGlmeSBlbXB0eSBkcm9wbGV0cwpiY19yYW5rMiA8LSBEcm9wbGV0VXRpbHM6OmVtcHR5RHJvcHMoc2NtYXQpCmBgYAoKV2UgY2FuIHNob3cgYSBzdW1tYXJ5IG9mIG9yZGVyZWQgYmFyY29kZXMgKHF1YWxpZmllZCBvbiB0b3ApIDoKCmBgYHtyIGVtcHR5c21yeSwgY2xhc3Muc291cmNlPSJub3RydW4iLCBjbGFzcy5vdXRwdXQ9Im5vdHJ1bm8ifQojIyBTaG93IGEgc3VtbWFyeSBvZiB0aGUgcXVhbGlmaWVkIHJhbmtzIChvcmRlcmVkIGZvciBkaXNwbGF5IGNvbnZlbmllbmNlKQpwcmludChiY19yYW5rMltvcmRlcihiY19yYW5rMiRUb3RhbCwgZGVjcmVhc2luZyA9IFRSVUUpLF0pCmBgYAoKPGJyPgoKIyMjIFNlbGVjdGlvbgoKQmFyY29kZXMgY29uc2lkZXJlZCBhcyBlbXB0eSBoYXZlICoqbm8gcC12YWx1ZSoqIChgRkRSYCA9IE5BKQoKYGBge3IgYmNyMl9uYX0KIyMgQ3JlYXRpbmcgYSB2YWxpZGF0aW9uIGNvbHVtbiAoZW1wdHkgZHJvcGxldHMgaGF2ZSBOQSBhcyBwLXZhbHVlKQpiY19yYW5rMiRWQUxJRCA8LSAhaXMubmEoYmNfcmFuazIkRkRSKSAKCiMjIFF1YW50aWZ5ICJyZWFsIGNlbGxzIgp0YWJsZShiY19yYW5rMiRWQUxJRCkKYGBgCgo8YnI+CgpXZSBjYW4gKipjb250cm9sIHRoZSBzdHJpbmdlbmN5KiogYnkgc2V0dGluZyBhIEZEUi1hZGp1c3RlZCBwLXZhbHVlICoqdGhyZXNob2xkKioKCmBgYHtyIHZhbGlkX2Zkcn0KIyMgV2UndmUgc2V0IHRoZSBtYXggcC12YWx1ZSB0byAxRS0wMwpiY19yYW5rMiRWQUxJRFtiY19yYW5rMiRGRFIgPj0gbWF4X3BdIDwtIEZBTFNFCgojIyBDb250cm9sCnRhYmxlKGJjX3JhbmsyJFZBTElEKQpgYGAKCjxicj4KCk5vdywgd2UgY2FuICoqZGlzcGxheSoqIG91ciAqKnRydWUgY2VsbHMqKiBvbiB0aGUga25lZXBsb3QgOgoKYGBge3Iga25lZXBsb3QyLCBmaWcuYWxpZ249ImNlbnRlciJ9CiMjIFBsb3QKZ3JhcGhpY3M6OnBsb3QoYmNfcmFuayRyYW5rLCAKICAgICAgICAgICAgICAgYmNfcmFuayR0b3RhbCsxLCAKICAgICAgICAgICAgICAgbG9nID0gInh5IiwgCiAgICAgICAgICAgICAgIHhsYWIgPSAiQmFyY29kZSByYW5rIiwgCiAgICAgICAgICAgICAgIHlsYWIgPSAiVG90YWwgVU1JcyIsIAogICAgICAgICAgICAgICBjb2wgPSBpZmVsc2UoYmNfcmFuazIkVkFMSUQsICJyZWQiLCAiYmxhY2siKSwgCiAgICAgICAgICAgICAgIHBjaCA9ICIuIiwgCiAgICAgICAgICAgICAgIGNleCA9IGlmZWxzZShiY19yYW5rMiRWQUxJRCwgNywgMyksIAogICAgICAgICAgICAgICBtYWluID0gcGFzdGUwKCJLbmVlcGxvdCAoIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGVuZ3RoKHdoaWNoKGJjX3JhbmsyJFZBTElEKSksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICIgYmFyY29kZXMga2VwdCkiKSkKYGBgCgo8YnI+CgotICAgKipRdWVzdGlvbioqIDoKCiAgICBgYGB7ciBxX2tuZWVyZWQsIGNsYXNzLnNvdXJjZT0icXVlc3Rpb24iLCBldmFsID0gRkFMU0V9CiAgICBJcyB0aGVyZSBzb21ldGhpbmcgdW5leHBlY3RlZCBmb3IgdGhlIHJldGFpbmVkICJUUlVFIiBjZWxscyBpbiB0aGlzIGtuZWVwbG90ID8KICAgIGBgYAogICAgCiAgICA8YnI+CiAgICAKICAgIGBgYHtyIGFfa25lZXJlZCwgY2xhc3Muc291cmNlID0gYygiZm9sZC1oaWRlIiwgImFuc3dlciIpLCBldmFsID0gRkFMU0V9CiAgICAjIyAuIFlvdSBtYXkgb2JzZXJ2ZSB0aGF0IHRoZSBzZWxlY3Rpb24gb2YgInRydWUiIGNlbGxzIChyZWQpCiAgICAjIyAgIGRvZXMgbm90IHN0cmljdGx5IGNvcnJlc3BvbmQgdG8gYSAiY3V0IiBpbiB0aGUga25lZXBsb3QKICAgICMjICAgZGVzY2VuZGluZyBjdXJ2ZS4KICAgICMjCiAgICAjIyAuIFRoaXMgaXMgYmVjYXVzZSBlbXB0eURyb3BzIGRvZXMgbm90IG9ubHkgImZvbGxvdyIgdGhlIGN1cnZlCiAgICAjIyAgIHRvIGRldGVybWluZSBhIHRocmVzaG9sZCBjb3JyZXNwb25kaW5nIHRvIGEgIm1pbmltYWwgY291bnQiIAogICAgIyMgICB2YWx1ZSB0byBjb25zaWRlciBhIGJhcmNvZGUgYXMgYSBjZWxsLCBidXQgYWxzbyBwZXJmb3JtcyBhIAogICAgIyMgICBzdGF0aXN0aWNhbCBhbmFseXNpcyBmb3IgZWFjaCBiYXJjb2RlLCBjb25zaWRlcmluZyBob3cgbXVjaCAKICAgICMjICAgaXRzIGV4cHJlc3Npb24gcHJvZmlsZSByZXNzZW1ibGVzIHRoZSBvbmUgb2Ygb3RoZXIgY2VsbHMsIAogICAgIyMgICBldmVuIHdpdGggYSB2ZXJ5IGRpZmZlcmVudCAobG93ZXIpIGdsb2JhbCBsZXZlbCBvZiBleHByZXNzaW9uLgogICAgYGBgCiAgICAKPGJyPjxicj4KCiMjIFJlbW92YWwKCldlIGp1c3QgbmVlZCB0byByZXN0cmljdCBvdXIgY291bnQgbWF0cml4IHRvICJ0cnVlIiBjZWxscy4KCi0gICBIb3cgbWFueSBkbyB3ZSBoYXZlIGF0IHRoZSBtb21lbnQgPwoKICAgIGBgYHtyIHNjZGltMywgY2xhc3Muc291cmNlPSJub3RydW4iLCBjbGFzcy5vdXRwdXQ9Im5vdHJ1bm8ifQogICAgZGltKHNjbWF0KQogICAgYGBgCiAgICAKICAgIDxicj4KICAgIAotICAgRmlsdGVyIGVtcHR5IGRyb3BsZXRzIG91dCA6CgogICAgYGBge3IgZWRmaWx0ZXJ9CiAgICAjIyBSZXN0cmljdCB0byB2YWxpZCBiYXJjb2RlcwogICAgc2NtYXRfY2VsbHMgPC0gc2NtYXRbLCBiY19yYW5rMiRWQUxJRF0KICAgIAogICAgIyMgQ29udHJvbAogICAgZGltKHNjbWF0X2NlbGxzKQogICAgYGBgCgo8YnI+CgotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQoKIyBBbWJpZW50IFJOQQoKLSAgIFRoZSBjb3VudHMgbWVhc3VyZWQgaW4gdGhpcyBtYXRyaXggb2YgKG5vdyBjb25zaWRlcmVkICJ0cnVlIikgc2luZ2xlIGNlbGxzCmRvZXMgKipub3QqKiByZWZsZWN0cyB0aGUgbWVhc3VyZW1lbnQgb2YgdGhlc2UgY2VsbHMgZXhwcmVzc2lvbiAqKm9ubHkqKgoKLSAgIER1ZSB0byBvdGhlciAqKm92ZXItbHlzZWQgLyBkeWluZyBjZWxscyoqIGluIHRoZSBlbXVsc2lvbiwgd2UgYWxzbyBtZWFzdXJlZAogICBmcmFjdGlvbiBvZiAqKmFtYmllbnQgUk5BKiogdGhhdCBhZGRzIHVwIHRvIHRoZSAicmVhbCIgZXhwcmVzc2lvbgogICAKLSAgIFdlIG1heSAqKmVzdGltYXRlKiogaXQgYnkgb2JzZXJ2aW5nIGNvdW50cyBleGlzdGluZyBhdCBhICoqbWluaW1hbCBsZXZlbCoqIGFjcm9zcyAKY2VsbHMgdGhhdCBncmVhdGx5IGRpZmZlciBpbiB0aGVpciBleHByZXNzaW9uIHByb2ZpbGUKCiMjIEVzdGltYXRlIHRoZSAic291cCIKClsqKlNvdXBYKipdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9Tb3VwWC9yZWFkbWUvUkVBRE1FLmh0bWwpe3RhcmdldD0iX2JsYW5rIn0KY2FuIGVzdGltYXRlIHRoZSByYXRlIGFuZCBjb21wb3NpdGlvbiBvZiBhbWJpZW50IFJOQSwgaW4gc2V2ZXJhbCB3YXlzIDoKCi0gICAqKk1hbnVhbCBtb2RlKiogOiBwcm92aWRpbmcgYSBsaXN0IG9mIGZlYXR1cmVzIGV4cGVjdGVkIHRvIHJlZmxlY3QKICAgIHRoZSBzb3VwIChnZW5lcyBleHByZXNzZWQgYXQgaGlnaCBsZXZlbHMgaW4gYSBtYWpvcml0eSBvZiB0aGUKICAgIGV4cGVjdGVkIGNlbGwgdHlwZXMpCiAgICAKLSAgICoqQXV0b21hdGljIG1vZGVzKiosIHVzaW5nIHRoZSBlbXB0eSBkcm9wbGV0cy0qKmZpbHRlcmVkKiogcmF3IGNvdW50CiAgICBtYXRyaXggLi4uCiAgICAKICAgIC0gICAuLi4gYWxvbmcgKip3aXRoIHRoZSB1bmZpbHRlcmVkKiogbWF0cml4IChpZSwgY29udGFpbmluZyBhbGwgcXVhbnRpZmllZAogICAgICAgIGRyb3BsZXRzKS4gVGhpcyBpcyB0aGUgbW9zdCBlZmZpY2llbnQgbW9kZS4KICAgICAgICAKICAgIC0gICAuLi4gKipzb2xlbHkqKiA6IHRoaXMgaXMgbGVzcyBlZmZpY2llbnQsIGJ1dCB1c2VmdWwgd2hlbiB0aGUgdW5maWx0ZXJlZAogICAgICAgIG1hdHJpeCBpcyBub3QgYXZhaWxhYmxlLgoKYGBge3IgaF9Tb3VwWF9hdXRvLCBldmFsID0gRkFMU0V9CiMjIFJlYWRpbmcgdGhlIGZ1bmN0aW9uIGhlbHAgcGFnZQo/U0MuaGVscGVyOjpTb3VwWF9hdXRvCmBgYAoKTGV0J3MgcnVuIFNvdXBYIDoKCmBgYHtyIHNvdXB4cmhvRX0KIyMgSW4gY2FzZSBvZiBlcnJvciA6CiMgaW5zdGFsbC5wYWNrYWdlcygiaXJsYmEiLCB0eXBlPSJzb3VyY2UiLCBmb3JjZT1UUlVFKQoKIyMgU291cFgKc291cF9mcmFjIDwtIFNDLmhlbHBlcjo6U291cFhfYXV0bygKICBzY21hdF9maWx0ID0gc2NtYXRfY2VsbHMsIAogIHNjbWF0X3JhdyA9IHNjbWF0KQpgYGAKCjxicj4KCioqKk5PVEUqKiogOiBXZSBjYW4gb2JzZXJ2ZSBbKipNQUxBVDEqKl0oaHR0cHM6Ly93d3cuYmlvcnhpdi5vcmcvY29udGVudC8xMC4xMTAxLzIwMjQuMDcuMTQuNjAzNDY5djIpe3RhcmdldD0iX2JsYW5rIn0gYXMgdGhlIHRvcCBmaXJzdCBnZW5lLgoKPGJyPgoKV2hhdCdzIHRoZSBlc3RpbWF0ZWQgZnJhY3Rpb24gb2Ygc291cCBpbiBvdXIgY291bnRzID8KCmBgYHtyIHNvdXB4ZnJhY30KY2F0KCJTb3VwIGZyYWN0aW9uIDogIiwgc291cF9mcmFjKQpgYGAKCjxicj4KCi0gICAqKlF1ZXN0aW9uKiogOgoKICAgIGBgYHtyIHFfc291cHJhdGUsIGNsYXNzLnNvdXJjZT0icXVlc3Rpb24iLCBldmFsID0gRkFMU0V9CiAgICBTaG91bGQgd2UgdHJ5IHRvIHJlbW92ZSBzdWNoIGFuIGFtb3VudCBvZiBhbWJpZW50IFJOQSA/CiAgICBgYGAKICAgIAogICAgPGJyPgogICAgCiAgICBgYGB7ciBhX3NvdXByYXRlLCBjbGFzcy5zb3VyY2UgPSBjKCJmb2xkLWhpZGUiLCAiYW5zd2VyIiksIGV2YWwgPSBGQUxTRX0KICAgIFRoZSBiZXN0IHdheSB0byBrbm93IGlzIHRvIHRyeSBpdCBvdXQgIQogICAgYGBgCgo8YnI+PGJyPgoKCiMjIFJlbW92ZSB0aGUgInNvdXAiCgpTb3VwWCBjYW4gKipyZW1vdmUqKiB0aGUgc291cCAoc3VidHJhY3QgY291bnRzIGZvciBpZGVudGlmaWVkIHNvdXAgZ2VuZXMKdG8gYWxsIGJhcmNvZGVzL2NlbGxzKSB1c2luZyBkaWZmZXJlbnQgbWV0aG9kcyA6CgotICAgKipBdXRvbWF0aWNhbGx5KiogKHNhZmVyKQoKLSAgIFNwZWNpZnlpbmcgYSAqKmZyYWN0aW9uIG9mIGNvdW50cyoqIHRvIHJlbW92ZS4KCiAgICAtICAgKlByb3MqIDoKICAgIAogICAgICAgIC0gICBCeSByZW1vdmluZyBYICUgb2YgcmVhZHMgOgogICAgICAgIAogICAgICAgICAgICAtICAgaWYgc291cCB3YXMgZWZmZWN0aXZlbHkgcHJlc2VudCAoYXQgYSByYXRlIFw8PSBYKSwgdGhpcwogICAgICAgICAgICAgICAgd2lsbCAqKm1vc3RseSBhZmZlY3Qgc291cCoqIGdlbmVzIGZpcnN0LgogICAgICAgICAgICAgICAgCiAgICAgICAgICAgIC0gICBJZiBub3QsIGl0IHdpbGwgKipkZWNyZWFzZSBjb3VudHMqKiBnbG9iYWxseSwgdGh1cyBoYXZlCiAgICAgICAgICAgICAgICBuZWdsaWdpYmxlIGVmZmVjdCBpbiBkaWZmZXJlbmNlcyBiZXR3ZWVuIGNlbGxzIC8gY2VsbAogICAgICAgICAgICAgICAgdHlwZXMuCiAgICAgICAgICAgICAgICAKICAgIC0gICAqQ29ucyogOgogICAgCiAgICAgICAgLSAgICoqQ291bnRzIGFyZSoqIGFscmVhZHkgKipyYXJlKiogaW4gZHJvcGxldC1iYXNlZCBzaW5nbGUgY2VsbAogICAgICAgICAgICB0ZWNobm9sb2dpZXMuLi4gUmVtb3ZpbmcgdGhlbSAiYnkgZGVmYXVsdCIgbWFrZXMgdGhlbSAqKmV2ZW4KICAgICAgICAgICAgbW9yZSByYXJlKiouLi4KICAgICAgICAgICAgCiAgICAgICAgLSAgIElmIGFjdHVhbCBzb3VwICoqcmF0ZSBpcyBoaWdoZXIqKiB0aGFuIHRoZSBkZWZpbmVkIHJhdGUgWCB0bwogICAgICAgICAgICByZW1vdmUsIHRoaXMgbW9kZSB3aWxsIHJlbWFpbiAqKnVuZWZmZWN0aXZlKiouLi4KCmBgYHtyIHNvdXB4cm19CiMjIFJ1biBTb3VwWCBpbiAicmVtb3ZhbCIgbW9kZQpzY21hdF91bnNvdXAgPC0gU0MuaGVscGVyOjpTb3VwWF9hdXRvKAogIHNjbWF0X2ZpbHQgPSBzY21hdF9jZWxscywgCiAgc2NtYXRfcmF3ID0gc2NtYXQsIAogIHJldHVybl9vYmplY3QgPSBUUlVFKQoKIyMgV2UgY2FuIHJlbW92ZSB0aGUgYmFyY29kZSBtYXRyaXggdG8gZnJlZSBzb21lIFJBTQpybShzY21hdCkKYGBgCgo8YnI+CgojIyBWaXN1YWxpemUgdGhlICJzb3VwIiByZW1vdmFsCgotICAgTm93LCB3ZSB3YW50IHRvICoqY2hhcmFjdGVyaXplIHRoZSBlZmZlY3QqKiBvZiB0aGUgcmVtb3ZhbCBvZiB0aGUgYW1iaWVudApSTkEsIGJ5IGNvbXBhcmluZyB0aGUgcHJlLSBhbmQgcG9zdC0gc291cFggbWF0cmljZXMuIAoKLSAgIEluIHRoaXMgcHVycG9zZSwgd2Ugd2lsbCBjb25zdHJ1Y3QgYSAqKlNldXJhdCBvYmplY3QqKiBmcm9tIGVhY2ggb2YgdGhlc2UKbWF0cmljZXMsIHRoYXQgd2Ugd2lsbCAqKmRlc2NyaWJlKiosIHRoZW4gKipyZXByZXNlbnQqKiBncmFwaGljYWxseS4KCi0gICBCdXQgaG93IHRvICoqY3JlYXRlIGEgU2V1cmF0IG9iamVjdCoqIGZyb20gYSBjb3VudCBtYXRyaXggPwoKYGBge3IgaF9DcmVhdGVTZXVyYXRPYmplY3QsIGNsYXNzLnNvdXJjZSA9ICJmb2xkLWhpZGUiLCBldmFsID0gRkFMU0V9CiMjIFJlYWRpbmcgdGhlIGZ1bmN0aW9uIGhlbHAgcGFnZQo/U2V1cmF0OjpDcmVhdGVTZXVyYXRPYmplY3QKYGBgCgo8YnI+PGJyPjxicj4KCiMjIyBDcmVhdGUgYW5kIGRlc2NyaWJlIHRoZSBvYmplY3RzIHsudGFic2V0IC50YWJzZXQtZmFkZSAudGFic2V0LXBpbGxzfQoKIyMjIyBCZWZvcmUgU291cFgKCi0gICBDcmVhdGUgdGhlIFNldXJhdCBvYmplY3QKCmBgYHtyIGNyZWF0ZXNldXJhdE5TfQojIyBDcmVhdGUgdGhlIFNldXJhdCBvYmplY3QgZm9yIHVuc291cGVkIGNlbGxzCnNvYmogPC0gU2V1cmF0OjpDcmVhdGVTZXVyYXRPYmplY3QoCiAgY291bnRzID0gc2NtYXRfY2VsbHMsIAogIHByb2plY3QgPSAiUEJNQzEwS19CZWZvcmVTb3VwWCIpCmBgYAoKLSAgIERlc2NyaWJlCgogICAgLSAgIFdlIHdpbGwgdXNlIGEgZnVuY3Rpb24gcHJlcGFyZWQgZm9yIHlvdSA6IGBTQy5oZWxwZXI6OlNldXJhdE9iamVjdF9kZXNjcmlwdG9yYAoKYGBge3IgZGVzY3NldXJhdE5TfQojIyBEZXNjaWJlIHRoZSBvYnRhaW5lZCBTZXVyYXQgb2JqZWN0ClNDLmhlbHBlcjo6U2V1cmF0T2JqZWN0X2Rlc2NyaXB0b3Ioc29iaiA9IHNvYmopCmBgYAoKPGJyPgoKIyMjIyBBZnRlciBTb3VwWAoKLSAgIENyZWF0ZSB0aGUgU2V1cmF0IG9iamVjdAoKYGBge3IgY3JlYXRlc2V1cmF0U30KIyMgQ3JlYXRlIHRoZSBTZXVyYXQgb2JqZWN0IGZvciB1bnNvdXBlZCBjZWxscwpzb2JqX3Vuc291cCA8LSBTZXVyYXQ6OkNyZWF0ZVNldXJhdE9iamVjdCgKICBjb3VudHMgPSBzY21hdF91bnNvdXAsIAogIHByb2plY3QgPSAiUEJNQzEwS19BZnRlclNvdXBYIikKYGBgCgotICAgRGVzY3JpYmUKCmBgYHtyIGRlc2NzZXVyYXRTLCBjbGFzcy5zb3VyY2U9Im5vdHJ1biIsIGNsYXNzLm91dHB1dD0ibm90cnVubyJ9CiMjIERlc2NyaWJlIHRoZSBuZXcgb2JqZWN0IGNvbnRlbnQKU0MuaGVscGVyOjpTZXVyYXRPYmplY3RfZGVzY3JpcHRvcihzb2JqID0gc29ial91bnNvdXApCmBgYAoKIyMgIHsudW5udW1iZXJlZH0KCjxicj4KCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQoKLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCgo8YnI+CgotICAgKipRdWVzdGlvbnMqKiA6CgogICAgYGBge3IgcV9kZXNjZGlmZiwgY2xhc3Muc291cmNlPSJxdWVzdGlvbiIsIGV2YWwgPSBGQUxTRX0KICAgIENhbiB5b3UgZGVzY3JpYmUgdGhlIGRpZmZlcmVuY2UgKGVzcGVjaWFsbHkgZm9yIGNvdW50cyBhbmQgc3BhcnNpdHkpID8KICAgICAgCiAgICBJcyBpdCBleHBlY3RlZCA/CiAgICBgYGAKICAgIAogICAgPGJyPgogICAgCiAgICBgYGB7ciBhX2Rlc2NkaWZmLCBjbGFzcy5zb3VyY2UgPSBjKCJmb2xkLWhpZGUiLCAiYW5zd2VyIiksIGV2YWwgPSBGQUxTRX0KICAgICMjIC4gVG90YWwgY291bnRzLCBtYXggdmFsdWUsIGNvdW50cyBwZXIgY2VsbCBhbmQgbnVtYmVyIG9mCiAgICAjIyAgIGV4cHJlc3NlZCBmZWF0dXJlcyBwZXIgY2VsbCwgYWxsIGhhdmUgZGVjcmVhc2VkLgogICAgIyMKICAgICMjIC4gVGhlIHNwYXJzaXR5IGxldmVsIGhhcyBzbGlnaHRseSBpbmNyZWFzZWQgKHJlbW92aW5nIHRoZSBzb3VwCiAgICAjIyAgIG9ibGl0ZXJhdGVkIGFsbCBjb3VudHMgZm9yIHNvbWUgZmVhdHVyZXMgaW4gc29tZSBjZWxscykuCiAgICAjIwogICAgIyMgLiBBbGwgb2YgdGhpcyBpcyBhYnNvbHV0ZWx5IGV4cGVjdGVkLgogICAgYGBgCiAgICAKPGJyPgoKV2hhdCBhcmUgdGhlIGZlYXR1cmVzIG1vc3QgYWZmZWN0ZWQgYnkgdGhlIHNvdXAgcmVtb3ZhbCA/CgpgYGB7ciBzb3VwZmVhdHVyZXMsIGNsYXNzLnNvdXJjZT0ibm90cnVuIiwgY2xhc3Mub3V0cHV0PSJub3RydW5vIn0KIyMgQ29tcHV0ZSB0aGUgZnJhY3Rpb24gbWF0cml4IChQUkUgLyBQT1NUKQojIyAgTk9URSA6IHdlIGhhdmUgMCBjb3VudHMgaW4gdGhlIGRpdmlkZXIsIAojIyAgc28gd2UgaW5jcmVtZW50IGJvdGggbWF0cml4IGJ5ICsxICEKY29udF9mcmFjIDwtIChzY21hdF9jZWxscyArIDEpIC8gKHNjbWF0X3Vuc291cCArIDEpCgojIyBGcmFjdGlvbiBvZiBjb3VudHMgZGVjcmVhc2VkIGJ5IHNvdXAgcmVtb3ZhbCwgcGVyIGZlYXR1cmUgKG9yZGVyZWQpCmZlYXRfZnJhYyA8LSBzb3J0KHNwYXJzZU1hdHJpeFN0YXRzOjpyb3dNZWFuczIoY29udF9mcmFjKSwgZGVjcmVhc2luZyA9IFRSVUUpCgojIyMgRGlzcGxheSBUb3AgNSBmZWF0dXJlcwp1dGlsczo6aGVhZChmZWF0X2ZyYWMsIGRlY3JlYXNpbmcgPSBUUlVFKQoKIyMgUmVtb3Zpbmcgb2JqZWN0cyB0byBmcmVlIHNvbWUgUkFNCnJtKGNvbnRfZnJhYywgZmVhdF9mcmFjKQpgYGAKCjxicj4KCmBgYHtyfQojIyBSZW1vdmUgdW51c2VkIG9iamVjdHMgdG8gZnJlZSBzb21lIFJBTQpybShzY21hdF9jZWxscywgc2NtYXRfdW5zb3VwKQpgYGAKCjxicj4KCiMjIyBQbG90IHRoZSAidG9wIDEiIGZlYXR1cmUgey50YWJzZXQgLnRhYnNldC1mYWRlIC50YWJzZXQtcGlsbHN9CgotICAgV2UgY29kZWQgYSBzbWFsbCBSIGZ1bmN0aW9uIHRvIGhlbHAgeW91IHZpc3VhbGl6ZSBpbiAyRCB5b3VyIGRhdGEsIGV2ZW4gYXQgCnN1Y2ggYSB2ZXJ5IGVhcmx5IHN0ZXAgb2YgdGhlIGFuYWx5c2lzLgoKLSAgIFRoaXMgZnVuY3Rpb24gd2lsbCBhbmFseXplIHlvdXIgZGF0YSAoYXMgYSBTZXVyYXQgb2JqZWN0KSBpbiB0aGUgc2hhZG93cyB3aXRoIGRlZmF1bHQgdmFsdWVzLCBhbmQgZ2VuZXJhdGUgYSAyRCByZXByZXNlbnRhdGlvbiBjYWxsZWQgWyoqVU1BUCoqXShodHRwczovL3VtYXAtbGVhcm4ucmVhZHRoZWRvY3MuaW8vZW4vbGF0ZXN0Lyl7dGFyZ2V0PSJfYmxhbmsifS4KCi0gICBBcyBkZWZhdWx0IHZhbHVlcyBhcmUgdXNlZCwgdGhleSBzaG91bGQgZml0IG1vc3QgY2FzZXMuICoqSG93ZXZlciwgZG8gbm90CmZ1bGx5IHRydXN0KiogYnkgZGVmYXVsdCBpdHMgcmVzdWx0cyBhcyBhIHB1cmUgZ3JvdW5kIHRydXRoLCBhcyB5b3VyIGRhdGEgbWF5IG5vdCBmaXQgdGhlc2UgZGVmYXVsdHMuLi4gVGhhdCdzIHdoeSB0aGUgZnVuY3Rpb24gaXMgbmFtbWVkICJRbkRfdml6IiwgYXMgaW4gIlF1aWNrIGFuZCBkaXJ0eSB2aXN1YWxpemF0aW9uIi4KCmBgYHtyIGhfUW5EX3ZpeiwgZXZhbCA9IEZBTFNFfQojIyBSZWFkaW5nIHRoZSBmdW5jdGlvbiBoZWxwIHBhZ2UKP1NDLmhlbHBlcjo6UW5EX3ZpegpgYGAKCiMjIyMgQmVmb3JlIFNvdXBYCgpgYGB7ciBic291cHgsIGZpZy5oZWlnaHQgPSA2LCBmaWcud2lkdGggPSA2fQojIyBUb3AxIGZlYXR1cmUgCnVtYXBfcHJlIDwtIFNDLmhlbHBlcjo6UW5EX3ZpeigKICBzb2JqID0gc29iaiwgCiAgZmVhdHVyZXMgPSAiTFlaIiwKICByZXR1cm5fcGxvdCA9IFRSVUUpCmBgYAoKPGJyPgoKIyMjIyBBZnRlciBTb3VwWAoKYGBge3IgYXNvdXB4LCBmaWcuaGVpZ2h0ID0gNiwgZmlnLndpZHRoID0gNiwgY2xhc3Muc291cmNlPSJiZXlvbmQiLCBjbGFzcy5vdXRwdXQ9ImJleW9uZG8ifQojIyBUb3AxIGZlYXR1cmUgCnVtYXBfcG9zdCA8LSBTQy5oZWxwZXI6OlFuRF92aXooCiAgc29iaiA9IHNvYmpfdW5zb3VwLCAKICBmZWF0dXJlcyA9ICJMWVoiLAogIHJldHVybl9wbG90ID0gVFJVRSkKYGBgCgo8YnI+CgojIyAgey51bm51bWJlcmVkfQoKLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCgotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KCk1lcmdpbmcgcGxvdHMgZm9yIGVhc2Ugb2YgdXNlIDoKCmBgYHtyIHNvdXB4X3VtYXBzLCBmaWcud2lkdGggPSAxMiwgZmlnLmhlaWdodCA9IDYsIGNsYXNzLnNvdXJjZT0iYmV5b25kIiwgY2xhc3Mub3V0cHV0PSJiZXlvbmRvIn0KIyMgVXNpbmcgdGhlIHBhdGNod29yayBwYWNrYWdlIHRvIG1lcmdlIHBsb3RzIChhbmQgZ2dwbG90MiB0byBhZGQgdGl0bGVzKQpwYXRjaHdvcms6OndyYXBfcGxvdHMoCiAgbGlzdCgKICAgIHVtYXBfcHJlICYgZ2dwbG90Mjo6Z2d0aXRsZShsYWJlbCA9ICJMWVogYmVmb3JlIFNvdXBYIiksIAogICAgdW1hcF9wb3N0ICYgZ2dwbG90Mjo6Z2d0aXRsZShsYWJlbCA9ICJMWVogYWR0ZXIgU291cFgiKSksIAogIG5yb3cgPSAxKQpgYGAKCjxicj4KCi0gICAqKlF1ZXN0aW9uKioKCiAgICBgYGB7ciBxX3VtYXBzb3VweCwgY2xhc3Muc291cmNlPSJxdWVzdGlvbiIsIGV2YWwgPSBGQUxTRX0KICAgIENhbiB5b3UgY29tcGFyZSB0aGUgdHdvIHBsb3RzIDoKICAgICAgLSBmb3IgdGhlIExZWiBleHByZXNzaW9uIGdsb2JhbGx5ID8KICAgICAgLSBmb3IgdGhlIExZWiBleHByZXNzaW9uIGFjcm9zcyB0aGUgMi1EIHNwYWNlID8KICAgICAgLSBhYm91dCB0aGUgc3BhY2UgdG9wb2xvZ2llcyA/CiAgICBgYGAKICAgIAogICAgPGJyPgogICAgCiAgICBgYGB7ciBjb21wYXIsIGNsYXNzLnNvdXJjZSA9IGMoImZvbGQtaGlkZSIsICJhbnN3ZXIiKSwgZXZhbCA9IEZBTFNFfQogICAgIyMgLiBUaGUgbGV2ZWwgZXhwcmVzc2lvbiBtZWFzdXJlZCBpbiBjZWxscyB3aXRoIHNtYWxsZXIgZXhwcmVzc2lvbgogICAgIyMgICBiZWZvcmUgU291cFggaGFzIGdvbmUgdG8gYWxtb3N0IG5vbmUgYWZ0ZXIgOiB0aGUgYW1iaWVudCBleHByZXNzaW9uCiAgICAjIyAgIG9mIHRoaXMgZ2VuZSBoYXMgZWZmaWNpZW50bHkgYmVlbiByZW1vdmVkLgogICAgIyMKICAgICMjIC4gSG9wZWZ1bGx5LCB0aGUgY2x1c3RlcihzKSB3aXRoIGhpZ2hlciBleHByZXNzaW9uIHJlbWFpbihzKSBoaWdoLgogICAgIyMKICAgICMjIC4gVGhlIGV4cHJlc3Npb24gc2NhbGUgdXBwZXIgYm91bmQgYWZ0ZXIgU291cFggaXMgaGlnaGVyIHRoYW4gYmVmb3JlID8hIAogICAgIyMgICBCdXQgd2UgUkVNT1ZFRCBzb21lIGNvdW50cyA/ISBXaGlsZSBiZWluZyBjb3VudGVyLWludHVpdGl2ZSwgdGhpcyBpcwogICAgIyMgICBhIHBvc2l0aXZlIGNvbnNlcXVlbmNlIG9mIHRoZSBhbWJpZW50IHJlbW92YWwgb24gdGhlIHNjYWxpbmcgb2YKICAgICMjICAgYmFyY29kZXMgZXhwcmVzc2lvbiAoc2VlIGxhdGVyKS4gCiAgICAjIwogICAgIyMgLiBUaGUgZ2xvYmFsIHRvcG9sb2d5IG9mIGNsdXN0ZXJzIHJlbWFpbnMgY2xvc2UgKGJ1dCBub3QgaWRlbnRpY2FsKS4KICAgIGBgYAogICAgCjxicj4KCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQoKLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCgo8YnI+PGJyPjxicj4KCioqQmV5b25kKiogOgoKMS4gIFNhdmUgdGhlIGBzb2JqYCBSIG9iamVjdCBvbiBkaXNrLgoKYGBge3IgYl9zYXZlX2Fuc3dlcjEsIGNsYXNzLnNvdXJjZSA9IGMoImZvbGQtaGlkZSIsICJiZXlvbmQiKSwgZXZhbCA9IEZBTFNFfQojIyBBIHNpbmdsZSBvYmplY3QgOiB1c2UgUkRTIGZvcm1hdCB3aXRoIHNhdmVSRFMoKQpzYXZlUkRTKG9iamVjdCA9IHNvYmosIGZpbGUgPSBwYXN0ZTAob3V0cHV0X2RpciwgIi9zb2JqLlJEUyIpKQpgYGAKCjIuICBTYXZlIGJvdGggdGhlIGBzb2JqYCBhbmQgYHNvYmpfdW5zb3VwYCBvYmplY3RzIGluIGEgc2luZ2xlIGFyY2hpdmUKICAgIG9uIGRpc2suCgpgYGB7ciBiX3NhdmVfYW5zd2VyMiwgY2xhc3Muc291cmNlID0gYygiZm9sZC1oaWRlIiwgImJleW9uZCIpLCBldmFsID0gRkFMU0V9CiMjIE11bHRpcGxlIG9iamVjdHMgOiB1c2UgUmRhdGEgZm9ybWF0IHdpdGggc2F2ZSgpCnNhdmUobGlzdCA9IGMoc29iaiwgc29ial91bnNvdXApLCBmaWxlID0gcGFzdGUwKG91dHB1dF9kaXIsICIvc29iamVjdHMuUkRBIikpCmBgYAoKMy4gIFNhbWUgYXMgMS4gYnV0IHVzZSB0aGUgImJ6aXAyIiBjb21wcmVzc2lvbiBhbGdvcml0aG0KCmBgYHtyIGJfc2F2ZV9hbnN3ZXIzLCBjbGFzcy5zb3VyY2UgPSBjKCJmb2xkLWhpZGUiLCAiYmV5b25kIiksIGV2YWwgPSBGQUxTRX0KIyMgQ29tcHJlc3Mgd2l0aCBiemlwMgpzYXZlUkRTKG9iamVjdCA9IHNvYmosIGZpbGUgPSBwYXN0ZTAob3V0cHV0X2RpciwgIi9zb2JqLlJEUyIpLCBjb21wcmVzcyA9ICJiemlwMiIpCmBgYAoKPGJyPgoKLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCgotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KCjxicj48YnI+PGJyPgoKIyBSc2Vzc2lvbgoKYGBge3IgcnNlc3Npb24sIGNsYXNzLnNvdXJjZT0ibm90cnVuIiwgY2xhc3Mub3V0cHV0PSJub3RydW5vIn0KdXRpbHM6OnNlc3Npb25JbmZvKCkKYGBgCg==