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