Introduction

This project “Geospatial Risk Predictions of Armed Robbery Incidents in Chicago” explores the application of predictive policing through geospatial risk modeling to forecast the likelihood of armed robbery incidents in Chicago. By utilizing historical crime data, socioeconomic indicators, and spatial risk factors such as abandoned cars, street light outages, and sanitation complaints, the model aims to identify high-risk areas for armed robbery incidents. The study employs Poisson regression to predict crime counts and compares the accuracy of spatial models against traditional methods like kernel density estimation. Emphasizing the importance of addressing bias and data selection issues, the project aims to improve the generalizability and fairness of crime prediction models across different neighborhoods, particularly considering racial and socioeconomic disparities. Through spatial cross-validation and the inclusion of spatial process features, this analysis seeks to enhance predictive accuracy and offer insights into better resource allocation for law enforcement.

The objective of this report is to develop a geospatial risk model for predicting armed robbery incidents in Chicago. Robbery data is often influenced by selection bias due to two key factors: not all robbery incidents are reported to law enforcement, and areas with historically high robbery rates, which receive increased police presence, tend to continue reporting higher incidents. The underlying hypothesis is that crime risk is driven by exposure to various geospatial risk and protective factors, such as signs of blight or the presence of recreation centers. As exposure to risk factors rises, so does the likelihood of crime. In addition to identifying these risk factors, the model incorporates spatial variables to improve prediction accuracy. Poisson regression is used in this analysis due to its suitability for modeling count data. 1

Crime Data Collection

All datasets for this study were sourced from Chicago Data Portal. The Chicago Data Portal is an online platform that provides access to a wide range of municipal datasets. The portal includes an extensive record of crime incidents in Chicago, updated annually. For this analysis, crime data for 2021 was downloaded using the RSocrata package, which allows easy access to open data from platforms like Socrata, the software behind the Chicago Data Portal.

This report specifically filter the armed robbery crime incidents in Chicago. The armed robbery include: KNIFE / CUTTING INSTRUMENT, HANDGUN, OTHER FIREARM, or OTHER DANGEROUS WEAPON. After filtering the crime data for armed robbery incidents, we focused on those involving the following weapon types: knife/cutting instrument, handgun, other firearms, or other dangerous weapons. These specific categories provide a clearer understanding of more serious and violent robbery offenses, as they pose a higher threat to victims and are more likely to occur in areas with distinct risk factors. By narrowing our analysis to armed robberies, we aim to explore the spatial distribution of these incidents and assess how environmental and social variables, such as abandoned buildings, street light outages, and sanitation complaints, correlate with higher occurrences of these violent crimes. This targeted approach enables us to develop a more precise predictive model for robbery risk in different areas of Chicago.

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

The map displays the spatial distribution of armed robbery incidents in Chicago for 2021, revealing a clear pattern of concentration in specific areas. The highest density of incidents occurs in the central and southern parts of the city, with particular clustering around the city’s south side and certain western neighborhoods. These areas likely experience higher rates of violent crime due to a combination of socio-economic factors and environmental conditions. In contrast, the northern parts of the city, especially near the lakefront, show significantly fewer armed robberies. This spatial pattern suggests that certain regions may face more pronounced crime risks, possibly influenced by urban infrastructure, economic distress, or other factors that contribute to the likelihood of robbery incidents. Understanding these clusters can help inform targeted interventions and crime prevention strategies in high-risk areas.

ggplot() + 
  geom_sf(data = chicagoBoundary, fill = "grey") +
  geom_sf(data = robbery21, colour="darkred", size=0.1, show.legend = "point") +
  labs(title= "Armed Robbery Incidents in Chicago 2021") + 
  theme(axis.text.x=element_blank(),
        axis.text.y=element_blank(),
        axis.ticks =element_blank(),
        axis.title.x = element_blank(),
        axis.title.y = element_blank(),
        plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 12, face = "bold"),
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill=NA, linewidth =0.8)
        )

Below is the density map of armed robbery incidents in Chicago. The density map of armed robbery incidents in Chicago reveals two prominent hotspots. The first cluster is located in the central-western part of the city, while the second significant concentration is found slightly to the south of downtown. These areas experience the highest intensity of armed robberies, indicating localized zones with increased risk. The surrounding areas show a gradual decrease in density as we move away from these core hotspots, suggesting that armed robberies are not evenly distributed across the city but are instead concentrated in specific regions. The northern and far southern parts of the city exhibit much lower densities, indicating that armed robberies are relatively less common in these areas. This pattern highlights the need for focused crime prevention efforts in the identified high-risk zones.

options(scipen=0)
ggplot() + 
  geom_sf(data = chicagoBoundary, fill = "grey") +
  stat_density2d(data = data.frame(st_coordinates(robbery21)), 
                 aes(X, Y, fill = ..level.., alpha = ..level..),
                 size = 0.01, bins = 60, geom = 'polygon') +
  scale_fill_viridis(option = "turbo", name = "Density") +
  scale_alpha(range = c(0.00, 0.35), guide = "none") +
  labs(title = "Density of Armed Robbery") +
  theme(axis.text.x=element_blank(),
        axis.text.y=element_blank(),
        axis.ticks =element_blank(),
        axis.title.x = element_blank(),
        axis.title.y = element_blank(),
        plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 12, face = "bold"),
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill=NA, linewidth=0.8)
        )

Crime risk tends to fluctuate gradually across geographic space rather than conforming to predefined administrative boundaries. To effectively capture this continuous variation, point-level data can be aggregated into a network of evenly spaced grid cells. The code provided below generates such a fishnet for Chicago, offering a structured way to represent crime risk spatially. This grid-based approach is ideal for preparing the data for regression analysis, allowing for a smoother depiction of crime trends across the urban landscape.

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

ggplot() +
  geom_sf(data=fishnet, color="black", fill="lightblue") +
  labs(title = "Fishnet of Chicago") +
  theme(axis.text.x=element_blank(),
        axis.text.y=element_blank(),
        axis.ticks =element_blank(),
        axis.title.x = element_blank(),
        axis.title.y = element_blank(),
        plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 12, face = "bold"),
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill=NA, linewidth=0.8)
        )

Once the fishnet was created, use it as the spatial framework to visualize the distribution of robbery incidents. From this, it becomes clear that the highest number of robberies are concentrated in a few grid cells located in northeast Chicago. This pattern highlights the emergence of a clustered spatial trend in robbery occurrences.

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

ggplot() +
  geom_sf(data = robbery21_net, aes(fill = countRobbery), color = NA) +
  scale_fill_viridis(option = "turbo", name = "Robbery Counts") +
  labs(title = "Count of Robberies for the Fishnet") +
  theme(axis.text.x=element_blank(),
        axis.text.y=element_blank(),
        axis.ticks =element_blank(),
        axis.title.x = element_blank(),
        axis.title.y = element_blank(),
        plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 12, face = "bold"),
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill=NA, linewidth=0.8)
        )

The bar chart below illustrates the distribution of robbery incidents in Chicago for 2021, revealing a highly skewed pattern. A large number of grid cells have few or no robbery incidents, while only a small number of grids experience significantly higher robbery counts. This non-normal distribution suggests that Poisson regression is a suitable method for analyzing this data, as it can handle the overdispersion and count nature of the variable.

ggplot(robbery21_net, aes(x = countRobbery)) +
  geom_histogram(fill = "steelblue", color = "#2c3e50", bins = 30) + 
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1, face = "plain", size = 7),
        axis.text.y = element_text(size = 7), 
        axis.title = element_text(size = 9), 
        plot.title = element_text(size = 14, face = "bold"), 
        plot.subtitle = element_text(size = 10), 
        panel.grid.major = element_line(color = "grey80"), 
        panel.grid.minor = element_blank(),
        panel.border = element_rect(colour = "grey60", fill = NA, size = 0.8)) + 
  labs(title = "Distribution of Robberies in Chicago 2021",
       subtitle = "Analysis of robbery incident distribution",
       caption = "Data: Chicago Data Portal Crimes 2021") +
  xlab("Robbery Incidents") +
  ylab("Count")

Risk Factors

In this project, eight risk factors are included in the model to predict robberies: reports of abandoned cars, street lights out, graffiti, sanitation complaints, abandoned buildings, locations of liquor stores selling alcohol to-go, ShotSpotter detections, and banks. Neighborhoods with abandoned cars, street lights out, graffiti, and sanitation issues often indicate neglect, economic distress, and reduced community oversight, which can create an environment conducive to criminal activities. Abandoned buildings can serve as hideouts for criminals, further contributing to neighborhood disorder. Liquor stores are associated with alcohol-related vulnerabilities, while ShotSpotter detections point to areas with higher gun violence. Finally, banks, due to the availability of cash, may attract robbers seeking financial gain. These factors collectively highlight areas that may be more susceptible to robberies.

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

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

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

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

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


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

Banks <- 
  read.socrata("https://data.cityofchicago.org/Administration-Finance/Lending-Equity-Depository-Locations-2021/jgup-zt7k") %>%  
  dplyr::select(location) %>%
  na.omit()
Banks <- Banks %>%   
  mutate(X = as.numeric(sub("POINT \\((-?\\d+\\.\\d+) \\d+\\.\\d+\\)", "\\1", Banks$location)),
         Y = as.numeric(sub("POINT \\(-?\\d+\\.\\d+ (-?\\d+\\.\\d+)\\)", "\\1", Banks$location))) %>%
  dplyr::select(-location) %>% 
  filter(!is.na(X) & !is.na(Y)) %>% 
  st_as_sf(coords = c("X", "Y"), crs = 4326, agr = "constant") %>%
    st_transform(st_crs(fishnet)) %>%
    mutate(Legend = "Bank") 
vars_net <- 
  rbind(abandonCars,streetLightsOut,abandonBuildings,
        liquorRetail, graffiti, sanitation, shotSpotter, Banks) %>%
  st_join(., fishnet, join=st_within) %>%
  st_drop_geometry() %>%
  group_by(uniqueID, Legend) %>%
  summarize(count = n()) %>%
    full_join(fishnet) %>%
    spread(Legend, count, fill=0) %>%
    st_sf() %>%
    dplyr::select(-`<NA>`) %>%
    na.omit() %>%
    ungroup()

The figure illustrates the distinct spatial patterns of each risk factor across Chicago. Abandoned buildings are predominantly concentrated in the southern part of the city, while banks and liquor retail stores cluster around the central business district (CBD). Abandoned cars, sanitation complaints, and street lights out complaints are more evenly scattered throughout the city. Graffiti removal requests are mainly concentrated near the city center, whereas ShotSpotter detections tend to occur in areas outside the city center, highlighting the spatial variability in factors associated with potential robbery risks.

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

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


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

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

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


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

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

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


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

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

Feature Engineering

Nearest Distance

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

st_c <- st_coordinates
st_coid <- st_centroid

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

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

The neighbor features provide a clearer picture of the spatial relationships between different risk factors.The southern and southeastern areas of Chicago appear to be most distant from many of these risks, particularly abandoned buildings, abandoned cars, and graffiti. Interestingly, the southwestern corner also shows some distance from risk factors like banks and liquor stores. One notable observation is that the northern part of the city is furthest from ShotSpotter locations, while still being relatively close to other risk factors such as sanitation issues and street lights out. The relative absence of ShotSpotters in this area raises questions about their role: are they an indicator of higher crime due to their concentration, or does their presence suggest increased policing, making neighborhoods safer? These observations highlight the need to examine whether ShotSpotters function as a crime detection tool or a proxy for neighborhood safety. These are important considerations for understanding the broader dynamics of crime and safety in Chicago.

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


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


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

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

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


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

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

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


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

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

Local Spatial Autocorrelation

identified robbery hotspots in Chicago using Local Moran’s I, which examines whether robbery counts at a location are clustered relative to neighboring cells. A spatial weights matrix was calculated using queen contiguity to define neighboring relationships. The results, visualized with Moran’s I values, p-values, and significant hotspots, show high spatial autocorrelation in northeastern Chicago near the CBD. Areas with significant hotspots (p-values ≤ 0.05) align with patterns seen in the kernel density map, indicating clusters of higher robbery counts than expected by chance.

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

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

final_net.localMorans <- 
  cbind(local_morans, as.data.frame(final_net)) %>% 
  st_sf() %>%
  dplyr::select(Robbery_Count = countRobbery, 
                Local_Morans_I = Ii, 
                P_Value = `Pr(z != E(Ii))`) %>%
  mutate(Significant_Hotspots = ifelse(P_Value <= 0.001, 1, 0)) %>%
  gather(Variable, Value, -geometry)
moran1 <- ggplot() +
  geom_sf(data = final_net.localMorans %>% filter(Variable == "Robbery_Count"), aes(fill=Value), colour=NA) +
  scale_fill_viridis(option = "turbo") + 
  labs(title = "Robbery Count") +
  theme(axis.text.x=element_blank(),
        axis.text.y=element_blank(),
        axis.ticks =element_blank(),
        axis.title.x = element_blank(),
        axis.title.y = element_blank(),
        plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 10, face = "bold"),
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill=NA, size=0.8)
        )

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

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


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

moran1 + moran2 + moran3 + moran4

Singificant Robbery Clusters

The map illustrates the distance to the nearest robbery hotspots across Chicago, with darker colors indicating shorter distances. The central and southern parts of the city exhibit the shortest distances to robbery hotspots, as shown by the deep blue and black areas. This suggests that these regions are located closer to areas with a high concentration of robbery incidents. In contrast, the outer northern and southern parts of the city, represented by yellow, green, and red hues, are farther away from these hotspots, indicating lower proximity to concentrated robbery activities. This pattern highlights the spatial distribution of robbery risks in Chicago, with more concentrated robbery activity occurring in specific regions, particularly in the central and southern areas. The distance gradient provides insight into how robbery risks decrease as one moves away from these core hotspots.

final_net <-
  final_net %>% 
  mutate(robbery.isSig = ifelse(localmoran(final_net$countRobbery, 
                             final_net.weights)[,5] <= 0.0000001, 1, 0)) %>%
  mutate(robbery.isSig.dist = nn_function(st_coordinates(st_centroid(final_net)),
                                          st_coordinates(st_centroid(
                                            filter(final_net, robbery.isSig == 1))), 1))
ggplot() +
      geom_sf(data = final_net, aes(fill=robbery.isSig.dist), colour=NA) +
      scale_fill_viridis(option = "turbo", name="NN Distance") +
      labs(title="Robbery NN Distance") +
      theme(axis.text.x=element_blank(),
        axis.text.y=element_blank(),
        axis.ticks =element_blank(),
        axis.title.x = element_blank(),
        axis.title.y = element_blank(),
        plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 10, face = "bold"),
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill=NA, size=0.8)
        )

All Risk Factors

The figure shows scatter plots of the count of predictor variables and their nearest neighbor distances in relation to robbery incidents. All count-based features, such as abandoned buildings, cars, and sanitation complaints, are positively correlated with the number of robberies, indicating that areas with higher occurrences of these risk factors tend to experience more robberies. The strongest positive relationships are observed between robbery incidents and sanitation complaints, ShotSpotter detections, and liquor retail locations.

Conversely, all nearest neighbor distance features are negatively correlated with robbery incidents, meaning that as the distance to risk factors increases, the number of robberies decreases. The strongest negative correlations are seen with distance to significant robbery hotspots, abandoned buildings, and liquor retail stores. This suggests that proximity to these risk factors plays a significant role in robbery patterns.

When it comes to model building, this information is critical for feature selection. To avoid multicollinearity, either the count or the nearest neighbor distance should be selected for each risk factor. In this case, the model incorporates nearest neighbor distances, as they tend to show stronger and more consistent correlations with robbery incidents.

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

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

final_net_long %>%
  ggplot(aes(x= Number, y = countRobbery)) +
  geom_point(size = 0.01, color = "#000004") +  
  geom_text(data = correlation.cor, aes(label = paste("r =", round(correlation, 2))),
            x=-Inf, y=Inf, vjust = 1.5, hjust = -.1, size=3) +
  geom_smooth(method='lm', formula= y~x, lwd=0.5, se = FALSE, color = "#BB3754") +
  facet_wrap(~ Type, scales = "free", ncol = 2, labeller= labeller(Type = c(
    `Abandoned_Buildings` = "Abandoned Buildings",
    `Abandoned_Buildings.nn` = "Distance to Abandoned Buildings",
    `Abandoned_Cars` = "Abandoned Cars",
    `Abandoned_Cars.nn` = "Distance to Abandoned Cars",
    `Bank` = "Bank",
    `Bank.nn` = "Distance to Bank",
    `Graffiti` = "Graffiti",
    `Graffiti.nn` = "Distance to Graffiti",
    `Liquor_Retail` = "Liquor Retail",
    `Liquor_Retail.nn` = "Distance to Liquor Retail",
    `robbery.isSig` = "Significant Robbery",
    `robbery.isSig.dist` = "Distance to Significant Robbery",
    `Sanitation` = "Sanitation",
    `Sanitation.nn` = "Distance to Sanitation",
    `Shot_Spotter` = "Shot Spotter", 
    `Shot_Spotter.nn` = "Distance to Shot Spotter",
    `Street_Lights_Out` = "Street Light Out",
    `Street_Lights_Out.nn` = "Distance to Broken Street Light")))  +
  labs(title = "Scatter Plots of Predictor Variables",
       caption = "Data: Chicago Data Portal") +
  theme(plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 12, face = "bold"), 
        axis.text.x=element_text(size=6),
        axis.text.y=element_text(size=6), 
        axis.title=element_text(size=8), 
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill=NA, size=0.8))

Poisson Regression

Random K-Fold CV

The table below presents goodness of fit metrics for four regression models. The first set of models includes only risk factors ‘reg.vars’, while the second set ‘reg.ss.vars’ incorporates both risk factors and the Local Moran’s I spatial process. To evaluate model performance, two types of cross-validation were conducted. The first method assigns a randomly generated cvID to each grid cell, allowing for random k-fold cross-validation to assess prediction accuracy.

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

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

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

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

Spatial LOGO CV

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

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

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

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

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

Error Analysis

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

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

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

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

st_drop_geometry(error_by_reg_and_fold) %>%
  group_by(Regression) %>% 
    summarize(Mean_MAE = round(mean(MAE), 2),
              SD_MAE = round(sd(MAE), 2)) %>%
  kable(col.name=c("Regression", 'Mean Absolute Error','Standard Deviation MAE')) %>%
    kable_styling(bootstrap_options = c("striped", "hover", "condensed")) %>%
    row_spec(2, color = "black", background = "#FCFFA4") %>%
    row_spec(4, color = "black", background = "#FCFFA4") 
Regression Mean Absolute Error Standard Deviation MAE
Random k-fold CV: Just Risk Factors 0.34 0.29
Random k-fold CV: Spatial Process 0.32 0.27
Spatial LOGO-CV: Just Risk Factors 0.93 0.96
Spatial LOGO-CV: Spatial Process 0.69 0.77

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

error_by_reg_and_fold %>% 
  ggplot() +
  geom_sf(aes(fill=MAE), color="transparent") +
  scale_fill_viridis(option = "turbo", direction = -1) + 
  facet_wrap(~Regression, ncol = 4) +
   labs(title = "MAE by Fold and Regression",
       caption = "Data: Chicago Data Portal") +
  theme(legend.position = "bottom",
        axis.text.x=element_blank(),
        axis.ticks.x=element_blank(),
        axis.text.y=element_blank(),
        axis.ticks.y=element_blank(),
        plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 12, face = "bold"),
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill=NA, size=0.8),
        strip.text = element_text(size = 7)) 

Prediction Accuracy

Generalizability Test

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

census_api_key(dlgInput(
  "Enter a Census API Key", # ask for an api key
  Sys.getenv("CENSUS_API_KEY")
)$res,
overwrite = TRUE)
tracts21 <- 
  get_acs(geography = "tract", 
          variables = c("B02001_001E", # total population
            "B02001_002E", # white population
            "B02001_003E", # black population
            "B02001_005E", # asian population
            "B03002_012E"), 
          year=2021, state=17, county=031, 
          geometry=TRUE, output="wide") %>%
  st_transform('ESRI:102271') %>% 
  rename(TotalPop = B02001_001E, 
         Whites = B02001_002E,
         African_Americans = B02001_003E,
         Asians = B02001_005E,
         Latinx = B03002_012E) %>% 
  mutate(pctMinority = ifelse(TotalPop > 0, (African_Americans + Asians + Latinx ) / TotalPop, 0), 
         majority = ifelse(pctMinority > 0.5, "minority", "majority")) %>%   .[neighborhoods,]

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

ggplot() + geom_sf(data = na.omit(tracts21), aes(fill = majority), color = NA) +
    scale_fill_manual(values = c("#BB3754", "#56106E"), name="Race Context") +
    labs(title = "Race Context ",
         caption = "Data: American Community Survey 2021") +
  theme(
        axis.text.x=element_blank(),
        axis.ticks.x=element_blank(),
        axis.text.y=element_blank(),
        axis.ticks.y=element_blank(),
        plot.title = element_text(size = 12, face = "bold"),
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill=NA, size=0.4)
        ) 

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

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


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


reg.summary %>% 
  mutate(raceContext = ifelse(meanMinor > .6, "Minority", "Majority White")) %>% 
  st_drop_geometry() %>% 
  group_by(Regression, raceContext) %>%
  summarize(mean.Error = mean(Error, na.rm = T)) %>%
  spread(raceContext, mean.Error) %>%
  kable() %>%
    kable_styling(bootstrap_options = c("striped", "hover", "condensed")) %>%
    row_spec(2, color = "black", background = "#FCFFA4") %>%
    row_spec(4, color = "black", background = "#FCFFA4") 
Regression Majority White Minority
Random k-fold CV: Just Risk Factors 0.2067014 -0.0155049
Random k-fold CV: Spatial Process 0.1954263 -0.0142177
Spatial LOGO-CV: Just Risk Factors 0.2224433 -0.1437801
Spatial LOGO-CV: Spatial Process 0.1220346 -0.0872692

Kernel Desntiy Estimation

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

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


ggplot(data=rob_KD.df, aes(x=x, y=y)) +
  geom_raster(aes(fill=layer)) + 
  coord_sf(crs=st_crs(final_net)) + 
  scale_fill_viridis(option = "turbo", name="Density") +
  labs(title = "Kernel Density of Robbery 1000ft Radii") +
    theme(axis.text.x=element_blank(),
        axis.text.y=element_blank(),
        axis.ticks =element_blank(),
        axis.title.x = element_blank(),
        axis.title.y = element_blank(),
        plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 12, face = "bold"),
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill=NA, size=0.8)
        )

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

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

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

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

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

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

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

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

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

Prediction Comparison

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

rbind(rob_KDE_sf, rob_risk_sf, rob_risk_sf_simple) %>%
  na.omit() %>%
  gather(Variable, Value, -label, -Risk_Category, -geometry) %>%
  ggplot() +
    geom_sf(aes(fill = Risk_Category), colour = NA) +
    facet_wrap(~label, ) +
    scale_fill_viridis(option = "turbo", discrete = TRUE, name = "Risk Category") +
      geom_sf(data = sample_n(robbery22, 3000), size = .03, colour = "white") +
    labs(title="Comparison of Kernel Density and Risk Predictions",
         subtitle="2021 robbery risk predictions; 2022 armed robberies") + 
  theme(axis.text.x=element_blank(),
        axis.text.y=element_blank(),
        axis.ticks =element_blank(),
        axis.title.x = element_blank(),
        axis.title.y = element_blank(),
        plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 12, face = "bold"),
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill=NA, size=0.8)
        )

An effective way to assess prediction accuracy is by using a histogram. Ideally, a well-fitting model should show that the highest-risk areas capture a larger proportion of 2022 robberies compared to the kernel density estimate. However, in this case, the kernel density model outperforms both risk prediction models in the highest-risk regions. Despite this, the risk prediction models excel in identifying armed robbery incidents in lower-risk areas that the kernel density model overlooked.

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

Conclusions

In conclusion, our model requires significant improvement before it can be implemented effectively. Compared to traditional kernel density estimation, it struggled to capture armed robbery incidents in the highest-risk areas. One key issue is that many of our predictor variables, such as reports of abandoned cars, graffiti, and sanitation complaints, were outdated, last updated in 2019. Using these to predict robberies in 2021 and 2022 likely led to inaccurate results, especially since shifts in these risk factors (like graffiti removal) would alter crime patterns. Up-to-date datasets are crucial for more accurate predictions.

Additionally, the COVID-19 pandemic likely impacted armed robbery patterns, with economic distress, lockdowns, and reallocation of police resources all contributing to shifts in crime locations. Our model needs to account for such factors to ensure predictions remain relevant over time.

On a positive note, the model performed better in lower-risk areas, where the kernel density method fell short. However, relying on a single model may not capture the full complexity of armed robbery patterns. A combined approach using both kernel density estimation and risk prediction could improve accuracy. While we avoided overpredicting in majority non-white neighborhoods, potential selection bias remains a concern. Incorporating spatial features and using k-fold cross-validation helped improve the model, but further refinement is needed to ensure it generalizes well across different contexts and time periods.

LS0tCnRpdGxlOiAiUmlzayBQcmVkaWN0aW9ucyBvZiBBcm1lZCBSb2JiZXJ5IEluY2lkZW50cyBpbiBDaGljYWdvIgphdXRob3I6ICJZdXRvbmcgSmlhbmciCmRhdGU6ICJPY3QsMTgsMjAyNCIKb3V0cHV0OgogIGh0bWxfZG9jdW1lbnQ6CiAgICB0aGVtZTogY29zbW8gCiAgICB0b2M6IHllcwogICAgdG9jX2Zsb2F0OiB5ZXMKICAgIGNvZGVfZm9sZGluZzogaGlkZQogICAgY29kZV9kb3dubG9hZDogeWVzCmVkaXRvcl9vcHRpb25zOgogIG1hcmtkb3duOgogICAgd3JhcDogc2VudGVuY2UKLS0tCgoKCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUpCmBgYAoKCmBgYHtyIGxvYWQgbGlicmFyaWVzLCBpbmNsdWRlPUZBTFNFfQoKbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkoc2YpCmxpYnJhcnkoUlNvY3JhdGEpCmxpYnJhcnkodmlyaWRpcykKbGlicmFyeShzcGF0c3RhdC5leHBsb3JlKQpsaWJyYXJ5KHJhc3RlcikKbGlicmFyeShzcGRlcCkKbGlicmFyeShGTk4pCmxpYnJhcnkoZ3JpZCkKbGlicmFyeShncmlkRXh0cmEpCmxpYnJhcnkoa25pdHIpCmxpYnJhcnkoa2FibGVFeHRyYSkKbGlicmFyeSh0aWR5Y2Vuc3VzKQpsaWJyYXJ5KGNsYXNzSW50KSAgICMgZm9yIEtERSBhbmQgTUwgcmlzayBjbGFzcyBpbnRlcnZhbHMKbGlicmFyeShzdkRpYWxvZ3MpCmxpYnJhcnkocGF0Y2h3b3JrKQoKIyBmdW5jdGlvbnMKcm9vdC5kaXIgPSAiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3VyYmFuU3BhdGlhbC9QdWJsaWMtUG9saWN5LUFuYWx5dGljcy1MYW5kaW5nL21hc3Rlci9EQVRBLyIKc291cmNlKCJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vdXJiYW5TcGF0aWFsL1B1YmxpYy1Qb2xpY3ktQW5hbHl0aWNzLUxhbmRpbmcvbWFzdGVyL2Z1bmN0aW9ucy5yIikKCmNoaWNhZ29Cb3VuZGFyeSA8LSAKICBzdF9yZWFkKGZpbGUucGF0aChyb290LmRpciwiL0NoYXB0ZXI1L2NoaWNhZ29Cb3VuZGFyeS5nZW9qc29uIikpICU+JQogIHN0X3RyYW5zZm9ybSgnRVNSSToxMDIyNzEnKSAKCmBgYAoKCiMgSW50cm9kdWN0aW9uCgpUaGlzIHByb2plY3QgIkdlb3NwYXRpYWwgUmlzayBQcmVkaWN0aW9ucyBvZiBBcm1lZCBSb2JiZXJ5IEluY2lkZW50cyBpbiBDaGljYWdvIiBleHBsb3JlcyB0aGUgYXBwbGljYXRpb24gb2YgcHJlZGljdGl2ZSBwb2xpY2luZyB0aHJvdWdoIGdlb3NwYXRpYWwgcmlzayBtb2RlbGluZyB0byBmb3JlY2FzdCB0aGUgbGlrZWxpaG9vZCBvZiBhcm1lZCByb2JiZXJ5IGluY2lkZW50cyBpbiBDaGljYWdvLiBCeSB1dGlsaXppbmcgaGlzdG9yaWNhbCBjcmltZSBkYXRhLCBzb2Npb2Vjb25vbWljIGluZGljYXRvcnMsIGFuZCBzcGF0aWFsIHJpc2sgZmFjdG9ycyBzdWNoIGFzIGFiYW5kb25lZCBjYXJzLCBzdHJlZXQgbGlnaHQgb3V0YWdlcywgYW5kIHNhbml0YXRpb24gY29tcGxhaW50cywgdGhlIG1vZGVsIGFpbXMgdG8gaWRlbnRpZnkgaGlnaC1yaXNrIGFyZWFzIGZvciBhcm1lZCByb2JiZXJ5IGluY2lkZW50cy4gVGhlIHN0dWR5IGVtcGxveXMgUG9pc3NvbiByZWdyZXNzaW9uIHRvIHByZWRpY3QgY3JpbWUgY291bnRzIGFuZCBjb21wYXJlcyB0aGUgYWNjdXJhY3kgb2Ygc3BhdGlhbCBtb2RlbHMgYWdhaW5zdCB0cmFkaXRpb25hbCBtZXRob2RzIGxpa2Uga2VybmVsIGRlbnNpdHkgZXN0aW1hdGlvbi4gRW1waGFzaXppbmcgdGhlIGltcG9ydGFuY2Ugb2YgYWRkcmVzc2luZyBiaWFzIGFuZCBkYXRhIHNlbGVjdGlvbiBpc3N1ZXMsIHRoZSBwcm9qZWN0IGFpbXMgdG8gaW1wcm92ZSB0aGUgZ2VuZXJhbGl6YWJpbGl0eSBhbmQgZmFpcm5lc3Mgb2YgY3JpbWUgcHJlZGljdGlvbiBtb2RlbHMgYWNyb3NzIGRpZmZlcmVudCBuZWlnaGJvcmhvb2RzLCBwYXJ0aWN1bGFybHkgY29uc2lkZXJpbmcgcmFjaWFsIGFuZCBzb2Npb2Vjb25vbWljIGRpc3Bhcml0aWVzLiBUaHJvdWdoIHNwYXRpYWwgY3Jvc3MtdmFsaWRhdGlvbiBhbmQgdGhlIGluY2x1c2lvbiBvZiBzcGF0aWFsIHByb2Nlc3MgZmVhdHVyZXMsIHRoaXMgYW5hbHlzaXMgc2Vla3MgdG8gZW5oYW5jZSBwcmVkaWN0aXZlIGFjY3VyYWN5IGFuZCBvZmZlciBpbnNpZ2h0cyBpbnRvIGJldHRlciByZXNvdXJjZSBhbGxvY2F0aW9uIGZvciBsYXcgZW5mb3JjZW1lbnQuCgpUaGUgb2JqZWN0aXZlIG9mIHRoaXMgcmVwb3J0IGlzIHRvIGRldmVsb3AgYSBnZW9zcGF0aWFsIHJpc2sgbW9kZWwgZm9yIHByZWRpY3RpbmcgYXJtZWQgcm9iYmVyeSBpbmNpZGVudHMgaW4gQ2hpY2Fnby4gUm9iYmVyeSBkYXRhIGlzIG9mdGVuIGluZmx1ZW5jZWQgYnkgc2VsZWN0aW9uIGJpYXMgZHVlIHRvIHR3byBrZXkgZmFjdG9yczogbm90IGFsbCByb2JiZXJ5IGluY2lkZW50cyBhcmUgcmVwb3J0ZWQgdG8gbGF3IGVuZm9yY2VtZW50LCBhbmQgYXJlYXMgd2l0aCBoaXN0b3JpY2FsbHkgaGlnaCByb2JiZXJ5IHJhdGVzLCB3aGljaCByZWNlaXZlIGluY3JlYXNlZCBwb2xpY2UgcHJlc2VuY2UsIHRlbmQgdG8gY29udGludWUgcmVwb3J0aW5nIGhpZ2hlciBpbmNpZGVudHMuIFRoZSB1bmRlcmx5aW5nIGh5cG90aGVzaXMgaXMgdGhhdCBjcmltZSByaXNrIGlzIGRyaXZlbiBieSBleHBvc3VyZSB0byB2YXJpb3VzIGdlb3NwYXRpYWwgcmlzayBhbmQgcHJvdGVjdGl2ZSBmYWN0b3JzLCBzdWNoIGFzIHNpZ25zIG9mIGJsaWdodCBvciB0aGUgcHJlc2VuY2Ugb2YgcmVjcmVhdGlvbiBjZW50ZXJzLiBBcyBleHBvc3VyZSB0byByaXNrIGZhY3RvcnMgcmlzZXMsIHNvIGRvZXMgdGhlIGxpa2VsaWhvb2Qgb2YgY3JpbWUuIEluIGFkZGl0aW9uIHRvIGlkZW50aWZ5aW5nIHRoZXNlIHJpc2sgZmFjdG9ycywgdGhlIG1vZGVsIGluY29ycG9yYXRlcyBzcGF0aWFsIHZhcmlhYmxlcyB0byBpbXByb3ZlIHByZWRpY3Rpb24gYWNjdXJhY3kuIFBvaXNzb24gcmVncmVzc2lvbiBpcyB1c2VkIGluIHRoaXMgYW5hbHlzaXMgZHVlIHRvIGl0cyBzdWl0YWJpbGl0eSBmb3IgbW9kZWxpbmcgY291bnQgZGF0YS4gCjEKCgoKIyBDcmltZSBEYXRhIENvbGxlY3Rpb24KCkFsbCBkYXRhc2V0cyBmb3IgdGhpcyBzdHVkeSB3ZXJlIHNvdXJjZWQgZnJvbSBbQ2hpY2FnbyBEYXRhIFBvcnRhbF0oaHR0cHM6Ly9kYXRhLmNpdHlvZmNoaWNhZ28ub3JnLykuIFRoZSBDaGljYWdvIERhdGEgUG9ydGFsIGlzIGFuIG9ubGluZSBwbGF0Zm9ybSB0aGF0IHByb3ZpZGVzIGFjY2VzcyB0byBhIHdpZGUgcmFuZ2Ugb2YgbXVuaWNpcGFsIGRhdGFzZXRzLiBUaGUgcG9ydGFsIGluY2x1ZGVzIGFuIGV4dGVuc2l2ZSByZWNvcmQgb2YgY3JpbWUgaW5jaWRlbnRzIGluIENoaWNhZ28sIHVwZGF0ZWQgYW5udWFsbHkuIEZvciB0aGlzIGFuYWx5c2lzLCBjcmltZSBkYXRhIGZvciAyMDIxIHdhcyBkb3dubG9hZGVkIHVzaW5nIHRoZSBSU29jcmF0YSBwYWNrYWdlLCB3aGljaCBhbGxvd3MgZWFzeSBhY2Nlc3MgdG8gb3BlbiBkYXRhIGZyb20gcGxhdGZvcm1zIGxpa2UgU29jcmF0YSwgdGhlIHNvZnR3YXJlIGJlaGluZCB0aGUgQ2hpY2FnbyBEYXRhIFBvcnRhbC4gCgpUaGlzIHJlcG9ydCBzcGVjaWZpY2FsbHkgZmlsdGVyIHRoZSBhcm1lZCByb2JiZXJ5IGNyaW1lIGluY2lkZW50cyBpbiBDaGljYWdvLiBUaGUgYXJtZWQgcm9iYmVyeSBpbmNsdWRlOiBLTklGRSAvIENVVFRJTkcgSU5TVFJVTUVOVCwgSEFOREdVTiwgT1RIRVIgRklSRUFSTSwgb3IgT1RIRVIgREFOR0VST1VTIFdFQVBPTi4gQWZ0ZXIgZmlsdGVyaW5nIHRoZSBjcmltZSBkYXRhIGZvciBhcm1lZCByb2JiZXJ5IGluY2lkZW50cywgd2UgZm9jdXNlZCBvbiB0aG9zZSBpbnZvbHZpbmcgdGhlIGZvbGxvd2luZyB3ZWFwb24gdHlwZXM6IGtuaWZlL2N1dHRpbmcgaW5zdHJ1bWVudCwgaGFuZGd1biwgb3RoZXIgZmlyZWFybXMsIG9yIG90aGVyIGRhbmdlcm91cyB3ZWFwb25zLiBUaGVzZSBzcGVjaWZpYyBjYXRlZ29yaWVzIHByb3ZpZGUgYSBjbGVhcmVyIHVuZGVyc3RhbmRpbmcgb2YgbW9yZSBzZXJpb3VzIGFuZCB2aW9sZW50IHJvYmJlcnkgb2ZmZW5zZXMsIGFzIHRoZXkgcG9zZSBhIGhpZ2hlciB0aHJlYXQgdG8gdmljdGltcyBhbmQgYXJlIG1vcmUgbGlrZWx5IHRvIG9jY3VyIGluIGFyZWFzIHdpdGggZGlzdGluY3QgcmlzayBmYWN0b3JzLiBCeSBuYXJyb3dpbmcgb3VyIGFuYWx5c2lzIHRvIGFybWVkIHJvYmJlcmllcywgd2UgYWltIHRvIGV4cGxvcmUgdGhlIHNwYXRpYWwgZGlzdHJpYnV0aW9uIG9mIHRoZXNlIGluY2lkZW50cyBhbmQgYXNzZXNzIGhvdyBlbnZpcm9ubWVudGFsIGFuZCBzb2NpYWwgdmFyaWFibGVzLCBzdWNoIGFzIGFiYW5kb25lZCBidWlsZGluZ3MsIHN0cmVldCBsaWdodCBvdXRhZ2VzLCBhbmQgc2FuaXRhdGlvbiBjb21wbGFpbnRzLCBjb3JyZWxhdGUgd2l0aCBoaWdoZXIgb2NjdXJyZW5jZXMgb2YgdGhlc2UgdmlvbGVudCBjcmltZXMuIFRoaXMgdGFyZ2V0ZWQgYXBwcm9hY2ggZW5hYmxlcyB1cyB0byBkZXZlbG9wIGEgbW9yZSBwcmVjaXNlIHByZWRpY3RpdmUgbW9kZWwgZm9yIHJvYmJlcnkgcmlzayBpbiBkaWZmZXJlbnQgYXJlYXMgb2YgQ2hpY2Fnby4KCmBgYHtyIGxvYWQgb3V0Y29tZSB2YXJpYWJsZSwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRSwgcmVzdWx0cz0naGlkZSd9CnJvYmJlcnkyMSA8LSByZWFkLnNvY3JhdGEoImh0dHBzOi8vZGF0YS5jaXR5b2ZjaGljYWdvLm9yZy9QdWJsaWMtU2FmZXR5L0NyaW1lcy0yMDIxL2R3bWUtdDk2YyIpCnNhdmVSRFMocm9iYmVyeTIxLCAicm9iYmVyeTIxLnJkcyIpCiMgUmVhZCBmcm9tIHRoZSBsb2NhbGx5IHNhdmVkIGZpbGUKcm9iYmVyeTIxIDwtIHJlYWRSRFMoInJvYmJlcnkyMS5yZHMiKSAlPiUgCiAgZmlsdGVyKFByaW1hcnkuVHlwZSA9PSAiUk9CQkVSWSIsIGdyZXBsKCJBUk1FRCIsIERlc2NyaXB0aW9uKSkgJT4lCiAgbXV0YXRlKHggPSBnc3ViKCJbKCldIiwgIiIsIExvY2F0aW9uKSkgJT4lCiAgc2VwYXJhdGUoeCxpbnRvPSBjKCJZIiwiWCIpLCBzZXA9IiwiKSAlPiUKICBtdXRhdGUoWCA9IGFzLm51bWVyaWMoWCksIFkgPSBhcy5udW1lcmljKFkpKSAlPiUgCiAgbmEub21pdCgpICU+JQogIHN0X2FzX3NmKGNvb3JkcyA9IGMoIlgiLCAiWSIpLCBjcnMgPSA0MzI2LCBhZ3IgPSAiY29uc3RhbnQiKSAlPiUKICBzdF90cmFuc2Zvcm0oJ0VTUkk6MTAyMjcxJykgJT4lCiAgZGlzdGluY3QoKQoKYGBgCgpUaGUgbWFwIGRpc3BsYXlzIHRoZSBzcGF0aWFsIGRpc3RyaWJ1dGlvbiBvZiBhcm1lZCByb2JiZXJ5IGluY2lkZW50cyBpbiBDaGljYWdvIGZvciAyMDIxLCByZXZlYWxpbmcgYSBjbGVhciBwYXR0ZXJuIG9mIGNvbmNlbnRyYXRpb24gaW4gc3BlY2lmaWMgYXJlYXMuIFRoZSBoaWdoZXN0IGRlbnNpdHkgb2YgaW5jaWRlbnRzIG9jY3VycyBpbiB0aGUgY2VudHJhbCBhbmQgc291dGhlcm4gcGFydHMgb2YgdGhlIGNpdHksIHdpdGggcGFydGljdWxhciBjbHVzdGVyaW5nIGFyb3VuZCB0aGUgY2l0eSdzIHNvdXRoIHNpZGUgYW5kIGNlcnRhaW4gd2VzdGVybiBuZWlnaGJvcmhvb2RzLiBUaGVzZSBhcmVhcyBsaWtlbHkgZXhwZXJpZW5jZSBoaWdoZXIgcmF0ZXMgb2YgdmlvbGVudCBjcmltZSBkdWUgdG8gYSBjb21iaW5hdGlvbiBvZiBzb2Npby1lY29ub21pYyBmYWN0b3JzIGFuZCBlbnZpcm9ubWVudGFsIGNvbmRpdGlvbnMuIEluIGNvbnRyYXN0LCB0aGUgbm9ydGhlcm4gcGFydHMgb2YgdGhlIGNpdHksIGVzcGVjaWFsbHkgbmVhciB0aGUgbGFrZWZyb250LCBzaG93IHNpZ25pZmljYW50bHkgZmV3ZXIgYXJtZWQgcm9iYmVyaWVzLiBUaGlzIHNwYXRpYWwgcGF0dGVybiBzdWdnZXN0cyB0aGF0IGNlcnRhaW4gcmVnaW9ucyBtYXkgZmFjZSBtb3JlIHByb25vdW5jZWQgY3JpbWUgcmlza3MsIHBvc3NpYmx5IGluZmx1ZW5jZWQgYnkgdXJiYW4gaW5mcmFzdHJ1Y3R1cmUsIGVjb25vbWljIGRpc3RyZXNzLCBvciBvdGhlciBmYWN0b3JzIHRoYXQgY29udHJpYnV0ZSB0byB0aGUgbGlrZWxpaG9vZCBvZiByb2JiZXJ5IGluY2lkZW50cy4gVW5kZXJzdGFuZGluZyB0aGVzZSBjbHVzdGVycyBjYW4gaGVscCBpbmZvcm0gdGFyZ2V0ZWQgaW50ZXJ2ZW50aW9ucyBhbmQgY3JpbWUgcHJldmVudGlvbiBzdHJhdGVnaWVzIGluIGhpZ2gtcmlzayBhcmVhcy4KCgpgYGB7ciByb2JiZXJ5IGxvY2F0aW9ucywgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRSwgZmlnLmFsaWduID0gJ2NlbnRlcid9CgpnZ3Bsb3QoKSArIAogIGdlb21fc2YoZGF0YSA9IGNoaWNhZ29Cb3VuZGFyeSwgZmlsbCA9ICJncmV5IikgKwogIGdlb21fc2YoZGF0YSA9IHJvYmJlcnkyMSwgY29sb3VyPSJkYXJrcmVkIiwgc2l6ZT0wLjEsIHNob3cubGVnZW5kID0gInBvaW50IikgKwogIGxhYnModGl0bGU9ICJBcm1lZCBSb2JiZXJ5IEluY2lkZW50cyBpbiBDaGljYWdvIDIwMjEiKSArIAogIHRoZW1lKGF4aXMudGV4dC54PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRleHQueT1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aWNrcyA9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpdGxlLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gOSxmYWNlID0gIml0YWxpYyIpLAogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSwKICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfcmVjdChjb2xvdXIgPSAiZ3JleSIsIGZpbGw9TkEsIGxpbmV3aWR0aCA9MC44KQogICAgICAgICkKCmBgYAoKQmVsb3cgaXMgdGhlIGRlbnNpdHkgbWFwIG9mIGFybWVkIHJvYmJlcnkgaW5jaWRlbnRzIGluIENoaWNhZ28uIFRoZSBkZW5zaXR5IG1hcCBvZiBhcm1lZCByb2JiZXJ5IGluY2lkZW50cyBpbiBDaGljYWdvIHJldmVhbHMgdHdvIHByb21pbmVudCBob3RzcG90cy4gVGhlIGZpcnN0IGNsdXN0ZXIgaXMgbG9jYXRlZCBpbiB0aGUgY2VudHJhbC13ZXN0ZXJuIHBhcnQgb2YgdGhlIGNpdHksIHdoaWxlIHRoZSBzZWNvbmQgc2lnbmlmaWNhbnQgY29uY2VudHJhdGlvbiBpcyBmb3VuZCBzbGlnaHRseSB0byB0aGUgc291dGggb2YgZG93bnRvd24uIFRoZXNlIGFyZWFzIGV4cGVyaWVuY2UgdGhlIGhpZ2hlc3QgaW50ZW5zaXR5IG9mIGFybWVkIHJvYmJlcmllcywgaW5kaWNhdGluZyBsb2NhbGl6ZWQgem9uZXMgd2l0aCBpbmNyZWFzZWQgcmlzay4gVGhlIHN1cnJvdW5kaW5nIGFyZWFzIHNob3cgYSBncmFkdWFsIGRlY3JlYXNlIGluIGRlbnNpdHkgYXMgd2UgbW92ZSBhd2F5IGZyb20gdGhlc2UgY29yZSBob3RzcG90cywgc3VnZ2VzdGluZyB0aGF0IGFybWVkIHJvYmJlcmllcyBhcmUgbm90IGV2ZW5seSBkaXN0cmlidXRlZCBhY3Jvc3MgdGhlIGNpdHkgYnV0IGFyZSBpbnN0ZWFkIGNvbmNlbnRyYXRlZCBpbiBzcGVjaWZpYyByZWdpb25zLiBUaGUgbm9ydGhlcm4gYW5kIGZhciBzb3V0aGVybiBwYXJ0cyBvZiB0aGUgY2l0eSBleGhpYml0IG11Y2ggbG93ZXIgZGVuc2l0aWVzLCBpbmRpY2F0aW5nIHRoYXQgYXJtZWQgcm9iYmVyaWVzIGFyZSByZWxhdGl2ZWx5IGxlc3MgY29tbW9uIGluIHRoZXNlIGFyZWFzLiBUaGlzIHBhdHRlcm4gaGlnaGxpZ2h0cyB0aGUgbmVlZCBmb3IgZm9jdXNlZCBjcmltZSBwcmV2ZW50aW9uIGVmZm9ydHMgaW4gdGhlIGlkZW50aWZpZWQgaGlnaC1yaXNrIHpvbmVzLgoKYGBge3Igcm9iYmVyeSBkZW5zaXR5LCBmaWcud2lkdGg9NSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgZmlnLmFsaWduID0gJ2NlbnRlcid9Cm9wdGlvbnMoc2NpcGVuPTApCmdncGxvdCgpICsgCiAgZ2VvbV9zZihkYXRhID0gY2hpY2Fnb0JvdW5kYXJ5LCBmaWxsID0gImdyZXkiKSArCiAgc3RhdF9kZW5zaXR5MmQoZGF0YSA9IGRhdGEuZnJhbWUoc3RfY29vcmRpbmF0ZXMocm9iYmVyeTIxKSksIAogICAgICAgICAgICAgICAgIGFlcyhYLCBZLCBmaWxsID0gLi5sZXZlbC4uLCBhbHBoYSA9IC4ubGV2ZWwuLiksCiAgICAgICAgICAgICAgICAgc2l6ZSA9IDAuMDEsIGJpbnMgPSA2MCwgZ2VvbSA9ICdwb2x5Z29uJykgKwogIHNjYWxlX2ZpbGxfdmlyaWRpcyhvcHRpb24gPSAidHVyYm8iLCBuYW1lID0gIkRlbnNpdHkiKSArCiAgc2NhbGVfYWxwaGEocmFuZ2UgPSBjKDAuMDAsIDAuMzUpLCBndWlkZSA9ICJub25lIikgKwogIGxhYnModGl0bGUgPSAiRGVuc2l0eSBvZiBBcm1lZCBSb2JiZXJ5IikgKwogIHRoZW1lKGF4aXMudGV4dC54PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRleHQueT1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aWNrcyA9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpdGxlLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gOSxmYWNlID0gIml0YWxpYyIpLAogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSwKICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfcmVjdChjb2xvdXIgPSAiZ3JleSIsIGZpbGw9TkEsIGxpbmV3aWR0aD0wLjgpCiAgICAgICAgKQpgYGAKCgpDcmltZSByaXNrIHRlbmRzIHRvIGZsdWN0dWF0ZSBncmFkdWFsbHkgYWNyb3NzIGdlb2dyYXBoaWMgc3BhY2UgcmF0aGVyIHRoYW4gY29uZm9ybWluZyB0byBwcmVkZWZpbmVkIGFkbWluaXN0cmF0aXZlIGJvdW5kYXJpZXMuIFRvIGVmZmVjdGl2ZWx5IGNhcHR1cmUgdGhpcyBjb250aW51b3VzIHZhcmlhdGlvbiwgcG9pbnQtbGV2ZWwgZGF0YSBjYW4gYmUgYWdncmVnYXRlZCBpbnRvIGEgbmV0d29yayBvZiBldmVubHkgc3BhY2VkIGdyaWQgY2VsbHMuIFRoZSBjb2RlIHByb3ZpZGVkIGJlbG93IGdlbmVyYXRlcyBzdWNoIGEgZmlzaG5ldCBmb3IgQ2hpY2Fnbywgb2ZmZXJpbmcgYSBzdHJ1Y3R1cmVkIHdheSB0byByZXByZXNlbnQgY3JpbWUgcmlzayBzcGF0aWFsbHkuIFRoaXMgZ3JpZC1iYXNlZCBhcHByb2FjaCBpcyBpZGVhbCBmb3IgcHJlcGFyaW5nIHRoZSBkYXRhIGZvciByZWdyZXNzaW9uIGFuYWx5c2lzLCBhbGxvd2luZyBmb3IgYSBzbW9vdGhlciBkZXBpY3Rpb24gb2YgY3JpbWUgdHJlbmRzIGFjcm9zcyB0aGUgdXJiYW4gbGFuZHNjYXBlLgoKCmBgYHtyIGNyZWF0ZSBmaXNobmV0LCBmaWcud2lkdGg9NSwgZmlnLmFsaWduID0gJ2NlbnRlcid9CmZpc2huZXQgPC0gCiAgc3RfbWFrZV9ncmlkKGNoaWNhZ29Cb3VuZGFyeSwKICAgICAgICAgICAgICAgY2VsbHNpemUgPSA1MDAsIAogICAgICAgICAgICAgICBzcXVhcmUgPSBUUlVFKSAlPiUKICAuW2NoaWNhZ29Cb3VuZGFyeV0gJT4lICAgICAgICAgICAgIyBmYXN0IHdheSB0byBzZWxlY3QgaW50ZXJzZWN0aW5nIHBvbHlnb25zCiAgc3Rfc2YoKSAlPiUKICBtdXRhdGUodW5pcXVlSUQgPSAxOm4oKSkKCmdncGxvdCgpICsKICBnZW9tX3NmKGRhdGE9ZmlzaG5ldCwgY29sb3I9ImJsYWNrIiwgZmlsbD0ibGlnaHRibHVlIikgKwogIGxhYnModGl0bGUgPSAiRmlzaG5ldCBvZiBDaGljYWdvIikgKwogIHRoZW1lKGF4aXMudGV4dC54PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRleHQueT1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aWNrcyA9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpdGxlLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gOSxmYWNlID0gIml0YWxpYyIpLAogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSwKICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfcmVjdChjb2xvdXIgPSAiZ3JleSIsIGZpbGw9TkEsIGxpbmV3aWR0aD0wLjgpCiAgICAgICAgKQpgYGAKCk9uY2UgdGhlIGZpc2huZXQgd2FzIGNyZWF0ZWQsIHVzZSBpdCBhcyB0aGUgc3BhdGlhbCBmcmFtZXdvcmsgdG8gdmlzdWFsaXplIHRoZSBkaXN0cmlidXRpb24gb2Ygcm9iYmVyeSBpbmNpZGVudHMuIEZyb20gdGhpcywgaXQgYmVjb21lcyBjbGVhciB0aGF0IHRoZSBoaWdoZXN0IG51bWJlciBvZiByb2JiZXJpZXMgYXJlIGNvbmNlbnRyYXRlZCBpbiBhIGZldyBncmlkIGNlbGxzIGxvY2F0ZWQgaW4gbm9ydGhlYXN0IENoaWNhZ28uIFRoaXMgcGF0dGVybiBoaWdobGlnaHRzIHRoZSBlbWVyZ2VuY2Ugb2YgYSBjbHVzdGVyZWQgc3BhdGlhbCB0cmVuZCBpbiByb2JiZXJ5IG9jY3VycmVuY2VzLgoKYGBge3Igam9pbiByb2JiZXJ5IHRvIGZpc2huZXQsIGZpZy5hbGlnbiA9ICdjZW50ZXInfQoKcm9iYmVyeTIxX25ldCA8LSAKICBkcGx5cjo6c2VsZWN0KHJvYmJlcnkyMSkgJT4lIAogIG11dGF0ZShjb3VudFJvYmJlcnkgPSAxKSAlPiUgCiAgYWdncmVnYXRlKC4sIGZpc2huZXQsIHN1bSkgJT4lCiAgbXV0YXRlKGNvdW50Um9iYmVyeSA9IHJlcGxhY2VfbmEoY291bnRSb2JiZXJ5LCAwKSwKICAgICAgICAgdW5pcXVlSUQgPSAxOm4oKSwKICAgICAgICAgY3ZJRCA9IHNhbXBsZShyb3VuZChucm93KGZpc2huZXQpIC8gMjQpLCAKICAgICAgICAgICAgICAgICAgICAgICBzaXplPW5yb3coZmlzaG5ldCksIHJlcGxhY2UgPSBUUlVFKSkKCmdncGxvdCgpICsKICBnZW9tX3NmKGRhdGEgPSByb2JiZXJ5MjFfbmV0LCBhZXMoZmlsbCA9IGNvdW50Um9iYmVyeSksIGNvbG9yID0gTkEpICsKICBzY2FsZV9maWxsX3ZpcmlkaXMob3B0aW9uID0gInR1cmJvIiwgbmFtZSA9ICJSb2JiZXJ5IENvdW50cyIpICsKICBsYWJzKHRpdGxlID0gIkNvdW50IG9mIFJvYmJlcmllcyBmb3IgdGhlIEZpc2huZXQiKSArCiAgdGhlbWUoYXhpcy50ZXh0Lng9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGV4dC55PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpY2tzID1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGl0bGUueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSA5LGZhY2UgPSAiaXRhbGljIiksCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIsIGZhY2UgPSAiYm9sZCIpLAogICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9yZWN0KGNvbG91ciA9ICJncmV5IiwgZmlsbD1OQSwgbGluZXdpZHRoPTAuOCkKICAgICAgICApCmBgYAoKVGhlIGJhciBjaGFydCBiZWxvdyBpbGx1c3RyYXRlcyB0aGUgZGlzdHJpYnV0aW9uIG9mIHJvYmJlcnkgaW5jaWRlbnRzIGluIENoaWNhZ28gZm9yIDIwMjEsIHJldmVhbGluZyBhIGhpZ2hseSBza2V3ZWQgcGF0dGVybi4gQSBsYXJnZSBudW1iZXIgb2YgZ3JpZCBjZWxscyBoYXZlIGZldyBvciBubyByb2JiZXJ5IGluY2lkZW50cywgd2hpbGUgb25seSBhIHNtYWxsIG51bWJlciBvZiBncmlkcyBleHBlcmllbmNlIHNpZ25pZmljYW50bHkgaGlnaGVyIHJvYmJlcnkgY291bnRzLiBUaGlzIG5vbi1ub3JtYWwgZGlzdHJpYnV0aW9uIHN1Z2dlc3RzIHRoYXQgUG9pc3NvbiByZWdyZXNzaW9uIGlzIGEgc3VpdGFibGUgbWV0aG9kIGZvciBhbmFseXppbmcgdGhpcyBkYXRhLCBhcyBpdCBjYW4gaGFuZGxlIHRoZSBvdmVyZGlzcGVyc2lvbiBhbmQgY291bnQgbmF0dXJlIG9mIHRoZSB2YXJpYWJsZS4KCmBgYHtyIGRpc3RyaWJ1dGlvbiBvZiByb2JiZXJpZXMsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGZpZy5hbGlnbiA9ICdjZW50ZXInfQpnZ3Bsb3Qocm9iYmVyeTIxX25ldCwgYWVzKHggPSBjb3VudFJvYmJlcnkpKSArCiAgZ2VvbV9oaXN0b2dyYW0oZmlsbCA9ICJzdGVlbGJsdWUiLCBjb2xvciA9ICIjMmMzZTUwIiwgYmlucyA9IDMwKSArIAogIHRoZW1lX21pbmltYWwoKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxLCBmYWNlID0gInBsYWluIiwgc2l6ZSA9IDcpLAogICAgICAgIGF4aXMudGV4dC55ID0gZWxlbWVudF90ZXh0KHNpemUgPSA3KSwgCiAgICAgICAgYXhpcy50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gOSksIAogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDE0LCBmYWNlID0gImJvbGQiKSwgCiAgICAgICAgcGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApLCAKICAgICAgICBwYW5lbC5ncmlkLm1ham9yID0gZWxlbWVudF9saW5lKGNvbG9yID0gImdyZXk4MCIpLCAKICAgICAgICBwYW5lbC5ncmlkLm1pbm9yID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfcmVjdChjb2xvdXIgPSAiZ3JleTYwIiwgZmlsbCA9IE5BLCBzaXplID0gMC44KSkgKyAKICBsYWJzKHRpdGxlID0gIkRpc3RyaWJ1dGlvbiBvZiBSb2JiZXJpZXMgaW4gQ2hpY2FnbyAyMDIxIiwKICAgICAgIHN1YnRpdGxlID0gIkFuYWx5c2lzIG9mIHJvYmJlcnkgaW5jaWRlbnQgZGlzdHJpYnV0aW9uIiwKICAgICAgIGNhcHRpb24gPSAiRGF0YTogQ2hpY2FnbyBEYXRhIFBvcnRhbCBDcmltZXMgMjAyMSIpICsKICB4bGFiKCJSb2JiZXJ5IEluY2lkZW50cyIpICsKICB5bGFiKCJDb3VudCIpCgpgYGAKCiMgUmlzayBGYWN0b3JzCgpJbiB0aGlzIHByb2plY3QsIGVpZ2h0IHJpc2sgZmFjdG9ycyBhcmUgaW5jbHVkZWQgaW4gdGhlIG1vZGVsIHRvIHByZWRpY3Qgcm9iYmVyaWVzOiByZXBvcnRzIG9mIGFiYW5kb25lZCBjYXJzLCBzdHJlZXQgbGlnaHRzIG91dCwgZ3JhZmZpdGksIHNhbml0YXRpb24gY29tcGxhaW50cywgYWJhbmRvbmVkIGJ1aWxkaW5ncywgbG9jYXRpb25zIG9mIGxpcXVvciBzdG9yZXMgc2VsbGluZyBhbGNvaG9sIHRvLWdvLCBTaG90U3BvdHRlciBkZXRlY3Rpb25zLCBhbmQgYmFua3MuIE5laWdoYm9yaG9vZHMgd2l0aCBhYmFuZG9uZWQgY2Fycywgc3RyZWV0IGxpZ2h0cyBvdXQsIGdyYWZmaXRpLCBhbmQgc2FuaXRhdGlvbiBpc3N1ZXMgb2Z0ZW4gaW5kaWNhdGUgbmVnbGVjdCwgZWNvbm9taWMgZGlzdHJlc3MsIGFuZCByZWR1Y2VkIGNvbW11bml0eSBvdmVyc2lnaHQsIHdoaWNoIGNhbiBjcmVhdGUgYW4gZW52aXJvbm1lbnQgY29uZHVjaXZlIHRvIGNyaW1pbmFsIGFjdGl2aXRpZXMuIEFiYW5kb25lZCBidWlsZGluZ3MgY2FuIHNlcnZlIGFzIGhpZGVvdXRzIGZvciBjcmltaW5hbHMsIGZ1cnRoZXIgY29udHJpYnV0aW5nIHRvIG5laWdoYm9yaG9vZCBkaXNvcmRlci4gTGlxdW9yIHN0b3JlcyBhcmUgYXNzb2NpYXRlZCB3aXRoIGFsY29ob2wtcmVsYXRlZCB2dWxuZXJhYmlsaXRpZXMsIHdoaWxlIFNob3RTcG90dGVyIGRldGVjdGlvbnMgcG9pbnQgdG8gYXJlYXMgd2l0aCBoaWdoZXIgZ3VuIHZpb2xlbmNlLiBGaW5hbGx5LCBiYW5rcywgZHVlIHRvIHRoZSBhdmFpbGFiaWxpdHkgb2YgY2FzaCwgbWF5IGF0dHJhY3Qgcm9iYmVycyBzZWVraW5nIGZpbmFuY2lhbCBnYWluLiBUaGVzZSBmYWN0b3JzIGNvbGxlY3RpdmVseSBoaWdobGlnaHQgYXJlYXMgdGhhdCBtYXkgYmUgbW9yZSBzdXNjZXB0aWJsZSB0byByb2JiZXJpZXMuCgpgYGB7ciBsb2FkIHByZWRpY3RvciB2YXJpYWJsZXMsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9CmFiYW5kb25DYXJzIDwtIAogIHJlYWQuc29jcmF0YSgiaHR0cHM6Ly9kYXRhLmNpdHlvZmNoaWNhZ28ub3JnL1NlcnZpY2UtUmVxdWVzdHMvMzExLVNlcnZpY2UtUmVxdWVzdHMtQWJhbmRvbmVkLVZlaGljbGVzLzNjOXYtcG52YSIpICU+JQogICAgbXV0YXRlKHllYXIgPSBzdWJzdHIoY3JlYXRpb25fZGF0ZSwxLDQpKSAlPiUgZmlsdGVyKHllYXIgPT0gIjIwMjAiKSAlPiUKICAgIGRwbHlyOjpzZWxlY3QoWSA9IGxhdGl0dWRlLCBYID0gbG9uZ2l0dWRlKSAlPiUKICAgIG5hLm9taXQoKSAlPiUKICAgIHN0X2FzX3NmKGNvb3JkcyA9IGMoIlgiLCAiWSIpLCBjcnMgPSA0MzI2LCBhZ3IgPSAiY29uc3RhbnQiKSAlPiUKICAgIHN0X3RyYW5zZm9ybShzdF9jcnMoZmlzaG5ldCkpICU+JQogICAgbXV0YXRlKExlZ2VuZCA9ICJBYmFuZG9uZWRfQ2FycyIpCiAgCmFiYW5kb25CdWlsZGluZ3MgPC0gCiAgcmVhZC5zb2NyYXRhKCJodHRwczovL2RhdGEuY2l0eW9mY2hpY2Fnby5vcmcvU2VydmljZS1SZXF1ZXN0cy8zMTEtU2VydmljZS1SZXF1ZXN0cy1WYWNhbnQtYW5kLUFiYW5kb25lZC1CdWlsZGluZy83bmlpLTdzcmQiKSAlPiUKICAgIG11dGF0ZSh5ZWFyID0gc3Vic3RyKGRhdGVfc2VydmljZV9yZXF1ZXN0X3dhc19yZWNlaXZlZCwxLDQpKSAlPiUgIGZpbHRlcih5ZWFyID09ICIyMDE4IikgJT4lCiAgICBkcGx5cjo6c2VsZWN0KFkgPSBsYXRpdHVkZSwgWCA9IGxvbmdpdHVkZSkgJT4lCiAgICBuYS5vbWl0KCkgJT4lCiAgICBzdF9hc19zZihjb29yZHMgPSBjKCJYIiwgIlkiKSwgY3JzID0gNDMyNiwgYWdyID0gImNvbnN0YW50IikgJT4lCiAgICBzdF90cmFuc2Zvcm0oc3RfY3JzKGZpc2huZXQpKSAlPiUKICAgIG11dGF0ZShMZWdlbmQgPSAiQWJhbmRvbmVkX0J1aWxkaW5ncyIpCgpncmFmZml0aSA8LSAKICByZWFkLnNvY3JhdGEoImh0dHBzOi8vZGF0YS5jaXR5b2ZjaGljYWdvLm9yZy9TZXJ2aWNlLVJlcXVlc3RzLzMxMS1TZXJ2aWNlLVJlcXVlc3RzLUdyYWZmaXRpLVJlbW92YWwtSGlzdG9yaWNhbC9oZWM1LXk0eDUiKSAlPiUKICAgIG11dGF0ZSh5ZWFyID0gc3Vic3RyKGNyZWF0aW9uX2RhdGUsMSw0KSkgJT4lIGZpbHRlcih5ZWFyID09ICIyMDE4IikgJT4lCiAgICBmaWx0ZXIod2hlcmVfaXNfdGhlX2dyYWZmaXRpX2xvY2F0ZWRfICVpbiUgYygiRnJvbnQiLCAiUmVhciIsICJTaWRlIikpICU+JQogICAgZHBseXI6OnNlbGVjdChZID0gbGF0aXR1ZGUsIFggPSBsb25naXR1ZGUpICU+JQogICAgbmEub21pdCgpICU+JQogICAgc3RfYXNfc2YoY29vcmRzID0gYygiWCIsICJZIiksIGNycyA9IDQzMjYsIGFnciA9ICJjb25zdGFudCIpICU+JQogICAgc3RfdHJhbnNmb3JtKHN0X2NycyhmaXNobmV0KSkgJT4lCiAgICBtdXRhdGUoTGVnZW5kID0gIkdyYWZmaXRpIikKCnN0cmVldExpZ2h0c091dCA8LSAKICByZWFkLnNvY3JhdGEoImh0dHBzOi8vZGF0YS5jaXR5b2ZjaGljYWdvLm9yZy9TZXJ2aWNlLVJlcXVlc3RzLzMxMS1TZXJ2aWNlLVJlcXVlc3RzLVN0cmVldC1MaWdodHMtQWxsLU91dC96dXhpLTd4ZW0iKSAlPiUKICAgIG11dGF0ZSh5ZWFyID0gc3Vic3RyKGNyZWF0aW9uX2RhdGUsMSw0KSkgJT4lIGZpbHRlcih5ZWFyID09ICIyMDE4IikgJT4lCiAgICBkcGx5cjo6c2VsZWN0KFkgPSBsYXRpdHVkZSwgWCA9IGxvbmdpdHVkZSkgJT4lCiAgICBuYS5vbWl0KCkgJT4lCiAgICBzdF9hc19zZihjb29yZHMgPSBjKCJYIiwgIlkiKSwgY3JzID0gNDMyNiwgYWdyID0gImNvbnN0YW50IikgJT4lCiAgICBzdF90cmFuc2Zvcm0oc3RfY3JzKGZpc2huZXQpKSAlPiUKICAgIG11dGF0ZShMZWdlbmQgPSAiU3RyZWV0X0xpZ2h0c19PdXQiKQoKc2FuaXRhdGlvbiA8LQogIHJlYWQuc29jcmF0YSgiaHR0cHM6Ly9kYXRhLmNpdHlvZmNoaWNhZ28ub3JnL1NlcnZpY2UtUmVxdWVzdHMvMzExLVNlcnZpY2UtUmVxdWVzdHMtU2FuaXRhdGlvbi1Db2RlLUNvbXBsYWludHMtSGkvbWU1OS01ZmFjIikgJT4lCiAgICBtdXRhdGUoeWVhciA9IHN1YnN0cihjcmVhdGlvbl9kYXRlLDEsNCkpICU+JSBmaWx0ZXIoeWVhciA9PSAiMjAxOCIpICU+JQogICAgZHBseXI6OnNlbGVjdChZID0gbGF0aXR1ZGUsIFggPSBsb25naXR1ZGUpICU+JQogICAgbmEub21pdCgpICU+JQogICAgc3RfYXNfc2YoY29vcmRzID0gYygiWCIsICJZIiksIGNycyA9IDQzMjYsIGFnciA9ICJjb25zdGFudCIpICU+JQogICAgc3RfdHJhbnNmb3JtKHN0X2NycyhmaXNobmV0KSkgJT4lCiAgICBtdXRhdGUoTGVnZW5kID0gIlNhbml0YXRpb24iKQoKbGlxdW9yUmV0YWlsIDwtIAogIHJlYWQuc29jcmF0YSgiaHR0cHM6Ly9kYXRhLmNpdHlvZmNoaWNhZ28ub3JnL3Jlc291cmNlL25ybWotM2tjZi5qc29uIikgJT4lICAKICAgIGZpbHRlcihidXNpbmVzc19hY3Rpdml0eSA9PSAiUmV0YWlsIFNhbGVzIG9mIFBhY2thZ2VkIExpcXVvciIpICU+JQogICAgZHBseXI6OnNlbGVjdChZID0gbGF0aXR1ZGUsIFggPSBsb25naXR1ZGUpICU+JQogICAgbmEub21pdCgpICU+JQogICAgc3RfYXNfc2YoY29vcmRzID0gYygiWCIsICJZIiksIGNycyA9IDQzMjYsIGFnciA9ICJjb25zdGFudCIpICU+JQogICAgc3RfdHJhbnNmb3JtKHN0X2NycyhmaXNobmV0KSkgJT4lCiAgICBtdXRhdGUoTGVnZW5kID0gIkxpcXVvcl9SZXRhaWwiKQoKCnNob3RTcG90dGVyIDwtIAogIHJlYWQuc29jcmF0YSgiaHR0cHM6Ly9kYXRhLmNpdHlvZmNoaWNhZ28ub3JnL1B1YmxpYy1TYWZldHkvVmlvbGVuY2UtUmVkdWN0aW9uLVNob3RzcG90dGVyLUFsZXJ0cy8zaDdxLTdtZGIiKSAlPiUgIAogICAgZHBseXI6OnNlbGVjdChZID0gbGF0aXR1ZGUsIFggPSBsb25naXR1ZGUpICU+JQogICAgbmEub21pdCgpICU+JQogICAgc3RfYXNfc2YoY29vcmRzID0gYygiWCIsICJZIiksIGNycyA9IDQzMjYsIGFnciA9ICJjb25zdGFudCIpICU+JQogICAgc3RfdHJhbnNmb3JtKHN0X2NycyhmaXNobmV0KSkgJT4lCiAgICBtdXRhdGUoTGVnZW5kID0gIlNob3RfU3BvdHRlciIpCgpCYW5rcyA8LSAKICByZWFkLnNvY3JhdGEoImh0dHBzOi8vZGF0YS5jaXR5b2ZjaGljYWdvLm9yZy9BZG1pbmlzdHJhdGlvbi1GaW5hbmNlL0xlbmRpbmctRXF1aXR5LURlcG9zaXRvcnktTG9jYXRpb25zLTIwMjEvamd1cC16dDdrIikgJT4lICAKICBkcGx5cjo6c2VsZWN0KGxvY2F0aW9uKSAlPiUKICBuYS5vbWl0KCkKQmFua3MgPC0gQmFua3MgJT4lICAgCiAgbXV0YXRlKFggPSBhcy5udW1lcmljKHN1YigiUE9JTlQgXFwoKC0/XFxkK1xcLlxcZCspIFxcZCtcXC5cXGQrXFwpIiwgIlxcMSIsIEJhbmtzJGxvY2F0aW9uKSksCiAgICAgICAgIFkgPSBhcy5udW1lcmljKHN1YigiUE9JTlQgXFwoLT9cXGQrXFwuXFxkKyAoLT9cXGQrXFwuXFxkKylcXCkiLCAiXFwxIiwgQmFua3MkbG9jYXRpb24pKSkgJT4lCiAgZHBseXI6OnNlbGVjdCgtbG9jYXRpb24pICU+JSAKICBmaWx0ZXIoIWlzLm5hKFgpICYgIWlzLm5hKFkpKSAlPiUgCiAgc3RfYXNfc2YoY29vcmRzID0gYygiWCIsICJZIiksIGNycyA9IDQzMjYsIGFnciA9ICJjb25zdGFudCIpICU+JQogICAgc3RfdHJhbnNmb3JtKHN0X2NycyhmaXNobmV0KSkgJT4lCiAgICBtdXRhdGUoTGVnZW5kID0gIkJhbmsiKSAKICAKCmBgYAoKCgpgYGB7ciBqb2luIHByZWRpY3RvciB2YXJpYWJsZXMgdG8gZmlzaG5ldCwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0KCnZhcnNfbmV0IDwtIAogIHJiaW5kKGFiYW5kb25DYXJzLHN0cmVldExpZ2h0c091dCxhYmFuZG9uQnVpbGRpbmdzLAogICAgICAgIGxpcXVvclJldGFpbCwgZ3JhZmZpdGksIHNhbml0YXRpb24sIHNob3RTcG90dGVyLCBCYW5rcykgJT4lCiAgc3Rfam9pbiguLCBmaXNobmV0LCBqb2luPXN0X3dpdGhpbikgJT4lCiAgc3RfZHJvcF9nZW9tZXRyeSgpICU+JQogIGdyb3VwX2J5KHVuaXF1ZUlELCBMZWdlbmQpICU+JQogIHN1bW1hcml6ZShjb3VudCA9IG4oKSkgJT4lCiAgICBmdWxsX2pvaW4oZmlzaG5ldCkgJT4lCiAgICBzcHJlYWQoTGVnZW5kLCBjb3VudCwgZmlsbD0wKSAlPiUKICAgIHN0X3NmKCkgJT4lCiAgICBkcGx5cjo6c2VsZWN0KC1gPE5BPmApICU+JQogICAgbmEub21pdCgpICU+JQogICAgdW5ncm91cCgpCgpgYGAKClRoZSBmaWd1cmUgaWxsdXN0cmF0ZXMgdGhlIGRpc3RpbmN0IHNwYXRpYWwgcGF0dGVybnMgb2YgZWFjaCByaXNrIGZhY3RvciBhY3Jvc3MgQ2hpY2Fnby4gQWJhbmRvbmVkIGJ1aWxkaW5ncyBhcmUgcHJlZG9taW5hbnRseSBjb25jZW50cmF0ZWQgaW4gdGhlIHNvdXRoZXJuIHBhcnQgb2YgdGhlIGNpdHksIHdoaWxlIGJhbmtzIGFuZCBsaXF1b3IgcmV0YWlsIHN0b3JlcyBjbHVzdGVyIGFyb3VuZCB0aGUgY2VudHJhbCBidXNpbmVzcyBkaXN0cmljdCAoQ0JEKS4gQWJhbmRvbmVkIGNhcnMsIHNhbml0YXRpb24gY29tcGxhaW50cywgYW5kIHN0cmVldCBsaWdodHMgb3V0IGNvbXBsYWludHMgYXJlIG1vcmUgZXZlbmx5IHNjYXR0ZXJlZCB0aHJvdWdob3V0IHRoZSBjaXR5LiBHcmFmZml0aSByZW1vdmFsIHJlcXVlc3RzIGFyZSBtYWlubHkgY29uY2VudHJhdGVkIG5lYXIgdGhlIGNpdHkgY2VudGVyLCB3aGVyZWFzIFNob3RTcG90dGVyIGRldGVjdGlvbnMgdGVuZCB0byBvY2N1ciBpbiBhcmVhcyBvdXRzaWRlIHRoZSBjaXR5IGNlbnRlciwgaGlnaGxpZ2h0aW5nIHRoZSBzcGF0aWFsIHZhcmlhYmlsaXR5IGluIGZhY3RvcnMgYXNzb2NpYXRlZCB3aXRoIHBvdGVudGlhbCByb2JiZXJ5IHJpc2tzLgoKYGBge3IgdmlzdWFsaXplIHByZWRpY3RvciB2YXJpYWJsZXMsIGZpZy5oZWlnaHQ9OCwgZmlnLndpZHRoPTExLCBmaWcuYWxpZ24gPSAnY2VudGVyJ30KdmFyc19uZXQubG9uZyA8LSAKICBnYXRoZXIodmFyc19uZXQsIFZhcmlhYmxlLCB2YWx1ZSwgLWdlb21ldHJ5LCAtdW5pcXVlSUQpCgpwbG90MSA8LSBnZ3Bsb3QoKSArCiAgZ2VvbV9zZihkYXRhID0gdmFyc19uZXQubG9uZyAlPiUgZmlsdGVyKFZhcmlhYmxlID09ICJBYmFuZG9uZWRfQnVpbGRpbmdzIiksIGFlcyhmaWxsPXZhbHVlKSwgY29sb3VyPU5BKSArCiAgc2NhbGVfZmlsbF92aXJpZGlzKG9wdGlvbiA9ICJ0dXJibyIpICsgCiAgbGFicyh0aXRsZSA9ICJBYmFuZG9uZWQgQnVpbGRpbmdzIikgKwogIHRoZW1lKGF4aXMudGV4dC54PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRleHQueT1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aWNrcyA9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpdGxlLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gOSxmYWNlID0gIml0YWxpYyIpLAogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwLCBmYWNlID0gImJvbGQiKSwKICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfcmVjdChjb2xvdXIgPSAiZ3JleSIsIGZpbGw9TkEsIHNpemU9MC44KQogICAgICAgICkKCgpwbG90MiA8LSBnZ3Bsb3QoKSArCiAgZ2VvbV9zZihkYXRhID0gdmFyc19uZXQubG9uZyAlPiUgZmlsdGVyKFZhcmlhYmxlID09ICJBYmFuZG9uZWRfQ2FycyIpLCBhZXMoZmlsbD12YWx1ZSksIGNvbG91cj1OQSkgKwogIHNjYWxlX2ZpbGxfdmlyaWRpcyhvcHRpb24gPSAidHVyYm8iKSArIAogIGxhYnModGl0bGUgPSAiQWJhbmRvbmVkIENhcnMiKSArCiAgdGhlbWUoYXhpcy50ZXh0Lng9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGV4dC55PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpY2tzID1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGl0bGUueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSA5LGZhY2UgPSAiaXRhbGljIiksCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTAsIGZhY2UgPSAiYm9sZCIpLAogICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9yZWN0KGNvbG91ciA9ICJncmV5IiwgZmlsbD1OQSwgc2l6ZT0wLjgpCiAgICAgICAgKQoKcGxvdDMgPC0gZ2dwbG90KCkgKwogIGdlb21fc2YoZGF0YSA9IHZhcnNfbmV0LmxvbmcgJT4lIGZpbHRlcihWYXJpYWJsZSA9PSAiQmFuayIpLCBhZXMoZmlsbD12YWx1ZSksIGNvbG91cj1OQSkgKwogIHNjYWxlX2ZpbGxfdmlyaWRpcyhvcHRpb24gPSAidHVyYm8iKSArIAogIGxhYnModGl0bGUgPSAiQmFua3MiKSArCiAgdGhlbWUoYXhpcy50ZXh0Lng9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGV4dC55PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpY2tzID1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGl0bGUueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSA5LGZhY2UgPSAiaXRhbGljIiksCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTAsIGZhY2UgPSAiYm9sZCIpLAogICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9yZWN0KGNvbG91ciA9ICJncmV5IiwgZmlsbD1OQSwgc2l6ZT0wLjgpCiAgICAgICAgKQoKcGxvdDQgPC0gZ2dwbG90KCkgKwogIGdlb21fc2YoZGF0YSA9IHZhcnNfbmV0LmxvbmcgJT4lIGZpbHRlcihWYXJpYWJsZSA9PSAiR3JhZmZpdGkiKSwgYWVzKGZpbGw9dmFsdWUpLCBjb2xvdXI9TkEpICsKICBzY2FsZV9maWxsX3ZpcmlkaXMob3B0aW9uID0gInR1cmJvIikgKyAKICBsYWJzKHRpdGxlID0gIkdyYWZmaXRpIikgKwogIHRoZW1lKGF4aXMudGV4dC54PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRleHQueT1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aWNrcyA9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpdGxlLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gOSxmYWNlID0gIml0YWxpYyIpLAogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwLCBmYWNlID0gImJvbGQiKSwKICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfcmVjdChjb2xvdXIgPSAiZ3JleSIsIGZpbGw9TkEsIHNpemU9MC44KQogICAgICAgICkKCgpwbG90NSA8LSBnZ3Bsb3QoKSArCiAgZ2VvbV9zZihkYXRhID0gdmFyc19uZXQubG9uZyAlPiUgZmlsdGVyKFZhcmlhYmxlID09ICJMaXF1b3JfUmV0YWlsIiksIGFlcyhmaWxsPXZhbHVlKSwgY29sb3VyPU5BKSArCiAgc2NhbGVfZmlsbF92aXJpZGlzKG9wdGlvbiA9ICJ0dXJibyIpICsgCiAgbGFicyh0aXRsZSA9ICJMaXF1b3IgUmV0YWlsIikgKwogIHRoZW1lKGF4aXMudGV4dC54PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRleHQueT1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aWNrcyA9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpdGxlLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gOSxmYWNlID0gIml0YWxpYyIpLAogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwLCBmYWNlID0gImJvbGQiKSwKICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfcmVjdChjb2xvdXIgPSAiZ3JleSIsIGZpbGw9TkEsIHNpemU9MC44KQogICAgICAgICkKCnBsb3Q2IDwtIGdncGxvdCgpICsKICBnZW9tX3NmKGRhdGEgPSB2YXJzX25ldC5sb25nICU+JSBmaWx0ZXIoVmFyaWFibGUgPT0gIlNhbml0YXRpb24iKSwgYWVzKGZpbGw9dmFsdWUpLCBjb2xvdXI9TkEpICsKICBzY2FsZV9maWxsX3ZpcmlkaXMob3B0aW9uID0gInR1cmJvIikgKyAKICBsYWJzKHRpdGxlID0gIlNhbml0YXRpb24iKSArCiAgdGhlbWUoYXhpcy50ZXh0Lng9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGV4dC55PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpY2tzID1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGl0bGUueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSA5LGZhY2UgPSAiaXRhbGljIiksCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTAsIGZhY2UgPSAiYm9sZCIpLAogICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9yZWN0KGNvbG91ciA9ICJncmV5IiwgZmlsbD1OQSwgc2l6ZT0wLjgpCiAgICAgICAgKQoKcGxvdDcgPC0gZ2dwbG90KCkgKwogIGdlb21fc2YoZGF0YSA9IHZhcnNfbmV0LmxvbmcgJT4lIGZpbHRlcihWYXJpYWJsZSA9PSAiU2hvdF9TcG90dGVyIiksIGFlcyhmaWxsPXZhbHVlKSwgY29sb3VyPU5BKSArCiAgc2NhbGVfZmlsbF92aXJpZGlzKG9wdGlvbiA9ICJ0dXJibyIpICsgCiAgbGFicyh0aXRsZSA9ICJTaG90IFNwb3R0ZXIiKSArCiAgdGhlbWUoYXhpcy50ZXh0Lng9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGV4dC55PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpY2tzID1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGl0bGUueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSA5LGZhY2UgPSAiaXRhbGljIiksCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTAsIGZhY2UgPSAiYm9sZCIpLAogICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9yZWN0KGNvbG91ciA9ICJncmV5IiwgZmlsbD1OQSwgc2l6ZT0wLjgpCiAgICAgICAgKQoKCnBsb3Q4IDwtIGdncGxvdCgpICsKICBnZW9tX3NmKGRhdGEgPSB2YXJzX25ldC5sb25nICU+JSBmaWx0ZXIoVmFyaWFibGUgPT0gIlN0cmVldF9MaWdodHNfT3V0IiksIGFlcyhmaWxsPXZhbHVlKSwgY29sb3VyPU5BKSArCiAgc2NhbGVfZmlsbF92aXJpZGlzKG9wdGlvbiA9ICJ0dXJibyIpICsgCiAgbGFicyh0aXRsZSA9ICJTdHJlZXQgTGlnaHQgT3V0IikgKwogIHRoZW1lKGF4aXMudGV4dC54PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRleHQueT1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aWNrcyA9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpdGxlLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gOSxmYWNlID0gIml0YWxpYyIpLAogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwLCBmYWNlID0gImJvbGQiKSwKICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfcmVjdChjb2xvdXIgPSAiZ3JleSIsIGZpbGw9TkEsIHNpemU9MC44KQogICAgICAgICkKCndyYXBfcGxvdHMoCiAgcGxvdDEsIHBsb3QzLCBwbG90MiwgcGxvdDQsCiAgcGxvdDUsIHBsb3Q2LCBwbG90NywgcGxvdDgsCiAgbmNvbCA9IDQgIAopCgoKYGBgCgojIEZlYXR1cmUgRW5naW5lZXJpbmcKCgojIyBOZWFyZXN0IERpc3RhbmNlCgpUaGUgZmlyc3QgZmVhdHVyZSBlbmdpbmVlcmluZyBhcHByb2FjaCB3ZSB0b29rIGlzIHRvIGNhbGN1bGF0ZSBhdmVyYWdlIG5lYXJlc3QgbmVpZ2hib3IgZGlzdGFuY2UgdG8gaHlwb3RoZXNpemUgYSBzbW9vdGhlciBleHBvc3VyZSByZWxhdGlvbnNoaXAgYWNyb3NzIHNwYWNlLiBIZXJlLCB0aGUgbm5fZnVuY3Rpb24gaXMgdXNlZC4gRm9yIGVhY2ggb2Ygb3VyIHJpc2sgZmFjdG9ycywgYXZlcmFnZSBuZWFyZXN0IGRpc3RhbmNlIGlzIGNhbGN1bGF0ZWQgZnJvbSB0aGUgY2VudHJvaWQgb2YgZWFjaCBjZWxsIHRvIGl0cyBuZWFyZXN0IHRocmVlLCBhYmFuZG9uZWQgY2FycywgZm9yIGV4YW1wbGUuIAoKYGBge3IgY2FsY3VsYXRlIG5lYXJlc3QgZGlzdGFuY2UsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9CgpzdF9jIDwtIHN0X2Nvb3JkaW5hdGVzCnN0X2NvaWQgPC0gc3RfY2VudHJvaWQKCnZhcnNfbmV0IDwtCiAgdmFyc19uZXQgJT4lCiAgICBtdXRhdGUoCiAgICAgIEFiYW5kb25lZF9CdWlsZGluZ3Mubm4gPQogICAgICAgIG5uX2Z1bmN0aW9uKHN0X2Moc3RfY29pZCh2YXJzX25ldCkpLCBzdF9jKGFiYW5kb25CdWlsZGluZ3MpLDMpLAogICAgICBBYmFuZG9uZWRfQ2Fycy5ubiA9CiAgICAgICAgbm5fZnVuY3Rpb24oc3RfYyhzdF9jb2lkKHZhcnNfbmV0KSksIHN0X2MoYWJhbmRvbkNhcnMpLDMpLAogICAgICBHcmFmZml0aS5ubiA9CiAgICAgICAgbm5fZnVuY3Rpb24oc3RfYyhzdF9jb2lkKHZhcnNfbmV0KSksIHN0X2MoZ3JhZmZpdGkpLDMpLAogICAgICBMaXF1b3JfUmV0YWlsLm5uID0KICAgICAgICBubl9mdW5jdGlvbihzdF9jKHN0X2NvaWQodmFyc19uZXQpKSwgc3RfYyhsaXF1b3JSZXRhaWwpLDMpLAogICAgICBTdHJlZXRfTGlnaHRzX091dC5ubiA9CiAgICAgICAgbm5fZnVuY3Rpb24oc3RfYyhzdF9jb2lkKHZhcnNfbmV0KSksIHN0X2Moc3RyZWV0TGlnaHRzT3V0KSwzKSwKICAgICAgU2FuaXRhdGlvbi5ubiA9CiAgICAgICAgbm5fZnVuY3Rpb24oc3RfYyhzdF9jb2lkKHZhcnNfbmV0KSksIHN0X2Moc2FuaXRhdGlvbiksMyksCiAgICAgIFNob3RfU3BvdHRlci5ubiA9CiAgICAgICAgbm5fZnVuY3Rpb24oc3RfYyhzdF9jb2lkKHZhcnNfbmV0KSksIHN0X2Moc2hvdFNwb3R0ZXIpLDMpKQoKdmFyc19uZXQgPC0KICB2YXJzX25ldCAlPiUgbXV0YXRlKEJhbmsubm4gPSBubl9mdW5jdGlvbihzdF9jKHN0X2NvaWQodmFyc19uZXQpKSwgc3RfYyhCYW5rcyksMykpCiAKCmBgYAoKVGhlIG5laWdoYm9yIGZlYXR1cmVzIHByb3ZpZGUgYSBjbGVhcmVyIHBpY3R1cmUgb2YgdGhlIHNwYXRpYWwgcmVsYXRpb25zaGlwcyBiZXR3ZWVuIGRpZmZlcmVudCByaXNrIGZhY3RvcnMuVGhlIHNvdXRoZXJuIGFuZCBzb3V0aGVhc3Rlcm4gYXJlYXMgb2YgQ2hpY2FnbyBhcHBlYXIgdG8gYmUgbW9zdCBkaXN0YW50IGZyb20gbWFueSBvZiB0aGVzZSByaXNrcywgcGFydGljdWxhcmx5IGFiYW5kb25lZCBidWlsZGluZ3MsIGFiYW5kb25lZCBjYXJzLCBhbmQgZ3JhZmZpdGkuIEludGVyZXN0aW5nbHksIHRoZSBzb3V0aHdlc3Rlcm4gY29ybmVyIGFsc28gc2hvd3Mgc29tZSBkaXN0YW5jZSBmcm9tIHJpc2sgZmFjdG9ycyBsaWtlIGJhbmtzIGFuZCBsaXF1b3Igc3RvcmVzLiBPbmUgbm90YWJsZSBvYnNlcnZhdGlvbiBpcyB0aGF0IHRoZSBub3J0aGVybiBwYXJ0IG9mIHRoZSBjaXR5IGlzIGZ1cnRoZXN0IGZyb20gU2hvdFNwb3R0ZXIgbG9jYXRpb25zLCB3aGlsZSBzdGlsbCBiZWluZyByZWxhdGl2ZWx5IGNsb3NlIHRvIG90aGVyIHJpc2sgZmFjdG9ycyBzdWNoIGFzIHNhbml0YXRpb24gaXNzdWVzIGFuZCBzdHJlZXQgbGlnaHRzIG91dC4gVGhlIHJlbGF0aXZlIGFic2VuY2Ugb2YgU2hvdFNwb3R0ZXJzIGluIHRoaXMgYXJlYSByYWlzZXMgcXVlc3Rpb25zIGFib3V0IHRoZWlyIHJvbGU6IGFyZSB0aGV5IGFuIGluZGljYXRvciBvZiBoaWdoZXIgY3JpbWUgZHVlIHRvIHRoZWlyIGNvbmNlbnRyYXRpb24sIG9yIGRvZXMgdGhlaXIgcHJlc2VuY2Ugc3VnZ2VzdCBpbmNyZWFzZWQgcG9saWNpbmcsIG1ha2luZyBuZWlnaGJvcmhvb2RzIHNhZmVyPyBUaGVzZSBvYnNlcnZhdGlvbnMgaGlnaGxpZ2h0IHRoZSBuZWVkIHRvIGV4YW1pbmUgd2hldGhlciBTaG90U3BvdHRlcnMgZnVuY3Rpb24gYXMgYSBjcmltZSBkZXRlY3Rpb24gdG9vbCBvciBhIHByb3h5IGZvciBuZWlnaGJvcmhvb2Qgc2FmZXR5LiBUaGVzZSBhcmUgaW1wb3J0YW50IGNvbnNpZGVyYXRpb25zIGZvciB1bmRlcnN0YW5kaW5nIHRoZSBicm9hZGVyIGR5bmFtaWNzIG9mIGNyaW1lIGFuZCBzYWZldHkgaW4gQ2hpY2Fnby4KCmBgYHtyIHZpc3VhbGl6ZSBuZWFyZXN0IGRpc3RhbmNlLCBmaWcuaGVpZ2h0PTgsIGZpZy53aWR0aD0xMSwgZmlnLmFsaWduID0gJ2NlbnRlcid9Cgp2YXJzX25ldC5sb25nLm5uIDwtIAogIGRwbHlyOjpzZWxlY3QodmFyc19uZXQsIGVuZHNfd2l0aCgiLm5uIikpICU+JQogICAgZ2F0aGVyKFZhcmlhYmxlLCB2YWx1ZSwgLWdlb21ldHJ5KQoKCnAxLm5uIDwtIGdncGxvdCgpICsKICBnZW9tX3NmKGRhdGEgPSB2YXJzX25ldC5sb25nLm5uICU+JSBmaWx0ZXIoVmFyaWFibGUgPT0gIkFiYW5kb25lZF9CdWlsZGluZ3Mubm4iKSwgYWVzKGZpbGw9dmFsdWUpLCBjb2xvdXI9TkEpICsKICBzY2FsZV9maWxsX3ZpcmlkaXMob3B0aW9uID0gInR1cmJvIikgKyAKICBsYWJzKHRpdGxlID0gIkFiYW5kb25lZCBCdWlsZGluZ3MgTk4iKSArCiAgdGhlbWUoYXhpcy50ZXh0Lng9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGV4dC55PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpY2tzID1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGl0bGUueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSA5LGZhY2UgPSAiaXRhbGljIiksCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTAsIGZhY2UgPSAiYm9sZCIpLAogICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9yZWN0KGNvbG91ciA9ICJncmV5IiwgZmlsbD1OQSwgc2l6ZT0wLjgpCiAgICAgICAgKQoKCnAyLm5uIDwtIGdncGxvdCgpICsKICBnZW9tX3NmKGRhdGEgPSB2YXJzX25ldC5sb25nLm5uICU+JSBmaWx0ZXIoVmFyaWFibGUgPT0gIkFiYW5kb25lZF9DYXJzLm5uIiksIGFlcyhmaWxsPXZhbHVlKSwgY29sb3VyPU5BKSArCiAgc2NhbGVfZmlsbF92aXJpZGlzKG9wdGlvbiA9ICJ0dXJibyIpICsgCiAgbGFicyh0aXRsZSA9ICJBYmFuZG9uZWQgQ2FycyBOTiIpICsKICB0aGVtZShheGlzLnRleHQueD1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGlja3MgPWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aXRsZS55ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDksZmFjZSA9ICJpdGFsaWMiKSwKICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCwgZmFjZSA9ICJib2xkIiksCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X3JlY3QoY29sb3VyID0gImdyZXkiLCBmaWxsPU5BLCBzaXplPTAuOCkKICAgICAgICApCgpwMy5ubiA8LSBnZ3Bsb3QoKSArCiAgZ2VvbV9zZihkYXRhID0gdmFyc19uZXQubG9uZy5ubiAlPiUgZmlsdGVyKFZhcmlhYmxlID09ICJCYW5rLm5uIiksIGFlcyhmaWxsPXZhbHVlKSwgY29sb3VyPU5BKSArCiAgc2NhbGVfZmlsbF92aXJpZGlzKG9wdGlvbiA9ICJ0dXJibyIpICsgCiAgbGFicyh0aXRsZSA9ICJCYW5rcyBOTiIpICsKICB0aGVtZShheGlzLnRleHQueD1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGlja3MgPWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aXRsZS55ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDksZmFjZSA9ICJpdGFsaWMiKSwKICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCwgZmFjZSA9ICJib2xkIiksCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X3JlY3QoY29sb3VyID0gImdyZXkiLCBmaWxsPU5BLCBzaXplPTAuOCkKICAgICAgICApCgpwNC5ubiA8LSBnZ3Bsb3QoKSArCiAgZ2VvbV9zZihkYXRhID0gdmFyc19uZXQubG9uZy5ubiAlPiUgZmlsdGVyKFZhcmlhYmxlID09ICJHcmFmZml0aS5ubiIpLCBhZXMoZmlsbD12YWx1ZSksIGNvbG91cj1OQSkgKwogIHNjYWxlX2ZpbGxfdmlyaWRpcyhvcHRpb24gPSAidHVyYm8iKSArIAogIGxhYnModGl0bGUgPSAiR3JhZmZpdGkgTk4iKSArCiAgdGhlbWUoYXhpcy50ZXh0Lng9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGV4dC55PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpY2tzID1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGl0bGUueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSA5LGZhY2UgPSAiaXRhbGljIiksCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTAsIGZhY2UgPSAiYm9sZCIpLAogICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9yZWN0KGNvbG91ciA9ICJncmV5IiwgZmlsbD1OQSwgc2l6ZT0wLjgpCiAgICAgICAgKQoKCnA1Lm5uIDwtIGdncGxvdCgpICsKICBnZW9tX3NmKGRhdGEgPSB2YXJzX25ldC5sb25nLm5uICU+JSBmaWx0ZXIoVmFyaWFibGUgPT0gIkxpcXVvcl9SZXRhaWwubm4iKSwgYWVzKGZpbGw9dmFsdWUpLCBjb2xvdXI9TkEpICsKICBzY2FsZV9maWxsX3ZpcmlkaXMob3B0aW9uID0gInR1cmJvIikgKyAKICBsYWJzKHRpdGxlID0gIkxpcXVvciBSZXRhaWwgTk4iKSArCiAgdGhlbWUoYXhpcy50ZXh0Lng9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGV4dC55PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpY2tzID1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGl0bGUueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSA5LGZhY2UgPSAiaXRhbGljIiksCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTAsIGZhY2UgPSAiYm9sZCIpLAogICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9yZWN0KGNvbG91ciA9ICJncmV5IiwgZmlsbD1OQSwgc2l6ZT0wLjgpCiAgICAgICAgKQoKcDYubm4gPC0gZ2dwbG90KCkgKwogIGdlb21fc2YoZGF0YSA9IHZhcnNfbmV0Lmxvbmcubm4gJT4lIGZpbHRlcihWYXJpYWJsZSA9PSAiU2FuaXRhdGlvbi5ubiIpLCBhZXMoZmlsbD12YWx1ZSksIGNvbG91cj1OQSkgKwogIHNjYWxlX2ZpbGxfdmlyaWRpcyhvcHRpb24gPSAidHVyYm8iKSArIAogIGxhYnModGl0bGUgPSAiU2FuaXRhdGlvbiBOTiIpICsKICB0aGVtZShheGlzLnRleHQueD1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGlja3MgPWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aXRsZS55ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDksZmFjZSA9ICJpdGFsaWMiKSwKICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCwgZmFjZSA9ICJib2xkIiksCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X3JlY3QoY29sb3VyID0gImdyZXkiLCBmaWxsPU5BLCBzaXplPTAuOCkKICAgICAgICApCgpwNy5ubiA8LSBnZ3Bsb3QoKSArCiAgZ2VvbV9zZihkYXRhID0gdmFyc19uZXQubG9uZy5ubiAlPiUgZmlsdGVyKFZhcmlhYmxlID09ICJTaG90X1Nwb3R0ZXIubm4iKSwgYWVzKGZpbGw9dmFsdWUpLCBjb2xvdXI9TkEpICsKICBzY2FsZV9maWxsX3ZpcmlkaXMob3B0aW9uID0gInR1cmJvIikgKyAKICBsYWJzKHRpdGxlID0gIlNob3QgU3BvdHRlciBOTiIpICsKICB0aGVtZShheGlzLnRleHQueD1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGlja3MgPWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aXRsZS55ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDksZmFjZSA9ICJpdGFsaWMiKSwKICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCwgZmFjZSA9ICJib2xkIiksCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X3JlY3QoY29sb3VyID0gImdyZXkiLCBmaWxsPU5BLCBzaXplPTAuOCkKICAgICAgICApCgoKcDgubm4gPC0gZ2dwbG90KCkgKwogIGdlb21fc2YoZGF0YSA9IHZhcnNfbmV0Lmxvbmcubm4gJT4lIGZpbHRlcihWYXJpYWJsZSA9PSAiU3RyZWV0X0xpZ2h0c19PdXQubm4iKSwgYWVzKGZpbGw9dmFsdWUpLCBjb2xvdXI9TkEpICsKICBzY2FsZV9maWxsX3ZpcmlkaXMob3B0aW9uID0gInR1cmJvIikgKyAKICBsYWJzKHRpdGxlID0gIlN0cmVldCBMaWdodCBPdXQgTk4iKSArCiAgdGhlbWUoYXhpcy50ZXh0Lng9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGV4dC55PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpY2tzID1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGl0bGUueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSA5LGZhY2UgPSAiaXRhbGljIiksCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTAsIGZhY2UgPSAiYm9sZCIpLAogICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9yZWN0KGNvbG91ciA9ICJncmV5IiwgZmlsbD1OQSwgc2l6ZT0wLjgpCiAgICAgICAgKQoKd3JhcF9wbG90cygKICBwMS5ubiwgcDMubm4sIHAyLm5uLCBwNC5ubiwKICBwNS5ubiwgcDYubm4sIHA3Lm5uLCBwOC5ubiwKICBuY29sID0gNCAgCikKCgpgYGAKCgojIyBMb2NhbCBTcGF0aWFsIEF1dG9jb3JyZWxhdGlvbgoKaWRlbnRpZmllZCByb2JiZXJ5IGhvdHNwb3RzIGluIENoaWNhZ28gdXNpbmcgTG9jYWwgTW9yYW4ncyBJLCB3aGljaCBleGFtaW5lcyB3aGV0aGVyIHJvYmJlcnkgY291bnRzIGF0IGEgbG9jYXRpb24gYXJlIGNsdXN0ZXJlZCByZWxhdGl2ZSB0byBuZWlnaGJvcmluZyBjZWxscy4gQSBzcGF0aWFsIHdlaWdodHMgbWF0cml4IHdhcyBjYWxjdWxhdGVkIHVzaW5nIHF1ZWVuIGNvbnRpZ3VpdHkgdG8gZGVmaW5lIG5laWdoYm9yaW5nIHJlbGF0aW9uc2hpcHMuIFRoZSByZXN1bHRzLCB2aXN1YWxpemVkIHdpdGggTW9yYW4ncyBJIHZhbHVlcywgcC12YWx1ZXMsIGFuZCBzaWduaWZpY2FudCBob3RzcG90cywgc2hvdyBoaWdoIHNwYXRpYWwgYXV0b2NvcnJlbGF0aW9uIGluIG5vcnRoZWFzdGVybiBDaGljYWdvIG5lYXIgdGhlIENCRC4gQXJlYXMgd2l0aCBzaWduaWZpY2FudCBob3RzcG90cyAocC12YWx1ZXMg4omkIDAuMDUpIGFsaWduIHdpdGggcGF0dGVybnMgc2VlbiBpbiB0aGUga2VybmVsIGRlbnNpdHkgbWFwLCBpbmRpY2F0aW5nIGNsdXN0ZXJzIG9mIGhpZ2hlciByb2JiZXJ5IGNvdW50cyB0aGFuIGV4cGVjdGVkIGJ5IGNoYW5jZS4KCgoKCmBgYHtyIGNhbGN1bGF0ZSBsb2NhbCBtb3JhbiBJLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQoKZmluYWxfbmV0IDwtCiAgbGVmdF9qb2luKHJvYmJlcnkyMV9uZXQsIHN0X2Ryb3BfZ2VvbWV0cnkodmFyc19uZXQpLCBieT0idW5pcXVlSUQiKQpmaW5hbF9uZXQubmIgPC0gcG9seTJuYihhc19TcGF0aWFsKGZpbmFsX25ldCksIHF1ZWVuPVRSVUUpCmZpbmFsX25ldC53ZWlnaHRzIDwtIG5iMmxpc3R3KGZpbmFsX25ldC5uYiwgc3R5bGU9IlciLCB6ZXJvLnBvbGljeT1UUlVFKQoKbG9jYWxfbW9yYW5zIDwtIGxvY2FsbW9yYW4oZmluYWxfbmV0JGNvdW50Um9iYmVyeSwgZmluYWxfbmV0LndlaWdodHMsIHplcm8ucG9saWN5PVRSVUUpICU+JSAKICBhcy5kYXRhLmZyYW1lKCkKCmZpbmFsX25ldC5sb2NhbE1vcmFucyA8LSAKICBjYmluZChsb2NhbF9tb3JhbnMsIGFzLmRhdGEuZnJhbWUoZmluYWxfbmV0KSkgJT4lIAogIHN0X3NmKCkgJT4lCiAgZHBseXI6OnNlbGVjdChSb2JiZXJ5X0NvdW50ID0gY291bnRSb2JiZXJ5LCAKICAgICAgICAgICAgICAgIExvY2FsX01vcmFuc19JID0gSWksIAogICAgICAgICAgICAgICAgUF9WYWx1ZSA9IGBQcih6ICE9IEUoSWkpKWApICU+JQogIG11dGF0ZShTaWduaWZpY2FudF9Ib3RzcG90cyA9IGlmZWxzZShQX1ZhbHVlIDw9IDAuMDAxLCAxLCAwKSkgJT4lCiAgZ2F0aGVyKFZhcmlhYmxlLCBWYWx1ZSwgLWdlb21ldHJ5KQpgYGAKCgpgYGB7ciB2aXN1YWxpemUgbW9yYW4gSSwgZmlnLmhlaWdodD04LCBmaWcud2lkdGg9MTEsIGZpZy5hbGlnbiA9ICdjZW50ZXInfQoKbW9yYW4xIDwtIGdncGxvdCgpICsKICBnZW9tX3NmKGRhdGEgPSBmaW5hbF9uZXQubG9jYWxNb3JhbnMgJT4lIGZpbHRlcihWYXJpYWJsZSA9PSAiUm9iYmVyeV9Db3VudCIpLCBhZXMoZmlsbD1WYWx1ZSksIGNvbG91cj1OQSkgKwogIHNjYWxlX2ZpbGxfdmlyaWRpcyhvcHRpb24gPSAidHVyYm8iKSArIAogIGxhYnModGl0bGUgPSAiUm9iYmVyeSBDb3VudCIpICsKICB0aGVtZShheGlzLnRleHQueD1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGlja3MgPWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aXRsZS55ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDksZmFjZSA9ICJpdGFsaWMiKSwKICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCwgZmFjZSA9ICJib2xkIiksCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X3JlY3QoY29sb3VyID0gImdyZXkiLCBmaWxsPU5BLCBzaXplPTAuOCkKICAgICAgICApCgptb3JhbjIgPC0gZ2dwbG90KCkgKwogIGdlb21fc2YoZGF0YSA9IGZpbmFsX25ldC5sb2NhbE1vcmFucyAlPiUgZmlsdGVyKFZhcmlhYmxlID09ICJMb2NhbF9Nb3JhbnNfSSIpLCBhZXMoZmlsbD1WYWx1ZSksIGNvbG91cj1OQSkgKwogIHNjYWxlX2ZpbGxfdmlyaWRpcyhvcHRpb24gPSAidHVyYm8iKSArIAogIGxhYnModGl0bGUgPSAiTG9jYWwgTW9yYW5zIEkiKSArCiAgdGhlbWUoYXhpcy50ZXh0Lng9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGV4dC55PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpY2tzID1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGl0bGUueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSA5LGZhY2UgPSAiaXRhbGljIiksCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTAsIGZhY2UgPSAiYm9sZCIpLAogICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9yZWN0KGNvbG91ciA9ICJncmV5IiwgZmlsbD1OQSwgc2l6ZT0wLjgpCiAgICAgICAgKQoKbW9yYW4zIDwtIGdncGxvdCgpICsKICBnZW9tX3NmKGRhdGEgPSBmaW5hbF9uZXQubG9jYWxNb3JhbnMgJT4lIGZpbHRlcihWYXJpYWJsZSA9PSAiUF9WYWx1ZSIpLCBhZXMoZmlsbD1WYWx1ZSksIGNvbG91cj1OQSkgKwogIHNjYWxlX2ZpbGxfdmlyaWRpcyhvcHRpb24gPSAidHVyYm8iKSArIAogIGxhYnModGl0bGUgPSAiUCBWYWx1ZSIpICsKICB0aGVtZShheGlzLnRleHQueD1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGlja3MgPWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aXRsZS55ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDksZmFjZSA9ICJpdGFsaWMiKSwKICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCwgZmFjZSA9ICJib2xkIiksCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X3JlY3QoY29sb3VyID0gImdyZXkiLCBmaWxsPU5BLCBzaXplPTAuOCkKICAgICAgICApCgoKbW9yYW40IDwtIGdncGxvdCgpICsKICBnZW9tX3NmKGRhdGEgPSBmaW5hbF9uZXQubG9jYWxNb3JhbnMgJT4lIGZpbHRlcihWYXJpYWJsZSA9PSAiU2lnbmlmaWNhbnRfSG90c3BvdHMiKSwgYWVzKGZpbGw9VmFsdWUpLCBjb2xvdXI9TkEpICsKICBzY2FsZV9maWxsX3ZpcmlkaXMob3B0aW9uID0gInR1cmJvIikgKyAKICBsYWJzKHRpdGxlID0gIlNpZ25pZ2ljYW50IEhvdHNwb3RzIikgKwogIHRoZW1lKGF4aXMudGV4dC54PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRleHQueT1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aWNrcyA9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpdGxlLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gOSxmYWNlID0gIml0YWxpYyIpLAogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwLCBmYWNlID0gImJvbGQiKSwKICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfcmVjdChjb2xvdXIgPSAiZ3JleSIsIGZpbGw9TkEsIHNpemU9MC44KQogICAgICAgICkKCm1vcmFuMSArIG1vcmFuMiArIG1vcmFuMyArIG1vcmFuNApgYGAKCiMjIFNpbmdpZmljYW50IFJvYmJlcnkgQ2x1c3RlcnMKClRoZSBtYXAgaWxsdXN0cmF0ZXMgdGhlIGRpc3RhbmNlIHRvIHRoZSBuZWFyZXN0IHJvYmJlcnkgaG90c3BvdHMgYWNyb3NzIENoaWNhZ28sIHdpdGggZGFya2VyIGNvbG9ycyBpbmRpY2F0aW5nIHNob3J0ZXIgZGlzdGFuY2VzLiBUaGUgY2VudHJhbCBhbmQgc291dGhlcm4gcGFydHMgb2YgdGhlIGNpdHkgZXhoaWJpdCB0aGUgc2hvcnRlc3QgZGlzdGFuY2VzIHRvIHJvYmJlcnkgaG90c3BvdHMsIGFzIHNob3duIGJ5IHRoZSBkZWVwIGJsdWUgYW5kIGJsYWNrIGFyZWFzLiBUaGlzIHN1Z2dlc3RzIHRoYXQgdGhlc2UgcmVnaW9ucyBhcmUgbG9jYXRlZCBjbG9zZXIgdG8gYXJlYXMgd2l0aCBhIGhpZ2ggY29uY2VudHJhdGlvbiBvZiByb2JiZXJ5IGluY2lkZW50cy4gSW4gY29udHJhc3QsIHRoZSBvdXRlciBub3J0aGVybiBhbmQgc291dGhlcm4gcGFydHMgb2YgdGhlIGNpdHksIHJlcHJlc2VudGVkIGJ5IHllbGxvdywgZ3JlZW4sIGFuZCByZWQgaHVlcywgYXJlIGZhcnRoZXIgYXdheSBmcm9tIHRoZXNlIGhvdHNwb3RzLCBpbmRpY2F0aW5nIGxvd2VyIHByb3hpbWl0eSB0byBjb25jZW50cmF0ZWQgcm9iYmVyeSBhY3Rpdml0aWVzLiBUaGlzIHBhdHRlcm4gaGlnaGxpZ2h0cyB0aGUgc3BhdGlhbCBkaXN0cmlidXRpb24gb2Ygcm9iYmVyeSByaXNrcyBpbiBDaGljYWdvLCB3aXRoIG1vcmUgY29uY2VudHJhdGVkIHJvYmJlcnkgYWN0aXZpdHkgb2NjdXJyaW5nIGluIHNwZWNpZmljIHJlZ2lvbnMsIHBhcnRpY3VsYXJseSBpbiB0aGUgY2VudHJhbCBhbmQgc291dGhlcm4gYXJlYXMuIFRoZSBkaXN0YW5jZSBncmFkaWVudCBwcm92aWRlcyBpbnNpZ2h0IGludG8gaG93IHJvYmJlcnkgcmlza3MgZGVjcmVhc2UgYXMgb25lIG1vdmVzIGF3YXkgZnJvbSB0aGVzZSBjb3JlIGhvdHNwb3RzLgoKYGBge3IgY2FsY3VsYXRlIHNpZ25pZmljYW50IHJvYmJlcnkgcG9pbnRzLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQoKZmluYWxfbmV0IDwtCiAgZmluYWxfbmV0ICU+JSAKICBtdXRhdGUocm9iYmVyeS5pc1NpZyA9IGlmZWxzZShsb2NhbG1vcmFuKGZpbmFsX25ldCRjb3VudFJvYmJlcnksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZpbmFsX25ldC53ZWlnaHRzKVssNV0gPD0gMC4wMDAwMDAxLCAxLCAwKSkgJT4lCiAgbXV0YXRlKHJvYmJlcnkuaXNTaWcuZGlzdCA9IG5uX2Z1bmN0aW9uKHN0X2Nvb3JkaW5hdGVzKHN0X2NlbnRyb2lkKGZpbmFsX25ldCkpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdF9jb29yZGluYXRlcyhzdF9jZW50cm9pZCgKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmaWx0ZXIoZmluYWxfbmV0LCByb2JiZXJ5LmlzU2lnID09IDEpKSksIDEpKQpgYGAKCgpgYGB7ciB2aXN1YWxpemUgcm9iYmVyeSBOTiBEaXN0YW5jZSwgZmlnLmFsaWduID0gJ2NlbnRlcid9CgpnZ3Bsb3QoKSArCiAgICAgIGdlb21fc2YoZGF0YSA9IGZpbmFsX25ldCwgYWVzKGZpbGw9cm9iYmVyeS5pc1NpZy5kaXN0KSwgY29sb3VyPU5BKSArCiAgICAgIHNjYWxlX2ZpbGxfdmlyaWRpcyhvcHRpb24gPSAidHVyYm8iLCBuYW1lPSJOTiBEaXN0YW5jZSIpICsKICAgICAgbGFicyh0aXRsZT0iUm9iYmVyeSBOTiBEaXN0YW5jZSIpICsKICAgICAgdGhlbWUoYXhpcy50ZXh0Lng9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGV4dC55PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpY2tzID1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGl0bGUueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSA5LGZhY2UgPSAiaXRhbGljIiksCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTAsIGZhY2UgPSAiYm9sZCIpLAogICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9yZWN0KGNvbG91ciA9ICJncmV5IiwgZmlsbD1OQSwgc2l6ZT0wLjgpCiAgICAgICAgKQoKCmBgYAoKIyMgQWxsIFJpc2sgRmFjdG9ycwoKVGhlIGZpZ3VyZSBzaG93cyBzY2F0dGVyIHBsb3RzIG9mIHRoZSBjb3VudCBvZiBwcmVkaWN0b3IgdmFyaWFibGVzIGFuZCB0aGVpciBuZWFyZXN0IG5laWdoYm9yIGRpc3RhbmNlcyBpbiByZWxhdGlvbiB0byByb2JiZXJ5IGluY2lkZW50cy4gQWxsIGNvdW50LWJhc2VkIGZlYXR1cmVzLCBzdWNoIGFzIGFiYW5kb25lZCBidWlsZGluZ3MsIGNhcnMsIGFuZCBzYW5pdGF0aW9uIGNvbXBsYWludHMsIGFyZSBwb3NpdGl2ZWx5IGNvcnJlbGF0ZWQgd2l0aCB0aGUgbnVtYmVyIG9mIHJvYmJlcmllcywgaW5kaWNhdGluZyB0aGF0IGFyZWFzIHdpdGggaGlnaGVyIG9jY3VycmVuY2VzIG9mIHRoZXNlIHJpc2sgZmFjdG9ycyB0ZW5kIHRvIGV4cGVyaWVuY2UgbW9yZSByb2JiZXJpZXMuIFRoZSBzdHJvbmdlc3QgcG9zaXRpdmUgcmVsYXRpb25zaGlwcyBhcmUgb2JzZXJ2ZWQgYmV0d2VlbiByb2JiZXJ5IGluY2lkZW50cyBhbmQgc2FuaXRhdGlvbiBjb21wbGFpbnRzLCBTaG90U3BvdHRlciBkZXRlY3Rpb25zLCBhbmQgbGlxdW9yIHJldGFpbCBsb2NhdGlvbnMuCgpDb252ZXJzZWx5LCBhbGwgbmVhcmVzdCBuZWlnaGJvciBkaXN0YW5jZSBmZWF0dXJlcyBhcmUgbmVnYXRpdmVseSBjb3JyZWxhdGVkIHdpdGggcm9iYmVyeSBpbmNpZGVudHMsIG1lYW5pbmcgdGhhdCBhcyB0aGUgZGlzdGFuY2UgdG8gcmlzayBmYWN0b3JzIGluY3JlYXNlcywgdGhlIG51bWJlciBvZiByb2JiZXJpZXMgZGVjcmVhc2VzLiBUaGUgc3Ryb25nZXN0IG5lZ2F0aXZlIGNvcnJlbGF0aW9ucyBhcmUgc2VlbiB3aXRoIGRpc3RhbmNlIHRvIHNpZ25pZmljYW50IHJvYmJlcnkgaG90c3BvdHMsIGFiYW5kb25lZCBidWlsZGluZ3MsIGFuZCBsaXF1b3IgcmV0YWlsIHN0b3Jlcy4gVGhpcyBzdWdnZXN0cyB0aGF0IHByb3hpbWl0eSB0byB0aGVzZSByaXNrIGZhY3RvcnMgcGxheXMgYSBzaWduaWZpY2FudCByb2xlIGluIHJvYmJlcnkgcGF0dGVybnMuCgpXaGVuIGl0IGNvbWVzIHRvIG1vZGVsIGJ1aWxkaW5nLCB0aGlzIGluZm9ybWF0aW9uIGlzIGNyaXRpY2FsIGZvciBmZWF0dXJlIHNlbGVjdGlvbi4gVG8gYXZvaWQgbXVsdGljb2xsaW5lYXJpdHksIGVpdGhlciB0aGUgY291bnQgb3IgdGhlIG5lYXJlc3QgbmVpZ2hib3IgZGlzdGFuY2Ugc2hvdWxkIGJlIHNlbGVjdGVkIGZvciBlYWNoIHJpc2sgZmFjdG9yLiBJbiB0aGlzIGNhc2UsIHRoZSBtb2RlbCBpbmNvcnBvcmF0ZXMgbmVhcmVzdCBuZWlnaGJvciBkaXN0YW5jZXMsIGFzIHRoZXkgdGVuZCB0byBzaG93IHN0cm9uZ2VyIGFuZCBtb3JlIGNvbnNpc3RlbnQgY29ycmVsYXRpb25zIHdpdGggcm9iYmVyeSBpbmNpZGVudHMuCgoKYGBge3IgY29ycmVsYXRpb24sIGZpZy5oZWlnaHQ9MTUsIGZpZy53aWR0aD04LCBmaWcuYWxpZ24gPSAnY2VudGVyJ30KCmZpbmFsX25ldF9sb25nIDwtIGZpbmFsX25ldCU+JSAKICBzdF9kcm9wX2dlb21ldHJ5KCkgJT4lIAogIGRwbHlyOjpzZWxlY3QoLWModW5pcXVlSUQsIGN2SUQpKSAlPiUgCiAgcGl2b3RfbG9uZ2VyKGNvbHMgPSAtY291bnRSb2JiZXJ5LCAjIGV2ZXJ5dGhpbmcgZXhjZXB0IG1lYXN1cmVtZW50CiAgICAgICAgICAgICAgIG5hbWVzX3RvID0gIlR5cGUiLCAjIGNhdGVnb3JpemVzIGFsbCBxdWFudGl0YXRpdmUgdmFyaWFibGVzIGludG8gVHlwZQogICAgICAgICAgICAgICB2YWx1ZXNfdG8gPSAiTnVtYmVyIikgCgpjb3JyZWxhdGlvbi5jb3IgPC0KICBmaW5hbF9uZXRfbG9uZyAlPiUKICAgIGdyb3VwX2J5KFR5cGUpICU+JQogICAgc3VtbWFyaXplKGNvcnJlbGF0aW9uID0gY29yKE51bWJlciwgY291bnRSb2JiZXJ5LCB1c2UgPSAiY29tcGxldGUub2JzIikpCgpmaW5hbF9uZXRfbG9uZyAlPiUKICBnZ3Bsb3QoYWVzKHg9IE51bWJlciwgeSA9IGNvdW50Um9iYmVyeSkpICsKICBnZW9tX3BvaW50KHNpemUgPSAwLjAxLCBjb2xvciA9ICIjMDAwMDA0IikgKyAgCiAgZ2VvbV90ZXh0KGRhdGEgPSBjb3JyZWxhdGlvbi5jb3IsIGFlcyhsYWJlbCA9IHBhc3RlKCJyID0iLCByb3VuZChjb3JyZWxhdGlvbiwgMikpKSwKICAgICAgICAgICAgeD0tSW5mLCB5PUluZiwgdmp1c3QgPSAxLjUsIGhqdXN0ID0gLS4xLCBzaXplPTMpICsKICBnZW9tX3Ntb290aChtZXRob2Q9J2xtJywgZm9ybXVsYT0geX54LCBsd2Q9MC41LCBzZSA9IEZBTFNFLCBjb2xvciA9ICIjQkIzNzU0IikgKwogIGZhY2V0X3dyYXAofiBUeXBlLCBzY2FsZXMgPSAiZnJlZSIsIG5jb2wgPSAyLCBsYWJlbGxlcj0gbGFiZWxsZXIoVHlwZSA9IGMoCiAgICBgQWJhbmRvbmVkX0J1aWxkaW5nc2AgPSAiQWJhbmRvbmVkIEJ1aWxkaW5ncyIsCiAgICBgQWJhbmRvbmVkX0J1aWxkaW5ncy5ubmAgPSAiRGlzdGFuY2UgdG8gQWJhbmRvbmVkIEJ1aWxkaW5ncyIsCiAgICBgQWJhbmRvbmVkX0NhcnNgID0gIkFiYW5kb25lZCBDYXJzIiwKICAgIGBBYmFuZG9uZWRfQ2Fycy5ubmAgPSAiRGlzdGFuY2UgdG8gQWJhbmRvbmVkIENhcnMiLAogICAgYEJhbmtgID0gIkJhbmsiLAogICAgYEJhbmsubm5gID0gIkRpc3RhbmNlIHRvIEJhbmsiLAogICAgYEdyYWZmaXRpYCA9ICJHcmFmZml0aSIsCiAgICBgR3JhZmZpdGkubm5gID0gIkRpc3RhbmNlIHRvIEdyYWZmaXRpIiwKICAgIGBMaXF1b3JfUmV0YWlsYCA9ICJMaXF1b3IgUmV0YWlsIiwKICAgIGBMaXF1b3JfUmV0YWlsLm5uYCA9ICJEaXN0YW5jZSB0byBMaXF1b3IgUmV0YWlsIiwKICAgIGByb2JiZXJ5LmlzU2lnYCA9ICJTaWduaWZpY2FudCBSb2JiZXJ5IiwKICAgIGByb2JiZXJ5LmlzU2lnLmRpc3RgID0gIkRpc3RhbmNlIHRvIFNpZ25pZmljYW50IFJvYmJlcnkiLAogICAgYFNhbml0YXRpb25gID0gIlNhbml0YXRpb24iLAogICAgYFNhbml0YXRpb24ubm5gID0gIkRpc3RhbmNlIHRvIFNhbml0YXRpb24iLAogICAgYFNob3RfU3BvdHRlcmAgPSAiU2hvdCBTcG90dGVyIiwgCiAgICBgU2hvdF9TcG90dGVyLm5uYCA9ICJEaXN0YW5jZSB0byBTaG90IFNwb3R0ZXIiLAogICAgYFN0cmVldF9MaWdodHNfT3V0YCA9ICJTdHJlZXQgTGlnaHQgT3V0IiwKICAgIGBTdHJlZXRfTGlnaHRzX091dC5ubmAgPSAiRGlzdGFuY2UgdG8gQnJva2VuIFN0cmVldCBMaWdodCIpKSkgICsKICBsYWJzKHRpdGxlID0gIlNjYXR0ZXIgUGxvdHMgb2YgUHJlZGljdG9yIFZhcmlhYmxlcyIsCiAgICAgICBjYXB0aW9uID0gIkRhdGE6IENoaWNhZ28gRGF0YSBQb3J0YWwiKSArCiAgdGhlbWUocGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gOSxmYWNlID0gIml0YWxpYyIpLAogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSwgCiAgICAgICAgYXhpcy50ZXh0Lng9ZWxlbWVudF90ZXh0KHNpemU9NiksCiAgICAgICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF90ZXh0KHNpemU9NiksIAogICAgICAgIGF4aXMudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9OCksIAogICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9yZWN0KGNvbG91ciA9ICJncmV5IiwgZmlsbD1OQSwgc2l6ZT0wLjgpKQoKYGBgCgoKIyBQb2lzc29uIFJlZ3Jlc3Npb24KCiMjIFJhbmRvbSBLLUZvbGQgQ1YKClRoZSB0YWJsZSBiZWxvdyBwcmVzZW50cyBnb29kbmVzcyBvZiBmaXQgbWV0cmljcyBmb3IgZm91ciByZWdyZXNzaW9uIG1vZGVscy4gVGhlIGZpcnN0IHNldCBvZiBtb2RlbHMgaW5jbHVkZXMgb25seSByaXNrIGZhY3RvcnMgJ3JlZy52YXJzJywgd2hpbGUgdGhlIHNlY29uZCBzZXQgJ3JlZy5zcy52YXJzJyBpbmNvcnBvcmF0ZXMgYm90aCByaXNrIGZhY3RvcnMgYW5kIHRoZSBMb2NhbCBNb3JhbidzIEkgc3BhdGlhbCBwcm9jZXNzLiBUbyBldmFsdWF0ZSBtb2RlbCBwZXJmb3JtYW5jZSwgdHdvIHR5cGVzIG9mIGNyb3NzLXZhbGlkYXRpb24gd2VyZSBjb25kdWN0ZWQuIFRoZSBmaXJzdCBtZXRob2QgYXNzaWducyBhIHJhbmRvbWx5IGdlbmVyYXRlZCBjdklEIHRvIGVhY2ggZ3JpZCBjZWxsLCBhbGxvd2luZyBmb3IgcmFuZG9tIGstZm9sZCBjcm9zcy12YWxpZGF0aW9uIHRvIGFzc2VzcyBwcmVkaWN0aW9uIGFjY3VyYWN5LgoKYGBge3IgcmFuZG9tIGstZm9sZCByZWdyZXNzaW9uLCB3YXJuaW5nPUZBTFNFLCByZXN1bHRzPSdoaWRlJ30KCnJlZy52YXJzIDwtIGMoIkFiYW5kb25lZF9CdWlsZGluZ3Mubm4iLCAiQWJhbmRvbmVkX0NhcnMubm4iLCAiR3JhZmZpdGkubm4iLCAKICAgICAgICAgICAgICAiTGlxdW9yX1JldGFpbC5ubiIsICJTdHJlZXRfTGlnaHRzX091dC5ubiIsICJTYW5pdGF0aW9uLm5uIiwgCiAgICAgICAgICAgICAgIlNob3RfU3BvdHRlci5ubiIsICJCYW5rLm5uIikKCnJlZy5zcy52YXJzIDwtIGMoIkFiYW5kb25lZF9CdWlsZGluZ3Mubm4iLCAiQWJhbmRvbmVkX0NhcnMubm4iLCAiR3JhZmZpdGkubm4iLCAKICAgICAgICAgICAgICAgICAiTGlxdW9yX1JldGFpbC5ubiIsICJTdHJlZXRfTGlnaHRzX091dC5ubiIsICJTYW5pdGF0aW9uLm5uIiwgCiAgICAgICAgICAgICAgICAgIlNob3RfU3BvdHRlci5ubiIsICJCYW5rLm5uIiwgInJvYmJlcnkuaXNTaWciLCAicm9iYmVyeS5pc1NpZy5kaXN0IikKCnJlZy5jdiA8LSBjcm9zc1ZhbGlkYXRlKAogIGRhdGFzZXQgPSBmaW5hbF9uZXQsCiAgaWQgPSAiY3ZJRCIsCiAgZGVwZW5kZW50VmFyaWFibGUgPSAiY291bnRSb2JiZXJ5IiwKICBpbmRWYXJpYWJsZXMgPSByZWcudmFycykgJT4lCiAgICBkcGx5cjo6c2VsZWN0KGN2SUQgPSBjdklELCBjb3VudFJvYmJlcnksIFByZWRpY3Rpb24sIGdlb21ldHJ5KQoKcmVnLnNzLmN2IDwtIGNyb3NzVmFsaWRhdGUoCiAgZGF0YXNldCA9IGZpbmFsX25ldCwKICBpZCA9ICJjdklEIiwKICBkZXBlbmRlbnRWYXJpYWJsZSA9ICJjb3VudFJvYmJlcnkiLAogIGluZFZhcmlhYmxlcyA9IHJlZy5zcy52YXJzKSAlPiUKICAgIGRwbHlyOjpzZWxlY3QoY3ZJRCA9IGN2SUQsIGNvdW50Um9iYmVyeSwgUHJlZGljdGlvbiwgZ2VvbWV0cnkpCgpgYGAKCiMjIFNwYXRpYWwgTE9HTyBDVgoKVGhlIHNlY29uZCBvbmUgaG9sZCBvdXQgb25lIG5laWdoYm9yaG9vZCwgdHJhaW4gdGhlIG1vZGVsIG9uIHRoZSByZW1haW5pbmcgbi0xIGFyZWFzLCBwcmVkaWN0IGZvciB0aGUgaG9sZCBvdXQsIGFuZCByZWNvcmQgdGhlIGdvb2RuZXNzIG9mIGZpdC4gVGhpcyBpcyBhbHNvIGNhbGxlZCB0aGUgc3BhdGlhbCAnTGVhdmUtb25lLWdyb3VwLW91dCcgY3Jvc3MtdmFsaWRhdGlvbiAoTE9HTy1DViksIGluIHdoaWNoIGVhY2ggbmVpZ2hib3Job29kIHRha2VzIGEgdHVybiBhcyBhIGhvbGQtb3V0LiBUaGUgcmVzdWx0IG9mIGVhY2ggYW5hbHlzaXMgaXMgYSBzZiBsYXllciB3aXRoIG9ic2VydmVkIGFuZCBwcmVkaWN0ZWQgYnVyZ2xhcnkgY291bnRzLgoKYGBge3Igc3BhdGlhbCBsb2dvIGN2IHJlZ3Jlc3Npb24sIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0UsIHJlc3VsdHM9J2hpZGUnfQoKbmVpZ2hib3Job29kcyA8LSAKICBzdF9yZWFkKCJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vYmxhY2ttYWQvbmVpZ2hib3Job29kcy9tYXN0ZXIvY2hpY2Fnby5nZW9qc29uIikgJT4lCiAgc3RfdHJhbnNmb3JtKHN0X2NycyhmaXNobmV0KSkgCgpmaW5hbF9uZXQgPC0KICBzdF9jZW50cm9pZChmaW5hbF9uZXQpICU+JQogICAgc3Rfam9pbihkcGx5cjo6c2VsZWN0KG5laWdoYm9yaG9vZHMsIG5hbWUpKSAlPiUKICAgICAgc3RfZHJvcF9nZW9tZXRyeSgpICU+JQogICAgICBsZWZ0X2pvaW4oZHBseXI6OnNlbGVjdChmaW5hbF9uZXQsIGdlb21ldHJ5LCB1bmlxdWVJRCkpICU+JQogICAgICBzdF9zZigpICU+JQogIG5hLm9taXQoKQoKcmVnLnNwYXRpYWxDViA8LSBjcm9zc1ZhbGlkYXRlKAogIGRhdGFzZXQgPSBmaW5hbF9uZXQsCiAgaWQgPSAibmFtZSIsCiAgZGVwZW5kZW50VmFyaWFibGUgPSAiY291bnRSb2JiZXJ5IiwKICBpbmRWYXJpYWJsZXMgPSByZWcudmFycykgJT4lCiAgICBkcGx5cjo6c2VsZWN0KGN2SUQgPSBuYW1lLCBjb3VudFJvYmJlcnksIFByZWRpY3Rpb24sIGdlb21ldHJ5KQoKcmVnLnNzLnNwYXRpYWxDViA8LSBjcm9zc1ZhbGlkYXRlKAogIGRhdGFzZXQgPSBmaW5hbF9uZXQsCiAgaWQgPSAibmFtZSIsCiAgZGVwZW5kZW50VmFyaWFibGUgPSAiY291bnRSb2JiZXJ5IiwKICBpbmRWYXJpYWJsZXMgPSByZWcuc3MudmFycykgJT4lCiAgICBkcGx5cjo6c2VsZWN0KGN2SUQgPSBuYW1lLCBjb3VudFJvYmJlcnksIFByZWRpY3Rpb24sIGdlb21ldHJ5KQpgYGAKCiMjIEVycm9yIEFuYWx5c2lzCgpUaGUgY29kZSBibG9jayBiZWxvdyBjcmVhdGVzIGEgbG9uZyBmb3JtIHJlZy5zdW1tYXJ5LCB0aGF0IGJpbmRzIHRvZ2V0aGVyIG9ic2VydmVkL3ByZWRpY3RlZCBjb3VudHMgYW5kIGVycm9ycyBmb3IgZWFjaCBncmlkIGNlbGwgYW5kIGZvciBlYWNoIHJlZ3Jlc3Npb24uIFJlc2lkdWFscyBhcmUgY2FsY3VsYXRlZCBmb3IgZWFjaCByZWdyZXNzaW9uIGJ5IHN1YnRyYWN0aW5nIHRoZSBvcmlnaW5hbCByb2JiZXJ5IGluY2lkZW50cyBmcm9tIHByZWRpY3RlZCBpbmNpZGVudHMuIAoKYGBge3Igc3VtbWFyaXplIHJlZ3Jlc3Npb259CgpyZWcuc3VtbWFyeSA8LSAKICByYmluZCgKICAgIG11dGF0ZShyZWcuY3YsICAgICAgICAgICBFcnJvciA9IFByZWRpY3Rpb24gLSBjb3VudFJvYmJlcnksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgUmVncmVzc2lvbiA9ICJSYW5kb20gay1mb2xkIENWOiBKdXN0IFJpc2sgRmFjdG9ycyIpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgbXV0YXRlKHJlZy5zcy5jdiwgICAgICAgIEVycm9yID0gUHJlZGljdGlvbiAtIGNvdW50Um9iYmVyeSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBSZWdyZXNzaW9uID0gIlJhbmRvbSBrLWZvbGQgQ1Y6IFNwYXRpYWwgUHJvY2VzcyIpLAogICAgCiAgICBtdXRhdGUocmVnLnNwYXRpYWxDViwgICAgRXJyb3IgPSBQcmVkaWN0aW9uIC0gY291bnRSb2JiZXJ5LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIFJlZ3Jlc3Npb24gPSAiU3BhdGlhbCBMT0dPLUNWOiBKdXN0IFJpc2sgRmFjdG9ycyIpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgbXV0YXRlKHJlZy5zcy5zcGF0aWFsQ1YsIEVycm9yID0gUHJlZGljdGlvbiAtIGNvdW50Um9iYmVyeSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBSZWdyZXNzaW9uID0gIlNwYXRpYWwgTE9HTy1DVjogU3BhdGlhbCBQcm9jZXNzIikpICU+JQogICAgc3Rfc2YoKSAKYGBgCgpDb21wYXJpbmcgdGhlIGVycm9yIGFjcm9zcyBkaWZmZXJlbnQgcmVncmVzc2lvbnMsIHdlIHNlZSB0aGF0IGFkZGluZyBzcGF0aWFsIHByb2Nlc3MgZmVhdHVyZXMgaW1wcm92ZSB0aGUgbW9kZWwuIEluIGFkZGl0aW9uLCB0aGUgbW9kZWwgYXBwZWFycyBzbGlnaHRseSBsZXNzIHJvYnVzdCBmb3IgdGhlIHNwYXRpYWwgY3Jvc3MtdmFsaWRhdGlvbiwgcHJvYmFibHkgYmVjYXVzZSBMT0dPLUNWIGlzIHN1Y2ggYSBjb25zZXJ2YXRpdmUgYXNzdW1wdGlvbi4gCgpgYGB7ciBzdW1tYXJpemUgZXJyb3JzIGJ5IGZvbGQgYW5kIHJlZ3Jlc3Npb24sIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CgplcnJvcl9ieV9yZWdfYW5kX2ZvbGQgPC0gCiAgcmVnLnN1bW1hcnkgJT4lCiAgICBncm91cF9ieShSZWdyZXNzaW9uLCBjdklEKSAlPiUgCiAgICBzdW1tYXJpemUoTWVhbl9FcnJvciA9IG1lYW4oUHJlZGljdGlvbiAtIGNvdW50Um9iYmVyeSwgbmEucm0gPSBUKSwKICAgICAgICAgICAgICBNQUUgPSBtZWFuKGFicyhNZWFuX0Vycm9yKSwgbmEucm0gPSBUKSwKICAgICAgICAgICAgICBTRF9NQUUgPSBtZWFuKGFicyhNZWFuX0Vycm9yKSwgbmEucm0gPSBUKSkgJT4lIHVuZ3JvdXAoKQoKc3RfZHJvcF9nZW9tZXRyeShlcnJvcl9ieV9yZWdfYW5kX2ZvbGQpICU+JQogIGdyb3VwX2J5KFJlZ3Jlc3Npb24pICU+JSAKICAgIHN1bW1hcml6ZShNZWFuX01BRSA9IHJvdW5kKG1lYW4oTUFFKSwgMiksCiAgICAgICAgICAgICAgU0RfTUFFID0gcm91bmQoc2QoTUFFKSwgMikpICU+JQogIGthYmxlKGNvbC5uYW1lPWMoIlJlZ3Jlc3Npb24iLCAnTWVhbiBBYnNvbHV0ZSBFcnJvcicsJ1N0YW5kYXJkIERldmlhdGlvbiBNQUUnKSkgJT4lCiAgICBrYWJsZV9zdHlsaW5nKGJvb3RzdHJhcF9vcHRpb25zID0gYygic3RyaXBlZCIsICJob3ZlciIsICJjb25kZW5zZWQiKSkgJT4lCiAgICByb3dfc3BlYygyLCBjb2xvciA9ICJibGFjayIsIGJhY2tncm91bmQgPSAiI0ZDRkZBNCIpICU+JQogICAgcm93X3NwZWMoNCwgY29sb3IgPSAiYmxhY2siLCBiYWNrZ3JvdW5kID0gIiNGQ0ZGQTQiKSAKCgpgYGAKClRoZSBzbWFsbCBtdWx0aXBsZSBtYXBzIGJlbG93IHZpc3VhbGl6ZXMgdGhlIGRpc3RyaWJ1dGlvbiBvZiBlcnJvcnMgYnkgcmVncmVzc2lvbi4gRm9yIHRoZSByYW5kb20gay1mb2xkIGN2IG1ldGhvZCwgdGhlIGVycm9ycyBhcmUgcHJldHR5IGV2ZW5seSBkaXN0cmlidXRlZCBhY3Jvc3Mgc3BhY2UuIEhvd2V2ZXIsIGZvciBzcGF0aWFsIGxvZ28tY3YgbWV0aG9kLCBoaWdoZXIgZXJyb3JzIG9jY3VyIGF0IHNwYXRpYWwgaG90c3BvdHMuIAoKYGBge3IgdmlzdWFsaXplIGVycm9yLGZpZy5oZWlnaHQ9OCwgZmlnLndpZHRoPTExLCBmaWcuYWxpZ24gPSAnY2VudGVyJ30KCmVycm9yX2J5X3JlZ19hbmRfZm9sZCAlPiUgCiAgZ2dwbG90KCkgKwogIGdlb21fc2YoYWVzKGZpbGw9TUFFKSwgY29sb3I9InRyYW5zcGFyZW50IikgKwogIHNjYWxlX2ZpbGxfdmlyaWRpcyhvcHRpb24gPSAidHVyYm8iLCBkaXJlY3Rpb24gPSAtMSkgKyAKICBmYWNldF93cmFwKH5SZWdyZXNzaW9uLCBuY29sID0gNCkgKwogICBsYWJzKHRpdGxlID0gIk1BRSBieSBGb2xkIGFuZCBSZWdyZXNzaW9uIiwKICAgICAgIGNhcHRpb24gPSAiRGF0YTogQ2hpY2FnbyBEYXRhIFBvcnRhbCIpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIiwKICAgICAgICBheGlzLnRleHQueD1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aWNrcy54PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRleHQueT1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aWNrcy55PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSA5LGZhY2UgPSAiaXRhbGljIiksCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIsIGZhY2UgPSAiYm9sZCIpLAogICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9yZWN0KGNvbG91ciA9ICJncmV5IiwgZmlsbD1OQSwgc2l6ZT0wLjgpLAogICAgICAgIHN0cmlwLnRleHQgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDcpKSAKCmBgYAoKIyBQcmVkaWN0aW9uIEFjY3VyYWN5IAoKIyMgR2VuZXJhbGl6YWJpbGl0eSBUZXN0CgpMZXTigJlzIG5vdyB0ZXN0IGZvciBnZW5lcmFsaXphYmlsaXR5IGFjcm9zcyByYWNpYWwtbmVpZ2hib3Job29kIGNvbnRleHQuIFRvIHRlc3QgdGhpcyBwcm9wb3NpdGlvbiwgdGlkeWNlbnN1cyBpcyB1c2VkIHRvIHB1bGwgcmFjZSBkYXRhIGJ5IGNlbnN1cyB0cmFjdC4gUGVyY2VudGFnZSBtaW5vcml0eSBwb3B1bGF0aW9uIGZvciBlYWNoIGNlbnN1cyB0cmFjdCBpcyBjYWxjdWxhdGVkIGJ5IGNvbXBhcmluZyB0aGUgdG90YWwgbnVtYmVyIG9mIExhdGlueCwgQWZyaWNhbiBBbWVyaWNhbiwgYW5kIEFzaWFuIHBvcHVsYXRpb24gdG8gdGhlIHRvdGFsIHBvcHVsYXRpb24uIENlbnN1cyB0cmFjdHMgYXJlIHNwbGl0IGludG8gdHdvIGdyb3VwczogbWFqb3JpdHkgd2hpdGUgYW5kIG1pbm9yaXR5LCBkZXBlbmRpbmcgb24gd2hldGhlciB0aGUgcHJvcG9ydGlvbiBvZiBtaW5vcml0eSBwb3B1bGF0aW9uIGluIHRoYXQgY2Vuc3VzIHRyYWN0IGV4Y2VlZHMgNjAlLiAKCgpgYGB7ciBkb3dubG9hZCByYWNlIGRhdGEsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIHJlc3VsdHM9J2hpZGUnfQoKY2Vuc3VzX2FwaV9rZXkoZGxnSW5wdXQoCiAgIkVudGVyIGEgQ2Vuc3VzIEFQSSBLZXkiLCAjIGFzayBmb3IgYW4gYXBpIGtleQogIFN5cy5nZXRlbnYoIkNFTlNVU19BUElfS0VZIikKKSRyZXMsCm92ZXJ3cml0ZSA9IFRSVUUpCnRyYWN0czIxIDwtIAogIGdldF9hY3MoZ2VvZ3JhcGh5ID0gInRyYWN0IiwgCiAgICAgICAgICB2YXJpYWJsZXMgPSBjKCJCMDIwMDFfMDAxRSIsICMgdG90YWwgcG9wdWxhdGlvbgogICAgICAgICAgICAiQjAyMDAxXzAwMkUiLCAjIHdoaXRlIHBvcHVsYXRpb24KICAgICAgICAgICAgIkIwMjAwMV8wMDNFIiwgIyBibGFjayBwb3B1bGF0aW9uCiAgICAgICAgICAgICJCMDIwMDFfMDA1RSIsICMgYXNpYW4gcG9wdWxhdGlvbgogICAgICAgICAgICAiQjAzMDAyXzAxMkUiKSwgCiAgICAgICAgICB5ZWFyPTIwMjEsIHN0YXRlPTE3LCBjb3VudHk9MDMxLCAKICAgICAgICAgIGdlb21ldHJ5PVRSVUUsIG91dHB1dD0id2lkZSIpICU+JQogIHN0X3RyYW5zZm9ybSgnRVNSSToxMDIyNzEnKSAlPiUgCiAgcmVuYW1lKFRvdGFsUG9wID0gQjAyMDAxXzAwMUUsIAogICAgICAgICBXaGl0ZXMgPSBCMDIwMDFfMDAyRSwKICAgICAgICAgQWZyaWNhbl9BbWVyaWNhbnMgPSBCMDIwMDFfMDAzRSwKICAgICAgICAgQXNpYW5zID0gQjAyMDAxXzAwNUUsCiAgICAgICAgIExhdGlueCA9IEIwMzAwMl8wMTJFKSAlPiUgCiAgbXV0YXRlKHBjdE1pbm9yaXR5ID0gaWZlbHNlKFRvdGFsUG9wID4gMCwgKEFmcmljYW5fQW1lcmljYW5zICsgQXNpYW5zICsgTGF0aW54ICkgLyBUb3RhbFBvcCwgMCksIAogICAgICAgICBtYWpvcml0eSA9IGlmZWxzZShwY3RNaW5vcml0eSA+IDAuNSwgIm1pbm9yaXR5IiwgIm1ham9yaXR5IikpICU+JSAgIC5bbmVpZ2hib3Job29kcyxdCgoKYGBgCgpXZSBtYXkgc2VlIHRoYXQgQ2hpY2FnbyByZW1haW5zIGEgdmVyeSBzZWdyZWdhdGVkIGNpdHkgYW5kIHRoZXJlJ3MgY2xlYXIgcmFjaWFsIGJvdW5kYXJ5IGJldHdlZW4gcGxhY2VzIHdoZXJlIG1vcmUgdGhhbiA2MCUgb2YgdGhlIHBvcHVsYXRpb24gYXJlIG1pbm9yaXR5IGFuZCBwbGFjZXMgdGhhdCdzIG1ham9yaXR5IHdoaXRlLiBJbiBwYXJ0aWN1bGFyLCBub3J0aGVybiBhbmQgbm9ydGhlYXN0ZXJuIChDQkQpIHBhcnQgb2YgQ2hpY2FnbyBhcmUgbWFqb3JpdHkgd2hpdGUgd2hpbGUgdGhlIHJlbWFpbmluZyBwYXJ0cyBhcmUgYWxsIG1hZGUgdXAgb2YgcmFjaWFsIG1pbm9yaXRpZXMuIAoKYGBge3IgdmlzdWFsaXplIHJhY2UgY29udGV4dCwgZmlnLmFsaWduID0gJ2NlbnRlcid9CgpnZ3Bsb3QoKSArIGdlb21fc2YoZGF0YSA9IG5hLm9taXQodHJhY3RzMjEpLCBhZXMoZmlsbCA9IG1ham9yaXR5KSwgY29sb3IgPSBOQSkgKwogICAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gYygiI0JCMzc1NCIsICIjNTYxMDZFIiksIG5hbWU9IlJhY2UgQ29udGV4dCIpICsKICAgIGxhYnModGl0bGUgPSAiUmFjZSBDb250ZXh0ICIsCiAgICAgICAgIGNhcHRpb24gPSAiRGF0YTogQW1lcmljYW4gQ29tbXVuaXR5IFN1cnZleSAyMDIxIikgKwogIHRoZW1lKAogICAgICAgIGF4aXMudGV4dC54PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpY2tzLng9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGV4dC55PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpY2tzLnk9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSwKICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfcmVjdChjb2xvdXIgPSAiZ3JleSIsIGZpbGw9TkEsIHNpemU9MC40KQogICAgICAgICkgCmBgYAoKRXJyb3IgaXMgY2FsY3VsYXRlZCBieSBzdWJ0cmFjdGluZyB0aGUgb2JzZXJ2ZWQgcm9iYmVyeSBjb3VudCBmcm9tIHRoZSBwcmVkaWN0aW9uLiBBIHBvc2l0aXZlIGRpZmZlcmVuY2UgcmVwcmVzZW50cyBhbiBvdmVyLXByZWRpY3Rpb24uIFRoZSBsZWFzdCBpZGVhbCByZXN1bHQgaXMgYSBtb2RlbCB0aGF0IG92ZXItcHJlZGljdHMgcmlzayBpbiBtaW5vcml0eSBhcmVhcywgYW5kIHVuZGVyLXByZWRpY3RzIGluIHdoaXRlIGFyZWFzLiBJZiByZXBvcnRpbmcgc2VsZWN0aW9uIGJpYXMgaXMgYW4gaXNzdWUsIHN1Y2ggYSBtb2RlbCBtYXkgdW5mYWlybHkgYWxsb2NhdGUgcG9saWNlIHJlc291cmNlIGRpc3Byb3BvcnRpb25hdGVseSBpbiBibGFjayBhbmQgYnJvd24gY29tbXVuaXRpZXMuIEluIG91ciBjYXNlLCBvdmVyLXByZWRpY3Rpb24gb2YgbWlub3JpdGllcyBpcyBhbiBpc3N1ZSBmb3IgcmFuZG9tIGstZm9sZCBjdiByZWdyZXNzaW9uLCBidXQgbm90IGZvciBsb2dvLWN2IHJlZ3Jlc3Npb25zLiBUaGUgbGF0dGVyIHR3byBtb2RlbHMgdW5kZXItcHJlZGljdCBtaW5vcml0eSBuZWlnaGJvcmhvb2RzIGFuZCBvdmVyLXByZWRpY3QgbWFqb3JpdHkgd2hpdGUgbmVpZ2hib3Job29kLCBtYWtpbmcgaXQgbG9va3MgbGlrZSB0aGF0IHRoaXMgYWxnb3JpdGhtcyBnZW5lcmFsaXplcyB3ZWxsIHdpdGggcmVzcGVjdCB0byByYWNlLiAKCmBgYHtyIGdlbmVyYWxpemFiaWxpdHkgYnkgcmFjZSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KCmpvaW5yYWNlIDwtIHN0X2NlbnRyb2lkKHJlZy5zdW1tYXJ5KSAlPiUgCiAgc3RfaW50ZXJzZWN0aW9uKHRyYWN0czIxICU+JWRwbHlyOjogc2VsZWN0KHBjdE1pbm9yaXR5KSkgJT4lIAogIHN0X2Ryb3BfZ2VvbWV0cnkoKSAlPiUgCiAgZ3JvdXBfYnkoY3ZJRCkgJT4lIAogIHN1bW1hcml6ZShtZWFuTWlub3IgPSBtZWFuKHBjdE1pbm9yaXR5KSkKCgpyZWcuc3VtbWFyeSA8LSByZWcuc3VtbWFyeSAlPiUgCiAgbGVmdF9qb2luKGpvaW5yYWNlLCBieSA9ICJjdklEIikgCgoKcmVnLnN1bW1hcnkgJT4lIAogIG11dGF0ZShyYWNlQ29udGV4dCA9IGlmZWxzZShtZWFuTWlub3IgPiAuNiwgIk1pbm9yaXR5IiwgIk1ham9yaXR5IFdoaXRlIikpICU+JSAKICBzdF9kcm9wX2dlb21ldHJ5KCkgJT4lIAogIGdyb3VwX2J5KFJlZ3Jlc3Npb24sIHJhY2VDb250ZXh0KSAlPiUKICBzdW1tYXJpemUobWVhbi5FcnJvciA9IG1lYW4oRXJyb3IsIG5hLnJtID0gVCkpICU+JQogIHNwcmVhZChyYWNlQ29udGV4dCwgbWVhbi5FcnJvcikgJT4lCiAga2FibGUoKSAlPiUKICAgIGthYmxlX3N0eWxpbmcoYm9vdHN0cmFwX29wdGlvbnMgPSBjKCJzdHJpcGVkIiwgImhvdmVyIiwgImNvbmRlbnNlZCIpKSAlPiUKICAgIHJvd19zcGVjKDIsIGNvbG9yID0gImJsYWNrIiwgYmFja2dyb3VuZCA9ICIjRkNGRkE0IikgJT4lCiAgICByb3dfc3BlYyg0LCBjb2xvciA9ICJibGFjayIsIGJhY2tncm91bmQgPSAiI0ZDRkZBNCIpIAoKYGBgCgojIyBLZXJuZWwgRGVzbnRpeSBFc3RpbWF0aW9uCgpUaGUgdXRpbGl0eSBvZiB0aGlzIGFsZ29yaXRobSBpcyBqdWRnZWQgcmVsYXRpdmUgdG8gYW4gYWx0ZXJuYXRpdmUgcG9saWNlIGFsbG9jYXRpb24gbWV0aG9kOiBrZXJuZWwgZGVuc2l0eSBlc3RpbWF0aW9uLiBLZXJuZWwgZGVuc2l0eSB3b3JrcyBieSBjZW50ZXJpbmcgYSBzbW9vdGgga2VybmVsLCBvciBjdXJ2ZSwgYXRvcCBlYWNoIGNyaW1lIHBvaW50IHN1Y2ggdGhhdCB0aGUgY3VydmUgaXMgYXQgaXRzIGhpZ2hlc3QgZGlyZWN0bHkgb3ZlciB0aGUgcG9pbnQgYW5kIHRoZSBsb3dlc3QgYXQgdGhlIHJhbmdlIG9mIGEgY2lyY3VsYXIgc2VhcmNoIHJhZGl1cy4gVGhlIGNvZGUgYmxvY2sgYmVsb3cgY3JlYXRlcyBhIEtlcm5lbCBkZW5zaXR5IG1hcCBvZiByb2JiZXJ5IHdpdGggYSAxMDAwIGZvb3Qgc2VhcmNoIHJhZGl1cy4gCiAKYGBge3IgY2FsY3VsYXRlIDIwMjEgS2VybmFsIERlbnNpdHksIGZpZy53aWR0aD02LCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBmaWcuYWxpZ24gPSAnY2VudGVyJ30KCnJvYl9wcHAgPC0gYXMucHBwKHN0X2Nvb3JkaW5hdGVzKHJvYmJlcnkyMSksIFcgPSBzdF9iYm94KGZpbmFsX25ldCkpCnJvYl9LRC4xMDAwIDwtIHNwYXRzdGF0LmV4cGxvcmU6OmRlbnNpdHkucHBwKHJvYl9wcHAsIDEwMDApCnJvYl9LRC5kZiA8LSBkYXRhLmZyYW1lKHJhc3RlclRvUG9pbnRzKG1hc2socmFzdGVyKHJvYl9LRC4xMDAwKSwgYXMobmVpZ2hib3Job29kcywgJ1NwYXRpYWwnKSkpKQoKCmdncGxvdChkYXRhPXJvYl9LRC5kZiwgYWVzKHg9eCwgeT15KSkgKwogIGdlb21fcmFzdGVyKGFlcyhmaWxsPWxheWVyKSkgKyAKICBjb29yZF9zZihjcnM9c3RfY3JzKGZpbmFsX25ldCkpICsgCiAgc2NhbGVfZmlsbF92aXJpZGlzKG9wdGlvbiA9ICJ0dXJibyIsIG5hbWU9IkRlbnNpdHkiKSArCiAgbGFicyh0aXRsZSA9ICJLZXJuZWwgRGVuc2l0eSBvZiBSb2JiZXJ5IDEwMDBmdCBSYWRpaSIpICsKICAgIHRoZW1lKGF4aXMudGV4dC54PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRleHQueT1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aWNrcyA9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpdGxlLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gOSxmYWNlID0gIml0YWxpYyIpLAogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSwKICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfcmVjdChjb2xvdXIgPSAiZ3JleSIsIGZpbGw9TkEsIHNpemU9MC44KQogICAgICAgICkKYGBgCgpOZXh0LCBhIG5ldyBnb29kbmVzcyBvZiBmaXQgaW5kaWNhdG9yIGlzIGNyZWF0ZWQgdG8gaWxsdXN0cmF0ZSB3aGV0aGVyIHRoZSAyMDIxIGtlcm5lbCBkZW5zaXR5IG9yIHJpc2sgcHJlZGljdGlvbnMgY2FwdHVyZSBtb3JlIG9mIHRoZSAyMDIyIHJvYmJlcmllcy4KCmBgYHtyIGRvd25sb2FkIDIwMjIgcm9iZXJ5IGRhdGEsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9Cgpyb2JiZXJ5MjIgPC0gCiAgcmVhZC5zb2NyYXRhKCJodHRwczovL2RhdGEuY2l0eW9mY2hpY2Fnby5vcmcvUHVibGljLVNhZmV0eS9DcmltZXMtMjAyMi85aHdyLTJ6eHAiKSAlPiUgCiAgIGZpbHRlcihQcmltYXJ5LlR5cGUgPT0gIlJPQkJFUlkiKSAlPiUgCiAgbXV0YXRlKHggPSBnc3ViKCJbKCldIiwgIiIsIExvY2F0aW9uKSkgJT4lCiAgc2VwYXJhdGUoeCxpbnRvPSBjKCJZIiwiWCIpLCBzZXA9IiwiKSAlPiUKICBtdXRhdGUoWCA9IGFzLm51bWVyaWMoWCksCiAgICAgICAgIFkgPSBhcy5udW1lcmljKFkpKSAlPiUgCiAgbmEub21pdCAlPiUKICBzdF9hc19zZihjb29yZHMgPSBjKCJYIiwgIlkiKSwgY3JzID0gNDMyNiwgYWdyID0gImNvbnN0YW50IikgJT4lCiAgc3RfdHJhbnNmb3JtKCdFU1JJOjEwMjI3MScpICU+JSAKICBkaXN0aW5jdCgpICU+JQogIC5bZmlzaG5ldCxdCgpgYGAKClRoZSBrZXJuZWwgZGVuc2l0eSBlc3RpbWF0aW9uIHdhcyBjbGFzc2lmaWVkIGludG8gNSByaXNrIGNhdGVnb3JpZXMgYW5kIHRoZSBjb3VudCBmb3IgdGhlIDIwMjIgcm9iYmVyaWVzIHdlcmUgc3VtbWFyaXplZCBpbnRvIHRoZSBzYW1lIHNmIGxheWVyLiAgVGhlIHB1cnBvc2UgaGVyZSBpcyB0byBzZWUgaWYgdGhlIHJpc2sgcHJlZGljdGlvbnMgY2FwdHVyZSBtb3JlIG9ic2VydmVkIGJ1cmdsYXJpZXMgdGhhbiB0aGUga2VybmVsIGRlbnNpdHkuIElmIHNvLCB0aGVuIHRoZSByaXNrIHByZWRpY3Rpb24gbW9kZWwgcHJvdmlkZXMgYSBtb3JlIHJvYnVzdCB0YXJnZXRpbmcgdG9vbCBmb3IgYWxsb2NhdGluZyBwb2xpY2UgcmVzb3VyY2VzLiAKCmBgYHtyIGNhdGVnb3JpemUgS0RFfQoKcm9iX0tERV9zdW0gPC0gYXMuZGF0YS5mcmFtZShyb2JfS0QuMTAwMCkgJT4lCiAgc3RfYXNfc2YoY29vcmRzID0gYygieCIsICJ5IiksIGNycyA9IHN0X2NycyhmaW5hbF9uZXQpKSAlPiUKICBhZ2dyZWdhdGUoLiwgZmluYWxfbmV0LCBtZWFuKSAKCmtkZV9icmVha3MgPC0gY2xhc3NJbnRlcnZhbHMocm9iX0tERV9zdW0kdmFsdWUsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIG4gPSA1LCAiZmlzaGVyIikKCnJvYl9LREVfc2YgPC0gcm9iX0tERV9zdW0gJT4lCiAgbXV0YXRlKGxhYmVsID0gIktlcm5lbCBEZW5zaXR5IiwKICAgICAgICAgUmlza19DYXRlZ29yeSA9IGNsYXNzSW50OjpmaW5kQ29scyhrZGVfYnJlYWtzKSwKICAgICAgICAgUmlza19DYXRlZ29yeSA9IGNhc2Vfd2hlbigKICAgICAgICAgICBSaXNrX0NhdGVnb3J5ID09IDUgfiAiNXRoIiwKICAgICAgICAgICBSaXNrX0NhdGVnb3J5ID09IDQgfiAiNHRoIiwKICAgICAgICAgICBSaXNrX0NhdGVnb3J5ID09IDMgfiAiM3JkIiwKICAgICAgICAgICBSaXNrX0NhdGVnb3J5ID09IDIgfiAiMm5kIiwKICAgICAgICAgICBSaXNrX0NhdGVnb3J5ID09IDEgfiAiMXN0IikpICU+JQogIGNiaW5kKAogICAgYWdncmVnYXRlKAogICAgICBkcGx5cjo6c2VsZWN0KHJvYmJlcnkyMSkgJT4lIG11dGF0ZShyb2JiZXJ5Q291bnQgPSAxKSwgLiwgc3VtKSAlPiUKICAgIG11dGF0ZShyb2JiZXJ5Q291bnQgPSByZXBsYWNlX25hKHJvYmJlcnlDb3VudCwgMCkpKSAlPiUKICBkcGx5cjo6c2VsZWN0KGxhYmVsLCBSaXNrX0NhdGVnb3J5LCByb2JiZXJ5Q291bnQpCgpgYGAKClRoZSBzYW1lIHByb2Nlc3Mgd2FzIHJlcGVhdGVkIGZvciB0aGUgcmlzayBwcmVkaWN0aW9uLiBOb3RlIHRoYXQgYm90aCBwcmVkaWN0aW9ucyBmcm9tIHRoZSBMT0dPLUNWIHdpdGggYW5kIHdpdGhvdXQgdGhlIHNwYXRpYWwgZmVhdHVyZXMgaXMgYmVpbmcgdXNlZCBoZXJlLgoKYGBge3IgY2F0ZWdvcml6ZSByYW5kb20gayBmb2xkfQoKbWxfYnJlYWtzIDwtIGNsYXNzSW50ZXJ2YWxzKHJlZy5zcy5zcGF0aWFsQ1YkUHJlZGljdGlvbiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbiA9IDUsICJmaXNoZXIiKQpyb2Jfcmlza19zZiA8LQogIHJlZy5zcy5zcGF0aWFsQ1YgJT4lCiAgbXV0YXRlKGxhYmVsID0gIlJpc2sgUHJlZGljdGlvbnMgU3BhdGlhbCIsCiAgICAgICAgIFJpc2tfQ2F0ZWdvcnkgPWNsYXNzSW50OjpmaW5kQ29scyhtbF9icmVha3MpLAogICAgICAgICBSaXNrX0NhdGVnb3J5ID0gY2FzZV93aGVuKAogICAgICAgICAgIFJpc2tfQ2F0ZWdvcnkgPT0gNSB+ICI1dGgiLAogICAgICAgICAgIFJpc2tfQ2F0ZWdvcnkgPT0gNCB+ICI0dGgiLAogICAgICAgICAgIFJpc2tfQ2F0ZWdvcnkgPT0gMyB+ICIzcmQiLAogICAgICAgICAgIFJpc2tfQ2F0ZWdvcnkgPT0gMiB+ICIybmQiLAogICAgICAgICAgIFJpc2tfQ2F0ZWdvcnkgPT0gMSB+ICIxc3QiKSkgJT4lCiAgY2JpbmQoCiAgICBhZ2dyZWdhdGUoCiAgICAgIGRwbHlyOjpzZWxlY3Qocm9iYmVyeTIxKSAlPiUgbXV0YXRlKHJvYmJlcnlDb3VudCA9IDEpLCAuLCBzdW0pICU+JQogICAgICBtdXRhdGUocm9iYmVyeUNvdW50ID0gcmVwbGFjZV9uYShyb2JiZXJ5Q291bnQsIDApKSkgJT4lCiAgZHBseXI6OnNlbGVjdChsYWJlbCxSaXNrX0NhdGVnb3J5LCByb2JiZXJ5Q291bnQpCgptbF9icmVha3Nfc2ltcGxlIDwtIGNsYXNzSW50ZXJ2YWxzKHJlZy5zcy5jdiRQcmVkaWN0aW9uLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuID0gNSwgImZpc2hlciIpCnJvYl9yaXNrX3NmX3NpbXBsZSA8LQogIHJlZy5zcy5jdiAlPiUKICBtdXRhdGUobGFiZWwgPSAiUmlzayBQcmVkaWN0aW9ucyBTaW1wbGUiLAogICAgICAgICBSaXNrX0NhdGVnb3J5ID1jbGFzc0ludDo6ZmluZENvbHMobWxfYnJlYWtzX3NpbXBsZSksCiAgICAgICAgIFJpc2tfQ2F0ZWdvcnkgPSBjYXNlX3doZW4oCiAgICAgICAgICAgUmlza19DYXRlZ29yeSA9PSA1IH4gIjV0aCIsCiAgICAgICAgICAgUmlza19DYXRlZ29yeSA9PSA0IH4gIjR0aCIsCiAgICAgICAgICAgUmlza19DYXRlZ29yeSA9PSAzIH4gIjNyZCIsCiAgICAgICAgICAgUmlza19DYXRlZ29yeSA9PSAyIH4gIjJuZCIsCiAgICAgICAgICAgUmlza19DYXRlZ29yeSA9PSAxIH4gIjFzdCIpKSAlPiUKICBjYmluZCgKICAgIGFnZ3JlZ2F0ZSgKICAgICAgZHBseXI6OnNlbGVjdChyb2JiZXJ5MjEpICU+JSBtdXRhdGUocm9iYmVyeUNvdW50ID0gMSksIC4sIHN1bSkgJT4lCiAgICAgIG11dGF0ZShyb2JiZXJ5Q291bnQgPSByZXBsYWNlX25hKHJvYmJlcnlDb3VudCwgMCkpKSAlPiUKICBkcGx5cjo6c2VsZWN0KGxhYmVsLFJpc2tfQ2F0ZWdvcnksIHJvYmJlcnlDb3VudCkKCmBgYAoKIyMgUHJlZGljdGlvbiBDb21wYXJpc29uCgpCZWxvdyBhIG1hcCBpcyBnZW5lcmF0ZWQgb2YgdGhlIHJpc2sgY2F0ZWdvcmllcyBmb3IgYWxsIHRocmVlIG1vZGVsIHR5cGVzLiBBIHN0cm9uZ2x5IGZpdCBtb2RlbCBzaG91bGQgc2hvdyB0aGF0IHRoZSBoaWdoZXN0IHJpc2sgY2F0ZWdvcnkgaXMgdW5pcXVlbHkgdGFyZ2V0ZWQgdG8gcGxhY2VzIHdpdGggYSBoaWdoIGRlbnNpdHkgb2YgYnVyZ2xhcnkgcG9pbnRzLiBUaGUgbWFwIHNob3dzIHRoYXQgb3VyIHJpc2sgcHJlZGljdGlvbiBtb2RlbHMgcGVyZm9ybSBhcyBnb29kIGFzIHRoZSBrZXJuYWwgZGVuc2l0eSBhcHByb2FjaCBpbiBjYXB0dXJpbmcgdGhlIGRpc3RyaWJ1dGlvbiBvZiByb2JiZXJ5IGluY2lkZW50cy4gCgpgYGB7ciBwcmVkaWN0aW9uIGNvbXBhcmlzb25zIG1hcCwgZmlnLmFsaWduID0gJ2NlbnRlcid9CnJiaW5kKHJvYl9LREVfc2YsIHJvYl9yaXNrX3NmLCByb2Jfcmlza19zZl9zaW1wbGUpICU+JQogIG5hLm9taXQoKSAlPiUKICBnYXRoZXIoVmFyaWFibGUsIFZhbHVlLCAtbGFiZWwsIC1SaXNrX0NhdGVnb3J5LCAtZ2VvbWV0cnkpICU+JQogIGdncGxvdCgpICsKICAgIGdlb21fc2YoYWVzKGZpbGwgPSBSaXNrX0NhdGVnb3J5KSwgY29sb3VyID0gTkEpICsKICAgIGZhY2V0X3dyYXAofmxhYmVsLCApICsKICAgIHNjYWxlX2ZpbGxfdmlyaWRpcyhvcHRpb24gPSAidHVyYm8iLCBkaXNjcmV0ZSA9IFRSVUUsIG5hbWUgPSAiUmlzayBDYXRlZ29yeSIpICsKICAgICAgZ2VvbV9zZihkYXRhID0gc2FtcGxlX24ocm9iYmVyeTIyLCAzMDAwKSwgc2l6ZSA9IC4wMywgY29sb3VyID0gIndoaXRlIikgKwogICAgbGFicyh0aXRsZT0iQ29tcGFyaXNvbiBvZiBLZXJuZWwgRGVuc2l0eSBhbmQgUmlzayBQcmVkaWN0aW9ucyIsCiAgICAgICAgIHN1YnRpdGxlPSIyMDIxIHJvYmJlcnkgcmlzayBwcmVkaWN0aW9uczsgMjAyMiBhcm1lZCByb2JiZXJpZXMiKSArIAogIHRoZW1lKGF4aXMudGV4dC54PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRleHQueT1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aWNrcyA9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpdGxlLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gOSxmYWNlID0gIml0YWxpYyIpLAogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSwKICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfcmVjdChjb2xvdXIgPSAiZ3JleSIsIGZpbGw9TkEsIHNpemU9MC44KQogICAgICAgICkKYGBgCgpBbiBlZmZlY3RpdmUgd2F5IHRvIGFzc2VzcyBwcmVkaWN0aW9uIGFjY3VyYWN5IGlzIGJ5IHVzaW5nIGEgaGlzdG9ncmFtLiBJZGVhbGx5LCBhIHdlbGwtZml0dGluZyBtb2RlbCBzaG91bGQgc2hvdyB0aGF0IHRoZSBoaWdoZXN0LXJpc2sgYXJlYXMgY2FwdHVyZSBhIGxhcmdlciBwcm9wb3J0aW9uIG9mIDIwMjIgcm9iYmVyaWVzIGNvbXBhcmVkIHRvIHRoZSBrZXJuZWwgZGVuc2l0eSBlc3RpbWF0ZS4gSG93ZXZlciwgaW4gdGhpcyBjYXNlLCB0aGUga2VybmVsIGRlbnNpdHkgbW9kZWwgb3V0cGVyZm9ybXMgYm90aCByaXNrIHByZWRpY3Rpb24gbW9kZWxzIGluIHRoZSBoaWdoZXN0LXJpc2sgcmVnaW9ucy4gRGVzcGl0ZSB0aGlzLCB0aGUgcmlzayBwcmVkaWN0aW9uIG1vZGVscyBleGNlbCBpbiBpZGVudGlmeWluZyBhcm1lZCByb2JiZXJ5IGluY2lkZW50cyBpbiBsb3dlci1yaXNrIGFyZWFzIHRoYXQgdGhlIGtlcm5lbCBkZW5zaXR5IG1vZGVsIG92ZXJsb29rZWQuCgoKCgoKYGBge3IgcHJlZGljdGlvbiBjb21wYXJpc29ucyBmaWd1cmUsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0UsIGZpZy5hbGlnbiA9ICdjZW50ZXInfQpyYmluZChyb2JfS0RFX3NmLCByb2Jfcmlza19zZiwgcm9iX3Jpc2tfc2Zfc2ltcGxlKSAlPiUKICBzdF9kcm9wX2dlb21ldHJ5KCkgJT4lCiAgbmEub21pdCgpICU+JQogIGdhdGhlcihWYXJpYWJsZSwgVmFsdWUsIC1sYWJlbCwgLVJpc2tfQ2F0ZWdvcnkpICU+JQogIGdyb3VwX2J5KGxhYmVsLCBSaXNrX0NhdGVnb3J5KSAlPiUKICBzdW1tYXJpemUoY291bnRSb2JiZXJ5ID0gc3VtKFZhbHVlKSkgJT4lCiAgdW5ncm91cCgpICU+JQogIGdyb3VwX2J5KGxhYmVsKSAlPiUKICBtdXRhdGUoUGNudF9vZl90ZXN0X3NldF9jcmltZXMgPSBjb3VudFJvYmJlcnkgLyBzdW0oY291bnRSb2JiZXJ5KSkgJT4lCiAgICBnZ3Bsb3QoYWVzKFJpc2tfQ2F0ZWdvcnksUGNudF9vZl90ZXN0X3NldF9jcmltZXMpKSArCiAgICAgIGdlb21fYmFyKGFlcyhmaWxsPWxhYmVsKSwgcG9zaXRpb249ImRvZGdlIiwgc3RhdD0iaWRlbnRpdHkiKSArCiAgICAgIHNjYWxlX2ZpbGxfdmlyaWRpcyhvcHRpb24gPSAidHVyYm8iLCBkaXNjcmV0ZSA9IFRSVUUsIG5hbWUgPSAiTW9kZWwiLCBkaXJlY3Rpb24gPSAtMSkgKwogICAgICBsYWJzKHRpdGxlID0gIlJpc2sgUHJlZGljdGlvbiB2cy4gS2VybmVsIERlbnNpdHkiLAogICAgICAgICAgIHkgPSAiJSBvZiBUZXN0IFNldCBSb2JiZXJpZXMgKHBlciBtb2RlbCkiLAogICAgICAgICAgIHggPSAiUmlzayBDYXRlZ29yeSIpICsKICB0aGVtZV9idygpICsKICAgICAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgdmp1c3QgPSAwLjUpKQpgYGAKCiMgQ29uY2x1c2lvbnMKCkluIGNvbmNsdXNpb24sIG91ciBtb2RlbCByZXF1aXJlcyBzaWduaWZpY2FudCBpbXByb3ZlbWVudCBiZWZvcmUgaXQgY2FuIGJlIGltcGxlbWVudGVkIGVmZmVjdGl2ZWx5LiBDb21wYXJlZCB0byB0cmFkaXRpb25hbCBrZXJuZWwgZGVuc2l0eSBlc3RpbWF0aW9uLCBpdCBzdHJ1Z2dsZWQgdG8gY2FwdHVyZSBhcm1lZCByb2JiZXJ5IGluY2lkZW50cyBpbiB0aGUgaGlnaGVzdC1yaXNrIGFyZWFzLiBPbmUga2V5IGlzc3VlIGlzIHRoYXQgbWFueSBvZiBvdXIgcHJlZGljdG9yIHZhcmlhYmxlcywgc3VjaCBhcyByZXBvcnRzIG9mIGFiYW5kb25lZCBjYXJzLCBncmFmZml0aSwgYW5kIHNhbml0YXRpb24gY29tcGxhaW50cywgd2VyZSBvdXRkYXRlZCwgbGFzdCB1cGRhdGVkIGluIDIwMTkuIFVzaW5nIHRoZXNlIHRvIHByZWRpY3Qgcm9iYmVyaWVzIGluIDIwMjEgYW5kIDIwMjIgbGlrZWx5IGxlZCB0byBpbmFjY3VyYXRlIHJlc3VsdHMsIGVzcGVjaWFsbHkgc2luY2Ugc2hpZnRzIGluIHRoZXNlIHJpc2sgZmFjdG9ycyAobGlrZSBncmFmZml0aSByZW1vdmFsKSB3b3VsZCBhbHRlciBjcmltZSBwYXR0ZXJucy4gVXAtdG8tZGF0ZSBkYXRhc2V0cyBhcmUgY3J1Y2lhbCBmb3IgbW9yZSBhY2N1cmF0ZSBwcmVkaWN0aW9ucy4KCkFkZGl0aW9uYWxseSwgdGhlIENPVklELTE5IHBhbmRlbWljIGxpa2VseSBpbXBhY3RlZCBhcm1lZCByb2JiZXJ5IHBhdHRlcm5zLCB3aXRoIGVjb25vbWljIGRpc3RyZXNzLCBsb2NrZG93bnMsIGFuZCByZWFsbG9jYXRpb24gb2YgcG9saWNlIHJlc291cmNlcyBhbGwgY29udHJpYnV0aW5nIHRvIHNoaWZ0cyBpbiBjcmltZSBsb2NhdGlvbnMuIE91ciBtb2RlbCBuZWVkcyB0byBhY2NvdW50IGZvciBzdWNoIGZhY3RvcnMgdG8gZW5zdXJlIHByZWRpY3Rpb25zIHJlbWFpbiByZWxldmFudCBvdmVyIHRpbWUuCgpPbiBhIHBvc2l0aXZlIG5vdGUsIHRoZSBtb2RlbCBwZXJmb3JtZWQgYmV0dGVyIGluIGxvd2VyLXJpc2sgYXJlYXMsIHdoZXJlIHRoZSBrZXJuZWwgZGVuc2l0eSBtZXRob2QgZmVsbCBzaG9ydC4gSG93ZXZlciwgcmVseWluZyBvbiBhIHNpbmdsZSBtb2RlbCBtYXkgbm90IGNhcHR1cmUgdGhlIGZ1bGwgY29tcGxleGl0eSBvZiBhcm1lZCByb2JiZXJ5IHBhdHRlcm5zLiBBIGNvbWJpbmVkIGFwcHJvYWNoIHVzaW5nIGJvdGgga2VybmVsIGRlbnNpdHkgZXN0aW1hdGlvbiBhbmQgcmlzayBwcmVkaWN0aW9uIGNvdWxkIGltcHJvdmUgYWNjdXJhY3kuIFdoaWxlIHdlIGF2b2lkZWQgb3ZlcnByZWRpY3RpbmcgaW4gbWFqb3JpdHkgbm9uLXdoaXRlIG5laWdoYm9yaG9vZHMsIHBvdGVudGlhbCBzZWxlY3Rpb24gYmlhcyByZW1haW5zIGEgY29uY2Vybi4gSW5jb3Jwb3JhdGluZyBzcGF0aWFsIGZlYXR1cmVzIGFuZCB1c2luZyBrLWZvbGQgY3Jvc3MtdmFsaWRhdGlvbiBoZWxwZWQgaW1wcm92ZSB0aGUgbW9kZWwsIGJ1dCBmdXJ0aGVyIHJlZmluZW1lbnQgaXMgbmVlZGVkIHRvIGVuc3VyZSBpdCBnZW5lcmFsaXplcyB3ZWxsIGFjcm9zcyBkaWZmZXJlbnQgY29udGV4dHMgYW5kIHRpbWUgcGVyaW9kcy4=