Planning Motivation

In June 2013 the city of Calgary experienced an extreme flooding events that resulted in the displacement of 100,000 people and 1.7 billion dollars were needed for repairs. The event was one of the costliest disaster in Canada’s history. In this project we aim to build a predictive model that will help Calgary and cities with similar topography be better prepared for future large scale flooding events such as the 2013 disaster.

The results of the model can help with early action and planning, as decision makers and public will have readily available information on what areas of the city might need to be evacuated. Additionally, the model can also help identify areas of the city that might need buyouts and/or revisions to the building code to require flood proofing.

An advantage of our algorithm compared to FEMA federal level floodplain mapping is that the model should be able to help identify areas of flooding beyond just river flooding. Because the model is based on a historical event that resulted in flooding not near rivers, the model should be able to capture flooding in other low lying areas and valleys outside of riverine areas. Our model will be trained and tested using inundation data from the historical flood event in 2013. We will then use our model to predict flood risk in Denver. Denver was selected because the city of Denver has similar geographic characteristics to Calgary and contains multiple rivers.

Model Predictors

The predictors of flooding that we will use in our model are:

  • Elevation above minimum elevation in the city
  • Dominant Land Cover
  • Slope
  • Distance to Water
  • Water Flow Accumulation (i.e: the number of grid squares flowing into a grid square)

Our analysis will use 100m x 100m grid and we use a logistic regression model to determine areas that are likely to be flooded during a flood event that is similar to the event that occurred in Calgary in 2013.

Import Libraries

library(sf)
library(caret)
library(pscl)
library(plotROC)
library(pROC)
library(tidyverse)
library(terra)
library(kableExtra)
library(tigris)
library(viridis)
library(gridExtra)

Data Setup for Calgary

Read Data for Calgary

Read the Calgary boundary and inundation layer.

Make Grid

Make a fishnet using st_make_grid and remove grid squares that are not located within the Calgary boundary. Out grid squares are 100 x 100 meters.

inundation <- project(inundation,'EPSG:3780') #Project innundation raster into correct cordinate system

grid <- st_make_grid(calgary_boundary,100,square=TRUE) %>%
  st_sf() %>%
  mutate(ID = row_number())

# Get just grid squares with centroid in Calgary city limits

grid <- grid %>%
  st_centroid() %>%
  st_join(.,calgary_boundary,join = st_intersects,left=FALSE) %>%
  st_drop_geometry() %>%
  left_join(.,grid,by='ID') %>%
  st_as_sf() %>%
  mutate(ID = row_number()) %>%
  select(ID)

Summarize the provided inundation data by grid square. Grid squares that are more than 25% inundated will be considered inundated in this model.

# Count the number of inundation cells in each grid square in fishnet

extract_values <- extract(inundation, grid) %>% #Assign each raster pixel to a grid square
  group_by(ID,w001001) %>% tally() %>%
  ungroup() %>%
  pivot_wider(id_cols=ID,names_from=w001001,values_from=n) %>%
  replace(is.na(.), 0) %>% #Replace NA with 0
  filter(`NA` == 0) %>% #Filter out grid squares that do not have inundation data
  # 1 and 2 are inundation, sum those together and sum 0 and 3 together for non_inundation
  mutate(innundation = `1` + `2`,
         non_innundate = `0` + `3` + `NA`,
         in_id = as.factor(ifelse(innundation/(innundation + non_innundate) > 0.25,1,0))) %>% #If grid square is more than 25% inundate consider it inundate (i.e: 1)
  select(ID,in_id)

The map below shows the flooded area in 2013 summarized by grid squares.

grid2 <- right_join(grid,extract_values,by='ID') 

# Make Map of inundated areas

ggplot()+
  geom_sf(data=grid2,aes(fill=in_id),color='transparent')+
  scale_fill_manual(values=c('#fc9c9c','lightblue'),name='',labels=c('No Flood','Flood'))+
  theme_void()

Engineer Predictor Features for Calgary

Elevation

Get zonal statistics for grid squares, calculate mean elevation for each grid square.

dem <- rast('Data/calgary/calgarydem/w001001.adf')

dem <- project(dem,'EPSG:3780')

elevation_extract <- terra::extract(dem,grid2,fun='mean',na.rm=TRUE) %>%
  rename(elevation_mean = w001001) %>%
  mutate(elevation = elevation_mean - min(elevation_mean))

grid3 <- cbind(grid2,elevation_extract %>% select(elevation))

Land Cover

Summarize land cover data by grid square and figure out the mode land cover class for each grid square.

#Need four land cover tiles to cover all of Calgary
land_cover1 <- rast('Data/calgary/worldcover/ESA_WorldCover_10m_2020_v100_N48W114_Map.tif')
land_cover2 <- rast('Data/calgary/worldcover/ESA_WorldCover_10m_2020_v100_N48W117_Map.tif')
land_cover3 <- rast('Data/calgary/worldcover/ESA_WorldCover_10m_2020_v100_N51W114_Map.tif')
land_cover4 <- rast('Data/calgary/worldcover/ESA_WorldCover_10m_2020_v100_N51W117_Map.tif')

calgary_land_cover <- terra::merge(land_cover1,land_cover2,land_cover3,land_cover4) #Merge together all land cover rasters that overlap Calgary

calgary_land_cover2 <- crop(calgary_land_cover, calgary_boundary %>% st_transform('EPSG:4326')) #Crop Raster to Calgary

grid4 <- grid3 %>% st_transform('EPSG:4326') #Project grid into WGS1984 so that I do not have to project the raster

mode <- function(class){
  which.max(tabulate(class))
}

# Extract raster values within each polygon and determine the most common raster value
land_cover_values <- extract(calgary_land_cover2, grid4) %>%
  rename(landcover = ESA_WorldCover_10m_2020_v100_N48W114_Map) %>%
  group_by(ID) %>% summarise(landcover_mode = mode(landcover)) %>%
  ungroup() %>%
  select(landcover_mode)

grid5 <- cbind(grid4,land_cover_values) %>% 
  st_transform('EPSG:3780') %>%
  mutate(landcover_mode = as.factor(landcover_mode))

Distance to Water

Calculate distance from grid square centroid to water.

water <- st_read('Data/calgary/Hydrology_20240320.geojson') %>% st_transform('EPSG:3780')

centroid <- grid5 %>%
  st_centroid()

nearest_feat <- st_nearest_feature(centroid,water)

grid5$water_dist <- as.double(st_distance(centroid, water[nearest_feat,], by_element=TRUE))

Slope

Calculate slope and summarize slope by grid square - use max slope in each grid square.

calgary_slope <- terrain(dem, v="slope", neighbors=8, unit="degrees")  

slope_extract <- terra::extract(calgary_slope,grid5, fun='max', na.rm=TRUE) %>%
  rename(slope_max = slope) 

grid6 <- cbind(grid5,slope_extract %>% select(slope_max))

Flow Accumilation

Summarize flow accumulation by grid square - use max flow accumulation for each grid square. Flow accumulation was calculated using ArcGIS Pro.

calgary_fa <- rast('Data/calgary/calgary_flowaccumilation.tif') 

calgary_fa <- project(calgary_fa,'EPSG:3780')

fa_extract <- terra::extract(calgary_fa,grid6, fun='max', na.rm=TRUE) %>%
  rename(fa_max = calgary_flowaccumilation) 

grid7 <- cbind(grid6,fa_extract %>% select(fa_max))

grid7$fa_log <- log(grid7$fa_max + 1)

Make Maps of Predictors

The maps below show four of our predictors summarized by grid square. The four predictors are elevation (meters), distance to water (meters), Slope (degrees), and flow accumulation (Number of Grid Squares). For flow accumulation we took the natural log of th grid square values in order to account for the large left skew in the data. As shown, areas with a low elevation tend to be located near water. Additionally, areas near water tend to have a low slope and high flow accumulation.

create_map <- function(variable,title,color_scale,legend_label){
  ggplot()+
    geom_sf(data=grid7,aes(fill={{variable}}),color='transparent')+
    scale_fill_viridis(option=color_scale,name=legend_label)+
    ggtitle(title)+
    theme_void()
}

m1 <- create_map(elevation,'Elevation Above Minimum City Elevation (meters)','rocket','elevation (meters')

m2 <- create_map(water_dist,'Distance to Water (meters)','plasma','distance (meters)')

m3 <- create_map(slope_max,'Slope','magma','Slope')

m4 <- create_map(fa_log,'Flow Accumilation (Natural Log)','cividis','Number of Pixels')

grid.arrange(m1,m2,m3,m4)

Model

Model Training Sets

We split our data into a training and test sets, the training data is used train and build the model, while the test data is used to test the accuracy of the model.

set.seed(3456)
trainIndex <- createDataPartition(grid7$landcover_mode, p = .70,
                                  list = FALSE,
                                  times = 1)

floodTrain <- grid7[ trainIndex,]
floodTest  <- grid7[-trainIndex,]

The table below shows our model’s regression summary. We will focus on the Estimate and the Pr(>|z|) columns (i.e t values). If the estimate column is negative it means that as the independent variable decreases the odds of a the area being inundated increase. For example, as elevation, and distance to water decrease the odds of an area being flooded increase. On the other hand, as the logged flow accumulation and slope increase the odds of an area being flooded increases. The low t values indicate that all our predictors are statistically significant predictors of flooding except for landcover class 100 and land cover class 90 variables. Land cover class 100 is Moss and lichen while land cover class 90 is Herbaceous Wetland.

floodModel <- glm(in_id ~ ., 
                    family="binomial"(link="logit"), data = floodTrain %>%
                                                            as.data.frame() %>%
                                                            select(-geometry, -ID, -fa_max))

summary(floodModel)$coefficients %>%
  kable() %>%
  kable_minimal()
Estimate Std. Error z value Pr(>|z|)
(Intercept) 2.2187999 0.1151586 19.2673398 0.0000000
elevation -0.0321810 0.0008572 -37.5401022 0.0000000
landcover_mode30 -1.8684578 0.0712500 -26.2239628 0.0000000
landcover_mode40 -3.8445072 0.1986528 -19.3528978 0.0000000
landcover_mode50 -1.5468849 0.0792850 -19.5104307 0.0000000
landcover_mode60 -1.6516599 0.0904368 -18.2631421 0.0000000
landcover_mode80 0.4068491 0.0883975 4.6024955 0.0000042
landcover_mode90 -12.7530422 138.8911827 -0.0918204 0.9268407
landcover_mode100 0.3285421 0.3244950 1.0124721 0.3113124
water_dist -0.0062808 0.0001982 -31.6964148 0.0000000
slope_max 0.0365059 0.0066835 5.4620701 0.0000000
fa_log 0.0991705 0.0082031 12.0893551 0.0000000

Model validation

We validate the model and check the accuracy using a variety of different metrics. The histogram below shows the predicted probabilities of a flood occurring for the grid squares in our test dataset. As shown, the predicted probability for the majority of grid squares is low.

classProbs <- predict(floodModel, floodTest, type="response") %>% data.frame %>% setNames(.,c('pred'))

ggplot(data=classProbs,aes(x=pred))+
  geom_histogram(bins=100,fill='orange',color='grey90')+
  theme_bw()+
  ylab('Count')+
  xlab('Probability')

The charts below show the predicted probability density for grid squares in the test dataset that flooded and did not flood. Grid squares that did not flood (0) have a very low predicated probability of flooding, the predicted probability for flooding is below 0.05 for almost all grid squares. For grid squares in the test dataset that flooded the predicted probability ranges significantly, but peaks around 0.12. The predicted probability of flooding is below 0.5 for the majority of flooded grid squares, indicating that a 0.5 threshold may not be optimal.

testProbs <- data.frame(obs = as.factor(floodTest$in_id),
                        pred = classProbs)

ggplot(testProbs, aes(x = pred, fill=as.factor(obs))) + 
  geom_density() +
  facet_grid(obs ~ .,scales = 'free') + 
  xlab("Probability") +
  ylab("Frequency")+
  geom_vline(xintercept = .5) +
  scale_fill_manual(values = c("dark blue", "dark green"),
                      labels = c("Not flooded","flooded"),
                      name = "")+
  theme_bw()

The chart below shows the receiver operating characteristic curve (ROC) for our model. The ROC curve plots the false positive fraction (i.e: percent of flooded grid squares incorrectly predicted) and True positive fraction (i.e: percent of flooded grid squares correctly predicted) at fifty different thresholds.

The ROC curves help show the trade offs in a logistic regression analysis. If we select a very low thresholds, the model will correctly predict more flooded grid squares (i.e: the true positive fraction will be higher). However, when a low threshold is used the model will also likely incorrectly predict many flooded grid squares as not being flooded (i.e: the false positive rate will also be high).

auc <- round(auc(testProbs$obs, testProbs$pred),2)

ggplot(testProbs, aes(d = as.numeric(obs), m = pred)) + 
  geom_roc(n.cuts = 50, labels = FALSE,color='blue') + 
  annotate("text", x = 0.1, y = 1, label=paste("AUC: ",as.character(auc)),color='blue')+
  style_roc(theme = theme_grey) +
  geom_abline(slope = 1, intercept = 0, size = 1.5, color = 'grey')+
  theme_bw()

Based on the ROC curve and the density graph above we determined that the optimal threshold is 0.12. Grid squares that the model predicts as having a flood probability higher than 0.12 are classified as flooded by our model. Grid squares that have a flood probability lower than 0.12 are classified as likely not to flood. At the 0.12 threshold, the model correctly predicts 83% of flooded grid squares (Sensitivity) and 88% of non flooded grid squares (Specificity).

testProbs$predClass <- ifelse(testProbs$pred > .12 ,1,0)

matrix <- caret::confusionMatrix(reference = as.factor(testProbs$obs), 
                       data = as.factor(testProbs$predClass), 
                       positive = "1")

matrix$table %>%   
  data.frame() %>%
  spread(key=Reference,value=Freq) %>%
  rename('No_Flood' = '0', 'Flood' = '1') %>%
  mutate(Total = No_Flood + Flood,
         Prediction = c('No_Flood','Flood')) %>%
  kbl() %>%
  add_header_above(header=c(" " = 1,"Actual" = 2," " = 1)) %>%
  kable_minimal() %>%
  kable_styling(full_width = F)
Actual
Prediction No_Flood Flood Total
No_Flood 18271 248 18519
Flood 2309 1206 3515
matrix$byClass %>%
  data.frame() %>%
  head(2) %>%
  rename(.,Value = .) %>%
  mutate(Value = round(Value*100,2)) %>%
  kbl(col.names = c('Accuracy Metric','Value')) %>%
  kable_minimal() %>%
  kable_styling(full_width = F)
Accuracy Metric Value
Sensitivity 82.94
Specificity 88.78

Model Cross validation

We also run a cross validation for our model. The cross validation helps us understand if the model is generalizable to different data. A cross validation involves running the model multiple times while changing the data points which are included in the training and test datasets. We run a 100-fold cross validation using the Calgary data and compare the predicted outcomes to the actual outcome in each of the 100 test datasets and calculate the accuracy for each fold.

The histogram below shows a histogram of the accuracy for all fifty folds. As seen in the histogram below the model accuracy is consistent across folds, indicating that the model is generalizable.

ctrl <- trainControl(method = "cv", 
                     number = 100, 
                     p = 0.7, 
                     savePredictions = TRUE)

cvFit <- train(as.factor(in_id) ~ .,  data = grid7 %>% 
                                                as.data.frame() %>%
                                                select(-geometry, -ID,-fa_max), 
               method="glm", family="binomial",
               trControl = ctrl)

ggplot(as.data.frame(cvFit$resample), aes(Accuracy)) + 
  geom_histogram(fill='orange',color='grey90',bins=100) +
  scale_x_continuous(limits = c(0, 1)) +
  labs(x="Accuracy",
       y="Count")+
  theme_bw()

floodtest1 <- cbind(floodTest,testProbs) %>%
  mutate(type = case_when(obs == 0 & predClass == 0 ~ 'TN',
                          obs == 1 & predClass == 1 ~ 'TP',
                          obs == 1 & predClass == 0 ~ 'FN',
                          obs == 0 & predClass == 1 ~ 'FP'))

grid7 <- grid7 %>% 
  mutate(predict = predict(floodModel, grid7, type="response"),
         outcome = as.factor(ifelse(predict > 0.12,1,0)))

The maps below show the results for predictions in the Calgary Test Set and Predictions for all of Calgary. The predictions for the test set show the location of false negatives, false positives, true negatives, and true positives. As shown, the majority of true positives are located near the river. However, there are also a large number of false positives located along the river in the Northern areas of the city and there is also a large cluster of false positives in the eastern area of the city.

grid.arrange(nrow=1,

ggplot()+
  geom_sf(data=floodtest1,aes(fill=type),color='transparent')+
  scale_fill_manual(values=c('grey50','blue','lightgreen','purple'),name='',labels=c('False Negative','False Positive','True Negative','True Positive'))+
  theme_void()+
  ggtitle('Results for Calgary Test Set'),

ggplot()+
  geom_sf(data=grid7,aes(fill=outcome),color='transparent')+
  scale_fill_manual(values=c('#fc9c9c','lightblue'),name='',labels=c('No Flood','Flood'))+
  theme_void()+
  ggtitle('Predictions for Calgary')
)

Denver Feature Engineering

Next we build a fishnet for the city of Denver. The fishnet includes the same variables as the Calgary fishnet and is also the same size as the Calgary fishnet.

Denver Elevation and Slope

dem_1 <- rast("Data/denver/n39_w105_1arc_v3.tif")

dem_2 <- rast("Data/denver/n39_w106_1arc_v32.tif")

merg_dem <- terra::merge(dem_1,dem_2)

merg_dem <- project(merg_dem, "EPSG:6427")


denver_bound <-
  st_read("Data/denver/county_boundary") %>%
  st_transform(crs = "EPSG:6427")
 

dem_crop <- crop(merg_dem, denver_bound)
# 'mask' out the values outside denver
denver_dem <- mask(dem_crop, denver_bound)


denver_slope <- terrain(denver_dem, v="slope", neighbors=8, unit="degrees")  

Denver Grid

Make grid for Denver.

grid_den <- st_make_grid(denver_bound,100,square=TRUE) %>%
  st_sf() %>%
  mutate(ID = row_number())

# Get just grid squares with centroid in Calgary city limits

grid_den <- grid_den %>%
  st_centroid() %>%
  st_join(.,denver_bound,join = st_intersects,left=FALSE) %>%
  st_drop_geometry() %>%
  left_join(.,grid_den,by='ID') %>%
  st_as_sf() %>%
  mutate(ID = row_number()) %>%
  select(ID)

Elevation Zonal Stats

Extract elevation data for each grid squares, calculate mean elevation for each.

elevation_extract <- terra::extract(merg_dem,grid_den, fun='mean', na.rm=TRUE) %>%
  rename(elevation_mean = n39_w105_1arc_v3) %>%
  mutate(elevation = elevation_mean - min(elevation_mean))

grid3_den <- cbind(grid_den,elevation_extract %>% select(elevation))

Slope Zonal Stats

Extract slope data for each grid squares, calculate mean slope for each.

den_slope_extract <- terra::extract(denver_slope,grid_den, fun='max', na.rm=TRUE) %>%
  rename(slope_max = slope) 

grid3_den <- cbind(grid3_den,den_slope_extract %>% select(slope_max))

Land Cover Zonal Stats

Summarize land cover data by grid square and figure out the mode land cover class for each grid square.

#Need four land cover tiles to cover all of Calgary
land_cover1 <- rast('Data/denver/landcover/ESA_WorldCover_10m_2020_v100_N39W105_Map.tif')
land_cover2 <- rast('Data/denver/landcover/ESA_WorldCover_10m_2020_v100_N39W108_Map.tif')

denver_land_cover <- terra::merge(land_cover1,land_cover2) #Merge together all land cover rasters that overlap Calgary

denver_land_cover2 <- crop(denver_land_cover , denver_bound %>% st_transform('EPSG:4326')) #Crop Raster to Calgary

grid4_den <- grid3_den %>% st_transform('EPSG:4326') #Project grid into WGS1984 so that I do not have to project the raster

mode <- function(class){
  which.max(tabulate(class))
}

# Extract raster values within each polygon and determine the most common raster value
land_cover_values <- extract(denver_land_cover2, grid4_den) %>%
  rename(landcover = ESA_WorldCover_10m_2020_v100_N39W105_Map) %>%
  group_by(ID) %>% summarise(landcover_mode = mode(landcover)) %>%
  ungroup() %>%
  select(landcover_mode)

grid5_den <- cbind(grid4_den,land_cover_values) %>% 
  st_transform('EPSG:6427') %>%
  mutate(landcover_mode = as.factor(landcover_mode))

Distance to Water

Calculate distance to water for each grid square. The Denver data includes seperate streams and lakes data, we calculated the distance from each grid square to the nearest lake and the nearest stream and then took the distance which was lowest as the distance to water.

lakes_ponds <- st_read("Data/denver/lakes_and_ponds/") %>%
  st_transform("EPSG:6427") 
streams <- st_read("Data/denver/streams/") %>%
  st_transform("EPSG:6427") 


centroid <- grid5_den %>%
  st_centroid()

nearest_feat <- st_nearest_feature(centroid,lakes_ponds)

grid5_den$lakes_ponds_dist <- as.double(st_distance(centroid, lakes_ponds[nearest_feat,], by_element=TRUE))

nearest_feat_streams <- st_nearest_feature(centroid,streams)

grid5_den$streams_dist <- as.double(st_distance(centroid, streams[nearest_feat_streams,], by_element=TRUE))

grid5_den$water_dist <- pmin(grid5_den$lakes_ponds_dist, grid5_den$streams_dist)

grid5_den <- grid5_den %>% select(-streams_dist,-lakes_ponds_dist)

Flow Accumilation

Summarize flow accumulation by grid square - use max flow accumulation for each grid square. Flow accumulation was calculated using ArcGIS Pro.

denver_fa <- rast('Data/denver/denver_flow_accumilation.tif') 

denver_fa <- project(denver_fa,'EPSG:6427')

fa_extract <- terra::extract(denver_fa,grid5_den, fun='max', na.rm=TRUE) %>%
  rename(fa_max = denver_flow_accumilation) 

grid6_den <- cbind(grid5_den,fa_extract %>% select(fa_max))

grid6_den$fa_log <- log(grid6_den$fa_max + 1)

Make Predictions for Denver

grid6_den <- grid6_den %>%
  mutate(prediction = predict(floodModel,grid6_den, type="response"),
  outcome = as.factor(ifelse(prediction > 0.12, 1,0)))
ggplot()+
  geom_sf(data=grid6_den,aes(fill=outcome),color='transparent')+
  scale_fill_manual(values=c('#fc9c9c','lightblue'),name='',labels=c('No Flood','Flood'))+
  theme_void()+
  ggtitle('Predictions for Denver')

LS0tDQp0aXRsZTogIkZsb29kIFByZWRpY3Rpb24gTW9kZWwiDQphdXRob3I6ICJSaWNoYXJkIEJhcmFkIGFuZCBKYXJyZWQgUmFuZGFsbCINCmRhdGU6ICIyMDI0LTA0LTAzIg0Kb3V0cHV0Og0KICBodG1sX2RvY3VtZW50Og0KICAgIHRoZW1lOiBqb3VybmFsDQogICAgdG9jOiB0cnVlDQogICAgdG9jX2Zsb2F0OiB0cnVlDQogICAgY29kZV9mb2xkaW5nOiBoaWRlDQogICAgY29kZV9kb3dubG9hZDogdHJ1ZQ0KLS0tDQoNCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRSkNCmBgYA0KDQojIFBsYW5uaW5nIE1vdGl2YXRpb24NCg0KSW4gSnVuZSAyMDEzIHRoZSBjaXR5IG9mIENhbGdhcnkgZXhwZXJpZW5jZWQgYW4gZXh0cmVtZSBmbG9vZGluZyBldmVudHMgdGhhdCByZXN1bHRlZCBpbiB0aGUgZGlzcGxhY2VtZW50IG9mIDEwMCwwMDAgcGVvcGxlIGFuZCAxLjcgYmlsbGlvbiBkb2xsYXJzIHdlcmUgbmVlZGVkIGZvciByZXBhaXJzLiBUaGUgZXZlbnQgd2FzIG9uZSBvZiB0aGUgY29zdGxpZXN0IGRpc2FzdGVyIGluIENhbmFkYSdzIGhpc3RvcnkuIEluIHRoaXMgcHJvamVjdCB3ZSBhaW0gdG8gYnVpbGQgYSBwcmVkaWN0aXZlIG1vZGVsIHRoYXQgd2lsbCBoZWxwIENhbGdhcnkgYW5kIGNpdGllcyB3aXRoIHNpbWlsYXIgdG9wb2dyYXBoeSBiZSBiZXR0ZXIgcHJlcGFyZWQgZm9yIGZ1dHVyZSBsYXJnZSBzY2FsZSBmbG9vZGluZyBldmVudHMgc3VjaCBhcyB0aGUgMjAxMyBkaXNhc3Rlci4gDQoNClRoZSByZXN1bHRzIG9mIHRoZSBtb2RlbCBjYW4gaGVscCB3aXRoIGVhcmx5IGFjdGlvbiBhbmQgcGxhbm5pbmcsIGFzIGRlY2lzaW9uIG1ha2VycyBhbmQgcHVibGljIHdpbGwgaGF2ZSByZWFkaWx5IGF2YWlsYWJsZSBpbmZvcm1hdGlvbiBvbiB3aGF0IGFyZWFzIG9mIHRoZSBjaXR5IG1pZ2h0IG5lZWQgdG8gYmUgZXZhY3VhdGVkLiBBZGRpdGlvbmFsbHksIHRoZSBtb2RlbCBjYW4gYWxzbyBoZWxwIGlkZW50aWZ5IGFyZWFzIG9mIHRoZSBjaXR5IHRoYXQgbWlnaHQgbmVlZCBidXlvdXRzIGFuZC9vciByZXZpc2lvbnMgdG8gdGhlIGJ1aWxkaW5nIGNvZGUgdG8gcmVxdWlyZSBmbG9vZCBwcm9vZmluZy4gDQoNCkFuIGFkdmFudGFnZSBvZiBvdXIgYWxnb3JpdGhtIGNvbXBhcmVkIHRvIEZFTUEgZmVkZXJhbCBsZXZlbCBmbG9vZHBsYWluIG1hcHBpbmcgaXMgdGhhdCB0aGUgbW9kZWwgc2hvdWxkIGJlIGFibGUgdG8gaGVscCBpZGVudGlmeSBhcmVhcyBvZiBmbG9vZGluZyBiZXlvbmQganVzdCByaXZlciBmbG9vZGluZy4gQmVjYXVzZSB0aGUgbW9kZWwgaXMgYmFzZWQgb24gYSBoaXN0b3JpY2FsIGV2ZW50IHRoYXQgcmVzdWx0ZWQgaW4gZmxvb2Rpbmcgbm90IG5lYXIgcml2ZXJzLCB0aGUgbW9kZWwgc2hvdWxkIGJlIGFibGUgdG8gY2FwdHVyZSBmbG9vZGluZyBpbiBvdGhlciBsb3cgbHlpbmcgYXJlYXMgYW5kIHZhbGxleXMgb3V0c2lkZSBvZiByaXZlcmluZSBhcmVhcy4gT3VyIG1vZGVsIHdpbGwgYmUgdHJhaW5lZCBhbmQgdGVzdGVkIHVzaW5nIGludW5kYXRpb24gZGF0YSBmcm9tIHRoZSBoaXN0b3JpY2FsIGZsb29kIGV2ZW50IGluIDIwMTMuIFdlIHdpbGwgdGhlbiB1c2Ugb3VyIG1vZGVsIHRvIHByZWRpY3QgZmxvb2QgcmlzayBpbiBEZW52ZXIuIERlbnZlciB3YXMgc2VsZWN0ZWQgYmVjYXVzZSB0aGUgY2l0eSBvZiBEZW52ZXIgaGFzIHNpbWlsYXIgZ2VvZ3JhcGhpYyBjaGFyYWN0ZXJpc3RpY3MgdG8gQ2FsZ2FyeSBhbmQgY29udGFpbnMgbXVsdGlwbGUgcml2ZXJzLiANCg0KIyMgTW9kZWwgUHJlZGljdG9ycw0KDQpUaGUgcHJlZGljdG9ycyBvZiBmbG9vZGluZyB0aGF0IHdlIHdpbGwgdXNlIGluIG91ciBtb2RlbCBhcmU6DQoNCiogRWxldmF0aW9uIGFib3ZlIG1pbmltdW0gZWxldmF0aW9uIGluIHRoZSBjaXR5DQoqIERvbWluYW50IExhbmQgQ292ZXINCiogU2xvcGUNCiogRGlzdGFuY2UgdG8gV2F0ZXINCiogV2F0ZXIgRmxvdyBBY2N1bXVsYXRpb24gKGkuZTogdGhlIG51bWJlciBvZiBncmlkIHNxdWFyZXMgZmxvd2luZyBpbnRvIGEgZ3JpZCBzcXVhcmUpDQoNCk91ciBhbmFseXNpcyB3aWxsIHVzZSAxMDBtIHggMTAwbSBncmlkIGFuZCB3ZSB1c2UgYSBsb2dpc3RpYyByZWdyZXNzaW9uIG1vZGVsIHRvIGRldGVybWluZSBhcmVhcyB0aGF0IGFyZSBsaWtlbHkgdG8gYmUgZmxvb2RlZCBkdXJpbmcgYSBmbG9vZCBldmVudCB0aGF0IGlzIHNpbWlsYXIgdG8gdGhlIGV2ZW50IHRoYXQgb2NjdXJyZWQgaW4gQ2FsZ2FyeSBpbiAyMDEzLiANCg0KIyBJbXBvcnQgTGlicmFyaWVzDQoNCmBgYHtyIGltcG9ydF9saWJyYXJpZXMsIHJlc3VsdHM9J2hpZGUnLHdhcm5pbmc9RkFMU0V9DQpsaWJyYXJ5KHNmKQ0KbGlicmFyeShjYXJldCkNCmxpYnJhcnkocHNjbCkNCmxpYnJhcnkocGxvdFJPQykNCmxpYnJhcnkocFJPQykNCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeSh0ZXJyYSkNCmxpYnJhcnkoa2FibGVFeHRyYSkNCmxpYnJhcnkodGlncmlzKQ0KbGlicmFyeSh2aXJpZGlzKQ0KbGlicmFyeShncmlkRXh0cmEpDQpgYGANCg0KIyBEYXRhIFNldHVwIGZvciBDYWxnYXJ5DQoNCiMjIFJlYWQgRGF0YSBmb3IgQ2FsZ2FyeQ0KDQpSZWFkIHRoZSBDYWxnYXJ5IGJvdW5kYXJ5IGFuZCBpbnVuZGF0aW9uIGxheWVyLg0KDQpgYGB7ciByZWFkX2ZpbGVzLCBlY2hvPUZBTFNFLCBtZXNzYWdlPUZBTFNFLCByZXN1bHRzPSdoaWRlJ30NCg0KY2FsZ2FyeV9ib3VuZGFyeSA8LSBzdF9yZWFkKCdEYXRhL2NhbGdhcnkvQ0FMR0lTX0NJVFlCT1VORF9MSU1JVC9DQUxHSVNfQ0lUWUJPVU5EX0xJTUlULnNocCcpICU+JSBzdF90cmFuc2Zvcm0oJ0VQU0c6Mzc4MCcpDQoNCmludW5kYXRpb24gPC0gcmFzdCgnRGF0YS9jYWxnYXJ5L2ludW5kYXRpb24vdzAwMTAwMS5hZGYnKQ0KDQpgYGANCg0KIyMgTWFrZSBHcmlkDQoNCk1ha2UgYSBmaXNobmV0IHVzaW5nIHN0X21ha2VfZ3JpZCBhbmQgcmVtb3ZlIGdyaWQgc3F1YXJlcyB0aGF0IGFyZSBub3QgbG9jYXRlZCB3aXRoaW4gdGhlIENhbGdhcnkgYm91bmRhcnkuIE91dCBncmlkIHNxdWFyZXMgYXJlIDEwMCB4IDEwMCBtZXRlcnMuDQoNCmBgYHtyIG1ha2VfZ3JpZDEsIHdhcm5pbmc9RkFMU0V9DQoNCmludW5kYXRpb24gPC0gcHJvamVjdChpbnVuZGF0aW9uLCdFUFNHOjM3ODAnKSAjUHJvamVjdCBpbm51bmRhdGlvbiByYXN0ZXIgaW50byBjb3JyZWN0IGNvcmRpbmF0ZSBzeXN0ZW0NCg0KZ3JpZCA8LSBzdF9tYWtlX2dyaWQoY2FsZ2FyeV9ib3VuZGFyeSwxMDAsc3F1YXJlPVRSVUUpICU+JQ0KICBzdF9zZigpICU+JQ0KICBtdXRhdGUoSUQgPSByb3dfbnVtYmVyKCkpDQoNCiMgR2V0IGp1c3QgZ3JpZCBzcXVhcmVzIHdpdGggY2VudHJvaWQgaW4gQ2FsZ2FyeSBjaXR5IGxpbWl0cw0KDQpncmlkIDwtIGdyaWQgJT4lDQogIHN0X2NlbnRyb2lkKCkgJT4lDQogIHN0X2pvaW4oLixjYWxnYXJ5X2JvdW5kYXJ5LGpvaW4gPSBzdF9pbnRlcnNlY3RzLGxlZnQ9RkFMU0UpICU+JQ0KICBzdF9kcm9wX2dlb21ldHJ5KCkgJT4lDQogIGxlZnRfam9pbiguLGdyaWQsYnk9J0lEJykgJT4lDQogIHN0X2FzX3NmKCkgJT4lDQogIG11dGF0ZShJRCA9IHJvd19udW1iZXIoKSkgJT4lDQogIHNlbGVjdChJRCkNCg0KYGBgDQoNClN1bW1hcml6ZSB0aGUgcHJvdmlkZWQgaW51bmRhdGlvbiBkYXRhIGJ5IGdyaWQgc3F1YXJlLiBHcmlkIHNxdWFyZXMgdGhhdCBhcmUgbW9yZSB0aGFuIDI1JSBpbnVuZGF0ZWQgd2lsbCBiZSBjb25zaWRlcmVkIGludW5kYXRlZCBpbiB0aGlzIG1vZGVsLiANCg0KYGBge3J9DQoNCiMgQ291bnQgdGhlIG51bWJlciBvZiBpbnVuZGF0aW9uIGNlbGxzIGluIGVhY2ggZ3JpZCBzcXVhcmUgaW4gZmlzaG5ldA0KDQpleHRyYWN0X3ZhbHVlcyA8LSBleHRyYWN0KGludW5kYXRpb24sIGdyaWQpICU+JSAjQXNzaWduIGVhY2ggcmFzdGVyIHBpeGVsIHRvIGEgZ3JpZCBzcXVhcmUNCiAgZ3JvdXBfYnkoSUQsdzAwMTAwMSkgJT4lIHRhbGx5KCkgJT4lDQogIHVuZ3JvdXAoKSAlPiUNCiAgcGl2b3Rfd2lkZXIoaWRfY29scz1JRCxuYW1lc19mcm9tPXcwMDEwMDEsdmFsdWVzX2Zyb209bikgJT4lDQogIHJlcGxhY2UoaXMubmEoLiksIDApICU+JSAjUmVwbGFjZSBOQSB3aXRoIDANCiAgZmlsdGVyKGBOQWAgPT0gMCkgJT4lICNGaWx0ZXIgb3V0IGdyaWQgc3F1YXJlcyB0aGF0IGRvIG5vdCBoYXZlIGludW5kYXRpb24gZGF0YQ0KICAjIDEgYW5kIDIgYXJlIGludW5kYXRpb24sIHN1bSB0aG9zZSB0b2dldGhlciBhbmQgc3VtIDAgYW5kIDMgdG9nZXRoZXIgZm9yIG5vbl9pbnVuZGF0aW9uDQogIG11dGF0ZShpbm51bmRhdGlvbiA9IGAxYCArIGAyYCwNCiAgICAgICAgIG5vbl9pbm51bmRhdGUgPSBgMGAgKyBgM2AgKyBgTkFgLA0KICAgICAgICAgaW5faWQgPSBhcy5mYWN0b3IoaWZlbHNlKGlubnVuZGF0aW9uLyhpbm51bmRhdGlvbiArIG5vbl9pbm51bmRhdGUpID4gMC4yNSwxLDApKSkgJT4lICNJZiBncmlkIHNxdWFyZSBpcyBtb3JlIHRoYW4gMjUlIGludW5kYXRlIGNvbnNpZGVyIGl0IGludW5kYXRlIChpLmU6IDEpDQogIHNlbGVjdChJRCxpbl9pZCkNCg0KYGBgDQoNClRoZSBtYXAgYmVsb3cgc2hvd3MgdGhlIGZsb29kZWQgYXJlYSBpbiAyMDEzIHN1bW1hcml6ZWQgYnkgZ3JpZCBzcXVhcmVzLg0KDQpgYGB7cn0NCg0KZ3JpZDIgPC0gcmlnaHRfam9pbihncmlkLGV4dHJhY3RfdmFsdWVzLGJ5PSdJRCcpIA0KDQojIE1ha2UgTWFwIG9mIGludW5kYXRlZCBhcmVhcw0KDQpnZ3Bsb3QoKSsNCiAgZ2VvbV9zZihkYXRhPWdyaWQyLGFlcyhmaWxsPWluX2lkKSxjb2xvcj0ndHJhbnNwYXJlbnQnKSsNCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzPWMoJyNmYzljOWMnLCdsaWdodGJsdWUnKSxuYW1lPScnLGxhYmVscz1jKCdObyBGbG9vZCcsJ0Zsb29kJykpKw0KICB0aGVtZV92b2lkKCkNCg0KYGBgDQoNCiMgRW5naW5lZXIgUHJlZGljdG9yIEZlYXR1cmVzIGZvciBDYWxnYXJ5DQoNCiMjIEVsZXZhdGlvbg0KDQpHZXQgem9uYWwgc3RhdGlzdGljcyBmb3IgZ3JpZCBzcXVhcmVzLCBjYWxjdWxhdGUgbWVhbiBlbGV2YXRpb24gZm9yIGVhY2ggZ3JpZCBzcXVhcmUuDQoNCmBgYHtyIGVsZXZhdGlvbn0NCg0KZGVtIDwtIHJhc3QoJ0RhdGEvY2FsZ2FyeS9jYWxnYXJ5ZGVtL3cwMDEwMDEuYWRmJykNCg0KZGVtIDwtIHByb2plY3QoZGVtLCdFUFNHOjM3ODAnKQ0KDQplbGV2YXRpb25fZXh0cmFjdCA8LSB0ZXJyYTo6ZXh0cmFjdChkZW0sZ3JpZDIsZnVuPSdtZWFuJyxuYS5ybT1UUlVFKSAlPiUNCiAgcmVuYW1lKGVsZXZhdGlvbl9tZWFuID0gdzAwMTAwMSkgJT4lDQogIG11dGF0ZShlbGV2YXRpb24gPSBlbGV2YXRpb25fbWVhbiAtIG1pbihlbGV2YXRpb25fbWVhbikpDQoNCmdyaWQzIDwtIGNiaW5kKGdyaWQyLGVsZXZhdGlvbl9leHRyYWN0ICU+JSBzZWxlY3QoZWxldmF0aW9uKSkNCg0KYGBgDQoNCiMjIExhbmQgQ292ZXINCg0KU3VtbWFyaXplIGxhbmQgY292ZXIgZGF0YSBieSBncmlkIHNxdWFyZSBhbmQgZmlndXJlIG91dCB0aGUgbW9kZSBsYW5kIGNvdmVyIGNsYXNzIGZvciBlYWNoIGdyaWQgc3F1YXJlLg0KDQpgYGB7ciBsYW5kX2NvdmVyX3pvbmFsLCByZXN1bHRzPSdoaWRlJ30NCg0KI05lZWQgZm91ciBsYW5kIGNvdmVyIHRpbGVzIHRvIGNvdmVyIGFsbCBvZiBDYWxnYXJ5DQpsYW5kX2NvdmVyMSA8LSByYXN0KCdEYXRhL2NhbGdhcnkvd29ybGRjb3Zlci9FU0FfV29ybGRDb3Zlcl8xMG1fMjAyMF92MTAwX040OFcxMTRfTWFwLnRpZicpDQpsYW5kX2NvdmVyMiA8LSByYXN0KCdEYXRhL2NhbGdhcnkvd29ybGRjb3Zlci9FU0FfV29ybGRDb3Zlcl8xMG1fMjAyMF92MTAwX040OFcxMTdfTWFwLnRpZicpDQpsYW5kX2NvdmVyMyA8LSByYXN0KCdEYXRhL2NhbGdhcnkvd29ybGRjb3Zlci9FU0FfV29ybGRDb3Zlcl8xMG1fMjAyMF92MTAwX041MVcxMTRfTWFwLnRpZicpDQpsYW5kX2NvdmVyNCA8LSByYXN0KCdEYXRhL2NhbGdhcnkvd29ybGRjb3Zlci9FU0FfV29ybGRDb3Zlcl8xMG1fMjAyMF92MTAwX041MVcxMTdfTWFwLnRpZicpDQoNCmNhbGdhcnlfbGFuZF9jb3ZlciA8LSB0ZXJyYTo6bWVyZ2UobGFuZF9jb3ZlcjEsbGFuZF9jb3ZlcjIsbGFuZF9jb3ZlcjMsbGFuZF9jb3ZlcjQpICNNZXJnZSB0b2dldGhlciBhbGwgbGFuZCBjb3ZlciByYXN0ZXJzIHRoYXQgb3ZlcmxhcCBDYWxnYXJ5DQoNCmNhbGdhcnlfbGFuZF9jb3ZlcjIgPC0gY3JvcChjYWxnYXJ5X2xhbmRfY292ZXIsIGNhbGdhcnlfYm91bmRhcnkgJT4lIHN0X3RyYW5zZm9ybSgnRVBTRzo0MzI2JykpICNDcm9wIFJhc3RlciB0byBDYWxnYXJ5DQoNCmdyaWQ0IDwtIGdyaWQzICU+JSBzdF90cmFuc2Zvcm0oJ0VQU0c6NDMyNicpICNQcm9qZWN0IGdyaWQgaW50byBXR1MxOTg0IHNvIHRoYXQgSSBkbyBub3QgaGF2ZSB0byBwcm9qZWN0IHRoZSByYXN0ZXINCg0KbW9kZSA8LSBmdW5jdGlvbihjbGFzcyl7DQogIHdoaWNoLm1heCh0YWJ1bGF0ZShjbGFzcykpDQp9DQoNCiMgRXh0cmFjdCByYXN0ZXIgdmFsdWVzIHdpdGhpbiBlYWNoIHBvbHlnb24gYW5kIGRldGVybWluZSB0aGUgbW9zdCBjb21tb24gcmFzdGVyIHZhbHVlDQpsYW5kX2NvdmVyX3ZhbHVlcyA8LSBleHRyYWN0KGNhbGdhcnlfbGFuZF9jb3ZlcjIsIGdyaWQ0KSAlPiUNCiAgcmVuYW1lKGxhbmRjb3ZlciA9IEVTQV9Xb3JsZENvdmVyXzEwbV8yMDIwX3YxMDBfTjQ4VzExNF9NYXApICU+JQ0KICBncm91cF9ieShJRCkgJT4lIHN1bW1hcmlzZShsYW5kY292ZXJfbW9kZSA9IG1vZGUobGFuZGNvdmVyKSkgJT4lDQogIHVuZ3JvdXAoKSAlPiUNCiAgc2VsZWN0KGxhbmRjb3Zlcl9tb2RlKQ0KDQpncmlkNSA8LSBjYmluZChncmlkNCxsYW5kX2NvdmVyX3ZhbHVlcykgJT4lIA0KICBzdF90cmFuc2Zvcm0oJ0VQU0c6Mzc4MCcpICU+JQ0KICBtdXRhdGUobGFuZGNvdmVyX21vZGUgPSBhcy5mYWN0b3IobGFuZGNvdmVyX21vZGUpKQ0KDQpgYGANCg0KIyMgRGlzdGFuY2UgdG8gV2F0ZXINCg0KQ2FsY3VsYXRlIGRpc3RhbmNlIGZyb20gZ3JpZCBzcXVhcmUgY2VudHJvaWQgdG8gd2F0ZXIuDQoNCmBgYHtyIHdhdGVyX2Rpc3QsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIHJlc3VsdHM9J2hpZGUnfQ0KDQp3YXRlciA8LSBzdF9yZWFkKCdEYXRhL2NhbGdhcnkvSHlkcm9sb2d5XzIwMjQwMzIwLmdlb2pzb24nKSAlPiUgc3RfdHJhbnNmb3JtKCdFUFNHOjM3ODAnKQ0KDQpjZW50cm9pZCA8LSBncmlkNSAlPiUNCiAgc3RfY2VudHJvaWQoKQ0KDQpuZWFyZXN0X2ZlYXQgPC0gc3RfbmVhcmVzdF9mZWF0dXJlKGNlbnRyb2lkLHdhdGVyKQ0KDQpncmlkNSR3YXRlcl9kaXN0IDwtIGFzLmRvdWJsZShzdF9kaXN0YW5jZShjZW50cm9pZCwgd2F0ZXJbbmVhcmVzdF9mZWF0LF0sIGJ5X2VsZW1lbnQ9VFJVRSkpDQoNCmBgYA0KDQoNCiMjIFNsb3BlDQoNCkNhbGN1bGF0ZSBzbG9wZSBhbmQgc3VtbWFyaXplIHNsb3BlIGJ5IGdyaWQgc3F1YXJlIC0gdXNlIG1heCBzbG9wZSBpbiBlYWNoIGdyaWQgc3F1YXJlLg0KDQpgYGB7ciBjYWxfc2xvcGV9DQoNCmNhbGdhcnlfc2xvcGUgPC0gdGVycmFpbihkZW0sIHY9InNsb3BlIiwgbmVpZ2hib3JzPTgsIHVuaXQ9ImRlZ3JlZXMiKSAgDQoNCnNsb3BlX2V4dHJhY3QgPC0gdGVycmE6OmV4dHJhY3QoY2FsZ2FyeV9zbG9wZSxncmlkNSwgZnVuPSdtYXgnLCBuYS5ybT1UUlVFKSAlPiUNCiAgcmVuYW1lKHNsb3BlX21heCA9IHNsb3BlKSANCg0KZ3JpZDYgPC0gY2JpbmQoZ3JpZDUsc2xvcGVfZXh0cmFjdCAlPiUgc2VsZWN0KHNsb3BlX21heCkpDQoNCmBgYA0KDQojIyBGbG93IEFjY3VtaWxhdGlvbg0KDQpTdW1tYXJpemUgZmxvdyBhY2N1bXVsYXRpb24gYnkgZ3JpZCBzcXVhcmUgLSB1c2UgbWF4IGZsb3cgYWNjdW11bGF0aW9uIGZvciBlYWNoIGdyaWQgc3F1YXJlLiBGbG93IGFjY3VtdWxhdGlvbiB3YXMgY2FsY3VsYXRlZCB1c2luZyBBcmNHSVMgUHJvLg0KDQpgYGB7ciBjYWxfZmF9DQoNCmNhbGdhcnlfZmEgPC0gcmFzdCgnRGF0YS9jYWxnYXJ5L2NhbGdhcnlfZmxvd2FjY3VtaWxhdGlvbi50aWYnKSANCg0KY2FsZ2FyeV9mYSA8LSBwcm9qZWN0KGNhbGdhcnlfZmEsJ0VQU0c6Mzc4MCcpDQoNCmZhX2V4dHJhY3QgPC0gdGVycmE6OmV4dHJhY3QoY2FsZ2FyeV9mYSxncmlkNiwgZnVuPSdtYXgnLCBuYS5ybT1UUlVFKSAlPiUNCiAgcmVuYW1lKGZhX21heCA9IGNhbGdhcnlfZmxvd2FjY3VtaWxhdGlvbikgDQoNCmdyaWQ3IDwtIGNiaW5kKGdyaWQ2LGZhX2V4dHJhY3QgJT4lIHNlbGVjdChmYV9tYXgpKQ0KDQpncmlkNyRmYV9sb2cgPC0gbG9nKGdyaWQ3JGZhX21heCArIDEpDQoNCmBgYA0KDQoNCiMgTWFrZSBNYXBzIG9mIFByZWRpY3RvcnMNCg0KVGhlIG1hcHMgYmVsb3cgc2hvdyBmb3VyIG9mIG91ciBwcmVkaWN0b3JzIHN1bW1hcml6ZWQgYnkgZ3JpZCBzcXVhcmUuIFRoZSBmb3VyIHByZWRpY3RvcnMgYXJlIGVsZXZhdGlvbiAobWV0ZXJzKSwgZGlzdGFuY2UgdG8gd2F0ZXIgKG1ldGVycyksIFNsb3BlIChkZWdyZWVzKSwgYW5kIGZsb3cgYWNjdW11bGF0aW9uIChOdW1iZXIgb2YgR3JpZCBTcXVhcmVzKS4gRm9yIGZsb3cgYWNjdW11bGF0aW9uIHdlIHRvb2sgdGhlIG5hdHVyYWwgbG9nIG9mIHRoIGdyaWQgc3F1YXJlIHZhbHVlcyBpbiBvcmRlciB0byBhY2NvdW50IGZvciB0aGUgbGFyZ2UgbGVmdCBza2V3IGluIHRoZSBkYXRhLiBBcyBzaG93biwgYXJlYXMgd2l0aCBhIGxvdyBlbGV2YXRpb24gdGVuZCB0byBiZSBsb2NhdGVkIG5lYXIgd2F0ZXIuIEFkZGl0aW9uYWxseSwgYXJlYXMgbmVhciB3YXRlciB0ZW5kIHRvIGhhdmUgYSBsb3cgc2xvcGUgYW5kIGhpZ2ggZmxvdyBhY2N1bXVsYXRpb24uDQoNCmBgYCB7ciBtYWtlX21hcHMsZmlnLndpZHRoPTE0LGZpZy5oZWlnaHQ9MTB9DQoNCmNyZWF0ZV9tYXAgPC0gZnVuY3Rpb24odmFyaWFibGUsdGl0bGUsY29sb3Jfc2NhbGUsbGVnZW5kX2xhYmVsKXsNCiAgZ2dwbG90KCkrDQogICAgZ2VvbV9zZihkYXRhPWdyaWQ3LGFlcyhmaWxsPXt7dmFyaWFibGV9fSksY29sb3I9J3RyYW5zcGFyZW50JykrDQogICAgc2NhbGVfZmlsbF92aXJpZGlzKG9wdGlvbj1jb2xvcl9zY2FsZSxuYW1lPWxlZ2VuZF9sYWJlbCkrDQogICAgZ2d0aXRsZSh0aXRsZSkrDQogICAgdGhlbWVfdm9pZCgpDQp9DQoNCm0xIDwtIGNyZWF0ZV9tYXAoZWxldmF0aW9uLCdFbGV2YXRpb24gQWJvdmUgTWluaW11bSBDaXR5IEVsZXZhdGlvbiAobWV0ZXJzKScsJ3JvY2tldCcsJ2VsZXZhdGlvbiAobWV0ZXJzJykNCg0KbTIgPC0gY3JlYXRlX21hcCh3YXRlcl9kaXN0LCdEaXN0YW5jZSB0byBXYXRlciAobWV0ZXJzKScsJ3BsYXNtYScsJ2Rpc3RhbmNlIChtZXRlcnMpJykNCg0KbTMgPC0gY3JlYXRlX21hcChzbG9wZV9tYXgsJ1Nsb3BlJywnbWFnbWEnLCdTbG9wZScpDQoNCm00IDwtIGNyZWF0ZV9tYXAoZmFfbG9nLCdGbG93IEFjY3VtaWxhdGlvbiAoTmF0dXJhbCBMb2cpJywnY2l2aWRpcycsJ051bWJlciBvZiBQaXhlbHMnKQ0KDQpncmlkLmFycmFuZ2UobTEsbTIsbTMsbTQpDQoNCmBgYA0KDQojIE1vZGVsDQoNCiMjIE1vZGVsIFRyYWluaW5nIFNldHMNCg0KV2Ugc3BsaXQgb3VyIGRhdGEgaW50byBhIHRyYWluaW5nIGFuZCB0ZXN0IHNldHMsIHRoZSB0cmFpbmluZyBkYXRhIGlzIHVzZWQgdHJhaW4gYW5kIGJ1aWxkIHRoZSBtb2RlbCwgd2hpbGUgdGhlIHRlc3QgZGF0YSBpcyB1c2VkIHRvIHRlc3QgdGhlIGFjY3VyYWN5IG9mIHRoZSBtb2RlbC4gDQoNCmBgYHtyIHRyYWluaW5nX3NldH0NCnNldC5zZWVkKDM0NTYpDQp0cmFpbkluZGV4IDwtIGNyZWF0ZURhdGFQYXJ0aXRpb24oZ3JpZDckbGFuZGNvdmVyX21vZGUsIHAgPSAuNzAsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGlzdCA9IEZBTFNFLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRpbWVzID0gMSkNCg0KZmxvb2RUcmFpbiA8LSBncmlkN1sgdHJhaW5JbmRleCxdDQpmbG9vZFRlc3QgIDwtIGdyaWQ3Wy10cmFpbkluZGV4LF0NCmBgYA0KDQpUaGUgdGFibGUgYmVsb3cgc2hvd3Mgb3VyIG1vZGVsJ3MgcmVncmVzc2lvbiBzdW1tYXJ5LiBXZSB3aWxsIGZvY3VzIG9uIHRoZSBFc3RpbWF0ZSBhbmQgdGhlIFByKD58enwpIGNvbHVtbnMgKGkuZSB0IHZhbHVlcykuIElmIHRoZSBlc3RpbWF0ZSBjb2x1bW4gaXMgbmVnYXRpdmUgaXQgbWVhbnMgdGhhdCBhcyB0aGUgaW5kZXBlbmRlbnQgdmFyaWFibGUgZGVjcmVhc2VzIHRoZSBvZGRzIG9mIGEgdGhlIGFyZWEgYmVpbmcgaW51bmRhdGVkIGluY3JlYXNlLiBGb3IgZXhhbXBsZSwgYXMgZWxldmF0aW9uLCBhbmQgZGlzdGFuY2UgdG8gd2F0ZXIgZGVjcmVhc2UgdGhlIG9kZHMgb2YgYW4gYXJlYSBiZWluZyBmbG9vZGVkIGluY3JlYXNlLiBPbiB0aGUgb3RoZXIgaGFuZCwgYXMgdGhlIGxvZ2dlZCBmbG93IGFjY3VtdWxhdGlvbiBhbmQgc2xvcGUgaW5jcmVhc2UgdGhlIG9kZHMgb2YgYW4gYXJlYSBiZWluZyBmbG9vZGVkIGluY3JlYXNlcy4gVGhlIGxvdyB0IHZhbHVlcyBpbmRpY2F0ZSB0aGF0IGFsbCBvdXIgcHJlZGljdG9ycyBhcmUgc3RhdGlzdGljYWxseSBzaWduaWZpY2FudCBwcmVkaWN0b3JzIG9mIGZsb29kaW5nIGV4Y2VwdCBmb3IgbGFuZGNvdmVyIGNsYXNzIDEwMCBhbmQgbGFuZCBjb3ZlciBjbGFzcyA5MCB2YXJpYWJsZXMuIExhbmQgY292ZXIgY2xhc3MgMTAwIGlzIE1vc3MgYW5kIGxpY2hlbiB3aGlsZSBsYW5kIGNvdmVyIGNsYXNzIDkwIGlzIEhlcmJhY2VvdXMgV2V0bGFuZC4NCg0KYGBge3IgZmlyc3RNb2RlbCwgd2FyaW5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFfQ0KZmxvb2RNb2RlbCA8LSBnbG0oaW5faWQgfiAuLCANCiAgICAgICAgICAgICAgICAgICAgZmFtaWx5PSJiaW5vbWlhbCIobGluaz0ibG9naXQiKSwgZGF0YSA9IGZsb29kVHJhaW4gJT4lDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhcy5kYXRhLmZyYW1lKCkgJT4lDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZWxlY3QoLWdlb21ldHJ5LCAtSUQsIC1mYV9tYXgpKQ0KDQpzdW1tYXJ5KGZsb29kTW9kZWwpJGNvZWZmaWNpZW50cyAlPiUNCiAga2FibGUoKSAlPiUNCiAga2FibGVfbWluaW1hbCgpDQoNCmBgYA0KDQojIyBNb2RlbCB2YWxpZGF0aW9uDQoNCldlIHZhbGlkYXRlIHRoZSBtb2RlbCBhbmQgY2hlY2sgdGhlIGFjY3VyYWN5IHVzaW5nIGEgdmFyaWV0eSBvZiBkaWZmZXJlbnQgbWV0cmljcy4gVGhlIGhpc3RvZ3JhbSBiZWxvdyBzaG93cyB0aGUgcHJlZGljdGVkIHByb2JhYmlsaXRpZXMgb2YgYSBmbG9vZCBvY2N1cnJpbmcgZm9yIHRoZSBncmlkIHNxdWFyZXMgaW4gb3VyIHRlc3QgZGF0YXNldC4gQXMgc2hvd24sIHRoZSBwcmVkaWN0ZWQgcHJvYmFiaWxpdHkgZm9yIHRoZSBtYWpvcml0eSBvZiBncmlkIHNxdWFyZXMgaXMgbG93LiANCg0KYGBge3IgcHJlZGljdF9maXJzdH0NCmNsYXNzUHJvYnMgPC0gcHJlZGljdChmbG9vZE1vZGVsLCBmbG9vZFRlc3QsIHR5cGU9InJlc3BvbnNlIikgJT4lIGRhdGEuZnJhbWUgJT4lIHNldE5hbWVzKC4sYygncHJlZCcpKQ0KDQpnZ3Bsb3QoZGF0YT1jbGFzc1Byb2JzLGFlcyh4PXByZWQpKSsNCiAgZ2VvbV9oaXN0b2dyYW0oYmlucz0xMDAsZmlsbD0nb3JhbmdlJyxjb2xvcj0nZ3JleTkwJykrDQogIHRoZW1lX2J3KCkrDQogIHlsYWIoJ0NvdW50JykrDQogIHhsYWIoJ1Byb2JhYmlsaXR5JykNCg0KDQpgYGANCg0KVGhlIGNoYXJ0cyBiZWxvdyBzaG93IHRoZSBwcmVkaWN0ZWQgcHJvYmFiaWxpdHkgZGVuc2l0eSBmb3IgZ3JpZCBzcXVhcmVzIGluIHRoZSB0ZXN0IGRhdGFzZXQgdGhhdCBmbG9vZGVkIGFuZCBkaWQgbm90IGZsb29kLiBHcmlkIHNxdWFyZXMgdGhhdCBkaWQgbm90IGZsb29kICgwKSBoYXZlIGEgdmVyeSBsb3cgcHJlZGljYXRlZCBwcm9iYWJpbGl0eSBvZiBmbG9vZGluZywgdGhlIHByZWRpY3RlZCBwcm9iYWJpbGl0eSBmb3IgZmxvb2RpbmcgaXMgYmVsb3cgMC4wNSBmb3IgYWxtb3N0IGFsbCBncmlkIHNxdWFyZXMuIEZvciBncmlkIHNxdWFyZXMgaW4gdGhlIHRlc3QgZGF0YXNldCB0aGF0IGZsb29kZWQgdGhlIHByZWRpY3RlZCBwcm9iYWJpbGl0eSByYW5nZXMgc2lnbmlmaWNhbnRseSwgYnV0IHBlYWtzIGFyb3VuZCAwLjEyLiBUaGUgcHJlZGljdGVkIHByb2JhYmlsaXR5IG9mIGZsb29kaW5nIGlzIGJlbG93IDAuNSBmb3IgdGhlIG1ham9yaXR5IG9mIGZsb29kZWQgZ3JpZCBzcXVhcmVzLCBpbmRpY2F0aW5nIHRoYXQgYSAwLjUgdGhyZXNob2xkIG1heSBub3QgYmUgb3B0aW1hbC4gDQoNCmBgYHtyIHBsb3RfcHJlZHN9DQp0ZXN0UHJvYnMgPC0gZGF0YS5mcmFtZShvYnMgPSBhcy5mYWN0b3IoZmxvb2RUZXN0JGluX2lkKSwNCiAgICAgICAgICAgICAgICAgICAgICAgIHByZWQgPSBjbGFzc1Byb2JzKQ0KDQpnZ3Bsb3QodGVzdFByb2JzLCBhZXMoeCA9IHByZWQsIGZpbGw9YXMuZmFjdG9yKG9icykpKSArIA0KICBnZW9tX2RlbnNpdHkoKSArDQogIGZhY2V0X2dyaWQob2JzIH4gLixzY2FsZXMgPSAnZnJlZScpICsgDQogIHhsYWIoIlByb2JhYmlsaXR5IikgKw0KICB5bGFiKCJGcmVxdWVuY3kiKSsNCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gLjUpICsNCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gYygiZGFyayBibHVlIiwgImRhcmsgZ3JlZW4iKSwNCiAgICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBjKCJOb3QgZmxvb2RlZCIsImZsb29kZWQiKSwNCiAgICAgICAgICAgICAgICAgICAgICBuYW1lID0gIiIpKw0KICB0aGVtZV9idygpDQpgYGANCiAgIA0KVGhlIGNoYXJ0IGJlbG93IHNob3dzIHRoZSByZWNlaXZlciBvcGVyYXRpbmcgY2hhcmFjdGVyaXN0aWMgY3VydmUgKFJPQykgZm9yIG91ciBtb2RlbC4gVGhlIFJPQyBjdXJ2ZSBwbG90cyB0aGUgZmFsc2UgcG9zaXRpdmUgZnJhY3Rpb24gKGkuZTogcGVyY2VudCBvZiBmbG9vZGVkIGdyaWQgc3F1YXJlcyBpbmNvcnJlY3RseSBwcmVkaWN0ZWQpIGFuZCBUcnVlIHBvc2l0aXZlIGZyYWN0aW9uIChpLmU6IHBlcmNlbnQgb2YgZmxvb2RlZCBncmlkIHNxdWFyZXMgY29ycmVjdGx5IHByZWRpY3RlZCkgYXQgZmlmdHkgZGlmZmVyZW50IHRocmVzaG9sZHMuIA0KDQpUaGUgUk9DIGN1cnZlcyBoZWxwIHNob3cgdGhlIHRyYWRlIG9mZnMgaW4gYSBsb2dpc3RpYyByZWdyZXNzaW9uIGFuYWx5c2lzLiBJZiB3ZSBzZWxlY3QgYSB2ZXJ5IGxvdyB0aHJlc2hvbGRzLCB0aGUgbW9kZWwgd2lsbCBjb3JyZWN0bHkgcHJlZGljdCBtb3JlIGZsb29kZWQgZ3JpZCBzcXVhcmVzIChpLmU6IHRoZSB0cnVlIHBvc2l0aXZlIGZyYWN0aW9uIHdpbGwgYmUgaGlnaGVyKS4gSG93ZXZlciwgd2hlbiBhIGxvdyB0aHJlc2hvbGQgaXMgdXNlZCB0aGUgbW9kZWwgd2lsbCBhbHNvIGxpa2VseSBpbmNvcnJlY3RseSBwcmVkaWN0IG1hbnkgZmxvb2RlZCBncmlkIHNxdWFyZXMgYXMgbm90IGJlaW5nIGZsb29kZWQgKGkuZTogdGhlIGZhbHNlIHBvc2l0aXZlIHJhdGUgd2lsbCBhbHNvIGJlIGhpZ2gpLg0KDQpgYGB7ciByb2NfY3VydmUsIG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFfQ0KDQphdWMgPC0gcm91bmQoYXVjKHRlc3RQcm9icyRvYnMsIHRlc3RQcm9icyRwcmVkKSwyKQ0KDQpnZ3Bsb3QodGVzdFByb2JzLCBhZXMoZCA9IGFzLm51bWVyaWMob2JzKSwgbSA9IHByZWQpKSArIA0KICBnZW9tX3JvYyhuLmN1dHMgPSA1MCwgbGFiZWxzID0gRkFMU0UsY29sb3I9J2JsdWUnKSArIA0KICBhbm5vdGF0ZSgidGV4dCIsIHggPSAwLjEsIHkgPSAxLCBsYWJlbD1wYXN0ZSgiQVVDOiAiLGFzLmNoYXJhY3RlcihhdWMpKSxjb2xvcj0nYmx1ZScpKw0KICBzdHlsZV9yb2ModGhlbWUgPSB0aGVtZV9ncmV5KSArDQogIGdlb21fYWJsaW5lKHNsb3BlID0gMSwgaW50ZXJjZXB0ID0gMCwgc2l6ZSA9IDEuNSwgY29sb3IgPSAnZ3JleScpKw0KICB0aGVtZV9idygpDQpgYGANCg0KQmFzZWQgb24gdGhlIFJPQyBjdXJ2ZSBhbmQgdGhlIGRlbnNpdHkgZ3JhcGggYWJvdmUgd2UgZGV0ZXJtaW5lZCB0aGF0IHRoZSBvcHRpbWFsIHRocmVzaG9sZCBpcyAwLjEyLiBHcmlkIHNxdWFyZXMgdGhhdCB0aGUgbW9kZWwgcHJlZGljdHMgYXMgaGF2aW5nIGEgZmxvb2QgcHJvYmFiaWxpdHkgaGlnaGVyIHRoYW4gMC4xMiBhcmUgY2xhc3NpZmllZCBhcyBmbG9vZGVkIGJ5IG91ciBtb2RlbC4gR3JpZCBzcXVhcmVzIHRoYXQgaGF2ZSBhIGZsb29kIHByb2JhYmlsaXR5IGxvd2VyIHRoYW4gMC4xMiBhcmUgY2xhc3NpZmllZCBhcyBsaWtlbHkgbm90IHRvIGZsb29kLiBBdCB0aGUgMC4xMiB0aHJlc2hvbGQsIHRoZSBtb2RlbCBjb3JyZWN0bHkgcHJlZGljdHMgODMlIG9mIGZsb29kZWQgZ3JpZCBzcXVhcmVzIChTZW5zaXRpdml0eSkgYW5kIDg4JSBvZiBub24gZmxvb2RlZCBncmlkIHNxdWFyZXMgKFNwZWNpZmljaXR5KS4gICAgIA0KDQpgYGB7ciBjb25mdXNpb25fbWF0cml4LCBtZXNzYWdlID0gRkFMU0UsIHdhcm5pbmcgPSBGQUxTRX0NCnRlc3RQcm9icyRwcmVkQ2xhc3MgPC0gaWZlbHNlKHRlc3RQcm9icyRwcmVkID4gLjEyICwxLDApDQoNCm1hdHJpeCA8LSBjYXJldDo6Y29uZnVzaW9uTWF0cml4KHJlZmVyZW5jZSA9IGFzLmZhY3Rvcih0ZXN0UHJvYnMkb2JzKSwgDQogICAgICAgICAgICAgICAgICAgICAgIGRhdGEgPSBhcy5mYWN0b3IodGVzdFByb2JzJHByZWRDbGFzcyksIA0KICAgICAgICAgICAgICAgICAgICAgICBwb3NpdGl2ZSA9ICIxIikNCg0KbWF0cml4JHRhYmxlICU+JSAgIA0KICBkYXRhLmZyYW1lKCkgJT4lDQogIHNwcmVhZChrZXk9UmVmZXJlbmNlLHZhbHVlPUZyZXEpICU+JQ0KICByZW5hbWUoJ05vX0Zsb29kJyA9ICcwJywgJ0Zsb29kJyA9ICcxJykgJT4lDQogIG11dGF0ZShUb3RhbCA9IE5vX0Zsb29kICsgRmxvb2QsDQogICAgICAgICBQcmVkaWN0aW9uID0gYygnTm9fRmxvb2QnLCdGbG9vZCcpKSAlPiUNCiAga2JsKCkgJT4lDQogIGFkZF9oZWFkZXJfYWJvdmUoaGVhZGVyPWMoIiAiID0gMSwiQWN0dWFsIiA9IDIsIiAiID0gMSkpICU+JQ0KICBrYWJsZV9taW5pbWFsKCkgJT4lDQogIGthYmxlX3N0eWxpbmcoZnVsbF93aWR0aCA9IEYpDQoNCm1hdHJpeCRieUNsYXNzICU+JQ0KICBkYXRhLmZyYW1lKCkgJT4lDQogIGhlYWQoMikgJT4lDQogIHJlbmFtZSguLFZhbHVlID0gLikgJT4lDQogIG11dGF0ZShWYWx1ZSA9IHJvdW5kKFZhbHVlKjEwMCwyKSkgJT4lDQogIGtibChjb2wubmFtZXMgPSBjKCdBY2N1cmFjeSBNZXRyaWMnLCdWYWx1ZScpKSAlPiUNCiAga2FibGVfbWluaW1hbCgpICU+JQ0KICBrYWJsZV9zdHlsaW5nKGZ1bGxfd2lkdGggPSBGKQ0KYGBgDQoNCg0KIyMgTW9kZWwgQ3Jvc3MgdmFsaWRhdGlvbg0KDQpXZSBhbHNvIHJ1biBhIGNyb3NzIHZhbGlkYXRpb24gZm9yIG91ciBtb2RlbC4gVGhlIGNyb3NzIHZhbGlkYXRpb24gaGVscHMgdXMgdW5kZXJzdGFuZCBpZiB0aGUgbW9kZWwgaXMgZ2VuZXJhbGl6YWJsZSB0byBkaWZmZXJlbnQgZGF0YS4gQSBjcm9zcyB2YWxpZGF0aW9uIGludm9sdmVzIHJ1bm5pbmcgdGhlIG1vZGVsIG11bHRpcGxlIHRpbWVzIHdoaWxlIGNoYW5naW5nIHRoZSBkYXRhIHBvaW50cyB3aGljaCBhcmUgaW5jbHVkZWQgaW4gdGhlIHRyYWluaW5nIGFuZCB0ZXN0IGRhdGFzZXRzLiBXZSBydW4gYSAxMDAtZm9sZCBjcm9zcyB2YWxpZGF0aW9uIHVzaW5nIHRoZSBDYWxnYXJ5IGRhdGEgYW5kIGNvbXBhcmUgdGhlIHByZWRpY3RlZCBvdXRjb21lcyB0byB0aGUgYWN0dWFsIG91dGNvbWUgaW4gZWFjaCBvZiB0aGUgMTAwIHRlc3QgZGF0YXNldHMgYW5kIGNhbGN1bGF0ZSB0aGUgYWNjdXJhY3kgZm9yIGVhY2ggZm9sZC4gDQoNClRoZSBoaXN0b2dyYW0gYmVsb3cgc2hvd3MgYSBoaXN0b2dyYW0gb2YgdGhlIGFjY3VyYWN5IGZvciBhbGwgZmlmdHkgZm9sZHMuIEFzIHNlZW4gaW4gdGhlIGhpc3RvZ3JhbSBiZWxvdyB0aGUgbW9kZWwgYWNjdXJhY3kgaXMgY29uc2lzdGVudCBhY3Jvc3MgZm9sZHMsIGluZGljYXRpbmcgdGhhdCB0aGUgbW9kZWwgaXMgZ2VuZXJhbGl6YWJsZS4NCg0KYGBge3Iga19mb2xkLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRX0NCg0KY3RybCA8LSB0cmFpbkNvbnRyb2wobWV0aG9kID0gImN2IiwgDQogICAgICAgICAgICAgICAgICAgICBudW1iZXIgPSAxMDAsIA0KICAgICAgICAgICAgICAgICAgICAgcCA9IDAuNywgDQogICAgICAgICAgICAgICAgICAgICBzYXZlUHJlZGljdGlvbnMgPSBUUlVFKQ0KDQpjdkZpdCA8LSB0cmFpbihhcy5mYWN0b3IoaW5faWQpIH4gLiwgIGRhdGEgPSBncmlkNyAlPiUgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhcy5kYXRhLmZyYW1lKCkgJT4lDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZWxlY3QoLWdlb21ldHJ5LCAtSUQsLWZhX21heCksIA0KICAgICAgICAgICAgICAgbWV0aG9kPSJnbG0iLCBmYW1pbHk9ImJpbm9taWFsIiwNCiAgICAgICAgICAgICAgIHRyQ29udHJvbCA9IGN0cmwpDQoNCmdncGxvdChhcy5kYXRhLmZyYW1lKGN2Rml0JHJlc2FtcGxlKSwgYWVzKEFjY3VyYWN5KSkgKyANCiAgZ2VvbV9oaXN0b2dyYW0oZmlsbD0nb3JhbmdlJyxjb2xvcj0nZ3JleTkwJyxiaW5zPTEwMCkgKw0KICBzY2FsZV94X2NvbnRpbnVvdXMobGltaXRzID0gYygwLCAxKSkgKw0KICBsYWJzKHg9IkFjY3VyYWN5IiwNCiAgICAgICB5PSJDb3VudCIpKw0KICB0aGVtZV9idygpDQoNCmBgYA0KDQoNCmBgYHtyfQ0KDQpmbG9vZHRlc3QxIDwtIGNiaW5kKGZsb29kVGVzdCx0ZXN0UHJvYnMpICU+JQ0KICBtdXRhdGUodHlwZSA9IGNhc2Vfd2hlbihvYnMgPT0gMCAmIHByZWRDbGFzcyA9PSAwIH4gJ1ROJywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgb2JzID09IDEgJiBwcmVkQ2xhc3MgPT0gMSB+ICdUUCcsDQogICAgICAgICAgICAgICAgICAgICAgICAgIG9icyA9PSAxICYgcHJlZENsYXNzID09IDAgfiAnRk4nLA0KICAgICAgICAgICAgICAgICAgICAgICAgICBvYnMgPT0gMCAmIHByZWRDbGFzcyA9PSAxIH4gJ0ZQJykpDQoNCmdyaWQ3IDwtIGdyaWQ3ICU+JSANCiAgbXV0YXRlKHByZWRpY3QgPSBwcmVkaWN0KGZsb29kTW9kZWwsIGdyaWQ3LCB0eXBlPSJyZXNwb25zZSIpLA0KICAgICAgICAgb3V0Y29tZSA9IGFzLmZhY3RvcihpZmVsc2UocHJlZGljdCA+IDAuMTIsMSwwKSkpDQogICAgICAgICANCg0KYGBgDQoNClRoZSBtYXBzIGJlbG93IHNob3cgdGhlIHJlc3VsdHMgZm9yIHByZWRpY3Rpb25zIGluIHRoZSBDYWxnYXJ5IFRlc3QgU2V0IGFuZCBQcmVkaWN0aW9ucyBmb3IgYWxsIG9mIENhbGdhcnkuIFRoZSBwcmVkaWN0aW9ucyBmb3IgdGhlIHRlc3Qgc2V0IHNob3cgdGhlIGxvY2F0aW9uIG9mIGZhbHNlIG5lZ2F0aXZlcywgZmFsc2UgcG9zaXRpdmVzLCB0cnVlIG5lZ2F0aXZlcywgYW5kIHRydWUgcG9zaXRpdmVzLiBBcyBzaG93biwgdGhlIG1ham9yaXR5IG9mIHRydWUgcG9zaXRpdmVzIGFyZSBsb2NhdGVkIG5lYXIgdGhlIHJpdmVyLiBIb3dldmVyLCB0aGVyZSBhcmUgYWxzbyBhIGxhcmdlIG51bWJlciBvZiBmYWxzZSBwb3NpdGl2ZXMgbG9jYXRlZCBhbG9uZyB0aGUgcml2ZXIgaW4gdGhlIE5vcnRoZXJuIGFyZWFzIG9mIHRoZSBjaXR5IGFuZCB0aGVyZSBpcyBhbHNvIGEgbGFyZ2UgY2x1c3RlciBvZiBmYWxzZSBwb3NpdGl2ZXMgaW4gdGhlIGVhc3Rlcm4gYXJlYSBvZiB0aGUgY2l0eS4gDQoNCmBgYHtyfQ0KDQpncmlkLmFycmFuZ2UobnJvdz0xLA0KDQpnZ3Bsb3QoKSsNCiAgZ2VvbV9zZihkYXRhPWZsb29kdGVzdDEsYWVzKGZpbGw9dHlwZSksY29sb3I9J3RyYW5zcGFyZW50JykrDQogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcz1jKCdncmV5NTAnLCdibHVlJywnbGlnaHRncmVlbicsJ3B1cnBsZScpLG5hbWU9JycsbGFiZWxzPWMoJ0ZhbHNlIE5lZ2F0aXZlJywnRmFsc2UgUG9zaXRpdmUnLCdUcnVlIE5lZ2F0aXZlJywnVHJ1ZSBQb3NpdGl2ZScpKSsNCiAgdGhlbWVfdm9pZCgpKw0KICBnZ3RpdGxlKCdSZXN1bHRzIGZvciBDYWxnYXJ5IFRlc3QgU2V0JyksDQoNCmdncGxvdCgpKw0KICBnZW9tX3NmKGRhdGE9Z3JpZDcsYWVzKGZpbGw9b3V0Y29tZSksY29sb3I9J3RyYW5zcGFyZW50JykrDQogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcz1jKCcjZmM5YzljJywnbGlnaHRibHVlJyksbmFtZT0nJyxsYWJlbHM9YygnTm8gRmxvb2QnLCdGbG9vZCcpKSsNCiAgdGhlbWVfdm9pZCgpKw0KICBnZ3RpdGxlKCdQcmVkaWN0aW9ucyBmb3IgQ2FsZ2FyeScpDQopDQoNCmBgYA0KDQojIERlbnZlciBGZWF0dXJlIEVuZ2luZWVyaW5nDQoNCk5leHQgd2UgYnVpbGQgYSBmaXNobmV0IGZvciB0aGUgY2l0eSBvZiBEZW52ZXIuIFRoZSBmaXNobmV0IGluY2x1ZGVzIHRoZSBzYW1lIHZhcmlhYmxlcyBhcyB0aGUgQ2FsZ2FyeSBmaXNobmV0IGFuZCBpcyBhbHNvIHRoZSBzYW1lIHNpemUgYXMgdGhlIENhbGdhcnkgZmlzaG5ldC4gDQoNCiMjIERlbnZlciBFbGV2YXRpb24gYW5kIFNsb3BlDQoNCmBgYHtyIGRlbnZlcl9zbG9wZSwgbWVzc2FnZT1GQUxTRSwgcmVzdWx0cz0naGlkZScsIHdhcm5pbmc9RkFMU0V9DQoNCmRlbV8xIDwtIHJhc3QoIkRhdGEvZGVudmVyL24zOV93MTA1XzFhcmNfdjMudGlmIikNCg0KZGVtXzIgPC0gcmFzdCgiRGF0YS9kZW52ZXIvbjM5X3cxMDZfMWFyY192MzIudGlmIikNCg0KbWVyZ19kZW0gPC0gdGVycmE6Om1lcmdlKGRlbV8xLGRlbV8yKQ0KDQptZXJnX2RlbSA8LSBwcm9qZWN0KG1lcmdfZGVtLCAiRVBTRzo2NDI3IikNCg0KDQpkZW52ZXJfYm91bmQgPC0NCiAgc3RfcmVhZCgiRGF0YS9kZW52ZXIvY291bnR5X2JvdW5kYXJ5IikgJT4lDQogIHN0X3RyYW5zZm9ybShjcnMgPSAiRVBTRzo2NDI3IikNCiANCg0KZGVtX2Nyb3AgPC0gY3JvcChtZXJnX2RlbSwgZGVudmVyX2JvdW5kKQ0KIyAnbWFzaycgb3V0IHRoZSB2YWx1ZXMgb3V0c2lkZSBkZW52ZXINCmRlbnZlcl9kZW0gPC0gbWFzayhkZW1fY3JvcCwgZGVudmVyX2JvdW5kKQ0KDQoNCmRlbnZlcl9zbG9wZSA8LSB0ZXJyYWluKGRlbnZlcl9kZW0sIHY9InNsb3BlIiwgbmVpZ2hib3JzPTgsIHVuaXQ9ImRlZ3JlZXMiKSAgDQpgYGANCg0KIyMgRGVudmVyIEdyaWQNCg0KTWFrZSBncmlkIGZvciBEZW52ZXIuDQoNCmBgYHtyIGdyaWRfZGVuLCB3YXJuaW5nPUZBTFNFfQ0KDQpncmlkX2RlbiA8LSBzdF9tYWtlX2dyaWQoZGVudmVyX2JvdW5kLDEwMCxzcXVhcmU9VFJVRSkgJT4lDQogIHN0X3NmKCkgJT4lDQogIG11dGF0ZShJRCA9IHJvd19udW1iZXIoKSkNCg0KIyBHZXQganVzdCBncmlkIHNxdWFyZXMgd2l0aCBjZW50cm9pZCBpbiBDYWxnYXJ5IGNpdHkgbGltaXRzDQoNCmdyaWRfZGVuIDwtIGdyaWRfZGVuICU+JQ0KICBzdF9jZW50cm9pZCgpICU+JQ0KICBzdF9qb2luKC4sZGVudmVyX2JvdW5kLGpvaW4gPSBzdF9pbnRlcnNlY3RzLGxlZnQ9RkFMU0UpICU+JQ0KICBzdF9kcm9wX2dlb21ldHJ5KCkgJT4lDQogIGxlZnRfam9pbiguLGdyaWRfZGVuLGJ5PSdJRCcpICU+JQ0KICBzdF9hc19zZigpICU+JQ0KICBtdXRhdGUoSUQgPSByb3dfbnVtYmVyKCkpICU+JQ0KICBzZWxlY3QoSUQpDQoNCmBgYA0KDQojIyBFbGV2YXRpb24gWm9uYWwgU3RhdHMNCg0KRXh0cmFjdCBlbGV2YXRpb24gZGF0YSBmb3IgZWFjaCBncmlkIHNxdWFyZXMsIGNhbGN1bGF0ZSBtZWFuIGVsZXZhdGlvbiBmb3IgZWFjaC4NCg0KYGBge3J9DQoNCmVsZXZhdGlvbl9leHRyYWN0IDwtIHRlcnJhOjpleHRyYWN0KG1lcmdfZGVtLGdyaWRfZGVuLCBmdW49J21lYW4nLCBuYS5ybT1UUlVFKSAlPiUNCiAgcmVuYW1lKGVsZXZhdGlvbl9tZWFuID0gbjM5X3cxMDVfMWFyY192MykgJT4lDQogIG11dGF0ZShlbGV2YXRpb24gPSBlbGV2YXRpb25fbWVhbiAtIG1pbihlbGV2YXRpb25fbWVhbikpDQoNCmdyaWQzX2RlbiA8LSBjYmluZChncmlkX2RlbixlbGV2YXRpb25fZXh0cmFjdCAlPiUgc2VsZWN0KGVsZXZhdGlvbikpDQoNCmBgYA0KDQojIyBTbG9wZSBab25hbCBTdGF0cw0KDQpFeHRyYWN0IHNsb3BlIGRhdGEgZm9yIGVhY2ggZ3JpZCBzcXVhcmVzLCBjYWxjdWxhdGUgbWVhbiBzbG9wZSBmb3IgZWFjaC4gDQoNCmBgYHtyfQ0KDQoNCmRlbl9zbG9wZV9leHRyYWN0IDwtIHRlcnJhOjpleHRyYWN0KGRlbnZlcl9zbG9wZSxncmlkX2RlbiwgZnVuPSdtYXgnLCBuYS5ybT1UUlVFKSAlPiUNCiAgcmVuYW1lKHNsb3BlX21heCA9IHNsb3BlKSANCg0KZ3JpZDNfZGVuIDwtIGNiaW5kKGdyaWQzX2RlbixkZW5fc2xvcGVfZXh0cmFjdCAlPiUgc2VsZWN0KHNsb3BlX21heCkpDQoNCmBgYA0KDQojIyBMYW5kIENvdmVyIFpvbmFsIFN0YXRzDQoNClN1bW1hcml6ZSBsYW5kIGNvdmVyIGRhdGEgYnkgZ3JpZCBzcXVhcmUgYW5kIGZpZ3VyZSBvdXQgdGhlIG1vZGUgbGFuZCBjb3ZlciBjbGFzcyBmb3IgZWFjaCBncmlkIHNxdWFyZS4NCg0KYGBge3IgZGVuX2xhbmRfY292ZXIsIHdhcm5pbmc9RkFMU0UsIHJlc3VsdHM9J2hpZGUnfQ0KI05lZWQgZm91ciBsYW5kIGNvdmVyIHRpbGVzIHRvIGNvdmVyIGFsbCBvZiBDYWxnYXJ5DQpsYW5kX2NvdmVyMSA8LSByYXN0KCdEYXRhL2RlbnZlci9sYW5kY292ZXIvRVNBX1dvcmxkQ292ZXJfMTBtXzIwMjBfdjEwMF9OMzlXMTA1X01hcC50aWYnKQ0KbGFuZF9jb3ZlcjIgPC0gcmFzdCgnRGF0YS9kZW52ZXIvbGFuZGNvdmVyL0VTQV9Xb3JsZENvdmVyXzEwbV8yMDIwX3YxMDBfTjM5VzEwOF9NYXAudGlmJykNCg0KZGVudmVyX2xhbmRfY292ZXIgPC0gdGVycmE6Om1lcmdlKGxhbmRfY292ZXIxLGxhbmRfY292ZXIyKSAjTWVyZ2UgdG9nZXRoZXIgYWxsIGxhbmQgY292ZXIgcmFzdGVycyB0aGF0IG92ZXJsYXAgQ2FsZ2FyeQ0KDQpkZW52ZXJfbGFuZF9jb3ZlcjIgPC0gY3JvcChkZW52ZXJfbGFuZF9jb3ZlciAsIGRlbnZlcl9ib3VuZCAlPiUgc3RfdHJhbnNmb3JtKCdFUFNHOjQzMjYnKSkgI0Nyb3AgUmFzdGVyIHRvIENhbGdhcnkNCg0KZ3JpZDRfZGVuIDwtIGdyaWQzX2RlbiAlPiUgc3RfdHJhbnNmb3JtKCdFUFNHOjQzMjYnKSAjUHJvamVjdCBncmlkIGludG8gV0dTMTk4NCBzbyB0aGF0IEkgZG8gbm90IGhhdmUgdG8gcHJvamVjdCB0aGUgcmFzdGVyDQoNCm1vZGUgPC0gZnVuY3Rpb24oY2xhc3Mpew0KICB3aGljaC5tYXgodGFidWxhdGUoY2xhc3MpKQ0KfQ0KDQojIEV4dHJhY3QgcmFzdGVyIHZhbHVlcyB3aXRoaW4gZWFjaCBwb2x5Z29uIGFuZCBkZXRlcm1pbmUgdGhlIG1vc3QgY29tbW9uIHJhc3RlciB2YWx1ZQ0KbGFuZF9jb3Zlcl92YWx1ZXMgPC0gZXh0cmFjdChkZW52ZXJfbGFuZF9jb3ZlcjIsIGdyaWQ0X2RlbikgJT4lDQogIHJlbmFtZShsYW5kY292ZXIgPSBFU0FfV29ybGRDb3Zlcl8xMG1fMjAyMF92MTAwX04zOVcxMDVfTWFwKSAlPiUNCiAgZ3JvdXBfYnkoSUQpICU+JSBzdW1tYXJpc2UobGFuZGNvdmVyX21vZGUgPSBtb2RlKGxhbmRjb3ZlcikpICU+JQ0KICB1bmdyb3VwKCkgJT4lDQogIHNlbGVjdChsYW5kY292ZXJfbW9kZSkNCg0KZ3JpZDVfZGVuIDwtIGNiaW5kKGdyaWQ0X2RlbixsYW5kX2NvdmVyX3ZhbHVlcykgJT4lIA0KICBzdF90cmFuc2Zvcm0oJ0VQU0c6NjQyNycpICU+JQ0KICBtdXRhdGUobGFuZGNvdmVyX21vZGUgPSBhcy5mYWN0b3IobGFuZGNvdmVyX21vZGUpKQ0KDQoNCmBgYA0KDQojIyBEaXN0YW5jZSB0byBXYXRlcg0KDQpDYWxjdWxhdGUgZGlzdGFuY2UgdG8gd2F0ZXIgZm9yIGVhY2ggZ3JpZCBzcXVhcmUuIFRoZSBEZW52ZXIgZGF0YSBpbmNsdWRlcyBzZXBlcmF0ZSBzdHJlYW1zIGFuZCBsYWtlcyBkYXRhLCB3ZSBjYWxjdWxhdGVkIHRoZSBkaXN0YW5jZSBmcm9tIGVhY2ggZ3JpZCBzcXVhcmUgdG8gdGhlIG5lYXJlc3QgbGFrZSBhbmQgdGhlIG5lYXJlc3Qgc3RyZWFtIGFuZCB0aGVuIHRvb2sgdGhlIGRpc3RhbmNlIHdoaWNoIHdhcyBsb3dlc3QgYXMgdGhlIGRpc3RhbmNlIHRvIHdhdGVyLiANCg0KYGBge3IgZGVuX3dhdGVyLCB3YXJuaW5nPUZBTFNFLCByZXN1bHRzPSdoaWRlJ30NCg0KbGFrZXNfcG9uZHMgPC0gc3RfcmVhZCgiRGF0YS9kZW52ZXIvbGFrZXNfYW5kX3BvbmRzLyIpICU+JQ0KICBzdF90cmFuc2Zvcm0oIkVQU0c6NjQyNyIpIA0Kc3RyZWFtcyA8LSBzdF9yZWFkKCJEYXRhL2RlbnZlci9zdHJlYW1zLyIpICU+JQ0KICBzdF90cmFuc2Zvcm0oIkVQU0c6NjQyNyIpIA0KDQoNCmNlbnRyb2lkIDwtIGdyaWQ1X2RlbiAlPiUNCiAgc3RfY2VudHJvaWQoKQ0KDQpuZWFyZXN0X2ZlYXQgPC0gc3RfbmVhcmVzdF9mZWF0dXJlKGNlbnRyb2lkLGxha2VzX3BvbmRzKQ0KDQpncmlkNV9kZW4kbGFrZXNfcG9uZHNfZGlzdCA8LSBhcy5kb3VibGUoc3RfZGlzdGFuY2UoY2VudHJvaWQsIGxha2VzX3BvbmRzW25lYXJlc3RfZmVhdCxdLCBieV9lbGVtZW50PVRSVUUpKQ0KDQpuZWFyZXN0X2ZlYXRfc3RyZWFtcyA8LSBzdF9uZWFyZXN0X2ZlYXR1cmUoY2VudHJvaWQsc3RyZWFtcykNCg0KZ3JpZDVfZGVuJHN0cmVhbXNfZGlzdCA8LSBhcy5kb3VibGUoc3RfZGlzdGFuY2UoY2VudHJvaWQsIHN0cmVhbXNbbmVhcmVzdF9mZWF0X3N0cmVhbXMsXSwgYnlfZWxlbWVudD1UUlVFKSkNCg0KZ3JpZDVfZGVuJHdhdGVyX2Rpc3QgPC0gcG1pbihncmlkNV9kZW4kbGFrZXNfcG9uZHNfZGlzdCwgZ3JpZDVfZGVuJHN0cmVhbXNfZGlzdCkNCg0KZ3JpZDVfZGVuIDwtIGdyaWQ1X2RlbiAlPiUgc2VsZWN0KC1zdHJlYW1zX2Rpc3QsLWxha2VzX3BvbmRzX2Rpc3QpDQoNCmBgYA0KDQojIyBGbG93IEFjY3VtaWxhdGlvbg0KDQpTdW1tYXJpemUgZmxvdyBhY2N1bXVsYXRpb24gYnkgZ3JpZCBzcXVhcmUgLSB1c2UgbWF4IGZsb3cgYWNjdW11bGF0aW9uIGZvciBlYWNoIGdyaWQgc3F1YXJlLiBGbG93IGFjY3VtdWxhdGlvbiB3YXMgY2FsY3VsYXRlZCB1c2luZyBBcmNHSVMgUHJvLg0KDQpgYGB7ciBkZW1fZmF9DQoNCmRlbnZlcl9mYSA8LSByYXN0KCdEYXRhL2RlbnZlci9kZW52ZXJfZmxvd19hY2N1bWlsYXRpb24udGlmJykgDQoNCmRlbnZlcl9mYSA8LSBwcm9qZWN0KGRlbnZlcl9mYSwnRVBTRzo2NDI3JykNCg0KZmFfZXh0cmFjdCA8LSB0ZXJyYTo6ZXh0cmFjdChkZW52ZXJfZmEsZ3JpZDVfZGVuLCBmdW49J21heCcsIG5hLnJtPVRSVUUpICU+JQ0KICByZW5hbWUoZmFfbWF4ID0gZGVudmVyX2Zsb3dfYWNjdW1pbGF0aW9uKSANCg0KZ3JpZDZfZGVuIDwtIGNiaW5kKGdyaWQ1X2RlbixmYV9leHRyYWN0ICU+JSBzZWxlY3QoZmFfbWF4KSkNCg0KZ3JpZDZfZGVuJGZhX2xvZyA8LSBsb2coZ3JpZDZfZGVuJGZhX21heCArIDEpDQoNCmBgYA0KDQojIyBNYWtlIFByZWRpY3Rpb25zIGZvciBEZW52ZXINCg0KYGBge3IgZGVudmVyX3ByZWRpY3Rpb259DQoNCmdyaWQ2X2RlbiA8LSBncmlkNl9kZW4gJT4lDQogIG11dGF0ZShwcmVkaWN0aW9uID0gcHJlZGljdChmbG9vZE1vZGVsLGdyaWQ2X2RlbiwgdHlwZT0icmVzcG9uc2UiKSwNCiAgb3V0Y29tZSA9IGFzLmZhY3RvcihpZmVsc2UocHJlZGljdGlvbiA+IDAuMTIsIDEsMCkpKQ0KDQpgYGANCg0KDQpgYGB7ciBmaWcud2lkdGg9MTEsZmlnLmhlaWdodD00fQ0KDQpnZ3Bsb3QoKSsNCiAgZ2VvbV9zZihkYXRhPWdyaWQ2X2RlbixhZXMoZmlsbD1vdXRjb21lKSxjb2xvcj0ndHJhbnNwYXJlbnQnKSsNCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzPWMoJyNmYzljOWMnLCdsaWdodGJsdWUnKSxuYW1lPScnLGxhYmVscz1jKCdObyBGbG9vZCcsJ0Zsb29kJykpKw0KICB0aGVtZV92b2lkKCkrDQogIGdndGl0bGUoJ1ByZWRpY3Rpb25zIGZvciBEZW52ZXInKQ0KICANCmBgYA==