Overview

In Tutorial 1 you developed a prediction model. A model always performs better on the data it was developed on than on new patients, so in this tutorial you will check how well it really works. It has three parts:

  1. Uniform shrinkage and optimism-corrected C-index: use the bootstrap to calculate the uniform shrinkage factor and calculate optimism.

  2. Model performance: calculate predicted risks, make calibration plots and a decision curve.

  3. Internal-external cross-validation: develop the model on some centers and test it on the center that was left out.

This tutorial can be run on its own: if you have not run Tutorial 1, the objects it needs are rebuilt automatically (see Load the data and models).

Download the tutorial

Do not just read this page: run the code yourself. You only learn R by doing it. Download the R Markdown file of this tutorial (Tutorial_2_Validate_Prediction_Model.Rmd) by clicking the Code button at the top right of this page and choosing Download Rmd, and run every chunk in RStudio, one by one. Read the output, change the code, break it and fix it, and try the exercises before you open the answers. Reading the results on this page is not a substitute.

To make sure everything works:

  1. Create a folder for the course, for example Predictive_models/, and put the downloaded .Rmd file in it. Tutorial 1 is not required.

  2. Open the .Rmd file in RStudio. When you knit or run chunks from an Rmd file, R uses the folder that contains the file as the working directory. Check this by running:

    getwd()

    The output should be the path of the folder from step 1. If it is not, set it with setwd("path/to/your/folder") or in RStudio via Session > Set Working Directory > To Source File Location.

  3. Do not use file paths in the code. All files are read from and saved to the working directory, so keep all files in this one folder.

  4. Running the tutorial saves its figures (calibration plots, decision curve and the internal-external cross-validation) in this same folder.

The folder will then look like this:

Predictive_models/
├── Tutorial_1_Develop_Prediction_Model.Rmd
├── Tutorial_2_Validate_Prediction_Model.Rmd
├── model_performance.png           (created by Tutorial 2)
├── dca.png                         (created by Tutorial 2)
└── Internal_external_cross_validation.png  (created by Tutorial 2)

Setup

Clear the environment and load libraries

As in Tutorial 1, everything is saved in one folder: the folder that contains this Rmd file (the working directory), so no file paths are needed. If you also do Tutorial 1, keep both tutorials in the same folder.

A package is a collection of functions that someone wrote and shared. library(package) loads a package, so that you can use its functions directly. We only load rms, because we use some of its functions (such as cph, validate and fastbw) without the package:: prefix. All other packages are called with the :: notation (see below), so they do not have to be loaded, but they have to be installed once. We use survival, PredictionTools (calibration plots), dcurves (decision curve analysis), ggplot2 and ggpubr (making and combining figures), and dplyr, mice and Hmisc (only to rebuild the models below).

# clear the environment: remove all objects that R currently remembers
rm(list=ls())

# load the rms package: after this, all its functions can be used directly
library(rms)

# The other packages must be installed once on your computer (not run here):
# install.packages(c("survival", "dcurves", "ggplot2", "ggpubr", "dplyr", "mice", "Hmisc"))
# remotes::install_github("CHMMaas/PredictionTools")  # documentation: https://github.com/CHMMaas/PredictionTools

The :: notation

In this tutorial you will often see code like rms::validate(...). The notation package::function() means “use the function function from the package package”.

You do not have to write it this way once you have loaded a package with library(): after library(rms), validate(...) works as well. We still write it out because it is nice to see where each function comes from, and it avoids mistakes when two packages have a function with the same name (R then uses the one from the package that was loaded last).

Set the seed

We set the random seed once, here at the top. Every random step below (the bootstrap samples) then continues from this starting point, so the whole tutorial is reproducible without resetting the seed before each call. If you change the order of the code or add extra random steps, the numbers will change.

# set the seed once for the whole tutorial
set.seed(1)

Load the data and models

This tutorial needs the data, the imputed data sets and the models from Tutorial 1. The code below rebuilds them (a condensed copy of Tutorial 1, which takes a minute), so you can start with this tutorial directly.

Show the code that rebuilds the Tutorial 1 objects
# data: pbc from the survival package, with an artificial center variable
data(pbc, package="survival")
data <- pbc
data$center <- rep(1:2, length.out=nrow(data))
data <- dplyr::select(data, time, status,
               trt, age, sex, ascites, hepato, spiders, edema,
               bili, chol, albumin, copper, alk.phos, ast, trig,
               platelet, protime, stage, center)
data$trt     <- factor(data$trt, levels=c(1, 2),
                       labels=c("D-penicillamine", "Placebo"))
data$sex     <- factor(data$sex, levels=c("m", "f"),
                       labels=c("Male", "Female"))
data$ascites <- factor(data$ascites, levels=c(0, 1),
                       labels=c("No", "Yes"))
data$hepato  <- factor(data$hepato, levels=c(0, 1),
                       labels=c("No", "Yes"))
data$spiders <- factor(data$spiders, levels=c(0, 1),
                       labels=c("No", "Yes"))
data$edema   <- factor(data$edema, levels=c(0, 0.5, 1),
                       labels=c("No edema", "Untreated or successfully treated",
                                "Despite diuretics"))
data$stage   <- factor(data$stage, levels=1:4,
                       labels=c("1", "2", "3", "4"))
data$center  <- factor(data$center, levels=c(1, 2),
                       labels=c("Center 1", "Center 2"))

# outcome: death within 10 years, transplant is censored
S <- survival::Surv(time=data$time/365, event=as.numeric(data$status==2))
horizon <- 10
S_10 <- S
S_10[S_10[, 1]>horizon, 2] <- 0
S_10[S_10[, 1]>horizon, 1] <- horizon

# multiple imputation with event indicator and Nelson-Aalen estimate
data_mi <- data
data_mi$time <- NULL
data_mi$status <- NULL
data_mi$event_10 <- S_10[, "status"]
data_mi$cum_hazard <- mice::nelsonaalen(data.frame(time=S_10[, "time"],
                                                   status=S_10[, "status"]),
                                        time, status)
m <- 5
imputed_data <- mice::mice(data_mi, m=m, seed=1, print=FALSE)

# models
dd <- rms::datadist(data)
options(datadist="dd")
fit_args <- list(x=TRUE, y=TRUE, surv=TRUE)
full_non_linear_model <- Hmisc::fit.mult.impute(
  S_10 ~ rcs(age, 3) + rcs(bili, 3) + rcs(albumin, 3) + sex + ascites +
    hepato + stage, rms::cph,
  fitargs=fit_args, xtrans=imputed_data, data=data, fit.reps=TRUE)
## 
## Wald Statistic Information
## 
## Variance Inflation Factors Due to Imputation:
## 
##         age        age'        bili       bili'     albumin    albumin' 
##        1.01        1.01        1.01        1.00        1.00        1.01 
##  sex=Female ascites=Yes  hepato=Yes     stage=2     stage=3     stage=4 
##        1.00        1.15        1.09        1.09        1.08        1.09 
## 
## Fraction of Missing Information:
## 
##         age        age'        bili       bili'     albumin    albumin' 
##        0.01        0.01        0.01        0.00        0.00        0.01 
##  sex=Female ascites=Yes  hepato=Yes     stage=2     stage=3     stage=4 
##        0.00        0.13        0.08        0.08        0.07        0.08 
## 
## d.f. for t-distribution for Tests of Single Coefficients:
## 
##         age        age'        bili       bili'     albumin    albumin' 
##    70992.09    29693.89   106704.51   199948.55   205552.84    32084.48 
##  sex=Female ascites=Yes  hepato=Yes     stage=2     stage=3     stage=4 
##   358897.26      242.90      630.61      651.50      799.20      602.05 
## 
## The following fit components were averaged over the 5 model fits:
## 
##   linear.predictors means stats center
form_full <- S_10 ~ rcs(age, 3) + rcs(bili, 3) + albumin + sex +
  ascites + hepato + stage
full_model <- Hmisc::fit.mult.impute(form_full, rms::cph,
  fitargs=fit_args, xtrans=imputed_data, data=data, fit.reps=TRUE)
## 
## Wald Statistic Information
## 
## Variance Inflation Factors Due to Imputation:
## 
##         age        age'        bili       bili'     albumin  sex=Female 
##        1.01        1.01        1.01        1.00        1.00        1.00 
## ascites=Yes  hepato=Yes     stage=2     stage=3     stage=4 
##        1.14        1.10        1.08        1.08        1.09 
## 
## Fraction of Missing Information:
## 
##         age        age'        bili       bili'     albumin  sex=Female 
##        0.01        0.01        0.01        0.00        0.00        0.00 
## ascites=Yes  hepato=Yes     stage=2     stage=3     stage=4 
##        0.13        0.09        0.08        0.07        0.08 
## 
## d.f. for t-distribution for Tests of Single Coefficients:
## 
##         age        age'        bili       bili'     albumin  sex=Female 
##    66673.51    25006.17   103533.09   203798.25 31013743.69   337198.38 
## ascites=Yes  hepato=Yes     stage=2     stage=3     stage=4 
##      251.90      492.12      655.56      784.71      576.02 
## 
## The following fit components were averaged over the 5 model fits:
## 
##   linear.predictors means stats center
form_bw <- S_10 ~ rcs(age, 3) + rcs(bili, 3) + albumin + stage
bw_model <- Hmisc::fit.mult.impute(form_bw, rms::cph,
  fitargs=fit_args, xtrans=imputed_data, data=data, fit.reps=TRUE)
## 
## Wald Statistic Information
## 
## Variance Inflation Factors Due to Imputation:
## 
##     age    age'    bili   bili' albumin stage=2 stage=3 stage=4 
##    1.00    1.00    1.00    1.00    1.01    1.09    1.06    1.07 
## 
## Fraction of Missing Information:
## 
##     age    age'    bili   bili' albumin stage=2 stage=3 stage=4 
##    0.00    0.00    0.00    0.00    0.01    0.08    0.06    0.06 
## 
## d.f. for t-distribution for Tests of Single Coefficients:
## 
##          age         age'         bili        bili'      albumin      stage=2 
##  20946309.50 131064158.78    381202.54    364582.19     97205.48       592.59 
##      stage=3      stage=4 
##      1199.64      1069.91 
## 
## The following fit components were averaged over the 5 model fits:
## 
##   linear.predictors means stats center
# set data distributions
dd <- rms::datadist(data)
options(datadist="dd")

1 Uniform shrinkage and optimism-corrected C-index

A model fitted on a data set always performs a bit better on that same data set than on new patients. This is called optimism or overfitting, and it is worse when the model was built using variable selection. In this part you use bootstrapping to:

  1. estimate a uniform shrinkage factor that shrinks the coefficients towards zero, which makes predictions less extreme and better calibrated on new patients, and
  2. estimate the optimism-corrected C-index, a measure of how well the model separates patients who die early from patients who survive.

The bootstrap repeats the entire backward selection procedure in every bootstrap sample, so the optimism includes the effect of variable selection.

1.1 Bootstrap validation on every imputed data set

For each imputed data set we validate the full model with 200 bootstrap samples. In each bootstrap sample validate() repeats backward selection (bw=TRUE, p < 0.05), refits the model, and tests it on the original data. Three quantities are stored:

  • the calibration slope in the test data (Slope, column test), which is our shrinkage factor,
  • the apparent Dxy of the final model on the development data (Dxy, column index.orig), and
  • the optimism in Dxy (Dxy, column optimism).

Dxy is converted to the C-index with C = Dxy/2 + 0.5, so the optimism in the C-index is the optimism in Dxy divided by 2.

# calculate uniform shrinkage factor across all imputations
# start with empty vectors, c() with nothing in it, to collect the results
slope <- c()
apparent_C <- c()
opt_C <- c()

# the for loop repeats the code between { } for every imputed data set i = 1, ..., m
for (i in 1:m){
  # validate() arguments:
  #   full_model$fits[[i]]: the model fitted on the i-th imputed data set
  #   B=200: the number of bootstrap samples
  #   bw=TRUE, rule="p", sls=0.05, type="individual": repeat the backward
  #     selection (p < 0.05) in every bootstrap sample
  #   tol: how strict R is about nearly identical predictors (here: less strict)
  val_model <- rms::validate(full_model$fits[[i]],
                        B=200,
                        bw=TRUE,
                        rule="p",
                        sls=0.05,
                        type="individual", tol=1e-13)

  # val_model["Slope", "test"] takes the cell in row "Slope" and column "test";
  # c(old, new) adds the new value to the end of the vector
  slope <- c(slope, val_model["Slope", "test"])
  # Dxy is converted to the C-index: C = Dxy/2 + 0.5
  apparent_C <- c(apparent_C, val_model["Dxy", "index.orig"]/2 + 0.5)
  opt_C <- c(opt_C, val_model["Dxy", "optimism"]/2)
}
## 
##      Backwards Step-down - Original Model
## 
##  Deleted Chi-Sq d.f. P      Residual d.f. P      AIC  
##  sex     0.11   1    0.7352 0.11     1    0.7352 -1.89
##  hepato  0.57   1    0.4521 0.68     2    0.7119 -3.32
##  ascites 2.16   1    0.1416 2.84     3    0.4170 -3.16
## 
## Approximate Estimates after Deleting Factors
## 
##             Coef    S.E.  Wald Z         P
## age      0.06839 0.02381  2.8723 4.074e-03
## age'    -0.03714 0.02236 -1.6610 9.672e-02
## bili     0.80138 0.10722  7.4745 7.749e-14
## bili'   -2.33364 0.37180 -6.2767 3.459e-10
## albumin -0.83717 0.19953 -4.1956 2.721e-05
## stage=2  0.37999 0.74155  0.5124 6.084e-01
## stage=3  0.79708 0.72348  1.1017 2.706e-01
## stage=4  1.25171 0.72724  1.7212 8.522e-02
## 
## Factors in Final Model
## 
## [1] age     bili    albumin stage
## 
##      Backwards Step-down - Original Model
## 
##  Deleted Chi-Sq d.f. P      Residual d.f. P      AIC  
##  hepato  0.02   1    0.9005 0.02     1    0.9005 -1.98
##  sex     0.19   1    0.6648 0.20     2    0.9033 -3.80
##  ascites 2.89   1    0.0891 3.09     3    0.3774 -2.91
## 
## Approximate Estimates after Deleting Factors
## 
##             Coef    S.E.  Wald Z         P
## age      0.06744 0.02387  2.8257 4.717e-03
## age'    -0.03699 0.02244 -1.6490 9.916e-02
## bili     0.79170 0.10823  7.3147 2.580e-13
## bili'   -2.29741 0.37576 -6.1140 9.717e-10
## albumin -0.82819 0.20013 -4.1383 3.499e-05
## stage=2  0.31366 0.74301  0.4222 6.729e-01
## stage=3  0.78342 0.72401  1.0821 2.792e-01
## stage=4  1.29211 0.72657  1.7784 7.534e-02
## 
## Factors in Final Model
## 
## [1] age     bili    albumin stage
## 
##      Backwards Step-down - Original Model
## 
##  Deleted Chi-Sq d.f. P      Residual d.f. P      AIC  
##  hepato  0.01   1    0.9156 0.01     1    0.9156 -1.99
##  sex     0.09   1    0.7694 0.10     2    0.9526 -3.90
## 
## Approximate Estimates after Deleting Factors
## 
##                 Coef    S.E.  Wald Z         P
## age          0.07121 0.02401  2.9652 3.024e-03
## age'        -0.04477 0.02273 -1.9701 4.882e-02
## bili         0.79408 0.10861  7.3113 2.647e-13
## bili'       -2.34493 0.37721 -6.2165 5.084e-10
## albumin     -0.72450 0.20710 -3.4983 4.682e-04
## ascites=Yes  0.58661 0.23893  2.4552 1.408e-02
## stage=2      0.34249 0.74112  0.4621 6.440e-01
## stage=3      0.83950 0.72398  1.1596 2.462e-01
## stage=4      1.21614 0.72765  1.6713 9.466e-02
## 
## Factors in Final Model
## 
## [1] age     bili    albumin ascites stage
## 
##      Backwards Step-down - Original Model
## 
##  Deleted Chi-Sq d.f. P      Residual d.f. P      AIC  
##  sex     0.10   1    0.7493 0.10     1    0.7493 -1.90
##  hepato  0.46   1    0.4971 0.56     2    0.7546 -3.44
##  ascites 3.23   1    0.0721 3.80     3    0.2842 -2.20
## 
## Approximate Estimates after Deleting Factors
## 
##             Coef    S.E.  Wald Z         P
## age      0.06761 0.02398  2.8190 4.817e-03
## age'    -0.03617 0.02249 -1.6079 1.079e-01
## bili     0.78418 0.10885  7.2039 5.850e-13
## bili'   -2.27420 0.37683 -6.0350 1.589e-09
## albumin -0.87070 0.20159 -4.3192 1.566e-05
## stage=2 -0.07336 0.62166 -0.1180 9.061e-01
## stage=3  0.43475 0.59812  0.7269 4.673e-01
## stage=4  0.91167 0.60111  1.5166 1.294e-01
## 
## Factors in Final Model
## 
## [1] age     bili    albumin stage
## 
##      Backwards Step-down - Original Model
## 
##  Deleted Chi-Sq d.f. P      Residual d.f. P      AIC  
##  sex     0.09   1    0.7581 0.09     1    0.7581 -1.91
##  hepato  0.11   1    0.7349 0.21     2    0.9005 -3.79
##  ascites 3.17   1    0.0751 3.38     3    0.3371 -2.62
## 
## Approximate Estimates after Deleting Factors
## 
##             Coef    S.E.  Wald Z         P
## age      0.06739 0.02384  2.8261 4.712e-03
## age'    -0.03690 0.02235 -1.6510 9.873e-02
## bili     0.78875 0.10923  7.2208 5.167e-13
## bili'   -2.28764 0.37968 -6.0252 1.689e-09
## albumin -0.84314 0.20032 -4.2090 2.565e-05
## stage=2  0.33196 0.74363  0.4464 6.553e-01
## stage=3  0.77505 0.72399  1.0705 2.844e-01
## stage=4  1.29600 0.72649  1.7839 7.444e-02
## 
## Factors in Final Model
## 
## [1] age     bili    albumin stage

1.2 Shrinkage factor

The shrinkage factor is the mean slope across imputations. A value below 1 indicates overfitting: the model’s predictions are too extreme, and multiplying the coefficients by the factor corrects this.

First look at the slope of every imputed data set. If they differ a lot, a single average would hide that:

# slope of each imputed data set
round(slope, 3)
## [1] 0.905 0.916 0.912 0.913 0.896
# how much do they vary around the mean?
round(c(mean=mean(slope), sd=sd(slope), min=min(slope), max=max(slope)), 3)
##  mean    sd   min   max 
## 0.908 0.008 0.896 0.916

The variation between imputations comes from the different imputed values and from the random bootstrap samples. Here the slopes are close to their mean, so averaging over the imputations gives one sensible shrinkage factor:

# shrinkage factor: the mean of the slopes over the imputed data sets
shrinkage_factor <- mean(slope)
# cat() prints text and values; "\n" starts a new line
cat("Shrinkage factor:", shrinkage_factor, "\n")
## Shrinkage factor: 0.9083665
# shrunk coefficients: multiply every coefficient of the final model by the factor
shrinkage_factor*bw_model$coefficients
##         age        age'        bili       bili'     albumin     stage=2 
##  0.06139414 -0.03347546  0.71964386 -2.09131974 -0.76150109  0.24212356 
##     stage=3     stage=4 
##  0.65159136  1.08803671

1.3 Optimism-corrected C-index

The C-index (also called the c-statistic) measures discrimination: how well the model distinguishes between low- and high-risk patients.

  • 0.5 means that the model performs no better than random guessing in distinguishing between low- and high-risk patients.
  • 1 means that the model perfectly distinguishes between low- and high-risk patients.

The validate() object already contains the C-index of the final model, so we do not have to calculate it ourselves. Here is the output for the last imputed data set (val_model from the loop above):

# output of validate() for the last imputed data set
val_model
##       index.orig training   test optimism index.corrected   n
## Dxy       0.6711   0.6868 0.6665   0.0203          0.6507 200
## R2        0.4309   0.4508 0.4133   0.0375          0.3934 200
## Slope     1.0000   1.0000 0.8962   0.1038          0.8962 200
## D         0.1338   0.1430 0.1266   0.0164          0.1174 200
## U        -0.0012  -0.0012 0.0039  -0.0051          0.0039 200
## Q         0.1349   0.1442 0.1227   0.0215          0.1134 200
## g         1.6010   1.7179 1.5335   0.1844          1.4166 200
## 
## Factors Retained in Backwards Elimination
## 
##  age bili albumin sex ascites hepato stage
##  *   *    *                          *    
##  *   *    *       *                  *    
##  *   *    *       *   *              *    
##  *   *    *                          *    
##  *   *    *           *              *    
##  *   *                *              *    
##  *   *    *           *              *    
##  *   *    *                          *    
##  *   *    *                          *    
##  *   *    *                          *    
##  *   *    *           *       *      *    
##  *   *                *              *    
##  *   *    *           *       *      *    
##  *   *                *              *    
##  *   *    *       *                  *    
##  *   *    *                          *    
##  *   *                *              *    
##  *   *    *           *              *    
##  *   *    *                          *    
##  *   *    *                          *    
##  *   *    *                   *           
##  *   *    *           *              *    
##  *   *    *                          *    
##  *   *                *              *    
##  *   *    *       *   *              *    
##  *   *    *           *                   
##  *   *    *                          *    
##  *   *    *       *                  *    
##  *   *    *           *              *    
##  *   *    *                          *    
##  *   *    *           *       *      *    
##  *   *    *           *              *    
##  *   *    *                          *    
##  *   *                               *    
##  *   *    *           *              *    
##  *   *    *                          *    
##  *   *    *           *              *    
##  *   *    *                          *    
##  *   *    *           *              *    
##  *   *    *                          *    
##  *   *    *           *                   
##  *   *    *       *   *                   
##  *   *    *                          *    
##  *   *    *           *              *    
##  *   *    *       *   *              *    
##  *   *    *           *       *           
##  *   *    *                          *    
##  *   *    *                          *    
##  *   *    *           *                   
##  *   *    *           *              *    
##  *   *    *           *              *    
##  *   *    *       *   *              *    
##  *   *                *              *    
##  *   *    *           *       *      *    
##  *   *    *           *              *    
##  *   *    *           *       *           
##  *   *    *           *              *    
##  *   *    *           *       *           
##  *   *    *                          *    
##  *   *    *           *              *    
##  *   *    *           *              *    
##  *   *    *           *              *    
##  *   *    *                          *    
##      *    *           *                   
##  *   *    *           *              *    
##  *   *    *           *              *    
##  *   *    *           *              *    
##  *   *    *                          *    
##  *   *    *           *              *    
##  *   *    *           *              *    
##  *   *    *           *              *    
##  *   *    *           *              *    
##  *   *    *       *                  *    
##  *   *    *                          *    
##  *   *    *                   *      *    
##  *   *    *           *              *    
##  *   *    *                          *    
##  *   *    *                          *    
##  *   *    *                          *    
##  *   *                *       *      *    
##  *   *    *           *                   
##  *   *    *           *              *    
##  *   *    *                          *    
##  *   *                *              *    
##  *   *    *       *                  *    
##  *   *                               *    
##  *   *    *       *   *              *    
##  *   *    *                          *    
##  *   *    *                          *    
##  *   *    *                          *    
##  *   *    *       *   *              *    
##  *   *    *                               
##  *   *    *                          *    
##  *   *    *       *   *                   
##  *   *            *   *              *    
##  *   *    *                          *    
##  *   *    *                          *    
##  *   *    *                          *    
##  *   *    *                          *    
##  *   *    *                          *    
##  *   *    *           *              *    
##  *   *    *           *                   
##  *   *    *           *              *    
##  *   *    *                          *    
##  *   *    *                          *    
##  *   *    *                          *    
##  *   *    *                          *    
##  *   *    *           *                   
##  *   *    *                          *    
##  *   *    *                          *    
##  *   *                *              *    
##  *   *    *           *              *    
##      *    *       *   *              *    
##  *   *    *           *              *    
##  *   *    *           *              *    
##      *    *           *              *    
##  *   *    *                          *    
##  *   *    *                          *    
##      *    *           *              *    
##  *   *    *                          *    
##  *   *    *                          *    
##  *   *    *                          *    
##  *   *    *                          *    
##  *   *    *       *   *              *    
##  *   *    *                   *      *    
##  *   *                *              *    
##  *   *    *                          *    
##  *   *    *                          *    
##  *   *    *           *              *    
##  *   *    *                          *    
##  *   *    *                          *    
##  *   *    *       *   *              *    
##  *   *    *       *   *                   
##  *   *    *       *                  *    
##  *   *    *                          *    
##  *   *    *           *              *    
##  *   *    *           *              *    
##  *   *    *       *   *              *    
##  *   *    *           *              *    
##  *   *    *       *   *              *    
##  *   *    *                          *    
##  *   *    *                          *    
##  *   *    *                          *    
##  *   *    *                   *      *    
##  *   *    *           *              *    
##  *   *    *           *       *           
##  *   *    *       *                  *    
##  *   *    *           *              *    
##  *   *    *                               
##  *   *    *                          *    
##  *   *    *           *       *           
##  *   *    *                          *    
##  *   *                               *    
##  *   *    *                          *    
##  *   *    *                          *    
##      *    *                          *    
##  *   *    *           *                   
##  *   *    *                          *    
##  *   *    *                          *    
##  *   *    *                          *    
##  *   *    *           *              *    
##  *   *    *                          *    
##  *   *    *           *              *    
##  *   *    *       *                  *    
##  *   *    *           *              *    
##  *   *    *       *   *              *    
##  *   *    *                          *    
##  *   *    *                          *    
##  *   *    *                          *    
##  *   *    *           *              *    
##  *   *    *           *              *    
##  *   *                               *    
##  *   *    *           *              *    
##      *    *           *              *    
##  *   *    *                               
##  *   *    *                          *    
##  *   *    *           *              *    
##  *   *    *           *              *    
##  *   *    *           *              *    
##  *   *    *           *              *    
##  *   *    *                          *    
##  *   *    *       *   *              *    
##  *   *    *           *              *    
##      *    *       *   *              *    
##  *   *    *       *                  *    
##  *   *                        *      *    
##  *   *    *           *       *      *    
##  *   *    *       *                  *    
##  *   *    *                          *    
##  *   *    *                          *    
##  *   *    *                          *    
##  *   *    *                          *    
##  *   *    *                          *    
##  *   *    *                          *    
##  *   *    *           *              *    
##  *   *    *                          *    
##  *   *    *           *              *    
##  *   *    *           *              *    
##  *   *                *              *    
##  *   *    *                               
## 
## Frequencies of Numbers of Factors Retained
## 
##  3  4  5  6 
## 10 97 76 17

Each row is a performance measure (Dxy is the C-index on another scale: C = Dxy/2 + 0.5, Slope is the calibration slope). The columns are:

  • index.orig: the apparent performance of the final model on the development data.
  • training: the average performance of the model developed in each bootstrap sample, evaluated on that same bootstrap sample.
  • test: the average performance of the model developed in each bootstrap sample, evaluated on the original data.
  • optimism: training minus test, how much the apparent performance is too optimistic.
  • index.corrected: index.orig minus optimism, the optimism-corrected performance.
  • n: the number of bootstrap samples that were used.

For the C-index we take index.orig and the optimism of Dxy from every imputed data set (stored in apparent_C and opt_C above), average them over the imputations and subtract the mean optimism:

# optimism-corrected C-index: mean(x) averages a vector over the imputed data sets
optimism_C <- mean(opt_C)
original_C <- mean(apparent_C)
cat("Original C-index:", round(original_C, 3), "\n",
    "Optimism:", round(optimism_C, 3), "\n",
    "Optimism-corrected C-index:", round(original_C - optimism_C, 3), "\n")
## Original C-index: 0.836 
##  Optimism: 0.009 
##  Optimism-corrected C-index: 0.827

Note on the confidence interval. validate() only returns the averages over the bootstrap samples, not a variance or the C-index of every bootstrap sample, so it does not give a confidence interval. If you want one, you have to code the bootstrap yourself: repeat the whole procedure (imputation, variable selection, fitting, validation) in bootstrap samples and take the percentiles of the optimism-corrected C-index.

1.4 Exercises: Part 1

Try each exercise yourself first. Then open the field below it to see the answer, the code and its results. The answers use new object names. Where they use random numbers, they restore the state of the random number generator afterwards, so the rest of the tutorial gives the same numbers with or without them.

Exercise 1. Compare the original C-index (original_C) with the optimism-corrected one. How large is the optimism, and is this a lot for this model?

Show answer, code and results
# original C-index, optimism and corrected C-index
round(c(original=original_C,
        optimism=optimism_C,
        corrected=original_C - optimism_C), 3)
##  original  optimism corrected 
##     0.836     0.009     0.827
# how much information do we have compared with the number of parameters?
n_events <- sum(S_10[, "status"])
n_param <- length(full_model$coefficients)
c(events=n_events, parameters=n_param, events_per_parameter=round(n_events/n_param, 1))
##               events           parameters events_per_parameter 
##                156.0                 11.0                 14.2

The optimism is the difference between the original and the corrected C-index. Optimism is larger when there are few events compared with the number of candidate parameters, so the last line gives a feeling for how much overfitting to expect. Whether the optimism is “a lot” depends on how it compares with the C-index you need for your purpose.

Exercise 2. Change B=200 to B=50 and back to B=500. How stable is the shrinkage factor?

Show answer, code and results

To keep this quick we use only the first imputed data set.

# save the state of the random number generator, so that this exercise does not
# change the random numbers used in the rest of the tutorial
old_seed <- .Random.seed

B_values <- c(50, 200, 500)
slope_B <- c()
for (B in B_values){
  val <- rms::validate(full_model$fits[[1]], B=B, bw=TRUE,
                       rule="p", sls=0.05, type="individual", tol=1e-13)
  slope_B <- c(slope_B, val["Slope", "test"])
}
## 
##      Backwards Step-down - Original Model
## 
##  Deleted Chi-Sq d.f. P      Residual d.f. P      AIC  
##  sex     0.11   1    0.7352 0.11     1    0.7352 -1.89
##  hepato  0.57   1    0.4521 0.68     2    0.7119 -3.32
##  ascites 2.16   1    0.1416 2.84     3    0.4170 -3.16
## 
## Approximate Estimates after Deleting Factors
## 
##             Coef    S.E.  Wald Z         P
## age      0.06839 0.02381  2.8723 4.074e-03
## age'    -0.03714 0.02236 -1.6610 9.672e-02
## bili     0.80138 0.10722  7.4745 7.749e-14
## bili'   -2.33364 0.37180 -6.2767 3.459e-10
## albumin -0.83717 0.19953 -4.1956 2.721e-05
## stage=2  0.37999 0.74155  0.5124 6.084e-01
## stage=3  0.79708 0.72348  1.1017 2.706e-01
## stage=4  1.25171 0.72724  1.7212 8.522e-02
## 
## Factors in Final Model
## 
## [1] age     bili    albumin stage  
## 
##      Backwards Step-down - Original Model
## 
##  Deleted Chi-Sq d.f. P      Residual d.f. P      AIC  
##  sex     0.11   1    0.7352 0.11     1    0.7352 -1.89
##  hepato  0.57   1    0.4521 0.68     2    0.7119 -3.32
##  ascites 2.16   1    0.1416 2.84     3    0.4170 -3.16
## 
## Approximate Estimates after Deleting Factors
## 
##             Coef    S.E.  Wald Z         P
## age      0.06839 0.02381  2.8723 4.074e-03
## age'    -0.03714 0.02236 -1.6610 9.672e-02
## bili     0.80138 0.10722  7.4745 7.749e-14
## bili'   -2.33364 0.37180 -6.2767 3.459e-10
## albumin -0.83717 0.19953 -4.1956 2.721e-05
## stage=2  0.37999 0.74155  0.5124 6.084e-01
## stage=3  0.79708 0.72348  1.1017 2.706e-01
## stage=4  1.25171 0.72724  1.7212 8.522e-02
## 
## Factors in Final Model
## 
## [1] age     bili    albumin stage  
## 
##      Backwards Step-down - Original Model
## 
##  Deleted Chi-Sq d.f. P      Residual d.f. P      AIC  
##  sex     0.11   1    0.7352 0.11     1    0.7352 -1.89
##  hepato  0.57   1    0.4521 0.68     2    0.7119 -3.32
##  ascites 2.16   1    0.1416 2.84     3    0.4170 -3.16
## 
## Approximate Estimates after Deleting Factors
## 
##             Coef    S.E.  Wald Z         P
## age      0.06839 0.02381  2.8723 4.074e-03
## age'    -0.03714 0.02236 -1.6610 9.672e-02
## bili     0.80138 0.10722  7.4745 7.749e-14
## bili'   -2.33364 0.37180 -6.2767 3.459e-10
## albumin -0.83717 0.19953 -4.1956 2.721e-05
## stage=2  0.37999 0.74155  0.5124 6.084e-01
## stage=3  0.79708 0.72348  1.1017 2.706e-01
## stage=4  1.25171 0.72724  1.7212 8.522e-02
## 
## Factors in Final Model
## 
## [1] age     bili    albumin stage
names(slope_B) <- paste0("B=", B_values)
round(slope_B, 3)
##  B=50 B=200 B=500 
## 0.898 0.893 0.908
# restore the random number generator
assign(".Random.seed", old_seed, envir=globalenv())

With few bootstrap samples the shrinkage factor is noisier; with more samples the estimates become more stable and are closer to each other. Run it again with another seed to see how much the estimate changes by chance alone.

Exercise 3. Validate the backward selected model bw_model on its own (bw=FALSE, so without repeating the variable selection), and compare it with the validation of the full model with variable selection in every bootstrap sample (bw=TRUE, as above). Use the first imputed data set. Compare the optimism in Dxy and the slope. Which one gives the larger optimism, and why?

Show answer, code and results
# save the state of the random number generator
old_seed <- .Random.seed

# final model only: the variable selection is not repeated
val_final <- rms::validate(bw_model$fits[[1]], B=200, bw=FALSE)

# full model with backward selection repeated in every bootstrap sample
val_strategy <- rms::validate(full_model$fits[[1]], B=200, bw=TRUE,
                              rule="p", sls=0.05, type="individual", tol=1e-13)
## 
##      Backwards Step-down - Original Model
## 
##  Deleted Chi-Sq d.f. P      Residual d.f. P      AIC  
##  sex     0.11   1    0.7352 0.11     1    0.7352 -1.89
##  hepato  0.57   1    0.4521 0.68     2    0.7119 -3.32
##  ascites 2.16   1    0.1416 2.84     3    0.4170 -3.16
## 
## Approximate Estimates after Deleting Factors
## 
##             Coef    S.E.  Wald Z         P
## age      0.06839 0.02381  2.8723 4.074e-03
## age'    -0.03714 0.02236 -1.6610 9.672e-02
## bili     0.80138 0.10722  7.4745 7.749e-14
## bili'   -2.33364 0.37180 -6.2767 3.459e-10
## albumin -0.83717 0.19953 -4.1956 2.721e-05
## stage=2  0.37999 0.74155  0.5124 6.084e-01
## stage=3  0.79708 0.72348  1.1017 2.706e-01
## stage=4  1.25171 0.72724  1.7212 8.522e-02
## 
## Factors in Final Model
## 
## [1] age     bili    albumin stage
# optimism in Dxy and the (test) slope
round(rbind(final_model_only=c(optimism_Dxy=val_final["Dxy", "optimism"],
                               slope=val_final["Slope", "test"]),
            whole_strategy=c(optimism_Dxy=val_strategy["Dxy", "optimism"],
                             slope=val_strategy["Slope", "test"])), 3)
##                  optimism_Dxy slope
## final_model_only        0.017 0.927
## whole_strategy          0.023 0.896
# restore the random number generator
assign(".Random.seed", old_seed, envir=globalenv())

Validating only the final model gives a smaller optimism and a slope closer to 1. This is because variable selection uses the outcome: the variables that remain are partly the ones that look good in this particular data set, also by chance. If you only validate the final, fixed model, you ignore this extra source of overfitting and underestimate the optimism. By repeating the selection in every bootstrap sample, the validation checks the whole modelling strategy, not only the final model.

2 Model performance

A prediction model is more than a C-index. In this part you calculate predicted 10-year risks for three models (the full model, the backward selected model, and the backward selected model with shrinkage), check how well the predicted risks agree with the observed risks (calibration), and assess the clinical usefulness of the model with a decision curve analysis.

2.1 Calculate predictions and make calibration plots

For each of the three models and for each imputed data set we do the following:

  1. Calculate the linear predictor (lp) for each patient. For the shrunk model this is the linear predictor of the backward selected model multiplied by the shrinkage factor.
  2. Estimate the baseline hazard at the 10-year horizon. For the shrunk model the baseline hazard has to be re-estimated, because the linear predictor changed. This is done with a Cox model that only has the linear predictor as an offset.
  3. Turn these into predicted risks at 10 years with fun.event().
  4. Make a calibration plot with val.surv.mi(), which pools the performance over the imputed data sets.

Important: this is apparent performance. All performance measures in this part are calculated on the same data that were used to develop the model, so they ignore optimism (overfitting) and are too optimistic. The optimism-corrected C-index in Part 1 corrects for this for the C-index only. For calibration and decision curves you would need to repeat the evaluation with a bootstrap or in new patients (see the internal-external cross-validation in Part 3).

# The three models we compare, and the title of each calibration plot.
# The shrunk model is the backward selected model with shrunk coefficients.
model_names   <- c("full", "bw", "shrunk")
model_titles  <- c(full   = "Full model",
                   bw     = "Final model without shrinkage",
                   shrunk = "Shrunk model")
fitted_models <- list(full=full_model, bw=bw_model, shrunk=bw_model)
model_formulas <- list(full=form_full, bw=form_bw)

# Open one png file with three panels next to each other: one calibration plot
# per model
png(filename="model_performance.png", width=3600, height=1400, res=300)
layout(matrix(1:3, ncol=3, byrow=TRUE))

for (model_name in model_names){
  cat("Now making plot for", model_name, "model \n")

  # predicted 10-year risks of all patients: one column per imputed data set
  predicted_risks <- c()

  for (i in 1:m){
    # the i-th imputed data set
    imputed_i <- mice::complete(imputed_data, i)

    # Step 1: linear predictor of every patient -------------------------------
    lp_i <- predict(fitted_models[[model_name]]$fits[[i]],
                    newdata=imputed_i, type="lp")
    if (model_name == "shrunk"){
      lp_bw_i <- lp_i                     # linear predictor without shrinkage
      lp_i <- shrinkage_factor*lp_bw_i    # shrink it towards zero
    }

    # Step 2: baseline hazard at the horizon ----------------------------------
    if (model_name == "shrunk"){
      # the linear predictor changed, so re-estimate the baseline hazard with a
      # Cox model that only has the linear predictor as an offset
      baseline_fit <- survival::coxph(S_10 ~ offset(lp_i), data=imputed_i)
    } else {
      # refit the Cox model on this imputed data set
      baseline_fit <- rms::cph(model_formulas[[model_name]], data=imputed_i,
                               x=TRUE, y=TRUE, se.fit=TRUE)
    }
    baseline_hazard <- survival::basehaz(baseline_fit)
    # cumulative baseline hazard at the last event time before the horizon
    last_time <- max(baseline_hazard$time[baseline_hazard$time <= horizon])
    h0_i <- baseline_hazard$hazard[baseline_hazard$time == last_time]

    # Step 3: predicted risk at the horizon -----------------------------------
    predicted_risks <- cbind(predicted_risks,
                             PredictionTools::fun.event(lp=lp_i, h0=h0_i))
  }

  # A spline is linear in the tails, so a patient with a very extreme value (for
  # example a very high bilirubin) can get a predicted risk of exactly 1, which
  # the calibration plot cannot handle: keep predictions strictly between 0 and 1
  predicted_risks <- as.matrix(predicted_risks)
  predicted_risks <- pmin(pmax(predicted_risks, 0.0001), 0.9999)

  # save under a name per model: p_full, p_bw and p_shrunk
  assign(paste0("p_", model_name), predicted_risks)

  # Step 4: calibration plot, in the next panel of the png --------------------
  PredictionTools::val.surv.mi(p=predicted_risks, y=S_10, time=horizon, g=3,
                               main=model_titles[[model_name]],
                               CI.metrics=TRUE,
                               show.metrics=c(rep(TRUE, 6), FALSE))
}
## Now making plot for full model
## Now making plot for bw model
## Now making plot for shrunk model
dev.off()
## png 
##   2

2.1.1 Look at the calibration plots

The plots show predicted risk (x-axis) against observed risk (y-axis), with patients grouped by predicted risk. Points on the diagonal mean perfect calibration. The metrics in the plot summarize this (for example calibration intercept and slope). How to read them:

  • The calibration intercept < 0 indicates overestimation of the risk, and an intercept > 0 indicates underestimation.
  • The calibration slope < 1 indicates overfitting, i.e., a weaker than expected strength of the association between the predictors and the outcome. A slope > 1 indicates underfitting, i.e., a stronger than expected strength of the association between the predictors and the outcome.

2.2 Do the models predict well on average?

Calibration-in-the-large: the average predicted risk should be close to the observed 10-year risk from the Kaplan-Meier curve (first number).

# check if models predict well on average
# observed 10-year risk: 1 minus the Kaplan-Meier survival at the horizon
1-summary(survival::survfit(S_10~1, data=data), time=horizon)$surv
## [1] 0.5578323
# average predicted risk: rowMeans() averages the predictions of each patient
# over the imputed data sets, mean() averages those over all patients
mean(rowMeans(p_full))
## [1] 0.5710267
mean(rowMeans(p_bw))
## [1] 0.5717457
mean(rowMeans(p_shrunk))
## [1] 0.5829371

The unshrunk models fit the development data best. The shrunk model is the furthest from the observed risk: because we shrank the coefficients, its predictions are less extreme and it underfits the development data a little. That is intended: shrinkage gives up some fit on these patients in exchange for better predictions in new patients, where the unshrunk model would be too extreme because it is overfitted. So do not expect the shrunk model to look best on the development data.

2.3 Decision curve analysis (DCA)

A decision curve shows the net benefit of using the model to make decisions (for example, treat patients with a predicted risk above a threshold), compared with treating everyone or no one. A model is clinically useful at the thresholds where its curve lies above both of these reference strategies.

# decision curve analysis (DCA)
# data.frame() puts the outcome (y) and the predicted risk of each patient (Model)
# in a table; dca() calculates the net benefit at the horizon, and the pipe |>
# passes the result on to plot(), which draws the curves (smooth=TRUE smooths them)
dca_plot <- dcurves::dca(y ~ Model,
                         time=horizon,
                         data=data.frame(y=S_10,
                                         Model=rowMeans(p_bw))) |>
  plot(smooth = TRUE)
dca_plot

The histogram shows how the predicted risks are distributed. Decision curves are most reliable in the range of thresholds where there are enough patients.

# histogram of the predicted risks: ggplot figures are built in layers that are
# added with +
hist_pred <- ggplot2::ggplot(data=data.frame(Predictions=rowMeans(p_bw)),
                             ggplot2::aes(x=Predictions)) +
  ggplot2::geom_histogram(fill="#619bff", color="grey", alpha=0.6, binwidth=0.01) +
  ggplot2::ylab("Count") +
  ggplot2::theme_minimal() +
  ggplot2::theme(panel.background = ggplot2::element_rect(fill="white", color=NA),
        plot.background = ggplot2::element_rect(fill="white", color=NA))
hist_pred

# save: ggarrange() puts the two figures below each other, ggsave() writes
# the result to a png file in the working directory
ggplot2::ggsave(file="dca.png",
       plot=ggpubr::ggarrange(dca_plot, hist_pred,
                      nrow=2, ncol=1, heights=c(2, 1), align="v"),
       width=16, height=16, units="cm", dpi=300)

2.4 Exercises: Part 2

Try each exercise yourself first. Then open the field below it to see the answer, the code and its results.

Exercise 1. Compare the three calibration plots. Which model has the best calibration slope? Does this match what you expect from shrinkage?

Show answer, code and results

We calculate the calibration slope ourselves for the last imputed data set (the objects lp_bw_i and lp_i are left over from the last round of the loop above): the slope of a Cox model with the linear predictor as the only predictor.

cal_slope_bw <- coef(survival::coxph(S_10 ~ lp_bw_i))
cal_slope_shrunk <- coef(survival::coxph(S_10 ~ lp_i))
round(c(without_shrinkage=cal_slope_bw,
        shrunk=cal_slope_shrunk,
        one_over_shrinkage_factor=1/shrinkage_factor), 3)
## without_shrinkage.lp_bw_i               shrunk.lp_i one_over_shrinkage_factor 
##                     1.000                     1.101                     1.101

On the development data, a fitted Cox model has a calibration slope of 1 by construction, so the model without shrinkage looks perfect, while the shrunk model has a slope of 1 divided by the shrinkage factor, which is above 1. You will most likely see the same pattern in the plots (the plotted slope may be calculated slightly differently, but the idea is the same). So the answer does not match what you might expect from shrinkage, and that is the point: shrinkage makes the model look worse in the data it was developed on, but it improves calibration in new patients, because the shrinkage factor was estimated with the bootstrap to correct for exactly this optimism.

Exercise 2. Is the average predicted risk close to the observed 10-year risk for all three models?

Show answer, code and results
observed_risk <- 1-summary(survival::survfit(S_10~1, data=data), time=horizon)$surv
round(c(observed=observed_risk,
        full=mean(rowMeans(p_full)),
        bw=mean(rowMeans(p_bw)),
        shrunk=mean(rowMeans(p_shrunk))), 3)
## observed     full       bw   shrunk 
##    0.558    0.571    0.572    0.583

If the average predicted risks are close to the observed risk, the models are well calibrated “in the large”. Note that for the shrunk model we re-estimated the baseline hazard after shrinking the linear predictor, but it still fits the development data a bit less well than the unshrunk models: due to the shrinkage it underfits these patients, in order to predict new patients better.

Exercise 3. At which threshold probabilities does the model have a higher net benefit than treating everyone or no one?

Show answer, code and results

Read this from the decision curve plot: the model is useful at the thresholds where its line is above both the “treat all” and the “treat none” lines. Only look at thresholds where there are patients with such predicted risks:

range(rowMeans(p_bw))
## [1] 0.05037855 0.99990000

Outside this range of predicted risks the curve is not informative, because (almost) no patients have such a predicted risk. Also note that the decision curve is based on the development data, so it is optimistic.

Exercise 4. The DCA above uses p_bw. Replace it with p_shrunk (the final model with shrinkage). Does the curve change?

Show answer, code and results
dca_shrunk <- dcurves::dca(y ~ Model,
                           time=horizon,
                           data=data.frame(y=S_10,
                                           Model=rowMeans(p_shrunk))) |>
  plot(smooth = TRUE)
dca_shrunk

Shrinkage does not change the ranking of the patients (so the C-index is the same), but it makes the predicted risks less extreme. A decision curve depends on the predicted risks, because the threshold is applied to them, so the curve can still change somewhat.

Exercise 5. Why are these performance measures optimistic, and how would you get an honest estimate?

Show answer

There is no code for this one. The models were developed and evaluated on the same patients, so the performance measures partly reflect noise in this data set that the model has fitted. For an honest estimate, evaluate the model on data that were not used to develop it: new patients (external validation), or repeat the whole modelling procedure in bootstrap samples and correct for the optimism (as we did for the C-index in Part 1), or use internal-external cross-validation (Part 3).

3 Internal-external cross-validation

Optimism-corrected performance (Part 1) uses random bootstrap samples. If your data come from several centers or regions, you can do something stronger: internal-external cross-validation. You leave out one center, develop the whole model (including variable selection and shrinkage) on the other centers, and test it on the center that was left out. You repeat this for every center. This shows how well the modelling strategy generalizes to new settings.

Note: the center variable in this tutorial is artificial (it alternates between 1 and 2), so there is no real heterogeneity between centers. This is fine for a demonstration of the code, but the results will not tell you anything about transportability.

3.1 Prepare the term names of the full model

Backward selection with fastbw() returns the names of the variables that were kept, for example age. But in the full model age and bilirubin are entered as rcs(age, 3) and rcs(bili, 3) (splines, so non-linear effects). If we would build the new formula from the variable names only, they would become linear. We therefore look up the full term specification for every variable in form_full, so the splines are kept.

# term labels of the full model, e.g. "rcs(age, 3)", "sex", ...
# (terms() splits the formula in its terms; the labels are stored as an attribute)
term_labels <- attr(terms(form_full), "term.labels")

# variable name that belongs to each term, e.g. "age", "sex", ...
# sapply() applies a function to every term label; all.vars() finds the variable
# names in a piece of R code and [1] takes the first one
term_vars <- sapply(term_labels, function(x) all.vars(parse(text=x))[1])
data.frame(term_labels, term_vars, row.names=NULL)

3.2 Run the internal-external cross-validation

For each center in turn (the left-out center) we do the following:

  1. Fit the full model on the data excluding the left-out center, and perform backward selection with fastbw(). We use the same settings as in the main analysis (type="individual", p < 0.05).
  2. Refit the selected model on the development centers.
  3. Calculate the shrinkage factor on the development centers with the bootstrap.
  4. Calculate the baseline hazard of the shrunk model on the development centers.
  5. Predict the risk for the patients in the left-out center and make a calibration plot.

Note: the data were imputed once in Tutorial 1, using all centers. Strictly, you should also impute within the development regions only, so that no information from the left-out region leaks into the model. For simplicity we do not do this here.

# Open a png file with one panel per center (one calibration plot per center)
png(file="Internal_external_cross_validation.png",
    width=4000, height=2000, res=300)
layout(matrix(1:2, ncol=2, byrow=TRUE))

# Containers to keep track of what happens in every round of the loop:
# the terms of the final model and its shrinkage factor, starting with the
# model that was developed on all centers (Part 1)
selected_terms_per_model <- paste(names(bw_model$coefficients), collapse=", ")
shrinkage_per_model <- c(shrinkage_factor)

# Every center is left out once ("left_out_center" is the validation center)
for (left_out_center in unique(data$center)){

  # ---- Which patients are used for development and which for validation? ----
  is_development <- data$center != left_out_center   # TRUE for the other centers
  is_validation  <- data$center == left_out_center   # TRUE for the left-out center
  S_10_development <- S_10[is_development, ]         # outcome of development patients
  S_10_validation  <- S_10[is_validation, ]          # outcome of validation patients

  # ---- Step 1: full model on the development centers, then backward selection ----
  # the outcome in the formula must only contain the development patients, so we
  # replace S_10 by the outcome of the development patients
  full_formula_dev <- update(form_full, S_10_development ~ .)
  full_model_dev <- fit.mult.impute(full_formula_dev, cph,
                                    n.impute=m,
                                    fitargs=list(x=TRUE, y=TRUE, surv=TRUE),
                                    xtrans=imputed_data,
                                    data=data,
                                    sub=is_development,   # only the development patients
                                    fit.reps=TRUE)

  # same settings as in the main analysis: type="individual", p < 0.05
  selected_variables <- fastbw(full_model_dev,
                               type="individual",
                               rule="p", sls=0.05)$names.kept

  # ---- Step 2: refit the selected model on the development centers ----
  # use the full term specification so that rcs(age, 3) stays a spline
  selected_terms <- term_labels[term_vars %in% selected_variables]
  selected_terms_per_model <- c(selected_terms_per_model,
                                paste(selected_terms, collapse=", "))
  final_formula_dev <- as.formula(paste("S_10_development ~",
                                        paste(selected_terms, collapse=" + ")))
  final_model_dev <- fit.mult.impute(final_formula_dev, cph,
                                     n.impute=m,
                                     fitargs=list(x=TRUE, y=TRUE, surv=TRUE),
                                     xtrans=imputed_data,
                                     data=data,
                                     sub=is_development,
                                     fit.reps=TRUE)

  # ---- Steps 3-5: for every imputed data set, shrinkage factor and predictions ----
  shrinkage_per_imputation <- c()         # shrinkage factor of each imputed data set
  predicted_risks_validation <- c()       # predicted risks in the left-out center
  for (i in 1:m){
    # the i-th imputed data set, split in development and validation patients
    imputed_i <- mice::complete(imputed_data, i)
    development_i <- imputed_i[is_development, ]
    validation_i  <- imputed_i[is_validation, ]

    # Step 3: shrinkage factor from the bootstrap on the development centers
    # (backward selection is repeated in every bootstrap sample)
    validation_results <- validate(full_model_dev$fits[[i]],
                                   pr=FALSE,
                                   B=200, bw=TRUE, rule="p", sls=0.05,
                                   type="individual", tol=1e-13)
    shrinkage_i <- validation_results["Slope", "test"]
    shrinkage_per_imputation <- c(shrinkage_per_imputation, shrinkage_i)

    # Step 4: baseline hazard of the shrunk model on the development centers
    # linear predictor of the development patients, shrunk towards zero
    lp_development <- predict(final_model_dev$fits[[i]],
                              newdata=development_i, type="lp")
    lp_development_shrunk <- shrinkage_i*lp_development
    # Cox model with only the shrunk linear predictor as offset gives the baseline hazard
    baseline_fit <- survival::coxph(S_10_development ~ offset(lp_development_shrunk))
    baseline_hazard <- survival::basehaz(baseline_fit)
    # cumulative baseline hazard at the last event time before the horizon
    last_time <- max(baseline_hazard$time[baseline_hazard$time <= horizon])
    h0_i <- baseline_hazard$hazard[baseline_hazard$time == last_time]

    # Step 5: predicted risks for the patients in the left-out center
    lp_validation_shrunk <- shrinkage_i*predict(final_model_dev$fits[[i]],
                                                newdata=validation_i, type="lp")
    predicted_risks_validation <- cbind(predicted_risks_validation,
                                        PredictionTools::fun.event(lp=lp_validation_shrunk,
                                                                   h0=h0_i))
  }

  # keep the mean shrinkage factor over the imputed data sets for this round
  shrinkage_per_model <- c(shrinkage_per_model, mean(shrinkage_per_imputation))

  # keep predictions strictly between 0 and 1 (see the note above)
  predicted_risks_validation <- pmin(pmax(predicted_risks_validation, 0.0001), 0.9999)

  # ---- Calibration plot for the left-out center ----
  PredictionTools::val.surv.mi(p=predicted_risks_validation, y=S_10_validation,
                               time=horizon, g=2,
                               CI.metrics=TRUE, main=left_out_center,
                               show.metrics=c(rep(TRUE, 6), FALSE))
}
## 
## Wald Statistic Information
## 
## Variance Inflation Factors Due to Imputation:
## 
##         age        age'        bili       bili'     albumin  sex=Female 
##        1.01        1.00        1.01        1.01        1.01        1.01 
## ascites=Yes  hepato=Yes     stage=2     stage=3     stage=4 
##        1.08        1.11        1.11        1.14        1.16 
## 
## Fraction of Missing Information:
## 
##         age        age'        bili       bili'     albumin  sex=Female 
##        0.01        0.00        0.01        0.01        0.01        0.01 
## ascites=Yes  hepato=Yes     stage=2     stage=3     stage=4 
##        0.07        0.10        0.10        0.12        0.13 
## 
## d.f. for t-distribution for Tests of Single Coefficients:
## 
##         age        age'        bili       bili'     albumin  sex=Female 
##    66572.61   332639.56    54254.44    50821.03    62116.70    63436.06 
## ascites=Yes  hepato=Yes     stage=2     stage=3     stage=4 
##      714.21      379.60      393.47      278.47      219.94 
## 
## The following fit components were averaged over the 5 model fits:
## 
##   linear.predictors means stats center 
## 
## 
## Wald Statistic Information
## 
## Variance Inflation Factors Due to Imputation:
## 
##     age    age'    bili   bili' albumin 
##       1       1       1       1       1 
## 
## Fraction of Missing Information:
## 
##     age    age'    bili   bili' albumin 
##       0       0       0       0       0 
## 
## d.f. for t-distribution for Tests of Single Coefficients:
## 
##     age    age'    bili   bili' albumin 
##     Inf     Inf     Inf     Inf     Inf 
## 
## The following fit components were averaged over the 5 model fits:
## 
##   linear.predictors means stats center 
## 
## 
##      Backwards Step-down - Original Model
## 
##  Deleted Chi-Sq d.f. P      Residual d.f. P      AIC  
##  sex     0.09   1    0.7673 0.09     1    0.7673 -1.91
##  stage   1.64   3    0.6512 1.72     4    0.7864 -6.28
##  ascites 0.96   1    0.3265 2.69     5    0.7482 -7.31
## 
## Approximate Estimates after Deleting Factors
## 
##                Coef    S.E.  Wald Z         P
## age         0.03819 0.03166  1.2064 2.277e-01
## age'        0.01178 0.03101  0.3798 7.041e-01
## bili        0.77535 0.15184  5.1065 3.282e-07
## bili'      -1.85404 0.42622 -4.3500 1.361e-05
## albumin    -1.48511 0.28401 -5.2290 1.704e-07
## hepato=Yes  0.56938 0.26562  2.1436 3.206e-02
## 
## Factors in Final Model
## 
## [1] age     bili    albumin hepato
## 
##      Backwards Step-down - Original Model
## 
##  Deleted Chi-Sq d.f. P      Residual d.f. P      AIC  
##  sex     0.06   1    0.8005 0.06     1    0.8005 -1.94
##  stage   2.91   3    0.4050 2.98     4    0.5615 -5.02
##  ascites 1.88   1    0.1699 4.86     5    0.4330 -5.14
##  hepato  3.77   1    0.0520 8.64     6    0.1951 -3.36
## 
## Approximate Estimates after Deleting Factors
## 
##             Coef    S.E.  Wald Z         P
## age      0.03802 0.03159  1.2038 2.287e-01
## age'     0.01198 0.03113  0.3849 7.003e-01
## bili     0.83894 0.15028  5.5827 2.368e-08
## bili'   -2.02884 0.42276 -4.7990 1.594e-06
## albumin -1.57702 0.28483 -5.5366 3.083e-08
## 
## Factors in Final Model
## 
## [1] age     bili    albumin
## 
##      Backwards Step-down - Original Model
## 
##  Deleted Chi-Sq d.f. P      Residual d.f. P      AIC  
##  sex     0.13   1    0.7188 0.13     1    0.7188 -1.87
##  ascites 0.26   1    0.6085 0.39     2    0.8220 -3.61
##  hepato  0.63   1    0.4276 1.02     3    0.7961 -4.98
##  stage   5.16   3    0.1605 6.18     6    0.4033 -5.82
## 
## Approximate Estimates after Deleting Factors
## 
##             Coef    S.E.  Wald Z         P
## age      0.03737 0.03170  1.1788 2.385e-01
## age'     0.01256 0.03118  0.4028 6.871e-01
## bili     0.83756 0.15049  5.5656 2.612e-08
## bili'   -2.03034 0.42394 -4.7892 1.674e-06
## albumin -1.57049 0.28673 -5.4771 4.322e-08
## 
## Factors in Final Model
## 
## [1] age     bili    albumin
## 
##      Backwards Step-down - Original Model
## 
##  Deleted Chi-Sq d.f. P      Residual d.f. P      AIC  
##  stage   1.62   3    0.6556 1.62     3    0.6556 -4.38
##  sex     0.24   1    0.6255 1.85     4    0.7624 -6.15
##  ascites 0.70   1    0.4030 2.55     5    0.7683 -7.45
## 
## Approximate Estimates after Deleting Factors
## 
##                Coef    S.E.  Wald Z         P
## age         0.04137 0.03180  1.3007 1.934e-01
## age'        0.01011 0.03117  0.3244 7.456e-01
## bili        0.78976 0.15021  5.2578 1.458e-07
## bili'      -1.89340 0.42132 -4.4940 6.991e-06
## albumin    -1.44804 0.29169 -4.9644 6.893e-07
## hepato=Yes  0.55255 0.26009  2.1244 3.364e-02
## 
## Factors in Final Model
## 
## [1] age     bili    albumin hepato
## 
##      Backwards Step-down - Original Model
## 
##  Deleted Chi-Sq d.f. P      Residual d.f. P      AIC  
##  sex     0.13   1    0.7212 0.13     1    0.7212 -1.87
##  ascites 0.22   1    0.6364 0.35     2    0.8391 -3.65
##  hepato  0.84   1    0.3580 1.20     3    0.7540 -4.80
##  stage   5.32   3    0.1498 6.52     6    0.3679 -5.48
## 
## Approximate Estimates after Deleting Factors
## 
##             Coef    S.E.  Wald Z         P
## age      0.03770 0.03169  1.1898 2.341e-01
## age'     0.01194 0.03115  0.3833 7.015e-01
## bili     0.83437 0.15027  5.5524 2.818e-08
## bili'   -2.02149 0.42337 -4.7747 1.800e-06
## albumin -1.56362 0.28645 -5.4586 4.798e-08
## 
## Factors in Final Model
## 
## [1] age     bili    albumin
## 
## Wald Statistic Information
## 
## Variance Inflation Factors Due to Imputation:
## 
##         age        age'        bili       bili'     albumin  sex=Female 
##        1.02        1.04        1.03        1.03        1.04        1.03 
## ascites=Yes  hepato=Yes     stage=2     stage=3     stage=4 
##        1.23        1.06        1.01        1.00        1.01 
## 
## Fraction of Missing Information:
## 
##         age        age'        bili       bili'     albumin  sex=Female 
##        0.02        0.04        0.03        0.03        0.04        0.03 
## ascites=Yes  hepato=Yes     stage=2     stage=3     stage=4 
##        0.19        0.06        0.01        0.00        0.01 
## 
## d.f. for t-distribution for Tests of Single Coefficients:
## 
##         age        age'        bili       bili'     albumin  sex=Female 
##     7782.08     2437.34     4020.35     4711.53     2839.42     4813.61 
## ascites=Yes  hepato=Yes     stage=2     stage=3     stage=4 
##      116.07     1090.99   124286.36   863231.11    98640.49 
## 
## The following fit components were averaged over the 5 model fits:
## 
##   linear.predictors means stats center 
## 
## 
## Wald Statistic Information
## 
## Variance Inflation Factors Due to Imputation:
## 
##         age        age'        bili       bili' ascites=Yes 
##        1.02        1.04        1.03        1.02        1.20 
## 
## Fraction of Missing Information:
## 
##         age        age'        bili       bili' ascites=Yes 
##        0.02        0.04        0.03        0.02        0.17 
## 
## d.f. for t-distribution for Tests of Single Coefficients:
## 
##         age        age'        bili       bili' ascites=Yes 
##    11956.63     2561.00     4814.62     8212.24      142.89 
## 
## The following fit components were averaged over the 5 model fits:
## 
##   linear.predictors means stats center 
## 
## 
##      Backwards Step-down - Original Model
## 
##  Deleted Chi-Sq d.f. P      Residual d.f. P      AIC  
##  hepato  0.01   1    0.9357 0.01     1    0.9357 -1.99
##  albumin 0.52   1    0.4729 0.52     2    0.7704 -3.48
##  sex     2.15   1    0.1430 2.67     3    0.4458 -3.33
##  stage   7.14   3    0.0674 9.81     6    0.1328 -2.19
## 
## Approximate Estimates after Deleting Factors
## 
##                Coef    S.E. Wald Z         P
## age          0.1262 0.03983  3.167 1.540e-03
## age'        -0.1083 0.04203 -2.577 9.960e-03
## bili         1.0661 0.17377  6.135 8.519e-10
## bili'       -3.7125 0.68721 -5.402 6.579e-08
## ascites=Yes  0.8852 0.36108  2.452 1.422e-02
## 
## Factors in Final Model
## 
## [1] age     bili    ascites
## 
##      Backwards Step-down - Original Model
## 
##  Deleted Chi-Sq d.f. P      Residual d.f. P      AIC  
##  albumin 0.33   1    0.5680  0.33    1    0.5680 -1.67
##  hepato  0.25   1    0.6197  0.57    2    0.7512 -3.43
##  sex     2.81   1    0.0940  3.38    3    0.3370 -2.62
##  stage   7.78   3    0.0508 11.16    6    0.0837 -0.84
## 
## Approximate Estimates after Deleting Factors
## 
##                Coef    S.E. Wald Z         P
## age          0.1235 0.04043  3.054 2.259e-03
## age'        -0.1114 0.04238 -2.627 8.610e-03
## bili         1.0664 0.17562  6.072 1.262e-09
## bili'       -3.7327 0.69420 -5.377 7.572e-08
## ascites=Yes  1.0448 0.34171  3.058 2.231e-03
## 
## Factors in Final Model
## 
## [1] age     bili    ascites
## 
##      Backwards Step-down - Original Model
## 
##  Deleted Chi-Sq d.f. P      Residual d.f. P      AIC  
##  hepato  0.41   1    0.5219 0.41     1    0.5219 -1.59
##  albumin 0.94   1    0.3310 1.36     2    0.5079 -2.64
##  sex     1.83   1    0.1762 3.18     3    0.3641 -2.82
##  stage   5.34   3    0.1486 8.52     6    0.2022 -3.48
## 
## Approximate Estimates after Deleting Factors
## 
##                Coef    S.E. Wald Z         P
## age          0.1331 0.04186  3.180 1.474e-03
## age'        -0.1186 0.04323 -2.744 6.062e-03
## bili         1.0362 0.17433  5.944 2.784e-09
## bili'       -3.6308 0.68553 -5.296 1.182e-07
## ascites=Yes  1.2113 0.35425  3.419 6.278e-04
## 
## Factors in Final Model
## 
## [1] age     bili    ascites
## 
##      Backwards Step-down - Original Model
## 
##  Deleted Chi-Sq d.f. P      Residual d.f. P      AIC  
##  hepato  0.19   1    0.6598 0.19     1    0.6598 -1.81
##  albumin 0.62   1    0.4309 0.81     2    0.6656 -3.19
##  sex     2.68   1    0.1015 3.50     3    0.3213 -2.50
## 
## Approximate Estimates after Deleting Factors
## 
##                Coef    S.E.  Wald Z         P
## age          0.1083 0.04082  2.6525 7.990e-03
## age'        -0.1047 0.04279 -2.4467 1.442e-02
## bili         1.0086 0.18158  5.5548 2.780e-08
## bili'       -3.5320 0.71667 -4.9283 8.294e-07
## ascites=Yes  0.9394 0.35670  2.6336 8.448e-03
## stage=2     -0.3880 1.07814 -0.3599 7.189e-01
## stage=3      0.5584 1.04055  0.5366 5.915e-01
## stage=4      0.9056 1.04241  0.8688 3.850e-01
## 
## Factors in Final Model
## 
## [1] age     bili    ascites stage
## 
##      Backwards Step-down - Original Model
## 
##  Deleted Chi-Sq d.f. P      Residual d.f. P      AIC  
##  hepato  0.25   1    0.6156 0.25     1    0.6156 -1.75
##  albumin 0.87   1    0.3507 1.12     2    0.5703 -2.88
##  sex     1.40   1    0.2366 2.52     3    0.4710 -3.48
## 
## Approximate Estimates after Deleting Factors
## 
##                 Coef    S.E.  Wald Z         P
## age          0.10432 0.04031  2.5878 9.658e-03
## age'        -0.09357 0.04140 -2.2600 2.382e-02
## bili         0.94415 0.18016  5.2405 1.601e-07
## bili'       -3.28659 0.71022 -4.6275 3.700e-06
## ascites=Yes  0.86770 0.34026  2.5501 1.077e-02
## stage=2     -0.23685 1.07737 -0.2198 8.260e-01
## stage=3      0.48136 1.04051  0.4626 6.436e-01
## stage=4      0.97350 1.04011  0.9360 3.493e-01
## 
## Factors in Final Model
## 
## [1] age     bili    ascites stage
dev.off()
## png 
##   2

3.3 Exercises: Part 3

Try each exercise yourself first. Then open the field below it to see the answer, the code and its results.

Exercise 1. Which variables are selected when center 1 or center 2 is left out? Are they the same as in the final model? (Tip: see the objects selected_terms_per_model and shrinkage_per_model in the code above.)

Show answer, code and results

The objects selected_terms_per_model and shrinkage_per_model hold exactly this information. Here we put them in one table:

data.frame(development_data=c("All data (final model)",
                              paste("Without", unique(as.character(data$center)))),
           selected_terms=selected_terms_per_model,
           shrinkage_factor=round(shrinkage_per_model, 3))

Compare the selected terms in each row with the final model on all data. Different selections in different development sets show that variable selection is unstable.

Exercise 2. Compare the calibration plots of the two centers. Would you expect big differences in real data with truly different centers? What could cause them?

Show answer

There is no code for this one. In this tutorial the centers are artificial, so the two plots should look alike, and differences are due to chance. In real data with truly different centers you would expect larger differences. Possible causes are differences in case mix (the patients differ), in how predictors and outcomes are measured, in treatment practice, and in follow-up.

Exercise 3. Why do we develop the model on the other centers and not on a random half of the data?

Show answer

There is no code for this one. A random split gives validation data that are very similar to the development data, so it mainly shows internal validity and wastes half of the data. Leaving out a whole center tests the model in a different setting from the development settings, which is closer to how the model will be used in practice, while still using most of the data for development.

Exercise 4. How would you change the code if you had 5 centers instead of 2?

Show answer and code (not run)

The loop already works for any number of centers, because it uses unique(data$center). Only the figure needs more room. This code is not run, because the tutorial data have 2 centers:

# one panel per center in the figure
n_centers <- length(unique(data$center))
png(file="Internal_external_cross_validation.png",
    width=2000*n_centers, height=2000, res=300)
layout(matrix(1:n_centers, ncol=n_centers, byrow=TRUE))

# ... the same loop as above ...

Also think about the number of patients per center. With 5 centers each center has fewer patients, so you may want fewer risk groups in the calibration plot (the argument g=2 of val.surv.mi). Each development set now contains 4 of the 5 centers, so more of the data is used for development than with 2 centers.

LS0tCnRpdGxlOiAiVHV0b3JpYWwgMjogVmFsaWRhdGUgdGhlIHByZWRpY3Rpb24gbW9kZWwiCm91dHB1dDoKICBodG1sX2RvY3VtZW50OgogICAgdG9jOiB0cnVlCiAgICB0b2NfZmxvYXQ6IHRydWUKICAgIG51bWJlcl9zZWN0aW9uczogdHJ1ZQogICAgZGZfcHJpbnQ6IHBhZ2VkCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlCi0tLQoKYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9CmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UpCmBgYAoKPHN0eWxlPgpkZXRhaWxzIHsgYm9yZGVyOiAxcHggc29saWQgI2M4YzhjODsgYm9yZGVyLXJhZGl1czogNHB4OyBwYWRkaW5nOiAwLjRlbSAxZW07IG1hcmdpbjogMC41ZW0gMCAxLjVlbSAwOyBiYWNrZ3JvdW5kOiAjZjhmOGY4OyB9CnN1bW1hcnkgeyBjdXJzb3I6IHBvaW50ZXI7IGZvbnQtd2VpZ2h0OiBib2xkOyB9Cjwvc3R5bGU+CgojIE92ZXJ2aWV3IHstfQoKSW4gVHV0b3JpYWwgMSB5b3UgZGV2ZWxvcGVkIGEgcHJlZGljdGlvbiBtb2RlbC4gQSBtb2RlbCBhbHdheXMgcGVyZm9ybXMgYmV0dGVyIG9uIHRoZSBkYXRhIGl0IHdhcyBkZXZlbG9wZWQgb24gdGhhbiBvbiBuZXcgcGF0aWVudHMsIHNvIGluIHRoaXMgdHV0b3JpYWwgeW91IHdpbGwgY2hlY2sgaG93IHdlbGwgaXQgcmVhbGx5IHdvcmtzLiBJdCBoYXMgdGhyZWUgcGFydHM6CgoxLiAqKlVuaWZvcm0gc2hyaW5rYWdlIGFuZCBvcHRpbWlzbS1jb3JyZWN0ZWQgQy1pbmRleDoqKiB1c2UgdGhlIGJvb3RzdHJhcCB0byBjYWxjdWxhdGUgdGhlIHVuaWZvcm0gc2hyaW5rYWdlIGZhY3RvciBhbmQgY2FsY3VsYXRlIG9wdGltaXNtLgoKMi4gKipNb2RlbCBwZXJmb3JtYW5jZToqKiBjYWxjdWxhdGUgcHJlZGljdGVkIHJpc2tzLCBtYWtlIGNhbGlicmF0aW9uIHBsb3RzIGFuZCBhIGRlY2lzaW9uIGN1cnZlLgoKMy4gKipJbnRlcm5hbC1leHRlcm5hbCBjcm9zcy12YWxpZGF0aW9uOioqIGRldmVsb3AgdGhlIG1vZGVsIG9uIHNvbWUgY2VudGVycyBhbmQgdGVzdCBpdCBvbiB0aGUgY2VudGVyIHRoYXQgd2FzIGxlZnQgb3V0LgoKVGhpcyB0dXRvcmlhbCBjYW4gYmUgcnVuIG9uIGl0cyBvd246IGlmIHlvdSBoYXZlIG5vdCBydW4gVHV0b3JpYWwgMSwgdGhlIG9iamVjdHMgaXQgbmVlZHMgYXJlIHJlYnVpbHQgYXV0b21hdGljYWxseSAoc2VlICpMb2FkIHRoZSBkYXRhIGFuZCBtb2RlbHMqKS4KCiMgRG93bmxvYWQgdGhlIHR1dG9yaWFsIHstfQoKKipEbyBub3QganVzdCByZWFkIHRoaXMgcGFnZTogcnVuIHRoZSBjb2RlIHlvdXJzZWxmLioqIFlvdSBvbmx5IGxlYXJuIFIgYnkgZG9pbmcgaXQuIERvd25sb2FkIHRoZSBSIE1hcmtkb3duIGZpbGUgb2YgdGhpcyB0dXRvcmlhbCAoYFR1dG9yaWFsXzJfVmFsaWRhdGVfUHJlZGljdGlvbl9Nb2RlbC5SbWRgKSBieSBjbGlja2luZyB0aGUgKipDb2RlKiogYnV0dG9uIGF0IHRoZSB0b3AgcmlnaHQgb2YgdGhpcyBwYWdlIGFuZCBjaG9vc2luZyAqKkRvd25sb2FkIFJtZCoqLCBhbmQgcnVuIGV2ZXJ5IGNodW5rIGluIFJTdHVkaW8sIG9uZSBieSBvbmUuIFJlYWQgdGhlIG91dHB1dCwgY2hhbmdlIHRoZSBjb2RlLCBicmVhayBpdCBhbmQgZml4IGl0LCBhbmQgdHJ5IHRoZSBleGVyY2lzZXMgYmVmb3JlIHlvdSBvcGVuIHRoZSBhbnN3ZXJzLiBSZWFkaW5nIHRoZSByZXN1bHRzIG9uIHRoaXMgcGFnZSBpcyBub3QgYSBzdWJzdGl0dXRlLgoKVG8gbWFrZSBzdXJlIGV2ZXJ5dGhpbmcgd29ya3M6CgoxLiAqKkNyZWF0ZSBhIGZvbGRlcioqIGZvciB0aGUgY291cnNlLCBmb3IgZXhhbXBsZSBgUHJlZGljdGl2ZV9tb2RlbHMvYCwgYW5kIHB1dCB0aGUgZG93bmxvYWRlZCBgLlJtZGAgZmlsZSBpbiBpdC4gVHV0b3JpYWwgMSBpcyBub3QgcmVxdWlyZWQuCgoyLiAqKk9wZW4gdGhlIGAuUm1kYCBmaWxlIGluIFJTdHVkaW8uKiogV2hlbiB5b3Uga25pdCBvciBydW4gY2h1bmtzIGZyb20gYW4gUm1kIGZpbGUsIFIgdXNlcyB0aGUgZm9sZGVyIHRoYXQgY29udGFpbnMgdGhlIGZpbGUgYXMgdGhlIHdvcmtpbmcgZGlyZWN0b3J5LiBDaGVjayB0aGlzIGJ5IHJ1bm5pbmc6CgogICBgYGByCiAgIGdldHdkKCkKICAgYGBgCgogICBUaGUgb3V0cHV0IHNob3VsZCBiZSB0aGUgcGF0aCBvZiB0aGUgZm9sZGVyIGZyb20gc3RlcCAxLiBJZiBpdCBpcyBub3QsIHNldCBpdCB3aXRoIGBzZXR3ZCgicGF0aC90by95b3VyL2ZvbGRlciIpYCBvciBpbiBSU3R1ZGlvIHZpYSAqU2Vzc2lvbiA+IFNldCBXb3JraW5nIERpcmVjdG9yeSA+IFRvIFNvdXJjZSBGaWxlIExvY2F0aW9uKi4KCjMuICoqRG8gbm90IHVzZSBmaWxlIHBhdGhzKiogaW4gdGhlIGNvZGUuIEFsbCBmaWxlcyBhcmUgcmVhZCBmcm9tIGFuZCBzYXZlZCB0byB0aGUgd29ya2luZyBkaXJlY3RvcnksIHNvIGtlZXAgYWxsIGZpbGVzIGluIHRoaXMgb25lIGZvbGRlci4KCjQuIFJ1bm5pbmcgdGhlIHR1dG9yaWFsIHNhdmVzIGl0cyBmaWd1cmVzIChjYWxpYnJhdGlvbiBwbG90cywgZGVjaXNpb24gY3VydmUgYW5kIHRoZSBpbnRlcm5hbC1leHRlcm5hbCBjcm9zcy12YWxpZGF0aW9uKSBpbiB0aGlzIHNhbWUgZm9sZGVyLgoKVGhlIGZvbGRlciB3aWxsIHRoZW4gbG9vayBsaWtlIHRoaXM6CgpgYGAKUHJlZGljdGl2ZV9tb2RlbHMvCuKUnOKUgOKUgCBUdXRvcmlhbF8xX0RldmVsb3BfUHJlZGljdGlvbl9Nb2RlbC5SbWQK4pSc4pSA4pSAIFR1dG9yaWFsXzJfVmFsaWRhdGVfUHJlZGljdGlvbl9Nb2RlbC5SbWQK4pSc4pSA4pSAIG1vZGVsX3BlcmZvcm1hbmNlLnBuZyAgICAgICAgICAgKGNyZWF0ZWQgYnkgVHV0b3JpYWwgMikK4pSc4pSA4pSAIGRjYS5wbmcgICAgICAgICAgICAgICAgICAgICAgICAgKGNyZWF0ZWQgYnkgVHV0b3JpYWwgMikK4pSU4pSA4pSAIEludGVybmFsX2V4dGVybmFsX2Nyb3NzX3ZhbGlkYXRpb24ucG5nICAoY3JlYXRlZCBieSBUdXRvcmlhbCAyKQpgYGAKCiMgU2V0dXAgey19CgojIyBDbGVhciB0aGUgZW52aXJvbm1lbnQgYW5kIGxvYWQgbGlicmFyaWVzIHstfQoKQXMgaW4gVHV0b3JpYWwgMSwgZXZlcnl0aGluZyBpcyBzYXZlZCBpbiBvbmUgZm9sZGVyOiB0aGUgZm9sZGVyIHRoYXQgY29udGFpbnMgdGhpcyBSbWQgZmlsZSAodGhlIHdvcmtpbmcgZGlyZWN0b3J5KSwgc28gbm8gZmlsZSBwYXRocyBhcmUgbmVlZGVkLiBJZiB5b3UgYWxzbyBkbyBUdXRvcmlhbCAxLCBrZWVwIGJvdGggdHV0b3JpYWxzIGluIHRoZSBzYW1lIGZvbGRlci4KCkEgKnBhY2thZ2UqIGlzIGEgY29sbGVjdGlvbiBvZiBmdW5jdGlvbnMgdGhhdCBzb21lb25lIHdyb3RlIGFuZCBzaGFyZWQuIGBsaWJyYXJ5KHBhY2thZ2UpYCBsb2FkcyBhIHBhY2thZ2UsIHNvIHRoYXQgeW91IGNhbiB1c2UgaXRzIGZ1bmN0aW9ucyBkaXJlY3RseS4gV2Ugb25seSBsb2FkIGBybXNgLCBiZWNhdXNlIHdlIHVzZSBzb21lIG9mIGl0cyBmdW5jdGlvbnMgKHN1Y2ggYXMgYGNwaGAsIGB2YWxpZGF0ZWAgYW5kIGBmYXN0YndgKSB3aXRob3V0IHRoZSBgcGFja2FnZTo6YCBwcmVmaXguIEFsbCBvdGhlciBwYWNrYWdlcyBhcmUgY2FsbGVkIHdpdGggdGhlIGA6OmAgbm90YXRpb24gKHNlZSBiZWxvdyksIHNvIHRoZXkgZG8gbm90IGhhdmUgdG8gYmUgbG9hZGVkLCBidXQgdGhleSBoYXZlIHRvIGJlIGluc3RhbGxlZCBvbmNlLiBXZSB1c2UgYHN1cnZpdmFsYCwgYFByZWRpY3Rpb25Ub29sc2AgKGNhbGlicmF0aW9uIHBsb3RzKSwgYGRjdXJ2ZXNgIChkZWNpc2lvbiBjdXJ2ZSBhbmFseXNpcyksIGBnZ3Bsb3QyYCBhbmQgYGdncHVicmAgKG1ha2luZyBhbmQgY29tYmluaW5nIGZpZ3VyZXMpLCBhbmQgYGRwbHlyYCwgYG1pY2VgIGFuZCBgSG1pc2NgIChvbmx5IHRvIHJlYnVpbGQgdGhlIG1vZGVscyBiZWxvdykuCgpgYGB7ciBsaWJyYXJpZXN9CiMgY2xlYXIgdGhlIGVudmlyb25tZW50OiByZW1vdmUgYWxsIG9iamVjdHMgdGhhdCBSIGN1cnJlbnRseSByZW1lbWJlcnMKcm0obGlzdD1scygpKQoKIyBsb2FkIHRoZSBybXMgcGFja2FnZTogYWZ0ZXIgdGhpcywgYWxsIGl0cyBmdW5jdGlvbnMgY2FuIGJlIHVzZWQgZGlyZWN0bHkKbGlicmFyeShybXMpCgojIFRoZSBvdGhlciBwYWNrYWdlcyBtdXN0IGJlIGluc3RhbGxlZCBvbmNlIG9uIHlvdXIgY29tcHV0ZXIgKG5vdCBydW4gaGVyZSk6CiMgaW5zdGFsbC5wYWNrYWdlcyhjKCJzdXJ2aXZhbCIsICJkY3VydmVzIiwgImdncGxvdDIiLCAiZ2dwdWJyIiwgImRwbHlyIiwgIm1pY2UiLCAiSG1pc2MiKSkKIyByZW1vdGVzOjppbnN0YWxsX2dpdGh1YigiQ0hNTWFhcy9QcmVkaWN0aW9uVG9vbHMiKSAgIyBkb2N1bWVudGF0aW9uOiBodHRwczovL2dpdGh1Yi5jb20vQ0hNTWFhcy9QcmVkaWN0aW9uVG9vbHMKYGBgCgojIyBUaGUgYDo6YCBub3RhdGlvbiB7LX0KCkluIHRoaXMgdHV0b3JpYWwgeW91IHdpbGwgb2Z0ZW4gc2VlIGNvZGUgbGlrZSBgcm1zOjp2YWxpZGF0ZSguLi4pYC4gVGhlIG5vdGF0aW9uIGBwYWNrYWdlOjpmdW5jdGlvbigpYCBtZWFucyAidXNlIHRoZSBmdW5jdGlvbiBgZnVuY3Rpb25gIGZyb20gdGhlIHBhY2thZ2UgYHBhY2thZ2VgIi4KCllvdSBkbyBub3QgaGF2ZSB0byB3cml0ZSBpdCB0aGlzIHdheSBvbmNlIHlvdSBoYXZlIGxvYWRlZCBhIHBhY2thZ2Ugd2l0aCBgbGlicmFyeSgpYDogYWZ0ZXIgYGxpYnJhcnkocm1zKWAsIGB2YWxpZGF0ZSguLi4pYCB3b3JrcyBhcyB3ZWxsLiBXZSBzdGlsbCB3cml0ZSBpdCBvdXQgYmVjYXVzZSBpdCBpcyBuaWNlIHRvIHNlZSB3aGVyZSBlYWNoIGZ1bmN0aW9uIGNvbWVzIGZyb20sIGFuZCBpdCBhdm9pZHMgbWlzdGFrZXMgd2hlbiB0d28gcGFja2FnZXMgaGF2ZSBhIGZ1bmN0aW9uIHdpdGggdGhlIHNhbWUgbmFtZSAoUiB0aGVuIHVzZXMgdGhlIG9uZSBmcm9tIHRoZSBwYWNrYWdlIHRoYXQgd2FzIGxvYWRlZCBsYXN0KS4KCiMjIFNldCB0aGUgc2VlZCB7LX0KCldlIHNldCB0aGUgcmFuZG9tIHNlZWQgKipvbmNlLCBoZXJlIGF0IHRoZSB0b3AqKi4gRXZlcnkgcmFuZG9tIHN0ZXAgYmVsb3cgKHRoZSBib290c3RyYXAgc2FtcGxlcykgdGhlbiBjb250aW51ZXMgZnJvbSB0aGlzIHN0YXJ0aW5nIHBvaW50LCBzbyB0aGUgd2hvbGUgdHV0b3JpYWwgaXMgcmVwcm9kdWNpYmxlIHdpdGhvdXQgcmVzZXR0aW5nIHRoZSBzZWVkIGJlZm9yZSBlYWNoIGNhbGwuIElmIHlvdSBjaGFuZ2UgdGhlIG9yZGVyIG9mIHRoZSBjb2RlIG9yIGFkZCBleHRyYSByYW5kb20gc3RlcHMsIHRoZSBudW1iZXJzIHdpbGwgY2hhbmdlLgoKYGBge3Igc2VlZH0KIyBzZXQgdGhlIHNlZWQgb25jZSBmb3IgdGhlIHdob2xlIHR1dG9yaWFsCnNldC5zZWVkKDEpCmBgYAoKIyMgTG9hZCB0aGUgZGF0YSBhbmQgbW9kZWxzIHstfQoKVGhpcyB0dXRvcmlhbCBuZWVkcyB0aGUgZGF0YSwgdGhlIGltcHV0ZWQgZGF0YSBzZXRzIGFuZCB0aGUgbW9kZWxzIGZyb20gVHV0b3JpYWwgMS4gVGhlIGNvZGUgYmVsb3cgcmVidWlsZHMgdGhlbSAoYSBjb25kZW5zZWQgY29weSBvZiBUdXRvcmlhbCAxLCB3aGljaCB0YWtlcyBhIG1pbnV0ZSksIHNvIHlvdSBjYW4gc3RhcnQgd2l0aCB0aGlzIHR1dG9yaWFsIGRpcmVjdGx5LgoKPGRldGFpbHM+CjxzdW1tYXJ5PlNob3cgdGhlIGNvZGUgdGhhdCByZWJ1aWxkcyB0aGUgVHV0b3JpYWwgMSBvYmplY3RzPC9zdW1tYXJ5PgoKYGBge3IgcmVidWlsZC10dXRvcmlhbC0xfQojIGRhdGE6IHBiYyBmcm9tIHRoZSBzdXJ2aXZhbCBwYWNrYWdlLCB3aXRoIGFuIGFydGlmaWNpYWwgY2VudGVyIHZhcmlhYmxlCmRhdGEocGJjLCBwYWNrYWdlPSJzdXJ2aXZhbCIpCmRhdGEgPC0gcGJjCmRhdGEkY2VudGVyIDwtIHJlcCgxOjIsIGxlbmd0aC5vdXQ9bnJvdyhkYXRhKSkKZGF0YSA8LSBkcGx5cjo6c2VsZWN0KGRhdGEsIHRpbWUsIHN0YXR1cywKICAgICAgICAgICAgICAgdHJ0LCBhZ2UsIHNleCwgYXNjaXRlcywgaGVwYXRvLCBzcGlkZXJzLCBlZGVtYSwKICAgICAgICAgICAgICAgYmlsaSwgY2hvbCwgYWxidW1pbiwgY29wcGVyLCBhbGsucGhvcywgYXN0LCB0cmlnLAogICAgICAgICAgICAgICBwbGF0ZWxldCwgcHJvdGltZSwgc3RhZ2UsIGNlbnRlcikKZGF0YSR0cnQgICAgIDwtIGZhY3RvcihkYXRhJHRydCwgbGV2ZWxzPWMoMSwgMiksCiAgICAgICAgICAgICAgICAgICAgICAgbGFiZWxzPWMoIkQtcGVuaWNpbGxhbWluZSIsICJQbGFjZWJvIikpCmRhdGEkc2V4ICAgICA8LSBmYWN0b3IoZGF0YSRzZXgsIGxldmVscz1jKCJtIiwgImYiKSwKICAgICAgICAgICAgICAgICAgICAgICBsYWJlbHM9YygiTWFsZSIsICJGZW1hbGUiKSkKZGF0YSRhc2NpdGVzIDwtIGZhY3RvcihkYXRhJGFzY2l0ZXMsIGxldmVscz1jKDAsIDEpLAogICAgICAgICAgICAgICAgICAgICAgIGxhYmVscz1jKCJObyIsICJZZXMiKSkKZGF0YSRoZXBhdG8gIDwtIGZhY3RvcihkYXRhJGhlcGF0bywgbGV2ZWxzPWMoMCwgMSksCiAgICAgICAgICAgICAgICAgICAgICAgbGFiZWxzPWMoIk5vIiwgIlllcyIpKQpkYXRhJHNwaWRlcnMgPC0gZmFjdG9yKGRhdGEkc3BpZGVycywgbGV2ZWxzPWMoMCwgMSksCiAgICAgICAgICAgICAgICAgICAgICAgbGFiZWxzPWMoIk5vIiwgIlllcyIpKQpkYXRhJGVkZW1hICAgPC0gZmFjdG9yKGRhdGEkZWRlbWEsIGxldmVscz1jKDAsIDAuNSwgMSksCiAgICAgICAgICAgICAgICAgICAgICAgbGFiZWxzPWMoIk5vIGVkZW1hIiwgIlVudHJlYXRlZCBvciBzdWNjZXNzZnVsbHkgdHJlYXRlZCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIkRlc3BpdGUgZGl1cmV0aWNzIikpCmRhdGEkc3RhZ2UgICA8LSBmYWN0b3IoZGF0YSRzdGFnZSwgbGV2ZWxzPTE6NCwKICAgICAgICAgICAgICAgICAgICAgICBsYWJlbHM9YygiMSIsICIyIiwgIjMiLCAiNCIpKQpkYXRhJGNlbnRlciAgPC0gZmFjdG9yKGRhdGEkY2VudGVyLCBsZXZlbHM9YygxLCAyKSwKICAgICAgICAgICAgICAgICAgICAgICBsYWJlbHM9YygiQ2VudGVyIDEiLCAiQ2VudGVyIDIiKSkKCiMgb3V0Y29tZTogZGVhdGggd2l0aGluIDEwIHllYXJzLCB0cmFuc3BsYW50IGlzIGNlbnNvcmVkClMgPC0gc3Vydml2YWw6OlN1cnYodGltZT1kYXRhJHRpbWUvMzY1LCBldmVudD1hcy5udW1lcmljKGRhdGEkc3RhdHVzPT0yKSkKaG9yaXpvbiA8LSAxMApTXzEwIDwtIFMKU18xMFtTXzEwWywgMV0+aG9yaXpvbiwgMl0gPC0gMApTXzEwW1NfMTBbLCAxXT5ob3Jpem9uLCAxXSA8LSBob3Jpem9uCgojIG11bHRpcGxlIGltcHV0YXRpb24gd2l0aCBldmVudCBpbmRpY2F0b3IgYW5kIE5lbHNvbi1BYWxlbiBlc3RpbWF0ZQpkYXRhX21pIDwtIGRhdGEKZGF0YV9taSR0aW1lIDwtIE5VTEwKZGF0YV9taSRzdGF0dXMgPC0gTlVMTApkYXRhX21pJGV2ZW50XzEwIDwtIFNfMTBbLCAic3RhdHVzIl0KZGF0YV9taSRjdW1faGF6YXJkIDwtIG1pY2U6Om5lbHNvbmFhbGVuKGRhdGEuZnJhbWUodGltZT1TXzEwWywgInRpbWUiXSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RhdHVzPVNfMTBbLCAic3RhdHVzIl0pLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGltZSwgc3RhdHVzKQptIDwtIDUKaW1wdXRlZF9kYXRhIDwtIG1pY2U6Om1pY2UoZGF0YV9taSwgbT1tLCBzZWVkPTEsIHByaW50PUZBTFNFKQoKIyBtb2RlbHMKZGQgPC0gcm1zOjpkYXRhZGlzdChkYXRhKQpvcHRpb25zKGRhdGFkaXN0PSJkZCIpCmZpdF9hcmdzIDwtIGxpc3QoeD1UUlVFLCB5PVRSVUUsIHN1cnY9VFJVRSkKZnVsbF9ub25fbGluZWFyX21vZGVsIDwtIEhtaXNjOjpmaXQubXVsdC5pbXB1dGUoCiAgU18xMCB+IHJjcyhhZ2UsIDMpICsgcmNzKGJpbGksIDMpICsgcmNzKGFsYnVtaW4sIDMpICsgc2V4ICsgYXNjaXRlcyArCiAgICBoZXBhdG8gKyBzdGFnZSwgcm1zOjpjcGgsCiAgZml0YXJncz1maXRfYXJncywgeHRyYW5zPWltcHV0ZWRfZGF0YSwgZGF0YT1kYXRhLCBmaXQucmVwcz1UUlVFKQpmb3JtX2Z1bGwgPC0gU18xMCB+IHJjcyhhZ2UsIDMpICsgcmNzKGJpbGksIDMpICsgYWxidW1pbiArIHNleCArCiAgYXNjaXRlcyArIGhlcGF0byArIHN0YWdlCmZ1bGxfbW9kZWwgPC0gSG1pc2M6OmZpdC5tdWx0LmltcHV0ZShmb3JtX2Z1bGwsIHJtczo6Y3BoLAogIGZpdGFyZ3M9Zml0X2FyZ3MsIHh0cmFucz1pbXB1dGVkX2RhdGEsIGRhdGE9ZGF0YSwgZml0LnJlcHM9VFJVRSkKZm9ybV9idyA8LSBTXzEwIH4gcmNzKGFnZSwgMykgKyByY3MoYmlsaSwgMykgKyBhbGJ1bWluICsgc3RhZ2UKYndfbW9kZWwgPC0gSG1pc2M6OmZpdC5tdWx0LmltcHV0ZShmb3JtX2J3LCBybXM6OmNwaCwKICBmaXRhcmdzPWZpdF9hcmdzLCB4dHJhbnM9aW1wdXRlZF9kYXRhLCBkYXRhPWRhdGEsIGZpdC5yZXBzPVRSVUUpCmBgYAoKPC9kZXRhaWxzPgoKYGBge3IgbG9hZC1kYXRhfQojIHNldCBkYXRhIGRpc3RyaWJ1dGlvbnMKZGQgPC0gcm1zOjpkYXRhZGlzdChkYXRhKQpvcHRpb25zKGRhdGFkaXN0PSJkZCIpCmBgYAoKIyBVbmlmb3JtIHNocmlua2FnZSBhbmQgb3B0aW1pc20tY29ycmVjdGVkIEMtaW5kZXgKCkEgbW9kZWwgZml0dGVkIG9uIGEgZGF0YSBzZXQgYWx3YXlzIHBlcmZvcm1zIGEgYml0IGJldHRlciBvbiB0aGF0IHNhbWUgZGF0YSBzZXQgdGhhbiBvbiBuZXcgcGF0aWVudHMuIFRoaXMgaXMgY2FsbGVkICpvcHRpbWlzbSogb3Igb3ZlcmZpdHRpbmcsIGFuZCBpdCBpcyB3b3JzZSB3aGVuIHRoZSBtb2RlbCB3YXMgYnVpbHQgdXNpbmcgdmFyaWFibGUgc2VsZWN0aW9uLiBJbiB0aGlzIHBhcnQgeW91IHVzZSBib290c3RyYXBwaW5nIHRvOgoKMS4gZXN0aW1hdGUgYSAqKnVuaWZvcm0gc2hyaW5rYWdlIGZhY3RvcioqIHRoYXQgc2hyaW5rcyB0aGUgY29lZmZpY2llbnRzIHRvd2FyZHMgemVybywgd2hpY2ggbWFrZXMgcHJlZGljdGlvbnMgbGVzcyBleHRyZW1lIGFuZCBiZXR0ZXIgY2FsaWJyYXRlZCBvbiBuZXcgcGF0aWVudHMsIGFuZAoyLiBlc3RpbWF0ZSB0aGUgKipvcHRpbWlzbS1jb3JyZWN0ZWQgQy1pbmRleCoqLCBhIG1lYXN1cmUgb2YgaG93IHdlbGwgdGhlIG1vZGVsIHNlcGFyYXRlcyBwYXRpZW50cyB3aG8gZGllIGVhcmx5IGZyb20gcGF0aWVudHMgd2hvIHN1cnZpdmUuCgpUaGUgYm9vdHN0cmFwIHJlcGVhdHMgdGhlIGVudGlyZSBiYWNrd2FyZCBzZWxlY3Rpb24gcHJvY2VkdXJlIGluIGV2ZXJ5IGJvb3RzdHJhcCBzYW1wbGUsIHNvIHRoZSBvcHRpbWlzbSBpbmNsdWRlcyB0aGUgZWZmZWN0IG9mIHZhcmlhYmxlIHNlbGVjdGlvbi4KCiMjIEJvb3RzdHJhcCB2YWxpZGF0aW9uIG9uIGV2ZXJ5IGltcHV0ZWQgZGF0YSBzZXQKCkZvciBlYWNoIGltcHV0ZWQgZGF0YSBzZXQgd2UgdmFsaWRhdGUgdGhlIGZ1bGwgbW9kZWwgd2l0aCAyMDAgYm9vdHN0cmFwIHNhbXBsZXMuIEluIGVhY2ggYm9vdHN0cmFwIHNhbXBsZSBgdmFsaWRhdGUoKWAgcmVwZWF0cyBiYWNrd2FyZCBzZWxlY3Rpb24gKGBidz1UUlVFYCwgcCA8IDAuMDUpLCByZWZpdHMgdGhlIG1vZGVsLCBhbmQgdGVzdHMgaXQgb24gdGhlIG9yaWdpbmFsIGRhdGEuIFRocmVlIHF1YW50aXRpZXMgYXJlIHN0b3JlZDoKCiogdGhlICoqY2FsaWJyYXRpb24gc2xvcGUqKiBpbiB0aGUgdGVzdCBkYXRhIChgU2xvcGVgLCBjb2x1bW4gYHRlc3RgKSwgd2hpY2ggaXMgb3VyIHNocmlua2FnZSBmYWN0b3IsCiogdGhlICoqYXBwYXJlbnQgRHh5Kiogb2YgdGhlIGZpbmFsIG1vZGVsIG9uIHRoZSBkZXZlbG9wbWVudCBkYXRhIChgRHh5YCwgY29sdW1uIGBpbmRleC5vcmlnYCksIGFuZAoqIHRoZSAqKm9wdGltaXNtIGluIER4eSoqIChgRHh5YCwgY29sdW1uIGBvcHRpbWlzbWApLgoKRHh5IGlzIGNvbnZlcnRlZCB0byB0aGUgQy1pbmRleCB3aXRoIEMgPSBEeHkvMiArIDAuNSwgc28gdGhlIG9wdGltaXNtIGluIHRoZSBDLWluZGV4IGlzIHRoZSBvcHRpbWlzbSBpbiBEeHkgZGl2aWRlZCBieSAyLgoKYGBge3IgcDEtdmFsaWRhdGV9CiMgY2FsY3VsYXRlIHVuaWZvcm0gc2hyaW5rYWdlIGZhY3RvciBhY3Jvc3MgYWxsIGltcHV0YXRpb25zCiMgc3RhcnQgd2l0aCBlbXB0eSB2ZWN0b3JzLCBjKCkgd2l0aCBub3RoaW5nIGluIGl0LCB0byBjb2xsZWN0IHRoZSByZXN1bHRzCnNsb3BlIDwtIGMoKQphcHBhcmVudF9DIDwtIGMoKQpvcHRfQyA8LSBjKCkKCiMgdGhlIGZvciBsb29wIHJlcGVhdHMgdGhlIGNvZGUgYmV0d2VlbiB7IH0gZm9yIGV2ZXJ5IGltcHV0ZWQgZGF0YSBzZXQgaSA9IDEsIC4uLiwgbQpmb3IgKGkgaW4gMTptKXsKICAjIHZhbGlkYXRlKCkgYXJndW1lbnRzOgogICMgICBmdWxsX21vZGVsJGZpdHNbW2ldXTogdGhlIG1vZGVsIGZpdHRlZCBvbiB0aGUgaS10aCBpbXB1dGVkIGRhdGEgc2V0CiAgIyAgIEI9MjAwOiB0aGUgbnVtYmVyIG9mIGJvb3RzdHJhcCBzYW1wbGVzCiAgIyAgIGJ3PVRSVUUsIHJ1bGU9InAiLCBzbHM9MC4wNSwgdHlwZT0iaW5kaXZpZHVhbCI6IHJlcGVhdCB0aGUgYmFja3dhcmQKICAjICAgICBzZWxlY3Rpb24gKHAgPCAwLjA1KSBpbiBldmVyeSBib290c3RyYXAgc2FtcGxlCiAgIyAgIHRvbDogaG93IHN0cmljdCBSIGlzIGFib3V0IG5lYXJseSBpZGVudGljYWwgcHJlZGljdG9ycyAoaGVyZTogbGVzcyBzdHJpY3QpCiAgdmFsX21vZGVsIDwtIHJtczo6dmFsaWRhdGUoZnVsbF9tb2RlbCRmaXRzW1tpXV0sCiAgICAgICAgICAgICAgICAgICAgICAgIEI9MjAwLAogICAgICAgICAgICAgICAgICAgICAgICBidz1UUlVFLAogICAgICAgICAgICAgICAgICAgICAgICBydWxlPSJwIiwKICAgICAgICAgICAgICAgICAgICAgICAgc2xzPTAuMDUsCiAgICAgICAgICAgICAgICAgICAgICAgIHR5cGU9ImluZGl2aWR1YWwiLCB0b2w9MWUtMTMpCgogICMgdmFsX21vZGVsWyJTbG9wZSIsICJ0ZXN0Il0gdGFrZXMgdGhlIGNlbGwgaW4gcm93ICJTbG9wZSIgYW5kIGNvbHVtbiAidGVzdCI7CiAgIyBjKG9sZCwgbmV3KSBhZGRzIHRoZSBuZXcgdmFsdWUgdG8gdGhlIGVuZCBvZiB0aGUgdmVjdG9yCiAgc2xvcGUgPC0gYyhzbG9wZSwgdmFsX21vZGVsWyJTbG9wZSIsICJ0ZXN0Il0pCiAgIyBEeHkgaXMgY29udmVydGVkIHRvIHRoZSBDLWluZGV4OiBDID0gRHh5LzIgKyAwLjUKICBhcHBhcmVudF9DIDwtIGMoYXBwYXJlbnRfQywgdmFsX21vZGVsWyJEeHkiLCAiaW5kZXgub3JpZyJdLzIgKyAwLjUpCiAgb3B0X0MgPC0gYyhvcHRfQywgdmFsX21vZGVsWyJEeHkiLCAib3B0aW1pc20iXS8yKQp9CmBgYAoKIyMgU2hyaW5rYWdlIGZhY3RvcgoKVGhlIHNocmlua2FnZSBmYWN0b3IgaXMgdGhlIG1lYW4gc2xvcGUgYWNyb3NzIGltcHV0YXRpb25zLiBBIHZhbHVlIGJlbG93IDEgaW5kaWNhdGVzIG92ZXJmaXR0aW5nOiB0aGUgbW9kZWwncyBwcmVkaWN0aW9ucyBhcmUgdG9vIGV4dHJlbWUsIGFuZCBtdWx0aXBseWluZyB0aGUgY29lZmZpY2llbnRzIGJ5IHRoZSBmYWN0b3IgY29ycmVjdHMgdGhpcy4KCkZpcnN0IGxvb2sgYXQgdGhlIHNsb3BlIG9mIGV2ZXJ5IGltcHV0ZWQgZGF0YSBzZXQuIElmIHRoZXkgZGlmZmVyIGEgbG90LCBhIHNpbmdsZSBhdmVyYWdlIHdvdWxkIGhpZGUgdGhhdDoKCmBgYHtyIHAxLXNsb3BlLWltcHV0YXRpb25zfQojIHNsb3BlIG9mIGVhY2ggaW1wdXRlZCBkYXRhIHNldApyb3VuZChzbG9wZSwgMykKCiMgaG93IG11Y2ggZG8gdGhleSB2YXJ5IGFyb3VuZCB0aGUgbWVhbj8Kcm91bmQoYyhtZWFuPW1lYW4oc2xvcGUpLCBzZD1zZChzbG9wZSksIG1pbj1taW4oc2xvcGUpLCBtYXg9bWF4KHNsb3BlKSksIDMpCmBgYAoKVGhlIHZhcmlhdGlvbiBiZXR3ZWVuIGltcHV0YXRpb25zIGNvbWVzIGZyb20gdGhlIGRpZmZlcmVudCBpbXB1dGVkIHZhbHVlcyBhbmQgZnJvbSB0aGUgcmFuZG9tIGJvb3RzdHJhcCBzYW1wbGVzLiBIZXJlIHRoZSBzbG9wZXMgYXJlIGNsb3NlIHRvIHRoZWlyIG1lYW4sIHNvIGF2ZXJhZ2luZyBvdmVyIHRoZSBpbXB1dGF0aW9ucyBnaXZlcyBvbmUgc2Vuc2libGUgc2hyaW5rYWdlIGZhY3RvcjoKCmBgYHtyIHAxLXNocmlua2FnZX0KIyBzaHJpbmthZ2UgZmFjdG9yOiB0aGUgbWVhbiBvZiB0aGUgc2xvcGVzIG92ZXIgdGhlIGltcHV0ZWQgZGF0YSBzZXRzCnNocmlua2FnZV9mYWN0b3IgPC0gbWVhbihzbG9wZSkKIyBjYXQoKSBwcmludHMgdGV4dCBhbmQgdmFsdWVzOyAiXG4iIHN0YXJ0cyBhIG5ldyBsaW5lCmNhdCgiU2hyaW5rYWdlIGZhY3RvcjoiLCBzaHJpbmthZ2VfZmFjdG9yLCAiXG4iKQoKIyBzaHJ1bmsgY29lZmZpY2llbnRzOiBtdWx0aXBseSBldmVyeSBjb2VmZmljaWVudCBvZiB0aGUgZmluYWwgbW9kZWwgYnkgdGhlIGZhY3RvcgpzaHJpbmthZ2VfZmFjdG9yKmJ3X21vZGVsJGNvZWZmaWNpZW50cwpgYGAKCiMjIE9wdGltaXNtLWNvcnJlY3RlZCBDLWluZGV4CgpUaGUgQy1pbmRleCAoYWxzbyBjYWxsZWQgdGhlIGMtc3RhdGlzdGljKSBtZWFzdXJlcyBkaXNjcmltaW5hdGlvbjogaG93IHdlbGwgdGhlIG1vZGVsIGRpc3Rpbmd1aXNoZXMgYmV0d2VlbiBsb3ctIGFuZCBoaWdoLXJpc2sgcGF0aWVudHMuCgoqIDAuNSBtZWFucyB0aGF0IHRoZSBtb2RlbCBwZXJmb3JtcyBubyBiZXR0ZXIgdGhhbiByYW5kb20gZ3Vlc3NpbmcgaW4gZGlzdGluZ3Vpc2hpbmcgYmV0d2VlbiBsb3ctIGFuZCBoaWdoLXJpc2sgcGF0aWVudHMuCiogMSBtZWFucyB0aGF0IHRoZSBtb2RlbCBwZXJmZWN0bHkgZGlzdGluZ3Vpc2hlcyBiZXR3ZWVuIGxvdy0gYW5kIGhpZ2gtcmlzayBwYXRpZW50cy4KClRoZSBgdmFsaWRhdGUoKWAgb2JqZWN0IGFscmVhZHkgY29udGFpbnMgdGhlIEMtaW5kZXggb2YgdGhlIGZpbmFsIG1vZGVsLCBzbyB3ZSBkbyBub3QgaGF2ZSB0byBjYWxjdWxhdGUgaXQgb3Vyc2VsdmVzLiBIZXJlIGlzIHRoZSBvdXRwdXQgZm9yIHRoZSBsYXN0IGltcHV0ZWQgZGF0YSBzZXQgKGB2YWxfbW9kZWxgIGZyb20gdGhlIGxvb3AgYWJvdmUpOgoKYGBge3IgcDEtdmFsaWRhdGUtb3V0cHV0fQojIG91dHB1dCBvZiB2YWxpZGF0ZSgpIGZvciB0aGUgbGFzdCBpbXB1dGVkIGRhdGEgc2V0CnZhbF9tb2RlbApgYGAKCkVhY2ggcm93IGlzIGEgcGVyZm9ybWFuY2UgbWVhc3VyZSAoYER4eWAgaXMgdGhlIEMtaW5kZXggb24gYW5vdGhlciBzY2FsZTogQyA9IER4eS8yICsgMC41LCBgU2xvcGVgIGlzIHRoZSBjYWxpYnJhdGlvbiBzbG9wZSkuIFRoZSBjb2x1bW5zIGFyZToKCiogYGluZGV4Lm9yaWdgOiB0aGUgKiphcHBhcmVudCoqIHBlcmZvcm1hbmNlIG9mIHRoZSBmaW5hbCBtb2RlbCBvbiB0aGUgZGV2ZWxvcG1lbnQgZGF0YS4KKiBgdHJhaW5pbmdgOiB0aGUgYXZlcmFnZSBwZXJmb3JtYW5jZSBvZiB0aGUgbW9kZWwgZGV2ZWxvcGVkIGluIGVhY2ggYm9vdHN0cmFwIHNhbXBsZSwgZXZhbHVhdGVkIG9uIHRoYXQgc2FtZSBib290c3RyYXAgc2FtcGxlLgoqIGB0ZXN0YDogdGhlIGF2ZXJhZ2UgcGVyZm9ybWFuY2Ugb2YgdGhlIG1vZGVsIGRldmVsb3BlZCBpbiBlYWNoIGJvb3RzdHJhcCBzYW1wbGUsIGV2YWx1YXRlZCBvbiB0aGUgb3JpZ2luYWwgZGF0YS4KKiBgb3B0aW1pc21gOiBgdHJhaW5pbmdgIG1pbnVzIGB0ZXN0YCwgaG93IG11Y2ggdGhlIGFwcGFyZW50IHBlcmZvcm1hbmNlIGlzIHRvbyBvcHRpbWlzdGljLgoqIGBpbmRleC5jb3JyZWN0ZWRgOiBgaW5kZXgub3JpZ2AgbWludXMgYG9wdGltaXNtYCwgdGhlICoqb3B0aW1pc20tY29ycmVjdGVkKiogcGVyZm9ybWFuY2UuCiogYG5gOiB0aGUgbnVtYmVyIG9mIGJvb3RzdHJhcCBzYW1wbGVzIHRoYXQgd2VyZSB1c2VkLgoKRm9yIHRoZSBDLWluZGV4IHdlIHRha2UgYGluZGV4Lm9yaWdgIGFuZCB0aGUgb3B0aW1pc20gb2YgRHh5IGZyb20gZXZlcnkgaW1wdXRlZCBkYXRhIHNldCAoc3RvcmVkIGluIGBhcHBhcmVudF9DYCBhbmQgYG9wdF9DYCBhYm92ZSksIGF2ZXJhZ2UgdGhlbSBvdmVyIHRoZSBpbXB1dGF0aW9ucyBhbmQgc3VidHJhY3QgdGhlIG1lYW4gb3B0aW1pc206CgpgYGB7ciBwMS1jLWluZGV4fQojIG9wdGltaXNtLWNvcnJlY3RlZCBDLWluZGV4OiBtZWFuKHgpIGF2ZXJhZ2VzIGEgdmVjdG9yIG92ZXIgdGhlIGltcHV0ZWQgZGF0YSBzZXRzCm9wdGltaXNtX0MgPC0gbWVhbihvcHRfQykKb3JpZ2luYWxfQyA8LSBtZWFuKGFwcGFyZW50X0MpCmNhdCgiT3JpZ2luYWwgQy1pbmRleDoiLCByb3VuZChvcmlnaW5hbF9DLCAzKSwgIlxuIiwKICAgICJPcHRpbWlzbToiLCByb3VuZChvcHRpbWlzbV9DLCAzKSwgIlxuIiwKICAgICJPcHRpbWlzbS1jb3JyZWN0ZWQgQy1pbmRleDoiLCByb3VuZChvcmlnaW5hbF9DIC0gb3B0aW1pc21fQywgMyksICJcbiIpCmBgYAoKKipOb3RlIG9uIHRoZSBjb25maWRlbmNlIGludGVydmFsLioqIGB2YWxpZGF0ZSgpYCBvbmx5IHJldHVybnMgdGhlIGF2ZXJhZ2VzIG92ZXIgdGhlIGJvb3RzdHJhcCBzYW1wbGVzLCBub3QgYSB2YXJpYW5jZSBvciB0aGUgQy1pbmRleCBvZiBldmVyeSBib290c3RyYXAgc2FtcGxlLCBzbyBpdCBkb2VzIG5vdCBnaXZlIGEgY29uZmlkZW5jZSBpbnRlcnZhbC4gSWYgeW91IHdhbnQgb25lLCB5b3UgaGF2ZSB0byBjb2RlIHRoZSBib290c3RyYXAgeW91cnNlbGY6IHJlcGVhdCB0aGUgd2hvbGUgcHJvY2VkdXJlIChpbXB1dGF0aW9uLCB2YXJpYWJsZSBzZWxlY3Rpb24sIGZpdHRpbmcsIHZhbGlkYXRpb24pIGluIGJvb3RzdHJhcCBzYW1wbGVzIGFuZCB0YWtlIHRoZSBwZXJjZW50aWxlcyBvZiB0aGUgb3B0aW1pc20tY29ycmVjdGVkIEMtaW5kZXguCgojIyBFeGVyY2lzZXM6IFBhcnQgMQoKVHJ5IGVhY2ggZXhlcmNpc2UgeW91cnNlbGYgZmlyc3QuIFRoZW4gb3BlbiB0aGUgZmllbGQgYmVsb3cgaXQgdG8gc2VlIHRoZSBhbnN3ZXIsIHRoZSBjb2RlIGFuZCBpdHMgcmVzdWx0cy4gVGhlIGFuc3dlcnMgdXNlIG5ldyBvYmplY3QgbmFtZXMuIFdoZXJlIHRoZXkgdXNlIHJhbmRvbSBudW1iZXJzLCB0aGV5IHJlc3RvcmUgdGhlIHN0YXRlIG9mIHRoZSByYW5kb20gbnVtYmVyIGdlbmVyYXRvciBhZnRlcndhcmRzLCBzbyB0aGUgcmVzdCBvZiB0aGUgdHV0b3JpYWwgZ2l2ZXMgdGhlIHNhbWUgbnVtYmVycyB3aXRoIG9yIHdpdGhvdXQgdGhlbS4KCioqRXhlcmNpc2UgMS4qKiBDb21wYXJlIHRoZSBvcmlnaW5hbCBDLWluZGV4IChgb3JpZ2luYWxfQ2ApIHdpdGggdGhlIG9wdGltaXNtLWNvcnJlY3RlZCBvbmUuIEhvdyBsYXJnZSBpcyB0aGUgb3B0aW1pc20sIGFuZCBpcyB0aGlzIGEgbG90IGZvciB0aGlzIG1vZGVsPwoKPGRldGFpbHM+CjxzdW1tYXJ5PlNob3cgYW5zd2VyLCBjb2RlIGFuZCByZXN1bHRzPC9zdW1tYXJ5PgoKYGBge3IgZXgtdDJwMS0xLCBlcnJvcj1UUlVFfQojIG9yaWdpbmFsIEMtaW5kZXgsIG9wdGltaXNtIGFuZCBjb3JyZWN0ZWQgQy1pbmRleApyb3VuZChjKG9yaWdpbmFsPW9yaWdpbmFsX0MsCiAgICAgICAgb3B0aW1pc209b3B0aW1pc21fQywKICAgICAgICBjb3JyZWN0ZWQ9b3JpZ2luYWxfQyAtIG9wdGltaXNtX0MpLCAzKQoKIyBob3cgbXVjaCBpbmZvcm1hdGlvbiBkbyB3ZSBoYXZlIGNvbXBhcmVkIHdpdGggdGhlIG51bWJlciBvZiBwYXJhbWV0ZXJzPwpuX2V2ZW50cyA8LSBzdW0oU18xMFssICJzdGF0dXMiXSkKbl9wYXJhbSA8LSBsZW5ndGgoZnVsbF9tb2RlbCRjb2VmZmljaWVudHMpCmMoZXZlbnRzPW5fZXZlbnRzLCBwYXJhbWV0ZXJzPW5fcGFyYW0sIGV2ZW50c19wZXJfcGFyYW1ldGVyPXJvdW5kKG5fZXZlbnRzL25fcGFyYW0sIDEpKQpgYGAKClRoZSBvcHRpbWlzbSBpcyB0aGUgZGlmZmVyZW5jZSBiZXR3ZWVuIHRoZSBvcmlnaW5hbCBhbmQgdGhlIGNvcnJlY3RlZCBDLWluZGV4LiBPcHRpbWlzbSBpcyBsYXJnZXIgd2hlbiB0aGVyZSBhcmUgZmV3IGV2ZW50cyBjb21wYXJlZCB3aXRoIHRoZSBudW1iZXIgb2YgY2FuZGlkYXRlIHBhcmFtZXRlcnMsIHNvIHRoZSBsYXN0IGxpbmUgZ2l2ZXMgYSBmZWVsaW5nIGZvciBob3cgbXVjaCBvdmVyZml0dGluZyB0byBleHBlY3QuIFdoZXRoZXIgdGhlIG9wdGltaXNtIGlzICJhIGxvdCIgZGVwZW5kcyBvbiBob3cgaXQgY29tcGFyZXMgd2l0aCB0aGUgQy1pbmRleCB5b3UgbmVlZCBmb3IgeW91ciBwdXJwb3NlLgoKPC9kZXRhaWxzPgoKKipFeGVyY2lzZSAyLioqIENoYW5nZSBgQj0yMDBgIHRvIGBCPTUwYCBhbmQgYmFjayB0byBgQj01MDBgLiBIb3cgc3RhYmxlIGlzIHRoZSBzaHJpbmthZ2UgZmFjdG9yPwoKPGRldGFpbHM+CjxzdW1tYXJ5PlNob3cgYW5zd2VyLCBjb2RlIGFuZCByZXN1bHRzPC9zdW1tYXJ5PgoKVG8ga2VlcCB0aGlzIHF1aWNrIHdlIHVzZSBvbmx5IHRoZSBmaXJzdCBpbXB1dGVkIGRhdGEgc2V0LgoKYGBge3IgZXgtdDJwMS0yLCBlcnJvcj1UUlVFfQojIHNhdmUgdGhlIHN0YXRlIG9mIHRoZSByYW5kb20gbnVtYmVyIGdlbmVyYXRvciwgc28gdGhhdCB0aGlzIGV4ZXJjaXNlIGRvZXMgbm90CiMgY2hhbmdlIHRoZSByYW5kb20gbnVtYmVycyB1c2VkIGluIHRoZSByZXN0IG9mIHRoZSB0dXRvcmlhbApvbGRfc2VlZCA8LSAuUmFuZG9tLnNlZWQKCkJfdmFsdWVzIDwtIGMoNTAsIDIwMCwgNTAwKQpzbG9wZV9CIDwtIGMoKQpmb3IgKEIgaW4gQl92YWx1ZXMpewogIHZhbCA8LSBybXM6OnZhbGlkYXRlKGZ1bGxfbW9kZWwkZml0c1tbMV1dLCBCPUIsIGJ3PVRSVUUsCiAgICAgICAgICAgICAgICAgICAgICAgcnVsZT0icCIsIHNscz0wLjA1LCB0eXBlPSJpbmRpdmlkdWFsIiwgdG9sPTFlLTEzKQogIHNsb3BlX0IgPC0gYyhzbG9wZV9CLCB2YWxbIlNsb3BlIiwgInRlc3QiXSkKfQpuYW1lcyhzbG9wZV9CKSA8LSBwYXN0ZTAoIkI9IiwgQl92YWx1ZXMpCnJvdW5kKHNsb3BlX0IsIDMpCgojIHJlc3RvcmUgdGhlIHJhbmRvbSBudW1iZXIgZ2VuZXJhdG9yCmFzc2lnbigiLlJhbmRvbS5zZWVkIiwgb2xkX3NlZWQsIGVudmlyPWdsb2JhbGVudigpKQpgYGAKCldpdGggZmV3IGJvb3RzdHJhcCBzYW1wbGVzIHRoZSBzaHJpbmthZ2UgZmFjdG9yIGlzIG5vaXNpZXI7IHdpdGggbW9yZSBzYW1wbGVzIHRoZSBlc3RpbWF0ZXMgYmVjb21lIG1vcmUgc3RhYmxlIGFuZCBhcmUgY2xvc2VyIHRvIGVhY2ggb3RoZXIuIFJ1biBpdCBhZ2FpbiB3aXRoIGFub3RoZXIgc2VlZCB0byBzZWUgaG93IG11Y2ggdGhlIGVzdGltYXRlIGNoYW5nZXMgYnkgY2hhbmNlIGFsb25lLgoKPC9kZXRhaWxzPgoKKipFeGVyY2lzZSAzLioqIFZhbGlkYXRlIHRoZSBiYWNrd2FyZCBzZWxlY3RlZCBtb2RlbCBgYndfbW9kZWxgIG9uIGl0cyBvd24gKGBidz1GQUxTRWAsIHNvIHdpdGhvdXQgcmVwZWF0aW5nIHRoZSB2YXJpYWJsZSBzZWxlY3Rpb24pLCBhbmQgY29tcGFyZSBpdCB3aXRoIHRoZSB2YWxpZGF0aW9uIG9mIHRoZSBmdWxsIG1vZGVsIHdpdGggdmFyaWFibGUgc2VsZWN0aW9uIGluIGV2ZXJ5IGJvb3RzdHJhcCBzYW1wbGUgKGBidz1UUlVFYCwgYXMgYWJvdmUpLiBVc2UgdGhlIGZpcnN0IGltcHV0ZWQgZGF0YSBzZXQuIENvbXBhcmUgdGhlIG9wdGltaXNtIGluIER4eSBhbmQgdGhlIHNsb3BlLiBXaGljaCBvbmUgZ2l2ZXMgdGhlIGxhcmdlciBvcHRpbWlzbSwgYW5kIHdoeT8KCjxkZXRhaWxzPgo8c3VtbWFyeT5TaG93IGFuc3dlciwgY29kZSBhbmQgcmVzdWx0czwvc3VtbWFyeT4KCmBgYHtyIGV4LXQycDEtMywgZXJyb3I9VFJVRX0KIyBzYXZlIHRoZSBzdGF0ZSBvZiB0aGUgcmFuZG9tIG51bWJlciBnZW5lcmF0b3IKb2xkX3NlZWQgPC0gLlJhbmRvbS5zZWVkCgojIGZpbmFsIG1vZGVsIG9ubHk6IHRoZSB2YXJpYWJsZSBzZWxlY3Rpb24gaXMgbm90IHJlcGVhdGVkCnZhbF9maW5hbCA8LSBybXM6OnZhbGlkYXRlKGJ3X21vZGVsJGZpdHNbWzFdXSwgQj0yMDAsIGJ3PUZBTFNFKQoKIyBmdWxsIG1vZGVsIHdpdGggYmFja3dhcmQgc2VsZWN0aW9uIHJlcGVhdGVkIGluIGV2ZXJ5IGJvb3RzdHJhcCBzYW1wbGUKdmFsX3N0cmF0ZWd5IDwtIHJtczo6dmFsaWRhdGUoZnVsbF9tb2RlbCRmaXRzW1sxXV0sIEI9MjAwLCBidz1UUlVFLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBydWxlPSJwIiwgc2xzPTAuMDUsIHR5cGU9ImluZGl2aWR1YWwiLCB0b2w9MWUtMTMpCgojIG9wdGltaXNtIGluIER4eSBhbmQgdGhlICh0ZXN0KSBzbG9wZQpyb3VuZChyYmluZChmaW5hbF9tb2RlbF9vbmx5PWMob3B0aW1pc21fRHh5PXZhbF9maW5hbFsiRHh5IiwgIm9wdGltaXNtIl0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzbG9wZT12YWxfZmluYWxbIlNsb3BlIiwgInRlc3QiXSksCiAgICAgICAgICAgIHdob2xlX3N0cmF0ZWd5PWMob3B0aW1pc21fRHh5PXZhbF9zdHJhdGVneVsiRHh5IiwgIm9wdGltaXNtIl0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2xvcGU9dmFsX3N0cmF0ZWd5WyJTbG9wZSIsICJ0ZXN0Il0pKSwgMykKCiMgcmVzdG9yZSB0aGUgcmFuZG9tIG51bWJlciBnZW5lcmF0b3IKYXNzaWduKCIuUmFuZG9tLnNlZWQiLCBvbGRfc2VlZCwgZW52aXI9Z2xvYmFsZW52KCkpCmBgYAoKVmFsaWRhdGluZyBvbmx5IHRoZSBmaW5hbCBtb2RlbCBnaXZlcyBhIHNtYWxsZXIgb3B0aW1pc20gYW5kIGEgc2xvcGUgY2xvc2VyIHRvIDEuIFRoaXMgaXMgYmVjYXVzZSB2YXJpYWJsZSBzZWxlY3Rpb24gdXNlcyB0aGUgb3V0Y29tZTogdGhlIHZhcmlhYmxlcyB0aGF0IHJlbWFpbiBhcmUgcGFydGx5IHRoZSBvbmVzIHRoYXQgbG9vayBnb29kIGluIHRoaXMgcGFydGljdWxhciBkYXRhIHNldCwgYWxzbyBieSBjaGFuY2UuIElmIHlvdSBvbmx5IHZhbGlkYXRlIHRoZSBmaW5hbCwgZml4ZWQgbW9kZWwsIHlvdSBpZ25vcmUgdGhpcyBleHRyYSBzb3VyY2Ugb2Ygb3ZlcmZpdHRpbmcgYW5kIHVuZGVyZXN0aW1hdGUgdGhlIG9wdGltaXNtLiBCeSByZXBlYXRpbmcgdGhlIHNlbGVjdGlvbiBpbiBldmVyeSBib290c3RyYXAgc2FtcGxlLCB0aGUgdmFsaWRhdGlvbiBjaGVja3MgdGhlIHdob2xlIG1vZGVsbGluZyBzdHJhdGVneSwgbm90IG9ubHkgdGhlIGZpbmFsIG1vZGVsLgoKPC9kZXRhaWxzPgoKIyBNb2RlbCBwZXJmb3JtYW5jZQoKQSBwcmVkaWN0aW9uIG1vZGVsIGlzIG1vcmUgdGhhbiBhIEMtaW5kZXguIEluIHRoaXMgcGFydCB5b3UgY2FsY3VsYXRlIHByZWRpY3RlZCAxMC15ZWFyIHJpc2tzIGZvciB0aHJlZSBtb2RlbHMgKHRoZSBmdWxsIG1vZGVsLCB0aGUgYmFja3dhcmQgc2VsZWN0ZWQgbW9kZWwsIGFuZCB0aGUgYmFja3dhcmQgc2VsZWN0ZWQgbW9kZWwgd2l0aCBzaHJpbmthZ2UpLCBjaGVjayBob3cgd2VsbCB0aGUgcHJlZGljdGVkIHJpc2tzIGFncmVlIHdpdGggdGhlIG9ic2VydmVkIHJpc2tzICgqY2FsaWJyYXRpb24qKSwgYW5kIGFzc2VzcyB0aGUgY2xpbmljYWwgdXNlZnVsbmVzcyBvZiB0aGUgbW9kZWwgd2l0aCBhICpkZWNpc2lvbiBjdXJ2ZSBhbmFseXNpcyouCgojIyBDYWxjdWxhdGUgcHJlZGljdGlvbnMgYW5kIG1ha2UgY2FsaWJyYXRpb24gcGxvdHMKCkZvciBlYWNoIG9mIHRoZSB0aHJlZSBtb2RlbHMgYW5kIGZvciBlYWNoIGltcHV0ZWQgZGF0YSBzZXQgd2UgZG8gdGhlIGZvbGxvd2luZzoKCjEuIENhbGN1bGF0ZSB0aGUgKipsaW5lYXIgcHJlZGljdG9yKiogKGBscGApIGZvciBlYWNoIHBhdGllbnQuIEZvciB0aGUgc2hydW5rIG1vZGVsIHRoaXMgaXMgdGhlIGxpbmVhciBwcmVkaWN0b3Igb2YgdGhlIGJhY2t3YXJkIHNlbGVjdGVkIG1vZGVsIG11bHRpcGxpZWQgYnkgdGhlIHNocmlua2FnZSBmYWN0b3IuCjIuIEVzdGltYXRlIHRoZSAqKmJhc2VsaW5lIGhhemFyZCoqIGF0IHRoZSAxMC15ZWFyIGhvcml6b24uIEZvciB0aGUgc2hydW5rIG1vZGVsIHRoZSBiYXNlbGluZSBoYXphcmQgaGFzIHRvIGJlIHJlLWVzdGltYXRlZCwgYmVjYXVzZSB0aGUgbGluZWFyIHByZWRpY3RvciBjaGFuZ2VkLiBUaGlzIGlzIGRvbmUgd2l0aCBhIENveCBtb2RlbCB0aGF0IG9ubHkgaGFzIHRoZSBsaW5lYXIgcHJlZGljdG9yIGFzIGFuIG9mZnNldC4KMy4gVHVybiB0aGVzZSBpbnRvICoqcHJlZGljdGVkIHJpc2tzKiogYXQgMTAgeWVhcnMgd2l0aCBgZnVuLmV2ZW50KClgLgo0LiBNYWtlIGEgY2FsaWJyYXRpb24gcGxvdCB3aXRoIGB2YWwuc3Vydi5taSgpYCwgd2hpY2ggcG9vbHMgdGhlIHBlcmZvcm1hbmNlIG92ZXIgdGhlIGltcHV0ZWQgZGF0YSBzZXRzLgoKKipJbXBvcnRhbnQ6IHRoaXMgaXMgYXBwYXJlbnQgcGVyZm9ybWFuY2UuKiogQWxsIHBlcmZvcm1hbmNlIG1lYXN1cmVzIGluIHRoaXMgcGFydCBhcmUgY2FsY3VsYXRlZCBvbiB0aGUgc2FtZSBkYXRhIHRoYXQgd2VyZSB1c2VkIHRvIGRldmVsb3AgdGhlIG1vZGVsLCBzbyB0aGV5IGlnbm9yZSBvcHRpbWlzbSAob3ZlcmZpdHRpbmcpIGFuZCBhcmUgdG9vIG9wdGltaXN0aWMuIFRoZSBvcHRpbWlzbS1jb3JyZWN0ZWQgQy1pbmRleCBpbiBQYXJ0IDEgY29ycmVjdHMgZm9yIHRoaXMgZm9yIHRoZSBDLWluZGV4IG9ubHkuIEZvciBjYWxpYnJhdGlvbiBhbmQgZGVjaXNpb24gY3VydmVzIHlvdSB3b3VsZCBuZWVkIHRvIHJlcGVhdCB0aGUgZXZhbHVhdGlvbiB3aXRoIGEgYm9vdHN0cmFwIG9yIGluIG5ldyBwYXRpZW50cyAoc2VlIHRoZSBpbnRlcm5hbC1leHRlcm5hbCBjcm9zcy12YWxpZGF0aW9uIGluIFBhcnQgMykuCgpgYGB7ciBwMi1wZXJmb3JtYW5jZX0KIyBUaGUgdGhyZWUgbW9kZWxzIHdlIGNvbXBhcmUsIGFuZCB0aGUgdGl0bGUgb2YgZWFjaCBjYWxpYnJhdGlvbiBwbG90LgojIFRoZSBzaHJ1bmsgbW9kZWwgaXMgdGhlIGJhY2t3YXJkIHNlbGVjdGVkIG1vZGVsIHdpdGggc2hydW5rIGNvZWZmaWNpZW50cy4KbW9kZWxfbmFtZXMgICA8LSBjKCJmdWxsIiwgImJ3IiwgInNocnVuayIpCm1vZGVsX3RpdGxlcyAgPC0gYyhmdWxsICAgPSAiRnVsbCBtb2RlbCIsCiAgICAgICAgICAgICAgICAgICBidyAgICAgPSAiRmluYWwgbW9kZWwgd2l0aG91dCBzaHJpbmthZ2UiLAogICAgICAgICAgICAgICAgICAgc2hydW5rID0gIlNocnVuayBtb2RlbCIpCmZpdHRlZF9tb2RlbHMgPC0gbGlzdChmdWxsPWZ1bGxfbW9kZWwsIGJ3PWJ3X21vZGVsLCBzaHJ1bms9YndfbW9kZWwpCm1vZGVsX2Zvcm11bGFzIDwtIGxpc3QoZnVsbD1mb3JtX2Z1bGwsIGJ3PWZvcm1fYncpCgojIE9wZW4gb25lIHBuZyBmaWxlIHdpdGggdGhyZWUgcGFuZWxzIG5leHQgdG8gZWFjaCBvdGhlcjogb25lIGNhbGlicmF0aW9uIHBsb3QKIyBwZXIgbW9kZWwKcG5nKGZpbGVuYW1lPSJtb2RlbF9wZXJmb3JtYW5jZS5wbmciLCB3aWR0aD0zNjAwLCBoZWlnaHQ9MTQwMCwgcmVzPTMwMCkKbGF5b3V0KG1hdHJpeCgxOjMsIG5jb2w9MywgYnlyb3c9VFJVRSkpCgpmb3IgKG1vZGVsX25hbWUgaW4gbW9kZWxfbmFtZXMpewogIGNhdCgiTm93IG1ha2luZyBwbG90IGZvciIsIG1vZGVsX25hbWUsICJtb2RlbCBcbiIpCgogICMgcHJlZGljdGVkIDEwLXllYXIgcmlza3Mgb2YgYWxsIHBhdGllbnRzOiBvbmUgY29sdW1uIHBlciBpbXB1dGVkIGRhdGEgc2V0CiAgcHJlZGljdGVkX3Jpc2tzIDwtIGMoKQoKICBmb3IgKGkgaW4gMTptKXsKICAgICMgdGhlIGktdGggaW1wdXRlZCBkYXRhIHNldAogICAgaW1wdXRlZF9pIDwtIG1pY2U6OmNvbXBsZXRlKGltcHV0ZWRfZGF0YSwgaSkKCiAgICAjIFN0ZXAgMTogbGluZWFyIHByZWRpY3RvciBvZiBldmVyeSBwYXRpZW50IC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KICAgIGxwX2kgPC0gcHJlZGljdChmaXR0ZWRfbW9kZWxzW1ttb2RlbF9uYW1lXV0kZml0c1tbaV1dLAogICAgICAgICAgICAgICAgICAgIG5ld2RhdGE9aW1wdXRlZF9pLCB0eXBlPSJscCIpCiAgICBpZiAobW9kZWxfbmFtZSA9PSAic2hydW5rIil7CiAgICAgIGxwX2J3X2kgPC0gbHBfaSAgICAgICAgICAgICAgICAgICAgICMgbGluZWFyIHByZWRpY3RvciB3aXRob3V0IHNocmlua2FnZQogICAgICBscF9pIDwtIHNocmlua2FnZV9mYWN0b3IqbHBfYndfaSAgICAjIHNocmluayBpdCB0b3dhcmRzIHplcm8KICAgIH0KCiAgICAjIFN0ZXAgMjogYmFzZWxpbmUgaGF6YXJkIGF0IHRoZSBob3Jpem9uIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KICAgIGlmIChtb2RlbF9uYW1lID09ICJzaHJ1bmsiKXsKICAgICAgIyB0aGUgbGluZWFyIHByZWRpY3RvciBjaGFuZ2VkLCBzbyByZS1lc3RpbWF0ZSB0aGUgYmFzZWxpbmUgaGF6YXJkIHdpdGggYQogICAgICAjIENveCBtb2RlbCB0aGF0IG9ubHkgaGFzIHRoZSBsaW5lYXIgcHJlZGljdG9yIGFzIGFuIG9mZnNldAogICAgICBiYXNlbGluZV9maXQgPC0gc3Vydml2YWw6OmNveHBoKFNfMTAgfiBvZmZzZXQobHBfaSksIGRhdGE9aW1wdXRlZF9pKQogICAgfSBlbHNlIHsKICAgICAgIyByZWZpdCB0aGUgQ294IG1vZGVsIG9uIHRoaXMgaW1wdXRlZCBkYXRhIHNldAogICAgICBiYXNlbGluZV9maXQgPC0gcm1zOjpjcGgobW9kZWxfZm9ybXVsYXNbW21vZGVsX25hbWVdXSwgZGF0YT1pbXB1dGVkX2ksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB4PVRSVUUsIHk9VFJVRSwgc2UuZml0PVRSVUUpCiAgICB9CiAgICBiYXNlbGluZV9oYXphcmQgPC0gc3Vydml2YWw6OmJhc2VoYXooYmFzZWxpbmVfZml0KQogICAgIyBjdW11bGF0aXZlIGJhc2VsaW5lIGhhemFyZCBhdCB0aGUgbGFzdCBldmVudCB0aW1lIGJlZm9yZSB0aGUgaG9yaXpvbgogICAgbGFzdF90aW1lIDwtIG1heChiYXNlbGluZV9oYXphcmQkdGltZVtiYXNlbGluZV9oYXphcmQkdGltZSA8PSBob3Jpem9uXSkKICAgIGgwX2kgPC0gYmFzZWxpbmVfaGF6YXJkJGhhemFyZFtiYXNlbGluZV9oYXphcmQkdGltZSA9PSBsYXN0X3RpbWVdCgogICAgIyBTdGVwIDM6IHByZWRpY3RlZCByaXNrIGF0IHRoZSBob3Jpem9uIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCiAgICBwcmVkaWN0ZWRfcmlza3MgPC0gY2JpbmQocHJlZGljdGVkX3Jpc2tzLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIFByZWRpY3Rpb25Ub29sczo6ZnVuLmV2ZW50KGxwPWxwX2ksIGgwPWgwX2kpKQogIH0KCiAgIyBBIHNwbGluZSBpcyBsaW5lYXIgaW4gdGhlIHRhaWxzLCBzbyBhIHBhdGllbnQgd2l0aCBhIHZlcnkgZXh0cmVtZSB2YWx1ZSAoZm9yCiAgIyBleGFtcGxlIGEgdmVyeSBoaWdoIGJpbGlydWJpbikgY2FuIGdldCBhIHByZWRpY3RlZCByaXNrIG9mIGV4YWN0bHkgMSwgd2hpY2gKICAjIHRoZSBjYWxpYnJhdGlvbiBwbG90IGNhbm5vdCBoYW5kbGU6IGtlZXAgcHJlZGljdGlvbnMgc3RyaWN0bHkgYmV0d2VlbiAwIGFuZCAxCiAgcHJlZGljdGVkX3Jpc2tzIDwtIGFzLm1hdHJpeChwcmVkaWN0ZWRfcmlza3MpCiAgcHJlZGljdGVkX3Jpc2tzIDwtIHBtaW4ocG1heChwcmVkaWN0ZWRfcmlza3MsIDAuMDAwMSksIDAuOTk5OSkKCiAgIyBzYXZlIHVuZGVyIGEgbmFtZSBwZXIgbW9kZWw6IHBfZnVsbCwgcF9idyBhbmQgcF9zaHJ1bmsKICBhc3NpZ24ocGFzdGUwKCJwXyIsIG1vZGVsX25hbWUpLCBwcmVkaWN0ZWRfcmlza3MpCgogICMgU3RlcCA0OiBjYWxpYnJhdGlvbiBwbG90LCBpbiB0aGUgbmV4dCBwYW5lbCBvZiB0aGUgcG5nIC0tLS0tLS0tLS0tLS0tLS0tLS0tCiAgUHJlZGljdGlvblRvb2xzOjp2YWwuc3Vydi5taShwPXByZWRpY3RlZF9yaXNrcywgeT1TXzEwLCB0aW1lPWhvcml6b24sIGc9MywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1haW49bW9kZWxfdGl0bGVzW1ttb2RlbF9uYW1lXV0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBDSS5tZXRyaWNzPVRSVUUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzaG93Lm1ldHJpY3M9YyhyZXAoVFJVRSwgNiksIEZBTFNFKSkKfQpkZXYub2ZmKCkKYGBgCgojIyMgTG9vayBhdCB0aGUgY2FsaWJyYXRpb24gcGxvdHMKClRoZSBwbG90cyBzaG93IHByZWRpY3RlZCByaXNrICh4LWF4aXMpIGFnYWluc3Qgb2JzZXJ2ZWQgcmlzayAoeS1heGlzKSwgd2l0aCBwYXRpZW50cyBncm91cGVkIGJ5IHByZWRpY3RlZCByaXNrLiBQb2ludHMgb24gdGhlIGRpYWdvbmFsIG1lYW4gcGVyZmVjdCBjYWxpYnJhdGlvbi4gVGhlIG1ldHJpY3MgaW4gdGhlIHBsb3Qgc3VtbWFyaXplIHRoaXMgKGZvciBleGFtcGxlIGNhbGlicmF0aW9uIGludGVyY2VwdCBhbmQgc2xvcGUpLiBIb3cgdG8gcmVhZCB0aGVtOgoKKiBUaGUgY2FsaWJyYXRpb24gaW50ZXJjZXB0IDwgMCBpbmRpY2F0ZXMgb3ZlcmVzdGltYXRpb24gb2YgdGhlIHJpc2ssIGFuZCBhbiBpbnRlcmNlcHQgPiAwIGluZGljYXRlcyB1bmRlcmVzdGltYXRpb24uCiogVGhlIGNhbGlicmF0aW9uIHNsb3BlIDwgMSBpbmRpY2F0ZXMgb3ZlcmZpdHRpbmcsIGkuZS4sIGEgd2Vha2VyIHRoYW4gZXhwZWN0ZWQgc3RyZW5ndGggb2YgdGhlIGFzc29jaWF0aW9uIGJldHdlZW4gdGhlIHByZWRpY3RvcnMgYW5kIHRoZSBvdXRjb21lLiBBIHNsb3BlID4gMSBpbmRpY2F0ZXMgdW5kZXJmaXR0aW5nLCBpLmUuLCBhIHN0cm9uZ2VyIHRoYW4gZXhwZWN0ZWQgc3RyZW5ndGggb2YgdGhlIGFzc29jaWF0aW9uIGJldHdlZW4gdGhlIHByZWRpY3RvcnMgYW5kIHRoZSBvdXRjb21lLgoKYGBge3IgcDItY2FsaWJyYXRpb24tc2hvdywgZWNobz1GQUxTRSwgb3V0LndpZHRoPSIxMDAlIn0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoIm1vZGVsX3BlcmZvcm1hbmNlLnBuZyIpCmBgYAoKIyMgRG8gdGhlIG1vZGVscyBwcmVkaWN0IHdlbGwgb24gYXZlcmFnZT8KCipDYWxpYnJhdGlvbi1pbi10aGUtbGFyZ2UqOiB0aGUgYXZlcmFnZSBwcmVkaWN0ZWQgcmlzayBzaG91bGQgYmUgY2xvc2UgdG8gdGhlIG9ic2VydmVkIDEwLXllYXIgcmlzayBmcm9tIHRoZSBLYXBsYW4tTWVpZXIgY3VydmUgKGZpcnN0IG51bWJlcikuCgpgYGB7ciBwMi1hdmVyYWdlfQojIGNoZWNrIGlmIG1vZGVscyBwcmVkaWN0IHdlbGwgb24gYXZlcmFnZQojIG9ic2VydmVkIDEwLXllYXIgcmlzazogMSBtaW51cyB0aGUgS2FwbGFuLU1laWVyIHN1cnZpdmFsIGF0IHRoZSBob3Jpem9uCjEtc3VtbWFyeShzdXJ2aXZhbDo6c3VydmZpdChTXzEwfjEsIGRhdGE9ZGF0YSksIHRpbWU9aG9yaXpvbikkc3VydgojIGF2ZXJhZ2UgcHJlZGljdGVkIHJpc2s6IHJvd01lYW5zKCkgYXZlcmFnZXMgdGhlIHByZWRpY3Rpb25zIG9mIGVhY2ggcGF0aWVudAojIG92ZXIgdGhlIGltcHV0ZWQgZGF0YSBzZXRzLCBtZWFuKCkgYXZlcmFnZXMgdGhvc2Ugb3ZlciBhbGwgcGF0aWVudHMKbWVhbihyb3dNZWFucyhwX2Z1bGwpKQptZWFuKHJvd01lYW5zKHBfYncpKQptZWFuKHJvd01lYW5zKHBfc2hydW5rKSkKYGBgCgpUaGUgdW5zaHJ1bmsgbW9kZWxzIGZpdCB0aGUgZGV2ZWxvcG1lbnQgZGF0YSBiZXN0LiBUaGUgc2hydW5rIG1vZGVsIGlzIHRoZSBmdXJ0aGVzdCBmcm9tIHRoZSBvYnNlcnZlZCByaXNrOiBiZWNhdXNlIHdlIHNocmFuayB0aGUgY29lZmZpY2llbnRzLCBpdHMgcHJlZGljdGlvbnMgYXJlIGxlc3MgZXh0cmVtZSBhbmQgaXQgKip1bmRlcmZpdHMgdGhlIGRldmVsb3BtZW50IGRhdGEqKiBhIGxpdHRsZS4gVGhhdCBpcyBpbnRlbmRlZDogc2hyaW5rYWdlIGdpdmVzIHVwIHNvbWUgZml0IG9uIHRoZXNlIHBhdGllbnRzIGluIGV4Y2hhbmdlIGZvciBiZXR0ZXIgcHJlZGljdGlvbnMgaW4gbmV3IHBhdGllbnRzLCB3aGVyZSB0aGUgdW5zaHJ1bmsgbW9kZWwgd291bGQgYmUgdG9vIGV4dHJlbWUgYmVjYXVzZSBpdCBpcyBvdmVyZml0dGVkLiBTbyBkbyBub3QgZXhwZWN0IHRoZSBzaHJ1bmsgbW9kZWwgdG8gbG9vayBiZXN0IG9uIHRoZSBkZXZlbG9wbWVudCBkYXRhLgoKIyMgRGVjaXNpb24gY3VydmUgYW5hbHlzaXMgKERDQSkKCkEgZGVjaXNpb24gY3VydmUgc2hvd3MgdGhlICpuZXQgYmVuZWZpdCogb2YgdXNpbmcgdGhlIG1vZGVsIHRvIG1ha2UgZGVjaXNpb25zIChmb3IgZXhhbXBsZSwgdHJlYXQgcGF0aWVudHMgd2l0aCBhIHByZWRpY3RlZCByaXNrIGFib3ZlIGEgdGhyZXNob2xkKSwgY29tcGFyZWQgd2l0aCB0cmVhdGluZyBldmVyeW9uZSBvciBubyBvbmUuIEEgbW9kZWwgaXMgY2xpbmljYWxseSB1c2VmdWwgYXQgdGhlIHRocmVzaG9sZHMgd2hlcmUgaXRzIGN1cnZlIGxpZXMgYWJvdmUgYm90aCBvZiB0aGVzZSByZWZlcmVuY2Ugc3RyYXRlZ2llcy4KCmBgYHtyIHAyLWRjYX0KIyBkZWNpc2lvbiBjdXJ2ZSBhbmFseXNpcyAoRENBKQojIGRhdGEuZnJhbWUoKSBwdXRzIHRoZSBvdXRjb21lICh5KSBhbmQgdGhlIHByZWRpY3RlZCByaXNrIG9mIGVhY2ggcGF0aWVudCAoTW9kZWwpCiMgaW4gYSB0YWJsZTsgZGNhKCkgY2FsY3VsYXRlcyB0aGUgbmV0IGJlbmVmaXQgYXQgdGhlIGhvcml6b24sIGFuZCB0aGUgcGlwZSB8PgojIHBhc3NlcyB0aGUgcmVzdWx0IG9uIHRvIHBsb3QoKSwgd2hpY2ggZHJhd3MgdGhlIGN1cnZlcyAoc21vb3RoPVRSVUUgc21vb3RocyB0aGVtKQpkY2FfcGxvdCA8LSBkY3VydmVzOjpkY2EoeSB+IE1vZGVsLAogICAgICAgICAgICAgICAgICAgICAgICAgdGltZT1ob3Jpem9uLAogICAgICAgICAgICAgICAgICAgICAgICAgZGF0YT1kYXRhLmZyYW1lKHk9U18xMCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBNb2RlbD1yb3dNZWFucyhwX2J3KSkpIHw+CiAgcGxvdChzbW9vdGggPSBUUlVFKQpkY2FfcGxvdApgYGAKClRoZSBoaXN0b2dyYW0gc2hvd3MgaG93IHRoZSBwcmVkaWN0ZWQgcmlza3MgYXJlIGRpc3RyaWJ1dGVkLiBEZWNpc2lvbiBjdXJ2ZXMgYXJlIG1vc3QgcmVsaWFibGUgaW4gdGhlIHJhbmdlIG9mIHRocmVzaG9sZHMgd2hlcmUgdGhlcmUgYXJlIGVub3VnaCBwYXRpZW50cy4KCmBgYHtyIHAyLWhpc3RvZ3JhbX0KIyBoaXN0b2dyYW0gb2YgdGhlIHByZWRpY3RlZCByaXNrczogZ2dwbG90IGZpZ3VyZXMgYXJlIGJ1aWx0IGluIGxheWVycyB0aGF0IGFyZQojIGFkZGVkIHdpdGggKwpoaXN0X3ByZWQgPC0gZ2dwbG90Mjo6Z2dwbG90KGRhdGE9ZGF0YS5mcmFtZShQcmVkaWN0aW9ucz1yb3dNZWFucyhwX2J3KSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZ2dwbG90Mjo6YWVzKHg9UHJlZGljdGlvbnMpKSArCiAgZ2dwbG90Mjo6Z2VvbV9oaXN0b2dyYW0oZmlsbD0iIzYxOWJmZiIsIGNvbG9yPSJncmV5IiwgYWxwaGE9MC42LCBiaW53aWR0aD0wLjAxKSArCiAgZ2dwbG90Mjo6eWxhYigiQ291bnQiKSArCiAgZ2dwbG90Mjo6dGhlbWVfbWluaW1hbCgpICsKICBnZ3Bsb3QyOjp0aGVtZShwYW5lbC5iYWNrZ3JvdW5kID0gZ2dwbG90Mjo6ZWxlbWVudF9yZWN0KGZpbGw9IndoaXRlIiwgY29sb3I9TkEpLAogICAgICAgIHBsb3QuYmFja2dyb3VuZCA9IGdncGxvdDI6OmVsZW1lbnRfcmVjdChmaWxsPSJ3aGl0ZSIsIGNvbG9yPU5BKSkKaGlzdF9wcmVkCmBgYAoKYGBge3IgcDItc2F2ZS1kY2F9CiMgc2F2ZTogZ2dhcnJhbmdlKCkgcHV0cyB0aGUgdHdvIGZpZ3VyZXMgYmVsb3cgZWFjaCBvdGhlciwgZ2dzYXZlKCkgd3JpdGVzCiMgdGhlIHJlc3VsdCB0byBhIHBuZyBmaWxlIGluIHRoZSB3b3JraW5nIGRpcmVjdG9yeQpnZ3Bsb3QyOjpnZ3NhdmUoZmlsZT0iZGNhLnBuZyIsCiAgICAgICBwbG90PWdncHVicjo6Z2dhcnJhbmdlKGRjYV9wbG90LCBoaXN0X3ByZWQsCiAgICAgICAgICAgICAgICAgICAgICBucm93PTIsIG5jb2w9MSwgaGVpZ2h0cz1jKDIsIDEpLCBhbGlnbj0idiIpLAogICAgICAgd2lkdGg9MTYsIGhlaWdodD0xNiwgdW5pdHM9ImNtIiwgZHBpPTMwMCkKYGBgCgojIyBFeGVyY2lzZXM6IFBhcnQgMgoKVHJ5IGVhY2ggZXhlcmNpc2UgeW91cnNlbGYgZmlyc3QuIFRoZW4gb3BlbiB0aGUgZmllbGQgYmVsb3cgaXQgdG8gc2VlIHRoZSBhbnN3ZXIsIHRoZSBjb2RlIGFuZCBpdHMgcmVzdWx0cy4KCioqRXhlcmNpc2UgMS4qKiBDb21wYXJlIHRoZSB0aHJlZSBjYWxpYnJhdGlvbiBwbG90cy4gV2hpY2ggbW9kZWwgaGFzIHRoZSBiZXN0IGNhbGlicmF0aW9uIHNsb3BlPyBEb2VzIHRoaXMgbWF0Y2ggd2hhdCB5b3UgZXhwZWN0IGZyb20gc2hyaW5rYWdlPwoKPGRldGFpbHM+CjxzdW1tYXJ5PlNob3cgYW5zd2VyLCBjb2RlIGFuZCByZXN1bHRzPC9zdW1tYXJ5PgoKV2UgY2FsY3VsYXRlIHRoZSBjYWxpYnJhdGlvbiBzbG9wZSBvdXJzZWx2ZXMgZm9yIHRoZSBsYXN0IGltcHV0ZWQgZGF0YSBzZXQgKHRoZSBvYmplY3RzIGBscF9id19pYCBhbmQgYGxwX2lgIGFyZSBsZWZ0IG92ZXIgZnJvbSB0aGUgbGFzdCByb3VuZCBvZiB0aGUgbG9vcCBhYm92ZSk6IHRoZSBzbG9wZSBvZiBhIENveCBtb2RlbCB3aXRoIHRoZSBsaW5lYXIgcHJlZGljdG9yIGFzIHRoZSBvbmx5IHByZWRpY3Rvci4KCmBgYHtyIGV4LXQycDItMSwgZXJyb3I9VFJVRX0KY2FsX3Nsb3BlX2J3IDwtIGNvZWYoc3Vydml2YWw6OmNveHBoKFNfMTAgfiBscF9id19pKSkKY2FsX3Nsb3BlX3NocnVuayA8LSBjb2VmKHN1cnZpdmFsOjpjb3hwaChTXzEwIH4gbHBfaSkpCnJvdW5kKGMod2l0aG91dF9zaHJpbmthZ2U9Y2FsX3Nsb3BlX2J3LAogICAgICAgIHNocnVuaz1jYWxfc2xvcGVfc2hydW5rLAogICAgICAgIG9uZV9vdmVyX3Nocmlua2FnZV9mYWN0b3I9MS9zaHJpbmthZ2VfZmFjdG9yKSwgMykKYGBgCgpPbiB0aGUgZGV2ZWxvcG1lbnQgZGF0YSwgYSBmaXR0ZWQgQ294IG1vZGVsIGhhcyBhIGNhbGlicmF0aW9uIHNsb3BlIG9mIDEgYnkgY29uc3RydWN0aW9uLCBzbyB0aGUgbW9kZWwgd2l0aG91dCBzaHJpbmthZ2UgbG9va3MgcGVyZmVjdCwgd2hpbGUgdGhlIHNocnVuayBtb2RlbCBoYXMgYSBzbG9wZSBvZiAxIGRpdmlkZWQgYnkgdGhlIHNocmlua2FnZSBmYWN0b3IsIHdoaWNoIGlzIGFib3ZlIDEuIFlvdSB3aWxsIG1vc3QgbGlrZWx5IHNlZSB0aGUgc2FtZSBwYXR0ZXJuIGluIHRoZSBwbG90cyAodGhlIHBsb3R0ZWQgc2xvcGUgbWF5IGJlIGNhbGN1bGF0ZWQgc2xpZ2h0bHkgZGlmZmVyZW50bHksIGJ1dCB0aGUgaWRlYSBpcyB0aGUgc2FtZSkuIFNvIHRoZSBhbnN3ZXIgZG9lcyAqbm90KiBtYXRjaCB3aGF0IHlvdSBtaWdodCBleHBlY3QgZnJvbSBzaHJpbmthZ2UsIGFuZCB0aGF0IGlzIHRoZSBwb2ludDogc2hyaW5rYWdlIG1ha2VzIHRoZSBtb2RlbCBsb29rIHdvcnNlIGluIHRoZSBkYXRhIGl0IHdhcyBkZXZlbG9wZWQgb24sIGJ1dCBpdCBpbXByb3ZlcyBjYWxpYnJhdGlvbiBpbiBuZXcgcGF0aWVudHMsIGJlY2F1c2UgdGhlIHNocmlua2FnZSBmYWN0b3Igd2FzIGVzdGltYXRlZCB3aXRoIHRoZSBib290c3RyYXAgdG8gY29ycmVjdCBmb3IgZXhhY3RseSB0aGlzIG9wdGltaXNtLgoKPC9kZXRhaWxzPgoKKipFeGVyY2lzZSAyLioqIElzIHRoZSBhdmVyYWdlIHByZWRpY3RlZCByaXNrIGNsb3NlIHRvIHRoZSBvYnNlcnZlZCAxMC15ZWFyIHJpc2sgZm9yIGFsbCB0aHJlZSBtb2RlbHM/Cgo8ZGV0YWlscz4KPHN1bW1hcnk+U2hvdyBhbnN3ZXIsIGNvZGUgYW5kIHJlc3VsdHM8L3N1bW1hcnk+CgpgYGB7ciBleC10MnAyLTIsIGVycm9yPVRSVUV9Cm9ic2VydmVkX3Jpc2sgPC0gMS1zdW1tYXJ5KHN1cnZpdmFsOjpzdXJ2Zml0KFNfMTB+MSwgZGF0YT1kYXRhKSwgdGltZT1ob3Jpem9uKSRzdXJ2CnJvdW5kKGMob2JzZXJ2ZWQ9b2JzZXJ2ZWRfcmlzaywKICAgICAgICBmdWxsPW1lYW4ocm93TWVhbnMocF9mdWxsKSksCiAgICAgICAgYnc9bWVhbihyb3dNZWFucyhwX2J3KSksCiAgICAgICAgc2hydW5rPW1lYW4ocm93TWVhbnMocF9zaHJ1bmspKSksIDMpCmBgYAoKSWYgdGhlIGF2ZXJhZ2UgcHJlZGljdGVkIHJpc2tzIGFyZSBjbG9zZSB0byB0aGUgb2JzZXJ2ZWQgcmlzaywgdGhlIG1vZGVscyBhcmUgd2VsbCBjYWxpYnJhdGVkICJpbiB0aGUgbGFyZ2UiLiBOb3RlIHRoYXQgZm9yIHRoZSBzaHJ1bmsgbW9kZWwgd2UgcmUtZXN0aW1hdGVkIHRoZSBiYXNlbGluZSBoYXphcmQgYWZ0ZXIgc2hyaW5raW5nIHRoZSBsaW5lYXIgcHJlZGljdG9yLCBidXQgaXQgc3RpbGwgZml0cyB0aGUgZGV2ZWxvcG1lbnQgZGF0YSBhIGJpdCBsZXNzIHdlbGwgdGhhbiB0aGUgdW5zaHJ1bmsgbW9kZWxzOiBkdWUgdG8gdGhlIHNocmlua2FnZSBpdCB1bmRlcmZpdHMgdGhlc2UgcGF0aWVudHMsIGluIG9yZGVyIHRvIHByZWRpY3QgbmV3IHBhdGllbnRzIGJldHRlci4KCjwvZGV0YWlscz4KCioqRXhlcmNpc2UgMy4qKiBBdCB3aGljaCB0aHJlc2hvbGQgcHJvYmFiaWxpdGllcyBkb2VzIHRoZSBtb2RlbCBoYXZlIGEgaGlnaGVyIG5ldCBiZW5lZml0IHRoYW4gdHJlYXRpbmcgZXZlcnlvbmUgb3Igbm8gb25lPwoKPGRldGFpbHM+CjxzdW1tYXJ5PlNob3cgYW5zd2VyLCBjb2RlIGFuZCByZXN1bHRzPC9zdW1tYXJ5PgoKUmVhZCB0aGlzIGZyb20gdGhlIGRlY2lzaW9uIGN1cnZlIHBsb3Q6IHRoZSBtb2RlbCBpcyB1c2VmdWwgYXQgdGhlIHRocmVzaG9sZHMgd2hlcmUgaXRzIGxpbmUgaXMgYWJvdmUgYm90aCB0aGUgInRyZWF0IGFsbCIgYW5kIHRoZSAidHJlYXQgbm9uZSIgbGluZXMuIE9ubHkgbG9vayBhdCB0aHJlc2hvbGRzIHdoZXJlIHRoZXJlIGFyZSBwYXRpZW50cyB3aXRoIHN1Y2ggcHJlZGljdGVkIHJpc2tzOgoKYGBge3IgZXgtdDJwMi0zLCBlcnJvcj1UUlVFfQpyYW5nZShyb3dNZWFucyhwX2J3KSkKYGBgCgpPdXRzaWRlIHRoaXMgcmFuZ2Ugb2YgcHJlZGljdGVkIHJpc2tzIHRoZSBjdXJ2ZSBpcyBub3QgaW5mb3JtYXRpdmUsIGJlY2F1c2UgKGFsbW9zdCkgbm8gcGF0aWVudHMgaGF2ZSBzdWNoIGEgcHJlZGljdGVkIHJpc2suIEFsc28gbm90ZSB0aGF0IHRoZSBkZWNpc2lvbiBjdXJ2ZSBpcyBiYXNlZCBvbiB0aGUgZGV2ZWxvcG1lbnQgZGF0YSwgc28gaXQgaXMgb3B0aW1pc3RpYy4KCjwvZGV0YWlscz4KCioqRXhlcmNpc2UgNC4qKiBUaGUgRENBIGFib3ZlIHVzZXMgYHBfYndgLiBSZXBsYWNlIGl0IHdpdGggYHBfc2hydW5rYCAodGhlIGZpbmFsIG1vZGVsIHdpdGggc2hyaW5rYWdlKS4gRG9lcyB0aGUgY3VydmUgY2hhbmdlPwoKPGRldGFpbHM+CjxzdW1tYXJ5PlNob3cgYW5zd2VyLCBjb2RlIGFuZCByZXN1bHRzPC9zdW1tYXJ5PgoKYGBge3IgZXgtdDJwMi00LCBlcnJvcj1UUlVFfQpkY2Ffc2hydW5rIDwtIGRjdXJ2ZXM6OmRjYSh5IH4gTW9kZWwsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHRpbWU9aG9yaXpvbiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0YT1kYXRhLmZyYW1lKHk9U18xMCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE1vZGVsPXJvd01lYW5zKHBfc2hydW5rKSkpIHw+CiAgcGxvdChzbW9vdGggPSBUUlVFKQpkY2Ffc2hydW5rCmBgYAoKU2hyaW5rYWdlIGRvZXMgbm90IGNoYW5nZSB0aGUgcmFua2luZyBvZiB0aGUgcGF0aWVudHMgKHNvIHRoZSBDLWluZGV4IGlzIHRoZSBzYW1lKSwgYnV0IGl0IG1ha2VzIHRoZSBwcmVkaWN0ZWQgcmlza3MgbGVzcyBleHRyZW1lLiBBIGRlY2lzaW9uIGN1cnZlIGRlcGVuZHMgb24gdGhlIHByZWRpY3RlZCByaXNrcywgYmVjYXVzZSB0aGUgdGhyZXNob2xkIGlzIGFwcGxpZWQgdG8gdGhlbSwgc28gdGhlIGN1cnZlIGNhbiBzdGlsbCBjaGFuZ2Ugc29tZXdoYXQuCgo8L2RldGFpbHM+CgoqKkV4ZXJjaXNlIDUuKiogV2h5IGFyZSB0aGVzZSBwZXJmb3JtYW5jZSBtZWFzdXJlcyBvcHRpbWlzdGljLCBhbmQgaG93IHdvdWxkIHlvdSBnZXQgYW4gaG9uZXN0IGVzdGltYXRlPwoKPGRldGFpbHM+CjxzdW1tYXJ5PlNob3cgYW5zd2VyPC9zdW1tYXJ5PgoKVGhlcmUgaXMgbm8gY29kZSBmb3IgdGhpcyBvbmUuIFRoZSBtb2RlbHMgd2VyZSBkZXZlbG9wZWQgYW5kIGV2YWx1YXRlZCBvbiB0aGUgc2FtZSBwYXRpZW50cywgc28gdGhlIHBlcmZvcm1hbmNlIG1lYXN1cmVzIHBhcnRseSByZWZsZWN0IG5vaXNlIGluIHRoaXMgZGF0YSBzZXQgdGhhdCB0aGUgbW9kZWwgaGFzIGZpdHRlZC4gRm9yIGFuIGhvbmVzdCBlc3RpbWF0ZSwgZXZhbHVhdGUgdGhlIG1vZGVsIG9uIGRhdGEgdGhhdCB3ZXJlIG5vdCB1c2VkIHRvIGRldmVsb3AgaXQ6IG5ldyBwYXRpZW50cyAoZXh0ZXJuYWwgdmFsaWRhdGlvbiksIG9yIHJlcGVhdCB0aGUgd2hvbGUgbW9kZWxsaW5nIHByb2NlZHVyZSBpbiBib290c3RyYXAgc2FtcGxlcyBhbmQgY29ycmVjdCBmb3IgdGhlIG9wdGltaXNtIChhcyB3ZSBkaWQgZm9yIHRoZSBDLWluZGV4IGluIFBhcnQgMSksIG9yIHVzZSBpbnRlcm5hbC1leHRlcm5hbCBjcm9zcy12YWxpZGF0aW9uIChQYXJ0IDMpLgoKPC9kZXRhaWxzPgoKIyBJbnRlcm5hbC1leHRlcm5hbCBjcm9zcy12YWxpZGF0aW9uCgpPcHRpbWlzbS1jb3JyZWN0ZWQgcGVyZm9ybWFuY2UgKFBhcnQgMSkgdXNlcyByYW5kb20gYm9vdHN0cmFwIHNhbXBsZXMuIElmIHlvdXIgZGF0YSBjb21lIGZyb20gc2V2ZXJhbCBjZW50ZXJzIG9yIHJlZ2lvbnMsIHlvdSBjYW4gZG8gc29tZXRoaW5nIHN0cm9uZ2VyOiAqaW50ZXJuYWwtZXh0ZXJuYWwgY3Jvc3MtdmFsaWRhdGlvbiouIFlvdSBsZWF2ZSBvdXQgb25lIGNlbnRlciwgZGV2ZWxvcCB0aGUgd2hvbGUgbW9kZWwgKGluY2x1ZGluZyB2YXJpYWJsZSBzZWxlY3Rpb24gYW5kIHNocmlua2FnZSkgb24gdGhlIG90aGVyIGNlbnRlcnMsIGFuZCB0ZXN0IGl0IG9uIHRoZSBjZW50ZXIgdGhhdCB3YXMgbGVmdCBvdXQuIFlvdSByZXBlYXQgdGhpcyBmb3IgZXZlcnkgY2VudGVyLiBUaGlzIHNob3dzIGhvdyB3ZWxsIHRoZSBtb2RlbGxpbmcgc3RyYXRlZ3kgZ2VuZXJhbGl6ZXMgdG8gbmV3IHNldHRpbmdzLgoKKipOb3RlOiB0aGUgYGNlbnRlcmAgdmFyaWFibGUgaW4gdGhpcyB0dXRvcmlhbCBpcyBhcnRpZmljaWFsKiogKGl0IGFsdGVybmF0ZXMgYmV0d2VlbiAxIGFuZCAyKSwgc28gdGhlcmUgaXMgbm8gcmVhbCBoZXRlcm9nZW5laXR5IGJldHdlZW4gY2VudGVycy4gVGhpcyBpcyBmaW5lIGZvciBhIGRlbW9uc3RyYXRpb24gb2YgdGhlIGNvZGUsIGJ1dCB0aGUgcmVzdWx0cyB3aWxsIG5vdCB0ZWxsIHlvdSBhbnl0aGluZyBhYm91dCB0cmFuc3BvcnRhYmlsaXR5LgoKIyMgUHJlcGFyZSB0aGUgdGVybSBuYW1lcyBvZiB0aGUgZnVsbCBtb2RlbAoKQmFja3dhcmQgc2VsZWN0aW9uIHdpdGggYGZhc3RidygpYCByZXR1cm5zIHRoZSBuYW1lcyBvZiB0aGUgKnZhcmlhYmxlcyogdGhhdCB3ZXJlIGtlcHQsIGZvciBleGFtcGxlIGBhZ2VgLiBCdXQgaW4gdGhlIGZ1bGwgbW9kZWwgYWdlIGFuZCBiaWxpcnViaW4gYXJlIGVudGVyZWQgYXMgYHJjcyhhZ2UsIDMpYCBhbmQgYHJjcyhiaWxpLCAzKWAgKHNwbGluZXMsIHNvIG5vbi1saW5lYXIgZWZmZWN0cykuIElmIHdlIHdvdWxkIGJ1aWxkIHRoZSBuZXcgZm9ybXVsYSBmcm9tIHRoZSB2YXJpYWJsZSBuYW1lcyBvbmx5LCB0aGV5IHdvdWxkIGJlY29tZSBsaW5lYXIuIFdlIHRoZXJlZm9yZSBsb29rIHVwIHRoZSBmdWxsIHRlcm0gc3BlY2lmaWNhdGlvbiBmb3IgZXZlcnkgdmFyaWFibGUgaW4gYGZvcm1fZnVsbGAsIHNvIHRoZSBzcGxpbmVzIGFyZSBrZXB0LgoKYGBge3IgcDMtdGVybXN9CiMgdGVybSBsYWJlbHMgb2YgdGhlIGZ1bGwgbW9kZWwsIGUuZy4gInJjcyhhZ2UsIDMpIiwgInNleCIsIC4uLgojICh0ZXJtcygpIHNwbGl0cyB0aGUgZm9ybXVsYSBpbiBpdHMgdGVybXM7IHRoZSBsYWJlbHMgYXJlIHN0b3JlZCBhcyBhbiBhdHRyaWJ1dGUpCnRlcm1fbGFiZWxzIDwtIGF0dHIodGVybXMoZm9ybV9mdWxsKSwgInRlcm0ubGFiZWxzIikKCiMgdmFyaWFibGUgbmFtZSB0aGF0IGJlbG9uZ3MgdG8gZWFjaCB0ZXJtLCBlLmcuICJhZ2UiLCAic2V4IiwgLi4uCiMgc2FwcGx5KCkgYXBwbGllcyBhIGZ1bmN0aW9uIHRvIGV2ZXJ5IHRlcm0gbGFiZWw7IGFsbC52YXJzKCkgZmluZHMgdGhlIHZhcmlhYmxlCiMgbmFtZXMgaW4gYSBwaWVjZSBvZiBSIGNvZGUgYW5kIFsxXSB0YWtlcyB0aGUgZmlyc3Qgb25lCnRlcm1fdmFycyA8LSBzYXBwbHkodGVybV9sYWJlbHMsIGZ1bmN0aW9uKHgpIGFsbC52YXJzKHBhcnNlKHRleHQ9eCkpWzFdKQpkYXRhLmZyYW1lKHRlcm1fbGFiZWxzLCB0ZXJtX3ZhcnMsIHJvdy5uYW1lcz1OVUxMKQpgYGAKCiMjIFJ1biB0aGUgaW50ZXJuYWwtZXh0ZXJuYWwgY3Jvc3MtdmFsaWRhdGlvbgoKRm9yIGVhY2ggY2VudGVyIGluIHR1cm4gKHRoZSAqbGVmdC1vdXQqIGNlbnRlcikgd2UgZG8gdGhlIGZvbGxvd2luZzoKCjEuIEZpdCB0aGUgZnVsbCBtb2RlbCBvbiB0aGUgZGF0YSAqKmV4Y2x1ZGluZyoqIHRoZSBsZWZ0LW91dCBjZW50ZXIsIGFuZCBwZXJmb3JtIGJhY2t3YXJkIHNlbGVjdGlvbiB3aXRoIGBmYXN0YncoKWAuIFdlIHVzZSB0aGUgc2FtZSBzZXR0aW5ncyBhcyBpbiB0aGUgbWFpbiBhbmFseXNpcyAoYHR5cGU9ImluZGl2aWR1YWwiYCwgcCA8IDAuMDUpLgoyLiBSZWZpdCB0aGUgc2VsZWN0ZWQgbW9kZWwgb24gdGhlIGRldmVsb3BtZW50IGNlbnRlcnMuCjMuIENhbGN1bGF0ZSB0aGUgc2hyaW5rYWdlIGZhY3RvciBvbiB0aGUgZGV2ZWxvcG1lbnQgY2VudGVycyB3aXRoIHRoZSBib290c3RyYXAuCjQuIENhbGN1bGF0ZSB0aGUgYmFzZWxpbmUgaGF6YXJkIG9mIHRoZSBzaHJ1bmsgbW9kZWwgb24gdGhlIGRldmVsb3BtZW50IGNlbnRlcnMuCjUuIFByZWRpY3QgdGhlIHJpc2sgZm9yIHRoZSBwYXRpZW50cyBpbiB0aGUgbGVmdC1vdXQgY2VudGVyIGFuZCBtYWtlIGEgY2FsaWJyYXRpb24gcGxvdC4KCioqTm90ZToqKiB0aGUgZGF0YSB3ZXJlIGltcHV0ZWQgb25jZSBpbiBUdXRvcmlhbCAxLCB1c2luZyBhbGwgY2VudGVycy4gU3RyaWN0bHksIHlvdSBzaG91bGQgYWxzbyBpbXB1dGUgd2l0aGluIHRoZSBkZXZlbG9wbWVudCByZWdpb25zIG9ubHksIHNvIHRoYXQgbm8gaW5mb3JtYXRpb24gZnJvbSB0aGUgbGVmdC1vdXQgcmVnaW9uIGxlYWtzIGludG8gdGhlIG1vZGVsLiBGb3Igc2ltcGxpY2l0eSB3ZSBkbyBub3QgZG8gdGhpcyBoZXJlLgoKYGBge3IgcDMtaWVjdiwgZmlnLndpZHRoPTE0LCBmaWcuaGVpZ2h0PTd9CiMgT3BlbiBhIHBuZyBmaWxlIHdpdGggb25lIHBhbmVsIHBlciBjZW50ZXIgKG9uZSBjYWxpYnJhdGlvbiBwbG90IHBlciBjZW50ZXIpCnBuZyhmaWxlPSJJbnRlcm5hbF9leHRlcm5hbF9jcm9zc192YWxpZGF0aW9uLnBuZyIsCiAgICB3aWR0aD00MDAwLCBoZWlnaHQ9MjAwMCwgcmVzPTMwMCkKbGF5b3V0KG1hdHJpeCgxOjIsIG5jb2w9MiwgYnlyb3c9VFJVRSkpCgojIENvbnRhaW5lcnMgdG8ga2VlcCB0cmFjayBvZiB3aGF0IGhhcHBlbnMgaW4gZXZlcnkgcm91bmQgb2YgdGhlIGxvb3A6CiMgdGhlIHRlcm1zIG9mIHRoZSBmaW5hbCBtb2RlbCBhbmQgaXRzIHNocmlua2FnZSBmYWN0b3IsIHN0YXJ0aW5nIHdpdGggdGhlCiMgbW9kZWwgdGhhdCB3YXMgZGV2ZWxvcGVkIG9uIGFsbCBjZW50ZXJzIChQYXJ0IDEpCnNlbGVjdGVkX3Rlcm1zX3Blcl9tb2RlbCA8LSBwYXN0ZShuYW1lcyhid19tb2RlbCRjb2VmZmljaWVudHMpLCBjb2xsYXBzZT0iLCAiKQpzaHJpbmthZ2VfcGVyX21vZGVsIDwtIGMoc2hyaW5rYWdlX2ZhY3RvcikKCiMgRXZlcnkgY2VudGVyIGlzIGxlZnQgb3V0IG9uY2UgKCJsZWZ0X291dF9jZW50ZXIiIGlzIHRoZSB2YWxpZGF0aW9uIGNlbnRlcikKZm9yIChsZWZ0X291dF9jZW50ZXIgaW4gdW5pcXVlKGRhdGEkY2VudGVyKSl7CgogICMgLS0tLSBXaGljaCBwYXRpZW50cyBhcmUgdXNlZCBmb3IgZGV2ZWxvcG1lbnQgYW5kIHdoaWNoIGZvciB2YWxpZGF0aW9uPyAtLS0tCiAgaXNfZGV2ZWxvcG1lbnQgPC0gZGF0YSRjZW50ZXIgIT0gbGVmdF9vdXRfY2VudGVyICAgIyBUUlVFIGZvciB0aGUgb3RoZXIgY2VudGVycwogIGlzX3ZhbGlkYXRpb24gIDwtIGRhdGEkY2VudGVyID09IGxlZnRfb3V0X2NlbnRlciAgICMgVFJVRSBmb3IgdGhlIGxlZnQtb3V0IGNlbnRlcgogIFNfMTBfZGV2ZWxvcG1lbnQgPC0gU18xMFtpc19kZXZlbG9wbWVudCwgXSAgICAgICAgICMgb3V0Y29tZSBvZiBkZXZlbG9wbWVudCBwYXRpZW50cwogIFNfMTBfdmFsaWRhdGlvbiAgPC0gU18xMFtpc192YWxpZGF0aW9uLCBdICAgICAgICAgICMgb3V0Y29tZSBvZiB2YWxpZGF0aW9uIHBhdGllbnRzCgogICMgLS0tLSBTdGVwIDE6IGZ1bGwgbW9kZWwgb24gdGhlIGRldmVsb3BtZW50IGNlbnRlcnMsIHRoZW4gYmFja3dhcmQgc2VsZWN0aW9uIC0tLS0KICAjIHRoZSBvdXRjb21lIGluIHRoZSBmb3JtdWxhIG11c3Qgb25seSBjb250YWluIHRoZSBkZXZlbG9wbWVudCBwYXRpZW50cywgc28gd2UKICAjIHJlcGxhY2UgU18xMCBieSB0aGUgb3V0Y29tZSBvZiB0aGUgZGV2ZWxvcG1lbnQgcGF0aWVudHMKICBmdWxsX2Zvcm11bGFfZGV2IDwtIHVwZGF0ZShmb3JtX2Z1bGwsIFNfMTBfZGV2ZWxvcG1lbnQgfiAuKQogIGZ1bGxfbW9kZWxfZGV2IDwtIGZpdC5tdWx0LmltcHV0ZShmdWxsX2Zvcm11bGFfZGV2LCBjcGgsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG4uaW1wdXRlPW0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZpdGFyZ3M9bGlzdCh4PVRSVUUsIHk9VFJVRSwgc3Vydj1UUlVFKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeHRyYW5zPWltcHV0ZWRfZGF0YSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0YT1kYXRhLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdWI9aXNfZGV2ZWxvcG1lbnQsICAgIyBvbmx5IHRoZSBkZXZlbG9wbWVudCBwYXRpZW50cwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmaXQucmVwcz1UUlVFKQoKICAjIHNhbWUgc2V0dGluZ3MgYXMgaW4gdGhlIG1haW4gYW5hbHlzaXM6IHR5cGU9ImluZGl2aWR1YWwiLCBwIDwgMC4wNQogIHNlbGVjdGVkX3ZhcmlhYmxlcyA8LSBmYXN0YncoZnVsbF9tb2RlbF9kZXYsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0eXBlPSJpbmRpdmlkdWFsIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJ1bGU9InAiLCBzbHM9MC4wNSkkbmFtZXMua2VwdAoKICAjIC0tLS0gU3RlcCAyOiByZWZpdCB0aGUgc2VsZWN0ZWQgbW9kZWwgb24gdGhlIGRldmVsb3BtZW50IGNlbnRlcnMgLS0tLQogICMgdXNlIHRoZSBmdWxsIHRlcm0gc3BlY2lmaWNhdGlvbiBzbyB0aGF0IHJjcyhhZ2UsIDMpIHN0YXlzIGEgc3BsaW5lCiAgc2VsZWN0ZWRfdGVybXMgPC0gdGVybV9sYWJlbHNbdGVybV92YXJzICVpbiUgc2VsZWN0ZWRfdmFyaWFibGVzXQogIHNlbGVjdGVkX3Rlcm1zX3Blcl9tb2RlbCA8LSBjKHNlbGVjdGVkX3Rlcm1zX3Blcl9tb2RlbCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwYXN0ZShzZWxlY3RlZF90ZXJtcywgY29sbGFwc2U9IiwgIikpCiAgZmluYWxfZm9ybXVsYV9kZXYgPC0gYXMuZm9ybXVsYShwYXN0ZSgiU18xMF9kZXZlbG9wbWVudCB+IiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBhc3RlKHNlbGVjdGVkX3Rlcm1zLCBjb2xsYXBzZT0iICsgIikpKQogIGZpbmFsX21vZGVsX2RldiA8LSBmaXQubXVsdC5pbXB1dGUoZmluYWxfZm9ybXVsYV9kZXYsIGNwaCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG4uaW1wdXRlPW0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmaXRhcmdzPWxpc3QoeD1UUlVFLCB5PVRSVUUsIHN1cnY9VFJVRSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB4dHJhbnM9aW1wdXRlZF9kYXRhLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0YT1kYXRhLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3ViPWlzX2RldmVsb3BtZW50LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZml0LnJlcHM9VFJVRSkKCiAgIyAtLS0tIFN0ZXBzIDMtNTogZm9yIGV2ZXJ5IGltcHV0ZWQgZGF0YSBzZXQsIHNocmlua2FnZSBmYWN0b3IgYW5kIHByZWRpY3Rpb25zIC0tLS0KICBzaHJpbmthZ2VfcGVyX2ltcHV0YXRpb24gPC0gYygpICAgICAgICAgIyBzaHJpbmthZ2UgZmFjdG9yIG9mIGVhY2ggaW1wdXRlZCBkYXRhIHNldAogIHByZWRpY3RlZF9yaXNrc192YWxpZGF0aW9uIDwtIGMoKSAgICAgICAjIHByZWRpY3RlZCByaXNrcyBpbiB0aGUgbGVmdC1vdXQgY2VudGVyCiAgZm9yIChpIGluIDE6bSl7CiAgICAjIHRoZSBpLXRoIGltcHV0ZWQgZGF0YSBzZXQsIHNwbGl0IGluIGRldmVsb3BtZW50IGFuZCB2YWxpZGF0aW9uIHBhdGllbnRzCiAgICBpbXB1dGVkX2kgPC0gbWljZTo6Y29tcGxldGUoaW1wdXRlZF9kYXRhLCBpKQogICAgZGV2ZWxvcG1lbnRfaSA8LSBpbXB1dGVkX2lbaXNfZGV2ZWxvcG1lbnQsIF0KICAgIHZhbGlkYXRpb25faSAgPC0gaW1wdXRlZF9pW2lzX3ZhbGlkYXRpb24sIF0KCiAgICAjIFN0ZXAgMzogc2hyaW5rYWdlIGZhY3RvciBmcm9tIHRoZSBib290c3RyYXAgb24gdGhlIGRldmVsb3BtZW50IGNlbnRlcnMKICAgICMgKGJhY2t3YXJkIHNlbGVjdGlvbiBpcyByZXBlYXRlZCBpbiBldmVyeSBib290c3RyYXAgc2FtcGxlKQogICAgdmFsaWRhdGlvbl9yZXN1bHRzIDwtIHZhbGlkYXRlKGZ1bGxfbW9kZWxfZGV2JGZpdHNbW2ldXSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwcj1GQUxTRSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBCPTIwMCwgYnc9VFJVRSwgcnVsZT0icCIsIHNscz0wLjA1LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHR5cGU9ImluZGl2aWR1YWwiLCB0b2w9MWUtMTMpCiAgICBzaHJpbmthZ2VfaSA8LSB2YWxpZGF0aW9uX3Jlc3VsdHNbIlNsb3BlIiwgInRlc3QiXQogICAgc2hyaW5rYWdlX3Blcl9pbXB1dGF0aW9uIDwtIGMoc2hyaW5rYWdlX3Blcl9pbXB1dGF0aW9uLCBzaHJpbmthZ2VfaSkKCiAgICAjIFN0ZXAgNDogYmFzZWxpbmUgaGF6YXJkIG9mIHRoZSBzaHJ1bmsgbW9kZWwgb24gdGhlIGRldmVsb3BtZW50IGNlbnRlcnMKICAgICMgbGluZWFyIHByZWRpY3RvciBvZiB0aGUgZGV2ZWxvcG1lbnQgcGF0aWVudHMsIHNocnVuayB0b3dhcmRzIHplcm8KICAgIGxwX2RldmVsb3BtZW50IDwtIHByZWRpY3QoZmluYWxfbW9kZWxfZGV2JGZpdHNbW2ldXSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbmV3ZGF0YT1kZXZlbG9wbWVudF9pLCB0eXBlPSJscCIpCiAgICBscF9kZXZlbG9wbWVudF9zaHJ1bmsgPC0gc2hyaW5rYWdlX2kqbHBfZGV2ZWxvcG1lbnQKICAgICMgQ294IG1vZGVsIHdpdGggb25seSB0aGUgc2hydW5rIGxpbmVhciBwcmVkaWN0b3IgYXMgb2Zmc2V0IGdpdmVzIHRoZSBiYXNlbGluZSBoYXphcmQKICAgIGJhc2VsaW5lX2ZpdCA8LSBzdXJ2aXZhbDo6Y294cGgoU18xMF9kZXZlbG9wbWVudCB+IG9mZnNldChscF9kZXZlbG9wbWVudF9zaHJ1bmspKQogICAgYmFzZWxpbmVfaGF6YXJkIDwtIHN1cnZpdmFsOjpiYXNlaGF6KGJhc2VsaW5lX2ZpdCkKICAgICMgY3VtdWxhdGl2ZSBiYXNlbGluZSBoYXphcmQgYXQgdGhlIGxhc3QgZXZlbnQgdGltZSBiZWZvcmUgdGhlIGhvcml6b24KICAgIGxhc3RfdGltZSA8LSBtYXgoYmFzZWxpbmVfaGF6YXJkJHRpbWVbYmFzZWxpbmVfaGF6YXJkJHRpbWUgPD0gaG9yaXpvbl0pCiAgICBoMF9pIDwtIGJhc2VsaW5lX2hhemFyZCRoYXphcmRbYmFzZWxpbmVfaGF6YXJkJHRpbWUgPT0gbGFzdF90aW1lXQoKICAgICMgU3RlcCA1OiBwcmVkaWN0ZWQgcmlza3MgZm9yIHRoZSBwYXRpZW50cyBpbiB0aGUgbGVmdC1vdXQgY2VudGVyCiAgICBscF92YWxpZGF0aW9uX3NocnVuayA8LSBzaHJpbmthZ2VfaSpwcmVkaWN0KGZpbmFsX21vZGVsX2RldiRmaXRzW1tpXV0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5ld2RhdGE9dmFsaWRhdGlvbl9pLCB0eXBlPSJscCIpCiAgICBwcmVkaWN0ZWRfcmlza3NfdmFsaWRhdGlvbiA8LSBjYmluZChwcmVkaWN0ZWRfcmlza3NfdmFsaWRhdGlvbiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFByZWRpY3Rpb25Ub29sczo6ZnVuLmV2ZW50KGxwPWxwX3ZhbGlkYXRpb25fc2hydW5rLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaDA9aDBfaSkpCiAgfQoKICAjIGtlZXAgdGhlIG1lYW4gc2hyaW5rYWdlIGZhY3RvciBvdmVyIHRoZSBpbXB1dGVkIGRhdGEgc2V0cyBmb3IgdGhpcyByb3VuZAogIHNocmlua2FnZV9wZXJfbW9kZWwgPC0gYyhzaHJpbmthZ2VfcGVyX21vZGVsLCBtZWFuKHNocmlua2FnZV9wZXJfaW1wdXRhdGlvbikpCgogICMga2VlcCBwcmVkaWN0aW9ucyBzdHJpY3RseSBiZXR3ZWVuIDAgYW5kIDEgKHNlZSB0aGUgbm90ZSBhYm92ZSkKICBwcmVkaWN0ZWRfcmlza3NfdmFsaWRhdGlvbiA8LSBwbWluKHBtYXgocHJlZGljdGVkX3Jpc2tzX3ZhbGlkYXRpb24sIDAuMDAwMSksIDAuOTk5OSkKCiAgIyAtLS0tIENhbGlicmF0aW9uIHBsb3QgZm9yIHRoZSBsZWZ0LW91dCBjZW50ZXIgLS0tLQogIFByZWRpY3Rpb25Ub29sczo6dmFsLnN1cnYubWkocD1wcmVkaWN0ZWRfcmlza3NfdmFsaWRhdGlvbiwgeT1TXzEwX3ZhbGlkYXRpb24sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aW1lPWhvcml6b24sIGc9MiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIENJLm1ldHJpY3M9VFJVRSwgbWFpbj1sZWZ0X291dF9jZW50ZXIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzaG93Lm1ldHJpY3M9YyhyZXAoVFJVRSwgNiksIEZBTFNFKSkKfQpkZXYub2ZmKCkKYGBgCgpgYGB7ciBwMy1pZWN2LXNob3csIGVjaG89RkFMU0UsIG91dC53aWR0aD0iMTAwJSJ9CmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKCJJbnRlcm5hbF9leHRlcm5hbF9jcm9zc192YWxpZGF0aW9uLnBuZyIpCmBgYAoKIyMgRXhlcmNpc2VzOiBQYXJ0IDMKClRyeSBlYWNoIGV4ZXJjaXNlIHlvdXJzZWxmIGZpcnN0LiBUaGVuIG9wZW4gdGhlIGZpZWxkIGJlbG93IGl0IHRvIHNlZSB0aGUgYW5zd2VyLCB0aGUgY29kZSBhbmQgaXRzIHJlc3VsdHMuCgoqKkV4ZXJjaXNlIDEuKiogV2hpY2ggdmFyaWFibGVzIGFyZSBzZWxlY3RlZCB3aGVuIGNlbnRlciAxIG9yIGNlbnRlciAyIGlzIGxlZnQgb3V0PyBBcmUgdGhleSB0aGUgc2FtZSBhcyBpbiB0aGUgZmluYWwgbW9kZWw/IChUaXA6IHNlZSB0aGUgb2JqZWN0cyBgc2VsZWN0ZWRfdGVybXNfcGVyX21vZGVsYCBhbmQgYHNocmlua2FnZV9wZXJfbW9kZWxgIGluIHRoZSBjb2RlIGFib3ZlLikKCjxkZXRhaWxzPgo8c3VtbWFyeT5TaG93IGFuc3dlciwgY29kZSBhbmQgcmVzdWx0czwvc3VtbWFyeT4KClRoZSBvYmplY3RzIGBzZWxlY3RlZF90ZXJtc19wZXJfbW9kZWxgIGFuZCBgc2hyaW5rYWdlX3Blcl9tb2RlbGAgaG9sZCBleGFjdGx5IHRoaXMgaW5mb3JtYXRpb24uIEhlcmUgd2UgcHV0IHRoZW0gaW4gb25lIHRhYmxlOgoKYGBge3IgZXgtdDJwMy0xLCBlcnJvcj1UUlVFfQpkYXRhLmZyYW1lKGRldmVsb3BtZW50X2RhdGE9YygiQWxsIGRhdGEgKGZpbmFsIG1vZGVsKSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBhc3RlKCJXaXRob3V0IiwgdW5pcXVlKGFzLmNoYXJhY3RlcihkYXRhJGNlbnRlcikpKSksCiAgICAgICAgICAgc2VsZWN0ZWRfdGVybXM9c2VsZWN0ZWRfdGVybXNfcGVyX21vZGVsLAogICAgICAgICAgIHNocmlua2FnZV9mYWN0b3I9cm91bmQoc2hyaW5rYWdlX3Blcl9tb2RlbCwgMykpCmBgYAoKQ29tcGFyZSB0aGUgc2VsZWN0ZWQgdGVybXMgaW4gZWFjaCByb3cgd2l0aCB0aGUgZmluYWwgbW9kZWwgb24gYWxsIGRhdGEuIERpZmZlcmVudCBzZWxlY3Rpb25zIGluIGRpZmZlcmVudCBkZXZlbG9wbWVudCBzZXRzIHNob3cgdGhhdCB2YXJpYWJsZSBzZWxlY3Rpb24gaXMgdW5zdGFibGUuCgo8L2RldGFpbHM+CgoqKkV4ZXJjaXNlIDIuKiogQ29tcGFyZSB0aGUgY2FsaWJyYXRpb24gcGxvdHMgb2YgdGhlIHR3byBjZW50ZXJzLiBXb3VsZCB5b3UgZXhwZWN0IGJpZyBkaWZmZXJlbmNlcyBpbiByZWFsIGRhdGEgd2l0aCB0cnVseSBkaWZmZXJlbnQgY2VudGVycz8gV2hhdCBjb3VsZCBjYXVzZSB0aGVtPwoKPGRldGFpbHM+CjxzdW1tYXJ5PlNob3cgYW5zd2VyPC9zdW1tYXJ5PgoKVGhlcmUgaXMgbm8gY29kZSBmb3IgdGhpcyBvbmUuIEluIHRoaXMgdHV0b3JpYWwgdGhlIGNlbnRlcnMgYXJlIGFydGlmaWNpYWwsIHNvIHRoZSB0d28gcGxvdHMgc2hvdWxkIGxvb2sgYWxpa2UsIGFuZCBkaWZmZXJlbmNlcyBhcmUgZHVlIHRvIGNoYW5jZS4gSW4gcmVhbCBkYXRhIHdpdGggdHJ1bHkgZGlmZmVyZW50IGNlbnRlcnMgeW91IHdvdWxkIGV4cGVjdCBsYXJnZXIgZGlmZmVyZW5jZXMuIFBvc3NpYmxlIGNhdXNlcyBhcmUgZGlmZmVyZW5jZXMgaW4gY2FzZSBtaXggKHRoZSBwYXRpZW50cyBkaWZmZXIpLCBpbiBob3cgcHJlZGljdG9ycyBhbmQgb3V0Y29tZXMgYXJlIG1lYXN1cmVkLCBpbiB0cmVhdG1lbnQgcHJhY3RpY2UsIGFuZCBpbiBmb2xsb3ctdXAuCgo8L2RldGFpbHM+CgoqKkV4ZXJjaXNlIDMuKiogV2h5IGRvIHdlIGRldmVsb3AgdGhlIG1vZGVsIG9uIHRoZSBvdGhlciBjZW50ZXJzIGFuZCBub3Qgb24gYSByYW5kb20gaGFsZiBvZiB0aGUgZGF0YT8KCjxkZXRhaWxzPgo8c3VtbWFyeT5TaG93IGFuc3dlcjwvc3VtbWFyeT4KClRoZXJlIGlzIG5vIGNvZGUgZm9yIHRoaXMgb25lLiBBIHJhbmRvbSBzcGxpdCBnaXZlcyB2YWxpZGF0aW9uIGRhdGEgdGhhdCBhcmUgdmVyeSBzaW1pbGFyIHRvIHRoZSBkZXZlbG9wbWVudCBkYXRhLCBzbyBpdCBtYWlubHkgc2hvd3MgaW50ZXJuYWwgdmFsaWRpdHkgYW5kIHdhc3RlcyBoYWxmIG9mIHRoZSBkYXRhLiBMZWF2aW5nIG91dCBhIHdob2xlIGNlbnRlciB0ZXN0cyB0aGUgbW9kZWwgaW4gYSBkaWZmZXJlbnQgc2V0dGluZyBmcm9tIHRoZSBkZXZlbG9wbWVudCBzZXR0aW5ncywgd2hpY2ggaXMgY2xvc2VyIHRvIGhvdyB0aGUgbW9kZWwgd2lsbCBiZSB1c2VkIGluIHByYWN0aWNlLCB3aGlsZSBzdGlsbCB1c2luZyBtb3N0IG9mIHRoZSBkYXRhIGZvciBkZXZlbG9wbWVudC4KCjwvZGV0YWlscz4KCioqRXhlcmNpc2UgNC4qKiBIb3cgd291bGQgeW91IGNoYW5nZSB0aGUgY29kZSBpZiB5b3UgaGFkIDUgY2VudGVycyBpbnN0ZWFkIG9mIDI/Cgo8ZGV0YWlscz4KPHN1bW1hcnk+U2hvdyBhbnN3ZXIgYW5kIGNvZGUgKG5vdCBydW4pPC9zdW1tYXJ5PgoKVGhlIGxvb3AgYWxyZWFkeSB3b3JrcyBmb3IgYW55IG51bWJlciBvZiBjZW50ZXJzLCBiZWNhdXNlIGl0IHVzZXMgYHVuaXF1ZShkYXRhJGNlbnRlcilgLiBPbmx5IHRoZSBmaWd1cmUgbmVlZHMgbW9yZSByb29tLiBUaGlzIGNvZGUgaXMgbm90IHJ1biwgYmVjYXVzZSB0aGUgdHV0b3JpYWwgZGF0YSBoYXZlIDIgY2VudGVyczoKCmBgYHtyIGV4LXQycDMtNCwgZXZhbD1GQUxTRX0KIyBvbmUgcGFuZWwgcGVyIGNlbnRlciBpbiB0aGUgZmlndXJlCm5fY2VudGVycyA8LSBsZW5ndGgodW5pcXVlKGRhdGEkY2VudGVyKSkKcG5nKGZpbGU9IkludGVybmFsX2V4dGVybmFsX2Nyb3NzX3ZhbGlkYXRpb24ucG5nIiwKICAgIHdpZHRoPTIwMDAqbl9jZW50ZXJzLCBoZWlnaHQ9MjAwMCwgcmVzPTMwMCkKbGF5b3V0KG1hdHJpeCgxOm5fY2VudGVycywgbmNvbD1uX2NlbnRlcnMsIGJ5cm93PVRSVUUpKQoKIyAuLi4gdGhlIHNhbWUgbG9vcCBhcyBhYm92ZSAuLi4KYGBgCgpBbHNvIHRoaW5rIGFib3V0IHRoZSBudW1iZXIgb2YgcGF0aWVudHMgcGVyIGNlbnRlci4gV2l0aCA1IGNlbnRlcnMgZWFjaCBjZW50ZXIgaGFzIGZld2VyIHBhdGllbnRzLCBzbyB5b3UgbWF5IHdhbnQgZmV3ZXIgcmlzayBncm91cHMgaW4gdGhlIGNhbGlicmF0aW9uIHBsb3QgKHRoZSBhcmd1bWVudCBgZz0yYCBvZiBgdmFsLnN1cnYubWlgKS4gRWFjaCBkZXZlbG9wbWVudCBzZXQgbm93IGNvbnRhaW5zIDQgb2YgdGhlIDUgY2VudGVycywgc28gbW9yZSBvZiB0aGUgZGF0YSBpcyB1c2VkIGZvciBkZXZlbG9wbWVudCB0aGFuIHdpdGggMiBjZW50ZXJzLgoKPC9kZXRhaWxzPgo=