1. Introduction

Construction is one of the most polluting industries in Philadelphia. The Office of Sustainability has been actively addressing this concern by collaborating with the Department of Licenses and Inspections. Together, they aim to introduce sustainable practices and reduce the environmental impact of construction in the city.

Neighborhoods in Philadelphia exhibit varying historical and current development trends. Recognizing these differences is crucial for proposing context-based Green Building recommendations for future development. For instance, areas with historical significance may require preservation-focused green guidelines, while rapidly developing neighborhoods may benefit from stricter regulations for green building and incentives for sustainable urban planning.

By understanding the unique characteristics of different neighborhoods, the Office of Sustainability can create targeted and effective recommendations which vary from neighborhood to neighborhood. This approach ensures that the proposed green building standards not only contribute to environmental conservation but also align with the specific needs and developmental nuances of each community. In doing so, this analysis aims to foster a more sustainable and resilient urban environment for the residents of Philadelphia. As the first step, this project aims to utilize historic data for construction to predict the areas where construction is most likely to occur.

The video presentation illustrating the use case for this model can be found here.

# Functions
## 1. Theme Functions

mapTheme <- function(base_size = 12) {
  theme(
    text = element_text( color = "black"),
    plot.title = element_text(size = 15,colour = "black"),
    plot.subtitle=element_text(face="italic"),
    plot.caption=element_text(hjust=0),
    axis.ticks = element_blank(),
    panel.background = element_blank(),axis.title = element_blank(),
    axis.text = element_blank(),
    axis.title.x = element_blank(),
    axis.title.y = element_blank(),
    panel.grid.minor = element_blank(),
    panel.border = element_rect(colour = "black", fill=NA, size=2),
    strip.text.x = element_text(size = 14))
}

plotTheme <- function(base_size = 12) {
  theme(
    text = element_text( color = "black"),
    plot.title = element_text(color = "darkred", size=15, face="bold"),
    plot.subtitle = element_text(face="italic"),
    plot.caption = element_text(hjust=0),
    axis.ticks = element_blank(),
    panel.background = element_blank(),
    panel.grid.major = element_line("grey80", size = 0.1),
    panel.grid.minor = element_blank(),
    panel.border = element_rect(colour = "black", fill=NA, size=2),
    strip.background = element_rect(fill = "grey80", color = "white"),
    strip.text = element_text(size=12),
    axis.title = element_text(size=12),
    axis.text = element_text(size=10),
    plot.background = element_blank(),
    legend.background = element_blank(),
    legend.title = element_text(colour = "black", face = "italic"),
    legend.text = element_text(colour = "black", face = "italic"),
    strip.text.x = element_text(size = 14)
  )
}


## 2. Load Quantile break functions

qBr <- function(df, variable, rnd) {
  if (missing(rnd)) {
    as.character(quantile(round(df[[variable]],0),
                          c(.01,.2,.4,.6,.8), na.rm=T))
  } else if (rnd == FALSE | rnd == F) {
    as.character(formatC(quantile(df[[variable]]), digits = 3),
                 c(.01,.2,.4,.6,.8), na.rm=T)
  }
}

q5 <- function(variable) {as.factor(ntile(variable, 5))}

## 3. Nearest neighbor distance function

nn_function <- function(measureFrom,measureTo,k) {
  measureFrom_Matrix <- as.matrix(measureFrom)
  measureTo_Matrix <- as.matrix(measureTo)
  nn <-   
    get.knnx(measureTo, measureFrom, k)$nn.dist
  output <-
    as.data.frame(nn) %>%
    rownames_to_column(var = "thisPoint") %>%
    gather(points, point_distance, V1:ncol(.)) %>%
    arrange(as.numeric(thisPoint)) %>%
    group_by(thisPoint) %>%
    dplyr::summarize(pointDistance = mean(point_distance)) %>%
    arrange(as.numeric(thisPoint)) %>% 
    dplyr::select(-thisPoint) %>%
    pull() 
  
  return(output)  
}

2. Data Wrangling

2.1 Data Overview - Primary Datasets

The Development Tracker Tool relies on a predictive model that utilizes diverse datasets to estimate the number of residential building permits throughout the city. These datasets encompass information obtained from OpenDataPhilly, which serves as the City of Philadelphia’s comprehensive catalog of available regional data. Additionally, we incorporate data from the U.S. Census Bureau’s 2021 5-year American Community Survey, enabling us to gain insights into population characteristics such as education levels and median income. The subsequent sections elaborate on our methodology for collecting and refining these datasets.

To create this model, data was collected from a variety of sources. Our primary data source was The Department of Licenses & Inspections collection of Building and Zoning Permits. The Department of L&I reviews construction plans and conducts building inspections to ensure the safety of the workers and the public. Zoning permits are issued to authorize new construction or additions to a building or to authorize the change of use in a building or ground. Building permits are required before the start of a specific construction activity to enlarge, repair, change, add to or demolish a structure, and to install equipment or systems in a structure.

The issuance of permits serves as a valuable indicator for predicting development changes in a city. By closely monitoring the types and volume of permits granted, urban planners and policymakers gain insights into the trajectory of construction activities. An increase in building permits for residential, commercial, or infrastructure projects often foreshadows growth and economic development in a particular area. Conversely, a decline in permit issuance may indicate a slowdown or shifting trends in development.

2.2 Data Overview - Spatial Datasets

To begin constructing a predictive model for the construction permits issued in Philadelphia, additional external variables are incorporated. These variables examine the proximity of various amenities and public services to the permits listed in the foundational dataset. The following code imports data related to these potentially influential amenities, including information about green spaces in the city, the locations of vacant lands and buildings, building demolitions and affordable housing. These datasets are collected to check if they have a positive influence on the number of permits issued. Additionally, information on potential permit issuance inhibitors such as sanitation and service complaints recorded through 311 requests are also brought in. These datasets are sourced from Open Data Philly.

2.3 Data Overview - Census Data

After loading the primary dataset and importing data on surrounding physical amenities, the next step is gathering relevant census data to understand demographic conditions pertaining to these data points. This data is fetched and processed from the American Community Survey (ACS) for Philadelphia census tracts in the year 2021. It retrieves various demographic and socioeconomic variables for these tracts and performs some data transformations. The year chosen for analysis is 2021 to factor recovery from Covid in order to make more accurate predictions.

A list of variables including population, income, housing-related information, education, poverty levels, and racial/ethnic demographics are gathered and transformed to be projected in the ESRI:102728 coordinate system. This information is stored in a new dataset - ‘tracts21’.

# Base Data

## 1. All Philadelphia Permits

phillyPermits <- 
  st_read("https://phl.carto.com/api/v2/sql?q=SELECT+*+FROM+permits+WHERE+permitissuedate+>=+'2013-01-01'+AND+permitissuedate+<+'2022-12-31'&filename=permits&format=geojson&skipfields=cartodb_id") %>% 
    st_transform('ESRI:102728')%>%
  mutate(Legend = "Philadelphia Permits")%>%
  mutate(Year = as.integer(format(permitissuedate, "%Y"))) 

## 2. Philadelphia Boundaries

phillyBoundary <- 
  st_read("http://data.phl.opendata.arcgis.com/datasets/405ec3da942d4e20869d4e1449a2be48_0.geojson") %>%
  st_transform('ESRI:102728') 

### 2.1. Fishnet Grid Based on Philadelphia Boundaries

phillyFishnet <- 
  st_make_grid(phillyBoundary,
               cellsize = 500, 
               square = TRUE) %>%
  .[phillyBoundary] %>% 
  st_sf() %>%
  mutate(uniqueID = rownames(.))

## 3. Philadelphia Neighborhoods

phillyNeighborhoods <-
  st_read("https://raw.githubusercontent.com/azavea/geo-data/master/Neighborhoods_Philadelphia/Neighborhoods_Philadelphia.geojson") %>%
  st_transform('ESRI:102728') %>%
  st_transform(st_crs(phillyFishnet)) 

## 4. Vacant Property

vacantLand <-
  st_read('https://opendata.arcgis.com/datasets/19c35fb02d544a9bad0032b58268c9f9_0.geojson') %>% 
  na.omit() %>%
  st_transform('ESRI:102728') %>%
  st_transform(st_crs(phillyFishnet)) %>%
  mutate(Legend = "Vacant Land") 

vacantBuilding <-
  st_read('https://opendata.arcgis.com/datasets/f7ed68293c5e40d58f1de9c8435c3e84_0.geojson') %>% 
  na.omit() %>%
  st_transform('ESRI:102728') %>%
  st_transform(st_crs(phillyFishnet)) %>%
  mutate(Legend = "Vacant Buildings")

## 5. Affordable Housing

affordableHousing <-
  st_read('https://opendata.arcgis.com/datasets/ca8944190b604b2aae7eff17c8dd9ef5_0.geojson') %>% 
  filter(FISCAL_YEAR_COMPLETE <= "2022" && FISCAL_YEAR_COMPLETE >= "2012") %>%
  st_transform('ESRI:102728') %>%
  st_transform(st_crs(phillyFishnet)) %>%
  mutate(Legend = "Affordable Housing")

## 6. Business Licenses

businessLicense <-
  st_read('https://phl.carto.com/api/v2/sql?q=SELECT+*+FROM+business_licenses&filename=business_licenses&format=geojson&skipfields=cartodb_id') %>%   filter(licensestatus == "Active")%>%
  st_transform('ESRI:102728') %>%
  st_transform(st_crs(phillyFishnet)) %>%
  mutate(Legend = "Business Licenses")

## 7. Green Spaces

greenSpace <-
  st_read('https://opendata.arcgis.com/datasets/d52445160ab14380a673e5849203eb64_0.geojson') %>% 
  st_transform('ESRI:102728') %>%
  st_transform(st_crs(phillyFishnet)) %>%
  mutate(Legend = "Green Spaces")

## 8. Sanitation Data

sanitation<-
  st_read("https://phl.carto.com/api/v2/sql?q=SELECT+*+FROM+public_cases_fc+WHERE+service_name+=+'Sanitation+/+Dumpster+Violation'OR+service_name+=+'Illegal+Dumping'+AND+requested_datetime+>=+'2013-01-01'+AND+requested_datetime+<+'2022-12-31'&filename=sanitation&format=geojson&skipfields=cartodb_id") %>%
    mutate(year = substr(requested_datetime,1,4)) %>% 
    dplyr::select(Y = lat, X = lon) %>%
    na.omit() %>%
    st_as_sf(coords = c("X", "Y"), crs = 4326, agr = "constant") %>%
    st_transform(st_crs(phillyFishnet)) %>%
    mutate(Legend = "Sanitation")

## 9. Service Data

service<-
  st_read("https://phl.carto.com/api/v2/sql?q=SELECT+*+FROM+public_cases_fc+WHERE+service_name+=+'Building+Dangerous'+AND+requested_datetime+>=+'2013-01-01'+AND+requested_datetime+<+'2022-12-31'&filename=service&format=geojson&skipfields=cartodb_id") %>%
    mutate(year = substr(requested_datetime,1,4)) %>% 
    dplyr::select(Y = lat, X = lon) %>%
    na.omit() %>%
    st_as_sf(coords = c("X", "Y"), crs = 4326, agr = "constant") %>%
    st_transform(st_crs(phillyFishnet)) %>%
    mutate(Legend = "Service")

## 10. Demolition Data

buildingDemolition <-
  st_read('https://phl.carto.com/api/v2/sql?q=SELECT+*+FROM+demolitions&filename=demolitions&format=geojson&skipfields=cartodb_id') %>% 
  mutate(year = substr(start_date,1,4)) %>%
  filter(year == '2022') %>%
  st_transform('ESRI:102728') %>%
  st_transform(st_crs(phillyFishnet)) %>%
  mutate(Legend = "Building Demolition") 

## 11. Census data - ACS 2021

tracts21 <- get_acs(
  geography = "tract",
  variables = c(
    "B01003_001",   # Total Population
    "B19013_001",   # Median Household Income
    "B25008_001E",  # Total Population in Housing Units
    "B25008_002",   # Owner-Occupied Units
    "B25008_003",   # Renter-Occupied Units
    "B15003_022",   # Educational Attainment: Bachelor's Degree
    "B06012_002E",  # Population Below the Poverty Level
    "B02001_002",   # Race and Ethnicity: White Alone
    "B02001_003",   # Race and Ethnicity: Black or African American Alone
    "B27011_008E",  # Population Unemployed
    "B08105F_007E"  # Population Working from Home
    
  ),
  year = 2021,
  state = "PA",
  county = "Philadelphia",
  geometry = TRUE,
  output = "wide"
)%>%
  dplyr::select(-NAME, -ends_with("M")) %>%
  rename(TotalPop = B01003_001E,                           # Total Population
         MedHHInc = B19013_001E,                           # Median Household Income
         TotalUnit = B25008_001E,                          # Total Population in Housing Units
         OwnerOccupied = B25008_002E,                      # Owner-Occupied Units
         RenterOccupied = B25008_003E,                     # Renter-Occupied Units
         BachelorsDegree = B15003_022E,                    # Educational Attainment: Bachelor's Degree
         TotalPoverty = B06012_002E,                       # Population Below the Poverty Level
         TotalUnemployment = B27011_008E,                  # Population Unemployed
         TotalWFH = B08105F_007E,                          # Population Working from Home
         RaceWhite = B02001_002E,                          # Race and Ethnicity: White Alone
         RaceBlack = B02001_003E,                          # Race and Ethnicity: Black or African American Alone
         )

### 11.1. Transform the data to ESRI:102728 projection

tracts21 <- tracts21 %>% st_transform(st_crs(phillyFishnet))

### 11.2 Create new variables

tracts21 <- tracts21 %>%
  mutate(pctWhite = ifelse(TotalPop > 0, RaceWhite / TotalPop * 100,0),
         pctBlack = ifelse(TotalPop > 0, RaceBlack / TotalPop * 100,0),
         pctPoverty = ifelse(TotalPop > 0, TotalPoverty / TotalPop *100, 0),
         pctUnemploy = ifelse(TotalPop > 0, TotalUnemployment / TotalPop *100, 0),
         pctWFH = ifelse(TotalPop > 0, TotalWFH / TotalPop *100, 0),
         pctBach = ifelse(TotalPop > 0, BachelorsDegree / TotalPop *100, 0),
         pctOwnerOccupied = ifelse(TotalPop > 0, OwnerOccupied / TotalUnit *100, 0),
         pctRenterOccupied = ifelse(TotalPop > 0, RenterOccupied / TotalUnit *100, 0)
         ) %>%
  dplyr::select(-RaceWhite, -RaceBlack, -TotalPoverty ,-TotalUnemployment,-OwnerOccupied, -RenterOccupied, -TotalUnit, -TotalWFH, -BachelorsDegree, -GEOID) %>%
  st_transform(st_crs(phillyFishnet)) 

### 11.3 Organize into datasets

tracts21.MedHHInc <- tracts21 %>%
  dplyr::select(MedHHInc) %>%
  rename(Legend = MedHHInc)

tracts21.pctWhite <- tracts21 %>%
  dplyr::select(pctWhite)%>%
  rename(Legend = pctWhite)

tracts21.pctBlack <- tracts21 %>%
  dplyr::select(pctBlack)%>%
  rename(Legend = pctBlack)

tracts21.pctPoverty <- tracts21 %>%
  dplyr::select(pctPoverty)%>%
  rename(Legend = pctPoverty)

tracts21.pctUnemploy <- tracts21 %>%
  dplyr::select(pctUnemploy)%>%
  rename(Legend = pctUnemploy)

tracts21.pctBach <- tracts21 %>%
   dplyr::select(pctBach)%>%
   rename(Legend = pctBach)

tracts21.pctWFH <- tracts21 %>%
  dplyr::select(pctWFH)%>%
  rename(Legend = pctWFH)

tracts21.pctOwnerOccupied <- tracts21 %>%
  dplyr::select(pctOwnerOccupied)%>%
  rename(Legend = pctOwnerOccupied)

tracts21.pctRenterOccupied <- tracts21 %>%
  dplyr::select(pctRenterOccupied)%>%
  rename(Legend = pctRenterOccupied)

3. Exploratory Analysis

3.1 Data Cleaning and Feature Engineering

After bringing in relevant demographic and spatial variables for analysis, this next step aims to clean and categorize various columns in the dataset. For the sake of this study, only construction permits are required. This is isolated from the larger permitting dataset. At first glance, it was observed that there are multiple permit types that correspond to demolition, construction, and zoning. These categories are grouped by their respective permit type. For example, both ‘Building’ and ‘BP_Newcnst’ refer to permitting for new construction; these are bucketed together.

The permits are then grouped by year to examine patterns and trends for permitting in construction, development and zoning over the last decade to understand how construction trends are similar to or different from the other two trends.

# Data Cleaning

## 1. Selecting Permits related to Construction, Demolition and Zoning

phillyPermits <- phillyPermits%>%
  filter(permittype == "BUILDING" |
         permittype == "BP_NEWCNST" |
         permittype == "DEMOLITION" |
         permittype == "BP_DEMO" |
         permittype == "ZONING" |
         permittype == "ZP_USE" |
         permittype == "ZP_ZONING" |
         permittype == "ZP_ZON/USE")

## 2. Grouping permits related to Construction, Demolition and Zoning by category

phillyPermits <- phillyPermits %>%
  mutate(newType = case_when(permittype == "BUILDING" | permittype == "BP_NEWCNST"  ~ 'CONSTRUCTION PERMIT',
  permittype == "DEMOLITION" | permittype == "BP_DEMO" ~ 'DEMOLITION PERMIT',
  permittype == "ZONING" | permittype == "ZP_ZONING" | permittype == "ZP_USE" | permittype == "ZP_ZON/USE" ~ 'ZONING PERMIT'))

## 3. Selecting relevant columns only

phillyPermits <- dplyr::select(phillyPermits, permitnumber, Year, newType, typeofwork, approvedscopeofwork, commercialorresidential, censustract, council_district, geocode_x, geocode_y, geometry)

## 4. New Datasets by Permit Type

constructionPermits <- phillyPermits%>%
  filter(newType == 'CONSTRUCTION PERMIT')

demolitionPermits <- phillyPermits%>%
  filter(newType == 'DEMOLITION PERMIT')

zoningPermits <- phillyPermits%>%
  filter(newType == 'ZONING PERMIT')

## 5. Philadelphia Permits by year

Permits_2013 <- phillyPermits%>%
  filter(Year == 2013)

Permits_2014 <- phillyPermits%>%
  filter(Year == 2014)

Permits_2015 <- phillyPermits%>%
  filter(Year == 2015)

Permits_2016 <- phillyPermits%>%
  filter(Year == 2016)

Permits_2017 <- phillyPermits%>%
  filter(Year == 2017)

Permits_2018 <- phillyPermits%>%
  filter(Year == 2018)

Permits_2019 <- phillyPermits%>%
  filter(Year == 2019)

Permits_2020 <- phillyPermits%>%
  filter(Year == 2020)

Permits_2021 <- phillyPermits%>%
  filter(Year == 2021)

Permits_2022 <- phillyPermits%>%
  filter(Year == 2022)

## 5. Construction Permits for 2022

constructionPermits_2022 <- constructionPermits%>%
  filter(Year == 2022)

3.2 Data Visualization

3.2.1 Mapping Permits issued in Philadelphia between 2013 and 2022

The figures below show permit location and density for all permits issued over the last decade that fall under the major categories of construction, demolition and zoning. It is observed that permits are most dense in the center city area.

# Mapping Permits
## 1. A map of all major permits issued between 2013-2022, Philadelphia

ggplot() +
  geom_sf(data = phillyBoundary, fill = "grey89", color = "darkgrey") +
  geom_sf(data = phillyPermits, aes(colour = newType), size = 0.5, show.legend = "point") +
  scale_colour_manual(values = palettef) +
  labs(title= "Major Permits Issued, 2013-22",
       subtitle = 'Philadelphia, PA\n',
       caption = 'Figure 1') +
  mapTheme() +
  plotTheme()

3.2.2 Mapping Permits issued in Philadelphia by Type

# Mapping Permits by Type
## 1. A map of new construction permits issued between 2013-2022, Philadelphia

grid.arrange(ncol=2,
             ggplot() +
             geom_sf(data = phillyBoundary, fill = "grey89", color = "darkgrey") +
             geom_sf(data = constructionPermits, aes(colour = "#FE9900"), size=0.5, show.legend = "point") +
             scale_colour_manual(values = "#FE9900") +
             labs(title= "New Construction Permits Issued, 2013-22",
                  subtitle = 'Philadelphia, PA\n',
                  caption = 'Figure 2.1') +
             mapTheme() +
             theme(legend.position = "none") +
             plotTheme(),
             
             ggplot() + 
               geom_sf(data = phillyBoundary, fill = "grey89", color = "darkgrey") +
               stat_density2d(data = data.frame(st_coordinates(constructionPermits)), 
                              aes(X, Y, fill = ..level.., alpha = ..level..),
                              size = 0.01, bins = 40, geom = 'polygon') +
               scale_fill_viridis_c(option = "plasma") +
               scale_alpha(range = c(0.00, 0.35), guide = FALSE) +
               labs(title = "Density of Construction Permits, 2013-22",
                    subtitle = 'Philadelphia, PA\n',
                    caption = 'Figure 2.2') +
               mapTheme() + 
               theme(legend.position = "none") +
               plotTheme())

## 2. A map of demolition permits issued between 2013-2022, Philadelphia

grid.arrange(ncol=2,
             ggplot() +
             geom_sf(data = phillyBoundary, fill = "grey89", color = "darkgrey") +
             geom_sf(data = demolitionPermits, aes(colour = '#d7191c'), size=0.5, show.legend = "point") +
             scale_colour_manual(values = '#d7191c') +
             labs(title= "New Demolition Permits Issued, 2013-22",
                  subtitle = 'Philadelphia, PA\n',
                  caption = 'Figure 3.1') +
             mapTheme() +
             theme(legend.position = "none") +
             plotTheme(),
             
             ggplot() + 
               geom_sf(data = phillyBoundary, fill = "grey89", color = "darkgrey") +
               stat_density2d(data = data.frame(st_coordinates(demolitionPermits)), 
                              aes(X, Y, fill = ..level.., alpha = ..level..),
                              size = 0.01, bins = 40, geom = 'polygon') +
               scale_fill_viridis_c(option = "plasma") +
               scale_alpha(range = c(0.00, 0.35), guide = FALSE) +
               labs(title = "Density of Demolition Permits, 2013-22",
                    subtitle = 'Philadelphia, PA\n',
                    caption = 'Figure 3.2') +
               mapTheme() + 
               theme(legend.position = "none") +
               plotTheme())

## 3. A map of zoning permits issued between 2013-2022, Philadelphia

grid.arrange(ncol=2,
             ggplot() + 
             geom_sf(data = phillyBoundary, fill = "grey89", color = "darkgrey") +
             geom_sf(data = zoningPermits, aes(colour = '#2c7bb6'), size=0.5, show.legend = "point") +
             scale_colour_manual(values = '#2c7bb6') +
             labs(title= "New Zoning Permits Issued, 2013-22",
                  subtitle = 'Philadelphia, PA\n',
                  caption = 'Figure 3.1') +
             mapTheme() +
             theme(legend.position = "none") +
             plotTheme(),
             
             ggplot() + 
               geom_sf(data = phillyBoundary, fill = "grey89", color = "darkgrey") +
               stat_density2d(data = data.frame(st_coordinates(zoningPermits)), 
                              aes(X, Y, fill = ..level.., alpha = ..level..),
                              size = 0.01, bins = 40, geom = 'polygon') +
               scale_fill_viridis_c(option = "plasma") +
               scale_alpha(range = c(0.00, 0.35), guide = FALSE) +
               labs(title = "Density of Zoning Permits, 2013-22",
                    subtitle = 'Philadelphia, PA\n',
                    caption = 'Figure 3.2') +
               mapTheme() + 
               theme(legend.position = "none") +
               plotTheme())

3.2.4 Mapping Demographic Variables

The following plots explore the geographical distribution of demographic variables in relation to construction permits issued. It is seen that construction permits tend to be concentrated in areas with higher median household income and higher percentages of white population and bachelor’s degree educational attainment.

# Mapping Demographic Variables around Construction Permits Issued

bottomCaption <- textGrob("Figure 5", gp = gpar(hjust = 0))

commonTitle <- textGrob("Demographic Variables around Construction Permits Issued", gp = gpar(fontsize = 16, fontface = "bold", col="darkred", vjust=1))

grid.arrange(ncol=2,
             top = commonTitle,
             
  ggplot()+
    geom_sf(data=tracts21, aes(color=NA, fill=TotalPop))+
    scale_color_viridis()+
    scale_fill_viridis()+
    geom_sf(data = constructionPermits, aes(color="#FE9900") ,
          show.legend = "point", size = .2, alpha=0.25) +
    scale_color_identity() +
    labs(title="Total Population around Permits Issued")+
    mapTheme()+theme(plot.title = element_text(size = 10), legend.title=element_blank()),
  ggplot()+
    geom_sf(data=tracts21, aes(color=NA, fill=MedHHInc))+
    scale_color_viridis()+
    scale_fill_viridis()+
    geom_sf(data = constructionPermits, aes(color="#FE9900") ,
          show.legend = "point", size = .1, alpha=0.3) +
    scale_color_identity() +
    labs(title="Median Household Income around Permits Issued")+
    mapTheme()+theme(plot.title = element_text(size = 10), legend.title=element_blank()),
  ggplot()+
    geom_sf(data=tracts21, aes(color=NA, fill=pctUnemploy))+
    scale_color_viridis()+
    scale_fill_viridis()+
    geom_sf(data = constructionPermits, aes(color="#FE9900") ,
          show.legend = "point", size = .1, alpha=0.3) +
    scale_color_identity() +
    labs(title="% Unemployment around Permits Issued")+
    mapTheme()+theme(plot.title = element_text(size = 10), legend.title=element_blank()),
  ggplot()+
    geom_sf(data=tracts21, aes(color=NA, fill=pctBach))+
    scale_color_viridis()+
    scale_fill_viridis()+
    geom_sf(data = constructionPermits, aes(color="#FE9900") ,
          show.legend = "point", size = .1, alpha=0.3) +
    scale_color_identity() +
    labs(title="% Population with Bachelor's Degree around Permits Issued")+
    mapTheme()+theme(plot.title = element_text(size = 10), legend.title=element_blank()),
  ggplot()+
    geom_sf(data=tracts21, aes(color=NA, fill=pctWhite))+
    scale_color_viridis()+
    scale_fill_viridis()+
    geom_sf(data = constructionPermits, aes(color="#FE9900") ,
          show.legend = "point", size = .1, alpha=0.3) +
    scale_color_identity() +
    labs(title="% White Population around Permits Issued")+
    mapTheme()+theme(plot.title = element_text(size = 10), legend.title=element_blank()),
  ggplot()+
    geom_sf(data=tracts21, aes(color=NA, fill=pctBlack))+
    scale_color_viridis()+
    scale_fill_viridis()+
    geom_sf(data = constructionPermits, aes(color="#FE9900") ,
          show.legend = "point", size = .1, alpha=0.3) +
    scale_color_identity() +
    labs(title="% Black Population around Permits Issued \n\n")+
    mapTheme()+theme(plot.title = element_text(size = 10), legend.title=element_blank()), bottom=bottomCaption)

3.2.5 Mapping Permits within Fishnet

In this stage of the analysis, a geospatial dataset is created to understand the spatial distribution of development risk throughout Philadelphia. This key element of our analysis is the “fishnet,” a continuous grid pattern overlay in which every polygon is connected on all sides to the other polygon neighbors. This analysis uses a fishnet with 500x500 foot grid cells. The fishnet is used to treat development risk (presence of new construction permits) as a phenomenon that varies continuously across space. All point level daya collected is aggregated at the fishnet level.

The figure below shows the count of new construction permits across the fishnet. Grid cells shown in yellow have the largest number of permits. These locations seem to be predominantly concentrated in the Center City area.

# Mapping Permits in Fishnet

## 1. Construction permits joined to Fishnet, 2013-2022

constructionPermits <- st_transform(constructionPermits, st_crs(phillyFishnet))

construction_net <- 
  dplyr::select(constructionPermits) %>% 
  mutate(countPermits = 1) %>% 
  aggregate(., phillyFishnet, sum) %>%
  mutate(countPermits = replace_na(countPermits, 0),
         uniqueID = rownames(.),
         cvID = sample(round(nrow(phillyFishnet) / 24), 
                       size=nrow(phillyFishnet), replace = TRUE))


## 2. Visualizing permits joined to Fishnet, 2013-2022

ggplot() +
  geom_sf(data = construction_net, aes(fill = countPermits), color = NA) +
  geom_sf(data = st_boundary(phillyFishnet), color = "darkred", lwd = .04, alpha=.2) + # Add boundaries in red
  scale_fill_viridis_c(option = "plasma",
                       name = 'Construction Counts') +
  labs(title = "Construction Permits Joined to Fishnet",
       subtitle = 'Philadelphia, PA\n',
       caption = 'Figure 6') +
  mapTheme() +
  plotTheme()

4. Spatial Processes

To predict construction permits across space, it is important to account for the spatial process involved in the clustering of construction permits. This section explains the features created to assess spatial patterns and clustering.

4.1 Distribution of Potential Influencing Factors within Fishnet

The spatial data collected is joined to the fishnet to visualize the footprint of these different parameters across the city by counting the number of occurrences per grid cell before they are engineered further. Figure 7 displays a selection of these spatial risk factors mapped to the fishnet.

# Attaching datasets on spatial factors to Fishnet

## 1. Extracting geometry for spatial factors

services <- service %>%
  dplyr::select(geometry, Legend)

affordableHousings <- affordableHousing %>%
  dplyr::select(geometry, Legend)

vacantBuildings <- vacantBuilding %>%
  dplyr::select(geometry, Legend)

vacantLands <- vacantLand %>%
  dplyr::select(geometry, Legend)

businessLicenses <- businessLicense %>%
  dplyr::select(geometry, Legend)

sanitations <- sanitation %>%
  dplyr::select(geometry, Legend)

greenSpaces <- greenSpace %>%
  dplyr::select(geometry, Legend)

buildingDemolitions <- buildingDemolition %>%
  dplyr::select(geometry, Legend)


## 2. Creating fishnet of spatial factor variables 

vars_net <- 
  rbind(affordableHousings, vacantBuildings, vacantLands, businessLicenses, 
        greenSpaces, services, sanitations, buildingDemolitions) %>%
  st_join(., phillyFishnet, join=st_within) %>%
  st_drop_geometry() %>%
  group_by(uniqueID, Legend) %>%
  summarize(count = n()) %>%
  full_join(phillyFishnet, by = "uniqueID") %>%
  spread(Legend, count, fill=0) %>%
  st_sf() %>%
  na.omit() %>% 
  dplyr::select(-`<NA>`) %>%
  ungroup()

## 3. Mapping spatial factor variables by count in fishnet

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

vars <- unique(vars_net.long$Variable)
mapList <- list()

for (i in vars) {
  mapList[[i]] <- 
    ggplot() +
    geom_sf(data = filter(vars_net.long, Variable == i), aes(fill = value), colour = NA) +
    scale_fill_viridis(option = "plasma", name = " ") +
    labs(title = i) +
    mapTheme()+
    theme(plot.title = element_text(size = 10, color = "black"))
}

bottomCaption <- textGrob("Figure 7", gp = gpar(hjust = 0))

grid.arrange(grobs = mapList, ncol = 2, top = textGrob("Potential Influencing Spatial Factors by Fishnet\n", gp = gpar(fontsize = 15, fontface = "bold", col="darkred")), bottom = bottomCaption)

4.2 Nearest Neighbor Factors by Fishnet

Next a nearest neighbor feature is created for each point-level risk factor by converting the grid cells to centroid points and then measuring the distance to the nearest risk factor point. This better accounts for the relative distance to our point risk factors compared to the alternative of simply summing the factors by grid cell. The outcome of this engineering is visualized in Figure 8 below.

# 1. Calculating Nearest Neighbors


## 1.1. Vacant Land

### Mapping nearest feature

nearest_vacantLand <- sf::st_nearest_feature(vars_net, vacantLand)

### Converting to rsgeo geometries

x <- rsgeo::as_rsgeo(vars_net)
y <- rsgeo::as_rsgeo(vacantLand)

### Calculating distance

vars_net$dist_vacantLand <- rsgeo::distance_euclidean_pairwise(x, y[nearest_vacantLand])


## 1.2. Vacant Buildings

### Mapping nearest feature

nearest_vacantBuilding <- sf::st_nearest_feature(vars_net, vacantBuilding)

### Converting to rsgeo geometries

x <- rsgeo::as_rsgeo(vars_net)
y <- rsgeo::as_rsgeo(vacantBuilding)

### Calculating distance

vars_net$dist_vacantBuilding <- rsgeo::distance_euclidean_pairwise(x, y[nearest_vacantBuilding])


## 1.3. Affordable Housing

### Mapping nearest feature

nearest_affordableHousing <- sf::st_nearest_feature(vars_net, affordableHousing)

### Converting to rsgeo geometries

x <- rsgeo::as_rsgeo(vars_net)
y <- rsgeo::as_rsgeo(affordableHousing)

### Calculating distance

vars_net$dist_affordableHousing <- rsgeo::distance_euclidean_pairwise(x, y[nearest_affordableHousing])


## 1.4. Green Spaces

### Mapping nearest feature

nearest_greenSpace <- sf::st_nearest_feature(vars_net, greenSpace)

### Converting to rsgeo geometries

x <- rsgeo::as_rsgeo(vars_net)
y <- rsgeo::as_rsgeo(greenSpace)

### Calculating distance

vars_net$dist_greenSpace <- rsgeo::distance_euclidean_pairwise(x, y[nearest_greenSpace])


## 1.5. Businesses

### Mapping nearest feature

nearest_businessLicense <- sf::st_nearest_feature(vars_net, businessLicense)

### Converting to rsgeo geometries

x <- rsgeo::as_rsgeo(vars_net)
y <- rsgeo::as_rsgeo(businessLicense)

### Calculating distance

vars_net$dist_businessLicense <- rsgeo::distance_euclidean_pairwise(x, y[nearest_businessLicense])


## 1.6. Sanitation Violation Reports

### Mapping nearest feature

nearest_sanitation <- sf::st_nearest_feature(vars_net, sanitation)

### Converting to rsgeo geometries

x <- rsgeo::as_rsgeo(vars_net)
y <- rsgeo::as_rsgeo(sanitation)

### Calculating distance

vars_net$dist_sanitation <- rsgeo::distance_euclidean_pairwise(x, y[nearest_sanitation])


## 1.7. Service Requests

### Mapping nearest feature

nearest_service <- sf::st_nearest_feature(vars_net, service)

### Converting to rsgeo geometries

x <- rsgeo::as_rsgeo(vars_net)
y <- rsgeo::as_rsgeo(service)

### Calculating distance

vars_net$dist_service <- rsgeo::distance_euclidean_pairwise(x, y[nearest_service])


## 1.8. Building Demolitions

### Mapping nearest feature

nearest_buildingDemolition <- sf::st_nearest_feature(vars_net, buildingDemolition)

### Converting to rsgeo geometries

x <- rsgeo::as_rsgeo(vars_net)
y <- rsgeo::as_rsgeo(buildingDemolition)

### Calculating distance

vars_net$dist_buildingDemolition <- rsgeo::distance_euclidean_pairwise(x, y[nearest_buildingDemolition])


# 2. Visualizing nearest distance for spatial factors on Fishnet

## 2.1. Visualizing the nearest three features

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

vars <- unique(vars_net.long.nn$Variable)
mapList <- list()

for(i in vars){
  mapList[[i]] <- 
    ggplot() +
    geom_sf(data = filter(vars_net.long.nn, Variable == i), aes(fill=value), colour=NA) +
    scale_fill_viridis_c(option = "plasma",
                         name = " ") +
    labs(title=i) +
    mapTheme()+
    theme(plot.title = element_text(size = 12, color = "black"))
  }

bottomCaption <- textGrob("Figure 8", gp = gpar(hjust = 0))

do.call(grid.arrange, c(list(grobs = mapList, ncol = 2, 
                             top = textGrob("Spatial Factors: Nearest Neighbor Distance for Permits Issued\n", 
                                            gp = gpar(fontsize = 15, fontface = "bold", col = "darkred")), 
                             bottom = bottomCaption)))

# 3. Creating new factor of distance to center city based on plots

ccPoint <-
  filter(phillyNeighborhoods, name == "CENTER_CITY") %>%
  st_centroid()

vars_net$dist_centerCity =
  st_distance(st_centroid(vars_net),ccPoint) %>%
  as.numeric() 

4.3 Performing Spatial Joins

Finally, spatial joins are performed to combine the permit data with the risk factor data the neighborhood data with the final net.

# Joining Census Data to Fishnet

tracts21 <- tracts21 %>%
  filter(TotalPop>0)

vars_net <-
  vars_net%>%
  st_centroid()%>%
  st_join(tracts21)

library(dplyr)
vars_net <- vars_net %>% mutate_all(~replace(., is.na(.), 0))

# Perform Spatial Join of variables with permits

final_net <-
  left_join(construction_net, st_drop_geometry(vars_net), by="uniqueID")

# Final Net

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

5. Modelling Process

Our aim is to forecast the likelihood of new construction permits within specific areas of the city. To this end, a geospatial risk model is likely to be employed for prediction with the dependent variable being new construction permits. The following steps examine the nature of the dependent variable and its relation to the engineered independent variables.

5.1 Examining Distribution of Dependent Variable

As is evident from the histogram below, construction permits are unevenly distributed across the city, that is, a large majority of permits are issued in only a handful of places across Philadelphia. This indicates the presence of spatial clustering and illustrates that the dependent variable is a suitable candidate for hotspot analysis.

# Distribution of permits

ggplot(data = final_net, aes(countPermits))+
  geom_histogram(bins = 30, color = 'black', fill = "yellow") +
  scale_x_continuous(breaks = seq(0, 80, by = 15)) +
  labs(title = 'Distribution of Permits', subtitle="Philadelphia, PA", y = "Occurrence Frequency", x="Count of Permits Issued per Fishnet Cell", caption="Figure 9") +
  theme(plot.margin = margin(5, 50, 5, 50, "pt"))+
    theme(axis.text.x=element_text(size=6))+
    theme(axis.text.y=element_text(size=6))+
  theme(
    axis.title = element_text(size = 7), 
    plot.title = element_text(size = 9.5, face= "bold", color = "darkred"),
    plot.subtitle = element_text(size = 7, face= "italic", color = "black"),
    plot.caption = element_text(size = 7), 
    plot.margin = margin(0, 50, 8, 50, "pt"))  

5.2 Examining Local Moran’s I for Fishnet Grid Cells

The effectiveness of our model hinges on its capacity to consider spatial clustering across various scales. To investigate the local spatial process, we utilize Local Moran’s I. The null hypothesis posits that the count of new construction permits exhibits a random distribution concerning its immediate neighbors. For this analysis, a nearest neighbor weights matrix is used, connecting a unit (in this case, a fishnet grid) to its eight adjacent neighbors with the queen continuity arrangement method.

The results presented are simple to interpret, as seen in the plots below. In the cases where the count of count of construction permits issued are at their highest, the Moran’s I further attaches to its neighbors. These cases are then checked for a low P-value that is less than 0.00001 to determine whether they are statistically significant. The fishnet cells that fulfill the above parameters are then identified as a construction or a construction permit hotspot.

# 1. Local Moran's I calculation

## 1.1. Assigning weights

final_net.nb <- poly2nb(as_Spatial(final_net), queen=TRUE)
final_net.weights <- nb2listw(final_net.nb, style="W", zero.policy=TRUE)

## 1.2. Creating Local Morans

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


## 1.3. Join local Moran's I results to fishnet

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


# 2. Local Moran's I  Plot

vars <- unique(final_net.localMorans$Variable)
varList <- list()

# for(i in vars){
#   varList[[i]] <-
#     ggplot() +
#       geom_sf(data = phillyBoundary, fill = "#808080", color = "darkgrey") +
#       geom_sf(data = filter(final_net.localMorans, Variable == i),
#               aes(fill = Value), colour=NA) +
#       scale_fill_viridis(option = "plasma",name="") +
#       labs(title=i) +
#       mapTheme() + theme(plot.title = element_text(size = 12, color = "black"),
#             legend.position = "bottom")}
# 
# bottomCaption <- textGrob("Figure 10", gp = gpar(hjust = 0))
# 
# do.call(grid.arrange, c(varList, list(ncol = 4,
#                                       top = textGrob("Local Morans I Statistics for Philadelphia Permits, 2013 - 2022\n",
#                                                      gp = gpar(fontsize = 15, fontface = "bold", col = "darkred")),
#                                       bottom = bottomCaption)))

saveRDS(local_morans, file = "C:/Users/user/Documents/GitHub/Final - Philly Permits/local_morans.rds")
Alt text
Alt text

Adopting a smaller p-value threshold(less than the universally accepted threshold of 0.05) allows for a reasonably accurate identification of construction permit hotspots or concentrated areas where development is expected to occur. This is particularly evident in Center City and northeast Philadelphia.

In executing the Local Moran’s I test, the null hypothesis that the permit count at a given location is randomly distributed relative to its immediate neighbors is rejected. This implies spatial autocorrelation of model errors at the local level that need to be assessed and controlled.

5.3 Calculating Distance to Hot Spots

The nearest neighbor function is used to establish a spatial association between incidents of permits/development and the risk factors that are identified.

The hotspot information generated is used first to establish a spatial association between incidents of permits/development with the risk factors identified. It is then used to construct a spatial feature aimed at mitigating local spatial autocorrelation. In this context, the distance of each grid cell to the nearest significant hotspot is calculated and stored in a model variable.

# 1. Examining Hot Spots

## 1.1. Distance to Hot spots

local_morans <- readRDS("C:/Users/user/Documents/GitHub/Final - Philly Permits/local_morans.rds")
  
final_net <-
  final_net %>% 
  mutate(permit.isSig = 
           ifelse(local_morans[,5] <= 0.0000001, 1, 0)) %>% 
  mutate(permit.isSig.dist = 
           nn_function(st_coordinates(st_centroid(final_net)),
                       st_coordinates(st_centroid(
                         filter(final_net, permit.isSig == 1))), 1))


## 1.2 Plotting NN Hot spots

ggplot() +
      geom_sf(data = final_net, aes(fill=permit.isSig.dist), colour=NA) +
      scale_fill_viridis(name="NN Distance") +
      labs(title="Permit NN Distance", caption= "Figure 11") +
      mapTheme()+
      plotTheme()

5.4 Examining Correlations between Independent Variables

To check how well the selected predictors are linked to the outcome, the correlation between the dependent variable that is permit count and the independent variables is studied. This is done in two steps. First, correlation scatter plots are created. It is to be noted that the predictors do not have high R-squared values which can be attributed to the size of the dataset as well as the skewed distribution of permit count per fishnet cell.

The plots illustrate that spatial variables such as ‘Affordable Housing’, ‘Building Demolition, ’Business Licences’, ‘Vacant Land’, ‘Sanitation’, ‘Services’, and demographic variables such as ‘Percent Bachelor’s Degree Holders’, ‘Percent Renter Occupied’, and ‘Median Household Income’ exhibit positive relationships with the dependent variable.

Additionally, it is seen that overall, the spatial process features have slightly higher R-squared values than their non-spatial process counterparts.

# Conducting correlation Tests for spatial and demographic factors

correlation.long <-
  st_drop_geometry(final_net) %>%
    dplyr::select(-uniqueID, -cvID, -name) %>%
    gather(Variable, Value, -countPermits) %>%
  mutate(Value = as.numeric(Value))

correlation.cor <-
  correlation.long %>%
    group_by(Variable) %>%
    summarize(correlation = cor(Value, countPermits, use = "complete.obs"))

# Visualizing correlations through scatter plots
    
ggplot(correlation.long, aes(Value, countPermits)) +
  geom_point(size = 0.1) +
  geom_text(data = correlation.cor, aes(label = paste("r =", round(correlation, 2))),
            x=-Inf, y=Inf, vjust = 1.5, hjust = -.1) +
  geom_smooth(method = "lm", se = FALSE, colour = "orange") +
  facet_wrap(~Variable, ncol = 4, scales = "free") +
  labs(title = "Permit Count as a function of risk factors", caption="Figure 12") +
  plotTheme()

Next, the correlations are visualized using a correlation matrix. This is done to examine correlations between the selected independent variables to minimize multi-collinearity in the regression model. It is seen that variables such as ‘Vacant Buildings’, ‘Sanitation’, and’Building Demolition’ have strong correlations with other predictor variables. These are no longer considered for regression. Additionally, it is seen that pairs such as ‘Distance to Center City’ and ‘Permit is Significant Distance’ as well as ‘Median Household Income’ and ’Percent Bachelor’s Degree Holders exhibit strong correlations, therefore they are also dropped in the process of selecting regression varibles.

# Visualizing correlations through a correlation matrix

numvars <- c("countPermits", "dist_vacantLand", "dist_vacantBuilding",  "dist_affordableHousing","dist_greenSpace","dist_businessLicense", "dist_service", "dist_sanitation", "dist_buildingDemolition", "TotalPop", "MedHHInc", "pctWhite", "pctBlack", "pctBach" ,"pctPoverty", "pctUnemploy", "pctWFH", "pctOwnerOccupied", "pctRenterOccupied", "dist_centerCity", "permit.isSig.dist")

numeric <- final_net %>%
  st_drop_geometry(final_net) %>%
  dplyr::select(numvars)%>%
  na.omit()

ggcorrplot(
  round(cor(numeric), 1), 
  p.mat = cor_pmat(numeric),
  colors = c('#d7191c','white','#2c7bb6'),
  type="lower",
  insig = "blank") +  
    labs(title = "Correlation across Variables\n", caption="Figure 13")+ 
    theme(plot.title = element_text(size = 11, face = "bold", color = "darkred"))+
    theme(axis.text.x=element_text(size=8))+
    theme(axis.text.y=element_text(size=8))

6. Regression

Construction permit issuance often exhibits clustering patterns, where nearby locations influence each other. As seen in the histogram in Figure 9 above, the count of permits issued per fishnet cell vary non-uniformly between 0 and 105, with most cells containing 0 permits. Given that development is a phenomenon dictated by various spatial, social, demographic, it is reasonable for most grid cells to contain few or no development events.

With these data trends in mind, a spatial Poisson Regression model which accounts for the spatial autocorrelation is selected. This allows the model to discern and leverage the local spatial processes influencing permit counts in adjacent cells. By capturing the spatial nuances of construction permit distributions, the Spatial Poisson Regression model is expected to provide more accurate predictions, offering a superior understanding of the spatial dynamics driving permit issuance across the fishnet, ultimately enhancing the model’s predictive performance for this specific spatial context.

6.1 Selecting Variables

For regression and cross validation, two sets of variables are created – one comprising recently added variables indicating the distance to a significant construction hotspot, and the other containing only risk factor variables. This approach helps illustrate the significance of generalizability across diverse spatial contexts and demonstrates how the hotspot variable enhances the model’s overall generalizability.

Based on the correlation analysis as seen in Figure 13, ‘Vacant Land’, ‘Green Space’, ‘Business License’, ‘Affordable Housing’, ‘Percent Renter Occupied’, ‘Percent Working From Home’ and ’Percent Bachelor’s Degree Holders are selected as non-spatial process model variables. The same variables with spatial process features added are considered for the spatial process models.

# Regression Variables Bucketing

reg.vars <- c("dist_vacantLand","dist_greenSpace", "dist_service", "dist_businessLicense","dist_affordableHousing", "pctRenterOccupied", "pctWFH", "pctBach")

reg.ss.vars <- c("dist_vacantLand","dist_greenSpace", "dist_service", "dist_businessLicense","dist_affordableHousing",  "pctRenterOccupied","pctWFH","pctBach", "permit.isSig", "permit.isSig.dist")

6.2 Splitting dataset for Cross Validation

Two types of data folds for cross-validation are implemented, namely, random folds and neighborhood folds.

The Leave-One-Group-Out Cross-Validation (LOGO-CV) method systematically withholds one group at a time from the model training, predicting on the excluded group. For neighborhood folds, all grid cells excluding the grid cells belonging to a particular neighborhood are allocated to the training set and then tested on the grid cells within the excluded neighborhood. This process repeats until each neighborhood has served as the holdout. By adopting this approach, we ensure that model training remains unaffected by the random inclusion or exclusion of an outlier in the training set. However, using neighborhoods as the holdout criteria assumes a degree of similarity across neighborhoods in the city, which is not the case and will be detailed further in the following sections.

# Creating functions for regression and cross validation

crossValidate <- function(dataset, id, dependentVariable, indVariables) {
  
  allPredictions <- data.frame()
  cvID_list <- unique(dataset[[id]])
  
  for (i in cvID_list) {
    
    thisFold <- i
    cat("This hold out fold is", thisFold, "\n")
    
    fold.train <- filter(dataset, dataset[[id]] != thisFold) %>% as.data.frame() %>% 
      dplyr::select(id, geometry, indVariables, dependentVariable)
    fold.test  <- filter(dataset, dataset[[id]] == thisFold) %>% as.data.frame() %>% 
      dplyr::select(id, geometry, indVariables, dependentVariable)
    
    regression <-
      glm(countPermits ~ ., family = "poisson", 
          data = fold.train %>% 
            dplyr::select(-geometry, -id))
    
    thisPrediction <- 
      mutate(fold.test, Prediction = predict(regression, fold.test, type = "response"))
    
    allPredictions <-
      rbind(allPredictions, thisPrediction)
    
  }
  return(st_sf(allPredictions))
}

6.3 Cross Validation

In the cross-validation process, four distinct regressions are examined. Two of them employ random k-fold cross-validation: one incorporating only the basic risk factors, and another integrating the variable measuring the distance to significant hotspots. The other two regressions adopt Leave-One-Group-Out Cross-Validation (LOGO-CV) based on the neighborhood name, similarly split between the simple risk factors and the inclusion of the distance to significant hotspots variable.

# Conducting Cross Validation on 4 Poisson Regression models

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

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

final_net$name <- ifelse(is.na(final_net$name), "UNKNOWN", final_net$name)
  
reg.spatialCV <- crossValidate(
  dataset = final_net,
  id = "name",
  dependentVariable = "countPermits",
  indVariables = reg.vars) %>%
    dplyr::select(cvID = name, countPermits, Prediction, geometry)

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

6.5 Calculating Errors

In each regression, the absolute error is calculated by determining the difference between the predicted count of new construction permits and the actual observed count of construction permits.

# Calculating Errors based on Cross Validation models

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

6.6 Mapping Regression Error Predictions

The relative performance of each regression is assessed by analyzing the distributions of mean absolute error (MAE) across different folds. The MAE, representing the absolute value of the mean error, is utilized to gauge the accuracy of the predictions.

6.6.1 Accuracy and Generalizability

A high Mean Absolute Error (MAE) value in a neighborhood indicates that, on average, the predictions made by the model for new construction permit counts in that neighborhood deviate significantly from the actual observed values. This indicates that the model is less accurate in predicting construction permit counts for that specific neighborhood.

In the case of this study, the higher MAE values are observed in the following neighborhoods while performing both LOGO CV and LOGO CV Spatial Process regressions and cross validations:

  • East Kensington,
  • Greenwich,
  • Ludlow,
  • Old Kensington, and
  • Point Breeze

However, a lower Mean Absolute Error (difference of minimum 10 counts) is observed for each of these neighborhoods in the LOGO CV spatial process regression compared to the LOGO CV model suggesting that incorporating the spatial process has improved the predictive accuracy of the model for that specific neighborhood. Overall too, the spatial process models exhibits fewer errors than the risk factor models and has improved generalizability as it accounts for spatial autocorrelation and local variations within neighborhoods.

The following plots visualize the relative performance of each regression by analyzing the distributions of mean absolute error (MAE) for each fold.

# Model Error Grouping (MAE)

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

error_by_reg_and_fold %>%
  arrange(desc(MAE))
error_by_reg_and_fold %>%
  arrange(MAE)

# Histogram of Errors by model

error_by_reg_and_fold %>%
  ggplot(aes(MAE)) + 
    geom_histogram(bins = 30, colour="black", fill = "#FDE725FF") +
  facet_wrap(~Regression) + 
  scale_x_continuous(breaks = seq(0, 11, by = 1)) + 
    labs(title="Distribution of MAE", subtitle = "Random K-Fold and LOGO-CV", caption = "Figure 16",
         x="Mean Absolute Error", y="Count")+
    theme(plot.title = element_text(size = 15, face= "bold", color = "darkred"))

The histograms in Figure 16 reveal that regressions incorporating the Distance to Hotspot variables exhibit more clustered errors, characterized by fewer errors of high magnitudes. LOGO-CV, under the assumption that spatial processes in other neighborhoods apply to the one held out, may not accurately capture the local variability. The inclusion of the Distance to Hotspot variable helps address this issue, enhancing predictive capability, especially in hotspots with high permit counts.

The following maps illustrate the reduction in mean absolute error (MAE) across space, emphasizing the improvement achieved by incorporating the spatial process variable alongside risk factors.

# Mapping Spatial LOGO CV Errors with and without Spatial Process incorporation

error_by_reg_and_fold %>%
  filter(str_detect(Regression, "LOGO")) %>%
  ggplot() +
  geom_sf(data = phillyBoundary, fill = "white", color = "darkgrey") +
    geom_sf(aes(fill = MAE)) +
    facet_wrap(~Regression) +
    scale_colour_viridis(option = "plasma") +
    scale_fill_viridis(option = "plasma") +
    labs(title = "Errors by LOGO-CV Regression", caption="Figure 15") +
    theme(plot.title = element_text(size = 15, face= "bold", color = "darkred"))+
    theme(strip.text = element_text(size = 8))

Table 1, (below) detailing the mean MAE and standard deviation of MAE, indicates that the random k-fold with spatial processes exhibits the smallest error. On the other hand, the spatial LOGO is likely to have a higher error due to its assumption that experiences in all other neighborhoods can accurately apply to a single one.

# Table of MAE and Standard Deviation MAE

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(caption = "Table 1: MAE and standard deviation MAE by regression") %>%
    kable_styling("striped", full_width = F) %>%
    row_spec(2, color = "black", background = "#FDE725FF") %>%
    row_spec(4, color = "black", background = "#FDE725FF")  
Table 1: MAE and standard deviation MAE by regression
Regression Mean_MAE SD_MAE
Random k-fold CV: Just Risk Factors 0.59 0.59
Random k-fold CV: Spatial Process 0.51 0.54
Spatial LOGO-CV: Just Risk Factors 1.59 2.77
Spatial LOGO-CV: Spatial Process 0.82 1.36

Table 2 below illustrates that both LOGO_CV regression models exhibit spatial autocorrelation in the errors, with the spatial process model having a higher Moran’s I and a lower p-value, indicating a stronger spatial clustering pattern in its errors. In this context, a lower p-value (e.g., 0.001) indicates that the observed spatial autocorrelation in the errors of the spatial process model is statistically significant. This suggests that there are spatial patterns or dependencies in the data that the model has captured, and these patterns are not explained by random variability alone.

# Table on Moran's I on Errors by Regression

neighborhood.weights <-
  filter(error_by_reg_and_fold, Regression == "Spatial LOGO-CV: Spatial Process") %>%
    group_by(cvID) %>%
      poly2nb(as_Spatial(.), queen=TRUE) %>%
      nb2listw(., style="W", zero.policy=TRUE)

filter(error_by_reg_and_fold, str_detect(Regression, "LOGO"))  %>% 
    st_drop_geometry() %>%
    group_by(Regression) %>%
    summarize(Morans_I = moran.mc(abs(Mean_Error), neighborhood.weights, 
                                 nsim = 999, zero.policy = TRUE, 
                                 na.action=na.omit)[[1]],
              p_value = moran.mc(abs(Mean_Error), neighborhood.weights, 
                                 nsim = 999, zero.policy = TRUE, 
                                 na.action=na.omit)[[3]]) %>% 
  kable(caption = "Table 2: Moran's I on Errors by Regression") %>%
    kable_styling("striped", full_width = F) %>%
    row_spec(1, color = "black", background = "#FDE725FF") %>%
    row_spec(1, color = "black", background = "#FDE725FF") 
Table 2: Moran’s I on Errors by Regression
Regression Morans_I p_value
Spatial LOGO-CV: Just Risk Factors 0.1225352 0.009
Spatial LOGO-CV: Spatial Process 0.2026576 0.002

6.6.2 Predicted vs Observed Outcomes

The above results are further analyzed by examining the MEA by decile of permit counts. The inclusion of the hotspot variable significantly mitigates errors in regions with the highest permit density. However, a consistent pattern emerges across all models, where they tend to underestimate in high-density permitting areas and overestimate in low-density permitting areas.

# Plotting Predicted vs Observed Permits by Decile of Observed Permits 

st_drop_geometry(reg.summary) %>%
  group_by(Regression) %>%
    mutate(Permit_Decile = ntile(countPermits, 10)) %>%
  group_by(Regression, Permit_Decile) %>%
    summarize(meanObserved = mean(countPermits, na.rm=T),
              meanPrediction = mean(Prediction, na.rm=T)) %>%
    gather(Variable, Value, -Regression, -Permit_Decile) %>%          
    ggplot(aes(Permit_Decile, Value, shape = Variable)) +
      geom_point(size = 2) + geom_path(aes(group = Permit_Decile), colour = "black") +
      scale_shape_manual(values = c(2, 17)) +
      facet_wrap(~Regression) + xlim(0,10) +
      labs(title = "Predicted and Observed Permits by Observed Permit Decile", caption = "Figure 16")  +
      theme(strip.text = element_text(size = 8)) + 
      theme(plot.title = element_text(size = 15, face= "bold", color = "darkred"))

6.6.3 Racial Generalizability

Another crucial aspect of generalizability involves considering social dimensions like race and income. The following table (Table 3) illustrates the permitting patterns in the racial context of Philadelphia, distinguishing between majority white and non-white census tracts.

# Understanding Prediction by Race Context

tracts21new <- tracts21 %>%
  st_transform('ESRI:102728')  %>% 
  mutate(raceContext = ifelse(pctWhite > 50, "Majority_White", "Majority_Non_White")) %>%
  .[phillyNeighborhoods,]

reg.summary %>% 
  filter(str_detect(Regression, "LOGO")) %>%
    st_centroid() %>%
    st_join(tracts21new) %>%
    na.omit() %>%
      st_drop_geometry() %>%
      group_by(Regression, raceContext) %>%
      summarize(mean.Error = mean(Error, na.rm = T)) %>%
      spread(raceContext, mean.Error) %>%
      kable(caption = "Table 3: Mean Error by neighborhood racial context") %>%
        kable_styling("striped", full_width = F) %>%
    row_spec(1, color = "black", background = "#FDE725FF")
Table 3: Mean Error by neighborhood racial context
Regression Majority_Non_White Majority_White
Spatial LOGO-CV: Just Risk Factors 0.1528365 -0.0417845
Spatial LOGO-CV: Spatial Process 0.0168224 0.0074744

When examining the mean error split by racial context, it becomes evident that the incorporation of the spatial process reduces the magnitude of errors in both non-white and white neighborhoods. However, a notable pattern emerges where the model tends to underestimate the risk of new construction permits in non-white areas and overestimate it in white areas. This observed tendency is a concern, highlighting the potential for decision-makers to be misled by lower risk predictions in majority non-white neighborhoods. Such underprediction could result in a misallocation of resources and lesser importance given for implementing green building strategies in these areas, potentially exacerbating existing disparities. Future iterations of the analysis aim to enhance the model’s generalizability across racial contexts to mitigate the risk of perpetuating resource inequities.

These tendencies are captured in Figure 17 that compares errors by race context for different neighborhoods from a fishnet level.

# Plotting Race Context


ggplot()+
    geom_sf(data = phillyBoundary, fill = "white", color = "darkgrey") +
    geom_sf(data=tracts21new, aes(fill =raceContext ))+
    scale_fill_viridis(discrete=TRUE)+
    labs(title="Race Context - Philadelphia, 2021", caption="Figure 17.1")+
    mapTheme()+
    plotTheme()

  ggplot()+
    geom_sf(data = phillyBoundary, fill = "white", color = "darkgrey") +
    geom_sf(data=joined_sf_spatial, aes(fill =ErrorbyRaceContext ))+
    scale_fill_viridis(discrete=TRUE)+
    labs(title="Errors by Race - LOGO CV Spatial", caption="Figure 17.2")+
    mapTheme()+
    plotTheme()

7. Model Alternatives and Comparison

7.1 Kernel Density of Predicted Permits

The results of the predictive model are also compared with the traditional Kernel Density approach. Towards this end kernel density maps are created for three different search radii - 500ft, 1000ft, and 1500 ft. To capture density, the 1500ft radius map is selected for comparison with the predictive model.

# Creating Kernel Density using 3 different radii

permits_ppp <- as.ppp(st_coordinates(constructionPermits), W = st_bbox(vars_net))
permits_KD.500 <- spatstat.explore::density.ppp(permits_ppp, 500)
permits_KD.1000 <- spatstat.explore::density.ppp(permits_ppp, 1000)
permits_KD.1500 <- spatstat.explore::density.ppp(permits_ppp, 1500)
permits_KD.df <- rbind(
  mutate(data.frame(rasterToPoints(mask(raster(permits_KD.500), as(phillyNeighborhoods, 'Spatial')))), Legend = "500 Ft."),
  mutate(data.frame(rasterToPoints(mask(raster(permits_KD.1000), as(phillyNeighborhoods, 'Spatial')))), Legend = "1000 Ft."),
  mutate(data.frame(rasterToPoints(mask(raster(permits_KD.1500), as(phillyNeighborhoods, 'Spatial')))), Legend = "1500 Ft.")) 

permits_KD.df$Legend <- factor(permits_KD.df$Legend, levels = c("500 Ft.", "1000 Ft.", "1500 Ft."))

# Plotting Density 

ggplot(data=permits_KD.df, aes(x=x, y=y)) +
  geom_raster(aes(fill=layer)) + 
  facet_wrap(~Legend) +
  coord_sf(crs=st_crs(vars_net)) + 
  scale_fill_viridis(option = 'plasma',name="Density") +
  labs(title = "Kernel density Maps with 3 different search radii", caption="Figure 18") +
  mapTheme()+
  plotTheme()

7.2 Comparison of Models

The comparison in Figure 19 shows that while the kernel density model does a relatively good job at accounting for and predicting permits in 2022, the predictive model is more accurate. However, the bar plots in Figure 20 indicate that the predictive model underpredicts by a slim margin cases of high and medium risk and overpredicts in areas of low risk.

# Prediction by Kernel Density Model

permits_KD_sf <- as.data.frame(permits_KD.1500) %>%
  st_as_sf(coords = c("x", "y"), crs = st_crs(final_net)) %>%
  aggregate(., final_net, mean) %>%
  mutate(label = "Kernel Density",
         Risk_Category = ntile(value, 100),
         Risk_Category = case_when(
           Risk_Category >= 90 ~ "90% to 100%",
           Risk_Category >= 70 & Risk_Category <= 89 ~ "70% to 89%",
           Risk_Category >= 50 & Risk_Category <= 69 ~ "50% to 69%",
           Risk_Category >= 30 & Risk_Category <= 49 ~ "30% to 49%",
           Risk_Category >= 1 & Risk_Category <= 29 ~ "1% to 29%")) %>%
  cbind(
    aggregate(
      dplyr::select(constructionPermits_2022) %>% mutate(permitCount = 1), ., sum) %>%
      mutate(permitCount = replace_na(permitCount, 0))) %>%
  dplyr::select(label, Risk_Category, permitCount)

# Prediction by Risk Prediction Model

permits_KD_risk_sf <-
  reg.ss.spatialCV %>%
  mutate(label = "Risk Predictions",
         Risk_Category = ntile(Prediction, 100),
         Risk_Category = case_when(
           Risk_Category >= 90 ~ "90% to 100%",
           Risk_Category >= 70 & Risk_Category <= 89 ~ "70% to 89%",
           Risk_Category >= 50 & Risk_Category <= 69 ~ "50% to 69%",
           Risk_Category >= 30 & Risk_Category <= 49 ~ "30% to 49%",
           Risk_Category >= 1 & Risk_Category <= 29 ~ "1% to 29%", 
           TRUE ~ "1% to 29%")) %>%
   cbind(
    aggregate(
      dplyr::select(constructionPermits_2022) %>% mutate(permitCount = 1), ., sum) %>%
      mutate(permitCount = replace_na(permitCount, 0))) %>%
  dplyr::select(label, Risk_Category, permitCount)

# Plotting comparison between both models

rbind(permits_KD_sf, permits_KD_risk_sf) %>%
  na.omit() %>%
  gather(Variable, Value, -label, -Risk_Category, -geometry) %>%
  ggplot() +
  geom_sf(aes(fill = Risk_Category), colour = NA) +
  geom_sf(data = sample_n(constructionPermits_2022, 1130), size = .5, colour = "black") +
  facet_wrap(~label, ) +
  scale_fill_viridis_d(option = "plasma",
                       name = 'Risk Category') + 
  labs(title="Comparison of Kernel Density and Risk Predictions",
       subtitle="Bottom Layer is 2022 Predicted Permit Counts.\nDots are observed 2022 Permit Counts.\n",
       caption = 'Figure 19') +
  mapTheme() +
  plotTheme()

# Comparison of predictions - Bar Plots

rbind(permits_KD_sf, permits_KD_risk_sf) %>%
  st_set_geometry(NULL) %>% 
  na.omit() %>%
  gather(Variable, Value, -label, -Risk_Category) %>%
  group_by(label, Risk_Category) %>%
  summarize(permitCount = sum(Value)) %>%
  ungroup() %>%
  group_by(label) %>%
  mutate(Rate_of_permit_count = permitCount / sum(permitCount)) %>%
  ggplot(aes(Risk_Category,Rate_of_permit_count)) +
  geom_bar(aes(fill=label), position="dodge", stat="identity") +
  scale_fill_viridis_d(option = "plasma",
                       name = 'Model') +
  labs(title = "Risk Prediction vs. Kernel Density",
       subtitle = 'Philadelphia, PA',
       caption = 'Figure 20',
       x = 'Risk Category',
       y = 'Rate of Permit Counts') +
  plotTheme()

8. Risk Map and Results

# Assigning Risk Categories for Predictions

permit_risk_sf <-
  filter(reg.summary, Regression == "Spatial LOGO-CV: Spatial Process") %>%
  mutate(label = "Risk Predictions",
         Risk_Category = ntile(Prediction, 100),
         Risk_Category = case_when(
           Risk_Category > 99 ~ "> 99%",
           Risk_Category >= 98 & Risk_Category <= 99 ~ "98% to 99%",
           Risk_Category >= 90 & Risk_Category <= 97 ~ "90% to 97%",
           Risk_Category >= 70 & Risk_Category <= 89 ~ "70% to 89%",
           Risk_Category >= 50 & Risk_Category <= 69 ~ "50% to 69%",
           Risk_Category >= 30 & Risk_Category <= 49 ~ "30% to 49%",
           Risk_Category >= 1 & Risk_Category <= 29 ~ "1% to 29%",
           TRUE ~ "1% to 29%")) 

permit_risk_sf %>%
  gather(Variable, Value, -label, -Risk_Category, -geometry) %>%
  ggplot() +
    geom_sf(aes(fill = Risk_Category), colour = NA) +
    geom_sf(data=phillyNeighborhoods, fill = "transparent") +
    scale_fill_viridis(discrete = TRUE) +
    labs(title="Risk Predictions",
         subtitle="New Construction Permit Predictions",
         caption="Figure 20") +
    mapTheme()+
    plotTheme()

In the bar chart illustrated in Figure 21, the top neighborhoods at risk of new construction permits and subsequently rapid development are depicted. Notably, many of these neighborhoods are situated in close proximity and can be grouped in clusters:

  • Northern Liberties, Fishtown, Old Kensington, and West Poplar
  • Northern Liberties, Old City, and Washington Square
  • Francisville and West Poplar
  • Logan Square and Rittenhouse

This observation underscores the significance of refining the spatial process in the model, as it becomes evident that the risk of new construction permits is not randomly distributed across space.

# Neighborhoods at highest risk of permitting construction

 highest_risk_nhoods <-
  permit_risk_sf %>%
  dplyr::select(cvID, Risk_Category, countPermits) %>%
  dplyr::filter(., Risk_Category =="> 99%") %>%
  st_drop_geometry() %>%
  group_by(cvID) %>%
  summarise(Predicted_Permits=sum(countPermits)) %>%
  dplyr::filter(., Predicted_Permits>52) %>%
  arrange(.,-Predicted_Permits) %>%
  rename(Neighborhood = cvID)

  ggplot(highest_risk_nhoods) +
  geom_bar(aes(x=reorder(Neighborhood, -Predicted_Permits), Predicted_Permits), fill = "#FE9900", stat = "identity", width=0.5, dodge=0.5) +
  labs(x = "Neighborhood", y="Predicted Permits", 
       title = "Predicted Permit Risk greater than 99%", subtitle = 'Philadelphia, PA', caption="Figure 21") +
    theme(axis.text.x=element_text(size=6, angle = 60, vjust=0.7, hjust=0.7))+
    theme(axis.text.y=element_text(size=6))+
  theme(
    axis.title = element_text(size = 8), 
    plot.title = element_text(size = 9, face= "bold", color = "darkred"),
    plot.subtitle = element_text(size = 7, face= "italic", color = "black"),
    plot.margin = margin(0, 50, 8, 50, "pt")  
  )

9. Conclusion

9.1 Implementation for Use Case

For the purpose of our original use case, this analysis and model has successfully laid the foundation for achieving our vision of a Development Tracking tool to be used by city’s Office of Sustainability.

  • This model accurately predicts the risk of new construction across the city, and has identified neighborhoods at risk of experiencing rapid development. These neighborhood based risk predictions will be useful for sustainability professionals as well as policy-makers to pilot and implement localized and neighborhood-based regulations for construction and green building.

  • The selected LOGO CV spatial process model indicates Mean Absolute Errors of less than ten for all neighborhoods. This means that while the actual value of permits issued may differ, it will differ by a small margin that is likely to be inconsequential for a policymaker. This indicates that the model is still effective at capturing the overall trends of development.

  • The model and subsequently the app illustrate the phenomenon of spatial clustering and spatial autocorrelation clearly. As predictions are made with smaller spatial boundaries in consideration (fishnet cell size of 500), they offer more nuance and accuracy. However they are not presented in isolation disregarding the larger surrounding context; both these scales are presented side by side. Thus, the model showcases likely development trends for specific neighborhoods as well as neighborhood clusters in close proximity.

  • The model also illustrates the errors and contrasts in prediction between White and Non White Neighborhoods. Historically, Non-White Neighborhoods have always been subject to environmental injustice. As climate change increases environmental risk across neighborhoods, Non-White neighborhoods are typically at a disadvantage. In presenting the disparity of prediction based on racial context, the model allows the Office of Sustainability to plan and allocate resources equitably to all neighborhoods.

  • Finally, the model assigns different risk categories based on the rate of permits issued and therefore the risk of development. This will allow the Office of Sustainability to be strategic in creating a roadmap and timeline for implementation, targeting a specific set of neighborhoods at a time based on their risk level for green building regulation and construction pollution mitigation.

9.2 Model Improvements

The present model and its predictive capabilities can be improved in several ways:

  • Data Collection:

  • Additional Spatial Data: The model could explore more granular spatial datasets, including information on neighborhood-level characteristics, zoning regulations, or land use patterns.

  • External Data Sources: For the sake of this analysis, all data has been sourced from the city’s Open Data portal and the American Community Survey five year estimates. Future iterations could look at other external data sources that are available to complement these datasets.

  • Data Wrangling: The model can be improved by establishing a system to impute missing values.

  • Feature Engineering:

  • Temporal Features: The model can be enhanced the model by including time-related features, such as seasonality, trends, or cyclical patterns in construction permit issuance.

  • Interaction Functions: It could benefit from the creation of new interaction variables or functions that test relations between relevant variables to further understand and predict for spatial autocorrelations.

  • Real Time Updates: The model could explore possibilities for integrating real-time data to capture evolving trends and respond to changing conditions in the city.

  • Stakeholder Involvement: Behavior and social patterns that influence development are hard to capture and therefore not adequately integrated in this model. It may be useful to engage with local community stakeholders and city officials to gain insights into factors that may not be captured in the current datasets.

  • Evaluation: The maintenance of this model should involve evaluation of the model’s relevance and effectiveness over time, redefining metrics for measurement of model success as needed.

9.3 Other Areas of Application

While the model has been specifically created with the City of Philadelphia’s Office of Sustainability in mind it can be replicated across different scales and geographies to fulfill different needs and purposes. A few examples are outlined below:

  • Urban Planning: City planners can use the predictions to inform land-use and development policies. Understanding where new construction is likely to occur can aid in creating sustainable and well-balanced urban development plans that consider factors like housing needs, environmental impact, and community well-being.

  • Community Engagement: The predictions can be used to engage with local communities. City officials can communicate upcoming construction projects, involve residents in the planning process, and address concerns related to issues such as gentrification, displacement, and changes in neighborhood character.

  • Infrastructure Planning: Anticipating construction permits can help in planning and expanding city infrastructure. This includes transportation networks, utilities, and other essential services to accommodate the growing needs of the areas expected to experience increased construction.

  • Housing Strategies: For city officials working on housing initiatives, the model can be a valuable tool for predicting demand in specific neighborhoods. This information can guide efforts to create affordable housing, prevent housing shortages, and address housing-related challenges.

  • Risk Management: Understanding the risk of new construction permits can help in developing risk management strategies. This could involve creating policies to handle issues related to housing affordability, displacement, and maintaining the cultural and social fabric of neighborhoods.

It is important to note that while the model provides valuable insights, we recommend that it be used as a complement to local knowledge and expertise. The predictions should be interpreted in the context of broader city sustainability and planning goals, community needs, and ethical considerations to ensure responsible and inclusive decision-making.

LS0tDQp0aXRsZTogIlBoaWxseSBQZXJtaXRzIg0KYXV0aG9yOiAiUm9zaGluaSBHYW5lc2ggJiBTYW1yaWRkaGkgS2hhcmUiDQpkYXRlOiAiYHIgU3lzLkRhdGUoKWAiDQpvdXRwdXQ6DQogIGh0bWxfZG9jdW1lbnQ6DQogICAgdG9jOiB0cnVlDQogICAgdG9jX2Zsb2F0OiB0cnVlDQogICAgY29kZV9mb2xkaW5nOiBoaWRlDQogICAgY29kZV9kb3dubG9hZDogdHJ1ZQ0KICAgIHRoZW1lOiBqb3VybmFsICANCi0tLQ0KDQojIyAxLiBJbnRyb2R1Y3Rpb24NCg0KQ29uc3RydWN0aW9uIGlzIG9uZSBvZiB0aGUgbW9zdCBwb2xsdXRpbmcgaW5kdXN0cmllcyBpbiBQaGlsYWRlbHBoaWEuIFRoZSBPZmZpY2Ugb2YgU3VzdGFpbmFiaWxpdHkgaGFzIGJlZW4gYWN0aXZlbHkgYWRkcmVzc2luZyB0aGlzIGNvbmNlcm4gYnkgY29sbGFib3JhdGluZyB3aXRoIHRoZSBEZXBhcnRtZW50IG9mIExpY2Vuc2VzIGFuZCBJbnNwZWN0aW9ucy4gVG9nZXRoZXIsIHRoZXkgYWltIHRvIGludHJvZHVjZSBzdXN0YWluYWJsZSBwcmFjdGljZXMgYW5kIHJlZHVjZSB0aGUgZW52aXJvbm1lbnRhbCBpbXBhY3Qgb2YgY29uc3RydWN0aW9uIGluIHRoZSBjaXR5Lg0KDQpOZWlnaGJvcmhvb2RzIGluIFBoaWxhZGVscGhpYSBleGhpYml0IHZhcnlpbmcgaGlzdG9yaWNhbCBhbmQgY3VycmVudCBkZXZlbG9wbWVudCB0cmVuZHMuIFJlY29nbml6aW5nIHRoZXNlIGRpZmZlcmVuY2VzIGlzIGNydWNpYWwgZm9yIHByb3Bvc2luZyBjb250ZXh0LWJhc2VkIEdyZWVuIEJ1aWxkaW5nIHJlY29tbWVuZGF0aW9ucyBmb3IgZnV0dXJlIGRldmVsb3BtZW50LiBGb3IgaW5zdGFuY2UsIGFyZWFzIHdpdGggaGlzdG9yaWNhbCBzaWduaWZpY2FuY2UgbWF5IHJlcXVpcmUgcHJlc2VydmF0aW9uLWZvY3VzZWQgZ3JlZW4gZ3VpZGVsaW5lcywgd2hpbGUgcmFwaWRseSBkZXZlbG9waW5nIG5laWdoYm9yaG9vZHMgbWF5IGJlbmVmaXQgZnJvbSBzdHJpY3RlciByZWd1bGF0aW9ucyBmb3IgZ3JlZW4gYnVpbGRpbmcgYW5kIGluY2VudGl2ZXMgZm9yIHN1c3RhaW5hYmxlIHVyYmFuIHBsYW5uaW5nLg0KDQpCeSB1bmRlcnN0YW5kaW5nIHRoZSB1bmlxdWUgY2hhcmFjdGVyaXN0aWNzIG9mIGRpZmZlcmVudCBuZWlnaGJvcmhvb2RzLCB0aGUgT2ZmaWNlIG9mIFN1c3RhaW5hYmlsaXR5IGNhbiBjcmVhdGUgdGFyZ2V0ZWQgYW5kIGVmZmVjdGl2ZSByZWNvbW1lbmRhdGlvbnMgd2hpY2ggdmFyeSBmcm9tIG5laWdoYm9yaG9vZCB0byBuZWlnaGJvcmhvb2QuIFRoaXMgYXBwcm9hY2ggZW5zdXJlcyB0aGF0IHRoZSBwcm9wb3NlZCBncmVlbiBidWlsZGluZyBzdGFuZGFyZHMgbm90IG9ubHkgY29udHJpYnV0ZSB0byBlbnZpcm9ubWVudGFsIGNvbnNlcnZhdGlvbiBidXQgYWxzbyBhbGlnbiB3aXRoIHRoZSBzcGVjaWZpYyBuZWVkcyBhbmQgZGV2ZWxvcG1lbnRhbCBudWFuY2VzIG9mIGVhY2ggY29tbXVuaXR5LiBJbiBkb2luZyBzbywgdGhpcyBhbmFseXNpcyBhaW1zIHRvIGZvc3RlciBhIG1vcmUgc3VzdGFpbmFibGUgYW5kIHJlc2lsaWVudCB1cmJhbiBlbnZpcm9ubWVudCBmb3IgdGhlIHJlc2lkZW50cyBvZiBQaGlsYWRlbHBoaWEuIEFzIHRoZSBmaXJzdCBzdGVwLCB0aGlzIHByb2plY3QgYWltcyB0byB1dGlsaXplIGhpc3RvcmljIGRhdGEgZm9yIGNvbnN0cnVjdGlvbiB0byBwcmVkaWN0IHRoZSBhcmVhcyB3aGVyZSBjb25zdHJ1Y3Rpb24gaXMgbW9zdCBsaWtlbHkgdG8gb2NjdXIuIA0KDQpUaGUgdmlkZW8gcHJlc2VudGF0aW9uIGlsbHVzdHJhdGluZyB0aGUgdXNlIGNhc2UgZm9yIHRoaXMgbW9kZWwgY2FuIGJlIGZvdW5kIFsqKmhlcmUuKipdKGh0dHBzOi8vZHJpdmUuZ29vZ2xlLmNvbS9kcml2ZS9mb2xkZXJzLzF6ZkRHNmgyR1F0WUU2MDQ5Qjc4bEFiQU92SEswY25tRz91c3A9ZHJpdmVfbGluaykNCg0KYGBge3IgS25pdHRpbmcgUGFyYW1ldGVycywgaW5jbHVkZT1GQUxTRX0NCg0KIyBLbml0dGluZyBQYXJhbWV0ZXJzDQoNCiAga25pdHI6Om9wdHNfY2h1bmskc2V0KA0KICAgIGVjaG8gPSBUUlVFLA0KICAgIHdhcm5pbmcgPSBGQUxTRSwNCiAgICBtZXNzYWdlID0gRkFMU0UsDQogICAgb3V0LndpZHRoID0gJzEwMCUnLA0KICAgIGZpZy5yZXRpbmEgPTMNCiAgKQ0KYGBgDQoNCmBgYHtyIFNldCB1cCwgaW5jbHVkZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCg0KIyBTZXQgVXANCiMjIDEuIGxvYWQgTGlicmFyaWVzDQoNCmxpYnJhcnkoc2YpDQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkodGlkeWNlbnN1cykNCmxpYnJhcnkobWFwdmlldykgDQpsaWJyYXJ5KFJDb2xvckJyZXdlcikNCmxpYnJhcnkoc3BkZXApDQpsaWJyYXJ5KGNhcmV0KQ0KbGlicmFyeShja2FucikgDQpsaWJyYXJ5KEZOTikNCmxpYnJhcnkoZ3JpZCkNCmxpYnJhcnkoZ3JpZEV4dHJhKQ0KbGlicmFyeShnZ2NvcnJwbG90KQ0KbGlicmFyeShqdG9vbHMpICAgICANCmxpYnJhcnkoc3RhcmdhemVyKSANCmxpYnJhcnkoYnJvb20pDQpsaWJyYXJ5KHR1ZnRlKQ0KbGlicmFyeShybWFya2Rvd24pDQpsaWJyYXJ5KGthYmxlRXh0cmEpDQpsaWJyYXJ5KFJTb2NyYXRhKQ0KbGlicmFyeSh2aXJpZGlzKQ0KbGlicmFyeShzcGF0c3RhdC5leHBsb3JlKSANCmxpYnJhcnkocmFzdGVyKQ0KbGlicmFyeShrbml0cikNCmxpYnJhcnkocmdkYWwpDQpsaWJyYXJ5KHRtYXApDQpsaWJyYXJ5KGx1YnJpZGF0ZSkNCmxpYnJhcnkocmVtb3RlcykNCg0KDQojIyAyLiBNYXN0ZXIgRnVuY3Rpb25zDQoNCnJvb3QuZGlyID0gImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS91cmJhblNwYXRpYWwvUHVibGljLVBvbGljeS1BbmFseXRpY3MtTGFuZGluZy9tYXN0ZXIvREFUQS8iDQpzb3VyY2UoImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS91cmJhblNwYXRpYWwvUHVibGljLVBvbGljeS1BbmFseXRpY3MtTGFuZGluZy9tYXN0ZXIvZnVuY3Rpb25zLnIiKQ0KDQojIyAzLiBSZWdpc3RlciBBUEkgS2V5IHRvIGJlIHVzZWQgKGlmIG5lZWRlZCkNCg0KY2Vuc3VzX2FwaV9rZXkoJ2JmMmQ1MDc2NTFiNWE2MjFkYmFkZDQ0NTMzZmI0ZjNkZWFhYjI2YmYnLCBvdmVyd3JpdGUgPSBUUlVFKQ0KDQojIyA0LiBJbnZva2UgY29sb3IgcGFsZXR0ZXMgdG8gYmUgdXNlZA0KDQpwYWxldHRlIDwtIGMoJyNmZWVkZGUnLCAnI2ZkYmU4NScsICcjZmQ4ZDNjJywgJyNlNjU1MGQnLCAnI2E2MzYwMycpDQoNCnBhbGV0dGVhIDwtIGMoIiNFQTUyNkYiLCIjRTc2Qjc0IiwiI0Q3QUY3MCIsIiM5MzdENjQiLCIjNTg1QjU2IikNCg0KcGFsZXR0ZWIgPC0gYygiI2Y3YjI2NyIsICIjZjc5ZDY1IiwgIiNmNDg0NWYiLCAiI2YyNzA1OSIsICIjZjI1YzU0IikNCg0KcGFsZXR0ZWMgPC0gYygiI2ZkZTcyNSIsIiM1ZWM5NjIiLCIjMjE5MThjIiwiIzNiNTI4YiIsIiM0NDAxNTQiKQ0KDQpwYWxldHRlZCA8LSBjKCIjMjVDQjEwIiwgIiM1QUI2MEMiLCAiIzhGQTEwOCIsICIjQzQ4QzA0IiwgIiNGQTc4MDAiKQ0KDQpwYWxldHRlZSA8LSBjKCIjZmZkNzAwIiwiI2ZmYjE0ZSIsIiNlYTVmOTQiLCIjOWQwMmQ3IiwiIzAwMDBmZiIpDQoNCnBhbGV0dGVmIDwtIGMoJyNmZGFlNjEnLCcjZDcxOTFjJywnIzJjN2JiNicsJyNmZmZmYmYnLCcjYWJkOWU5JykNCg0KcGFsZXR0ZWcgPC0gYygiIzk4MUZBQyIsIiNDQjBGOEIiLCIjRkYwMDZBIiwiI0ZFNEMzNSIsIiNGRTk5MDAiKQ0KDQpwYWxldHRlaCA8LSBjKCIjOTgxRkFDIiwiI0ZGMDA2QSIsIiNGRTRDMzUiLCIjRkU5OTAwIikNCg0KcGFsZXR0ZWkgPC0gYygiIzk4MUZBQyIsIiNGRjAwNkEiKQ0KDQpgYGANCg0KYGBge3IgRnVuY3Rpb25zLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KDQojIEZ1bmN0aW9ucw0KIyMgMS4gVGhlbWUgRnVuY3Rpb25zDQoNCm1hcFRoZW1lIDwtIGZ1bmN0aW9uKGJhc2Vfc2l6ZSA9IDEyKSB7DQogIHRoZW1lKA0KICAgIHRleHQgPSBlbGVtZW50X3RleHQoIGNvbG9yID0gImJsYWNrIiksDQogICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTUsY29sb3VyID0gImJsYWNrIiksDQogICAgcGxvdC5zdWJ0aXRsZT1lbGVtZW50X3RleHQoZmFjZT0iaXRhbGljIiksDQogICAgcGxvdC5jYXB0aW9uPWVsZW1lbnRfdGV4dChoanVzdD0wKSwNCiAgICBheGlzLnRpY2tzID0gZWxlbWVudF9ibGFuaygpLA0KICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksYXhpcy50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSwNCiAgICBheGlzLnRleHQgPSBlbGVtZW50X2JsYW5rKCksDQogICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF9ibGFuaygpLA0KICAgIGF4aXMudGl0bGUueSA9IGVsZW1lbnRfYmxhbmsoKSwNCiAgICBwYW5lbC5ncmlkLm1pbm9yID0gZWxlbWVudF9ibGFuaygpLA0KICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfcmVjdChjb2xvdXIgPSAiYmxhY2siLCBmaWxsPU5BLCBzaXplPTIpLA0KICAgIHN0cmlwLnRleHQueCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTQpKQ0KfQ0KDQpwbG90VGhlbWUgPC0gZnVuY3Rpb24oYmFzZV9zaXplID0gMTIpIHsNCiAgdGhlbWUoDQogICAgdGV4dCA9IGVsZW1lbnRfdGV4dCggY29sb3IgPSAiYmxhY2siKSwNCiAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGNvbG9yID0gImRhcmtyZWQiLCBzaXplPTE1LCBmYWNlPSJib2xkIiksDQogICAgcGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChmYWNlPSJpdGFsaWMiKSwNCiAgICBwbG90LmNhcHRpb24gPSBlbGVtZW50X3RleHQoaGp1c3Q9MCksDQogICAgYXhpcy50aWNrcyA9IGVsZW1lbnRfYmxhbmsoKSwNCiAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLA0KICAgIHBhbmVsLmdyaWQubWFqb3IgPSBlbGVtZW50X2xpbmUoImdyZXk4MCIsIHNpemUgPSAwLjEpLA0KICAgIHBhbmVsLmdyaWQubWlub3IgPSBlbGVtZW50X2JsYW5rKCksDQogICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9yZWN0KGNvbG91ciA9ICJibGFjayIsIGZpbGw9TkEsIHNpemU9MiksDQogICAgc3RyaXAuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gImdyZXk4MCIsIGNvbG9yID0gIndoaXRlIiksDQogICAgc3RyaXAudGV4dCA9IGVsZW1lbnRfdGV4dChzaXplPTEyKSwNCiAgICBheGlzLnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemU9MTIpLA0KICAgIGF4aXMudGV4dCA9IGVsZW1lbnRfdGV4dChzaXplPTEwKSwNCiAgICBwbG90LmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksDQogICAgbGVnZW5kLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksDQogICAgbGVnZW5kLnRpdGxlID0gZWxlbWVudF90ZXh0KGNvbG91ciA9ICJibGFjayIsIGZhY2UgPSAiaXRhbGljIiksDQogICAgbGVnZW5kLnRleHQgPSBlbGVtZW50X3RleHQoY29sb3VyID0gImJsYWNrIiwgZmFjZSA9ICJpdGFsaWMiKSwNCiAgICBzdHJpcC50ZXh0LnggPSBlbGVtZW50X3RleHQoc2l6ZSA9IDE0KQ0KICApDQp9DQoNCg0KIyMgMi4gTG9hZCBRdWFudGlsZSBicmVhayBmdW5jdGlvbnMNCg0KcUJyIDwtIGZ1bmN0aW9uKGRmLCB2YXJpYWJsZSwgcm5kKSB7DQogIGlmIChtaXNzaW5nKHJuZCkpIHsNCiAgICBhcy5jaGFyYWN0ZXIocXVhbnRpbGUocm91bmQoZGZbW3ZhcmlhYmxlXV0sMCksDQogICAgICAgICAgICAgICAgICAgICAgICAgIGMoLjAxLC4yLC40LC42LC44KSwgbmEucm09VCkpDQogIH0gZWxzZSBpZiAocm5kID09IEZBTFNFIHwgcm5kID09IEYpIHsNCiAgICBhcy5jaGFyYWN0ZXIoZm9ybWF0QyhxdWFudGlsZShkZltbdmFyaWFibGVdXSksIGRpZ2l0cyA9IDMpLA0KICAgICAgICAgICAgICAgICBjKC4wMSwuMiwuNCwuNiwuOCksIG5hLnJtPVQpDQogIH0NCn0NCg0KcTUgPC0gZnVuY3Rpb24odmFyaWFibGUpIHthcy5mYWN0b3IobnRpbGUodmFyaWFibGUsIDUpKX0NCg0KIyMgMy4gTmVhcmVzdCBuZWlnaGJvciBkaXN0YW5jZSBmdW5jdGlvbg0KDQpubl9mdW5jdGlvbiA8LSBmdW5jdGlvbihtZWFzdXJlRnJvbSxtZWFzdXJlVG8saykgew0KICBtZWFzdXJlRnJvbV9NYXRyaXggPC0gYXMubWF0cml4KG1lYXN1cmVGcm9tKQ0KICBtZWFzdXJlVG9fTWF0cml4IDwtIGFzLm1hdHJpeChtZWFzdXJlVG8pDQogIG5uIDwtICAgDQogICAgZ2V0LmtubngobWVhc3VyZVRvLCBtZWFzdXJlRnJvbSwgaykkbm4uZGlzdA0KICBvdXRwdXQgPC0NCiAgICBhcy5kYXRhLmZyYW1lKG5uKSAlPiUNCiAgICByb3duYW1lc190b19jb2x1bW4odmFyID0gInRoaXNQb2ludCIpICU+JQ0KICAgIGdhdGhlcihwb2ludHMsIHBvaW50X2Rpc3RhbmNlLCBWMTpuY29sKC4pKSAlPiUNCiAgICBhcnJhbmdlKGFzLm51bWVyaWModGhpc1BvaW50KSkgJT4lDQogICAgZ3JvdXBfYnkodGhpc1BvaW50KSAlPiUNCiAgICBkcGx5cjo6c3VtbWFyaXplKHBvaW50RGlzdGFuY2UgPSBtZWFuKHBvaW50X2Rpc3RhbmNlKSkgJT4lDQogICAgYXJyYW5nZShhcy5udW1lcmljKHRoaXNQb2ludCkpICU+JSANCiAgICBkcGx5cjo6c2VsZWN0KC10aGlzUG9pbnQpICU+JQ0KICAgIHB1bGwoKSANCiAgDQogIHJldHVybihvdXRwdXQpICANCn0NCg0KYGBgDQoNCiMjIDIuIERhdGEgV3JhbmdsaW5nDQoNCiMjIyAyLjEgRGF0YSBPdmVydmlldyAtIFByaW1hcnkgRGF0YXNldHMNCg0KDQpUaGUgRGV2ZWxvcG1lbnQgVHJhY2tlciBUb29sIHJlbGllcyBvbiBhIHByZWRpY3RpdmUgbW9kZWwgdGhhdCB1dGlsaXplcyBkaXZlcnNlIGRhdGFzZXRzIHRvIGVzdGltYXRlIHRoZSBudW1iZXIgb2YgcmVzaWRlbnRpYWwgYnVpbGRpbmcgcGVybWl0cyB0aHJvdWdob3V0IHRoZSBjaXR5LiBUaGVzZSBkYXRhc2V0cyBlbmNvbXBhc3MgaW5mb3JtYXRpb24gb2J0YWluZWQgZnJvbSBbT3BlbkRhdGFQaGlsbHldKGh0dHBzOi8vb3BlbmRhdGFwaGlsbHkub3JnLyksIHdoaWNoIHNlcnZlcyBhcyB0aGUgQ2l0eSBvZiBQaGlsYWRlbHBoaWHigJlzIGNvbXByZWhlbnNpdmUgY2F0YWxvZyBvZiBhdmFpbGFibGUgcmVnaW9uYWwgZGF0YS4gQWRkaXRpb25hbGx5LCB3ZSBpbmNvcnBvcmF0ZSBkYXRhIGZyb20gdGhlIFUuUy4gQ2Vuc3VzIEJ1cmVhdeKAmXMgMjAyMSA1LXllYXIgW0FtZXJpY2FuIENvbW11bml0eSBTdXJ2ZXldKGh0dHBzOi8vd3d3LmNlbnN1cy5nb3YvcHJvZ3JhbXMtc3VydmV5cy9hY3MpLCBlbmFibGluZyB1cyB0byBnYWluIGluc2lnaHRzIGludG8gcG9wdWxhdGlvbiBjaGFyYWN0ZXJpc3RpY3Mgc3VjaCBhcyBlZHVjYXRpb24gbGV2ZWxzIGFuZCBtZWRpYW4gaW5jb21lLiBUaGUgc3Vic2VxdWVudCBzZWN0aW9ucyBlbGFib3JhdGUgb24gb3VyIG1ldGhvZG9sb2d5IGZvciBjb2xsZWN0aW5nIGFuZCByZWZpbmluZyB0aGVzZSBkYXRhc2V0cy4NCg0KVG8gY3JlYXRlIHRoaXMgbW9kZWwsIGRhdGEgd2FzIGNvbGxlY3RlZCBmcm9tIGEgdmFyaWV0eSBvZiBzb3VyY2VzLiBPdXIgcHJpbWFyeSBkYXRhIHNvdXJjZSB3YXMgVGhlIERlcGFydG1lbnQgb2YgTGljZW5zZXMgJiBJbnNwZWN0aW9ucyBjb2xsZWN0aW9uIG9mIEJ1aWxkaW5nIGFuZCBab25pbmcgUGVybWl0cy4gVGhlIERlcGFydG1lbnQgb2YgTCZJIHJldmlld3MgY29uc3RydWN0aW9uIHBsYW5zIGFuZCBjb25kdWN0cyBidWlsZGluZyBpbnNwZWN0aW9ucyB0byBlbnN1cmUgdGhlIHNhZmV0eSBvZiB0aGUgd29ya2VycyBhbmQgdGhlIHB1YmxpYy4gWm9uaW5nIHBlcm1pdHMgYXJlIGlzc3VlZCB0byBhdXRob3JpemUgbmV3IGNvbnN0cnVjdGlvbiBvciBhZGRpdGlvbnMgdG8gYSBidWlsZGluZyBvciB0byBhdXRob3JpemUgdGhlIGNoYW5nZSBvZiB1c2UgaW4gYSBidWlsZGluZyBvciBncm91bmQuIEJ1aWxkaW5nIHBlcm1pdHMgYXJlIHJlcXVpcmVkIGJlZm9yZSB0aGUgc3RhcnQgb2YgYSBzcGVjaWZpYyBjb25zdHJ1Y3Rpb24gYWN0aXZpdHkgdG8gZW5sYXJnZSwgcmVwYWlyLCBjaGFuZ2UsIGFkZCB0byBvciBkZW1vbGlzaCBhIHN0cnVjdHVyZSwgYW5kIHRvIGluc3RhbGwgZXF1aXBtZW50IG9yIHN5c3RlbXMgaW4gYSBzdHJ1Y3R1cmUuDQoNClRoZSBpc3N1YW5jZSBvZiBwZXJtaXRzIHNlcnZlcyBhcyBhIHZhbHVhYmxlIGluZGljYXRvciBmb3IgcHJlZGljdGluZyBkZXZlbG9wbWVudCBjaGFuZ2VzIGluIGEgY2l0eS4gQnkgY2xvc2VseSBtb25pdG9yaW5nIHRoZSB0eXBlcyBhbmQgdm9sdW1lIG9mIHBlcm1pdHMgZ3JhbnRlZCwgdXJiYW4gcGxhbm5lcnMgYW5kIHBvbGljeW1ha2VycyBnYWluIGluc2lnaHRzIGludG8gdGhlIHRyYWplY3Rvcnkgb2YgY29uc3RydWN0aW9uIGFjdGl2aXRpZXMuIEFuIGluY3JlYXNlIGluIGJ1aWxkaW5nIHBlcm1pdHMgZm9yIHJlc2lkZW50aWFsLCBjb21tZXJjaWFsLCBvciBpbmZyYXN0cnVjdHVyZSBwcm9qZWN0cyBvZnRlbiBmb3Jlc2hhZG93cyBncm93dGggYW5kIGVjb25vbWljIGRldmVsb3BtZW50IGluIGEgcGFydGljdWxhciBhcmVhLiBDb252ZXJzZWx5LCBhIGRlY2xpbmUgaW4gcGVybWl0IGlzc3VhbmNlIG1heSBpbmRpY2F0ZSBhIHNsb3dkb3duIG9yIHNoaWZ0aW5nIHRyZW5kcyBpbiBkZXZlbG9wbWVudC4gDQoNCiMjIyAyLjIgRGF0YSBPdmVydmlldyAtIFNwYXRpYWwgRGF0YXNldHMNCg0KVG8gYmVnaW4gY29uc3RydWN0aW5nIGEgcHJlZGljdGl2ZSBtb2RlbCBmb3IgdGhlIGNvbnN0cnVjdGlvbiBwZXJtaXRzIGlzc3VlZCBpbiBQaGlsYWRlbHBoaWEsIGFkZGl0aW9uYWwgZXh0ZXJuYWwgdmFyaWFibGVzIGFyZSBpbmNvcnBvcmF0ZWQuIFRoZXNlIHZhcmlhYmxlcyBleGFtaW5lIHRoZSBwcm94aW1pdHkgb2YgdmFyaW91cyBhbWVuaXRpZXMgYW5kIHB1YmxpYyBzZXJ2aWNlcyB0byB0aGUgcGVybWl0cyBsaXN0ZWQgaW4gdGhlIGZvdW5kYXRpb25hbCBkYXRhc2V0LiBUaGUgZm9sbG93aW5nIGNvZGUgaW1wb3J0cyBkYXRhIHJlbGF0ZWQgdG8gdGhlc2UgcG90ZW50aWFsbHkgaW5mbHVlbnRpYWwgYW1lbml0aWVzLCBpbmNsdWRpbmcgaW5mb3JtYXRpb24gYWJvdXQgZ3JlZW4gc3BhY2VzIGluIHRoZSBjaXR5LCB0aGUgbG9jYXRpb25zIG9mIHZhY2FudCBsYW5kcyBhbmQgYnVpbGRpbmdzLCBidWlsZGluZyBkZW1vbGl0aW9ucyBhbmQgYWZmb3JkYWJsZSBob3VzaW5nLiBUaGVzZSBkYXRhc2V0cyBhcmUgY29sbGVjdGVkIHRvIGNoZWNrIGlmIHRoZXkgaGF2ZSBhIHBvc2l0aXZlIGluZmx1ZW5jZSBvbiB0aGUgbnVtYmVyIG9mIHBlcm1pdHMgaXNzdWVkLiBBZGRpdGlvbmFsbHksIGluZm9ybWF0aW9uIG9uIHBvdGVudGlhbCBwZXJtaXQgaXNzdWFuY2UgaW5oaWJpdG9ycyBzdWNoIGFzIHNhbml0YXRpb24gYW5kIHNlcnZpY2UgY29tcGxhaW50cyByZWNvcmRlZCB0aHJvdWdoIDMxMSByZXF1ZXN0cyBhcmUgYWxzbyBicm91Z2h0IGluLiBUaGVzZSBkYXRhc2V0cyBhcmUgc291cmNlZCBmcm9tIFtPcGVuIERhdGEgUGhpbGx5Ll0oaHR0cHM6Ly9kYXRhLXBobC5vcGVuZGF0YS5hcmNnaXMuY29tLykNCg0KIyMjIDIuMyBEYXRhIE92ZXJ2aWV3IC0gQ2Vuc3VzIERhdGENCg0KQWZ0ZXIgbG9hZGluZyB0aGUgcHJpbWFyeSBkYXRhc2V0IGFuZCBpbXBvcnRpbmcgZGF0YSBvbiBzdXJyb3VuZGluZyBwaHlzaWNhbCBhbWVuaXRpZXMsIHRoZSBuZXh0IHN0ZXAgaXMgZ2F0aGVyaW5nIHJlbGV2YW50IGNlbnN1cyBkYXRhIHRvIHVuZGVyc3RhbmQgZGVtb2dyYXBoaWMgY29uZGl0aW9ucyBwZXJ0YWluaW5nIHRvIHRoZXNlIGRhdGEgcG9pbnRzLiBUaGlzIGRhdGEgaXMgZmV0Y2hlZCBhbmQgcHJvY2Vzc2VkIGZyb20gdGhlIEFtZXJpY2FuIENvbW11bml0eSBTdXJ2ZXkgKEFDUykgZm9yIFBoaWxhZGVscGhpYSBjZW5zdXMgdHJhY3RzIGluIHRoZSB5ZWFyIDIwMjEuIEl0IHJldHJpZXZlcyB2YXJpb3VzIGRlbW9ncmFwaGljIGFuZCBzb2Npb2Vjb25vbWljIHZhcmlhYmxlcyBmb3IgdGhlc2UgdHJhY3RzIGFuZCBwZXJmb3JtcyBzb21lIGRhdGEgdHJhbnNmb3JtYXRpb25zLiBUaGUgeWVhciBjaG9zZW4gZm9yIGFuYWx5c2lzIGlzIDIwMjEgdG8gZmFjdG9yIHJlY292ZXJ5IGZyb20gQ292aWQgaW4gb3JkZXIgdG8gbWFrZSBtb3JlIGFjY3VyYXRlIHByZWRpY3Rpb25zLiANCg0KQSBsaXN0IG9mIHZhcmlhYmxlcyBpbmNsdWRpbmcgcG9wdWxhdGlvbiwgaW5jb21lLCBob3VzaW5nLXJlbGF0ZWQgaW5mb3JtYXRpb24sIGVkdWNhdGlvbiwgcG92ZXJ0eSBsZXZlbHMsIGFuZCByYWNpYWwvZXRobmljIGRlbW9ncmFwaGljcyBhcmUgZ2F0aGVyZWQgYW5kIHRyYW5zZm9ybWVkIHRvIGJlIHByb2plY3RlZCBpbiB0aGUgRVNSSToxMDI3MjggY29vcmRpbmF0ZSBzeXN0ZW0uIFRoaXMgaW5mb3JtYXRpb24gaXMgc3RvcmVkIGluIGEgbmV3IGRhdGFzZXQgLSAndHJhY3RzMjEnLg0KDQoNCmBgYHtyIEJhc2UgRGF0YSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRSwgcmVzdWx0cz0naGlkZSd9DQoNCiMgQmFzZSBEYXRhDQoNCiMjIDEuIEFsbCBQaGlsYWRlbHBoaWEgUGVybWl0cw0KDQpwaGlsbHlQZXJtaXRzIDwtIA0KICBzdF9yZWFkKCJodHRwczovL3BobC5jYXJ0by5jb20vYXBpL3YyL3NxbD9xPVNFTEVDVCsqK0ZST00rcGVybWl0cytXSEVSRStwZXJtaXRpc3N1ZWRhdGUrPj0rJzIwMTMtMDEtMDEnK0FORCtwZXJtaXRpc3N1ZWRhdGUrPCsnMjAyMi0xMi0zMScmZmlsZW5hbWU9cGVybWl0cyZmb3JtYXQ9Z2VvanNvbiZza2lwZmllbGRzPWNhcnRvZGJfaWQiKSAlPiUgDQogICAgc3RfdHJhbnNmb3JtKCdFU1JJOjEwMjcyOCcpJT4lDQogIG11dGF0ZShMZWdlbmQgPSAiUGhpbGFkZWxwaGlhIFBlcm1pdHMiKSU+JQ0KICBtdXRhdGUoWWVhciA9IGFzLmludGVnZXIoZm9ybWF0KHBlcm1pdGlzc3VlZGF0ZSwgIiVZIikpKSANCg0KIyMgMi4gUGhpbGFkZWxwaGlhIEJvdW5kYXJpZXMNCg0KcGhpbGx5Qm91bmRhcnkgPC0gDQogIHN0X3JlYWQoImh0dHA6Ly9kYXRhLnBobC5vcGVuZGF0YS5hcmNnaXMuY29tL2RhdGFzZXRzLzQwNWVjM2RhOTQyZDRlMjA4NjlkNGUxNDQ5YTJiZTQ4XzAuZ2VvanNvbiIpICU+JQ0KICBzdF90cmFuc2Zvcm0oJ0VTUkk6MTAyNzI4JykgDQoNCiMjIyAyLjEuIEZpc2huZXQgR3JpZCBCYXNlZCBvbiBQaGlsYWRlbHBoaWEgQm91bmRhcmllcw0KDQpwaGlsbHlGaXNobmV0IDwtIA0KICBzdF9tYWtlX2dyaWQocGhpbGx5Qm91bmRhcnksDQogICAgICAgICAgICAgICBjZWxsc2l6ZSA9IDUwMCwgDQogICAgICAgICAgICAgICBzcXVhcmUgPSBUUlVFKSAlPiUNCiAgLltwaGlsbHlCb3VuZGFyeV0gJT4lIA0KICBzdF9zZigpICU+JQ0KICBtdXRhdGUodW5pcXVlSUQgPSByb3duYW1lcyguKSkNCg0KIyMgMy4gUGhpbGFkZWxwaGlhIE5laWdoYm9yaG9vZHMNCg0KcGhpbGx5TmVpZ2hib3Job29kcyA8LQ0KICBzdF9yZWFkKCJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vYXphdmVhL2dlby1kYXRhL21hc3Rlci9OZWlnaGJvcmhvb2RzX1BoaWxhZGVscGhpYS9OZWlnaGJvcmhvb2RzX1BoaWxhZGVscGhpYS5nZW9qc29uIikgJT4lDQogIHN0X3RyYW5zZm9ybSgnRVNSSToxMDI3MjgnKSAlPiUNCiAgc3RfdHJhbnNmb3JtKHN0X2NycyhwaGlsbHlGaXNobmV0KSkgDQoNCiMjIDQuIFZhY2FudCBQcm9wZXJ0eQ0KDQp2YWNhbnRMYW5kIDwtDQogIHN0X3JlYWQoJ2h0dHBzOi8vb3BlbmRhdGEuYXJjZ2lzLmNvbS9kYXRhc2V0cy8xOWMzNWZiMDJkNTQ0YTliYWQwMDMyYjU4MjY4YzlmOV8wLmdlb2pzb24nKSAlPiUgDQogIG5hLm9taXQoKSAlPiUNCiAgc3RfdHJhbnNmb3JtKCdFU1JJOjEwMjcyOCcpICU+JQ0KICBzdF90cmFuc2Zvcm0oc3RfY3JzKHBoaWxseUZpc2huZXQpKSAlPiUNCiAgbXV0YXRlKExlZ2VuZCA9ICJWYWNhbnQgTGFuZCIpIA0KDQp2YWNhbnRCdWlsZGluZyA8LQ0KICBzdF9yZWFkKCdodHRwczovL29wZW5kYXRhLmFyY2dpcy5jb20vZGF0YXNldHMvZjdlZDY4MjkzYzVlNDBkNThmMWRlOWM4NDM1YzNlODRfMC5nZW9qc29uJykgJT4lIA0KICBuYS5vbWl0KCkgJT4lDQogIHN0X3RyYW5zZm9ybSgnRVNSSToxMDI3MjgnKSAlPiUNCiAgc3RfdHJhbnNmb3JtKHN0X2NycyhwaGlsbHlGaXNobmV0KSkgJT4lDQogIG11dGF0ZShMZWdlbmQgPSAiVmFjYW50IEJ1aWxkaW5ncyIpDQoNCiMjIDUuIEFmZm9yZGFibGUgSG91c2luZw0KDQphZmZvcmRhYmxlSG91c2luZyA8LQ0KICBzdF9yZWFkKCdodHRwczovL29wZW5kYXRhLmFyY2dpcy5jb20vZGF0YXNldHMvY2E4OTQ0MTkwYjYwNGIyYWFlN2VmZjE3YzhkZDllZjVfMC5nZW9qc29uJykgJT4lIA0KICBmaWx0ZXIoRklTQ0FMX1lFQVJfQ09NUExFVEUgPD0gIjIwMjIiICYmIEZJU0NBTF9ZRUFSX0NPTVBMRVRFID49ICIyMDEyIikgJT4lDQogIHN0X3RyYW5zZm9ybSgnRVNSSToxMDI3MjgnKSAlPiUNCiAgc3RfdHJhbnNmb3JtKHN0X2NycyhwaGlsbHlGaXNobmV0KSkgJT4lDQogIG11dGF0ZShMZWdlbmQgPSAiQWZmb3JkYWJsZSBIb3VzaW5nIikNCg0KIyMgNi4gQnVzaW5lc3MgTGljZW5zZXMNCg0KYnVzaW5lc3NMaWNlbnNlIDwtDQogIHN0X3JlYWQoJ2h0dHBzOi8vcGhsLmNhcnRvLmNvbS9hcGkvdjIvc3FsP3E9U0VMRUNUKyorRlJPTStidXNpbmVzc19saWNlbnNlcyZmaWxlbmFtZT1idXNpbmVzc19saWNlbnNlcyZmb3JtYXQ9Z2VvanNvbiZza2lwZmllbGRzPWNhcnRvZGJfaWQnKSAlPiUgICBmaWx0ZXIobGljZW5zZXN0YXR1cyA9PSAiQWN0aXZlIiklPiUNCiAgc3RfdHJhbnNmb3JtKCdFU1JJOjEwMjcyOCcpICU+JQ0KICBzdF90cmFuc2Zvcm0oc3RfY3JzKHBoaWxseUZpc2huZXQpKSAlPiUNCiAgbXV0YXRlKExlZ2VuZCA9ICJCdXNpbmVzcyBMaWNlbnNlcyIpDQoNCiMjIDcuIEdyZWVuIFNwYWNlcw0KDQpncmVlblNwYWNlIDwtDQogIHN0X3JlYWQoJ2h0dHBzOi8vb3BlbmRhdGEuYXJjZ2lzLmNvbS9kYXRhc2V0cy9kNTI0NDUxNjBhYjE0MzgwYTY3M2U1ODQ5MjAzZWI2NF8wLmdlb2pzb24nKSAlPiUgDQogIHN0X3RyYW5zZm9ybSgnRVNSSToxMDI3MjgnKSAlPiUNCiAgc3RfdHJhbnNmb3JtKHN0X2NycyhwaGlsbHlGaXNobmV0KSkgJT4lDQogIG11dGF0ZShMZWdlbmQgPSAiR3JlZW4gU3BhY2VzIikNCg0KIyMgOC4gU2FuaXRhdGlvbiBEYXRhDQoNCnNhbml0YXRpb248LQ0KICBzdF9yZWFkKCJodHRwczovL3BobC5jYXJ0by5jb20vYXBpL3YyL3NxbD9xPVNFTEVDVCsqK0ZST00rcHVibGljX2Nhc2VzX2ZjK1dIRVJFK3NlcnZpY2VfbmFtZSs9KydTYW5pdGF0aW9uKy8rRHVtcHN0ZXIrVmlvbGF0aW9uJ09SK3NlcnZpY2VfbmFtZSs9KydJbGxlZ2FsK0R1bXBpbmcnK0FORCtyZXF1ZXN0ZWRfZGF0ZXRpbWUrPj0rJzIwMTMtMDEtMDEnK0FORCtyZXF1ZXN0ZWRfZGF0ZXRpbWUrPCsnMjAyMi0xMi0zMScmZmlsZW5hbWU9c2FuaXRhdGlvbiZmb3JtYXQ9Z2VvanNvbiZza2lwZmllbGRzPWNhcnRvZGJfaWQiKSAlPiUNCiAgICBtdXRhdGUoeWVhciA9IHN1YnN0cihyZXF1ZXN0ZWRfZGF0ZXRpbWUsMSw0KSkgJT4lIA0KICAgIGRwbHlyOjpzZWxlY3QoWSA9IGxhdCwgWCA9IGxvbikgJT4lDQogICAgbmEub21pdCgpICU+JQ0KICAgIHN0X2FzX3NmKGNvb3JkcyA9IGMoIlgiLCAiWSIpLCBjcnMgPSA0MzI2LCBhZ3IgPSAiY29uc3RhbnQiKSAlPiUNCiAgICBzdF90cmFuc2Zvcm0oc3RfY3JzKHBoaWxseUZpc2huZXQpKSAlPiUNCiAgICBtdXRhdGUoTGVnZW5kID0gIlNhbml0YXRpb24iKQ0KDQojIyA5LiBTZXJ2aWNlIERhdGENCg0Kc2VydmljZTwtDQogIHN0X3JlYWQoImh0dHBzOi8vcGhsLmNhcnRvLmNvbS9hcGkvdjIvc3FsP3E9U0VMRUNUKyorRlJPTStwdWJsaWNfY2FzZXNfZmMrV0hFUkUrc2VydmljZV9uYW1lKz0rJ0J1aWxkaW5nK0Rhbmdlcm91cycrQU5EK3JlcXVlc3RlZF9kYXRldGltZSs+PSsnMjAxMy0wMS0wMScrQU5EK3JlcXVlc3RlZF9kYXRldGltZSs8KycyMDIyLTEyLTMxJyZmaWxlbmFtZT1zZXJ2aWNlJmZvcm1hdD1nZW9qc29uJnNraXBmaWVsZHM9Y2FydG9kYl9pZCIpICU+JQ0KICAgIG11dGF0ZSh5ZWFyID0gc3Vic3RyKHJlcXVlc3RlZF9kYXRldGltZSwxLDQpKSAlPiUgDQogICAgZHBseXI6OnNlbGVjdChZID0gbGF0LCBYID0gbG9uKSAlPiUNCiAgICBuYS5vbWl0KCkgJT4lDQogICAgc3RfYXNfc2YoY29vcmRzID0gYygiWCIsICJZIiksIGNycyA9IDQzMjYsIGFnciA9ICJjb25zdGFudCIpICU+JQ0KICAgIHN0X3RyYW5zZm9ybShzdF9jcnMocGhpbGx5RmlzaG5ldCkpICU+JQ0KICAgIG11dGF0ZShMZWdlbmQgPSAiU2VydmljZSIpDQoNCiMjIDEwLiBEZW1vbGl0aW9uIERhdGENCg0KYnVpbGRpbmdEZW1vbGl0aW9uIDwtDQogIHN0X3JlYWQoJ2h0dHBzOi8vcGhsLmNhcnRvLmNvbS9hcGkvdjIvc3FsP3E9U0VMRUNUKyorRlJPTStkZW1vbGl0aW9ucyZmaWxlbmFtZT1kZW1vbGl0aW9ucyZmb3JtYXQ9Z2VvanNvbiZza2lwZmllbGRzPWNhcnRvZGJfaWQnKSAlPiUgDQogIG11dGF0ZSh5ZWFyID0gc3Vic3RyKHN0YXJ0X2RhdGUsMSw0KSkgJT4lDQogIGZpbHRlcih5ZWFyID09ICcyMDIyJykgJT4lDQogIHN0X3RyYW5zZm9ybSgnRVNSSToxMDI3MjgnKSAlPiUNCiAgc3RfdHJhbnNmb3JtKHN0X2NycyhwaGlsbHlGaXNobmV0KSkgJT4lDQogIG11dGF0ZShMZWdlbmQgPSAiQnVpbGRpbmcgRGVtb2xpdGlvbiIpIA0KDQojIyAxMS4gQ2Vuc3VzIGRhdGEgLSBBQ1MgMjAyMQ0KDQp0cmFjdHMyMSA8LSBnZXRfYWNzKA0KICBnZW9ncmFwaHkgPSAidHJhY3QiLA0KICB2YXJpYWJsZXMgPSBjKA0KICAgICJCMDEwMDNfMDAxIiwgICAjIFRvdGFsIFBvcHVsYXRpb24NCiAgICAiQjE5MDEzXzAwMSIsICAgIyBNZWRpYW4gSG91c2Vob2xkIEluY29tZQ0KICAgICJCMjUwMDhfMDAxRSIsICAjIFRvdGFsIFBvcHVsYXRpb24gaW4gSG91c2luZyBVbml0cw0KICAgICJCMjUwMDhfMDAyIiwgICAjIE93bmVyLU9jY3VwaWVkIFVuaXRzDQogICAgIkIyNTAwOF8wMDMiLCAgICMgUmVudGVyLU9jY3VwaWVkIFVuaXRzDQogICAgIkIxNTAwM18wMjIiLCAgICMgRWR1Y2F0aW9uYWwgQXR0YWlubWVudDogQmFjaGVsb3IncyBEZWdyZWUNCiAgICAiQjA2MDEyXzAwMkUiLCAgIyBQb3B1bGF0aW9uIEJlbG93IHRoZSBQb3ZlcnR5IExldmVsDQogICAgIkIwMjAwMV8wMDIiLCAgICMgUmFjZSBhbmQgRXRobmljaXR5OiBXaGl0ZSBBbG9uZQ0KICAgICJCMDIwMDFfMDAzIiwgICAjIFJhY2UgYW5kIEV0aG5pY2l0eTogQmxhY2sgb3IgQWZyaWNhbiBBbWVyaWNhbiBBbG9uZQ0KICAgICJCMjcwMTFfMDA4RSIsICAjIFBvcHVsYXRpb24gVW5lbXBsb3llZA0KICAgICJCMDgxMDVGXzAwN0UiICAjIFBvcHVsYXRpb24gV29ya2luZyBmcm9tIEhvbWUNCiAgICANCiAgKSwNCiAgeWVhciA9IDIwMjEsDQogIHN0YXRlID0gIlBBIiwNCiAgY291bnR5ID0gIlBoaWxhZGVscGhpYSIsDQogIGdlb21ldHJ5ID0gVFJVRSwNCiAgb3V0cHV0ID0gIndpZGUiDQopJT4lDQogIGRwbHlyOjpzZWxlY3QoLU5BTUUsIC1lbmRzX3dpdGgoIk0iKSkgJT4lDQogIHJlbmFtZShUb3RhbFBvcCA9IEIwMTAwM18wMDFFLCAgICAgICAgICAgICAgICAgICAgICAgICAgICMgVG90YWwgUG9wdWxhdGlvbg0KICAgICAgICAgTWVkSEhJbmMgPSBCMTkwMTNfMDAxRSwgICAgICAgICAgICAgICAgICAgICAgICAgICAjIE1lZGlhbiBIb3VzZWhvbGQgSW5jb21lDQogICAgICAgICBUb3RhbFVuaXQgPSBCMjUwMDhfMDAxRSwgICAgICAgICAgICAgICAgICAgICAgICAgICMgVG90YWwgUG9wdWxhdGlvbiBpbiBIb3VzaW5nIFVuaXRzDQogICAgICAgICBPd25lck9jY3VwaWVkID0gQjI1MDA4XzAwMkUsICAgICAgICAgICAgICAgICAgICAgICMgT3duZXItT2NjdXBpZWQgVW5pdHMNCiAgICAgICAgIFJlbnRlck9jY3VwaWVkID0gQjI1MDA4XzAwM0UsICAgICAgICAgICAgICAgICAgICAgIyBSZW50ZXItT2NjdXBpZWQgVW5pdHMNCiAgICAgICAgIEJhY2hlbG9yc0RlZ3JlZSA9IEIxNTAwM18wMjJFLCAgICAgICAgICAgICAgICAgICAgIyBFZHVjYXRpb25hbCBBdHRhaW5tZW50OiBCYWNoZWxvcidzIERlZ3JlZQ0KICAgICAgICAgVG90YWxQb3ZlcnR5ID0gQjA2MDEyXzAwMkUsICAgICAgICAgICAgICAgICAgICAgICAjIFBvcHVsYXRpb24gQmVsb3cgdGhlIFBvdmVydHkgTGV2ZWwNCiAgICAgICAgIFRvdGFsVW5lbXBsb3ltZW50ID0gQjI3MDExXzAwOEUsICAgICAgICAgICAgICAgICAgIyBQb3B1bGF0aW9uIFVuZW1wbG95ZWQNCiAgICAgICAgIFRvdGFsV0ZIID0gQjA4MTA1Rl8wMDdFLCAgICAgICAgICAgICAgICAgICAgICAgICAgIyBQb3B1bGF0aW9uIFdvcmtpbmcgZnJvbSBIb21lDQogICAgICAgICBSYWNlV2hpdGUgPSBCMDIwMDFfMDAyRSwgICAgICAgICAgICAgICAgICAgICAgICAgICMgUmFjZSBhbmQgRXRobmljaXR5OiBXaGl0ZSBBbG9uZQ0KICAgICAgICAgUmFjZUJsYWNrID0gQjAyMDAxXzAwM0UsICAgICAgICAgICAgICAgICAgICAgICAgICAjIFJhY2UgYW5kIEV0aG5pY2l0eTogQmxhY2sgb3IgQWZyaWNhbiBBbWVyaWNhbiBBbG9uZQ0KICAgICAgICAgKQ0KDQojIyMgMTEuMS4gVHJhbnNmb3JtIHRoZSBkYXRhIHRvIEVTUkk6MTAyNzI4IHByb2plY3Rpb24NCg0KdHJhY3RzMjEgPC0gdHJhY3RzMjEgJT4lIHN0X3RyYW5zZm9ybShzdF9jcnMocGhpbGx5RmlzaG5ldCkpDQoNCiMjIyAxMS4yIENyZWF0ZSBuZXcgdmFyaWFibGVzDQoNCnRyYWN0czIxIDwtIHRyYWN0czIxICU+JQ0KICBtdXRhdGUocGN0V2hpdGUgPSBpZmVsc2UoVG90YWxQb3AgPiAwLCBSYWNlV2hpdGUgLyBUb3RhbFBvcCAqIDEwMCwwKSwNCiAgICAgICAgIHBjdEJsYWNrID0gaWZlbHNlKFRvdGFsUG9wID4gMCwgUmFjZUJsYWNrIC8gVG90YWxQb3AgKiAxMDAsMCksDQogICAgICAgICBwY3RQb3ZlcnR5ID0gaWZlbHNlKFRvdGFsUG9wID4gMCwgVG90YWxQb3ZlcnR5IC8gVG90YWxQb3AgKjEwMCwgMCksDQogICAgICAgICBwY3RVbmVtcGxveSA9IGlmZWxzZShUb3RhbFBvcCA+IDAsIFRvdGFsVW5lbXBsb3ltZW50IC8gVG90YWxQb3AgKjEwMCwgMCksDQogICAgICAgICBwY3RXRkggPSBpZmVsc2UoVG90YWxQb3AgPiAwLCBUb3RhbFdGSCAvIFRvdGFsUG9wICoxMDAsIDApLA0KICAgICAgICAgcGN0QmFjaCA9IGlmZWxzZShUb3RhbFBvcCA+IDAsIEJhY2hlbG9yc0RlZ3JlZSAvIFRvdGFsUG9wICoxMDAsIDApLA0KICAgICAgICAgcGN0T3duZXJPY2N1cGllZCA9IGlmZWxzZShUb3RhbFBvcCA+IDAsIE93bmVyT2NjdXBpZWQgLyBUb3RhbFVuaXQgKjEwMCwgMCksDQogICAgICAgICBwY3RSZW50ZXJPY2N1cGllZCA9IGlmZWxzZShUb3RhbFBvcCA+IDAsIFJlbnRlck9jY3VwaWVkIC8gVG90YWxVbml0ICoxMDAsIDApDQogICAgICAgICApICU+JQ0KICBkcGx5cjo6c2VsZWN0KC1SYWNlV2hpdGUsIC1SYWNlQmxhY2ssIC1Ub3RhbFBvdmVydHkgLC1Ub3RhbFVuZW1wbG95bWVudCwtT3duZXJPY2N1cGllZCwgLVJlbnRlck9jY3VwaWVkLCAtVG90YWxVbml0LCAtVG90YWxXRkgsIC1CYWNoZWxvcnNEZWdyZWUsIC1HRU9JRCkgJT4lDQogIHN0X3RyYW5zZm9ybShzdF9jcnMocGhpbGx5RmlzaG5ldCkpIA0KDQojIyMgMTEuMyBPcmdhbml6ZSBpbnRvIGRhdGFzZXRzDQoNCnRyYWN0czIxLk1lZEhISW5jIDwtIHRyYWN0czIxICU+JQ0KICBkcGx5cjo6c2VsZWN0KE1lZEhISW5jKSAlPiUNCiAgcmVuYW1lKExlZ2VuZCA9IE1lZEhISW5jKQ0KDQp0cmFjdHMyMS5wY3RXaGl0ZSA8LSB0cmFjdHMyMSAlPiUNCiAgZHBseXI6OnNlbGVjdChwY3RXaGl0ZSklPiUNCiAgcmVuYW1lKExlZ2VuZCA9IHBjdFdoaXRlKQ0KDQp0cmFjdHMyMS5wY3RCbGFjayA8LSB0cmFjdHMyMSAlPiUNCiAgZHBseXI6OnNlbGVjdChwY3RCbGFjayklPiUNCiAgcmVuYW1lKExlZ2VuZCA9IHBjdEJsYWNrKQ0KDQp0cmFjdHMyMS5wY3RQb3ZlcnR5IDwtIHRyYWN0czIxICU+JQ0KICBkcGx5cjo6c2VsZWN0KHBjdFBvdmVydHkpJT4lDQogIHJlbmFtZShMZWdlbmQgPSBwY3RQb3ZlcnR5KQ0KDQp0cmFjdHMyMS5wY3RVbmVtcGxveSA8LSB0cmFjdHMyMSAlPiUNCiAgZHBseXI6OnNlbGVjdChwY3RVbmVtcGxveSklPiUNCiAgcmVuYW1lKExlZ2VuZCA9IHBjdFVuZW1wbG95KQ0KDQp0cmFjdHMyMS5wY3RCYWNoIDwtIHRyYWN0czIxICU+JQ0KICAgZHBseXI6OnNlbGVjdChwY3RCYWNoKSU+JQ0KICAgcmVuYW1lKExlZ2VuZCA9IHBjdEJhY2gpDQoNCnRyYWN0czIxLnBjdFdGSCA8LSB0cmFjdHMyMSAlPiUNCiAgZHBseXI6OnNlbGVjdChwY3RXRkgpJT4lDQogIHJlbmFtZShMZWdlbmQgPSBwY3RXRkgpDQoNCnRyYWN0czIxLnBjdE93bmVyT2NjdXBpZWQgPC0gdHJhY3RzMjEgJT4lDQogIGRwbHlyOjpzZWxlY3QocGN0T3duZXJPY2N1cGllZCklPiUNCiAgcmVuYW1lKExlZ2VuZCA9IHBjdE93bmVyT2NjdXBpZWQpDQoNCnRyYWN0czIxLnBjdFJlbnRlck9jY3VwaWVkIDwtIHRyYWN0czIxICU+JQ0KICBkcGx5cjo6c2VsZWN0KHBjdFJlbnRlck9jY3VwaWVkKSU+JQ0KICByZW5hbWUoTGVnZW5kID0gcGN0UmVudGVyT2NjdXBpZWQpDQoNCmBgYA0KDQojIyAzLiBFeHBsb3JhdG9yeSBBbmFseXNpcyANCg0KIyMjIDMuMSBEYXRhIENsZWFuaW5nIGFuZCBGZWF0dXJlIEVuZ2luZWVyaW5nDQoNCkFmdGVyIGJyaW5naW5nIGluIHJlbGV2YW50IGRlbW9ncmFwaGljIGFuZCBzcGF0aWFsIHZhcmlhYmxlcyBmb3IgYW5hbHlzaXMsIHRoaXMgbmV4dCBzdGVwIGFpbXMgdG8gY2xlYW4gYW5kIGNhdGVnb3JpemUgdmFyaW91cyBjb2x1bW5zIGluIHRoZSBkYXRhc2V0LiBGb3IgdGhlIHNha2Ugb2YgdGhpcyBzdHVkeSwgb25seSBjb25zdHJ1Y3Rpb24gcGVybWl0cyBhcmUgcmVxdWlyZWQuIFRoaXMgaXMgaXNvbGF0ZWQgZnJvbSB0aGUgbGFyZ2VyIHBlcm1pdHRpbmcgZGF0YXNldC4gQXQgZmlyc3QgZ2xhbmNlLCBpdCB3YXMgb2JzZXJ2ZWQgdGhhdCB0aGVyZSBhcmUgbXVsdGlwbGUgcGVybWl0IHR5cGVzIHRoYXQgY29ycmVzcG9uZCB0byBkZW1vbGl0aW9uLCBjb25zdHJ1Y3Rpb24sIGFuZCB6b25pbmcuIFRoZXNlIGNhdGVnb3JpZXMgYXJlIGdyb3VwZWQgYnkgdGhlaXIgcmVzcGVjdGl2ZSBwZXJtaXQgdHlwZS4gRm9yIGV4YW1wbGUsIGJvdGggJ0J1aWxkaW5nJyBhbmQgJ0JQX05ld2Nuc3QnIHJlZmVyIHRvIHBlcm1pdHRpbmcgZm9yIG5ldyBjb25zdHJ1Y3Rpb247IHRoZXNlIGFyZSBidWNrZXRlZCB0b2dldGhlci4NCg0KVGhlIHBlcm1pdHMgYXJlIHRoZW4gZ3JvdXBlZCBieSB5ZWFyIHRvIGV4YW1pbmUgcGF0dGVybnMgYW5kIHRyZW5kcyBmb3IgcGVybWl0dGluZyBpbiBjb25zdHJ1Y3Rpb24sIGRldmVsb3BtZW50IGFuZCB6b25pbmcgb3ZlciB0aGUgbGFzdCBkZWNhZGUgdG8gdW5kZXJzdGFuZCBob3cgY29uc3RydWN0aW9uIHRyZW5kcyBhcmUgc2ltaWxhciB0byBvciBkaWZmZXJlbnQgZnJvbSB0aGUgb3RoZXIgdHdvIHRyZW5kcy4gDQoNCmBgYHtyIERhdGEgQ2xlYW5pbmcsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0UsIHJlc3VsdHM9J2hpZGUnfQ0KDQojIERhdGEgQ2xlYW5pbmcNCg0KIyMgMS4gU2VsZWN0aW5nIFBlcm1pdHMgcmVsYXRlZCB0byBDb25zdHJ1Y3Rpb24sIERlbW9saXRpb24gYW5kIFpvbmluZw0KDQpwaGlsbHlQZXJtaXRzIDwtIHBoaWxseVBlcm1pdHMlPiUNCiAgZmlsdGVyKHBlcm1pdHR5cGUgPT0gIkJVSUxESU5HIiB8DQogICAgICAgICBwZXJtaXR0eXBlID09ICJCUF9ORVdDTlNUIiB8DQogICAgICAgICBwZXJtaXR0eXBlID09ICJERU1PTElUSU9OIiB8DQogICAgICAgICBwZXJtaXR0eXBlID09ICJCUF9ERU1PIiB8DQogICAgICAgICBwZXJtaXR0eXBlID09ICJaT05JTkciIHwNCiAgICAgICAgIHBlcm1pdHR5cGUgPT0gIlpQX1VTRSIgfA0KICAgICAgICAgcGVybWl0dHlwZSA9PSAiWlBfWk9OSU5HIiB8DQogICAgICAgICBwZXJtaXR0eXBlID09ICJaUF9aT04vVVNFIikNCg0KIyMgMi4gR3JvdXBpbmcgcGVybWl0cyByZWxhdGVkIHRvIENvbnN0cnVjdGlvbiwgRGVtb2xpdGlvbiBhbmQgWm9uaW5nIGJ5IGNhdGVnb3J5DQoNCnBoaWxseVBlcm1pdHMgPC0gcGhpbGx5UGVybWl0cyAlPiUNCiAgbXV0YXRlKG5ld1R5cGUgPSBjYXNlX3doZW4ocGVybWl0dHlwZSA9PSAiQlVJTERJTkciIHwgcGVybWl0dHlwZSA9PSAiQlBfTkVXQ05TVCIgIH4gJ0NPTlNUUlVDVElPTiBQRVJNSVQnLA0KICBwZXJtaXR0eXBlID09ICJERU1PTElUSU9OIiB8IHBlcm1pdHR5cGUgPT0gIkJQX0RFTU8iIH4gJ0RFTU9MSVRJT04gUEVSTUlUJywNCiAgcGVybWl0dHlwZSA9PSAiWk9OSU5HIiB8IHBlcm1pdHR5cGUgPT0gIlpQX1pPTklORyIgfCBwZXJtaXR0eXBlID09ICJaUF9VU0UiIHwgcGVybWl0dHlwZSA9PSAiWlBfWk9OL1VTRSIgfiAnWk9OSU5HIFBFUk1JVCcpKQ0KDQojIyAzLiBTZWxlY3RpbmcgcmVsZXZhbnQgY29sdW1ucyBvbmx5DQoNCnBoaWxseVBlcm1pdHMgPC0gZHBseXI6OnNlbGVjdChwaGlsbHlQZXJtaXRzLCBwZXJtaXRudW1iZXIsIFllYXIsIG5ld1R5cGUsIHR5cGVvZndvcmssIGFwcHJvdmVkc2NvcGVvZndvcmssIGNvbW1lcmNpYWxvcnJlc2lkZW50aWFsLCBjZW5zdXN0cmFjdCwgY291bmNpbF9kaXN0cmljdCwgZ2VvY29kZV94LCBnZW9jb2RlX3ksIGdlb21ldHJ5KQ0KDQojIyA0LiBOZXcgRGF0YXNldHMgYnkgUGVybWl0IFR5cGUNCg0KY29uc3RydWN0aW9uUGVybWl0cyA8LSBwaGlsbHlQZXJtaXRzJT4lDQogIGZpbHRlcihuZXdUeXBlID09ICdDT05TVFJVQ1RJT04gUEVSTUlUJykNCg0KZGVtb2xpdGlvblBlcm1pdHMgPC0gcGhpbGx5UGVybWl0cyU+JQ0KICBmaWx0ZXIobmV3VHlwZSA9PSAnREVNT0xJVElPTiBQRVJNSVQnKQ0KDQp6b25pbmdQZXJtaXRzIDwtIHBoaWxseVBlcm1pdHMlPiUNCiAgZmlsdGVyKG5ld1R5cGUgPT0gJ1pPTklORyBQRVJNSVQnKQ0KDQojIyA1LiBQaGlsYWRlbHBoaWEgUGVybWl0cyBieSB5ZWFyDQoNClBlcm1pdHNfMjAxMyA8LSBwaGlsbHlQZXJtaXRzJT4lDQogIGZpbHRlcihZZWFyID09IDIwMTMpDQoNClBlcm1pdHNfMjAxNCA8LSBwaGlsbHlQZXJtaXRzJT4lDQogIGZpbHRlcihZZWFyID09IDIwMTQpDQoNClBlcm1pdHNfMjAxNSA8LSBwaGlsbHlQZXJtaXRzJT4lDQogIGZpbHRlcihZZWFyID09IDIwMTUpDQoNClBlcm1pdHNfMjAxNiA8LSBwaGlsbHlQZXJtaXRzJT4lDQogIGZpbHRlcihZZWFyID09IDIwMTYpDQoNClBlcm1pdHNfMjAxNyA8LSBwaGlsbHlQZXJtaXRzJT4lDQogIGZpbHRlcihZZWFyID09IDIwMTcpDQoNClBlcm1pdHNfMjAxOCA8LSBwaGlsbHlQZXJtaXRzJT4lDQogIGZpbHRlcihZZWFyID09IDIwMTgpDQoNClBlcm1pdHNfMjAxOSA8LSBwaGlsbHlQZXJtaXRzJT4lDQogIGZpbHRlcihZZWFyID09IDIwMTkpDQoNClBlcm1pdHNfMjAyMCA8LSBwaGlsbHlQZXJtaXRzJT4lDQogIGZpbHRlcihZZWFyID09IDIwMjApDQoNClBlcm1pdHNfMjAyMSA8LSBwaGlsbHlQZXJtaXRzJT4lDQogIGZpbHRlcihZZWFyID09IDIwMjEpDQoNClBlcm1pdHNfMjAyMiA8LSBwaGlsbHlQZXJtaXRzJT4lDQogIGZpbHRlcihZZWFyID09IDIwMjIpDQoNCiMjIDUuIENvbnN0cnVjdGlvbiBQZXJtaXRzIGZvciAyMDIyDQoNCmNvbnN0cnVjdGlvblBlcm1pdHNfMjAyMiA8LSBjb25zdHJ1Y3Rpb25QZXJtaXRzJT4lDQogIGZpbHRlcihZZWFyID09IDIwMjIpDQoNCmBgYA0KDQojIyMgMy4yIERhdGEgVmlzdWFsaXphdGlvbiANCg0KIyMjIyAzLjIuMSBNYXBwaW5nIFBlcm1pdHMgaXNzdWVkIGluIFBoaWxhZGVscGhpYSBiZXR3ZWVuIDIwMTMgYW5kIDIwMjINCg0KVGhlIGZpZ3VyZXMgYmVsb3cgc2hvdyBwZXJtaXQgbG9jYXRpb24gYW5kIGRlbnNpdHkgZm9yIGFsbCBwZXJtaXRzIGlzc3VlZCBvdmVyIHRoZSBsYXN0IGRlY2FkZSB0aGF0IGZhbGwgdW5kZXIgdGhlIG1ham9yIGNhdGVnb3JpZXMgb2YgY29uc3RydWN0aW9uLCBkZW1vbGl0aW9uIGFuZCB6b25pbmcuIEl0IGlzIG9ic2VydmVkIHRoYXQgcGVybWl0cyBhcmUgbW9zdCBkZW5zZSBpbiB0aGUgY2VudGVyIGNpdHkgYXJlYS4NCg0KYGBge3IgTWFwcGluZyBQZXJtaXRzLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBmaWcud2lkdGg9NywgZmlnLmhlaWdodD02fQ0KDQojIE1hcHBpbmcgUGVybWl0cw0KIyMgMS4gQSBtYXAgb2YgYWxsIG1ham9yIHBlcm1pdHMgaXNzdWVkIGJldHdlZW4gMjAxMy0yMDIyLCBQaGlsYWRlbHBoaWENCg0KZ2dwbG90KCkgKw0KICBnZW9tX3NmKGRhdGEgPSBwaGlsbHlCb3VuZGFyeSwgZmlsbCA9ICJncmV5ODkiLCBjb2xvciA9ICJkYXJrZ3JleSIpICsNCiAgZ2VvbV9zZihkYXRhID0gcGhpbGx5UGVybWl0cywgYWVzKGNvbG91ciA9IG5ld1R5cGUpLCBzaXplID0gMC41LCBzaG93LmxlZ2VuZCA9ICJwb2ludCIpICsNCiAgc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXMgPSBwYWxldHRlZikgKw0KICBsYWJzKHRpdGxlPSAiTWFqb3IgUGVybWl0cyBJc3N1ZWQsIDIwMTMtMjIiLA0KICAgICAgIHN1YnRpdGxlID0gJ1BoaWxhZGVscGhpYSwgUEFcbicsDQogICAgICAgY2FwdGlvbiA9ICdGaWd1cmUgMScpICsNCiAgbWFwVGhlbWUoKSArDQogIHBsb3RUaGVtZSgpDQoNCmBgYA0KDQojIyMjIDMuMi4yIE1hcHBpbmcgUGVybWl0cyBpc3N1ZWQgaW4gUGhpbGFkZWxwaGlhIGJ5IFR5cGUNCg0KYGBge3IgTWFwcGluZyBQZXJtaXRzIGJ5IFR5cGUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGZpZy53aWR0aD0xMH0NCg0KIyBNYXBwaW5nIFBlcm1pdHMgYnkgVHlwZQ0KIyMgMS4gQSBtYXAgb2YgbmV3IGNvbnN0cnVjdGlvbiBwZXJtaXRzIGlzc3VlZCBiZXR3ZWVuIDIwMTMtMjAyMiwgUGhpbGFkZWxwaGlhDQoNCmdyaWQuYXJyYW5nZShuY29sPTIsDQogICAgICAgICAgICAgZ2dwbG90KCkgKw0KICAgICAgICAgICAgIGdlb21fc2YoZGF0YSA9IHBoaWxseUJvdW5kYXJ5LCBmaWxsID0gImdyZXk4OSIsIGNvbG9yID0gImRhcmtncmV5IikgKw0KICAgICAgICAgICAgIGdlb21fc2YoZGF0YSA9IGNvbnN0cnVjdGlvblBlcm1pdHMsIGFlcyhjb2xvdXIgPSAiI0ZFOTkwMCIpLCBzaXplPTAuNSwgc2hvdy5sZWdlbmQgPSAicG9pbnQiKSArDQogICAgICAgICAgICAgc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXMgPSAiI0ZFOTkwMCIpICsNCiAgICAgICAgICAgICBsYWJzKHRpdGxlPSAiTmV3IENvbnN0cnVjdGlvbiBQZXJtaXRzIElzc3VlZCwgMjAxMy0yMiIsDQogICAgICAgICAgICAgICAgICBzdWJ0aXRsZSA9ICdQaGlsYWRlbHBoaWEsIFBBXG4nLA0KICAgICAgICAgICAgICAgICAgY2FwdGlvbiA9ICdGaWd1cmUgMi4xJykgKw0KICAgICAgICAgICAgIG1hcFRoZW1lKCkgKw0KICAgICAgICAgICAgIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikgKw0KICAgICAgICAgICAgIHBsb3RUaGVtZSgpLA0KICAgICAgICAgICAgIA0KICAgICAgICAgICAgIGdncGxvdCgpICsgDQogICAgICAgICAgICAgICBnZW9tX3NmKGRhdGEgPSBwaGlsbHlCb3VuZGFyeSwgZmlsbCA9ICJncmV5ODkiLCBjb2xvciA9ICJkYXJrZ3JleSIpICsNCiAgICAgICAgICAgICAgIHN0YXRfZGVuc2l0eTJkKGRhdGEgPSBkYXRhLmZyYW1lKHN0X2Nvb3JkaW5hdGVzKGNvbnN0cnVjdGlvblBlcm1pdHMpKSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhZXMoWCwgWSwgZmlsbCA9IC4ubGV2ZWwuLiwgYWxwaGEgPSAuLmxldmVsLi4pLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2l6ZSA9IDAuMDEsIGJpbnMgPSA0MCwgZ2VvbSA9ICdwb2x5Z29uJykgKw0KICAgICAgICAgICAgICAgc2NhbGVfZmlsbF92aXJpZGlzX2Mob3B0aW9uID0gInBsYXNtYSIpICsNCiAgICAgICAgICAgICAgIHNjYWxlX2FscGhhKHJhbmdlID0gYygwLjAwLCAwLjM1KSwgZ3VpZGUgPSBGQUxTRSkgKw0KICAgICAgICAgICAgICAgbGFicyh0aXRsZSA9ICJEZW5zaXR5IG9mIENvbnN0cnVjdGlvbiBQZXJtaXRzLCAyMDEzLTIyIiwNCiAgICAgICAgICAgICAgICAgICAgc3VidGl0bGUgPSAnUGhpbGFkZWxwaGlhLCBQQVxuJywNCiAgICAgICAgICAgICAgICAgICAgY2FwdGlvbiA9ICdGaWd1cmUgMi4yJykgKw0KICAgICAgICAgICAgICAgbWFwVGhlbWUoKSArIA0KICAgICAgICAgICAgICAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSArDQogICAgICAgICAgICAgICBwbG90VGhlbWUoKSkNCg0KIyMgMi4gQSBtYXAgb2YgZGVtb2xpdGlvbiBwZXJtaXRzIGlzc3VlZCBiZXR3ZWVuIDIwMTMtMjAyMiwgUGhpbGFkZWxwaGlhDQoNCmdyaWQuYXJyYW5nZShuY29sPTIsDQogICAgICAgICAgICAgZ2dwbG90KCkgKw0KICAgICAgICAgICAgIGdlb21fc2YoZGF0YSA9IHBoaWxseUJvdW5kYXJ5LCBmaWxsID0gImdyZXk4OSIsIGNvbG9yID0gImRhcmtncmV5IikgKw0KICAgICAgICAgICAgIGdlb21fc2YoZGF0YSA9IGRlbW9saXRpb25QZXJtaXRzLCBhZXMoY29sb3VyID0gJyNkNzE5MWMnKSwgc2l6ZT0wLjUsIHNob3cubGVnZW5kID0gInBvaW50IikgKw0KICAgICAgICAgICAgIHNjYWxlX2NvbG91cl9tYW51YWwodmFsdWVzID0gJyNkNzE5MWMnKSArDQogICAgICAgICAgICAgbGFicyh0aXRsZT0gIk5ldyBEZW1vbGl0aW9uIFBlcm1pdHMgSXNzdWVkLCAyMDEzLTIyIiwNCiAgICAgICAgICAgICAgICAgIHN1YnRpdGxlID0gJ1BoaWxhZGVscGhpYSwgUEFcbicsDQogICAgICAgICAgICAgICAgICBjYXB0aW9uID0gJ0ZpZ3VyZSAzLjEnKSArDQogICAgICAgICAgICAgbWFwVGhlbWUoKSArDQogICAgICAgICAgICAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSArDQogICAgICAgICAgICAgcGxvdFRoZW1lKCksDQogICAgICAgICAgICAgDQogICAgICAgICAgICAgZ2dwbG90KCkgKyANCiAgICAgICAgICAgICAgIGdlb21fc2YoZGF0YSA9IHBoaWxseUJvdW5kYXJ5LCBmaWxsID0gImdyZXk4OSIsIGNvbG9yID0gImRhcmtncmV5IikgKw0KICAgICAgICAgICAgICAgc3RhdF9kZW5zaXR5MmQoZGF0YSA9IGRhdGEuZnJhbWUoc3RfY29vcmRpbmF0ZXMoZGVtb2xpdGlvblBlcm1pdHMpKSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhZXMoWCwgWSwgZmlsbCA9IC4ubGV2ZWwuLiwgYWxwaGEgPSAuLmxldmVsLi4pLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2l6ZSA9IDAuMDEsIGJpbnMgPSA0MCwgZ2VvbSA9ICdwb2x5Z29uJykgKw0KICAgICAgICAgICAgICAgc2NhbGVfZmlsbF92aXJpZGlzX2Mob3B0aW9uID0gInBsYXNtYSIpICsNCiAgICAgICAgICAgICAgIHNjYWxlX2FscGhhKHJhbmdlID0gYygwLjAwLCAwLjM1KSwgZ3VpZGUgPSBGQUxTRSkgKw0KICAgICAgICAgICAgICAgbGFicyh0aXRsZSA9ICJEZW5zaXR5IG9mIERlbW9saXRpb24gUGVybWl0cywgMjAxMy0yMiIsDQogICAgICAgICAgICAgICAgICAgIHN1YnRpdGxlID0gJ1BoaWxhZGVscGhpYSwgUEFcbicsDQogICAgICAgICAgICAgICAgICAgIGNhcHRpb24gPSAnRmlndXJlIDMuMicpICsNCiAgICAgICAgICAgICAgIG1hcFRoZW1lKCkgKyANCiAgICAgICAgICAgICAgIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikgKw0KICAgICAgICAgICAgICAgcGxvdFRoZW1lKCkpDQoNCiMjIDMuIEEgbWFwIG9mIHpvbmluZyBwZXJtaXRzIGlzc3VlZCBiZXR3ZWVuIDIwMTMtMjAyMiwgUGhpbGFkZWxwaGlhDQoNCmdyaWQuYXJyYW5nZShuY29sPTIsDQogICAgICAgICAgICAgZ2dwbG90KCkgKyANCiAgICAgICAgICAgICBnZW9tX3NmKGRhdGEgPSBwaGlsbHlCb3VuZGFyeSwgZmlsbCA9ICJncmV5ODkiLCBjb2xvciA9ICJkYXJrZ3JleSIpICsNCiAgICAgICAgICAgICBnZW9tX3NmKGRhdGEgPSB6b25pbmdQZXJtaXRzLCBhZXMoY29sb3VyID0gJyMyYzdiYjYnKSwgc2l6ZT0wLjUsIHNob3cubGVnZW5kID0gInBvaW50IikgKw0KICAgICAgICAgICAgIHNjYWxlX2NvbG91cl9tYW51YWwodmFsdWVzID0gJyMyYzdiYjYnKSArDQogICAgICAgICAgICAgbGFicyh0aXRsZT0gIk5ldyBab25pbmcgUGVybWl0cyBJc3N1ZWQsIDIwMTMtMjIiLA0KICAgICAgICAgICAgICAgICAgc3VidGl0bGUgPSAnUGhpbGFkZWxwaGlhLCBQQVxuJywNCiAgICAgICAgICAgICAgICAgIGNhcHRpb24gPSAnRmlndXJlIDMuMScpICsNCiAgICAgICAgICAgICBtYXBUaGVtZSgpICsNCiAgICAgICAgICAgICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpICsNCiAgICAgICAgICAgICBwbG90VGhlbWUoKSwNCiAgICAgICAgICAgICANCiAgICAgICAgICAgICBnZ3Bsb3QoKSArIA0KICAgICAgICAgICAgICAgZ2VvbV9zZihkYXRhID0gcGhpbGx5Qm91bmRhcnksIGZpbGwgPSAiZ3JleTg5IiwgY29sb3IgPSAiZGFya2dyZXkiKSArDQogICAgICAgICAgICAgICBzdGF0X2RlbnNpdHkyZChkYXRhID0gZGF0YS5mcmFtZShzdF9jb29yZGluYXRlcyh6b25pbmdQZXJtaXRzKSksIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYWVzKFgsIFksIGZpbGwgPSAuLmxldmVsLi4sIGFscGhhID0gLi5sZXZlbC4uKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNpemUgPSAwLjAxLCBiaW5zID0gNDAsIGdlb20gPSAncG9seWdvbicpICsNCiAgICAgICAgICAgICAgIHNjYWxlX2ZpbGxfdmlyaWRpc19jKG9wdGlvbiA9ICJwbGFzbWEiKSArDQogICAgICAgICAgICAgICBzY2FsZV9hbHBoYShyYW5nZSA9IGMoMC4wMCwgMC4zNSksIGd1aWRlID0gRkFMU0UpICsNCiAgICAgICAgICAgICAgIGxhYnModGl0bGUgPSAiRGVuc2l0eSBvZiBab25pbmcgUGVybWl0cywgMjAxMy0yMiIsDQogICAgICAgICAgICAgICAgICAgIHN1YnRpdGxlID0gJ1BoaWxhZGVscGhpYSwgUEFcbicsDQogICAgICAgICAgICAgICAgICAgIGNhcHRpb24gPSAnRmlndXJlIDMuMicpICsNCiAgICAgICAgICAgICAgIG1hcFRoZW1lKCkgKyANCiAgICAgICAgICAgICAgIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikgKw0KICAgICAgICAgICAgICAgcGxvdFRoZW1lKCkpDQoNCmBgYA0KDQojIyMjIDMuMi4zIE1hcHBpbmcgUGVybWl0dGluZyBUcmVuZHMgaW4gUGhpbGFkZWxwaGlhIGJ5IFllYXINCg0KVGhlIHBlcm1pdHRpbmcgdHJlbmRzIGZvciB0aGVzZSBtYWpvciBwZXJtaXRzIGFyZSBleGFtaW5lZCBmb3IgdGhlIHllYXJzIGJldHdlZW4gMjAxMyBhbmQgMjAyMi4gSXQgaXMgb2JzZXJ2ZWQgdGhhdCB0aGUgbnVtYmVyIG9mIHBlcm1pdHMgZm9yIHpvbmluZyBhbmQgZGVtb2xpdGlvbiBoYXZlIGRlY2xpbmVkIHNpbmNlIDIwMTkuIEhvd2V2ZXIgdGhlcmUgaXMgYSBjb25zaXN0ZW50IHVwd2FyZCB0cmVuZCBmb3IgdGhlIHBlcm1pdHMgaXNzdWVkIGZvciBjb25zdHJ1Y3Rpb24gd2l0aCBtb3N0IG51bWJlciBvZiBjb25zdHJ1Y3Rpb24gcGVybWl0cyBpc3N1ZWQgaW4gMjAyMi4NCg0KYGBge3IgUGVybWl0cyBieSBZZWFyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBmaWcud2lkdGg9MTQsIGZpZy5oZWlnaHQ9OC41fQ0KDQojIFRyZW5kcyBieSBZZWFyDQojIyAyMDEzDQoNCg0KYSA8LSBnZ3Bsb3QoUGVybWl0c18yMDEzLCBhZXMoeCA9IG5ld1R5cGUsIGZpbGwgPSBuZXdUeXBlKSkgKw0KICBnZW9tX2JhcigpICsNCiAgbGFicyhzdWJ0aXRsZSA9IDIwMTMsIHggPSAiUGVybWl0IFR5cGUiLCB5ID0gIkNvdW50IikgKyANCiAgcGxvdFRoZW1lKCkgKyB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpKSsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSArDQogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGMoIkNPTlNUUlVDVElPTiBQRVJNSVQiID0gIiNGRTk5MDAiLCAiREVNT0xJVElPTiBQRVJNSVQiID0gJyNkNzE5MWMnLCAiWk9OSU5HIFBFUk1JVCIgPSAnIzJjN2JiNicpKQ0KDQojIyAyMDE0DQoNCmIgPC0gZ2dwbG90KFBlcm1pdHNfMjAxNCwgYWVzKHggPSBuZXdUeXBlLCBmaWxsID0gbmV3VHlwZSkpICsNCiAgZ2VvbV9iYXIoKSArDQogIGxhYnMoc3VidGl0bGUgPSAyMDE0LCB4ID0gIlBlcm1pdCBUeXBlIiwgeSA9ICJDb3VudCIpICsgDQogIHBsb3RUaGVtZSgpICsgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSkrDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikgKw0KICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjKCJDT05TVFJVQ1RJT04gUEVSTUlUIiA9ICIjRkU5OTAwIiwgIkRFTU9MSVRJT04gUEVSTUlUIiA9ICcjZDcxOTFjJywgIlpPTklORyBQRVJNSVQiID0gJyMyYzdiYjYnKSkNCg0KIyMgMjAxNQ0KDQpjIDwtIGdncGxvdChQZXJtaXRzXzIwMTUsIGFlcyh4ID0gbmV3VHlwZSwgZmlsbCA9IG5ld1R5cGUpKSArDQogIGdlb21fYmFyKCkgKw0KICBsYWJzKHN1YnRpdGxlID0gMjAxNSwgeCA9ICJQZXJtaXQgVHlwZSIsIHkgPSAiQ291bnQiKSArIA0KICBwbG90VGhlbWUoKSArIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkpKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpICsNCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gYygiQ09OU1RSVUNUSU9OIFBFUk1JVCIgPSAiI0ZFOTkwMCIsICJERU1PTElUSU9OIFBFUk1JVCIgPSAnI2Q3MTkxYycsICJaT05JTkcgUEVSTUlUIiA9ICcjMmM3YmI2JykpDQoNCiMjIDIwMTYNCg0KZCA8LSBnZ3Bsb3QoUGVybWl0c18yMDE2LCBhZXMoeCA9IG5ld1R5cGUsIGZpbGwgPSBuZXdUeXBlKSkgKw0KICBnZW9tX2JhcigpICsNCiAgbGFicyhzdWJ0aXRsZSA9IDIwMTYsIHggPSAiUGVybWl0IFR5cGUiLCB5ID0gIkNvdW50IikgKyANCiAgcGxvdFRoZW1lKCkgKyB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpKSsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSArDQogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGMoIkNPTlNUUlVDVElPTiBQRVJNSVQiID0gIiNGRTk5MDAiLCAiREVNT0xJVElPTiBQRVJNSVQiID0gJyNkNzE5MWMnLCAiWk9OSU5HIFBFUk1JVCIgPSAnIzJjN2JiNicpKQ0KDQojIyAyMDE3DQoNCmUgPC0gZ2dwbG90KFBlcm1pdHNfMjAxNywgYWVzKHggPSBuZXdUeXBlLCBmaWxsID0gbmV3VHlwZSkpICsNCiAgZ2VvbV9iYXIoKSArDQogIGxhYnMoc3VidGl0bGUgPSAyMDE3LCB4ID0gIlBlcm1pdCBUeXBlIiwgeSA9ICJDb3VudCIpICsgDQogIHBsb3RUaGVtZSgpICsgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSkrDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikgKw0KICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjKCJDT05TVFJVQ1RJT04gUEVSTUlUIiA9ICIjRkU5OTAwIiwgIkRFTU9MSVRJT04gUEVSTUlUIiA9ICcjZDcxOTFjJywgIlpPTklORyBQRVJNSVQiID0gJyMyYzdiYjYnKSkNCg0KIyMgMjAxOA0KDQpmIDwtIGdncGxvdChQZXJtaXRzXzIwMTgsIGFlcyh4ID0gbmV3VHlwZSwgZmlsbCA9IG5ld1R5cGUpKSArDQogIGdlb21fYmFyKCkgKw0KICBsYWJzKHN1YnRpdGxlID0gMjAxOCwgeCA9ICJQZXJtaXQgVHlwZSIsIHkgPSAiQ291bnQiKSArIA0KICBwbG90VGhlbWUoKSArIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkpKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpICsNCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gYygiQ09OU1RSVUNUSU9OIFBFUk1JVCIgPSAiI0ZFOTkwMCIsICJERU1PTElUSU9OIFBFUk1JVCIgPSAnI2Q3MTkxYycsICJaT05JTkcgUEVSTUlUIiA9ICcjMmM3YmI2JykpDQoNCiMjIDIwMTkNCg0KZyA8LSBnZ3Bsb3QoUGVybWl0c18yMDE5LCBhZXMoeCA9IG5ld1R5cGUsIGZpbGwgPSBuZXdUeXBlKSkgKw0KICBnZW9tX2JhcigpICsNCiAgbGFicyhzdWJ0aXRsZSA9IDIwMTksIHggPSAiUGVybWl0IFR5cGUiLCB5ID0gIkNvdW50IikgKyANCiAgcGxvdFRoZW1lKCkgKyB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpKSsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSArDQogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGMoIkNPTlNUUlVDVElPTiBQRVJNSVQiID0gIiNGRTk5MDAiLCAiREVNT0xJVElPTiBQRVJNSVQiID0gJyNkNzE5MWMnLCAiWk9OSU5HIFBFUk1JVCIgPSAnIzJjN2JiNicpKQ0KDQojIyAyMDIwDQoNCmggPC0gZ2dwbG90KFBlcm1pdHNfMjAyMCwgYWVzKHggPSBuZXdUeXBlLCBmaWxsID0gbmV3VHlwZSkpICsNCiAgZ2VvbV9iYXIoKSArDQogIGxhYnMoc3VidGl0bGUgPSAyMDIwLCB4ID0gIlBlcm1pdCBUeXBlIiwgeSA9ICJDb3VudCIpICsgDQogIHBsb3RUaGVtZSgpICsgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSkrDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikgKw0KICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjKCJDT05TVFJVQ1RJT04gUEVSTUlUIiA9ICIjRkU5OTAwIiwgIkRFTU9MSVRJT04gUEVSTUlUIiA9ICcjZDcxOTFjJywgIlpPTklORyBQRVJNSVQiID0gJyMyYzdiYjYnKSkNCg0KIyMgMjAyMQ0KDQppIDwtIGdncGxvdChQZXJtaXRzXzIwMjEsIGFlcyh4ID0gbmV3VHlwZSwgZmlsbCA9IG5ld1R5cGUpKSArDQogIGdlb21fYmFyKCkgKw0KICBsYWJzKHN1YnRpdGxlID0gMjAyMSwgeCA9ICJQZXJtaXQgVHlwZSIsIHkgPSAiQ291bnQiKSArIA0KICBwbG90VGhlbWUoKSArIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkpKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpICsNCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gYygiQ09OU1RSVUNUSU9OIFBFUk1JVCIgPSAiI0ZFOTkwMCIsICJERU1PTElUSU9OIFBFUk1JVCIgPSAnI2Q3MTkxYycsICJaT05JTkcgUEVSTUlUIiA9ICcjMmM3YmI2JykpDQoNCiMjIDIwMjINCg0KaiA8LSBnZ3Bsb3QoUGVybWl0c18yMDIyLCBhZXMoeCA9IG5ld1R5cGUsIGZpbGwgPSBuZXdUeXBlKSkgKw0KICBnZW9tX2JhcigpICsNCiAgbGFicyhzdWJ0aXRsZSA9IDIwMjIsIHggPSAiUGVybWl0IFR5cGUiLCB5ID0gIkNvdW50IikgKyANCiAgcGxvdFRoZW1lKCkgKyB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpKSsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSArDQogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGMoIkNPTlNUUlVDVElPTiBQRVJNSVQiID0gIiNGRTk5MDAiLCAiREVNT0xJVElPTiBQRVJNSVQiID0gJyNkNzE5MWMnLCAiWk9OSU5HIFBFUk1JVCIgPSAnIzJjN2JiNicpKQ0KDQpsaWJyYXJ5KHBhdGNod29yaykNCg0KY29tYmluZWRfcGxvdCA8LSBhICsgYiArIGMgKyBkICsgZSArIGYgKyBnICsgaCArIGkgKyBqICsNCiAgcGxvdF9sYXlvdXQobmNvbCA9IDUsIGJ5cm93ID0gVFJVRSkgKyAgDQogIHBsb3RfYW5ub3RhdGlvbih0aXRsZSA9ICJIaXN0b2dyYW1zIG9mIFBlcm1pdHMgSXNzdWVkIGJ5IFR5cGUiLCBjYXB0aW9uID0gJ0ZpZ3VyZSA0JywgdGhlbWUgPSB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGNvbG9yID0gImRhcmtyZWQiLCBzaXplID0gMTUsIGZhY2UgPSAiYm9sZCIpKSkgKyANCiAgcGxvdFRoZW1lKCkgDQoNCmNvbWJpbmVkX3Bsb3QNCg0KYGBgDQoNCiMjIyMgMy4yLjQgTWFwcGluZyBEZW1vZ3JhcGhpYyBWYXJpYWJsZXMNCg0KVGhlIGZvbGxvd2luZyBwbG90cyBleHBsb3JlIHRoZSBnZW9ncmFwaGljYWwgZGlzdHJpYnV0aW9uIG9mIGRlbW9ncmFwaGljIHZhcmlhYmxlcyBpbiByZWxhdGlvbiB0byBjb25zdHJ1Y3Rpb24gcGVybWl0cyBpc3N1ZWQuIEl0IGlzIHNlZW4gdGhhdCBjb25zdHJ1Y3Rpb24gcGVybWl0cyB0ZW5kIHRvIGJlIGNvbmNlbnRyYXRlZCBpbiBhcmVhcyB3aXRoIGhpZ2hlciBtZWRpYW4gaG91c2Vob2xkIGluY29tZSBhbmQgaGlnaGVyIHBlcmNlbnRhZ2VzIG9mIHdoaXRlIHBvcHVsYXRpb24gYW5kIGJhY2hlbG9yJ3MgZGVncmVlIGVkdWNhdGlvbmFsIGF0dGFpbm1lbnQuIA0KDQpgYGB7ciBEZW1vZ3JhcGhpYyBQbG90cywgZmlnLndpZHRoPTEyLCBmaWcuaGVpZ2h0PTE2LCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQ0KDQojIE1hcHBpbmcgRGVtb2dyYXBoaWMgVmFyaWFibGVzIGFyb3VuZCBDb25zdHJ1Y3Rpb24gUGVybWl0cyBJc3N1ZWQNCg0KYm90dG9tQ2FwdGlvbiA8LSB0ZXh0R3JvYigiRmlndXJlIDUiLCBncCA9IGdwYXIoaGp1c3QgPSAwKSkNCg0KY29tbW9uVGl0bGUgPC0gdGV4dEdyb2IoIkRlbW9ncmFwaGljIFZhcmlhYmxlcyBhcm91bmQgQ29uc3RydWN0aW9uIFBlcm1pdHMgSXNzdWVkIiwgZ3AgPSBncGFyKGZvbnRzaXplID0gMTYsIGZvbnRmYWNlID0gImJvbGQiLCBjb2w9ImRhcmtyZWQiLCB2anVzdD0xKSkNCg0KZ3JpZC5hcnJhbmdlKG5jb2w9MiwNCiAgICAgICAgICAgICB0b3AgPSBjb21tb25UaXRsZSwNCiAgICAgICAgICAgICANCiAgZ2dwbG90KCkrDQogICAgZ2VvbV9zZihkYXRhPXRyYWN0czIxLCBhZXMoY29sb3I9TkEsIGZpbGw9VG90YWxQb3ApKSsNCiAgICBzY2FsZV9jb2xvcl92aXJpZGlzKCkrDQogICAgc2NhbGVfZmlsbF92aXJpZGlzKCkrDQogICAgZ2VvbV9zZihkYXRhID0gY29uc3RydWN0aW9uUGVybWl0cywgYWVzKGNvbG9yPSIjRkU5OTAwIikgLA0KICAgICAgICAgIHNob3cubGVnZW5kID0gInBvaW50Iiwgc2l6ZSA9IC4yLCBhbHBoYT0wLjI1KSArDQogICAgc2NhbGVfY29sb3JfaWRlbnRpdHkoKSArDQogICAgbGFicyh0aXRsZT0iVG90YWwgUG9wdWxhdGlvbiBhcm91bmQgUGVybWl0cyBJc3N1ZWQiKSsNCiAgICBtYXBUaGVtZSgpK3RoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSwgbGVnZW5kLnRpdGxlPWVsZW1lbnRfYmxhbmsoKSksDQogIGdncGxvdCgpKw0KICAgIGdlb21fc2YoZGF0YT10cmFjdHMyMSwgYWVzKGNvbG9yPU5BLCBmaWxsPU1lZEhISW5jKSkrDQogICAgc2NhbGVfY29sb3JfdmlyaWRpcygpKw0KICAgIHNjYWxlX2ZpbGxfdmlyaWRpcygpKw0KICAgIGdlb21fc2YoZGF0YSA9IGNvbnN0cnVjdGlvblBlcm1pdHMsIGFlcyhjb2xvcj0iI0ZFOTkwMCIpICwNCiAgICAgICAgICBzaG93LmxlZ2VuZCA9ICJwb2ludCIsIHNpemUgPSAuMSwgYWxwaGE9MC4zKSArDQogICAgc2NhbGVfY29sb3JfaWRlbnRpdHkoKSArDQogICAgbGFicyh0aXRsZT0iTWVkaWFuIEhvdXNlaG9sZCBJbmNvbWUgYXJvdW5kIFBlcm1pdHMgSXNzdWVkIikrDQogICAgbWFwVGhlbWUoKSt0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCksIGxlZ2VuZC50aXRsZT1lbGVtZW50X2JsYW5rKCkpLA0KICBnZ3Bsb3QoKSsNCiAgICBnZW9tX3NmKGRhdGE9dHJhY3RzMjEsIGFlcyhjb2xvcj1OQSwgZmlsbD1wY3RVbmVtcGxveSkpKw0KICAgIHNjYWxlX2NvbG9yX3ZpcmlkaXMoKSsNCiAgICBzY2FsZV9maWxsX3ZpcmlkaXMoKSsNCiAgICBnZW9tX3NmKGRhdGEgPSBjb25zdHJ1Y3Rpb25QZXJtaXRzLCBhZXMoY29sb3I9IiNGRTk5MDAiKSAsDQogICAgICAgICAgc2hvdy5sZWdlbmQgPSAicG9pbnQiLCBzaXplID0gLjEsIGFscGhhPTAuMykgKw0KICAgIHNjYWxlX2NvbG9yX2lkZW50aXR5KCkgKw0KICAgIGxhYnModGl0bGU9IiUgVW5lbXBsb3ltZW50IGFyb3VuZCBQZXJtaXRzIElzc3VlZCIpKw0KICAgIG1hcFRoZW1lKCkrdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApLCBsZWdlbmQudGl0bGU9ZWxlbWVudF9ibGFuaygpKSwNCiAgZ2dwbG90KCkrDQogICAgZ2VvbV9zZihkYXRhPXRyYWN0czIxLCBhZXMoY29sb3I9TkEsIGZpbGw9cGN0QmFjaCkpKw0KICAgIHNjYWxlX2NvbG9yX3ZpcmlkaXMoKSsNCiAgICBzY2FsZV9maWxsX3ZpcmlkaXMoKSsNCiAgICBnZW9tX3NmKGRhdGEgPSBjb25zdHJ1Y3Rpb25QZXJtaXRzLCBhZXMoY29sb3I9IiNGRTk5MDAiKSAsDQogICAgICAgICAgc2hvdy5sZWdlbmQgPSAicG9pbnQiLCBzaXplID0gLjEsIGFscGhhPTAuMykgKw0KICAgIHNjYWxlX2NvbG9yX2lkZW50aXR5KCkgKw0KICAgIGxhYnModGl0bGU9IiUgUG9wdWxhdGlvbiB3aXRoIEJhY2hlbG9yJ3MgRGVncmVlIGFyb3VuZCBQZXJtaXRzIElzc3VlZCIpKw0KICAgIG1hcFRoZW1lKCkrdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApLCBsZWdlbmQudGl0bGU9ZWxlbWVudF9ibGFuaygpKSwNCiAgZ2dwbG90KCkrDQogICAgZ2VvbV9zZihkYXRhPXRyYWN0czIxLCBhZXMoY29sb3I9TkEsIGZpbGw9cGN0V2hpdGUpKSsNCiAgICBzY2FsZV9jb2xvcl92aXJpZGlzKCkrDQogICAgc2NhbGVfZmlsbF92aXJpZGlzKCkrDQogICAgZ2VvbV9zZihkYXRhID0gY29uc3RydWN0aW9uUGVybWl0cywgYWVzKGNvbG9yPSIjRkU5OTAwIikgLA0KICAgICAgICAgIHNob3cubGVnZW5kID0gInBvaW50Iiwgc2l6ZSA9IC4xLCBhbHBoYT0wLjMpICsNCiAgICBzY2FsZV9jb2xvcl9pZGVudGl0eSgpICsNCiAgICBsYWJzKHRpdGxlPSIlIFdoaXRlIFBvcHVsYXRpb24gYXJvdW5kIFBlcm1pdHMgSXNzdWVkIikrDQogICAgbWFwVGhlbWUoKSt0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCksIGxlZ2VuZC50aXRsZT1lbGVtZW50X2JsYW5rKCkpLA0KICBnZ3Bsb3QoKSsNCiAgICBnZW9tX3NmKGRhdGE9dHJhY3RzMjEsIGFlcyhjb2xvcj1OQSwgZmlsbD1wY3RCbGFjaykpKw0KICAgIHNjYWxlX2NvbG9yX3ZpcmlkaXMoKSsNCiAgICBzY2FsZV9maWxsX3ZpcmlkaXMoKSsNCiAgICBnZW9tX3NmKGRhdGEgPSBjb25zdHJ1Y3Rpb25QZXJtaXRzLCBhZXMoY29sb3I9IiNGRTk5MDAiKSAsDQogICAgICAgICAgc2hvdy5sZWdlbmQgPSAicG9pbnQiLCBzaXplID0gLjEsIGFscGhhPTAuMykgKw0KICAgIHNjYWxlX2NvbG9yX2lkZW50aXR5KCkgKw0KICAgIGxhYnModGl0bGU9IiUgQmxhY2sgUG9wdWxhdGlvbiBhcm91bmQgUGVybWl0cyBJc3N1ZWQgXG5cbiIpKw0KICAgIG1hcFRoZW1lKCkrdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApLCBsZWdlbmQudGl0bGU9ZWxlbWVudF9ibGFuaygpKSwgYm90dG9tPWJvdHRvbUNhcHRpb24pDQpgYGANCg0KIyMjIyAzLjIuNSBNYXBwaW5nIFBlcm1pdHMgd2l0aGluIEZpc2huZXQNCg0KSW4gdGhpcyBzdGFnZSBvZiB0aGUgYW5hbHlzaXMsIGEgZ2Vvc3BhdGlhbCBkYXRhc2V0IGlzIGNyZWF0ZWQgdG8gdW5kZXJzdGFuZCB0aGUgc3BhdGlhbCBkaXN0cmlidXRpb24gb2YgZGV2ZWxvcG1lbnQgcmlzayB0aHJvdWdob3V0IFBoaWxhZGVscGhpYS4gVGhpcyBrZXkgZWxlbWVudCBvZiBvdXIgYW5hbHlzaXMgaXMgdGhlIOKAnGZpc2huZXQs4oCdIGEgY29udGludW91cyBncmlkIHBhdHRlcm4gb3ZlcmxheSBpbiB3aGljaCBldmVyeSBwb2x5Z29uIGlzIGNvbm5lY3RlZCBvbiBhbGwgc2lkZXMgdG8gdGhlIG90aGVyIHBvbHlnb24gbmVpZ2hib3JzLiBUaGlzIGFuYWx5c2lzIHVzZXMgYSBmaXNobmV0IHdpdGggNTAweDUwMCBmb290IGdyaWQgY2VsbHMuIFRoZSBmaXNobmV0IGlzIHVzZWQgdG8gdHJlYXQgZGV2ZWxvcG1lbnQgcmlzayAocHJlc2VuY2Ugb2YgbmV3IGNvbnN0cnVjdGlvbiBwZXJtaXRzKSBhcyBhIHBoZW5vbWVub24gdGhhdCB2YXJpZXMgY29udGludW91c2x5IGFjcm9zcyBzcGFjZS4gQWxsIHBvaW50IGxldmVsIGRheWEgY29sbGVjdGVkIGlzIGFnZ3JlZ2F0ZWQgYXQgdGhlIGZpc2huZXQgbGV2ZWwuIA0KDQpUaGUgZmlndXJlIGJlbG93IHNob3dzIHRoZSBjb3VudCBvZiBuZXcgY29uc3RydWN0aW9uIHBlcm1pdHMgYWNyb3NzIHRoZSBmaXNobmV0LiBHcmlkIGNlbGxzIHNob3duIGluIHllbGxvdyBoYXZlIHRoZSBsYXJnZXN0IG51bWJlciBvZiBwZXJtaXRzLiBUaGVzZSBsb2NhdGlvbnMgc2VlbSB0byBiZSBwcmVkb21pbmFudGx5IGNvbmNlbnRyYXRlZCBpbiB0aGUgQ2VudGVyIENpdHkgYXJlYS4NCg0KYGBge3IgRmlzaG5ldCBQZXJtaXQgTWFwcywgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgZmlnLndpZHRoPTcsIGZpZy5oZWlnaHQ9Nn0NCg0KIyBNYXBwaW5nIFBlcm1pdHMgaW4gRmlzaG5ldA0KDQojIyAxLiBDb25zdHJ1Y3Rpb24gcGVybWl0cyBqb2luZWQgdG8gRmlzaG5ldCwgMjAxMy0yMDIyDQoNCmNvbnN0cnVjdGlvblBlcm1pdHMgPC0gc3RfdHJhbnNmb3JtKGNvbnN0cnVjdGlvblBlcm1pdHMsIHN0X2NycyhwaGlsbHlGaXNobmV0KSkNCg0KY29uc3RydWN0aW9uX25ldCA8LSANCiAgZHBseXI6OnNlbGVjdChjb25zdHJ1Y3Rpb25QZXJtaXRzKSAlPiUgDQogIG11dGF0ZShjb3VudFBlcm1pdHMgPSAxKSAlPiUgDQogIGFnZ3JlZ2F0ZSguLCBwaGlsbHlGaXNobmV0LCBzdW0pICU+JQ0KICBtdXRhdGUoY291bnRQZXJtaXRzID0gcmVwbGFjZV9uYShjb3VudFBlcm1pdHMsIDApLA0KICAgICAgICAgdW5pcXVlSUQgPSByb3duYW1lcyguKSwNCiAgICAgICAgIGN2SUQgPSBzYW1wbGUocm91bmQobnJvdyhwaGlsbHlGaXNobmV0KSAvIDI0KSwgDQogICAgICAgICAgICAgICAgICAgICAgIHNpemU9bnJvdyhwaGlsbHlGaXNobmV0KSwgcmVwbGFjZSA9IFRSVUUpKQ0KDQoNCiMjIDIuIFZpc3VhbGl6aW5nIHBlcm1pdHMgam9pbmVkIHRvIEZpc2huZXQsIDIwMTMtMjAyMg0KDQpnZ3Bsb3QoKSArDQogIGdlb21fc2YoZGF0YSA9IGNvbnN0cnVjdGlvbl9uZXQsIGFlcyhmaWxsID0gY291bnRQZXJtaXRzKSwgY29sb3IgPSBOQSkgKw0KICBnZW9tX3NmKGRhdGEgPSBzdF9ib3VuZGFyeShwaGlsbHlGaXNobmV0KSwgY29sb3IgPSAiZGFya3JlZCIsIGx3ZCA9IC4wNCwgYWxwaGE9LjIpICsgIyBBZGQgYm91bmRhcmllcyBpbiByZWQNCiAgc2NhbGVfZmlsbF92aXJpZGlzX2Mob3B0aW9uID0gInBsYXNtYSIsDQogICAgICAgICAgICAgICAgICAgICAgIG5hbWUgPSAnQ29uc3RydWN0aW9uIENvdW50cycpICsNCiAgbGFicyh0aXRsZSA9ICJDb25zdHJ1Y3Rpb24gUGVybWl0cyBKb2luZWQgdG8gRmlzaG5ldCIsDQogICAgICAgc3VidGl0bGUgPSAnUGhpbGFkZWxwaGlhLCBQQVxuJywNCiAgICAgICBjYXB0aW9uID0gJ0ZpZ3VyZSA2JykgKw0KICBtYXBUaGVtZSgpICsNCiAgcGxvdFRoZW1lKCkNCg0KYGBgDQoNCiMjIDQuIFNwYXRpYWwgUHJvY2Vzc2VzDQoNClRvIHByZWRpY3QgY29uc3RydWN0aW9uIHBlcm1pdHMgYWNyb3NzIHNwYWNlLCBpdCBpcyBpbXBvcnRhbnQgdG8gYWNjb3VudCBmb3IgdGhlIHNwYXRpYWwgcHJvY2VzcyBpbnZvbHZlZCBpbiB0aGUgY2x1c3RlcmluZyBvZiBjb25zdHJ1Y3Rpb24gcGVybWl0cy4gVGhpcyBzZWN0aW9uIGV4cGxhaW5zIHRoZSBmZWF0dXJlcyBjcmVhdGVkIHRvIGFzc2VzcyBzcGF0aWFsIHBhdHRlcm5zIGFuZCBjbHVzdGVyaW5nLg0KDQojIyMgNC4xIERpc3RyaWJ1dGlvbiBvZiBQb3RlbnRpYWwgSW5mbHVlbmNpbmcgRmFjdG9ycyB3aXRoaW4gRmlzaG5ldA0KDQpUaGUgc3BhdGlhbCBkYXRhIGNvbGxlY3RlZCBpcyBqb2luZWQgdG8gdGhlIGZpc2huZXQgdG8gdmlzdWFsaXplIHRoZSBmb290cHJpbnQgb2YgdGhlc2UgZGlmZmVyZW50IHBhcmFtZXRlcnMgYWNyb3NzIHRoZSBjaXR5IGJ5IGNvdW50aW5nIHRoZSBudW1iZXIgb2Ygb2NjdXJyZW5jZXMgcGVyIGdyaWQgY2VsbCBiZWZvcmUgdGhleSBhcmUgZW5naW5lZXJlZCBmdXJ0aGVyLiBGaWd1cmUgNyBkaXNwbGF5cyBhIHNlbGVjdGlvbiBvZiB0aGVzZSBzcGF0aWFsIHJpc2sgZmFjdG9ycyBtYXBwZWQgdG8gdGhlIGZpc2huZXQuDQoNCmBgYHtyIEZpc2huZXQgRmFjdG9ycywgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgZmlnLndpZHRoPTksIGZpZy5oZWlnaHQ9MTR9DQoNCiMgQXR0YWNoaW5nIGRhdGFzZXRzIG9uIHNwYXRpYWwgZmFjdG9ycyB0byBGaXNobmV0DQoNCiMjIDEuIEV4dHJhY3RpbmcgZ2VvbWV0cnkgZm9yIHNwYXRpYWwgZmFjdG9ycw0KDQpzZXJ2aWNlcyA8LSBzZXJ2aWNlICU+JQ0KICBkcGx5cjo6c2VsZWN0KGdlb21ldHJ5LCBMZWdlbmQpDQoNCmFmZm9yZGFibGVIb3VzaW5ncyA8LSBhZmZvcmRhYmxlSG91c2luZyAlPiUNCiAgZHBseXI6OnNlbGVjdChnZW9tZXRyeSwgTGVnZW5kKQ0KDQp2YWNhbnRCdWlsZGluZ3MgPC0gdmFjYW50QnVpbGRpbmcgJT4lDQogIGRwbHlyOjpzZWxlY3QoZ2VvbWV0cnksIExlZ2VuZCkNCg0KdmFjYW50TGFuZHMgPC0gdmFjYW50TGFuZCAlPiUNCiAgZHBseXI6OnNlbGVjdChnZW9tZXRyeSwgTGVnZW5kKQ0KDQpidXNpbmVzc0xpY2Vuc2VzIDwtIGJ1c2luZXNzTGljZW5zZSAlPiUNCiAgZHBseXI6OnNlbGVjdChnZW9tZXRyeSwgTGVnZW5kKQ0KDQpzYW5pdGF0aW9ucyA8LSBzYW5pdGF0aW9uICU+JQ0KICBkcGx5cjo6c2VsZWN0KGdlb21ldHJ5LCBMZWdlbmQpDQoNCmdyZWVuU3BhY2VzIDwtIGdyZWVuU3BhY2UgJT4lDQogIGRwbHlyOjpzZWxlY3QoZ2VvbWV0cnksIExlZ2VuZCkNCg0KYnVpbGRpbmdEZW1vbGl0aW9ucyA8LSBidWlsZGluZ0RlbW9saXRpb24gJT4lDQogIGRwbHlyOjpzZWxlY3QoZ2VvbWV0cnksIExlZ2VuZCkNCg0KDQojIyAyLiBDcmVhdGluZyBmaXNobmV0IG9mIHNwYXRpYWwgZmFjdG9yIHZhcmlhYmxlcyANCg0KdmFyc19uZXQgPC0gDQogIHJiaW5kKGFmZm9yZGFibGVIb3VzaW5ncywgdmFjYW50QnVpbGRpbmdzLCB2YWNhbnRMYW5kcywgYnVzaW5lc3NMaWNlbnNlcywgDQogICAgICAgIGdyZWVuU3BhY2VzLCBzZXJ2aWNlcywgc2FuaXRhdGlvbnMsIGJ1aWxkaW5nRGVtb2xpdGlvbnMpICU+JQ0KICBzdF9qb2luKC4sIHBoaWxseUZpc2huZXQsIGpvaW49c3Rfd2l0aGluKSAlPiUNCiAgc3RfZHJvcF9nZW9tZXRyeSgpICU+JQ0KICBncm91cF9ieSh1bmlxdWVJRCwgTGVnZW5kKSAlPiUNCiAgc3VtbWFyaXplKGNvdW50ID0gbigpKSAlPiUNCiAgZnVsbF9qb2luKHBoaWxseUZpc2huZXQsIGJ5ID0gInVuaXF1ZUlEIikgJT4lDQogIHNwcmVhZChMZWdlbmQsIGNvdW50LCBmaWxsPTApICU+JQ0KICBzdF9zZigpICU+JQ0KICBuYS5vbWl0KCkgJT4lIA0KICBkcGx5cjo6c2VsZWN0KC1gPE5BPmApICU+JQ0KICB1bmdyb3VwKCkNCg0KIyMgMy4gTWFwcGluZyBzcGF0aWFsIGZhY3RvciB2YXJpYWJsZXMgYnkgY291bnQgaW4gZmlzaG5ldA0KDQp2YXJzX25ldC5sb25nIDwtIA0KICBnYXRoZXIodmFyc19uZXQsIFZhcmlhYmxlLCB2YWx1ZSwgLWdlb21ldHJ5LCAtdW5pcXVlSUQpDQoNCnZhcnMgPC0gdW5pcXVlKHZhcnNfbmV0LmxvbmckVmFyaWFibGUpDQptYXBMaXN0IDwtIGxpc3QoKQ0KDQpmb3IgKGkgaW4gdmFycykgew0KICBtYXBMaXN0W1tpXV0gPC0gDQogICAgZ2dwbG90KCkgKw0KICAgIGdlb21fc2YoZGF0YSA9IGZpbHRlcih2YXJzX25ldC5sb25nLCBWYXJpYWJsZSA9PSBpKSwgYWVzKGZpbGwgPSB2YWx1ZSksIGNvbG91ciA9IE5BKSArDQogICAgc2NhbGVfZmlsbF92aXJpZGlzKG9wdGlvbiA9ICJwbGFzbWEiLCBuYW1lID0gIiAiKSArDQogICAgbGFicyh0aXRsZSA9IGkpICsNCiAgICBtYXBUaGVtZSgpKw0KICAgIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwLCBjb2xvciA9ICJibGFjayIpKQ0KfQ0KDQpib3R0b21DYXB0aW9uIDwtIHRleHRHcm9iKCJGaWd1cmUgNyIsIGdwID0gZ3BhcihoanVzdCA9IDApKQ0KDQpncmlkLmFycmFuZ2UoZ3JvYnMgPSBtYXBMaXN0LCBuY29sID0gMiwgdG9wID0gdGV4dEdyb2IoIlBvdGVudGlhbCBJbmZsdWVuY2luZyBTcGF0aWFsIEZhY3RvcnMgYnkgRmlzaG5ldFxuIiwgZ3AgPSBncGFyKGZvbnRzaXplID0gMTUsIGZvbnRmYWNlID0gImJvbGQiLCBjb2w9ImRhcmtyZWQiKSksIGJvdHRvbSA9IGJvdHRvbUNhcHRpb24pDQoNCmBgYA0KDQojIyMgNC4yIE5lYXJlc3QgTmVpZ2hib3IgRmFjdG9ycyBieSBGaXNobmV0DQoNCk5leHQgYSBuZWFyZXN0IG5laWdoYm9yIGZlYXR1cmUgaXMgY3JlYXRlZCBmb3IgZWFjaCBwb2ludC1sZXZlbCByaXNrIGZhY3RvciBieSBjb252ZXJ0aW5nIHRoZSBncmlkIGNlbGxzIHRvIGNlbnRyb2lkIHBvaW50cyBhbmQgdGhlbiBtZWFzdXJpbmcgdGhlIGRpc3RhbmNlIHRvIHRoZSBuZWFyZXN0IHJpc2sgZmFjdG9yIHBvaW50LiBUaGlzIGJldHRlciBhY2NvdW50cyBmb3IgdGhlIHJlbGF0aXZlIGRpc3RhbmNlIHRvIG91ciBwb2ludCByaXNrIGZhY3RvcnMgY29tcGFyZWQgdG8gdGhlIGFsdGVybmF0aXZlIG9mIHNpbXBseSBzdW1taW5nIHRoZSBmYWN0b3JzIGJ5IGdyaWQgY2VsbC4gVGhlIG91dGNvbWUgb2YgdGhpcyBlbmdpbmVlcmluZyBpcyB2aXN1YWxpemVkIGluIEZpZ3VyZSA4IGJlbG93LiANCg0KYGBge3IgTmVhcmVzdCBOZWlnaGJvcnMsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGZpZy53aWR0aD0xMCwgZmlnLmhlaWdodD0xNn0NCg0KIyAxLiBDYWxjdWxhdGluZyBOZWFyZXN0IE5laWdoYm9ycw0KDQoNCiMjIDEuMS4gVmFjYW50IExhbmQNCg0KIyMjIE1hcHBpbmcgbmVhcmVzdCBmZWF0dXJlDQoNCm5lYXJlc3RfdmFjYW50TGFuZCA8LSBzZjo6c3RfbmVhcmVzdF9mZWF0dXJlKHZhcnNfbmV0LCB2YWNhbnRMYW5kKQ0KDQojIyMgQ29udmVydGluZyB0byByc2dlbyBnZW9tZXRyaWVzDQoNCnggPC0gcnNnZW86OmFzX3JzZ2VvKHZhcnNfbmV0KQ0KeSA8LSByc2dlbzo6YXNfcnNnZW8odmFjYW50TGFuZCkNCg0KIyMjIENhbGN1bGF0aW5nIGRpc3RhbmNlDQoNCnZhcnNfbmV0JGRpc3RfdmFjYW50TGFuZCA8LSByc2dlbzo6ZGlzdGFuY2VfZXVjbGlkZWFuX3BhaXJ3aXNlKHgsIHlbbmVhcmVzdF92YWNhbnRMYW5kXSkNCg0KDQojIyAxLjIuIFZhY2FudCBCdWlsZGluZ3MNCg0KIyMjIE1hcHBpbmcgbmVhcmVzdCBmZWF0dXJlDQoNCm5lYXJlc3RfdmFjYW50QnVpbGRpbmcgPC0gc2Y6OnN0X25lYXJlc3RfZmVhdHVyZSh2YXJzX25ldCwgdmFjYW50QnVpbGRpbmcpDQoNCiMjIyBDb252ZXJ0aW5nIHRvIHJzZ2VvIGdlb21ldHJpZXMNCg0KeCA8LSByc2dlbzo6YXNfcnNnZW8odmFyc19uZXQpDQp5IDwtIHJzZ2VvOjphc19yc2dlbyh2YWNhbnRCdWlsZGluZykNCg0KIyMjIENhbGN1bGF0aW5nIGRpc3RhbmNlDQoNCnZhcnNfbmV0JGRpc3RfdmFjYW50QnVpbGRpbmcgPC0gcnNnZW86OmRpc3RhbmNlX2V1Y2xpZGVhbl9wYWlyd2lzZSh4LCB5W25lYXJlc3RfdmFjYW50QnVpbGRpbmddKQ0KDQoNCiMjIDEuMy4gQWZmb3JkYWJsZSBIb3VzaW5nDQoNCiMjIyBNYXBwaW5nIG5lYXJlc3QgZmVhdHVyZQ0KDQpuZWFyZXN0X2FmZm9yZGFibGVIb3VzaW5nIDwtIHNmOjpzdF9uZWFyZXN0X2ZlYXR1cmUodmFyc19uZXQsIGFmZm9yZGFibGVIb3VzaW5nKQ0KDQojIyMgQ29udmVydGluZyB0byByc2dlbyBnZW9tZXRyaWVzDQoNCnggPC0gcnNnZW86OmFzX3JzZ2VvKHZhcnNfbmV0KQ0KeSA8LSByc2dlbzo6YXNfcnNnZW8oYWZmb3JkYWJsZUhvdXNpbmcpDQoNCiMjIyBDYWxjdWxhdGluZyBkaXN0YW5jZQ0KDQp2YXJzX25ldCRkaXN0X2FmZm9yZGFibGVIb3VzaW5nIDwtIHJzZ2VvOjpkaXN0YW5jZV9ldWNsaWRlYW5fcGFpcndpc2UoeCwgeVtuZWFyZXN0X2FmZm9yZGFibGVIb3VzaW5nXSkNCg0KDQojIyAxLjQuIEdyZWVuIFNwYWNlcw0KDQojIyMgTWFwcGluZyBuZWFyZXN0IGZlYXR1cmUNCg0KbmVhcmVzdF9ncmVlblNwYWNlIDwtIHNmOjpzdF9uZWFyZXN0X2ZlYXR1cmUodmFyc19uZXQsIGdyZWVuU3BhY2UpDQoNCiMjIyBDb252ZXJ0aW5nIHRvIHJzZ2VvIGdlb21ldHJpZXMNCg0KeCA8LSByc2dlbzo6YXNfcnNnZW8odmFyc19uZXQpDQp5IDwtIHJzZ2VvOjphc19yc2dlbyhncmVlblNwYWNlKQ0KDQojIyMgQ2FsY3VsYXRpbmcgZGlzdGFuY2UNCg0KdmFyc19uZXQkZGlzdF9ncmVlblNwYWNlIDwtIHJzZ2VvOjpkaXN0YW5jZV9ldWNsaWRlYW5fcGFpcndpc2UoeCwgeVtuZWFyZXN0X2dyZWVuU3BhY2VdKQ0KDQoNCiMjIDEuNS4gQnVzaW5lc3Nlcw0KDQojIyMgTWFwcGluZyBuZWFyZXN0IGZlYXR1cmUNCg0KbmVhcmVzdF9idXNpbmVzc0xpY2Vuc2UgPC0gc2Y6OnN0X25lYXJlc3RfZmVhdHVyZSh2YXJzX25ldCwgYnVzaW5lc3NMaWNlbnNlKQ0KDQojIyMgQ29udmVydGluZyB0byByc2dlbyBnZW9tZXRyaWVzDQoNCnggPC0gcnNnZW86OmFzX3JzZ2VvKHZhcnNfbmV0KQ0KeSA8LSByc2dlbzo6YXNfcnNnZW8oYnVzaW5lc3NMaWNlbnNlKQ0KDQojIyMgQ2FsY3VsYXRpbmcgZGlzdGFuY2UNCg0KdmFyc19uZXQkZGlzdF9idXNpbmVzc0xpY2Vuc2UgPC0gcnNnZW86OmRpc3RhbmNlX2V1Y2xpZGVhbl9wYWlyd2lzZSh4LCB5W25lYXJlc3RfYnVzaW5lc3NMaWNlbnNlXSkNCg0KDQojIyAxLjYuIFNhbml0YXRpb24gVmlvbGF0aW9uIFJlcG9ydHMNCg0KIyMjIE1hcHBpbmcgbmVhcmVzdCBmZWF0dXJlDQoNCm5lYXJlc3Rfc2FuaXRhdGlvbiA8LSBzZjo6c3RfbmVhcmVzdF9mZWF0dXJlKHZhcnNfbmV0LCBzYW5pdGF0aW9uKQ0KDQojIyMgQ29udmVydGluZyB0byByc2dlbyBnZW9tZXRyaWVzDQoNCnggPC0gcnNnZW86OmFzX3JzZ2VvKHZhcnNfbmV0KQ0KeSA8LSByc2dlbzo6YXNfcnNnZW8oc2FuaXRhdGlvbikNCg0KIyMjIENhbGN1bGF0aW5nIGRpc3RhbmNlDQoNCnZhcnNfbmV0JGRpc3Rfc2FuaXRhdGlvbiA8LSByc2dlbzo6ZGlzdGFuY2VfZXVjbGlkZWFuX3BhaXJ3aXNlKHgsIHlbbmVhcmVzdF9zYW5pdGF0aW9uXSkNCg0KDQojIyAxLjcuIFNlcnZpY2UgUmVxdWVzdHMNCg0KIyMjIE1hcHBpbmcgbmVhcmVzdCBmZWF0dXJlDQoNCm5lYXJlc3Rfc2VydmljZSA8LSBzZjo6c3RfbmVhcmVzdF9mZWF0dXJlKHZhcnNfbmV0LCBzZXJ2aWNlKQ0KDQojIyMgQ29udmVydGluZyB0byByc2dlbyBnZW9tZXRyaWVzDQoNCnggPC0gcnNnZW86OmFzX3JzZ2VvKHZhcnNfbmV0KQ0KeSA8LSByc2dlbzo6YXNfcnNnZW8oc2VydmljZSkNCg0KIyMjIENhbGN1bGF0aW5nIGRpc3RhbmNlDQoNCnZhcnNfbmV0JGRpc3Rfc2VydmljZSA8LSByc2dlbzo6ZGlzdGFuY2VfZXVjbGlkZWFuX3BhaXJ3aXNlKHgsIHlbbmVhcmVzdF9zZXJ2aWNlXSkNCg0KDQojIyAxLjguIEJ1aWxkaW5nIERlbW9saXRpb25zDQoNCiMjIyBNYXBwaW5nIG5lYXJlc3QgZmVhdHVyZQ0KDQpuZWFyZXN0X2J1aWxkaW5nRGVtb2xpdGlvbiA8LSBzZjo6c3RfbmVhcmVzdF9mZWF0dXJlKHZhcnNfbmV0LCBidWlsZGluZ0RlbW9saXRpb24pDQoNCiMjIyBDb252ZXJ0aW5nIHRvIHJzZ2VvIGdlb21ldHJpZXMNCg0KeCA8LSByc2dlbzo6YXNfcnNnZW8odmFyc19uZXQpDQp5IDwtIHJzZ2VvOjphc19yc2dlbyhidWlsZGluZ0RlbW9saXRpb24pDQoNCiMjIyBDYWxjdWxhdGluZyBkaXN0YW5jZQ0KDQp2YXJzX25ldCRkaXN0X2J1aWxkaW5nRGVtb2xpdGlvbiA8LSByc2dlbzo6ZGlzdGFuY2VfZXVjbGlkZWFuX3BhaXJ3aXNlKHgsIHlbbmVhcmVzdF9idWlsZGluZ0RlbW9saXRpb25dKQ0KDQoNCiMgMi4gVmlzdWFsaXppbmcgbmVhcmVzdCBkaXN0YW5jZSBmb3Igc3BhdGlhbCBmYWN0b3JzIG9uIEZpc2huZXQNCg0KIyMgMi4xLiBWaXN1YWxpemluZyB0aGUgbmVhcmVzdCB0aHJlZSBmZWF0dXJlcw0KDQp2YXJzX25ldC5sb25nLm5uIDwtIA0KICBkcGx5cjo6c2VsZWN0KHZhcnNfbmV0LCBzdGFydHNfd2l0aCgiZGlzdCIpKSAlPiUNCiAgZ2F0aGVyKFZhcmlhYmxlLCB2YWx1ZSwgLWdlb21ldHJ5KQ0KDQp2YXJzIDwtIHVuaXF1ZSh2YXJzX25ldC5sb25nLm5uJFZhcmlhYmxlKQ0KbWFwTGlzdCA8LSBsaXN0KCkNCg0KZm9yKGkgaW4gdmFycyl7DQogIG1hcExpc3RbW2ldXSA8LSANCiAgICBnZ3Bsb3QoKSArDQogICAgZ2VvbV9zZihkYXRhID0gZmlsdGVyKHZhcnNfbmV0Lmxvbmcubm4sIFZhcmlhYmxlID09IGkpLCBhZXMoZmlsbD12YWx1ZSksIGNvbG91cj1OQSkgKw0KICAgIHNjYWxlX2ZpbGxfdmlyaWRpc19jKG9wdGlvbiA9ICJwbGFzbWEiLA0KICAgICAgICAgICAgICAgICAgICAgICAgIG5hbWUgPSAiICIpICsNCiAgICBsYWJzKHRpdGxlPWkpICsNCiAgICBtYXBUaGVtZSgpKw0KICAgIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBjb2xvciA9ICJibGFjayIpKQ0KICB9DQoNCmJvdHRvbUNhcHRpb24gPC0gdGV4dEdyb2IoIkZpZ3VyZSA4IiwgZ3AgPSBncGFyKGhqdXN0ID0gMCkpDQoNCmRvLmNhbGwoZ3JpZC5hcnJhbmdlLCBjKGxpc3QoZ3JvYnMgPSBtYXBMaXN0LCBuY29sID0gMiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRvcCA9IHRleHRHcm9iKCJTcGF0aWFsIEZhY3RvcnM6IE5lYXJlc3QgTmVpZ2hib3IgRGlzdGFuY2UgZm9yIFBlcm1pdHMgSXNzdWVkXG4iLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZ3AgPSBncGFyKGZvbnRzaXplID0gMTUsIGZvbnRmYWNlID0gImJvbGQiLCBjb2wgPSAiZGFya3JlZCIpKSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJvdHRvbSA9IGJvdHRvbUNhcHRpb24pKSkNCg0KIyAzLiBDcmVhdGluZyBuZXcgZmFjdG9yIG9mIGRpc3RhbmNlIHRvIGNlbnRlciBjaXR5IGJhc2VkIG9uIHBsb3RzDQoNCmNjUG9pbnQgPC0NCiAgZmlsdGVyKHBoaWxseU5laWdoYm9yaG9vZHMsIG5hbWUgPT0gIkNFTlRFUl9DSVRZIikgJT4lDQogIHN0X2NlbnRyb2lkKCkNCg0KdmFyc19uZXQkZGlzdF9jZW50ZXJDaXR5ID0NCiAgc3RfZGlzdGFuY2Uoc3RfY2VudHJvaWQodmFyc19uZXQpLGNjUG9pbnQpICU+JQ0KICBhcy5udW1lcmljKCkgDQoNCiANCmBgYA0KDQojIyMgNC4zIFBlcmZvcm1pbmcgU3BhdGlhbCBKb2lucw0KDQpGaW5hbGx5LCBzcGF0aWFsIGpvaW5zIGFyZSBwZXJmb3JtZWQgdG8gY29tYmluZSB0aGUgcGVybWl0IGRhdGEgd2l0aCB0aGUgcmlzayBmYWN0b3IgZGF0YSB0aGUgbmVpZ2hib3Job29kIGRhdGEgd2l0aCB0aGUgZmluYWwgbmV0Lg0KDQpgYGB7ciBTcGF0aWFsIEpvaW4sIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGluY2x1ZGU9VFJVRX0NCg0KIyBKb2luaW5nIENlbnN1cyBEYXRhIHRvIEZpc2huZXQNCg0KdHJhY3RzMjEgPC0gdHJhY3RzMjEgJT4lDQogIGZpbHRlcihUb3RhbFBvcD4wKQ0KDQp2YXJzX25ldCA8LQ0KICB2YXJzX25ldCU+JQ0KICBzdF9jZW50cm9pZCgpJT4lDQogIHN0X2pvaW4odHJhY3RzMjEpDQoNCmxpYnJhcnkoZHBseXIpDQp2YXJzX25ldCA8LSB2YXJzX25ldCAlPiUgbXV0YXRlX2FsbCh+cmVwbGFjZSguLCBpcy5uYSguKSwgMCkpDQoNCiMgUGVyZm9ybSBTcGF0aWFsIEpvaW4gb2YgdmFyaWFibGVzIHdpdGggcGVybWl0cw0KDQpmaW5hbF9uZXQgPC0NCiAgbGVmdF9qb2luKGNvbnN0cnVjdGlvbl9uZXQsIHN0X2Ryb3BfZ2VvbWV0cnkodmFyc19uZXQpLCBieT0idW5pcXVlSUQiKQ0KDQojIEZpbmFsIE5ldA0KDQpmaW5hbF9uZXQgPC0NCiAgc3RfY2VudHJvaWQoZmluYWxfbmV0KSAlPiUgDQogICAgc3Rfam9pbihkcGx5cjo6c2VsZWN0KHBoaWxseU5laWdoYm9yaG9vZHMsIG5hbWUpLCBieSA9ICJ1bmlxdWVJRCIpICU+JSANCiAgICAgIHN0X2Ryb3BfZ2VvbWV0cnkoKSAlPiUNCiAgICAgIGxlZnRfam9pbihkcGx5cjo6c2VsZWN0KGZpbmFsX25ldCwgZ2VvbWV0cnksIHVuaXF1ZUlEKSkgJT4lICANCiAgICAgIHN0X3NmKCkgJT4lDQogIG5hLm9taXQoKQ0KDQpgYGANCg0KIyMgNS4gTW9kZWxsaW5nIFByb2Nlc3MNCg0KT3VyIGFpbSBpcyB0byBmb3JlY2FzdCB0aGUgbGlrZWxpaG9vZCBvZiBuZXcgY29uc3RydWN0aW9uIHBlcm1pdHMgd2l0aGluIHNwZWNpZmljIGFyZWFzIG9mIHRoZSBjaXR5LiBUbyB0aGlzIGVuZCwgYSBnZW9zcGF0aWFsIHJpc2sgbW9kZWwgaXMgbGlrZWx5IHRvIGJlIGVtcGxveWVkIGZvciBwcmVkaWN0aW9uIHdpdGggdGhlIGRlcGVuZGVudCB2YXJpYWJsZSBiZWluZyBuZXcgY29uc3RydWN0aW9uIHBlcm1pdHMuIFRoZSBmb2xsb3dpbmcgc3RlcHMgZXhhbWluZSB0aGUgbmF0dXJlIG9mIHRoZSBkZXBlbmRlbnQgdmFyaWFibGUgYW5kIGl0cyByZWxhdGlvbiB0byB0aGUgZW5naW5lZXJlZCBpbmRlcGVuZGVudCB2YXJpYWJsZXMuDQoNCiMjIyA1LjEgRXhhbWluaW5nIERpc3RyaWJ1dGlvbiBvZiBEZXBlbmRlbnQgVmFyaWFibGUNCg0KQXMgaXMgZXZpZGVudCBmcm9tIHRoZSBoaXN0b2dyYW0gYmVsb3csIGNvbnN0cnVjdGlvbiBwZXJtaXRzIGFyZSB1bmV2ZW5seSBkaXN0cmlidXRlZCBhY3Jvc3MgdGhlIGNpdHksIHRoYXQgaXMsIGEgbGFyZ2UgbWFqb3JpdHkgb2YgcGVybWl0cyBhcmUgaXNzdWVkIGluIG9ubHkgYSBoYW5kZnVsIG9mIHBsYWNlcyBhY3Jvc3MgUGhpbGFkZWxwaGlhLiBUaGlzIGluZGljYXRlcyB0aGUgcHJlc2VuY2Ugb2Ygc3BhdGlhbCBjbHVzdGVyaW5nIGFuZCBpbGx1c3RyYXRlcyB0aGF0IHRoZSBkZXBlbmRlbnQgdmFyaWFibGUgaXMgYSBzdWl0YWJsZSBjYW5kaWRhdGUgZm9yIGhvdHNwb3QgYW5hbHlzaXMuDQoNCmBgYHtyIGhpc3RvZ3JhbSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgZmlnLndpZHRoPTUsIGZpZy5oZWlnaHQ9M30NCg0KIyBEaXN0cmlidXRpb24gb2YgcGVybWl0cw0KDQpnZ3Bsb3QoZGF0YSA9IGZpbmFsX25ldCwgYWVzKGNvdW50UGVybWl0cykpKw0KICBnZW9tX2hpc3RvZ3JhbShiaW5zID0gMzAsIGNvbG9yID0gJ2JsYWNrJywgZmlsbCA9ICJ5ZWxsb3ciKSArDQogIHNjYWxlX3hfY29udGludW91cyhicmVha3MgPSBzZXEoMCwgODAsIGJ5ID0gMTUpKSArDQogIGxhYnModGl0bGUgPSAnRGlzdHJpYnV0aW9uIG9mIFBlcm1pdHMnLCBzdWJ0aXRsZT0iUGhpbGFkZWxwaGlhLCBQQSIsIHkgPSAiT2NjdXJyZW5jZSBGcmVxdWVuY3kiLCB4PSJDb3VudCBvZiBQZXJtaXRzIElzc3VlZCBwZXIgRmlzaG5ldCBDZWxsIiwgY2FwdGlvbj0iRmlndXJlIDkiKSArDQogIHRoZW1lKHBsb3QubWFyZ2luID0gbWFyZ2luKDUsIDUwLCA1LCA1MCwgInB0IikpKw0KICAgIHRoZW1lKGF4aXMudGV4dC54PWVsZW1lbnRfdGV4dChzaXplPTYpKSsNCiAgICB0aGVtZShheGlzLnRleHQueT1lbGVtZW50X3RleHQoc2l6ZT02KSkrDQogIHRoZW1lKA0KICAgIGF4aXMudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDcpLCANCiAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSA5LjUsIGZhY2U9ICJib2xkIiwgY29sb3IgPSAiZGFya3JlZCIpLA0KICAgIHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDcsIGZhY2U9ICJpdGFsaWMiLCBjb2xvciA9ICJibGFjayIpLA0KICAgIHBsb3QuY2FwdGlvbiA9IGVsZW1lbnRfdGV4dChzaXplID0gNyksIA0KICAgIHBsb3QubWFyZ2luID0gbWFyZ2luKDAsIDUwLCA4LCA1MCwgInB0IikpICANCg0KYGBgDQoNCiMjIyA1LjIgRXhhbWluaW5nIExvY2FsIE1vcmFuJ3MgSSBmb3IgRmlzaG5ldCBHcmlkIENlbGxzDQoNClRoZSBlZmZlY3RpdmVuZXNzIG9mIG91ciBtb2RlbCBoaW5nZXMgb24gaXRzIGNhcGFjaXR5IHRvIGNvbnNpZGVyIHNwYXRpYWwgY2x1c3RlcmluZyBhY3Jvc3MgdmFyaW91cyBzY2FsZXMuIFRvIGludmVzdGlnYXRlIHRoZSBsb2NhbCBzcGF0aWFsIHByb2Nlc3MsIHdlIHV0aWxpemUgTG9jYWwgTW9yYW7igJlzIEkuIFRoZSBudWxsIGh5cG90aGVzaXMgcG9zaXRzIHRoYXQgdGhlIGNvdW50IG9mIG5ldyBjb25zdHJ1Y3Rpb24gcGVybWl0cyBleGhpYml0cyBhIHJhbmRvbSBkaXN0cmlidXRpb24gY29uY2VybmluZyBpdHMgaW1tZWRpYXRlIG5laWdoYm9ycy4gRm9yIHRoaXMgYW5hbHlzaXMsIGEgbmVhcmVzdCBuZWlnaGJvciB3ZWlnaHRzIG1hdHJpeCBpcyB1c2VkLCBjb25uZWN0aW5nIGEgdW5pdCAoaW4gdGhpcyBjYXNlLCBhIGZpc2huZXQgZ3JpZCkgdG8gaXRzIGVpZ2h0IGFkamFjZW50IG5laWdoYm9ycyB3aXRoIHRoZSBxdWVlbiBjb250aW51aXR5IGFycmFuZ2VtZW50IG1ldGhvZC4NCg0KVGhlIHJlc3VsdHMgcHJlc2VudGVkIGFyZSBzaW1wbGUgdG8gaW50ZXJwcmV0LCBhcyBzZWVuIGluIHRoZSBwbG90cyBiZWxvdy4gSW4gdGhlIGNhc2VzIHdoZXJlIHRoZSBjb3VudCBvZiBjb3VudCBvZiBjb25zdHJ1Y3Rpb24gcGVybWl0cyBpc3N1ZWQgYXJlIGF0IHRoZWlyIGhpZ2hlc3QsIHRoZSBNb3JhbuKAmXMgSSBmdXJ0aGVyIGF0dGFjaGVzIHRvIGl0cyBuZWlnaGJvcnMuIFRoZXNlIGNhc2VzIGFyZSB0aGVuIGNoZWNrZWQgZm9yIGEgbG93IFAtdmFsdWUgdGhhdCBpcyBsZXNzIHRoYW4gMC4wMDAwMSB0byBkZXRlcm1pbmUgd2hldGhlciB0aGV5IGFyZSBzdGF0aXN0aWNhbGx5IHNpZ25pZmljYW50LiBUaGUgZmlzaG5ldCBjZWxscyB0aGF0IGZ1bGZpbGwgdGhlIGFib3ZlIHBhcmFtZXRlcnMgYXJlIHRoZW4gaWRlbnRpZmllZCBhcyBhIGNvbnN0cnVjdGlvbiBvciBhIGNvbnN0cnVjdGlvbiBwZXJtaXQgaG90c3BvdC4NCg0KYGBge3IgTG9jYWwgTW9yYW5zSSwgZXZhbD1GQUxTRSwgZmlnLndpZHRoPTksIGZpZy5oZWlnaHQ9NCwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCg0KIyAxLiBMb2NhbCBNb3JhbidzIEkgY2FsY3VsYXRpb24NCg0KIyMgMS4xLiBBc3NpZ25pbmcgd2VpZ2h0cw0KDQpmaW5hbF9uZXQubmIgPC0gcG9seTJuYihhc19TcGF0aWFsKGZpbmFsX25ldCksIHF1ZWVuPVRSVUUpDQpmaW5hbF9uZXQud2VpZ2h0cyA8LSBuYjJsaXN0dyhmaW5hbF9uZXQubmIsIHN0eWxlPSJXIiwgemVyby5wb2xpY3k9VFJVRSkNCg0KIyMgMS4yLiBDcmVhdGluZyBMb2NhbCBNb3JhbnMNCg0KbG9jYWxfbW9yYW5zIDwtIGxvY2FsbW9yYW4oZmluYWxfbmV0JGNvdW50UGVybWl0cywgZmluYWxfbmV0LndlaWdodHMsIHplcm8ucG9saWN5PVRSVUUpICU+JSANCiAgYXMuZGF0YS5mcmFtZSgpDQoNCg0KIyMgMS4zLiBKb2luIGxvY2FsIE1vcmFuJ3MgSSByZXN1bHRzIHRvIGZpc2huZXQNCg0KZmluYWxfbmV0LmxvY2FsTW9yYW5zIDwtIA0KICBjYmluZChsb2NhbF9tb3JhbnMsIGFzLmRhdGEuZnJhbWUoZmluYWxfbmV0KSkgJT4lIA0KICBzdF9zZigpICU+JQ0KICBkcGx5cjo6c2VsZWN0KFBlcm1pdF9Db3VudCA9IGNvdW50UGVybWl0cywgDQogICAgICAgICAgICAgICAgTG9jYWxfTW9yYW5zX0kgPSBJaSwgDQogICAgICAgICAgICAgICAgUF9WYWx1ZSA9IGBQcih6ICE9IEUoSWkpKWApICU+JQ0KICBtdXRhdGUoU2lnbmlmaWNhbnRfSG90c3BvdHMgPSBpZmVsc2UoUF9WYWx1ZSA8PSAwLjAwMDAxLCAxLCAwKSkgJT4lDQogIGdhdGhlcihWYXJpYWJsZSwgVmFsdWUsIC1nZW9tZXRyeSkNCg0KDQojIDIuIExvY2FsIE1vcmFuJ3MgSSAgUGxvdA0KDQp2YXJzIDwtIHVuaXF1ZShmaW5hbF9uZXQubG9jYWxNb3JhbnMkVmFyaWFibGUpDQp2YXJMaXN0IDwtIGxpc3QoKQ0KDQojIGZvcihpIGluIHZhcnMpew0KIyAgIHZhckxpc3RbW2ldXSA8LQ0KIyAgICAgZ2dwbG90KCkgKw0KIyAgICAgICBnZW9tX3NmKGRhdGEgPSBwaGlsbHlCb3VuZGFyeSwgZmlsbCA9ICIjODA4MDgwIiwgY29sb3IgPSAiZGFya2dyZXkiKSArDQojICAgICAgIGdlb21fc2YoZGF0YSA9IGZpbHRlcihmaW5hbF9uZXQubG9jYWxNb3JhbnMsIFZhcmlhYmxlID09IGkpLA0KIyAgICAgICAgICAgICAgIGFlcyhmaWxsID0gVmFsdWUpLCBjb2xvdXI9TkEpICsNCiMgICAgICAgc2NhbGVfZmlsbF92aXJpZGlzKG9wdGlvbiA9ICJwbGFzbWEiLG5hbWU9IiIpICsNCiMgICAgICAgbGFicyh0aXRsZT1pKSArDQojICAgICAgIG1hcFRoZW1lKCkgKyB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgY29sb3IgPSAiYmxhY2siKSwNCiMgICAgICAgICAgICAgbGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIpfQ0KIyANCiMgYm90dG9tQ2FwdGlvbiA8LSB0ZXh0R3JvYigiRmlndXJlIDEwIiwgZ3AgPSBncGFyKGhqdXN0ID0gMCkpDQojIA0KIyBkby5jYWxsKGdyaWQuYXJyYW5nZSwgYyh2YXJMaXN0LCBsaXN0KG5jb2wgPSA0LA0KIyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRvcCA9IHRleHRHcm9iKCJMb2NhbCBNb3JhbnMgSSBTdGF0aXN0aWNzIGZvciBQaGlsYWRlbHBoaWEgUGVybWl0cywgMjAxMyAtIDIwMjJcbiIsDQojICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZ3AgPSBncGFyKGZvbnRzaXplID0gMTUsIGZvbnRmYWNlID0gImJvbGQiLCBjb2wgPSAiZGFya3JlZCIpKSwNCiMgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBib3R0b20gPSBib3R0b21DYXB0aW9uKSkpDQoNCnNhdmVSRFMobG9jYWxfbW9yYW5zLCBmaWxlID0gIkM6L1VzZXJzL3VzZXIvRG9jdW1lbnRzL0dpdEh1Yi9GaW5hbCAtIFBoaWxseSBQZXJtaXRzL2xvY2FsX21vcmFucy5yZHMiKQ0KDQpgYGANCjxkaXYgc3R5bGU9InRleHQtYWxpZ246IGNlbnRlcjsiPg0KIVtBbHQgdGV4dF0oQzovVXNlcnMvdXNlci9Eb2N1bWVudHMvR2l0SHViL0ZpbmFsIC0gUGhpbGx5IFBlcm1pdHMvUGhpbGx5IFBlcm1pdHMvUnBsb3QwMy5wbmcpDQo8L2Rpdj4NCg0KQWRvcHRpbmcgYSBzbWFsbGVyIHAtdmFsdWUgdGhyZXNob2xkKGxlc3MgdGhhbiB0aGUgdW5pdmVyc2FsbHkgYWNjZXB0ZWQgdGhyZXNob2xkIG9mIDAuMDUpIGFsbG93cyBmb3IgYSByZWFzb25hYmx5IGFjY3VyYXRlIGlkZW50aWZpY2F0aW9uIG9mIGNvbnN0cnVjdGlvbiBwZXJtaXQgaG90c3BvdHMgb3IgY29uY2VudHJhdGVkIGFyZWFzIHdoZXJlIGRldmVsb3BtZW50IGlzIGV4cGVjdGVkIHRvIG9jY3VyLiBUaGlzIGlzIHBhcnRpY3VsYXJseSBldmlkZW50IGluIENlbnRlciBDaXR5IGFuZCBub3J0aGVhc3QgUGhpbGFkZWxwaGlhLg0KDQpJbiBleGVjdXRpbmcgdGhlIExvY2FsIE1vcmFu4oCZcyBJIHRlc3QsIHRoZSBudWxsIGh5cG90aGVzaXMgdGhhdCB0aGUgcGVybWl0IGNvdW50IGF0IGEgZ2l2ZW4gbG9jYXRpb24gaXMgcmFuZG9tbHkgZGlzdHJpYnV0ZWQgcmVsYXRpdmUgdG8gaXRzIGltbWVkaWF0ZSBuZWlnaGJvcnMgaXMgcmVqZWN0ZWQuIFRoaXMgaW1wbGllcyBzcGF0aWFsIGF1dG9jb3JyZWxhdGlvbiBvZiBtb2RlbCBlcnJvcnMgYXQgdGhlIGxvY2FsIGxldmVsIHRoYXQgbmVlZCB0byBiZSBhc3Nlc3NlZCBhbmQgY29udHJvbGxlZC4NCg0KDQojIyMgNS4zIENhbGN1bGF0aW5nIERpc3RhbmNlIHRvIEhvdCBTcG90cw0KDQpUaGUgbmVhcmVzdCBuZWlnaGJvciBmdW5jdGlvbiBpcyB1c2VkIHRvIGVzdGFibGlzaCBhIHNwYXRpYWwgYXNzb2NpYXRpb24gYmV0d2VlbiBpbmNpZGVudHMgb2YgcGVybWl0cy9kZXZlbG9wbWVudCBhbmQgdGhlIHJpc2sgZmFjdG9ycyB0aGF0IGFyZSBpZGVudGlmaWVkLiANCg0KVGhlIGhvdHNwb3QgaW5mb3JtYXRpb24gZ2VuZXJhdGVkIGlzIHVzZWQgZmlyc3QgdG8gZXN0YWJsaXNoIGEgc3BhdGlhbCBhc3NvY2lhdGlvbiBiZXR3ZWVuIGluY2lkZW50cyBvZiBwZXJtaXRzL2RldmVsb3BtZW50IHdpdGggdGhlIHJpc2sgZmFjdG9ycyBpZGVudGlmaWVkLiBJdCBpcyB0aGVuIHVzZWQgdG8gY29uc3RydWN0IGEgc3BhdGlhbCBmZWF0dXJlIGFpbWVkIGF0IG1pdGlnYXRpbmcgbG9jYWwgc3BhdGlhbCBhdXRvY29ycmVsYXRpb24uIEluIHRoaXMgY29udGV4dCwgdGhlIGRpc3RhbmNlIG9mIGVhY2ggZ3JpZCBjZWxsIHRvIHRoZSBuZWFyZXN0IHNpZ25pZmljYW50IGhvdHNwb3QgaXMgY2FsY3VsYXRlZCBhbmQgc3RvcmVkIGluIGEgbW9kZWwgdmFyaWFibGUuDQoNCmBgYHtyIE5OIEhvdHNwb3RzLCBmaWcud2lkdGg9NywgZmlnLmhlaWdodD03LCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KDQojIDEuIEV4YW1pbmluZyBIb3QgU3BvdHMNCg0KIyMgMS4xLiBEaXN0YW5jZSB0byBIb3Qgc3BvdHMNCg0KbG9jYWxfbW9yYW5zIDwtIHJlYWRSRFMoIkM6L1VzZXJzL3VzZXIvRG9jdW1lbnRzL0dpdEh1Yi9GaW5hbCAtIFBoaWxseSBQZXJtaXRzL2xvY2FsX21vcmFucy5yZHMiKQ0KICANCmZpbmFsX25ldCA8LQ0KICBmaW5hbF9uZXQgJT4lIA0KICBtdXRhdGUocGVybWl0LmlzU2lnID0gDQogICAgICAgICAgIGlmZWxzZShsb2NhbF9tb3JhbnNbLDVdIDw9IDAuMDAwMDAwMSwgMSwgMCkpICU+JSANCiAgbXV0YXRlKHBlcm1pdC5pc1NpZy5kaXN0ID0gDQogICAgICAgICAgIG5uX2Z1bmN0aW9uKHN0X2Nvb3JkaW5hdGVzKHN0X2NlbnRyb2lkKGZpbmFsX25ldCkpLA0KICAgICAgICAgICAgICAgICAgICAgICBzdF9jb29yZGluYXRlcyhzdF9jZW50cm9pZCgNCiAgICAgICAgICAgICAgICAgICAgICAgICBmaWx0ZXIoZmluYWxfbmV0LCBwZXJtaXQuaXNTaWcgPT0gMSkpKSwgMSkpDQoNCg0KIyMgMS4yIFBsb3R0aW5nIE5OIEhvdCBzcG90cw0KDQpnZ3Bsb3QoKSArDQogICAgICBnZW9tX3NmKGRhdGEgPSBmaW5hbF9uZXQsIGFlcyhmaWxsPXBlcm1pdC5pc1NpZy5kaXN0KSwgY29sb3VyPU5BKSArDQogICAgICBzY2FsZV9maWxsX3ZpcmlkaXMobmFtZT0iTk4gRGlzdGFuY2UiKSArDQogICAgICBsYWJzKHRpdGxlPSJQZXJtaXQgTk4gRGlzdGFuY2UiLCBjYXB0aW9uPSAiRmlndXJlIDExIikgKw0KICAgICAgbWFwVGhlbWUoKSsNCiAgICAgIHBsb3RUaGVtZSgpDQpgYGANCg0KIyMjIDUuNCBFeGFtaW5pbmcgQ29ycmVsYXRpb25zIGJldHdlZW4gSW5kZXBlbmRlbnQgVmFyaWFibGVzDQoNClRvIGNoZWNrIGhvdyB3ZWxsIHRoZSBzZWxlY3RlZCBwcmVkaWN0b3JzIGFyZSBsaW5rZWQgdG8gdGhlIG91dGNvbWUsIHRoZSBjb3JyZWxhdGlvbiBiZXR3ZWVuIHRoZSBkZXBlbmRlbnQgdmFyaWFibGUgdGhhdCBpcyBwZXJtaXQgY291bnQgYW5kIHRoZSBpbmRlcGVuZGVudCB2YXJpYWJsZXMgaXMgc3R1ZGllZC4gVGhpcyBpcyBkb25lIGluIHR3byBzdGVwcy4gRmlyc3QsIGNvcnJlbGF0aW9uIHNjYXR0ZXIgcGxvdHMgYXJlIGNyZWF0ZWQuIEl0IGlzIHRvIGJlIG5vdGVkIHRoYXQgdGhlIHByZWRpY3RvcnMgZG8gbm90IGhhdmUgaGlnaCBSLXNxdWFyZWQgdmFsdWVzIHdoaWNoIGNhbiBiZSBhdHRyaWJ1dGVkIHRvIHRoZSBzaXplIG9mIHRoZSBkYXRhc2V0IGFzIHdlbGwgYXMgdGhlIHNrZXdlZCBkaXN0cmlidXRpb24gb2YgcGVybWl0IGNvdW50IHBlciBmaXNobmV0IGNlbGwuIA0KDQpUaGUgcGxvdHMgaWxsdXN0cmF0ZSB0aGF0IHNwYXRpYWwgdmFyaWFibGVzIHN1Y2ggYXMgJ0FmZm9yZGFibGUgSG91c2luZycsICdCdWlsZGluZyBEZW1vbGl0aW9uLCAnQnVzaW5lc3MgTGljZW5jZXMnLCAnVmFjYW50IExhbmQnLCAnU2FuaXRhdGlvbicsICdTZXJ2aWNlcycsIGFuZCBkZW1vZ3JhcGhpYyB2YXJpYWJsZXMgc3VjaCBhcyAnUGVyY2VudCBCYWNoZWxvcidzIERlZ3JlZSBIb2xkZXJzJywgJ1BlcmNlbnQgUmVudGVyIE9jY3VwaWVkJywgYW5kICdNZWRpYW4gSG91c2Vob2xkIEluY29tZScgZXhoaWJpdCBwb3NpdGl2ZSByZWxhdGlvbnNoaXBzIHdpdGggdGhlIGRlcGVuZGVudCB2YXJpYWJsZS4gDQoNCkFkZGl0aW9uYWxseSwgaXQgaXMgc2VlbiB0aGF0IG92ZXJhbGwsIHRoZSBzcGF0aWFsIHByb2Nlc3MgZmVhdHVyZXMgaGF2ZSBzbGlnaHRseSBoaWdoZXIgUi1zcXVhcmVkIHZhbHVlcyB0aGFuIHRoZWlyIG5vbi1zcGF0aWFsIHByb2Nlc3MgY291bnRlcnBhcnRzLiANCg0KYGBge3IgQ29ycmVsYXRpb24gVGVzdHMsIGZpZy5oZWlnaHQ9MjEsIGZpZy53aWR0aD0xNSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgaW5jbHVkZT1UUlVFfQ0KDQojIENvbmR1Y3RpbmcgY29ycmVsYXRpb24gVGVzdHMgZm9yIHNwYXRpYWwgYW5kIGRlbW9ncmFwaGljIGZhY3RvcnMNCg0KY29ycmVsYXRpb24ubG9uZyA8LQ0KICBzdF9kcm9wX2dlb21ldHJ5KGZpbmFsX25ldCkgJT4lDQogICAgZHBseXI6OnNlbGVjdCgtdW5pcXVlSUQsIC1jdklELCAtbmFtZSkgJT4lDQogICAgZ2F0aGVyKFZhcmlhYmxlLCBWYWx1ZSwgLWNvdW50UGVybWl0cykgJT4lDQogIG11dGF0ZShWYWx1ZSA9IGFzLm51bWVyaWMoVmFsdWUpKQ0KDQpjb3JyZWxhdGlvbi5jb3IgPC0NCiAgY29ycmVsYXRpb24ubG9uZyAlPiUNCiAgICBncm91cF9ieShWYXJpYWJsZSkgJT4lDQogICAgc3VtbWFyaXplKGNvcnJlbGF0aW9uID0gY29yKFZhbHVlLCBjb3VudFBlcm1pdHMsIHVzZSA9ICJjb21wbGV0ZS5vYnMiKSkNCg0KIyBWaXN1YWxpemluZyBjb3JyZWxhdGlvbnMgdGhyb3VnaCBzY2F0dGVyIHBsb3RzDQogICAgDQpnZ3Bsb3QoY29ycmVsYXRpb24ubG9uZywgYWVzKFZhbHVlLCBjb3VudFBlcm1pdHMpKSArDQogIGdlb21fcG9pbnQoc2l6ZSA9IDAuMSkgKw0KICBnZW9tX3RleHQoZGF0YSA9IGNvcnJlbGF0aW9uLmNvciwgYWVzKGxhYmVsID0gcGFzdGUoInIgPSIsIHJvdW5kKGNvcnJlbGF0aW9uLCAyKSkpLA0KICAgICAgICAgICAgeD0tSW5mLCB5PUluZiwgdmp1c3QgPSAxLjUsIGhqdXN0ID0gLS4xKSArDQogIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsbSIsIHNlID0gRkFMU0UsIGNvbG91ciA9ICJvcmFuZ2UiKSArDQogIGZhY2V0X3dyYXAoflZhcmlhYmxlLCBuY29sID0gNCwgc2NhbGVzID0gImZyZWUiKSArDQogIGxhYnModGl0bGUgPSAiUGVybWl0IENvdW50IGFzIGEgZnVuY3Rpb24gb2YgcmlzayBmYWN0b3JzIiwgY2FwdGlvbj0iRmlndXJlIDEyIikgKw0KICBwbG90VGhlbWUoKQ0KDQoNCmBgYA0KDQpOZXh0LCB0aGUgY29ycmVsYXRpb25zIGFyZSB2aXN1YWxpemVkIHVzaW5nIGEgY29ycmVsYXRpb24gbWF0cml4LiBUaGlzIGlzIGRvbmUgdG8gZXhhbWluZSBjb3JyZWxhdGlvbnMgYmV0d2VlbiB0aGUgc2VsZWN0ZWQgaW5kZXBlbmRlbnQgdmFyaWFibGVzIHRvIG1pbmltaXplIG11bHRpLWNvbGxpbmVhcml0eSBpbiB0aGUgcmVncmVzc2lvbiBtb2RlbC4gSXQgaXMgc2VlbiB0aGF0IHZhcmlhYmxlcyBzdWNoIGFzICdWYWNhbnQgQnVpbGRpbmdzJywgJ1Nhbml0YXRpb24nLCBhbmQnQnVpbGRpbmcgRGVtb2xpdGlvbicgaGF2ZSBzdHJvbmcgY29ycmVsYXRpb25zIHdpdGggb3RoZXIgcHJlZGljdG9yIHZhcmlhYmxlcy4gVGhlc2UgYXJlIG5vIGxvbmdlciBjb25zaWRlcmVkIGZvciByZWdyZXNzaW9uLiBBZGRpdGlvbmFsbHksIGl0IGlzIHNlZW4gdGhhdCBwYWlycyBzdWNoIGFzICdEaXN0YW5jZSB0byBDZW50ZXIgQ2l0eScgYW5kICdQZXJtaXQgaXMgU2lnbmlmaWNhbnQgRGlzdGFuY2UnIGFzIHdlbGwgYXMgJ01lZGlhbiBIb3VzZWhvbGQgSW5jb21lJyBhbmQgJ1BlcmNlbnQgQmFjaGVsb3IncyBEZWdyZWUgSG9sZGVycyBleGhpYml0IHN0cm9uZyBjb3JyZWxhdGlvbnMsIHRoZXJlZm9yZSB0aGV5IGFyZSBhbHNvIGRyb3BwZWQgaW4gdGhlIHByb2Nlc3Mgb2Ygc2VsZWN0aW5nIHJlZ3Jlc3Npb24gdmFyaWJsZXMuIA0KDQoNCmBgYHtyIENvcnJlbGF0aW9uIE1hdHJpeCwgZmlnLndpZHRoPTYsIGZpZy5oZWlnaHQ9Nn0NCg0KIyBWaXN1YWxpemluZyBjb3JyZWxhdGlvbnMgdGhyb3VnaCBhIGNvcnJlbGF0aW9uIG1hdHJpeA0KDQpudW12YXJzIDwtIGMoImNvdW50UGVybWl0cyIsICJkaXN0X3ZhY2FudExhbmQiLCAiZGlzdF92YWNhbnRCdWlsZGluZyIsICAiZGlzdF9hZmZvcmRhYmxlSG91c2luZyIsImRpc3RfZ3JlZW5TcGFjZSIsImRpc3RfYnVzaW5lc3NMaWNlbnNlIiwgImRpc3Rfc2VydmljZSIsICJkaXN0X3Nhbml0YXRpb24iLCAiZGlzdF9idWlsZGluZ0RlbW9saXRpb24iLCAiVG90YWxQb3AiLCAiTWVkSEhJbmMiLCAicGN0V2hpdGUiLCAicGN0QmxhY2siLCAicGN0QmFjaCIgLCJwY3RQb3ZlcnR5IiwgInBjdFVuZW1wbG95IiwgInBjdFdGSCIsICJwY3RPd25lck9jY3VwaWVkIiwgInBjdFJlbnRlck9jY3VwaWVkIiwgImRpc3RfY2VudGVyQ2l0eSIsICJwZXJtaXQuaXNTaWcuZGlzdCIpDQoNCm51bWVyaWMgPC0gZmluYWxfbmV0ICU+JQ0KICBzdF9kcm9wX2dlb21ldHJ5KGZpbmFsX25ldCkgJT4lDQogIGRwbHlyOjpzZWxlY3QobnVtdmFycyklPiUNCiAgbmEub21pdCgpDQoNCmdnY29ycnBsb3QoDQogIHJvdW5kKGNvcihudW1lcmljKSwgMSksIA0KICBwLm1hdCA9IGNvcl9wbWF0KG51bWVyaWMpLA0KICBjb2xvcnMgPSBjKCcjZDcxOTFjJywnd2hpdGUnLCcjMmM3YmI2JyksDQogIHR5cGU9Imxvd2VyIiwNCiAgaW5zaWcgPSAiYmxhbmsiKSArICANCiAgICBsYWJzKHRpdGxlID0gIkNvcnJlbGF0aW9uIGFjcm9zcyBWYXJpYWJsZXNcbiIsIGNhcHRpb249IkZpZ3VyZSAxMyIpKyANCiAgICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMSwgZmFjZSA9ICJib2xkIiwgY29sb3IgPSAiZGFya3JlZCIpKSsNCiAgICB0aGVtZShheGlzLnRleHQueD1lbGVtZW50X3RleHQoc2l6ZT04KSkrDQogICAgdGhlbWUoYXhpcy50ZXh0Lnk9ZWxlbWVudF90ZXh0KHNpemU9OCkpDQpgYGANCg0KIyMgNi4gUmVncmVzc2lvbg0KDQpDb25zdHJ1Y3Rpb24gcGVybWl0IGlzc3VhbmNlIG9mdGVuIGV4aGliaXRzIGNsdXN0ZXJpbmcgcGF0dGVybnMsIHdoZXJlIG5lYXJieSBsb2NhdGlvbnMgaW5mbHVlbmNlIGVhY2ggb3RoZXIuIEFzIHNlZW4gaW4gdGhlIGhpc3RvZ3JhbSBpbiBGaWd1cmUgOSBhYm92ZSwgdGhlIGNvdW50IG9mIHBlcm1pdHMgaXNzdWVkIHBlciBmaXNobmV0IGNlbGwgdmFyeSBub24tdW5pZm9ybWx5IGJldHdlZW4gMCBhbmQgMTA1LCB3aXRoIG1vc3QgY2VsbHMgY29udGFpbmluZyAwIHBlcm1pdHMuIEdpdmVuIHRoYXQgZGV2ZWxvcG1lbnQgaXMgYSBwaGVub21lbm9uIGRpY3RhdGVkIGJ5IHZhcmlvdXMgc3BhdGlhbCwgc29jaWFsLCBkZW1vZ3JhcGhpYywgaXQgaXMgcmVhc29uYWJsZSBmb3IgbW9zdCBncmlkIGNlbGxzIHRvIGNvbnRhaW4gZmV3IG9yIG5vIGRldmVsb3BtZW50IGV2ZW50cy4gDQoNCldpdGggdGhlc2UgZGF0YSB0cmVuZHMgaW4gbWluZCwgYSBzcGF0aWFsIFBvaXNzb24gUmVncmVzc2lvbiBtb2RlbCB3aGljaCBhY2NvdW50cyBmb3IgdGhlIHNwYXRpYWwgYXV0b2NvcnJlbGF0aW9uIGlzIHNlbGVjdGVkLiBUaGlzIGFsbG93cyB0aGUgbW9kZWwgdG8gZGlzY2VybiBhbmQgbGV2ZXJhZ2UgdGhlIGxvY2FsIHNwYXRpYWwgcHJvY2Vzc2VzIGluZmx1ZW5jaW5nIHBlcm1pdCBjb3VudHMgaW4gYWRqYWNlbnQgY2VsbHMuIEJ5IGNhcHR1cmluZyB0aGUgc3BhdGlhbCBudWFuY2VzIG9mIGNvbnN0cnVjdGlvbiBwZXJtaXQgZGlzdHJpYnV0aW9ucywgdGhlIFNwYXRpYWwgUG9pc3NvbiBSZWdyZXNzaW9uIG1vZGVsIGlzIGV4cGVjdGVkIHRvIHByb3ZpZGUgbW9yZSBhY2N1cmF0ZSBwcmVkaWN0aW9ucywgb2ZmZXJpbmcgYSBzdXBlcmlvciB1bmRlcnN0YW5kaW5nIG9mIHRoZSBzcGF0aWFsIGR5bmFtaWNzIGRyaXZpbmcgcGVybWl0IGlzc3VhbmNlIGFjcm9zcyB0aGUgZmlzaG5ldCwgdWx0aW1hdGVseSBlbmhhbmNpbmcgdGhlIG1vZGVsJ3MgcHJlZGljdGl2ZSBwZXJmb3JtYW5jZSBmb3IgdGhpcyBzcGVjaWZpYyBzcGF0aWFsIGNvbnRleHQuDQoNCiMjIyA2LjEgU2VsZWN0aW5nIFZhcmlhYmxlcw0KDQpGb3IgcmVncmVzc2lvbiBhbmQgY3Jvc3MgdmFsaWRhdGlvbiwgdHdvIHNldHMgb2YgdmFyaWFibGVzIGFyZSBjcmVhdGVkIOKAkyBvbmUgY29tcHJpc2luZyByZWNlbnRseSBhZGRlZCB2YXJpYWJsZXMgaW5kaWNhdGluZyB0aGUgZGlzdGFuY2UgdG8gYSBzaWduaWZpY2FudCBjb25zdHJ1Y3Rpb24gaG90c3BvdCwgYW5kIHRoZSBvdGhlciBjb250YWluaW5nIG9ubHkgcmlzayBmYWN0b3IgdmFyaWFibGVzLiBUaGlzIGFwcHJvYWNoIGhlbHBzIGlsbHVzdHJhdGUgdGhlIHNpZ25pZmljYW5jZSBvZiBnZW5lcmFsaXphYmlsaXR5IGFjcm9zcyBkaXZlcnNlIHNwYXRpYWwgY29udGV4dHMgYW5kIGRlbW9uc3RyYXRlcyBob3cgdGhlIGhvdHNwb3QgdmFyaWFibGUgZW5oYW5jZXMgdGhlIG1vZGVsJ3Mgb3ZlcmFsbCBnZW5lcmFsaXphYmlsaXR5Lg0KDQpCYXNlZCBvbiB0aGUgY29ycmVsYXRpb24gYW5hbHlzaXMgYXMgc2VlbiBpbiBGaWd1cmUgMTMsICdWYWNhbnQgTGFuZCcsICdHcmVlbiBTcGFjZScsICdCdXNpbmVzcyBMaWNlbnNlJywgJ0FmZm9yZGFibGUgSG91c2luZycsICdQZXJjZW50IFJlbnRlciBPY2N1cGllZCcsICdQZXJjZW50IFdvcmtpbmcgRnJvbSBIb21lJyBhbmQgJ1BlcmNlbnQgQmFjaGVsb3IncyBEZWdyZWUgSG9sZGVycyBhcmUgc2VsZWN0ZWQgYXMgbm9uLXNwYXRpYWwgcHJvY2VzcyBtb2RlbCB2YXJpYWJsZXMuIFRoZSBzYW1lIHZhcmlhYmxlcyB3aXRoIHNwYXRpYWwgcHJvY2VzcyBmZWF0dXJlcyBhZGRlZCBhcmUgY29uc2lkZXJlZCBmb3IgdGhlIHNwYXRpYWwgcHJvY2VzcyBtb2RlbHMuIA0KDQpgYGB7ciBSZWdyZXNzaW9uIFZhcmlhYmxlIEJ1Y2tldGluZywgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgaW5jbHVkZT1UUlVFfQ0KDQojIFJlZ3Jlc3Npb24gVmFyaWFibGVzIEJ1Y2tldGluZw0KDQpyZWcudmFycyA8LSBjKCJkaXN0X3ZhY2FudExhbmQiLCJkaXN0X2dyZWVuU3BhY2UiLCAiZGlzdF9zZXJ2aWNlIiwgImRpc3RfYnVzaW5lc3NMaWNlbnNlIiwiZGlzdF9hZmZvcmRhYmxlSG91c2luZyIsICJwY3RSZW50ZXJPY2N1cGllZCIsICJwY3RXRkgiLCAicGN0QmFjaCIpDQoNCnJlZy5zcy52YXJzIDwtIGMoImRpc3RfdmFjYW50TGFuZCIsImRpc3RfZ3JlZW5TcGFjZSIsICJkaXN0X3NlcnZpY2UiLCAiZGlzdF9idXNpbmVzc0xpY2Vuc2UiLCJkaXN0X2FmZm9yZGFibGVIb3VzaW5nIiwgICJwY3RSZW50ZXJPY2N1cGllZCIsInBjdFdGSCIsInBjdEJhY2giLCAicGVybWl0LmlzU2lnIiwgInBlcm1pdC5pc1NpZy5kaXN0IikNCmBgYA0KDQojIyMgNi4yIFNwbGl0dGluZyBkYXRhc2V0IGZvciBDcm9zcyBWYWxpZGF0aW9uDQoNClR3byB0eXBlcyBvZiBkYXRhIGZvbGRzIGZvciBjcm9zcy12YWxpZGF0aW9uIGFyZSBpbXBsZW1lbnRlZCwgbmFtZWx5LCByYW5kb20gZm9sZHMgYW5kIG5laWdoYm9yaG9vZCBmb2xkcy4NCg0KVGhlIExlYXZlLU9uZS1Hcm91cC1PdXQgQ3Jvc3MtVmFsaWRhdGlvbiAoTE9HTy1DVikgbWV0aG9kIHN5c3RlbWF0aWNhbGx5IHdpdGhob2xkcyBvbmUgZ3JvdXAgYXQgYSB0aW1lIGZyb20gdGhlIG1vZGVsIHRyYWluaW5nLCBwcmVkaWN0aW5nIG9uIHRoZSBleGNsdWRlZCBncm91cC4gRm9yIG5laWdoYm9yaG9vZCBmb2xkcywgYWxsIGdyaWQgY2VsbHMgZXhjbHVkaW5nIHRoZSBncmlkIGNlbGxzIGJlbG9uZ2luZyB0byBhIHBhcnRpY3VsYXIgbmVpZ2hib3Job29kIGFyZSBhbGxvY2F0ZWQgdG8gdGhlIHRyYWluaW5nIHNldCBhbmQgdGhlbiB0ZXN0ZWQgb24gdGhlIGdyaWQgY2VsbHMgd2l0aGluIHRoZSBleGNsdWRlZCBuZWlnaGJvcmhvb2QuIFRoaXMgcHJvY2VzcyByZXBlYXRzIHVudGlsIGVhY2ggbmVpZ2hib3Job29kIGhhcyBzZXJ2ZWQgYXMgdGhlIGhvbGRvdXQuIEJ5IGFkb3B0aW5nIHRoaXMgYXBwcm9hY2gsIHdlIGVuc3VyZSB0aGF0IG1vZGVsIHRyYWluaW5nIHJlbWFpbnMgdW5hZmZlY3RlZCBieSB0aGUgcmFuZG9tIGluY2x1c2lvbiBvciBleGNsdXNpb24gb2YgYW4gb3V0bGllciBpbiB0aGUgdHJhaW5pbmcgc2V0LiBIb3dldmVyLCB1c2luZyBuZWlnaGJvcmhvb2RzIGFzIHRoZSBob2xkb3V0IGNyaXRlcmlhIGFzc3VtZXMgYSBkZWdyZWUgb2Ygc2ltaWxhcml0eSBhY3Jvc3MgbmVpZ2hib3Job29kcyBpbiB0aGUgY2l0eSwgd2hpY2ggaXMgbm90IHRoZSBjYXNlIGFuZCB3aWxsIGJlIGRldGFpbGVkIGZ1cnRoZXIgaW4gdGhlIGZvbGxvd2luZyBzZWN0aW9ucy4NCg0KYGBge3IgU3BsaXR0aW5nIGZvciBDcm9zcyBWYWxpZGF0aW9uLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBpbmNsdWRlPVRSVUV9DQoNCiMgQ3JlYXRpbmcgZnVuY3Rpb25zIGZvciByZWdyZXNzaW9uIGFuZCBjcm9zcyB2YWxpZGF0aW9uDQoNCmNyb3NzVmFsaWRhdGUgPC0gZnVuY3Rpb24oZGF0YXNldCwgaWQsIGRlcGVuZGVudFZhcmlhYmxlLCBpbmRWYXJpYWJsZXMpIHsNCiAgDQogIGFsbFByZWRpY3Rpb25zIDwtIGRhdGEuZnJhbWUoKQ0KICBjdklEX2xpc3QgPC0gdW5pcXVlKGRhdGFzZXRbW2lkXV0pDQogIA0KICBmb3IgKGkgaW4gY3ZJRF9saXN0KSB7DQogICAgDQogICAgdGhpc0ZvbGQgPC0gaQ0KICAgIGNhdCgiVGhpcyBob2xkIG91dCBmb2xkIGlzIiwgdGhpc0ZvbGQsICJcbiIpDQogICAgDQogICAgZm9sZC50cmFpbiA8LSBmaWx0ZXIoZGF0YXNldCwgZGF0YXNldFtbaWRdXSAhPSB0aGlzRm9sZCkgJT4lIGFzLmRhdGEuZnJhbWUoKSAlPiUgDQogICAgICBkcGx5cjo6c2VsZWN0KGlkLCBnZW9tZXRyeSwgaW5kVmFyaWFibGVzLCBkZXBlbmRlbnRWYXJpYWJsZSkNCiAgICBmb2xkLnRlc3QgIDwtIGZpbHRlcihkYXRhc2V0LCBkYXRhc2V0W1tpZF1dID09IHRoaXNGb2xkKSAlPiUgYXMuZGF0YS5mcmFtZSgpICU+JSANCiAgICAgIGRwbHlyOjpzZWxlY3QoaWQsIGdlb21ldHJ5LCBpbmRWYXJpYWJsZXMsIGRlcGVuZGVudFZhcmlhYmxlKQ0KICAgIA0KICAgIHJlZ3Jlc3Npb24gPC0NCiAgICAgIGdsbShjb3VudFBlcm1pdHMgfiAuLCBmYW1pbHkgPSAicG9pc3NvbiIsIA0KICAgICAgICAgIGRhdGEgPSBmb2xkLnRyYWluICU+JSANCiAgICAgICAgICAgIGRwbHlyOjpzZWxlY3QoLWdlb21ldHJ5LCAtaWQpKQ0KICAgIA0KICAgIHRoaXNQcmVkaWN0aW9uIDwtIA0KICAgICAgbXV0YXRlKGZvbGQudGVzdCwgUHJlZGljdGlvbiA9IHByZWRpY3QocmVncmVzc2lvbiwgZm9sZC50ZXN0LCB0eXBlID0gInJlc3BvbnNlIikpDQogICAgDQogICAgYWxsUHJlZGljdGlvbnMgPC0NCiAgICAgIHJiaW5kKGFsbFByZWRpY3Rpb25zLCB0aGlzUHJlZGljdGlvbikNCiAgICANCiAgfQ0KICByZXR1cm4oc3Rfc2YoYWxsUHJlZGljdGlvbnMpKQ0KfQ0KDQpgYGANCg0KDQojIyMgNi4zIENyb3NzIFZhbGlkYXRpb24NCg0KSW4gdGhlIGNyb3NzLXZhbGlkYXRpb24gcHJvY2VzcywgZm91ciBkaXN0aW5jdCByZWdyZXNzaW9ucyBhcmUgZXhhbWluZWQuIFR3byBvZiB0aGVtIGVtcGxveSByYW5kb20gay1mb2xkIGNyb3NzLXZhbGlkYXRpb246IG9uZSBpbmNvcnBvcmF0aW5nIG9ubHkgdGhlIGJhc2ljIHJpc2sgZmFjdG9ycywgYW5kIGFub3RoZXIgaW50ZWdyYXRpbmcgdGhlIHZhcmlhYmxlIG1lYXN1cmluZyB0aGUgZGlzdGFuY2UgdG8gc2lnbmlmaWNhbnQgaG90c3BvdHMuIFRoZSBvdGhlciB0d28gcmVncmVzc2lvbnMgYWRvcHQgTGVhdmUtT25lLUdyb3VwLU91dCBDcm9zcy1WYWxpZGF0aW9uIChMT0dPLUNWKSBiYXNlZCBvbiB0aGUgbmVpZ2hib3Job29kIG5hbWUsIHNpbWlsYXJseSBzcGxpdCBiZXR3ZWVuIHRoZSBzaW1wbGUgcmlzayBmYWN0b3JzIGFuZCB0aGUgaW5jbHVzaW9uIG9mIHRoZSBkaXN0YW5jZSB0byBzaWduaWZpY2FudCBob3RzcG90cyB2YXJpYWJsZS4NCg0KYGBge3IgQ1Ygb24gZm91ciByZWdyZXNzaW9ucywgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgaW5jbHVkZT1UUlVFLCByZXN1bHRzPUZBTFNFfQ0KDQojIENvbmR1Y3RpbmcgQ3Jvc3MgVmFsaWRhdGlvbiBvbiA0IFBvaXNzb24gUmVncmVzc2lvbiBtb2RlbHMNCg0KcmVnLmN2IDwtIGNyb3NzVmFsaWRhdGUoDQogIGRhdGFzZXQgPSBmaW5hbF9uZXQsDQogIGlkID0gImN2SUQiLA0KICBkZXBlbmRlbnRWYXJpYWJsZSA9ICJjb3VudFBlcm1pdHMiLA0KICBpbmRWYXJpYWJsZXMgPSByZWcudmFycykgJT4lDQogICAgZHBseXI6OnNlbGVjdChjdklEID0gY3ZJRCwgY291bnRQZXJtaXRzLCBQcmVkaWN0aW9uLCBnZW9tZXRyeSkNCg0KcmVnLnNzLmN2IDwtIGNyb3NzVmFsaWRhdGUoDQogIGRhdGFzZXQgPSBmaW5hbF9uZXQsDQogIGlkID0gImN2SUQiLA0KICBkZXBlbmRlbnRWYXJpYWJsZSA9ICJjb3VudFBlcm1pdHMiLA0KICBpbmRWYXJpYWJsZXMgPSByZWcuc3MudmFycykgJT4lDQogICAgZHBseXI6OnNlbGVjdChjdklEID0gY3ZJRCwgY291bnRQZXJtaXRzLCBQcmVkaWN0aW9uLCBnZW9tZXRyeSkNCg0KZmluYWxfbmV0JG5hbWUgPC0gaWZlbHNlKGlzLm5hKGZpbmFsX25ldCRuYW1lKSwgIlVOS05PV04iLCBmaW5hbF9uZXQkbmFtZSkNCiAgDQpyZWcuc3BhdGlhbENWIDwtIGNyb3NzVmFsaWRhdGUoDQogIGRhdGFzZXQgPSBmaW5hbF9uZXQsDQogIGlkID0gIm5hbWUiLA0KICBkZXBlbmRlbnRWYXJpYWJsZSA9ICJjb3VudFBlcm1pdHMiLA0KICBpbmRWYXJpYWJsZXMgPSByZWcudmFycykgJT4lDQogICAgZHBseXI6OnNlbGVjdChjdklEID0gbmFtZSwgY291bnRQZXJtaXRzLCBQcmVkaWN0aW9uLCBnZW9tZXRyeSkNCg0KcmVnLnNzLnNwYXRpYWxDViA8LSBjcm9zc1ZhbGlkYXRlKA0KICBkYXRhc2V0ID0gZmluYWxfbmV0LA0KICBpZCA9ICJuYW1lIiwNCiAgZGVwZW5kZW50VmFyaWFibGUgPSAiY291bnRQZXJtaXRzIiwNCiAgaW5kVmFyaWFibGVzID0gcmVnLnNzLnZhcnMpICU+JQ0KICAgIGRwbHlyOjpzZWxlY3QoY3ZJRCA9IG5hbWUsIGNvdW50UGVybWl0cywgUHJlZGljdGlvbiwgZ2VvbWV0cnkpDQoNCmBgYA0KDQojIyMgNi41IENhbGN1bGF0aW5nIEVycm9ycw0KDQoNCkluIGVhY2ggcmVncmVzc2lvbiwgdGhlIGFic29sdXRlIGVycm9yIGlzIGNhbGN1bGF0ZWQgYnkgZGV0ZXJtaW5pbmcgdGhlIGRpZmZlcmVuY2UgYmV0d2VlbiB0aGUgcHJlZGljdGVkIGNvdW50IG9mIG5ldyBjb25zdHJ1Y3Rpb24gcGVybWl0cyBhbmQgdGhlIGFjdHVhbCBvYnNlcnZlZCBjb3VudCBvZiBjb25zdHJ1Y3Rpb24gcGVybWl0cy4NCg0KYGBge3IgUmVncmVzc2lvbiBTdW1tYXJ5LCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQ0KDQojIENhbGN1bGF0aW5nIEVycm9ycyBiYXNlZCBvbiBDcm9zcyBWYWxpZGF0aW9uIG1vZGVscw0KDQpyZWcuc3VtbWFyeSA8LSANCiAgcmJpbmQoDQogICAgbXV0YXRlKHJlZy5jdiwgICAgICAgICAgIEVycm9yID0gUHJlZGljdGlvbiAtIGNvdW50UGVybWl0cywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgUmVncmVzc2lvbiA9ICJSYW5kb20gay1mb2xkIENWOiBKdXN0IFJpc2sgRmFjdG9ycyIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICANCiAgICBtdXRhdGUocmVnLnNzLmN2LCAgICAgICAgRXJyb3IgPSBQcmVkaWN0aW9uIC0gY291bnRQZXJtaXRzLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBSZWdyZXNzaW9uID0gIlJhbmRvbSBrLWZvbGQgQ1Y6IFNwYXRpYWwgUHJvY2VzcyIpLA0KICAgIA0KICAgIG11dGF0ZShyZWcuc3BhdGlhbENWLCAgICBFcnJvciA9IFByZWRpY3Rpb24gLSBjb3VudFBlcm1pdHMsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIFJlZ3Jlc3Npb24gPSAiU3BhdGlhbCBMT0dPLUNWOiBKdXN0IFJpc2sgRmFjdG9ycyIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICANCiAgICBtdXRhdGUocmVnLnNzLnNwYXRpYWxDViwgRXJyb3IgPSBQcmVkaWN0aW9uIC0gY291bnRQZXJtaXRzLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBSZWdyZXNzaW9uID0gIlNwYXRpYWwgTE9HTy1DVjogU3BhdGlhbCBQcm9jZXNzIikpICU+JQ0KICAgIHN0X3NmKCkgIA0KDQoNCmBgYA0KDQojIyMgNi42IE1hcHBpbmcgUmVncmVzc2lvbiBFcnJvciBQcmVkaWN0aW9ucyANCg0KVGhlIHJlbGF0aXZlIHBlcmZvcm1hbmNlIG9mIGVhY2ggcmVncmVzc2lvbiBpcyBhc3Nlc3NlZCBieSBhbmFseXppbmcgdGhlIGRpc3RyaWJ1dGlvbnMgb2YgbWVhbiBhYnNvbHV0ZSBlcnJvciAoTUFFKSBhY3Jvc3MgZGlmZmVyZW50IGZvbGRzLiBUaGUgTUFFLCByZXByZXNlbnRpbmcgdGhlIGFic29sdXRlIHZhbHVlIG9mIHRoZSBtZWFuIGVycm9yLCBpcyB1dGlsaXplZCB0byBnYXVnZSB0aGUgYWNjdXJhY3kgb2YgdGhlIHByZWRpY3Rpb25zLiANCg0KIyMjIyA2LjYuMSBBY2N1cmFjeSBhbmQgR2VuZXJhbGl6YWJpbGl0eQ0KDQpBIGhpZ2ggTWVhbiBBYnNvbHV0ZSBFcnJvciAoTUFFKSB2YWx1ZSBpbiBhIG5laWdoYm9yaG9vZCBpbmRpY2F0ZXMgdGhhdCwgb24gYXZlcmFnZSwgdGhlIHByZWRpY3Rpb25zIG1hZGUgYnkgdGhlIG1vZGVsIGZvciBuZXcgY29uc3RydWN0aW9uIHBlcm1pdCBjb3VudHMgaW4gdGhhdCBuZWlnaGJvcmhvb2QgZGV2aWF0ZSBzaWduaWZpY2FudGx5IGZyb20gdGhlIGFjdHVhbCBvYnNlcnZlZCB2YWx1ZXMuIFRoaXMgaW5kaWNhdGVzIHRoYXQgdGhlIG1vZGVsIGlzIGxlc3MgYWNjdXJhdGUgaW4gcHJlZGljdGluZyBjb25zdHJ1Y3Rpb24gcGVybWl0IGNvdW50cyBmb3IgdGhhdCBzcGVjaWZpYyBuZWlnaGJvcmhvb2QuIA0KDQpJbiB0aGUgY2FzZSBvZiB0aGlzIHN0dWR5LCB0aGUgaGlnaGVyIE1BRSB2YWx1ZXMgYXJlIG9ic2VydmVkIGluIHRoZSBmb2xsb3dpbmcgbmVpZ2hib3Job29kcyB3aGlsZSBwZXJmb3JtaW5nIGJvdGggTE9HTyBDViBhbmQgTE9HTyBDViBTcGF0aWFsIFByb2Nlc3MgcmVncmVzc2lvbnMgYW5kIGNyb3NzIHZhbGlkYXRpb25zOg0KDQoqIEVhc3QgS2Vuc2luZ3RvbiwNCiogR3JlZW53aWNoLA0KKiBMdWRsb3csDQoqIE9sZCBLZW5zaW5ndG9uLCBhbmQNCiogUG9pbnQgQnJlZXplDQoNCkhvd2V2ZXIsIGEgbG93ZXIgTWVhbiBBYnNvbHV0ZSBFcnJvciAoZGlmZmVyZW5jZSBvZiBtaW5pbXVtIDEwIGNvdW50cykgaXMgb2JzZXJ2ZWQgZm9yIGVhY2ggb2YgdGhlc2UgbmVpZ2hib3Job29kcyBpbiB0aGUgTE9HTyBDViBzcGF0aWFsIHByb2Nlc3MgcmVncmVzc2lvbiBjb21wYXJlZCB0byB0aGUgTE9HTyBDViBtb2RlbCBzdWdnZXN0aW5nIHRoYXQgaW5jb3Jwb3JhdGluZyB0aGUgc3BhdGlhbCBwcm9jZXNzIGhhcyBpbXByb3ZlZCB0aGUgcHJlZGljdGl2ZSBhY2N1cmFjeSBvZiB0aGUgbW9kZWwgZm9yIHRoYXQgc3BlY2lmaWMgbmVpZ2hib3Job29kLiBPdmVyYWxsIHRvbywgdGhlIHNwYXRpYWwgcHJvY2VzcyBtb2RlbHMgZXhoaWJpdHMgZmV3ZXIgZXJyb3JzIHRoYW4gdGhlIHJpc2sgZmFjdG9yIG1vZGVscyBhbmQgaGFzIGltcHJvdmVkIGdlbmVyYWxpemFiaWxpdHkgYXMgaXQgYWNjb3VudHMgZm9yIHNwYXRpYWwgYXV0b2NvcnJlbGF0aW9uIGFuZCBsb2NhbCB2YXJpYXRpb25zIHdpdGhpbiBuZWlnaGJvcmhvb2RzLg0KDQpUaGUgZm9sbG93aW5nIHBsb3RzIHZpc3VhbGl6ZSB0aGUgcmVsYXRpdmUgcGVyZm9ybWFuY2Ugb2YgZWFjaCByZWdyZXNzaW9uIGJ5IGFuYWx5emluZyB0aGUgZGlzdHJpYnV0aW9ucyBvZiBtZWFuIGFic29sdXRlIGVycm9yIChNQUUpIGZvciBlYWNoIGZvbGQuDQoNCmBgYHtyIEVycm9ycyBieSBMT0dPX0NWLCByZXN1bHRzPSdoaWRlJywgZmlnLmhlaWdodD01fQ0KDQojIE1vZGVsIEVycm9yIEdyb3VwaW5nIChNQUUpDQoNCmVycm9yX2J5X3JlZ19hbmRfZm9sZCA8LQ0KICByZWcuc3VtbWFyeSAlPiUNCiAgICBncm91cF9ieShSZWdyZXNzaW9uLCBjdklEKSAlPiUNCiAgICBzdW1tYXJpemUoTWVhbl9FcnJvciA9IG1lYW4oUHJlZGljdGlvbiAtIGNvdW50UGVybWl0cywgbmEucm0gPSBUKSwNCiAgICAgICAgICAgICAgTUFFID0gbWVhbihhYnMoTWVhbl9FcnJvciksIG5hLnJtID0gVCksDQogICAgICAgICAgICAgIFNEX01BRSA9IG1lYW4oYWJzKE1lYW5fRXJyb3IpLCBuYS5ybSA9IFQpKSAlPiUNCiAgdW5ncm91cCgpDQoNCmVycm9yX2J5X3JlZ19hbmRfZm9sZCAlPiUNCiAgYXJyYW5nZShkZXNjKE1BRSkpDQplcnJvcl9ieV9yZWdfYW5kX2ZvbGQgJT4lDQogIGFycmFuZ2UoTUFFKQ0KDQojIEhpc3RvZ3JhbSBvZiBFcnJvcnMgYnkgbW9kZWwNCg0KZXJyb3JfYnlfcmVnX2FuZF9mb2xkICU+JQ0KICBnZ3Bsb3QoYWVzKE1BRSkpICsgDQogICAgZ2VvbV9oaXN0b2dyYW0oYmlucyA9IDMwLCBjb2xvdXI9ImJsYWNrIiwgZmlsbCA9ICIjRkRFNzI1RkYiKSArDQogIGZhY2V0X3dyYXAoflJlZ3Jlc3Npb24pICsgDQogIHNjYWxlX3hfY29udGludW91cyhicmVha3MgPSBzZXEoMCwgMTEsIGJ5ID0gMSkpICsgDQogICAgbGFicyh0aXRsZT0iRGlzdHJpYnV0aW9uIG9mIE1BRSIsIHN1YnRpdGxlID0gIlJhbmRvbSBLLUZvbGQgYW5kIExPR08tQ1YiLCBjYXB0aW9uID0gIkZpZ3VyZSAxNiIsDQogICAgICAgICB4PSJNZWFuIEFic29sdXRlIEVycm9yIiwgeT0iQ291bnQiKSsNCiAgICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxNSwgZmFjZT0gImJvbGQiLCBjb2xvciA9ICJkYXJrcmVkIikpDQoNCg0KYGBgDQoNClRoZSBoaXN0b2dyYW1zIGluIEZpZ3VyZSAxNiByZXZlYWwgdGhhdCByZWdyZXNzaW9ucyBpbmNvcnBvcmF0aW5nIHRoZSBEaXN0YW5jZSB0byBIb3RzcG90IHZhcmlhYmxlcyBleGhpYml0IG1vcmUgY2x1c3RlcmVkIGVycm9ycywgY2hhcmFjdGVyaXplZCBieSBmZXdlciBlcnJvcnMgb2YgaGlnaCBtYWduaXR1ZGVzLiBMT0dPLUNWLCB1bmRlciB0aGUgYXNzdW1wdGlvbiB0aGF0IHNwYXRpYWwgcHJvY2Vzc2VzIGluIG90aGVyIG5laWdoYm9yaG9vZHMgYXBwbHkgdG8gdGhlIG9uZSBoZWxkIG91dCwgbWF5IG5vdCBhY2N1cmF0ZWx5IGNhcHR1cmUgdGhlIGxvY2FsIHZhcmlhYmlsaXR5LiBUaGUgaW5jbHVzaW9uIG9mIHRoZSBEaXN0YW5jZSB0byBIb3RzcG90IHZhcmlhYmxlIGhlbHBzIGFkZHJlc3MgdGhpcyBpc3N1ZSwgZW5oYW5jaW5nIHByZWRpY3RpdmUgY2FwYWJpbGl0eSwgZXNwZWNpYWxseSBpbiBob3RzcG90cyB3aXRoIGhpZ2ggcGVybWl0IGNvdW50cy4gDQoNClRoZSBmb2xsb3dpbmcgbWFwcyBpbGx1c3RyYXRlIHRoZSByZWR1Y3Rpb24gaW4gbWVhbiBhYnNvbHV0ZSBlcnJvciAoTUFFKSBhY3Jvc3Mgc3BhY2UsIGVtcGhhc2l6aW5nIHRoZSBpbXByb3ZlbWVudCBhY2hpZXZlZCBieSBpbmNvcnBvcmF0aW5nIHRoZSBzcGF0aWFsIHByb2Nlc3MgdmFyaWFibGUgYWxvbmdzaWRlIHJpc2sgZmFjdG9ycy4NCg0KYGBge3IgTWFwcGluZyBTcGF0aWFsIExPR08gQ1YgRXJyb3JzLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRSwgZmlnLndpZHRoPTE0LCBmaWcuaGVpZ2h0PTh9DQoNCiMgTWFwcGluZyBTcGF0aWFsIExPR08gQ1YgRXJyb3JzIHdpdGggYW5kIHdpdGhvdXQgU3BhdGlhbCBQcm9jZXNzIGluY29ycG9yYXRpb24NCg0KZXJyb3JfYnlfcmVnX2FuZF9mb2xkICU+JQ0KICBmaWx0ZXIoc3RyX2RldGVjdChSZWdyZXNzaW9uLCAiTE9HTyIpKSAlPiUNCiAgZ2dwbG90KCkgKw0KICBnZW9tX3NmKGRhdGEgPSBwaGlsbHlCb3VuZGFyeSwgZmlsbCA9ICJ3aGl0ZSIsIGNvbG9yID0gImRhcmtncmV5IikgKw0KICAgIGdlb21fc2YoYWVzKGZpbGwgPSBNQUUpKSArDQogICAgZmFjZXRfd3JhcCh+UmVncmVzc2lvbikgKw0KICAgIHNjYWxlX2NvbG91cl92aXJpZGlzKG9wdGlvbiA9ICJwbGFzbWEiKSArDQogICAgc2NhbGVfZmlsbF92aXJpZGlzKG9wdGlvbiA9ICJwbGFzbWEiKSArDQogICAgbGFicyh0aXRsZSA9ICJFcnJvcnMgYnkgTE9HTy1DViBSZWdyZXNzaW9uIiwgY2FwdGlvbj0iRmlndXJlIDE1IikgKw0KICAgIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDE1LCBmYWNlPSAiYm9sZCIsIGNvbG9yID0gImRhcmtyZWQiKSkrDQogICAgdGhlbWUoc3RyaXAudGV4dCA9IGVsZW1lbnRfdGV4dChzaXplID0gOCkpDQoNCmBgYA0KDQpUYWJsZSAxLCAoYmVsb3cpIGRldGFpbGluZyB0aGUgbWVhbiBNQUUgYW5kIHN0YW5kYXJkIGRldmlhdGlvbiBvZiBNQUUsIGluZGljYXRlcyB0aGF0IHRoZSByYW5kb20gay1mb2xkIHdpdGggc3BhdGlhbCBwcm9jZXNzZXMgZXhoaWJpdHMgdGhlIHNtYWxsZXN0IGVycm9yLiBPbiB0aGUgb3RoZXIgaGFuZCwgdGhlIHNwYXRpYWwgTE9HTyBpcyBsaWtlbHkgdG8gaGF2ZSBhIGhpZ2hlciBlcnJvciBkdWUgdG8gaXRzIGFzc3VtcHRpb24gdGhhdCBleHBlcmllbmNlcyBpbiBhbGwgb3RoZXIgbmVpZ2hib3Job29kcyBjYW4gYWNjdXJhdGVseSBhcHBseSB0byBhIHNpbmdsZSBvbmUuDQoNCmBgYHtyIHRhYmxlMSBNQUUgYW5kIFNEIE1BRSwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0NCg0KIyBUYWJsZSBvZiBNQUUgYW5kIFN0YW5kYXJkIERldmlhdGlvbiBNQUUNCg0Kc3RfZHJvcF9nZW9tZXRyeShlcnJvcl9ieV9yZWdfYW5kX2ZvbGQpICU+JQ0KICBncm91cF9ieShSZWdyZXNzaW9uKSAlPiUgDQogICAgc3VtbWFyaXplKE1lYW5fTUFFID0gcm91bmQobWVhbihNQUUpLCAyKSwNCiAgICAgICAgICAgICAgU0RfTUFFID0gcm91bmQoc2QoTUFFKSwgMikpICU+JQ0KICBrYWJsZShjYXB0aW9uID0gIlRhYmxlIDE6IE1BRSBhbmQgc3RhbmRhcmQgZGV2aWF0aW9uIE1BRSBieSByZWdyZXNzaW9uIikgJT4lDQogICAga2FibGVfc3R5bGluZygic3RyaXBlZCIsIGZ1bGxfd2lkdGggPSBGKSAlPiUNCiAgICByb3dfc3BlYygyLCBjb2xvciA9ICJibGFjayIsIGJhY2tncm91bmQgPSAiI0ZERTcyNUZGIikgJT4lDQogICAgcm93X3NwZWMoNCwgY29sb3IgPSAiYmxhY2siLCBiYWNrZ3JvdW5kID0gIiNGREU3MjVGRiIpICANCmBgYA0KDQpUYWJsZSAyIGJlbG93IGlsbHVzdHJhdGVzIHRoYXQgYm90aCBMT0dPX0NWIHJlZ3Jlc3Npb24gbW9kZWxzIGV4aGliaXQgc3BhdGlhbCBhdXRvY29ycmVsYXRpb24gaW4gdGhlIGVycm9ycywgd2l0aCB0aGUgc3BhdGlhbCBwcm9jZXNzIG1vZGVsIGhhdmluZyBhIGhpZ2hlciBNb3JhbidzIEkgYW5kIGEgbG93ZXIgcC12YWx1ZSwgaW5kaWNhdGluZyBhIHN0cm9uZ2VyIHNwYXRpYWwgY2x1c3RlcmluZyBwYXR0ZXJuIGluIGl0cyBlcnJvcnMuIEluIHRoaXMgY29udGV4dCwgYSBsb3dlciBwLXZhbHVlIChlLmcuLCAwLjAwMSkgaW5kaWNhdGVzIHRoYXQgdGhlIG9ic2VydmVkIHNwYXRpYWwgYXV0b2NvcnJlbGF0aW9uIGluIHRoZSBlcnJvcnMgb2YgdGhlIHNwYXRpYWwgcHJvY2VzcyBtb2RlbCBpcyBzdGF0aXN0aWNhbGx5IHNpZ25pZmljYW50LiBUaGlzIHN1Z2dlc3RzIHRoYXQgdGhlcmUgYXJlIHNwYXRpYWwgcGF0dGVybnMgb3IgZGVwZW5kZW5jaWVzIGluIHRoZSBkYXRhIHRoYXQgdGhlIG1vZGVsIGhhcyBjYXB0dXJlZCwgYW5kIHRoZXNlIHBhdHRlcm5zIGFyZSBub3QgZXhwbGFpbmVkIGJ5IHJhbmRvbSB2YXJpYWJpbGl0eSBhbG9uZS4NCg0KYGBge3IgdGFibGUyIE1vcmFucyBFcnJvcnMsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9DQoNCiMgVGFibGUgb24gTW9yYW4ncyBJIG9uIEVycm9ycyBieSBSZWdyZXNzaW9uDQoNCm5laWdoYm9yaG9vZC53ZWlnaHRzIDwtDQogIGZpbHRlcihlcnJvcl9ieV9yZWdfYW5kX2ZvbGQsIFJlZ3Jlc3Npb24gPT0gIlNwYXRpYWwgTE9HTy1DVjogU3BhdGlhbCBQcm9jZXNzIikgJT4lDQogICAgZ3JvdXBfYnkoY3ZJRCkgJT4lDQogICAgICBwb2x5Mm5iKGFzX1NwYXRpYWwoLiksIHF1ZWVuPVRSVUUpICU+JQ0KICAgICAgbmIybGlzdHcoLiwgc3R5bGU9IlciLCB6ZXJvLnBvbGljeT1UUlVFKQ0KDQpmaWx0ZXIoZXJyb3JfYnlfcmVnX2FuZF9mb2xkLCBzdHJfZGV0ZWN0KFJlZ3Jlc3Npb24sICJMT0dPIikpICAlPiUgDQogICAgc3RfZHJvcF9nZW9tZXRyeSgpICU+JQ0KICAgIGdyb3VwX2J5KFJlZ3Jlc3Npb24pICU+JQ0KICAgIHN1bW1hcml6ZShNb3JhbnNfSSA9IG1vcmFuLm1jKGFicyhNZWFuX0Vycm9yKSwgbmVpZ2hib3Job29kLndlaWdodHMsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbnNpbSA9IDk5OSwgemVyby5wb2xpY3kgPSBUUlVFLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5hLmFjdGlvbj1uYS5vbWl0KVtbMV1dLA0KICAgICAgICAgICAgICBwX3ZhbHVlID0gbW9yYW4ubWMoYWJzKE1lYW5fRXJyb3IpLCBuZWlnaGJvcmhvb2Qud2VpZ2h0cywgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuc2ltID0gOTk5LCB6ZXJvLnBvbGljeSA9IFRSVUUsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbmEuYWN0aW9uPW5hLm9taXQpW1szXV0pICU+JSANCiAga2FibGUoY2FwdGlvbiA9ICJUYWJsZSAyOiBNb3JhbidzIEkgb24gRXJyb3JzIGJ5IFJlZ3Jlc3Npb24iKSAlPiUNCiAgICBrYWJsZV9zdHlsaW5nKCJzdHJpcGVkIiwgZnVsbF93aWR0aCA9IEYpICU+JQ0KICAgIHJvd19zcGVjKDEsIGNvbG9yID0gImJsYWNrIiwgYmFja2dyb3VuZCA9ICIjRkRFNzI1RkYiKSAlPiUNCiAgICByb3dfc3BlYygxLCBjb2xvciA9ICJibGFjayIsIGJhY2tncm91bmQgPSAiI0ZERTcyNUZGIikgDQpgYGANCg0KIyMjIyA2LjYuMiBQcmVkaWN0ZWQgdnMgT2JzZXJ2ZWQgT3V0Y29tZXMNCg0KVGhlIGFib3ZlIHJlc3VsdHMgYXJlIGZ1cnRoZXIgYW5hbHl6ZWQgYnkgZXhhbWluaW5nIHRoZSBNRUEgYnkgZGVjaWxlIG9mIHBlcm1pdCBjb3VudHMuIFRoZSBpbmNsdXNpb24gb2YgdGhlIGhvdHNwb3QgdmFyaWFibGUgc2lnbmlmaWNhbnRseSBtaXRpZ2F0ZXMgZXJyb3JzIGluIHJlZ2lvbnMgd2l0aCB0aGUgaGlnaGVzdCBwZXJtaXQgZGVuc2l0eS4gSG93ZXZlciwgYSBjb25zaXN0ZW50IHBhdHRlcm4gZW1lcmdlcyBhY3Jvc3MgYWxsIG1vZGVscywgd2hlcmUgdGhleSB0ZW5kIHRvIHVuZGVyZXN0aW1hdGUgaW4gaGlnaC1kZW5zaXR5IHBlcm1pdHRpbmcgYXJlYXMgYW5kIG92ZXJlc3RpbWF0ZSBpbiBsb3ctZGVuc2l0eSBwZXJtaXR0aW5nIGFyZWFzLiANCg0KYGBge3IgUHJlZHZzT2JzLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQ0KDQojIFBsb3R0aW5nIFByZWRpY3RlZCB2cyBPYnNlcnZlZCBQZXJtaXRzIGJ5IERlY2lsZSBvZiBPYnNlcnZlZCBQZXJtaXRzIA0KDQpzdF9kcm9wX2dlb21ldHJ5KHJlZy5zdW1tYXJ5KSAlPiUNCiAgZ3JvdXBfYnkoUmVncmVzc2lvbikgJT4lDQogICAgbXV0YXRlKFBlcm1pdF9EZWNpbGUgPSBudGlsZShjb3VudFBlcm1pdHMsIDEwKSkgJT4lDQogIGdyb3VwX2J5KFJlZ3Jlc3Npb24sIFBlcm1pdF9EZWNpbGUpICU+JQ0KICAgIHN1bW1hcml6ZShtZWFuT2JzZXJ2ZWQgPSBtZWFuKGNvdW50UGVybWl0cywgbmEucm09VCksDQogICAgICAgICAgICAgIG1lYW5QcmVkaWN0aW9uID0gbWVhbihQcmVkaWN0aW9uLCBuYS5ybT1UKSkgJT4lDQogICAgZ2F0aGVyKFZhcmlhYmxlLCBWYWx1ZSwgLVJlZ3Jlc3Npb24sIC1QZXJtaXRfRGVjaWxlKSAlPiUgICAgICAgICAgDQogICAgZ2dwbG90KGFlcyhQZXJtaXRfRGVjaWxlLCBWYWx1ZSwgc2hhcGUgPSBWYXJpYWJsZSkpICsNCiAgICAgIGdlb21fcG9pbnQoc2l6ZSA9IDIpICsgZ2VvbV9wYXRoKGFlcyhncm91cCA9IFBlcm1pdF9EZWNpbGUpLCBjb2xvdXIgPSAiYmxhY2siKSArDQogICAgICBzY2FsZV9zaGFwZV9tYW51YWwodmFsdWVzID0gYygyLCAxNykpICsNCiAgICAgIGZhY2V0X3dyYXAoflJlZ3Jlc3Npb24pICsgeGxpbSgwLDEwKSArDQogICAgICBsYWJzKHRpdGxlID0gIlByZWRpY3RlZCBhbmQgT2JzZXJ2ZWQgUGVybWl0cyBieSBPYnNlcnZlZCBQZXJtaXQgRGVjaWxlIiwgY2FwdGlvbiA9ICJGaWd1cmUgMTYiKSAgKw0KICAgICAgdGhlbWUoc3RyaXAudGV4dCA9IGVsZW1lbnRfdGV4dChzaXplID0gOCkpICsgDQogICAgICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxNSwgZmFjZT0gImJvbGQiLCBjb2xvciA9ICJkYXJrcmVkIikpDQoNCmBgYA0KDQojIyMjIDYuNi4zIFJhY2lhbCBHZW5lcmFsaXphYmlsaXR5DQoNCkFub3RoZXIgY3J1Y2lhbCBhc3BlY3Qgb2YgZ2VuZXJhbGl6YWJpbGl0eSBpbnZvbHZlcyBjb25zaWRlcmluZyBzb2NpYWwgZGltZW5zaW9ucyBsaWtlIHJhY2UgYW5kIGluY29tZS4gVGhlIGZvbGxvd2luZyB0YWJsZSAoVGFibGUgMykgaWxsdXN0cmF0ZXMgdGhlIHBlcm1pdHRpbmcgcGF0dGVybnMgaW4gdGhlIHJhY2lhbCBjb250ZXh0IG9mIFBoaWxhZGVscGhpYSwgZGlzdGluZ3Vpc2hpbmcgYmV0d2VlbiBtYWpvcml0eSB3aGl0ZSBhbmQgbm9uLXdoaXRlIGNlbnN1cyB0cmFjdHMuDQoNCmBgYHtyIHJhY2UgcHJlZGljdGlvbnMsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGluY2x1ZGU9VFJVRX0NCg0KIyBVbmRlcnN0YW5kaW5nIFByZWRpY3Rpb24gYnkgUmFjZSBDb250ZXh0DQoNCnRyYWN0czIxbmV3IDwtIHRyYWN0czIxICU+JQ0KICBzdF90cmFuc2Zvcm0oJ0VTUkk6MTAyNzI4JykgICU+JSANCiAgbXV0YXRlKHJhY2VDb250ZXh0ID0gaWZlbHNlKHBjdFdoaXRlID4gNTAsICJNYWpvcml0eV9XaGl0ZSIsICJNYWpvcml0eV9Ob25fV2hpdGUiKSkgJT4lDQogIC5bcGhpbGx5TmVpZ2hib3Job29kcyxdDQoNCnJlZy5zdW1tYXJ5ICU+JSANCiAgZmlsdGVyKHN0cl9kZXRlY3QoUmVncmVzc2lvbiwgIkxPR08iKSkgJT4lDQogICAgc3RfY2VudHJvaWQoKSAlPiUNCiAgICBzdF9qb2luKHRyYWN0czIxbmV3KSAlPiUNCiAgICBuYS5vbWl0KCkgJT4lDQogICAgICBzdF9kcm9wX2dlb21ldHJ5KCkgJT4lDQogICAgICBncm91cF9ieShSZWdyZXNzaW9uLCByYWNlQ29udGV4dCkgJT4lDQogICAgICBzdW1tYXJpemUobWVhbi5FcnJvciA9IG1lYW4oRXJyb3IsIG5hLnJtID0gVCkpICU+JQ0KICAgICAgc3ByZWFkKHJhY2VDb250ZXh0LCBtZWFuLkVycm9yKSAlPiUNCiAgICAgIGthYmxlKGNhcHRpb24gPSAiVGFibGUgMzogTWVhbiBFcnJvciBieSBuZWlnaGJvcmhvb2QgcmFjaWFsIGNvbnRleHQiKSAlPiUNCiAgICAgICAga2FibGVfc3R5bGluZygic3RyaXBlZCIsIGZ1bGxfd2lkdGggPSBGKSAlPiUNCiAgICByb3dfc3BlYygxLCBjb2xvciA9ICJibGFjayIsIGJhY2tncm91bmQgPSAiI0ZERTcyNUZGIikNCg0KYGBgDQoNCldoZW4gZXhhbWluaW5nIHRoZSBtZWFuIGVycm9yIHNwbGl0IGJ5IHJhY2lhbCBjb250ZXh0LCBpdCBiZWNvbWVzIGV2aWRlbnQgdGhhdCB0aGUgaW5jb3Jwb3JhdGlvbiBvZiB0aGUgc3BhdGlhbCBwcm9jZXNzIHJlZHVjZXMgdGhlIG1hZ25pdHVkZSBvZiBlcnJvcnMgaW4gYm90aCBub24td2hpdGUgYW5kIHdoaXRlIG5laWdoYm9yaG9vZHMuIEhvd2V2ZXIsIGEgbm90YWJsZSBwYXR0ZXJuIGVtZXJnZXMgd2hlcmUgdGhlIG1vZGVsIHRlbmRzIHRvIHVuZGVyZXN0aW1hdGUgdGhlIHJpc2sgb2YgbmV3IGNvbnN0cnVjdGlvbiBwZXJtaXRzIGluIG5vbi13aGl0ZSBhcmVhcyBhbmQgb3ZlcmVzdGltYXRlIGl0IGluIHdoaXRlIGFyZWFzLiBUaGlzIG9ic2VydmVkIHRlbmRlbmN5IGlzIGEgY29uY2VybiwgaGlnaGxpZ2h0aW5nIHRoZSBwb3RlbnRpYWwgZm9yIGRlY2lzaW9uLW1ha2VycyB0byBiZSBtaXNsZWQgYnkgbG93ZXIgcmlzayBwcmVkaWN0aW9ucyBpbiBtYWpvcml0eSBub24td2hpdGUgbmVpZ2hib3Job29kcy4gU3VjaCB1bmRlcnByZWRpY3Rpb24gY291bGQgcmVzdWx0IGluIGEgbWlzYWxsb2NhdGlvbiBvZiByZXNvdXJjZXMgYW5kIGxlc3NlciBpbXBvcnRhbmNlIGdpdmVuIGZvciBpbXBsZW1lbnRpbmcgZ3JlZW4gYnVpbGRpbmcgc3RyYXRlZ2llcyBpbiB0aGVzZSBhcmVhcywgcG90ZW50aWFsbHkgZXhhY2VyYmF0aW5nIGV4aXN0aW5nIGRpc3Bhcml0aWVzLiBGdXR1cmUgaXRlcmF0aW9ucyBvZiB0aGUgYW5hbHlzaXMgYWltIHRvIGVuaGFuY2UgdGhlIG1vZGVsJ3MgZ2VuZXJhbGl6YWJpbGl0eSBhY3Jvc3MgcmFjaWFsIGNvbnRleHRzIHRvIG1pdGlnYXRlIHRoZSByaXNrIG9mIHBlcnBldHVhdGluZyByZXNvdXJjZSBpbmVxdWl0aWVzLiANCg0KVGhlc2UgdGVuZGVuY2llcyBhcmUgY2FwdHVyZWQgaW4gRmlndXJlIDE3IHRoYXQgY29tcGFyZXMgZXJyb3JzIGJ5IHJhY2UgY29udGV4dCBmb3IgZGlmZmVyZW50IG5laWdoYm9yaG9vZHMgZnJvbSBhIGZpc2huZXQgbGV2ZWwuIA0KDQpgYGB7ciByYWNlIG1hcCBkYXRhLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFLCBpbmNsdWRlPUZBTFNFLCBmaWcud2lkdGg9MTQsIGZpZy5oZWlnaHQ9Nn0NCg0KIyBQbG90dGluZyBSYWNlIENvbnRleHQNCg0Kam9pbmVkX3NmX3NwYXRpYWwgPC0gc3Rfam9pbihyZWcuc3VtbWFyeSwgdHJhY3RzMjFuZXcsIGJ5ID0gImdlb21ldHJ5IikgJT4lIG5hLm9taXQoKQ0KDQoNCmpvaW5lZF9zZl9zcGF0aWFsICU+JSANCiAgZmlsdGVyKFJlZ3Jlc3Npb24gPT0gIlNwYXRpYWwgTE9HTy1DVjogU3BhdGlhbCBQcm9jZXNzIikNCg0KDQpqb2luZWRfc2Zfc3BhdGlhbCA8LSBqb2luZWRfc2Zfc3BhdGlhbCAlPiUNCiAgbXV0YXRlKEVycm9yYnlSYWNlQ29udGV4dCA9IGNhc2Vfd2hlbigNCiAgICByYWNlQ29udGV4dCA9PSAiTWFqb3JpdHlfV2hpdGUiICYgRXJyb3IgPiAwICB+ICdXaGl0ZSAtIE92ZXIgUHJlZGljdGVkJywNCiAgICByYWNlQ29udGV4dCA9PSAiTWFqb3JpdHlfV2hpdGUiICYgRXJyb3IgPCAwICB+ICdXaGl0ZSAtIFVuZGVyIFByZWRpY3RlZCcsDQogICAgcmFjZUNvbnRleHQgPT0gIk1ham9yaXR5X05vbl9XaGl0ZSIgJiBFcnJvciA+IDAgIH4gJ05vbiBXaGl0ZSAtIE92ZXIgUHJlZGljdGVkJywNCiAgICByYWNlQ29udGV4dCA9PSAiTWFqb3JpdHlfTm9uX1doaXRlIiAmIEVycm9yIDwgMCAgfiAnTm9uIFdoaXRlIC0gVW5kZXIgUHJlZGljdGVkJywNCiAgICBUUlVFIH4gJ090aGVyJyAgDQogICkpDQpgYGANCg0KYGBge3IgbWFwLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFLCBpbmNsdWRlPVRSVUUsIGZpZy53aWR0aD03LCBmaWcuaGVpZ2h0PTZ9DQoNCiMgUGxvdHRpbmcgUmFjZSBDb250ZXh0DQoNCg0KZ2dwbG90KCkrDQogICAgZ2VvbV9zZihkYXRhID0gcGhpbGx5Qm91bmRhcnksIGZpbGwgPSAid2hpdGUiLCBjb2xvciA9ICJkYXJrZ3JleSIpICsNCiAgICBnZW9tX3NmKGRhdGE9dHJhY3RzMjFuZXcsIGFlcyhmaWxsID1yYWNlQ29udGV4dCApKSsNCiAgICBzY2FsZV9maWxsX3ZpcmlkaXMoZGlzY3JldGU9VFJVRSkrDQogICAgbGFicyh0aXRsZT0iUmFjZSBDb250ZXh0IC0gUGhpbGFkZWxwaGlhLCAyMDIxIiwgY2FwdGlvbj0iRmlndXJlIDE3LjEiKSsNCiAgICBtYXBUaGVtZSgpKw0KICAgIHBsb3RUaGVtZSgpDQoNCiAgZ2dwbG90KCkrDQogICAgZ2VvbV9zZihkYXRhID0gcGhpbGx5Qm91bmRhcnksIGZpbGwgPSAid2hpdGUiLCBjb2xvciA9ICJkYXJrZ3JleSIpICsNCiAgICBnZW9tX3NmKGRhdGE9am9pbmVkX3NmX3NwYXRpYWwsIGFlcyhmaWxsID1FcnJvcmJ5UmFjZUNvbnRleHQgKSkrDQogICAgc2NhbGVfZmlsbF92aXJpZGlzKGRpc2NyZXRlPVRSVUUpKw0KICAgIGxhYnModGl0bGU9IkVycm9ycyBieSBSYWNlIC0gTE9HTyBDViBTcGF0aWFsIiwgY2FwdGlvbj0iRmlndXJlIDE3LjIiKSsNCiAgICBtYXBUaGVtZSgpKw0KICAgIHBsb3RUaGVtZSgpDQogDQoNCmBgYA0KDQojIyA3LiBNb2RlbCBBbHRlcm5hdGl2ZXMgYW5kIENvbXBhcmlzb24NCg0KIyMjIDcuMSBLZXJuZWwgRGVuc2l0eSBvZiBQcmVkaWN0ZWQgUGVybWl0cw0KDQpUaGUgcmVzdWx0cyBvZiB0aGUgcHJlZGljdGl2ZSBtb2RlbCBhcmUgYWxzbyBjb21wYXJlZCB3aXRoIHRoZSB0cmFkaXRpb25hbCBLZXJuZWwgRGVuc2l0eSBhcHByb2FjaC4gVG93YXJkcyB0aGlzIGVuZCBrZXJuZWwgZGVuc2l0eSBtYXBzIGFyZSBjcmVhdGVkIGZvciB0aHJlZSBkaWZmZXJlbnQgc2VhcmNoIHJhZGlpIC0gNTAwZnQsIDEwMDBmdCwgYW5kIDE1MDAgZnQuIFRvIGNhcHR1cmUgZGVuc2l0eSwgdGhlIDE1MDBmdCByYWRpdXMgbWFwIGlzIHNlbGVjdGVkIGZvciBjb21wYXJpc29uIHdpdGggdGhlIHByZWRpY3RpdmUgbW9kZWwuIA0KDQpgYGB7ciBLZXJuZWwgRGVuc2l0eSwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRSxmaWcud2lkdGg9MTQsIGZpZy5oZWlnaHQ9Nn0NCg0KIyBDcmVhdGluZyBLZXJuZWwgRGVuc2l0eSB1c2luZyAzIGRpZmZlcmVudCByYWRpaQ0KDQpwZXJtaXRzX3BwcCA8LSBhcy5wcHAoc3RfY29vcmRpbmF0ZXMoY29uc3RydWN0aW9uUGVybWl0cyksIFcgPSBzdF9iYm94KHZhcnNfbmV0KSkNCnBlcm1pdHNfS0QuNTAwIDwtIHNwYXRzdGF0LmV4cGxvcmU6OmRlbnNpdHkucHBwKHBlcm1pdHNfcHBwLCA1MDApDQpwZXJtaXRzX0tELjEwMDAgPC0gc3BhdHN0YXQuZXhwbG9yZTo6ZGVuc2l0eS5wcHAocGVybWl0c19wcHAsIDEwMDApDQpwZXJtaXRzX0tELjE1MDAgPC0gc3BhdHN0YXQuZXhwbG9yZTo6ZGVuc2l0eS5wcHAocGVybWl0c19wcHAsIDE1MDApDQpwZXJtaXRzX0tELmRmIDwtIHJiaW5kKA0KICBtdXRhdGUoZGF0YS5mcmFtZShyYXN0ZXJUb1BvaW50cyhtYXNrKHJhc3RlcihwZXJtaXRzX0tELjUwMCksIGFzKHBoaWxseU5laWdoYm9yaG9vZHMsICdTcGF0aWFsJykpKSksIExlZ2VuZCA9ICI1MDAgRnQuIiksDQogIG11dGF0ZShkYXRhLmZyYW1lKHJhc3RlclRvUG9pbnRzKG1hc2socmFzdGVyKHBlcm1pdHNfS0QuMTAwMCksIGFzKHBoaWxseU5laWdoYm9yaG9vZHMsICdTcGF0aWFsJykpKSksIExlZ2VuZCA9ICIxMDAwIEZ0LiIpLA0KICBtdXRhdGUoZGF0YS5mcmFtZShyYXN0ZXJUb1BvaW50cyhtYXNrKHJhc3RlcihwZXJtaXRzX0tELjE1MDApLCBhcyhwaGlsbHlOZWlnaGJvcmhvb2RzLCAnU3BhdGlhbCcpKSkpLCBMZWdlbmQgPSAiMTUwMCBGdC4iKSkgDQoNCnBlcm1pdHNfS0QuZGYkTGVnZW5kIDwtIGZhY3RvcihwZXJtaXRzX0tELmRmJExlZ2VuZCwgbGV2ZWxzID0gYygiNTAwIEZ0LiIsICIxMDAwIEZ0LiIsICIxNTAwIEZ0LiIpKQ0KDQojIFBsb3R0aW5nIERlbnNpdHkgDQoNCmdncGxvdChkYXRhPXBlcm1pdHNfS0QuZGYsIGFlcyh4PXgsIHk9eSkpICsNCiAgZ2VvbV9yYXN0ZXIoYWVzKGZpbGw9bGF5ZXIpKSArIA0KICBmYWNldF93cmFwKH5MZWdlbmQpICsNCiAgY29vcmRfc2YoY3JzPXN0X2Nycyh2YXJzX25ldCkpICsgDQogIHNjYWxlX2ZpbGxfdmlyaWRpcyhvcHRpb24gPSAncGxhc21hJyxuYW1lPSJEZW5zaXR5IikgKw0KICBsYWJzKHRpdGxlID0gIktlcm5lbCBkZW5zaXR5IE1hcHMgd2l0aCAzIGRpZmZlcmVudCBzZWFyY2ggcmFkaWkiLCBjYXB0aW9uPSJGaWd1cmUgMTgiKSArDQogIG1hcFRoZW1lKCkrDQogIHBsb3RUaGVtZSgpDQoNCmBgYA0KDQojIyMgNy4yIENvbXBhcmlzb24gb2YgTW9kZWxzIA0KDQpUaGUgY29tcGFyaXNvbiBpbiBGaWd1cmUgMTkgc2hvd3MgdGhhdCB3aGlsZSB0aGUga2VybmVsIGRlbnNpdHkgbW9kZWwgZG9lcyBhIHJlbGF0aXZlbHkgZ29vZCBqb2IgYXQgYWNjb3VudGluZyBmb3IgYW5kIHByZWRpY3RpbmcgcGVybWl0cyBpbiAyMDIyLCB0aGUgcHJlZGljdGl2ZSBtb2RlbCBpcyBtb3JlIGFjY3VyYXRlLiBIb3dldmVyLCB0aGUgYmFyIHBsb3RzIGluIEZpZ3VyZSAyMCBpbmRpY2F0ZSB0aGF0IHRoZSBwcmVkaWN0aXZlIG1vZGVsIHVuZGVycHJlZGljdHMgYnkgYSBzbGltIG1hcmdpbiBjYXNlcyBvZiBoaWdoIGFuZCBtZWRpdW0gcmlzayBhbmQgb3ZlcnByZWRpY3RzIGluIGFyZWFzIG9mIGxvdyByaXNrLiANCg0KYGBge3IgS0QgdnMgUmlzayBQcmVkaWN0aW9uIE1vZGVsLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFLCBmaWcud2lkdGg9MTQsIGZpZy5oZWlnaHQ9OH0NCg0KIyBQcmVkaWN0aW9uIGJ5IEtlcm5lbCBEZW5zaXR5IE1vZGVsDQoNCnBlcm1pdHNfS0Rfc2YgPC0gYXMuZGF0YS5mcmFtZShwZXJtaXRzX0tELjE1MDApICU+JQ0KICBzdF9hc19zZihjb29yZHMgPSBjKCJ4IiwgInkiKSwgY3JzID0gc3RfY3JzKGZpbmFsX25ldCkpICU+JQ0KICBhZ2dyZWdhdGUoLiwgZmluYWxfbmV0LCBtZWFuKSAlPiUNCiAgbXV0YXRlKGxhYmVsID0gIktlcm5lbCBEZW5zaXR5IiwNCiAgICAgICAgIFJpc2tfQ2F0ZWdvcnkgPSBudGlsZSh2YWx1ZSwgMTAwKSwNCiAgICAgICAgIFJpc2tfQ2F0ZWdvcnkgPSBjYXNlX3doZW4oDQogICAgICAgICAgIFJpc2tfQ2F0ZWdvcnkgPj0gOTAgfiAiOTAlIHRvIDEwMCUiLA0KICAgICAgICAgICBSaXNrX0NhdGVnb3J5ID49IDcwICYgUmlza19DYXRlZ29yeSA8PSA4OSB+ICI3MCUgdG8gODklIiwNCiAgICAgICAgICAgUmlza19DYXRlZ29yeSA+PSA1MCAmIFJpc2tfQ2F0ZWdvcnkgPD0gNjkgfiAiNTAlIHRvIDY5JSIsDQogICAgICAgICAgIFJpc2tfQ2F0ZWdvcnkgPj0gMzAgJiBSaXNrX0NhdGVnb3J5IDw9IDQ5IH4gIjMwJSB0byA0OSUiLA0KICAgICAgICAgICBSaXNrX0NhdGVnb3J5ID49IDEgJiBSaXNrX0NhdGVnb3J5IDw9IDI5IH4gIjElIHRvIDI5JSIpKSAlPiUNCiAgY2JpbmQoDQogICAgYWdncmVnYXRlKA0KICAgICAgZHBseXI6OnNlbGVjdChjb25zdHJ1Y3Rpb25QZXJtaXRzXzIwMjIpICU+JSBtdXRhdGUocGVybWl0Q291bnQgPSAxKSwgLiwgc3VtKSAlPiUNCiAgICAgIG11dGF0ZShwZXJtaXRDb3VudCA9IHJlcGxhY2VfbmEocGVybWl0Q291bnQsIDApKSkgJT4lDQogIGRwbHlyOjpzZWxlY3QobGFiZWwsIFJpc2tfQ2F0ZWdvcnksIHBlcm1pdENvdW50KQ0KDQojIFByZWRpY3Rpb24gYnkgUmlzayBQcmVkaWN0aW9uIE1vZGVsDQoNCnBlcm1pdHNfS0Rfcmlza19zZiA8LQ0KICByZWcuc3Muc3BhdGlhbENWICU+JQ0KICBtdXRhdGUobGFiZWwgPSAiUmlzayBQcmVkaWN0aW9ucyIsDQogICAgICAgICBSaXNrX0NhdGVnb3J5ID0gbnRpbGUoUHJlZGljdGlvbiwgMTAwKSwNCiAgICAgICAgIFJpc2tfQ2F0ZWdvcnkgPSBjYXNlX3doZW4oDQogICAgICAgICAgIFJpc2tfQ2F0ZWdvcnkgPj0gOTAgfiAiOTAlIHRvIDEwMCUiLA0KICAgICAgICAgICBSaXNrX0NhdGVnb3J5ID49IDcwICYgUmlza19DYXRlZ29yeSA8PSA4OSB+ICI3MCUgdG8gODklIiwNCiAgICAgICAgICAgUmlza19DYXRlZ29yeSA+PSA1MCAmIFJpc2tfQ2F0ZWdvcnkgPD0gNjkgfiAiNTAlIHRvIDY5JSIsDQogICAgICAgICAgIFJpc2tfQ2F0ZWdvcnkgPj0gMzAgJiBSaXNrX0NhdGVnb3J5IDw9IDQ5IH4gIjMwJSB0byA0OSUiLA0KICAgICAgICAgICBSaXNrX0NhdGVnb3J5ID49IDEgJiBSaXNrX0NhdGVnb3J5IDw9IDI5IH4gIjElIHRvIDI5JSIsIA0KICAgICAgICAgICBUUlVFIH4gIjElIHRvIDI5JSIpKSAlPiUNCiAgIGNiaW5kKA0KICAgIGFnZ3JlZ2F0ZSgNCiAgICAgIGRwbHlyOjpzZWxlY3QoY29uc3RydWN0aW9uUGVybWl0c18yMDIyKSAlPiUgbXV0YXRlKHBlcm1pdENvdW50ID0gMSksIC4sIHN1bSkgJT4lDQogICAgICBtdXRhdGUocGVybWl0Q291bnQgPSByZXBsYWNlX25hKHBlcm1pdENvdW50LCAwKSkpICU+JQ0KICBkcGx5cjo6c2VsZWN0KGxhYmVsLCBSaXNrX0NhdGVnb3J5LCBwZXJtaXRDb3VudCkNCg0KIyBQbG90dGluZyBjb21wYXJpc29uIGJldHdlZW4gYm90aCBtb2RlbHMNCg0KcmJpbmQocGVybWl0c19LRF9zZiwgcGVybWl0c19LRF9yaXNrX3NmKSAlPiUNCiAgbmEub21pdCgpICU+JQ0KICBnYXRoZXIoVmFyaWFibGUsIFZhbHVlLCAtbGFiZWwsIC1SaXNrX0NhdGVnb3J5LCAtZ2VvbWV0cnkpICU+JQ0KICBnZ3Bsb3QoKSArDQogIGdlb21fc2YoYWVzKGZpbGwgPSBSaXNrX0NhdGVnb3J5KSwgY29sb3VyID0gTkEpICsNCiAgZ2VvbV9zZihkYXRhID0gc2FtcGxlX24oY29uc3RydWN0aW9uUGVybWl0c18yMDIyLCAxMTMwKSwgc2l6ZSA9IC41LCBjb2xvdXIgPSAiYmxhY2siKSArDQogIGZhY2V0X3dyYXAofmxhYmVsLCApICsNCiAgc2NhbGVfZmlsbF92aXJpZGlzX2Qob3B0aW9uID0gInBsYXNtYSIsDQogICAgICAgICAgICAgICAgICAgICAgIG5hbWUgPSAnUmlzayBDYXRlZ29yeScpICsgDQogIGxhYnModGl0bGU9IkNvbXBhcmlzb24gb2YgS2VybmVsIERlbnNpdHkgYW5kIFJpc2sgUHJlZGljdGlvbnMiLA0KICAgICAgIHN1YnRpdGxlPSJCb3R0b20gTGF5ZXIgaXMgMjAyMiBQcmVkaWN0ZWQgUGVybWl0IENvdW50cy5cbkRvdHMgYXJlIG9ic2VydmVkIDIwMjIgUGVybWl0IENvdW50cy5cbiIsDQogICAgICAgY2FwdGlvbiA9ICdGaWd1cmUgMTknKSArDQogIG1hcFRoZW1lKCkgKw0KICBwbG90VGhlbWUoKQ0KYGBgDQoNCmBgYHtyIEtEIHZzIFJpc2sgUHJlZGljdGlvbiBCYXIgR3JhcGhzLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFLCBmaWcud2lkdGg9NiwgZmlnLmhlaWdodD00IH0NCg0KIyBDb21wYXJpc29uIG9mIHByZWRpY3Rpb25zIC0gQmFyIFBsb3RzDQoNCnJiaW5kKHBlcm1pdHNfS0Rfc2YsIHBlcm1pdHNfS0Rfcmlza19zZikgJT4lDQogIHN0X3NldF9nZW9tZXRyeShOVUxMKSAlPiUgDQogIG5hLm9taXQoKSAlPiUNCiAgZ2F0aGVyKFZhcmlhYmxlLCBWYWx1ZSwgLWxhYmVsLCAtUmlza19DYXRlZ29yeSkgJT4lDQogIGdyb3VwX2J5KGxhYmVsLCBSaXNrX0NhdGVnb3J5KSAlPiUNCiAgc3VtbWFyaXplKHBlcm1pdENvdW50ID0gc3VtKFZhbHVlKSkgJT4lDQogIHVuZ3JvdXAoKSAlPiUNCiAgZ3JvdXBfYnkobGFiZWwpICU+JQ0KICBtdXRhdGUoUmF0ZV9vZl9wZXJtaXRfY291bnQgPSBwZXJtaXRDb3VudCAvIHN1bShwZXJtaXRDb3VudCkpICU+JQ0KICBnZ3Bsb3QoYWVzKFJpc2tfQ2F0ZWdvcnksUmF0ZV9vZl9wZXJtaXRfY291bnQpKSArDQogIGdlb21fYmFyKGFlcyhmaWxsPWxhYmVsKSwgcG9zaXRpb249ImRvZGdlIiwgc3RhdD0iaWRlbnRpdHkiKSArDQogIHNjYWxlX2ZpbGxfdmlyaWRpc19kKG9wdGlvbiA9ICJwbGFzbWEiLA0KICAgICAgICAgICAgICAgICAgICAgICBuYW1lID0gJ01vZGVsJykgKw0KICBsYWJzKHRpdGxlID0gIlJpc2sgUHJlZGljdGlvbiB2cy4gS2VybmVsIERlbnNpdHkiLA0KICAgICAgIHN1YnRpdGxlID0gJ1BoaWxhZGVscGhpYSwgUEEnLA0KICAgICAgIGNhcHRpb24gPSAnRmlndXJlIDIwJywNCiAgICAgICB4ID0gJ1Jpc2sgQ2F0ZWdvcnknLA0KICAgICAgIHkgPSAnUmF0ZSBvZiBQZXJtaXQgQ291bnRzJykgKw0KICBwbG90VGhlbWUoKQ0KYGBgDQoNCiMjIDguIFJpc2sgTWFwIGFuZCBSZXN1bHRzDQoNCg0KYGBge3IgUmlzayBjYXRlZ29yeSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgaW5jbHVkZT1UUlVFLCBmaWcud2lkdGg9NywgZmlnLmhlaWdodD03fQ0KDQojIEFzc2lnbmluZyBSaXNrIENhdGVnb3JpZXMgZm9yIFByZWRpY3Rpb25zDQoNCnBlcm1pdF9yaXNrX3NmIDwtDQogIGZpbHRlcihyZWcuc3VtbWFyeSwgUmVncmVzc2lvbiA9PSAiU3BhdGlhbCBMT0dPLUNWOiBTcGF0aWFsIFByb2Nlc3MiKSAlPiUNCiAgbXV0YXRlKGxhYmVsID0gIlJpc2sgUHJlZGljdGlvbnMiLA0KICAgICAgICAgUmlza19DYXRlZ29yeSA9IG50aWxlKFByZWRpY3Rpb24sIDEwMCksDQogICAgICAgICBSaXNrX0NhdGVnb3J5ID0gY2FzZV93aGVuKA0KICAgICAgICAgICBSaXNrX0NhdGVnb3J5ID4gOTkgfiAiPiA5OSUiLA0KICAgICAgICAgICBSaXNrX0NhdGVnb3J5ID49IDk4ICYgUmlza19DYXRlZ29yeSA8PSA5OSB+ICI5OCUgdG8gOTklIiwNCiAgICAgICAgICAgUmlza19DYXRlZ29yeSA+PSA5MCAmIFJpc2tfQ2F0ZWdvcnkgPD0gOTcgfiAiOTAlIHRvIDk3JSIsDQogICAgICAgICAgIFJpc2tfQ2F0ZWdvcnkgPj0gNzAgJiBSaXNrX0NhdGVnb3J5IDw9IDg5IH4gIjcwJSB0byA4OSUiLA0KICAgICAgICAgICBSaXNrX0NhdGVnb3J5ID49IDUwICYgUmlza19DYXRlZ29yeSA8PSA2OSB+ICI1MCUgdG8gNjklIiwNCiAgICAgICAgICAgUmlza19DYXRlZ29yeSA+PSAzMCAmIFJpc2tfQ2F0ZWdvcnkgPD0gNDkgfiAiMzAlIHRvIDQ5JSIsDQogICAgICAgICAgIFJpc2tfQ2F0ZWdvcnkgPj0gMSAmIFJpc2tfQ2F0ZWdvcnkgPD0gMjkgfiAiMSUgdG8gMjklIiwNCiAgICAgICAgICAgVFJVRSB+ICIxJSB0byAyOSUiKSkgDQoNCnBlcm1pdF9yaXNrX3NmICU+JQ0KICBnYXRoZXIoVmFyaWFibGUsIFZhbHVlLCAtbGFiZWwsIC1SaXNrX0NhdGVnb3J5LCAtZ2VvbWV0cnkpICU+JQ0KICBnZ3Bsb3QoKSArDQogICAgZ2VvbV9zZihhZXMoZmlsbCA9IFJpc2tfQ2F0ZWdvcnkpLCBjb2xvdXIgPSBOQSkgKw0KICAgIGdlb21fc2YoZGF0YT1waGlsbHlOZWlnaGJvcmhvb2RzLCBmaWxsID0gInRyYW5zcGFyZW50IikgKw0KICAgIHNjYWxlX2ZpbGxfdmlyaWRpcyhkaXNjcmV0ZSA9IFRSVUUpICsNCiAgICBsYWJzKHRpdGxlPSJSaXNrIFByZWRpY3Rpb25zIiwNCiAgICAgICAgIHN1YnRpdGxlPSJOZXcgQ29uc3RydWN0aW9uIFBlcm1pdCBQcmVkaWN0aW9ucyIsDQogICAgICAgICBjYXB0aW9uPSJGaWd1cmUgMjAiKSArDQogICAgbWFwVGhlbWUoKSsNCiAgICBwbG90VGhlbWUoKQ0KDQoNCg0KYGBgDQoNCg0KSW4gdGhlIGJhciBjaGFydCBpbGx1c3RyYXRlZCBpbiBGaWd1cmUgMjEsIHRoZSB0b3AgbmVpZ2hib3Job29kcyBhdCByaXNrIG9mIG5ldyBjb25zdHJ1Y3Rpb24gcGVybWl0cyBhbmQgc3Vic2VxdWVudGx5IHJhcGlkIGRldmVsb3BtZW50IGFyZSBkZXBpY3RlZC4gTm90YWJseSwgbWFueSBvZiB0aGVzZSBuZWlnaGJvcmhvb2RzIGFyZSBzaXR1YXRlZCBpbiBjbG9zZSBwcm94aW1pdHkgYW5kIGNhbiBiZSBncm91cGVkIGluIGNsdXN0ZXJzOg0KDQoqIE5vcnRoZXJuIExpYmVydGllcywgRmlzaHRvd24sIE9sZCBLZW5zaW5ndG9uLCBhbmQgV2VzdCBQb3BsYXIgDQoqIE5vcnRoZXJuIExpYmVydGllcywgT2xkIENpdHksIGFuZCBXYXNoaW5ndG9uIFNxdWFyZQ0KKiBGcmFuY2lzdmlsbGUgYW5kIFdlc3QgUG9wbGFyDQoqIExvZ2FuIFNxdWFyZSBhbmQgUml0dGVuaG91c2UNCg0KVGhpcyBvYnNlcnZhdGlvbiB1bmRlcnNjb3JlcyB0aGUgc2lnbmlmaWNhbmNlIG9mIHJlZmluaW5nIHRoZSBzcGF0aWFsIHByb2Nlc3MgaW4gdGhlIG1vZGVsLCBhcyBpdCBiZWNvbWVzIGV2aWRlbnQgdGhhdCB0aGUgcmlzayBvZiBuZXcgY29uc3RydWN0aW9uIHBlcm1pdHMgaXMgbm90IHJhbmRvbWx5IGRpc3RyaWJ1dGVkIGFjcm9zcyBzcGFjZS4NCg0KYGBge3IgZm9yIHJpc2sgcHJlZGljdGlvbiwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgaW5jbHVkZT1UUlVFLCBmaWcud2lkdGg9NSwgZmlnLmhlaWdodD0zfQ0KDQojIE5laWdoYm9yaG9vZHMgYXQgaGlnaGVzdCByaXNrIG9mIHBlcm1pdHRpbmcgY29uc3RydWN0aW9uDQoNCiBoaWdoZXN0X3Jpc2tfbmhvb2RzIDwtDQogIHBlcm1pdF9yaXNrX3NmICU+JQ0KICBkcGx5cjo6c2VsZWN0KGN2SUQsIFJpc2tfQ2F0ZWdvcnksIGNvdW50UGVybWl0cykgJT4lDQogIGRwbHlyOjpmaWx0ZXIoLiwgUmlza19DYXRlZ29yeSA9PSI+IDk5JSIpICU+JQ0KICBzdF9kcm9wX2dlb21ldHJ5KCkgJT4lDQogIGdyb3VwX2J5KGN2SUQpICU+JQ0KICBzdW1tYXJpc2UoUHJlZGljdGVkX1Blcm1pdHM9c3VtKGNvdW50UGVybWl0cykpICU+JQ0KICBkcGx5cjo6ZmlsdGVyKC4sIFByZWRpY3RlZF9QZXJtaXRzPjUyKSAlPiUNCiAgYXJyYW5nZSguLC1QcmVkaWN0ZWRfUGVybWl0cykgJT4lDQogIHJlbmFtZShOZWlnaGJvcmhvb2QgPSBjdklEKQ0KDQogIGdncGxvdChoaWdoZXN0X3Jpc2tfbmhvb2RzKSArDQogIGdlb21fYmFyKGFlcyh4PXJlb3JkZXIoTmVpZ2hib3Job29kLCAtUHJlZGljdGVkX1Blcm1pdHMpLCBQcmVkaWN0ZWRfUGVybWl0cyksIGZpbGwgPSAiI0ZFOTkwMCIsIHN0YXQgPSAiaWRlbnRpdHkiLCB3aWR0aD0wLjUsIGRvZGdlPTAuNSkgKw0KICBsYWJzKHggPSAiTmVpZ2hib3Job29kIiwgeT0iUHJlZGljdGVkIFBlcm1pdHMiLCANCiAgICAgICB0aXRsZSA9ICJQcmVkaWN0ZWQgUGVybWl0IFJpc2sgZ3JlYXRlciB0aGFuIDk5JSIsIHN1YnRpdGxlID0gJ1BoaWxhZGVscGhpYSwgUEEnLCBjYXB0aW9uPSJGaWd1cmUgMjEiKSArDQogICAgdGhlbWUoYXhpcy50ZXh0Lng9ZWxlbWVudF90ZXh0KHNpemU9NiwgYW5nbGUgPSA2MCwgdmp1c3Q9MC43LCBoanVzdD0wLjcpKSsNCiAgICB0aGVtZShheGlzLnRleHQueT1lbGVtZW50X3RleHQoc2l6ZT02KSkrDQogIHRoZW1lKA0KICAgIGF4aXMudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDgpLCANCiAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSA5LCBmYWNlPSAiYm9sZCIsIGNvbG9yID0gImRhcmtyZWQiKSwNCiAgICBwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSA3LCBmYWNlPSAiaXRhbGljIiwgY29sb3IgPSAiYmxhY2siKSwNCiAgICBwbG90Lm1hcmdpbiA9IG1hcmdpbigwLCA1MCwgOCwgNTAsICJwdCIpICANCiAgKQ0KDQpgYGANCg0KIyMgOS4gQ29uY2x1c2lvbg0KDQojIyMgOS4xIEltcGxlbWVudGF0aW9uIGZvciBVc2UgQ2FzZQ0KDQpGb3IgdGhlIHB1cnBvc2Ugb2Ygb3VyIG9yaWdpbmFsIHVzZSBjYXNlLCB0aGlzIGFuYWx5c2lzIGFuZCBtb2RlbCBoYXMgc3VjY2Vzc2Z1bGx5IGxhaWQgdGhlIGZvdW5kYXRpb24gZm9yIGFjaGlldmluZyBvdXIgdmlzaW9uIG9mIGEgRGV2ZWxvcG1lbnQgVHJhY2tpbmcgdG9vbCB0byBiZSB1c2VkIGJ5IGNpdHkncyBPZmZpY2Ugb2YgU3VzdGFpbmFiaWxpdHkuIA0KDQoqIFRoaXMgbW9kZWwgYWNjdXJhdGVseSBwcmVkaWN0cyB0aGUgcmlzayBvZiBuZXcgY29uc3RydWN0aW9uIGFjcm9zcyB0aGUgY2l0eSwgYW5kIGhhcyBpZGVudGlmaWVkIG5laWdoYm9yaG9vZHMgYXQgcmlzayBvZiBleHBlcmllbmNpbmcgcmFwaWQgZGV2ZWxvcG1lbnQuIFRoZXNlIG5laWdoYm9yaG9vZCBiYXNlZCByaXNrIHByZWRpY3Rpb25zIHdpbGwgYmUgdXNlZnVsIGZvciBzdXN0YWluYWJpbGl0eSBwcm9mZXNzaW9uYWxzIGFzIHdlbGwgYXMgcG9saWN5LW1ha2VycyB0byBwaWxvdCBhbmQgaW1wbGVtZW50IGxvY2FsaXplZCBhbmQgbmVpZ2hib3Job29kLWJhc2VkIHJlZ3VsYXRpb25zIGZvciBjb25zdHJ1Y3Rpb24gYW5kIGdyZWVuIGJ1aWxkaW5nLiANCg0KKiBUaGUgc2VsZWN0ZWQgTE9HTyBDViBzcGF0aWFsIHByb2Nlc3MgbW9kZWwgaW5kaWNhdGVzIE1lYW4gQWJzb2x1dGUgRXJyb3JzIG9mIGxlc3MgdGhhbiB0ZW4gZm9yIGFsbCBuZWlnaGJvcmhvb2RzLiBUaGlzIG1lYW5zIHRoYXQgd2hpbGUgdGhlIGFjdHVhbCB2YWx1ZSBvZiBwZXJtaXRzIGlzc3VlZCBtYXkgZGlmZmVyLCBpdCB3aWxsIGRpZmZlciBieSBhIHNtYWxsIG1hcmdpbiB0aGF0IGlzIGxpa2VseSB0byBiZSBpbmNvbnNlcXVlbnRpYWwgZm9yIGEgcG9saWN5bWFrZXIuIFRoaXMgaW5kaWNhdGVzIHRoYXQgdGhlIG1vZGVsIGlzIHN0aWxsIGVmZmVjdGl2ZSBhdCBjYXB0dXJpbmcgdGhlIG92ZXJhbGwgdHJlbmRzIG9mIGRldmVsb3BtZW50Lg0KDQoqIFRoZSBtb2RlbCBhbmQgc3Vic2VxdWVudGx5IHRoZSBhcHAgaWxsdXN0cmF0ZSB0aGUgcGhlbm9tZW5vbiBvZiBzcGF0aWFsIGNsdXN0ZXJpbmcgYW5kIHNwYXRpYWwgYXV0b2NvcnJlbGF0aW9uIGNsZWFybHkuIEFzIHByZWRpY3Rpb25zIGFyZSBtYWRlIHdpdGggc21hbGxlciBzcGF0aWFsIGJvdW5kYXJpZXMgaW4gY29uc2lkZXJhdGlvbiAoZmlzaG5ldCBjZWxsIHNpemUgb2YgNTAwKSwgdGhleSBvZmZlciBtb3JlIG51YW5jZSBhbmQgYWNjdXJhY3kuIEhvd2V2ZXIgdGhleSBhcmUgbm90IHByZXNlbnRlZCBpbiBpc29sYXRpb24gZGlzcmVnYXJkaW5nIHRoZSBsYXJnZXIgc3Vycm91bmRpbmcgY29udGV4dDsgYm90aCB0aGVzZSBzY2FsZXMgYXJlIHByZXNlbnRlZCBzaWRlIGJ5IHNpZGUuIFRodXMsIHRoZSBtb2RlbCBzaG93Y2FzZXMgbGlrZWx5IGRldmVsb3BtZW50IHRyZW5kcyBmb3Igc3BlY2lmaWMgbmVpZ2hib3Job29kcyBhcyB3ZWxsIGFzIG5laWdoYm9yaG9vZCBjbHVzdGVycyBpbiBjbG9zZSBwcm94aW1pdHkuDQoNCiogVGhlIG1vZGVsIGFsc28gaWxsdXN0cmF0ZXMgdGhlIGVycm9ycyBhbmQgY29udHJhc3RzIGluIHByZWRpY3Rpb24gYmV0d2VlbiBXaGl0ZSBhbmQgTm9uIFdoaXRlIE5laWdoYm9yaG9vZHMuIEhpc3RvcmljYWxseSwgTm9uLVdoaXRlIE5laWdoYm9yaG9vZHMgaGF2ZSBhbHdheXMgYmVlbiBzdWJqZWN0IHRvIGVudmlyb25tZW50YWwgaW5qdXN0aWNlLiBBcyBjbGltYXRlIGNoYW5nZSBpbmNyZWFzZXMgZW52aXJvbm1lbnRhbCByaXNrIGFjcm9zcyBuZWlnaGJvcmhvb2RzLCBOb24tV2hpdGUgbmVpZ2hib3Job29kcyBhcmUgdHlwaWNhbGx5IGF0IGEgZGlzYWR2YW50YWdlLiBJbiBwcmVzZW50aW5nIHRoZSBkaXNwYXJpdHkgb2YgcHJlZGljdGlvbiBiYXNlZCBvbiByYWNpYWwgY29udGV4dCwgdGhlIG1vZGVsIGFsbG93cyB0aGUgT2ZmaWNlIG9mIFN1c3RhaW5hYmlsaXR5IHRvIHBsYW4gYW5kIGFsbG9jYXRlIHJlc291cmNlcyBlcXVpdGFibHkgdG8gYWxsIG5laWdoYm9yaG9vZHMuIA0KDQoqIEZpbmFsbHksIHRoZSBtb2RlbCBhc3NpZ25zIGRpZmZlcmVudCByaXNrIGNhdGVnb3JpZXMgYmFzZWQgb24gdGhlIHJhdGUgb2YgcGVybWl0cyBpc3N1ZWQgYW5kIHRoZXJlZm9yZSB0aGUgcmlzayBvZiBkZXZlbG9wbWVudC4gVGhpcyB3aWxsIGFsbG93IHRoZSBPZmZpY2Ugb2YgU3VzdGFpbmFiaWxpdHkgdG8gYmUgc3RyYXRlZ2ljIGluIGNyZWF0aW5nIGEgcm9hZG1hcCBhbmQgdGltZWxpbmUgZm9yIGltcGxlbWVudGF0aW9uLCB0YXJnZXRpbmcgYSBzcGVjaWZpYyBzZXQgb2YgbmVpZ2hib3Job29kcyBhdCBhIHRpbWUgYmFzZWQgb24gdGhlaXIgcmlzayBsZXZlbCBmb3IgZ3JlZW4gYnVpbGRpbmcgcmVndWxhdGlvbiBhbmQgY29uc3RydWN0aW9uIHBvbGx1dGlvbiBtaXRpZ2F0aW9uLg0KDQojIyMgOS4yIE1vZGVsIEltcHJvdmVtZW50cw0KDQpUaGUgcHJlc2VudCBtb2RlbCBhbmQgaXRzIHByZWRpY3RpdmUgY2FwYWJpbGl0aWVzIGNhbiBiZSBpbXByb3ZlZCBpbiBzZXZlcmFsIHdheXM6DQoNCiogRGF0YSBDb2xsZWN0aW9uOg0KDQorIEFkZGl0aW9uYWwgU3BhdGlhbCBEYXRhOiBUaGUgbW9kZWwgY291bGQgZXhwbG9yZSBtb3JlIGdyYW51bGFyIHNwYXRpYWwgZGF0YXNldHMsIGluY2x1ZGluZyBpbmZvcm1hdGlvbiBvbiBuZWlnaGJvcmhvb2QtbGV2ZWwgY2hhcmFjdGVyaXN0aWNzLCB6b25pbmcgcmVndWxhdGlvbnMsIG9yIGxhbmQgdXNlIHBhdHRlcm5zLg0KKyBFeHRlcm5hbCBEYXRhIFNvdXJjZXM6IEZvciB0aGUgc2FrZSBvZiB0aGlzIGFuYWx5c2lzLCBhbGwgZGF0YSBoYXMgYmVlbiBzb3VyY2VkIGZyb20gdGhlIGNpdHkncyBPcGVuIERhdGEgcG9ydGFsIGFuZCB0aGUgQW1lcmljYW4gQ29tbXVuaXR5IFN1cnZleSBmaXZlIHllYXIgZXN0aW1hdGVzLiBGdXR1cmUgaXRlcmF0aW9ucyBjb3VsZCBsb29rIGF0IG90aGVyIGV4dGVybmFsIGRhdGEgc291cmNlcyB0aGF0IGFyZSBhdmFpbGFibGUgdG8gY29tcGxlbWVudCB0aGVzZSBkYXRhc2V0cy4NCg0KKiBEYXRhIFdyYW5nbGluZzogVGhlIG1vZGVsIGNhbiBiZSBpbXByb3ZlZCBieSBlc3RhYmxpc2hpbmcgYSBzeXN0ZW0gdG8gaW1wdXRlIG1pc3NpbmcgdmFsdWVzLg0KDQoqIEZlYXR1cmUgRW5naW5lZXJpbmc6DQoNCisgVGVtcG9yYWwgRmVhdHVyZXM6IFRoZSBtb2RlbCBjYW4gYmUgZW5oYW5jZWQgdGhlIG1vZGVsIGJ5IGluY2x1ZGluZyB0aW1lLXJlbGF0ZWQgZmVhdHVyZXMsIHN1Y2ggYXMgc2Vhc29uYWxpdHksIHRyZW5kcywgb3IgY3ljbGljYWwgcGF0dGVybnMgaW4gY29uc3RydWN0aW9uIHBlcm1pdCBpc3N1YW5jZS4NCisgSW50ZXJhY3Rpb24gRnVuY3Rpb25zOiBJdCBjb3VsZCBiZW5lZml0IGZyb20gdGhlIGNyZWF0aW9uIG9mIG5ldyBpbnRlcmFjdGlvbiB2YXJpYWJsZXMgb3IgZnVuY3Rpb25zIHRoYXQgdGVzdCByZWxhdGlvbnMgYmV0d2VlbiByZWxldmFudCB2YXJpYWJsZXMgdG8gZnVydGhlciB1bmRlcnN0YW5kIGFuZCBwcmVkaWN0IGZvciBzcGF0aWFsIGF1dG9jb3JyZWxhdGlvbnMuDQoNCiogUmVhbCBUaW1lIFVwZGF0ZXM6IFRoZSBtb2RlbCBjb3VsZCBleHBsb3JlIHBvc3NpYmlsaXRpZXMgZm9yIGludGVncmF0aW5nIHJlYWwtdGltZSBkYXRhIHRvIGNhcHR1cmUgZXZvbHZpbmcgdHJlbmRzIGFuZCByZXNwb25kIHRvIGNoYW5naW5nIGNvbmRpdGlvbnMgaW4gdGhlIGNpdHkuDQoNCiogU3Rha2Vob2xkZXIgSW52b2x2ZW1lbnQ6IEJlaGF2aW9yIGFuZCBzb2NpYWwgcGF0dGVybnMgdGhhdCBpbmZsdWVuY2UgZGV2ZWxvcG1lbnQgYXJlIGhhcmQgdG8gY2FwdHVyZSBhbmQgdGhlcmVmb3JlIG5vdCBhZGVxdWF0ZWx5IGludGVncmF0ZWQgaW4gdGhpcyBtb2RlbC4gSXQgbWF5IGJlIHVzZWZ1bCB0byBlbmdhZ2Ugd2l0aCBsb2NhbCBjb21tdW5pdHkgc3Rha2Vob2xkZXJzIGFuZCBjaXR5IG9mZmljaWFscyB0byBnYWluIGluc2lnaHRzIGludG8gZmFjdG9ycyB0aGF0IG1heSBub3QgYmUgY2FwdHVyZWQgaW4gdGhlIGN1cnJlbnQgZGF0YXNldHMuDQoNCiogRXZhbHVhdGlvbjogVGhlIG1haW50ZW5hbmNlIG9mIHRoaXMgbW9kZWwgc2hvdWxkIGludm9sdmUgZXZhbHVhdGlvbiBvZiB0aGUgbW9kZWwncyByZWxldmFuY2UgYW5kIGVmZmVjdGl2ZW5lc3Mgb3ZlciB0aW1lLCByZWRlZmluaW5nIG1ldHJpY3MgZm9yIG1lYXN1cmVtZW50IG9mIG1vZGVsIHN1Y2Nlc3MgYXMgbmVlZGVkLiANCg0KIyMjIDkuMyBPdGhlciBBcmVhcyBvZiBBcHBsaWNhdGlvbg0KDQpXaGlsZSB0aGUgbW9kZWwgaGFzIGJlZW4gc3BlY2lmaWNhbGx5IGNyZWF0ZWQgd2l0aCB0aGUgQ2l0eSBvZiBQaGlsYWRlbHBoaWEncyBPZmZpY2Ugb2YgU3VzdGFpbmFiaWxpdHkgaW4gbWluZCBpdCBjYW4gYmUgcmVwbGljYXRlZCBhY3Jvc3MgZGlmZmVyZW50IHNjYWxlcyBhbmQgZ2VvZ3JhcGhpZXMgdG8gZnVsZmlsbCBkaWZmZXJlbnQgbmVlZHMgYW5kIHB1cnBvc2VzLiBBIGZldyBleGFtcGxlcyBhcmUgb3V0bGluZWQgYmVsb3c6DQoNCiogVXJiYW4gUGxhbm5pbmc6IENpdHkgcGxhbm5lcnMgY2FuIHVzZSB0aGUgcHJlZGljdGlvbnMgdG8gaW5mb3JtIGxhbmQtdXNlIGFuZCBkZXZlbG9wbWVudCBwb2xpY2llcy4gVW5kZXJzdGFuZGluZyB3aGVyZSBuZXcgY29uc3RydWN0aW9uIGlzIGxpa2VseSB0byBvY2N1ciBjYW4gYWlkIGluIGNyZWF0aW5nIHN1c3RhaW5hYmxlIGFuZCB3ZWxsLWJhbGFuY2VkIHVyYmFuIGRldmVsb3BtZW50IHBsYW5zIHRoYXQgY29uc2lkZXIgZmFjdG9ycyBsaWtlIGhvdXNpbmcgbmVlZHMsIGVudmlyb25tZW50YWwgaW1wYWN0LCBhbmQgY29tbXVuaXR5IHdlbGwtYmVpbmcuDQoNCiogQ29tbXVuaXR5IEVuZ2FnZW1lbnQ6IFRoZSBwcmVkaWN0aW9ucyBjYW4gYmUgdXNlZCB0byBlbmdhZ2Ugd2l0aCBsb2NhbCBjb21tdW5pdGllcy4gQ2l0eSBvZmZpY2lhbHMgY2FuIGNvbW11bmljYXRlIHVwY29taW5nIGNvbnN0cnVjdGlvbiBwcm9qZWN0cywgaW52b2x2ZSByZXNpZGVudHMgaW4gdGhlIHBsYW5uaW5nIHByb2Nlc3MsIGFuZCBhZGRyZXNzIGNvbmNlcm5zIHJlbGF0ZWQgdG8gaXNzdWVzIHN1Y2ggYXMgZ2VudHJpZmljYXRpb24sIGRpc3BsYWNlbWVudCwgYW5kIGNoYW5nZXMgaW4gbmVpZ2hib3Job29kIGNoYXJhY3Rlci4NCg0KKiBJbmZyYXN0cnVjdHVyZSBQbGFubmluZzogQW50aWNpcGF0aW5nIGNvbnN0cnVjdGlvbiBwZXJtaXRzIGNhbiBoZWxwIGluIHBsYW5uaW5nIGFuZCBleHBhbmRpbmcgY2l0eSBpbmZyYXN0cnVjdHVyZS4gVGhpcyBpbmNsdWRlcyB0cmFuc3BvcnRhdGlvbiBuZXR3b3JrcywgdXRpbGl0aWVzLCBhbmQgb3RoZXIgZXNzZW50aWFsIHNlcnZpY2VzIHRvIGFjY29tbW9kYXRlIHRoZSBncm93aW5nIG5lZWRzIG9mIHRoZSBhcmVhcyBleHBlY3RlZCB0byBleHBlcmllbmNlIGluY3JlYXNlZCBjb25zdHJ1Y3Rpb24uDQoNCiogSG91c2luZyBTdHJhdGVnaWVzOiBGb3IgY2l0eSBvZmZpY2lhbHMgd29ya2luZyBvbiBob3VzaW5nIGluaXRpYXRpdmVzLCB0aGUgbW9kZWwgY2FuIGJlIGEgdmFsdWFibGUgdG9vbCBmb3IgcHJlZGljdGluZyBkZW1hbmQgaW4gc3BlY2lmaWMgbmVpZ2hib3Job29kcy4gVGhpcyBpbmZvcm1hdGlvbiBjYW4gZ3VpZGUgZWZmb3J0cyB0byBjcmVhdGUgYWZmb3JkYWJsZSBob3VzaW5nLCBwcmV2ZW50IGhvdXNpbmcgc2hvcnRhZ2VzLCBhbmQgYWRkcmVzcyBob3VzaW5nLXJlbGF0ZWQgY2hhbGxlbmdlcy4NCg0KKiBSaXNrIE1hbmFnZW1lbnQ6IFVuZGVyc3RhbmRpbmcgdGhlIHJpc2sgb2YgbmV3IGNvbnN0cnVjdGlvbiBwZXJtaXRzIGNhbiBoZWxwIGluIGRldmVsb3BpbmcgcmlzayBtYW5hZ2VtZW50IHN0cmF0ZWdpZXMuIFRoaXMgY291bGQgaW52b2x2ZSBjcmVhdGluZyBwb2xpY2llcyB0byBoYW5kbGUgaXNzdWVzIHJlbGF0ZWQgdG8gaG91c2luZyBhZmZvcmRhYmlsaXR5LCBkaXNwbGFjZW1lbnQsIGFuZCBtYWludGFpbmluZyB0aGUgY3VsdHVyYWwgYW5kIHNvY2lhbCBmYWJyaWMgb2YgbmVpZ2hib3Job29kcy4NCg0KSXQgaXMgaW1wb3J0YW50IHRvIG5vdGUgdGhhdCB3aGlsZSB0aGUgbW9kZWwgcHJvdmlkZXMgdmFsdWFibGUgaW5zaWdodHMsIHdlIHJlY29tbWVuZCB0aGF0IGl0IGJlIHVzZWQgYXMgYSBjb21wbGVtZW50IHRvIGxvY2FsIGtub3dsZWRnZSBhbmQgZXhwZXJ0aXNlLiBUaGUgcHJlZGljdGlvbnMgc2hvdWxkIGJlIGludGVycHJldGVkIGluIHRoZSBjb250ZXh0IG9mIGJyb2FkZXIgY2l0eSBzdXN0YWluYWJpbGl0eSBhbmQgcGxhbm5pbmcgZ29hbHMsIGNvbW11bml0eSBuZWVkcywgYW5kIGV0aGljYWwgY29uc2lkZXJhdGlvbnMgdG8gZW5zdXJlIHJlc3BvbnNpYmxlIGFuZCBpbmNsdXNpdmUgZGVjaXNpb24tbWFraW5nLg0K