This notebook illustrates the use of Tidyverse libraries (dplyr, tidyr, etc. ) for feature engineering. The data is used for this notebook the San Francisco Crime Classification competition on Kaggle. Let us first load all the libraries that we will use
# Load libraries
library(plyr)
library(dplyr)
library(tidyr)
library(readr)
library(stringr)
library(purrr)
library(lubridate)
library(Matrix)
library(MatrixModels)
library(caret)
The libraries dplyr, tidyr, readr, stringr, and purrr are parts of the tidyverse collection. We load the data using the read_csv function of readr which is more efficient than the corressponding read.csv of base R.
train <- read_csv("./data/train.csv", col_types = "Tccccccnn")
test <- read_csv("./data/test.csv", col_types = "iTcccnn")
The variables test and train are tibble objects, a more efficient version of the base R data.frame. Let’s print train and test to explore what they look like
train
and
test
The print method of the tibble object never attempts to print the whole data.frame as opposed to base R, which is helpful especially in data exploration. The Category column is the target category which we need to predict for the test data. As can be seen, the test data does not contain the columns Resolution and Descript, so we remove them from train as well. It may be possible to use the information in these columns to engineer some features, but for now we ignore them for simplicity.
train$Descript <- NULL
train$Resolution <- NULL
Now, we are ready for some feature engineering!
Feature Engineeing
The data contains several useful features that are already suitable for using in a model. PdDistrict is a categorical variable that encodes which police district the recorded crime is associated with, and X and Y are the longitude and latitude of the location. One obvious attempt to generate new features would be to use the Dates column and create year, month, hour (day is already encoded in DayOfWeek). Not as straightforward is the engineering of features from the Address column. Let’s first look at how many distinct addresses there are in the dataset
length(unique(train$Address))
[1] 23228
length(unique(test$Address))
[1] 23184
In principle, one can construct a categorical Address variable with around 20,000 levels (or better perform feature hashing, see here for an example). This would lead to very long training times for models that we want to use, so we rather resort to engineering simpler features from Address. One idea is to construct numeric features from Address using the ratio of each crime in a given category to the total crimes (of all categories) recorded in a given Address. Namely, we use the following as a numeric feature
\[r_{A,c} = 1 + \frac{n_{A,c}}{\sum_{i=1}^{39} n_{A,i}}\] where \(n_{A,c}\) is the number of crimes of category \(c\) in a given address \(A\). There are 39 different crime categories, which explains the limits of the sum in the denominator, while the 1 is added to the ratio since we will compute the log of this feature eventually.
levels(as.factor(train$Category))
[1] "ARSON" "ASSAULT" "BAD CHECKS"
[4] "BRIBERY" "BURGLARY" "DISORDERLY CONDUCT"
[7] "DRIVING UNDER THE INFLUENCE" "DRUG/NARCOTIC" "DRUNKENNESS"
[10] "EMBEZZLEMENT" "EXTORTION" "FAMILY OFFENSES"
[13] "FORGERY/COUNTERFEITING" "FRAUD" "GAMBLING"
[16] "KIDNAPPING" "LARCENY/THEFT" "LIQUOR LAWS"
[19] "LOITERING" "MISSING PERSON" "NON-CRIMINAL"
[22] "OTHER OFFENSES" "PORNOGRAPHY/OBSCENE MAT" "PROSTITUTION"
[25] "RECOVERED VEHICLE" "ROBBERY" "RUNAWAY"
[28] "SECONDARY CODES" "SEX OFFENSES FORCIBLE" "SEX OFFENSES NON FORCIBLE"
[31] "STOLEN PROPERTY" "SUICIDE" "SUSPICIOUS OCC"
[34] "TREA" "TRESPASS" "VANDALISM"
[37] "VEHICLE THEFT" "WARRANTS" "WEAPON LAWS"
This idea is easy to implement (especially with tidyverse functionalities, as will be shown below), however requires some care. In a naive implementation, one would compute the crime by address ratios (defined above) for each Address in the train set. Then, these ratios can be merged with test by the Address column (i.e. by using left_join function of dplyr with by="Address"). Doing so, one will immediately realize (after obtaining predictions for Category) that this leads to overfitting. The reason is that we have used the target variable Category to construct the new crime by address ratio features. As a result, the trained model found much higher weights for these features, since they are highly correlated to Category by construction. Thus, the model memorizes the train data and cannot generalize properly when it encounters new data.
This looks pretty bad and it seems like constructing new features using the target Category is doomed. However, if one splits the train data into 2 pieces, and construct crime by address ratios from piece_1 and merge them with piece_2 (and repeat vice versa from piece_2 to piece_1) then the overfitting could be mitigated. The reason that this works is because the new features are constructed by using out-of-sample target values (Category) and so the crime by address ratios of each piece is not memorized.
As an illustration consider the following piece of example data:
Let’s split this data such that piece 1 contains rows (1,2,3,4,5,8) and piece 2 contains (6,7). Now, construct the crime by Address ratios (using the above formula) using piece 1, which would result in
and merge these with piece 2, which results in
The NAs are the result of the fact that Address E was not a part of piece 1. In a large dataset, such NA values would reflect the fact that in that address there is not much crime so one can impute them with the default value 1.0 (i.e. no crimes) as an approximation.
Now, it would be ideal to have more divisions of train so that each piece contains Address values that are present in the remaining pieces, so that a precise estimate of its crime by address feature can be constructed (thus fewer NAs would be encountered). This calls for the use of a k-folds divison of train. This is in fact a reminescent of the stacking predictions idea, which is used for combining predictions of different models, and to remove the bias on the best perfroming model.
Now it is time to construct some functions to implement this idea. The first one is the simple function for imputing NAs with a default value (1.0)
# Fill NA's with default ratio
fill_na <- function(x, default_ratio){
x[is.na(x)] <- default_ratio
x
}
The second one constructs k-folds division of train and computes the crime by address ratios. Notice the use of spread function from tidyr which is used to contsruct new columns from the rows. The cool aspect of Tidyverse is that all the data wrangling operations are extremely intuitive.
create_ratios_folds <- function(train, nfolds, seed = 1234){
# Create folds
set.seed(seed)
folds <- createFolds(train$Category, k = nfolds)
# Initiate a list where the holdout data frames from each fold will be stored
list_out <- list()
for (ifold in 1:length(folds)){
train_in <- train[-folds[[ifold]], ]
train_ho <- train[folds[[ifold]], ]
# Group by Address & Category
df <- train_in %>%
mutate(Address = as.factor(Address)) %>%
mutate(Category = as.factor(Category)) %>%
group_by(Address, Category) %>% count() %>%
rename(tot_address_category = n)
# Group by Address
df2 <- train_in %>%
mutate(Address = as.factor(Address)) %>%
mutate(Category = as.factor(Category)) %>%
group_by(Address) %>% count() %>%
rename(tot_address = n)
# Join and determine the ratio of each Category per Address
df3 <- left_join(df, df2, by = "Address")
df3 <- df3 %>%
mutate(freq = ifelse(tot_address_category >= 5, 1.0 + tot_address_category / tot_address, 1.0))
# Now spread the ratios to columns
df3$tot_address_category <- NULL # Not needed
df3$tot_address <- NULL # Not needed
df4 <- spread(df3, key = Category, value = freq, fill = 1.0) # Fill NA's with 1
# Collapse the feature names (useful for future use in model Matrices)
names(df4) <- str_replace_all(names(df4), "( )", "")
names(df4) <- str_replace(names(df4), "/", "_")
names(df4) <- str_replace(names(df4), "-", "_")
# Join df4 with train_ho
train_ho <- left_join(train_ho,
df4 %>% ungroup(Address) %>% mutate(Address = as.character(Address)),
by = "Address")
# Impute NAs with 1.0 using fill_na function
train_ho <- train_ho %>% mutate_at(names(df4)[-1], fill_na, default_ratio=1.0)
# Add to list
list_out[[ifold]] <- train_ho
}
# Return train_out
bind_rows(list_out)
}
Now, we can create the crime by address ratios from the train set by a 5-fold division
# Create new fetures using 5 folds
train_new <- create_ratios_folds(train, 5)
OK, it is time to merge with the test set so that it has these newly engineered features. Below is the function that we use to perform this step
create_ratios_test <- function(train, test){
# Features to be created
log_feats <- names(train)[8:46]
# At each address, the median of the determined Address features
df <- train %>% select_(.dots = c(log_feats, "Address")) %>% distinct() %>%
group_by(Address) %>% summarise_at(log_feats, median)
# Join df4 with train_out
test_out <- left_join(test,
df %>% ungroup(Address),
by = "Address")
# Impute NAs with 1.0
test_out <- test_out %>% mutate_at(log_feats, fill_na, default_ratio=1.0)
# Return test_out
test_out
}
Notice that we do not perform a simple merge by left_join. First, we create a table df which contains the crime by address ratios and the corressponding addresses. This table has duplicates (more than one crime happened in one address in the whole data) so we get rid of them by the distinct function. However, since we have used out-of-sample values for crime ratios, it happens that for a given address, there are multiple possible values of each crime ratio. A simple solution to obtain unique values is simply to use the median value in such cases, which is implemented by the summarize_at function above. The rest is simply merging test with df and returning the new data test_out that contains the newly engineered features!
# Cerate new features in test
test_new <- create_ratios_test(train_new, test)
used (Mb) gc trigger (Mb) max used (Mb)
Ncells 1617791 86.4 2637877 140.9 2637877 140.9
Vcells 82123966 626.6 163649973 1248.6 135650070 1035.0
Finally, we compute the log of the new crime by address ratios (which leads to less skewed features), and the mutate_at function is suitable for this task
# Compute log
log_feats <- names(train_new)[8:46]
train_new <- train_new %>% mutate_at(log_feats, log)
test_new <- test_new %>% mutate_at(log_feats, log)
Further Feature Engineering
Now that we have performed the most intricate part of feature engineering, let’s contsruct a few more based on Dates and the text features of the Address column. It is always a good idea to merge train and test first to perform these wrangling operations
# Combine test and train
test_ids <- test_new$Id # Save id's
test_new$Id <- NULL
test_new$Category <- NA # Fill test Category with NAs
full_data <- bind_rows(train_new, test_new) # Filter test by is.na(Category) == TRUE
rm(train_new, test_new) # train_new and test_new not needed anymore
Let’s construct hour, month and year from Dates using lubridate functions
# Date and time
full_data <- full_data %>%
mutate(year = year(ymd_hms(Dates))) %>%
mutate(month = month(ymd_hms(Dates))) %>%
mutate(hour = hour(ymd_hms(Dates)))
full_data$Dates <- NULL
Now, let’s squeeze some more features from Address, based on the text it contains. We will look for the following features:
- Has the crime occured in an intersection (look for “/”)?
- Has the crime ocured in an address that contains the word “Block”?
- What type of abbreviation is used in the address (e.g. ST, AV, BLD,..)?
- Is the Block numbered 0 in the address (instead of a valid numeric value like 200)?
- Is the street numbered (e.g. 23RD, 15TH)?
# Address feature: is it an intersection?
find_intersection <- function(x){ as.integer(str_detect(x, "/")) }
full_data <- full_data %>%
mutate(is.intersection = find_intersection(Address))
# Address feature: is it block?
find_block <- function(x) { as.integer(str_detect(x, "Block")) }
full_data <- full_data %>%
mutate(is.block = find_block(Address))
# Address features: is it ST, AV, DR, WY, PZ, LN, BL, RD, zeroBlock, numberedST
find_type <- function(x, t){
as.integer(str_detect(x, t))
}
full_data <- full_data %>%
mutate(is.AV = find_type(Address, "( AV)$|( AVE)$|( AV )| ( AVE )")) %>%
mutate(is.ST = find_type(Address, "( ST)$|( ST )")) %>%
mutate(is.DR = find_type(Address, "( DR)$| ( DR )")) %>%
mutate(is.WY = find_type(Address, "( WY)$|( WAY)$|( WY )|( WAY )")) %>%
mutate(is.PZ = find_type(Address, "( PZ)$|( PZ )")) %>%
mutate(is.PL = find_type(Address, "( PL)$|( PL )")) %>%
mutate(is.LN = find_type(Address, "( LN)$|( LN )")) %>%
mutate(is.BL = find_type(Address, "( BL)$|( BLVD)$|( BL )|( BLVD )")) %>%
mutate(is.RD = find_type(Address, "( RD)$|( RD )")) %>%
mutate(is.zeroBlock = find_type(Address, "^(0 Block)")) %>%
mutate(is.numberedST = find_type(Address, "[0-9](RD)|[0-9](TH)"))
# Remove Adress feature
full_data$Address <- NULL
The stringr function str_detect has been very useful to capture regular expressions for this step. Let’s convert year, month, DayOfWeek, Category and PdDistrict into categorical variables using mutate
# Convert Date features, PdDistrict into factor
full_data <- full_data %>%
mutate(Category = as.factor(Category)) %>%
mutate(PdDistrict = as.factor(PdDistrict)) %>%
mutate(year = as.factor(year)) %>%
mutate(month = as.factor(month)) %>%
mutate(DayOfWeek = as.factor(DayOfWeek))
We have chosen to keep hour numeric since there seems to be a continuous relationship between hour and the type of crime. One thing that we noticed is that there are some values of latitude and longitude which do not make sense, since thay are out of San Francisco city limits
summary(full_data$X)
Min. 1st Qu. Median Mean 3rd Qu. Max.
-122.5 -122.4 -122.4 -122.4 -122.4 -120.5
summary(full_data$Y)
Min. 1st Qu. Median Mean 3rd Qu. Max.
37.71 37.75 37.78 37.77 37.78 90.00
Definitely, one cannot have a crime located at North Pole to be recorded in San Fransisco! Let’s remove these strange outliers by calculating the median values of X and Y by PdDistrict and then replace outliers with the median corressponding to their PdDistrict
# Medians by PdDistrict
med_table <- full_data %>% group_by(PdDistrict) %>% summarise(med_X = median(X), med_Y = median(Y))
full_data <- full_data %>%
left_join(med_table, by = "PdDistrict")
# Replace outliers
outs <- full_data$Y > 40 # Outliers, this removes outliers in Y too
full_data[outs, ]$X <- full_data[outs, ]$med_X
full_data[outs, ]$Y <- full_data[outs, ]$med_Y
full_data$med_X <- NULL
full_data$med_Y <- NULL
Finally, let’s standardize the numeric columns X, Y and hour
# Normalize X,Y,hour
normalize_feat <- function(x){
(x - mean(x))/sd(x)
}
full_data <- full_data %>%
mutate(X = normalize_feat(X)) %>%
mutate(Y = normalize_feat(Y)) %>%
mutate(hour = normalize_feat(hour))
Now our data is ready for modelling!
Modelling with XGBoost
By popular demand, the XGBoost implementation of gradient boosted tree algorithm is our choice here. First we split back into train and test and remove the variable full_data to clean up some space in the memory
# Split back into train and test
train <- full_data %>% filter(is.na(Category) == FALSE)
test <- full_data %>% filter(is.na(Category) == TRUE)
rm(full_data); gc()
used (Mb) gc trigger (Mb) max used (Mb)
Ncells 1602234 85.6 5103933 272.6 7717408 412.2
Vcells 96741344 738.1 236034262 1800.8 235989328 1800.5
Then, we construct a sparse model matrix to encode the categorical variables we have created above
Y <- train$Category
X <- model.Matrix(~.,
data = train %>% select(-Category),
sparse = TRUE)[,-1]
Let’s prepare the data for XGBoost
# XGBoost
library(xgboost)
# XGboost wants levels to start with 0
class.names <- levels(Y)
levels(Y) <- 0:38
Y <- as.numeric(levels(Y))[Y]
and split the original train into training and validation sets:
# Split into train and validation
library(caret)
set.seed(123456789)
idx_train <- createDataPartition(train$Category, p = 0.7)[[1]]
X_tr <- X[idx_train, ]; X_vld <- X[-idx_train, ]
Y_tr <- Y[idx_train]; Y_vld <- Y[-idx_train]
# XGB style matrices
dtrain <- xgb.DMatrix(data = X_tr, label = Y_tr)
dvalid <- xgb.DMatrix(data = X_vld, label = Y_vld)
watchlist <- list(train=dtrain, test=dvalid)
Now train (this takes a while)
### Train using standard params
# Parameters
params <- list(booster = "gbtree",
eval_metric = "mlogloss",
objective = "multi:softprob",
subsample = 1,
colsample_bytree = 1,
eta = 0.3,
min_child_weight = 1,
max_depth = 6,
gamma = 0.0,
seed = 123)
# Fit
fit_tr <- xgb.train(params = params,
data = dtrain,
num_class = 39,
nrounds = 500,
watchlist = watchlist,
early_stopping_rounds = 10,
print_every_n = 10,
maximize = FALSE)
[1] train-mlogloss:2.988178 test-mlogloss:2.994273
Multiple eval metrics are present. Will use test_mlogloss for early stopping.
Will train until test_mlogloss hasn't improved in 10 rounds.
[11] train-mlogloss:2.371029 test-mlogloss:2.404007
[21] train-mlogloss:2.286314 test-mlogloss:2.342143
[31] train-mlogloss:2.254193 test-mlogloss:2.329931
[41] train-mlogloss:2.234521 test-mlogloss:2.326505
[51] train-mlogloss:2.218702 test-mlogloss:2.324803
[61] train-mlogloss:2.204673 test-mlogloss:2.323799
[71] train-mlogloss:2.192091 test-mlogloss:2.323144
[81] train-mlogloss:2.179863 test-mlogloss:2.322827
[91] train-mlogloss:2.167418 test-mlogloss:2.322321
[101] train-mlogloss:2.156822 test-mlogloss:2.322483
Stopping. Best iteration:
[93] train-mlogloss:2.165367 test-mlogloss:2.322304
A few points deserve more explanation here. The loss function that is being minimized here is the multi-class log-loss which is defined as \[ L = -\frac{1}{N} \sum_{i=1}^N\, \sum_{c=1}^{K}\, Y_{ic}\, \log(P_{ic}) \] where \(N\) is the number of training examples and \(K\) is the number of classes (crime categories here). \(Y_{ic}\) is the one-hot encoded matrix of true classes which assings the value 1 to \(Y_{ic}\) if the example \(i\) is of class \(c\) and 0 otherwise. \(P_{ic}\) is the matrix of predicted class probabilities from the model. This loss function awards high predicted probabilities for correct class and low predicted probabilities for others, so that it contains more information than a simple accuracy measure.
This post is not about XGBoost and how to tune its hyperparameters. However, a few observations from the above training log deserves some explanation. In this dataset, it is hard to perform a cross-validation sweep over the hyperparameters on a grid, since training is very slow. Thus, one would need to watch train/test loss by tweaking the hyperparameters by hand. As can be seen, early stopping kicked in just after about 100 rounds. This means that we started to overfit rather quickly. In this situation, it would be useful to reduce eta and increase min_child_weight. In addition, reducing subsample will help, but we have to make sure that there are enough nrounds to let the training loss to become low enough. The next step would be to try to increase max_depth to capture more complex patterns in each round, while lowering eta and increasing min_child_weight to prevent overfitting. It could also be useful to reduce colsample_bytree which would help reduce overfitting, since at each step a different subset of all features will be used to grow a tree. Again in such a case, one has to make sure nrounds is large enough. I would not expect gamma having much of a use in this case, since we do not see the test loss skyrocketing at any point.
Now let’s look at the importance of each feature (measured by the amount they reduce the loss function, see here for more details)
importance <- xgb.importance(feature_names = X_tr@Dimnames[[2]], model = fit_tr)
importance
and plot the first 25 most important features
xgb.plot.importance(importance_matrix = head(importance, 25))

As can be seen, the crime by address features are on the top of the importance list! The other features that look important are hour, is.intersection, X and Y, which are expected by intuition. The hour and location at which the crime happens correlate with which crime it is.
Final words
In this post we have shown how to wrangle columns in a dataset to engineer new features. We have also described a method to avoid pitfalls of overfitting when the target variable in the training data is incorporated in feature engineering. All of the data wrangling is made quite simple and intuitive by the Tidyverse collection of libraries. I strongly recommend the book R for data science to learn more about the these functionalities and further examples of usage.
LS0tCnRpdGxlOiAiRmlnaHRpbmcgQ3JpbWUgd2l0aCBUaWR5dmVyc2UiCm91dHB1dDoKICBodG1sX25vdGVib29rOiBkZWZhdWx0CiAgaHRtbF9kb2N1bWVudDogZGVmYXVsdAogIHBkZl9kb2N1bWVudDogZGVmYXVsdAotLS0KClRoaXMgbm90ZWJvb2sgaWxsdXN0cmF0ZXMgdGhlIHVzZSBvZiBbVGlkeXZlcnNlXShodHRwOi8vdGlkeXZlcnNlLm9yZy8pIGxpYnJhcmllcyAoZHBseXIsIHRpZHlyLCBldGMuICkgZm9yIGZlYXR1cmUgZW5naW5lZXJpbmcuIFRoZSBkYXRhIGlzIHVzZWQgZm9yIHRoaXMgbm90ZWJvb2sgdGhlIFtTYW4gRnJhbmNpc2NvIENyaW1lIENsYXNzaWZpY2F0aW9uXShodHRwczovL3d3dy5rYWdnbGUuY29tL2Mvc2YtY3JpbWUpIGNvbXBldGl0aW9uIG9uIEthZ2dsZS4gTGV0IHVzIGZpcnN0IGxvYWQgYWxsIHRoZSBsaWJyYXJpZXMgdGhhdCB3ZSB3aWxsIHVzZQpgYGB7ciwgd2FybmluZz1GQUxTRX0KIyBMb2FkIGxpYnJhcmllcwpsaWJyYXJ5KHBseXIpCmxpYnJhcnkoZHBseXIpCmxpYnJhcnkodGlkeXIpCmxpYnJhcnkocmVhZHIpCmxpYnJhcnkoc3RyaW5ncikKbGlicmFyeShwdXJycikKbGlicmFyeShsdWJyaWRhdGUpCmxpYnJhcnkoTWF0cml4KQpsaWJyYXJ5KE1hdHJpeE1vZGVscykKbGlicmFyeShjYXJldCkKYGBgClRoZSBsaWJyYXJpZXMgYGRwbHlyYCwgYHRpZHlyYCwgYHJlYWRyYCwgYHN0cmluZ3JgLCBhbmQgYHB1cnJyYCBhcmUgcGFydHMgb2YgdGhlIHRpZHl2ZXJzZSBjb2xsZWN0aW9uLiBXZSBsb2FkIHRoZSBkYXRhIHVzaW5nIHRoZSBgcmVhZF9jc3ZgIGZ1bmN0aW9uIG9mIGByZWFkcmAgd2hpY2ggaXMgbW9yZSBlZmZpY2llbnQgdGhhbiB0aGUgY29ycmVzc3BvbmRpbmcgYHJlYWQuY3N2YCBvZiBiYXNlIFIuCmBgYHtyfQp0cmFpbiA8LSByZWFkX2NzdigiLi9kYXRhL3RyYWluLmNzdiIsIGNvbF90eXBlcyA9ICJUY2NjY2Njbm4iKQp0ZXN0IDwtIHJlYWRfY3N2KCIuL2RhdGEvdGVzdC5jc3YiLCBjb2xfdHlwZXMgPSAiaVRjY2NubiIpCmBgYApUaGUgdmFyaWFibGVzIGB0ZXN0YCBhbmQgYHRyYWluYCBhcmUgdGliYmxlIG9iamVjdHMsIGEgbW9yZSBlZmZpY2llbnQgdmVyc2lvbiBvZiB0aGUgYmFzZSBSIGBkYXRhLmZyYW1lYC4gTGV0J3MgcHJpbnQgYHRyYWluYCBhbmQgYHRlc3RgIHRvIGV4cGxvcmUgd2hhdCB0aGV5IGxvb2sgbGlrZQpgYGB7cn0KdHJhaW4KYGBgCmFuZApgYGB7cn0KdGVzdApgYGAKVGhlIHByaW50IG1ldGhvZCBvZiB0aGUgdGliYmxlIG9iamVjdCBuZXZlciBhdHRlbXB0cyB0byBwcmludCB0aGUgd2hvbGUgZGF0YS5mcmFtZSBhcyBvcHBvc2VkIHRvIGJhc2UgUiwgd2hpY2ggaXMgaGVscGZ1bCBlc3BlY2lhbGx5IGluIGRhdGEgZXhwbG9yYXRpb24uIFRoZSBgQ2F0ZWdvcnlgIGNvbHVtbiBpcyB0aGUgdGFyZ2V0IGNhdGVnb3J5IHdoaWNoIHdlIG5lZWQgdG8gcHJlZGljdCBmb3IgdGhlIHRlc3QgZGF0YS4gQXMgY2FuIGJlIHNlZW4sIHRoZSBgdGVzdGAgZGF0YSBkb2VzIG5vdCBjb250YWluIHRoZSBjb2x1bW5zIGBSZXNvbHV0aW9uYCBhbmQgYERlc2NyaXB0YCwgc28gd2UgcmVtb3ZlIHRoZW0gZnJvbSBgdHJhaW5gIGFzIHdlbGwuIEl0IG1heSBiZSBwb3NzaWJsZSB0byB1c2UgdGhlIGluZm9ybWF0aW9uIGluIHRoZXNlIGNvbHVtbnMgdG8gZW5naW5lZXIgc29tZSBmZWF0dXJlcywgYnV0IGZvciBub3cgd2UgaWdub3JlIHRoZW0gZm9yIHNpbXBsaWNpdHkuIApgYGB7cn0KdHJhaW4kRGVzY3JpcHQgPC0gTlVMTAp0cmFpbiRSZXNvbHV0aW9uIDwtIE5VTEwKYGBgCk5vdywgd2UgYXJlIHJlYWR5IGZvciBzb21lIGZlYXR1cmUgZW5naW5lZXJpbmchCgojIyMgRmVhdHVyZSBFbmdpbmVlaW5nCgpUaGUgZGF0YSBjb250YWlucyBzZXZlcmFsIHVzZWZ1bCBmZWF0dXJlcyB0aGF0IGFyZSBhbHJlYWR5IHN1aXRhYmxlIGZvciB1c2luZyBpbiBhIG1vZGVsLiBgUGREaXN0cmljdGAgaXMgYSBjYXRlZ29yaWNhbCB2YXJpYWJsZSB0aGF0IGVuY29kZXMgd2hpY2ggcG9saWNlIGRpc3RyaWN0IHRoZSByZWNvcmRlZCBjcmltZSBpcyBhc3NvY2lhdGVkIHdpdGgsIGFuZCBgWGAgYW5kIGBZYCBhcmUgdGhlIGxvbmdpdHVkZSBhbmQgbGF0aXR1ZGUgb2YgdGhlIGxvY2F0aW9uLiBPbmUgb2J2aW91cyBhdHRlbXB0IHRvIGdlbmVyYXRlIG5ldyBmZWF0dXJlcyB3b3VsZCBiZSB0byB1c2UgdGhlIGBEYXRlc2AgY29sdW1uIGFuZCBjcmVhdGUgYHllYXJgLCBgbW9udGhgLCBgaG91cmAgKGRheSBpcyBhbHJlYWR5IGVuY29kZWQgaW4gYERheU9mV2Vla2ApLiBOb3QgYXMgc3RyYWlnaHRmb3J3YXJkIGlzIHRoZSBlbmdpbmVlcmluZyBvZiBmZWF0dXJlcyBmcm9tIHRoZSBgQWRkcmVzc2AgY29sdW1uLiBMZXQncyBmaXJzdCBsb29rIGF0IGhvdyBtYW55IGRpc3RpbmN0IGFkZHJlc3NlcyB0aGVyZSBhcmUgaW4gdGhlIGRhdGFzZXQKYGBge3J9Cmxlbmd0aCh1bmlxdWUodHJhaW4kQWRkcmVzcykpCmxlbmd0aCh1bmlxdWUodGVzdCRBZGRyZXNzKSkKYGBgCkluIHByaW5jaXBsZSwgb25lIGNhbiBjb25zdHJ1Y3QgYSBjYXRlZ29yaWNhbCBgQWRkcmVzc2AgdmFyaWFibGUgd2l0aCBhcm91bmQgMjAsMDAwIGxldmVscyAob3IgYmV0dGVyIHBlcmZvcm0gZmVhdHVyZSBoYXNoaW5nLCBzZWUgW2hlcmVdKDlodHRwOi8vYW11bmF0ZWd1aS5naXRodWIuaW8vZmVhdHVyZS1oYXNoaW5nLykgZm9yIGFuIGV4YW1wbGUpLiBUaGlzIHdvdWxkIGxlYWQgdG8gdmVyeSBsb25nIHRyYWluaW5nIHRpbWVzIGZvciBtb2RlbHMgdGhhdCB3ZSB3YW50IHRvIHVzZSwgc28gd2UgcmF0aGVyIHJlc29ydCB0byBlbmdpbmVlcmluZyBzaW1wbGVyIGZlYXR1cmVzIGZyb20gYEFkZHJlc3NgLiBPbmUgaWRlYSBpcyB0byBjb25zdHJ1Y3QgbnVtZXJpYyBmZWF0dXJlcyBmcm9tIGBBZGRyZXNzYCB1c2luZyB0aGUgcmF0aW8gb2YgZWFjaCBjcmltZSBpbiBhIGdpdmVuIGNhdGVnb3J5IHRvIHRoZSB0b3RhbCBjcmltZXMgKG9mIGFsbCBjYXRlZ29yaWVzKSByZWNvcmRlZCBpbiBhIGdpdmVuIEFkZHJlc3MuIE5hbWVseSwgd2UgdXNlIHRoZSBmb2xsb3dpbmcgYXMgYSBudW1lcmljIGZlYXR1cmUKCiQkcl97QSxjfSA9IDEgKyBcZnJhY3tuX3tBLGN9fXtcc3VtX3tpPTF9XnszOX0gbl97QSxpfX0kJAp3aGVyZSAkbl97QSxjfSQgaXMgdGhlIG51bWJlciBvZiBjcmltZXMgb2YgY2F0ZWdvcnkgJGMkIGluIGEgZ2l2ZW4gYWRkcmVzcyAkQSQuIFRoZXJlIGFyZSAzOSBkaWZmZXJlbnQgY3JpbWUgY2F0ZWdvcmllcywgd2hpY2ggZXhwbGFpbnMgdGhlIGxpbWl0cyBvZiB0aGUgc3VtIGluIHRoZSBkZW5vbWluYXRvciwgd2hpbGUgdGhlIDEgaXMgYWRkZWQgdG8gdGhlIHJhdGlvIHNpbmNlIHdlIHdpbGwgY29tcHV0ZSB0aGUgbG9nIG9mIHRoaXMgZmVhdHVyZSBldmVudHVhbGx5LiAKYGBge3J9CmxldmVscyhhcy5mYWN0b3IodHJhaW4kQ2F0ZWdvcnkpKQpgYGAKVGhpcyBpZGVhIGlzIGVhc3kgdG8gaW1wbGVtZW50IChlc3BlY2lhbGx5IHdpdGggdGlkeXZlcnNlIGZ1bmN0aW9uYWxpdGllcywgYXMgd2lsbCBiZSBzaG93biBiZWxvdyksIGhvd2V2ZXIgcmVxdWlyZXMgc29tZSBjYXJlLiBJbiBhIG5haXZlIAppbXBsZW1lbnRhdGlvbiwgb25lIHdvdWxkIGNvbXB1dGUgdGhlIGNyaW1lIGJ5IGFkZHJlc3MgcmF0aW9zIChkZWZpbmVkIGFib3ZlKSBmb3IgZWFjaCBgQWRkcmVzc2AgaW4gdGhlIGB0cmFpbmAgc2V0LiBUaGVuLCB0aGVzZSByYXRpb3MgY2FuIGJlIG1lcmdlZCB3aXRoIGB0ZXN0YCBieSB0aGUgYEFkZHJlc3NgIGNvbHVtbiAoaS5lLiBieSB1c2luZyBgbGVmdF9qb2luYCBmdW5jdGlvbiBvZiBgZHBseXJgIHdpdGggYGJ5PSJBZGRyZXNzImApLiBEb2luZyBzbywgb25lIHdpbGwgaW1tZWRpYXRlbHkgcmVhbGl6ZSAoYWZ0ZXIgb2J0YWluaW5nIHByZWRpY3Rpb25zIGZvciBgQ2F0ZWdvcnlgKSB0aGF0IHRoaXMgbGVhZHMgdG8gb3ZlcmZpdHRpbmcuIFRoZSByZWFzb24gaXMgdGhhdCB3ZSBoYXZlIHVzZWQgdGhlIHRhcmdldCB2YXJpYWJsZSBgQ2F0ZWdvcnlgIHRvIGNvbnN0cnVjdCB0aGUgbmV3IGNyaW1lIGJ5IGFkZHJlc3MgcmF0aW8gZmVhdHVyZXMuIEFzIGEgcmVzdWx0LCB0aGUgdHJhaW5lZCBtb2RlbCBmb3VuZCBtdWNoIGhpZ2hlciB3ZWlnaHRzIGZvciB0aGVzZSBmZWF0dXJlcywgc2luY2UgdGhleSBhcmUgaGlnaGx5IGNvcnJlbGF0ZWQgdG8gYENhdGVnb3J5YCBieSBjb25zdHJ1Y3Rpb24uIFRodXMsIHRoZSBtb2RlbCBtZW1vcml6ZXMgdGhlIGB0cmFpbmAgZGF0YSBhbmQgY2Fubm90IGdlbmVyYWxpemUgcHJvcGVybHkgd2hlbiBpdCBlbmNvdW50ZXJzIG5ldyBkYXRhLiAKClRoaXMgbG9va3MgcHJldHR5IGJhZCBhbmQgaXQgc2VlbXMgbGlrZSBjb25zdHJ1Y3RpbmcgbmV3IGZlYXR1cmVzIHVzaW5nIHRoZSB0YXJnZXQgYENhdGVnb3J5YCBpcyBkb29tZWQuIEhvd2V2ZXIsIGlmIG9uZSBzcGxpdHMgdGhlIGB0cmFpbmAgZGF0YSBpbnRvIDIgcGllY2VzLCBhbmQgY29uc3RydWN0IGNyaW1lIGJ5IGFkZHJlc3MgcmF0aW9zIGZyb20gYHBpZWNlXzFgIGFuZCBtZXJnZSB0aGVtIHdpdGggYHBpZWNlXzJgIChhbmQgcmVwZWF0IHZpY2UgdmVyc2EgZnJvbSBgcGllY2VfMmAgdG8gYHBpZWNlXzFgKSB0aGVuIHRoZSBvdmVyZml0dGluZyBjb3VsZCBiZSBtaXRpZ2F0ZWQuIFRoZSByZWFzb24gdGhhdCB0aGlzIHdvcmtzIGlzIGJlY2F1c2UgdGhlIG5ldyBmZWF0dXJlcyBhcmUgY29uc3RydWN0ZWQgYnkgdXNpbmcgb3V0LW9mLXNhbXBsZSB0YXJnZXQgdmFsdWVzIChgQ2F0ZWdvcnlgKSBhbmQgc28gdGhlIGNyaW1lIGJ5IGFkZHJlc3MgcmF0aW9zIG9mIGVhY2ggcGllY2UgaXMgbm90IG1lbW9yaXplZC4KCkFzIGFuIGlsbHVzdHJhdGlvbiBjb25zaWRlciB0aGUgZm9sbG93aW5nIHBpZWNlIG9mIGV4YW1wbGUgZGF0YToKYGBge3IsIGVjaG89RkFMU0V9CnNldDEgPC0gdGliYmxlKCBBZGRyZXNzID0gYygiQSIsICJBIiwgIkEiLCAiQiIsICJDIiwgIkMiLCAiRSIsICJEIiksCiAgICAgICAgICAgICAgICBDYXRlZ29yeSA9IGMoIkFSU09OIiwgIkFSU09OIiwgIkJVUkdMQVJZIiwgIkFSU09OIiwgIkFTU0FVTFQiLCAiQlVSR0xBUlkiLCAiQVNTQVVMVCIsICJUUkVTUEFTUyIpKQpgYGAKYGBge3J9CnNldDEKYGBgCkxldCdzIHNwbGl0IHRoaXMgZGF0YSBzdWNoIHRoYXQgYHBpZWNlIDFgIGNvbnRhaW5zIHJvd3MgKDEsMiwzLDQsNSw4KSBhbmQgYHBpZWNlIDJgIGNvbnRhaW5zICg2LDcpLiBOb3csIGNvbnN0cnVjdCB0aGUgY3JpbWUgYnkgQWRkcmVzcyByYXRpb3MgKHVzaW5nIHRoZSBhYm92ZSBmb3JtdWxhKSB1c2luZyBgcGllY2UgMWAsIHdoaWNoIHdvdWxkIHJlc3VsdCBpbgpgYGB7ciwgZWNobz1GQUxTRX0Kc2V0MiA8LSB0aWJibGUoIEFkZHJlc3MgPSBjKCJBIiwgIkIiLCAiQyIsICJEIiksCiAgICAgICAgICAgICAgICBBUlNPTiA9IGMoMS42NiwgMS4zMywgMS4wLCAxLjApLAogICAgICAgICAgICAgICAgQVNTQVVMVCA9IGMoMS4wLCAxLjAsIDEuMzMsIDEuMCksIAogICAgICAgICAgICAgICAgQlVSR0xBUlkgPSBjKDEuMzMsIDEuMCwgMS4wLCAxLjApLAogICAgICAgICAgICAgICAgVFJFU1BBU1MgPSBjKDEuMCwgMS4wLCAxLjAsIDEuMzMpKQpzZXQzIDwtIHRpYmJsZShBZGRyZXNzID0gYygiQyIsICJFIiksCiAgICAgICAgICAgICAgICBBUlNPTiA9IGMoMS4wMCwgTkEpLAogICAgICAgICAgICAgICAgQVNTQVVMVCA9IGMoMS4zMywgTkEpLCAKICAgICAgICAgICAgICAgIEJVUkdMQVJZID0gYygxLjAsIE5BKSwKICAgICAgICAgICAgICAgIFRSRVNQQVNTID0gYygxLjAsIE5BKSkKYGBgCmBgYHtyfQpzZXQyCmBgYAphbmQgbWVyZ2UgdGhlc2Ugd2l0aCBgcGllY2UgMmAsIHdoaWNoIHJlc3VsdHMgaW4KYGBge3J9CnNldDMKYGBgClRoZSBgTkFgcyBhcmUgdGhlIHJlc3VsdCBvZiB0aGUgZmFjdCB0aGF0IEFkZHJlc3MgYEVgIHdhcyBub3QgYSBwYXJ0IG9mIGBwaWVjZSAxYC4gSW4gYSBsYXJnZSBkYXRhc2V0LCBzdWNoIGBOQWAgdmFsdWVzIHdvdWxkIHJlZmxlY3QgdGhlIGZhY3QgdGhhdCBpbiB0aGF0IGFkZHJlc3MgdGhlcmUgaXMgbm90IG11Y2ggY3JpbWUgc28gb25lIGNhbiBpbXB1dGUgdGhlbSB3aXRoIHRoZSBkZWZhdWx0IHZhbHVlIDEuMCAoaS5lLiBubyBjcmltZXMpIGFzIGFuIGFwcHJveGltYXRpb24uIAoKTm93LCBpdCB3b3VsZCBiZSBpZGVhbCB0byBoYXZlIG1vcmUgZGl2aXNpb25zIG9mIGB0cmFpbmAgc28gdGhhdCBlYWNoIHBpZWNlIGNvbnRhaW5zIGBBZGRyZXNzYCB2YWx1ZXMgdGhhdCBhcmUgcHJlc2VudCBpbiB0aGUgcmVtYWluaW5nIHBpZWNlcywgc28gdGhhdCBhIHByZWNpc2UgZXN0aW1hdGUgb2YgaXRzIGNyaW1lIGJ5IGFkZHJlc3MgZmVhdHVyZSBjYW4gYmUgY29uc3RydWN0ZWQgKHRodXMgZmV3ZXIgYE5BYHMgd291bGQgYmUgZW5jb3VudGVyZWQpLiBUaGlzIGNhbGxzIGZvciB0aGUgdXNlIG9mIGEgay1mb2xkcyBkaXZpc29uIG9mIGB0cmFpbmAuIFRoaXMgaXMgaW4gZmFjdCBhIHJlbWluZXNjZW50IG9mIHRoZSBbc3RhY2tpbmcgcHJlZGljdGlvbnNdKGh0dHBzOi8vYnVyYWtoaW1tZXRvZ2x1LmNvbS8yMDE2LzEyLzAxL3N0YWNraW5nLW1vZGVscy1mb3ItaW1wcm92ZWQtcHJlZGljdGlvbnMvKSBpZGVhLCB3aGljaCBpcyB1c2VkIGZvciBjb21iaW5pbmcgcHJlZGljdGlvbnMgb2YgZGlmZmVyZW50IG1vZGVscywgYW5kIHRvIHJlbW92ZSB0aGUgYmlhcyBvbiB0aGUgYmVzdCBwZXJmcm9taW5nIG1vZGVsLiAKCk5vdyBpdCBpcyB0aW1lIHRvIGNvbnN0cnVjdCBzb21lIGZ1bmN0aW9ucyB0byBpbXBsZW1lbnQgdGhpcyBpZGVhLiBUaGUgZmlyc3Qgb25lIGlzIHRoZSBzaW1wbGUgZnVuY3Rpb24gZm9yIGltcHV0aW5nIGBOQWBzIHdpdGggYSBkZWZhdWx0IHZhbHVlICgxLjApCmBgYHtyfQojIEZpbGwgTkEncyB3aXRoIGRlZmF1bHQgcmF0aW8KZmlsbF9uYSA8LSBmdW5jdGlvbih4LCBkZWZhdWx0X3JhdGlvKXsKICB4W2lzLm5hKHgpXSA8LSBkZWZhdWx0X3JhdGlvCiAgeAp9CmBgYApUaGUgc2Vjb25kIG9uZSBjb25zdHJ1Y3RzIGstZm9sZHMgZGl2aXNpb24gb2YgYHRyYWluYCBhbmQgY29tcHV0ZXMgdGhlIGNyaW1lIGJ5IGFkZHJlc3MgcmF0aW9zLiBOb3RpY2UgdGhlIHVzZSBvZiBgc3ByZWFkYCBmdW5jdGlvbiBmcm9tIGB0aWR5cmAgd2hpY2ggaXMgdXNlZCB0byBjb250c3J1Y3QgbmV3IGNvbHVtbnMgZnJvbSB0aGUgcm93cy4gVGhlIGNvb2wgYXNwZWN0IG9mIFRpZHl2ZXJzZSBpcyB0aGF0IGFsbCB0aGUgZGF0YSB3cmFuZ2xpbmcgb3BlcmF0aW9ucyBhcmUgZXh0cmVtZWx5IGludHVpdGl2ZS4gCmBgYHtyfQpjcmVhdGVfcmF0aW9zX2ZvbGRzIDwtIGZ1bmN0aW9uKHRyYWluLCBuZm9sZHMsIHNlZWQgPSAxMjM0KXsKICAjIENyZWF0ZSBmb2xkcwogIHNldC5zZWVkKHNlZWQpCiAgZm9sZHMgPC0gY3JlYXRlRm9sZHModHJhaW4kQ2F0ZWdvcnksIGsgPSBuZm9sZHMpCiAgCiAgIyBJbml0aWF0ZSBhIGxpc3Qgd2hlcmUgdGhlIGhvbGRvdXQgZGF0YSBmcmFtZXMgZnJvbSBlYWNoIGZvbGQgd2lsbCBiZSBzdG9yZWQKICBsaXN0X291dCA8LSBsaXN0KCkKICAKICBmb3IgKGlmb2xkIGluIDE6bGVuZ3RoKGZvbGRzKSl7CiAgICB0cmFpbl9pbiA8LSB0cmFpblstZm9sZHNbW2lmb2xkXV0sIF0KICAgIHRyYWluX2hvIDwtIHRyYWluW2ZvbGRzW1tpZm9sZF1dLCBdCiAgICAKICAgICMgR3JvdXAgYnkgQWRkcmVzcyAmIENhdGVnb3J5CiAgICBkZiA8LSB0cmFpbl9pbiAlPiUKICAgICAgbXV0YXRlKEFkZHJlc3MgPSBhcy5mYWN0b3IoQWRkcmVzcykpICU+JQogICAgICBtdXRhdGUoQ2F0ZWdvcnkgPSBhcy5mYWN0b3IoQ2F0ZWdvcnkpKSAlPiUKICAgICAgZ3JvdXBfYnkoQWRkcmVzcywgQ2F0ZWdvcnkpICU+JSBjb3VudCgpICU+JQogICAgICByZW5hbWUodG90X2FkZHJlc3NfY2F0ZWdvcnkgPSBuKQogICAgCiAgICAjIEdyb3VwIGJ5IEFkZHJlc3MKICAgIGRmMiA8LSB0cmFpbl9pbiAlPiUKICAgICAgbXV0YXRlKEFkZHJlc3MgPSBhcy5mYWN0b3IoQWRkcmVzcykpICU+JQogICAgICBtdXRhdGUoQ2F0ZWdvcnkgPSBhcy5mYWN0b3IoQ2F0ZWdvcnkpKSAlPiUKICAgICAgZ3JvdXBfYnkoQWRkcmVzcykgJT4lIGNvdW50KCkgJT4lIAogICAgICByZW5hbWUodG90X2FkZHJlc3MgPSBuKQogICAgCiAgICAjIEpvaW4gYW5kIGRldGVybWluZSB0aGUgcmF0aW8gb2YgZWFjaCBDYXRlZ29yeSBwZXIgQWRkcmVzcwogICAgZGYzIDwtIGxlZnRfam9pbihkZiwgZGYyLCBieSA9ICJBZGRyZXNzIikKICAgIGRmMyA8LSBkZjMgJT4lCiAgICAgIG11dGF0ZShmcmVxID0gaWZlbHNlKHRvdF9hZGRyZXNzX2NhdGVnb3J5ID49IDUsIDEuMCArIHRvdF9hZGRyZXNzX2NhdGVnb3J5IC8gdG90X2FkZHJlc3MsIDEuMCkpCiAgICAKICAgICMgTm93IHNwcmVhZCB0aGUgcmF0aW9zIHRvIGNvbHVtbnMKICAgIGRmMyR0b3RfYWRkcmVzc19jYXRlZ29yeSA8LSBOVUxMICMgTm90IG5lZWRlZAogICAgZGYzJHRvdF9hZGRyZXNzIDwtIE5VTEwgICAgICAgICAgIyBOb3QgbmVlZGVkCiAgICBkZjQgPC0gc3ByZWFkKGRmMywga2V5ID0gQ2F0ZWdvcnksIHZhbHVlID0gZnJlcSwgZmlsbCA9IDEuMCkgIyBGaWxsIE5BJ3Mgd2l0aCAxCiAgICAKICAgICMgQ29sbGFwc2UgdGhlIGZlYXR1cmUgbmFtZXMgKHVzZWZ1bCBmb3IgZnV0dXJlIHVzZSBpbiBtb2RlbCBNYXRyaWNlcykKICAgIG5hbWVzKGRmNCkgPC0gc3RyX3JlcGxhY2VfYWxsKG5hbWVzKGRmNCksICIoICkiLCAiIikKICAgIG5hbWVzKGRmNCkgPC0gc3RyX3JlcGxhY2UobmFtZXMoZGY0KSwgIi8iLCAiXyIpCiAgICBuYW1lcyhkZjQpIDwtIHN0cl9yZXBsYWNlKG5hbWVzKGRmNCksICItIiwgIl8iKQogICAgCiAgICAjIEpvaW4gZGY0IHdpdGggdHJhaW5faG8KICAgIHRyYWluX2hvIDwtIGxlZnRfam9pbih0cmFpbl9obywKICAgICAgICAgICAgICAgICAgICAgICAgICAgZGY0ICU+JSB1bmdyb3VwKEFkZHJlc3MpICU+JSBtdXRhdGUoQWRkcmVzcyA9IGFzLmNoYXJhY3RlcihBZGRyZXNzKSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGJ5ID0gIkFkZHJlc3MiKQogICAgCiAgICAjIEltcHV0ZSBOQXMgd2l0aCAxLjAgdXNpbmcgZmlsbF9uYSBmdW5jdGlvbgogICAgdHJhaW5faG8gPC0gdHJhaW5faG8gJT4lIG11dGF0ZV9hdChuYW1lcyhkZjQpWy0xXSwgZmlsbF9uYSwgZGVmYXVsdF9yYXRpbz0xLjApCiAgCiAgICAjIEFkZCB0byBsaXN0CiAgICBsaXN0X291dFtbaWZvbGRdXSA8LSB0cmFpbl9obwogIH0KICAKICAjIFJldHVybiB0cmFpbl9vdXQKICBiaW5kX3Jvd3MobGlzdF9vdXQpCn0KYGBgCk5vdywgd2UgY2FuIGNyZWF0ZSB0aGUgY3JpbWUgYnkgYWRkcmVzcyByYXRpb3MgZnJvbSB0aGUgYHRyYWluYCBzZXQgYnkgYSA1LWZvbGQgZGl2aXNpb24KYGBge3J9CiMgQ3JlYXRlIG5ldyBmZXR1cmVzIHVzaW5nIDUgZm9sZHMKdHJhaW5fbmV3IDwtIGNyZWF0ZV9yYXRpb3NfZm9sZHModHJhaW4sIDUpCmBgYApPSywgaXQgaXMgdGltZSB0byBtZXJnZSB3aXRoIHRoZSBgdGVzdGAgc2V0IHNvIHRoYXQgaXQgaGFzIHRoZXNlIG5ld2x5IGVuZ2luZWVyZWQgZmVhdHVyZXMuIEJlbG93IGlzIHRoZSBmdW5jdGlvbiB0aGF0IHdlIHVzZSB0byBwZXJmb3JtIHRoaXMgc3RlcApgYGB7cn0KY3JlYXRlX3JhdGlvc190ZXN0IDwtIGZ1bmN0aW9uKHRyYWluLCB0ZXN0KXsKICAjIEZlYXR1cmVzIHRvIGJlIGNyZWF0ZWQKICBsb2dfZmVhdHMgPC0gbmFtZXModHJhaW4pWzg6NDZdCiAgCiAgIyBBdCBlYWNoIGFkZHJlc3MsIHRoZSBtZWRpYW4gb2YgdGhlIGRldGVybWluZWQgQWRkcmVzcyBmZWF0dXJlcwogIGRmIDwtIHRyYWluICU+JSBzZWxlY3RfKC5kb3RzID0gYyhsb2dfZmVhdHMsICJBZGRyZXNzIikpICU+JSBkaXN0aW5jdCgpICU+JQogICAgZ3JvdXBfYnkoQWRkcmVzcykgJT4lIHN1bW1hcmlzZV9hdChsb2dfZmVhdHMsIG1lZGlhbikKICAKICAjIEpvaW4gZGY0IHdpdGggdHJhaW5fb3V0CiAgdGVzdF9vdXQgPC0gbGVmdF9qb2luKHRlc3QsCiAgICAgICAgICAgICAgICAgICAgICAgIGRmICU+JSB1bmdyb3VwKEFkZHJlc3MpLAogICAgICAgICAgICAgICAgICAgICAgICBieSA9ICJBZGRyZXNzIikKICAKICAjIEltcHV0ZSBOQXMgd2l0aCAxLjAKICB0ZXN0X291dCA8LSB0ZXN0X291dCAlPiUgbXV0YXRlX2F0KGxvZ19mZWF0cywgZmlsbF9uYSwgZGVmYXVsdF9yYXRpbz0xLjApCiAgCiAgIyBSZXR1cm4gdGVzdF9vdXQKICB0ZXN0X291dAp9CmBgYApOb3RpY2UgdGhhdCB3ZSBkbyBub3QgcGVyZm9ybSBhIHNpbXBsZSBtZXJnZSBieSBgbGVmdF9qb2luYC4gRmlyc3QsIHdlIGNyZWF0ZSBhIHRhYmxlIGBkZmAgd2hpY2ggY29udGFpbnMgdGhlIGNyaW1lIGJ5IGFkZHJlc3MgcmF0aW9zIGFuZCB0aGUgY29ycmVzc3BvbmRpbmcgYWRkcmVzc2VzLiBUaGlzIHRhYmxlIGhhcyBkdXBsaWNhdGVzIChtb3JlIHRoYW4gb25lIGNyaW1lIGhhcHBlbmVkIGluIG9uZSBhZGRyZXNzIGluIHRoZSB3aG9sZSBkYXRhKSBzbyB3ZSBnZXQgcmlkIG9mIHRoZW0gYnkgdGhlIGBkaXN0aW5jdGAgZnVuY3Rpb24uIEhvd2V2ZXIsIHNpbmNlIHdlIGhhdmUgdXNlZCBvdXQtb2Ytc2FtcGxlIHZhbHVlcyBmb3IgY3JpbWUgcmF0aW9zLCBpdCBoYXBwZW5zIHRoYXQgZm9yIGEgZ2l2ZW4gYWRkcmVzcywgdGhlcmUgYXJlIG11bHRpcGxlIHBvc3NpYmxlIHZhbHVlcyBvZiBlYWNoIGNyaW1lIHJhdGlvLiBBIHNpbXBsZSBzb2x1dGlvbiB0byBvYnRhaW4gdW5pcXVlIHZhbHVlcyBpcyBzaW1wbHkgdG8gdXNlIHRoZSBtZWRpYW4gdmFsdWUgaW4gc3VjaCBjYXNlcywgd2hpY2ggaXMgaW1wbGVtZW50ZWQgYnkgdGhlIGBzdW1tYXJpemVfYXRgIGZ1bmN0aW9uIGFib3ZlLiBUaGUgcmVzdCBpcyBzaW1wbHkgbWVyZ2luZyBgdGVzdGAgd2l0aCBgZGZgIGFuZCByZXR1cm5pbmcgdGhlIG5ldyBkYXRhIGB0ZXN0X291dGAgdGhhdCBjb250YWlucyB0aGUgbmV3bHkgZW5naW5lZXJlZCBmZWF0dXJlcyEKYGBge3J9CiMgQ2VyYXRlIG5ldyBmZWF0dXJlcyBpbiB0ZXN0CnRlc3RfbmV3IDwtIGNyZWF0ZV9yYXRpb3NfdGVzdCh0cmFpbl9uZXcsIHRlc3QpCmBgYApgYGB7ciwgZWNobz1GQUxTRX0Kcm0odHJhaW4sdGVzdCk7IGdjKCkKYGBgCkZpbmFsbHksIHdlIGNvbXB1dGUgdGhlIGxvZyBvZiB0aGUgbmV3IGNyaW1lIGJ5IGFkZHJlc3MgcmF0aW9zICh3aGljaCBsZWFkcyB0byBsZXNzIHNrZXdlZCBmZWF0dXJlcyksIGFuZCB0aGUgYG11dGF0ZV9hdGAgZnVuY3Rpb24gaXMgc3VpdGFibGUgZm9yIHRoaXMgdGFzawpgYGB7cn0KIyBDb21wdXRlIGxvZwpsb2dfZmVhdHMgPC0gbmFtZXModHJhaW5fbmV3KVs4OjQ2XQp0cmFpbl9uZXcgPC0gdHJhaW5fbmV3ICU+JSBtdXRhdGVfYXQobG9nX2ZlYXRzLCBsb2cpCnRlc3RfbmV3IDwtIHRlc3RfbmV3ICU+JSBtdXRhdGVfYXQobG9nX2ZlYXRzLCBsb2cpCgpgYGAKCiMjIyBGdXJ0aGVyIEZlYXR1cmUgRW5naW5lZXJpbmcKCk5vdyB0aGF0IHdlIGhhdmUgcGVyZm9ybWVkIHRoZSBtb3N0IGludHJpY2F0ZSBwYXJ0IG9mIGZlYXR1cmUgZW5naW5lZXJpbmcsIGxldCdzIGNvbnRzcnVjdCBhIGZldyBtb3JlIGJhc2VkIG9uIGBEYXRlc2AgYW5kIHRoZSB0ZXh0IGZlYXR1cmVzIG9mIHRoZSBgQWRkcmVzc2AgY29sdW1uLiBJdCBpcyBhbHdheXMgYSBnb29kIGlkZWEgdG8gbWVyZ2UgdHJhaW4gYW5kIHRlc3QgZmlyc3QgdG8gcGVyZm9ybSB0aGVzZSB3cmFuZ2xpbmcgb3BlcmF0aW9ucwpgYGB7cn0KIyBDb21iaW5lIHRlc3QgYW5kIHRyYWluCnRlc3RfaWRzIDwtIHRlc3RfbmV3JElkICMgU2F2ZSBpZCdzCnRlc3RfbmV3JElkIDwtIE5VTEwKdGVzdF9uZXckQ2F0ZWdvcnkgPC0gTkEgIyBGaWxsIHRlc3QgQ2F0ZWdvcnkgd2l0aCBOQXMKZnVsbF9kYXRhIDwtIGJpbmRfcm93cyh0cmFpbl9uZXcsIHRlc3RfbmV3KSAjIEZpbHRlciB0ZXN0IGJ5IGlzLm5hKENhdGVnb3J5KSA9PSBUUlVFCnJtKHRyYWluX25ldywgdGVzdF9uZXcpICMgdHJhaW5fbmV3IGFuZCB0ZXN0X25ldyBub3QgbmVlZGVkIGFueW1vcmUKYGBgCkxldCdzIGNvbnN0cnVjdCBgaG91cmAsIGBtb250aGAgYW5kIGB5ZWFyYCBmcm9tIGBEYXRlc2AgdXNpbmcgYGx1YnJpZGF0ZWAgZnVuY3Rpb25zCmBgYHtyfQojIERhdGUgYW5kIHRpbWUKZnVsbF9kYXRhIDwtIGZ1bGxfZGF0YSAlPiUKICBtdXRhdGUoeWVhciA9IHllYXIoeW1kX2htcyhEYXRlcykpKSAlPiUKICBtdXRhdGUobW9udGggPSBtb250aCh5bWRfaG1zKERhdGVzKSkpICU+JQogIG11dGF0ZShob3VyID0gaG91cih5bWRfaG1zKERhdGVzKSkpCmZ1bGxfZGF0YSREYXRlcyA8LSBOVUxMCmBgYAoKTm93LCBsZXQncyBzcXVlZXplIHNvbWUgbW9yZSBmZWF0dXJlcyBmcm9tIGBBZGRyZXNzYCwgYmFzZWQgb24gdGhlIHRleHQgaXQgY29udGFpbnMuIFdlIHdpbGwgbG9vayBmb3IgdGhlIGZvbGxvd2luZyBmZWF0dXJlczoKCjEuIEhhcyB0aGUgY3JpbWUgb2NjdXJlZCBpbiBhbiBpbnRlcnNlY3Rpb24gKGxvb2sgZm9yICIvIik/CjIuIEhhcyB0aGUgY3JpbWUgb2N1cmVkIGluIGFuIGFkZHJlc3MgdGhhdCBjb250YWlucyB0aGUgd29yZCAiQmxvY2siPwozLiBXaGF0IHR5cGUgb2YgYWJicmV2aWF0aW9uIGlzIHVzZWQgaW4gdGhlIGFkZHJlc3MgKGUuZy4gU1QsIEFWLCBCTEQsLi4pPwo0LiBJcyB0aGUgQmxvY2sgbnVtYmVyZWQgMCBpbiB0aGUgYWRkcmVzcyAoaW5zdGVhZCBvZiBhIHZhbGlkIG51bWVyaWMgdmFsdWUgbGlrZSAyMDApPwo1LiBJcyB0aGUgc3RyZWV0IG51bWJlcmVkIChlLmcuIDIzUkQsIDE1VEgpPwpgYGB7cn0KIyBBZGRyZXNzIGZlYXR1cmU6IGlzIGl0IGFuIGludGVyc2VjdGlvbj8KZmluZF9pbnRlcnNlY3Rpb24gPC0gZnVuY3Rpb24oeCl7IGFzLmludGVnZXIoc3RyX2RldGVjdCh4LCAiLyIpKSB9CmZ1bGxfZGF0YSA8LSBmdWxsX2RhdGEgJT4lCiAgbXV0YXRlKGlzLmludGVyc2VjdGlvbiA9IGZpbmRfaW50ZXJzZWN0aW9uKEFkZHJlc3MpKQoKIyBBZGRyZXNzIGZlYXR1cmU6IGlzIGl0IGJsb2NrPwpmaW5kX2Jsb2NrIDwtIGZ1bmN0aW9uKHgpIHsgYXMuaW50ZWdlcihzdHJfZGV0ZWN0KHgsICJCbG9jayIpKSB9CmZ1bGxfZGF0YSA8LSBmdWxsX2RhdGEgJT4lIAogIG11dGF0ZShpcy5ibG9jayA9IGZpbmRfYmxvY2soQWRkcmVzcykpCgojIEFkZHJlc3MgZmVhdHVyZXM6IGlzIGl0IFNULCBBViwgRFIsIFdZLCBQWiwgTE4sIEJMLCBSRCwgemVyb0Jsb2NrLCBudW1iZXJlZFNUCmZpbmRfdHlwZSA8LSBmdW5jdGlvbih4LCB0KXsKICBhcy5pbnRlZ2VyKHN0cl9kZXRlY3QoeCwgdCkpCn0KCmZ1bGxfZGF0YSA8LSBmdWxsX2RhdGEgJT4lIAogIG11dGF0ZShpcy5BViA9IGZpbmRfdHlwZShBZGRyZXNzLCAiKCBBVikkfCggQVZFKSR8KCBBViApfCAoIEFWRSApIikpICU+JQogIG11dGF0ZShpcy5TVCA9IGZpbmRfdHlwZShBZGRyZXNzLCAiKCBTVCkkfCggU1QgKSIpKSAlPiUKICBtdXRhdGUoaXMuRFIgPSBmaW5kX3R5cGUoQWRkcmVzcywgIiggRFIpJHwgKCBEUiApIikpICU+JQogIG11dGF0ZShpcy5XWSA9IGZpbmRfdHlwZShBZGRyZXNzLCAiKCBXWSkkfCggV0FZKSR8KCBXWSApfCggV0FZICkiKSkgJT4lCiAgbXV0YXRlKGlzLlBaID0gZmluZF90eXBlKEFkZHJlc3MsICIoIFBaKSR8KCBQWiApIikpICU+JQogIG11dGF0ZShpcy5QTCA9IGZpbmRfdHlwZShBZGRyZXNzLCAiKCBQTCkkfCggUEwgKSIpKSAlPiUKICBtdXRhdGUoaXMuTE4gPSBmaW5kX3R5cGUoQWRkcmVzcywgIiggTE4pJHwoIExOICkiKSkgJT4lCiAgbXV0YXRlKGlzLkJMID0gZmluZF90eXBlKEFkZHJlc3MsICIoIEJMKSR8KCBCTFZEKSR8KCBCTCApfCggQkxWRCApIikpICU+JQogIG11dGF0ZShpcy5SRCA9IGZpbmRfdHlwZShBZGRyZXNzLCAiKCBSRCkkfCggUkQgKSIpKSAlPiUKICBtdXRhdGUoaXMuemVyb0Jsb2NrID0gZmluZF90eXBlKEFkZHJlc3MsICJeKDAgQmxvY2spIikpICU+JQogIG11dGF0ZShpcy5udW1iZXJlZFNUID0gZmluZF90eXBlKEFkZHJlc3MsICJbMC05XShSRCl8WzAtOV0oVEgpIikpCgojIFJlbW92ZSBBZHJlc3MgZmVhdHVyZQpmdWxsX2RhdGEkQWRkcmVzcyA8LSBOVUxMCmBgYApUaGUgYHN0cmluZ3JgIGZ1bmN0aW9uIGBzdHJfZGV0ZWN0YCBoYXMgYmVlbiB2ZXJ5IHVzZWZ1bCB0byBjYXB0dXJlIHJlZ3VsYXIgZXhwcmVzc2lvbnMgZm9yIHRoaXMgc3RlcC4gCkxldCdzIGNvbnZlcnQgYHllYXJgLCBgbW9udGhgLCBgRGF5T2ZXZWVrYCwgYENhdGVnb3J5YCBhbmQgYFBkRGlzdHJpY3RgIGludG8gY2F0ZWdvcmljYWwgdmFyaWFibGVzIHVzaW5nIGBtdXRhdGVgCmBgYHtyfQojIENvbnZlcnQgRGF0ZSBmZWF0dXJlcywgUGREaXN0cmljdCBpbnRvIGZhY3RvcgpmdWxsX2RhdGEgPC0gZnVsbF9kYXRhICU+JSAKICBtdXRhdGUoQ2F0ZWdvcnkgPSBhcy5mYWN0b3IoQ2F0ZWdvcnkpKSAlPiUKICBtdXRhdGUoUGREaXN0cmljdCA9IGFzLmZhY3RvcihQZERpc3RyaWN0KSkgJT4lCiAgbXV0YXRlKHllYXIgPSBhcy5mYWN0b3IoeWVhcikpICU+JQogIG11dGF0ZShtb250aCA9IGFzLmZhY3Rvcihtb250aCkpICU+JQogIG11dGF0ZShEYXlPZldlZWsgPSBhcy5mYWN0b3IoRGF5T2ZXZWVrKSkKYGBgCldlIGhhdmUgY2hvc2VuIHRvIGtlZXAgYGhvdXJgIG51bWVyaWMgc2luY2UgdGhlcmUgc2VlbXMgdG8gYmUgYSBjb250aW51b3VzIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIGBob3VyYCBhbmQgdGhlIHR5cGUgb2YgY3JpbWUuIE9uZSB0aGluZyB0aGF0IHdlIG5vdGljZWQgaXMgdGhhdCB0aGVyZSBhcmUgc29tZSB2YWx1ZXMgb2YgbGF0aXR1ZGUgYW5kIGxvbmdpdHVkZSB3aGljaCBkbyBub3QgbWFrZSBzZW5zZSwgc2luY2UgdGhheSBhcmUgb3V0IG9mIFNhbiBGcmFuY2lzY28gY2l0eSBsaW1pdHMKYGBge3J9CnN1bW1hcnkoZnVsbF9kYXRhJFgpCnN1bW1hcnkoZnVsbF9kYXRhJFkpCmBgYApEZWZpbml0ZWx5LCBvbmUgY2Fubm90IGhhdmUgYSBjcmltZSBsb2NhdGVkIGF0IE5vcnRoIFBvbGUgdG8gYmUgcmVjb3JkZWQgaW4gU2FuIEZyYW5zaXNjbyEgTGV0J3MgcmVtb3ZlIHRoZXNlIHN0cmFuZ2Ugb3V0bGllcnMgYnkgY2FsY3VsYXRpbmcgdGhlIG1lZGlhbiB2YWx1ZXMgb2YgYFhgIGFuZCBgWWAgYnkgYFBkRGlzdHJpY3RgIGFuZCB0aGVuIHJlcGxhY2Ugb3V0bGllcnMgd2l0aCB0aGUgbWVkaWFuIGNvcnJlc3Nwb25kaW5nIHRvIHRoZWlyIGBQZERpc3RyaWN0YApgYGB7cn0KIyBNZWRpYW5zIGJ5IFBkRGlzdHJpY3QKbWVkX3RhYmxlIDwtIGZ1bGxfZGF0YSAlPiUgZ3JvdXBfYnkoUGREaXN0cmljdCkgJT4lIHN1bW1hcmlzZShtZWRfWCA9IG1lZGlhbihYKSwgbWVkX1kgPSBtZWRpYW4oWSkpCmZ1bGxfZGF0YSA8LSBmdWxsX2RhdGEgJT4lCiAgbGVmdF9qb2luKG1lZF90YWJsZSwgYnkgPSAiUGREaXN0cmljdCIpCgojIFJlcGxhY2Ugb3V0bGllcnMKb3V0cyA8LSBmdWxsX2RhdGEkWSA+IDQwICMgT3V0bGllcnMsIHRoaXMgcmVtb3ZlcyBvdXRsaWVycyBpbiBZIHRvbyAKZnVsbF9kYXRhW291dHMsIF0kWCA8LSBmdWxsX2RhdGFbb3V0cywgXSRtZWRfWApmdWxsX2RhdGFbb3V0cywgXSRZIDwtIGZ1bGxfZGF0YVtvdXRzLCBdJG1lZF9ZCmZ1bGxfZGF0YSRtZWRfWCA8LSBOVUxMCmZ1bGxfZGF0YSRtZWRfWSA8LSBOVUxMCmBgYApGaW5hbGx5LCBsZXQncyBzdGFuZGFyZGl6ZSB0aGUgbnVtZXJpYyBjb2x1bW5zIGBYYCwgYFlgIGFuZCBgaG91cmAKYGBge3J9CiMgTm9ybWFsaXplIFgsWSxob3VyCm5vcm1hbGl6ZV9mZWF0IDwtIGZ1bmN0aW9uKHgpewogICh4IC0gbWVhbih4KSkvc2QoeCkKfQoKZnVsbF9kYXRhIDwtIGZ1bGxfZGF0YSAlPiUKICBtdXRhdGUoWCA9IG5vcm1hbGl6ZV9mZWF0KFgpKSAlPiUKICBtdXRhdGUoWSA9IG5vcm1hbGl6ZV9mZWF0KFkpKSAlPiUKICBtdXRhdGUoaG91ciA9IG5vcm1hbGl6ZV9mZWF0KGhvdXIpKQoKYGBgCk5vdyBvdXIgZGF0YSBpcyByZWFkeSBmb3IgbW9kZWxsaW5nIQoKIyMjIE1vZGVsbGluZyB3aXRoIFhHQm9vc3QKCkJ5IHBvcHVsYXIgZGVtYW5kLCB0aGUgW1hHQm9vc3RdKGh0dHA6Ly94Z2Jvb3N0LnJlYWR0aGVkb2NzLmlvL2VuL2xhdGVzdC9SLXBhY2thZ2UvaW5kZXguaHRtbCkgaW1wbGVtZW50YXRpb24gb2YgZ3JhZGllbnQgYm9vc3RlZCB0cmVlIGFsZ29yaXRobSBpcyBvdXIgY2hvaWNlIGhlcmUuIEZpcnN0IHdlIHNwbGl0IGJhY2sgaW50byBgdHJhaW5gIGFuZCBgdGVzdGAgYW5kIHJlbW92ZSB0aGUgdmFyaWFibGUgYGZ1bGxfZGF0YWAgdG8gY2xlYW4gdXAgc29tZSBzcGFjZSBpbiB0aGUgbWVtb3J5CmBgYHtyfQojIFNwbGl0IGJhY2sgaW50byB0cmFpbiBhbmQgdGVzdAp0cmFpbiA8LSBmdWxsX2RhdGEgJT4lIGZpbHRlcihpcy5uYShDYXRlZ29yeSkgPT0gRkFMU0UpCnRlc3QgPC0gZnVsbF9kYXRhICU+JSBmaWx0ZXIoaXMubmEoQ2F0ZWdvcnkpID09IFRSVUUpCnJtKGZ1bGxfZGF0YSk7IGdjKCkKCmBgYApUaGVuLCB3ZSBjb25zdHJ1Y3QgYSBzcGFyc2UgbW9kZWwgbWF0cml4IHRvIGVuY29kZSB0aGUgY2F0ZWdvcmljYWwgdmFyaWFibGVzIHdlIGhhdmUgY3JlYXRlZCBhYm92ZQpgYGB7cn0KWSA8LSB0cmFpbiRDYXRlZ29yeQpYIDwtIG1vZGVsLk1hdHJpeCh+LiwgCiAgICAgICAgICAgICAgICAgIGRhdGEgPSB0cmFpbiAlPiUgc2VsZWN0KC1DYXRlZ29yeSksIAogICAgICAgICAgICAgICAgICBzcGFyc2UgPSBUUlVFKVssLTFdIApgYGAKTGV0J3MgcHJlcGFyZSB0aGUgZGF0YSBmb3IgWEdCb29zdApgYGB7cn0KIyBYR0Jvb3N0CmxpYnJhcnkoeGdib29zdCkKCiMgWEdib29zdCB3YW50cyBsZXZlbHMgdG8gc3RhcnQgd2l0aCAwCmNsYXNzLm5hbWVzIDwtIGxldmVscyhZKQpsZXZlbHMoWSkgPC0gMDozOApZIDwtIGFzLm51bWVyaWMobGV2ZWxzKFkpKVtZXQpgYGAKYW5kIHNwbGl0IHRoZSBvcmlnaW5hbCBgdHJhaW5gIGludG8gdHJhaW5pbmcgYW5kIHZhbGlkYXRpb24gc2V0czoKYGBge3J9CiMgU3BsaXQgaW50byB0cmFpbiBhbmQgdmFsaWRhdGlvbgpsaWJyYXJ5KGNhcmV0KQpzZXQuc2VlZCgxMjM0NTY3ODkpCmlkeF90cmFpbiA8LSBjcmVhdGVEYXRhUGFydGl0aW9uKHRyYWluJENhdGVnb3J5LCBwID0gMC43KVtbMV1dClhfdHIgPC0gWFtpZHhfdHJhaW4sIF07IFhfdmxkIDwtIFhbLWlkeF90cmFpbiwgXQpZX3RyIDwtIFlbaWR4X3RyYWluXTsgWV92bGQgPC0gWVstaWR4X3RyYWluXQoKIyBYR0Igc3R5bGUgbWF0cmljZXMKZHRyYWluIDwtIHhnYi5ETWF0cml4KGRhdGEgPSBYX3RyLCBsYWJlbCA9IFlfdHIpCmR2YWxpZCA8LSB4Z2IuRE1hdHJpeChkYXRhID0gWF92bGQsIGxhYmVsID0gWV92bGQpCndhdGNobGlzdCA8LSBsaXN0KHRyYWluPWR0cmFpbiwgdGVzdD1kdmFsaWQpCgpgYGAKTm93IHRyYWluICh0aGlzIHRha2VzIGEgd2hpbGUpCmBgYHtyLCBjYWNoZT1UUlVFfQojIyMgVHJhaW4gdXNpbmcgc3RhbmRhcmQgcGFyYW1zCiMgUGFyYW1ldGVycwpwYXJhbXMgPC0gbGlzdChib29zdGVyID0gImdidHJlZSIsCiAgICAgICAgICAgICAgIGV2YWxfbWV0cmljID0gIm1sb2dsb3NzIiwKICAgICAgICAgICAgICAgb2JqZWN0aXZlID0gIm11bHRpOnNvZnRwcm9iIiwKICAgICAgICAgICAgICAgc3Vic2FtcGxlID0gMSwKICAgICAgICAgICAgICAgY29sc2FtcGxlX2J5dHJlZSA9IDEsCiAgICAgICAgICAgICAgIGV0YSA9IDAuMywgIAogICAgICAgICAgICAgICBtaW5fY2hpbGRfd2VpZ2h0ID0gMSwKICAgICAgICAgICAgICAgbWF4X2RlcHRoID0gNiwKICAgICAgICAgICAgICAgZ2FtbWEgPSAwLjAsCiAgICAgICAgICAgICAgIHNlZWQgPSAxMjMpCiMgRml0IApmaXRfdHIgPC0geGdiLnRyYWluKHBhcmFtcyA9IHBhcmFtcywKICAgICAgICAgICAgICAgICAgICBkYXRhID0gZHRyYWluLAogICAgICAgICAgICAgICAgICAgIG51bV9jbGFzcyA9IDM5LAogICAgICAgICAgICAgICAgICAgIG5yb3VuZHMgPSA1MDAsCiAgICAgICAgICAgICAgICAgICAgd2F0Y2hsaXN0ID0gd2F0Y2hsaXN0LAogICAgICAgICAgICAgICAgICAgIGVhcmx5X3N0b3BwaW5nX3JvdW5kcyA9IDEwLAogICAgICAgICAgICAgICAgICAgIHByaW50X2V2ZXJ5X24gPSAxMCwKICAgICAgICAgICAgICAgICAgICBtYXhpbWl6ZSA9IEZBTFNFKQpgYGAKCkEgZmV3IHBvaW50cyBkZXNlcnZlIG1vcmUgZXhwbGFuYXRpb24gaGVyZS4gVGhlIGxvc3MgZnVuY3Rpb24gdGhhdCBpcyBiZWluZyBtaW5pbWl6ZWQgaGVyZSBpcyB0aGUgbXVsdGktY2xhc3MgbG9nLWxvc3Mgd2hpY2ggaXMgZGVmaW5lZCBhcwokJCBMID0gLVxmcmFjezF9e059IFxzdW1fe2k9MX1eTlwsIFxzdW1fe2M9MX1ee0t9XCwgWV97aWN9XCwgXGxvZyhQX3tpY30pICQkCndoZXJlICROJCBpcyB0aGUgbnVtYmVyIG9mIHRyYWluaW5nIGV4YW1wbGVzIGFuZCAkSyQgaXMgdGhlIG51bWJlciBvZiBjbGFzc2VzIChjcmltZSBjYXRlZ29yaWVzIGhlcmUpLiAkWV97aWN9JCBpcyB0aGUgb25lLWhvdCBlbmNvZGVkIG1hdHJpeCBvZiB0cnVlIGNsYXNzZXMgd2hpY2ggYXNzaW5ncyB0aGUgdmFsdWUgMSB0byAkWV97aWN9JCBpZiB0aGUgZXhhbXBsZSAkaSQgaXMgb2YgY2xhc3MgJGMkIGFuZCAwIG90aGVyd2lzZS4gJFBfe2ljfSQgaXMgdGhlIG1hdHJpeCBvZiBwcmVkaWN0ZWQgY2xhc3MgcHJvYmFiaWxpdGllcyBmcm9tIHRoZSBtb2RlbC4gVGhpcyBsb3NzIGZ1bmN0aW9uIGF3YXJkcyBoaWdoIHByZWRpY3RlZCBwcm9iYWJpbGl0aWVzIGZvciBjb3JyZWN0IGNsYXNzIGFuZCBsb3cgcHJlZGljdGVkIHByb2JhYmlsaXRpZXMgZm9yIG90aGVycywgc28gdGhhdCBpdCBjb250YWlucyBtb3JlIGluZm9ybWF0aW9uIHRoYW4gYSBzaW1wbGUgYWNjdXJhY3kgbWVhc3VyZS4KClRoaXMgcG9zdCBpcyBub3QgYWJvdXQgWEdCb29zdCBhbmQgaG93IHRvIHR1bmUgaXRzIFtoeXBlcnBhcmFtZXRlcnNdKGh0dHBzOi8vZ2l0aHViLmNvbS9kbWxjL3hnYm9vc3QvYmxvYi9tYXN0ZXIvZG9jL3BhcmFtZXRlci5tZCkuIEhvd2V2ZXIsIGEgZmV3IG9ic2VydmF0aW9ucyBmcm9tIHRoZSBhYm92ZSB0cmFpbmluZyBsb2cgZGVzZXJ2ZXMgc29tZSBleHBsYW5hdGlvbi4gSW4gdGhpcyBkYXRhc2V0LCBpdCBpcyBoYXJkIHRvIHBlcmZvcm0gYSBjcm9zcy12YWxpZGF0aW9uIHN3ZWVwIG92ZXIgdGhlIGh5cGVycGFyYW1ldGVycyBvbiBhIGdyaWQsIHNpbmNlIHRyYWluaW5nIGlzIHZlcnkgc2xvdy4gVGh1cywgb25lIHdvdWxkIG5lZWQgdG8gd2F0Y2ggdHJhaW4vdGVzdCBsb3NzIGJ5IHR3ZWFraW5nIHRoZSBoeXBlcnBhcmFtZXRlcnMgYnkgaGFuZC4gQXMgY2FuIGJlIHNlZW4sIGBlYXJseSBzdG9wcGluZ2Aga2lja2VkIGluIGp1c3QgYWZ0ZXIgYWJvdXQgMTAwIHJvdW5kcy4gVGhpcyBtZWFucyB0aGF0IHdlIHN0YXJ0ZWQgdG8gb3ZlcmZpdCByYXRoZXIgcXVpY2tseS4gSW4gdGhpcyBzaXR1YXRpb24sIGl0IHdvdWxkIGJlIHVzZWZ1bCB0byByZWR1Y2UgYGV0YWAgYW5kIGluY3JlYXNlIGBtaW5fY2hpbGRfd2VpZ2h0YC4gSW4gYWRkaXRpb24sIHJlZHVjaW5nIGBzdWJzYW1wbGVgIHdpbGwgaGVscCwgYnV0IHdlIGhhdmUgdG8gbWFrZSBzdXJlIHRoYXQgdGhlcmUgYXJlIGVub3VnaCBgbnJvdW5kc2AgdG8gbGV0IHRoZSB0cmFpbmluZyBsb3NzIHRvIGJlY29tZSBsb3cgZW5vdWdoLiBUaGUgbmV4dCBzdGVwIHdvdWxkIGJlIHRvIHRyeSB0byBpbmNyZWFzZSBgbWF4X2RlcHRoYCB0byBjYXB0dXJlIG1vcmUgY29tcGxleCBwYXR0ZXJucyBpbiBlYWNoIHJvdW5kLCB3aGlsZSBsb3dlcmluZyBgZXRhYCBhbmQgaW5jcmVhc2luZyBgbWluX2NoaWxkX3dlaWdodGAgdG8gcHJldmVudCBvdmVyZml0dGluZy4gSXQgY291bGQgYWxzbyBiZSB1c2VmdWwgdG8gcmVkdWNlIGBjb2xzYW1wbGVfYnl0cmVlYCB3aGljaCB3b3VsZCBoZWxwIHJlZHVjZSBvdmVyZml0dGluZywgc2luY2UgYXQgZWFjaCBzdGVwIGEgZGlmZmVyZW50IHN1YnNldCBvZiBhbGwgZmVhdHVyZXMgd2lsbCBiZSB1c2VkIHRvIGdyb3cgYSB0cmVlLiBBZ2FpbiBpbiBzdWNoIGEgY2FzZSwgb25lIGhhcyB0byBtYWtlIHN1cmUgYG5yb3VuZHNgIGlzIGxhcmdlIGVub3VnaC4gSSB3b3VsZCBub3QgZXhwZWN0IGBnYW1tYWAgaGF2aW5nIG11Y2ggb2YgYSB1c2UgaW4gdGhpcyBjYXNlLCBzaW5jZSB3ZSBkbyBub3Qgc2VlIHRoZSB0ZXN0IGxvc3Mgc2t5cm9ja2V0aW5nIGF0IGFueSBwb2ludC4gIAoKTm93IGxldCdzIGxvb2sgYXQgdGhlIGltcG9ydGFuY2Ugb2YgZWFjaCBmZWF0dXJlIChtZWFzdXJlZCBieSB0aGUgYW1vdW50IHRoZXkgcmVkdWNlIHRoZSBsb3NzIGZ1bmN0aW9uLCBzZWUgW2hlcmVdKGh0dHA6Ly94Z2Jvb3N0LnJlYWR0aGVkb2NzLmlvL2VuL2xhdGVzdC9SLXBhY2thZ2UvZGlzY292ZXJZb3VyRGF0YS5odG1sKSBmb3IgbW9yZSBkZXRhaWxzKQpgYGB7cn0KaW1wb3J0YW5jZSA8LSB4Z2IuaW1wb3J0YW5jZShmZWF0dXJlX25hbWVzID0gWF90ckBEaW1uYW1lc1tbMl1dLCBtb2RlbCA9IGZpdF90cikKaW1wb3J0YW5jZQpgYGAKYW5kIHBsb3QgdGhlIGZpcnN0IDI1IG1vc3QgaW1wb3J0YW50IGZlYXR1cmVzCmBgYHtyfQp4Z2IucGxvdC5pbXBvcnRhbmNlKGltcG9ydGFuY2VfbWF0cml4ID0gaGVhZChpbXBvcnRhbmNlLCAyNSkpCmBgYApBcyBjYW4gYmUgc2VlbiwgdGhlIGNyaW1lIGJ5IGFkZHJlc3MgZmVhdHVyZXMgYXJlIG9uIHRoZSB0b3Agb2YgdGhlIGltcG9ydGFuY2UgbGlzdCEgVGhlIG90aGVyIGZlYXR1cmVzIHRoYXQgbG9vayBpbXBvcnRhbnQgYXJlIGBob3VyYCwgYGlzLmludGVyc2VjdGlvbmAsIGBYYCBhbmQgYFlgLCB3aGljaCBhcmUgZXhwZWN0ZWQgYnkgaW50dWl0aW9uLiBUaGUgaG91ciBhbmQgbG9jYXRpb24gYXQgd2hpY2ggdGhlIGNyaW1lIGhhcHBlbnMgY29ycmVsYXRlIHdpdGggd2hpY2ggY3JpbWUgaXQgaXMuIAoKIyMjIEZpbmFsIHdvcmRzCkluIHRoaXMgcG9zdCB3ZSBoYXZlIHNob3duIGhvdyB0byB3cmFuZ2xlIGNvbHVtbnMgaW4gYSBkYXRhc2V0IHRvIGVuZ2luZWVyIG5ldyBmZWF0dXJlcy4gV2UgaGF2ZSBhbHNvIGRlc2NyaWJlZCBhIG1ldGhvZCB0byBhdm9pZCBwaXRmYWxscyBvZiBvdmVyZml0dGluZyB3aGVuIHRoZSB0YXJnZXQgdmFyaWFibGUgaW4gdGhlIHRyYWluaW5nIGRhdGEgaXMgaW5jb3Jwb3JhdGVkIGluIGZlYXR1cmUgZW5naW5lZXJpbmcuIEFsbCBvZiB0aGUgZGF0YSB3cmFuZ2xpbmcgaXMgbWFkZSBxdWl0ZSBzaW1wbGUgYW5kIGludHVpdGl2ZSBieSB0aGUgVGlkeXZlcnNlIGNvbGxlY3Rpb24gb2YgbGlicmFyaWVzLiBJIHN0cm9uZ2x5IHJlY29tbWVuZCB0aGUgYm9vayBbUiBmb3IgZGF0YSBzY2llbmNlXShodHRwOi8vcjRkcy5oYWQuY28ubnovKSB0byBsZWFybiBtb3JlIGFib3V0IHRoZSB0aGVzZSBmdW5jdGlvbmFsaXRpZXMgYW5kIGZ1cnRoZXIgZXhhbXBsZXMgb2YgdXNhZ2UuIAoK