Introduction

This analysis aims to predict housing prices using various machine learning models and census data. We will compare the performance of Linear Regression, Random Forest, Mixed Effects, and Neural Network models to identify the best approach for predicting housing prices.

The dataset includes housing features such as the number of bedrooms, bathrooms, square footage, and location, as well as census data like median income, population, education levels, and employment rates by ZIP code.

Data Preprocessing

First, we read the housing dataset download from kaggle. We convert relevant columns to appropriate data types and create new features like house age, years since renovation, and living area to lot size ratio.

head(housing_data)

datatable(housing_data, options = list(pageLength = 5))
Warning: It seems your data is too big for client-side DataTables. You may consider server-side processing: https://rstudio.github.io/DT/server.htmlWarning: It seems your data is too big for client-side DataTables. You may consider server-side processing: https://rstudio.github.io/DT/server.html
skim(housing_data)
# Price Distribution
ggplot(housing_data, aes(x = price)) + 
  geom_histogram(bins = 30, fill = "lightblue", color = "black") + 
  theme_minimal() + 
  labs(title = "Price Distribution")

# Bedrooms Distribution
ggplot(housing_data, aes(x = factor(bedrooms))) + 
  geom_bar(fill = "lightblue", color = "black") + 
  theme_minimal() + 
  labs(title = "Bedrooms Distribution", x = "Bedrooms")

# Bathrooms Distribution
ggplot(housing_data, aes(x = factor(bathrooms))) + 
  geom_bar(fill = "lightblue", color = "black") + 
  theme_minimal() + 
  labs(title = "Bathrooms Distribution", x = "Bathrooms")

# Living Area sqft Distribution
ggplot(housing_data, aes(x = sqft_living)) + 
  geom_histogram(bins = 30, fill = "lightblue", color = "black") + 
  theme_minimal() + 
  labs(title = "Living Area sqft Distribution")

# Correlation Heatmap
numeric_data <- select(housing_data, where(is.numeric))
cor_matrix <- cor(numeric_data)
corrplot(cor_matrix, method = 'circle', type = "upper", order = "hclust", 
         tl.col = "black", tl.srt = 45)

Incorperating Census Data

# Set the Census API key
census_api_key('bcbac889885cad20c1a787bc242d4aa4011251a2')

# Retrieve median income data by ZIP code
income_data <- get_acs(
  geography = "zcta",
  variables = "B19013_001",
  year = 2019,
  survey = "acs5"
)

f_income <- income_data %>%
  filter(GEOID %in% housing_data$ZIP) %>%
  rename(ZIP = GEOID, median_income = estimate)

# Retrieve population data for each ZIP code (ZCTA) from the latest ACS data
population_data <- get_acs(
  geography = "zcta",
  variables = "B01003_001",
  year = 2019,
  survey = "acs5"
)

f_pop <- population_data %>%
  filter(GEOID %in% housing_data$ZIP) %>%
  rename(ZIP = GEOID, population = estimate)

# Retrieve education data by ZIP code
education_data <- get_acs(
  geography = "zcta",
  variables = c(
    "DP02_0059P", # Percent high school graduate or higher
    "DP02_0065PE"  # Percent bachelor's degree or higher
  ),
  year = 2019,
  survey = "acs5"
)

education_data <- education_data %>%
  pivot_wider(names_from = variable, values_from = estimate) %>%
  group_by(GEOID) %>%
  summarize(
    pct_high_school_grad = mean(DP02_0059P, na.rm = TRUE),
    pct_bachelors_degree = mean(DP02_0065P, na.rm = TRUE)
  )

# Retrieve employment and occupation data by ZIP code
employment_data <- get_acs(
  geography = "zcta",
  variables = c(
    "DP03_0005PE", # Percent unemployed
    "DP03_0027PE", # Percent in management, business, science, and arts occupations
    "DP03_0028PE", # Percent in service occupations
    "DP03_0029PE", # Percent in sales and office occupations
    "DP03_0030PE"  # Percent in natural resources, construction, and maintenance occupations
  ),
  year = 2019,
  survey = "acs5"
)

employment_data <- employment_data %>%
  pivot_wider(names_from = variable, values_from = estimate) %>%
  group_by(GEOID) %>%
  summarize(
    pct_unemployed = mean(DP03_0005P, na.rm = TRUE),
    pct_mgmt_occupation = mean(DP03_0027P, na.rm = TRUE),
    pct_service_occupation = mean(DP03_0028P, na.rm = TRUE),
    pct_sales_office_occupation = mean(DP03_0029P, na.rm = TRUE),
    pct_construction_occupation = mean(DP03_0030P, na.rm = TRUE)
  )

# Retrieve housing characteristics data by ZIP code
housing_chars_data <- get_acs(
  geography = "zcta",
  variables = c(
    "DP04_0046PE", # Percent owner-occupied housing units
    "DP04_0039E", # Median year built for housing structures
    "DP04_0093PE"  # Percent of housing units with central air conditioning
  ),
  year = 2019,
  survey = "acs5"
)

housing_chars_data <- housing_chars_data %>%
  pivot_wider(names_from = variable, values_from = estimate) %>%
  group_by(GEOID) %>%
  summarize(
    pct_owner_occupied = mean(DP04_0046P, na.rm = TRUE),
    median_year_built = mean(DP04_0039, na.rm = TRUE),
    pct_central_air = mean(DP04_0093P, na.rm = TRUE)
  )

# Retrieve commuting patterns data by ZIP code
commuting_data <- get_acs(
  geography = "zcta",
  variables = c(
    "DP03_0025E", # Mean travel time to work (minutes)
    "DP03_0021PE", # Percent using public transportation (excluding taxicab)
    "DP03_0024PE"  # Percent working from home
  ),
  year = 2019,
  survey = "acs5"
)

commuting_data <- commuting_data %>%
  pivot_wider(names_from = variable, values_from = estimate) %>%
  group_by(GEOID) %>%
  summarize(
    mean_travel_time = mean(DP03_0025, na.rm = TRUE),
    pct_public_transport = mean(DP03_0021P, na.rm = TRUE),
    pct_work_from_home = mean(DP03_0024P, na.rm = TRUE)
  )

# Retrieve household characteristics data by ZIP code
household_data <- get_acs(
  geography = "zcta",
  variables = c(
    "DP02_0016E", # Average household size
    "DP02_0007PE", # Percent of households with own children under 18 years
    "DP02_0009PE", # Percent of single-parent households
    "DP05_0018E"  # Median age
  ),
  year = 2019,
  survey = "acs5"
)

household_data <- household_data %>%
  pivot_wider(names_from = variable, values_from = estimate) %>%
  group_by(GEOID) %>%
  summarize(
    avg_household_size = mean(DP02_0016, na.rm = TRUE),
    pct_households_with_children = mean(DP02_0007P, na.rm = TRUE),
    pct_single_parent_households = mean(DP02_0009P, na.rm = TRUE),
    median_age = mean(DP05_0018, na.rm = TRUE)
  )

# Merge all the Census data with the housing data
housing_data_with_census <- housing_data %>%
  left_join(f_income, by = "ZIP") %>%
  left_join(f_pop, by = "ZIP") %>%
  left_join(education_data, by = c("ZIP" = "GEOID")) %>%
  left_join(employment_data, by = c("ZIP" = "GEOID")) %>%
  left_join(housing_chars_data, by = c("ZIP" = "GEOID")) %>%
  left_join(commuting_data, by = c("ZIP" = "GEOID")) %>%
  left_join(household_data, by = c("ZIP" = "GEOID"))%>%
    select(
    date, price, bedrooms, bathrooms, sqft_living, sqft_lot, floors, waterfront, view, condition,
    sqft_above, sqft_basement, yr_built, yr_renovated, street, city, country, ZIP,
    house_age, years_since_renovation, total_rooms, living_lot_ratio, has_basement, renovated,
    floor_area_ratio, outdoor_space, season,
    median_income, population,
    pct_high_school_grad, pct_bachelors_degree,
    pct_unemployed, pct_mgmt_occupation, pct_service_occupation, pct_sales_office_occupation, pct_construction_occupation,
    pct_owner_occupied, median_year_built, pct_central_air,
    mean_travel_time, pct_public_transport, pct_work_from_home,
    avg_household_size, pct_households_with_children, pct_single_parent_households, median_age
  )%>%
  #write_csv('Data_with_census.csv')

Preprocessing

housing_data_with_census <- read_csv('Data_with_census.csv')
Rows: 4412 Columns: 47── Column specification ───────────────────────────────────────────
Delimiter: ","
chr   (5): street, city, country, State, season
dbl  (41): price, bedrooms, bathrooms, sqft_living, sqft_lot, f...
date  (1): date
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.

We decided to perform a log transformation on the price for make the distribution more normal

print(vif_values)
                    bedrooms                    bathrooms 
                    1.708079                     2.840310 
                 sqft_living                       floors 
                    3.061817                     2.763125 
                   condition             living_lot_ratio 
                    1.271854                     2.283708 
      pct_service_occupation  pct_sales_office_occupation 
                    3.574640                     2.379895 
 pct_construction_occupation            median_year_built 
                    4.023819                     4.305020 
            mean_travel_time           pct_work_from_home 
                    2.076649                     3.530957 
          avg_household_size pct_households_with_children 
                    4.812068                     1.362034 
pct_single_parent_households                   median_age 
                    2.331042                     3.079090 
               waterfront_X1                      view_X1 
                    1.326975                     1.034925 
                     view_X2                      view_X3 
                    1.061864                     1.082554 
                     view_X4              has_basement_X1 
                    1.303335                     1.705690 
                renovated_X1                season_Summer 
                    1.235108                     1.005978 

VIF is bellow 5 for all predictors so we have addressed colinearity problems.

library(multilevelmod)
set.seed(333)

## Create cross-validation folds for each model type
cv_folds_lm_rf <- vfold_cv(lm_rf_data, v = 10)
cv_folds_me <- vfold_cv(me_data_selected, v = 10)
cv_folds_nn <- vfold_cv(housing_data_for_model, v = 10)

# Create recipes for models
rec_lm_rf <- recipe(log_price ~ ., data = lm_rf_data)
rec_me <- recipe(log_price ~ ., data = me_data_selected)
rec_nn <- recipe(log_price ~ ., data = housing_data_for_model) %>%
  step_normalize(all_numeric_predictors(), -all_outcomes()) %>%
  step_dummy(all_nominal_predictors(), -all_outcomes())

# Define the models
lm_model <- linear_reg() %>% set_engine("lm") %>% set_mode("regression")
rf_model <- rand_forest(trees = 1000) %>% set_mode("regression") %>% set_engine("ranger", importance = "impurity")
me_model <- linear_reg() %>% set_engine("lmer") %>% set_mode("regression")
nn_model <- mlp(hidden_units = 10) %>% set_engine("nnet", linout = TRUE, trace = T) %>% set_mode("regression")

# Create workflows
lm_workflow <- workflow() %>% add_recipe(rec_lm_rf) %>% add_model(lm_model)
rf_workflow <- workflow() %>% add_recipe(rec_lm_rf) %>% add_model(rf_model)
me_workflow <- workflow() %>% add_recipe(rec_me) %>% add_model(me_model, formula = log_price ~ . + (1 | ZIP))
nn_workflow <- workflow() %>% add_recipe(rec_nn) %>% add_model(nn_model)

# Perform cross-validation
lm_cv_results <- fit_resamples(lm_workflow, resamples = cv_folds_lm_rf, metrics = metric_set(rmse, mae, rsq))
rf_cv_results <- fit_resamples(rf_workflow, resamples = cv_folds_lm_rf, metrics = metric_set(rmse, mae, rsq))
me_cv_results <- fit_resamples(me_workflow, resamples = cv_folds_me, metrics = metric_set(rmse, mae, rsq))
fixed-effect model matrix is rank deficient so dropping 10 columns / coefficients
fixed-effect model matrix is rank deficient so dropping 10 columns / coefficients
fixed-effect model matrix is rank deficient so dropping 10 columns / coefficients
fixed-effect model matrix is rank deficient so dropping 10 columns / coefficients
fixed-effect model matrix is rank deficient so dropping 10 columns / coefficients
fixed-effect model matrix is rank deficient so dropping 10 columns / coefficients
fixed-effect model matrix is rank deficient so dropping 10 columns / coefficients
fixed-effect model matrix is rank deficient so dropping 10 columns / coefficients
fixed-effect model matrix is rank deficient so dropping 10 columns / coefficients
fixed-effect model matrix is rank deficient so dropping 10 columns / coefficients
→ A | warning: unable to evaluate scaled gradient, Model failed to converge: degenerate  Hessian with 1 negative eigenvalues

There were issues with some computations   A: x1

There were issues with some computations   A: x1
nn_cv_results <- fit_resamples(nn_workflow, resamples = cv_folds_nn, metrics = metric_set(rmse, mae, rsq))

# Summarize and compare the cross-validation results
cv_results <- bind_rows(
  collect_metrics(lm_cv_results) %>% mutate(model = "Linear Regression"),
  collect_metrics(rf_cv_results) %>% mutate(model = "Random Forest"),
  collect_metrics(me_cv_results) %>% mutate(model = "Mixed Effects"),
  collect_metrics(nn_cv_results) %>% mutate(model = "Neural Network")
)

cv_summary <- cv_results %>%
  group_by(model, .metric) %>%
  summarize(
    mean_value = mean(mean, na.rm = TRUE),
    std_error = mean(std_err, na.rm = TRUE),
    .groups = 'drop'
  ) %>%
  pivot_wider(names_from = .metric, values_from = c(mean_value, std_error))

print(cv_summary)
# Split the data into training and testing sets for each model type
data_split_lm_rf <- initial_split(lm_rf_data, prop = 0.8)
train_data_lm_rf <- training(data_split_lm_rf)
test_data_lm_rf <- testing(data_split_lm_rf)

data_split_me <- initial_split(me_data_selected, prop = 0.8)
train_data_me <- training(data_split_me)
test_data_me <- testing(data_split_me)

data_split_nn <- initial_split(housing_data_for_model, prop = 0.8)
train_data_nn <- training(data_split_nn)
test_data_nn <- testing(data_split_nn)

# Fit and evaluate each model using last_fit()
lm_last_fit <- last_fit(lm_workflow, data_split_lm_rf)
rf_last_fit <- last_fit(rf_workflow, data_split_lm_rf)
me_last_fit <- last_fit(me_workflow, data_split_me)
fixed-effect model matrix is rank deficient so dropping 10 columns / coefficients
boundary (singular) fit: see help('isSingular')
nn_last_fit <- last_fit(nn_workflow, data_split_nn)

# Extract predictions and metrics for each model
lm_results <- lm_last_fit %>% collect_predictions() %>% mutate(model = "Linear Regression")
rf_results <- rf_last_fit %>% collect_predictions() %>% mutate(model = "Random Forest")
me_results <- me_last_fit %>% collect_predictions() %>% mutate(model = "Mixed Effects")
nn_results <- nn_last_fit %>% collect_predictions() %>% mutate(model = "Neural Network")

# Combine the results
results <- bind_rows(lm_results, rf_results, me_results, nn_results)

# Plot predicted vs. actual for each model with R^2
results %>%
  ggplot(aes(x = log_price, y = .pred, color = model)) +
  geom_point(alpha = 0.5) +
  geom_abline(slope = 1, intercept = 0, linetype = "dashed") +
  facet_wrap(~ model, scales = "free") +
  labs(
    title = "Predicted vs. Actual House Prices",
    x = "Actual Price",
    y = "Predicted Price"
  ) +
  theme_minimal() +
  theme(legend.position = "none") +
  geom_text(
    data = results %>%
      group_by(model) %>%
      summarise(rsq = cor(log_price, .pred)^2),
    aes(x = Inf, y = -Inf, label = sprintf("R^2 = %.3f", rsq)),
    hjust = 1.1,
    vjust = -1.1
  )

The model with the best predictive accuracy was the random forest model, followed by the linear regression model, neural network model, and mixed effects model. Issues with model fitting are clear with the mixed effects model due to the strong left skew in the error.

# Extract the trained Random Forest model from the last_fit object
rf_model <- rf_last_fit %>% 
  extract_fit_engine()

# Extract variable importance from the Random Forest model
rf_importance <- rf_model %>% 
  ranger::importance()

# Plot variable importance
ggplot(data.frame(variable = names(rf_importance), importance = rf_importance), 
       aes(x = reorder(variable, importance), y = importance, fill = importance)) +
  geom_bar(stat = "identity") +
  coord_flip() +
  labs(x = "Variable", y = "Importance", title = "Random Forest Variable Importance") +
  theme_minimal()

SQFT of living space followed by occupation prevalence appear to be most predictive of housing price. Things like view, and commuting time are suprisingly much lower by comparison!

Conversly in the linear model, having an outswtanding view seems to be the most important predictor. Very different apporaches!

LS0tDQp0aXRsZTogIkVEQSBIb3VzaW5nIg0Kb3V0cHV0Og0KICBodG1sX25vdGVib29rOiBkZWZhdWx0DQogIHBkZl9kb2N1bWVudDogZGVmYXVsdA0KLS0tDQoNCmBgYHtyLCBlY2hvPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQ0KbGlicmFyeSh0aWR5dmVyc2UsIHdhcm4uY29uZmxpY3RzID0gRikNCmxpYnJhcnkoR0dhbGx5KQ0KbGlicmFyeShjb3JycGxvdCkNCmxpYnJhcnkobG1lNCkNCmxpYnJhcnkobHVicmlkYXRlKSAgIyBMb2FkIGx1YnJpZGF0ZSBmb3IgZGF0ZSBtYW5pcHVsYXRpb24NCmxpYnJhcnkodGlkeWNlbnN1cykgICMgTG9hZCB0aWR5Y2Vuc3VzIGZvciBjZW5zdXMgZGF0YSByZXRyaWV2YWwNCmxpYnJhcnkoY2FyKSAgIyBMb2FkIGNhciBmb3IgVklGIGNhbGN1bGF0aW9uDQpsaWJyYXJ5KGNhcmV0KSAgIyBMb2FkIGNhcmV0IGZvciBmaW5kaW5nIGhpZ2ggY29ycmVsYXRpb25zDQpsaWJyYXJ5KE1BU1MpICAjIExvYWQgTUFTUyBmb3Igc3RlcEFJQyBmdW5jdGlvbg0KbGlicmFyeSh0aWR5bW9kZWxzLCB3YXJuLmNvbmZsaWN0cyA9IEYpICAjIExvYWQgdGlkeW1vZGVscyBmb3IgbW9kZWxpbmcNCmxpYnJhcnkocmFuZ2VyKSAgIyBMb2FkIHJhbmdlciBmb3IgUmFuZG9tIEZvcmVzdA0KbGlicmFyeShza2ltcikNCmxpYnJhcnkoRFQpDQoNCnRpZHltb2RlbHNfcHJlZmVyKCkNCmBgYA0KDQojIyBJbnRyb2R1Y3Rpb24NCg0KVGhpcyBhbmFseXNpcyBhaW1zIHRvIHByZWRpY3QgaG91c2luZyBwcmljZXMgdXNpbmcgdmFyaW91cyBtYWNoaW5lIGxlYXJuaW5nIG1vZGVscyBhbmQgY2Vuc3VzIGRhdGEuIFdlIHdpbGwgY29tcGFyZSB0aGUgcGVyZm9ybWFuY2Ugb2YgTGluZWFyIFJlZ3Jlc3Npb24sIFJhbmRvbSBGb3Jlc3QsIE1peGVkIEVmZmVjdHMsIGFuZCBOZXVyYWwgTmV0d29yayBtb2RlbHMgdG8gaWRlbnRpZnkgdGhlIGJlc3QgYXBwcm9hY2ggZm9yIHByZWRpY3RpbmcgaG91c2luZyBwcmljZXMuDQoNClRoZSBkYXRhc2V0IGluY2x1ZGVzIGhvdXNpbmcgZmVhdHVyZXMgc3VjaCBhcyB0aGUgbnVtYmVyIG9mIGJlZHJvb21zLCBiYXRocm9vbXMsIHNxdWFyZSBmb290YWdlLCBhbmQgbG9jYXRpb24sIGFzIHdlbGwgYXMgY2Vuc3VzIGRhdGEgbGlrZSBtZWRpYW4gaW5jb21lLCBwb3B1bGF0aW9uLCBlZHVjYXRpb24gbGV2ZWxzLCBhbmQgZW1wbG95bWVudCByYXRlcyBieSBaSVAgY29kZS4NCg0KIyMgRGF0YSBQcmVwcm9jZXNzaW5nDQoNCkZpcnN0LCB3ZSByZWFkIHRoZSBob3VzaW5nIGRhdGFzZXQgZG93bmxvYWQgZnJvbSBba2FnZ2xlXShodHRwczovL3d3dy5rYWdnbGUuY29tL2RhdGFzZXRzL3NocmVlMTk5Mi9ob3VzZWRhdGEvZGF0YSAiRGF0YXNldCBEb3dubG9hZCIpLiBXZSBjb252ZXJ0IHJlbGV2YW50IGNvbHVtbnMgdG8gYXBwcm9wcmlhdGUgZGF0YSB0eXBlcyBhbmQgY3JlYXRlIG5ldyBmZWF0dXJlcyBsaWtlIGhvdXNlIGFnZSwgeWVhcnMgc2luY2UgcmVub3ZhdGlvbiwgYW5kIGxpdmluZyBhcmVhIHRvIGxvdCBzaXplIHJhdGlvLg0KDQpoZWFkKGhvdXNpbmdfZGF0YSkNCg0KYGBge3J9DQpob3VzaW5nX2RhdGEgPC0gcmVhZF9jc3YoJ2RhdGEuY3N2JykNCg0KIyBDb252ZXJ0IHJlbGV2YW50IGNvbHVtbnMgdG8gYXBwcm9wcmlhdGUgZGF0YSB0eXBlcw0KaG91c2luZ19kYXRhJHZpZXcgPC0gYXMuZmFjdG9yKGhvdXNpbmdfZGF0YSR2aWV3KQ0KaG91c2luZ19kYXRhJHdhdGVyZnJvbnQgPC0gYXMuZmFjdG9yKGhvdXNpbmdfZGF0YSR3YXRlcmZyb250KQ0KaG91c2luZ19kYXRhJGNvbmRpdGlvbiA8LSBhcy5mYWN0b3IoaG91c2luZ19kYXRhJGNvbmRpdGlvbikNCmhvdXNpbmdfZGF0YSRkYXRlIDwtIGFzLkRhdGUoaG91c2luZ19kYXRhJGRhdGUpDQpob3VzaW5nX2RhdGEkcHJpY2UgPC0gYXMubnVtZXJpYyhob3VzaW5nX2RhdGEkcHJpY2UpDQpob3VzaW5nX2RhdGEkYmVkcm9vbXMgPC0gYXMubnVtZXJpYyhob3VzaW5nX2RhdGEkYmVkcm9vbXMpDQpob3VzaW5nX2RhdGEkYmF0aHJvb21zIDwtIGFzLm51bWVyaWMoaG91c2luZ19kYXRhJGJhdGhyb29tcykNCmhvdXNpbmdfZGF0YSRzcWZ0X2xpdmluZyA8LSBhcy5udW1lcmljKGhvdXNpbmdfZGF0YSRzcWZ0X2xpdmluZykNCmhvdXNpbmdfZGF0YSRzcWZ0X2xvdCA8LSBhcy5udW1lcmljKGhvdXNpbmdfZGF0YSRzcWZ0X2xvdCkNCmhvdXNpbmdfZGF0YSRmbG9vcnMgPC0gYXMubnVtZXJpYyhob3VzaW5nX2RhdGEkZmxvb3JzKQ0KaG91c2luZ19kYXRhJHNxZnRfYWJvdmUgPC0gYXMubnVtZXJpYyhob3VzaW5nX2RhdGEkc3FmdF9hYm92ZSkNCmhvdXNpbmdfZGF0YSRzcWZ0X2Jhc2VtZW50IDwtIGFzLm51bWVyaWMoaG91c2luZ19kYXRhJHNxZnRfYmFzZW1lbnQpDQpob3VzaW5nX2RhdGEkeXJfYnVpbHQgPC0gYXMuaW50ZWdlcihob3VzaW5nX2RhdGEkeXJfYnVpbHQpDQpob3VzaW5nX2RhdGEkeXJfcmVub3ZhdGVkIDwtIGFzLmludGVnZXIoaG91c2luZ19kYXRhJHlyX3Jlbm92YXRlZCkNCmhvdXNpbmdfZGF0YSRzdHJlZXQgPC0gYXMuY2hhcmFjdGVyKGhvdXNpbmdfZGF0YSRzdHJlZXQpDQpob3VzaW5nX2RhdGEkY2l0eSA8LSBhcy5jaGFyYWN0ZXIoaG91c2luZ19kYXRhJGNpdHkpDQpob3VzaW5nX2RhdGEkc3RhdGV6aXAgPC0gYXMuY2hhcmFjdGVyKGhvdXNpbmdfZGF0YSRzdGF0ZXppcCkNCmhvdXNpbmdfZGF0YSRjb3VudHJ5IDwtIGFzLmNoYXJhY3Rlcihob3VzaW5nX2RhdGEkY291bnRyeSkNCg0Kc3VtbWFyeShob3VzaW5nX2RhdGEpDQoNCmhvdXNpbmdfZGF0YSA8LSBob3VzaW5nX2RhdGEgJT4lDQogIG11dGF0ZShaSVAgPSBzdHJfZXh0cmFjdChzdGF0ZXppcCwgIlxcZHs1fSIpKQ0KDQoNCg0KaG91c2luZ19kYXRhIDwtIGhvdXNpbmdfZGF0YSAlPiUNCiAgbXV0YXRlKA0KICAgIGhvdXNlX2FnZSA9IGFzLmludGVnZXIoZm9ybWF0KFN5cy5EYXRlKCksICIlWSIpKSAtIHlyX2J1aWx0LA0KICAgIHllYXJzX3NpbmNlX3Jlbm92YXRpb24gPSBpZl9lbHNlKHlyX3Jlbm92YXRlZCA+IDAsIGFzLmludGVnZXIoZm9ybWF0KFN5cy5EYXRlKCksICIlWSIpKSAtIHlyX3Jlbm92YXRlZCwgaG91c2VfYWdlKSwNCiAgICB0b3RhbF9yb29tcyA9IGJlZHJvb21zICsgYmF0aHJvb21zLA0KICAgIGxpdmluZ19sb3RfcmF0aW8gPSBzcWZ0X2xpdmluZyAvIHNxZnRfbG90LA0KICAgIGhhc19iYXNlbWVudCA9IGFzLmludGVnZXIoc3FmdF9iYXNlbWVudCA+IDApLA0KICAgIHJlbm92YXRlZCA9IGFzLmludGVnZXIoeXJfcmVub3ZhdGVkID4gMCksDQogICAgZmxvb3JfYXJlYV9yYXRpbyA9IHNxZnRfYWJvdmUgLyBzcWZ0X2xvdCwNCiAgICBvdXRkb29yX3NwYWNlID0gc3FmdF9sb3QgLSBzcWZ0X2xpdmluZywNCiAgICBzZWFzb24gPSBjYXNlX3doZW4oDQogICAgICBtb250aChkYXRlKSAlaW4lIDM6NSB+ICJTcHJpbmciLA0KICAgICAgbW9udGgoZGF0ZSkgJWluJSA2OjggfiAiU3VtbWVyIiwNCiAgICAgIG1vbnRoKGRhdGUpICVpbiUgOToxMSB+ICJBdXR1bW4iLA0KICAgICAgVFJVRSB+ICJXaW50ZXIiDQogICAgKQ0KICApDQpkYXRhdGFibGUoaG91c2luZ19kYXRhLCBvcHRpb25zID0gbGlzdChwYWdlTGVuZ3RoID0gNSkpDQpgYGANCg0KLSAgIHByaWNlOiBSYW5nZXMgZnJvbSBcJDAgKHdoaWNoIG1heSBpbmRpY2F0ZSBtaXNzaW5nIG9yIHBsYWNlaG9sZGVyIHZhbHVlcykgdG8gXCQyNiw1OTAsMDAwIHdpdGggYSBtZWFuIG9mIFwkNTUxLDk2My4NCg0KLSAgIGJlZHJvb21zOiBSYW5nZXMgZnJvbSAwIHRvIDksIHdpdGggYW4gYXZlcmFnZSBvZiBhYm91dCAzLjQgYmVkcm9vbXMgcGVyIGhvdXNlLg0KDQotICAgYmF0aHJvb21zOiBSYW5nZXMgZnJvbSAwIHRvIDgsIHdpdGggYSBtZWFuIG9mIGFwcHJveGltYXRlbHkgMi4xNiBiYXRocm9vbXMgcGVyIGhvdXNlLg0KDQotICAgc3FmdF9saXZpbmc6IExpdmluZyBhcmVhIHNxdWFyZSBmb290YWdlIHJhbmdlcyBmcm9tIDM3MCB0byAxMyw1NDAgc3FmdCwgd2l0aCBhIG1lYW4gb2YgMiwxMzkgc3FmdC4NCg0KLSAgIHNxZnRfbG90OiBMb3Qgc2l6ZSBzcXVhcmUgZm9vdGFnZSB2YXJpZXMgd2lkZWx5IGZyb20gNjM4IHRvIDEsMDc0LDIxOCBzcWZ0LCBpbmRpY2F0aW5nIGEgbWl4IG9mIHVyYmFuIHRvIHZlcnkgbGFyZ2UgcnVyYWwgcHJvcGVydGllcy4NCg0KLSAgIGZsb29ycywgd2F0ZXJmcm9udCwgdmlldywgY29uZGl0aW9uOiBUaGVzZSBmZWF0dXJlcyBhcmUgY2F0ZWdvcmljYWwgb3IgYmluYXJ5LCB3aXRoIGZsb29ycyByYW5naW5nIGZyb20gMSB0byAzLjUsIGluZGljYXRpbmcgbXVsdGktbGV2ZWwgaG91c2VzIGFyZSBpbmNsdWRlZC4gV2F0ZXJmcm9udCBoYXMgYSBiaW5hcnkgaW5kaWNhdG9yLCB3aXRoIHZlcnkgZmV3IHdhdGVyZnJvbnQgcHJvcGVydGllcyBwcmVzZW50LiBWaWV3IGFuZCBjb25kaXRpb24gYXJlIG9yZGluYWwsIHdpdGggdGhlaXIgb3duIHJhbmdlIG9mIHZhbHVlcy4NCg0KYGBge3J9DQpza2ltKGhvdXNpbmdfZGF0YSkNCiMgUHJpY2UgRGlzdHJpYnV0aW9uDQpnZ3Bsb3QoaG91c2luZ19kYXRhLCBhZXMoeCA9IHByaWNlKSkgKyANCiAgZ2VvbV9oaXN0b2dyYW0oYmlucyA9IDMwLCBmaWxsID0gImxpZ2h0Ymx1ZSIsIGNvbG9yID0gImJsYWNrIikgKyANCiAgdGhlbWVfbWluaW1hbCgpICsgDQogIGxhYnModGl0bGUgPSAiUHJpY2UgRGlzdHJpYnV0aW9uIikNCg0KIyBCZWRyb29tcyBEaXN0cmlidXRpb24NCmdncGxvdChob3VzaW5nX2RhdGEsIGFlcyh4ID0gZmFjdG9yKGJlZHJvb21zKSkpICsgDQogIGdlb21fYmFyKGZpbGwgPSAibGlnaHRibHVlIiwgY29sb3IgPSAiYmxhY2siKSArIA0KICB0aGVtZV9taW5pbWFsKCkgKyANCiAgbGFicyh0aXRsZSA9ICJCZWRyb29tcyBEaXN0cmlidXRpb24iLCB4ID0gIkJlZHJvb21zIikNCg0KIyBCYXRocm9vbXMgRGlzdHJpYnV0aW9uDQpnZ3Bsb3QoaG91c2luZ19kYXRhLCBhZXMoeCA9IGZhY3RvcihiYXRocm9vbXMpKSkgKyANCiAgZ2VvbV9iYXIoZmlsbCA9ICJsaWdodGJsdWUiLCBjb2xvciA9ICJibGFjayIpICsgDQogIHRoZW1lX21pbmltYWwoKSArIA0KICBsYWJzKHRpdGxlID0gIkJhdGhyb29tcyBEaXN0cmlidXRpb24iLCB4ID0gIkJhdGhyb29tcyIpDQoNCiMgTGl2aW5nIEFyZWEgc3FmdCBEaXN0cmlidXRpb24NCmdncGxvdChob3VzaW5nX2RhdGEsIGFlcyh4ID0gc3FmdF9saXZpbmcpKSArIA0KICBnZW9tX2hpc3RvZ3JhbShiaW5zID0gMzAsIGZpbGwgPSAibGlnaHRibHVlIiwgY29sb3IgPSAiYmxhY2siKSArIA0KICB0aGVtZV9taW5pbWFsKCkgKyANCiAgbGFicyh0aXRsZSA9ICJMaXZpbmcgQXJlYSBzcWZ0IERpc3RyaWJ1dGlvbiIpDQoNCiMgQ29ycmVsYXRpb24gSGVhdG1hcA0KbnVtZXJpY19kYXRhIDwtIHNlbGVjdChob3VzaW5nX2RhdGEsIHdoZXJlKGlzLm51bWVyaWMpKQ0KY29yX21hdHJpeCA8LSBjb3IobnVtZXJpY19kYXRhKQ0KY29ycnBsb3QoY29yX21hdHJpeCwgbWV0aG9kID0gJ2NpcmNsZScsIHR5cGUgPSAidXBwZXIiLCBvcmRlciA9ICJoY2x1c3QiLCANCiAgICAgICAgIHRsLmNvbCA9ICJibGFjayIsIHRsLnNydCA9IDQ1KQ0KYGBgDQoNCiMjIEluY29ycGVyYXRpbmcgQ2Vuc3VzIERhdGENCmBgYHtyfQ0KIyBTZXQgdGhlIENlbnN1cyBBUEkga2V5DQpjZW5zdXNfYXBpX2tleSgnYmNiYWM4ODk4ODVjYWQyMGMxYTc4N2JjMjQyZDRhYTQwMTEyNTFhMicpDQoNCiMgUmV0cmlldmUgbWVkaWFuIGluY29tZSBkYXRhIGJ5IFpJUCBjb2RlDQppbmNvbWVfZGF0YSA8LSBnZXRfYWNzKA0KICBnZW9ncmFwaHkgPSAiemN0YSIsDQogIHZhcmlhYmxlcyA9ICJCMTkwMTNfMDAxIiwNCiAgeWVhciA9IDIwMTksDQogIHN1cnZleSA9ICJhY3M1Ig0KKQ0KDQpmX2luY29tZSA8LSBpbmNvbWVfZGF0YSAlPiUNCiAgZmlsdGVyKEdFT0lEICVpbiUgaG91c2luZ19kYXRhJFpJUCkgJT4lDQogIHJlbmFtZShaSVAgPSBHRU9JRCwgbWVkaWFuX2luY29tZSA9IGVzdGltYXRlKQ0KDQojIFJldHJpZXZlIHBvcHVsYXRpb24gZGF0YSBmb3IgZWFjaCBaSVAgY29kZSAoWkNUQSkgZnJvbSB0aGUgbGF0ZXN0IEFDUyBkYXRhDQpwb3B1bGF0aW9uX2RhdGEgPC0gZ2V0X2FjcygNCiAgZ2VvZ3JhcGh5ID0gInpjdGEiLA0KICB2YXJpYWJsZXMgPSAiQjAxMDAzXzAwMSIsDQogIHllYXIgPSAyMDE5LA0KICBzdXJ2ZXkgPSAiYWNzNSINCikNCg0KZl9wb3AgPC0gcG9wdWxhdGlvbl9kYXRhICU+JQ0KICBmaWx0ZXIoR0VPSUQgJWluJSBob3VzaW5nX2RhdGEkWklQKSAlPiUNCiAgcmVuYW1lKFpJUCA9IEdFT0lELCBwb3B1bGF0aW9uID0gZXN0aW1hdGUpDQoNCiMgUmV0cmlldmUgZWR1Y2F0aW9uIGRhdGEgYnkgWklQIGNvZGUNCmVkdWNhdGlvbl9kYXRhIDwtIGdldF9hY3MoDQogIGdlb2dyYXBoeSA9ICJ6Y3RhIiwNCiAgdmFyaWFibGVzID0gYygNCiAgICAiRFAwMl8wMDU5UCIsICMgUGVyY2VudCBoaWdoIHNjaG9vbCBncmFkdWF0ZSBvciBoaWdoZXINCiAgICAiRFAwMl8wMDY1UEUiICAjIFBlcmNlbnQgYmFjaGVsb3IncyBkZWdyZWUgb3IgaGlnaGVyDQogICksDQogIHllYXIgPSAyMDE5LA0KICBzdXJ2ZXkgPSAiYWNzNSINCikNCg0KZWR1Y2F0aW9uX2RhdGEgPC0gZWR1Y2F0aW9uX2RhdGEgJT4lDQogIHBpdm90X3dpZGVyKG5hbWVzX2Zyb20gPSB2YXJpYWJsZSwgdmFsdWVzX2Zyb20gPSBlc3RpbWF0ZSkgJT4lDQogIGdyb3VwX2J5KEdFT0lEKSAlPiUNCiAgc3VtbWFyaXplKA0KICAgIHBjdF9oaWdoX3NjaG9vbF9ncmFkID0gbWVhbihEUDAyXzAwNTlQLCBuYS5ybSA9IFRSVUUpLA0KICAgIHBjdF9iYWNoZWxvcnNfZGVncmVlID0gbWVhbihEUDAyXzAwNjVQLCBuYS5ybSA9IFRSVUUpDQogICkNCg0KIyBSZXRyaWV2ZSBlbXBsb3ltZW50IGFuZCBvY2N1cGF0aW9uIGRhdGEgYnkgWklQIGNvZGUNCmVtcGxveW1lbnRfZGF0YSA8LSBnZXRfYWNzKA0KICBnZW9ncmFwaHkgPSAiemN0YSIsDQogIHZhcmlhYmxlcyA9IGMoDQogICAgIkRQMDNfMDAwNVBFIiwgIyBQZXJjZW50IHVuZW1wbG95ZWQNCiAgICAiRFAwM18wMDI3UEUiLCAjIFBlcmNlbnQgaW4gbWFuYWdlbWVudCwgYnVzaW5lc3MsIHNjaWVuY2UsIGFuZCBhcnRzIG9jY3VwYXRpb25zDQogICAgIkRQMDNfMDAyOFBFIiwgIyBQZXJjZW50IGluIHNlcnZpY2Ugb2NjdXBhdGlvbnMNCiAgICAiRFAwM18wMDI5UEUiLCAjIFBlcmNlbnQgaW4gc2FsZXMgYW5kIG9mZmljZSBvY2N1cGF0aW9ucw0KICAgICJEUDAzXzAwMzBQRSIgICMgUGVyY2VudCBpbiBuYXR1cmFsIHJlc291cmNlcywgY29uc3RydWN0aW9uLCBhbmQgbWFpbnRlbmFuY2Ugb2NjdXBhdGlvbnMNCiAgKSwNCiAgeWVhciA9IDIwMTksDQogIHN1cnZleSA9ICJhY3M1Ig0KKQ0KDQplbXBsb3ltZW50X2RhdGEgPC0gZW1wbG95bWVudF9kYXRhICU+JQ0KICBwaXZvdF93aWRlcihuYW1lc19mcm9tID0gdmFyaWFibGUsIHZhbHVlc19mcm9tID0gZXN0aW1hdGUpICU+JQ0KICBncm91cF9ieShHRU9JRCkgJT4lDQogIHN1bW1hcml6ZSgNCiAgICBwY3RfdW5lbXBsb3llZCA9IG1lYW4oRFAwM18wMDA1UCwgbmEucm0gPSBUUlVFKSwNCiAgICBwY3RfbWdtdF9vY2N1cGF0aW9uID0gbWVhbihEUDAzXzAwMjdQLCBuYS5ybSA9IFRSVUUpLA0KICAgIHBjdF9zZXJ2aWNlX29jY3VwYXRpb24gPSBtZWFuKERQMDNfMDAyOFAsIG5hLnJtID0gVFJVRSksDQogICAgcGN0X3NhbGVzX29mZmljZV9vY2N1cGF0aW9uID0gbWVhbihEUDAzXzAwMjlQLCBuYS5ybSA9IFRSVUUpLA0KICAgIHBjdF9jb25zdHJ1Y3Rpb25fb2NjdXBhdGlvbiA9IG1lYW4oRFAwM18wMDMwUCwgbmEucm0gPSBUUlVFKQ0KICApDQoNCiMgUmV0cmlldmUgaG91c2luZyBjaGFyYWN0ZXJpc3RpY3MgZGF0YSBieSBaSVAgY29kZQ0KaG91c2luZ19jaGFyc19kYXRhIDwtIGdldF9hY3MoDQogIGdlb2dyYXBoeSA9ICJ6Y3RhIiwNCiAgdmFyaWFibGVzID0gYygNCiAgICAiRFAwNF8wMDQ2UEUiLCAjIFBlcmNlbnQgb3duZXItb2NjdXBpZWQgaG91c2luZyB1bml0cw0KICAgICJEUDA0XzAwMzlFIiwgIyBNZWRpYW4geWVhciBidWlsdCBmb3IgaG91c2luZyBzdHJ1Y3R1cmVzDQogICAgIkRQMDRfMDA5M1BFIiAgIyBQZXJjZW50IG9mIGhvdXNpbmcgdW5pdHMgd2l0aCBjZW50cmFsIGFpciBjb25kaXRpb25pbmcNCiAgKSwNCiAgeWVhciA9IDIwMTksDQogIHN1cnZleSA9ICJhY3M1Ig0KKQ0KDQpob3VzaW5nX2NoYXJzX2RhdGEgPC0gaG91c2luZ19jaGFyc19kYXRhICU+JQ0KICBwaXZvdF93aWRlcihuYW1lc19mcm9tID0gdmFyaWFibGUsIHZhbHVlc19mcm9tID0gZXN0aW1hdGUpICU+JQ0KICBncm91cF9ieShHRU9JRCkgJT4lDQogIHN1bW1hcml6ZSgNCiAgICBwY3Rfb3duZXJfb2NjdXBpZWQgPSBtZWFuKERQMDRfMDA0NlAsIG5hLnJtID0gVFJVRSksDQogICAgbWVkaWFuX3llYXJfYnVpbHQgPSBtZWFuKERQMDRfMDAzOSwgbmEucm0gPSBUUlVFKSwNCiAgICBwY3RfY2VudHJhbF9haXIgPSBtZWFuKERQMDRfMDA5M1AsIG5hLnJtID0gVFJVRSkNCiAgKQ0KDQojIFJldHJpZXZlIGNvbW11dGluZyBwYXR0ZXJucyBkYXRhIGJ5IFpJUCBjb2RlDQpjb21tdXRpbmdfZGF0YSA8LSBnZXRfYWNzKA0KICBnZW9ncmFwaHkgPSAiemN0YSIsDQogIHZhcmlhYmxlcyA9IGMoDQogICAgIkRQMDNfMDAyNUUiLCAjIE1lYW4gdHJhdmVsIHRpbWUgdG8gd29yayAobWludXRlcykNCiAgICAiRFAwM18wMDIxUEUiLCAjIFBlcmNlbnQgdXNpbmcgcHVibGljIHRyYW5zcG9ydGF0aW9uIChleGNsdWRpbmcgdGF4aWNhYikNCiAgICAiRFAwM18wMDI0UEUiICAjIFBlcmNlbnQgd29ya2luZyBmcm9tIGhvbWUNCiAgKSwNCiAgeWVhciA9IDIwMTksDQogIHN1cnZleSA9ICJhY3M1Ig0KKQ0KDQpjb21tdXRpbmdfZGF0YSA8LSBjb21tdXRpbmdfZGF0YSAlPiUNCiAgcGl2b3Rfd2lkZXIobmFtZXNfZnJvbSA9IHZhcmlhYmxlLCB2YWx1ZXNfZnJvbSA9IGVzdGltYXRlKSAlPiUNCiAgZ3JvdXBfYnkoR0VPSUQpICU+JQ0KICBzdW1tYXJpemUoDQogICAgbWVhbl90cmF2ZWxfdGltZSA9IG1lYW4oRFAwM18wMDI1LCBuYS5ybSA9IFRSVUUpLA0KICAgIHBjdF9wdWJsaWNfdHJhbnNwb3J0ID0gbWVhbihEUDAzXzAwMjFQLCBuYS5ybSA9IFRSVUUpLA0KICAgIHBjdF93b3JrX2Zyb21faG9tZSA9IG1lYW4oRFAwM18wMDI0UCwgbmEucm0gPSBUUlVFKQ0KICApDQoNCiMgUmV0cmlldmUgaG91c2Vob2xkIGNoYXJhY3RlcmlzdGljcyBkYXRhIGJ5IFpJUCBjb2RlDQpob3VzZWhvbGRfZGF0YSA8LSBnZXRfYWNzKA0KICBnZW9ncmFwaHkgPSAiemN0YSIsDQogIHZhcmlhYmxlcyA9IGMoDQogICAgIkRQMDJfMDAxNkUiLCAjIEF2ZXJhZ2UgaG91c2Vob2xkIHNpemUNCiAgICAiRFAwMl8wMDA3UEUiLCAjIFBlcmNlbnQgb2YgaG91c2Vob2xkcyB3aXRoIG93biBjaGlsZHJlbiB1bmRlciAxOCB5ZWFycw0KICAgICJEUDAyXzAwMDlQRSIsICMgUGVyY2VudCBvZiBzaW5nbGUtcGFyZW50IGhvdXNlaG9sZHMNCiAgICAiRFAwNV8wMDE4RSIgICMgTWVkaWFuIGFnZQ0KICApLA0KICB5ZWFyID0gMjAxOSwNCiAgc3VydmV5ID0gImFjczUiDQopDQoNCmhvdXNlaG9sZF9kYXRhIDwtIGhvdXNlaG9sZF9kYXRhICU+JQ0KICBwaXZvdF93aWRlcihuYW1lc19mcm9tID0gdmFyaWFibGUsIHZhbHVlc19mcm9tID0gZXN0aW1hdGUpICU+JQ0KICBncm91cF9ieShHRU9JRCkgJT4lDQogIHN1bW1hcml6ZSgNCiAgICBhdmdfaG91c2Vob2xkX3NpemUgPSBtZWFuKERQMDJfMDAxNiwgbmEucm0gPSBUUlVFKSwNCiAgICBwY3RfaG91c2Vob2xkc193aXRoX2NoaWxkcmVuID0gbWVhbihEUDAyXzAwMDdQLCBuYS5ybSA9IFRSVUUpLA0KICAgIHBjdF9zaW5nbGVfcGFyZW50X2hvdXNlaG9sZHMgPSBtZWFuKERQMDJfMDAwOVAsIG5hLnJtID0gVFJVRSksDQogICAgbWVkaWFuX2FnZSA9IG1lYW4oRFAwNV8wMDE4LCBuYS5ybSA9IFRSVUUpDQogICkNCg0KIyBNZXJnZSBhbGwgdGhlIENlbnN1cyBkYXRhIHdpdGggdGhlIGhvdXNpbmcgZGF0YQ0KaG91c2luZ19kYXRhX3dpdGhfY2Vuc3VzIDwtIGhvdXNpbmdfZGF0YSAlPiUNCiAgbGVmdF9qb2luKGZfaW5jb21lLCBieSA9ICJaSVAiKSAlPiUNCiAgbGVmdF9qb2luKGZfcG9wLCBieSA9ICJaSVAiKSAlPiUNCiAgbGVmdF9qb2luKGVkdWNhdGlvbl9kYXRhLCBieSA9IGMoIlpJUCIgPSAiR0VPSUQiKSkgJT4lDQogIGxlZnRfam9pbihlbXBsb3ltZW50X2RhdGEsIGJ5ID0gYygiWklQIiA9ICJHRU9JRCIpKSAlPiUNCiAgbGVmdF9qb2luKGhvdXNpbmdfY2hhcnNfZGF0YSwgYnkgPSBjKCJaSVAiID0gIkdFT0lEIikpICU+JQ0KICBsZWZ0X2pvaW4oY29tbXV0aW5nX2RhdGEsIGJ5ID0gYygiWklQIiA9ICJHRU9JRCIpKSAlPiUNCiAgbGVmdF9qb2luKGhvdXNlaG9sZF9kYXRhLCBieSA9IGMoIlpJUCIgPSAiR0VPSUQiKSklPiUNCiAgICBzZWxlY3QoDQogICAgZGF0ZSwgcHJpY2UsIGJlZHJvb21zLCBiYXRocm9vbXMsIHNxZnRfbGl2aW5nLCBzcWZ0X2xvdCwgZmxvb3JzLCB3YXRlcmZyb250LCB2aWV3LCBjb25kaXRpb24sDQogICAgc3FmdF9hYm92ZSwgc3FmdF9iYXNlbWVudCwgeXJfYnVpbHQsIHlyX3Jlbm92YXRlZCwgc3RyZWV0LCBjaXR5LCBjb3VudHJ5LCBaSVAsDQogICAgaG91c2VfYWdlLCB5ZWFyc19zaW5jZV9yZW5vdmF0aW9uLCB0b3RhbF9yb29tcywgbGl2aW5nX2xvdF9yYXRpbywgaGFzX2Jhc2VtZW50LCByZW5vdmF0ZWQsDQogICAgZmxvb3JfYXJlYV9yYXRpbywgb3V0ZG9vcl9zcGFjZSwgc2Vhc29uLA0KICAgIG1lZGlhbl9pbmNvbWUsIHBvcHVsYXRpb24sDQogICAgcGN0X2hpZ2hfc2Nob29sX2dyYWQsIHBjdF9iYWNoZWxvcnNfZGVncmVlLA0KICAgIHBjdF91bmVtcGxveWVkLCBwY3RfbWdtdF9vY2N1cGF0aW9uLCBwY3Rfc2VydmljZV9vY2N1cGF0aW9uLCBwY3Rfc2FsZXNfb2ZmaWNlX29jY3VwYXRpb24sIHBjdF9jb25zdHJ1Y3Rpb25fb2NjdXBhdGlvbiwNCiAgICBwY3Rfb3duZXJfb2NjdXBpZWQsIG1lZGlhbl95ZWFyX2J1aWx0LCBwY3RfY2VudHJhbF9haXIsDQogICAgbWVhbl90cmF2ZWxfdGltZSwgcGN0X3B1YmxpY190cmFuc3BvcnQsIHBjdF93b3JrX2Zyb21faG9tZSwNCiAgICBhdmdfaG91c2Vob2xkX3NpemUsIHBjdF9ob3VzZWhvbGRzX3dpdGhfY2hpbGRyZW4sIHBjdF9zaW5nbGVfcGFyZW50X2hvdXNlaG9sZHMsIG1lZGlhbl9hZ2UNCiAgKSU+JQ0KICAjd3JpdGVfY3N2KCdEYXRhX3dpdGhfY2Vuc3VzLmNzdicpDQoNCg0KDQpgYGANCg0KIyBQcmVwcm9jZXNzaW5nDQpgYGB7cn0NCmhvdXNpbmdfZGF0YV93aXRoX2NlbnN1cyA8LSByZWFkX2NzdignRGF0YV93aXRoX2NlbnN1cy5jc3YnKQ0KDQojIENoZWNrIGZvciBtdWx0aWNvbGxpbmVhcml0eSB1c2luZyBWSUYNCmhvdXNpbmdfZGF0YV9maW5hbF9udW0gPC0gaG91c2luZ19kYXRhX2Zvcl9tb2RlbCAlPiUNCiAgc2VsZWN0X2lmKGlzLm51bWVyaWMpDQoNCnZpZl9tb2RlbCA8LSBsbShwcmljZSB+IC4sIGRhdGEgPSBob3VzaW5nX2RhdGFfZmluYWxfbnVtKQ0KdmlmX3ZhbHVlcyA8LSB2aWYodmlmX21vZGVsKQ0KcHJpbnQodmlmX3ZhbHVlcykNCg0KIyBDb3JyZWxhdGlvbiBtYXRyaXgNCmNvcl9tYXRyaXggPC0gY29yKGhvdXNpbmdfZGF0YV9maW5hbF9udW0pDQojd3JpdGUuY3N2KGNvcl9tYXRyaXgsIGZpbGUgPSAiY29ycmVsYXRpb25fbWF0cml4LmNzdiIsIHJvdy5uYW1lcyA9IFRSVUUpDQpjb3JycGxvdDo6Y29ycnBsb3QoY29yX21hdHJpeCwgbWV0aG9kID0gImNpcmNsZSIpDQoNCiMgRmluZCBoaWdobHkgY29ycmVsYXRlZCB2YXJpYWJsZXMNCmhpZ2hfY29ycmVsYXRpb24gPC0gZmluZENvcnJlbGF0aW9uKGNvcl9tYXRyaXgsIGN1dG9mZiA9IDAuNzUpDQpoaWdoX2NvcnJlbGF0aW9uX25hbWVzIDwtIG5hbWVzKGhvdXNpbmdfZGF0YV9maW5hbF9udW0pW2hpZ2hfY29ycmVsYXRpb25dDQpwcmludChoaWdoX2NvcnJlbGF0aW9uX25hbWVzKQ0KDQojIFJlbW92ZSBoaWdobHkgY29ycmVsYXRlZCB2YXJpYWJsZXMgKG9uZSBmcm9tIGVhY2ggcGFpcikNCmhvdXNpbmdfZGF0YV9yZWR1Y2VkIDwtIGhvdXNpbmdfZGF0YV9maW5hbF9udW1bLCAtaGlnaF9jb3JyZWxhdGlvbl0NCmBgYA0KDQpgYGB7cn0NCiMgUHJlcGFyZSBkYXRhIGZvciBtb2RlbGluZw0KaG91c2luZ19kYXRhX2Zvcl9tb2RlbCA8LSBob3VzaW5nX2RhdGFfd2l0aF9jZW5zdXMgJT4lDQogIHNlbGVjdCgNCiAgICAteXJfYnVpbHQsIC15cl9yZW5vdmF0ZWQsIC1mbG9vcl9hcmVhX3JhdGlvLCAtY291bnRyeSwNCiAgICAtU3RhdGUsIC1zdHJlZXQsIC1kYXRlLCAtdG90YWxfcm9vbXMsIC1vdXRkb29yX3NwYWNlLA0KICAgIC1zcWZ0X2Jhc2VtZW50LCAtc3FmdF9hYm92ZSwgLXBjdF9tZ210X29jY3VwYXRpb24sIC1wY3RfaGlnaF9zY2hvb2xfZ3JhZCwgLXBjdF9wdWJsaWNfdHJhbnNwb3J0LCAtbWVkaWFuX2luY29tZSwgLXBjdF9vd25lcl9vY2N1cGllZCwgLXBjdF9jZW50cmFsX2FpciwgLXBjdF9iYWNoZWxvcnNfZGVncmVlLCAtY2l0eSwgLWNvdW50cnkpDQoNCiMgVHJhbnNmb3JtIHByaWNlIHRvIGxvZyBzY2FsZQ0KaG91c2luZ19kYXRhX2Zvcl9tb2RlbCA8LSBob3VzaW5nX2RhdGFfZm9yX21vZGVsICU+JQ0KICBmaWx0ZXIocHJpY2UgIT0gMCwgIWlzLm5hKG1lYW5fdHJhdmVsX3RpbWUpKSU+JQ0KICBtdXRhdGUobG9nX3ByaWNlID0gbG9nKHByaWNlKSkNCg0KZ2dwbG90KGhvdXNpbmdfZGF0YV9mb3JfbW9kZWwsIGFlcyh4PWxvZ19wcmljZSkpICsNCiAgZ2VvbV9oaXN0b2dyYW0oYmlucz0zMCwgZmlsbD0ibGlnaHRibHVlIiwgY29sb3I9J2JsYWNrJywgYWxwaGE9MC43KSArDQogIGdndGl0bGUoIkhpc3RvZ3JhbSBvZiBMb2ctdHJhbnNmb3JtZWQgUHJpY2VzIikgKw0KICB4bGFiKCJMb2cgb2YgUHJpY2UiKSArDQogIHlsYWIoIkZyZXF1ZW5jeSIpICMgbXVjaCBiZXR0ZXIgc2hhcGUgdGhhbiBlYXJsaWVyDQoNCmBgYA0KV2UgZGVjaWRlZCB0byBwZXJmb3JtIGEgbG9nIHRyYW5zZm9ybWF0aW9uIG9uIHRoZSBwcmljZSBmb3IgbWFrZSB0aGUgZGlzdHJpYnV0aW9uIG1vcmUgbm9ybWFsDQoNCg0KYGBge3J9DQojIEJhc2ljIHByZXByb2Nlc3NpbmcgYXBwbGljYWJsZSB0byBhbGwgbW9kZWxzDQpob3VzaW5nX2RhdGFfZm9yX21vZGVsIDwtIGhvdXNpbmdfZGF0YV93aXRoX2NlbnN1cyAlPiUNCiAgc2VsZWN0KC15cl9idWlsdCwgLXlyX3Jlbm92YXRlZCwgLWZsb29yX2FyZWFfcmF0aW8sIC1jb3VudHJ5LCANCiAgICAgICAgIC1TdGF0ZSwgLXN0cmVldCwgLWRhdGUsIC10b3RhbF9yb29tcywgLW91dGRvb3Jfc3BhY2UsDQogICAgICAgICAtc3FmdF9iYXNlbWVudCwgLXNxZnRfYWJvdmUsIC1wY3RfbWdtdF9vY2N1cGF0aW9uLCANCiAgICAgICAgIC1wY3RfaGlnaF9zY2hvb2xfZ3JhZCwgLXBjdF9wdWJsaWNfdHJhbnNwb3J0LCAtbWVkaWFuX2luY29tZSwgDQogICAgICAgICAtcGN0X293bmVyX29jY3VwaWVkLCAtcGN0X2NlbnRyYWxfYWlyLCAtcGN0X2JhY2hlbG9yc19kZWdyZWUsIC1jaXR5LCAtY291bnRyeSkgJT4lDQogIGZpbHRlcihwcmljZSAhPSAwLCAhaXMubmEobWVhbl90cmF2ZWxfdGltZSkpICU+JQ0KICBtdXRhdGUobG9nX3ByaWNlID0gbG9nKHByaWNlKSkgJT4lDQogIHNlbGVjdCgtcHJpY2UpJT4lDQogIG11dGF0ZSgNCiAgICBzZWFzb24gPSBhcy5mYWN0b3Ioc2Vhc29uKSwNCiAgICB3YXRlcmZyb250ID0gYXMuZmFjdG9yKHdhdGVyZnJvbnQpLA0KICAgIHZpZXcgPSBhcy5mYWN0b3IodmlldyksDQogICAgaGFzX2Jhc2VtZW50ID0gYXMuZmFjdG9yKGhhc19iYXNlbWVudCksDQogICAgcmVub3ZhdGVkID0gYXMuZmFjdG9yKHJlbm92YXRlZCkNCiAgKQ0KIyBTZXBhcmF0ZSBkYXRhIGZvciBtaXhlZC1lZmZlY3RzIG1vZGVsIHRvIGluY2x1ZGUgWklQDQptZV9kYXRhIDwtIGhvdXNpbmdfZGF0YV9mb3JfbW9kZWwgJT4lDQogIG11dGF0ZShaSVAgPSBhcy5mYWN0b3IoWklQKSkNCg0KIyBEYXRhIGZvciBvdGhlciBtb2RlbHMgd2hlcmUgWklQIGlzIG5vdCByZXF1aXJlZA0Kb3RoZXJfbW9kZWxzX2RhdGEgPC0gc2VsZWN0KGhvdXNpbmdfZGF0YV9mb3JfbW9kZWwsIC1aSVApDQoNCiMgQ3JlYXRlIHJlY2lwZXMgZm9yIG90aGVyIG1vZGVscyBhbmQgbWl4ZWQtZWZmZWN0cyBtb2RlbA0KcmVjX290aGVyIDwtIHJlY2lwZShsb2dfcHJpY2UgfiAuLCBkYXRhID0gb3RoZXJfbW9kZWxzX2RhdGEpICU+JQ0KICBzdGVwX25vcm1hbGl6ZShhbGxfbnVtZXJpY19wcmVkaWN0b3JzKCksIC1hbGxfb3V0Y29tZXMoKSkgJT4lDQogIHN0ZXBfZHVtbXkoYWxsX25vbWluYWxfcHJlZGljdG9ycygpLCAtYWxsX291dGNvbWVzKCkpDQoNCnJlY19tZSA8LSByZWNpcGUobG9nX3ByaWNlIH4gLiwgZGF0YSA9IG1lX2RhdGEpICU+JQ0KICBzdGVwX25vcm1hbGl6ZShhbGxfbnVtZXJpY19wcmVkaWN0b3JzKCksIC1hbGxfb3V0Y29tZXMoKSkgJT4lDQogIHN0ZXBfZHVtbXkoYWxsX25vbWluYWxfcHJlZGljdG9ycygpLCAtYWxsX291dGNvbWVzKCksIC1aSVApDQoNCiMgUHJlcHJvY2VzcyB0aGUgZGF0YSBmb3Igc3RlcHdpc2UgQUlDDQpwcmVwcm9jZXNzZWRfZGF0YSA8LSBwcmVwKHJlY19vdGhlcikgJT4lIGJha2UobmV3X2RhdGEgPSBOVUxMKQ0KcHJlcHJvY2Vzc2VkX21lX2RhdGEgPC0gcHJlcChyZWNfbWUpICU+JSBiYWtlKG5ld19kYXRhID0gTlVMTCkNCg0KIyBQZXJmb3JtIHN0ZXB3aXNlIEFJQyBmZWF0dXJlIHNlbGVjdGlvbg0KZnVsbF9tb2RlbCA8LSBsbShsb2dfcHJpY2UgfiAuLCBkYXRhID0gcHJlcHJvY2Vzc2VkX2RhdGEpDQpyZWR1Y2VkX21vZGVsIDwtIHN0ZXBBSUMoZnVsbF9tb2RlbCwgZGlyZWN0aW9uID0gImJvdGgiLCB0cmFjZSA9IFQpDQoNCiMgUHJpbnQgdGhlIHNlbGVjdGVkIHZhcmlhYmxlcw0Kc2VsZWN0ZWRfdmFycyA8LSBuYW1lcyhyZWR1Y2VkX21vZGVsJGNvZWZmaWNpZW50cylbLTFdDQpwcmludChwYXN0ZSgiU2VsZWN0ZWQgdmFyaWFibGVzOiIsIHBhc3RlKHNlbGVjdGVkX3ZhcnMsIGNvbGxhcHNlID0gIiwgIikpKQ0KDQojIFVwZGF0ZSB0aGUgZGF0YSBmb3IgbW9kZWxzIChleGNlcHQgbmV1cmFsIG5ldHdvcmspIHdpdGggdGhlIHNlbGVjdGVkIHZhcmlhYmxlcw0KbG1fcmZfZGF0YSA8LSBwcmVwcm9jZXNzZWRfZGF0YSAlPiUNCiAgc2VsZWN0KGxvZ19wcmljZSwgYWxsX29mKHNlbGVjdGVkX3ZhcnMpKQ0KDQptZV9kYXRhX3NlbGVjdGVkIDwtIHByZXByb2Nlc3NlZF9tZV9kYXRhICU+JQ0KICBzZWxlY3QobG9nX3ByaWNlLCBaSVAsIGFsbF9vZihzZWxlY3RlZF92YXJzKSkNCg0KIyBDaGVjayBmb3IgbXVsdGljb2xsaW5lYXJpdHkgdXNpbmcgVklGDQp2aWZfbW9kZWwgPC0gbG0obG9nX3ByaWNlIH4gLiwgZGF0YSA9IGxtX3JmX2RhdGEpDQp2aWZfdmFsdWVzIDwtIHZpZih2aWZfbW9kZWwpDQpwcmludCh2aWZfdmFsdWVzKQ0KYGBgDQpWSUYgaXMgYmVsbG93IDUgZm9yIGFsbCBwcmVkaWN0b3JzIHNvIHdlIGhhdmUgYWRkcmVzc2VkIGNvbGluZWFyaXR5IHByb2JsZW1zLg0KDQpgYGB7cn0NCmxpYnJhcnkobXVsdGlsZXZlbG1vZCkNCnNldC5zZWVkKDMzMykNCg0KIyMgQ3JlYXRlIGNyb3NzLXZhbGlkYXRpb24gZm9sZHMgZm9yIGVhY2ggbW9kZWwgdHlwZQ0KY3ZfZm9sZHNfbG1fcmYgPC0gdmZvbGRfY3YobG1fcmZfZGF0YSwgdiA9IDEwKQ0KY3ZfZm9sZHNfbWUgPC0gdmZvbGRfY3YobWVfZGF0YV9zZWxlY3RlZCwgdiA9IDEwKQ0KY3ZfZm9sZHNfbm4gPC0gdmZvbGRfY3YoaG91c2luZ19kYXRhX2Zvcl9tb2RlbCwgdiA9IDEwKQ0KDQojIENyZWF0ZSByZWNpcGVzIGZvciBtb2RlbHMNCnJlY19sbV9yZiA8LSByZWNpcGUobG9nX3ByaWNlIH4gLiwgZGF0YSA9IGxtX3JmX2RhdGEpDQpyZWNfbWUgPC0gcmVjaXBlKGxvZ19wcmljZSB+IC4sIGRhdGEgPSBtZV9kYXRhX3NlbGVjdGVkKQ0KcmVjX25uIDwtIHJlY2lwZShsb2dfcHJpY2UgfiAuLCBkYXRhID0gaG91c2luZ19kYXRhX2Zvcl9tb2RlbCkgJT4lDQogIHN0ZXBfbm9ybWFsaXplKGFsbF9udW1lcmljX3ByZWRpY3RvcnMoKSwgLWFsbF9vdXRjb21lcygpKSAlPiUNCiAgc3RlcF9kdW1teShhbGxfbm9taW5hbF9wcmVkaWN0b3JzKCksIC1hbGxfb3V0Y29tZXMoKSkNCg0KIyBEZWZpbmUgdGhlIG1vZGVscw0KbG1fbW9kZWwgPC0gbGluZWFyX3JlZygpICU+JSBzZXRfZW5naW5lKCJsbSIpICU+JSBzZXRfbW9kZSgicmVncmVzc2lvbiIpDQpyZl9tb2RlbCA8LSByYW5kX2ZvcmVzdCh0cmVlcyA9IDEwMDApICU+JSBzZXRfbW9kZSgicmVncmVzc2lvbiIpICU+JSBzZXRfZW5naW5lKCJyYW5nZXIiLCBpbXBvcnRhbmNlID0gImltcHVyaXR5IikNCm1lX21vZGVsIDwtIGxpbmVhcl9yZWcoKSAlPiUgc2V0X2VuZ2luZSgibG1lciIpICU+JSBzZXRfbW9kZSgicmVncmVzc2lvbiIpDQpubl9tb2RlbCA8LSBtbHAoaGlkZGVuX3VuaXRzID0gMTApICU+JSBzZXRfZW5naW5lKCJubmV0IiwgbGlub3V0ID0gVFJVRSwgdHJhY2UgPSBUKSAlPiUgc2V0X21vZGUoInJlZ3Jlc3Npb24iKQ0KDQojIENyZWF0ZSB3b3JrZmxvd3MNCmxtX3dvcmtmbG93IDwtIHdvcmtmbG93KCkgJT4lIGFkZF9yZWNpcGUocmVjX2xtX3JmKSAlPiUgYWRkX21vZGVsKGxtX21vZGVsKQ0KcmZfd29ya2Zsb3cgPC0gd29ya2Zsb3coKSAlPiUgYWRkX3JlY2lwZShyZWNfbG1fcmYpICU+JSBhZGRfbW9kZWwocmZfbW9kZWwpDQptZV93b3JrZmxvdyA8LSB3b3JrZmxvdygpICU+JSBhZGRfcmVjaXBlKHJlY19tZSkgJT4lIGFkZF9tb2RlbChtZV9tb2RlbCwgZm9ybXVsYSA9IGxvZ19wcmljZSB+IC4gKyAoMSB8IFpJUCkpDQpubl93b3JrZmxvdyA8LSB3b3JrZmxvdygpICU+JSBhZGRfcmVjaXBlKHJlY19ubikgJT4lIGFkZF9tb2RlbChubl9tb2RlbCkNCg0KIyBQZXJmb3JtIGNyb3NzLXZhbGlkYXRpb24NCmxtX2N2X3Jlc3VsdHMgPC0gZml0X3Jlc2FtcGxlcyhsbV93b3JrZmxvdywgcmVzYW1wbGVzID0gY3ZfZm9sZHNfbG1fcmYsIG1ldHJpY3MgPSBtZXRyaWNfc2V0KHJtc2UsIG1hZSwgcnNxKSkNCnJmX2N2X3Jlc3VsdHMgPC0gZml0X3Jlc2FtcGxlcyhyZl93b3JrZmxvdywgcmVzYW1wbGVzID0gY3ZfZm9sZHNfbG1fcmYsIG1ldHJpY3MgPSBtZXRyaWNfc2V0KHJtc2UsIG1hZSwgcnNxKSkNCm1lX2N2X3Jlc3VsdHMgPC0gZml0X3Jlc2FtcGxlcyhtZV93b3JrZmxvdywgcmVzYW1wbGVzID0gY3ZfZm9sZHNfbWUsIG1ldHJpY3MgPSBtZXRyaWNfc2V0KHJtc2UsIG1hZSwgcnNxKSkNCm5uX2N2X3Jlc3VsdHMgPC0gZml0X3Jlc2FtcGxlcyhubl93b3JrZmxvdywgcmVzYW1wbGVzID0gY3ZfZm9sZHNfbm4sIG1ldHJpY3MgPSBtZXRyaWNfc2V0KHJtc2UsIG1hZSwgcnNxKSkNCg0KIyBTdW1tYXJpemUgYW5kIGNvbXBhcmUgdGhlIGNyb3NzLXZhbGlkYXRpb24gcmVzdWx0cw0KY3ZfcmVzdWx0cyA8LSBiaW5kX3Jvd3MoDQogIGNvbGxlY3RfbWV0cmljcyhsbV9jdl9yZXN1bHRzKSAlPiUgbXV0YXRlKG1vZGVsID0gIkxpbmVhciBSZWdyZXNzaW9uIiksDQogIGNvbGxlY3RfbWV0cmljcyhyZl9jdl9yZXN1bHRzKSAlPiUgbXV0YXRlKG1vZGVsID0gIlJhbmRvbSBGb3Jlc3QiKSwNCiAgY29sbGVjdF9tZXRyaWNzKG1lX2N2X3Jlc3VsdHMpICU+JSBtdXRhdGUobW9kZWwgPSAiTWl4ZWQgRWZmZWN0cyIpLA0KICBjb2xsZWN0X21ldHJpY3Mobm5fY3ZfcmVzdWx0cykgJT4lIG11dGF0ZShtb2RlbCA9ICJOZXVyYWwgTmV0d29yayIpDQopDQoNCmN2X3N1bW1hcnkgPC0gY3ZfcmVzdWx0cyAlPiUNCiAgZ3JvdXBfYnkobW9kZWwsIC5tZXRyaWMpICU+JQ0KICBzdW1tYXJpemUoDQogICAgbWVhbl92YWx1ZSA9IG1lYW4obWVhbiwgbmEucm0gPSBUUlVFKSwNCiAgICBzdGRfZXJyb3IgPSBtZWFuKHN0ZF9lcnIsIG5hLnJtID0gVFJVRSksDQogICAgLmdyb3VwcyA9ICdkcm9wJw0KICApICU+JQ0KICBwaXZvdF93aWRlcihuYW1lc19mcm9tID0gLm1ldHJpYywgdmFsdWVzX2Zyb20gPSBjKG1lYW5fdmFsdWUsIHN0ZF9lcnJvcikpDQoNCnByaW50KGN2X3N1bW1hcnkpDQojIFNwbGl0IHRoZSBkYXRhIGludG8gdHJhaW5pbmcgYW5kIHRlc3Rpbmcgc2V0cyBmb3IgZWFjaCBtb2RlbCB0eXBlDQpkYXRhX3NwbGl0X2xtX3JmIDwtIGluaXRpYWxfc3BsaXQobG1fcmZfZGF0YSwgcHJvcCA9IDAuOCkNCnRyYWluX2RhdGFfbG1fcmYgPC0gdHJhaW5pbmcoZGF0YV9zcGxpdF9sbV9yZikNCnRlc3RfZGF0YV9sbV9yZiA8LSB0ZXN0aW5nKGRhdGFfc3BsaXRfbG1fcmYpDQoNCmRhdGFfc3BsaXRfbWUgPC0gaW5pdGlhbF9zcGxpdChtZV9kYXRhX3NlbGVjdGVkLCBwcm9wID0gMC44KQ0KdHJhaW5fZGF0YV9tZSA8LSB0cmFpbmluZyhkYXRhX3NwbGl0X21lKQ0KdGVzdF9kYXRhX21lIDwtIHRlc3RpbmcoZGF0YV9zcGxpdF9tZSkNCg0KZGF0YV9zcGxpdF9ubiA8LSBpbml0aWFsX3NwbGl0KGhvdXNpbmdfZGF0YV9mb3JfbW9kZWwsIHByb3AgPSAwLjgpDQp0cmFpbl9kYXRhX25uIDwtIHRyYWluaW5nKGRhdGFfc3BsaXRfbm4pDQp0ZXN0X2RhdGFfbm4gPC0gdGVzdGluZyhkYXRhX3NwbGl0X25uKQ0KDQojIEZpdCBhbmQgZXZhbHVhdGUgZWFjaCBtb2RlbCB1c2luZyBsYXN0X2ZpdCgpDQpsbV9sYXN0X2ZpdCA8LSBsYXN0X2ZpdChsbV93b3JrZmxvdywgZGF0YV9zcGxpdF9sbV9yZikNCnJmX2xhc3RfZml0IDwtIGxhc3RfZml0KHJmX3dvcmtmbG93LCBkYXRhX3NwbGl0X2xtX3JmKQ0KbWVfbGFzdF9maXQgPC0gbGFzdF9maXQobWVfd29ya2Zsb3csIGRhdGFfc3BsaXRfbWUpDQpubl9sYXN0X2ZpdCA8LSBsYXN0X2ZpdChubl93b3JrZmxvdywgZGF0YV9zcGxpdF9ubikNCg0KIyBFeHRyYWN0IHByZWRpY3Rpb25zIGFuZCBtZXRyaWNzIGZvciBlYWNoIG1vZGVsDQpsbV9yZXN1bHRzIDwtIGxtX2xhc3RfZml0ICU+JSBjb2xsZWN0X3ByZWRpY3Rpb25zKCkgJT4lIG11dGF0ZShtb2RlbCA9ICJMaW5lYXIgUmVncmVzc2lvbiIpDQpyZl9yZXN1bHRzIDwtIHJmX2xhc3RfZml0ICU+JSBjb2xsZWN0X3ByZWRpY3Rpb25zKCkgJT4lIG11dGF0ZShtb2RlbCA9ICJSYW5kb20gRm9yZXN0IikNCm1lX3Jlc3VsdHMgPC0gbWVfbGFzdF9maXQgJT4lIGNvbGxlY3RfcHJlZGljdGlvbnMoKSAlPiUgbXV0YXRlKG1vZGVsID0gIk1peGVkIEVmZmVjdHMiKQ0Kbm5fcmVzdWx0cyA8LSBubl9sYXN0X2ZpdCAlPiUgY29sbGVjdF9wcmVkaWN0aW9ucygpICU+JSBtdXRhdGUobW9kZWwgPSAiTmV1cmFsIE5ldHdvcmsiKQ0KDQojIENvbWJpbmUgdGhlIHJlc3VsdHMNCnJlc3VsdHMgPC0gYmluZF9yb3dzKGxtX3Jlc3VsdHMsIHJmX3Jlc3VsdHMsIG1lX3Jlc3VsdHMsIG5uX3Jlc3VsdHMpDQoNCiMgUGxvdCBwcmVkaWN0ZWQgdnMuIGFjdHVhbCBmb3IgZWFjaCBtb2RlbCB3aXRoIFJeMg0KcmVzdWx0cyAlPiUNCiAgZ2dwbG90KGFlcyh4ID0gbG9nX3ByaWNlLCB5ID0gLnByZWQsIGNvbG9yID0gbW9kZWwpKSArDQogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjUpICsNCiAgZ2VvbV9hYmxpbmUoc2xvcGUgPSAxLCBpbnRlcmNlcHQgPSAwLCBsaW5ldHlwZSA9ICJkYXNoZWQiKSArDQogIGZhY2V0X3dyYXAofiBtb2RlbCwgc2NhbGVzID0gImZyZWUiKSArDQogIGxhYnMoDQogICAgdGl0bGUgPSAiUHJlZGljdGVkIHZzLiBBY3R1YWwgSG91c2UgUHJpY2VzIiwNCiAgICB4ID0gIkFjdHVhbCBQcmljZSIsDQogICAgeSA9ICJQcmVkaWN0ZWQgUHJpY2UiDQogICkgKw0KICB0aGVtZV9taW5pbWFsKCkgKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpICsNCiAgZ2VvbV90ZXh0KA0KICAgIGRhdGEgPSByZXN1bHRzICU+JQ0KICAgICAgZ3JvdXBfYnkobW9kZWwpICU+JQ0KICAgICAgc3VtbWFyaXNlKHJzcSA9IGNvcihsb2dfcHJpY2UsIC5wcmVkKV4yKSwNCiAgICBhZXMoeCA9IEluZiwgeSA9IC1JbmYsIGxhYmVsID0gc3ByaW50ZigiUl4yID0gJS4zZiIsIHJzcSkpLA0KICAgIGhqdXN0ID0gMS4xLA0KICAgIHZqdXN0ID0gLTEuMQ0KICApDQpgYGANClRoZSBtb2RlbCB3aXRoIHRoZSBiZXN0IHByZWRpY3RpdmUgYWNjdXJhY3kgd2FzIHRoZSByYW5kb20gZm9yZXN0IG1vZGVsLCBmb2xsb3dlZCBieSB0aGUgbGluZWFyIHJlZ3Jlc3Npb24gbW9kZWwsIG5ldXJhbCBuZXR3b3JrIG1vZGVsLCBhbmQgbWl4ZWQgZWZmZWN0cyBtb2RlbC4gSXNzdWVzIHdpdGggbW9kZWwgZml0dGluZyBhcmUgY2xlYXIgd2l0aCB0aGUgbWl4ZWQgZWZmZWN0cyBtb2RlbCBkdWUgdG8gdGhlIHN0cm9uZyBsZWZ0IHNrZXcgaW4gdGhlIGVycm9yLg0KDQpgYGB7cn0NCiMgRXh0cmFjdCB0aGUgdHJhaW5lZCBSYW5kb20gRm9yZXN0IG1vZGVsIGZyb20gdGhlIGxhc3RfZml0IG9iamVjdA0KcmZfbW9kZWwgPC0gcmZfbGFzdF9maXQgJT4lIA0KICBleHRyYWN0X2ZpdF9lbmdpbmUoKQ0KDQojIEV4dHJhY3QgdmFyaWFibGUgaW1wb3J0YW5jZSBmcm9tIHRoZSBSYW5kb20gRm9yZXN0IG1vZGVsDQpyZl9pbXBvcnRhbmNlIDwtIHJmX21vZGVsICU+JSANCiAgcmFuZ2VyOjppbXBvcnRhbmNlKCkNCg0KIyBQbG90IHZhcmlhYmxlIGltcG9ydGFuY2UNCmdncGxvdChkYXRhLmZyYW1lKHZhcmlhYmxlID0gbmFtZXMocmZfaW1wb3J0YW5jZSksIGltcG9ydGFuY2UgPSByZl9pbXBvcnRhbmNlKSwgDQogICAgICAgYWVzKHggPSByZW9yZGVyKHZhcmlhYmxlLCBpbXBvcnRhbmNlKSwgeSA9IGltcG9ydGFuY2UsIGZpbGwgPSBpbXBvcnRhbmNlKSkgKw0KICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IikgKw0KICBjb29yZF9mbGlwKCkgKw0KICBsYWJzKHggPSAiVmFyaWFibGUiLCB5ID0gIkltcG9ydGFuY2UiLCB0aXRsZSA9ICJSYW5kb20gRm9yZXN0IFZhcmlhYmxlIEltcG9ydGFuY2UiKSArDQogIHRoZW1lX21pbmltYWwoKQ0KDQpgYGANClNRRlQgb2YgbGl2aW5nIHNwYWNlIGZvbGxvd2VkIGJ5IG9jY3VwYXRpb24gcHJldmFsZW5jZSBhcHBlYXIgdG8gYmUgbW9zdCBwcmVkaWN0aXZlIG9mIGhvdXNpbmcgcHJpY2UuIFRoaW5ncyBsaWtlIHZpZXcsIGFuZCBjb21tdXRpbmcgdGltZSBhcmUgc3VwcmlzaW5nbHkgbXVjaCBsb3dlciBieSBjb21wYXJpc29uIQ0KYGBge3J9DQojIEV4dHJhY3QgdGhlIHRyYWluZWQgbW9kZWxzIGZyb20gdGhlIGxhc3RfZml0IG9iamVjdHMNCmxtX21vZGVsIDwtIGxtX2xhc3RfZml0ICU+JSBleHRyYWN0X2ZpdF9lbmdpbmUoKQ0KcmZfbW9kZWwgPC0gcmZfbGFzdF9maXQgJT4lIGV4dHJhY3RfZml0X2VuZ2luZSgpDQptZV9tb2RlbCA8LSBtZV9sYXN0X2ZpdCAlPiUgZXh0cmFjdF9maXRfZW5naW5lKCkNCm5uX21vZGVsIDwtIG5uX2xhc3RfZml0ICU+JSBleHRyYWN0X2ZpdF9lbmdpbmUoKQ0KDQojIEV4dHJhY3QgY29lZmZpY2llbnRzIGZyb20gdGhlIExpbmVhciBSZWdyZXNzaW9uIG1vZGVsDQpsbV9jb2VmIDwtIGNvZWYobG1fbW9kZWwpDQpsbV9jb2VmX2RmIDwtIGRhdGEuZnJhbWUodmFyaWFibGUgPSBuYW1lcyhsbV9jb2VmKSwgY29lZmZpY2llbnQgPSBsbV9jb2VmKQ0KDQojIFBsb3QgY29lZmZpY2llbnRzIGZvciB0aGUgTGluZWFyIFJlZ3Jlc3Npb24gbW9kZWwNCmdncGxvdChsbV9jb2VmX2RmLCBhZXMoeCA9IHJlb3JkZXIodmFyaWFibGUsIGNvZWZmaWNpZW50KSwgeSA9IGNvZWZmaWNpZW50LCBmaWxsID0gY29lZmZpY2llbnQpKSArDQogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiKSArDQogIGNvb3JkX2ZsaXAoKSArDQogIGxhYnMoeCA9ICJWYXJpYWJsZSIsIHkgPSAiQ29lZmZpY2llbnQiLCB0aXRsZSA9ICJMaW5lYXIgUmVncmVzc2lvbiBDb2VmZmljaWVudHMiKSArDQogIHRoZW1lX21pbmltYWwoKQ0KYGBgDQpDb252ZXJzbHkgaW4gdGhlIGxpbmVhciBtb2RlbCwgaGF2aW5nIGFuIG91dHN3dGFuZGluZyB2aWV3IHNlZW1zIHRvIGJlIHRoZSBtb3N0IGltcG9ydGFudCBwcmVkaWN0b3IuIFZlcnkgZGlmZmVyZW50IGFwcG9yYWNoZXMhDQoNCg==