Version 1.0 | First Created Oct 12, 2023 | Updated Oct 18, 2023

Keywords: Poisson Regression, Spatial Hotspot Analysis, Local Moran’s I, Spatial LOGO Cross Validation, K-fold Cross Validation, K Nearest Neighbors, Kernel Density Estimation

Introduction

Predictive policing is an application of machine learning through building geospatial risk models that aggregate historical crime data, socioeconomic factors, and spatial attributes to forecast where and when crimes are likely to occur. These models are crucial because they offer law enforcement agencies a proactive approach to crime prevention. While traditional policing method often rely on reacting to incidents after they occur, predictive policing enables law enforcement to anticipate and prevent crimes in advance. Because of that, law enforcement agencies can optimize their resources and deploy officers strategically, leading to more cost-effective law enforcement practices.

However, there are notable limitations and biases associated with these models. One significant concern is the potential for bias in the historical data used to train these algorithms. If the historical data reflects existing biases in law enforcement practices, such as racial profiling or discriminatory policies, the algorithms can perpetuate these biases, leading to unfair or disproportionate targeting of certain communities. Additionally, there is the risk of self-fulfilling prophecies: if law enforcement concentrates their efforts in specific areas predicted by the algorithm, crime data from those areas might be inflated, creating a feedback loop of over-policing. That said, predictions from geospatial risk models can be thought of as latent risk - areas at risk even if a crime has not actually been reported there. Selection bias can be a problematic issue for it is very likely that observed crime would discount actual crime risks.

With these in mind, the goal of this report is to fit a geospatial risk model predicting robbery incidents in Chicago, a city facing various crime and policing challenges, to the best of our ability. Robberies are often subjected to selection bias because 1) certainly not all incidents of robbery are reported to the police and 2) areas that had been heavily patrolled in the past due to higher robbery incidents will continue to report higher robbery incidents. For any type of crime, the hypothesis is that crime risk is a function of exposure to a series of geospatial risk and protective factors, such as blight or recreation centers, respectively. As exposure to risk factors increases, so does crime risk. In addition to identifying risk factors, the model used in this report also considers spatial factors to improve accuracy. Poisson regression is used here for its compatibility with count data. All data sets used for predictions here are downloaded from Chicago Data Portal.

This report is structured as follow: Firstly, we did a close examination of our dependent variable, in this case robbery incidents.Secondly, we discuss our rational of our selection of risk factors and presented our approach to feature engineering, which includes the calculation of nearest distance to each risk factors and spatial hotspots. Thirdly, we fit two regression models: one simple poisson regression and one that account for spatial attributes. We subsequently perform two kinds of cross validation: one random k-fold cross validation. Finally, we compared the errors of different regression models, tested their generalization, and compare our models to the traditional kernel density estimation.

The GitHub repository for this report is here.

Robbery Incidents

The Chicago Data Portal is an online platform that serves as a central repository for a wide range of municipal data sets. The portal maintains a record for crime incidents for Chicago every year. In this report, crime data for 2021 is downloaded using the RSocrata package. Socrata is the creator of the open source platform that Chicago uses to share its data. After downloading, robbery incidents were filtered out. Some additional data wrangling removes extraneous characters from the Location field with gsub. The resulting field is then converted to separate fields of X and Y coordinates.

robbery21 <- 
  read.socrata("https://data.cityofchicago.org/Public-Safety/Crimes-2021/dwme-t96c") %>% 
    filter(Primary.Type == "ROBBERY") %>%
    mutate(x = gsub("[()]", "", Location)) %>%
    separate(x,into= c("Y","X"), sep=",") %>%
    mutate(X = as.numeric(X),Y = as.numeric(Y)) %>% 
    na.omit() %>%
    st_as_sf(coords = c("X", "Y"), crs = 4326, agr = "constant")%>%
    st_transform('ESRI:102271') %>% 
    distinct()

A quick mapping of each individual incident in Chicago reveals that robberies are pretty scattered in all parts of the city, though slightly more concentrated in central and northeast Chicago.

ggplot() + 
  geom_sf(data = chicagoBoundary, fill = "black") +
  geom_sf(data = robbery21, colour="#BB3754", size=0.1, show.legend = "point") +
  labs(title= "Robbery Incidents in Chicago 2021") + 
  theme(axis.text.x=element_blank(),
        axis.text.y=element_blank(),
        axis.ticks =element_blank(),
        axis.title.x = element_blank(),
        axis.title.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, linewidth =0.8)
        )

The density map shows three clear robbery hotspots in Chicago. The most prominent hotspots are located in north east and north west part of Chicago, with some smaller hotspots in the southern part.

options(scipen=0)
ggplot() + 
  geom_sf(data = chicagoBoundary, fill = "black") +
  stat_density2d(data = data.frame(st_coordinates(robbery21)), 
                 aes(X, Y, fill = ..level.., alpha = ..level..),
                 size = 0.01, bins = 60, geom = 'polygon') +
  scale_fill_viridis(option = "magma", name = "Density") +
  scale_alpha(range = c(0.00, 0.35), guide = "none") +
  labs(title = "Density of Robbery") +
  theme(axis.text.x=element_blank(),
        axis.text.y=element_blank(),
        axis.ticks =element_blank(),
        axis.title.x = element_blank(),
        axis.title.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, linewidth=0.8)
        )

Since crime risk is not as a phenomenon that varies across administrative units, but one varying smoothly across the landscape, the best way to represent this spatial trend in a regression-ready form is to aggregate point-level data into a lattice of grid cells. The code below generates a fishnet of Chicago to achieve this goal.

fishnet <- 
  st_make_grid(chicagoBoundary,
               cellsize = 500, 
               square = TRUE) %>%
  .[chicagoBoundary] %>%            # fast way to select intersecting polygons
  st_sf() %>%
  mutate(uniqueID = 1:n())

ggplot() +
  geom_sf(data=fishnet, color="black", fill="#FFF5EE") +
  labs(title = "Fishnet of Chicago") +
  theme(axis.text.x=element_blank(),
        axis.text.y=element_blank(),
        axis.ticks =element_blank(),
        axis.title.x = element_blank(),
        axis.title.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, linewidth=0.8)
        )

After generating the fishnet, we used that as a spatial unit for visualizing robbery incidents, from which we may see that number of robberies are the highest in a couple of girds in northeast Chicago. This is where the clustered spatial process of robbery begins to take shape.

robbery21_net <- 
  dplyr::select(robbery21) %>% 
  mutate(countRobbery = 1) %>% 
  aggregate(., fishnet, sum) %>%
  mutate(countRobbery = replace_na(countRobbery, 0),
         uniqueID = 1:n(),
         cvID = sample(round(nrow(fishnet) / 24), 
                       size=nrow(fishnet), replace = TRUE))

ggplot() +
  geom_sf(data = robbery21_net, aes(fill = countRobbery), color = NA) +
  scale_fill_viridis(option = "magma", name = "Robbery Counts") +
  labs(title = "Count of Robberies for the Fishnet") +
  theme(axis.text.x=element_blank(),
        axis.text.y=element_blank(),
        axis.ticks =element_blank(),
        axis.title.x = element_blank(),
        axis.title.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, linewidth=0.8)
        )

The figure below shows the distribution of robbery incidents in Chicago, which is non-normally distributed with a few grids having significantly higher number of robberies. To account for this non-normality, it is necessary to use Possion regression for this analysis.

ggplot(robbery21_net, aes(x = countRobbery)) +
  geom_histogram( fill="#56106E", color="#e9ecef") + 
  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)) + 
  labs(title = "Distribution of Robberies in Chicago 2021",
       caption = "Data: Chicago Data Portal Crimes 2021") +
  xlab("Robbery Incidents") +
  ylab("Count")

Risk Factors

We included in our model eight different risk factors. This includes 311 reports of abandoned cars, street lights out, graffiti remediation, sanitation complaints, abandon buildings, location of retail stores that sell liquor to go, location of shotspotter, and banks. Here are the rationale for including these predictors in the model.

Abandoned Cars: Neighborhoods with a high number of abandoned cars might indicate economic distress, lack of community engagement, and lower levels of surveillance. These conditions can create an environment conducive to criminal activities like robberies.

Street Lights Out: Poorly lit areas are often targeted by criminals because darkness provides cover. Reports of street lights out can highlight areas with reduced visibility, making them potential hotspots for robberies.

Graffiti Presence: Areas with frequent graffiti might suggest a lack of community maintenance and oversight. Such areas can be perceived as neglected, making them attractive for criminal activities, including robberies.

Sanitation Complaints: Similarly, neighborhoods with sanitation issues might suggest the lack of maintenance. This breakdown in community can contribute to an environment where criminal activities, such as robberies, are more likely to occur.

Abandoned Buildings: Abandoned buildings also serve as hideouts for criminals and can be perceived as unsafe by residents. They create a sense of disorder and can attract criminal elements, making nearby areas more susceptible to robberies.

Retail Stores Selling Liquor to Go: Liquor stores are associated with alcohol consumption. Areas with a high concentration of liquor stores might indicate higher lead to higher alcohol related crime (though this is a very subjective statement) because inebriated individuals, particularly during late hours, might be more vulnerable and less aware of their surroundings, making them easy targets for robberies.

Location of ShotSpotter: ShotSpotter technology detects gunfire in real-time and areas with frequent gunshot incidents likely experience higher crime rates, including robberies. These locations can be considered as potential risk factors for predicting robbery incidents.

Banks: Bank locations are often targeted due to the availability of cash. Since we are predicting robberies here, areas with a high density of banks might attract robbers seeking immediate financial gains.

abandonCars <- 
  read.socrata("https://data.cityofchicago.org/Service-Requests/311-Service-Requests-Abandoned-Vehicles/3c9v-pnva") %>%
    mutate(year = substr(creation_date,1,4)) %>% filter(year == "2020") %>%
    dplyr::select(Y = latitude, X = longitude) %>%
    na.omit() %>%
    st_as_sf(coords = c("X", "Y"), crs = 4326, agr = "constant") %>%
    st_transform(st_crs(fishnet)) %>%
    mutate(Legend = "Abandoned_Cars")
  
abandonBuildings <- 
  read.socrata("https://data.cityofchicago.org/Service-Requests/311-Service-Requests-Vacant-and-Abandoned-Building/7nii-7srd") %>%
    mutate(year = substr(date_service_request_was_received,1,4)) %>%  filter(year == "2018") %>%
    dplyr::select(Y = latitude, X = longitude) %>%
    na.omit() %>%
    st_as_sf(coords = c("X", "Y"), crs = 4326, agr = "constant") %>%
    st_transform(st_crs(fishnet)) %>%
    mutate(Legend = "Abandoned_Buildings")

graffiti <- 
  read.socrata("https://data.cityofchicago.org/Service-Requests/311-Service-Requests-Graffiti-Removal-Historical/hec5-y4x5") %>%
    mutate(year = substr(creation_date,1,4)) %>% filter(year == "2018") %>%
    filter(where_is_the_graffiti_located_ %in% c("Front", "Rear", "Side")) %>%
    dplyr::select(Y = latitude, X = longitude) %>%
    na.omit() %>%
    st_as_sf(coords = c("X", "Y"), crs = 4326, agr = "constant") %>%
    st_transform(st_crs(fishnet)) %>%
    mutate(Legend = "Graffiti")

streetLightsOut <- 
  read.socrata("https://data.cityofchicago.org/Service-Requests/311-Service-Requests-Street-Lights-All-Out/zuxi-7xem") %>%
    mutate(year = substr(creation_date,1,4)) %>% filter(year == "2018") %>%
    dplyr::select(Y = latitude, X = longitude) %>%
    na.omit() %>%
    st_as_sf(coords = c("X", "Y"), crs = 4326, agr = "constant") %>%
    st_transform(st_crs(fishnet)) %>%
    mutate(Legend = "Street_Lights_Out")

sanitation <-
  read.socrata("https://data.cityofchicago.org/Service-Requests/311-Service-Requests-Sanitation-Code-Complaints-Hi/me59-5fac") %>%
    mutate(year = substr(creation_date,1,4)) %>% filter(year == "2018") %>%
    dplyr::select(Y = latitude, X = longitude) %>%
    na.omit() %>%
    st_as_sf(coords = c("X", "Y"), crs = 4326, agr = "constant") %>%
    st_transform(st_crs(fishnet)) %>%
    mutate(Legend = "Sanitation")

liquorRetail <- 
  read.socrata("https://data.cityofchicago.org/resource/nrmj-3kcf.json") %>%  
    filter(business_activity == "Retail Sales of Packaged Liquor") %>%
    dplyr::select(Y = latitude, X = longitude) %>%
    na.omit() %>%
    st_as_sf(coords = c("X", "Y"), crs = 4326, agr = "constant") %>%
    st_transform(st_crs(fishnet)) %>%
    mutate(Legend = "Liquor_Retail")


shotSpotter <- 
  read.socrata("https://data.cityofchicago.org/Public-Safety/Violence-Reduction-Shotspotter-Alerts/3h7q-7mdb") %>%  
    dplyr::select(Y = latitude, X = longitude) %>%
    na.omit() %>%
    st_as_sf(coords = c("X", "Y"), crs = 4326, agr = "constant") %>%
    st_transform(st_crs(fishnet)) %>%
    mutate(Legend = "Shot_Spotter")

Banks <- 
  read.socrata("https://data.cityofchicago.org/Administration-Finance/Lending-Equity-Depository-Locations-2021/jgup-zt7k") %>%  
  dplyr::select(location) %>%
  na.omit()
Banks <- Banks %>%   
  mutate(X = as.numeric(sub("POINT \\((-?\\d+\\.\\d+) \\d+\\.\\d+\\)", "\\1", Banks$location)),
         Y = as.numeric(sub("POINT \\(-?\\d+\\.\\d+ (-?\\d+\\.\\d+)\\)", "\\1", Banks$location))) %>%
  dplyr::select(-location) %>% 
  filter(!is.na(X) & !is.na(Y)) %>% 
  st_as_sf(coords = c("X", "Y"), crs = 4326, agr = "constant") %>%
    st_transform(st_crs(fishnet)) %>%
    mutate(Legend = "Bank") 

All of these predictor variables were then joined to the fishnet. Each cell in the fishnet would now contain a number summarizing the number of respective risk factor occurrence in the cell.

vars_net <- 
  rbind(abandonCars,streetLightsOut,abandonBuildings,
        liquorRetail, graffiti, sanitation, shotSpotter, Banks) %>%
  st_join(., fishnet, join=st_within) %>%
  st_drop_geometry() %>%
  group_by(uniqueID, Legend) %>%
  summarize(count = n()) %>%
    full_join(fishnet) %>%
    spread(Legend, count, fill=0) %>%
    st_sf() %>%
    dplyr::select(-`<NA>`) %>%
    na.omit() %>%
    ungroup()

The figure below shows that each risk factor has its unique spatial distribution in Chicago. Abandoned buildings are concentrated mostly concentrated in southern Chicago. Banks and liquor retail stores are concentrated in the CBD. Abandoned cars, sanitation complaints, and streets lights out complaints are scattered everywhere in the city. Graffiti cleaning request are mostly found close to the city center while shot spotter are mostly outside of the city center.

vars_net.long <- 
  gather(vars_net, Variable, value, -geometry, -uniqueID)

plot1 <- ggplot() +
  geom_sf(data = vars_net.long %>% filter(Variable == "Abandoned_Buildings"), aes(fill=value), colour=NA) +
  scale_fill_viridis(option = "magma") + 
  labs(title = "Abandoned Buildings") +
  theme(axis.text.x=element_blank(),
        axis.text.y=element_blank(),
        axis.ticks =element_blank(),
        axis.title.x = element_blank(),
        axis.title.y = element_blank(),
        plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 10, face = "bold"),
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill=NA, size=0.8)
        )


plot2 <- ggplot() +
  geom_sf(data = vars_net.long %>% filter(Variable == "Abandoned_Cars"), aes(fill=value), colour=NA) +
  scale_fill_viridis(option = "magma") + 
  labs(title = "Abandoned Cars") +
  theme(axis.text.x=element_blank(),
        axis.text.y=element_blank(),
        axis.ticks =element_blank(),
        axis.title.x = element_blank(),
        axis.title.y = element_blank(),
        plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 10, face = "bold"),
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill=NA, size=0.8)
        )

plot3 <- ggplot() +
  geom_sf(data = vars_net.long %>% filter(Variable == "Bank"), aes(fill=value), colour=NA) +
  scale_fill_viridis(option = "magma") + 
  labs(title = "Banks") +
  theme(axis.text.x=element_blank(),
        axis.text.y=element_blank(),
        axis.ticks =element_blank(),
        axis.title.x = element_blank(),
        axis.title.y = element_blank(),
        plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 10, face = "bold"),
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill=NA, size=0.8)
        )

plot4 <- ggplot() +
  geom_sf(data = vars_net.long %>% filter(Variable == "Graffiti"), aes(fill=value), colour=NA) +
  scale_fill_viridis(option = "magma") + 
  labs(title = "Graffiti") +
  theme(axis.text.x=element_blank(),
        axis.text.y=element_blank(),
        axis.ticks =element_blank(),
        axis.title.x = element_blank(),
        axis.title.y = element_blank(),
        plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 10, face = "bold"),
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill=NA, size=0.8)
        )


plot5 <- ggplot() +
  geom_sf(data = vars_net.long %>% filter(Variable == "Liquor_Retail"), aes(fill=value), colour=NA) +
  scale_fill_viridis(option = "magma") + 
  labs(title = "Liquor Retail") +
  theme(axis.text.x=element_blank(),
        axis.text.y=element_blank(),
        axis.ticks =element_blank(),
        axis.title.x = element_blank(),
        axis.title.y = element_blank(),
        plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 10, face = "bold"),
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill=NA, size=0.8)
        )

plot6 <- ggplot() +
  geom_sf(data = vars_net.long %>% filter(Variable == "Sanitation"), aes(fill=value), colour=NA) +
  scale_fill_viridis(option = "magma") + 
  labs(title = "Sanitation") +
  theme(axis.text.x=element_blank(),
        axis.text.y=element_blank(),
        axis.ticks =element_blank(),
        axis.title.x = element_blank(),
        axis.title.y = element_blank(),
        plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 10, face = "bold"),
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill=NA, size=0.8)
        )

plot7 <- ggplot() +
  geom_sf(data = vars_net.long %>% filter(Variable == "Shot_Spotter"), aes(fill=value), colour=NA) +
  scale_fill_viridis(option = "magma") + 
  labs(title = "Shot Spotter") +
  theme(axis.text.x=element_blank(),
        axis.text.y=element_blank(),
        axis.ticks =element_blank(),
        axis.title.x = element_blank(),
        axis.title.y = element_blank(),
        plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 10, face = "bold"),
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill=NA, size=0.8)
        )


plot8 <- ggplot() +
  geom_sf(data = vars_net.long %>% filter(Variable == "Street_Lights_Out"), aes(fill=value), colour=NA) +
  scale_fill_viridis(option = "magma") + 
  labs(title = "Street Light Out") +
  theme(axis.text.x=element_blank(),
        axis.text.y=element_blank(),
        axis.ticks =element_blank(),
        axis.title.x = element_blank(),
        axis.title.y = element_blank(),
        plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 10, face = "bold"),
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill=NA, size=0.8)
        )

wrap_plots(
  plot1, plot3, plot2, plot4,
  plot5, plot6, plot7, plot8,
  ncol = 4  
)

Feature Engineering

Nearest Distance

The first feature engineering approach we took is to calculate average nearest neighbor distance to hypothesize a smoother exposure relationship across space. Here, the nn_function is used. For each of our risk factors, average nearest distance is calculated from the centroid of each cell to its nearest three, abandoned cars, for example.

st_c <- st_coordinates
st_coid <- st_centroid

vars_net <-
  vars_net %>%
    mutate(
      Abandoned_Buildings.nn =
        nn_function(st_c(st_coid(vars_net)), st_c(abandonBuildings),3),
      Abandoned_Cars.nn =
        nn_function(st_c(st_coid(vars_net)), st_c(abandonCars),3),
      Graffiti.nn =
        nn_function(st_c(st_coid(vars_net)), st_c(graffiti),3),
      Liquor_Retail.nn =
        nn_function(st_c(st_coid(vars_net)), st_c(liquorRetail),3),
      Street_Lights_Out.nn =
        nn_function(st_c(st_coid(vars_net)), st_c(streetLightsOut),3),
      Sanitation.nn =
        nn_function(st_c(st_coid(vars_net)), st_c(sanitation),3),
      Shot_Spotter.nn =
        nn_function(st_c(st_coid(vars_net)), st_c(shotSpotter),3))

vars_net <-
  vars_net %>% mutate(Bank.nn = nn_function(st_c(st_coid(vars_net)), st_c(Banks),3))

The nearest neighbor features are then plotted below. Visualizing distance provides us with a better view of the spatial distribution of various risk factors. It appears that the southern and southwest part of Chicago are the furthest away from all kinds of risks. One interesting observation is that the northeastern corner of Chicago is the furthest away from shotspotters while being relatively close to all other kinds of risk factors. If shot spotter are used to location crime incidents, their absence in this area raises question of whether this “risk” factor act the same as others. Are shotspotter an indication of more crime in the area or does the presence of shot spotter means greater policing and therefore safter neighborhood? These questions are worth considering.

vars_net.long.nn <- 
  dplyr::select(vars_net, ends_with(".nn")) %>%
    gather(Variable, value, -geometry)


p1.nn <- ggplot() +
  geom_sf(data = vars_net.long.nn %>% filter(Variable == "Abandoned_Buildings.nn"), aes(fill=value), colour=NA) +
  scale_fill_viridis(option = "magma") + 
  labs(title = "Abandoned Buildings NN") +
  theme(axis.text.x=element_blank(),
        axis.text.y=element_blank(),
        axis.ticks =element_blank(),
        axis.title.x = element_blank(),
        axis.title.y = element_blank(),
        plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 10, face = "bold"),
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill=NA, size=0.8)
        )


p2.nn <- ggplot() +
  geom_sf(data = vars_net.long.nn %>% filter(Variable == "Abandoned_Cars.nn"), aes(fill=value), colour=NA) +
  scale_fill_viridis(option = "magma") + 
  labs(title = "Abandoned Cars NN") +
  theme(axis.text.x=element_blank(),
        axis.text.y=element_blank(),
        axis.ticks =element_blank(),
        axis.title.x = element_blank(),
        axis.title.y = element_blank(),
        plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 10, face = "bold"),
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill=NA, size=0.8)
        )

p3.nn <- ggplot() +
  geom_sf(data = vars_net.long.nn %>% filter(Variable == "Bank.nn"), aes(fill=value), colour=NA) +
  scale_fill_viridis(option = "magma") + 
  labs(title = "Banks NN") +
  theme(axis.text.x=element_blank(),
        axis.text.y=element_blank(),
        axis.ticks =element_blank(),
        axis.title.x = element_blank(),
        axis.title.y = element_blank(),
        plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 10, face = "bold"),
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill=NA, size=0.8)
        )

p4.nn <- ggplot() +
  geom_sf(data = vars_net.long.nn %>% filter(Variable == "Graffiti.nn"), aes(fill=value), colour=NA) +
  scale_fill_viridis(option = "magma") + 
  labs(title = "Graffiti NN") +
  theme(axis.text.x=element_blank(),
        axis.text.y=element_blank(),
        axis.ticks =element_blank(),
        axis.title.x = element_blank(),
        axis.title.y = element_blank(),
        plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 10, face = "bold"),
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill=NA, size=0.8)
        )


p5.nn <- ggplot() +
  geom_sf(data = vars_net.long.nn %>% filter(Variable == "Liquor_Retail.nn"), aes(fill=value), colour=NA) +
  scale_fill_viridis(option = "magma") + 
  labs(title = "Liquor Retail NN") +
  theme(axis.text.x=element_blank(),
        axis.text.y=element_blank(),
        axis.ticks =element_blank(),
        axis.title.x = element_blank(),
        axis.title.y = element_blank(),
        plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 10, face = "bold"),
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill=NA, size=0.8)
        )

p6.nn <- ggplot() +
  geom_sf(data = vars_net.long.nn %>% filter(Variable == "Sanitation.nn"), aes(fill=value), colour=NA) +
  scale_fill_viridis(option = "magma") + 
  labs(title = "Sanitation NN") +
  theme(axis.text.x=element_blank(),
        axis.text.y=element_blank(),
        axis.ticks =element_blank(),
        axis.title.x = element_blank(),
        axis.title.y = element_blank(),
        plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 10, face = "bold"),
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill=NA, size=0.8)
        )

p7.nn <- ggplot() +
  geom_sf(data = vars_net.long.nn %>% filter(Variable == "Shot_Spotter.nn"), aes(fill=value), colour=NA) +
  scale_fill_viridis(option = "magma") + 
  labs(title = "Shot Spotter NN") +
  theme(axis.text.x=element_blank(),
        axis.text.y=element_blank(),
        axis.ticks =element_blank(),
        axis.title.x = element_blank(),
        axis.title.y = element_blank(),
        plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 10, face = "bold"),
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill=NA, size=0.8)
        )


p8.nn <- ggplot() +
  geom_sf(data = vars_net.long.nn %>% filter(Variable == "Street_Lights_Out.nn"), aes(fill=value), colour=NA) +
  scale_fill_viridis(option = "magma") + 
  labs(title = "Street Light Out NN") +
  theme(axis.text.x=element_blank(),
        axis.text.y=element_blank(),
        axis.ticks =element_blank(),
        axis.title.x = element_blank(),
        axis.title.y = element_blank(),
        plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 10, face = "bold"),
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill=NA, size=0.8)
        )

wrap_plots(
  p1.nn, p3.nn, p2.nn, p4.nn,
  p5.nn, p6.nn, p7.nn, p8.nn,
  ncol = 4  
)

Local Spatial Autocorrelation

The second feature engineering approach we took is to account for the spatial process of robbery incidents through determining robbery hotspots in Chicago. Unlike the global Moran’s I which would tell us if there’s spatial autocorrelation of robberies of the entire area, local Moran’s I examine if robbery count at a given location is randomly distributed relative to its immediate neighbors. A spatial weights matrix is calcualted below, which is used to relate a unit to its neighbors using queen contiguity (every grid cell is related to its eight adjacent neighbors).

final_net <-
  left_join(robbery21_net, st_drop_geometry(vars_net), by="uniqueID")
final_net.nb <- poly2nb(as_Spatial(final_net), queen=TRUE)
final_net.weights <- nb2listw(final_net.nb, style="W", zero.policy=TRUE)

local_morans <- localmoran(final_net$countRobbery, final_net.weights, zero.policy=TRUE) %>% 
  as.data.frame()

final_net.localMorans <- 
  cbind(local_morans, as.data.frame(final_net)) %>% 
  st_sf() %>%
  dplyr::select(Robbery_Count = countRobbery, 
                Local_Morans_I = Ii, 
                P_Value = `Pr(z != E(Ii))`) %>%
  mutate(Significant_Hotspots = ifelse(P_Value <= 0.001, 1, 0)) %>%
  gather(Variable, Value, -geometry)

The results of the localmoran test is combined with the spatial layer for visualization. Several useful test statistics, including Moran’s I, the p-value, and significant robbery hotspots are visualized. We may see that the Local Moran’s I value is significantly higher in northeastern Chicago, close to the CBD area. Areas with low p-value suggests that there is spatial autocorrelation between robbery incidents in cells that are proximate to each other. Significant hotspots, meaning those grid cells with higher local counts than what might otherwise be expected under randomness (p-values <= 0.05), follows the same pattern as what’s shown in the kernal density map above.

moran1 <- ggplot() +
  geom_sf(data = final_net.localMorans %>% filter(Variable == "Robbery_Count"), aes(fill=Value), colour=NA) +
  scale_fill_viridis(option = "magma") + 
  labs(title = "Robbery Count") +
  theme(axis.text.x=element_blank(),
        axis.text.y=element_blank(),
        axis.ticks =element_blank(),
        axis.title.x = element_blank(),
        axis.title.y = element_blank(),
        plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 10, face = "bold"),
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill=NA, size=0.8)
        )

moran2 <- ggplot() +
  geom_sf(data = final_net.localMorans %>% filter(Variable == "Local_Morans_I"), aes(fill=Value), colour=NA) +
  scale_fill_viridis(option = "magma") + 
  labs(title = "Local Morans I") +
  theme(axis.text.x=element_blank(),
        axis.text.y=element_blank(),
        axis.ticks =element_blank(),
        axis.title.x = element_blank(),
        axis.title.y = element_blank(),
        plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 10, face = "bold"),
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill=NA, size=0.8)
        )

moran3 <- ggplot() +
  geom_sf(data = final_net.localMorans %>% filter(Variable == "P_Value"), aes(fill=Value), colour=NA) +
  scale_fill_viridis(option = "magma") + 
  labs(title = "P Value") +
  theme(axis.text.x=element_blank(),
        axis.text.y=element_blank(),
        axis.ticks =element_blank(),
        axis.title.x = element_blank(),
        axis.title.y = element_blank(),
        plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 10, face = "bold"),
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill=NA, size=0.8)
        )


moran4 <- ggplot() +
  geom_sf(data = final_net.localMorans %>% filter(Variable == "Significant_Hotspots"), aes(fill=Value), colour=NA) +
  scale_fill_viridis(option = "magma") + 
  labs(title = "Signigicant Hotspots") +
  theme(axis.text.x=element_blank(),
        axis.text.y=element_blank(),
        axis.ticks =element_blank(),
        axis.title.x = element_blank(),
        axis.title.y = element_blank(),
        plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 10, face = "bold"),
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill=NA, size=0.8)
        )

moran1 + moran2 + moran3 + moran4

Singificant Robbery Clusters

Following the calculation of significant robbery clusters, we created a dummy variable indicating whether this grid cell is located within the hotspot. We also calculated the distance from the centroid of each grid cell to its nearest robbery hotspot.

final_net <-
  final_net %>% 
  mutate(robbery.isSig = ifelse(localmoran(final_net$countRobbery, 
                             final_net.weights)[,5] <= 0.0000001, 1, 0)) %>%
  mutate(robbery.isSig.dist = nn_function(st_coordinates(st_centroid(final_net)),
                                          st_coordinates(st_centroid(
                                            filter(final_net, robbery.isSig == 1))), 1))

The figure below visualizes distance to nearest robbery hotspots in Chicago, where the darker the color, the shorter the distance.

ggplot() +
      geom_sf(data = final_net, aes(fill=robbery.isSig.dist), colour=NA) +
      scale_fill_viridis(option = "magma", name="NN Distance") +
      labs(title="Robbery NN Distance") +
      theme(axis.text.x=element_blank(),
        axis.text.y=element_blank(),
        axis.ticks =element_blank(),
        axis.title.x = element_blank(),
        axis.title.y = element_blank(),
        plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 10, face = "bold"),
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill=NA, size=0.8)
        )

All Risk Factors

The figure below organizes count and nearest neighbor (nn) correlations side-by-side. All of our count features are positively correlated with robbery incidents, meaning the greater number of risk factors, the higher number of robbery. The relationship is the strongest between robbery and sanitation complaint as well as shot spotter. All of our nearest neighbor features are negatively correlated with robbery incidents, meaning that the greater the distance, the less number of robbery. The relationship is the strongest for distance to signficant robbery clusters. While correlation for count features is a bit awkward, this approach can help with feature selection. For a given risk factor, we have to avoid colinearity by selecting either the count or nearest neighbor feature. In the case of our model, we used nearest distance.

final_net_long <- final_net%>% 
  st_drop_geometry() %>% 
  dplyr::select(-c(uniqueID, cvID)) %>% 
  pivot_longer(cols = -countRobbery, # everything except measurement
               names_to = "Type", # categorizes all quantitative variables into Type
               values_to = "Number") 

correlation.cor <-
  final_net_long %>%
    group_by(Type) %>%
    summarize(correlation = cor(Number, countRobbery, use = "complete.obs"))

final_net_long %>%
  ggplot(aes(x= Number, y = countRobbery)) +
  geom_point(size = 0.01, color = "#000004") +  
  geom_text(data = correlation.cor, aes(label = paste("r =", round(correlation, 2))),
            x=-Inf, y=Inf, vjust = 1.5, hjust = -.1, size=3) +
  geom_smooth(method='lm', formula= y~x, lwd=0.5, se = FALSE, color = "#BB3754") +
  facet_wrap(~ Type, scales = "free", ncol = 2, labeller= labeller(Type = c(
    `Abandoned_Buildings` = "Abandoned Buildings",
    `Abandoned_Buildings.nn` = "Distance to Abandoned Buildings",
    `Abandoned_Cars` = "Abandoned Cars",
    `Abandoned_Cars.nn` = "Distance to Abandoned Cars",
    `Bank` = "Bank",
    `Bank.nn` = "Distance to Bank",
    `Graffiti` = "Graffiti",
    `Graffiti.nn` = "Distance to Graffiti",
    `Liquor_Retail` = "Liquor Retail",
    `Liquor_Retail.nn` = "Distance to Liquor Retail",
    `robbery.isSig` = "Significant Robbery",
    `robbery.isSig.dist` = "Distance to Significant Robbery",
    `Sanitation` = "Sanitation",
    `Sanitation.nn` = "Distance to Sanitation",
    `Shot_Spotter` = "Shot Spotter", 
    `Shot_Spotter.nn` = "Distance to Shot Spotter",
    `Street_Lights_Out` = "Street Light Out",
    `Street_Lights_Out.nn` = "Distance to Broken Street Light")))  +
  labs(title = "Scatter Plots of Predictor Variables",
       caption = "Data: Chicago Data Portal") +
  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))

Poisson Regression

Random K-Fold CV

Below, goodness of fit metrics are generated for four regressions - two including Just Risk Factors reg.vars, and a second reg.ss.vars includes risk factors plus the Local Moran’s I Spatial Process. Two kinds of cross validation test were performed. The first one uses a randomly generated cvID associated with each grid cell for random k-fold cross validation.

reg.vars <- c("Abandoned_Buildings.nn", "Abandoned_Cars.nn", "Graffiti.nn", 
              "Liquor_Retail.nn", "Street_Lights_Out.nn", "Sanitation.nn", 
              "Shot_Spotter.nn", "Bank.nn")

reg.ss.vars <- c("Abandoned_Buildings.nn", "Abandoned_Cars.nn", "Graffiti.nn", 
                 "Liquor_Retail.nn", "Street_Lights_Out.nn", "Sanitation.nn", 
                 "Shot_Spotter.nn", "Bank.nn", "robbery.isSig", "robbery.isSig.dist")

reg.cv <- crossValidate(
  dataset = final_net,
  id = "cvID",
  dependentVariable = "countRobbery",
  indVariables = reg.vars) %>%
    dplyr::select(cvID = cvID, countRobbery, Prediction, geometry)

reg.ss.cv <- crossValidate(
  dataset = final_net,
  id = "cvID",
  dependentVariable = "countRobbery",
  indVariables = reg.ss.vars) %>%
    dplyr::select(cvID = cvID, countRobbery, Prediction, geometry)

Spatial LOGO CV

The second one hold out one neighborhood, train the model on the remaining n-1 areas, predict for the hold out, and record the goodness of fit. This is also called the spatial ‘Leave-one-group-out’ cross-validation (LOGO-CV), in which each neighborhood takes a turn as a hold-out. The result of each analysis is a sf layer with observed and predicted burglary counts.

neighborhoods <- 
  st_read("https://raw.githubusercontent.com/blackmad/neighborhoods/master/chicago.geojson") %>%
  st_transform(st_crs(fishnet)) 

final_net <-
  st_centroid(final_net) %>%
    st_join(dplyr::select(neighborhoods, name)) %>%
      st_drop_geometry() %>%
      left_join(dplyr::select(final_net, geometry, uniqueID)) %>%
      st_sf() %>%
  na.omit()

reg.spatialCV <- crossValidate(
  dataset = final_net,
  id = "name",
  dependentVariable = "countRobbery",
  indVariables = reg.vars) %>%
    dplyr::select(cvID = name, countRobbery, Prediction, geometry)

reg.ss.spatialCV <- crossValidate(
  dataset = final_net,
  id = "name",
  dependentVariable = "countRobbery",
  indVariables = reg.ss.vars) %>%
    dplyr::select(cvID = name, countRobbery, Prediction, geometry)

Error Analysis

The code block below creates a long form reg.summary, that binds together observed/predicted counts and errors for each grid cell and for each regression. Residuals are calculated for each regression by subtracting the original robbery incidents from predicted incidents.

reg.summary <- 
  rbind(
    mutate(reg.cv,           Error = Prediction - countRobbery,
                             Regression = "Random k-fold CV: Just Risk Factors"),
                             
    mutate(reg.ss.cv,        Error = Prediction - countRobbery,
                             Regression = "Random k-fold CV: Spatial Process"),
    
    mutate(reg.spatialCV,    Error = Prediction - countRobbery,
                             Regression = "Spatial LOGO-CV: Just Risk Factors"),
                             
    mutate(reg.ss.spatialCV, Error = Prediction - countRobbery,
                             Regression = "Spatial LOGO-CV: Spatial Process")) %>%
    st_sf() 

Comparing the error across different regressions, we see that adding spatial process features improve the model. In addition, the model appears slightly less robust for the spatial cross-validation, probably because LOGO-CV is such a conservative assumption.

error_by_reg_and_fold <- 
  reg.summary %>%
    group_by(Regression, cvID) %>% 
    summarize(Mean_Error = mean(Prediction - countRobbery, na.rm = T),
              MAE = mean(abs(Mean_Error), na.rm = T),
              SD_MAE = mean(abs(Mean_Error), na.rm = T)) %>% ungroup()

st_drop_geometry(error_by_reg_and_fold) %>%
  group_by(Regression) %>% 
    summarize(Mean_MAE = round(mean(MAE), 2),
              SD_MAE = round(sd(MAE), 2)) %>%
  kable(col.name=c("Regression", 'Mean Absolute Error','Standard Deviation MAE')) %>%
    kable_styling(bootstrap_options = c("striped", "hover", "condensed")) %>%
    row_spec(2, color = "black", background = "#FCFFA4") %>%
    row_spec(4, color = "black", background = "#FCFFA4") 
Regression Mean Absolute Error Standard Deviation MAE
Random k-fold CV: Just Risk Factors 0.66 0.59
Random k-fold CV: Spatial Process 0.64 0.55
Spatial LOGO-CV: Just Risk Factors 2.53 2.77
Spatial LOGO-CV: Spatial Process 1.82 2.04

The small multiple maps below visualizes the distribution of errors by regression. For the random k-fold cv method, the errors are pretty evenly distributed across space. However, for spatial logo-cv method, higher errors occur at spatial hotspots.

error_by_reg_and_fold %>% 
  ggplot() +
  geom_sf(aes(fill=MAE), color="transparent") +
  scale_fill_viridis(option = "magma", direction = -1) + 
  facet_wrap(~Regression, ncol = 4) +
   labs(title = "MAE by Fold and Regression",
       caption = "Data: Chicago Data Portal") +
  theme(legend.position = "bottom",
        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.8),
        strip.text = element_text(size = 7)) 

Prediction Accuracy

Generalizability Test

Let’s now test for generalizability across racial-neighborhood context. To test this proposition, tidycensus is used to pull race data by census tract. Percentage minority population for each census tract is calculated by comparing the total number of Latinx, African American, and Asian population to the total population. Census tracts are split into two groups: majority white and minority, depending on whether the proportion of minority population in that census tract exceeds 60%.

census_api_key(dlgInput(
  "Enter a Census API Key", # ask for an api key
  Sys.getenv("CENSUS_API_KEY")
)$res,
overwrite = TRUE)
tracts21 <- 
  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=2021, state=17, county=031, 
          geometry=TRUE, output="wide") %>%
  st_transform('ESRI:102271') %>% 
  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")) %>%   .[neighborhoods,]

We may see that Chicago remains a very segregated city and there’s clear racial boundary between places where more than 60% of the population are minority and places that’s majority white. In particular, northern and northeastern (CBD) part of Chicago are majority white while the remaining parts are all made up of racial minorities.

ggplot() + geom_sf(data = na.omit(tracts21), aes(fill = majority), color = NA) +
    scale_fill_manual(values = c("#BB3754", "#56106E"), name="Race Context") +
    labs(title = "Race Context ",
         caption = "Data: American Community Survey 2021") +
  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)
        ) 

Error is calculated by subtracting the observed robbery count from the prediction. A positive difference represents an over-prediction. The least ideal result is a model that over-predicts risk in minority areas, and under-predicts in white areas. If reporting selection bias is an issue, such a model may unfairly allocate police resource disproportionately in black and brown communities. In our case, over-prediction of minorities is an issue for random k-fold cv regression, but not for logo-cv regressions. The latter two models under-predict minority neighborhoods and over-predict majority white neighborhood, making it looks like that this algorithms generalizes well with respect to race.

joinrace <- st_centroid(reg.summary) %>% 
  st_intersection(tracts21 %>%dplyr:: select(pctMinority)) %>% 
  st_drop_geometry() %>% 
  group_by(cvID) %>% 
  summarize(meanMinor = mean(pctMinority))


reg.summary <- reg.summary %>% 
  left_join(joinrace, by = "cvID") 


reg.summary %>% 
  mutate(raceContext = ifelse(meanMinor > .6, "Minority", "Majority White")) %>% 
  st_drop_geometry() %>% 
  group_by(Regression, raceContext) %>%
  summarize(mean.Error = mean(Error, na.rm = T)) %>%
  spread(raceContext, mean.Error) %>%
  kable() %>%
    kable_styling(bootstrap_options = c("striped", "hover", "condensed")) %>%
    row_spec(2, color = "black", background = "#FCFFA4") %>%
    row_spec(4, color = "black", background = "#FCFFA4") 
Regression Majority White Minority
Random k-fold CV: Just Risk Factors 0.3945728 -0.0191392
Random k-fold CV: Spatial Process 0.5608446 -0.0273303
Spatial LOGO-CV: Just Risk Factors 0.5010425 -0.3246241
Spatial LOGO-CV: Spatial Process 0.2887490 -0.2015924

Kernel Desntiy Estimation

The utility of this algorithm is judged relative to an alternative police allocation method: kernel density estimation. Kernel density works by centering a smooth kernel, or curve, atop each crime point such that the curve is at its highest directly over the point and the lowest at the range of a circular search radius. The code block below creates a Kernel density map of robbery with a 1000 foot search radius.

rob_ppp <- as.ppp(st_coordinates(robbery21), W = st_bbox(final_net))
rob_KD.1000 <- spatstat.explore::density.ppp(rob_ppp, 1000)
rob_KD.df <- data.frame(rasterToPoints(mask(raster(rob_KD.1000), as(neighborhoods, 'Spatial'))))


ggplot(data=rob_KD.df, aes(x=x, y=y)) +
  geom_raster(aes(fill=layer)) + 
  coord_sf(crs=st_crs(final_net)) + 
  scale_fill_viridis(option = "magma", name="Density") +
  labs(title = "Kernel Density of Robbery 1000ft Radii") +
    theme(axis.text.x=element_blank(),
        axis.text.y=element_blank(),
        axis.ticks =element_blank(),
        axis.title.x = element_blank(),
        axis.title.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.8)
        )

Next, a new goodness of fit indicator is created to illustrate whether the 2021 kernel density or risk predictions capture more of the 2022 robberies.

robbery22 <- 
  read.socrata("https://data.cityofchicago.org/Public-Safety/Crimes-2022/9hwr-2zxp") %>% 
   filter(Primary.Type == "ROBBERY") %>% 
  mutate(x = gsub("[()]", "", Location)) %>%
  separate(x,into= c("Y","X"), sep=",") %>%
  mutate(X = as.numeric(X),
         Y = as.numeric(Y)) %>% 
  na.omit %>%
  st_as_sf(coords = c("X", "Y"), crs = 4326, agr = "constant") %>%
  st_transform('ESRI:102271') %>% 
  distinct() %>%
  .[fishnet,]

The kernel density estimation was classified into 5 risk categories and the count for the 2022 robberies were summarized into the same sf layer. The purpose here is to see if the risk predictions capture more observed burglaries than the kernel density. If so, then the risk prediction model provides a more robust targeting tool for allocating police resources.

rob_KDE_sum <- as.data.frame(rob_KD.1000) %>%
  st_as_sf(coords = c("x", "y"), crs = st_crs(final_net)) %>%
  aggregate(., final_net, mean) 

kde_breaks <- classIntervals(rob_KDE_sum$value, 
                             n = 5, "fisher")

rob_KDE_sf <- rob_KDE_sum %>%
  mutate(label = "Kernel Density",
         Risk_Category = classInt::findCols(kde_breaks),
         Risk_Category = case_when(
           Risk_Category == 5 ~ "5th",
           Risk_Category == 4 ~ "4th",
           Risk_Category == 3 ~ "3rd",
           Risk_Category == 2 ~ "2nd",
           Risk_Category == 1 ~ "1st")) %>%
  cbind(
    aggregate(
      dplyr::select(robbery21) %>% mutate(robberyCount = 1), ., sum) %>%
    mutate(robberyCount = replace_na(robberyCount, 0))) %>%
  dplyr::select(label, Risk_Category, robberyCount)

The same process was repeated for the risk prediction. Note that both predictions from the LOGO-CV with and without the spatial features is being used here.

ml_breaks <- classIntervals(reg.ss.spatialCV$Prediction, 
                             n = 5, "fisher")
rob_risk_sf <-
  reg.ss.spatialCV %>%
  mutate(label = "Risk Predictions Spatial",
         Risk_Category =classInt::findCols(ml_breaks),
         Risk_Category = case_when(
           Risk_Category == 5 ~ "5th",
           Risk_Category == 4 ~ "4th",
           Risk_Category == 3 ~ "3rd",
           Risk_Category == 2 ~ "2nd",
           Risk_Category == 1 ~ "1st")) %>%
  cbind(
    aggregate(
      dplyr::select(robbery21) %>% mutate(robberyCount = 1), ., sum) %>%
      mutate(robberyCount = replace_na(robberyCount, 0))) %>%
  dplyr::select(label,Risk_Category, robberyCount)

ml_breaks_simple <- classIntervals(reg.ss.cv$Prediction, 
                             n = 5, "fisher")
rob_risk_sf_simple <-
  reg.ss.cv %>%
  mutate(label = "Risk Predictions Simple",
         Risk_Category =classInt::findCols(ml_breaks_simple),
         Risk_Category = case_when(
           Risk_Category == 5 ~ "5th",
           Risk_Category == 4 ~ "4th",
           Risk_Category == 3 ~ "3rd",
           Risk_Category == 2 ~ "2nd",
           Risk_Category == 1 ~ "1st")) %>%
  cbind(
    aggregate(
      dplyr::select(robbery21) %>% mutate(robberyCount = 1), ., sum) %>%
      mutate(robberyCount = replace_na(robberyCount, 0))) %>%
  dplyr::select(label,Risk_Category, robberyCount)

Prediction Comparison

Below a map is generated of the risk categories for all three model types. A strongly fit model should show that the highest risk category is uniquely targeted to places with a high density of burglary points. The map shows that our risk prediction models perform as good as the kernal density approach in capturing the distribution of robbery incidents.

rbind(rob_KDE_sf, rob_risk_sf, rob_risk_sf_simple) %>%
  na.omit() %>%
  gather(Variable, Value, -label, -Risk_Category, -geometry) %>%
  ggplot() +
    geom_sf(aes(fill = Risk_Category), colour = NA) +
    facet_wrap(~label, ) +
    scale_fill_viridis(option = "magma", discrete = TRUE, name = "Risk Category") +
      geom_sf(data = sample_n(robbery22, 3000), size = .03, colour = "white") +
    labs(title="Comparison of Kernel Density and Risk Predictions",
         subtitle="2021 robbery risk predictions; 2022 robberies") + 
  theme(axis.text.x=element_blank(),
        axis.text.y=element_blank(),
        axis.ticks =element_blank(),
        axis.title.x = element_blank(),
        axis.title.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.8)
        )

A more straightforward way to compare the accuracy of prediction is through histogram. A well fit model should show that the risk predictions capture a greater share of 2022 robberies in the highest risk category relative to the Kernel density. Unfortunately, the kernel density model edges out both risk prediction model for highest risk areas. Nevertheless, the risk prediction model does a better job in capturing robbery incidents in lower-risk areas that the kernel density model had ignored.

rbind(rob_KDE_sf, rob_risk_sf, rob_risk_sf_simple) %>%
  st_drop_geometry() %>%
  na.omit() %>%
  gather(Variable, Value, -label, -Risk_Category) %>%
  group_by(label, Risk_Category) %>%
  summarize(countRobbery = sum(Value)) %>%
  ungroup() %>%
  group_by(label) %>%
  mutate(Pcnt_of_test_set_crimes = countRobbery / sum(countRobbery)) %>%
    ggplot(aes(Risk_Category,Pcnt_of_test_set_crimes)) +
      geom_bar(aes(fill=label), position="dodge", stat="identity") +
      scale_fill_viridis(option = "magma", discrete = TRUE, name = "Model", direction = -1) +
      labs(title = "Risk Prediction vs. Kernel Density",
           y = "% of Test Set Robberies (per model)",
           x = "Risk Category") +
  theme_bw() +
      theme(axis.text.x = element_text(angle = 45, vjust = 0.5))

Conclusions

In conclusion, there are still a lot of work that needs to be done before this model could be put into practice. Comparing to the traditional methodology of using kernel density to estimate crime locations, our model did not do very well in capturing robbery incidents in areas designated as the highest risk category. There are two main reasons behind. Firstly, most predictor variables we used in our model are out of date. While the Chicago Data Portal has been constantly updating its crime dataset, almost all of the risk factor dataset we used were last updated in 2019. That said, we are using reports of abandoned cars, graffiti, abandoned buildings, street light incidents, sanitation complaints, etc., from 2019 to fit a model that summarize robberies in 2021, which is then subsequently used to predict robberies in 2022. If there’s a correlation between locations of these risk factors and robbery incidents, then the removals of graffiti and renovations of abandoned buildings in 2021, for example, would very much likely to shift the geographies of robbery incidents. For a better prediction, we highly encourage the release of risk factor datasets that are up to date.

In addition, we believe that the global pandemic since 2020 would have had a significant impact on locations of robberies. Economic distress, driven by high unemployment rates, could create financial strain, leading to an increase in robberies in areas already facing economic challenges. Lockdowns and changed routines due to social distancing measures could also create opportunities for criminals. In addition, the reallocation of police resources to enforce public health measures would result in decreased police presence in areas that were regularly patrolled. All of these would contribute to a growing number of robbery incidents in area unexpected before. If we sticked with a more general model to predict robbery incidents during this very unusual time, then it is very likely that our model produces skewed results. This reminds us again the importance of ensuring the generalizability of any machine learning model not only across space but also time. In this case, the model should consider risk factors related to the pandemic.

Nevertheless, our model did a better job in capturing robbery incidents in lower-risk areas that the kernel density model had ignored. While being low risk, it is also important not to overlook potential robbery incidents in these areas. Ignoring the “risk” of low-risk area and overly emphasizing high risk areas would lead to over-policing in high-risk areas, which would continue to remain as high risk ever after. This leads us to think about whether using a single model is not enough to capture the complexities of robbery incidents in one place. A collective approach that strategically make use of the result of both the kernel density estimation and the risk prediction models can probably lead to more accurate predictions.

On top of that, we have avoided the overprediction of robbery incidents in majority non-white neighborhood. Yet, despite finding that the model generalizes well across different neighborhood contexts, we cannot be sure the model doesn’t suffer from selection bias. If law enforcement systematically over-polices certain communities, and this selection criteria goes unaccounted for in the model, then the model may be still biased regardless of the above tests. We also found that adding spatial process features improve the model and using the random k-fold cross validation method would enhance the model.

LS0tCnRpdGxlOiAiR2Vvc3BhdGlhbCBSaXNrIFByZWRpY3Rpb25zIG9mIFJvYmJlcnkgSW5jaWRlbnRzIGluIENoaWNhZ28iCmF1dGhvcjogIkVtaWx5IFpob3UiCmRhdGU6ICJgciBTeXMuRGF0ZSgpYCIKb3V0cHV0OgogIGh0bWxfZG9jdW1lbnQ6CiAgICB0aGVtZTogc2ltcGxleAogICAgdG9jOiB5ZXMKICAgIHRvY19mbG9hdDogeWVzCiAgICBjb2RlX2ZvbGRpbmc6IGhpZGUKICAgIGNvZGVfZG93bmxvYWQ6IHllcwplZGl0b3Jfb3B0aW9uczoKICBtYXJrZG93bjoKICAgIHdyYXA6IHNlbnRlbmNlCi0tLQoKVmVyc2lvbiAxLjAgXHwgRmlyc3QgQ3JlYXRlZCBPY3QgMTIsIDIwMjMgXHwgVXBkYXRlZCBPY3QgMTgsIDIwMjMKCktleXdvcmRzOiBQb2lzc29uIFJlZ3Jlc3Npb24sIFNwYXRpYWwgSG90c3BvdCBBbmFseXNpcywgTG9jYWwgTW9yYW4ncyBJLCBTcGF0aWFsIExPR08gQ3Jvc3MgVmFsaWRhdGlvbiwgSy1mb2xkIENyb3NzIFZhbGlkYXRpb24sIEsgTmVhcmVzdCBOZWlnaGJvcnMsIEtlcm5lbCBEZW5zaXR5IEVzdGltYXRpb24KCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUpCmBgYAoKCmBgYHtyIGxvYWQgbGlicmFyaWVzLCBpbmNsdWRlPUZBTFNFfQoKbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkoc2YpCmxpYnJhcnkoUlNvY3JhdGEpCmxpYnJhcnkodmlyaWRpcykKbGlicmFyeShzcGF0c3RhdC5leHBsb3JlKQpsaWJyYXJ5KHJhc3RlcikKbGlicmFyeShzcGRlcCkKbGlicmFyeShGTk4pCmxpYnJhcnkoZ3JpZCkKbGlicmFyeShncmlkRXh0cmEpCmxpYnJhcnkoa25pdHIpCmxpYnJhcnkoa2FibGVFeHRyYSkKbGlicmFyeSh0aWR5Y2Vuc3VzKQpsaWJyYXJ5KGNsYXNzSW50KSAgICMgZm9yIEtERSBhbmQgTUwgcmlzayBjbGFzcyBpbnRlcnZhbHMKbGlicmFyeShzdkRpYWxvZ3MpCmxpYnJhcnkocGF0Y2h3b3JrKQoKIyBmdW5jdGlvbnMKcm9vdC5kaXIgPSAiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3VyYmFuU3BhdGlhbC9QdWJsaWMtUG9saWN5LUFuYWx5dGljcy1MYW5kaW5nL21hc3Rlci9EQVRBLyIKc291cmNlKCJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vdXJiYW5TcGF0aWFsL1B1YmxpYy1Qb2xpY3ktQW5hbHl0aWNzLUxhbmRpbmcvbWFzdGVyL2Z1bmN0aW9ucy5yIikKCmNoaWNhZ29Cb3VuZGFyeSA8LSAKICBzdF9yZWFkKGZpbGUucGF0aChyb290LmRpciwiL0NoYXB0ZXI1L2NoaWNhZ29Cb3VuZGFyeS5nZW9qc29uIikpICU+JQogIHN0X3RyYW5zZm9ybSgnRVNSSToxMDIyNzEnKSAKCmBgYAoKCiMgSW50cm9kdWN0aW9uCgpQcmVkaWN0aXZlIHBvbGljaW5nIGlzIGFuIGFwcGxpY2F0aW9uIG9mIG1hY2hpbmUgbGVhcm5pbmcgdGhyb3VnaCBidWlsZGluZyBnZW9zcGF0aWFsIHJpc2sgbW9kZWxzIHRoYXQgYWdncmVnYXRlIGhpc3RvcmljYWwgY3JpbWUgZGF0YSwgc29jaW9lY29ub21pYyBmYWN0b3JzLCBhbmQgc3BhdGlhbCBhdHRyaWJ1dGVzIHRvIGZvcmVjYXN0IHdoZXJlIGFuZCB3aGVuIGNyaW1lcyBhcmUgbGlrZWx5IHRvIG9jY3VyLiBUaGVzZSBtb2RlbHMgYXJlIGNydWNpYWwgYmVjYXVzZSB0aGV5IG9mZmVyIGxhdyBlbmZvcmNlbWVudCBhZ2VuY2llcyBhIHByb2FjdGl2ZSBhcHByb2FjaCB0byBjcmltZSBwcmV2ZW50aW9uLiBXaGlsZSB0cmFkaXRpb25hbCBwb2xpY2luZyBtZXRob2Qgb2Z0ZW4gcmVseSBvbiByZWFjdGluZyB0byBpbmNpZGVudHMgYWZ0ZXIgdGhleSBvY2N1ciwgcHJlZGljdGl2ZSBwb2xpY2luZyBlbmFibGVzIGxhdyBlbmZvcmNlbWVudCB0byBhbnRpY2lwYXRlIGFuZCBwcmV2ZW50IGNyaW1lcyBpbiBhZHZhbmNlLiBCZWNhdXNlIG9mIHRoYXQsIGxhdyBlbmZvcmNlbWVudCBhZ2VuY2llcyBjYW4gb3B0aW1pemUgdGhlaXIgcmVzb3VyY2VzIGFuZCBkZXBsb3kgb2ZmaWNlcnMgc3RyYXRlZ2ljYWxseSwgbGVhZGluZyB0byBtb3JlIGNvc3QtZWZmZWN0aXZlIGxhdyBlbmZvcmNlbWVudCBwcmFjdGljZXMuIAoKSG93ZXZlciwgdGhlcmUgYXJlIG5vdGFibGUgbGltaXRhdGlvbnMgYW5kIGJpYXNlcyBhc3NvY2lhdGVkIHdpdGggdGhlc2UgbW9kZWxzLiBPbmUgc2lnbmlmaWNhbnQgY29uY2VybiBpcyB0aGUgcG90ZW50aWFsIGZvciBiaWFzIGluIHRoZSBoaXN0b3JpY2FsIGRhdGEgdXNlZCB0byB0cmFpbiB0aGVzZSBhbGdvcml0aG1zLiBJZiB0aGUgaGlzdG9yaWNhbCBkYXRhIHJlZmxlY3RzIGV4aXN0aW5nIGJpYXNlcyBpbiBsYXcgZW5mb3JjZW1lbnQgcHJhY3RpY2VzLCBzdWNoIGFzIHJhY2lhbCBwcm9maWxpbmcgb3IgZGlzY3JpbWluYXRvcnkgcG9saWNpZXMsIHRoZSBhbGdvcml0aG1zIGNhbiBwZXJwZXR1YXRlIHRoZXNlIGJpYXNlcywgbGVhZGluZyB0byB1bmZhaXIgb3IgZGlzcHJvcG9ydGlvbmF0ZSB0YXJnZXRpbmcgb2YgY2VydGFpbiBjb21tdW5pdGllcy4gQWRkaXRpb25hbGx5LCB0aGVyZSBpcyB0aGUgcmlzayBvZiBzZWxmLWZ1bGZpbGxpbmcgcHJvcGhlY2llczogaWYgbGF3IGVuZm9yY2VtZW50IGNvbmNlbnRyYXRlcyB0aGVpciBlZmZvcnRzIGluIHNwZWNpZmljIGFyZWFzIHByZWRpY3RlZCBieSB0aGUgYWxnb3JpdGhtLCBjcmltZSBkYXRhIGZyb20gdGhvc2UgYXJlYXMgbWlnaHQgYmUgaW5mbGF0ZWQsIGNyZWF0aW5nIGEgZmVlZGJhY2sgbG9vcCBvZiBvdmVyLXBvbGljaW5nLiBUaGF0IHNhaWQsIHByZWRpY3Rpb25zIGZyb20gZ2Vvc3BhdGlhbCByaXNrIG1vZGVscyBjYW4gYmUgdGhvdWdodCBvZiBhcyBsYXRlbnQgcmlzayAtIGFyZWFzIGF0IHJpc2sgZXZlbiBpZiBhIGNyaW1lIGhhcyBub3QgYWN0dWFsbHkgYmVlbiByZXBvcnRlZCB0aGVyZS4gU2VsZWN0aW9uIGJpYXMgY2FuIGJlIGEgcHJvYmxlbWF0aWMgaXNzdWUgZm9yIGl0IGlzIHZlcnkgbGlrZWx5IHRoYXQgb2JzZXJ2ZWQgY3JpbWUgd291bGQgZGlzY291bnQgYWN0dWFsIGNyaW1lIHJpc2tzLiAKCldpdGggdGhlc2UgaW4gbWluZCwgdGhlIGdvYWwgb2YgdGhpcyByZXBvcnQgaXMgdG8gZml0IGEgZ2Vvc3BhdGlhbCByaXNrIG1vZGVsIHByZWRpY3Rpbmcgcm9iYmVyeSBpbmNpZGVudHMgaW4gQ2hpY2FnbywgYSBjaXR5IGZhY2luZyB2YXJpb3VzIGNyaW1lIGFuZCBwb2xpY2luZyBjaGFsbGVuZ2VzLCB0byB0aGUgYmVzdCBvZiBvdXIgYWJpbGl0eS4gUm9iYmVyaWVzIGFyZSBvZnRlbiBzdWJqZWN0ZWQgdG8gc2VsZWN0aW9uIGJpYXMgYmVjYXVzZSAxKSBjZXJ0YWlubHkgbm90IGFsbCBpbmNpZGVudHMgb2Ygcm9iYmVyeSBhcmUgcmVwb3J0ZWQgdG8gdGhlIHBvbGljZSBhbmQgMikgYXJlYXMgdGhhdCBoYWQgYmVlbiBoZWF2aWx5IHBhdHJvbGxlZCBpbiB0aGUgcGFzdCBkdWUgdG8gaGlnaGVyIHJvYmJlcnkgaW5jaWRlbnRzIHdpbGwgY29udGludWUgdG8gcmVwb3J0IGhpZ2hlciByb2JiZXJ5IGluY2lkZW50cy4gRm9yIGFueSB0eXBlIG9mIGNyaW1lLCB0aGUgaHlwb3RoZXNpcyBpcyB0aGF0IGNyaW1lIHJpc2sgaXMgYSBmdW5jdGlvbiBvZiBleHBvc3VyZSB0byBhIHNlcmllcyBvZiBnZW9zcGF0aWFsIHJpc2sgYW5kIHByb3RlY3RpdmUgZmFjdG9ycywgc3VjaCBhcyBibGlnaHQgb3IgcmVjcmVhdGlvbiBjZW50ZXJzLCByZXNwZWN0aXZlbHkuIEFzIGV4cG9zdXJlIHRvIHJpc2sgZmFjdG9ycyBpbmNyZWFzZXMsIHNvIGRvZXMgY3JpbWUgcmlzay4gSW4gYWRkaXRpb24gdG8gaWRlbnRpZnlpbmcgcmlzayBmYWN0b3JzLCB0aGUgbW9kZWwgdXNlZCBpbiB0aGlzIHJlcG9ydCBhbHNvIGNvbnNpZGVycyBzcGF0aWFsIGZhY3RvcnMgdG8gaW1wcm92ZSBhY2N1cmFjeS4gUG9pc3NvbiByZWdyZXNzaW9uIGlzIHVzZWQgaGVyZSBmb3IgaXRzIGNvbXBhdGliaWxpdHkgd2l0aCBjb3VudCBkYXRhLiBBbGwgZGF0YSBzZXRzIHVzZWQgZm9yIHByZWRpY3Rpb25zIGhlcmUgYXJlIGRvd25sb2FkZWQgZnJvbSBbQ2hpY2FnbyBEYXRhIFBvcnRhbF0oaHR0cHM6Ly9kYXRhLmNpdHlvZmNoaWNhZ28ub3JnLykuIAoKVGhpcyByZXBvcnQgaXMgc3RydWN0dXJlZCBhcyBmb2xsb3c6ICoqRmlyc3RseSoqLCB3ZSBkaWQgYSBjbG9zZSBleGFtaW5hdGlvbiBvZiBvdXIgZGVwZW5kZW50IHZhcmlhYmxlLCBpbiB0aGlzIGNhc2Ugcm9iYmVyeSBpbmNpZGVudHMuKipTZWNvbmRseSoqLCB3ZSBkaXNjdXNzIG91ciByYXRpb25hbCBvZiBvdXIgc2VsZWN0aW9uIG9mIHJpc2sgZmFjdG9ycyBhbmQgcHJlc2VudGVkIG91ciBhcHByb2FjaCB0byBmZWF0dXJlIGVuZ2luZWVyaW5nLCB3aGljaCBpbmNsdWRlcyB0aGUgY2FsY3VsYXRpb24gb2YgbmVhcmVzdCBkaXN0YW5jZSB0byBlYWNoIHJpc2sgZmFjdG9ycyBhbmQgc3BhdGlhbCBob3RzcG90cy4gKipUaGlyZGx5KiosIHdlIGZpdCB0d28gcmVncmVzc2lvbiBtb2RlbHM6IG9uZSBzaW1wbGUgcG9pc3NvbiByZWdyZXNzaW9uIGFuZCBvbmUgdGhhdCBhY2NvdW50IGZvciBzcGF0aWFsIGF0dHJpYnV0ZXMuIFdlIHN1YnNlcXVlbnRseSBwZXJmb3JtIHR3byBraW5kcyBvZiBjcm9zcyB2YWxpZGF0aW9uOiBvbmUgcmFuZG9tIGstZm9sZCBjcm9zcyB2YWxpZGF0aW9uLiAqKkZpbmFsbHkqKiwgd2UgY29tcGFyZWQgdGhlIGVycm9ycyBvZiBkaWZmZXJlbnQgcmVncmVzc2lvbiBtb2RlbHMsIHRlc3RlZCB0aGVpciBnZW5lcmFsaXphdGlvbiwgYW5kIGNvbXBhcmUgb3VyIG1vZGVscyB0byB0aGUgdHJhZGl0aW9uYWwga2VybmVsIGRlbnNpdHkgZXN0aW1hdGlvbi4gCgpUaGUgR2l0SHViIHJlcG9zaXRvcnkgZm9yIHRoaXMgcmVwb3J0IGlzIFtoZXJlXShodHRwczovL2dpdGh1Yi5jb20vZW1pbHl6aG91MTEyL01VU0E1MDgwLUNoaWNhZ28tUmlza3MpLiAKCgojIFJvYmJlcnkgSW5jaWRlbnRzCgpUaGUgQ2hpY2FnbyBEYXRhIFBvcnRhbCBpcyBhbiBvbmxpbmUgcGxhdGZvcm0gdGhhdCBzZXJ2ZXMgYXMgYSBjZW50cmFsIHJlcG9zaXRvcnkgZm9yIGEgd2lkZSByYW5nZSBvZiBtdW5pY2lwYWwgZGF0YSBzZXRzLiBUaGUgcG9ydGFsIG1haW50YWlucyBhIHJlY29yZCBmb3IgY3JpbWUgaW5jaWRlbnRzIGZvciBDaGljYWdvIGV2ZXJ5IHllYXIuIEluIHRoaXMgcmVwb3J0LCBjcmltZSBkYXRhIGZvciAyMDIxIGlzIGRvd25sb2FkZWQgdXNpbmcgdGhlIGBSU29jcmF0YWAgcGFja2FnZS4gU29jcmF0YSBpcyB0aGUgY3JlYXRvciBvZiB0aGUgb3BlbiBzb3VyY2UgcGxhdGZvcm0gdGhhdCBDaGljYWdvIHVzZXMgdG8gc2hhcmUgaXRzIGRhdGEuIEFmdGVyIGRvd25sb2FkaW5nLCByb2JiZXJ5IGluY2lkZW50cyB3ZXJlIGZpbHRlcmVkIG91dC4gU29tZSBhZGRpdGlvbmFsIGRhdGEgd3JhbmdsaW5nIHJlbW92ZXMgZXh0cmFuZW91cyBjaGFyYWN0ZXJzIGZyb20gdGhlIGBMb2NhdGlvbmAgZmllbGQgd2l0aCBnc3ViLiBUaGUgcmVzdWx0aW5nIGZpZWxkIGlzIHRoZW4gY29udmVydGVkIHRvIHNlcGFyYXRlIGZpZWxkcyBvZiBYIGFuZCBZIGNvb3JkaW5hdGVzLiAKCmBgYHtyIGxvYWQgb3V0Y29tZSB2YXJpYWJsZSwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRSwgcmVzdWx0cz0naGlkZSd9Cgpyb2JiZXJ5MjEgPC0gCiAgcmVhZC5zb2NyYXRhKCJodHRwczovL2RhdGEuY2l0eW9mY2hpY2Fnby5vcmcvUHVibGljLVNhZmV0eS9DcmltZXMtMjAyMS9kd21lLXQ5NmMiKSAlPiUgCiAgICBmaWx0ZXIoUHJpbWFyeS5UeXBlID09ICJST0JCRVJZIikgJT4lCiAgICBtdXRhdGUoeCA9IGdzdWIoIlsoKV0iLCAiIiwgTG9jYXRpb24pKSAlPiUKICAgIHNlcGFyYXRlKHgsaW50bz0gYygiWSIsIlgiKSwgc2VwPSIsIikgJT4lCiAgICBtdXRhdGUoWCA9IGFzLm51bWVyaWMoWCksWSA9IGFzLm51bWVyaWMoWSkpICU+JSAKICAgIG5hLm9taXQoKSAlPiUKICAgIHN0X2FzX3NmKGNvb3JkcyA9IGMoIlgiLCAiWSIpLCBjcnMgPSA0MzI2LCBhZ3IgPSAiY29uc3RhbnQiKSU+JQogICAgc3RfdHJhbnNmb3JtKCdFU1JJOjEwMjI3MScpICU+JSAKICAgIGRpc3RpbmN0KCkKYGBgCgpBIHF1aWNrIG1hcHBpbmcgb2YgZWFjaCBpbmRpdmlkdWFsIGluY2lkZW50IGluIENoaWNhZ28gcmV2ZWFscyB0aGF0IHJvYmJlcmllcyBhcmUgcHJldHR5IHNjYXR0ZXJlZCBpbiBhbGwgcGFydHMgb2YgdGhlIGNpdHksIHRob3VnaCBzbGlnaHRseSBtb3JlIGNvbmNlbnRyYXRlZCBpbiBjZW50cmFsIGFuZCBub3J0aGVhc3QgQ2hpY2Fnby4gCgpgYGB7ciByb2JiZXJ5IGxvY2F0aW9ucywgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRSwgZmlnLmFsaWduID0gJ2NlbnRlcid9CgpnZ3Bsb3QoKSArIAogIGdlb21fc2YoZGF0YSA9IGNoaWNhZ29Cb3VuZGFyeSwgZmlsbCA9ICJibGFjayIpICsKICBnZW9tX3NmKGRhdGEgPSByb2JiZXJ5MjEsIGNvbG91cj0iI0JCMzc1NCIsIHNpemU9MC4xLCBzaG93LmxlZ2VuZCA9ICJwb2ludCIpICsKICBsYWJzKHRpdGxlPSAiUm9iYmVyeSBJbmNpZGVudHMgaW4gQ2hpY2FnbyAyMDIxIikgKyAKICB0aGVtZShheGlzLnRleHQueD1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGlja3MgPWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aXRsZS55ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDksZmFjZSA9ICJpdGFsaWMiKSwKICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgZmFjZSA9ICJib2xkIiksCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X3JlY3QoY29sb3VyID0gImdyZXkiLCBmaWxsPU5BLCBsaW5ld2lkdGggPTAuOCkKICAgICAgICApCgpgYGAKClRoZSBkZW5zaXR5IG1hcCBzaG93cyB0aHJlZSBjbGVhciByb2JiZXJ5IGhvdHNwb3RzIGluIENoaWNhZ28uIFRoZSBtb3N0IHByb21pbmVudCBob3RzcG90cyBhcmUgbG9jYXRlZCBpbiBub3J0aCBlYXN0IGFuZCBub3J0aCB3ZXN0IHBhcnQgb2YgQ2hpY2Fnbywgd2l0aCBzb21lIHNtYWxsZXIgaG90c3BvdHMgaW4gdGhlIHNvdXRoZXJuIHBhcnQuIAoKYGBge3Igcm9iYmVyeSBkZW5zaXR5LCBmaWcud2lkdGg9NSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgZmlnLmFsaWduID0gJ2NlbnRlcid9Cm9wdGlvbnMoc2NpcGVuPTApCmdncGxvdCgpICsgCiAgZ2VvbV9zZihkYXRhID0gY2hpY2Fnb0JvdW5kYXJ5LCBmaWxsID0gImJsYWNrIikgKwogIHN0YXRfZGVuc2l0eTJkKGRhdGEgPSBkYXRhLmZyYW1lKHN0X2Nvb3JkaW5hdGVzKHJvYmJlcnkyMSkpLCAKICAgICAgICAgICAgICAgICBhZXMoWCwgWSwgZmlsbCA9IC4ubGV2ZWwuLiwgYWxwaGEgPSAuLmxldmVsLi4pLAogICAgICAgICAgICAgICAgIHNpemUgPSAwLjAxLCBiaW5zID0gNjAsIGdlb20gPSAncG9seWdvbicpICsKICBzY2FsZV9maWxsX3ZpcmlkaXMob3B0aW9uID0gIm1hZ21hIiwgbmFtZSA9ICJEZW5zaXR5IikgKwogIHNjYWxlX2FscGhhKHJhbmdlID0gYygwLjAwLCAwLjM1KSwgZ3VpZGUgPSAibm9uZSIpICsKICBsYWJzKHRpdGxlID0gIkRlbnNpdHkgb2YgUm9iYmVyeSIpICsKICB0aGVtZShheGlzLnRleHQueD1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGlja3MgPWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aXRsZS55ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDksZmFjZSA9ICJpdGFsaWMiKSwKICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgZmFjZSA9ICJib2xkIiksCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X3JlY3QoY29sb3VyID0gImdyZXkiLCBmaWxsPU5BLCBsaW5ld2lkdGg9MC44KQogICAgICAgICkKYGBgCgpTaW5jZSBjcmltZSByaXNrIGlzIG5vdCBhcyBhIHBoZW5vbWVub24gdGhhdCB2YXJpZXMgYWNyb3NzIGFkbWluaXN0cmF0aXZlIHVuaXRzLCBidXQgb25lIHZhcnlpbmcgc21vb3RobHkgYWNyb3NzIHRoZSBsYW5kc2NhcGUsIHRoZSBiZXN0IHdheSB0byByZXByZXNlbnQgdGhpcyBzcGF0aWFsIHRyZW5kIGluIGEgcmVncmVzc2lvbi1yZWFkeSBmb3JtIGlzIHRvIGFnZ3JlZ2F0ZSBwb2ludC1sZXZlbCBkYXRhIGludG8gYSBsYXR0aWNlIG9mIGdyaWQgY2VsbHMuIFRoZSBjb2RlIGJlbG93IGdlbmVyYXRlcyBhIGZpc2huZXQgb2YgQ2hpY2FnbyB0byBhY2hpZXZlIHRoaXMgZ29hbC4gCgoKYGBge3IgY3JlYXRlIGZpc2huZXQsIGZpZy53aWR0aD01LCBmaWcuYWxpZ24gPSAnY2VudGVyJ30KZmlzaG5ldCA8LSAKICBzdF9tYWtlX2dyaWQoY2hpY2Fnb0JvdW5kYXJ5LAogICAgICAgICAgICAgICBjZWxsc2l6ZSA9IDUwMCwgCiAgICAgICAgICAgICAgIHNxdWFyZSA9IFRSVUUpICU+JQogIC5bY2hpY2Fnb0JvdW5kYXJ5XSAlPiUgICAgICAgICAgICAjIGZhc3Qgd2F5IHRvIHNlbGVjdCBpbnRlcnNlY3RpbmcgcG9seWdvbnMKICBzdF9zZigpICU+JQogIG11dGF0ZSh1bmlxdWVJRCA9IDE6bigpKQoKZ2dwbG90KCkgKwogIGdlb21fc2YoZGF0YT1maXNobmV0LCBjb2xvcj0iYmxhY2siLCBmaWxsPSIjRkZGNUVFIikgKwogIGxhYnModGl0bGUgPSAiRmlzaG5ldCBvZiBDaGljYWdvIikgKwogIHRoZW1lKGF4aXMudGV4dC54PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRleHQueT1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aWNrcyA9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpdGxlLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gOSxmYWNlID0gIml0YWxpYyIpLAogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSwKICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfcmVjdChjb2xvdXIgPSAiZ3JleSIsIGZpbGw9TkEsIGxpbmV3aWR0aD0wLjgpCiAgICAgICAgKQpgYGAKCkFmdGVyIGdlbmVyYXRpbmcgdGhlIGZpc2huZXQsIHdlIHVzZWQgdGhhdCBhcyBhIHNwYXRpYWwgdW5pdCBmb3IgdmlzdWFsaXppbmcgcm9iYmVyeSBpbmNpZGVudHMsIGZyb20gd2hpY2ggd2UgbWF5IHNlZSB0aGF0IG51bWJlciBvZiByb2JiZXJpZXMgYXJlIHRoZSBoaWdoZXN0IGluIGEgY291cGxlIG9mIGdpcmRzIGluIG5vcnRoZWFzdCBDaGljYWdvLiBUaGlzIGlzIHdoZXJlIHRoZSBjbHVzdGVyZWQgc3BhdGlhbCBwcm9jZXNzIG9mIHJvYmJlcnkgYmVnaW5zIHRvIHRha2Ugc2hhcGUuIAoKYGBge3Igam9pbiByb2JiZXJ5IHRvIGZpc2huZXQsIGZpZy5hbGlnbiA9ICdjZW50ZXInfQoKcm9iYmVyeTIxX25ldCA8LSAKICBkcGx5cjo6c2VsZWN0KHJvYmJlcnkyMSkgJT4lIAogIG11dGF0ZShjb3VudFJvYmJlcnkgPSAxKSAlPiUgCiAgYWdncmVnYXRlKC4sIGZpc2huZXQsIHN1bSkgJT4lCiAgbXV0YXRlKGNvdW50Um9iYmVyeSA9IHJlcGxhY2VfbmEoY291bnRSb2JiZXJ5LCAwKSwKICAgICAgICAgdW5pcXVlSUQgPSAxOm4oKSwKICAgICAgICAgY3ZJRCA9IHNhbXBsZShyb3VuZChucm93KGZpc2huZXQpIC8gMjQpLCAKICAgICAgICAgICAgICAgICAgICAgICBzaXplPW5yb3coZmlzaG5ldCksIHJlcGxhY2UgPSBUUlVFKSkKCmdncGxvdCgpICsKICBnZW9tX3NmKGRhdGEgPSByb2JiZXJ5MjFfbmV0LCBhZXMoZmlsbCA9IGNvdW50Um9iYmVyeSksIGNvbG9yID0gTkEpICsKICBzY2FsZV9maWxsX3ZpcmlkaXMob3B0aW9uID0gIm1hZ21hIiwgbmFtZSA9ICJSb2JiZXJ5IENvdW50cyIpICsKICBsYWJzKHRpdGxlID0gIkNvdW50IG9mIFJvYmJlcmllcyBmb3IgdGhlIEZpc2huZXQiKSArCiAgdGhlbWUoYXhpcy50ZXh0Lng9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGV4dC55PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpY2tzID1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGl0bGUueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSA5LGZhY2UgPSAiaXRhbGljIiksCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIsIGZhY2UgPSAiYm9sZCIpLAogICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9yZWN0KGNvbG91ciA9ICJncmV5IiwgZmlsbD1OQSwgbGluZXdpZHRoPTAuOCkKICAgICAgICApCmBgYAoKVGhlIGZpZ3VyZSBiZWxvdyBzaG93cyB0aGUgZGlzdHJpYnV0aW9uIG9mIHJvYmJlcnkgaW5jaWRlbnRzIGluIENoaWNhZ28sIHdoaWNoIGlzIG5vbi1ub3JtYWxseSBkaXN0cmlidXRlZCB3aXRoIGEgZmV3IGdyaWRzIGhhdmluZyBzaWduaWZpY2FudGx5IGhpZ2hlciBudW1iZXIgb2Ygcm9iYmVyaWVzLiBUbyBhY2NvdW50IGZvciB0aGlzIG5vbi1ub3JtYWxpdHksIGl0IGlzIG5lY2Vzc2FyeSB0byB1c2UgUG9zc2lvbiByZWdyZXNzaW9uIGZvciB0aGlzIGFuYWx5c2lzLiAKCmBgYHtyIGRpc3RyaWJ1dGlvbiBvZiByb2JiZXJpZXMsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGZpZy5hbGlnbiA9ICdjZW50ZXInfQpnZ3Bsb3Qocm9iYmVyeTIxX25ldCwgYWVzKHggPSBjb3VudFJvYmJlcnkpKSArCiAgZ2VvbV9oaXN0b2dyYW0oIGZpbGw9IiM1NjEwNkUiLCBjb2xvcj0iI2U5ZWNlZiIpICsgCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSkgKwogIHRoZW1lKHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDksZmFjZSA9ICJpdGFsaWMiKSwKICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgZmFjZSA9ICJib2xkIiksIAogICAgICAgIGF4aXMudGV4dC54PWVsZW1lbnRfdGV4dChzaXplPTYpLAogICAgICAgIGF4aXMudGV4dC55PWVsZW1lbnRfdGV4dChzaXplPTYpLCAKICAgICAgICBheGlzLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTgpLCAKICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfcmVjdChjb2xvdXIgPSAiZ3JleSIsIGZpbGw9TkEsIHNpemU9MC44KSkgKyAKICBsYWJzKHRpdGxlID0gIkRpc3RyaWJ1dGlvbiBvZiBSb2JiZXJpZXMgaW4gQ2hpY2FnbyAyMDIxIiwKICAgICAgIGNhcHRpb24gPSAiRGF0YTogQ2hpY2FnbyBEYXRhIFBvcnRhbCBDcmltZXMgMjAyMSIpICsKICB4bGFiKCJSb2JiZXJ5IEluY2lkZW50cyIpICsKICB5bGFiKCJDb3VudCIpCmBgYAoKIyBSaXNrIEZhY3RvcnMKCldlIGluY2x1ZGVkIGluIG91ciBtb2RlbCBlaWdodCBkaWZmZXJlbnQgcmlzayBmYWN0b3JzLiBUaGlzIGluY2x1ZGVzIDMxMSByZXBvcnRzIG9mIGFiYW5kb25lZCBjYXJzLCBzdHJlZXQgbGlnaHRzIG91dCwgZ3JhZmZpdGkgcmVtZWRpYXRpb24sIHNhbml0YXRpb24gY29tcGxhaW50cywgIGFiYW5kb24gYnVpbGRpbmdzLCBsb2NhdGlvbiBvZiByZXRhaWwgc3RvcmVzIHRoYXQgc2VsbCBsaXF1b3IgdG8gZ28sIGxvY2F0aW9uIG9mIHNob3RzcG90dGVyLCBhbmQgYmFua3MuIEhlcmUgYXJlIHRoZSByYXRpb25hbGUgZm9yIGluY2x1ZGluZyB0aGVzZSBwcmVkaWN0b3JzIGluIHRoZSBtb2RlbC4gCgoqKkFiYW5kb25lZCBDYXJzOioqIE5laWdoYm9yaG9vZHMgd2l0aCBhIGhpZ2ggbnVtYmVyIG9mIGFiYW5kb25lZCBjYXJzIG1pZ2h0IGluZGljYXRlIGVjb25vbWljIGRpc3RyZXNzLCBsYWNrIG9mIGNvbW11bml0eSBlbmdhZ2VtZW50LCBhbmQgbG93ZXIgbGV2ZWxzIG9mIHN1cnZlaWxsYW5jZS4gVGhlc2UgY29uZGl0aW9ucyBjYW4gY3JlYXRlIGFuIGVudmlyb25tZW50IGNvbmR1Y2l2ZSB0byBjcmltaW5hbCBhY3Rpdml0aWVzIGxpa2Ugcm9iYmVyaWVzLgoKKipTdHJlZXQgTGlnaHRzIE91dDoqKiBQb29ybHkgbGl0IGFyZWFzIGFyZSBvZnRlbiB0YXJnZXRlZCBieSBjcmltaW5hbHMgYmVjYXVzZSBkYXJrbmVzcyBwcm92aWRlcyBjb3Zlci4gUmVwb3J0cyBvZiBzdHJlZXQgbGlnaHRzIG91dCBjYW4gaGlnaGxpZ2h0IGFyZWFzIHdpdGggcmVkdWNlZCB2aXNpYmlsaXR5LCBtYWtpbmcgdGhlbSBwb3RlbnRpYWwgaG90c3BvdHMgZm9yIHJvYmJlcmllcy4KCioqR3JhZmZpdGkgUHJlc2VuY2UqKjogQXJlYXMgd2l0aCBmcmVxdWVudCBncmFmZml0aSBtaWdodCBzdWdnZXN0IGEgbGFjayBvZiBjb21tdW5pdHkgbWFpbnRlbmFuY2UgYW5kIG92ZXJzaWdodC4gU3VjaCBhcmVhcyBjYW4gYmUgcGVyY2VpdmVkIGFzIG5lZ2xlY3RlZCwgbWFraW5nIHRoZW0gYXR0cmFjdGl2ZSBmb3IgY3JpbWluYWwgYWN0aXZpdGllcywgaW5jbHVkaW5nIHJvYmJlcmllcy4KCioqU2FuaXRhdGlvbiBDb21wbGFpbnRzOioqIFNpbWlsYXJseSwgbmVpZ2hib3Job29kcyB3aXRoIHNhbml0YXRpb24gaXNzdWVzIG1pZ2h0IHN1Z2dlc3QgdGhlIGxhY2sgb2YgbWFpbnRlbmFuY2UuIFRoaXMgYnJlYWtkb3duIGluIGNvbW11bml0eSBjYW4gY29udHJpYnV0ZSB0byBhbiBlbnZpcm9ubWVudCB3aGVyZSBjcmltaW5hbCBhY3Rpdml0aWVzLCBzdWNoIGFzIHJvYmJlcmllcywgYXJlIG1vcmUgbGlrZWx5IHRvIG9jY3VyLgoKKipBYmFuZG9uZWQgQnVpbGRpbmdzOioqIEFiYW5kb25lZCBidWlsZGluZ3MgYWxzbyBzZXJ2ZSBhcyBoaWRlb3V0cyBmb3IgY3JpbWluYWxzIGFuZCBjYW4gYmUgcGVyY2VpdmVkIGFzIHVuc2FmZSBieSByZXNpZGVudHMuIFRoZXkgY3JlYXRlIGEgc2Vuc2Ugb2YgZGlzb3JkZXIgYW5kIGNhbiBhdHRyYWN0IGNyaW1pbmFsIGVsZW1lbnRzLCBtYWtpbmcgbmVhcmJ5IGFyZWFzIG1vcmUgc3VzY2VwdGlibGUgdG8gcm9iYmVyaWVzLgoKKipSZXRhaWwgU3RvcmVzIFNlbGxpbmcgTGlxdW9yIHRvIEdvOioqIExpcXVvciBzdG9yZXMgYXJlIGFzc29jaWF0ZWQgd2l0aCBhbGNvaG9sIGNvbnN1bXB0aW9uLiBBcmVhcyB3aXRoIGEgaGlnaCBjb25jZW50cmF0aW9uIG9mIGxpcXVvciBzdG9yZXMgbWlnaHQgaW5kaWNhdGUgaGlnaGVyIGxlYWQgdG8gaGlnaGVyIGFsY29ob2wgcmVsYXRlZCBjcmltZSAodGhvdWdoIHRoaXMgaXMgYSB2ZXJ5IHN1YmplY3RpdmUgc3RhdGVtZW50KSBiZWNhdXNlIGluZWJyaWF0ZWQgaW5kaXZpZHVhbHMsIHBhcnRpY3VsYXJseSBkdXJpbmcgbGF0ZSBob3VycywgbWlnaHQgYmUgbW9yZSB2dWxuZXJhYmxlIGFuZCBsZXNzIGF3YXJlIG9mIHRoZWlyIHN1cnJvdW5kaW5ncywgbWFraW5nIHRoZW0gZWFzeSB0YXJnZXRzIGZvciByb2JiZXJpZXMuIAoKKipMb2NhdGlvbiBvZiBTaG90U3BvdHRlcjoqKiBTaG90U3BvdHRlciB0ZWNobm9sb2d5IGRldGVjdHMgZ3VuZmlyZSBpbiByZWFsLXRpbWUgYW5kIGFyZWFzIHdpdGggZnJlcXVlbnQgZ3Vuc2hvdCBpbmNpZGVudHMgbGlrZWx5IGV4cGVyaWVuY2UgaGlnaGVyIGNyaW1lIHJhdGVzLCBpbmNsdWRpbmcgcm9iYmVyaWVzLiBUaGVzZSBsb2NhdGlvbnMgY2FuIGJlIGNvbnNpZGVyZWQgYXMgcG90ZW50aWFsIHJpc2sgZmFjdG9ycyBmb3IgcHJlZGljdGluZyByb2JiZXJ5IGluY2lkZW50cy4KCioqQmFua3M6KiogQmFuayBsb2NhdGlvbnMgYXJlIG9mdGVuIHRhcmdldGVkIGR1ZSB0byB0aGUgYXZhaWxhYmlsaXR5IG9mIGNhc2guIFNpbmNlIHdlIGFyZSBwcmVkaWN0aW5nIHJvYmJlcmllcyBoZXJlLCBhcmVhcyB3aXRoIGEgaGlnaCBkZW5zaXR5IG9mIGJhbmtzIG1pZ2h0IGF0dHJhY3Qgcm9iYmVycyBzZWVraW5nIGltbWVkaWF0ZSBmaW5hbmNpYWwgZ2FpbnMuCgpgYGB7ciBsb2FkIHByZWRpY3RvciB2YXJpYWJsZXMsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9CmFiYW5kb25DYXJzIDwtIAogIHJlYWQuc29jcmF0YSgiaHR0cHM6Ly9kYXRhLmNpdHlvZmNoaWNhZ28ub3JnL1NlcnZpY2UtUmVxdWVzdHMvMzExLVNlcnZpY2UtUmVxdWVzdHMtQWJhbmRvbmVkLVZlaGljbGVzLzNjOXYtcG52YSIpICU+JQogICAgbXV0YXRlKHllYXIgPSBzdWJzdHIoY3JlYXRpb25fZGF0ZSwxLDQpKSAlPiUgZmlsdGVyKHllYXIgPT0gIjIwMjAiKSAlPiUKICAgIGRwbHlyOjpzZWxlY3QoWSA9IGxhdGl0dWRlLCBYID0gbG9uZ2l0dWRlKSAlPiUKICAgIG5hLm9taXQoKSAlPiUKICAgIHN0X2FzX3NmKGNvb3JkcyA9IGMoIlgiLCAiWSIpLCBjcnMgPSA0MzI2LCBhZ3IgPSAiY29uc3RhbnQiKSAlPiUKICAgIHN0X3RyYW5zZm9ybShzdF9jcnMoZmlzaG5ldCkpICU+JQogICAgbXV0YXRlKExlZ2VuZCA9ICJBYmFuZG9uZWRfQ2FycyIpCiAgCmFiYW5kb25CdWlsZGluZ3MgPC0gCiAgcmVhZC5zb2NyYXRhKCJodHRwczovL2RhdGEuY2l0eW9mY2hpY2Fnby5vcmcvU2VydmljZS1SZXF1ZXN0cy8zMTEtU2VydmljZS1SZXF1ZXN0cy1WYWNhbnQtYW5kLUFiYW5kb25lZC1CdWlsZGluZy83bmlpLTdzcmQiKSAlPiUKICAgIG11dGF0ZSh5ZWFyID0gc3Vic3RyKGRhdGVfc2VydmljZV9yZXF1ZXN0X3dhc19yZWNlaXZlZCwxLDQpKSAlPiUgIGZpbHRlcih5ZWFyID09ICIyMDE4IikgJT4lCiAgICBkcGx5cjo6c2VsZWN0KFkgPSBsYXRpdHVkZSwgWCA9IGxvbmdpdHVkZSkgJT4lCiAgICBuYS5vbWl0KCkgJT4lCiAgICBzdF9hc19zZihjb29yZHMgPSBjKCJYIiwgIlkiKSwgY3JzID0gNDMyNiwgYWdyID0gImNvbnN0YW50IikgJT4lCiAgICBzdF90cmFuc2Zvcm0oc3RfY3JzKGZpc2huZXQpKSAlPiUKICAgIG11dGF0ZShMZWdlbmQgPSAiQWJhbmRvbmVkX0J1aWxkaW5ncyIpCgpncmFmZml0aSA8LSAKICByZWFkLnNvY3JhdGEoImh0dHBzOi8vZGF0YS5jaXR5b2ZjaGljYWdvLm9yZy9TZXJ2aWNlLVJlcXVlc3RzLzMxMS1TZXJ2aWNlLVJlcXVlc3RzLUdyYWZmaXRpLVJlbW92YWwtSGlzdG9yaWNhbC9oZWM1LXk0eDUiKSAlPiUKICAgIG11dGF0ZSh5ZWFyID0gc3Vic3RyKGNyZWF0aW9uX2RhdGUsMSw0KSkgJT4lIGZpbHRlcih5ZWFyID09ICIyMDE4IikgJT4lCiAgICBmaWx0ZXIod2hlcmVfaXNfdGhlX2dyYWZmaXRpX2xvY2F0ZWRfICVpbiUgYygiRnJvbnQiLCAiUmVhciIsICJTaWRlIikpICU+JQogICAgZHBseXI6OnNlbGVjdChZID0gbGF0aXR1ZGUsIFggPSBsb25naXR1ZGUpICU+JQogICAgbmEub21pdCgpICU+JQogICAgc3RfYXNfc2YoY29vcmRzID0gYygiWCIsICJZIiksIGNycyA9IDQzMjYsIGFnciA9ICJjb25zdGFudCIpICU+JQogICAgc3RfdHJhbnNmb3JtKHN0X2NycyhmaXNobmV0KSkgJT4lCiAgICBtdXRhdGUoTGVnZW5kID0gIkdyYWZmaXRpIikKCnN0cmVldExpZ2h0c091dCA8LSAKICByZWFkLnNvY3JhdGEoImh0dHBzOi8vZGF0YS5jaXR5b2ZjaGljYWdvLm9yZy9TZXJ2aWNlLVJlcXVlc3RzLzMxMS1TZXJ2aWNlLVJlcXVlc3RzLVN0cmVldC1MaWdodHMtQWxsLU91dC96dXhpLTd4ZW0iKSAlPiUKICAgIG11dGF0ZSh5ZWFyID0gc3Vic3RyKGNyZWF0aW9uX2RhdGUsMSw0KSkgJT4lIGZpbHRlcih5ZWFyID09ICIyMDE4IikgJT4lCiAgICBkcGx5cjo6c2VsZWN0KFkgPSBsYXRpdHVkZSwgWCA9IGxvbmdpdHVkZSkgJT4lCiAgICBuYS5vbWl0KCkgJT4lCiAgICBzdF9hc19zZihjb29yZHMgPSBjKCJYIiwgIlkiKSwgY3JzID0gNDMyNiwgYWdyID0gImNvbnN0YW50IikgJT4lCiAgICBzdF90cmFuc2Zvcm0oc3RfY3JzKGZpc2huZXQpKSAlPiUKICAgIG11dGF0ZShMZWdlbmQgPSAiU3RyZWV0X0xpZ2h0c19PdXQiKQoKc2FuaXRhdGlvbiA8LQogIHJlYWQuc29jcmF0YSgiaHR0cHM6Ly9kYXRhLmNpdHlvZmNoaWNhZ28ub3JnL1NlcnZpY2UtUmVxdWVzdHMvMzExLVNlcnZpY2UtUmVxdWVzdHMtU2FuaXRhdGlvbi1Db2RlLUNvbXBsYWludHMtSGkvbWU1OS01ZmFjIikgJT4lCiAgICBtdXRhdGUoeWVhciA9IHN1YnN0cihjcmVhdGlvbl9kYXRlLDEsNCkpICU+JSBmaWx0ZXIoeWVhciA9PSAiMjAxOCIpICU+JQogICAgZHBseXI6OnNlbGVjdChZID0gbGF0aXR1ZGUsIFggPSBsb25naXR1ZGUpICU+JQogICAgbmEub21pdCgpICU+JQogICAgc3RfYXNfc2YoY29vcmRzID0gYygiWCIsICJZIiksIGNycyA9IDQzMjYsIGFnciA9ICJjb25zdGFudCIpICU+JQogICAgc3RfdHJhbnNmb3JtKHN0X2NycyhmaXNobmV0KSkgJT4lCiAgICBtdXRhdGUoTGVnZW5kID0gIlNhbml0YXRpb24iKQoKbGlxdW9yUmV0YWlsIDwtIAogIHJlYWQuc29jcmF0YSgiaHR0cHM6Ly9kYXRhLmNpdHlvZmNoaWNhZ28ub3JnL3Jlc291cmNlL25ybWotM2tjZi5qc29uIikgJT4lICAKICAgIGZpbHRlcihidXNpbmVzc19hY3Rpdml0eSA9PSAiUmV0YWlsIFNhbGVzIG9mIFBhY2thZ2VkIExpcXVvciIpICU+JQogICAgZHBseXI6OnNlbGVjdChZID0gbGF0aXR1ZGUsIFggPSBsb25naXR1ZGUpICU+JQogICAgbmEub21pdCgpICU+JQogICAgc3RfYXNfc2YoY29vcmRzID0gYygiWCIsICJZIiksIGNycyA9IDQzMjYsIGFnciA9ICJjb25zdGFudCIpICU+JQogICAgc3RfdHJhbnNmb3JtKHN0X2NycyhmaXNobmV0KSkgJT4lCiAgICBtdXRhdGUoTGVnZW5kID0gIkxpcXVvcl9SZXRhaWwiKQoKCnNob3RTcG90dGVyIDwtIAogIHJlYWQuc29jcmF0YSgiaHR0cHM6Ly9kYXRhLmNpdHlvZmNoaWNhZ28ub3JnL1B1YmxpYy1TYWZldHkvVmlvbGVuY2UtUmVkdWN0aW9uLVNob3RzcG90dGVyLUFsZXJ0cy8zaDdxLTdtZGIiKSAlPiUgIAogICAgZHBseXI6OnNlbGVjdChZID0gbGF0aXR1ZGUsIFggPSBsb25naXR1ZGUpICU+JQogICAgbmEub21pdCgpICU+JQogICAgc3RfYXNfc2YoY29vcmRzID0gYygiWCIsICJZIiksIGNycyA9IDQzMjYsIGFnciA9ICJjb25zdGFudCIpICU+JQogICAgc3RfdHJhbnNmb3JtKHN0X2NycyhmaXNobmV0KSkgJT4lCiAgICBtdXRhdGUoTGVnZW5kID0gIlNob3RfU3BvdHRlciIpCgpCYW5rcyA8LSAKICByZWFkLnNvY3JhdGEoImh0dHBzOi8vZGF0YS5jaXR5b2ZjaGljYWdvLm9yZy9BZG1pbmlzdHJhdGlvbi1GaW5hbmNlL0xlbmRpbmctRXF1aXR5LURlcG9zaXRvcnktTG9jYXRpb25zLTIwMjEvamd1cC16dDdrIikgJT4lICAKICBkcGx5cjo6c2VsZWN0KGxvY2F0aW9uKSAlPiUKICBuYS5vbWl0KCkKQmFua3MgPC0gQmFua3MgJT4lICAgCiAgbXV0YXRlKFggPSBhcy5udW1lcmljKHN1YigiUE9JTlQgXFwoKC0/XFxkK1xcLlxcZCspIFxcZCtcXC5cXGQrXFwpIiwgIlxcMSIsIEJhbmtzJGxvY2F0aW9uKSksCiAgICAgICAgIFkgPSBhcy5udW1lcmljKHN1YigiUE9JTlQgXFwoLT9cXGQrXFwuXFxkKyAoLT9cXGQrXFwuXFxkKylcXCkiLCAiXFwxIiwgQmFua3MkbG9jYXRpb24pKSkgJT4lCiAgZHBseXI6OnNlbGVjdCgtbG9jYXRpb24pICU+JSAKICBmaWx0ZXIoIWlzLm5hKFgpICYgIWlzLm5hKFkpKSAlPiUgCiAgc3RfYXNfc2YoY29vcmRzID0gYygiWCIsICJZIiksIGNycyA9IDQzMjYsIGFnciA9ICJjb25zdGFudCIpICU+JQogICAgc3RfdHJhbnNmb3JtKHN0X2NycyhmaXNobmV0KSkgJT4lCiAgICBtdXRhdGUoTGVnZW5kID0gIkJhbmsiKSAKICAKCmBgYAoKQWxsIG9mIHRoZXNlIHByZWRpY3RvciB2YXJpYWJsZXMgd2VyZSB0aGVuIGpvaW5lZCB0byB0aGUgZmlzaG5ldC4gRWFjaCBjZWxsIGluIHRoZSBmaXNobmV0IHdvdWxkIG5vdyBjb250YWluIGEgbnVtYmVyIHN1bW1hcml6aW5nIHRoZSBudW1iZXIgb2YgcmVzcGVjdGl2ZSByaXNrIGZhY3RvciBvY2N1cnJlbmNlIGluIHRoZSBjZWxsLgoKYGBge3Igam9pbiBwcmVkaWN0b3IgdmFyaWFibGVzIHRvIGZpc2huZXQsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9Cgp2YXJzX25ldCA8LSAKICByYmluZChhYmFuZG9uQ2FycyxzdHJlZXRMaWdodHNPdXQsYWJhbmRvbkJ1aWxkaW5ncywKICAgICAgICBsaXF1b3JSZXRhaWwsIGdyYWZmaXRpLCBzYW5pdGF0aW9uLCBzaG90U3BvdHRlciwgQmFua3MpICU+JQogIHN0X2pvaW4oLiwgZmlzaG5ldCwgam9pbj1zdF93aXRoaW4pICU+JQogIHN0X2Ryb3BfZ2VvbWV0cnkoKSAlPiUKICBncm91cF9ieSh1bmlxdWVJRCwgTGVnZW5kKSAlPiUKICBzdW1tYXJpemUoY291bnQgPSBuKCkpICU+JQogICAgZnVsbF9qb2luKGZpc2huZXQpICU+JQogICAgc3ByZWFkKExlZ2VuZCwgY291bnQsIGZpbGw9MCkgJT4lCiAgICBzdF9zZigpICU+JQogICAgZHBseXI6OnNlbGVjdCgtYDxOQT5gKSAlPiUKICAgIG5hLm9taXQoKSAlPiUKICAgIHVuZ3JvdXAoKQoKYGBgCgpUaGUgZmlndXJlIGJlbG93IHNob3dzIHRoYXQgZWFjaCByaXNrIGZhY3RvciBoYXMgaXRzIHVuaXF1ZSBzcGF0aWFsIGRpc3RyaWJ1dGlvbiBpbiBDaGljYWdvLiBBYmFuZG9uZWQgYnVpbGRpbmdzIGFyZSBjb25jZW50cmF0ZWQgbW9zdGx5IGNvbmNlbnRyYXRlZCBpbiBzb3V0aGVybiBDaGljYWdvLiBCYW5rcyBhbmQgbGlxdW9yIHJldGFpbCBzdG9yZXMgYXJlIGNvbmNlbnRyYXRlZCBpbiB0aGUgQ0JELiBBYmFuZG9uZWQgY2Fycywgc2FuaXRhdGlvbiBjb21wbGFpbnRzLCBhbmQgc3RyZWV0cyBsaWdodHMgb3V0IGNvbXBsYWludHMgYXJlIHNjYXR0ZXJlZCBldmVyeXdoZXJlIGluIHRoZSBjaXR5LiBHcmFmZml0aSBjbGVhbmluZyByZXF1ZXN0IGFyZSBtb3N0bHkgZm91bmQgY2xvc2UgdG8gdGhlIGNpdHkgY2VudGVyIHdoaWxlIHNob3Qgc3BvdHRlciBhcmUgbW9zdGx5IG91dHNpZGUgb2YgdGhlIGNpdHkgY2VudGVyLiAKCmBgYHtyIHZpc3VhbGl6ZSBwcmVkaWN0b3IgdmFyaWFibGVzLCBmaWcuaGVpZ2h0PTgsIGZpZy53aWR0aD0xMSwgZmlnLmFsaWduID0gJ2NlbnRlcid9CnZhcnNfbmV0LmxvbmcgPC0gCiAgZ2F0aGVyKHZhcnNfbmV0LCBWYXJpYWJsZSwgdmFsdWUsIC1nZW9tZXRyeSwgLXVuaXF1ZUlEKQoKcGxvdDEgPC0gZ2dwbG90KCkgKwogIGdlb21fc2YoZGF0YSA9IHZhcnNfbmV0LmxvbmcgJT4lIGZpbHRlcihWYXJpYWJsZSA9PSAiQWJhbmRvbmVkX0J1aWxkaW5ncyIpLCBhZXMoZmlsbD12YWx1ZSksIGNvbG91cj1OQSkgKwogIHNjYWxlX2ZpbGxfdmlyaWRpcyhvcHRpb24gPSAibWFnbWEiKSArIAogIGxhYnModGl0bGUgPSAiQWJhbmRvbmVkIEJ1aWxkaW5ncyIpICsKICB0aGVtZShheGlzLnRleHQueD1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGlja3MgPWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aXRsZS55ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDksZmFjZSA9ICJpdGFsaWMiKSwKICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCwgZmFjZSA9ICJib2xkIiksCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X3JlY3QoY29sb3VyID0gImdyZXkiLCBmaWxsPU5BLCBzaXplPTAuOCkKICAgICAgICApCgoKcGxvdDIgPC0gZ2dwbG90KCkgKwogIGdlb21fc2YoZGF0YSA9IHZhcnNfbmV0LmxvbmcgJT4lIGZpbHRlcihWYXJpYWJsZSA9PSAiQWJhbmRvbmVkX0NhcnMiKSwgYWVzKGZpbGw9dmFsdWUpLCBjb2xvdXI9TkEpICsKICBzY2FsZV9maWxsX3ZpcmlkaXMob3B0aW9uID0gIm1hZ21hIikgKyAKICBsYWJzKHRpdGxlID0gIkFiYW5kb25lZCBDYXJzIikgKwogIHRoZW1lKGF4aXMudGV4dC54PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRleHQueT1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aWNrcyA9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpdGxlLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gOSxmYWNlID0gIml0YWxpYyIpLAogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwLCBmYWNlID0gImJvbGQiKSwKICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfcmVjdChjb2xvdXIgPSAiZ3JleSIsIGZpbGw9TkEsIHNpemU9MC44KQogICAgICAgICkKCnBsb3QzIDwtIGdncGxvdCgpICsKICBnZW9tX3NmKGRhdGEgPSB2YXJzX25ldC5sb25nICU+JSBmaWx0ZXIoVmFyaWFibGUgPT0gIkJhbmsiKSwgYWVzKGZpbGw9dmFsdWUpLCBjb2xvdXI9TkEpICsKICBzY2FsZV9maWxsX3ZpcmlkaXMob3B0aW9uID0gIm1hZ21hIikgKyAKICBsYWJzKHRpdGxlID0gIkJhbmtzIikgKwogIHRoZW1lKGF4aXMudGV4dC54PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRleHQueT1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aWNrcyA9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpdGxlLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gOSxmYWNlID0gIml0YWxpYyIpLAogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwLCBmYWNlID0gImJvbGQiKSwKICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfcmVjdChjb2xvdXIgPSAiZ3JleSIsIGZpbGw9TkEsIHNpemU9MC44KQogICAgICAgICkKCnBsb3Q0IDwtIGdncGxvdCgpICsKICBnZW9tX3NmKGRhdGEgPSB2YXJzX25ldC5sb25nICU+JSBmaWx0ZXIoVmFyaWFibGUgPT0gIkdyYWZmaXRpIiksIGFlcyhmaWxsPXZhbHVlKSwgY29sb3VyPU5BKSArCiAgc2NhbGVfZmlsbF92aXJpZGlzKG9wdGlvbiA9ICJtYWdtYSIpICsgCiAgbGFicyh0aXRsZSA9ICJHcmFmZml0aSIpICsKICB0aGVtZShheGlzLnRleHQueD1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGlja3MgPWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aXRsZS55ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDksZmFjZSA9ICJpdGFsaWMiKSwKICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCwgZmFjZSA9ICJib2xkIiksCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X3JlY3QoY29sb3VyID0gImdyZXkiLCBmaWxsPU5BLCBzaXplPTAuOCkKICAgICAgICApCgoKcGxvdDUgPC0gZ2dwbG90KCkgKwogIGdlb21fc2YoZGF0YSA9IHZhcnNfbmV0LmxvbmcgJT4lIGZpbHRlcihWYXJpYWJsZSA9PSAiTGlxdW9yX1JldGFpbCIpLCBhZXMoZmlsbD12YWx1ZSksIGNvbG91cj1OQSkgKwogIHNjYWxlX2ZpbGxfdmlyaWRpcyhvcHRpb24gPSAibWFnbWEiKSArIAogIGxhYnModGl0bGUgPSAiTGlxdW9yIFJldGFpbCIpICsKICB0aGVtZShheGlzLnRleHQueD1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGlja3MgPWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aXRsZS55ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDksZmFjZSA9ICJpdGFsaWMiKSwKICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCwgZmFjZSA9ICJib2xkIiksCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X3JlY3QoY29sb3VyID0gImdyZXkiLCBmaWxsPU5BLCBzaXplPTAuOCkKICAgICAgICApCgpwbG90NiA8LSBnZ3Bsb3QoKSArCiAgZ2VvbV9zZihkYXRhID0gdmFyc19uZXQubG9uZyAlPiUgZmlsdGVyKFZhcmlhYmxlID09ICJTYW5pdGF0aW9uIiksIGFlcyhmaWxsPXZhbHVlKSwgY29sb3VyPU5BKSArCiAgc2NhbGVfZmlsbF92aXJpZGlzKG9wdGlvbiA9ICJtYWdtYSIpICsgCiAgbGFicyh0aXRsZSA9ICJTYW5pdGF0aW9uIikgKwogIHRoZW1lKGF4aXMudGV4dC54PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRleHQueT1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aWNrcyA9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpdGxlLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gOSxmYWNlID0gIml0YWxpYyIpLAogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwLCBmYWNlID0gImJvbGQiKSwKICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfcmVjdChjb2xvdXIgPSAiZ3JleSIsIGZpbGw9TkEsIHNpemU9MC44KQogICAgICAgICkKCnBsb3Q3IDwtIGdncGxvdCgpICsKICBnZW9tX3NmKGRhdGEgPSB2YXJzX25ldC5sb25nICU+JSBmaWx0ZXIoVmFyaWFibGUgPT0gIlNob3RfU3BvdHRlciIpLCBhZXMoZmlsbD12YWx1ZSksIGNvbG91cj1OQSkgKwogIHNjYWxlX2ZpbGxfdmlyaWRpcyhvcHRpb24gPSAibWFnbWEiKSArIAogIGxhYnModGl0bGUgPSAiU2hvdCBTcG90dGVyIikgKwogIHRoZW1lKGF4aXMudGV4dC54PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRleHQueT1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aWNrcyA9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpdGxlLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gOSxmYWNlID0gIml0YWxpYyIpLAogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwLCBmYWNlID0gImJvbGQiKSwKICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfcmVjdChjb2xvdXIgPSAiZ3JleSIsIGZpbGw9TkEsIHNpemU9MC44KQogICAgICAgICkKCgpwbG90OCA8LSBnZ3Bsb3QoKSArCiAgZ2VvbV9zZihkYXRhID0gdmFyc19uZXQubG9uZyAlPiUgZmlsdGVyKFZhcmlhYmxlID09ICJTdHJlZXRfTGlnaHRzX091dCIpLCBhZXMoZmlsbD12YWx1ZSksIGNvbG91cj1OQSkgKwogIHNjYWxlX2ZpbGxfdmlyaWRpcyhvcHRpb24gPSAibWFnbWEiKSArIAogIGxhYnModGl0bGUgPSAiU3RyZWV0IExpZ2h0IE91dCIpICsKICB0aGVtZShheGlzLnRleHQueD1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGlja3MgPWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aXRsZS55ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDksZmFjZSA9ICJpdGFsaWMiKSwKICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCwgZmFjZSA9ICJib2xkIiksCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X3JlY3QoY29sb3VyID0gImdyZXkiLCBmaWxsPU5BLCBzaXplPTAuOCkKICAgICAgICApCgp3cmFwX3Bsb3RzKAogIHBsb3QxLCBwbG90MywgcGxvdDIsIHBsb3Q0LAogIHBsb3Q1LCBwbG90NiwgcGxvdDcsIHBsb3Q4LAogIG5jb2wgPSA0ICAKKQoKCmBgYAoKIyBGZWF0dXJlIEVuZ2luZWVyaW5nCgoKIyMgTmVhcmVzdCBEaXN0YW5jZQoKVGhlIGZpcnN0IGZlYXR1cmUgZW5naW5lZXJpbmcgYXBwcm9hY2ggd2UgdG9vayBpcyB0byBjYWxjdWxhdGUgYXZlcmFnZSBuZWFyZXN0IG5laWdoYm9yIGRpc3RhbmNlIHRvIGh5cG90aGVzaXplIGEgc21vb3RoZXIgZXhwb3N1cmUgcmVsYXRpb25zaGlwIGFjcm9zcyBzcGFjZS4gSGVyZSwgdGhlIG5uX2Z1bmN0aW9uIGlzIHVzZWQuIEZvciBlYWNoIG9mIG91ciByaXNrIGZhY3RvcnMsIGF2ZXJhZ2UgbmVhcmVzdCBkaXN0YW5jZSBpcyBjYWxjdWxhdGVkIGZyb20gdGhlIGNlbnRyb2lkIG9mIGVhY2ggY2VsbCB0byBpdHMgbmVhcmVzdCB0aHJlZSwgYWJhbmRvbmVkIGNhcnMsIGZvciBleGFtcGxlLiAKCmBgYHtyIGNhbGN1bGF0ZSBuZWFyZXN0IGRpc3RhbmNlLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQoKc3RfYyA8LSBzdF9jb29yZGluYXRlcwpzdF9jb2lkIDwtIHN0X2NlbnRyb2lkCgp2YXJzX25ldCA8LQogIHZhcnNfbmV0ICU+JQogICAgbXV0YXRlKAogICAgICBBYmFuZG9uZWRfQnVpbGRpbmdzLm5uID0KICAgICAgICBubl9mdW5jdGlvbihzdF9jKHN0X2NvaWQodmFyc19uZXQpKSwgc3RfYyhhYmFuZG9uQnVpbGRpbmdzKSwzKSwKICAgICAgQWJhbmRvbmVkX0NhcnMubm4gPQogICAgICAgIG5uX2Z1bmN0aW9uKHN0X2Moc3RfY29pZCh2YXJzX25ldCkpLCBzdF9jKGFiYW5kb25DYXJzKSwzKSwKICAgICAgR3JhZmZpdGkubm4gPQogICAgICAgIG5uX2Z1bmN0aW9uKHN0X2Moc3RfY29pZCh2YXJzX25ldCkpLCBzdF9jKGdyYWZmaXRpKSwzKSwKICAgICAgTGlxdW9yX1JldGFpbC5ubiA9CiAgICAgICAgbm5fZnVuY3Rpb24oc3RfYyhzdF9jb2lkKHZhcnNfbmV0KSksIHN0X2MobGlxdW9yUmV0YWlsKSwzKSwKICAgICAgU3RyZWV0X0xpZ2h0c19PdXQubm4gPQogICAgICAgIG5uX2Z1bmN0aW9uKHN0X2Moc3RfY29pZCh2YXJzX25ldCkpLCBzdF9jKHN0cmVldExpZ2h0c091dCksMyksCiAgICAgIFNhbml0YXRpb24ubm4gPQogICAgICAgIG5uX2Z1bmN0aW9uKHN0X2Moc3RfY29pZCh2YXJzX25ldCkpLCBzdF9jKHNhbml0YXRpb24pLDMpLAogICAgICBTaG90X1Nwb3R0ZXIubm4gPQogICAgICAgIG5uX2Z1bmN0aW9uKHN0X2Moc3RfY29pZCh2YXJzX25ldCkpLCBzdF9jKHNob3RTcG90dGVyKSwzKSkKCnZhcnNfbmV0IDwtCiAgdmFyc19uZXQgJT4lIG11dGF0ZShCYW5rLm5uID0gbm5fZnVuY3Rpb24oc3RfYyhzdF9jb2lkKHZhcnNfbmV0KSksIHN0X2MoQmFua3MpLDMpKQogCgpgYGAKClRoZSBuZWFyZXN0IG5laWdoYm9yIGZlYXR1cmVzIGFyZSB0aGVuIHBsb3R0ZWQgYmVsb3cuIFZpc3VhbGl6aW5nIGRpc3RhbmNlIHByb3ZpZGVzIHVzIHdpdGggYSBiZXR0ZXIgdmlldyBvZiB0aGUgc3BhdGlhbCBkaXN0cmlidXRpb24gb2YgdmFyaW91cyByaXNrIGZhY3RvcnMuIEl0IGFwcGVhcnMgdGhhdCB0aGUgc291dGhlcm4gYW5kIHNvdXRod2VzdCBwYXJ0IG9mIENoaWNhZ28gYXJlIHRoZSBmdXJ0aGVzdCBhd2F5IGZyb20gYWxsIGtpbmRzIG9mIHJpc2tzLiBPbmUgaW50ZXJlc3Rpbmcgb2JzZXJ2YXRpb24gaXMgdGhhdCB0aGUgbm9ydGhlYXN0ZXJuIGNvcm5lciBvZiBDaGljYWdvIGlzIHRoZSBmdXJ0aGVzdCBhd2F5IGZyb20gc2hvdHNwb3R0ZXJzIHdoaWxlIGJlaW5nIHJlbGF0aXZlbHkgY2xvc2UgdG8gYWxsIG90aGVyIGtpbmRzIG9mIHJpc2sgZmFjdG9ycy4gSWYgc2hvdCBzcG90dGVyIGFyZSB1c2VkIHRvIGxvY2F0aW9uIGNyaW1lIGluY2lkZW50cywgdGhlaXIgYWJzZW5jZSBpbiB0aGlzIGFyZWEgcmFpc2VzIHF1ZXN0aW9uIG9mIHdoZXRoZXIgdGhpcyAicmlzayIgZmFjdG9yIGFjdCB0aGUgc2FtZSBhcyBvdGhlcnMuIEFyZSBzaG90c3BvdHRlciBhbiBpbmRpY2F0aW9uIG9mIG1vcmUgY3JpbWUgaW4gdGhlIGFyZWEgb3IgZG9lcyB0aGUgcHJlc2VuY2Ugb2Ygc2hvdCBzcG90dGVyIG1lYW5zIGdyZWF0ZXIgcG9saWNpbmcgYW5kIHRoZXJlZm9yZSBzYWZ0ZXIgbmVpZ2hib3Job29kPyBUaGVzZSBxdWVzdGlvbnMgYXJlIHdvcnRoIGNvbnNpZGVyaW5nLiAKCmBgYHtyIHZpc3VhbGl6ZSBuZWFyZXN0IGRpc3RhbmNlLCBmaWcuaGVpZ2h0PTgsIGZpZy53aWR0aD0xMSwgZmlnLmFsaWduID0gJ2NlbnRlcid9Cgp2YXJzX25ldC5sb25nLm5uIDwtIAogIGRwbHlyOjpzZWxlY3QodmFyc19uZXQsIGVuZHNfd2l0aCgiLm5uIikpICU+JQogICAgZ2F0aGVyKFZhcmlhYmxlLCB2YWx1ZSwgLWdlb21ldHJ5KQoKCnAxLm5uIDwtIGdncGxvdCgpICsKICBnZW9tX3NmKGRhdGEgPSB2YXJzX25ldC5sb25nLm5uICU+JSBmaWx0ZXIoVmFyaWFibGUgPT0gIkFiYW5kb25lZF9CdWlsZGluZ3Mubm4iKSwgYWVzKGZpbGw9dmFsdWUpLCBjb2xvdXI9TkEpICsKICBzY2FsZV9maWxsX3ZpcmlkaXMob3B0aW9uID0gIm1hZ21hIikgKyAKICBsYWJzKHRpdGxlID0gIkFiYW5kb25lZCBCdWlsZGluZ3MgTk4iKSArCiAgdGhlbWUoYXhpcy50ZXh0Lng9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGV4dC55PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpY2tzID1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGl0bGUueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSA5LGZhY2UgPSAiaXRhbGljIiksCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTAsIGZhY2UgPSAiYm9sZCIpLAogICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9yZWN0KGNvbG91ciA9ICJncmV5IiwgZmlsbD1OQSwgc2l6ZT0wLjgpCiAgICAgICAgKQoKCnAyLm5uIDwtIGdncGxvdCgpICsKICBnZW9tX3NmKGRhdGEgPSB2YXJzX25ldC5sb25nLm5uICU+JSBmaWx0ZXIoVmFyaWFibGUgPT0gIkFiYW5kb25lZF9DYXJzLm5uIiksIGFlcyhmaWxsPXZhbHVlKSwgY29sb3VyPU5BKSArCiAgc2NhbGVfZmlsbF92aXJpZGlzKG9wdGlvbiA9ICJtYWdtYSIpICsgCiAgbGFicyh0aXRsZSA9ICJBYmFuZG9uZWQgQ2FycyBOTiIpICsKICB0aGVtZShheGlzLnRleHQueD1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGlja3MgPWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aXRsZS55ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDksZmFjZSA9ICJpdGFsaWMiKSwKICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCwgZmFjZSA9ICJib2xkIiksCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X3JlY3QoY29sb3VyID0gImdyZXkiLCBmaWxsPU5BLCBzaXplPTAuOCkKICAgICAgICApCgpwMy5ubiA8LSBnZ3Bsb3QoKSArCiAgZ2VvbV9zZihkYXRhID0gdmFyc19uZXQubG9uZy5ubiAlPiUgZmlsdGVyKFZhcmlhYmxlID09ICJCYW5rLm5uIiksIGFlcyhmaWxsPXZhbHVlKSwgY29sb3VyPU5BKSArCiAgc2NhbGVfZmlsbF92aXJpZGlzKG9wdGlvbiA9ICJtYWdtYSIpICsgCiAgbGFicyh0aXRsZSA9ICJCYW5rcyBOTiIpICsKICB0aGVtZShheGlzLnRleHQueD1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGlja3MgPWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aXRsZS55ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDksZmFjZSA9ICJpdGFsaWMiKSwKICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCwgZmFjZSA9ICJib2xkIiksCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X3JlY3QoY29sb3VyID0gImdyZXkiLCBmaWxsPU5BLCBzaXplPTAuOCkKICAgICAgICApCgpwNC5ubiA8LSBnZ3Bsb3QoKSArCiAgZ2VvbV9zZihkYXRhID0gdmFyc19uZXQubG9uZy5ubiAlPiUgZmlsdGVyKFZhcmlhYmxlID09ICJHcmFmZml0aS5ubiIpLCBhZXMoZmlsbD12YWx1ZSksIGNvbG91cj1OQSkgKwogIHNjYWxlX2ZpbGxfdmlyaWRpcyhvcHRpb24gPSAibWFnbWEiKSArIAogIGxhYnModGl0bGUgPSAiR3JhZmZpdGkgTk4iKSArCiAgdGhlbWUoYXhpcy50ZXh0Lng9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGV4dC55PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpY2tzID1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGl0bGUueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSA5LGZhY2UgPSAiaXRhbGljIiksCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTAsIGZhY2UgPSAiYm9sZCIpLAogICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9yZWN0KGNvbG91ciA9ICJncmV5IiwgZmlsbD1OQSwgc2l6ZT0wLjgpCiAgICAgICAgKQoKCnA1Lm5uIDwtIGdncGxvdCgpICsKICBnZW9tX3NmKGRhdGEgPSB2YXJzX25ldC5sb25nLm5uICU+JSBmaWx0ZXIoVmFyaWFibGUgPT0gIkxpcXVvcl9SZXRhaWwubm4iKSwgYWVzKGZpbGw9dmFsdWUpLCBjb2xvdXI9TkEpICsKICBzY2FsZV9maWxsX3ZpcmlkaXMob3B0aW9uID0gIm1hZ21hIikgKyAKICBsYWJzKHRpdGxlID0gIkxpcXVvciBSZXRhaWwgTk4iKSArCiAgdGhlbWUoYXhpcy50ZXh0Lng9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGV4dC55PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpY2tzID1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGl0bGUueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSA5LGZhY2UgPSAiaXRhbGljIiksCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTAsIGZhY2UgPSAiYm9sZCIpLAogICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9yZWN0KGNvbG91ciA9ICJncmV5IiwgZmlsbD1OQSwgc2l6ZT0wLjgpCiAgICAgICAgKQoKcDYubm4gPC0gZ2dwbG90KCkgKwogIGdlb21fc2YoZGF0YSA9IHZhcnNfbmV0Lmxvbmcubm4gJT4lIGZpbHRlcihWYXJpYWJsZSA9PSAiU2FuaXRhdGlvbi5ubiIpLCBhZXMoZmlsbD12YWx1ZSksIGNvbG91cj1OQSkgKwogIHNjYWxlX2ZpbGxfdmlyaWRpcyhvcHRpb24gPSAibWFnbWEiKSArIAogIGxhYnModGl0bGUgPSAiU2FuaXRhdGlvbiBOTiIpICsKICB0aGVtZShheGlzLnRleHQueD1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGlja3MgPWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aXRsZS55ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDksZmFjZSA9ICJpdGFsaWMiKSwKICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCwgZmFjZSA9ICJib2xkIiksCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X3JlY3QoY29sb3VyID0gImdyZXkiLCBmaWxsPU5BLCBzaXplPTAuOCkKICAgICAgICApCgpwNy5ubiA8LSBnZ3Bsb3QoKSArCiAgZ2VvbV9zZihkYXRhID0gdmFyc19uZXQubG9uZy5ubiAlPiUgZmlsdGVyKFZhcmlhYmxlID09ICJTaG90X1Nwb3R0ZXIubm4iKSwgYWVzKGZpbGw9dmFsdWUpLCBjb2xvdXI9TkEpICsKICBzY2FsZV9maWxsX3ZpcmlkaXMob3B0aW9uID0gIm1hZ21hIikgKyAKICBsYWJzKHRpdGxlID0gIlNob3QgU3BvdHRlciBOTiIpICsKICB0aGVtZShheGlzLnRleHQueD1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGlja3MgPWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aXRsZS55ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDksZmFjZSA9ICJpdGFsaWMiKSwKICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCwgZmFjZSA9ICJib2xkIiksCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X3JlY3QoY29sb3VyID0gImdyZXkiLCBmaWxsPU5BLCBzaXplPTAuOCkKICAgICAgICApCgoKcDgubm4gPC0gZ2dwbG90KCkgKwogIGdlb21fc2YoZGF0YSA9IHZhcnNfbmV0Lmxvbmcubm4gJT4lIGZpbHRlcihWYXJpYWJsZSA9PSAiU3RyZWV0X0xpZ2h0c19PdXQubm4iKSwgYWVzKGZpbGw9dmFsdWUpLCBjb2xvdXI9TkEpICsKICBzY2FsZV9maWxsX3ZpcmlkaXMob3B0aW9uID0gIm1hZ21hIikgKyAKICBsYWJzKHRpdGxlID0gIlN0cmVldCBMaWdodCBPdXQgTk4iKSArCiAgdGhlbWUoYXhpcy50ZXh0Lng9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGV4dC55PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpY2tzID1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGl0bGUueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSA5LGZhY2UgPSAiaXRhbGljIiksCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTAsIGZhY2UgPSAiYm9sZCIpLAogICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9yZWN0KGNvbG91ciA9ICJncmV5IiwgZmlsbD1OQSwgc2l6ZT0wLjgpCiAgICAgICAgKQoKd3JhcF9wbG90cygKICBwMS5ubiwgcDMubm4sIHAyLm5uLCBwNC5ubiwKICBwNS5ubiwgcDYubm4sIHA3Lm5uLCBwOC5ubiwKICBuY29sID0gNCAgCikKCgpgYGAKCgojIyBMb2NhbCBTcGF0aWFsIEF1dG9jb3JyZWxhdGlvbgoKVGhlIHNlY29uZCBmZWF0dXJlIGVuZ2luZWVyaW5nIGFwcHJvYWNoIHdlIHRvb2sgaXMgdG8gYWNjb3VudCBmb3IgdGhlIHNwYXRpYWwgcHJvY2VzcyBvZiByb2JiZXJ5IGluY2lkZW50cyB0aHJvdWdoIGRldGVybWluaW5nIHJvYmJlcnkgaG90c3BvdHMgaW4gQ2hpY2Fnby4gVW5saWtlIHRoZSBnbG9iYWwgTW9yYW4ncyBJIHdoaWNoIHdvdWxkIHRlbGwgdXMgaWYgdGhlcmUncyBzcGF0aWFsIGF1dG9jb3JyZWxhdGlvbiBvZiByb2JiZXJpZXMgb2YgdGhlIGVudGlyZSBhcmVhLCBsb2NhbCBNb3JhbidzIEkgZXhhbWluZSBpZiByb2JiZXJ5IGNvdW50IGF0IGEgZ2l2ZW4gbG9jYXRpb24gaXMgcmFuZG9tbHkgZGlzdHJpYnV0ZWQgcmVsYXRpdmUgdG8gaXRzIGltbWVkaWF0ZSBuZWlnaGJvcnMuIEEgc3BhdGlhbCB3ZWlnaHRzIG1hdHJpeCBpcyBjYWxjdWFsdGVkIGJlbG93LCB3aGljaCBpcyB1c2VkIHRvIHJlbGF0ZSBhIHVuaXQgdG8gaXRzIG5laWdoYm9ycyB1c2luZyBxdWVlbiBjb250aWd1aXR5IChldmVyeSBncmlkIGNlbGwgaXMgcmVsYXRlZCB0byBpdHMgZWlnaHQgYWRqYWNlbnQgbmVpZ2hib3JzKS4gCgpgYGB7ciBjYWxjdWxhdGUgbG9jYWwgbW9yYW4gSSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KCmZpbmFsX25ldCA8LQogIGxlZnRfam9pbihyb2JiZXJ5MjFfbmV0LCBzdF9kcm9wX2dlb21ldHJ5KHZhcnNfbmV0KSwgYnk9InVuaXF1ZUlEIikKZmluYWxfbmV0Lm5iIDwtIHBvbHkybmIoYXNfU3BhdGlhbChmaW5hbF9uZXQpLCBxdWVlbj1UUlVFKQpmaW5hbF9uZXQud2VpZ2h0cyA8LSBuYjJsaXN0dyhmaW5hbF9uZXQubmIsIHN0eWxlPSJXIiwgemVyby5wb2xpY3k9VFJVRSkKCmxvY2FsX21vcmFucyA8LSBsb2NhbG1vcmFuKGZpbmFsX25ldCRjb3VudFJvYmJlcnksIGZpbmFsX25ldC53ZWlnaHRzLCB6ZXJvLnBvbGljeT1UUlVFKSAlPiUgCiAgYXMuZGF0YS5mcmFtZSgpCgpmaW5hbF9uZXQubG9jYWxNb3JhbnMgPC0gCiAgY2JpbmQobG9jYWxfbW9yYW5zLCBhcy5kYXRhLmZyYW1lKGZpbmFsX25ldCkpICU+JSAKICBzdF9zZigpICU+JQogIGRwbHlyOjpzZWxlY3QoUm9iYmVyeV9Db3VudCA9IGNvdW50Um9iYmVyeSwgCiAgICAgICAgICAgICAgICBMb2NhbF9Nb3JhbnNfSSA9IElpLCAKICAgICAgICAgICAgICAgIFBfVmFsdWUgPSBgUHIoeiAhPSBFKElpKSlgKSAlPiUKICBtdXRhdGUoU2lnbmlmaWNhbnRfSG90c3BvdHMgPSBpZmVsc2UoUF9WYWx1ZSA8PSAwLjAwMSwgMSwgMCkpICU+JQogIGdhdGhlcihWYXJpYWJsZSwgVmFsdWUsIC1nZW9tZXRyeSkKYGBgCgpUaGUgcmVzdWx0cyBvZiB0aGUgbG9jYWxtb3JhbiB0ZXN0IGlzIGNvbWJpbmVkIHdpdGggdGhlIHNwYXRpYWwgbGF5ZXIgZm9yIHZpc3VhbGl6YXRpb24uIFNldmVyYWwgdXNlZnVsIHRlc3Qgc3RhdGlzdGljcywgaW5jbHVkaW5nIE1vcmFuJ3MgSSwgdGhlIHAtdmFsdWUsIGFuZCBzaWduaWZpY2FudCByb2JiZXJ5IGhvdHNwb3RzIGFyZSB2aXN1YWxpemVkLiBXZSBtYXkgc2VlIHRoYXQgdGhlIExvY2FsIE1vcmFuJ3MgSSB2YWx1ZSBpcyBzaWduaWZpY2FudGx5IGhpZ2hlciBpbiBub3J0aGVhc3Rlcm4gQ2hpY2FnbywgY2xvc2UgdG8gdGhlIENCRCBhcmVhLiBBcmVhcyB3aXRoIGxvdyBwLXZhbHVlIHN1Z2dlc3RzIHRoYXQgdGhlcmUgaXMgc3BhdGlhbCBhdXRvY29ycmVsYXRpb24gYmV0d2VlbiByb2JiZXJ5IGluY2lkZW50cyBpbiBjZWxscyB0aGF0IGFyZSBwcm94aW1hdGUgdG8gZWFjaCBvdGhlci4gU2lnbmlmaWNhbnQgaG90c3BvdHMsIG1lYW5pbmcgdGhvc2UgZ3JpZCBjZWxscyB3aXRoIGhpZ2hlciBsb2NhbCBjb3VudHMgdGhhbiB3aGF0IG1pZ2h0IG90aGVyd2lzZSBiZSBleHBlY3RlZCB1bmRlciByYW5kb21uZXNzIChwLXZhbHVlcyA8PSAwLjA1KSwgZm9sbG93cyB0aGUgc2FtZSBwYXR0ZXJuIGFzIHdoYXQncyBzaG93biBpbiB0aGUga2VybmFsIGRlbnNpdHkgbWFwIGFib3ZlLiAgCgpgYGB7ciB2aXN1YWxpemUgbW9yYW4gSSwgZmlnLmhlaWdodD04LCBmaWcud2lkdGg9MTEsIGZpZy5hbGlnbiA9ICdjZW50ZXInfQoKbW9yYW4xIDwtIGdncGxvdCgpICsKICBnZW9tX3NmKGRhdGEgPSBmaW5hbF9uZXQubG9jYWxNb3JhbnMgJT4lIGZpbHRlcihWYXJpYWJsZSA9PSAiUm9iYmVyeV9Db3VudCIpLCBhZXMoZmlsbD1WYWx1ZSksIGNvbG91cj1OQSkgKwogIHNjYWxlX2ZpbGxfdmlyaWRpcyhvcHRpb24gPSAibWFnbWEiKSArIAogIGxhYnModGl0bGUgPSAiUm9iYmVyeSBDb3VudCIpICsKICB0aGVtZShheGlzLnRleHQueD1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGlja3MgPWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aXRsZS55ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDksZmFjZSA9ICJpdGFsaWMiKSwKICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCwgZmFjZSA9ICJib2xkIiksCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X3JlY3QoY29sb3VyID0gImdyZXkiLCBmaWxsPU5BLCBzaXplPTAuOCkKICAgICAgICApCgptb3JhbjIgPC0gZ2dwbG90KCkgKwogIGdlb21fc2YoZGF0YSA9IGZpbmFsX25ldC5sb2NhbE1vcmFucyAlPiUgZmlsdGVyKFZhcmlhYmxlID09ICJMb2NhbF9Nb3JhbnNfSSIpLCBhZXMoZmlsbD1WYWx1ZSksIGNvbG91cj1OQSkgKwogIHNjYWxlX2ZpbGxfdmlyaWRpcyhvcHRpb24gPSAibWFnbWEiKSArIAogIGxhYnModGl0bGUgPSAiTG9jYWwgTW9yYW5zIEkiKSArCiAgdGhlbWUoYXhpcy50ZXh0Lng9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGV4dC55PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpY2tzID1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGl0bGUueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSA5LGZhY2UgPSAiaXRhbGljIiksCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTAsIGZhY2UgPSAiYm9sZCIpLAogICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9yZWN0KGNvbG91ciA9ICJncmV5IiwgZmlsbD1OQSwgc2l6ZT0wLjgpCiAgICAgICAgKQoKbW9yYW4zIDwtIGdncGxvdCgpICsKICBnZW9tX3NmKGRhdGEgPSBmaW5hbF9uZXQubG9jYWxNb3JhbnMgJT4lIGZpbHRlcihWYXJpYWJsZSA9PSAiUF9WYWx1ZSIpLCBhZXMoZmlsbD1WYWx1ZSksIGNvbG91cj1OQSkgKwogIHNjYWxlX2ZpbGxfdmlyaWRpcyhvcHRpb24gPSAibWFnbWEiKSArIAogIGxhYnModGl0bGUgPSAiUCBWYWx1ZSIpICsKICB0aGVtZShheGlzLnRleHQueD1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGlja3MgPWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aXRsZS55ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDksZmFjZSA9ICJpdGFsaWMiKSwKICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCwgZmFjZSA9ICJib2xkIiksCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X3JlY3QoY29sb3VyID0gImdyZXkiLCBmaWxsPU5BLCBzaXplPTAuOCkKICAgICAgICApCgoKbW9yYW40IDwtIGdncGxvdCgpICsKICBnZW9tX3NmKGRhdGEgPSBmaW5hbF9uZXQubG9jYWxNb3JhbnMgJT4lIGZpbHRlcihWYXJpYWJsZSA9PSAiU2lnbmlmaWNhbnRfSG90c3BvdHMiKSwgYWVzKGZpbGw9VmFsdWUpLCBjb2xvdXI9TkEpICsKICBzY2FsZV9maWxsX3ZpcmlkaXMob3B0aW9uID0gIm1hZ21hIikgKyAKICBsYWJzKHRpdGxlID0gIlNpZ25pZ2ljYW50IEhvdHNwb3RzIikgKwogIHRoZW1lKGF4aXMudGV4dC54PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRleHQueT1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aWNrcyA9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpdGxlLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gOSxmYWNlID0gIml0YWxpYyIpLAogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwLCBmYWNlID0gImJvbGQiKSwKICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfcmVjdChjb2xvdXIgPSAiZ3JleSIsIGZpbGw9TkEsIHNpemU9MC44KQogICAgICAgICkKCm1vcmFuMSArIG1vcmFuMiArIG1vcmFuMyArIG1vcmFuNApgYGAKCiMjIFNpbmdpZmljYW50IFJvYmJlcnkgQ2x1c3RlcnMKCkZvbGxvd2luZyB0aGUgY2FsY3VsYXRpb24gb2Ygc2lnbmlmaWNhbnQgcm9iYmVyeSBjbHVzdGVycywgd2UgY3JlYXRlZCBhIGR1bW15IHZhcmlhYmxlIGluZGljYXRpbmcgd2hldGhlciB0aGlzIGdyaWQgY2VsbCBpcyBsb2NhdGVkIHdpdGhpbiB0aGUgaG90c3BvdC4gV2UgYWxzbyBjYWxjdWxhdGVkIHRoZSBkaXN0YW5jZSBmcm9tIHRoZSBjZW50cm9pZCBvZiBlYWNoIGdyaWQgY2VsbCB0byBpdHMgbmVhcmVzdCByb2JiZXJ5IGhvdHNwb3QuIAoKYGBge3IgY2FsY3VsYXRlIHNpZ25pZmljYW50IHJvYmJlcnkgcG9pbnRzLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQoKZmluYWxfbmV0IDwtCiAgZmluYWxfbmV0ICU+JSAKICBtdXRhdGUocm9iYmVyeS5pc1NpZyA9IGlmZWxzZShsb2NhbG1vcmFuKGZpbmFsX25ldCRjb3VudFJvYmJlcnksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZpbmFsX25ldC53ZWlnaHRzKVssNV0gPD0gMC4wMDAwMDAxLCAxLCAwKSkgJT4lCiAgbXV0YXRlKHJvYmJlcnkuaXNTaWcuZGlzdCA9IG5uX2Z1bmN0aW9uKHN0X2Nvb3JkaW5hdGVzKHN0X2NlbnRyb2lkKGZpbmFsX25ldCkpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdF9jb29yZGluYXRlcyhzdF9jZW50cm9pZCgKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmaWx0ZXIoZmluYWxfbmV0LCByb2JiZXJ5LmlzU2lnID09IDEpKSksIDEpKQpgYGAKClRoZSBmaWd1cmUgYmVsb3cgdmlzdWFsaXplcyBkaXN0YW5jZSB0byBuZWFyZXN0IHJvYmJlcnkgaG90c3BvdHMgaW4gQ2hpY2Fnbywgd2hlcmUgdGhlIGRhcmtlciB0aGUgY29sb3IsIHRoZSBzaG9ydGVyIHRoZSBkaXN0YW5jZS4gCgpgYGB7ciB2aXN1YWxpemUgcm9iYmVyeSBOTiBEaXN0YW5jZSwgZmlnLmFsaWduID0gJ2NlbnRlcid9CgpnZ3Bsb3QoKSArCiAgICAgIGdlb21fc2YoZGF0YSA9IGZpbmFsX25ldCwgYWVzKGZpbGw9cm9iYmVyeS5pc1NpZy5kaXN0KSwgY29sb3VyPU5BKSArCiAgICAgIHNjYWxlX2ZpbGxfdmlyaWRpcyhvcHRpb24gPSAibWFnbWEiLCBuYW1lPSJOTiBEaXN0YW5jZSIpICsKICAgICAgbGFicyh0aXRsZT0iUm9iYmVyeSBOTiBEaXN0YW5jZSIpICsKICAgICAgdGhlbWUoYXhpcy50ZXh0Lng9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGV4dC55PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpY2tzID1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGl0bGUueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSA5LGZhY2UgPSAiaXRhbGljIiksCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTAsIGZhY2UgPSAiYm9sZCIpLAogICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9yZWN0KGNvbG91ciA9ICJncmV5IiwgZmlsbD1OQSwgc2l6ZT0wLjgpCiAgICAgICAgKQoKCmBgYAoKIyMgQWxsIFJpc2sgRmFjdG9ycwoKVGhlIGZpZ3VyZSBiZWxvdyBvcmdhbml6ZXMgY291bnQgYW5kIG5lYXJlc3QgbmVpZ2hib3IgKG5uKSBjb3JyZWxhdGlvbnMgc2lkZS1ieS1zaWRlLiBBbGwgb2Ygb3VyIGNvdW50IGZlYXR1cmVzIGFyZSBwb3NpdGl2ZWx5IGNvcnJlbGF0ZWQgd2l0aCByb2JiZXJ5IGluY2lkZW50cywgbWVhbmluZyB0aGUgZ3JlYXRlciBudW1iZXIgb2YgcmlzayBmYWN0b3JzLCB0aGUgaGlnaGVyIG51bWJlciBvZiByb2JiZXJ5LiBUaGUgcmVsYXRpb25zaGlwIGlzIHRoZSBzdHJvbmdlc3QgYmV0d2VlbiByb2JiZXJ5IGFuZCBzYW5pdGF0aW9uIGNvbXBsYWludCBhcyB3ZWxsIGFzIHNob3Qgc3BvdHRlci4gQWxsIG9mIG91ciBuZWFyZXN0IG5laWdoYm9yIGZlYXR1cmVzIGFyZSBuZWdhdGl2ZWx5IGNvcnJlbGF0ZWQgd2l0aCByb2JiZXJ5IGluY2lkZW50cywgbWVhbmluZyB0aGF0IHRoZSBncmVhdGVyIHRoZSBkaXN0YW5jZSwgdGhlIGxlc3MgbnVtYmVyIG9mIHJvYmJlcnkuIFRoZSByZWxhdGlvbnNoaXAgaXMgdGhlIHN0cm9uZ2VzdCBmb3IgZGlzdGFuY2UgdG8gc2lnbmZpY2FudCByb2JiZXJ5IGNsdXN0ZXJzLiBXaGlsZSBjb3JyZWxhdGlvbiBmb3IgY291bnQgZmVhdHVyZXMgaXMgYSBiaXQgYXdrd2FyZCwgdGhpcyBhcHByb2FjaCBjYW4gaGVscCB3aXRoIGZlYXR1cmUgc2VsZWN0aW9uLiBGb3IgYSBnaXZlbiByaXNrIGZhY3Rvciwgd2UgaGF2ZSB0byBhdm9pZCBjb2xpbmVhcml0eSBieSBzZWxlY3RpbmcgZWl0aGVyIHRoZSBjb3VudCBvciBuZWFyZXN0IG5laWdoYm9yIGZlYXR1cmUuIEluIHRoZSBjYXNlIG9mIG91ciBtb2RlbCwgd2UgdXNlZCBuZWFyZXN0IGRpc3RhbmNlLiAKCgpgYGB7ciBjb3JyZWxhdGlvbiwgZmlnLmhlaWdodD0xNSwgZmlnLndpZHRoPTgsIGZpZy5hbGlnbiA9ICdjZW50ZXInfQoKZmluYWxfbmV0X2xvbmcgPC0gZmluYWxfbmV0JT4lIAogIHN0X2Ryb3BfZ2VvbWV0cnkoKSAlPiUgCiAgZHBseXI6OnNlbGVjdCgtYyh1bmlxdWVJRCwgY3ZJRCkpICU+JSAKICBwaXZvdF9sb25nZXIoY29scyA9IC1jb3VudFJvYmJlcnksICMgZXZlcnl0aGluZyBleGNlcHQgbWVhc3VyZW1lbnQKICAgICAgICAgICAgICAgbmFtZXNfdG8gPSAiVHlwZSIsICMgY2F0ZWdvcml6ZXMgYWxsIHF1YW50aXRhdGl2ZSB2YXJpYWJsZXMgaW50byBUeXBlCiAgICAgICAgICAgICAgIHZhbHVlc190byA9ICJOdW1iZXIiKSAKCmNvcnJlbGF0aW9uLmNvciA8LQogIGZpbmFsX25ldF9sb25nICU+JQogICAgZ3JvdXBfYnkoVHlwZSkgJT4lCiAgICBzdW1tYXJpemUoY29ycmVsYXRpb24gPSBjb3IoTnVtYmVyLCBjb3VudFJvYmJlcnksIHVzZSA9ICJjb21wbGV0ZS5vYnMiKSkKCmZpbmFsX25ldF9sb25nICU+JQogIGdncGxvdChhZXMoeD0gTnVtYmVyLCB5ID0gY291bnRSb2JiZXJ5KSkgKwogIGdlb21fcG9pbnQoc2l6ZSA9IDAuMDEsIGNvbG9yID0gIiMwMDAwMDQiKSArICAKICBnZW9tX3RleHQoZGF0YSA9IGNvcnJlbGF0aW9uLmNvciwgYWVzKGxhYmVsID0gcGFzdGUoInIgPSIsIHJvdW5kKGNvcnJlbGF0aW9uLCAyKSkpLAogICAgICAgICAgICB4PS1JbmYsIHk9SW5mLCB2anVzdCA9IDEuNSwgaGp1c3QgPSAtLjEsIHNpemU9MykgKwogIGdlb21fc21vb3RoKG1ldGhvZD0nbG0nLCBmb3JtdWxhPSB5fngsIGx3ZD0wLjUsIHNlID0gRkFMU0UsIGNvbG9yID0gIiNCQjM3NTQiKSArCiAgZmFjZXRfd3JhcCh+IFR5cGUsIHNjYWxlcyA9ICJmcmVlIiwgbmNvbCA9IDIsIGxhYmVsbGVyPSBsYWJlbGxlcihUeXBlID0gYygKICAgIGBBYmFuZG9uZWRfQnVpbGRpbmdzYCA9ICJBYmFuZG9uZWQgQnVpbGRpbmdzIiwKICAgIGBBYmFuZG9uZWRfQnVpbGRpbmdzLm5uYCA9ICJEaXN0YW5jZSB0byBBYmFuZG9uZWQgQnVpbGRpbmdzIiwKICAgIGBBYmFuZG9uZWRfQ2Fyc2AgPSAiQWJhbmRvbmVkIENhcnMiLAogICAgYEFiYW5kb25lZF9DYXJzLm5uYCA9ICJEaXN0YW5jZSB0byBBYmFuZG9uZWQgQ2FycyIsCiAgICBgQmFua2AgPSAiQmFuayIsCiAgICBgQmFuay5ubmAgPSAiRGlzdGFuY2UgdG8gQmFuayIsCiAgICBgR3JhZmZpdGlgID0gIkdyYWZmaXRpIiwKICAgIGBHcmFmZml0aS5ubmAgPSAiRGlzdGFuY2UgdG8gR3JhZmZpdGkiLAogICAgYExpcXVvcl9SZXRhaWxgID0gIkxpcXVvciBSZXRhaWwiLAogICAgYExpcXVvcl9SZXRhaWwubm5gID0gIkRpc3RhbmNlIHRvIExpcXVvciBSZXRhaWwiLAogICAgYHJvYmJlcnkuaXNTaWdgID0gIlNpZ25pZmljYW50IFJvYmJlcnkiLAogICAgYHJvYmJlcnkuaXNTaWcuZGlzdGAgPSAiRGlzdGFuY2UgdG8gU2lnbmlmaWNhbnQgUm9iYmVyeSIsCiAgICBgU2FuaXRhdGlvbmAgPSAiU2FuaXRhdGlvbiIsCiAgICBgU2FuaXRhdGlvbi5ubmAgPSAiRGlzdGFuY2UgdG8gU2FuaXRhdGlvbiIsCiAgICBgU2hvdF9TcG90dGVyYCA9ICJTaG90IFNwb3R0ZXIiLCAKICAgIGBTaG90X1Nwb3R0ZXIubm5gID0gIkRpc3RhbmNlIHRvIFNob3QgU3BvdHRlciIsCiAgICBgU3RyZWV0X0xpZ2h0c19PdXRgID0gIlN0cmVldCBMaWdodCBPdXQiLAogICAgYFN0cmVldF9MaWdodHNfT3V0Lm5uYCA9ICJEaXN0YW5jZSB0byBCcm9rZW4gU3RyZWV0IExpZ2h0IikpKSAgKwogIGxhYnModGl0bGUgPSAiU2NhdHRlciBQbG90cyBvZiBQcmVkaWN0b3IgVmFyaWFibGVzIiwKICAgICAgIGNhcHRpb24gPSAiRGF0YTogQ2hpY2FnbyBEYXRhIFBvcnRhbCIpICsKICB0aGVtZShwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSA5LGZhY2UgPSAiaXRhbGljIiksCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIsIGZhY2UgPSAiYm9sZCIpLCAKICAgICAgICBheGlzLnRleHQueD1lbGVtZW50X3RleHQoc2l6ZT02KSwKICAgICAgICBheGlzLnRleHQueT1lbGVtZW50X3RleHQoc2l6ZT02KSwgCiAgICAgICAgYXhpcy50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT04KSwgCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X3JlY3QoY29sb3VyID0gImdyZXkiLCBmaWxsPU5BLCBzaXplPTAuOCkpCgpgYGAKCgojIFBvaXNzb24gUmVncmVzc2lvbgoKIyMgUmFuZG9tIEstRm9sZCBDVgoKQmVsb3csIGdvb2RuZXNzIG9mIGZpdCBtZXRyaWNzIGFyZSBnZW5lcmF0ZWQgZm9yIGZvdXIgcmVncmVzc2lvbnMgLSB0d28gaW5jbHVkaW5nIEp1c3QgUmlzayBGYWN0b3JzIGByZWcudmFyc2AsIGFuZCBhIHNlY29uZCBgcmVnLnNzLnZhcnNgIGluY2x1ZGVzIHJpc2sgZmFjdG9ycyBwbHVzIHRoZSBMb2NhbCBNb3JhbuKAmXMgSSBTcGF0aWFsIFByb2Nlc3MuIFR3byBraW5kcyBvZiBjcm9zcyB2YWxpZGF0aW9uIHRlc3Qgd2VyZSBwZXJmb3JtZWQuIFRoZSBmaXJzdCBvbmUgdXNlcyBhIHJhbmRvbWx5IGdlbmVyYXRlZCBgY3ZJRGAgYXNzb2NpYXRlZCB3aXRoIGVhY2ggZ3JpZCBjZWxsIGZvciByYW5kb20gay1mb2xkIGNyb3NzIHZhbGlkYXRpb24uIAoKYGBge3IgcmFuZG9tIGstZm9sZCByZWdyZXNzaW9uLCB3YXJuaW5nPUZBTFNFLCByZXN1bHRzPSdoaWRlJ30KCnJlZy52YXJzIDwtIGMoIkFiYW5kb25lZF9CdWlsZGluZ3Mubm4iLCAiQWJhbmRvbmVkX0NhcnMubm4iLCAiR3JhZmZpdGkubm4iLCAKICAgICAgICAgICAgICAiTGlxdW9yX1JldGFpbC5ubiIsICJTdHJlZXRfTGlnaHRzX091dC5ubiIsICJTYW5pdGF0aW9uLm5uIiwgCiAgICAgICAgICAgICAgIlNob3RfU3BvdHRlci5ubiIsICJCYW5rLm5uIikKCnJlZy5zcy52YXJzIDwtIGMoIkFiYW5kb25lZF9CdWlsZGluZ3Mubm4iLCAiQWJhbmRvbmVkX0NhcnMubm4iLCAiR3JhZmZpdGkubm4iLCAKICAgICAgICAgICAgICAgICAiTGlxdW9yX1JldGFpbC5ubiIsICJTdHJlZXRfTGlnaHRzX091dC5ubiIsICJTYW5pdGF0aW9uLm5uIiwgCiAgICAgICAgICAgICAgICAgIlNob3RfU3BvdHRlci5ubiIsICJCYW5rLm5uIiwgInJvYmJlcnkuaXNTaWciLCAicm9iYmVyeS5pc1NpZy5kaXN0IikKCnJlZy5jdiA8LSBjcm9zc1ZhbGlkYXRlKAogIGRhdGFzZXQgPSBmaW5hbF9uZXQsCiAgaWQgPSAiY3ZJRCIsCiAgZGVwZW5kZW50VmFyaWFibGUgPSAiY291bnRSb2JiZXJ5IiwKICBpbmRWYXJpYWJsZXMgPSByZWcudmFycykgJT4lCiAgICBkcGx5cjo6c2VsZWN0KGN2SUQgPSBjdklELCBjb3VudFJvYmJlcnksIFByZWRpY3Rpb24sIGdlb21ldHJ5KQoKcmVnLnNzLmN2IDwtIGNyb3NzVmFsaWRhdGUoCiAgZGF0YXNldCA9IGZpbmFsX25ldCwKICBpZCA9ICJjdklEIiwKICBkZXBlbmRlbnRWYXJpYWJsZSA9ICJjb3VudFJvYmJlcnkiLAogIGluZFZhcmlhYmxlcyA9IHJlZy5zcy52YXJzKSAlPiUKICAgIGRwbHlyOjpzZWxlY3QoY3ZJRCA9IGN2SUQsIGNvdW50Um9iYmVyeSwgUHJlZGljdGlvbiwgZ2VvbWV0cnkpCgpgYGAKCiMjIFNwYXRpYWwgTE9HTyBDVgoKVGhlIHNlY29uZCBvbmUgaG9sZCBvdXQgb25lIG5laWdoYm9yaG9vZCwgdHJhaW4gdGhlIG1vZGVsIG9uIHRoZSByZW1haW5pbmcgbi0xIGFyZWFzLCBwcmVkaWN0IGZvciB0aGUgaG9sZCBvdXQsIGFuZCByZWNvcmQgdGhlIGdvb2RuZXNzIG9mIGZpdC4gVGhpcyBpcyBhbHNvIGNhbGxlZCB0aGUgc3BhdGlhbCAnTGVhdmUtb25lLWdyb3VwLW91dCcgY3Jvc3MtdmFsaWRhdGlvbiAoTE9HTy1DViksIGluIHdoaWNoIGVhY2ggbmVpZ2hib3Job29kIHRha2VzIGEgdHVybiBhcyBhIGhvbGQtb3V0LiBUaGUgcmVzdWx0IG9mIGVhY2ggYW5hbHlzaXMgaXMgYSBzZiBsYXllciB3aXRoIG9ic2VydmVkIGFuZCBwcmVkaWN0ZWQgYnVyZ2xhcnkgY291bnRzLgoKYGBge3Igc3BhdGlhbCBsb2dvIGN2IHJlZ3Jlc3Npb24sIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0UsIHJlc3VsdHM9J2hpZGUnfQoKbmVpZ2hib3Job29kcyA8LSAKICBzdF9yZWFkKCJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vYmxhY2ttYWQvbmVpZ2hib3Job29kcy9tYXN0ZXIvY2hpY2Fnby5nZW9qc29uIikgJT4lCiAgc3RfdHJhbnNmb3JtKHN0X2NycyhmaXNobmV0KSkgCgpmaW5hbF9uZXQgPC0KICBzdF9jZW50cm9pZChmaW5hbF9uZXQpICU+JQogICAgc3Rfam9pbihkcGx5cjo6c2VsZWN0KG5laWdoYm9yaG9vZHMsIG5hbWUpKSAlPiUKICAgICAgc3RfZHJvcF9nZW9tZXRyeSgpICU+JQogICAgICBsZWZ0X2pvaW4oZHBseXI6OnNlbGVjdChmaW5hbF9uZXQsIGdlb21ldHJ5LCB1bmlxdWVJRCkpICU+JQogICAgICBzdF9zZigpICU+JQogIG5hLm9taXQoKQoKcmVnLnNwYXRpYWxDViA8LSBjcm9zc1ZhbGlkYXRlKAogIGRhdGFzZXQgPSBmaW5hbF9uZXQsCiAgaWQgPSAibmFtZSIsCiAgZGVwZW5kZW50VmFyaWFibGUgPSAiY291bnRSb2JiZXJ5IiwKICBpbmRWYXJpYWJsZXMgPSByZWcudmFycykgJT4lCiAgICBkcGx5cjo6c2VsZWN0KGN2SUQgPSBuYW1lLCBjb3VudFJvYmJlcnksIFByZWRpY3Rpb24sIGdlb21ldHJ5KQoKcmVnLnNzLnNwYXRpYWxDViA8LSBjcm9zc1ZhbGlkYXRlKAogIGRhdGFzZXQgPSBmaW5hbF9uZXQsCiAgaWQgPSAibmFtZSIsCiAgZGVwZW5kZW50VmFyaWFibGUgPSAiY291bnRSb2JiZXJ5IiwKICBpbmRWYXJpYWJsZXMgPSByZWcuc3MudmFycykgJT4lCiAgICBkcGx5cjo6c2VsZWN0KGN2SUQgPSBuYW1lLCBjb3VudFJvYmJlcnksIFByZWRpY3Rpb24sIGdlb21ldHJ5KQpgYGAKCiMjIEVycm9yIEFuYWx5c2lzCgpUaGUgY29kZSBibG9jayBiZWxvdyBjcmVhdGVzIGEgbG9uZyBmb3JtIHJlZy5zdW1tYXJ5LCB0aGF0IGJpbmRzIHRvZ2V0aGVyIG9ic2VydmVkL3ByZWRpY3RlZCBjb3VudHMgYW5kIGVycm9ycyBmb3IgZWFjaCBncmlkIGNlbGwgYW5kIGZvciBlYWNoIHJlZ3Jlc3Npb24uIFJlc2lkdWFscyBhcmUgY2FsY3VsYXRlZCBmb3IgZWFjaCByZWdyZXNzaW9uIGJ5IHN1YnRyYWN0aW5nIHRoZSBvcmlnaW5hbCByb2JiZXJ5IGluY2lkZW50cyBmcm9tIHByZWRpY3RlZCBpbmNpZGVudHMuIAoKYGBge3Igc3VtbWFyaXplIHJlZ3Jlc3Npb259CgpyZWcuc3VtbWFyeSA8LSAKICByYmluZCgKICAgIG11dGF0ZShyZWcuY3YsICAgICAgICAgICBFcnJvciA9IFByZWRpY3Rpb24gLSBjb3VudFJvYmJlcnksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgUmVncmVzc2lvbiA9ICJSYW5kb20gay1mb2xkIENWOiBKdXN0IFJpc2sgRmFjdG9ycyIpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgbXV0YXRlKHJlZy5zcy5jdiwgICAgICAgIEVycm9yID0gUHJlZGljdGlvbiAtIGNvdW50Um9iYmVyeSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBSZWdyZXNzaW9uID0gIlJhbmRvbSBrLWZvbGQgQ1Y6IFNwYXRpYWwgUHJvY2VzcyIpLAogICAgCiAgICBtdXRhdGUocmVnLnNwYXRpYWxDViwgICAgRXJyb3IgPSBQcmVkaWN0aW9uIC0gY291bnRSb2JiZXJ5LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIFJlZ3Jlc3Npb24gPSAiU3BhdGlhbCBMT0dPLUNWOiBKdXN0IFJpc2sgRmFjdG9ycyIpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgbXV0YXRlKHJlZy5zcy5zcGF0aWFsQ1YsIEVycm9yID0gUHJlZGljdGlvbiAtIGNvdW50Um9iYmVyeSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBSZWdyZXNzaW9uID0gIlNwYXRpYWwgTE9HTy1DVjogU3BhdGlhbCBQcm9jZXNzIikpICU+JQogICAgc3Rfc2YoKSAKYGBgCgpDb21wYXJpbmcgdGhlIGVycm9yIGFjcm9zcyBkaWZmZXJlbnQgcmVncmVzc2lvbnMsIHdlIHNlZSB0aGF0IGFkZGluZyBzcGF0aWFsIHByb2Nlc3MgZmVhdHVyZXMgaW1wcm92ZSB0aGUgbW9kZWwuIEluIGFkZGl0aW9uLCB0aGUgbW9kZWwgYXBwZWFycyBzbGlnaHRseSBsZXNzIHJvYnVzdCBmb3IgdGhlIHNwYXRpYWwgY3Jvc3MtdmFsaWRhdGlvbiwgcHJvYmFibHkgYmVjYXVzZSBMT0dPLUNWIGlzIHN1Y2ggYSBjb25zZXJ2YXRpdmUgYXNzdW1wdGlvbi4gCgpgYGB7ciBzdW1tYXJpemUgZXJyb3JzIGJ5IGZvbGQgYW5kIHJlZ3Jlc3Npb24sIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CgplcnJvcl9ieV9yZWdfYW5kX2ZvbGQgPC0gCiAgcmVnLnN1bW1hcnkgJT4lCiAgICBncm91cF9ieShSZWdyZXNzaW9uLCBjdklEKSAlPiUgCiAgICBzdW1tYXJpemUoTWVhbl9FcnJvciA9IG1lYW4oUHJlZGljdGlvbiAtIGNvdW50Um9iYmVyeSwgbmEucm0gPSBUKSwKICAgICAgICAgICAgICBNQUUgPSBtZWFuKGFicyhNZWFuX0Vycm9yKSwgbmEucm0gPSBUKSwKICAgICAgICAgICAgICBTRF9NQUUgPSBtZWFuKGFicyhNZWFuX0Vycm9yKSwgbmEucm0gPSBUKSkgJT4lIHVuZ3JvdXAoKQoKc3RfZHJvcF9nZW9tZXRyeShlcnJvcl9ieV9yZWdfYW5kX2ZvbGQpICU+JQogIGdyb3VwX2J5KFJlZ3Jlc3Npb24pICU+JSAKICAgIHN1bW1hcml6ZShNZWFuX01BRSA9IHJvdW5kKG1lYW4oTUFFKSwgMiksCiAgICAgICAgICAgICAgU0RfTUFFID0gcm91bmQoc2QoTUFFKSwgMikpICU+JQogIGthYmxlKGNvbC5uYW1lPWMoIlJlZ3Jlc3Npb24iLCAnTWVhbiBBYnNvbHV0ZSBFcnJvcicsJ1N0YW5kYXJkIERldmlhdGlvbiBNQUUnKSkgJT4lCiAgICBrYWJsZV9zdHlsaW5nKGJvb3RzdHJhcF9vcHRpb25zID0gYygic3RyaXBlZCIsICJob3ZlciIsICJjb25kZW5zZWQiKSkgJT4lCiAgICByb3dfc3BlYygyLCBjb2xvciA9ICJibGFjayIsIGJhY2tncm91bmQgPSAiI0ZDRkZBNCIpICU+JQogICAgcm93X3NwZWMoNCwgY29sb3IgPSAiYmxhY2siLCBiYWNrZ3JvdW5kID0gIiNGQ0ZGQTQiKSAKCgpgYGAKClRoZSBzbWFsbCBtdWx0aXBsZSBtYXBzIGJlbG93IHZpc3VhbGl6ZXMgdGhlIGRpc3RyaWJ1dGlvbiBvZiBlcnJvcnMgYnkgcmVncmVzc2lvbi4gRm9yIHRoZSByYW5kb20gay1mb2xkIGN2IG1ldGhvZCwgdGhlIGVycm9ycyBhcmUgcHJldHR5IGV2ZW5seSBkaXN0cmlidXRlZCBhY3Jvc3Mgc3BhY2UuIEhvd2V2ZXIsIGZvciBzcGF0aWFsIGxvZ28tY3YgbWV0aG9kLCBoaWdoZXIgZXJyb3JzIG9jY3VyIGF0IHNwYXRpYWwgaG90c3BvdHMuIAoKYGBge3IgdmlzdWFsaXplIGVycm9yLGZpZy5oZWlnaHQ9OCwgZmlnLndpZHRoPTExLCBmaWcuYWxpZ24gPSAnY2VudGVyJ30KCmVycm9yX2J5X3JlZ19hbmRfZm9sZCAlPiUgCiAgZ2dwbG90KCkgKwogIGdlb21fc2YoYWVzKGZpbGw9TUFFKSwgY29sb3I9InRyYW5zcGFyZW50IikgKwogIHNjYWxlX2ZpbGxfdmlyaWRpcyhvcHRpb24gPSAibWFnbWEiLCBkaXJlY3Rpb24gPSAtMSkgKyAKICBmYWNldF93cmFwKH5SZWdyZXNzaW9uLCBuY29sID0gNCkgKwogICBsYWJzKHRpdGxlID0gIk1BRSBieSBGb2xkIGFuZCBSZWdyZXNzaW9uIiwKICAgICAgIGNhcHRpb24gPSAiRGF0YTogQ2hpY2FnbyBEYXRhIFBvcnRhbCIpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIiwKICAgICAgICBheGlzLnRleHQueD1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aWNrcy54PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRleHQueT1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aWNrcy55PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSA5LGZhY2UgPSAiaXRhbGljIiksCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIsIGZhY2UgPSAiYm9sZCIpLAogICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9yZWN0KGNvbG91ciA9ICJncmV5IiwgZmlsbD1OQSwgc2l6ZT0wLjgpLAogICAgICAgIHN0cmlwLnRleHQgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDcpKSAKCmBgYAoKIyBQcmVkaWN0aW9uIEFjY3VyYWN5IAoKIyMgR2VuZXJhbGl6YWJpbGl0eSBUZXN0CgpMZXTigJlzIG5vdyB0ZXN0IGZvciBnZW5lcmFsaXphYmlsaXR5IGFjcm9zcyByYWNpYWwtbmVpZ2hib3Job29kIGNvbnRleHQuIFRvIHRlc3QgdGhpcyBwcm9wb3NpdGlvbiwgdGlkeWNlbnN1cyBpcyB1c2VkIHRvIHB1bGwgcmFjZSBkYXRhIGJ5IGNlbnN1cyB0cmFjdC4gUGVyY2VudGFnZSBtaW5vcml0eSBwb3B1bGF0aW9uIGZvciBlYWNoIGNlbnN1cyB0cmFjdCBpcyBjYWxjdWxhdGVkIGJ5IGNvbXBhcmluZyB0aGUgdG90YWwgbnVtYmVyIG9mIExhdGlueCwgQWZyaWNhbiBBbWVyaWNhbiwgYW5kIEFzaWFuIHBvcHVsYXRpb24gdG8gdGhlIHRvdGFsIHBvcHVsYXRpb24uIENlbnN1cyB0cmFjdHMgYXJlIHNwbGl0IGludG8gdHdvIGdyb3VwczogbWFqb3JpdHkgd2hpdGUgYW5kIG1pbm9yaXR5LCBkZXBlbmRpbmcgb24gd2hldGhlciB0aGUgcHJvcG9ydGlvbiBvZiBtaW5vcml0eSBwb3B1bGF0aW9uIGluIHRoYXQgY2Vuc3VzIHRyYWN0IGV4Y2VlZHMgNjAlLiAKCgpgYGB7ciBkb3dubG9hZCByYWNlIGRhdGEsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIHJlc3VsdHM9J2hpZGUnfQoKY2Vuc3VzX2FwaV9rZXkoZGxnSW5wdXQoCiAgIkVudGVyIGEgQ2Vuc3VzIEFQSSBLZXkiLCAjIGFzayBmb3IgYW4gYXBpIGtleQogIFN5cy5nZXRlbnYoIkNFTlNVU19BUElfS0VZIikKKSRyZXMsCm92ZXJ3cml0ZSA9IFRSVUUpCnRyYWN0czIxIDwtIAogIGdldF9hY3MoZ2VvZ3JhcGh5ID0gInRyYWN0IiwgCiAgICAgICAgICB2YXJpYWJsZXMgPSBjKCJCMDIwMDFfMDAxRSIsICMgdG90YWwgcG9wdWxhdGlvbgogICAgICAgICAgICAiQjAyMDAxXzAwMkUiLCAjIHdoaXRlIHBvcHVsYXRpb24KICAgICAgICAgICAgIkIwMjAwMV8wMDNFIiwgIyBibGFjayBwb3B1bGF0aW9uCiAgICAgICAgICAgICJCMDIwMDFfMDA1RSIsICMgYXNpYW4gcG9wdWxhdGlvbgogICAgICAgICAgICAiQjAzMDAyXzAxMkUiKSwgCiAgICAgICAgICB5ZWFyPTIwMjEsIHN0YXRlPTE3LCBjb3VudHk9MDMxLCAKICAgICAgICAgIGdlb21ldHJ5PVRSVUUsIG91dHB1dD0id2lkZSIpICU+JQogIHN0X3RyYW5zZm9ybSgnRVNSSToxMDIyNzEnKSAlPiUgCiAgcmVuYW1lKFRvdGFsUG9wID0gQjAyMDAxXzAwMUUsIAogICAgICAgICBXaGl0ZXMgPSBCMDIwMDFfMDAyRSwKICAgICAgICAgQWZyaWNhbl9BbWVyaWNhbnMgPSBCMDIwMDFfMDAzRSwKICAgICAgICAgQXNpYW5zID0gQjAyMDAxXzAwNUUsCiAgICAgICAgIExhdGlueCA9IEIwMzAwMl8wMTJFKSAlPiUgCiAgbXV0YXRlKHBjdE1pbm9yaXR5ID0gaWZlbHNlKFRvdGFsUG9wID4gMCwgKEFmcmljYW5fQW1lcmljYW5zICsgQXNpYW5zICsgTGF0aW54ICkgLyBUb3RhbFBvcCwgMCksIAogICAgICAgICBtYWpvcml0eSA9IGlmZWxzZShwY3RNaW5vcml0eSA+IDAuNSwgIm1pbm9yaXR5IiwgIm1ham9yaXR5IikpICU+JSAgIC5bbmVpZ2hib3Job29kcyxdCgoKYGBgCgpXZSBtYXkgc2VlIHRoYXQgQ2hpY2FnbyByZW1haW5zIGEgdmVyeSBzZWdyZWdhdGVkIGNpdHkgYW5kIHRoZXJlJ3MgY2xlYXIgcmFjaWFsIGJvdW5kYXJ5IGJldHdlZW4gcGxhY2VzIHdoZXJlIG1vcmUgdGhhbiA2MCUgb2YgdGhlIHBvcHVsYXRpb24gYXJlIG1pbm9yaXR5IGFuZCBwbGFjZXMgdGhhdCdzIG1ham9yaXR5IHdoaXRlLiBJbiBwYXJ0aWN1bGFyLCBub3J0aGVybiBhbmQgbm9ydGhlYXN0ZXJuIChDQkQpIHBhcnQgb2YgQ2hpY2FnbyBhcmUgbWFqb3JpdHkgd2hpdGUgd2hpbGUgdGhlIHJlbWFpbmluZyBwYXJ0cyBhcmUgYWxsIG1hZGUgdXAgb2YgcmFjaWFsIG1pbm9yaXRpZXMuIAoKYGBge3IgdmlzdWFsaXplIHJhY2UgY29udGV4dCwgZmlnLmFsaWduID0gJ2NlbnRlcid9CgpnZ3Bsb3QoKSArIGdlb21fc2YoZGF0YSA9IG5hLm9taXQodHJhY3RzMjEpLCBhZXMoZmlsbCA9IG1ham9yaXR5KSwgY29sb3IgPSBOQSkgKwogICAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gYygiI0JCMzc1NCIsICIjNTYxMDZFIiksIG5hbWU9IlJhY2UgQ29udGV4dCIpICsKICAgIGxhYnModGl0bGUgPSAiUmFjZSBDb250ZXh0ICIsCiAgICAgICAgIGNhcHRpb24gPSAiRGF0YTogQW1lcmljYW4gQ29tbXVuaXR5IFN1cnZleSAyMDIxIikgKwogIHRoZW1lKAogICAgICAgIGF4aXMudGV4dC54PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpY2tzLng9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGV4dC55PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpY2tzLnk9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSwKICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfcmVjdChjb2xvdXIgPSAiZ3JleSIsIGZpbGw9TkEsIHNpemU9MC40KQogICAgICAgICkgCmBgYAoKRXJyb3IgaXMgY2FsY3VsYXRlZCBieSBzdWJ0cmFjdGluZyB0aGUgb2JzZXJ2ZWQgcm9iYmVyeSBjb3VudCBmcm9tIHRoZSBwcmVkaWN0aW9uLiBBIHBvc2l0aXZlIGRpZmZlcmVuY2UgcmVwcmVzZW50cyBhbiBvdmVyLXByZWRpY3Rpb24uIFRoZSBsZWFzdCBpZGVhbCByZXN1bHQgaXMgYSBtb2RlbCB0aGF0IG92ZXItcHJlZGljdHMgcmlzayBpbiBtaW5vcml0eSBhcmVhcywgYW5kIHVuZGVyLXByZWRpY3RzIGluIHdoaXRlIGFyZWFzLiBJZiByZXBvcnRpbmcgc2VsZWN0aW9uIGJpYXMgaXMgYW4gaXNzdWUsIHN1Y2ggYSBtb2RlbCBtYXkgdW5mYWlybHkgYWxsb2NhdGUgcG9saWNlIHJlc291cmNlIGRpc3Byb3BvcnRpb25hdGVseSBpbiBibGFjayBhbmQgYnJvd24gY29tbXVuaXRpZXMuIEluIG91ciBjYXNlLCBvdmVyLXByZWRpY3Rpb24gb2YgbWlub3JpdGllcyBpcyBhbiBpc3N1ZSBmb3IgcmFuZG9tIGstZm9sZCBjdiByZWdyZXNzaW9uLCBidXQgbm90IGZvciBsb2dvLWN2IHJlZ3Jlc3Npb25zLiBUaGUgbGF0dGVyIHR3byBtb2RlbHMgdW5kZXItcHJlZGljdCBtaW5vcml0eSBuZWlnaGJvcmhvb2RzIGFuZCBvdmVyLXByZWRpY3QgbWFqb3JpdHkgd2hpdGUgbmVpZ2hib3Job29kLCBtYWtpbmcgaXQgbG9va3MgbGlrZSB0aGF0IHRoaXMgYWxnb3JpdGhtcyBnZW5lcmFsaXplcyB3ZWxsIHdpdGggcmVzcGVjdCB0byByYWNlLiAKCmBgYHtyIGdlbmVyYWxpemFiaWxpdHkgYnkgcmFjZSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KCmpvaW5yYWNlIDwtIHN0X2NlbnRyb2lkKHJlZy5zdW1tYXJ5KSAlPiUgCiAgc3RfaW50ZXJzZWN0aW9uKHRyYWN0czIxICU+JWRwbHlyOjogc2VsZWN0KHBjdE1pbm9yaXR5KSkgJT4lIAogIHN0X2Ryb3BfZ2VvbWV0cnkoKSAlPiUgCiAgZ3JvdXBfYnkoY3ZJRCkgJT4lIAogIHN1bW1hcml6ZShtZWFuTWlub3IgPSBtZWFuKHBjdE1pbm9yaXR5KSkKCgpyZWcuc3VtbWFyeSA8LSByZWcuc3VtbWFyeSAlPiUgCiAgbGVmdF9qb2luKGpvaW5yYWNlLCBieSA9ICJjdklEIikgCgoKcmVnLnN1bW1hcnkgJT4lIAogIG11dGF0ZShyYWNlQ29udGV4dCA9IGlmZWxzZShtZWFuTWlub3IgPiAuNiwgIk1pbm9yaXR5IiwgIk1ham9yaXR5IFdoaXRlIikpICU+JSAKICBzdF9kcm9wX2dlb21ldHJ5KCkgJT4lIAogIGdyb3VwX2J5KFJlZ3Jlc3Npb24sIHJhY2VDb250ZXh0KSAlPiUKICBzdW1tYXJpemUobWVhbi5FcnJvciA9IG1lYW4oRXJyb3IsIG5hLnJtID0gVCkpICU+JQogIHNwcmVhZChyYWNlQ29udGV4dCwgbWVhbi5FcnJvcikgJT4lCiAga2FibGUoKSAlPiUKICAgIGthYmxlX3N0eWxpbmcoYm9vdHN0cmFwX29wdGlvbnMgPSBjKCJzdHJpcGVkIiwgImhvdmVyIiwgImNvbmRlbnNlZCIpKSAlPiUKICAgIHJvd19zcGVjKDIsIGNvbG9yID0gImJsYWNrIiwgYmFja2dyb3VuZCA9ICIjRkNGRkE0IikgJT4lCiAgICByb3dfc3BlYyg0LCBjb2xvciA9ICJibGFjayIsIGJhY2tncm91bmQgPSAiI0ZDRkZBNCIpIAoKYGBgCgojIyBLZXJuZWwgRGVzbnRpeSBFc3RpbWF0aW9uCgpUaGUgdXRpbGl0eSBvZiB0aGlzIGFsZ29yaXRobSBpcyBqdWRnZWQgcmVsYXRpdmUgdG8gYW4gYWx0ZXJuYXRpdmUgcG9saWNlIGFsbG9jYXRpb24gbWV0aG9kOiBrZXJuZWwgZGVuc2l0eSBlc3RpbWF0aW9uLiBLZXJuZWwgZGVuc2l0eSB3b3JrcyBieSBjZW50ZXJpbmcgYSBzbW9vdGgga2VybmVsLCBvciBjdXJ2ZSwgYXRvcCBlYWNoIGNyaW1lIHBvaW50IHN1Y2ggdGhhdCB0aGUgY3VydmUgaXMgYXQgaXRzIGhpZ2hlc3QgZGlyZWN0bHkgb3ZlciB0aGUgcG9pbnQgYW5kIHRoZSBsb3dlc3QgYXQgdGhlIHJhbmdlIG9mIGEgY2lyY3VsYXIgc2VhcmNoIHJhZGl1cy4gVGhlIGNvZGUgYmxvY2sgYmVsb3cgY3JlYXRlcyBhIEtlcm5lbCBkZW5zaXR5IG1hcCBvZiByb2JiZXJ5IHdpdGggYSAxMDAwIGZvb3Qgc2VhcmNoIHJhZGl1cy4gCiAKYGBge3IgY2FsY3VsYXRlIDIwMjEgS2VybmFsIERlbnNpdHksIGZpZy53aWR0aD02LCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBmaWcuYWxpZ24gPSAnY2VudGVyJ30KCnJvYl9wcHAgPC0gYXMucHBwKHN0X2Nvb3JkaW5hdGVzKHJvYmJlcnkyMSksIFcgPSBzdF9iYm94KGZpbmFsX25ldCkpCnJvYl9LRC4xMDAwIDwtIHNwYXRzdGF0LmV4cGxvcmU6OmRlbnNpdHkucHBwKHJvYl9wcHAsIDEwMDApCnJvYl9LRC5kZiA8LSBkYXRhLmZyYW1lKHJhc3RlclRvUG9pbnRzKG1hc2socmFzdGVyKHJvYl9LRC4xMDAwKSwgYXMobmVpZ2hib3Job29kcywgJ1NwYXRpYWwnKSkpKQoKCmdncGxvdChkYXRhPXJvYl9LRC5kZiwgYWVzKHg9eCwgeT15KSkgKwogIGdlb21fcmFzdGVyKGFlcyhmaWxsPWxheWVyKSkgKyAKICBjb29yZF9zZihjcnM9c3RfY3JzKGZpbmFsX25ldCkpICsgCiAgc2NhbGVfZmlsbF92aXJpZGlzKG9wdGlvbiA9ICJtYWdtYSIsIG5hbWU9IkRlbnNpdHkiKSArCiAgbGFicyh0aXRsZSA9ICJLZXJuZWwgRGVuc2l0eSBvZiBSb2JiZXJ5IDEwMDBmdCBSYWRpaSIpICsKICAgIHRoZW1lKGF4aXMudGV4dC54PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRleHQueT1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aWNrcyA9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpdGxlLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gOSxmYWNlID0gIml0YWxpYyIpLAogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSwKICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfcmVjdChjb2xvdXIgPSAiZ3JleSIsIGZpbGw9TkEsIHNpemU9MC44KQogICAgICAgICkKYGBgCgpOZXh0LCBhIG5ldyBnb29kbmVzcyBvZiBmaXQgaW5kaWNhdG9yIGlzIGNyZWF0ZWQgdG8gaWxsdXN0cmF0ZSB3aGV0aGVyIHRoZSAyMDIxIGtlcm5lbCBkZW5zaXR5IG9yIHJpc2sgcHJlZGljdGlvbnMgY2FwdHVyZSBtb3JlIG9mIHRoZSAyMDIyIHJvYmJlcmllcy4KCmBgYHtyIGRvd25sb2FkIDIwMjIgcm9iZXJ5IGRhdGEsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9Cgpyb2JiZXJ5MjIgPC0gCiAgcmVhZC5zb2NyYXRhKCJodHRwczovL2RhdGEuY2l0eW9mY2hpY2Fnby5vcmcvUHVibGljLVNhZmV0eS9DcmltZXMtMjAyMi85aHdyLTJ6eHAiKSAlPiUgCiAgIGZpbHRlcihQcmltYXJ5LlR5cGUgPT0gIlJPQkJFUlkiKSAlPiUgCiAgbXV0YXRlKHggPSBnc3ViKCJbKCldIiwgIiIsIExvY2F0aW9uKSkgJT4lCiAgc2VwYXJhdGUoeCxpbnRvPSBjKCJZIiwiWCIpLCBzZXA9IiwiKSAlPiUKICBtdXRhdGUoWCA9IGFzLm51bWVyaWMoWCksCiAgICAgICAgIFkgPSBhcy5udW1lcmljKFkpKSAlPiUgCiAgbmEub21pdCAlPiUKICBzdF9hc19zZihjb29yZHMgPSBjKCJYIiwgIlkiKSwgY3JzID0gNDMyNiwgYWdyID0gImNvbnN0YW50IikgJT4lCiAgc3RfdHJhbnNmb3JtKCdFU1JJOjEwMjI3MScpICU+JSAKICBkaXN0aW5jdCgpICU+JQogIC5bZmlzaG5ldCxdCgpgYGAKClRoZSBrZXJuZWwgZGVuc2l0eSBlc3RpbWF0aW9uIHdhcyBjbGFzc2lmaWVkIGludG8gNSByaXNrIGNhdGVnb3JpZXMgYW5kIHRoZSBjb3VudCBmb3IgdGhlIDIwMjIgcm9iYmVyaWVzIHdlcmUgc3VtbWFyaXplZCBpbnRvIHRoZSBzYW1lIHNmIGxheWVyLiAgVGhlIHB1cnBvc2UgaGVyZSBpcyB0byBzZWUgaWYgdGhlIHJpc2sgcHJlZGljdGlvbnMgY2FwdHVyZSBtb3JlIG9ic2VydmVkIGJ1cmdsYXJpZXMgdGhhbiB0aGUga2VybmVsIGRlbnNpdHkuIElmIHNvLCB0aGVuIHRoZSByaXNrIHByZWRpY3Rpb24gbW9kZWwgcHJvdmlkZXMgYSBtb3JlIHJvYnVzdCB0YXJnZXRpbmcgdG9vbCBmb3IgYWxsb2NhdGluZyBwb2xpY2UgcmVzb3VyY2VzLiAKCmBgYHtyIGNhdGVnb3JpemUgS0RFfQoKcm9iX0tERV9zdW0gPC0gYXMuZGF0YS5mcmFtZShyb2JfS0QuMTAwMCkgJT4lCiAgc3RfYXNfc2YoY29vcmRzID0gYygieCIsICJ5IiksIGNycyA9IHN0X2NycyhmaW5hbF9uZXQpKSAlPiUKICBhZ2dyZWdhdGUoLiwgZmluYWxfbmV0LCBtZWFuKSAKCmtkZV9icmVha3MgPC0gY2xhc3NJbnRlcnZhbHMocm9iX0tERV9zdW0kdmFsdWUsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIG4gPSA1LCAiZmlzaGVyIikKCnJvYl9LREVfc2YgPC0gcm9iX0tERV9zdW0gJT4lCiAgbXV0YXRlKGxhYmVsID0gIktlcm5lbCBEZW5zaXR5IiwKICAgICAgICAgUmlza19DYXRlZ29yeSA9IGNsYXNzSW50OjpmaW5kQ29scyhrZGVfYnJlYWtzKSwKICAgICAgICAgUmlza19DYXRlZ29yeSA9IGNhc2Vfd2hlbigKICAgICAgICAgICBSaXNrX0NhdGVnb3J5ID09IDUgfiAiNXRoIiwKICAgICAgICAgICBSaXNrX0NhdGVnb3J5ID09IDQgfiAiNHRoIiwKICAgICAgICAgICBSaXNrX0NhdGVnb3J5ID09IDMgfiAiM3JkIiwKICAgICAgICAgICBSaXNrX0NhdGVnb3J5ID09IDIgfiAiMm5kIiwKICAgICAgICAgICBSaXNrX0NhdGVnb3J5ID09IDEgfiAiMXN0IikpICU+JQogIGNiaW5kKAogICAgYWdncmVnYXRlKAogICAgICBkcGx5cjo6c2VsZWN0KHJvYmJlcnkyMSkgJT4lIG11dGF0ZShyb2JiZXJ5Q291bnQgPSAxKSwgLiwgc3VtKSAlPiUKICAgIG11dGF0ZShyb2JiZXJ5Q291bnQgPSByZXBsYWNlX25hKHJvYmJlcnlDb3VudCwgMCkpKSAlPiUKICBkcGx5cjo6c2VsZWN0KGxhYmVsLCBSaXNrX0NhdGVnb3J5LCByb2JiZXJ5Q291bnQpCgpgYGAKClRoZSBzYW1lIHByb2Nlc3Mgd2FzIHJlcGVhdGVkIGZvciB0aGUgcmlzayBwcmVkaWN0aW9uLiBOb3RlIHRoYXQgYm90aCBwcmVkaWN0aW9ucyBmcm9tIHRoZSBMT0dPLUNWIHdpdGggYW5kIHdpdGhvdXQgdGhlIHNwYXRpYWwgZmVhdHVyZXMgaXMgYmVpbmcgdXNlZCBoZXJlLgoKYGBge3IgY2F0ZWdvcml6ZSByYW5kb20gayBmb2xkfQoKbWxfYnJlYWtzIDwtIGNsYXNzSW50ZXJ2YWxzKHJlZy5zcy5zcGF0aWFsQ1YkUHJlZGljdGlvbiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbiA9IDUsICJmaXNoZXIiKQpyb2Jfcmlza19zZiA8LQogIHJlZy5zcy5zcGF0aWFsQ1YgJT4lCiAgbXV0YXRlKGxhYmVsID0gIlJpc2sgUHJlZGljdGlvbnMgU3BhdGlhbCIsCiAgICAgICAgIFJpc2tfQ2F0ZWdvcnkgPWNsYXNzSW50OjpmaW5kQ29scyhtbF9icmVha3MpLAogICAgICAgICBSaXNrX0NhdGVnb3J5ID0gY2FzZV93aGVuKAogICAgICAgICAgIFJpc2tfQ2F0ZWdvcnkgPT0gNSB+ICI1dGgiLAogICAgICAgICAgIFJpc2tfQ2F0ZWdvcnkgPT0gNCB+ICI0dGgiLAogICAgICAgICAgIFJpc2tfQ2F0ZWdvcnkgPT0gMyB+ICIzcmQiLAogICAgICAgICAgIFJpc2tfQ2F0ZWdvcnkgPT0gMiB+ICIybmQiLAogICAgICAgICAgIFJpc2tfQ2F0ZWdvcnkgPT0gMSB+ICIxc3QiKSkgJT4lCiAgY2JpbmQoCiAgICBhZ2dyZWdhdGUoCiAgICAgIGRwbHlyOjpzZWxlY3Qocm9iYmVyeTIxKSAlPiUgbXV0YXRlKHJvYmJlcnlDb3VudCA9IDEpLCAuLCBzdW0pICU+JQogICAgICBtdXRhdGUocm9iYmVyeUNvdW50ID0gcmVwbGFjZV9uYShyb2JiZXJ5Q291bnQsIDApKSkgJT4lCiAgZHBseXI6OnNlbGVjdChsYWJlbCxSaXNrX0NhdGVnb3J5LCByb2JiZXJ5Q291bnQpCgptbF9icmVha3Nfc2ltcGxlIDwtIGNsYXNzSW50ZXJ2YWxzKHJlZy5zcy5jdiRQcmVkaWN0aW9uLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuID0gNSwgImZpc2hlciIpCnJvYl9yaXNrX3NmX3NpbXBsZSA8LQogIHJlZy5zcy5jdiAlPiUKICBtdXRhdGUobGFiZWwgPSAiUmlzayBQcmVkaWN0aW9ucyBTaW1wbGUiLAogICAgICAgICBSaXNrX0NhdGVnb3J5ID1jbGFzc0ludDo6ZmluZENvbHMobWxfYnJlYWtzX3NpbXBsZSksCiAgICAgICAgIFJpc2tfQ2F0ZWdvcnkgPSBjYXNlX3doZW4oCiAgICAgICAgICAgUmlza19DYXRlZ29yeSA9PSA1IH4gIjV0aCIsCiAgICAgICAgICAgUmlza19DYXRlZ29yeSA9PSA0IH4gIjR0aCIsCiAgICAgICAgICAgUmlza19DYXRlZ29yeSA9PSAzIH4gIjNyZCIsCiAgICAgICAgICAgUmlza19DYXRlZ29yeSA9PSAyIH4gIjJuZCIsCiAgICAgICAgICAgUmlza19DYXRlZ29yeSA9PSAxIH4gIjFzdCIpKSAlPiUKICBjYmluZCgKICAgIGFnZ3JlZ2F0ZSgKICAgICAgZHBseXI6OnNlbGVjdChyb2JiZXJ5MjEpICU+JSBtdXRhdGUocm9iYmVyeUNvdW50ID0gMSksIC4sIHN1bSkgJT4lCiAgICAgIG11dGF0ZShyb2JiZXJ5Q291bnQgPSByZXBsYWNlX25hKHJvYmJlcnlDb3VudCwgMCkpKSAlPiUKICBkcGx5cjo6c2VsZWN0KGxhYmVsLFJpc2tfQ2F0ZWdvcnksIHJvYmJlcnlDb3VudCkKCmBgYAoKIyMgUHJlZGljdGlvbiBDb21wYXJpc29uCgpCZWxvdyBhIG1hcCBpcyBnZW5lcmF0ZWQgb2YgdGhlIHJpc2sgY2F0ZWdvcmllcyBmb3IgYWxsIHRocmVlIG1vZGVsIHR5cGVzLiBBIHN0cm9uZ2x5IGZpdCBtb2RlbCBzaG91bGQgc2hvdyB0aGF0IHRoZSBoaWdoZXN0IHJpc2sgY2F0ZWdvcnkgaXMgdW5pcXVlbHkgdGFyZ2V0ZWQgdG8gcGxhY2VzIHdpdGggYSBoaWdoIGRlbnNpdHkgb2YgYnVyZ2xhcnkgcG9pbnRzLiBUaGUgbWFwIHNob3dzIHRoYXQgb3VyIHJpc2sgcHJlZGljdGlvbiBtb2RlbHMgcGVyZm9ybSBhcyBnb29kIGFzIHRoZSBrZXJuYWwgZGVuc2l0eSBhcHByb2FjaCBpbiBjYXB0dXJpbmcgdGhlIGRpc3RyaWJ1dGlvbiBvZiByb2JiZXJ5IGluY2lkZW50cy4gCgpgYGB7ciBwcmVkaWN0aW9uIGNvbXBhcmlzb25zIG1hcCwgZmlnLmFsaWduID0gJ2NlbnRlcid9CnJiaW5kKHJvYl9LREVfc2YsIHJvYl9yaXNrX3NmLCByb2Jfcmlza19zZl9zaW1wbGUpICU+JQogIG5hLm9taXQoKSAlPiUKICBnYXRoZXIoVmFyaWFibGUsIFZhbHVlLCAtbGFiZWwsIC1SaXNrX0NhdGVnb3J5LCAtZ2VvbWV0cnkpICU+JQogIGdncGxvdCgpICsKICAgIGdlb21fc2YoYWVzKGZpbGwgPSBSaXNrX0NhdGVnb3J5KSwgY29sb3VyID0gTkEpICsKICAgIGZhY2V0X3dyYXAofmxhYmVsLCApICsKICAgIHNjYWxlX2ZpbGxfdmlyaWRpcyhvcHRpb24gPSAibWFnbWEiLCBkaXNjcmV0ZSA9IFRSVUUsIG5hbWUgPSAiUmlzayBDYXRlZ29yeSIpICsKICAgICAgZ2VvbV9zZihkYXRhID0gc2FtcGxlX24ocm9iYmVyeTIyLCAzMDAwKSwgc2l6ZSA9IC4wMywgY29sb3VyID0gIndoaXRlIikgKwogICAgbGFicyh0aXRsZT0iQ29tcGFyaXNvbiBvZiBLZXJuZWwgRGVuc2l0eSBhbmQgUmlzayBQcmVkaWN0aW9ucyIsCiAgICAgICAgIHN1YnRpdGxlPSIyMDIxIHJvYmJlcnkgcmlzayBwcmVkaWN0aW9uczsgMjAyMiByb2JiZXJpZXMiKSArIAogIHRoZW1lKGF4aXMudGV4dC54PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRleHQueT1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aWNrcyA9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpdGxlLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gOSxmYWNlID0gIml0YWxpYyIpLAogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSwKICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfcmVjdChjb2xvdXIgPSAiZ3JleSIsIGZpbGw9TkEsIHNpemU9MC44KQogICAgICAgICkKYGBgCgpBIG1vcmUgc3RyYWlnaHRmb3J3YXJkIHdheSB0byBjb21wYXJlIHRoZSBhY2N1cmFjeSBvZiBwcmVkaWN0aW9uIGlzIHRocm91Z2ggaGlzdG9ncmFtLiBBIHdlbGwgZml0IG1vZGVsIHNob3VsZCBzaG93IHRoYXQgdGhlIHJpc2sgcHJlZGljdGlvbnMgY2FwdHVyZSBhIGdyZWF0ZXIgc2hhcmUgb2YgMjAyMiByb2JiZXJpZXMgaW4gdGhlIGhpZ2hlc3QgcmlzayBjYXRlZ29yeSByZWxhdGl2ZSB0byB0aGUgS2VybmVsIGRlbnNpdHkuIFVuZm9ydHVuYXRlbHksIHRoZSBrZXJuZWwgZGVuc2l0eSBtb2RlbCBlZGdlcyBvdXQgYm90aCByaXNrIHByZWRpY3Rpb24gbW9kZWwgZm9yIGhpZ2hlc3QgcmlzayBhcmVhcy4gTmV2ZXJ0aGVsZXNzLCB0aGUgcmlzayBwcmVkaWN0aW9uIG1vZGVsIGRvZXMgYSBiZXR0ZXIgam9iIGluIGNhcHR1cmluZyByb2JiZXJ5IGluY2lkZW50cyBpbiBsb3dlci1yaXNrIGFyZWFzIHRoYXQgdGhlIGtlcm5lbCBkZW5zaXR5IG1vZGVsIGhhZCBpZ25vcmVkLiAKCmBgYHtyIHByZWRpY3Rpb24gY29tcGFyaXNvbnMgZmlndXJlLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFLCBmaWcuYWxpZ24gPSAnY2VudGVyJ30KcmJpbmQocm9iX0tERV9zZiwgcm9iX3Jpc2tfc2YsIHJvYl9yaXNrX3NmX3NpbXBsZSkgJT4lCiAgc3RfZHJvcF9nZW9tZXRyeSgpICU+JQogIG5hLm9taXQoKSAlPiUKICBnYXRoZXIoVmFyaWFibGUsIFZhbHVlLCAtbGFiZWwsIC1SaXNrX0NhdGVnb3J5KSAlPiUKICBncm91cF9ieShsYWJlbCwgUmlza19DYXRlZ29yeSkgJT4lCiAgc3VtbWFyaXplKGNvdW50Um9iYmVyeSA9IHN1bShWYWx1ZSkpICU+JQogIHVuZ3JvdXAoKSAlPiUKICBncm91cF9ieShsYWJlbCkgJT4lCiAgbXV0YXRlKFBjbnRfb2ZfdGVzdF9zZXRfY3JpbWVzID0gY291bnRSb2JiZXJ5IC8gc3VtKGNvdW50Um9iYmVyeSkpICU+JQogICAgZ2dwbG90KGFlcyhSaXNrX0NhdGVnb3J5LFBjbnRfb2ZfdGVzdF9zZXRfY3JpbWVzKSkgKwogICAgICBnZW9tX2JhcihhZXMoZmlsbD1sYWJlbCksIHBvc2l0aW9uPSJkb2RnZSIsIHN0YXQ9ImlkZW50aXR5IikgKwogICAgICBzY2FsZV9maWxsX3ZpcmlkaXMob3B0aW9uID0gIm1hZ21hIiwgZGlzY3JldGUgPSBUUlVFLCBuYW1lID0gIk1vZGVsIiwgZGlyZWN0aW9uID0gLTEpICsKICAgICAgbGFicyh0aXRsZSA9ICJSaXNrIFByZWRpY3Rpb24gdnMuIEtlcm5lbCBEZW5zaXR5IiwKICAgICAgICAgICB5ID0gIiUgb2YgVGVzdCBTZXQgUm9iYmVyaWVzIChwZXIgbW9kZWwpIiwKICAgICAgICAgICB4ID0gIlJpc2sgQ2F0ZWdvcnkiKSArCiAgdGhlbWVfYncoKSArCiAgICAgIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIHZqdXN0ID0gMC41KSkKYGBgCgojIENvbmNsdXNpb25zCgpJbiBjb25jbHVzaW9uLCB0aGVyZSBhcmUgc3RpbGwgYSBsb3Qgb2Ygd29yayB0aGF0IG5lZWRzIHRvIGJlIGRvbmUgYmVmb3JlIHRoaXMgbW9kZWwgY291bGQgYmUgcHV0IGludG8gcHJhY3RpY2UuIENvbXBhcmluZyB0byB0aGUgdHJhZGl0aW9uYWwgbWV0aG9kb2xvZ3kgb2YgdXNpbmcga2VybmVsIGRlbnNpdHkgdG8gZXN0aW1hdGUgY3JpbWUgbG9jYXRpb25zLCBvdXIgbW9kZWwgZGlkIG5vdCBkbyB2ZXJ5IHdlbGwgaW4gY2FwdHVyaW5nIHJvYmJlcnkgaW5jaWRlbnRzIGluIGFyZWFzIGRlc2lnbmF0ZWQgYXMgdGhlIGhpZ2hlc3QgcmlzayBjYXRlZ29yeS4gVGhlcmUgYXJlIHR3byBtYWluIHJlYXNvbnMgYmVoaW5kLiBGaXJzdGx5LCBtb3N0IHByZWRpY3RvciB2YXJpYWJsZXMgd2UgdXNlZCBpbiBvdXIgbW9kZWwgYXJlIG91dCBvZiBkYXRlLiBXaGlsZSB0aGUgQ2hpY2FnbyBEYXRhIFBvcnRhbCBoYXMgYmVlbiBjb25zdGFudGx5IHVwZGF0aW5nIGl0cyBjcmltZSBkYXRhc2V0LCBhbG1vc3QgYWxsIG9mIHRoZSByaXNrIGZhY3RvciBkYXRhc2V0IHdlIHVzZWQgd2VyZSBsYXN0IHVwZGF0ZWQgaW4gMjAxOS4gVGhhdCBzYWlkLCB3ZSBhcmUgdXNpbmcgcmVwb3J0cyBvZiBhYmFuZG9uZWQgY2FycywgZ3JhZmZpdGksIGFiYW5kb25lZCBidWlsZGluZ3MsIHN0cmVldCBsaWdodCBpbmNpZGVudHMsIHNhbml0YXRpb24gY29tcGxhaW50cywgZXRjLiwgZnJvbSAyMDE5IHRvIGZpdCBhIG1vZGVsIHRoYXQgc3VtbWFyaXplIHJvYmJlcmllcyBpbiAyMDIxLCB3aGljaCBpcyB0aGVuIHN1YnNlcXVlbnRseSB1c2VkIHRvIHByZWRpY3Qgcm9iYmVyaWVzIGluIDIwMjIuIElmIHRoZXJl4oCZcyBhIGNvcnJlbGF0aW9uIGJldHdlZW4gbG9jYXRpb25zIG9mIHRoZXNlIHJpc2sgZmFjdG9ycyBhbmQgcm9iYmVyeSBpbmNpZGVudHMsIHRoZW4gdGhlIHJlbW92YWxzIG9mIGdyYWZmaXRpIGFuZCByZW5vdmF0aW9ucyBvZiBhYmFuZG9uZWQgYnVpbGRpbmdzIGluIDIwMjEsIGZvciBleGFtcGxlLCB3b3VsZCB2ZXJ5IG11Y2ggbGlrZWx5IHRvIHNoaWZ0IHRoZSBnZW9ncmFwaGllcyBvZiByb2JiZXJ5IGluY2lkZW50cy4gRm9yIGEgYmV0dGVyIHByZWRpY3Rpb24sIHdlIGhpZ2hseSBlbmNvdXJhZ2UgdGhlIHJlbGVhc2Ugb2YgcmlzayBmYWN0b3IgZGF0YXNldHMgdGhhdCBhcmUgdXAgdG8gZGF0ZS4gCgpJbiBhZGRpdGlvbiwgd2UgYmVsaWV2ZSB0aGF0IHRoZSBnbG9iYWwgcGFuZGVtaWMgc2luY2UgMjAyMCB3b3VsZCBoYXZlIGhhZCBhIHNpZ25pZmljYW50IGltcGFjdCBvbiBsb2NhdGlvbnMgb2Ygcm9iYmVyaWVzLiBFY29ub21pYyBkaXN0cmVzcywgZHJpdmVuIGJ5IGhpZ2ggdW5lbXBsb3ltZW50IHJhdGVzLCBjb3VsZCBjcmVhdGUgZmluYW5jaWFsIHN0cmFpbiwgbGVhZGluZyB0byBhbiBpbmNyZWFzZSBpbiByb2JiZXJpZXMgaW4gYXJlYXMgYWxyZWFkeSBmYWNpbmcgZWNvbm9taWMgY2hhbGxlbmdlcy4gTG9ja2Rvd25zIGFuZCBjaGFuZ2VkIHJvdXRpbmVzIGR1ZSB0byBzb2NpYWwgZGlzdGFuY2luZyBtZWFzdXJlcyBjb3VsZCBhbHNvIGNyZWF0ZSBvcHBvcnR1bml0aWVzIGZvciBjcmltaW5hbHMuIEluIGFkZGl0aW9uLCB0aGUgcmVhbGxvY2F0aW9uIG9mIHBvbGljZSByZXNvdXJjZXMgdG8gZW5mb3JjZSBwdWJsaWMgaGVhbHRoIG1lYXN1cmVzIHdvdWxkIHJlc3VsdCBpbiBkZWNyZWFzZWQgcG9saWNlIHByZXNlbmNlIGluIGFyZWFzIHRoYXQgd2VyZSByZWd1bGFybHkgcGF0cm9sbGVkLiBBbGwgb2YgdGhlc2Ugd291bGQgY29udHJpYnV0ZSB0byBhIGdyb3dpbmcgbnVtYmVyIG9mIHJvYmJlcnkgaW5jaWRlbnRzIGluIGFyZWEgdW5leHBlY3RlZCBiZWZvcmUuIElmIHdlIHN0aWNrZWQgd2l0aCBhIG1vcmUgZ2VuZXJhbCBtb2RlbCB0byBwcmVkaWN0IHJvYmJlcnkgaW5jaWRlbnRzIGR1cmluZyB0aGlzIHZlcnkgdW51c3VhbCB0aW1lLCB0aGVuIGl0IGlzIHZlcnkgbGlrZWx5IHRoYXQgb3VyIG1vZGVsIHByb2R1Y2VzIHNrZXdlZCByZXN1bHRzLiBUaGlzIHJlbWluZHMgdXMgYWdhaW4gdGhlIGltcG9ydGFuY2Ugb2YgZW5zdXJpbmcgdGhlIGdlbmVyYWxpemFiaWxpdHkgb2YgYW55IG1hY2hpbmUgbGVhcm5pbmcgbW9kZWwgbm90IG9ubHkgYWNyb3NzIHNwYWNlIGJ1dCBhbHNvIHRpbWUuIEluIHRoaXMgY2FzZSwgdGhlIG1vZGVsIHNob3VsZCBjb25zaWRlciByaXNrIGZhY3RvcnMgcmVsYXRlZCB0byB0aGUgcGFuZGVtaWMuIAoKTmV2ZXJ0aGVsZXNzLCBvdXIgbW9kZWwgZGlkIGEgYmV0dGVyIGpvYiBpbiBjYXB0dXJpbmcgcm9iYmVyeSBpbmNpZGVudHMgaW4gbG93ZXItcmlzayBhcmVhcyB0aGF0IHRoZSBrZXJuZWwgZGVuc2l0eSBtb2RlbCBoYWQgaWdub3JlZC4gV2hpbGUgYmVpbmcgbG93IHJpc2ssIGl0IGlzIGFsc28gaW1wb3J0YW50IG5vdCB0byBvdmVybG9vayBwb3RlbnRpYWwgcm9iYmVyeSBpbmNpZGVudHMgaW4gdGhlc2UgYXJlYXMuIElnbm9yaW5nIHRoZSDigJxyaXNr4oCdIG9mIGxvdy1yaXNrIGFyZWEgYW5kIG92ZXJseSBlbXBoYXNpemluZyBoaWdoIHJpc2sgYXJlYXMgd291bGQgbGVhZCB0byBvdmVyLXBvbGljaW5nIGluIGhpZ2gtcmlzayBhcmVhcywgd2hpY2ggd291bGQgY29udGludWUgdG8gcmVtYWluIGFzIGhpZ2ggcmlzayBldmVyIGFmdGVyLiBUaGlzIGxlYWRzIHVzIHRvIHRoaW5rIGFib3V0IHdoZXRoZXIgdXNpbmcgYSBzaW5nbGUgbW9kZWwgaXMgbm90IGVub3VnaCB0byBjYXB0dXJlIHRoZSBjb21wbGV4aXRpZXMgb2Ygcm9iYmVyeSBpbmNpZGVudHMgaW4gb25lIHBsYWNlLiBBIGNvbGxlY3RpdmUgYXBwcm9hY2ggdGhhdCBzdHJhdGVnaWNhbGx5IG1ha2UgdXNlIG9mIHRoZSByZXN1bHQgb2YgYm90aCB0aGUga2VybmVsIGRlbnNpdHkgZXN0aW1hdGlvbiBhbmQgdGhlIHJpc2sgcHJlZGljdGlvbiBtb2RlbHMgY2FuIHByb2JhYmx5IGxlYWQgdG8gbW9yZSBhY2N1cmF0ZSBwcmVkaWN0aW9ucy4gCgpPbiB0b3Agb2YgdGhhdCwgd2UgaGF2ZSBhdm9pZGVkIHRoZSBvdmVycHJlZGljdGlvbiBvZiByb2JiZXJ5IGluY2lkZW50cyBpbiBtYWpvcml0eSBub24td2hpdGUgbmVpZ2hib3Job29kLiBZZXQsIGRlc3BpdGUgZmluZGluZyB0aGF0IHRoZSBtb2RlbCBnZW5lcmFsaXplcyB3ZWxsIGFjcm9zcyBkaWZmZXJlbnQgbmVpZ2hib3Job29kIGNvbnRleHRzLCB3ZSBjYW5ub3QgYmUgc3VyZSB0aGUgbW9kZWwgZG9lc27igJl0IHN1ZmZlciBmcm9tIHNlbGVjdGlvbiBiaWFzLiBJZiBsYXcgZW5mb3JjZW1lbnQgc3lzdGVtYXRpY2FsbHkgb3Zlci1wb2xpY2VzIGNlcnRhaW4gY29tbXVuaXRpZXMsIGFuZCB0aGlzIHNlbGVjdGlvbiBjcml0ZXJpYSBnb2VzIHVuYWNjb3VudGVkIGZvciBpbiB0aGUgbW9kZWwsIHRoZW4gdGhlIG1vZGVsIG1heSBiZSBzdGlsbCBiaWFzZWQgcmVnYXJkbGVzcyBvZiB0aGUgYWJvdmUgdGVzdHMuIFdlIGFsc28gZm91bmQgdGhhdCBhZGRpbmcgc3BhdGlhbCBwcm9jZXNzIGZlYXR1cmVzIGltcHJvdmUgdGhlIG1vZGVsIGFuZCB1c2luZyB0aGUgcmFuZG9tIGstZm9sZCBjcm9zcyB2YWxpZGF0aW9uIG1ldGhvZCB3b3VsZCBlbmhhbmNlIHRoZSBtb2RlbC4g