Creative Commons License

This is part of the online course Statistical Genomics Analysis (SGA)

This chapter builds upon the introductory course to mixed models for proteomics data analysis. We will here cover more advanced concepts using msqrob2. To illustrate these advanced concepts, we will use the spike-in study published by Huang et al. (2020). We chose this data set because:

  1. Spike-in data contain ground truth information about which proteins are differentially abundant, enabling us to show the impact of different analysis strategies.
  2. It has been acquired with a TMT-labelling strategy that require a complex experimental design. This provides an excellent example to explain the different sources of variability in an MS experiment and demonstrate the flexibility of msqrob2 to model this variability.

1 Background

Labelling strategies in mass spectrometry (MS)-based proteomics enhance sample throughput by enabling the acquisition of multiple samples within a single run. The labelling strategy that allows the highest multiplexing is the tandem mass tag (TMT) labelling and will be the focus of the current tutorial.

1.1 TMT workflow

TMT-based workflow highly overlap with label-free workflows. However, TMT-based workflows have an additional sample preparation step, where the digested peptides from each sample are labelled with a TMT reagent and samples with different TMT reagents are pooled in a single TMT mixture. The signal processing is also slightly affected since the quantification no longer occurs in the MS1 space but at the MS2 level. It is important to understand that TMT reagent are isobaric, meaning that the same peptide with different TMT labels will have the same mass for the intact ion, as recorded during MS1. However, the TMT fragments that are released upon fragmentation during MS2, also called TMT reporter ions, have label-specific masses. The TMT fragments have an expected mass and are distributed in a low-mass range of the MS2 space. The intensity of each TMT fragment is directly proportional to the peptide quantity in the original sample before pooling. The TMT fragment intensities measured during MS2 are used as quantitative data. The higher mass range contains the peptide fragments that compose the peptide fingerprint, similarly to LFQ. This data range is therefore used for peptide identification. Interestingly, the peptide fingerprint originates from the same peptide across multiple samples. This leads to a signal boost for low abundant peptides and hence should improve data sensitivity and consistency.

Overview of an TMT-based proteomics workflow.

Overview of an TMT-based proteomics workflow.

1.2 Challenges

The analysis of TMT-based proteomics data shares the same challenges as the data analysis challenges for LFQ:

  • MS-based proteomics doesn’t measure proteins directly, but their constituting peptide ions. The protein-level information needs to be reconstructed from the ion data. In this tutorial, we will start from the peptide data, which has been constructed from the ion data by MaxQuant.
  • All peptides cannot be ionised with the same efficiency. Poor ionisation will lead to reduced signal as less ions will hit the detector, hence leading to a huge variability in intensity among different peptide species, even when they originate from the same protein.
  • The identification step is not trivial and prone to errors. PSM misidentification leads to the assignment of a quantitative values from another peptide with likely another ionisation efficiency and relative abundance. Hence this misassigned value will become an outlier.
  • Moreover, the ion selection for MS2 depends on its intensity (recall that only the top most intense ion peaks are send for MS2). Therefore, the chance to measure and, subsequently, identify a peptide will depend on its abundance. Non identified peptides will lead to data missingness, which is related to the underlying quantification value. This phenomenon is known as missingness not at random. Next to that, many reasons can lead to ions not being selected or identified irrespective of their quantification value leading to missingness that is not related to its quantitative value. This is referred to as missingness completely at random. The missingness issue is not negligible: only 41% of all proteins are quantified across all samples, and the number drops to 6.6% when considering peptides.
  • The identification issues lead to unbalanced peptide missingness across samples, and the patterns of missing values are potentially different for every peptide, highlighting the need for an automatised solution that is robust against missing values.
  • Technical variations during the experiment can lead to systematic fluctuations across samples. The most obvious reason is when different sample amounts are injected into the instruments, due to small pipetting inconsistencies for instance. However, these differences lead to unwanted variation that should be discarded when answering biological questions.
  • TMT workflows impose an additional challenge. Contemporary experiments often involve increasingly complex designs, where the number of samples exceeds the capacity of a single TMT mixture, resulting in a complex correlation structure that must be addressed for accurate statistical inference. We will describe in the modelling section the different sources of variation.

1.3 Experimental context

The data set used in this chapter is a spike-in experiment (PXD0015258) published by Huang et al. (2020). It consists of controlled mixtures with known ground truth. UPS1 peptides at concentrations of 500, 333, 250, and 62.5 fmol were spiked into 50 g of SILAC HeLa peptides, each in duplicate. These concentrations form a dilution series of 1, 0.667, 0.5, and 0.125 relative to the highest UPS1 peptide amount (500 fmol). A reference sample was created by combining the diluted UPS1 peptide samples with 50g of SILAC HeLa peptides. All dilutions and the reference sample were prepared in duplicate, resulting in a total of ten samples. These samples were then treated with TMT10-plex reagents and combined before LC-MS/MS analysis. This protocol was repeated five times, each with three technical replicates, totaling 15 MS runs.

We will start from the PSM data generated by Skyline and infer protein-level differences between samples. To achieve this goal, we will apply an msqrob2TMT workflow, a data processing and modelling workflow dedicated to the analysis of TMT-based proteomics datasets. We will demonstrate how the workflow can highlight the spiked-in proteins. Before delving into the analysis, let us prepare our computational environment.

2 Load packages

We load the msqrob2 package, along with additional packages for data manipulation and visualisation.

library("msqrob2")
library("dplyr")
library("ggplot2")
library("patchwork")

We also configure the parallelisation framework.

library("BiocParallel")
register(SerialParam())

3 Data

The data have been deposited by the authors in the MSV000084264 MASSiVE repository, but we will retrieve the time stamped data from our Zenodo repository. We need 2 files: the Skyline identification and quantification table generated by the authors and the sample annotation files.

To facilitate management of the files, we download the required files using the BiocFileCache package. The chunk below will take some time to complete the first time you run it as it needs to download the (large) file locally, but will fetch the local copy the following times.

library("BiocFileCache")
bfc <- BiocFileCache()
psmFile <- bfcrpath(bfc, "https://zenodo.org/records/14767905/files/spikein1_psms.txt?download=1")
annotFile <- bfcrpath(bfc, "https://zenodo.org/records/14767905/files/spikein1_annotations.csv?download=1")

Now the files are downloaded, we can load the two tables.

3.1 PSM table

An MS experiment generates spectra. Each MS2 spectra are used to infer the peptide identity thanks to a search engine. When an observed spectrum is matched to a theoretical peptide spectrum, we have a peptide-to-spectrum match (PSM). The identification software compiles all the PSMs inside a table. Hence, the PSM data is the lowest possible level to perform data modelling.

Each row in the PSM data table contains information for one PSM (the table below shows the first 6 rows). The columns contains various information about the PSM, such as the peptide sequence and charge, the quantified value, the inferred protein group, the measured and predicted retention time and precursor mass, the score of the match, … In the case of TMT data, the quantification values are provides in multiple columns (start with "Abundance."), one for each TMT label. Regardless of TMT or LFQ experiments, the PSM table stacks the quantitative values from samples in different runs below each other.

psms <- read.delim(psmFile)
qcols <- grep("Abundance", colnames(psms), value = TRUE)
Checked Confidence Identifying.Node PSM.Ambiguity Annotated.Sequence Modifications Marked.as X..Protein.Groups X..Proteins Master.Protein.Accessions Master.Protein.Descriptions Protein.Accessions Protein.Descriptions X..Missed.Cleavages Charge DeltaScore DeltaCn Rank Search.Engine.Rank m.z..Da. MH…Da. Theo..MH…Da. DeltaM..ppm. Deltam.z..Da. Activation.Type MS.Order Isolation.Interference…. Average.Reporter.S.N Ion.Inject.Time..ms. RT..min. First.Scan Spectrum.File File.ID Abundance..126 Abundance..127N Abundance..127C Abundance..128N Abundance..128C Abundance..129N Abundance..129C Abundance..130N Abundance..130C Abundance..131 Quan.Info Ions.Score Identity.Strict Identity.Relaxed Expectation.Value Percolator.q.Value Percolator.PEP
False High Mascot (O4) Unambiguous [K].gFQQILAGEYDHLPEQAFYMVGPIEEAVAk.[A] N-Term(TMT6plex); K30(TMT6plex) 1 1 P06576 ATP synthase subunit beta, mitochondrial OS=Homo sapiens GN=ATP5B PE=1 SV=3 P06576 ATP synthase subunit beta, mitochondrial OS=Homo sapiens GN=ATP5B PE=1 SV=3 0 3 1.0000 0 1 1 1270.3249 3808.960 3808.966 -1.51 -0.00192 CID MS2 47.955590 8.7 50.000 212.2487 112815 161117_SILAC_HeLa_UPS1_TMT10_Mixture1_03.raw F1 2548.326 3231.929 2760.839 4111.639 3127.254 1874.163 2831.423 2298.401 3798.876 3739.067 NA 90 28 21 0.0000000 0 0.0000140
False High Mascot (K2) Unambiguous [R].qYPWGVAEVENGEHcDFTILr.[N] N-Term(TMT6plex); C15(Carbamidomethyl); R21(Label:13C(6)15N(4)) 1 1 Q16181 Septin-7 OS=Homo sapiens GN=SEPT7 PE=1 SV=2 Q16181 Septin-7 OS=Homo sapiens GN=SEPT7 PE=1 SV=2 0 3 1.0000 0 1 1 920.4493 2759.333 2759.332 0.31 0.00028 CID MS2 9.377507 8.1 3.242 164.7507 87392 161117_SILAC_HeLa_UPS1_TMT10_Mixture3_03.raw F5 22861.765 25817.946 23349.498 29449.609 25995.929 22955.769 30578.971 30660.488 38728.853 25047.280 NA 76 24 17 0.0000001 0 0.0000003
False High Mascot (K2) Unambiguous [R].dkPSVEPVEEYDYEDLk.[E] N-Term(TMT6plex); K2(Label); K17(Label) 1 1 Q9Y450 HBS1-like protein OS=Homo sapiens GN=HBS1L PE=1 SV=1 Q9Y450 HBS1-like protein OS=Homo sapiens GN=HBS1L PE=1 SV=1 1 3 0.9730 0 1 1 920.1605 2758.467 2758.461 2.08 0.00192 CID MS2 38.317050 17.8 13.596 143.4534 74786 161117_SILAC_HeLa_UPS1_TMT10_Mixture3_03.raw F5 25504.083 27740.450 25144.974 25754.579 29923.176 34097.637 31650.255 27632.692 23886.881 35331.092 NA 74 30 23 0.0000004 0 0.0000010
False High Mascot (F2) Selected [R].hEHQVMLmr.[Q] N-Term(TMT6plex); M8(Oxidation); R9(Label:13C(6)15N(4)) 1 1 Q15233 Non-POU domain-containing octamer-binding protein OS=Homo sapiens GN=NONO PE=1 SV=4 Q15233 Non-POU domain-containing octamer-binding protein OS=Homo sapiens GN=NONO PE=1 SV=4 0 4 0.5250 0 1 1 359.6898 1435.737 1435.738 -0.04 -0.00002 CID MS2 21.390040 36.5 50.000 21.6426 6458 161117_SILAC_HeLa_UPS1_TMT10_Mixture4_02.raw F10 13493.228 14674.490 11187.900 12831.495 13839.426 12441.353 13450.885 14777.844 13039.995 12057.121 NA 40 25 18 0.0003351 0 0.0001175
False High Mascot (K2) Unambiguous [R].dNLTLWTADNAGEEGGEAPQEPQS.[-] N-Term(TMT6plex) 1 1 P31947 14-3-3 protein sigma OS=Homo sapiens GN=SFN PE=1 SV=1 P31947 14-3-3 protein sigma OS=Homo sapiens GN=SFN PE=1 SV=1 0 3 1.0000 0 1 1 920.0943 2758.268 2758.264 1.53 0.00141 CID MS2 0.000000 16.7 6.723 174.1863 92950 161117_SILAC_HeLa_UPS1_TMT10_Mixture3_03.raw F5 64582.786 50576.417 47126.037 56285.129 46257.310 52634.885 49716.850 60660.574 55830.488 40280.577 NA 38 21 14 0.0002153 0 0.0000138
False High Mascot (K2) Unambiguous [R].aLVAIGTHDLDTLSGPFTYTAk.[R] N-Term(TMT6plex); K22(Label) 1 1 Q9NSD9 Phenylalanine–tRNA ligase beta subunit OS=Homo sapiens GN=FARSB PE=1 SV=3 Q9NSD9 Phenylalanine–tRNA ligase beta subunit OS=Homo sapiens GN=FARSB PE=1 SV=3 0 3 0.9783 0 1 1 919.8502 2757.536 2757.532 1.48 0.00136 CID MS2 30.619960 26.7 8.958 176.4863 94294 161117_SILAC_HeLa_UPS1_TMT10_Mixture3_03.raw F5 35404.709 31905.852 30993.941 36854.351 37506.001 25703.444 38626.598 35447.942 33788.409 32031.516 NA 46 29 22 0.0002060 0 0.0000720

There is a peculiarity with the dataset: the spectra have been identified with 2 nodes. In one node, the authors searched the SwissProt database for proteins with static modifications related to the metabolic labelling, in the other node they searched the Sigma_UPS protein database without these static modifications. However, some spectra were identified by both nodes leading to duplicate PSMs. We here remove these duplicated PSMs that are identification artefacts.

duplicatesQuants <- duplicated(psms[, qcols]) | duplicated(psms[, qcols], fromLast = TRUE)
psms <- psms[!duplicatesQuants, ]

We will also subset the data set to reduce computational costs. If you want to run the analysis on the full data set, you can skip this code chunk. The subsetting will keep all UPS proteins, known to be differentially abundant by experimental design, and we will keep 500 background proteins known to be unchanged across condition.

allProteins <- unique(psms$Protein.Accessions)
upsProteins <- grep("ups", allProteins, value = TRUE)
helaProteins <- grep("ups", allProteins, value = TRUE, invert = TRUE)
set.seed(1234)
keepProteins <- c(upsProteins, sample(helaProteins, 500))
psms <- psms[psms$Protein.Accessions %in% keepProteins, ]

3.2 Sample annotation table

Each row in the annotation table contains information about one sample. The columns contain various descriptors about the sample, such as the name of the sample or the MS run, the treatment (here the spike-in condition), the lab that acquired the sample or any other biological or technical information that may impact the data quality or the quantification. Without an annotation table, no analysis can be performed. The annotation table used in this tutorial has been generated by the authors.

coldata <- read.csv(annotFile)

We perform a little cleanup for concise output, and rename the Channel column to Label because it contains information about the TMT label used.

coldata <- coldata[, c("Run", "Channel", "Condition", "Mixture", "TechRepMixture")]
coldata$File.Name <- coldata$Run
coldata$Run <- sub("^.*(Mix.*).raw", "\\1", coldata$Run)
colnames(coldata)[2] <- "Label"
Run Label Condition Mixture TechRepMixture File.Name
Mixture1_01 126 Norm Mixture1 1 161117_SILAC_HeLa_UPS1_TMT10_Mixture1_01.raw
Mixture1_01 127N 0.667 Mixture1 1 161117_SILAC_HeLa_UPS1_TMT10_Mixture1_01.raw
Mixture1_01 127C 0.125 Mixture1 1 161117_SILAC_HeLa_UPS1_TMT10_Mixture1_01.raw
Mixture1_01 128N 0.5 Mixture1 1 161117_SILAC_HeLa_UPS1_TMT10_Mixture1_01.raw
Mixture1_01 128C 1 Mixture1 1 161117_SILAC_HeLa_UPS1_TMT10_Mixture1_01.raw
Mixture1_01 129N 0.125 Mixture1 1 161117_SILAC_HeLa_UPS1_TMT10_Mixture1_01.raw

3.3 Create the QFeatures object

We use readQFeatures() to create a QFeatures object. Since we start from the PSM-level data, the approach is somewhat more elaborate. You can find an illustrated step-by-step guide in the QFeatures vignette. First, recall that every quantitative column in the PSM table contains information for multiple runs. Therefore, the function split the table based on the run identifier, given by the runCol argument (for Skyline, that identifier is contained in Spectrum.File). So, the QFeatures object after import will contain as many sets as there are runs. Next, the function links the annotation table with the PSM data. To achieve this, the annotation table must contain a runCol column that provides the run identifier in which each sample has been acquired, and this information will be used to match the identifiers in the Spectrum.File column of the PSM table. The annotation table must also contain a quantCols column that tells the function which column in the PSM table contains the quantitative information for a given sample. In this case, the quantCols depend on

coldata$runCol <- coldata$File.Name
coldata$quantCols <- paste0("Abundance..", coldata$Label)
(spikein <- readQFeatures(
  psms, colData = coldata,
  runCol = "Spectrum.File",
  quantCols = qcols
))
## An instance of class QFeatures (type: bulk) with 15 sets:
## 
##  [1] 161117_SILAC_HeLa_UPS1_TMT10_Mixture1_01.raw: SummarizedExperiment with 1905 rows and 10 columns 
##  [2] 161117_SILAC_HeLa_UPS1_TMT10_Mixture1_02.raw: SummarizedExperiment with 1902 rows and 10 columns 
##  [3] 161117_SILAC_HeLa_UPS1_TMT10_Mixture1_03.raw: SummarizedExperiment with 1952 rows and 10 columns 
##  ...
##  [13] 161117_SILAC_HeLa_UPS1_TMT10_Mixture5_01.raw: SummarizedExperiment with 1919 rows and 10 columns 
##  [14] 161117_SILAC_HeLa_UPS1_TMT10_Mixture5_02.raw: SummarizedExperiment with 1909 rows and 10 columns 
##  [15] 161117_SILAC_HeLa_UPS1_TMT10_Mixture5_03.raw: SummarizedExperiment with 1844 rows and 10 columns

We now have a QFeatures object with 15 sets, each containing data associated with an MS run. The name of each set is defined by the name of the corresponding file name of the run, which is unnecessarily long. We simplify the set names, although this step is optional and only meant to improve the clarity of the output.

## This is optional
names(spikein) <- sub("^.*(Mix.*).raw", "\\1", names(spikein))
(inputNames <- names(spikein))
##  [1] "Mixture1_01" "Mixture1_02" "Mixture1_03" "Mixture2_01" "Mixture2_02"
##  [6] "Mixture2_03" "Mixture3_01" "Mixture3_02" "Mixture3_03" "Mixture4_01"
## [11] "Mixture4_02" "Mixture4_03" "Mixture5_01" "Mixture5_02" "Mixture5_03"

4 Data preprocessing

We will follow a similar data processing workflow as before.

4.1 Encoding missing values

Any zero value needs to be encoded by a missing value.

spikein <- zeroIsNA(spikein, inputNames)

4.2 Log2 transformation

Log2-transformation solves the heteroskedasticity issue, but also provides a scale that directly relates to biological interpretation.

logNames  <- paste0(inputNames, "_log")
spikein <- logTransform(
    spikein, inputNames, name = logNames, base = 2
)

4.3 Sample filtering

We remove the reference samples since msqrob2 workflows do not use reference normalisation.

spikein <- subsetByColData(spikein, spikein$Condition != "Norm")

4.4 PSM filtering

Filtering removes low-quality and unreliable PSMs that would otherwise introduce noise and artefacts in the data. Conceptually, PSM filtering is identical to peptide filtering, but we will here apply filtering criteria for which some are not readily available in the data. Therefore, we will add custom filtering variable to the rowData that will then be used with filterFeatures(). This provides an ideal use case to demonstrate the customisation of a filtering workflow.

4.4.1 Remove ambiguous identifications

The background proteins originate from HeLa cells, which also contain UPS proteins. The background UPS proteins and the spiked-in UPS proteins differ in metabolic labelling, so we should be able to distinguish them. We used the PSM-level data searched with mascot, as provided by the MSstatsTMT authors who used two mascot identification nodes. In one node they searched the SwissProt database for proteins with static modifications related to the metabolic labelling, in the other node they searched the Sigma_UPS protein database without these static modifications. Ideally, this should separate the spiked-in UPS proteins and the UPS proteins from the HeLa cells, however, this is not always the case. The SwissProt search is expected to return peptide-spectrum matches (PSMs) for all proteins, including non-UPS HeLa, UPS HeLa, and spike-in UPS proteins. Conversely, the Sigma_UPS search is expected to return PSMs exclusively for spike-in UPS proteins. However, a PSM that matches a UPS protein in the SwissProt search but is not identified as such in the Sigma_UPS search could either correctly originate from a HeLa protein or represent a spiked-in UPS protein that was not recognised as such in the Sigma_UPS search. Additionally, there are ambiguous PSMs that are not matched to a UPS protein in the HeLa search but are matched to a UPS protein in the SwissProt search. To address this, we exclude these ambiguous proteins from the analysis.

To define the amibiguous PSMs, we retrieve the PSM annotations from the rowData and create a new colum indicating whether a PSM belongs to a UPS protein or not, based on the protein SwissProt identifiers. For this, we apply a custom filtering worklow:

  1. Collect data: combine all the rowData information in a single table. We will apply the filter on the
rowdata <- rbindRowData(spikein, logNames)
  1. Compute new variable: (2a) define whether the PSM’s protein group is a UPS protein and then (2b) define an ambiguous PSM as a PSM that is marked as UPS by the SwissProt identifier but not by the Sigma_UPS node (Marked.as column), and inversely.
## 2a.
rowdata$isUps <- "no"
isUpsProtein <- grepl("ups", rowdata$Protein.Accessions)
rowdata$isUps[isUpsProtein] <- "yes"
## 2b.
rowdata$isUps[!isUpsProtein & grepl("UPS", rowdata$Marked.as)] <- "amb"
rowdata$isUps[isUpsProtein & !grepl("UPS", rowdata$Marked.as)] <- "amb"
  1. Reinsert in the rowData: insert the modified table with new information back in the rowData of the different sets. This means that the single table with rowData information needs to be split by each set. split() will produce a named list of tables and each table will be iteratively inserted as rowData of the set.
rowData(spikein) <- split(rowdata, rowdata$assay)
  1. Apply the filter: the filtering is performed by filterFeatures() using the new information from the rowData. We specify keep = TRUE because the input sets (before log-transformation) do not contain the filtering variable, so we tell the function to keep all PSMs for the sets that don’t have the variable isUps.
spikein <- filterFeatures(spikein, ~ isUps != "amb", keep = TRUE)

4.4.2 Remove failed protein inference

Next, we remove PSMs that could not be mapped to a protein or that map to multiple proteins, i.e. a protein group. For the latter, the protein identifier contains multiple identifiers separated by a ;). This information is readily available in the rowData, so there is no need for a custom filtering.

spikein <- filterFeatures(
    spikein, ~ Protein.Accessions != "" & ## Remove failed protein inference
        !grepl(";", Protein.Accessions)) ## Remove protein groups

4.4.3 Remove inconsistent protein inference

We also remove peptide ions that map to a different protein depending on the run. Again, this requires a custom filtering and we apply the same filtering workflow as above.

## 1. Collect data
rowdata <- rbindRowData(spikein, logNames)
## 2. Compute new variable
rowdata <- data.frame(rowdata) |>
    group_by(Annotated.Sequence, Charge) |>
    mutate(nProtsMapped = length(unique(Protein.Accessions)))
## 3. Reinsert in the rowData
rowData(spikein) <- split(rowdata, rowdata$assay)
## 4. Apply the filter
spikein <- filterFeatures(spikein, ~ nProtsMapped == 1, keep = TRUE)

4.4.4 Remove one-run wonders

We also remove proteins that can only be found in one run as such proteins may not be trustworthy. In this case,

## 1. Collect data
rowdata <- rbindRowData(spikein, logNames)
## 2. Compute new variable
rowdata <- data.frame(rowdata) |>
    group_by(Protein.Accessions) |>
    mutate(nRuns = length(unique(assay)))
## 3. Reinsert in the rowData
rowData(spikein) <- split(rowdata, rowdata$assay)
## 4. Apply the filter
spikein <- filterFeatures(spikein, ~ nRuns > 1, keep = TRUE)

4.4.5 Remove duplicated PSMs

Finally, peptide ions that were identified with multiple PSMs in a run are collapsed to the PSM with the highest summed intensity over the TMT labels, a strategy that is also used by MSstats.

This filtering requires a more complex workflow because it mixes information from the rowData (to obtain ion identities) with quantitative data (to obtain PSM intensity ranks). We therefore compute the filtering variable for every set iteratively:

  1. Get the rowData for the current set.
  2. Make a new variable ionID.
  3. We calculate the rowSums for each ion.
  4. Make a new variable psmRank that ranks the PSMs for each ion identifier based on the summed intensity.
  5. We store the new information back in the rowData.
for (i in logNames) { ## for each set of interest
    rowdata <- rowData(spikein[[i]]) ## 1.
    rowdata$ionID <- paste0(rowdata$Annotated.Sequence, rowdata$Charge) ## 2.
    rowdata$rowSums <- rowSums(assay(spikein[[i]]), na.rm = TRUE) ## 3.
    rowdata <- data.frame(rowdata) |>
        group_by(ionID) |>
        mutate(psmRank = rank(-rowSums)) ## 4.
    rowData(spikein[[i]]) <- DataFrame(rowdata) ## 5.
}

For each ion that maps to multiple PSMs, we keep the PSM with the highest summed intensity, that is that ranks first.

spikein <- filterFeatures(spikein, ~ psmRank == 1, keep = TRUE)

4.4.6 Remove highly missing PSMs

We then remove PSMs with five or more missing values out of the ten TMT labels (>= 50%). This is an arbitrary value that may need to be adjusted depending on the experiment and the data set.

spikein <- filterNA(spikein, logNames, pNA = 0.5)

4.5 Normalisation

We normalise the data by median centering.

normNames  <- paste0(inputNames, "_norm")
spikein <- normalize(
    spikein, logNames, name = normNames,
    method = "center.median"
)

And we confirm that the normalisation resulted in a correct alignment of the intensity distribution across samples.

spikein[, , normNames] |>
    longForm(colvars = c("Mixture", "TechRepMixture")) |>
    data.frame() |>
    ggplot() +
    aes(x = value, colour = as.factor(TechRepMixture), group = colname) +
    geom_density() +
    labs(title = "Intensity distribution for each observational unit",
         subtitle = "Before normalisation",
         colour = "Technical replicate") +
    facet_grid(Mixture ~ .) +
    theme(legend.position = "bottom")

4.6 Summarisation

Below, we illustrate the challenges of summarising TMT data using one of the UPS proteins in Mixture 1 (separating the data for each technical replicate). We also focus on the 0.125x and the 1x spike-in conditions. We illustrate the different peptide ions on the x axis and plot the log2 normalised intensities across samples on y axis. All the points belonging to the same sample are linked through a grey line.

We see that the same challenges observed for LFQ data also apply to TMT data. Briefly:

  1. Data for a protein can consist of many peptide ions.
  2. Peptide ions have different intensity baselines.
  3. There is strong missingness across runs (compare points between replicates), but the missingness is mitigated within runs (compare points within replicates. Note that the data points from one peptide ion in one replicate has been extracted from a single MS2 spectrum.).
  4. Subtle intensity shifts for the same peptide across different replicates, called spectrum effects, are caused by small run-to-run fluctuations.
  5. Presence of outliers. For instance, the first peptide ion doesn’t show the same change in intensity between conditions compared to majority of the peptides.

Here, we summarise the ion-level data into protein intensities through the median polish approach, which alternately removes the peptide-ions and the sample medians from the data until the summaries stabilise. Removing the peptide-ion medians will solve issue 2. as it removes the ion-specific effects. Using the median instead of the mean will solve issue 5. Note that we perform summarisation for each run separately, hence the ion effect will be different for each run, effectively allowing for a spectrum effect and solving issue 4.

summNames <- paste0(inputNames, "_proteins")
(spikein <- aggregateFeatures(
    spikein, i = normNames,  name = summNames,
    fcol = "Protein.Accessions", fun = MsCoreUtils::medianPolish,
    na.rm = TRUE
))
## An instance of class QFeatures (type: bulk) with 60 sets:
## 
##  [1] Mixture1_01: SummarizedExperiment with 1719 rows and 8 columns 
##  [2] Mixture1_02: SummarizedExperiment with 1722 rows and 8 columns 
##  [3] Mixture1_03: SummarizedExperiment with 1776 rows and 8 columns 
##  ...
##  [58] Mixture5_01_proteins: SummarizedExperiment with 307 rows and 8 columns 
##  [59] Mixture5_02_proteins: SummarizedExperiment with 296 rows and 8 columns 
##  [60] Mixture5_03_proteins: SummarizedExperiment with 299 rows and 8 columns

Up to now, the data from different runs were kept in separate assays. We can now join the normalised sets into an proteins set using joinAssays(). Sets are joined by stacking the columns (samples) in a matrix and rows (features) are matched according to the row names (i.e. the protein identifiers).

spikein <- joinAssays( 
    spikein, summNames, "proteins"
)

5 Data exploration

We perform data exploration using MDS.

library("scater")
se <- getWithColData(spikein, "proteins")
se <- runMDS(as(se, "SingleCellExperiment"), exprs_values = 1)
plotMDS(se, colour_by = "Condition") +
  plotMDS(se, colour_by = "Run") + 
  plotMDS(se, colour_by = "Mixture")

There is a strong run-to-run effect, which is partly explained by a mixture effect as the runs from the same mixture tend to be closer than runs from different mixtures. The condition effect is much more subtle to find, probably because we know only a few UPS proteins were spiked in while the majority of the background proteins are unchanged. We can see that normalisation and summarisation alone are not sufficient to correct for these unwanted effects. We will take care of these effects during the data modelling.

6 Data modelling

Proteomics data contain several sources of variation that need to be accounted for by the model. We will build the model by progressively adding the different sources of variation.

6.1 Effect of treatment of interest

We model the source of variation induced by the experimental treatment of interest as a fixed effect, which we consider non-random, i.e. the treatment effect is assumed to be the same in repeated experiments, but it is unknown and has to be estimated. When modelling a typical label-free experiment at the protein level, the model boils down to a linear model, again we suppress the index for protein:

\[ y_r = \mathbf{x}^T_r \boldsymbol{\beta} + \epsilon_r, \]

with \(y_r\) the \(\log_2\)-normalized protein intensities in run r; \(\mathbf{x}_r\) a vector with the covariate pattern for the sample in run \(r\) encoding the intercept, treatment, potential batch effects and confounders; \(\boldsymbol{\beta}\) the vector of parameters that model the association between the covariates and the outcome; and \(\epsilon_r\) the residuals reflecting variation that is not captured by the fixed effects. Note that \(\mathbf{x}_r\) allows for a flexible parameterization of the treatment beyond a single covariate, i.e. including a 1 for the intercept, continuous and categorical variables as well as their interactions. For all models considered in this work, we assume the residuals to be independent and identically distributed (i.i.d) according to a normal distribution with zero mean and constant variance, i.e. \(\epsilon_{r} \sim N(0,\sigma_\epsilon^2)\), that can differ from protein to protein.

Now we defined a model, we must estimate from the data. Using msqrob(), the model translates into the following code:

spikein <- msqrob(
    spikein,  i = "proteins",
    formula = ~  Condition ## fixed effect for experimental condition
)

This model is however incomplete and relying on its results would lead to incorrect conclusions as we are still missing important source of variation. We did therefore not run the modelling command and will expand the model.

6.2 Effect of TMT label and run

As label-free experiments contain only a single sample per run, run-specific effects will be absorbed in the residuals. However, the data analysis of labeled experiments, e.g. using TMT multiplexing, involving multiple MS runs has to account for run- and label-specific effects, explicitly. If all treatments are present in each run, and if TMT label swaps are performed so as to avoid confounding between label and treatment, then the model parameters can be estimated using fixed label and run effects. Indeed, for these designs run acts as a blocking variable as all treatment effects can be estimated within each run.

However, for more complex designs this is no longer possible and the uncertainty in the estimation of the mean model parameters can involve both within and between TMT label and run variability. For these designs we can resort to mixed models where the label and run effect are modelled using random effects, i.e. they are considered as a random sample from the population of all possible runs and TMT labels, which are assumed to be i.i.d normally distributed with mean 0 and constant variance, \(u_r \sim N(0,\sigma^{2,\text{run}})\) (\(u_{label} \sim N(0,\sigma^{2,\text{label}})\)). The use of random effects thus models the correlation in the data, explicitly. Indeed, protein intensities that are measured within the same run/label will be more similar than protein intensities between runs/labels.

Hence, the model is extended to:

\[ y_{rl} = \mathbf{x}^T_{rl} \boldsymbol{\beta} + u_l^\text{label} + u_r^\text{run} + \epsilon_{rl} \] with \(y_{rl}\) the normalised \(\log_2\) protein intensities in run \(r\) and label \(l\), \(u_l^\text{label}\) the effect introduced by the label \(l\), and \(u_r^\text{run}\) the effect for MS run \(r\).

This translates in the following code:

spikein <- msqrob(
    spikein,  i = "proteins",
    formula = ~  Condition + ## fixed effect for experimental condition
        (1 | Label) + ## random effect for label
        (1 | Run) ## random effect for MS run
    )

This model is still incomplete and is no executed as we still need to account that each mixture has been replicated three times.

6.3 Effect of replication

Some experiments also include technical replication where a TMT mixture can be acquired multiple times. This again will induce correlation. Indeed, protein intensities from the same mixture will be more alike than those of different mixtures. Hence, we also include a random effect to account for this pseudo-replication, i.e. \(u^\text{mix}_m \sim N(0, \sigma^{2,\text{mix}})\). The model thus extends to:

\[ y_{rlm} = \mathbf{x}^T_{rlm} \boldsymbol{\beta} + u_{l}^\text{label} + u_r^\text{run} + u_m^\text{mix} + \epsilon_{rlm} \]

with \(m\) the index for mixture.

The model translates to the following code:

spikein <- msqrob(
    spikein,  i = "proteins",
    formula = ~  Condition + ## fixed effect for experimental condition
        (1 | Label) + ## random effect for label
        (1 | Run) + ## random effect for MS run
        (1 | Mixture) ## random effect for mixture
)

This model provides a sensible representation of the sources of variation in the data.

7 Statistical inference

We can now convert the biological question “does the spike-in condition affect the protein intensities?” into a statistical hypothesis. In other words, we need to define the contrast. We plot an overview of the model parameters.

library("ExploreModelMatrix")
vd <- VisualizeDesign(
    sampleData =  colData(spikein),
    designFormula = ~ Condition,
    textSizeFitted = 4
)
vd$plotlist
## [[1]]

7.1 Hypothesis testing

The average difference intensity between the 1x and the 0.5x conditions is provided by the contrast Condition1 - Condition0.5.

hypothesis <- "Condition1 - Condition0.5 = 0"
L <- makeContrast(
    hypothesis,
    parameterNames = c("Condition1", "Condition0.5")
)

We test our null hypotheses using hypothesisTest() and the estimated model stored in proteins.

spikein <- hypothesisTest(spikein, i = "proteins", contrast = L)
(inference <- rowData(spikein[["proteins"]])[, colnames(L)])

7.2 Volcano plots

We generate a volcano plot with all the results for the 1- 0.5x comparison.

inference$Protein <- rownames(inference)
inference$isUps <- grepl("ups", inference$Protein)
ggplot(inference) +
    aes(x = logFC,
        y = -log10(pval),
        color = isUps) +
    geom_point() +
    geom_hline(yintercept = -log10(inference$pval[which.min(abs(inference$adjPval - 0.05))] )) +
    scale_color_manual(
      values = c("grey20", "firebrick"), name = "",
      labels = c("HeLA background", "UPS standard")
    ) +
    ggtitle("Statistical inference the 1x - 0.5x comparison") 

7.3 Fold change distributions

As this is a spike-in study with known ground truth, we can also plot the log2 fold change distributions against the expected values, in this case 0 for the HeLa proteins and 1 for the UPS standards.

ggplot(inference) +
    aes(y = logFC,
        x = isUps,
        colour = isUps) +
    geom_boxplot() +
    scale_color_manual(
        values = c("grey20", "firebrick"), name = "",
        labels = c("HeLA background", "UPS standard")
    ) +
    ggtitle("Distribution of the log2 fold changes")

The estimated log2 fold changes for HeLa proteins are closely distributed around 0, as expected. log2 fold changes for UPS standard proteins are distributed toward 1, but it has been underestimated due to ion suppression effects that are characteristic of data set with high spike-in concentrations.

8 Exercise: the mouse diet use case

Let’s repeat this analysis, but on a real-life problem, instead of a synthetic spike-in study.

8.1 Experimental context

The data used in this exercise has been published by Plubell et al. (2017) (PXD005953). The objective of the experiment was to explore the impact of low-fat and high-fat diets on the proteomic content of adipose tissue in mice. It also assesses whether the duration of the diet may impact the results. The authors assigned twenty mice into four groups (5 mice per group) based on their diet, either low-fat (LF) or high-fat (HF), and the duration of the diet, which was classified as short (8 weeks) or long (18 weeks). Samples from the epididymal adipose tissue were extracted from each mice. The samples were then randomly distributed across three TMT 10-plex mixtures for analysis. In each mixture, two reference labels were used, each containing pooled samples that included a range of peptides from all the samples. Not all labels were used, leading to an unbalanced design. Each TMT 10-plex mixture was fractionated into nine parts, resulting in a total of 27 MS runs.

Overview of the experimental design. Taken from Plubell et al. 2017.

Overview of the experimental design. Taken from Plubell et al. 2017.

8.2 Data

The data were reanalyzed by Huang et al. (2020) and have been deposited in the MSV000084264 MASSiVE repository, but we will retrieve the timestamped data from our Zenodo repository. We need 2 files: the Skyline identification and quantification table generated by the authors and the sample annotation files.

library("BiocFileCache")
bfc <- BiocFileCache()
psmFile <- bfcrpath(bfc, "https://zenodo.org/records/14767905/files/mouse_psms.txt?download=1")
annotFile <- bfcrpath(bfc, "https://zenodo.org/records/14767905/files/mouse_annotations.csv?download=1")

Let’s prepare the data (same approach as above). We will also subset the data set to reduce computational costs. We here randomly sample 500 proteins from the experiment.

## Reading data
psms <- read.delim(psmFile)
coldata <- read.csv(annotFile)
coldata$Duration <- gsub("_.*", "", coldata$Condition) ## 1.
coldata$Diet <- gsub(".*_", "", coldata$Condition) ## 2.
colnames(coldata)[1] <- "Label" ## 3.
## Subsetting data
proteinIds <- unique(psms$Protein.Accessions)
set.seed(1234)
psms <- psms[psms$Protein.Accessions %in% sample(proteinIds, 500), ]
## Converting data
coldata$runCol <- coldata$Run
coldata$quantCols <- paste0("Abundance..", coldata$Label)
mouse <- readQFeatures(psms, colData = coldata,
                       quantCols = unique(coldata$quantCols),
                       runCol = "Spectrum.File", name = "psms")
names(mouse) <- sub("^.*(Mouse.*ACN).*raw", "\\1", names(mouse))

8.3 Data preprocessing

We use a data processing workflow very similar to above.

## Encode missing values
mouse <- zeroIsNA(mouse, names(mouse))
## Sample filtering
mouse <- subsetByColData(
    mouse, mouse$Condition != "Norm" & mouse$Condition != "Long_M"
)
## PSM filtering
mouse <- filterNA(mouse, names(mouse), pNA = 0.7)
mouse <- filterFeatures(
    mouse, ~ Protein.Accessions != "" & ## Remove failed protein inference
        !grepl(";", Protein.Accessions)) ## Remove protein groups
for (i in names(mouse)) {
    rowdata <- rowData(mouse[[i]])
    rowdata$ionID <- paste0(rowdata$Annotated.Sequence, rowdata$Charge)
    rowdata$rowSums <- rowSums(assay(mouse[[i]]), na.rm = TRUE)
    rowdata <- data.frame(rowdata) |>
        group_by(ionID) |>
        mutate(psmRank = rank(-rowSums))
    rowData(mouse[[i]]) <- DataFrame(rowdata)
}
mouse <- filterFeatures(mouse, ~ psmRank == 1)
## Log transformation
sNames <- names(mouse)
mouse <- logTransform(
    mouse, sNames, name = paste0(sNames, "_log"), base = 2
)
## Normalisation
mouse <- normalize(
    mouse, paste0(sNames, "_log"), name = paste0(sNames, "_norm"),
    method = "center.median"
)
## Protein summarisation
mouse <- aggregateFeatures(
    mouse, i = paste0(sNames, "_norm"), name = paste0(sNames, "_proteins"),
    fcol = "Protein.Accessions", fun = MsCoreUtils::medianPolish,
    na.rm=TRUE
)
## Join sets
mouse <- joinAssays(mouse, paste0(sNames, "_proteins"), "proteins")

8.4 Problem statement

Starting from the processed proteins set, build a model accounting for all sources of variation in the experiments (use the figure shown in the experimental context to guide your decisions), and allowing you to answer the following questions:

  • What is the difference in protein abundance between low fat and high fat diet after short duration?
  • What is the difference in protein abundance between low fat and high fat diet after long duration?
  • What is the average difference in protein abundance between low fat and high fat diet?
  • Does the diet effect change according to duration? (hint: is there an interaction between diet and duration?)

Note that you should use Diet and Duration as the experimental variables of interest, and you can explore the available variables for modelling using colData(mouse). Recall that you can also explore your data using MDS and colour for the potential sources of variation.

Estimate your model and perform statistical inference using msqrob2. Each question will require you to test a different contrast. Don’t forget that the VisualizeDesign() function can help you with identify the correct combination of parameters to define your contrast.

The report the results using a volcano plot and a differential analysis table to answer the questions.

8.5 Solution

Click to see the solution

Let’s explore the data.

library("scater")
se <- getWithColData(mouse, "proteins") |> 
  as("SingleCellExperiment") |> 
  runMDS(exprs_values = 1)

We plot the MDS and colour each sample based on different potential sources of variation.

plotMDS(se, colour_by = "Run") + ggtitle("Coloured by Run") +
  scale_colour_manual(values = rainbow(27)) +
  plotMDS(se, colour_by = "Fraction") + ggtitle("Coloured by Fraction") +
  plotMDS(se, colour_by = "BioReplicate") + ggtitle("Coloured by BioReplicate") +
  plotMDS(se, colour_by = "Mixture") + ggtitle("Coloured by Mixture") +
  plotMDS(se, colour_by = "Diet") + ggtitle("Coloured by Diet") +
  plotMDS(se, colour_by = "Duration") + ggtitle("Coloured by Duration") &
  theme(legend.position = "none") 

The data exploration leads to several observations:

  • The strongest source of variation is associated with the MS acquisition run.
  • Part of this run effect is influenced by which fraction it contains since samples from the same fraction tend to be closer than samples from different fractions.
  • It is difficult to identify an effect of mouse (biological replicate) because every run contains distinct mice. However, this does not exclude an effect of mice which has been identified as a potential source of variation and hence should still be modelled.
  • There is potentially also an effect of TMT mixture since samples from the same mixtures tend to cluster together (in the center of the plot). However, this effect are more subtle to detect and difficult to disentangle from the run and fraction effects.
  • Although again very subtle, we can see within each run that samples from the mice with the same diet tends to group together. However, these effects are overwhelmed by the run effects. An effect of duration is to subtle to pinpoint from the current data exploration.

Data modelling disentangles the different sources of variation, given they are properly defined in the model.

Proteomics data contain several sources of variation that need to be accounted for by the model:

  1. Treatment of interest: we model the source of variation induced by the experimental treatment of interest as a fixed effect. Fixed effects are effect that are considered non-random, i.e. the treatment effect is assumed to be the same and reproducible across repeated experiments, but it is unknown and has to be estimated. We will include Diet as a fixed effect that models the fact that a change in diet type can induce changes in protein abundance. Similarly, we also include Duration as a fixed effect to model the change in protein abundance induces by the diet duration. Finally, we will also include an interaction between the two variables allowing that the changes in protein abundance induced by diet type can be different whether the mice were fed for a short or long duration.

  2. Biological replicate effect: the experiment involves biological replication as the adipose tissue extracts were sampled from 20 mice (5 mice per Diet x Duration combination). Replicate-specific effects occurs due to uncontrollable factors, such as social behavior, feeding behavior, physical activity, occasional injury,… Two mice will never provide exactly the same sample material, even were they genetically identical and manipulated identically. These effects are typically modelled as random effects which are considered as a random sample from the population of all possible mice and are assumed to be i.i.d normally distributed with mean 0 and constant variance, \(u_{mouse} \sim N(0,\sigma^{2,\text{mouse}})\). The use of random effects thus models the correlation in the data, explicitly. We expect that intensities from the same mouse are more alike than intensities between mice.

length(unique(mouse$BioReplicate))
## [1] 20
  1. Labelling effects: the 20 mouse adipose tissue samples have been labelled using 18-plex TMT. We can expect that samples measured within the same TMT label may be more similar than samples measured within different TMT labels. Since these effects may not be reproducible from one experiment to another, for instance because each TMT kit may potentially contain different impurity ratios, we can account for this correlation using a random effect for TMT label.
length(unique(mouse$Label))
## [1] 10
  1. Mixture effects: the 20 mouse samples were assigned to one out of 3 mixtures. Again, we expect protein intensities from the same mixture will be more alike than those of different mixtures. Hence, we will add a random effect for mixture.
table(mouse$Mixture)
## 
##  PAMI-176_Mouse_A-J  PAMI-176_Mouse_K-T PAMI-194_Mouse_U-Dd 
##                  54                  63                  63
  1. Run effects: protein intensities that are measured within the same run will be more similar than protein intensities between runs. We will use a random effect for run to explicitly model this correlation in the data. Note that each sample has been acquired in 9 fractions, each fraction being measured in a separate run. Accounting for the effects of run will also absorb the effects of fraction.
length(unique(mouse$Run))
## [1] 27

We will model the main effects for Diet and Duration, and a Diet:Duration interaction, to account for proteins for which the Diet effect changes according to Duration, and vice versa, which can be written as Diet + Duration + Diet:Duration, shortened into Diet * Duration (recall the heart example in a previous course). Adding the technical sources of variation, the model becomes.

model <- ~ Diet * Duration + ## (1) fixed effect for Diet and Duration with interaction
  (1 | BioReplicate) +  ## (2) random effect for biological replicate (mouse)      
  (1 | Label) + ## (3) random effect for label
  (1 | Mixture) + ## (4) random effect for mixture
  (1 | Run) ## (5) random effect for MS run

We estimate the model with msqrob().

mouse <- msqrob(mouse, i = "proteins", formula = model)

Difference between low fat and high fat diet after short duration

We need to convert this question in a combination of the model parameters. We guide the contrast definition using the ExploreModelMatrix package. Since we are not interested in technical effects, we will only focus on the variables of interest, here Diet * Duration.

library("ExploreModelMatrix")
vd <- VisualizeDesign(
    sampleData =  colData(mouse),
    designFormula = ~ Diet * Duration,
    textSizeFitted = 4
)
vd$plotlist[[1]]

Assessing the difference between low-fat and high-fat diets for short duration boils down to assessing the difference between the Short_LF and Short_HF. The mean for the short low-fat diet group is defined by (Intercept) + DietLF + DurationShort + DietLF:DurationShort. The mean for the short high-fat diet group is defined by (Intercept) + DurationShort. The difference between the two results in the contrast below:

(L <- makeContrast(
    "DietLF + DietLF:DurationShort = 0",
    parameterNames = c("DietLF", "DietLF:DurationShort")
))
##                      DietLF + DietLF:DurationShort
## DietLF                                           1
## DietLF:DurationShort                             1
mouse <- hypothesisTest(mouse, i = "proteins", L)

Let us retrieve the result table from the rowData. Note that the model column is named after the column names of the contrast matrix L.

inference <- rowData(mouse[["proteins"]])[[colnames(L)]]
head(inference, 10)

The table contains the hypothesis testing results for every protein.

We can use the table above directly to build a volcano plot using ggplot2 functionality.

ggplot(inference) +
    aes(x = logFC, y = -log10(pval)) +
    geom_hline(yintercept = -log10(inference$pval[which.min(abs(inference$adjPval - 0.05))] )) +
    geom_point() +
    ggtitle("Statistical inference on differences between LF and HF (short duration)",
            paste("Hypothesis test:", colnames(L), "= 0"))

In this example (remember this is a subset of the complete data set), only a few proteins pass the significance threshold of 5% FDR.

Difference between low fat and high fat diet after long duration

Following the same approach as above, the hypothesis test becomes:

L <- makeContrast("DietLF = 0", parameterNames = "DietLF")
mouse <- hypothesisTest(mouse, i = "proteins", L)
inference <- rowData(mouse[["proteins"]])[[colnames(L)]]
head(inference)

And we plot the results.

ggplot(inference) +
    aes(x = logFC, y = -log10(pval)) +
    geom_hline(yintercept = -log10(inference$pval[which.min(abs(inference$adjPval - 0.05))] )) +
    geom_point() +
    ggtitle("Statistical inference on differences between LF and HF (long duration)",
            paste("Hypothesis test:", colnames(L), "= 0"))

Again, only a few proteins come out differentially abundant between the two diets, but after a long diet duration.

Average difference between low fat and high fat diet

One may want to identify the set of proteins that are systematically differentially abundant between diets, irrespective of the duration. To answer this question, we want to infer on the average difference between group LF and group HF. The average low-fat diet is defined by ((Intercept) + DietLF + DurationShort + DietLF:DurationShort + (Intercept) + DietLF)/2. The average high-fat diet group is defined by ((Intercept) + DurationShort + (Intercept))/2. The difference between the two results in the hypothesis test below:

L <- makeContrast(
    "DietLF + (DietLF:DurationShort)/2 = 0",
    parameterNames = c("DietLF", "DietLF:DurationShort")
)
mouse <- hypothesisTest(mouse, i = "proteins", L)
(inference <- rowData(mouse[["proteins"]])[[colnames(L)]])

And we plot the results.

ggplot(inference) +
    aes(x = logFC, y = -log10(pval)) +
    geom_hline(yintercept = -log10(inference$pval[which.min(abs(inference$adjPval - 0.05))] )) +
    geom_point() +
    ggtitle("Statistical inference on average difference between LF and HF",
            paste("Hypothesis test:", colnames(L), "= 0"))

Interaction: does the diet effect change according to duration?

We will now explore whether the effect of diet on protein abundance may be affected by duration, i.e. we want to infer on the difference of differences. The difference between hypothesis 1 and 2 is (DietLF + DietLF:DurationShort) - (DietLF) and results in the hypothesis below:

We can proceed with the same statistical pipeline.

L <- makeContrast(
    "DietLF:DurationShort = 0",
    parameterNames = "DietLF:DurationShort"
)
mouse <- hypothesisTest(mouse, i = "proteins", L)
(inference <- rowData(mouse[["proteins"]])[[colnames(L)]])

And we plot the results.

ggplot(inference) +
    aes(x = logFC, y = -log10(pval)) +
    geom_hline(yintercept = -log10(inference$pval[which.min(abs(inference$adjPval - 0.05))] )) +
    geom_point() +
    ggtitle("Statistical inference on the effect of duration on the differences between diets",
            paste("Hypothesis test:", colnames(L), "= 0"))

9 Session Info

With respect to reproducibility, it is highly recommended to include a session info in your script so that readers of your output can see your particular setup of R.

sessionInfo()
## R version 4.5.1 (2025-06-13)
## Platform: x86_64-pc-linux-gnu
## Running under: Ubuntu 24.04.3 LTS
## 
## Matrix products: default
## BLAS:   /usr/lib/x86_64-linux-gnu/blas/libblas.so.3.12.0 
## LAPACK: /usr/lib/x86_64-linux-gnu/lapack/liblapack.so.3.12.0  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/Brussels
## tzcode source: system (glibc)
## 
## attached base packages:
## [1] stats4    stats     graphics  grDevices utils     datasets  methods  
## [8] base     
## 
## other attached packages:
##  [1] ExploreModelMatrix_1.21.0   scater_1.37.0              
##  [3] scuttle_1.19.0              SingleCellExperiment_1.31.0
##  [5] BiocFileCache_2.99.0        dbplyr_2.5.0               
##  [7] BiocParallel_1.43.0         patchwork_1.3.0            
##  [9] ggplot2_3.5.2               dplyr_1.1.4                
## [11] msqrob2_1.17.1              QFeatures_1.19.3           
## [13] MultiAssayExperiment_1.35.1 SummarizedExperiment_1.39.0
## [15] Biobase_2.69.0              GenomicRanges_1.61.0       
## [17] GenomeInfoDb_1.45.3         IRanges_2.43.0             
## [19] S4Vectors_0.47.0            BiocGenerics_0.55.0        
## [21] generics_0.1.3              MatrixGenerics_1.21.0      
## [23] matrixStats_1.5.0          
## 
## loaded via a namespace (and not attached):
##   [1] RColorBrewer_1.1-3      rstudioapi_0.17.1       jsonlite_2.0.0         
##   [4] magrittr_2.0.3          ggbeeswarm_0.7.2        farver_2.1.2           
##   [7] nloptr_2.2.1            rmarkdown_2.29          vctrs_0.6.5            
##  [10] memoise_2.0.1           minqa_1.2.8             htmltools_0.5.8.1      
##  [13] S4Arrays_1.9.0          BiocBaseUtils_1.11.0    curl_6.2.2             
##  [16] BiocNeighbors_2.3.0     SparseArray_1.9.0       sass_0.4.10            
##  [19] bslib_0.9.0             htmlwidgets_1.6.4       plyr_1.8.9             
##  [22] httr2_1.1.2             cachem_1.1.0            igraph_2.1.4           
##  [25] mime_0.13               lifecycle_1.0.4         pkgconfig_2.0.3        
##  [28] rsvd_1.0.5              Matrix_1.7-4            R6_2.6.1               
##  [31] fastmap_1.2.0           rbibutils_2.3           shiny_1.10.0           
##  [34] clue_0.3-66             digest_0.6.37           irlba_2.3.5.1          
##  [37] RSQLite_2.3.11          beachmat_2.25.0         filelock_1.0.3         
##  [40] labeling_0.4.3          httr_1.4.7              abind_1.4-8            
##  [43] compiler_4.5.1          bit64_4.6.0-1           withr_3.0.2            
##  [46] viridis_0.6.5           DBI_1.2.3               MASS_7.3-65            
##  [49] rappdirs_0.3.3          DelayedArray_0.35.1     tools_4.5.1            
##  [52] vipor_0.4.7             beeswarm_0.4.0          httpuv_1.6.16          
##  [55] glue_1.8.0              nlme_3.1-168            promises_1.3.2         
##  [58] grid_4.5.1              cluster_2.1.8.1         reshape2_1.4.4         
##  [61] gtable_0.3.6            tidyr_1.3.1             BiocSingular_1.25.0    
##  [64] ScaledMatrix_1.17.0     XVector_0.49.0          ggrepel_0.9.6          
##  [67] pillar_1.10.2           stringr_1.5.1           limma_3.65.0           
##  [70] later_1.4.2             rintrojs_0.3.4          splines_4.5.1          
##  [73] lattice_0.22-7          bit_4.6.0               tidyselect_1.2.1       
##  [76] knitr_1.50              reformulas_0.4.1        gridExtra_2.3          
##  [79] ProtGenerics_1.41.0     shinydashboard_0.7.3    xfun_0.52              
##  [82] statmod_1.5.0           DT_0.33                 stringi_1.8.7          
##  [85] UCSC.utils_1.5.0        lazyeval_0.2.2          yaml_2.3.10            
##  [88] boot_1.3-31             evaluate_1.0.3          codetools_0.2-20       
##  [91] MsCoreUtils_1.21.0      tibble_3.2.1            cli_3.6.5              
##  [94] xtable_1.8-4            Rdpack_2.6.4            jquerylib_0.1.4        
##  [97] Rcpp_1.0.14             png_0.1-8               parallel_4.5.1         
## [100] blob_1.2.4              AnnotationFilter_1.33.0 lme4_1.1-37            
## [103] viridisLite_0.4.2       scales_1.4.0            purrr_1.0.4            
## [106] crayon_1.5.3            rlang_1.1.6             cowplot_1.1.3          
## [109] shinyjs_2.1.0

References

Huang, Ting, Meena Choi, Manuel Tzouros, Sabrina Golling, Nikhil Janak Pandya, Balazs Banfai, Tom Dunkley, and Olga Vitek. 2020. MSstatsTMT: Statistical Detection of Differentially Abundant Proteins in Experiments with Isobaric Labeling and Multiple Mixtures.” Mol. Cell. Proteomics 19 (10): 1706–23.
Plubell, Deanna L, Phillip A Wilmarth, Yuqi Zhao, Alexandra M Fenton, Jessica Minnier, Ashok P Reddy, John Klimek, Xia Yang, Larry L David, and Nathalie Pamir. 2017. “Extended Multiplexing of Tandem Mass Tags (TMT) Labeling Reveals Age and High Fat Diet Specific Proteome Changes in Mouse Epididymal Adipose Tissue.” Mol. Cell. Proteomics 16 (5): 873–90.
LS0tCnRpdGxlOiAiTWl4ZWQgTW9kZWxzIGZvciBsYWJlbGxlZCBwcm90ZW9taWNzOiIKYXV0aG9yOiAiQ2hyaXN0b3BoZSBWYW5kZXJhYSBhbmQgTGlldmVuIENsZW1lbnQiCmRhdGU6ICJzdGF0T21pY3MsIEdoZW50IFVuaXZlcnNpdHkgKGh0dHBzOi8vc3RhdG9taWNzLmdpdGh1Yi5pbykiCm91dHB1dDoKICAgIHBkZl9kb2N1bWVudDoKICAgICAgdG9jOiB0cnVlCiAgICAgIG51bWJlcl9zZWN0aW9uczogdHJ1ZQpsaW5rY29sb3I6IGJsdWUKdXJsY29sb3I6IGJsdWUKY2l0ZWNvbG9yOiBibHVlCgpiaWJsaW9ncmFwaHk6IG1zcXJvYjIuYmliCgotLS0KCmBgYHtyIHNldHVwLCBlY2hvID0gRkFMU0V9CmtuaXRyOjpvcHRzX2NodW5rJHNldCgKICAgIHdhcm5pbmcgPSBGQUxTRSwKICAgIG1lc3NhZ2UgPSBGQUxTRQopCmBgYAoKCjxhIHJlbD0ibGljZW5zZSIKaHJlZj0iaHR0cHM6Ly9jcmVhdGl2ZWNvbW1vbnMub3JnL2xpY2Vuc2VzL2J5LW5jLXNhLzQuMCI+PGltZwphbHQ9IkNyZWF0aXZlIENvbW1vbnMgTGljZW5zZSIgc3R5bGU9ImJvcmRlci13aWR0aDowIgpzcmM9Imh0dHBzOi8vaS5jcmVhdGl2ZWNvbW1vbnMub3JnL2wvYnktbmMtc2EvNC4wLzg4eDMxLnBuZyIgLz48L2E+CgpUaGlzIGlzIHBhcnQgb2YgdGhlIG9ubGluZSBjb3Vyc2UgW1N0YXRpc3RpY2FsIEdlbm9taWNzIEFuYWx5c2lzCihTR0EpXShodHRwczovL3N0YXRvbWljcy5naXRodWIuaW8vU0dBLykKClRoaXMgY2hhcHRlciBidWlsZHMgdXBvbiB0aGUgW2ludHJvZHVjdG9yeQpjb3Vyc2VdKGh0dHBzOi8vc3RhdG9taWNzLmdpdGh1Yi5pby9TR0EvZnJhbmNpc2VsbGFNaXhlZE1vZGVsLmh0bWwpIHRvCm1peGVkIG1vZGVscyBmb3IgcHJvdGVvbWljcyBkYXRhIGFuYWx5c2lzLiBXZSB3aWxsIGhlcmUgY292ZXIgbW9yZQphZHZhbmNlZCBjb25jZXB0cyB1c2luZyBgbXNxcm9iMmAuIFRvIGlsbHVzdHJhdGUgdGhlc2UgYWR2YW5jZWQKY29uY2VwdHMsIHdlIHdpbGwgdXNlIHRoZSBzcGlrZS1pbiBzdHVkeSBwdWJsaXNoZWQgYnkgQEh1YW5nMjAyMC1sdS4KV2UgY2hvc2UgdGhpcyBkYXRhIHNldCBiZWNhdXNlOgoKMS4gU3Bpa2UtaW4gZGF0YSBjb250YWluIGdyb3VuZCB0cnV0aCBpbmZvcm1hdGlvbiBhYm91dCB3aGljaCBwcm90ZWlucyAKICAgYXJlIGRpZmZlcmVudGlhbGx5IGFidW5kYW50LCBlbmFibGluZyB1cyB0byBzaG93IHRoZSBpbXBhY3Qgb2YgCiAgIGRpZmZlcmVudCBhbmFseXNpcyBzdHJhdGVnaWVzLgoyLiBJdCBoYXMgYmVlbiBhY3F1aXJlZCB3aXRoIGEgVE1ULWxhYmVsbGluZyBzdHJhdGVneSB0aGF0IHJlcXVpcmUgYQogICBjb21wbGV4IGV4cGVyaW1lbnRhbCBkZXNpZ24uIFRoaXMgcHJvdmlkZXMgYW4gZXhjZWxsZW50IGV4YW1wbGUKICAgdG8gZXhwbGFpbiB0aGUgZGlmZmVyZW50IHNvdXJjZXMgb2YgdmFyaWFiaWxpdHkgaW4gYW4gTVMgZXhwZXJpbWVudAogICBhbmQgZGVtb25zdHJhdGUgdGhlIGZsZXhpYmlsaXR5IG9mIGBtc3Fyb2IyYCB0byBtb2RlbCB0aGlzCiAgIHZhcmlhYmlsaXR5LgoKIyMgQmFja2dyb3VuZCAKCkxhYmVsbGluZyBzdHJhdGVnaWVzIGluIG1hc3Mgc3BlY3Ryb21ldHJ5IChNUyktYmFzZWQgcHJvdGVvbWljcyAKZW5oYW5jZSBzYW1wbGUgdGhyb3VnaHB1dCBieSBlbmFibGluZyB0aGUgYWNxdWlzaXRpb24gb2YgbXVsdGlwbGUKc2FtcGxlcyB3aXRoaW4gYSBzaW5nbGUgcnVuLiBUaGUgbGFiZWxsaW5nIHN0cmF0ZWd5IHRoYXQgYWxsb3dzIHRoZSAKaGlnaGVzdCBtdWx0aXBsZXhpbmcgaXMgdGhlIHRhbmRlbSBtYXNzIHRhZyAoVE1UKSBsYWJlbGxpbmcgYW5kIHdpbGwKYmUgdGhlIGZvY3VzIG9mIHRoZSBjdXJyZW50IHR1dG9yaWFsLgoKIyMjIFRNVCB3b3JrZmxvdwoKVE1ULWJhc2VkIHdvcmtmbG93IGhpZ2hseSBvdmVybGFwIHdpdGggW2xhYmVsLWZyZWUKd29ya2Zsb3dzXShodHRwczovL3N0YXRvbWljcy5naXRodWIuaW8vU0dBL3BkYV9xdWFudGlmaWNhdGlvbl9wcmVwcm9jZXNzaW5nLmh0bWwjMTFfTVMtYmFzZWRfd29ya2Zsb3cpLgpIb3dldmVyLCBUTVQtYmFzZWQgd29ya2Zsb3dzIGhhdmUgYW4gYWRkaXRpb25hbCBzYW1wbGUgcHJlcGFyYXRpb24Kc3RlcCwgd2hlcmUgdGhlIGRpZ2VzdGVkIHBlcHRpZGVzIGZyb20gZWFjaCBzYW1wbGUgYXJlIGxhYmVsbGVkIHdpdGggYQpUTVQgcmVhZ2VudCBhbmQgc2FtcGxlcyB3aXRoIGRpZmZlcmVudCBUTVQgcmVhZ2VudHMgYXJlIHBvb2xlZCBpbiBhCnNpbmdsZSBUTVQgbWl4dHVyZS4gVGhlIHNpZ25hbCBwcm9jZXNzaW5nIGlzIGFsc28Kc2xpZ2h0bHkgYWZmZWN0ZWQgc2luY2UgdGhlIHF1YW50aWZpY2F0aW9uIG5vIGxvbmdlciBvY2N1cnMgaW4gdGhlIE1TMQpzcGFjZSBidXQgYXQgdGhlIE1TMiBsZXZlbC4gSXQgaXMgaW1wb3J0YW50IHRvIHVuZGVyc3RhbmQgdGhhdCBUTVQKcmVhZ2VudCBhcmUgaXNvYmFyaWMsIG1lYW5pbmcgdGhhdCB0aGUgc2FtZSBwZXB0aWRlIHdpdGggZGlmZmVyZW50IFRNVApsYWJlbHMgd2lsbCBoYXZlIHRoZSBzYW1lIG1hc3MgZm9yIHRoZSBpbnRhY3QgaW9uLCBhcyByZWNvcmRlZCBkdXJpbmcKTVMxLiBIb3dldmVyLCB0aGUgVE1UIGZyYWdtZW50cyB0aGF0IGFyZSByZWxlYXNlZCB1cG9uIGZyYWdtZW50YXRpb24KZHVyaW5nIE1TMiwgYWxzbyBjYWxsZWQgVE1UIHJlcG9ydGVyIGlvbnMsIGhhdmUgbGFiZWwtc3BlY2lmaWMgbWFzc2VzLgpUaGUgVE1UIGZyYWdtZW50cyBoYXZlIGFuIGV4cGVjdGVkIG1hc3MgYW5kIGFyZSBkaXN0cmlidXRlZCBpbiBhCmxvdy1tYXNzIHJhbmdlIG9mIHRoZSBNUzIgc3BhY2UuIFRoZSBpbnRlbnNpdHkgb2YgZWFjaCBUTVQgZnJhZ21lbnQgaXMKZGlyZWN0bHkgcHJvcG9ydGlvbmFsIHRvIHRoZSBwZXB0aWRlIHF1YW50aXR5IGluIHRoZSBvcmlnaW5hbCBzYW1wbGUKYmVmb3JlIHBvb2xpbmcuIFRoZSBUTVQgZnJhZ21lbnQgaW50ZW5zaXRpZXMgbWVhc3VyZWQgZHVyaW5nIE1TMiBhcmUKdXNlZCBhcyBxdWFudGl0YXRpdmUgZGF0YS4gVGhlIGhpZ2hlciBtYXNzIHJhbmdlIGNvbnRhaW5zIHRoZSBwZXB0aWRlCmZyYWdtZW50cyB0aGF0IGNvbXBvc2UgdGhlIHBlcHRpZGUgZmluZ2VycHJpbnQsIHNpbWlsYXJseSB0byBMRlEuIFRoaXMKZGF0YSByYW5nZSBpcyB0aGVyZWZvcmUgdXNlZCBmb3IgcGVwdGlkZSBpZGVudGlmaWNhdGlvbi4KSW50ZXJlc3RpbmdseSwgdGhlIHBlcHRpZGUgZmluZ2VycHJpbnQgb3JpZ2luYXRlcyBmcm9tIHRoZSBzYW1lCnBlcHRpZGUgYWNyb3NzIG11bHRpcGxlIHNhbXBsZXMuIFRoaXMgbGVhZHMgdG8gYSBzaWduYWwgYm9vc3QgZm9yIGxvdwphYnVuZGFudCBwZXB0aWRlcyBhbmQgaGVuY2Ugc2hvdWxkIGltcHJvdmUgZGF0YSBzZW5zaXRpdml0eSBhbmQKY29uc2lzdGVuY3kuCgpgYGB7ciwgZWNobyA9IEZBTFNFLCBvdXQud2lkdGggPSAiNzUlIiwgZmlnLmNhcCA9ICJPdmVydmlldyBvZiBhbiBUTVQtYmFzZWQgcHJvdGVvbWljcyB3b3JrZmxvdy4ifQprbml0cjo6aW5jbHVkZV9ncmFwaGljcygiZmlncy90bXRfd29ya2Zsb3cucG5nIikKYGBgCgojIyMgQ2hhbGxlbmdlcyAKClRoZSBhbmFseXNpcyBvZiBUTVQtYmFzZWQgcHJvdGVvbWljcyBkYXRhIHNoYXJlcyB0aGUgc2FtZSBjaGFsbGVuZ2VzCmFzIHRoZSBkYXRhIGFuYWx5c2lzIGNoYWxsZW5nZXMgZm9yIExGUToKCi0gTVMtYmFzZWQgcHJvdGVvbWljcyBkb2Vzbid0IG1lYXN1cmUgcHJvdGVpbnMgZGlyZWN0bHksIGJ1dCB0aGVpciAKY29uc3RpdHV0aW5nICoqcGVwdGlkZSBpb25zKiouIFRoZSBwcm90ZWluLWxldmVsIGluZm9ybWF0aW9uIG5lZWRzCnRvIGJlIHJlY29uc3RydWN0ZWQgZnJvbSB0aGUgaW9uIGRhdGEuIEluIHRoaXMgdHV0b3JpYWwsIHdlIHdpbGwKc3RhcnQgZnJvbSB0aGUgcGVwdGlkZSBkYXRhLCB3aGljaCBoYXMgYmVlbiBjb25zdHJ1Y3RlZCBmcm9tIHRoZSBpb24KZGF0YSBieSBNYXhRdWFudC4KLSBBbGwgcGVwdGlkZXMgY2Fubm90IGJlIGlvbmlzZWQgd2l0aCB0aGUgc2FtZSBlZmZpY2llbmN5LiBQb29yIAppb25pc2F0aW9uIHdpbGwgbGVhZCB0byByZWR1Y2VkIHNpZ25hbCBhcyBsZXNzIGlvbnMgd2lsbCBoaXQgdGhlCmRldGVjdG9yLCBoZW5jZSBsZWFkaW5nIHRvIGEgaHVnZSB2YXJpYWJpbGl0eSBpbiBpbnRlbnNpdHkgYW1vbmcKZGlmZmVyZW50IHBlcHRpZGUgc3BlY2llcywgZXZlbiB3aGVuIHRoZXkgb3JpZ2luYXRlIGZyb20gdGhlIHNhbWUKcHJvdGVpbi4KLSBUaGUgaWRlbnRpZmljYXRpb24gc3RlcCBpcyBub3QgdHJpdmlhbCBhbmQgcHJvbmUgdG8gCmVycm9ycy4gUFNNIG1pc2lkZW50aWZpY2F0aW9uIGxlYWRzIHRvIHRoZSBhc3NpZ25tZW50IG9mIGEKcXVhbnRpdGF0aXZlIHZhbHVlcyBmcm9tIGFub3RoZXIgcGVwdGlkZSB3aXRoIGxpa2VseSBhbm90aGVyCmlvbmlzYXRpb24gZWZmaWNpZW5jeSBhbmQgcmVsYXRpdmUgYWJ1bmRhbmNlLiBIZW5jZSB0aGlzIG1pc2Fzc2lnbmVkCnZhbHVlIHdpbGwgYmVjb21lIGFuIG91dGxpZXIuCi0gTW9yZW92ZXIsIHRoZSBpb24gc2VsZWN0aW9uIGZvciBNUzIgZGVwZW5kcyBvbiBpdHMKaW50ZW5zaXR5IChyZWNhbGwgdGhhdCBvbmx5IHRoZSB0b3AgbW9zdCBpbnRlbnNlIGlvbiBwZWFrcyBhcmUgc2VuZApmb3IgTVMyKS4gVGhlcmVmb3JlLCB0aGUgY2hhbmNlIHRvIG1lYXN1cmUgYW5kLCBzdWJzZXF1ZW50bHksIAppZGVudGlmeSBhIHBlcHRpZGUgd2lsbCBkZXBlbmQgb24gaXRzIGFidW5kYW5jZS4gTm9uIGlkZW50aWZpZWQKcGVwdGlkZXMgd2lsbCBsZWFkIHRvIGRhdGEgbWlzc2luZ25lc3MsIHdoaWNoIGlzIHJlbGF0ZWQgdG8gdGhlCnVuZGVybHlpbmcgcXVhbnRpZmljYXRpb24gdmFsdWUuIFRoaXMgcGhlbm9tZW5vbiBpcyBrbm93biBhcwptaXNzaW5nbmVzcyBub3QgYXQgcmFuZG9tLiBOZXh0IHRvIHRoYXQsIG1hbnkgcmVhc29ucyBjYW4gbGVhZCB0bwppb25zIG5vdCBiZWluZyBzZWxlY3RlZCBvciBpZGVudGlmaWVkIGlycmVzcGVjdGl2ZSBvZiB0aGVpciAKcXVhbnRpZmljYXRpb24gdmFsdWUgbGVhZGluZyB0byBtaXNzaW5nbmVzcyB0aGF0IGlzIG5vdCByZWxhdGVkIHRvCml0cyBxdWFudGl0YXRpdmUgdmFsdWUuIFRoaXMgaXMgcmVmZXJyZWQgdG8gYXMgbWlzc2luZ25lc3MgCmNvbXBsZXRlbHkgYXQgcmFuZG9tLiBUaGUgbWlzc2luZ25lc3MgaXNzdWUgaXMgbm90IG5lZ2xpZ2libGU6IG9ubHkKNDFcJSBvZiBhbGwgcHJvdGVpbnMgYXJlIHF1YW50aWZpZWQgYWNyb3NzIGFsbCBzYW1wbGVzLCBhbmQgdGhlIApudW1iZXIgZHJvcHMgdG8gNi42XCUgd2hlbiBjb25zaWRlcmluZyBwZXB0aWRlcy4KLSBUaGUgaWRlbnRpZmljYXRpb24gaXNzdWVzIGxlYWQgdG8gdW5iYWxhbmNlZCBwZXB0aWRlIG1pc3NpbmduZXNzCmFjcm9zcyBzYW1wbGVzLCBhbmQgdGhlIHBhdHRlcm5zIG9mIG1pc3NpbmcgdmFsdWVzIGFyZSBwb3RlbnRpYWxseQpkaWZmZXJlbnQgZm9yIGV2ZXJ5IHBlcHRpZGUsIGhpZ2hsaWdodGluZyB0aGUgbmVlZCBmb3IgYW4KYXV0b21hdGlzZWQgc29sdXRpb24gdGhhdCBpcyByb2J1c3QgYWdhaW5zdCBtaXNzaW5nIHZhbHVlcy4KLSBUZWNobmljYWwgdmFyaWF0aW9ucyBkdXJpbmcgdGhlIGV4cGVyaW1lbnQgY2FuIGxlYWQgdG8gc3lzdGVtYXRpYyAKZmx1Y3R1YXRpb25zIGFjcm9zcyBzYW1wbGVzLiBUaGUgbW9zdCBvYnZpb3VzIHJlYXNvbiBpcyB3aGVuCmRpZmZlcmVudCBzYW1wbGUgYW1vdW50cyBhcmUgaW5qZWN0ZWQgaW50byB0aGUgaW5zdHJ1bWVudHMsIGR1ZSB0bwpzbWFsbCBwaXBldHRpbmcgaW5jb25zaXN0ZW5jaWVzIGZvciBpbnN0YW5jZS4gSG93ZXZlciwgdGhlc2UKZGlmZmVyZW5jZXMgbGVhZCB0byB1bndhbnRlZCB2YXJpYXRpb24gdGhhdCBzaG91bGQgYmUgZGlzY2FyZGVkIHdoZW4KYW5zd2VyaW5nIGJpb2xvZ2ljYWwgcXVlc3Rpb25zLgotIFRNVCB3b3JrZmxvd3MgaW1wb3NlIGFuIGFkZGl0aW9uYWwgY2hhbGxlbmdlLiBDb250ZW1wb3JhcnkKICBleHBlcmltZW50cyBvZnRlbiBpbnZvbHZlIGluY3JlYXNpbmdseSBjb21wbGV4IGRlc2lnbnMsIHdoZXJlIHRoZQogIG51bWJlciBvZiBzYW1wbGVzIGV4Y2VlZHMgdGhlIGNhcGFjaXR5IG9mIGEgc2luZ2xlIFRNVCBtaXh0dXJlLAogIHJlc3VsdGluZyBpbiBhIGNvbXBsZXggY29ycmVsYXRpb24gc3RydWN0dXJlIHRoYXQgbXVzdCBiZSBhZGRyZXNzZWQKICBmb3IgYWNjdXJhdGUgc3RhdGlzdGljYWwgaW5mZXJlbmNlLiBXZSB3aWxsIGRlc2NyaWJlIGluIHRoZQogIG1vZGVsbGluZyBzZWN0aW9uIHRoZSBkaWZmZXJlbnQgc291cmNlcyBvZiB2YXJpYXRpb24uCgojIyMgRXhwZXJpbWVudGFsIGNvbnRleHQKClRoZSBkYXRhIHNldCB1c2VkIGluIHRoaXMgY2hhcHRlciBpcyBhIHNwaWtlLWluIGV4cGVyaW1lbnQKKFBYRDAwMTUyNTgpIHB1Ymxpc2hlZCBieSBASHVhbmcyMDIwLWx1LiBJdCBjb25zaXN0cyBvZiBjb250cm9sbGVkCm1peHR1cmVzIHdpdGgga25vd24gZ3JvdW5kIHRydXRoLiBVUFMxIHBlcHRpZGVzIGF0IGNvbmNlbnRyYXRpb25zIG9mCjUwMCwgMzMzLCAyNTAsIGFuZCA2Mi41IGZtb2wgd2VyZSBzcGlrZWQgaW50byA1MCBnIG9mIFNJTEFDIEhlTGEKcGVwdGlkZXMsIGVhY2ggaW4gZHVwbGljYXRlLiBUaGVzZSBjb25jZW50cmF0aW9ucyBmb3JtIGEgZGlsdXRpb24Kc2VyaWVzIG9mIDEsIDAuNjY3LCAwLjUsIGFuZCAwLjEyNSByZWxhdGl2ZSB0byB0aGUgaGlnaGVzdCBVUFMxCnBlcHRpZGUgYW1vdW50ICg1MDAgZm1vbCkuIEEgcmVmZXJlbmNlIHNhbXBsZSB3YXMgY3JlYXRlZCBieSBjb21iaW5pbmcKdGhlIGRpbHV0ZWQgVVBTMSBwZXB0aWRlIHNhbXBsZXMgd2l0aCA1MGcgb2YgU0lMQUMgSGVMYSBwZXB0aWRlcy4gQWxsCmRpbHV0aW9ucyBhbmQgdGhlIHJlZmVyZW5jZSBzYW1wbGUgd2VyZSBwcmVwYXJlZCBpbiBkdXBsaWNhdGUsCnJlc3VsdGluZyBpbiBhIHRvdGFsIG9mIHRlbiBzYW1wbGVzLiBUaGVzZSBzYW1wbGVzIHdlcmUgdGhlbiB0cmVhdGVkCndpdGggVE1UMTAtcGxleCByZWFnZW50cyBhbmQgY29tYmluZWQgYmVmb3JlIExDLU1TL01TIGFuYWx5c2lzLiBUaGlzCnByb3RvY29sIHdhcyByZXBlYXRlZCBmaXZlIHRpbWVzLCBlYWNoIHdpdGggdGhyZWUgdGVjaG5pY2FsCnJlcGxpY2F0ZXMsIHRvdGFsaW5nIDE1IE1TIHJ1bnMuCgpXZSB3aWxsIHN0YXJ0IGZyb20gdGhlIFBTTSBkYXRhIGdlbmVyYXRlZCBieSBTa3lsaW5lIGFuZCBpbmZlcgpwcm90ZWluLWxldmVsIGRpZmZlcmVuY2VzIGJldHdlZW4gc2FtcGxlcy4gVG8gYWNoaWV2ZSB0aGlzIGdvYWwsIHdlCndpbGwgYXBwbHkgYW4gbXNxcm9iMlRNVCB3b3JrZmxvdywgYSBkYXRhIHByb2Nlc3NpbmcgYW5kIG1vZGVsbGluZwp3b3JrZmxvdyBkZWRpY2F0ZWQgdG8gdGhlIGFuYWx5c2lzIG9mIFRNVC1iYXNlZCBwcm90ZW9taWNzIGRhdGFzZXRzLgpXZSB3aWxsIGRlbW9uc3RyYXRlIGhvdyB0aGUgd29ya2Zsb3cgY2FuIGhpZ2hsaWdodCB0aGUgc3Bpa2VkLWluCnByb3RlaW5zLiBCZWZvcmUgZGVsdmluZyBpbnRvIHRoZSBhbmFseXNpcywgbGV0IHVzIHByZXBhcmUgb3VyCmNvbXB1dGF0aW9uYWwgZW52aXJvbm1lbnQuCgojIyBMb2FkIHBhY2thZ2VzCgpXZSBsb2FkIHRoZSBgbXNxcm9iMmAgcGFja2FnZSwgYWxvbmcgd2l0aCBhZGRpdGlvbmFsIHBhY2thZ2VzIGZvcgpkYXRhIG1hbmlwdWxhdGlvbiBhbmQgdmlzdWFsaXNhdGlvbi4KCmBgYHtyfQpsaWJyYXJ5KCJtc3Fyb2IyIikKbGlicmFyeSgiZHBseXIiKQpsaWJyYXJ5KCJnZ3Bsb3QyIikKbGlicmFyeSgicGF0Y2h3b3JrIikKYGBgCgpXZSBhbHNvIGNvbmZpZ3VyZSB0aGUgcGFyYWxsZWxpc2F0aW9uIGZyYW1ld29yay4KCmBgYHtyfQpsaWJyYXJ5KCJCaW9jUGFyYWxsZWwiKQpyZWdpc3RlcihTZXJpYWxQYXJhbSgpKQpgYGAKCiMjIERhdGEKClRoZSBkYXRhIGhhdmUgYmVlbiBkZXBvc2l0ZWQgYnkgdGhlIGF1dGhvcnMgaW4gdGhlIGBNU1YwMDAwODQyNjRgCk1BU1NpVkUgcmVwb3NpdG9yeSwgYnV0IHdlIHdpbGwgcmV0cmlldmUgdGhlIHRpbWUgc3RhbXBlZCBkYXRhIGZyb20Kb3VyIFtaZW5vZG8gcmVwb3NpdG9yeV0oaHR0cHM6Ly96ZW5vZG8ub3JnL3JlY29yZHMvMTQ3Njc5MDUpLiBXZSBuZWVkCjIgZmlsZXM6IHRoZSBTa3lsaW5lIGlkZW50aWZpY2F0aW9uIGFuZCBxdWFudGlmaWNhdGlvbiB0YWJsZSBnZW5lcmF0ZWQKYnkgdGhlIGF1dGhvcnMgYW5kIHRoZSBzYW1wbGUgYW5ub3RhdGlvbiBmaWxlcy4KClRvIGZhY2lsaXRhdGUgbWFuYWdlbWVudCBvZiB0aGUgZmlsZXMsIHdlIGRvd25sb2FkIHRoZSByZXF1aXJlZCBmaWxlcwp1c2luZyB0aGUgYEJpb2NGaWxlQ2FjaGVgIHBhY2thZ2UuIFRoZSBjaHVuayBiZWxvdyB3aWxsIHRha2Ugc29tZSB0aW1lCnRvIGNvbXBsZXRlIHRoZSBmaXJzdCB0aW1lIHlvdSBydW4gaXQgYXMgaXQgbmVlZHMgdG8gZG93bmxvYWQgdGhlCihsYXJnZSkgZmlsZSBsb2NhbGx5LCBidXQgd2lsbCBmZXRjaCB0aGUgbG9jYWwgY29weSB0aGUgZm9sbG93aW5nCnRpbWVzLgoKYGBge3IgZG93bmxvYWRfZGF0YX0KbGlicmFyeSgiQmlvY0ZpbGVDYWNoZSIpCmJmYyA8LSBCaW9jRmlsZUNhY2hlKCkKcHNtRmlsZSA8LSBiZmNycGF0aChiZmMsICJodHRwczovL3plbm9kby5vcmcvcmVjb3Jkcy8xNDc2NzkwNS9maWxlcy9zcGlrZWluMV9wc21zLnR4dD9kb3dubG9hZD0xIikKYW5ub3RGaWxlIDwtIGJmY3JwYXRoKGJmYywgImh0dHBzOi8vemVub2RvLm9yZy9yZWNvcmRzLzE0NzY3OTA1L2ZpbGVzL3NwaWtlaW4xX2Fubm90YXRpb25zLmNzdj9kb3dubG9hZD0xIikKYGBgCgpOb3cgdGhlIGZpbGVzIGFyZSBkb3dubG9hZGVkLCB3ZSBjYW4gbG9hZCB0aGUgdHdvIHRhYmxlcy4gCgoKIyMjIFBTTSB0YWJsZQoKQW4gTVMgZXhwZXJpbWVudCBnZW5lcmF0ZXMgc3BlY3RyYS4gRWFjaCBNUzIgc3BlY3RyYSBhcmUgdXNlZCB0byBpbmZlciAKdGhlIHBlcHRpZGUgaWRlbnRpdHkgdGhhbmtzIHRvIGEgc2VhcmNoIGVuZ2luZS4gV2hlbgphbiBvYnNlcnZlZCBzcGVjdHJ1bSBpcyBtYXRjaGVkIHRvIGEgdGhlb3JldGljYWwgcGVwdGlkZSBzcGVjdHJ1bSwgd2UKaGF2ZSBhIHBlcHRpZGUtdG8tc3BlY3RydW0gbWF0Y2ggKFBTTSkuIFRoZSBpZGVudGlmaWNhdGlvbiBzb2Z0d2FyZQpjb21waWxlcyBhbGwgdGhlIFBTTXMgaW5zaWRlIGEgdGFibGUuIEhlbmNlLCB0aGUgUFNNIGRhdGEgaXMgdGhlCmxvd2VzdCBwb3NzaWJsZSBsZXZlbCB0byBwZXJmb3JtIGRhdGEgbW9kZWxsaW5nLgoKRWFjaCByb3cgaW4gdGhlIFBTTSBkYXRhIHRhYmxlIGNvbnRhaW5zIGluZm9ybWF0aW9uIGZvciBvbmUgUFNNICh0aGUKdGFibGUgYmVsb3cgc2hvd3MgdGhlIGZpcnN0IDYgcm93cykuIFRoZSBjb2x1bW5zIGNvbnRhaW5zIHZhcmlvdXMKaW5mb3JtYXRpb24gYWJvdXQgdGhlIFBTTSwgc3VjaCBhcyB0aGUgcGVwdGlkZSBzZXF1ZW5jZSBhbmQgY2hhcmdlLAp0aGUgcXVhbnRpZmllZCB2YWx1ZSwgdGhlIGluZmVycmVkIHByb3RlaW4gZ3JvdXAsIHRoZSBtZWFzdXJlZCBhbmQKcHJlZGljdGVkIHJldGVudGlvbiB0aW1lIGFuZCBwcmVjdXJzb3IgbWFzcywgdGhlIHNjb3JlIG9mIHRoZSBtYXRjaCwKLi4uIEluIHRoZSBjYXNlIG9mIFRNVCBkYXRhLCB0aGUgcXVhbnRpZmljYXRpb24gdmFsdWVzIGFyZSBwcm92aWRlcyBpbgptdWx0aXBsZSBjb2x1bW5zIChzdGFydCB3aXRoIGAiQWJ1bmRhbmNlLiJgKSwgb25lIGZvciBlYWNoIFRNVCBsYWJlbC4KUmVnYXJkbGVzcyBvZiBUTVQgb3IgTEZRIGV4cGVyaW1lbnRzLCB0aGUgUFNNIHRhYmxlIHN0YWNrcyB0aGUKcXVhbnRpdGF0aXZlIHZhbHVlcyBmcm9tIHNhbXBsZXMgaW4gZGlmZmVyZW50IHJ1bnMgYmVsb3cgZWFjaCBvdGhlci4KCmBgYHtyfQpwc21zIDwtIHJlYWQuZGVsaW0ocHNtRmlsZSkKcWNvbHMgPC0gZ3JlcCgiQWJ1bmRhbmNlIiwgY29sbmFtZXMocHNtcyksIHZhbHVlID0gVFJVRSkKYGBgCmBgYHtyLCBlY2hvPUZBTFNFfQprbml0cjo6a2FibGUoaGVhZChwc21zKSkKYGBgCgpUaGVyZSBpcyBhIHBlY3VsaWFyaXR5IHdpdGggdGhlIGRhdGFzZXQ6IHRoZSBzcGVjdHJhIGhhdmUgYmVlbgppZGVudGlmaWVkIHdpdGggMiBub2Rlcy4gSW4gb25lIG5vZGUsIHRoZSBhdXRob3JzIHNlYXJjaGVkIHRoZQpTd2lzc1Byb3QgZGF0YWJhc2UgZm9yIHByb3RlaW5zIHdpdGggc3RhdGljIG1vZGlmaWNhdGlvbnMgcmVsYXRlZCB0bwp0aGUgbWV0YWJvbGljIGxhYmVsbGluZywgaW4gdGhlIG90aGVyIG5vZGUgdGhleSBzZWFyY2hlZCB0aGUgU2lnbWFfVVBTCnByb3RlaW4gZGF0YWJhc2Ugd2l0aG91dCB0aGVzZSBzdGF0aWMgbW9kaWZpY2F0aW9ucy4gSG93ZXZlciwgc29tZQpzcGVjdHJhIHdlcmUgaWRlbnRpZmllZCBieSBib3RoIG5vZGVzIGxlYWRpbmcgdG8gZHVwbGljYXRlIFBTTXMuIFdlCmhlcmUgcmVtb3ZlIHRoZXNlIGR1cGxpY2F0ZWQgUFNNcyB0aGF0IGFyZSBpZGVudGlmaWNhdGlvbiBhcnRlZmFjdHMuCgpgYGB7cn0KZHVwbGljYXRlc1F1YW50cyA8LSBkdXBsaWNhdGVkKHBzbXNbLCBxY29sc10pIHwgZHVwbGljYXRlZChwc21zWywgcWNvbHNdLCBmcm9tTGFzdCA9IFRSVUUpCnBzbXMgPC0gcHNtc1shZHVwbGljYXRlc1F1YW50cywgXQpgYGAKCldlIHdpbGwgYWxzbyBzdWJzZXQgdGhlIGRhdGEgc2V0IHRvIHJlZHVjZSBjb21wdXRhdGlvbmFsIGNvc3RzLiBJZiB5b3UKd2FudCB0byBydW4gdGhlIGFuYWx5c2lzIG9uIHRoZSBmdWxsIGRhdGEgc2V0LCB5b3UgY2FuIHNraXAgdGhpcyBjb2RlCmNodW5rLiBUaGUgc3Vic2V0dGluZyB3aWxsIGtlZXAgYWxsIFVQUyBwcm90ZWlucywga25vd24gdG8gYmUKZGlmZmVyZW50aWFsbHkgYWJ1bmRhbnQgYnkgZXhwZXJpbWVudGFsIGRlc2lnbiwgYW5kIHdlIHdpbGwga2VlcCA1MDAKYmFja2dyb3VuZCBwcm90ZWlucyBrbm93biB0byBiZSB1bmNoYW5nZWQgYWNyb3NzIGNvbmRpdGlvbi4KCmBgYHtyfQphbGxQcm90ZWlucyA8LSB1bmlxdWUocHNtcyRQcm90ZWluLkFjY2Vzc2lvbnMpCnVwc1Byb3RlaW5zIDwtIGdyZXAoInVwcyIsIGFsbFByb3RlaW5zLCB2YWx1ZSA9IFRSVUUpCmhlbGFQcm90ZWlucyA8LSBncmVwKCJ1cHMiLCBhbGxQcm90ZWlucywgdmFsdWUgPSBUUlVFLCBpbnZlcnQgPSBUUlVFKQpzZXQuc2VlZCgxMjM0KQprZWVwUHJvdGVpbnMgPC0gYyh1cHNQcm90ZWlucywgc2FtcGxlKGhlbGFQcm90ZWlucywgNTAwKSkKcHNtcyA8LSBwc21zW3BzbXMkUHJvdGVpbi5BY2Nlc3Npb25zICVpbiUga2VlcFByb3RlaW5zLCBdCmBgYAoKIyMjIFNhbXBsZSBhbm5vdGF0aW9uIHRhYmxlCgpFYWNoIHJvdyBpbiB0aGUgYW5ub3RhdGlvbiB0YWJsZSBjb250YWlucyBpbmZvcm1hdGlvbiBhYm91dCBvbmUKc2FtcGxlLiBUaGUgY29sdW1ucyBjb250YWluIHZhcmlvdXMgZGVzY3JpcHRvcnMgYWJvdXQgdGhlIHNhbXBsZSwgc3VjaAphcyB0aGUgbmFtZSBvZiB0aGUgc2FtcGxlIG9yIHRoZSBNUyBydW4sIHRoZSB0cmVhdG1lbnQgKGhlcmUgdGhlCnNwaWtlLWluIGNvbmRpdGlvbiksIHRoZSBsYWIgdGhhdCBhY3F1aXJlZCB0aGUgc2FtcGxlIG9yIGFueSBvdGhlcgpiaW9sb2dpY2FsIG9yIHRlY2huaWNhbCBpbmZvcm1hdGlvbiB0aGF0IG1heSBpbXBhY3QgdGhlIGRhdGEgcXVhbGl0eQpvciB0aGUgcXVhbnRpZmljYXRpb24uIFdpdGhvdXQgYW4gYW5ub3RhdGlvbiB0YWJsZSwgbm8gYW5hbHlzaXMKY2FuIGJlIHBlcmZvcm1lZC4gVGhlIGFubm90YXRpb24gdGFibGUgdXNlZCBpbiB0aGlzIHR1dG9yaWFsIGhhcyBiZWVuCmdlbmVyYXRlZCBieSB0aGUgYXV0aG9ycy4KCmBgYHtyfQpjb2xkYXRhIDwtIHJlYWQuY3N2KGFubm90RmlsZSkKYGBgCgpXZSBwZXJmb3JtIGEgbGl0dGxlIGNsZWFudXAgZm9yIGNvbmNpc2Ugb3V0cHV0LCBhbmQgcmVuYW1lIHRoZQpgQ2hhbm5lbGAgY29sdW1uIHRvIGBMYWJlbGAgYmVjYXVzZSBpdCBjb250YWlucyBpbmZvcm1hdGlvbiBhYm91dCB0aGUKVE1UIGxhYmVsIHVzZWQuCgpgYGB7cn0KY29sZGF0YSA8LSBjb2xkYXRhWywgYygiUnVuIiwgIkNoYW5uZWwiLCAiQ29uZGl0aW9uIiwgIk1peHR1cmUiLCAiVGVjaFJlcE1peHR1cmUiKV0KY29sZGF0YSRGaWxlLk5hbWUgPC0gY29sZGF0YSRSdW4KY29sZGF0YSRSdW4gPC0gc3ViKCJeLiooTWl4LiopLnJhdyIsICJcXDEiLCBjb2xkYXRhJFJ1bikKY29sbmFtZXMoY29sZGF0YSlbMl0gPC0gIkxhYmVsIgpgYGAKYGBge3IsIGVjaG89RkFMU0V9CmtuaXRyOjprYWJsZShoZWFkKGNvbGRhdGEpKQpgYGAKCiMjIyBDcmVhdGUgdGhlIFFGZWF0dXJlcyBvYmplY3QKCldlIHVzZSBgcmVhZFFGZWF0dXJlcygpYCB0byBjcmVhdGUgYSBgUUZlYXR1cmVzYCBvYmplY3QuIFNpbmNlIHdlCnN0YXJ0IGZyb20gdGhlIFBTTS1sZXZlbCBkYXRhLCB0aGUgYXBwcm9hY2ggaXMgc29tZXdoYXQgbW9yZQplbGFib3JhdGUuIFlvdSBjYW4gZmluZCBhbiBpbGx1c3RyYXRlZCBzdGVwLWJ5LXN0ZXAgZ3VpZGUgaW4gdGhlCltRRmVhdHVyZXMKdmlnbmV0dGVdKGh0dHBzOi8vcmZvcm1hc3NzcGVjdHJvbWV0cnkuZ2l0aHViLmlvL1FGZWF0dXJlcy9hcnRpY2xlcy9yZWFkX1FGZWF0dXJlcy5odG1sKS4KRmlyc3QsIHJlY2FsbCB0aGF0IGV2ZXJ5IHF1YW50aXRhdGl2ZSBjb2x1bW4gaW4gdGhlIFBTTSB0YWJsZSBjb250YWlucwppbmZvcm1hdGlvbiBmb3IgbXVsdGlwbGUgcnVucy4gVGhlcmVmb3JlLCB0aGUgZnVuY3Rpb24gc3BsaXQgdGhlIHRhYmxlCmJhc2VkIG9uIHRoZSBydW4gaWRlbnRpZmllciwgZ2l2ZW4gYnkgdGhlIGBydW5Db2xgIGFyZ3VtZW50IChmb3IKU2t5bGluZSwgdGhhdCBpZGVudGlmaWVyIGlzIGNvbnRhaW5lZCBpbiBgU3BlY3RydW0uRmlsZWApLiBTbywgdGhlCmBRRmVhdHVyZXNgIG9iamVjdCBhZnRlciBpbXBvcnQgd2lsbCBjb250YWluIGFzIG1hbnkgc2V0cyBhcyB0aGVyZSBhcmUKcnVucy4gTmV4dCwgdGhlIGZ1bmN0aW9uIGxpbmtzIHRoZSBhbm5vdGF0aW9uIHRhYmxlIHdpdGggdGhlIFBTTSBkYXRhLgpUbyBhY2hpZXZlIHRoaXMsIHRoZSBhbm5vdGF0aW9uIHRhYmxlIG11c3QgY29udGFpbiBhIGBydW5Db2xgIGNvbHVtbgp0aGF0IHByb3ZpZGVzIHRoZSBydW4gaWRlbnRpZmllciBpbiB3aGljaCBlYWNoIHNhbXBsZSBoYXMgYmVlbgphY3F1aXJlZCwgYW5kIHRoaXMgaW5mb3JtYXRpb24gd2lsbCBiZSB1c2VkIHRvIG1hdGNoIHRoZSBpZGVudGlmaWVycwppbiB0aGUgYFNwZWN0cnVtLkZpbGVgIGNvbHVtbiBvZiB0aGUgUFNNIHRhYmxlLiBUaGUgYW5ub3RhdGlvbiB0YWJsZQptdXN0IGFsc28gY29udGFpbiBhIGBxdWFudENvbHNgIGNvbHVtbiB0aGF0IHRlbGxzIHRoZSBmdW5jdGlvbiB3aGljaApjb2x1bW4gaW4gdGhlIFBTTSB0YWJsZSBjb250YWlucyB0aGUgcXVhbnRpdGF0aXZlIGluZm9ybWF0aW9uIGZvciBhCmdpdmVuIHNhbXBsZS4gSW4gdGhpcyBjYXNlLCB0aGUgYHF1YW50Q29sc2AgZGVwZW5kIG9uCgpgYGB7cn0KY29sZGF0YSRydW5Db2wgPC0gY29sZGF0YSRGaWxlLk5hbWUKY29sZGF0YSRxdWFudENvbHMgPC0gcGFzdGUwKCJBYnVuZGFuY2UuLiIsIGNvbGRhdGEkTGFiZWwpCihzcGlrZWluIDwtIHJlYWRRRmVhdHVyZXMoCiAgcHNtcywgY29sRGF0YSA9IGNvbGRhdGEsCiAgcnVuQ29sID0gIlNwZWN0cnVtLkZpbGUiLAogIHF1YW50Q29scyA9IHFjb2xzCikpCmBgYAoKV2Ugbm93IGhhdmUgYSBgUUZlYXR1cmVzYCBvYmplY3Qgd2l0aCAxNSBzZXRzLCBlYWNoIGNvbnRhaW5pbmcgZGF0YQphc3NvY2lhdGVkIHdpdGggYW4gTVMgcnVuLiBUaGUgbmFtZSBvZiBlYWNoIHNldCBpcyBkZWZpbmVkIGJ5IHRoZSBuYW1lCm9mIHRoZSBjb3JyZXNwb25kaW5nIGZpbGUgbmFtZSBvZiB0aGUgcnVuLCB3aGljaCBpcyB1bm5lY2Vzc2FyaWx5CmxvbmcuIFdlIHNpbXBsaWZ5IHRoZSBzZXQgbmFtZXMsIGFsdGhvdWdoIHRoaXMgc3RlcCBpcyBvcHRpb25hbCBhbmQKb25seSBtZWFudCB0byBpbXByb3ZlIHRoZSBjbGFyaXR5IG9mIHRoZSBvdXRwdXQuCgpgYGB7cn0KIyMgVGhpcyBpcyBvcHRpb25hbApuYW1lcyhzcGlrZWluKSA8LSBzdWIoIl4uKihNaXguKikucmF3IiwgIlxcMSIsIG5hbWVzKHNwaWtlaW4pKQooaW5wdXROYW1lcyA8LSBuYW1lcyhzcGlrZWluKSkKYGBgCgojIyBEYXRhIHByZXByb2Nlc3NpbmcKCldlIHdpbGwgZm9sbG93IGEgc2ltaWxhciBkYXRhIHByb2Nlc3Npbmcgd29ya2Zsb3cgYXMgYmVmb3JlLgoKIyMjIEVuY29kaW5nIG1pc3NpbmcgdmFsdWVzCgpBbnkgemVybyB2YWx1ZSBuZWVkcyB0byBiZSBlbmNvZGVkIGJ5IGEgbWlzc2luZyB2YWx1ZS4KCmBgYHtyfQpzcGlrZWluIDwtIHplcm9Jc05BKHNwaWtlaW4sIGlucHV0TmFtZXMpCmBgYAoKIyMjIExvZzIgdHJhbnNmb3JtYXRpb24KCkxvZzItdHJhbnNmb3JtYXRpb24gc29sdmVzIHRoZSBoZXRlcm9za2VkYXN0aWNpdHkgaXNzdWUsIGJ1dCBhbHNvCnByb3ZpZGVzIGEgc2NhbGUgdGhhdCBkaXJlY3RseSByZWxhdGVzIHRvIGJpb2xvZ2ljYWwgaW50ZXJwcmV0YXRpb24uCgpgYGB7cn0KbG9nTmFtZXMgIDwtIHBhc3RlMChpbnB1dE5hbWVzLCAiX2xvZyIpCnNwaWtlaW4gPC0gbG9nVHJhbnNmb3JtKAogICAgc3Bpa2VpbiwgaW5wdXROYW1lcywgbmFtZSA9IGxvZ05hbWVzLCBiYXNlID0gMgopCmBgYAoKIyMjIFNhbXBsZSBmaWx0ZXJpbmcKCldlIHJlbW92ZSB0aGUgcmVmZXJlbmNlIHNhbXBsZXMgc2luY2UgYG1zcXJvYjJgIHdvcmtmbG93cyBkbyBub3QKdXNlIHJlZmVyZW5jZSBub3JtYWxpc2F0aW9uLgoKYGBge3J9CnNwaWtlaW4gPC0gc3Vic2V0QnlDb2xEYXRhKHNwaWtlaW4sIHNwaWtlaW4kQ29uZGl0aW9uICE9ICJOb3JtIikKYGBgCgojIyMgUFNNIGZpbHRlcmluZwoKRmlsdGVyaW5nIHJlbW92ZXMgbG93LXF1YWxpdHkgYW5kIHVucmVsaWFibGUgUFNNcyB0aGF0IHdvdWxkIG90aGVyd2lzZQppbnRyb2R1Y2Ugbm9pc2UgYW5kIGFydGVmYWN0cyBpbiB0aGUgZGF0YS4gQ29uY2VwdHVhbGx5LCBQU00gZmlsdGVyaW5nCmlzIGlkZW50aWNhbCB0byBwZXB0aWRlIGZpbHRlcmluZywgYnV0IHdlIHdpbGwgaGVyZSBhcHBseSBmaWx0ZXJpbmcKY3JpdGVyaWEgZm9yIHdoaWNoIHNvbWUgYXJlIG5vdCByZWFkaWx5IGF2YWlsYWJsZSBpbiB0aGUgZGF0YS4KVGhlcmVmb3JlLCB3ZSB3aWxsIGFkZCBjdXN0b20gZmlsdGVyaW5nIHZhcmlhYmxlIHRvIHRoZSBgcm93RGF0YWAgdGhhdAp3aWxsIHRoZW4gYmUgdXNlZCB3aXRoIGBmaWx0ZXJGZWF0dXJlcygpYC4gVGhpcyBwcm92aWRlcyBhbiBpZGVhbCB1c2UKY2FzZSB0byBkZW1vbnN0cmF0ZSB0aGUgY3VzdG9taXNhdGlvbiBvZiBhIGZpbHRlcmluZyB3b3JrZmxvdy4KCiMjIyMgUmVtb3ZlIGFtYmlndW91cyBpZGVudGlmaWNhdGlvbnMKClRoZSBiYWNrZ3JvdW5kIHByb3RlaW5zIG9yaWdpbmF0ZSBmcm9tIEhlTGEgY2VsbHMsIHdoaWNoIGFsc28gY29udGFpbgpVUFMgcHJvdGVpbnMuIFRoZSBiYWNrZ3JvdW5kIFVQUyBwcm90ZWlucyBhbmQgdGhlIHNwaWtlZC1pbiBVUFMKcHJvdGVpbnMgZGlmZmVyIGluIG1ldGFib2xpYyBsYWJlbGxpbmcsIHNvIHdlIHNob3VsZCBiZSBhYmxlIHRvCmRpc3Rpbmd1aXNoIHRoZW0uIFdlIHVzZWQgdGhlIFBTTS1sZXZlbCBkYXRhIHNlYXJjaGVkIHdpdGggbWFzY290LCBhcwpwcm92aWRlZCBieSB0aGUgTVNzdGF0c1RNVCBhdXRob3JzIHdobyB1c2VkIHR3byBtYXNjb3QgaWRlbnRpZmljYXRpb24Kbm9kZXMuIEluIG9uZSBub2RlIHRoZXkgc2VhcmNoZWQgdGhlIFN3aXNzUHJvdCBkYXRhYmFzZSBmb3IgcHJvdGVpbnMKd2l0aCBzdGF0aWMgbW9kaWZpY2F0aW9ucyByZWxhdGVkIHRvIHRoZSBtZXRhYm9saWMgbGFiZWxsaW5nLCBpbiB0aGUKb3RoZXIgbm9kZSB0aGV5IHNlYXJjaGVkIHRoZSBTaWdtYV9VUFMgcHJvdGVpbiBkYXRhYmFzZSB3aXRob3V0IHRoZXNlCnN0YXRpYyBtb2RpZmljYXRpb25zLiBJZGVhbGx5LCB0aGlzIHNob3VsZCBzZXBhcmF0ZSB0aGUgc3Bpa2VkLWluIFVQUwpwcm90ZWlucyBhbmQgdGhlIFVQUyBwcm90ZWlucyBmcm9tIHRoZSBIZUxhIGNlbGxzLCBob3dldmVyLCB0aGlzIGlzCm5vdCBhbHdheXMgdGhlIGNhc2UuIFRoZSBTd2lzc1Byb3Qgc2VhcmNoIGlzIGV4cGVjdGVkIHRvIHJldHVybgpwZXB0aWRlLXNwZWN0cnVtIG1hdGNoZXMgKFBTTXMpIGZvciBhbGwgcHJvdGVpbnMsIGluY2x1ZGluZyBub24tVVBTCkhlTGEsIFVQUyBIZUxhLCBhbmQgc3Bpa2UtaW4gVVBTIHByb3RlaW5zLiBDb252ZXJzZWx5LCB0aGUgU2lnbWFfVVBTCnNlYXJjaCBpcyBleHBlY3RlZCB0byByZXR1cm4gUFNNcyBleGNsdXNpdmVseSBmb3Igc3Bpa2UtaW4gVVBTCnByb3RlaW5zLiBIb3dldmVyLCBhIFBTTSB0aGF0IG1hdGNoZXMgYSBVUFMgcHJvdGVpbiBpbiB0aGUgU3dpc3NQcm90CnNlYXJjaCBidXQgaXMgbm90IGlkZW50aWZpZWQgYXMgc3VjaCBpbiB0aGUgU2lnbWFfVVBTIHNlYXJjaCBjb3VsZAplaXRoZXIgY29ycmVjdGx5IG9yaWdpbmF0ZSBmcm9tIGEgSGVMYSBwcm90ZWluIG9yIHJlcHJlc2VudCBhCnNwaWtlZC1pbiBVUFMgcHJvdGVpbiB0aGF0IHdhcyBub3QgcmVjb2duaXNlZCBhcyBzdWNoIGluIHRoZSBTaWdtYV9VUFMKc2VhcmNoLiBBZGRpdGlvbmFsbHksIHRoZXJlIGFyZSBhbWJpZ3VvdXMgUFNNcyB0aGF0IGFyZSBub3QgbWF0Y2hlZCB0bwphIFVQUyBwcm90ZWluIGluIHRoZSBIZUxhIHNlYXJjaCBidXQgYXJlIG1hdGNoZWQgdG8gYSBVUFMgcHJvdGVpbiBpbgp0aGUgU3dpc3NQcm90IHNlYXJjaC4gVG8gYWRkcmVzcyB0aGlzLCB3ZSBleGNsdWRlIHRoZXNlIGFtYmlndW91cwpwcm90ZWlucyBmcm9tIHRoZSBhbmFseXNpcy4KClRvIGRlZmluZSB0aGUgYW1pYmlndW91cyBQU01zLCB3ZSByZXRyaWV2ZSB0aGUgUFNNIGFubm90YXRpb25zIGZyb20KdGhlIGByb3dEYXRhYCBhbmQgY3JlYXRlIGEgbmV3IGNvbHVtIGluZGljYXRpbmcgd2hldGhlciBhIFBTTSBiZWxvbmdzCnRvIGEgVVBTIHByb3RlaW4gb3Igbm90LCBiYXNlZCBvbiB0aGUgcHJvdGVpbiBTd2lzc1Byb3QgaWRlbnRpZmllcnMuCkZvciB0aGlzLCB3ZSBhcHBseSBhIGN1c3RvbSBmaWx0ZXJpbmcgd29ya2xvdzoKCjEuICoqQ29sbGVjdCBkYXRhKio6IGNvbWJpbmUgYWxsIHRoZSBgcm93RGF0YWAgaW5mb3JtYXRpb24gaW4gYSBzaW5nbGUKICAgdGFibGUuIFdlIHdpbGwgYXBwbHkgdGhlIGZpbHRlciBvbiB0aGUgCgpgYGB7cn0Kcm93ZGF0YSA8LSByYmluZFJvd0RhdGEoc3Bpa2VpbiwgbG9nTmFtZXMpCmBgYAoKMi4gKipDb21wdXRlIG5ldyB2YXJpYWJsZSoqOiAoMmEpIGRlZmluZSB3aGV0aGVyIHRoZSBQU00ncyBwcm90ZWluIAogICBncm91cCBpcyBhIFVQUyBwcm90ZWluIGFuZCB0aGVuICgyYikgZGVmaW5lIGFuIGFtYmlndW91cyBQU00gYXMgYQogICBQU00gdGhhdCBpcyBtYXJrZWQgYXMgVVBTIGJ5IHRoZSBTd2lzc1Byb3QgaWRlbnRpZmllciBidXQgbm90IGJ5CiAgIHRoZSBTaWdtYV9VUFMgbm9kZSAoYE1hcmtlZC5hc2AgY29sdW1uKSwgYW5kIGludmVyc2VseS4KCmBgYHtyfQojIyAyYS4Kcm93ZGF0YSRpc1VwcyA8LSAibm8iCmlzVXBzUHJvdGVpbiA8LSBncmVwbCgidXBzIiwgcm93ZGF0YSRQcm90ZWluLkFjY2Vzc2lvbnMpCnJvd2RhdGEkaXNVcHNbaXNVcHNQcm90ZWluXSA8LSAieWVzIgojIyAyYi4Kcm93ZGF0YSRpc1Vwc1shaXNVcHNQcm90ZWluICYgZ3JlcGwoIlVQUyIsIHJvd2RhdGEkTWFya2VkLmFzKV0gPC0gImFtYiIKcm93ZGF0YSRpc1Vwc1tpc1Vwc1Byb3RlaW4gJiAhZ3JlcGwoIlVQUyIsIHJvd2RhdGEkTWFya2VkLmFzKV0gPC0gImFtYiIKYGBgCgozLiAqKlJlaW5zZXJ0IGluIHRoZSByb3dEYXRhKio6IGluc2VydCB0aGUgbW9kaWZpZWQgdGFibGUgd2l0aCBuZXcgCiAgIGluZm9ybWF0aW9uIGJhY2sgaW4gdGhlIGByb3dEYXRhYCBvZiB0aGUgZGlmZmVyZW50IHNldHMuIFRoaXMgbWVhbnMKICAgdGhhdCB0aGUgc2luZ2xlIHRhYmxlIHdpdGggYHJvd0RhdGFgIGluZm9ybWF0aW9uIG5lZWRzIHRvIGJlIHNwbGl0CiAgIGJ5IGVhY2ggc2V0LiBgc3BsaXQoKWAgd2lsbCBwcm9kdWNlIGEgbmFtZWQgbGlzdCBvZiB0YWJsZXMgYW5kIGVhY2gKICAgdGFibGUgd2lsbCBiZSBpdGVyYXRpdmVseSBpbnNlcnRlZCBhcyBgcm93RGF0YWAgb2YgdGhlIHNldC4KCmBgYHtyfQpyb3dEYXRhKHNwaWtlaW4pIDwtIHNwbGl0KHJvd2RhdGEsIHJvd2RhdGEkYXNzYXkpCmBgYAoKNC4gKipBcHBseSB0aGUgZmlsdGVyKio6IHRoZSBmaWx0ZXJpbmcgaXMgcGVyZm9ybWVkIGJ5IAogICBgZmlsdGVyRmVhdHVyZXMoKWAgdXNpbmcgdGhlIG5ldyBpbmZvcm1hdGlvbiBmcm9tIHRoZSBgcm93RGF0YWAuIFdlCiAgIHNwZWNpZnkgYGtlZXAgPSBUUlVFYCBiZWNhdXNlIHRoZSBpbnB1dCBzZXRzIChiZWZvcmUKICAgbG9nLXRyYW5zZm9ybWF0aW9uKSBkbyBub3QgY29udGFpbiB0aGUgZmlsdGVyaW5nIHZhcmlhYmxlLCBzbyB3ZQogICB0ZWxsIHRoZSBmdW5jdGlvbiB0byBrZWVwIGFsbCBQU01zIGZvciB0aGUgc2V0cyB0aGF0IGRvbid0IGhhdmUgdGhlCiAgIHZhcmlhYmxlIGBpc1Vwc2AuCgpgYGB7cn0Kc3Bpa2VpbiA8LSBmaWx0ZXJGZWF0dXJlcyhzcGlrZWluLCB+IGlzVXBzICE9ICJhbWIiLCBrZWVwID0gVFJVRSkKYGBgCgojIyMjIFJlbW92ZSBmYWlsZWQgcHJvdGVpbiBpbmZlcmVuY2UKCk5leHQsIHdlIHJlbW92ZSBQU01zIHRoYXQgY291bGQgbm90IGJlIG1hcHBlZCB0byBhIHByb3RlaW4gb3IgdGhhdCBtYXAKdG8gbXVsdGlwbGUgcHJvdGVpbnMsIGkuZS4gYSBwcm90ZWluIGdyb3VwLiBGb3IgdGhlIGxhdHRlciwgdGhlCnByb3RlaW4gaWRlbnRpZmllciBjb250YWlucyBtdWx0aXBsZSBpZGVudGlmaWVycyBzZXBhcmF0ZWQgYnkgYSBgO2ApLgpUaGlzIGluZm9ybWF0aW9uIGlzIHJlYWRpbHkgYXZhaWxhYmxlIGluIHRoZSBgcm93RGF0YWAsIHNvIHRoZXJlIGlzIG5vCm5lZWQgZm9yIGEgY3VzdG9tIGZpbHRlcmluZy4KCmBgYHtyfQpzcGlrZWluIDwtIGZpbHRlckZlYXR1cmVzKAogICAgc3Bpa2VpbiwgfiBQcm90ZWluLkFjY2Vzc2lvbnMgIT0gIiIgJiAjIyBSZW1vdmUgZmFpbGVkIHByb3RlaW4gaW5mZXJlbmNlCiAgICAgICAgIWdyZXBsKCI7IiwgUHJvdGVpbi5BY2Nlc3Npb25zKSkgIyMgUmVtb3ZlIHByb3RlaW4gZ3JvdXBzCmBgYAoKIyMjIyBSZW1vdmUgaW5jb25zaXN0ZW50IHByb3RlaW4gaW5mZXJlbmNlCgpXZSBhbHNvIHJlbW92ZSBwZXB0aWRlIGlvbnMgdGhhdCBtYXAgdG8gYSBkaWZmZXJlbnQgcHJvdGVpbiBkZXBlbmRpbmcKb24gdGhlIHJ1bi4gQWdhaW4sIHRoaXMgcmVxdWlyZXMgYSBjdXN0b20gZmlsdGVyaW5nIGFuZCB3ZSBhcHBseSB0aGUKc2FtZSBmaWx0ZXJpbmcgd29ya2Zsb3cgYXMgYWJvdmUuCgpgYGB7cn0KIyMgMS4gQ29sbGVjdCBkYXRhCnJvd2RhdGEgPC0gcmJpbmRSb3dEYXRhKHNwaWtlaW4sIGxvZ05hbWVzKQojIyAyLiBDb21wdXRlIG5ldyB2YXJpYWJsZQpyb3dkYXRhIDwtIGRhdGEuZnJhbWUocm93ZGF0YSkgfD4KICAgIGdyb3VwX2J5KEFubm90YXRlZC5TZXF1ZW5jZSwgQ2hhcmdlKSB8PgogICAgbXV0YXRlKG5Qcm90c01hcHBlZCA9IGxlbmd0aCh1bmlxdWUoUHJvdGVpbi5BY2Nlc3Npb25zKSkpCiMjIDMuIFJlaW5zZXJ0IGluIHRoZSByb3dEYXRhCnJvd0RhdGEoc3Bpa2VpbikgPC0gc3BsaXQocm93ZGF0YSwgcm93ZGF0YSRhc3NheSkKIyMgNC4gQXBwbHkgdGhlIGZpbHRlcgpzcGlrZWluIDwtIGZpbHRlckZlYXR1cmVzKHNwaWtlaW4sIH4gblByb3RzTWFwcGVkID09IDEsIGtlZXAgPSBUUlVFKQpgYGAKCiMjIyMgUmVtb3ZlIG9uZS1ydW4gd29uZGVycwoKV2UgYWxzbyByZW1vdmUgcHJvdGVpbnMgdGhhdCBjYW4gb25seSBiZSBmb3VuZCBpbiBvbmUgcnVuIGFzIHN1Y2gKcHJvdGVpbnMgbWF5IG5vdCBiZSB0cnVzdHdvcnRoeS4gSW4gdGhpcyBjYXNlLCAKCmBgYHtyfQojIyAxLiBDb2xsZWN0IGRhdGEKcm93ZGF0YSA8LSByYmluZFJvd0RhdGEoc3Bpa2VpbiwgbG9nTmFtZXMpCiMjIDIuIENvbXB1dGUgbmV3IHZhcmlhYmxlCnJvd2RhdGEgPC0gZGF0YS5mcmFtZShyb3dkYXRhKSB8PgogICAgZ3JvdXBfYnkoUHJvdGVpbi5BY2Nlc3Npb25zKSB8PgogICAgbXV0YXRlKG5SdW5zID0gbGVuZ3RoKHVuaXF1ZShhc3NheSkpKQojIyAzLiBSZWluc2VydCBpbiB0aGUgcm93RGF0YQpyb3dEYXRhKHNwaWtlaW4pIDwtIHNwbGl0KHJvd2RhdGEsIHJvd2RhdGEkYXNzYXkpCiMjIDQuIEFwcGx5IHRoZSBmaWx0ZXIKc3Bpa2VpbiA8LSBmaWx0ZXJGZWF0dXJlcyhzcGlrZWluLCB+IG5SdW5zID4gMSwga2VlcCA9IFRSVUUpCmBgYAoKIyMjIyBSZW1vdmUgZHVwbGljYXRlZCBQU01zCgpGaW5hbGx5LCBwZXB0aWRlIGlvbnMgdGhhdCB3ZXJlIGlkZW50aWZpZWQgd2l0aCBtdWx0aXBsZSBQU01zIGluIGEgcnVuCmFyZSBjb2xsYXBzZWQgdG8gdGhlIFBTTSB3aXRoIHRoZSBoaWdoZXN0IHN1bW1lZCBpbnRlbnNpdHkgb3ZlciB0aGUKVE1UIGxhYmVscywgYSBzdHJhdGVneSB0aGF0IGlzIGFsc28gdXNlZCBieSBNU3N0YXRzLgoKVGhpcyBmaWx0ZXJpbmcgcmVxdWlyZXMgYSBtb3JlIGNvbXBsZXggd29ya2Zsb3cgYmVjYXVzZSBpdCBtaXhlcwppbmZvcm1hdGlvbiBmcm9tIHRoZSBgcm93RGF0YWAgKHRvIG9idGFpbiBpb24gaWRlbnRpdGllcykgd2l0aApxdWFudGl0YXRpdmUgZGF0YSAodG8gb2J0YWluIFBTTSBpbnRlbnNpdHkgcmFua3MpLiBXZSB0aGVyZWZvcmUgCmNvbXB1dGUgdGhlIGZpbHRlcmluZyB2YXJpYWJsZSBmb3IgZXZlcnkgc2V0IGl0ZXJhdGl2ZWx5OgoKMS4gR2V0IHRoZSBgcm93RGF0YWAgZm9yIHRoZSBjdXJyZW50IHNldC4KMi4gTWFrZSBhIG5ldyB2YXJpYWJsZSBgaW9uSURgLgozLiBXZSBjYWxjdWxhdGUgdGhlIGByb3dTdW1zYCBmb3IgZWFjaCBpb24uCjQuIE1ha2UgYSBuZXcgdmFyaWFibGUgYHBzbVJhbmtgIHRoYXQgcmFua3MgdGhlIFBTTXMgZm9yIGVhY2ggaW9uIAogICBpZGVudGlmaWVyIGJhc2VkIG9uIHRoZSBzdW1tZWQgaW50ZW5zaXR5Lgo1LiBXZSBzdG9yZSB0aGUgbmV3IGluZm9ybWF0aW9uIGJhY2sgaW4gdGhlIGByb3dEYXRhYC4KCmBgYHtyfQpmb3IgKGkgaW4gbG9nTmFtZXMpIHsgIyMgZm9yIGVhY2ggc2V0IG9mIGludGVyZXN0CiAgICByb3dkYXRhIDwtIHJvd0RhdGEoc3Bpa2VpbltbaV1dKSAjIyAxLgogICAgcm93ZGF0YSRpb25JRCA8LSBwYXN0ZTAocm93ZGF0YSRBbm5vdGF0ZWQuU2VxdWVuY2UsIHJvd2RhdGEkQ2hhcmdlKSAjIyAyLgogICAgcm93ZGF0YSRyb3dTdW1zIDwtIHJvd1N1bXMoYXNzYXkoc3Bpa2VpbltbaV1dKSwgbmEucm0gPSBUUlVFKSAjIyAzLgogICAgcm93ZGF0YSA8LSBkYXRhLmZyYW1lKHJvd2RhdGEpIHw+CiAgICAgICAgZ3JvdXBfYnkoaW9uSUQpIHw+CiAgICAgICAgbXV0YXRlKHBzbVJhbmsgPSByYW5rKC1yb3dTdW1zKSkgIyMgNC4KICAgIHJvd0RhdGEoc3Bpa2VpbltbaV1dKSA8LSBEYXRhRnJhbWUocm93ZGF0YSkgIyMgNS4KfQpgYGAKCkZvciBlYWNoIGlvbiB0aGF0IG1hcHMgdG8gbXVsdGlwbGUgUFNNcywgd2Uga2VlcCB0aGUgUFNNIHdpdGggdGhlCmhpZ2hlc3Qgc3VtbWVkIGludGVuc2l0eSwgdGhhdCBpcyB0aGF0IHJhbmtzIGZpcnN0LiAKCmBgYHtyfQpzcGlrZWluIDwtIGZpbHRlckZlYXR1cmVzKHNwaWtlaW4sIH4gcHNtUmFuayA9PSAxLCBrZWVwID0gVFJVRSkKYGBgCgojIyMjIFJlbW92ZSBoaWdobHkgbWlzc2luZyBQU01zCgpXZSB0aGVuIHJlbW92ZSBQU01zIHdpdGggZml2ZSBvciBtb3JlIG1pc3NpbmcgdmFsdWVzIG91dCBvZiB0aGUgdGVuClRNVCBsYWJlbHMgKD49IDUwJSkuIFRoaXMgaXMgYW4gYXJiaXRyYXJ5IHZhbHVlIHRoYXQgbWF5IG5lZWQgdG8gYmUKYWRqdXN0ZWQgZGVwZW5kaW5nIG9uIHRoZSBleHBlcmltZW50IGFuZCB0aGUgZGF0YSBzZXQuCgpgYGB7cn0Kc3Bpa2VpbiA8LSBmaWx0ZXJOQShzcGlrZWluLCBsb2dOYW1lcywgcE5BID0gMC41KQpgYGAKCiMjIyBOb3JtYWxpc2F0aW9uCgpXZSBub3JtYWxpc2UgdGhlIGRhdGEgYnkgbWVkaWFuIGNlbnRlcmluZy4KCmBgYHtyfQpub3JtTmFtZXMgIDwtIHBhc3RlMChpbnB1dE5hbWVzLCAiX25vcm0iKQpzcGlrZWluIDwtIG5vcm1hbGl6ZSgKICAgIHNwaWtlaW4sIGxvZ05hbWVzLCBuYW1lID0gbm9ybU5hbWVzLAogICAgbWV0aG9kID0gImNlbnRlci5tZWRpYW4iCikKYGBgCgpBbmQgd2UgY29uZmlybSB0aGF0IHRoZSBub3JtYWxpc2F0aW9uIHJlc3VsdGVkIGluIGEgY29ycmVjdCBhbGlnbm1lbnQKb2YgdGhlIGludGVuc2l0eSBkaXN0cmlidXRpb24gYWNyb3NzIHNhbXBsZXMuCgpgYGB7ciwgZmlnLndpZHRoPTUsIGZpZy5oZWlnaHQ9Nn0Kc3Bpa2VpblssICwgbm9ybU5hbWVzXSB8PgogICAgbG9uZ0Zvcm0oY29sdmFycyA9IGMoIk1peHR1cmUiLCAiVGVjaFJlcE1peHR1cmUiKSkgfD4KICAgIGRhdGEuZnJhbWUoKSB8PgogICAgZ2dwbG90KCkgKwogICAgYWVzKHggPSB2YWx1ZSwgY29sb3VyID0gYXMuZmFjdG9yKFRlY2hSZXBNaXh0dXJlKSwgZ3JvdXAgPSBjb2xuYW1lKSArCiAgICBnZW9tX2RlbnNpdHkoKSArCiAgICBsYWJzKHRpdGxlID0gIkludGVuc2l0eSBkaXN0cmlidXRpb24gZm9yIGVhY2ggb2JzZXJ2YXRpb25hbCB1bml0IiwKICAgICAgICAgc3VidGl0bGUgPSAiQmVmb3JlIG5vcm1hbGlzYXRpb24iLAogICAgICAgICBjb2xvdXIgPSAiVGVjaG5pY2FsIHJlcGxpY2F0ZSIpICsKICAgIGZhY2V0X2dyaWQoTWl4dHVyZSB+IC4pICsKICAgIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iKQpgYGAKCiMjIyBTdW1tYXJpc2F0aW9uCgpCZWxvdywgd2UgaWxsdXN0cmF0ZSB0aGUgY2hhbGxlbmdlcyBvZiBzdW1tYXJpc2luZyBUTVQgZGF0YSB1c2luZyBvbmUKb2YgdGhlIFVQUyBwcm90ZWlucyBpbiBNaXh0dXJlIDEgKHNlcGFyYXRpbmcgdGhlIGRhdGEgZm9yIGVhY2gKdGVjaG5pY2FsIHJlcGxpY2F0ZSkuIFdlIGFsc28gZm9jdXMgb24gdGhlIDAuMTI1eCBhbmQgdGhlIDF4IHNwaWtlLWluCmNvbmRpdGlvbnMuIFdlIGlsbHVzdHJhdGUgdGhlIGRpZmZlcmVudCBwZXB0aWRlIGlvbnMgb24gdGhlIHggYXhpcyBhbmQKcGxvdCB0aGUgbG9nMiBub3JtYWxpc2VkIGludGVuc2l0aWVzIGFjcm9zcyBzYW1wbGVzIG9uIHkgYXhpcy4gQWxsIHRoZQpwb2ludHMgYmVsb25naW5nIHRvIHRoZSBzYW1lIHNhbXBsZSBhcmUgbGlua2VkIHRocm91Z2ggYSBncmV5IGxpbmUuCgpgYGB7ciwgZWNobyA9IEZBTFNFLCBmaWcuaGVpZ2h0PTYsIGZpZy53aWR0aD02fQpzcGlrZWluWywgLCBub3JtTmFtZXNdIHw+CiAgICBmaWx0ZXJGZWF0dXJlcyh+IFByb3RlaW4uQWNjZXNzaW9ucyA9PSAiUDAyNzg3dXBzIikgfD4gCiAgICBsb25nRm9ybShjb2x2YXJzID0gY29sbmFtZXMoY29sRGF0YShzcGlrZWluKSksIHJvd3ZhcnMgPSAiaW9uSUQiKSB8PiAKICAgIGRhdGEuZnJhbWUoKSB8PiAKICAgIGZpbHRlcihNaXh0dXJlID09ICJNaXh0dXJlMSIgJiBDb25kaXRpb24gJWluJSBjKCIxIiwgIjAuMTI1IikpIHw+IAogICAgcmVuYW1lKFJlcGxpY2F0ZSA9ICJUZWNoUmVwTWl4dHVyZSIpIHw+IAogICAgbXV0YXRlKGNvbG5hbWUgPSBmYWN0b3IoY29sbmFtZSwgbGV2ZWxzID0gdW5pcXVlKGNvbG5hbWVbb3JkZXIoQ29uZGl0aW9uKV0pKSkgfD4gCiAgICBnZ3Bsb3QoKSArCiAgICBhZXMoeCA9IGlvbklELCAKICAgICAgICB5ID0gdmFsdWUsCiAgICAgICAgZ3JvdXAgPSBjb2xuYW1lKSArCiAgICBnZW9tX2xpbmUobGluZXdpZHRoID0gMC4xKSArCiAgICBnZW9tX3BvaW50KGFlcyhmaWxsID0gQ29uZGl0aW9uKSwgc2l6ZSA9IDMsIHNoYXBlID0gMjEpICsKICAgIGZhY2V0X2dyaWQoUmVwbGljYXRlIH4gLiwgbGFiZWxsZXIgPSBsYWJlbF9ib3RoKSArCiAgICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCBoanVzdCA9IDEsIHZqdXMgPSAwLjUpLAogICAgICAgICAgbGVnZW5kLnBvc2l0aW9uID0gInRvcCIpICsKICAgIGxhYnModGl0bGUgPSAiUDAyNzg3dXBzIChpbiBNaXh0dXJlIDEpIiwKICAgICAgICAgeCA9ICJQZXB0aWRlIGlvbiIsIHkgPSAibG9nMihub3JtIEludGVuc2l0eSkiKQpgYGAKCldlIHNlZSB0aGF0IHRoZSBzYW1lIGNoYWxsZW5nZXMgb2JzZXJ2ZWQgZm9yIExGUSBkYXRhIGFsc28gYXBwbHkgdG8gClRNVCBkYXRhLiBCcmllZmx5OgoKMS4gRGF0YSBmb3IgYSBwcm90ZWluIGNhbiBjb25zaXN0IG9mIG1hbnkgcGVwdGlkZSBpb25zLiAKMi4gUGVwdGlkZSBpb25zIGhhdmUgZGlmZmVyZW50IGludGVuc2l0eSBiYXNlbGluZXMuCjMuIFRoZXJlIGlzIHN0cm9uZyBtaXNzaW5nbmVzcyBhY3Jvc3MgcnVucyAoY29tcGFyZSBwb2ludHMgYmV0d2VlbiAKICAgcmVwbGljYXRlcyksIGJ1dCB0aGUgbWlzc2luZ25lc3MgaXMgbWl0aWdhdGVkIHdpdGhpbiBydW5zIChjb21wYXJlCiAgIHBvaW50cyB3aXRoaW4gcmVwbGljYXRlcy4gTm90ZSB0aGF0IHRoZSBkYXRhIHBvaW50cyBmcm9tIG9uZQogICBwZXB0aWRlIGlvbiBpbiBvbmUgcmVwbGljYXRlIGhhcyBiZWVuIGV4dHJhY3RlZCBmcm9tIGEgc2luZ2xlIE1TMgogICBzcGVjdHJ1bS4pLgo0LiBTdWJ0bGUgaW50ZW5zaXR5IHNoaWZ0cyBmb3IgdGhlIHNhbWUgcGVwdGlkZSBhY3Jvc3MgZGlmZmVyZW50CiAgIHJlcGxpY2F0ZXMsIGNhbGxlZCBzcGVjdHJ1bSBlZmZlY3RzLCBhcmUgY2F1c2VkIGJ5IHNtYWxsIHJ1bi10by1ydW4KICAgZmx1Y3R1YXRpb25zLgo1LiBQcmVzZW5jZSBvZiBvdXRsaWVycy4gRm9yIGluc3RhbmNlLCB0aGUgZmlyc3QgcGVwdGlkZSBpb24gCiAgIGRvZXNuJ3Qgc2hvdyB0aGUgc2FtZSBjaGFuZ2UgaW4gaW50ZW5zaXR5IGJldHdlZW4gY29uZGl0aW9ucwogICBjb21wYXJlZCB0byBtYWpvcml0eSBvZiB0aGUgcGVwdGlkZXMuIAoKSGVyZSwgd2Ugc3VtbWFyaXNlIHRoZSBpb24tbGV2ZWwgZGF0YSBpbnRvIHByb3RlaW4gaW50ZW5zaXRpZXMKdGhyb3VnaCB0aGUgbWVkaWFuIHBvbGlzaCBhcHByb2FjaCwgd2hpY2ggYWx0ZXJuYXRlbHkgcmVtb3ZlcyB0aGUKcGVwdGlkZS1pb25zIGFuZCB0aGUgc2FtcGxlIG1lZGlhbnMgZnJvbSB0aGUgZGF0YSB1bnRpbCB0aGUgc3VtbWFyaWVzCnN0YWJpbGlzZS4gUmVtb3ZpbmcgdGhlIHBlcHRpZGUtaW9uIG1lZGlhbnMgd2lsbCBzb2x2ZSBpc3N1ZSAyLiBhcyBpdApyZW1vdmVzIHRoZSBpb24tc3BlY2lmaWMgZWZmZWN0cy4gVXNpbmcgdGhlIG1lZGlhbiBpbnN0ZWFkIG9mCnRoZSBtZWFuIHdpbGwgc29sdmUgaXNzdWUgNS4gTm90ZSB0aGF0IHdlIHBlcmZvcm0gc3VtbWFyaXNhdGlvbiBmb3IKKiplYWNoIHJ1biBzZXBhcmF0ZWx5KiosIGhlbmNlIHRoZSBpb24gZWZmZWN0IHdpbGwgYmUgZGlmZmVyZW50IGZvcgplYWNoIHJ1biwgZWZmZWN0aXZlbHkgYWxsb3dpbmcgZm9yIGEgc3BlY3RydW0gZWZmZWN0IGFuZCBzb2x2aW5nCmlzc3VlIDQuCgpgYGB7ciwgd2FybmluZz1GQUxTRX0Kc3VtbU5hbWVzIDwtIHBhc3RlMChpbnB1dE5hbWVzLCAiX3Byb3RlaW5zIikKKHNwaWtlaW4gPC0gYWdncmVnYXRlRmVhdHVyZXMoCiAgICBzcGlrZWluLCBpID0gbm9ybU5hbWVzLCAgbmFtZSA9IHN1bW1OYW1lcywKICAgIGZjb2wgPSAiUHJvdGVpbi5BY2Nlc3Npb25zIiwgZnVuID0gTXNDb3JlVXRpbHM6Om1lZGlhblBvbGlzaCwKICAgIG5hLnJtID0gVFJVRQopKQpgYGAKClVwIHRvIG5vdywgdGhlIGRhdGEgZnJvbSBkaWZmZXJlbnQgcnVucyB3ZXJlIGtlcHQgaW4gc2VwYXJhdGUgYXNzYXlzLgpXZSBjYW4gbm93IGpvaW4gdGhlIG5vcm1hbGlzZWQgc2V0cyBpbnRvIGFuIGBwcm90ZWluc2Agc2V0IHVzaW5nCmBqb2luQXNzYXlzKClgLiBTZXRzIGFyZSBqb2luZWQgYnkgc3RhY2tpbmcgdGhlIGNvbHVtbnMgKHNhbXBsZXMpIGluIGEKbWF0cml4IGFuZCByb3dzIChmZWF0dXJlcykgYXJlIG1hdGNoZWQgYWNjb3JkaW5nIHRvIHRoZSByb3cgbmFtZXMKKGkuZS4gdGhlIHByb3RlaW4gaWRlbnRpZmllcnMpLgoKYGBge3J9CnNwaWtlaW4gPC0gam9pbkFzc2F5cyggCiAgICBzcGlrZWluLCBzdW1tTmFtZXMsICJwcm90ZWlucyIKKQpgYGAKCiMjIERhdGEgZXhwbG9yYXRpb24KCldlIHBlcmZvcm0gZGF0YSBleHBsb3JhdGlvbiB1c2luZyBNRFMuCgpgYGB7cn0KbGlicmFyeSgic2NhdGVyIikKc2UgPC0gZ2V0V2l0aENvbERhdGEoc3Bpa2VpbiwgInByb3RlaW5zIikKc2UgPC0gcnVuTURTKGFzKHNlLCAiU2luZ2xlQ2VsbEV4cGVyaW1lbnQiKSwgZXhwcnNfdmFsdWVzID0gMSkKcGxvdE1EUyhzZSwgY29sb3VyX2J5ID0gIkNvbmRpdGlvbiIpICsKICBwbG90TURTKHNlLCBjb2xvdXJfYnkgPSAiUnVuIikgKyAKICBwbG90TURTKHNlLCBjb2xvdXJfYnkgPSAiTWl4dHVyZSIpCmBgYAoKVGhlcmUgaXMgYSBzdHJvbmcgcnVuLXRvLXJ1biBlZmZlY3QsIHdoaWNoIGlzIHBhcnRseSBleHBsYWluZWQgYnkgYQptaXh0dXJlIGVmZmVjdCBhcyB0aGUgcnVucyBmcm9tIHRoZSBzYW1lIG1peHR1cmUgdGVuZCB0byBiZSBjbG9zZXIKdGhhbiBydW5zIGZyb20gZGlmZmVyZW50IG1peHR1cmVzLiBUaGUgY29uZGl0aW9uIGVmZmVjdCBpcyBtdWNoIG1vcmUKc3VidGxlIHRvIGZpbmQsIHByb2JhYmx5IGJlY2F1c2Ugd2Uga25vdyBvbmx5IGEgZmV3IFVQUyBwcm90ZWlucyB3ZXJlCnNwaWtlZCBpbiB3aGlsZSB0aGUgbWFqb3JpdHkgb2YgdGhlIGJhY2tncm91bmQgcHJvdGVpbnMgYXJlIHVuY2hhbmdlZC4KV2UgY2FuIHNlZSB0aGF0IG5vcm1hbGlzYXRpb24gYW5kIHN1bW1hcmlzYXRpb24gYWxvbmUgYXJlIG5vdApzdWZmaWNpZW50IHRvIGNvcnJlY3QgZm9yIHRoZXNlIHVud2FudGVkIGVmZmVjdHMuIFdlIHdpbGwgdGFrZSBjYXJlIG9mCnRoZXNlIGVmZmVjdHMgZHVyaW5nIHRoZSBkYXRhIG1vZGVsbGluZy4KCiMjIERhdGEgbW9kZWxsaW5nCgpQcm90ZW9taWNzIGRhdGEgY29udGFpbiBzZXZlcmFsIHNvdXJjZXMgb2YgdmFyaWF0aW9uIHRoYXQgbmVlZCB0byBiZQphY2NvdW50ZWQgZm9yIGJ5IHRoZSBtb2RlbC4gV2Ugd2lsbCBidWlsZCB0aGUgbW9kZWwgYnkgcHJvZ3Jlc3NpdmVseQphZGRpbmcgdGhlIGRpZmZlcmVudCBzb3VyY2VzIG9mIHZhcmlhdGlvbi4KCiMjIyBFZmZlY3Qgb2YgdHJlYXRtZW50IG9mIGludGVyZXN0CgpXZSBtb2RlbCB0aGUgc291cmNlIG9mIHZhcmlhdGlvbiBpbmR1Y2VkIGJ5IHRoZSBleHBlcmltZW50YWwgdHJlYXRtZW50Cm9mIGludGVyZXN0IGFzIGEgKipmaXhlZCBlZmZlY3QqKiwgd2hpY2ggd2UgY29uc2lkZXIgbm9uLXJhbmRvbSwgaS5lLgp0aGUgdHJlYXRtZW50IGVmZmVjdCBpcyBhc3N1bWVkIHRvIGJlIHRoZSBzYW1lIGluIHJlcGVhdGVkCmV4cGVyaW1lbnRzLCBidXQgaXQgaXMgdW5rbm93biBhbmQgaGFzIHRvIGJlIGVzdGltYXRlZC4gV2hlbiBtb2RlbGxpbmcKYSB0eXBpY2FsIGxhYmVsLWZyZWUgZXhwZXJpbWVudCBhdCB0aGUgcHJvdGVpbiBsZXZlbCwgdGhlIG1vZGVsIGJvaWxzCmRvd24gdG8gYSBsaW5lYXIgbW9kZWwsIGFnYWluIHdlIHN1cHByZXNzIHRoZSBpbmRleCBmb3IgcHJvdGVpbjoKCiQkCnlfciA9IFxtYXRoYmZ7eH1eVF9yIFxib2xkc3ltYm9se1xiZXRhfSArIFxlcHNpbG9uX3IsCiQkCgp3aXRoICR5X3IkIHRoZSAkXGxvZ18yJC1ub3JtYWxpemVkIHByb3RlaW4gaW50ZW5zaXRpZXMgaW4gcnVuIHI7CiRcbWF0aGJme3h9X3IkIGEgdmVjdG9yIHdpdGggdGhlIGNvdmFyaWF0ZSBwYXR0ZXJuIGZvciB0aGUgc2FtcGxlIGluCnJ1biAkciQgZW5jb2RpbmcgdGhlIGludGVyY2VwdCwgdHJlYXRtZW50LCBwb3RlbnRpYWwgYmF0Y2ggZWZmZWN0cyBhbmQKY29uZm91bmRlcnM7ICRcYm9sZHN5bWJvbHtcYmV0YX0kIHRoZSB2ZWN0b3Igb2YgcGFyYW1ldGVycyB0aGF0IG1vZGVsCnRoZSBhc3NvY2lhdGlvbiBiZXR3ZWVuIHRoZSBjb3ZhcmlhdGVzIGFuZCB0aGUgb3V0Y29tZTsgYW5kCiRcZXBzaWxvbl9yJCB0aGUgcmVzaWR1YWxzIHJlZmxlY3RpbmcgdmFyaWF0aW9uIHRoYXQgaXMgbm90IGNhcHR1cmVkCmJ5IHRoZSBmaXhlZCBlZmZlY3RzLiBOb3RlIHRoYXQgJFxtYXRoYmZ7eH1fciQgYWxsb3dzIGZvciBhIGZsZXhpYmxlCnBhcmFtZXRlcml6YXRpb24gb2YgdGhlIHRyZWF0bWVudCBiZXlvbmQgYSBzaW5nbGUgY292YXJpYXRlLCBpLmUuCmluY2x1ZGluZyBhIDEgZm9yIHRoZSBpbnRlcmNlcHQsIGNvbnRpbnVvdXMgYW5kIGNhdGVnb3JpY2FsIHZhcmlhYmxlcwphcyB3ZWxsIGFzIHRoZWlyIGludGVyYWN0aW9ucy4gRm9yIGFsbCBtb2RlbHMgY29uc2lkZXJlZCBpbiB0aGlzIHdvcmssCndlIGFzc3VtZSB0aGUgcmVzaWR1YWxzIHRvIGJlIGluZGVwZW5kZW50IGFuZCBpZGVudGljYWxseSBkaXN0cmlidXRlZAooaS5pLmQpIGFjY29yZGluZyB0byBhIG5vcm1hbCBkaXN0cmlidXRpb24gd2l0aCB6ZXJvIG1lYW4gYW5kIGNvbnN0YW50CnZhcmlhbmNlLCBpLmUuICRcZXBzaWxvbl97cn0gXHNpbSBOKDAsXHNpZ21hX1xlcHNpbG9uXjIpJCwgdGhhdCBjYW4KZGlmZmVyIGZyb20gcHJvdGVpbiB0byBwcm90ZWluLgoKTm93IHdlIGRlZmluZWQgYSBtb2RlbCwgd2UgbXVzdCBlc3RpbWF0ZSBmcm9tIHRoZSBkYXRhLiBVc2luZyAKYG1zcXJvYigpYCwgdGhlIG1vZGVsIHRyYW5zbGF0ZXMgaW50byB0aGUgZm9sbG93aW5nIGNvZGU6CgpgYGB7ciBydW5fbXNxcm9iX3Byb3RlaW4xLCBldmFsID0gRkFMU0V9CnNwaWtlaW4gPC0gbXNxcm9iKAogICAgc3Bpa2VpbiwgIGkgPSAicHJvdGVpbnMiLAogICAgZm9ybXVsYSA9IH4gIENvbmRpdGlvbiAjIyBmaXhlZCBlZmZlY3QgZm9yIGV4cGVyaW1lbnRhbCBjb25kaXRpb24KKQpgYGAKClRoaXMgbW9kZWwgaXMgaG93ZXZlciBpbmNvbXBsZXRlIGFuZCByZWx5aW5nIG9uIGl0cyByZXN1bHRzIHdvdWxkIGxlYWQKdG8gaW5jb3JyZWN0IGNvbmNsdXNpb25zIGFzIHdlIGFyZSBzdGlsbCBtaXNzaW5nIGltcG9ydGFudCBzb3VyY2Ugb2YgCnZhcmlhdGlvbi4gV2UgZGlkIHRoZXJlZm9yZSBub3QgcnVuIHRoZSBtb2RlbGxpbmcgY29tbWFuZCBhbmQgd2lsbApleHBhbmQgdGhlIG1vZGVsLgoKIyMjIEVmZmVjdCBvZiBUTVQgbGFiZWwgYW5kIHJ1bgoKQXMgbGFiZWwtZnJlZSBleHBlcmltZW50cyBjb250YWluIG9ubHkgYSBzaW5nbGUgc2FtcGxlIHBlciBydW4sCnJ1bi1zcGVjaWZpYyBlZmZlY3RzIHdpbGwgYmUgYWJzb3JiZWQgaW4gdGhlIHJlc2lkdWFscy4gSG93ZXZlciwgdGhlCmRhdGEgYW5hbHlzaXMgb2YgbGFiZWxlZCBleHBlcmltZW50cywgZS5nLiB1c2luZyBUTVQgbXVsdGlwbGV4aW5nLAppbnZvbHZpbmcgbXVsdGlwbGUgTVMgcnVucyBoYXMgdG8gYWNjb3VudCBmb3IgcnVuLSBhbmQgbGFiZWwtc3BlY2lmaWMKZWZmZWN0cywgZXhwbGljaXRseS4gSWYgYWxsIHRyZWF0bWVudHMgYXJlIHByZXNlbnQgaW4gZWFjaCBydW4sIGFuZCBpZgpUTVQgbGFiZWwgc3dhcHMgYXJlIHBlcmZvcm1lZCBzbyBhcyB0byBhdm9pZCBjb25mb3VuZGluZyBiZXR3ZWVuIGxhYmVsCmFuZCB0cmVhdG1lbnQsIHRoZW4gdGhlIG1vZGVsIHBhcmFtZXRlcnMgY2FuIGJlIGVzdGltYXRlZCB1c2luZyBmaXhlZApsYWJlbCBhbmQgcnVuIGVmZmVjdHMuIEluZGVlZCwgZm9yIHRoZXNlIGRlc2lnbnMgcnVuIGFjdHMgYXMgYQpibG9ja2luZyB2YXJpYWJsZSBhcyBhbGwgdHJlYXRtZW50IGVmZmVjdHMgY2FuIGJlIGVzdGltYXRlZCB3aXRoaW4KZWFjaCBydW4uCgpIb3dldmVyLCBmb3IgbW9yZSBjb21wbGV4IGRlc2lnbnMgdGhpcyBpcyBubyBsb25nZXIgcG9zc2libGUgYW5kIHRoZQp1bmNlcnRhaW50eSBpbiB0aGUgZXN0aW1hdGlvbiBvZiB0aGUgbWVhbiBtb2RlbCBwYXJhbWV0ZXJzIGNhbiBpbnZvbHZlCmJvdGggd2l0aGluIGFuZCBiZXR3ZWVuIFRNVCBsYWJlbCBhbmQgcnVuIHZhcmlhYmlsaXR5LiBGb3IgdGhlc2UgZGVzaWducwp3ZSBjYW4gcmVzb3J0IHRvIG1peGVkIG1vZGVscyB3aGVyZSB0aGUgbGFiZWwgYW5kIHJ1biBlZmZlY3QgYXJlCm1vZGVsbGVkIHVzaW5nICoqcmFuZG9tIGVmZmVjdHMqKiwgaS5lLiB0aGV5IGFyZSBjb25zaWRlcmVkIGFzIGEgcmFuZG9tCnNhbXBsZSBmcm9tIHRoZSBwb3B1bGF0aW9uIG9mIGFsbCBwb3NzaWJsZSBydW5zIGFuZCBUTVQgbGFiZWxzLAp3aGljaCBhcmUgYXNzdW1lZCB0byBiZSBpLmkuZCBub3JtYWxseSBkaXN0cmlidXRlZCB3aXRoIG1lYW4gMCBhbmQKY29uc3RhbnQgdmFyaWFuY2UsICAkdV9yIFxzaW0gTigwLFxzaWdtYV57MixcdGV4dHtydW59fSkkCigkdV97bGFiZWx9IFxzaW0gTigwLFxzaWdtYV57MixcdGV4dHtsYWJlbH19KSQpLiBUaGUgdXNlIG9mIHJhbmRvbQplZmZlY3RzIHRodXMgbW9kZWxzIHRoZSBjb3JyZWxhdGlvbiBpbiB0aGUgZGF0YSwgZXhwbGljaXRseS4gSW5kZWVkLApwcm90ZWluIGludGVuc2l0aWVzIHRoYXQgYXJlIG1lYXN1cmVkIHdpdGhpbiB0aGUgc2FtZSBydW4vbGFiZWwKd2lsbCBiZSBtb3JlIHNpbWlsYXIgdGhhbiBwcm90ZWluIGludGVuc2l0aWVzIGJldHdlZW4gcnVucy9sYWJlbHMuCgpIZW5jZSwgdGhlIG1vZGVsIGlzIGV4dGVuZGVkIHRvOgoKJCQKeV97cmx9ID0KXG1hdGhiZnt4fV5UX3tybH0gXGJvbGRzeW1ib2x7XGJldGF9ICsgdV9sXlx0ZXh0e2xhYmVsfSArIHVfcl5cdGV4dHtydW59ICsKXGVwc2lsb25fe3JsfQokJAp3aXRoICR5X3tybH0kIHRoZSBub3JtYWxpc2VkICRcbG9nXzIkIHByb3RlaW4gaW50ZW5zaXRpZXMgaW4gcnVuICRyJAphbmQgbGFiZWwgJGwkLCAkdV9sXlx0ZXh0e2xhYmVsfSQgdGhlIGVmZmVjdCBpbnRyb2R1Y2VkIGJ5CnRoZSBsYWJlbCAkbCQsIGFuZCAkdV9yXlx0ZXh0e3J1bn0kIHRoZSBlZmZlY3QgZm9yIE1TIHJ1bgokciQuCgpUaGlzIHRyYW5zbGF0ZXMgaW4gdGhlIGZvbGxvd2luZyBjb2RlOgoKYGBge3IgcnVuX21zcXJvYl9wcm90ZWluMiwgZXZhbD1GQUxTRX0Kc3Bpa2VpbiA8LSBtc3Fyb2IoCiAgICBzcGlrZWluLCAgaSA9ICJwcm90ZWlucyIsCiAgICBmb3JtdWxhID0gfiAgQ29uZGl0aW9uICsgIyMgZml4ZWQgZWZmZWN0IGZvciBleHBlcmltZW50YWwgY29uZGl0aW9uCiAgICAgICAgKDEgfCBMYWJlbCkgKyAjIyByYW5kb20gZWZmZWN0IGZvciBsYWJlbAogICAgICAgICgxIHwgUnVuKSAjIyByYW5kb20gZWZmZWN0IGZvciBNUyBydW4KICAgICkKYGBgCgpUaGlzIG1vZGVsIGlzIHN0aWxsIGluY29tcGxldGUgYW5kIGlzIG5vIGV4ZWN1dGVkIGFzIHdlIHN0aWxsIG5lZWQgdG8gCmFjY291bnQgdGhhdCBlYWNoIG1peHR1cmUgaGFzIGJlZW4gcmVwbGljYXRlZCB0aHJlZSB0aW1lcy4KCiMjIyBFZmZlY3Qgb2YgcmVwbGljYXRpb24KClNvbWUgZXhwZXJpbWVudHMgYWxzbyBpbmNsdWRlIHRlY2huaWNhbCByZXBsaWNhdGlvbiB3aGVyZSBhIFRNVAptaXh0dXJlIGNhbiBiZSBhY3F1aXJlZCBtdWx0aXBsZSB0aW1lcy4gVGhpcyBhZ2FpbiB3aWxsIGluZHVjZQpjb3JyZWxhdGlvbi4gSW5kZWVkLCBwcm90ZWluIGludGVuc2l0aWVzIGZyb20gdGhlIHNhbWUgbWl4dHVyZSB3aWxsIGJlCm1vcmUgYWxpa2UgdGhhbiB0aG9zZSBvZiBkaWZmZXJlbnQgbWl4dHVyZXMuIEhlbmNlLCB3ZSBhbHNvIGluY2x1ZGUgYQpyYW5kb20gZWZmZWN0IHRvIGFjY291bnQgZm9yIHRoaXMgcHNldWRvLXJlcGxpY2F0aW9uLCBpLmUuCiR1Xlx0ZXh0e21peH1fbSBcc2ltIE4oMCwgXHNpZ21hXnsyLFx0ZXh0e21peH19KSQuIFRoZSBtb2RlbCB0aHVzCmV4dGVuZHMgdG86CgokJAp5X3tybG19ID0KXG1hdGhiZnt4fV5UX3tybG19IFxib2xkc3ltYm9se1xiZXRhfSArICB1X3tsfV5cdGV4dHtsYWJlbH0gKwp1X3JeXHRleHR7cnVufSArIHVfbV5cdGV4dHttaXh9ICsgXGVwc2lsb25fe3JsbX0KJCQKCndpdGggJG0kIHRoZSBpbmRleCBmb3IgbWl4dHVyZS4KClRoZSBtb2RlbCB0cmFuc2xhdGVzIHRvIHRoZSBmb2xsb3dpbmcgY29kZToKCmBgYHtyIHJ1bl9tc3Fyb2JfcHJvdGVpbjMsIGNhY2hlPVRSVUUsIHdhcm5pbmc9RkFMU0V9CnNwaWtlaW4gPC0gbXNxcm9iKAogICAgc3Bpa2VpbiwgIGkgPSAicHJvdGVpbnMiLAogICAgZm9ybXVsYSA9IH4gIENvbmRpdGlvbiArICMjIGZpeGVkIGVmZmVjdCBmb3IgZXhwZXJpbWVudGFsIGNvbmRpdGlvbgogICAgICAgICgxIHwgTGFiZWwpICsgIyMgcmFuZG9tIGVmZmVjdCBmb3IgbGFiZWwKICAgICAgICAoMSB8IFJ1bikgKyAjIyByYW5kb20gZWZmZWN0IGZvciBNUyBydW4KICAgICAgICAoMSB8IE1peHR1cmUpICMjIHJhbmRvbSBlZmZlY3QgZm9yIG1peHR1cmUKKQpgYGAKClRoaXMgbW9kZWwgcHJvdmlkZXMgYSBzZW5zaWJsZSByZXByZXNlbnRhdGlvbiBvZiB0aGUgc291cmNlcyBvZgp2YXJpYXRpb24gaW4gdGhlIGRhdGEuIAoKIyMgU3RhdGlzdGljYWwgaW5mZXJlbmNlCgpXZSBjYW4gbm93IGNvbnZlcnQgdGhlIGJpb2xvZ2ljYWwgcXVlc3Rpb24gImRvZXMgdGhlIHNwaWtlLWluCmNvbmRpdGlvbiBhZmZlY3QgdGhlIHByb3RlaW4gaW50ZW5zaXRpZXM/IiBpbnRvIGEgc3RhdGlzdGljYWwKaHlwb3RoZXNpcy4gSW4gb3RoZXIgd29yZHMsIHdlIG5lZWQgdG8gZGVmaW5lIHRoZSBjb250cmFzdC4KV2UgcGxvdCBhbiBvdmVydmlldyBvZiB0aGUgbW9kZWwgcGFyYW1ldGVycy4KCmBgYHtyfQpsaWJyYXJ5KCJFeHBsb3JlTW9kZWxNYXRyaXgiKQp2ZCA8LSBWaXN1YWxpemVEZXNpZ24oCiAgICBzYW1wbGVEYXRhID0gIGNvbERhdGEoc3Bpa2VpbiksCiAgICBkZXNpZ25Gb3JtdWxhID0gfiBDb25kaXRpb24sCiAgICB0ZXh0U2l6ZUZpdHRlZCA9IDQKKQp2ZCRwbG90bGlzdApgYGAKCiMjIyBIeXBvdGhlc2lzIHRlc3RpbmcKClRoZSBhdmVyYWdlIGRpZmZlcmVuY2UgaW50ZW5zaXR5IGJldHdlZW4gdGhlIDF4IGFuZCB0aGUgMC41eApjb25kaXRpb25zIGlzIHByb3ZpZGVkIGJ5IHRoZSBjb250cmFzdCBgQ29uZGl0aW9uMSAtIENvbmRpdGlvbjAuNWAuCgpgYGB7cn0KaHlwb3RoZXNpcyA8LSAiQ29uZGl0aW9uMSAtIENvbmRpdGlvbjAuNSA9IDAiCkwgPC0gbWFrZUNvbnRyYXN0KAogICAgaHlwb3RoZXNpcywKICAgIHBhcmFtZXRlck5hbWVzID0gYygiQ29uZGl0aW9uMSIsICJDb25kaXRpb24wLjUiKQopCmBgYAoKV2UgdGVzdCBvdXIgbnVsbCBoeXBvdGhlc2VzIHVzaW5nIGBoeXBvdGhlc2lzVGVzdCgpYCBhbmQgdGhlIGVzdGltYXRlZAptb2RlbCBzdG9yZWQgaW4gYHByb3RlaW5zYC4KCmBgYHtyfQpzcGlrZWluIDwtIGh5cG90aGVzaXNUZXN0KHNwaWtlaW4sIGkgPSAicHJvdGVpbnMiLCBjb250cmFzdCA9IEwpCihpbmZlcmVuY2UgPC0gcm93RGF0YShzcGlrZWluW1sicHJvdGVpbnMiXV0pWywgY29sbmFtZXMoTCldKQpgYGAKCiMjIyBWb2xjYW5vIHBsb3RzCgpXZSBnZW5lcmF0ZSBhIHZvbGNhbm8gcGxvdCB3aXRoIGFsbCB0aGUgcmVzdWx0cyBmb3IgdGhlIDEtIDAuNXggCmNvbXBhcmlzb24uIAoKYGBge3J9CmluZmVyZW5jZSRQcm90ZWluIDwtIHJvd25hbWVzKGluZmVyZW5jZSkKaW5mZXJlbmNlJGlzVXBzIDwtIGdyZXBsKCJ1cHMiLCBpbmZlcmVuY2UkUHJvdGVpbikKZ2dwbG90KGluZmVyZW5jZSkgKwogICAgYWVzKHggPSBsb2dGQywKICAgICAgICB5ID0gLWxvZzEwKHB2YWwpLAogICAgICAgIGNvbG9yID0gaXNVcHMpICsKICAgIGdlb21fcG9pbnQoKSArCiAgICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAtbG9nMTAoaW5mZXJlbmNlJHB2YWxbd2hpY2gubWluKGFicyhpbmZlcmVuY2UkYWRqUHZhbCAtIDAuMDUpKV0gKSkgKwogICAgc2NhbGVfY29sb3JfbWFudWFsKAogICAgICB2YWx1ZXMgPSBjKCJncmV5MjAiLCAiZmlyZWJyaWNrIiksIG5hbWUgPSAiIiwKICAgICAgbGFiZWxzID0gYygiSGVMQSBiYWNrZ3JvdW5kIiwgIlVQUyBzdGFuZGFyZCIpCiAgICApICsKICAgIGdndGl0bGUoIlN0YXRpc3RpY2FsIGluZmVyZW5jZSB0aGUgMXggLSAwLjV4IGNvbXBhcmlzb24iKSAKYGBgCgojIyMgRm9sZCBjaGFuZ2UgZGlzdHJpYnV0aW9ucwoKQXMgdGhpcyBpcyBhIHNwaWtlLWluIHN0dWR5IHdpdGgga25vd24gZ3JvdW5kIHRydXRoLCB3ZSBjYW4gYWxzbyBwbG90CnRoZSBsb2cyIGZvbGQgY2hhbmdlIGRpc3RyaWJ1dGlvbnMgYWdhaW5zdCB0aGUgZXhwZWN0ZWQgdmFsdWVzLCBpbgp0aGlzIGNhc2UgMCBmb3IgdGhlIEhlTGEgcHJvdGVpbnMgYW5kIDEgZm9yIHRoZSBVUFMgc3RhbmRhcmRzLgoKYGBge3J9CmdncGxvdChpbmZlcmVuY2UpICsKICAgIGFlcyh5ID0gbG9nRkMsCiAgICAgICAgeCA9IGlzVXBzLAogICAgICAgIGNvbG91ciA9IGlzVXBzKSArCiAgICBnZW9tX2JveHBsb3QoKSArCiAgICBzY2FsZV9jb2xvcl9tYW51YWwoCiAgICAgICAgdmFsdWVzID0gYygiZ3JleTIwIiwgImZpcmVicmljayIpLCBuYW1lID0gIiIsCiAgICAgICAgbGFiZWxzID0gYygiSGVMQSBiYWNrZ3JvdW5kIiwgIlVQUyBzdGFuZGFyZCIpCiAgICApICsKICAgIGdndGl0bGUoIkRpc3RyaWJ1dGlvbiBvZiB0aGUgbG9nMiBmb2xkIGNoYW5nZXMiKQpgYGAKClRoZSBlc3RpbWF0ZWQgbG9nMiBmb2xkIGNoYW5nZXMgZm9yIEhlTGEgcHJvdGVpbnMgYXJlIGNsb3NlbHkKZGlzdHJpYnV0ZWQgYXJvdW5kIDAsIGFzIGV4cGVjdGVkLiBsb2cyIGZvbGQgY2hhbmdlcyBmb3IgVVBTIHN0YW5kYXJkCnByb3RlaW5zIGFyZSBkaXN0cmlidXRlZCB0b3dhcmQgMSwgYnV0IGl0IGhhcyBiZWVuIHVuZGVyZXN0aW1hdGVkIGR1ZQp0byBpb24gc3VwcHJlc3Npb24gZWZmZWN0cyB0aGF0IGFyZSBjaGFyYWN0ZXJpc3RpYyBvZiBkYXRhIHNldCB3aXRoCmhpZ2ggc3Bpa2UtaW4gY29uY2VudHJhdGlvbnMuCgojIyBFeGVyY2lzZTogdGhlIG1vdXNlIGRpZXQgdXNlIGNhc2UKCkxldCdzIHJlcGVhdCB0aGlzIGFuYWx5c2lzLCBidXQgb24gYSByZWFsLWxpZmUgcHJvYmxlbSwgaW5zdGVhZCBvZiBhCnN5bnRoZXRpYyBzcGlrZS1pbiBzdHVkeS4KCiMjIyBFeHBlcmltZW50YWwgY29udGV4dAoKVGhlIGRhdGEgdXNlZCBpbiB0aGlzIGV4ZXJjaXNlIGhhcyBiZWVuIHB1Ymxpc2hlZCBieSBAUGx1YmVsbDIwMTctdGgKKFBYRDAwNTk1MykuIFRoZSBvYmplY3RpdmUgb2YgdGhlIGV4cGVyaW1lbnQgd2FzIHRvIGV4cGxvcmUgdGhlIGltcGFjdApvZiBsb3ctZmF0IGFuZCBoaWdoLWZhdCBkaWV0cyBvbiB0aGUgcHJvdGVvbWljIGNvbnRlbnQgb2YgYWRpcG9zZQp0aXNzdWUgaW4gbWljZS4gSXQgYWxzbyBhc3Nlc3NlcyB3aGV0aGVyIHRoZSBkdXJhdGlvbiBvZiB0aGUgZGlldCBtYXkKaW1wYWN0IHRoZSByZXN1bHRzLiBUaGUgYXV0aG9ycyBhc3NpZ25lZCB0d2VudHkgbWljZSBpbnRvIGZvdXIgZ3JvdXBzCig1IG1pY2UgcGVyIGdyb3VwKSBiYXNlZCBvbiB0aGVpciBkaWV0LCBlaXRoZXIgbG93LWZhdCAoTEYpIG9yCmhpZ2gtZmF0IChIRiksIGFuZCB0aGUgZHVyYXRpb24gb2YgdGhlIGRpZXQsIHdoaWNoIHdhcyBjbGFzc2lmaWVkIGFzCnNob3J0ICg4IHdlZWtzKSBvciBsb25nICgxOCB3ZWVrcykuIFNhbXBsZXMgZnJvbSB0aGUgZXBpZGlkeW1hbAphZGlwb3NlIHRpc3N1ZSB3ZXJlIGV4dHJhY3RlZCBmcm9tIGVhY2ggbWljZS4gVGhlIHNhbXBsZXMgd2VyZSB0aGVuCnJhbmRvbWx5IGRpc3RyaWJ1dGVkIGFjcm9zcyB0aHJlZSBUTVQgMTAtcGxleCBtaXh0dXJlcyBmb3IgYW5hbHlzaXMuCkluIGVhY2ggbWl4dHVyZSwgdHdvIHJlZmVyZW5jZSBsYWJlbHMgd2VyZSB1c2VkLCBlYWNoIGNvbnRhaW5pbmcKcG9vbGVkIHNhbXBsZXMgdGhhdCBpbmNsdWRlZCBhIHJhbmdlIG9mIHBlcHRpZGVzIGZyb20gYWxsIHRoZSBzYW1wbGVzLgpOb3QgYWxsIGxhYmVscyB3ZXJlIHVzZWQsIGxlYWRpbmcgdG8gYW4gdW5iYWxhbmNlZCBkZXNpZ24uIEVhY2ggVE1UCjEwLXBsZXggbWl4dHVyZSB3YXMgZnJhY3Rpb25hdGVkIGludG8gbmluZSBwYXJ0cywgcmVzdWx0aW5nIGluIGEgdG90YWwKb2YgMjcgTVMgcnVucy4KCmBgYHtyLCBlY2hvID0gRkFMU0UsIGZpZy5jYXA9Ik92ZXJ2aWV3IG9mIHRoZSBleHBlcmltZW50YWwgZGVzaWduLiBUYWtlbiBmcm9tIFBsdWJlbGwgZXQgYWwuIDIwMTcuIn0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoImZpZ3MvbW91c2VfZXhwZXJpbWVudC5wbmciKQpgYGAKCiMjIyBEYXRhIAoKVGhlIGRhdGEgd2VyZSByZWFuYWx5emVkIGJ5IEBIdWFuZzIwMjAtbHUgYW5kIGhhdmUgYmVlbiBkZXBvc2l0ZWQgaW4KdGhlIGBNU1YwMDAwODQyNjRgIE1BU1NpVkUgcmVwb3NpdG9yeSwgYnV0IHdlIHdpbGwgcmV0cmlldmUgdGhlCnRpbWVzdGFtcGVkIGRhdGEgZnJvbSBvdXIgW1plbm9kbwpyZXBvc2l0b3J5XShodHRwczovL3plbm9kby5vcmcvcmVjb3Jkcy8xNDc2NzkwNSkuIFdlIG5lZWQgMiBmaWxlczogdGhlClNreWxpbmUgaWRlbnRpZmljYXRpb24gYW5kIHF1YW50aWZpY2F0aW9uIHRhYmxlIGdlbmVyYXRlZCBieSB0aGUKYXV0aG9ycyBhbmQgdGhlIHNhbXBsZSBhbm5vdGF0aW9uIGZpbGVzLgoKYGBge3J9CmxpYnJhcnkoIkJpb2NGaWxlQ2FjaGUiKQpiZmMgPC0gQmlvY0ZpbGVDYWNoZSgpCnBzbUZpbGUgPC0gYmZjcnBhdGgoYmZjLCAiaHR0cHM6Ly96ZW5vZG8ub3JnL3JlY29yZHMvMTQ3Njc5MDUvZmlsZXMvbW91c2VfcHNtcy50eHQ/ZG93bmxvYWQ9MSIpCmFubm90RmlsZSA8LSBiZmNycGF0aChiZmMsICJodHRwczovL3plbm9kby5vcmcvcmVjb3Jkcy8xNDc2NzkwNS9maWxlcy9tb3VzZV9hbm5vdGF0aW9ucy5jc3Y/ZG93bmxvYWQ9MSIpCmBgYAoKTGV0J3MgcHJlcGFyZSB0aGUgZGF0YSAoc2FtZSBhcHByb2FjaCBhcyBhYm92ZSkuIFdlIHdpbGwgYWxzbyBzdWJzZXQKdGhlIGRhdGEgc2V0IHRvIHJlZHVjZSBjb21wdXRhdGlvbmFsIGNvc3RzLiBXZSBoZXJlIHJhbmRvbWx5IHNhbXBsZQo1MDAgcHJvdGVpbnMgZnJvbSB0aGUgZXhwZXJpbWVudC4KCgpgYGB7cn0KIyMgUmVhZGluZyBkYXRhCnBzbXMgPC0gcmVhZC5kZWxpbShwc21GaWxlKQpjb2xkYXRhIDwtIHJlYWQuY3N2KGFubm90RmlsZSkKY29sZGF0YSREdXJhdGlvbiA8LSBnc3ViKCJfLioiLCAiIiwgY29sZGF0YSRDb25kaXRpb24pICMjIDEuCmNvbGRhdGEkRGlldCA8LSBnc3ViKCIuKl8iLCAiIiwgY29sZGF0YSRDb25kaXRpb24pICMjIDIuCmNvbG5hbWVzKGNvbGRhdGEpWzFdIDwtICJMYWJlbCIgIyMgMy4KIyMgU3Vic2V0dGluZyBkYXRhCnByb3RlaW5JZHMgPC0gdW5pcXVlKHBzbXMkUHJvdGVpbi5BY2Nlc3Npb25zKQpzZXQuc2VlZCgxMjM0KQpwc21zIDwtIHBzbXNbcHNtcyRQcm90ZWluLkFjY2Vzc2lvbnMgJWluJSBzYW1wbGUocHJvdGVpbklkcywgNTAwKSwgXQojIyBDb252ZXJ0aW5nIGRhdGEKY29sZGF0YSRydW5Db2wgPC0gY29sZGF0YSRSdW4KY29sZGF0YSRxdWFudENvbHMgPC0gcGFzdGUwKCJBYnVuZGFuY2UuLiIsIGNvbGRhdGEkTGFiZWwpCm1vdXNlIDwtIHJlYWRRRmVhdHVyZXMocHNtcywgY29sRGF0YSA9IGNvbGRhdGEsCiAgICAgICAgICAgICAgICAgICAgICAgcXVhbnRDb2xzID0gdW5pcXVlKGNvbGRhdGEkcXVhbnRDb2xzKSwKICAgICAgICAgICAgICAgICAgICAgICBydW5Db2wgPSAiU3BlY3RydW0uRmlsZSIsIG5hbWUgPSAicHNtcyIpCm5hbWVzKG1vdXNlKSA8LSBzdWIoIl4uKihNb3VzZS4qQUNOKS4qcmF3IiwgIlxcMSIsIG5hbWVzKG1vdXNlKSkKYGBgCgojIyMgRGF0YSBwcmVwcm9jZXNzaW5nCgpXZSB1c2UgYSBkYXRhIHByb2Nlc3Npbmcgd29ya2Zsb3cgdmVyeSBzaW1pbGFyIHRvIGFib3ZlLgoKYGBge3J9CiMjIEVuY29kZSBtaXNzaW5nIHZhbHVlcwptb3VzZSA8LSB6ZXJvSXNOQShtb3VzZSwgbmFtZXMobW91c2UpKQojIyBTYW1wbGUgZmlsdGVyaW5nCm1vdXNlIDwtIHN1YnNldEJ5Q29sRGF0YSgKICAgIG1vdXNlLCBtb3VzZSRDb25kaXRpb24gIT0gIk5vcm0iICYgbW91c2UkQ29uZGl0aW9uICE9ICJMb25nX00iCikKIyMgUFNNIGZpbHRlcmluZwptb3VzZSA8LSBmaWx0ZXJOQShtb3VzZSwgbmFtZXMobW91c2UpLCBwTkEgPSAwLjcpCm1vdXNlIDwtIGZpbHRlckZlYXR1cmVzKAogICAgbW91c2UsIH4gUHJvdGVpbi5BY2Nlc3Npb25zICE9ICIiICYgIyMgUmVtb3ZlIGZhaWxlZCBwcm90ZWluIGluZmVyZW5jZQogICAgICAgICFncmVwbCgiOyIsIFByb3RlaW4uQWNjZXNzaW9ucykpICMjIFJlbW92ZSBwcm90ZWluIGdyb3Vwcwpmb3IgKGkgaW4gbmFtZXMobW91c2UpKSB7CiAgICByb3dkYXRhIDwtIHJvd0RhdGEobW91c2VbW2ldXSkKICAgIHJvd2RhdGEkaW9uSUQgPC0gcGFzdGUwKHJvd2RhdGEkQW5ub3RhdGVkLlNlcXVlbmNlLCByb3dkYXRhJENoYXJnZSkKICAgIHJvd2RhdGEkcm93U3VtcyA8LSByb3dTdW1zKGFzc2F5KG1vdXNlW1tpXV0pLCBuYS5ybSA9IFRSVUUpCiAgICByb3dkYXRhIDwtIGRhdGEuZnJhbWUocm93ZGF0YSkgfD4KICAgICAgICBncm91cF9ieShpb25JRCkgfD4KICAgICAgICBtdXRhdGUocHNtUmFuayA9IHJhbmsoLXJvd1N1bXMpKQogICAgcm93RGF0YShtb3VzZVtbaV1dKSA8LSBEYXRhRnJhbWUocm93ZGF0YSkKfQptb3VzZSA8LSBmaWx0ZXJGZWF0dXJlcyhtb3VzZSwgfiBwc21SYW5rID09IDEpCiMjIExvZyB0cmFuc2Zvcm1hdGlvbgpzTmFtZXMgPC0gbmFtZXMobW91c2UpCm1vdXNlIDwtIGxvZ1RyYW5zZm9ybSgKICAgIG1vdXNlLCBzTmFtZXMsIG5hbWUgPSBwYXN0ZTAoc05hbWVzLCAiX2xvZyIpLCBiYXNlID0gMgopCiMjIE5vcm1hbGlzYXRpb24KbW91c2UgPC0gbm9ybWFsaXplKAogICAgbW91c2UsIHBhc3RlMChzTmFtZXMsICJfbG9nIiksIG5hbWUgPSBwYXN0ZTAoc05hbWVzLCAiX25vcm0iKSwKICAgIG1ldGhvZCA9ICJjZW50ZXIubWVkaWFuIgopCiMjIFByb3RlaW4gc3VtbWFyaXNhdGlvbgptb3VzZSA8LSBhZ2dyZWdhdGVGZWF0dXJlcygKICAgIG1vdXNlLCBpID0gcGFzdGUwKHNOYW1lcywgIl9ub3JtIiksIG5hbWUgPSBwYXN0ZTAoc05hbWVzLCAiX3Byb3RlaW5zIiksCiAgICBmY29sID0gIlByb3RlaW4uQWNjZXNzaW9ucyIsIGZ1biA9IE1zQ29yZVV0aWxzOjptZWRpYW5Qb2xpc2gsCiAgICBuYS5ybT1UUlVFCikKIyMgSm9pbiBzZXRzCm1vdXNlIDwtIGpvaW5Bc3NheXMobW91c2UsIHBhc3RlMChzTmFtZXMsICJfcHJvdGVpbnMiKSwgInByb3RlaW5zIikKYGBgCgojIyMgUHJvYmxlbSBzdGF0ZW1lbnQKClN0YXJ0aW5nIGZyb20gdGhlIHByb2Nlc3NlZCBgcHJvdGVpbnNgIHNldCwgYnVpbGQgYSBtb2RlbCBhY2NvdW50aW5nIApmb3IgYWxsIHNvdXJjZXMgb2YgdmFyaWF0aW9uIGluIHRoZSBleHBlcmltZW50cyAodXNlIHRoZSBmaWd1cmUgc2hvd24KaW4gdGhlIGV4cGVyaW1lbnRhbCBjb250ZXh0IHRvIGd1aWRlIHlvdXIgZGVjaXNpb25zKSwgYW5kIGFsbG93aW5nIHlvdQp0byBhbnN3ZXIgdGhlIGZvbGxvd2luZyBxdWVzdGlvbnM6CgotIFdoYXQgaXMgdGhlIGRpZmZlcmVuY2UgaW4gcHJvdGVpbiBhYnVuZGFuY2UgYmV0d2VlbiBsb3cgZmF0IGFuZCBoaWdoCiAgZmF0IGRpZXQgYWZ0ZXIgc2hvcnQgZHVyYXRpb24/Ci0gV2hhdCBpcyB0aGUgZGlmZmVyZW5jZSBpbiBwcm90ZWluIGFidW5kYW5jZSBiZXR3ZWVuIGxvdyBmYXQgYW5kIGhpZ2gKICBmYXQgZGlldCBhZnRlciBsb25nIGR1cmF0aW9uPwotIFdoYXQgaXMgdGhlIGF2ZXJhZ2UgZGlmZmVyZW5jZSBpbiBwcm90ZWluIGFidW5kYW5jZSBiZXR3ZWVuIGxvdyBmYXQKICBhbmQgaGlnaCBmYXQgZGlldD8KLSBEb2VzIHRoZSBkaWV0IGVmZmVjdCBjaGFuZ2UgYWNjb3JkaW5nIHRvIGR1cmF0aW9uPyAoaGludDogaXMgdGhlcmUgCiAgYW4gaW50ZXJhY3Rpb24gYmV0d2VlbiBkaWV0IGFuZCBkdXJhdGlvbj8pCgpOb3RlIHRoYXQgeW91IHNob3VsZCB1c2UgYERpZXRgIGFuZCBgRHVyYXRpb25gIGFzIHRoZSBleHBlcmltZW50YWwKdmFyaWFibGVzIG9mIGludGVyZXN0LCBhbmQgeW91IGNhbiBleHBsb3JlIHRoZSBhdmFpbGFibGUgdmFyaWFibGVzCmZvciBtb2RlbGxpbmcgdXNpbmcgYGNvbERhdGEobW91c2UpYC4gUmVjYWxsIHRoYXQgeW91IGNhbiBhbHNvIGV4cGxvcmUKeW91ciBkYXRhIHVzaW5nIE1EUyBhbmQgY29sb3VyIGZvciB0aGUgcG90ZW50aWFsIHNvdXJjZXMgb2YgdmFyaWF0aW9uLgoKRXN0aW1hdGUgeW91ciBtb2RlbCBhbmQgcGVyZm9ybSBzdGF0aXN0aWNhbCBpbmZlcmVuY2UgdXNpbmcgYG1zcXJvYjJgLgpFYWNoIHF1ZXN0aW9uIHdpbGwgcmVxdWlyZSB5b3UgdG8gdGVzdCBhIGRpZmZlcmVudCBjb250cmFzdC4gRG9uJ3QKZm9yZ2V0IHRoYXQgdGhlIGBWaXN1YWxpemVEZXNpZ24oKWAgZnVuY3Rpb24gY2FuIGhlbHAgeW91IHdpdGgKaWRlbnRpZnkgdGhlIGNvcnJlY3QgY29tYmluYXRpb24gb2YgcGFyYW1ldGVycyB0byBkZWZpbmUgeW91cgpjb250cmFzdC4KClRoZSByZXBvcnQgdGhlIHJlc3VsdHMgdXNpbmcgYSB2b2xjYW5vIHBsb3QgYW5kIGEgZGlmZmVyZW50aWFsIAphbmFseXNpcyB0YWJsZSB0byBhbnN3ZXIgdGhlIHF1ZXN0aW9ucy4KCiMjIyBTb2x1dGlvbiAKCjxkZXRhaWxzPjxzdW1tYXJ5PiBDbGljayB0byBzZWUgdGhlIHNvbHV0aW9uPC9zdW1tYXJ5PjxwPgoKTGV0J3MgZXhwbG9yZSB0aGUgZGF0YS4KCmBgYHtyfQpsaWJyYXJ5KCJzY2F0ZXIiKQpzZSA8LSBnZXRXaXRoQ29sRGF0YShtb3VzZSwgInByb3RlaW5zIikgfD4gCiAgYXMoIlNpbmdsZUNlbGxFeHBlcmltZW50IikgfD4gCiAgcnVuTURTKGV4cHJzX3ZhbHVlcyA9IDEpCmBgYAoKV2UgcGxvdCB0aGUgTURTIGFuZCBjb2xvdXIgZWFjaCBzYW1wbGUgYmFzZWQgb24gZGlmZmVyZW50IHBvdGVudGlhbApzb3VyY2VzIG9mIHZhcmlhdGlvbi4KCmBgYHtyfQpwbG90TURTKHNlLCBjb2xvdXJfYnkgPSAiUnVuIikgKyBnZ3RpdGxlKCJDb2xvdXJlZCBieSBSdW4iKSArCiAgc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXMgPSByYWluYm93KDI3KSkgKwogIHBsb3RNRFMoc2UsIGNvbG91cl9ieSA9ICJGcmFjdGlvbiIpICsgZ2d0aXRsZSgiQ29sb3VyZWQgYnkgRnJhY3Rpb24iKSArCiAgcGxvdE1EUyhzZSwgY29sb3VyX2J5ID0gIkJpb1JlcGxpY2F0ZSIpICsgZ2d0aXRsZSgiQ29sb3VyZWQgYnkgQmlvUmVwbGljYXRlIikgKwogIHBsb3RNRFMoc2UsIGNvbG91cl9ieSA9ICJNaXh0dXJlIikgKyBnZ3RpdGxlKCJDb2xvdXJlZCBieSBNaXh0dXJlIikgKwogIHBsb3RNRFMoc2UsIGNvbG91cl9ieSA9ICJEaWV0IikgKyBnZ3RpdGxlKCJDb2xvdXJlZCBieSBEaWV0IikgKwogIHBsb3RNRFMoc2UsIGNvbG91cl9ieSA9ICJEdXJhdGlvbiIpICsgZ2d0aXRsZSgiQ29sb3VyZWQgYnkgRHVyYXRpb24iKSAmCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSAKYGBgCgpUaGUgZGF0YSBleHBsb3JhdGlvbiBsZWFkcyB0byBzZXZlcmFsIG9ic2VydmF0aW9uczogCgotIFRoZSBzdHJvbmdlc3Qgc291cmNlIG9mIHZhcmlhdGlvbiBpcyBhc3NvY2lhdGVkIHdpdGggdGhlIE1TIAogIGFjcXVpc2l0aW9uIHJ1bi4gCi0gUGFydCBvZiB0aGlzIHJ1biBlZmZlY3QgaXMgaW5mbHVlbmNlZCBieSB3aGljaCBmcmFjdGlvbiBpdCBjb250YWlucwogIHNpbmNlIHNhbXBsZXMgZnJvbSB0aGUgc2FtZSBmcmFjdGlvbiB0ZW5kIHRvIGJlIGNsb3NlciB0aGFuIHNhbXBsZXMgZnJvbSBkaWZmZXJlbnQKICBmcmFjdGlvbnMuIAotIEl0IGlzIGRpZmZpY3VsdCB0byBpZGVudGlmeSBhbiBlZmZlY3Qgb2YgbW91c2UgKGJpb2xvZ2ljYWwKICByZXBsaWNhdGUpIGJlY2F1c2UgZXZlcnkgcnVuIGNvbnRhaW5zIGRpc3RpbmN0IG1pY2UuIEhvd2V2ZXIsIHRoaXMKICBkb2VzIG5vdCBleGNsdWRlIGFuIGVmZmVjdCBvZiBtaWNlIHdoaWNoIGhhcyBiZWVuIGlkZW50aWZpZWQgYXMgYQogIHBvdGVudGlhbCBzb3VyY2Ugb2YgdmFyaWF0aW9uIGFuZCBoZW5jZSBzaG91bGQgc3RpbGwgYmUgbW9kZWxsZWQuCi0gVGhlcmUgaXMgcG90ZW50aWFsbHkgYWxzbyBhbiBlZmZlY3Qgb2YgVE1UIG1peHR1cmUgc2luY2Ugc2FtcGxlcwogIGZyb20gdGhlIHNhbWUgbWl4dHVyZXMgdGVuZCB0byBjbHVzdGVyIHRvZ2V0aGVyIChpbiB0aGUgY2VudGVyIG9mCiAgdGhlIHBsb3QpLiBIb3dldmVyLCB0aGlzIGVmZmVjdCBhcmUgbW9yZSBzdWJ0bGUgdG8gZGV0ZWN0IGFuZAogIGRpZmZpY3VsdCB0byBkaXNlbnRhbmdsZSBmcm9tIHRoZSBydW4gYW5kIGZyYWN0aW9uIGVmZmVjdHMuCi0gQWx0aG91Z2ggYWdhaW4gdmVyeSBzdWJ0bGUsIHdlIGNhbiBzZWUgd2l0aGluIGVhY2ggcnVuIHRoYXQgc2FtcGxlcwogIGZyb20gdGhlIG1pY2Ugd2l0aCB0aGUgc2FtZSBkaWV0IHRlbmRzIHRvIGdyb3VwIHRvZ2V0aGVyLiBIb3dldmVyLAogIHRoZXNlIGVmZmVjdHMgYXJlIG92ZXJ3aGVsbWVkIGJ5IHRoZSBydW4gZWZmZWN0cy4gQW4gZWZmZWN0IG9mCiAgZHVyYXRpb24gaXMgdG8gc3VidGxlIHRvIHBpbnBvaW50IGZyb20gdGhlIGN1cnJlbnQgZGF0YSBleHBsb3JhdGlvbi4KCkRhdGEgbW9kZWxsaW5nIGRpc2VudGFuZ2xlcyB0aGUgZGlmZmVyZW50IHNvdXJjZXMgb2YgdmFyaWF0aW9uLCBnaXZlbgp0aGV5IGFyZSBwcm9wZXJseSBkZWZpbmVkIGluIHRoZSBtb2RlbC4gCgpQcm90ZW9taWNzIGRhdGEgY29udGFpbiBzZXZlcmFsIHNvdXJjZXMgb2YgdmFyaWF0aW9uIHRoYXQgbmVlZCB0byBiZQphY2NvdW50ZWQgZm9yIGJ5IHRoZSBtb2RlbDoKCjEuICoqVHJlYXRtZW50IG9mIGludGVyZXN0Kio6IHdlIG1vZGVsIHRoZSBzb3VyY2Ugb2YgdmFyaWF0aW9uIGluZHVjZWQKICAgYnkgdGhlIGV4cGVyaW1lbnRhbCB0cmVhdG1lbnQgb2YgaW50ZXJlc3QgYXMgYSAqKmZpeGVkIGVmZmVjdCoqLgogICBGaXhlZCBlZmZlY3RzIGFyZSBlZmZlY3QgdGhhdCBhcmUgY29uc2lkZXJlZCBub24tcmFuZG9tLCBpLmUuIHRoZQogICB0cmVhdG1lbnQgZWZmZWN0IGlzIGFzc3VtZWQgdG8gYmUgdGhlIHNhbWUgYW5kIHJlcHJvZHVjaWJsZSBhY3Jvc3MKICAgcmVwZWF0ZWQgZXhwZXJpbWVudHMsIGJ1dCBpdCBpcyB1bmtub3duIGFuZCBoYXMgdG8gYmUgZXN0aW1hdGVkLiBXZQogICB3aWxsIGluY2x1ZGUgYERpZXRgIGFzIGEgZml4ZWQgZWZmZWN0IHRoYXQgbW9kZWxzIHRoZSBmYWN0IHRoYXQgYQogICBjaGFuZ2UgaW4gZGlldCB0eXBlIGNhbiBpbmR1Y2UgY2hhbmdlcyBpbiBwcm90ZWluIGFidW5kYW5jZS4KICAgU2ltaWxhcmx5LCB3ZSBhbHNvIGluY2x1ZGUgYER1cmF0aW9uYCBhcyBhIGZpeGVkIGVmZmVjdCB0byBtb2RlbAogICB0aGUgY2hhbmdlIGluIHByb3RlaW4gYWJ1bmRhbmNlIGluZHVjZXMgYnkgdGhlIGRpZXQgZHVyYXRpb24uCiAgIEZpbmFsbHksIHdlIHdpbGwgYWxzbyBpbmNsdWRlIGFuIGludGVyYWN0aW9uIGJldHdlZW4gdGhlIHR3bwogICB2YXJpYWJsZXMgYWxsb3dpbmcgdGhhdCB0aGUgY2hhbmdlcyBpbiBwcm90ZWluIGFidW5kYW5jZSBpbmR1Y2VkIGJ5CiAgIGRpZXQgdHlwZSBjYW4gYmUgZGlmZmVyZW50IHdoZXRoZXIgdGhlIG1pY2Ugd2VyZSBmZWQgZm9yIGEgc2hvcnQgb3IKICAgbG9uZyBkdXJhdGlvbi4KCjIuICoqQmlvbG9naWNhbCByZXBsaWNhdGUgZWZmZWN0Kio6IHRoZSBleHBlcmltZW50IGludm9sdmVzIGJpb2xvZ2ljYWwKICAgcmVwbGljYXRpb24gYXMgdGhlIGFkaXBvc2UgdGlzc3VlIGV4dHJhY3RzIHdlcmUgc2FtcGxlZCBmcm9tIDIwCiAgIG1pY2UgKDUgbWljZSBwZXIgRGlldCB4IER1cmF0aW9uIGNvbWJpbmF0aW9uKS4gUmVwbGljYXRlLXNwZWNpZmljCiAgIGVmZmVjdHMgb2NjdXJzIGR1ZSB0byB1bmNvbnRyb2xsYWJsZSBmYWN0b3JzLCBzdWNoIGFzIHNvY2lhbAogICBiZWhhdmlvciwgZmVlZGluZyBiZWhhdmlvciwgcGh5c2ljYWwgYWN0aXZpdHksIG9jY2FzaW9uYWwKICAgaW5qdXJ5LC4uLiBUd28gbWljZSB3aWxsIG5ldmVyIHByb3ZpZGUgZXhhY3RseSB0aGUgc2FtZSBzYW1wbGUgbWF0ZXJpYWwsCiAgIGV2ZW4gd2VyZSB0aGV5IGdlbmV0aWNhbGx5IGlkZW50aWNhbCBhbmQgbWFuaXB1bGF0ZWQgaWRlbnRpY2FsbHkuCiAgIFRoZXNlIGVmZmVjdHMgYXJlIHR5cGljYWxseSBtb2RlbGxlZCBhcyByYW5kb20gZWZmZWN0cyB3aGljaCBhcmUKICAgY29uc2lkZXJlZCBhcyBhIHJhbmRvbSBzYW1wbGUgZnJvbSB0aGUgcG9wdWxhdGlvbiBvZiBhbGwgcG9zc2libGUKICAgbWljZSBhbmQgYXJlIGFzc3VtZWQgdG8gYmUgaS5pLmQgbm9ybWFsbHkgZGlzdHJpYnV0ZWQgd2l0aCBtZWFuIDAKICAgYW5kIGNvbnN0YW50IHZhcmlhbmNlLCAkdV97bW91c2V9IFxzaW0KICAgTigwLFxzaWdtYV57MixcdGV4dHttb3VzZX19KSQuIFRoZSB1c2Ugb2YgcmFuZG9tIGVmZmVjdHMgdGh1cwogICBtb2RlbHMgdGhlIGNvcnJlbGF0aW9uIGluIHRoZSBkYXRhLCBleHBsaWNpdGx5LiBXZSBleHBlY3QgdGhhdAogICBpbnRlbnNpdGllcyBmcm9tIHRoZSBzYW1lIG1vdXNlIGFyZSBtb3JlIGFsaWtlIHRoYW4gaW50ZW5zaXRpZXMKICAgYmV0d2VlbiBtaWNlLgoKYGBge3J9Cmxlbmd0aCh1bmlxdWUobW91c2UkQmlvUmVwbGljYXRlKSkKYGBgCgozLiAqKkxhYmVsbGluZyBlZmZlY3RzKio6IHRoZSAyMCBtb3VzZSBhZGlwb3NlIHRpc3N1ZSBzYW1wbGVzIGhhdmUgYmVlbgogICBsYWJlbGxlZCB1c2luZyAxOC1wbGV4IFRNVC4gV2UgY2FuIGV4cGVjdCB0aGF0IHNhbXBsZXMgbWVhc3VyZWQKICAgd2l0aGluIHRoZSBzYW1lIFRNVCBsYWJlbCBtYXkgYmUgbW9yZSBzaW1pbGFyIHRoYW4gc2FtcGxlcwogICBtZWFzdXJlZCB3aXRoaW4gZGlmZmVyZW50IFRNVCBsYWJlbHMuIFNpbmNlIHRoZXNlIGVmZmVjdHMgbWF5IG5vdAogICBiZSByZXByb2R1Y2libGUgZnJvbSBvbmUgZXhwZXJpbWVudCB0byBhbm90aGVyLCBmb3IgaW5zdGFuY2UKICAgYmVjYXVzZSBlYWNoIFRNVCBraXQgbWF5IHBvdGVudGlhbGx5IGNvbnRhaW4gZGlmZmVyZW50IGltcHVyaXR5CiAgIHJhdGlvcywgd2UgY2FuIGFjY291bnQgZm9yIHRoaXMgY29ycmVsYXRpb24gdXNpbmcgYSByYW5kb20gZWZmZWN0CiAgIGZvciBUTVQgbGFiZWwuCgpgYGB7cn0KbGVuZ3RoKHVuaXF1ZShtb3VzZSRMYWJlbCkpCmBgYAoKNC4gKipNaXh0dXJlIGVmZmVjdHMqKjogdGhlIDIwIG1vdXNlIHNhbXBsZXMgd2VyZSBhc3NpZ25lZCB0byBvbmUgb3V0CiAgIG9mIDMgbWl4dHVyZXMuIEFnYWluLCB3ZSBleHBlY3QgcHJvdGVpbiBpbnRlbnNpdGllcyBmcm9tIHRoZSBzYW1lCiAgIG1peHR1cmUgd2lsbCBiZSBtb3JlIGFsaWtlIHRoYW4gdGhvc2Ugb2YgZGlmZmVyZW50IG1peHR1cmVzLiBIZW5jZSwKICAgd2Ugd2lsbCBhZGQgYSByYW5kb20gZWZmZWN0IGZvciBtaXh0dXJlLgoKYGBge3J9CnRhYmxlKG1vdXNlJE1peHR1cmUpCmBgYAoKNS4gKipSdW4gZWZmZWN0cyoqOiBwcm90ZWluIGludGVuc2l0aWVzIHRoYXQgYXJlIG1lYXN1cmVkIHdpdGhpbiB0aGUgCiAgIHNhbWUgcnVuIHdpbGwgYmUgbW9yZSBzaW1pbGFyIHRoYW4gcHJvdGVpbiBpbnRlbnNpdGllcyBiZXR3ZWVuCiAgIHJ1bnMuIFdlIHdpbGwgdXNlIGEgcmFuZG9tIGVmZmVjdCBmb3IgcnVuIHRvIGV4cGxpY2l0bHkgbW9kZWwgdGhpcwogICBjb3JyZWxhdGlvbiBpbiB0aGUgZGF0YS4gTm90ZSB0aGF0IGVhY2ggc2FtcGxlIGhhcyBiZWVuIGFjcXVpcmVkIGluCiAgIDkgZnJhY3Rpb25zLCBlYWNoIGZyYWN0aW9uIGJlaW5nIG1lYXN1cmVkIGluIGEgc2VwYXJhdGUgcnVuLgogICBBY2NvdW50aW5nIGZvciB0aGUgZWZmZWN0cyBvZiBydW4gd2lsbCBhbHNvIGFic29yYiB0aGUgZWZmZWN0cyBvZgogICBmcmFjdGlvbi4KCmBgYHtyfQpsZW5ndGgodW5pcXVlKG1vdXNlJFJ1bikpCmBgYAoKV2Ugd2lsbCBtb2RlbCB0aGUgbWFpbiBlZmZlY3RzIGZvciBgRGlldGAgYW5kIGBEdXJhdGlvbmAsIGFuZCBhCmBEaWV0OkR1cmF0aW9uYCBpbnRlcmFjdGlvbiwgdG8gYWNjb3VudCBmb3IgcHJvdGVpbnMgZm9yIHdoaWNoIHRoZQpgRGlldGAgZWZmZWN0IGNoYW5nZXMgYWNjb3JkaW5nIHRvIGBEdXJhdGlvbmAsIGFuZCB2aWNlIHZlcnNhLCB3aGljaApjYW4gYmUgd3JpdHRlbiBhcyBgRGlldCArIER1cmF0aW9uICsgRGlldDpEdXJhdGlvbmAsIHNob3J0ZW5lZCBpbnRvCmBEaWV0ICogRHVyYXRpb25gIChyZWNhbGwgdGhlIGhlYXJ0IGV4YW1wbGUgaW4gYSBwcmV2aW91cyBjb3Vyc2UpLgpBZGRpbmcgdGhlIHRlY2huaWNhbCBzb3VyY2VzIG9mIHZhcmlhdGlvbiwgdGhlIG1vZGVsIGJlY29tZXMuCgpgYGB7cn0KbW9kZWwgPC0gfiBEaWV0ICogRHVyYXRpb24gKyAjIyAoMSkgZml4ZWQgZWZmZWN0IGZvciBEaWV0IGFuZCBEdXJhdGlvbiB3aXRoIGludGVyYWN0aW9uCiAgKDEgfCBCaW9SZXBsaWNhdGUpICsgICMjICgyKSByYW5kb20gZWZmZWN0IGZvciBiaW9sb2dpY2FsIHJlcGxpY2F0ZSAobW91c2UpICAgICAgCiAgKDEgfCBMYWJlbCkgKyAjIyAoMykgcmFuZG9tIGVmZmVjdCBmb3IgbGFiZWwKICAoMSB8IE1peHR1cmUpICsgIyMgKDQpIHJhbmRvbSBlZmZlY3QgZm9yIG1peHR1cmUKICAoMSB8IFJ1bikgIyMgKDUpIHJhbmRvbSBlZmZlY3QgZm9yIE1TIHJ1bgpgYGAKCldlIGVzdGltYXRlIHRoZSBtb2RlbCB3aXRoIGBtc3Fyb2IoKWAuCgpgYGB7ciBydW5fbXNxcm9iX2lvbiwgY2FjaGU9VFJVRX0KbW91c2UgPC0gbXNxcm9iKG1vdXNlLCBpID0gInByb3RlaW5zIiwgZm9ybXVsYSA9IG1vZGVsKQpgYGAKCioqRGlmZmVyZW5jZSBiZXR3ZWVuIGxvdyBmYXQgYW5kIGhpZ2ggZmF0IGRpZXQgYWZ0ZXIgc2hvcnQgZHVyYXRpb24qKgoKV2UgbmVlZCB0byBjb252ZXJ0IHRoaXMgcXVlc3Rpb24gaW4gYSBjb21iaW5hdGlvbiBvZiB0aGUgbW9kZWwKcGFyYW1ldGVycy4gV2UgZ3VpZGUgdGhlIGNvbnRyYXN0IGRlZmluaXRpb24gdXNpbmcgdGhlCmBFeHBsb3JlTW9kZWxNYXRyaXhgIHBhY2thZ2UuIFNpbmNlIHdlIGFyZSBub3QgaW50ZXJlc3RlZCBpbiB0ZWNobmljYWwKZWZmZWN0cywgd2Ugd2lsbCBvbmx5IGZvY3VzIG9uIHRoZSB2YXJpYWJsZXMgb2YgaW50ZXJlc3QsIGhlcmUgYERpZXQgKgpEdXJhdGlvbmAuCgpgYGB7cn0KbGlicmFyeSgiRXhwbG9yZU1vZGVsTWF0cml4IikKdmQgPC0gVmlzdWFsaXplRGVzaWduKAogICAgc2FtcGxlRGF0YSA9ICBjb2xEYXRhKG1vdXNlKSwKICAgIGRlc2lnbkZvcm11bGEgPSB+IERpZXQgKiBEdXJhdGlvbiwKICAgIHRleHRTaXplRml0dGVkID0gNAopCnZkJHBsb3RsaXN0W1sxXV0KYGBgCgpBc3Nlc3NpbmcgdGhlIGRpZmZlcmVuY2UgYmV0d2VlbiBsb3ctZmF0IGFuZCBoaWdoLWZhdCBkaWV0cyBmb3Igc2hvcnQKZHVyYXRpb24gYm9pbHMgZG93biB0byBhc3Nlc3NpbmcgdGhlIGRpZmZlcmVuY2UgYmV0d2VlbiB0aGUgYFNob3J0X0xGYCBhbmQKYFNob3J0X0hGYC4gVGhlIG1lYW4gZm9yIHRoZSBzaG9ydCBsb3ctZmF0IGRpZXQgZ3JvdXAgaXMgZGVmaW5lZCBieQpgKEludGVyY2VwdCkgKyBEaWV0TEYgKyBEdXJhdGlvblNob3J0ICsgRGlldExGOkR1cmF0aW9uU2hvcnRgLiBUaGUKbWVhbiBmb3IgdGhlIHNob3J0IGhpZ2gtZmF0IGRpZXQgZ3JvdXAgaXMgZGVmaW5lZCBieSBgKEludGVyY2VwdCkgKwpEdXJhdGlvblNob3J0YC4gVGhlIGRpZmZlcmVuY2UgYmV0d2VlbiB0aGUgdHdvIHJlc3VsdHMgaW4gdGhlCmNvbnRyYXN0IGJlbG93OgoKYGBge3J9CihMIDwtIG1ha2VDb250cmFzdCgKICAgICJEaWV0TEYgKyBEaWV0TEY6RHVyYXRpb25TaG9ydCA9IDAiLAogICAgcGFyYW1ldGVyTmFtZXMgPSBjKCJEaWV0TEYiLCAiRGlldExGOkR1cmF0aW9uU2hvcnQiKQopKQptb3VzZSA8LSBoeXBvdGhlc2lzVGVzdChtb3VzZSwgaSA9ICJwcm90ZWlucyIsIEwpCmBgYAoKTGV0IHVzIHJldHJpZXZlIHRoZSByZXN1bHQgdGFibGUgZnJvbSB0aGUgYHJvd0RhdGFgLiBOb3RlIHRoYXQgdGhlCm1vZGVsIGNvbHVtbiBpcyBuYW1lZCBhZnRlciB0aGUgY29sdW1uIG5hbWVzIG9mIHRoZSBjb250cmFzdCBtYXRyaXggYExgLgoKYGBge3J9CmluZmVyZW5jZSA8LSByb3dEYXRhKG1vdXNlW1sicHJvdGVpbnMiXV0pW1tjb2xuYW1lcyhMKV1dCmhlYWQoaW5mZXJlbmNlLCAxMCkKYGBgCgpUaGUgdGFibGUgY29udGFpbnMgdGhlIGh5cG90aGVzaXMgdGVzdGluZyByZXN1bHRzIGZvciBldmVyeSBwcm90ZWluLgoKV2UgY2FuIHVzZSB0aGUgdGFibGUgYWJvdmUgZGlyZWN0bHkgdG8gYnVpbGQgYSB2b2xjYW5vIHBsb3QgdXNpbmcKYGdncGxvdDJgIGZ1bmN0aW9uYWxpdHkuCgpgYGB7cn0KZ2dwbG90KGluZmVyZW5jZSkgKwogICAgYWVzKHggPSBsb2dGQywgeSA9IC1sb2cxMChwdmFsKSkgKwogICAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gLWxvZzEwKGluZmVyZW5jZSRwdmFsW3doaWNoLm1pbihhYnMoaW5mZXJlbmNlJGFkalB2YWwgLSAwLjA1KSldICkpICsKICAgIGdlb21fcG9pbnQoKSArCiAgICBnZ3RpdGxlKCJTdGF0aXN0aWNhbCBpbmZlcmVuY2Ugb24gZGlmZmVyZW5jZXMgYmV0d2VlbiBMRiBhbmQgSEYgKHNob3J0IGR1cmF0aW9uKSIsCiAgICAgICAgICAgIHBhc3RlKCJIeXBvdGhlc2lzIHRlc3Q6IiwgY29sbmFtZXMoTCksICI9IDAiKSkKYGBgCgpJbiB0aGlzIGV4YW1wbGUgKHJlbWVtYmVyIHRoaXMgaXMgYSBzdWJzZXQgb2YgdGhlIGNvbXBsZXRlIGRhdGEgc2V0KSwKb25seSBhIGZldyBwcm90ZWlucyBwYXNzIHRoZSBzaWduaWZpY2FuY2UgdGhyZXNob2xkIG9mIDUlIEZEUi4gCgoqKkRpZmZlcmVuY2UgYmV0d2VlbiBsb3cgZmF0IGFuZCBoaWdoIGZhdCBkaWV0IGFmdGVyIGxvbmcgZHVyYXRpb24qKgoKRm9sbG93aW5nIHRoZSBzYW1lIGFwcHJvYWNoIGFzIGFib3ZlLCB0aGUgaHlwb3RoZXNpcyB0ZXN0IGJlY29tZXM6IAoKYGBge3J9CkwgPC0gbWFrZUNvbnRyYXN0KCJEaWV0TEYgPSAwIiwgcGFyYW1ldGVyTmFtZXMgPSAiRGlldExGIikKbW91c2UgPC0gaHlwb3RoZXNpc1Rlc3QobW91c2UsIGkgPSAicHJvdGVpbnMiLCBMKQppbmZlcmVuY2UgPC0gcm93RGF0YShtb3VzZVtbInByb3RlaW5zIl1dKVtbY29sbmFtZXMoTCldXQpoZWFkKGluZmVyZW5jZSkKYGBgCgpBbmQgd2UgcGxvdCB0aGUgcmVzdWx0cy4KCmBgYHtyfQpnZ3Bsb3QoaW5mZXJlbmNlKSArCiAgICBhZXMoeCA9IGxvZ0ZDLCB5ID0gLWxvZzEwKHB2YWwpKSArCiAgICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAtbG9nMTAoaW5mZXJlbmNlJHB2YWxbd2hpY2gubWluKGFicyhpbmZlcmVuY2UkYWRqUHZhbCAtIDAuMDUpKV0gKSkgKwogICAgZ2VvbV9wb2ludCgpICsKICAgIGdndGl0bGUoIlN0YXRpc3RpY2FsIGluZmVyZW5jZSBvbiBkaWZmZXJlbmNlcyBiZXR3ZWVuIExGIGFuZCBIRiAobG9uZyBkdXJhdGlvbikiLAogICAgICAgICAgICBwYXN0ZSgiSHlwb3RoZXNpcyB0ZXN0OiIsIGNvbG5hbWVzKEwpLCAiPSAwIikpCmBgYAoKQWdhaW4sIG9ubHkgYSBmZXcgcHJvdGVpbnMgY29tZSBvdXQgZGlmZmVyZW50aWFsbHkgYWJ1bmRhbnQgYmV0d2Vlbgp0aGUgdHdvIGRpZXRzLCBidXQgYWZ0ZXIgYSBsb25nIGRpZXQgZHVyYXRpb24uCgoqKkF2ZXJhZ2UgZGlmZmVyZW5jZSBiZXR3ZWVuIGxvdyBmYXQgYW5kIGhpZ2ggZmF0IGRpZXQqKgoKT25lIG1heSB3YW50IHRvIGlkZW50aWZ5IHRoZSBzZXQgb2YgcHJvdGVpbnMgdGhhdCBhcmUgc3lzdGVtYXRpY2FsbHkKZGlmZmVyZW50aWFsbHkgYWJ1bmRhbnQgYmV0d2VlbiBkaWV0cywgaXJyZXNwZWN0aXZlIG9mIHRoZSBkdXJhdGlvbi4KVG8gYW5zd2VyIHRoaXMgcXVlc3Rpb24sIHdlIHdhbnQgdG8gaW5mZXIgb24gdGhlIGF2ZXJhZ2UgZGlmZmVyZW5jZQpiZXR3ZWVuIGdyb3VwIGBMRmAgYW5kIGdyb3VwIGBIRmAuIFRoZSBhdmVyYWdlIGxvdy1mYXQgZGlldCBpcyBkZWZpbmVkCmJ5IGAoKEludGVyY2VwdCkgKyBEaWV0TEYgKyBEdXJhdGlvblNob3J0ICsgRGlldExGOkR1cmF0aW9uU2hvcnQgKwooSW50ZXJjZXB0KSArIERpZXRMRikvMmAuIFRoZSBhdmVyYWdlIGhpZ2gtZmF0IGRpZXQgZ3JvdXAgaXMgZGVmaW5lZApieSBgKChJbnRlcmNlcHQpICsgRHVyYXRpb25TaG9ydCArIChJbnRlcmNlcHQpKS8yYC4gVGhlIGRpZmZlcmVuY2UKYmV0d2VlbiB0aGUgdHdvIHJlc3VsdHMgaW4gdGhlIGh5cG90aGVzaXMgdGVzdCBiZWxvdzoKCmBgYHtyfQpMIDwtIG1ha2VDb250cmFzdCgKICAgICJEaWV0TEYgKyAoRGlldExGOkR1cmF0aW9uU2hvcnQpLzIgPSAwIiwKICAgIHBhcmFtZXRlck5hbWVzID0gYygiRGlldExGIiwgIkRpZXRMRjpEdXJhdGlvblNob3J0IikKKQptb3VzZSA8LSBoeXBvdGhlc2lzVGVzdChtb3VzZSwgaSA9ICJwcm90ZWlucyIsIEwpCihpbmZlcmVuY2UgPC0gcm93RGF0YShtb3VzZVtbInByb3RlaW5zIl1dKVtbY29sbmFtZXMoTCldXSkKYGBgCgpBbmQgd2UgcGxvdCB0aGUgcmVzdWx0cy4KCmBgYHtyfQpnZ3Bsb3QoaW5mZXJlbmNlKSArCiAgICBhZXMoeCA9IGxvZ0ZDLCB5ID0gLWxvZzEwKHB2YWwpKSArCiAgICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAtbG9nMTAoaW5mZXJlbmNlJHB2YWxbd2hpY2gubWluKGFicyhpbmZlcmVuY2UkYWRqUHZhbCAtIDAuMDUpKV0gKSkgKwogICAgZ2VvbV9wb2ludCgpICsKICAgIGdndGl0bGUoIlN0YXRpc3RpY2FsIGluZmVyZW5jZSBvbiBhdmVyYWdlIGRpZmZlcmVuY2UgYmV0d2VlbiBMRiBhbmQgSEYiLAogICAgICAgICAgICBwYXN0ZSgiSHlwb3RoZXNpcyB0ZXN0OiIsIGNvbG5hbWVzKEwpLCAiPSAwIikpCmBgYAoKKipJbnRlcmFjdGlvbjogZG9lcyB0aGUgZGlldCBlZmZlY3QgY2hhbmdlIGFjY29yZGluZyB0byBkdXJhdGlvbj8qKgoKV2Ugd2lsbCBub3cgZXhwbG9yZSB3aGV0aGVyIHRoZSBlZmZlY3Qgb2YgZGlldCBvbiBwcm90ZWluIGFidW5kYW5jZQptYXkgYmUgYWZmZWN0ZWQgYnkgZHVyYXRpb24sIGkuZS4gd2Ugd2FudCB0byBpbmZlciBvbiB0aGUgZGlmZmVyZW5jZQpvZiBkaWZmZXJlbmNlcy4gVGhlIGRpZmZlcmVuY2UgYmV0d2VlbiBoeXBvdGhlc2lzIDEgYW5kIDIgaXMgYChEaWV0TEYKKyBEaWV0TEY6RHVyYXRpb25TaG9ydCkgLSAoRGlldExGKWAgYW5kIHJlc3VsdHMgaW4gdGhlIGh5cG90aGVzaXMKYmVsb3c6CgpXZSBjYW4gcHJvY2VlZCB3aXRoIHRoZSBzYW1lIHN0YXRpc3RpY2FsIHBpcGVsaW5lLgoKYGBge3J9CkwgPC0gbWFrZUNvbnRyYXN0KAogICAgIkRpZXRMRjpEdXJhdGlvblNob3J0ID0gMCIsCiAgICBwYXJhbWV0ZXJOYW1lcyA9ICJEaWV0TEY6RHVyYXRpb25TaG9ydCIKKQptb3VzZSA8LSBoeXBvdGhlc2lzVGVzdChtb3VzZSwgaSA9ICJwcm90ZWlucyIsIEwpCihpbmZlcmVuY2UgPC0gcm93RGF0YShtb3VzZVtbInByb3RlaW5zIl1dKVtbY29sbmFtZXMoTCldXSkKYGBgCgpBbmQgd2UgcGxvdCB0aGUgcmVzdWx0cy4KCmBgYHtyfQpnZ3Bsb3QoaW5mZXJlbmNlKSArCiAgICBhZXMoeCA9IGxvZ0ZDLCB5ID0gLWxvZzEwKHB2YWwpKSArCiAgICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAtbG9nMTAoaW5mZXJlbmNlJHB2YWxbd2hpY2gubWluKGFicyhpbmZlcmVuY2UkYWRqUHZhbCAtIDAuMDUpKV0gKSkgKwogICAgZ2VvbV9wb2ludCgpICsKICAgIGdndGl0bGUoIlN0YXRpc3RpY2FsIGluZmVyZW5jZSBvbiB0aGUgZWZmZWN0IG9mIGR1cmF0aW9uIG9uIHRoZSBkaWZmZXJlbmNlcyBiZXR3ZWVuIGRpZXRzIiwKICAgICAgICAgICAgcGFzdGUoIkh5cG90aGVzaXMgdGVzdDoiLCBjb2xuYW1lcyhMKSwgIj0gMCIpKQpgYGAKCjwvcD48L2RldGFpbHM+CgojIyBTZXNzaW9uIEluZm8KCldpdGggcmVzcGVjdCB0byByZXByb2R1Y2liaWxpdHksIGl0IGlzIGhpZ2hseSByZWNvbW1lbmRlZCB0byBpbmNsdWRlIGEKc2Vzc2lvbiBpbmZvIGluIHlvdXIgc2NyaXB0IHNvIHRoYXQgcmVhZGVycyBvZiB5b3VyIG91dHB1dCBjYW4gc2VlCnlvdXIgcGFydGljdWxhciBzZXR1cCBvZiBSLgoKYGBge3J9CnNlc3Npb25JbmZvKCkKYGBgCgojIyBSZWZlcmVuY2VzCg==