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:

Variable Name Source
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