1 Introduction

This article should serve as a tutorial on using the publicly available code which accompanies the paper “Effective Ways to Build and Evaluate Individual Survival Distributions”. There are two primary components to this article, (1) a quickstart option which gives an overview of using the master file, “analysisMaster.R”, and (2) the entire code overview which details all the functions and files found in the code. While using the “analysisMaster.R” file gives users access to most of the functionality of the codebase, no specialized modifications can be made to the models/evaluations directly so we give a detailed explanation of how to start with a dataset and end with evaluations of different models.

2 Quickstart Guide (analysisMaster.R)

Before you begin using the code, make sure all required packages are downloaded: caret, dataPreparation, ggplot2, reshape2,randomForestSRC, Rcpp, prodlim, survival, fastcox, plyr,and dplyr. To save you some time I have supplied a code snippet you can use to download any of the packages you may be missing:

#Taken from https://gist.github.com/stevenworthington/3178163
ipak <- function(pkg){
    new.pkg <- pkg[!(pkg %in% installed.packages()[, "Package"])]
    if (length(new.pkg)) 
        install.packages(new.pkg, dependencies = TRUE)
    sapply(pkg, require, character.only = TRUE)
}
packages <- c("caret", "dataPreparation", "ggplot2", "reshape2","randomForestSRC", "Rcpp", "prodlim", "survival", "fastcox", "plyr", "dplyr")
ipak(packages)

Next we will load the example data (which can be found on the PSSP website). Details about this dataset can be found in the Introduction to the Example Data.

exampleDat = read.csv(file = "http://pssp.srv.ualberta.ca/system/predictors/datasets/000/000/076/original/All_Data_updated_may2011_Stage4_Stomach.csv?1354708504")

To use this codebase, the time to event feature must be named time, and the indicator of death (uncensored) should be named delta. In the example data these are the first two columns and are currently named SURVIVAL and CENSORED, so we make this change. Additionally, this data has CENSORED = 1 mean that a patient was censored. We need 1 to indicate a death.

names(exampleDat)[c(1,2)] = c("time", "delta")
exampleDat$delta = 1 - exampleDat$delta 

Now that the data is formatted correctly and packages are loaded, go ahead and load “analysisMaster.R”

source("analysisMaster.R")

This file contains the function analysisMaster() which takes many arguments, each of which controls some aspect of either the different models or evaluations.

Arguments:

For this example we will simply use all the default arguments (with the exception of verbose, which we set to FALSE for brevity). Note that this command may take a few minutes to run (excluding Random Survival Forests will speed up computation time).

#RSF and CoxEN are non-deterministic, so set the seed for reproducible results. (Additional stochasticity is included in 1-Calibration).
set.seed(42)
res = analysisMaster(exampleDat, verbose = FALSE)
The variable(s) SITE_BRUNCHUS_LUNG, SITE_COLORECTAL, SITE_HEAD_AND_NECK, SITE_ESOPHAGUS, SITE_PANCREAS, SITE_STOMACH, SITE_OTHER_DIGESTIVE, MISC, STAGE_1, STAGE_2, STAGE_3, STAGE_4, STAGE_NUMERICAL contained only 1 unique value. They have been removed.

You will notice some variables were removed – See Section 4.1 for more details.

res is now a list of 3 items:

Looking at each of these in more detail, note the difference in size between res$datasetUsed and exampleDat.

dim(exampleDat)
[1] 128  53
dim(res$datasetUsed)
[1] 128  22


Additionally, when we sourced “analysisMaster.R” we brought in plotSurvivalCurves() which can be used to examine the survival curves of each model. Here we show examples of Cox Elastic-Net, Random survival forests, and Multi-task Logistic regression, however, survival curves for all models are available.

#CoxEN
plotSurvivalCurves(survivalCurves = res$survivalCurves$CoxEN, indexToPlot = 1:10)

#RSFKM
plotSurvivalCurves(survivalCurves = res$survivalCurves$RSF, indexToPlot = 1:10)

#MTLR
plotSurvivalCurves(survivalCurves = res$survivalCurves$MTLR, indexToPlot = 1:10)

Note that each index relates to the corresponding row in res$datasetUsed (e.g. the survival curve for Index 1 is for the for the patient in the first row of res$datasetUsed).

The last component of res is the results dataframe, which includes the evaluations for every fold (Concordance, L1-loss, Brier score) and the calibration evaluated on the entire dataset (1-Calibration, D-Calibration). Below is a view of all the results and an example of aggregating the result. (Note that we average the p-values of 1-Calibration and D-Calibration because they are equal across the folds because they were evaluated on all the test data as opposed to per fold – see Section 6.3 and Section 6.6)

res$results
res$results %>%
  group_by(Model)  %>%
  summarise(N = mean(N),
            NumFeatures = mean(NumFeatures),
            PercentCensored = mean(PercentCensored),
            DCalibration = mean(DCalibration),
            OneCalibration10th = mean(OneCalibration_1),
            OneCalibration25th = mean(OneCalibration_2),
            OneCalibration50th = mean(OneCalibration_3),
            OneCalibration75th = mean(OneCalibration_4),
            OneCalibration90th = mean(OneCalibration_5),
            ConcordanceM = mean(Concordance),
            ConcordanceSD = sd(Concordance),
            SingleBrierM = mean(BrierSingle),
            SingleBrierSD = sd(BrierSingle),
            IntegratedBrierM = mean(BrierInt),
            IntegratedBrierSD = sd(BrierInt),
            L1M = mean(L1Loss),
            L1SD = sd(L1Loss))

Here we have given a fairly brief introduction to using the codebase to run the 6 survival models across the 5 different evaluation metrics. For a much more detailed introduction to the code (and a walk through of all the functions) continue on to the next sections or jump to a specific section using the table of contents.

3 Code Overview and Introduction to Example Data

The code here has been written for survival datasets, i.e. datasets are comprised of (1) a variable which indicates time of event, (2) an indicator variable for RIGHT censoring, and (3) features for each patient. Note that the code only allows for right censored patients, left censored or interval censored data will not work with the current implementation. To follow along with the examples please make sure that you have downloaded all the required packages (see the first paragraph of the Quickstart Guide), have your working directory in ~/ISDEvaluation, and sourced all the files (available by running `source(“analysisMaster.R”)).

As a case study we will use the Northern Alberta Cancer Dataset (NACD) which was also included in the original paper. However, to simplify things we will use a new subset of the dataset, namely we will only consider patients with stage 4 stomach cancers. This data contains a total of 128 patients (100 uncensored) and 53 features, and is also available on the github repository under the name “exampleData.csv”. Below we give the summary statistics of the first five variables:

NACD = read.csv(file = "http://pssp.srv.ualberta.ca/system/predictors/datasets/000/000/076/original/All_Data_updated_may2011_Stage4_Stomach.csv?1354708504")
head(NACD)
summary(NACD[,1:5])
    SURVIVAL          CENSORED          GENDER         BOX1_SCORE      BOX2_SCORE 
 Min.   : 0.2333   Min.   :0.0000   Min.   :0.0000   Min.   :0.000   Min.   :0.0  
 1st Qu.: 4.3447   1st Qu.:0.0000   1st Qu.:0.0000   1st Qu.:0.000   1st Qu.:1.0  
 Median : 9.9877   Median :0.0000   Median :1.0000   Median :2.000   Median :1.0  
 Mean   :14.0799   Mean   :0.2188   Mean   :0.6797   Mean   :2.109   Mean   :1.5  
 3rd Qu.:17.2667   3rd Qu.:0.0000   3rd Qu.:1.0000   3rd Qu.:4.000   3rd Qu.:2.0  
 Max.   :62.7667   Max.   :1.0000   Max.   :1.0000   Max.   :5.000   Max.   :4.0  

Here, we have SURVIVAL as the feature indicating the time until the event occurred and CENSORED representing the indicator for death, that is, a 1 indicates the patient was censored and a 0 indicates the patient died – these two types of variables are required for all datasets in this codebase. Feel free to explore the other variables if you are interested.

A quick note on notation – In general features and packages will be given as verbatim, e.g. package1 and feature1, functions will also be verbatim but also have parentheses e.g. function1(), arguments to functions will be italicized, variable assignments will be bolded, and files will be quoted (“file1.R”). However, there are exceptions when functions/file names are contained in section headers.

4 Pre-processing the Data

4.1 Validating and Cleaning (validateAndClean.R)

File Purpose: This file is used to make sure the survival dataset is in the correct format for analysis. For example, the variable used for survival time must be named exactly time. Additionally, a minimal amount of data cleansing is completed in this file.

File Path: “ISDEvaluation/ValidateCleanCV/validateAndClean.R”.

Functions:

  1. validateAndClean(survivalDataset, imputeZero=T)
  2. validate(survivalDataset, imputeZero)
  3. clean(survivalDataset)

Note: You will notice this file structure throughout the codebase, there will often be a master function (here validateAndClean()) which runs the other functions included in the file, e.g. validate() and clean().

Arguments:

  • survivalDataset: a (non-empty) dataframe representing the survival dataset – e.g. NACD
  • imputeZero: a Boolean indicating if zero valued survival times should be imputed (see below for details).

Packages Required:

  1. caret – The nearZeroVar() function to identify variables with only 1 unique value and remove them in the clean() function.

4.1.1 validate(survivalDataset, imputeZero)

The function validate() checks a number of things:

  1. A survival dataset was nonempty.
  2. The survival time variable MUST be named time.
  3. Similarly, the indicator of death variable must be named delta. The naming choice of this variable is the common notation in survival analysis.
  4. If time includes NA values these rows will be removed.
  5. If time includes Inf values these rows will be removed.
  6. If time includes negative values these rows will be removed.
  7. If time includes zero values there are two options depending on the value of the argument imputeZero. If imputeZero = TRUE, then zero valued times will be imputed to half the minimum non zero valued time. For example in the NACD dataset we are using here the minimum time is 0.2333. If there was another row where the event time was 0, this would be imputed to \(\frac{0.2333}{2} = 0.1166\). We include this option as some survival models (e.g. Accelerated Failure Time with the Weibull distribution) will fail if given a zero valued time. If imputeZero = FALSE then nothing will happen to these zero valued survival times. Currently there is no option to remove zero valued times other than removing them from the dataset prior to running the code available here.
  8. If delta includes NA values these rows will be removed.
  9. If delta includes Inf values these rows will be removed.
  10. delta must only have two unique values.
  11. The unique values of delta must be 0 and 1 – 0 for censored data and 1 for uncensored data.

If we try to use validate() on our NACD dataset we will get an error since currently our survival time variable is named Time and our death indicator is named Status. Note that below we include imputeZero = TRUE, but since there are no zero-valued times the same result would occur if we included imputeZero=FALSE.

validatedNACD = validate(NACD, imputeZero = TRUE)
Error in validate(NACD, imputeZero = TRUE) : 
  The variable 'time' in not included in the given dataset.
names(NACD)[1] = "time"
validatedNACD = validate(NACD, imputeZero = TRUE)
Error in validate(NACD, imputeZero = TRUE) : 
  The variable 'delta' in not included in the given dataset.
#Additionally, we need to swap the values for delta since CENSORED = 1 meant a patient was censored. We need 1 to mean a death.
names(NACD)[2] = "delta"
NACD$delta = 1 - NACD$delta
validatedNACD = validate(NACD, imputeZero = TRUE)

4.1.2 clean(survivalDataset)

Here, clean() does a very minimal amount of cleaning on the survival dataset. Specifically, the goal is to remove variables which will be not be meaningful to analysis. There are two cases where variables are removed:

  1. A variable contains all unique values. For example, in our NACD dataset there is a STAGE_4 variable which is an indicator (1 or 0) whether or not the cancer is in stage 4. Since we only have stage 4 stomach cancer patients this variable will be be all 1s and will be removed from the dataset.
  2. Variables such that 25% of rows (patients) have a missing value will be removed. However, the coding of missing data must be such that is.na() would identify the data as missing. For example is.na("") returns TRUE even though an empty string likely indicates a missing value.

Whenever a variable is removed or turned into a factor, clean() will return a warning indicating the name of the variable(s) which were removed/factored. For example, on our validated NACD dataset we can now call clean():

cleanedNACD = clean(validatedNACD)
The variable(s) SITE_BRUNCHUS_LUNG, SITE_COLORECTAL, SITE_HEAD_AND_NECK, SITE_ESOPHAGUS, SITE_PANCREAS, SITE_STOMACH, SITE_OTHER_DIGESTIVE, MISC, STAGE_1, STAGE_2, STAGE_3, STAGE_4, STAGE_NUMERICAL contained only 1 unique value. They have been removed.

As noted above a number of variables were removed due to having only 1 unique value.

4.1.3 validateAndClean(survivalDataset, imputeZero=T)

This function simply runs both validate() and clean() on a survival dataset. Note that here we use default the default (TRUE) for imputeZero. Below we verify that validateAndClean(NACD) is equivalent to cleanedNACD.

identical(validateAndClean(NACD),cleanedNACD)
[1] TRUE

4.2 Creating Cross-Validation Folds and Normalizing Features (createFoldsAndNormalize.R)

File Purpose: This file is used to create folds from the survival dataset for cross validation and normalize features. As part of normalization, mean/mode imputation is employed to handle missing data.

File Path: “ISDEvaluation/ValidateCleanCV/createFoldsAndNormalize.R”.

Functions:

  1. createFoldsAndNormalize(survivalDataset, numberOfFolds)
  2. createFoldsOfData(survivalDataset, numberOfFolds)
  3. meanImputation(listOfDatasets)
  4. normalizeVariables(listOfImputedDatasets)

Arguments:

  • survivalDataset: the survivalDataset AFTER going through validation and cleaning – e.g. cleanedNACD
  • numberOfFolds: the desired number of cross-validation folds, must be greater than 1.
  • listOfDatasets: a list containing a list of Training datasets and a list of Testing datasets - i.e. the output of createFoldsOfData()
  • listOfImputedDatasets: same as listOfDatasets but missing values have been imputed (the output of meanImputation())

Packages Required:

  1. caret – The dummyVars() function is used to create a one hot encoding of factor (nominal) variables.
  2. dataPreparation – The build_scales() and fastScale() function are used to normalize variables.

4.2.1 createFoldsOfData(survivalDataset, numberOfFolds)

This code base requires that data be in cross-validation format with a minimum of 2 folds. The cross-validation used here is specific to survival analysis in that cross-validation is deterministic – rows are first stratified by their censoring status (delta) and then sorted by time and sequentially placed into folds (see Figure 1). The reasoning behind this procedure is two-fold (pun-intended):

  1. The number of censored patients should be (roughly) equal across all folds.
  2. The range of event times should also be (roughly) equal across all folds.

By creating folds in this way models will be tested on data which will be mostly representative of what they saw in the training data.

Figure 1: Depiction of 3-fold cross-validation scheme used for survival data. The dataset is first stratefied by delta and then sorted by time. Rows are then sequentially ordered into the three folds.

Figure 1: Depiction of 3-fold cross-validation scheme used for survival data. The dataset is first stratefied by delta and then sorted by time. Rows are then sequentially ordered into the three folds.

In addition to creating cross validation folds, createFoldsOfData() also returns the indexes of the rows that each test set belongs. See the below example for details. Here we put our cleaned NACD dataset (cleanedNACD) into 5 folds:

folds = createFoldsOfData(cleanedNACD, 5)
#folds comprises a list of (2) items. (1) is the indexs of the test set. For example, examine the first test set fold:
testSetIndexs = folds[[1]]
#Test fold 1:
testSetIndexs[[1]]
 [1] 123 101  62  18  96  21 110 125  17  34 122 124  97  44  10  95  51  91 112  72 106  74  79 105  14  77

Here the first test fold is comprised of the 123rd, 101st, 62nd, …, and the 77th rows of the original data. We will later use these indexes for plotting in Section 7.1.

The second item returned by createFoldsOfData() is a list of (1) the training sets, and (2) the testing sets. Below we simply give the dimension of each:

trainingAndTestingSets = folds[[2]]
#First Training Set:
dim(trainingAndTestingSets$Training[[1]])
[1] 102  40
#First Testing Set:
dim(trainingAndTestingSets$Testing[[1]])
[1] 26 40

4.2.2 meanImputation(listOfDatasets)

Following the splitting of folds we perform imputation for missing data. Since the primary focus of this work was not in any way geared towards imputation methods we do a very basic mean imputation for missing values within fold. The means for all variables for each training fold are computed and the corresponding test fold is imputed with the mean value from the training fold.

In order to impute factor (nominal) variables, we use the one-hot encoding of these variables and then impute missing values with the mean of the one-hot encoded vector. Note that time and delta are not subject to imputation – if either of these variables contained missing values the entire row was removed in validate().

Below we would impute missing data, however, the NACD dataset we are using has no missing data so the following step is not necessary.

imputedData = meanImputation(trainingAndTestingSets)

4.2.3 normalizeVariables(listOfImputedDatasets)

Following variable imputation, all the variables are normalized (except time and delta) by taking the scales (mean and standard deviation) of the training set variables and normalizing both the training set and test set with those scales. Here normalizing follows the standard convention of subtracting the mean and dividing by the standard deviation. This function also does some cleaning of variable names – if a variable name has a comma in it functions later of will break so all commas and other special characters are removed.

Below we normalize the data and show the difference between the data before and after it was normalized.

head(imputedData$Training[[1]])
normalizedData = normalizeVariables(imputedData)
head(normalizedData$Training[[1]])

4.2.4 createFoldsAndNormalize(survivalDataset, numberOfFolds)

Similar to validateAndClean() above, createFoldsAndNormalize() serves as a master function which will run createFoldsOfData(), meanImputation(), and normalizeVariables() and will return a list of (2) items, (1) the indexes of the test set as given in Section 2.2.1, and (2) a list containing: (1) a list of normalized training folds and (2) a list of normalized testing folds.

Here we show the equivalence between testSetIndexs from above and the first item of createFoldsAndNormalize() and equivalence of normalizedData with the second item of createFoldsAndNormalize():

output = createFoldsAndNormalize(cleanedNACD,5)
all.equal(testSetIndexs, output[[1]])
[1] TRUE
all.equal(normalizedData, output[[2]])
[1] TRUE

5 Creating Individual Survival Distribution Models

The intention of this work is to examine different survival analysis models which can produce individualized survival distributions for different patients. In total we explored five models which produced individual survival distributions and one model which gave a population based survival curve (Kaplan-Meier). Below we give a minimal description for each model and describe how to implement each in the current codebase.

5.1 Kaplan-Meier (KaplanMeier.R)

The Kaplan-Meier (KM) estimator was devised as a non-parametric approach to estimating a survival curve even in the presence of censored data (see the original paper here). To give the KM estimator we first make the following definitions:

  • \(\tau_j\) – the \(j^\textrm{th}\) distinct death time,
  • \(d_j\) – the number of deaths at time \(\tau_j\),
  • \(r_j\) – the number of patients who were at risk at time \(\tau_j\). Note this also includes those who died at \(\tau_j\).

With these definitions, we say the probability of survival at time \(t\) is given by the KM estimator as \[\hat{S}_{KM}(t) = \prod_{j: \tau_j < t} \left(1 - \frac{d_j}{r_j}\right)\]. Note that here KM does not take into account any features and thus is considered to a non-parametric model. To give an example of the Kaplan-Meier approach consider the following data:

Example data for the Kaplan-Meier model. Note the data is transposed from the normal format.
Patient 1 Patient 2 Patient 3 Patient 4 Patient 5
time 1 3 5 7 9
delta 1 0 1 1 1

Given the data above, we have \(\hat{S}_{KM}(1) = \left(1 - \frac{1}{5}\right) = 0.8\). Note that no change occurs at \(t= 3\) since this patient was censored and \(\tau_j\) only refers to unique death times. Below we give the full KM curve for this example.

Figure 2: Kaplan-Meier plot using example data. Censored patients indicated by vertical dash on survival curve.

Since the KM model does not account for individual features, the survival distribution generated is supposed to represent the population’s overall survival curve, i.e. the average survival curve. So given \(K\)-fold cross-validation, there will only be \(K\) different survival curves as opposed to the \(N\) different survival curves the other models generate, given \(N\) unique patients. Next we give the details for running KM on a dataset after it has prepossessed by validateAndClean() and createFoldsAndNormalize().

5.1.1 Kaplan-Meier Code (KaplanMeier.R)

File Purpose: Use the Kaplan-Meier model that has been processed and split into folds by createFoldsAndNormalize().

File Path: “ISDEvaluation/Models/KaplanMeier.R”.

Functions:

  1. KM(training, testing)

Arguments:

  • training: The training set.
  • testing: The testing set.

Packages Required:

  1. survival – We require the Surv() function to change the data into a survival format.
  2. prodlim – prodlim contains a Kaplan-Meier implementation that includes a predict() function.

File Notes:

Given the list of datasets generated by createFoldsAndNormalize(), KM() will take a single training and testing set and build the Kaplan-Meier curve for the training set using the prodlim() function from the prodlim package. The output (along with the output of all following models) is a list of four objects:

  1. TestCurves: The survival curves for test patients (identical for all patients for KM).
  2. TestData: The values of time and delta for the test patients (used later when evaluating survival curves).
  3. TrainData: The values of time and delta for the training patients (used later when evaluating survival curves).
  4. TrainCurves: The survival curves for the training patients.

Note the output is the same for all following models.

Example Usage

Here, we give an example of using KM on a single training/testing fold – extending the results to all folds can be accomplished by using a for loop.

validatedAndCleaned = validateAndClean(survivalDataset = NACD)
folds = createFoldsAndNormalize(survivalDataset = validatedAndCleaned, numberOfFolds = 5)
#Recall the first argument of folds is the indexs of the test set on validatedAndCleaned and the second argument is the list of training and testing sets.
trainAndTest = folds[[2]]
training1stFold = trainAndTest$Training[[1]]
testing1stFold = trainAndTest$Testing[[1]]
kmMod = KM(training1stFold, testing1stFold)
testCurves = kmMod$TestCurves
testDat = kmMod$TestData
trainDat = kmMod$TrainData
testCurves

Above you will see that the survival curves for all patients are the exact same – the desired effect for the Kaplan-Meier model. Note that time will correspond to all the event times (both death and censored event times) of the training data. Additionally we include a time = 0 point where the survival probability will be 1 – this is consistent across all models.

For a more picturesque approach, we give the survival curve below:

One should notice that the survival curve does not drop to zero and instead ends at \(\approx 0.05\) – this is because the last patient in the training data was censored. Additionally, the above plot does not follow the classical Kaplan-Meier step function and is smoother using a spline fit – the details can be found in Section 7.1 which describes the plotting function.

So far we have shown the output of curves but ignored trainDat and testDat which are just the training and testing values for time and delta. Across all the following models, trainDat and testDat will be the same thing, but for clarity we will include them below and not in following models.

trainDat
time delta
15.8333333 1
10.3000000 1
12.5666667 1
13.6333333 1
55.1000000 1
17.5666667 1
10.5409840 1
7.0666667 0
18.1393440 1
15.6147540 1
30.8333333 0
11.4000000 1
11.5000000 1
5.9333333 1
3.1475410 1
7.8114750 1
5.1639340 1
7.1333333 1
19.0333333 1
0.6333333 1
5.8666667 0
16.4000000 1
2.8114750 1
15.0737700 1
12.5491800 1
12.3360660 1
7.0666667 1
35.3196720 1
2.6393440 1
19.2666667 1
17.1666667 1
50.7666667 1
0.7540980 1
4.4000000 1
19.0819670 1
10.6803280 1
1.6000000 1
10.6803280 1
44.7333333 1
35.9333333 1
0.9672130 1
13.9754100 1
31.9666667 0
48.7666667 0
1.6000000 1
6.6393440 1
10.3852460 1
5.5573770 1
20.7000000 1
1.7704920 1
13.2704920 1
4.3666667 1
2.9333333 1
1.2049180 1
0.9333333 0
9.2000000 1
2.4000000 1
6.5409840 1
6.3000000 0
32.1000000 1
6.8770490 1
2.1666667 1
36.4333333 1
7.3333333 0
15.3666667 1
9.8032790 1
62.7666667 0
3.5901640 1
31.3666667 0
36.7333333 0
5.1967210 1
43.6333333 0
1.2622950 1
13.9098360 1
10.1721310 1
13.8666667 1
25.1000000 1
11.5573770 1
5.1666667 0
4.2786890 1
19.8000000 1
7.1967210 1
3.1967210 1
7.9098360 1
42.0333333 0
8.5819670 1
1.3333333 0
9.0333333 0
6.1311480 1
4.1639340 1
3.3770490 1
10.6333333 1
16.6666667 1
5.3666667 1
4.2000000 0
1.2295080 1
12.9666667 1
2.6666667 0
37.5333333 0
56.8000000 0
0.4666667 0
4.5666667 0



testDat
time delta
0.2333333 0
3.7000000 0
6.1000000 0
23.5666667 0
36.8000000 0
50.1333333 0
0.7704920 1
1.2666667 1
2.2622950 1
3.0409840 1
4.1475410 1
4.9333333 1
5.5737700 1
6.8000000 1
7.2704920 1
9.5901640 1
10.4016390 1
10.7704920 1
12.5000000 1
13.5000000 1
14.7666667 1
16.3333333 1
18.0737700 1
19.5000000 1
35.2666667 1
46.8333333 1

5.2 Accelerated Failure Time

The Accelerated Failure Time (AFT) has been a commonly used survival analysis technique for finding significant features in a survival setting. The model is motivated by assuming survival time, \(T\) follows some distribution with density function \(f(t)\) and survival function \(S(t)\). Common AFT distributions are log-logistic, Weibull, log-normal, exponential and more. While we make assumptions on the distribution of \(T\), inference is instead made on \(\log(T)\), that is, for a patient, \(\mathbf{x_i}\) where \(i = 1,2,\ldots N\), AFT models follow the equation: \[ log(T_i) = \boldsymbol{w}^T\boldsymbol{x_i} + \sigma\epsilon_i, \] where \(\boldsymbol{w}\) are feature weights, \(\sigma\) is called a scale parameter and \(\epsilon_i\) is an independent, identically distributed noise parameter dependent on the assumed distribution of \(T\). This is essentially a normal linear regression but on the log scale of \(T\), i.e. features are assumed to have a linear relationship with the log of survival time. Here, \(\sigma\) is assumed to be constant across all patients and can be learned by maximum likelihood along with the weights \(\boldsymbol{w}\).

5.2.1 Accelerated Failure Time Code (AcceleratedFailureTime.R)

File Purpose: Apply the Accelerated Failure Time model (for a number of distributions) to data that has been processed and split into folds by createFoldsAndNormalize().

File Path: “ISDEvaluation/Models/AcceleratedFailureTime.R”.

Functions:

  1. AFT(training, testing, AFTDistribution)
  2. survfunc(object,t,newdata, name) - Used to extract survival cures from an AFT model. Altered from a function previously written by Timothy Johnson.

Arguments:

  • training: The training set.
  • testing: The testing set.
  • AFTDistribution: The distribution for the AFT model. Possible distributions: exponential, weibull, lognormal, gaussian, loglogistic, logistic.
  • object: A survreg object from the survreg() function.
  • t: The times for which to evaluate the AFT model.
  • newdata: The testing data to predict with using the AFT model.
  • name: The name of the variable containing the predicted times (t).

Packages Required:

  1. survival – We use the survreg() function to perform analysis for AFT models.

File Notes:

The primary function for this file is AFT(); survfunc() is a helper function AFT uses to build survival curves for test data after the model has been learned.

Given the list of datasets generated by createFoldsAndNormalize(), AFT() will take a single training set, testing set, and a specified distribution and learn the AFT model using the training set. Only one distribution can be tested at a time. In addition, the code contains a tryCatch command the catch the event where the AFT model fails to run, however, this only showed to occur for high dimensional datasets (when no feature selection was applied).

As with the Kaplan-Meier model, the output is a list of four items:

  1. TestCurves: The survival curves for test patients (identical for all patients for KM).
  2. TestData: The values of time and delta for the test patients (used later when evaluating survival curves).
  3. TrainData: The values of time and delta for the training patients (used later when evaluating survival curves).
  4. TrainCurves: The survival curves for the training patients.

Example Usage:

validatedAndCleaned = validateAndClean(survivalDataset = NACD)
folds = createFoldsAndNormalize(survivalDataset = validatedAndCleaned, numberOfFolds = 5)
trainAndTest = folds[[2]]
training1stFold = trainAndTest$Training[[1]]
testing1stFold = trainAndTest$Testing[[1]]
AFTMod = AFT(training1stFold, testing1stFold, "weibull")
curves = AFTMod$TestCurves
curves


5.3 Cox-Proportional Hazards with Kalbfleisch-Prentice Extension

The Cox proportional-hazards (Cox-PH) model is extremely common in the survival analysis literature. Previously, we mentioned the Kaplan-Meier model which modeled the survival function, \(S_{KM}(t)\) of a group of patients. Next, the Accelerated Failure Time model assumed a distribution on the survival time, \(T\), which allowed us to include features and build curves for individual patients. Instead, Cox-PH models the hazard function of patients, where hazard is interpreted as the risk of failure (dying) at any given time, specifically for a time \(t\) and covariates \(\boldsymbol{x}\) the hazard function is defined as \[ h(t\,|\,\boldsymbol{x}) = \lim_{\Delta t \rightarrow \,0} \frac{P(t \leq T < t + \Delta t\,\,\, |\,\,\, T \geq t, \boldsymbol{x} )}{\Delta t}. \]

Cox-PH models this hazard function in terms of a baseline hazard, \(h_0(t)\), equal for all patients and scaled by a learned, individual risk dependening on features, that is, \(h(t|\boldsymbol{x}) = h_0(t)\,e^{\boldsymbol{w}^T\boldsymbol{x}}\). This is why the model is termed the proportional hazards model, the hazard for one patient is proportional to the hazard of all other patients, by a scale depending on individual features.

To learn the values for \(\boldsymbol{w}^T\) one does not need to know the baseline hazard function. Suppose a single patient died at time \(t_j\). The probability that patient \(i\) was the one who died is given by \[\begin{align*} \frac{h(t_j\,|\,\boldsymbol{x_i})}{\sum_{k \in R(t_j)} h(t_j\,|\,\boldsymbol{x_k})} \quad &= \quad \frac{h_0(t_j)\,e^{\boldsymbol{w}^T\,\boldsymbol{x_i}}}{\sum_{k \in R(t_j)} h_0(t_j)\,e^{\boldsymbol{w}^T\,\boldsymbol{x_k}}}, \\ &= \quad \frac{e^{\boldsymbol{w}^T\,\boldsymbol{x_i}}}{\sum_{k \in R(t_j)} e^{\boldsymbol{w}^T\,\boldsymbol{x_k}}}, \end{align*}\]

where \(R(t_j)\) is the set of patients still alive (at risk) at time \(t_j\). Thus the likelihood for \(\boldsymbol{w}^T\) is defined as \[ L\left(\boldsymbol{w}^T\right) = \prod_j \frac{e^{\boldsymbol{w}^T\,\boldsymbol{x_{i\,(j\,)}}}}{\sum_{k \in R(t_j)} e^{\boldsymbol{w}^T\,\boldsymbol{x_k}}}, \] where subscript \(i (j)\) is interpreted as it is patient \(i\) who died at time \(j\).

By this formulation of the likelihood equation, we see that feature weights are independent of time, e.g in a study of survival post surgery, lab test results immediately following surgery have the same weight one day after surgery as they do a year from surgery.

While we are able to estimate feature weights without specifying the baseline hazard function, we cannot determine the survival distribution. A number of methods exist for calculating the the baseline hazard and therefore the survival distribution including the Kalbfleisch-Prentice estimator which we use in this work. The thesis “Baseline Survival Function Estimator under Proporitional Hazards Assumption” by Weng provides a nice discussion of the Breslow and Kalbfleisch-Prentice estimators.

5.3.1 Cox-Proportional Hazards with Kalbfleisch-Prentice Extension Code (CoxPH_KP.R)

File Purpose: Apply the Cox Proportional-Hazards model to data that has been processed and split into folds by createFoldsAndNormalize().

File Path: “ISDEvaluation/Models/CoxPH_KP.R”.

Functions:

  1. CoxPH_KP(training, testing, ElasticNet)
  2. KPEstimator(lp, lpTime, censorStatus) - Used to estimate the Kalbfleisch-Prentice estimator when doing elastic-net Cox.

Arguments:

  • training: The training set.
  • testing: The testing set.
  • ElasticNet: A Boolean specifying whether or not to
  • lp: The linear predictions (\(\boldsymbol{w}^T\boldsymbol{x}\)) from the Cox-PH model for the training data.
  • lpTime: The training dataset event times, i.e. time.
  • censorStatus: The training dataset censor status, i.e delta.

Packages Required:

  1. survival – Needed for coxph() and survfit().
  2. fastcox – See Elastic-Net Cox.
  3. prodlim – See Elastic-Net Cox.

File Notes:

The primary function for this file is CoxPH_KP(); KPEstimator() is only used for the Elastic-Net Cox model.

Given the list of datasets generated by createFoldsAndNormalize(), CoxPH_KP() will take a single training set, testing set, and a Boolean specifying whether or not to use the elastic-net model. For this section we assume ElasticNet = FALSE, that is we are NOT using the elastic net formulation. The code uses a tryCatch function to avoid models which are failing to converge (this happened for high dimensional datasets). The Cox-PH model is fit using coxph() from the survival package and then survival curves are fit using survfit() and specifying the type to be the Kalbfleisch-Prentice estimator. Ties are accounted for using the default of coxph(), i.e. the Efron approximation.

The output is the same as the four previous models.

Example Usage:

validatedAndCleaned = validateAndClean(survivalDataset = NACD)
folds = createFoldsAndNormalize(survivalDataset = validatedAndCleaned, numberOfFolds = 5)
trainAndTest = folds[[2]]
training1stFold = trainAndTest$Training[[1]]
testing1stFold = trainAndTest$Testing[[1]]
CoxMod = CoxPH_KP(training1stFold, testing1stFold, ElasticNet=F)
curves = CoxMod$TestCurves
curves


Note the preceding Figure depicts proportional hazards – all curves are the exact same shape but at different heights, i.e. they are proportional to one another.

5.4 Cox-Proportional Hazards with Kalbfleisch-Prentice Extension – Elastic Net

We include the elastic net version of Cox-PH to include a regularized version of Cox-PH to compete with other models such as Random Survival Forests and Multi-Task Logistic Regression which have built in regularization. The notation and implementations of elastic net come from Yang and Zou’s paper entitiled “A Cocktail Algorithm for Solving The Elastic Net Penalized Cox’s Regression in High Dimensions”“. The elastic net penalty adds the following term to the likelihood equation, \[ P_{\lambda, \alpha}(\boldsymbol{w}) = \sum_{j=1}^p \lambda\left(\alpha\,|w_j| + \frac{1}{2}\,(1-\alpha)\, w_j^2 \right), \] for \(p\) features where \(\lambda > 0\) and \(0 < \alpha \leq 1\). The first penalty term, \(|w_j|\) corresponds to the LASSO loss and is responsible for feature selection whereas the quadratic term is to control the size of feature weights. For example, \(\alpha = 1\) would correspond to just LASSO-Cox, whereas \(\alpha \approx 0\) would correspond to Ridge-Cox.

For more information regarding elastic net Cox see the Yang and Zou’s paper referenced above.

5.4.1 Cox-Proportional Hazards with Kalbfleisch-Prentice Extension – Elastic Net Code (CoxPH_KP.R)

File Purpose: Apply the Cox Proportional-Hazards model to data that has been processed and split into folds by createFoldsAndNormalize().

File Path: “ISDEvaluation/Models/CoxPH_KP.R”.

Functions:

  1. CoxPH_KP(training, testing, ElasticNet)
  2. KPEstimator(lp, lpTime, censorStatus) - Used to estimate the Kalbfleisch-Prentice estimator when doing elastic net Cox.

Arguments:

  • training: The training set.
  • testing: The testing set.
  • ElasticNet: A Boolean specifying whether or not to
  • lp: The linear predictions (\(\boldsymbol{w}^T\boldsymbol{x}\)) from the Cox-PH model for the training data.
  • lpTime: The training dataset event times, i.e. time.
  • censorStatus: The training dataset censor status, i.e delta.

Packages Required:

  1. survival – Needed for the normal Cox implementation.
  2. fastcox – Used for the implementation of elastic net cox (cocktail())
  3. prodlim – Used for the `sindex() function – sindex returns the indexes of positions, ideal for evaluating step functions.

File Notes:

Given the list of datasets generated by createFoldsAndNormalize(), CoxPH_KP() will take a single training set, testing set, and a Boolean specifying whether or not to use the elastic-net model. For this section we assume ElasticNet = TRUE, that is we are using the elastic net formulation. There are two hyperparameters involved for elastic net Cox, the strength of the penalty, \(\lambda\), and the type of penalty (more LASSO like or more Ridge like), \(\alpha\). Six values are tested for \(\alpha\), 0.01, 0.2, 0.4, 0.6, 0.8, and 1.0. For each of these values the function cv.cocktail() tests 100 values of \(\lambda\) using 5-fold cross validation and returns \(\lambda_{min}\), the optimal value of \(\lambda\) to minimize the cross validation error (see the `cocktail() function of the package documentation).The optimal value for \(\alpha\) and \(\lambda\) from internal (10) repeated 5-fold cross validation are then used for evaluating the test fold. Note the optimal values for \(\alpha\) and \(\lambda\) will (likely) differ across folds.

One the feature weights have been estimated, linear predictors are passed to KPEstimator() to estimate the survival curves. As of now, ties are not accounted for and the resulting survival distribution are using the estimate derived in the presence of no ties. Future iterations of this codebase should account for the correct estimate when handling ties.

The output is the same as previous models.

Example Usage:

validatedAndCleaned = validateAndClean(survivalDataset = NACD)
folds = createFoldsAndNormalize(survivalDataset = validatedAndCleaned, numberOfFolds = 5)
trainAndTest = folds[[2]]
training1stFold = trainAndTest$Training[[1]]
testing1stFold = trainAndTest$Testing[[1]]
CoxMod = CoxPH_KP(training1stFold, testing1stFold, ElasticNet=T)
curves = CoxMod$TestCurves
curves


5.5 Random Survival Forest - Kaplan-Meier Extension

Random Survival Forests (RSFs) were developed by Ishwaran et al. in 2008 and have been growing in popularity in recent years. While we do not give vast detail regarding the formation of these forests, the original paper and R package details provide an excellent overview.

The primary focus for RSFs were to build a risk score using an averaged cumulative hazard from the terminal nodes which in which patients landed. As previously mentioned here and explained further in our paper, risk scores are useful for discriminating between patients, but do not provide individual survival distributions.While survival distributions are not mentioned in the original RSF paper, the R package details briefly describe how RSFs build individual survival distributions in Section 8.1. Specifically, they state survival estimates are computed using Kaplan-Meier estimators. However, given a total of \(M\) trees in a forest, there will be \(M\) Kaplan-Meier estimators for each patient and the details of how these Kaplan-Meier estimators were combined are not given. After some backwards engineering I was able to come up with the conclusion that Kaplan-Meier curves are “averaged” across all the Kaplan-Meier curves - see Figure below.

Figure: Depiction of averaging two Kaplan-Meier curves together to produce one individual survival distribution for a test patient. Blue Kaplan-Meier curves represent the Kaplan-Meier estimators in two terminal nodes whereas the red curve represents the combination - giving form to the individual survival distribution.

5.5.1 Random Survival Forest - Kaplan-Meier Extension Code (RandomSurvivalForests.R)

File Purpose: Apply the RSF model to data that has been processed and split into folds by createFoldsAndNormalize().

File Path: “ISDEvaluation/Models/RandomSurvivalForests.R”.

Functions:

  1. RSF(training, testing)
  2. internalCV_RSF(training, numFolds) – Helper function for RSF

Arguments:

  • training: The training set.
  • testing: The testing set.
  • numFolds: The number of folds for internal cross validation.

Packages Required:

  1. survival – Needed for the Surv() function
  2. randomForestSRC – Used for the RSF implementation.

File Notes:

Given the list of datasets generated by createFoldsAndNormalize(), RSF() will take a single training set, testing set, and the number of folds to use for internal cross validation to select hyperparameters (namely ntree and mtry) and build the RSF model using rfsrc() from the randomForestSRC package. Internal cross validation is performed for ntree \(\in \{500,1000, 2000\}\) and mtry (number of patients in each terminal node) ${1,2,3} $ Then the predict() function for rfsrc is used to build survival curves for test patients.

The output is the same as previous models.

Example Usage:

validatedAndCleaned = validateAndClean(survivalDataset = NACD)
folds = createFoldsAndNormalize(survivalDataset = validatedAndCleaned, numberOfFolds = 5)
trainAndTest = folds[[2]]
training1stFold = trainAndTest$Training[[1]]
testing1stFold = trainAndTest$Testing[[1]]
rsfMod = RSF(training1stFold, testing1stFold)
curves = rsfMod$TestCurves
names(curves) = c("time",1:26)
curves

5.6 Multi-task Logistic Regression (MTLR.R)

Of the models tested, Multi-task Logistic Regression (MTLR) is the least well known so we give a brief description here. Consider modeling the probability of survival of patients at each of a vector of time points \(\tau = [t_1, t_2, \ldots, t_m]\), e.g. \(\tau\) could be the 60 monthly intervals from 1 month up to 60 months. We can set up a series of logistic regression models: For each patient, represented as \(\vec{x}\), \[ P(T \geq t_i \,|\, \vec{x}) = \left(1 + \exp(\vec{\theta_{i}}\cdot \vec{x} )\right)^{-1} \] where \(\vec{\theta_i}\) are the time-specific feature vectors. While the input features \(\vec{x}\) stay the same for all these classification tasks,
the binary labels \(y_i = [T\geq t_i]\) can change depending on the threshold \(t_i\).%

We encode the survival time \(d\) of a patient as a binary sequence \(y = y(d) = (y_1, y_2, \ldots, y_m)\), where \(y_i = y_i(d) \in \{0,1\}\) denotes the survival status of the patient at time \(t_i\), so that \(y_i = 0\) (no death event yet) for all \(i\) with \(t_i <d\), and \(y_i = 1\) (death) for all \(i\) with \(t_i \geq d\).

Here there are \(m+1\) possible legal sequences of the form \((0,0,\ldots,1,1,\ldots,1)\), including the sequence of all 0’s and the sequence of all 1’s. The probability of observing the survival status sequence \(y = (y_1, y_2, \ldots, y_m)\) can be represented as: \[ P_\Theta(Y\!\! =\!\! (y_1, y_2, \ldots, y_m) \,\,|\,\, \vec{x}) = \frac{\exp(\sum_{i=1}^m y_i \times \theta_{i}\cdot\vec{x} )} {\sum_{k=0}^m \exp(f_{\Theta}(\vec{x}, k))}, \] where \(\Theta = (\theta_{1}, \ldots, \theta_{m})\), and \(f_{\Theta}(\vec{x}, k) = \sum_{i=k+1}^m (\theta_{i}\cdot\vec{x})\) for \(0\leq k\leq m\) is the score of the sequence with the event occurring in the interval \([t_k, t_{k+1})\) before taking the logistic transform, with the boundary case \(f_{\Theta}(\vec{x}, k)= 0\) being the score for the sequence of all `0’s. Given a dataset of \(n\) patients \(\{ \vec{x_r}\}\) with associated time of deaths \(\{ d_r \}\), we find the optimal parameters (for the MTLR model) \(\Theta^*\) as \[ \Theta^*\ =\ \arg\max_{\Theta} \sum_{r=1}^n \left[\sum_{i=1}^m y_j(d_r)(\theta{i}\!\cdot\!\vec{x_r})\! - \log \sum_{k=0}^m \exp f_{\Theta}(\vec{x_r},k) \right] - \frac{C}{2}\!\sum_{j=1}^m\! \|\theta_{j}\|^2\! % +\! \frac{C_2}{2}\sum_{j=1}^{m-1}\! \|\theta_{j+1}\!-\!\theta_j\|^2\! \] where the \(C\) (for the regularizer) is found by an internal cross-validation process. }

There are many details here – to insure that the survival function starts at 1.0, and decreases monotonically and smoothly until reaching 0.0 for the final time point; to deal appropriately with censored patients; to decide how many time points to consider (\(m\)); and to minimize the risk of overfitting (by regularizing), and by selecting the relevant features. The paper by Yu et al. provides the details.

5.6.1 Multi-task Logistic Regression Code (MTLR.R)

File Purpose: Apply the MTLR model to data that has been processed and split into folds by createFoldsAndNormalize().

File Paths:

  1. “ISDEvaluation/Models/MTLR.R”.

Functions:

  1. MTLR(training, testing,C1 = NULL, numFolds = 5)
  2. internalCV_MTLR(training, numFolds) – Helper function for MTLR().
  3. avgLogLikLoss(params, dat, timePoints) – Helper function for internalCV_MTLR().

Arguments:

  • training: The training set.
  • testing: The testing set.
  • C1: The regularization parameter.
  • numFolds: The number of folds for internal cross validation.
  • params: The feature weights.
  • dat: The testing dataset (from internal CV).
  • timePoints: The time points used for evaluating MTLR().

Packages Required:

  1. Rcpp – Used for fast implementation of gradient and objective function calculation.

File Notes:

Given the list of datasets generated by createFoldsAndNormalize(), MTLR() will take a single training set, testing set, optionally a value for the regularization parameter, and the number of folds for internal cross-validation if C1 is left as NULL. Internal cross-validation is performed to select an the optimal regularization parameter (the average log-likelihood loss is optimized). Features are optimized via gradient descent and the optim() function. Since the objective function is non-convex with censored data, the weights are trained once treating all patients as uncensored and then trained again with the true censor values (this is in hopes of finding good starting weights).

The output is the same as previous models.

Example Usage:

validatedAndCleaned = validateAndClean(survivalDataset = NACD)
folds = createFoldsAndNormalize(survivalDataset = validatedAndCleaned, numberOfFolds = 5)
trainAndTest = folds[[2]]
training1stFold = trainAndTest$Training[[1]]
testing1stFold = trainAndTest$Testing[[1]]
mtlrMod = MTLR(training1stFold, testing1stFold)
curves = mtlrMod$TestCurves
names(curves) = c("time",1:26)
curves

Figure: MTLR Survival curves without last time point added.

6 Evaluting ISD Models

Once the survival curves have been generated it is likely beneficial to evaluate them using some type of metric. We tested (5) different metrics, Concordance (a discriminatory measure), 1-Calibration, Brier score, L1-Loss and novel D-Calibration. Below we only give the details for the code – for explanations of each metric please see the paper associated with this tutorial (Link). In addition to the evaluation metrics we also describe the function used to calculate the mean/median survival from each survival curve. We will show all evaluations using the unregularized Cox model as this is one of the most common models used in survival analysis.

6.1 Calculating ``Average’’ Survival (EvaluationHelperFunctions.R)

File Purpose: Calculate the mean/median survival time given a single survival curve.

File Paths:

  1. “ISDEvaluation/Evaluations/EvaluationHelperFunctions.R”

Functions:

  1. predictProbabilityFromCurve(survivalCurve,predictedTimes, timeToPredict)
  2. predictMeanSurvivalTimeSpline(survivalCurve, predictedTimes)
  3. predictMedianSurvivalTimeSpline(survivalCurve, predictedTimes)

Arguments:

  1. survivalCurve: The survival probabilities generated for a individual survival curve.
  2. predictedTimes: The times corresponding to survival probabilities, e.g. time = 0 corresponds to a survival probability of 1.
  3. timeToPredict: The time for which to extract a survival probability given a survival curve. Note this can either be a single time or a vector of times.

Packages Required: None.

File Notes:

This files purpose is to provide support for the evaluation metrics. Some metrics (concordance, L1-loss) require a single risk/survival measure and for those we use the mean (or median) survival times, predictMeanSurvivalTimeSpline() and predictMedianSurvivalTimeSpline() accordingly. To predict the mean time a monotonic spline fit is used to make the survival probabilities continuous. Specifically, we use the Hyman filtering of the cubic Hermite spline (see the Wikipedia page and the page’s references for an introduction). However, if the survival curves do not go to 0 probability we use a linear fit from the time = 0, survival probability = 1 point to the last time point we have and extend this linear fit to zero, see the Figure below. Once the survival curves have been extended to zero the integral of the survival curve is taken to be the mean survival time.

Figure: Linear fitting to extend survival curves to 0 survival probability. Left: Survival probabilities with no linear fit, Right: Survival probabilityies with linear fitting.

Figure: Linear fitting to extend survival curves to 0 survival probability. Left: Survival probabilities with no linear fit, Right: Survival probabilityies with linear fitting.

For calculating the median survival time the same spline fit is used and then an inverse spline is fit to the original spline fitting. The time associated with a survival probability of 0.5 is used as the median survival time.

Additionally, some evaluation metrics (specifically calibration metrics) require survival probabilities for a given time. This is implemented by predictProbabilityFromCurve(). The same spline fit (with linear extension to zero) from before is fitted to the survival probabilities and the given time(s) to predict (timeToPredict) is calculated using the internal spline() function.

Example Usage:

#Set up:
validatedAndCleaned = validateAndClean(survivalDataset = NACD)
folds = createFoldsAndNormalize(survivalDataset = validatedAndCleaned, numberOfFolds = 5)
trainAndTest = folds[[2]]
training1stFold = trainAndTest$Training[[1]]
testing1stFold = trainAndTest$Testing[[1]]
CoxMod = CoxPH_KP(training1stFold, testing1stFold, ElasticNet=F)
curves = CoxMod[[1]]
survivalTimes = curves[,1]
#We use the second patient because the first had a very shallow survival curve - see Cox curves in Seciton 3.3.1.
secondSurvivalCurve = curves[,3]
#Getting the survival probability for 5 months:
predictProbabilityFromCurve(survivalCurve = secondSurvivalCurve, predictedTimes = survivalTimes, timeToPredict = 5)
[1] 0.9571609
#Getting multiple survival probabilities:
predictProbabilityFromCurve(survivalCurve = secondSurvivalCurve, predictedTimes = survivalTimes, timeToPredict = c(5,10,20,40,80,160))
[1] 0.9571609 0.8646244 0.3927712 0.2527547 0.0000000 0.0000000
#Getting the mean survival time:
predictMeanSurvivalTimeSpline(survivalCurve = secondSurvivalCurve, predictedTimes = survivalTimes)
[1] 25.72988
#Getting the median survival time:
predictMedianSurvivalTimeSpline(survivalCurve = secondSurvivalCurve, predictedTimes = survivalTimes)
[1] 17.34427

6.2 Concordance (Concordance.R)

File Purpose: Calculate the concordance of a trained model on test data.

File Path: “ISDEvaluation/Evaluations/Concordance.R”

Functions:

  1. Concordance(survMod, ties = "None", method = "Median")

Arguments:

  1. survMod: The output of any of the survival models. Specifically this must be a list of (1) the survival curves of the test patients, (2) the test patients true time of death and censor status, and (3) the training patients true time of death and censor status. Technically the third component is not required for the concordance calculation.

  2. ties: A string indicating the method in which to handle ties. There are four options: (1) “None” which will exclude all ties, (2) “Risk” which will include ties who were tied in their risk (mean/median survival time), (3) “Time” which will include ties whos survival time was equal, and (4) “All” which includes both risk and time ties.

  3. method: A string indicating to use the “Mean” or the “Median” for the risk score evaluation.

Packages Required:

  1. survival - This package is used for the survConcordance() function – an time-efficient function for calculating concordance.

File Notes:

As discussed in the paper, concordance is a discriminative measure typically used to evaluated models which produce risk scores for patients. Here the risk score is given by the negative of the mean/median of an individuals survival distribution. Once the average survival times are calculated survConcordance() calculated the concordance information and the concordance score is returned depending on the values of the ties argument.

Example Usage:

#Set up:
validatedAndCleaned = validateAndClean(survivalDataset = NACD)
folds = createFoldsAndNormalize(survivalDataset = validatedAndCleaned, numberOfFolds = 5)
trainAndTest = folds[[2]]
training1stFold = trainAndTest$Training[[1]]
testing1stFold = trainAndTest$Testing[[1]]
CoxMod = CoxPH_KP(training1stFold, testing1stFold, ElasticNet=F)
#Evaluation:
Concordance(CoxMod, ties = "None", method = "Mean")
concordant 
 0.6317829 

6.3 1-Calibration (OneCalibration.R) {#1Cal}

File Purpose: Perform the 1-Calibration test on survival data.

File Path: “ISDEvaluation/Evaluations/OneCalibration.R”

Functions:

  1. OneCalibration(survMod, timeOfInterest = c(), type = "DN", numBuckets = 10)
  2. OneCalibrationCumulative(listOfSurvivalModels, timeOfInterest = c(), type = "DN", numBuckets = 10)
  3. binItUp(trueDeathTimes,censorStatus, predictions, type, numBuckets,timeOfInterest)

Arguments:

  1. survMod: The output of any of the survival models. Specifically this must be a list of (1) the survival curves of the test patients, (2) the test patients true time of death and censor status, and (3) the training patients true time of death and censor status. Technically the third component is not required for the concordance calculation.

  2. timeOfInterest: The time at which to evaluate the model for 1-Calibration. If nothing is passed in the quantiles (.1, .25,.5,.75,.9) of the true event times will be used.

  3. type: This is the type of 1-Calibration to be used, either “Uncensored” to use 1-Calibration for only the uncensored patients and discard all censored patients or “DN” for the D’Agostino-Nam translation.

  4. numBuckets: The number of buckets/bins to use for 1-Calibration.

  5. listOfSurvivalModels: A list of the output of survival models, e.g. a list of different survMod like objects.

  6. trueDeathTimes: The true event times of the subjects.

  7. censorStatus: The bit identifying if a patient died or was censored, i.e delta.

  8. predictions: The predicted survival probability for patients at their true event times.

Packages Required:

  1. survival – This package is used for the Surv() function.

  2. dplyr – This is used for group_by(), summarise(), and mutate()`.

File Notes:

There are two ways to perform 1-Calibration here – one can either pass the output from a survival model and have 1-Calibration evaluated on the test fold or one can pass a list of multiple survival models and have 1-Calibration evaluated on all patients. Note by doing the first way, OneCalibration(), there will end up being the same number of p-values as there are cross-validation folds and there will be no straightforward way of combining them (DO NOT AVERAGE THEM). OneCalibrationCumulative() mitigates this by taking in the model results from all folds and performing a single 1-Calibration test using all the test data. binItUp() is a helper function that takes probability predictions and performs the actual specified 1-Calibration test.

Example Usage:

#Set up for OneCalibration():
validatedAndCleaned = validateAndClean(survivalDataset = NACD)
folds = createFoldsAndNormalize(survivalDataset = validatedAndCleaned, numberOfFolds = 5)
trainAndTest = folds[[2]]
training1stFold = trainAndTest$Training[[1]]
testing1stFold = trainAndTest$Testing[[1]]
CoxMod = CoxPH_KP(training1stFold, testing1stFold, ElasticNet=F)
#Evaluation:
#By not specifying timeOfInterest we will get the 10th, 25th, 50th, 75th, and 90th quantiles of the true event times in the testing fold.
OneCalibration(CoxMod, timeOfInterest = c(), type = "DN",numBuckets = 10)
[1] 0.1524982 0.0000000 0.0000000 0.0000000 0.0000000
#One Calibration for uncensored patients.
OneCalibration(CoxMod, timeOfInterest = c(), type = "Uncensored",numBuckets = 10)
[1] 0.3689704 0.0000000 0.0000000 0.0000000 0.0000000
#Set up for OneCalibrationCumulative():
coxList = list()
for(i in 1:5){
  trainingFold = trainAndTest$Training[[i]]
  testingFold = trainAndTest$Testing[[i]]
  CoxMod = CoxPH_KP(trainingFold, testingFold, ElasticNet=F)
  coxList[[i]] = CoxMod
}
#Cumulative 1-Calibration for default time of Interest
OneCalibrationCumulative(listOfSurvivalModels = coxList, timeOfInterest = c(), type = "DN", numBuckets = 10)
[1] 0.118622 0.000000 0.000000 0.000000 0.000000
#Cumulative 1-Calibration for a specific time (2 months)
OneCalibrationCumulative(listOfSurvivalModels = coxList, timeOfInterest = 2, type = "DN", numBuckets = 10)
[1] 0.002524264

6.4 Brier Score (BrierScore.R)

File Purpose: Evaluate the Brier score on test data.

File Path: “ISDEvaluation/Evaluations/BrierScore.R”

Functions:

  1. BrierScore(survMod. type = "Interated", singleTime = NULL, integratedBrierTimes = NULL, numPoints = NULL)
  2. singleBrier(survMod, BrierTime = NULL)
  3. integratedBrier(survMod, integratedBrierTimes, numPoints)
  4. sinleBrierMultiplePoints(survMod, BrierTimes)

Arguments:

  1. survMod: The output of any of the survival models. Specifically this must be a list of (1) the survival curves of the test patients, (2) the test patients true time of death and censor status, and (3) the training patients true time of death and censor status. Technically the third component is not required for the concordance calculation.

  2. type: A string of either “Integrated” or “Single”. This will indicate whether to return the Brier score for a point or the integrated Brier score.

  3. singleBrierTime: The time point to evaluate the (non-integrated) Brier score. If left as NULL, the 50th percentile of the training and testing data’s event times combined will be used.

  4. integratedBrierTimes: A vector of length 2 indicating the limits of integration for the integrated Brier score. If left as NULL the range will be from zero to the max event time of the entire dataset (training and testing set combined).

  5. numPoints: The number of points to evaluate the integral, i.e. the higher the number of points the less error in the integration. If left as NULL the number of points will correspond to the number of events between the limits of integration.

  6. BrierTimes: A vector of times to evaluate a single Brier score – used in an internal function (singleBrierMultiplePoints()) of integratedBrier().

Packages Required:

  1. prodlim – Used for the prodlim() function that calculates the Kaplan-Meier curve. This is then used for the inverse probability of censoring weights (IPCW).

File Notes:

In this file BrierScore() is the only function that needs to be called – all the other functions simply work within BrierScore(). Note that there is no option for removing censored patients and the Brier score returned will always be the score corrected by the IPCWs. For the integrated Brier score the integral is evaluated using the simple trapezoidal rule. If numPoints is specified then the points where the Brier score is evaluated will be evenly spread across the limits of integration. However, if numPoints is not specified then the points evaluated will be the event times in the testing data. These rules follow from the sbrier() function in the ipred library written by Torsten Hothorn.

Example Usage:

#Set up:
validatedAndCleaned = validateAndClean(survivalDataset = NACD)
folds = createFoldsAndNormalize(survivalDataset = validatedAndCleaned, numberOfFolds = 5)
trainAndTest = folds[[2]]
training1stFold = trainAndTest$Training[[1]]
testing1stFold = trainAndTest$Testing[[1]]
CoxMod = CoxPH_KP(training1stFold, testing1stFold, ElasticNet=F)
#Evaluation:
#Integrated with specified times:
BrierScore(CoxMod, type = "Integrated", integratedBrierTimes = c(0,36))
[1] 0.1460349
#Integrated with unspecified times:
BrierScore(CoxMod, type = "Integrated")
[1] 0.1223641
#Single with specified times:
BrierScore(CoxMod, type = "Single", singleBrierTime = 12)
[1] 0.1820266
#Single with unspecified times:
BrierScore(CoxMod, type = "Single")
[1] 0.1662345

6.5 L1-Loss (L1Measures.R)

File Purpose: Evaluate the L1 loss on test data.

File Path: “ISDEvaluation/Evaluations/L1Measures.R”

Functions:

  1. L1(survMod. type = "Uncensored", logScale = FALSE, method = "Median")

Arguments:

  1. survMod: The output of any of the survival models. Specifically this must be a list of (1) the survival curves of the test patients, (2) the test patients true time of death and censor status, and (3) the training patients true time of death and censor status. Technically the third component is not required for the concordance calculation.

  2. type: A string indicating the type of L1-loss, options are one of “Uncensored”, “Hinge”, or “Margin”. See the paper for details regarding the mathematical definition of each option.

  3. logScale: A Boolean indicating whether to use the log scaled version of the L1-loss.

  4. method: A string indicating whether to use the “Mean” or “Median” as survival predictions.

Packages Required:

  1. prodlim – Used for the prodlim() function that calculates the Kaplan-Meier curve. See File Notes for more details.

  2. survival – Used for the Surv() function.

File Notes:

L1() returns the average L1 loss based on the specifications given, e.g the Margin L1-loss using the mean survival to compute the loss. There is one important specification to note – the Kaplan-Meier curve is generated (from the training data) and is completed using the linear extension as specified in Section 4.1 above. The mean of this Kaplan-Meier curve is computed and the survival estimate returned is the minimum of the mean survival time for individual survival curves and the mean of this Kaplan-Meier curve. See “L1Measures.R”" for the implementation details.

Example Usage:

#Set up:
validatedAndCleaned = validateAndClean(survivalDataset = NACD)
folds = createFoldsAndNormalize(survivalDataset = validatedAndCleaned, numberOfFolds = 5)
trainAndTest = folds[[2]]
training1stFold = trainAndTest$Training[[1]]
testing1stFold = trainAndTest$Testing[[1]]
CoxMod = CoxPH_KP(training1stFold, testing1stFold, ElasticNet=F)
#Evaluation:
#Uncensored Loss with Mean:
L1(CoxMod, type = "Uncensored", logScale = FALSE, method = "Mean")
[1] 8.222021
#Hinge Loss with Mean:
L1(CoxMod, type = "Hinge", logScale = FALSE, method = "Mean")
[1] 6.324632
#Margin Loss with Mean:
L1(CoxMod, type = "Margin", logScale = FALSE, method = "Mean")
[1] 46.98087
#Margin Loss with Median:
L1(CoxMod, type = "Margin", logScale = FALSE, method = "Median")
[1] 47.33277
#Margin Loss with Median on Log Scale:
L1(CoxMod, type = "Margin", logScale = TRUE, method = "Median")
[1] 2.612281

6.6 D-Calibration (DCalibration.R)

File Purpose: Evaluate test data using D-Calibration.

File Path: “ISDEvaluation/Evaluations/DCalibration.R”

Functions:

  1. DCalibration(survMod, numBins)

  2. DCalibrationCumulative(listOfSurivalModels, numBins)

  3. getBinned(survMod, numBins)

Arguments:

  1. survMod: The output of any of the survival models. Specifically this must be a list of (1) the survival curves of the test patients, (2) the test patients true time of death and censor status, and (3) the training patients true time of death and censor status. Technically the third component is not required for the concordance calculation.

  2. numBins: The number of bins to use for D-calibration.

  3. listOfSurvivalModels: A list of the output of survival models, e.g. a list of different survMod like objects.

Packages Required:

  1. prodlim – Used for the sindex() function.

  2. plyr – Used for the ldply() function to combine lists.

File Notes:

The format of the DCalibration.R file follows the same as OneCalibration.R. That is, DCalibration() is used for a single fold and returns a p-value. However, since p-values should not be averaged there is also a DCalibrationCumulative() function which will take all the folds test data and perform a single D-Calibration test. Lastly, getBinned() is a helper function for DCalibration() and DCalibrationCumulative() and does not need to be called directly.

Example Usage:

Here we do two models, Cox and MTLR. Below you will see that the Cox model fails whereas MTLR performs well. Additionally we show the corresponding cumulative D-Calibration histograms for each model.

#Set up:
validatedAndCleaned = validateAndClean(survivalDataset = NACD)
folds = createFoldsAndNormalize(survivalDataset = validatedAndCleaned, numberOfFolds = 5)
trainAndTest = folds[[2]]
training1stFold = trainAndTest$Training[[1]]
testing1stFold = trainAndTest$Testing[[1]]
CoxMod = CoxPH_KP(training1stFold, testing1stFold, ElasticNet=F)
MTLRMod = MTLR(training1stFold, testing1stFold)
#D-Calibration for Cox
DCalibration(CoxMod, numBins = 10)
[1] 0.0005961342
#D-Calibration for MTLR
DCalibration(MTLRMod, numBins = 10)
[1] 0.2756404
#Set up for DCalibrationCumulative():
coxList = list()
mtlrList = list()
for(i in 1:5){
  trainingFold = trainAndTest$Training[[i]]
  testingFold = trainAndTest$Testing[[i]]
  CoxMod = CoxPH_KP(trainingFold, testingFold, ElasticNet=F)
  coxList[[i]] = CoxMod
  
  MTLRMod = MTLR(trainingFold, testingFold)
  mtlrList[[i]] = MTLRMod
}
#Cumulative D-Calibration for cox
DCalibrationCumulative(listOfSurvivalModels = coxList, numBins = 10)
[1] 2.671878e-10

Figure: Histogram for D-Calibration corresponding to Cox model. Bin 1 refers to the [0,.1) bin and Bin 10 refers to the [0.9, 1] bin. Here we see the cox model puts too many of the patients in the first and last bin – leading to a failed D-Calibration test.
DCalibrationCumulative(listOfSurvivalModels = mtlrList, numBins = 10)
[1] 0.1750337

Figure: Histogram for D-Calibration corresponding to MTLR model. Bin 1 refers to the [0,.1) bin and Bin 10 refers to the [0.9, 1] bin. The bins are much more even here than those given by the Cox model, resulting in a passed D-Calibration test.

7 Miscellanious

7.1 Plotting Individual Survival Curves (plotSurvivalCurves.R)

File Purpose: Plot individual survival curves.

File Path: “ISDEvaluation/Plotting/plotSurvivalCurves.R”

Functions:

  1. plotSurvivalCurves(survivalCurves, indexToPlot, color = c(), xlim=c())

Arguments:

  1. survivalCurves: A dataframe where the first column corresponds to survival times and all other columns are survival probabilities of individual patients. This corresponds to the first item of the list returned by all models, e.g. CoxPH_KP().

  2. indexToPlot: The patient number you wish to plot. From survivalCurves patient 1 would be the second column. This can either be an integer specifying which patient or a vector of integers, specifying multiple patients.

  3. color: The color of the survival curves. The length much match that of indexToPlot.

  4. xlim: You can specify the range of the x-axis. This should be a vector of length two.

Packages Required:

  1. ggplot2 – Used for plotting.

  2. reshape2 – Used to reshape the data to long form.

File Notes:

The plotting code will select the columns specified by indexToPlot and fit a spline using the time column and the survival probabilities columns. This spline is then used to interpolate many points between the probabilities such that we have a smooth fit to plot. See below for a few plotting examples. Note that indexToPlot will match up testing data depending on the fold. Additionally, if over 15 indexes are specified, there will be no legend (the legend fills up the plot space).

Example Usage:

#Without feature selection.
validatedAndCleaned = validateAndClean(survivalDataset = NACD)
folds = createFoldsAndNormalize(survivalDataset = validatedAndCleaned, numberOfFolds = 5)
trainAndTest = folds[[2]]
training1stFold = trainAndTest$Training[[1]]
testing1stFold = trainAndTest$Testing[[1]]
CoxMod = CoxPH_KP(training1stFold, testing1stFold, ElasticNet=F)
survivalCurves = CoxMod[[1]]
plotSurvivalCurves(survivalCurves, indexToPlot = 1)

plotSurvivalCurves(survivalCurves, indexToPlot = 2)

plotSurvivalCurves(survivalCurves, indexToPlot = 2, color = "blue")

plotSurvivalCurves(survivalCurves, indexToPlot = 1:10)

plotSurvivalCurves(survivalCurves, indexToPlot = 1:10, xlim = c(0,25))

Note here that the index corresponds to the row number in the original dataset (they are all out of order here since we used an arbitrary cross-validation fold).

7.2 Feature Selection (FeatureSelection.R)

File Purpose: Perform feature selection on survival data.

File Path: “ISDEvaluation/FeatureSelection/FeatureSelection.R”

Functions:

  1. FeatureSelection(dataset, type = "UniCox", obs_thresh = 0, pThresh = 0.1)

  2. uniCox(dataset, obsthresh = 0, pThresh=0.1)

Arguments:

  1. dataset: This is a dataset which has been validated for use – i.e. gone through validateAndClean()

  2. type: The type of feature selection to use. Currently (August 28, 2018) the only option is “UniCox”. See File Notes for details.

  3. obs_thresh: The number of observations that a variable must have to qualify for selection.

  4. pThresh: The p-value threshold to be included in the analysis.

Packages Required:

  1. caret – the dummyVars() function is used for one-hot encoding.

File Notes:

The code here is written so custom feature selection methods can be easily added – they just have to added to a switch command. Here we use uni-variate cox for our feature selection – the details of this method are given in the paper. For this feature selection we first check that that each variable has a obs_thresh number of events (deaths) occur. The default for this is 0 events. Next the p-value for the feature must be below 0.1 to be included in the selected dataset. Note that variables are one-hot encoded before selection so all the values of a categorical variable may not be present post feature selection.

Example Usage:

Below you will see that unselected data has 40 features whereas the data post feature selection has only 22 features.

#Without feature selection.
validatedAndCleaned = validateAndClean(survivalDataset = NACD)
dim(validatedAndCleaned)
[1] 128  40
#After we have the selected data we would then pass it to createFoldsAndNormalize().
selectedData = FeatureSelection(validatedAndCleaned)
dim(selectedData)
[1] 128  22
LS0tCnRpdGxlOiAiSW5kaXZpZHVhbCBTdXJ2aXZhbCBEaXN0cmlidXRpb25zIC0gQ2FzZSBTdHVkeSBhbmQgQ29kZSBUdXRvcmlhbCIKYXV0aG9yOiAiSHVtemEgSGFpZGVyIgpkYXRlOiAiYHIgZm9ybWF0KFN5cy50aW1lKCksICclQiAlZSwgJVknKWAiCm91dHB1dDoKICBodG1sX25vdGVib29rOiAKICAgIHRvYzogVFJVRQogICAgbnVtYmVyX3NlY3Rpb25zOiB0cnVlCiAgICBmaWdfY2FwdGlvbjogeWVzCiAgICBmaWdfYWxpZ246ICJjZW50ZXIiCi0tLQoKI0ludHJvZHVjdGlvbgpUaGlzIGFydGljbGUgc2hvdWxkIHNlcnZlIGFzIGEgdHV0b3JpYWwgb24gdXNpbmcgdGhlIFtwdWJsaWNseSBhdmFpbGFibGUgY29kZV0oaHR0cHM6Ly9naXRodWIuY29tL2hhaWRlcnN0YXRzL0lTREV2YWx1YXRpb24pIHdoaWNoIGFjY29tcGFuaWVzIHRoZSBwYXBlciAiRWZmZWN0aXZlIFdheXMgdG8gQnVpbGQgYW5kIEV2YWx1YXRlIEluZGl2aWR1YWwgU3Vydml2YWwgRGlzdHJpYnV0aW9ucyIuIFRoZXJlIGFyZSB0d28gcHJpbWFyeSBjb21wb25lbnRzIHRvIHRoaXMgYXJ0aWNsZSwgKDEpIGEgW3F1aWNrc3RhcnQgb3B0aW9uXSgjcXVpY2tzdGFydCkgd2hpY2ggZ2l2ZXMgYW4gb3ZlcnZpZXcgb2YgdXNpbmcgdGhlIG1hc3RlciBmaWxlLCAiYW5hbHlzaXNNYXN0ZXIuUiIsIGFuZCAoMikgW3RoZSBlbnRpcmUgY29kZSBvdmVydmlld10oI3RoZURldGFpbHMpIHdoaWNoIGRldGFpbHMgYWxsIHRoZSBmdW5jdGlvbnMgYW5kIGZpbGVzIGZvdW5kIGluIHRoZSBjb2RlLiBXaGlsZSB1c2luZyB0aGUgImFuYWx5c2lzTWFzdGVyLlIiIGZpbGUgZ2l2ZXMgdXNlcnMgYWNjZXNzIHRvIG1vc3Qgb2YgdGhlIGZ1bmN0aW9uYWxpdHkgb2YgdGhlIGNvZGViYXNlLCBubyBzcGVjaWFsaXplZCBtb2RpZmljYXRpb25zIGNhbiBiZSBtYWRlIHRvIHRoZSBtb2RlbHMvZXZhbHVhdGlvbnMgZGlyZWN0bHkgc28gd2UgZ2l2ZSBhIGRldGFpbGVkIGV4cGxhbmF0aW9uIG9mIGhvdyB0byBzdGFydCB3aXRoIGEgZGF0YXNldCBhbmQgZW5kIHdpdGggZXZhbHVhdGlvbnMgb2YgZGlmZmVyZW50IG1vZGVscy4KCiNRdWlja3N0YXJ0IEd1aWRlIChhbmFseXNpc01hc3Rlci5SKSB7I3F1aWNrc3RhcnR9CgpCZWZvcmUgeW91IGJlZ2luIHVzaW5nIHRoZSBjb2RlLCBtYWtlIHN1cmUgYWxsIHJlcXVpcmVkIHBhY2thZ2VzIGFyZSBkb3dubG9hZGVkOiAgYGNhcmV0YCwgYGRhdGFQcmVwYXJhdGlvbmAsIGBnZ3Bsb3QyYCwgYHJlc2hhcGUyYCxgcmFuZG9tRm9yZXN0U1JDYCwgYFJjcHBgLCBgcHJvZGxpbWAsIGBzdXJ2aXZhbGAsIGBmYXN0Y294YCwgYHBseXJgLGFuZCBgZHBseXJgLiBUbyBzYXZlIHlvdSBzb21lIHRpbWUgSSBoYXZlIHN1cHBsaWVkIGEgY29kZSBzbmlwcGV0IHlvdSBjYW4gdXNlIHRvIGRvd25sb2FkIGFueSBvZiB0aGUgcGFja2FnZXMgeW91IG1heSBiZSBtaXNzaW5nOgoKYGBge3IscmVzdWx0cz0naGlkZSd9CiNUYWtlbiBmcm9tIGh0dHBzOi8vZ2lzdC5naXRodWIuY29tL3N0ZXZlbndvcnRoaW5ndG9uLzMxNzgxNjMKaXBhayA8LSBmdW5jdGlvbihwa2cpewogICAgbmV3LnBrZyA8LSBwa2dbIShwa2cgJWluJSBpbnN0YWxsZWQucGFja2FnZXMoKVssICJQYWNrYWdlIl0pXQogICAgaWYgKGxlbmd0aChuZXcucGtnKSkgCiAgICAgICAgaW5zdGFsbC5wYWNrYWdlcyhuZXcucGtnLCBkZXBlbmRlbmNpZXMgPSBUUlVFKQogICAgc2FwcGx5KHBrZywgcmVxdWlyZSwgY2hhcmFjdGVyLm9ubHkgPSBUUlVFKQp9CgpwYWNrYWdlcyA8LSBjKCJjYXJldCIsICJkYXRhUHJlcGFyYXRpb24iLCAiZ2dwbG90MiIsICJyZXNoYXBlMiIsInJhbmRvbUZvcmVzdFNSQyIsICJSY3BwIiwgInByb2RsaW0iLCAic3Vydml2YWwiLCAiZmFzdGNveCIsICJwbHlyIiwgImRwbHlyIikKaXBhayhwYWNrYWdlcykKYGBgCgoKTmV4dCB3ZSB3aWxsIGxvYWQgdGhlIGV4YW1wbGUgZGF0YSAod2hpY2ggY2FuIGJlIGZvdW5kIG9uIHRoZSBbUFNTUCB3ZWJzaXRlXShwc3NwLnNydi51YWxiZXJ0YS5jYS9zeXN0ZW0vcHJlZGljdG9ycy9kYXRhc2V0cy8wMDAvMDAwLzA3Ni9vcmlnaW5hbC9BbGxfRGF0YV91cGRhdGVkX21heTIwMTFfU3RhZ2U0X1N0b21hY2guY3N2PzEzNTQ3MDg1MDQpKS4gRGV0YWlscyBhYm91dCB0aGlzIGRhdGFzZXQgY2FuIGJlIGZvdW5kIGluIHRoZSBbSW50cm9kdWN0aW9uIHRvIHRoZSBFeGFtcGxlIERhdGFdKCN0aGVEZXRhaWxzKS4KCmBgYHtyfQpleGFtcGxlRGF0ID0gcmVhZC5jc3YoZmlsZSA9ICJodHRwOi8vcHNzcC5zcnYudWFsYmVydGEuY2Evc3lzdGVtL3ByZWRpY3RvcnMvZGF0YXNldHMvMDAwLzAwMC8wNzYvb3JpZ2luYWwvQWxsX0RhdGFfdXBkYXRlZF9tYXkyMDExX1N0YWdlNF9TdG9tYWNoLmNzdj8xMzU0NzA4NTA0IikKYGBgCgpUbyB1c2UgdGhpcyBjb2RlYmFzZSwgdGhlIHRpbWUgdG8gZXZlbnQgZmVhdHVyZSBtdXN0IGJlIG5hbWVkIGB0aW1lYCwgYW5kIHRoZSBpbmRpY2F0b3Igb2YgZGVhdGggKHVuY2Vuc29yZWQpIHNob3VsZCBiZSBuYW1lZCBgZGVsdGFgLiBJbiB0aGUgZXhhbXBsZSBkYXRhIHRoZXNlIGFyZSB0aGUgZmlyc3QgdHdvIGNvbHVtbnMgYW5kIGFyZSBjdXJyZW50bHkgbmFtZWQgYFNVUlZJVkFMYCBhbmQgYENFTlNPUkVEYCwgc28gd2UgbWFrZSB0aGlzIGNoYW5nZS4gQWRkaXRpb25hbGx5LCB0aGlzIGRhdGEgaGFzIENFTlNPUkVEID0gMSBtZWFuIHRoYXQgYSBwYXRpZW50IHdhcyBjZW5zb3JlZC4gV2UgbmVlZCAxIHRvIGluZGljYXRlIGEgZGVhdGguCgpgYGB7cn0KbmFtZXMoZXhhbXBsZURhdClbYygxLDIpXSA9IGMoInRpbWUiLCAiZGVsdGEiKQpleGFtcGxlRGF0JGRlbHRhID0gMSAtIGV4YW1wbGVEYXQkZGVsdGEgCmBgYAoKTm93IHRoYXQgdGhlIGRhdGEgaXMgZm9ybWF0dGVkIGNvcnJlY3RseSBhbmQgcGFja2FnZXMgYXJlIGxvYWRlZCwgZ28gYWhlYWQgYW5kIGxvYWQgImFuYWx5c2lzTWFzdGVyLlIiCgpgYGB7cn0Kc291cmNlKCJhbmFseXNpc01hc3Rlci5SIikKYGBgCgpUaGlzIGZpbGUgY29udGFpbnMgdGhlIGZ1bmN0aW9uIGBhbmFseXNpc01hc3RlcigpYCB3aGljaCB0YWtlcyBtYW55IGFyZ3VtZW50cywgZWFjaCBvZiB3aGljaCBjb250cm9scyBzb21lIGFzcGVjdCBvZiBlaXRoZXIgdGhlIGRpZmZlcmVudCBtb2RlbHMgb3IgZXZhbHVhdGlvbnMuCgoqKkFyZ3VtZW50cyoqOgoKKiAqc3Vydml2YWxEYXRhc2V0KjogYSAobm9uLWVtcHR5KSBkYXRhZnJhbWUgcmVwcmVzZW50aW5nIHRoZSBzdXJ2aXZhbCBkYXRhc2V0LgoqICpudW1iZXJPZkZvbGRzKjogVGhlIG51bWJlciBvZiBkZXNpcmVkIGNyb3NzLXZhbGlkYXRpb24gZm9sZHMuIERlZmF1bHQgaXMgNS4KKiAqQ294S1AqLCAqQ294S1BFTiosICpLYXBsYW5NZWllciosICpSU0ZNb2RlbCosICpBRlRNb2RlbCosICpNVExSTW9kZWwqOiBBbGwgQm9vbGVhbiBzcGVjaWZ5aW5nIHdoZXRoZXIgdG8gaW5jbHVkZSBlYWNoIG1vZGVsIChDb3ggcHJvcG9ydGlvbmFsLWhhemFyZHMsIENveC0gRWxhc3RpYyBOZXQsIEthcGxhbi1NZWllciwgUmFuZG9tIFN1cnZpdmFsIEZvcmVzdHMsIEFjY2VsZXJhdGVkIEZhaWx1cmUgVGltZSwgTXVsdGktdGFzayBMb2dpc3RpYyBSZWdyZXNzaW9uKS4gQWxsIGRlZmF1bHQgdG8gVFJVRS4KKiAqRENhbCogKk9uZUNhbCosICpDb25jb3IqLCAqTDFNZWFzdXJlKiwgKkJyaWVyU2luZ2xlKiwgKkJyaWVySW50KjogQm9vbGVhbiBzcGVjaWZ5aW5nIHdoZXRoZXIgb3Igbm90IHRvIGluY2x1ZGUgZWFjaCBldmFsdWF0aW9uIG1ldHJpYy4gKEQtQ2FsaWJyYXRpb24sIDEtQ2FsaWJyYXRpb24sIENvbmNvcmRhbmNlLCBMMS1sb3NzLCBCcmllciBzY29yZSAoc2luZ2xlIHRpbWUpLCBJbnRlZ3JhdGVkIEJyaWVyIFNjb3JlKS4gQWxsIGRlZmF1bHQgdG8gVFJVRS4KKiAqRENhbEJpbnMqOiBOdW1iZXIgb2YgYmlucyBmb3IgRC1DYWxpYnJhdGlvbi4gRGVmYXVsdCBpcyAxMC4KKiAqT25lQ2FsVGltZSo6IEEgbnVtZXJpYyBzcGVjaWZ5aW5nIHRoZSB0aW1lIHRvIGV2YWx1YXRlIDEtQ2FsaWJyYXRpb24uIElmIGxlZnQgYXMgTlVMTCBidXQgT25lQ2FsID0gVFJVRSwgdGhlbiB0aGUgMTB0aCwgMjV0aCwgNTB0aCwgNzV0aCwgYW5kIDkwdGggcGVyY2VudGlsZXMgb2YgYWxsIGV2ZW50IHRpbWVzIGFyZSB1c2VkLiBEZWZhdWx0IGlzIE5VTEwuCiogKmNvbmNvcmRhbmNlVGllcyo6IEEgc3RyaW5nICgiTm9uZSIsICJUaW1lIiwgIlJpc2siLCJBbGwiKSBpbmRpY2F0aW5nIGhvdyB0byBoYW5kbGUgdGllcyBpbiBjb25jb3JkYW5jZS4gRGVmYXVsdCBpcyAiUmlzayIuCiogKlNpbmdsZUJyaWVyVGltZSo6IFRoZSB0aW1lIHRvIGV2YWx1YXRlIHRoZSBCcmllciBTY29yZS4gSWYgbGVmdCBhcyBOVUxMLCB0aGUgNTB0aCBwZXJjZW50aWxlIG9mIGFsbCBldmVudCB0aW1lcyBpcyB1c2VkLiBEZWZhdWx0IGlzIE5VTEwuCiogKkludGVncmF0ZWRCcmllclRpbWVzKjogQSAyIGxlbmd0aCB2ZWN0b3IgKGUuZy4gYygwLDEwMCkpIHNwZWNpZnlpbmcgdGhlIGxvd2VyIGFuZCB1cHBlciBib3VuZHMgb24gdGhlIGludGVncmF0ZWQgQnJpZXIgc2NvcmUuIElmIE5VTEwgdGhlbiB0aGUgZGVmYXVsdCBpcyAwIGFzIGEgbG93ZXIgYm91bmQgYW5kIHRoZSBtYXggZXZlbnQgdGltZSBvZiB0aGUgZW50aXJlIGRhdGFzZXQgaXMgdXNlZCBhcyBhbiB1cHBlciBib3VuZC4gRGVmYXVsdCBpcyBOVUxMLgoqICpudW1CcmllclBvaW50cyo6IFRoZSBudW1iZXIgb2YgcG9pbnRzIHRvIGV2YWx1YXRlIHRoZSBpbnRlZ3JhdGVkIEJyaWVyIHNjb3JlLiBBIHNpbXBsZSB0cmFwZXpvaWRhbCBudW1lcmljYWwgYXBwcm94aW1hdGlvbiBpcyB1c2VkLiBEZWZhdWx0IGlzIDEwMDAgcG9pbnRzLgoqICpMdHlwZSo6IFRoZSB0eXBlIG9mIEwxLWxvc3MuIE11c3QgYmUgb25lIG9mICJVbmNlbnNvcmVkIiwiSGluZ2UiLCBvciAiTWFyZ2luIi4gRGVmYXVsdCBpcyAiTWFyZ2luIi4KKiAqTGxvZyo6IEEgQm9vbGVhbiBzcGVjaWZ5aW5nIHdoZXRoZXIgb3Igbm90IHRvIHVzZSBsb2ctTDEgbWV0cmljLiBEZWZhdWx0IGlzIEZBTFNFLgoqICp0eXBlT25lQ2FsKjogQSBzdHJpbmcgaW5kaWNhdGluZyB0aGUgdHlwZSBvZiAxLUNhbGlicmF0aW9uIHRvIHVzZS4gTXVzdCBiZSBvbmUgb2YgIkROIiBvciAiVW5jZW5zb3JlZCIuIERlZmF1bHQgaXMgIkROIi4KKiAqb25lQ2FsQnVja2V0cyo6IEFuIGludGVnZXIgc3BlY2lmeWluZyBudW1iZXIgb2YgYmlucy9idWNrZXRzIGZvciAxLUNhbGlicmF0aW9uLiBEZWZhdWx0IGlzIDEwLgoqICpzdXJ2aXZhbFByZWRpY3Rpb25NZXRob2QqOiBUaGUgd2F5IGluIHdoaWNoIHRvIGVzdGltYXRlIGF2ZXJhZ2Ugc3Vydml2YWwgdGltZXMuIE11c3QgYmUgb25lIG9mICJNZWFuIiBvciAiTWVkaWFuIi4gRGVmYXVsdCBpcyAiTWVkaWFuIi4KKiAqQUZURGlzdHJpYnV0aW9uKjogVGhlIGRpc3RyaWJ1dGlvbiB0byB1c2UgZm9yIEFGVCwgZGVmYXVsdCBpcyAid2VpYnVsbCIuIE11c3QgYmUgb25lIG9mICJ3ZWlidWxsIiwiZXhwb25lbnRpYWwiLCJsb2dub3JtYWwiLCJnYXVzc2lhbiIsICJsb2dsb2dpc3RpYyIsImxvZ2lzdGljIi4KKiAqRlMqOiBBIEJvb2xlYW4gc3BlY2lmeWluZyB3aGV0aGVyIG9yIG5vdCB0byB1c2UgZmVhdHVyZSBzZWxlY3Rpb24uIERlZmF1bHQgaXMgVFJVRS4KKiAqaW1wdXRlWmVybyo6IEEgQm9vbGVhbiBzcGVjaWZ5aW5nIHdoZXRoZXIgMCB2YWx1ZWQgdGltZXMgc2hvdWxkIGJlIGltcHV0ZWQgKEFGVCBicmVha3MgZm9yIDAgdmFsdWVkIHRpbWVzKS4gSWYgVFJVRSB0aGVuIDAgdmFsdWVkIHRpbWVzIGFyZSBpbXB1dGVkIHRvIGhhbGYgdGhlIG1pbmltdW0gbm9uLXplcm8gdGltZS4gRGVmYXVsdCBpcyBUUlVFLiAKKiAqdmVyYm9zZSo6IEEgQm9vbGVhbiBzcGVjaWZ5aW5nIHdoZXRoZXIgb3Igbm90IHRvIHJldHVybiByZXN1bHRzIGFuZCBwcm9ncmVzcyBpbmZvcm1hdGlvbi4KCgpGb3IgdGhpcyBleGFtcGxlIHdlIHdpbGwgc2ltcGx5IHVzZSBhbGwgdGhlIGRlZmF1bHQgYXJndW1lbnRzICh3aXRoIHRoZSBleGNlcHRpb24gb2YgdmVyYm9zZSwgd2hpY2ggd2Ugc2V0IHRvIEZBTFNFIGZvciBicmV2aXR5KS4gTm90ZSB0aGF0IHRoaXMgY29tbWFuZCBtYXkgdGFrZSBhIGZldyBtaW51dGVzIHRvIHJ1biAoZXhjbHVkaW5nIFJhbmRvbSBTdXJ2aXZhbCBGb3Jlc3RzIHdpbGwgc3BlZWQgdXAgY29tcHV0YXRpb24gdGltZSkuCgpgYGB7cn0KI1JTRiBhbmQgQ294RU4gYXJlIG5vbi1kZXRlcm1pbmlzdGljLCBzbyBzZXQgdGhlIHNlZWQgZm9yIHJlcHJvZHVjaWJsZSByZXN1bHRzLiAoQWRkaXRpb25hbCBzdG9jaGFzdGljaXR5IGlzIGluY2x1ZGVkIGluIDEtQ2FsaWJyYXRpb24pLgpzZXQuc2VlZCg0MikKcmVzID0gYW5hbHlzaXNNYXN0ZXIoZXhhbXBsZURhdCwgdmVyYm9zZSA9IEZBTFNFKQpgYGAKWW91IHdpbGwgbm90aWNlIHNvbWUgdmFyaWFibGVzIHdlcmUgcmVtb3ZlZCAtLSBTZWUgW1NlY3Rpb24gNC4xXSgjdmFsaWRhdGVBbmRDbGVhbikgZm9yIG1vcmUgZGV0YWlscy4KCgpgcmVzYCBpcyBub3cgYSBsaXN0IG9mIDMgaXRlbXM6CgoqIGBkYXRhc2V0VXNlZGAgLS0gVGhpcyBpcyB0aGUgZGF0YXNldCBwb3N0IHZhbGlkYXRpb24gYW5kIGZlYXR1cmUgc2VsZWN0aW9uLiBTb21lIHJvd3MgbWF5IGhhdmUgaGFkIG1pc3NpbmcgYHRpbWVgIG9yIGBkZWx0YWAgYW5kIGJlZW4gcmVtb3ZlZCBhbmQgZmVhdHVyZXMgd2hpY2ggd2VyZSByZW1vdmVkIHZpYSBmZWF0dXJlIHNlbGVjdGlvbiBvciB3ZXJlIGRlZW1lZCBhcyB1bnVzYWJsZSBbKFNlY3Rpb24gNC4xKV0oI3ZhbGlkYXRlQW5kQ2xlYW4pIHdpbGwgbm8gbG9uZ2VyIGJlIHByZXNlbnQuIAoqIGBzdXJ2aXZhbEN1cnZlc2AgLS0gIFRoaXMgaXMgYSBsaXN0IGNvbnRhaW5pbmcgdGhlIHN1cnZpdmFsIGN1cnZlcyBmb3IgYWxsIHBhdGllbnRzIGZvciBlYWNoIG1vZGVsIHRoYXQgd2FzIHRlc3RlZC4gVGhlc2UgY2FuIGJlIHBsb3R0ZWQgdXNpbmcgYHBsb3RTdXJ2aXZhbEN1cnZlcygpYC4KKiBgcmVzdWx0c2AgLS0gVGhpcyBpcyBhIGRhdGFmcmFtZSBjb250YWluaW5nIGFsbCB0aGUgZXZhbHVhdGlvbiByZXN1bHRzIHdpdGggc3BlY2lmaWVkIG1vZGVsIGFuZCBmb2xkIG51bWJlci4gQWRkaXRpb25hbGx5IHRoZSBzYW1wbGUgc2l6ZSwgbnVtYmVyIG9mIGZlYXR1cmVzIChwb3N0IGZlYXR1cmUgc2VsZWN0aW9uIGFuZCBvbmUtaG90IGVuY29kaW5nLCBleGNsdWRpbmcgYHRpbWVgIGFuZCBgZGVsdGFgKSwgYW5kIGNlbnNvcmluZyBwZXJjZW50YWdlIGFyZSByZXR1cm5lZC4KCkxvb2tpbmcgYXQgZWFjaCBvZiB0aGVzZSBpbiBtb3JlIGRldGFpbCwgbm90ZSB0aGUgZGlmZmVyZW5jZSBpbiBzaXplIGJldHdlZW4gYHJlcyRkYXRhc2V0VXNlZGAgYW5kIGBleGFtcGxlRGF0YC4KCmBgYHtyfQpkaW0oZXhhbXBsZURhdCkKZGltKHJlcyRkYXRhc2V0VXNlZCkKYGBgCjxicj4KQWRkaXRpb25hbGx5LCB3aGVuIHdlIHNvdXJjZWQgImFuYWx5c2lzTWFzdGVyLlIiIHdlIGJyb3VnaHQgaW4gYHBsb3RTdXJ2aXZhbEN1cnZlcygpYCB3aGljaCBjYW4gYmUgdXNlZCB0byBleGFtaW5lIHRoZSBzdXJ2aXZhbCBjdXJ2ZXMgb2YgZWFjaCBtb2RlbC4gSGVyZSB3ZSBzaG93IGV4YW1wbGVzIG9mIENveCBFbGFzdGljLU5ldCwgUmFuZG9tIHN1cnZpdmFsIGZvcmVzdHMsIGFuZCBNdWx0aS10YXNrIExvZ2lzdGljIHJlZ3Jlc3Npb24sIGhvd2V2ZXIsIHN1cnZpdmFsIGN1cnZlcyBmb3IgYWxsIG1vZGVscyBhcmUgYXZhaWxhYmxlLgoKYGBge3J9CiNDb3hFTgpwbG90U3Vydml2YWxDdXJ2ZXMoc3Vydml2YWxDdXJ2ZXMgPSByZXMkc3Vydml2YWxDdXJ2ZXMkQ294RU4sIGluZGV4VG9QbG90ID0gMToxMCkKI1JTRktNCnBsb3RTdXJ2aXZhbEN1cnZlcyhzdXJ2aXZhbEN1cnZlcyA9IHJlcyRzdXJ2aXZhbEN1cnZlcyRSU0YsIGluZGV4VG9QbG90ID0gMToxMCkKI01UTFIKcGxvdFN1cnZpdmFsQ3VydmVzKHN1cnZpdmFsQ3VydmVzID0gcmVzJHN1cnZpdmFsQ3VydmVzJE1UTFIsIGluZGV4VG9QbG90ID0gMToxMCkKYGBgCgpOb3RlIHRoYXQgZWFjaCBpbmRleCByZWxhdGVzIHRvIHRoZSBjb3JyZXNwb25kaW5nIHJvdyBpbiBgcmVzJGRhdGFzZXRVc2VkYCAoZS5nLiB0aGUgc3Vydml2YWwgY3VydmUgZm9yIEluZGV4IDEgaXMgZm9yIHRoZSBmb3IgdGhlIHBhdGllbnQgaW4gdGhlIGZpcnN0IHJvdyBvZiBgcmVzJGRhdGFzZXRVc2VkYCkuCgpUaGUgbGFzdCBjb21wb25lbnQgb2YgYHJlc2AgaXMgdGhlIGByZXN1bHRzYCBkYXRhZnJhbWUsIHdoaWNoIGluY2x1ZGVzIHRoZSBldmFsdWF0aW9ucyBmb3IgZXZlcnkgZm9sZCAoQ29uY29yZGFuY2UsIEwxLWxvc3MsIEJyaWVyIHNjb3JlKSBhbmQgdGhlIGNhbGlicmF0aW9uIGV2YWx1YXRlZCBvbiB0aGUgZW50aXJlIGRhdGFzZXQgKDEtQ2FsaWJyYXRpb24sIEQtQ2FsaWJyYXRpb24pLiBCZWxvdyBpcyBhIHZpZXcgb2YgYWxsIHRoZSByZXN1bHRzIGFuZCBhbiBleGFtcGxlIG9mIGFnZ3JlZ2F0aW5nIHRoZSByZXN1bHQuIChOb3RlIHRoYXQgd2UgYXZlcmFnZSB0aGUgcC12YWx1ZXMgb2YgMS1DYWxpYnJhdGlvbiBhbmQgRC1DYWxpYnJhdGlvbiBiZWNhdXNlIHRoZXkgYXJlIGVxdWFsIGFjcm9zcyB0aGUgZm9sZHMgYmVjYXVzZSB0aGV5IHdlcmUgZXZhbHVhdGVkIG9uIGFsbCB0aGUgdGVzdCBkYXRhIGFzIG9wcG9zZWQgdG8gcGVyIGZvbGQgLS0gc2VlIFtTZWN0aW9uIDYuM10oIzFDYWwpIGFuZCBbU2VjdGlvbiA2LjZdKCNEQ2FsKSkKCmBgYHtyfQpyZXMkcmVzdWx0cwpyZXMkcmVzdWx0cyAlPiUKICBncm91cF9ieShNb2RlbCkgICU+JQogIHN1bW1hcmlzZShOID0gbWVhbihOKSwKICAgICAgICAgICAgTnVtRmVhdHVyZXMgPSBtZWFuKE51bUZlYXR1cmVzKSwKICAgICAgICAgICAgUGVyY2VudENlbnNvcmVkID0gbWVhbihQZXJjZW50Q2Vuc29yZWQpLAogICAgICAgICAgICBEQ2FsaWJyYXRpb24gPSBtZWFuKERDYWxpYnJhdGlvbiksCiAgICAgICAgICAgIE9uZUNhbGlicmF0aW9uMTB0aCA9IG1lYW4oT25lQ2FsaWJyYXRpb25fMSksCiAgICAgICAgICAgIE9uZUNhbGlicmF0aW9uMjV0aCA9IG1lYW4oT25lQ2FsaWJyYXRpb25fMiksCiAgICAgICAgICAgIE9uZUNhbGlicmF0aW9uNTB0aCA9IG1lYW4oT25lQ2FsaWJyYXRpb25fMyksCiAgICAgICAgICAgIE9uZUNhbGlicmF0aW9uNzV0aCA9IG1lYW4oT25lQ2FsaWJyYXRpb25fNCksCiAgICAgICAgICAgIE9uZUNhbGlicmF0aW9uOTB0aCA9IG1lYW4oT25lQ2FsaWJyYXRpb25fNSksCiAgICAgICAgICAgIENvbmNvcmRhbmNlTSA9IG1lYW4oQ29uY29yZGFuY2UpLAogICAgICAgICAgICBDb25jb3JkYW5jZVNEID0gc2QoQ29uY29yZGFuY2UpLAogICAgICAgICAgICBTaW5nbGVCcmllck0gPSBtZWFuKEJyaWVyU2luZ2xlKSwKICAgICAgICAgICAgU2luZ2xlQnJpZXJTRCA9IHNkKEJyaWVyU2luZ2xlKSwKICAgICAgICAgICAgSW50ZWdyYXRlZEJyaWVyTSA9IG1lYW4oQnJpZXJJbnQpLAogICAgICAgICAgICBJbnRlZ3JhdGVkQnJpZXJTRCA9IHNkKEJyaWVySW50KSwKICAgICAgICAgICAgTDFNID0gbWVhbihMMUxvc3MpLAogICAgICAgICAgICBMMVNEID0gc2QoTDFMb3NzKSkKYGBgCgpIZXJlIHdlIGhhdmUgZ2l2ZW4gYSBmYWlybHkgYnJpZWYgaW50cm9kdWN0aW9uIHRvIHVzaW5nIHRoZSBjb2RlYmFzZSB0byBydW4gdGhlIDYgc3Vydml2YWwgbW9kZWxzIGFjcm9zcyB0aGUgNSBkaWZmZXJlbnQgZXZhbHVhdGlvbiBtZXRyaWNzLiBGb3IgYSBtdWNoIG1vcmUgZGV0YWlsZWQgaW50cm9kdWN0aW9uIHRvIHRoZSBjb2RlIChhbmQgYSB3YWxrIHRocm91Z2ggb2YgYWxsIHRoZSBmdW5jdGlvbnMpIGNvbnRpbnVlIG9uIHRvIHRoZSBuZXh0IHNlY3Rpb25zIG9yIGp1bXAgdG8gYSBzcGVjaWZpYyBzZWN0aW9uIHVzaW5nIHRoZSB0YWJsZSBvZiBjb250ZW50cy4KCiNDb2RlIE92ZXJ2aWV3IGFuZCBJbnRyb2R1Y3Rpb24gdG8gRXhhbXBsZSBEYXRhIHsjdGhlRGV0YWlsc30KClRoZSBjb2RlIGhlcmUgaGFzIGJlZW4gd3JpdHRlbiBmb3IgKnN1cnZpdmFsKiBkYXRhc2V0cywgKmkuZS4qIGRhdGFzZXRzIGFyZSBjb21wcmlzZWQgb2YgKDEpIGEgdmFyaWFibGUgd2hpY2ggaW5kaWNhdGVzIHRpbWUgb2YgZXZlbnQsICgyKSBhbiBpbmRpY2F0b3IgdmFyaWFibGUgZm9yIFJJR0hUIGNlbnNvcmluZywgYW5kICgzKSBmZWF0dXJlcyBmb3IgZWFjaCBwYXRpZW50LiBOb3RlIHRoYXQgdGhlIGNvZGUgb25seSBhbGxvd3MgZm9yIHJpZ2h0IGNlbnNvcmVkIHBhdGllbnRzLCBsZWZ0IGNlbnNvcmVkIG9yIGludGVydmFsIGNlbnNvcmVkIGRhdGEgd2lsbCBub3Qgd29yayB3aXRoIHRoZSBjdXJyZW50IGltcGxlbWVudGF0aW9uLiBUbyBmb2xsb3cgYWxvbmcgd2l0aCB0aGUgZXhhbXBsZXMgcGxlYXNlIG1ha2Ugc3VyZSB0aGF0IHlvdSBoYXZlIGRvd25sb2FkZWQgYWxsIHRoZSByZXF1aXJlZCBwYWNrYWdlcyAoc2VlIHRoZSBmaXJzdCBwYXJhZ3JhcGggb2YgdGhlIFtRdWlja3N0YXJ0IEd1aWRlXSgjcXVpY2tzdGFydCkpLCBoYXZlIHlvdXIgd29ya2luZyBkaXJlY3RvcnkgaW4gYH4vSVNERXZhbHVhdGlvbmAsIGFuZCBzb3VyY2VkIGFsbCB0aGUgZmlsZXMgKGF2YWlsYWJsZSBieSBydW5uaW5nIGBzb3VyY2UoImFuYWx5c2lzTWFzdGVyLlIiKSkuCgpBcyBhIGNhc2Ugc3R1ZHkgd2Ugd2lsbCB1c2UgdGhlIE5vcnRoZXJuIEFsYmVydGEgQ2FuY2VyIERhdGFzZXQgKE5BQ0QpIHdoaWNoIHdhcyBhbHNvIGluY2x1ZGVkIGluIHRoZSBvcmlnaW5hbCBwYXBlci4gSG93ZXZlciwgdG8gc2ltcGxpZnkgdGhpbmdzIHdlIHdpbGwgdXNlIGEgbmV3IHN1YnNldCBvZiB0aGUgZGF0YXNldCwgbmFtZWx5IHdlIHdpbGwgb25seSBjb25zaWRlciBwYXRpZW50cyB3aXRoIHN0YWdlIDQgc3RvbWFjaCBjYW5jZXJzLiBUaGlzIGRhdGEgY29udGFpbnMgYSB0b3RhbCBvZiAxMjggcGF0aWVudHMgKDEwMCB1bmNlbnNvcmVkKSBhbmQgNTMgZmVhdHVyZXMsIGFuZCBpcyBhbHNvIGF2YWlsYWJsZSBvbiB0aGUgW2dpdGh1YiByZXBvc2l0b3J5XShodHRwczovL2dpdGh1Yi5jb20vaGFpZGVyc3RhdHMvSVNERXZhbHVhdGlvbikgdW5kZXIgdGhlIG5hbWUgImV4YW1wbGVEYXRhLmNzdiIuIEJlbG93IHdlIGdpdmUgdGhlIHN1bW1hcnkgc3RhdGlzdGljcyBvZiB0aGUgZmlyc3QgZml2ZSB2YXJpYWJsZXM6CgpgYGB7cn0KTkFDRCA9IHJlYWQuY3N2KGZpbGUgPSAiaHR0cDovL3Bzc3Auc3J2LnVhbGJlcnRhLmNhL3N5c3RlbS9wcmVkaWN0b3JzL2RhdGFzZXRzLzAwMC8wMDAvMDc2L29yaWdpbmFsL0FsbF9EYXRhX3VwZGF0ZWRfbWF5MjAxMV9TdGFnZTRfU3RvbWFjaC5jc3Y/MTM1NDcwODUwNCIpCmhlYWQoTkFDRCkKc3VtbWFyeShOQUNEWywxOjVdKQpgYGAKCgpIZXJlLCB3ZSBoYXZlIGBTVVJWSVZBTGAgYXMgdGhlIGZlYXR1cmUgaW5kaWNhdGluZyB0aGUgdGltZSB1bnRpbCB0aGUgZXZlbnQgb2NjdXJyZWQgYW5kIGBDRU5TT1JFRGAgcmVwcmVzZW50aW5nIHRoZSBpbmRpY2F0b3IgZm9yIGRlYXRoLCB0aGF0IGlzLCBhIDEgaW5kaWNhdGVzIHRoZSBwYXRpZW50IHdhcyBjZW5zb3JlZCBhbmQgYSAwIGluZGljYXRlcyB0aGUgcGF0aWVudCBkaWVkIC0tIHRoZXNlIHR3byB0eXBlcyBvZiB2YXJpYWJsZXMgYXJlIHJlcXVpcmVkIGZvciBhbGwgZGF0YXNldHMgaW4gdGhpcyBjb2RlYmFzZS4gRmVlbCBmcmVlIHRvIGV4cGxvcmUgdGhlIG90aGVyIHZhcmlhYmxlcyBpZiB5b3UgYXJlIGludGVyZXN0ZWQuCgpBIHF1aWNrIG5vdGUgb24gbm90YXRpb24gLS0gSW4gZ2VuZXJhbCBmZWF0dXJlcyBhbmQgcGFja2FnZXMgd2lsbCBiZSBnaXZlbiBhcyB2ZXJiYXRpbSwgZS5nLiBgcGFja2FnZTFgIGFuZCBgZmVhdHVyZTFgLCBmdW5jdGlvbnMgd2lsbCBhbHNvIGJlIHZlcmJhdGltIGJ1dCBhbHNvIGhhdmUgcGFyZW50aGVzZXMgZS5nLiBgZnVuY3Rpb24xKClgLCBhcmd1bWVudHMgdG8gZnVuY3Rpb25zIHdpbGwgYmUgKml0YWxpY2l6ZWQqLCB2YXJpYWJsZSBhc3NpZ25tZW50cyB3aWxsIGJlICoqYm9sZGVkKiosIGFuZCBmaWxlcyB3aWxsIGJlIHF1b3RlZCAoImZpbGUxLlIiKS4gSG93ZXZlciwgdGhlcmUgYXJlIGV4Y2VwdGlvbnMgd2hlbiBmdW5jdGlvbnMvZmlsZSBuYW1lcyBhcmUgY29udGFpbmVkIGluIHNlY3Rpb24gaGVhZGVycy4KCiNQcmUtcHJvY2Vzc2luZyB0aGUgRGF0YQoKIyNWYWxpZGF0aW5nIGFuZCBDbGVhbmluZyAgKHZhbGlkYXRlQW5kQ2xlYW4uUikgeyN2YWxpZGF0ZUFuZENsZWFufQoKKipGaWxlIFB1cnBvc2UqKjogVGhpcyBmaWxlIGlzIHVzZWQgdG8gbWFrZSBzdXJlIHRoZSBzdXJ2aXZhbCBkYXRhc2V0IGlzIGluIHRoZSBjb3JyZWN0IGZvcm1hdCBmb3IgYW5hbHlzaXMuIEZvciBleGFtcGxlLCB0aGUgdmFyaWFibGUgdXNlZCBmb3Igc3Vydml2YWwgdGltZSBtdXN0IGJlIG5hbWVkIGV4YWN0bHkgYHRpbWVgLiBBZGRpdGlvbmFsbHksIGEgbWluaW1hbCBhbW91bnQgb2YgZGF0YSBjbGVhbnNpbmcgaXMgY29tcGxldGVkIGluIHRoaXMgZmlsZS4KCioqRmlsZSBQYXRoKio6ICJJU0RFdmFsdWF0aW9uL1ZhbGlkYXRlQ2xlYW5DVi92YWxpZGF0ZUFuZENsZWFuLlIiLgoKKipGdW5jdGlvbnMqKjoKCjEuIGB2YWxpZGF0ZUFuZENsZWFuKHN1cnZpdmFsRGF0YXNldCwgaW1wdXRlWmVybz1UKWAKMi4gYHZhbGlkYXRlKHN1cnZpdmFsRGF0YXNldCwgaW1wdXRlWmVybylgCjMuIGBjbGVhbihzdXJ2aXZhbERhdGFzZXQpYAoKTm90ZTogWW91IHdpbGwgbm90aWNlIHRoaXMgZmlsZSBzdHJ1Y3R1cmUgdGhyb3VnaG91dCB0aGUgY29kZWJhc2UsIHRoZXJlIHdpbGwgb2Z0ZW4gYmUgYSBtYXN0ZXIgZnVuY3Rpb24gKGhlcmUgYHZhbGlkYXRlQW5kQ2xlYW4oKWApIHdoaWNoIHJ1bnMgdGhlIG90aGVyIGZ1bmN0aW9ucyBpbmNsdWRlZCBpbiB0aGUgZmlsZSwgKmUuZy4qIGB2YWxpZGF0ZSgpYCBhbmQgYGNsZWFuKClgLiAKCioqQXJndW1lbnRzKio6CgoqICpzdXJ2aXZhbERhdGFzZXQqOiBhIChub24tZW1wdHkpIGRhdGFmcmFtZSByZXByZXNlbnRpbmcgdGhlIHN1cnZpdmFsIGRhdGFzZXQgLS0gKmUuZy4qICoqTkFDRCoqCiogKmltcHV0ZVplcm8qOiBhIEJvb2xlYW4gaW5kaWNhdGluZyBpZiB6ZXJvIHZhbHVlZCBzdXJ2aXZhbCB0aW1lcyBzaG91bGQgYmUgaW1wdXRlZCAoc2VlIGJlbG93IGZvciBkZXRhaWxzKS4KCioqUGFja2FnZXMgUmVxdWlyZWQqKjoKCjEuIGBjYXJldGAgLS0gVGhlIGBuZWFyWmVyb1ZhcigpYCBmdW5jdGlvbiB0byBpZGVudGlmeSB2YXJpYWJsZXMgd2l0aCBvbmx5IDEgdW5pcXVlIHZhbHVlIGFuZCByZW1vdmUgdGhlbSBpbiB0aGUgYGNsZWFuKClgIGZ1bmN0aW9uLgoKCiMjIyp2YWxpZGF0ZShzdXJ2aXZhbERhdGFzZXQsIGltcHV0ZVplcm8pKiAKVGhlIGZ1bmN0aW9uIGB2YWxpZGF0ZSgpYCBjaGVja3MgYSBudW1iZXIgb2YgdGhpbmdzOgoKMS4gQSBzdXJ2aXZhbCBkYXRhc2V0IHdhcyBub25lbXB0eS4KMi4gVGhlIHN1cnZpdmFsIHRpbWUgdmFyaWFibGUgTVVTVCBiZSBuYW1lZCBgdGltZWAuCjMuIFNpbWlsYXJseSwgdGhlIGluZGljYXRvciBvZiBkZWF0aCB2YXJpYWJsZSBtdXN0IGJlIG5hbWVkIGBkZWx0YWAuIFRoZSBuYW1pbmcgY2hvaWNlIG9mIHRoaXMgdmFyaWFibGUgaXMgdGhlIGNvbW1vbiBub3RhdGlvbiBpbiBzdXJ2aXZhbCBhbmFseXNpcy4KNC4gSWYgYHRpbWVgIGluY2x1ZGVzIE5BIHZhbHVlcyB0aGVzZSByb3dzIHdpbGwgYmUgcmVtb3ZlZC4KNS4gSWYgYHRpbWVgIGluY2x1ZGVzIEluZiB2YWx1ZXMgdGhlc2Ugcm93cyB3aWxsIGJlIHJlbW92ZWQuCjYuIElmIGB0aW1lYCBpbmNsdWRlcyBuZWdhdGl2ZSB2YWx1ZXMgdGhlc2Ugcm93cyB3aWxsIGJlIHJlbW92ZWQuCjcuIElmIGB0aW1lYCBpbmNsdWRlcyB6ZXJvIHZhbHVlcyB0aGVyZSBhcmUgdHdvIG9wdGlvbnMgZGVwZW5kaW5nIG9uIHRoZSB2YWx1ZSBvZiB0aGUgYXJndW1lbnQgaW1wdXRlWmVyby4gSWYgaW1wdXRlWmVybyA9IFRSVUUsIHRoZW4gemVybyB2YWx1ZWQgdGltZXMgd2lsbCBiZSBpbXB1dGVkIHRvIGhhbGYgdGhlIG1pbmltdW0gbm9uIHplcm8gdmFsdWVkIHRpbWUuIEZvciBleGFtcGxlIGluIHRoZSBOQUNEIGRhdGFzZXQgd2UgYXJlIHVzaW5nIGhlcmUgdGhlIG1pbmltdW0gdGltZSBpcyAwLjIzMzMuIElmIHRoZXJlIHdhcyBhbm90aGVyIHJvdyB3aGVyZSB0aGUgZXZlbnQgdGltZSB3YXMgMCwgdGhpcyB3b3VsZCBiZSBpbXB1dGVkIHRvICRcZnJhY3swLjIzMzN9ezJ9ID0gMC4xMTY2JC4gV2UgaW5jbHVkZSB0aGlzIG9wdGlvbiBhcyBzb21lIHN1cnZpdmFsIG1vZGVscyAoKmUuZy4qIEFjY2VsZXJhdGVkIEZhaWx1cmUgVGltZSB3aXRoIHRoZSBXZWlidWxsIGRpc3RyaWJ1dGlvbikgd2lsbCBmYWlsIGlmIGdpdmVuIGEgemVybyB2YWx1ZWQgdGltZS4gSWYgaW1wdXRlWmVybyA9IEZBTFNFIHRoZW4gbm90aGluZyB3aWxsIGhhcHBlbiB0byB0aGVzZSB6ZXJvIHZhbHVlZCBzdXJ2aXZhbCB0aW1lcy4gQ3VycmVudGx5IHRoZXJlIGlzIG5vIG9wdGlvbiB0byByZW1vdmUgemVybyB2YWx1ZWQgdGltZXMgb3RoZXIgdGhhbiByZW1vdmluZyB0aGVtIGZyb20gdGhlIGRhdGFzZXQgcHJpb3IgdG8gcnVubmluZyB0aGUgY29kZSBhdmFpbGFibGUgaGVyZS4KOC4gSWYgYGRlbHRhYCBpbmNsdWRlcyBOQSB2YWx1ZXMgdGhlc2Ugcm93cyB3aWxsIGJlIHJlbW92ZWQuCjkuIElmIGBkZWx0YWAgaW5jbHVkZXMgSW5mIHZhbHVlcyB0aGVzZSByb3dzIHdpbGwgYmUgcmVtb3ZlZC4KMTAuIGBkZWx0YWAgbXVzdCBvbmx5IGhhdmUgdHdvIHVuaXF1ZSB2YWx1ZXMuCjExLiBUaGUgdW5pcXVlIHZhbHVlcyBvZiBgZGVsdGFgIG11c3QgYmUgMCBhbmQgMSAtLSAwIGZvciBjZW5zb3JlZCBkYXRhIGFuZCAxIGZvciB1bmNlbnNvcmVkIGRhdGEuCgpJZiB3ZSB0cnkgdG8gdXNlIGB2YWxpZGF0ZSgpYCBvbiBvdXIgTkFDRCBkYXRhc2V0IHdlIHdpbGwgZ2V0IGFuIGVycm9yIHNpbmNlIGN1cnJlbnRseSBvdXIgc3Vydml2YWwgdGltZSB2YXJpYWJsZSBpcyBuYW1lZCBgVGltZWAgYW5kIG91ciBkZWF0aCBpbmRpY2F0b3IgaXMgbmFtZWQgYFN0YXR1c2AuIE5vdGUgdGhhdCBiZWxvdyB3ZSBpbmNsdWRlICppbXB1dGVaZXJvID0gVFJVRSosIGJ1dCBzaW5jZSB0aGVyZSBhcmUgbm8gemVyby12YWx1ZWQgdGltZXMgdGhlIHNhbWUgcmVzdWx0IHdvdWxkIG9jY3VyIGlmIHdlIGluY2x1ZGVkICppbXB1dGVaZXJvPUZBTFNFKi4KCmBgYHtyLCBlcnJvcj0gVFJVRX0KdmFsaWRhdGVkTkFDRCA9IHZhbGlkYXRlKE5BQ0QsIGltcHV0ZVplcm8gPSBUUlVFKQpuYW1lcyhOQUNEKVsxXSA9ICJ0aW1lIgp2YWxpZGF0ZWROQUNEID0gdmFsaWRhdGUoTkFDRCwgaW1wdXRlWmVybyA9IFRSVUUpCgojQWRkaXRpb25hbGx5LCB3ZSBuZWVkIHRvIHN3YXAgdGhlIHZhbHVlcyBmb3IgZGVsdGEgc2luY2UgQ0VOU09SRUQgPSAxIG1lYW50IGEgcGF0aWVudCB3YXMgY2Vuc29yZWQuIFdlIG5lZWQgMSB0byBtZWFuIGEgZGVhdGguCm5hbWVzKE5BQ0QpWzJdID0gImRlbHRhIgpOQUNEJGRlbHRhID0gMSAtIE5BQ0QkZGVsdGEKdmFsaWRhdGVkTkFDRCA9IHZhbGlkYXRlKE5BQ0QsIGltcHV0ZVplcm8gPSBUUlVFKQpgYGAKCiMjIypjbGVhbihzdXJ2aXZhbERhdGFzZXQpKgoKSGVyZSwgYGNsZWFuKClgIGRvZXMgYSB2ZXJ5IG1pbmltYWwgYW1vdW50IG9mIGNsZWFuaW5nIG9uIHRoZSBzdXJ2aXZhbCBkYXRhc2V0LiBTcGVjaWZpY2FsbHksIHRoZSBnb2FsIGlzIHRvIHJlbW92ZSB2YXJpYWJsZXMgd2hpY2ggd2lsbCBiZSBub3QgYmUgbWVhbmluZ2Z1bCB0byBhbmFseXNpcy4gVGhlcmUgYXJlIHR3byBjYXNlcyB3aGVyZSB2YXJpYWJsZXMgYXJlIHJlbW92ZWQ6CgoxLiBBIHZhcmlhYmxlIGNvbnRhaW5zIGFsbCB1bmlxdWUgdmFsdWVzLiBGb3IgZXhhbXBsZSwgaW4gb3VyIE5BQ0QgZGF0YXNldCB0aGVyZSBpcyBhIGBTVEFHRV80YCB2YXJpYWJsZSB3aGljaCBpcyBhbiBpbmRpY2F0b3IgKDEgb3IgMCkgd2hldGhlciBvciBub3QgdGhlIGNhbmNlciBpcyBpbiBzdGFnZSA0LiBTaW5jZSB3ZSBvbmx5IGhhdmUgc3RhZ2UgNCBzdG9tYWNoIGNhbmNlciBwYXRpZW50cyB0aGlzIHZhcmlhYmxlIHdpbGwgYmUgYmUgYWxsIDFzIGFuZCB3aWxsIGJlIHJlbW92ZWQgZnJvbSB0aGUgZGF0YXNldC4KMi4gVmFyaWFibGVzIHN1Y2ggdGhhdCAyNSUgb2Ygcm93cyAocGF0aWVudHMpIGhhdmUgYSBtaXNzaW5nIHZhbHVlIHdpbGwgYmUgcmVtb3ZlZC4gSG93ZXZlciwgdGhlIGNvZGluZyBvZiBtaXNzaW5nIGRhdGEgbXVzdCBiZSBzdWNoIHRoYXQgYGlzLm5hKClgIHdvdWxkIGlkZW50aWZ5IHRoZSBkYXRhIGFzIG1pc3NpbmcuIEZvciBleGFtcGxlIGBpcy5uYSgiIilgIHJldHVybnMgVFJVRSBldmVuIHRob3VnaCBhbiBlbXB0eSBzdHJpbmcgbGlrZWx5IGluZGljYXRlcyBhIG1pc3NpbmcgdmFsdWUuCgpXaGVuZXZlciBhIHZhcmlhYmxlIGlzIHJlbW92ZWQgb3IgdHVybmVkIGludG8gYSBmYWN0b3IsIGBjbGVhbigpYCB3aWxsIHJldHVybiBhIHdhcm5pbmcgaW5kaWNhdGluZyB0aGUgbmFtZSBvZiB0aGUgdmFyaWFibGUocykgd2hpY2ggd2VyZSByZW1vdmVkL2ZhY3RvcmVkLiBGb3IgZXhhbXBsZSwgb24gb3VyIHZhbGlkYXRlZCBOQUNEIGRhdGFzZXQgd2UgY2FuIG5vdyBjYWxsIGBjbGVhbigpYDoKCgpgYGB7cn0KY2xlYW5lZE5BQ0QgPSBjbGVhbih2YWxpZGF0ZWROQUNEKQpgYGAKQXMgbm90ZWQgYWJvdmUgYSBudW1iZXIgb2YgdmFyaWFibGVzIHdlcmUgcmVtb3ZlZCBkdWUgdG8gaGF2aW5nIG9ubHkgMSB1bmlxdWUgdmFsdWUuIAoKCiMjIyp2YWxpZGF0ZUFuZENsZWFuKHN1cnZpdmFsRGF0YXNldCwgaW1wdXRlWmVybz1UKSoKClRoaXMgZnVuY3Rpb24gc2ltcGx5IHJ1bnMgYm90aCBgdmFsaWRhdGUoKWAgYW5kIGBjbGVhbigpYCBvbiBhIHN1cnZpdmFsIGRhdGFzZXQuIE5vdGUgdGhhdCBoZXJlIHdlIHVzZSBkZWZhdWx0IHRoZSBkZWZhdWx0IChUUlVFKSBmb3IgKmltcHV0ZVplcm8qLiBCZWxvdyB3ZSB2ZXJpZnkgdGhhdCBgdmFsaWRhdGVBbmRDbGVhbihOQUNEKWAgaXMgZXF1aXZhbGVudCB0byAqKmNsZWFuZWROQUNEKiouCgpgYGB7ciwgd2FybmluZz1GQUxTRX0KaWRlbnRpY2FsKHZhbGlkYXRlQW5kQ2xlYW4oTkFDRCksY2xlYW5lZE5BQ0QpCmBgYAoKCiMjQ3JlYXRpbmcgQ3Jvc3MtVmFsaWRhdGlvbiBGb2xkcyBhbmQgTm9ybWFsaXppbmcgRmVhdHVyZXMgKGNyZWF0ZUZvbGRzQW5kTm9ybWFsaXplLlIpCgoqKkZpbGUgUHVycG9zZSoqOiBUaGlzIGZpbGUgaXMgdXNlZCB0byBjcmVhdGUgZm9sZHMgZnJvbSB0aGUgc3Vydml2YWwgZGF0YXNldCBmb3IgY3Jvc3MgdmFsaWRhdGlvbiBhbmQgbm9ybWFsaXplIGZlYXR1cmVzLiBBcyBwYXJ0IG9mIG5vcm1hbGl6YXRpb24sIG1lYW4vbW9kZSBpbXB1dGF0aW9uIGlzIGVtcGxveWVkIHRvIGhhbmRsZSBtaXNzaW5nIGRhdGEuCgoqKkZpbGUgUGF0aCoqOiAiSVNERXZhbHVhdGlvbi9WYWxpZGF0ZUNsZWFuQ1YvY3JlYXRlRm9sZHNBbmROb3JtYWxpemUuUiIuCgoqKkZ1bmN0aW9ucyoqOgoKMS4gYGNyZWF0ZUZvbGRzQW5kTm9ybWFsaXplKHN1cnZpdmFsRGF0YXNldCwgbnVtYmVyT2ZGb2xkcylgCjIuIGBjcmVhdGVGb2xkc09mRGF0YShzdXJ2aXZhbERhdGFzZXQsIG51bWJlck9mRm9sZHMpYAozLiBgbWVhbkltcHV0YXRpb24obGlzdE9mRGF0YXNldHMpYAo0LiBgbm9ybWFsaXplVmFyaWFibGVzKGxpc3RPZkltcHV0ZWREYXRhc2V0cylgCgoKKipBcmd1bWVudHMqKjoKCiogKnN1cnZpdmFsRGF0YXNldCo6IHRoZSBzdXJ2aXZhbERhdGFzZXQgQUZURVIgZ29pbmcgdGhyb3VnaCB2YWxpZGF0aW9uIGFuZCBjbGVhbmluZyAtLSAqZS5nLiogKipjbGVhbmVkTkFDRCoqCiogKm51bWJlck9mRm9sZHMqOiB0aGUgZGVzaXJlZCBudW1iZXIgb2YgY3Jvc3MtdmFsaWRhdGlvbiBmb2xkcywgbXVzdCBiZSBncmVhdGVyIHRoYW4gMS4KKiAqbGlzdE9mRGF0YXNldHMqOiBhIGxpc3QgY29udGFpbmluZyBhIGxpc3Qgb2YgVHJhaW5pbmcgZGF0YXNldHMgYW5kIGEgbGlzdCBvZiBUZXN0aW5nIGRhdGFzZXRzIC0gKmkuZS4qIHRoZSBvdXRwdXQgb2YgYGNyZWF0ZUZvbGRzT2ZEYXRhKClgCiogKmxpc3RPZkltcHV0ZWREYXRhc2V0cyo6IHNhbWUgYXMgKmxpc3RPZkRhdGFzZXRzKiBidXQgbWlzc2luZyB2YWx1ZXMgaGF2ZSBiZWVuIGltcHV0ZWQgKHRoZSBvdXRwdXQgb2YgYG1lYW5JbXB1dGF0aW9uKClgKQoKKipQYWNrYWdlcyBSZXF1aXJlZCoqOgoKMS4gYGNhcmV0YCAtLSBUaGUgYGR1bW15VmFycygpYCBmdW5jdGlvbiBpcyB1c2VkIHRvIGNyZWF0ZSBhIG9uZSBob3QgZW5jb2Rpbmcgb2YgZmFjdG9yIChub21pbmFsKSB2YXJpYWJsZXMuCjIuIGBkYXRhUHJlcGFyYXRpb25gIC0tIFRoZSBgYnVpbGRfc2NhbGVzKClgIGFuZCBgZmFzdFNjYWxlKClgIGZ1bmN0aW9uIGFyZSB1c2VkIHRvIG5vcm1hbGl6ZSB2YXJpYWJsZXMuCgojIyMqY3JlYXRlRm9sZHNPZkRhdGEoc3Vydml2YWxEYXRhc2V0LCBudW1iZXJPZkZvbGRzKSp7I2ZvbGRzfQoKVGhpcyBjb2RlIGJhc2UgcmVxdWlyZXMgdGhhdCBkYXRhIGJlIGluIGNyb3NzLXZhbGlkYXRpb24gZm9ybWF0IHdpdGggYSBtaW5pbXVtIG9mIDIgZm9sZHMuIFRoZSBjcm9zcy12YWxpZGF0aW9uIHVzZWQgaGVyZSBpcyBzcGVjaWZpYyB0byBzdXJ2aXZhbCBhbmFseXNpcyBpbiB0aGF0IGNyb3NzLXZhbGlkYXRpb24gaXMgZGV0ZXJtaW5pc3RpYyAtLSByb3dzIGFyZSBmaXJzdCBzdHJhdGlmaWVkIGJ5IHRoZWlyIGNlbnNvcmluZyBzdGF0dXMgKGBkZWx0YWApIGFuZCB0aGVuIHNvcnRlZCBieSBgdGltZWAgYW5kIHNlcXVlbnRpYWxseSBwbGFjZWQgaW50byBmb2xkcyAoc2VlIEZpZ3VyZSAxKS4gVGhlIHJlYXNvbmluZyBiZWhpbmQgdGhpcyBwcm9jZWR1cmUgaXMgdHdvLWZvbGQgKHB1bi1pbnRlbmRlZCk6CgoxLiBUaGUgbnVtYmVyIG9mIGNlbnNvcmVkIHBhdGllbnRzIHNob3VsZCBiZSAocm91Z2hseSkgZXF1YWwgYWNyb3NzIGFsbCBmb2xkcy4KMi4gVGhlIHJhbmdlIG9mIGV2ZW50IHRpbWVzIHNob3VsZCBhbHNvIGJlIChyb3VnaGx5KSBlcXVhbCBhY3Jvc3MgYWxsIGZvbGRzLgoKQnkgY3JlYXRpbmcgZm9sZHMgaW4gdGhpcyB3YXkgbW9kZWxzIHdpbGwgYmUgdGVzdGVkIG9uIGRhdGEgd2hpY2ggd2lsbCBiZSBtb3N0bHkgcmVwcmVzZW50YXRpdmUgb2Ygd2hhdCB0aGV5IHNhdyBpbiB0aGUgdHJhaW5pbmcgZGF0YS4KCiFbRmlndXJlIDE6IERlcGljdGlvbiBvZiAzLWZvbGQgY3Jvc3MtdmFsaWRhdGlvbiBzY2hlbWUgdXNlZCBmb3Igc3Vydml2YWwgZGF0YS4gVGhlIGRhdGFzZXQgaXMgZmlyc3Qgc3RyYXRlZmllZCBieSBkZWx0YSBhbmQgdGhlbiBzb3J0ZWQgYnkgdGltZS4gUm93cyBhcmUgdGhlbiBzZXF1ZW50aWFsbHkgb3JkZXJlZCBpbnRvIHRoZSB0aHJlZSBmb2xkcy5dKC4vRmlndXJlcy9TdXJ2aXZhbENWLnBuZykKCkluIGFkZGl0aW9uIHRvIGNyZWF0aW5nIGNyb3NzIHZhbGlkYXRpb24gZm9sZHMsIGBjcmVhdGVGb2xkc09mRGF0YSgpYCBhbHNvIHJldHVybnMgdGhlIGluZGV4ZXMgb2YgdGhlIHJvd3MgdGhhdCBlYWNoIHRlc3Qgc2V0IGJlbG9uZ3MuIFNlZSB0aGUgYmVsb3cgZXhhbXBsZSBmb3IgZGV0YWlscy4gSGVyZSB3ZSBwdXQgb3VyIGNsZWFuZWQgTkFDRCBkYXRhc2V0ICgqKmNsZWFuZWROQUNEKiopIGludG8gNSBmb2xkczoKCmBgYHtyfQpmb2xkcyA9IGNyZWF0ZUZvbGRzT2ZEYXRhKGNsZWFuZWROQUNELCA1KQojZm9sZHMgY29tcHJpc2VzIGEgbGlzdCBvZiAoMikgaXRlbXMuICgxKSBpcyB0aGUgaW5kZXhzIG9mIHRoZSB0ZXN0IHNldC4gRm9yIGV4YW1wbGUsIGV4YW1pbmUgdGhlIGZpcnN0IHRlc3Qgc2V0IGZvbGQ6CnRlc3RTZXRJbmRleHMgPSBmb2xkc1tbMV1dCiNUZXN0IGZvbGQgMToKdGVzdFNldEluZGV4c1tbMV1dCmBgYApIZXJlIHRoZSBmaXJzdCB0ZXN0IGZvbGQgaXMgY29tcHJpc2VkIG9mIHRoZSAxMjNyZCwgMTAxc3QsIDYybmQsIC4uLiwgYW5kIHRoZSA3N3RoIHJvd3Mgb2YgdGhlIG9yaWdpbmFsIGRhdGEuIFdlIHdpbGwgbGF0ZXIgdXNlIHRoZXNlIGluZGV4ZXMgZm9yIHBsb3R0aW5nIGluIFtTZWN0aW9uIDcuMV0oI3Bsb3R0aW5nKS4KClRoZSBzZWNvbmQgaXRlbSByZXR1cm5lZCBieSBgY3JlYXRlRm9sZHNPZkRhdGEoKWAgaXMgYSBsaXN0IG9mICgxKSB0aGUgdHJhaW5pbmcgc2V0cywgYW5kICgyKSB0aGUgdGVzdGluZyBzZXRzLiBCZWxvdyB3ZSBzaW1wbHkgZ2l2ZSB0aGUgZGltZW5zaW9uIG9mIGVhY2g6CmBgYHtyfQp0cmFpbmluZ0FuZFRlc3RpbmdTZXRzID0gZm9sZHNbWzJdXQojRmlyc3QgVHJhaW5pbmcgU2V0OgpkaW0odHJhaW5pbmdBbmRUZXN0aW5nU2V0cyRUcmFpbmluZ1tbMV1dKQojRmlyc3QgVGVzdGluZyBTZXQ6CmRpbSh0cmFpbmluZ0FuZFRlc3RpbmdTZXRzJFRlc3RpbmdbWzFdXSkKYGBgCgojIyMqbWVhbkltcHV0YXRpb24obGlzdE9mRGF0YXNldHMpKgoKRm9sbG93aW5nIHRoZSBzcGxpdHRpbmcgb2YgZm9sZHMgd2UgcGVyZm9ybSBpbXB1dGF0aW9uIGZvciBtaXNzaW5nIGRhdGEuIFNpbmNlIHRoZSBwcmltYXJ5IGZvY3VzIG9mIHRoaXMgd29yayB3YXMgbm90IGluIGFueSB3YXkgZ2VhcmVkIHRvd2FyZHMgaW1wdXRhdGlvbiBtZXRob2RzIHdlIGRvIGEgdmVyeSBiYXNpYyBtZWFuIGltcHV0YXRpb24gZm9yIG1pc3NpbmcgdmFsdWVzICp3aXRoaW4gZm9sZCouIFRoZSBtZWFucyBmb3IgYWxsIHZhcmlhYmxlcyBmb3IgZWFjaCB0cmFpbmluZyBmb2xkIGFyZSBjb21wdXRlZCBhbmQgdGhlIGNvcnJlc3BvbmRpbmcgdGVzdCBmb2xkIGlzIGltcHV0ZWQgd2l0aCB0aGUgbWVhbiB2YWx1ZSBmcm9tIHRoZSB0cmFpbmluZyBmb2xkLgoKSW4gb3JkZXIgdG8gaW1wdXRlIGZhY3RvciAobm9taW5hbCkgdmFyaWFibGVzLCB3ZSB1c2UgdGhlIG9uZS1ob3QgZW5jb2Rpbmcgb2YgdGhlc2UgdmFyaWFibGVzIGFuZCB0aGVuIGltcHV0ZSBtaXNzaW5nIHZhbHVlcyB3aXRoIHRoZSBtZWFuIG9mIHRoZSBvbmUtaG90IGVuY29kZWQgdmVjdG9yLiBOb3RlIHRoYXQgYHRpbWVgIGFuZCBgZGVsdGFgIGFyZSBub3Qgc3ViamVjdCB0byBpbXB1dGF0aW9uIC0tIGlmIGVpdGhlciBvZiB0aGVzZSB2YXJpYWJsZXMgY29udGFpbmVkIG1pc3NpbmcgdmFsdWVzIHRoZSBlbnRpcmUgcm93IHdhcyByZW1vdmVkIGluIGB2YWxpZGF0ZSgpYC4KCkJlbG93IHdlIHdvdWxkIGltcHV0ZSBtaXNzaW5nIGRhdGEsIGhvd2V2ZXIsIHRoZSBOQUNEIGRhdGFzZXQgd2UgYXJlIHVzaW5nIGhhcyBubyBtaXNzaW5nIGRhdGEgc28gdGhlIGZvbGxvd2luZyBzdGVwIGlzIG5vdCBuZWNlc3NhcnkuCmBgYHtyLCBlY2hvPVRSVUUsaW5jbHVkZT1UUlVFfQppbXB1dGVkRGF0YSA9IG1lYW5JbXB1dGF0aW9uKHRyYWluaW5nQW5kVGVzdGluZ1NldHMpCmBgYAoKIyMjKm5vcm1hbGl6ZVZhcmlhYmxlcyhsaXN0T2ZJbXB1dGVkRGF0YXNldHMpKgoKRm9sbG93aW5nIHZhcmlhYmxlIGltcHV0YXRpb24sIGFsbCB0aGUgdmFyaWFibGVzIGFyZSBub3JtYWxpemVkIChleGNlcHQgYHRpbWVgIGFuZCBgZGVsdGFgKSBieSB0YWtpbmcgdGhlIHNjYWxlcyAobWVhbiBhbmQgc3RhbmRhcmQgZGV2aWF0aW9uKSBvZiB0aGUgdHJhaW5pbmcgc2V0IHZhcmlhYmxlcyBhbmQgbm9ybWFsaXppbmcgYm90aCB0aGUgdHJhaW5pbmcgc2V0IGFuZCB0ZXN0IHNldCB3aXRoIHRob3NlIHNjYWxlcy4gSGVyZSBub3JtYWxpemluZyBmb2xsb3dzIHRoZSBzdGFuZGFyZCBjb252ZW50aW9uIG9mIHN1YnRyYWN0aW5nIHRoZSBtZWFuIGFuZCBkaXZpZGluZyBieSB0aGUgc3RhbmRhcmQgZGV2aWF0aW9uLiBUaGlzIGZ1bmN0aW9uIGFsc28gZG9lcyBzb21lIGNsZWFuaW5nIG9mIHZhcmlhYmxlIG5hbWVzIC0tIGlmIGEgdmFyaWFibGUgbmFtZSBoYXMgYSBjb21tYSBpbiBpdCBmdW5jdGlvbnMgbGF0ZXIgb2Ygd2lsbCBicmVhayBzbyBhbGwgY29tbWFzIGFuZCBvdGhlciBzcGVjaWFsIGNoYXJhY3RlcnMgYXJlIHJlbW92ZWQuCgpCZWxvdyB3ZSBub3JtYWxpemUgdGhlIGRhdGEgYW5kIHNob3cgdGhlIGRpZmZlcmVuY2UgYmV0d2VlbiB0aGUgZGF0YSBiZWZvcmUgYW5kIGFmdGVyIGl0IHdhcyBub3JtYWxpemVkLgpgYGB7cn0KaGVhZChpbXB1dGVkRGF0YSRUcmFpbmluZ1tbMV1dKQpub3JtYWxpemVkRGF0YSA9IG5vcm1hbGl6ZVZhcmlhYmxlcyhpbXB1dGVkRGF0YSkKaGVhZChub3JtYWxpemVkRGF0YSRUcmFpbmluZ1tbMV1dKQpgYGAKCiMjIypjcmVhdGVGb2xkc0FuZE5vcm1hbGl6ZShzdXJ2aXZhbERhdGFzZXQsIG51bWJlck9mRm9sZHMpKgoKU2ltaWxhciB0byBgdmFsaWRhdGVBbmRDbGVhbigpYCBhYm92ZSwgYGNyZWF0ZUZvbGRzQW5kTm9ybWFsaXplKClgIHNlcnZlcyBhcyBhIG1hc3RlciBmdW5jdGlvbiB3aGljaCB3aWxsIHJ1biBgY3JlYXRlRm9sZHNPZkRhdGEoKWAsIGBtZWFuSW1wdXRhdGlvbigpYCwgYW5kIGBub3JtYWxpemVWYXJpYWJsZXMoKWAgYW5kIHdpbGwgcmV0dXJuIGEgbGlzdCBvZiAoMikgaXRlbXMsICgxKSB0aGUgaW5kZXhlcyBvZiB0aGUgdGVzdCBzZXQgYXMgZ2l2ZW4gaW4gU2VjdGlvbiAyLjIuMSwgYW5kICgyKSBhIGxpc3QgY29udGFpbmluZzogKDEpIGEgbGlzdCBvZiBub3JtYWxpemVkIHRyYWluaW5nIGZvbGRzIGFuZCAoMikgYSBsaXN0IG9mIG5vcm1hbGl6ZWQgdGVzdGluZyBmb2xkcy4gCgpIZXJlIHdlIHNob3cgdGhlIGVxdWl2YWxlbmNlIGJldHdlZW4gKip0ZXN0U2V0SW5kZXhzKiogZnJvbSBhYm92ZSBhbmQgdGhlIGZpcnN0IGl0ZW0gb2YgYGNyZWF0ZUZvbGRzQW5kTm9ybWFsaXplKClgICBhbmQgIGVxdWl2YWxlbmNlIG9mICoqbm9ybWFsaXplZERhdGEqKiB3aXRoIHRoZSBzZWNvbmQgaXRlbSBvZiBgY3JlYXRlRm9sZHNBbmROb3JtYWxpemUoKWA6CgpgYGB7cn0Kb3V0cHV0ID0gY3JlYXRlRm9sZHNBbmROb3JtYWxpemUoY2xlYW5lZE5BQ0QsNSkKYWxsLmVxdWFsKHRlc3RTZXRJbmRleHMsIG91dHB1dFtbMV1dKQphbGwuZXF1YWwobm9ybWFsaXplZERhdGEsIG91dHB1dFtbMl1dKQpgYGAKCiNDcmVhdGluZyBJbmRpdmlkdWFsIFN1cnZpdmFsIERpc3RyaWJ1dGlvbiAgTW9kZWxzCgpUaGUgaW50ZW50aW9uIG9mIHRoaXMgd29yayBpcyB0byBleGFtaW5lIGRpZmZlcmVudCBzdXJ2aXZhbCBhbmFseXNpcyBtb2RlbHMgd2hpY2ggY2FuIHByb2R1Y2UgaW5kaXZpZHVhbGl6ZWQgc3Vydml2YWwgZGlzdHJpYnV0aW9ucyBmb3IgZGlmZmVyZW50IHBhdGllbnRzLiBJbiB0b3RhbCB3ZSBleHBsb3JlZCBmaXZlIG1vZGVscyB3aGljaCBwcm9kdWNlZCBpbmRpdmlkdWFsIHN1cnZpdmFsIGRpc3RyaWJ1dGlvbnMgYW5kIG9uZSBtb2RlbCB3aGljaCBnYXZlIGEgcG9wdWxhdGlvbiBiYXNlZCBzdXJ2aXZhbCBjdXJ2ZSAoS2FwbGFuLU1laWVyKS4gQmVsb3cgd2UgZ2l2ZSBhIG1pbmltYWwgZGVzY3JpcHRpb24gZm9yIGVhY2ggbW9kZWwgYW5kIGRlc2NyaWJlIGhvdyB0byBpbXBsZW1lbnQgZWFjaCBpbiB0aGUgY3VycmVudCBjb2RlYmFzZS4KCgojI0thcGxhbi1NZWllciAoS2FwbGFuTWVpZXIuUikKClRoZSBLYXBsYW4tTWVpZXIgKEtNKSBlc3RpbWF0b3Igd2FzIGRldmlzZWQgYXMgYSBub24tcGFyYW1ldHJpYyBhcHByb2FjaCB0byBlc3RpbWF0aW5nIGEgc3Vydml2YWwgY3VydmUgZXZlbiBpbiB0aGUgcHJlc2VuY2Ugb2YgY2Vuc29yZWQgZGF0YSAoc2VlIHRoZSBvcmlnaW5hbCBwYXBlciBbaGVyZV0oaHR0cHM6Ly93ZWIuc3RhbmZvcmQuZWR1L35sdXRpYW4vY291cnNlcGRmL0tNcGFwZXIucGRmKSkuIFRvIGdpdmUgdGhlIEtNIGVzdGltYXRvciB3ZSBmaXJzdCBtYWtlIHRoZSBmb2xsb3dpbmcgZGVmaW5pdGlvbnM6CgoqIFwoXHRhdV9qXCkgLS0gdGhlIFwoal5cdGV4dHJte3RofVwpIGRpc3RpbmN0IGRlYXRoIHRpbWUsCiogXChkX2pcKSAtLSB0aGUgbnVtYmVyIG9mIGRlYXRocyBhdCB0aW1lIFwoXHRhdV9qXCksCiogXChyX2pcKSAtLSB0aGUgbnVtYmVyIG9mIHBhdGllbnRzIHdobyB3ZXJlIGF0IHJpc2sgYXQgdGltZSBcKFx0YXVfalwpLiBOb3RlIHRoaXMgYWxzbyBpbmNsdWRlcyB0aG9zZSB3aG8gZGllZCBhdCBcKFx0YXVfalwpLgoKV2l0aCB0aGVzZSBkZWZpbml0aW9ucywgd2Ugc2F5IHRoZSBwcm9iYWJpbGl0eSBvZiBzdXJ2aXZhbCBhdCB0aW1lIFwodFwpIGlzIGdpdmVuIGJ5IHRoZSBLTSBlc3RpbWF0b3IgYXMKXFtcaGF0e1N9X3tLTX0odCkgPSBccHJvZF97ajogXHRhdV9qIDwgdH0gXGxlZnQoMSAtIFxmcmFje2Rfan17cl9qfVxyaWdodClcXS4KTm90ZSB0aGF0IGhlcmUgS00gZG9lcyBub3QgdGFrZSBpbnRvIGFjY291bnQgYW55IGZlYXR1cmVzIGFuZCB0aHVzIGlzIGNvbnNpZGVyZWQgdG8gYSBub24tcGFyYW1ldHJpYyBtb2RlbC4gVG8gZ2l2ZSBhbiBleGFtcGxlIG9mIHRoZSBLYXBsYW4tTWVpZXIgYXBwcm9hY2ggY29uc2lkZXIgdGhlIGZvbGxvd2luZyBkYXRhOgoKYGBge3IsIGVjaG8gPSBGQUxTRX0KbGlicmFyeShrbml0cikKbGlicmFyeShrYWJsZUV4dHJhKQp0aW1lID0gYygxLDMsNSw3LDkpCmRlbHRhID0gYygxLDAsMSwxLDEpCmRhdCA9IGNiaW5kLmRhdGEuZnJhbWUodGltZSxkZWx0YSkKcm93Lm5hbWVzKGRhdCkgPSBjKCJQYXRpZW50IDEiLCJQYXRpZW50IDIiLCAiUGF0aWVudCAzIiwiUGF0aWVudCA0IiwiUGF0aWVudCA1IikKa2FibGUodChkYXQpLCBjYXB0aW9uID0gIkV4YW1wbGUgZGF0YSBmb3IgdGhlIEthcGxhbi1NZWllciBtb2RlbC4gTm90ZSB0aGUgZGF0YSBpcyB0cmFuc3Bvc2VkIGZyb20gdGhlIG5vcm1hbCBmb3JtYXQuIiwgZm9ybWF0ID0gImh0bWwiLCBhbGlnbiA9IGMoJ2MnLCdjJywnYycpKSAlPiUKICAgIGthYmxlX3N0eWxpbmcoYm9vdHN0cmFwX29wdGlvbnMgPSAic3RyaXBlZCIsIGZ1bGxfd2lkdGggPSBGICkKYGBgCgpHaXZlbiB0aGUgZGF0YSBhYm92ZSwgd2UgaGF2ZSBcKFxoYXR7U31fe0tNfSgxKSA9IFxsZWZ0KDEgLSBcZnJhY3sxfXs1fVxyaWdodCkgPSAwLjhcKS4gTm90ZSB0aGF0IG5vIGNoYW5nZSBvY2N1cnMgYXQgXCh0PSAzXCkgc2luY2UgdGhpcyBwYXRpZW50IHdhcyBjZW5zb3JlZCBhbmQgXChcdGF1X2pcKSBvbmx5IHJlZmVycyB0byB1bmlxdWUgKmRlYXRoKiB0aW1lcy4gQmVsb3cgd2UgZ2l2ZSB0aGUgZnVsbCBLTSBjdXJ2ZSBmb3IgdGhpcyBleGFtcGxlLgoKPGNlbnRlcj4KYGBge3IsIGVjaG8gPSBGQUxTRSxtZXNzYWdlPUZBTFNFLCBmaWcuaGVpZ2h0PTN9CmxpYnJhcnkoc3Vydml2YWwpCmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShzdXJ2bWluZXIpCmZpdCA9IHN1cnZmaXQoU3Vydih0aW1lLGRlbHRhKX4xLCBkYXRhID0gZGF0KQpwID0gZ2dzdXJ2cGxvdChmaXQsIGRhdGEgPSBkYXQsIGNvbmYuaW50ID0gRiwgbGVnZW5kID0gIm5vbmUiLCBwYWxldHRlID0gImJsYWNrIiwgY2Vuc29yLnNpemUgPSAxMCkKcCRwbG90IApgYGAKPGZpZ2NhcHRpb24+RmlndXJlIDI6IEthcGxhbi1NZWllciBwbG90IHVzaW5nIGV4YW1wbGUgZGF0YS4gQ2Vuc29yZWQgcGF0aWVudHMgaW5kaWNhdGVkIGJ5IHZlcnRpY2FsIGRhc2ggb24gc3Vydml2YWwgY3VydmUuPC9maWdjYXB0aW9uPgo8L2NlbnRlcj4KClNpbmNlIHRoZSBLTSBtb2RlbCBkb2VzIG5vdCBhY2NvdW50IGZvciBpbmRpdmlkdWFsIGZlYXR1cmVzLCB0aGUgc3Vydml2YWwgZGlzdHJpYnV0aW9uIGdlbmVyYXRlZCBpcyBzdXBwb3NlZCB0byByZXByZXNlbnQgdGhlIHBvcHVsYXRpb24ncyBvdmVyYWxsIHN1cnZpdmFsIGN1cnZlLCAqaS5lLiogdGhlIGF2ZXJhZ2Ugc3Vydml2YWwgY3VydmUuIFNvIGdpdmVuIFwoS1wpLWZvbGQgY3Jvc3MtdmFsaWRhdGlvbiwgdGhlcmUgd2lsbCBvbmx5IGJlIFwoS1wpIGRpZmZlcmVudCBzdXJ2aXZhbCBjdXJ2ZXMgYXMgb3Bwb3NlZCB0byB0aGUgXChOXCkgZGlmZmVyZW50IHN1cnZpdmFsIGN1cnZlcyB0aGUgb3RoZXIgbW9kZWxzIGdlbmVyYXRlLCBnaXZlbiBcKE5cKSB1bmlxdWUgcGF0aWVudHMuIE5leHQgd2UgZ2l2ZSB0aGUgZGV0YWlscyBmb3IgcnVubmluZyBLTSBvbiBhIGRhdGFzZXQgYWZ0ZXIgaXQgaGFzIHByZXBvc3Nlc3NlZCBieSBgdmFsaWRhdGVBbmRDbGVhbigpYCBhbmQgYGNyZWF0ZUZvbGRzQW5kTm9ybWFsaXplKClgLgoKIyMjS2FwbGFuLU1laWVyIENvZGUgKEthcGxhbk1laWVyLlIpCgoqKkZpbGUgUHVycG9zZSoqOiBVc2UgdGhlIEthcGxhbi1NZWllciBtb2RlbCB0aGF0IGhhcyBiZWVuIHByb2Nlc3NlZCBhbmQgc3BsaXQgaW50byBmb2xkcyBieSBgY3JlYXRlRm9sZHNBbmROb3JtYWxpemUoKWAuCgoqKkZpbGUgUGF0aCoqOiAiSVNERXZhbHVhdGlvbi9Nb2RlbHMvS2FwbGFuTWVpZXIuUiIuCgoqKkZ1bmN0aW9ucyoqOgoKMS4gYEtNKHRyYWluaW5nLCB0ZXN0aW5nKWAKCioqQXJndW1lbnRzKio6CgoqICp0cmFpbmluZyo6IFRoZSB0cmFpbmluZyBzZXQuCiogKnRlc3RpbmcqOiBUaGUgdGVzdGluZyBzZXQuCgoqKlBhY2thZ2VzIFJlcXVpcmVkKio6CgoxLiBgc3Vydml2YWxgIC0tIFdlIHJlcXVpcmUgdGhlIGBTdXJ2KClgIGZ1bmN0aW9uIHRvIGNoYW5nZSB0aGUgZGF0YSBpbnRvIGEgc3Vydml2YWwgZm9ybWF0LgoyLiBgcHJvZGxpbWAgLS0gcHJvZGxpbSBjb250YWlucyBhIEthcGxhbi1NZWllciBpbXBsZW1lbnRhdGlvbiB0aGF0IGluY2x1ZGVzIGEgYHByZWRpY3QoKWAgZnVuY3Rpb24uCgoqKkZpbGUgTm90ZXMqKjoKCkdpdmVuIHRoZSBsaXN0IG9mIGRhdGFzZXRzIGdlbmVyYXRlZCBieSBgY3JlYXRlRm9sZHNBbmROb3JtYWxpemUoKWAsIGBLTSgpYCB3aWxsIHRha2UgYSBzaW5nbGUgdHJhaW5pbmcgYW5kIHRlc3Rpbmcgc2V0IGFuZCBidWlsZCB0aGUgS2FwbGFuLU1laWVyIGN1cnZlIGZvciB0aGUgdHJhaW5pbmcgc2V0IHVzaW5nIHRoZSBgcHJvZGxpbSgpYCBmdW5jdGlvbiBmcm9tIHRoZSBgcHJvZGxpbWAgcGFja2FnZS4gVGhlIG91dHB1dCAoYWxvbmcgd2l0aCB0aGUgb3V0cHV0IG9mIGFsbCBmb2xsb3dpbmcgbW9kZWxzKSBpcyBhIGxpc3Qgb2YgZm91ciBvYmplY3RzOgoKMS4gVGVzdEN1cnZlczogVGhlIHN1cnZpdmFsIGN1cnZlcyBmb3IgdGVzdCBwYXRpZW50cyAoaWRlbnRpY2FsIGZvciBhbGwgcGF0aWVudHMgZm9yIEtNKS4KMi4gVGVzdERhdGE6IFRoZSB2YWx1ZXMgb2YgYHRpbWVgIGFuZCBgZGVsdGFgIGZvciB0aGUgdGVzdCBwYXRpZW50cyAodXNlZCBsYXRlciB3aGVuIGV2YWx1YXRpbmcgc3Vydml2YWwgY3VydmVzKS4KMy4gVHJhaW5EYXRhOiBUaGUgdmFsdWVzIG9mIGB0aW1lYCBhbmQgYGRlbHRhYCBmb3IgdGhlIHRyYWluaW5nIHBhdGllbnRzICh1c2VkIGxhdGVyIHdoZW4gZXZhbHVhdGluZyBzdXJ2aXZhbCBjdXJ2ZXMpLgo0LiBUcmFpbkN1cnZlczogVGhlIHN1cnZpdmFsIGN1cnZlcyBmb3IgdGhlIHRyYWluaW5nIHBhdGllbnRzLgoKIE5vdGUgdGhlIG91dHB1dCBpcyB0aGUgc2FtZSBmb3IgKiphbGwqKiBmb2xsb3dpbmcgbW9kZWxzLgoKKipFeGFtcGxlIFVzYWdlKioKCkhlcmUsIHdlIGdpdmUgYW4gZXhhbXBsZSBvZiB1c2luZyBLTSBvbiBhIHNpbmdsZSB0cmFpbmluZy90ZXN0aW5nIGZvbGQgLS0gZXh0ZW5kaW5nIHRoZSByZXN1bHRzIHRvIGFsbCBmb2xkcyBjYW4gYmUgYWNjb21wbGlzaGVkIGJ5IHVzaW5nIGEgZm9yIGxvb3AuIAoKCmBgYHtyLCB3YXJuaW5nPUZBTFNFfQp2YWxpZGF0ZWRBbmRDbGVhbmVkID0gdmFsaWRhdGVBbmRDbGVhbihzdXJ2aXZhbERhdGFzZXQgPSBOQUNEKQpmb2xkcyA9IGNyZWF0ZUZvbGRzQW5kTm9ybWFsaXplKHN1cnZpdmFsRGF0YXNldCA9IHZhbGlkYXRlZEFuZENsZWFuZWQsIG51bWJlck9mRm9sZHMgPSA1KQojUmVjYWxsIHRoZSBmaXJzdCBhcmd1bWVudCBvZiBmb2xkcyBpcyB0aGUgaW5kZXhzIG9mIHRoZSB0ZXN0IHNldCBvbiB2YWxpZGF0ZWRBbmRDbGVhbmVkIGFuZCB0aGUgc2Vjb25kIGFyZ3VtZW50IGlzIHRoZSBsaXN0IG9mIHRyYWluaW5nIGFuZCB0ZXN0aW5nIHNldHMuCnRyYWluQW5kVGVzdCA9IGZvbGRzW1syXV0KdHJhaW5pbmcxc3RGb2xkID0gdHJhaW5BbmRUZXN0JFRyYWluaW5nW1sxXV0KdGVzdGluZzFzdEZvbGQgPSB0cmFpbkFuZFRlc3QkVGVzdGluZ1tbMV1dCmttTW9kID0gS00odHJhaW5pbmcxc3RGb2xkLCB0ZXN0aW5nMXN0Rm9sZCkKdGVzdEN1cnZlcyA9IGttTW9kJFRlc3RDdXJ2ZXMKdGVzdERhdCA9IGttTW9kJFRlc3REYXRhCnRyYWluRGF0ID0ga21Nb2QkVHJhaW5EYXRhCnRlc3RDdXJ2ZXMKYGBgCgpBYm92ZSB5b3Ugd2lsbCBzZWUgdGhhdCB0aGUgc3Vydml2YWwgY3VydmVzIGZvciBhbGwgcGF0aWVudHMgYXJlIHRoZSBleGFjdCBzYW1lIC0tIHRoZSBkZXNpcmVkIGVmZmVjdCBmb3IgdGhlIEthcGxhbi1NZWllciBtb2RlbC4gTm90ZSB0aGF0IGB0aW1lYCB3aWxsIGNvcnJlc3BvbmQgdG8gYWxsIHRoZSBldmVudCB0aW1lcyAoYm90aCBkZWF0aCBhbmQgY2Vuc29yZWQgZXZlbnQgdGltZXMpIG9mIHRoZSB0cmFpbmluZyBkYXRhLiBBZGRpdGlvbmFsbHkgd2UgaW5jbHVkZSBhIGB0aW1lYCA9IDAgcG9pbnQgd2hlcmUgdGhlIHN1cnZpdmFsIHByb2JhYmlsaXR5IHdpbGwgYmUgMSAtLSB0aGlzIGlzIGNvbnNpc3RlbnQgYWNyb3NzIGFsbCBtb2RlbHMuIAoKRm9yIGEgbW9yZSBwaWN0dXJlc3F1ZSBhcHByb2FjaCwgd2UgZ2l2ZSB0aGUgc3Vydml2YWwgY3VydmUgYmVsb3c6Cgo8Y2VudGVyPgpgYGB7ciwgZWNobyA9IEZBTFNFfQpwbG90U3Vydml2YWxDdXJ2ZXModGVzdEN1cnZlcywgMToxMCkKYGBgCjwvY2VudGVyPgoKT25lIHNob3VsZCBub3RpY2UgdGhhdCB0aGUgc3Vydml2YWwgY3VydmUgZG9lcyBub3QgZHJvcCB0byB6ZXJvIGFuZCBpbnN0ZWFkIGVuZHMgYXQgXChcYXBwcm94IDAuMDVcKSAtLSB0aGlzIGlzIGJlY2F1c2UgdGhlIGxhc3QgcGF0aWVudCBpbiB0aGUgdHJhaW5pbmcgZGF0YSB3YXMgY2Vuc29yZWQuIEFkZGl0aW9uYWxseSwgdGhlIGFib3ZlIHBsb3QgZG9lcyBub3QgZm9sbG93IHRoZSBjbGFzc2ljYWwgS2FwbGFuLU1laWVyIHN0ZXAgZnVuY3Rpb24gYW5kIGlzIHNtb290aGVyIHVzaW5nIGEgc3BsaW5lIGZpdCAtLSB0aGUgZGV0YWlscyBjYW4gYmUgZm91bmQgaW4gW1NlY3Rpb24gNy4xXSgjcGxvdHRpbmcpIHdoaWNoIGRlc2NyaWJlcyB0aGUgcGxvdHRpbmcgZnVuY3Rpb24uCgpTbyBmYXIgd2UgaGF2ZSBzaG93biB0aGUgb3V0cHV0IG9mICoqY3VydmVzKiogYnV0IGlnbm9yZWQgKip0cmFpbkRhdCoqIGFuZCAqKnRlc3REYXQqKiB3aGljaCBhcmUganVzdCB0aGUgdHJhaW5pbmcgYW5kIHRlc3RpbmcgdmFsdWVzIGZvciBgdGltZWAgYW5kIGBkZWx0YWAuIEFjcm9zcyBhbGwgdGhlIGZvbGxvd2luZyBtb2RlbHMsICoqdHJhaW5EYXQqKiBhbmQgKip0ZXN0RGF0Kiogd2lsbCBiZSB0aGUgc2FtZSB0aGluZywgYnV0IGZvciBjbGFyaXR5IHdlIHdpbGwgaW5jbHVkZSB0aGVtIGJlbG93IGFuZCBub3QgaW4gZm9sbG93aW5nIG1vZGVscy4KCjxjZW50ZXI+Kip0cmFpbkRhdCoqPC9jZW50ZXI+CmBgYHtyLGVjaG89RkFMU0V9CmthYmxlKHRyYWluRGF0LCBmb3JtYXQgPSAiaHRtbCIsIGFsaWduID0gYygnYycsJ2MnLCdjJykpICU+JQogICAga2FibGVfc3R5bGluZyhib290c3RyYXBfb3B0aW9ucyA9ICJzdHJpcGVkIiwgcG9zaXRpb24gPSAiY2VudGVyIikgJT4lIAogICAgICBzY3JvbGxfYm94KHdpZHRoID0gIjY1JSIsIGhlaWdodCA9ICIyMDBweCIsZXh0cmFfY3NzID0gIm1hcmdpbjogYXV0bzsiKSAKYGBgCgo8YnI+PGJyPgoKPGNlbnRlcj4qKnRlc3REYXQqKjwvY2VudGVyPgoKYGBge3IsIGVjaG89RkFMU0V9CmthYmxlKHRlc3REYXQsIGZvcm1hdCA9ICJodG1sIiwgYWxpZ24gPSBjKCdjJywnYycsJ2MnKSkgJT4lCiAgICBrYWJsZV9zdHlsaW5nKGJvb3RzdHJhcF9vcHRpb25zID0gInN0cmlwZWQiLCBwb3NpdGlvbiA9ICJjZW50ZXIiKSAlPiUgCiAgICAgIHNjcm9sbF9ib3god2lkdGggPSAiNjUlIiwgaGVpZ2h0ID0gIjIwMHB4IixleHRyYV9jc3MgPSAibWFyZ2luOiBhdXRvOyIpIApgYGAKCiMjQWNjZWxlcmF0ZWQgRmFpbHVyZSBUaW1lIAoKVGhlIEFjY2VsZXJhdGVkIEZhaWx1cmUgVGltZSAoQUZUKSBoYXMgYmVlbiBhIGNvbW1vbmx5IHVzZWQgc3Vydml2YWwgYW5hbHlzaXMgdGVjaG5pcXVlIGZvciBmaW5kaW5nIHNpZ25pZmljYW50IGZlYXR1cmVzIGluIGEgc3Vydml2YWwgc2V0dGluZy4gVGhlIG1vZGVsIGlzIG1vdGl2YXRlZCBieSBhc3N1bWluZyBzdXJ2aXZhbCB0aW1lLCBcKFRcKSBmb2xsb3dzIHNvbWUgZGlzdHJpYnV0aW9uIHdpdGggZGVuc2l0eSBmdW5jdGlvbiBcKGYodClcKSBhbmQgc3Vydml2YWwgZnVuY3Rpb24gXChTKHQpXCkuIENvbW1vbiBBRlQgZGlzdHJpYnV0aW9ucyBhcmUgbG9nLWxvZ2lzdGljLCBXZWlidWxsLCBsb2ctbm9ybWFsLCBleHBvbmVudGlhbCBhbmQgbW9yZS4gV2hpbGUgd2UgbWFrZSBhc3N1bXB0aW9ucyBvbiB0aGUgZGlzdHJpYnV0aW9uIG9mIFwoVFwpLCBpbmZlcmVuY2UgaXMgaW5zdGVhZCBtYWRlIG9uIFwoXGxvZyhUKVwpLCB0aGF0IGlzLCBmb3IgYSBwYXRpZW50LCBcKFxtYXRoYmZ7eF9pfVwpIHdoZXJlIFwoaSA9IDEsMixcbGRvdHMgTlwpLCBBRlQgbW9kZWxzIGZvbGxvdyB0aGUgZXF1YXRpb246ClxbCmxvZyhUX2kpID0gXGJvbGRzeW1ib2x7d31eVFxib2xkc3ltYm9se3hfaX0gKyBcc2lnbWFcZXBzaWxvbl9pLApcXQp3aGVyZSBcKFxib2xkc3ltYm9se3d9XCkgYXJlIGZlYXR1cmUgd2VpZ2h0cywgXChcc2lnbWFcKSBpcyBjYWxsZWQgYSBzY2FsZSBwYXJhbWV0ZXIgYW5kIFwoXGVwc2lsb25faVwpIGlzIGFuIGluZGVwZW5kZW50LCBpZGVudGljYWxseSBkaXN0cmlidXRlZCBub2lzZSBwYXJhbWV0ZXIgZGVwZW5kZW50IG9uIHRoZSBhc3N1bWVkIGRpc3RyaWJ1dGlvbiBvZiBcKFRcKS4gVGhpcyBpcyBlc3NlbnRpYWxseSBhIG5vcm1hbCBsaW5lYXIgcmVncmVzc2lvbiBidXQgb24gdGhlIGxvZyBzY2FsZSBvZiBcKFRcKSwgKmkuZS4qIGZlYXR1cmVzIGFyZSBhc3N1bWVkIHRvIGhhdmUgYSBsaW5lYXIgcmVsYXRpb25zaGlwIHdpdGggdGhlIGxvZyBvZiBzdXJ2aXZhbCB0aW1lLiBIZXJlLCBcKFxzaWdtYVwpIGlzIGFzc3VtZWQgdG8gYmUgY29uc3RhbnQgYWNyb3NzIGFsbCBwYXRpZW50cyBhbmQgY2FuIGJlIGxlYXJuZWQgYnkgbWF4aW11bSBsaWtlbGlob29kIGFsb25nIHdpdGggdGhlIHdlaWdodHMgXChcYm9sZHN5bWJvbHt3fVwpLiAKCiMjI0FjY2VsZXJhdGVkIEZhaWx1cmUgVGltZSBDb2RlIChBY2NlbGVyYXRlZEZhaWx1cmVUaW1lLlIpCgoqKkZpbGUgUHVycG9zZSoqOiBBcHBseSB0aGUgQWNjZWxlcmF0ZWQgRmFpbHVyZSBUaW1lIG1vZGVsIChmb3IgYSBudW1iZXIgb2YgZGlzdHJpYnV0aW9ucykgdG8gZGF0YSB0aGF0IGhhcyBiZWVuIHByb2Nlc3NlZCBhbmQgc3BsaXQgaW50byBmb2xkcyBieSBgY3JlYXRlRm9sZHNBbmROb3JtYWxpemUoKWAuCgoqKkZpbGUgUGF0aCoqOiAiSVNERXZhbHVhdGlvbi9Nb2RlbHMvQWNjZWxlcmF0ZWRGYWlsdXJlVGltZS5SIi4KCioqRnVuY3Rpb25zKio6CgoxLiBgQUZUKHRyYWluaW5nLCB0ZXN0aW5nLCBBRlREaXN0cmlidXRpb24pYAoyLiBgc3VydmZ1bmMob2JqZWN0LHQsbmV3ZGF0YSwgbmFtZSlgIC0gVXNlZCB0byBleHRyYWN0IHN1cnZpdmFsIGN1cmVzIGZyb20gYW4gQUZUIG1vZGVsLiBBbHRlcmVkIGZyb20gW2EgZnVuY3Rpb24gcHJldmlvdXNseSB3cml0dGVuIGJ5IFRpbW90aHkgSm9obnNvbi5dKGh0dHA6Ly9yc3R1ZGlvLXB1YnMtc3RhdGljLnMzLmFtYXpvbmF3cy5jb20vMTYxMjAzXzZlZTc0M2ViMjhkZjRjZDY4MDg5YTUxOWFhNzA1MTIzLmh0bWwpCgoKKipBcmd1bWVudHMqKjoKCiogKnRyYWluaW5nKjogVGhlIHRyYWluaW5nIHNldC4KKiAqdGVzdGluZyo6IFRoZSB0ZXN0aW5nIHNldC4KKiAqQUZURGlzdHJpYnV0aW9uKjogVGhlIGRpc3RyaWJ1dGlvbiBmb3IgdGhlIEFGVCBtb2RlbC4gUG9zc2libGUgZGlzdHJpYnV0aW9uczogKmV4cG9uZW50aWFsLCB3ZWlidWxsLCBsb2dub3JtYWwsIGdhdXNzaWFuLCBsb2dsb2dpc3RpYywgbG9naXN0aWMqLgoqICpvYmplY3QqOiBBIHN1cnZyZWcgb2JqZWN0IGZyb20gdGhlIGBzdXJ2cmVnKClgIGZ1bmN0aW9uLgoqICp0KjogVGhlIHRpbWVzIGZvciB3aGljaCB0byBldmFsdWF0ZSB0aGUgQUZUIG1vZGVsLgoqICpuZXdkYXRhKjogVGhlIHRlc3RpbmcgZGF0YSB0byBwcmVkaWN0IHdpdGggdXNpbmcgdGhlIEFGVCBtb2RlbC4KKiAqbmFtZSo6IFRoZSBuYW1lIG9mIHRoZSB2YXJpYWJsZSBjb250YWluaW5nIHRoZSBwcmVkaWN0ZWQgdGltZXMgKCp0KikuCgoqKlBhY2thZ2VzIFJlcXVpcmVkKio6CgoxLiBgc3Vydml2YWxgIC0tIFdlIHVzZSB0aGUgYHN1cnZyZWcoKWAgZnVuY3Rpb24gdG8gcGVyZm9ybSBhbmFseXNpcyBmb3IgQUZUIG1vZGVscy4KCioqRmlsZSBOb3RlcyoqOgoKVGhlIHByaW1hcnkgZnVuY3Rpb24gZm9yIHRoaXMgZmlsZSBpcyBgQUZUKClgOyBgc3VydmZ1bmMoKWAgaXMgYSBoZWxwZXIgZnVuY3Rpb24gQUZUIHVzZXMgdG8gYnVpbGQgc3Vydml2YWwgY3VydmVzIGZvciB0ZXN0IGRhdGEgYWZ0ZXIgdGhlIG1vZGVsIGhhcyBiZWVuIGxlYXJuZWQuIAoKR2l2ZW4gdGhlIGxpc3Qgb2YgZGF0YXNldHMgZ2VuZXJhdGVkIGJ5IGBjcmVhdGVGb2xkc0FuZE5vcm1hbGl6ZSgpYCwgYEFGVCgpYCB3aWxsIHRha2UgYSBzaW5nbGUgdHJhaW5pbmcgc2V0LCB0ZXN0aW5nIHNldCwgYW5kIGEgc3BlY2lmaWVkIGRpc3RyaWJ1dGlvbiBhbmQgbGVhcm4gdGhlIEFGVCBtb2RlbCB1c2luZyB0aGUgdHJhaW5pbmcgc2V0LiBPbmx5IG9uZSBkaXN0cmlidXRpb24gY2FuIGJlIHRlc3RlZCBhdCBhIHRpbWUuIEluIGFkZGl0aW9uLCB0aGUgY29kZSBjb250YWlucyBhIHRyeUNhdGNoIGNvbW1hbmQgdGhlIGNhdGNoIHRoZSBldmVudCB3aGVyZSB0aGUgQUZUIG1vZGVsIGZhaWxzIHRvIHJ1biwgaG93ZXZlciwgdGhpcyBvbmx5IHNob3dlZCB0byBvY2N1ciBmb3IgaGlnaCBkaW1lbnNpb25hbCBkYXRhc2V0cyAod2hlbiBubyBmZWF0dXJlIHNlbGVjdGlvbiB3YXMgYXBwbGllZCkuIAoKQXMgd2l0aCB0aGUgS2FwbGFuLU1laWVyIG1vZGVsLCB0aGUgb3V0cHV0IGlzIGEgbGlzdCBvZiBmb3VyIGl0ZW1zOgoKMS4gVGVzdEN1cnZlczogVGhlIHN1cnZpdmFsIGN1cnZlcyBmb3IgdGVzdCBwYXRpZW50cyAoaWRlbnRpY2FsIGZvciBhbGwgcGF0aWVudHMgZm9yIEtNKS4KMi4gVGVzdERhdGE6IFRoZSB2YWx1ZXMgb2YgYHRpbWVgIGFuZCBgZGVsdGFgIGZvciB0aGUgdGVzdCBwYXRpZW50cyAodXNlZCBsYXRlciB3aGVuIGV2YWx1YXRpbmcgc3Vydml2YWwgY3VydmVzKS4KMy4gVHJhaW5EYXRhOiBUaGUgdmFsdWVzIG9mIGB0aW1lYCBhbmQgYGRlbHRhYCBmb3IgdGhlIHRyYWluaW5nIHBhdGllbnRzICh1c2VkIGxhdGVyIHdoZW4gZXZhbHVhdGluZyBzdXJ2aXZhbCBjdXJ2ZXMpLgo0LiBUcmFpbkN1cnZlczogVGhlIHN1cnZpdmFsIGN1cnZlcyBmb3IgdGhlIHRyYWluaW5nIHBhdGllbnRzLgoKKipFeGFtcGxlIFVzYWdlOioqCgpgYGB7ciwgd2FybmluZz1GQUxTRX0KdmFsaWRhdGVkQW5kQ2xlYW5lZCA9IHZhbGlkYXRlQW5kQ2xlYW4oc3Vydml2YWxEYXRhc2V0ID0gTkFDRCkKZm9sZHMgPSBjcmVhdGVGb2xkc0FuZE5vcm1hbGl6ZShzdXJ2aXZhbERhdGFzZXQgPSB2YWxpZGF0ZWRBbmRDbGVhbmVkLCBudW1iZXJPZkZvbGRzID0gNSkKdHJhaW5BbmRUZXN0ID0gZm9sZHNbWzJdXQp0cmFpbmluZzFzdEZvbGQgPSB0cmFpbkFuZFRlc3QkVHJhaW5pbmdbWzFdXQp0ZXN0aW5nMXN0Rm9sZCA9IHRyYWluQW5kVGVzdCRUZXN0aW5nW1sxXV0KQUZUTW9kID0gQUZUKHRyYWluaW5nMXN0Rm9sZCwgdGVzdGluZzFzdEZvbGQsICJ3ZWlidWxsIikKY3VydmVzID0gQUZUTW9kJFRlc3RDdXJ2ZXMKY3VydmVzCmBgYAoKPGJyPgoKPGNlbnRlcj4KYGBge3IsIGVjaG89RkFMU0V9CnBsb3RTdXJ2aXZhbEN1cnZlcyhjdXJ2ZXMsIDE6MTApCmBgYAo8L2NlbnRlcj4KCgojI0NveC1Qcm9wb3J0aW9uYWwgSGF6YXJkcyB3aXRoIEthbGJmbGVpc2NoLVByZW50aWNlIEV4dGVuc2lvbgoKVGhlIENveCBwcm9wb3J0aW9uYWwtaGF6YXJkcyAoQ294LVBIKSBtb2RlbCBpcyBleHRyZW1lbHkgY29tbW9uIGluIHRoZSBzdXJ2aXZhbCBhbmFseXNpcyBsaXRlcmF0dXJlLiBQcmV2aW91c2x5LCB3ZSBtZW50aW9uZWQgdGhlIEthcGxhbi1NZWllciBtb2RlbCB3aGljaCBtb2RlbGVkIHRoZSBzdXJ2aXZhbCBmdW5jdGlvbiwgXChTX3tLTX0odClcKSBvZiBhIGdyb3VwIG9mIHBhdGllbnRzLiBOZXh0LCB0aGUgQWNjZWxlcmF0ZWQgRmFpbHVyZSBUaW1lIG1vZGVsIGFzc3VtZWQgYSBkaXN0cmlidXRpb24gb24gdGhlIHN1cnZpdmFsIHRpbWUsIFwoVFwpLCB3aGljaCBhbGxvd2VkIHVzIHRvIGluY2x1ZGUgZmVhdHVyZXMgYW5kIGJ1aWxkIGN1cnZlcyBmb3IgaW5kaXZpZHVhbCBwYXRpZW50cy4gSW5zdGVhZCwgQ294LVBIIG1vZGVscyB0aGUgKmhhemFyZCBmdW5jdGlvbiogb2YgcGF0aWVudHMsIHdoZXJlIGhhemFyZCBpcyBpbnRlcnByZXRlZCBhcyB0aGUgcmlzayBvZiBmYWlsdXJlIChkeWluZykgYXQgYW55IGdpdmVuIHRpbWUsIHNwZWNpZmljYWxseSBmb3IgYSB0aW1lIFwodFwpIGFuZCBjb3ZhcmlhdGVzIFwoXGJvbGRzeW1ib2x7eH1cKSB0aGUgaGF6YXJkIGZ1bmN0aW9uIGlzIGRlZmluZWQgYXMKXFsKaCh0XCx8XCxcYm9sZHN5bWJvbHt4fSkgPSBcbGltX3tcRGVsdGEgdCBccmlnaHRhcnJvdyBcLDB9IFxmcmFje1AodCBcbGVxIFQgPCB0ICsgXERlbHRhIHRcLFwsXCwgfFwsXCxcLCBUIFxnZXEgdCwgXGJvbGRzeW1ib2x7eH0gKX17XERlbHRhIHR9LiAKXF0KCkNveC1QSCBtb2RlbHMgdGhpcyBoYXphcmQgZnVuY3Rpb24gaW4gdGVybXMgb2YgYSBiYXNlbGluZSBoYXphcmQsIFwoaF8wKHQpXCksIGVxdWFsIGZvciBhbGwgcGF0aWVudHMgYW5kIHNjYWxlZCBieSBhIGxlYXJuZWQsIGluZGl2aWR1YWwgcmlzayBkZXBlbmRlbmluZyBvbiBmZWF0dXJlcywgdGhhdCBpcywgXChoKHR8XGJvbGRzeW1ib2x7eH0pID0gaF8wKHQpXCxlXntcYm9sZHN5bWJvbHt3fV5UXGJvbGRzeW1ib2x7eH19XCkuIFRoaXMgaXMgd2h5IHRoZSBtb2RlbCBpcyB0ZXJtZWQgdGhlICpwcm9wb3J0aW9uYWwgaGF6YXJkcyogbW9kZWwsIHRoZSBoYXphcmQgZm9yIG9uZSBwYXRpZW50IGlzIHByb3BvcnRpb25hbCB0byB0aGUgaGF6YXJkIG9mIGFsbCBvdGhlciBwYXRpZW50cywgYnkgYSBzY2FsZSBkZXBlbmRpbmcgb24gaW5kaXZpZHVhbCBmZWF0dXJlcy4KClRvIGxlYXJuIHRoZSB2YWx1ZXMgZm9yIFwoXGJvbGRzeW1ib2x7d31eVFwpIG9uZSBkb2VzIG5vdCBuZWVkIHRvIGtub3cgdGhlIGJhc2VsaW5lIGhhemFyZCBmdW5jdGlvbi4gU3VwcG9zZSBhIHNpbmdsZSBwYXRpZW50IGRpZWQgYXQgdGltZSBcKHRfalwpLiBUaGUgcHJvYmFiaWxpdHkgdGhhdCBwYXRpZW50IFwoaVwpIHdhcyB0aGUgb25lIHdobyBkaWVkIGlzIGdpdmVuIGJ5ClxiZWdpbnthbGlnbip9ClxmcmFje2godF9qXCx8XCxcYm9sZHN5bWJvbHt4X2l9KX17XHN1bV97ayBcaW4gUih0X2opfSBoKHRfalwsfFwsXGJvbGRzeW1ib2x7eF9rfSl9IFxxdWFkICAmPSBccXVhZCBcZnJhY3toXzAodF9qKVwsZV57XGJvbGRzeW1ib2x7d31eVFwsXGJvbGRzeW1ib2x7eF9pfX19e1xzdW1fe2sgXGluIFIodF9qKX0gaF8wKHRfailcLGVee1xib2xkc3ltYm9se3d9XlRcLFxib2xkc3ltYm9se3hfa319fSwgXFwKJj0gXHF1YWQgXGZyYWN7ZV57XGJvbGRzeW1ib2x7d31eVFwsXGJvbGRzeW1ib2x7eF9pfX19e1xzdW1fe2sgXGluIFIodF9qKX0gZV57XGJvbGRzeW1ib2x7d31eVFwsXGJvbGRzeW1ib2x7eF9rfX19LApcZW5ke2FsaWduKn0Kd2hlcmUgXChSKHRfailcKSBpcyB0aGUgc2V0IG9mIHBhdGllbnRzIHN0aWxsIGFsaXZlIChhdCByaXNrKSBhdCB0aW1lIFwodF9qXCkuIFRodXMgdGhlIGxpa2VsaWhvb2QgZm9yIFwoXGJvbGRzeW1ib2x7d31eVFwpIGlzIGRlZmluZWQgYXMKXFsKTFxsZWZ0KFxib2xkc3ltYm9se3d9XlRccmlnaHQpID0gXHByb2RfaiBcZnJhY3tlXntcYm9sZHN5bWJvbHt3fV5UXCxcYm9sZHN5bWJvbHt4X3tpXCwoalwsKX19fX17XHN1bV97ayBcaW4gUih0X2opfSBlXntcYm9sZHN5bWJvbHt3fV5UXCxcYm9sZHN5bWJvbHt4X2t9fX0sClxdCndoZXJlIHN1YnNjcmlwdCBcKGkgKGopXCkgaXMgaW50ZXJwcmV0ZWQgYXMgaXQgaXMgcGF0aWVudCBcKGlcKSB3aG8gZGllZCBhdCB0aW1lIFwoalwpLgoKQnkgdGhpcyBmb3JtdWxhdGlvbiBvZiB0aGUgbGlrZWxpaG9vZCBlcXVhdGlvbiwgd2Ugc2VlIHRoYXQgZmVhdHVyZSB3ZWlnaHRzIGFyZSBpbmRlcGVuZGVudCBvZiB0aW1lLCAqZS5nKiBpbiBhIHN0dWR5IG9mIHN1cnZpdmFsIHBvc3Qgc3VyZ2VyeSwgbGFiIHRlc3QgcmVzdWx0cyBpbW1lZGlhdGVseSBmb2xsb3dpbmcgc3VyZ2VyeSBoYXZlIHRoZSBzYW1lIHdlaWdodCBvbmUgZGF5IGFmdGVyIHN1cmdlcnkgYXMgdGhleSBkbyBhIHllYXIgZnJvbSBzdXJnZXJ5LiAKCldoaWxlIHdlIGFyZSBhYmxlIHRvIGVzdGltYXRlIGZlYXR1cmUgd2VpZ2h0cyB3aXRob3V0IHNwZWNpZnlpbmcgdGhlIGJhc2VsaW5lIGhhemFyZCBmdW5jdGlvbiwgd2UgY2Fubm90IGRldGVybWluZSB0aGUgc3Vydml2YWwgZGlzdHJpYnV0aW9uLiBBIG51bWJlciBvZiBtZXRob2RzIGV4aXN0IGZvciBjYWxjdWxhdGluZyB0aGUgdGhlIGJhc2VsaW5lIGhhemFyZCBhbmQgdGhlcmVmb3JlIHRoZSBzdXJ2aXZhbCBkaXN0cmlidXRpb24gaW5jbHVkaW5nIHRoZSBLYWxiZmxlaXNjaC1QcmVudGljZSBlc3RpbWF0b3Igd2hpY2ggd2UgdXNlIGluIHRoaXMgd29yay4gVGhlIHRoZXNpcyBbIkJhc2VsaW5lIFN1cnZpdmFsIEZ1bmN0aW9uIEVzdGltYXRvciB1bmRlciBQcm9wb3JpdGlvbmFsIEhhemFyZHMgQXNzdW1wdGlvbiJdKGh0dHBzOi8vd3d3Lmdvb2dsZS5jb20vdXJsP3NhPXQmcmN0PWomcT0mZXNyYz1zJnNvdXJjZT13ZWImY2Q9MSZ2ZWQ9MmFoVUtFd2pwbG9hb252VGNBaFdLSjN3S0hibFRBWG9RRmpBQWVnUUlCQkFDJnVybD1odHRwJTNBJTJGJTJGc3RhdC5udWsuZWR1LnR3JTJGZ3JhZHVhdGUlMkY5Nl9wYXBlciUyRk0wOTQ0NDExLnBkZiZ1c2c9QU92VmF3MUxBYldKMmUxTVFMMktJWEJQcUlaQSkgYnkgV2VuZyBwcm92aWRlcyBhIG5pY2UgZGlzY3Vzc2lvbiBvZiB0aGUgQnJlc2xvdyBhbmQgS2FsYmZsZWlzY2gtUHJlbnRpY2UgZXN0aW1hdG9ycy4KCgojIyNDb3gtUHJvcG9ydGlvbmFsIEhhemFyZHMgd2l0aCBLYWxiZmxlaXNjaC1QcmVudGljZSBFeHRlbnNpb24gQ29kZSAoQ294UEhfS1AuUikKCioqRmlsZSBQdXJwb3NlKio6IEFwcGx5IHRoZSBDb3ggUHJvcG9ydGlvbmFsLUhhemFyZHMgbW9kZWwgdG8gZGF0YSB0aGF0IGhhcyBiZWVuIHByb2Nlc3NlZCBhbmQgc3BsaXQgaW50byBmb2xkcyBieSAqY3JlYXRlRm9sZHNBbmROb3JtYWxpemUoKSouCgoqKkZpbGUgUGF0aCoqOiAiSVNERXZhbHVhdGlvbi9Nb2RlbHMvQ294UEhfS1AuUiIuCgoqKkZ1bmN0aW9ucyoqOgoKMS4gYENveFBIX0tQKHRyYWluaW5nLCB0ZXN0aW5nLCBFbGFzdGljTmV0KWAKMi4gYEtQRXN0aW1hdG9yKGxwLCBscFRpbWUsIGNlbnNvclN0YXR1cylgIC0gVXNlZCB0byBlc3RpbWF0ZSB0aGUgS2FsYmZsZWlzY2gtUHJlbnRpY2UgZXN0aW1hdG9yIHdoZW4gZG9pbmcgZWxhc3RpYy1uZXQgQ294LgoKKipBcmd1bWVudHMqKjoKCiogKnRyYWluaW5nKjogVGhlIHRyYWluaW5nIHNldC4KKiAqdGVzdGluZyo6IFRoZSB0ZXN0aW5nIHNldC4KKiAqRWxhc3RpY05ldCo6IEEgQm9vbGVhbiBzcGVjaWZ5aW5nIHdoZXRoZXIgb3Igbm90IHRvCiogKmxwKjogVGhlIGxpbmVhciBwcmVkaWN0aW9ucyAoXChcYm9sZHN5bWJvbHt3fV5UXGJvbGRzeW1ib2x7eH1cKSkgZnJvbSB0aGUgQ294LVBIIG1vZGVsIGZvciB0aGUgdHJhaW5pbmcgZGF0YS4KKiAqbHBUaW1lKjogVGhlIHRyYWluaW5nIGRhdGFzZXQgZXZlbnQgdGltZXMsICppLmUuKiBgdGltZWAuCiogKmNlbnNvclN0YXR1cyo6IFRoZSB0cmFpbmluZyBkYXRhc2V0IGNlbnNvciBzdGF0dXMsICppLmUqIGBkZWx0YWAuCgoqKlBhY2thZ2VzIFJlcXVpcmVkKio6CgoxLiBgc3Vydml2YWxgIC0tIE5lZWRlZCBmb3IgYGNveHBoKClgIGFuZCBgc3VydmZpdCgpYC4KMi4gYGZhc3Rjb3hgIC0tIFNlZSBFbGFzdGljLU5ldCBDb3guCjMuIGBwcm9kbGltYCAtLSBTZWUgRWxhc3RpYy1OZXQgQ294LgoKKipGaWxlIE5vdGVzKio6CgpUaGUgcHJpbWFyeSBmdW5jdGlvbiBmb3IgdGhpcyBmaWxlIGlzIGBDb3hQSF9LUCgpYDsgYEtQRXN0aW1hdG9yKClgIGlzIG9ubHkgdXNlZCBmb3IgdGhlIEVsYXN0aWMtTmV0IENveCBtb2RlbC4KCkdpdmVuIHRoZSBsaXN0IG9mIGRhdGFzZXRzIGdlbmVyYXRlZCBieSBgY3JlYXRlRm9sZHNBbmROb3JtYWxpemUoKWAsIGBDb3hQSF9LUCgpYCB3aWxsIHRha2UgYSBzaW5nbGUgdHJhaW5pbmcgc2V0LCB0ZXN0aW5nIHNldCwgYW5kIGEgQm9vbGVhbiBzcGVjaWZ5aW5nIHdoZXRoZXIgb3Igbm90IHRvIHVzZSB0aGUgZWxhc3RpYy1uZXQgbW9kZWwuIEZvciB0aGlzIHNlY3Rpb24gd2UgYXNzdW1lIEVsYXN0aWNOZXQgPSBGQUxTRSwgdGhhdCBpcyB3ZSBhcmUgTk9UIHVzaW5nIHRoZSBlbGFzdGljIG5ldCBmb3JtdWxhdGlvbi4gVGhlIGNvZGUgdXNlcyBhIHRyeUNhdGNoIGZ1bmN0aW9uIHRvIGF2b2lkIG1vZGVscyB3aGljaCBhcmUgZmFpbGluZyB0byBjb252ZXJnZSAodGhpcyBoYXBwZW5lZCBmb3IgaGlnaCBkaW1lbnNpb25hbCBkYXRhc2V0cykuIFRoZSBDb3gtUEggbW9kZWwgaXMgZml0IHVzaW5nIGBjb3hwaCgpYCBmcm9tIHRoZSBzdXJ2aXZhbCBwYWNrYWdlIGFuZCB0aGVuIHN1cnZpdmFsIGN1cnZlcyBhcmUgZml0IHVzaW5nIGBzdXJ2Zml0KClgIGFuZCBzcGVjaWZ5aW5nIHRoZSB0eXBlIHRvIGJlIHRoZSBLYWxiZmxlaXNjaC1QcmVudGljZSBlc3RpbWF0b3IuIFRpZXMgYXJlIGFjY291bnRlZCBmb3IgdXNpbmcgdGhlIGRlZmF1bHQgb2YgYGNveHBoKClgLCAqaS5lLiogdGhlIEVmcm9uIGFwcHJveGltYXRpb24uCgpUaGUgb3V0cHV0IGlzIHRoZSBzYW1lIGFzIHRoZSBmb3VyIHByZXZpb3VzIG1vZGVscy4KCioqRXhhbXBsZSBVc2FnZToqKgoKYGBge3IsIHdhcm5pbmc9RkFMU0V9CnZhbGlkYXRlZEFuZENsZWFuZWQgPSB2YWxpZGF0ZUFuZENsZWFuKHN1cnZpdmFsRGF0YXNldCA9IE5BQ0QpCmZvbGRzID0gY3JlYXRlRm9sZHNBbmROb3JtYWxpemUoc3Vydml2YWxEYXRhc2V0ID0gdmFsaWRhdGVkQW5kQ2xlYW5lZCwgbnVtYmVyT2ZGb2xkcyA9IDUpCnRyYWluQW5kVGVzdCA9IGZvbGRzW1syXV0KdHJhaW5pbmcxc3RGb2xkID0gdHJhaW5BbmRUZXN0JFRyYWluaW5nW1sxXV0KdGVzdGluZzFzdEZvbGQgPSB0cmFpbkFuZFRlc3QkVGVzdGluZ1tbMV1dCkNveE1vZCA9IENveFBIX0tQKHRyYWluaW5nMXN0Rm9sZCwgdGVzdGluZzFzdEZvbGQsIEVsYXN0aWNOZXQ9RikKY3VydmVzID0gQ294TW9kJFRlc3RDdXJ2ZXMKY3VydmVzCmBgYAo8YnI+Cgo8Y2VudGVyPgpgYGB7ciwgZWNobz1GQUxTRX0KcGxvdFN1cnZpdmFsQ3VydmVzKGN1cnZlcywgMToxMCkKYGBgCjwvY2VudGVyPgoKTm90ZSB0aGUgcHJlY2VkaW5nIEZpZ3VyZSBkZXBpY3RzIHByb3BvcnRpb25hbCBoYXphcmRzIC0tIGFsbCBjdXJ2ZXMgYXJlIHRoZSBleGFjdCBzYW1lIHNoYXBlIGJ1dCBhdCBkaWZmZXJlbnQgaGVpZ2h0cywgKmkuZS4qIHRoZXkgYXJlIHByb3BvcnRpb25hbCB0byBvbmUgYW5vdGhlci4gCgojI0NveC1Qcm9wb3J0aW9uYWwgSGF6YXJkcyB3aXRoIEthbGJmbGVpc2NoLVByZW50aWNlIEV4dGVuc2lvbiAtLSBFbGFzdGljIE5ldAoKV2UgaW5jbHVkZSB0aGUgZWxhc3RpYyBuZXQgdmVyc2lvbiBvZiBDb3gtUEggdG8gaW5jbHVkZSBhIHJlZ3VsYXJpemVkIHZlcnNpb24gb2YgQ294LVBIIHRvIGNvbXBldGUgd2l0aCBvdGhlciBtb2RlbHMgc3VjaCBhcyBSYW5kb20gU3Vydml2YWwgRm9yZXN0cyBhbmQgTXVsdGktVGFzayBMb2dpc3RpYyBSZWdyZXNzaW9uIHdoaWNoIGhhdmUgYnVpbHQgaW4gcmVndWxhcml6YXRpb24uIFRoZSBub3RhdGlvbiBhbmQgaW1wbGVtZW50YXRpb25zIG9mIGVsYXN0aWMgbmV0IGNvbWUgZnJvbSBZYW5nIGFuZCBab3UncyBwYXBlciBlbnRpdGlsZWQgWyJBIENvY2t0YWlsIEFsZ29yaXRobSBmb3IgU29sdmluZyBUaGUgRWxhc3RpYyBOZXQgUGVuYWxpemVkIENveCdzIFJlZ3Jlc3Npb24gaW4gSGlnaCBEaW1lbnNpb25zIiJdKGh0dHBzOi8vd3d3Lmdvb2dsZS5jb20vdXJsP3NhPXQmcmN0PWomcT0mZXNyYz1zJnNvdXJjZT13ZWImY2Q9MSZ2ZWQ9MmFoVUtFd2lHM3JMWXV2VGNBaFVCTjMwS0hmSVFEWVVRRmpBQWVnUUlBQkFDJnVybD1odHRwJTNBJTJGJTJGdXNlcnMuc3RhdC51bW4uZWR1JTJGfnpvdXh4MDE5JTJGUGFwZXJzJTJGZmFzdGNveC5wZGYmdXNnPUFPdlZhdzIxb0c2eGV5ZlRiN2phS0ZCa1lKY3QpLiBUaGUgZWxhc3RpYyBuZXQgcGVuYWx0eSBhZGRzIHRoZSBmb2xsb3dpbmcgdGVybSB0byB0aGUgbGlrZWxpaG9vZCBlcXVhdGlvbiwKXFsKUF97XGxhbWJkYSwgXGFscGhhfShcYm9sZHN5bWJvbHt3fSkgPSBcc3VtX3tqPTF9XnAgXGxhbWJkYVxsZWZ0KFxhbHBoYVwsfHdfanwgKyBcZnJhY3sxfXsyfVwsKDEtXGFscGhhKVwsIHdfal4yIFxyaWdodCksClxdCmZvciBcKHBcKSBmZWF0dXJlcyB3aGVyZSBcKFxsYW1iZGEgPiAwXCkgYW5kIFwoMCA8IFxhbHBoYSBcbGVxIDFcKS4gVGhlIGZpcnN0IHBlbmFsdHkgdGVybSwgXCh8d19qfFwpIGNvcnJlc3BvbmRzIHRvIHRoZSBMQVNTTyBsb3NzIGFuZCBpcyByZXNwb25zaWJsZSBmb3IgZmVhdHVyZSBzZWxlY3Rpb24gd2hlcmVhcyB0aGUgcXVhZHJhdGljIHRlcm0gaXMgdG8gY29udHJvbCB0aGUgc2l6ZSBvZiBmZWF0dXJlIHdlaWdodHMuIEZvciBleGFtcGxlLCBcKFxhbHBoYSA9IDFcKSB3b3VsZCBjb3JyZXNwb25kIHRvIGp1c3QgTEFTU08tQ294LCB3aGVyZWFzIFwoXGFscGhhIFxhcHByb3ggMFwpIHdvdWxkIGNvcnJlc3BvbmQgdG8gUmlkZ2UtQ294LiAKCkZvciBtb3JlIGluZm9ybWF0aW9uIHJlZ2FyZGluZyBlbGFzdGljIG5ldCBDb3ggc2VlIHRoZSBZYW5nIGFuZCBab3UncyBwYXBlciByZWZlcmVuY2VkIGFib3ZlLgoKIyMjQ294LVByb3BvcnRpb25hbCBIYXphcmRzIHdpdGggS2FsYmZsZWlzY2gtUHJlbnRpY2UgRXh0ZW5zaW9uIC0tIEVsYXN0aWMgTmV0IENvZGUgKENveFBIX0tQLlIpCgoqKkZpbGUgUHVycG9zZSoqOiBBcHBseSB0aGUgQ294IFByb3BvcnRpb25hbC1IYXphcmRzIG1vZGVsIHRvIGRhdGEgdGhhdCBoYXMgYmVlbiBwcm9jZXNzZWQgYW5kIHNwbGl0IGludG8gZm9sZHMgYnkgYGNyZWF0ZUZvbGRzQW5kTm9ybWFsaXplKClgLgoKKipGaWxlIFBhdGgqKjogIklTREV2YWx1YXRpb24vTW9kZWxzL0NveFBIX0tQLlIiLgoKKipGdW5jdGlvbnMqKjoKCjEuIGBDb3hQSF9LUCh0cmFpbmluZywgdGVzdGluZywgRWxhc3RpY05ldClgCjIuIGBLUEVzdGltYXRvcihscCwgbHBUaW1lLCBjZW5zb3JTdGF0dXMpYCAtIFVzZWQgdG8gZXN0aW1hdGUgdGhlIEthbGJmbGVpc2NoLVByZW50aWNlIGVzdGltYXRvciB3aGVuIGRvaW5nIGVsYXN0aWMgbmV0IENveC4KCioqQXJndW1lbnRzKio6CgoqICp0cmFpbmluZyo6IFRoZSB0cmFpbmluZyBzZXQuCiogKnRlc3RpbmcqOiBUaGUgdGVzdGluZyBzZXQuCiogKkVsYXN0aWNOZXQqOiBBIEJvb2xlYW4gc3BlY2lmeWluZyB3aGV0aGVyIG9yIG5vdCB0bwoqICpscCo6IFRoZSBsaW5lYXIgcHJlZGljdGlvbnMgKFwoXGJvbGRzeW1ib2x7d31eVFxib2xkc3ltYm9se3h9XCkpIGZyb20gdGhlIENveC1QSCBtb2RlbCBmb3IgdGhlIHRyYWluaW5nIGRhdGEuCiogKmxwVGltZSo6IFRoZSB0cmFpbmluZyBkYXRhc2V0IGV2ZW50IHRpbWVzLCAqaS5lLiogYHRpbWVgLgoqICpjZW5zb3JTdGF0dXMqOiBUaGUgdHJhaW5pbmcgZGF0YXNldCBjZW5zb3Igc3RhdHVzLCAqaS5lKiBgZGVsdGFgLgoKKipQYWNrYWdlcyBSZXF1aXJlZCoqOgoKMS4gYHN1cnZpdmFsYCAtLSBOZWVkZWQgZm9yIHRoZSBub3JtYWwgQ294IGltcGxlbWVudGF0aW9uLgoyLiBgZmFzdGNveGAgLS0gVXNlZCBmb3IgdGhlIGltcGxlbWVudGF0aW9uIG9mIGVsYXN0aWMgbmV0IGNveCAoYGNvY2t0YWlsKClgKQozLiBgcHJvZGxpbWAgLS0gVXNlZCBmb3IgdGhlIGBzaW5kZXgoKSBmdW5jdGlvbiAtLSBzaW5kZXggcmV0dXJucyB0aGUgaW5kZXhlcyBvZiBwb3NpdGlvbnMsIGlkZWFsIGZvciBldmFsdWF0aW5nIHN0ZXAgZnVuY3Rpb25zLgoKKipGaWxlIE5vdGVzKio6CgpHaXZlbiB0aGUgbGlzdCBvZiBkYXRhc2V0cyBnZW5lcmF0ZWQgYnkgYGNyZWF0ZUZvbGRzQW5kTm9ybWFsaXplKClgLCBgQ294UEhfS1AoKWAgd2lsbCB0YWtlIGEgc2luZ2xlIHRyYWluaW5nIHNldCwgdGVzdGluZyBzZXQsIGFuZCBhIEJvb2xlYW4gc3BlY2lmeWluZyB3aGV0aGVyIG9yIG5vdCB0byB1c2UgdGhlIGVsYXN0aWMtbmV0IG1vZGVsLiBGb3IgdGhpcyBzZWN0aW9uIHdlIGFzc3VtZSBFbGFzdGljTmV0ID0gVFJVRSwgdGhhdCBpcyB3ZSBhcmUgdXNpbmcgdGhlIGVsYXN0aWMgbmV0IGZvcm11bGF0aW9uLiBUaGVyZSBhcmUgdHdvIGh5cGVycGFyYW1ldGVycyBpbnZvbHZlZCBmb3IgZWxhc3RpYyBuZXQgQ294LCB0aGUgc3RyZW5ndGggb2YgdGhlIHBlbmFsdHksIFwoXGxhbWJkYVwpLCBhbmQgdGhlIHR5cGUgb2YgcGVuYWx0eSAobW9yZSBMQVNTTyBsaWtlIG9yIG1vcmUgUmlkZ2UgbGlrZSksIFwoXGFscGhhXCkuIFNpeCB2YWx1ZXMgYXJlIHRlc3RlZCBmb3IgXChcYWxwaGFcKSwgMC4wMSwgMC4yLCAwLjQsIDAuNiwgMC44LCBhbmQgMS4wLiBGb3IgZWFjaCBvZiB0aGVzZSB2YWx1ZXMgdGhlIGZ1bmN0aW9uIGBjdi5jb2NrdGFpbCgpYCB0ZXN0cyAxMDAgdmFsdWVzIG9mIFwoXGxhbWJkYVwpIHVzaW5nIDUtZm9sZCBjcm9zcyB2YWxpZGF0aW9uIGFuZCByZXR1cm5zIFwoXGxhbWJkYV97bWlufVwpLCB0aGUgb3B0aW1hbCB2YWx1ZSBvZiBcKFxsYW1iZGFcKSB0byBtaW5pbWl6ZSB0aGUgY3Jvc3MgdmFsaWRhdGlvbiBlcnJvciAoc2VlIHRoZSBgY29ja3RhaWwoKSBmdW5jdGlvbiBvZiB0aGUgW3BhY2thZ2UgZG9jdW1lbnRhdGlvbl0oaHR0cHM6Ly93d3cuZ29vZ2xlLmNvbS91cmw/c2E9dCZyY3Q9aiZxPSZlc3JjPXMmc291cmNlPXdlYiZjZD0yJnZlZD0yYWhVS0V3akNtTUNoeHZUY0FoVU1JalFJSFR1bURvRVFGakFCZWdRSUNSQUMmdXJsPWh0dHBzJTNBJTJGJTJGY3Jhbi5yLXByb2plY3Qub3JnJTJGd2ViJTJGcGFja2FnZXMlMkZmYXN0Y294JTJGZmFzdGNveC5wZGYmdXNnPUFPdlZhdzE4cjRhdjlnYnhBS3doLUx1RVAzajUpKS5UaGUgb3B0aW1hbCB2YWx1ZSBmb3IgXChcYWxwaGFcKSBhbmQgXChcbGFtYmRhXCkgZnJvbSBpbnRlcm5hbCAoMTApIHJlcGVhdGVkIDUtZm9sZCBjcm9zcyB2YWxpZGF0aW9uIGFyZSB0aGVuIHVzZWQgZm9yIGV2YWx1YXRpbmcgdGhlIHRlc3QgZm9sZC4gTm90ZSB0aGUgb3B0aW1hbCB2YWx1ZXMgZm9yIFwoXGFscGhhXCkgYW5kIFwoXGxhbWJkYVwpIHdpbGwgKGxpa2VseSkgZGlmZmVyIGFjcm9zcyBmb2xkcy4KCk9uZSB0aGUgZmVhdHVyZSB3ZWlnaHRzIGhhdmUgYmVlbiBlc3RpbWF0ZWQsIGxpbmVhciBwcmVkaWN0b3JzIGFyZSBwYXNzZWQgdG8gYEtQRXN0aW1hdG9yKClgIHRvIGVzdGltYXRlIHRoZSBzdXJ2aXZhbCBjdXJ2ZXMuIEFzIG9mIG5vdywgdGllcyBhcmUgbm90IGFjY291bnRlZCBmb3IgYW5kIHRoZSByZXN1bHRpbmcgc3Vydml2YWwgZGlzdHJpYnV0aW9uIGFyZSB1c2luZyB0aGUgZXN0aW1hdGUgZGVyaXZlZCBpbiB0aGUgcHJlc2VuY2Ugb2Ygbm8gdGllcy4gRnV0dXJlIGl0ZXJhdGlvbnMgb2YgdGhpcyBjb2RlYmFzZSBzaG91bGQgYWNjb3VudCBmb3IgdGhlIGNvcnJlY3QgZXN0aW1hdGUgd2hlbiBoYW5kbGluZyB0aWVzLgoKVGhlIG91dHB1dCBpcyB0aGUgc2FtZSBhcyBwcmV2aW91cyBtb2RlbHMuCgoqKkV4YW1wbGUgVXNhZ2U6KioKCmBgYHtyLCB3YXJuaW5nPUZBTFNFfQp2YWxpZGF0ZWRBbmRDbGVhbmVkID0gdmFsaWRhdGVBbmRDbGVhbihzdXJ2aXZhbERhdGFzZXQgPSBOQUNEKQpmb2xkcyA9IGNyZWF0ZUZvbGRzQW5kTm9ybWFsaXplKHN1cnZpdmFsRGF0YXNldCA9IHZhbGlkYXRlZEFuZENsZWFuZWQsIG51bWJlck9mRm9sZHMgPSA1KQp0cmFpbkFuZFRlc3QgPSBmb2xkc1tbMl1dCnRyYWluaW5nMXN0Rm9sZCA9IHRyYWluQW5kVGVzdCRUcmFpbmluZ1tbMV1dCnRlc3Rpbmcxc3RGb2xkID0gdHJhaW5BbmRUZXN0JFRlc3RpbmdbWzFdXQpDb3hNb2QgPSBDb3hQSF9LUCh0cmFpbmluZzFzdEZvbGQsIHRlc3Rpbmcxc3RGb2xkLCBFbGFzdGljTmV0PVQpCmN1cnZlcyA9IENveE1vZCRUZXN0Q3VydmVzCmN1cnZlcwpgYGAKPGJyPgoKPGNlbnRlcj4KYGBge3IsIGVjaG89RkFMU0V9CnBsb3RTdXJ2aXZhbEN1cnZlcyhjdXJ2ZXMsIDE6MTApCmBgYAo8L2NlbnRlcj4KCgoKIyNSYW5kb20gU3Vydml2YWwgRm9yZXN0IC0gS2FwbGFuLU1laWVyIEV4dGVuc2lvbiAKCltSYW5kb20gU3Vydml2YWwgRm9yZXN0c10oaHR0cHM6Ly9hcnhpdi5vcmcvcGRmLzA4MTEuMTY0NSkgKFJTRnMpIHdlcmUgZGV2ZWxvcGVkIGJ5IElzaHdhcmFuIGV0IGFsLiBpbiAyMDA4IGFuZCBoYXZlIGJlZW4gZ3Jvd2luZyBpbiBwb3B1bGFyaXR5IGluIHJlY2VudCB5ZWFycy4gV2hpbGUgd2UgZG8gbm90IGdpdmUgdmFzdCBkZXRhaWwgcmVnYXJkaW5nIHRoZSBmb3JtYXRpb24gb2YgdGhlc2UgZm9yZXN0cywgdGhlIFtvcmlnaW5hbCBwYXBlcl0oaHR0cHM6Ly9hcnhpdi5vcmcvcGRmLzA4MTEuMTY0NSkgYW5kIFtSIHBhY2thZ2UgZGV0YWlsc10oaHR0cHM6Ly9rb2dhbHVyLmdpdGh1Yi5pby9yYW5kb21Gb3Jlc3RTUkMvdGhlb3J5Lmh0bWwpIHByb3ZpZGUgYW4gZXhjZWxsZW50IG92ZXJ2aWV3LiAKClRoZSBwcmltYXJ5IGZvY3VzIGZvciBSU0ZzIHdlcmUgdG8gYnVpbGQgYSByaXNrIHNjb3JlIHVzaW5nIGFuIGF2ZXJhZ2VkIGN1bXVsYXRpdmUgaGF6YXJkIGZyb20gdGhlIHRlcm1pbmFsIG5vZGVzIHdoaWNoIGluIHdoaWNoIHBhdGllbnRzIGxhbmRlZC4gQXMgcHJldmlvdXNseSBtZW50aW9uZWQgaGVyZSBhbmQgZXhwbGFpbmVkIGZ1cnRoZXIgaW4gb3VyIHBhcGVyLCByaXNrIHNjb3JlcyBhcmUgdXNlZnVsIGZvciBkaXNjcmltaW5hdGluZyBiZXR3ZWVuIHBhdGllbnRzLCBidXQgZG8gbm90IHByb3ZpZGUgaW5kaXZpZHVhbCBzdXJ2aXZhbCBkaXN0cmlidXRpb25zLldoaWxlIHN1cnZpdmFsIGRpc3RyaWJ1dGlvbnMgYXJlIG5vdCBtZW50aW9uZWQgaW4gdGhlIG9yaWdpbmFsIFJTRiBwYXBlciwgdGhlIFIgcGFja2FnZSBkZXRhaWxzIGJyaWVmbHkgZGVzY3JpYmUgaG93IFJTRnMgYnVpbGQgaW5kaXZpZHVhbCBzdXJ2aXZhbCBkaXN0cmlidXRpb25zIGluIFNlY3Rpb24gOC4xLiBTcGVjaWZpY2FsbHksIHRoZXkgc3RhdGUgc3Vydml2YWwgZXN0aW1hdGVzIGFyZSBjb21wdXRlZCB1c2luZyBLYXBsYW4tTWVpZXIgZXN0aW1hdG9ycy4gSG93ZXZlciwgZ2l2ZW4gYSB0b3RhbCBvZiBcKE1cKSB0cmVlcyBpbiBhIGZvcmVzdCwgdGhlcmUgd2lsbCBiZSBcKE1cKSBLYXBsYW4tTWVpZXIgZXN0aW1hdG9ycyBmb3IgZWFjaCBwYXRpZW50IGFuZCB0aGUgZGV0YWlscyBvZiBob3cgdGhlc2UgS2FwbGFuLU1laWVyIGVzdGltYXRvcnMgd2VyZSBjb21iaW5lZCBhcmUgbm90IGdpdmVuLiBBZnRlciBzb21lIGJhY2t3YXJkcyBlbmdpbmVlcmluZyBJIHdhcyBhYmxlIHRvIGNvbWUgdXAgd2l0aCB0aGUgY29uY2x1c2lvbiB0aGF0IEthcGxhbi1NZWllciBjdXJ2ZXMgYXJlICJhdmVyYWdlZCIgYWNyb3NzIGFsbCB0aGUgS2FwbGFuLU1laWVyIGN1cnZlcyAtIHNlZSBGaWd1cmUgYmVsb3cuCgoKPGNlbnRlcj4KYGBge3IsIGVjaG89RkFMU0V9CnRpbWUxID0gYygzLDUsOCwxMCwxNSkKY2VuczEgPSBjKDEsMSwxLDEsMSkKdGltZTIgPSBjKDYsOSwxMCwxNywyMCkKY2VuczIgPSBjKDEsMSwxLDEsMSkKZGF0ID0gY2JpbmQuZGF0YS5mcmFtZSh0aW1lMSxjZW5zMSx0aW1lMixjZW5zMikKCgpwMSA9IHByb2RsaW0oU3Vydih0aW1lMSxjZW5zMSl+MSxkYXRhID0gZGF0KQpwMiA9IHByb2RsaW0oU3Vydih0aW1lMixjZW5zMil+MSwgZGF0YSA9IGRhdCkKS00xID0gcHJlZGljdChwMSwgc2VxKDAsMjAsYnkgPSAwLjAxKSkgCktNMiA9IHByZWRpY3QocDIsIHNlcSgwLDIwLGJ5ID0gMC4wMSkpCktNMSA9IGlmZWxzZShpcy5uYShLTTEpLDAsS00xKQpLTTIgPSBpZmVsc2UoaXMubmEoS00yKSwwLEtNMikKUktNID0gKEtNMSArIEtNMikvMgoKZGF0MiA9IGNiaW5kLmRhdGEuZnJhbWUodGltZSA9IHNlcSgwLDIwLGJ5PTAuMDEpLCBSS00gPSBSS00sIEtNMSA9IEtNMSwgS00yID0gS00yKQoKZ2dwbG90KGRhdGEgPSBkYXQyKSsKICBnZW9tX2xpbmUoYWVzKHggPSB0aW1lLCB5ID0gS00xKSwgY29sb3IgPSAiZG9kZ2VyYmx1ZTIiLHNpemUgPSAuOCkrCiAgZ2VvbV9saW5lKGFlcyh4ID0gdGltZSwgeSA9IEtNMiksIGNvbG9yID0gImRvZGdlcmJsdWUyIiwgc2l6ZSA9IC44KSsKICBnZW9tX2xpbmUoYWVzKHggPSB0aW1lLCB5ID0gUktNKSwgY29sb3IgPSAib3JhbmdlcmVkMiIsc2l6ZSA9IDEpICsKICB0aGVtZV9idygpKwogIGxhYnMoeCA9ICJUaW1lIix5ID0gIlN1cnZpdmFsIFByb2JhYmlsaXR5IikrCiAgdGhlbWUodGV4dCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTgsIGZhY2UgPSAiYm9sZCIpKQoKYGBgCjwvY2VudGVyPgoKPGZpZ2NhcHRpb24+RmlndXJlOiBEZXBpY3Rpb24gb2YgYXZlcmFnaW5nIHR3byBLYXBsYW4tTWVpZXIgY3VydmVzIHRvZ2V0aGVyIHRvIHByb2R1Y2Ugb25lIGluZGl2aWR1YWwgc3Vydml2YWwgZGlzdHJpYnV0aW9uIGZvciBhIHRlc3QgcGF0aWVudC4gQmx1ZSBLYXBsYW4tTWVpZXIgY3VydmVzIHJlcHJlc2VudCB0aGUgS2FwbGFuLU1laWVyIGVzdGltYXRvcnMgaW4gdHdvIHRlcm1pbmFsIG5vZGVzIHdoZXJlYXMgdGhlIHJlZCBjdXJ2ZSByZXByZXNlbnRzIHRoZSBjb21iaW5hdGlvbiAtIGdpdmluZyBmb3JtIHRvIHRoZSBpbmRpdmlkdWFsIHN1cnZpdmFsIGRpc3RyaWJ1dGlvbi48L2ZpZ2NhcHRpb24+CgojIyNSYW5kb20gU3Vydml2YWwgRm9yZXN0IC0gS2FwbGFuLU1laWVyIEV4dGVuc2lvbiBDb2RlIChSYW5kb21TdXJ2aXZhbEZvcmVzdHMuUikKCioqRmlsZSBQdXJwb3NlKio6IEFwcGx5IHRoZSBSU0YgbW9kZWwgdG8gZGF0YSB0aGF0IGhhcyBiZWVuIHByb2Nlc3NlZCBhbmQgc3BsaXQgaW50byBmb2xkcyBieSAqY3JlYXRlRm9sZHNBbmROb3JtYWxpemUoKSouCgoqKkZpbGUgUGF0aCoqOiAiSVNERXZhbHVhdGlvbi9Nb2RlbHMvUmFuZG9tU3Vydml2YWxGb3Jlc3RzLlIiLgoKKipGdW5jdGlvbnMqKjoKCjEuIGBSU0YodHJhaW5pbmcsIHRlc3RpbmcpYAoyLiBgaW50ZXJuYWxDVl9SU0YodHJhaW5pbmcsIG51bUZvbGRzKWAgLS0gSGVscGVyIGZ1bmN0aW9uIGZvciBSU0YKCioqQXJndW1lbnRzKio6CgoqICp0cmFpbmluZyo6IFRoZSB0cmFpbmluZyBzZXQuCiogKnRlc3RpbmcqOiBUaGUgdGVzdGluZyBzZXQuCiogKm51bUZvbGRzKjogVGhlIG51bWJlciBvZiBmb2xkcyBmb3IgaW50ZXJuYWwgY3Jvc3MgdmFsaWRhdGlvbi4KCioqUGFja2FnZXMgUmVxdWlyZWQqKjoKCjEuIGBzdXJ2aXZhbGAgLS0gTmVlZGVkIGZvciB0aGUgYFN1cnYoKWAgZnVuY3Rpb24KMi4gYHJhbmRvbUZvcmVzdFNSQ2AgLS0gVXNlZCBmb3IgdGhlIFJTRiBpbXBsZW1lbnRhdGlvbi4KCioqRmlsZSBOb3RlcyoqOgoKR2l2ZW4gdGhlIGxpc3Qgb2YgZGF0YXNldHMgZ2VuZXJhdGVkIGJ5IGBjcmVhdGVGb2xkc0FuZE5vcm1hbGl6ZSgpYCwgYFJTRigpYCB3aWxsIHRha2UgYSBzaW5nbGUgdHJhaW5pbmcgc2V0LCB0ZXN0aW5nIHNldCwgYW5kIHRoZSBudW1iZXIgb2YgZm9sZHMgdG8gdXNlIGZvciBpbnRlcm5hbCBjcm9zcyB2YWxpZGF0aW9uIHRvIHNlbGVjdCBoeXBlcnBhcmFtZXRlcnMgKG5hbWVseSBudHJlZSBhbmQgbXRyeSkgYW5kIGJ1aWxkIHRoZSBSU0YgbW9kZWwgdXNpbmcgYHJmc3JjKClgIGZyb20gdGhlIHJhbmRvbUZvcmVzdFNSQyBwYWNrYWdlLiBJbnRlcm5hbCBjcm9zcyB2YWxpZGF0aW9uIGlzIHBlcmZvcm1lZCBmb3IgbnRyZWUgJFxpbiBcezUwMCwxMDAwLCAyMDAwXH0kIGFuZCBtdHJ5IChudW1iZXIgb2YgcGF0aWVudHMgaW4gZWFjaCB0ZXJtaW5hbCBub2RlKSAkXGluIFx7MSwyLDNcfSAkIFRoZW4gdGhlIGBwcmVkaWN0KClgIGZ1bmN0aW9uIGZvciByZnNyYyBpcyB1c2VkIHRvIGJ1aWxkIHN1cnZpdmFsIGN1cnZlcyBmb3IgdGVzdCBwYXRpZW50cy4KClRoZSBvdXRwdXQgaXMgdGhlIHNhbWUgYXMgcHJldmlvdXMgbW9kZWxzLgoKKipFeGFtcGxlIFVzYWdlOioqCgpgYGB7ciwgd2FybmluZz1GQUxTRX0KdmFsaWRhdGVkQW5kQ2xlYW5lZCA9IHZhbGlkYXRlQW5kQ2xlYW4oc3Vydml2YWxEYXRhc2V0ID0gTkFDRCkKZm9sZHMgPSBjcmVhdGVGb2xkc0FuZE5vcm1hbGl6ZShzdXJ2aXZhbERhdGFzZXQgPSB2YWxpZGF0ZWRBbmRDbGVhbmVkLCBudW1iZXJPZkZvbGRzID0gNSkKdHJhaW5BbmRUZXN0ID0gZm9sZHNbWzJdXQp0cmFpbmluZzFzdEZvbGQgPSB0cmFpbkFuZFRlc3QkVHJhaW5pbmdbWzFdXQp0ZXN0aW5nMXN0Rm9sZCA9IHRyYWluQW5kVGVzdCRUZXN0aW5nW1sxXV0KcnNmTW9kID0gUlNGKHRyYWluaW5nMXN0Rm9sZCwgdGVzdGluZzFzdEZvbGQpCmN1cnZlcyA9IHJzZk1vZCRUZXN0Q3VydmVzCm5hbWVzKGN1cnZlcykgPSBjKCJ0aW1lIiwxOjI2KQpjdXJ2ZXMKYGBgCjxjZW50ZXI+CmBgYHtyLCBlY2hvPUZBTFNFfQpwbG90U3Vydml2YWxDdXJ2ZXMoY3VydmVzLCAxOjEwKQpgYGAKPC9jZW50ZXI+CgoKIyNNdWx0aS10YXNrIExvZ2lzdGljIFJlZ3Jlc3Npb24gKE1UTFIuUikKCk9mIHRoZSBtb2RlbHMgdGVzdGVkLCBNdWx0aS10YXNrIExvZ2lzdGljIFJlZ3Jlc3Npb24gKE1UTFIpIGlzIHRoZSBsZWFzdCB3ZWxsIGtub3duIHNvIHdlIGdpdmUgYSBicmllZiBkZXNjcmlwdGlvbiBoZXJlLiBDb25zaWRlciBtb2RlbGluZyB0aGUgcHJvYmFiaWxpdHkgb2Ygc3Vydml2YWwgb2YgcGF0aWVudHMgYXQgZWFjaCBvZiBhIHZlY3RvciBvZiB0aW1lIHBvaW50cwokXHRhdSA9IFt0XzEsIHRfMiwgXGxkb3RzLCB0X21dJCwgKmUuZy4qICRcdGF1JCBjb3VsZCBiZSB0aGUgNjAgbW9udGhseSBpbnRlcnZhbHMgZnJvbSAxIG1vbnRoIHVwIHRvIDYwIG1vbnRocy4gCldlIGNhbiBzZXQgdXAgYSBzZXJpZXMgb2YgbG9naXN0aWMgcmVncmVzc2lvbiBtb2RlbHM6CkZvciBlYWNoIHBhdGllbnQsIHJlcHJlc2VudGVkIGFzICRcdmVje3h9JCwKXFsKUChUIFxnZXEgdF9pIFwsfFwsIFx2ZWN7eH0pID0gIFxsZWZ0KDEgKyBcZXhwKFx2ZWN7XHRoZXRhX3tpfX1cY2RvdCBcdmVje3h9IClccmlnaHQpXnstMX0KXF0Kd2hlcmUgJFx2ZWN7XHRoZXRhX2l9JCAgYXJlIHRoZSB0aW1lLXNwZWNpZmljIGZlYXR1cmUgdmVjdG9ycy4KV2hpbGUgdGhlIGlucHV0IGZlYXR1cmVzICRcdmVje3h9JCBzdGF5IHRoZSBzYW1lIGZvciBhbGwgdGhlc2UgY2xhc3NpZmljYXRpb24gdGFza3MsICAKdGhlIGJpbmFyeSBsYWJlbHMgJHlfaSA9IFtUXGdlcSB0X2ldJCBjYW4gY2hhbmdlIGRlcGVuZGluZyBvbiB0aGUgdGhyZXNob2xkICR0X2kkLiUKCldlIGVuY29kZSB0aGUgc3Vydml2YWwgdGltZSAkZCQgb2YgYSBwYXRpZW50IGFzIGEgYmluYXJ5IHNlcXVlbmNlIAokeSA9IHkoZCkgPSAoeV8xLCB5XzIsIFxsZG90cywgeV9tKSQsIHdoZXJlICR5X2kgPSB5X2koZCkgXGluIFx7MCwxXH0kIGRlbm90ZXMgdGhlIHN1cnZpdmFsIHN0YXR1cyBvZiB0aGUgcGF0aWVudCBhdCB0aW1lICR0X2kkLCAKc28gdGhhdCAkeV9pID0gMCQgKG5vIGRlYXRoIGV2ZW50IHlldCkgZm9yIGFsbCAkaSQgd2l0aCAkdF9pIDxkJCwgYW5kICR5X2kgPSAxJCAoZGVhdGgpIGZvciBhbGwgJGkkIHdpdGggJHRfaSBcZ2VxIGQkLgoKCgpIZXJlIHRoZXJlIGFyZSAkbSsxJCBwb3NzaWJsZSBsZWdhbCBzZXF1ZW5jZXMgb2YgdGhlIGZvcm0gJCgwLDAsXGxkb3RzLDEsMSxcbGRvdHMsMSkkLCBpbmNsdWRpbmcgdGhlIHNlcXVlbmNlIG9mIGFsbCAwJ3MgYW5kIHRoZSBzZXF1ZW5jZSBvZiBhbGwgMSdzLiAKVGhlIHByb2JhYmlsaXR5IG9mIG9ic2VydmluZyB0aGUgc3Vydml2YWwgc3RhdHVzIHNlcXVlbmNlICR5ID0gKHlfMSwgeV8yLCBcbGRvdHMsIHlfbSkkIGNhbiBiZSByZXByZXNlbnRlZCBhczoKXFsKICBQX1xUaGV0YShZXCFcISA9XCFcISAoeV8xLCB5XzIsIFxsZG90cywgeV9tKSBcLFwsfFwsXCwgXHZlY3t4fSkgPSBcZnJhY3tcZXhwKFxzdW1fe2k9MX1ebSB5X2kgXHRpbWVzIFx0aGV0YV97aX1cY2RvdFx2ZWN7eH0gKX0KICAgICB7XHN1bV97az0wfV5tIFxleHAoZl97XFRoZXRhfShcdmVje3h9LCBrKSl9LCAKXF0Kd2hlcmUgJFxUaGV0YSA9IChcdGhldGFfezF9LCBcbGRvdHMsIFx0aGV0YV97bX0pJCwgYW5kCiRmX3tcVGhldGF9KFx2ZWN7eH0sIGspID0gXHN1bV97aT1rKzF9Xm0gKFx0aGV0YV97aX1cY2RvdFx2ZWN7eH0pJCAKZm9yICQwXGxlcSBrXGxlcSBtJCBpcyB0aGUgc2NvcmUgb2YgdGhlIHNlcXVlbmNlIHdpdGggdGhlIGV2ZW50IG9jY3VycmluZyBpbiB0aGUgaW50ZXJ2YWwgJFt0X2ssIHRfe2srMX0pJCBiZWZvcmUgdGFraW5nIHRoZSBsb2dpc3RpYyB0cmFuc2Zvcm0sIHdpdGggdGhlIGJvdW5kYXJ5IGNhc2UgJGZfe1xUaGV0YX0oXHZlY3t4fSwgayk9IDAkIGJlaW5nIHRoZSBzY29yZSBmb3IgdGhlIHNlcXVlbmNlIG9mIGFsbCBgMCdzLiAKR2l2ZW4gYSBkYXRhc2V0IG9mICRuJCBwYXRpZW50cyAkXHsgXHZlY3t4X3J9XH0kIHdpdGggYXNzb2NpYXRlZCB0aW1lIG9mIGRlYXRocyAkXHsgZF9yIFx9JCwgCndlIGZpbmQgdGhlIG9wdGltYWwgcGFyYW1ldGVycyAoZm9yIHRoZSBNVExSIG1vZGVsKSAkXFRoZXRhXiokIGFzClxbClxUaGV0YV4qXCA9XApcYXJnXG1heF97XFRoZXRhfSAKXHN1bV97cj0xfV5uIFxsZWZ0W1xzdW1fe2k9MX1ebSB5X2ooZF9yKShcdGhldGF7aX1cIVxjZG90XCFcdmVje3hfcn0pXCEgLSBcbG9nIFxzdW1fe2s9MH1ebSBcZXhwIGZfe1xUaGV0YX0oXHZlY3t4X3J9LGspIFxyaWdodF0KICAtICAKXGZyYWN7Q317Mn1cIVxzdW1fe2o9MX1ebVwhIFx8XHRoZXRhX3tqfVx8XjJcISAKJSArXCEgXGZyYWN7Q18yfXsyfVxzdW1fe2o9MX1ee20tMX1cISBcfFx0aGV0YV97aisxfVwhLVwhXHRoZXRhX2pcfF4yXCEKXF0Kd2hlcmUgdGhlICRDJCAoZm9yIHRoZSByZWd1bGFyaXplcikgaXMgZm91bmQgYnkgYW4gaW50ZXJuYWwgY3Jvc3MtdmFsaWRhdGlvbiBwcm9jZXNzLgp9CgoKVGhlcmUgYXJlIG1hbnkgZGV0YWlscyBoZXJlIC0tIFxlZyAKdG8gaW5zdXJlIHRoYXQgdGhlIHN1cnZpdmFsIGZ1bmN0aW9uIHN0YXJ0cyBhdCAxLjAsIGFuZCBkZWNyZWFzZXMgbW9ub3RvbmljYWxseSBhbmQgc21vb3RobHkgdW50aWwgcmVhY2hpbmcgMC4wIGZvciB0aGUgZmluYWwgdGltZSBwb2ludDsgCnRvIGRlYWwgYXBwcm9wcmlhdGVseSB3aXRoIGNlbnNvcmVkIHBhdGllbnRzOyAKdG8gZGVjaWRlIGhvdyBtYW55IHRpbWUgcG9pbnRzIHRvIGNvbnNpZGVyICgkbSQpOyBhbmQgCnRvIG1pbmltaXplIHRoZSByaXNrIG9mIG92ZXJmaXR0aW5nIChieSByZWd1bGFyaXppbmcpLAphbmQgYnkgc2VsZWN0aW5nIHRoZSByZWxldmFudCBmZWF0dXJlcy4KVGhlIFtwYXBlciBieSBZdSBldCBhbC5dKGh0dHBzOi8vcGFwZXJzLm5pcHMuY2MvcGFwZXIvNDIxMC1sZWFybmluZy1wYXRpZW50LXNwZWNpZmljLWNhbmNlci1zdXJ2aXZhbC1kaXN0cmlidXRpb25zLWFzLWEtc2VxdWVuY2Utb2YtZGVwZW5kZW50LXJlZ3Jlc3NvcnMpIHByb3ZpZGVzIHRoZSBkZXRhaWxzLgoKCiMjI011bHRpLXRhc2sgTG9naXN0aWMgUmVncmVzc2lvbiBDb2RlIChNVExSLlIpCgoqKkZpbGUgUHVycG9zZSoqOiBBcHBseSB0aGUgTVRMUiBtb2RlbCB0byBkYXRhIHRoYXQgaGFzIGJlZW4gcHJvY2Vzc2VkIGFuZCBzcGxpdCBpbnRvIGZvbGRzIGJ5ICpjcmVhdGVGb2xkc0FuZE5vcm1hbGl6ZSgpKi4KCioqRmlsZSBQYXRocyoqOiAKCjEuICJJU0RFdmFsdWF0aW9uL01vZGVscy9NVExSLlIiLgoKKipGdW5jdGlvbnMqKjoKCjEuIGBNVExSKHRyYWluaW5nLCB0ZXN0aW5nLEMxID0gTlVMTCwgbnVtRm9sZHMgPSA1KWAKMi4gYGludGVybmFsQ1ZfTVRMUih0cmFpbmluZywgbnVtRm9sZHMpYCAtLSBIZWxwZXIgZnVuY3Rpb24gZm9yIGBNVExSKClgLgozLiBgYXZnTG9nTGlrTG9zcyhwYXJhbXMsIGRhdCwgdGltZVBvaW50cylgIC0tIEhlbHBlciBmdW5jdGlvbiBmb3IgYGludGVybmFsQ1ZfTVRMUigpYC4KCioqQXJndW1lbnRzKio6CgoqICp0cmFpbmluZyo6IFRoZSB0cmFpbmluZyBzZXQuCiogKnRlc3RpbmcqOiBUaGUgdGVzdGluZyBzZXQuCiogKkMxKjogVGhlIHJlZ3VsYXJpemF0aW9uIHBhcmFtZXRlci4KKiAqbnVtRm9sZHMqOiBUaGUgbnVtYmVyIG9mIGZvbGRzIGZvciBpbnRlcm5hbCBjcm9zcyB2YWxpZGF0aW9uLgoqICpwYXJhbXMqOiBUaGUgZmVhdHVyZSB3ZWlnaHRzLgoqICpkYXQqOiBUaGUgdGVzdGluZyBkYXRhc2V0IChmcm9tIGludGVybmFsIENWKS4KKiAqdGltZVBvaW50cyo6IFRoZSB0aW1lIHBvaW50cyB1c2VkIGZvciBldmFsdWF0aW5nIGBNVExSKClgLgoKKipQYWNrYWdlcyBSZXF1aXJlZCoqOiAgCgoxLiBgUmNwcGAgLS0gVXNlZCBmb3IgZmFzdCBpbXBsZW1lbnRhdGlvbiBvZiBncmFkaWVudCBhbmQgb2JqZWN0aXZlIGZ1bmN0aW9uIGNhbGN1bGF0aW9uLgoKKipGaWxlIE5vdGVzKio6CgpHaXZlbiB0aGUgbGlzdCBvZiBkYXRhc2V0cyBnZW5lcmF0ZWQgYnkgYGNyZWF0ZUZvbGRzQW5kTm9ybWFsaXplKClgLCBgTVRMUigpYCB3aWxsIHRha2UgYSBzaW5nbGUgdHJhaW5pbmcgc2V0LCB0ZXN0aW5nIHNldCwgb3B0aW9uYWxseSBhIHZhbHVlIGZvciB0aGUgcmVndWxhcml6YXRpb24gcGFyYW1ldGVyLCBhbmQgdGhlIG51bWJlciBvZiBmb2xkcyBmb3IgaW50ZXJuYWwgY3Jvc3MtdmFsaWRhdGlvbiBpZiAqQzEqIGlzIGxlZnQgYXMgTlVMTC4gSW50ZXJuYWwgY3Jvc3MtdmFsaWRhdGlvbiBpcyBwZXJmb3JtZWQgdG8gc2VsZWN0IGFuIHRoZSBvcHRpbWFsIHJlZ3VsYXJpemF0aW9uIHBhcmFtZXRlciAodGhlIGF2ZXJhZ2UgbG9nLWxpa2VsaWhvb2QgbG9zcyBpcyBvcHRpbWl6ZWQpLiBGZWF0dXJlcyBhcmUgb3B0aW1pemVkIHZpYSBncmFkaWVudCBkZXNjZW50IGFuZCB0aGUgYG9wdGltKClgIGZ1bmN0aW9uLiBTaW5jZSB0aGUgb2JqZWN0aXZlIGZ1bmN0aW9uIGlzIG5vbi1jb252ZXggd2l0aCBjZW5zb3JlZCBkYXRhLCB0aGUgd2VpZ2h0cyBhcmUgdHJhaW5lZCBvbmNlIHRyZWF0aW5nIGFsbCBwYXRpZW50cyBhcyB1bmNlbnNvcmVkIGFuZCB0aGVuIHRyYWluZWQgYWdhaW4gd2l0aCB0aGUgdHJ1ZSBjZW5zb3IgdmFsdWVzICh0aGlzIGlzIGluIGhvcGVzIG9mIGZpbmRpbmcgZ29vZCBzdGFydGluZyB3ZWlnaHRzKS4gCgpUaGUgb3V0cHV0IGlzIHRoZSBzYW1lIGFzIHByZXZpb3VzIG1vZGVscy4gCgoqKkV4YW1wbGUgVXNhZ2U6KioKCmBgYHtyLCB3YXJuaW5nPUZBTFNFfQp2YWxpZGF0ZWRBbmRDbGVhbmVkID0gdmFsaWRhdGVBbmRDbGVhbihzdXJ2aXZhbERhdGFzZXQgPSBOQUNEKQpmb2xkcyA9IGNyZWF0ZUZvbGRzQW5kTm9ybWFsaXplKHN1cnZpdmFsRGF0YXNldCA9IHZhbGlkYXRlZEFuZENsZWFuZWQsIG51bWJlck9mRm9sZHMgPSA1KQp0cmFpbkFuZFRlc3QgPSBmb2xkc1tbMl1dCnRyYWluaW5nMXN0Rm9sZCA9IHRyYWluQW5kVGVzdCRUcmFpbmluZ1tbMV1dCnRlc3Rpbmcxc3RGb2xkID0gdHJhaW5BbmRUZXN0JFRlc3RpbmdbWzFdXQptdGxyTW9kID0gTVRMUih0cmFpbmluZzFzdEZvbGQsIHRlc3Rpbmcxc3RGb2xkKQpjdXJ2ZXMgPSBtdGxyTW9kJFRlc3RDdXJ2ZXMKbmFtZXMoY3VydmVzKSA9IGMoInRpbWUiLDE6MjYpCmN1cnZlcwpgYGAKPGNlbnRlcj4KYGBge3IsIGVjaG89RkFMU0V9CnBsb3RTdXJ2aXZhbEN1cnZlcyhjdXJ2ZXMsIDE6MTApCmBgYAo8ZmlnY2FwdGlvbj5GaWd1cmU6IE1UTFIgU3Vydml2YWwgY3VydmVzICoqd2l0aG91dCoqIGxhc3QgdGltZSBwb2ludCBhZGRlZC48L2ZpZ2NhcHRpb24+CjwvY2VudGVyPgoKCiNFdmFsdXRpbmcgSVNEIE1vZGVscwoKT25jZSB0aGUgc3Vydml2YWwgY3VydmVzIGhhdmUgYmVlbiBnZW5lcmF0ZWQgaXQgaXMgbGlrZWx5IGJlbmVmaWNpYWwgdG8gZXZhbHVhdGUgdGhlbSB1c2luZyBzb21lIHR5cGUgb2YgbWV0cmljLiBXZSB0ZXN0ZWQgKDUpIGRpZmZlcmVudCBtZXRyaWNzLCBDb25jb3JkYW5jZSAoYSBkaXNjcmltaW5hdG9yeSBtZWFzdXJlKSwgMS1DYWxpYnJhdGlvbiwgQnJpZXIgc2NvcmUsIEwxLUxvc3MgYW5kIG5vdmVsIEQtQ2FsaWJyYXRpb24uIEJlbG93IHdlIG9ubHkgZ2l2ZSB0aGUgZGV0YWlscyBmb3IgdGhlIGNvZGUgLS0gZm9yIGV4cGxhbmF0aW9ucyBvZiBlYWNoIG1ldHJpYyBwbGVhc2Ugc2VlIHRoZSBwYXBlciBhc3NvY2lhdGVkIHdpdGggdGhpcyB0dXRvcmlhbCAoTGluaykuIEluIGFkZGl0aW9uIHRvIHRoZSBldmFsdWF0aW9uIG1ldHJpY3Mgd2UgYWxzbyBkZXNjcmliZSB0aGUgZnVuY3Rpb24gdXNlZCB0byBjYWxjdWxhdGUgdGhlIG1lYW4vbWVkaWFuIHN1cnZpdmFsIGZyb20gZWFjaCBzdXJ2aXZhbCBjdXJ2ZS4gV2Ugd2lsbCBzaG93IGFsbCBldmFsdWF0aW9ucyB1c2luZyB0aGUgdW5yZWd1bGFyaXplZCBDb3ggbW9kZWwgYXMgdGhpcyBpcyBvbmUgb2YgdGhlIG1vc3QgY29tbW9uIG1vZGVscyB1c2VkIGluIHN1cnZpdmFsIGFuYWx5c2lzLgoKIyNDYWxjdWxhdGluZyBgYEF2ZXJhZ2UnJyBTdXJ2aXZhbCAoRXZhbHVhdGlvbkhlbHBlckZ1bmN0aW9ucy5SKQoKKipGaWxlIFB1cnBvc2UqKjogQ2FsY3VsYXRlIHRoZSBtZWFuL21lZGlhbiBzdXJ2aXZhbCB0aW1lIGdpdmVuIGEgc2luZ2xlIHN1cnZpdmFsIGN1cnZlLgoKKipGaWxlIFBhdGhzKio6IAoKMS4gIklTREV2YWx1YXRpb24vRXZhbHVhdGlvbnMvRXZhbHVhdGlvbkhlbHBlckZ1bmN0aW9ucy5SIgoKKipGdW5jdGlvbnMqKjoKCjEuIGBwcmVkaWN0UHJvYmFiaWxpdHlGcm9tQ3VydmUoc3Vydml2YWxDdXJ2ZSxwcmVkaWN0ZWRUaW1lcywgdGltZVRvUHJlZGljdClgCjIuIGBwcmVkaWN0TWVhblN1cnZpdmFsVGltZVNwbGluZShzdXJ2aXZhbEN1cnZlLCBwcmVkaWN0ZWRUaW1lcylgCjMuIGBwcmVkaWN0TWVkaWFuU3Vydml2YWxUaW1lU3BsaW5lKHN1cnZpdmFsQ3VydmUsIHByZWRpY3RlZFRpbWVzKWAKCioqQXJndW1lbnRzKio6CgoxLiAqc3Vydml2YWxDdXJ2ZSo6IFRoZSBzdXJ2aXZhbCBwcm9iYWJpbGl0aWVzIGdlbmVyYXRlZCBmb3IgYSBpbmRpdmlkdWFsIHN1cnZpdmFsIGN1cnZlLgoyLiAqcHJlZGljdGVkVGltZXMqOiBUaGUgdGltZXMgY29ycmVzcG9uZGluZyB0byBzdXJ2aXZhbCBwcm9iYWJpbGl0aWVzLCAqZS5nLiogdGltZSA9IDAgY29ycmVzcG9uZHMgdG8gYSBzdXJ2aXZhbCBwcm9iYWJpbGl0eSBvZiAxLgozLiAqdGltZVRvUHJlZGljdCo6IFRoZSB0aW1lIGZvciB3aGljaCB0byBleHRyYWN0IGEgc3Vydml2YWwgcHJvYmFiaWxpdHkgZ2l2ZW4gYSBzdXJ2aXZhbCBjdXJ2ZS4gTm90ZSB0aGlzIGNhbiBlaXRoZXIgYmUgYSBzaW5nbGUgdGltZSBvciBhIHZlY3RvciBvZiB0aW1lcy4KCioqUGFja2FnZXMgUmVxdWlyZWQqKjogTm9uZS4KCioqRmlsZSBOb3RlcyoqOiAKClRoaXMgZmlsZXMgcHVycG9zZSBpcyB0byBwcm92aWRlIHN1cHBvcnQgZm9yIHRoZSBldmFsdWF0aW9uIG1ldHJpY3MuIFNvbWUgbWV0cmljcyAoY29uY29yZGFuY2UsIEwxLWxvc3MpIHJlcXVpcmUgYSBzaW5nbGUgcmlzay9zdXJ2aXZhbCBtZWFzdXJlIGFuZCBmb3IgdGhvc2Ugd2UgdXNlIHRoZSBtZWFuIChvciBtZWRpYW4pIHN1cnZpdmFsIHRpbWVzLCBgcHJlZGljdE1lYW5TdXJ2aXZhbFRpbWVTcGxpbmUoKWAgYW5kIGBwcmVkaWN0TWVkaWFuU3Vydml2YWxUaW1lU3BsaW5lKClgIGFjY29yZGluZ2x5LiBUbyBwcmVkaWN0IHRoZSBtZWFuIHRpbWUgYSBtb25vdG9uaWMgc3BsaW5lIGZpdCBpcyB1c2VkIHRvIG1ha2UgdGhlIHN1cnZpdmFsIHByb2JhYmlsaXRpZXMgY29udGludW91cy4gU3BlY2lmaWNhbGx5LCB3ZSB1c2UgdGhlIEh5bWFuIGZpbHRlcmluZyBvZiB0aGUgY3ViaWMgSGVybWl0ZSBzcGxpbmUgKHNlZSB0aGUgW1dpa2lwZWRpYSBwYWdlXShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9Nb25vdG9uZV9jdWJpY19pbnRlcnBvbGF0aW9uKSBhbmQgdGhlIHBhZ2UncyByZWZlcmVuY2VzIGZvciBhbiBpbnRyb2R1Y3Rpb24pLiBIb3dldmVyLCBpZiB0aGUgc3Vydml2YWwgY3VydmVzIGRvIG5vdCBnbyB0byAwIHByb2JhYmlsaXR5IHdlIHVzZSBhIGxpbmVhciBmaXQgZnJvbSB0aGUgdGltZSA9IDAsIHN1cnZpdmFsIHByb2JhYmlsaXR5ID0gMSBwb2ludCB0byB0aGUgbGFzdCB0aW1lIHBvaW50IHdlIGhhdmUgYW5kIGV4dGVuZCB0aGlzIGxpbmVhciBmaXQgdG8gemVybywgc2VlIHRoZSBGaWd1cmUgYmVsb3cuIE9uY2UgdGhlIHN1cnZpdmFsIGN1cnZlcyBoYXZlIGJlZW4gZXh0ZW5kZWQgdG8gemVybyB0aGUgaW50ZWdyYWwgb2YgdGhlIHN1cnZpdmFsIGN1cnZlIGlzIHRha2VuIHRvIGJlIHRoZSBtZWFuIHN1cnZpdmFsIHRpbWUuCgohW0ZpZ3VyZTogTGluZWFyIGZpdHRpbmcgdG8gZXh0ZW5kIHN1cnZpdmFsIGN1cnZlcyB0byAwIHN1cnZpdmFsIHByb2JhYmlsaXR5LiBMZWZ0OiBTdXJ2aXZhbCBwcm9iYWJpbGl0aWVzIHdpdGggbm8gbGluZWFyIGZpdCwgUmlnaHQ6IFN1cnZpdmFsIHByb2JhYmlsaXR5aWVzIHdpdGggbGluZWFyIGZpdHRpbmcuXSguL0ZpZ3VyZXMvTm9MaW5lYXJWc0xpbmVhci5wbmcpCgpGb3IgY2FsY3VsYXRpbmcgdGhlIG1lZGlhbiBzdXJ2aXZhbCB0aW1lIHRoZSBzYW1lIHNwbGluZSBmaXQgaXMgdXNlZCBhbmQgdGhlbiBhbiBpbnZlcnNlIHNwbGluZSBpcyBmaXQgdG8gdGhlIG9yaWdpbmFsIHNwbGluZSBmaXR0aW5nLiBUaGUgdGltZSBhc3NvY2lhdGVkIHdpdGggYSBzdXJ2aXZhbCBwcm9iYWJpbGl0eSBvZiAwLjUgaXMgdXNlZCBhcyB0aGUgbWVkaWFuIHN1cnZpdmFsIHRpbWUuCgpBZGRpdGlvbmFsbHksIHNvbWUgZXZhbHVhdGlvbiBtZXRyaWNzIChzcGVjaWZpY2FsbHkgY2FsaWJyYXRpb24gbWV0cmljcykgcmVxdWlyZSBzdXJ2aXZhbCBwcm9iYWJpbGl0aWVzIGZvciBhIGdpdmVuIHRpbWUuIFRoaXMgaXMgaW1wbGVtZW50ZWQgYnkgYHByZWRpY3RQcm9iYWJpbGl0eUZyb21DdXJ2ZSgpYC4gVGhlIHNhbWUgc3BsaW5lIGZpdCAod2l0aCBsaW5lYXIgZXh0ZW5zaW9uIHRvIHplcm8pIGZyb20gYmVmb3JlIGlzIGZpdHRlZCB0byB0aGUgc3Vydml2YWwgcHJvYmFiaWxpdGllcyBhbmQgdGhlIGdpdmVuIHRpbWUocykgdG8gcHJlZGljdCAoKnRpbWVUb1ByZWRpY3QqKSBpcyBjYWxjdWxhdGVkIHVzaW5nIHRoZSBpbnRlcm5hbCBgc3BsaW5lKClgIGZ1bmN0aW9uLiAKCioqRXhhbXBsZSBVc2FnZSoqOgpgYGB7ciwgaW5jbHVkZSA9IEZBTFNFfQpwbG90U3Vydml2YWxDdXJ2ZXNXaXRoTGluZWFyID0gZnVuY3Rpb24oc3Vydml2YWxDdXJ2ZXMsIGluZGV4VG9QbG90ID0gMSwgY29sb3IgPSBjKCksIHhsaW0gPSBjKCkpewogIGNvbG9yT0sgPSBUCiAgaWYobGVuZ3RoKGNvbG9yKSA9PSAwKQogICAgY29sb3JPSyA9IEYKICBlbHNlIGlmKGxlbmd0aChjb2xvcikgIT0gbGVuZ3RoKGluZGV4VG9QbG90KSl7CiAgICB3YXJuaW5nKCJJZiB5b3Ugd291bGQgbGlrZSB0byBzZWxlY3QgY3VzdG9tIGNvbG9ycyBwbGVhc2UgbWFrZSBzdXJlIHRoZSBudW1iZXIgb2YgY29sb3JzCiAgICAgICAgICAgIG1hdGNoZXMgdGhlIG51bWJlciBvZiBjdXJ2ZXMuIikKICAgIGNvbG9yT0sgPUYgCiAgfQogIHRpbWUgPSBzdXJ2aXZhbEN1cnZlcyR0aW1lCiAgY3VydmVzID0gc3Vydml2YWxDdXJ2ZXNbLGluZGV4VG9QbG90ICsxLGRyb3A9Rl0KICBwbG90VGltZXMgPSBzZXEobWluKHRpbWUpLG1heCh0aW1lKSwgbGVuZ3RoLm91dCA9IGxlbmd0aCh0aW1lKSoyMCkKICBwbG90UHJvYnMgPSBhcy5kYXRhLmZyYW1lKHNhcHBseShjdXJ2ZXMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZnVuY3Rpb24oY3VydmUpewogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY3VydmUgPSBpZmVsc2UoY3VydmUgPCAxZS0yMCwwLGN1cnZlKQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3Vydml2aWFsU3BsaW5lID0gc3BsaW5lZnVuKHRpbWUsIGN1cnZlLCBtZXRob2QgPSAiaHltYW4iKQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmV0dXJuKHBtYXgoc3Vydml2aWFsU3BsaW5lKHBsb3RUaW1lcyksMCkpCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfQogICkpCiAgbWF4UHJvYiA9IG1heChwbG90UHJvYnNbbnJvdyhwbG90UHJvYnMpLF0pCiAgbWF4U2xvcGUgPSAoMSAtIG1heFByb2IpLygwIC0gbWF4KHBsb3RUaW1lcykpCiAgbWF4TGluZWFyVGltZSA9IG1heChwbG90VGltZXMpICsgKDAgLSBtYXhQcm9iKS9tYXhTbG9wZQogIGFkZGl0aW9uYWxQbG90VGltZXMgPSBzZXEobWF4KHBsb3RUaW1lcyksIG1heExpbmVhclRpbWUsIGxlbmd0aC5vdXQgPSBjZWlsaW5nKChtYXhMaW5lYXJUaW1lIC0gbWF4KHBsb3RUaW1lcykpKSoyMCkKICBwbG90TGluZWFyUHJvYnMgPSBhcy5kYXRhLmZyYW1lKHNhcHBseShwbG90UHJvYnMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZnVuY3Rpb24ocHJvYnMpewogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2xvcGUgPSAoMSAtIG1pbihwcm9icykpLygwIC0gbWF4KHBsb3RUaW1lcykpCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXR1cm4ocG1heChtaW4ocHJvYnMpICsgKGFkZGl0aW9uYWxQbG90VGltZXMgLSBtYXgocGxvdFRpbWVzKSkqc2xvcGUsMCkpCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfQogICkpCiAgcGxvdFRpbWVzID0gYyhwbG90VGltZXMsIGFkZGl0aW9uYWxQbG90VGltZXMpCiAgcGxvdFByb2JzID0gcmJpbmQuZGF0YS5mcmFtZShwbG90UHJvYnMsIHBsb3RMaW5lYXJQcm9icykKICBkYXRhID0gY2JpbmQuZGF0YS5mcmFtZShwbG90VGltZXMscGxvdFByb2JzKQogIGxvbmdGb3JtRGF0YSA9IG1lbHQoZGF0YSxtZWFzdXJlLnZhcnMgPSBuYW1lcyhkYXRhKVstMV0sIHZhcmlhYmxlLm5hbWUgPSAiUGF0aWVudCIpCiAgcGxvdCA9IGdncGxvdChkYXRhID0gbG9uZ0Zvcm1EYXRhLCBhZXMoeCA9IHBsb3RUaW1lcyx5ID0gdmFsdWUsIGNvbG91ciA9IFBhdGllbnQpKSsKICAgIGdlb21fbGluZShzaXplID0gMS41KQogIGlmKGNvbG9yT0spCiAgICBwbG90ID0gcGxvdCArIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBjb2xvcikgCiAgaWYobGVuZ3RoKHhsaW0pPT0yKXsKICAgIHBsb3QgPSBwbG90KyB4bGltKGMoeGxpbVsxXSx4bGltWzJdKSkKICB9CiAgcGxvdCA9IHBsb3QgK3NjYWxlX3lfY29udGludW91cyggbGltaXRzPSBjKDAsMSksYnJlYWtzID0gYygwLC4xLC4yLC4zLC40LC41LC42LC43LC44LC45LDEpKSsKICAgIHRoZW1lX2J3KCkgKwogICAgdGhlbWUodGV4dCA9IGVsZW1lbnRfdGV4dChzaXplPTE4LCBmYWNlPSAgImJvbGQiKSwKICAgICAgICAgIGF4aXMudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDIwKSwKICAgICAgICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfdGV4dChtYXJnaW4gPSBtYXJnaW4odCA9IDE1KSksCiAgICAgICAgICBheGlzLnRpdGxlLnkgPSBlbGVtZW50X3RleHQobWFyZ2luID0gbWFyZ2luKHIgPSAxNSkpKSArIAogICAgbGFicyh5ID0gIlN1cnZpdmFsIFByb2JhaWxpdHkiLHggPSAiVGltZSIgKQogIAogIHJldHVybihwbG90KQp9CmBgYAoKYGBge3IsIHdhcm5pbmc9RkFMU0V9CiNTZXQgdXA6CnZhbGlkYXRlZEFuZENsZWFuZWQgPSB2YWxpZGF0ZUFuZENsZWFuKHN1cnZpdmFsRGF0YXNldCA9IE5BQ0QpCmZvbGRzID0gY3JlYXRlRm9sZHNBbmROb3JtYWxpemUoc3Vydml2YWxEYXRhc2V0ID0gdmFsaWRhdGVkQW5kQ2xlYW5lZCwgbnVtYmVyT2ZGb2xkcyA9IDUpCnRyYWluQW5kVGVzdCA9IGZvbGRzW1syXV0KdHJhaW5pbmcxc3RGb2xkID0gdHJhaW5BbmRUZXN0JFRyYWluaW5nW1sxXV0KdGVzdGluZzFzdEZvbGQgPSB0cmFpbkFuZFRlc3QkVGVzdGluZ1tbMV1dCkNveE1vZCA9IENveFBIX0tQKHRyYWluaW5nMXN0Rm9sZCwgdGVzdGluZzFzdEZvbGQsIEVsYXN0aWNOZXQ9RikKY3VydmVzID0gQ294TW9kW1sxXV0KCgpzdXJ2aXZhbFRpbWVzID0gY3VydmVzWywxXQojV2UgdXNlIHRoZSBzZWNvbmQgcGF0aWVudCBiZWNhdXNlIHRoZSBmaXJzdCBoYWQgYSB2ZXJ5IHNoYWxsb3cgc3Vydml2YWwgY3VydmUgLSBzZWUgQ294IGN1cnZlcyBpbiBTZWNpdG9uIDMuMy4xLgpzZWNvbmRTdXJ2aXZhbEN1cnZlID0gY3VydmVzWywzXQoKI0dldHRpbmcgdGhlIHN1cnZpdmFsIHByb2JhYmlsaXR5IGZvciA1IG1vbnRoczoKcHJlZGljdFByb2JhYmlsaXR5RnJvbUN1cnZlKHN1cnZpdmFsQ3VydmUgPSBzZWNvbmRTdXJ2aXZhbEN1cnZlLCBwcmVkaWN0ZWRUaW1lcyA9IHN1cnZpdmFsVGltZXMsIHRpbWVUb1ByZWRpY3QgPSA1KQojR2V0dGluZyBtdWx0aXBsZSBzdXJ2aXZhbCBwcm9iYWJpbGl0aWVzOgpwcmVkaWN0UHJvYmFiaWxpdHlGcm9tQ3VydmUoc3Vydml2YWxDdXJ2ZSA9IHNlY29uZFN1cnZpdmFsQ3VydmUsIHByZWRpY3RlZFRpbWVzID0gc3Vydml2YWxUaW1lcywgdGltZVRvUHJlZGljdCA9IGMoNSwxMCwyMCw0MCw4MCwxNjApKQoKI0dldHRpbmcgdGhlIG1lYW4gc3Vydml2YWwgdGltZToKcHJlZGljdE1lYW5TdXJ2aXZhbFRpbWVTcGxpbmUoc3Vydml2YWxDdXJ2ZSA9IHNlY29uZFN1cnZpdmFsQ3VydmUsIHByZWRpY3RlZFRpbWVzID0gc3Vydml2YWxUaW1lcykKCiNHZXR0aW5nIHRoZSBtZWRpYW4gc3Vydml2YWwgdGltZToKcHJlZGljdE1lZGlhblN1cnZpdmFsVGltZVNwbGluZShzdXJ2aXZhbEN1cnZlID0gc2Vjb25kU3Vydml2YWxDdXJ2ZSwgcHJlZGljdGVkVGltZXMgPSBzdXJ2aXZhbFRpbWVzKQpgYGAKPGNlbnRlcj4KYGBge3IsIGVjaG89RkFMU0UsfQpwbG90U3Vydml2YWxDdXJ2ZXNXaXRoTGluZWFyKGN1cnZlcywyKQpgYGAKPC9jZW50ZXI+CiMjQ29uY29yZGFuY2UgKENvbmNvcmRhbmNlLlIpCgoqKkZpbGUgUHVycG9zZSoqOiBDYWxjdWxhdGUgdGhlIGNvbmNvcmRhbmNlIG9mIGEgdHJhaW5lZCBtb2RlbCBvbiB0ZXN0IGRhdGEuCgoqKkZpbGUgUGF0aCoqOiAiSVNERXZhbHVhdGlvbi9FdmFsdWF0aW9ucy9Db25jb3JkYW5jZS5SIgoKKipGdW5jdGlvbnMqKjoKCjEuIGBDb25jb3JkYW5jZShzdXJ2TW9kLCB0aWVzID0gIk5vbmUiLCBtZXRob2QgPSAiTWVkaWFuIilgCgoqKkFyZ3VtZW50cyoqOgoKMS4gKnN1cnZNb2QqOiBUaGUgb3V0cHV0IG9mIGFueSBvZiB0aGUgc3Vydml2YWwgbW9kZWxzLiBTcGVjaWZpY2FsbHkgdGhpcyBtdXN0IGJlIGEgbGlzdCBvZiAoMSkgdGhlIHN1cnZpdmFsIGN1cnZlcyBvZiB0aGUgdGVzdCBwYXRpZW50cywgKDIpIHRoZSB0ZXN0IHBhdGllbnRzIHRydWUgdGltZSBvZiBkZWF0aCBhbmQgY2Vuc29yIHN0YXR1cywgYW5kICgzKSB0aGUgdHJhaW5pbmcgcGF0aWVudHMgdHJ1ZSB0aW1lIG9mIGRlYXRoIGFuZCBjZW5zb3Igc3RhdHVzLiBUZWNobmljYWxseSB0aGUgdGhpcmQgY29tcG9uZW50IGlzIG5vdCByZXF1aXJlZCBmb3IgdGhlIGNvbmNvcmRhbmNlIGNhbGN1bGF0aW9uLgoKMi4gKnRpZXMqOiBBIHN0cmluZyBpbmRpY2F0aW5nIHRoZSBtZXRob2QgaW4gd2hpY2ggdG8gaGFuZGxlIHRpZXMuIFRoZXJlIGFyZSBmb3VyIG9wdGlvbnM6ICgxKSAiTm9uZSIgd2hpY2ggd2lsbCBleGNsdWRlIGFsbCB0aWVzLCAoMikgIlJpc2siIHdoaWNoIHdpbGwgaW5jbHVkZSB0aWVzIHdobyB3ZXJlIHRpZWQgaW4gdGhlaXIgcmlzayAobWVhbi9tZWRpYW4gc3Vydml2YWwgdGltZSksICgzKSAiVGltZSIgd2hpY2ggd2lsbCBpbmNsdWRlIHRpZXMgd2hvcyBzdXJ2aXZhbCB0aW1lIHdhcyBlcXVhbCwgYW5kICg0KSAiQWxsIiB3aGljaCBpbmNsdWRlcyBib3RoIHJpc2sgYW5kIHRpbWUgdGllcy4KCjMuICptZXRob2QqOiBBIHN0cmluZyBpbmRpY2F0aW5nIHRvIHVzZSB0aGUgIk1lYW4iIG9yIHRoZSAiTWVkaWFuIiBmb3IgdGhlIHJpc2sgc2NvcmUgZXZhbHVhdGlvbi4gCgoqKlBhY2thZ2VzIFJlcXVpcmVkKio6IAoKMS4gYHN1cnZpdmFsYCAtIFRoaXMgcGFja2FnZSBpcyB1c2VkIGZvciB0aGUgYHN1cnZDb25jb3JkYW5jZSgpYCBmdW5jdGlvbiAtLSBhbiB0aW1lLWVmZmljaWVudCBmdW5jdGlvbiBmb3IgY2FsY3VsYXRpbmcgY29uY29yZGFuY2UuCgoqKkZpbGUgTm90ZXMqKjogCgpBcyBkaXNjdXNzZWQgaW4gdGhlIHBhcGVyLCBjb25jb3JkYW5jZSBpcyBhIGRpc2NyaW1pbmF0aXZlIG1lYXN1cmUgdHlwaWNhbGx5IHVzZWQgdG8gZXZhbHVhdGVkIG1vZGVscyB3aGljaCBwcm9kdWNlIHJpc2sgc2NvcmVzIGZvciBwYXRpZW50cy4gSGVyZSB0aGUgcmlzayBzY29yZSBpcyBnaXZlbiBieSB0aGUgbmVnYXRpdmUgb2YgdGhlIG1lYW4vbWVkaWFuIG9mIGFuIGluZGl2aWR1YWxzIHN1cnZpdmFsIGRpc3RyaWJ1dGlvbi4gT25jZSB0aGUgYXZlcmFnZSBzdXJ2aXZhbCB0aW1lcyBhcmUgY2FsY3VsYXRlZCBgc3VydkNvbmNvcmRhbmNlKClgIGNhbGN1bGF0ZWQgdGhlIGNvbmNvcmRhbmNlIGluZm9ybWF0aW9uIGFuZCB0aGUgY29uY29yZGFuY2Ugc2NvcmUgaXMgcmV0dXJuZWQgZGVwZW5kaW5nIG9uIHRoZSB2YWx1ZXMgb2YgdGhlICp0aWVzKiBhcmd1bWVudC4KCioqRXhhbXBsZSBVc2FnZSoqOgoKYGBge3IsIHdhcm5pbmc9RkFMU0V9CiNTZXQgdXA6CnZhbGlkYXRlZEFuZENsZWFuZWQgPSB2YWxpZGF0ZUFuZENsZWFuKHN1cnZpdmFsRGF0YXNldCA9IE5BQ0QpCmZvbGRzID0gY3JlYXRlRm9sZHNBbmROb3JtYWxpemUoc3Vydml2YWxEYXRhc2V0ID0gdmFsaWRhdGVkQW5kQ2xlYW5lZCwgbnVtYmVyT2ZGb2xkcyA9IDUpCnRyYWluQW5kVGVzdCA9IGZvbGRzW1syXV0KdHJhaW5pbmcxc3RGb2xkID0gdHJhaW5BbmRUZXN0JFRyYWluaW5nW1sxXV0KdGVzdGluZzFzdEZvbGQgPSB0cmFpbkFuZFRlc3QkVGVzdGluZ1tbMV1dCkNveE1vZCA9IENveFBIX0tQKHRyYWluaW5nMXN0Rm9sZCwgdGVzdGluZzFzdEZvbGQsIEVsYXN0aWNOZXQ9RikKCiNFdmFsdWF0aW9uOgpDb25jb3JkYW5jZShDb3hNb2QsIHRpZXMgPSAiTm9uZSIsIG1ldGhvZCA9ICJNZWFuIikKCmBgYAoKIyMxLUNhbGlicmF0aW9uIChPbmVDYWxpYnJhdGlvbi5SKSB7IzFDYWx9CgoqKkZpbGUgUHVycG9zZSoqOiBQZXJmb3JtIHRoZSAxLUNhbGlicmF0aW9uIHRlc3Qgb24gc3Vydml2YWwgZGF0YS4KCioqRmlsZSBQYXRoKio6ICJJU0RFdmFsdWF0aW9uL0V2YWx1YXRpb25zL09uZUNhbGlicmF0aW9uLlIiCgoqKkZ1bmN0aW9ucyoqOgoKMS4gYE9uZUNhbGlicmF0aW9uKHN1cnZNb2QsIHRpbWVPZkludGVyZXN0ID0gYygpLCB0eXBlID0gIkROIiwgbnVtQnVja2V0cyA9IDEwKWAKMi4gYE9uZUNhbGlicmF0aW9uQ3VtdWxhdGl2ZShsaXN0T2ZTdXJ2aXZhbE1vZGVscywgdGltZU9mSW50ZXJlc3QgPSBjKCksIHR5cGUgPSAiRE4iLCBudW1CdWNrZXRzID0gMTApYAozLiBgYmluSXRVcCh0cnVlRGVhdGhUaW1lcyxjZW5zb3JTdGF0dXMsIHByZWRpY3Rpb25zLCB0eXBlLCBudW1CdWNrZXRzLHRpbWVPZkludGVyZXN0KWAKCioqQXJndW1lbnRzKio6CgoxLiAqc3Vydk1vZCo6IFRoZSBvdXRwdXQgb2YgYW55IG9mIHRoZSBzdXJ2aXZhbCBtb2RlbHMuIFNwZWNpZmljYWxseSB0aGlzIG11c3QgYmUgYSBsaXN0IG9mICgxKSB0aGUgc3Vydml2YWwgY3VydmVzIG9mIHRoZSB0ZXN0IHBhdGllbnRzLCAoMikgdGhlIHRlc3QgcGF0aWVudHMgdHJ1ZSB0aW1lIG9mIGRlYXRoIGFuZCBjZW5zb3Igc3RhdHVzLCBhbmQgKDMpIHRoZSB0cmFpbmluZyBwYXRpZW50cyB0cnVlIHRpbWUgb2YgZGVhdGggYW5kIGNlbnNvciBzdGF0dXMuIFRlY2huaWNhbGx5IHRoZSB0aGlyZCBjb21wb25lbnQgaXMgbm90IHJlcXVpcmVkIGZvciB0aGUgY29uY29yZGFuY2UgY2FsY3VsYXRpb24uCgoyLiAqdGltZU9mSW50ZXJlc3QqOiBUaGUgdGltZSBhdCB3aGljaCB0byBldmFsdWF0ZSB0aGUgbW9kZWwgZm9yIDEtQ2FsaWJyYXRpb24uIElmIG5vdGhpbmcgaXMgcGFzc2VkIGluIHRoZSBxdWFudGlsZXMgKC4xLCAuMjUsLjUsLjc1LC45KSBvZiB0aGUgdHJ1ZSBldmVudCB0aW1lcyB3aWxsIGJlIHVzZWQuCgozLiAqdHlwZSo6IFRoaXMgaXMgdGhlIHR5cGUgb2YgMS1DYWxpYnJhdGlvbiB0byBiZSB1c2VkLCBlaXRoZXIgIlVuY2Vuc29yZWQiIHRvIHVzZSAxLUNhbGlicmF0aW9uIGZvciBvbmx5IHRoZSB1bmNlbnNvcmVkIHBhdGllbnRzIGFuZCBkaXNjYXJkIGFsbCBjZW5zb3JlZCBwYXRpZW50cyBvciAiRE4iIGZvciB0aGUgRCdBZ29zdGluby1OYW0gdHJhbnNsYXRpb24uCgo0LiAqbnVtQnVja2V0cyo6IFRoZSBudW1iZXIgb2YgYnVja2V0cy9iaW5zIHRvIHVzZSBmb3IgMS1DYWxpYnJhdGlvbi4KCjUuICpsaXN0T2ZTdXJ2aXZhbE1vZGVscyo6IEEgbGlzdCBvZiB0aGUgb3V0cHV0IG9mIHN1cnZpdmFsIG1vZGVscywgKmUuZy4qIGEgbGlzdCBvZiBkaWZmZXJlbnQgKnN1cnZNb2QqIGxpa2Ugb2JqZWN0cy4KCjYuICp0cnVlRGVhdGhUaW1lcyo6IFRoZSB0cnVlIGV2ZW50IHRpbWVzIG9mIHRoZSBzdWJqZWN0cy4KCjcuICpjZW5zb3JTdGF0dXMqOiBUaGUgYml0IGlkZW50aWZ5aW5nIGlmIGEgcGF0aWVudCBkaWVkIG9yIHdhcyBjZW5zb3JlZCwgKmkuZSogYGRlbHRhYC4KCjguICpwcmVkaWN0aW9ucyo6IFRoZSBwcmVkaWN0ZWQgc3Vydml2YWwgcHJvYmFiaWxpdHkgZm9yIHBhdGllbnRzIGF0IHRoZWlyIHRydWUgZXZlbnQgdGltZXMuCgoqKlBhY2thZ2VzIFJlcXVpcmVkKio6IAoKMS4gYHN1cnZpdmFsYCAtLSBUaGlzIHBhY2thZ2UgaXMgdXNlZCBmb3IgdGhlIGBTdXJ2KClgIGZ1bmN0aW9uLgoKMi4gYGRwbHlyYCAtLSBUaGlzIGlzIHVzZWQgZm9yIGBncm91cF9ieSgpYCwgYHN1bW1hcmlzZSgpYCwgYW5kIG11dGF0ZSgpYC4KCioqRmlsZSBOb3RlcyoqOiAKClRoZXJlIGFyZSB0d28gd2F5cyB0byBwZXJmb3JtIDEtQ2FsaWJyYXRpb24gaGVyZSAtLSBvbmUgY2FuIGVpdGhlciBwYXNzIHRoZSBvdXRwdXQgZnJvbSBhIHN1cnZpdmFsIG1vZGVsIGFuZCBoYXZlIDEtQ2FsaWJyYXRpb24gZXZhbHVhdGVkIG9uIHRoZSB0ZXN0IGZvbGQgb3Igb25lIGNhbiBwYXNzIGEgbGlzdCBvZiBtdWx0aXBsZSBzdXJ2aXZhbCBtb2RlbHMgYW5kIGhhdmUgMS1DYWxpYnJhdGlvbiBldmFsdWF0ZWQgb24gYWxsIHBhdGllbnRzLiBOb3RlIGJ5IGRvaW5nIHRoZSBmaXJzdCB3YXksIGBPbmVDYWxpYnJhdGlvbigpYCwgdGhlcmUgd2lsbCBlbmQgdXAgYmVpbmcgdGhlIHNhbWUgbnVtYmVyIG9mIHAtdmFsdWVzIGFzIHRoZXJlIGFyZSBjcm9zcy12YWxpZGF0aW9uIGZvbGRzIGFuZCB0aGVyZSB3aWxsIGJlIG5vIHN0cmFpZ2h0Zm9yd2FyZCB3YXkgb2YgY29tYmluaW5nIHRoZW0gKERPIE5PVCBBVkVSQUdFIFRIRU0pLiBgT25lQ2FsaWJyYXRpb25DdW11bGF0aXZlKClgIG1pdGlnYXRlcyB0aGlzIGJ5IHRha2luZyBpbiB0aGUgbW9kZWwgcmVzdWx0cyBmcm9tIGFsbCBmb2xkcyBhbmQgcGVyZm9ybWluZyBhIHNpbmdsZSAxLUNhbGlicmF0aW9uIHRlc3QgdXNpbmcgYWxsIHRoZSB0ZXN0IGRhdGEuIGBiaW5JdFVwKClgIGlzIGEgaGVscGVyIGZ1bmN0aW9uIHRoYXQgdGFrZXMgcHJvYmFiaWxpdHkgcHJlZGljdGlvbnMgYW5kIHBlcmZvcm1zIHRoZSBhY3R1YWwgc3BlY2lmaWVkIDEtQ2FsaWJyYXRpb24gdGVzdC4KCioqRXhhbXBsZSBVc2FnZSoqOgoKYGBge3IsIHdhcm5pbmc9RkFMU0V9CiNTZXQgdXAgZm9yIE9uZUNhbGlicmF0aW9uKCk6CnZhbGlkYXRlZEFuZENsZWFuZWQgPSB2YWxpZGF0ZUFuZENsZWFuKHN1cnZpdmFsRGF0YXNldCA9IE5BQ0QpCmZvbGRzID0gY3JlYXRlRm9sZHNBbmROb3JtYWxpemUoc3Vydml2YWxEYXRhc2V0ID0gdmFsaWRhdGVkQW5kQ2xlYW5lZCwgbnVtYmVyT2ZGb2xkcyA9IDUpCnRyYWluQW5kVGVzdCA9IGZvbGRzW1syXV0KdHJhaW5pbmcxc3RGb2xkID0gdHJhaW5BbmRUZXN0JFRyYWluaW5nW1sxXV0KdGVzdGluZzFzdEZvbGQgPSB0cmFpbkFuZFRlc3QkVGVzdGluZ1tbMV1dCkNveE1vZCA9IENveFBIX0tQKHRyYWluaW5nMXN0Rm9sZCwgdGVzdGluZzFzdEZvbGQsIEVsYXN0aWNOZXQ9RikKCiNFdmFsdWF0aW9uOgojQnkgbm90IHNwZWNpZnlpbmcgdGltZU9mSW50ZXJlc3Qgd2Ugd2lsbCBnZXQgdGhlIDEwdGgsIDI1dGgsIDUwdGgsIDc1dGgsIGFuZCA5MHRoIHF1YW50aWxlcyBvZiB0aGUgdHJ1ZSBldmVudCB0aW1lcyBpbiB0aGUgdGVzdGluZyBmb2xkLgpPbmVDYWxpYnJhdGlvbihDb3hNb2QsIHRpbWVPZkludGVyZXN0ID0gYygpLCB0eXBlID0gIkROIixudW1CdWNrZXRzID0gMTApCgojT25lIENhbGlicmF0aW9uIGZvciB1bmNlbnNvcmVkIHBhdGllbnRzLgpPbmVDYWxpYnJhdGlvbihDb3hNb2QsIHRpbWVPZkludGVyZXN0ID0gYygpLCB0eXBlID0gIlVuY2Vuc29yZWQiLG51bUJ1Y2tldHMgPSAxMCkKCiNTZXQgdXAgZm9yIE9uZUNhbGlicmF0aW9uQ3VtdWxhdGl2ZSgpOgpjb3hMaXN0ID0gbGlzdCgpCmZvcihpIGluIDE6NSl7CiAgdHJhaW5pbmdGb2xkID0gdHJhaW5BbmRUZXN0JFRyYWluaW5nW1tpXV0KICB0ZXN0aW5nRm9sZCA9IHRyYWluQW5kVGVzdCRUZXN0aW5nW1tpXV0KICBDb3hNb2QgPSBDb3hQSF9LUCh0cmFpbmluZ0ZvbGQsIHRlc3RpbmdGb2xkLCBFbGFzdGljTmV0PUYpCiAgY294TGlzdFtbaV1dID0gQ294TW9kCn0KI0N1bXVsYXRpdmUgMS1DYWxpYnJhdGlvbiBmb3IgZGVmYXVsdCB0aW1lIG9mIEludGVyZXN0Ck9uZUNhbGlicmF0aW9uQ3VtdWxhdGl2ZShsaXN0T2ZTdXJ2aXZhbE1vZGVscyA9IGNveExpc3QsIHRpbWVPZkludGVyZXN0ID0gYygpLCB0eXBlID0gIkROIiwgbnVtQnVja2V0cyA9IDEwKQojQ3VtdWxhdGl2ZSAxLUNhbGlicmF0aW9uIGZvciBhIHNwZWNpZmljIHRpbWUgKDIgbW9udGhzKQpPbmVDYWxpYnJhdGlvbkN1bXVsYXRpdmUobGlzdE9mU3Vydml2YWxNb2RlbHMgPSBjb3hMaXN0LCB0aW1lT2ZJbnRlcmVzdCA9IDIsIHR5cGUgPSAiRE4iLCBudW1CdWNrZXRzID0gMTApCmBgYAoKCiMjQnJpZXIgU2NvcmUgKEJyaWVyU2NvcmUuUikKCioqRmlsZSBQdXJwb3NlKio6IEV2YWx1YXRlIHRoZSBCcmllciBzY29yZSBvbiB0ZXN0IGRhdGEuCgoqKkZpbGUgUGF0aCoqOiAiSVNERXZhbHVhdGlvbi9FdmFsdWF0aW9ucy9CcmllclNjb3JlLlIiCgoqKkZ1bmN0aW9ucyoqOgoKMS4gYEJyaWVyU2NvcmUoc3Vydk1vZC4gdHlwZSA9ICJJbnRlcmF0ZWQiLCBzaW5nbGVUaW1lID0gTlVMTCwgaW50ZWdyYXRlZEJyaWVyVGltZXMgPSBOVUxMLCBudW1Qb2ludHMgPSBOVUxMKWAKMi4gYHNpbmdsZUJyaWVyKHN1cnZNb2QsIEJyaWVyVGltZSA9IE5VTEwpYAozLiBgaW50ZWdyYXRlZEJyaWVyKHN1cnZNb2QsIGludGVncmF0ZWRCcmllclRpbWVzLCBudW1Qb2ludHMpYAo0LiBgc2lubGVCcmllck11bHRpcGxlUG9pbnRzKHN1cnZNb2QsIEJyaWVyVGltZXMpYAoKKipBcmd1bWVudHMqKjoKCjEuICpzdXJ2TW9kKjogVGhlIG91dHB1dCBvZiBhbnkgb2YgdGhlIHN1cnZpdmFsIG1vZGVscy4gU3BlY2lmaWNhbGx5IHRoaXMgbXVzdCBiZSBhIGxpc3Qgb2YgKDEpIHRoZSBzdXJ2aXZhbCBjdXJ2ZXMgb2YgdGhlIHRlc3QgcGF0aWVudHMsICgyKSB0aGUgdGVzdCBwYXRpZW50cyB0cnVlIHRpbWUgb2YgZGVhdGggYW5kIGNlbnNvciBzdGF0dXMsIGFuZCAoMykgdGhlIHRyYWluaW5nIHBhdGllbnRzIHRydWUgdGltZSBvZiBkZWF0aCBhbmQgY2Vuc29yIHN0YXR1cy4gVGVjaG5pY2FsbHkgdGhlIHRoaXJkIGNvbXBvbmVudCBpcyBub3QgcmVxdWlyZWQgZm9yIHRoZSBjb25jb3JkYW5jZSBjYWxjdWxhdGlvbi4KCjIuICp0eXBlKjogQSBzdHJpbmcgb2YgZWl0aGVyICJJbnRlZ3JhdGVkIiBvciAiU2luZ2xlIi4gVGhpcyB3aWxsIGluZGljYXRlIHdoZXRoZXIgdG8gcmV0dXJuIHRoZSBCcmllciBzY29yZSBmb3IgYSBwb2ludCBvciB0aGUgaW50ZWdyYXRlZCBCcmllciBzY29yZS4KCjMuICpzaW5nbGVCcmllclRpbWUqOiBUaGUgdGltZSBwb2ludCB0byBldmFsdWF0ZSB0aGUgKG5vbi1pbnRlZ3JhdGVkKSBCcmllciBzY29yZS4gSWYgbGVmdCBhcyBOVUxMLCB0aGUgNTB0aCBwZXJjZW50aWxlIG9mIHRoZSB0cmFpbmluZyBhbmQgdGVzdGluZyBkYXRhJ3MgZXZlbnQgdGltZXMgY29tYmluZWQgd2lsbCBiZSB1c2VkLgoKNC4gKmludGVncmF0ZWRCcmllclRpbWVzKjogQSB2ZWN0b3Igb2YgbGVuZ3RoIDIgaW5kaWNhdGluZyB0aGUgbGltaXRzIG9mIGludGVncmF0aW9uIGZvciB0aGUgaW50ZWdyYXRlZCBCcmllciBzY29yZS4gSWYgbGVmdCBhcyBOVUxMIHRoZSByYW5nZSB3aWxsIGJlIGZyb20gemVybyB0byB0aGUgbWF4IGV2ZW50IHRpbWUgb2YgdGhlIGVudGlyZSBkYXRhc2V0ICh0cmFpbmluZyBhbmQgdGVzdGluZyBzZXQgY29tYmluZWQpLgoKNS4gKm51bVBvaW50cyo6IFRoZSBudW1iZXIgb2YgcG9pbnRzIHRvIGV2YWx1YXRlIHRoZSBpbnRlZ3JhbCwgKmkuZS4qIHRoZSBoaWdoZXIgdGhlIG51bWJlciBvZiBwb2ludHMgdGhlIGxlc3MgZXJyb3IgaW4gdGhlIGludGVncmF0aW9uLiBJZiBsZWZ0IGFzIE5VTEwgdGhlIG51bWJlciBvZiBwb2ludHMgd2lsbCBjb3JyZXNwb25kIHRvIHRoZSBudW1iZXIgb2YgZXZlbnRzIGJldHdlZW4gdGhlIGxpbWl0cyBvZiBpbnRlZ3JhdGlvbi4KCjYuICpCcmllclRpbWVzKjogQSB2ZWN0b3Igb2YgdGltZXMgdG8gZXZhbHVhdGUgYSBzaW5nbGUgQnJpZXIgc2NvcmUgLS0gdXNlZCBpbiBhbiBpbnRlcm5hbCBmdW5jdGlvbiAoYHNpbmdsZUJyaWVyTXVsdGlwbGVQb2ludHMoKWApIG9mIGBpbnRlZ3JhdGVkQnJpZXIoKWAuCgoqKlBhY2thZ2VzIFJlcXVpcmVkKio6IAoKMS4gYHByb2RsaW1gIC0tIFVzZWQgZm9yIHRoZSBgcHJvZGxpbSgpYCBmdW5jdGlvbiB0aGF0IGNhbGN1bGF0ZXMgdGhlIEthcGxhbi1NZWllciBjdXJ2ZS4gVGhpcyBpcyB0aGVuIHVzZWQgZm9yIHRoZSBpbnZlcnNlIHByb2JhYmlsaXR5IG9mIGNlbnNvcmluZyB3ZWlnaHRzIChJUENXKS4KCioqRmlsZSBOb3RlcyoqOiAKCkluIHRoaXMgZmlsZSBgQnJpZXJTY29yZSgpYCBpcyB0aGUgb25seSBmdW5jdGlvbiB0aGF0IG5lZWRzIHRvIGJlIGNhbGxlZCAtLSBhbGwgdGhlIG90aGVyIGZ1bmN0aW9ucyBzaW1wbHkgd29yayB3aXRoaW4gYEJyaWVyU2NvcmUoKWAuIE5vdGUgdGhhdCB0aGVyZSBpcyBubyBvcHRpb24gZm9yIHJlbW92aW5nIGNlbnNvcmVkIHBhdGllbnRzIGFuZCB0aGUgQnJpZXIgc2NvcmUgcmV0dXJuZWQgd2lsbCBhbHdheXMgYmUgdGhlIHNjb3JlIGNvcnJlY3RlZCBieSB0aGUgSVBDV3MuIEZvciB0aGUgaW50ZWdyYXRlZCBCcmllciBzY29yZSB0aGUgaW50ZWdyYWwgaXMgZXZhbHVhdGVkIHVzaW5nIHRoZSBzaW1wbGUgW3RyYXBlem9pZGFsIHJ1bGVdKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL1RyYXBlem9pZGFsX3J1bGUpLiBJZiBudW1Qb2ludHMgaXMgc3BlY2lmaWVkIHRoZW4gdGhlIHBvaW50cyB3aGVyZSB0aGUgQnJpZXIgc2NvcmUgaXMgZXZhbHVhdGVkIHdpbGwgYmUgZXZlbmx5IHNwcmVhZCBhY3Jvc3MgdGhlIGxpbWl0cyBvZiBpbnRlZ3JhdGlvbi4gSG93ZXZlciwgaWYgbnVtUG9pbnRzIGlzIG5vdCBzcGVjaWZpZWQgdGhlbiB0aGUgcG9pbnRzIGV2YWx1YXRlZCB3aWxsIGJlIHRoZSBldmVudCB0aW1lcyBpbiB0aGUgdGVzdGluZyBkYXRhLiBUaGVzZSBydWxlcyBmb2xsb3cgZnJvbSB0aGUgYHNicmllcigpYCBmdW5jdGlvbiBpbiB0aGUgaXByZWQgbGlicmFyeSB3cml0dGVuIGJ5IFRvcnN0ZW4gSG90aG9ybi4KCioqRXhhbXBsZSBVc2FnZSoqOgoKCmBgYHtyLCB3YXJuaW5nPUZBTFNFfQojU2V0IHVwOgp2YWxpZGF0ZWRBbmRDbGVhbmVkID0gdmFsaWRhdGVBbmRDbGVhbihzdXJ2aXZhbERhdGFzZXQgPSBOQUNEKQpmb2xkcyA9IGNyZWF0ZUZvbGRzQW5kTm9ybWFsaXplKHN1cnZpdmFsRGF0YXNldCA9IHZhbGlkYXRlZEFuZENsZWFuZWQsIG51bWJlck9mRm9sZHMgPSA1KQp0cmFpbkFuZFRlc3QgPSBmb2xkc1tbMl1dCnRyYWluaW5nMXN0Rm9sZCA9IHRyYWluQW5kVGVzdCRUcmFpbmluZ1tbMV1dCnRlc3Rpbmcxc3RGb2xkID0gdHJhaW5BbmRUZXN0JFRlc3RpbmdbWzFdXQpDb3hNb2QgPSBDb3hQSF9LUCh0cmFpbmluZzFzdEZvbGQsIHRlc3Rpbmcxc3RGb2xkLCBFbGFzdGljTmV0PUYpCgojRXZhbHVhdGlvbjoKI0ludGVncmF0ZWQgd2l0aCBzcGVjaWZpZWQgdGltZXM6CkJyaWVyU2NvcmUoQ294TW9kLCB0eXBlID0gIkludGVncmF0ZWQiLCBpbnRlZ3JhdGVkQnJpZXJUaW1lcyA9IGMoMCwzNikpCgojSW50ZWdyYXRlZCB3aXRoIHVuc3BlY2lmaWVkIHRpbWVzOgpCcmllclNjb3JlKENveE1vZCwgdHlwZSA9ICJJbnRlZ3JhdGVkIikKCiNTaW5nbGUgd2l0aCBzcGVjaWZpZWQgdGltZXM6CkJyaWVyU2NvcmUoQ294TW9kLCB0eXBlID0gIlNpbmdsZSIsIHNpbmdsZUJyaWVyVGltZSA9IDEyKQoKI1NpbmdsZSB3aXRoIHVuc3BlY2lmaWVkIHRpbWVzOgpCcmllclNjb3JlKENveE1vZCwgdHlwZSA9ICJTaW5nbGUiKQpgYGAKCiMjTDEtTG9zcyAoTDFNZWFzdXJlcy5SKSAKCioqRmlsZSBQdXJwb3NlKio6IEV2YWx1YXRlIHRoZSBMMSBsb3NzIG9uIHRlc3QgZGF0YS4KCioqRmlsZSBQYXRoKio6ICJJU0RFdmFsdWF0aW9uL0V2YWx1YXRpb25zL0wxTWVhc3VyZXMuUiIKCioqRnVuY3Rpb25zKio6CgoxLiBgTDEoc3Vydk1vZC4gdHlwZSA9ICJVbmNlbnNvcmVkIiwgbG9nU2NhbGUgPSBGQUxTRSwgbWV0aG9kID0gIk1lZGlhbiIpYAoKKipBcmd1bWVudHMqKjoKCjEuICpzdXJ2TW9kKjogVGhlIG91dHB1dCBvZiBhbnkgb2YgdGhlIHN1cnZpdmFsIG1vZGVscy4gU3BlY2lmaWNhbGx5IHRoaXMgbXVzdCBiZSBhIGxpc3Qgb2YgKDEpIHRoZSBzdXJ2aXZhbCBjdXJ2ZXMgb2YgdGhlIHRlc3QgcGF0aWVudHMsICgyKSB0aGUgdGVzdCBwYXRpZW50cyB0cnVlIHRpbWUgb2YgZGVhdGggYW5kIGNlbnNvciBzdGF0dXMsIGFuZCAoMykgdGhlIHRyYWluaW5nIHBhdGllbnRzIHRydWUgdGltZSBvZiBkZWF0aCBhbmQgY2Vuc29yIHN0YXR1cy4gVGVjaG5pY2FsbHkgdGhlIHRoaXJkIGNvbXBvbmVudCBpcyBub3QgcmVxdWlyZWQgZm9yIHRoZSBjb25jb3JkYW5jZSBjYWxjdWxhdGlvbi4KCjIuICp0eXBlKjogQSBzdHJpbmcgaW5kaWNhdGluZyB0aGUgdHlwZSBvZiBMMS1sb3NzLCBvcHRpb25zIGFyZSBvbmUgb2YgIlVuY2Vuc29yZWQiLCAiSGluZ2UiLCBvciAiTWFyZ2luIi4gU2VlIHRoZSBwYXBlciBmb3IgZGV0YWlscyByZWdhcmRpbmcgdGhlIG1hdGhlbWF0aWNhbCBkZWZpbml0aW9uIG9mIGVhY2ggb3B0aW9uLgoKMy4gKmxvZ1NjYWxlKjogQSBCb29sZWFuIGluZGljYXRpbmcgd2hldGhlciB0byB1c2UgdGhlIGxvZyBzY2FsZWQgdmVyc2lvbiBvZiB0aGUgTDEtbG9zcy4KCjQuICptZXRob2QqOiBBIHN0cmluZyBpbmRpY2F0aW5nIHdoZXRoZXIgdG8gdXNlIHRoZSAiTWVhbiIgb3IgIk1lZGlhbiIgYXMgc3Vydml2YWwgcHJlZGljdGlvbnMuCgoqKlBhY2thZ2VzIFJlcXVpcmVkKio6IAoKMS4gYHByb2RsaW1gIC0tIFVzZWQgZm9yIHRoZSBgcHJvZGxpbSgpYCBmdW5jdGlvbiB0aGF0IGNhbGN1bGF0ZXMgdGhlIEthcGxhbi1NZWllciBjdXJ2ZS4gU2VlIEZpbGUgTm90ZXMgZm9yIG1vcmUgZGV0YWlscy4KCjIuIGBzdXJ2aXZhbGAgLS0gVXNlZCBmb3IgdGhlIGBTdXJ2KClgIGZ1bmN0aW9uLgoKKipGaWxlIE5vdGVzKio6IAoKYEwxKClgIHJldHVybnMgdGhlIGF2ZXJhZ2UgTDEgbG9zcyBiYXNlZCBvbiB0aGUgc3BlY2lmaWNhdGlvbnMgZ2l2ZW4sICplLmcqIHRoZSBNYXJnaW4gTDEtbG9zcyB1c2luZyB0aGUgbWVhbiBzdXJ2aXZhbCB0byBjb21wdXRlIHRoZSBsb3NzLiBUaGVyZSBpcyBvbmUgaW1wb3J0YW50IHNwZWNpZmljYXRpb24gdG8gbm90ZSAtLSB0aGUgS2FwbGFuLU1laWVyIGN1cnZlIGlzIGdlbmVyYXRlZCAoZnJvbSB0aGUgdHJhaW5pbmcgZGF0YSkgYW5kIGlzIGNvbXBsZXRlZCB1c2luZyB0aGUgbGluZWFyIGV4dGVuc2lvbiBhcyBzcGVjaWZpZWQgaW4gU2VjdGlvbiA0LjEgYWJvdmUuIFRoZSBtZWFuIG9mIHRoaXMgS2FwbGFuLU1laWVyIGN1cnZlIGlzIGNvbXB1dGVkIGFuZCB0aGUgc3Vydml2YWwgZXN0aW1hdGUgcmV0dXJuZWQgaXMgdGhlIG1pbmltdW0gb2YgdGhlIG1lYW4gc3Vydml2YWwgdGltZSBmb3IgaW5kaXZpZHVhbCBzdXJ2aXZhbCBjdXJ2ZXMgYW5kIHRoZSBtZWFuIG9mIHRoaXMgS2FwbGFuLU1laWVyIGN1cnZlLiBTZWUgIkwxTWVhc3VyZXMuUiIiIGZvciB0aGUgaW1wbGVtZW50YXRpb24gZGV0YWlscy4KCioqRXhhbXBsZSBVc2FnZSoqOgoKCmBgYHtyLCB3YXJuaW5nPUZBTFNFfQojU2V0IHVwOgp2YWxpZGF0ZWRBbmRDbGVhbmVkID0gdmFsaWRhdGVBbmRDbGVhbihzdXJ2aXZhbERhdGFzZXQgPSBOQUNEKQpmb2xkcyA9IGNyZWF0ZUZvbGRzQW5kTm9ybWFsaXplKHN1cnZpdmFsRGF0YXNldCA9IHZhbGlkYXRlZEFuZENsZWFuZWQsIG51bWJlck9mRm9sZHMgPSA1KQp0cmFpbkFuZFRlc3QgPSBmb2xkc1tbMl1dCnRyYWluaW5nMXN0Rm9sZCA9IHRyYWluQW5kVGVzdCRUcmFpbmluZ1tbMV1dCnRlc3Rpbmcxc3RGb2xkID0gdHJhaW5BbmRUZXN0JFRlc3RpbmdbWzFdXQpDb3hNb2QgPSBDb3hQSF9LUCh0cmFpbmluZzFzdEZvbGQsIHRlc3Rpbmcxc3RGb2xkLCBFbGFzdGljTmV0PUYpCgojRXZhbHVhdGlvbjoKI1VuY2Vuc29yZWQgTG9zcyB3aXRoIE1lYW46CkwxKENveE1vZCwgdHlwZSA9ICJVbmNlbnNvcmVkIiwgbG9nU2NhbGUgPSBGQUxTRSwgbWV0aG9kID0gIk1lYW4iKQoKI0hpbmdlIExvc3Mgd2l0aCBNZWFuOgpMMShDb3hNb2QsIHR5cGUgPSAiSGluZ2UiLCBsb2dTY2FsZSA9IEZBTFNFLCBtZXRob2QgPSAiTWVhbiIpCgojTWFyZ2luIExvc3Mgd2l0aCBNZWFuOgpMMShDb3hNb2QsIHR5cGUgPSAiTWFyZ2luIiwgbG9nU2NhbGUgPSBGQUxTRSwgbWV0aG9kID0gIk1lYW4iKQoKI01hcmdpbiBMb3NzIHdpdGggTWVkaWFuOgpMMShDb3hNb2QsIHR5cGUgPSAiTWFyZ2luIiwgbG9nU2NhbGUgPSBGQUxTRSwgbWV0aG9kID0gIk1lZGlhbiIpCgojTWFyZ2luIExvc3Mgd2l0aCBNZWRpYW4gb24gTG9nIFNjYWxlOgpMMShDb3hNb2QsIHR5cGUgPSAiTWFyZ2luIiwgbG9nU2NhbGUgPSBUUlVFLCBtZXRob2QgPSAiTWVkaWFuIikKYGBgCgojI0QtQ2FsaWJyYXRpb24gKERDYWxpYnJhdGlvbi5SKSB7I0RDYWx9CgoqKkZpbGUgUHVycG9zZSoqOiBFdmFsdWF0ZSB0ZXN0IGRhdGEgdXNpbmcgRC1DYWxpYnJhdGlvbi4KCioqRmlsZSBQYXRoKio6ICJJU0RFdmFsdWF0aW9uL0V2YWx1YXRpb25zL0RDYWxpYnJhdGlvbi5SIgoKKipGdW5jdGlvbnMqKjoKCjEuIGBEQ2FsaWJyYXRpb24oc3Vydk1vZCwgbnVtQmlucylgCgoyLiBgRENhbGlicmF0aW9uQ3VtdWxhdGl2ZShsaXN0T2ZTdXJpdmFsTW9kZWxzLCBudW1CaW5zKWAKCjMuIGBnZXRCaW5uZWQoc3Vydk1vZCwgbnVtQmlucylgCgoqKkFyZ3VtZW50cyoqOgoKMS4gKnN1cnZNb2QqOiBUaGUgb3V0cHV0IG9mIGFueSBvZiB0aGUgc3Vydml2YWwgbW9kZWxzLiBTcGVjaWZpY2FsbHkgdGhpcyBtdXN0IGJlIGEgbGlzdCBvZiAoMSkgdGhlIHN1cnZpdmFsIGN1cnZlcyBvZiB0aGUgdGVzdCBwYXRpZW50cywgKDIpIHRoZSB0ZXN0IHBhdGllbnRzIHRydWUgdGltZSBvZiBkZWF0aCBhbmQgY2Vuc29yIHN0YXR1cywgYW5kICgzKSB0aGUgdHJhaW5pbmcgcGF0aWVudHMgdHJ1ZSB0aW1lIG9mIGRlYXRoIGFuZCBjZW5zb3Igc3RhdHVzLiBUZWNobmljYWxseSB0aGUgdGhpcmQgY29tcG9uZW50IGlzIG5vdCByZXF1aXJlZCBmb3IgdGhlIGNvbmNvcmRhbmNlIGNhbGN1bGF0aW9uLgoKMi4gKm51bUJpbnMqOiBUaGUgbnVtYmVyIG9mIGJpbnMgdG8gdXNlIGZvciBELWNhbGlicmF0aW9uLgoKMy4gKmxpc3RPZlN1cnZpdmFsTW9kZWxzKjogQSBsaXN0IG9mIHRoZSBvdXRwdXQgb2Ygc3Vydml2YWwgbW9kZWxzLCAqZS5nLiogYSBsaXN0IG9mIGRpZmZlcmVudCAqc3Vydk1vZCogbGlrZSBvYmplY3RzLgoKKipQYWNrYWdlcyBSZXF1aXJlZCoqOiAKCjEuIGBwcm9kbGltYCAtLSBVc2VkIGZvciB0aGUgYHNpbmRleCgpYCBmdW5jdGlvbi4KCjIuIGBwbHlyYCAtLSBVc2VkIGZvciB0aGUgYGxkcGx5KClgIGZ1bmN0aW9uIHRvIGNvbWJpbmUgbGlzdHMuCgoqKkZpbGUgTm90ZXMqKjogCgpUaGUgZm9ybWF0IG9mIHRoZSBEQ2FsaWJyYXRpb24uUiBmaWxlIGZvbGxvd3MgdGhlIHNhbWUgYXMgT25lQ2FsaWJyYXRpb24uUi4gVGhhdCBpcywgYERDYWxpYnJhdGlvbigpYCBpcyB1c2VkIGZvciBhIHNpbmdsZSBmb2xkIGFuZCByZXR1cm5zIGEgKnAqLXZhbHVlLiBIb3dldmVyLCBzaW5jZSAqcCotdmFsdWVzIHNob3VsZCBub3QgYmUgYXZlcmFnZWQgdGhlcmUgaXMgYWxzbyBhIGBEQ2FsaWJyYXRpb25DdW11bGF0aXZlKClgIGZ1bmN0aW9uIHdoaWNoIHdpbGwgdGFrZSBhbGwgdGhlIGZvbGRzIHRlc3QgZGF0YSBhbmQgcGVyZm9ybSBhIHNpbmdsZSBELUNhbGlicmF0aW9uIHRlc3QuIExhc3RseSwgYGdldEJpbm5lZCgpYCBpcyBhIGhlbHBlciBmdW5jdGlvbiBmb3IgYERDYWxpYnJhdGlvbigpYCBhbmQgYERDYWxpYnJhdGlvbkN1bXVsYXRpdmUoKWAgYW5kIGRvZXMgbm90IG5lZWQgdG8gYmUgY2FsbGVkIGRpcmVjdGx5LiAKCioqRXhhbXBsZSBVc2FnZSoqOgoKSGVyZSB3ZSBkbyB0d28gbW9kZWxzLCBDb3ggYW5kIE1UTFIuIEJlbG93IHlvdSB3aWxsIHNlZSB0aGF0IHRoZSBDb3ggbW9kZWwgZmFpbHMgd2hlcmVhcyBNVExSIHBlcmZvcm1zIHdlbGwuIEFkZGl0aW9uYWxseSB3ZSBzaG93IHRoZSBjb3JyZXNwb25kaW5nIGN1bXVsYXRpdmUgRC1DYWxpYnJhdGlvbiBoaXN0b2dyYW1zIGZvciBlYWNoIG1vZGVsLgoKYGBge3IsIHdhcm5pbmc9RkFMU0V9CiNTZXQgdXA6CnZhbGlkYXRlZEFuZENsZWFuZWQgPSB2YWxpZGF0ZUFuZENsZWFuKHN1cnZpdmFsRGF0YXNldCA9IE5BQ0QpCmZvbGRzID0gY3JlYXRlRm9sZHNBbmROb3JtYWxpemUoc3Vydml2YWxEYXRhc2V0ID0gdmFsaWRhdGVkQW5kQ2xlYW5lZCwgbnVtYmVyT2ZGb2xkcyA9IDUpCnRyYWluQW5kVGVzdCA9IGZvbGRzW1syXV0KdHJhaW5pbmcxc3RGb2xkID0gdHJhaW5BbmRUZXN0JFRyYWluaW5nW1sxXV0KdGVzdGluZzFzdEZvbGQgPSB0cmFpbkFuZFRlc3QkVGVzdGluZ1tbMV1dCkNveE1vZCA9IENveFBIX0tQKHRyYWluaW5nMXN0Rm9sZCwgdGVzdGluZzFzdEZvbGQsIEVsYXN0aWNOZXQ9RikKCk1UTFJNb2QgPSBNVExSKHRyYWluaW5nMXN0Rm9sZCwgdGVzdGluZzFzdEZvbGQpCgojRC1DYWxpYnJhdGlvbiBmb3IgQ294CkRDYWxpYnJhdGlvbihDb3hNb2QsIG51bUJpbnMgPSAxMCkKCiNELUNhbGlicmF0aW9uIGZvciBNVExSCkRDYWxpYnJhdGlvbihNVExSTW9kLCBudW1CaW5zID0gMTApCgoKI1NldCB1cCBmb3IgRENhbGlicmF0aW9uQ3VtdWxhdGl2ZSgpOgpjb3hMaXN0ID0gbGlzdCgpCm10bHJMaXN0ID0gbGlzdCgpCmZvcihpIGluIDE6NSl7CiAgdHJhaW5pbmdGb2xkID0gdHJhaW5BbmRUZXN0JFRyYWluaW5nW1tpXV0KICB0ZXN0aW5nRm9sZCA9IHRyYWluQW5kVGVzdCRUZXN0aW5nW1tpXV0KICBDb3hNb2QgPSBDb3hQSF9LUCh0cmFpbmluZ0ZvbGQsIHRlc3RpbmdGb2xkLCBFbGFzdGljTmV0PUYpCiAgY294TGlzdFtbaV1dID0gQ294TW9kCiAgCiAgTVRMUk1vZCA9IE1UTFIodHJhaW5pbmdGb2xkLCB0ZXN0aW5nRm9sZCkKICBtdGxyTGlzdFtbaV1dID0gTVRMUk1vZAp9CiNDdW11bGF0aXZlIEQtQ2FsaWJyYXRpb24gZm9yIGNveApEQ2FsaWJyYXRpb25DdW11bGF0aXZlKGxpc3RPZlN1cnZpdmFsTW9kZWxzID0gY294TGlzdCwgbnVtQmlucyA9IDEwKQpgYGAKCjxjZW50ZXI+CmBgYHtyLCBlY2hvID0gRkFMU0V9CmZvckhpc3QgPSBsZHBseShsYXBwbHkoc2VxX2Fsb25nKGNveExpc3QpLCBmdW5jdGlvbih4KSBnZXRCaW5uZWRGb3JIaXN0b3JncmFtKGNveExpc3RbW3hdXSwgMTApKSwgcmJpbmQpICU+JSAKICBncm91cF9ieShCaW4pICU+JSAKICBzdW1tYXJpc2UoVW5jZW4gPSBzdW0oVW5jZW5zb3JlZCkvMTI4LCBDZW4gPSBzdW0oQ2Vuc29yZWQpLzEyOCkKbGlicmFyeShsYXR0aWNlKQpiYXJjaGFydChCaW5+VW5jZW4rQ2VuLGRhdGE9Zm9ySGlzdCwKICAgICAgICAgc3RhY2s9VCwKICAgICAgICAgYXV0by5rZXkgPSBsaXN0KHNwYWNlPSdyaWdodCcsY2V4PS44LHRleHQ9YygnVW5jZW5zb3JlZCcsJ0NlbnNvcmVkJykpLAogICAgICAgICB4bGFiID0gIlByb3BvcnRpb24gb2YgUGF0aWVudHMiLAogICAgICAgICBzY2FsZXM9bGlzdCh5PWxpc3QodGljay5udW1iZXIgPSAxNjApKSkKYGBgCjwvY2VudGVyPgo8ZmlnY2FwdGlvbj5GaWd1cmU6IEhpc3RvZ3JhbSBmb3IgRC1DYWxpYnJhdGlvbiBjb3JyZXNwb25kaW5nIHRvIENveCBtb2RlbC4gQmluIDEgcmVmZXJzIHRvIHRoZSBbMCwuMSkgYmluIGFuZCBCaW4gMTAgcmVmZXJzIHRvIHRoZSBbMC45LCAxXSBiaW4uIEhlcmUgd2Ugc2VlIHRoZSBjb3ggbW9kZWwgcHV0cyB0b28gbWFueSBvZiB0aGUgcGF0aWVudHMgaW4gdGhlIGZpcnN0IGFuZCBsYXN0IGJpbiAtLSBsZWFkaW5nIHRvIGEgZmFpbGVkIEQtQ2FsaWJyYXRpb24gdGVzdC4gPC9maWdjYXB0aW9uPgoKYGBge3J9CkRDYWxpYnJhdGlvbkN1bXVsYXRpdmUobGlzdE9mU3Vydml2YWxNb2RlbHMgPSBtdGxyTGlzdCwgbnVtQmlucyA9IDEwKQpgYGAKCjxjZW50ZXI+CmBgYHtyLCBlY2hvID0gRkFMU0V9CmZvckhpc3QgPSBsZHBseShsYXBwbHkoc2VxX2Fsb25nKGNveExpc3QpLCBmdW5jdGlvbih4KSBnZXRCaW5uZWRGb3JIaXN0b3JncmFtKG10bHJMaXN0W1t4XV0sIDEwKSksIHJiaW5kKSAlPiUgCiAgZ3JvdXBfYnkoQmluKSAlPiUgCiAgc3VtbWFyaXNlKFVuY2VuID0gc3VtKFVuY2Vuc29yZWQpLzEyOCwgQ2VuID0gc3VtKENlbnNvcmVkKS8xMjgpCmxpYnJhcnkobGF0dGljZSkKYmFyY2hhcnQoQmluflVuY2VuK0NlbixkYXRhPWZvckhpc3QsCiAgICAgICAgIHN0YWNrPVQsCiAgICAgICAgIGF1dG8ua2V5ID0gbGlzdChzcGFjZT0ncmlnaHQnLGNleD0uOCx0ZXh0PWMoJ1VuY2Vuc29yZWQnLCdDZW5zb3JlZCcpKSwKICAgICAgICAgeGxhYiA9ICJQcm9wb3J0aW9uIG9mIFBhdGllbnRzIiwKICAgICAgICAgc2NhbGVzPWxpc3QoeT1saXN0KHRpY2subnVtYmVyID0gMTYwKSkpCmBgYAo8L2NlbnRlcj4KCjxmaWdjYXB0aW9uPkZpZ3VyZTogSGlzdG9ncmFtIGZvciBELUNhbGlicmF0aW9uIGNvcnJlc3BvbmRpbmcgdG8gTVRMUiBtb2RlbC4gQmluIDEgcmVmZXJzIHRvIHRoZSBbMCwuMSkgYmluIGFuZCBCaW4gMTAgcmVmZXJzIHRvIHRoZSBbMC45LCAxXSBiaW4uIFRoZSBiaW5zIGFyZSBtdWNoIG1vcmUgZXZlbiBoZXJlIHRoYW4gdGhvc2UgZ2l2ZW4gYnkgdGhlIENveCBtb2RlbCwgcmVzdWx0aW5nIGluIGEgcGFzc2VkIEQtQ2FsaWJyYXRpb24gdGVzdC4gPC9maWdjYXB0aW9uPgoKI01pc2NlbGxhbmlvdXMKCiMjUGxvdHRpbmcgSW5kaXZpZHVhbCBTdXJ2aXZhbCBDdXJ2ZXMgKHBsb3RTdXJ2aXZhbEN1cnZlcy5SKSB7I3Bsb3R0aW5nfQoKKipGaWxlIFB1cnBvc2UqKjogUGxvdCBpbmRpdmlkdWFsIHN1cnZpdmFsIGN1cnZlcy4KCioqRmlsZSBQYXRoKio6ICJJU0RFdmFsdWF0aW9uL1Bsb3R0aW5nL3Bsb3RTdXJ2aXZhbEN1cnZlcy5SIgoKKipGdW5jdGlvbnMqKjoKCjEuIGBwbG90U3Vydml2YWxDdXJ2ZXMoc3Vydml2YWxDdXJ2ZXMsIGluZGV4VG9QbG90LCBjb2xvciA9IGMoKSwgeGxpbT1jKCkpYAoKKipBcmd1bWVudHMqKjoKCjEuICpzdXJ2aXZhbEN1cnZlcyo6IEEgZGF0YWZyYW1lIHdoZXJlIHRoZSBmaXJzdCBjb2x1bW4gY29ycmVzcG9uZHMgdG8gc3Vydml2YWwgdGltZXMgYW5kIGFsbCBvdGhlciBjb2x1bW5zIGFyZSBzdXJ2aXZhbCBwcm9iYWJpbGl0aWVzIG9mIGluZGl2aWR1YWwgcGF0aWVudHMuIFRoaXMgY29ycmVzcG9uZHMgdG8gdGhlIGZpcnN0IGl0ZW0gb2YgdGhlIGxpc3QgcmV0dXJuZWQgYnkgYWxsIG1vZGVscywgKmUuZy4qIGBDb3hQSF9LUCgpYC4KCjIuICppbmRleFRvUGxvdCo6IFRoZSBwYXRpZW50IG51bWJlciB5b3Ugd2lzaCB0byBwbG90LiBGcm9tICpzdXJ2aXZhbEN1cnZlcyogcGF0aWVudCAxIHdvdWxkIGJlIHRoZSBzZWNvbmQgY29sdW1uLiBUaGlzIGNhbiBlaXRoZXIgYmUgYW4gaW50ZWdlciBzcGVjaWZ5aW5nIHdoaWNoIHBhdGllbnQgb3IgYSB2ZWN0b3Igb2YgaW50ZWdlcnMsIHNwZWNpZnlpbmcgbXVsdGlwbGUgcGF0aWVudHMuCgozLiAqY29sb3IqOiBUaGUgY29sb3Igb2YgdGhlIHN1cnZpdmFsIGN1cnZlcy4gVGhlIGxlbmd0aCBtdWNoIG1hdGNoIHRoYXQgb2YgKmluZGV4VG9QbG90Ki4KCjQuICp4bGltKjogWW91IGNhbiBzcGVjaWZ5IHRoZSByYW5nZSBvZiB0aGUgeC1heGlzLiBUaGlzIHNob3VsZCBiZSBhIHZlY3RvciBvZiBsZW5ndGggdHdvLgoKKipQYWNrYWdlcyBSZXF1aXJlZCoqOiAKCjEuIGBnZ3Bsb3QyYCAtLSBVc2VkIGZvciBwbG90dGluZy4KCjIuIGByZXNoYXBlMmAgLS0gVXNlZCB0byByZXNoYXBlIHRoZSBkYXRhIHRvIGxvbmcgZm9ybS4KCioqRmlsZSBOb3RlcyoqOiAKClRoZSBwbG90dGluZyBjb2RlIHdpbGwgc2VsZWN0IHRoZSBjb2x1bW5zIHNwZWNpZmllZCBieSAqaW5kZXhUb1Bsb3QqIGFuZCBmaXQgYSBzcGxpbmUgdXNpbmcgdGhlIHRpbWUgY29sdW1uIGFuZCB0aGUgc3Vydml2YWwgcHJvYmFiaWxpdGllcyBjb2x1bW5zLiBUaGlzIHNwbGluZSBpcyB0aGVuIHVzZWQgdG8gaW50ZXJwb2xhdGUgbWFueSBwb2ludHMgYmV0d2VlbiB0aGUgcHJvYmFiaWxpdGllcyBzdWNoIHRoYXQgd2UgaGF2ZSBhIHNtb290aCBmaXQgdG8gcGxvdC4gU2VlIGJlbG93IGZvciBhIGZldyBwbG90dGluZyBleGFtcGxlcy4gTm90ZSB0aGF0ICppbmRleFRvUGxvdCogd2lsbCBtYXRjaCB1cCB0ZXN0aW5nIGRhdGEgZGVwZW5kaW5nIG9uIHRoZSBmb2xkLiBBZGRpdGlvbmFsbHksIGlmIG92ZXIgMTUgaW5kZXhlcyBhcmUgc3BlY2lmaWVkLCB0aGVyZSB3aWxsIGJlIG5vIGxlZ2VuZCAodGhlIGxlZ2VuZCBmaWxscyB1cCB0aGUgcGxvdCBzcGFjZSkuCgoqKkV4YW1wbGUgVXNhZ2UqKjoKCgpgYGB7ciwgd2FybmluZz1GQUxTRX0KI1dpdGhvdXQgZmVhdHVyZSBzZWxlY3Rpb24uCnZhbGlkYXRlZEFuZENsZWFuZWQgPSB2YWxpZGF0ZUFuZENsZWFuKHN1cnZpdmFsRGF0YXNldCA9IE5BQ0QpCmZvbGRzID0gY3JlYXRlRm9sZHNBbmROb3JtYWxpemUoc3Vydml2YWxEYXRhc2V0ID0gdmFsaWRhdGVkQW5kQ2xlYW5lZCwgbnVtYmVyT2ZGb2xkcyA9IDUpCnRyYWluQW5kVGVzdCA9IGZvbGRzW1syXV0KdHJhaW5pbmcxc3RGb2xkID0gdHJhaW5BbmRUZXN0JFRyYWluaW5nW1sxXV0KdGVzdGluZzFzdEZvbGQgPSB0cmFpbkFuZFRlc3QkVGVzdGluZ1tbMV1dCkNveE1vZCA9IENveFBIX0tQKHRyYWluaW5nMXN0Rm9sZCwgdGVzdGluZzFzdEZvbGQsIEVsYXN0aWNOZXQ9RikKc3Vydml2YWxDdXJ2ZXMgPSBDb3hNb2RbWzFdXQpwbG90U3Vydml2YWxDdXJ2ZXMoc3Vydml2YWxDdXJ2ZXMsIGluZGV4VG9QbG90ID0gMSkKCnBsb3RTdXJ2aXZhbEN1cnZlcyhzdXJ2aXZhbEN1cnZlcywgaW5kZXhUb1Bsb3QgPSAyKQoKcGxvdFN1cnZpdmFsQ3VydmVzKHN1cnZpdmFsQ3VydmVzLCBpbmRleFRvUGxvdCA9IDIsIGNvbG9yID0gImJsdWUiKQoKcGxvdFN1cnZpdmFsQ3VydmVzKHN1cnZpdmFsQ3VydmVzLCBpbmRleFRvUGxvdCA9IDE6MTApCgpwbG90U3Vydml2YWxDdXJ2ZXMoc3Vydml2YWxDdXJ2ZXMsIGluZGV4VG9QbG90ID0gMToxMCwgeGxpbSA9IGMoMCwyNSkpCgpgYGAKCk5vdGUgaGVyZSB0aGF0IHRoZSBpbmRleCBjb3JyZXNwb25kcyB0byB0aGUgcm93IG51bWJlciBpbiB0aGUgb3JpZ2luYWwgZGF0YXNldCAodGhleSBhcmUgYWxsIG91dCBvZiBvcmRlciBoZXJlIHNpbmNlIHdlIHVzZWQgYW4gYXJiaXRyYXJ5IGNyb3NzLXZhbGlkYXRpb24gZm9sZCkuCgojI0ZlYXR1cmUgU2VsZWN0aW9uIChGZWF0dXJlU2VsZWN0aW9uLlIpCgoqKkZpbGUgUHVycG9zZSoqOiBQZXJmb3JtIGZlYXR1cmUgc2VsZWN0aW9uIG9uIHN1cnZpdmFsIGRhdGEuCgoqKkZpbGUgUGF0aCoqOiAiSVNERXZhbHVhdGlvbi9GZWF0dXJlU2VsZWN0aW9uL0ZlYXR1cmVTZWxlY3Rpb24uUiIKCioqRnVuY3Rpb25zKio6CgoxLiBgRmVhdHVyZVNlbGVjdGlvbihkYXRhc2V0LCB0eXBlID0gIlVuaUNveCIsIG9ic190aHJlc2ggPSAwLCBwVGhyZXNoID0gMC4xKWAKCjIuIGB1bmlDb3goZGF0YXNldCwgb2JzdGhyZXNoID0gMCwgcFRocmVzaD0wLjEpYAoKKipBcmd1bWVudHMqKjoKCjEuICpkYXRhc2V0KjogVGhpcyBpcyBhIGRhdGFzZXQgd2hpY2ggaGFzIGJlZW4gdmFsaWRhdGVkIGZvciB1c2UgLS0gaS5lLiBnb25lIHRocm91Z2ggYHZhbGlkYXRlQW5kQ2xlYW4oKWAKCjIuICp0eXBlKjogVGhlIHR5cGUgb2YgZmVhdHVyZSBzZWxlY3Rpb24gdG8gdXNlLiBDdXJyZW50bHkgKEF1Z3VzdCAyOCwgMjAxOCkgdGhlIG9ubHkgb3B0aW9uIGlzICJVbmlDb3giLiBTZWUgRmlsZSBOb3RlcyBmb3IgZGV0YWlscy4KCjMuICpvYnNfdGhyZXNoKjogVGhlIG51bWJlciBvZiBvYnNlcnZhdGlvbnMgdGhhdCBhIHZhcmlhYmxlIG11c3QgaGF2ZSB0byBxdWFsaWZ5IGZvciBzZWxlY3Rpb24uCgo0LiAqcFRocmVzaCo6IFRoZSBwLXZhbHVlIHRocmVzaG9sZCB0byBiZSBpbmNsdWRlZCBpbiB0aGUgYW5hbHlzaXMuCgoqKlBhY2thZ2VzIFJlcXVpcmVkKio6IAoKMS4gYGNhcmV0YCAtLSB0aGUgYGR1bW15VmFycygpYCBmdW5jdGlvbiBpcyB1c2VkIGZvciBvbmUtaG90IGVuY29kaW5nLgoKKipGaWxlIE5vdGVzKio6IAoKVGhlIGNvZGUgaGVyZSBpcyB3cml0dGVuIHNvIGN1c3RvbSBmZWF0dXJlIHNlbGVjdGlvbiBtZXRob2RzIGNhbiBiZSBlYXNpbHkgYWRkZWQgLS0gdGhleSBqdXN0IGhhdmUgdG8gYWRkZWQgdG8gYSBzd2l0Y2ggY29tbWFuZC4gSGVyZSB3ZSB1c2UgdW5pLXZhcmlhdGUgY294IGZvciBvdXIgZmVhdHVyZSBzZWxlY3Rpb24gLS0gdGhlIGRldGFpbHMgb2YgdGhpcyBtZXRob2QgYXJlIGdpdmVuIGluIHRoZSBwYXBlci4gRm9yIHRoaXMgZmVhdHVyZSBzZWxlY3Rpb24gd2UgZmlyc3QgY2hlY2sgdGhhdCB0aGF0IGVhY2ggdmFyaWFibGUgaGFzIGEgKm9ic190aHJlc2gqIG51bWJlciBvZiBldmVudHMgKGRlYXRocykgb2NjdXIuIFRoZSBkZWZhdWx0IGZvciB0aGlzIGlzIDAgZXZlbnRzLiBOZXh0IHRoZSBwLXZhbHVlIGZvciB0aGUgZmVhdHVyZSBtdXN0IGJlIGJlbG93IDAuMSB0byBiZSBpbmNsdWRlZCBpbiB0aGUgc2VsZWN0ZWQgZGF0YXNldC4gTm90ZSB0aGF0IHZhcmlhYmxlcyBhcmUgb25lLWhvdCBlbmNvZGVkIGJlZm9yZSBzZWxlY3Rpb24gc28gYWxsIHRoZSB2YWx1ZXMgb2YgYSBjYXRlZ29yaWNhbCB2YXJpYWJsZSBtYXkgbm90IGJlIHByZXNlbnQgcG9zdCBmZWF0dXJlIHNlbGVjdGlvbi4KCioqRXhhbXBsZSBVc2FnZSoqOgoKQmVsb3cgeW91IHdpbGwgc2VlIHRoYXQgdW5zZWxlY3RlZCBkYXRhIGhhcyA0MCBmZWF0dXJlcyB3aGVyZWFzIHRoZSBkYXRhIHBvc3QgZmVhdHVyZSBzZWxlY3Rpb24gaGFzIG9ubHkgMjIgZmVhdHVyZXMuCgpgYGB7ciwgd2FybmluZz1GQUxTRX0KI1dpdGhvdXQgZmVhdHVyZSBzZWxlY3Rpb24uCnZhbGlkYXRlZEFuZENsZWFuZWQgPSB2YWxpZGF0ZUFuZENsZWFuKHN1cnZpdmFsRGF0YXNldCA9IE5BQ0QpCmRpbSh2YWxpZGF0ZWRBbmRDbGVhbmVkKQoKI0FmdGVyIHdlIGhhdmUgdGhlIHNlbGVjdGVkIGRhdGEgd2Ugd291bGQgdGhlbiBwYXNzIGl0IHRvIGNyZWF0ZUZvbGRzQW5kTm9ybWFsaXplKCkuCnNlbGVjdGVkRGF0YSA9IEZlYXR1cmVTZWxlY3Rpb24odmFsaWRhdGVkQW5kQ2xlYW5lZCkKZGltKHNlbGVjdGVkRGF0YSkKYGBgCgoKCgo=