miRNA and isomiR Differential Expression Analysis in Breast Cancer

Iris Mestres Pascual



In this study, the differential expression analysis of the results provided by the MIRFLOWZ pipeline is performed. The main goal is to asses if there are any miRNA species differentially expressed across normal and tumor tissues. The conclusions are found in the main manuscript. Here, only the workflow with step-by-step explanations is reported.


Data Preprocessing



Data Loading and Sample Selection

The MIRFLOWZ pipeline has been ran with a specific set of samples. Therefore, the metadata for the differential expression analysis must contain exactly the same ones. This information is provided in two different files, one containing data per case and the other one with per sample information.

The per case file, contains data from NCI’s Cancer Genome Atlas Program (TCGA). Thus, after loading the data frame, only the cases belonging to the breast cancer (BRCA) study are kept.

raw_case_metadata <- read_excel("case_metadata.xlsx",
                           sheet = 1,
                           col_types = NULL)

case_metadata <- raw_case_metadata %>% 
  select(2:34) %>% 
  filter(type == "BRCA")


The per sample metadata is already from the TCGA-BRCA study so, from the data set, a particular set of columns are selected; the file name (File.Name), the case and sample ID (Case.ID and Sample.ID respectively), and the sample’s tissue type (Sample.Type). In addition, the file name will be formatted as to match the sample names in the counts table and an extra field with the sample’s plate is added.

raw_sample_metadata <- read.table("./sample_metadata.csv",
                                  sep = "\t",
                                  header = TRUE)

sample_metadata <- raw_sample_metadata %>% 
  select(c(File.Name, Case.ID, Sample.ID, Sample.Type))

for (i in 1:nrow(sample_metadata)){
  sample_metadata$File.Name[i] <- substr(raw_sample_metadata$File.Name[i], 6, 28)
  sample_metadata$Plate[i] <- strsplit(sample_metadata$File.Name[i], "-")[[1]][5]
}


Following the sample selection criteria used in the main report, female cases containing solely a samples coming from a normal tissue and another one from a primary tumor with no missing data are kept. In addition, any case with a new tumor event or has died without tumors it is removed. Moreover, only infiltrating ductal carcinoma bellow Stage IIB cases are maintained. Finally, the sample’s tissue types are modified to be labeled as either Normal or Tumor.

sample_metadata <- sample_metadata %>% 
  group_by(Case.ID) %>% 
  filter( n() == 2 ,
          any(Sample.Type == "Solid Tissue Normal"), 
          !Sample.Type == "Metastatic") %>%
  arrange(Case.ID)

sample_metadata$Sample.Type[sample_metadata$Sample.Type == "Primary Tumor"] <- "Tumor"
sample_metadata$Sample.Type[sample_metadata$Sample.Type == "Solid Tissue Normal"] <- "Normal"

case_metadata <- case_metadata %>%
  filter(bcr_patient_barcode %in% sample_metadata$Case.ID,
         !(vital_status == "Dead"& tumor_status == "TUMOR FREE"),
         gender == "FEMALE",
         !is.na(tumor_status),
         is.na(new_tumor_event_type),
         race != "[Not Available]",
         histological_type == "Infiltrating Ductal Carcinoma",
         !ajcc_pathologic_tumor_stage %in% c("Stage IIIB", 
                                             "Stage IIIC", 
                                             "Stage IV")) %>%
  select(bcr_patient_barcode, 
         age_at_initial_pathologic_diagnosis,
         race, 
         ajcc_pathologic_tumor_stage,
         vital_status,
         tumor_status, 
         menopause_status, 
         initial_pathologic_dx_year, 
         birth_days_to, 
         death_days_to) 
colnames(case_metadata)[1] <- "Case.ID"

sample_metadata <- sample_metadata %>%
  filter(Case.ID %in% case_metadata$Case.ID) %>%
  select(File.Name, Case.ID, Sample.Type, Plate) %>%
  arrange(File.Name)


The filtered data sets are combined into a single one. The merge leads to have samples coming from normal tissues to have a cancer stage assigned thus, they are labeled as having no stage.

metadata <- sample_metadata %>% 
  inner_join(case_metadata, by = "Case.ID")

metadata <- column_to_rownames(metadata, var = "File.Name")

metadata$ajcc_pathologic_tumor_stage[metadata$Sample.Type == "Normal"] <- "None"


The sample’s count tables are joined together. There are miRNA species present in one or several samples that are not in others. With the merging, these counts are changed from NA to 0. Furthermore, the sample file names are used as column names and, given that the file names in the metadata and the counts table contain different separator characters, the column names in the counts table are changed to match the metadata. Finally, all the loaded information is stored in an edgeR (Robinson, McCarthy, and Smyth 2010).

options(scipen=999)

fnames <- list.files("./mirna_samples", full.names = T)
mirna_counts <-  read.csv(fnames[1],sep = "\t", header = TRUE)

for (table in fnames[-1]){
  table <- read.csv(table, header = TRUE, sep = "\t")
  mirna_counts <- mirna_counts %>% full_join(table)
}

mirna_counts[is.na(mirna_counts)] <- 0

rownames(mirna_counts) <- mirna_counts[,1]
mirna_counts <- mirna_counts[,-1]

colnames(mirna_counts) <-gsub("\\.", "-", colnames(mirna_counts))

dge <- DGEList(counts = mirna_counts, group = as.factor(metadata$Sample.Type))
options(digits = 2)


Exploratory Data Analysis

In this exploratory data analysis the nature of the data is analyzed from different perspectives as to identify trends, relationships and possible anomalies such as outliers or batch effects.

Data Distribution

The first visualization focuses on the miRNA species counts per sample as a way to get a glance of the count’s distribution. In the following plot, each horizontal line represents a miRNA species. For an easier interpretation, samples are colored.


The most notable aspect of the plot is the presence of low counts between the different samples. Due to the MIRFLOWZ unambiguous notation, there are very specific isomiRs present in low frequency in just a few samples. Hence, this trend was expected. On the other hand, a second set of miRNA species has a greater presence among the samples with up to 1000000 counts and only four samples stand out, being the higher count from the sample E2-A1LH-01A-11R-A14C-13 in the hsa-miR-21-5p canonical miRNA.

From a statistical point of view, genes with consistently low counts are very unlikely be assessed as significantly differentially expressed because low counts do not provide enough statistical evidence for a reliable judgement to be made. Such genes can therefore be removed from the analysis without any loss of information.

In order to asses which miRNAs will be kept, the edgeR function filterByExpr is used. This function bases the filtering on count-per-million (CPM) values so as to avoid favoring genes that are expressed in larger libraries over those expressed in smaller libraries following a similar approach as the one used by Chen, Lun, and Smyth (2016).

mirna2keep <- filterByExpr(dge)
dge <- dge[mirna2keep, , keep.lib.sizes=F]


Furthermore, to eliminate composition biases between libraries, the trimmed mean of M-values (TMM) variant proposed by Robinson and Oshlack (2010), “TMM with singleton pairing” ( TMMwsp ), is applied to the data set with the edgeR function calcNormFactors. This variant is preferred over the normal TMM as it is intended to perform better for data with a high proportion of zeros. As a result, an extra field with the library scaling factors is added to the DGEList object.

dge <- calcNormFactors(dge, method = "TMMwsp")


Principal Component Analysis

In order to accomplish a greater understanding of the data, a Principal Component Analysis (PCA) is performed using the R function prcomp. The PCA must be conducted over the normalized counts which are obtained with the edgeR function cpm. In addition, the normalized counts are returned as their log2 value.

The main goal of this analysis is to assess whether the different miRNA species are differentially expressed across tissues, each sample is colored according to it. Mind that as the final plot will be interactive, all the possible relevant information about each sample will be displayed when hovering over it.

log_norm_mirna <- cpm(dge, log = T, normalized.lib.sizes = )
PCA <- prcomp(t(log_norm_mirna), scale. = T)


In this second visualization, three outliers are found: samples BH-A0BZ-01A-31R-A12O-13, BH-A1FN-01A-11R-A13P-13 and E2-A1LH-01A-11R-A14C-13. Even if the former is the only one that has been already spotted as such, all three are removed from the data set. Prior to the PCA, the filtering of low expressed miRNAs and the counts normalization are performed again in order to capture the lacking of these miRNA species.

outliers <-  c("BH-A0BZ-01A-31R-A12O-13",
               "BH-A1FN-01A-11R-A13P-13",
               "E2-A1LH-01A-11R-A14C-13")

dge <- dge[, !rownames(dge$samples) %in% outliers]
metadata <- metadata[!rownames(metadata) %in% outliers, ]

mirna2keep <- filterByExpr(dge)
dge <- dge[mirna2keep, , keep.lib.sizes = F]

dge <- calcNormFactors(dge, method = "TMMwsp")
log_norm_mirna <- cpm(dge, log = T, normalized.lib.sizes = T)

PCA <- prcomp(t(log_norm_mirna), scale. = T)


There is a clear tendency in the 4 different clusters. Some parameters such as the sample’s vital status had been used in order to assess if there is any relation between them and the clustering. At the end, the sample’s origin plate and whether is present or not in both kind of tissues is the cause of it.


Plates A090 and A057 exhibit this effect the most followed by A085 (with a single sample) and A010. From these four plates, plates A057, A085 and A010 are the ones that were only used for tumor samples, while plate A090 was only used for normal tissue samples. It is important to mention that the small sample set used in this study, could be the reason behind this effect to be so large.


Differential Expression Analysis


It is of interest to see which miRNA species are expressed at different levels across tissues. With this goal in mind, a linear model is going to be fitted, to perform the differential expression analysis.


Design Matrix and Dispersion Estimation

The first step towards the objective is to build a design matrix. The design matrix links each tissue (columns) to the samples that belong to it (rows). Even if a slightly batch effect has been observed due to the plate of origin, the design in this study is considered to a one-way design under the assumption that the difference observed is not due to the plate of origin to be a potential confounder but an effect of the sample size.

design_mat <- model.matrix(~0 + dge$samples$group)
colnames(design_mat) <- levels(dge$samples$group)
  Normal Tumor
1      0     1
2      1     0
3      0     1
4      1     0
5      0     1
6      1     0


Due the nature of the data to be analyzed, there could be biological variability that may turn the observed counts into noise. Estimating this variability within the different tissues is going to help on modeling the statistical uncertainty associated with the observed counts and on making accurate inferences about differential expression between them.

The dispersion estimates, are computed with the edgeR function estimateDisp. This function uses the negative binomial distribution to model the read counts per gene per sample. With this function three different dispersion estimates are computed. The first one is an empirical Bayes moderated dispersion for each individual miRNA species (tagwise). The second one, the common dispersion, is a global estimate averaged across all species. Finally, a predicted dispersion for each miRNA is obtained from its abundance (trended).

In addition, to guard the tagwise estimates from outlier miRNAs with remarkably large or small individual dispersions, the robust = TRUE option is used (Phipson et al. 2016). The dispersion estimates are visualized with the edgeR function plotBCV. The biological coefficient of variation in the y-axis, is the square root of the negative binomial dispersion. In the x-axis the is the mean of the log normalized counts.

dge <- estimateDisp(dge, design_mat, robust=TRUE)


A characteristic of RNA-seq studies, is the tendency for higher negative binomial dispersions in genes with very low counts whereas the dispersion trend decreases asymptoticaly to a constant value for genes with higher counts (Chen, Lun, and Smyth 2016). Once more, due to the isomiRs naming strategy, the data contains a lot of low counts even after removing the less significant miRNA species.


In order to further account for technical variability. the negative binomial model can be extended with quasi-likelihood methods (Lun, Chen, and Smyth 2016) (Lund et al. 2012) . In this approach, the negative binomial dispersion trend describes the biological variability across all miRNAs. This overall trend is used to select the miRNA-specific variability that lay above and below it. Individual dispersions (tagwise) are not used.

Using the edgeR function glmQLFit several empirical Bayes statistics such as the quasi-likelihood dispersion trend, the squeezed quasi-likelihood dispersion estimates or the prior degrees of freedom, as well as the estimated values of the generalized linear model coefficients for each gene are computed. Incorporating robust handling of zero counts, especially when the fitted values of these counts yield useless residual degrees of freedom for the quasi-likelihood dispersion estimation (Phipson et al. 2016), is a focal point of this function. To obtain the squeezed quasi-likelihood dispersion estimates, the function tightens the raw quasi-likelihood dispersion estimates to adhere to a global trend. This serves to mitigate the uncertainty inherent in the estimates, thereby enhancing the statistical power of subsequent tests. Furthermore, the compression magnitude during the estimation is contingent upon the value of the prior degrees of freedom. Substantial prior degrees of freedom estimates indicate quasi-likelihood dispersions with lower variability across miRNA species. In such scenario, it is advisable to engage in robust empirical Bayes moderation. Conversely, smaller prior degrees of freedom estimates imply a higher degree of variability. In these cases, a milder moderation approach is more appropriate. This modulation, ensures that the empirical Bayes procedure adapts to the underlying characteristics of the data, yielding more accurate and meaningful results.

Setting robust = TRUE. as to facilitate the generation of miRNA-specific prior degrees of freedom estimates This approach minimizes the likelihood of encountering false positives from miRNA species with exceedingly high or low raw dispersions. Therefore, it ensures that the analysis remains resilient against data aberrations while amplifying its ability to identify significant differential expression across the majority of miRNAs.

The new dispersion estimates are visualized with the edgeR function plotQLDisp.

fit <- glmQLFit(dge, design_mat, robust=TRUE)


Differential Expression Analysis (p-value)

As a means to retrieve the upcoming results as a comparison between normal and tumor tissues, a contrast matrix is build using the limma (Ritchie et al. 2015) function makeContrasts.

contrast_mat <- makeContrasts(Normal - Tumor, levels = design_mat)


All the subsequent results will now be relative to normal tissues. That is, a positive log2-fold-change (logFC) will designate an over-expressed miRNA species in normal tissues. Conversely, a negative logFC will denote a more highly expressed miRNA species in tumor tissues.

To account for the uncertainty in dispersion estimation in the error rate control, rather than the usual likelihood ratio test, the quasi-likelihood F-tests is performed with the edgeR function glmQLFTest. This approach leads to p-values greater than or equal to the ones that the likelihood ratio tests would had provided using the same negative binomial dispersions.

Finally, the obtained p-values are submitted to a multiple testing correction as to regulate the false discovery rate (FDR) using the Benjamin-Hochberg method. The edgeR function topTags provides a table with all the differentially expressed miRNA species that have an adjusted p-value smaller than or equal to 0.01. The decision of setting the significance level to 0.01 instead of the usual 0.05, is made to account for the small size of the data set and the small batch effect previously seen. This stringent significance level aims to reduce the likelihood of false positives that the mentioned facts could originate. Note that the table with the results is not shown as it is automatically added in the next plot.

res <- glmQLFTest(fit, contrast = contrast_mat)
top <- topTags(res, n = Inf, p.value = 0.01)


Using the Glimma function glimmaVolcano the results of the previous analysis are displayed in an interactive volcano-plot.


Differential Expression Analysis (fold-change)

The previous differential expression analysis, was based on statistical significance. 2131 differentially expressed miRNA species between the normal and tumor tissues have been found among the 3291 present in the data set. Having this huge amount of differentially expressed miRNA species, incites narrowing down of the results to keep only the ones that are more biologically meaningful.

This new approach focuses on spotting whether the differential expression logFCs are significantly broad than log2(1.5) by using the edgeR function glmTreat. This method, prioritizes genes that are likely to have a higher biological significance with larger changes. It is worth mentioning that glmTreat does not set a fold change cutoff but evaluates both, the magnitude of change of expressions values and its variability. Thus, not only the logFC estimated value must be above the threshold, but its whole confidence interval must.

res2 <- glmTreat(fit, contrast = contrast_mat, lfc = log2(1.5))
top2 <- topTags(res2, n = Inf, p.value = 0.01)


The final amount of differentially expressed miRNA species is 1440. Once more, using the Glimma function glimmaVolcano the results of the analysis are visualized in an interactive volcano-plot.


Bibliography



Chen, Yunshun, Aaron T. L. Lun, and Gordon K. Smyth. 2016. “From Reads to Genes to Pathways: Differential Expression Analysis of RNA-Seq Experiments Using Rsubread and the edgeR Quasi-Likelihood Pipeline,” no. 5:1438 (August). https://doi.org/10.12688/f1000research.8987.2.
Lun, Aaron T. L., Yunshun Chen, and Gordon K. Smyth. 2016. “It’s DE-licious: A Recipe for Differential Expression Analyses of RNA-seq Experiments Using Quasi-Likelihood Methods in edgeR.” In Statistical Genomics: Methods and Protocols, edited by Ewy Mathé and Sean Davis, 391–416. Methods in Molecular Biology. New York, NY: Springer. https://doi.org/10.1007/978-1-4939-3578-9_19.
Lund, Steven P., Dan Nettleton, Davis J. McCarthy, and Gordon K. Smyth. 2012. “Detecting Differential Expression in RNA-sequence Data Using Quasi-Likelihood with Shrunken Dispersion Estimates.” Statistical Applications in Genetics and Molecular Biology 11 (5): /j/sagmb.2012.11.issue-5/1544-6115.1826/1544-6115.1826.xml. https://doi.org/10.1515/1544-6115.1826.
Phipson, Belinda, Stanley Lee, Ian J. Majewski, Warren S. Alexander, and Gordon K. Smyth. 2016. “Robust Hyperparameter Estimation Protects Against Hypervariable Genes and Improves Power to Detect Differential Expression.” The Annals of Applied Statistics 10 (2): 946–63. https://doi.org/10.1214/16-AOAS920.
Ritchie, Matthew E., Belinda Phipson, Di Wu, Yifang Hu, Charity W. Law, Wei Shi, and Gordon K. Smyth. 2015. “Limma Powers Differential Expression Analyses for RNA-sequencing and Microarray Studies.” Nucleic Acids Research 43 (7): e47. https://doi.org/10.1093/nar/gkv007.
Robinson, Mark D., Davis J. McCarthy, and Gordon K. Smyth. 2010. edgeR: A Bioconductor Package for Differential Expression Analysis of Digital Gene Expression Data.” Bioinformatics 26 (1): 139–40. https://doi.org/10.1093/bioinformatics/btp616.
Robinson, Mark D., and Alicia Oshlack. 2010. “A Scaling Normalization Method for Differential Expression Analysis of RNA-seq Data.” Genome Biology 11 (3): R25. https://doi.org/10.1186/gb-2010-11-3-r25.
LS0tCmJpYmxpb2dyYXBoeTogLi4vZmRwLmJpYgpsaW5rLWNpdGF0aW9uczogdHJ1ZQpvdXRwdXQ6CiAgaHRtbF9kb2N1bWVudDoKICAgIGNzczogYm9vdHN0cmFwLmNzcwogICAgaGlnaGxpZ2h0OiBoYWRkb2NrCiAgICBjb2RlX2Rvd25sb2FkOiBUUlVFCi0tLQoKYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9CmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSwgY29tbWVudCA9IE5BLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRSkKCmxpYnJhcnkoZHBseXIpCmxpYnJhcnkoZWRnZVIpCmxpYnJhcnkoZmFjdG9leHRyYSkKbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KEdsaW1tYSkKbGlicmFyeShrYWJsZUV4dHJhKQpsaWJyYXJ5KGtuaXRyKQpsaWJyYXJ5KHBoZWF0bWFwKQpsaWJyYXJ5KHBsb3RseSkKbGlicmFyeShSQ29sb3JCcmV3ZXIpCmxpYnJhcnkocmVhZHhsKQpsaWJyYXJ5KHJlc2hhcGUyKQpsaWJyYXJ5KHN0YXRtb2QpCmxpYnJhcnkodGlkeXZlcnNlKQoKIyBDaGFuZ2luZyBkZWZhdWx0IHRhYmxlJ3Mgc3R5bGUKa2FibGUgPC0gZnVuY3Rpb24oZGF0YSwgLi4uKXsKICAgICAgICBrbml0cjo6a2FibGUoZGF0YSwgCiAgICAgICAgICAgICAgICAgICAgIGZvcm1hdCA9ICJodG1sIiwKICAgICAgICAgICAgICAgICAgICAgZGlnaXRzID0gMywKICAgICAgICAgICAgICAgICAgICAgZm9ybWF0LmFyZ3MgPSBsaXN0KHNjaWVudGlmaWMgPSBUUlVFKSwKICAgICAgICAgICAgICAgICAgICAgYWxpZ24gPSAnYycsCiAgICAgICAgICAgICAgICAgICAgIGxpbmVzZXAgPSAnJykgJT4lCiAgICAgICAga2FibGVfcGFwZXIoKSAlPiUKICAgICAgICBzY3JvbGxfYm94KHdpZHRoID0gIjYwMHB4IiwgaGVpZ2h0ID0gIjQwMHB4IikKfQoKa25pdF9wcmludC5kYXRhLmZyYW1lIDwtIGZ1bmN0aW9uKHgsIC4uLil7CiAgICAgICAgcmVzIDwtIHBhc3RlKGMoJycsICcnLCBrYWJsZSh4KSksCiAgICAgICAgICAgICAgICAgICAgIGNvbGxhcHNlID0gIlxuIikKICAgICAgICBhc2lzX291dHB1dChyZXMpCn0KCnJlZ2lzdGVyUzNtZXRob2QoImtuaXRfcHJpbnQiLCAKICAgICAgICAgICAgICAgICAiZGF0YS5mcmFtZSIsCiAgICAgICAgICAgICAgICAga25pdF9wcmludC5kYXRhLmZyYW1lKQoKIyBQcmludCBub3RoaW5nIGlmIE5BIHdpdGhpbiBhIHRhYmxlCm9wdGlvbnMoa25pdC5rYWJsZS50YWJsZS5OQSA9ICcnKQoKdGhlbWVfc2V0KHRoZW1lX2NsYXNzaWMoKSkKYGBgCgo8YnI+Cgo8aDE+bWlSTkEgYW5kIGlzb21pUiBEaWZmZXJlbnRpYWwgRXhwcmVzc2lvbiBBbmFseXNpcyBpbiBCcmVhc3QgQ2FuY2VyPC9oMT4KCjxoMyBhbGlnbj0icmlnaHQiPklyaXMgTWVzdHJlcyBQYXNjdWFsPC9oMz4KCjxocj4KCjxicj4KCkluIHRoaXMgc3R1ZHksIHRoZSBkaWZmZXJlbnRpYWwgZXhwcmVzc2lvbiBhbmFseXNpcyBvZiB0aGUgcmVzdWx0cyBwcm92aWRlZCBieQp0aGUgKk1JUkZMT1daKiBwaXBlbGluZSBpcyBwZXJmb3JtZWQuIFRoZSBtYWluIGdvYWwgaXMgdG8gYXNzZXMgaWYgdGhlcmUgYXJlIGFueQptaVJOQSBzcGVjaWVzIGRpZmZlcmVudGlhbGx5IGV4cHJlc3NlZCBhY3Jvc3Mgbm9ybWFsIGFuZCB0dW1vciB0aXNzdWVzLiBUaGUKY29uY2x1c2lvbnMgYXJlIGZvdW5kIGluIHRoZSBtYWluIG1hbnVzY3JpcHQuIEhlcmUsIG9ubHkgdGhlIHdvcmtmbG93IHdpdGgKc3RlcC1ieS1zdGVwIGV4cGxhbmF0aW9ucyBpcyByZXBvcnRlZC4KCjxicj4KCjxoMT5EYXRhIFByZXByb2Nlc3Npbmc8L2gxPgo8aHIgY2xhc3M9ImhhbGYtd2lkdGgiPgoKPGJyPgoKPGgyIGFsaWduPSJyaWdodCI+RGF0YSBMb2FkaW5nIGFuZCBTYW1wbGUgU2VsZWN0aW9uPC9oMj4KClRoZSAqTUlSRkxPV1oqIHBpcGVsaW5lIGhhcyBiZWVuIHJhbiB3aXRoIGEgc3BlY2lmaWMgc2V0IG9mIHNhbXBsZXMuIFRoZXJlZm9yZSwgCnRoZSBtZXRhZGF0YSBmb3IgdGhlIGRpZmZlcmVudGlhbCBleHByZXNzaW9uIGFuYWx5c2lzIG11c3QgY29udGFpbiBleGFjdGx5IHRoZSAKc2FtZSBvbmVzLiBUaGlzIGluZm9ybWF0aW9uIGlzIHByb3ZpZGVkIGluIHR3byBkaWZmZXJlbnQgZmlsZXMsIG9uZSBjb250YWluaW5nIApkYXRhICpwZXIgY2FzZSogYW5kIHRoZSBvdGhlciBvbmUgd2l0aCAqcGVyIHNhbXBsZSogaW5mb3JtYXRpb24uCgpUaGUgKnBlciBjYXNlKiBmaWxlLCBjb250YWlucyBkYXRhIGZyb20gTkNJJ3MgQ2FuY2VyIEdlbm9tZSBBdGxhcyBQcm9ncmFtIAooVENHQSkuIFRodXMsIGFmdGVyIGxvYWRpbmcgdGhlIGRhdGEgZnJhbWUsIG9ubHkgdGhlIGNhc2VzIGJlbG9uZ2luZyB0byB0aGUgCmJyZWFzdCBjYW5jZXIgKEJSQ0EpIHN0dWR5IGFyZSBrZXB0LgoKYGBge3IgY2FzZV9tZXRhZGF0YSwgd2FybmluZz1GQUxTRX0KcmF3X2Nhc2VfbWV0YWRhdGEgPC0gcmVhZF9leGNlbCgiY2FzZV9tZXRhZGF0YS54bHN4IiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgc2hlZXQgPSAxLAogICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xfdHlwZXMgPSBOVUxMKQoKY2FzZV9tZXRhZGF0YSA8LSByYXdfY2FzZV9tZXRhZGF0YSAlPiUgCiAgc2VsZWN0KDI6MzQpICU+JSAKICBmaWx0ZXIodHlwZSA9PSAiQlJDQSIpCmBgYAoKPGJyPgoKVGhlICpwZXIgc2FtcGxlKiBtZXRhZGF0YSBpcyBhbHJlYWR5IGZyb20gdGhlIFRDR0EtQlJDQSBzdHVkeSBzbywgZnJvbSB0aGUgZGF0YQpzZXQsIGEgcGFydGljdWxhciBzZXQgb2YgY29sdW1ucyBhcmUgc2VsZWN0ZWQ7IHRoZSBmaWxlIG5hbWUgKGBGaWxlLk5hbWVgKSwgdGhlCmNhc2UgYW5kIHNhbXBsZSBJRCAoYENhc2UuSURgIGFuZCBgU2FtcGxlLklEYCByZXNwZWN0aXZlbHkpLCBhbmQgdGhlIHNhbXBsZSdzCnRpc3N1ZSB0eXBlIChgU2FtcGxlLlR5cGVgKS4gSW4gYWRkaXRpb24sIHRoZSBmaWxlIG5hbWUgd2lsbCBiZSBmb3JtYXR0ZWQgYXMgdG8KbWF0Y2ggdGhlIHNhbXBsZSBuYW1lcyBpbiB0aGUgY291bnRzIHRhYmxlIGFuZCBhbiBleHRyYSBmaWVsZCB3aXRoIHRoZSBzYW1wbGUncwpwbGF0ZSBpcyBhZGRlZC4KCmBgYHtyIHNhbXBsZV9tZXRhZGF0YX0KcmF3X3NhbXBsZV9tZXRhZGF0YSA8LSByZWFkLnRhYmxlKCIuL3NhbXBsZV9tZXRhZGF0YS5jc3YiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2VwID0gIlx0IiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhlYWRlciA9IFRSVUUpCgpzYW1wbGVfbWV0YWRhdGEgPC0gcmF3X3NhbXBsZV9tZXRhZGF0YSAlPiUgCiAgc2VsZWN0KGMoRmlsZS5OYW1lLCBDYXNlLklELCBTYW1wbGUuSUQsIFNhbXBsZS5UeXBlKSkKCmZvciAoaSBpbiAxOm5yb3coc2FtcGxlX21ldGFkYXRhKSl7CiAgc2FtcGxlX21ldGFkYXRhJEZpbGUuTmFtZVtpXSA8LSBzdWJzdHIocmF3X3NhbXBsZV9tZXRhZGF0YSRGaWxlLk5hbWVbaV0sIDYsIDI4KQogIHNhbXBsZV9tZXRhZGF0YSRQbGF0ZVtpXSA8LSBzdHJzcGxpdChzYW1wbGVfbWV0YWRhdGEkRmlsZS5OYW1lW2ldLCAiLSIpW1sxXV1bNV0KfQpgYGAKCjxicj4KCkZvbGxvd2luZyB0aGUgc2FtcGxlIHNlbGVjdGlvbiBjcml0ZXJpYSB1c2VkIGluIHRoZSBtYWluIHJlcG9ydCwgZmVtYWxlIGNhc2VzCmNvbnRhaW5pbmcgc29sZWx5IGEgc2FtcGxlcyBjb21pbmcgZnJvbSBhIG5vcm1hbCB0aXNzdWUgYW5kIGFub3RoZXIgb25lIGZyb20gYQpwcmltYXJ5IHR1bW9yIHdpdGggbm8gbWlzc2luZyBkYXRhIGFyZSBrZXB0LiBJbiBhZGRpdGlvbiwgYW55IGNhc2Ugd2l0aCBhIG5ldwp0dW1vciBldmVudCBvciBoYXMgZGllZCB3aXRob3V0IHR1bW9ycyBpdCBpcyByZW1vdmVkLiBNb3Jlb3Zlciwgb25seQppbmZpbHRyYXRpbmcgZHVjdGFsIGNhcmNpbm9tYSBiZWxsb3cgKlN0YWdlIElJQiogY2FzZXMgYXJlIG1haW50YWluZWQuIEZpbmFsbHksCnRoZSBzYW1wbGUncyB0aXNzdWUgdHlwZXMgYXJlIG1vZGlmaWVkIHRvIGJlIGxhYmVsZWQgYXMgZWl0aGVyIGBOb3JtYWxgIG9yCmBUdW1vcmAuCgpgYGB7ciBzYW1wbGVfc2VsZWN0aW9ufQpzYW1wbGVfbWV0YWRhdGEgPC0gc2FtcGxlX21ldGFkYXRhICU+JSAKICBncm91cF9ieShDYXNlLklEKSAlPiUgCiAgZmlsdGVyKCBuKCkgPT0gMiAsCiAgICAgICAgICBhbnkoU2FtcGxlLlR5cGUgPT0gIlNvbGlkIFRpc3N1ZSBOb3JtYWwiKSwgCiAgICAgICAgICAhU2FtcGxlLlR5cGUgPT0gIk1ldGFzdGF0aWMiKSAlPiUKICBhcnJhbmdlKENhc2UuSUQpCgpzYW1wbGVfbWV0YWRhdGEkU2FtcGxlLlR5cGVbc2FtcGxlX21ldGFkYXRhJFNhbXBsZS5UeXBlID09ICJQcmltYXJ5IFR1bW9yIl0gPC0gIlR1bW9yIgpzYW1wbGVfbWV0YWRhdGEkU2FtcGxlLlR5cGVbc2FtcGxlX21ldGFkYXRhJFNhbXBsZS5UeXBlID09ICJTb2xpZCBUaXNzdWUgTm9ybWFsIl0gPC0gIk5vcm1hbCIKCmNhc2VfbWV0YWRhdGEgPC0gY2FzZV9tZXRhZGF0YSAlPiUKICBmaWx0ZXIoYmNyX3BhdGllbnRfYmFyY29kZSAlaW4lIHNhbXBsZV9tZXRhZGF0YSRDYXNlLklELAogICAgICAgICAhKHZpdGFsX3N0YXR1cyA9PSAiRGVhZCImIHR1bW9yX3N0YXR1cyA9PSAiVFVNT1IgRlJFRSIpLAogICAgICAgICBnZW5kZXIgPT0gIkZFTUFMRSIsCiAgICAgICAgICFpcy5uYSh0dW1vcl9zdGF0dXMpLAogICAgICAgICBpcy5uYShuZXdfdHVtb3JfZXZlbnRfdHlwZSksCiAgICAgICAgIHJhY2UgIT0gIltOb3QgQXZhaWxhYmxlXSIsCiAgICAgICAgIGhpc3RvbG9naWNhbF90eXBlID09ICJJbmZpbHRyYXRpbmcgRHVjdGFsIENhcmNpbm9tYSIsCiAgICAgICAgICFhamNjX3BhdGhvbG9naWNfdHVtb3Jfc3RhZ2UgJWluJSBjKCJTdGFnZSBJSUlCIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJTdGFnZSBJSUlDIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJTdGFnZSBJViIpKSAlPiUKICBzZWxlY3QoYmNyX3BhdGllbnRfYmFyY29kZSwgCiAgICAgICAgIGFnZV9hdF9pbml0aWFsX3BhdGhvbG9naWNfZGlhZ25vc2lzLAogICAgICAgICByYWNlLCAKICAgICAgICAgYWpjY19wYXRob2xvZ2ljX3R1bW9yX3N0YWdlLAogICAgICAgICB2aXRhbF9zdGF0dXMsCiAgICAgICAgIHR1bW9yX3N0YXR1cywgCiAgICAgICAgIG1lbm9wYXVzZV9zdGF0dXMsIAogICAgICAgICBpbml0aWFsX3BhdGhvbG9naWNfZHhfeWVhciwgCiAgICAgICAgIGJpcnRoX2RheXNfdG8sIAogICAgICAgICBkZWF0aF9kYXlzX3RvKSAKY29sbmFtZXMoY2FzZV9tZXRhZGF0YSlbMV0gPC0gIkNhc2UuSUQiCgpzYW1wbGVfbWV0YWRhdGEgPC0gc2FtcGxlX21ldGFkYXRhICU+JQogIGZpbHRlcihDYXNlLklEICVpbiUgY2FzZV9tZXRhZGF0YSRDYXNlLklEKSAlPiUKICBzZWxlY3QoRmlsZS5OYW1lLCBDYXNlLklELCBTYW1wbGUuVHlwZSwgUGxhdGUpICU+JQogIGFycmFuZ2UoRmlsZS5OYW1lKQpgYGAKCjxicj4KClRoZSBmaWx0ZXJlZCBkYXRhIHNldHMgYXJlIGNvbWJpbmVkIGludG8gYSBzaW5nbGUgb25lLiBUaGUgbWVyZ2UgbGVhZHMgdG8gaGF2ZQpzYW1wbGVzIGNvbWluZyBmcm9tIG5vcm1hbCB0aXNzdWVzIHRvIGhhdmUgYSBjYW5jZXIgc3RhZ2UgYXNzaWduZWQgdGh1cywgdGhleQphcmUgbGFiZWxlZCBhcyBoYXZpbmcgbm8gc3RhZ2UuCgpgYGB7ciBtZXJnZV9tZXRhZGF0YX0KbWV0YWRhdGEgPC0gc2FtcGxlX21ldGFkYXRhICU+JSAKICBpbm5lcl9qb2luKGNhc2VfbWV0YWRhdGEsIGJ5ID0gIkNhc2UuSUQiKQoKbWV0YWRhdGEgPC0gY29sdW1uX3RvX3Jvd25hbWVzKG1ldGFkYXRhLCB2YXIgPSAiRmlsZS5OYW1lIikKCm1ldGFkYXRhJGFqY2NfcGF0aG9sb2dpY190dW1vcl9zdGFnZVttZXRhZGF0YSRTYW1wbGUuVHlwZSA9PSAiTm9ybWFsIl0gPC0gIk5vbmUiCmBgYAoKPGJyPgoKVGhlIHNhbXBsZSdzIGNvdW50IHRhYmxlcyBhcmUgam9pbmVkIHRvZ2V0aGVyLiBUaGVyZSBhcmUgbWlSTkEgc3BlY2llcyBwcmVzZW50CmluIG9uZSBvciBzZXZlcmFsIHNhbXBsZXMgdGhhdCBhcmUgbm90IGluIG90aGVycy4gV2l0aCB0aGUgbWVyZ2luZywgdGhlc2UgY291bnRzCmFyZSBjaGFuZ2VkIGZyb20gYE5BYCB0byAwLiBGdXJ0aGVybW9yZSwgdGhlIHNhbXBsZSBmaWxlIG5hbWVzIGFyZSB1c2VkIGFzCmNvbHVtbiBuYW1lcyBhbmQsIGdpdmVuIHRoYXQgdGhlIGZpbGUgbmFtZXMgaW4gdGhlIG1ldGFkYXRhIGFuZCB0aGUgY291bnRzIHRhYmxlCmNvbnRhaW4gZGlmZmVyZW50IHNlcGFyYXRvciBjaGFyYWN0ZXJzLCB0aGUgY29sdW1uIG5hbWVzIGluIHRoZSBjb3VudHMgdGFibGUgYXJlCmNoYW5nZWQgdG8gbWF0Y2ggdGhlIG1ldGFkYXRhLiBGaW5hbGx5LCBhbGwgdGhlIGxvYWRlZCBpbmZvcm1hdGlvbiBpcyBzdG9yZWQgaW4KYW4gYGVkZ2VSYCBbQHJvYmluc29uRWRnZVJCaW9jb25kdWN0b3JQYWNrYWdlMjAxMF0uCgpgYGB7ciBlZGdlUl9taXItdGFibGVzfQpvcHRpb25zKHNjaXBlbj05OTkpCgpmbmFtZXMgPC0gbGlzdC5maWxlcygiLi9taXJuYV9zYW1wbGVzIiwgZnVsbC5uYW1lcyA9IFQpCm1pcm5hX2NvdW50cyA8LSAgcmVhZC5jc3YoZm5hbWVzWzFdLHNlcCA9ICJcdCIsIGhlYWRlciA9IFRSVUUpCgpmb3IgKHRhYmxlIGluIGZuYW1lc1stMV0pewogIHRhYmxlIDwtIHJlYWQuY3N2KHRhYmxlLCBoZWFkZXIgPSBUUlVFLCBzZXAgPSAiXHQiKQogIG1pcm5hX2NvdW50cyA8LSBtaXJuYV9jb3VudHMgJT4lIGZ1bGxfam9pbih0YWJsZSkKfQoKbWlybmFfY291bnRzW2lzLm5hKG1pcm5hX2NvdW50cyldIDwtIDAKCnJvd25hbWVzKG1pcm5hX2NvdW50cykgPC0gbWlybmFfY291bnRzWywxXQptaXJuYV9jb3VudHMgPC0gbWlybmFfY291bnRzWywtMV0KCmNvbG5hbWVzKG1pcm5hX2NvdW50cykgPC1nc3ViKCJcXC4iLCAiLSIsIGNvbG5hbWVzKG1pcm5hX2NvdW50cykpCgpkZ2UgPC0gREdFTGlzdChjb3VudHMgPSBtaXJuYV9jb3VudHMsIGdyb3VwID0gYXMuZmFjdG9yKG1ldGFkYXRhJFNhbXBsZS5UeXBlKSkKb3B0aW9ucyhkaWdpdHMgPSAyKQpgYGAKCjxicj4KCjxoMiBhbGlnbj0icmlnaHQiPkV4cGxvcmF0b3J5IERhdGEgQW5hbHlzaXM8L2gyPgoKSW4gdGhpcyBleHBsb3JhdG9yeSBkYXRhIGFuYWx5c2lzIHRoZSBuYXR1cmUgb2YgdGhlIGRhdGEgaXMgYW5hbHl6ZWQgZnJvbQpkaWZmZXJlbnQgcGVyc3BlY3RpdmVzIGFzIHRvIGlkZW50aWZ5IHRyZW5kcywgcmVsYXRpb25zaGlwcyBhbmQgcG9zc2libGUKYW5vbWFsaWVzIHN1Y2ggYXMgb3V0bGllcnMgb3IgYmF0Y2ggZWZmZWN0cy4KCjxoMyBhbGlnbj0icmlnaHQiPkRhdGEgRGlzdHJpYnV0aW9uPC9oMz4KClRoZSBmaXJzdCB2aXN1YWxpemF0aW9uIGZvY3VzZXMgb24gdGhlIG1pUk5BIHNwZWNpZXMgY291bnRzIHBlciBzYW1wbGUgYXMgYQp3YXkgdG8gZ2V0IGEgZ2xhbmNlIG9mIHRoZSBjb3VudCdzIGRpc3RyaWJ1dGlvbi4gSW4gdGhlIGZvbGxvd2luZyBwbG90LCBlYWNoCmhvcml6b250YWwgbGluZSByZXByZXNlbnRzIGEgbWlSTkEgc3BlY2llcy4gRm9yIGFuIGVhc2llciBpbnRlcnByZXRhdGlvbiwKc2FtcGxlcyBhcmUgY29sb3JlZC4KCmBgYHtyIGNvdW50cGVyc2FtcGxlLCBlY2hvPUYsIGZpZy5hbGlnbiA9ICdjZW50ZXInfQptaXJuYV9tZWx0IDwtIG1lbHQoYXMubWF0cml4KGRnZSRjb3VudHMpKQoKdCA8LSBnZ3Bsb3QobWlybmFfbWVsdCwgYWVzKHggPSBWYXIyLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHkgPSB2YWx1ZSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBncm91cCA9IFZhcjEsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sb3IgPSBWYXIyKSkgKwogIGdlb21fbGluZShzdGF0ID0gImlkZW50aXR5IikgKwogIGxhYnMoIHRpdGxlID0gIm1pUk5BIFNwZWNpZXMgQ291bnRzIHBlciBTYW1wbGUiLCAKICAgICAgICB4ID0gIlNhbXBsZSIsIHkgPSAiQ291bnRzIikgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIiwgCiAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCkpCnQKYGBgCgo8YnI+CgpUaGUgbW9zdCBub3RhYmxlIGFzcGVjdCBvZiB0aGUgcGxvdCBpcyB0aGUgcHJlc2VuY2Ugb2YgbG93IGNvdW50cyBiZXR3ZWVuIHRoZQpkaWZmZXJlbnQgc2FtcGxlcy4gRHVlIHRvIHRoZSAqTUlSRkxPV1oqIHVuYW1iaWd1b3VzIG5vdGF0aW9uLCB0aGVyZSBhcmUgdmVyeQpzcGVjaWZpYyBpc29taVJzIHByZXNlbnQgaW4gbG93IGZyZXF1ZW5jeSBpbiBqdXN0IGEgZmV3IHNhbXBsZXMuIEhlbmNlLCB0aGlzCnRyZW5kIHdhcyBleHBlY3RlZC4gT24gdGhlIG90aGVyIGhhbmQsIGEgc2Vjb25kIHNldCBvZiBtaVJOQSBzcGVjaWVzIGhhcyBhCmdyZWF0ZXIgcHJlc2VuY2UgYW1vbmcgdGhlIHNhbXBsZXMgd2l0aCB1cCB0byAxMDAwMDAwIGNvdW50cyBhbmQgb25seSBmb3VyCnNhbXBsZXMgc3RhbmQgb3V0LCBiZWluZyB0aGUgaGlnaGVyIGNvdW50IGZyb20gdGhlIHNhbXBsZQpgRTItQTFMSC0wMUEtMTFSLUExNEMtMTNgIGluIHRoZSBgaHNhLW1pUi0yMS01cGAgY2Fub25pY2FsIG1pUk5BLgoKRnJvbSBhIHN0YXRpc3RpY2FsIHBvaW50IG9mIHZpZXcsIGdlbmVzIHdpdGggY29uc2lzdGVudGx5IGxvdyBjb3VudHMgYXJlIHZlcnkKdW5saWtlbHkgYmUgYXNzZXNzZWQgYXMgc2lnbmlmaWNhbnRseSBkaWZmZXJlbnRpYWxseSBleHByZXNzZWQgYmVjYXVzZSBsb3cKY291bnRzIGRvIG5vdCBwcm92aWRlIGVub3VnaCBzdGF0aXN0aWNhbCBldmlkZW5jZSBmb3IgYSByZWxpYWJsZSBqdWRnZW1lbnQgdG8gYmUKbWFkZS4gU3VjaCBnZW5lcyBjYW4gdGhlcmVmb3JlIGJlIHJlbW92ZWQgZnJvbSB0aGUgYW5hbHlzaXMgd2l0aG91dCBhbnkgbG9zcyBvZgppbmZvcm1hdGlvbi4KCkluIG9yZGVyIHRvIGFzc2VzIHdoaWNoIG1pUk5BcyB3aWxsIGJlIGtlcHQsIHRoZSBgZWRnZVJgIGZ1bmN0aW9uIGBmaWx0ZXJCeUV4cHJgCmlzIHVzZWQuIFRoaXMgZnVuY3Rpb24gYmFzZXMgdGhlIGZpbHRlcmluZyBvbiBjb3VudC1wZXItbWlsbGlvbiAoQ1BNKSB2YWx1ZXMgc28KYXMgdG8gYXZvaWQgZmF2b3JpbmcgZ2VuZXMgdGhhdCBhcmUgZXhwcmVzc2VkIGluIGxhcmdlciBsaWJyYXJpZXMgb3ZlciB0aG9zZQpleHByZXNzZWQgaW4gc21hbGxlciBsaWJyYXJpZXMgZm9sbG93aW5nIGEgc2ltaWxhciBhcHByb2FjaCBhcyB0aGUgb25lIHVzZWQgYnkKQGNoZW5SZWFkc0dlbmVzUGF0aHdheXMyMDE2LgoKYGBge3IgbWlybmEya2VlcH0KbWlybmEya2VlcCA8LSBmaWx0ZXJCeUV4cHIoZGdlKQpkZ2UgPC0gZGdlW21pcm5hMmtlZXAsICwga2VlcC5saWIuc2l6ZXM9Rl0KYGBgCgo8YnI+CgpGdXJ0aGVybW9yZSwgdG8gZWxpbWluYXRlIGNvbXBvc2l0aW9uIGJpYXNlcyBiZXR3ZWVuIGxpYnJhcmllcywgdGhlIHRyaW1tZWQgbWVhbgpvZiBNLXZhbHVlcyAoVE1NKSB2YXJpYW50IHByb3Bvc2VkIGJ5IEByb2JpbnNvblNjYWxpbmdOb3JtYWxpemF0aW9uTWV0aG9kMjAxMGEsCiJUTU0gd2l0aCBzaW5nbGV0b24gcGFpcmluZyIgKCAqVE1Nd3NwKiApLCBpcyBhcHBsaWVkIHRvIHRoZSBkYXRhIHNldCB3aXRoIHRoZQpgZWRnZVJgIGZ1bmN0aW9uIGBjYWxjTm9ybUZhY3RvcnNgLiBUaGlzIHZhcmlhbnQgaXMgcHJlZmVycmVkIG92ZXIgdGhlIG5vcm1hbApUTU0gYXMgaXQgaXMgaW50ZW5kZWQgdG8gcGVyZm9ybSBiZXR0ZXIgZm9yIGRhdGEgd2l0aCBhIGhpZ2ggcHJvcG9ydGlvbiBvZgp6ZXJvcy4gQXMgYSByZXN1bHQsIGFuIGV4dHJhIGZpZWxkIHdpdGggdGhlIGxpYnJhcnkgc2NhbGluZyBmYWN0b3JzIGlzIGFkZGVkIHRvCnRoZSBgREdFTGlzdGAgb2JqZWN0LgoKYGBge3Igbm9ybWFsaXphdGlvbn0KZGdlIDwtIGNhbGNOb3JtRmFjdG9ycyhkZ2UsIG1ldGhvZCA9ICJUTU13c3AiKQpgYGAKCjxicj4KCjxoMyBhbGlnbj0icmlnaHQiPlByaW5jaXBhbCBDb21wb25lbnQgQW5hbHlzaXM8L2gzPgoKSW4gb3JkZXIgdG8gYWNjb21wbGlzaCBhIGdyZWF0ZXIgdW5kZXJzdGFuZGluZyBvZiB0aGUgZGF0YSwgYSBQcmluY2lwYWwKQ29tcG9uZW50IEFuYWx5c2lzIChQQ0EpIGlzIHBlcmZvcm1lZCB1c2luZyB0aGUgYFJgIGZ1bmN0aW9uIGBwcmNvbXBgLiBUaGUgUENBCm11c3QgYmUgY29uZHVjdGVkIG92ZXIgdGhlIG5vcm1hbGl6ZWQgY291bnRzIHdoaWNoIGFyZSBvYnRhaW5lZCB3aXRoIHRoZSBgZWRnZVJgCmZ1bmN0aW9uIGBjcG1gLiBJbiBhZGRpdGlvbiwgdGhlIG5vcm1hbGl6ZWQgY291bnRzIGFyZSByZXR1cm5lZCBhcyB0aGVpciBsb2d+Mn4KdmFsdWUuCgpUaGUgbWFpbiBnb2FsIG9mIHRoaXMgYW5hbHlzaXMgaXMgdG8gYXNzZXNzIHdoZXRoZXIgdGhlIGRpZmZlcmVudCBtaVJOQSBzcGVjaWVzCmFyZSBkaWZmZXJlbnRpYWxseSBleHByZXNzZWQgYWNyb3NzIHRpc3N1ZXMsIGVhY2ggc2FtcGxlIGlzIGNvbG9yZWQgYWNjb3JkaW5nIHRvCml0LiBNaW5kIHRoYXQgYXMgdGhlIGZpbmFsIHBsb3Qgd2lsbCBiZSBpbnRlcmFjdGl2ZSwgYWxsIHRoZSBwb3NzaWJsZSByZWxldmFudAppbmZvcm1hdGlvbiBhYm91dCBlYWNoIHNhbXBsZSB3aWxsIGJlIGRpc3BsYXllZCB3aGVuIGhvdmVyaW5nIG92ZXIgaXQuCgpgYGB7ciB0aXNzdWUgUENBfQpsb2dfbm9ybV9taXJuYSA8LSBjcG0oZGdlLCBsb2cgPSBULCBub3JtYWxpemVkLmxpYi5zaXplcyA9ICkKUENBIDwtIHByY29tcCh0KGxvZ19ub3JtX21pcm5hKSwgc2NhbGUuID0gVCkKYGBgCgpgYGB7ciBwbG90X3Rpc3N1ZS1QQ0EsIGVjaG89RiwgZmlnLmFsaWduID0gJ2NlbnRlcid9CmNvbXBvbmVudHMgPC0gZGF0YS5mcmFtZShQQ0EkeCkKCkNhc2UgPC0gbWV0YWRhdGEkQ2FzZS5JRApTYW1wbGUgPC0gcm93bmFtZXMoY29tcG9uZW50cykKU3RhZ2UgPC0gbWV0YWRhdGEkYWpjY19wYXRob2xvZ2ljX3R1bW9yX3N0YWdlClRpc3N1ZSA8LSBtZXRhZGF0YSRTYW1wbGUuVHlwZQoiVml0YWwgc3RhdHVzIiA8LSBtZXRhZGF0YSR2aXRhbF9zdGF0dXMKCmdnIDwtIGdncGxvdChjb21wb25lbnRzLGFlcyh4ID0gUEMxLCB5PSBQQzIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgY2FzZSA9IENhc2UsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBzYW1wbGUgPSBTYW1wbGUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBzaGFwZSA9IFRpc3N1ZSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xvciA9IFRpc3N1ZSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdGFnZSA9IFN0YWdlLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZpdGFsID0gYFZpdGFsIHN0YXR1c2ApKSArCiAgICBnZW9tX3BvaW50KGFscGhhID0gMC44LCBzaXplID0gNCkgKwogIGdlb21faGxpbmUoeWludGVyY2VwdD0wLCBsaW5ldHlwZT0iZGFzaGVkIiwgYWxwaGEgPSAwLjIpICsKICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQ9MCwgbGluZXR5cGU9ImRhc2hlZCIsIGFscGhhID0gMC4yKSArCiAgbGFicyh0aXRsZSA9ICJtaVJOQSBFeHByZXNzaW9uIEFjcm9zcyBTYW1wbGVzIFBDQSIsIAogICAgICAgeCA9ICJQQzEgKDIzLjAxJSkiLCAKICAgICAgIHkgPSAiUEMyICg5LjM1JSkiKSArCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcz1jKCIjNDBFMEQwIiwiIzhBMkJFMiIpKSArCiAgc2NhbGVfc2hhcGVfbWFudWFsKHZhbHVlcyA9IGMoMSwgNSkpCgpnZ3Bsb3RseShnZykKYGBgCgo8YnI+CgpJbiB0aGlzIHNlY29uZCB2aXN1YWxpemF0aW9uLCB0aHJlZSBvdXRsaWVycyBhcmUgZm91bmQ6IHNhbXBsZXMKYEJILUEwQlotMDFBLTMxUi1BMTJPLTEzYCwgYEJILUExRk4tMDFBLTExUi1BMTNQLTEzYCBhbmQKYEUyLUExTEgtMDFBLTExUi1BMTRDLTEzYC4gRXZlbiBpZiB0aGUgZm9ybWVyIGlzIHRoZSBvbmx5IG9uZSB0aGF0IGhhcyBiZWVuCmFscmVhZHkgc3BvdHRlZCBhcyBzdWNoLCBhbGwgdGhyZWUgYXJlIHJlbW92ZWQgZnJvbSB0aGUgZGF0YSBzZXQuIFByaW9yIHRvIHRoZQpQQ0EsIHRoZSBmaWx0ZXJpbmcgb2YgbG93IGV4cHJlc3NlZCBtaVJOQXMgYW5kIHRoZSBjb3VudHMgbm9ybWFsaXphdGlvbiBhcmUKcGVyZm9ybWVkIGFnYWluIGluIG9yZGVyIHRvIGNhcHR1cmUgdGhlIGxhY2tpbmcgb2YgdGhlc2UgbWlSTkEgc3BlY2llcy4KCmBgYHtyIHJlbW92ZU91dGxpZXJzfQpvdXRsaWVycyA8LSAgYygiQkgtQTBCWi0wMUEtMzFSLUExMk8tMTMiLAogICAgICAgICAgICAgICAiQkgtQTFGTi0wMUEtMTFSLUExM1AtMTMiLAogICAgICAgICAgICAgICAiRTItQTFMSC0wMUEtMTFSLUExNEMtMTMiKQoKZGdlIDwtIGRnZVssICFyb3duYW1lcyhkZ2Ukc2FtcGxlcykgJWluJSBvdXRsaWVyc10KbWV0YWRhdGEgPC0gbWV0YWRhdGFbIXJvd25hbWVzKG1ldGFkYXRhKSAlaW4lIG91dGxpZXJzLCBdCgptaXJuYTJrZWVwIDwtIGZpbHRlckJ5RXhwcihkZ2UpCmRnZSA8LSBkZ2VbbWlybmEya2VlcCwgLCBrZWVwLmxpYi5zaXplcyA9IEZdCgpkZ2UgPC0gY2FsY05vcm1GYWN0b3JzKGRnZSwgbWV0aG9kID0gIlRNTXdzcCIpCmxvZ19ub3JtX21pcm5hIDwtIGNwbShkZ2UsIGxvZyA9IFQsIG5vcm1hbGl6ZWQubGliLnNpemVzID0gVCkKClBDQSA8LSBwcmNvbXAodChsb2dfbm9ybV9taXJuYSksIHNjYWxlLiA9IFQpCmBgYAoKYGBge3IgcGxvdF9yZW1vdmVPdXRsaWVycywgZWNobz1GLCBmaWcuYWxpZ24gPSAnY2VudGVyJ30KY29tcG9uZW50cyA8LSBkYXRhLmZyYW1lKFBDQSR4KQoKQ2FzZSA8LSBtZXRhZGF0YSRDYXNlLklEClNhbXBsZSA8LSByb3duYW1lcyhjb21wb25lbnRzKQpTdGFnZSA8LSBtZXRhZGF0YSRhamNjX3BhdGhvbG9naWNfdHVtb3Jfc3RhZ2UKVGlzc3VlIDwtIG1ldGFkYXRhJFNhbXBsZS5UeXBlCiJWaXRhbCBzdGF0dXMiIDwtIG1ldGFkYXRhJHZpdGFsX3N0YXR1cwoKZ2cgPC0gZ2dwbG90KGNvbXBvbmVudHMsYWVzKHggPSBQQzEsIHk9IFBDMiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBjYXNlID0gQ2FzZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNhbXBsZSA9IFNhbXBsZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNoYXBlID0gVGlzc3VlLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbG9yID0gVGlzc3VlLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN0YWdlID0gU3RhZ2UsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgdml0YWwgPSBgVml0YWwgc3RhdHVzYCkpICsKICAgIGdlb21fcG9pbnQoYWxwaGEgPSAwLjgsIHNpemUgPSA0KSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0PTAsIGxpbmV0eXBlPSJkYXNoZWQiLCBhbHBoYSA9IDAuMikgKwogIGdlb21fdmxpbmUoeGludGVyY2VwdD0wLCBsaW5ldHlwZT0iZGFzaGVkIiwgYWxwaGEgPSAwLjIpICsKICBsYWJzKHRpdGxlID0gIm1pUk5BIEV4cHJlc3Npb24gQWNyb3NzIFNhbXBsZXMgUENBXG53aXRob3V0IG91dGxpZXJzIiwgCiAgICAgICB4ID0gIlBDMSAoMjcuMzclKSIsIAogICAgICAgeSA9ICJQQzIgKDE0LjAyJSkiKSArCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcz1jKCIjNDBFMEQwIiwiIzhBMkJFMiIpKSArCiAgc2NhbGVfc2hhcGVfbWFudWFsKHZhbHVlcyA9IGMoMSwgNSkpCgpnZ3Bsb3RseShnZykKYGBgCgo8YnI+CgpUaGVyZSBpcyBhIGNsZWFyIHRlbmRlbmN5IGluIHRoZSA0IGRpZmZlcmVudCBjbHVzdGVycy4gU29tZSBwYXJhbWV0ZXJzIHN1Y2ggYXMKdGhlIHNhbXBsZSdzIHZpdGFsIHN0YXR1cyBoYWQgYmVlbiB1c2VkIGluIG9yZGVyIHRvIGFzc2VzcyBpZiB0aGVyZSBpcyBhbnkKcmVsYXRpb24gYmV0d2VlbiB0aGVtIGFuZCB0aGUgY2x1c3RlcmluZy4gQXQgdGhlIGVuZCwgdGhlIHNhbXBsZSdzIG9yaWdpbiBwbGF0ZQphbmQgd2hldGhlciBpcyBwcmVzZW50IG9yIG5vdCBpbiBib3RoIGtpbmQgb2YgdGlzc3VlcyBpcyB0aGUgY2F1c2Ugb2YgaXQuCgpgYGB7ciBwbG90X3BsYXRlLVBDQSwgZWNobz1GLCBmaWcuYWxpZ24gPSAnY2VudGVyJ30KUGxhdGUgPC0gbWV0YWRhdGEkUGxhdGUKCnBsYXRlc19jb2wgPSBjKCAiIzRCMDA4MiIsICIjMDBCRkZGIiwgIiNGRjAwMDAiLCAiI0VCMjJCRSIsICIjN0NGQzAwIiwKICAgICAgICAgICAgICAgICIjRkYxNDkzIiwgIiM4QTJCRTIiLCAiIzIyOEIyMiIsICIjQzcxNTg1IiwgIiNGRjhDMDAiLAogICAgICAgICAgICAgICAgIiM5OTAwQ0MiLCAiI0E1MkEyQSIsICIjMDBGRkZGIiwgIiNGRkQ3MDAiLCAiIzAwRkE5QSIpCgpnZyA8LSBnZ3Bsb3QoY29tcG9uZW50cyxhZXMoeCA9IFBDMSwgeT0gUEMyLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNhc2UgPSBDYXNlLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgc2FtcGxlID0gU2FtcGxlLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RhZ2UgPSBTdGFnZSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xvciA9IFBsYXRlLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgc2hhcGUgPSBUaXNzdWUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICB2aXRhbCA9IGBWaXRhbCBzdGF0dXNgKSkgKwogICAgZ2VvbV9wb2ludChhbHBoYSA9IDAuOCwgc2l6ZSA9IDQpICsKICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQ9MCwgbGluZXR5cGU9ImRhc2hlZCIsIGFscGhhID0gMC4yKSArCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0PTAsIGxpbmV0eXBlPSJkYXNoZWQiLCBhbHBoYSA9IDAuMikgKwogIGxhYnModGl0bGUgPSAibWlSTkEgRXhwcmVzc2lvbiBBY3Jvc3MgU2FtcGxlcyBwZXIgUGxhdGUgUENBIiwgCiAgICAgICB4ID0gIlBDMSAoMjcuMzclKSIsIAogICAgICAgeSA9ICJQQzIgKDE0LjAyJSkiKSArCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IHBsYXRlc19jb2wpICsKICBzY2FsZV9zaGFwZV9tYW51YWwodmFsdWVzID0gYygxLCA1KSkgCgpnZ3Bsb3RseShnZykKYGBgCgo8YnI+CgpQbGF0ZXMgYEEwOTBgIGFuZCBgQTA1N2AgZXhoaWJpdCB0aGlzIGVmZmVjdCB0aGUgbW9zdCBmb2xsb3dlZCBieSBgQTA4NWAgKHdpdGggYQpzaW5nbGUgc2FtcGxlKSBhbmQgYEEwMTBgLiBGcm9tIHRoZXNlIGZvdXIgcGxhdGVzLCBwbGF0ZXMgYEEwNTdgLCBgQTA4NWAgYW5kIApgQTAxMGAgYXJlIHRoZSBvbmVzIHRoYXQgd2VyZSBvbmx5IHVzZWQgZm9yIHR1bW9yIHNhbXBsZXMsIHdoaWxlIHBsYXRlIGBBMDkwYAp3YXMgb25seSB1c2VkIGZvciBub3JtYWwgdGlzc3VlIHNhbXBsZXMuIEl0IGlzIGltcG9ydGFudCB0byBtZW50aW9uIHRoYXQgdGhlCnNtYWxsIHNhbXBsZSBzZXQgdXNlZCBpbiB0aGlzIHN0dWR5LCBjb3VsZCBiZSB0aGUgcmVhc29uIGJlaGluZCB0aGlzIGVmZmVjdCB0bwpiZSBzbyBsYXJnZS4KCjxicj4KCjxoMT5EaWZmZXJlbnRpYWwgRXhwcmVzc2lvbiBBbmFseXNpczwvaDE+CjxociBjbGFzcz0iaGFsZi13aWR0aDIiPgoKSXQgaXMgb2YgaW50ZXJlc3QgdG8gc2VlIHdoaWNoIG1pUk5BIHNwZWNpZXMgYXJlIGV4cHJlc3NlZCBhdCBkaWZmZXJlbnQgbGV2ZWxzCmFjcm9zcyB0aXNzdWVzLiBXaXRoIHRoaXMgZ29hbCBpbiBtaW5kLCBhIGxpbmVhciBtb2RlbCBpcyBnb2luZyB0byBiZSBmaXR0ZWQsIHRvCnBlcmZvcm0gdGhlIGRpZmZlcmVudGlhbCBleHByZXNzaW9uIGFuYWx5c2lzLgoKPGJyPgoKPGgyIGFsaWduPSJyaWdodCI+RGVzaWduIE1hdHJpeCBhbmQgRGlzcGVyc2lvbiBFc3RpbWF0aW9uPC9oMj4KClRoZSBmaXJzdCBzdGVwIHRvd2FyZHMgdGhlIG9iamVjdGl2ZSBpcyB0byBidWlsZCBhIGRlc2lnbiBtYXRyaXguIFRoZSBkZXNpZ24KbWF0cml4IGxpbmtzIGVhY2ggdGlzc3VlIChjb2x1bW5zKSB0byB0aGUgc2FtcGxlcyB0aGF0IGJlbG9uZyB0byBpdCAocm93cykuIEV2ZW4KaWYgYSBzbGlnaHRseSBiYXRjaCBlZmZlY3QgaGFzIGJlZW4gb2JzZXJ2ZWQgZHVlIHRvIHRoZSBwbGF0ZSBvZiBvcmlnaW4sIHRoZQpkZXNpZ24gaW4gdGhpcyBzdHVkeSBpcyBjb25zaWRlcmVkIHRvIGEgb25lLXdheSBkZXNpZ24gdW5kZXIgdGhlIGFzc3VtcHRpb24gdGhhdAp0aGUgZGlmZmVyZW5jZSBvYnNlcnZlZCBpcyBub3QgZHVlIHRvIHRoZSBwbGF0ZSBvZiBvcmlnaW4gdG8gYmUgYSBwb3RlbnRpYWwKY29uZm91bmRlciBidXQgYW4gZWZmZWN0IG9mIHRoZSBzYW1wbGUgc2l6ZS4KCmBgYHtyIGRlc2lnbiBtYXQudjJ9CmRlc2lnbl9tYXQgPC0gbW9kZWwubWF0cml4KH4wICsgZGdlJHNhbXBsZXMkZ3JvdXApCmNvbG5hbWVzKGRlc2lnbl9tYXQpIDwtIGxldmVscyhkZ2Ukc2FtcGxlcyRncm91cCkKYGBgCgpgYGB7ciB2aWV3X2Rlc2lnbi1tYXQsIGVjaG89Rn0KaGVhZChkZXNpZ25fbWF0KQpgYGAKCjxicj4KCkR1ZSB0aGUgbmF0dXJlIG9mIHRoZSBkYXRhIHRvIGJlIGFuYWx5emVkLCB0aGVyZSBjb3VsZCBiZSBiaW9sb2dpY2FsIHZhcmlhYmlsaXR5CnRoYXQgbWF5IHR1cm4gdGhlIG9ic2VydmVkIGNvdW50cyBpbnRvIG5vaXNlLiBFc3RpbWF0aW5nIHRoaXMgdmFyaWFiaWxpdHkgd2l0aGluCnRoZSBkaWZmZXJlbnQgdGlzc3VlcyBpcyBnb2luZyB0byBoZWxwIG9uIG1vZGVsaW5nIHRoZSBzdGF0aXN0aWNhbCB1bmNlcnRhaW50eQphc3NvY2lhdGVkIHdpdGggdGhlIG9ic2VydmVkIGNvdW50cyBhbmQgb24gbWFraW5nIGFjY3VyYXRlIGluZmVyZW5jZXMgYWJvdXQKZGlmZmVyZW50aWFsIGV4cHJlc3Npb24gYmV0d2VlbiB0aGVtLgoKVGhlIGRpc3BlcnNpb24gZXN0aW1hdGVzLCBhcmUgY29tcHV0ZWQgd2l0aCB0aGUgYGVkZ2VSYCBmdW5jdGlvbiBgZXN0aW1hdGVEaXNwYC4KVGhpcyBmdW5jdGlvbiB1c2VzIHRoZSBuZWdhdGl2ZSBiaW5vbWlhbCBkaXN0cmlidXRpb24gdG8gbW9kZWwgdGhlIHJlYWQgY291bnRzCnBlciBnZW5lIHBlciBzYW1wbGUuIFdpdGggdGhpcyBmdW5jdGlvbiB0aHJlZSBkaWZmZXJlbnQgZGlzcGVyc2lvbiBlc3RpbWF0ZXMgYXJlCmNvbXB1dGVkLiBUaGUgZmlyc3Qgb25lIGlzIGFuIGVtcGlyaWNhbCBCYXllcyBtb2RlcmF0ZWQgZGlzcGVyc2lvbiBmb3IgZWFjaAppbmRpdmlkdWFsIG1pUk5BIHNwZWNpZXMgKCp0YWd3aXNlKikuIFRoZSBzZWNvbmQgb25lLCB0aGUgY29tbW9uIGRpc3BlcnNpb24sIGlzCmEgZ2xvYmFsIGVzdGltYXRlIGF2ZXJhZ2VkIGFjcm9zcyBhbGwgc3BlY2llcy4gRmluYWxseSwgYSBwcmVkaWN0ZWQgZGlzcGVyc2lvbgpmb3IgZWFjaCBtaVJOQSBpcyBvYnRhaW5lZCBmcm9tIGl0cyBhYnVuZGFuY2UgKCp0cmVuZGVkKikuCgpJbiBhZGRpdGlvbiwgdG8gZ3VhcmQgdGhlICp0YWd3aXNlKiBlc3RpbWF0ZXMgZnJvbSBvdXRsaWVyIG1pUk5BcyB3aXRoCnJlbWFya2FibHkgbGFyZ2Ugb3Igc21hbGwgaW5kaXZpZHVhbCBkaXNwZXJzaW9ucywgdGhlIGByb2J1c3QgPSBUUlVFYCBvcHRpb24gaXMKdXNlZCBbQHBoaXBzb25Sb2J1c3RIeXBlcnBhcmFtZXRlckVzdGltYXRpb24yMDE2XS4gVGhlIGRpc3BlcnNpb24gZXN0aW1hdGVzIGFyZQp2aXN1YWxpemVkIHdpdGggdGhlIGBlZGdlUmAgZnVuY3Rpb24gYHBsb3RCQ1ZgLiBUaGUgYmlvbG9naWNhbCBjb2VmZmljaWVudCBvZgp2YXJpYXRpb24gaW4gdGhlIGB5LWF4aXNgLCBpcyB0aGUgc3F1YXJlIHJvb3Qgb2YgdGhlIG5lZ2F0aXZlIGJpbm9taWFsCmRpc3BlcnNpb24uIEluIHRoZSBgeC1heGlzYCB0aGUgaXMgdGhlIG1lYW4gb2YgdGhlIGxvZyBub3JtYWxpemVkIGNvdW50cy4KCmBgYHtyIGRpc3AuZXN0aW1hdH0KZGdlIDwtIGVzdGltYXRlRGlzcChkZ2UsIGRlc2lnbl9tYXQsIHJvYnVzdD1UUlVFKQpgYGAKCmBgYHtyIHBsb3RfZGlzLmVzdGltYXQsIGVjaG89RiwgZmlnLmFsaWduID0gJ2NlbnRlcid9CnBsb3RCQ1YoZGdlLCBtYWluID0gIm1pUk5BIENvdW50cyBEaXNwZXJzaW9uIEVzdGltYXRlcyIsIAogICAgICAgIGZyYW1lLnBsb3Q9RkFMU0UgLCAKICAgICAgICBjb2wudHJlbmQgPSAiIzhBMkJFMiIsIGNvbC5jb21tb24gPSAiIzAwQkZGRiIpCmJveChidHk9ImwiKQpheGlzKDIpCmF4aXMoMSkKYGBgCgo8YnI+CgpBIGNoYXJhY3RlcmlzdGljIG9mIFJOQS1zZXEgc3R1ZGllcywgaXMgdGhlIHRlbmRlbmN5IGZvciBoaWdoZXIgbmVnYXRpdmUKYmlub21pYWwgZGlzcGVyc2lvbnMgaW4gZ2VuZXMgd2l0aCB2ZXJ5IGxvdyBjb3VudHMgd2hlcmVhcyB0aGUgZGlzcGVyc2lvbiB0cmVuZApkZWNyZWFzZXMgYXN5bXB0b3RpY2FseSB0byBhIGNvbnN0YW50IHZhbHVlIGZvciBnZW5lcyB3aXRoIGhpZ2hlciBjb3VudHMKW0BjaGVuUmVhZHNHZW5lc1BhdGh3YXlzMjAxNl0uIE9uY2UgbW9yZSwgZHVlIHRvIHRoZSBpc29taVJzIG5hbWluZyBzdHJhdGVneSwKdGhlIGRhdGEgY29udGFpbnMgYSBsb3Qgb2YgbG93IGNvdW50cyBldmVuIGFmdGVyIHJlbW92aW5nIHRoZSBsZXNzIHNpZ25pZmljYW50Cm1pUk5BIHNwZWNpZXMuCgpgYGB7ciBwbG90X2NvdW50cy1kaXN0LCBlY2hvPUYsIGZpZy5hbGlnbiA9ICdjZW50ZXInfQptaXJuYV9tZWx0IDwtIG1lbHQobG9nX25vcm1fbWlybmEpCgpnZ3Bsb3QobWlybmFfbWVsdCwgYWVzKHggPSB2YWx1ZSkpICsKICBnZW9tX2hpc3RvZ3JhbShiaW53aWR0aCA9IDAuNSwgCiAgICAgICAgICAgICAgICAgY29sb3IgPSAiI0ZGRkZGRiIsIAogICAgICAgICAgICAgICAgIGZpbGwgPSAiIzk5MDBDQyIsCiAgICAgICAgICAgICAgICAgYWxwaGEgPSAwLjcpICsKICBsYWJzKCB0aXRsZSA9ICJOb3JtYWxpemVkIGxvZyBDb3VudHMgRGlzdHJpYnV0aW9uIiwgCiAgICAgICAgeCA9ICJDb3VudHMiLCB5ID0gIm1pUk5BIHNwZWNpZXMiKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKQpgYGAKCjxicj4KCkluIG9yZGVyIHRvIGZ1cnRoZXIgYWNjb3VudCBmb3IgdGVjaG5pY2FsIHZhcmlhYmlsaXR5LiB0aGUgbmVnYXRpdmUgYmlub21pYWwKbW9kZWwgY2FuIGJlIGV4dGVuZGVkIHdpdGggcXVhc2ktbGlrZWxpaG9vZCBtZXRob2RzIFtAbHVuSXRERWxpY2lvdXNSZWNpcGUyMDE2YV0KW0BsdW5kRGV0ZWN0aW5nRGlmZmVyZW50aWFsRXhwcmVzc2lvbjIwMTJdIC4gSW4gdGhpcyBhcHByb2FjaCwgdGhlIG5lZ2F0aXZlCmJpbm9taWFsIGRpc3BlcnNpb24gdHJlbmQgZGVzY3JpYmVzIHRoZSBiaW9sb2dpY2FsIHZhcmlhYmlsaXR5IGFjcm9zcyBhbGwKbWlSTkFzLiBUaGlzIG92ZXJhbGwgdHJlbmQgaXMgdXNlZCB0byBzZWxlY3QgdGhlIG1pUk5BLXNwZWNpZmljIHZhcmlhYmlsaXR5IHRoYXQKbGF5IGFib3ZlIGFuZCBiZWxvdyBpdC4gSW5kaXZpZHVhbCBkaXNwZXJzaW9ucyAoKnRhZ3dpc2UqKSBhcmUgbm90IHVzZWQuCgpVc2luZyB0aGUgYGVkZ2VSYCBmdW5jdGlvbiBgZ2xtUUxGaXRgIHNldmVyYWwgZW1waXJpY2FsIEJheWVzIHN0YXRpc3RpY3Mgc3VjaCBhcwp0aGUgcXVhc2ktbGlrZWxpaG9vZCBkaXNwZXJzaW9uIHRyZW5kLCB0aGUgc3F1ZWV6ZWQgcXVhc2ktbGlrZWxpaG9vZCBkaXNwZXJzaW9uCmVzdGltYXRlcyBvciB0aGUgcHJpb3IgZGVncmVlcyBvZiBmcmVlZG9tLCBhcyB3ZWxsIGFzIHRoZSBlc3RpbWF0ZWQgdmFsdWVzIG9mCnRoZSBnZW5lcmFsaXplZCBsaW5lYXIgbW9kZWwgY29lZmZpY2llbnRzIGZvciBlYWNoIGdlbmUgYXJlIGNvbXB1dGVkLgpJbmNvcnBvcmF0aW5nIHJvYnVzdCBoYW5kbGluZyBvZiB6ZXJvIGNvdW50cywgZXNwZWNpYWxseSB3aGVuIHRoZSBmaXR0ZWQgdmFsdWVzCm9mIHRoZXNlIGNvdW50cyB5aWVsZCB1c2VsZXNzIHJlc2lkdWFsIGRlZ3JlZXMgb2YgZnJlZWRvbSBmb3IgdGhlCnF1YXNpLWxpa2VsaWhvb2QgZGlzcGVyc2lvbiBlc3RpbWF0aW9uIApbQHBoaXBzb25Sb2J1c3RIeXBlcnBhcmFtZXRlckVzdGltYXRpb24yMDE2XSwgaXMgYSBmb2NhbCBwb2ludCBvZiB0aGlzIGZ1bmN0aW9uLgpUbyBvYnRhaW4gdGhlIHNxdWVlemVkIHF1YXNpLWxpa2VsaWhvb2QgZGlzcGVyc2lvbiBlc3RpbWF0ZXMsIHRoZSBmdW5jdGlvbgp0aWdodGVucyB0aGUgcmF3IHF1YXNpLWxpa2VsaWhvb2QgZGlzcGVyc2lvbiBlc3RpbWF0ZXMgdG8gYWRoZXJlIHRvIGEgZ2xvYmFsCnRyZW5kLiBUaGlzIHNlcnZlcyB0byBtaXRpZ2F0ZSB0aGUgdW5jZXJ0YWludHkgaW5oZXJlbnQgaW4gdGhlIGVzdGltYXRlcywKdGhlcmVieSBlbmhhbmNpbmcgdGhlIHN0YXRpc3RpY2FsIHBvd2VyIG9mIHN1YnNlcXVlbnQgdGVzdHMuIEZ1cnRoZXJtb3JlLCB0aGUKY29tcHJlc3Npb24gbWFnbml0dWRlIGR1cmluZyB0aGUgZXN0aW1hdGlvbiBpcyBjb250aW5nZW50IHVwb24gdGhlIHZhbHVlIG9mIHRoZQpwcmlvciBkZWdyZWVzIG9mIGZyZWVkb20uIFN1YnN0YW50aWFsIHByaW9yIGRlZ3JlZXMgb2YgZnJlZWRvbSBlc3RpbWF0ZXMKaW5kaWNhdGUgcXVhc2ktbGlrZWxpaG9vZCBkaXNwZXJzaW9ucyB3aXRoIGxvd2VyIHZhcmlhYmlsaXR5IGFjcm9zcyBtaVJOQQpzcGVjaWVzLiBJbiBzdWNoIHNjZW5hcmlvLCBpdCBpcyBhZHZpc2FibGUgdG8gZW5nYWdlIGluIHJvYnVzdCBlbXBpcmljYWwgQmF5ZXMKbW9kZXJhdGlvbi4gQ29udmVyc2VseSwgc21hbGxlciBwcmlvciBkZWdyZWVzIG9mIGZyZWVkb20gZXN0aW1hdGVzIGltcGx5IGEgCmhpZ2hlciBkZWdyZWUgb2YgdmFyaWFiaWxpdHkuIEluIHRoZXNlIGNhc2VzLCBhIG1pbGRlciBtb2RlcmF0aW9uIGFwcHJvYWNoIGlzIAptb3JlIGFwcHJvcHJpYXRlLiBUaGlzIG1vZHVsYXRpb24sIGVuc3VyZXMgdGhhdCB0aGUgZW1waXJpY2FsIEJheWVzIHByb2NlZHVyZQphZGFwdHMgdG8gdGhlIHVuZGVybHlpbmcgY2hhcmFjdGVyaXN0aWNzIG9mIHRoZSBkYXRhLCB5aWVsZGluZyBtb3JlIGFjY3VyYXRlIGFuZAptZWFuaW5nZnVsIHJlc3VsdHMuCgpTZXR0aW5nIGByb2J1c3QgPSBUUlVFYC4gYXMgdG8gZmFjaWxpdGF0ZSB0aGUgZ2VuZXJhdGlvbiBvZiBtaVJOQS1zcGVjaWZpYyBwcmlvcgpkZWdyZWVzIG9mIGZyZWVkb20gZXN0aW1hdGVzIFRoaXMgYXBwcm9hY2ggbWluaW1pemVzIHRoZSBsaWtlbGlob29kIG9mIAplbmNvdW50ZXJpbmcgZmFsc2UgcG9zaXRpdmVzIGZyb20gbWlSTkEgc3BlY2llcyB3aXRoIGV4Y2VlZGluZ2x5IGhpZ2ggb3IgbG93IHJhdwpkaXNwZXJzaW9ucy4gVGhlcmVmb3JlLCBpdCBlbnN1cmVzIHRoYXQgdGhlIGFuYWx5c2lzIHJlbWFpbnMgcmVzaWxpZW50IGFnYWluc3QgCmRhdGEgYWJlcnJhdGlvbnMgd2hpbGUgYW1wbGlmeWluZyBpdHMgYWJpbGl0eSB0byBpZGVudGlmeSBzaWduaWZpY2FudCAKZGlmZmVyZW50aWFsIGV4cHJlc3Npb24gYWNyb3NzIHRoZSBtYWpvcml0eSBvZiBtaVJOQXMuCgpUaGUgbmV3IGRpc3BlcnNpb24gZXN0aW1hdGVzIGFyZSB2aXN1YWxpemVkIHdpdGggdGhlIGBlZGdlUmAgZnVuY3Rpb24KYHBsb3RRTERpc3BgLgoKYGBge3IgcXVhc2ktbGlrZWxpRml0fQpmaXQgPC0gZ2xtUUxGaXQoZGdlLCBkZXNpZ25fbWF0LCByb2J1c3Q9VFJVRSkKYGBgCgpgYGB7ciBwbG90X3F1YXNpLWxpa2VsaUZpdCwgZWNobz1GLCBmaWcuYWxpZ24gPSAnY2VudGVyJ30KcGxvdFFMRGlzcChmaXQsICBtYWluID0gIm1pUk5BIENvdW50cyBRdWFzaS1MaWtlbGlob29kIERpc3BlcnNpb24gRXN0aW1hdGVzIiwgCiAgICAgICAgICAgZnJhbWUucGxvdD1GQUxTRSAsIAogICAgICAgICAgIGNvbC50cmVuZCA9ICIjOEEyQkUyIiwgY29sLnNocnVuayA9ICIjMDBCRkZGIikKYm94KGJ0eT0ibCIpCmF4aXMoMikKYXhpcygxKQpgYGAKCjxicj4KCjxoMiBhbGlnbj0icmlnaHQiPkRpZmZlcmVudGlhbCBFeHByZXNzaW9uIEFuYWx5c2lzIChwLXZhbHVlKTwvaDI+CgpBcyBhIG1lYW5zIHRvIHJldHJpZXZlIHRoZSB1cGNvbWluZyByZXN1bHRzIGFzIGEgY29tcGFyaXNvbiBiZXR3ZWVuIG5vcm1hbCBhbmQKdHVtb3IgdGlzc3VlcywgYSBjb250cmFzdCBtYXRyaXggaXMgYnVpbGQgdXNpbmcgdGhlIGBsaW1tYWAKW0ByaXRjaGllTGltbWFQb3dlcnNEaWZmZXJlbnRpYWwyMDE1XSBmdW5jdGlvbiBgbWFrZUNvbnRyYXN0c2AuCgpgYGB7ciBjb250cmFzdF9tYXR9CmNvbnRyYXN0X21hdCA8LSBtYWtlQ29udHJhc3RzKE5vcm1hbCAtIFR1bW9yLCBsZXZlbHMgPSBkZXNpZ25fbWF0KQpgYGAKCjxicj4KCkFsbCB0aGUgc3Vic2VxdWVudCByZXN1bHRzIHdpbGwgbm93IGJlIHJlbGF0aXZlIHRvIG5vcm1hbCB0aXNzdWVzLiBUaGF0IGlzLCBhCnBvc2l0aXZlIGxvZ34yfi1mb2xkLWNoYW5nZSAobG9nRkMpIHdpbGwgZGVzaWduYXRlIGFuIG92ZXItZXhwcmVzc2VkIG1pUk5BIApzcGVjaWVzIGluIG5vcm1hbCB0aXNzdWVzLiBDb252ZXJzZWx5LCBhIG5lZ2F0aXZlIGxvZ0ZDIHdpbGwgZGVub3RlIGEgbW9yZQpoaWdobHkgZXhwcmVzc2VkIG1pUk5BIHNwZWNpZXMgaW4gdHVtb3IgdGlzc3Vlcy4KClRvIGFjY291bnQgZm9yIHRoZSB1bmNlcnRhaW50eSBpbiBkaXNwZXJzaW9uIGVzdGltYXRpb24gaW4gdGhlIGVycm9yIHJhdGUKY29udHJvbCwgcmF0aGVyIHRoYW4gdGhlIHVzdWFsIGxpa2VsaWhvb2QgcmF0aW8gdGVzdCwgdGhlIHF1YXNpLWxpa2VsaWhvb2QKRi10ZXN0cyBpcyBwZXJmb3JtZWQgd2l0aCB0aGUgYGVkZ2VSYCBmdW5jdGlvbiBgZ2xtUUxGVGVzdGAuIFRoaXMgYXBwcm9hY2ggbGVhZHMKdG8gKnAtdmFsdWVzKiBncmVhdGVyIHRoYW4gb3IgZXF1YWwgdG8gdGhlIG9uZXMgdGhhdCB0aGUgbGlrZWxpaG9vZCByYXRpbyB0ZXN0cwp3b3VsZCBoYWQgcHJvdmlkZWQgdXNpbmcgdGhlIHNhbWUgbmVnYXRpdmUgYmlub21pYWwgZGlzcGVyc2lvbnMuCgpGaW5hbGx5LCB0aGUgb2J0YWluZWQgKnAtdmFsdWVzKiBhcmUgc3VibWl0dGVkIHRvIGEgbXVsdGlwbGUgdGVzdGluZyBjb3JyZWN0aW9uCmFzIHRvIHJlZ3VsYXRlIHRoZSBmYWxzZSBkaXNjb3ZlcnkgcmF0ZSAoRkRSKSB1c2luZyB0aGUgQmVuamFtaW4tSG9jaGJlcmcKbWV0aG9kLiBUaGUgYGVkZ2VSYCBmdW5jdGlvbiBgdG9wVGFnc2AgcHJvdmlkZXMgYSB0YWJsZSB3aXRoIGFsbCB0aGUKZGlmZmVyZW50aWFsbHkgZXhwcmVzc2VkIG1pUk5BIHNwZWNpZXMgdGhhdCBoYXZlIGFuIGFkanVzdGVkICpwLXZhbHVlKiBzbWFsbGVyCnRoYW4gb3IgZXF1YWwgdG8gMC4wMS4gVGhlIGRlY2lzaW9uIG9mIHNldHRpbmcgdGhlIHNpZ25pZmljYW5jZSBsZXZlbCB0byAwLjAxCmluc3RlYWQgb2YgdGhlIHVzdWFsIDAuMDUsIGlzIG1hZGUgdG8gYWNjb3VudCBmb3IgdGhlIHNtYWxsIHNpemUgb2YgdGhlIGRhdGEgc2V0CmFuZCB0aGUgc21hbGwgYmF0Y2ggZWZmZWN0IHByZXZpb3VzbHkgc2Vlbi4gVGhpcyBzdHJpbmdlbnQgc2lnbmlmaWNhbmNlIGxldmVsCmFpbXMgdG8gcmVkdWNlIHRoZSBsaWtlbGlob29kIG9mIGZhbHNlIHBvc2l0aXZlcyB0aGF0IHRoZSBtZW50aW9uZWQgZmFjdHMgY291bGQKb3JpZ2luYXRlLiBOb3RlIHRoYXQgdGhlIHRhYmxlIHdpdGggdGhlIHJlc3VsdHMgaXMgbm90IHNob3duIGFzIGl0IGlzCmF1dG9tYXRpY2FsbHkgYWRkZWQgaW4gdGhlIG5leHQgcGxvdC4KCmBgYHtyIHFsZlRlc3R9CnJlcyA8LSBnbG1RTEZUZXN0KGZpdCwgY29udHJhc3QgPSBjb250cmFzdF9tYXQpCnRvcCA8LSB0b3BUYWdzKHJlcywgbiA9IEluZiwgcC52YWx1ZSA9IDAuMDEpCmBgYAoKPGJyPgoKVXNpbmcgdGhlIGBHbGltbWFgIGZ1bmN0aW9uIGBnbGltbWFWb2xjYW5vYCB0aGUgcmVzdWx0cyBvZiB0aGUgcHJldmlvdXMgYW5hbHlzaXMKYXJlIGRpc3BsYXllZCBpbiBhbiBpbnRlcmFjdGl2ZSB2b2xjYW5vLXBsb3QuCgpgYGB7ciB2b2xjYW5vLCBlY2hvPUYsIGZpZy5hbGlnbiA9ICdjZW50ZXInfQpnbGltbWFWb2xjYW5vKHJlcywgZGdlID0gZml0LCAKICAgICAgICAgICAgICBzdGF0dXMgPSBkZWNpZGVUZXN0c0RHRShyZXMsIHAudmFsdWUgPSAwLjAxKSwKICAgICAgICAgICAgICBtYWluID0gIk5vcm1hbCB2cyBUdW1vciIpCmBgYAoKPGJyPgoKPGgyIGFsaWduPSJyaWdodCI+RGlmZmVyZW50aWFsIEV4cHJlc3Npb24gQW5hbHlzaXMgKGZvbGQtY2hhbmdlKTwvaDI+CgpUaGUgcHJldmlvdXMgZGlmZmVyZW50aWFsIGV4cHJlc3Npb24gYW5hbHlzaXMsIHdhcyBiYXNlZCBvbiBzdGF0aXN0aWNhbCBzaWduaWZpY2FuY2UuIDIxMzEgZGlmZmVyZW50aWFsbHkgZXhwcmVzc2VkIG1pUk5BIHNwZWNpZXMgYmV0d2VlbiB0aGUgbm9ybWFsIGFuZAp0dW1vciB0aXNzdWVzIGhhdmUgYmVlbiBmb3VuZCBhbW9uZyB0aGUgMzI5MSBwcmVzZW50IGluIHRoZSBkYXRhIHNldC4gSGF2aW5nCnRoaXMgaHVnZSBhbW91bnQgb2YgZGlmZmVyZW50aWFsbHkgZXhwcmVzc2VkIG1pUk5BIHNwZWNpZXMsIGluY2l0ZXMgbmFycm93aW5nCmRvd24gb2YgdGhlIHJlc3VsdHMgdG8ga2VlcCBvbmx5IHRoZSBvbmVzIHRoYXQgYXJlIG1vcmUgYmlvbG9naWNhbGx5IG1lYW5pbmdmdWwuCgpUaGlzIG5ldyBhcHByb2FjaCBmb2N1c2VzIG9uIHNwb3R0aW5nIHdoZXRoZXIgdGhlIGRpZmZlcmVudGlhbCBleHByZXNzaW9uIGxvZ0ZDcwphcmUgc2lnbmlmaWNhbnRseSBicm9hZCB0aGFuIGxvZ34yfigxLjUpIGJ5IHVzaW5nIHRoZSBgZWRnZVJgIGZ1bmN0aW9uCmBnbG1UcmVhdGAuIFRoaXMgbWV0aG9kLCBwcmlvcml0aXplcyBnZW5lcyB0aGF0IGFyZSBsaWtlbHkgdG8gaGF2ZSBhIGhpZ2hlcgpiaW9sb2dpY2FsIHNpZ25pZmljYW5jZSB3aXRoIGxhcmdlciBjaGFuZ2VzLiBJdCBpcyB3b3J0aCBtZW50aW9uaW5nIHRoYXQKYGdsbVRyZWF0YCBkb2VzIG5vdCBzZXQgYSBmb2xkIGNoYW5nZSBjdXRvZmYgYnV0IGV2YWx1YXRlcyBib3RoLCB0aGUgbWFnbml0dWRlCm9mIGNoYW5nZSBvZiBleHByZXNzaW9ucyB2YWx1ZXMgYW5kIGl0cyB2YXJpYWJpbGl0eS4gVGh1cywgbm90IG9ubHkgdGhlIGxvZ0ZDCmVzdGltYXRlZCB2YWx1ZSBtdXN0IGJlIGFib3ZlIHRoZSB0aHJlc2hvbGQsIGJ1dCBpdHMgd2hvbGUgY29uZmlkZW5jZSBpbnRlcnZhbAptdXN0LgoKYGBge3IgZ2xtVHJlYXR9CnJlczIgPC0gZ2xtVHJlYXQoZml0LCBjb250cmFzdCA9IGNvbnRyYXN0X21hdCwgbGZjID0gbG9nMigxLjUpKQp0b3AyIDwtIHRvcFRhZ3MocmVzMiwgbiA9IEluZiwgcC52YWx1ZSA9IDAuMDEpCmBgYAoKPGJyPgoKVGhlIGZpbmFsIGFtb3VudCBvZiBkaWZmZXJlbnRpYWxseSBleHByZXNzZWQgbWlSTkEgc3BlY2llcyBpcyAxNDQwLiBPbmNlIG1vcmUsCnVzaW5nIHRoZSBgR2xpbW1hYCBmdW5jdGlvbiBgZ2xpbW1hVm9sY2Fub2AgdGhlIHJlc3VsdHMgb2YgdGhlIGFuYWx5c2lzIGFyZQp2aXN1YWxpemVkIGluIGFuIGludGVyYWN0aXZlIHZvbGNhbm8tcGxvdC4KCmBgYHtyIHZvbGNhbm8yLCBlY2hvPUYsIGZpZy5hbGlnbiA9ICdjZW50ZXInfQpnbGltbWFWb2xjYW5vKHJlczIsIGRnZT1maXQsIAogICAgICAgICAgICAgIHN0YXR1cyA9IGRlY2lkZVRlc3RzREdFKHJlczIsIHAudmFsdWUgPSAwLjAxKSwgCiAgICAgICAgICAgICAgbWFpbiA9ICJOb3JtYWwgdnMgVHVtb3IiKQpgYGAKCjxicj4KCiMgQmlibGlvZ3JhcGh5Cgo8aHIgY2xhc3M9ImhhbGYtd2lkdGgiPgoKPGJyPgo=