Introduction

This documents describes data import, exploration, preprocessing and analysis of LCMS experiments with xcms version >= 3.

Data import

xcms supports analysis of LC/MS data from files in (AIA/ANDI) NetCDF, mzML/mzXML and mzData format. For demonstration purpose we will analyze a subset of the data from [1] in which the metabolic consequences of knocking out the fatty acid amide hydrolase (FAAH) gene in mice was investigated. The raw data files (in NetCDF format) are provided with the faahKO data package. The data set consists of samples from the spinal cords of 6 knock-out and 6 wild-type mice. Each file contains data in centroid mode acquired in positive ion mode form 200-600 m/z and 2500-4500 seconds.

Below we load all required packages, locate the raw CDF files within the faahKO package and build a phenodata data frame describing the experimental setup.

library(xcms)
Loading required package: Biobase
Loading required package: BiocGenerics
Loading required package: parallel

Attaching package: <U+393C><U+3E31>BiocGenerics<U+393C><U+3E32>

The following objects are masked from <U+393C><U+3E31>package:parallel<U+393C><U+3E32>:

    clusterApply, clusterApplyLB, clusterCall, clusterEvalQ,
    clusterExport, clusterMap, parApply, parCapply, parLapply,
    parLapplyLB, parRapply, parSapply, parSapplyLB

The following objects are masked from <U+393C><U+3E31>package:stats<U+393C><U+3E32>:

    IQR, mad, sd, var, xtabs

The following objects are masked from <U+393C><U+3E31>package:base<U+393C><U+3E32>:

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

Welcome to Bioconductor

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

Loading required package: BiocParallel
Loading required package: MSnbase
Loading required package: mzR
Loading required package: Rcpp
mzR has been built against a different Rcpp version (0.12.16)
than is installed on your system (0.12.17). This might lead to errors
when loading mzR. If you encounter such issues, please send a report,
including the output of sessionInfo() to the Bioc support forum at 
https://support.bioconductor.org/. For details see also
https://github.com/sneumann/mzR/wiki/mzR-Rcpp-compiler-linker-issue.Loading required package: ProtGenerics

This is MSnbase version 2.6.1 
  Visit https://lgatto.github.io/MSnbase/ to get started.


Attaching package: <U+393C><U+3E31>MSnbase<U+393C><U+3E32>

The following object is masked from <U+393C><U+3E31>package:stats<U+393C><U+3E32>:

    smooth

The following object is masked from <U+393C><U+3E31>package:base<U+393C><U+3E32>:

    trimws


This is xcms version 3.2.0 


Attaching package: <U+393C><U+3E31>xcms<U+393C><U+3E32>

The following object is masked from <U+393C><U+3E31>package:stats<U+393C><U+3E32>:

    sigma
library(faahKO)
library(RColorBrewer)
library(pander)
library(magrittr)
## Get the full path to the CDF files
cdfs <- dir(system.file("cdf", package = "faahKO"), full.names = TRUE,
        recursive = TRUE)
## Create a phenodata data.frame
pd <- data.frame(sample_name = sub(basename(cdfs), pattern = ".CDF",
                   replacement = "", fixed = TRUE),
         sample_group = c(rep("KO", 6), rep("WT", 6)),
         stringsAsFactors = FALSE) 
pd

Subsequently we load the raw data as an OnDiskMSnExp object using the readMSData method from the MSnbase package. While the MSnbase package was originally developed for proteomics data processing, many of its functionality, including raw data import and data representation, can be shared and reused in metabolomics data analysis.

raw_data <- readMSData(files = cdfs, pdata = new("NAnnotatedDataFrame", pd),
               mode = "onDisk")
Polarity can not be extracted from netCDF files, please set manually the polarity with the 'polarity' method.

The OnDiskMSnExp object contains general information about the number of spectra, retention times, the measured total ion current etc, but does not contain the full raw data (i.e. the m/z and intensity values from each measured spectrum). Its memory footprint is thus rather small making it an ideal object to represent large metabolomics experiments while still allowing to perform simple quality controls, data inspection and exploration as well as data sub-setting operations. The m/z and intensity values are imported from the raw data files on demand, hence the location of the raw data files should not be changed after initial data import.

Initial data inspection

The OnDiskMSnExp organizes the MS data by spectrum and provides the methods intensity, mz and rtime to access the raw data from the files (the measured intensity values, the corresponding m/z and retention time values). In addition, the spectra method could be used to return all data encapsulated in Spectrum classes. Below we extract the retention time values from the object.

head(rtime(raw_data)) 
F01.S0001 F01.S0002 F01.S0003 F01.S0004 F01.S0005 F01.S0006 
 2501.378  2502.943  2504.508  2506.073  2507.638  2509.203 

All data is returned as one-dimensional vectors (a numeric vector for rtime and a list of numeric vectors for mz and intensity, each containing the values from one spectrum), even if the experiment consists of multiple files/samples. The fromFile function returns a numeric vector that provides the mapping of the values to the originating file. Below we use the fromFile indices to organize the mz values by file.

mzs <- mz(raw_data)
## Split the list by file
mzs_by_file <- split(mzs, f = fromFile(raw_data))
length(mzs_by_file)
[1] 12

As a first evaluation of the data we plot below the base peak chromatogram (BPC) for each file in our experiment. We use the chromatogram method and set the aggregationFun to "max" to return for each spectrum the maximal intensity and hence create the BPC from the raw data. To create a total ion chromatogram we could set aggregationFun to sum.

## Get the base peak chromatograms. This reads data from the files.
bpis <- chromatogram(raw_data, aggregationFun = "max")
## Define colors for the two groups
group_colors <- brewer.pal(3, "Set1")[1:2]
names(group_colors) <- c("KO", "WT")
## Plot all chromatograms.
plot(bpis, col = group_colors[raw_data$sample_group])

Below we extract the chromatogram of the first sample and access its retention time and intensity values.

str(bpis[2,12])
Error in x@.Data[i, j, drop = TRUE] : subscript out of bounds

The chromatogram method supports also extraction of chromatographic data from a m/z-rt slice of the MS data. In the next section we will use this method to create an extracted ion chromatogram (EIC) for a selected peak.

Below we create boxplots representing the distribution of total ion currents per file. Such plots can be very useful to spot problematic or failing MS runs.

## Get the total ion current by file
tc <- split(tic(raw_data), f = fromFile(raw_data))
boxplot(tc, col = group_colors[raw_data$sample_group],
    ylab = "intensity", main = "Total ion current") 

Chromatographic peak detection

Next we perform the chromatographic peak detection using the centWave algorithm [2]. Before running the peak detection it is however strongly suggested to visually inspect e.g. the extracted ion chromatogram of internal standards or known compounds to evaluate and adapt the peak detection settings since the default settings will not be appropriate for most LCMS experiments. The two most critical parameters for centWave are the peakwidth (expected range of chromatographic peak widths) and ppm (maximum expected deviation of m/z values of centroids corresponding to one chromatographic peak; this is usually much larger than the ppm specified by the manufacturer) parameters. To evaluate the typical chromatographic peak width we plot the EIC for one peak.

## Define the rt and m/z range of the peak area
rtr <- c(2700, 2900)
mzr <- c(334.9, 335.1)
## extract the chromatogram
chr_raw <- chromatogram(raw_data, mz = mzr, rt = rtr)
plot(chr_raw, col = group_colors[chr_raw$sample_group])

For the ppm parameter we extract the full MS data (intensity, retention time and m/z values) corresponding to the above peak. To this end we first filter the raw object by retention time, then by m/z and finally plot the object with type = "XIC" to produce the plot below. We use the pipe (%>%) command better illustrate the corresponding workflow.

raw_data %>%
    filterRt(rt = rtr) %>%
    filterMz(mz = mzr) %>%
    plot(type = "XIC") 

For each plot: upper panel: chromatogram plotting the intensity values against the retention time, lower panel m/z against retention time plot. The individual data points are colored according to the intensity.

Below we perform the chromatographic peak detection using the findChromPeaks method. The submitted parameter object defines which algorithm will be used and allows to define the settings for this algorithm. Note that we set the argument noise to 1000 to slightly speed up the analysis by considering only signals with a value larger than 1000 in the peak detection step.

cwp <- CentWaveParam(peakwidth = c(30, 80), noise = 1000)
xdata <- findChromPeaks(raw_data, param = cwp)

The results are returned as an XCMSnExp object which extends the OnDiskMSnExp object by storing also LC/GC-MS preprocessing results. This means also that all methods to sub-set and filter the data or to access the (raw) data are inherited from the OnDiskMSnExp object. The results from the chromatographic peak detection can be accessed with the chromPeaks method.

head(chromPeaks(xdata)) 
        mz mzmin mzmax       rt    rtmin    rtmax       into       intb   maxo
[1,] 236.1 236.1 236.1 2520.158 2501.378 2553.022  301289.53  268211.58  12957
[2,] 362.9 362.9 362.9 2596.840 2587.451 2604.665   19916.60   19674.79   1453
[3,] 302.0 302.0 302.0 2617.185 2587.451 2648.484  699458.52  687162.27  30552
[4,] 316.2 316.2 316.2 2668.828 2661.003 2676.653   51310.09   50854.60   4267
[5,] 370.1 370.1 370.1 2673.523 2643.789 2706.387  458247.09  423667.40  25672
[6,] 360.0 360.0 360.0 2682.913 2635.964 2718.907 9034381.61 8518838.59 317568
     sn sample is_filled
[1,] 13      1         0
[2,] 12      1         0
[3,] 73      1         0
[4,] 17      1         0
[5,] 16      1         0
[6,] 20      1         0

We can also plot the location of the identified chromatographic peaks in the m/z - retention time space for one file using the plotChromPeaks function. Below we plot the chromatographic peaks for the 5th sample.

plotChromPeaks(xdata, file = 5) 

To get a global overview of the peak detection we can plot the frequency of identified peaks per file along the retention time axis. This allows to identify time periods along the MS run in which a higher number of peaks was identified and evaluate whether this is consistent across files.

plotChromPeakImage(xdata) 

Next we highlight the identified chromatographic peaks for the example peak from before. Evaluating such plots on a list of peaks corresponding to known peaks or internal standards helps to ensure that peak detection settings were appropriate and correctly identified the expected peaks. The rectangles indicate the identified chromatographic peaks per sample.

plot(chr_raw, col = group_colors[chr_raw$sample_group], lwd = 3)
highlightChromPeaks(xdata, border = group_colors[chr_raw$sample_group],
            lty = 3, rt = rtr, mz = mzr) 

pander(chromPeaks(xdata, mz = mzr, rt = rtr),
       caption = paste("Identified chromatographic peaks in a selected ",
               "m/z and retention time range."))

-----------------------------------------------------------------------------
 mz    mzmin   mzmax    rt    rtmin   rtmax    into      intb     maxo    sn 
----- ------- ------- ------ ------- ------- --------- --------- ------- ----
 335    335     335    2783   2756    2817    421286    392692    16856   26 

 335    335     335    2788   2756    2821    1529680   1490861   58736   77 

 335    335     335    2788   2758    2822    221355    204694    8158    19 

 335    335     335    2786   2756    2821    299084    281522    9154    22 

 335    335     335    2799   2758    2838    174587    160226    6295    13 

 335    335     335    2788   2756    2822    954335    933983    35856   76 

 335    335     335    2791   2758    2827    1668431   1635820   54640   94 

 335    335     335    2786   2756    2810    644912    632013    20672   54 

 335    335     335    2794   2769    2832    931316    904196    27200   49 
-----------------------------------------------------------------------------

Table: Identified chromatographic peaks in a selected  m/z and retention time range. (continued below)

 
--------------------
 sample   is_filled 
-------- -----------
   1          0     

   2          0     

   3          0     

   4          0     

   5          0     

   8          0     

   9          0     

   10         0     

   11         0     
--------------------

Finally we plot also the distribution of peak intensity per sample. This allows to investigate whether systematic differences in peak signals between samples are present.

## Extract a list of per-sample peak intensities (in log2 scale)
ints <- split(log2(chromPeaks(xdata)[, "into"]),
          f = chromPeaks(xdata)[, "sample"])
boxplot(ints, varwidth = TRUE, col = group_colors[xdata$sample_group],
    ylab = expression(log[2]~intensity), main = "Peak intensities")
grid(nx = NA, ny = NULL) 

Alignment

The time at which analytes elute in the chromatography can vary between samples (and even compounds). Such a difference was already observable in the extracted ion chromatogram plot shown as an example in the previous section. The alignment step, also referred to as retention time correction, aims at adjusting this by shifting signals along the retention time axis to align the signals between different samples within an experiment. The method to perform the alignment/retention time correction in xcms is adjustRtime which uses different alignment algorithms depending on the provided parameter class. In the example below we use the obiwarp method [4] to align the samples. We use a binSize = 0.6 which creates warping functions in mz bins of 0.6. Also here it is advisable to modify the settings for each experiment and evaluate if retention time correction did align internal controls or known compounds properly.

xdata <- adjustRtime(xdata, param = ObiwarpParam(binSize = 0.6)) 

adjustRtime, besides calculating adjusted retention times for each spectrum, does also adjust the reported retention times of the identified chromatographic peaks.

To extract the adjusted retention times we can use the adjustedRtime method, or simply the rtime method that, if present, returns by default adjusted retention times from an XCMSnExp object.

## Or simply use the rtime method
head(rtime(xdata, adjusted = TRUE)) 
F01.S0001 F01.S0002 F01.S0003 F01.S0004 F01.S0005 F01.S0006 
 2501.378  2502.958  2504.538  2506.118  2507.699  2509.280 

To evaluate the impact of the alignment we plot the BPC on the adjusted data. In addition we plot the differences of the adjusted- to the raw retention times per sample using the plotAdjustedRtime function.

## Get the base peak chromatograms.
bpis_adj <- chromatogram(xdata, aggregationFun = "max")
par(mfrow = c(2, 1), mar = c(4.5, 4.2, 1, 0.5))
plot(bpis_adj, col = group_colors[bpis_adj$sample_group])
## Plot also the difference of adjusted to raw retention time.
plotAdjustedRtime(xdata, col = group_colors[xdata$sample_group]) 

Base peak chromatogram after alignment (top) and difference between adjusted and raw retention times along the retention time axis (bottom).

Too large differences between adjusted and raw retention times could indicate poorly performing samples or alignment.

At last we evaluate the impact of the alignment on the test peak.

par(mfrow = c(2, 1))
## Plot the raw data
plot(chr_raw, col = group_colors[chr_raw$sample_group])
## Extract the chromatogram from the adjusted object
chr_adj <- chromatogram(xdata, rt = rtr, mz = mzr)
plot(chr_adj, col = group_colors[chr_raw$sample_group])

Correspondance

The final step in the metabolomics preprocessing is the correspondence that matches detected chromatographic peaks between samples. The method to perform the correspondence in xcms is groupChromPeaks. We will use the peak density method to group chromatographic peaks. The algorithm combines chromatographic peaks depending on the density of peaks along the retention time axis within small slices along the mz dimension.

To illustrate this we plot below the chromatogram for an mz slice with multiple chromatographic peaks within each sample. We use below a value of 0.4 for the minFraction parameter hence only chromatographic peaks present in at least 40% of the samples per sample group are grouped into a feature. The sample group assignment is specified with the sampleGroups argument.

## Define the mz slice.
mzr <- c(305.05, 305.15)
## Extract and plot the chromatograms
chr_mzr <- chromatogram(xdata, mz = mzr, rt = c(2500, 4000))
par(mfrow = c(3, 1), mar = c(1, 4, 1, 0.5))
cols <- group_colors[chr_mzr$sample_group]
plot(chr_mzr, col = cols, xaxt = "n", xlab = "")
## Highlight the detected peaks in that region.
highlightChromPeaks(xdata, mz = mzr, col = cols, type = "point", pch = 16)
## Define the parameters for the peak density method
pdp <- PeakDensityParam(sampleGroups = xdata$sample_group,
            minFraction = 0.99, bw = 30)
par(mar = c(4, 4, 1, 0.5))
plotChromPeakDensity(xdata, mz = mzr, col = cols, param = pdp,
             pch = 16, xlim = c(2500, 4000))
## Use a different bw
pdp <- PeakDensityParam(sampleGroups = xdata$sample_group,
            minFraction = 0.4, bw = 20)
plotChromPeakDensity(xdata, mz = mzr, col = cols, param = pdp,
             pch = 16, xlim = c(2500, 4000))

The upper panel in the plot above shows the extracted ion chromatogram for each sample with the detected peaks highlighted. The middle and lower plot shows the retention time for each detected peak within the different samples. The black solid line represents the density distribution of detected peaks along the retention times. Peaks combined into features (peak groups) are indicated with grey rectangles. Different values for the bw parameter of the PeakDensityParam were used: bw = 30 in the middle and bw = 20 in the lower panel. With the default value for the parameter bw the two neighboring chromatographic peaks would be grouped into the same feature, while with a bw of 20 they would be grouped into separate features. This grouping depends on the parameters for the density function and other parameters passed to the algorithm with the PeakDensityParam.

## Perform the correspondence
pdp <- PeakDensityParam(sampleGroups = xdata$sample_group,
            minFraction = 0.4, bw = 20)
xdata <- groupChromPeaks(xdata, param = pdp) 

The results from the correspondence can be extracted using the featureDefinitions method, that returns a DataFrame with the definition of the features. The featureValues method returns a matrix with rows being features and columns samples. The content of this matrix can be defined using the value argument. Setting value = "into" returns a matrix with the integrated signal of the peaks corresponding to a feature in a sample. Any column name of the chromPeaks matrix can be passed to the argument value. Below we extract the integrated peak intensity per feature/sample.

## Extract the feature definitions
featureDefinitions(xdata)

## Extract the into column for each feature.
head(featureValues(xdata, value = "into"))

This feature matrix contains NA for samples in which no chromatographic peak was detected in the feature’s m/z-rt region. While in many cases there might indeed be no peak signal in the respective region, it might also be that there is signal, but the peak detection algorithm failed to detect a chromatographic peak. xcms provides the fillChromPeaks method to fill in intensity data for such missing values from the original files. The filled in peaks are added to the chromPeaks matrix and are flagged with an 1 in the "is_filled" column. Below we perform such a filling-in of missing peaks.

xdata <- fillChromPeaks(xdata)
Requesting 103 missing peaks from ko15.CDF ... got 100.
Requesting 97 missing peaks from ko16.CDF ... got 94.
Requesting 82 missing peaks from ko18.CDF ... got 81.
Requesting 117 missing peaks from ko19.CDF ... got 115.
Requesting 133 missing peaks from ko21.CDF ... got 125.
Requesting 112 missing peaks from ko22.CDF ... got 106.
Requesting 122 missing peaks from wt15.CDF ... got 115.
Requesting 104 missing peaks from wt16.CDF ... got 99.
Requesting 126 missing peaks from wt18.CDF ... got 120.
Requesting 122 missing peaks from wt19.CDF ... got 118.
Requesting 137 missing peaks from wt21.CDF ... got 129.
Requesting 110 missing peaks from wt22.CDF ... got 102.
head(featureValues(xdata))
      ko15.CDF ko16.CDF ko18.CDF ko19.CDF ko21.CDF ko22.CDF wt15.CDF wt16.CDF
FT001       43      416      912     1242     1500     1727     1978     2350
FT002       25     3898     3992     1232     1495     1724     1968     2341
FT003     3798      399      881     4073     1482     1707     1962     2327
FT004        1      376     3993     4074     4188     4313     1943     2304
FT005      253     3899     1122     1397     4189     1880     4419     2540
FT006       46      424      916     4075     4190     1728     1984     4534
      wt18.CDF wt19.CDF wt21.CDF wt22.CDF
FT001     2705     3000     3270     3508
FT002     2693     2996     3267     3501
FT003     2673     4753     4871     3482
FT004     4633     4754     3235     5000
FT005     4634     3184     4872     5001
FT006     2706     3004     4873     3519

For features without detected peaks in a sample, the method extracts all intensities in the mz-rt region of the feature, integrates the signal and adds a filled-in peak to the chromPeaks matrix. No peak is added if no signal is measured/available for the mz-rt region of the feature. For these, even after filling in missing peak data, a NA is reported in the featureValues matrix.

Below we compare the number of missing values before and after filling in missing values. We can use the parameter filled of the featureValues method to define whether or not filled-in peak values should be returned too.

## Missing values before filling in peaks
apply(featureValues(xdata, filled = FALSE), MARGIN = 2,
      FUN = function(z) sum(is.na(z)))
ko15.CDF ko16.CDF ko18.CDF ko19.CDF ko21.CDF ko22.CDF wt15.CDF wt16.CDF 
     103       97       82      117      133      112      122      104 
wt18.CDF wt19.CDF wt21.CDF wt22.CDF 
     126      122      137      110 
## Missing values after filling in peaks
apply(featureValues(xdata), MARGIN = 2,
      FUN = function(z) sum(is.na(z)))
ko15.CDF ko16.CDF ko18.CDF ko19.CDF ko21.CDF ko22.CDF wt15.CDF wt16.CDF 
       3        3        1        2        8        6        7        5 
wt18.CDF wt19.CDF wt21.CDF wt22.CDF 
       6        4        8        8 

At last we perform a principal component analysis to evaluate the grouping of the samples in this experiment. Note that we did not perform any data normalization hence the grouping might (and will) also be influenced by technical biases.

## Extract the features and log2 transform them
ft_ints <- log2(featureValues(xdata, value = "into"))
## Perform the PCA omitting all features with an NA in any of the
## samples. Also, the intensities are mean centered.
pc <- prcomp(t(na.omit(ft_ints)), center = TRUE)
## Plot the PCA
cols <- group_colors[xdata$sample_group]
pcSummary <- summary(pc)
plot(pc$x[, 1], pc$x[,2], pch = 21, main = "", 
     xlab = paste0("PC1: ", format(pcSummary$importance[2, 1] * 100,
                   digits = 3), " % variance"),
     ylab = paste0("PC2: ", format(pcSummary$importance[2, 2] * 100,
                   digits = 3), " % variance"),
     col = "darkgrey", bg = cols, cex = 2)
grid()
text(pc$x[, 1], pc$x[,2], labels = xdata$sample_name, col = "darkgrey",
     pos = 3, cex = 2)

We can see the expected separation between the KO and WT samples on PC2. On PC1 samples separate based on their ID, samples with an ID <= 18 from samples with an ID > 18. This separation might be caused by a technical bias (e.g. measurements performed on different days/weeks) or due to biological properties of the mice analyzed (sex, age, litter mates etc).

LS0tDQp0aXRsZTogIk1ldGFib2xvbWljcyB3aXRoIFIsIFNlc3Npb24gMSINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KLS0tDQoNCiNJbnRyb2R1Y3Rpb24NClRoaXMgZG9jdW1lbnRzIGRlc2NyaWJlcyBkYXRhIGltcG9ydCwgZXhwbG9yYXRpb24sIHByZXByb2Nlc3NpbmcgYW5kIGFuYWx5c2lzIG9mIExDTVMgZXhwZXJpbWVudHMgd2l0aCB4Y21zIHZlcnNpb24gPj0gMy4NCg0KI0RhdGEgaW1wb3J0DQpgeGNtc2Agc3VwcG9ydHMgYW5hbHlzaXMgb2YgTEMvTVMgZGF0YSBmcm9tIGZpbGVzIGluIChBSUEvQU5ESSkgTmV0Q0RGLCBtek1ML216WE1MIGFuZCBtekRhdGEgZm9ybWF0LiBGb3IgZGVtb25zdHJhdGlvbiBwdXJwb3NlIHdlIHdpbGwgYW5hbHl6ZSBhIHN1YnNldCBvZiB0aGUgZGF0YSBmcm9tIFsxXSBpbiB3aGljaCB0aGUgbWV0YWJvbGljIGNvbnNlcXVlbmNlcyBvZiBrbm9ja2luZyBvdXQgdGhlIGZhdHR5IGFjaWQgYW1pZGUgaHlkcm9sYXNlIChGQUFIKSBnZW5lIGluIG1pY2Ugd2FzIGludmVzdGlnYXRlZC4gVGhlIHJhdyBkYXRhIGZpbGVzIChpbiBOZXRDREYgZm9ybWF0KSBhcmUgcHJvdmlkZWQgd2l0aCB0aGUgZmFhaEtPIGRhdGEgcGFja2FnZS4gVGhlIGRhdGEgc2V0IGNvbnNpc3RzIG9mIHNhbXBsZXMgZnJvbSB0aGUgc3BpbmFsIGNvcmRzIG9mIDYga25vY2stb3V0IGFuZCA2IHdpbGQtdHlwZSBtaWNlLiBFYWNoIGZpbGUgY29udGFpbnMgZGF0YSBpbiBjZW50cm9pZCBtb2RlIGFjcXVpcmVkIGluIHBvc2l0aXZlIGlvbiBtb2RlIGZvcm0gMjAwLTYwMCBtL3ogYW5kIDI1MDAtNDUwMCBzZWNvbmRzLg0KDQpCZWxvdyB3ZSBsb2FkIGFsbCByZXF1aXJlZCBwYWNrYWdlcywgbG9jYXRlIHRoZSByYXcgQ0RGIGZpbGVzIHdpdGhpbiB0aGUgZmFhaEtPIHBhY2thZ2UgYW5kIGJ1aWxkIGEgcGhlbm9kYXRhIGRhdGEgZnJhbWUgZGVzY3JpYmluZyB0aGUgZXhwZXJpbWVudGFsIHNldHVwLg0KDQpgYGB7cn0NCmxpYnJhcnkoeGNtcykNCmxpYnJhcnkoZmFhaEtPKQ0KbGlicmFyeShSQ29sb3JCcmV3ZXIpDQpsaWJyYXJ5KHBhbmRlcikNCmxpYnJhcnkobWFncml0dHIpDQoNCiMjIEdldCB0aGUgZnVsbCBwYXRoIHRvIHRoZSBDREYgZmlsZXMNCmNkZnMgPC0gZGlyKHN5c3RlbS5maWxlKCJjZGYiLCBwYWNrYWdlID0gImZhYWhLTyIpLCBmdWxsLm5hbWVzID0gVFJVRSwNCgkgICAgcmVjdXJzaXZlID0gVFJVRSkNCiMjIENyZWF0ZSBhIHBoZW5vZGF0YSBkYXRhLmZyYW1lDQpwZCA8LSBkYXRhLmZyYW1lKHNhbXBsZV9uYW1lID0gc3ViKGJhc2VuYW1lKGNkZnMpLCBwYXR0ZXJuID0gIi5DREYiLA0KCQkJCSAgIHJlcGxhY2VtZW50ID0gIiIsIGZpeGVkID0gVFJVRSksDQoJCSBzYW1wbGVfZ3JvdXAgPSBjKHJlcCgiS08iLCA2KSwgcmVwKCJXVCIsIDYpKSwNCgkJIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRSkgDQpwZA0KYGBgDQoNClN1YnNlcXVlbnRseSB3ZSBsb2FkIHRoZSByYXcgZGF0YSBhcyBhbiBgT25EaXNrTVNuRXhwYCBvYmplY3QgdXNpbmcgdGhlIGByZWFkTVNEYXRhYCBtZXRob2QgZnJvbSB0aGUgYE1TbmJhc2VgIHBhY2thZ2UuIFdoaWxlIHRoZSBgTVNuYmFzZWAgcGFja2FnZSB3YXMgb3JpZ2luYWxseSBkZXZlbG9wZWQgZm9yIHByb3Rlb21pY3MgZGF0YSBwcm9jZXNzaW5nLCBtYW55IG9mIGl0cyBmdW5jdGlvbmFsaXR5LCBpbmNsdWRpbmcgcmF3IGRhdGEgaW1wb3J0IGFuZCBkYXRhIHJlcHJlc2VudGF0aW9uLCBjYW4gYmUgc2hhcmVkIGFuZCByZXVzZWQgaW4gbWV0YWJvbG9taWNzIGRhdGEgYW5hbHlzaXMuDQoNCmBgYHtyfQ0KcmF3X2RhdGEgPC0gcmVhZE1TRGF0YShmaWxlcyA9IGNkZnMsIHBkYXRhID0gbmV3KCJOQW5ub3RhdGVkRGF0YUZyYW1lIiwgcGQpLA0KCQkgICAgICAgbW9kZSA9ICJvbkRpc2siKQ0KYGBgDQoNClRoZSBgT25EaXNrTVNuRXhwYCBvYmplY3QgY29udGFpbnMgZ2VuZXJhbCBpbmZvcm1hdGlvbiBhYm91dCB0aGUgbnVtYmVyIG9mIHNwZWN0cmEsIHJldGVudGlvbiB0aW1lcywgdGhlIG1lYXN1cmVkIHRvdGFsIGlvbiBjdXJyZW50IGV0YywgYnV0IGRvZXMgbm90IGNvbnRhaW4gdGhlIGZ1bGwgcmF3IGRhdGEgKGkuZS4gdGhlIG0veiBhbmQgaW50ZW5zaXR5IHZhbHVlcyBmcm9tIGVhY2ggbWVhc3VyZWQgc3BlY3RydW0pLiBJdHMgbWVtb3J5IGZvb3RwcmludCBpcyB0aHVzIHJhdGhlciBzbWFsbCBtYWtpbmcgaXQgYW4gaWRlYWwgb2JqZWN0IHRvIHJlcHJlc2VudCBsYXJnZSBtZXRhYm9sb21pY3MgZXhwZXJpbWVudHMgd2hpbGUgc3RpbGwgYWxsb3dpbmcgdG8gcGVyZm9ybSBzaW1wbGUgcXVhbGl0eSBjb250cm9scywgZGF0YSBpbnNwZWN0aW9uIGFuZCBleHBsb3JhdGlvbiBhcyB3ZWxsIGFzIGRhdGEgc3ViLXNldHRpbmcgb3BlcmF0aW9ucy4gVGhlIG0veiBhbmQgaW50ZW5zaXR5IHZhbHVlcyBhcmUgaW1wb3J0ZWQgZnJvbSB0aGUgcmF3IGRhdGEgZmlsZXMgb24gZGVtYW5kLCBoZW5jZSB0aGUgbG9jYXRpb24gb2YgdGhlIHJhdyBkYXRhIGZpbGVzIHNob3VsZCBub3QgYmUgY2hhbmdlZCBhZnRlciBpbml0aWFsIGRhdGEgaW1wb3J0Lg0KDQojSW5pdGlhbCBkYXRhIGluc3BlY3Rpb24NCg0KVGhlIGBPbkRpc2tNU25FeHBgIG9yZ2FuaXplcyB0aGUgTVMgZGF0YSBieSBzcGVjdHJ1bSBhbmQgcHJvdmlkZXMgdGhlIG1ldGhvZHMgaW50ZW5zaXR5LCAgbXogYW5kIHJ0aW1lIHRvIGFjY2VzcyB0aGUgcmF3IGRhdGEgZnJvbSB0aGUgZmlsZXMgKHRoZSBtZWFzdXJlZCBpbnRlbnNpdHkgdmFsdWVzLCB0aGUgY29ycmVzcG9uZGluZyBtL3ogYW5kIHJldGVudGlvbiB0aW1lIHZhbHVlcykuIEluIGFkZGl0aW9uLCB0aGUgYHNwZWN0cmFgIG1ldGhvZCBjb3VsZCBiZSB1c2VkIHRvIHJldHVybiBhbGwgZGF0YSBlbmNhcHN1bGF0ZWQgaW4gYFNwZWN0cnVtYCBjbGFzc2VzLiBCZWxvdyB3ZSBleHRyYWN0IHRoZSByZXRlbnRpb24gdGltZSB2YWx1ZXMgZnJvbSB0aGUgb2JqZWN0Lg0KDQpgYGB7cn0NCmhlYWQocnRpbWUocmF3X2RhdGEpKSANCmBgYA0KDQpBbGwgZGF0YSBpcyByZXR1cm5lZCBhcyBvbmUtZGltZW5zaW9uYWwgdmVjdG9ycyAoYSBudW1lcmljIHZlY3RvciBmb3IgYHJ0aW1lYCBhbmQgYSBgbGlzdGAgb2YgbnVtZXJpYyB2ZWN0b3JzIGZvciBgbXpgIGFuZCBgaW50ZW5zaXR5YCwgZWFjaCBjb250YWluaW5nIHRoZSB2YWx1ZXMgZnJvbSBvbmUgc3BlY3RydW0pLCBldmVuIGlmIHRoZSBleHBlcmltZW50IGNvbnNpc3RzIG9mIG11bHRpcGxlIGZpbGVzL3NhbXBsZXMuIFRoZSBgZnJvbUZpbGVgIGZ1bmN0aW9uIHJldHVybnMgYSBudW1lcmljIHZlY3RvciB0aGF0IHByb3ZpZGVzIHRoZSBtYXBwaW5nIG9mIHRoZSB2YWx1ZXMgdG8gdGhlIG9yaWdpbmF0aW5nIGZpbGUuIEJlbG93IHdlIHVzZSB0aGUgIGBmcm9tRmlsZWAgaW5kaWNlcyB0byBvcmdhbml6ZSB0aGUgbXogdmFsdWVzIGJ5IGZpbGUuDQoNCmBgYHtyfQ0KbXpzIDwtIG16KHJhd19kYXRhKQ0KDQojIyBTcGxpdCB0aGUgbGlzdCBieSBmaWxlDQptenNfYnlfZmlsZSA8LSBzcGxpdChtenMsIGYgPSBmcm9tRmlsZShyYXdfZGF0YSkpDQoNCmxlbmd0aChtenNfYnlfZmlsZSkNCmBgYA0KDQpBcyBhIGZpcnN0IGV2YWx1YXRpb24gb2YgdGhlIGRhdGEgd2UgcGxvdCBiZWxvdyB0aGUgYmFzZSBwZWFrIGNocm9tYXRvZ3JhbSAoQlBDKSBmb3IgZWFjaCBmaWxlIGluIG91ciBleHBlcmltZW50LiBXZSB1c2UgdGhlIGBjaHJvbWF0b2dyYW1gIG1ldGhvZCBhbmQgc2V0IHRoZSBgYWdncmVnYXRpb25GdW5gIHRvIGAibWF4ImAgdG8gcmV0dXJuIGZvciBlYWNoIHNwZWN0cnVtIHRoZSBtYXhpbWFsIGludGVuc2l0eSBhbmQgaGVuY2UgY3JlYXRlIHRoZSBCUEMgZnJvbSB0aGUgcmF3IGRhdGEuIFRvIGNyZWF0ZSBhIHRvdGFsIGlvbiBjaHJvbWF0b2dyYW0gd2UgY291bGQgc2V0IGBhZ2dyZWdhdGlvbkZ1bmAgdG8gYHN1bWAuDQogDQpgYGB7cn0NCiMjIEdldCB0aGUgYmFzZSBwZWFrIGNocm9tYXRvZ3JhbXMuIFRoaXMgcmVhZHMgZGF0YSBmcm9tIHRoZSBmaWxlcy4NCmJwaXMgPC0gY2hyb21hdG9ncmFtKHJhd19kYXRhLCBhZ2dyZWdhdGlvbkZ1biA9ICJtYXgiKQ0KIyMgRGVmaW5lIGNvbG9ycyBmb3IgdGhlIHR3byBncm91cHMNCmdyb3VwX2NvbG9ycyA8LSBicmV3ZXIucGFsKDMsICJTZXQxIilbMToyXQ0KbmFtZXMoZ3JvdXBfY29sb3JzKSA8LSBjKCJLTyIsICJXVCIpDQoNCiMjIFBsb3QgYWxsIGNocm9tYXRvZ3JhbXMuDQpwbG90KGJwaXMsIGNvbCA9IGdyb3VwX2NvbG9yc1tyYXdfZGF0YSRzYW1wbGVfZ3JvdXBdKQ0KDQpgYGANCg0KQmVsb3cgd2UgZXh0cmFjdCB0aGUgY2hyb21hdG9ncmFtIG9mIHRoZSBmaXJzdCBzYW1wbGUgYW5kIGFjY2VzcyBpdHMgcmV0ZW50aW9uIHRpbWUgYW5kIGludGVuc2l0eSB2YWx1ZXMuDQoNCmBgYHtyfQ0KYnBpXzEgPC0gYnBpc1sxLCAxXQ0KaGVhZChydGltZShicGlfMSkpDQpoZWFkKGludGVuc2l0eShicGlfMSkpDQpgYGANCg0KVGhlIGBjaHJvbWF0b2dyYW1gIG1ldGhvZCBzdXBwb3J0cyBhbHNvIGV4dHJhY3Rpb24gb2YgY2hyb21hdG9ncmFwaGljIGRhdGEgZnJvbSBhIG0vei1ydCBzbGljZSBvZiB0aGUgTVMgZGF0YS4gSW4gdGhlIG5leHQgc2VjdGlvbiB3ZSB3aWxsIHVzZSB0aGlzIG1ldGhvZCB0byBjcmVhdGUgYW4gZXh0cmFjdGVkIGlvbiBjaHJvbWF0b2dyYW0gKEVJQykgZm9yIGEgc2VsZWN0ZWQgcGVhay4NCg0KQmVsb3cgd2UgY3JlYXRlIGJveHBsb3RzIHJlcHJlc2VudGluZyB0aGUgZGlzdHJpYnV0aW9uIG9mIHRvdGFsIGlvbiBjdXJyZW50cyBwZXIgZmlsZS4gU3VjaCBwbG90cyBjYW4gYmUgdmVyeSB1c2VmdWwgdG8gc3BvdCBwcm9ibGVtYXRpYyBvciBmYWlsaW5nIE1TIHJ1bnMuDQoNCmBgYHtyfQ0KIyMgR2V0IHRoZSB0b3RhbCBpb24gY3VycmVudCBieSBmaWxlDQp0YyA8LSBzcGxpdCh0aWMocmF3X2RhdGEpLCBmID0gZnJvbUZpbGUocmF3X2RhdGEpKQ0KYm94cGxvdCh0YywgY29sID0gZ3JvdXBfY29sb3JzW3Jhd19kYXRhJHNhbXBsZV9ncm91cF0sDQoJeWxhYiA9ICJpbnRlbnNpdHkiLCBtYWluID0gIlRvdGFsIGlvbiBjdXJyZW50IikgDQoNCmBgYA0KDQojQ2hyb21hdG9ncmFwaGljIHBlYWsgZGV0ZWN0aW9uDQoNCk5leHQgd2UgcGVyZm9ybSB0aGUgY2hyb21hdG9ncmFwaGljIHBlYWsgZGV0ZWN0aW9uIHVzaW5nIHRoZSBjZW50V2F2ZSBhbGdvcml0aG0gWzJdLiBCZWZvcmUgcnVubmluZyB0aGUgcGVhayBkZXRlY3Rpb24gaXQgaXMgaG93ZXZlciBzdHJvbmdseSBzdWdnZXN0ZWQgdG8gdmlzdWFsbHkgaW5zcGVjdCBlLmcuIHRoZSBleHRyYWN0ZWQgaW9uIGNocm9tYXRvZ3JhbSBvZiBpbnRlcm5hbCBzdGFuZGFyZHMgb3Iga25vd24gY29tcG91bmRzIHRvIGV2YWx1YXRlIGFuZCBhZGFwdCB0aGUgcGVhayBkZXRlY3Rpb24gc2V0dGluZ3Mgc2luY2UgdGhlIGRlZmF1bHQgc2V0dGluZ3Mgd2lsbCBub3QgYmUgYXBwcm9wcmlhdGUgZm9yIG1vc3QgTENNUyBleHBlcmltZW50cy4gVGhlIHR3byBtb3N0IGNyaXRpY2FsIHBhcmFtZXRlcnMgZm9yIGNlbnRXYXZlIGFyZSB0aGUgYHBlYWt3aWR0aGAgKGV4cGVjdGVkIHJhbmdlIG9mIGNocm9tYXRvZ3JhcGhpYyBwZWFrIHdpZHRocykgYW5kIGBwcG1gIChtYXhpbXVtIGV4cGVjdGVkIGRldmlhdGlvbiBvZiBtL3ogdmFsdWVzIG9mIGNlbnRyb2lkcyBjb3JyZXNwb25kaW5nIHRvIG9uZSBjaHJvbWF0b2dyYXBoaWMgcGVhazsgdGhpcyBpcyB1c3VhbGx5IG11Y2ggbGFyZ2VyIHRoYW4gdGhlIHBwbSBzcGVjaWZpZWQgYnkgdGhlIG1hbnVmYWN0dXJlcikgcGFyYW1ldGVycy4gVG8gZXZhbHVhdGUgdGhlIHR5cGljYWwgY2hyb21hdG9ncmFwaGljIHBlYWsgd2lkdGggd2UgcGxvdCB0aGUgRUlDIGZvciBvbmUgcGVhay4NCg0KYGBge3J9DQojIyBEZWZpbmUgdGhlIHJ0IGFuZCBtL3ogcmFuZ2Ugb2YgdGhlIHBlYWsgYXJlYQ0KcnRyIDwtIGMoMjcwMCwgMjkwMCkNCm16ciA8LSBjKDMzNC45LCAzMzUuMSkNCiMjIGV4dHJhY3QgdGhlIGNocm9tYXRvZ3JhbQ0KY2hyX3JhdyA8LSBjaHJvbWF0b2dyYW0ocmF3X2RhdGEsIG16ID0gbXpyLCBydCA9IHJ0cikNCnBsb3QoY2hyX3JhdywgY29sID0gZ3JvdXBfY29sb3JzW2Nocl9yYXckc2FtcGxlX2dyb3VwXSkNCmBgYA0KDQpGb3IgdGhlIGBwcG1gIHBhcmFtZXRlciB3ZSBleHRyYWN0IHRoZSBmdWxsIE1TIGRhdGEgKGludGVuc2l0eSwgcmV0ZW50aW9uIHRpbWUgYW5kIG0veiB2YWx1ZXMpIGNvcnJlc3BvbmRpbmcgdG8gdGhlIGFib3ZlIHBlYWsuIFRvIHRoaXMgZW5kIHdlIGZpcnN0IGZpbHRlciB0aGUgcmF3IG9iamVjdCBieSByZXRlbnRpb24gdGltZSwgdGhlbiBieSBtL3ogYW5kIGZpbmFsbHkgcGxvdCB0aGUgb2JqZWN0IHdpdGggYHR5cGUgPSAiWElDImAgdG8gcHJvZHVjZSB0aGUgcGxvdCBiZWxvdy4gV2UgdXNlIHRoZSBwaXBlIGAoJT4lKWAgY29tbWFuZCBiZXR0ZXIgaWxsdXN0cmF0ZSB0aGUgY29ycmVzcG9uZGluZyB3b3JrZmxvdy4NCg0KYGBge3J9DQoNCnJhd19kYXRhICU+JQ0KICAgIGZpbHRlclJ0KHJ0ID0gcnRyKSAlPiUNCiAgICBmaWx0ZXJNeihteiA9IG16cikgJT4lDQogICAgcGxvdCh0eXBlID0gIlhJQyIpIA0KDQpgYGANCg0KRm9yIGVhY2ggcGxvdDogdXBwZXIgcGFuZWw6IGNocm9tYXRvZ3JhbSBwbG90dGluZyB0aGUgaW50ZW5zaXR5IHZhbHVlcyBhZ2FpbnN0IHRoZSByZXRlbnRpb24gdGltZSwgbG93ZXIgcGFuZWwgbS96IGFnYWluc3QgcmV0ZW50aW9uIHRpbWUgcGxvdC4gVGhlIGluZGl2aWR1YWwgZGF0YSBwb2ludHMgYXJlIGNvbG9yZWQgYWNjb3JkaW5nIHRvIHRoZSBpbnRlbnNpdHkuDQoNCkJlbG93IHdlIHBlcmZvcm0gdGhlIGNocm9tYXRvZ3JhcGhpYyBwZWFrIGRldGVjdGlvbiB1c2luZyB0aGUgZmluZENocm9tUGVha3MgbWV0aG9kLiBUaGUgc3VibWl0dGVkIHBhcmFtZXRlciBvYmplY3QgZGVmaW5lcyB3aGljaCBhbGdvcml0aG0gd2lsbCBiZSB1c2VkIGFuZCBhbGxvd3MgdG8gZGVmaW5lIHRoZSBzZXR0aW5ncyBmb3IgdGhpcyBhbGdvcml0aG0uIE5vdGUgdGhhdCB3ZSBzZXQgdGhlIGFyZ3VtZW50IG5vaXNlIHRvIDEwMDAgdG8gc2xpZ2h0bHkgc3BlZWQgdXAgdGhlIGFuYWx5c2lzIGJ5IGNvbnNpZGVyaW5nIG9ubHkgc2lnbmFscyB3aXRoIGEgdmFsdWUgbGFyZ2VyIHRoYW4gMTAwMCBpbiB0aGUgcGVhayBkZXRlY3Rpb24gc3RlcC4NCg0KYGBge3IsIG1lc3NhZ2U9IEZBTFNFfQ0KY3dwIDwtIENlbnRXYXZlUGFyYW0ocGVha3dpZHRoID0gYygzMCwgODApLCBub2lzZSA9IDEwMDApDQp4ZGF0YSA8LSBmaW5kQ2hyb21QZWFrcyhyYXdfZGF0YSwgcGFyYW0gPSBjd3ApDQpgYGANCg0KVGhlIHJlc3VsdHMgYXJlIHJldHVybmVkIGFzIGFuIFhDTVNuRXhwIG9iamVjdCB3aGljaCBleHRlbmRzIHRoZSBPbkRpc2tNU25FeHAgb2JqZWN0IGJ5IHN0b3JpbmcgYWxzbyBMQy9HQy1NUyBwcmVwcm9jZXNzaW5nIHJlc3VsdHMuIFRoaXMgbWVhbnMgYWxzbyB0aGF0IGFsbCBtZXRob2RzIHRvIHN1Yi1zZXQgYW5kIGZpbHRlciB0aGUgZGF0YSBvciB0byBhY2Nlc3MgdGhlIChyYXcpIGRhdGEgYXJlIGluaGVyaXRlZCBmcm9tIHRoZSBPbkRpc2tNU25FeHAgb2JqZWN0LiBUaGUgcmVzdWx0cyBmcm9tIHRoZSBjaHJvbWF0b2dyYXBoaWMgcGVhayBkZXRlY3Rpb24gY2FuIGJlIGFjY2Vzc2VkIHdpdGggdGhlIGNocm9tUGVha3MgbWV0aG9kLg0KDQpgYGB7cn0NCmhlYWQoY2hyb21QZWFrcyh4ZGF0YSkpIA0KDQpgYGANCg0KV2UgY2FuIGFsc28gcGxvdCB0aGUgbG9jYXRpb24gb2YgdGhlIGlkZW50aWZpZWQgY2hyb21hdG9ncmFwaGljIHBlYWtzIGluIHRoZSBtL3ogLSByZXRlbnRpb24gdGltZSBzcGFjZSBmb3Igb25lIGZpbGUgdXNpbmcgdGhlIHBsb3RDaHJvbVBlYWtzIGZ1bmN0aW9uLiBCZWxvdyB3ZSBwbG90IHRoZSBjaHJvbWF0b2dyYXBoaWMgcGVha3MgZm9yIHRoZSA1dGggc2FtcGxlLg0KDQpgYGB7cn0NCnBsb3RDaHJvbVBlYWtzKHhkYXRhLCBmaWxlID0gNSkgDQoNCmBgYA0KDQpUbyBnZXQgYSBnbG9iYWwgb3ZlcnZpZXcgb2YgdGhlIHBlYWsgZGV0ZWN0aW9uIHdlIGNhbiBwbG90IHRoZSBmcmVxdWVuY3kgb2YgaWRlbnRpZmllZCBwZWFrcyBwZXIgZmlsZSBhbG9uZyB0aGUgcmV0ZW50aW9uIHRpbWUgYXhpcy4gVGhpcyBhbGxvd3MgdG8gaWRlbnRpZnkgdGltZSBwZXJpb2RzIGFsb25nIHRoZSBNUyBydW4gaW4gd2hpY2ggYSBoaWdoZXIgbnVtYmVyIG9mIHBlYWtzIHdhcyBpZGVudGlmaWVkIGFuZCBldmFsdWF0ZSB3aGV0aGVyIHRoaXMgaXMgY29uc2lzdGVudCBhY3Jvc3MgZmlsZXMuDQoNCmBgYHtyfQ0KcGxvdENocm9tUGVha0ltYWdlKHhkYXRhKSANCg0KYGBgDQoNCk5leHQgd2UgaGlnaGxpZ2h0IHRoZSBpZGVudGlmaWVkIGNocm9tYXRvZ3JhcGhpYyBwZWFrcyBmb3IgdGhlIGV4YW1wbGUgcGVhayBmcm9tIGJlZm9yZS4gRXZhbHVhdGluZyBzdWNoIHBsb3RzIG9uIGEgbGlzdCBvZiBwZWFrcyBjb3JyZXNwb25kaW5nIHRvIGtub3duIHBlYWtzIG9yIGludGVybmFsIHN0YW5kYXJkcyBoZWxwcyB0byBlbnN1cmUgdGhhdCBwZWFrIGRldGVjdGlvbiBzZXR0aW5ncyB3ZXJlIGFwcHJvcHJpYXRlIGFuZCBjb3JyZWN0bHkgaWRlbnRpZmllZCB0aGUgZXhwZWN0ZWQgcGVha3MuIFRoZSByZWN0YW5nbGVzIGluZGljYXRlIHRoZSBpZGVudGlmaWVkIGNocm9tYXRvZ3JhcGhpYyBwZWFrcyBwZXIgc2FtcGxlLg0KDQpgYGB7cn0NCnBsb3QoY2hyX3JhdywgY29sID0gZ3JvdXBfY29sb3JzW2Nocl9yYXckc2FtcGxlX2dyb3VwXSwgbHdkID0gMykNCmhpZ2hsaWdodENocm9tUGVha3MoeGRhdGEsIGJvcmRlciA9IGdyb3VwX2NvbG9yc1tjaHJfcmF3JHNhbXBsZV9ncm91cF0sDQoJCSAgICBsdHkgPSAzLCBydCA9IHJ0ciwgbXogPSBtenIpIA0KDQpwYW5kZXIoY2hyb21QZWFrcyh4ZGF0YSwgbXogPSBtenIsIHJ0ID0gcnRyKSwNCiAgICAgICBjYXB0aW9uID0gcGFzdGUoIklkZW50aWZpZWQgY2hyb21hdG9ncmFwaGljIHBlYWtzIGluIGEgc2VsZWN0ZWQgIiwNCgkJICAgICAgICJtL3ogYW5kIHJldGVudGlvbiB0aW1lIHJhbmdlLiIpKQ0KYGBgDQoNCkZpbmFsbHkgd2UgcGxvdCBhbHNvIHRoZSBkaXN0cmlidXRpb24gb2YgcGVhayBpbnRlbnNpdHkgcGVyIHNhbXBsZS4gVGhpcyBhbGxvd3MgdG8gaW52ZXN0aWdhdGUgd2hldGhlciBzeXN0ZW1hdGljIGRpZmZlcmVuY2VzIGluIHBlYWsgc2lnbmFscyBiZXR3ZWVuIHNhbXBsZXMgYXJlIHByZXNlbnQuDQoNCmBgYHtyfQ0KIyMgRXh0cmFjdCBhIGxpc3Qgb2YgcGVyLXNhbXBsZSBwZWFrIGludGVuc2l0aWVzIChpbiBsb2cyIHNjYWxlKQ0KaW50cyA8LSBzcGxpdChsb2cyKGNocm9tUGVha3MoeGRhdGEpWywgImludG8iXSksDQoJICAgICAgZiA9IGNocm9tUGVha3MoeGRhdGEpWywgInNhbXBsZSJdKQ0KYm94cGxvdChpbnRzLCB2YXJ3aWR0aCA9IFRSVUUsIGNvbCA9IGdyb3VwX2NvbG9yc1t4ZGF0YSRzYW1wbGVfZ3JvdXBdLA0KCXlsYWIgPSBleHByZXNzaW9uKGxvZ1syXX5pbnRlbnNpdHkpLCBtYWluID0gIlBlYWsgaW50ZW5zaXRpZXMiKQ0KZ3JpZChueCA9IE5BLCBueSA9IE5VTEwpIA0KDQpgYGANCiNBbGlnbm1lbnQNClRoZSB0aW1lIGF0IHdoaWNoIGFuYWx5dGVzIGVsdXRlIGluIHRoZSBjaHJvbWF0b2dyYXBoeSBjYW4gdmFyeSBiZXR3ZWVuIHNhbXBsZXMgKGFuZCBldmVuIGNvbXBvdW5kcykuIFN1Y2ggYSBkaWZmZXJlbmNlIHdhcyBhbHJlYWR5IG9ic2VydmFibGUgaW4gdGhlIGV4dHJhY3RlZCBpb24gY2hyb21hdG9ncmFtIHBsb3Qgc2hvd24gYXMgYW4gZXhhbXBsZSBpbiB0aGUgcHJldmlvdXMgc2VjdGlvbi4gVGhlIGFsaWdubWVudCBzdGVwLCBhbHNvIHJlZmVycmVkIHRvIGFzICoqcmV0ZW50aW9uIHRpbWUgY29ycmVjdGlvbioqLCBhaW1zIGF0IGFkanVzdGluZyB0aGlzIGJ5IHNoaWZ0aW5nIHNpZ25hbHMgYWxvbmcgdGhlIHJldGVudGlvbiB0aW1lIGF4aXMgdG8gYWxpZ24gdGhlIHNpZ25hbHMgYmV0d2VlbiBkaWZmZXJlbnQgc2FtcGxlcyB3aXRoaW4gYW4gZXhwZXJpbWVudC4gVGhlIG1ldGhvZCB0byBwZXJmb3JtIHRoZSBhbGlnbm1lbnQvcmV0ZW50aW9uIHRpbWUgY29ycmVjdGlvbiBpbiBgeGNtc2AgaXMgYGFkanVzdFJ0aW1lYCB3aGljaCB1c2VzIGRpZmZlcmVudCBhbGlnbm1lbnQgYWxnb3JpdGhtcyBkZXBlbmRpbmcgb24gdGhlIHByb3ZpZGVkIHBhcmFtZXRlciBjbGFzcy4gSW4gdGhlIGV4YW1wbGUgYmVsb3cgd2UgdXNlIHRoZSBvYml3YXJwIG1ldGhvZCBbNF0gdG8gYWxpZ24gdGhlIHNhbXBsZXMuIFdlIHVzZSBhIGBiaW5TaXplID0gMC42YCB3aGljaCBjcmVhdGVzIHdhcnBpbmcgZnVuY3Rpb25zIGluIG16IGJpbnMgb2YgMC42LiBBbHNvIGhlcmUgaXQgaXMgYWR2aXNhYmxlIHRvIG1vZGlmeSB0aGUgc2V0dGluZ3MgZm9yIGVhY2ggZXhwZXJpbWVudCBhbmQgZXZhbHVhdGUgaWYgcmV0ZW50aW9uIHRpbWUgY29ycmVjdGlvbiBkaWQgYWxpZ24gaW50ZXJuYWwgY29udHJvbHMgb3Iga25vd24gY29tcG91bmRzIHByb3Blcmx5Lg0KDQpgYGB7ciwgbWVzc2FnZT0gRkFMU0V9DQp4ZGF0YSA8LSBhZGp1c3RSdGltZSh4ZGF0YSwgcGFyYW0gPSBPYml3YXJwUGFyYW0oYmluU2l6ZSA9IDAuNikpIA0KYGBgDQoNCmBhZGp1c3RSdGltZWAsIGJlc2lkZXMgY2FsY3VsYXRpbmcgYWRqdXN0ZWQgcmV0ZW50aW9uIHRpbWVzIGZvciBlYWNoIHNwZWN0cnVtLCBkb2VzIGFsc28gYWRqdXN0IHRoZSByZXBvcnRlZCByZXRlbnRpb24gdGltZXMgb2YgdGhlIGlkZW50aWZpZWQgY2hyb21hdG9ncmFwaGljIHBlYWtzLg0KDQpUbyBleHRyYWN0IHRoZSBhZGp1c3RlZCByZXRlbnRpb24gdGltZXMgd2UgY2FuIHVzZSB0aGUgYGFkanVzdGVkUnRpbWVgIG1ldGhvZCwgb3Igc2ltcGx5IHRoZSAgYHJ0aW1lYCBtZXRob2QgdGhhdCwgaWYgcHJlc2VudCwgcmV0dXJucyBieSBkZWZhdWx0IGFkanVzdGVkIHJldGVudGlvbiB0aW1lcyBmcm9tIGFuIGBYQ01TbkV4cGAgb2JqZWN0Lg0KDQpgYGB7cn0NCiMjIEV4dHJhY3QgYWRqdXN0ZWQgcmV0ZW50aW9uIHRpbWVzDQpoZWFkKGFkanVzdGVkUnRpbWUoeGRhdGEpKQ0KDQojIyBPciBzaW1wbHkgdXNlIHRoZSBydGltZSBtZXRob2QNCmhlYWQocnRpbWUoeGRhdGEsIGFkanVzdGVkID0gRkFMU0UpKSANCmBgYA0KDQpUbyBldmFsdWF0ZSB0aGUgaW1wYWN0IG9mIHRoZSBhbGlnbm1lbnQgd2UgcGxvdCB0aGUgQlBDIG9uIHRoZSBhZGp1c3RlZCBkYXRhLiBJbiBhZGRpdGlvbiB3ZSBwbG90IHRoZSBkaWZmZXJlbmNlcyBvZiB0aGUgYWRqdXN0ZWQtIHRvIHRoZSByYXcgcmV0ZW50aW9uIHRpbWVzIHBlciBzYW1wbGUgdXNpbmcgdGhlICBgcGxvdEFkanVzdGVkUnRpbWVgIGZ1bmN0aW9uLg0KDQpgYGB7cn0NCiMjIEdldCB0aGUgYmFzZSBwZWFrIGNocm9tYXRvZ3JhbXMuDQpicGlzX2FkaiA8LSBjaHJvbWF0b2dyYW0oeGRhdGEsIGFnZ3JlZ2F0aW9uRnVuID0gIm1heCIpDQpwYXIobWZyb3cgPSBjKDIsIDEpLCBtYXIgPSBjKDQuNSwgNC4yLCAxLCAwLjUpKQ0KcGxvdChicGlzX2FkaiwgY29sID0gZ3JvdXBfY29sb3JzW2JwaXNfYWRqJHNhbXBsZV9ncm91cF0pDQojIyBQbG90IGFsc28gdGhlIGRpZmZlcmVuY2Ugb2YgYWRqdXN0ZWQgdG8gcmF3IHJldGVudGlvbiB0aW1lLg0KcGxvdEFkanVzdGVkUnRpbWUoeGRhdGEsIGNvbCA9IGdyb3VwX2NvbG9yc1t4ZGF0YSRzYW1wbGVfZ3JvdXBdKSANCmBgYA0KQmFzZSBwZWFrIGNocm9tYXRvZ3JhbSBhZnRlciBhbGlnbm1lbnQgKHRvcCkgYW5kIGRpZmZlcmVuY2UgYmV0d2VlbiBhZGp1c3RlZCBhbmQgcmF3IHJldGVudGlvbiB0aW1lcyBhbG9uZyB0aGUgcmV0ZW50aW9uIHRpbWUgYXhpcyAoYm90dG9tKS4NCg0KVG9vIGxhcmdlIGRpZmZlcmVuY2VzIGJldHdlZW4gYWRqdXN0ZWQgYW5kIHJhdyByZXRlbnRpb24gdGltZXMgY291bGQgaW5kaWNhdGUgcG9vcmx5IHBlcmZvcm1pbmcgc2FtcGxlcyBvciBhbGlnbm1lbnQuDQoNCkF0IGxhc3Qgd2UgZXZhbHVhdGUgdGhlIGltcGFjdCBvZiB0aGUgYWxpZ25tZW50IG9uIHRoZSB0ZXN0IHBlYWsuDQoNCmBgYHtyfQ0KcGFyKG1mcm93ID0gYygyLCAxKSkNCiMjIFBsb3QgdGhlIHJhdyBkYXRhDQpwbG90KGNocl9yYXcsIGNvbCA9IGdyb3VwX2NvbG9yc1tjaHJfcmF3JHNhbXBsZV9ncm91cF0pDQoNCiMjIEV4dHJhY3QgdGhlIGNocm9tYXRvZ3JhbSBmcm9tIHRoZSBhZGp1c3RlZCBvYmplY3QNCmNocl9hZGogPC0gY2hyb21hdG9ncmFtKHhkYXRhLCBydCA9IHJ0ciwgbXogPSBtenIpDQpwbG90KGNocl9hZGosIGNvbCA9IGdyb3VwX2NvbG9yc1tjaHJfcmF3JHNhbXBsZV9ncm91cF0pDQpgYGANCg0KI0NvcnJlc3BvbmRhbmNlDQoNClRoZSBmaW5hbCBzdGVwIGluIHRoZSBtZXRhYm9sb21pY3MgcHJlcHJvY2Vzc2luZyBpcyB0aGUgY29ycmVzcG9uZGVuY2UgdGhhdCBtYXRjaGVzIGRldGVjdGVkIGNocm9tYXRvZ3JhcGhpYyBwZWFrcyBiZXR3ZWVuIHNhbXBsZXMuIFRoZSBtZXRob2QgdG8gcGVyZm9ybSB0aGUgY29ycmVzcG9uZGVuY2UgaW4gYHhjbXNgIGlzIGBncm91cENocm9tUGVha3NgLiBXZSB3aWxsIHVzZSB0aGUgcGVhayBkZW5zaXR5IG1ldGhvZCB0byBncm91cCBjaHJvbWF0b2dyYXBoaWMgcGVha3MuIFRoZSBhbGdvcml0aG0gY29tYmluZXMgY2hyb21hdG9ncmFwaGljIHBlYWtzIGRlcGVuZGluZyBvbiB0aGUgZGVuc2l0eSBvZiBwZWFrcyBhbG9uZyB0aGUgcmV0ZW50aW9uIHRpbWUgYXhpcyB3aXRoaW4gc21hbGwgc2xpY2VzIGFsb25nIHRoZSBteiBkaW1lbnNpb24uIA0KDQpUbyBpbGx1c3RyYXRlIHRoaXMgd2UgcGxvdCBiZWxvdyB0aGUgY2hyb21hdG9ncmFtIGZvciBhbiBteiBzbGljZSB3aXRoIG11bHRpcGxlIGNocm9tYXRvZ3JhcGhpYyBwZWFrcyB3aXRoaW4gZWFjaCBzYW1wbGUuIFdlIHVzZSBiZWxvdyBhIHZhbHVlIG9mIDAuNCBmb3IgdGhlICBgbWluRnJhY3Rpb25gIHBhcmFtZXRlciBoZW5jZSBvbmx5IGNocm9tYXRvZ3JhcGhpYyBwZWFrcyBwcmVzZW50IGluIGF0IGxlYXN0IDQwJSBvZiB0aGUgc2FtcGxlcyBwZXIgc2FtcGxlIGdyb3VwIGFyZSBncm91cGVkIGludG8gYSBmZWF0dXJlLiBUaGUgc2FtcGxlIGdyb3VwIGFzc2lnbm1lbnQgaXMgc3BlY2lmaWVkIHdpdGggdGhlIGBzYW1wbGVHcm91cHNgIGFyZ3VtZW50Lg0KDQpgYGB7cn0NCiMjIERlZmluZSB0aGUgbXogc2xpY2UuDQptenIgPC0gYygzMDUuMDUsIDMwNS4xNSkNCg0KIyMgRXh0cmFjdCBhbmQgcGxvdCB0aGUgY2hyb21hdG9ncmFtcw0KY2hyX216ciA8LSBjaHJvbWF0b2dyYW0oeGRhdGEsIG16ID0gbXpyLCBydCA9IGMoMjUwMCwgNDAwMCkpDQpwYXIobWZyb3cgPSBjKDMsIDEpLCBtYXIgPSBjKDEsIDQsIDEsIDAuNSkpDQpjb2xzIDwtIGdyb3VwX2NvbG9yc1tjaHJfbXpyJHNhbXBsZV9ncm91cF0NCnBsb3QoY2hyX216ciwgY29sID0gY29scywgeGF4dCA9ICJuIiwgeGxhYiA9ICIiKQ0KDQojIyBIaWdobGlnaHQgdGhlIGRldGVjdGVkIHBlYWtzIGluIHRoYXQgcmVnaW9uLg0KaGlnaGxpZ2h0Q2hyb21QZWFrcyh4ZGF0YSwgbXogPSBtenIsIGNvbCA9IGNvbHMsIHR5cGUgPSAicG9pbnQiLCBwY2ggPSAxNikNCg0KIyMgRGVmaW5lIHRoZSBwYXJhbWV0ZXJzIGZvciB0aGUgcGVhayBkZW5zaXR5IG1ldGhvZA0KcGRwIDwtIFBlYWtEZW5zaXR5UGFyYW0oc2FtcGxlR3JvdXBzID0geGRhdGEkc2FtcGxlX2dyb3VwLA0KCQkJbWluRnJhY3Rpb24gPSAwLjQsIGJ3ID0gMzApDQpwYXIobWFyID0gYyg0LCA0LCAxLCAwLjUpKQ0KcGxvdENocm9tUGVha0RlbnNpdHkoeGRhdGEsIG16ID0gbXpyLCBjb2wgPSBjb2xzLCBwYXJhbSA9IHBkcCwNCgkJICAgICBwY2ggPSAxNiwgeGxpbSA9IGMoMjUwMCwgNDAwMCkpDQoNCiMjIFVzZSBhIGRpZmZlcmVudCBidw0KcGRwIDwtIFBlYWtEZW5zaXR5UGFyYW0oc2FtcGxlR3JvdXBzID0geGRhdGEkc2FtcGxlX2dyb3VwLA0KCQkJbWluRnJhY3Rpb24gPSAwLjQsIGJ3ID0gMjApDQpwbG90Q2hyb21QZWFrRGVuc2l0eSh4ZGF0YSwgbXogPSBtenIsIGNvbCA9IGNvbHMsIHBhcmFtID0gcGRwLA0KCQkgICAgIHBjaCA9IDE2LCB4bGltID0gYygyNTAwLCA0MDAwKSkNCmBgYA0KDQpUaGUgdXBwZXIgcGFuZWwgaW4gdGhlIHBsb3QgYWJvdmUgc2hvd3MgdGhlIGV4dHJhY3RlZCBpb24gY2hyb21hdG9ncmFtIGZvciBlYWNoIHNhbXBsZSB3aXRoIHRoZSBkZXRlY3RlZCBwZWFrcyBoaWdobGlnaHRlZC4gVGhlIG1pZGRsZSBhbmQgbG93ZXIgcGxvdCBzaG93cyB0aGUgcmV0ZW50aW9uIHRpbWUgZm9yIGVhY2ggZGV0ZWN0ZWQgcGVhayB3aXRoaW4gdGhlIGRpZmZlcmVudCBzYW1wbGVzLiBUaGUgYmxhY2sgc29saWQgbGluZSByZXByZXNlbnRzIHRoZSBkZW5zaXR5IGRpc3RyaWJ1dGlvbiBvZiBkZXRlY3RlZCBwZWFrcyBhbG9uZyB0aGUgcmV0ZW50aW9uIHRpbWVzLiBQZWFrcyBjb21iaW5lZCBpbnRvIGZlYXR1cmVzIChwZWFrIGdyb3VwcykgYXJlIGluZGljYXRlZCB3aXRoIGdyZXkgcmVjdGFuZ2xlcy4gRGlmZmVyZW50IHZhbHVlcyBmb3IgdGhlIGBid2AgcGFyYW1ldGVyIG9mIHRoZSBgUGVha0RlbnNpdHlQYXJhbWAgd2VyZSB1c2VkOiBgYncgPSAzMGAgaW4gdGhlIG1pZGRsZSBhbmQgYGJ3ID0gMjBgIGluIHRoZSBsb3dlciBwYW5lbC4gV2l0aCB0aGUgZGVmYXVsdCB2YWx1ZSBmb3IgdGhlIHBhcmFtZXRlciBidyB0aGUgdHdvIG5laWdoYm9yaW5nIGNocm9tYXRvZ3JhcGhpYyBwZWFrcyB3b3VsZCBiZSBncm91cGVkIGludG8gdGhlIHNhbWUgZmVhdHVyZSwgd2hpbGUgd2l0aCBhIGBid2Agb2YgMjAgdGhleSB3b3VsZCBiZSBncm91cGVkIGludG8gc2VwYXJhdGUgZmVhdHVyZXMuIFRoaXMgZ3JvdXBpbmcgZGVwZW5kcyBvbiB0aGUgcGFyYW1ldGVycyBmb3IgdGhlIGRlbnNpdHkgZnVuY3Rpb24gYW5kIG90aGVyIHBhcmFtZXRlcnMgcGFzc2VkIHRvIHRoZSBhbGdvcml0aG0gd2l0aCB0aGUgYFBlYWtEZW5zaXR5UGFyYW1gLg0KDQoNCmBgYHtyfQ0KIyMgUGVyZm9ybSB0aGUgY29ycmVzcG9uZGVuY2UNCnBkcCA8LSBQZWFrRGVuc2l0eVBhcmFtKHNhbXBsZUdyb3VwcyA9IHhkYXRhJHNhbXBsZV9ncm91cCwNCgkJCW1pbkZyYWN0aW9uID0gMC40LCBidyA9IDIwKQ0KeGRhdGEgPC0gZ3JvdXBDaHJvbVBlYWtzKHhkYXRhLCBwYXJhbSA9IHBkcCkgDQpgYGANCg0KVGhlIHJlc3VsdHMgZnJvbSB0aGUgY29ycmVzcG9uZGVuY2UgY2FuIGJlIGV4dHJhY3RlZCB1c2luZyB0aGUgYGZlYXR1cmVEZWZpbml0aW9uc2AgbWV0aG9kLCB0aGF0IHJldHVybnMgYSBgRGF0YUZyYW1lYCB3aXRoIHRoZSBkZWZpbml0aW9uIG9mIHRoZSBmZWF0dXJlcy4gVGhlIGBmZWF0dXJlVmFsdWVzYCBtZXRob2QgcmV0dXJucyBhIG1hdHJpeCB3aXRoIHJvd3MgYmVpbmcgZmVhdHVyZXMgYW5kIGNvbHVtbnMgc2FtcGxlcy4gVGhlIGNvbnRlbnQgb2YgdGhpcyBtYXRyaXggY2FuIGJlIGRlZmluZWQgdXNpbmcgdGhlIGB2YWx1ZWAgYXJndW1lbnQuIFNldHRpbmcgYHZhbHVlID0gImludG8iYCByZXR1cm5zIGEgbWF0cml4IHdpdGggdGhlIGludGVncmF0ZWQgc2lnbmFsIG9mIHRoZSBwZWFrcyBjb3JyZXNwb25kaW5nIHRvIGEgZmVhdHVyZSBpbiBhIHNhbXBsZS4gQW55IGNvbHVtbiBuYW1lIG9mIHRoZSBgY2hyb21QZWFrc2AgbWF0cml4IGNhbiBiZSBwYXNzZWQgdG8gdGhlIGFyZ3VtZW50IGB2YWx1ZWAuIEJlbG93IHdlIGV4dHJhY3QgdGhlIGludGVncmF0ZWQgcGVhayBpbnRlbnNpdHkgcGVyIGZlYXR1cmUvc2FtcGxlLg0KDQpgYGB7cn0NCiMjIEV4dHJhY3QgdGhlIGZlYXR1cmUgZGVmaW5pdGlvbnMNCmZlYXR1cmVEZWZpbml0aW9ucyh4ZGF0YSkNCg0KIyMgRXh0cmFjdCB0aGUgaW50byBjb2x1bW4gZm9yIGVhY2ggZmVhdHVyZS4NCmhlYWQoZmVhdHVyZVZhbHVlcyh4ZGF0YSwgdmFsdWUgPSAiaW50byIpKQ0KYGBgIA0KDQpUaGlzIGZlYXR1cmUgbWF0cml4IGNvbnRhaW5zIGBOQWAgZm9yIHNhbXBsZXMgaW4gd2hpY2ggbm8gY2hyb21hdG9ncmFwaGljIHBlYWsgd2FzIGRldGVjdGVkIGluIHRoZSBmZWF0dXJlJ3MgbS96LXJ0IHJlZ2lvbi4gV2hpbGUgaW4gbWFueSBjYXNlcyB0aGVyZSBtaWdodCBpbmRlZWQgYmUgbm8gcGVhayBzaWduYWwgaW4gdGhlIHJlc3BlY3RpdmUgcmVnaW9uLCBpdCBtaWdodCBhbHNvIGJlIHRoYXQgdGhlcmUgaXMgc2lnbmFsLCBidXQgdGhlIHBlYWsgZGV0ZWN0aW9uIGFsZ29yaXRobSBmYWlsZWQgdG8gZGV0ZWN0IGEgY2hyb21hdG9ncmFwaGljIHBlYWsuIGB4Y21zYCBwcm92aWRlcyB0aGUgYGZpbGxDaHJvbVBlYWtzYCBtZXRob2QgdG8gZmlsbCBpbiBpbnRlbnNpdHkgZGF0YSBmb3Igc3VjaCBtaXNzaW5nIHZhbHVlcyBmcm9tIHRoZSBvcmlnaW5hbCBmaWxlcy4gVGhlIGZpbGxlZCBpbiBwZWFrcyBhcmUgYWRkZWQgdG8gdGhlIGBjaHJvbVBlYWtzYCBtYXRyaXggYW5kIGFyZSBmbGFnZ2VkIHdpdGggYW4gYDFgIGluIHRoZSBgImlzX2ZpbGxlZCJgIGNvbHVtbi4gQmVsb3cgd2UgcGVyZm9ybSBzdWNoIGEgZmlsbGluZy1pbiBvZiBtaXNzaW5nIHBlYWtzLg0KDQpgYGB7cn0NCnhkYXRhIDwtIGZpbGxDaHJvbVBlYWtzKHhkYXRhKQ0KDQpoZWFkKGZlYXR1cmVWYWx1ZXMoeGRhdGEpKQ0KYGBgDQoNCkZvciBmZWF0dXJlcyB3aXRob3V0IGRldGVjdGVkIHBlYWtzIGluIGEgc2FtcGxlLCB0aGUgbWV0aG9kIGV4dHJhY3RzIGFsbCBpbnRlbnNpdGllcyBpbiB0aGUgbXotcnQgcmVnaW9uIG9mIHRoZSBmZWF0dXJlLCBpbnRlZ3JhdGVzIHRoZSBzaWduYWwgYW5kIGFkZHMgYSBmaWxsZWQtaW4gcGVhayB0byB0aGUgYGNocm9tUGVha3NgIG1hdHJpeC4gTm8gcGVhayBpcyBhZGRlZCBpZiBubyBzaWduYWwgaXMgbWVhc3VyZWQvYXZhaWxhYmxlIGZvciB0aGUgbXotcnQgcmVnaW9uIG9mIHRoZSBmZWF0dXJlLiBGb3IgdGhlc2UsIGV2ZW4gYWZ0ZXIgZmlsbGluZyBpbiBtaXNzaW5nIHBlYWsgZGF0YSwgYSBOQSBpcyByZXBvcnRlZCBpbiB0aGUgZmVhdHVyZVZhbHVlcyBtYXRyaXguDQoNCkJlbG93IHdlIGNvbXBhcmUgdGhlIG51bWJlciBvZiBtaXNzaW5nIHZhbHVlcyBiZWZvcmUgYW5kIGFmdGVyIGZpbGxpbmcgaW4gbWlzc2luZyB2YWx1ZXMuIFdlIGNhbiB1c2UgdGhlIHBhcmFtZXRlciBmaWxsZWQgb2YgdGhlIGZlYXR1cmVWYWx1ZXMgbWV0aG9kIHRvIGRlZmluZSB3aGV0aGVyIG9yIG5vdCBmaWxsZWQtaW4gcGVhayB2YWx1ZXMgc2hvdWxkIGJlIHJldHVybmVkIHRvby4NCg0KYGBge3J9DQojIyBNaXNzaW5nIHZhbHVlcyBiZWZvcmUgZmlsbGluZyBpbiBwZWFrcw0KYXBwbHkoZmVhdHVyZVZhbHVlcyh4ZGF0YSwgZmlsbGVkID0gRkFMU0UpLCBNQVJHSU4gPSAyLA0KICAgICAgRlVOID0gZnVuY3Rpb24oeikgc3VtKGlzLm5hKHopKSkNCg0KIyMgTWlzc2luZyB2YWx1ZXMgYWZ0ZXIgZmlsbGluZyBpbiBwZWFrcw0KYXBwbHkoZmVhdHVyZVZhbHVlcyh4ZGF0YSksIE1BUkdJTiA9IDIsDQogICAgICBGVU4gPSBmdW5jdGlvbih6KSBzdW0oaXMubmEoeikpKQ0KYGBgDQoNCkF0IGxhc3Qgd2UgcGVyZm9ybSBhIHByaW5jaXBhbCBjb21wb25lbnQgYW5hbHlzaXMgdG8gZXZhbHVhdGUgdGhlIGdyb3VwaW5nIG9mIHRoZSBzYW1wbGVzIGluIHRoaXMgZXhwZXJpbWVudC4gTm90ZSB0aGF0IHdlIGRpZCBub3QgcGVyZm9ybSBhbnkgZGF0YSBub3JtYWxpemF0aW9uIGhlbmNlIHRoZSBncm91cGluZyBtaWdodCAoYW5kIHdpbGwpIGFsc28gYmUgaW5mbHVlbmNlZCBieSB0ZWNobmljYWwgYmlhc2VzLg0KDQpgYGB7cn0NCiMjIEV4dHJhY3QgdGhlIGZlYXR1cmVzIGFuZCBsb2cyIHRyYW5zZm9ybSB0aGVtDQpmdF9pbnRzIDwtIGxvZzIoZmVhdHVyZVZhbHVlcyh4ZGF0YSwgdmFsdWUgPSAiaW50byIpKQ0KDQojIyBQZXJmb3JtIHRoZSBQQ0Egb21pdHRpbmcgYWxsIGZlYXR1cmVzIHdpdGggYW4gTkEgaW4gYW55IG9mIHRoZQ0KIyMgc2FtcGxlcy4gQWxzbywgdGhlIGludGVuc2l0aWVzIGFyZSBtZWFuIGNlbnRlcmVkLg0KcGMgPC0gcHJjb21wKHQobmEub21pdChmdF9pbnRzKSksIGNlbnRlciA9IFRSVUUpDQoNCiMjIFBsb3QgdGhlIFBDQQ0KY29scyA8LSBncm91cF9jb2xvcnNbeGRhdGEkc2FtcGxlX2dyb3VwXQ0KcGNTdW1tYXJ5IDwtIHN1bW1hcnkocGMpDQpwbG90KHBjJHhbLCAxXSwgcGMkeFssMl0sIHBjaCA9IDIxLCBtYWluID0gIiIsIA0KICAgICB4bGFiID0gcGFzdGUwKCJQQzE6ICIsIGZvcm1hdChwY1N1bW1hcnkkaW1wb3J0YW5jZVsyLCAxXSAqIDEwMCwNCgkJCQkgICBkaWdpdHMgPSAzKSwgIiAlIHZhcmlhbmNlIiksDQogICAgIHlsYWIgPSBwYXN0ZTAoIlBDMjogIiwgZm9ybWF0KHBjU3VtbWFyeSRpbXBvcnRhbmNlWzIsIDJdICogMTAwLA0KCQkJCSAgIGRpZ2l0cyA9IDMpLCAiICUgdmFyaWFuY2UiKSwNCiAgICAgY29sID0gImRhcmtncmV5IiwgYmcgPSBjb2xzLCBjZXggPSAyKQ0KZ3JpZCgpDQp0ZXh0KHBjJHhbLCAxXSwgcGMkeFssMl0sIGxhYmVscyA9IHhkYXRhJHNhbXBsZV9uYW1lLCBjb2wgPSAiZGFya2dyZXkiLA0KICAgICBwb3MgPSAzLCBjZXggPSAyKQ0KYGBgDQoNCldlIGNhbiBzZWUgdGhlIGV4cGVjdGVkIHNlcGFyYXRpb24gYmV0d2VlbiB0aGUgS08gYW5kIFdUIHNhbXBsZXMgb24gUEMyLiBPbiBQQzEgc2FtcGxlcyBzZXBhcmF0ZSBiYXNlZCBvbiB0aGVpciBJRCwgc2FtcGxlcyB3aXRoIGFuIElEIDw9IDE4IGZyb20gc2FtcGxlcyB3aXRoIGFuIElEID4gMTguIFRoaXMgc2VwYXJhdGlvbiBtaWdodCBiZSBjYXVzZWQgYnkgYSB0ZWNobmljYWwgYmlhcyAoZS5nLiBtZWFzdXJlbWVudHMgcGVyZm9ybWVkIG9uIGRpZmZlcmVudCBkYXlzL3dlZWtzKSBvciBkdWUgdG8gYmlvbG9naWNhbCBwcm9wZXJ0aWVzIG9mIHRoZSBtaWNlIGFuYWx5emVkIChzZXgsIGFnZSwgbGl0dGVyIG1hdGVzIGV0YykuIA0K