The aim of this project is to predict a car’s market price using its various characteristics, including body style, engine type and horsepower using the mulivariate K-nearest neighbours algorithm.

#install caret package
Warning messages:
1: In readChar(file, size, TRUE) : truncating string with embedded nuls
2: In readChar(file, size, TRUE) : truncating string with embedded nuls
3: In readChar(file, size, TRUE) : truncating string with embedded nuls
4: In readChar(file, size, TRUE) : truncating string with embedded nuls
5: In readChar(file, size, TRUE) : truncating string with embedded nuls
install.packages('caret')
Error in install.packages : Updating loaded packages
#load libraries

library(readr)
library(stringr)
library(ggplot2)
library(dplyr)
library(purrr)
library(tidyverse)
library(ggplot2)
library(tidyr)
library(broom)
library(caret)

#load raw data
setwd("C:/Users/Ana/Desktop/Data Analytics/CSV Files")
raw_data <- read_csv("cars.csv")
Parsed with column specification:
cols(
  .default = col_character(),
  symboling = col_double(),
  wheel_base = col_double(),
  length = col_double(),
  width = col_double(),
  height = col_double(),
  curb_weight = col_double(),
  engine_size = col_double(),
  compression_ratio = col_double(),
  city_mpg = col_double(),
  highway_mpg = col_double()
)
See spec(...) for full column specifications.
#view top rows of dataframe
head(raw_data)
#remove any columns which contain non-numerical data types and convert the num_doors data to numerical
data_1 <- raw_data %>%
  select(-engine_location, -engine_type, -fuel_system, -make, -fuel_type, - aspiration, -body_style, -drive_wheels) %>%
  mutate(num_doors = if_else(num_doors == "two", 2, 4)) 

#return number of rows in dataframe
nrow(data_1)
[1] 205
#write function to take written numbers and return numeric

v1 <- c("one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten", "eleven", "twelve")
v2 <- c(1:12)

lookup <- as.data.frame(cbind(v1, v2))
lookup <- lookup %>% mutate(v2 = as.numeric(v2))

lookup

convert_to_number <- function(x) {
    lookup$v2[str_detect(lookup$v1, x)]
}
#count NA values in each column
no_nas <- as.data.frame(colSums(is.na(data_2)))
no_nas

#change the column data type to numeric and drop any rows comtaining NA values
data_2 <- data_1 %>%
  mutate(num_cylinders = map_dbl(num_cylinders, convert_to_number),
         bore = as.numeric(bore),
         stroke = as.numeric(stroke),
         horsepower = as.numeric(horsepower),
         price = as.numeric(price),
         peak_rpm = as.numeric(peak_rpm))%>%
  drop_na()
NAs introduced by coercionNAs introduced by coercionNAs introduced by coercionNAs introduced by coercionNAs introduced by coercion
head(data_2, 20)

#count ? entries in `normalised_losses` column
sum(str_detect(data_2$normalized_losses, "\\?"))
[1] 35

There are 35 ‘?’ entries in the normalized_losses column. As there are only 200 entries in total, removing these entries will significantly reduce the size of the dataset. Therefore, remove the whole column instead.

#remove normalised_losses column
data_3 <- data_2 %>%
  select(-normalized_losses)

head(data_3)

Now the data is cleaned and only the columns with numeric values have been kept, we can move on to see which of these variables are correlated to price. This cleaned dataset will be renamed cars.

cars <- data_3

#plot graphs of each variable against price to identify variables which are correlated to price
featurePlot(cars, cars$price)

The following variables appear to have a strong positive correlation with price: -horsepower, curb_weight, num_cylinders, engine_size The following variables appear to have a weak positive correlation with price: -bore, wheel_base, length, width The following variables appear to have a strong negative correlation with price: - city_mpg, highway_mpg The following variables appear to have little to no correlation with price: - peak_rpm, stroke, impression_rate, symboling, num_doors, height

Next, visualise the distribution of price

#plot boxplot of price
ggplot(data = cars,
       aes(x = price))+
  geom_boxplot()

The boxplot of price shows that most of the vehicles are priced between $7500 and $16000. There are some outliers shown on the boxplot but none of these suggest an erronous entry as the most expensive vehicle is under $50,000 which is not an unusual price for a vehicle.

#split the dataset into training and test sets

set.seed(1)

train_indices <- createDataPartition(y = cars[["price"]],
                                     p = 0.8,
                                     list = FALSE)

train_listings <- cars[train_indices,]
test_listings <- cars[-train_indices,]

#check the number of rows in the training and test sets
nrow(train_listings)
[1] 159
nrow(test_listings)
[1] 36
#create grid of k values 1 to 20
knn_grid <- expand.grid(k = 1:20)
knn_grid
#set 5-fold cross validation (i.e. k-fold with 5 folds)
train_control <- trainControl(method = "cv", number = 5)


#run a knn model with normalised data. Include all the variables which appear to have a correlation with price
model <- train(price ~ horsepower + curb_weight + engine_size + num_cylinders + city_mpg + highway_mpg + bore + wheel_base + length + width,
               data = train_listings,
               method = "knn",
               trControl = train_control,
               preProcess = c("center", "scale"),
               tuneGrid = knn_grid)
model
k-Nearest Neighbors 

159 samples
 10 predictor

Pre-processing: centered (10), scaled (10) 
Resampling: Cross-Validated (5 fold) 
Summary of sample sizes: 128, 127, 127, 127, 127 
Resampling results across tuning parameters:

  k   RMSE      Rsquared   MAE     
   1  2974.391  0.8874459  1888.892
   2  2635.996  0.9100430  1838.104
   3  2986.687  0.8926193  1909.449
   4  3286.810  0.8670844  2081.739
   5  3455.385  0.8630038  2166.483
   6  3577.612  0.8522202  2202.450
   7  3761.488  0.8357604  2272.914
   8  3859.429  0.8166946  2320.885
   9  3939.228  0.8140066  2359.872
  10  3876.878  0.8261265  2350.108
  11  3921.719  0.8207215  2354.333
  12  3981.244  0.8206515  2408.287
  13  4035.622  0.8211317  2468.332
  14  4113.682  0.8179072  2496.333
  15  4189.996  0.8150818  2536.305
  16  4263.751  0.8061355  2587.911
  17  4303.379  0.8044902  2604.410
  18  4355.183  0.7999812  2601.270
  19  4405.379  0.7933899  2608.540
  20  4453.594  0.7925094  2633.513

RMSE was used to select the optimal model using the smallest value.
The final value used for the model was k = 2.

plot(model)


model[[4]]$RMSE
 [1] 2974.391 2635.996 2986.687 3286.810 3455.385 3577.612 3761.488 3859.429 3939.228 3876.878 3921.719 3981.244
[13] 4035.622 4113.682 4189.996 4263.751 4303.379 4355.183 4405.379 4453.594

This graph shows that, with the variables included, the number of nearest neighbours to be considered should be 2. This gives a RMSE of $2706

It is difficult to say how well this model works without comparing it to different models i.e. models that use different variables and different numbers of variables. Therefore, look at the models based on individual variables and then based on combinations of variables

#create models based on each variable which is shown to be correlated to price. Identify which of these are good predictors of price with knn

#knn model price ~ horsepower

model_1 <- train(price ~ horsepower,
               data = train_listings,
               method = "knn",
               trControl = train_control,
               preProcess = c("center", "scale"),
               tuneGrid = knn_grid)
  
  horsepower_rmse <- model_1[[4]]$RMSE
  
#knn model price ~ curb_Weight  
  
model_2 <- train(price ~ curb_weight,
               data = train_listings,
               method = "knn",
               trControl = train_control,
               preProcess = c("center", "scale"),
               tuneGrid = knn_grid)
  
  curb_weight_rmse <- model_2[[4]]$RMSE
  
#knn model price ~ engine_size  
  
model_3 <- train(price ~ engine_size,
               data = train_listings,
               method = "knn",
               trControl = train_control,
               preProcess = c("center", "scale"),
               tuneGrid = knn_grid)
  
  engine_size_rmse <- model_3[[4]]$RMSE
  
#knn model price ~ num_cylinders 
  
model_4 <- train(price ~ num_cylinders,
               data = train_listings,
               method = "knn",
               trControl = train_control,
               preProcess = c("center", "scale"),
               tuneGrid = knn_grid)
  
  num_cylinders_rmse <- model_4[[4]]$RMSE
  
#knn model price ~ city_mpg 
  
model_5 <- train(price ~ city_mpg,
               data = train_listings,
               method = "knn",
               trControl = train_control,
               preProcess = c("center", "scale"),
               tuneGrid = knn_grid)
  
  city_mpg_rmse <- model_5[[4]]$RMSE
  
#knn model price ~ highway_mpg 
  
model_6 <- train(price ~ highway_mpg,
               data = train_listings,
               method = "knn",
               trControl = train_control,
               preProcess = c("center", "scale"),
               tuneGrid = knn_grid)
  
  highway_mpg_rmse <- model_6[[4]]$RMSE

#knn model price ~ bore
  
model_7 <- train(price ~ bore,
               data = train_listings,
               method = "knn",
               trControl = train_control,
               preProcess = c("center", "scale"),
               tuneGrid = knn_grid)
  
  bore_rmse <- model_7[[4]]$RMSE
  
#knn model price ~ wheel_base
  
model_8 <- train(price ~ wheel_base,
               data = train_listings,
               method = "knn",
               trControl = train_control,
               preProcess = c("center", "scale"),
               tuneGrid = knn_grid)
  
  wheel_base_rmse <- model_8[[4]]$RMSE
  
#knn model price ~ length
  
model_9 <- train(price ~ length,
               data = train_listings,
               method = "knn",
               trControl = train_control,
               preProcess = c("center", "scale"),
               tuneGrid = knn_grid)
  
  length_rmse <- model_9[[4]]$RMSE
  
#knn model price ~ width
  
model_10 <- train(price ~ width,
               data = train_listings,
               method = "knn",
               trControl = train_control,
               preProcess = c("center", "scale"),
               tuneGrid = knn_grid)
  
  width_rmse <- model_10[[4]]$RMSE
  
#create a dataframe with all the RMSE from each variable from k = 1 to 20.
  
rmse_all <- as.data.frame(cbind(k= 1:20, horsepower_rmse, curb_weight_rmse, engine_size_rmse, num_cylinders_rmse, city_mpg_rmse, highway_mpg_rmse, bore_rmse, wheel_base_rmse, length_rmse, width_rmse))

rmse_all
NA
#in order to plot this, use pivot_longer
rmse_all_longer <- rmse_all %>%
  pivot_longer(cols = horsepower_rmse:width_rmse, names_to = "variable", values_to = "RMSE")
#plot the RMSE from the KNN model from each individual variable on a graph

ggplot(data = rmse_all_longer,
       aes(x = k, y = RMSE, color = variable)) +
    geom_line(lwd = 1) +
  labs(title = "RMSE vs k (no. nearest neighbours) for each variable", x = "k", y = "RMSE") +
      theme(panel.background = element_rect(fill = "white"))

NA

A K-nearest neighbours model based on engine size provides the lowest RMSE. The next best performing variable is city_mpg, then horsepower, highway_mpg, width.

It is possible that the best model is one just based on engine_size. However, it is also possible that a model with combinations of variables performs better.

Next, the following knn models will be run and the RMSE will be compared to identify whether a single variable or a combination of variables works best to predict price.

  1. engine size + city_mpg
  2. engine size + city_mpg + horsepower
  3. engine size + city_mpg + horsepower + highway_mpg
  4. engine size + city_mpg + horsepower + highway_mpg + width
model_C1 <- train(price ~ engine_size + city_mpg,
               data = train_listings,
               method = "knn",
               trControl = train_control,
               preProcess = c("center", "scale"),
               tuneGrid = knn_grid)
  
  comb1 <- model_C1[[4]]$RMSE
  
#knn model price ~ curb_Weight  
  
model_C2 <- train(price ~ engine_size + city_mpg + horsepower,
               data = train_listings,
               method = "knn",
               trControl = train_control,
               preProcess = c("center", "scale"),
               tuneGrid = knn_grid)
  
  comb2 <- model_C2[[4]]$RMSE
  
#knn model price ~ engine_size  
  
model_C3 <- train(price ~ engine_size + city_mpg + horsepower + highway_mpg,
               data = train_listings,
               method = "knn",
               trControl = train_control,
               preProcess = c("center", "scale"),
               tuneGrid = knn_grid)
  
  comb3 <- model_C3[[4]]$RMSE
  
#knn model price ~ num_cylinders 
  
model_C4 <- train(price ~ engine_size + city_mpg + horsepower + highway_mpg + width,
               data = train_listings,
               method = "knn",
               trControl = train_control,
               preProcess = c("center", "scale"),
               tuneGrid = knn_grid)
  
  comb4 <- model_C4[[4]]$RMSE
#create a dataframe with all the RMSE from each variable from k = 1 to 20.
comb_rmse_all <- as.data.frame(cbind(k= 1:20, comb1, comb2, comb3, comb4))

comb_rmse_all
NA
#in order to plot this, use pivot_longer
comb_rmse_all_longer <- comb_rmse_all %>%
  pivot_longer(cols = comb1:comb4, names_to = "variable", values_to = "RMSE")
#plot the RMSE from the KNN model from each individual variable on a graph
ggplot(data = comb_rmse_all_longer,
       aes(x = k, y = RMSE, color = variable)) +
    geom_line(lwd = 1)+
  labs(title = "RMSE vs k (no. nearest neighbours) for each variable combinations", x = "k", y = "RMSE") +
      theme(panel.background = element_rect(fill = "white"))

NA

From this plot, it can be seen that the combination of 4 variables (engine_size + city_mpg + horsepower + highway_mpg + width) with k (number of nearest neighbours) = 2 provides the lowest RMSE

#now use the test data set in the model and obtain the RMSE of the test set. 

predictions <- predict(model_C4, newdata = test_listings)

postResample(pred = predictions, obs = test_listings$price)
        RMSE     Rsquared          MAE 
2751.3648044    0.8132928 1783.8018519 

The RMSE value for the test set of data is $2527 which is lower than in the training set. This is therefore acceptable. However, this still seems like quite a high RMSE, especially for a vehicle which may cost say £7000. There may potentially be more factors which have greater influence over the price which either were not included in this dataset i.e the car year, or were not of numeric type, i.e. the car make and model.

LS0tDQp0aXRsZTogIk1hY2hpbmUgTGVhcm5pbmcgUHJvamVjdDogUHJlZGljdGluZyBDYXIgU2FsZSBQcmljZXMiDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KVGhlIGFpbSBvZiB0aGlzIHByb2plY3QgaXMgdG8gcHJlZGljdCBhIGNhcidzIG1hcmtldCBwcmljZSB1c2luZyBpdHMgdmFyaW91cyBjaGFyYWN0ZXJpc3RpY3MsIGluY2x1ZGluZyBib2R5IHN0eWxlLCBlbmdpbmUgdHlwZSBhbmQgaG9yc2Vwb3dlciB1c2luZyB0aGUgbXVsaXZhcmlhdGUgSy1uZWFyZXN0IG5laWdoYm91cnMgYWxnb3JpdGhtLg0KYGBge3J9DQojaW5zdGFsbCBjYXJldCBwYWNrYWdlDQppbnN0YWxsLnBhY2thZ2VzKCdjYXJldCcpDQpgYGANCg0KDQpgYGB7cn0NCiNsb2FkIGxpYnJhcmllcw0KDQpsaWJyYXJ5KHJlYWRyKQ0KbGlicmFyeShzdHJpbmdyKQ0KbGlicmFyeShnZ3Bsb3QyKQ0KbGlicmFyeShkcGx5cikNCmxpYnJhcnkocHVycnIpDQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkoZ2dwbG90MikNCmxpYnJhcnkodGlkeXIpDQpsaWJyYXJ5KGJyb29tKQ0KbGlicmFyeShjYXJldCkNCg0KI2xvYWQgcmF3IGRhdGENCnNldHdkKCJDOi9Vc2Vycy9BbmEvRGVza3RvcC9EYXRhIEFuYWx5dGljcy9DU1YgRmlsZXMiKQ0KcmF3X2RhdGEgPC0gcmVhZF9jc3YoImNhcnMuY3N2IikNCg0KI3ZpZXcgdG9wIHJvd3Mgb2YgZGF0YWZyYW1lDQpoZWFkKHJhd19kYXRhKQ0KYGBgDQpgYGB7cn0NCiNyZW1vdmUgYW55IGNvbHVtbnMgd2hpY2ggY29udGFpbiBub24tbnVtZXJpY2FsIGRhdGEgdHlwZXMgYW5kIGNvbnZlcnQgdGhlIG51bV9kb29ycyBkYXRhIHRvIG51bWVyaWNhbA0KZGF0YV8xIDwtIHJhd19kYXRhICU+JQ0KICBzZWxlY3QoLWVuZ2luZV9sb2NhdGlvbiwgLWVuZ2luZV90eXBlLCAtZnVlbF9zeXN0ZW0sIC1tYWtlLCAtZnVlbF90eXBlLCAtIGFzcGlyYXRpb24sIC1ib2R5X3N0eWxlLCAtZHJpdmVfd2hlZWxzKSAlPiUNCiAgbXV0YXRlKG51bV9kb29ycyA9IGlmX2Vsc2UobnVtX2Rvb3JzID09ICJ0d28iLCAyLCA0KSkgDQoNCiNyZXR1cm4gbnVtYmVyIG9mIHJvd3MgaW4gZGF0YWZyYW1lDQpucm93KGRhdGFfMSkNCmBgYA0KYGBge3J9DQojd3JpdGUgZnVuY3Rpb24gdG8gdGFrZSB3cml0dGVuIG51bWJlcnMgYW5kIHJldHVybiBudW1lcmljDQoNCnYxIDwtIGMoIm9uZSIsICJ0d28iLCAidGhyZWUiLCAiZm91ciIsICJmaXZlIiwgInNpeCIsICJzZXZlbiIsICJlaWdodCIsICJuaW5lIiwgInRlbiIsICJlbGV2ZW4iLCAidHdlbHZlIikNCnYyIDwtIGMoMToxMikNCg0KbG9va3VwIDwtIGFzLmRhdGEuZnJhbWUoY2JpbmQodjEsIHYyKSkNCmxvb2t1cCA8LSBsb29rdXAgJT4lIG11dGF0ZSh2MiA9IGFzLm51bWVyaWModjIpKQ0KDQpsb29rdXANCg0KY29udmVydF90b19udW1iZXIgPC0gZnVuY3Rpb24oeCkgew0KICAgIGxvb2t1cCR2MltzdHJfZGV0ZWN0KGxvb2t1cCR2MSwgeCldDQp9DQpgYGANCg0KDQpgYGB7cn0NCiNjb3VudCBOQSB2YWx1ZXMgaW4gZWFjaCBjb2x1bW4NCm5vX25hcyA8LSBhcy5kYXRhLmZyYW1lKGNvbFN1bXMoaXMubmEoZGF0YV8yKSkpDQpub19uYXMNCg0KI2NoYW5nZSB0aGUgY29sdW1uIGRhdGEgdHlwZSB0byBudW1lcmljIGFuZCBkcm9wIGFueSByb3dzIGNvbXRhaW5pbmcgTkEgdmFsdWVzDQpkYXRhXzIgPC0gZGF0YV8xICU+JQ0KICBtdXRhdGUobnVtX2N5bGluZGVycyA9IG1hcF9kYmwobnVtX2N5bGluZGVycywgY29udmVydF90b19udW1iZXIpLA0KICAgICAgICAgYm9yZSA9IGFzLm51bWVyaWMoYm9yZSksDQogICAgICAgICBzdHJva2UgPSBhcy5udW1lcmljKHN0cm9rZSksDQogICAgICAgICBob3JzZXBvd2VyID0gYXMubnVtZXJpYyhob3JzZXBvd2VyKSwNCiAgICAgICAgIHByaWNlID0gYXMubnVtZXJpYyhwcmljZSksDQogICAgICAgICBwZWFrX3JwbSA9IGFzLm51bWVyaWMocGVha19ycG0pKSU+JQ0KICBkcm9wX25hKCkNCg0KaGVhZChkYXRhXzIsIDIwKQ0KDQojY291bnQgPyBlbnRyaWVzIGluIGBub3JtYWxpc2VkX2xvc3Nlc2AgY29sdW1uDQpzdW0oc3RyX2RldGVjdChkYXRhXzIkbm9ybWFsaXplZF9sb3NzZXMsICJcXD8iKSkNCg0KYGBgDQpUaGVyZSBhcmUgMzUgJz8nIGVudHJpZXMgaW4gdGhlIGBub3JtYWxpemVkX2xvc3Nlc2AgY29sdW1uLiBBcyB0aGVyZSBhcmUgb25seSAyMDAgZW50cmllcyBpbiB0b3RhbCwgcmVtb3ZpbmcgdGhlc2UgZW50cmllcyB3aWxsIHNpZ25pZmljYW50bHkgcmVkdWNlIHRoZSBzaXplIG9mIHRoZSBkYXRhc2V0LiBUaGVyZWZvcmUsIHJlbW92ZSB0aGUgd2hvbGUgY29sdW1uIGluc3RlYWQuIA0KDQpgYGB7cn0NCiNyZW1vdmUgbm9ybWFsaXNlZF9sb3NzZXMgY29sdW1uDQpkYXRhXzMgPC0gZGF0YV8yICU+JQ0KICBzZWxlY3QoLW5vcm1hbGl6ZWRfbG9zc2VzKQ0KDQpoZWFkKGRhdGFfMykNCmBgYA0KTm93IHRoZSBkYXRhIGlzIGNsZWFuZWQgYW5kIG9ubHkgdGhlIGNvbHVtbnMgd2l0aCBudW1lcmljIHZhbHVlcyBoYXZlIGJlZW4ga2VwdCwgd2UgY2FuIG1vdmUgb24gdG8gc2VlIHdoaWNoIG9mIHRoZXNlIHZhcmlhYmxlcyBhcmUgY29ycmVsYXRlZCB0byBwcmljZS4gVGhpcyBjbGVhbmVkIGRhdGFzZXQgd2lsbCBiZSByZW5hbWVkIGBjYXJzYC4NCmBgYHtyfQ0KY2FycyA8LSBkYXRhXzMNCg0KI3Bsb3QgZ3JhcGhzIG9mIGVhY2ggdmFyaWFibGUgYWdhaW5zdCBwcmljZSB0byBpZGVudGlmeSB2YXJpYWJsZXMgd2hpY2ggYXJlIGNvcnJlbGF0ZWQgdG8gcHJpY2UNCmZlYXR1cmVQbG90KGNhcnMsIGNhcnMkcHJpY2UpDQpgYGANClRoZSBmb2xsb3dpbmcgdmFyaWFibGVzIGFwcGVhciB0byBoYXZlIGEgc3Ryb25nIHBvc2l0aXZlIGNvcnJlbGF0aW9uIHdpdGggcHJpY2U6DQogIC1ob3JzZXBvd2VyLCBjdXJiX3dlaWdodCwgbnVtX2N5bGluZGVycywgZW5naW5lX3NpemUNClRoZSBmb2xsb3dpbmcgdmFyaWFibGVzIGFwcGVhciB0byBoYXZlIGEgd2VhayBwb3NpdGl2ZSBjb3JyZWxhdGlvbiB3aXRoIHByaWNlOg0KICAtYm9yZSwgd2hlZWxfYmFzZSwgbGVuZ3RoLCB3aWR0aA0KVGhlIGZvbGxvd2luZyB2YXJpYWJsZXMgYXBwZWFyIHRvIGhhdmUgYSBzdHJvbmcgbmVnYXRpdmUgY29ycmVsYXRpb24gd2l0aCBwcmljZToNCiAgLSBjaXR5X21wZywgaGlnaHdheV9tcGcNClRoZSBmb2xsb3dpbmcgdmFyaWFibGVzIGFwcGVhciB0byBoYXZlIGxpdHRsZSB0byBubyBjb3JyZWxhdGlvbiB3aXRoIHByaWNlOg0KICAtIHBlYWtfcnBtLCBzdHJva2UsIGltcHJlc3Npb25fcmF0ZSwgc3ltYm9saW5nLCBudW1fZG9vcnMsIGhlaWdodA0KICANCk5leHQsIHZpc3VhbGlzZSB0aGUgZGlzdHJpYnV0aW9uIG9mIHByaWNlDQoNCmBgYHtyfQ0KI3Bsb3QgYm94cGxvdCBvZiBwcmljZQ0KZ2dwbG90KGRhdGEgPSBjYXJzLA0KICAgICAgIGFlcyh4ID0gcHJpY2UpKSsNCiAgZ2VvbV9ib3hwbG90KCkNCmBgYA0KVGhlIGJveHBsb3Qgb2YgcHJpY2Ugc2hvd3MgdGhhdCBtb3N0IG9mIHRoZSB2ZWhpY2xlcyBhcmUgcHJpY2VkIGJldHdlZW4gJDc1MDAgYW5kICQxNjAwMC4gVGhlcmUgYXJlIHNvbWUgb3V0bGllcnMgc2hvd24gb24gdGhlIGJveHBsb3QgYnV0IG5vbmUgb2YgdGhlc2Ugc3VnZ2VzdCBhbiBlcnJvbm91cyBlbnRyeSBhcyB0aGUgbW9zdCBleHBlbnNpdmUgdmVoaWNsZSBpcyB1bmRlciAkNTAsMDAwIHdoaWNoIGlzIG5vdCBhbiB1bnVzdWFsIHByaWNlIGZvciBhIHZlaGljbGUuIA0KYGBge3J9DQojc3BsaXQgdGhlIGRhdGFzZXQgaW50byB0cmFpbmluZyBhbmQgdGVzdCBzZXRzDQoNCnNldC5zZWVkKDEpDQoNCnRyYWluX2luZGljZXMgPC0gY3JlYXRlRGF0YVBhcnRpdGlvbih5ID0gY2Fyc1tbInByaWNlIl1dLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHAgPSAwLjgsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGlzdCA9IEZBTFNFKQ0KDQp0cmFpbl9saXN0aW5ncyA8LSBjYXJzW3RyYWluX2luZGljZXMsXQ0KdGVzdF9saXN0aW5ncyA8LSBjYXJzWy10cmFpbl9pbmRpY2VzLF0NCg0KI2NoZWNrIHRoZSBudW1iZXIgb2Ygcm93cyBpbiB0aGUgdHJhaW5pbmcgYW5kIHRlc3Qgc2V0cw0KbnJvdyh0cmFpbl9saXN0aW5ncykNCm5yb3codGVzdF9saXN0aW5ncykNCg0KI2NyZWF0ZSBncmlkIG9mIGsgdmFsdWVzIDEgdG8gMjANCmtubl9ncmlkIDwtIGV4cGFuZC5ncmlkKGsgPSAxOjIwKQ0Ka25uX2dyaWQNCmBgYA0KDQoNCmBgYHtyfQ0KI3NldCA1LWZvbGQgY3Jvc3MgdmFsaWRhdGlvbiAoaS5lLiBrLWZvbGQgd2l0aCA1IGZvbGRzKQ0KdHJhaW5fY29udHJvbCA8LSB0cmFpbkNvbnRyb2wobWV0aG9kID0gImN2IiwgbnVtYmVyID0gNSkNCg0KDQojcnVuIGEga25uIG1vZGVsIHdpdGggbm9ybWFsaXNlZCBkYXRhLiBJbmNsdWRlIGFsbCB0aGUgdmFyaWFibGVzIHdoaWNoIGFwcGVhciB0byBoYXZlIGEgY29ycmVsYXRpb24gd2l0aCBwcmljZQ0KbW9kZWwgPC0gdHJhaW4ocHJpY2UgfiBob3JzZXBvd2VyICsgY3VyYl93ZWlnaHQgKyBlbmdpbmVfc2l6ZSArIG51bV9jeWxpbmRlcnMgKyBjaXR5X21wZyArIGhpZ2h3YXlfbXBnICsgYm9yZSArIHdoZWVsX2Jhc2UgKyBsZW5ndGggKyB3aWR0aCwNCiAgICAgICAgICAgICAgIGRhdGEgPSB0cmFpbl9saXN0aW5ncywNCiAgICAgICAgICAgICAgIG1ldGhvZCA9ICJrbm4iLA0KICAgICAgICAgICAgICAgdHJDb250cm9sID0gdHJhaW5fY29udHJvbCwNCiAgICAgICAgICAgICAgIHByZVByb2Nlc3MgPSBjKCJjZW50ZXIiLCAic2NhbGUiKSwNCiAgICAgICAgICAgICAgIHR1bmVHcmlkID0ga25uX2dyaWQpDQoNCmBgYA0KDQpgYGB7cn0NCm1vZGVsDQoNCmBgYA0KDQpgYGB7cn0NCg0KcGxvdChtb2RlbCkNCg0KbW9kZWxbWzRdXSRSTVNFDQoNCmBgYA0KVGhpcyBncmFwaCBzaG93cyB0aGF0LCB3aXRoIHRoZSB2YXJpYWJsZXMgaW5jbHVkZWQsIHRoZSBudW1iZXIgb2YgbmVhcmVzdCBuZWlnaGJvdXJzIHRvIGJlIGNvbnNpZGVyZWQgc2hvdWxkIGJlIDIuIFRoaXMgZ2l2ZXMgYSBSTVNFIG9mICQyNzA2DQoNCkl0IGlzIGRpZmZpY3VsdCB0byBzYXkgaG93IHdlbGwgdGhpcyBtb2RlbCB3b3JrcyB3aXRob3V0IGNvbXBhcmluZyBpdCB0byBkaWZmZXJlbnQgbW9kZWxzIGkuZS4gbW9kZWxzIHRoYXQgdXNlIGRpZmZlcmVudCB2YXJpYWJsZXMgYW5kIGRpZmZlcmVudCBudW1iZXJzIG9mIHZhcmlhYmxlcy4gVGhlcmVmb3JlLCBsb29rIGF0IHRoZSBtb2RlbHMgYmFzZWQgb24gaW5kaXZpZHVhbCB2YXJpYWJsZXMgYW5kIHRoZW4gYmFzZWQgb24gY29tYmluYXRpb25zIG9mIHZhcmlhYmxlcw0KYGBge3J9DQojY3JlYXRlIG1vZGVscyBiYXNlZCBvbiBlYWNoIHZhcmlhYmxlIHdoaWNoIGlzIHNob3duIHRvIGJlIGNvcnJlbGF0ZWQgdG8gcHJpY2UuIElkZW50aWZ5IHdoaWNoIG9mIHRoZXNlIGFyZSBnb29kIHByZWRpY3RvcnMgb2YgcHJpY2Ugd2l0aCBrbm4NCg0KI2tubiBtb2RlbCBwcmljZSB+IGhvcnNlcG93ZXINCg0KbW9kZWxfMSA8LSB0cmFpbihwcmljZSB+IGhvcnNlcG93ZXIsDQogICAgICAgICAgICAgICBkYXRhID0gdHJhaW5fbGlzdGluZ3MsDQogICAgICAgICAgICAgICBtZXRob2QgPSAia25uIiwNCiAgICAgICAgICAgICAgIHRyQ29udHJvbCA9IHRyYWluX2NvbnRyb2wsDQogICAgICAgICAgICAgICBwcmVQcm9jZXNzID0gYygiY2VudGVyIiwgInNjYWxlIiksDQogICAgICAgICAgICAgICB0dW5lR3JpZCA9IGtubl9ncmlkKQ0KICANCiAgaG9yc2Vwb3dlcl9ybXNlIDwtIG1vZGVsXzFbWzRdXSRSTVNFDQogIA0KI2tubiBtb2RlbCBwcmljZSB+IGN1cmJfV2VpZ2h0ICANCiAgDQptb2RlbF8yIDwtIHRyYWluKHByaWNlIH4gY3VyYl93ZWlnaHQsDQogICAgICAgICAgICAgICBkYXRhID0gdHJhaW5fbGlzdGluZ3MsDQogICAgICAgICAgICAgICBtZXRob2QgPSAia25uIiwNCiAgICAgICAgICAgICAgIHRyQ29udHJvbCA9IHRyYWluX2NvbnRyb2wsDQogICAgICAgICAgICAgICBwcmVQcm9jZXNzID0gYygiY2VudGVyIiwgInNjYWxlIiksDQogICAgICAgICAgICAgICB0dW5lR3JpZCA9IGtubl9ncmlkKQ0KICANCiAgY3VyYl93ZWlnaHRfcm1zZSA8LSBtb2RlbF8yW1s0XV0kUk1TRQ0KICANCiNrbm4gbW9kZWwgcHJpY2UgfiBlbmdpbmVfc2l6ZSAgDQogIA0KbW9kZWxfMyA8LSB0cmFpbihwcmljZSB+IGVuZ2luZV9zaXplLA0KICAgICAgICAgICAgICAgZGF0YSA9IHRyYWluX2xpc3RpbmdzLA0KICAgICAgICAgICAgICAgbWV0aG9kID0gImtubiIsDQogICAgICAgICAgICAgICB0ckNvbnRyb2wgPSB0cmFpbl9jb250cm9sLA0KICAgICAgICAgICAgICAgcHJlUHJvY2VzcyA9IGMoImNlbnRlciIsICJzY2FsZSIpLA0KICAgICAgICAgICAgICAgdHVuZUdyaWQgPSBrbm5fZ3JpZCkNCiAgDQogIGVuZ2luZV9zaXplX3Jtc2UgPC0gbW9kZWxfM1tbNF1dJFJNU0UNCiAgDQoja25uIG1vZGVsIHByaWNlIH4gbnVtX2N5bGluZGVycyANCiAgDQptb2RlbF80IDwtIHRyYWluKHByaWNlIH4gbnVtX2N5bGluZGVycywNCiAgICAgICAgICAgICAgIGRhdGEgPSB0cmFpbl9saXN0aW5ncywNCiAgICAgICAgICAgICAgIG1ldGhvZCA9ICJrbm4iLA0KICAgICAgICAgICAgICAgdHJDb250cm9sID0gdHJhaW5fY29udHJvbCwNCiAgICAgICAgICAgICAgIHByZVByb2Nlc3MgPSBjKCJjZW50ZXIiLCAic2NhbGUiKSwNCiAgICAgICAgICAgICAgIHR1bmVHcmlkID0ga25uX2dyaWQpDQogIA0KICBudW1fY3lsaW5kZXJzX3Jtc2UgPC0gbW9kZWxfNFtbNF1dJFJNU0UNCiAgDQoja25uIG1vZGVsIHByaWNlIH4gY2l0eV9tcGcgDQogIA0KbW9kZWxfNSA8LSB0cmFpbihwcmljZSB+IGNpdHlfbXBnLA0KICAgICAgICAgICAgICAgZGF0YSA9IHRyYWluX2xpc3RpbmdzLA0KICAgICAgICAgICAgICAgbWV0aG9kID0gImtubiIsDQogICAgICAgICAgICAgICB0ckNvbnRyb2wgPSB0cmFpbl9jb250cm9sLA0KICAgICAgICAgICAgICAgcHJlUHJvY2VzcyA9IGMoImNlbnRlciIsICJzY2FsZSIpLA0KICAgICAgICAgICAgICAgdHVuZUdyaWQgPSBrbm5fZ3JpZCkNCiAgDQogIGNpdHlfbXBnX3Jtc2UgPC0gbW9kZWxfNVtbNF1dJFJNU0UNCiAgDQoja25uIG1vZGVsIHByaWNlIH4gaGlnaHdheV9tcGcgDQogIA0KbW9kZWxfNiA8LSB0cmFpbihwcmljZSB+IGhpZ2h3YXlfbXBnLA0KICAgICAgICAgICAgICAgZGF0YSA9IHRyYWluX2xpc3RpbmdzLA0KICAgICAgICAgICAgICAgbWV0aG9kID0gImtubiIsDQogICAgICAgICAgICAgICB0ckNvbnRyb2wgPSB0cmFpbl9jb250cm9sLA0KICAgICAgICAgICAgICAgcHJlUHJvY2VzcyA9IGMoImNlbnRlciIsICJzY2FsZSIpLA0KICAgICAgICAgICAgICAgdHVuZUdyaWQgPSBrbm5fZ3JpZCkNCiAgDQogIGhpZ2h3YXlfbXBnX3Jtc2UgPC0gbW9kZWxfNltbNF1dJFJNU0UNCg0KI2tubiBtb2RlbCBwcmljZSB+IGJvcmUNCiAgDQptb2RlbF83IDwtIHRyYWluKHByaWNlIH4gYm9yZSwNCiAgICAgICAgICAgICAgIGRhdGEgPSB0cmFpbl9saXN0aW5ncywNCiAgICAgICAgICAgICAgIG1ldGhvZCA9ICJrbm4iLA0KICAgICAgICAgICAgICAgdHJDb250cm9sID0gdHJhaW5fY29udHJvbCwNCiAgICAgICAgICAgICAgIHByZVByb2Nlc3MgPSBjKCJjZW50ZXIiLCAic2NhbGUiKSwNCiAgICAgICAgICAgICAgIHR1bmVHcmlkID0ga25uX2dyaWQpDQogIA0KICBib3JlX3Jtc2UgPC0gbW9kZWxfN1tbNF1dJFJNU0UNCiAgDQoja25uIG1vZGVsIHByaWNlIH4gd2hlZWxfYmFzZQ0KICANCm1vZGVsXzggPC0gdHJhaW4ocHJpY2UgfiB3aGVlbF9iYXNlLA0KICAgICAgICAgICAgICAgZGF0YSA9IHRyYWluX2xpc3RpbmdzLA0KICAgICAgICAgICAgICAgbWV0aG9kID0gImtubiIsDQogICAgICAgICAgICAgICB0ckNvbnRyb2wgPSB0cmFpbl9jb250cm9sLA0KICAgICAgICAgICAgICAgcHJlUHJvY2VzcyA9IGMoImNlbnRlciIsICJzY2FsZSIpLA0KICAgICAgICAgICAgICAgdHVuZUdyaWQgPSBrbm5fZ3JpZCkNCiAgDQogIHdoZWVsX2Jhc2Vfcm1zZSA8LSBtb2RlbF84W1s0XV0kUk1TRQ0KICANCiNrbm4gbW9kZWwgcHJpY2UgfiBsZW5ndGgNCiAgDQptb2RlbF85IDwtIHRyYWluKHByaWNlIH4gbGVuZ3RoLA0KICAgICAgICAgICAgICAgZGF0YSA9IHRyYWluX2xpc3RpbmdzLA0KICAgICAgICAgICAgICAgbWV0aG9kID0gImtubiIsDQogICAgICAgICAgICAgICB0ckNvbnRyb2wgPSB0cmFpbl9jb250cm9sLA0KICAgICAgICAgICAgICAgcHJlUHJvY2VzcyA9IGMoImNlbnRlciIsICJzY2FsZSIpLA0KICAgICAgICAgICAgICAgdHVuZUdyaWQgPSBrbm5fZ3JpZCkNCiAgDQogIGxlbmd0aF9ybXNlIDwtIG1vZGVsXzlbWzRdXSRSTVNFDQogIA0KI2tubiBtb2RlbCBwcmljZSB+IHdpZHRoDQogIA0KbW9kZWxfMTAgPC0gdHJhaW4ocHJpY2UgfiB3aWR0aCwNCiAgICAgICAgICAgICAgIGRhdGEgPSB0cmFpbl9saXN0aW5ncywNCiAgICAgICAgICAgICAgIG1ldGhvZCA9ICJrbm4iLA0KICAgICAgICAgICAgICAgdHJDb250cm9sID0gdHJhaW5fY29udHJvbCwNCiAgICAgICAgICAgICAgIHByZVByb2Nlc3MgPSBjKCJjZW50ZXIiLCAic2NhbGUiKSwNCiAgICAgICAgICAgICAgIHR1bmVHcmlkID0ga25uX2dyaWQpDQogIA0KICB3aWR0aF9ybXNlIDwtIG1vZGVsXzEwW1s0XV0kUk1TRQ0KICANCmBgYA0KDQoNCmBgYHtyfQ0KI2NyZWF0ZSBhIGRhdGFmcmFtZSB3aXRoIGFsbCB0aGUgUk1TRSBmcm9tIGVhY2ggdmFyaWFibGUgZnJvbSBrID0gMSB0byAyMC4NCiAgDQpybXNlX2FsbCA8LSBhcy5kYXRhLmZyYW1lKGNiaW5kKGs9IDE6MjAsIGhvcnNlcG93ZXJfcm1zZSwgY3VyYl93ZWlnaHRfcm1zZSwgZW5naW5lX3NpemVfcm1zZSwgbnVtX2N5bGluZGVyc19ybXNlLCBjaXR5X21wZ19ybXNlLCBoaWdod2F5X21wZ19ybXNlLCBib3JlX3Jtc2UsIHdoZWVsX2Jhc2Vfcm1zZSwgbGVuZ3RoX3Jtc2UsIHdpZHRoX3Jtc2UpKQ0KDQpybXNlX2FsbA0KDQpgYGANCg0KYGBge3J9DQojaW4gb3JkZXIgdG8gcGxvdCB0aGlzLCB1c2UgcGl2b3RfbG9uZ2VyDQpybXNlX2FsbF9sb25nZXIgPC0gcm1zZV9hbGwgJT4lDQogIHBpdm90X2xvbmdlcihjb2xzID0gaG9yc2Vwb3dlcl9ybXNlOndpZHRoX3Jtc2UsIG5hbWVzX3RvID0gInZhcmlhYmxlIiwgdmFsdWVzX3RvID0gIlJNU0UiKQ0KDQpgYGANCg0KYGBge3J9DQojcGxvdCB0aGUgUk1TRSBmcm9tIHRoZSBLTk4gbW9kZWwgZnJvbSBlYWNoIGluZGl2aWR1YWwgdmFyaWFibGUgb24gYSBncmFwaA0KDQpnZ3Bsb3QoZGF0YSA9IHJtc2VfYWxsX2xvbmdlciwNCiAgICAgICBhZXMoeCA9IGssIHkgPSBSTVNFLCBjb2xvciA9IHZhcmlhYmxlKSkgKw0KICAgIGdlb21fbGluZShsd2QgPSAxKSArDQogIGxhYnModGl0bGUgPSAiUk1TRSB2cyBrIChuby4gbmVhcmVzdCBuZWlnaGJvdXJzKSBmb3IgZWFjaCB2YXJpYWJsZSIsIHggPSAiayIsIHkgPSAiUk1TRSIpICsNCiAgICAgIHRoZW1lKHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoZmlsbCA9ICJ3aGl0ZSIpKQ0KICANCmBgYA0KQSBLLW5lYXJlc3QgbmVpZ2hib3VycyBtb2RlbCBiYXNlZCBvbiBlbmdpbmUgc2l6ZSBwcm92aWRlcyB0aGUgbG93ZXN0IFJNU0UuIA0KVGhlIG5leHQgYmVzdCBwZXJmb3JtaW5nIHZhcmlhYmxlIGlzIGNpdHlfbXBnLCB0aGVuIGhvcnNlcG93ZXIsIGhpZ2h3YXlfbXBnLCB3aWR0aC4NCg0KSXQgaXMgcG9zc2libGUgdGhhdCB0aGUgYmVzdCBtb2RlbCBpcyBvbmUganVzdCBiYXNlZCBvbiBlbmdpbmVfc2l6ZS4gSG93ZXZlciwgaXQgaXMgYWxzbyBwb3NzaWJsZSB0aGF0IGEgbW9kZWwgd2l0aCBjb21iaW5hdGlvbnMgb2YgdmFyaWFibGVzIHBlcmZvcm1zIGJldHRlci4NCg0KTmV4dCwgdGhlIGZvbGxvd2luZyBrbm4gbW9kZWxzIHdpbGwgYmUgcnVuIGFuZCB0aGUgUk1TRSB3aWxsIGJlIGNvbXBhcmVkIHRvIGlkZW50aWZ5IHdoZXRoZXIgYSBzaW5nbGUgdmFyaWFibGUgb3IgYSBjb21iaW5hdGlvbiBvZiB2YXJpYWJsZXMgd29ya3MgYmVzdCB0byBwcmVkaWN0IHByaWNlLg0KDQoxLiBlbmdpbmUgc2l6ZSArIGNpdHlfbXBnDQoyLiBlbmdpbmUgc2l6ZSArIGNpdHlfbXBnICsgaG9yc2Vwb3dlcg0KMy4gZW5naW5lIHNpemUgKyBjaXR5X21wZyArIGhvcnNlcG93ZXIgKyBoaWdod2F5X21wZw0KNC4gZW5naW5lIHNpemUgKyBjaXR5X21wZyArIGhvcnNlcG93ZXIgKyBoaWdod2F5X21wZyArIHdpZHRoDQoNCmBgYHtyfQ0KbW9kZWxfQzEgPC0gdHJhaW4ocHJpY2UgfiBlbmdpbmVfc2l6ZSArIGNpdHlfbXBnLA0KICAgICAgICAgICAgICAgZGF0YSA9IHRyYWluX2xpc3RpbmdzLA0KICAgICAgICAgICAgICAgbWV0aG9kID0gImtubiIsDQogICAgICAgICAgICAgICB0ckNvbnRyb2wgPSB0cmFpbl9jb250cm9sLA0KICAgICAgICAgICAgICAgcHJlUHJvY2VzcyA9IGMoImNlbnRlciIsICJzY2FsZSIpLA0KICAgICAgICAgICAgICAgdHVuZUdyaWQgPSBrbm5fZ3JpZCkNCiAgDQogIGNvbWIxIDwtIG1vZGVsX0MxW1s0XV0kUk1TRQ0KICANCiNrbm4gbW9kZWwgcHJpY2UgfiBjdXJiX1dlaWdodCAgDQogIA0KbW9kZWxfQzIgPC0gdHJhaW4ocHJpY2UgfiBlbmdpbmVfc2l6ZSArIGNpdHlfbXBnICsgaG9yc2Vwb3dlciwNCiAgICAgICAgICAgICAgIGRhdGEgPSB0cmFpbl9saXN0aW5ncywNCiAgICAgICAgICAgICAgIG1ldGhvZCA9ICJrbm4iLA0KICAgICAgICAgICAgICAgdHJDb250cm9sID0gdHJhaW5fY29udHJvbCwNCiAgICAgICAgICAgICAgIHByZVByb2Nlc3MgPSBjKCJjZW50ZXIiLCAic2NhbGUiKSwNCiAgICAgICAgICAgICAgIHR1bmVHcmlkID0ga25uX2dyaWQpDQogIA0KICBjb21iMiA8LSBtb2RlbF9DMltbNF1dJFJNU0UNCiAgDQoja25uIG1vZGVsIHByaWNlIH4gZW5naW5lX3NpemUgIA0KICANCm1vZGVsX0MzIDwtIHRyYWluKHByaWNlIH4gZW5naW5lX3NpemUgKyBjaXR5X21wZyArIGhvcnNlcG93ZXIgKyBoaWdod2F5X21wZywNCiAgICAgICAgICAgICAgIGRhdGEgPSB0cmFpbl9saXN0aW5ncywNCiAgICAgICAgICAgICAgIG1ldGhvZCA9ICJrbm4iLA0KICAgICAgICAgICAgICAgdHJDb250cm9sID0gdHJhaW5fY29udHJvbCwNCiAgICAgICAgICAgICAgIHByZVByb2Nlc3MgPSBjKCJjZW50ZXIiLCAic2NhbGUiKSwNCiAgICAgICAgICAgICAgIHR1bmVHcmlkID0ga25uX2dyaWQpDQogIA0KICBjb21iMyA8LSBtb2RlbF9DM1tbNF1dJFJNU0UNCiAgDQoja25uIG1vZGVsIHByaWNlIH4gbnVtX2N5bGluZGVycyANCiAgDQptb2RlbF9DNCA8LSB0cmFpbihwcmljZSB+IGVuZ2luZV9zaXplICsgY2l0eV9tcGcgKyBob3JzZXBvd2VyICsgaGlnaHdheV9tcGcgKyB3aWR0aCwNCiAgICAgICAgICAgICAgIGRhdGEgPSB0cmFpbl9saXN0aW5ncywNCiAgICAgICAgICAgICAgIG1ldGhvZCA9ICJrbm4iLA0KICAgICAgICAgICAgICAgdHJDb250cm9sID0gdHJhaW5fY29udHJvbCwNCiAgICAgICAgICAgICAgIHByZVByb2Nlc3MgPSBjKCJjZW50ZXIiLCAic2NhbGUiKSwNCiAgICAgICAgICAgICAgIHR1bmVHcmlkID0ga25uX2dyaWQpDQogIA0KICBjb21iNCA8LSBtb2RlbF9DNFtbNF1dJFJNU0UNCmBgYA0KDQpgYGB7cn0NCiNjcmVhdGUgYSBkYXRhZnJhbWUgd2l0aCBhbGwgdGhlIFJNU0UgZnJvbSBlYWNoIHZhcmlhYmxlIGZyb20gayA9IDEgdG8gMjAuDQpjb21iX3Jtc2VfYWxsIDwtIGFzLmRhdGEuZnJhbWUoY2JpbmQoaz0gMToyMCwgY29tYjEsIGNvbWIyLCBjb21iMywgY29tYjQpKQ0KDQpjb21iX3Jtc2VfYWxsDQoNCmBgYA0KYGBge3J9DQojaW4gb3JkZXIgdG8gcGxvdCB0aGlzLCB1c2UgcGl2b3RfbG9uZ2VyDQpjb21iX3Jtc2VfYWxsX2xvbmdlciA8LSBjb21iX3Jtc2VfYWxsICU+JQ0KICBwaXZvdF9sb25nZXIoY29scyA9IGNvbWIxOmNvbWI0LCBuYW1lc190byA9ICJ2YXJpYWJsZSIsIHZhbHVlc190byA9ICJSTVNFIikNCg0KYGBgDQpgYGB7cn0NCiNwbG90IHRoZSBSTVNFIGZyb20gdGhlIEtOTiBtb2RlbCBmcm9tIGVhY2ggaW5kaXZpZHVhbCB2YXJpYWJsZSBvbiBhIGdyYXBoDQpnZ3Bsb3QoZGF0YSA9IGNvbWJfcm1zZV9hbGxfbG9uZ2VyLA0KICAgICAgIGFlcyh4ID0gaywgeSA9IFJNU0UsIGNvbG9yID0gdmFyaWFibGUpKSArDQogICAgZ2VvbV9saW5lKGx3ZCA9IDEpKw0KICBsYWJzKHRpdGxlID0gIlJNU0UgdnMgayAobm8uIG5lYXJlc3QgbmVpZ2hib3VycykgZm9yIGVhY2ggdmFyaWFibGUgY29tYmluYXRpb25zIiwgeCA9ICJrIiwgeSA9ICJSTVNFIikgKw0KICAgICAgdGhlbWUocGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gIndoaXRlIikpDQogIA0KYGBgDQpGcm9tIHRoaXMgcGxvdCwgaXQgY2FuIGJlIHNlZW4gdGhhdCB0aGUgY29tYmluYXRpb24gb2YgNCB2YXJpYWJsZXMgKGVuZ2luZV9zaXplICsgY2l0eV9tcGcgKyBob3JzZXBvd2VyICsgaGlnaHdheV9tcGcgKyB3aWR0aCkgd2l0aCBrIChudW1iZXIgb2YgbmVhcmVzdCBuZWlnaGJvdXJzKSA9IDIgcHJvdmlkZXMgdGhlIGxvd2VzdCBSTVNFDQoNCmBgYHtyfQ0KI25vdyB1c2UgdGhlIHRlc3QgZGF0YSBzZXQgaW4gdGhlIG1vZGVsIGFuZCBvYnRhaW4gdGhlIFJNU0Ugb2YgdGhlIHRlc3Qgc2V0LiANCg0KcHJlZGljdGlvbnMgPC0gcHJlZGljdChtb2RlbF9DNCwgbmV3ZGF0YSA9IHRlc3RfbGlzdGluZ3MpDQpwb3N0UmVzYW1wbGUocHJlZCA9IHByZWRpY3Rpb25zLCBvYnMgPSB0ZXN0X2xpc3RpbmdzJHByaWNlKQ0KYGBgDQpUaGUgUk1TRSB2YWx1ZSBmb3IgdGhlIHRlc3Qgc2V0IG9mIGRhdGEgaXMgJDI1Mjcgd2hpY2ggaXMgbG93ZXIgdGhhbiBpbiB0aGUgdHJhaW5pbmcgc2V0LiBUaGlzIGlzIHRoZXJlZm9yZSBhY2NlcHRhYmxlLiBIb3dldmVyLCB0aGlzIHN0aWxsIHNlZW1zIGxpa2UgcXVpdGUgYSBoaWdoIFJNU0UsIGVzcGVjaWFsbHkgZm9yIGEgdmVoaWNsZSB3aGljaCBtYXkgY29zdCBzYXkgwqM3MDAwLiBUaGVyZSBtYXkgcG90ZW50aWFsbHkgYmUgbW9yZSBmYWN0b3JzIHdoaWNoIGhhdmUgZ3JlYXRlciBpbmZsdWVuY2Ugb3ZlciB0aGUgcHJpY2Ugd2hpY2ggZWl0aGVyIHdlcmUgbm90IGluY2x1ZGVkIGluIHRoaXMgZGF0YXNldCBpLmUgdGhlIGNhciB5ZWFyLCBvciB3ZXJlIG5vdCBvZiBudW1lcmljIHR5cGUsIGkuZS4gdGhlIGNhciBtYWtlIGFuZCBtb2RlbC4NCg0KDQo=