Version 1.0 | First Created Oct 3, 2023 | Updated Oct 11, 2023

Keywords: Ordinary Least Square Regression, Spatial Auto-correlation, Moran’s I, Monte Carlo Simulation, Getis-Ord Gi*, K-fold Cross Validation, K Nearest Neighbors

Introduction

Machine learning is a powerful tool in the field of city planning for its ability to efficiently process vast amounts of data, discern intricate patterns, and generate predictions on the unknown, which consequently guides municipal decision-making. Among all kinds of machine learning techniques, the Ordinary Least Squares (OLS) regression has emerged as a powerful tool for predicting house prices.

Property values are influenced by a variety of social, environmental, economic, and cultural factors that is hard to capture using pure data analysis and visualization. At the same time, it remains a fundamental aspect of city planning and development: predicting sale prices is essential for fostering a transparent, stable, and efficient real estate market. For the city government, accurate property value prediction enables them to plan infrastructure development, allocate resources efficiently, and formulate housing policies that cater to the needs of diverse populations. For individual householders, it helps with making informed decisions regarding property investments and home-ownership, ensuring financial stability and fostering sustainable communities.

The city of Philadelphia is known for complexities in real estate investment. Its housing market reveal stark disparities rooted in geography, race, and income. Neighborhoods in the city exhibit vast differences in home prices that in part foreshadows neighborhood social-demographic characteristics, access to amenities, housing types, and historical patterns of investment and disinvestment. In particular, Center City (plus its surrounding neighborhoods) and Northwest Philadelphia command high property values due to their proximity to amenities and picturesque neighborhood, whereas areas in Northeast and West Philadelphia grapple with lower home prices due to systematic income equities, racial tensions, and limited access to opportunities.

In this report, we attempted to predict property values in Philadelphia with OLS regression. We acknowledge the difficulties of making such prediction using a linear regression model, because the city’s diverse and multifaceted housing market shaped by historical injustices and social disparities would defy the simplistic linear assumptions of OLS regression. That said, our goal is to fit a linear regression model that captures the internal characteristics, surrounding public services, and spatial characteristics of properties in Philadelphia as much as possible. The main data set used in this report was obtained from PHILA.GOV that contains a variety of property characteristics, including sale price. In addition, we have made use of resources from OpenDataPhilly, US Census Bureau, and Open Data PHL Maps to gain more insights into the distribution of social amenities, location of crimes incidents, and neighborhood characteristics within the city.

This report is therefore structured as follows. Firstly, we discuss our rational of our selection of dependent variables as well as our approach to data manipulation and feature engineering using our main housing data set. Secondly, we present our findings from performing both a global and a local G test for spatial autocorrelation. This is followed by another discussion on the importance of accounting for spatial features when predicting property values and a comparison of regression models with and without spatial features. Thirdly, we present a holistic summary of all of our selected and processed dependent variables that we will use for our final model. Finally, we make predictions of sale price using our model. This is supplemented with a variety of tests of checking model’s generalizability and goodness of fit as well as a reflection on the model’s effectiveness and bias.

Overall, our model is able to capture the majority of variations in property values throughout Philadelphia. However, we are critical of this model’s effectiveness in real world setting as it seems to significantly under-predict property values in Philadelphia, especially for lower-income racial minority neighborhoods. Given Philadelphia’s diverse and multifaceted housing market, we suspect that this model had failed to account for some of the intra-neighborhood nuances cannot be properly generalized and represented using a linear model. To fully account for variations in home price, we must get a better understanding of the social and historical landscape of Philadelphia to select predictor variables that contextualize with the housing characteristics of the city and avoid accidentally devaluing certain neighborhoods.

The GitHub repository for this report is here.

Data Processing

Based on our prior knowledge in house price and the given data set, we believe that the sale price of houses may be related to both the internal and geographical attributes of the houses. Internal attributes refer to factors such as the age of the house, the number of bedrooms and bathrooms, the presence of amenities, and the overall condition of the house. (We will elaborate on geographical attributes later in the report).

Regarding internal attributes, we selected the following variables from: the year the house was built, the number of bedrooms, the number of bathrooms, the number of air conditioning units, basement condition, the number of fireplaces, garage condition, the number of stories, total area, livable area, heating condition, view category, quality grade and the type of building description. Other indicators related to house sale prices, such as topography, internal condition, and external condition, were not included in the scope of indicators due to their correlation with the selected indicators.

We calculated the age of the houses by subtracting 2023 from the year they were built and used it as a new time measurement indicator. Since the number of bedrooms and bathrooms are correlated, we used the total number of rooms as a new indicator. Specific quantities or detailed conditions of air conditioning, basement, fireplace, garage, and heating did not have a significant impact individually, but their presence or absence had a significant impact on house prices. Therefore, we created new binary variables for each of them. Regarding the number of stories, we collapsed their categories into single, double, and multiple. For total area, we chose the larger of the total area and livable area as the measurement indicator and applied a logarithmic transformation to achieve a normal distribution. For the view type near the house, we summarized the categories into three classes: Scenic (including Cityscape/Skyline, Flowing Water, Park/Green Area), Typical, and Others (Urban). For quality and building type, we performed similar processing, combining indicators above “average” into a “good” category and those “average” or below into a “bad” category.

tr_processed <- training %>% 
  mutate(Age = 2023 - year_built) %>% 
  mutate(numRooms = case_when(
         is.na(number_of_bedrooms) & !is.na(number_of_bedrooms) ~ number_of_bathrooms,
         is.na(number_of_bathrooms) & !is.na(number_of_bedrooms) ~ number_of_bedrooms,
         is.na(number_of_bathrooms) & is.na(number_of_bedrooms) ~ 0,
         TRUE~ number_of_bedrooms + number_of_bathrooms) )%>% 
  mutate(hasAC = case_when(
         central_air %in% c("1", "Y") ~ "Y",
         TRUE~ "N"),
         hasBasement = case_when(
         basements %in% c("1", "4", "A", "B", "C", "D", "E", "F") ~ "Y",
         TRUE~ "N"),
         hasFireplace = case_when(
         fireplaces == 0 | is.na(fireplaces)  ~ "N",
         TRUE~ "Y"),
         hasGarage = case_when(
         garage_type == 0 | is.na(garage_type)  ~ "N",
         TRUE~ "Y"),
         stories = case_when(
         number_stories == 1  ~ "single",
         number_stories == 2  ~ "double",
         TRUE~ "multiple"),
         area = case_when(
           total_livable_area > total_area ~ total_livable_area,  
           TRUE ~ total_area),
         hasHeater = case_when(
         type_heater == 0 | is.na(type_heater)  ~ "N",
         TRUE~ "Y"),
         view = case_when(
         view_type == "I" | view_type == "0" | is.na(view_type)   ~ "Typical",
         view_type == "A" | view_type == "B" | view_type == "C" ~ "Scenic",
         TRUE~ "Urban"), 
         quality = case_when(
         quality_grade %in% c("4", "5", "6", "A", "A+", "A-", "B", "B+","B-","S","S+","X-")  ~ "Good",
         TRUE~ "Bad"),
         buildingdis = case_when(
         grepl("ROW",building_code_description_new, ignore.case = FALSE) ~ "Row",
         grepl("TWIN",building_code_description_new, ignore.case = FALSE) ~ "TWIN",
         TRUE~ "Other")) %>% 
  mutate(logarea = log(area))

In addition to these transformations, we excluded unreasonable outliers in the training data set. We selected houses where age is less than 500 years, room count is less than 30, and both total house area and livable house area are non-zero or missing. We also excluded houses with area larger than 50,000 square feet because these are not typical single family houses. We then limited the housing prices in the training data set to be below $2,000,000 to reduce the influence of extreme values on the model.

baseline <- tr_processed %>% 
  filter(toPredict != "CHALLENGE") %>%
  filter(Age < 500) %>% 
  filter(sale_price < 2000000) %>% 
  filter(numRooms < 30) %>% 
  filter(total_livable_area != 0 & is.na(total_area) == FALSE) %>% 
  filter(area < 50000) 

Fitting Regression Model

Baseline Regression

We conducted an initial regression and obtained the following results. All indicators show statistically significant correlations with house sale prices with p < 0.001. The adjusted R-squared value is 0.443, indicating that the model can explain 44.3% of variations in sale price. The standard errors for predicting the presence of a fireplace, garage, and landscape type are greater than 5000, which may affect the precision of the model, but this is the optimal result we achieved so far. The Generalized Inverse Variance Inflation Factor (GIVF) for each indicator is less than 3, indicating that there are no issues of multicollinearity in the model.

reg1 <- lm(sale_price ~ ., data = baseline %>% st_drop_geometry() %>% 
                                 dplyr::select(sale_price, Age, numRooms, hasBasement, hasAC, quality, buildingdis, 
                                               hasFireplace, hasGarage, stories, logarea, view))
summary_reg1 <- summary(reg1)
coefficients_table <- as.data.frame(summary_reg1$coefficients)

coefficients_table$significance <- ifelse(coefficients_table$`Pr(>|t|)` < 0.001, '***',
                                         ifelse(coefficients_table$`Pr(>|t|)` < 0.01, '**',
                                                ifelse(coefficients_table$`Pr(>|t|)` < 0.05, '*',
                                                       ifelse(coefficients_table$`Pr(>|t|)` < 0.1, '.', ''))))

coefficients_table$p_value <- paste0(round(coefficients_table$`Pr(>|t|)`, digits = 3), coefficients_table$significance)

coefficients_table %>%
  select(-significance, -`Pr(>|t|)`) %>% 
  kable(align = "r") %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed")) %>%
  footnote(general_title = "\n", general = "Table 1")
Estimate Std. Error t value p_value
(Intercept) -459209 21058.6 -21.81 0***
Age -168 37.8 -4.46 0***
numRooms 4253 638.3 6.66 0***
hasBasementY 11262 2124.1 5.30 0***
hasACY 133857 2324.0 57.60 0***
qualityGood 127945 4931.6 25.94 0***
buildingdisRow -9345 4254.9 -2.20 0.028*
buildingdisTWIN -51329 4314.6 -11.90 0***
hasFireplaceY 135353 5627.4 24.05 0***
hasGarageY -35223 8099.3 -4.35 0***
storiesmultiple 167908 3197.6 52.51 0***
storiessingle 11213 3026.8 3.71 0***
logarea 97509 2277.2 42.82 0***
viewTypical -31620 5019.4 -6.30 0***
viewUrban -47331 8632.5 -5.48 0***

Table 1
model_summary <- data.frame(
  Statistic = c("Multiple R-squared", "Adjusted R-squared", "F-statistic"),
  Value = c(
    summary_reg1$r.squared,
    summary_reg1$adj.r.squared,
    summary_reg1$fstatistic[1]
  )
)

model_summary %>%
  kable() %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"))%>%
  footnote(general_title = "\n", general = "Table 2")
Statistic Value
Multiple R-squared 0.444
Adjusted R-squared 0.443
F-statistic 1342.545

Table 2
new_variable_names <- c(
  "Age",
  "Number of Rooms",
  "Basement",
  "Air Conditioner",
  "Quality Grade",
  "Building Type",
  "Fireplace",
  "Garage",
  "Number Stories",
  "Type of View",
  "Area"
)
vif_reg1 <- vif(reg1)
vif_df <- as.data.frame(vif_reg1)
vif_df$Variable <- new_variable_names
vif_df <- vif_df[, c("Variable", names(vif_df)[!names(vif_df) %in% "Variable"])]

vif_df %>%
  kable(row.names = FALSE) %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed")) %>%
  footnote(general_title = "\n", general = "Table 3")
Variable GVIF Df GVIF^(1/(2*Df))
Age 1.26 1 1.12
Number of Rooms 1.25 1 1.12
Basement 1.15 1 1.07
Air Conditioner 1.25 1 1.12
Quality Grade 1.11 1 1.05
Building Type 2.05 2 1.20
Fireplace 1.13 1 1.06
Garage 1.02 1 1.01
Number Stories 1.49 2 1.10
Type of View 1.68 1 1.30
Area 1.03 2 1.01

Table 3

According to the residual plot, the scatter points exhibit divergent and irregular patterns around y=0. Most points are concentrated in the upper part, suggesting that the original model does not have a linear relationship. The distribution trend of scatter points slants upwards, indicating an underestimation tendency to some extent.

residuals_df <- data.frame(Residuals = resid(reg1), Fitted = fitted(reg1))
ggplot(residuals_df, aes(x = Fitted, y = Residuals)) +
  geom_point(size = 0.4, color = "#283d3b") +
  geom_hline(yintercept = 0, linetype = "dashed", color = "#c44536") +
  labs(title = "Residual Plot for Baseline Regression",
       subtitle = "Each dot represent one property ",
       caption = "Data: Philadelphia Properties and Current Assessments", 
       x = "Fitted Values",
       y = "Residuals") +
  theme_light() +   
  theme(plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 12, face = "bold"), 
        axis.text.x=element_text(size=6),
        axis.text.y=element_text(size=6), 
        axis.title=element_text(size=8))

Using the baseline model, we predicted the sale price through splitting the data into training and testing set, and calculated the Mean Absolute Error (MAE) and Mean Absolute Percentage Error (MAPE) for our testing set. The current MAE and MAPE are 98329 and 39%, respectively.

set.seed(485) 
inTrain_base <- createDataPartition(
   y = paste(baseline$hasBasement, baseline$hasAC, baseline$quality, baseline$buildingdis, baseline$hasFireplace, baseline$hasGarage, baseline$stories, baseline$view),
              p = .60, list = FALSE)

tr_processed.training <- baseline[inTrain_base,] 
tr_processed.test <- baseline[-inTrain_base,]  


reg.train_base <- lm(sale_price ~ ., data = tr_processed.training %>% st_drop_geometry() %>% 
                                 dplyr::select(sale_price, Age, numRooms, hasBasement, hasAC, quality, buildingdis, 
                                               hasFireplace, hasGarage, stories, logarea, view))
tr_processed.test <- 
  tr_processed.test %>% 
  mutate(Regression = "Base", 
         SalePrice.Predict = predict(reg.train_base, tr_processed.test),
         SalePrice.Error = SalePrice.Predict - sale_price,
         SalePrice.AbsError = abs(SalePrice.Predict - sale_price),
         SalePrice.APE = (abs(SalePrice.Predict - sale_price)) / SalePrice.Predict)

tr_processed.test %>% 
  st_drop_geometry() %>%
  summarise(MAE = mean(SalePrice.AbsError),
            MAPE = mean(SalePrice.APE)*100) %>%
  kbl(col.name=c('Mean Absolute Error','Mean Absolute Percentage Error')) %>%
  kable_styling( bootstrap_options = c("striped", "hover", "condensed")) %>%
  footnote(general_title = "\n", general = "Table 4")
Mean Absolute Error Mean Absolute Percentage Error
98329 39

Table 4

Spatial Autocorrelation Check

Using baseline regression to predict sale price only tells part of the story because property values are also closed related to their location in the city. Properties in close proximity often share similar attributes due to neighborhood amenities, socioeconomic factors, and infrastructural development. Therefore, we need to check the degree to which sale price of properties close to each other are correlated. We create a spatial weight matrix that relate each sale price to five of its nearest sale prices. We computed Moran’s I under the null hypothesis that sale price is randomly distributed. The figure below plot the frequency of 999 randomly permutated I under Monte Carlo simulation, which shows that our observed I is much higher than all of the randomly generated I’s. This validates the existence of spatial autocorrelation.

coords.test <-  st_coordinates(tr_processed.test) 
neighborList.test <- knn2nb(knearneigh(coords.test, 5))
spatialWeights.test <- nb2listw(neighborList.test, style="W")

moranTest <- moran.mc(tr_processed.test$SalePrice.Error, 
                      spatialWeights.test, nsim = 999)

ggplot(as.data.frame(moranTest$res[c(1:999)]), aes(moranTest$res[c(1:999)])) +
  geom_histogram(binwidth = 0.01, fill = "#283d3b") +
  geom_vline(aes(xintercept = moranTest$statistic), colour = "#c44536" ,size=1) +
  scale_x_continuous(limits = c(-0.5, 0.5)) +
  labs(title = "Observed and Permuted Moran's I",
       subtitle = "Observed Moran's I in Red",
       x = "Moran's I",
       y = "Count") +
  theme_light() +   
theme(plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 12, face = "bold"), 
        axis.text.x=element_text(size=6),
        axis.text.y=element_text(size=6), 
        axis.title=element_text(size=8))

Spatial autocorrelation can be evaluated at a global or local level. While global spatial autocorrelation provides an overall assessment of the spatial pattern across the entire study area, a local indicator of spatial association (LISA) identifies hotspots and coldspots of high or low values for each feature, offering a more detailed analysis of spatial patterns. Relying solely on global tests might overlook localized patterns within the data. In our case, we are interested in how high or low sale price clustered in Philadelphia. To do that, we can calculate the Gi statistic using local_g_perm() function in the sfdep package. The Gi is the ratio of the spatial lag of a feature to the sum of the feature’s values for its neighbors. A positive Gi value indicates that a feature and its neighbors have high values, while a negative Gi value indicates that they have low values. The magnitude of the Gi value indicates the strength of the clustering. Note: we used the block groups in Philadelphia as the spatial unit, to which we joined the sale price data in and summarized the mean sale price for each block group.

tr_block <- st_join(tr_processed, block %>% select(GEOID10))

gi_data <- tr_block %>% 
  st_drop_geometry() %>% 
  select(GEOID10, sale_price) %>% 
  group_by(GEOID10) %>% 
  summarize(mean_sp = mean(sale_price)) %>% 
  na.omit() %>% 
  left_join(block) %>% 
  st_sf() %>% 
  st_make_valid()

We may see from the plot below that there are some small significant clusters of sale price in Philadelphia. In particular, we see hotspot, meaning continuous numbers of high sale price clustered in Northwest Philadelphia and some clusters of cold spot, meaning continuous numbers of low sale price in North Philadelphia. This again point out the importance to account for spatial features when predicting sale prices.

# Check for empty neighbor sets
# card() calculates number of neighbors for each polygon in the list
# which() finds polygons with 0 neighbors
# Remove polygons with empty neighbor sets from the data
list_nb <- poly2nb(gi_data, queen = TRUE)
empty_nb <- which(card(list_nb) == 0)
gi_subset <- gi_data[-empty_nb, ]

neigh_nbs <- gi_subset %>% 
  mutate(
    nb = st_contiguity(geometry),  # neighbors share border    
    wt = st_weights(nb), # row-standardized weights              
    neigh_lag = st_lag(mean_sp, nb, wt)  # calculate spatial lag of mean sale price
  )

gi_hot_spots <- neigh_nbs %>% 
  mutate(Gi = local_g_perm(mean_sp, nb, wt, nsim = 999)) %>% 
  unnest(Gi) 

gi_hot_spots %>% 
  ggplot() + 
  geom_sf(data = dst, color="grey",  fill = "grey") +
  geom_sf(data = gi_hot_spots, aes(fill = gi), color = NA)+
  scale_fill_gradient2(low = "#283d3b", high = "#c44536", name = "Gi") +
  theme(
        axis.text.x=element_blank(),
        axis.ticks.x=element_blank(),
        axis.text.y=element_blank(),
        axis.ticks.y=element_blank(),
        plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 12, face = "bold"),
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill=NA, size=0.4)
        ) +
  labs(title = "Getid-Ord Spatial Hotspot for Sale Price",
       subtitle = "Red cluster indicate hotspots",
       caption = "Note: Errors with block group dataset is \n leading to missing values for particular blocks") 

Neighborhood Effect Regression

Given that sale prices may be related to the geographical attributes of the houses, we added neighborhood information (in this case the planning district) to the existing baseline model and added other relevant attributes.

tr_neigh <- st_intersection(tr_processed, dst %>% dplyr::select("DIST_NAME"))

Crime rate is related to the safety of houses and residents. Areas with lower crime rates tend to have higher house sale prices. Therefore, we introduced the number of crimes around the house as an indicator of safety. We used Aggravated Assault as the main measure of crime and counted the occurrences within a 200-meter radius of the house’s location.

Similarly, we considered schools as an important factor for families with minor children when choosing a house. Private schools tend to have higher tuition fees, which may attract affluent household and thereby raising local house prices. As such, we selected schools and counted the number of private K-12 schools within a 1.5-kilometer radius of the house’s location.

Likewise, convenience for shopping or commuting can also affect house prices, reflecting both the transit-oriented development (TOD) area and the extent of bustling areas. Therefore, we also counted and included the number of commercial activities or subway stations within the vicinity of the house. The presence of hospitals has a more complex impact on house prices for easy access to medical service is critical for the health and safety of the neighborhood. We calculated the distance from the house to the nearest hospital to account for the relationship between the distance and house prices. Finally, considering the effect of racial segregation on sale price, we categorized houses based on whether or not they are located in a census tract with over 50% of minority populations.

# Crime
assault <- crime %>% 
  filter(text_gener == "Aggravated Assault Firearm" | text_gener == "Aggravated Assault No Firearm") %>% 
  dplyr::select(point_x, point_y) %>%
  na.omit() %>% 
  st_as_sf(coords = c("Long", "Lat"), crs = "EPSG:102728") %>%
  distinct()
tr_neigh$crimes.Buffer <- tr_neigh %>% 
    st_buffer(660) %>% 
    aggregate(mutate(assault, counter = 1),., sum) %>%
    pull(counter) 
tr_neigh$crimes.Buffer[is.na(tr_neigh$crimes.Buffer)] <- 0
  
# School
schools <- schools %>% 
  filter(GRADE_ORG == "K-12" | GRADE_ORG == "9-12") %>%  
  filter(TYPE_SPECIFIC == "PRIVATE") 
school_buffer <-
  schools %>%
  dplyr::select(geometry) %>%
  st_transform('ESRI:102728') %>%
    na.omit() 
tr_neigh$school_buffer <- 
    tr_neigh$geometry %>% 
    st_buffer(5280) %>% 
    aggregate(mutate(school_buffer, counter = 1),., sum) %>%
    pull(counter)
tr_neigh$school_buffer[is.na(tr_neigh$school_buffer)] <- 0


# Hospital
tr_neigh <-
  tr_neigh %>% 
    mutate(hospitals_nn1 = nn_function(st_coordinates(tr_neigh), 
                              st_coordinates(hospital), k = 1))

# Shops
shops <- st_centroid(shopping) %>% dplyr::select("OBJECTID", "geometry")
tr_neigh <-
  tr_neigh %>% 
    mutate(shops_nn3 = nn_function(st_coordinates(tr_neigh), 
                              st_coordinates(shops), k = 3))

# Transit Stops
septaStops <- 
  rbind(
     el %>% 
      mutate(Line = "El") %>%
      dplyr::select(Station, Line),
     Broad_St %>%
      mutate(Line ="Broad_St") %>%
      dplyr::select(Station, Line)) %>%
  st_transform('ESRI:102728')
joinstops <- tr_neigh %>% 
  st_intersection(st_buffer(septaStops, 3000)) %>% 
  st_drop_geometry() %>% 
  group_by(parcel_number) %>% 
  summarize(
    totalstops = n())
tr_neigh <- tr_neigh %>% 
  left_join(joinstops, by = "parcel_number") 
tr_neigh <- tr_neigh %>% 
  mutate(totalstops = ifelse(is.na(totalstops), 0, totalstops))


# Race
census_api_key(dlgInput(
  "Enter a Census API Key", # ask for an api key
  Sys.getenv("CENSUS_API_KEY")
)$res,
overwrite = TRUE)
tracts20 <- 
  get_acs(geography = "tract", 
          variables = c("B02001_001E", # total population
            "B02001_002E", # white population
            "B02001_003E", # black population
            "B02001_005E", # asian population
            "B03002_012E"), 
          year=2020, state=42, county=101, 
          geometry=TRUE, output="wide") %>%
  st_transform('ESRI:102728') %>% 
  rename(TotalPop = B02001_001E, 
         Whites = B02001_002E,
         African_Americans = B02001_003E,
         Asians = B02001_005E,
         Latinx = B03002_012E) %>% 
  mutate(pctMinority = ifelse(TotalPop > 0, (African_Americans + Asians + Latinx ) / TotalPop, 0), 
         majority = ifelse(pctMinority > 0.5, "minority", "majority"))
joinrace <- tr_neigh %>% 
  st_intersection(tracts20 %>% select(pctMinority)) %>% 
  st_drop_geometry() %>% 
  group_by(parcel_number) %>% 
  summarize(
    meanMinor = mean(pctMinority))
tr_neigh <- tr_neigh %>% 
  left_join(joinrace, by = "parcel_number") 

We built a new regression of the model with these indicators and obtained the following results. All indicators show statistically significant correlations with house sale prices at the 95% level, except for the presence of a basement and landscape type near the house. Adjusted R-squared is 0.659, indicating that the model can explain 66.7% of variations in sale price. The standard errors for predicting the presence of a garage, neighborhood area, landscape type, and minority-majority community are greater than 5000, which may affect the precision of the model, but it is still the optimal result obtained so far. The Generalized Inverse Variance Inflation Factor (GIVF) for each indicator is less than 3, indicating that there are no issues of multicollinearity in the model.

reg2 <- lm(sale_price ~ ., data = neighboreffect %>% st_drop_geometry() %>% 
                                 dplyr::select(sale_price, Age, numRooms, hasBasement, hasAC, quality, buildingdis, 
                                               hasFireplace, hasGarage, stories, logarea, view, DIST_NAME, crimes.Buffer, totalstops, hospitals_nn1, shops_nn3, school_buffer, meanMinor))
summary_reg2 <- summary(reg2)
coefficients_table <- as.data.frame(summary_reg2$coefficients)


coefficients_table$significance <- ifelse(coefficients_table$`Pr(>|t|)` < 0.001, '***',
                                         ifelse(coefficients_table$`Pr(>|t|)` < 0.01, '**',
                                                ifelse(coefficients_table$`Pr(>|t|)` < 0.05, '*',
                                                       ifelse(coefficients_table$`Pr(>|t|)` < 0.1, '.', ''))))

coefficients_table$p_value <- paste0(round(coefficients_table$`Pr(>|t|)`, digits = 3), coefficients_table$significance)

coefficients_table %>%
  select(-significance, -`Pr(>|t|)`) %>% 
  kable(align = "r") %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed")) %>%
  footnote(general_title = "\n", general = "Table 5")
Estimate Std. Error t value p_value
(Intercept) -512485.43 17858.59 -28.70 0***
Age -493.20 31.56 -15.63 0***
numRooms 11596.05 516.88 22.43 0***
hasBasementY 3906.81 1755.84 2.23 0.026*
hasACY 56042.75 1960.86 28.58 0***
qualityGood 52554.07 3965.66 13.25 0***
buildingdisRow 55783.06 3473.19 16.06 0***
buildingdisTWIN 15173.55 3496.51 4.34 0***
hasFireplaceY 78991.68 4529.54 17.44 0***
hasGarageY -32904.44 6365.34 -5.17 0***
storiesmultiple 71068.23 2725.01 26.08 0***
storiessingle -14668.54 2457.08 -5.97 0***
logarea 151561.74 2055.65 73.73 0***
viewTypical -23809.29 4105.23 -5.80 0***
viewUrban -21019.12 6896.51 -3.05 0.002**
DIST_NAMECentral Northeast -258339.67 5678.39 -45.49 0***
DIST_NAMELower Far Northeast -299487.41 5680.49 -52.72 0***
DIST_NAMELower North -191975.01 5164.54 -37.17 0***
DIST_NAMELower Northeast -230793.27 5365.49 -43.01 0***
DIST_NAMELower Northwest -270498.71 4899.12 -55.21 0***
DIST_NAMELower South -150961.77 15225.61 -9.91 0***
DIST_NAMELower Southwest -182200.34 7395.90 -24.64 0***
DIST_NAMENorth -227148.51 5401.41 -42.05 0***
DIST_NAMENorth Delaware -257030.56 4975.61 -51.66 0***
DIST_NAMERiver Wards -227939.29 4376.26 -52.09 0***
DIST_NAMESouth -158083.17 3961.28 -39.91 0***
DIST_NAMEUniversity Southwest -150505.69 6303.60 -23.88 0***
DIST_NAMEUpper Far Northeast -237406.16 7072.20 -33.57 0***
DIST_NAMEUpper North -211494.08 5542.54 -38.16 0***
DIST_NAMEUpper Northwest -174983.67 5694.63 -30.73 0***
DIST_NAMEWest -197151.44 5450.48 -36.17 0***
DIST_NAMEWest Park -217117.82 7366.94 -29.47 0***
crimes.Buffer -3053.32 256.37 -11.91 0***
totalstops 7520.68 909.10 8.27 0***
hospitals_nn1 -1.74 0.35 -4.98 0***
shops_nn3 -15.81 1.29 -12.28 0***
school_buffer 9548.06 1484.05 6.43 0***
meanMinor -195101.02 5158.75 -37.82 0***

Table 5
new_variable_names <- c(
  "Age",
  "Number of Rooms",
  "Basement",
  "Air Conditioner",
  "Quality Grade",
  "Building Type",
  "Fireplace",
  "Garage",
  "Number Stories",
  "Area",
  "Type of View",
  "District Name",
  "Crimes",
  "Transit Stops",
  "Hospitals",
  "Shops",
  "Schools",
  "Minor Race"
)




model_summary <- data.frame(
  Statistic = c("Multiple R-squared", "Adjusted R-squared", "F-statistic"),
  Value = c(
    summary_reg1$r.squared,
    summary_reg1$adj.r.squared,
    summary_reg1$fstatistic[1]
  )
)

model_summary %>%
  kable() %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"))%>%
  footnote(general_title = "\n", general = "Table 6")
Statistic Value
Multiple R-squared 0.444
Adjusted R-squared 0.443
F-statistic 1342.545

Table 6
vif_reg2 <- vif(reg2)
vif_df <- as.data.frame(vif_reg2)
vif_df$Variable <- new_variable_names
vif_df <- vif_df[, c("Variable", names(vif_df)[!names(vif_df) %in% "Variable"])]

vif_df %>%
  kable(row.names = FALSE) %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed")) %>%
  footnote(general_title = "\n", general = "Table 7")
Variable GVIF Df GVIF^(1/(2*Df))
Age 1.44 1 1.20
Number of Rooms 1.34 1 1.16
Basement 1.28 1 1.13
Air Conditioner 1.45 1 1.21
Quality Grade 1.17 1 1.08
Building Type 2.48 2 1.25
Fireplace 1.19 1 1.09
Garage 1.03 1 1.01
Number Stories 1.89 2 1.17
Area 2.24 1 1.50
Type of View 1.15 2 1.03
District Name 74.00 17 1.14
Crimes 1.99 1 1.41
Transit Stops 1.54 1 1.24
Hospitals 2.56 1 1.60
Shops 1.84 1 1.36
Schools 1.64 1 1.28
Minor Race 4.62 1 2.15

Table 7

The residual plot based on neighborhood effect regression model is much improved from the previous model, with dots distributed much more evenly along the best fit line, despite that there are still showing some trends of underprediction.

residuals_neigh <- data.frame(Residuals = resid(reg2), Fitted = fitted(reg2))
ggplot(residuals_neigh, aes(x = Fitted, y = Residuals)) +
  geom_point(size = 0.4, color = "#283d3b") +
  geom_hline(yintercept = 0, linetype = "dashed", color = "#c44536") +
  labs(title = "Residual Plot for Neighborhood Effect Regression",
       subtitle = "Each dot represent one property ",
       caption = "Data: Philadelphia Properties and Current Assessments \n Open Data PHl Maps", 
       x = "Fitted Values",
       y = "Residuals") +
  theme_light() +   
  theme(plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 12, face = "bold"), 
        axis.text.x=element_text(size=6),
        axis.text.y=element_text(size=6), 
        axis.title=element_text(size=8))

Regression Comparison

Comparing across two regression models, we see that while baseline regression only account 44% of the variance in sale price, this figure rise to 66% in neighborhood effect regression, suggesting a significant improvement.

Model 1 Model 2
Age -168.48 *** -493.20 ***
Number of Rooms 4253.03 *** 11596.05 ***
Basement-Yes 11261.69 *** 3906.81 *
AC-Yes 133857.02 *** 56042.75 ***
Quality Grade-Good 127945.33 *** 52554.08 ***
Type of Building-Row -9345.07 * 55783.06 ***
Type of Building-TWIN -51328.87 *** 15173.55 ***
Fireplace-Yes 135352.95 *** 78991.68 ***
Garage-Yes -35222.93 *** -32904.45 ***
Number Stories-Multiple 167907.96 *** 71068.23 ***
Number Stories-Single 11213.33 *** -14668.54 ***
Area 97509.20 *** 151561.74 ***
Type of View-Typical -31620.21 *** -23809.29 ***
Type of View-Urban -47330.70 *** -21019.12 **
District-Central Northeast -258339.67 ***
District-Lower Far Northeast -299487.41 ***
District-Lower North -191975.01 ***
District-Lower Northeast -230793.28 ***
District-Lower Northwest -270498.71 ***
District-Lower South -150961.77 ***
District-Lower Southwest -182200.34 ***
District-North -227148.51 ***
District-North Delaware -257030.56 ***
District-River Wards -227939.29 ***
District-South -158083.17 ***
District-UniversitySouthwest -150505.69 ***
District-Upper Far Northeast -237406.16 ***
District-Upper North -211494.08 ***
District-Upper Northwest -174983.67 ***
District-West -197151.44 ***
District-West Park -217117.82 ***
Crimes -3053.32 ***
Transit Stops 7520.68 ***
Hospitals -1.74 ***
Shops -15.81 ***
Schools 9548.06 ***
Minor -195101.02 ***
N 23588 23587
R2 0.44 0.66

Table 8 *** p < 0.001; ** p < 0.01; * p < 0.05

Variables Summary

Learning how accounting for neighborhood effect as well as proximity to public services and crime would significantly impact sale price, we will be using our second regression model for sale price prediction. Before doing that, we would like to present a full detailed summary of all of the variables in our model to ensure their legibility. We have organized this section as follows. First, we present a overall summary table of all of the variables, categorized by data type and effect on housing price. Then, we split the data into numerical and categorical. For the numerical variables, we performed cor test to test their significance whereas for categorical variables, we performed ANOVA test. This is followed by some selected visualizations of our predictors.

Variable Name Type
Internal Characteristics
Age Numeric
Number of Rooms Numeric
Basement (Y/N) Categorical
Air Conditioner (Y/N) Categorical
Quality Grade Categorical
Building Type Categorical
Fireplace (Y/N) Categorical
Garage (Y/N) Categorical
Stories Type Categorical
Area Numeric
Type of View Categorical
Public Service
Surrounding Crimes Numeric
Nearby Transit Stops Numeric
Distance to Hospitals Numeric
Distance to Shops Numeric
Nearby Private High School Numeric
Spatial Characteristics
Planning District Categorical
Minority Neighborhood Numeric

Table 9

Numeric Variables

The average sale price is approximately $265,664, with a relatively high standard deviation of $201,069, indicating a significant variation in sale prices in the data. The lowest price is $10,100, and the highest price is close to $2,000,000. The average age of houses is 85.7 years, with a relatively scattered distribution. The maximum age is 273 years, and the most recently built house is less than one year old. The average number of rooms is approximately 3.63, with a standard deviation of 1.71, indicating that the variation in room counts is not significant, with a maximum of 14 rooms per house.

The average number of crime incidents within a 200-meter radius of the house is 3.88, with a standard deviation of 4.21, indicating a significant variation in crime counts. The average number of subway stations near houses is 0.583, with a standard deviation of approximately 1, indicating a relatively small variation in the number of stations. The maximum number of stations within the radius is 8. The average distance to the nearest hospital is 1.6 kilometers, with a large variation as indicated by the standard deviation. Houses are as close as less than 30 meters and as far as over 8 kilometers from the nearest hospital. The average distance to the nearest commercial activity is approximately 700 meters, with significant variation, ranging from as close as 200 meters to as far as over 2.6 kilometers. The average number of schools within a 1.5-kilometer radius is 0.543, with a small standard deviation, indicating relatively little variation in school counts, with a maximum of 2 schools within the radius.

Statistic N Mean St.Dev. Min Max
Sale Price 23,587 265,664 201,069 10,100 1,990,000
Age 23,587 85.700 29.100 0 273
Rooms 23,587 3.630 1.710 0 14
Area 23,587 7.380 0.557 5.230 10.800
Crimes 23,587 3.880 4.210 0 42
Transit 23,587 0.583 1.040 0 8
Hospital 23,587 5,298.000 3,495.000 79.400 26,069.000
Shops 23,587 2,227.000 807.000 747.000 8,790.000
School 23,587 0.543 0.660 0 2
Minority 23,587 0.602 0.319 0.000 1.100

Table 10

The following chart shows the correlations between the variables. There is a strong positive correlation between nearby crime incidents and being a minority community. This impact is reflected in the negative correlations with house prices. In addition to house age, the distance to the nearest hospital also shows a negative correlation with house prices, confirming that a noisy environment near hospitals can indeed negatively affect the living experience. Furthermore, house area, the number of subway stations nearby, and the number of private schools within a 1.5-kilometer radius all show positive correlations with house prices, indicating that houses with larger areas, more nearby subway stations, and more nearby private schools have an advantage in pricing.

varibles <- 
  neighboreffect %>% 
  dplyr::select(sale_price, Age, numRooms, hasBasement, hasAC, quality, buildingdis, hasFireplace, hasGarage, stories, logarea, view, DIST_NAME, crimes.Buffer, totalstops, hospitals_nn1, shops_nn3, school_buffer, meanMinor)

vars <- select_if(st_drop_geometry(varibles), is.numeric) %>% na.omit()

ggcorrplot(
  round(cor(vars), 1), 
  p.mat = cor_pmat(vars),
  colors = c("#283d3b", "white", "#c44536"),
  type="lower",
  insig = "blank") +  
  labs(title = "Correlation Matrix for all Numeric Variables",
       caption = "Data: Philadelphia Properties and Current Assessments \n Open Data Philly") +
  theme(plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 12, face = "bold"), 
        axis.text.x=element_text(size=6),
        axis.text.y=element_text(size=6), 
        axis.title=element_text(size=8))

We made an additional scatterplot in order to better visualize the direction and strength of correlation between sale price and our numeric predictor variables.

neighboreffect_nongeom_long <- neighboreffect_nongeom%>% 
  pivot_longer(cols = -sale_price, # everything except measurement
               names_to = "Type", # categorizes all quantitative variables into Type
               values_to = "Number") # the name of values is Number

neighboreffect_nongeom_long %>%
  ggplot(aes(x= Number, y = sale_price)) +
  geom_point(size = 0.01, color = "#283d3b") +  
  geom_smooth(method='lm', formula= y~x, lwd=0.5, color = "#c44536") +
  facet_wrap(~ Type, scales = "free", labeller= labeller(Type = c(
    `Age` = "Age",
    `hospitals_nn1` = "Crimes",
    `crimes.Buffer` = "Hospital",
    `logarea` = "Area",
    `meanMinor` = "Minority Pop",
    `numRooms` = "Num Rooms",
    `school_buffer` = "School",
    `shops_nn3` = "Shops",
    `totalstops` = "Transit Stops")))  +
  labs(title = "Scatter Plot of Sale Price over Numeric Predicted Variables",
       caption = "Data: Philadelphia Properties and Current Assessments \n Open Data Philly") +
  theme(plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 12, face = "bold"), 
        axis.text.x=element_text(size=6),
        axis.text.y=element_text(size=6), 
        axis.title=element_text(size=8), 
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill=NA, size=0.8))

Based on this, we conducted a correlation analysis among the variables, and the results are shown in the table below. According to the results, house age (P<0.001, T= -31.16), crime count within 200 meters (P<0.001, T= -64.71), distance to the nearest hospital (P<0.001, T= -19.49), and being a minority-majority community (P<0.001, T= -95.12) all have significant negative correlations. The correlation coefficients are -0.199, -0.388, -0.126, and the correlation coefficient for being a minority community is as high as -0.527. Room count (P<0.001, T= 14.72), the logarithm of area (P<0.001, T= 66.22), the number of subway stations (P<0.001, T= 19.15), and the number of schools within a 1.5-kilometer radius (P<0.001, T= 23.61) all show significant positive correlations. Additionally, although the number of commercial activities nearby (P<0.001, T= -4.34) also shows a significant negative correlation, its coefficient is low, at -0.028.

cor_var <- c("Age", "numRooms", "logarea", "crimes.Buffer", "totalstops", "hospitals_nn1", "shops_nn3", "school_buffer", "meanMinor")

new_row_names <- c(
  "Age",
  "Number of Rooms",
  "Area",
  "Crimes",
  "Transit Stops",
  "Hospitals",
  "Shops",
  "Schools",
  "Minor"
)

cor_summary_table <- data.frame(Variable = character(0), Correlation = numeric(0))

for (var in cor_var) {
  correlation_test <- cor.test(neighboreffect$sale_price, neighboreffect[[var]])
  correlation <- correlation_test$estimate
  p_value <- correlation_test$p.value
  t_statistic <- correlation_test$statistic 
  ci_lower <- correlation_test$conf.int[1] 
  ci_upper <- correlation_test$conf.int[2] 
  summary_data <- data.frame(Correlation = correlation, P_Value = p_value, T_Statistic = t_statistic, CI_Lower = ci_lower, CI_Upper = ci_upper)
  cor_summary_table <- rbind(cor_summary_table, summary_data)
}

cor_summary_table$P_Value <- sprintf("%.3f", cor_summary_table$P_Value)

cor_summary_table$P_Value <- ifelse(cor_summary_table$P_Value < 0.001, paste0("< 0.001", "***"), ifelse(cor_summary_table$P_Value < 0.01, paste0(cor_summary_table$P_Value, "**"), ifelse(cor_summary_table$P_Value < 0.05, paste0(cor_summary_table$P_Value, "*"), as.character(cor_summary_table$P_Value))))

rownames(cor_summary_table) <- new_row_names

kable(cor_summary_table) %>% kable_styling( bootstrap_options = c("striped", "hover", "condensed")) %>%
 footnote(general_title = "\n", general = "Table 11")
Correlation P_Value T_Statistic CI_Lower CI_Upper
Age -0.199 < 0.001*** -31.16 -0.211 -0.187
Number of Rooms 0.095 < 0.001*** 14.72 0.083 0.108
Area 0.396 < 0.001*** 66.22 0.385 0.407
Crimes -0.388 < 0.001*** -64.71 -0.399 -0.377
Transit Stops 0.124 < 0.001*** 19.15 0.111 0.136
Hospitals -0.126 < 0.001*** -19.49 -0.138 -0.113
Shops -0.028 < 0.001*** -4.34 -0.041 -0.015
Schools 0.152 < 0.001*** 23.61 0.139 0.164
Minor -0.527 < 0.001*** -95.12 -0.536 -0.517

Table 11

Categorical Variables

The table below shows all of the categorical variables in our model as well as the count for subgroups in each variable.

Variable Name Count (%)
Basement
Yes 13,863 (59%)
No 9,724 (41%)
Air Conditioner
Yes 7,771 (33%)
No 15,816 (67%)
Quality Grade
Good 1,074 (4.6%)
Bad 22,513 (95%)
Building Types
Other 2,352 (10.0%)
Row 17,978 (76%)
Twin 3,257 (14%)
Fire Place
Yes 831 (3.5%)
No 22,756 (96%)
Garage
Yes 355 (1.5%)
No 23,232 (98%)
Stories Number
Double 16,688 (71%)
Multiple 2,943 (12%)
Single 3,956 (17%)
View
Scenic 959 (4.1%)
Urban 449 (1.9%)
Typical 22,179 (94%)
District Name
Central 2,072 (8.8%)
Central Northeast 1,015 (4.3%)
Lower Far Northeast 1,038 (4.4%)
Lower North 1,353 (5.7%)
Lower Northeast 1,472 (6.2%)
Lower Northwest 1,260 (5.3%)
Lower South 63 (0.3%)
Lower Southwest 609 (2.6%)
North 1,902 (8.1%)
North Delaware 1,888 (8.0%)
River Wards 1,733 (7.3%)
South 2,953 (13%)
University Southwest 646 (2.7%)
Upper Far Northeast 785 (3.3%)
Upper North 1,786 (7.6%)
Upper Northwest 1,112 (4.7%)
West 1,481 (6.3%)
West Park 419 (1.8%)

Table 12

For each of these categorical variables, we performed an Analysis of Variance (ANOVA) test. ANOVA is a statistical test that allows us to compare means across across three or more groups. Its significance lies in its ability to efficiently assess variations among these groups by partitioning the total variance, thereby determining if the differences are statistically significant. In our case, we are interested in whether there’s a significant difference in sale price between houses with different views, for example. We found that all of our categorical predictor variables are significant predictors of sale prices.

anova_var <- c("DIST_NAME", "view", "stories", "hasGarage", "hasFireplace", "buildingdis", "quality", "hasAC", "hasBasement")

new_row_names <- c(
  "District Name",
  "Type of View",
  "Number Stories",
  "Garage",
  "Fireplace",
  "Type of Building",
  "Quality Grade",
  "Air Conditioning",
  "Basement"
)

anova_summary_table <- data.frame(Df = integer(0), Sum_Sq = numeric(0), Mean_Sq = numeric(0), F_value = numeric(0))
row_names <- character(0)

for (i in seq_along(anova_var)) {
  var <- anova_var[i]
  anova_result <- aov(neighboreffect$sale_price ~ as.factor(neighboreffect[[var]]))
  #paste("ANOVA result for", var)
  summary_data <- summary(anova_result)[[1]][, c("Df", "Sum Sq", "Mean Sq", "F value")]
  anova_summary_table <- rbind(anova_summary_table, summary_data)
   row_names <- c(row_names, paste(var))
}

filter_condition <- !grepl("Residuals", rownames(anova_summary_table))
filtered_anova_summary <- anova_summary_table[filter_condition, ]

rownames(filtered_anova_summary) <- new_row_names
kable(filtered_anova_summary) %>% kable_styling( bootstrap_options = c("striped", "hover", "condensed")) %>%
  footnote(general_title = "\n", general = "Table 13")
Df Sum Sq Mean Sq F value
District Name 17 347643373653073 20449610214887 795.5
Type of View 2 3961573728389 1980786864195 49.2
Number Stories 2 171403194520597 85701597260298 2584.1
Garage 1 4467688280911 4467688280911 111.0
Fireplace 1 77015475270635 77015475270635 2072.2
Type of Building 2 48204071165325 24102035582662 627.8
Quality Grade 1 77929904228236 77929904228236 2099.0
Air Conditioning 1 183455630806319 183455630806319 5618.5
Basement 1 11188758130033 11188758130033 280.0

Table 13

We proceed to make bar charts for all of our categorical predictor variables. We may see that most houses in Philadelphia, do not have a basement, do not have air conditioner, are of less satisfying quality, are row houses, do not have a fire place, have a garage, are of multiple stories, and has typical view.

variables_to_plot <- c("hasBasement", "hasAC", "hasFireplace", "hasGarage", "stories", "view",  "quality", "buildingdis")
subset_varibales <- neighboreffect %>% select(all_of(variables_to_plot)) %>%  st_drop_geometry()

long_vars <- gather(subset_varibales)

plot1 <- ggplot(long_vars, aes(x = value)) +
  geom_bar(fill= "#283d3b") +
  facet_wrap(~ key, scales = "free", ncol = 4,  labeller= labeller(key = c(
    `buildingdis` = "Building Type",
    `hasAC` = "Air Conditioner",
    `hasBasement` = "Basement",
    `hasFireplace` = "Fireplace",
    `hasGarage` = "Garage",
    `quality` = "Quality Grade",
    `stories` = "Number Stories",
    `view` = "Type of View"))) +
  labs(title = "Distribution of Categorical Predictor Variables",
       x = "Value",
       y = "Count") +
  theme(axis.text.x = element_text(angle = 45, hjust = 1)) +
  theme(plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 12, face = "bold"), 
        axis.text.x=element_text(size=6),
        axis.text.y=element_text(size=6), 
        axis.title=element_text(size=8), 
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill=NA, size=0.8))


variables_to_plot <- c("DIST_NAME")
subset_varibales <- neighboreffect %>% select(all_of(variables_to_plot)) %>%  st_drop_geometry()
long_vars <- gather(subset_varibales)
plot2 <- ggplot(long_vars, aes(x = value)) +
  geom_bar(fill = "#283d3b") +
  facet_wrap(~ key, scales = "free", labeller= labeller(key = c(
    `DIST_NAME` = "District Name"))) +
  theme(axis.text.x = element_text(angle = 45, hjust = 1)) +
  theme(plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 12, face = "bold"), 
        axis.text.x=element_text(size=6),
        axis.text.y=element_text(size=6), 
        axis.title=element_text(size=8), 
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill=NA, size=0.8))

(plot1 | plot2) + plot_layout(widths = c(2, 1.5))

Variable Maps

Below, we made some visualizations of selected predictor variables to understand their spatial characteristics. The first map we made shows the spatial distribution of property sale price in Philadelphia, from which we can see that sale price in Philadelphia is pretty spatially clustered. Houses that are worth a million or more are typically located at the outskirt of the city, such as the northeast corner, as well as the center city, whereas houses that worth than 250k are located in Northeast and West Philadelphia.

neighboreffect <- neighboreffect %>% 
  mutate(sale_class = cut(sale_price, breaks = c(0, 250000, 500000, 750000, 1000000, max(neighboreffect$sale_price, na.rm=TRUE))))
palette <- c("#edddd4","#c44536","#197278","#772e25","#283d3b")


ggplot()+
    geom_sf(data=dst, fill='grey80',color='transparent')+
    geom_sf(data=neighboreffect, size=0.75,aes(colour = q5(sale_class)))+
    scale_color_manual(values = palette,
                    name = "Sales Price",
                    na.value = 'grey80',
                    labels = c('0-250k', '250k-500k', '500k-750k', '750k-1m', '1m More')) +
   theme(
        axis.text.x=element_blank(),
        axis.ticks.x=element_blank(),
        axis.text.y=element_blank(),
        axis.ticks.y=element_blank(),
        plot.title = element_text(size = 12, face = "bold"),
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill=NA, size=0.4)
        ) +
  labs(title = "Distribution of Sale Price in Philadelphia",
       caption = "Data: Philadelphia Properties and Current Assessments") 

The second map shows the distribution of crimes in Philadelphia. Note that while this maps below is not a kernel density plot. Rather, each point represents of single house, of which we created a buffer around and summarized the number of crime within that particular buffer. Therefore, each point is colored by the number of crimes nearby - the darker the color, the higher the number of crime incidents nearby. We can see that houses with more crime incidents nearby are mainly located in West and North Philadelphia.

ggplot()+
  geom_sf(data=dst,fill='grey80',color='transparent')+
  geom_sf(data=neighboreffect,aes(colour = (crimes.Buffer)),size=0.24)+
 scale_color_continuous(low = "#FAF9F6", high = "#c44536", name= "Crime Buffer") + 
     theme(
        axis.text.x=element_blank(),
        axis.ticks.x=element_blank(),
        axis.text.y=element_blank(),
        axis.ticks.y=element_blank(),
        plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 12, face = "bold"),
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill=NA, size=0.4)
        ) +
  labs(title = "Distribution of Crimes in Philadelphia",
       subtitle = "Note: each point represents a single house colored by number of crimes nearby", 
       caption = "Data: OpenDataPhilly") 

The third map shows the distribution of minority population in Philadelphia. Again, each point represents a single house. We calculated the percentage of minority population within each census tract and this value was applied to all houses within that census tract. Based on our observations from previous two maps, we may see that minority populations are located in areas where there tends to be lower house price and higher crime rate. This provides important implications to our house price predictions.

ggplot()+
  geom_sf(data=dst,fill='grey80',color='transparent')+
  geom_sf(data=neighboreffect,aes(colour = meanMinor),size=0.5)+
  scale_color_continuous(low = "#FAF9F6", high = "#283d3b", name= "Pct Minority Population ")+
   theme(
        axis.text.x=element_blank(),
        axis.ticks.x=element_blank(),
        axis.text.y=element_blank(),
        axis.ticks.y=element_blank(),
        plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 12, face = "bold"),
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill=NA, size=0.4)
        ) +
  labs(title = "Distribution of Minority Population in Philadelphia",
       subtitle = "Note: each point represents a single house colored by pct of minority pop", 
       caption = "Data: American Community Survey 2020 ") 

For our final map, we visualized the distribution of housing type in Philadelphia. It is evident that housing type is also spatially clustered with row house being the most dominant type of housing in the city, leaving twin houses and other types of houses at the outskirt of Philadelphia. Drawing from previous observations regarding how race, crime, and sale price are also spatially clustered, it would not be difficult to make connection between variations in sale price and various house types.

ggplot()+
  geom_sf(data=dst,fill='grey80',color='transparent')+
  geom_sf(data=neighboreffect,aes(colour = buildingdis),size=0.5)+
  scale_color_manual(values =c("#c44536","#197278","#772e25")  , name = "Building Type")+
  theme(
        axis.text.x=element_blank(),
        axis.ticks.x=element_blank(),
        axis.text.y=element_blank(),
        axis.ticks.y=element_blank(),
        plot.title = element_text(size = 12, face = "bold"),
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill=NA, size=0.4)
        ) +
  labs(title = "Distribution of House Type in Philadelphia",
       caption = "Data: Philadelphia Properties and Current Assessments") 

Final Model

Making Predictions

Based on the neighborhood effect model with added and adjusted geographical attributes, we predicted the sale prices of randomly selected training data set houses and calculated the Mean Absolute Error (MAE) and Mean Absolute Percentage Error (MAPE). The MAE and MAPE for the neighborhood effect model are 73326 and 12.3%, respectively. Compared to the previous values of 98329 and 39%, both MAE and MAPE have significantly decreased, indicating an improved accuracy of the model’s predictions.

set.seed(485) 

inTrain <- createDataPartition(
    y = paste(neighboreffect$DIST_NAME, neighboreffect$hasBasement, neighboreffect$hasAC, neighboreffect$quality, neighboreffect$buildingdis, neighboreffect$hasFireplace, neighboreffect$hasGarage, neighboreffect$stories, neighboreffect$view), 
             p = .60, list = FALSE)


tr_neigh.training <- neighboreffect[inTrain,] 
tr_neigh.test <- neighboreffect[-inTrain,]  

reg.train <- lm(sale_price ~ ., data = tr_neigh.training %>% st_drop_geometry() %>% 
                                 dplyr::select(sale_price, Age, numRooms, hasBasement, hasAC, quality, buildingdis, 
                                               hasFireplace, hasGarage, stories, logarea, view, DIST_NAME, crimes.Buffer, totalstops, hospitals_nn1, shops_nn3, school_buffer, meanMinor))


tr_neigh.test <- 
  tr_neigh.test %>% 
  mutate(Regression = "Neighborhood Effects", 
    SalePrice.Predict = predict(reg.train, tr_neigh.test),
         SalePrice.Error = SalePrice.Predict - sale_price,
         SalePrice.AbsError = abs(SalePrice.Predict - sale_price),
         SalePrice.APE = (abs(SalePrice.Predict - sale_price)) / SalePrice.Predict)

tr_neigh.test %>% 
  st_drop_geometry() %>%
  summarise(MAE = mean(SalePrice.AbsError),
            MAPE = mean(SalePrice.APE)*100) %>%
  kbl(col.name=c('Mean Absolute Error','Mean Absolute Percentage Error')) %>%
  kable_styling( bootstrap_options = c("striped", "hover", "condensed")) %>%
  footnote(general_title = "\n", general = "Table 14")  
Mean Absolute Error Mean Absolute Percentage Error
73326 12.3

Table 14

Checking Predictions

We quickly ran the Moran’s I again to check for spatial autocorrelation using our final model that account for neighborhood effects. The figure below plot the frequency of 999 randomly permutated I under Monte Carlo simulation, which shows that our observed I is still higher than all of the randomly generated I’s, but much smaller than our previous Moran’s I plot when neighborhood effect is not accounted for. This means that the errors of our prediction are still clustered in space.

coords.test_n <-  st_coordinates(tr_neigh.test) 
neighborList.test_n <- knn2nb(knearneigh(coords.test_n, 5))
spatialWeights.test_n <- nb2listw(neighborList.test_n, style="W")

moranTest_n <- moran.mc(tr_neigh.test$SalePrice.Error, 
                      spatialWeights.test_n, nsim = 999)

ggplot(as.data.frame(moranTest_n$res[c(1:999)]), aes(moranTest_n$res[c(1:999)])) +
  geom_histogram(binwidth = 0.01, fill = "#283d3b") +
  geom_vline(aes(xintercept = moranTest_n$statistic), colour = "#c44536" ,size=1) +
  scale_x_continuous(limits = c(-0.5, 0.5)) +
  labs(title = "Observed and Permuted Moran's I - Neighborhood Effect",
       subtitle = "Observed Moran's I in Red",
       x = "Moran's I",
       y = "Count") +
  theme_light() +   
  theme(plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 12, face = "bold"), 
        axis.text.x=element_text(size=6),
        axis.text.y=element_text(size=6), 
        axis.title=element_text(size=8))

We then plotted the predicted prices as a function of observed sale prices. The green line represents a would-be perfect fit, while the dark green line represents the predicted fit. We see that with the addition of the neighborhood effect, our model does a pretty satisfying job at predicting the sale price, perhaps by explaining part of the spatial process and local amenities.

tr_neigh.test %>%
  dplyr::select(SalePrice.Predict, sale_price) %>%
  ggplot(aes(sale_price, SalePrice.Predict)) +
  geom_point(color = "#c44536", size = 0.3) +
  stat_smooth(aes(sale_price, sale_price), 
             method = "lm", se = FALSE, size = 1, colour="#197278") + 
  stat_smooth(aes(SalePrice.Predict, sale_price), 
              method = "lm", se = FALSE, size = 1, colour="#283d3b") +
  labs(title="Predicted Sale Price as a Function of Observed Price",
       subtitle="Green line represents a perfect prediction; dark green line represents prediction",
       x = "Sale Price",
       y = "Predict Price") +
    theme_light() +   
theme(plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 12, face = "bold"), 
        axis.text.x=element_text(size=6),
        axis.text.y=element_text(size=6), 
        axis.title=element_text(size=8))

We also mapped the distribution of sale price error of our testing set using the final model, from which we see that our prediction error is distributed uniformly across space, with no presence of extremely big errors, which is a satisfying observation.

ggplot()+
  geom_sf(data=dst,fill='#E5E4E2',color='transparent')+
  geom_sf(data=tr_neigh.test,aes(colour = SalePrice.Error),size=0.5)+
  scale_color_continuous(high = "#FAF9F6", low = "#197278", name= "Sale Price Error ") +
  theme(
        axis.text.x=element_blank(),
        axis.ticks.x=element_blank(),
        axis.text.y=element_blank(),
        axis.ticks.y=element_blank(),
        plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 12, face = "bold"),
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill=NA, size=0.4)
        ) +
  labs(title = "Distribution of Sale Price Error Testing Set") 

Finally, we mapped the sale price error as a function of lag price error, which is the average price of a home sale’s 5 nearest neighbors. The plot suggests a positive correlation between sale price error and lag price error. The fact that points are compacted around the line of best fit means that the correlation here is strong. However, we do see a cluster of points with both high sale price error and lag price error.

tr_neigh.test %>% 
  mutate(lagPriceError = lag.listw(spatialWeights.test_n, SalePrice.Error)) %>%
  ggplot()+
  geom_point(aes(x =lagPriceError, y =SalePrice.Error), color = "#c44536", size = 0.3)+
  stat_smooth(aes(lagPriceError, SalePrice.Error), 
             method = "lm", se = FALSE, size = 1, colour="#197278")  +
  labs(title="Sale Price Error as a Function of Lag Price Error",
       x = "Lag Price Error",
       y = "Sale Price Error") +
theme_light() +   
theme(plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 12, face = "bold"), 
        axis.text.x=element_text(size=6),
        axis.text.y=element_text(size=6), 
        axis.title=element_text(size=8))

Up until this point, it seems that our testing data is making a good prediction on the actual sale price. However, this only partially proves that our model is effective at predicting property value of the entire Philadelphia. The story behind could be that the 40% of data points being selected through partition as the testing data set fits well into the model coincidentally. To check for the generalizability of our model to the entire Philadelphia, we would need to perform a cross-validation test.

Cross Validation

On this basis, we conducted 100 cross-validations of the neighborhood effect model with 18 predictor variables. The RMSE value is 116278, indicating that despite being our best solution so far, the model’s prediction error can still be relatively large. The R-squared value is 0.667, indicating a good level of variance explanation by the model.

fitControl <- trainControl(method = "cv", number = 100)
set.seed(485)

reg.cv <- 
  train(sale_price ~ ., data = st_drop_geometry(neighboreffect) %>% 
                             dplyr::select(sale_price, Age, numRooms, hasBasement, hasAC, quality, buildingdis, 
                                               hasFireplace, hasGarage, stories, logarea, view, DIST_NAME, crimes.Buffer, totalstops, hospitals_nn1, shops_nn3, school_buffer, meanMinor), method = "lm", trControl = fitControl, na.action = na.pass)

reg.cv$resample %>% 
  summarise(MAE = mean(reg.cv$resample[,3]),
            sd(reg.cv$resample[,3])
) %>%
  kbl(col.name=c('Mean Absolute Error','Standard Deviation of MAE')) %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed")) %>%
  footnote(general_title = "\n", general = "Table 15")
Mean Absolute Error Standard Deviation of MAE
76019 5782

Table 15

On average, the model’s Mean Absolute Error is approximately 76,019, with a standard deviation of 5,782. This suggests that while there is some dispersion, the MAE is generally concentrated around the mean.

reg.cv$resample %>% 
ggplot(aes(x=reg.cv$resample[,3])) +
    geom_histogram( fill="#197278", color="#e9ecef") + 
    labs(title="Distribution of Mean Absolute Error for Overall Prediction",
       x = "Mean Absolute Error",
       y = "Count") +
theme_light() +   
theme(plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 12, face = "bold"), 
        axis.text.x=element_text(size=6),
        axis.text.y=element_text(size=6), 
        axis.title=element_text(size=8))

Overall Prediction

For our overall prediction, we see that house priced above $1 million are mainly concentrated in the northern, northwestern, and central parts of Philadelphia. The northern area, in particular, has a denser concentration of high-priced homes, while the central and western areas have a more dispersed distribution. Most houses with values ranging from $750,000 to $1 million are located in the southern and northeastern areas, with a few scattered in the northwestern region, overlapping with the distribution range of homes priced above $1 million. Houses in the $500,000 to $750,000 range are primarily found in the western, southwestern, and northeastern parts of the city. In the southwestern and northeastern areas, these homes are closer to the city center compared to higher-priced ones. In the western area, they tend to be more remote. Houses valued between $250,000 and $500,000 are mainly located in the central part of the city, surrounded by higher-priced houses. Houses priced below $250,000 exhibit stronger clustering characteristics and are primarily clustered in the eastern and southwestern parts of the city, with another portion scattered in the northwestern area, overlapping with the distribution range of houses priced between $250,000 and $500,000. In summary, Philadelphia’s predicted housing prices are generally higher in the northeastern, northwestern, and southern parts of the city, while the central city area tends to have lower-priced houses. In terms of the north-south direction, there is a higher concentration of high-priced houses in the northern region compared to the southern area. In terms of the east-west direction, the eastern part of the city has a higher concentration of high-priced houses compared to the western area.

PredictAllVal <- tr_neigh %>% 
  mutate(prediction = predict(reg.train, tr_neigh)) 

PredictAllVal <- PredictAllVal %>% 
  mutate(prediction_class = cut(prediction, breaks = c(0, 250000, 500000, 750000, 1000000, max(prediction, na.rm=TRUE))))



ggplot()+
    geom_sf(data=dst, fill='grey80',color='transparent')+
    geom_sf(data=PredictAllVal, size=0.75,aes(colour = q5(prediction_class)))+
    scale_color_manual(values = palette,
                    name = "Sales Price",
                    na.value = 'grey80',
                    labels = c('0-250k', '250k-500k', '500k-750k', '750k-1m', '1m More')) +
   theme(
        axis.text.x=element_blank(),
        axis.ticks.x=element_blank(),
        axis.text.y=element_blank(),
        axis.ticks.y=element_blank(),
        plot.title = element_text(size = 12, face = "bold"),
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill=NA, size=0.4)
        ) +
  labs(title = "Distribution of Predicted Sale Price in Philadelphia") 

We tested our model’s generalizability across different racial groups in Philadelphia.

ggplot() + geom_sf(data = na.omit(tracts20), aes(fill = majority), color = NA) +
    scale_fill_manual(values = c("#c44536", "#283d3b"), name="Race Context") +
    labs(title = "Race Context for Philadelphia Census Tracts",
         caption = "Data: American Community Survey 2020") +
    theme(legend.position="bottom") +
  theme(
        axis.text.x=element_blank(),
        axis.ticks.x=element_blank(),
        axis.text.y=element_blank(),
        axis.ticks.y=element_blank(),
        plot.title = element_text(size = 12, face = "bold"),
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill=NA, size=0.4)
        ) 

According to the test results for the prediction of house prices in majority and minority communities by the neighborhood effect model, the MAPE for predicting majority communities is 26%, while the MAPE for predicting minority communities is only 3%. Although there is a significant deviation in predicting house prices in the Kensington community compared to actual prices, this indicates that the model has lower accuracy in predicting house prices in majority communities and higher generality in predicting house prices in most minority communities, excluding Kensington.

st_join(tr_neigh.test, tracts20) %>% 
  filter(!is.na(majority)) %>%
  group_by(majority) %>%
  summarize(mean.MAPE = scales::percent(mean(SalePrice.APE, na.rm = T))) %>%
  st_drop_geometry() %>%
  spread(majority, mean.MAPE) %>%
  kbl(col.name=c('Majority Tracts','Minority Tracts')) %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed")) %>%
  footnote(general_title = "\n", general = "Table 16")
Majority Tracts Minority Tracts
26% 3%

Table 16

Finally, we tested our model’s generalizability across different neighborhoods in Philadelphia. After checking the mean absolute percentage errors of property values for each neighborhood, we’re surprised to notice that our model had predicted a couple of sale price to be negative. On top of that, we found that the majority of those houses predicted to have negative sale price are concentrated in the Kensington Neighborhood. While it is possible that OLS regression models can be off sometime at predicting sale price of houses with certain unique characteristics, such continuous streams of errors concentrated a single neighborhood suggest some systematic problems with our model.

The Kensington neighborhood has faced a variety of challenges, particularly concerning poverty, drug addiction, and crime. A significant portion of Kensington’s population have limited access to economic opportunities, quality education, and healthcare resources. High rates of unemployment and underemployment have contributed to financial instability for many families in the area. Considering that our final model have taken crime rate, proximity to schools, hospitals, and transit stops into account, it is possible that our model has significantly devalued properties at neighborhood facing severe socio-economic challenges like Kensington.

Looking at the overall MAPE is not enough to determine the model’s accuracy as it could mask variations at a more granular spatial scale, for example the neighborhood. Our model has a pretty low MAPE, but we have to be aware that those negative, erroneous sale prices are also contributing to the low MAPE. If we had the right prediction of sale price for these houses, our MAPE should be higher. Therefore, for the map below, we have removed Kensington neighborhood on purpose for it does not make any sense for a neighborhood to have a negative absolute percentage error in sale price.

to_plot <- st_intersection(tr_neigh.test, Philly %>% dplyr::select("NAME")) %>% 
  st_drop_geometry() %>% 
  group_by(NAME) %>%
  summarise(mean.MAPE = mean(SalePrice.APE, na.rm = T)) %>% 
  left_join(Philly) %>% 
  st_sf()
to_plot %>%
  filter(NAME != "KENSINGTON") %>% 
  ggplot() + 
      geom_sf(aes(fill = mean.MAPE)) +
  scale_fill_continuous(low = "grey80", high = "#772e25", name= "MAPE") +
  theme(
        axis.text.x=element_blank(),
        axis.ticks.x=element_blank(),
        axis.text.y=element_blank(),
        axis.ticks.y=element_blank(),
        plot.title = element_text(size = 12, face = "bold"),
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill=NA, size=0.4)
        ) +
  labs(title = "Mean Absolute Percentage Error By Neighborhood") 

Here, we can also see that errors in sale price prediction tend to be larger when the sale price itself is already low. Since we know that houses with low sale price are clustered in areas where there’s higher crime rate, minority population as well as poorer public services nearby, this proves our concern that our model tends to devalue property values in these areas.

tr_neigh.testPhilly <- st_join(tr_neigh.test, Philly %>% select(NAME)) %>% 
  group_by(NAME) %>%
  summarise(mean.MAPE = mean(SalePrice.APE, na.rm = T)) %>% 
  mutate(st_join(tr_neigh.test, Philly %>% select(NAME)) %>% group_by(NAME) %>%
  summarise(mean.Price = mean(sale_price, na.rm = T)))

tr_neigh.testPhilly %>% filter(NAME != "KENSINGTON") %>%
  ggplot(aes(mean.MAPE,mean.Price)) +
  geom_point(color = "#c44536", size = 0.8 ) +
 # geom_smooth(method='lm', formula= y~x, lwd=0.5, color = "#197278") +
    labs(title="Sale Price as a Function of MAPE by Neighborhood",
       x = "MAPE",
       y = "Sale Price") +
  theme_light() +   
  theme(plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 12, face = "bold"), 
        axis.text.x=element_text(size=6),
        axis.text.y=element_text(size=6), 
        axis.title=element_text(size=8))

Conclusion

In conclusion, our model is effective in predicting property values in the city of Philadelphia only to a certain extent. While we are able to fit a model that account for variations of sale price in terms of their internal characteristics, spatial locations, as well as nearby amenities, this model lacks generalizability and tends to devalue certain properties in the city, especially for houses in lower-income communities, such as the Kensington neighborhood. The reason behind this error is probably because our model contains predictors such as nearby crime incidents, school locations, transit stops, etc. While crime incidents are prevalent in Kensington, the latter two predictors have historically been inaccessible neighborhoods like that.

Some further online research into migration and gentrification patterns in Philadelphia reveals that a lot of families choose to leave Philadelphia after their kids reached school age in order to seek for better primary schools. In addition, our previous report investigating transit oriented development suggests that proximity to public transport systems plays a significant role in affecting people’s decision on where to live. Further, studies by City of Philadelphia’s Office of Controller suggests that on average, sales that occur closer to a homicide tend to have lower prices than sales that occur farther from a homicide: a reduction of one homicide would lead to a corresponding 2.3 percent increase of sale prices in the immediate neighborhood. All of these evidence suggests that the absence of critical public amenities/service as well as high concentration of crime incidents would together contribute to housing disinvestment. And over-fitting a model with those kind of predictors might lead to severe devaluation of property values.

At this point, there’s still some work to be done before we recommend this model with Zillow. However, our report does find a variety of significant predictor variables that can explain the variability of property values in Philadelphia. This could provide valuable insights for local real estate market when determining sale price for houses.

With respect to improving this current model, we provide the following suggestions. Firstly, there needs to be a better and more complete data set for property values in Philadelphia. At the data wrangling phase, we found that a lot of the variables in our data set contains missing attributes, errors data entry, inconsistent data categorization, and extreme outliers (that does not make sense). On top of that, the metadata has been poor at documenting data categories. Because of that, we had to make a variety of different assumptions in terms of whether to keep or remove missing data, as well as to combine certain data categories for them to be statistically significant. This makes the model prone to errors. Secondly, we hope to look for some new predictor variables, such as urban greenspace coverage, considering that some of the current variables we have, likely crime incidents, would devalue properties. Nevertheless, we argue that the predictor variables for house price is specific to the context of each city. For places like Philadelphia where crime incidents play a significant role in shaping neighborhood landscape, it is still important to consider crime as a factor in affecting sale price - perhaps we should look for an alternative way to account for crime. Thirdly, given Philadelphia’s complex housing market, we suspect that our model had failed to account for some of the intra-neighborhood nuances. These nuances cannot be properly generalized and represented using a linear model. To fully account for these variations, we should explore alternative machine learning approaches. In addition, we must get a better understanding of the social and historical landscape of Philadelphia when selecting predictor variables that contextualize with their housing characteristics.

LS0tCnRpdGxlOiAiUGhpbGFkZWxwaGlhIEhvbWUgUHJpY2UgUHJlZGljdGlvbiB3aXRoIEdlb3NwYXRpYWwgTWFjaGluZSBMZWFybmluZyIKYXV0aG9yOiAiRW1pbHkgWmhvdSwgRWNoaW4gTGkiCmRhdGU6ICJgciBTeXMuRGF0ZSgpYCIKb3V0cHV0OgogIGh0bWxfZG9jdW1lbnQ6CiAgICB0aGVtZTogc2ltcGxleAogICAgdG9jOiB5ZXMKICAgIHRvY19mbG9hdDogeWVzCiAgICBjb2RlX2ZvbGRpbmc6IGhpZGUKICAgIGNvZGVfZG93bmxvYWQ6IHllcwplZGl0b3Jfb3B0aW9uczoKICBtYXJrZG93bjoKICAgIHdyYXA6IHNlbnRlbmNlCi0tLQoKYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9CmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSkKYGBgCgpWZXJzaW9uIDEuMCBcfCBGaXJzdCBDcmVhdGVkIE9jdCAzLCAyMDIzIFx8IFVwZGF0ZWQgT2N0IDExLCAyMDIzCgpLZXl3b3JkczogT3JkaW5hcnkgTGVhc3QgU3F1YXJlIFJlZ3Jlc3Npb24sIFNwYXRpYWwgQXV0by1jb3JyZWxhdGlvbiwgTW9yYW4ncyBJLCBNb250ZSBDYXJsbyBTaW11bGF0aW9uLCBHZXRpcy1PcmQgR2kqLCBLLWZvbGQgQ3Jvc3MgVmFsaWRhdGlvbiwgSyBOZWFyZXN0IE5laWdoYm9ycwoKCiMgSW50cm9kdWN0aW9uIAoKTWFjaGluZSBsZWFybmluZyBpcyBhIHBvd2VyZnVsIHRvb2wgaW4gdGhlIGZpZWxkIG9mIGNpdHkgcGxhbm5pbmcgZm9yIGl0cyBhYmlsaXR5IHRvIGVmZmljaWVudGx5IHByb2Nlc3MgdmFzdCBhbW91bnRzIG9mIGRhdGEsIGRpc2Nlcm4gaW50cmljYXRlIHBhdHRlcm5zLCBhbmQgZ2VuZXJhdGUgcHJlZGljdGlvbnMgb24gdGhlIHVua25vd24sIHdoaWNoIGNvbnNlcXVlbnRseSBndWlkZXMgbXVuaWNpcGFsIGRlY2lzaW9uLW1ha2luZy4gQW1vbmcgYWxsIGtpbmRzIG9mIG1hY2hpbmUgbGVhcm5pbmcgdGVjaG5pcXVlcywgdGhlIE9yZGluYXJ5IExlYXN0IFNxdWFyZXMgKE9MUykgcmVncmVzc2lvbiBoYXMgZW1lcmdlZCBhcyBhIHBvd2VyZnVsIHRvb2wgZm9yIHByZWRpY3RpbmcgaG91c2UgcHJpY2VzLiAKClByb3BlcnR5IHZhbHVlcyBhcmUgaW5mbHVlbmNlZCBieSBhIHZhcmlldHkgb2Ygc29jaWFsLCBlbnZpcm9ubWVudGFsLCBlY29ub21pYywgYW5kIGN1bHR1cmFsIGZhY3RvcnMgdGhhdCBpcyBoYXJkIHRvIGNhcHR1cmUgdXNpbmcgcHVyZSBkYXRhIGFuYWx5c2lzIGFuZCB2aXN1YWxpemF0aW9uLiBBdCB0aGUgc2FtZSB0aW1lLCBpdCByZW1haW5zIGEgZnVuZGFtZW50YWwgYXNwZWN0IG9mIGNpdHkgcGxhbm5pbmcgYW5kIGRldmVsb3BtZW50OiBwcmVkaWN0aW5nIHNhbGUgcHJpY2VzIGlzIGVzc2VudGlhbCBmb3IgZm9zdGVyaW5nIGEgdHJhbnNwYXJlbnQsIHN0YWJsZSwgYW5kIGVmZmljaWVudCByZWFsIGVzdGF0ZSBtYXJrZXQuIEZvciB0aGUgY2l0eSBnb3Zlcm5tZW50LCBhY2N1cmF0ZSBwcm9wZXJ0eSB2YWx1ZSBwcmVkaWN0aW9uIGVuYWJsZXMgdGhlbSB0byBwbGFuIGluZnJhc3RydWN0dXJlIGRldmVsb3BtZW50LCBhbGxvY2F0ZSByZXNvdXJjZXMgZWZmaWNpZW50bHksIGFuZCBmb3JtdWxhdGUgaG91c2luZyBwb2xpY2llcyB0aGF0IGNhdGVyIHRvIHRoZSBuZWVkcyBvZiBkaXZlcnNlIHBvcHVsYXRpb25zLiBGb3IgaW5kaXZpZHVhbCBob3VzZWhvbGRlcnMsIGl0IGhlbHBzIHdpdGggbWFraW5nIGluZm9ybWVkIGRlY2lzaW9ucyByZWdhcmRpbmcgcHJvcGVydHkgaW52ZXN0bWVudHMgYW5kIGhvbWUtb3duZXJzaGlwLCBlbnN1cmluZyBmaW5hbmNpYWwgc3RhYmlsaXR5IGFuZCBmb3N0ZXJpbmcgc3VzdGFpbmFibGUgY29tbXVuaXRpZXMuCgpUaGUgY2l0eSBvZiBQaGlsYWRlbHBoaWEgaXMga25vd24gZm9yIGNvbXBsZXhpdGllcyBpbiByZWFsIGVzdGF0ZSBpbnZlc3RtZW50LiBJdHMgaG91c2luZyBtYXJrZXQgcmV2ZWFsIHN0YXJrIGRpc3Bhcml0aWVzIHJvb3RlZCBpbiBnZW9ncmFwaHksIHJhY2UsIGFuZCBpbmNvbWUuIE5laWdoYm9yaG9vZHMgaW4gdGhlIGNpdHkgZXhoaWJpdCB2YXN0IGRpZmZlcmVuY2VzIGluIGhvbWUgcHJpY2VzIHRoYXQgaW4gcGFydCBmb3Jlc2hhZG93cyBuZWlnaGJvcmhvb2Qgc29jaWFsLWRlbW9ncmFwaGljIGNoYXJhY3RlcmlzdGljcywgYWNjZXNzIHRvIGFtZW5pdGllcywgaG91c2luZyB0eXBlcywgYW5kIGhpc3RvcmljYWwgcGF0dGVybnMgb2YgaW52ZXN0bWVudCBhbmQgZGlzaW52ZXN0bWVudC4gSW4gcGFydGljdWxhciwgQ2VudGVyIENpdHkgKHBsdXMgaXRzIHN1cnJvdW5kaW5nIG5laWdoYm9yaG9vZHMpIGFuZCBOb3J0aHdlc3QgUGhpbGFkZWxwaGlhIGNvbW1hbmQgaGlnaCBwcm9wZXJ0eSB2YWx1ZXMgZHVlIHRvIHRoZWlyIHByb3hpbWl0eSB0byBhbWVuaXRpZXMgYW5kIHBpY3R1cmVzcXVlIG5laWdoYm9yaG9vZCwgd2hlcmVhcyBhcmVhcyBpbiBOb3J0aGVhc3QgYW5kIFdlc3QgUGhpbGFkZWxwaGlhIGdyYXBwbGUgd2l0aCBsb3dlciBob21lIHByaWNlcyBkdWUgdG8gc3lzdGVtYXRpYyBpbmNvbWUgZXF1aXRpZXMsIHJhY2lhbCB0ZW5zaW9ucywgYW5kIGxpbWl0ZWQgYWNjZXNzIHRvIG9wcG9ydHVuaXRpZXMuIAoKSW4gdGhpcyByZXBvcnQsIHdlIGF0dGVtcHRlZCB0byBwcmVkaWN0IHByb3BlcnR5IHZhbHVlcyBpbiBQaGlsYWRlbHBoaWEgd2l0aCBPTFMgcmVncmVzc2lvbi4gV2UgYWNrbm93bGVkZ2UgdGhlIGRpZmZpY3VsdGllcyBvZiBtYWtpbmcgc3VjaCBwcmVkaWN0aW9uIHVzaW5nIGEgbGluZWFyIHJlZ3Jlc3Npb24gbW9kZWwsIGJlY2F1c2UgdGhlIGNpdHkncyBkaXZlcnNlIGFuZCBtdWx0aWZhY2V0ZWQgaG91c2luZyBtYXJrZXQgc2hhcGVkIGJ5IGhpc3RvcmljYWwgaW5qdXN0aWNlcyBhbmQgc29jaWFsIGRpc3Bhcml0aWVzIHdvdWxkIGRlZnkgdGhlIHNpbXBsaXN0aWMgbGluZWFyIGFzc3VtcHRpb25zIG9mIE9MUyByZWdyZXNzaW9uLiAqKlRoYXQgc2FpZCwgb3VyIGdvYWwgaXMgdG8gZml0IGEgbGluZWFyIHJlZ3Jlc3Npb24gbW9kZWwgdGhhdCBjYXB0dXJlcyB0aGUgaW50ZXJuYWwgY2hhcmFjdGVyaXN0aWNzLCBzdXJyb3VuZGluZyBwdWJsaWMgc2VydmljZXMsIGFuZCBzcGF0aWFsIGNoYXJhY3RlcmlzdGljcyBvZiBwcm9wZXJ0aWVzIGluIFBoaWxhZGVscGhpYSBhcyBtdWNoIGFzIHBvc3NpYmxlLioqIFRoZSBtYWluIGRhdGEgc2V0IHVzZWQgaW4gdGhpcyByZXBvcnQgd2FzIG9idGFpbmVkIGZyb20gW1BISUxBLkdPVl0oaHR0cHM6Ly93d3cucGhpbGEuZ292LykgdGhhdCBjb250YWlucyBhIHZhcmlldHkgb2YgcHJvcGVydHkgY2hhcmFjdGVyaXN0aWNzLCBpbmNsdWRpbmcgc2FsZSBwcmljZS4gSW4gYWRkaXRpb24sIHdlIGhhdmUgbWFkZSB1c2Ugb2YgcmVzb3VyY2VzIGZyb20gW09wZW5EYXRhUGhpbGx5XShodHRwczovL29wZW5kYXRhcGhpbGx5Lm9yZy8pLCBbVVMgQ2Vuc3VzIEJ1cmVhdV0oaHR0cHM6Ly9kYXRhLmNlbnN1cy5nb3YvKSwgYW5kIFtPcGVuIERhdGEgUEhMIE1hcHNdKGh0dHBzOi8vZGF0YS1waGwub3BlbmRhdGEuYXJjZ2lzLmNvbS8pIHRvIGdhaW4gbW9yZSBpbnNpZ2h0cyBpbnRvIHRoZSBkaXN0cmlidXRpb24gb2Ygc29jaWFsIGFtZW5pdGllcywgbG9jYXRpb24gb2YgY3JpbWVzIGluY2lkZW50cywgYW5kIG5laWdoYm9yaG9vZCBjaGFyYWN0ZXJpc3RpY3Mgd2l0aGluIHRoZSBjaXR5LiAKClRoaXMgcmVwb3J0IGlzIHRoZXJlZm9yZSBzdHJ1Y3R1cmVkIGFzIGZvbGxvd3MuICoqRmlyc3RseSoqLCB3ZSBkaXNjdXNzIG91ciByYXRpb25hbCBvZiBvdXIgc2VsZWN0aW9uIG9mIGRlcGVuZGVudCB2YXJpYWJsZXMgYXMgd2VsbCBhcyBvdXIgYXBwcm9hY2ggdG8gZGF0YSBtYW5pcHVsYXRpb24gYW5kIGZlYXR1cmUgZW5naW5lZXJpbmcgdXNpbmcgb3VyIG1haW4gaG91c2luZyBkYXRhIHNldC4gKipTZWNvbmRseSoqLCB3ZSBwcmVzZW50IG91ciBmaW5kaW5ncyBmcm9tIHBlcmZvcm1pbmcgYm90aCBhIGdsb2JhbCBhbmQgYSBsb2NhbCBHIHRlc3QgZm9yIHNwYXRpYWwgYXV0b2NvcnJlbGF0aW9uLiBUaGlzIGlzIGZvbGxvd2VkIGJ5IGFub3RoZXIgZGlzY3Vzc2lvbiBvbiB0aGUgaW1wb3J0YW5jZSBvZiBhY2NvdW50aW5nIGZvciBzcGF0aWFsIGZlYXR1cmVzIHdoZW4gcHJlZGljdGluZyBwcm9wZXJ0eSB2YWx1ZXMgYW5kIGEgY29tcGFyaXNvbiBvZiByZWdyZXNzaW9uIG1vZGVscyB3aXRoIGFuZCB3aXRob3V0IHNwYXRpYWwgZmVhdHVyZXMuICoqVGhpcmRseSoqLCB3ZSBwcmVzZW50IGEgaG9saXN0aWMgc3VtbWFyeSBvZiBhbGwgb2Ygb3VyIHNlbGVjdGVkIGFuZCBwcm9jZXNzZWQgZGVwZW5kZW50IHZhcmlhYmxlcyB0aGF0IHdlIHdpbGwgdXNlIGZvciBvdXIgZmluYWwgbW9kZWwuICoqRmluYWxseSoqLCB3ZSBtYWtlIHByZWRpY3Rpb25zIG9mIHNhbGUgcHJpY2UgdXNpbmcgb3VyIG1vZGVsLiBUaGlzIGlzIHN1cHBsZW1lbnRlZCB3aXRoIGEgdmFyaWV0eSBvZiB0ZXN0cyBvZiBjaGVja2luZyBtb2RlbCdzIGdlbmVyYWxpemFiaWxpdHkgYW5kIGdvb2RuZXNzIG9mIGZpdCBhcyB3ZWxsIGFzIGEgcmVmbGVjdGlvbiBvbiB0aGUgbW9kZWwncyBlZmZlY3RpdmVuZXNzIGFuZCBiaWFzLiAKCk92ZXJhbGwsIG91ciBtb2RlbCBpcyBhYmxlIHRvIGNhcHR1cmUgdGhlIG1ham9yaXR5IG9mIHZhcmlhdGlvbnMgaW4gcHJvcGVydHkgdmFsdWVzIHRocm91Z2hvdXQgUGhpbGFkZWxwaGlhLiBIb3dldmVyLCB3ZSBhcmUgY3JpdGljYWwgb2YgdGhpcyBtb2RlbCdzIGVmZmVjdGl2ZW5lc3MgaW4gcmVhbCB3b3JsZCBzZXR0aW5nIGFzIGl0IHNlZW1zIHRvIHNpZ25pZmljYW50bHkgdW5kZXItcHJlZGljdCBwcm9wZXJ0eSB2YWx1ZXMgaW4gUGhpbGFkZWxwaGlhLCBlc3BlY2lhbGx5IGZvciBsb3dlci1pbmNvbWUgcmFjaWFsIG1pbm9yaXR5IG5laWdoYm9yaG9vZHMuIEdpdmVuIFBoaWxhZGVscGhpYSdzIGRpdmVyc2UgYW5kIG11bHRpZmFjZXRlZCBob3VzaW5nIG1hcmtldCwgd2Ugc3VzcGVjdCB0aGF0IHRoaXMgbW9kZWwgaGFkIGZhaWxlZCB0byBhY2NvdW50IGZvciBzb21lIG9mIHRoZSBpbnRyYS1uZWlnaGJvcmhvb2QgbnVhbmNlcyBjYW5ub3QgYmUgcHJvcGVybHkgZ2VuZXJhbGl6ZWQgYW5kIHJlcHJlc2VudGVkIHVzaW5nIGEgbGluZWFyIG1vZGVsLiBUbyBmdWxseSBhY2NvdW50IGZvciB2YXJpYXRpb25zIGluIGhvbWUgcHJpY2UsIHdlIG11c3QgZ2V0IGEgYmV0dGVyIHVuZGVyc3RhbmRpbmcgb2YgdGhlIHNvY2lhbCBhbmQgaGlzdG9yaWNhbCBsYW5kc2NhcGUgb2YgUGhpbGFkZWxwaGlhIHRvIHNlbGVjdCBwcmVkaWN0b3IgdmFyaWFibGVzIHRoYXQgY29udGV4dHVhbGl6ZSB3aXRoIHRoZSBob3VzaW5nIGNoYXJhY3RlcmlzdGljcyBvZiB0aGUgY2l0eSBhbmQgYXZvaWQgYWNjaWRlbnRhbGx5IGRldmFsdWluZyBjZXJ0YWluIG5laWdoYm9yaG9vZHMuIAoKVGhlIEdpdEh1YiByZXBvc2l0b3J5IGZvciB0aGlzIHJlcG9ydCBpcyBbaGVyZV0oaHR0cHM6Ly9naXRodWIuY29tL2VtaWx5emhvdTExMi9NVVNBNTA4MC1QaGlsbHktTUwvdHJlZS9tYWluKS4gCgpgYGB7ciBsaWJyYXJpZXMsIGluY2x1ZGU9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CiMgZ2xvYmFsIG9wdGlvbnMgCm9wdGlvbnMoc2NpcGVuPTk5OSkKb3B0aW9ucyh0aWdyaXNfY2xhc3MgPSAic2YiKQpvcHRpb25zKGRpZ2l0cyA9IDMpCm9wdGlvbnMocmVwb3MgPSBsaXN0KENSQU49Imh0dHA6Ly9jcmFuLnJzdHVkaW8uY29tLyIpKQoKIyBsaXN0IG9mIHJlcXVpcmVkIHBhY2thZ2VzCnBhY2thZ2VzIDwtIGMoICJ0aWR5dmVyc2UiLCAiZ2dwbG90MiIsICJzdkRpYWxvZ3MiLCAidGlkeWNlbnN1cyIsICJzZiIsICJrbml0ciIsICJybWFya2Rvd24iLCAia2FibGVFeHRyYSIsICJzdHJpbmdyIiwgInNwZGVwIiwgInNmZGVwIiwgImNhcmV0IiwgImNhciIsICJja2FuciIsICJGTk4iLCAiZ3JpZCIsICJncmlkRXh0cmEiLCAiZ2djb3JycGxvdCIsICJjb3JyciIsICJqdG9vbHMiLCAiZ2dzdGFuY2UiLCAiZ2dwdWJyIiwgImJyb29tLm1peGVkIiwgInN0YXJnYXplciIsICJndHN1bW1hcnkiLCAicGF0Y2h3b3JrIikKCiMgaW5zdGFsbCBhbmQgbG9hZCByZXF1aXJlZCBwYWNrYWdlcwpwYWNrYWdlLmNoZWNrIDwtIGxhcHBseSgKICBwYWNrYWdlcywKICBGVU4gPSBmdW5jdGlvbih4KSB7CiAgICBpZiAoIXJlcXVpcmUoeCwgY2hhcmFjdGVyLm9ubHkgPSBUUlVFKSkgewogICAgICBpbnN0YWxsLnBhY2thZ2VzKHgsIGRlcGVuZGVuY2llcyA9IFRSVUUsIHF1aWV0bHk9VFJVRSkKICAgICAgbGlicmFyeSh4LCBjaGFyYWN0ZXIub25seSA9IFRSVUUpCiAgICB9CiAgfQopCgoKCiMgbG9hZCBpbiBtdWx0aXBsZSByaW5nIGJ1ZmZlciBmdW5jdGlvbgpzb3VyY2UoImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS91cmJhblNwYXRpYWwvUHVibGljLVBvbGljeS1BbmFseXRpY3MtTGFuZGluZy9tYXN0ZXIvZnVuY3Rpb25zLnIiKQoKYGBgCgoKYGBge3IgZGF0YSwgaW5jbHVkZT1GQUxTRX0KCnRyYWluaW5nIDwtIHN0X3JlYWQoImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9lbWlseXpob3UxMTIvTVVTQTUwODAtUGhpbGx5LU1ML21haW4vZGF0YS9zdHVkZW50RGF0YS5nZW9qc29uIikgJT4lIHN0X3RyYW5zZm9ybSgnRVNSSToxMDI3MjgnKQoKY3JpbWUgPC0gc3RfcmVhZCgiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL2VtaWx5emhvdTExMi9NVVNBNTA4MC1QaGlsbHktTUwvbWFpbi9kYXRhL2NyaW1lLmdlb2pzb24iKSAlPiUgc3RfdHJhbnNmb3JtKCdFU1JJOjEwMjcyOCcpCgpob3NwaXRhbCA8LSBzdF9yZWFkKCJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vZW1pbHl6aG91MTEyL01VU0E1MDgwLVBoaWxseS1NTC9tYWluL2RhdGEvSG9zcGl0YWxzLmdlb2pzb24iKSAlPiUgc3RfdHJhbnNmb3JtKCdFU1JJOjEwMjcyOCcpCgplbCA8LSBzdF9yZWFkKCJodHRwczovL29wZW5kYXRhLmFyY2dpcy5jb20vZGF0YXNldHMvOGM2ZTI1NzVjOGFkNDZlYjg4N2U2YmIzNTgyNWUxYTZfMC5nZW9qc29uIikKQnJvYWRfU3QgPC0gc3RfcmVhZCgiaHR0cHM6Ly9vcGVuZGF0YS5hcmNnaXMuY29tL2RhdGFzZXRzLzJlOTAzN2ZkNWJlZjQwNjQ4OGZmZTViYjY3ZDIxMzEyXzAuZ2VvanNvbiIpCgpzY2hvb2xzIDwtIHN0X3JlYWQoImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9lbWlseXpob3UxMTIvTVVTQTUwODAtUGhpbGx5LU1ML21haW4vZGF0YS9zY2hvb2wuZ2VvanNvbiIpICU+JSBzdF90cmFuc2Zvcm0oJ0VTUkk6MTAyNzI4JykKCnNob3BwaW5nIDwtIHN0X3JlYWQoImh0dHBzOi8vc2VydmljZXMuYXJjZ2lzLmNvbS9mTGVHamI3dTR1WHFlRjlxL2FyY2dpcy9yZXN0L3NlcnZpY2VzL0NvbW1lcmNpYWxfQ29ycmlkb3JzL0ZlYXR1cmVTZXJ2ZXIvMC9xdWVyeT9vdXRGaWVsZHM9KiZ3aGVyZT0xJTNEMSZmPWdlb2pzb24iKSAlPiUgc3RfdHJhbnNmb3JtKCdFU1JJOjEwMjcyOCcpIAoKZHN0PC0gc3RfcmVhZCgiaHR0cHM6Ly9vcGVuZGF0YS5hcmNnaXMuY29tL2RhdGFzZXRzLzA5NjBlYTBmMzhmNDQxNDZiYjU2MmYyYjIxMjA3NWFhXzAuZ2VvanNvbiIpJT4lCiAgc3RfdHJhbnNmb3JtKCdFU1JJOjEwMjcyOCcpIAoKUGhpbGx5PC0gc3RfcmVhZCgiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL2VtaWx5emhvdTExMi9NVVNBNTA4MC1QaGlsbHktTUwvbWFpbi9kYXRhL25laWdoYm9yaG9vZC5nZW9qc29uIikgJT4lIHN0X3RyYW5zZm9ybSgnRVNSSToxMDI3MjgnKQoKYmxvY2sgPC0gc3RfcmVhZCgiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL2VtaWx5emhvdTExMi9NVVNBNTA4MC1QaGlsbHktTUwvbWFpbi9kYXRhL0NlbnN1c19CbG9ja3NfMjAxMC5nZW9qc29uIikgJT4lIHN0X3RyYW5zZm9ybSgnRVNSSToxMDI3MjgnKQpgYGAKCgojIERhdGEgUHJvY2Vzc2luZyAKCkJhc2VkIG9uIG91ciBwcmlvciBrbm93bGVkZ2UgaW4gaG91c2UgcHJpY2UgYW5kIHRoZSBnaXZlbiBkYXRhIHNldCwgd2UgYmVsaWV2ZSB0aGF0IHRoZSBzYWxlIHByaWNlIG9mIGhvdXNlcyBtYXkgYmUgcmVsYXRlZCB0byBib3RoIHRoZSBpbnRlcm5hbCBhbmQgZ2VvZ3JhcGhpY2FsIGF0dHJpYnV0ZXMgb2YgdGhlIGhvdXNlcy4gSW50ZXJuYWwgYXR0cmlidXRlcyByZWZlciB0byBmYWN0b3JzIHN1Y2ggYXMgdGhlIGFnZSBvZiB0aGUgaG91c2UsIHRoZSBudW1iZXIgb2YgYmVkcm9vbXMgYW5kIGJhdGhyb29tcywgdGhlIHByZXNlbmNlIG9mIGFtZW5pdGllcywgYW5kIHRoZSBvdmVyYWxsIGNvbmRpdGlvbiBvZiB0aGUgaG91c2UuIChXZSB3aWxsIGVsYWJvcmF0ZSBvbiBnZW9ncmFwaGljYWwgYXR0cmlidXRlcyBsYXRlciBpbiB0aGUgcmVwb3J0KS4gCgpSZWdhcmRpbmcgaW50ZXJuYWwgYXR0cmlidXRlcywgd2Ugc2VsZWN0ZWQgdGhlIGZvbGxvd2luZyB2YXJpYWJsZXMgZnJvbTogdGhlIHllYXIgdGhlIGhvdXNlIHdhcyBidWlsdCwgdGhlIG51bWJlciBvZiBiZWRyb29tcywgdGhlIG51bWJlciBvZiBiYXRocm9vbXMsIHRoZSBudW1iZXIgb2YgYWlyIGNvbmRpdGlvbmluZyB1bml0cywgYmFzZW1lbnQgY29uZGl0aW9uLCB0aGUgbnVtYmVyIG9mIGZpcmVwbGFjZXMsIGdhcmFnZSBjb25kaXRpb24sIHRoZSBudW1iZXIgb2Ygc3RvcmllcywgdG90YWwgYXJlYSwgbGl2YWJsZSBhcmVhLCBoZWF0aW5nIGNvbmRpdGlvbiwgdmlldyBjYXRlZ29yeSwgcXVhbGl0eSBncmFkZSBhbmQgdGhlIHR5cGUgb2YgYnVpbGRpbmcgZGVzY3JpcHRpb24uIE90aGVyIGluZGljYXRvcnMgcmVsYXRlZCB0byBob3VzZSBzYWxlIHByaWNlcywgc3VjaCBhcyB0b3BvZ3JhcGh5LCBpbnRlcm5hbCBjb25kaXRpb24sIGFuZCBleHRlcm5hbCBjb25kaXRpb24sIHdlcmUgbm90IGluY2x1ZGVkIGluIHRoZSBzY29wZSBvZiBpbmRpY2F0b3JzIGR1ZSB0byB0aGVpciBjb3JyZWxhdGlvbiB3aXRoIHRoZSBzZWxlY3RlZCBpbmRpY2F0b3JzLgoKV2UgY2FsY3VsYXRlZCB0aGUgYWdlIG9mIHRoZSBob3VzZXMgYnkgc3VidHJhY3RpbmcgMjAyMyBmcm9tIHRoZSB5ZWFyIHRoZXkgd2VyZSBidWlsdCBhbmQgdXNlZCBpdCBhcyBhIG5ldyB0aW1lIG1lYXN1cmVtZW50IGluZGljYXRvci4gU2luY2UgdGhlIG51bWJlciBvZiBiZWRyb29tcyBhbmQgYmF0aHJvb21zIGFyZSBjb3JyZWxhdGVkLCB3ZSB1c2VkIHRoZSB0b3RhbCBudW1iZXIgb2Ygcm9vbXMgYXMgYSBuZXcgaW5kaWNhdG9yLiBTcGVjaWZpYyBxdWFudGl0aWVzIG9yIGRldGFpbGVkIGNvbmRpdGlvbnMgb2YgYWlyIGNvbmRpdGlvbmluZywgYmFzZW1lbnQsIGZpcmVwbGFjZSwgZ2FyYWdlLCBhbmQgaGVhdGluZyBkaWQgbm90IGhhdmUgYSBzaWduaWZpY2FudCBpbXBhY3QgaW5kaXZpZHVhbGx5LCBidXQgdGhlaXIgcHJlc2VuY2Ugb3IgYWJzZW5jZSBoYWQgYSBzaWduaWZpY2FudCBpbXBhY3Qgb24gaG91c2UgcHJpY2VzLiBUaGVyZWZvcmUsIHdlIGNyZWF0ZWQgbmV3IGJpbmFyeSB2YXJpYWJsZXMgZm9yIGVhY2ggb2YgdGhlbS4gUmVnYXJkaW5nIHRoZSBudW1iZXIgb2Ygc3Rvcmllcywgd2UgY29sbGFwc2VkIHRoZWlyIGNhdGVnb3JpZXMgaW50byBzaW5nbGUsIGRvdWJsZSwgYW5kIG11bHRpcGxlLiBGb3IgdG90YWwgYXJlYSwgd2UgY2hvc2UgdGhlIGxhcmdlciBvZiB0aGUgdG90YWwgYXJlYSBhbmQgbGl2YWJsZSBhcmVhIGFzIHRoZSBtZWFzdXJlbWVudCBpbmRpY2F0b3IgYW5kIGFwcGxpZWQgYSBsb2dhcml0aG1pYyB0cmFuc2Zvcm1hdGlvbiB0byBhY2hpZXZlIGEgbm9ybWFsIGRpc3RyaWJ1dGlvbi4gRm9yIHRoZSB2aWV3IHR5cGUgbmVhciB0aGUgaG91c2UsIHdlIHN1bW1hcml6ZWQgdGhlIGNhdGVnb3JpZXMgaW50byB0aHJlZSBjbGFzc2VzOiBTY2VuaWMgKGluY2x1ZGluZyBDaXR5c2NhcGUvU2t5bGluZSwgRmxvd2luZyBXYXRlciwgUGFyay9HcmVlbiBBcmVhKSwgVHlwaWNhbCwgYW5kIE90aGVycyAoVXJiYW4pLiBGb3IgcXVhbGl0eSBhbmQgYnVpbGRpbmcgdHlwZSwgd2UgcGVyZm9ybWVkIHNpbWlsYXIgcHJvY2Vzc2luZywgY29tYmluaW5nIGluZGljYXRvcnMgYWJvdmUgImF2ZXJhZ2UiIGludG8gYSAiZ29vZCIgY2F0ZWdvcnkgYW5kIHRob3NlICJhdmVyYWdlIiBvciBiZWxvdyBpbnRvIGEgImJhZCIgY2F0ZWdvcnkuCiAKCmBgYHtyIGJhc2VsaW5lIGRhdGEgcHJvY2Vzc30KdHJfcHJvY2Vzc2VkIDwtIHRyYWluaW5nICU+JSAKICBtdXRhdGUoQWdlID0gMjAyMyAtIHllYXJfYnVpbHQpICU+JSAKICBtdXRhdGUobnVtUm9vbXMgPSBjYXNlX3doZW4oCiAgICAgICAgIGlzLm5hKG51bWJlcl9vZl9iZWRyb29tcykgJiAhaXMubmEobnVtYmVyX29mX2JlZHJvb21zKSB+IG51bWJlcl9vZl9iYXRocm9vbXMsCiAgICAgICAgIGlzLm5hKG51bWJlcl9vZl9iYXRocm9vbXMpICYgIWlzLm5hKG51bWJlcl9vZl9iZWRyb29tcykgfiBudW1iZXJfb2ZfYmVkcm9vbXMsCiAgICAgICAgIGlzLm5hKG51bWJlcl9vZl9iYXRocm9vbXMpICYgaXMubmEobnVtYmVyX29mX2JlZHJvb21zKSB+IDAsCiAgICAgICAgIFRSVUV+IG51bWJlcl9vZl9iZWRyb29tcyArIG51bWJlcl9vZl9iYXRocm9vbXMpICklPiUgCiAgbXV0YXRlKGhhc0FDID0gY2FzZV93aGVuKAogICAgICAgICBjZW50cmFsX2FpciAlaW4lIGMoIjEiLCAiWSIpIH4gIlkiLAogICAgICAgICBUUlVFfiAiTiIpLAogICAgICAgICBoYXNCYXNlbWVudCA9IGNhc2Vfd2hlbigKICAgICAgICAgYmFzZW1lbnRzICVpbiUgYygiMSIsICI0IiwgIkEiLCAiQiIsICJDIiwgIkQiLCAiRSIsICJGIikgfiAiWSIsCiAgICAgICAgIFRSVUV+ICJOIiksCiAgICAgICAgIGhhc0ZpcmVwbGFjZSA9IGNhc2Vfd2hlbigKICAgICAgICAgZmlyZXBsYWNlcyA9PSAwIHwgaXMubmEoZmlyZXBsYWNlcykgIH4gIk4iLAogICAgICAgICBUUlVFfiAiWSIpLAogICAgICAgICBoYXNHYXJhZ2UgPSBjYXNlX3doZW4oCiAgICAgICAgIGdhcmFnZV90eXBlID09IDAgfCBpcy5uYShnYXJhZ2VfdHlwZSkgIH4gIk4iLAogICAgICAgICBUUlVFfiAiWSIpLAogICAgICAgICBzdG9yaWVzID0gY2FzZV93aGVuKAogICAgICAgICBudW1iZXJfc3RvcmllcyA9PSAxICB+ICJzaW5nbGUiLAogICAgICAgICBudW1iZXJfc3RvcmllcyA9PSAyICB+ICJkb3VibGUiLAogICAgICAgICBUUlVFfiAibXVsdGlwbGUiKSwKICAgICAgICAgYXJlYSA9IGNhc2Vfd2hlbigKICAgICAgICAgICB0b3RhbF9saXZhYmxlX2FyZWEgPiB0b3RhbF9hcmVhIH4gdG90YWxfbGl2YWJsZV9hcmVhLCAgCiAgICAgICAgICAgVFJVRSB+IHRvdGFsX2FyZWEpLAogICAgICAgICBoYXNIZWF0ZXIgPSBjYXNlX3doZW4oCiAgICAgICAgIHR5cGVfaGVhdGVyID09IDAgfCBpcy5uYSh0eXBlX2hlYXRlcikgIH4gIk4iLAogICAgICAgICBUUlVFfiAiWSIpLAogICAgICAgICB2aWV3ID0gY2FzZV93aGVuKAogICAgICAgICB2aWV3X3R5cGUgPT0gIkkiIHwgdmlld190eXBlID09ICIwIiB8IGlzLm5hKHZpZXdfdHlwZSkgICB+ICJUeXBpY2FsIiwKICAgICAgICAgdmlld190eXBlID09ICJBIiB8IHZpZXdfdHlwZSA9PSAiQiIgfCB2aWV3X3R5cGUgPT0gIkMiIH4gIlNjZW5pYyIsCiAgICAgICAgIFRSVUV+ICJVcmJhbiIpLCAKICAgICAgICAgcXVhbGl0eSA9IGNhc2Vfd2hlbigKICAgICAgICAgcXVhbGl0eV9ncmFkZSAlaW4lIGMoIjQiLCAiNSIsICI2IiwgIkEiLCAiQSsiLCAiQS0iLCAiQiIsICJCKyIsIkItIiwiUyIsIlMrIiwiWC0iKSAgfiAiR29vZCIsCiAgICAgICAgIFRSVUV+ICJCYWQiKSwKICAgICAgICAgYnVpbGRpbmdkaXMgPSBjYXNlX3doZW4oCiAgICAgICAgIGdyZXBsKCJST1ciLGJ1aWxkaW5nX2NvZGVfZGVzY3JpcHRpb25fbmV3LCBpZ25vcmUuY2FzZSA9IEZBTFNFKSB+ICJSb3ciLAogICAgICAgICBncmVwbCgiVFdJTiIsYnVpbGRpbmdfY29kZV9kZXNjcmlwdGlvbl9uZXcsIGlnbm9yZS5jYXNlID0gRkFMU0UpIH4gIlRXSU4iLAogICAgICAgICBUUlVFfiAiT3RoZXIiKSkgJT4lIAogIG11dGF0ZShsb2dhcmVhID0gbG9nKGFyZWEpKQoKYGBgCgpJbiBhZGRpdGlvbiB0byB0aGVzZSB0cmFuc2Zvcm1hdGlvbnMsIHdlIGV4Y2x1ZGVkIHVucmVhc29uYWJsZSBvdXRsaWVycyBpbiB0aGUgdHJhaW5pbmcgZGF0YSBzZXQuIFdlIHNlbGVjdGVkIGhvdXNlcyB3aGVyZSBhZ2UgaXMgbGVzcyB0aGFuIDUwMCB5ZWFycywgcm9vbSBjb3VudCBpcyBsZXNzIHRoYW4gMzAsIGFuZCBib3RoIHRvdGFsIGhvdXNlIGFyZWEgYW5kIGxpdmFibGUgaG91c2UgYXJlYSBhcmUgbm9uLXplcm8gb3IgbWlzc2luZy4gV2UgYWxzbyBleGNsdWRlZCBob3VzZXMgd2l0aCBhcmVhIGxhcmdlciB0aGFuIDUwLDAwMCBzcXVhcmUgZmVldCBiZWNhdXNlIHRoZXNlIGFyZSBub3QgdHlwaWNhbCBzaW5nbGUgZmFtaWx5IGhvdXNlcy4gV2UgdGhlbiBsaW1pdGVkIHRoZSBob3VzaW5nIHByaWNlcyBpbiB0aGUgdHJhaW5pbmcgZGF0YSBzZXQgdG8gYmUgYmVsb3cgJDIsMDAwLDAwMCB0byByZWR1Y2UgdGhlIGluZmx1ZW5jZSBvZiBleHRyZW1lIHZhbHVlcyBvbiB0aGUgbW9kZWwuCgpgYGB7ciBmaWx0ZXIgZXh0cmVtZSB2YWx1ZXN9CgpiYXNlbGluZSA8LSB0cl9wcm9jZXNzZWQgJT4lIAogIGZpbHRlcih0b1ByZWRpY3QgIT0gIkNIQUxMRU5HRSIpICU+JQogIGZpbHRlcihBZ2UgPCA1MDApICU+JSAKICBmaWx0ZXIoc2FsZV9wcmljZSA8IDIwMDAwMDApICU+JSAKICBmaWx0ZXIobnVtUm9vbXMgPCAzMCkgJT4lIAogIGZpbHRlcih0b3RhbF9saXZhYmxlX2FyZWEgIT0gMCAmIGlzLm5hKHRvdGFsX2FyZWEpID09IEZBTFNFKSAlPiUgCiAgZmlsdGVyKGFyZWEgPCA1MDAwMCkgCgpgYGAKCgojIEZpdHRpbmcgUmVncmVzc2lvbiBNb2RlbAoKIyMgQmFzZWxpbmUgUmVncmVzc2lvbgoKV2UgY29uZHVjdGVkIGFuIGluaXRpYWwgcmVncmVzc2lvbiBhbmQgb2J0YWluZWQgdGhlIGZvbGxvd2luZyByZXN1bHRzLiBBbGwgaW5kaWNhdG9ycyBzaG93IHN0YXRpc3RpY2FsbHkgc2lnbmlmaWNhbnQgY29ycmVsYXRpb25zIHdpdGggaG91c2Ugc2FsZSBwcmljZXMgd2l0aCBwIDwgMC4wMDEuIFRoZSBhZGp1c3RlZCBSLXNxdWFyZWQgdmFsdWUgaXMgMC40NDMsIGluZGljYXRpbmcgdGhhdCB0aGUgbW9kZWwgY2FuIGV4cGxhaW4gNDQuMyUgb2YgdmFyaWF0aW9ucyBpbiBzYWxlIHByaWNlLiBUaGUgc3RhbmRhcmQgZXJyb3JzIGZvciBwcmVkaWN0aW5nIHRoZSBwcmVzZW5jZSBvZiBhIGZpcmVwbGFjZSwgZ2FyYWdlLCBhbmQgbGFuZHNjYXBlIHR5cGUgYXJlIGdyZWF0ZXIgdGhhbiA1MDAwLCB3aGljaCBtYXkgYWZmZWN0IHRoZSBwcmVjaXNpb24gb2YgdGhlIG1vZGVsLCBidXQgdGhpcyBpcyB0aGUgb3B0aW1hbCByZXN1bHQgd2UgYWNoaWV2ZWQgc28gZmFyLiBUaGUgR2VuZXJhbGl6ZWQgSW52ZXJzZSBWYXJpYW5jZSBJbmZsYXRpb24gRmFjdG9yIChHSVZGKSBmb3IgZWFjaCBpbmRpY2F0b3IgaXMgbGVzcyB0aGFuIDMsIGluZGljYXRpbmcgdGhhdCB0aGVyZSBhcmUgbm8gaXNzdWVzIG9mIG11bHRpY29sbGluZWFyaXR5IGluIHRoZSBtb2RlbC4KCmBgYHtyIGJhc2VsaW5lIHJlZ3Jlc3Npb24sIGZpZy5oZWlnaHQ9OCwgZmlnLndpZHRoPTExfQoKcmVnMSA8LSBsbShzYWxlX3ByaWNlIH4gLiwgZGF0YSA9IGJhc2VsaW5lICU+JSBzdF9kcm9wX2dlb21ldHJ5KCkgJT4lIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkcGx5cjo6c2VsZWN0KHNhbGVfcHJpY2UsIEFnZSwgbnVtUm9vbXMsIGhhc0Jhc2VtZW50LCBoYXNBQywgcXVhbGl0eSwgYnVpbGRpbmdkaXMsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhhc0ZpcmVwbGFjZSwgaGFzR2FyYWdlLCBzdG9yaWVzLCBsb2dhcmVhLCB2aWV3KSkKc3VtbWFyeV9yZWcxIDwtIHN1bW1hcnkocmVnMSkKY29lZmZpY2llbnRzX3RhYmxlIDwtIGFzLmRhdGEuZnJhbWUoc3VtbWFyeV9yZWcxJGNvZWZmaWNpZW50cykKCmNvZWZmaWNpZW50c190YWJsZSRzaWduaWZpY2FuY2UgPC0gaWZlbHNlKGNvZWZmaWNpZW50c190YWJsZSRgUHIoPnx0fClgIDwgMC4wMDEsICcqKionLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmZWxzZShjb2VmZmljaWVudHNfdGFibGUkYFByKD58dHwpYCA8IDAuMDEsICcqKicsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmZWxzZShjb2VmZmljaWVudHNfdGFibGUkYFByKD58dHwpYCA8IDAuMDUsICcqJywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmZWxzZShjb2VmZmljaWVudHNfdGFibGUkYFByKD58dHwpYCA8IDAuMSwgJy4nLCAnJykpKSkKCmNvZWZmaWNpZW50c190YWJsZSRwX3ZhbHVlIDwtIHBhc3RlMChyb3VuZChjb2VmZmljaWVudHNfdGFibGUkYFByKD58dHwpYCwgZGlnaXRzID0gMyksIGNvZWZmaWNpZW50c190YWJsZSRzaWduaWZpY2FuY2UpCgpjb2VmZmljaWVudHNfdGFibGUgJT4lCiAgc2VsZWN0KC1zaWduaWZpY2FuY2UsIC1gUHIoPnx0fClgKSAlPiUgCiAga2FibGUoYWxpZ24gPSAiciIpICU+JQogIGthYmxlX3N0eWxpbmcoYm9vdHN0cmFwX29wdGlvbnMgPSBjKCJzdHJpcGVkIiwgImhvdmVyIiwgImNvbmRlbnNlZCIpKSAlPiUKICBmb290bm90ZShnZW5lcmFsX3RpdGxlID0gIlxuIiwgZ2VuZXJhbCA9ICJUYWJsZSAxIikKCm1vZGVsX3N1bW1hcnkgPC0gZGF0YS5mcmFtZSgKICBTdGF0aXN0aWMgPSBjKCJNdWx0aXBsZSBSLXNxdWFyZWQiLCAiQWRqdXN0ZWQgUi1zcXVhcmVkIiwgIkYtc3RhdGlzdGljIiksCiAgVmFsdWUgPSBjKAogICAgc3VtbWFyeV9yZWcxJHIuc3F1YXJlZCwKICAgIHN1bW1hcnlfcmVnMSRhZGouci5zcXVhcmVkLAogICAgc3VtbWFyeV9yZWcxJGZzdGF0aXN0aWNbMV0KICApCikKCm1vZGVsX3N1bW1hcnkgJT4lCiAga2FibGUoKSAlPiUKICBrYWJsZV9zdHlsaW5nKGJvb3RzdHJhcF9vcHRpb25zID0gYygic3RyaXBlZCIsICJob3ZlciIsICJjb25kZW5zZWQiKSklPiUKICBmb290bm90ZShnZW5lcmFsX3RpdGxlID0gIlxuIiwgZ2VuZXJhbCA9ICJUYWJsZSAyIikKCm5ld192YXJpYWJsZV9uYW1lcyA8LSBjKAogICJBZ2UiLAogICJOdW1iZXIgb2YgUm9vbXMiLAogICJCYXNlbWVudCIsCiAgIkFpciBDb25kaXRpb25lciIsCiAgIlF1YWxpdHkgR3JhZGUiLAogICJCdWlsZGluZyBUeXBlIiwKICAiRmlyZXBsYWNlIiwKICAiR2FyYWdlIiwKICAiTnVtYmVyIFN0b3JpZXMiLAogICJUeXBlIG9mIFZpZXciLAogICJBcmVhIgopCnZpZl9yZWcxIDwtIHZpZihyZWcxKQp2aWZfZGYgPC0gYXMuZGF0YS5mcmFtZSh2aWZfcmVnMSkKdmlmX2RmJFZhcmlhYmxlIDwtIG5ld192YXJpYWJsZV9uYW1lcwp2aWZfZGYgPC0gdmlmX2RmWywgYygiVmFyaWFibGUiLCBuYW1lcyh2aWZfZGYpWyFuYW1lcyh2aWZfZGYpICVpbiUgIlZhcmlhYmxlIl0pXQoKdmlmX2RmICU+JQogIGthYmxlKHJvdy5uYW1lcyA9IEZBTFNFKSAlPiUKICBrYWJsZV9zdHlsaW5nKGJvb3RzdHJhcF9vcHRpb25zID0gYygic3RyaXBlZCIsICJob3ZlciIsICJjb25kZW5zZWQiKSkgJT4lCiAgZm9vdG5vdGUoZ2VuZXJhbF90aXRsZSA9ICJcbiIsIGdlbmVyYWwgPSAiVGFibGUgMyIpCmBgYAoKQWNjb3JkaW5nIHRvIHRoZSByZXNpZHVhbCBwbG90LCB0aGUgc2NhdHRlciBwb2ludHMgZXhoaWJpdCBkaXZlcmdlbnQgYW5kIGlycmVndWxhciBwYXR0ZXJucyBhcm91bmQgeT0wLiBNb3N0IHBvaW50cyBhcmUgY29uY2VudHJhdGVkIGluIHRoZSB1cHBlciBwYXJ0LCBzdWdnZXN0aW5nIHRoYXQgdGhlIG9yaWdpbmFsIG1vZGVsIGRvZXMgbm90IGhhdmUgYSBsaW5lYXIgcmVsYXRpb25zaGlwLiBUaGUgZGlzdHJpYnV0aW9uIHRyZW5kIG9mIHNjYXR0ZXIgcG9pbnRzIHNsYW50cyB1cHdhcmRzLCBpbmRpY2F0aW5nIGFuIHVuZGVyZXN0aW1hdGlvbiB0ZW5kZW5jeSB0byBzb21lIGV4dGVudC4KCmBgYHtyIHJlc2lkdWFsIHBsb3QgYmFzZWxpbmV9CgpyZXNpZHVhbHNfZGYgPC0gZGF0YS5mcmFtZShSZXNpZHVhbHMgPSByZXNpZChyZWcxKSwgRml0dGVkID0gZml0dGVkKHJlZzEpKQpnZ3Bsb3QocmVzaWR1YWxzX2RmLCBhZXMoeCA9IEZpdHRlZCwgeSA9IFJlc2lkdWFscykpICsKICBnZW9tX3BvaW50KHNpemUgPSAwLjQsIGNvbG9yID0gIiMyODNkM2IiKSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgbGluZXR5cGUgPSAiZGFzaGVkIiwgY29sb3IgPSAiI2M0NDUzNiIpICsKICBsYWJzKHRpdGxlID0gIlJlc2lkdWFsIFBsb3QgZm9yIEJhc2VsaW5lIFJlZ3Jlc3Npb24iLAogICAgICAgc3VidGl0bGUgPSAiRWFjaCBkb3QgcmVwcmVzZW50IG9uZSBwcm9wZXJ0eSAiLAogICAgICAgY2FwdGlvbiA9ICJEYXRhOiBQaGlsYWRlbHBoaWEgUHJvcGVydGllcyBhbmQgQ3VycmVudCBBc3Nlc3NtZW50cyIsIAogICAgICAgeCA9ICJGaXR0ZWQgVmFsdWVzIiwKICAgICAgIHkgPSAiUmVzaWR1YWxzIikgKwogIHRoZW1lX2xpZ2h0KCkgKyAgIAogIHRoZW1lKHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDksZmFjZSA9ICJpdGFsaWMiKSwKICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgZmFjZSA9ICJib2xkIiksIAogICAgICAgIGF4aXMudGV4dC54PWVsZW1lbnRfdGV4dChzaXplPTYpLAogICAgICAgIGF4aXMudGV4dC55PWVsZW1lbnRfdGV4dChzaXplPTYpLCAKICAgICAgICBheGlzLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTgpKQoKCmBgYAoKVXNpbmcgdGhlIGJhc2VsaW5lIG1vZGVsLCB3ZSBwcmVkaWN0ZWQgdGhlIHNhbGUgcHJpY2UgdGhyb3VnaCBzcGxpdHRpbmcgdGhlIGRhdGEgaW50byB0cmFpbmluZyBhbmQgdGVzdGluZyBzZXQsIGFuZCBjYWxjdWxhdGVkIHRoZSBNZWFuIEFic29sdXRlIEVycm9yIChNQUUpIGFuZCBNZWFuIEFic29sdXRlIFBlcmNlbnRhZ2UgRXJyb3IgKE1BUEUpIGZvciBvdXIgdGVzdGluZyBzZXQuIFRoZSBjdXJyZW50IE1BRSBhbmQgTUFQRSBhcmUgOTgzMjkgYW5kIDM5JSwgcmVzcGVjdGl2ZWx5LgoKYGBge3IgcHJlZGljdGlvbiB1c2luZyBiYXNlbGluZSwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0KCnNldC5zZWVkKDQ4NSkgCmluVHJhaW5fYmFzZSA8LSBjcmVhdGVEYXRhUGFydGl0aW9uKAogICB5ID0gcGFzdGUoYmFzZWxpbmUkaGFzQmFzZW1lbnQsIGJhc2VsaW5lJGhhc0FDLCBiYXNlbGluZSRxdWFsaXR5LCBiYXNlbGluZSRidWlsZGluZ2RpcywgYmFzZWxpbmUkaGFzRmlyZXBsYWNlLCBiYXNlbGluZSRoYXNHYXJhZ2UsIGJhc2VsaW5lJHN0b3JpZXMsIGJhc2VsaW5lJHZpZXcpLAogICAgICAgICAgICAgIHAgPSAuNjAsIGxpc3QgPSBGQUxTRSkKCnRyX3Byb2Nlc3NlZC50cmFpbmluZyA8LSBiYXNlbGluZVtpblRyYWluX2Jhc2UsXSAKdHJfcHJvY2Vzc2VkLnRlc3QgPC0gYmFzZWxpbmVbLWluVHJhaW5fYmFzZSxdICAKCgpyZWcudHJhaW5fYmFzZSA8LSBsbShzYWxlX3ByaWNlIH4gLiwgZGF0YSA9IHRyX3Byb2Nlc3NlZC50cmFpbmluZyAlPiUgc3RfZHJvcF9nZW9tZXRyeSgpICU+JSAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZHBseXI6OnNlbGVjdChzYWxlX3ByaWNlLCBBZ2UsIG51bVJvb21zLCBoYXNCYXNlbWVudCwgaGFzQUMsIHF1YWxpdHksIGJ1aWxkaW5nZGlzLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBoYXNGaXJlcGxhY2UsIGhhc0dhcmFnZSwgc3RvcmllcywgbG9nYXJlYSwgdmlldykpCnRyX3Byb2Nlc3NlZC50ZXN0IDwtIAogIHRyX3Byb2Nlc3NlZC50ZXN0ICU+JSAKICBtdXRhdGUoUmVncmVzc2lvbiA9ICJCYXNlIiwgCiAgICAgICAgIFNhbGVQcmljZS5QcmVkaWN0ID0gcHJlZGljdChyZWcudHJhaW5fYmFzZSwgdHJfcHJvY2Vzc2VkLnRlc3QpLAogICAgICAgICBTYWxlUHJpY2UuRXJyb3IgPSBTYWxlUHJpY2UuUHJlZGljdCAtIHNhbGVfcHJpY2UsCiAgICAgICAgIFNhbGVQcmljZS5BYnNFcnJvciA9IGFicyhTYWxlUHJpY2UuUHJlZGljdCAtIHNhbGVfcHJpY2UpLAogICAgICAgICBTYWxlUHJpY2UuQVBFID0gKGFicyhTYWxlUHJpY2UuUHJlZGljdCAtIHNhbGVfcHJpY2UpKSAvIFNhbGVQcmljZS5QcmVkaWN0KQoKdHJfcHJvY2Vzc2VkLnRlc3QgJT4lIAogIHN0X2Ryb3BfZ2VvbWV0cnkoKSAlPiUKICBzdW1tYXJpc2UoTUFFID0gbWVhbihTYWxlUHJpY2UuQWJzRXJyb3IpLAogICAgICAgICAgICBNQVBFID0gbWVhbihTYWxlUHJpY2UuQVBFKSoxMDApICU+JQogIGtibChjb2wubmFtZT1jKCdNZWFuIEFic29sdXRlIEVycm9yJywnTWVhbiBBYnNvbHV0ZSBQZXJjZW50YWdlIEVycm9yJykpICU+JQogIGthYmxlX3N0eWxpbmcoIGJvb3RzdHJhcF9vcHRpb25zID0gYygic3RyaXBlZCIsICJob3ZlciIsICJjb25kZW5zZWQiKSkgJT4lCiAgZm9vdG5vdGUoZ2VuZXJhbF90aXRsZSA9ICJcbiIsIGdlbmVyYWwgPSAiVGFibGUgNCIpCgpgYGAKCgojIyBTcGF0aWFsIEF1dG9jb3JyZWxhdGlvbiBDaGVjayAKClVzaW5nIGJhc2VsaW5lIHJlZ3Jlc3Npb24gdG8gcHJlZGljdCBzYWxlIHByaWNlIG9ubHkgdGVsbHMgcGFydCBvZiB0aGUgc3RvcnkgYmVjYXVzZSBwcm9wZXJ0eSB2YWx1ZXMgYXJlIGFsc28gY2xvc2VkIHJlbGF0ZWQgdG8gdGhlaXIgbG9jYXRpb24gaW4gdGhlIGNpdHkuIFByb3BlcnRpZXMgaW4gY2xvc2UgcHJveGltaXR5IG9mdGVuIHNoYXJlIHNpbWlsYXIgYXR0cmlidXRlcyBkdWUgdG8gbmVpZ2hib3Job29kIGFtZW5pdGllcywgc29jaW9lY29ub21pYyBmYWN0b3JzLCBhbmQgaW5mcmFzdHJ1Y3R1cmFsIGRldmVsb3BtZW50LiBUaGVyZWZvcmUsIHdlIG5lZWQgdG8gY2hlY2sgdGhlIGRlZ3JlZSB0byB3aGljaCBzYWxlIHByaWNlIG9mIHByb3BlcnRpZXMgY2xvc2UgdG8gZWFjaCBvdGhlciBhcmUgY29ycmVsYXRlZC4gV2UgY3JlYXRlIGEgc3BhdGlhbCB3ZWlnaHQgbWF0cml4IHRoYXQgcmVsYXRlIGVhY2ggc2FsZSBwcmljZSB0byBmaXZlIG9mIGl0cyBuZWFyZXN0IHNhbGUgcHJpY2VzLiBXZSBjb21wdXRlZCBNb3JhbidzIEkgdW5kZXIgdGhlIG51bGwgaHlwb3RoZXNpcyB0aGF0IHNhbGUgcHJpY2UgaXMgcmFuZG9tbHkgZGlzdHJpYnV0ZWQuIFRoZSBmaWd1cmUgYmVsb3cgcGxvdCB0aGUgZnJlcXVlbmN5IG9mIDk5OSByYW5kb21seSBwZXJtdXRhdGVkIEkgdW5kZXIgTW9udGUgQ2FybG8gc2ltdWxhdGlvbiwgd2hpY2ggc2hvd3MgdGhhdCBvdXIgb2JzZXJ2ZWQgSSBpcyBtdWNoIGhpZ2hlciB0aGFuIGFsbCBvZiB0aGUgcmFuZG9tbHkgZ2VuZXJhdGVkIEkncy4gVGhpcyB2YWxpZGF0ZXMgdGhlIGV4aXN0ZW5jZSBvZiBzcGF0aWFsIGF1dG9jb3JyZWxhdGlvbi4gCgoKYGBge3IgR2xvYmFsIE1vcmFuIEksIHdhcm5pbmc9RkFMU0V9Cgpjb29yZHMudGVzdCA8LSAgc3RfY29vcmRpbmF0ZXModHJfcHJvY2Vzc2VkLnRlc3QpIApuZWlnaGJvckxpc3QudGVzdCA8LSBrbm4ybmIoa25lYXJuZWlnaChjb29yZHMudGVzdCwgNSkpCnNwYXRpYWxXZWlnaHRzLnRlc3QgPC0gbmIybGlzdHcobmVpZ2hib3JMaXN0LnRlc3QsIHN0eWxlPSJXIikKCm1vcmFuVGVzdCA8LSBtb3Jhbi5tYyh0cl9wcm9jZXNzZWQudGVzdCRTYWxlUHJpY2UuRXJyb3IsIAogICAgICAgICAgICAgICAgICAgICAgc3BhdGlhbFdlaWdodHMudGVzdCwgbnNpbSA9IDk5OSkKCmdncGxvdChhcy5kYXRhLmZyYW1lKG1vcmFuVGVzdCRyZXNbYygxOjk5OSldKSwgYWVzKG1vcmFuVGVzdCRyZXNbYygxOjk5OSldKSkgKwogIGdlb21faGlzdG9ncmFtKGJpbndpZHRoID0gMC4wMSwgZmlsbCA9ICIjMjgzZDNiIikgKwogIGdlb21fdmxpbmUoYWVzKHhpbnRlcmNlcHQgPSBtb3JhblRlc3Qkc3RhdGlzdGljKSwgY29sb3VyID0gIiNjNDQ1MzYiICxzaXplPTEpICsKICBzY2FsZV94X2NvbnRpbnVvdXMobGltaXRzID0gYygtMC41LCAwLjUpKSArCiAgbGFicyh0aXRsZSA9ICJPYnNlcnZlZCBhbmQgUGVybXV0ZWQgTW9yYW4ncyBJIiwKICAgICAgIHN1YnRpdGxlID0gIk9ic2VydmVkIE1vcmFuJ3MgSSBpbiBSZWQiLAogICAgICAgeCA9ICJNb3JhbidzIEkiLAogICAgICAgeSA9ICJDb3VudCIpICsKICB0aGVtZV9saWdodCgpICsgICAKdGhlbWUocGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gOSxmYWNlID0gIml0YWxpYyIpLAogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSwgCiAgICAgICAgYXhpcy50ZXh0Lng9ZWxlbWVudF90ZXh0KHNpemU9NiksCiAgICAgICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF90ZXh0KHNpemU9NiksIAogICAgICAgIGF4aXMudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9OCkpCgpgYGAKClNwYXRpYWwgYXV0b2NvcnJlbGF0aW9uIGNhbiBiZSBldmFsdWF0ZWQgYXQgYSBnbG9iYWwgb3IgbG9jYWwgbGV2ZWwuIFdoaWxlIGdsb2JhbCBzcGF0aWFsIGF1dG9jb3JyZWxhdGlvbiBwcm92aWRlcyBhbiBvdmVyYWxsIGFzc2Vzc21lbnQgb2YgdGhlIHNwYXRpYWwgcGF0dGVybiBhY3Jvc3MgdGhlIGVudGlyZSBzdHVkeSBhcmVhLCBhIGxvY2FsIGluZGljYXRvciBvZiBzcGF0aWFsIGFzc29jaWF0aW9uIChMSVNBKSBpZGVudGlmaWVzIGhvdHNwb3RzIGFuZCBjb2xkc3BvdHMgb2YgaGlnaCBvciBsb3cgdmFsdWVzIGZvciBlYWNoIGZlYXR1cmUsIG9mZmVyaW5nIGEgbW9yZSBkZXRhaWxlZCBhbmFseXNpcyBvZiBzcGF0aWFsIHBhdHRlcm5zLiBSZWx5aW5nIHNvbGVseSBvbiBnbG9iYWwgdGVzdHMgbWlnaHQgb3Zlcmxvb2sgbG9jYWxpemVkIHBhdHRlcm5zIHdpdGhpbiB0aGUgZGF0YS4gSW4gb3VyIGNhc2UsIHdlIGFyZSBpbnRlcmVzdGVkIGluIGhvdyBoaWdoIG9yIGxvdyBzYWxlIHByaWNlIGNsdXN0ZXJlZCBpbiBQaGlsYWRlbHBoaWEuIFRvIGRvIHRoYXQsIHdlIGNhbiBjYWxjdWxhdGUgdGhlIEdpIHN0YXRpc3RpYyB1c2luZyBgbG9jYWxfZ19wZXJtKClgIGZ1bmN0aW9uIGluIHRoZSBgc2ZkZXBgIHBhY2thZ2UuIFRoZSBHaSBpcyB0aGUgcmF0aW8gb2YgdGhlIHNwYXRpYWwgbGFnIG9mIGEgZmVhdHVyZSB0byB0aGUgc3VtIG9mIHRoZSBmZWF0dXJl4oCZcyB2YWx1ZXMgZm9yIGl0cyBuZWlnaGJvcnMuIEEgcG9zaXRpdmUgR2kgdmFsdWUgaW5kaWNhdGVzIHRoYXQgYSBmZWF0dXJlIGFuZCBpdHMgbmVpZ2hib3JzIGhhdmUgaGlnaCB2YWx1ZXMsIHdoaWxlIGEgbmVnYXRpdmUgR2kgdmFsdWUgaW5kaWNhdGVzIHRoYXQgdGhleSBoYXZlIGxvdyB2YWx1ZXMuIFRoZSBtYWduaXR1ZGUgb2YgdGhlIEdpIHZhbHVlIGluZGljYXRlcyB0aGUgc3RyZW5ndGggb2YgdGhlIGNsdXN0ZXJpbmcuIE5vdGU6IHdlIHVzZWQgdGhlIGJsb2NrIGdyb3VwcyBpbiBQaGlsYWRlbHBoaWEgYXMgdGhlIHNwYXRpYWwgdW5pdCwgdG8gd2hpY2ggd2Ugam9pbmVkIHRoZSBzYWxlIHByaWNlIGRhdGEgaW4gYW5kIHN1bW1hcml6ZWQgdGhlIG1lYW4gc2FsZSBwcmljZSBmb3IgZWFjaCBibG9jayBncm91cC4gCgpgYGB7ciBwcmVwYXJpbmcgbG9jYWwgRyBzdGF0aXN0aWNzLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCByZXN1bHRzPSdoaWRlJ30KCnRyX2Jsb2NrIDwtIHN0X2pvaW4odHJfcHJvY2Vzc2VkLCBibG9jayAlPiUgc2VsZWN0KEdFT0lEMTApKQoKZ2lfZGF0YSA8LSB0cl9ibG9jayAlPiUgCiAgc3RfZHJvcF9nZW9tZXRyeSgpICU+JSAKICBzZWxlY3QoR0VPSUQxMCwgc2FsZV9wcmljZSkgJT4lIAogIGdyb3VwX2J5KEdFT0lEMTApICU+JSAKICBzdW1tYXJpemUobWVhbl9zcCA9IG1lYW4oc2FsZV9wcmljZSkpICU+JSAKICBuYS5vbWl0KCkgJT4lIAogIGxlZnRfam9pbihibG9jaykgJT4lIAogIHN0X3NmKCkgJT4lIAogIHN0X21ha2VfdmFsaWQoKQpgYGAKCldlIG1heSBzZWUgZnJvbSB0aGUgcGxvdCBiZWxvdyB0aGF0IHRoZXJlIGFyZSBzb21lIHNtYWxsIHNpZ25pZmljYW50IGNsdXN0ZXJzIG9mIHNhbGUgcHJpY2UgaW4gUGhpbGFkZWxwaGlhLiBJbiBwYXJ0aWN1bGFyLCB3ZSBzZWUgaG90c3BvdCwgbWVhbmluZyBjb250aW51b3VzIG51bWJlcnMgb2YgaGlnaCBzYWxlIHByaWNlIGNsdXN0ZXJlZCBpbiBOb3J0aHdlc3QgUGhpbGFkZWxwaGlhIGFuZCBzb21lIGNsdXN0ZXJzIG9mIGNvbGQgc3BvdCwgbWVhbmluZyBjb250aW51b3VzIG51bWJlcnMgb2YgbG93IHNhbGUgcHJpY2UgaW4gTm9ydGggUGhpbGFkZWxwaGlhLiBUaGlzIGFnYWluIHBvaW50IG91dCB0aGUgaW1wb3J0YW5jZSB0byBhY2NvdW50IGZvciBzcGF0aWFsIGZlYXR1cmVzIHdoZW4gcHJlZGljdGluZyBzYWxlIHByaWNlcy4gCgpgYGB7ciBHZXRpZCBPcmQgbG9jYWwgRyBzdGF0aXN0aWNzLCBmaWcuaGVpZ2h0PTYsIGZpZy53aWR0aD04LCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQoKIyBDaGVjayBmb3IgZW1wdHkgbmVpZ2hib3Igc2V0cwojIGNhcmQoKSBjYWxjdWxhdGVzIG51bWJlciBvZiBuZWlnaGJvcnMgZm9yIGVhY2ggcG9seWdvbiBpbiB0aGUgbGlzdAojIHdoaWNoKCkgZmluZHMgcG9seWdvbnMgd2l0aCAwIG5laWdoYm9ycwojIFJlbW92ZSBwb2x5Z29ucyB3aXRoIGVtcHR5IG5laWdoYm9yIHNldHMgZnJvbSB0aGUgZGF0YQpsaXN0X25iIDwtIHBvbHkybmIoZ2lfZGF0YSwgcXVlZW4gPSBUUlVFKQplbXB0eV9uYiA8LSB3aGljaChjYXJkKGxpc3RfbmIpID09IDApCmdpX3N1YnNldCA8LSBnaV9kYXRhWy1lbXB0eV9uYiwgXQoKbmVpZ2hfbmJzIDwtIGdpX3N1YnNldCAlPiUgCiAgbXV0YXRlKAogICAgbmIgPSBzdF9jb250aWd1aXR5KGdlb21ldHJ5KSwgICMgbmVpZ2hib3JzIHNoYXJlIGJvcmRlciAgICAKICAgIHd0ID0gc3Rfd2VpZ2h0cyhuYiksICMgcm93LXN0YW5kYXJkaXplZCB3ZWlnaHRzICAgICAgICAgICAgICAKICAgIG5laWdoX2xhZyA9IHN0X2xhZyhtZWFuX3NwLCBuYiwgd3QpICAjIGNhbGN1bGF0ZSBzcGF0aWFsIGxhZyBvZiBtZWFuIHNhbGUgcHJpY2UKICApCgpnaV9ob3Rfc3BvdHMgPC0gbmVpZ2hfbmJzICU+JSAKICBtdXRhdGUoR2kgPSBsb2NhbF9nX3Blcm0obWVhbl9zcCwgbmIsIHd0LCBuc2ltID0gOTk5KSkgJT4lIAogIHVubmVzdChHaSkgCgpnaV9ob3Rfc3BvdHMgJT4lIAogIGdncGxvdCgpICsgCiAgZ2VvbV9zZihkYXRhID0gZHN0LCBjb2xvcj0iZ3JleSIsICBmaWxsID0gImdyZXkiKSArCiAgZ2VvbV9zZihkYXRhID0gZ2lfaG90X3Nwb3RzLCBhZXMoZmlsbCA9IGdpKSwgY29sb3IgPSBOQSkrCiAgc2NhbGVfZmlsbF9ncmFkaWVudDIobG93ID0gIiMyODNkM2IiLCBoaWdoID0gIiNjNDQ1MzYiLCBuYW1lID0gIkdpIikgKwogIHRoZW1lKAogICAgICAgIGF4aXMudGV4dC54PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpY2tzLng9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGV4dC55PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpY2tzLnk9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDksZmFjZSA9ICJpdGFsaWMiKSwKICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgZmFjZSA9ICJib2xkIiksCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X3JlY3QoY29sb3VyID0gImdyZXkiLCBmaWxsPU5BLCBzaXplPTAuNCkKICAgICAgICApICsKICBsYWJzKHRpdGxlID0gIkdldGlkLU9yZCBTcGF0aWFsIEhvdHNwb3QgZm9yIFNhbGUgUHJpY2UiLAogICAgICAgc3VidGl0bGUgPSAiUmVkIGNsdXN0ZXIgaW5kaWNhdGUgaG90c3BvdHMiLAogICAgICAgY2FwdGlvbiA9ICJOb3RlOiBFcnJvcnMgd2l0aCBibG9jayBncm91cCBkYXRhc2V0IGlzIFxuIGxlYWRpbmcgdG8gbWlzc2luZyB2YWx1ZXMgZm9yIHBhcnRpY3VsYXIgYmxvY2tzIikgCmBgYAoKIyMgTmVpZ2hib3Job29kIEVmZmVjdCBSZWdyZXNzaW9uCgpHaXZlbiB0aGF0IHNhbGUgcHJpY2VzIG1heSBiZSByZWxhdGVkIHRvIHRoZSBnZW9ncmFwaGljYWwgYXR0cmlidXRlcyBvZiB0aGUgaG91c2VzLCB3ZSBhZGRlZCBuZWlnaGJvcmhvb2QgaW5mb3JtYXRpb24gKGluIHRoaXMgY2FzZSB0aGUgcGxhbm5pbmcgZGlzdHJpY3QpIHRvIHRoZSBleGlzdGluZyBiYXNlbGluZSBtb2RlbCBhbmQgYWRkZWQgb3RoZXIgcmVsZXZhbnQgYXR0cmlidXRlcy4gCgpgYGB7ciBicmluZyBpbiBuZWlnaGJvcmhvb2QsIHdhcm5pbmc9RkFMU0V9Cgp0cl9uZWlnaCA8LSBzdF9pbnRlcnNlY3Rpb24odHJfcHJvY2Vzc2VkLCBkc3QgJT4lIGRwbHlyOjpzZWxlY3QoIkRJU1RfTkFNRSIpKQoKYGBgCgoqKkNyaW1lIHJhdGUqKiBpcyByZWxhdGVkIHRvIHRoZSBzYWZldHkgb2YgaG91c2VzIGFuZCByZXNpZGVudHMuIEFyZWFzIHdpdGggbG93ZXIgY3JpbWUgcmF0ZXMgdGVuZCB0byBoYXZlIGhpZ2hlciBob3VzZSBzYWxlIHByaWNlcy4gVGhlcmVmb3JlLCB3ZSBpbnRyb2R1Y2VkIHRoZSBudW1iZXIgb2YgY3JpbWVzIGFyb3VuZCB0aGUgaG91c2UgYXMgYW4gaW5kaWNhdG9yIG9mIHNhZmV0eS4gV2UgdXNlZCBBZ2dyYXZhdGVkIEFzc2F1bHQgYXMgdGhlIG1haW4gbWVhc3VyZSBvZiBjcmltZSBhbmQgY291bnRlZCB0aGUgb2NjdXJyZW5jZXMgd2l0aGluIGEgMjAwLW1ldGVyIHJhZGl1cyBvZiB0aGUgaG91c2UncyBsb2NhdGlvbi4gCgpTaW1pbGFybHksIHdlIGNvbnNpZGVyZWQgKipzY2hvb2xzKiogYXMgYW4gaW1wb3J0YW50IGZhY3RvciBmb3IgZmFtaWxpZXMgd2l0aCBtaW5vciBjaGlsZHJlbiB3aGVuIGNob29zaW5nIGEgaG91c2UuIFByaXZhdGUgc2Nob29scyB0ZW5kIHRvIGhhdmUgaGlnaGVyIHR1aXRpb24gZmVlcywgd2hpY2ggbWF5IGF0dHJhY3QgYWZmbHVlbnQgaG91c2Vob2xkIGFuZCB0aGVyZWJ5IHJhaXNpbmcgbG9jYWwgaG91c2UgcHJpY2VzLiBBcyBzdWNoLCB3ZSBzZWxlY3RlZCBzY2hvb2xzIGFuZCBjb3VudGVkIHRoZSBudW1iZXIgb2YgcHJpdmF0ZSBLLTEyIHNjaG9vbHMgd2l0aGluIGEgMS41LWtpbG9tZXRlciByYWRpdXMgb2YgdGhlIGhvdXNlJ3MgbG9jYXRpb24uIAoKTGlrZXdpc2UsIGNvbnZlbmllbmNlIGZvciAqKnNob3BwaW5nKiogb3IgKipjb21tdXRpbmcqKiBjYW4gYWxzbyBhZmZlY3QgaG91c2UgcHJpY2VzLCByZWZsZWN0aW5nIGJvdGggdGhlIHRyYW5zaXQtb3JpZW50ZWQgZGV2ZWxvcG1lbnQgKFRPRCkgYXJlYSBhbmQgdGhlIGV4dGVudCBvZiBidXN0bGluZyBhcmVhcy4gVGhlcmVmb3JlLCB3ZSBhbHNvIGNvdW50ZWQgYW5kIGluY2x1ZGVkIHRoZSBudW1iZXIgb2YgY29tbWVyY2lhbCBhY3Rpdml0aWVzIG9yIHN1YndheSBzdGF0aW9ucyB3aXRoaW4gdGhlIHZpY2luaXR5IG9mIHRoZSBob3VzZS4gVGhlIHByZXNlbmNlIG9mICoqaG9zcGl0YWxzKiogaGFzIGEgbW9yZSBjb21wbGV4IGltcGFjdCBvbiBob3VzZSBwcmljZXMgZm9yIGVhc3kgYWNjZXNzIHRvIG1lZGljYWwgc2VydmljZSBpcyBjcml0aWNhbCBmb3IgdGhlIGhlYWx0aCBhbmQgc2FmZXR5IG9mIHRoZSBuZWlnaGJvcmhvb2QuIFdlIGNhbGN1bGF0ZWQgdGhlIGRpc3RhbmNlIGZyb20gdGhlIGhvdXNlIHRvIHRoZSBuZWFyZXN0IGhvc3BpdGFsIHRvIGFjY291bnQgZm9yIHRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiB0aGUgZGlzdGFuY2UgYW5kIGhvdXNlIHByaWNlcy4gRmluYWxseSwgY29uc2lkZXJpbmcgdGhlIGVmZmVjdCBvZiByYWNpYWwgc2VncmVnYXRpb24gb24gc2FsZSBwcmljZSwgd2UgY2F0ZWdvcml6ZWQgaG91c2VzIGJhc2VkIG9uIHdoZXRoZXIgb3Igbm90IHRoZXkgYXJlIGxvY2F0ZWQgaW4gYSBjZW5zdXMgdHJhY3Qgd2l0aCBvdmVyIDUwJSBvZiAqKm1pbm9yaXR5IHBvcHVsYXRpb25zKiouIAoKYGBge3IgYnJpbmcgaW4gY3JpbWUgc2Nob29sIGhvc3BpdGFsIHNob3BzIHN0b3BzIGFuZCByYWNlLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFLCBjYWNoZT1UUlVFLCAgcmVzdWx0cz0iaGlkZSJ9CgojIENyaW1lCmFzc2F1bHQgPC0gY3JpbWUgJT4lIAogIGZpbHRlcih0ZXh0X2dlbmVyID09ICJBZ2dyYXZhdGVkIEFzc2F1bHQgRmlyZWFybSIgfCB0ZXh0X2dlbmVyID09ICJBZ2dyYXZhdGVkIEFzc2F1bHQgTm8gRmlyZWFybSIpICU+JSAKICBkcGx5cjo6c2VsZWN0KHBvaW50X3gsIHBvaW50X3kpICU+JQogIG5hLm9taXQoKSAlPiUgCiAgc3RfYXNfc2YoY29vcmRzID0gYygiTG9uZyIsICJMYXQiKSwgY3JzID0gIkVQU0c6MTAyNzI4IikgJT4lCiAgZGlzdGluY3QoKQp0cl9uZWlnaCRjcmltZXMuQnVmZmVyIDwtIHRyX25laWdoICU+JSAKICAgIHN0X2J1ZmZlcig2NjApICU+JSAKICAgIGFnZ3JlZ2F0ZShtdXRhdGUoYXNzYXVsdCwgY291bnRlciA9IDEpLC4sIHN1bSkgJT4lCiAgICBwdWxsKGNvdW50ZXIpIAp0cl9uZWlnaCRjcmltZXMuQnVmZmVyW2lzLm5hKHRyX25laWdoJGNyaW1lcy5CdWZmZXIpXSA8LSAwCiAgCiMgU2Nob29sCnNjaG9vbHMgPC0gc2Nob29scyAlPiUgCiAgZmlsdGVyKEdSQURFX09SRyA9PSAiSy0xMiIgfCBHUkFERV9PUkcgPT0gIjktMTIiKSAlPiUgIAogIGZpbHRlcihUWVBFX1NQRUNJRklDID09ICJQUklWQVRFIikgCnNjaG9vbF9idWZmZXIgPC0KICBzY2hvb2xzICU+JQogIGRwbHlyOjpzZWxlY3QoZ2VvbWV0cnkpICU+JQogIHN0X3RyYW5zZm9ybSgnRVNSSToxMDI3MjgnKSAlPiUKICAgIG5hLm9taXQoKSAKdHJfbmVpZ2gkc2Nob29sX2J1ZmZlciA8LSAKICAgIHRyX25laWdoJGdlb21ldHJ5ICU+JSAKICAgIHN0X2J1ZmZlcig1MjgwKSAlPiUgCiAgICBhZ2dyZWdhdGUobXV0YXRlKHNjaG9vbF9idWZmZXIsIGNvdW50ZXIgPSAxKSwuLCBzdW0pICU+JQogICAgcHVsbChjb3VudGVyKQp0cl9uZWlnaCRzY2hvb2xfYnVmZmVyW2lzLm5hKHRyX25laWdoJHNjaG9vbF9idWZmZXIpXSA8LSAwCgoKIyBIb3NwaXRhbAp0cl9uZWlnaCA8LQogIHRyX25laWdoICU+JSAKICAgIG11dGF0ZShob3NwaXRhbHNfbm4xID0gbm5fZnVuY3Rpb24oc3RfY29vcmRpbmF0ZXModHJfbmVpZ2gpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RfY29vcmRpbmF0ZXMoaG9zcGl0YWwpLCBrID0gMSkpCgojIFNob3BzCnNob3BzIDwtIHN0X2NlbnRyb2lkKHNob3BwaW5nKSAlPiUgZHBseXI6OnNlbGVjdCgiT0JKRUNUSUQiLCAiZ2VvbWV0cnkiKQp0cl9uZWlnaCA8LQogIHRyX25laWdoICU+JSAKICAgIG11dGF0ZShzaG9wc19ubjMgPSBubl9mdW5jdGlvbihzdF9jb29yZGluYXRlcyh0cl9uZWlnaCksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdF9jb29yZGluYXRlcyhzaG9wcyksIGsgPSAzKSkKCiMgVHJhbnNpdCBTdG9wcwpzZXB0YVN0b3BzIDwtIAogIHJiaW5kKAogICAgIGVsICU+JSAKICAgICAgbXV0YXRlKExpbmUgPSAiRWwiKSAlPiUKICAgICAgZHBseXI6OnNlbGVjdChTdGF0aW9uLCBMaW5lKSwKICAgICBCcm9hZF9TdCAlPiUKICAgICAgbXV0YXRlKExpbmUgPSJCcm9hZF9TdCIpICU+JQogICAgICBkcGx5cjo6c2VsZWN0KFN0YXRpb24sIExpbmUpKSAlPiUKICBzdF90cmFuc2Zvcm0oJ0VTUkk6MTAyNzI4JykKam9pbnN0b3BzIDwtIHRyX25laWdoICU+JSAKICBzdF9pbnRlcnNlY3Rpb24oc3RfYnVmZmVyKHNlcHRhU3RvcHMsIDMwMDApKSAlPiUgCiAgc3RfZHJvcF9nZW9tZXRyeSgpICU+JSAKICBncm91cF9ieShwYXJjZWxfbnVtYmVyKSAlPiUgCiAgc3VtbWFyaXplKAogICAgdG90YWxzdG9wcyA9IG4oKSkKdHJfbmVpZ2ggPC0gdHJfbmVpZ2ggJT4lIAogIGxlZnRfam9pbihqb2luc3RvcHMsIGJ5ID0gInBhcmNlbF9udW1iZXIiKSAKdHJfbmVpZ2ggPC0gdHJfbmVpZ2ggJT4lIAogIG11dGF0ZSh0b3RhbHN0b3BzID0gaWZlbHNlKGlzLm5hKHRvdGFsc3RvcHMpLCAwLCB0b3RhbHN0b3BzKSkKCgojIFJhY2UKY2Vuc3VzX2FwaV9rZXkoZGxnSW5wdXQoCiAgIkVudGVyIGEgQ2Vuc3VzIEFQSSBLZXkiLCAjIGFzayBmb3IgYW4gYXBpIGtleQogIFN5cy5nZXRlbnYoIkNFTlNVU19BUElfS0VZIikKKSRyZXMsCm92ZXJ3cml0ZSA9IFRSVUUpCnRyYWN0czIwIDwtIAogIGdldF9hY3MoZ2VvZ3JhcGh5ID0gInRyYWN0IiwgCiAgICAgICAgICB2YXJpYWJsZXMgPSBjKCJCMDIwMDFfMDAxRSIsICMgdG90YWwgcG9wdWxhdGlvbgogICAgICAgICAgICAiQjAyMDAxXzAwMkUiLCAjIHdoaXRlIHBvcHVsYXRpb24KICAgICAgICAgICAgIkIwMjAwMV8wMDNFIiwgIyBibGFjayBwb3B1bGF0aW9uCiAgICAgICAgICAgICJCMDIwMDFfMDA1RSIsICMgYXNpYW4gcG9wdWxhdGlvbgogICAgICAgICAgICAiQjAzMDAyXzAxMkUiKSwgCiAgICAgICAgICB5ZWFyPTIwMjAsIHN0YXRlPTQyLCBjb3VudHk9MTAxLCAKICAgICAgICAgIGdlb21ldHJ5PVRSVUUsIG91dHB1dD0id2lkZSIpICU+JQogIHN0X3RyYW5zZm9ybSgnRVNSSToxMDI3MjgnKSAlPiUgCiAgcmVuYW1lKFRvdGFsUG9wID0gQjAyMDAxXzAwMUUsIAogICAgICAgICBXaGl0ZXMgPSBCMDIwMDFfMDAyRSwKICAgICAgICAgQWZyaWNhbl9BbWVyaWNhbnMgPSBCMDIwMDFfMDAzRSwKICAgICAgICAgQXNpYW5zID0gQjAyMDAxXzAwNUUsCiAgICAgICAgIExhdGlueCA9IEIwMzAwMl8wMTJFKSAlPiUgCiAgbXV0YXRlKHBjdE1pbm9yaXR5ID0gaWZlbHNlKFRvdGFsUG9wID4gMCwgKEFmcmljYW5fQW1lcmljYW5zICsgQXNpYW5zICsgTGF0aW54ICkgLyBUb3RhbFBvcCwgMCksIAogICAgICAgICBtYWpvcml0eSA9IGlmZWxzZShwY3RNaW5vcml0eSA+IDAuNSwgIm1pbm9yaXR5IiwgIm1ham9yaXR5IikpCmpvaW5yYWNlIDwtIHRyX25laWdoICU+JSAKICBzdF9pbnRlcnNlY3Rpb24odHJhY3RzMjAgJT4lIHNlbGVjdChwY3RNaW5vcml0eSkpICU+JSAKICBzdF9kcm9wX2dlb21ldHJ5KCkgJT4lIAogIGdyb3VwX2J5KHBhcmNlbF9udW1iZXIpICU+JSAKICBzdW1tYXJpemUoCiAgICBtZWFuTWlub3IgPSBtZWFuKHBjdE1pbm9yaXR5KSkKdHJfbmVpZ2ggPC0gdHJfbmVpZ2ggJT4lIAogIGxlZnRfam9pbihqb2lucmFjZSwgYnkgPSAicGFyY2VsX251bWJlciIpIAogIApgYGAKCgpgYGB7ciBmaWx0ZXIgb3V0LCBpbmNsdWRlPUZBTFNFfQoKbmVpZ2hib3JlZmZlY3QgPC0gdHJfbmVpZ2ggJT4lIAogIGZpbHRlcih0b1ByZWRpY3QgIT0gIkNIQUxMRU5HRSIpICU+JQogIGZpbHRlcihBZ2UgPCA1MDAgJiBBZ2UgPiAtMSkgJT4lIAogIGZpbHRlcihzYWxlX3ByaWNlIDwgMjAwMDAwMCkgJT4lIAogIGZpbHRlcihudW1Sb29tcyA8IDMwKSAlPiUgCiAgZmlsdGVyKHRvdGFsX2xpdmFibGVfYXJlYSAhPSAwICYgaXMubmEodG90YWxfYXJlYSkgPT0gRkFMU0UpICU+JSAKICBmaWx0ZXIoYXJlYSA8IDUwMDAwKSAKCmBgYAoKCldlIGJ1aWx0IGEgbmV3IHJlZ3Jlc3Npb24gb2YgdGhlIG1vZGVsIHdpdGggdGhlc2UgaW5kaWNhdG9ycyBhbmQgb2J0YWluZWQgdGhlIGZvbGxvd2luZyByZXN1bHRzLiBBbGwgaW5kaWNhdG9ycyBzaG93IHN0YXRpc3RpY2FsbHkgc2lnbmlmaWNhbnQgY29ycmVsYXRpb25zIHdpdGggaG91c2Ugc2FsZSBwcmljZXMgYXQgdGhlIDk1JSBsZXZlbCwgZXhjZXB0IGZvciB0aGUgcHJlc2VuY2Ugb2YgYSBiYXNlbWVudCBhbmQgbGFuZHNjYXBlIHR5cGUgbmVhciB0aGUgaG91c2UuIEFkanVzdGVkIFItc3F1YXJlZCBpcyAwLjY1OSwgaW5kaWNhdGluZyB0aGF0IHRoZSBtb2RlbCBjYW4gZXhwbGFpbiA2Ni43JSBvZiB2YXJpYXRpb25zIGluIHNhbGUgcHJpY2UuIFRoZSBzdGFuZGFyZCBlcnJvcnMgZm9yIHByZWRpY3RpbmcgdGhlIHByZXNlbmNlIG9mIGEgZ2FyYWdlLCBuZWlnaGJvcmhvb2QgYXJlYSwgbGFuZHNjYXBlIHR5cGUsIGFuZCBtaW5vcml0eS1tYWpvcml0eSBjb21tdW5pdHkgYXJlIGdyZWF0ZXIgdGhhbiA1MDAwLCB3aGljaCBtYXkgYWZmZWN0IHRoZSBwcmVjaXNpb24gb2YgdGhlIG1vZGVsLCBidXQgaXQgaXMgc3RpbGwgdGhlIG9wdGltYWwgcmVzdWx0IG9idGFpbmVkIHNvIGZhci4gVGhlIEdlbmVyYWxpemVkIEludmVyc2UgVmFyaWFuY2UgSW5mbGF0aW9uIEZhY3RvciAoR0lWRikgZm9yIGVhY2ggaW5kaWNhdG9yIGlzIGxlc3MgdGhhbiAzLCBpbmRpY2F0aW5nIHRoYXQgdGhlcmUgYXJlIG5vIGlzc3VlcyBvZiBtdWx0aWNvbGxpbmVhcml0eSBpbiB0aGUgbW9kZWwuIAoKYGBge3IgbmVpZ2hib3Job29kIGVmZmVjdCByZWdyZXNzaW9ufQoKcmVnMiA8LSBsbShzYWxlX3ByaWNlIH4gLiwgZGF0YSA9IG5laWdoYm9yZWZmZWN0ICU+JSBzdF9kcm9wX2dlb21ldHJ5KCkgJT4lIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkcGx5cjo6c2VsZWN0KHNhbGVfcHJpY2UsIEFnZSwgbnVtUm9vbXMsIGhhc0Jhc2VtZW50LCBoYXNBQywgcXVhbGl0eSwgYnVpbGRpbmdkaXMsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhhc0ZpcmVwbGFjZSwgaGFzR2FyYWdlLCBzdG9yaWVzLCBsb2dhcmVhLCB2aWV3LCBESVNUX05BTUUsIGNyaW1lcy5CdWZmZXIsIHRvdGFsc3RvcHMsIGhvc3BpdGFsc19ubjEsIHNob3BzX25uMywgc2Nob29sX2J1ZmZlciwgbWVhbk1pbm9yKSkKc3VtbWFyeV9yZWcyIDwtIHN1bW1hcnkocmVnMikKY29lZmZpY2llbnRzX3RhYmxlIDwtIGFzLmRhdGEuZnJhbWUoc3VtbWFyeV9yZWcyJGNvZWZmaWNpZW50cykKCgpjb2VmZmljaWVudHNfdGFibGUkc2lnbmlmaWNhbmNlIDwtIGlmZWxzZShjb2VmZmljaWVudHNfdGFibGUkYFByKD58dHwpYCA8IDAuMDAxLCAnKioqJywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZmVsc2UoY29lZmZpY2llbnRzX3RhYmxlJGBQcig+fHR8KWAgPCAwLjAxLCAnKionLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZmVsc2UoY29lZmZpY2llbnRzX3RhYmxlJGBQcig+fHR8KWAgPCAwLjA1LCAnKicsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZmVsc2UoY29lZmZpY2llbnRzX3RhYmxlJGBQcig+fHR8KWAgPCAwLjEsICcuJywgJycpKSkpCgpjb2VmZmljaWVudHNfdGFibGUkcF92YWx1ZSA8LSBwYXN0ZTAocm91bmQoY29lZmZpY2llbnRzX3RhYmxlJGBQcig+fHR8KWAsIGRpZ2l0cyA9IDMpLCBjb2VmZmljaWVudHNfdGFibGUkc2lnbmlmaWNhbmNlKQoKY29lZmZpY2llbnRzX3RhYmxlICU+JQogIHNlbGVjdCgtc2lnbmlmaWNhbmNlLCAtYFByKD58dHwpYCkgJT4lIAogIGthYmxlKGFsaWduID0gInIiKSAlPiUKICBrYWJsZV9zdHlsaW5nKGJvb3RzdHJhcF9vcHRpb25zID0gYygic3RyaXBlZCIsICJob3ZlciIsICJjb25kZW5zZWQiKSkgJT4lCiAgZm9vdG5vdGUoZ2VuZXJhbF90aXRsZSA9ICJcbiIsIGdlbmVyYWwgPSAiVGFibGUgNSIpCgoKbmV3X3ZhcmlhYmxlX25hbWVzIDwtIGMoCiAgIkFnZSIsCiAgIk51bWJlciBvZiBSb29tcyIsCiAgIkJhc2VtZW50IiwKICAiQWlyIENvbmRpdGlvbmVyIiwKICAiUXVhbGl0eSBHcmFkZSIsCiAgIkJ1aWxkaW5nIFR5cGUiLAogICJGaXJlcGxhY2UiLAogICJHYXJhZ2UiLAogICJOdW1iZXIgU3RvcmllcyIsCiAgIkFyZWEiLAogICJUeXBlIG9mIFZpZXciLAogICJEaXN0cmljdCBOYW1lIiwKICAiQ3JpbWVzIiwKICAiVHJhbnNpdCBTdG9wcyIsCiAgIkhvc3BpdGFscyIsCiAgIlNob3BzIiwKICAiU2Nob29scyIsCiAgIk1pbm9yIFJhY2UiCikKCgoKCm1vZGVsX3N1bW1hcnkgPC0gZGF0YS5mcmFtZSgKICBTdGF0aXN0aWMgPSBjKCJNdWx0aXBsZSBSLXNxdWFyZWQiLCAiQWRqdXN0ZWQgUi1zcXVhcmVkIiwgIkYtc3RhdGlzdGljIiksCiAgVmFsdWUgPSBjKAogICAgc3VtbWFyeV9yZWcxJHIuc3F1YXJlZCwKICAgIHN1bW1hcnlfcmVnMSRhZGouci5zcXVhcmVkLAogICAgc3VtbWFyeV9yZWcxJGZzdGF0aXN0aWNbMV0KICApCikKCm1vZGVsX3N1bW1hcnkgJT4lCiAga2FibGUoKSAlPiUKICBrYWJsZV9zdHlsaW5nKGJvb3RzdHJhcF9vcHRpb25zID0gYygic3RyaXBlZCIsICJob3ZlciIsICJjb25kZW5zZWQiKSklPiUKICBmb290bm90ZShnZW5lcmFsX3RpdGxlID0gIlxuIiwgZ2VuZXJhbCA9ICJUYWJsZSA2IikKCnZpZl9yZWcyIDwtIHZpZihyZWcyKQp2aWZfZGYgPC0gYXMuZGF0YS5mcmFtZSh2aWZfcmVnMikKdmlmX2RmJFZhcmlhYmxlIDwtIG5ld192YXJpYWJsZV9uYW1lcwp2aWZfZGYgPC0gdmlmX2RmWywgYygiVmFyaWFibGUiLCBuYW1lcyh2aWZfZGYpWyFuYW1lcyh2aWZfZGYpICVpbiUgIlZhcmlhYmxlIl0pXQoKdmlmX2RmICU+JQogIGthYmxlKHJvdy5uYW1lcyA9IEZBTFNFKSAlPiUKICBrYWJsZV9zdHlsaW5nKGJvb3RzdHJhcF9vcHRpb25zID0gYygic3RyaXBlZCIsICJob3ZlciIsICJjb25kZW5zZWQiKSkgJT4lCiAgZm9vdG5vdGUoZ2VuZXJhbF90aXRsZSA9ICJcbiIsIGdlbmVyYWwgPSAiVGFibGUgNyIpCmBgYAoKClRoZSByZXNpZHVhbCBwbG90IGJhc2VkIG9uIG5laWdoYm9yaG9vZCBlZmZlY3QgcmVncmVzc2lvbiBtb2RlbCBpcyBtdWNoIGltcHJvdmVkIGZyb20gdGhlIHByZXZpb3VzIG1vZGVsLCB3aXRoIGRvdHMgZGlzdHJpYnV0ZWQgbXVjaCBtb3JlIGV2ZW5seSBhbG9uZyB0aGUgYmVzdCBmaXQgbGluZSwgZGVzcGl0ZSB0aGF0IHRoZXJlIGFyZSBzdGlsbCBzaG93aW5nIHNvbWUgdHJlbmRzIG9mIHVuZGVycHJlZGljdGlvbi4gCgpgYGB7ciByZXNpZHVhbCBwbG90IG5laWdoYm9yaG9vZH0KCnJlc2lkdWFsc19uZWlnaCA8LSBkYXRhLmZyYW1lKFJlc2lkdWFscyA9IHJlc2lkKHJlZzIpLCBGaXR0ZWQgPSBmaXR0ZWQocmVnMikpCmdncGxvdChyZXNpZHVhbHNfbmVpZ2gsIGFlcyh4ID0gRml0dGVkLCB5ID0gUmVzaWR1YWxzKSkgKwogIGdlb21fcG9pbnQoc2l6ZSA9IDAuNCwgY29sb3IgPSAiIzI4M2QzYiIpICsKICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLCBsaW5ldHlwZSA9ICJkYXNoZWQiLCBjb2xvciA9ICIjYzQ0NTM2IikgKwogIGxhYnModGl0bGUgPSAiUmVzaWR1YWwgUGxvdCBmb3IgTmVpZ2hib3Job29kIEVmZmVjdCBSZWdyZXNzaW9uIiwKICAgICAgIHN1YnRpdGxlID0gIkVhY2ggZG90IHJlcHJlc2VudCBvbmUgcHJvcGVydHkgIiwKICAgICAgIGNhcHRpb24gPSAiRGF0YTogUGhpbGFkZWxwaGlhIFByb3BlcnRpZXMgYW5kIEN1cnJlbnQgQXNzZXNzbWVudHMgXG4gT3BlbiBEYXRhIFBIbCBNYXBzIiwgCiAgICAgICB4ID0gIkZpdHRlZCBWYWx1ZXMiLAogICAgICAgeSA9ICJSZXNpZHVhbHMiKSArCiAgdGhlbWVfbGlnaHQoKSArICAgCiAgdGhlbWUocGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gOSxmYWNlID0gIml0YWxpYyIpLAogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSwgCiAgICAgICAgYXhpcy50ZXh0Lng9ZWxlbWVudF90ZXh0KHNpemU9NiksCiAgICAgICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF90ZXh0KHNpemU9NiksIAogICAgICAgIGF4aXMudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9OCkpCgoKYGBgCgoKIyMgUmVncmVzc2lvbiBDb21wYXJpc29uIAoKQ29tcGFyaW5nIGFjcm9zcyB0d28gcmVncmVzc2lvbiBtb2RlbHMsIHdlIHNlZSB0aGF0IHdoaWxlIGJhc2VsaW5lIHJlZ3Jlc3Npb24gb25seSBhY2NvdW50IDQ0JSBvZiB0aGUgdmFyaWFuY2UgaW4gc2FsZSBwcmljZSwgdGhpcyBmaWd1cmUgcmlzZSB0byA2NiUgaW4gbmVpZ2hib3Job29kIGVmZmVjdCByZWdyZXNzaW9uLCBzdWdnZXN0aW5nIGEgc2lnbmlmaWNhbnQgaW1wcm92ZW1lbnQuIAoKYGBge3IgY29tcGFyZSBiYXNlbGluZSBhbmQgbmVpZ2hib3Job29kLCBpbmNsdWRlPUZBTFNFfQpleHBvcnRfc3VtbXMocmVnMSwgcmVnMikKYGBgCnwgICAgICAgICAgICAgICAgICAgfCAgICAgICAgICAgICAgIE1vZGVsIDEgICAgfCAgICAgIE1vZGVsIDIgICAgICB8Cnw6LS0tLS0tLS0tLS0tLS0tLS0gfCA6LS0tLS0tLS0tLS0tLS0tLS0tLS0tLSAgfCA6LS0tLS0tLS0tLS0tLS0tLSB8CnwgICAgQWdlICAgICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAtMTY4LjQ4ICoqKiB8ICAgICAgLTQ5My4yMCAqKiogIHwKfCAgIE51bWJlciBvZiBSb29tcyAgICAgICAgfCAgICAgICAgICAgICAgICAgIDQyNTMuMDMgKioqIHwgICAgIDExNTk2LjA1ICoqKiAgfAp8ICAgQmFzZW1lbnQtWWVzICAgIHwgICAgICAgICAgICAgICAgIDExMjYxLjY5ICoqKiAgfCAgICAzOTA2LjgxICogICAgfAogICAgICAgICAgICB8ICAgQUMtWWVzICAgICAgICAgIHwgICAgICAgICAgICAgICAgMTMzODU3LjAyICoqKiAgfCAgIDU2MDQyLjc1ICoqKiAgfAogICAgICAgICAgICB8ICAgUXVhbGl0eSBHcmFkZS1Hb29kICAgICB8ICAgICAgICAgICAgICAxMjc5NDUuMzMgKioqIHwgICAgNTI1NTQuMDggKioqICB8CiAgICAgICAgICAgIHwgICBUeXBlIG9mIEJ1aWxkaW5nLVJvdyAgfCAgICAgICAgICAgICAgICAtOTM0NS4wNyAqICAgfCAgICA1NTc4My4wNiAqKiogfAogICAgICAgICAgICB8ICAgVHlwZSBvZiBCdWlsZGluZy1UV0lOIHwgICAgICAgICAgICAgICAtNTEzMjguODcgKioqIHwgICAgMTUxNzMuNTUgKioqICB8CiAgICAgICAgICAgIHwgICBGaXJlcGxhY2UtWWVzICAgfCAgICAgICAgICAgICAgIDEzNTM1Mi45NSAqKiogfCAgICA3ODk5MS42OCAqKiogIHwKICAgICAgICAgICAgfCAgIEdhcmFnZS1ZZXMgICAgIHwgICAgICAgICAgICAgICAtMzUyMjIuOTMgKioqICB8ICAtMzI5MDQuNDUgKioqICB8CiAgICAgICAgICAgIHwgICBOdW1iZXIgU3Rvcmllcy1NdWx0aXBsZSB8ICAgICAgICAgICAgICAgMTY3OTA3Ljk2ICoqKiB8ICAgIDcxMDY4LjIzICoqKiAgfAogICAgICAgICAgICB8ICAgTnVtYmVyIFN0b3JpZXMtU2luZ2xlICAgfCAgICAgICAgICAgICAgIDExMjEzLjMzICoqKiB8ICAgLTE0NjY4LjU0ICoqKiAgfAogICAgICAgICAgICB8ICAgQXJlYSAgICAgICAgIHwgICAgICAgICAgICAgICAgOTc1MDkuMjAgKioqICB8ICAxNTE1NjEuNzQgKioqICAgICAgICAgICB8CiAgICAgICAgICAgIHwgICBUeXBlIG9mIFZpZXctVHlwaWNhbCAgICAgfCAgICAgICAgICAgICAgIC0zMTYyMC4yMSAqKiogIHwgIC0yMzgwOS4yOSAqKiogIHwKICAgICAgICAgICAgfCAgIFR5cGUgb2YgVmlldy1VcmJhbiAgICAgICB8ICAgICAgICAgICAgICAgLTQ3MzMwLjcwICoqKiAgfCAgLTIxMDE5LjEyICoqICAgfAogICAgICAgICAgICB8ICAgRGlzdHJpY3QtQ2VudHJhbCBOb3J0aGVhc3QgIHwgICAgICAgICAgICAgICAgfCAgICAtMjU4MzM5LjY3ICoqKiAgIHwKICAgICAgICAgICAgfCAgIERpc3RyaWN0LUxvd2VyIEZhciBOb3J0aGVhc3R8ICAgICAgICAgICAgICAgIHwgICAgLTI5OTQ4Ny40MSAqKiogICAgfAogICAgICAgICAgICB8ICAgRGlzdHJpY3QtTG93ZXIgTm9ydGggICAgICAgICB8ICAgICAgICAgICAgICAgfCAgICAtMTkxOTc1LjAxICoqKiAgfAogICAgICAgICAgICB8ICAgRGlzdHJpY3QtTG93ZXIgTm9ydGhlYXN0ICAgIHwgICAgICAgICAgICAgICAgfCAgICAtMjMwNzkzLjI4ICoqKiAgfAogICAgICAgICAgICB8ICAgRGlzdHJpY3QtTG93ZXIgTm9ydGh3ZXN0ICAgIHwgICAgICAgICAgICAgICAgfCAgICAtMjcwNDk4LjcxICoqKiAgIHwKICAgICAgICAgICAgfCAgIERpc3RyaWN0LUxvd2VyIFNvdXRoICAgICAgIHwgICAgICAgICAgICAgICAgIHwgICAgLTE1MDk2MS43NyAqKiogIHwKICAgICAgICAgICAgfCAgIERpc3RyaWN0LUxvd2VyIFNvdXRod2VzdCAgIHwgICAgICAgICAgICAgICAgIHwgICAgLTE4MjIwMC4zNCAqKiogICB8CiAgICAgICAgICAgIHwgICBEaXN0cmljdC1Ob3J0aCAgICAgICAgICAgIHwgICAgICAgICAgICAgICAgICB8ICAtMjI3MTQ4LjUxICoqKiAgICB8CiAgICAgICAgICAgIHwgICBEaXN0cmljdC1Ob3J0aCBEZWxhd2FyZSAgIHwgICAgICAgICAgICAgICAgICB8ICAgIC0yNTcwMzAuNTYgKioqICB8CiAgICAgICAgICAgIHwgICBEaXN0cmljdC1SaXZlciBXYXJkcyAgICAgIHwgICAgICAgICAgICAgICAgICB8ICAgIC0yMjc5MzkuMjkgKioqICB8CiAgICAgICAgICAgIHwgICBEaXN0cmljdC1Tb3V0aCAgICAgICAgICAgIHwgICAgICAgICAgICAgICAgICB8ICAgIC0xNTgwODMuMTcgKioqICB8CiAgICAgICAgICAgIHwgICBEaXN0cmljdC1Vbml2ZXJzaXR5U291dGh3ZXN0ICB8ICAgICAgICAgICAgICAgfCAgIC0xNTA1MDUuNjkgKioqICAgfCAgICAgICAgICAgICAgCiAgICAgICAgICAgIHwgICBEaXN0cmljdC1VcHBlciBGYXIgTm9ydGhlYXN0ICB8ICAgICAgICAgICAgICAgfCAgIC0yMzc0MDYuMTYgKioqICB8CiAgICAgICAgICAgIHwgICBEaXN0cmljdC1VcHBlciBOb3J0aCAgICAgICAgIHwgICAgICAgICAgICAgICB8ICAgIC0yMTE0OTQuMDggKioqICB8CiAgICAgICAgICAgIHwgICBEaXN0cmljdC1VcHBlciBOb3J0aHdlc3QgICAgIHwgICAgICAgICAgICAgICB8ICAgIC0xNzQ5ODMuNjcgKioqICB8CiAgICAgICAgICAgIHwgICBEaXN0cmljdC1XZXN0ICAgICAgICAgICAgICAgIHwgICAgICAgICAgICAgICB8ICAgIC0xOTcxNTEuNDQgKioqICB8CiAgICAgICAgICAgIHwgICBEaXN0cmljdC1XZXN0IFBhcmsgICAgICAgICAgIHwgICAgICAgICAgICAgIHwgICAgIC0yMTcxMTcuODIgKioqICB8CiAgICAgICAgICAgIHwgICBDcmltZXMgICAgICAgICAgICAgICAgICAgICAgIHwgICAgICAgICAgIHwgICAtMzA1My4zMiAqKiogICAgICB8CiAgICAgICAgICAgIHwgICBUcmFuc2l0IFN0b3BzICAgICAgICAgICAgICAgIHwgICAgICAgICAgICAgIHwgICAgICAgICAgIDc1MjAuNjggKioqICB8CiAgICAgICAgICAgIHwgICBIb3NwaXRhbHMgICAgICAgICAgICAgICAgICAgIHwgICAgICAgICAgICAgIHwgICAgICAtMS43NCAqKiogIHwKICAgICAgICAgICAgfCAgIFNob3BzICAgICAgICAgICAgICAgICAgICAgICAgfCAgICAgICAgICAgICAgfCAgICAgLTE1LjgxICoqKiAgfAogICAgICAgICAgICB8ICAgU2Nob29scyAgICAgICAgICAgICAgICAgICAgICB8ICAgICAgICAgICAgIHwgICA5NTQ4LjA2ICoqKiAgICB8CiAgICAgICAgICAgIHwgICBNaW5vciAgICAgICAgICAgICAgICAgICAgICAgfCAgICAgICAgICAgICAgfCAgLTE5NTEwMS4wMiAqKiogIHwKICAgICAgICAgICAgfCAgIE4gICAgICAgICAgICAgICAgICAgICAgICAgICB8ICAgICAyMzU4OCAgIHwgICAgICAgICAyMzU4NyAgICAgfCAgICAKICAgICAgICAgICAgfCAgIFIyICAgICAgICAgICAgICAgICAgICAgICAgICB8ICAgICAgICAgMC40NCB8ICAgICAgICAgICAgIDAuNjYgIHwgICAgClRhYmxlIDggKioqIHAgPCAwLjAwMTsgKiogcCA8IDAuMDE7ICogcCA8IDAuMDUKCiMgVmFyaWFibGVzIFN1bW1hcnkKCkxlYXJuaW5nIGhvdyBhY2NvdW50aW5nIGZvciBuZWlnaGJvcmhvb2QgZWZmZWN0IGFzIHdlbGwgYXMgcHJveGltaXR5IHRvIHB1YmxpYyBzZXJ2aWNlcyBhbmQgY3JpbWUgd291bGQgc2lnbmlmaWNhbnRseSBpbXBhY3Qgc2FsZSBwcmljZSwgd2Ugd2lsbCBiZSB1c2luZyBvdXIgc2Vjb25kIHJlZ3Jlc3Npb24gbW9kZWwgZm9yIHNhbGUgcHJpY2UgcHJlZGljdGlvbi4gQmVmb3JlIGRvaW5nIHRoYXQsIHdlIHdvdWxkIGxpa2UgdG8gcHJlc2VudCBhIGZ1bGwgZGV0YWlsZWQgc3VtbWFyeSBvZiBhbGwgb2YgdGhlIHZhcmlhYmxlcyBpbiBvdXIgbW9kZWwgdG8gZW5zdXJlIHRoZWlyIGxlZ2liaWxpdHkuIFdlIGhhdmUgb3JnYW5pemVkIHRoaXMgc2VjdGlvbiBhcyBmb2xsb3dzLiBGaXJzdCwgd2UgcHJlc2VudCBhIG92ZXJhbGwgc3VtbWFyeSB0YWJsZSBvZiBhbGwgb2YgdGhlIHZhcmlhYmxlcywgY2F0ZWdvcml6ZWQgYnkgZGF0YSB0eXBlIGFuZCBlZmZlY3Qgb24gaG91c2luZyBwcmljZS4gVGhlbiwgd2Ugc3BsaXQgdGhlIGRhdGEgaW50byBudW1lcmljYWwgYW5kIGNhdGVnb3JpY2FsLiBGb3IgdGhlIG51bWVyaWNhbCB2YXJpYWJsZXMsIHdlIHBlcmZvcm1lZCBjb3IgdGVzdCB0byB0ZXN0IHRoZWlyIHNpZ25pZmljYW5jZSB3aGVyZWFzIGZvciBjYXRlZ29yaWNhbCB2YXJpYWJsZXMsIHdlIHBlcmZvcm1lZCBBTk9WQSB0ZXN0LiBUaGlzIGlzIGZvbGxvd2VkIGJ5IHNvbWUgc2VsZWN0ZWQgdmlzdWFsaXphdGlvbnMgb2Ygb3VyIHByZWRpY3RvcnMuCgp8IFZhcmlhYmxlIE5hbWUgICAgICAgICAgICAgICAgfCBUeXBlICAgICAgICB8IAp8IDotLS0tLS0tLS0tOiB8IDotLS0tLS0tLS0tOiAgfCAgCnwgKipJbnRlcm5hbCBDaGFyYWN0ZXJpc3RpY3MqKiB8ICAgICAgICAgICAgIHwKfCBBZ2UgICAgICAgICAgICAgICAgICAgICAgICAgIHwgTnVtZXJpYyAgICAgfAp8IE51bWJlciBvZiBSb29tcyAgICAgICAgICAgICAgfCBOdW1lcmljICAgICB8CnwgQmFzZW1lbnQgKFkvTikgICAgICAgICAgICAgICB8IENhdGVnb3JpY2FsIHwKfCBBaXIgQ29uZGl0aW9uZXIgKFkvTikgICAgICAgIHwgQ2F0ZWdvcmljYWwgfAp8IFF1YWxpdHkgR3JhZGUgICAgICAgICAgICAgICAgfCBDYXRlZ29yaWNhbCB8CnwgQnVpbGRpbmcgVHlwZSAgICAgICAgICAgICAgICB8IENhdGVnb3JpY2FsIHwKfCBGaXJlcGxhY2UgKFkvTikgICAgICAgICAgICAgIHwgQ2F0ZWdvcmljYWwgfAp8IEdhcmFnZSAoWS9OKSAgICAgICAgICAgICAgICAgfCBDYXRlZ29yaWNhbCB8CnwgU3RvcmllcyBUeXBlICAgICAgICAgICAgICAgICB8IENhdGVnb3JpY2FsIHwKfCBBcmVhICAgICAgICAgICAgICAgICAgICAgICAgIHwgTnVtZXJpYyAgICAgfAp8IFR5cGUgb2YgVmlldyAgICAgICAgICAgICAgICAgfCBDYXRlZ29yaWNhbCB8CnwgKipQdWJsaWMgU2VydmljZSoqICAgICAgICAgICB8ICAgICAgICAgICAgIHwKfCBTdXJyb3VuZGluZyBDcmltZXMgICAgICAgICAgIHwgTnVtZXJpYyAgICAgfAp8IE5lYXJieSBUcmFuc2l0IFN0b3BzICAgICAgICAgfCBOdW1lcmljICAgICB8CnwgRGlzdGFuY2UgdG8gSG9zcGl0YWxzICAgICAgICB8IE51bWVyaWMgICAgIHwKfCBEaXN0YW5jZSB0byBTaG9wcyAgICAgICAgICAgIHwgTnVtZXJpYyAgICAgfAp8IE5lYXJieSBQcml2YXRlIEhpZ2ggU2Nob29sICAgfCBOdW1lcmljICAgICB8CnwgKipTcGF0aWFsIENoYXJhY3RlcmlzdGljcyoqICB8ICAgICAgICAgICAgIHwKfCBQbGFubmluZyBEaXN0cmljdCAgICAgICAgICAgIHwgQ2F0ZWdvcmljYWwgfAp8IE1pbm9yaXR5IE5laWdoYm9yaG9vZCAgICAgICAgfCBOdW1lcmljICAgICB8ClRhYmxlIDkKCiMjIE51bWVyaWMgVmFyaWFibGVzCgpUaGUgYXZlcmFnZSBzYWxlIHByaWNlIGlzIGFwcHJveGltYXRlbHkgXCQyNjUsNjY0LCB3aXRoIGEgcmVsYXRpdmVseSBoaWdoIHN0YW5kYXJkIGRldmlhdGlvbiBvZiBcJDIwMSwwNjksIGluZGljYXRpbmcgYSBzaWduaWZpY2FudCB2YXJpYXRpb24gaW4gc2FsZSBwcmljZXMgaW4gdGhlIGRhdGEuIFRoZSBsb3dlc3QgcHJpY2UgaXMgXCQxMCwxMDAsIGFuZCB0aGUgaGlnaGVzdCBwcmljZSBpcyBjbG9zZSB0byBcJDIsMDAwLDAwMC4gVGhlIGF2ZXJhZ2UgYWdlIG9mIGhvdXNlcyBpcyA4NS43IHllYXJzLCB3aXRoIGEgcmVsYXRpdmVseSBzY2F0dGVyZWQgZGlzdHJpYnV0aW9uLiBUaGUgbWF4aW11bSBhZ2UgaXMgMjczIHllYXJzLCBhbmQgdGhlIG1vc3QgcmVjZW50bHkgYnVpbHQgaG91c2UgaXMgbGVzcyB0aGFuIG9uZSB5ZWFyIG9sZC4gVGhlIGF2ZXJhZ2UgbnVtYmVyIG9mIHJvb21zIGlzIGFwcHJveGltYXRlbHkgMy42Mywgd2l0aCBhIHN0YW5kYXJkIGRldmlhdGlvbiBvZiAxLjcxLCBpbmRpY2F0aW5nIHRoYXQgdGhlIHZhcmlhdGlvbiBpbiByb29tIGNvdW50cyBpcyBub3Qgc2lnbmlmaWNhbnQsIHdpdGggYSBtYXhpbXVtIG9mIDE0IHJvb21zIHBlciBob3VzZS4gCgpUaGUgYXZlcmFnZSBudW1iZXIgb2YgY3JpbWUgaW5jaWRlbnRzIHdpdGhpbiBhIDIwMC1tZXRlciByYWRpdXMgb2YgdGhlIGhvdXNlIGlzIDMuODgsIHdpdGggYSBzdGFuZGFyZCBkZXZpYXRpb24gb2YgNC4yMSwgaW5kaWNhdGluZyBhIHNpZ25pZmljYW50IHZhcmlhdGlvbiBpbiBjcmltZSBjb3VudHMuIFRoZSBhdmVyYWdlIG51bWJlciBvZiBzdWJ3YXkgc3RhdGlvbnMgbmVhciBob3VzZXMgaXMgMC41ODMsIHdpdGggYSBzdGFuZGFyZCBkZXZpYXRpb24gb2YgYXBwcm94aW1hdGVseSAxLCBpbmRpY2F0aW5nIGEgcmVsYXRpdmVseSBzbWFsbCB2YXJpYXRpb24gaW4gdGhlIG51bWJlciBvZiBzdGF0aW9ucy4gVGhlIG1heGltdW0gbnVtYmVyIG9mIHN0YXRpb25zIHdpdGhpbiB0aGUgcmFkaXVzIGlzIDguIFRoZSBhdmVyYWdlIGRpc3RhbmNlIHRvIHRoZSBuZWFyZXN0IGhvc3BpdGFsIGlzIDEuNiBraWxvbWV0ZXJzLCB3aXRoIGEgbGFyZ2UgdmFyaWF0aW9uIGFzIGluZGljYXRlZCBieSB0aGUgc3RhbmRhcmQgZGV2aWF0aW9uLiBIb3VzZXMgYXJlIGFzIGNsb3NlIGFzIGxlc3MgdGhhbiAzMCBtZXRlcnMgYW5kIGFzIGZhciBhcyBvdmVyIDgga2lsb21ldGVycyBmcm9tIHRoZSBuZWFyZXN0IGhvc3BpdGFsLiBUaGUgYXZlcmFnZSBkaXN0YW5jZSB0byB0aGUgbmVhcmVzdCBjb21tZXJjaWFsIGFjdGl2aXR5IGlzIGFwcHJveGltYXRlbHkgNzAwIG1ldGVycywgd2l0aCBzaWduaWZpY2FudCB2YXJpYXRpb24sIHJhbmdpbmcgZnJvbSBhcyBjbG9zZSBhcyAyMDAgbWV0ZXJzIHRvIGFzIGZhciBhcyBvdmVyIDIuNiBraWxvbWV0ZXJzLiBUaGUgYXZlcmFnZSBudW1iZXIgb2Ygc2Nob29scyB3aXRoaW4gYSAxLjUta2lsb21ldGVyIHJhZGl1cyBpcyAwLjU0Mywgd2l0aCBhIHNtYWxsIHN0YW5kYXJkIGRldmlhdGlvbiwgaW5kaWNhdGluZyByZWxhdGl2ZWx5IGxpdHRsZSB2YXJpYXRpb24gaW4gc2Nob29sIGNvdW50cywgd2l0aCBhIG1heGltdW0gb2YgMiBzY2hvb2xzIHdpdGhpbiB0aGUgcmFkaXVzLgoKYGBge3IgbnVtZXJpYyB0YWJsZSwgaW5jbHVkZT1GQUxTRX0KCm5laWdoYm9yZWZmZWN0X25vbmdlb20gPC0gbmVpZ2hib3JlZmZlY3QgJT4lIHN0X2Ryb3BfZ2VvbWV0cnkoKQpuZWlnaGJvcmVmZmVjdF9ub25nZW9tICA8LSBuZWlnaGJvcmVmZmVjdF9ub25nZW9tICAlPiUKICBkcGx5cjo6c2VsZWN0KHNhbGVfcHJpY2UsIEFnZSwgbnVtUm9vbXMsIGhhc0Jhc2VtZW50LCBoYXNBQywgcXVhbGl0eSwgYnVpbGRpbmdkaXMsIGhhc0ZpcmVwbGFjZSwgaGFzR2FyYWdlLCBzdG9yaWVzLCBsb2dhcmVhLCB2aWV3LCBESVNUX05BTUUsIGNyaW1lcy5CdWZmZXIsIHRvdGFsc3RvcHMsIGhvc3BpdGFsc19ubjEsIHNob3BzX25uMywgc2Nob29sX2J1ZmZlciwgbWVhbk1pbm9yKSAlPiUgCiAgICBkcGx5cjo6c2VsZWN0X2lmKGlzLm51bWVyaWMpCgpzdGFyZ2F6ZXIobmVpZ2hib3JlZmZlY3Rfbm9uZ2VvbSwgdHlwZSA9ICd0ZXh0JywgdGl0bGU9ICJUYWJsZSAxOiBTdW1tYXJ5IFN0YXRpc3RpY3MiKQoKYGBgCgp8IFN0YXRpc3RpYyB8IE4gICAgICAgICB8IE1lYW4gICAgICB8IFN0LkRldi4gICB8IE1pbiAgICAgICB8IE1heCAgICAgICB8Cnw6LS0tLS0tLS0tLXw6LS0tLS0tLS0tLXw6LS0tLS0tLS0tLXw6LS0tLS0tLS0tLXw6LS0tLS0tLS0tLXw6LS0tLS0tLS0tLXwKfCBTYWxlIFByaWNlfCAyMyw1ODcgICAgfCAyNjUsNjY0ICAgfCAyMDEsMDY5ICAgfCAxMCwxMDAgICAgfCAxLDk5MCwwMDAgfAp8IEFnZSAgICAgICB8IDIzLDU4NyAgICB8IDg1LjcwMCAgICB8IDI5LjEwMCAgICB8IDAgICAgICAgICB8IDI3MyAgIHwKfCBSb29tcyAgICAgfCAyMyw1ODcgICAgfCAzLjYzMCAgICAgfCAxLjcxMCAgICAgfCAwICAgICAgICAgfCAxNCAgICAgIHwKfCBBcmVhICAgICAgfCAyMyw1ODcgICAgfCA3LjM4MCAgICAgfCAwLjU1NyAgICAgfCA1LjIzMCAgICAgfCAxMC44MDAgICAgIHwKfCBDcmltZXMgICAgfCAyMyw1ODcgICAgfCAzLjg4MCAgICAgfCA0LjIxMCAgICAgfCAwICAgICAgICAgfCA0MiAgICB8CnwgVHJhbnNpdCAgIHwgMjMsNTg3ICAgIHwgMC41ODMgICAgIHwgMS4wNDAgICAgIHwgMCAgICAgICAgIHwgOCAgICAgICB8CnwgSG9zcGl0YWwgIHwgMjMsNTg3ICAgIHwgNSwyOTguMDAwIHwgMyw0OTUuMDAwIHwgNzkuNDAwICAgIHwgMjYsMDY5LjAwMCAgICB8CnwgU2hvcHMgICAgIHwgMjMsNTg3ICAgIHwgMiwyMjcuMDAwIHwgODA3LjAwMCAgIHwgNzQ3LjAwMCAgIHwgOCw3OTAuMDAwICAgICAgfAp8IFNjaG9vbCAgICB8IDIzLDU4NyAgICB8IDAuNTQzICAgICB8IDAuNjYwICAgICB8IDAgICAgICAgICB8MiAgICAgfAp8IE1pbm9yaXR5ICB8IDIzLDU4NyAgICB8IDAuNjAyICAgICB8IDAuMzE5ICAgICB8IDAuMDAwICAgICB8MS4xMDAgICAgICAgfApUYWJsZSAxMAoKVGhlIGZvbGxvd2luZyBjaGFydCBzaG93cyB0aGUgY29ycmVsYXRpb25zIGJldHdlZW4gdGhlIHZhcmlhYmxlcy4gVGhlcmUgaXMgYSBzdHJvbmcgcG9zaXRpdmUgY29ycmVsYXRpb24gYmV0d2VlbiBuZWFyYnkgY3JpbWUgaW5jaWRlbnRzIGFuZCBiZWluZyBhIG1pbm9yaXR5IGNvbW11bml0eS4gVGhpcyBpbXBhY3QgaXMgcmVmbGVjdGVkIGluIHRoZSBuZWdhdGl2ZSBjb3JyZWxhdGlvbnMgd2l0aCBob3VzZSBwcmljZXMuIEluIGFkZGl0aW9uIHRvIGhvdXNlIGFnZSwgdGhlIGRpc3RhbmNlIHRvIHRoZSBuZWFyZXN0IGhvc3BpdGFsIGFsc28gc2hvd3MgYSBuZWdhdGl2ZSBjb3JyZWxhdGlvbiB3aXRoIGhvdXNlIHByaWNlcywgY29uZmlybWluZyB0aGF0IGEgbm9pc3kgZW52aXJvbm1lbnQgbmVhciBob3NwaXRhbHMgY2FuIGluZGVlZCBuZWdhdGl2ZWx5IGFmZmVjdCB0aGUgbGl2aW5nIGV4cGVyaWVuY2UuIEZ1cnRoZXJtb3JlLCBob3VzZSBhcmVhLCB0aGUgbnVtYmVyIG9mIHN1YndheSBzdGF0aW9ucyBuZWFyYnksIGFuZCB0aGUgbnVtYmVyIG9mIHByaXZhdGUgc2Nob29scyB3aXRoaW4gYSAxLjUta2lsb21ldGVyIHJhZGl1cyBhbGwgc2hvdyBwb3NpdGl2ZSBjb3JyZWxhdGlvbnMgd2l0aCBob3VzZSBwcmljZXMsIGluZGljYXRpbmcgdGhhdCBob3VzZXMgd2l0aCBsYXJnZXIgYXJlYXMsIG1vcmUgbmVhcmJ5IHN1YndheSBzdGF0aW9ucywgYW5kIG1vcmUgbmVhcmJ5IHByaXZhdGUgc2Nob29scyBoYXZlIGFuIGFkdmFudGFnZSBpbiBwcmljaW5nLgoKYGBge3IgbnVtZXJpYyBjb3JycGxvdH0KdmFyaWJsZXMgPC0gCiAgbmVpZ2hib3JlZmZlY3QgJT4lIAogIGRwbHlyOjpzZWxlY3Qoc2FsZV9wcmljZSwgQWdlLCBudW1Sb29tcywgaGFzQmFzZW1lbnQsIGhhc0FDLCBxdWFsaXR5LCBidWlsZGluZ2RpcywgaGFzRmlyZXBsYWNlLCBoYXNHYXJhZ2UsIHN0b3JpZXMsIGxvZ2FyZWEsIHZpZXcsIERJU1RfTkFNRSwgY3JpbWVzLkJ1ZmZlciwgdG90YWxzdG9wcywgaG9zcGl0YWxzX25uMSwgc2hvcHNfbm4zLCBzY2hvb2xfYnVmZmVyLCBtZWFuTWlub3IpCgp2YXJzIDwtIHNlbGVjdF9pZihzdF9kcm9wX2dlb21ldHJ5KHZhcmlibGVzKSwgaXMubnVtZXJpYykgJT4lIG5hLm9taXQoKQoKZ2djb3JycGxvdCgKICByb3VuZChjb3IodmFycyksIDEpLCAKICBwLm1hdCA9IGNvcl9wbWF0KHZhcnMpLAogIGNvbG9ycyA9IGMoIiMyODNkM2IiLCAid2hpdGUiLCAiI2M0NDUzNiIpLAogIHR5cGU9Imxvd2VyIiwKICBpbnNpZyA9ICJibGFuayIpICsgIAogIGxhYnModGl0bGUgPSAiQ29ycmVsYXRpb24gTWF0cml4IGZvciBhbGwgTnVtZXJpYyBWYXJpYWJsZXMiLAogICAgICAgY2FwdGlvbiA9ICJEYXRhOiBQaGlsYWRlbHBoaWEgUHJvcGVydGllcyBhbmQgQ3VycmVudCBBc3Nlc3NtZW50cyBcbiBPcGVuIERhdGEgUGhpbGx5IikgKwogIHRoZW1lKHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDksZmFjZSA9ICJpdGFsaWMiKSwKICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgZmFjZSA9ICJib2xkIiksIAogICAgICAgIGF4aXMudGV4dC54PWVsZW1lbnRfdGV4dChzaXplPTYpLAogICAgICAgIGF4aXMudGV4dC55PWVsZW1lbnRfdGV4dChzaXplPTYpLCAKICAgICAgICBheGlzLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTgpKQoKCgoKYGBgCgpXZSBtYWRlIGFuIGFkZGl0aW9uYWwgc2NhdHRlcnBsb3QgaW4gb3JkZXIgdG8gYmV0dGVyIHZpc3VhbGl6ZSB0aGUgZGlyZWN0aW9uIGFuZCBzdHJlbmd0aCBvZiBjb3JyZWxhdGlvbiBiZXR3ZWVuIHNhbGUgcHJpY2UgYW5kIG91ciBudW1lcmljIHByZWRpY3RvciB2YXJpYWJsZXMuIAoKYGBge3Igc2NhdHRlciBwbG90LCBmaWcuaGVpZ2h0PTgsIGZpZy53aWR0aD0xMX0KCm5laWdoYm9yZWZmZWN0X25vbmdlb21fbG9uZyA8LSBuZWlnaGJvcmVmZmVjdF9ub25nZW9tJT4lIAogIHBpdm90X2xvbmdlcihjb2xzID0gLXNhbGVfcHJpY2UsICMgZXZlcnl0aGluZyBleGNlcHQgbWVhc3VyZW1lbnQKICAgICAgICAgICAgICAgbmFtZXNfdG8gPSAiVHlwZSIsICMgY2F0ZWdvcml6ZXMgYWxsIHF1YW50aXRhdGl2ZSB2YXJpYWJsZXMgaW50byBUeXBlCiAgICAgICAgICAgICAgIHZhbHVlc190byA9ICJOdW1iZXIiKSAjIHRoZSBuYW1lIG9mIHZhbHVlcyBpcyBOdW1iZXIKCm5laWdoYm9yZWZmZWN0X25vbmdlb21fbG9uZyAlPiUKICBnZ3Bsb3QoYWVzKHg9IE51bWJlciwgeSA9IHNhbGVfcHJpY2UpKSArCiAgZ2VvbV9wb2ludChzaXplID0gMC4wMSwgY29sb3IgPSAiIzI4M2QzYiIpICsgIAogIGdlb21fc21vb3RoKG1ldGhvZD0nbG0nLCBmb3JtdWxhPSB5fngsIGx3ZD0wLjUsIGNvbG9yID0gIiNjNDQ1MzYiKSArCiAgZmFjZXRfd3JhcCh+IFR5cGUsIHNjYWxlcyA9ICJmcmVlIiwgbGFiZWxsZXI9IGxhYmVsbGVyKFR5cGUgPSBjKAogICAgYEFnZWAgPSAiQWdlIiwKICAgIGBob3NwaXRhbHNfbm4xYCA9ICJDcmltZXMiLAogICAgYGNyaW1lcy5CdWZmZXJgID0gIkhvc3BpdGFsIiwKICAgIGBsb2dhcmVhYCA9ICJBcmVhIiwKICAgIGBtZWFuTWlub3JgID0gIk1pbm9yaXR5IFBvcCIsCiAgICBgbnVtUm9vbXNgID0gIk51bSBSb29tcyIsCiAgICBgc2Nob29sX2J1ZmZlcmAgPSAiU2Nob29sIiwKICAgIGBzaG9wc19ubjNgID0gIlNob3BzIiwKICAgIGB0b3RhbHN0b3BzYCA9ICJUcmFuc2l0IFN0b3BzIikpKSAgKwogIGxhYnModGl0bGUgPSAiU2NhdHRlciBQbG90IG9mIFNhbGUgUHJpY2Ugb3ZlciBOdW1lcmljIFByZWRpY3RlZCBWYXJpYWJsZXMiLAogICAgICAgY2FwdGlvbiA9ICJEYXRhOiBQaGlsYWRlbHBoaWEgUHJvcGVydGllcyBhbmQgQ3VycmVudCBBc3Nlc3NtZW50cyBcbiBPcGVuIERhdGEgUGhpbGx5IikgKwogIHRoZW1lKHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDksZmFjZSA9ICJpdGFsaWMiKSwKICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgZmFjZSA9ICJib2xkIiksIAogICAgICAgIGF4aXMudGV4dC54PWVsZW1lbnRfdGV4dChzaXplPTYpLAogICAgICAgIGF4aXMudGV4dC55PWVsZW1lbnRfdGV4dChzaXplPTYpLCAKICAgICAgICBheGlzLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTgpLCAKICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfcmVjdChjb2xvdXIgPSAiZ3JleSIsIGZpbGw9TkEsIHNpemU9MC44KSkKCgpgYGAKCgpCYXNlZCBvbiB0aGlzLCB3ZSBjb25kdWN0ZWQgYSBjb3JyZWxhdGlvbiBhbmFseXNpcyBhbW9uZyB0aGUgdmFyaWFibGVzLCBhbmQgdGhlIHJlc3VsdHMgYXJlIHNob3duIGluIHRoZSB0YWJsZSBiZWxvdy4gQWNjb3JkaW5nIHRvIHRoZSByZXN1bHRzLCBob3VzZSBhZ2UgYChQPDAuMDAxLCBUPSAtMzEuMTYpYCwgY3JpbWUgY291bnQgd2l0aGluIDIwMCBtZXRlcnMgYChQPDAuMDAxLCBUPSAtNjQuNzEpYCwgZGlzdGFuY2UgdG8gdGhlIG5lYXJlc3QgaG9zcGl0YWwgYChQPDAuMDAxLCBUPSAtMTkuNDkpYCwgYW5kIGJlaW5nIGEgbWlub3JpdHktbWFqb3JpdHkgY29tbXVuaXR5IGAoUDwwLjAwMSwgVD0gLTk1LjEyKWAgYWxsIGhhdmUgc2lnbmlmaWNhbnQgbmVnYXRpdmUgY29ycmVsYXRpb25zLiBUaGUgY29ycmVsYXRpb24gY29lZmZpY2llbnRzIGFyZSAtMC4xOTksIC0wLjM4OCwgLTAuMTI2LCBhbmQgdGhlIGNvcnJlbGF0aW9uIGNvZWZmaWNpZW50IGZvciBiZWluZyBhIG1pbm9yaXR5IGNvbW11bml0eSBpcyBhcyBoaWdoIGFzIC0wLjUyNy4gUm9vbSBjb3VudCBgKFA8MC4wMDEsIFQ9IDE0LjcyKWAsIHRoZSBsb2dhcml0aG0gb2YgYXJlYSBgKFA8MC4wMDEsIFQ9IDY2LjIyKWAsIHRoZSBudW1iZXIgb2Ygc3Vid2F5IHN0YXRpb25zIGAoUDwwLjAwMSwgVD0gMTkuMTUpYCwgYW5kIHRoZSBudW1iZXIgb2Ygc2Nob29scyB3aXRoaW4gYSAxLjUta2lsb21ldGVyIHJhZGl1cyBgKFA8MC4wMDEsIFQ9IDIzLjYxKWAgYWxsIHNob3cgc2lnbmlmaWNhbnQgcG9zaXRpdmUgY29ycmVsYXRpb25zLiBBZGRpdGlvbmFsbHksIGFsdGhvdWdoIHRoZSBudW1iZXIgb2YgY29tbWVyY2lhbCBhY3Rpdml0aWVzIG5lYXJieSBgKFA8MC4wMDEsIFQ9IC00LjM0KWAgYWxzbyBzaG93cyBhIHNpZ25pZmljYW50IG5lZ2F0aXZlIGNvcnJlbGF0aW9uLCBpdHMgY29lZmZpY2llbnQgaXMgbG93LCBhdCAtMC4wMjguCgpgYGB7ciBjb3JyIHRlc3R9Cgpjb3JfdmFyIDwtIGMoIkFnZSIsICJudW1Sb29tcyIsICJsb2dhcmVhIiwgImNyaW1lcy5CdWZmZXIiLCAidG90YWxzdG9wcyIsICJob3NwaXRhbHNfbm4xIiwgInNob3BzX25uMyIsICJzY2hvb2xfYnVmZmVyIiwgIm1lYW5NaW5vciIpCgpuZXdfcm93X25hbWVzIDwtIGMoCiAgIkFnZSIsCiAgIk51bWJlciBvZiBSb29tcyIsCiAgIkFyZWEiLAogICJDcmltZXMiLAogICJUcmFuc2l0IFN0b3BzIiwKICAiSG9zcGl0YWxzIiwKICAiU2hvcHMiLAogICJTY2hvb2xzIiwKICAiTWlub3IiCikKCmNvcl9zdW1tYXJ5X3RhYmxlIDwtIGRhdGEuZnJhbWUoVmFyaWFibGUgPSBjaGFyYWN0ZXIoMCksIENvcnJlbGF0aW9uID0gbnVtZXJpYygwKSkKCmZvciAodmFyIGluIGNvcl92YXIpIHsKICBjb3JyZWxhdGlvbl90ZXN0IDwtIGNvci50ZXN0KG5laWdoYm9yZWZmZWN0JHNhbGVfcHJpY2UsIG5laWdoYm9yZWZmZWN0W1t2YXJdXSkKICBjb3JyZWxhdGlvbiA8LSBjb3JyZWxhdGlvbl90ZXN0JGVzdGltYXRlCiAgcF92YWx1ZSA8LSBjb3JyZWxhdGlvbl90ZXN0JHAudmFsdWUKICB0X3N0YXRpc3RpYyA8LSBjb3JyZWxhdGlvbl90ZXN0JHN0YXRpc3RpYyAKICBjaV9sb3dlciA8LSBjb3JyZWxhdGlvbl90ZXN0JGNvbmYuaW50WzFdIAogIGNpX3VwcGVyIDwtIGNvcnJlbGF0aW9uX3Rlc3QkY29uZi5pbnRbMl0gCiAgc3VtbWFyeV9kYXRhIDwtIGRhdGEuZnJhbWUoQ29ycmVsYXRpb24gPSBjb3JyZWxhdGlvbiwgUF9WYWx1ZSA9IHBfdmFsdWUsIFRfU3RhdGlzdGljID0gdF9zdGF0aXN0aWMsIENJX0xvd2VyID0gY2lfbG93ZXIsIENJX1VwcGVyID0gY2lfdXBwZXIpCiAgY29yX3N1bW1hcnlfdGFibGUgPC0gcmJpbmQoY29yX3N1bW1hcnlfdGFibGUsIHN1bW1hcnlfZGF0YSkKfQoKY29yX3N1bW1hcnlfdGFibGUkUF9WYWx1ZSA8LSBzcHJpbnRmKCIlLjNmIiwgY29yX3N1bW1hcnlfdGFibGUkUF9WYWx1ZSkKCmNvcl9zdW1tYXJ5X3RhYmxlJFBfVmFsdWUgPC0gaWZlbHNlKGNvcl9zdW1tYXJ5X3RhYmxlJFBfVmFsdWUgPCAwLjAwMSwgcGFzdGUwKCI8IDAuMDAxIiwgIioqKiIpLCBpZmVsc2UoY29yX3N1bW1hcnlfdGFibGUkUF9WYWx1ZSA8IDAuMDEsIHBhc3RlMChjb3Jfc3VtbWFyeV90YWJsZSRQX1ZhbHVlLCAiKioiKSwgaWZlbHNlKGNvcl9zdW1tYXJ5X3RhYmxlJFBfVmFsdWUgPCAwLjA1LCBwYXN0ZTAoY29yX3N1bW1hcnlfdGFibGUkUF9WYWx1ZSwgIioiKSwgYXMuY2hhcmFjdGVyKGNvcl9zdW1tYXJ5X3RhYmxlJFBfVmFsdWUpKSkpCgpyb3duYW1lcyhjb3Jfc3VtbWFyeV90YWJsZSkgPC0gbmV3X3Jvd19uYW1lcwoKa2FibGUoY29yX3N1bW1hcnlfdGFibGUpICU+JSBrYWJsZV9zdHlsaW5nKCBib290c3RyYXBfb3B0aW9ucyA9IGMoInN0cmlwZWQiLCAiaG92ZXIiLCAiY29uZGVuc2VkIikpICU+JQogZm9vdG5vdGUoZ2VuZXJhbF90aXRsZSA9ICJcbiIsIGdlbmVyYWwgPSAiVGFibGUgMTEiKQoKYGBgCgoKIyMgQ2F0ZWdvcmljYWwgVmFyaWFibGVzIAoKVGhlIHRhYmxlIGJlbG93IHNob3dzIGFsbCBvZiB0aGUgY2F0ZWdvcmljYWwgdmFyaWFibGVzIGluIG91ciBtb2RlbCBhcyB3ZWxsIGFzIHRoZSBjb3VudCBmb3Igc3ViZ3JvdXBzIGluIGVhY2ggdmFyaWFibGUuIAoKCnwgVmFyaWFibGUgTmFtZSAgICAgICAgIHwgQ291bnQgKCUpICAgICAgICB8IAp8IDotLS0tLS0tLS0tICAgICAgICAgICB8IDotLS0tLS0tLS0tOiAgfCAgCnwgKipCYXNlbWVudCoqICAgICAgICAgIHwgICAgICAgICAgICB8CnwgWWVzICAgICAgICAgICAgICAgICAgIHwgMTMsODYzICg1OSUpICAgICAgfAp8IE5vICAgICAgICAgICAgICAgICAgICB8IDksNzI0ICg0MSUpICAgIHwKfCAqKkFpciBDb25kaXRpb25lcioqICAgfCAgICAgICAgfAp8IFllcyAgICAgICAgICAgICAgICAgICB8IDcsNzcxICgzMyUpIHwKfCBObyAgICAgICAgICAgICAgICAgICAgfCAxNSw4MTYgKDY3JSkgfAp8ICoqUXVhbGl0eSBHcmFkZSoqICAgICB8ICAgICAgICAgICAgICB8CnwgR29vZCAgICAgICAgICAgICAgICAgIHwgMSwwNzQgKDQuNiUpIHwKfCBCYWQgICAgICAgICAgICAgICAgICAgfCAyMiw1MTMgKDk1JSkgfAp8ICoqQnVpbGRpbmcgVHlwZXMqKiAgICB8ICAgICAgICAgICAgICB8CnwgT3RoZXIgICAgICAgICAgICAgICAgIHwgMiwzNTIgKDEwLjAlKSAgICAgfAp8IFJvdyAgICAgICAgICAgICAgICAgICB8IDE3LDk3OCAoNzYlKSB8CnwgVHdpbiAgICAgICAgICAgICAgICAgIHwgMywyNTcgKDE0JSkgfAp8ICoqRmlyZSBQbGFjZSoqICAgICAgICB8ICAgICAgICAgICAgIHwKfCBZZXMgICAgICAgICAgICAgICAgICAgfCA4MzEgKDMuNSUpICAgICB8CnwgTm8gICAgICAgICAgICAgICAgICAgIHwgMjIsNzU2ICg5NiUpICAgICB8CnwgKipHYXJhZ2UqKiAgICAgICAgICAgIHwgICAgIHwKfCBZZXMgICAgICAgICAgICAgICAgICAgfCAzNTUgKDEuNSUpICAgICB8CnwgTm8gICAgICAgICAgICAgICAgICAgIHwgMjMsMjMyICg5OCUpICAgICB8CnwgKipTdG9yaWVzIE51bWJlcioqICAgIHwgICAgICAgICAgICAgfAp8IERvdWJsZSAgICAgICAgICAgICAgICB8IDE2LDY4OCAoNzElKSB8CnwgTXVsdGlwbGUgICAgICAgICAgICAgIHwgMiw5NDMgKDEyJSkgICAgfAp8IFNpbmdsZSAgICAgICAgICAgICAgICB8IDMsOTU2ICgxNyUpICAgICB8CnwgKipWaWV3KiogICAgICAgICAgICAgIHwgICAgICB8CnwgU2NlbmljICAgICAgICAgICAgICAgIHwgOTU5ICg0LjElKSAgICAgfAp8IFVyYmFuICAgICAgICAgICAgICAgICB8IDQ0OSAoMS45JSkgICAgfAp8IFR5cGljYWwgICAgICAgICAgICAgICB8IDIyLDE3OSAoOTQlKSAgICAgfAp8ICoqRGlzdHJpY3QgTmFtZSoqICAgICB8ICAgfAp8IENlbnRyYWwJICAgICAgICAgICAgICB8IDIsMDcyICg4LjglKSB8CnwgQ2VudHJhbCBOb3J0aGVhc3QgICAgIHwJMSwwMTUgKDQuMyUpIHwKfCBMb3dlciBGYXIgTm9ydGhlYXN0ICAgfAkxLDAzOCAoNC40JSkgfAp8IExvd2VyIE5vcnRoCSAgICAgICAgICB8IDEsMzUzICg1LjclKSB8CnwgTG93ZXIgTm9ydGhlYXN0CSAgICAgIHwgMSw0NzIgKDYuMiUpIHwKfCBMb3dlciBOb3J0aHdlc3QJICAgICAgfCAxLDI2MCAoNS4zJSkgfAp8IExvd2VyIFNvdXRoCSAgICAgICAgICB8IDYzICgwLjMlKSAgICB8CnwgTG93ZXIgU291dGh3ZXN0CSAgICAgIHwgNjA5ICgyLjYlKSAgIHwKfCBOb3J0aAkgICAgICAgICAgICAgICAgfCAxLDkwMiAoOC4xJSkgfAp8IE5vcnRoIERlbGF3YXJlCSAgICAgIHwgMSw4ODggKDguMCUpIHwKfCBSaXZlciBXYXJkcwkgICAgICAgICAgfCAxLDczMyAoNy4zJSkgfAp8IFNvdXRoCSAgICAgICAgICAgICAgICB8IDIsOTUzICgxMyUpICB8CnwgVW5pdmVyc2l0eSBTb3V0aHdlc3QJfCA2NDYgKDIuNyUpIHwKfCBVcHBlciBGYXIgTm9ydGhlYXN0CSAgfCA3ODUgKDMuMyUpIHwKfCBVcHBlciBOb3J0aAkgICAgICAgICAgfCAxLDc4NiAoNy42JSkgfAp8IFVwcGVyIE5vcnRod2VzdAkgICAgICB8IDEsMTEyICg0LjclKSB8CnwgV2VzdAkgICAgICAgICAgICAgICAgfCAxLDQ4MSAoNi4zJSkgfAp8IFdlc3QgUGFyawkgICAgICAgICAgICB8IDQxOSAoMS44JSkgfApUYWJsZSAxMgoKYGBge3IgY2F0ZWdvcmljYWwgdGFibGUsIGluY2x1ZGU9RkFMU0V9CnZhcnMgPC0gc2VsZWN0X2lmKHN0X2Ryb3BfZ2VvbWV0cnkodmFyaWJsZXMpLCBuZWdhdGUoaXMubnVtZXJpYykpICU+JSBuYS5vbWl0KCkKdmFycyAlPiUgdGJsX3N1bW1hcnkoKSAlPiUgYXNfa2FibGUoKSAlPiUgIGthYmxlX3N0eWxpbmcoKQpgYGAKCgpGb3IgZWFjaCBvZiB0aGVzZSBjYXRlZ29yaWNhbCB2YXJpYWJsZXMsIHdlIHBlcmZvcm1lZCBhbiBBbmFseXNpcyBvZiBWYXJpYW5jZSAoQU5PVkEpIHRlc3QuIEFOT1ZBIGlzIGEgc3RhdGlzdGljYWwgdGVzdCB0aGF0IGFsbG93cyB1cyB0byBjb21wYXJlIG1lYW5zIGFjcm9zcyBhY3Jvc3MgdGhyZWUgb3IgbW9yZSBncm91cHMuIEl0cyBzaWduaWZpY2FuY2UgbGllcyBpbiBpdHMgYWJpbGl0eSB0byBlZmZpY2llbnRseSBhc3Nlc3MgdmFyaWF0aW9ucyBhbW9uZyB0aGVzZSBncm91cHMgYnkgcGFydGl0aW9uaW5nIHRoZSB0b3RhbCB2YXJpYW5jZSwgdGhlcmVieSBkZXRlcm1pbmluZyBpZiB0aGUgZGlmZmVyZW5jZXMgYXJlIHN0YXRpc3RpY2FsbHkgc2lnbmlmaWNhbnQuIEluIG91ciBjYXNlLCB3ZSBhcmUgaW50ZXJlc3RlZCBpbiB3aGV0aGVyIHRoZXJlJ3MgYSBzaWduaWZpY2FudCBkaWZmZXJlbmNlIGluIHNhbGUgcHJpY2UgYmV0d2VlbiBob3VzZXMgd2l0aCBkaWZmZXJlbnQgdmlld3MsIGZvciBleGFtcGxlLiBXZSBmb3VuZCB0aGF0IGFsbCBvZiBvdXIgY2F0ZWdvcmljYWwgcHJlZGljdG9yIHZhcmlhYmxlcyBhcmUgc2lnbmlmaWNhbnQgcHJlZGljdG9ycyBvZiBzYWxlIHByaWNlcy4KCmBgYHtyIGFub3ZhIHRlc3RzfQphbm92YV92YXIgPC0gYygiRElTVF9OQU1FIiwgInZpZXciLCAic3RvcmllcyIsICJoYXNHYXJhZ2UiLCAiaGFzRmlyZXBsYWNlIiwgImJ1aWxkaW5nZGlzIiwgInF1YWxpdHkiLCAiaGFzQUMiLCAiaGFzQmFzZW1lbnQiKQoKbmV3X3Jvd19uYW1lcyA8LSBjKAogICJEaXN0cmljdCBOYW1lIiwKICAiVHlwZSBvZiBWaWV3IiwKICAiTnVtYmVyIFN0b3JpZXMiLAogICJHYXJhZ2UiLAogICJGaXJlcGxhY2UiLAogICJUeXBlIG9mIEJ1aWxkaW5nIiwKICAiUXVhbGl0eSBHcmFkZSIsCiAgIkFpciBDb25kaXRpb25pbmciLAogICJCYXNlbWVudCIKKQoKYW5vdmFfc3VtbWFyeV90YWJsZSA8LSBkYXRhLmZyYW1lKERmID0gaW50ZWdlcigwKSwgU3VtX1NxID0gbnVtZXJpYygwKSwgTWVhbl9TcSA9IG51bWVyaWMoMCksIEZfdmFsdWUgPSBudW1lcmljKDApKQpyb3dfbmFtZXMgPC0gY2hhcmFjdGVyKDApCgpmb3IgKGkgaW4gc2VxX2Fsb25nKGFub3ZhX3ZhcikpIHsKICB2YXIgPC0gYW5vdmFfdmFyW2ldCiAgYW5vdmFfcmVzdWx0IDwtIGFvdihuZWlnaGJvcmVmZmVjdCRzYWxlX3ByaWNlIH4gYXMuZmFjdG9yKG5laWdoYm9yZWZmZWN0W1t2YXJdXSkpCiAgI3Bhc3RlKCJBTk9WQSByZXN1bHQgZm9yIiwgdmFyKQogIHN1bW1hcnlfZGF0YSA8LSBzdW1tYXJ5KGFub3ZhX3Jlc3VsdClbWzFdXVssIGMoIkRmIiwgIlN1bSBTcSIsICJNZWFuIFNxIiwgIkYgdmFsdWUiKV0KICBhbm92YV9zdW1tYXJ5X3RhYmxlIDwtIHJiaW5kKGFub3ZhX3N1bW1hcnlfdGFibGUsIHN1bW1hcnlfZGF0YSkKICAgcm93X25hbWVzIDwtIGMocm93X25hbWVzLCBwYXN0ZSh2YXIpKQp9CgpmaWx0ZXJfY29uZGl0aW9uIDwtICFncmVwbCgiUmVzaWR1YWxzIiwgcm93bmFtZXMoYW5vdmFfc3VtbWFyeV90YWJsZSkpCmZpbHRlcmVkX2Fub3ZhX3N1bW1hcnkgPC0gYW5vdmFfc3VtbWFyeV90YWJsZVtmaWx0ZXJfY29uZGl0aW9uLCBdCgpyb3duYW1lcyhmaWx0ZXJlZF9hbm92YV9zdW1tYXJ5KSA8LSBuZXdfcm93X25hbWVzCmthYmxlKGZpbHRlcmVkX2Fub3ZhX3N1bW1hcnkpICU+JSBrYWJsZV9zdHlsaW5nKCBib290c3RyYXBfb3B0aW9ucyA9IGMoInN0cmlwZWQiLCAiaG92ZXIiLCAiY29uZGVuc2VkIikpICU+JQogIGZvb3Rub3RlKGdlbmVyYWxfdGl0bGUgPSAiXG4iLCBnZW5lcmFsID0gIlRhYmxlIDEzIikKYGBgCgoKV2UgcHJvY2VlZCB0byBtYWtlIGJhciBjaGFydHMgZm9yIGFsbCBvZiBvdXIgY2F0ZWdvcmljYWwgcHJlZGljdG9yIHZhcmlhYmxlcy4gV2UgbWF5IHNlZSB0aGF0IG1vc3QgaG91c2VzIGluIFBoaWxhZGVscGhpYSwgZG8gbm90IGhhdmUgYSBiYXNlbWVudCwgZG8gbm90IGhhdmUgYWlyIGNvbmRpdGlvbmVyLCBhcmUgb2YgbGVzcyBzYXRpc2Z5aW5nIHF1YWxpdHksIGFyZSByb3cgaG91c2VzLCBkbyBub3QgaGF2ZSBhIGZpcmUgcGxhY2UsIGhhdmUgYSBnYXJhZ2UsIGFyZSBvZiBtdWx0aXBsZSBzdG9yaWVzLCBhbmQgaGFzIHR5cGljYWwgdmlldy4gCgpgYGB7ciBiYXIgcGxvdCwgZmlnLmhlaWdodD04LCBmaWcud2lkdGg9MTF9Cgp2YXJpYWJsZXNfdG9fcGxvdCA8LSBjKCJoYXNCYXNlbWVudCIsICJoYXNBQyIsICJoYXNGaXJlcGxhY2UiLCAiaGFzR2FyYWdlIiwgInN0b3JpZXMiLCAidmlldyIsICAicXVhbGl0eSIsICJidWlsZGluZ2RpcyIpCnN1YnNldF92YXJpYmFsZXMgPC0gbmVpZ2hib3JlZmZlY3QgJT4lIHNlbGVjdChhbGxfb2YodmFyaWFibGVzX3RvX3Bsb3QpKSAlPiUgIHN0X2Ryb3BfZ2VvbWV0cnkoKQoKbG9uZ192YXJzIDwtIGdhdGhlcihzdWJzZXRfdmFyaWJhbGVzKQoKcGxvdDEgPC0gZ2dwbG90KGxvbmdfdmFycywgYWVzKHggPSB2YWx1ZSkpICsKICBnZW9tX2JhcihmaWxsPSAiIzI4M2QzYiIpICsKICBmYWNldF93cmFwKH4ga2V5LCBzY2FsZXMgPSAiZnJlZSIsIG5jb2wgPSA0LCAgbGFiZWxsZXI9IGxhYmVsbGVyKGtleSA9IGMoCiAgICBgYnVpbGRpbmdkaXNgID0gIkJ1aWxkaW5nIFR5cGUiLAogICAgYGhhc0FDYCA9ICJBaXIgQ29uZGl0aW9uZXIiLAogICAgYGhhc0Jhc2VtZW50YCA9ICJCYXNlbWVudCIsCiAgICBgaGFzRmlyZXBsYWNlYCA9ICJGaXJlcGxhY2UiLAogICAgYGhhc0dhcmFnZWAgPSAiR2FyYWdlIiwKICAgIGBxdWFsaXR5YCA9ICJRdWFsaXR5IEdyYWRlIiwKICAgIGBzdG9yaWVzYCA9ICJOdW1iZXIgU3RvcmllcyIsCiAgICBgdmlld2AgPSAiVHlwZSBvZiBWaWV3IikpKSArCiAgbGFicyh0aXRsZSA9ICJEaXN0cmlidXRpb24gb2YgQ2F0ZWdvcmljYWwgUHJlZGljdG9yIFZhcmlhYmxlcyIsCiAgICAgICB4ID0gIlZhbHVlIiwKICAgICAgIHkgPSAiQ291bnQiKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSkgKwogIHRoZW1lKHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDksZmFjZSA9ICJpdGFsaWMiKSwKICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgZmFjZSA9ICJib2xkIiksIAogICAgICAgIGF4aXMudGV4dC54PWVsZW1lbnRfdGV4dChzaXplPTYpLAogICAgICAgIGF4aXMudGV4dC55PWVsZW1lbnRfdGV4dChzaXplPTYpLCAKICAgICAgICBheGlzLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTgpLCAKICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfcmVjdChjb2xvdXIgPSAiZ3JleSIsIGZpbGw9TkEsIHNpemU9MC44KSkKCgp2YXJpYWJsZXNfdG9fcGxvdCA8LSBjKCJESVNUX05BTUUiKQpzdWJzZXRfdmFyaWJhbGVzIDwtIG5laWdoYm9yZWZmZWN0ICU+JSBzZWxlY3QoYWxsX29mKHZhcmlhYmxlc190b19wbG90KSkgJT4lICBzdF9kcm9wX2dlb21ldHJ5KCkKbG9uZ192YXJzIDwtIGdhdGhlcihzdWJzZXRfdmFyaWJhbGVzKQpwbG90MiA8LSBnZ3Bsb3QobG9uZ192YXJzLCBhZXMoeCA9IHZhbHVlKSkgKwogIGdlb21fYmFyKGZpbGwgPSAiIzI4M2QzYiIpICsKICBmYWNldF93cmFwKH4ga2V5LCBzY2FsZXMgPSAiZnJlZSIsIGxhYmVsbGVyPSBsYWJlbGxlcihrZXkgPSBjKAogICAgYERJU1RfTkFNRWAgPSAiRGlzdHJpY3QgTmFtZSIpKSkgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkpICsKICB0aGVtZShwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSA5LGZhY2UgPSAiaXRhbGljIiksCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIsIGZhY2UgPSAiYm9sZCIpLCAKICAgICAgICBheGlzLnRleHQueD1lbGVtZW50X3RleHQoc2l6ZT02KSwKICAgICAgICBheGlzLnRleHQueT1lbGVtZW50X3RleHQoc2l6ZT02KSwgCiAgICAgICAgYXhpcy50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT04KSwgCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X3JlY3QoY29sb3VyID0gImdyZXkiLCBmaWxsPU5BLCBzaXplPTAuOCkpCgoocGxvdDEgfCBwbG90MikgKyBwbG90X2xheW91dCh3aWR0aHMgPSBjKDIsIDEuNSkpCgpgYGAKCgojIyBWYXJpYWJsZSBNYXBzIAoKQmVsb3csIHdlIG1hZGUgc29tZSB2aXN1YWxpemF0aW9ucyBvZiBzZWxlY3RlZCBwcmVkaWN0b3IgdmFyaWFibGVzIHRvIHVuZGVyc3RhbmQgdGhlaXIgc3BhdGlhbCBjaGFyYWN0ZXJpc3RpY3MuIFRoZSBmaXJzdCBtYXAgd2UgbWFkZSBzaG93cyB0aGUgc3BhdGlhbCBkaXN0cmlidXRpb24gb2YgcHJvcGVydHkgc2FsZSBwcmljZSBpbiBQaGlsYWRlbHBoaWEsIGZyb20gd2hpY2ggd2UgY2FuIHNlZSB0aGF0IHNhbGUgcHJpY2UgaW4gUGhpbGFkZWxwaGlhIGlzIHByZXR0eSBzcGF0aWFsbHkgY2x1c3RlcmVkLiBIb3VzZXMgdGhhdCBhcmUgd29ydGggYSBtaWxsaW9uIG9yIG1vcmUgYXJlIHR5cGljYWxseSBsb2NhdGVkIGF0IHRoZSBvdXRza2lydCBvZiB0aGUgY2l0eSwgc3VjaCBhcyB0aGUgbm9ydGhlYXN0IGNvcm5lciwgYXMgd2VsbCBhcyB0aGUgY2VudGVyIGNpdHksIHdoZXJlYXMgaG91c2VzIHRoYXQgd29ydGggdGhhbiAyNTBrIGFyZSBsb2NhdGVkIGluIE5vcnRoZWFzdCBhbmQgV2VzdCBQaGlsYWRlbHBoaWEuIAoKYGBge3Igc2FsZSBwcmljZSBtYXB9CgpuZWlnaGJvcmVmZmVjdCA8LSBuZWlnaGJvcmVmZmVjdCAlPiUgCiAgbXV0YXRlKHNhbGVfY2xhc3MgPSBjdXQoc2FsZV9wcmljZSwgYnJlYWtzID0gYygwLCAyNTAwMDAsIDUwMDAwMCwgNzUwMDAwLCAxMDAwMDAwLCBtYXgobmVpZ2hib3JlZmZlY3Qkc2FsZV9wcmljZSwgbmEucm09VFJVRSkpKSkKcGFsZXR0ZSA8LSBjKCIjZWRkZGQ0IiwiI2M0NDUzNiIsIiMxOTcyNzgiLCIjNzcyZTI1IiwiIzI4M2QzYiIpCgoKZ2dwbG90KCkrCiAgICBnZW9tX3NmKGRhdGE9ZHN0LCBmaWxsPSdncmV5ODAnLGNvbG9yPSd0cmFuc3BhcmVudCcpKwogICAgZ2VvbV9zZihkYXRhPW5laWdoYm9yZWZmZWN0LCBzaXplPTAuNzUsYWVzKGNvbG91ciA9IHE1KHNhbGVfY2xhc3MpKSkrCiAgICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gcGFsZXR0ZSwKICAgICAgICAgICAgICAgICAgICBuYW1lID0gIlNhbGVzIFByaWNlIiwKICAgICAgICAgICAgICAgICAgICBuYS52YWx1ZSA9ICdncmV5ODAnLAogICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IGMoJzAtMjUwaycsICcyNTBrLTUwMGsnLCAnNTAway03NTBrJywgJzc1MGstMW0nLCAnMW0gTW9yZScpKSArCiAgIHRoZW1lKAogICAgICAgIGF4aXMudGV4dC54PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpY2tzLng9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGV4dC55PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpY2tzLnk9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSwKICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfcmVjdChjb2xvdXIgPSAiZ3JleSIsIGZpbGw9TkEsIHNpemU9MC40KQogICAgICAgICkgKwogIGxhYnModGl0bGUgPSAiRGlzdHJpYnV0aW9uIG9mIFNhbGUgUHJpY2UgaW4gUGhpbGFkZWxwaGlhIiwKICAgICAgIGNhcHRpb24gPSAiRGF0YTogUGhpbGFkZWxwaGlhIFByb3BlcnRpZXMgYW5kIEN1cnJlbnQgQXNzZXNzbWVudHMiKSAKCmBgYAoKVGhlIHNlY29uZCBtYXAgc2hvd3MgdGhlIGRpc3RyaWJ1dGlvbiBvZiBjcmltZXMgaW4gUGhpbGFkZWxwaGlhLiBOb3RlIHRoYXQgd2hpbGUgdGhpcyBtYXBzIGJlbG93IGlzIG5vdCBhIGtlcm5lbCBkZW5zaXR5IHBsb3QuIFJhdGhlciwgZWFjaCBwb2ludCByZXByZXNlbnRzIG9mIHNpbmdsZSBob3VzZSwgb2Ygd2hpY2ggd2UgY3JlYXRlZCBhIGJ1ZmZlciBhcm91bmQgYW5kIHN1bW1hcml6ZWQgdGhlIG51bWJlciBvZiBjcmltZSB3aXRoaW4gdGhhdCBwYXJ0aWN1bGFyIGJ1ZmZlci4gVGhlcmVmb3JlLCBlYWNoIHBvaW50IGlzIGNvbG9yZWQgYnkgdGhlIG51bWJlciBvZiBjcmltZXMgbmVhcmJ5IC0gdGhlIGRhcmtlciB0aGUgY29sb3IsIHRoZSBoaWdoZXIgdGhlIG51bWJlciBvZiBjcmltZSBpbmNpZGVudHMgbmVhcmJ5LiBXZSBjYW4gc2VlIHRoYXQgaG91c2VzIHdpdGggbW9yZSBjcmltZSBpbmNpZGVudHMgbmVhcmJ5IGFyZSBtYWlubHkgbG9jYXRlZCBpbiBXZXN0IGFuZCBOb3J0aCBQaGlsYWRlbHBoaWEuIAoKYGBge3IgY3JpbWUgbWFwfQoKZ2dwbG90KCkrCiAgZ2VvbV9zZihkYXRhPWRzdCxmaWxsPSdncmV5ODAnLGNvbG9yPSd0cmFuc3BhcmVudCcpKwogIGdlb21fc2YoZGF0YT1uZWlnaGJvcmVmZmVjdCxhZXMoY29sb3VyID0gKGNyaW1lcy5CdWZmZXIpKSxzaXplPTAuMjQpKwogc2NhbGVfY29sb3JfY29udGludW91cyhsb3cgPSAiI0ZBRjlGNiIsIGhpZ2ggPSAiI2M0NDUzNiIsIG5hbWU9ICJDcmltZSBCdWZmZXIiKSArIAogICAgIHRoZW1lKAogICAgICAgIGF4aXMudGV4dC54PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpY2tzLng9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGV4dC55PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpY2tzLnk9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDksZmFjZSA9ICJpdGFsaWMiKSwKICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgZmFjZSA9ICJib2xkIiksCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X3JlY3QoY29sb3VyID0gImdyZXkiLCBmaWxsPU5BLCBzaXplPTAuNCkKICAgICAgICApICsKICBsYWJzKHRpdGxlID0gIkRpc3RyaWJ1dGlvbiBvZiBDcmltZXMgaW4gUGhpbGFkZWxwaGlhIiwKICAgICAgIHN1YnRpdGxlID0gIk5vdGU6IGVhY2ggcG9pbnQgcmVwcmVzZW50cyBhIHNpbmdsZSBob3VzZSBjb2xvcmVkIGJ5IG51bWJlciBvZiBjcmltZXMgbmVhcmJ5IiwgCiAgICAgICBjYXB0aW9uID0gIkRhdGE6IE9wZW5EYXRhUGhpbGx5IikgCgpgYGAKCgpUaGUgdGhpcmQgbWFwIHNob3dzIHRoZSBkaXN0cmlidXRpb24gb2YgbWlub3JpdHkgcG9wdWxhdGlvbiBpbiBQaGlsYWRlbHBoaWEuIEFnYWluLCBlYWNoIHBvaW50IHJlcHJlc2VudHMgYSBzaW5nbGUgaG91c2UuIFdlIGNhbGN1bGF0ZWQgdGhlIHBlcmNlbnRhZ2Ugb2YgbWlub3JpdHkgcG9wdWxhdGlvbiB3aXRoaW4gZWFjaCBjZW5zdXMgdHJhY3QgYW5kIHRoaXMgdmFsdWUgd2FzIGFwcGxpZWQgdG8gYWxsIGhvdXNlcyB3aXRoaW4gdGhhdCBjZW5zdXMgdHJhY3QuIEJhc2VkIG9uIG91ciBvYnNlcnZhdGlvbnMgZnJvbSBwcmV2aW91cyB0d28gbWFwcywgd2UgbWF5IHNlZSB0aGF0IG1pbm9yaXR5IHBvcHVsYXRpb25zIGFyZSBsb2NhdGVkIGluIGFyZWFzIHdoZXJlIHRoZXJlIHRlbmRzIHRvIGJlIGxvd2VyIGhvdXNlIHByaWNlIGFuZCBoaWdoZXIgY3JpbWUgcmF0ZS4gVGhpcyBwcm92aWRlcyBpbXBvcnRhbnQgaW1wbGljYXRpb25zIHRvIG91ciBob3VzZSBwcmljZSBwcmVkaWN0aW9ucy4gCgpgYGB7ciBtaW5vcml0eSBtYXB9CgpnZ3Bsb3QoKSsKICBnZW9tX3NmKGRhdGE9ZHN0LGZpbGw9J2dyZXk4MCcsY29sb3I9J3RyYW5zcGFyZW50JykrCiAgZ2VvbV9zZihkYXRhPW5laWdoYm9yZWZmZWN0LGFlcyhjb2xvdXIgPSBtZWFuTWlub3IpLHNpemU9MC41KSsKICBzY2FsZV9jb2xvcl9jb250aW51b3VzKGxvdyA9ICIjRkFGOUY2IiwgaGlnaCA9ICIjMjgzZDNiIiwgbmFtZT0gIlBjdCBNaW5vcml0eSBQb3B1bGF0aW9uICIpKwogICB0aGVtZSgKICAgICAgICBheGlzLnRleHQueD1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aWNrcy54PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRleHQueT1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aWNrcy55PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSA5LGZhY2UgPSAiaXRhbGljIiksCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIsIGZhY2UgPSAiYm9sZCIpLAogICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9yZWN0KGNvbG91ciA9ICJncmV5IiwgZmlsbD1OQSwgc2l6ZT0wLjQpCiAgICAgICAgKSArCiAgbGFicyh0aXRsZSA9ICJEaXN0cmlidXRpb24gb2YgTWlub3JpdHkgUG9wdWxhdGlvbiBpbiBQaGlsYWRlbHBoaWEiLAogICAgICAgc3VidGl0bGUgPSAiTm90ZTogZWFjaCBwb2ludCByZXByZXNlbnRzIGEgc2luZ2xlIGhvdXNlIGNvbG9yZWQgYnkgcGN0IG9mIG1pbm9yaXR5IHBvcCIsIAogICAgICAgY2FwdGlvbiA9ICJEYXRhOiBBbWVyaWNhbiBDb21tdW5pdHkgU3VydmV5IDIwMjAgIikgCgpgYGAKCkZvciBvdXIgZmluYWwgbWFwLCB3ZSB2aXN1YWxpemVkIHRoZSBkaXN0cmlidXRpb24gb2YgaG91c2luZyB0eXBlIGluIFBoaWxhZGVscGhpYS4gSXQgaXMgZXZpZGVudCB0aGF0IGhvdXNpbmcgdHlwZSBpcyBhbHNvIHNwYXRpYWxseSBjbHVzdGVyZWQgd2l0aCByb3cgaG91c2UgYmVpbmcgdGhlIG1vc3QgZG9taW5hbnQgdHlwZSBvZiBob3VzaW5nIGluIHRoZSBjaXR5LCBsZWF2aW5nIHR3aW4gaG91c2VzIGFuZCBvdGhlciB0eXBlcyBvZiBob3VzZXMgYXQgdGhlIG91dHNraXJ0IG9mIFBoaWxhZGVscGhpYS4gRHJhd2luZyBmcm9tIHByZXZpb3VzIG9ic2VydmF0aW9ucyByZWdhcmRpbmcgaG93IHJhY2UsIGNyaW1lLCBhbmQgc2FsZSBwcmljZSBhcmUgYWxzbyBzcGF0aWFsbHkgY2x1c3RlcmVkLCBpdCB3b3VsZCBub3QgYmUgZGlmZmljdWx0IHRvIG1ha2UgY29ubmVjdGlvbiBiZXR3ZWVuIHZhcmlhdGlvbnMgaW4gc2FsZSBwcmljZSBhbmQgdmFyaW91cyBob3VzZSB0eXBlcy4gCgoKYGBge3IgYnVpbGRpbmcgdHlwZSBtYXB9CmdncGxvdCgpKwogIGdlb21fc2YoZGF0YT1kc3QsZmlsbD0nZ3JleTgwJyxjb2xvcj0ndHJhbnNwYXJlbnQnKSsKICBnZW9tX3NmKGRhdGE9bmVpZ2hib3JlZmZlY3QsYWVzKGNvbG91ciA9IGJ1aWxkaW5nZGlzKSxzaXplPTAuNSkrCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9YygiI2M0NDUzNiIsIiMxOTcyNzgiLCIjNzcyZTI1IikgICwgbmFtZSA9ICJCdWlsZGluZyBUeXBlIikrCiAgdGhlbWUoCiAgICAgICAgYXhpcy50ZXh0Lng9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGlja3MueD1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGlja3MueT1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIsIGZhY2UgPSAiYm9sZCIpLAogICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9yZWN0KGNvbG91ciA9ICJncmV5IiwgZmlsbD1OQSwgc2l6ZT0wLjQpCiAgICAgICAgKSArCiAgbGFicyh0aXRsZSA9ICJEaXN0cmlidXRpb24gb2YgSG91c2UgVHlwZSBpbiBQaGlsYWRlbHBoaWEiLAogICAgICAgY2FwdGlvbiA9ICJEYXRhOiBQaGlsYWRlbHBoaWEgUHJvcGVydGllcyBhbmQgQ3VycmVudCBBc3Nlc3NtZW50cyIpIApgYGAKCgojIEZpbmFsIE1vZGVsIAoKIyMgTWFraW5nIFByZWRpY3Rpb25zCgpCYXNlZCBvbiB0aGUgbmVpZ2hib3Job29kIGVmZmVjdCBtb2RlbCB3aXRoIGFkZGVkIGFuZCBhZGp1c3RlZCBnZW9ncmFwaGljYWwgYXR0cmlidXRlcywgd2UgcHJlZGljdGVkIHRoZSBzYWxlIHByaWNlcyBvZiByYW5kb21seSBzZWxlY3RlZCB0cmFpbmluZyBkYXRhIHNldCBob3VzZXMgYW5kIGNhbGN1bGF0ZWQgdGhlIE1lYW4gQWJzb2x1dGUgRXJyb3IgKE1BRSkgYW5kIE1lYW4gQWJzb2x1dGUgUGVyY2VudGFnZSBFcnJvciAoTUFQRSkuIFRoZSBNQUUgYW5kIE1BUEUgZm9yIHRoZSBuZWlnaGJvcmhvb2QgZWZmZWN0IG1vZGVsIGFyZSA3MzMyNiBhbmQgMTIuMyUsIHJlc3BlY3RpdmVseS4gQ29tcGFyZWQgdG8gdGhlIHByZXZpb3VzIHZhbHVlcyBvZiA5ODMyOSBhbmQgMzklLCBib3RoIE1BRSBhbmQgTUFQRSBoYXZlIHNpZ25pZmljYW50bHkgZGVjcmVhc2VkLCBpbmRpY2F0aW5nIGFuIGltcHJvdmVkIGFjY3VyYWN5IG9mIHRoZSBtb2RlbCdzIHByZWRpY3Rpb25zLgoKYGBge3IgcHJlZGljdGlvbiB3aXRoIGZpbmFsIG1vZGVsLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQoKc2V0LnNlZWQoNDg1KSAKCmluVHJhaW4gPC0gY3JlYXRlRGF0YVBhcnRpdGlvbigKICAgIHkgPSBwYXN0ZShuZWlnaGJvcmVmZmVjdCRESVNUX05BTUUsIG5laWdoYm9yZWZmZWN0JGhhc0Jhc2VtZW50LCBuZWlnaGJvcmVmZmVjdCRoYXNBQywgbmVpZ2hib3JlZmZlY3QkcXVhbGl0eSwgbmVpZ2hib3JlZmZlY3QkYnVpbGRpbmdkaXMsIG5laWdoYm9yZWZmZWN0JGhhc0ZpcmVwbGFjZSwgbmVpZ2hib3JlZmZlY3QkaGFzR2FyYWdlLCBuZWlnaGJvcmVmZmVjdCRzdG9yaWVzLCBuZWlnaGJvcmVmZmVjdCR2aWV3KSwgCiAgICAgICAgICAgICBwID0gLjYwLCBsaXN0ID0gRkFMU0UpCgoKdHJfbmVpZ2gudHJhaW5pbmcgPC0gbmVpZ2hib3JlZmZlY3RbaW5UcmFpbixdIAp0cl9uZWlnaC50ZXN0IDwtIG5laWdoYm9yZWZmZWN0Wy1pblRyYWluLF0gIAoKcmVnLnRyYWluIDwtIGxtKHNhbGVfcHJpY2UgfiAuLCBkYXRhID0gdHJfbmVpZ2gudHJhaW5pbmcgJT4lIHN0X2Ryb3BfZ2VvbWV0cnkoKSAlPiUgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRwbHlyOjpzZWxlY3Qoc2FsZV9wcmljZSwgQWdlLCBudW1Sb29tcywgaGFzQmFzZW1lbnQsIGhhc0FDLCBxdWFsaXR5LCBidWlsZGluZ2RpcywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaGFzRmlyZXBsYWNlLCBoYXNHYXJhZ2UsIHN0b3JpZXMsIGxvZ2FyZWEsIHZpZXcsIERJU1RfTkFNRSwgY3JpbWVzLkJ1ZmZlciwgdG90YWxzdG9wcywgaG9zcGl0YWxzX25uMSwgc2hvcHNfbm4zLCBzY2hvb2xfYnVmZmVyLCBtZWFuTWlub3IpKQoKCnRyX25laWdoLnRlc3QgPC0gCiAgdHJfbmVpZ2gudGVzdCAlPiUgCiAgbXV0YXRlKFJlZ3Jlc3Npb24gPSAiTmVpZ2hib3Job29kIEVmZmVjdHMiLCAKICAgIFNhbGVQcmljZS5QcmVkaWN0ID0gcHJlZGljdChyZWcudHJhaW4sIHRyX25laWdoLnRlc3QpLAogICAgICAgICBTYWxlUHJpY2UuRXJyb3IgPSBTYWxlUHJpY2UuUHJlZGljdCAtIHNhbGVfcHJpY2UsCiAgICAgICAgIFNhbGVQcmljZS5BYnNFcnJvciA9IGFicyhTYWxlUHJpY2UuUHJlZGljdCAtIHNhbGVfcHJpY2UpLAogICAgICAgICBTYWxlUHJpY2UuQVBFID0gKGFicyhTYWxlUHJpY2UuUHJlZGljdCAtIHNhbGVfcHJpY2UpKSAvIFNhbGVQcmljZS5QcmVkaWN0KQoKdHJfbmVpZ2gudGVzdCAlPiUgCiAgc3RfZHJvcF9nZW9tZXRyeSgpICU+JQogIHN1bW1hcmlzZShNQUUgPSBtZWFuKFNhbGVQcmljZS5BYnNFcnJvciksCiAgICAgICAgICAgIE1BUEUgPSBtZWFuKFNhbGVQcmljZS5BUEUpKjEwMCkgJT4lCiAga2JsKGNvbC5uYW1lPWMoJ01lYW4gQWJzb2x1dGUgRXJyb3InLCdNZWFuIEFic29sdXRlIFBlcmNlbnRhZ2UgRXJyb3InKSkgJT4lCiAga2FibGVfc3R5bGluZyggYm9vdHN0cmFwX29wdGlvbnMgPSBjKCJzdHJpcGVkIiwgImhvdmVyIiwgImNvbmRlbnNlZCIpKSAlPiUKICBmb290bm90ZShnZW5lcmFsX3RpdGxlID0gIlxuIiwgZ2VuZXJhbCA9ICJUYWJsZSAxNCIpICAKCmBgYAoKIyMgQ2hlY2tpbmcgUHJlZGljdGlvbnMgCgpXZSBxdWlja2x5IHJhbiB0aGUgTW9yYW4ncyBJIGFnYWluIHRvIGNoZWNrIGZvciBzcGF0aWFsIGF1dG9jb3JyZWxhdGlvbiB1c2luZyBvdXIgZmluYWwgbW9kZWwgdGhhdCBhY2NvdW50IGZvciBuZWlnaGJvcmhvb2QgZWZmZWN0cy4gVGhlIGZpZ3VyZSBiZWxvdyBwbG90IHRoZSBmcmVxdWVuY3kgb2YgOTk5IHJhbmRvbWx5IHBlcm11dGF0ZWQgSSB1bmRlciBNb250ZSBDYXJsbyBzaW11bGF0aW9uLCB3aGljaCBzaG93cyB0aGF0IG91ciBvYnNlcnZlZCBJIGlzIHN0aWxsIGhpZ2hlciB0aGFuIGFsbCBvZiB0aGUgcmFuZG9tbHkgZ2VuZXJhdGVkIEkncywgYnV0IG11Y2ggc21hbGxlciB0aGFuIG91ciBwcmV2aW91cyBNb3JhbidzIEkgcGxvdCB3aGVuIG5laWdoYm9yaG9vZCBlZmZlY3QgaXMgbm90IGFjY291bnRlZCBmb3IuIFRoaXMgbWVhbnMgdGhhdCB0aGUgZXJyb3JzIG9mIG91ciBwcmVkaWN0aW9uIGFyZSBzdGlsbCBjbHVzdGVyZWQgaW4gc3BhY2UuIAoKYGBge3IgZmluYWwgbW9kZWwgR2xvYmFsIEcgc3RhdGlzdGljcywgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KCmNvb3Jkcy50ZXN0X24gPC0gIHN0X2Nvb3JkaW5hdGVzKHRyX25laWdoLnRlc3QpIApuZWlnaGJvckxpc3QudGVzdF9uIDwtIGtubjJuYihrbmVhcm5laWdoKGNvb3Jkcy50ZXN0X24sIDUpKQpzcGF0aWFsV2VpZ2h0cy50ZXN0X24gPC0gbmIybGlzdHcobmVpZ2hib3JMaXN0LnRlc3Rfbiwgc3R5bGU9IlciKQoKbW9yYW5UZXN0X24gPC0gbW9yYW4ubWModHJfbmVpZ2gudGVzdCRTYWxlUHJpY2UuRXJyb3IsIAogICAgICAgICAgICAgICAgICAgICAgc3BhdGlhbFdlaWdodHMudGVzdF9uLCBuc2ltID0gOTk5KQoKZ2dwbG90KGFzLmRhdGEuZnJhbWUobW9yYW5UZXN0X24kcmVzW2MoMTo5OTkpXSksIGFlcyhtb3JhblRlc3RfbiRyZXNbYygxOjk5OSldKSkgKwogIGdlb21faGlzdG9ncmFtKGJpbndpZHRoID0gMC4wMSwgZmlsbCA9ICIjMjgzZDNiIikgKwogIGdlb21fdmxpbmUoYWVzKHhpbnRlcmNlcHQgPSBtb3JhblRlc3RfbiRzdGF0aXN0aWMpLCBjb2xvdXIgPSAiI2M0NDUzNiIgLHNpemU9MSkgKwogIHNjYWxlX3hfY29udGludW91cyhsaW1pdHMgPSBjKC0wLjUsIDAuNSkpICsKICBsYWJzKHRpdGxlID0gIk9ic2VydmVkIGFuZCBQZXJtdXRlZCBNb3JhbidzIEkgLSBOZWlnaGJvcmhvb2QgRWZmZWN0IiwKICAgICAgIHN1YnRpdGxlID0gIk9ic2VydmVkIE1vcmFuJ3MgSSBpbiBSZWQiLAogICAgICAgeCA9ICJNb3JhbidzIEkiLAogICAgICAgeSA9ICJDb3VudCIpICsKICB0aGVtZV9saWdodCgpICsgICAKICB0aGVtZShwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSA5LGZhY2UgPSAiaXRhbGljIiksCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIsIGZhY2UgPSAiYm9sZCIpLCAKICAgICAgICBheGlzLnRleHQueD1lbGVtZW50X3RleHQoc2l6ZT02KSwKICAgICAgICBheGlzLnRleHQueT1lbGVtZW50X3RleHQoc2l6ZT02KSwgCiAgICAgICAgYXhpcy50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT04KSkKCmBgYAoKV2UgdGhlbiBwbG90dGVkIHRoZSBwcmVkaWN0ZWQgcHJpY2VzIGFzIGEgZnVuY3Rpb24gb2Ygb2JzZXJ2ZWQgc2FsZSBwcmljZXMuIFRoZSBncmVlbiBsaW5lIHJlcHJlc2VudHMgYSB3b3VsZC1iZSBwZXJmZWN0IGZpdCwgd2hpbGUgdGhlIGRhcmsgZ3JlZW4gbGluZSByZXByZXNlbnRzIHRoZSBwcmVkaWN0ZWQgZml0LiBXZSBzZWUgdGhhdCB3aXRoIHRoZSBhZGRpdGlvbiBvZiB0aGUgbmVpZ2hib3Job29kIGVmZmVjdCwgb3VyIG1vZGVsIGRvZXMgYSBwcmV0dHkgc2F0aXNmeWluZyBqb2IgYXQgcHJlZGljdGluZyB0aGUgc2FsZSBwcmljZSwgcGVyaGFwcyBieSBleHBsYWluaW5nIHBhcnQgb2YgdGhlIHNwYXRpYWwgcHJvY2VzcyBhbmQgbG9jYWwgYW1lbml0aWVzLiAKCgpgYGB7ciBvYnNlcnZlZCBhbmQgcHJlZGljdGVkIHNhbGUgcHJpY2UsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9Cgp0cl9uZWlnaC50ZXN0ICU+JQogIGRwbHlyOjpzZWxlY3QoU2FsZVByaWNlLlByZWRpY3QsIHNhbGVfcHJpY2UpICU+JQogIGdncGxvdChhZXMoc2FsZV9wcmljZSwgU2FsZVByaWNlLlByZWRpY3QpKSArCiAgZ2VvbV9wb2ludChjb2xvciA9ICIjYzQ0NTM2Iiwgc2l6ZSA9IDAuMykgKwogIHN0YXRfc21vb3RoKGFlcyhzYWxlX3ByaWNlLCBzYWxlX3ByaWNlKSwgCiAgICAgICAgICAgICBtZXRob2QgPSAibG0iLCBzZSA9IEZBTFNFLCBzaXplID0gMSwgY29sb3VyPSIjMTk3Mjc4IikgKyAKICBzdGF0X3Ntb290aChhZXMoU2FsZVByaWNlLlByZWRpY3QsIHNhbGVfcHJpY2UpLCAKICAgICAgICAgICAgICBtZXRob2QgPSAibG0iLCBzZSA9IEZBTFNFLCBzaXplID0gMSwgY29sb3VyPSIjMjgzZDNiIikgKwogIGxhYnModGl0bGU9IlByZWRpY3RlZCBTYWxlIFByaWNlIGFzIGEgRnVuY3Rpb24gb2YgT2JzZXJ2ZWQgUHJpY2UiLAogICAgICAgc3VidGl0bGU9IkdyZWVuIGxpbmUgcmVwcmVzZW50cyBhIHBlcmZlY3QgcHJlZGljdGlvbjsgZGFyayBncmVlbiBsaW5lIHJlcHJlc2VudHMgcHJlZGljdGlvbiIsCiAgICAgICB4ID0gIlNhbGUgUHJpY2UiLAogICAgICAgeSA9ICJQcmVkaWN0IFByaWNlIikgKwogICAgdGhlbWVfbGlnaHQoKSArICAgCnRoZW1lKHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDksZmFjZSA9ICJpdGFsaWMiKSwKICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgZmFjZSA9ICJib2xkIiksIAogICAgICAgIGF4aXMudGV4dC54PWVsZW1lbnRfdGV4dChzaXplPTYpLAogICAgICAgIGF4aXMudGV4dC55PWVsZW1lbnRfdGV4dChzaXplPTYpLCAKICAgICAgICBheGlzLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTgpKQoKYGBgCgpXZSBhbHNvIG1hcHBlZCB0aGUgZGlzdHJpYnV0aW9uIG9mIHNhbGUgcHJpY2UgZXJyb3Igb2Ygb3VyIHRlc3Rpbmcgc2V0IHVzaW5nIHRoZSBmaW5hbCBtb2RlbCwgZnJvbSB3aGljaCB3ZSBzZWUgdGhhdCBvdXIgcHJlZGljdGlvbiBlcnJvciBpcyBkaXN0cmlidXRlZCB1bmlmb3JtbHkgYWNyb3NzIHNwYWNlLCB3aXRoIG5vIHByZXNlbmNlIG9mIGV4dHJlbWVseSBiaWcgZXJyb3JzLCB3aGljaCBpcyBhIHNhdGlzZnlpbmcgb2JzZXJ2YXRpb24uIAoKYGBge3Igc2FscGUgcHJpY2UgZXJyb3IgbWFwfQpnZ3Bsb3QoKSsKICBnZW9tX3NmKGRhdGE9ZHN0LGZpbGw9JyNFNUU0RTInLGNvbG9yPSd0cmFuc3BhcmVudCcpKwogIGdlb21fc2YoZGF0YT10cl9uZWlnaC50ZXN0LGFlcyhjb2xvdXIgPSBTYWxlUHJpY2UuRXJyb3IpLHNpemU9MC41KSsKICBzY2FsZV9jb2xvcl9jb250aW51b3VzKGhpZ2ggPSAiI0ZBRjlGNiIsIGxvdyA9ICIjMTk3Mjc4IiwgbmFtZT0gIlNhbGUgUHJpY2UgRXJyb3IgIikgKwogIHRoZW1lKAogICAgICAgIGF4aXMudGV4dC54PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpY2tzLng9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGV4dC55PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpY2tzLnk9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDksZmFjZSA9ICJpdGFsaWMiKSwKICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgZmFjZSA9ICJib2xkIiksCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X3JlY3QoY29sb3VyID0gImdyZXkiLCBmaWxsPU5BLCBzaXplPTAuNCkKICAgICAgICApICsKICBsYWJzKHRpdGxlID0gIkRpc3RyaWJ1dGlvbiBvZiBTYWxlIFByaWNlIEVycm9yIFRlc3RpbmcgU2V0IikgCgpgYGAKCkZpbmFsbHksIHdlIG1hcHBlZCB0aGUgc2FsZSBwcmljZSBlcnJvciBhcyBhIGZ1bmN0aW9uIG9mIGxhZyBwcmljZSBlcnJvciwgd2hpY2ggaXMgdGhlIGF2ZXJhZ2UgcHJpY2Ugb2YgYSBob21lIHNhbGXigJlzIDUgbmVhcmVzdCBuZWlnaGJvcnMuIFRoZSBwbG90IHN1Z2dlc3RzIGEgcG9zaXRpdmUgY29ycmVsYXRpb24gYmV0d2VlbiBzYWxlIHByaWNlIGVycm9yIGFuZCBsYWcgcHJpY2UgZXJyb3IuIFRoZSBmYWN0IHRoYXQgcG9pbnRzIGFyZSBjb21wYWN0ZWQgYXJvdW5kIHRoZSBsaW5lIG9mIGJlc3QgZml0IG1lYW5zIHRoYXQgdGhlIGNvcnJlbGF0aW9uIGhlcmUgaXMgc3Ryb25nLiBIb3dldmVyLCB3ZSBkbyBzZWUgYSBjbHVzdGVyIG9mIHBvaW50cyB3aXRoIGJvdGggaGlnaCBzYWxlIHByaWNlIGVycm9yIGFuZCBsYWcgcHJpY2UgZXJyb3IuCgoKYGBge3Igc2FsZSBwcmljZSBlcnJvciBhbmQgbGFnZXJyb3IsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9Cgp0cl9uZWlnaC50ZXN0ICU+JSAKICBtdXRhdGUobGFnUHJpY2VFcnJvciA9IGxhZy5saXN0dyhzcGF0aWFsV2VpZ2h0cy50ZXN0X24sIFNhbGVQcmljZS5FcnJvcikpICU+JQogIGdncGxvdCgpKwogIGdlb21fcG9pbnQoYWVzKHggPWxhZ1ByaWNlRXJyb3IsIHkgPVNhbGVQcmljZS5FcnJvciksIGNvbG9yID0gIiNjNDQ1MzYiLCBzaXplID0gMC4zKSsKICBzdGF0X3Ntb290aChhZXMobGFnUHJpY2VFcnJvciwgU2FsZVByaWNlLkVycm9yKSwgCiAgICAgICAgICAgICBtZXRob2QgPSAibG0iLCBzZSA9IEZBTFNFLCBzaXplID0gMSwgY29sb3VyPSIjMTk3Mjc4IikgICsKICBsYWJzKHRpdGxlPSJTYWxlIFByaWNlIEVycm9yIGFzIGEgRnVuY3Rpb24gb2YgTGFnIFByaWNlIEVycm9yIiwKICAgICAgIHggPSAiTGFnIFByaWNlIEVycm9yIiwKICAgICAgIHkgPSAiU2FsZSBQcmljZSBFcnJvciIpICsKdGhlbWVfbGlnaHQoKSArICAgCnRoZW1lKHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDksZmFjZSA9ICJpdGFsaWMiKSwKICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgZmFjZSA9ICJib2xkIiksIAogICAgICAgIGF4aXMudGV4dC54PWVsZW1lbnRfdGV4dChzaXplPTYpLAogICAgICAgIGF4aXMudGV4dC55PWVsZW1lbnRfdGV4dChzaXplPTYpLCAKICAgICAgICBheGlzLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTgpKQpgYGAKClVwIHVudGlsIHRoaXMgcG9pbnQsIGl0IHNlZW1zIHRoYXQgb3VyIHRlc3RpbmcgZGF0YSBpcyBtYWtpbmcgYSBnb29kIHByZWRpY3Rpb24gb24gdGhlIGFjdHVhbCBzYWxlIHByaWNlLiBIb3dldmVyLCB0aGlzIG9ubHkgcGFydGlhbGx5IHByb3ZlcyB0aGF0IG91ciBtb2RlbCBpcyBlZmZlY3RpdmUgYXQgcHJlZGljdGluZyBwcm9wZXJ0eSB2YWx1ZSBvZiB0aGUgZW50aXJlIFBoaWxhZGVscGhpYS4gVGhlIHN0b3J5IGJlaGluZCBjb3VsZCBiZSB0aGF0IHRoZSA0MCUgb2YgZGF0YSBwb2ludHMgYmVpbmcgc2VsZWN0ZWQgdGhyb3VnaCBwYXJ0aXRpb24gYXMgdGhlIHRlc3RpbmcgZGF0YSBzZXQgZml0cyB3ZWxsIGludG8gdGhlIG1vZGVsIGNvaW5jaWRlbnRhbGx5LiBUbyBjaGVjayBmb3IgdGhlIGdlbmVyYWxpemFiaWxpdHkgb2Ygb3VyIG1vZGVsIHRvIHRoZSBlbnRpcmUgUGhpbGFkZWxwaGlhLCB3ZSB3b3VsZCBuZWVkIHRvIHBlcmZvcm0gYSBjcm9zcy12YWxpZGF0aW9uIHRlc3QuIAoKIyMgQ3Jvc3MgVmFsaWRhdGlvbiAKCk9uIHRoaXMgYmFzaXMsIHdlIGNvbmR1Y3RlZCAxMDAgY3Jvc3MtdmFsaWRhdGlvbnMgb2YgdGhlIG5laWdoYm9yaG9vZCBlZmZlY3QgbW9kZWwgd2l0aCAxOCBwcmVkaWN0b3IgdmFyaWFibGVzLiBUaGUgUk1TRSB2YWx1ZSBpcyAxMTYyNzgsIGluZGljYXRpbmcgdGhhdCBkZXNwaXRlIGJlaW5nIG91ciBiZXN0IHNvbHV0aW9uIHNvIGZhciwgdGhlIG1vZGVsJ3MgcHJlZGljdGlvbiBlcnJvciBjYW4gc3RpbGwgYmUgcmVsYXRpdmVseSBsYXJnZS4gVGhlIFItc3F1YXJlZCB2YWx1ZSBpcyAwLjY2NywgaW5kaWNhdGluZyBhIGdvb2QgbGV2ZWwgb2YgdmFyaWFuY2UgZXhwbGFuYXRpb24gYnkgdGhlIG1vZGVsLiAKCmBgYHtyIGNyb3NzIHZhbGlkYXRpb24sIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CgpmaXRDb250cm9sIDwtIHRyYWluQ29udHJvbChtZXRob2QgPSAiY3YiLCBudW1iZXIgPSAxMDApCnNldC5zZWVkKDQ4NSkKCnJlZy5jdiA8LSAKICB0cmFpbihzYWxlX3ByaWNlIH4gLiwgZGF0YSA9IHN0X2Ryb3BfZ2VvbWV0cnkobmVpZ2hib3JlZmZlY3QpICU+JSAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkcGx5cjo6c2VsZWN0KHNhbGVfcHJpY2UsIEFnZSwgbnVtUm9vbXMsIGhhc0Jhc2VtZW50LCBoYXNBQywgcXVhbGl0eSwgYnVpbGRpbmdkaXMsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhhc0ZpcmVwbGFjZSwgaGFzR2FyYWdlLCBzdG9yaWVzLCBsb2dhcmVhLCB2aWV3LCBESVNUX05BTUUsIGNyaW1lcy5CdWZmZXIsIHRvdGFsc3RvcHMsIGhvc3BpdGFsc19ubjEsIHNob3BzX25uMywgc2Nob29sX2J1ZmZlciwgbWVhbk1pbm9yKSwgbWV0aG9kID0gImxtIiwgdHJDb250cm9sID0gZml0Q29udHJvbCwgbmEuYWN0aW9uID0gbmEucGFzcykKCnJlZy5jdiRyZXNhbXBsZSAlPiUgCiAgc3VtbWFyaXNlKE1BRSA9IG1lYW4ocmVnLmN2JHJlc2FtcGxlWywzXSksCiAgICAgICAgICAgIHNkKHJlZy5jdiRyZXNhbXBsZVssM10pCikgJT4lCiAga2JsKGNvbC5uYW1lPWMoJ01lYW4gQWJzb2x1dGUgRXJyb3InLCdTdGFuZGFyZCBEZXZpYXRpb24gb2YgTUFFJykpICU+JQogIGthYmxlX3N0eWxpbmcoYm9vdHN0cmFwX29wdGlvbnMgPSBjKCJzdHJpcGVkIiwgImhvdmVyIiwgImNvbmRlbnNlZCIpKSAlPiUKICBmb290bm90ZShnZW5lcmFsX3RpdGxlID0gIlxuIiwgZ2VuZXJhbCA9ICJUYWJsZSAxNSIpCmBgYAoKT24gYXZlcmFnZSwgdGhlIG1vZGVsJ3MgTWVhbiBBYnNvbHV0ZSBFcnJvciBpcyBhcHByb3hpbWF0ZWx5IDc2LDAxOSwgd2l0aCBhIHN0YW5kYXJkIGRldmlhdGlvbiBvZiA1LDc4Mi4gVGhpcyBzdWdnZXN0cyB0aGF0IHdoaWxlIHRoZXJlIGlzIHNvbWUgZGlzcGVyc2lvbiwgdGhlIE1BRSBpcyBnZW5lcmFsbHkgY29uY2VudHJhdGVkIGFyb3VuZCB0aGUgbWVhbi4KCmBgYHtyIGhpc3RvZ3JhbSBNQUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CnJlZy5jdiRyZXNhbXBsZSAlPiUgCmdncGxvdChhZXMoeD1yZWcuY3YkcmVzYW1wbGVbLDNdKSkgKwogICAgZ2VvbV9oaXN0b2dyYW0oIGZpbGw9IiMxOTcyNzgiLCBjb2xvcj0iI2U5ZWNlZiIpICsgCiAgICBsYWJzKHRpdGxlPSJEaXN0cmlidXRpb24gb2YgTWVhbiBBYnNvbHV0ZSBFcnJvciBmb3IgT3ZlcmFsbCBQcmVkaWN0aW9uIiwKICAgICAgIHggPSAiTWVhbiBBYnNvbHV0ZSBFcnJvciIsCiAgICAgICB5ID0gIkNvdW50IikgKwp0aGVtZV9saWdodCgpICsgICAKdGhlbWUocGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gOSxmYWNlID0gIml0YWxpYyIpLAogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSwgCiAgICAgICAgYXhpcy50ZXh0Lng9ZWxlbWVudF90ZXh0KHNpemU9NiksCiAgICAgICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF90ZXh0KHNpemU9NiksIAogICAgICAgIGF4aXMudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9OCkpCmBgYAoKCiMgT3ZlcmFsbCBQcmVkaWN0aW9uCgpGb3Igb3VyIG92ZXJhbGwgcHJlZGljdGlvbiwgd2Ugc2VlIHRoYXQgaG91c2UgcHJpY2VkIGFib3ZlIFwkMSBtaWxsaW9uIGFyZSBtYWlubHkgY29uY2VudHJhdGVkIGluIHRoZSBub3J0aGVybiwgbm9ydGh3ZXN0ZXJuLCBhbmQgY2VudHJhbCBwYXJ0cyBvZiBQaGlsYWRlbHBoaWEuIFRoZSBub3J0aGVybiBhcmVhLCBpbiBwYXJ0aWN1bGFyLCBoYXMgYSBkZW5zZXIgY29uY2VudHJhdGlvbiBvZiBoaWdoLXByaWNlZCBob21lcywgd2hpbGUgdGhlIGNlbnRyYWwgYW5kIHdlc3Rlcm4gYXJlYXMgaGF2ZSBhIG1vcmUgZGlzcGVyc2VkIGRpc3RyaWJ1dGlvbi4gTW9zdCBob3VzZXMgd2l0aCB2YWx1ZXMgcmFuZ2luZyBmcm9tIFwkNzUwLDAwMCB0byBcJDEgbWlsbGlvbiBhcmUgbG9jYXRlZCBpbiB0aGUgc291dGhlcm4gYW5kIG5vcnRoZWFzdGVybiBhcmVhcywgd2l0aCBhIGZldyBzY2F0dGVyZWQgaW4gdGhlIG5vcnRod2VzdGVybiByZWdpb24sIG92ZXJsYXBwaW5nIHdpdGggdGhlIGRpc3RyaWJ1dGlvbiByYW5nZSBvZiBob21lcyBwcmljZWQgYWJvdmUgXCQxIG1pbGxpb24uIEhvdXNlcyBpbiB0aGUgXCQ1MDAsMDAwIHRvIFwkNzUwLDAwMCByYW5nZSBhcmUgcHJpbWFyaWx5IGZvdW5kIGluIHRoZSB3ZXN0ZXJuLCBzb3V0aHdlc3Rlcm4sIGFuZCBub3J0aGVhc3Rlcm4gcGFydHMgb2YgdGhlIGNpdHkuIEluIHRoZSBzb3V0aHdlc3Rlcm4gYW5kIG5vcnRoZWFzdGVybiBhcmVhcywgdGhlc2UgaG9tZXMgYXJlIGNsb3NlciB0byB0aGUgY2l0eSBjZW50ZXIgY29tcGFyZWQgdG8gaGlnaGVyLXByaWNlZCBvbmVzLiBJbiB0aGUgd2VzdGVybiBhcmVhLCB0aGV5IHRlbmQgdG8gYmUgbW9yZSByZW1vdGUuIEhvdXNlcyB2YWx1ZWQgYmV0d2VlbiBcJDI1MCwwMDAgYW5kIFwkNTAwLDAwMCBhcmUgbWFpbmx5IGxvY2F0ZWQgaW4gdGhlIGNlbnRyYWwgcGFydCBvZiB0aGUgY2l0eSwgc3Vycm91bmRlZCBieSBoaWdoZXItcHJpY2VkIGhvdXNlcy4gSG91c2VzIHByaWNlZCBiZWxvdyBcJDI1MCwwMDAgZXhoaWJpdCBzdHJvbmdlciBjbHVzdGVyaW5nIGNoYXJhY3RlcmlzdGljcyBhbmQgYXJlIHByaW1hcmlseSBjbHVzdGVyZWQgaW4gdGhlIGVhc3Rlcm4gYW5kIHNvdXRod2VzdGVybiBwYXJ0cyBvZiB0aGUgY2l0eSwgd2l0aCBhbm90aGVyIHBvcnRpb24gc2NhdHRlcmVkIGluIHRoZSBub3J0aHdlc3Rlcm4gYXJlYSwgb3ZlcmxhcHBpbmcgd2l0aCB0aGUgZGlzdHJpYnV0aW9uIHJhbmdlIG9mIGhvdXNlcyBwcmljZWQgYmV0d2VlbiBcJDI1MCwwMDAgYW5kIFwkNTAwLDAwMC4gSW4gc3VtbWFyeSwgUGhpbGFkZWxwaGlhJ3MgcHJlZGljdGVkIGhvdXNpbmcgcHJpY2VzIGFyZSBnZW5lcmFsbHkgaGlnaGVyIGluIHRoZSBub3J0aGVhc3Rlcm4sIG5vcnRod2VzdGVybiwgYW5kIHNvdXRoZXJuIHBhcnRzIG9mIHRoZSBjaXR5LCB3aGlsZSB0aGUgY2VudHJhbCBjaXR5IGFyZWEgdGVuZHMgdG8gaGF2ZSBsb3dlci1wcmljZWQgaG91c2VzLiBJbiB0ZXJtcyBvZiB0aGUgbm9ydGgtc291dGggZGlyZWN0aW9uLCB0aGVyZSBpcyBhIGhpZ2hlciBjb25jZW50cmF0aW9uIG9mIGhpZ2gtcHJpY2VkIGhvdXNlcyBpbiB0aGUgbm9ydGhlcm4gcmVnaW9uIGNvbXBhcmVkIHRvIHRoZSBzb3V0aGVybiBhcmVhLiBJbiB0ZXJtcyBvZiB0aGUgZWFzdC13ZXN0IGRpcmVjdGlvbiwgdGhlIGVhc3Rlcm4gcGFydCBvZiB0aGUgY2l0eSBoYXMgYSBoaWdoZXIgY29uY2VudHJhdGlvbiBvZiBoaWdoLXByaWNlZCBob3VzZXMgY29tcGFyZWQgdG8gdGhlIHdlc3Rlcm4gYXJlYS4KCmBgYHtyIGZ1bGwgZGF0YXNldCBwcmVkaWN0aW9ufQoKUHJlZGljdEFsbFZhbCA8LSB0cl9uZWlnaCAlPiUgCiAgbXV0YXRlKHByZWRpY3Rpb24gPSBwcmVkaWN0KHJlZy50cmFpbiwgdHJfbmVpZ2gpKSAKClByZWRpY3RBbGxWYWwgPC0gUHJlZGljdEFsbFZhbCAlPiUgCiAgbXV0YXRlKHByZWRpY3Rpb25fY2xhc3MgPSBjdXQocHJlZGljdGlvbiwgYnJlYWtzID0gYygwLCAyNTAwMDAsIDUwMDAwMCwgNzUwMDAwLCAxMDAwMDAwLCBtYXgocHJlZGljdGlvbiwgbmEucm09VFJVRSkpKSkKCgoKZ2dwbG90KCkrCiAgICBnZW9tX3NmKGRhdGE9ZHN0LCBmaWxsPSdncmV5ODAnLGNvbG9yPSd0cmFuc3BhcmVudCcpKwogICAgZ2VvbV9zZihkYXRhPVByZWRpY3RBbGxWYWwsIHNpemU9MC43NSxhZXMoY29sb3VyID0gcTUocHJlZGljdGlvbl9jbGFzcykpKSsKICAgIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBwYWxldHRlLAogICAgICAgICAgICAgICAgICAgIG5hbWUgPSAiU2FsZXMgUHJpY2UiLAogICAgICAgICAgICAgICAgICAgIG5hLnZhbHVlID0gJ2dyZXk4MCcsCiAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gYygnMC0yNTBrJywgJzI1MGstNTAwaycsICc1MDBrLTc1MGsnLCAnNzUway0xbScsICcxbSBNb3JlJykpICsKICAgdGhlbWUoCiAgICAgICAgYXhpcy50ZXh0Lng9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGlja3MueD1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGlja3MueT1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIsIGZhY2UgPSAiYm9sZCIpLAogICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9yZWN0KGNvbG91ciA9ICJncmV5IiwgZmlsbD1OQSwgc2l6ZT0wLjQpCiAgICAgICAgKSArCiAgbGFicyh0aXRsZSA9ICJEaXN0cmlidXRpb24gb2YgUHJlZGljdGVkIFNhbGUgUHJpY2UgaW4gUGhpbGFkZWxwaGlhIikgCgpgYGAKCldlIHRlc3RlZCBvdXIgbW9kZWwncyBnZW5lcmFsaXphYmlsaXR5IGFjcm9zcyBkaWZmZXJlbnQgcmFjaWFsIGdyb3VwcyBpbiBQaGlsYWRlbHBoaWEuIAoKYGBge3IgbWFwIGJhc2VkIG9uIHJhY2V9CgpnZ3Bsb3QoKSArIGdlb21fc2YoZGF0YSA9IG5hLm9taXQodHJhY3RzMjApLCBhZXMoZmlsbCA9IG1ham9yaXR5KSwgY29sb3IgPSBOQSkgKwogICAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gYygiI2M0NDUzNiIsICIjMjgzZDNiIiksIG5hbWU9IlJhY2UgQ29udGV4dCIpICsKICAgIGxhYnModGl0bGUgPSAiUmFjZSBDb250ZXh0IGZvciBQaGlsYWRlbHBoaWEgQ2Vuc3VzIFRyYWN0cyIsCiAgICAgICAgIGNhcHRpb24gPSAiRGF0YTogQW1lcmljYW4gQ29tbXVuaXR5IFN1cnZleSAyMDIwIikgKwogICAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJib3R0b20iKSArCiAgdGhlbWUoCiAgICAgICAgYXhpcy50ZXh0Lng9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGlja3MueD1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGlja3MueT1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIsIGZhY2UgPSAiYm9sZCIpLAogICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9yZWN0KGNvbG91ciA9ICJncmV5IiwgZmlsbD1OQSwgc2l6ZT0wLjQpCiAgICAgICAgKSAKCmBgYAoKQWNjb3JkaW5nIHRvIHRoZSB0ZXN0IHJlc3VsdHMgZm9yIHRoZSBwcmVkaWN0aW9uIG9mIGhvdXNlIHByaWNlcyBpbiBtYWpvcml0eSBhbmQgbWlub3JpdHkgY29tbXVuaXRpZXMgYnkgdGhlIG5laWdoYm9yaG9vZCBlZmZlY3QgbW9kZWwsIHRoZSBNQVBFIGZvciBwcmVkaWN0aW5nIG1ham9yaXR5IGNvbW11bml0aWVzIGlzIDI2JSwgd2hpbGUgdGhlIE1BUEUgZm9yIHByZWRpY3RpbmcgbWlub3JpdHkgY29tbXVuaXRpZXMgaXMgb25seSAzJS4gQWx0aG91Z2ggdGhlcmUgaXMgYSBzaWduaWZpY2FudCBkZXZpYXRpb24gaW4gcHJlZGljdGluZyBob3VzZSBwcmljZXMgaW4gdGhlIEtlbnNpbmd0b24gY29tbXVuaXR5IGNvbXBhcmVkIHRvIGFjdHVhbCBwcmljZXMsIHRoaXMgaW5kaWNhdGVzIHRoYXQgdGhlIG1vZGVsIGhhcyBsb3dlciBhY2N1cmFjeSBpbiBwcmVkaWN0aW5nIGhvdXNlIHByaWNlcyBpbiBtYWpvcml0eSBjb21tdW5pdGllcyBhbmQgaGlnaGVyIGdlbmVyYWxpdHkgaW4gcHJlZGljdGluZyBob3VzZSBwcmljZXMgaW4gbW9zdCBtaW5vcml0eSBjb21tdW5pdGllcywgZXhjbHVkaW5nIEtlbnNpbmd0b24uCgpgYGB7ciBwcmVkaWN0aW9uIGJhc2VkIG9uIHJhY2V9CgpzdF9qb2luKHRyX25laWdoLnRlc3QsIHRyYWN0czIwKSAlPiUgCiAgZmlsdGVyKCFpcy5uYShtYWpvcml0eSkpICU+JQogIGdyb3VwX2J5KG1ham9yaXR5KSAlPiUKICBzdW1tYXJpemUobWVhbi5NQVBFID0gc2NhbGVzOjpwZXJjZW50KG1lYW4oU2FsZVByaWNlLkFQRSwgbmEucm0gPSBUKSkpICU+JQogIHN0X2Ryb3BfZ2VvbWV0cnkoKSAlPiUKICBzcHJlYWQobWFqb3JpdHksIG1lYW4uTUFQRSkgJT4lCiAga2JsKGNvbC5uYW1lPWMoJ01ham9yaXR5IFRyYWN0cycsJ01pbm9yaXR5IFRyYWN0cycpKSAlPiUKICBrYWJsZV9zdHlsaW5nKGJvb3RzdHJhcF9vcHRpb25zID0gYygic3RyaXBlZCIsICJob3ZlciIsICJjb25kZW5zZWQiKSkgJT4lCiAgZm9vdG5vdGUoZ2VuZXJhbF90aXRsZSA9ICJcbiIsIGdlbmVyYWwgPSAiVGFibGUgMTYiKQoKYGBgCgoKRmluYWxseSwgd2UgdGVzdGVkIG91ciBtb2RlbCdzIGdlbmVyYWxpemFiaWxpdHkgYWNyb3NzIGRpZmZlcmVudCBuZWlnaGJvcmhvb2RzIGluIFBoaWxhZGVscGhpYS4gQWZ0ZXIgY2hlY2tpbmcgdGhlIG1lYW4gYWJzb2x1dGUgcGVyY2VudGFnZSBlcnJvcnMgb2YgcHJvcGVydHkgdmFsdWVzIGZvciBlYWNoIG5laWdoYm9yaG9vZCwgd2UncmUgc3VycHJpc2VkIHRvIG5vdGljZSB0aGF0IG91ciBtb2RlbCBoYWQgcHJlZGljdGVkIGEgY291cGxlIG9mIHNhbGUgcHJpY2UgdG8gYmUgbmVnYXRpdmUuIE9uIHRvcCBvZiB0aGF0LCB3ZSBmb3VuZCB0aGF0IHRoZSBtYWpvcml0eSBvZiB0aG9zZSBob3VzZXMgcHJlZGljdGVkIHRvIGhhdmUgbmVnYXRpdmUgc2FsZSBwcmljZSBhcmUgY29uY2VudHJhdGVkIGluIHRoZSBLZW5zaW5ndG9uIE5laWdoYm9yaG9vZC4gV2hpbGUgaXQgaXMgcG9zc2libGUgdGhhdCBPTFMgcmVncmVzc2lvbiBtb2RlbHMgY2FuIGJlIG9mZiBzb21ldGltZSBhdCBwcmVkaWN0aW5nIHNhbGUgcHJpY2Ugb2YgaG91c2VzIHdpdGggY2VydGFpbiB1bmlxdWUgY2hhcmFjdGVyaXN0aWNzLCBzdWNoIGNvbnRpbnVvdXMgc3RyZWFtcyBvZiBlcnJvcnMgY29uY2VudHJhdGVkIGEgc2luZ2xlIG5laWdoYm9yaG9vZCBzdWdnZXN0IHNvbWUgc3lzdGVtYXRpYyBwcm9ibGVtcyB3aXRoIG91ciBtb2RlbC4gCgpUaGUgS2Vuc2luZ3RvbiBuZWlnaGJvcmhvb2QgaGFzIGZhY2VkIGEgdmFyaWV0eSBvZiBjaGFsbGVuZ2VzLCBwYXJ0aWN1bGFybHkgY29uY2VybmluZyBwb3ZlcnR5LCBkcnVnIGFkZGljdGlvbiwgYW5kIGNyaW1lLiBBIHNpZ25pZmljYW50IHBvcnRpb24gb2YgS2Vuc2luZ3RvbidzIHBvcHVsYXRpb24gaGF2ZSBsaW1pdGVkIGFjY2VzcyB0byBlY29ub21pYyBvcHBvcnR1bml0aWVzLCBxdWFsaXR5IGVkdWNhdGlvbiwgYW5kIGhlYWx0aGNhcmUgcmVzb3VyY2VzLiBIaWdoIHJhdGVzIG9mIHVuZW1wbG95bWVudCBhbmQgdW5kZXJlbXBsb3ltZW50IGhhdmUgY29udHJpYnV0ZWQgdG8gZmluYW5jaWFsIGluc3RhYmlsaXR5IGZvciBtYW55IGZhbWlsaWVzIGluIHRoZSBhcmVhLiBDb25zaWRlcmluZyB0aGF0IG91ciBmaW5hbCBtb2RlbCBoYXZlIHRha2VuIGNyaW1lIHJhdGUsIHByb3hpbWl0eSB0byBzY2hvb2xzLCBob3NwaXRhbHMsIGFuZCB0cmFuc2l0IHN0b3BzIGludG8gYWNjb3VudCwgaXQgaXMgcG9zc2libGUgdGhhdCBvdXIgbW9kZWwgaGFzIHNpZ25pZmljYW50bHkgZGV2YWx1ZWQgcHJvcGVydGllcyBhdCBuZWlnaGJvcmhvb2QgZmFjaW5nIHNldmVyZSBzb2Npby1lY29ub21pYyBjaGFsbGVuZ2VzIGxpa2UgS2Vuc2luZ3Rvbi4gCgpMb29raW5nIGF0IHRoZSBvdmVyYWxsIE1BUEUgaXMgbm90IGVub3VnaCB0byBkZXRlcm1pbmUgdGhlIG1vZGVsJ3MgYWNjdXJhY3kgYXMgaXQgY291bGQgbWFzayB2YXJpYXRpb25zIGF0IGEgbW9yZSBncmFudWxhciBzcGF0aWFsIHNjYWxlLCBmb3IgZXhhbXBsZSB0aGUgbmVpZ2hib3Job29kLiBPdXIgbW9kZWwgaGFzIGEgcHJldHR5IGxvdyBNQVBFLCBidXQgd2UgaGF2ZSB0byBiZSBhd2FyZSB0aGF0IHRob3NlIG5lZ2F0aXZlLCBlcnJvbmVvdXMgc2FsZSBwcmljZXMgYXJlIGFsc28gY29udHJpYnV0aW5nIHRvIHRoZSBsb3cgTUFQRS4gSWYgd2UgaGFkIHRoZSByaWdodCBwcmVkaWN0aW9uIG9mIHNhbGUgcHJpY2UgZm9yIHRoZXNlIGhvdXNlcywgb3VyIE1BUEUgc2hvdWxkIGJlIGhpZ2hlci4gVGhlcmVmb3JlLCBmb3IgdGhlIG1hcCBiZWxvdywgd2UgaGF2ZSByZW1vdmVkIEtlbnNpbmd0b24gbmVpZ2hib3Job29kIG9uIHB1cnBvc2UgZm9yIGl0IGRvZXMgbm90IG1ha2UgYW55IHNlbnNlIGZvciBhIG5laWdoYm9yaG9vZCB0byBoYXZlIGEgbmVnYXRpdmUgYWJzb2x1dGUgcGVyY2VudGFnZSBlcnJvciBpbiBzYWxlIHByaWNlLiAKCmBgYHtyIHByZWRpY3Rpb24gYmFzZWQgb24gbmVpZ2hib3Job29kIG1hcCwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KCnRvX3Bsb3QgPC0gc3RfaW50ZXJzZWN0aW9uKHRyX25laWdoLnRlc3QsIFBoaWxseSAlPiUgZHBseXI6OnNlbGVjdCgiTkFNRSIpKSAlPiUgCiAgc3RfZHJvcF9nZW9tZXRyeSgpICU+JSAKICBncm91cF9ieShOQU1FKSAlPiUKICBzdW1tYXJpc2UobWVhbi5NQVBFID0gbWVhbihTYWxlUHJpY2UuQVBFLCBuYS5ybSA9IFQpKSAlPiUgCiAgbGVmdF9qb2luKFBoaWxseSkgJT4lIAogIHN0X3NmKCkKdG9fcGxvdCAlPiUKICBmaWx0ZXIoTkFNRSAhPSAiS0VOU0lOR1RPTiIpICU+JSAKICBnZ3Bsb3QoKSArIAogICAgICBnZW9tX3NmKGFlcyhmaWxsID0gbWVhbi5NQVBFKSkgKwogIHNjYWxlX2ZpbGxfY29udGludW91cyhsb3cgPSAiZ3JleTgwIiwgaGlnaCA9ICIjNzcyZTI1IiwgbmFtZT0gIk1BUEUiKSArCiAgdGhlbWUoCiAgICAgICAgYXhpcy50ZXh0Lng9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGlja3MueD1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGlja3MueT1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIsIGZhY2UgPSAiYm9sZCIpLAogICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9yZWN0KGNvbG91ciA9ICJncmV5IiwgZmlsbD1OQSwgc2l6ZT0wLjQpCiAgICAgICAgKSArCiAgbGFicyh0aXRsZSA9ICJNZWFuIEFic29sdXRlIFBlcmNlbnRhZ2UgRXJyb3IgQnkgTmVpZ2hib3Job29kIikgCgpgYGAKCkhlcmUsIHdlIGNhbiBhbHNvIHNlZSB0aGF0IGVycm9ycyBpbiBzYWxlIHByaWNlIHByZWRpY3Rpb24gdGVuZCB0byBiZSBsYXJnZXIgd2hlbiB0aGUgc2FsZSBwcmljZSBpdHNlbGYgaXMgYWxyZWFkeSBsb3cuIFNpbmNlIHdlIGtub3cgdGhhdCBob3VzZXMgd2l0aCBsb3cgc2FsZSBwcmljZSBhcmUgY2x1c3RlcmVkIGluIGFyZWFzIHdoZXJlIHRoZXJlJ3MgaGlnaGVyIGNyaW1lIHJhdGUsIG1pbm9yaXR5IHBvcHVsYXRpb24gYXMgd2VsbCBhcyBwb29yZXIgcHVibGljIHNlcnZpY2VzIG5lYXJieSwgdGhpcyBwcm92ZXMgb3VyIGNvbmNlcm4gdGhhdCBvdXIgbW9kZWwgdGVuZHMgdG8gZGV2YWx1ZSBwcm9wZXJ0eSB2YWx1ZXMgaW4gdGhlc2UgYXJlYXMuIAoKYGBge3IgcHJlZGljdGlvbiBiYXNlZCBvbiBuZWlnaGJvcmhvb2QgcGxvdH0KdHJfbmVpZ2gudGVzdFBoaWxseSA8LSBzdF9qb2luKHRyX25laWdoLnRlc3QsIFBoaWxseSAlPiUgc2VsZWN0KE5BTUUpKSAlPiUgCiAgZ3JvdXBfYnkoTkFNRSkgJT4lCiAgc3VtbWFyaXNlKG1lYW4uTUFQRSA9IG1lYW4oU2FsZVByaWNlLkFQRSwgbmEucm0gPSBUKSkgJT4lIAogIG11dGF0ZShzdF9qb2luKHRyX25laWdoLnRlc3QsIFBoaWxseSAlPiUgc2VsZWN0KE5BTUUpKSAlPiUgZ3JvdXBfYnkoTkFNRSkgJT4lCiAgc3VtbWFyaXNlKG1lYW4uUHJpY2UgPSBtZWFuKHNhbGVfcHJpY2UsIG5hLnJtID0gVCkpKQoKdHJfbmVpZ2gudGVzdFBoaWxseSAlPiUgZmlsdGVyKE5BTUUgIT0gIktFTlNJTkdUT04iKSAlPiUKICBnZ3Bsb3QoYWVzKG1lYW4uTUFQRSxtZWFuLlByaWNlKSkgKwogIGdlb21fcG9pbnQoY29sb3IgPSAiI2M0NDUzNiIsIHNpemUgPSAwLjggKSArCiAjIGdlb21fc21vb3RoKG1ldGhvZD0nbG0nLCBmb3JtdWxhPSB5fngsIGx3ZD0wLjUsIGNvbG9yID0gIiMxOTcyNzgiKSArCiAgICBsYWJzKHRpdGxlPSJTYWxlIFByaWNlIGFzIGEgRnVuY3Rpb24gb2YgTUFQRSBieSBOZWlnaGJvcmhvb2QiLAogICAgICAgeCA9ICJNQVBFIiwKICAgICAgIHkgPSAiU2FsZSBQcmljZSIpICsKICB0aGVtZV9saWdodCgpICsgICAKICB0aGVtZShwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSA5LGZhY2UgPSAiaXRhbGljIiksCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIsIGZhY2UgPSAiYm9sZCIpLCAKICAgICAgICBheGlzLnRleHQueD1lbGVtZW50X3RleHQoc2l6ZT02KSwKICAgICAgICBheGlzLnRleHQueT1lbGVtZW50X3RleHQoc2l6ZT02KSwgCiAgICAgICAgYXhpcy50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT04KSkKYGBgCgoKYGBge3Igd3JpdGUgY3N2LCBpbmNsdWRlPUZBTFNFfQpjaGFsbGVuZ2UgPC0gdHJfbmVpZ2ggJT4lIAogIGZpbHRlcih0b1ByZWRpY3QgPT0gIkNIQUxMRU5HRSIpCgpQcmljZVByZWRpY3Rpb24gPC0KICBzdF9kcm9wX2dlb21ldHJ5KGNoYWxsZW5nZSklPiUKICBtdXRhdGUocHJlZGljdGlvbiA9IHByZWRpY3QocmVnLnRyYWluLCBjaGFsbGVuZ2UpLAogICAgICAgICB0ZWFtID0gIkh1c2t5IiklPiUKICBkcGx5cjo6c2VsZWN0KG11c2FJRCwgcHJlZGljdGlvbiwgdGVhbSkKCndyaXRlLmNzdihQcmljZVByZWRpY3Rpb24sIlJlZ3Jlc3NUb0ltcHJlc3MuY3N2Iiwgcm93Lm5hbWVzID0gRkFMU0UpCmBgYAoKIyBDb25jbHVzaW9uCgpJbiBjb25jbHVzaW9uLCBvdXIgbW9kZWwgaXMgZWZmZWN0aXZlIGluIHByZWRpY3RpbmcgcHJvcGVydHkgdmFsdWVzIGluIHRoZSBjaXR5IG9mIFBoaWxhZGVscGhpYSBvbmx5IHRvIGEgY2VydGFpbiBleHRlbnQuIFdoaWxlIHdlIGFyZSBhYmxlIHRvIGZpdCBhIG1vZGVsIHRoYXQgYWNjb3VudCBmb3IgdmFyaWF0aW9ucyBvZiBzYWxlIHByaWNlIGluIHRlcm1zIG9mIHRoZWlyIGludGVybmFsIGNoYXJhY3RlcmlzdGljcywgc3BhdGlhbCBsb2NhdGlvbnMsIGFzIHdlbGwgYXMgbmVhcmJ5IGFtZW5pdGllcywgdGhpcyBtb2RlbCBsYWNrcyBnZW5lcmFsaXphYmlsaXR5IGFuZCB0ZW5kcyB0byBkZXZhbHVlIGNlcnRhaW4gcHJvcGVydGllcyBpbiB0aGUgY2l0eSwgZXNwZWNpYWxseSBmb3IgaG91c2VzIGluIGxvd2VyLWluY29tZSBjb21tdW5pdGllcywgc3VjaCBhcyB0aGUgS2Vuc2luZ3RvbiBuZWlnaGJvcmhvb2QuIFRoZSByZWFzb24gYmVoaW5kIHRoaXMgZXJyb3IgaXMgcHJvYmFibHkgYmVjYXVzZSBvdXIgbW9kZWwgY29udGFpbnMgcHJlZGljdG9ycyBzdWNoIGFzIG5lYXJieSBjcmltZSBpbmNpZGVudHMsIHNjaG9vbCBsb2NhdGlvbnMsIHRyYW5zaXQgc3RvcHMsIGV0Yy4gV2hpbGUgY3JpbWUgaW5jaWRlbnRzIGFyZSBwcmV2YWxlbnQgaW4gS2Vuc2luZ3RvbiwgdGhlIGxhdHRlciB0d28gcHJlZGljdG9ycyBoYXZlIGhpc3RvcmljYWxseSBiZWVuIGluYWNjZXNzaWJsZSBuZWlnaGJvcmhvb2RzIGxpa2UgdGhhdC4gCgpTb21lIGZ1cnRoZXIgb25saW5lIHJlc2VhcmNoIGludG8gbWlncmF0aW9uIGFuZCBnZW50cmlmaWNhdGlvbiBwYXR0ZXJucyBpbiBQaGlsYWRlbHBoaWEgcmV2ZWFscyB0aGF0IGEgbG90IG9mIGZhbWlsaWVzIGNob29zZSB0byBsZWF2ZSBQaGlsYWRlbHBoaWEgYWZ0ZXIgdGhlaXIga2lkcyByZWFjaGVkIHNjaG9vbCBhZ2UgaW4gb3JkZXIgdG8gc2VlayBmb3IgYmV0dGVyIHByaW1hcnkgc2Nob29scy4gSW4gYWRkaXRpb24sIG91ciBwcmV2aW91cyByZXBvcnQgaW52ZXN0aWdhdGluZyB0cmFuc2l0IG9yaWVudGVkIGRldmVsb3BtZW50IHN1Z2dlc3RzIHRoYXQgcHJveGltaXR5IHRvIHB1YmxpYyB0cmFuc3BvcnQgc3lzdGVtcyBwbGF5cyBhIHNpZ25pZmljYW50IHJvbGUgaW4gYWZmZWN0aW5nIHBlb3BsZSdzIGRlY2lzaW9uIG9uIHdoZXJlIHRvIGxpdmUuIEZ1cnRoZXIsIHN0dWRpZXMgYnkgQ2l0eSBvZiBQaGlsYWRlbHBoaWEncyBPZmZpY2Ugb2YgQ29udHJvbGxlciBzdWdnZXN0cyB0aGF0IG9uIGF2ZXJhZ2UsIHNhbGVzIHRoYXQgb2NjdXIgY2xvc2VyIHRvIGEgaG9taWNpZGUgdGVuZCB0byBoYXZlIGxvd2VyIHByaWNlcyB0aGFuIHNhbGVzIHRoYXQgb2NjdXIgZmFydGhlciBmcm9tIGEgaG9taWNpZGU6IGEgcmVkdWN0aW9uIG9mIG9uZSBob21pY2lkZSB3b3VsZCBsZWFkIHRvIGEgY29ycmVzcG9uZGluZyAyLjMgcGVyY2VudCBpbmNyZWFzZSBvZiBzYWxlIHByaWNlcyBpbiB0aGUgaW1tZWRpYXRlIG5laWdoYm9yaG9vZC4gQWxsIG9mIHRoZXNlIGV2aWRlbmNlIHN1Z2dlc3RzIHRoYXQgdGhlIGFic2VuY2Ugb2YgY3JpdGljYWwgcHVibGljIGFtZW5pdGllcy9zZXJ2aWNlIGFzIHdlbGwgYXMgaGlnaCBjb25jZW50cmF0aW9uIG9mIGNyaW1lIGluY2lkZW50cyB3b3VsZCB0b2dldGhlciBjb250cmlidXRlIHRvIGhvdXNpbmcgZGlzaW52ZXN0bWVudC4gQW5kIG92ZXItZml0dGluZyBhIG1vZGVsIHdpdGggdGhvc2Uga2luZCBvZiBwcmVkaWN0b3JzIG1pZ2h0IGxlYWQgdG8gc2V2ZXJlIGRldmFsdWF0aW9uIG9mIHByb3BlcnR5IHZhbHVlcy4gCgpBdCB0aGlzIHBvaW50LCB0aGVyZSdzIHN0aWxsIHNvbWUgd29yayB0byBiZSBkb25lIGJlZm9yZSB3ZSByZWNvbW1lbmQgdGhpcyBtb2RlbCB3aXRoIFppbGxvdy4gSG93ZXZlciwgb3VyIHJlcG9ydCBkb2VzIGZpbmQgYSB2YXJpZXR5IG9mIHNpZ25pZmljYW50IHByZWRpY3RvciB2YXJpYWJsZXMgdGhhdCBjYW4gZXhwbGFpbiB0aGUgdmFyaWFiaWxpdHkgb2YgcHJvcGVydHkgdmFsdWVzIGluIFBoaWxhZGVscGhpYS4gVGhpcyBjb3VsZCBwcm92aWRlIHZhbHVhYmxlIGluc2lnaHRzIGZvciBsb2NhbCByZWFsIGVzdGF0ZSBtYXJrZXQgd2hlbiBkZXRlcm1pbmluZyBzYWxlIHByaWNlIGZvciBob3VzZXMuIAoKV2l0aCByZXNwZWN0IHRvIGltcHJvdmluZyB0aGlzIGN1cnJlbnQgbW9kZWwsIHdlIHByb3ZpZGUgdGhlIGZvbGxvd2luZyBzdWdnZXN0aW9ucy4gRmlyc3RseSwgdGhlcmUgbmVlZHMgdG8gYmUgYSBiZXR0ZXIgYW5kIG1vcmUgY29tcGxldGUgZGF0YSBzZXQgZm9yIHByb3BlcnR5IHZhbHVlcyBpbiBQaGlsYWRlbHBoaWEuIEF0IHRoZSBkYXRhIHdyYW5nbGluZyBwaGFzZSwgd2UgZm91bmQgdGhhdCBhIGxvdCBvZiB0aGUgdmFyaWFibGVzIGluIG91ciBkYXRhIHNldCBjb250YWlucyBtaXNzaW5nIGF0dHJpYnV0ZXMsIGVycm9ycyBkYXRhIGVudHJ5LCBpbmNvbnNpc3RlbnQgZGF0YSBjYXRlZ29yaXphdGlvbiwgYW5kIGV4dHJlbWUgb3V0bGllcnMgKHRoYXQgZG9lcyBub3QgbWFrZSBzZW5zZSkuIE9uIHRvcCBvZiB0aGF0LCB0aGUgbWV0YWRhdGEgaGFzIGJlZW4gcG9vciBhdCBkb2N1bWVudGluZyBkYXRhIGNhdGVnb3JpZXMuIEJlY2F1c2Ugb2YgdGhhdCwgd2UgaGFkIHRvIG1ha2UgYSB2YXJpZXR5IG9mIGRpZmZlcmVudCBhc3N1bXB0aW9ucyBpbiB0ZXJtcyBvZiB3aGV0aGVyIHRvIGtlZXAgb3IgcmVtb3ZlIG1pc3NpbmcgZGF0YSwgYXMgd2VsbCBhcyB0byBjb21iaW5lIGNlcnRhaW4gZGF0YSBjYXRlZ29yaWVzIGZvciB0aGVtIHRvIGJlIHN0YXRpc3RpY2FsbHkgc2lnbmlmaWNhbnQuIFRoaXMgbWFrZXMgdGhlIG1vZGVsIHByb25lIHRvIGVycm9ycy4gU2Vjb25kbHksIHdlIGhvcGUgdG8gbG9vayBmb3Igc29tZSBuZXcgcHJlZGljdG9yIHZhcmlhYmxlcywgc3VjaCBhcyB1cmJhbiBncmVlbnNwYWNlIGNvdmVyYWdlLCBjb25zaWRlcmluZyB0aGF0IHNvbWUgb2YgdGhlIGN1cnJlbnQgdmFyaWFibGVzIHdlIGhhdmUsIGxpa2VseSBjcmltZSBpbmNpZGVudHMsIHdvdWxkIGRldmFsdWUgcHJvcGVydGllcy4gTmV2ZXJ0aGVsZXNzLCB3ZSBhcmd1ZSB0aGF0IHRoZSBwcmVkaWN0b3IgdmFyaWFibGVzIGZvciBob3VzZSBwcmljZSBpcyBzcGVjaWZpYyB0byB0aGUgY29udGV4dCBvZiBlYWNoIGNpdHkuIEZvciBwbGFjZXMgbGlrZSBQaGlsYWRlbHBoaWEgd2hlcmUgY3JpbWUgaW5jaWRlbnRzIHBsYXkgYSBzaWduaWZpY2FudCByb2xlIGluIHNoYXBpbmcgbmVpZ2hib3Job29kIGxhbmRzY2FwZSwgaXQgaXMgc3RpbGwgaW1wb3J0YW50IHRvIGNvbnNpZGVyIGNyaW1lIGFzIGEgZmFjdG9yIGluIGFmZmVjdGluZyBzYWxlIHByaWNlIC0gcGVyaGFwcyB3ZSBzaG91bGQgbG9vayBmb3IgYW4gYWx0ZXJuYXRpdmUgd2F5IHRvIGFjY291bnQgZm9yIGNyaW1lLiBUaGlyZGx5LCBnaXZlbiBQaGlsYWRlbHBoaWEncyBjb21wbGV4IGhvdXNpbmcgbWFya2V0LCB3ZSBzdXNwZWN0IHRoYXQgb3VyIG1vZGVsIGhhZCBmYWlsZWQgdG8gYWNjb3VudCBmb3Igc29tZSBvZiB0aGUgaW50cmEtbmVpZ2hib3Job29kIG51YW5jZXMuIFRoZXNlIG51YW5jZXMgY2Fubm90IGJlIHByb3Blcmx5IGdlbmVyYWxpemVkIGFuZCByZXByZXNlbnRlZCB1c2luZyBhIGxpbmVhciBtb2RlbC4gVG8gZnVsbHkgYWNjb3VudCBmb3IgdGhlc2UgdmFyaWF0aW9ucywgd2Ugc2hvdWxkIGV4cGxvcmUgYWx0ZXJuYXRpdmUgbWFjaGluZSBsZWFybmluZyBhcHByb2FjaGVzLiBJbiBhZGRpdGlvbiwgd2UgbXVzdCBnZXQgYSBiZXR0ZXIgdW5kZXJzdGFuZGluZyBvZiB0aGUgc29jaWFsIGFuZCBoaXN0b3JpY2FsIGxhbmRzY2FwZSBvZiBQaGlsYWRlbHBoaWEgd2hlbiBzZWxlY3RpbmcgcHJlZGljdG9yIHZhcmlhYmxlcyB0aGF0IGNvbnRleHR1YWxpemUgd2l0aCB0aGVpciBob3VzaW5nIGNoYXJhY3RlcmlzdGljcy4g