Version 3.0 | First Created Nov 17, 2023 | Updated Dec 8, 2023
Keywords: K Nearest Neighbors, Moran’s I, Getis-Ord Gi*, Kernel
Density Estimation, Random K-fold Cross Validation, Logistic Regression,
Poisson Regression, ROC Curve, Principal Component Analysis,
Multi-Criteria Analysis, Health Geography
Important Links: study
repository, pitch
presentation
Introduction
The opioid overdose crisis continues as one of the most significant
public health challenges of the past two decades in the US. Between 1999
and 2019, the national age-adjusted opioid-involved overdose death rate
increased from 2.9 per 100,000 population to 15.5 per 100,000. Rates
have increased steeply during the COVID-19 pandemic. In 2020, the rate
climbed to 21.4 per 100,000 and to 24.7 per 100,000 in 2021. Most
literature to this date have focused on understanding the drivers of
overdose crisis, which include drug supply, drug manufacturing, pain
arising from work-related injuries, income inequality, and added stress,
isolation, and economic disadvantage connected to the COVID-19 pandemic.
Until recently, however, more and more studies have begun to consider
the association between drug use, opioid-related mortality, and the
built-environment. Contextual risk factors, especially features of the
built environment, the presence of violent crime or crime associated
with substance use, and neighborhood-level socioeconomic factors, have
enabled more accurate identification of high-risk overdose areas, thus
driving initiatives on alleviating overdose risk and crises.
We conducted a case study on opioid-overdose risk modeling in
Cincinnati, Ohio where the opioid epidemic has been a serious issue.
Since 2009, drug overdose deaths in Ohio have continued to increase, and
Cincinnati, a large city in southwestern Ohio with a population of about
300,000 people, was one of 12 opioid hotspots in Ohio and had the
highest per capita rates of a fatal overdose between 2010 and 2017.
Today, heroin overdose incidences still remain high in the city since
heroin and prescription drugs abuse have further led to other public
health problems such as HIV and needle-borne diseases.
We combined multiple data sources, including emergency medical
service response, crime and non-emergency reports data, American
Community Survey data, and datasets that are proxies for local built
environment to analyze the distributions of heroin-related overdose
incidents in Cincinnati, Ohio.
Among studies of spatial epidemiology, geospatial clustering has been
used to analyze the geographic variation of phenomena, such as the
disproportionate impact of public health crisis on people with
disabilities, and the concentrated vulnerability of socio-economically
disadvantaged population under natural hazards. It allows the
investigators to identify groups of spatial objects (i.e. clusters) that
have similar characteristics and analyze patterns of the clusters (e.g.,
hot spots, cold spots). As such, we used Getis-Ord hotspot analyses to
identify locations of persistent risk in Cincinnati from 2016-2020. We
constructed measures of the socio-built environment and neighborhood
demographic characteristics using principal components analysis (PCA).
The resulting measures were used as covariates in a logistic regression
with random k-fold cross validation to predict if an area was part of an
opioid hotspot cluster. All built environment risk factors were
aggregated, scaled, and weighted to generate an interactive overdose
risk terrain map.
On the technical side, the goal of our study is to
build a model that could predict opioid overdose clusters as well as
socioeconomic and built environment factors associated with opioid
overdose rates in Cincinnati from 2016 to 2020 as much as possible.
On the practical side, we would like our risk terrain
model to benefit both the stakeholders and community members in the main
form of a municipal dashboard and a mobile application. For
stakeholders, the dashboard would serve as an early warning system by
predicting/showing areas at high risk of opioid overdose. Specifically,
it should include maps with overdose cluster locations, trend graphs
over time, and charts illustrating the correlation between overdose
rates and various factors. For community members, this application
connects them to healthcare and rehabilitation resources within the
city. Specifically, the application allow them to call for emergency
services and medical assistance for themselves or for others at one tap.
To encourage public-private partnership, this app would adopt the
approach that OpenStreetMap took to allow both technical and
non-technical users, private and public entities to update existing
information, such as crime incidents, rehabilitation center capacities
and location. We hope that non-technical stakeholders can use the app to
allocate resources efficiently, directing outreach efforts, treatment
facilities, and educational campaigns to areas with the highest
predicted risk. We hope that the public would use this platform to
foster community engagement and support for initiatives addressing the
opioid crisis.
The rest of the report is organized as follows. Section one describes
our approach to access and pre-process the data. Section two documents
an exploratory analysis we did using poission regression with only crime
and 311 incidents. Section three explains the conceptualization our
approach applying the Getis Ord G* statistics to determine spatial
hotspots. Section four is the main part of this study, where we iclude
all built environment predictors to run a logistic regression predicting
if an area is an opioid overdose hotspot or not. Section five applies
principal component analysis to reduce colinerity and include only half
of the principal components into the model. Section six validates the
accuracy of this model against a kernel density estimation while the
last section constructs the risk terrain map.
The conceptualization and methodologies of this study were drawn in
part from:
Choi, et al.(2022) Spatial clustering of heroin-related overdose
incidents: a case study in Cincinnati, Ohio
Srinivasan, et al. (2023) Risk factors for persistent fatal
opioid-involved overdose hotspots in Massachusetts 2011-2021
Chichester, et al.(2023) Crime and Features of the Built
Environment Predicting Risk of Fatal Overdose: A Comparison of Rural and
Urban Ohio Counties with Risk Terrain Modeling.
Data
We used the EMS
dataset that includes all responses to heroin-related overdose
incidents from the Cincinnati Fire Department between 2016 and 2020.
Each incident record contains incident location information, including
geospatial information, location address, a classification of a
neighborhood in the city, time of the incident, and disposition of
incident response. Data records regarding incidents outside of the study
area, without geospatial information, and with unassociated disposition
codes were excluded from this study. Records from canceled calls or
false alarms and duplicated records were also excluded.
heroin <- read.csv(here("data", "public", "Cincinnati_Fire_Incidents__CAD___including_EMS__ALS_BLS_.csv"))
cincinnati <- st_read(here("data", "public", "Cincinnati_City_Boundary.geojson")) %>% st_transform("EPSG:3735")
heroin <- heroin %>%
filter(CFD_INCIDENT_TYPE_GROUP != "NON-PROTOCOL PROBLEM TYPES") %>%
filter(is.na(LATITUDE_X) == FALSE & is.na(LONGITUDE_X) == FALSE) %>% # remove incidents without spatial info
filter(is.na(DISPOSITION_TEXT) == FALSE # remove record with un-associated disposition codes
& CFD_INCIDENT_TYPE_GROUP !="CN: CANCEL" # remove records from canceled calls, false alarm, and duplication
& CFD_INCIDENT_TYPE_GROUP !="CANCEL INCIDENT"
& CFD_INCIDENT_TYPE_GROUP !="CN: CANCEL,DEF: DEFAULT"
& CFD_INCIDENT_TYPE_GROUP !="CN: CANCEL,EMSF: FALSE"
& CFD_INCIDENT_TYPE_GROUP !="CN: CANCEL,DUPF: DUPLICATE"
& CFD_INCIDENT_TYPE_GROUP !="CN: CANCEL,FALA: FIRE FALSE AC"
& CFD_INCIDENT_TYPE_GROUP !="CN: CANCEL,MEDD: MT DISREGARDE"
& CFD_INCIDENT_TYPE_GROUP !="DUPF: DUPLICATE"
& CFD_INCIDENT_TYPE_GROUP !="DUPLICATE INCIDENT"
& CFD_INCIDENT_TYPE_GROUP !="MAL: SYSTEM MALFUNCTION") %>%
mutate(time = mdy_hms(CREATE_TIME_INCIDENT, tz = "UTC"),
year_column = year(time)) %>%
st_as_sf(., coords = c("LONGITUDE_X", "LATITUDE_X"), crs = 4326) %>%
st_transform("EPSG:3735") %>%
st_intersection(cincinnati %>% dplyr::select(OBJECTID),.) %>% # remove incidents outside of study area
filter(year_column %in% c(2016, 2017, 2018, 2019, 2020))
In addition, we used Homeland
Infrastructure Foundation-Level Data (HIFLD) and Substance Abuse
and Mental Health Services Administration (SAMHSA) data for
2015–2020 to obtain information about available healthcare services,
such as hospitals, opioid treatment programs, and buprenorphine
prescribing physicians, etc.
We filtered data only for Cincinnati in the HIFLD dataset. The SAMHSA
data were geocoded manually to get its location information first before
reading into R.
hospitals <- st_read(here("data","public", "Hospitals.geojson"))
hospitals <- hospitals %>%
filter(STATE == "OH" & CITY == "CINCINNATI") %>%
st_transform("EPSG:3735") # select those only in Cincinnati
rehab <- read.csv(here("data", "public", "rehabilitation.csv"))
rehab <- rehab %>%
filter(is.na(Latitude) == FALSE & is.na(Longitude) == FALSE) %>%
st_as_sf(., coords = c("Longitude", "Latitude"), crs = 4326) %>%
st_transform("EPSG:3735")
Furthermore, we included the crime
rate data from the Cincinnati Police Department between 2016 and
2020. Specific crimes were selected for inclusion in analyses based on
having been identified in the literature as a spatial risk factor for
drug overdose or substance use behavior or for having a theoretical
relationship with drug overdose.
crime <- read.csv(here("data", "private", "PDI__Police_Data_Initiative__Crime_Incidents.csv"))
crime_list <- c("AGGRAVATED ASSAULT", "BURGLARY", "BREAKING AND ENTERING", "ROBBERY", "DOMESTIC VIOLENCE", "MURDER ", "RAPE", "THEFT")
crime <- crime %>%
filter(OFFENSE %in% crime_list) %>%
filter(is.na(LATITUDE_X) == FALSE & is.na(LONGITUDE_X) == FALSE) %>%
st_as_sf(., coords = c("LONGITUDE_X", "LATITUDE_X"), crs = 4326) %>%
st_transform("EPSG:3735") %>%
mutate(time = mdy_hms(DATE_REPORTED, tz = "UTC"),
year = year(time)) %>%
filter(year %in% c(2016, 2017, 2018, 2019, 2020))
From the same data source, we included the 311
non-emergency service requests from 2016 to 2020.
complaints <- read_csv(here("data", "private", "Cincinnati_311__Non-Emergency__Service_Requests.csv"))
complaints <- complaints %>%
filter(is.na(LATITUDE) == FALSE & is.na(LONGITUDE) == FALSE) %>%
st_as_sf(., coords = c("LONGITUDE", "LATITUDE"), crs = 4326) %>%
st_transform("EPSG:3735") %>%
mutate(time = mdy_hms(REQUESTED_DATE, tz = "UTC"),
year = year(time)) %>%
filter(year %in% c(2016, 2017, 2018, 2019, 2020))
The connection between substance use and built environment variables
(access to public restrooms, access to pharmacies, and driving distance
to services, defined in our study as fast-food restaurants, gas stations
and public parks) are also important. Public restrooms are associated
with people who inject drugs (PWID) because many people use drugs in
these spaces.Pharmacies represent an important access variable for
several reasons. During the initial wave of the overdose crisis,
pharmaceutical prescriptions, either legitimate, diverted, or
potentially inappropriate, fed the opioid supply. In addition, naloxone
(a medication to reverse an opioid overdose) is available at pharmacies
without a prescription, although this provision may vary by neighborhood
socio-demographic levels. All of these data were queried from
OpenStreetMap and preprocessed in QGIS (fixing geometry, creating
spatial indices, and unioning point with polygon data pieces)
# gas station
fuel <- st_read(here("data", "public", "fuel.geojson")) %>% st_transform("EPSG:3735")
# fast food restaurant
fastfood <- st_read(here("data", "public", "fastfood.geojson")) %>% st_centroid() %>% st_transform("EPSG:3735")
# public parks
parks <- st_read(here("data", "public", "parks.geojson")) %>% st_transform("EPSG:3735") %>% st_centroid()
# pharmacy
pharmacy <- st_read(here("data","public", "pharmacy.geojson")) %>% st_transform("EPSG:3735") %>% st_centroid()
To identify demographic and sociographic characteristics
corresponding to the EMS dataset, we utilized the ACS data for 2016 –
2020. We downloaded demographic information for each census tract,
including total population, gender, adult population size, and
race/ethnicity. The ACS data also included socioeconomic characteristics
of each census tract, such as education, income, and poverty. For each
census tract, we computed the population aged between 25 and 54 (since
this is the age group most vulnerable to opioid overdose), gender ratio,
minority popultion ratio, percentage poverty, and percentage of
population with a bachelor’s degree. Note that the boundary of
Cincinnati does not fully contain several census tracts and therefore we
have tried our best to include tracts within Hamilton county that mostly
fall within Cincinnati.
cincinnati20 <- get_acs(geography = "tract",
variables = c(
"B01001_001E", # total population
"B01001_002E", # total male
"B01001_011E", # male 25-29
"B01001_012E",
"B01001_013E",
"B01001_014E",
"B01001_015E",
"B01001_016E", # male 50-54
"B01001_026E", # total female
"B01001_035E", # female 25-29
"B01001_036E",
"B01001_037E",
"B01001_038E",
"B01001_039E",
"B01001_040E", # female 50-54
"B02001_002E", # white population
"B02001_003E", # black population
"B02001_005E", # asian population
"B03002_012E", # latinx population
"B19013_001E", # median household income
"B06012_002E", # poverty
"B06009_005E" # bachelor
),
year=2020, state="OH", county="Hamilton",
geometry=TRUE, output="wide") %>%
st_transform("EPSG:3735")
cincinnati_tracts <- st_read(here("data", "public", "cincinnati_tracts.geojson")) %>% st_transform("EPSG:3735")
cincinnati_tracts <- cincinnati_tracts %>% st_drop_geometry() %>% dplyr::select(GEOID) %>% as.list(GEOID)
cincinnati20 <- cincinnati20 %>%
filter(GEOID %in% cincinnati_tracts$GEOID) %>%
rename(Totalpop = B01001_001E,
MedHHInc = B19013_001E) %>%
mutate(pop25_54 = B01001_011E + B01001_012E + B01001_013E + B01001_014E + B01001_015E + B01001_016E + B01001_035E + B01001_036E + B01001_037E + B01001_038E+ B01001_039E + B01001_040E) %>%
mutate(MF_ratio = B01001_002E / B01001_026E) %>%
mutate(race_ratio = B02001_002E / (B02001_003E + B02001_005E + B03002_012E)) %>%
mutate(pctPoverty = B06012_002E / Totalpop) %>%
mutate(pctBachelor = B06009_005E / Totalpop) %>%
dplyr::select(pop25_54, Totalpop, MedHHInc, GEOID, MF_ratio, race_ratio, pctPoverty, pctBachelor)
The table below summarizes all of our predictor variables in this
study:
Medical Service |
|
Hospital |
Homeland Infrastructure Foundation-Level Data |
Rehabilitation Center |
Substance Abuse and Mental Health Services
Administration |
Built Environment |
|
Crime Rate |
Open Data Cincinnati |
311 complaints |
Open Data Cincinnati |
Gas station |
OSM |
Public parks |
OSM |
Fast food restaurants |
OSM |
Pharmacies |
OSM |
Socio-demographic
Characteristics |
|
Population aged between 25 and 54 |
ACS |
Gender ratio |
ACS |
White over minority ratio |
ACS |
Population with a Bachelor’s degree |
ACS |
Median household income |
ACS |
Population under poverty line |
ACS |
Median Household Income |
ACS |
Poission Regression
As part of our exploratory analysis, we ran a poisson regression
using only the number of crime incidents and the number of 311
complaints as our predictor variables. Since heroin overdose incident is
not a phenomenon that varies across administrative units, but one
varying smoothly across the landscape, we would need to aggregate these
point-level data into a lattice grid of cells. The code below generates
a 500 by 500 meters (820 feet) fishnet for Cincinnati to achieve this
goal.
fishnet <- st_make_grid(cincinnati,
cellsize = 820,
square = TRUE) %>%
.[cincinnati] %>%
st_sf() %>%
mutate(uniqueID = 1:n())
ggplot() +
geom_sf(data=fishnet, color="black", fill="#FFF5EE") +
labs(title = "Fishnet of Cincinnati") +
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)
)
We would like to compare across both space and time, and so we divide
the heroin dataset into one training set and two testing sets. The
training set contains incidents from 2016 to 2018 while the testing sets
contain incidents from 2019 and 2020 respectively. Since each individual
year has well enough observations of heroin-overdose incidents,
separating the testing set into 2019 and 2020 allows us to understand
how COVID-19 plays a role in affecting drug overdose rate. We also
separate the crime incident data and the 311 complaints data into
respective year groups so that we could aggregate them into the
fishnet.
heroinTrain <- heroin %>%
filter(year_column %in% c(2016, 2017, 2018))
heroin19 <- heroin %>%
filter(year_column == 2019)
heroin20 <- heroin %>%
filter(year_column == 2020)
crimeTrain <- crime %>%
filter(year %in% c(2016, 2017, 2018))
crime19 <- crime %>%
filter(year == 2019)
crime20 <- crime %>%
filter(year == 2020)
complaintTrain <- complaints %>%
filter(year %in% c(2016, 2017, 2018))
complaint19 <- complaints %>%
filter(year == 2019)
complaint20 <- complaints%>%
filter(year == 2020)
The following two code chunks perform all the necessary tasks to
prepare our training and testing datasets by aggregating
heroin-incidents, crime incidents, and 311 complaints all into the same
spatial unit, in this case, the fishnet.
# add heroin in net
heroinTrain_net <-
dplyr::select(heroinTrain) %>%
mutate(countHeroin = 1) %>%
aggregate(., fishnet, sum) %>%
mutate(countHeroin = replace_na(countHeroin, 0),
uniqueID = 1:n(),
cvID = sample(round(nrow(fishnet) / 24),
size=nrow(fishnet), replace = TRUE))
# adding crime to net
heroinTrain_net <- heroinTrain_net %>%
st_join(crimeTrain, ., join=st_within) %>%
st_drop_geometry() %>%
group_by(uniqueID) %>%
summarize(Crimecount = n()) %>%
left_join(heroinTrain_net, . ) %>%
st_sf() %>%
mutate(Crimecount = ifelse(is.na(Crimecount), 0, Crimecount))
# add 311 to net
heroinTrain_net <- heroinTrain_net %>%
st_join(complaintTrain, ., join=st_within) %>%
st_drop_geometry() %>%
group_by(uniqueID) %>%
summarize(Complaintscount = n()) %>%
left_join(heroinTrain_net, . ) %>%
st_sf() %>%
mutate(Complaintscount = ifelse(is.na(Complaintscount), 0, Complaintscount))
# 2019 test sets
heroin19_net <-
dplyr::select(heroin19) %>%
mutate(countHeroin = 1) %>%
aggregate(., fishnet, sum) %>%
mutate(countHeroin = replace_na(countHeroin, 0),
uniqueID = 1:n(),
cvID = sample(round(nrow(fishnet) / 24),
size=nrow(fishnet), replace = TRUE))
heroin19_net <- heroin19_net %>%
st_join(crime19, ., join=st_within) %>%
st_drop_geometry() %>%
group_by(uniqueID) %>%
summarize(Crimecount = n()) %>%
left_join(heroin19_net, . ) %>%
st_sf() %>%
mutate(Crimecount = ifelse(is.na(Crimecount), 0, Crimecount))
heroin19_net <- heroin19_net %>%
st_join(complaint19, ., join=st_within) %>%
st_drop_geometry() %>%
group_by(uniqueID) %>%
summarize(Complaintscount = n()) %>%
left_join(heroin19_net, . ) %>%
st_sf() %>%
mutate(Complaintscount = ifelse(is.na(Complaintscount), 0, Complaintscount))
# 2020 test set
heroin20_net <-
dplyr::select(heroin20) %>%
mutate(countHeroin = 1) %>%
aggregate(., fishnet, sum) %>%
mutate(countHeroin = replace_na(countHeroin, 0),
uniqueID = 1:n(),
cvID = sample(round(nrow(fishnet) / 24),
size=nrow(fishnet), replace = TRUE))
heroin20_net <- heroin20_net %>%
st_join(crime20, ., join=st_within) %>%
st_drop_geometry() %>%
group_by(uniqueID) %>%
summarize(Crimecount = n()) %>%
left_join(heroin20_net, . ) %>%
st_sf() %>%
mutate(Crimecount = ifelse(is.na(Crimecount), 0, Crimecount))
heroin20_net <- heroin20_net %>%
st_join(complaint20, ., join=st_within) %>%
st_drop_geometry() %>%
group_by(uniqueID) %>%
summarize(Complaintscount = n()) %>%
left_join(heroin20_net, . ) %>%
st_sf() %>%
mutate(Complaintscount = ifelse(is.na(Complaintscount), 0, Complaintscount))
We performed some data visualization work to see if there are any
differences between heroin-overdose incidents over year. We found that
the distribution of these incidents are pretty consistent between
2016-2018 and 2019, with the majority of incidents significantly
clustered in south Cincinnati. However, in 2020, despite that this
overdose cluster still remain, overdose incidents became more disperse.
We may see some small, emerging clusters of high overdose incidents
scattered around the city.
plot1 <- ggplot() +
geom_sf(data = heroinTrain_net, aes(fill = countHeroin), color = NA) +
scale_fill_viridis(option = "rocket", name = "Counts") +
labs(title = "Heroin OD 2016-2018") +
theme(legend.position="bottom",
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)
)
plot2 <- ggplot() +
geom_sf(data = heroin19_net, aes(fill = countHeroin), color = NA) +
scale_fill_viridis(option = "rocket", name = "Counts") +
labs(title = "Heroin OD 2019") +
theme(legend.position="bottom",
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)
)
plot3 <- ggplot() +
geom_sf(data = heroin20_net, aes(fill = countHeroin), color = NA) +
scale_fill_viridis(option = "rocket", name = "Counts") +
labs(title = "Heroin OD 2020") +
theme(legend.position="bottom",
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)
)
plot1 + plot2 + plot3
We created heat maps of heroin-overdose incidents from 2016 to 2018
and overlayed crime incidents and 311 complaints above. Based on the
visualization, we assume crime and 311 complaints to be strong
predictors of overdose risk as places of high overdose incidents align
perfectly with places with higher crime rate and more complaints. Yet,
it is also pretty evident that crime incidents are correlated with 311
complaints.
plot1_1 <- ggplot() +
geom_sf(data = cincinnati, fill = "black") +
stat_density2d(data = data.frame(st_coordinates(heroinTrain)),
aes(X, Y, fill = after_stat(level), alpha = after_stat(level)),
size = 0.01, bins = 60, geom = 'polygon') +
scale_fill_viridis(option = "rocket", name = "Density") +
scale_alpha(range = c(0.00, 0.35), guide = "none") +
geom_sf(data = sample_n(crimeTrain, 3000), size = 0.03, colour = "white", alpha = 0.5) +
labs(title = "Heroin OD and Crime 2016-2018") +
theme(legend.position="bottom",
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)
)
plot1_2 <- ggplot() +
geom_sf(data = cincinnati, fill = "black") +
stat_density2d(data = data.frame(st_coordinates(heroinTrain)),
aes(X, Y, fill = after_stat(level), alpha = after_stat(level)),
size = 0.01, bins = 60, geom = 'polygon') +
scale_fill_viridis(option = "rocket", name = "Density") +
scale_alpha(range = c(0.00, 0.35), guide = "none") +
geom_sf(data = sample_n(complaintTrain, 3000), size = 0.03, colour = "white", alpha = 0.5) +
labs(title = "Heroin OD and Complaint 2016-2018") +
theme(legend.position="bottom",
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)
)
plot1_1 + plot1_2
Mapping the correlation between our predictor variables reveals that
indeed, there’s a strong, linear, and positive relationship between
heroin-overdose incidents and crime as well as 311 complaints, though
the relationships become weaker in 2020. We suspect that disruptions in
the drug supply chain, changes in law enforcement priorities, as well as
increase social isolation and mental health challenges during the
pandemic might have contribute to the decrease in correlation between
overall crime incidents and heroin overdose incidents in 2020.
plot4 <- heroinTrain_net %>%
st_drop_geometry() %>%
dplyr::select(-c(uniqueID, cvID)) %>%
pivot_longer(cols = -countHeroin, # everything except measurement
names_to = "Type", # categorizes all quantitative variables into Type
values_to = "Number") %>%
ggplot(aes(x= countHeroin, y = Number)) +
geom_point(size = 0.01, color = "#000004") +
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(
`Crimecount` = "Crime",
`Complaintscount` = "311 Complaints"))) +
labs(title = "Scatter Plots of Predictor Variables 2016-18") +
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, linewidth =0.8))
plot5 <- heroin19_net %>%
st_drop_geometry() %>%
dplyr::select(-c(uniqueID, cvID)) %>%
pivot_longer(cols = -countHeroin, # everything except measurement
names_to = "Type", # categorizes all quantitative variables into Type
values_to = "Number") %>%
ggplot(aes(x= countHeroin, y = Number)) +
geom_point(size = 0.01, color = "#000004") +
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(
`Crimecount` = "Crime",
`Complaintscount` = "311 Complaints"))) +
labs(title = "Scatter Plots of Predictor Variables 2019") +
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, linewidth =0.8))
plot6 <- heroin20_net %>%
st_drop_geometry() %>%
dplyr::select(-c(uniqueID, cvID)) %>%
pivot_longer(cols = -countHeroin, # everything except measurement
names_to = "Type", # categorizes all quantitative variables into Type
values_to = "Number") %>%
ggplot(aes(x= countHeroin, y = Number)) +
geom_point(size = 0.01, color = "#000004") +
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(
`Crimecount` = "Crime",
`Complaintscount` = "311 Complaints"))) +
labs(title = "Scatter Plots of Predictor Variables 2020") +
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, linewidth =0.8))
plot4 + plot5 + plot6
We ran the Poisson regression and confirmed that crime incidents and
311 complaints are good at predicting heroin-overdose risk. The model is
slightly better when used to predict 2020 data, but the difference is
minimal.
poissionTrain <- glm(countHeroin ~ Crimecount + Complaintscount, family = "poisson",
data = heroinTrain_net)
Prediction19 <-
mutate(heroin19_net, Prediction = predict(poissionTrain, heroin19_net, type = "response")) %>%
mutate(Error = countHeroin - Prediction) %>%
mutate(MAE = mean(abs(Error))) %>%
mutate(SD_MAE = sd(Error)) %>%
mutate(prediction = "2019") %>%
slice(1:1)
Prediction20 <-
mutate(heroin20_net, Prediction = predict(poissionTrain, heroin20_net, type = "response")) %>%
mutate(Error = countHeroin - Prediction) %>%
mutate(MAE = mean(abs(Error))) %>%
mutate(SD_MAE = sd(Error)) %>%
mutate(prediction = "2020") %>%
slice(1:1)
rbind(Prediction19, Prediction20) %>%
st_drop_geometry() %>%
group_by(prediction) %>%
summarize(Mean_MAE = round(MAE, 2),
SD_MAE = round(SD_MAE, 2)) %>%
kable(col.name=c("Prediction", "Mean Absolute Error",'Standard Deviation MAE')) %>%
kable_styling(bootstrap_options = c("striped", "hover", "condensed"))
Prediction
|
Mean Absolute Error
|
Standard Deviation MAE
|
2019
|
0.96
|
1.40
|
2020
|
0.91
|
1.14
|
Here are our reflections up to this point:
While there’s significant associations between crime incidents, 311
complaints, and heroin-overdose incidents, building a model that relies
solely on them to predict risk is simply not enough because substance
abuse and overdose risks are complex phenomena influenced by various
factors beyond just criminal activity. The geographical and
environmental context, including the availability of treatment
facilities, proximity to support networks, and neighborhood
characteristics, also plays a crucial role in understanding overdose
risks. Therefore, we must integrate more features showing built
environment and local contexts into the model.
On top of that, Poisson regression is a traditional method used in
geospatial risk model to predict the number (count) of discrete event,
especially crime. However, in making decisions about how to alleviate
the risk of particular medical or criminal incidence, we shouldn’t just
look at the number of accidents at an intersection in a day at a very
specific place. Rather, we should flag areas in a city that are more
vulnerable/risky than others. It is not wrong to care to focus on the
number of incidence, but from a policy-making perspective, learning
whether or not a place is risky would be more efficient, helpful, and
straightforward in guiding decision making. In other words, stakeholders
and the general public do not have to know how many incidents
happened at this place, but whether this place is risky
and therefore requires more attention.
Getis Ord Spatial Hotspot
We adapted this research workflow from Srinivasan, et al. (2023), who
studied the risk factors for opioid overdose in Massachusetts. In its
essence, Srinivasan, et al used principal component analysis to reduce
colinerarity between predictor variables and ran a logistic regression
to predict whether places in Massachusetts are within overdose hotspot
or not.
To replicate their approach as much as possible, we first examined
the spatial distribution of heroin-related incident rates using the
Jenks natural breaks maps. The Jenks natural breaks maps use a nonlinear
algorithm to create groups where within-group similarity is maximized,
and between-group similarity is minimized. The map shows at least one
significant incidence cluster in south Cincinnati and several other
smaller clusters surrounding that hotspot.
heroin_net <-
dplyr::select(heroin) %>%
mutate(countHeroin = 1) %>%
aggregate(., fishnet, sum) %>%
mutate(countHeroin = replace_na(countHeroin, 0),
uniqueID = 1:n(),
cvID = sample(round(nrow(fishnet) / 24),
size=nrow(fishnet), replace = TRUE))
breaks <- classIntervals(heroin_net$countHeroin, n = 5, style = "jenks")
ggplot() +
geom_sf(data = heroin_net, aes(fill = countHeroin), color = NA) +
scale_fill_viridis(option = "rocket", breaks = breaks$brks, name = "Count") +
labs(title = "Count of Heroin Overdose 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)
) +
theme(legend.key.size = unit(0.8, 'cm'), legend.text = element_text(size=6))
We validated our observation by checking the global moran’s I under
the hypothesis that heroin incidences are randomly distributed across
space. The figure below plot the frequency of 999 randomly permutated I
under Monte Carlo simulation, which shows that our observed I is much
higher than all of the randomly generated I’s. This validates the
existence of spatial autocorrelation.
coords <- st_coordinates(heroin_net %>% st_centroid())
neighborList <- knn2nb(knearneigh(coords, 4))
spatialWeights <- nb2listw(neighborList, style="W")
moranTest <- moran.mc(heroin_net$countHeroin,
spatialWeights, nsim = 999)
ggplot(as.data.frame(moranTest$res[c(1:999)]), aes(moranTest$res[c(1:999)])) +
geom_histogram(binwidth = 0.01, fill = "black") +
geom_vline(aes(xintercept = moranTest$statistic), colour = "#6E0E0A" ,lwd=1) +
labs(title = "Observed and Permuted Moran's I",
subtitle = "Observed Moran's I in Red",
x = "Moran's I",
y = "Count") +
theme_light() +
theme(plot.subtitle = element_text(size = 9,face = "italic"),
plot.title = element_text(size = 12, face = "bold"),
axis.text.x=element_text(size=8),
axis.text.y=element_text(size=8),
axis.title=element_text(size=10))
Since the global spatial clustering analysis yields only one
statistic to describe the overall point pattern across the whole study
area, we have to use the Getis-Ord Gi* hotspot statistic to identify and
map clusters (i.e., grids with high overdose rates surrounded by grids
with high rates). Below, each hotspot identified is statistically
significant at the p<0.1 level. For estimating the clusters, we used
the queen’s criterion to define our spatial weights matrix.
neigh_nbs <- heroin_net %>%
mutate(
nb = st_contiguity(geometry), # neighbors share border
wt = st_weights(nb), # row-standardized weights
neigh_lag = st_lag(countHeroin, nb, wt)
)
gi_hot_spots <- neigh_nbs %>%
mutate(Gi = local_g_perm(countHeroin, nb, wt, nsim = 999)) %>%
unnest(Gi)
gi_hot_spots <- gi_hot_spots %>%
dplyr::select(gi, p_folded_sim, uniqueID) |>
mutate(
classification = case_when(
# Classify based on the following criteria:
gi > 0 & p_folded_sim <= 0.01 ~ "Very hot",
gi > 0 & p_folded_sim <= 0.05 ~ "Hot",
gi > 0 & p_folded_sim <= 0.1 ~ "Somewhat hot",
gi < 0 & p_folded_sim <= 0.01 ~ "Very cold",
gi < 0 & p_folded_sim <= 0.05 ~ "Cold",
gi < 0 & p_folded_sim <= 0.1 ~ "Somewhat cold",
TRUE ~ "Insignificant"
), # Convert 'classification' into a factor for easier plotting
classification = factor(
classification,
levels = c("Very hot", "Hot", "Somewhat hot",
"Insignificant",
"Somewhat cold", "Cold", "Very cold")
)
)
gi_hot_spots %>%
ggplot() +
geom_sf(aes(fill = classification), color = "black", lwd = 0.1) +
scale_fill_brewer(type = "div", palette = 6, name = "Classification") +
labs(title = "Spatial Hotspots and Coldspots of Heroin Overdose Incidence") +
theme(legend.position="bottom",
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)
)
heroin_net <- gi_hot_spots %>%
mutate(hotspot = ifelse(classification %in% c("Very hot", "Hot", "Somewhat hot"), 1, 0)) %>%
st_drop_geometry() %>%
left_join(heroin_net, ., by = "uniqueID")
The cluster map shows seven types of spatial association determined
based on heroin-related overdose incident rate within a grid and its
neighboring grids. If a grid has a high heroin incident rate with
neighboring grids of high heroin incident rates, it was determined as a
hot spot. At the same scheme, if a grid shows a low heroin incident rate
with neighboring grids of low heroin incident rates, it was classified
as a cold spot. Hotspots with p<0.01 are considered as “Very hot”,
with p<0.05 are considered as “Hot”, with p<0.1 are consiered as
“Somewhat hot”. All grids that fall into these three categories received
a 1 for hotspot, otherwise, it gets a zero.
Logistic Regression
Built Environment Features
We aggregated built environment features in addition to crime and 311
complaints into the fishnet of Cincinnati. For crime and complaints, we
counted their number in each grid. For hospitals, rehabilitation
centers, pharmacies, fast food restaurants, gas stations, and public
parks, we calculated the average distance from the center of each grid
to these locations. For demographic variables, we spatially joined tract
level into the grids depending on which census tract each centroid of
the grid fall into.
# adding crime to net
heroin_net <- heroin_net %>%
st_join(crime, ., join=st_within) %>%
st_drop_geometry() %>%
group_by(uniqueID) %>%
summarize(Crimecount = n()) %>%
left_join(heroin_net, . ) %>%
st_sf() %>%
mutate(Crimecount = ifelse(is.na(Crimecount), 0, Crimecount))
# add 311 to net
heroin_net <- heroin_net %>%
st_join(complaints, ., join=st_within) %>%
st_drop_geometry() %>%
group_by(uniqueID) %>%
summarize(Complaintscount = n()) %>%
left_join(heroin_net, . ) %>%
st_sf() %>%
mutate(Complaintscount = ifelse(is.na(Complaintscount), 0, Complaintscount))
net_centroid <- st_centroid(heroin_net)
# add hospitals
heroin_net <- heroin_net %>%
left_join(net_centroid %>%
mutate(hospital.nn = nn_function(st_coordinates(net_centroid),
st_coordinates(hospitals), 1)*0.3048) %>%
st_drop_geometry() %>% dplyr::select(hospital.nn, uniqueID),
by = "uniqueID")
# add rehab center
heroin_net <- heroin_net %>%
left_join(net_centroid %>%
mutate(rehab.nn = nn_function(st_coordinates(net_centroid),
st_coordinates(rehab), 1)*0.3048) %>%
st_drop_geometry() %>% dplyr::select(rehab.nn, uniqueID),
by = "uniqueID")
# add pharmacy
heroin_net <- heroin_net %>%
left_join(net_centroid %>%
mutate(pharm.nn = nn_function(st_coordinates(net_centroid),
st_coordinates(pharmacy), 1)*0.3048) %>%
st_drop_geometry() %>% dplyr::select(pharm.nn, uniqueID),
by = "uniqueID")
# add gas station
heroin_net <- heroin_net %>%
left_join(net_centroid %>%
mutate(fuel.nn = nn_function(st_coordinates(net_centroid),
st_coordinates(fuel), 2)*0.3048) %>%
st_drop_geometry() %>% dplyr::select(fuel.nn, uniqueID),
by = "uniqueID")
# add fast food restaurant
heroin_net <- heroin_net %>%
left_join(net_centroid %>%
mutate(fast.nn = nn_function(st_coordinates(net_centroid),
st_coordinates(fastfood), 2)*0.3048) %>%
st_drop_geometry() %>% dplyr::select(fast.nn, uniqueID),
by = "uniqueID")
# add parks
heroin_net <- heroin_net %>%
left_join(net_centroid %>%
mutate(parks.nn = nn_function(st_coordinates(net_centroid),
st_coordinates(parks), 2)*0.3048) %>%
st_drop_geometry() %>% dplyr::select(parks.nn, uniqueID),
by = "uniqueID")
# add demographic vars
heroin_net <- heroin_net %>%
left_join(net_centroid %>%
dplyr::select(uniqueID) %>%
st_intersection(cincinnati20) %>%
st_drop_geometry() %>% dplyr::select(-GEOID), by = "uniqueID") %>%
filter(is.na(pop25_54) == FALSE)
Some exploratory data analysis were performed, including computing
the correlation between all of our predictor variables and heroin
incidence. The figure below shows that all of our predictor variables
are correlated with heroin-overdose incidence, among which all of our
nearest distance variables are negatively correlated with incidence.
This indicates that the further away we are from fast food restaurants
and gas stations, for example, the less number of heroin-overdose
incidence in that area. The relationship is the strongest for crime
incidence and weakest for population aged between 25-54.
heroin_net_long <- heroin_net%>%
st_drop_geometry() %>%
dplyr::select(-c(uniqueID, cvID, gi, p_folded_sim, classification, hotspot, Totalpop)) %>%
pivot_longer(cols = -countHeroin, # everything except measurement
names_to = "Type", # categorizes all quantitative variables into Type
values_to = "Number")
correlation.cor <-
heroin_net_long %>%
group_by(Type) %>%
summarize(correlation = cor(Number, countHeroin, use = "complete.obs"))
heroin_net_long %>%
ggplot(aes(x= Number, y = countHeroin)) +
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(
`Complaintscount` = "Complaints",
`Crimecount` = "Crime",
`fast.nn` = "Distance to Fast Food",
`fuel.nn` = "Distance to Gas Stations",
`hospital.nn` = "Distance to Hospital",
`MedHHInc` = "Median Household Income",
`MF_ratio` = "Gender Ratio",
`parks.nn` = "Distance to Parks",
`pctBachelor` = "Percent Bachelor's Degree",
`pctPoverty` = "Poverty Rate",
`pharm.nn` = "Distance to Pharmacy",
`pop25_54` = "Population 25_54",
`rehab.nn` = "Distance to Rehab Center",
`race_ratio` = "Racial Majority over Minority"))) +
labs(title = "Scatter Plots of All Predictor Variables") +
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))
The figure below shows the difference in the number of hotspots or
cold spots based on each of our predicting variable and we may see that
there seems to be a significant difference in all scenarios.
anova_dataset <- heroin_net %>% st_drop_geometry() %>%
dplyr::select(hotspot, Crimecount, Complaintscount, pharm.nn, hospital.nn, fast.nn, fuel.nn, rehab.nn, parks.nn, pop25_54, MedHHInc, MF_ratio, race_ratio, pctPoverty, pctBachelor)
anova_dataset %>%
gather(Variable, value, -hotspot) %>%
ggplot(aes(as.character(hotspot), value, fill=as.character(hotspot))) +
geom_bar(position = "dodge", stat = "summary", fun = "mean") +
facet_wrap(~Variable, scales = "free", ncol = 5, labeller= labeller(Variable = c(
`Complaintscount` = "Complaints",
`Crimecount` = "Crime",
`fast.nn` = "Distance to Fast Food",
`fuel.nn` = "Distance to Gas Stations",
`hospital.nn` = "Distance to Hospital",
`MedHHInc` = "Median Household Income",
`MF_ratio` = "Gender Ratio",
`parks.nn` = "Distance to Parks",
`pctBachelor` = "Percent Bachelor's Degree",
`pctPoverty` = "Poverty Rate",
`pharm.nn` = "Distance to Pharmacy",
`pop25_54` = "Population 25_54",
`rehab.nn` = "Distance to Rehab Center",
`race_ratio` = "Racial Majority over Minority"))) +
scale_fill_manual(values = c("#000004", "#BB3754")) +
labs(x="Hotspot", y="Value",
title = "Feature Associations with the Likelihood of Overdose Hotspot") +
theme(legend.position = "none") +
theme(plot.subtitle = element_text(size = 12,face = "italic"),
plot.title = element_text(size = 18, face = "bold"),
axis.text.x=element_text(size=10),
axis.text.y=element_text(size=10),
axis.title=element_text(size=9),
panel.background = element_blank(),
panel.border = element_rect(colour = "grey", fill=NA, linewidth =0.8))
We further conducted a series of ANOVA tests for all predictor
variables to validate if there’s a significant difference in mean number
of crimes, for example, between cold spot and hot spot. The result is in
concur with our expectations: all variables are significant
predictors.
anova_var <- c("Crimecount", "Complaintscount", "pharm.nn", "hospital.nn", "fast.nn", "fuel.nn", "rehab.nn", "parks.nn", "pop25_54", "MedHHInc", "MF_ratio", "race_ratio", "pctPoverty", "pctBachelor")
anova_results <- data.frame(
Df = integer(),
Sum_Sq = numeric(),
Mean_Sq = numeric(),
F_value = numeric(),
P_Value = numeric(), stringsAsFactors = FALSE)
new_names <- c("Crimecount" = "Crime",
"Complaintscount" = "Complaints",
"pharm.nn" = "Distance to Pharmacy",
"hospital.nn" = "Distance to Hospital",
"fast.nn" = "Distance to Fast Food",
"fuel.nn" = "Distance to Gas Stations",
"rehab.nn" = "Distance to Rehab Center",
"parks.nn" = "Distance to Parks",
"pop25_54" = "Population 25_54",
"MedHHInc" = "Median Household Income",
"MF_ratio" = "Gender Ratio",
"race_ratio" = "Racial Majority over Minority",
"pctPoverty" = "Poverty Rate",
"pctBachelor" = "Percent Bachelor's Degree"
)
for (var in anova_var) {
anova_result <- aov(anova_dataset[[var]] ~ anova_dataset$hotspot)
summary_data <- summary(anova_result)[[1]][, c("Df", "Sum Sq", "Mean Sq", "F value", "Pr(>F)")][1:1, ]
anova_results <- rbind(anova_results, summary_data)
}
rownames(anova_results) <- new_names
anova_results %>%
kable() %>%
kable_styling(bootstrap_options = c("striped", "hover", "condensed")) %>%
footnote(general_title = "\n")
|
Df
|
Sum Sq
|
Mean Sq
|
F value
|
Pr(>F)
|
Crime
|
1
|
1.052003e+06
|
1.052003e+06
|
650.50625
|
0.0000000
|
Complaints
|
1
|
1.375003e+07
|
1.375003e+07
|
474.07239
|
0.0000000
|
Distance to Pharmacy
|
1
|
3.429349e+08
|
3.429349e+08
|
149.89597
|
0.0000000
|
Distance to Hospital
|
1
|
1.091828e+08
|
1.091828e+08
|
34.67735
|
0.0000000
|
Distance to Fast Food
|
1
|
2.791552e+08
|
2.791552e+08
|
251.76801
|
0.0000000
|
Distance to Gas Stations
|
1
|
7.760263e+07
|
7.760263e+07
|
95.70131
|
0.0000000
|
Distance to Rehab Center
|
1
|
1.684621e+09
|
1.684621e+09
|
232.82620
|
0.0000000
|
Distance to Parks
|
1
|
7.582010e+07
|
7.582010e+07
|
199.87377
|
0.0000000
|
Population 25_54
|
1
|
2.719819e+07
|
2.719819e+07
|
63.34770
|
0.0000000
|
Median Household Income
|
1
|
5.935462e+10
|
5.935462e+10
|
57.82477
|
0.0000000
|
Gender Ratio
|
1
|
4.562751e+00
|
4.562751e+00
|
115.01797
|
0.0000000
|
Racial Majority over Minority
|
1
|
1.549411e+03
|
1.549411e+03
|
55.81814
|
0.0000000
|
Poverty Rate
|
1
|
3.966738e+00
|
3.966738e+00
|
150.92897
|
0.0000000
|
Percent Bachelor’s Degree
|
1
|
1.328479e-01
|
1.328479e-01
|
14.58168
|
0.0001366
|
Simple Logit
Considering that all predictor variables used in the anova test
appear to be significant, we included all of them in a kitchen-sink
logistic regression. The model shows that in this scenario,
number of crimes, number of
complaints, distance to hospital,
distance to fast food restaurant, gender
ratio, and distance to public parks are highly
significant predictors of heroin-overdose hotspot whereas other
predictors become insignificant.
kitchensink <- glm(hotspot ~ .,
data=heroin_net %>%
st_drop_geometry() %>%
dplyr::select(hotspot, Crimecount, Complaintscount, pharm.nn, hospital.nn, fast.nn, fuel.nn, rehab.nn, parks.nn, pop25_54, MedHHInc, MF_ratio, race_ratio, pctPoverty, pctBachelor), family="binomial" (link="logit"))
kitchen_sum <- summary(kitchensink)
coefficients_table <- as.data.frame(kitchen_sum$coefficients)
coefficients_table$significance <- ifelse(coefficients_table$`Pr(>|z|)` < 0.001, '***',
ifelse(coefficients_table$`Pr(>|z|)` < 0.01, '**',
ifelse(coefficients_table$`Pr(>|z|)` < 0.05, '*',
ifelse(coefficients_table$`Pr(>|z|)` < 0.1, '.', ''))))
coefficients_table$p_value <- paste0(round(coefficients_table$`Pr(>|z|)`, digits = 3), coefficients_table$significance)
coefficients_table %>%
dplyr::select(-significance, -`Pr(>|z|)`) %>%
kable(align = "r") %>%
kable_styling(bootstrap_options = c("striped", "hover", "condensed")) %>%
footnote(general_title = "\n")
|
Estimate
|
Std. Error
|
z value
|
p_value
|
(Intercept)
|
-1.4654205
|
0.6035523
|
-2.4279925
|
0.015*
|
Crimecount
|
0.0075275
|
0.0014618
|
5.1493408
|
0***
|
Complaintscount
|
0.0024300
|
0.0003841
|
6.3269221
|
0***
|
pharm.nn
|
-0.0002075
|
0.0000910
|
-2.2803200
|
0.023*
|
hospital.nn
|
0.0004531
|
0.0000833
|
5.4404140
|
0***
|
fast.nn
|
-0.0020144
|
0.0002101
|
-9.5865098
|
0***
|
fuel.nn
|
-0.0000786
|
0.0001555
|
-0.5053237
|
0.613
|
rehab.nn
|
-0.0000281
|
0.0000614
|
-0.4574004
|
0.647
|
parks.nn
|
-0.0014241
|
0.0002891
|
-4.9260257
|
0***
|
pop25_54
|
-0.0001910
|
0.0001751
|
-1.0908460
|
0.275
|
MedHHInc
|
-0.0000089
|
0.0000082
|
-1.0929448
|
0.274
|
MF_ratio
|
1.2936016
|
0.3804092
|
3.4005528
|
0.001***
|
race_ratio
|
-0.1054348
|
0.0481930
|
-2.1877625
|
0.029*
|
pctPoverty
|
1.1738101
|
0.7640073
|
1.5363858
|
0.124
|
pctBachelor
|
0.0356627
|
1.5429941
|
0.0231126
|
0.982
|
Logit with CV
We would like to perform cross validation on this model to measures
its overall performance and robustness. We decide to modify a cross
validation function that was originally built on cases using Poission
regression by making it suitable for use in our scenario (logistic
regression). This function takes in four parameters: the dataset, a
cross validation ID, which it could use to separate the dataset into
training and testing, a list of dependent variables, and a independent
variable. In our case, we use a randomly generated cvID associated with
each grid cell for random k-fold cross validation. Each group of grids
with the same cvID gets hold out during one iteration.
logitCV <- function(dataset, id, dependentVariable, indVariables) {
allPredictions <- data.frame()
cvID_list <- unique(dataset[[id]])
for (i in cvID_list) {
thisFold <- i
fold.train <- filter(dataset, dataset[[id]] != thisFold) %>% as.data.frame() %>%
dplyr::select(id, geometry, all_of(indVariables),
all_of(dependentVariable))
fold.test <- filter(dataset, dataset[[id]] == thisFold) %>% as.data.frame() %>%
dplyr::select(id, geometry, all_of(indVariables),
all_of(dependentVariable))
form_parts <- paste0(dependentVariable, " ~ ", paste0(indVariables, collapse = "+"))
form <- as.formula(form_parts)
regression <- glm(form, data = fold.train %>%
dplyr::select(-geometry, -id), family="binomial" (link="logit"))
thisPrediction <-
mutate(fold.test, Prediction = predict(regression, fold.test, type = "response"))
allPredictions <-
rbind(allPredictions, thisPrediction)
}
return(st_sf(allPredictions))
}
Here, another helper function is written to visualize the confusion
matrix.
draw_confusion_matrix <- function(cm) {
layout(matrix(c(1,1,2)))
par(mar=c(2,2,2,2))
plot(c(100, 345), c(300, 450), type = "n", xlab="", ylab="", xaxt='n', yaxt='n')
title('CONFUSION MATRIX', cex.main=2)
# create the matrix
rect(150, 430, 240, 370, col='black')
text(195, 435, '0', cex=1.2)
rect(250, 430, 340, 370, col="#BB3754")
text(295, 435, '1', cex=1.2)
text(125, 370, 'Predicted', cex=1.3, srt=90, font=2)
text(245, 450, 'Actual', cex=1.3, font=2)
rect(150, 305, 240, 365, col="#BB3754")
rect(250, 305, 340, 365, col='black')
text(140, 400, '0', cex=1.2, srt=90)
text(140, 335, '1', cex=1.2, srt=90)
# add in the cm results
res <- as.numeric(cm$table)
text(195, 400, res[1], cex=1.6, font=2, col='white')
text(195, 335, res[2], cex=1.6, font=2, col='white')
text(295, 400, res[3], cex=1.6, font=2, col='white')
text(295, 335, res[4], cex=1.6, font=2, col='white')
# add in the specifics
plot(c(100, 0), c(100, 0), type = "n", xlab="", ylab="", main = "DETAILS", xaxt='n', yaxt='n')
text(10, 85, names(cm$byClass[1]), cex=1.2, font=2)
text(10, 70, round(as.numeric(cm$byClass[1]), 3), cex=1.2)
text(30, 85, names(cm$byClass[2]), cex=1.2, font=2)
text(30, 70, round(as.numeric(cm$byClass[2]), 3), cex=1.2)
text(50, 85, names(cm$byClass[5]), cex=1.2, font=2)
text(50, 70, round(as.numeric(cm$byClass[5]), 3), cex=1.2)
text(70, 85, names(cm$byClass[6]), cex=1.2, font=2)
text(70, 70, round(as.numeric(cm$byClass[6]), 3), cex=1.2)
text(90, 85, names(cm$byClass[7]), cex=1.2, font=2)
text(90, 70, round(as.numeric(cm$byClass[7]), 3), cex=1.2)
# add in the accuracy information
text(30, 35, names(cm$overall[1]), cex=1.5, font=2)
text(30, 20, round(as.numeric(cm$overall[1]), 3), cex=1.4)
text(70, 35, names(cm$overall[2]), cex=1.5, font=2)
text(70, 20, round(as.numeric(cm$overall[2]), 3), cex=1.4)
}
The cross validation model is visualized below, from which we see
that our model has a high accuracy rate of 0.936 and a moderate
sensitivity rate of 0.727. Sensitivity is the true-positive rate, which
is the proportion of actual hotspot grids that were correctly identified
by the model. Given this model’s high accuracy rate, this means that our
model is missing out a few heorin-overdose hotspots.
However, note that in Cincinnati, the number of grids that are
classified as hotspot is much smaller than the number of grids that are
classified as coldspot. While this is a good news for Cincinnati,
logistic regression can be sensitive to class imbalance, meaning that
the model might be biased towards the majority class, leading to poor
performance on the minority class.
reg.vars <- c("Crimecount", "Complaintscount", "pharm.nn", "hospital.nn", "fast.nn", "fuel.nn", "rehab.nn", "parks.nn", "pop25_54", "MedHHInc", "MF_ratio", "race_ratio", "pctPoverty", "pctBachelor")
kitchensink.logit.cv <- logitCV (
dataset = heroin_net,
id = "cvID",
dependentVariable = "hotspot",
indVariables = reg.vars) %>%
dplyr::select(cvID = cvID, hotspot, Prediction, geometry)
kitchensink.logit.cv <- kitchensink.logit.cv %>%
mutate(PredictionCat = as.factor(ifelse(kitchensink.logit.cv$Prediction > 0.5 , 1, 0))) %>%
mutate(hotspot = as.factor(hotspot))
cm_basic <- caret::confusionMatrix(kitchensink.logit.cv$hotspot, kitchensink.logit.cv$PredictionCat,
positive = "1")
draw_confusion_matrix(cm_basic)
Principal Component Analysis (PCA)
One of the reasons why each individual predictor variable is
correlated with overdose hotspot but when combined in a regression, some
of them become insignificant is because of multicollinearity. This
commonly occurs in regression analysis when predictor variables are
highly correlated. It won’t be surprise to see that many of those
demographic variables are highly correlated with each other. It is
possible that distance to hospitals is correlated with distance to
parks, given that there are more those kind of services in downtown
areas. Srinivasan, et al. (2023)’s approach to this issue is to use
Principal Component Analysis (PCA) as a preprocessing step before
running the regression.
PC Identification
PCA is a dimension reduction technique that is commonly used to
reduce the number of variables used in analyses while preserving the
information from them. The prcomp
function below is used to
identify the principal components among all 14 of our predictor
variables.
pca_heroin <- heroin_net %>%
st_drop_geometry() %>%
dplyr::select(Crimecount, Complaintscount, pharm.nn, hospital.nn, fast.nn, fuel.nn, rehab.nn, parks.nn, pop25_54, MedHHInc, MF_ratio, race_ratio, pctPoverty, pctBachelor)
pca_heroin <- na.omit(pca_heroin)
pc <- prcomp(pca_heroin,
center = TRUE,
scale. = TRUE)
The scree plot is used to visualize the importance of each principal
component and can be used to determine the number of principal
components to retain. The y axis is eigenvalues, which essentially stand
for the amount of variation. Here, we may see that the first six, or
seven components are capturing the majority of variability. We can
therefore, just use the first seven principal components and ignore the
rest.
fviz_eig(pc, addlabels = TRUE, barfill = "#BB3754", barcolor = "transparent", xlab = "Principal Components", choice = "eigenvalue")
The code above computed the square cosine value for each variable
with respect to the first seven principal components. From the
illustration below, gender ratio, population aged between 25-54,
distance to parks, and poverty rate are top four variables with the
highest cos2, hence contributing the most to the top seven principal
components.
fviz_cos2(pc, choice = "var", axes = 1:7) +
theme_minimal() +
theme(axis.text.x = element_text(angle = 45, hjust = 1)) +
geom_bar(stat = "identity", fill = "#BB3754", color = "transparent")
We also did a biplot based on our principal component analysis, which
helps us to visualize the similarities and dissimilarities between the
samples, and further understand the impact of each attribute on each of
the principal components. Here, the length of the arrow represents the
strength of the contribution of the corresponding variable to the
specific principal component. The color of the arrow represents the
relative contribution of the variable to the principal component. Darker
colors typically indicate larger contributions. For variables with long
arrow, but light color, such as percentage of population with a
Bachelor’s degree and percentage poverty, their contributions were less
significant compared to other variables, and might only contribute
significantly to one PC. For variables with short arrow but dark color,
such as 311 complaints and crime, they have relatively strong
contributions to all PC. Also, the direction of those arrows indicate
that crimes and complaints have positive contributions to hotspots.
fviz_pca_var(pc, col.var = "cos2", repel = TRUE,
arrowsize = 1, ggtheme = theme_minimal(), gradient.cols = c("black", "#BB3754"),) + ggtitle("Biplot Combind with Contribution")
Below, we computed the first seven principal components based on the
loadings of each of our predictor variables in that component.
pca_net <- heroin_net %>%
mutate(pc1 = Crimecount * pc$rotation[1] + Complaintscount * pc$rotation[2] + pharm.nn * pc$rotation[3] + hospital.nn * pc$rotation[4] + fast.nn * pc$rotation[5] + fuel.nn * pc$rotation[6] + rehab.nn * pc$rotation[7] + parks.nn * pc$rotation[8] + pop25_54 * pc$rotation[9] + MedHHInc * pc$rotation[10] + MF_ratio * pc$rotation[11] + race_ratio * pc$rotation[12] + pctPoverty * pc$rotation[13] + pctBachelor * pc$rotation[14]) %>%
mutate(pc2 = Crimecount * pc$rotation[15] + Complaintscount * pc$rotation[16] + pharm.nn * pc$rotation[17] + hospital.nn * pc$rotation[18] + fast.nn * pc$rotation[19] + fuel.nn * pc$rotation[20] + rehab.nn * pc$rotation[21] + parks.nn * pc$rotation[22] + pop25_54 * pc$rotation[23] + MedHHInc * pc$rotation[24] + MF_ratio * pc$rotation[25] + race_ratio * pc$rotation[26] + pctPoverty * pc$rotation[27] + pctBachelor * pc$rotation[28]) %>%
mutate(pc3 = Crimecount * pc$rotation[29] + Complaintscount * pc$rotation[30] + pharm.nn * pc$rotation[31] + hospital.nn * pc$rotation[32] + fast.nn * pc$rotation[33] + fuel.nn * pc$rotation[34] + rehab.nn * pc$rotation[35] + parks.nn * pc$rotation[36] + pop25_54 * pc$rotation[37] + MedHHInc * pc$rotation[38] + MF_ratio * pc$rotation[39] + race_ratio * pc$rotation[40] + pctPoverty * pc$rotation[41] + pctBachelor * pc$rotation[42]) %>%
mutate(pc4 = Crimecount * pc$rotation[43] + Complaintscount * pc$rotation[44] + pharm.nn * pc$rotation[45] + hospital.nn * pc$rotation[46] + fast.nn * pc$rotation[47] + fuel.nn * pc$rotation[48] + rehab.nn * pc$rotation[49] + parks.nn * pc$rotation[50] + pop25_54 * pc$rotation[51] + MedHHInc * pc$rotation[52] + MF_ratio * pc$rotation[53] + race_ratio * pc$rotation[54] + pctPoverty * pc$rotation[55] + pctBachelor * pc$rotation[56]) %>%
mutate(pc5 = Crimecount * pc$rotation[57] + Complaintscount * pc$rotation[58] + pharm.nn * pc$rotation[59] + hospital.nn * pc$rotation[60] + fast.nn * pc$rotation[61] + fuel.nn * pc$rotation[62] + rehab.nn * pc$rotation[63] + parks.nn * pc$rotation[64] + pop25_54 * pc$rotation[65] + MedHHInc * pc$rotation[66] + MF_ratio * pc$rotation[67] + race_ratio * pc$rotation[68] + pctPoverty * pc$rotation[69] + pctBachelor * pc$rotation[70]) %>%
mutate(pc6 = Crimecount * pc$rotation[71] + Complaintscount * pc$rotation[72] + pharm.nn * pc$rotation[73] + hospital.nn * pc$rotation[74] + fast.nn * pc$rotation[75] + fuel.nn * pc$rotation[76] + rehab.nn * pc$rotation[77] + parks.nn * pc$rotation[78] + pop25_54 * pc$rotation[79] + MedHHInc * pc$rotation[80] + MF_ratio * pc$rotation[81] + race_ratio * pc$rotation[82] + pctPoverty * pc$rotation[83] + pctBachelor * pc$rotation[84]) %>%
mutate(pc7 = Crimecount * pc$rotation[85] + Complaintscount * pc$rotation[86] + pharm.nn * pc$rotation[87] + hospital.nn * pc$rotation[88] + fast.nn * pc$rotation[89] + fuel.nn * pc$rotation[90] + rehab.nn * pc$rotation[91] + parks.nn * pc$rotation[92] + pop25_54 * pc$rotation[93] + MedHHInc * pc$rotation[94] + MF_ratio * pc$rotation[95] + race_ratio * pc$rotation[96] + pctPoverty * pc$rotation[97] + pctBachelor * pc$rotation[98])
PCA Logistic Regression
Following Srinivasan, et al. (2023)’s method, now we can estimated
this logistic regression to predict if a grid was in a hotspot (0/1) as
identified by the Getis-Ord Gi* statistic using components derived from
the PCA as the explanatory variables. For this model using the principal
components as predictors, we still get a accuracy rate of 0.928. Our
sensitivity rate dropped a little bit to 0.66 compared to the kitchen
sink model, probably because we addressed the issue of multicolinearity,
but that is still considered as a moderate sensitivity.
reg.vars <- c("pc1", "pc2", "pc3", "pc4", "pc5", "pc6", "pc7")
pca.logit.cv <- logitCV (
dataset = pca_net,
id = "cvID",
dependentVariable = "hotspot",
indVariables = reg.vars) %>%
dplyr::select(cvID = cvID, hotspot, Prediction, geometry)
pca.logit.cv <- pca.logit.cv %>%
mutate(PredictionCat = as.factor(ifelse(pca.logit.cv$Prediction > 0.5 , 1, 0))) %>%
mutate(hotspot = as.factor(hotspot))
cm_pca <- caret::confusionMatrix(pca.logit.cv$hotspot, pca.logit.cv$PredictionCat,
positive = "1")
draw_confusion_matrix(cm_pca)
In addition to cross-validating using random cvID, we also tried the
spatial ‘Leave-one-group-out’ cross-validation (LOGO-CV) approach,
during which we hold out one neighborhood, train the model on the
remaining n-1 areas, predict for the hold out, and record the goodness
of fit. The result shows that our model has high accuracy, but the
sensitivity further decreases. One possible explanation here is that
different neighborhoods may have distinct characteristics or patterns.
If the model is trained on a set of neighborhoods and then tested on a
neighborhood with different characteristics, its performance may be
negatively affected. Random cross-validation IDs might not capture these
spatial variations as explicitly, but further investigation is required
here.
neighborhood <- st_read(here("data", "public", "neighborhood.geojson")) %>% st_transform("EPSG:3735") %>% st_zm()
pca_net <- pca_net %>%
left_join(net_centroid %>% dplyr::select(uniqueID) %>%
st_intersection(neighborhood %>% dplyr::select(SNA_NAME)) %>%
st_drop_geometry(), by = "uniqueID") %>%
mutate(SNA_NAME = ifelse(is.na(SNA_NAME), "NOT APPLICABLE", SNA_NAME))
pca.logit.cv.neigh <- logitCV (
dataset = pca_net,
id = "SNA_NAME",
dependentVariable = "hotspot",
indVariables = reg.vars) %>%
dplyr::select(SNA_NAME = SNA_NAME, hotspot, Prediction, geometry)
pca.logit.cv.neigh <- pca.logit.cv.neigh %>%
mutate(PredictionCat = as.factor(ifelse(pca.logit.cv.neigh$Prediction > 0.5 , 1, 0))) %>%
mutate(hotspot = as.factor(hotspot))
cm_pca_neigh <- caret::confusionMatrix(pca.logit.cv.neigh$hotspot, pca.logit.cv.neigh$PredictionCat,
positive = "1")
draw_confusion_matrix(cm_pca_neigh)
We computed the Area Under the Receiver Operating Characteristic
Curve (AUC-ROC) metric for our model to evaluate their performance. The
AUC value ranges from 0 to 1, where 0.5 means a model with no
discriminatory power (it performs as well as random chance), and 1 means
a perfect model (it perfectly distinguishes between positive and
negative cases). Generally, an AUC over 0.8 indicates a good performance
of the model. For the random K-fold cross validation, we get a AUC score
of 0.904 and for the Spatial LOGO-CV, we get a AUC score of 0.8628. Both
are good scores and indicate that model is capturing important patterns
in the data and making predictions better than random guessing.
plot7 <- ggplot(pca.logit.cv, aes(d = as.numeric(hotspot), m = Prediction)) +
geom_roc(n.cuts = 50, labels = FALSE, colour = "black") +
labs(title = "ROC Curve for Logistic Regression Model using PCA",
subtitle = "Random Cross Validation") +
style_roc(theme = theme_grey) +
geom_abline(slope = 1, intercept = 0, size = 1.5, color = "#BB3754") +
theme(plot.subtitle = element_text(size = 9,face = "italic"),
plot.title = element_text(size = 12, face = "bold"),
axis.text.x=element_text(size=8),
axis.text.y=element_text(size=8),
axis.title=element_text(size=9),
panel.background = element_blank(),
panel.border = element_rect(colour = "grey", fill=NA, linewidth =0.8))
plot8 <- ggplot(pca.logit.cv.neigh, aes(d = as.numeric(hotspot), m = Prediction)) +
geom_roc(n.cuts = 50, labels = FALSE, colour = "black") +
labs(title = "ROC Curve for Logistic Regression Model using PCA",
subtitle = "Spatial LOGO Cross Validation") +
style_roc(theme = theme_grey) +
geom_abline(slope = 1, intercept = 0, size = 1.5, color = "#BB3754") +
theme(plot.subtitle = element_text(size = 9,face = "italic"),
plot.title = element_text(size = 12, face = "bold"),
axis.text.x=element_text(size=8),
axis.text.y=element_text(size=8),
axis.title=element_text(size=9),
panel.background = element_blank(),
panel.border = element_rect(colour = "grey", fill=NA, linewidth =0.8))
plot7 + plot8
plot9 <- pca.logit.cv %>%
filter(is.na(Prediction) == FALSE) %>%
mutate(result = case_when(
PredictionCat==0 & hotspot==0 ~ "Accurate",
PredictionCat==1 & hotspot==1 ~"Accurate",
PredictionCat==0 & hotspot==1 ~"Problematic",
TRUE ~ "Inaccurate"
)) %>%
ggplot() +
geom_sf(aes(fill=as.character(hotspot)), color="transparent")+
scale_fill_manual(values = c("black", "#BB3754"), name = "Hotspot") +
labs(title = "Actual Hotspot") +
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)
)
plot10 <- pca.logit.cv %>%
ggplot() +
geom_sf(aes(fill=as.character(PredictionCat)), color="transparent")+
scale_fill_manual(values = c("black", "#BB3754"), name = "Hotspot") +
labs(title = "Predicted Hotspot",
subtitle = "Random Cross Validation") +
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)
)
plot11 <- pca.logit.cv.neigh %>%
ggplot() +
geom_sf(aes(fill=as.character(PredictionCat)), color="transparent")+
scale_fill_manual(values = c("black", "#BB3754"), name = "Hotspot") +
labs(title = "Predicted Hotspot",
subtitle = "Spatial LOGO Validation") +
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)
)
plot12 <- pca.logit.cv %>%
filter(is.na(Prediction) == FALSE) %>%
mutate(result = case_when(
PredictionCat==0 & hotspot==0 ~ "Accurate",
PredictionCat==1 & hotspot==1 ~"Accurate",
PredictionCat==0 & hotspot==1 ~"Problematic",
TRUE ~ "Inaccurate"
)) %>%
ggplot() +
geom_sf(aes(fill=as.character(result)), color="transparent")+
scale_fill_manual(values = c("black", "#FFC000", "#BB3754"), name = "Hotspot") +
labs(title = "Mapping Error",
subtitle = "Random Cross Validation") +
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)
)
plot9 + plot10 + plot11 + plot12
If our model has generally been good at making prediction (high
accuracy) but with some uncertainties in correctly locating the hotspot,
then we would like to know where it is making mistakes. We mapped the
error below and compared it to the actual hotspot. What we can see is
that our model is accurate in roughly estimating the location of the
hotspot, but tends to make mistakes when deciding which of these grids
in particular should be categorized as hotspot.
We highlighted those grids which are actual hotspot but were wrongly
predicted as problematic, from which we may see that those most of those
problematic grids pretty much neighbors hotspot grids. This implies that
when drawing conclusion from our model, we should not only look at
specific hotspot grid, but also probably pay attention to grids
nearby.
KDE Validation
The accuracy of our model is also compared relative to 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.
heroin_ppp <- as.ppp(st_coordinates(heroin), W = st_bbox(heroin_net))
heroin_KD.1000 <- spatstat.explore::density.ppp(heroin_ppp, 1000)
heroin_KD.df <- data.frame(rasterToPoints(mask(raster(heroin_KD.1000), as(cincinnati20, 'Spatial'))))
ggplot(data=heroin_KD.df, aes(x=x, y=y)) +
geom_raster(aes(fill=layer)) +
coord_sf(crs=st_crs(heroin_net)) +
scale_fill_viridis(option = "rocket", name="Density") +
labs(title = "Kernel Density of Heroin 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)
)
The kernel density estimation was classified into two categories,
lower risk and higher risk, matching the binary of hotspot and coldspot.
We used the Fisher's method
that involves finding intervals
such that the variance within each interval is minimized while
maximizing the variance between the intervals. The purpose here is to
see if the our model capture the same hotspot grids as the kernel
density. The map below shows that our model perform as good as, if not
worse, the kernal density approach in capturing hotspots (high risk
areas).
heroin_KDE_sum <- as.data.frame(heroin_KD.1000) %>%
st_as_sf(coords = c("x", "y"), crs = st_crs(heroin_net)) %>%
aggregate(., heroin_net, mean)
kde_breaks <- classIntervals(heroin_KDE_sum$value,
n = 2, "fisher")
heroin_KDE_sf <- heroin_KDE_sum %>%
mutate(label = "Kernel Density",
Risk_Category = classInt::findCols(kde_breaks),
Risk_Category = case_when(
Risk_Category == 2 ~ "High",
Risk_Category == 1 ~ "Low"))
heroin_risk_sf <-
pca.logit.cv %>%
mutate(label = "Logistic Regression",
Risk_Category = case_when(
PredictionCat == 1 ~ "High",
PredictionCat == 0 ~ "Low")) %>%
dplyr::select(PredictionCat, label, Risk_Category) %>%
rename(value = PredictionCat)
rbind(heroin_KDE_sf, heroin_risk_sf) %>%
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 = "rocket", discrete = TRUE, name = "Risk Category") +
labs(title="Comparison of Kernel Density and Risk Predictions") +
theme(legend.position="bottom",
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)
)
Multi Criteria Analysis
We would like to conclude our analysis by building
multi-criteria-analysis-centered geospatial risk terrain model that
stakeholders and the public can use to learn about the community
vulnerability to heroin-overdose in Cincinnati. In GIS, a Multi-Criteria
Analysis (MCA) model is a decision-making approach that involves
evaluating and comparing multiple criteria or factors to make informed
spatial decisions. The criteria we use here are the predictor variables
we use in our model. These criteria are standardized to ensure that all
of them are on a scale of 0 to 1. Then, some of those scaled variables
are reversed if necessary based on whether they contribute positively or
negatively to the heroin-overdose risk. We supply a weight on each of
those variables based on their contribution. Finally, a risk score is
computed for each grid.
scale_values <- function(x){(x-min(x))/(max(x)-min(x))}
risk_score <- heroin_net %>%
mutate(MedHHInc = ifelse(is.na(MedHHInc), 0, MedHHInc)) %>%
mutate(scl_crime = scale_values(Crimecount),
scl_complaints = scale_values(Complaintscount),
scl_pharm = scale_values(pharm.nn),
scl_hospital = scale_values(hospital.nn),
scl_rehab = scale_values(rehab.nn),
scl_park = scale_values(parks.nn),
scl_fuel = scale_values(fuel.nn),
scl_fast = scale_values(fast.nn),
scl_pop = scale_values(pop25_54),
scl_income = scale_values(MedHHInc),
scl_gender = scale_values(MF_ratio),
scl_race = scale_values(race_ratio),
scl_poverty = scale_values(pctPoverty),
scl_edu = scale_values(pctBachelor)) %>%
mutate(scl_race_re = 0 - scl_race + 1,
scl_edu_re = 0 - scl_edu + 1,
scl_income_re = 0 - scl_income + 1,
scl_fuel_re = 0 - scl_fuel + 1,
scl_fast_re = 0 - scl_fast + 1,
scl_park_re = 0 - scl_park + 1,
scl_pharm_re = 0 - scl_pharm + 1) %>%
mutate(score = 0.1 * (scl_hospital + scl_crime + scl_complaints + scl_gender + scl_income_re + scl_fuel_re + scl_fast_re + scl_park_re) + 0.05 * (scl_race_re + scl_pharm_re) + 0.025*(scl_pop +scl_edu_re + scl_poverty + scl_rehab)) %>%
st_as_sf()
risk_score <- risk_score %>%
left_join(net_centroid %>% dplyr::select(uniqueID) %>%
st_intersection(neighborhood %>% dplyr::select(SNA_NAME)) %>%
st_drop_geometry(), by = "uniqueID") %>%
mutate(SNA_NAME = ifelse(is.na(SNA_NAME), "NOT APPLICABLE", SNA_NAME))
This below interactive leaflet map shows the risk score of each grid,
the neighborhood that grid fall into, and whether or not it falls into a
hotspot that our model predicts. We hope that this will serve as our
first step towards building up our use case that present stakeholders
with a clearer picture of community vulnerability to heroin overdose and
that help the public to have a better understanding of the severity of
heroin-overdose in Cincinnati as well as existing rehabilitation
resources.
colors <- rev(viridis::viridis(6, option = "rocket"))
pal <- colorBin(palette = colors,
domain = risk_score$score,
bins = c(0.2, 0.3, 0.4, 0.5, 0.6, 0.7))
labs <- paste0("<strong>", risk_score$SNA_NAME,
"</strong><br/>Heorin-Overdose Risk Score: ", risk_score$score
)
leaflet <- risk_score %>%
st_transform("EPSG:4326") %>%
leaflet() %>%
setView(lng = -84.51, lat = 39.10, zoom = 10.5) %>%
addProviderTiles(providers$CartoDB.Positron) %>%
addPolygons(weight = 1,
fillColor = ~pal(score), # change the name of the parameter in the bracket
popup = labs,
color = "transparent",
group = "Risk Score",
fillOpacity = 1) %>%
addPolygons(
data = pca.logit.cv %>% filter(PredictionCat == 1) %>% st_transform("EPSG:4326"),
weight = 2,
group = "Overdose Hotspot",
color = "yellow") %>%
addLayersControl(
overlayGroups = c("Risk Score", "Overdose Hotspot"),
options = layersControlOptions(collapsed = FALSE)
) %>%
addLegend(position = "bottomright",
pal = pal,
values = ~risk_score, # change the name of the parameter after ~
title = "Risk Score",
opacity = 1) # change the name of the legend
leaflet
mapshot(leaflet, url = "cincyrisk.html")
Conclusion
In this report, we conducted a study on opioid-overdose risk modeling
in Cincinnati, Ohio where the opioid epidemic has been a serious issue.
Drawing on methods by Srinivasan, et al. (2023), we used Getis-Ord
hotspot analyses to identify locations of persistent risk in Cincinnati
from 2016-2020. We constructed measures of the socio-built environment
and neighborhood demographic characteristics using principal components
analysis and ran logistic regression to determine if places in
Cincinnati fall into heroin-overdose hotspots or not. While Poisson
regression has long been the traditional method used in geospatial risk
model to predict the number (count) of discrete event, we propose this
alternative method in this report because we believe that it is less
useful for policymakers to focus on the number of heroin-overdose
incidence among neighborhoods in Cincinnati. Rather, we should develop a
way to quickly identify areas/clusters of high heroin-overdose
incidence.
Our model, which uses principal components as predictors, has an
accuracy rate of 0.928, a sensitivity rate of 0.66, and an AUC score of
0.904, meaning that it has been effective overall in distinguishing
between hotspots and cold spots. We mapped the error and compared that
to the actual hotspot, from which we noticed that our model is accurate
in roughly estimating the location of the hotspot but tends to make
mistakes when deciding which of these grids in particular should be
categorized as hotspot. On top of that, many of those grids are
neighbors of hotspot grids that are correctly predicted.
Reflecting on the overall study design, we have the following
concerns, which we would like to continue working on to improve the
accuracy and reliability of our model. Firstly, our study utilized a
binary classification of hotspot and cold spot to describe risk. While
this is a common approach in risk modeling, we should recognize the
complexity within these categories as areas neighboring hotspots may
also exhibit varying degrees of risk. Building onto that, another major
issue is that our analysis has categorized “insignificant” grids into
cold spots. In theory, cold spot should refer to places with a series of
grids that have low heroin-incidence count whereas for insignificant
grid, there might be a high number of incidences within specific grid.
It’s just that those grids do not form clusters with surrounding grids.
Merging insignificant grids into cold spot might risk overlooking
heroin-incidence within those grids.
Secondly, within the discussion, we highlight the potential imbalance
in logistic regression, prompting consideration for weighted logistic
regression to address disparities in the prediction of positive and
negative cases. Thirdly, during cross validation, we found that our
model does not generalize well across neighborhoods. This highlights
significant variations between different neighborhoods in Cincinnati,
which requires further investigations. Fourthly, as we need to arrogate
information into a standardized spatial unit that’s different from any
existing spatial unit, we need to be aware of how our model might be
sensitive to errors and uncertainties introduced by area-weighted
re-aggregation and boundary/edge effect.
Regardless, the takeaways from this analysis will contribute to our
understanding of the nuanced landscape of heroin-overdose crisis in
Cincinnati and provide important guidance on public health initiatives
aiming to address heroin addiction.
We believe that our risk terrain model could effectively visualize
heroin-overdose hotspots and could serve a good starting point for
providing real-time update through calculating a risk score for each
part of the city, allowing the government to conduct real-time data
analysis to efficiently allocate EMS resources. We hope that our effort
in bringing in data from a variety of sources would encourage data
sharing between public and private sectors so that the government can
manage public service and monitor its built environment. Reports can be
collected from public parks, fast food restaurants, gas stations, places
where most overdose incidence occurs. Having data from local hospitals
and rehabilitation centers allows the government to see capacity
information, thereby efficiently dispatching preparing and dispatching
care so that the risk of heroin overdose is minimized before they
happen.
We look forward to seeing our risk terrain model being adapted in
various scenarios that truly benefit both the stakeholders in managing
community health and well-being as well as serve the needs of local
residents.
References
Choi, J. I., Lee, J., Yeh, A. B., Lan, Q., & Kang, H. (2022).
Spatial clustering of heroin-related overdose incidents: a case study in
Cincinnati, Ohio. BMC public health, 22(1), 1-12. https://doi.org/10.1186/s12889-022-13557-3
Srinivasan, S., Pustz, J., Marsh, E., Young, L. D., & Stopka, T.
J. (2023). Risk factors for persistent fatal opioid-involved overdose
hotspots in Massachusetts 2011-2021: A spatial statistical analysis with
socio-economic, access and prescription factors.https://doi.org/10.21203/rs.3.rs-3249650/v1
Chichester, K. R., Drawve, G., Sisson, M., Giménez-Santana, A.,
McCleskey, B., Goodin, B. R., … & Cropsey, K. L. (2023). Crime and
Features of the Built Environment Predicting Risk of Fatal Overdose: A
Comparison of Rural and Urban Ohio Counties with Risk Terrain Modeling.
American Journal of Criminal Justice, 1-25. https://doi.org/10.1007/s12103-023-09739-3
LS0tCnRpdGxlOiAiR2Vvc3BhdGlhbCBSaXNrIFByZWRpY3Rpb24gb2YgSGVyb2luIE92ZXJkb3NlIEhvdHNwb3RzIGluIENpbmNpbm5hdGkgT2hpbyIKYXV0aG9yOiAiRW1pbHkgWmhvdSwgRUNoaW4gTGkiCmRhdGU6ICJgciBTeXMuRGF0ZSgpYCIKb3V0cHV0OgogIGh0bWxfZG9jdW1lbnQ6CiAgICB0aGVtZTogc2ltcGxleAogICAgdG9jOiB5ZXMKICAgIHRvY19mbG9hdDogeWVzCiAgICBjb2RlX2ZvbGRpbmc6IGhpZGUKICAgIGNvZGVfZG93bmxvYWQ6IHllcwplZGl0b3Jfb3B0aW9uczoKICBtYXJrZG93bjoKICAgIHdyYXA6IHNlbnRlbmNlCi0tLQoKYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9Cgprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUpCgpgYGAKClZlcnNpb24gMy4wIFx8IEZpcnN0IENyZWF0ZWQgTm92IDE3LCAyMDIzIFx8IFVwZGF0ZWQgRGVjIDgsIDIwMjMKCktleXdvcmRzOiBLIE5lYXJlc3QgTmVpZ2hib3JzLCBNb3JhbidzIEksIEdldGlzLU9yZCBHaVwqLCBLZXJuZWwgRGVuc2l0eSBFc3RpbWF0aW9uLCBSYW5kb20gSy1mb2xkIENyb3NzIFZhbGlkYXRpb24sIExvZ2lzdGljIFJlZ3Jlc3Npb24sIFBvaXNzb24gUmVncmVzc2lvbiwgUk9DIEN1cnZlLCBQcmluY2lwYWwgQ29tcG9uZW50IEFuYWx5c2lzLCBNdWx0aS1Dcml0ZXJpYSBBbmFseXNpcywgSGVhbHRoIEdlb2dyYXBoeQoKSW1wb3J0YW50IExpbmtzOiBbc3R1ZHkgcmVwb3NpdG9yeV0oaHR0cHM6Ly9naXRodWIuY29tL2VtaWx5emhvdTExMi9NVVNBNTA4MC1DaW5jaW5uYXRpLUhlcm9pbiksIFtwaXRjaCBwcmVzZW50YXRpb25dKGh0dHBzOi8veW91dHUuYmUvTFJXZ1RlSFhOdGspCgojIEludHJvZHVjdGlvbgoKVGhlIG9waW9pZCBvdmVyZG9zZSBjcmlzaXMgY29udGludWVzIGFzIG9uZSBvZiB0aGUgbW9zdCBzaWduaWZpY2FudCBwdWJsaWMgaGVhbHRoIGNoYWxsZW5nZXMgb2YgdGhlIHBhc3QgdHdvIGRlY2FkZXMgaW4gdGhlIFVTLgpCZXR3ZWVuIDE5OTkgYW5kIDIwMTksIHRoZSBuYXRpb25hbCBhZ2UtYWRqdXN0ZWQgb3Bpb2lkLWludm9sdmVkIG92ZXJkb3NlIGRlYXRoIHJhdGUgaW5jcmVhc2VkIGZyb20gMi45IHBlciAxMDAsMDAwIHBvcHVsYXRpb24gdG8gMTUuNSBwZXIgMTAwLDAwMC4KUmF0ZXMgaGF2ZSBpbmNyZWFzZWQgc3RlZXBseSBkdXJpbmcgdGhlIENPVklELTE5IHBhbmRlbWljLgpJbiAyMDIwLCB0aGUgcmF0ZSBjbGltYmVkIHRvIDIxLjQgcGVyIDEwMCwwMDAgYW5kIHRvIDI0LjcgcGVyIDEwMCwwMDAgaW4gMjAyMS4KTW9zdCBsaXRlcmF0dXJlIHRvIHRoaXMgZGF0ZSBoYXZlIGZvY3VzZWQgb24gdW5kZXJzdGFuZGluZyB0aGUgZHJpdmVycyBvZiBvdmVyZG9zZSBjcmlzaXMsIHdoaWNoIGluY2x1ZGUgZHJ1ZyBzdXBwbHksIGRydWcgbWFudWZhY3R1cmluZywgcGFpbiBhcmlzaW5nIGZyb20gd29yay1yZWxhdGVkIGluanVyaWVzLCBpbmNvbWUgaW5lcXVhbGl0eSwgYW5kIGFkZGVkIHN0cmVzcywgaXNvbGF0aW9uLCBhbmQgZWNvbm9taWMgZGlzYWR2YW50YWdlIGNvbm5lY3RlZCB0byB0aGUgQ09WSUQtMTkgcGFuZGVtaWMuClVudGlsIHJlY2VudGx5LCBob3dldmVyLCBtb3JlIGFuZCBtb3JlIHN0dWRpZXMgaGF2ZSBiZWd1biB0byBjb25zaWRlciB0aGUgYXNzb2NpYXRpb24gYmV0d2VlbiBkcnVnIHVzZSwgb3Bpb2lkLXJlbGF0ZWQgbW9ydGFsaXR5LCBhbmQgdGhlIGJ1aWx0LWVudmlyb25tZW50LgpDb250ZXh0dWFsIHJpc2sgZmFjdG9ycywgZXNwZWNpYWxseSBmZWF0dXJlcyBvZiB0aGUgYnVpbHQgZW52aXJvbm1lbnQsIHRoZSBwcmVzZW5jZSBvZiB2aW9sZW50IGNyaW1lIG9yIGNyaW1lIGFzc29jaWF0ZWQgd2l0aCBzdWJzdGFuY2UgdXNlLCBhbmQgbmVpZ2hib3Job29kLWxldmVsIHNvY2lvZWNvbm9taWMgZmFjdG9ycywgaGF2ZSBlbmFibGVkIG1vcmUgYWNjdXJhdGUgaWRlbnRpZmljYXRpb24gb2YgaGlnaC1yaXNrIG92ZXJkb3NlIGFyZWFzLCB0aHVzIGRyaXZpbmcgaW5pdGlhdGl2ZXMgb24gYWxsZXZpYXRpbmcgb3ZlcmRvc2UgcmlzayBhbmQgY3Jpc2VzLgoKV2UgY29uZHVjdGVkIGEgY2FzZSBzdHVkeSBvbiBvcGlvaWQtb3ZlcmRvc2UgcmlzayBtb2RlbGluZyBpbiBDaW5jaW5uYXRpLCBPaGlvIHdoZXJlIHRoZSBvcGlvaWQgZXBpZGVtaWMgaGFzIGJlZW4gYSBzZXJpb3VzIGlzc3VlLgpTaW5jZSAyMDA5LCBkcnVnIG92ZXJkb3NlIGRlYXRocyBpbiBPaGlvIGhhdmUgY29udGludWVkIHRvIGluY3JlYXNlLCBhbmQgQ2luY2lubmF0aSwgYSBsYXJnZSBjaXR5IGluIHNvdXRod2VzdGVybiBPaGlvIHdpdGggYSBwb3B1bGF0aW9uIG9mIGFib3V0IDMwMCwwMDAgcGVvcGxlLCB3YXMgb25lIG9mIDEyIG9waW9pZCBob3RzcG90cyBpbiBPaGlvIGFuZCBoYWQgdGhlIGhpZ2hlc3QgcGVyIGNhcGl0YSByYXRlcyBvZiBhIGZhdGFsIG92ZXJkb3NlIGJldHdlZW4gMjAxMCBhbmQgMjAxNy4KVG9kYXksIGhlcm9pbiBvdmVyZG9zZSBpbmNpZGVuY2VzIHN0aWxsIHJlbWFpbiBoaWdoIGluIHRoZSBjaXR5IHNpbmNlIGhlcm9pbiBhbmQgcHJlc2NyaXB0aW9uIGRydWdzIGFidXNlIGhhdmUgZnVydGhlciBsZWQgdG8gb3RoZXIgcHVibGljIGhlYWx0aCBwcm9ibGVtcyBzdWNoIGFzIEhJViBhbmQgbmVlZGxlLWJvcm5lIGRpc2Vhc2VzLgoKV2UgY29tYmluZWQgbXVsdGlwbGUgZGF0YSBzb3VyY2VzLCBpbmNsdWRpbmcgZW1lcmdlbmN5IG1lZGljYWwgc2VydmljZSByZXNwb25zZSwgY3JpbWUgYW5kIG5vbi1lbWVyZ2VuY3kgcmVwb3J0cyBkYXRhLCBBbWVyaWNhbiBDb21tdW5pdHkgU3VydmV5IGRhdGEsIGFuZCBkYXRhc2V0cyB0aGF0IGFyZSBwcm94aWVzIGZvciBsb2NhbCBidWlsdCBlbnZpcm9ubWVudCB0byBhbmFseXplIHRoZSBkaXN0cmlidXRpb25zIG9mIGhlcm9pbi1yZWxhdGVkIG92ZXJkb3NlIGluY2lkZW50cyBpbiBDaW5jaW5uYXRpLCBPaGlvLgoKQW1vbmcgc3R1ZGllcyBvZiBzcGF0aWFsIGVwaWRlbWlvbG9neSwgZ2Vvc3BhdGlhbCBjbHVzdGVyaW5nIGhhcyBiZWVuIHVzZWQgdG8gYW5hbHl6ZSB0aGUgZ2VvZ3JhcGhpYyB2YXJpYXRpb24gb2YgcGhlbm9tZW5hLCBzdWNoIGFzIHRoZSBkaXNwcm9wb3J0aW9uYXRlIGltcGFjdCBvZiBwdWJsaWMgaGVhbHRoIGNyaXNpcyBvbiBwZW9wbGUgd2l0aCBkaXNhYmlsaXRpZXMsIGFuZCB0aGUgY29uY2VudHJhdGVkIHZ1bG5lcmFiaWxpdHkgb2Ygc29jaW8tZWNvbm9taWNhbGx5IGRpc2FkdmFudGFnZWQgcG9wdWxhdGlvbiB1bmRlciBuYXR1cmFsIGhhemFyZHMuCkl0IGFsbG93cyB0aGUgaW52ZXN0aWdhdG9ycyB0byBpZGVudGlmeSBncm91cHMgb2Ygc3BhdGlhbCBvYmplY3RzIChpLmUuIGNsdXN0ZXJzKSB0aGF0IGhhdmUgc2ltaWxhciBjaGFyYWN0ZXJpc3RpY3MgYW5kIGFuYWx5emUgcGF0dGVybnMgb2YgdGhlIGNsdXN0ZXJzIChlLmcuLCBob3Qgc3BvdHMsIGNvbGQgc3BvdHMpLgpBcyBzdWNoLCB3ZSB1c2VkIEdldGlzLU9yZCBob3RzcG90IGFuYWx5c2VzIHRvIGlkZW50aWZ5IGxvY2F0aW9ucyBvZiBwZXJzaXN0ZW50IHJpc2sgaW4gQ2luY2lubmF0aSBmcm9tIDIwMTYtMjAyMC4KV2UgY29uc3RydWN0ZWQgbWVhc3VyZXMgb2YgdGhlIHNvY2lvLWJ1aWx0IGVudmlyb25tZW50IGFuZCBuZWlnaGJvcmhvb2QgZGVtb2dyYXBoaWMgY2hhcmFjdGVyaXN0aWNzIHVzaW5nIHByaW5jaXBhbCBjb21wb25lbnRzIGFuYWx5c2lzIChQQ0EpLgpUaGUgcmVzdWx0aW5nIG1lYXN1cmVzIHdlcmUgdXNlZCBhcyBjb3ZhcmlhdGVzIGluIGEgbG9naXN0aWMgcmVncmVzc2lvbiB3aXRoIHJhbmRvbSBrLWZvbGQgY3Jvc3MgdmFsaWRhdGlvbiB0byBwcmVkaWN0IGlmIGFuIGFyZWEgd2FzIHBhcnQgb2YgYW4gb3Bpb2lkIGhvdHNwb3QgY2x1c3Rlci4KQWxsIGJ1aWx0IGVudmlyb25tZW50IHJpc2sgZmFjdG9ycyB3ZXJlIGFnZ3JlZ2F0ZWQsIHNjYWxlZCwgYW5kIHdlaWdodGVkIHRvIGdlbmVyYXRlIGFuIGludGVyYWN0aXZlIG92ZXJkb3NlIHJpc2sgdGVycmFpbiBtYXAuCgoqKk9uIHRoZSB0ZWNobmljYWwgc2lkZSoqLCB0aGUgZ29hbCBvZiBvdXIgc3R1ZHkgaXMgdG8gYnVpbGQgYSBtb2RlbCB0aGF0IGNvdWxkIHByZWRpY3Qgb3Bpb2lkIG92ZXJkb3NlIGNsdXN0ZXJzIGFzIHdlbGwgYXMgc29jaW9lY29ub21pYyBhbmQgYnVpbHQgZW52aXJvbm1lbnQgZmFjdG9ycyBhc3NvY2lhdGVkIHdpdGggb3Bpb2lkIG92ZXJkb3NlIHJhdGVzIGluIENpbmNpbm5hdGkgZnJvbSAyMDE2IHRvIDIwMjAgYXMgbXVjaCBhcyBwb3NzaWJsZS4gCioqT24gdGhlIHByYWN0aWNhbCBzaWRlKiosIHdlIHdvdWxkIGxpa2Ugb3VyIHJpc2sgdGVycmFpbiBtb2RlbCB0byBiZW5lZml0IGJvdGggdGhlIHN0YWtlaG9sZGVycyBhbmQgY29tbXVuaXR5IG1lbWJlcnMgaW4gdGhlIG1haW4gZm9ybSBvZiBhIG11bmljaXBhbCBkYXNoYm9hcmQgYW5kIGEgbW9iaWxlIGFwcGxpY2F0aW9uLiBGb3Igc3Rha2Vob2xkZXJzLCB0aGUgZGFzaGJvYXJkIHdvdWxkIHNlcnZlIGFzIGFuIGVhcmx5IHdhcm5pbmcgc3lzdGVtIGJ5IHByZWRpY3Rpbmcvc2hvd2luZyBhcmVhcyBhdCBoaWdoIHJpc2sgb2Ygb3Bpb2lkIG92ZXJkb3NlLiBTcGVjaWZpY2FsbHksIGl0IHNob3VsZCBpbmNsdWRlIG1hcHMgd2l0aCBvdmVyZG9zZSBjbHVzdGVyIGxvY2F0aW9ucywgdHJlbmQgZ3JhcGhzIG92ZXIgdGltZSwgYW5kIGNoYXJ0cyBpbGx1c3RyYXRpbmcgdGhlIGNvcnJlbGF0aW9uIGJldHdlZW4gb3ZlcmRvc2UgcmF0ZXMgYW5kIHZhcmlvdXMgZmFjdG9ycy4gRm9yIGNvbW11bml0eSBtZW1iZXJzLCB0aGlzIGFwcGxpY2F0aW9uIGNvbm5lY3RzIHRoZW0gdG8gaGVhbHRoY2FyZSBhbmQgcmVoYWJpbGl0YXRpb24gcmVzb3VyY2VzIHdpdGhpbiB0aGUgY2l0eS4gU3BlY2lmaWNhbGx5LCB0aGUgYXBwbGljYXRpb24gYWxsb3cgdGhlbSB0byBjYWxsIGZvciBlbWVyZ2VuY3kgc2VydmljZXMgYW5kIG1lZGljYWwgYXNzaXN0YW5jZSBmb3IgdGhlbXNlbHZlcyBvciBmb3Igb3RoZXJzIGF0IG9uZSB0YXAuIFRvIGVuY291cmFnZSBwdWJsaWMtcHJpdmF0ZSBwYXJ0bmVyc2hpcCwgdGhpcyBhcHAgd291bGQgYWRvcHQgdGhlIGFwcHJvYWNoIHRoYXQgT3BlblN0cmVldE1hcCB0b29rIHRvIGFsbG93IGJvdGggdGVjaG5pY2FsIGFuZCBub24tdGVjaG5pY2FsIHVzZXJzLCBwcml2YXRlIGFuZCBwdWJsaWMgZW50aXRpZXMgdG8gdXBkYXRlIGV4aXN0aW5nIGluZm9ybWF0aW9uLCBzdWNoIGFzIGNyaW1lIGluY2lkZW50cywgcmVoYWJpbGl0YXRpb24gY2VudGVyIGNhcGFjaXRpZXMgYW5kIGxvY2F0aW9uLiBXZSBob3BlIHRoYXQgbm9uLXRlY2huaWNhbCBzdGFrZWhvbGRlcnMgY2FuIHVzZSB0aGUgYXBwIHRvIGFsbG9jYXRlIHJlc291cmNlcyBlZmZpY2llbnRseSwgZGlyZWN0aW5nIG91dHJlYWNoIGVmZm9ydHMsIHRyZWF0bWVudCBmYWNpbGl0aWVzLCBhbmQgZWR1Y2F0aW9uYWwgY2FtcGFpZ25zIHRvIGFyZWFzIHdpdGggdGhlIGhpZ2hlc3QgcHJlZGljdGVkIHJpc2suIFdlIGhvcGUgdGhhdCB0aGUgcHVibGljIHdvdWxkIHVzZSB0aGlzIHBsYXRmb3JtIHRvIGZvc3RlciBjb21tdW5pdHkgZW5nYWdlbWVudCBhbmQgc3VwcG9ydCBmb3IgaW5pdGlhdGl2ZXMgYWRkcmVzc2luZyB0aGUgb3Bpb2lkIGNyaXNpcy4KClRoZSByZXN0IG9mIHRoZSByZXBvcnQgaXMgb3JnYW5pemVkIGFzIGZvbGxvd3MuClNlY3Rpb24gb25lIGRlc2NyaWJlcyBvdXIgYXBwcm9hY2ggdG8gYWNjZXNzIGFuZCBwcmUtcHJvY2VzcyB0aGUgZGF0YS4KU2VjdGlvbiB0d28gZG9jdW1lbnRzIGFuIGV4cGxvcmF0b3J5IGFuYWx5c2lzIHdlIGRpZCB1c2luZyBwb2lzc2lvbiByZWdyZXNzaW9uIHdpdGggb25seSBjcmltZSBhbmQgMzExIGluY2lkZW50cy4KU2VjdGlvbiB0aHJlZSBleHBsYWlucyB0aGUgY29uY2VwdHVhbGl6YXRpb24gb3VyIGFwcHJvYWNoIGFwcGx5aW5nIHRoZSBHZXRpcyBPcmQgR1wqIHN0YXRpc3RpY3MgdG8gZGV0ZXJtaW5lIHNwYXRpYWwgaG90c3BvdHMuClNlY3Rpb24gZm91ciBpcyB0aGUgbWFpbiBwYXJ0IG9mIHRoaXMgc3R1ZHksIHdoZXJlIHdlIGljbHVkZSBhbGwgYnVpbHQgZW52aXJvbm1lbnQgcHJlZGljdG9ycyB0byBydW4gYSBsb2dpc3RpYyByZWdyZXNzaW9uIHByZWRpY3RpbmcgaWYgYW4gYXJlYSBpcyBhbiBvcGlvaWQgb3ZlcmRvc2UgaG90c3BvdCBvciBub3QuClNlY3Rpb24gZml2ZSBhcHBsaWVzIHByaW5jaXBhbCBjb21wb25lbnQgYW5hbHlzaXMgdG8gcmVkdWNlIGNvbGluZXJpdHkgYW5kIGluY2x1ZGUgb25seSBoYWxmIG9mIHRoZSBwcmluY2lwYWwgY29tcG9uZW50cyBpbnRvIHRoZSBtb2RlbC4KU2VjdGlvbiBzaXggdmFsaWRhdGVzIHRoZSBhY2N1cmFjeSBvZiB0aGlzIG1vZGVsIGFnYWluc3QgYSBrZXJuZWwgZGVuc2l0eSBlc3RpbWF0aW9uIHdoaWxlIHRoZSBsYXN0IHNlY3Rpb24gY29uc3RydWN0cyB0aGUgcmlzayB0ZXJyYWluIG1hcC4KClRoZSBjb25jZXB0dWFsaXphdGlvbiBhbmQgbWV0aG9kb2xvZ2llcyBvZiB0aGlzIHN0dWR5IHdlcmUgZHJhd24gaW4gcGFydCBmcm9tOiAKCi0gQ2hvaSwgZXQgYWwuKDIwMjIpIFNwYXRpYWwgY2x1c3RlcmluZyBvZiBoZXJvaW4tcmVsYXRlZCBvdmVyZG9zZSBpbmNpZGVudHM6IGEgY2FzZSBzdHVkeSBpbiBDaW5jaW5uYXRpLCBPaGlvCgotICBTcmluaXZhc2FuLCBldCBhbC4gKDIwMjMpIFJpc2sgZmFjdG9ycyBmb3IgcGVyc2lzdGVudCBmYXRhbCBvcGlvaWQtaW52b2x2ZWQgb3ZlcmRvc2UgaG90c3BvdHMgaW4gTWFzc2FjaHVzZXR0cyAyMDExLTIwMjEgCgotIENoaWNoZXN0ZXIsIGV0IGFsLigyMDIzKSBDcmltZSBhbmQgRmVhdHVyZXMgb2YgdGhlIEJ1aWx0IEVudmlyb25tZW50IFByZWRpY3RpbmcgUmlzayBvZiBGYXRhbCBPdmVyZG9zZTogQSBDb21wYXJpc29uIG9mIFJ1cmFsIGFuZCBVcmJhbiBPaGlvIENvdW50aWVzIHdpdGggUmlzayBUZXJyYWluIE1vZGVsaW5nLgoKCmBgYHtyIGxpYnJhcnkgYW5kIHNldHVwLCBpbmNsdWRlPUZBTFNFfQoKbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkodGlkeWNlbnN1cykKbGlicmFyeShzZikKbGlicmFyeShsdWJyaWRhdGUpCmxpYnJhcnkodGlncmlzKQpsaWJyYXJ5KG1hcHZpZXcpCmxpYnJhcnkoc2ZkZXApCmxpYnJhcnkoc3BkZXApCmxpYnJhcnkoY2FyZXQpCmxpYnJhcnkoa2FibGVFeHRyYSkKbGlicmFyeShrbml0cikKbGlicmFyeShoZXJlKQpsaWJyYXJ5KHZpcmlkaXMpCmxpYnJhcnkoRk5OKQpsaWJyYXJ5KHN0YXRzKQpsaWJyYXJ5KGZhY3RvZXh0cmEpCmxpYnJhcnkocGxvdFJPQykKbGlicmFyeShjbGFzc0ludCkKbGlicmFyeShyYXN0ZXIpCmxpYnJhcnkoc3BhdHN0YXQuZXhwbG9yZSkKbGlicmFyeShsZWFmbGV0KQpsaWJyYXJ5KHBhdGNod29yaykKCm9wdGlvbnModGlncmlzX2NsYXNzID0gInNmIikKc291cmNlKCJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vdXJiYW5TcGF0aWFsL1B1YmxpYy1Qb2xpY3ktQW5hbHl0aWNzLUxhbmRpbmcvbWFzdGVyL2Z1bmN0aW9ucy5yIikKdGlkeWNlbnN1czo6Y2Vuc3VzX2FwaV9rZXkoImU3OWYzNzA2YjZkNjEyNDk5NjhjNmNlODg3OTRmNmY1NTZlNWJmM2QiLCBvdmVyd3JpdGUgPSBUUlVFKQoKYGBgCgojIERhdGEKCldlIHVzZWQgdGhlIFtFTVMgZGF0YXNldF0oaHR0cHM6Ly9kYXRhLmNpbmNpbm5hdGktb2guZ292L1NhZmV0eS9DaW5jaW5uYXRpLUZpcmUtSW5jaWRlbnRzLUNBRC1pbmNsdWRpbmctRU1TLUFMUy1CTC92bnN6LWEzd3ApIHRoYXQgaW5jbHVkZXMgYWxsIHJlc3BvbnNlcyB0byBoZXJvaW4tcmVsYXRlZCBvdmVyZG9zZSBpbmNpZGVudHMgZnJvbSB0aGUgQ2luY2lubmF0aSBGaXJlIERlcGFydG1lbnQgYmV0d2VlbiAyMDE2IGFuZCAyMDIwLgpFYWNoIGluY2lkZW50IHJlY29yZCBjb250YWlucyBpbmNpZGVudCBsb2NhdGlvbiBpbmZvcm1hdGlvbiwgaW5jbHVkaW5nIGdlb3NwYXRpYWwgaW5mb3JtYXRpb24sIGxvY2F0aW9uIGFkZHJlc3MsIGEgY2xhc3NpZmljYXRpb24gb2YgYSBuZWlnaGJvcmhvb2QgaW4gdGhlIGNpdHksIHRpbWUgb2YgdGhlIGluY2lkZW50LCBhbmQgZGlzcG9zaXRpb24gb2YgaW5jaWRlbnQgcmVzcG9uc2UuCkRhdGEgcmVjb3JkcyByZWdhcmRpbmcgaW5jaWRlbnRzIG91dHNpZGUgb2YgdGhlIHN0dWR5IGFyZWEsIHdpdGhvdXQgZ2Vvc3BhdGlhbCBpbmZvcm1hdGlvbiwgYW5kIHdpdGggdW5hc3NvY2lhdGVkIGRpc3Bvc2l0aW9uIGNvZGVzIHdlcmUgZXhjbHVkZWQgZnJvbSB0aGlzIHN0dWR5LgpSZWNvcmRzIGZyb20gY2FuY2VsZWQgY2FsbHMgb3IgZmFsc2UgYWxhcm1zIGFuZCBkdXBsaWNhdGVkIHJlY29yZHMgd2VyZSBhbHNvIGV4Y2x1ZGVkLgoKYGBge3IgaGVyb2luIGRhdGEsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGNsYXNzLnNvdXJjZT0nZm9sZC1zaG93JywgcmVzdWx0cz0naGlkZSd9CgpoZXJvaW4gPC0gcmVhZC5jc3YoaGVyZSgiZGF0YSIsICJwdWJsaWMiLCAiQ2luY2lubmF0aV9GaXJlX0luY2lkZW50c19fQ0FEX19faW5jbHVkaW5nX0VNU19fQUxTX0JMU18uY3N2IikpCmNpbmNpbm5hdGkgPC0gc3RfcmVhZChoZXJlKCJkYXRhIiwgInB1YmxpYyIsICJDaW5jaW5uYXRpX0NpdHlfQm91bmRhcnkuZ2VvanNvbiIpKSAlPiUgc3RfdHJhbnNmb3JtKCJFUFNHOjM3MzUiKQoKaGVyb2luIDwtIGhlcm9pbiAlPiUgCiAgZmlsdGVyKENGRF9JTkNJREVOVF9UWVBFX0dST1VQICE9ICJOT04tUFJPVE9DT0wgUFJPQkxFTSBUWVBFUyIpICU+JSAKICBmaWx0ZXIoaXMubmEoTEFUSVRVREVfWCkgPT0gRkFMU0UgJiBpcy5uYShMT05HSVRVREVfWCkgPT0gRkFMU0UpICU+JSAgIyByZW1vdmUgaW5jaWRlbnRzIHdpdGhvdXQgc3BhdGlhbCBpbmZvCiAgZmlsdGVyKGlzLm5hKERJU1BPU0lUSU9OX1RFWFQpID09IEZBTFNFICAgICAgICAgIyByZW1vdmUgcmVjb3JkIHdpdGggdW4tYXNzb2NpYXRlZCBkaXNwb3NpdGlvbiBjb2RlcwogICAgICAgICAmIENGRF9JTkNJREVOVF9UWVBFX0dST1VQICE9IkNOOiBDQU5DRUwiICMgcmVtb3ZlIHJlY29yZHMgZnJvbSBjYW5jZWxlZCBjYWxscywgZmFsc2UgYWxhcm0sIGFuZCBkdXBsaWNhdGlvbgogICAgICAgICAmIENGRF9JTkNJREVOVF9UWVBFX0dST1VQICE9IkNBTkNFTCBJTkNJREVOVCIgCiAgICAgICAgICYgQ0ZEX0lOQ0lERU5UX1RZUEVfR1JPVVAgIT0iQ046IENBTkNFTCxERUY6IERFRkFVTFQiCiAgICAgICAgICYgQ0ZEX0lOQ0lERU5UX1RZUEVfR1JPVVAgIT0iQ046IENBTkNFTCxFTVNGOiBGQUxTRSIKICAgICAgICAgJiBDRkRfSU5DSURFTlRfVFlQRV9HUk9VUCAhPSJDTjogQ0FOQ0VMLERVUEY6IERVUExJQ0FURSIKICAgICAgICAgJiBDRkRfSU5DSURFTlRfVFlQRV9HUk9VUCAhPSJDTjogQ0FOQ0VMLEZBTEE6IEZJUkUgRkFMU0UgQUMiCiAgICAgICAgICYgQ0ZEX0lOQ0lERU5UX1RZUEVfR1JPVVAgIT0iQ046IENBTkNFTCxNRUREOiBNVCBESVNSRUdBUkRFIgogICAgICAgICAmIENGRF9JTkNJREVOVF9UWVBFX0dST1VQICE9IkRVUEY6IERVUExJQ0FURSIKICAgICAgICAgJiBDRkRfSU5DSURFTlRfVFlQRV9HUk9VUCAhPSJEVVBMSUNBVEUgSU5DSURFTlQiCiAgICAgICAgICYgQ0ZEX0lOQ0lERU5UX1RZUEVfR1JPVVAgIT0iTUFMOiBTWVNURU0gTUFMRlVOQ1RJT04iKSAlPiUgCiAgbXV0YXRlKHRpbWUgPSBtZHlfaG1zKENSRUFURV9USU1FX0lOQ0lERU5ULCB0eiA9ICJVVEMiKSwgIAogICAgICAgICB5ZWFyX2NvbHVtbiA9IHllYXIodGltZSkpICU+JSAKICBzdF9hc19zZiguLCBjb29yZHMgPSBjKCJMT05HSVRVREVfWCIsICJMQVRJVFVERV9YIiksIGNycyA9IDQzMjYpICU+JSAKICBzdF90cmFuc2Zvcm0oIkVQU0c6MzczNSIpICU+JSAKICBzdF9pbnRlcnNlY3Rpb24oY2luY2lubmF0aSAlPiUgZHBseXI6OnNlbGVjdChPQkpFQ1RJRCksLikgJT4lICAgIyByZW1vdmUgaW5jaWRlbnRzIG91dHNpZGUgb2Ygc3R1ZHkgYXJlYQogIGZpbHRlcih5ZWFyX2NvbHVtbiAlaW4lIGMoMjAxNiwgMjAxNywgMjAxOCwgMjAxOSwgMjAyMCkpCgoKYGBgCgpJbiBhZGRpdGlvbiwgd2UgdXNlZCBbSG9tZWxhbmQgSW5mcmFzdHJ1Y3R1cmUgRm91bmRhdGlvbi1MZXZlbCBEYXRhIChISUZMRCldKGh0dHBzOi8vaGlmbGQtZ2VvcGxhdGZvcm0ub3BlbmRhdGEuYXJjZ2lzLmNvbS9tYXBzL2dlb3BsYXRmb3JtOjpob3NwaXRhbHMvYWJvdXQpIGFuZCBbU3Vic3RhbmNlIEFidXNlIGFuZCBNZW50YWwgSGVhbHRoIFNlcnZpY2VzIEFkbWluaXN0cmF0aW9uIChTQU1IU0EpIGRhdGFdKGh0dHBzOi8vZHB0Mi5zYW1oc2EuZ292L3RyZWF0bWVudC9kaXJlY3RvcnkuYXNweCkgZm9yIDIwMTUtLTIwMjAgdG8gb2J0YWluIGluZm9ybWF0aW9uIGFib3V0IGF2YWlsYWJsZSBoZWFsdGhjYXJlIHNlcnZpY2VzLCBzdWNoIGFzIGhvc3BpdGFscywgb3Bpb2lkIHRyZWF0bWVudCBwcm9ncmFtcywgYW5kIGJ1cHJlbm9ycGhpbmUgcHJlc2NyaWJpbmcgcGh5c2ljaWFucywgZXRjLgoKV2UgZmlsdGVyZWQgZGF0YSBvbmx5IGZvciBDaW5jaW5uYXRpIGluIHRoZSBISUZMRCBkYXRhc2V0LgpUaGUgU0FNSFNBIGRhdGEgd2VyZSBnZW9jb2RlZCBtYW51YWxseSB0byBnZXQgaXRzIGxvY2F0aW9uIGluZm9ybWF0aW9uIGZpcnN0IGJlZm9yZSByZWFkaW5nIGludG8gUi4KCmBgYHtyIG1lZGljYWwgcmVzb3VyY2UgZGF0YSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgY2xhc3Muc291cmNlPSdmb2xkLXNob3cnLCByZXN1bHRzPSdoaWRlJ30KCmhvc3BpdGFscyA8LSBzdF9yZWFkKGhlcmUoImRhdGEiLCJwdWJsaWMiLCAiSG9zcGl0YWxzLmdlb2pzb24iKSkKaG9zcGl0YWxzIDwtIGhvc3BpdGFscyAlPiUgCiAgZmlsdGVyKFNUQVRFID09ICJPSCIgJiBDSVRZID09ICJDSU5DSU5OQVRJIikgJT4lIAogIHN0X3RyYW5zZm9ybSgiRVBTRzozNzM1IikgIyBzZWxlY3QgdGhvc2Ugb25seSBpbiBDaW5jaW5uYXRpCgoKcmVoYWIgPC0gcmVhZC5jc3YoaGVyZSgiZGF0YSIsICJwdWJsaWMiLCAicmVoYWJpbGl0YXRpb24uY3N2IikpCnJlaGFiIDwtIHJlaGFiICU+JSAKICBmaWx0ZXIoaXMubmEoTGF0aXR1ZGUpID09IEZBTFNFICYgaXMubmEoTG9uZ2l0dWRlKSA9PSBGQUxTRSkgJT4lCiAgc3RfYXNfc2YoLiwgY29vcmRzID0gYygiTG9uZ2l0dWRlIiwgIkxhdGl0dWRlIiksIGNycyA9IDQzMjYpICU+JSAKICBzdF90cmFuc2Zvcm0oIkVQU0c6MzczNSIpCgpgYGAKCkZ1cnRoZXJtb3JlLCB3ZSBpbmNsdWRlZCB0aGUgW2NyaW1lIHJhdGUgZGF0YV0oaHR0cHM6Ly9kYXRhLmNpbmNpbm5hdGktb2guZ292L3NhZmV0eS9QREktUG9saWNlLURhdGEtSW5pdGlhdGl2ZS1DcmltZS1JbmNpZGVudHMvazU5ZS0ycHZmKSBmcm9tIHRoZSBDaW5jaW5uYXRpIFBvbGljZSBEZXBhcnRtZW50IGJldHdlZW4gMjAxNiBhbmQgMjAyMC4KU3BlY2lmaWMgY3JpbWVzIHdlcmUgc2VsZWN0ZWQgZm9yIGluY2x1c2lvbiBpbiBhbmFseXNlcyBiYXNlZCBvbiBoYXZpbmcgYmVlbiBpZGVudGlmaWVkIGluIHRoZSBsaXRlcmF0dXJlIGFzIGEgc3BhdGlhbCByaXNrIGZhY3RvciBmb3IgZHJ1ZyBvdmVyZG9zZSBvciBzdWJzdGFuY2UgdXNlIGJlaGF2aW9yIG9yIGZvciBoYXZpbmcgYSB0aGVvcmV0aWNhbCByZWxhdGlvbnNoaXAgd2l0aCBkcnVnIG92ZXJkb3NlLgoKYGBge3IgY3JpbWUgZGF0YSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgY2xhc3Muc291cmNlPSdmb2xkLXNob3cnLCByZXN1bHRzPSdoaWRlJ30KCmNyaW1lIDwtIHJlYWQuY3N2KGhlcmUoImRhdGEiLCAicHJpdmF0ZSIsICJQRElfX1BvbGljZV9EYXRhX0luaXRpYXRpdmVfX0NyaW1lX0luY2lkZW50cy5jc3YiKSkKY3JpbWVfbGlzdCA8LSBjKCJBR0dSQVZBVEVEIEFTU0FVTFQiLCAiQlVSR0xBUlkiLCAiQlJFQUtJTkcgQU5EIEVOVEVSSU5HIiwgIlJPQkJFUlkiLCAiRE9NRVNUSUMgVklPTEVOQ0UiLCAiTVVSREVSCSIsICJSQVBFIiwgIlRIRUZUIikKY3JpbWUgPC0gY3JpbWUgJT4lIAogIGZpbHRlcihPRkZFTlNFICVpbiUgY3JpbWVfbGlzdCkgJT4lIAogIGZpbHRlcihpcy5uYShMQVRJVFVERV9YKSA9PSBGQUxTRSAmIGlzLm5hKExPTkdJVFVERV9YKSA9PSBGQUxTRSkgJT4lCiAgc3RfYXNfc2YoLiwgY29vcmRzID0gYygiTE9OR0lUVURFX1giLCAiTEFUSVRVREVfWCIpLCBjcnMgPSA0MzI2KSAlPiUgCiAgc3RfdHJhbnNmb3JtKCJFUFNHOjM3MzUiKSAlPiUgCiAgbXV0YXRlKHRpbWUgPSBtZHlfaG1zKERBVEVfUkVQT1JURUQsIHR6ID0gIlVUQyIpLCAgCiAgICAgICAgIHllYXIgPSB5ZWFyKHRpbWUpKSAlPiUgCiAgZmlsdGVyKHllYXIgJWluJSBjKDIwMTYsIDIwMTcsIDIwMTgsIDIwMTksIDIwMjApKQoKYGBgCgpGcm9tIHRoZSBzYW1lIGRhdGEgc291cmNlLCB3ZSBpbmNsdWRlZCB0aGUgWzMxMSBub24tZW1lcmdlbmN5IHNlcnZpY2UgcmVxdWVzdHNdKGh0dHBzOi8vZGF0YS5jaW5jaW5uYXRpLW9oLmdvdi9UaHJpdmluZy1OZWlnaGJvcmhvb2RzL0NpbmNpbm5hdGktMzExLU5vbi1FbWVyZ2VuY3ktU2VydmljZS1SZXF1ZXN0cy80Y2poLWJtOGIpIGZyb20gMjAxNiB0byAyMDIwLgoKYGBge3IgMzExIGNvbXBsYWludHMgZGF0YSwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRSwgY2xhc3Muc291cmNlPSdmb2xkLXNob3cnLCByZXN1bHRzPSdoaWRlJ30KCmNvbXBsYWludHMgPC0gcmVhZF9jc3YoaGVyZSgiZGF0YSIsICJwcml2YXRlIiwgIkNpbmNpbm5hdGlfMzExX19Ob24tRW1lcmdlbmN5X19TZXJ2aWNlX1JlcXVlc3RzLmNzdiIpKQpjb21wbGFpbnRzIDwtIGNvbXBsYWludHMgJT4lIAogIGZpbHRlcihpcy5uYShMQVRJVFVERSkgPT0gRkFMU0UgJiBpcy5uYShMT05HSVRVREUpID09IEZBTFNFKSAlPiUKICBzdF9hc19zZiguLCBjb29yZHMgPSBjKCJMT05HSVRVREUiLCAiTEFUSVRVREUiKSwgY3JzID0gNDMyNikgJT4lIAogIHN0X3RyYW5zZm9ybSgiRVBTRzozNzM1IikgJT4lIAogIG11dGF0ZSh0aW1lID0gbWR5X2htcyhSRVFVRVNURURfREFURSwgdHogPSAiVVRDIiksICAKICAgICAgICAgeWVhciA9IHllYXIodGltZSkpICU+JSAKICBmaWx0ZXIoeWVhciAlaW4lIGMoMjAxNiwgMjAxNywgMjAxOCwgMjAxOSwgMjAyMCkpCgpgYGAKClRoZSBjb25uZWN0aW9uIGJldHdlZW4gc3Vic3RhbmNlIHVzZSBhbmQgYnVpbHQgZW52aXJvbm1lbnQgdmFyaWFibGVzIChhY2Nlc3MgdG8gcHVibGljIHJlc3Ryb29tcywgYWNjZXNzIHRvIHBoYXJtYWNpZXMsIGFuZCBkcml2aW5nIGRpc3RhbmNlIHRvIHNlcnZpY2VzLCBkZWZpbmVkIGluIG91ciBzdHVkeSBhcyBmYXN0LWZvb2QgcmVzdGF1cmFudHMsIGdhcyBzdGF0aW9ucyBhbmQgcHVibGljIHBhcmtzKSBhcmUgYWxzbyBpbXBvcnRhbnQuClB1YmxpYyByZXN0cm9vbXMgYXJlIGFzc29jaWF0ZWQgd2l0aCBwZW9wbGUgd2hvIGluamVjdCBkcnVncyAoUFdJRCkgYmVjYXVzZSBtYW55IHBlb3BsZSB1c2UgZHJ1Z3MgaW4gdGhlc2Ugc3BhY2VzLlBoYXJtYWNpZXMgcmVwcmVzZW50IGFuIGltcG9ydGFudCBhY2Nlc3MgdmFyaWFibGUgZm9yIHNldmVyYWwgcmVhc29ucy4gCkR1cmluZyB0aGUgaW5pdGlhbCB3YXZlIG9mIHRoZSBvdmVyZG9zZSBjcmlzaXMsIHBoYXJtYWNldXRpY2FsIHByZXNjcmlwdGlvbnMsIGVpdGhlciBsZWdpdGltYXRlLCBkaXZlcnRlZCwgb3IgcG90ZW50aWFsbHkgaW5hcHByb3ByaWF0ZSwgZmVkIHRoZSBvcGlvaWQgc3VwcGx5LiAKSW4gYWRkaXRpb24sIG5hbG94b25lIChhIG1lZGljYXRpb24gdG8gcmV2ZXJzZSBhbiBvcGlvaWQgb3ZlcmRvc2UpIGlzIGF2YWlsYWJsZSBhdCBwaGFybWFjaWVzIHdpdGhvdXQgYSBwcmVzY3JpcHRpb24sIGFsdGhvdWdoIHRoaXMgcHJvdmlzaW9uIG1heSB2YXJ5IGJ5IG5laWdoYm9yaG9vZCBzb2Npby1kZW1vZ3JhcGhpYyBsZXZlbHMuIApBbGwgb2YgdGhlc2UgZGF0YSB3ZXJlIHF1ZXJpZWQgZnJvbSBPcGVuU3RyZWV0TWFwIGFuZCBwcmVwcm9jZXNzZWQgaW4gUUdJUyAoZml4aW5nIGdlb21ldHJ5LCBjcmVhdGluZyBzcGF0aWFsIGluZGljZXMsIGFuZCB1bmlvbmluZyBwb2ludCB3aXRoIHBvbHlnb24gZGF0YSBwaWVjZXMpCgpgYGB7ciBidWlsdCBlbnZpcm9ubWVudCBkYXRhLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFLCByZXN1bHRzPSdoaWRlJ30KIyBnYXMgc3RhdGlvbgpmdWVsIDwtIHN0X3JlYWQoaGVyZSgiZGF0YSIsICJwdWJsaWMiLCAiZnVlbC5nZW9qc29uIikpICU+JSBzdF90cmFuc2Zvcm0oIkVQU0c6MzczNSIpCgojIGZhc3QgZm9vZCByZXN0YXVyYW50IApmYXN0Zm9vZCA8LSBzdF9yZWFkKGhlcmUoImRhdGEiLCAicHVibGljIiwgImZhc3Rmb29kLmdlb2pzb24iKSkgJT4lIHN0X2NlbnRyb2lkKCkgJT4lIHN0X3RyYW5zZm9ybSgiRVBTRzozNzM1IikKCiMgcHVibGljIHBhcmtzCnBhcmtzIDwtIHN0X3JlYWQoaGVyZSgiZGF0YSIsICJwdWJsaWMiLCAicGFya3MuZ2VvanNvbiIpKSAlPiUgc3RfdHJhbnNmb3JtKCJFUFNHOjM3MzUiKSAlPiUgc3RfY2VudHJvaWQoKQoKIyBwaGFybWFjeQpwaGFybWFjeSA8LSBzdF9yZWFkKGhlcmUoImRhdGEiLCJwdWJsaWMiLCAicGhhcm1hY3kuZ2VvanNvbiIpKSAlPiUgc3RfdHJhbnNmb3JtKCJFUFNHOjM3MzUiKSAlPiUgc3RfY2VudHJvaWQoKQpgYGAKClRvIGlkZW50aWZ5IGRlbW9ncmFwaGljIGFuZCBzb2Npb2dyYXBoaWMgY2hhcmFjdGVyaXN0aWNzIGNvcnJlc3BvbmRpbmcgdG8gdGhlIEVNUyBkYXRhc2V0LCB3ZSB1dGlsaXplZCB0aGUgQUNTIGRhdGEgZm9yIDIwMTYgLS0gMjAyMC4KV2UgZG93bmxvYWRlZCBkZW1vZ3JhcGhpYyBpbmZvcm1hdGlvbiBmb3IgZWFjaCBjZW5zdXMgdHJhY3QsIGluY2x1ZGluZyB0b3RhbCBwb3B1bGF0aW9uLCBnZW5kZXIsIGFkdWx0IHBvcHVsYXRpb24gc2l6ZSwgYW5kIHJhY2UvZXRobmljaXR5LgpUaGUgQUNTIGRhdGEgYWxzbyBpbmNsdWRlZCBzb2Npb2Vjb25vbWljIGNoYXJhY3RlcmlzdGljcyBvZiBlYWNoIGNlbnN1cyB0cmFjdCwgc3VjaCBhcyBlZHVjYXRpb24sIGluY29tZSwgYW5kIHBvdmVydHkuCkZvciBlYWNoIGNlbnN1cyB0cmFjdCwgd2UgY29tcHV0ZWQgdGhlIHBvcHVsYXRpb24gYWdlZCBiZXR3ZWVuIDI1IGFuZCA1NCAoc2luY2UgdGhpcyBpcyB0aGUgYWdlIGdyb3VwIG1vc3QgdnVsbmVyYWJsZSB0byBvcGlvaWQgb3ZlcmRvc2UpLCBnZW5kZXIgcmF0aW8sIG1pbm9yaXR5IHBvcHVsdGlvbiByYXRpbywgcGVyY2VudGFnZSBwb3ZlcnR5LCBhbmQgcGVyY2VudGFnZSBvZiBwb3B1bGF0aW9uIHdpdGggYSBiYWNoZWxvcidzIGRlZ3JlZS4KTm90ZSB0aGF0IHRoZSBib3VuZGFyeSBvZiBDaW5jaW5uYXRpIGRvZXMgbm90IGZ1bGx5IGNvbnRhaW4gc2V2ZXJhbCBjZW5zdXMgdHJhY3RzIGFuZCB0aGVyZWZvcmUgd2UgaGF2ZSB0cmllZCBvdXIgYmVzdCB0byBpbmNsdWRlIHRyYWN0cyB3aXRoaW4gSGFtaWx0b24gY291bnR5IHRoYXQgbW9zdGx5IGZhbGwgd2l0aGluIENpbmNpbm5hdGkuCgpgYGB7ciBjZW5zdXMgZGF0YSwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRSwgcmVzdWx0cz0naGlkZSd9CgpjaW5jaW5uYXRpMjAgPC0gZ2V0X2FjcyhnZW9ncmFwaHkgPSAidHJhY3QiLCAKICAgICAgICAgIHZhcmlhYmxlcyA9IGMoCiAgICAgICAgICAgICJCMDEwMDFfMDAxRSIsICMgdG90YWwgcG9wdWxhdGlvbgogICAgICAgICAgICAiQjAxMDAxXzAwMkUiLCAjIHRvdGFsIG1hbGUKICAgICAgICAgICAgIkIwMTAwMV8wMTFFIiwgIyBtYWxlIDI1LTI5CiAgICAgICAgICAgICJCMDEwMDFfMDEyRSIsCiAgICAgICAgICAgICJCMDEwMDFfMDEzRSIsCiAgICAgICAgICAgICJCMDEwMDFfMDE0RSIsCiAgICAgICAgICAgICJCMDEwMDFfMDE1RSIsCiAgICAgICAgICAgICJCMDEwMDFfMDE2RSIsICMgbWFsZSA1MC01NAogICAgICAgICAgICAiQjAxMDAxXzAyNkUiLCAjIHRvdGFsIGZlbWFsZQogICAgICAgICAgICAiQjAxMDAxXzAzNUUiLCAjIGZlbWFsZSAyNS0yOQogICAgICAgICAgICAiQjAxMDAxXzAzNkUiLAogICAgICAgICAgICAiQjAxMDAxXzAzN0UiLAogICAgICAgICAgICAiQjAxMDAxXzAzOEUiLAogICAgICAgICAgICAiQjAxMDAxXzAzOUUiLAogICAgICAgICAgICAiQjAxMDAxXzA0MEUiLCAjIGZlbWFsZSA1MC01NAogICAgICAgICAgICAiQjAyMDAxXzAwMkUiLCAjIHdoaXRlIHBvcHVsYXRpb24KICAgICAgICAgICAgIkIwMjAwMV8wMDNFIiwgIyBibGFjayBwb3B1bGF0aW9uCiAgICAgICAgICAgICJCMDIwMDFfMDA1RSIsICMgYXNpYW4gcG9wdWxhdGlvbgogICAgICAgICAgICAiQjAzMDAyXzAxMkUiLCAjIGxhdGlueCBwb3B1bGF0aW9uCiAgICAgICAgICAgICJCMTkwMTNfMDAxRSIsICMgbWVkaWFuIGhvdXNlaG9sZCBpbmNvbWUKICAgICAgICAgICAgIkIwNjAxMl8wMDJFIiwgIyBwb3ZlcnR5CiAgICAgICAgICAgICJCMDYwMDlfMDA1RSIgIyBiYWNoZWxvcgogICAgICAgICAgICApLCAKICAgICAgICAgIHllYXI9MjAyMCwgc3RhdGU9Ik9IIiwgY291bnR5PSJIYW1pbHRvbiIsIAogICAgICAgICAgZ2VvbWV0cnk9VFJVRSwgb3V0cHV0PSJ3aWRlIikgJT4lCiAgc3RfdHJhbnNmb3JtKCJFUFNHOjM3MzUiKQoKY2luY2lubmF0aV90cmFjdHMgPC0gc3RfcmVhZChoZXJlKCJkYXRhIiwgInB1YmxpYyIsICJjaW5jaW5uYXRpX3RyYWN0cy5nZW9qc29uIikpICU+JSBzdF90cmFuc2Zvcm0oIkVQU0c6MzczNSIpCgpjaW5jaW5uYXRpX3RyYWN0cyA8LSBjaW5jaW5uYXRpX3RyYWN0cyAlPiUgc3RfZHJvcF9nZW9tZXRyeSgpICU+JSBkcGx5cjo6c2VsZWN0KEdFT0lEKSAlPiUgYXMubGlzdChHRU9JRCkKCmNpbmNpbm5hdGkyMCA8LSBjaW5jaW5uYXRpMjAgJT4lIAogIGZpbHRlcihHRU9JRCAlaW4lIGNpbmNpbm5hdGlfdHJhY3RzJEdFT0lEKSAlPiUgCiAgcmVuYW1lKFRvdGFscG9wID0gQjAxMDAxXzAwMUUsCiAgICAgICAgIE1lZEhISW5jID0gQjE5MDEzXzAwMUUpICU+JSAKICBtdXRhdGUocG9wMjVfNTQgPSBCMDEwMDFfMDExRSArIEIwMTAwMV8wMTJFICsgQjAxMDAxXzAxM0UgKyBCMDEwMDFfMDE0RSArIEIwMTAwMV8wMTVFICsgQjAxMDAxXzAxNkUgKyBCMDEwMDFfMDM1RSArIEIwMTAwMV8wMzZFICsgQjAxMDAxXzAzN0UgKyBCMDEwMDFfMDM4RSsgQjAxMDAxXzAzOUUgKyBCMDEwMDFfMDQwRSkgJT4lIAogIG11dGF0ZShNRl9yYXRpbyA9IEIwMTAwMV8wMDJFIC8gQjAxMDAxXzAyNkUpICU+JSAKICBtdXRhdGUocmFjZV9yYXRpbyA9IEIwMjAwMV8wMDJFIC8gKEIwMjAwMV8wMDNFICsgQjAyMDAxXzAwNUUgKyBCMDMwMDJfMDEyRSkpICU+JSAKICBtdXRhdGUocGN0UG92ZXJ0eSA9IEIwNjAxMl8wMDJFIC8gVG90YWxwb3ApICU+JSAKICBtdXRhdGUocGN0QmFjaGVsb3IgPSBCMDYwMDlfMDA1RSAvIFRvdGFscG9wKSAlPiUgCiAgZHBseXI6OnNlbGVjdChwb3AyNV81NCwgVG90YWxwb3AsIE1lZEhISW5jLCBHRU9JRCwgTUZfcmF0aW8sIHJhY2VfcmF0aW8sIHBjdFBvdmVydHksIHBjdEJhY2hlbG9yKQoKCmBgYAoKVGhlIHRhYmxlIGJlbG93IHN1bW1hcml6ZXMgYWxsIG9mIG91ciBwcmVkaWN0b3IgdmFyaWFibGVzIGluIHRoaXMgc3R1ZHk6Cgp8ICAgICAgICAgICAgIFZhcmlhYmxlIE5hbWUgICAgICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgICAgICAgU291cmNlICAgICAgICAgICAgICAgICAgICAgICB8Cnw6LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLTogICB8Oi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS06ICAgICAgICAgICAgICAgICAgIHwKfCAgICAgICAgICAqKk1lZGljYWwgU2VydmljZSoqICAgICAgICAgIHwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfAp8ICAgICAgICAgICAgICAgSG9zcGl0YWwgICAgICAgICAgICAgICAgfCAgICAgICBIb21lbGFuZCBJbmZyYXN0cnVjdHVyZSBGb3VuZGF0aW9uLUxldmVsIERhdGEgICB8CnwgICAgICAgICBSZWhhYmlsaXRhdGlvbiBDZW50ZXIgICAgICAgICB8IFN1YnN0YW5jZSBBYnVzZSBhbmQgTWVudGFsIEhlYWx0aCBTZXJ2aWNlcyBBZG1pbmlzdHJhdGlvbiB8CnwgICAgICAgICAqKkJ1aWx0IEVudmlyb25tZW50KiogICAgICAgICB8ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwKfCAgICAgICAgICAgICAgQ3JpbWUgUmF0ZSAgICAgICAgICAgICAgIHwgICAgICAgICAgICAgICAgICAgT3BlbiBEYXRhIENpbmNpbm5hdGkgICAgICAgICAgICAgICAgfAp8ICAgICAgICAgICAgMzExIGNvbXBsYWludHMgICAgICAgICAgICAgfCAgICAgICAgICAgICAgICAgICBPcGVuIERhdGEgQ2luY2lubmF0aSAgICAgICAgICAgICAgICB8CnwgICAgICAgICAgICAgIEdhcyBzdGF0aW9uICAgICAgICAgICAgICB8ICAgICAgICAgICAgICAgICAgICAgICAgICAgIE9TTSAgICAgICAgICAgICAgICAgICAgICAgIHwKfCAgICAgICAgICAgICBQdWJsaWMgcGFya3MgICAgICAgICAgICAgIHwgICAgICAgICAgICAgICAgICAgICAgICAgICAgT1NNICAgICAgICAgICAgICAgICAgICAgICAgfAp8ICAgICAgICAgRmFzdCBmb29kIHJlc3RhdXJhbnRzICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgICAgICAgICBPU00gICAgICAgICAgICAgICAgICAgICAgICB8CnwgICAgICAgICAgICAgIFBoYXJtYWNpZXMgICAgICAgICAgICAgICB8ICAgICAgICAgICAgICAgICAgICAgICAgICAgIE9TTSAgICAgICAgICAgICAgICAgICAgICAgIHwKfCAqKlNvY2lvLWRlbW9ncmFwaGljIENoYXJhY3RlcmlzdGljcyoqIHwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfAp8ICAgUG9wdWxhdGlvbiBhZ2VkIGJldHdlZW4gMjUgYW5kIDU0ICAgfCAgICAgICAgICAgICAgICAgICAgICAgICAgICBBQ1MgICAgICAgICAgICAgICAgICAgICAgICB8CnwgICAgICAgICAgICAgR2VuZGVyIHJhdGlvICAgICAgICAgICAgICB8ICAgICAgICAgICAgICAgICAgICAgICAgICAgIEFDUyAgICAgICAgICAgICAgICAgICAgICAgIHwKfCAgICAgICBXaGl0ZSBvdmVyIG1pbm9yaXR5IHJhdGlvICAgICAgIHwgICAgICAgICAgICAgICAgICAgICAgICAgICAgQUNTICAgICAgICAgICAgICAgICAgICAgICAgfAp8ICBQb3B1bGF0aW9uIHdpdGggYSBCYWNoZWxvcidzIGRlZ3JlZSAgfCAgICAgICAgICAgICAgICAgICAgICAgICAgICBBQ1MgICAgICAgICAgICAgICAgICAgICAgICB8CnwgICAgICAgIE1lZGlhbiBob3VzZWhvbGQgaW5jb21lICAgICAgICB8ICAgICAgICAgICAgICAgICAgICAgICAgICAgIEFDUyAgICAgICAgICAgICAgICAgICAgICAgIHwKfCAgICAgUG9wdWxhdGlvbiB1bmRlciBwb3ZlcnR5IGxpbmUgICAgIHwgICAgICAgICAgICAgICAgICAgICAgICAgICAgQUNTICAgICAgICAgICAgICAgICAgICAgICAgfAp8ICAgICAgICBNZWRpYW4gSG91c2Vob2xkIEluY29tZSAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgICAgICAgICBBQ1MgICAgICAgICAgICAgICAgICAgICAgICB8CgoKIyBQb2lzc2lvbiBSZWdyZXNzaW9uCgpBcyBwYXJ0IG9mIG91ciBleHBsb3JhdG9yeSBhbmFseXNpcywgd2UgcmFuIGEgcG9pc3NvbiByZWdyZXNzaW9uIHVzaW5nIG9ubHkgdGhlIG51bWJlciBvZiBjcmltZSBpbmNpZGVudHMgYW5kIHRoZSBudW1iZXIgb2YgMzExIGNvbXBsYWludHMgYXMgb3VyIHByZWRpY3RvciB2YXJpYWJsZXMuIFNpbmNlIGhlcm9pbiBvdmVyZG9zZSBpbmNpZGVudCBpcyBub3QgYSBwaGVub21lbm9uIHRoYXQgdmFyaWVzIGFjcm9zcyBhZG1pbmlzdHJhdGl2ZSB1bml0cywgYnV0IG9uZSB2YXJ5aW5nIHNtb290aGx5IGFjcm9zcyB0aGUgbGFuZHNjYXBlLCB3ZSB3b3VsZCBuZWVkIHRvIGFnZ3JlZ2F0ZSB0aGVzZSBwb2ludC1sZXZlbCBkYXRhIGludG8gYSBsYXR0aWNlIGdyaWQgb2YgY2VsbHMuIFRoZSBjb2RlIGJlbG93IGdlbmVyYXRlcyBhIDUwMCBieSA1MDAgbWV0ZXJzICg4MjAgZmVldCkgZmlzaG5ldCBmb3IgQ2luY2lubmF0aSB0byBhY2hpZXZlIHRoaXMgZ29hbC4gCgpgYGB7ciBjcmVhdGUgZmlzaG5ldCwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0KZmlzaG5ldCA8LSBzdF9tYWtlX2dyaWQoY2luY2lubmF0aSwKICAgICAgICAgICAgICAgY2VsbHNpemUgPSA4MjAsIAogICAgICAgICAgICAgICBzcXVhcmUgPSBUUlVFKSAlPiUKICAuW2NpbmNpbm5hdGldICU+JSAgICAgICAgICAgCiAgc3Rfc2YoKSAlPiUKICBtdXRhdGUodW5pcXVlSUQgPSAxOm4oKSkKCmdncGxvdCgpICsKICBnZW9tX3NmKGRhdGE9ZmlzaG5ldCwgY29sb3I9ImJsYWNrIiwgZmlsbD0iI0ZGRjVFRSIpICsKICBsYWJzKHRpdGxlID0gIkZpc2huZXQgb2YgQ2luY2lubmF0aSIpICsKICB0aGVtZShheGlzLnRleHQueD1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGlja3MgPWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aXRsZS55ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDksZmFjZSA9ICJpdGFsaWMiKSwKICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgZmFjZSA9ICJib2xkIiksCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X3JlY3QoY29sb3VyID0gImdyZXkiLCBmaWxsPU5BLCBsaW5ld2lkdGg9MC44KQogICAgICAgICkKYGBgCgpXZSB3b3VsZCBsaWtlIHRvIGNvbXBhcmUgYWNyb3NzIGJvdGggc3BhY2UgYW5kIHRpbWUsIGFuZCBzbyB3ZSBkaXZpZGUgdGhlIGhlcm9pbiBkYXRhc2V0IGludG8gb25lIHRyYWluaW5nIHNldCBhbmQgdHdvIHRlc3Rpbmcgc2V0cy4gVGhlIHRyYWluaW5nIHNldCBjb250YWlucyBpbmNpZGVudHMgZnJvbSAyMDE2IHRvIDIwMTggd2hpbGUgdGhlIHRlc3Rpbmcgc2V0cyBjb250YWluIGluY2lkZW50cyBmcm9tIDIwMTkgYW5kIDIwMjAgcmVzcGVjdGl2ZWx5LiBTaW5jZSBlYWNoIGluZGl2aWR1YWwgeWVhciBoYXMgd2VsbCBlbm91Z2ggb2JzZXJ2YXRpb25zIG9mIGhlcm9pbi1vdmVyZG9zZSBpbmNpZGVudHMsIHNlcGFyYXRpbmcgdGhlIHRlc3Rpbmcgc2V0IGludG8gMjAxOSBhbmQgMjAyMCBhbGxvd3MgdXMgdG8gdW5kZXJzdGFuZCBob3cgQ09WSUQtMTkgcGxheXMgYSByb2xlIGluIGFmZmVjdGluZyBkcnVnIG92ZXJkb3NlIHJhdGUuIFdlIGFsc28gc2VwYXJhdGUgdGhlIGNyaW1lIGluY2lkZW50IGRhdGEgYW5kIHRoZSAzMTEgY29tcGxhaW50cyBkYXRhIGludG8gcmVzcGVjdGl2ZSB5ZWFyIGdyb3VwcyBzbyB0aGF0IHdlIGNvdWxkIGFnZ3JlZ2F0ZSB0aGVtIGludG8gdGhlIGZpc2huZXQuIAoKYGBge3Igc3BsaXR0aW5nIHRyYWluIGFuZCB0ZXN0LCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQoKaGVyb2luVHJhaW4gPC0gaGVyb2luICU+JSAKICBmaWx0ZXIoeWVhcl9jb2x1bW4gJWluJSBjKDIwMTYsIDIwMTcsIDIwMTgpKQpoZXJvaW4xOSA8LSBoZXJvaW4gJT4lIAogIGZpbHRlcih5ZWFyX2NvbHVtbiA9PSAyMDE5KQpoZXJvaW4yMCA8LSBoZXJvaW4gJT4lIAogIGZpbHRlcih5ZWFyX2NvbHVtbiA9PSAyMDIwKQoKY3JpbWVUcmFpbiA8LSBjcmltZSAlPiUgCiAgZmlsdGVyKHllYXIgJWluJSBjKDIwMTYsIDIwMTcsIDIwMTgpKQpjcmltZTE5IDwtIGNyaW1lICU+JSAKICBmaWx0ZXIoeWVhciA9PSAyMDE5KQpjcmltZTIwIDwtIGNyaW1lICU+JSAKICBmaWx0ZXIoeWVhciA9PSAyMDIwKQoKY29tcGxhaW50VHJhaW4gPC0gY29tcGxhaW50cyAlPiUgCiAgZmlsdGVyKHllYXIgJWluJSBjKDIwMTYsIDIwMTcsIDIwMTgpKQpjb21wbGFpbnQxOSA8LSBjb21wbGFpbnRzICU+JSAKICBmaWx0ZXIoeWVhciA9PSAyMDE5KQpjb21wbGFpbnQyMCA8LSBjb21wbGFpbnRzJT4lIAogIGZpbHRlcih5ZWFyID09IDIwMjApCgpgYGAKClRoZSBmb2xsb3dpbmcgdHdvIGNvZGUgY2h1bmtzIHBlcmZvcm0gYWxsIHRoZSBuZWNlc3NhcnkgdGFza3MgdG8gcHJlcGFyZSBvdXIgdHJhaW5pbmcgYW5kIHRlc3RpbmcgZGF0YXNldHMgYnkgYWdncmVnYXRpbmcgaGVyb2luLWluY2lkZW50cywgY3JpbWUgaW5jaWRlbnRzLCBhbmQgMzExIGNvbXBsYWludHMgYWxsIGludG8gdGhlIHNhbWUgc3BhdGlhbCB1bml0LCBpbiB0aGlzIGNhc2UsIHRoZSBmaXNobmV0LiAKCmBgYHtyIHByZXBhcmluZyB0cmFpbmluZyBzZXQsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9CgojIGFkZCBoZXJvaW4gaW4gbmV0Cmhlcm9pblRyYWluX25ldCA8LSAKICBkcGx5cjo6c2VsZWN0KGhlcm9pblRyYWluKSAlPiUgIAogIG11dGF0ZShjb3VudEhlcm9pbiA9IDEpICU+JSAKICBhZ2dyZWdhdGUoLiwgZmlzaG5ldCwgc3VtKSAlPiUKICBtdXRhdGUoY291bnRIZXJvaW4gPSByZXBsYWNlX25hKGNvdW50SGVyb2luLCAwKSwKICAgICAgICAgdW5pcXVlSUQgPSAxOm4oKSwgCiAgICAgICAgIGN2SUQgPSBzYW1wbGUocm91bmQobnJvdyhmaXNobmV0KSAvIDI0KSwgCiAgICAgICAgICAgICAgICAgICAgICAgc2l6ZT1ucm93KGZpc2huZXQpLCByZXBsYWNlID0gVFJVRSkpCgojIGFkZGluZyBjcmltZSB0byBuZXQKaGVyb2luVHJhaW5fbmV0IDwtIGhlcm9pblRyYWluX25ldCAlPiUgCiAgc3Rfam9pbihjcmltZVRyYWluLCAuLCBqb2luPXN0X3dpdGhpbikgJT4lIAogIHN0X2Ryb3BfZ2VvbWV0cnkoKSAlPiUKICBncm91cF9ieSh1bmlxdWVJRCkgJT4lCiAgc3VtbWFyaXplKENyaW1lY291bnQgPSBuKCkpICU+JQogICAgbGVmdF9qb2luKGhlcm9pblRyYWluX25ldCwgLiApICU+JQogICAgc3Rfc2YoKSAlPiUKICBtdXRhdGUoQ3JpbWVjb3VudCA9IGlmZWxzZShpcy5uYShDcmltZWNvdW50KSwgMCwgQ3JpbWVjb3VudCkpCgojIGFkZCAzMTEgdG8gbmV0Cmhlcm9pblRyYWluX25ldCA8LSBoZXJvaW5UcmFpbl9uZXQgJT4lIAogIHN0X2pvaW4oY29tcGxhaW50VHJhaW4sIC4sIGpvaW49c3Rfd2l0aGluKSAlPiUgCiAgc3RfZHJvcF9nZW9tZXRyeSgpICU+JQogIGdyb3VwX2J5KHVuaXF1ZUlEKSAlPiUKICBzdW1tYXJpemUoQ29tcGxhaW50c2NvdW50ID0gbigpKSAlPiUKICAgIGxlZnRfam9pbihoZXJvaW5UcmFpbl9uZXQsIC4gKSAlPiUKICAgIHN0X3NmKCkgJT4lCiAgbXV0YXRlKENvbXBsYWludHNjb3VudCA9IGlmZWxzZShpcy5uYShDb21wbGFpbnRzY291bnQpLCAwLCBDb21wbGFpbnRzY291bnQpKQpgYGAKCgpgYGB7ciB0ZXN0IHNldHMsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9CgojIDIwMTkgdGVzdCBzZXRzCmhlcm9pbjE5X25ldCA8LSAKICBkcGx5cjo6c2VsZWN0KGhlcm9pbjE5KSAlPiUgIAogIG11dGF0ZShjb3VudEhlcm9pbiA9IDEpICU+JSAKICBhZ2dyZWdhdGUoLiwgZmlzaG5ldCwgc3VtKSAlPiUKICBtdXRhdGUoY291bnRIZXJvaW4gPSByZXBsYWNlX25hKGNvdW50SGVyb2luLCAwKSwKICAgICAgICAgdW5pcXVlSUQgPSAxOm4oKSwgCiAgICAgICAgIGN2SUQgPSBzYW1wbGUocm91bmQobnJvdyhmaXNobmV0KSAvIDI0KSwgCiAgICAgICAgICAgICAgICAgICAgICAgc2l6ZT1ucm93KGZpc2huZXQpLCByZXBsYWNlID0gVFJVRSkpCgpoZXJvaW4xOV9uZXQgPC0gaGVyb2luMTlfbmV0ICU+JSAKICBzdF9qb2luKGNyaW1lMTksIC4sIGpvaW49c3Rfd2l0aGluKSAlPiUgCiAgc3RfZHJvcF9nZW9tZXRyeSgpICU+JQogIGdyb3VwX2J5KHVuaXF1ZUlEKSAlPiUKICBzdW1tYXJpemUoQ3JpbWVjb3VudCA9IG4oKSkgJT4lCiAgICBsZWZ0X2pvaW4oaGVyb2luMTlfbmV0LCAuICkgJT4lCiAgICBzdF9zZigpICU+JQogIG11dGF0ZShDcmltZWNvdW50ID0gaWZlbHNlKGlzLm5hKENyaW1lY291bnQpLCAwLCBDcmltZWNvdW50KSkKCmhlcm9pbjE5X25ldCA8LSBoZXJvaW4xOV9uZXQgJT4lIAogIHN0X2pvaW4oY29tcGxhaW50MTksIC4sIGpvaW49c3Rfd2l0aGluKSAlPiUgCiAgc3RfZHJvcF9nZW9tZXRyeSgpICU+JQogIGdyb3VwX2J5KHVuaXF1ZUlEKSAlPiUKICBzdW1tYXJpemUoQ29tcGxhaW50c2NvdW50ID0gbigpKSAlPiUKICAgIGxlZnRfam9pbihoZXJvaW4xOV9uZXQsIC4gKSAlPiUKICAgIHN0X3NmKCkgJT4lCiAgbXV0YXRlKENvbXBsYWludHNjb3VudCA9IGlmZWxzZShpcy5uYShDb21wbGFpbnRzY291bnQpLCAwLCBDb21wbGFpbnRzY291bnQpKQoKCiMgMjAyMCB0ZXN0IHNldApoZXJvaW4yMF9uZXQgPC0gCiAgZHBseXI6OnNlbGVjdChoZXJvaW4yMCkgJT4lICAKICBtdXRhdGUoY291bnRIZXJvaW4gPSAxKSAlPiUgCiAgYWdncmVnYXRlKC4sIGZpc2huZXQsIHN1bSkgJT4lCiAgbXV0YXRlKGNvdW50SGVyb2luID0gcmVwbGFjZV9uYShjb3VudEhlcm9pbiwgMCksCiAgICAgICAgIHVuaXF1ZUlEID0gMTpuKCksIAogICAgICAgICBjdklEID0gc2FtcGxlKHJvdW5kKG5yb3coZmlzaG5ldCkgLyAyNCksIAogICAgICAgICAgICAgICAgICAgICAgIHNpemU9bnJvdyhmaXNobmV0KSwgcmVwbGFjZSA9IFRSVUUpKQoKaGVyb2luMjBfbmV0IDwtIGhlcm9pbjIwX25ldCAlPiUgCiAgc3Rfam9pbihjcmltZTIwLCAuLCBqb2luPXN0X3dpdGhpbikgJT4lIAogIHN0X2Ryb3BfZ2VvbWV0cnkoKSAlPiUKICBncm91cF9ieSh1bmlxdWVJRCkgJT4lCiAgc3VtbWFyaXplKENyaW1lY291bnQgPSBuKCkpICU+JQogICAgbGVmdF9qb2luKGhlcm9pbjIwX25ldCwgLiApICU+JQogICAgc3Rfc2YoKSAlPiUKICBtdXRhdGUoQ3JpbWVjb3VudCA9IGlmZWxzZShpcy5uYShDcmltZWNvdW50KSwgMCwgQ3JpbWVjb3VudCkpCgpoZXJvaW4yMF9uZXQgPC0gaGVyb2luMjBfbmV0ICU+JSAKICBzdF9qb2luKGNvbXBsYWludDIwLCAuLCBqb2luPXN0X3dpdGhpbikgJT4lIAogIHN0X2Ryb3BfZ2VvbWV0cnkoKSAlPiUKICBncm91cF9ieSh1bmlxdWVJRCkgJT4lCiAgc3VtbWFyaXplKENvbXBsYWludHNjb3VudCA9IG4oKSkgJT4lCiAgICBsZWZ0X2pvaW4oaGVyb2luMjBfbmV0LCAuICkgJT4lCiAgICBzdF9zZigpICU+JQogIG11dGF0ZShDb21wbGFpbnRzY291bnQgPSBpZmVsc2UoaXMubmEoQ29tcGxhaW50c2NvdW50KSwgMCwgQ29tcGxhaW50c2NvdW50KSkKYGBgCgpXZSBwZXJmb3JtZWQgc29tZSBkYXRhIHZpc3VhbGl6YXRpb24gd29yayB0byBzZWUgaWYgdGhlcmUgYXJlIGFueSBkaWZmZXJlbmNlcyBiZXR3ZWVuIGhlcm9pbi1vdmVyZG9zZSBpbmNpZGVudHMgb3ZlciB5ZWFyLiBXZSBmb3VuZCB0aGF0IHRoZSBkaXN0cmlidXRpb24gb2YgdGhlc2UgaW5jaWRlbnRzIGFyZSBwcmV0dHkgY29uc2lzdGVudCBiZXR3ZWVuIDIwMTYtMjAxOCBhbmQgMjAxOSwgd2l0aCB0aGUgbWFqb3JpdHkgb2YgaW5jaWRlbnRzIHNpZ25pZmljYW50bHkgY2x1c3RlcmVkIGluIHNvdXRoIENpbmNpbm5hdGkuIEhvd2V2ZXIsIGluIDIwMjAsIGRlc3BpdGUgdGhhdCB0aGlzIG92ZXJkb3NlIGNsdXN0ZXIgc3RpbGwgcmVtYWluLCBvdmVyZG9zZSBpbmNpZGVudHMgYmVjYW1lIG1vcmUgZGlzcGVyc2UuIFdlIG1heSBzZWUgc29tZSBzbWFsbCwgZW1lcmdpbmcgY2x1c3RlcnMgb2YgaGlnaCBvdmVyZG9zZSBpbmNpZGVudHMgc2NhdHRlcmVkIGFyb3VuZCB0aGUgY2l0eS4gCgpgYGB7ciBpcyB0aGVyZSBhIGRpZmZlcmVuY2UgYmV0d2VlbiBoZXJvaW4gaW5jaWRlbnRzIG92ZXIgeWVhciwgZmlnLmhlaWdodD02LCBmaWcud2lkdGg9MTUsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9CgoKcGxvdDEgPC0gZ2dwbG90KCkgKwogIGdlb21fc2YoZGF0YSA9IGhlcm9pblRyYWluX25ldCwgYWVzKGZpbGwgPSBjb3VudEhlcm9pbiksIGNvbG9yID0gTkEpICsKICBzY2FsZV9maWxsX3ZpcmlkaXMob3B0aW9uID0gInJvY2tldCIsIG5hbWUgPSAiQ291bnRzIikgKwogIGxhYnModGl0bGUgPSAiSGVyb2luIE9EIDIwMTYtMjAxOCIpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb249ImJvdHRvbSIsCiAgICAgICAgYXhpcy50ZXh0Lng9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGV4dC55PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpY2tzID1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGl0bGUueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSA5LGZhY2UgPSAiaXRhbGljIiksCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIsIGZhY2UgPSAiYm9sZCIpLAogICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9yZWN0KGNvbG91ciA9ICJncmV5IiwgZmlsbD1OQSwgbGluZXdpZHRoPTAuOCkKICAgICAgICApCgpwbG90MiA8LSBnZ3Bsb3QoKSArCiAgZ2VvbV9zZihkYXRhID0gaGVyb2luMTlfbmV0LCBhZXMoZmlsbCA9IGNvdW50SGVyb2luKSwgY29sb3IgPSBOQSkgKwogIHNjYWxlX2ZpbGxfdmlyaWRpcyhvcHRpb24gPSAicm9ja2V0IiwgbmFtZSA9ICJDb3VudHMiKSArCiAgbGFicyh0aXRsZSA9ICJIZXJvaW4gT0QgMjAxOSIpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb249ImJvdHRvbSIsCiAgICAgICAgYXhpcy50ZXh0Lng9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGV4dC55PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpY2tzID1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGl0bGUueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSA5LGZhY2UgPSAiaXRhbGljIiksCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIsIGZhY2UgPSAiYm9sZCIpLAogICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9yZWN0KGNvbG91ciA9ICJncmV5IiwgZmlsbD1OQSwgbGluZXdpZHRoPTAuOCkKICAgICAgICApCgogcGxvdDMgPC0gZ2dwbG90KCkgKwogIGdlb21fc2YoZGF0YSA9IGhlcm9pbjIwX25ldCwgYWVzKGZpbGwgPSBjb3VudEhlcm9pbiksIGNvbG9yID0gTkEpICsKICBzY2FsZV9maWxsX3ZpcmlkaXMob3B0aW9uID0gInJvY2tldCIsIG5hbWUgPSAiQ291bnRzIikgKwogIGxhYnModGl0bGUgPSAiSGVyb2luIE9EIDIwMjAiKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJib3R0b20iLAogICAgICAgIGF4aXMudGV4dC54PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRleHQueT1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aWNrcyA9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpdGxlLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gOSxmYWNlID0gIml0YWxpYyIpLAogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSwKICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfcmVjdChjb2xvdXIgPSAiZ3JleSIsIGZpbGw9TkEsIGxpbmV3aWR0aD0wLjgpCiAgICAgICAgKQogCnBsb3QxICsgcGxvdDIgKyBwbG90MwpgYGAKCldlIGNyZWF0ZWQgaGVhdCBtYXBzIG9mIGhlcm9pbi1vdmVyZG9zZSBpbmNpZGVudHMgZnJvbSAyMDE2IHRvIDIwMTggYW5kIG92ZXJsYXllZCBjcmltZSBpbmNpZGVudHMgYW5kIDMxMSBjb21wbGFpbnRzIGFib3ZlLiBCYXNlZCBvbiB0aGUgdmlzdWFsaXphdGlvbiwgd2UgYXNzdW1lIGNyaW1lIGFuZCAzMTEgY29tcGxhaW50cyB0byBiZSBzdHJvbmcgcHJlZGljdG9ycyBvZiBvdmVyZG9zZSByaXNrIGFzIHBsYWNlcyBvZiBoaWdoIG92ZXJkb3NlIGluY2lkZW50cyBhbGlnbiBwZXJmZWN0bHkgd2l0aCBwbGFjZXMgd2l0aCBoaWdoZXIgY3JpbWUgcmF0ZSBhbmQgbW9yZSBjb21wbGFpbnRzLiBZZXQsIGl0IGlzIGFsc28gcHJldHR5IGV2aWRlbnQgdGhhdCBjcmltZSBpbmNpZGVudHMgYXJlIGNvcnJlbGF0ZWQgd2l0aCAzMTEgY29tcGxhaW50cy4gCgpgYGB7ciBjcmltZSBhbmQgY29tcGxhaW50cywgZmlnLmhlaWdodD02LCBmaWcud2lkdGg9MTMsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9CgpwbG90MV8xIDwtIGdncGxvdCgpICsKICBnZW9tX3NmKGRhdGEgPSBjaW5jaW5uYXRpLCBmaWxsID0gImJsYWNrIikgKwogIHN0YXRfZGVuc2l0eTJkKGRhdGEgPSBkYXRhLmZyYW1lKHN0X2Nvb3JkaW5hdGVzKGhlcm9pblRyYWluKSksIAogICAgICAgICAgICAgICAgIGFlcyhYLCBZLCBmaWxsID0gYWZ0ZXJfc3RhdChsZXZlbCksIGFscGhhID0gYWZ0ZXJfc3RhdChsZXZlbCkpLAogICAgICAgICAgICAgICAgIHNpemUgPSAwLjAxLCBiaW5zID0gNjAsIGdlb20gPSAncG9seWdvbicpICsKICBzY2FsZV9maWxsX3ZpcmlkaXMob3B0aW9uID0gInJvY2tldCIsIG5hbWUgPSAiRGVuc2l0eSIpICsKICBzY2FsZV9hbHBoYShyYW5nZSA9IGMoMC4wMCwgMC4zNSksIGd1aWRlID0gIm5vbmUiKSArCiAgZ2VvbV9zZihkYXRhID0gc2FtcGxlX24oY3JpbWVUcmFpbiwgMzAwMCksIHNpemUgPSAwLjAzLCBjb2xvdXIgPSAid2hpdGUiLCBhbHBoYSA9IDAuNSkgKwogIGxhYnModGl0bGUgPSAiSGVyb2luIE9EIGFuZCBDcmltZSAyMDE2LTIwMTgiKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJib3R0b20iLAogICAgICAgIGF4aXMudGV4dC54PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRleHQueT1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aWNrcyA9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpdGxlLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gOSxmYWNlID0gIml0YWxpYyIpLAogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSwKICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfcmVjdChjb2xvdXIgPSAiZ3JleSIsIGZpbGw9TkEsIGxpbmV3aWR0aD0wLjgpCiAgICAgICAgKQoKcGxvdDFfMiA8LSBnZ3Bsb3QoKSArCiAgZ2VvbV9zZihkYXRhID0gY2luY2lubmF0aSwgZmlsbCA9ICJibGFjayIpICsKICBzdGF0X2RlbnNpdHkyZChkYXRhID0gZGF0YS5mcmFtZShzdF9jb29yZGluYXRlcyhoZXJvaW5UcmFpbikpLCAKICAgICAgICAgICAgICAgICBhZXMoWCwgWSwgZmlsbCA9IGFmdGVyX3N0YXQobGV2ZWwpLCBhbHBoYSA9IGFmdGVyX3N0YXQobGV2ZWwpKSwKICAgICAgICAgICAgICAgICBzaXplID0gMC4wMSwgYmlucyA9IDYwLCBnZW9tID0gJ3BvbHlnb24nKSArCiAgc2NhbGVfZmlsbF92aXJpZGlzKG9wdGlvbiA9ICJyb2NrZXQiLCBuYW1lID0gIkRlbnNpdHkiKSArCiAgc2NhbGVfYWxwaGEocmFuZ2UgPSBjKDAuMDAsIDAuMzUpLCBndWlkZSA9ICJub25lIikgKwogIGdlb21fc2YoZGF0YSA9IHNhbXBsZV9uKGNvbXBsYWludFRyYWluLCAzMDAwKSwgc2l6ZSA9IDAuMDMsIGNvbG91ciA9ICJ3aGl0ZSIsIGFscGhhID0gMC41KSArCiAgbGFicyh0aXRsZSA9ICJIZXJvaW4gT0QgYW5kIENvbXBsYWludCAyMDE2LTIwMTgiKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJib3R0b20iLAogICAgICAgIGF4aXMudGV4dC54PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRleHQueT1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aWNrcyA9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpdGxlLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gOSxmYWNlID0gIml0YWxpYyIpLAogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSwKICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfcmVjdChjb2xvdXIgPSAiZ3JleSIsIGZpbGw9TkEsIGxpbmV3aWR0aD0wLjgpCiAgICAgICAgKQoKcGxvdDFfMSArIHBsb3QxXzIKYGBgCgpNYXBwaW5nIHRoZSBjb3JyZWxhdGlvbiBiZXR3ZWVuIG91ciBwcmVkaWN0b3IgdmFyaWFibGVzIHJldmVhbHMgdGhhdCBpbmRlZWQsIHRoZXJlJ3MgYSBzdHJvbmcsIGxpbmVhciwgYW5kIHBvc2l0aXZlIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIGhlcm9pbi1vdmVyZG9zZSBpbmNpZGVudHMgYW5kIGNyaW1lIGFzIHdlbGwgYXMgMzExIGNvbXBsYWludHMsIHRob3VnaCB0aGUgcmVsYXRpb25zaGlwcyBiZWNvbWUgd2Vha2VyIGluIDIwMjAuIFdlIHN1c3BlY3QgdGhhdCBkaXNydXB0aW9ucyBpbiB0aGUgZHJ1ZyBzdXBwbHkgY2hhaW4sIGNoYW5nZXMgaW4gbGF3IGVuZm9yY2VtZW50IHByaW9yaXRpZXMsIGFzIHdlbGwgYXMgaW5jcmVhc2Ugc29jaWFsIGlzb2xhdGlvbiBhbmQgbWVudGFsIGhlYWx0aCBjaGFsbGVuZ2VzIGR1cmluZyB0aGUgcGFuZGVtaWMgbWlnaHQgaGF2ZSBjb250cmlidXRlIHRvIHRoZSBkZWNyZWFzZSBpbiBjb3JyZWxhdGlvbiBiZXR3ZWVuIG92ZXJhbGwgY3JpbWUgaW5jaWRlbnRzIGFuZCBoZXJvaW4gb3ZlcmRvc2UgaW5jaWRlbnRzIGluIDIwMjAuIAoKYGBge3IgY29yIHBsb3RzLCBmaWcuaGVpZ2h0PTYsIGZpZy53aWR0aD0xMiwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KCnBsb3Q0IDwtIGhlcm9pblRyYWluX25ldCAlPiUKICBzdF9kcm9wX2dlb21ldHJ5KCkgJT4lIAogIGRwbHlyOjpzZWxlY3QoLWModW5pcXVlSUQsIGN2SUQpKSAlPiUgCiAgcGl2b3RfbG9uZ2VyKGNvbHMgPSAtY291bnRIZXJvaW4sICMgZXZlcnl0aGluZyBleGNlcHQgbWVhc3VyZW1lbnQKICAgICAgICAgICAgICAgbmFtZXNfdG8gPSAiVHlwZSIsICMgY2F0ZWdvcml6ZXMgYWxsIHF1YW50aXRhdGl2ZSB2YXJpYWJsZXMgaW50byBUeXBlCiAgICAgICAgICAgICAgIHZhbHVlc190byA9ICJOdW1iZXIiKSAlPiUgCiAgZ2dwbG90KGFlcyh4PSBjb3VudEhlcm9pbiwgeSA9IE51bWJlcikpICsKICBnZW9tX3BvaW50KHNpemUgPSAwLjAxLCBjb2xvciA9ICIjMDAwMDA0IikgKyAgCiAgZ2VvbV9zbW9vdGgobWV0aG9kPSdsbScsIGZvcm11bGE9IHl+eCwgbHdkPTAuNSwgc2UgPSBGQUxTRSwgY29sb3IgPSAiI0JCMzc1NCIpICsKICBmYWNldF93cmFwKH4gVHlwZSwgc2NhbGVzID0gImZyZWUiLCBuY29sID0gMiwgbGFiZWxsZXI9IGxhYmVsbGVyKFR5cGUgPSBjKAogICAgYENyaW1lY291bnRgID0gIkNyaW1lIiwKICAgIGBDb21wbGFpbnRzY291bnRgID0gIjMxMSBDb21wbGFpbnRzIikpKSArCiAgbGFicyh0aXRsZSA9ICJTY2F0dGVyIFBsb3RzIG9mIFByZWRpY3RvciBWYXJpYWJsZXMgMjAxNi0xOCIpICsKICAgIHRoZW1lKHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDksZmFjZSA9ICJpdGFsaWMiKSwKICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgZmFjZSA9ICJib2xkIiksIAogICAgICAgIGF4aXMudGV4dC54PWVsZW1lbnRfdGV4dChzaXplPTYpLAogICAgICAgIGF4aXMudGV4dC55PWVsZW1lbnRfdGV4dChzaXplPTYpLCAKICAgICAgICBheGlzLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTgpLCAKICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfcmVjdChjb2xvdXIgPSAiZ3JleSIsIGZpbGw9TkEsIGxpbmV3aWR0aCA9MC44KSkKCnBsb3Q1IDwtIGhlcm9pbjE5X25ldCAlPiUKICBzdF9kcm9wX2dlb21ldHJ5KCkgJT4lIAogIGRwbHlyOjpzZWxlY3QoLWModW5pcXVlSUQsIGN2SUQpKSAlPiUgCiAgcGl2b3RfbG9uZ2VyKGNvbHMgPSAtY291bnRIZXJvaW4sICMgZXZlcnl0aGluZyBleGNlcHQgbWVhc3VyZW1lbnQKICAgICAgICAgICAgICAgbmFtZXNfdG8gPSAiVHlwZSIsICMgY2F0ZWdvcml6ZXMgYWxsIHF1YW50aXRhdGl2ZSB2YXJpYWJsZXMgaW50byBUeXBlCiAgICAgICAgICAgICAgIHZhbHVlc190byA9ICJOdW1iZXIiKSAlPiUgCiAgZ2dwbG90KGFlcyh4PSBjb3VudEhlcm9pbiwgeSA9IE51bWJlcikpICsKICBnZW9tX3BvaW50KHNpemUgPSAwLjAxLCBjb2xvciA9ICIjMDAwMDA0IikgKyAgCiAgZ2VvbV9zbW9vdGgobWV0aG9kPSdsbScsIGZvcm11bGE9IHl+eCwgbHdkPTAuNSwgc2UgPSBGQUxTRSwgY29sb3IgPSAiI0JCMzc1NCIpICsKICBmYWNldF93cmFwKH4gVHlwZSwgc2NhbGVzID0gImZyZWUiLCBuY29sID0gMiwgbGFiZWxsZXI9IGxhYmVsbGVyKFR5cGUgPSBjKAogICAgYENyaW1lY291bnRgID0gIkNyaW1lIiwKICAgIGBDb21wbGFpbnRzY291bnRgID0gIjMxMSBDb21wbGFpbnRzIikpKSArCiAgbGFicyh0aXRsZSA9ICJTY2F0dGVyIFBsb3RzIG9mIFByZWRpY3RvciBWYXJpYWJsZXMgMjAxOSIpICsKICAgIHRoZW1lKHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDksZmFjZSA9ICJpdGFsaWMiKSwKICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgZmFjZSA9ICJib2xkIiksIAogICAgICAgIGF4aXMudGV4dC54PWVsZW1lbnRfdGV4dChzaXplPTYpLAogICAgICAgIGF4aXMudGV4dC55PWVsZW1lbnRfdGV4dChzaXplPTYpLCAKICAgICAgICBheGlzLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTgpLCAKICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfcmVjdChjb2xvdXIgPSAiZ3JleSIsIGZpbGw9TkEsIGxpbmV3aWR0aCA9MC44KSkKCgpwbG90NiA8LSBoZXJvaW4yMF9uZXQgJT4lCiAgc3RfZHJvcF9nZW9tZXRyeSgpICU+JSAKICBkcGx5cjo6c2VsZWN0KC1jKHVuaXF1ZUlELCBjdklEKSkgJT4lIAogIHBpdm90X2xvbmdlcihjb2xzID0gLWNvdW50SGVyb2luLCAjIGV2ZXJ5dGhpbmcgZXhjZXB0IG1lYXN1cmVtZW50CiAgICAgICAgICAgICAgIG5hbWVzX3RvID0gIlR5cGUiLCAjIGNhdGVnb3JpemVzIGFsbCBxdWFudGl0YXRpdmUgdmFyaWFibGVzIGludG8gVHlwZQogICAgICAgICAgICAgICB2YWx1ZXNfdG8gPSAiTnVtYmVyIikgJT4lIAogIGdncGxvdChhZXMoeD0gY291bnRIZXJvaW4sIHkgPSBOdW1iZXIpKSArCiAgZ2VvbV9wb2ludChzaXplID0gMC4wMSwgY29sb3IgPSAiIzAwMDAwNCIpICsgIAogIGdlb21fc21vb3RoKG1ldGhvZD0nbG0nLCBmb3JtdWxhPSB5fngsIGx3ZD0wLjUsIHNlID0gRkFMU0UsIGNvbG9yID0gIiNCQjM3NTQiKSArCiAgZmFjZXRfd3JhcCh+IFR5cGUsIHNjYWxlcyA9ICJmcmVlIiwgbmNvbCA9IDIsIGxhYmVsbGVyPSBsYWJlbGxlcihUeXBlID0gYygKICAgIGBDcmltZWNvdW50YCA9ICJDcmltZSIsCiAgICBgQ29tcGxhaW50c2NvdW50YCA9ICIzMTEgQ29tcGxhaW50cyIpKSkgKwogIGxhYnModGl0bGUgPSAiU2NhdHRlciBQbG90cyBvZiBQcmVkaWN0b3IgVmFyaWFibGVzIDIwMjAiKSArCiAgICB0aGVtZShwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSA5LGZhY2UgPSAiaXRhbGljIiksCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIsIGZhY2UgPSAiYm9sZCIpLCAKICAgICAgICBheGlzLnRleHQueD1lbGVtZW50X3RleHQoc2l6ZT02KSwKICAgICAgICBheGlzLnRleHQueT1lbGVtZW50X3RleHQoc2l6ZT02KSwgCiAgICAgICAgYXhpcy50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT04KSwgCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X3JlY3QoY29sb3VyID0gImdyZXkiLCBmaWxsPU5BLCBsaW5ld2lkdGggPTAuOCkpCgpwbG90NCArIHBsb3Q1ICsgcGxvdDYKCmBgYAoKV2UgcmFuIHRoZSBQb2lzc29uIHJlZ3Jlc3Npb24gYW5kIGNvbmZpcm1lZCB0aGF0IGNyaW1lIGluY2lkZW50cyBhbmQgMzExIGNvbXBsYWludHMgYXJlIGdvb2QgYXQgcHJlZGljdGluZyBoZXJvaW4tb3ZlcmRvc2Ugcmlzay4gVGhlIG1vZGVsIGlzIHNsaWdodGx5IGJldHRlciB3aGVuIHVzZWQgdG8gcHJlZGljdCAyMDIwIGRhdGEsIGJ1dCB0aGUgZGlmZmVyZW5jZSBpcyBtaW5pbWFsLiAKCmBgYHtyIHJ1bm5pbmcgcG9pc3Npb24sIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9Cgpwb2lzc2lvblRyYWluIDwtIGdsbShjb3VudEhlcm9pbiB+IENyaW1lY291bnQgKyBDb21wbGFpbnRzY291bnQsIGZhbWlseSA9ICJwb2lzc29uIiwKICAgICAgICAgICAgICAgICAgICAgIGRhdGEgPSBoZXJvaW5UcmFpbl9uZXQpCgpQcmVkaWN0aW9uMTkgPC0KICBtdXRhdGUoaGVyb2luMTlfbmV0LCBQcmVkaWN0aW9uID0gcHJlZGljdChwb2lzc2lvblRyYWluLCBoZXJvaW4xOV9uZXQsIHR5cGUgPSAicmVzcG9uc2UiKSkgJT4lIAogIG11dGF0ZShFcnJvciA9IGNvdW50SGVyb2luIC0gUHJlZGljdGlvbikgJT4lIAogIG11dGF0ZShNQUUgPSBtZWFuKGFicyhFcnJvcikpKSAlPiUgCiAgbXV0YXRlKFNEX01BRSA9IHNkKEVycm9yKSkgJT4lIAogIG11dGF0ZShwcmVkaWN0aW9uID0gIjIwMTkiKSAlPiUgCiAgc2xpY2UoMToxKQoKUHJlZGljdGlvbjIwIDwtCiAgbXV0YXRlKGhlcm9pbjIwX25ldCwgUHJlZGljdGlvbiA9IHByZWRpY3QocG9pc3Npb25UcmFpbiwgaGVyb2luMjBfbmV0LCB0eXBlID0gInJlc3BvbnNlIikpICU+JSAKICBtdXRhdGUoRXJyb3IgPSBjb3VudEhlcm9pbiAtIFByZWRpY3Rpb24pICU+JSAKICBtdXRhdGUoTUFFID0gbWVhbihhYnMoRXJyb3IpKSkgJT4lIAogIG11dGF0ZShTRF9NQUUgPSBzZChFcnJvcikpICU+JSAKICBtdXRhdGUocHJlZGljdGlvbiA9ICIyMDIwIikgJT4lIAogIHNsaWNlKDE6MSkKCnJiaW5kKFByZWRpY3Rpb24xOSwgUHJlZGljdGlvbjIwKSAlPiUgCiAgc3RfZHJvcF9nZW9tZXRyeSgpICU+JSAKICBncm91cF9ieShwcmVkaWN0aW9uKSAlPiUgCiAgc3VtbWFyaXplKE1lYW5fTUFFID0gcm91bmQoTUFFLCAyKSwKICAgICAgICAgICAgICBTRF9NQUUgPSByb3VuZChTRF9NQUUsIDIpKSAlPiUKICBrYWJsZShjb2wubmFtZT1jKCJQcmVkaWN0aW9uIiwgIk1lYW4gQWJzb2x1dGUgRXJyb3IiLCdTdGFuZGFyZCBEZXZpYXRpb24gTUFFJykpICU+JQogICAga2FibGVfc3R5bGluZyhib290c3RyYXBfb3B0aW9ucyA9IGMoInN0cmlwZWQiLCAiaG92ZXIiLCAiY29uZGVuc2VkIikpIAoKYGBgCgpIZXJlIGFyZSBvdXIgcmVmbGVjdGlvbnMgdXAgdG8gdGhpcyBwb2ludDoKCldoaWxlIHRoZXJlJ3Mgc2lnbmlmaWNhbnQgYXNzb2NpYXRpb25zIGJldHdlZW4gY3JpbWUgaW5jaWRlbnRzLCAzMTEgY29tcGxhaW50cywgYW5kIGhlcm9pbi1vdmVyZG9zZSBpbmNpZGVudHMsIGJ1aWxkaW5nIGEgbW9kZWwgdGhhdCByZWxpZXMgc29sZWx5IG9uIHRoZW0gdG8gcHJlZGljdCByaXNrIGlzIHNpbXBseSBub3QgZW5vdWdoIGJlY2F1c2Ugc3Vic3RhbmNlIGFidXNlIGFuZCBvdmVyZG9zZSByaXNrcyBhcmUgY29tcGxleCBwaGVub21lbmEgaW5mbHVlbmNlZCBieSB2YXJpb3VzIGZhY3RvcnMgYmV5b25kIGp1c3QgY3JpbWluYWwgYWN0aXZpdHkuIFRoZSBnZW9ncmFwaGljYWwgYW5kIGVudmlyb25tZW50YWwgY29udGV4dCwgaW5jbHVkaW5nIHRoZSBhdmFpbGFiaWxpdHkgb2YgdHJlYXRtZW50IGZhY2lsaXRpZXMsIHByb3hpbWl0eSB0byBzdXBwb3J0IG5ldHdvcmtzLCBhbmQgbmVpZ2hib3Job29kIGNoYXJhY3RlcmlzdGljcywgYWxzbyBwbGF5cyBhIGNydWNpYWwgcm9sZSBpbiB1bmRlcnN0YW5kaW5nIG92ZXJkb3NlIHJpc2tzLiBUaGVyZWZvcmUsIHdlIG11c3QgaW50ZWdyYXRlIG1vcmUgZmVhdHVyZXMgc2hvd2luZyBidWlsdCBlbnZpcm9ubWVudCBhbmQgbG9jYWwgY29udGV4dHMgaW50byB0aGUgbW9kZWwuIAoKT24gdG9wIG9mIHRoYXQsIFBvaXNzb24gcmVncmVzc2lvbiBpcyBhIHRyYWRpdGlvbmFsIG1ldGhvZCB1c2VkIGluIGdlb3NwYXRpYWwgcmlzayBtb2RlbCB0byBwcmVkaWN0IHRoZSBudW1iZXIgKGNvdW50KSBvZiBkaXNjcmV0ZSBldmVudCwgZXNwZWNpYWxseSBjcmltZS4gSG93ZXZlciwgaW4gbWFraW5nIGRlY2lzaW9ucyBhYm91dCBob3cgdG8gYWxsZXZpYXRlIHRoZSByaXNrIG9mIHBhcnRpY3VsYXIgbWVkaWNhbCBvciBjcmltaW5hbCBpbmNpZGVuY2UsIHdlIHNob3VsZG4ndCBqdXN0IGxvb2sgYXQgdGhlIG51bWJlciBvZiBhY2NpZGVudHMgYXQgYW4gaW50ZXJzZWN0aW9uIGluIGEgZGF5IGF0IGEgdmVyeSBzcGVjaWZpYyBwbGFjZS4gUmF0aGVyLCB3ZSBzaG91bGQgZmxhZyBhcmVhcyBpbiBhIGNpdHkgdGhhdCBhcmUgbW9yZSB2dWxuZXJhYmxlL3Jpc2t5IHRoYW4gb3RoZXJzLiBJdCBpcyBub3Qgd3JvbmcgdG8gY2FyZSB0byBmb2N1cyBvbiB0aGUgbnVtYmVyIG9mIGluY2lkZW5jZSwgYnV0IGZyb20gYSBwb2xpY3ktbWFraW5nIHBlcnNwZWN0aXZlLCBsZWFybmluZyB3aGV0aGVyIG9yIG5vdCBhIHBsYWNlIGlzIHJpc2t5IHdvdWxkIGJlIG1vcmUgZWZmaWNpZW50LCBoZWxwZnVsLCBhbmQgc3RyYWlnaHRmb3J3YXJkIGluIGd1aWRpbmcgZGVjaXNpb24gbWFraW5nLiBJbiBvdGhlciB3b3Jkcywgc3Rha2Vob2xkZXJzIGFuZCB0aGUgZ2VuZXJhbCBwdWJsaWMgZG8gbm90IGhhdmUgdG8ga25vdyAqaG93IG1hbnkgaW5jaWRlbnRzIGhhcHBlbmVkIGF0IHRoaXMgcGxhY2UqLCBidXQgKndoZXRoZXIgdGhpcyBwbGFjZSBpcyByaXNreSogYW5kIHRoZXJlZm9yZSByZXF1aXJlcyBtb3JlIGF0dGVudGlvbi4gCgojIEdldGlzIE9yZCBTcGF0aWFsIEhvdHNwb3QKCldlIGFkYXB0ZWQgdGhpcyByZXNlYXJjaCB3b3JrZmxvdyBmcm9tIFNyaW5pdmFzYW4sIGV0IGFsLiAoMjAyMyksIHdobyBzdHVkaWVkIHRoZSByaXNrIGZhY3RvcnMgZm9yIG9waW9pZCBvdmVyZG9zZSBpbiBNYXNzYWNodXNldHRzLiBJbiBpdHMgZXNzZW5jZSwgU3Jpbml2YXNhbiwgZXQgYWwgdXNlZCBwcmluY2lwYWwgY29tcG9uZW50IGFuYWx5c2lzIHRvIHJlZHVjZSBjb2xpbmVyYXJpdHkgYmV0d2VlbiBwcmVkaWN0b3IgdmFyaWFibGVzIGFuZCByYW4gYSBsb2dpc3RpYyByZWdyZXNzaW9uIHRvIHByZWRpY3Qgd2hldGhlciBwbGFjZXMgaW4gTWFzc2FjaHVzZXR0cyBhcmUgd2l0aGluIG92ZXJkb3NlIGhvdHNwb3Qgb3Igbm90LiAKClRvIHJlcGxpY2F0ZSB0aGVpciBhcHByb2FjaCBhcyBtdWNoIGFzIHBvc3NpYmxlLCB3ZSBmaXJzdCBleGFtaW5lZCB0aGUgc3BhdGlhbCBkaXN0cmlidXRpb24gb2YgaGVyb2luLXJlbGF0ZWQgaW5jaWRlbnQgcmF0ZXMgdXNpbmcgdGhlIEplbmtzIG5hdHVyYWwgYnJlYWtzIG1hcHMuClRoZSBKZW5rcyBuYXR1cmFsIGJyZWFrcyBtYXBzIHVzZSBhIG5vbmxpbmVhciBhbGdvcml0aG0gdG8gY3JlYXRlIGdyb3VwcyB3aGVyZSB3aXRoaW4tZ3JvdXAgc2ltaWxhcml0eSBpcyBtYXhpbWl6ZWQsIGFuZCBiZXR3ZWVuLWdyb3VwIHNpbWlsYXJpdHkgaXMgbWluaW1pemVkLiAKVGhlIG1hcCBzaG93cyBhdCBsZWFzdCBvbmUgc2lnbmlmaWNhbnQgaW5jaWRlbmNlIGNsdXN0ZXIgaW4gc291dGggQ2luY2lubmF0aSBhbmQgc2V2ZXJhbCBvdGhlciBzbWFsbGVyIGNsdXN0ZXJzIHN1cnJvdW5kaW5nIHRoYXQgaG90c3BvdC4gCgpgYGB7ciBqZW5rcyBicmVhaywgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0KCmhlcm9pbl9uZXQgPC0gCiAgZHBseXI6OnNlbGVjdChoZXJvaW4pICU+JSAgCiAgbXV0YXRlKGNvdW50SGVyb2luID0gMSkgJT4lIAogIGFnZ3JlZ2F0ZSguLCBmaXNobmV0LCBzdW0pICU+JQogIG11dGF0ZShjb3VudEhlcm9pbiA9IHJlcGxhY2VfbmEoY291bnRIZXJvaW4sIDApLAogICAgICAgICB1bmlxdWVJRCA9IDE6bigpLCAKICAgICAgICAgY3ZJRCA9IHNhbXBsZShyb3VuZChucm93KGZpc2huZXQpIC8gMjQpLCAKICAgICAgICAgICAgICAgICAgICAgICBzaXplPW5yb3coZmlzaG5ldCksIHJlcGxhY2UgPSBUUlVFKSkKYnJlYWtzIDwtIGNsYXNzSW50ZXJ2YWxzKGhlcm9pbl9uZXQkY291bnRIZXJvaW4sIG4gPSA1LCBzdHlsZSA9ICJqZW5rcyIpCgpnZ3Bsb3QoKSArCiAgZ2VvbV9zZihkYXRhID0gaGVyb2luX25ldCwgYWVzKGZpbGwgPSBjb3VudEhlcm9pbiksIGNvbG9yID0gTkEpICsKICBzY2FsZV9maWxsX3ZpcmlkaXMob3B0aW9uID0gInJvY2tldCIsIGJyZWFrcyA9IGJyZWFrcyRicmtzLCBuYW1lID0gIkNvdW50IikgICsgCmxhYnModGl0bGUgPSAiQ291bnQgb2YgSGVyb2luIE92ZXJkb3NlIGZvciB0aGUgRmlzaG5ldCIpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRleHQueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpY2tzID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpdGxlLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gOSwgZmFjZSA9ICJpdGFsaWMiKSwKICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgZmFjZSA9ICJib2xkIiksCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X3JlY3QoY29sb3VyID0gImdyZXkiLCBmaWxsID0gTkEsIGxpbmV3aWR0aCA9IDAuOCkKICApICsgCiAgdGhlbWUobGVnZW5kLmtleS5zaXplID0gdW5pdCgwLjgsICdjbScpLCBsZWdlbmQudGV4dCA9IGVsZW1lbnRfdGV4dChzaXplPTYpKQpgYGAKCldlIHZhbGlkYXRlZCBvdXIgb2JzZXJ2YXRpb24gYnkgY2hlY2tpbmcgdGhlIGdsb2JhbCBtb3JhbidzIEkgdW5kZXIgdGhlIGh5cG90aGVzaXMgdGhhdCBoZXJvaW4gaW5jaWRlbmNlcyBhcmUgcmFuZG9tbHkgZGlzdHJpYnV0ZWQgYWNyb3NzIHNwYWNlLiAgVGhlIGZpZ3VyZSBiZWxvdyBwbG90IHRoZSBmcmVxdWVuY3kgb2YgOTk5IHJhbmRvbWx5IHBlcm11dGF0ZWQgSSB1bmRlciBNb250ZSBDYXJsbyBzaW11bGF0aW9uLCB3aGljaCBzaG93cyB0aGF0IG91ciBvYnNlcnZlZCBJIGlzIG11Y2ggaGlnaGVyIHRoYW4gYWxsIG9mIHRoZSByYW5kb21seSBnZW5lcmF0ZWQgSeKAmXMuIFRoaXMgdmFsaWRhdGVzIHRoZSBleGlzdGVuY2Ugb2Ygc3BhdGlhbCBhdXRvY29ycmVsYXRpb24uCgpgYGB7ciBnbG9iYWwgbW9yYW5zIEksIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9Cgpjb29yZHMgPC0gIHN0X2Nvb3JkaW5hdGVzKGhlcm9pbl9uZXQgJT4lIHN0X2NlbnRyb2lkKCkpIApuZWlnaGJvckxpc3QgPC0ga25uMm5iKGtuZWFybmVpZ2goY29vcmRzLCA0KSkKc3BhdGlhbFdlaWdodHMgPC0gbmIybGlzdHcobmVpZ2hib3JMaXN0LCBzdHlsZT0iVyIpCgptb3JhblRlc3QgPC0gbW9yYW4ubWMoaGVyb2luX25ldCRjb3VudEhlcm9pbiwgCiAgICAgICAgICAgICAgICAgICAgICBzcGF0aWFsV2VpZ2h0cywgbnNpbSA9IDk5OSkKCmdncGxvdChhcy5kYXRhLmZyYW1lKG1vcmFuVGVzdCRyZXNbYygxOjk5OSldKSwgYWVzKG1vcmFuVGVzdCRyZXNbYygxOjk5OSldKSkgKwogIGdlb21faGlzdG9ncmFtKGJpbndpZHRoID0gMC4wMSwgZmlsbCA9ICJibGFjayIpICsKICBnZW9tX3ZsaW5lKGFlcyh4aW50ZXJjZXB0ID0gbW9yYW5UZXN0JHN0YXRpc3RpYyksIGNvbG91ciA9ICIjNkUwRTBBIiAsbHdkPTEpICsKICBsYWJzKHRpdGxlID0gIk9ic2VydmVkIGFuZCBQZXJtdXRlZCBNb3JhbidzIEkiLAogICAgICAgc3VidGl0bGUgPSAiT2JzZXJ2ZWQgTW9yYW4ncyBJIGluIFJlZCIsCiAgICAgICB4ID0gIk1vcmFuJ3MgSSIsCiAgICAgICB5ID0gIkNvdW50IikgKwogIHRoZW1lX2xpZ2h0KCkgKyAgIAp0aGVtZShwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSA5LGZhY2UgPSAiaXRhbGljIiksCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIsIGZhY2UgPSAiYm9sZCIpLCAKICAgICAgICBheGlzLnRleHQueD1lbGVtZW50X3RleHQoc2l6ZT04KSwKICAgICAgICBheGlzLnRleHQueT1lbGVtZW50X3RleHQoc2l6ZT04KSwgCiAgICAgICAgYXhpcy50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT0xMCkpCgpgYGAKClNpbmNlIHRoZSBnbG9iYWwgc3BhdGlhbCBjbHVzdGVyaW5nIGFuYWx5c2lzIHlpZWxkcyBvbmx5IG9uZSBzdGF0aXN0aWMgdG8gZGVzY3JpYmUgdGhlIG92ZXJhbGwgcG9pbnQgcGF0dGVybiBhY3Jvc3MgdGhlIHdob2xlIHN0dWR5IGFyZWEsIHdlIGhhdmUgdG8gdXNlIHRoZSBHZXRpcy1PcmQgR2lcKiBob3RzcG90IHN0YXRpc3RpYyB0byBpZGVudGlmeSBhbmQgbWFwIGNsdXN0ZXJzIChpLmUuLCBncmlkcyB3aXRoIGhpZ2ggb3ZlcmRvc2UgcmF0ZXMgc3Vycm91bmRlZCBieSBncmlkcyB3aXRoIGhpZ2ggcmF0ZXMpLiBCZWxvdywgZWFjaCBob3RzcG90IGlkZW50aWZpZWQgaXMgc3RhdGlzdGljYWxseSBzaWduaWZpY2FudCBhdCB0aGUgcFw8MC4xIGxldmVsLiBGb3IgZXN0aW1hdGluZyB0aGUgY2x1c3RlcnMsIHdlIHVzZWQgdGhlIHF1ZWVuJ3MgY3JpdGVyaW9uIHRvIGRlZmluZSBvdXIgc3BhdGlhbCB3ZWlnaHRzIG1hdHJpeC4KCmBgYHtyIGxvY2FsIG1vcmFuIEksIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9CgpuZWlnaF9uYnMgPC0gaGVyb2luX25ldCAlPiUgCiAgbXV0YXRlKAogICAgbmIgPSBzdF9jb250aWd1aXR5KGdlb21ldHJ5KSwgICMgbmVpZ2hib3JzIHNoYXJlIGJvcmRlciAgICAKICAgIHd0ID0gc3Rfd2VpZ2h0cyhuYiksICMgcm93LXN0YW5kYXJkaXplZCB3ZWlnaHRzICAgICAgICAgICAgICAKICAgIG5laWdoX2xhZyA9IHN0X2xhZyhjb3VudEhlcm9pbiwgbmIsIHd0KSAgCiAgKQoKZ2lfaG90X3Nwb3RzIDwtIG5laWdoX25icyAlPiUgCiAgbXV0YXRlKEdpID0gbG9jYWxfZ19wZXJtKGNvdW50SGVyb2luLCBuYiwgd3QsIG5zaW0gPSA5OTkpKSAlPiUgCiAgdW5uZXN0KEdpKSAKCmdpX2hvdF9zcG90cyA8LSBnaV9ob3Rfc3BvdHMgJT4lICAKICBkcGx5cjo6c2VsZWN0KGdpLCBwX2ZvbGRlZF9zaW0sIHVuaXF1ZUlEKSB8PiAKICBtdXRhdGUoCiAgICBjbGFzc2lmaWNhdGlvbiA9IGNhc2Vfd2hlbigKICAgICAgIyBDbGFzc2lmeSBiYXNlZCBvbiB0aGUgZm9sbG93aW5nIGNyaXRlcmlhOgogICAgICBnaSA+IDAgJiBwX2ZvbGRlZF9zaW0gPD0gMC4wMSB+ICJWZXJ5IGhvdCIsCiAgICAgIGdpID4gMCAmIHBfZm9sZGVkX3NpbSA8PSAwLjA1IH4gIkhvdCIsCiAgICAgIGdpID4gMCAmIHBfZm9sZGVkX3NpbSA8PSAwLjEgfiAiU29tZXdoYXQgaG90IiwKICAgICAgZ2kgPCAwICYgcF9mb2xkZWRfc2ltIDw9IDAuMDEgfiAiVmVyeSBjb2xkIiwKICAgICAgZ2kgPCAwICYgcF9mb2xkZWRfc2ltIDw9IDAuMDUgfiAiQ29sZCIsCiAgICAgIGdpIDwgMCAmIHBfZm9sZGVkX3NpbSA8PSAwLjEgfiAiU29tZXdoYXQgY29sZCIsCiAgICAgIFRSVUUgfiAiSW5zaWduaWZpY2FudCIKICAgICksICAgICMgQ29udmVydCAnY2xhc3NpZmljYXRpb24nIGludG8gYSBmYWN0b3IgZm9yIGVhc2llciBwbG90dGluZwogICAgY2xhc3NpZmljYXRpb24gPSBmYWN0b3IoCiAgICAgIGNsYXNzaWZpY2F0aW9uLAogICAgICBsZXZlbHMgPSBjKCJWZXJ5IGhvdCIsICJIb3QiLCAiU29tZXdoYXQgaG90IiwKICAgICAgICAgICAgICAgICAiSW5zaWduaWZpY2FudCIsCiAgICAgICAgICAgICAgICAgIlNvbWV3aGF0IGNvbGQiLCAiQ29sZCIsICJWZXJ5IGNvbGQiKQogICAgKQogICkKCmdpX2hvdF9zcG90cyAlPiUgCiAgZ2dwbG90KCkgKyAKICBnZW9tX3NmKGFlcyhmaWxsID0gY2xhc3NpZmljYXRpb24pLCBjb2xvciA9ICJibGFjayIsIGx3ZCA9IDAuMSkgKwogIHNjYWxlX2ZpbGxfYnJld2VyKHR5cGUgPSAiZGl2IiwgcGFsZXR0ZSA9IDYsIG5hbWUgPSAiQ2xhc3NpZmljYXRpb24iKSArCiAgbGFicyh0aXRsZSA9ICJTcGF0aWFsIEhvdHNwb3RzIGFuZCBDb2xkc3BvdHMgb2YgSGVyb2luIE92ZXJkb3NlIEluY2lkZW5jZSIpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb249ImJvdHRvbSIsCiAgICAgICAgYXhpcy50ZXh0Lng9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGV4dC55PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpY2tzID1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGl0bGUueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSA5LGZhY2UgPSAiaXRhbGljIiksCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIsIGZhY2UgPSAiYm9sZCIpLAogICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9yZWN0KGNvbG91ciA9ICJncmV5IiwgZmlsbD1OQSwgbGluZXdpZHRoPTAuOCkKICAgICAgICApCgpoZXJvaW5fbmV0IDwtIGdpX2hvdF9zcG90cyAlPiUgCiAgbXV0YXRlKGhvdHNwb3QgPSBpZmVsc2UoY2xhc3NpZmljYXRpb24gJWluJSBjKCJWZXJ5IGhvdCIsICJIb3QiLCAiU29tZXdoYXQgaG90IiksIDEsIDApKSAlPiUgCiAgc3RfZHJvcF9nZW9tZXRyeSgpICU+JSAKICBsZWZ0X2pvaW4oaGVyb2luX25ldCwgLiwgYnkgPSAidW5pcXVlSUQiKQpgYGAKClRoZSBjbHVzdGVyIG1hcCBzaG93cyBzZXZlbiB0eXBlcyBvZiBzcGF0aWFsIGFzc29jaWF0aW9uIGRldGVybWluZWQgYmFzZWQgb24gaGVyb2luLXJlbGF0ZWQgb3ZlcmRvc2UgaW5jaWRlbnQgcmF0ZSB3aXRoaW4gYSBncmlkIGFuZCBpdHMgbmVpZ2hib3JpbmcgZ3JpZHMuIElmIGEgZ3JpZCBoYXMgYSBoaWdoIGhlcm9pbiBpbmNpZGVudCByYXRlIHdpdGggbmVpZ2hib3JpbmcgZ3JpZHMgb2YgaGlnaCBoZXJvaW4gaW5jaWRlbnQgcmF0ZXMsIGl0IHdhcyBkZXRlcm1pbmVkIGFzIGEgaG90IHNwb3QuIEF0IHRoZSBzYW1lIHNjaGVtZSwgaWYgYSBncmlkIHNob3dzIGEgbG93IGhlcm9pbiBpbmNpZGVudCByYXRlIHdpdGggbmVpZ2hib3JpbmcgZ3JpZHMgb2YgbG93IGhlcm9pbiBpbmNpZGVudCByYXRlcywgaXQgd2FzIGNsYXNzaWZpZWQgYXMgYSBjb2xkIHNwb3QuIEhvdHNwb3RzIHdpdGggcFw8MC4wMSBhcmUgY29uc2lkZXJlZCBhcyAiVmVyeSBob3QiLCB3aXRoIHBcPDAuMDUgYXJlIGNvbnNpZGVyZWQgYXMgIkhvdCIsIHdpdGggcFw8MC4xIGFyZSBjb25zaWVyZWQgYXMgIlNvbWV3aGF0IGhvdCIuIEFsbCBncmlkcyB0aGF0IGZhbGwgaW50byB0aGVzZSB0aHJlZSBjYXRlZ29yaWVzIHJlY2VpdmVkIGEgMSBmb3IgaG90c3BvdCwgb3RoZXJ3aXNlLCBpdCBnZXRzIGEgemVyby4gCgojIExvZ2lzdGljIFJlZ3Jlc3Npb24KCiMjIEJ1aWx0IEVudmlyb25tZW50IEZlYXR1cmVzCgpXZSBhZ2dyZWdhdGVkIGJ1aWx0IGVudmlyb25tZW50IGZlYXR1cmVzIGluIGFkZGl0aW9uIHRvIGNyaW1lIGFuZCAzMTEgY29tcGxhaW50cyBpbnRvIHRoZSBmaXNobmV0IG9mIENpbmNpbm5hdGkuIEZvciBjcmltZSBhbmQgY29tcGxhaW50cywgd2UgY291bnRlZCB0aGVpciBudW1iZXIgaW4gZWFjaCBncmlkLiBGb3IgaG9zcGl0YWxzLCByZWhhYmlsaXRhdGlvbiBjZW50ZXJzLCBwaGFybWFjaWVzLCBmYXN0IGZvb2QgcmVzdGF1cmFudHMsIGdhcyBzdGF0aW9ucywgYW5kIHB1YmxpYyBwYXJrcywgd2UgY2FsY3VsYXRlZCB0aGUgYXZlcmFnZSBkaXN0YW5jZSBmcm9tIHRoZSBjZW50ZXIgb2YgZWFjaCBncmlkIHRvIHRoZXNlIGxvY2F0aW9ucy4gRm9yIGRlbW9ncmFwaGljIHZhcmlhYmxlcywgd2Ugc3BhdGlhbGx5IGpvaW5lZCB0cmFjdCBsZXZlbCBpbnRvIHRoZSBncmlkcyBkZXBlbmRpbmcgb24gd2hpY2ggY2Vuc3VzIHRyYWN0IGVhY2ggY2VudHJvaWQgb2YgdGhlIGdyaWQgZmFsbCBpbnRvLiAgIAoKYGBge3IgYWRkaW5nIHJpc2sgZmFjdG9ycyB0byBuZXQsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9CgojIGFkZGluZyBjcmltZSB0byBuZXQKaGVyb2luX25ldCA8LSBoZXJvaW5fbmV0ICU+JSAKICBzdF9qb2luKGNyaW1lLCAuLCBqb2luPXN0X3dpdGhpbikgJT4lIAogIHN0X2Ryb3BfZ2VvbWV0cnkoKSAlPiUKICBncm91cF9ieSh1bmlxdWVJRCkgJT4lCiAgc3VtbWFyaXplKENyaW1lY291bnQgPSBuKCkpICU+JQogICAgbGVmdF9qb2luKGhlcm9pbl9uZXQsIC4gKSAlPiUKICAgIHN0X3NmKCkgJT4lCiAgbXV0YXRlKENyaW1lY291bnQgPSBpZmVsc2UoaXMubmEoQ3JpbWVjb3VudCksIDAsIENyaW1lY291bnQpKQoKIyBhZGQgMzExIHRvIG5ldApoZXJvaW5fbmV0IDwtIGhlcm9pbl9uZXQgJT4lIAogIHN0X2pvaW4oY29tcGxhaW50cywgLiwgam9pbj1zdF93aXRoaW4pICU+JSAKICBzdF9kcm9wX2dlb21ldHJ5KCkgJT4lCiAgZ3JvdXBfYnkodW5pcXVlSUQpICU+JQogIHN1bW1hcml6ZShDb21wbGFpbnRzY291bnQgPSBuKCkpICU+JQogICAgbGVmdF9qb2luKGhlcm9pbl9uZXQsIC4gKSAlPiUKICAgIHN0X3NmKCkgJT4lCiAgbXV0YXRlKENvbXBsYWludHNjb3VudCA9IGlmZWxzZShpcy5uYShDb21wbGFpbnRzY291bnQpLCAwLCBDb21wbGFpbnRzY291bnQpKQoKbmV0X2NlbnRyb2lkIDwtIHN0X2NlbnRyb2lkKGhlcm9pbl9uZXQpCgojIGFkZCBob3NwaXRhbHMKaGVyb2luX25ldCA8LSBoZXJvaW5fbmV0ICU+JSAKICBsZWZ0X2pvaW4obmV0X2NlbnRyb2lkICU+JSAKICAgICAgICAgICAgICBtdXRhdGUoaG9zcGl0YWwubm4gPSBubl9mdW5jdGlvbihzdF9jb29yZGluYXRlcyhuZXRfY2VudHJvaWQpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN0X2Nvb3JkaW5hdGVzKGhvc3BpdGFscyksIDEpKjAuMzA0OCkgJT4lIAogICAgICAgICAgICAgIHN0X2Ryb3BfZ2VvbWV0cnkoKSAlPiUgZHBseXI6OnNlbGVjdChob3NwaXRhbC5ubiwgdW5pcXVlSUQpLCAKICAgICAgICAgICAgYnkgPSAidW5pcXVlSUQiKQoKIyBhZGQgcmVoYWIgY2VudGVyCmhlcm9pbl9uZXQgPC0gaGVyb2luX25ldCAlPiUgCiAgbGVmdF9qb2luKG5ldF9jZW50cm9pZCAlPiUgCiAgICAgICAgICAgICAgbXV0YXRlKHJlaGFiLm5uID0gbm5fZnVuY3Rpb24oc3RfY29vcmRpbmF0ZXMobmV0X2NlbnRyb2lkKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdF9jb29yZGluYXRlcyhyZWhhYiksIDEpKjAuMzA0OCkgJT4lIAogICAgICAgICAgICAgIHN0X2Ryb3BfZ2VvbWV0cnkoKSAlPiUgZHBseXI6OnNlbGVjdChyZWhhYi5ubiwgdW5pcXVlSUQpLCAKICAgICAgICAgICAgYnkgPSAidW5pcXVlSUQiKQoKIyBhZGQgcGhhcm1hY3kKaGVyb2luX25ldCA8LSBoZXJvaW5fbmV0ICU+JSAKICBsZWZ0X2pvaW4obmV0X2NlbnRyb2lkICU+JSAKICAgICAgICAgICAgICBtdXRhdGUocGhhcm0ubm4gPSBubl9mdW5jdGlvbihzdF9jb29yZGluYXRlcyhuZXRfY2VudHJvaWQpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN0X2Nvb3JkaW5hdGVzKHBoYXJtYWN5KSwgMSkqMC4zMDQ4KSAlPiUgCiAgICAgICAgICAgICAgc3RfZHJvcF9nZW9tZXRyeSgpICU+JSBkcGx5cjo6c2VsZWN0KHBoYXJtLm5uLCB1bmlxdWVJRCksIAogICAgICAgICAgICBieSA9ICJ1bmlxdWVJRCIpCgoKIyBhZGQgZ2FzIHN0YXRpb24KaGVyb2luX25ldCA8LSBoZXJvaW5fbmV0ICU+JSAKICBsZWZ0X2pvaW4obmV0X2NlbnRyb2lkICU+JSAKICAgICAgICAgICAgICBtdXRhdGUoZnVlbC5ubiA9IG5uX2Z1bmN0aW9uKHN0X2Nvb3JkaW5hdGVzKG5ldF9jZW50cm9pZCksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RfY29vcmRpbmF0ZXMoZnVlbCksIDIpKjAuMzA0OCkgJT4lIAogICAgICAgICAgICAgIHN0X2Ryb3BfZ2VvbWV0cnkoKSAlPiUgZHBseXI6OnNlbGVjdChmdWVsLm5uLCB1bmlxdWVJRCksIAogICAgICAgICAgICBieSA9ICJ1bmlxdWVJRCIpCgojIGFkZCBmYXN0IGZvb2QgcmVzdGF1cmFudApoZXJvaW5fbmV0IDwtIGhlcm9pbl9uZXQgJT4lIAogIGxlZnRfam9pbihuZXRfY2VudHJvaWQgJT4lIAogICAgICAgICAgICAgIG11dGF0ZShmYXN0Lm5uID0gbm5fZnVuY3Rpb24oc3RfY29vcmRpbmF0ZXMobmV0X2NlbnRyb2lkKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdF9jb29yZGluYXRlcyhmYXN0Zm9vZCksIDIpKjAuMzA0OCkgJT4lIAogICAgICAgICAgICAgIHN0X2Ryb3BfZ2VvbWV0cnkoKSAlPiUgZHBseXI6OnNlbGVjdChmYXN0Lm5uLCB1bmlxdWVJRCksIAogICAgICAgICAgICBieSA9ICJ1bmlxdWVJRCIpCgojIGFkZCBwYXJrcwpoZXJvaW5fbmV0IDwtIGhlcm9pbl9uZXQgJT4lIAogIGxlZnRfam9pbihuZXRfY2VudHJvaWQgJT4lIAogICAgICAgICAgICAgIG11dGF0ZShwYXJrcy5ubiA9IG5uX2Z1bmN0aW9uKHN0X2Nvb3JkaW5hdGVzKG5ldF9jZW50cm9pZCksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RfY29vcmRpbmF0ZXMocGFya3MpLCAyKSowLjMwNDgpICU+JSAKICAgICAgICAgICAgICBzdF9kcm9wX2dlb21ldHJ5KCkgJT4lIGRwbHlyOjpzZWxlY3QocGFya3Mubm4sIHVuaXF1ZUlEKSwgCiAgICAgICAgICAgIGJ5ID0gInVuaXF1ZUlEIikKCgojIGFkZCBkZW1vZ3JhcGhpYyB2YXJzCmhlcm9pbl9uZXQgPC0gaGVyb2luX25ldCAlPiUgCiAgbGVmdF9qb2luKG5ldF9jZW50cm9pZCAlPiUgCiAgICAgICAgICAgICAgZHBseXI6OnNlbGVjdCh1bmlxdWVJRCkgJT4lIAogICAgICAgICAgICAgIHN0X2ludGVyc2VjdGlvbihjaW5jaW5uYXRpMjApICU+JSAKICAgICAgICAgICAgICBzdF9kcm9wX2dlb21ldHJ5KCkgJT4lIGRwbHlyOjpzZWxlY3QoLUdFT0lEKSwgYnkgPSAidW5pcXVlSUQiKSAlPiUgCiAgZmlsdGVyKGlzLm5hKHBvcDI1XzU0KSA9PSBGQUxTRSkKYGBgCgpTb21lIGV4cGxvcmF0b3J5IGRhdGEgYW5hbHlzaXMgd2VyZSBwZXJmb3JtZWQsIGluY2x1ZGluZyBjb21wdXRpbmcgdGhlIGNvcnJlbGF0aW9uIGJldHdlZW4gYWxsIG9mIG91ciBwcmVkaWN0b3IgdmFyaWFibGVzIGFuZCBoZXJvaW4gaW5jaWRlbmNlLiBUaGUgZmlndXJlIGJlbG93IHNob3dzIHRoYXQgYWxsIG9mIG91ciBwcmVkaWN0b3IgdmFyaWFibGVzIGFyZSBjb3JyZWxhdGVkIHdpdGggaGVyb2luLW92ZXJkb3NlIGluY2lkZW5jZSwgYW1vbmcgd2hpY2ggYWxsIG9mIG91ciBuZWFyZXN0IGRpc3RhbmNlIHZhcmlhYmxlcyBhcmUgbmVnYXRpdmVseSBjb3JyZWxhdGVkIHdpdGggaW5jaWRlbmNlLiBUaGlzIGluZGljYXRlcyB0aGF0IHRoZSBmdXJ0aGVyIGF3YXkgd2UgYXJlIGZyb20gZmFzdCBmb29kIHJlc3RhdXJhbnRzIGFuZCBnYXMgc3RhdGlvbnMsIGZvciBleGFtcGxlLCB0aGUgbGVzcyBudW1iZXIgb2YgaGVyb2luLW92ZXJkb3NlIGluY2lkZW5jZSBpbiB0aGF0IGFyZWEuIFRoZSByZWxhdGlvbnNoaXAgaXMgdGhlIHN0cm9uZ2VzdCBmb3IgY3JpbWUgaW5jaWRlbmNlIGFuZCB3ZWFrZXN0IGZvciBwb3B1bGF0aW9uIGFnZWQgYmV0d2VlbiAyNS01NC4gCgoKYGBge3IgY29yIGZvciBhbGwgcHJlZGljdG9ycywgZmlnLmhlaWdodD0xMiwgZmlnLndpZHRoPTgsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CgpoZXJvaW5fbmV0X2xvbmcgPC0gaGVyb2luX25ldCU+JSAKICBzdF9kcm9wX2dlb21ldHJ5KCkgJT4lIAogIGRwbHlyOjpzZWxlY3QoLWModW5pcXVlSUQsIGN2SUQsIGdpLCBwX2ZvbGRlZF9zaW0sIGNsYXNzaWZpY2F0aW9uLCBob3RzcG90LCBUb3RhbHBvcCkpICU+JSAKICBwaXZvdF9sb25nZXIoY29scyA9IC1jb3VudEhlcm9pbiwgIyBldmVyeXRoaW5nIGV4Y2VwdCBtZWFzdXJlbWVudAogICAgICAgICAgICAgICBuYW1lc190byA9ICJUeXBlIiwgIyBjYXRlZ29yaXplcyBhbGwgcXVhbnRpdGF0aXZlIHZhcmlhYmxlcyBpbnRvIFR5cGUKICAgICAgICAgICAgICAgdmFsdWVzX3RvID0gIk51bWJlciIpIAoKY29ycmVsYXRpb24uY29yIDwtCiAgaGVyb2luX25ldF9sb25nICU+JQogICAgZ3JvdXBfYnkoVHlwZSkgJT4lCiAgICBzdW1tYXJpemUoY29ycmVsYXRpb24gPSBjb3IoTnVtYmVyLCBjb3VudEhlcm9pbiwgdXNlID0gImNvbXBsZXRlLm9icyIpKQoKaGVyb2luX25ldF9sb25nICU+JQogIGdncGxvdChhZXMoeD0gTnVtYmVyLCB5ID0gY291bnRIZXJvaW4pKSArCiAgZ2VvbV9wb2ludChzaXplID0gMC4wMSwgY29sb3IgPSAiIzAwMDAwNCIpICsgIAogIGdlb21fdGV4dChkYXRhID0gY29ycmVsYXRpb24uY29yLCBhZXMobGFiZWwgPSBwYXN0ZSgiciA9Iiwgcm91bmQoY29ycmVsYXRpb24sIDIpKSksCiAgICAgICAgICAgIHg9LUluZiwgeT1JbmYsIHZqdXN0ID0gMS41LCBoanVzdCA9IC0uMSwgc2l6ZT0zKSArCiAgZ2VvbV9zbW9vdGgobWV0aG9kPSdsbScsIGZvcm11bGE9IHl+eCwgbHdkPTAuNSwgc2UgPSBGQUxTRSwgY29sb3IgPSAiI0JCMzc1NCIpICsKICBmYWNldF93cmFwKH4gVHlwZSwgc2NhbGVzID0gImZyZWUiLCBuY29sID0gMiwgbGFiZWxsZXI9IGxhYmVsbGVyKFR5cGUgPSBjKAogICAgYENvbXBsYWludHNjb3VudGAgPSAiQ29tcGxhaW50cyIsCiAgICBgQ3JpbWVjb3VudGAgPSAiQ3JpbWUiLAogICAgYGZhc3Qubm5gID0gIkRpc3RhbmNlIHRvIEZhc3QgRm9vZCIsCiAgICBgZnVlbC5ubmAgPSAiRGlzdGFuY2UgdG8gR2FzIFN0YXRpb25zIiwKICAgIGBob3NwaXRhbC5ubmAgPSAiRGlzdGFuY2UgdG8gSG9zcGl0YWwiLAogICAgYE1lZEhISW5jYCA9ICJNZWRpYW4gSG91c2Vob2xkIEluY29tZSIsCiAgICBgTUZfcmF0aW9gID0gIkdlbmRlciBSYXRpbyIsCiAgICBgcGFya3Mubm5gID0gIkRpc3RhbmNlIHRvIFBhcmtzIiwKICAgIGBwY3RCYWNoZWxvcmAgPSAiUGVyY2VudCBCYWNoZWxvcidzIERlZ3JlZSIsCiAgICBgcGN0UG92ZXJ0eWAgPSAiUG92ZXJ0eSBSYXRlIiwKICAgIGBwaGFybS5ubmAgPSAiRGlzdGFuY2UgdG8gUGhhcm1hY3kiLAogICAgYHBvcDI1XzU0YCA9ICJQb3B1bGF0aW9uIDI1XzU0IiwgCiAgICBgcmVoYWIubm5gID0gIkRpc3RhbmNlIHRvIFJlaGFiIENlbnRlciIsCiAgICBgcmFjZV9yYXRpb2AgPSAiUmFjaWFsIE1ham9yaXR5IG92ZXIgTWlub3JpdHkiKSkpICsKICBsYWJzKHRpdGxlID0gIlNjYXR0ZXIgUGxvdHMgb2YgQWxsIFByZWRpY3RvciBWYXJpYWJsZXMiKSArCiAgdGhlbWUocGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gOSxmYWNlID0gIml0YWxpYyIpLAogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSwgCiAgICAgICAgYXhpcy50ZXh0Lng9ZWxlbWVudF90ZXh0KHNpemU9NiksCiAgICAgICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF90ZXh0KHNpemU9NiksIAogICAgICAgIGF4aXMudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9OCksIAogICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9yZWN0KGNvbG91ciA9ICJncmV5IiwgZmlsbD1OQSwgc2l6ZT0wLjgpKQpgYGAKClRoZSBmaWd1cmUgYmVsb3cgc2hvd3MgdGhlIGRpZmZlcmVuY2UgaW4gdGhlIG51bWJlciBvZiBob3RzcG90cyBvciBjb2xkIHNwb3RzIGJhc2VkIG9uIGVhY2ggb2Ygb3VyIHByZWRpY3RpbmcgdmFyaWFibGUgYW5kIHdlIG1heSBzZWUgdGhhdCB0aGVyZSBzZWVtcyB0byBiZSBhIHNpZ25pZmljYW50IGRpZmZlcmVuY2UgaW4gYWxsIHNjZW5hcmlvcy4gCgpgYGB7ciBhbm92YSBmaWd1cmUsIGZpZy5oZWlnaHQ9MTAsIGZpZy53aWR0aD0xNSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KCmFub3ZhX2RhdGFzZXQgPC0gaGVyb2luX25ldCAlPiUgc3RfZHJvcF9nZW9tZXRyeSgpICU+JSAKICAgICAgICAgICAgICAgICAgICBkcGx5cjo6c2VsZWN0KGhvdHNwb3QsIENyaW1lY291bnQsIENvbXBsYWludHNjb3VudCwgcGhhcm0ubm4sIGhvc3BpdGFsLm5uLCBmYXN0Lm5uLCBmdWVsLm5uLCByZWhhYi5ubiwgcGFya3Mubm4sIHBvcDI1XzU0LCBNZWRISEluYywgTUZfcmF0aW8sIHJhY2VfcmF0aW8sIHBjdFBvdmVydHksIHBjdEJhY2hlbG9yKQoKYW5vdmFfZGF0YXNldCAlPiUKICBnYXRoZXIoVmFyaWFibGUsIHZhbHVlLCAtaG90c3BvdCkgJT4lCiAgICBnZ3Bsb3QoYWVzKGFzLmNoYXJhY3Rlcihob3RzcG90KSwgdmFsdWUsIGZpbGw9YXMuY2hhcmFjdGVyKGhvdHNwb3QpKSkgKyAKICAgICAgZ2VvbV9iYXIocG9zaXRpb24gPSAiZG9kZ2UiLCBzdGF0ID0gInN1bW1hcnkiLCBmdW4gPSAibWVhbiIpICsgCiAgICAgIGZhY2V0X3dyYXAoflZhcmlhYmxlLCBzY2FsZXMgPSAiZnJlZSIsIG5jb2wgPSA1LCBsYWJlbGxlcj0gbGFiZWxsZXIoVmFyaWFibGUgPSBjKAogICAgICAgIGBDb21wbGFpbnRzY291bnRgID0gIkNvbXBsYWludHMiLAogICAgICAgIGBDcmltZWNvdW50YCA9ICJDcmltZSIsCiAgICAgICAgYGZhc3Qubm5gID0gIkRpc3RhbmNlIHRvIEZhc3QgRm9vZCIsCiAgICAgICAgYGZ1ZWwubm5gID0gIkRpc3RhbmNlIHRvIEdhcyBTdGF0aW9ucyIsCiAgICBgaG9zcGl0YWwubm5gID0gIkRpc3RhbmNlIHRvIEhvc3BpdGFsIiwKICAgIGBNZWRISEluY2AgPSAiTWVkaWFuIEhvdXNlaG9sZCBJbmNvbWUiLAogICAgYE1GX3JhdGlvYCA9ICJHZW5kZXIgUmF0aW8iLAogICAgYHBhcmtzLm5uYCA9ICJEaXN0YW5jZSB0byBQYXJrcyIsCiAgICBgcGN0QmFjaGVsb3JgID0gIlBlcmNlbnQgQmFjaGVsb3IncyBEZWdyZWUiLAogICAgYHBjdFBvdmVydHlgID0gIlBvdmVydHkgUmF0ZSIsCiAgICBgcGhhcm0ubm5gID0gIkRpc3RhbmNlIHRvIFBoYXJtYWN5IiwKICAgIGBwb3AyNV81NGAgPSAiUG9wdWxhdGlvbiAyNV81NCIsIAogICAgYHJlaGFiLm5uYCA9ICJEaXN0YW5jZSB0byBSZWhhYiBDZW50ZXIiLAogICAgYHJhY2VfcmF0aW9gID0gIlJhY2lhbCBNYWpvcml0eSBvdmVyIE1pbm9yaXR5IikpKSArCiAgICAgIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGMoIiMwMDAwMDQiLCAgIiNCQjM3NTQiKSkgKwogICAgICBsYWJzKHg9IkhvdHNwb3QiLCB5PSJWYWx1ZSIsIAogICAgICAgICAgIHRpdGxlID0gIkZlYXR1cmUgQXNzb2NpYXRpb25zIHdpdGggdGhlIExpa2VsaWhvb2Qgb2YgT3ZlcmRvc2UgSG90c3BvdCIpICsKICAgICAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSArCiAgICB0aGVtZShwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMixmYWNlID0gIml0YWxpYyIpLAogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDE4LCBmYWNlID0gImJvbGQiKSwgCiAgICAgICAgYXhpcy50ZXh0Lng9ZWxlbWVudF90ZXh0KHNpemU9MTApLAogICAgICAgIGF4aXMudGV4dC55PWVsZW1lbnRfdGV4dChzaXplPTEwKSwgCiAgICAgICAgYXhpcy50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT05KSwgCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X3JlY3QoY29sb3VyID0gImdyZXkiLCBmaWxsPU5BLCBsaW5ld2lkdGggPTAuOCkpCmBgYAoKV2UgZnVydGhlciBjb25kdWN0ZWQgYSBzZXJpZXMgb2YgQU5PVkEgdGVzdHMgZm9yIGFsbCBwcmVkaWN0b3IgdmFyaWFibGVzIHRvIHZhbGlkYXRlIGlmIHRoZXJlJ3MgYSBzaWduaWZpY2FudCBkaWZmZXJlbmNlIGluIG1lYW4gbnVtYmVyIG9mIGNyaW1lcywgZm9yIGV4YW1wbGUsIGJldHdlZW4gY29sZCBzcG90IGFuZCBob3Qgc3BvdC4gVGhlIHJlc3VsdCBpcyBpbiBjb25jdXIgd2l0aCBvdXIgZXhwZWN0YXRpb25zOiBhbGwgdmFyaWFibGVzIGFyZSBzaWduaWZpY2FudCBwcmVkaWN0b3JzLgoKYGBge3IgYW5vdmEgdGVzdCwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KCmFub3ZhX3ZhciA8LSBjKCJDcmltZWNvdW50IiwgIkNvbXBsYWludHNjb3VudCIsICJwaGFybS5ubiIsICJob3NwaXRhbC5ubiIsICJmYXN0Lm5uIiwgImZ1ZWwubm4iLCAicmVoYWIubm4iLCAicGFya3Mubm4iLCAicG9wMjVfNTQiLCAiTWVkSEhJbmMiLCAiTUZfcmF0aW8iLCAicmFjZV9yYXRpbyIsICJwY3RQb3ZlcnR5IiwgInBjdEJhY2hlbG9yIikKCmFub3ZhX3Jlc3VsdHMgPC0gZGF0YS5mcmFtZSgKICAgICAgICAgICAgICAgICAgICAgICAgICAgIERmID0gaW50ZWdlcigpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgIFN1bV9TcSA9IG51bWVyaWMoKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBNZWFuX1NxID0gbnVtZXJpYygpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgIEZfdmFsdWUgPSBudW1lcmljKCksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgUF9WYWx1ZSA9IG51bWVyaWMoKSwgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFKQoKbmV3X25hbWVzIDwtIGMoIkNyaW1lY291bnQiID0gIkNyaW1lIiwKICAgICAgICAgICAgICAgICJDb21wbGFpbnRzY291bnQiID0gIkNvbXBsYWludHMiLAogICAgICAgICAgICAgICAgInBoYXJtLm5uIiA9ICJEaXN0YW5jZSB0byBQaGFybWFjeSIsCiAgICAgICAgICAgICAgICAiaG9zcGl0YWwubm4iID0gIkRpc3RhbmNlIHRvIEhvc3BpdGFsIiwKICAgICAgICAgICAgICAgICJmYXN0Lm5uIiA9ICJEaXN0YW5jZSB0byBGYXN0IEZvb2QiLAogICAgICAgICAgICAgICAgImZ1ZWwubm4iID0gIkRpc3RhbmNlIHRvIEdhcyBTdGF0aW9ucyIsCiAgICAgICAgICAgICAgICAicmVoYWIubm4iID0gIkRpc3RhbmNlIHRvIFJlaGFiIENlbnRlciIsIAogICAgICAgICAgICAgICAgInBhcmtzLm5uIiA9ICJEaXN0YW5jZSB0byBQYXJrcyIsIAogICAgICAgICAgICAgICAgInBvcDI1XzU0IiA9ICJQb3B1bGF0aW9uIDI1XzU0IiwgCiAgICAgICAgICAgICAgICAiTWVkSEhJbmMiID0gIk1lZGlhbiBIb3VzZWhvbGQgSW5jb21lIiwKICAgICAgICAgICAgICAgICJNRl9yYXRpbyIgPSAiR2VuZGVyIFJhdGlvIiwKICAgICAgICAgICAgICAgICJyYWNlX3JhdGlvIiA9ICJSYWNpYWwgTWFqb3JpdHkgb3ZlciBNaW5vcml0eSIsCiAgICAgICAgICAgICAgICAicGN0UG92ZXJ0eSIgPSAiUG92ZXJ0eSBSYXRlIiwgCiAgICAgICAgICAgICAgICAicGN0QmFjaGVsb3IiID0gIlBlcmNlbnQgQmFjaGVsb3IncyBEZWdyZWUiCiAgICAgICAgICAgICAgICkKCmZvciAodmFyIGluIGFub3ZhX3ZhcikgewogIGFub3ZhX3Jlc3VsdCA8LSBhb3YoYW5vdmFfZGF0YXNldFtbdmFyXV0gfiBhbm92YV9kYXRhc2V0JGhvdHNwb3QpCiAgc3VtbWFyeV9kYXRhIDwtIHN1bW1hcnkoYW5vdmFfcmVzdWx0KVtbMV1dWywgYygiRGYiLCAiU3VtIFNxIiwgIk1lYW4gU3EiLCAiRiB2YWx1ZSIsICJQcig+RikiKV1bMToxLCBdCiAgYW5vdmFfcmVzdWx0cyA8LSByYmluZChhbm92YV9yZXN1bHRzLCBzdW1tYXJ5X2RhdGEpCn0KCnJvd25hbWVzKGFub3ZhX3Jlc3VsdHMpIDwtIG5ld19uYW1lcwoKYW5vdmFfcmVzdWx0cyAlPiUgCiBrYWJsZSgpICU+JSAKIGthYmxlX3N0eWxpbmcoYm9vdHN0cmFwX29wdGlvbnMgPSBjKCJzdHJpcGVkIiwgImhvdmVyIiwgImNvbmRlbnNlZCIpKSAlPiUgCiAgICBmb290bm90ZShnZW5lcmFsX3RpdGxlID0gIlxuIikKCmBgYAoKIyMgU2ltcGxlIExvZ2l0CgpDb25zaWRlcmluZyB0aGF0IGFsbCBwcmVkaWN0b3IgdmFyaWFibGVzIHVzZWQgaW4gdGhlIGFub3ZhIHRlc3QgYXBwZWFyIHRvIGJlIHNpZ25pZmljYW50LCB3ZSBpbmNsdWRlZCBhbGwgb2YgdGhlbSBpbiBhIGtpdGNoZW4tc2luayBsb2dpc3RpYyByZWdyZXNzaW9uLiBUaGUgbW9kZWwgc2hvd3MgdGhhdCBpbiB0aGlzIHNjZW5hcmlvLCAqKm51bWJlciBvZiBjcmltZXMqKiwgKipudW1iZXIgb2YgY29tcGxhaW50cyoqLCAqKmRpc3RhbmNlIHRvIGhvc3BpdGFsKiosICoqZGlzdGFuY2UgdG8gZmFzdCBmb29kIHJlc3RhdXJhbnQqKiwgKipnZW5kZXIgcmF0aW8qKiwgYW5kICoqZGlzdGFuY2UgdG8gcHVibGljIHBhcmtzKiogYXJlIGhpZ2hseSBzaWduaWZpY2FudCBwcmVkaWN0b3JzIG9mIGhlcm9pbi1vdmVyZG9zZSBob3RzcG90IHdoZXJlYXMgb3RoZXIgcHJlZGljdG9ycyBiZWNvbWUgaW5zaWduaWZpY2FudC4gCgpgYGB7ciBsb2dpc3RpYyB3aXRob3V0IGN2LCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQoKa2l0Y2hlbnNpbmsgPC0gZ2xtKGhvdHNwb3QgfiAuLAogICAgICAgICAgICAgICAgICBkYXRhPWhlcm9pbl9uZXQgJT4lIAogICAgICAgICAgICAgICAgICAgIHN0X2Ryb3BfZ2VvbWV0cnkoKSAlPiUgCiAgICAgICAgICAgICAgICAgICAgZHBseXI6OnNlbGVjdChob3RzcG90LCBDcmltZWNvdW50LCBDb21wbGFpbnRzY291bnQsIHBoYXJtLm5uLCBob3NwaXRhbC5ubiwgZmFzdC5ubiwgZnVlbC5ubiwgcmVoYWIubm4sIHBhcmtzLm5uLCBwb3AyNV81NCwgTWVkSEhJbmMsIE1GX3JhdGlvLCByYWNlX3JhdGlvLCBwY3RQb3ZlcnR5LCBwY3RCYWNoZWxvciksIGZhbWlseT0iYmlub21pYWwiIChsaW5rPSJsb2dpdCIpKQoKCmtpdGNoZW5fc3VtIDwtIHN1bW1hcnkoa2l0Y2hlbnNpbmspCmNvZWZmaWNpZW50c190YWJsZSA8LSBhcy5kYXRhLmZyYW1lKGtpdGNoZW5fc3VtJGNvZWZmaWNpZW50cykKCmNvZWZmaWNpZW50c190YWJsZSRzaWduaWZpY2FuY2UgPC0gaWZlbHNlKGNvZWZmaWNpZW50c190YWJsZSRgUHIoPnx6fClgIDwgMC4wMDEsICcqKionLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmZWxzZShjb2VmZmljaWVudHNfdGFibGUkYFByKD58enwpYCA8IDAuMDEsICcqKicsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmZWxzZShjb2VmZmljaWVudHNfdGFibGUkYFByKD58enwpYCA8IDAuMDUsICcqJywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmZWxzZShjb2VmZmljaWVudHNfdGFibGUkYFByKD58enwpYCA8IDAuMSwgJy4nLCAnJykpKSkKCmNvZWZmaWNpZW50c190YWJsZSRwX3ZhbHVlIDwtIHBhc3RlMChyb3VuZChjb2VmZmljaWVudHNfdGFibGUkYFByKD58enwpYCwgZGlnaXRzID0gMyksIGNvZWZmaWNpZW50c190YWJsZSRzaWduaWZpY2FuY2UpCgpjb2VmZmljaWVudHNfdGFibGUgJT4lCiAgZHBseXI6OnNlbGVjdCgtc2lnbmlmaWNhbmNlLCAtYFByKD58enwpYCkgJT4lIAogIGthYmxlKGFsaWduID0gInIiKSAlPiUKICBrYWJsZV9zdHlsaW5nKGJvb3RzdHJhcF9vcHRpb25zID0gYygic3RyaXBlZCIsICJob3ZlciIsICJjb25kZW5zZWQiKSkgICU+JQogIGZvb3Rub3RlKGdlbmVyYWxfdGl0bGUgPSAiXG4iKQpgYGAKCiMjIExvZ2l0IHdpdGggQ1YKCldlIHdvdWxkIGxpa2UgdG8gcGVyZm9ybSBjcm9zcyB2YWxpZGF0aW9uIG9uIHRoaXMgbW9kZWwgdG8gbWVhc3VyZXMgaXRzIG92ZXJhbGwgcGVyZm9ybWFuY2UgYW5kIHJvYnVzdG5lc3MuIFdlIGRlY2lkZSB0byBtb2RpZnkgYSBjcm9zcyB2YWxpZGF0aW9uIGZ1bmN0aW9uIHRoYXQgd2FzIG9yaWdpbmFsbHkgYnVpbHQgb24gY2FzZXMgdXNpbmcgUG9pc3Npb24gcmVncmVzc2lvbiBieSBtYWtpbmcgaXQgc3VpdGFibGUgZm9yIHVzZSBpbiBvdXIgc2NlbmFyaW8gKGxvZ2lzdGljIHJlZ3Jlc3Npb24pLiBUaGlzIGZ1bmN0aW9uIHRha2VzIGluIGZvdXIgcGFyYW1ldGVyczogdGhlIGRhdGFzZXQsIGEgY3Jvc3MgdmFsaWRhdGlvbiBJRCwgd2hpY2ggaXQgY291bGQgdXNlIHRvIHNlcGFyYXRlIHRoZSBkYXRhc2V0IGludG8gdHJhaW5pbmcgYW5kIHRlc3RpbmcsIGEgbGlzdCBvZiBkZXBlbmRlbnQgdmFyaWFibGVzLCBhbmQgYSBpbmRlcGVuZGVudCB2YXJpYWJsZS4gSW4gb3VyIGNhc2UsIHdlIHVzZSBhIHJhbmRvbWx5IGdlbmVyYXRlZCBjdklEIGFzc29jaWF0ZWQgd2l0aCBlYWNoIGdyaWQgY2VsbCBmb3IgcmFuZG9tIGstZm9sZCBjcm9zcyB2YWxpZGF0aW9uLiBFYWNoIGdyb3VwIG9mIGdyaWRzIHdpdGggdGhlIHNhbWUgY3ZJRCBnZXRzIGhvbGQgb3V0IGR1cmluZyBvbmUgaXRlcmF0aW9uLiAKCgpgYGB7ciBsb2dpdCBmdW5jdGlvbn0KCmxvZ2l0Q1YgPC0gZnVuY3Rpb24oZGF0YXNldCwgaWQsIGRlcGVuZGVudFZhcmlhYmxlLCBpbmRWYXJpYWJsZXMpIHsKICAKICBhbGxQcmVkaWN0aW9ucyA8LSBkYXRhLmZyYW1lKCkKICBjdklEX2xpc3QgPC0gdW5pcXVlKGRhdGFzZXRbW2lkXV0pCgogIGZvciAoaSBpbiBjdklEX2xpc3QpIHsKICAgIAogICAgdGhpc0ZvbGQgPC0gaQoKICAgIGZvbGQudHJhaW4gPC0gZmlsdGVyKGRhdGFzZXQsIGRhdGFzZXRbW2lkXV0gIT0gdGhpc0ZvbGQpICU+JSBhcy5kYXRhLmZyYW1lKCkgJT4lIAogICAgICBkcGx5cjo6c2VsZWN0KGlkLCBnZW9tZXRyeSwgYWxsX29mKGluZFZhcmlhYmxlcyksCiAgICAgICAgICAgICAgICAgICAgYWxsX29mKGRlcGVuZGVudFZhcmlhYmxlKSkKICAgIGZvbGQudGVzdCAgPC0gZmlsdGVyKGRhdGFzZXQsIGRhdGFzZXRbW2lkXV0gPT0gdGhpc0ZvbGQpICU+JSBhcy5kYXRhLmZyYW1lKCkgJT4lIAogICAgICBkcGx5cjo6c2VsZWN0KGlkLCBnZW9tZXRyeSwgYWxsX29mKGluZFZhcmlhYmxlcyksCiAgICAgICAgICAgICAgICAgICAgYWxsX29mKGRlcGVuZGVudFZhcmlhYmxlKSkKICAgIAogICAgZm9ybV9wYXJ0cyA8LSBwYXN0ZTAoZGVwZW5kZW50VmFyaWFibGUsICIgfiAiLCBwYXN0ZTAoaW5kVmFyaWFibGVzLCBjb2xsYXBzZSA9ICIrIikpCiAgICBmb3JtIDwtIGFzLmZvcm11bGEoZm9ybV9wYXJ0cykKICAgIHJlZ3Jlc3Npb24gPC0gZ2xtKGZvcm0sIGRhdGEgPSBmb2xkLnRyYWluICU+JQogICAgICAgICAgICAgICAgICAgICAgICBkcGx5cjo6c2VsZWN0KC1nZW9tZXRyeSwgLWlkKSwgIGZhbWlseT0iYmlub21pYWwiIChsaW5rPSJsb2dpdCIpKQogICAgCiAgICB0aGlzUHJlZGljdGlvbiA8LQogICAgICBtdXRhdGUoZm9sZC50ZXN0LCBQcmVkaWN0aW9uID0gcHJlZGljdChyZWdyZXNzaW9uLCBmb2xkLnRlc3QsIHR5cGUgPSAicmVzcG9uc2UiKSkKICAgIAogICAgYWxsUHJlZGljdGlvbnMgPC0KICAgICAgcmJpbmQoYWxsUHJlZGljdGlvbnMsIHRoaXNQcmVkaWN0aW9uKQogICAgCiAgfQogIHJldHVybihzdF9zZihhbGxQcmVkaWN0aW9ucykpCn0KCmBgYAoKSGVyZSwgYW5vdGhlciBoZWxwZXIgZnVuY3Rpb24gaXMgd3JpdHRlbiB0byB2aXN1YWxpemUgdGhlIGNvbmZ1c2lvbiBtYXRyaXguIAoKYGBge3IgY20gZnVuY3Rpb259CgpkcmF3X2NvbmZ1c2lvbl9tYXRyaXggPC0gZnVuY3Rpb24oY20pIHsKCiAgbGF5b3V0KG1hdHJpeChjKDEsMSwyKSkpCiAgcGFyKG1hcj1jKDIsMiwyLDIpKQogIHBsb3QoYygxMDAsIDM0NSksIGMoMzAwLCA0NTApLCB0eXBlID0gIm4iLCB4bGFiPSIiLCB5bGFiPSIiLCB4YXh0PSduJywgeWF4dD0nbicpCiAgdGl0bGUoJ0NPTkZVU0lPTiBNQVRSSVgnLCBjZXgubWFpbj0yKQoKICAjIGNyZWF0ZSB0aGUgbWF0cml4IAogIHJlY3QoMTUwLCA0MzAsIDI0MCwgMzcwLCBjb2w9J2JsYWNrJykKICB0ZXh0KDE5NSwgNDM1LCAnMCcsIGNleD0xLjIpCiAgcmVjdCgyNTAsIDQzMCwgMzQwLCAzNzAsIGNvbD0iI0JCMzc1NCIpCiAgdGV4dCgyOTUsIDQzNSwgJzEnLCBjZXg9MS4yKQogIHRleHQoMTI1LCAzNzAsICdQcmVkaWN0ZWQnLCBjZXg9MS4zLCBzcnQ9OTAsIGZvbnQ9MikKICB0ZXh0KDI0NSwgNDUwLCAnQWN0dWFsJywgY2V4PTEuMywgZm9udD0yKQogIHJlY3QoMTUwLCAzMDUsIDI0MCwgMzY1LCBjb2w9IiNCQjM3NTQiKQogIHJlY3QoMjUwLCAzMDUsIDM0MCwgMzY1LCBjb2w9J2JsYWNrJykKICB0ZXh0KDE0MCwgNDAwLCAnMCcsIGNleD0xLjIsIHNydD05MCkKICB0ZXh0KDE0MCwgMzM1LCAnMScsIGNleD0xLjIsIHNydD05MCkKCiAgIyBhZGQgaW4gdGhlIGNtIHJlc3VsdHMgCiAgcmVzIDwtIGFzLm51bWVyaWMoY20kdGFibGUpCiAgdGV4dCgxOTUsIDQwMCwgcmVzWzFdLCBjZXg9MS42LCBmb250PTIsIGNvbD0nd2hpdGUnKQogIHRleHQoMTk1LCAzMzUsIHJlc1syXSwgY2V4PTEuNiwgZm9udD0yLCBjb2w9J3doaXRlJykKICB0ZXh0KDI5NSwgNDAwLCByZXNbM10sIGNleD0xLjYsIGZvbnQ9MiwgY29sPSd3aGl0ZScpCiAgdGV4dCgyOTUsIDMzNSwgcmVzWzRdLCBjZXg9MS42LCBmb250PTIsIGNvbD0nd2hpdGUnKQoKICAjIGFkZCBpbiB0aGUgc3BlY2lmaWNzIAogIHBsb3QoYygxMDAsIDApLCBjKDEwMCwgMCksIHR5cGUgPSAibiIsIHhsYWI9IiIsIHlsYWI9IiIsIG1haW4gPSAiREVUQUlMUyIsIHhheHQ9J24nLCB5YXh0PSduJykKICB0ZXh0KDEwLCA4NSwgbmFtZXMoY20kYnlDbGFzc1sxXSksIGNleD0xLjIsIGZvbnQ9MikKICB0ZXh0KDEwLCA3MCwgcm91bmQoYXMubnVtZXJpYyhjbSRieUNsYXNzWzFdKSwgMyksIGNleD0xLjIpCiAgdGV4dCgzMCwgODUsIG5hbWVzKGNtJGJ5Q2xhc3NbMl0pLCBjZXg9MS4yLCBmb250PTIpCiAgdGV4dCgzMCwgNzAsIHJvdW5kKGFzLm51bWVyaWMoY20kYnlDbGFzc1syXSksIDMpLCBjZXg9MS4yKQogIHRleHQoNTAsIDg1LCBuYW1lcyhjbSRieUNsYXNzWzVdKSwgY2V4PTEuMiwgZm9udD0yKQogIHRleHQoNTAsIDcwLCByb3VuZChhcy5udW1lcmljKGNtJGJ5Q2xhc3NbNV0pLCAzKSwgY2V4PTEuMikKICB0ZXh0KDcwLCA4NSwgbmFtZXMoY20kYnlDbGFzc1s2XSksIGNleD0xLjIsIGZvbnQ9MikKICB0ZXh0KDcwLCA3MCwgcm91bmQoYXMubnVtZXJpYyhjbSRieUNsYXNzWzZdKSwgMyksIGNleD0xLjIpCiAgdGV4dCg5MCwgODUsIG5hbWVzKGNtJGJ5Q2xhc3NbN10pLCBjZXg9MS4yLCBmb250PTIpCiAgdGV4dCg5MCwgNzAsIHJvdW5kKGFzLm51bWVyaWMoY20kYnlDbGFzc1s3XSksIDMpLCBjZXg9MS4yKQoKICAjIGFkZCBpbiB0aGUgYWNjdXJhY3kgaW5mb3JtYXRpb24gCiAgdGV4dCgzMCwgMzUsIG5hbWVzKGNtJG92ZXJhbGxbMV0pLCBjZXg9MS41LCBmb250PTIpCiAgdGV4dCgzMCwgMjAsIHJvdW5kKGFzLm51bWVyaWMoY20kb3ZlcmFsbFsxXSksIDMpLCBjZXg9MS40KQogIHRleHQoNzAsIDM1LCBuYW1lcyhjbSRvdmVyYWxsWzJdKSwgY2V4PTEuNSwgZm9udD0yKQogIHRleHQoNzAsIDIwLCByb3VuZChhcy5udW1lcmljKGNtJG92ZXJhbGxbMl0pLCAzKSwgY2V4PTEuNCkKfSAgCmBgYAoKVGhlIGNyb3NzIHZhbGlkYXRpb24gbW9kZWwgaXMgdmlzdWFsaXplZCBiZWxvdywgZnJvbSB3aGljaCB3ZSBzZWUgdGhhdCBvdXIgbW9kZWwgaGFzIGEgaGlnaCBhY2N1cmFjeSByYXRlIG9mIDAuOTM2IGFuZCBhIG1vZGVyYXRlIHNlbnNpdGl2aXR5IHJhdGUgb2YgMC43MjcuIFNlbnNpdGl2aXR5IGlzIHRoZSB0cnVlLXBvc2l0aXZlIHJhdGUsIHdoaWNoIGlzIHRoZSBwcm9wb3J0aW9uIG9mIGFjdHVhbCBob3RzcG90IGdyaWRzIHRoYXQgd2VyZSBjb3JyZWN0bHkgaWRlbnRpZmllZCBieSB0aGUgbW9kZWwuIEdpdmVuIHRoaXMgbW9kZWwncyBoaWdoIGFjY3VyYWN5IHJhdGUsIHRoaXMgbWVhbnMgdGhhdCBvdXIgbW9kZWwgaXMgbWlzc2luZyBvdXQgYSBmZXcgaGVvcmluLW92ZXJkb3NlIGhvdHNwb3RzLiAKCkhvd2V2ZXIsIG5vdGUgdGhhdCBpbiBDaW5jaW5uYXRpLCB0aGUgbnVtYmVyIG9mIGdyaWRzIHRoYXQgYXJlIGNsYXNzaWZpZWQgYXMgaG90c3BvdCBpcyBtdWNoIHNtYWxsZXIgdGhhbiB0aGUgbnVtYmVyIG9mIGdyaWRzIHRoYXQgYXJlIGNsYXNzaWZpZWQgYXMgY29sZHNwb3QuIFdoaWxlIHRoaXMgaXMgYSBnb29kIG5ld3MgZm9yIENpbmNpbm5hdGksIGxvZ2lzdGljIHJlZ3Jlc3Npb24gY2FuIGJlIHNlbnNpdGl2ZSB0byBjbGFzcyBpbWJhbGFuY2UsIG1lYW5pbmcgdGhhdCB0aGUgbW9kZWwgbWlnaHQgYmUgYmlhc2VkIHRvd2FyZHMgdGhlIG1ham9yaXR5IGNsYXNzLCBsZWFkaW5nIHRvIHBvb3IgcGVyZm9ybWFuY2Ugb24gdGhlIG1pbm9yaXR5IGNsYXNzLiAKCmBgYHtyIGxvZ2lzdGljIHdpdGggQ1YsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9CgpyZWcudmFycyA8LSBjKCJDcmltZWNvdW50IiwgIkNvbXBsYWludHNjb3VudCIsICJwaGFybS5ubiIsICJob3NwaXRhbC5ubiIsICJmYXN0Lm5uIiwgImZ1ZWwubm4iLCAicmVoYWIubm4iLCAicGFya3Mubm4iLCAicG9wMjVfNTQiLCAiTWVkSEhJbmMiLCAiTUZfcmF0aW8iLCAicmFjZV9yYXRpbyIsICJwY3RQb3ZlcnR5IiwgInBjdEJhY2hlbG9yIikKCgpraXRjaGVuc2luay5sb2dpdC5jdiA8LSBsb2dpdENWICgKICBkYXRhc2V0ID0gaGVyb2luX25ldCwKICBpZCA9ICJjdklEIiwKICBkZXBlbmRlbnRWYXJpYWJsZSA9ICJob3RzcG90IiwKICBpbmRWYXJpYWJsZXMgPSByZWcudmFycykgJT4lCiAgICBkcGx5cjo6c2VsZWN0KGN2SUQgPSBjdklELCBob3RzcG90LCBQcmVkaWN0aW9uLCBnZW9tZXRyeSkKCmtpdGNoZW5zaW5rLmxvZ2l0LmN2IDwtIGtpdGNoZW5zaW5rLmxvZ2l0LmN2ICU+JSAKICBtdXRhdGUoUHJlZGljdGlvbkNhdCAgPSBhcy5mYWN0b3IoaWZlbHNlKGtpdGNoZW5zaW5rLmxvZ2l0LmN2JFByZWRpY3Rpb24gPiAwLjUgLCAxLCAwKSkpICU+JSAKICBtdXRhdGUoaG90c3BvdCA9IGFzLmZhY3Rvcihob3RzcG90KSkKCgpjbV9iYXNpYyA8LSBjYXJldDo6Y29uZnVzaW9uTWF0cml4KGtpdGNoZW5zaW5rLmxvZ2l0LmN2JGhvdHNwb3QsIGtpdGNoZW5zaW5rLmxvZ2l0LmN2JFByZWRpY3Rpb25DYXQsIAogICAgICAgICAgICAgICAgICAgICAgIHBvc2l0aXZlID0gIjEiKQoKZHJhd19jb25mdXNpb25fbWF0cml4KGNtX2Jhc2ljKQoKYGBgCgojIFByaW5jaXBhbCBDb21wb25lbnQgQW5hbHlzaXMgKFBDQSkKCk9uZSBvZiB0aGUgcmVhc29ucyB3aHkgZWFjaCBpbmRpdmlkdWFsIHByZWRpY3RvciB2YXJpYWJsZSBpcyBjb3JyZWxhdGVkIHdpdGggb3ZlcmRvc2UgaG90c3BvdCBidXQgd2hlbiBjb21iaW5lZCBpbiBhIHJlZ3Jlc3Npb24sIHNvbWUgb2YgdGhlbSBiZWNvbWUgaW5zaWduaWZpY2FudCBpcyBiZWNhdXNlIG9mIG11bHRpY29sbGluZWFyaXR5LiBUaGlzIGNvbW1vbmx5IG9jY3VycyBpbiByZWdyZXNzaW9uIGFuYWx5c2lzIHdoZW4gcHJlZGljdG9yIHZhcmlhYmxlcyBhcmUgaGlnaGx5IGNvcnJlbGF0ZWQuIEl0IHdvbid0IGJlIHN1cnByaXNlIHRvIHNlZSB0aGF0IG1hbnkgb2YgdGhvc2UgZGVtb2dyYXBoaWMgdmFyaWFibGVzIGFyZSBoaWdobHkgY29ycmVsYXRlZCB3aXRoIGVhY2ggb3RoZXIuIEl0IGlzIHBvc3NpYmxlIHRoYXQgZGlzdGFuY2UgdG8gaG9zcGl0YWxzIGlzIGNvcnJlbGF0ZWQgd2l0aCBkaXN0YW5jZSB0byBwYXJrcywgZ2l2ZW4gdGhhdCB0aGVyZSBhcmUgbW9yZSB0aG9zZSBraW5kIG9mIHNlcnZpY2VzIGluIGRvd250b3duIGFyZWFzLiBTcmluaXZhc2FuLCBldCBhbC4gKDIwMjMpJ3MgYXBwcm9hY2ggdG8gdGhpcyBpc3N1ZSBpcyB0byB1c2UgUHJpbmNpcGFsIENvbXBvbmVudCBBbmFseXNpcyAoUENBKSBhcyBhIHByZXByb2Nlc3Npbmcgc3RlcCBiZWZvcmUgcnVubmluZyB0aGUgcmVncmVzc2lvbi4gIAoKCiMjIFBDIElkZW50aWZpY2F0aW9uCgpQQ0EgaXMgYSBkaW1lbnNpb24gcmVkdWN0aW9uIHRlY2huaXF1ZSB0aGF0IGlzIGNvbW1vbmx5IHVzZWQgdG8gcmVkdWNlIHRoZSBudW1iZXIgb2YgdmFyaWFibGVzIHVzZWQgaW4gYW5hbHlzZXMgd2hpbGUgcHJlc2VydmluZyB0aGUgaW5mb3JtYXRpb24gZnJvbSB0aGVtLiBUaGUgYHByY29tcGAgZnVuY3Rpb24gYmVsb3cgaXMgdXNlZCB0byBpZGVudGlmeSB0aGUgcHJpbmNpcGFsIGNvbXBvbmVudHMgYW1vbmcgYWxsIDE0IG9mIG91ciBwcmVkaWN0b3IgdmFyaWFibGVzLiAKCmBgYHtyIGRldGVybWluZSBwYywgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0KCnBjYV9oZXJvaW4gPC0gaGVyb2luX25ldCAlPiUgCiAgc3RfZHJvcF9nZW9tZXRyeSgpICU+JSAKICBkcGx5cjo6c2VsZWN0KENyaW1lY291bnQsIENvbXBsYWludHNjb3VudCwgcGhhcm0ubm4sIGhvc3BpdGFsLm5uLCBmYXN0Lm5uLCBmdWVsLm5uLCByZWhhYi5ubiwgcGFya3Mubm4sIHBvcDI1XzU0LCBNZWRISEluYywgTUZfcmF0aW8sIHJhY2VfcmF0aW8sIHBjdFBvdmVydHksIHBjdEJhY2hlbG9yKQoKcGNhX2hlcm9pbiA8LSBuYS5vbWl0KHBjYV9oZXJvaW4pCnBjIDwtIHByY29tcChwY2FfaGVyb2luLAogICAgICAgICAgICAgY2VudGVyID0gVFJVRSwKICAgICAgICAgICAgc2NhbGUuID0gVFJVRSkKYGBgCgpUaGUgc2NyZWUgcGxvdCBpcyB1c2VkIHRvIHZpc3VhbGl6ZSB0aGUgaW1wb3J0YW5jZSBvZiBlYWNoIHByaW5jaXBhbCBjb21wb25lbnQgYW5kIGNhbiBiZSB1c2VkIHRvIGRldGVybWluZSB0aGUgbnVtYmVyIG9mIHByaW5jaXBhbCBjb21wb25lbnRzIHRvIHJldGFpbi4gVGhlIHkgYXhpcyBpcyBlaWdlbnZhbHVlcywgd2hpY2ggZXNzZW50aWFsbHkgc3RhbmQgZm9yIHRoZSBhbW91bnQgb2YgdmFyaWF0aW9uLiBIZXJlLCB3ZSBtYXkgc2VlIHRoYXQgdGhlIGZpcnN0IHNpeCwgb3Igc2V2ZW4gY29tcG9uZW50cyBhcmUgY2FwdHVyaW5nIHRoZSBtYWpvcml0eSBvZiB2YXJpYWJpbGl0eS4gV2UgY2FuIHRoZXJlZm9yZSwganVzdCB1c2UgdGhlIGZpcnN0IHNldmVuIHByaW5jaXBhbCBjb21wb25lbnRzIGFuZCBpZ25vcmUgdGhlIHJlc3QuIAoKYGBge3IgcGNhIHZpejEsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9Cgpmdml6X2VpZyhwYywgYWRkbGFiZWxzID0gVFJVRSwgYmFyZmlsbCA9ICIjQkIzNzU0IiwgYmFyY29sb3IgPSAidHJhbnNwYXJlbnQiLCB4bGFiID0gIlByaW5jaXBhbCBDb21wb25lbnRzIiwgY2hvaWNlID0gImVpZ2VudmFsdWUiKQoKYGBgCgpUaGUgY29kZSBhYm92ZSBjb21wdXRlZCB0aGUgc3F1YXJlIGNvc2luZSB2YWx1ZSBmb3IgZWFjaCB2YXJpYWJsZSB3aXRoIHJlc3BlY3QgdG8gdGhlIGZpcnN0IHNldmVuIHByaW5jaXBhbCBjb21wb25lbnRzLiBGcm9tIHRoZSBpbGx1c3RyYXRpb24gYmVsb3csIGdlbmRlciByYXRpbywgcG9wdWxhdGlvbiBhZ2VkIGJldHdlZW4gMjUtNTQsIGRpc3RhbmNlIHRvIHBhcmtzLCBhbmQgcG92ZXJ0eSByYXRlIGFyZSB0b3AgZm91ciB2YXJpYWJsZXMgd2l0aCB0aGUgaGlnaGVzdCBjb3MyLCBoZW5jZSBjb250cmlidXRpbmcgdGhlIG1vc3QgdG8gdGhlIHRvcCBzZXZlbiBwcmluY2lwYWwgY29tcG9uZW50cy4gCgpgYGB7ciBwY2Egdml6MiwgIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9Cgpmdml6X2NvczIocGMsIGNob2ljZSA9ICJ2YXIiLCBheGVzID0gMTo3KSArCiAgdGhlbWVfbWluaW1hbCgpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpKSArCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIGZpbGwgPSAiI0JCMzc1NCIsIGNvbG9yID0gInRyYW5zcGFyZW50IikKCmBgYAoKV2UgYWxzbyBkaWQgYSBiaXBsb3QgYmFzZWQgb24gb3VyIHByaW5jaXBhbCBjb21wb25lbnQgYW5hbHlzaXMsIHdoaWNoIGhlbHBzIHVzIHRvIHZpc3VhbGl6ZSB0aGUgc2ltaWxhcml0aWVzIGFuZCBkaXNzaW1pbGFyaXRpZXMgYmV0d2VlbiB0aGUgc2FtcGxlcywgYW5kIGZ1cnRoZXIgdW5kZXJzdGFuZCB0aGUgaW1wYWN0IG9mIGVhY2ggYXR0cmlidXRlIG9uIGVhY2ggb2YgdGhlIHByaW5jaXBhbCBjb21wb25lbnRzLiBIZXJlLCB0aGUgbGVuZ3RoIG9mIHRoZSBhcnJvdyByZXByZXNlbnRzIHRoZSBzdHJlbmd0aCBvZiB0aGUgY29udHJpYnV0aW9uIG9mIHRoZSBjb3JyZXNwb25kaW5nIHZhcmlhYmxlIHRvIHRoZSBzcGVjaWZpYyBwcmluY2lwYWwgY29tcG9uZW50LiBUaGUgY29sb3Igb2YgdGhlIGFycm93IHJlcHJlc2VudHMgdGhlIHJlbGF0aXZlIGNvbnRyaWJ1dGlvbiBvZiB0aGUgdmFyaWFibGUgdG8gdGhlIHByaW5jaXBhbCBjb21wb25lbnQuIERhcmtlciBjb2xvcnMgdHlwaWNhbGx5IGluZGljYXRlIGxhcmdlciBjb250cmlidXRpb25zLiBGb3IgdmFyaWFibGVzIHdpdGggbG9uZyBhcnJvdywgYnV0IGxpZ2h0IGNvbG9yLCBzdWNoIGFzIHBlcmNlbnRhZ2Ugb2YgcG9wdWxhdGlvbiB3aXRoIGEgQmFjaGVsb3IncyBkZWdyZWUgYW5kIHBlcmNlbnRhZ2UgcG92ZXJ0eSwgdGhlaXIgY29udHJpYnV0aW9ucyB3ZXJlIGxlc3Mgc2lnbmlmaWNhbnQgY29tcGFyZWQgdG8gb3RoZXIgdmFyaWFibGVzLCBhbmQgbWlnaHQgb25seSBjb250cmlidXRlIHNpZ25pZmljYW50bHkgdG8gb25lIFBDLiBGb3IgdmFyaWFibGVzIHdpdGggc2hvcnQgYXJyb3cgYnV0IGRhcmsgY29sb3IsIHN1Y2ggYXMgMzExIGNvbXBsYWludHMgYW5kIGNyaW1lLCB0aGV5IGhhdmUgcmVsYXRpdmVseSBzdHJvbmcgY29udHJpYnV0aW9ucyB0byBhbGwgUEMuIEFsc28sIHRoZSBkaXJlY3Rpb24gb2YgdGhvc2UgYXJyb3dzIGluZGljYXRlIHRoYXQgY3JpbWVzIGFuZCBjb21wbGFpbnRzIGhhdmUgcG9zaXRpdmUgY29udHJpYnV0aW9ucyB0byBob3RzcG90cy4gCgpgYGB7ciBwY2Egdml6MywgZmlnLmhlaWdodD02LCBmaWcud2lkdGg9OSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KCmZ2aXpfcGNhX3ZhcihwYywgY29sLnZhciA9ICJjb3MyIiwgcmVwZWwgPSBUUlVFLCAKICAgICAgICAgICAgIGFycm93c2l6ZSA9IDEsICBnZ3RoZW1lID0gdGhlbWVfbWluaW1hbCgpLCBncmFkaWVudC5jb2xzID0gYygiYmxhY2siLCAiI0JCMzc1NCIpLCkgKyBnZ3RpdGxlKCJCaXBsb3QgQ29tYmluZCB3aXRoIENvbnRyaWJ1dGlvbiIpCgoKYGBgCgpCZWxvdywgd2UgY29tcHV0ZWQgdGhlIGZpcnN0IHNldmVuIHByaW5jaXBhbCBjb21wb25lbnRzIGJhc2VkIG9uIHRoZSBsb2FkaW5ncyBvZiBlYWNoIG9mIG91ciBwcmVkaWN0b3IgdmFyaWFibGVzIGluIHRoYXQgY29tcG9uZW50LiAKCmBgYHtyIGNvbXB1dGUgcGNhLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQoKcGNhX25ldCA8LSBoZXJvaW5fbmV0ICU+JSAKICBtdXRhdGUocGMxID0gQ3JpbWVjb3VudCAqIHBjJHJvdGF0aW9uWzFdICsgQ29tcGxhaW50c2NvdW50ICogcGMkcm90YXRpb25bMl0gKyBwaGFybS5ubiAqIHBjJHJvdGF0aW9uWzNdICsgaG9zcGl0YWwubm4gKiBwYyRyb3RhdGlvbls0XSArIGZhc3Qubm4gKiBwYyRyb3RhdGlvbls1XSArICBmdWVsLm5uICogcGMkcm90YXRpb25bNl0gKyByZWhhYi5ubiAqIHBjJHJvdGF0aW9uWzddICsgcGFya3Mubm4gKiAgcGMkcm90YXRpb25bOF0gKyBwb3AyNV81NCAqIHBjJHJvdGF0aW9uWzldICsgTWVkSEhJbmMgKiBwYyRyb3RhdGlvblsxMF0gKyBNRl9yYXRpbyAqIHBjJHJvdGF0aW9uWzExXSArIHJhY2VfcmF0aW8gKiBwYyRyb3RhdGlvblsxMl0gKyAgcGN0UG92ZXJ0eSAqIHBjJHJvdGF0aW9uWzEzXSArIHBjdEJhY2hlbG9yICogcGMkcm90YXRpb25bMTRdKSAlPiUgCiAgbXV0YXRlKHBjMiA9IENyaW1lY291bnQgKiBwYyRyb3RhdGlvblsxNV0gKyBDb21wbGFpbnRzY291bnQgKiBwYyRyb3RhdGlvblsxNl0gKyBwaGFybS5ubiAqIHBjJHJvdGF0aW9uWzE3XSArIGhvc3BpdGFsLm5uICogcGMkcm90YXRpb25bMThdICsgZmFzdC5ubiAqIHBjJHJvdGF0aW9uWzE5XSArICBmdWVsLm5uICogcGMkcm90YXRpb25bMjBdICsgcmVoYWIubm4gKiBwYyRyb3RhdGlvblsyMV0gKyBwYXJrcy5ubiAqICBwYyRyb3RhdGlvblsyMl0gKyBwb3AyNV81NCAqIHBjJHJvdGF0aW9uWzIzXSArIE1lZEhISW5jICogcGMkcm90YXRpb25bMjRdICsgTUZfcmF0aW8gKiBwYyRyb3RhdGlvblsyNV0gKyByYWNlX3JhdGlvICogcGMkcm90YXRpb25bMjZdICsgIHBjdFBvdmVydHkgKiBwYyRyb3RhdGlvblsyN10gKyBwY3RCYWNoZWxvciAqIHBjJHJvdGF0aW9uWzI4XSkgJT4lCiAgbXV0YXRlKHBjMyA9IENyaW1lY291bnQgKiBwYyRyb3RhdGlvblsyOV0gKyBDb21wbGFpbnRzY291bnQgKiBwYyRyb3RhdGlvblszMF0gKyBwaGFybS5ubiAqIHBjJHJvdGF0aW9uWzMxXSArIGhvc3BpdGFsLm5uICogcGMkcm90YXRpb25bMzJdICsgZmFzdC5ubiAqIHBjJHJvdGF0aW9uWzMzXSArICBmdWVsLm5uICogcGMkcm90YXRpb25bMzRdICsgcmVoYWIubm4gKiBwYyRyb3RhdGlvblszNV0gKyBwYXJrcy5ubiAqICBwYyRyb3RhdGlvblszNl0gKyBwb3AyNV81NCAqIHBjJHJvdGF0aW9uWzM3XSArIE1lZEhISW5jICogcGMkcm90YXRpb25bMzhdICsgTUZfcmF0aW8gKiBwYyRyb3RhdGlvblszOV0gKyByYWNlX3JhdGlvICogcGMkcm90YXRpb25bNDBdICsgIHBjdFBvdmVydHkgKiBwYyRyb3RhdGlvbls0MV0gKyBwY3RCYWNoZWxvciAqIHBjJHJvdGF0aW9uWzQyXSkgJT4lCiAgbXV0YXRlKHBjNCA9IENyaW1lY291bnQgKiBwYyRyb3RhdGlvbls0M10gKyBDb21wbGFpbnRzY291bnQgKiBwYyRyb3RhdGlvbls0NF0gKyBwaGFybS5ubiAqIHBjJHJvdGF0aW9uWzQ1XSArIGhvc3BpdGFsLm5uICogcGMkcm90YXRpb25bNDZdICsgZmFzdC5ubiAqIHBjJHJvdGF0aW9uWzQ3XSArICBmdWVsLm5uICogcGMkcm90YXRpb25bNDhdICsgcmVoYWIubm4gKiBwYyRyb3RhdGlvbls0OV0gKyBwYXJrcy5ubiAqICBwYyRyb3RhdGlvbls1MF0gKyBwb3AyNV81NCAqIHBjJHJvdGF0aW9uWzUxXSArIE1lZEhISW5jICogcGMkcm90YXRpb25bNTJdICsgTUZfcmF0aW8gKiBwYyRyb3RhdGlvbls1M10gKyByYWNlX3JhdGlvICogcGMkcm90YXRpb25bNTRdICsgIHBjdFBvdmVydHkgKiBwYyRyb3RhdGlvbls1NV0gKyBwY3RCYWNoZWxvciAqIHBjJHJvdGF0aW9uWzU2XSkgJT4lCiAgbXV0YXRlKHBjNSA9IENyaW1lY291bnQgKiBwYyRyb3RhdGlvbls1N10gKyBDb21wbGFpbnRzY291bnQgKiBwYyRyb3RhdGlvbls1OF0gKyBwaGFybS5ubiAqIHBjJHJvdGF0aW9uWzU5XSArIGhvc3BpdGFsLm5uICogcGMkcm90YXRpb25bNjBdICsgZmFzdC5ubiAqIHBjJHJvdGF0aW9uWzYxXSArICBmdWVsLm5uICogcGMkcm90YXRpb25bNjJdICsgcmVoYWIubm4gKiBwYyRyb3RhdGlvbls2M10gKyBwYXJrcy5ubiAqICBwYyRyb3RhdGlvbls2NF0gKyBwb3AyNV81NCAqIHBjJHJvdGF0aW9uWzY1XSArIE1lZEhISW5jICogcGMkcm90YXRpb25bNjZdICsgTUZfcmF0aW8gKiBwYyRyb3RhdGlvbls2N10gKyByYWNlX3JhdGlvICogcGMkcm90YXRpb25bNjhdICsgIHBjdFBvdmVydHkgKiBwYyRyb3RhdGlvbls2OV0gKyBwY3RCYWNoZWxvciAqIHBjJHJvdGF0aW9uWzcwXSkgJT4lCiAgbXV0YXRlKHBjNiA9IENyaW1lY291bnQgKiBwYyRyb3RhdGlvbls3MV0gKyBDb21wbGFpbnRzY291bnQgKiBwYyRyb3RhdGlvbls3Ml0gKyBwaGFybS5ubiAqIHBjJHJvdGF0aW9uWzczXSArIGhvc3BpdGFsLm5uICogcGMkcm90YXRpb25bNzRdICsgZmFzdC5ubiAqIHBjJHJvdGF0aW9uWzc1XSArICBmdWVsLm5uICogcGMkcm90YXRpb25bNzZdICsgcmVoYWIubm4gKiBwYyRyb3RhdGlvbls3N10gKyBwYXJrcy5ubiAqICBwYyRyb3RhdGlvbls3OF0gKyBwb3AyNV81NCAqIHBjJHJvdGF0aW9uWzc5XSArIE1lZEhISW5jICogcGMkcm90YXRpb25bODBdICsgTUZfcmF0aW8gKiBwYyRyb3RhdGlvbls4MV0gKyByYWNlX3JhdGlvICogcGMkcm90YXRpb25bODJdICsgIHBjdFBvdmVydHkgKiBwYyRyb3RhdGlvbls4M10gKyBwY3RCYWNoZWxvciAqIHBjJHJvdGF0aW9uWzg0XSkgJT4lCiAgbXV0YXRlKHBjNyA9IENyaW1lY291bnQgKiBwYyRyb3RhdGlvbls4NV0gKyBDb21wbGFpbnRzY291bnQgKiBwYyRyb3RhdGlvbls4Nl0gKyBwaGFybS5ubiAqIHBjJHJvdGF0aW9uWzg3XSArIGhvc3BpdGFsLm5uICogcGMkcm90YXRpb25bODhdICsgZmFzdC5ubiAqIHBjJHJvdGF0aW9uWzg5XSArICBmdWVsLm5uICogcGMkcm90YXRpb25bOTBdICsgcmVoYWIubm4gKiBwYyRyb3RhdGlvbls5MV0gKyBwYXJrcy5ubiAqICBwYyRyb3RhdGlvbls5Ml0gKyBwb3AyNV81NCAqIHBjJHJvdGF0aW9uWzkzXSArIE1lZEhISW5jICogcGMkcm90YXRpb25bOTRdICsgTUZfcmF0aW8gKiBwYyRyb3RhdGlvbls5NV0gKyByYWNlX3JhdGlvICogcGMkcm90YXRpb25bOTZdICsgIHBjdFBvdmVydHkgKiBwYyRyb3RhdGlvbls5N10gKyBwY3RCYWNoZWxvciAqIHBjJHJvdGF0aW9uWzk4XSkKICAKCmBgYAoKIyMgUENBIExvZ2lzdGljIFJlZ3Jlc3Npb24KCkZvbGxvd2luZyBTcmluaXZhc2FuLCBldCBhbC4gKDIwMjMpJ3MgbWV0aG9kLCBub3cgd2UgY2FuIGVzdGltYXRlZCB0aGlzIGxvZ2lzdGljIHJlZ3Jlc3Npb24gdG8gcHJlZGljdCBpZiBhIGdyaWQgd2FzIGluIGEgaG90c3BvdCAoMC8xKSBhcyBpZGVudGlmaWVkIGJ5IHRoZSBHZXRpcy1PcmQgR2lcKiBzdGF0aXN0aWMgdXNpbmcgY29tcG9uZW50cyBkZXJpdmVkIGZyb20gdGhlIFBDQSBhcyB0aGUgZXhwbGFuYXRvcnkgdmFyaWFibGVzLiBGb3IgdGhpcyBtb2RlbCB1c2luZyB0aGUgcHJpbmNpcGFsIGNvbXBvbmVudHMgYXMgcHJlZGljdG9ycywgd2Ugc3RpbGwgZ2V0IGEgYWNjdXJhY3kgcmF0ZSBvZiAwLjkyOC4gT3VyIHNlbnNpdGl2aXR5IHJhdGUgZHJvcHBlZCBhIGxpdHRsZSBiaXQgdG8gMC42NiBjb21wYXJlZCB0byB0aGUga2l0Y2hlbiBzaW5rIG1vZGVsLCBwcm9iYWJseSBiZWNhdXNlIHdlIGFkZHJlc3NlZCB0aGUgaXNzdWUgb2YgbXVsdGljb2xpbmVhcml0eSwgIGJ1dCB0aGF0IGlzIHN0aWxsIGNvbnNpZGVyZWQgYXMgYSBtb2RlcmF0ZSBzZW5zaXRpdml0eS4gCgpgYGB7ciB1c2luZyBwY2EgaW4gbG9naXN0aWMgcmVncmVzc2lvbiwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KCnJlZy52YXJzIDwtIGMoInBjMSIsICJwYzIiLCAicGMzIiwgInBjNCIsICJwYzUiLCAicGM2IiwgInBjNyIpCgpwY2EubG9naXQuY3YgPC0gbG9naXRDViAoCiAgZGF0YXNldCA9IHBjYV9uZXQsCiAgaWQgPSAiY3ZJRCIsCiAgZGVwZW5kZW50VmFyaWFibGUgPSAiaG90c3BvdCIsCiAgaW5kVmFyaWFibGVzID0gcmVnLnZhcnMpICU+JQogICAgZHBseXI6OnNlbGVjdChjdklEID0gY3ZJRCwgaG90c3BvdCwgUHJlZGljdGlvbiwgZ2VvbWV0cnkpCgpwY2EubG9naXQuY3YgPC0gcGNhLmxvZ2l0LmN2ICU+JSAKICBtdXRhdGUoUHJlZGljdGlvbkNhdCAgPSBhcy5mYWN0b3IoaWZlbHNlKHBjYS5sb2dpdC5jdiRQcmVkaWN0aW9uID4gMC41ICwgMSwgMCkpKSAlPiUgCiAgbXV0YXRlKGhvdHNwb3QgPSBhcy5mYWN0b3IoaG90c3BvdCkpCgpjbV9wY2EgPC0gY2FyZXQ6OmNvbmZ1c2lvbk1hdHJpeChwY2EubG9naXQuY3YkaG90c3BvdCwgcGNhLmxvZ2l0LmN2JFByZWRpY3Rpb25DYXQsIAogICAgICAgICAgICAgICAgICAgICAgIHBvc2l0aXZlID0gIjEiKQoKZHJhd19jb25mdXNpb25fbWF0cml4KGNtX3BjYSkKCmBgYAoKSW4gYWRkaXRpb24gdG8gY3Jvc3MtdmFsaWRhdGluZyB1c2luZyByYW5kb20gY3ZJRCwgd2UgYWxzbyB0cmllZCB0aGUgc3BhdGlhbCDigJhMZWF2ZS1vbmUtZ3JvdXAtb3V04oCZIGNyb3NzLXZhbGlkYXRpb24gKExPR08tQ1YpIGFwcHJvYWNoLCBkdXJpbmcgd2hpY2ggd2UgaG9sZCBvdXQgb25lIG5laWdoYm9yaG9vZCwgdHJhaW4gdGhlIG1vZGVsIG9uIHRoZSByZW1haW5pbmcgbi0xIGFyZWFzLCBwcmVkaWN0IGZvciB0aGUgaG9sZCBvdXQsIGFuZCByZWNvcmQgdGhlIGdvb2RuZXNzIG9mIGZpdC4gVGhlIHJlc3VsdCBzaG93cyB0aGF0IG91ciBtb2RlbCBoYXMgaGlnaCBhY2N1cmFjeSwgYnV0IHRoZSBzZW5zaXRpdml0eSBmdXJ0aGVyIGRlY3JlYXNlcy4gT25lIHBvc3NpYmxlIGV4cGxhbmF0aW9uIGhlcmUgaXMgdGhhdCBkaWZmZXJlbnQgbmVpZ2hib3Job29kcyBtYXkgaGF2ZSBkaXN0aW5jdCBjaGFyYWN0ZXJpc3RpY3Mgb3IgcGF0dGVybnMuIElmIHRoZSBtb2RlbCBpcyB0cmFpbmVkIG9uIGEgc2V0IG9mIG5laWdoYm9yaG9vZHMgYW5kIHRoZW4gdGVzdGVkIG9uIGEgbmVpZ2hib3Job29kIHdpdGggZGlmZmVyZW50IGNoYXJhY3RlcmlzdGljcywgaXRzIHBlcmZvcm1hbmNlIG1heSBiZSBuZWdhdGl2ZWx5IGFmZmVjdGVkLiBSYW5kb20gY3Jvc3MtdmFsaWRhdGlvbiBJRHMgbWlnaHQgbm90IGNhcHR1cmUgdGhlc2Ugc3BhdGlhbCB2YXJpYXRpb25zIGFzIGV4cGxpY2l0bHksIGJ1dCBmdXJ0aGVyIGludmVzdGlnYXRpb24gaXMgcmVxdWlyZWQgaGVyZS4gCgpgYGB7ciB1c2luZyBwY2EgaW4gbG9naXN0aWMgcmVncmVzc2lvbiB3aXRoIG5laWdoYm9yaG9vZCwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgcmVzdWx0cz0naGlkZSd9CgpuZWlnaGJvcmhvb2QgPC0gc3RfcmVhZChoZXJlKCJkYXRhIiwgInB1YmxpYyIsICJuZWlnaGJvcmhvb2QuZ2VvanNvbiIpKSAlPiUgc3RfdHJhbnNmb3JtKCJFUFNHOjM3MzUiKSAlPiUgc3Rfem0oKQpwY2FfbmV0IDwtIHBjYV9uZXQgJT4lCiAgbGVmdF9qb2luKG5ldF9jZW50cm9pZCAlPiUgZHBseXI6OnNlbGVjdCh1bmlxdWVJRCkgJT4lIAogICAgICAgICAgICAgICBzdF9pbnRlcnNlY3Rpb24obmVpZ2hib3Job29kICU+JSBkcGx5cjo6c2VsZWN0KFNOQV9OQU1FKSkgJT4lIAogICAgICAgICAgICAgIHN0X2Ryb3BfZ2VvbWV0cnkoKSwgYnkgPSAidW5pcXVlSUQiKSAlPiUgCiAgbXV0YXRlKFNOQV9OQU1FID0gaWZlbHNlKGlzLm5hKFNOQV9OQU1FKSwgIk5PVCBBUFBMSUNBQkxFIiwgU05BX05BTUUpKQoKCnBjYS5sb2dpdC5jdi5uZWlnaCA8LSBsb2dpdENWICgKICBkYXRhc2V0ID0gcGNhX25ldCwKICBpZCA9ICJTTkFfTkFNRSIsCiAgZGVwZW5kZW50VmFyaWFibGUgPSAiaG90c3BvdCIsCiAgaW5kVmFyaWFibGVzID0gcmVnLnZhcnMpICU+JQogICAgZHBseXI6OnNlbGVjdChTTkFfTkFNRSA9IFNOQV9OQU1FLCBob3RzcG90LCBQcmVkaWN0aW9uLCBnZW9tZXRyeSkKCnBjYS5sb2dpdC5jdi5uZWlnaCA8LSBwY2EubG9naXQuY3YubmVpZ2ggJT4lIAogIG11dGF0ZShQcmVkaWN0aW9uQ2F0ICA9IGFzLmZhY3RvcihpZmVsc2UocGNhLmxvZ2l0LmN2Lm5laWdoJFByZWRpY3Rpb24gPiAwLjUgLCAxLCAwKSkpICU+JSAKICBtdXRhdGUoaG90c3BvdCA9IGFzLmZhY3Rvcihob3RzcG90KSkKCmNtX3BjYV9uZWlnaCA8LSBjYXJldDo6Y29uZnVzaW9uTWF0cml4KHBjYS5sb2dpdC5jdi5uZWlnaCRob3RzcG90LCBwY2EubG9naXQuY3YubmVpZ2gkUHJlZGljdGlvbkNhdCwgCiAgICAgICAgICAgICAgICAgICAgICAgcG9zaXRpdmUgPSAiMSIpCgpkcmF3X2NvbmZ1c2lvbl9tYXRyaXgoY21fcGNhX25laWdoKQpgYGAKCldlIGNvbXB1dGVkIHRoZSBBcmVhIFVuZGVyIHRoZSBSZWNlaXZlciBPcGVyYXRpbmcgQ2hhcmFjdGVyaXN0aWMgQ3VydmUgKEFVQy1ST0MpIG1ldHJpYyBmb3Igb3VyIG1vZGVsIHRvIGV2YWx1YXRlIHRoZWlyIHBlcmZvcm1hbmNlLiBUaGUgQVVDIHZhbHVlIHJhbmdlcyBmcm9tIDAgdG8gMSwgd2hlcmUgMC41IG1lYW5zIGEgbW9kZWwgd2l0aCBubyBkaXNjcmltaW5hdG9yeSBwb3dlciAoaXQgcGVyZm9ybXMgYXMgd2VsbCBhcyByYW5kb20gY2hhbmNlKSwgYW5kIDEgbWVhbnMgYSBwZXJmZWN0IG1vZGVsIChpdCBwZXJmZWN0bHkgZGlzdGluZ3Vpc2hlcyBiZXR3ZWVuIHBvc2l0aXZlIGFuZCBuZWdhdGl2ZSBjYXNlcykuIEdlbmVyYWxseSwgYW4gQVVDIG92ZXIgMC44IGluZGljYXRlcyBhIGdvb2QgcGVyZm9ybWFuY2Ugb2YgdGhlIG1vZGVsLiBGb3IgdGhlIHJhbmRvbSBLLWZvbGQgY3Jvc3MgdmFsaWRhdGlvbiwgd2UgZ2V0IGEgQVVDIHNjb3JlIG9mIDAuOTA0IGFuZCBmb3IgdGhlIFNwYXRpYWwgTE9HTy1DViwgd2UgZ2V0IGEgQVVDIHNjb3JlIG9mIDAuODYyOC4gQm90aCBhcmUgZ29vZCBzY29yZXMgYW5kIGluZGljYXRlIHRoYXQgbW9kZWwgaXMgY2FwdHVyaW5nIGltcG9ydGFudCBwYXR0ZXJucyBpbiB0aGUgZGF0YSBhbmQgbWFraW5nIHByZWRpY3Rpb25zIGJldHRlciB0aGFuIHJhbmRvbSBndWVzc2luZy4KCmBgYHtyIFJPQyBjdXJ2ZSwgZmlnLmhlaWdodD02LCBmaWcud2lkdGg9MTEsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CgpwbG90NyA8LSBnZ3Bsb3QocGNhLmxvZ2l0LmN2LCBhZXMoZCA9IGFzLm51bWVyaWMoaG90c3BvdCksIG0gPSBQcmVkaWN0aW9uKSkgKwogIGdlb21fcm9jKG4uY3V0cyA9IDUwLCBsYWJlbHMgPSBGQUxTRSwgY29sb3VyID0gImJsYWNrIikgKwogICAgbGFicyh0aXRsZSA9ICJST0MgQ3VydmUgZm9yIExvZ2lzdGljIFJlZ3Jlc3Npb24gTW9kZWwgdXNpbmcgUENBIiwgCiAgICAgICAgIHN1YnRpdGxlID0gIlJhbmRvbSBDcm9zcyBWYWxpZGF0aW9uIikgKwogIHN0eWxlX3JvYyh0aGVtZSA9IHRoZW1lX2dyZXkpICsKICBnZW9tX2FibGluZShzbG9wZSA9IDEsIGludGVyY2VwdCA9IDAsIHNpemUgPSAxLjUsIGNvbG9yID0gIiNCQjM3NTQiKSArCiAgdGhlbWUocGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gOSxmYWNlID0gIml0YWxpYyIpLAogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSwgCiAgICAgICAgYXhpcy50ZXh0Lng9ZWxlbWVudF90ZXh0KHNpemU9OCksCiAgICAgICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF90ZXh0KHNpemU9OCksIAogICAgICAgIGF4aXMudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9OSksIAogICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9yZWN0KGNvbG91ciA9ICJncmV5IiwgZmlsbD1OQSwgbGluZXdpZHRoID0wLjgpKQoKcGxvdDggPC0gZ2dwbG90KHBjYS5sb2dpdC5jdi5uZWlnaCwgYWVzKGQgPSBhcy5udW1lcmljKGhvdHNwb3QpLCBtID0gUHJlZGljdGlvbikpICsKICBnZW9tX3JvYyhuLmN1dHMgPSA1MCwgbGFiZWxzID0gRkFMU0UsIGNvbG91ciA9ICJibGFjayIpICsKICAgIGxhYnModGl0bGUgPSAiUk9DIEN1cnZlIGZvciBMb2dpc3RpYyBSZWdyZXNzaW9uIE1vZGVsIHVzaW5nIFBDQSIsIAogICAgICAgICBzdWJ0aXRsZSA9ICJTcGF0aWFsIExPR08gQ3Jvc3MgVmFsaWRhdGlvbiIpICsKICBzdHlsZV9yb2ModGhlbWUgPSB0aGVtZV9ncmV5KSArCiAgZ2VvbV9hYmxpbmUoc2xvcGUgPSAxLCBpbnRlcmNlcHQgPSAwLCBzaXplID0gMS41LCBjb2xvciA9ICIjQkIzNzU0IikgKwogIHRoZW1lKHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDksZmFjZSA9ICJpdGFsaWMiKSwKICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgZmFjZSA9ICJib2xkIiksIAogICAgICAgIGF4aXMudGV4dC54PWVsZW1lbnRfdGV4dChzaXplPTgpLAogICAgICAgIGF4aXMudGV4dC55PWVsZW1lbnRfdGV4dChzaXplPTgpLCAKICAgICAgICBheGlzLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTkpLCAKICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfcmVjdChjb2xvdXIgPSAiZ3JleSIsIGZpbGw9TkEsIGxpbmV3aWR0aCA9MC44KSkKCnBsb3Q3ICsgcGxvdDgKCmBgYAoKCgoKYGBge3IgbWFwcGluZyBlcnJvciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgZmlnLmhlaWdodD02LCBmaWcud2lkdGg9MTF9CgpwbG90OSA8LSBwY2EubG9naXQuY3YgJT4lCiAgZmlsdGVyKGlzLm5hKFByZWRpY3Rpb24pID09IEZBTFNFKSAlPiUgCiAgIG11dGF0ZShyZXN1bHQgPSBjYXNlX3doZW4oCiAgIFByZWRpY3Rpb25DYXQ9PTAgJiBob3RzcG90PT0wIH4gIkFjY3VyYXRlIiwKICAgUHJlZGljdGlvbkNhdD09MSAmIGhvdHNwb3Q9PTEgfiJBY2N1cmF0ZSIsCiAgIFByZWRpY3Rpb25DYXQ9PTAgJiBob3RzcG90PT0xIH4iUHJvYmxlbWF0aWMiLAogICBUUlVFIH4gIkluYWNjdXJhdGUiCiAgKSkgJT4lIAogIGdncGxvdCgpICsKICBnZW9tX3NmKGFlcyhmaWxsPWFzLmNoYXJhY3Rlcihob3RzcG90KSksIGNvbG9yPSJ0cmFuc3BhcmVudCIpKwogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGMoImJsYWNrIiwgIiNCQjM3NTQiKSwgbmFtZSA9ICJIb3RzcG90IikgKwogIGxhYnModGl0bGUgPSAiQWN0dWFsIEhvdHNwb3QiKSArCiAgICB0aGVtZShheGlzLnRleHQueD1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGlja3MgPWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aXRsZS55ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDksZmFjZSA9ICJpdGFsaWMiKSwKICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgZmFjZSA9ICJib2xkIiksCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X3JlY3QoY29sb3VyID0gImdyZXkiLCBmaWxsPU5BLCBsaW5ld2lkdGg9MC44KQogICAgICAgICkKICAKCnBsb3QxMCA8LSBwY2EubG9naXQuY3YgJT4lCiAgZ2dwbG90KCkgKwogIGdlb21fc2YoYWVzKGZpbGw9YXMuY2hhcmFjdGVyKFByZWRpY3Rpb25DYXQpKSwgY29sb3I9InRyYW5zcGFyZW50IikrCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gYygiYmxhY2siLCAiI0JCMzc1NCIpLCBuYW1lID0gIkhvdHNwb3QiKSArCiAgbGFicyh0aXRsZSA9ICJQcmVkaWN0ZWQgSG90c3BvdCIsIAogICAgICAgICBzdWJ0aXRsZSA9ICJSYW5kb20gQ3Jvc3MgVmFsaWRhdGlvbiIpICsKICAgIHRoZW1lKGF4aXMudGV4dC54PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRleHQueT1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aWNrcyA9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpdGxlLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gOSxmYWNlID0gIml0YWxpYyIpLAogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSwKICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfcmVjdChjb2xvdXIgPSAiZ3JleSIsIGZpbGw9TkEsIGxpbmV3aWR0aD0wLjgpCiAgICAgICAgKQoKcGxvdDExIDwtIHBjYS5sb2dpdC5jdi5uZWlnaCAlPiUKICBnZ3Bsb3QoKSArCiAgZ2VvbV9zZihhZXMoZmlsbD1hcy5jaGFyYWN0ZXIoUHJlZGljdGlvbkNhdCkpLCBjb2xvcj0idHJhbnNwYXJlbnQiKSsKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjKCJibGFjayIsICIjQkIzNzU0IiksIG5hbWUgPSAiSG90c3BvdCIpICsKICBsYWJzKHRpdGxlID0gIlByZWRpY3RlZCBIb3RzcG90IiwgCiAgICAgICAgIHN1YnRpdGxlID0gIlNwYXRpYWwgTE9HTyBWYWxpZGF0aW9uIikgKwogICAgdGhlbWUoYXhpcy50ZXh0Lng9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGV4dC55PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpY2tzID1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGl0bGUueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSA5LGZhY2UgPSAiaXRhbGljIiksCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIsIGZhY2UgPSAiYm9sZCIpLAogICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9yZWN0KGNvbG91ciA9ICJncmV5IiwgZmlsbD1OQSwgbGluZXdpZHRoPTAuOCkKICAgICAgICApCiAgCgpwbG90MTIgPC0gcGNhLmxvZ2l0LmN2ICU+JQogIGZpbHRlcihpcy5uYShQcmVkaWN0aW9uKSA9PSBGQUxTRSkgJT4lIAogICBtdXRhdGUocmVzdWx0ID0gY2FzZV93aGVuKAogICBQcmVkaWN0aW9uQ2F0PT0wICYgaG90c3BvdD09MCB+ICJBY2N1cmF0ZSIsCiAgIFByZWRpY3Rpb25DYXQ9PTEgJiBob3RzcG90PT0xIH4iQWNjdXJhdGUiLAogICBQcmVkaWN0aW9uQ2F0PT0wICYgaG90c3BvdD09MSB+IlByb2JsZW1hdGljIiwKICAgVFJVRSB+ICJJbmFjY3VyYXRlIgogICkpICU+JSAKICBnZ3Bsb3QoKSArCiAgZ2VvbV9zZihhZXMoZmlsbD1hcy5jaGFyYWN0ZXIocmVzdWx0KSksIGNvbG9yPSJ0cmFuc3BhcmVudCIpKwogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGMoImJsYWNrIiwgIiNGRkMwMDAiLCAiI0JCMzc1NCIpLCBuYW1lID0gIkhvdHNwb3QiKSArCiAgbGFicyh0aXRsZSA9ICJNYXBwaW5nIEVycm9yIiwgCiAgICAgICAgIHN1YnRpdGxlID0gIlJhbmRvbSBDcm9zcyBWYWxpZGF0aW9uIikgKwogICAgdGhlbWUoYXhpcy50ZXh0Lng9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGV4dC55PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpY2tzID1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGl0bGUueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSA5LGZhY2UgPSAiaXRhbGljIiksCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIsIGZhY2UgPSAiYm9sZCIpLAogICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9yZWN0KGNvbG91ciA9ICJncmV5IiwgZmlsbD1OQSwgbGluZXdpZHRoPTAuOCkKICAgICAgICApCgpwbG90OSArIHBsb3QxMCArIHBsb3QxMSArIHBsb3QxMgpgYGAKCgpJZiBvdXIgbW9kZWwgaGFzIGdlbmVyYWxseSBiZWVuIGdvb2QgYXQgbWFraW5nIHByZWRpY3Rpb24gKGhpZ2ggYWNjdXJhY3kpIGJ1dCB3aXRoIHNvbWUgdW5jZXJ0YWludGllcyBpbiBjb3JyZWN0bHkgbG9jYXRpbmcgdGhlIGhvdHNwb3QsIHRoZW4gd2Ugd291bGQgbGlrZSB0byBrbm93IHdoZXJlIGl0IGlzIG1ha2luZyBtaXN0YWtlcy4gV2UgbWFwcGVkIHRoZSBlcnJvciBiZWxvdyBhbmQgY29tcGFyZWQgaXQgdG8gdGhlIGFjdHVhbCBob3RzcG90LiBXaGF0IHdlIGNhbiBzZWUgaXMgdGhhdCBvdXIgbW9kZWwgaXMgYWNjdXJhdGUgaW4gcm91Z2hseSBlc3RpbWF0aW5nIHRoZSBsb2NhdGlvbiBvZiB0aGUgaG90c3BvdCwgYnV0IHRlbmRzIHRvIG1ha2UgbWlzdGFrZXMgd2hlbiBkZWNpZGluZyB3aGljaCBvZiB0aGVzZSBncmlkcyBpbiBwYXJ0aWN1bGFyIHNob3VsZCBiZSBjYXRlZ29yaXplZCBhcyBob3RzcG90LiAKCldlIGhpZ2hsaWdodGVkIHRob3NlIGdyaWRzIHdoaWNoIGFyZSBhY3R1YWwgaG90c3BvdCBidXQgd2VyZSB3cm9uZ2x5IHByZWRpY3RlZCBhcyBwcm9ibGVtYXRpYywgZnJvbSB3aGljaCB3ZSBtYXkgc2VlIHRoYXQgdGhvc2UgbW9zdCBvZiB0aG9zZSBwcm9ibGVtYXRpYyBncmlkcyBwcmV0dHkgbXVjaCBuZWlnaGJvcnMgaG90c3BvdCBncmlkcy4gVGhpcyBpbXBsaWVzIHRoYXQgd2hlbiBkcmF3aW5nIGNvbmNsdXNpb24gZnJvbSBvdXIgbW9kZWwsIHdlIHNob3VsZCBub3Qgb25seSBsb29rIGF0IHNwZWNpZmljIGhvdHNwb3QgZ3JpZCwgYnV0IGFsc28gcHJvYmFibHkgcGF5IGF0dGVudGlvbiB0byBncmlkcyBuZWFyYnkuIAoKCiMgS0RFIFZhbGlkYXRpb24KClRoZSBhY2N1cmFjeSBvZiBvdXIgbW9kZWwgaXMgYWxzbyBjb21wYXJlZCByZWxhdGl2ZSB0byBrZXJuZWwgZGVuc2l0eSBlc3RpbWF0aW9uLiBLZXJuZWwgZGVuc2l0eSB3b3JrcyBieSBjZW50ZXJpbmcgYSBzbW9vdGgga2VybmVsLCBvciBjdXJ2ZSwgYXRvcCBlYWNoIGNyaW1lIHBvaW50IHN1Y2ggdGhhdCB0aGUgY3VydmUgaXMgYXQgaXRzIGhpZ2hlc3QgZGlyZWN0bHkgb3ZlciB0aGUgcG9pbnQgYW5kIHRoZSBsb3dlc3QgYXQgdGhlIHJhbmdlIG9mIGEgY2lyY3VsYXIgc2VhcmNoIHJhZGl1cy4gVGhlIGNvZGUgYmxvY2sgYmVsb3cgY3JlYXRlcyBhIEtlcm5lbCBkZW5zaXR5IG1hcCBvZiByb2JiZXJ5IHdpdGggYSAxMDAwIGZvb3Qgc2VhcmNoIHJhZGl1cy4KCmBgYHtyIEtERSAxMDAwIHJhZGlpLCAgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KCmhlcm9pbl9wcHAgPC0gYXMucHBwKHN0X2Nvb3JkaW5hdGVzKGhlcm9pbiksIFcgPSBzdF9iYm94KGhlcm9pbl9uZXQpKQpoZXJvaW5fS0QuMTAwMCA8LSBzcGF0c3RhdC5leHBsb3JlOjpkZW5zaXR5LnBwcChoZXJvaW5fcHBwLCAxMDAwKQpoZXJvaW5fS0QuZGYgPC0gZGF0YS5mcmFtZShyYXN0ZXJUb1BvaW50cyhtYXNrKHJhc3RlcihoZXJvaW5fS0QuMTAwMCksIGFzKGNpbmNpbm5hdGkyMCwgJ1NwYXRpYWwnKSkpKQoKCmdncGxvdChkYXRhPWhlcm9pbl9LRC5kZiwgYWVzKHg9eCwgeT15KSkgKwogIGdlb21fcmFzdGVyKGFlcyhmaWxsPWxheWVyKSkgKyAKICBjb29yZF9zZihjcnM9c3RfY3JzKGhlcm9pbl9uZXQpKSArIAogIHNjYWxlX2ZpbGxfdmlyaWRpcyhvcHRpb24gPSAicm9ja2V0IiwgbmFtZT0iRGVuc2l0eSIpICsKICBsYWJzKHRpdGxlID0gIktlcm5lbCBEZW5zaXR5IG9mIEhlcm9pbiAxMDAwZnQgUmFkaWkiKSArCiAgICB0aGVtZShheGlzLnRleHQueD1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGlja3MgPWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aXRsZS55ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDksZmFjZSA9ICJpdGFsaWMiKSwKICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgZmFjZSA9ICJib2xkIiksCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X3JlY3QoY29sb3VyID0gImdyZXkiLCBmaWxsPU5BLCBzaXplPTAuOCkKICAgICAgICApCmBgYAoKVGhlIGtlcm5lbCBkZW5zaXR5IGVzdGltYXRpb24gd2FzIGNsYXNzaWZpZWQgaW50byB0d28gY2F0ZWdvcmllcywgbG93ZXIgcmlzayBhbmQgaGlnaGVyIHJpc2ssIG1hdGNoaW5nIHRoZSBiaW5hcnkgb2YgaG90c3BvdCBhbmQgY29sZHNwb3QuIFdlIHVzZWQgdGhlIGBGaXNoZXIncyBtZXRob2RgIHRoYXQgaW52b2x2ZXMgZmluZGluZyBpbnRlcnZhbHMgc3VjaCB0aGF0IHRoZSB2YXJpYW5jZSB3aXRoaW4gZWFjaCBpbnRlcnZhbCBpcyBtaW5pbWl6ZWQgd2hpbGUgbWF4aW1pemluZyB0aGUgdmFyaWFuY2UgYmV0d2VlbiB0aGUgaW50ZXJ2YWxzLiBUaGUgcHVycG9zZSBoZXJlIGlzIHRvIHNlZSBpZiB0aGUgb3VyIG1vZGVsIGNhcHR1cmUgdGhlIHNhbWUgaG90c3BvdCBncmlkcyBhcyB0aGUga2VybmVsIGRlbnNpdHkuIFRoZSBtYXAgYmVsb3cgc2hvd3MgdGhhdCBvdXIgbW9kZWwgcGVyZm9ybSBhcyBnb29kIGFzLCBpZiBub3Qgd29yc2UsIHRoZSBrZXJuYWwgZGVuc2l0eSBhcHByb2FjaCBpbiBjYXB0dXJpbmcgaG90c3BvdHMgKGhpZ2ggcmlzayBhcmVhcykuIAoKYGBge3IgY29tcGFyaXNvbiwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KCmhlcm9pbl9LREVfc3VtIDwtIGFzLmRhdGEuZnJhbWUoaGVyb2luX0tELjEwMDApICU+JQogIHN0X2FzX3NmKGNvb3JkcyA9IGMoIngiLCAieSIpLCBjcnMgPSBzdF9jcnMoaGVyb2luX25ldCkpICU+JQogIGFnZ3JlZ2F0ZSguLCBoZXJvaW5fbmV0LCBtZWFuKSAKCmtkZV9icmVha3MgPC0gY2xhc3NJbnRlcnZhbHMoaGVyb2luX0tERV9zdW0kdmFsdWUsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIG4gPSAyLCAiZmlzaGVyIikKCmhlcm9pbl9LREVfc2YgPC0gaGVyb2luX0tERV9zdW0gJT4lCiAgbXV0YXRlKGxhYmVsID0gIktlcm5lbCBEZW5zaXR5IiwKICAgICAgICAgUmlza19DYXRlZ29yeSA9IGNsYXNzSW50OjpmaW5kQ29scyhrZGVfYnJlYWtzKSwKICAgICAgICAgUmlza19DYXRlZ29yeSA9IGNhc2Vfd2hlbigKICAgICAgICAgICBSaXNrX0NhdGVnb3J5ID09IDIgfiAiSGlnaCIsCiAgICAgICAgICAgUmlza19DYXRlZ29yeSA9PSAxIH4gIkxvdyIpKQoKaGVyb2luX3Jpc2tfc2YgPC0KICBwY2EubG9naXQuY3YgJT4lCiAgbXV0YXRlKGxhYmVsID0gIkxvZ2lzdGljIFJlZ3Jlc3Npb24iLAogICAgICAgICBSaXNrX0NhdGVnb3J5ID0gY2FzZV93aGVuKAogICAgICAgICAgIFByZWRpY3Rpb25DYXQgPT0gMSB+ICJIaWdoIiwKICAgICAgICAgICBQcmVkaWN0aW9uQ2F0ID09IDAgfiAiTG93IikpICU+JSAKICBkcGx5cjo6c2VsZWN0KFByZWRpY3Rpb25DYXQsIGxhYmVsLCBSaXNrX0NhdGVnb3J5KSAlPiUgCiAgcmVuYW1lKHZhbHVlID0gUHJlZGljdGlvbkNhdCkKCgpyYmluZChoZXJvaW5fS0RFX3NmLCBoZXJvaW5fcmlza19zZikgJT4lCiAgbmEub21pdCgpICU+JQogIGdhdGhlcihWYXJpYWJsZSwgVmFsdWUsIC1sYWJlbCwgLVJpc2tfQ2F0ZWdvcnksIC1nZW9tZXRyeSkgJT4lCiAgZ2dwbG90KCkgKwogICAgZ2VvbV9zZihhZXMoZmlsbCA9IFJpc2tfQ2F0ZWdvcnkpLCBjb2xvdXIgPSBOQSkgKwogICAgZmFjZXRfd3JhcCh+bGFiZWwsICkgKwogICAgc2NhbGVfZmlsbF92aXJpZGlzKG9wdGlvbiA9ICJyb2NrZXQiLCBkaXNjcmV0ZSA9IFRSVUUsIG5hbWUgPSAiUmlzayBDYXRlZ29yeSIpICsKICAgIGxhYnModGl0bGU9IkNvbXBhcmlzb24gb2YgS2VybmVsIERlbnNpdHkgYW5kIFJpc2sgUHJlZGljdGlvbnMiKSArIAogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0iYm90dG9tIiwKICAgIGF4aXMudGV4dC54PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRleHQueT1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aWNrcyA9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpdGxlLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gOSxmYWNlID0gIml0YWxpYyIpLAogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSwKICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfcmVjdChjb2xvdXIgPSAiZ3JleSIsIGZpbGw9TkEsIHNpemU9MC44KQogICAgICAgICkKYGBgCgoKIyBNdWx0aSBDcml0ZXJpYSBBbmFseXNpcwoKV2Ugd291bGQgbGlrZSB0byBjb25jbHVkZSBvdXIgYW5hbHlzaXMgYnkgYnVpbGRpbmcgbXVsdGktY3JpdGVyaWEtYW5hbHlzaXMtY2VudGVyZWQgZ2Vvc3BhdGlhbCByaXNrIHRlcnJhaW4gbW9kZWwgdGhhdCBzdGFrZWhvbGRlcnMgYW5kIHRoZSBwdWJsaWMgY2FuIHVzZSB0byBsZWFybiBhYm91dCB0aGUgY29tbXVuaXR5IHZ1bG5lcmFiaWxpdHkgdG8gaGVyb2luLW92ZXJkb3NlIGluIENpbmNpbm5hdGkuIEluIEdJUywgYSBNdWx0aS1Dcml0ZXJpYSBBbmFseXNpcyAoTUNBKSBtb2RlbCBpcyBhIGRlY2lzaW9uLW1ha2luZyBhcHByb2FjaCB0aGF0IGludm9sdmVzIGV2YWx1YXRpbmcgYW5kIGNvbXBhcmluZyBtdWx0aXBsZSBjcml0ZXJpYSBvciBmYWN0b3JzIHRvIG1ha2UgaW5mb3JtZWQgc3BhdGlhbCBkZWNpc2lvbnMuIFRoZSBjcml0ZXJpYSB3ZSB1c2UgaGVyZSBhcmUgdGhlIHByZWRpY3RvciB2YXJpYWJsZXMgd2UgdXNlIGluIG91ciBtb2RlbC4gVGhlc2UgY3JpdGVyaWEgYXJlIHN0YW5kYXJkaXplZCB0byBlbnN1cmUgdGhhdCBhbGwgb2YgdGhlbSBhcmUgb24gYSBzY2FsZSBvZiAwIHRvIDEuIFRoZW4sIHNvbWUgb2YgdGhvc2Ugc2NhbGVkIHZhcmlhYmxlcyBhcmUgcmV2ZXJzZWQgaWYgbmVjZXNzYXJ5IGJhc2VkIG9uIHdoZXRoZXIgdGhleSBjb250cmlidXRlIHBvc2l0aXZlbHkgb3IgbmVnYXRpdmVseSB0byB0aGUgaGVyb2luLW92ZXJkb3NlIHJpc2suIFdlIHN1cHBseSBhIHdlaWdodCBvbiBlYWNoIG9mIHRob3NlIHZhcmlhYmxlcyBiYXNlZCBvbiB0aGVpciBjb250cmlidXRpb24uIEZpbmFsbHksIGEgcmlzayBzY29yZSBpcyBjb21wdXRlZCBmb3IgZWFjaCBncmlkLiAKCmBgYHtyIE1DQSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KCnNjYWxlX3ZhbHVlcyA8LSBmdW5jdGlvbih4KXsoeC1taW4oeCkpLyhtYXgoeCktbWluKHgpKX0KCnJpc2tfc2NvcmUgPC0gaGVyb2luX25ldCAlPiUgCiAgbXV0YXRlKE1lZEhISW5jID0gaWZlbHNlKGlzLm5hKE1lZEhISW5jKSwgMCwgTWVkSEhJbmMpKSAlPiUgCiAgbXV0YXRlKHNjbF9jcmltZSA9IHNjYWxlX3ZhbHVlcyhDcmltZWNvdW50KSwKICAgICAgICAgc2NsX2NvbXBsYWludHMgPSBzY2FsZV92YWx1ZXMoQ29tcGxhaW50c2NvdW50KSwKICAgICAgICAgc2NsX3BoYXJtID0gc2NhbGVfdmFsdWVzKHBoYXJtLm5uKSwKICAgICAgICAgc2NsX2hvc3BpdGFsID0gc2NhbGVfdmFsdWVzKGhvc3BpdGFsLm5uKSwKICAgICAgICAgc2NsX3JlaGFiID0gc2NhbGVfdmFsdWVzKHJlaGFiLm5uKSwKICAgICAgICAgc2NsX3BhcmsgPSBzY2FsZV92YWx1ZXMocGFya3Mubm4pLAogICAgICAgICBzY2xfZnVlbCA9IHNjYWxlX3ZhbHVlcyhmdWVsLm5uKSwKICAgICAgICAgc2NsX2Zhc3QgPSBzY2FsZV92YWx1ZXMoZmFzdC5ubiksCiAgICAgICAgIHNjbF9wb3AgPSBzY2FsZV92YWx1ZXMocG9wMjVfNTQpLAogICAgICAgICBzY2xfaW5jb21lID0gc2NhbGVfdmFsdWVzKE1lZEhISW5jKSwKICAgICAgICAgc2NsX2dlbmRlciA9IHNjYWxlX3ZhbHVlcyhNRl9yYXRpbyksCiAgICAgICAgIHNjbF9yYWNlID0gc2NhbGVfdmFsdWVzKHJhY2VfcmF0aW8pLAogICAgICAgICBzY2xfcG92ZXJ0eSA9IHNjYWxlX3ZhbHVlcyhwY3RQb3ZlcnR5KSwKICAgICAgICAgc2NsX2VkdSA9IHNjYWxlX3ZhbHVlcyhwY3RCYWNoZWxvcikpICU+JSAKICBtdXRhdGUoc2NsX3JhY2VfcmUgPSAwIC0gc2NsX3JhY2UgKyAxLAogICAgICAgICBzY2xfZWR1X3JlID0gMCAtIHNjbF9lZHUgKyAxLAogICAgICAgICBzY2xfaW5jb21lX3JlID0gMCAtIHNjbF9pbmNvbWUgKyAxLCAKICAgICAgICAgc2NsX2Z1ZWxfcmUgPSAwIC0gc2NsX2Z1ZWwgKyAxLAogICAgICAgICBzY2xfZmFzdF9yZSA9IDAgLSBzY2xfZmFzdCArIDEsCiAgICAgICAgIHNjbF9wYXJrX3JlID0gMCAtIHNjbF9wYXJrICsgMSwKICAgICAgICAgc2NsX3BoYXJtX3JlID0gMCAtIHNjbF9waGFybSArIDEpICU+JSAKICBtdXRhdGUoc2NvcmUgPSAwLjEgKiAoc2NsX2hvc3BpdGFsICsgc2NsX2NyaW1lICsgc2NsX2NvbXBsYWludHMgKyAgc2NsX2dlbmRlciArIHNjbF9pbmNvbWVfcmUgKyBzY2xfZnVlbF9yZSArIHNjbF9mYXN0X3JlICsgIHNjbF9wYXJrX3JlKSArIDAuMDUgKiAoc2NsX3JhY2VfcmUgKyAgc2NsX3BoYXJtX3JlKSArIDAuMDI1KihzY2xfcG9wICtzY2xfZWR1X3JlICsgc2NsX3BvdmVydHkgKyAgc2NsX3JlaGFiKSkgJT4lIAogIHN0X2FzX3NmKCkKICAKCnJpc2tfc2NvcmUgPC0gcmlza19zY29yZSAlPiUKICBsZWZ0X2pvaW4obmV0X2NlbnRyb2lkICU+JSBkcGx5cjo6c2VsZWN0KHVuaXF1ZUlEKSAlPiUgCiAgICAgICAgICAgICAgIHN0X2ludGVyc2VjdGlvbihuZWlnaGJvcmhvb2QgJT4lIGRwbHlyOjpzZWxlY3QoU05BX05BTUUpKSAlPiUgCiAgICAgICAgICAgICAgc3RfZHJvcF9nZW9tZXRyeSgpLCBieSA9ICJ1bmlxdWVJRCIpICU+JSAKICBtdXRhdGUoU05BX05BTUUgPSBpZmVsc2UoaXMubmEoU05BX05BTUUpLCAiTk9UIEFQUExJQ0FCTEUiLCBTTkFfTkFNRSkpCgoKYGBgCgpUaGlzIGJlbG93IGludGVyYWN0aXZlIGxlYWZsZXQgbWFwIHNob3dzIHRoZSByaXNrIHNjb3JlIG9mIGVhY2ggZ3JpZCwgdGhlIG5laWdoYm9yaG9vZCB0aGF0IGdyaWQgZmFsbCBpbnRvLCBhbmQgd2hldGhlciBvciBub3QgaXQgZmFsbHMgaW50byBhIGhvdHNwb3QgdGhhdCBvdXIgbW9kZWwgcHJlZGljdHMuIFdlIGhvcGUgdGhhdCB0aGlzIHdpbGwgc2VydmUgYXMgb3VyIGZpcnN0IHN0ZXAgdG93YXJkcyBidWlsZGluZyB1cCBvdXIgdXNlIGNhc2UgdGhhdCBwcmVzZW50IHN0YWtlaG9sZGVycyB3aXRoIGEgY2xlYXJlciBwaWN0dXJlIG9mIGNvbW11bml0eSB2dWxuZXJhYmlsaXR5IHRvIGhlcm9pbiBvdmVyZG9zZSBhbmQgdGhhdCBoZWxwIHRoZSBwdWJsaWMgdG8gaGF2ZSBhIGJldHRlciB1bmRlcnN0YW5kaW5nIG9mIHRoZSBzZXZlcml0eSBvZiBoZXJvaW4tb3ZlcmRvc2UgaW4gQ2luY2lubmF0aSBhcyB3ZWxsIGFzIGV4aXN0aW5nIHJlaGFiaWxpdGF0aW9uIHJlc291cmNlcy4gIAoKYGBge3IgbWFwIHJpc2ssIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9Cgpjb2xvcnMgPC0gcmV2KHZpcmlkaXM6OnZpcmlkaXMoNiwgb3B0aW9uID0gInJvY2tldCIpKQoKcGFsIDwtIGNvbG9yQmluKHBhbGV0dGUgPSBjb2xvcnMsIAogICAgICAgICAgICAgICAgZG9tYWluID0gcmlza19zY29yZSRzY29yZSwgCiAgICAgICAgICAgICAgICBiaW5zID0gYygwLjIsIDAuMywgMC40LCAwLjUsIDAuNiwgMC43KSkgCgpsYWJzIDwtIHBhc3RlMCgiPHN0cm9uZz4iLCByaXNrX3Njb3JlJFNOQV9OQU1FLAogICAgICAgICAgICAgICAiPC9zdHJvbmc+PGJyLz5IZW9yaW4tT3ZlcmRvc2UgUmlzayBTY29yZTogIiwgcmlza19zY29yZSRzY29yZQogICAgICAgICAgICAgICApIAoKbGVhZmxldCA8LSByaXNrX3Njb3JlICU+JQogIHN0X3RyYW5zZm9ybSgiRVBTRzo0MzI2IikgJT4lIAogIGxlYWZsZXQoKSAlPiUKICBzZXRWaWV3KGxuZyA9IC04NC41MSwgbGF0ID0gMzkuMTAsIHpvb20gPSAxMC41KSAlPiUKICBhZGRQcm92aWRlclRpbGVzKHByb3ZpZGVycyRDYXJ0b0RCLlBvc2l0cm9uKSAlPiUgCiAgYWRkUG9seWdvbnMod2VpZ2h0ID0gMSwKICAgICAgICAgICAgICBmaWxsQ29sb3IgPSB+cGFsKHNjb3JlKSwgIyBjaGFuZ2UgdGhlIG5hbWUgb2YgdGhlIHBhcmFtZXRlciBpbiB0aGUgYnJhY2tldAogICAgICAgICAgICAgIHBvcHVwID0gbGFicywKICAgICAgICAgICAgICBjb2xvciA9ICJ0cmFuc3BhcmVudCIsCiAgICAgICAgICAgICAgZ3JvdXAgPSAiUmlzayBTY29yZSIsIAogICAgICAgICAgICAgIGZpbGxPcGFjaXR5ID0gMSkgJT4lCiAgYWRkUG9seWdvbnMoCiAgICBkYXRhID0gcGNhLmxvZ2l0LmN2ICU+JSBmaWx0ZXIoUHJlZGljdGlvbkNhdCA9PSAxKSAlPiUgc3RfdHJhbnNmb3JtKCJFUFNHOjQzMjYiKSwgCiAgICB3ZWlnaHQgPSAyLAogICAgZ3JvdXAgPSAiT3ZlcmRvc2UgSG90c3BvdCIsIAogICAgY29sb3IgPSAieWVsbG93IikgJT4lIAogIGFkZExheWVyc0NvbnRyb2woCiAgICBvdmVybGF5R3JvdXBzID0gYygiUmlzayBTY29yZSIsICJPdmVyZG9zZSBIb3RzcG90IiksCiAgICBvcHRpb25zID0gbGF5ZXJzQ29udHJvbE9wdGlvbnMoY29sbGFwc2VkID0gRkFMU0UpCiAgKSAlPiUKICBhZGRMZWdlbmQocG9zaXRpb24gPSAiYm90dG9tcmlnaHQiLAogICAgICAgICAgICBwYWwgPSBwYWwsCiAgICAgICAgICAgIHZhbHVlcyA9IH5yaXNrX3Njb3JlLCAjIGNoYW5nZSB0aGUgbmFtZSBvZiB0aGUgcGFyYW1ldGVyIGFmdGVyIH4KICAgICAgICAgICAgdGl0bGUgPSAiUmlzayBTY29yZSIsCiAgICAgICAgICAgIG9wYWNpdHkgPSAxKSAjIGNoYW5nZSB0aGUgbmFtZSBvZiB0aGUgbGVnZW5kIAoKbGVhZmxldAptYXBzaG90KGxlYWZsZXQsIHVybCA9ICJjaW5jeXJpc2suaHRtbCIpCmBgYAoKCiMgQ29uY2x1c2lvbgoKSW4gdGhpcyByZXBvcnQsIHdlIGNvbmR1Y3RlZCBhIHN0dWR5IG9uIG9waW9pZC1vdmVyZG9zZSByaXNrIG1vZGVsaW5nIGluIENpbmNpbm5hdGksIE9oaW8gd2hlcmUgdGhlIG9waW9pZCBlcGlkZW1pYyBoYXMgYmVlbiBhIHNlcmlvdXMgaXNzdWUuIERyYXdpbmcgb24gbWV0aG9kcyBieSBTcmluaXZhc2FuLCBldCBhbC4gKDIwMjMpLCAKd2UgdXNlZCBHZXRpcy1PcmQgaG90c3BvdCBhbmFseXNlcyB0byBpZGVudGlmeSBsb2NhdGlvbnMgb2YgcGVyc2lzdGVudCByaXNrIGluIENpbmNpbm5hdGkgZnJvbSAyMDE2LTIwMjAuIFdlIGNvbnN0cnVjdGVkIG1lYXN1cmVzIG9mIHRoZSBzb2Npby1idWlsdCBlbnZpcm9ubWVudCBhbmQgbmVpZ2hib3Job29kIGRlbW9ncmFwaGljIGNoYXJhY3RlcmlzdGljcyB1c2luZyBwcmluY2lwYWwgY29tcG9uZW50cyBhbmFseXNpcyBhbmQgcmFuIGxvZ2lzdGljIHJlZ3Jlc3Npb24gdG8gZGV0ZXJtaW5lIGlmIHBsYWNlcyBpbiBDaW5jaW5uYXRpIGZhbGwgaW50byBoZXJvaW4tb3ZlcmRvc2UgaG90c3BvdHMgb3Igbm90LiBXaGlsZSBQb2lzc29uIHJlZ3Jlc3Npb24gaGFzIGxvbmcgYmVlbiB0aGUgdHJhZGl0aW9uYWwgbWV0aG9kIHVzZWQgaW4gZ2Vvc3BhdGlhbCByaXNrIG1vZGVsIHRvIHByZWRpY3QgdGhlIG51bWJlciAoY291bnQpIG9mIGRpc2NyZXRlIGV2ZW50LCB3ZSBwcm9wb3NlIHRoaXMgYWx0ZXJuYXRpdmUgbWV0aG9kIGluIHRoaXMgcmVwb3J0IGJlY2F1c2Ugd2UgYmVsaWV2ZSB0aGF0IGl0IGlzIGxlc3MgdXNlZnVsIGZvciBwb2xpY3ltYWtlcnMgdG8gZm9jdXMgb24gdGhlIG51bWJlciBvZiBoZXJvaW4tb3ZlcmRvc2UgaW5jaWRlbmNlIGFtb25nIG5laWdoYm9yaG9vZHMgaW4gQ2luY2lubmF0aS4gUmF0aGVyLCB3ZSBzaG91bGQgZGV2ZWxvcCBhIHdheSB0byBxdWlja2x5IGlkZW50aWZ5IGFyZWFzL2NsdXN0ZXJzIG9mIGhpZ2ggaGVyb2luLW92ZXJkb3NlIGluY2lkZW5jZS4gCgpPdXIgbW9kZWwsIHdoaWNoIHVzZXMgcHJpbmNpcGFsIGNvbXBvbmVudHMgYXMgcHJlZGljdG9ycywgaGFzIGFuIGFjY3VyYWN5IHJhdGUgb2YgMC45MjgsIGEgc2Vuc2l0aXZpdHkgcmF0ZSBvZiAwLjY2LCBhbmQgYW4gQVVDIHNjb3JlIG9mIDAuOTA0LCBtZWFuaW5nIHRoYXQgaXQgaGFzIGJlZW4gZWZmZWN0aXZlIG92ZXJhbGwgaW4gZGlzdGluZ3Vpc2hpbmcgYmV0d2VlbiBob3RzcG90cyBhbmQgY29sZCBzcG90cy4gV2UgbWFwcGVkIHRoZSBlcnJvciBhbmQgY29tcGFyZWQgdGhhdCB0byB0aGUgYWN0dWFsIGhvdHNwb3QsIGZyb20gd2hpY2ggd2Ugbm90aWNlZCB0aGF0IG91ciBtb2RlbCBpcyBhY2N1cmF0ZSBpbiByb3VnaGx5IGVzdGltYXRpbmcgdGhlIGxvY2F0aW9uIG9mIHRoZSBob3RzcG90IGJ1dCB0ZW5kcyB0byBtYWtlIG1pc3Rha2VzIHdoZW4gZGVjaWRpbmcgd2hpY2ggb2YgdGhlc2UgZ3JpZHMgaW4gcGFydGljdWxhciBzaG91bGQgYmUgY2F0ZWdvcml6ZWQgYXMgaG90c3BvdC4gT24gdG9wIG9mIHRoYXQsIG1hbnkgb2YgdGhvc2UgZ3JpZHMgYXJlIG5laWdoYm9ycyBvZiBob3RzcG90IGdyaWRzIHRoYXQgYXJlIGNvcnJlY3RseSBwcmVkaWN0ZWQuICAKClJlZmxlY3Rpbmcgb24gdGhlIG92ZXJhbGwgc3R1ZHkgZGVzaWduLCB3ZSBoYXZlIHRoZSBmb2xsb3dpbmcgY29uY2VybnMsIHdoaWNoIHdlIHdvdWxkIGxpa2UgdG8gY29udGludWUgd29ya2luZyBvbiB0byBpbXByb3ZlIHRoZSBhY2N1cmFjeSBhbmQgcmVsaWFiaWxpdHkgb2Ygb3VyIG1vZGVsLiBGaXJzdGx5LCBvdXIgc3R1ZHkgdXRpbGl6ZWQgYSBiaW5hcnkgY2xhc3NpZmljYXRpb24gb2YgaG90c3BvdCBhbmQgY29sZCBzcG90IHRvIGRlc2NyaWJlIHJpc2suIFdoaWxlIHRoaXMgaXMgYSBjb21tb24gYXBwcm9hY2ggaW4gcmlzayBtb2RlbGluZywgd2Ugc2hvdWxkIHJlY29nbml6ZSB0aGUgY29tcGxleGl0eSB3aXRoaW4gdGhlc2UgY2F0ZWdvcmllcyBhcyBhcmVhcyBuZWlnaGJvcmluZyBob3RzcG90cyBtYXkgYWxzbyBleGhpYml0IHZhcnlpbmcgZGVncmVlcyBvZiByaXNrLiBCdWlsZGluZyBvbnRvIHRoYXQsIGFub3RoZXIgbWFqb3IgaXNzdWUgaXMgdGhhdCBvdXIgYW5hbHlzaXMgaGFzIGNhdGVnb3JpemVkICJpbnNpZ25pZmljYW50IiBncmlkcyBpbnRvIGNvbGQgc3BvdHMuIEluIHRoZW9yeSwgY29sZCBzcG90IHNob3VsZCByZWZlciB0byBwbGFjZXMgd2l0aCBhIHNlcmllcyBvZiBncmlkcyB0aGF0IGhhdmUgbG93IGhlcm9pbi1pbmNpZGVuY2UgY291bnQgd2hlcmVhcyBmb3IgaW5zaWduaWZpY2FudCBncmlkLCB0aGVyZSBtaWdodCBiZSBhIGhpZ2ggbnVtYmVyIG9mIGluY2lkZW5jZXMgd2l0aGluIHNwZWNpZmljIGdyaWQuIEl04oCZcyBqdXN0IHRoYXQgdGhvc2UgZ3JpZHMgZG8gbm90IGZvcm0gY2x1c3RlcnMgd2l0aCBzdXJyb3VuZGluZyBncmlkcy4gTWVyZ2luZyBpbnNpZ25pZmljYW50IGdyaWRzIGludG8gY29sZCBzcG90IG1pZ2h0IHJpc2sgb3Zlcmxvb2tpbmcgaGVyb2luLWluY2lkZW5jZSB3aXRoaW4gdGhvc2UgZ3JpZHMuIAoKU2Vjb25kbHksIHdpdGhpbiB0aGUgZGlzY3Vzc2lvbiwgd2UgaGlnaGxpZ2h0IHRoZSBwb3RlbnRpYWwgaW1iYWxhbmNlIGluIGxvZ2lzdGljIHJlZ3Jlc3Npb24sIHByb21wdGluZyBjb25zaWRlcmF0aW9uIGZvciB3ZWlnaHRlZCBsb2dpc3RpYyByZWdyZXNzaW9uIHRvIGFkZHJlc3MgZGlzcGFyaXRpZXMgaW4gdGhlIHByZWRpY3Rpb24gb2YgcG9zaXRpdmUgYW5kIG5lZ2F0aXZlIGNhc2VzLiBUaGlyZGx5LCBkdXJpbmcgY3Jvc3MgdmFsaWRhdGlvbiwgd2UgZm91bmQgdGhhdCBvdXIgbW9kZWwgZG9lcyBub3QgZ2VuZXJhbGl6ZSB3ZWxsIGFjcm9zcyBuZWlnaGJvcmhvb2RzLiBUaGlzIGhpZ2hsaWdodHMgc2lnbmlmaWNhbnQgdmFyaWF0aW9ucyBiZXR3ZWVuIGRpZmZlcmVudCBuZWlnaGJvcmhvb2RzIGluIENpbmNpbm5hdGksIHdoaWNoIHJlcXVpcmVzIGZ1cnRoZXIgaW52ZXN0aWdhdGlvbnMuIEZvdXJ0aGx5LCBhcyB3ZSBuZWVkIHRvIGFycm9nYXRlIGluZm9ybWF0aW9uIGludG8gYSBzdGFuZGFyZGl6ZWQgc3BhdGlhbCB1bml0IHRoYXTigJlzIGRpZmZlcmVudCBmcm9tIGFueSBleGlzdGluZyBzcGF0aWFsIHVuaXQsIHdlIG5lZWQgdG8gYmUgYXdhcmUgb2YgaG93IG91ciBtb2RlbCBtaWdodCBiZSBzZW5zaXRpdmUgdG8gZXJyb3JzIGFuZCB1bmNlcnRhaW50aWVzIGludHJvZHVjZWQgYnkgYXJlYS13ZWlnaHRlZCByZS1hZ2dyZWdhdGlvbiBhbmQgYm91bmRhcnkvZWRnZSBlZmZlY3QuIAoKUmVnYXJkbGVzcywgdGhlIHRha2Vhd2F5cyBmcm9tIHRoaXMgYW5hbHlzaXMgd2lsbCBjb250cmlidXRlIHRvIG91ciB1bmRlcnN0YW5kaW5nIG9mIHRoZSBudWFuY2VkIGxhbmRzY2FwZSBvZiBoZXJvaW4tb3ZlcmRvc2UgY3Jpc2lzIGluIENpbmNpbm5hdGkgYW5kIHByb3ZpZGUgaW1wb3J0YW50IGd1aWRhbmNlIG9uIHB1YmxpYyBoZWFsdGggaW5pdGlhdGl2ZXMgYWltaW5nIHRvIGFkZHJlc3MgaGVyb2luIGFkZGljdGlvbi4gCgpXZSBiZWxpZXZlIHRoYXQgb3VyIHJpc2sgdGVycmFpbiBtb2RlbCBjb3VsZCBlZmZlY3RpdmVseSB2aXN1YWxpemUgaGVyb2luLW92ZXJkb3NlIGhvdHNwb3RzIGFuZCBjb3VsZCBzZXJ2ZSBhIGdvb2Qgc3RhcnRpbmcgcG9pbnQgZm9yIHByb3ZpZGluZyByZWFsLXRpbWUgdXBkYXRlIHRocm91Z2ggY2FsY3VsYXRpbmcgYSByaXNrIHNjb3JlIGZvciBlYWNoIHBhcnQgb2YgdGhlIGNpdHksIGFsbG93aW5nIHRoZSBnb3Zlcm5tZW50IHRvIGNvbmR1Y3QgcmVhbC10aW1lIGRhdGEgYW5hbHlzaXMgdG8gZWZmaWNpZW50bHkgYWxsb2NhdGUgRU1TIHJlc291cmNlcy4gV2UgaG9wZSB0aGF0IG91ciBlZmZvcnQgaW4gYnJpbmdpbmcgaW4gZGF0YSBmcm9tIGEgdmFyaWV0eSBvZiBzb3VyY2VzIHdvdWxkIGVuY291cmFnZSBkYXRhIHNoYXJpbmcgYmV0d2VlbiBwdWJsaWMgYW5kIHByaXZhdGUgc2VjdG9ycyBzbyB0aGF0IHRoZSBnb3Zlcm5tZW50IGNhbiBtYW5hZ2UgcHVibGljIHNlcnZpY2UgYW5kIG1vbml0b3IgaXRzIGJ1aWx0IGVudmlyb25tZW50LiBSZXBvcnRzIGNhbiBiZSBjb2xsZWN0ZWQgZnJvbSBwdWJsaWMgcGFya3MsIGZhc3QgZm9vZCByZXN0YXVyYW50cywgZ2FzIHN0YXRpb25zLCBwbGFjZXMgd2hlcmUgbW9zdCBvdmVyZG9zZSBpbmNpZGVuY2Ugb2NjdXJzLiBIYXZpbmcgZGF0YSBmcm9tIGxvY2FsIGhvc3BpdGFscyBhbmQgcmVoYWJpbGl0YXRpb24gY2VudGVycyBhbGxvd3MgdGhlIGdvdmVybm1lbnQgdG8gc2VlIGNhcGFjaXR5IGluZm9ybWF0aW9uLCB0aGVyZWJ5IGVmZmljaWVudGx5IGRpc3BhdGNoaW5nIHByZXBhcmluZyBhbmQgZGlzcGF0Y2hpbmcgY2FyZSBzbyB0aGF0IHRoZSByaXNrIG9mIGhlcm9pbiBvdmVyZG9zZSBpcyBtaW5pbWl6ZWQgYmVmb3JlIHRoZXkgaGFwcGVuLiAKCldlIGxvb2sgZm9yd2FyZCB0byBzZWVpbmcgb3VyIHJpc2sgdGVycmFpbiBtb2RlbCBiZWluZyBhZGFwdGVkIGluIHZhcmlvdXMgc2NlbmFyaW9zIHRoYXQgdHJ1bHkgYmVuZWZpdCBib3RoIHRoZSBzdGFrZWhvbGRlcnMgaW4gbWFuYWdpbmcgY29tbXVuaXR5IGhlYWx0aCBhbmQgd2VsbC1iZWluZyBhcyB3ZWxsIGFzIHNlcnZlIHRoZSBuZWVkcyBvZiBsb2NhbCByZXNpZGVudHMuIAoKCiMgUmVmZXJlbmNlcwoKQ2hvaSwgSi4gSS4sIExlZSwgSi4sIFllaCwgQS4gQi4sIExhbiwgUS4sICYgS2FuZywgSC4gKDIwMjIpLiBTcGF0aWFsIGNsdXN0ZXJpbmcgb2YgaGVyb2luLXJlbGF0ZWQgb3ZlcmRvc2UgaW5jaWRlbnRzOiBhIGNhc2Ugc3R1ZHkgaW4gQ2luY2lubmF0aSwgT2hpby4gQk1DIHB1YmxpYyBoZWFsdGgsIDIyKDEpLCAxLTEyLiBbaHR0cHM6Ly9kb2kub3JnLzEwLjExODYvczEyODg5LTAyMi0xMzU1Ny0zXShodHRwczovL2RvaS5vcmcvMTAuMTE4Ni9zMTI4ODktMDIyLTEzNTU3LTMpCgpTcmluaXZhc2FuLCBTLiwgUHVzdHosIEouLCBNYXJzaCwgRS4sIFlvdW5nLCBMLiBELiwgJiBTdG9wa2EsIFQuIEouICgyMDIzKS4gUmlzayBmYWN0b3JzIGZvciBwZXJzaXN0ZW50IGZhdGFsIG9waW9pZC1pbnZvbHZlZCBvdmVyZG9zZSBob3RzcG90cyBpbiBNYXNzYWNodXNldHRzIDIwMTEtMjAyMTogQSBzcGF0aWFsIHN0YXRpc3RpY2FsIGFuYWx5c2lzIHdpdGggc29jaW8tZWNvbm9taWMsIGFjY2VzcyBhbmQgcHJlc2NyaXB0aW9uIGZhY3RvcnMuW2h0dHBzOi8vZG9pLm9yZy8xMC4yMTIwMy9ycy4zLnJzLTMyNDk2NTAvdjFdKGh0dHBzOi8vZG9pLm9yZy8xMC4yMTIwMy9ycy4zLnJzLTMyNDk2NTAvdjEpCgpDaGljaGVzdGVyLCBLLiBSLiwgRHJhd3ZlLCBHLiwgU2lzc29uLCBNLiwgR2ltw6luZXotU2FudGFuYSwgQS4sIE1jQ2xlc2tleSwgQi4sIEdvb2RpbiwgQi4gUi4sIC4uLiAmIENyb3BzZXksIEsuIEwuICgyMDIzKS4gQ3JpbWUgYW5kIEZlYXR1cmVzIG9mIHRoZSBCdWlsdCBFbnZpcm9ubWVudCBQcmVkaWN0aW5nIFJpc2sgb2YgRmF0YWwgT3ZlcmRvc2U6IEEgQ29tcGFyaXNvbiBvZiBSdXJhbCBhbmQgVXJiYW4gT2hpbyBDb3VudGllcyB3aXRoIFJpc2sgVGVycmFpbiBNb2RlbGluZy4gQW1lcmljYW4gSm91cm5hbCBvZiBDcmltaW5hbCBKdXN0aWNlLCAxLTI1LiBbaHR0cHM6Ly9kb2kub3JnLzEwLjEwMDcvczEyMTAzLTAyMy0wOTczOS0zXShodHRwczovL2RvaS5vcmcvMTAuMTAwNy9zMTIxMDMtMDIzLTA5NzM5LTMpCgoK