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.
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==