About the Notebook

This Rnotebook covers part of the material in chapter 4 of “An Introduction to Statistical Learning with Applications in R” by James, Witten, Hastie and Tibshirani (ISLR). We are going to work on sections 4.6.3 (Linear Discriminant Analysis) and section 4.6.4 (Quadratic Discriminant Analysis).

The first part of the notebook covers important concepts behind Discriminant Analysis and its relation to Bayes’ Theorem then we go over creating a model using the mlr package plotting and ROC curve, on this notebook we are also going to use the MASS package to follow a similar example on the book (ISLR).

Notebook Setup: Set the working directory to the source location by: Session -> Set Working Directory -> To Source File Location

#### load and install packages if necessary
### https://mlr-org.github.io/
if(!require("mlr",quietly = TRUE)) install.packages("mlr")
### http://www.stats.ox.ac.uk/pub/MASS4/
if(!require("MASS",quietly = TRUE)) install.packages("MASS")
### http://ggplot2.org/
if(!require("ggplot2",quietly = TRUE)) install.packages("ggplot2")
### https://github.com/taiyun/corrplot
if(!require("corrplot",quietly = TRUE)) install.packages("corrplot")
#### set seed and turn off scientific notation to see small/large numbers
set.seed(1234)
options(scipen = 9999)

Background


Logistic Regression (Review)

\[ p(X) = \frac{e^{\beta_0+\beta_1X_1+\dots+\beta_pX_p}}{1 + e^{\beta_0+\beta_1X_1+···+\beta_pX_p}} .\]

Linear discriminant model (LDA)

When to use Linear Discriminant Analysis


Classification using Bayes’ Theorem


Bayes’ theorem

\[ Pr(Y=k|X=x) = \frac{\pi_kf_k(x)}{\sum^K_{l=1}\pi_lf_l(x)}. \qquad \qquad (4.10) \]

Posterior Probability

We refer to \(pk(x)\) as the posterior probability that an observation \(X = x\) belongs to the \(kth\) class. That is, it is the probability that the observation belongs to the \(kth\) class, given the predictor value for that observation.

Suppose we assume that \(f_k(x)\) is normal or Gaussian. In the one normal dimensional setting, the normal density takes the form:

\[ f_k(x) = \frac{1}{\sqrt{2\pi\sigma_k}}exp\Bigg( -\frac{1}{2\sigma^2_k}(x-\mu_k)^2\Bigg). \qquad \qquad (4.11) \]

By plugging (4.11) into (4.10), we get:

\[ p_k(x) = \frac{\pi_k\frac{1}{\sqrt{2\pi}\sigma}\text{exp}\big(-\frac{1}{2\sigma^2}(x-\mu_k)^2 \big)}{\sum^K_{l=1} \pi_l\frac{1}{\sqrt{2\pi}\sigma}\text{exp}\big(-\frac{1}{2\sigma^2}(x- \mu_l)^2 \big)}. \qquad \qquad \qquad (4.12) \]


Bayes classifier and Decision Boundary


Bayes classifier, which classifies an observation (\(X = x\)) to the class for which \(p_k(X)\) (4.12) is largest. By taking the log of (4.12) and rearranging the terms we get:

\[ \delta_k(x) = x \cdot \frac{\mu_k}{\sigma^2} - \frac{\mu^2_k}{2\sigma^2} + log(\pi_k) \qquad \qquad \qquad (4.13) \] If \(K=2\) and \(\pi_1 = \pi_2\), then the Bayes classifier assigns:

\[ \text{BayesClassifier} = \Bigg\{ \begin{align} 1, & \text{ if } 2x(\mu1-\mu2)>\mu^2_1-\mu^2_2 \\ 2, & \text{ otherwise } \end{align} \]

The Bayes decision boundary corresponds to the point where:

\[ x = \frac{\mu^2_1 - \mu^2_2}{2(\mu_1 - \mu_2)}=\frac{\mu1 + \mu2}{2}. \qquad \qquad \qquad (4.14) \]

Plot of two one-dimensional normal density functions

The dashed vertical line represents the Bayes decision boundary calculated using the formula above.

mean_a <- 1.5
mean_b <- -1.5
bayes_decision_boundary <- (mean_a + mean_b)/2
p <- ggplot( data.frame(x=c(-4,4)), aes(x)) +
  stat_function( fun = dnorm, args = list(mean = mean_a, sd = 1), col='red' ) +
  stat_function( fun = dnorm, args = list(mean = mean_b, sd = 1), col='blue' )
p + geom_vline(xintercept = bayes_decision_boundary, linetype="dashed") 


Linear Discriminant Analysis


First lets create a data set by of size \(n=500\) by randomly sampling from a normal distribution.

x <- seq(-4, 4, length = 500)
df <- data.frame(cond = rep(letters[1:2], each = 500),
                 values = c( rnorm(x,mean = mean_a, sd = 1), rnorm(x,mean = mean_b, sd = 1)))
head(df, n=5)

The linear discriminant analysis (LDA) method approximates the Bayes classifier by plugging estimates for \(\pi_k\), \(\mu_k\), and \(\sigma^2\) into (4.13)

\[ \hat{\mu_k} = \frac{1}{n_k}\sum_{i:y_i=k}x_i \qquad \qquad \qquad \qquad \qquad \qquad \qquad \\ \hat{\sigma}^2 = \frac{1}{n-K}\sum^K_{k=1}\sum_{i:y_i=k}(x_i-\hat{\mu}_k)^2 \qquad \qquad \quad (4.15) \]

\[ \hat{\delta_k(x)} = x \cdot \frac{\hat{\mu_k}}{\hat{\sigma}^2} - \frac{\hat{\mu}^2_k}{2\hat{\sigma}^2} + log(\hat{\pi}_k) \qquad \qquad \qquad (4.17) \]

sample_a <- data.frame("values"= sample(df$values[df$cond=="a"], 100), "cond" = "a")
sample_b <- data.frame("values"= sample(df$values[df$cond=="b"], 100), "cond" = "b")
mean_sample_a <- mean( sample_a$values )
mean_sample_b <- mean( sample_b$values )
hist_a <- hist( sample_a$values, plot = FALSE )
hist_b <- hist( sample_b$values, plot = FALSE )
sigma_a <- weighted.mean( hist_a$density, hist_a$counts )
sigma_b <- weighted.mean( hist_b$density, hist_b$counts )
prob_a <- 30/100
prob_b <- 30/100

Now to were drawn from each of the two classes, and are shown as histograms. The Bayes decision boundary is again shown as a dashed vertical line. The solid vertical line represents the LDA decision boundary estimated from the training data.

LDA_decision_boundary <- (mean_sample_a + mean_sample_b)/2
ggplot(df, aes(x = values, fill = cond)) +
  geom_histogram( binwidth = 0.4, alpha = 0.5, position = "identity" ) +
  geom_vline( xintercept = LDA_decision_boundary, linetype = "solid" ) +
  geom_vline( xintercept = bayes_decision_boundary, linetype = "dashed" )


Use case: IMDb Movies, TV and Celebrities – Ratings Dataset


For this use case we are going to explore the IMDb movie ratings data set then we are going to use the Linear Discriminant Analysis and Quadratic Discriminant Analysis to classify movies by gender given a number of features present in the data.

First lets load the data, explore a couple of rows, data types make some changes and deal with NA’s

imdb <- read.csv("data/imdb_rating.csv")
head(imdb)

Lets take a closer look to the numeric data by running the command summary() and `str() to try to find any irregularities in the data determine the data types.

summary(imdb[7:20])
     length          budget            director_fb_likes actor1_fb_likes  actor2_fb_likes  actor3_fb_likes  
 Min.   :  7.0   Min.   :        218   Min.   :    0.0   Min.   :     0   Min.   :     0   Min.   :    0.0  
 1st Qu.: 93.0   1st Qu.:    6000000   1st Qu.:    7.0   1st Qu.:   614   1st Qu.:   281   1st Qu.:  133.0  
 Median :103.0   Median :   20000000   Median :   49.0   Median :   988   Median :   595   Median :  371.5  
 Mean   :107.2   Mean   :   39752620   Mean   :  686.5   Mean   :  6560   Mean   :  1652   Mean   :  645.0  
 3rd Qu.:118.0   3rd Qu.:   45000000   3rd Qu.:  194.5   3rd Qu.: 11000   3rd Qu.:   918   3rd Qu.:  636.0  
 Max.   :511.0   Max.   :12215500000   Max.   :23000.0   Max.   :640000   Max.   :137000   Max.   :23000.0  
 NA's   :15      NA's   :492           NA's   :104       NA's   :7        NA's   :13       NA's   :23       
 total_cast_likes    fb_likes      critic_reviews  users_reviews     users_votes          score        aspect_ratio  
 Min.   :     0   Min.   :     0   Min.   :  1.0   Min.   :   1.0   Min.   :      5   Min.   :1.600   Min.   : 1.18  
 1st Qu.:  1411   1st Qu.:     0   1st Qu.: 50.0   1st Qu.:  65.0   1st Qu.:   8594   1st Qu.:5.800   1st Qu.: 1.85  
 Median :  3090   Median :   166   Median :110.0   Median : 156.0   Median :  34359   Median :6.600   Median : 2.35  
 Mean   :  9699   Mean   :  7526   Mean   :140.2   Mean   : 272.8   Mean   :  83668   Mean   :6.442   Mean   : 2.22  
 3rd Qu.: 13756   3rd Qu.:  3000   3rd Qu.:195.0   3rd Qu.: 326.0   3rd Qu.:  96309   3rd Qu.:7.200   3rd Qu.: 2.35  
 Max.   :656730   Max.   :349000   Max.   :813.0   Max.   :5060.0   Max.   :1689764   Max.   :9.500   Max.   :16.00  
                                   NA's   :50      NA's   :21                                         NA's   :329    
     gross          
 Min.   :      162  
 1st Qu.:  5340988  
 Median : 25517500  
 Mean   : 48468408  
 3rd Qu.: 62309438  
 Max.   :760505847  
 NA's   :884        

Check the structure of the data set and data types

str(imdb)
'data.frame':   5043 obs. of  21 variables:
 $ title            : Factor w/ 4917 levels "[Rec] 2 ","[Rec] ",..: 398 2731 3279 3707 3332 1961 3289 3459 399 1631 ...
 $ genres           : Factor w/ 914 levels "Action","Action|Adventure",..: 107 101 128 288 754 126 120 308 126 447 ...
 $ director         : Factor w/ 2399 levels "","A. Raven Cruz",..: 929 801 2027 380 606 109 2030 1652 1228 554 ...
 $ actor1           : Factor w/ 2098 levels "","50 Cent","A.J. Buckley",..: 305 983 355 1968 528 443 787 223 338 34 ...
 $ actor2           : Factor w/ 3033 levels "","50 Cent","A. Michael Baldwin",..: 1408 2218 2489 534 2433 2549 1228 801 2440 653 ...
 $ actor3           : Factor w/ 3522 levels "","50 Cent","A.J. Buckley",..: 3442 1395 3134 1771 1 2714 1970 2163 3018 2941 ...
 $ length           : int  178 169 148 164 NA 132 156 100 141 153 ...
 $ budget           : num  237000000 300000000 245000000 250000000 NA ...
 $ director_fb_likes: int  0 563 0 22000 131 475 0 15 0 282 ...
 $ actor1_fb_likes  : int  1000 40000 11000 27000 131 640 24000 799 26000 25000 ...
 $ actor2_fb_likes  : int  936 5000 393 23000 12 632 11000 553 21000 11000 ...
 $ actor3_fb_likes  : int  855 1000 161 23000 NA 530 4000 284 19000 10000 ...
 $ total_cast_likes : int  4834 48350 11700 106759 143 1873 46055 2036 92000 58753 ...
 $ fb_likes         : int  33000 0 85000 164000 0 24000 0 29000 118000 10000 ...
 $ critic_reviews   : int  723 302 602 813 NA 462 392 324 635 375 ...
 $ users_reviews    : int  3054 1238 994 2701 NA 738 1902 387 1117 973 ...
 $ users_votes      : int  886204 471220 275868 1144337 8 212204 383056 294810 462669 321795 ...
 $ score            : num  7.9 7.1 6.8 8.5 7.1 6.6 6.2 7.8 7.5 7.5 ...
 $ aspect_ratio     : num  1.78 2.35 2.35 2.35 NA 2.35 2.35 1.85 2.35 2.35 ...
 $ gross            : int  760505847 309404152 200074175 448130642 NA 73058679 336530303 200807262 458991599 301956980 ...
 $ year             : int  2009 2007 2015 2012 NA 2012 2007 2010 2015 2009 ...

From the table above we can see that there is some work to do in order to start working with the imdb data. First we need to change the genres column to character type and extract the first entry in order to create our labels. Also on this step we want to find a way to remove or replace missing values for this case we are going to remove them.

### change data type and remove NA's from the data
imdb$genres <- as.character(imdb$genres)
imdb <- na.omit(imdb)
### Now we want to extract the first movie genre from the column and find the frequency of the genre in the data set
genre <- sapply( imdb$genres, strsplit,"[|]", USE.NAMES = FALSE )
get_one_genre <- function(entry) for(i in entry) return(i[[1]])
imdb$genres <- unlist(lapply(genre, get_one_genre))
table_genre <- table(imdb$genres)
table_genre

     Action   Adventure   Animation   Biography      Comedy       Crime Documentary       Drama      Family     Fantasy 
        963         372          45         208        1001         259          30         685           3          37 
     Horror     Musical     Mystery     Romance      Sci-Fi    Thriller     Western 
        165           2          23           2           8           1           3 

The frequency table above we can see that some of the genres don’t have enough observations and others are over represented so lets take the observations greater than 50 and less than 400 as this range would give us a set of classes which are proportional to each other. Also we have to check for classes that might represent similar/related genres such as Crime and Horror

### Here we are selecting observations greather than 50 and less than 400 alse
### creating a vector containing the name of the classes that are interested
table_genre_400 <- table_genre[ table_genre > 50 & table_genre < 400 ]
y_classes <- names(table_genre_400[ c(1,2,4) ])
y_classes
[1] "Adventure" "Biography" "Horror"   

Now we are going to use those cases to select movies in the genres that we are interested We also want to consider newer movies as most metrics use FB likes, critics reviews and other digital forms of engagement with movie that if we use older data it might not be that accurate

### To create ROC plot for two classes 
#y_classes <- y_classes[c(1,3)] 
movies <- imdb[ imdb$genres %in% y_classes, ]
movies <- movies[ movies$year > 2006, ]
### Here we are only selecting genre and other numeric values that we are going to use to create the model
head(movies)
movies <- movies[ c(2,7:18,20) ]

Training, Testing Data Split with mlr package


### First lets selesct two classes so we can later create an ROC plot
y_classes_mlr <- y_classes[ c(1,3) ] 
### Select only movies mad ein the last 10 years within the selected classes  
movies_mlr <- imdb[ imdb$genres %in% y_classes_mlr,]
movies_mlr <- movies_mlr[ movies_mlr$year > 2006, ]
### Disregard non-numeric variables and some correlated variables
movies_mlr <- movies_mlr[c(2,7:18,20)]
### Create a mlr task object using the movies dataset and gender as a target
movies_mlr.task <- makeClassifTask(data = movies_mlr, target = "genres")
### Now we can create the Training (~65%) and Testing (~35%) sets based on the number of samples 
n = getTaskSize(movies_mlr.task)
train_mlr.set = sample( n, size = round(2/3 * n) )
test_mlr.set = setdiff( seq_len(n), train_mlr.set )

Training, Testing Data Split


When building the model it is important to split our data in train and test sets using about \(75\%\) of the data for training and the remaining for testing. In this case we are taking a sample of \(300\) observations from the larger movies data set

### Here we are creating a random sample of size n=100 from the dataset
### the sample function return the index of the selected rows
idx_smp <- sample(seq_len(nrow(movies)), size = 100)
### Now we extract select that datsa using the index of the rows and take another sample
### But this time is to create the Training and Testing sets
movies <- movies[idx_smp,]
smp_size <- floor(0.75 * nrow(movies))
train_ind <- sample(seq_len(nrow(movies)), size = smp_size)
### TRAINING
train_data <- movies[ train_ind, ]
### TESTING
test_data <- movies[ -train_ind, ]
### Labels from the testing set
y_test <- as.factor(test_data['genres']$genres)
test_data['genres'] <- NULL
head(train_data)

Visual Analysis


Lets make some scatter plots to visually identify any variable interactions

selected_columns = c('director_fb_likes',
                     'total_cast_likes',
                     'fb_likes',
                     'users_votes',
                     'score',
                     'gross')
#plot(train)
plot( train_data[ selected_columns ] )

Creating Z-scores using the training data

### Uncomment to use all the columns in the dataset
#zscores <- apply(train, 1, function(x) (x - mean(x)) / sd(x))
zscores <- apply( train_data[selected_columns], 1, function(x) (x - mean(x)) / sd(x))
zscores <- data.frame( t(zscores) )

Correlation Plot

corr <- cor(zscores)
corrplot(corr)


Model Building: Linear Discriminat Analysis with mlr


### Here we are making a object of type makeLearner using the LDA classifier
lrn = makeLearner("classif.lda", predict.type = "prob")
### Now using the train function in mlr we can train our model using the training set
mod = train(lrn, task = movies_mlr.task, subset = train_mlr.set)
### After trining the model we test it on the testing data
pred = predict(mod, task = movies_mlr.task, subset = test_mlr.set)
### Measure performance of prediction
performance(pred, measures = list(mmce, acc))
  mmce    acc 
0.1875 0.8125 

Threshold vs. Performance(s) for 2-class classification Table

The following mlr command generates data on threshold vs. performance(s) for a 2-class classification that can be used for plotting.

df = generateThreshVsPerfData(pred, measures = list(fpr, tpr) )
head(df)
$measures
$measures$fpr
Name: False positive rate
Performance measure: fpr
Properties: classif,req.pred,req.truth
Minimize: TRUE
Best: 0; Worst: 1
Aggregated by: test.mean
Note: Percentage of misclassified observations in the positive class. Also called false alarm rate or fall-out.

$measures$tpr
Name: True positive rate
Performance measure: tpr
Properties: classif,req.pred,req.truth
Minimize: FALSE
Best: 1; Worst: 0
Aggregated by: test.mean
Note: Percentage of correctly classified observations in the positive class. Also called hit rate or recall.


$data

$aggregate
[1] TRUE

Plotting ROCCurves with mlr for 2-class classification

plotROCCurves(df)

Plotting threshold vs. performance with mlr

plotThreshVsPerf(df)


Model Building: Linear Discriminat Analysis with MASS


Lets fit a LDA model for using the training data ( movies after 2006 )

### Uncomment to create a model containing only selected columns
#lda.fit = lda( genres ~ ., data = train_data[ c('genres', selected_columns) ])
lda.fit = lda(genres ~ ., data = train_data)
lda.fit
Call:
lda(genres ~ ., data = train_data)

Prior probabilities of groups:
Adventure Biography    Horror 
0.5066667 0.2666667 0.2266667 

Group means:
             length   budget director_fb_likes actor1_fb_likes actor2_fb_likes actor3_fb_likes total_cast_likes fb_likes
Adventure 110.28947 83521053         346.76316       12283.842       5506.4474       1616.6842        21143.421 37318.18
Biography 117.75000 18781500        1718.00000        7663.750       1830.0000        366.2500        10406.000 25596.15
Horror     97.11765 10923235          63.64706        3626.765        585.1765        387.6471         5455.882  8000.00
          critic_reviews users_reviews users_votes    score     gross
Adventure       269.7632      393.0789   167158.97 6.539474 139676049
Biography       271.5500      253.2000   113415.45 7.175000  37172899
Horror          198.2941      196.7647    41990.29 5.541176  25836690

Coefficients of linear discriminants:
                                 LD1                LD2
length             0.028589606074320 -0.030733759679642
budget            -0.000000026963002 -0.000000004151626
director_fb_likes  0.000050624280169 -0.000057538957066
actor1_fb_likes    0.000197787882912  0.000033187876416
actor2_fb_likes    0.000106542979971 -0.000001814925458
actor3_fb_likes    0.000308201807052  0.000014853943635
total_cast_likes  -0.000184779740657 -0.000028537049727
fb_likes          -0.000003348054640  0.000007656551923
critic_reviews     0.005663886688470  0.004775206739475
users_reviews     -0.001085486238006  0.000536940711429
users_votes       -0.000004606327040 -0.000004104176206
score              0.169874351479755 -0.845638008525884
gross              0.000000004142433  0.000000001638829

Proportion of trace:
   LD1    LD2 
0.7115 0.2885 

The LDA output shows Prior probabilities for each group such as Adventure (\(\hat{\pi_1} = 0.5066667\)), Biography (\(\hat{\pi_2} = 0.2666667\)) and Horror (\(\hat{\pi_3} = 0.2266667\)). From the prior probabilities we can determine that about \(50\%\) of the training data correspond to genre Adventure the other \(50\%\) is split between the other two genres (Biography and Horros).

plot(lda.fit)

lda.pred = predict( lda.fit, test_data )
names(lda.pred)
[1] "class"     "posterior" "x"        
lda.class = lda.pred$class
table(lda.class, y_test)
           y_test
lda.class   Adventure Biography Horror
  Adventure        12         3      0
  Biography         1         3      0
  Horror            0         1      5
mean(lda.class == y_test)
[1] 0.8

Quadratic Discriminat Analysis


To perform a Quadratic Discriminant Analysis we need to resample the data and follow similar steps that when performing a Linear Discriminant Analysis. But for this case we cant plot the QLD model.

### Here we are only selecting genre and other numeric values that we are going to use to create the model
movies <- imdb[ imdb$genres %in% y_classes, ]
movies <- movies[ movies$year > 1996, ]
movies <- movies[ c(2,7:18,20) ]
### Now we extract select that datsa using the index of the rows and take another sample
### But this time is to create the Training and Testing sets
idx_smp <- sample(seq_len(nrow(movies)), size = 500)
movies <- movies[idx_smp,]
smp_size <- floor(0.75 * nrow(movies))
train_ind <- sample(seq_len(nrow(movies)), size = smp_size)
### TRAINING
train_data <- movies[ train_ind, ]
### TESTING
test_data <- movies[ -train_ind, ]
### Labels from the testing set
y_test <- as.factor(test_data['genres']$genres)
test_data['genres'] <- NULL

We are going to fit a QDA model that is larger in size as the training data starts from 1996.

qda.fit = qda(genres ~ ., data = train_data)
qda.fit
Call:
qda(genres ~ ., data = train_data)

Prior probabilities of groups:
Adventure Biography    Horror 
0.4906667 0.2826667 0.2266667 

Group means:
             length   budget director_fb_likes actor1_fb_likes actor2_fb_likes actor3_fb_likes total_cast_likes  fb_likes
Adventure 105.25000 89126685         830.52717        8298.935       2868.2609       1391.7120        13820.837 16321.016
Biography 118.02830 24977927        1567.05660        9294.925       2652.8113       1032.2075        14025.472 11439.962
Horror     98.23529 16383176          77.30588        3472.906        648.8941        409.2824         5318.847  7219.106
          critic_reviews users_reviews users_votes    score    gross
Adventure       205.3370      371.6957   136349.41 6.520652 93081457
Biography       199.8868      250.4057    82349.67 7.074528 32278978
Horror          207.8235      437.2706    71073.22 5.650588 38360442

For the QDA model the output contains the group means and Prior probabilities of the groups just like in LDA, but it does not contain the coefficients of the linear discriminants, because the QDA classifier involves a quadratic, rather than a linear, function of the predictors.

The predict() function works in exactly the same fashion as for LDA.

qda.pred = predict(qda.fit, test_data)
qda.class = qda.pred$class
table(qda.class, y_test)
           y_test
qda.class   Adventure Biography Horror
  Adventure        38         4      1
  Biography         5        19      0
  Horror           20        13     25
mean(qda.class == y_test)
[1] 0.656
LS0tCnRpdGxlOiAiTGluZWFyICYgTm9ubGluZWFyIC0gRGlzY3JpbWluYW50IEFuYWx5c2lzIgphdXRob3I6ICJKb3NlIEx1aXMgUm9kcmlndWV6IgpkYXRlOiAiU2VwdGVtYmVyIDIxLCAyMDE3IgpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazogZGVmYXVsdAogIGh0bWxfZG9jdW1lbnQ6IGRlZmF1bHQKICBwZGZfZG9jdW1lbnQ6IGRlZmF1bHQKICB0b2M6IHllcwpzdWJ0aXRsZTogU1RBVCA0ODggLSBQcmVkaWN0aXZlIEFuYWx5dGljcwotLS0KCi0tLS0tLS0tLS0tLQoKIyMgQWJvdXQgdGhlIE5vdGVib29rCgpUaGlzIFJub3RlYm9vayBjb3ZlcnMgcGFydCBvZiB0aGUgbWF0ZXJpYWwgaW4gY2hhcHRlciA0IG9mICJBbiBJbnRyb2R1Y3Rpb24gdG8gU3RhdGlzdGljYWwgTGVhcm5pbmcgd2l0aCBBcHBsaWNhdGlvbnMgaW4gUiIgYnkgSmFtZXMsIFdpdHRlbiwgSGFzdGllIGFuZCBUaWJzaGlyYW5pIChJU0xSKS4gV2UgYXJlIGdvaW5nIHRvIHdvcmsgb24gc2VjdGlvbnMgNC42LjMgKExpbmVhciBEaXNjcmltaW5hbnQgQW5hbHlzaXMpIGFuZCBzZWN0aW9uIDQuNi40IChRdWFkcmF0aWMgRGlzY3JpbWluYW50IEFuYWx5c2lzKS4gCgpUaGUgZmlyc3QgcGFydCBvZiB0aGUgbm90ZWJvb2sgY292ZXJzIGltcG9ydGFudCBjb25jZXB0cyBiZWhpbmQgRGlzY3JpbWluYW50IEFuYWx5c2lzIGFuZCBpdHMgcmVsYXRpb24gdG8gQmF5ZXMnIFRoZW9yZW0gdGhlbiB3ZSBnbyBvdmVyIGNyZWF0aW5nIGEgbW9kZWwgdXNpbmcgdGhlICptbHIqIHBhY2thZ2UgcGxvdHRpbmcgYW5kIFJPQyBjdXJ2ZSwgb24gdGhpcyBub3RlYm9vayB3ZSBhcmUgYWxzbyBnb2luZyB0byB1c2UgdGhlICpNQVNTKiBwYWNrYWdlIHRvIGZvbGxvdyBhIHNpbWlsYXIgZXhhbXBsZSBvbiB0aGUgYm9vayAoSVNMUikuCgoqKk5vdGVib29rIFNldHVwOioqIFNldCB0aGUgd29ya2luZyBkaXJlY3RvcnkgdG8gdGhlIHNvdXJjZSBsb2NhdGlvbiBieTogKlNlc3Npb24gLT4gU2V0IFdvcmtpbmcgRGlyZWN0b3J5IC0+IFRvIFNvdXJjZSBGaWxlIExvY2F0aW9uKgoKYGBge3J9CiMjIyMgbG9hZCBhbmQgaW5zdGFsbCBwYWNrYWdlcyBpZiBuZWNlc3NhcnkKCiMjIyBodHRwczovL21sci1vcmcuZ2l0aHViLmlvLwppZighcmVxdWlyZSgibWxyIixxdWlldGx5ID0gVFJVRSkpIGluc3RhbGwucGFja2FnZXMoIm1sciIpCgojIyMgaHR0cDovL3d3dy5zdGF0cy5veC5hYy51ay9wdWIvTUFTUzQvCmlmKCFyZXF1aXJlKCJNQVNTIixxdWlldGx5ID0gVFJVRSkpIGluc3RhbGwucGFja2FnZXMoIk1BU1MiKQoKIyMjIGh0dHA6Ly9nZ3Bsb3QyLm9yZy8KaWYoIXJlcXVpcmUoImdncGxvdDIiLHF1aWV0bHkgPSBUUlVFKSkgaW5zdGFsbC5wYWNrYWdlcygiZ2dwbG90MiIpCgojIyMgaHR0cHM6Ly9naXRodWIuY29tL3RhaXl1bi9jb3JycGxvdAppZighcmVxdWlyZSgiY29ycnBsb3QiLHF1aWV0bHkgPSBUUlVFKSkgaW5zdGFsbC5wYWNrYWdlcygiY29ycnBsb3QiKQoKIyMjIyBzZXQgc2VlZCBhbmQgdHVybiBvZmYgc2NpZW50aWZpYyBub3RhdGlvbiB0byBzZWUgc21hbGwvbGFyZ2UgbnVtYmVycwpzZXQuc2VlZCgxMjM0KQpvcHRpb25zKHNjaXBlbiA9IDk5OTkpCmBgYAoKLS0tLS0tLS0tLS0KCiMjIEJhY2tncm91bmQKCi0tLS0tLS0tLS0tLQoKCioqTG9naXN0aWMgUmVncmVzc2lvbiAoUmV2aWV3KSoqIAoKKiBNb2RlbHMgdGhlIGNvbmRpdGlvbmFsIGRpc3RyaWJ1dGlvbiBvZiB0aGUgcmVzcG9uc2UgWSwgJFByKFkgPSBrfFggPSB4KSQgZ2l2ZW4gdGhlIHByZWRpY3RvcihzKSBYLCB1c2luZyB0aGUgbG9naXN0aWMgZnVuY3Rpb24gZm9yIHRoZSBjYXNlIG9mIHR3byByZXNwb25zZSBjbGFzc2VzOgoKJCQgcChYKSA9IFxmcmFje2Vee1xiZXRhXzArXGJldGFfMVhfMStcZG90cytcYmV0YV9wWF9wfX17MSArIGVee1xiZXRhXzArXGJldGFfMVhfMSvCt8K3wrcrXGJldGFfcFhfcH19IC4kJAoKCioqTGluZWFyIGRpc2NyaW1pbmFudCBtb2RlbCAoTERBKSAqKiAKCiogQ2FuIGJlIGNvbnNpZGVyIGEgbGVzcyBkaXJlY3QgYXBwcm9hY2ggdG8gY2xhc3NpZmljYXRpb24gY29tcGFyZWQgdG8gTG9naXN0aWMgcmVncmVzc2lvbi4KKiBNb2RlbCdzIHRoZSBkaXN0cmlidXRpb24gb2YgdGhlIHByZWRpY3RvcnMgJFgkIHNlcGFyYXRlbHkgaW4gZWFjaCBvZiB0aGUgcmVzcG9uc2UgY2xhc3NlcyAoaS5lLiBnaXZlbiAkWSQgKSwgYW5kIHRoZW4gdXNlIEJheWVzJyB0aGVvcmVtIHRvIGZsaXAgdGhlc2UgYXJvdW5kIGludG8gZXN0aW1hdGVzIGZvciAkUHIoWT1rfFg9eCkkLgoqIE1vcmVvdmVyIExpbmVhciBkaXNjcmltaW5hbnQgYW5hbHlzaXMgKExEQSkgaXMgYSBnZW5lcmFsaXphdGlvbiBvZiBGaXNoZXIncyBsaW5lYXIgZGlzY3JpbWluYW50LCBhIG1ldGhvZCB1c2VkIGluIHN0YXRpc3RpY3MsIHBhdHRlcm4gcmVjb2duaXRpb24gYW5kIG1hY2hpbmUgbGVhcm5pbmcgdG8gZmluZCBhIGxpbmVhciBjb21iaW5hdGlvbiBvZiBmZWF0dXJlcyB0aGF0IGNoYXJhY3Rlcml6ZXMgb3Igc2VwYXJhdGVzIHR3byBvciBtb3JlIGNsYXNzZXMgb2Ygb2JqZWN0cyBvciBldmVudHMuIAoqIFRoZSByZXN1bHRpbmcgY29tYmluYXRpb24gbWF5IGJlIHVzZWQgYXMgYSBsaW5lYXIgY2xhc3NpZmllciwgb3IsIG1vcmUgY29tbW9ubHksIGZvciBkaW1lbnNpb25hbGl0eSByZWR1Y3Rpb24gYmVmb3JlIGxhdGVyIGNsYXNzaWZpY2F0aW9uLgoKKipXaGVuIHRvIHVzZSBMaW5lYXIgRGlzY3JpbWluYW50IEFuYWx5c2lzKioKCiogV2hlbiB0aGUgY2xhc3NlcyBhcmUgd2VsbC1zZXBhcmF0ZWQsIHRoZSBwYXJhbWV0ZXIgZXN0aW1hdGVzIGZvciB0aGUgbG9naXN0aWMgcmVncmVzc2lvbiBtb2RlbCBhcmUgc3VycHJpc2luZ2x5IHVuc3RhYmxlLiBMaW5lYXIgZGlzY3JpbWluYW50IGFuYWx5c2lzIGRvZXMgbm90IHN1ZmZlciBmcm9tIHRoaXMgcHJvYmxlbS4KCiogSWYgJG4kIGlzIHNtYWxsIGFuZCB0aGUgZGlzdHJpYnV0aW9uIG9mIHRoZSBwcmVkaWN0b3JzICRYJCBpcyBhcHByb3hpbWF0ZWx5IG5vcm1hbCBpbiBlYWNoIG9mIHRoZSBjbGFzc2VzLCB0aGUgbGluZWFyIGRpc2NyaW1pbmFudCBtb2RlbCBpcyBhZ2FpbiBtb3JlIHN0YWJsZSB0aGFuIHRoZSBsb2dpc3RpYyByZWdyZXNzaW9uIG1vZGVsLgoKKiBMaW5lYXIgZGlzY3JpbWluYW50IGFuYWx5c2lzIGlzIHBvcHVsYXIgd2hlbiB0aGVyZSBhcmUgbW9yZSB0aGFuIHR3byByZXNwb25zZSBjbGFzc2VzLgoKPCEtLSBJU0xSIDEzOCAtLT4KCi0tLS0tLS0tLS0tLQoKIyMgQ2xhc3NpZmljYXRpb24gdXNpbmcgQmF5ZXMnIFRoZW9yZW0gCgotLS0tLS0tLS0tLS0KCioqQmF5ZXMnIHRoZW9yZW0qKgoKJCQKUHIoWT1rfFg9eCkgPSBcZnJhY3tccGlfa2Zfayh4KX17XHN1bV5LX3tsPTF9XHBpX2xmX2woeCl9LiBccXF1YWQgXHFxdWFkICg0LjEwKQokJAoKKipQb3N0ZXJpb3IgUHJvYmFiaWxpdHkqKgoKV2UgcmVmZXIgdG8gJHBrKHgpJCBhcyB0aGUgcG9zdGVyaW9yIHByb2JhYmlsaXR5IHRoYXQgYW4gb2JzZXJ2YXRpb24gJFggPSB4JCBiZWxvbmdzIHRvIHRoZSAka3RoJCBjbGFzcy4gVGhhdCBpcywgaXQgaXMgdGhlIHByb2JhYmlsaXR5IHRoYXQgdGhlIG9ic2VydmF0aW9uIGJlbG9uZ3MgdG8gdGhlICRrdGgkIGNsYXNzLCBnaXZlbiB0aGUgcHJlZGljdG9yIHZhbHVlIGZvciB0aGF0IG9ic2VydmF0aW9uLgoKU3VwcG9zZSB3ZSBhc3N1bWUgdGhhdCAkZl9rKHgpJCBpcyBub3JtYWwgb3IgR2F1c3NpYW4uIEluIHRoZSBvbmUgbm9ybWFsIGRpbWVuc2lvbmFsIHNldHRpbmcsIHRoZSBub3JtYWwgZGVuc2l0eSB0YWtlcyB0aGUgZm9ybToKCiQkCmZfayh4KSA9IFxmcmFjezF9e1xzcXJ0ezJccGlcc2lnbWFfa319ZXhwXEJpZ2coIC1cZnJhY3sxfXsyXHNpZ21hXjJfa30oeC1cbXVfayleMlxCaWdnKS4gXHFxdWFkIFxxcXVhZCAoNC4xMSkKJCQKCiogV2hlcmUgJFxtdV9rJCBhbmQgJFxzaWdtYV4yX2skIGFyZSB0aGUgbWVhbiBhbmQgdmFyaWFuY2UgcGFyYW1ldGVycyBmb3IgdGhlICRrdGgkIGNsYXNzLiAKCiogQXNzdW1lIHRoYXQgJFxzaWdtYV4yXzEgPSBcZG90cyA9IFxzaWdtYV4yX0skLCBzdWNoIGFzIHRoZXJlIGlzIGEgc2hhcmVkIHZhcmlhbmNlIHRlcm0gYWNyb3NzIGFsbCAkSyQgY2xhc3NlcywgZGVub3RlIGJ5ICRcc2lnbWFeMiQuCgpCeSBwbHVnZ2luZyAoNC4xMSkgaW50byAoNC4xMCksIHdlIGdldDogCgokJApwX2soeCkgPSBcZnJhY3tccGlfa1xmcmFjezF9e1xzcXJ0ezJccGl9XHNpZ21hfVx0ZXh0e2V4cH1cYmlnKC1cZnJhY3sxfXsyXHNpZ21hXjJ9KHgtXG11X2spXjIgXGJpZyl9e1xzdW1eS197bD0xfSBccGlfbFxmcmFjezF9e1xzcXJ0ezJccGl9XHNpZ21hfVx0ZXh0e2V4cH1cYmlnKC1cZnJhY3sxfXsyXHNpZ21hXjJ9KHgtIFxtdV9sKV4yIFxiaWcpfS4gXHFxdWFkIFxxcXVhZCBccXF1YWQgKDQuMTIpCiQkCgoqIEluIGVxdWF0aW9uICg0LjEyKSAkXHBpX2skIGRlbm90ZXMgdGhlICoqcHJpb3IgcHJvYmFiaWxpdHkqKiB0aGF0IGFuIG9ic2VydmF0aW9uIGJlbG9uZ3MgdG8gdGhlICprdGgqIGNsYXNzIChub3QgJFxwaSBcYXBwcm94IDMuMTQxNTkkLCB0aGUgbWF0aGVtYXRpY2FsIGNvbnN0YW50KS4gCgotLS0tLS0tLS0tLS0KCiMjIEJheWVzIGNsYXNzaWZpZXIgYW5kIERlY2lzaW9uIEJvdW5kYXJ5CgotLS0tLS0tLS0tLS0KCkJheWVzIGNsYXNzaWZpZXIsIHdoaWNoIGNsYXNzaWZpZXMgYW4gb2JzZXJ2YXRpb24gKCRYID0geCQpIHRvIHRoZSBjbGFzcyBmb3Igd2hpY2ggJHBfayhYKSQgKDQuMTIpIGlzIGxhcmdlc3QuIEJ5IHRha2luZyB0aGUgbG9nIG9mICg0LjEyKSBhbmQgcmVhcnJhbmdpbmcgdGhlIHRlcm1zIHdlIGdldDoKCiQkClxkZWx0YV9rKHgpID0geCBcY2RvdCBcZnJhY3tcbXVfa317XHNpZ21hXjJ9IC0gXGZyYWN7XG11XjJfa317MlxzaWdtYV4yfSArIGxvZyhccGlfaykgXHFxdWFkIFxxcXVhZCBccXF1YWQgKDQuMTMpCiQkCklmICRLPTIkIGFuZCAkXHBpXzEgPSBccGlfMiQsIHRoZW4gdGhlIEJheWVzIGNsYXNzaWZpZXIgYXNzaWduczoKCiQkClx0ZXh0e0JheWVzQ2xhc3NpZmllcn0gPSBcQmlnZ1x7ClxiZWdpbnthbGlnbn0gCiAgMSwgJiBcdGV4dHsgaWYgfSAgMngoXG11MS1cbXUyKT5cbXVeMl8xLVxtdV4yXzIgXFwgCiAgMiwgJiBcdGV4dHsgb3RoZXJ3aXNlIH0gClxlbmR7YWxpZ259CiQkCgpUaGUgKipCYXllcyBkZWNpc2lvbiBib3VuZGFyeSoqIGNvcnJlc3BvbmRzIHRvIHRoZSBwb2ludCB3aGVyZToKCiQkCnggPSBcZnJhY3tcbXVeMl8xIC0gXG11XjJfMn17MihcbXVfMSAtIFxtdV8yKX09XGZyYWN7XG11MSArIFxtdTJ9ezJ9LiBccXF1YWQgXHFxdWFkIFxxcXVhZCAoNC4xNCkKJCQKCioqUGxvdCBvZiB0d28gb25lLWRpbWVuc2lvbmFsIG5vcm1hbCBkZW5zaXR5IGZ1bmN0aW9ucyoqCgpUaGUgZGFzaGVkIHZlcnRpY2FsIGxpbmUgcmVwcmVzZW50cyB0aGUgQmF5ZXMgZGVjaXNpb24gYm91bmRhcnkgY2FsY3VsYXRlZCB1c2luZyB0aGUgZm9ybXVsYSBhYm92ZS4KCmBgYHtyfQptZWFuX2EgPC0gMS41Cm1lYW5fYiA8LSAtMS41CmJheWVzX2RlY2lzaW9uX2JvdW5kYXJ5IDwtIChtZWFuX2EgKyBtZWFuX2IpLzIKCnAgPC0gZ2dwbG90KCBkYXRhLmZyYW1lKHg9YygtNCw0KSksIGFlcyh4KSkgKwogIHN0YXRfZnVuY3Rpb24oIGZ1biA9IGRub3JtLCBhcmdzID0gbGlzdChtZWFuID0gbWVhbl9hLCBzZCA9IDEpLCBjb2w9J3JlZCcgKSArCiAgc3RhdF9mdW5jdGlvbiggZnVuID0gZG5vcm0sIGFyZ3MgPSBsaXN0KG1lYW4gPSBtZWFuX2IsIHNkID0gMSksIGNvbD0nYmx1ZScgKQoKcCArIGdlb21fdmxpbmUoeGludGVyY2VwdCA9IGJheWVzX2RlY2lzaW9uX2JvdW5kYXJ5LCBsaW5ldHlwZT0iZGFzaGVkIikgCgpgYGAKCjwhLS0gSVNMUiAxMzggLS0+CgotLS0tLS0tLS0tLS0KCiMjIExpbmVhciBEaXNjcmltaW5hbnQgQW5hbHlzaXMKCi0tLS0tLS0tLS0tLQoKRmlyc3QgbGV0cyBjcmVhdGUgYSBkYXRhIHNldCBieSBvZiBzaXplICRuPTUwMCQgYnkgcmFuZG9tbHkgc2FtcGxpbmcgZnJvbSBhIG5vcm1hbCBkaXN0cmlidXRpb24uCgpgYGB7cn0KeCA8LSBzZXEoLTQsIDQsIGxlbmd0aCA9IDUwMCkKZGYgPC0gZGF0YS5mcmFtZShjb25kID0gcmVwKGxldHRlcnNbMToyXSwgZWFjaCA9IDUwMCksCiAgICAgICAgICAgICAgICAgdmFsdWVzID0gYyggcm5vcm0oeCxtZWFuID0gbWVhbl9hLCBzZCA9IDEpLCBybm9ybSh4LG1lYW4gPSBtZWFuX2IsIHNkID0gMSkpKQpoZWFkKGRmLCBuPTUpCmBgYAoKKiBGcm9tIHRoZSBnZW5lcmF0ZWQgZGF0YSBzZXQgbGV0cyB0YWtlIGEgc2FtcGxlIG9mIHNpemUgJDEwMCQgZnJvbSBlYWNoIGNhdGVnb3J5ICoqY29uZCA9IGEgfCBiKiogIGFsc28gZnJvbSB0aGUgc2FtcGxlcyB3ZSBuZWVkIHRvIGNhbGN1bGF0ZSB0aGUgJFxtdSQsJFxzaWdtYSQgYW5kIHByb2JhYmlsaXRpZXMuCgpUaGUgbGluZWFyIGRpc2NyaW1pbmFudCBhbmFseXNpcyAoTERBKSBtZXRob2QgYXBwcm94aW1hdGVzIHRoZSBCYXllcyBjbGFzc2lmaWVyIGJ5IHBsdWdnaW5nIGVzdGltYXRlcyBmb3IgJFxwaV9rJCwgJFxtdV9rJCwgYW5kICRcc2lnbWFeMiQgaW50byAoNC4xMykKCiQkClxoYXR7XG11X2t9ID0gXGZyYWN7MX17bl9rfVxzdW1fe2k6eV9pPWt9eF9pIFxxcXVhZCBccXF1YWQgXHFxdWFkIFxxcXVhZCBccXF1YWQgXHFxdWFkIFxxcXVhZCAgXFwgClxoYXR7XHNpZ21hfV4yID0gXGZyYWN7MX17bi1LfVxzdW1eS197az0xfVxzdW1fe2k6eV9pPWt9KHhfaS1caGF0e1xtdX1fayleMiBccXF1YWQgXHFxdWFkIFxxdWFkICg0LjE1KQokJAoKKiBXaGVyZSAkbiQgaXMgdGhlIHRvdGFsIG51bWJlciBvZiB0cmFpbmluZyBvYnNlcnZhdGlvbnMsIGFuZCAkbl9rJCBpcyB0aGUgbnVtYmVyIG9mIHRyYWluaW5nIG9ic2VydmF0aW9ucyBpbiB0aGUgJGt0aCQgY2xhc3MuCipUaGUgZXN0aW1hdGUgZm9yICRcbXVfayQgaXMgc2ltcGx5IHRoZSBhdmVyYWdlIG9mIGFsbCB0aGUgdHJhaW5pbmcgb2JzZXJ2YXRpb25zIGZyb20gdGhlICRrdGgkIGNsYXNzCiogJFxoYXR7XHNpZ21hXjJ9JCBpcyBnaXZlbiBieSB0aGUgd2VpZ2h0ZWQgYXZlcmFnZSBvZiB0aGUgc2FtcGxlIHZhcmlhbmNlcyBmb3IgZWFjaCBvZiB0aGUgJEskIGNsYXNzZXMuCiogJFxwaV9rJCBpcyBlc3RpbWF0aW5nIGJ5IHVzaW5nIHRoZSBwcm9wb3J0aW9uIG9mIHRoZSB0cmFpbmluZyBvYnNlcnZhdGlvbnMgdGhhdCBiZWxvbmcgdG8gdGhlICRrdGgkIGNsYXNzIGdpdmVuIGJ5OiAkXGhhdHtccGlfa30gPSBuX2svbiQuCgokJApcaGF0e1xkZWx0YV9rKHgpfSA9IHggXGNkb3QgXGZyYWN7XGhhdHtcbXVfa319e1xoYXR7XHNpZ21hfV4yfSAtIFxmcmFje1xoYXR7XG11fV4yX2t9ezJcaGF0e1xzaWdtYX1eMn0gKyBsb2coXGhhdHtccGl9X2spIFxxcXVhZCBccXF1YWQgXHFxdWFkICg0LjE3KQokJAoKYGBge3J9CnNhbXBsZV9hIDwtIGRhdGEuZnJhbWUoInZhbHVlcyI9IHNhbXBsZShkZiR2YWx1ZXNbZGYkY29uZD09ImEiXSwgMTAwKSwgImNvbmQiID0gImEiKQpzYW1wbGVfYiA8LSBkYXRhLmZyYW1lKCJ2YWx1ZXMiPSBzYW1wbGUoZGYkdmFsdWVzW2RmJGNvbmQ9PSJiIl0sIDEwMCksICJjb25kIiA9ICJiIikKCm1lYW5fc2FtcGxlX2EgPC0gbWVhbiggc2FtcGxlX2EkdmFsdWVzICkKbWVhbl9zYW1wbGVfYiA8LSBtZWFuKCBzYW1wbGVfYiR2YWx1ZXMgKQoKaGlzdF9hIDwtIGhpc3QoIHNhbXBsZV9hJHZhbHVlcywgcGxvdCA9IEZBTFNFICkKaGlzdF9iIDwtIGhpc3QoIHNhbXBsZV9iJHZhbHVlcywgcGxvdCA9IEZBTFNFICkKCnNpZ21hX2EgPC0gd2VpZ2h0ZWQubWVhbiggaGlzdF9hJGRlbnNpdHksIGhpc3RfYSRjb3VudHMgKQpzaWdtYV9iIDwtIHdlaWdodGVkLm1lYW4oIGhpc3RfYiRkZW5zaXR5LCBoaXN0X2IkY291bnRzICkKcHJvYl9hIDwtIDMwLzEwMApwcm9iX2IgPC0gMzAvMTAwCmBgYAoKCk5vdyB0byB3ZXJlIGRyYXduIGZyb20gZWFjaCBvZiB0aGUgdHdvIGNsYXNzZXMsIGFuZCBhcmUgc2hvd24gYXMgaGlzdG9ncmFtcy4gVGhlIEJheWVzIGRlY2lzaW9uIGJvdW5kYXJ5IGlzIGFnYWluIHNob3duIGFzIGEgZGFzaGVkIHZlcnRpY2FsIGxpbmUuIFRoZSBzb2xpZCB2ZXJ0aWNhbCBsaW5lIHJlcHJlc2VudHMgdGhlIExEQSBkZWNpc2lvbiBib3VuZGFyeSBlc3RpbWF0ZWQgZnJvbSB0aGUgdHJhaW5pbmcgZGF0YS4KCgpgYGB7cn0KTERBX2RlY2lzaW9uX2JvdW5kYXJ5IDwtIChtZWFuX3NhbXBsZV9hICsgbWVhbl9zYW1wbGVfYikvMgoKZ2dwbG90KGRmLCBhZXMoeCA9IHZhbHVlcywgZmlsbCA9IGNvbmQpKSArCiAgZ2VvbV9oaXN0b2dyYW0oIGJpbndpZHRoID0gMC40LCBhbHBoYSA9IDAuNSwgcG9zaXRpb24gPSAiaWRlbnRpdHkiICkgKwogIGdlb21fdmxpbmUoIHhpbnRlcmNlcHQgPSBMREFfZGVjaXNpb25fYm91bmRhcnksIGxpbmV0eXBlID0gInNvbGlkIiApICsKICBnZW9tX3ZsaW5lKCB4aW50ZXJjZXB0ID0gYmF5ZXNfZGVjaXNpb25fYm91bmRhcnksIGxpbmV0eXBlID0gImRhc2hlZCIgKQpgYGAKCi0tLS0tLS0tLS0tLQoKIyMgVXNlIGNhc2U6IElNRGIgTW92aWVzLCBUViBhbmQgQ2VsZWJyaXRpZXMgLS0gUmF0aW5ncyBEYXRhc2V0CgotLS0tLS0tLS0tLS0KCkZvciB0aGlzIHVzZSBjYXNlIHdlIGFyZSBnb2luZyB0byBleHBsb3JlIHRoZSBJTURiIG1vdmllIHJhdGluZ3MgZGF0YSBzZXQgdGhlbiB3ZSBhcmUgZ29pbmcgdG8gdXNlIHRoZSBMaW5lYXIgRGlzY3JpbWluYW50IEFuYWx5c2lzIGFuZCBRdWFkcmF0aWMgRGlzY3JpbWluYW50IEFuYWx5c2lzIHRvIGNsYXNzaWZ5IG1vdmllcyBieSBnZW5kZXIgZ2l2ZW4gYSBudW1iZXIgb2YgZmVhdHVyZXMgcHJlc2VudCBpbiB0aGUgZGF0YS4KCgpGaXJzdCBsZXRzIGxvYWQgdGhlIGRhdGEsIGV4cGxvcmUgYSBjb3VwbGUgb2Ygcm93cywgZGF0YSB0eXBlcyBtYWtlIHNvbWUgY2hhbmdlcyBhbmQgZGVhbCB3aXRoICBOQSdzCmBgYHtyfQppbWRiIDwtIHJlYWQuY3N2KCJkYXRhL2ltZGJfcmF0aW5nLmNzdiIpCmhlYWQoaW1kYikKYGBgCgpMZXRzIHRha2UgYSBjbG9zZXIgbG9vayB0byB0aGUgbnVtZXJpYyBkYXRhIGJ5IHJ1bm5pbmcgdGhlIGNvbW1hbmQgYGBgc3VtbWFyeSgpYGBgIGFuZCBgYGBzdHIoKWBgIHRvIHRyeSB0byBmaW5kIGFueSBpcnJlZ3VsYXJpdGllcyBpbiB0aGUgZGF0YSBkZXRlcm1pbmUgdGhlIGRhdGEgdHlwZXMuIApgYGB7cn0Kc3VtbWFyeShpbWRiWzc6MjBdKQpgYGAKCkNoZWNrIHRoZSBzdHJ1Y3R1cmUgb2YgdGhlIGRhdGEgc2V0IGFuZCBkYXRhIHR5cGVzCmBgYHtyfQpzdHIoaW1kYikKYGBgCgpGcm9tIHRoZSB0YWJsZSBhYm92ZSB3ZSBjYW4gc2VlIHRoYXQgdGhlcmUgaXMgc29tZSB3b3JrIHRvIGRvIGluIG9yZGVyIHRvIHN0YXJ0IHdvcmtpbmcgd2l0aCB0aGUgaW1kYiBkYXRhLiBGaXJzdCB3ZSBuZWVkIHRvIGNoYW5nZSB0aGUgKmdlbnJlcyogY29sdW1uIHRvIGNoYXJhY3RlciB0eXBlIGFuZCBleHRyYWN0IHRoZSBmaXJzdCBlbnRyeSBpbiBvcmRlciB0byBjcmVhdGUgb3VyIGxhYmVscy4gQWxzbyBvbiB0aGlzIHN0ZXAgd2Ugd2FudCB0byBmaW5kIGEgd2F5IHRvIHJlbW92ZSBvciByZXBsYWNlIG1pc3NpbmcgdmFsdWVzIGZvciB0aGlzIGNhc2Ugd2UgYXJlIGdvaW5nIHRvIHJlbW92ZSB0aGVtLgoKYGBge3J9CiMjIyBjaGFuZ2UgZGF0YSB0eXBlIGFuZCByZW1vdmUgTkEncyBmcm9tIHRoZSBkYXRhCmltZGIkZ2VucmVzIDwtIGFzLmNoYXJhY3RlcihpbWRiJGdlbnJlcykKaW1kYiA8LSBuYS5vbWl0KGltZGIpCgojIyMgTm93IHdlIHdhbnQgdG8gZXh0cmFjdCB0aGUgZmlyc3QgbW92aWUgZ2VucmUgZnJvbSB0aGUgY29sdW1uIGFuZCBmaW5kIHRoZSBmcmVxdWVuY3kgb2YgdGhlIGdlbnJlIGluIHRoZSBkYXRhIHNldApnZW5yZSA8LSBzYXBwbHkoIGltZGIkZ2VucmVzLCBzdHJzcGxpdCwiW3xdIiwgVVNFLk5BTUVTID0gRkFMU0UgKQpnZXRfb25lX2dlbnJlIDwtIGZ1bmN0aW9uKGVudHJ5KSBmb3IoaSBpbiBlbnRyeSkgcmV0dXJuKGlbWzFdXSkKaW1kYiRnZW5yZXMgPC0gdW5saXN0KGxhcHBseShnZW5yZSwgZ2V0X29uZV9nZW5yZSkpCnRhYmxlX2dlbnJlIDwtIHRhYmxlKGltZGIkZ2VucmVzKQp0YWJsZV9nZW5yZQpgYGAKClRoZSBmcmVxdWVuY3kgdGFibGUgYWJvdmUgd2UgY2FuIHNlZSB0aGF0IHNvbWUgb2YgdGhlIGdlbnJlcyBkb24ndCBoYXZlIGVub3VnaCBvYnNlcnZhdGlvbnMgYW5kIG90aGVycyBhcmUgb3ZlciByZXByZXNlbnRlZCBzbyBsZXRzIHRha2UgdGhlIG9ic2VydmF0aW9ucyBncmVhdGVyIHRoYW4gNTAgYW5kIGxlc3MgdGhhbiA0MDAgYXMgdGhpcyByYW5nZSB3b3VsZCBnaXZlIHVzIGEgc2V0IG9mIGNsYXNzZXMgd2hpY2ggYXJlIHByb3BvcnRpb25hbCB0byBlYWNoIG90aGVyLiBBbHNvIHdlIGhhdmUgdG8gY2hlY2sgZm9yIGNsYXNzZXMgdGhhdCBtaWdodCByZXByZXNlbnQgc2ltaWxhci9yZWxhdGVkIGdlbnJlcyBzdWNoIGFzICpDcmltZSogYW5kICpIb3Jyb3IqCgpgYGB7cn0KIyMjIEhlcmUgd2UgYXJlIHNlbGVjdGluZyBvYnNlcnZhdGlvbnMgZ3JlYXRoZXIgdGhhbiA1MCBhbmQgbGVzcyB0aGFuIDQwMCBhbHNlCiMjIyBjcmVhdGluZyBhIHZlY3RvciBjb250YWluaW5nIHRoZSBuYW1lIG9mIHRoZSBjbGFzc2VzIHRoYXQgYXJlIGludGVyZXN0ZWQKCnRhYmxlX2dlbnJlXzQwMCA8LSB0YWJsZV9nZW5yZVsgdGFibGVfZ2VucmUgPiA1MCAmIHRhYmxlX2dlbnJlIDwgNDAwIF0KeV9jbGFzc2VzIDwtIG5hbWVzKHRhYmxlX2dlbnJlXzQwMFsgYygxLDIsNCkgXSkKeV9jbGFzc2VzCmBgYAoKTm93IHdlIGFyZSBnb2luZyB0byB1c2UgdGhvc2UgY2FzZXMgdG8gc2VsZWN0IG1vdmllcyBpbiB0aGUgZ2VucmVzIHRoYXQgd2UgYXJlIGludGVyZXN0ZWQgV2UgYWxzbyB3YW50IHRvIGNvbnNpZGVyIG5ld2VyIG1vdmllcyBhcyBtb3N0IG1ldHJpY3MgdXNlIEZCIGxpa2VzLCBjcml0aWNzIHJldmlld3MgYW5kIG90aGVyIGRpZ2l0YWwgZm9ybXMgb2YgZW5nYWdlbWVudCB3aXRoIG1vdmllIHRoYXQgaWYgd2UgdXNlIG9sZGVyIGRhdGEgaXQgbWlnaHQgbm90IGJlIHRoYXQgYWNjdXJhdGUKCmBgYHtyfQojIyMgVG8gY3JlYXRlIFJPQyBwbG90IGZvciB0d28gY2xhc3NlcyAKI3lfY2xhc3NlcyA8LSB5X2NsYXNzZXNbYygxLDMpXSAKCm1vdmllcyA8LSBpbWRiWyBpbWRiJGdlbnJlcyAlaW4lIHlfY2xhc3NlcywgXQptb3ZpZXMgPC0gbW92aWVzWyBtb3ZpZXMkeWVhciA+IDIwMDYsIF0KCiMjIyBIZXJlIHdlIGFyZSBvbmx5IHNlbGVjdGluZyBnZW5yZSBhbmQgb3RoZXIgbnVtZXJpYyB2YWx1ZXMgdGhhdCB3ZSBhcmUgZ29pbmcgdG8gdXNlIHRvIGNyZWF0ZSB0aGUgbW9kZWwKCmhlYWQobW92aWVzKQptb3ZpZXMgPC0gbW92aWVzWyBjKDIsNzoxOCwyMCkgXQpgYGAKCi0tLS0tLS0tLS0tLQoKIyMgVHJhaW5pbmcsIFRlc3RpbmcgRGF0YSBTcGxpdCB3aXRoIG1sciBwYWNrYWdlIAoKLS0tLS0tLS0tLS0tCgpgYGB7cn0KIyMjIEZpcnN0IGxldHMgc2VsZXNjdCB0d28gY2xhc3NlcyBzbyB3ZSBjYW4gbGF0ZXIgY3JlYXRlIGFuIFJPQyBwbG90CnlfY2xhc3Nlc19tbHIgPC0geV9jbGFzc2VzWyBjKDEsMykgXSAKCiMjIyBTZWxlY3Qgb25seSBtb3ZpZXMgbWFkIGVpbiB0aGUgbGFzdCAxMCB5ZWFycyB3aXRoaW4gdGhlIHNlbGVjdGVkIGNsYXNzZXMgIAptb3ZpZXNfbWxyIDwtIGltZGJbIGltZGIkZ2VucmVzICVpbiUgeV9jbGFzc2VzX21scixdCm1vdmllc19tbHIgPC0gbW92aWVzX21sclsgbW92aWVzX21sciR5ZWFyID4gMjAwNiwgXQoKIyMjIERpc3JlZ2FyZCBub24tbnVtZXJpYyB2YXJpYWJsZXMgYW5kIHNvbWUgY29ycmVsYXRlZCB2YXJpYWJsZXMKbW92aWVzX21sciA8LSBtb3ZpZXNfbWxyW2MoMiw3OjE4LDIwKV0KCiMjIyBDcmVhdGUgYSBtbHIgdGFzayBvYmplY3QgdXNpbmcgdGhlIG1vdmllcyBkYXRhc2V0IGFuZCBnZW5kZXIgYXMgYSB0YXJnZXQKbW92aWVzX21sci50YXNrIDwtIG1ha2VDbGFzc2lmVGFzayhkYXRhID0gbW92aWVzX21sciwgdGFyZ2V0ID0gImdlbnJlcyIpCgojIyMgTm93IHdlIGNhbiBjcmVhdGUgdGhlIFRyYWluaW5nICh+NjUlKSBhbmQgVGVzdGluZyAofjM1JSkgc2V0cyBiYXNlZCBvbiB0aGUgbnVtYmVyIG9mIHNhbXBsZXMgCm4gPSBnZXRUYXNrU2l6ZShtb3ZpZXNfbWxyLnRhc2spCnRyYWluX21sci5zZXQgPSBzYW1wbGUoIG4sIHNpemUgPSByb3VuZCgyLzMgKiBuKSApCnRlc3RfbWxyLnNldCA9IHNldGRpZmYoIHNlcV9sZW4obiksIHRyYWluX21sci5zZXQgKQpgYGAKCi0tLS0tLS0tLS0tLQoKIyMgVHJhaW5pbmcsIFRlc3RpbmcgRGF0YSBTcGxpdAoKLS0tLS0tLS0tLS0tCgpXaGVuIGJ1aWxkaW5nIHRoZSBtb2RlbCBpdCBpcyBpbXBvcnRhbnQgdG8gc3BsaXQgb3VyIGRhdGEgaW4gdHJhaW4gYW5kIHRlc3Qgc2V0cyB1c2luZyBhYm91dCAkNzVcJSQgb2YgdGhlIGRhdGEgZm9yIHRyYWluaW5nIGFuZCB0aGUgcmVtYWluaW5nIGZvciB0ZXN0aW5nLiBJbiB0aGlzIGNhc2Ugd2UgYXJlIHRha2luZyBhIHNhbXBsZSBvZiAkMzAwJCBvYnNlcnZhdGlvbnMgZnJvbSB0aGUgbGFyZ2VyIG1vdmllcyBkYXRhIHNldAoKYGBge3J9CiMjIyBIZXJlIHdlIGFyZSBjcmVhdGluZyBhIHJhbmRvbSBzYW1wbGUgb2Ygc2l6ZSBuPTEwMCBmcm9tIHRoZSBkYXRhc2V0CiMjIyB0aGUgc2FtcGxlIGZ1bmN0aW9uIHJldHVybiB0aGUgaW5kZXggb2YgdGhlIHNlbGVjdGVkIHJvd3MKaWR4X3NtcCA8LSBzYW1wbGUoc2VxX2xlbihucm93KG1vdmllcykpLCBzaXplID0gMTAwKQoKIyMjIE5vdyB3ZSBleHRyYWN0IHNlbGVjdCB0aGF0IGRhdHNhIHVzaW5nIHRoZSBpbmRleCBvZiB0aGUgcm93cyBhbmQgdGFrZSBhbm90aGVyIHNhbXBsZQojIyMgQnV0IHRoaXMgdGltZSBpcyB0byBjcmVhdGUgdGhlIFRyYWluaW5nIGFuZCBUZXN0aW5nIHNldHMKbW92aWVzIDwtIG1vdmllc1tpZHhfc21wLF0Kc21wX3NpemUgPC0gZmxvb3IoMC43NSAqIG5yb3cobW92aWVzKSkKdHJhaW5faW5kIDwtIHNhbXBsZShzZXFfbGVuKG5yb3cobW92aWVzKSksIHNpemUgPSBzbXBfc2l6ZSkKCiMjIyBUUkFJTklORwp0cmFpbl9kYXRhIDwtIG1vdmllc1sgdHJhaW5faW5kLCBdCgojIyMgVEVTVElORwp0ZXN0X2RhdGEgPC0gbW92aWVzWyAtdHJhaW5faW5kLCBdCgojIyMgTGFiZWxzIGZyb20gdGhlIHRlc3Rpbmcgc2V0CnlfdGVzdCA8LSBhcy5mYWN0b3IodGVzdF9kYXRhWydnZW5yZXMnXSRnZW5yZXMpCnRlc3RfZGF0YVsnZ2VucmVzJ10gPC0gTlVMTAoKaGVhZCh0cmFpbl9kYXRhKQpgYGAKCi0tLS0tLS0tLS0tLQoKIyMgVmlzdWFsIEFuYWx5c2lzCgotLS0tLS0tLS0tLS0KCkxldHMgbWFrZSBzb21lIHNjYXR0ZXIgcGxvdHMgdG8gdmlzdWFsbHkgaWRlbnRpZnkgYW55IHZhcmlhYmxlIGludGVyYWN0aW9ucwoKYGBge3J9CnNlbGVjdGVkX2NvbHVtbnMgPSBjKCdkaXJlY3Rvcl9mYl9saWtlcycsCiAgICAgICAgICAgICAgICAgICAgICd0b3RhbF9jYXN0X2xpa2VzJywKICAgICAgICAgICAgICAgICAgICAgJ2ZiX2xpa2VzJywKICAgICAgICAgICAgICAgICAgICAgJ3VzZXJzX3ZvdGVzJywKICAgICAgICAgICAgICAgICAgICAgJ3Njb3JlJywKICAgICAgICAgICAgICAgICAgICAgJ2dyb3NzJykKCiNwbG90KHRyYWluKQpwbG90KCB0cmFpbl9kYXRhWyBzZWxlY3RlZF9jb2x1bW5zIF0gKQpgYGAKCgoqKkNyZWF0aW5nIFotc2NvcmVzIHVzaW5nIHRoZSB0cmFpbmluZyBkYXRhKioKYGBge3J9CiMjIyBVbmNvbW1lbnQgdG8gdXNlIGFsbCB0aGUgY29sdW1ucyBpbiB0aGUgZGF0YXNldAojenNjb3JlcyA8LSBhcHBseSh0cmFpbiwgMSwgZnVuY3Rpb24oeCkgKHggLSBtZWFuKHgpKSAvIHNkKHgpKQp6c2NvcmVzIDwtIGFwcGx5KCB0cmFpbl9kYXRhW3NlbGVjdGVkX2NvbHVtbnNdLCAxLCBmdW5jdGlvbih4KSAoeCAtIG1lYW4oeCkpIC8gc2QoeCkpCnpzY29yZXMgPC0gZGF0YS5mcmFtZSggdCh6c2NvcmVzKSApCmBgYAoKKipDb3JyZWxhdGlvbiBQbG90KioKYGBge3J9CmNvcnIgPC0gY29yKHpzY29yZXMpCmNvcnJwbG90KGNvcnIpCmBgYAoKLS0tLS0tLS0tLS0tCgojIyBNb2RlbCBCdWlsZGluZzogTGluZWFyIERpc2NyaW1pbmF0IEFuYWx5c2lzIHdpdGggbWxyCgotLS0tLS0tLS0tLS0KCmBgYHtyfQojIyMgSGVyZSB3ZSBhcmUgbWFraW5nIGEgb2JqZWN0IG9mIHR5cGUgbWFrZUxlYXJuZXIgdXNpbmcgdGhlIExEQSBjbGFzc2lmaWVyCmxybiA9IG1ha2VMZWFybmVyKCJjbGFzc2lmLmxkYSIsIHByZWRpY3QudHlwZSA9ICJwcm9iIikKCiMjIyBOb3cgdXNpbmcgdGhlIHRyYWluIGZ1bmN0aW9uIGluIG1sciB3ZSBjYW4gdHJhaW4gb3VyIG1vZGVsIHVzaW5nIHRoZSB0cmFpbmluZyBzZXQKbW9kID0gdHJhaW4obHJuLCB0YXNrID0gbW92aWVzX21sci50YXNrLCBzdWJzZXQgPSB0cmFpbl9tbHIuc2V0KQoKIyMjIEFmdGVyIHRyaW5pbmcgdGhlIG1vZGVsIHdlIHRlc3QgaXQgb24gdGhlIHRlc3RpbmcgZGF0YQpwcmVkID0gcHJlZGljdChtb2QsIHRhc2sgPSBtb3ZpZXNfbWxyLnRhc2ssIHN1YnNldCA9IHRlc3RfbWxyLnNldCkKCiMjIyBNZWFzdXJlIHBlcmZvcm1hbmNlIG9mIHByZWRpY3Rpb24KcGVyZm9ybWFuY2UocHJlZCwgbWVhc3VyZXMgPSBsaXN0KG1tY2UsIGFjYykpCmBgYAoKKipUaHJlc2hvbGQgdnMuIFBlcmZvcm1hbmNlKHMpIGZvciAyLWNsYXNzIGNsYXNzaWZpY2F0aW9uIFRhYmxlKioKClRoZSBmb2xsb3dpbmcgbWxyIGNvbW1hbmQgZ2VuZXJhdGVzIGRhdGEgb24gdGhyZXNob2xkIHZzLiBwZXJmb3JtYW5jZShzKSBmb3IgYSAyLWNsYXNzIGNsYXNzaWZpY2F0aW9uIHRoYXQgY2FuIGJlIHVzZWQgZm9yIHBsb3R0aW5nLgoKYGBge3J9CmRmID0gZ2VuZXJhdGVUaHJlc2hWc1BlcmZEYXRhKHByZWQsIG1lYXN1cmVzID0gbGlzdChmcHIsIHRwcikgKQpoZWFkKGRmKQpgYGAKCioqUGxvdHRpbmcgUk9DQ3VydmVzIHdpdGggbWxyIGZvciAyLWNsYXNzIGNsYXNzaWZpY2F0aW9uKioKCiogVGhlIFJPQyBjdXJ2ZSBpcyBhIHBvcHVsYXIgZ3JhcGhpYyBmb3Igc2ltdWx0YW5lb3VzbHkgZGlzcGxheWluZyB0aGUgUk9DIGN1cnZlIHR3byB0eXBlcyBvZiBlcnJvcnMgZm9yIGFsbCBwb3NzaWJsZSB0aHJlc2hvbGRzLiAKCiogVGhlIG92ZXJhbGwgcGVyZm9ybWFuY2Ugb2YgYSBjbGFzc2lmaWVyLCBzdW1tYXJpemVkIG92ZXIgYWxsIHBvc3NpYmxlIHRocmVzaG9sZHMsIGlzIGdpdmVuIGJ5IHRoZSBhcmVhIHVuZGVyIHRoZSAoUk9DKSBjdXJ2ZSAoQVVDKS4gCgoqIEFuIGlkZWFsIFJPQyBjdXJ2ZSB3aWxsIGh1ZyB0aGUgdG9wIGxlZnQgY29ybmVyLCBzbyB0aGUgbGFyZ2VyIGFyZWEgdW5kZXIgdGhlIChST0MpIGN1cnZlIHRoZSBBVUMgdGhlIGJldHRlciB0aGUgY2xhc3NpZmllci4KCmBgYHtyfQpwbG90Uk9DQ3VydmVzKGRmKQpgYGAKCioqUGxvdHRpbmcgdGhyZXNob2xkIHZzLiBwZXJmb3JtYW5jZSB3aXRoIG1scioqCgpgYGB7cn0KcGxvdFRocmVzaFZzUGVyZihkZikKYGBgCgotLS0tLS0tLS0tLS0KCiMjIE1vZGVsIEJ1aWxkaW5nOiBMaW5lYXIgRGlzY3JpbWluYXQgQW5hbHlzaXMgd2l0aCBNQVNTCgotLS0tLS0tLS0tLS0KCkxldHMgZml0IGEgTERBIG1vZGVsIGZvciB1c2luZyB0aGUgdHJhaW5pbmcgZGF0YSAoIG1vdmllcyBhZnRlciAyMDA2ICkKCmBgYHtyfQojIyMgVW5jb21tZW50IHRvIGNyZWF0ZSBhIG1vZGVsIGNvbnRhaW5pbmcgb25seSBzZWxlY3RlZCBjb2x1bW5zCiNsZGEuZml0ID0gbGRhKCBnZW5yZXMgfiAuLCBkYXRhID0gdHJhaW5fZGF0YVsgYygnZ2VucmVzJywgc2VsZWN0ZWRfY29sdW1ucykgXSkKbGRhLmZpdCA9IGxkYShnZW5yZXMgfiAuLCBkYXRhID0gdHJhaW5fZGF0YSkKbGRhLmZpdApgYGAKClRoZSBMREEgb3V0cHV0IHNob3dzIFByaW9yIHByb2JhYmlsaXRpZXMgZm9yIGVhY2ggZ3JvdXAgc3VjaCBhcyAqQWR2ZW50dXJlKiAoJFxoYXR7XHBpXzF9ID0gMC41MDY2NjY3JCksICpCaW9ncmFwaHkqICgkXGhhdHtccGlfMn0gPSAwLjI2NjY2NjckKSBhbmQgKkhvcnJvciogKCRcaGF0e1xwaV8zfSA9IDAuMjI2NjY2NyQpLiBGcm9tIHRoZSBwcmlvciBwcm9iYWJpbGl0aWVzIHdlIGNhbiBkZXRlcm1pbmUgdGhhdCBhYm91dCAkNTBcJSQgb2YgdGhlIHRyYWluaW5nIGRhdGEgY29ycmVzcG9uZCB0byBnZW5yZSAqQWR2ZW50dXJlKiB0aGUgb3RoZXIgJDUwXCUkIGlzIHNwbGl0IGJldHdlZW4gdGhlIG90aGVyIHR3byBnZW5yZXMgKCpCaW9ncmFwaHkqICBhbmQgKkhvcnJvcyopLgoKYGBge3J9CnBsb3QobGRhLmZpdCkKYGBgCgoKYGBge3J9CmxkYS5wcmVkID0gcHJlZGljdCggbGRhLmZpdCwgdGVzdF9kYXRhICkKbmFtZXMobGRhLnByZWQpCmBgYAoKKiBUaGUgYGBgcHJlZGljdCgpYGBgIGZ1bmN0aW9uIHJldHVybnMgYSBsaXN0IHdpdGggdGhyZWUgZWxlbWVudHMuIFRoZSBmaXJzdCBlbGVtZW50LCAqKmNsYXNzKiosIGNvbnRhaW5zIExEQeKAmXMgcHJlZGljdGlvbnMgb2YgZ2VucmUuCgoqIFRoZSBzZWNvbmQgZWxlbWVudCwgcG9zdGVyaW9yLCBpcyBhIG1hdHJpeCB3aG9zZSAka3RoJCBjb2x1bW4gY29udGFpbnMgdGhlIHBvc3RlcmlvciBwcm9iYWJpbGl0eSB0aGF0IHRoZSBjb3JyZXNwb25kaW5nIG9ic2VydmF0aW9uIGJlbG9uZ3MgdG8gdGhlICRrdGgkIGNsYXNzCgoqICR4JCBjb250YWlucyB0aGUgbGluZWFyIGRpc2NyaW1pbmFudCwgZGVzY3JpYmVkIGVhcmxpZXIuCgpgYGB7cn0KbGRhLmNsYXNzID0gbGRhLnByZWQkY2xhc3MKdGFibGUobGRhLmNsYXNzLCB5X3Rlc3QpCmBgYAoKCmBgYHtyfQptZWFuKGxkYS5jbGFzcyA9PSB5X3Rlc3QpCmBgYAoKLS0tLS0tLS0tLS0tCgojIyBRdWFkcmF0aWMgRGlzY3JpbWluYXQgQW5hbHlzaXMKCi0tLS0tLS0tLS0tLQoKVG8gcGVyZm9ybSBhIFF1YWRyYXRpYyBEaXNjcmltaW5hbnQgQW5hbHlzaXMgd2UgbmVlZCB0byByZXNhbXBsZSB0aGUgZGF0YSBhbmQgZm9sbG93IHNpbWlsYXIgc3RlcHMgdGhhdCB3aGVuIHBlcmZvcm1pbmcgYSBMaW5lYXIgRGlzY3JpbWluYW50IEFuYWx5c2lzLiBCdXQgZm9yIHRoaXMgY2FzZSB3ZSBjYW50IHBsb3QgdGhlIFFMRCBtb2RlbC4KCmBgYHtyfQojIyMgSGVyZSB3ZSBhcmUgb25seSBzZWxlY3RpbmcgZ2VucmUgYW5kIG90aGVyIG51bWVyaWMgdmFsdWVzIHRoYXQgd2UgYXJlIGdvaW5nIHRvIHVzZSB0byBjcmVhdGUgdGhlIG1vZGVsCgptb3ZpZXMgPC0gaW1kYlsgaW1kYiRnZW5yZXMgJWluJSB5X2NsYXNzZXMsIF0KbW92aWVzIDwtIG1vdmllc1sgbW92aWVzJHllYXIgPiAxOTk2LCBdCm1vdmllcyA8LSBtb3ZpZXNbIGMoMiw3OjE4LDIwKSBdCgojIyMgTm93IHdlIGV4dHJhY3Qgc2VsZWN0IHRoYXQgZGF0c2EgdXNpbmcgdGhlIGluZGV4IG9mIHRoZSByb3dzIGFuZCB0YWtlIGFub3RoZXIgc2FtcGxlCiMjIyBCdXQgdGhpcyB0aW1lIGlzIHRvIGNyZWF0ZSB0aGUgVHJhaW5pbmcgYW5kIFRlc3Rpbmcgc2V0cwppZHhfc21wIDwtIHNhbXBsZShzZXFfbGVuKG5yb3cobW92aWVzKSksIHNpemUgPSA1MDApCm1vdmllcyA8LSBtb3ZpZXNbaWR4X3NtcCxdCnNtcF9zaXplIDwtIGZsb29yKDAuNzUgKiBucm93KG1vdmllcykpCnRyYWluX2luZCA8LSBzYW1wbGUoc2VxX2xlbihucm93KG1vdmllcykpLCBzaXplID0gc21wX3NpemUpCgojIyMgVFJBSU5JTkcKdHJhaW5fZGF0YSA8LSBtb3ZpZXNbIHRyYWluX2luZCwgXQoKIyMjIFRFU1RJTkcKdGVzdF9kYXRhIDwtIG1vdmllc1sgLXRyYWluX2luZCwgXQoKIyMjIExhYmVscyBmcm9tIHRoZSB0ZXN0aW5nIHNldAp5X3Rlc3QgPC0gYXMuZmFjdG9yKHRlc3RfZGF0YVsnZ2VucmVzJ10kZ2VucmVzKQp0ZXN0X2RhdGFbJ2dlbnJlcyddIDwtIE5VTEwKYGBgCgpXZSBhcmUgZ29pbmcgdG8gZml0IGEgUURBIG1vZGVsIHRoYXQgaXMgbGFyZ2VyIGluIHNpemUgYXMgdGhlIHRyYWluaW5nIGRhdGEgc3RhcnRzIGZyb20gMTk5Ni4KCmBgYHtyfQpxZGEuZml0ID0gcWRhKGdlbnJlcyB+IC4sIGRhdGEgPSB0cmFpbl9kYXRhKQpxZGEuZml0CmBgYAoKRm9yIHRoZSBRREEgbW9kZWwgdGhlIG91dHB1dCBjb250YWlucyB0aGUgZ3JvdXAgbWVhbnMgYW5kIFByaW9yIHByb2JhYmlsaXRpZXMgb2YgdGhlIGdyb3VwcyBqdXN0IGxpa2UgaW4gTERBLCBidXQgaXQgZG9lcyBub3QgY29udGFpbiB0aGUgY29lZmZpY2llbnRzIG9mIHRoZSBsaW5lYXIgZGlzY3JpbWluYW50cywgYmVjYXVzZSB0aGUgUURBIGNsYXNzaWZpZXIgaW52b2x2ZXMgYSBxdWFkcmF0aWMsIHJhdGhlciB0aGFuIGEgbGluZWFyLCBmdW5jdGlvbiBvZiB0aGUgcHJlZGljdG9ycy4gCgpUaGUgcHJlZGljdCgpIGZ1bmN0aW9uIHdvcmtzIGluIGV4YWN0bHkgdGhlIHNhbWUgZmFzaGlvbiBhcyBmb3IgTERBLgoKYGBge3J9CnFkYS5wcmVkID0gcHJlZGljdChxZGEuZml0LCB0ZXN0X2RhdGEpCnFkYS5jbGFzcyA9IHFkYS5wcmVkJGNsYXNzCnRhYmxlKHFkYS5jbGFzcywgeV90ZXN0KQpgYGAKCgpgYGB7cn0KbWVhbihxZGEuY2xhc3MgPT0geV90ZXN0KQpgYGAK