CPLN675 Midterm: Forecasting flood inundation

To use markdown first we install the package: install.packages("rmarkdown")

Basic procedure - provided by professor

  1. Gather open data from both Calgary’s open data site and your comparable city’s open data site as well as other internet sources.
  2. Using what we’ve learned about feature engineering over the first part of the semester, build as many useful variables describing the natural, hydrological and built environment features that might help explain flood inundation. You must include at least one feature from the watershed analysis.
  3. Join these features to the vector Fishnet. Remember that ‘distance or density to Feature A’ might describe the spatial relationship better than simply, ‘Feature A’.
  4. Move your Fishnet dataset into R and run some logistic regressions with both a test set and a training set. Experiment until you find a model with enough statistically significant variables.
  5. Run goodness of fit metrics; More advanced groups (e.g. if you have taken MUSA 508) should experiment with spatial cross-validation. Visualize your results in chart and map form.

Motivation

As a result of climate change, there has been an increase in flooding across the U.S., particularly along the coasts and in low-lying areas. Warmer temperatures increase evaporation and rain availability. Warmer oceans can also cause storms along the coast in intensity, in turn causing increased flooding. Therefore, we suggest establishing a flood modeling and planning center.

A flood inundation modeling and planning center could lead to a comprehensive flood mitigation program to make urban construction more ecological and sustainable and provide timely flood warnings for post-disaster relief and reconstruction.

The center’s establishment will enable simulation of flood inundation, provide more accurate boundary information and flood prevention recommendations to planning authorities, and provide timely flood warnings. The advice and alerts will reduce economic losses and fatalities caused by floods.

This analysis could help planners understand why a local area needs a flood forecast, and how a data center could help manage flood risk. With more detailed data, the data center could provide more accurate analysis.

2. Setup

To get started, first install libraries.

library(caret)
library(pscl)
library(plotROC)
library(pROC)
library(sf)
library(tidyverse)
library(knitr)
library(kableExtra)
library(tigris)
library(viridis)
library(maptools)
library(raster)
library(rasterVis)
library(dplyr)

3.Maps

3.1. Flood Map

Set ggplot styles called mapTheme and plotTheme, also load our data

We are going to include two datasets in this step:cal_cityboundary, which is the boundary shapefile of Calgary; and flood, which is the original flood dataset.

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

plotTheme <- theme(
  plot.title =element_text(size=12),
  plot.subtitle = element_text(size=8),
  plot.caption = element_text(size = 6),
  axis.text.x = element_text(size = 10, angle = 45, hjust = 1),
  axis.text.y = element_text(size = 10),
  axis.title.y = element_text(size = 10),
  # Set the entire chart region to blank
  panel.background=element_blank(),
  plot.background=element_blank(),
  #panel.border=element_rect(colour="#F0F0F0"),
  # Format the grid
  panel.grid.major=element_line(colour="#D0D0D0",size=.75),
  axis.ticks=element_blank())

cal_b <- 
  st_read("C:/Users/jrach/Desktop/CPLN675Midterm/Resource/Cal_cityboundline/Cal_cityboundline.shp") %>%
  st_transform(crs=26912)

fishnet <- 
  st_make_grid(cal_b, cellsize = 500) %>%
  st_sf()

fishnet <- 
  fishnet[cal_b,] %>%
  mutate(uniqueID = rownames(.)) %>%
  dplyr::select(uniqueID)

flood <- st_read("C:/Users/jrach/Desktop/CPLN675Midterm/Resource/flood/cal_flood.shp") %>%
  st_transform(crs=26912)

ggplot() + 
  geom_sf(data=flood,color="orange", fill="orange") +
  geom_sf(data=cal_b) +
  labs(title="Regulatory Flood Map (Bylaw Flood Hazard) in Calgary") +
  mapTheme()

This is a flood map shown above.

3.2. variable map

Now, let’s load cal_result, which is a fishnet dataset representing four variables:slope, distance from water, drainage, and infiltration probability, and each variable is shown below:

cal_result <- 
  st_read("C:/Users/jrach/Desktop/CPLN675Midterm/result/cal_result.shp")%>%
  st_transform(crs=26912)

ggplot() + 
  geom_sf(data=cal_result,aes(fill=diwater),size=0.1) +
  scale_fill_viridis() +
  #geom_sf(data=flood,color="orange", fill="orange", alpha=0.2) +
  labs(title="distance from water body in Calgary") +
  mapTheme()

ggplot() + 
  geom_sf(data=cal_result,aes(fill=slope),size=0.05) +
  #geom_sf(data=flood,color="orange", fill="orange", alpha=0.2) +
  scale_fill_viridis() +
  labs(title="slope in Calgary") +
  mapTheme()

ggplot() + 
  geom_sf(data=cal_result,aes(fill=drain),size=0.05) +
  scale_fill_viridis() +
  #geom_sf(data=flood,color="orange", fill="orange", alpha=0.2) +
  labs(title="drainage in Calgary") +
  mapTheme()

ggplot() + 
  geom_sf(data=cal_result,aes(fill=infilp), size=0.05) +
  scale_fill_viridis() +
  #geom_sf(data=flood,color="orange", fill="orange", alpha=0.2) +
  labs(title="infiltration probability in Calgary") +
  mapTheme()

4. Modeling

4.1. Partition training and test sets

set.seed generates a random number createDataPartition randomly separates our data into two sets. We set p to p - a 70% training set and 30% test set.

overl <-
  st_intersects(cal_result$geometry,flood$geometry,sparse = FALSE) %>%
  apply(1,any)

cal_temp <-
  cal_result %>%
  mutate(is_flood=ifelse(overl==FALSE,0,1))
cal_m <- 
  cal_temp %>%
  dplyr::select(diwater,slope,is_flood,drain,infilp) %>%
  mutate(infilp = as.factor(infilp))

set.seed(42)
trainIndex <- createDataPartition(cal_m$diwater, p = .70,
                                  list = FALSE,
                                  times = 1)
floodTrain <- cal_m[ trainIndex,]

4.2. Make a binomial model

The binomial logit model runs in the glm function (generalized linear models). We specify the dependent variable as is_flood and run the model on our training set floodTrain.

floodModel <- glm( is_flood ~ ., 
                     family="binomial"(link="logit"), data = floodTrain %>%
                       as.data.frame() %>%
                       select(-geometry))
summary(floodModel)
## 
## Call:
## glm(formula = is_flood ~ ., family = binomial(link = "logit"), 
##     data = floodTrain %>% as.data.frame() %>% select(-geometry))
## 
## Deviance Residuals: 
##     Min       1Q   Median       3Q      Max  
## -4.1357  -0.6044  -0.4056  -0.1546   2.7406  
## 
## Coefficients:
##               Estimate Std. Error z value Pr(>|z|)    
## (Intercept) -1.451e+01  3.394e+02  -0.043    0.966    
## diwater     -2.323e+02  2.253e+01 -10.311  < 2e-16 ***
## slope        9.710e-02  2.494e-02   3.893 9.89e-05 ***
## drain        1.591e-05  1.923e-06   8.277  < 2e-16 ***
## infilp1      1.367e+01  3.394e+02   0.040    0.968    
## infilp2      1.304e+01  3.394e+02   0.038    0.969    
## infilp3      1.440e+01  3.394e+02   0.042    0.966    
## infilp4      1.335e+01  3.394e+02   0.039    0.969    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## (Dispersion parameter for binomial family taken to be 1)
## 
##     Null deviance: 2290.6  on 2512  degrees of freedom
## Residual deviance: 1771.9  on 2505  degrees of freedom
## AIC: 1787.9
## 
## Number of Fisher Scoring iterations: 12

4.3. Model validation

We usepredict function to create a vector of classification probabilities called classProbs and set the parameter type="reponse" which will returns probabilities that range from 0 to 1.

floodTest  <- cal_m[-trainIndex,]
classProbs <- predict(floodModel, floodTest, type="response")

hist(classProbs)

We build testProbsPlot. The vertical line represents a 0.5 probability of inundation.

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

ggplot(testProbs, aes(x = pred, fill=as.factor(obs))) + 
  geom_density() +
  facet_grid(obs ~ .) + 
  xlab("Probability") + 
  geom_vline(xintercept = .5) +
  scale_fill_manual(values = c("dark blue", "dark green"),
                    labels = c("not flood","flood"),
                    name = "")+
  plotTheme

4.3.1. Confusion metrics

We build Confusion metrics

Predicted = 0, Observed = 0 —> True Negative

Predicted = 1, Observed = 1 —> True Positive

Predicted = 1, Observed = 0 —> False Positive

Predicted = 0, Observed = 1 —> False Negative

  1. Sensitivity - the proportion of actual positives (1’s) that were predicted to be positive. Also known as “true positive rate”.

  2. Specificity - The proportion of actual negatives (0’s) that were predicted to be negatives. Also known as “true negative rate”.

testProbs$predClass  = ifelse(testProbs$pred > .5 ,1,0)

caret::confusionMatrix(reference = as.factor(testProbs$obs), 
                       data = as.factor(testProbs$predClass), 
                       positive = "1")
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction   0   1
##          0 855 152
##          1  14  54
##                                           
##                Accuracy : 0.8456          
##                  95% CI : (0.8226, 0.8667)
##     No Information Rate : 0.8084          
##     P-Value [Acc > NIR] : 0.0008601       
##                                           
##                   Kappa : 0.3305          
##                                           
##  Mcnemar's Test P-Value : < 2.2e-16       
##                                           
##             Sensitivity : 0.26214         
##             Specificity : 0.98389         
##          Pos Pred Value : 0.79412         
##          Neg Pred Value : 0.84906         
##              Prevalence : 0.19163         
##          Detection Rate : 0.05023         
##    Detection Prevalence : 0.06326         
##       Balanced Accuracy : 0.62301         
##                                           
##        'Positive' Class : 1               
## 

4.3.2. ROC Curve

Then we create an ROC (receiver operating characteristic) curve. It’s better when the black line is more curved to the y=1.00

ggplot(testProbs, aes(d = obs, m = pred)) + 
  geom_roc(n.cuts = 50, labels = FALSE) + 
  style_roc(theme = theme_grey) +
  geom_abline(slope = 1, intercept = 0, size = 1.5, color = 'grey') 

auc(testProbs$obs, testProbs$pred)
## Setting levels: control = 0, case = 1
## Setting direction: controls < cases
## Area under the curve: 0.8124

4.3.3. Cross validation

Cross-validation iteratively creates many randomly generated test sets or ‘folds’, testing the power of the model on each.

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

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

cvFit

ggplot(as.data.frame(cvFit$resample), aes(Accuracy)) + 
  geom_histogram() +
  scale_x_continuous(limits = c(0, 1)) +
  labs(x="Accuracy",
       y="Count")+
  plotTheme
## Generalized Linear Model 
## 
## 3588 samples
##    4 predictor
##    2 classes: '0', '1' 
## 
## No pre-processing
## Resampling: Cross-Validated (100 fold) 
## Summary of sample sizes: 3553, 3552, 3552, 3552, 3552, 3551, ... 
## Resampling results:
## 
##   Accuracy   Kappa    
##   0.8526246  0.2710506

In our opinion, we have a generalizable model.

4.3.4. Map predictions

Then, we predict for the entire dataset and assess our predictions.

allPredictions <- 
  predict(cvFit, cal_m, type="prob")[,2]

cal_pred <- 
  cbind(cal_m,allPredictions) %>%
  mutate(allPredictions = round(allPredictions * 100)) 
ggplot() + 
  geom_sf(data=cal_pred, aes(fill=factor(ntile(allPredictions,5))), 
          colour=NA) +
  scale_fill_manual(values = c("#edf8fb","#b3cde3","#8c96c6","#8856a7","#810f7c"),
                    labels=as.character(quantile(cal_pred$allPredictions,
                                                 c(0.1,.2,.4,.6,.8),
                                                 na.rm=T)),
                    name="Predicted\nProbabilities(%)\n(Quintile\nBreaks)") +
  mapTheme()

Observed and Predicted Flood Areas Calgary; flood area in red

ggplot() + 
  geom_sf(data=cal_pred, aes(fill=factor(ntile(allPredictions,5))), colour=NA) +
  scale_fill_manual(values = c("#edf8fb","#b3cde3","#8c96c6","#8856a7","#810f7c"),
                    labels=as.character(quantile(cal_pred$allPredictions,
                                                 c(0.1,.2,.4,.6,.8),
                                                 na.rm=T)),
                    name="Predicted\nProbabilities(%)\n(Quintile\nBreaks)") +
  geom_sf(data=cal_pred  %>% 
            filter(is_flood == 1), 
          fill="red",colour=NA, alpha=0.5) +
  mapTheme ()

cal_pred %>%
  mutate(confResult=case_when(allPredictions < 50 & is_flood==0 ~ "True_Negative",
                              allPredictions >= 50 & is_flood==1 ~ "True_Positive",
                              allPredictions < 50 & is_flood==1 ~ "False_Negative",
                              allPredictions >= 50 & is_flood==0 ~ "False_Positive")) %>%
  ggplot()+
  geom_sf(aes(fill = confResult), color = "transparent")+
  scale_fill_manual(values = c("Red","Orange","Light Blue","Light Green"),
                    name="Outcomes")+
  labs(title="Confusion Metrics") +
  mapTheme()

5. Comparable City - Denver

5.1 Predicted Probabilities

den_result <- 
  st_read("C:/Users/jrach/Desktop/CPLN675Midterm/result/den_result.shp") %>%
  st_transform(crs=6427)

den_temp<-
  den_result %>%
  mutate(slope = slope /11)%>%
  mutate(diwater = diwater/6246)

den_m <- 
  den_temp %>%
  select(diwater,slope,drain,infilp)
  

classProbs_den <- predict(floodModel, den_m, type="response")

finalpred <- 
  cbind(den_m,classProbs_den) %>%
  mutate(allPredictions_den = round(classProbs_den * 100)) 

ggplot() + 
  geom_sf(data=finalpred, aes(fill=factor(ntile(allPredictions_den,5))), colour=NA) +
  scale_fill_manual(values = c("#edf8fb","#b3cde3","#8c96c6","#8856a7","#810f7c"),
                    labels=as.character(quantile(finalpred$allPredictions_den,
                                                 c(0.1,.2,.4,.6,.8),na.rm=T)),
                    name="Predicted\nProbabilities(%)\n(Quintile\nBreaks)") +
  mapTheme()

5.2. Final Prediction

Finally, we use our model on the dataset we collected for Denver. Here is the prediction map.

finalprediction <- 
  finalpred %>%
  mutate(pre = ifelse(allPredictions_den<24,1,0)) 

ggplot() + 
  geom_sf(data=finalprediction, aes(fill=factor(pre)), colour=NA) +
  scale_fill_manual(values = c("blue","grey"),
                    labels=c("flood","non-flood"),
                    name="Final Prediction\n(comparable city)") +
  mapTheme() 

LS0tDQp0aXRsZTogIkNQTE42NzU6TGFuZCBVc2UgQW5kIEVudmlyb25tZW50YWwgTW9kZWxpbmciDQphdXRob3I6ICJSdWkgSmlhbmcsIENoYW5naGFvIExpIg0KZGF0ZTogIjMvMjIvMjAyMiINCm91dHB1dDoNCiAgaHRtbF9kb2N1bWVudDoNCiAgICB0b2M6IHRydWUNCiAgICB0b2NfZmxvYXQ6IHRydWUNCiAgICBjb2RlX2ZvbGRpbmc6IGhpZGUNCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlDQotLS0NCg0KYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9DQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUpDQpgYGANCg0KIyBDUExONjc1IE1pZHRlcm06IEZvcmVjYXN0aW5nIGZsb29kIGludW5kYXRpb24NCg0KVG8gdXNlICoqbWFya2Rvd24qKiBmaXJzdCB3ZSBpbnN0YWxsIHRoZSBwYWNrYWdlOiBgaW5zdGFsbC5wYWNrYWdlcygicm1hcmtkb3duIilgDQoNCmBgYHtyIGNhcnN9DQoNCmBgYA0KDQojIEJhc2ljIHByb2NlZHVyZSAtIHByb3ZpZGVkIGJ5IHByb2Zlc3Nvcg0KDQoxLiAgR2F0aGVyIG9wZW4gZGF0YSBmcm9tIGJvdGggQ2FsZ2FyeSdzIG9wZW4gZGF0YSBzaXRlIGFuZCB5b3VyIGNvbXBhcmFibGUgY2l0eSdzIG9wZW4gZGF0YSBzaXRlIGFzIHdlbGwgYXMgb3RoZXIgaW50ZXJuZXQgc291cmNlcy4NCjIuICBVc2luZyB3aGF0IHdlJ3ZlIGxlYXJuZWQgYWJvdXQgZmVhdHVyZSBlbmdpbmVlcmluZyBvdmVyIHRoZSBmaXJzdCBwYXJ0IG9mIHRoZSBzZW1lc3RlciwgYnVpbGQgYXMgbWFueSB1c2VmdWwgdmFyaWFibGVzIGRlc2NyaWJpbmcgdGhlIG5hdHVyYWwsIGh5ZHJvbG9naWNhbCBhbmQgYnVpbHQgZW52aXJvbm1lbnQgZmVhdHVyZXMgdGhhdCBtaWdodCBoZWxwIGV4cGxhaW4gZmxvb2QgaW51bmRhdGlvbi4gWW91IG11c3QgaW5jbHVkZSBhdCBsZWFzdCBvbmUgZmVhdHVyZSBmcm9tIHRoZSB3YXRlcnNoZWQgYW5hbHlzaXMuDQozLiAgSm9pbiB0aGVzZSBmZWF0dXJlcyB0byB0aGUgdmVjdG9yIEZpc2huZXQuIFJlbWVtYmVyIHRoYXQgJ2Rpc3RhbmNlIG9yIGRlbnNpdHkgdG8gRmVhdHVyZSBBJyBtaWdodCBkZXNjcmliZSB0aGUgc3BhdGlhbCByZWxhdGlvbnNoaXAgYmV0dGVyIHRoYW4gc2ltcGx5LCAnRmVhdHVyZSBBJy4NCjQuICBNb3ZlIHlvdXIgRmlzaG5ldCBkYXRhc2V0IGludG8gUiBhbmQgcnVuIHNvbWUgbG9naXN0aWMgcmVncmVzc2lvbnMgd2l0aCBib3RoIGEgdGVzdCBzZXQgYW5kIGEgdHJhaW5pbmcgc2V0LiBFeHBlcmltZW50IHVudGlsIHlvdSBmaW5kIGEgbW9kZWwgd2l0aCBlbm91Z2ggc3RhdGlzdGljYWxseSBzaWduaWZpY2FudCB2YXJpYWJsZXMuDQo1LiAgUnVuIGdvb2RuZXNzIG9mIGZpdCBtZXRyaWNzOyBNb3JlIGFkdmFuY2VkIGdyb3VwcyAoZS5nLiBpZiB5b3UgaGF2ZSB0YWtlbiBNVVNBIDUwOCkgc2hvdWxkIGV4cGVyaW1lbnQgd2l0aCBzcGF0aWFsIGNyb3NzLXZhbGlkYXRpb24uIFZpc3VhbGl6ZSB5b3VyIHJlc3VsdHMgaW4gY2hhcnQgYW5kIG1hcCBmb3JtLg0KDQoNCiMjIE1vdGl2YXRpb24NCkFzIGEgcmVzdWx0IG9mIGNsaW1hdGUgY2hhbmdlLCB0aGVyZSBoYXMgYmVlbiBhbiBpbmNyZWFzZSBpbiBmbG9vZGluZyBhY3Jvc3MgdGhlIFUuUy4sIHBhcnRpY3VsYXJseSBhbG9uZyB0aGUgY29hc3RzIGFuZCBpbiBsb3ctbHlpbmcgYXJlYXMuIFdhcm1lciB0ZW1wZXJhdHVyZXMgaW5jcmVhc2UgZXZhcG9yYXRpb24gYW5kIHJhaW4gYXZhaWxhYmlsaXR5LiBXYXJtZXIgb2NlYW5zIGNhbiBhbHNvIGNhdXNlIHN0b3JtcyBhbG9uZyB0aGUgY29hc3QgaW4gaW50ZW5zaXR5LCBpbiB0dXJuIGNhdXNpbmcgaW5jcmVhc2VkIGZsb29kaW5nLiBUaGVyZWZvcmUsIHdlIHN1Z2dlc3QgZXN0YWJsaXNoaW5nIGEgZmxvb2QgbW9kZWxpbmcgYW5kIHBsYW5uaW5nIGNlbnRlci4gDQoNCkEgZmxvb2QgaW51bmRhdGlvbiBtb2RlbGluZyBhbmQgcGxhbm5pbmcgY2VudGVyIGNvdWxkIGxlYWQgdG8gYSBjb21wcmVoZW5zaXZlIGZsb29kIG1pdGlnYXRpb24gcHJvZ3JhbSB0byBtYWtlIHVyYmFuIGNvbnN0cnVjdGlvbiBtb3JlIGVjb2xvZ2ljYWwgYW5kIHN1c3RhaW5hYmxlIGFuZCBwcm92aWRlIHRpbWVseSBmbG9vZCB3YXJuaW5ncyBmb3IgcG9zdC1kaXNhc3RlciByZWxpZWYgYW5kIHJlY29uc3RydWN0aW9uLiANCg0KVGhlIGNlbnRlcidzIGVzdGFibGlzaG1lbnQgd2lsbCBlbmFibGUgc2ltdWxhdGlvbiBvZiBmbG9vZCBpbnVuZGF0aW9uLCBwcm92aWRlIG1vcmUgYWNjdXJhdGUgYm91bmRhcnkgaW5mb3JtYXRpb24gYW5kIGZsb29kIHByZXZlbnRpb24gcmVjb21tZW5kYXRpb25zIHRvIHBsYW5uaW5nIGF1dGhvcml0aWVzLCBhbmQgcHJvdmlkZSB0aW1lbHkgZmxvb2Qgd2FybmluZ3MuIFRoZSBhZHZpY2UgYW5kIGFsZXJ0cyB3aWxsIHJlZHVjZSBlY29ub21pYyBsb3NzZXMgYW5kIGZhdGFsaXRpZXMgY2F1c2VkIGJ5IGZsb29kcy4gDQoNClRoaXMgYW5hbHlzaXMgY291bGQgaGVscCBwbGFubmVycyB1bmRlcnN0YW5kIHdoeSBhIGxvY2FsIGFyZWEgbmVlZHMgYSBmbG9vZCBmb3JlY2FzdCwgYW5kIGhvdyBhIGRhdGEgY2VudGVyIGNvdWxkIGhlbHAgbWFuYWdlIGZsb29kIHJpc2suIFdpdGggbW9yZSBkZXRhaWxlZCBkYXRhLCB0aGUgZGF0YSBjZW50ZXIgY291bGQgcHJvdmlkZSBtb3JlIGFjY3VyYXRlIGFuYWx5c2lzLg0KDQoNCg0KIyAxLiBEYXRhDQotIGJvdW5kYXJ5DQotIGZsb29kIG1hcDogaHR0cHM6Ly9kYXRhLmNhbGdhcnkuY2EvRW52aXJvbm1lbnQvMS01LUZsb29kLU1hcC0yMC1jaGFuY2Utb2Ytb2NjdXJyaW5nLWluLWFueS15ZWFyLS9peXFpLWR2dmoNCi0gaHR0cHM6Ly9taGZkLm9yZy9zZXJ2aWNlcy9mbG9vZHBsYWluLW1hbmFnZW1lbnQvDQotIGh0dHBzOi8vZGF0YS5jYWxnYXJ5LmNhL0Jhc2UtTWFwcy9EaWdpdGFsLUVsZXZhdGlvbi1Nb2RlbC1ERU0tQVNDSUktMk0vZWluay10dTlwDQotIGh0dHA6Ly93d3cuY2VjLm9yZy9ub3J0aC1hbWVyaWNhbi1lbnZpcm9ubWVudGFsLWF0bGFzL2xhbmQtY292ZXItMzBtLTIwMTUtbGFuZHNhdC1hbmQtcmFwaWRleWUvDQoNCi0gQ1JTKGNvb3JkaW5hdGUgcmVmZXJlbmNlIHN5c3RlbSk6IDI2OTEyDQotIENvb3JkaW5hdGUgU3lzdGVtIGluIEFyY0dJUyBQcm86IE5BRCAxOTgzIENTUlMgVVRNIFpvbmUgMTJODQotIHNwYXRpYWxyZWZlcmVuY2Uub3JnIGZvciBwcm9qZWN0aW9ucw0KDQoNCiMgMi4gU2V0dXANCg0KVG8gZ2V0IHN0YXJ0ZWQsIGZpcnN0IGluc3RhbGwgbGlicmFyaWVzLiANCmBgYCB7ciBsaWJyYXJpZXMsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFfQ0KbGlicmFyeShjYXJldCkNCmxpYnJhcnkocHNjbCkNCmxpYnJhcnkocGxvdFJPQykNCmxpYnJhcnkocFJPQykNCmxpYnJhcnkoc2YpDQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkoa25pdHIpDQpsaWJyYXJ5KGthYmxlRXh0cmEpDQpsaWJyYXJ5KHRpZ3JpcykNCmxpYnJhcnkodmlyaWRpcykNCmxpYnJhcnkobWFwdG9vbHMpDQpsaWJyYXJ5KHJhc3RlcikNCmxpYnJhcnkocmFzdGVyVmlzKQ0KbGlicmFyeShkcGx5cikNCmBgYA0KDQojIDMuTWFwcw0KIyMgMy4xLiBGbG9vZCBNYXANCg0KU2V0IGdncGxvdCBzdHlsZXMgY2FsbGVkIGBtYXBUaGVtZWAgYW5kIGBwbG90VGhlbWVgLCBhbHNvIGxvYWQgb3VyIGRhdGENCg0KV2UgYXJlIGdvaW5nIHRvIGluY2x1ZGUgdHdvIGRhdGFzZXRzIGluIHRoaXMgc3RlcDpgY2FsX2NpdHlib3VuZGFyeWAsIHdoaWNoIGlzIHRoZSBib3VuZGFyeSBzaGFwZWZpbGUgb2YgQ2FsZ2FyeTsgYW5kIGBmbG9vZGAsIHdoaWNoIGlzIHRoZSBvcmlnaW5hbCBmbG9vZCBkYXRhc2V0Lg0KYGBgIHtyIHByZXNzdXJlLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRSxyZXN1bHRzID0gImhpZGUifQ0KDQptYXBUaGVtZSA8LSBmdW5jdGlvbihiYXNlX3NpemUgPSAxNCkgew0KICB0aGVtZSgNCiAgICB0ZXh0ID0gZWxlbWVudF90ZXh0KCBjb2xvciA9ICJibGFjayIpLA0KICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLGNvbG91ciA9ICJibGFjayIpLA0KICAgIHBsb3Quc3VidGl0bGU9ZWxlbWVudF90ZXh0KGZhY2U9Iml0YWxpYyIpLA0KICAgIHBsb3QuY2FwdGlvbj1lbGVtZW50X3RleHQoaGp1c3Q9MCksDQogICAgYXhpcy50aWNrcyA9IGVsZW1lbnRfYmxhbmsoKSwNCiAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLGF4aXMudGl0bGUgPSBlbGVtZW50X2JsYW5rKCksDQogICAgYXhpcy50ZXh0ID0gZWxlbWVudF9ibGFuaygpLA0KICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfYmxhbmsoKSwNCiAgICBheGlzLnRpdGxlLnkgPSBlbGVtZW50X2JsYW5rKCksDQogICAgcGFuZWwuZ3JpZC5taW5vciA9IGVsZW1lbnRfYmxhbmsoKSwNCiAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X3JlY3QoY29sb3VyID0gImJsYWNrIiwgZmlsbD1OQSwgc2l6ZT0xKQ0KICApDQp9DQoNCnBsb3RUaGVtZSA8LSB0aGVtZSgNCiAgcGxvdC50aXRsZSA9ZWxlbWVudF90ZXh0KHNpemU9MTIpLA0KICBwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemU9OCksDQogIHBsb3QuY2FwdGlvbiA9IGVsZW1lbnRfdGV4dChzaXplID0gNiksDQogIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCwgYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSwNCiAgYXhpcy50ZXh0LnkgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSwNCiAgYXhpcy50aXRsZS55ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCksDQogICMgU2V0IHRoZSBlbnRpcmUgY2hhcnQgcmVnaW9uIHRvIGJsYW5rDQogIHBhbmVsLmJhY2tncm91bmQ9ZWxlbWVudF9ibGFuaygpLA0KICBwbG90LmJhY2tncm91bmQ9ZWxlbWVudF9ibGFuaygpLA0KICAjcGFuZWwuYm9yZGVyPWVsZW1lbnRfcmVjdChjb2xvdXI9IiNGMEYwRjAiKSwNCiAgIyBGb3JtYXQgdGhlIGdyaWQNCiAgcGFuZWwuZ3JpZC5tYWpvcj1lbGVtZW50X2xpbmUoY29sb3VyPSIjRDBEMEQwIixzaXplPS43NSksDQogIGF4aXMudGlja3M9ZWxlbWVudF9ibGFuaygpKQ0KDQpjYWxfYiA8LSANCiAgc3RfcmVhZCgiQzovVXNlcnMvanJhY2gvRGVza3RvcC9DUExONjc1TWlkdGVybS9SZXNvdXJjZS9DYWxfY2l0eWJvdW5kbGluZS9DYWxfY2l0eWJvdW5kbGluZS5zaHAiKSAlPiUNCiAgc3RfdHJhbnNmb3JtKGNycz0yNjkxMikNCg0KZmlzaG5ldCA8LSANCiAgc3RfbWFrZV9ncmlkKGNhbF9iLCBjZWxsc2l6ZSA9IDUwMCkgJT4lDQogIHN0X3NmKCkNCg0KZmlzaG5ldCA8LSANCiAgZmlzaG5ldFtjYWxfYixdICU+JQ0KICBtdXRhdGUodW5pcXVlSUQgPSByb3duYW1lcyguKSkgJT4lDQogIGRwbHlyOjpzZWxlY3QodW5pcXVlSUQpDQoNCmZsb29kIDwtIHN0X3JlYWQoIkM6L1VzZXJzL2pyYWNoL0Rlc2t0b3AvQ1BMTjY3NU1pZHRlcm0vUmVzb3VyY2UvZmxvb2QvY2FsX2Zsb29kLnNocCIpICU+JQ0KICBzdF90cmFuc2Zvcm0oY3JzPTI2OTEyKQ0KDQpnZ3Bsb3QoKSArIA0KICBnZW9tX3NmKGRhdGE9Zmxvb2QsY29sb3I9Im9yYW5nZSIsIGZpbGw9Im9yYW5nZSIpICsNCiAgZ2VvbV9zZihkYXRhPWNhbF9iKSArDQogIGxhYnModGl0bGU9IlJlZ3VsYXRvcnkgRmxvb2QgTWFwIChCeWxhdyBGbG9vZCBIYXphcmQpIGluIENhbGdhcnkiKSArDQogIG1hcFRoZW1lKCkNCmBgYA0KDQpUaGlzIGlzIGEgZmxvb2QgbWFwIHNob3duIGFib3ZlLg0KDQojIyAzLjIuIHZhcmlhYmxlIG1hcA0KTm93LCBsZXQncyBsb2FkIGBjYWxfcmVzdWx0YCwgd2hpY2ggaXMgYSBmaXNobmV0IGRhdGFzZXQgcmVwcmVzZW50aW5nIGZvdXIgdmFyaWFibGVzOnNsb3BlLCBkaXN0YW5jZSBmcm9tIHdhdGVyLCBkcmFpbmFnZSwgYW5kIGluZmlsdHJhdGlvbiBwcm9iYWJpbGl0eSwgYW5kIGVhY2ggdmFyaWFibGUgaXMgc2hvd24gYmVsb3c6DQoNCmBgYHtyICB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRSwgcmVzdWx0cyA9ICJoaWRlIn0NCmNhbF9yZXN1bHQgPC0gDQogIHN0X3JlYWQoIkM6L1VzZXJzL2pyYWNoL0Rlc2t0b3AvQ1BMTjY3NU1pZHRlcm0vcmVzdWx0L2NhbF9yZXN1bHQuc2hwIiklPiUNCiAgc3RfdHJhbnNmb3JtKGNycz0yNjkxMikNCg0KZ2dwbG90KCkgKyANCiAgZ2VvbV9zZihkYXRhPWNhbF9yZXN1bHQsYWVzKGZpbGw9ZGl3YXRlciksc2l6ZT0wLjEpICsNCiAgc2NhbGVfZmlsbF92aXJpZGlzKCkgKw0KICAjZ2VvbV9zZihkYXRhPWZsb29kLGNvbG9yPSJvcmFuZ2UiLCBmaWxsPSJvcmFuZ2UiLCBhbHBoYT0wLjIpICsNCiAgbGFicyh0aXRsZT0iZGlzdGFuY2UgZnJvbSB3YXRlciBib2R5IGluIENhbGdhcnkiKSArDQogIG1hcFRoZW1lKCkNCmBgYA0KDQpgYGB7ciAgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0UsIHJlc3VsdHMgPSAiaGlkZSJ9DQoNCmdncGxvdCgpICsgDQogIGdlb21fc2YoZGF0YT1jYWxfcmVzdWx0LGFlcyhmaWxsPXNsb3BlKSxzaXplPTAuMDUpICsNCiAgI2dlb21fc2YoZGF0YT1mbG9vZCxjb2xvcj0ib3JhbmdlIiwgZmlsbD0ib3JhbmdlIiwgYWxwaGE9MC4yKSArDQogIHNjYWxlX2ZpbGxfdmlyaWRpcygpICsNCiAgbGFicyh0aXRsZT0ic2xvcGUgaW4gQ2FsZ2FyeSIpICsNCiAgbWFwVGhlbWUoKQ0KYGBgDQoNCmBgYHtyICB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRSwgcmVzdWx0cyA9ICJoaWRlIn0NCmdncGxvdCgpICsgDQogIGdlb21fc2YoZGF0YT1jYWxfcmVzdWx0LGFlcyhmaWxsPWRyYWluKSxzaXplPTAuMDUpICsNCiAgc2NhbGVfZmlsbF92aXJpZGlzKCkgKw0KICAjZ2VvbV9zZihkYXRhPWZsb29kLGNvbG9yPSJvcmFuZ2UiLCBmaWxsPSJvcmFuZ2UiLCBhbHBoYT0wLjIpICsNCiAgbGFicyh0aXRsZT0iZHJhaW5hZ2UgaW4gQ2FsZ2FyeSIpICsNCiAgbWFwVGhlbWUoKQ0KYGBgDQoNCmBgYHtyICB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRSwgcmVzdWx0cyA9ICJoaWRlIn0NCmdncGxvdCgpICsgDQogIGdlb21fc2YoZGF0YT1jYWxfcmVzdWx0LGFlcyhmaWxsPWluZmlscCksIHNpemU9MC4wNSkgKw0KICBzY2FsZV9maWxsX3ZpcmlkaXMoKSArDQogICNnZW9tX3NmKGRhdGE9Zmxvb2QsY29sb3I9Im9yYW5nZSIsIGZpbGw9Im9yYW5nZSIsIGFscGhhPTAuMikgKw0KICBsYWJzKHRpdGxlPSJpbmZpbHRyYXRpb24gcHJvYmFiaWxpdHkgaW4gQ2FsZ2FyeSIpICsNCiAgbWFwVGhlbWUoKQ0KYGBgDQoNCiMgNC4gTW9kZWxpbmcNCg0KIyMgNC4xLiBQYXJ0aXRpb24gdHJhaW5pbmcgYW5kIHRlc3Qgc2V0cw0KDQpgc2V0LnNlZWRgIGdlbmVyYXRlcyBhIHJhbmRvbSBudW1iZXINCmBjcmVhdGVEYXRhUGFydGl0aW9uYCByYW5kb21seSBzZXBhcmF0ZXMgb3VyIGRhdGEgaW50byB0d28gc2V0cy4gV2Ugc2V0IHAgdG8gYHBgIC0gYSA3MCUgdHJhaW5pbmcgc2V0IGFuZCAzMCUgdGVzdCBzZXQuDQoNCmBgYHtyICB3YXJuaW5nID0gRkFMU0UsIHJlc3VsdHMgPSAiaGlkZSIsZWNobz1UUlVFLCBldmFsPUZBTFNFfQ0KDQpvdmVybCA8LQ0KICBzdF9pbnRlcnNlY3RzKGNhbF9yZXN1bHQkZ2VvbWV0cnksZmxvb2QkZ2VvbWV0cnksc3BhcnNlID0gRkFMU0UpICU+JQ0KICBhcHBseSgxLGFueSkNCg0KY2FsX3RlbXAgPC0NCiAgY2FsX3Jlc3VsdCAlPiUNCiAgbXV0YXRlKGlzX2Zsb29kPWlmZWxzZShvdmVybD09RkFMU0UsMCwxKSkNCmNhbF9tIDwtIA0KICBjYWxfdGVtcCAlPiUNCiAgZHBseXI6OnNlbGVjdChkaXdhdGVyLHNsb3BlLGlzX2Zsb29kLGRyYWluLGluZmlscCkgJT4lDQogIG11dGF0ZShpbmZpbHAgPSBhcy5mYWN0b3IoaW5maWxwKSkNCg0Kc2V0LnNlZWQoNDIpDQp0cmFpbkluZGV4IDwtIGNyZWF0ZURhdGFQYXJ0aXRpb24oY2FsX20kZGl3YXRlciwgcCA9IC43MCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsaXN0ID0gRkFMU0UsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGltZXMgPSAxKQ0KZmxvb2RUcmFpbiA8LSBjYWxfbVsgdHJhaW5JbmRleCxdDQoNCmBgYA0KIyMgNC4yLiBNYWtlIGEgYmlub21pYWwgbW9kZWwNClRoZSBiaW5vbWlhbCBsb2dpdCBtb2RlbCBydW5zIGluIHRoZSBgZ2xtYCBmdW5jdGlvbiAoZ2VuZXJhbGl6ZWQgbGluZWFyIG1vZGVscykuIFdlIHNwZWNpZnkgdGhlIGRlcGVuZGVudCB2YXJpYWJsZSBhcyBgaXNfZmxvb2RgIGFuZCBydW4gdGhlIG1vZGVsIG9uIG91ciB0cmFpbmluZyBzZXQgYGZsb29kVHJhaW5gLg0KYGBge3IgIHdhcm5pbmcgPSBGQUxTRSwgcmVzdWx0cyA9ICJoaWRlIixlY2hvPVRSVUUsIGV2YWw9RkFMU0V9DQpmbG9vZE1vZGVsIDwtIGdsbSggaXNfZmxvb2QgfiAuLCANCiAgICAgICAgICAgICAgICAgICAgIGZhbWlseT0iYmlub21pYWwiKGxpbms9ImxvZ2l0IiksIGRhdGEgPSBmbG9vZFRyYWluICU+JQ0KICAgICAgICAgICAgICAgICAgICAgICBhcy5kYXRhLmZyYW1lKCkgJT4lDQogICAgICAgICAgICAgICAgICAgICAgIHNlbGVjdCgtZ2VvbWV0cnkpKQ0Kc3VtbWFyeShmbG9vZE1vZGVsKQ0KYGBgDQpgYGB7ciBlY2hvPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KDQpsb2FkKCJDOi9Vc2Vycy9qcmFjaC9EZXNrdG9wL01pZHRlcm0vY2FsX20uUkRhdGEiKQ0KbG9hZCgiQzovVXNlcnMvanJhY2gvRGVza3RvcC9NaWR0ZXJtL2Zsb29kTW9kZWwuUkRhdGEiKQ0KdHJhaW5JbmRleCA8LSBjcmVhdGVEYXRhUGFydGl0aW9uKGNhbF9tJGRpd2F0ZXIsIHAgPSAuNzAsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGlzdCA9IEZBTFNFLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRpbWVzID0gMSkNCmZsb29kVHJhaW4gPC0gY2FsX21bIHRyYWluSW5kZXgsXQ0Kc3VtbWFyeShmbG9vZE1vZGVsKQ0KYGBgDQoNCiMjIDQuMy4gTW9kZWwgdmFsaWRhdGlvbg0KV2UgdXNlYHByZWRpY3RgIGZ1bmN0aW9uIHRvIGNyZWF0ZSBhIHZlY3RvciBvZiBjbGFzc2lmaWNhdGlvbiBwcm9iYWJpbGl0aWVzIGNhbGxlZCBgY2xhc3NQcm9ic2AgYW5kIHNldCB0aGUgcGFyYW1ldGVyIGB0eXBlPSJyZXBvbnNlImAgd2hpY2ggd2lsbCByZXR1cm5zIHByb2JhYmlsaXRpZXMgdGhhdCByYW5nZSBmcm9tIDAgdG8gMS4NCg0KYGBge3IgIHdhcm5pbmcgPSBGQUxTRSwgcmVzdWx0cyA9ICJoaWRlIixlY2hvPVRSVUUsIGV2YWw9RkFMU0V9DQpmbG9vZFRlc3QgIDwtIGNhbF9tWy10cmFpbkluZGV4LF0NCmNsYXNzUHJvYnMgPC0gcHJlZGljdChmbG9vZE1vZGVsLCBmbG9vZFRlc3QsIHR5cGU9InJlc3BvbnNlIikNCg0KaGlzdChjbGFzc1Byb2JzKQ0KYGBgDQoNCmBgYHtyIGVjaG89RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpsb2FkKCJDOi9Vc2Vycy9qcmFjaC9EZXNrdG9wL01pZHRlcm0vZmxvb2RUZXN0LlJEYXRhIikNCmxvYWQoIkM6L1VzZXJzL2pyYWNoL0Rlc2t0b3AvTWlkdGVybS9jbGFzc1Byb2JzLlJEYXRhIikNCmhpc3QoY2xhc3NQcm9icykNCmBgYA0KDQpXZSBidWlsZCBgdGVzdFByb2JzUGxvdGAuIFRoZSB2ZXJ0aWNhbCBsaW5lIHJlcHJlc2VudHMgYSAwLjUgcHJvYmFiaWxpdHkgb2YgaW51bmRhdGlvbi4NCmBgYHtyICB3YXJuaW5nID0gRkFMU0UsIHJlc3VsdHMgPSAiaGlkZSIsZWNobz1UUlVFLCBldmFsPUZBTFNFfQ0KdGVzdFByb2JzIDwtIGRhdGEuZnJhbWUob2JzID0gYXMubnVtZXJpYyhmbG9vZFRlc3QkaXNfZmxvb2QpLA0KICAgICAgICAgICAgICAgICAgICAgICAgcHJlZCA9IGNsYXNzUHJvYnMpDQoNCmdncGxvdCh0ZXN0UHJvYnMsIGFlcyh4ID0gcHJlZCwgZmlsbD1hcy5mYWN0b3Iob2JzKSkpICsgDQogIGdlb21fZGVuc2l0eSgpICsNCiAgZmFjZXRfZ3JpZChvYnMgfiAuKSArIA0KICB4bGFiKCJQcm9iYWJpbGl0eSIpICsgDQogIGdlb21fdmxpbmUoeGludGVyY2VwdCA9IC41KSArDQogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGMoImRhcmsgYmx1ZSIsICJkYXJrIGdyZWVuIiksDQogICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IGMoIm5vdCBmbG9vZCIsImZsb29kIiksDQogICAgICAgICAgICAgICAgICAgIG5hbWUgPSAiIikrDQogIHBsb3RUaGVtZQ0KYGBgDQpgYGB7ciBlY2hvPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KbG9hZCgiQzovVXNlcnMvanJhY2gvRGVza3RvcC9NaWR0ZXJtL3Rlc3RQcm9icy5SRGF0YSIpDQpnZ3Bsb3QodGVzdFByb2JzLCBhZXMoeCA9IHByZWQsIGZpbGw9YXMuZmFjdG9yKG9icykpKSArIA0KICBnZW9tX2RlbnNpdHkoKSArDQogIGZhY2V0X2dyaWQob2JzIH4gLikgKyANCiAgeGxhYigiUHJvYmFiaWxpdHkiKSArIA0KICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSAuNSkgKw0KICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjKCJkYXJrIGJsdWUiLCAiZGFyayBncmVlbiIpLA0KICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBjKCJub3QgZmxvb2QiLCJmbG9vZCIpLA0KICAgICAgICAgICAgICAgICAgICBuYW1lID0gIiIpKw0KICBwbG90VGhlbWUNCmBgYA0KDQojIyMgNC4zLjEuIENvbmZ1c2lvbiBtZXRyaWNzDQoNCldlIGJ1aWxkIGBDb25mdXNpb24gbWV0cmljc2ANCg0KUHJlZGljdGVkID0gMCwgT2JzZXJ2ZWQgPSAwIOKAlD4gVHJ1ZSBOZWdhdGl2ZQ0KDQpQcmVkaWN0ZWQgPSAxLCBPYnNlcnZlZCA9IDEg4oCUPiBUcnVlIFBvc2l0aXZlDQoNClByZWRpY3RlZCA9IDEsIE9ic2VydmVkID0gMCDigJQ+IEZhbHNlIFBvc2l0aXZlDQoNClByZWRpY3RlZCA9IDAsIE9ic2VydmVkID0gMSDigJQ+IEZhbHNlIE5lZ2F0aXZlDQoNCjEuIFNlbnNpdGl2aXR5IC0gdGhlIHByb3BvcnRpb24gb2YgYWN0dWFsIHBvc2l0aXZlcyAoMeKAmXMpIHRoYXQgd2VyZSBwcmVkaWN0ZWQgdG8gYmUgcG9zaXRpdmUuIEFsc28ga25vd24gYXMg4oCcdHJ1ZSBwb3NpdGl2ZSByYXRl4oCdLg0KDQoyLiBTcGVjaWZpY2l0eSAtIFRoZSBwcm9wb3J0aW9uIG9mIGFjdHVhbCBuZWdhdGl2ZXMgKDDigJlzKSB0aGF0IHdlcmUgcHJlZGljdGVkIHRvIGJlIG5lZ2F0aXZlcy4gQWxzbyBrbm93biBhcyDigJx0cnVlIG5lZ2F0aXZlIHJhdGXigJ0uDQpgYGB7ciBlY2hvPVRSVUUsIHdhcm5pbmc9RkFMU0V9DQp0ZXN0UHJvYnMkcHJlZENsYXNzICA9IGlmZWxzZSh0ZXN0UHJvYnMkcHJlZCA+IC41ICwxLDApDQoNCmNhcmV0Ojpjb25mdXNpb25NYXRyaXgocmVmZXJlbmNlID0gYXMuZmFjdG9yKHRlc3RQcm9icyRvYnMpLCANCiAgICAgICAgICAgICAgICAgICAgICAgZGF0YSA9IGFzLmZhY3Rvcih0ZXN0UHJvYnMkcHJlZENsYXNzKSwgDQogICAgICAgICAgICAgICAgICAgICAgIHBvc2l0aXZlID0gIjEiKQ0KYGBgDQoNCiMjIyA0LjMuMi4gUk9DIEN1cnZlDQpUaGVuIHdlIGNyZWF0ZSBhbiBST0MgKHJlY2VpdmVyIG9wZXJhdGluZyBjaGFyYWN0ZXJpc3RpYykgY3VydmUuIEl0J3MgYmV0dGVyIHdoZW4gdGhlIGJsYWNrIGxpbmUgaXMgbW9yZSBjdXJ2ZWQgdG8gdGhlIHk9MS4wMA0KYGBge3IgZWNobz1UUlVFLCB3YXJuaW5nPUZBTFNFfQ0KZ2dwbG90KHRlc3RQcm9icywgYWVzKGQgPSBvYnMsIG0gPSBwcmVkKSkgKyANCiAgZ2VvbV9yb2Mobi5jdXRzID0gNTAsIGxhYmVscyA9IEZBTFNFKSArIA0KICBzdHlsZV9yb2ModGhlbWUgPSB0aGVtZV9ncmV5KSArDQogIGdlb21fYWJsaW5lKHNsb3BlID0gMSwgaW50ZXJjZXB0ID0gMCwgc2l6ZSA9IDEuNSwgY29sb3IgPSAnZ3JleScpIA0KDQpgYGANCmBgYHtyIGVjaG89VFJVRSwgd2FybmluZz1GQUxTRX0NCmF1Yyh0ZXN0UHJvYnMkb2JzLCB0ZXN0UHJvYnMkcHJlZCkNCmBgYA0KDQojIyMgNC4zLjMuIENyb3NzIHZhbGlkYXRpb24NCg0KQ3Jvc3MtdmFsaWRhdGlvbiBpdGVyYXRpdmVseSBjcmVhdGVzIG1hbnkgcmFuZG9tbHkgZ2VuZXJhdGVkIHRlc3Qgc2V0cyBvciDigJhmb2xkc+KAmSwgdGVzdGluZyB0aGUgcG93ZXIgb2YgdGhlIG1vZGVsIG9uIGVhY2guDQpgYGB7ciAgd2FybmluZyA9IEZBTFNFLCByZXN1bHRzID0gImhpZGUiLGVjaG89VFJVRSwgZXZhbD1GQUxTRX0NCg0KDQpjdHJsIDwtIHRyYWluQ29udHJvbChtZXRob2QgPSAiY3YiLCANCiAgICAgICAgICAgICAgICAgICAgIG51bWJlciA9IDEwMCwgDQogICAgICAgICAgICAgICAgICAgICBzYXZlUHJlZGljdGlvbnMgPSBUUlVFKQ0KDQpjdkZpdCA8LSB0cmFpbihhcy5mYWN0b3IoaXNfZmxvb2QpIH4gLiwgIGRhdGEgPSBjYWxfbSAlPiUgDQogICAgICAgICAgICAgICAgIGFzLmRhdGEuZnJhbWUoKSAlPiUNCiAgICAgICAgICAgICAgICAgc2VsZWN0KC1nZW9tZXRyeSksIA0KICAgICAgICAgICAgICAgbWV0aG9kPSJnbG0iLCBmYW1pbHk9ImJpbm9taWFsIiwNCiAgICAgICAgICAgICAgIHRyQ29udHJvbCA9IGN0cmwpDQoNCmN2Rml0DQoNCmdncGxvdChhcy5kYXRhLmZyYW1lKGN2Rml0JHJlc2FtcGxlKSwgYWVzKEFjY3VyYWN5KSkgKyANCiAgZ2VvbV9oaXN0b2dyYW0oKSArDQogIHNjYWxlX3hfY29udGludW91cyhsaW1pdHMgPSBjKDAsIDEpKSArDQogIGxhYnMoeD0iQWNjdXJhY3kiLA0KICAgICAgIHk9IkNvdW50IikrDQogIHBsb3RUaGVtZQ0KYGBgDQoNCmBgYHtyIGVjaG89RkFMU0UsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0UsIH0NCmxvYWQoIkM6L1VzZXJzL2pyYWNoL0Rlc2t0b3AvTWlkdGVybS9jdkZpdC5SRGF0YSIpDQpjdkZpdA0KZ2dwbG90KGFzLmRhdGEuZnJhbWUoY3ZGaXQkcmVzYW1wbGUpLCBhZXMoQWNjdXJhY3kpKSArIA0KICBnZW9tX2hpc3RvZ3JhbSgpICsNCiAgc2NhbGVfeF9jb250aW51b3VzKGxpbWl0cyA9IGMoMCwgMSkpICsNCiAgbGFicyh4PSJBY2N1cmFjeSIsDQogICAgICAgeT0iQ291bnQiKSsNCiAgcGxvdFRoZW1lDQpgYGANCg0KSW4gb3VyIG9waW5pb24sIHdlIGhhdmUgYSBnZW5lcmFsaXphYmxlIG1vZGVsLg0KDQojIyMgNC4zLjQuIE1hcCBwcmVkaWN0aW9ucw0KVGhlbiwgd2UgcHJlZGljdCBmb3IgdGhlIGVudGlyZSBkYXRhc2V0IGFuZCBhc3Nlc3Mgb3VyIHByZWRpY3Rpb25zLg0KDQpgYGB7ciAgd2FybmluZyA9IEZBTFNFLCByZXN1bHRzID0gImhpZGUiLGVjaG89VFJVRSwgZXZhbD1GQUxTRX0NCmFsbFByZWRpY3Rpb25zIDwtIA0KICBwcmVkaWN0KGN2Rml0LCBjYWxfbSwgdHlwZT0icHJvYiIpWywyXQ0KDQpjYWxfcHJlZCA8LSANCiAgY2JpbmQoY2FsX20sYWxsUHJlZGljdGlvbnMpICU+JQ0KICBtdXRhdGUoYWxsUHJlZGljdGlvbnMgPSByb3VuZChhbGxQcmVkaWN0aW9ucyAqIDEwMCkpIA0KZ2dwbG90KCkgKyANCiAgZ2VvbV9zZihkYXRhPWNhbF9wcmVkLCBhZXMoZmlsbD1mYWN0b3IobnRpbGUoYWxsUHJlZGljdGlvbnMsNSkpKSwgDQogICAgICAgICAgY29sb3VyPU5BKSArDQogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGMoIiNlZGY4ZmIiLCIjYjNjZGUzIiwiIzhjOTZjNiIsIiM4ODU2YTciLCIjODEwZjdjIiksDQogICAgICAgICAgICAgICAgICAgIGxhYmVscz1hcy5jaGFyYWN0ZXIocXVhbnRpbGUoY2FsX3ByZWQkYWxsUHJlZGljdGlvbnMsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYygwLjEsLjIsLjQsLjYsLjgpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5hLnJtPVQpKSwNCiAgICAgICAgICAgICAgICAgICAgbmFtZT0iUHJlZGljdGVkXG5Qcm9iYWJpbGl0aWVzKCUpXG4oUXVpbnRpbGVcbkJyZWFrcykiKSArDQogIG1hcFRoZW1lKCkNCmBgYA0KDQpgYGB7ciBlY2hvPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQ0KbG9hZCgiQzovVXNlcnMvanJhY2gvRGVza3RvcC9NaWR0ZXJtL2NhbF9wcmVkLlJEYXRhIikNCmdncGxvdCgpICsgDQogIGdlb21fc2YoZGF0YT1jYWxfcHJlZCwgYWVzKGZpbGw9ZmFjdG9yKG50aWxlKGFsbFByZWRpY3Rpb25zLDUpKSksIA0KICAgICAgICAgIGNvbG91cj1OQSkgKw0KICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjKCIjZWRmOGZiIiwiI2IzY2RlMyIsIiM4Yzk2YzYiLCIjODg1NmE3IiwiIzgxMGY3YyIpLA0KICAgICAgICAgICAgICAgICAgICBsYWJlbHM9YXMuY2hhcmFjdGVyKHF1YW50aWxlKGNhbF9wcmVkJGFsbFByZWRpY3Rpb25zLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGMoMC4xLC4yLC40LC42LC44KSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuYS5ybT1UKSksDQogICAgICAgICAgICAgICAgICAgIG5hbWU9IlByZWRpY3RlZFxuUHJvYmFiaWxpdGllcyglKVxuKFF1aW50aWxlXG5CcmVha3MpIikgKw0KICBtYXBUaGVtZSgpDQpgYGANCioqKk9ic2VydmVkIGFuZCBQcmVkaWN0ZWQgRmxvb2QgQXJlYXMqKioNCkNhbGdhcnk7IGZsb29kIGFyZWEgaW4gcmVkDQpgYGB7ciBlY2hvPVRSVUV9DQpnZ3Bsb3QoKSArIA0KICBnZW9tX3NmKGRhdGE9Y2FsX3ByZWQsIGFlcyhmaWxsPWZhY3RvcihudGlsZShhbGxQcmVkaWN0aW9ucyw1KSkpLCBjb2xvdXI9TkEpICsNCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gYygiI2VkZjhmYiIsIiNiM2NkZTMiLCIjOGM5NmM2IiwiIzg4NTZhNyIsIiM4MTBmN2MiKSwNCiAgICAgICAgICAgICAgICAgICAgbGFiZWxzPWFzLmNoYXJhY3RlcihxdWFudGlsZShjYWxfcHJlZCRhbGxQcmVkaWN0aW9ucywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjKDAuMSwuMiwuNCwuNiwuOCksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbmEucm09VCkpLA0KICAgICAgICAgICAgICAgICAgICBuYW1lPSJQcmVkaWN0ZWRcblByb2JhYmlsaXRpZXMoJSlcbihRdWludGlsZVxuQnJlYWtzKSIpICsNCiAgZ2VvbV9zZihkYXRhPWNhbF9wcmVkICAlPiUgDQogICAgICAgICAgICBmaWx0ZXIoaXNfZmxvb2QgPT0gMSksIA0KICAgICAgICAgIGZpbGw9InJlZCIsY29sb3VyPU5BLCBhbHBoYT0wLjUpICsNCiAgbWFwVGhlbWUgKCkNCmBgYA0KDQpgYGB7ciBlY2hvPVRSVUV9DQpjYWxfcHJlZCAlPiUNCiAgbXV0YXRlKGNvbmZSZXN1bHQ9Y2FzZV93aGVuKGFsbFByZWRpY3Rpb25zIDwgNTAgJiBpc19mbG9vZD09MCB+ICJUcnVlX05lZ2F0aXZlIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFsbFByZWRpY3Rpb25zID49IDUwICYgaXNfZmxvb2Q9PTEgfiAiVHJ1ZV9Qb3NpdGl2ZSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhbGxQcmVkaWN0aW9ucyA8IDUwICYgaXNfZmxvb2Q9PTEgfiAiRmFsc2VfTmVnYXRpdmUiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYWxsUHJlZGljdGlvbnMgPj0gNTAgJiBpc19mbG9vZD09MCB+ICJGYWxzZV9Qb3NpdGl2ZSIpKSAlPiUNCiAgZ2dwbG90KCkrDQogIGdlb21fc2YoYWVzKGZpbGwgPSBjb25mUmVzdWx0KSwgY29sb3IgPSAidHJhbnNwYXJlbnQiKSsNCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gYygiUmVkIiwiT3JhbmdlIiwiTGlnaHQgQmx1ZSIsIkxpZ2h0IEdyZWVuIiksDQogICAgICAgICAgICAgICAgICAgIG5hbWU9Ik91dGNvbWVzIikrDQogIGxhYnModGl0bGU9IkNvbmZ1c2lvbiBNZXRyaWNzIikgKw0KICBtYXBUaGVtZSgpDQpgYGANCg0KIyA1LiBDb21wYXJhYmxlIENpdHkgLSBEZW52ZXINCiMjIDUuMSBQcmVkaWN0ZWQgUHJvYmFiaWxpdGllcw0KYGBge3IgIHdhcm5pbmcgPSBGQUxTRSwgcmVzdWx0cyA9ICJoaWRlIixlY2hvPVRSVUUsIGV2YWw9RkFMU0V9DQpkZW5fcmVzdWx0IDwtIA0KICBzdF9yZWFkKCJDOi9Vc2Vycy9qcmFjaC9EZXNrdG9wL0NQTE42NzVNaWR0ZXJtL3Jlc3VsdC9kZW5fcmVzdWx0LnNocCIpICU+JQ0KICBzdF90cmFuc2Zvcm0oY3JzPTY0MjcpDQoNCmRlbl90ZW1wPC0NCiAgZGVuX3Jlc3VsdCAlPiUNCiAgbXV0YXRlKHNsb3BlID0gc2xvcGUgLzExKSU+JQ0KICBtdXRhdGUoZGl3YXRlciA9IGRpd2F0ZXIvNjI0NikNCg0KZGVuX20gPC0gDQogIGRlbl90ZW1wICU+JQ0KICBzZWxlY3QoZGl3YXRlcixzbG9wZSxkcmFpbixpbmZpbHApDQogIA0KDQpjbGFzc1Byb2JzX2RlbiA8LSBwcmVkaWN0KGZsb29kTW9kZWwsIGRlbl9tLCB0eXBlPSJyZXNwb25zZSIpDQoNCmZpbmFscHJlZCA8LSANCiAgY2JpbmQoZGVuX20sY2xhc3NQcm9ic19kZW4pICU+JQ0KICBtdXRhdGUoYWxsUHJlZGljdGlvbnNfZGVuID0gcm91bmQoY2xhc3NQcm9ic19kZW4gKiAxMDApKSANCg0KZ2dwbG90KCkgKyANCiAgZ2VvbV9zZihkYXRhPWZpbmFscHJlZCwgYWVzKGZpbGw9ZmFjdG9yKG50aWxlKGFsbFByZWRpY3Rpb25zX2Rlbiw1KSkpLCBjb2xvdXI9TkEpICsNCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gYygiI2VkZjhmYiIsIiNiM2NkZTMiLCIjOGM5NmM2IiwiIzg4NTZhNyIsIiM4MTBmN2MiKSwNCiAgICAgICAgICAgICAgICAgICAgbGFiZWxzPWFzLmNoYXJhY3RlcihxdWFudGlsZShmaW5hbHByZWQkYWxsUHJlZGljdGlvbnNfZGVuLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGMoMC4xLC4yLC40LC42LC44KSxuYS5ybT1UKSksDQogICAgICAgICAgICAgICAgICAgIG5hbWU9IlByZWRpY3RlZFxuUHJvYmFiaWxpdGllcyglKVxuKFF1aW50aWxlXG5CcmVha3MpIikgKw0KICBtYXBUaGVtZSgpDQpgYGANCmBgYHtyIGVjaG89RkFMU0UsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0UgfQ0KbG9hZCgiQzovVXNlcnMvanJhY2gvRGVza3RvcC9NaWR0ZXJtL2ZpbmFscHJlZC5SRGF0YSIpDQpnZ3Bsb3QoKSArIA0KICBnZW9tX3NmKGRhdGE9ZmluYWxwcmVkLCBhZXMoZmlsbD1mYWN0b3IobnRpbGUoYWxsUHJlZGljdGlvbnNfZGVuLDUpKSksIGNvbG91cj1OQSkgKw0KICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjKCIjZWRmOGZiIiwiI2IzY2RlMyIsIiM4Yzk2YzYiLCIjODg1NmE3IiwiIzgxMGY3YyIpLA0KICAgICAgICAgICAgICAgICAgICBsYWJlbHM9YXMuY2hhcmFjdGVyKHF1YW50aWxlKGZpbmFscHJlZCRhbGxQcmVkaWN0aW9uc19kZW4sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYygwLjEsLjIsLjQsLjYsLjgpLG5hLnJtPVQpKSwNCiAgICAgICAgICAgICAgICAgICAgbmFtZT0iUHJlZGljdGVkXG5Qcm9iYWJpbGl0aWVzKCUpXG4oUXVpbnRpbGVcbkJyZWFrcykiKSArDQogIG1hcFRoZW1lKCkNCg0KDQoNCmBgYA0KDQojIyA1LjIuIEZpbmFsIFByZWRpY3Rpb24NCkZpbmFsbHksIHdlIHVzZSBvdXIgbW9kZWwgb24gdGhlIGRhdGFzZXQgd2UgY29sbGVjdGVkIGZvciBEZW52ZXIuDQpIZXJlIGlzIHRoZSBwcmVkaWN0aW9uIG1hcC4NCg0KYGBge3IgIHdhcm5pbmcgPSBGQUxTRSwgcmVzdWx0cyA9ICJoaWRlIixlY2hvPVRSVUUsIGV2YWw9RkFMU0V9DQpmaW5hbHByZWRpY3Rpb24gPC0gDQogIGZpbmFscHJlZCAlPiUNCiAgbXV0YXRlKHByZSA9IGlmZWxzZShhbGxQcmVkaWN0aW9uc19kZW48MjQsMSwwKSkgDQoNCmdncGxvdCgpICsgDQogIGdlb21fc2YoZGF0YT1maW5hbHByZWRpY3Rpb24sIGFlcyhmaWxsPWZhY3RvcihwcmUpKSwgY29sb3VyPU5BKSArDQogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGMoImJsdWUiLCJncmV5IiksDQogICAgICAgICAgICAgICAgICAgIGxhYmVscz1jKCJmbG9vZCIsIm5vbi1mbG9vZCIpLA0KICAgICAgICAgICAgICAgICAgICBuYW1lPSJGaW5hbCBQcmVkaWN0aW9uXG4oY29tcGFyYWJsZSBjaXR5KSIpICsNCiAgbWFwVGhlbWUoKSANCmBgYA0KYGBge3IgZWNobz1GQUxTRSwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRSwgfQ0KbG9hZCgiQzovVXNlcnMvanJhY2gvRGVza3RvcC9NaWR0ZXJtL2ZpbmFscHJlZGljdGlvbi5SRGF0YSIpDQpmaW5hbHByZWRpY3Rpb24gPC0gDQogIGZpbmFscHJlZCAlPiUNCiAgbXV0YXRlKHByZSA9IGlmZWxzZShhbGxQcmVkaWN0aW9uc19kZW48MjQsMSwwKSkgDQoNCmdncGxvdCgpICsgDQogIGdlb21fc2YoZGF0YT1maW5hbHByZWRpY3Rpb24sIGFlcyhmaWxsPWZhY3RvcihwcmUpKSwgY29sb3VyPU5BKSArDQogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGMoImJsdWUiLCJncmV5IiksDQogICAgICAgICAgICAgICAgICAgIGxhYmVscz1jKCJmbG9vZCIsIm5vbi1mbG9vZCIpLA0KICAgICAgICAgICAgICAgICAgICBuYW1lPSJGaW5hbCBQcmVkaWN0aW9uXG4oY29tcGFyYWJsZSBjaXR5KSIpICsNCiAgbWFwVGhlbWUoKSANCmBgYA0KDQojIDYuIFBvd2VycG9pbnQgWW91dHViZSBMaW5rDQpodHRwczovL3lvdXR1LmJlL0FTWmRkN19uSWlZDQo=