License

Copyright (c) 2020 Geosyntec Consultants, Inc.  Mozilla Public License Version 2.0

This software is provided “as is”, without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose and noninfringement. In no event shall the authors or copyright holders be liable for any claim, damages or other liability, whether in an action of contract, tort or otherwise, arising from, out of or in connection with the software or the use or other dealings in the software.

Description

This notebook is the second step in the watershed regression task. It uses the relationships from Part 1 (linear models) to run generalized linear mixed models with censored dependent variables. This approach uses Markov chain Monte Carlo techniques and relies heavily on the MCMCglmm package (Hadfield 2009).

Here are the steps in the code below:

  1. Code set up
  2. Get and prepare monitoring data
  3. Merge spatial data with monitoring data
  4. Remove mulitcolinear predictors
  5. Peform model selection
  6. Select the best model (these results are then used in the Bayesian model in step 2)

Setup

clear workspace and load libraries. (code not shown in html file )


#library("renv")
library(caret)
library(readr)
library(dplyr)
 
setwd("~/repos/stormwaterheatmap/R-scripts/WatershedRegression")
The working directory was changed to C:/Users/cnilsen/Documents/repos/stormwaterheatmap/R-scripts/WatershedRegression inside a notebook chunk. The working directory will be reset when the chunk is finished running. Use the knitr root.dir option in the setup chunk to change the working directory for notebook chunks.

Get and prepare monitoring data

The stormwater outfall data is available from the Department of Ecology at https://data.wa.gov/Natural-Resources-Environment/Municipal-Stormwater-Permit-Outfall-Data/d958-q2ci.

A .csv file is saved in WatershedRegression/data/S8_data.csv

all.S8.data <- read_csv("data/S8_data.csv", 
    col_types = cols(field_collection_end_date = col_date(format = "%m/%d/%Y"), 
        field_collection_start_date = col_date(format = "%m/%d/%Y")) )
Missing column names filled in: 'X1' [1]10838 parsing failures.
 row          col           expected actual               file
2384 result_basis 1/0/T/F/TRUE/FALSE    Dry 'data/S8_data.csv'
2385 result_basis 1/0/T/F/TRUE/FALSE    Dry 'data/S8_data.csv'
2386 result_basis 1/0/T/F/TRUE/FALSE    Dry 'data/S8_data.csv'
2387 result_basis 1/0/T/F/TRUE/FALSE    Dry 'data/S8_data.csv'
2388 result_basis 1/0/T/F/TRUE/FALSE    Dry 'data/S8_data.csv'
.... ............ .................. ...... ..................
See problems(...) for more details.
#filter out rejected data
all.S8.data <- (filter(all.S8.data,!result_data_qualifier %in% 'REJ'))

#filter out replicates 
all.S8.data <- (filter(all.S8.data,!sample_replicate_flag %in% 'Y'))

#change nondetect warnings to detects
warnings <- all.S8.data$nondetect_flag == "WARNING"
all.S8.data$nondetect_flag[warnings] <- FALSE 

#Change NA to detect
all.S8.data$nondetect_flag[is.na(all.S8.data$nondetect_flag)] <- FALSE

#Change season to factor 
all.S8.data$season <- as.factor(all.S8.data$season)

The chunk below makes a list of parameters we are interested in.

#Select Parameters
params <- c('Zinc - Water - Total',
 'Copper - Water - Total',
 'Nitrite-Nitrate - Water - Dissolved',
 'Lead - Water - Total',
 'Total Phosphorus - Water - Total',
 'Total Suspended Solids - Water - Total',
 'Total Phthalate - Water - Total',
'Total PAH - Water - Total',
#'Chrysene - Water - Total',
'CPAH - Water - Total',
'HPAH - Water - Total' 
#'Total Kjeldahl Nitrogen - Water - Total',
#'Total PCB - Water - Total'
)

#save a list of all the parameters in case we want to use mor. 
params.all <- data.frame(unique(all.S8.data$parameter))
s8data <- all.S8.data 
#

Clean up extracted data and rename columns:

s8data <- all.S8.data %>% 

  dplyr::select(
    study_name,
    location_id,parameter,
    type,
    season,
    new_result_value,
    nondetect_flag,
    study_id,
    access_id,
    field_collection_end_date,
    field_collection_start_date,
    type)


#rename some columns
colnames(s8data)[colnames(s8data) == "location_id"] <- "Location"
colnames(s8data)[colnames(s8data) == "new_result_value"] <-
  "concentration"
s8data$nondetect_flag <- as.logical(s8data$nondetect_flag)
s8data$concentration <- as.numeric(s8data$concentration)

Check for outliers

Set up a jitter plot of all the data to look for outliers:


scatter_cocs <- function(df.coc,title) {
 p <- ggplot(df.coc, aes(1, concentration)) + geom_jitter() + labs(
  title = title,
  subtitle = "Data collected 2009-2013",
  caption =
    " Data source: Ecology, 2015",
  x = "Observations"
  #y = y.title
)  
p + facet_wrap( ~ parameter, ncol=3, scales = 'free') 
}

scatter_cocs(s8data[which(s8data$parameter %in% params),],'All Observations')

outliers are apprent for TSS, TP, and no2-no3. Remove these.

#remove and replot 

outlierParams <- c("Total Suspended Solids - Water - Total", "Total Phosphorus - Water - Total", "Nitrite-Nitrate - Water - Dissolved")

#This removes the highest values 
outlierVals <-
  top_n(group_by(s8data[which(s8data$parameter %in% outlierParams), ], parameter), 1, concentration)$concentration

s8data <- s8data %>%
  group_by(parameter) %>%
  slice(which(!(
    parameter %in% outlierParams & concentration %in% outlierVals
  )))

scatter_cocs(s8data[which(s8data$parameter %in% params),],'All Observations - Outliers Removed')

Looks better. Move on to the next Chunk.

Get Spatial data and merge


#Spatial predcitors have been extracted and saved as a csv file. 

spatial_data <- read_csv("C:/Users/cnilsen/Google Drive/repos/spatialPredictors.csv")
Parsed with column specification:
cols(
  `system:index` = col_character(),
  COM = col_double(),
  Location = col_character(),
  RES = col_double(),
  depSplusN = col_double(),
  impervious = col_double(),
  logPopulation = col_double(),
  nighttime_lights = col_double(),
  pm25 = col_double(),
  rev_logTraffic = col_double(),
  roadDensity = col_double(),
  .geo = col_logical()
)
#RES and COM are compositional data. Change to a ratio
spatial_data$LU_ratio = spatial_data$COM/spatial_data$RES 
spatial_data <- dplyr::select(spatial_data, -c(RES,COM,.geo))
#merge spatial predictors with monitoring data 
s8data.wPredictors <<- merge(s8data, spatial_data)%>% 
  dplyr::select(-c(depSplusN))

# spatial_data<-read_csv("data/spatialPredictors42.csv", col_types = cols(X1 = col_skip()))
# 
# #merge spatial predictors with monitoring data 
# s8data.wPredictors <- merge(s8data, spatial_data)

Perform MCMC modeling

Functions

Some helper functions to help

Function to add a survival object to the S8 dataframe
add_surv <- function(df) {
  df$cenMin <- ifelse(df$nondetect_flag,-Inf, (df$concentration))
  df$cenMax  <- (df$concentration)
  df$cenMin_log <- ifelse(df$nondetect_flag,-Inf, log(df$concentration))
  df$cenMax_log  <- log(df$concentration)
  
  return(df)
}
Function to return a chart of predictions from the model
scatter_predict <- function(model_df,predictions) {
# model_to_predict <- CuModel 
# coc = params[2]
# df <- (subset(s8data.wPredictors, parameter == coc)) %>%
#   add_surv()
# predictions <- predict(model_to_predict, newdata=df, 
#          type="response", interval="none", level=0.95, it=NULL, 
#          posterior="all", verbose=FALSE, approx="numerical")
# 
 obs <- log(model_df$concentration)
 
 ggstatsplot::ggscatterstats(
  data =tibble(p = predictions, obs = obs,L = model_df$Location),
  x = p,
  y = obs,
  type = "bf",
  point.width.jitter = 0.02,
  #point.height.jitter = 0.1,
  marginal = FALSE,
  xlab = "Predicted log(µg/L)",
  ylab = "Observed  log(µg/L)",
  title = coc,
  results.subtitle = FALSE,
  subtitle = "Predictions vs. Observations",
  smooth.line.args = list(size = 1, color = "blue"),
  messages = FALSE
)
}
add tidy and glance functions to the mcmcglmm objects since they don’t play nicely with tidyverse objects.
# add custom functions to extract estimates (tidy) and goodness-of-fit (glance) information
tidy.MCMCglmm <- function(object, ...) {
    s <- summary(object, ...)
    ret <- tibble::tibble(term = row.names(s$solutions),
                          estimate = s$solutions[, 1],
                          conf.low = s$solutions[, 2],
                          conf.high = s$solutions[, 3])
    ret
}
glance.MCMCglmm <- function(object, ...) {
    ret <- tibble::tibble(dic = object$DIC,
                          n = nrow(object$X))
    ret
}

# estimate a simple model
#model <- MCMCglmm(PO ~ 1 + plate, random = ~ FSfamily, data = PlodiaPO, verbose=FALSE, pr=TRUE)

mcmc_calc function

This is the main funciton to run the mcmc model. It does the following:
1. subsets a dataframe to include only the parameter we want to predict
2. adds a survival object to handle censored data
3. sets up a simiple prior structure
4. performs mcmc modeling on either the log-transformed or non-log transformed responses.
5. returns the results

mcmc_calc <- function(coc.local, fixed_list, lhs) {
  
  df <- (subset(s8data.wPredictors, parameter == coc.local))

   data <-
  df %>%
  add_surv()
#make the prior_structures 
prior.1<-list(R=list(V=1, fix=1), G=list(G1=list(V=1, nu=0.002)))
prior.2<-list(R=list(V=2, fix=1), G=list(G1=list(V=1, nu=0)))

  
  if (lhs == 'log') {
    mcmc_formula <-
      as.formula(paste(
        "cbind(cenMin_log, cenMax_log) ~ ",
        paste0(fixed_list , collapse = "+")
      ))
  }
  else {
    mcmc_formula <-
      as.formula(paste(
        "cbind(cenMin, cenMax) ~ ",
        paste0(fixed_list , collapse = "+")
      ))
  }
  
  mcmc_results <-
    MCMCglmm(
      mcmc_formula,
      random = ~ Location,
      data =  data,
      family = "cengaussian", 
      verbose = FALSE, prior = prior.1, singular.ok = TRUE,
      nitt = 60000, thin = 13, burnin = 10000
    )
    return((mcmc_results))
}
function that returns bayesian plots
# Do some predictive checks 
library(bayesplot)
color_scheme_set("blue")
bayes_plots <- function(fit,coc,df) {
#fit <- TSSModel

#coc = 'Total Suspended Solids - Water - Total'

#df <- (subset(s8data.wPredictors, parameter == coc)) %>%
 # add_surv()
yrep_c <- predict(fit, newdata=df, 
         type="response", interval="confidence", level=0.9, it=NULL, 
         posterior="all", verbose=FALSE, approx="numerical")
yrep_p <- predict(fit, newdata=df, 
         type="response", interval="prediction", level=0.9, it=NULL, 
         posterior="all", verbose=FALSE, approx="numerical")

#show uncertainty intervals under esimated posterior density curves 
plot.1 <- mcmc_areas(fit$Sol,prob = 0.80, pprob_outer = 0.95,point_est="mean")+ggplot2::labs(title = coc, subtitle   = "Posterior distributions with medians and 80% intervals")

#generate scatter plot of predictions 

colnames(yrep_p) <-  c("fit.p", "lwr.p", "upr.p")
scatterdata <- cbind(df, yrep_c, yrep_p)

#generate scatter plot of predictions 
plot.2 <- ggplot(scatterdata) + 
  geom_ribbon(aes(ymin = lwr.p, ymax = upr.p, x = fit),fill="grey", alpha = 0.5) + 
  geom_ribbon(aes(ymin = lwr, ymax = upr, x = fit), fill = "grey", alpha = 0.8) + 
  geom_line(aes(x=fit,y=fit),color="blue",linetype=5)+
  geom_point(aes(x = fit, y = log(concentration)), alpha = 0.5)+
  ggplot2::labs(x="yrep",y="fit",title = coc, subtitle   = "Scatter plot of observed data vs simulated",caption="dark shade: confidence intervals \n light shade: prediction intervals")


#simulate with 100 draws  
ysim <- (simulate(fit,nsim = 100))


#overlay of predictions 
plot.3<- ppc_dens_overlay(log(df$concentration),t(ysim))+ggplot2::labs(x="log concentration, μg/L ",title = coc, subtitle   = "Observed (y) vs. simulated draws (yrep)")


#predictions vs observed, densities 
scatterdata <- cbind(scatterdata,sim=ysim[,50])
plot.4<-ggplot(scatterdata) + geom_density(aes(x=log(concentration)),fill="lightblue",alpha=0.5)+
geom_density(aes(x=sim),fill="darkblue",alpha=0.5,linetype=2)+facet_wrap(vars(type),scales="free")+ ggplot2::labs(y="log concentration, μg/L ",title = coc, subtitle   = "Simulated vs. Observed Concetrations for reported land use types",caption="dark shade: simulated, light shade: observed \n COM: Commercial, HDR: High Density Residential, LDR: Low Density Residential, IND: Industrial")
return(list(plot.1,plot.2,plot.3,plot.4))
}
#function that returns dot-whisker plots
dotwhiskerplot <- function(model,coc) {
  return(ggstatsplot::ggcoefstats(
  x = model,
  title = coc,
  #subtitle = "multivariate generalized linear mixed model",
  #conf.method = "HPDinterval",
  
  meta.analytic.effect = TRUE,
  exclude.intercept = FALSE,
  robust = TRUE,
  meta.type = "bayes",
  bf.message = TRUE
)+ ggstatsplot::theme_ggstatsplot())}

#funciton for other diagnostic plots 
diagnostic_plots <- function(chains,coc) {
  plotTrace(chains,axes=TRUE,same.limits=TRUE)
  plotDens(chains,main=paste('Posterior Distributions \n',coc),probs=c(0.050,0.950),same.limits=FALSE)}

Run model

For each chunk below, we run the model and output diagnostic and prediction plots.

Zinc

summary(mod)

 Iterations = 10001:59999
 Thinning interval  = 13
 Sample size  = 3847 

 DIC: 928 

 G-structure:  ~Location

 R-structure:  ~units

 Location effects: cbind(cenMin_log, cenMax_log) ~ impervious 

            post.mean l-95% CI u-95% CI eff.samp   pMCMC    
(Intercept)     3.644    3.239    4.067     3847 <0.0003 ***
impervious      0.714    0.414    1.015     3847 <0.0003 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Copper

summary(mod)

 Iterations = 10001:59999
 Thinning interval  = 13
 Sample size  = 3847 

 DIC: 928 

 G-structure:  ~Location

 R-structure:  ~units

 Location effects: cbind(cenMin_log, cenMax_log) ~ impervious 

            post.mean l-95% CI u-95% CI eff.samp   pMCMC    
(Intercept)     3.644    3.239    4.067     3847 <0.0003 ***
impervious      0.714    0.414    1.015     3847 <0.0003 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Nitrite-Nitrate

df <- (subset(s8data.wPredictors, parameter == coc)) %>%
  add_surv()
bayes_plots(mod,coc,df)
The following arguments were unrecognized and ignored: pprob_outer`expand_scale()` is deprecated; use `expansion()` instead.
[[1]]

[[2]]

[[3]]

[[4]]

Lead

coc <- params[4]
PbModel <- mcmc_calc(params[4],c('impervious'),'log')

mod <- PbModel
summary(mod$Sol)

Iterations = 10001:59999
Thinning interval = 13 
Number of chains = 1 
Sample size per chain = 3847 

1. Empirical mean and standard deviation for each variable,
   plus standard error of the mean:

             Mean    SD Naive SE Time-series SE
(Intercept) 1.193 0.327  0.00527        0.00527
impervious  0.654 0.233  0.00375        0.00388

2. Quantiles for each variable:

             2.5%   25%   50%  75% 97.5%
(Intercept) 0.545 0.991 1.196 1.39  1.84
impervious  0.204 0.502 0.652 0.80  1.11
summary(mod)

 Iterations = 10001:59999
 Thinning interval  = 13
 Sample size  = 3847 

 DIC: 1040 

 G-structure:  ~Location

 R-structure:  ~units

 Location effects: cbind(cenMin_log, cenMax_log) ~ impervious 

            post.mean l-95% CI u-95% CI eff.samp  pMCMC   
(Intercept)     1.193    0.573    1.866     3847 0.0026 **
impervious      0.654    0.236    1.135     3588 0.0078 **
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
df <- (subset(s8data.wPredictors, parameter == coc)) %>%
  add_surv()
bayes_plots(mod,coc,df)
The following arguments were unrecognized and ignored: pprob_outer`expand_scale()` is deprecated; use `expansion()` instead.
[[1]]

[[2]]

[[3]]

[[4]]

Cadium

mod <- CdModel
Error: object 'CdModel' not found

Total Phosphorus

coc = 'Total Phosphorus - Water - Total'
TPModel <- mcmc_calc('Total Phosphorus - Water - Total',c('rev_logTraffic'),'log')
mod <- TPModel
summary(mod$Sol)

Iterations = 10001:59999
Thinning interval = 13 
Number of chains = 1 
Sample size per chain = 3847 

1. Empirical mean and standard deviation for each variable,
   plus standard error of the mean:

               Mean    SD Naive SE Time-series SE
(Intercept)    7.23 0.778  0.01255        0.01255
rev_logTraffic 1.71 0.485  0.00782        0.00782

2. Quantiles for each variable:

               2.5%  25%  50%  75% 97.5%
(Intercept)    5.70 6.74 7.21 7.72  8.74
rev_logTraffic 0.77 1.40 1.70 2.02  2.67
summary(mod)

 Iterations = 10001:59999
 Thinning interval  = 13
 Sample size  = 3847 

 DIC: 952 

 G-structure:  ~Location

 R-structure:  ~units

 Location effects: cbind(cenMin_log, cenMax_log) ~ rev_logTraffic 

               post.mean l-95% CI u-95% CI eff.samp   pMCMC    
(Intercept)        7.226    5.704    8.735     3847 <0.0003 ***
rev_logTraffic     1.713    0.833    2.705     3847  0.0031 ** 
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
df <- (subset(s8data.wPredictors, parameter == coc)) %>%
  add_surv()
bayes_plots(mod,coc,df)
The following arguments were unrecognized and ignored: pprob_outer`expand_scale()` is deprecated; use `expansion()` instead.
[[1]]

[[2]]

[[3]]

[[4]]

Total Kjeldahl Nitrogen

coc = 'Total Kjeldahl Nitrogen - Water - Total'
TKNModel<- mcmc_calc('Total Kjeldahl Nitrogen - Water - Total','rev_logTraffic','log') 
mod <- TKNModel
df <- (subset(s8data.wPredictors, parameter == coc)) %>%
  add_surv()
bayes_plots(mod,coc,df)
The following arguments were unrecognized and ignored: pprob_outer`expand_scale()` is deprecated; use `expansion()` instead.
[[1]]

[[2]]

[[3]]

[[4]]

TSS

coc = 'Total Suspended Solids - Water - Total'
TSSModel <- mcmc_calc('Total Suspended Solids - Water - Total','rev_logTraffic','log')
df <- (subset(s8data.wPredictors, parameter == coc)) %>%
  add_surv()
bayes_plots(TSSModel,coc,df)
The following arguments were unrecognized and ignored: pprob_outer`expand_scale()` is deprecated; use `expansion()` instead.
[[1]]

[[2]]

[[3]]

[[4]]

‘Total PAH - Water - Total’,

coc = 'Total PAH - Water - Total'
PAHModel<- mcmc_calc('Total PAH - Water - Total','LU_ratio','log') 
mod <- PAHModel
df <- (subset(s8data.wPredictors, parameter == coc)) %>%
  add_surv()
bayes_plots(mod,coc,df)
The following arguments were unrecognized and ignored: pprob_outer`expand_scale()` is deprecated; use `expansion()` instead.
[[1]]

[[2]]

[[3]]

[[4]]

‘CPAH - Water - Total’

coc = 'CPAH - Water - Total'
CPAHModel<- mcmc_calc('CPAH - Water - Total',c('logPopulation','rev_logTraffic'),'log') 
mod <- CPAHModel
df <- (subset(s8data.wPredictors, parameter == coc)) %>%
  add_surv()
bayes_plots(mod,coc,df)
The following arguments were unrecognized and ignored: pprob_outer`expand_scale()` is deprecated; use `expansion()` instead.
[[1]]

[[2]]

[[3]]

[[4]]

‘HPAH - Water - Total’

coc = 'HPAH - Water - Total'
HPAHModel<- mcmc_calc('HPAH - Water - Total',c('LU_ratio'),'log') 
mod <- HPAHModel
df <- (subset(s8data.wPredictors, parameter == coc)) %>%
  add_surv()
bayes_plots(mod,coc,df)
The following arguments were unrecognized and ignored: pprob_outer`expand_scale()` is deprecated; use `expansion()` instead.
[[1]]

[[2]]

[[3]]

[[4]]

Summary

Summarize posterior results for use in heatmap.

#summarize 
metals <- list()
metals[['Total Copper']] <- (CuModel)
metals[['Total Zinc']] <- (ZnModel)
metals[['Total Cadmium']]<- (CdModel)
metals[['Total Lead']]<- (PbModel)

others <- list() 
others[['Total Phosphorus']] <- TPModel
others[['Total Kjeldahl Nitrogen']] <- TKNModel
others[['Total Suspended Sediment']] <-  TSSModel
#others['FC'] <- FCModel 





msummary(metals,title='Total Metals',statistic_vertical = TRUE,statistic = 'conf.int', conf_level = 0.95)
msummary(others,title='Nutrients and TSS',statistic_vertical = TRUE,statistic = 'conf.int', conf_level = 0.95)

Print out a messy list of results for the heatmap

post.mean l-95% CI u-95% CI eff.samp pMCMC
(Intercept) 5.390 3.572 7.180 3847 0.000
rev_logTraffic 2.197 1.024 3.295 3847 0.001
impervious 0.305 0.059 0.554 4034 0.028
post.mean l-95% CI u-95% CI eff.samp pMCMC
(Intercept) 3.644 3.239 4.07 3847 0
impervious 0.714 0.414 1.01 3847 0
post.mean l-95% CI u-95% CI eff.samp pMCMC
(Intercept) -0.142 -0.297 0.026 3847 0.084
impervious 0.295 0.180 0.405 3847 0.001
logPopulation -0.254 -0.360 -0.144 3847 0.000
post.mean l-95% CI u-95% CI eff.samp pMCMC
(Intercept) 1.193 0.573 1.87 3847 0.003
impervious 0.654 0.236 1.14 3588 0.008
post.mean l-95% CI u-95% CI eff.samp pMCMC
(Intercept) 7.23 5.704 8.73 3847 0.000
rev_logTraffic 1.71 0.833 2.71 3847 0.003
post.mean l-95% CI u-95% CI eff.samp pMCMC
(Intercept) -1.826 -2.369 -1.204 3847 0.000
LU_ratio -0.166 -0.382 0.075 3847 0.146
post.mean l-95% CI u-95% CI eff.samp pMCMC
(Intercept) 13.23 11.537 14.92 3847 0.000
rev_logTraffic 1.97 0.893 3.03 3847 0.002

LS0tDQp0aXRsZTogIldhdGVyc2hlZCBSZWdyZXNzaW9uIC0gUGFydCAyOiBDZW5zb3JlZCBNQ01DIEdlbmVyYWxpemVkIExpbmVhciBNaXhlZCBNb2RlbHMiDQphdXRob3I6ICJDaHJpc3RpYW4gTmlsc2VuIg0Kb3V0cHV0Og0KICBodG1sX2RvY3VtZW50Og0KICAgIHRvYzogeWVzDQogICAgaGlnaGxpZ2h0OiB6ZW5idXJuDQogICAgZGZfcHJpbnQ6IHBhZ2VkDQogIGh0bWxfbm90ZWJvb2s6DQogICAgdG9jOiB5ZXMNCiAgICBoaWdobGlnaHQ6IHplbmJ1cm4NCiAgICBkZl9wcmludDogcGFnZWQNCiAgICBmaWdfY2FwdGlvbjogeWVzDQotLS0NCg0KIyBMaWNlbnNlDQoNCj4gQ29weXJpZ2h0IChjKSAyMDIwIEdlb3N5bnRlYyBDb25zdWx0YW50cywgSW5jLiANCltNb3ppbGxhIFB1YmxpYyBMaWNlbnNlIFZlcnNpb24gMi4wXShodHRwczovL2Nob29zZWFsaWNlbnNlLmNvbS9saWNlbnNlcy9tcGwtMi4wLykNCg0KDQpUaGlzIHNvZnR3YXJlIGlzIHByb3ZpZGVkICJhcyBpcyIsIHdpdGhvdXQgd2FycmFudHkgb2YgYW55IGtpbmQsIGV4cHJlc3Mgb3IgaW1wbGllZCwgaW5jbHVkaW5nIGJ1dCBub3QgbGltaXRlZCB0byB0aGUgd2FycmFudGllcyBvZiBtZXJjaGFudGFiaWxpdHksIGZpdG5lc3MgZm9yIGEgcGFydGljdWxhciBwdXJwb3NlIGFuZCBub25pbmZyaW5nZW1lbnQuIEluIG5vIGV2ZW50IHNoYWxsIHRoZSBhdXRob3JzIG9yIGNvcHlyaWdodCBob2xkZXJzIGJlIGxpYWJsZSBmb3IgYW55IGNsYWltLCBkYW1hZ2VzIG9yIG90aGVyIGxpYWJpbGl0eSwgd2hldGhlciBpbiBhbiBhY3Rpb24gb2YgY29udHJhY3QsIHRvcnQgb3Igb3RoZXJ3aXNlLCBhcmlzaW5nIGZyb20sIG91dCBvZiBvciBpbiBjb25uZWN0aW9uIHdpdGggdGhlIHNvZnR3YXJlIG9yIHRoZSB1c2Ugb3Igb3RoZXIgZGVhbGluZ3MgaW4gdGhlIHNvZnR3YXJlLiAgDQoNCg0KDQoNCiMgRGVzY3JpcHRpb24gDQoNClRoaXMgbm90ZWJvb2sgaXMgdGhlIHNlY29uZCBzdGVwIGluIHRoZSB3YXRlcnNoZWQgcmVncmVzc2lvbiB0YXNrLiBJdCB1c2VzIHRoZSByZWxhdGlvbnNoaXBzIGZyb20gUGFydCAxIChsaW5lYXIgbW9kZWxzKSB0byBydW4gZ2VuZXJhbGl6ZWQgbGluZWFyIG1peGVkIG1vZGVscyB3aXRoIGNlbnNvcmVkIGRlcGVuZGVudCB2YXJpYWJsZXMuIFRoaXMgYXBwcm9hY2ggdXNlcyAgTWFya292IGNoYWluIE1vbnRlIENhcmxvIHRlY2huaXF1ZXMgYW5kIHJlbGllcyBoZWF2aWx5IG9uIHRoZSBgYGBNQ01DZ2xtbWBgYCBwYWNrYWdlIChIYWRmaWVsZCAyMDA5KS4gIA0KDQoNCkhlcmUgYXJlIHRoZSBzdGVwcyBpbiB0aGUgY29kZSBiZWxvdzoNCiAgIA0KMS4gQ29kZSBzZXQgdXAgIA0KMi4gR2V0IGFuZCBwcmVwYXJlIG1vbml0b3JpbmcgZGF0YSAgDQozLiBNZXJnZSBzcGF0aWFsIGRhdGEgd2l0aCBtb25pdG9yaW5nIGRhdGEgIA0KNC4gUmVtb3ZlIG11bGl0Y29saW5lYXIgcHJlZGljdG9ycyAgIA0KNS4gUGVmb3JtIG1vZGVsIHNlbGVjdGlvbiAgIA0KNi4gU2VsZWN0IHRoZSBiZXN0IG1vZGVsICh0aGVzZSByZXN1bHRzIGFyZSB0aGVuIHVzZWQgaW4gdGhlIEJheWVzaWFuIG1vZGVsIGluIHN0ZXAgMikgIA0KICANCg0KDQojIFNldHVwIA0KY2xlYXIgd29ya3NwYWNlIGFuZCBsb2FkIGxpYnJhcmllcy4gKGNvZGUgbm90IHNob3duIGluIGh0bWwgZmlsZSApIA0KDQpgYGB7ciBrbml0cl9pbml0LCBjYWNoZT1GQUxTRSwgaW5jbHVkZT1GQUxTRX0NCnJtKGxpc3QgPSBscygpKQ0KbGlicmFyeShrbml0cikNCmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzDQpsaWJyYXJ5KHJtZGZvcm1hdHMpDQpsaWJyYXJ5KHRpYmJsZSkNCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShnZ3Bsb3QyKQ0KbGlicmFyeShjYXJldCkNCmxpYnJhcnkocHN5Y2gpDQpsaWJyYXJ5KERhdGFFeHBsb3JlcikNCmxpYnJhcnkoZHBseXIpDQpsaWJyYXJ5KGNhcikNCmxpYnJhcnkobG1lNCkNCmxpYnJhcnkobmxtZSkNCmxpYnJhcnkoaHJicnRoZW1lcykNCmxpYnJhcnkoc2pQbG90KQ0KI2xpYnJhcnkoZ2dwbG90Mik7IA0KbGlicmFyeShNQ01DZ2xtbSkNCmxpYnJhcnkocGxvdE1DTUMpDQpsaWJyYXJ5KG1vZGVsc3VtbWFyeSkNCiNsaWJyYXJ5KGxtZWMpDQojbGlicmFyeShNZXRyaWNzKQ0KI2xpYnJhcnkoZ2dzdGF0c3Bsb3QpDQojaW5zdGFsbC5wYWNrYWdlcygnbW9kZWxzdW1tYXJ5JykNCiNsaWJyYXJ5KG1vZGVsc3VtbWFyeSkNCiNsaWJyYXJ5KCdlbW1lYW5zJykNCiNsaWJyYXJ5KCd0aWR5YmF5ZXMnKQ0KdGhlbWVfc2V0KHRoZW1lX2lwc3VtX3JjKCkpDQoNCiNkZXRhY2goInBhY2thZ2U6ZGF0YXNldHMiLCB1bmxvYWQ9VFJVRSkNCiMjIEdsb2JhbCBvcHRpb25zDQpvcHRpb25zKG1heC5wcmludD0iNzUiKQ0Kb3B0c19jaHVuayRzZXQoY2FjaGU9VFJVRSwNCiAgICAgICAgICAgICAgIHByb21wdD1GQUxTRSwNCiAgICAgICAgICAgICAgIHRpZHk9VFJVRSwNCiAgICAgICAgICAgICAgIGNvbW1lbnQ9TkEsDQogICAgICAgICAgICAgICBtZXNzYWdlPUZBTFNFLA0KICAgICAgICAgICAgICAgd2FybmluZz1GQUxTRSkNCm9wdHNfa25pdCRzZXQod2lkdGg9NzUpDQoNCnNldC5zZWVkKDUwKQ0KYGBgDQpgYGB7cn0NCg0KI2xpYnJhcnkoInJlbnYiKQ0KbGlicmFyeShjYXJldCkNCmxpYnJhcnkocmVhZHIpDQpsaWJyYXJ5KGRwbHlyKQ0KIA0KYGBgDQoNCg0KYGBge3J9DQpzZXR3ZCgifi9yZXBvcy9zdG9ybXdhdGVyaGVhdG1hcC9SLXNjcmlwdHMvV2F0ZXJzaGVkUmVncmVzc2lvbiIpDQoNCg0KDQpgYGANCg0KDQoNCiMgR2V0IGFuZCBwcmVwYXJlIG1vbml0b3JpbmcgZGF0YQ0KDQpUaGUgc3Rvcm13YXRlciBvdXRmYWxsIGRhdGEgaXMgYXZhaWxhYmxlIGZyb20gdGhlIERlcGFydG1lbnQgb2YgRWNvbG9neSBhdCBodHRwczovL2RhdGEud2EuZ292L05hdHVyYWwtUmVzb3VyY2VzLUVudmlyb25tZW50L011bmljaXBhbC1TdG9ybXdhdGVyLVBlcm1pdC1PdXRmYWxsLURhdGEvZDk1OC1xMmNpLg0KDQpBIC5jc3YgZmlsZSBpcyBzYXZlZCBpbiBgYGBXYXRlcnNoZWRSZWdyZXNzaW9uL2RhdGEvUzhfZGF0YS5jc3ZgYGAgDQoNCmBgYHtyIGVjaG89VFJVRX0NCmFsbC5TOC5kYXRhIDwtIHJlYWRfY3N2KCJkYXRhL1M4X2RhdGEuY3N2IiwgDQogICAgY29sX3R5cGVzID0gY29scyhmaWVsZF9jb2xsZWN0aW9uX2VuZF9kYXRlID0gY29sX2RhdGUoZm9ybWF0ID0gIiVtLyVkLyVZIiksIA0KICAgICAgICBmaWVsZF9jb2xsZWN0aW9uX3N0YXJ0X2RhdGUgPSBjb2xfZGF0ZShmb3JtYXQgPSAiJW0vJWQvJVkiKSkgKQ0KDQojZmlsdGVyIG91dCByZWplY3RlZCBkYXRhDQphbGwuUzguZGF0YSA8LSAoZmlsdGVyKGFsbC5TOC5kYXRhLCFyZXN1bHRfZGF0YV9xdWFsaWZpZXIgJWluJSAnUkVKJykpDQoNCiNmaWx0ZXIgb3V0IHJlcGxpY2F0ZXMgDQphbGwuUzguZGF0YSA8LSAoZmlsdGVyKGFsbC5TOC5kYXRhLCFzYW1wbGVfcmVwbGljYXRlX2ZsYWcgJWluJSAnWScpKQ0KDQojY2hhbmdlIG5vbmRldGVjdCB3YXJuaW5ncyB0byBkZXRlY3RzDQp3YXJuaW5ncyA8LSBhbGwuUzguZGF0YSRub25kZXRlY3RfZmxhZyA9PSAiV0FSTklORyINCmFsbC5TOC5kYXRhJG5vbmRldGVjdF9mbGFnW3dhcm5pbmdzXSA8LSBGQUxTRSANCg0KI0NoYW5nZSBOQSB0byBkZXRlY3QNCmFsbC5TOC5kYXRhJG5vbmRldGVjdF9mbGFnW2lzLm5hKGFsbC5TOC5kYXRhJG5vbmRldGVjdF9mbGFnKV0gPC0gRkFMU0UNCg0KI0NoYW5nZSBzZWFzb24gdG8gZmFjdG9yIA0KYWxsLlM4LmRhdGEkc2Vhc29uIDwtIGFzLmZhY3RvcihhbGwuUzguZGF0YSRzZWFzb24pDQoNCg0KYGBgDQoNClRoZSBjaHVuayBiZWxvdyBtYWtlcyBhIGxpc3Qgb2YgcGFyYW1ldGVycyB3ZSBhcmUgaW50ZXJlc3RlZCBpbi4gDQoNCmBgYHtyIFNlbGVjdCBQYXJhbWV0ZXJzOiB9DQojU2VsZWN0IFBhcmFtZXRlcnMNCnBhcmFtcyA8LSBjKCdaaW5jIC0gV2F0ZXIgLSBUb3RhbCcsDQogJ0NvcHBlciAtIFdhdGVyIC0gVG90YWwnLA0KICdOaXRyaXRlLU5pdHJhdGUgLSBXYXRlciAtIERpc3NvbHZlZCcsDQogJ0xlYWQgLSBXYXRlciAtIFRvdGFsJywNCiAnVG90YWwgUGhvc3Bob3J1cyAtIFdhdGVyIC0gVG90YWwnLA0KICdUb3RhbCBTdXNwZW5kZWQgU29saWRzIC0gV2F0ZXIgLSBUb3RhbCcsDQogJ1RvdGFsIFBodGhhbGF0ZSAtIFdhdGVyIC0gVG90YWwnLA0KJ1RvdGFsIFBBSCAtIFdhdGVyIC0gVG90YWwnLA0KIydDaHJ5c2VuZSAtIFdhdGVyIC0gVG90YWwnLA0KJ0NQQUggLSBXYXRlciAtIFRvdGFsJywNCidIUEFIIC0gV2F0ZXIgLSBUb3RhbCcgDQojJ1RvdGFsIEtqZWxkYWhsIE5pdHJvZ2VuIC0gV2F0ZXIgLSBUb3RhbCcsDQojJ1RvdGFsIFBDQiAtIFdhdGVyIC0gVG90YWwnDQopDQoNCiNzYXZlIGEgbGlzdCBvZiBhbGwgdGhlIHBhcmFtZXRlcnMgaW4gY2FzZSB3ZSB3YW50IHRvIHVzZSBtb3IuIA0KcGFyYW1zLmFsbCA8LSBkYXRhLmZyYW1lKHVuaXF1ZShhbGwuUzguZGF0YSRwYXJhbWV0ZXIpKQ0KczhkYXRhIDwtIGFsbC5TOC5kYXRhIA0KIw0KYGBgDQoNCkNsZWFuIHVwIGV4dHJhY3RlZCBkYXRhIGFuZCByZW5hbWUgY29sdW1uczogDQoNCmBgYHtyIH0NCnM4ZGF0YSA8LSBhbGwuUzguZGF0YSAlPiUgDQoNCiAgZHBseXI6OnNlbGVjdCgNCiAgICBzdHVkeV9uYW1lLA0KICAgIGxvY2F0aW9uX2lkLHBhcmFtZXRlciwNCiAgICB0eXBlLA0KICAgIHNlYXNvbiwNCiAgICBuZXdfcmVzdWx0X3ZhbHVlLA0KICAgIG5vbmRldGVjdF9mbGFnLA0KICAgIHN0dWR5X2lkLA0KICAgIGFjY2Vzc19pZCwNCiAgICBmaWVsZF9jb2xsZWN0aW9uX2VuZF9kYXRlLA0KICAgIGZpZWxkX2NvbGxlY3Rpb25fc3RhcnRfZGF0ZSwNCiAgICB0eXBlKQ0KDQoNCiNyZW5hbWUgc29tZSBjb2x1bW5zDQpjb2xuYW1lcyhzOGRhdGEpW2NvbG5hbWVzKHM4ZGF0YSkgPT0gImxvY2F0aW9uX2lkIl0gPC0gIkxvY2F0aW9uIg0KY29sbmFtZXMoczhkYXRhKVtjb2xuYW1lcyhzOGRhdGEpID09ICJuZXdfcmVzdWx0X3ZhbHVlIl0gPC0NCiAgImNvbmNlbnRyYXRpb24iDQpzOGRhdGEkbm9uZGV0ZWN0X2ZsYWcgPC0gYXMubG9naWNhbChzOGRhdGEkbm9uZGV0ZWN0X2ZsYWcpDQpzOGRhdGEkY29uY2VudHJhdGlvbiA8LSBhcy5udW1lcmljKHM4ZGF0YSRjb25jZW50cmF0aW9uKQ0KDQpgYGANCg0KIyMgQ2hlY2sgZm9yIG91dGxpZXJzDQoNClNldCB1cCBhIGppdHRlciBwbG90IG9mIGFsbCB0aGUgZGF0YSB0byBsb29rIGZvciBvdXRsaWVyczogDQogIA0KYGBge3IgZmlnLmhlaWdodD04fQ0KDQpzY2F0dGVyX2NvY3MgPC0gZnVuY3Rpb24oZGYuY29jLHRpdGxlKSB7DQogcCA8LSBnZ3Bsb3QoZGYuY29jLCBhZXMoMSwgY29uY2VudHJhdGlvbikpICsgZ2VvbV9qaXR0ZXIoKSArIGxhYnMoDQogIHRpdGxlID0gdGl0bGUsDQogIHN1YnRpdGxlID0gIkRhdGEgY29sbGVjdGVkIDIwMDktMjAxMyIsDQogIGNhcHRpb24gPQ0KICAgICIgRGF0YSBzb3VyY2U6IEVjb2xvZ3ksIDIwMTUiLA0KICB4ID0gIk9ic2VydmF0aW9ucyINCiAgI3kgPSB5LnRpdGxlDQopICANCnAgKyBmYWNldF93cmFwKCB+IHBhcmFtZXRlciwgbmNvbD0zLCBzY2FsZXMgPSAnZnJlZScpIA0KfQ0KDQpzY2F0dGVyX2NvY3MoczhkYXRhW3doaWNoKHM4ZGF0YSRwYXJhbWV0ZXIgJWluJSBwYXJhbXMpLF0sJ0FsbCBPYnNlcnZhdGlvbnMnKQ0KYGBgDQogICANCm91dGxpZXJzIGFyZSBhcHByZW50IGZvciBUU1MsIFRQLCBhbmQgbm8yLW5vMy4gUmVtb3ZlIHRoZXNlLiANCmBgYHtyIHJlbW92ZSBvdXRsaWVycywgZmlnLmhlaWdodD04fQ0KI3JlbW92ZSBhbmQgcmVwbG90IA0KDQpvdXRsaWVyUGFyYW1zIDwtIGMoIlRvdGFsIFN1c3BlbmRlZCBTb2xpZHMgLSBXYXRlciAtIFRvdGFsIiwgIlRvdGFsIFBob3NwaG9ydXMgLSBXYXRlciAtIFRvdGFsIiwgIk5pdHJpdGUtTml0cmF0ZSAtIFdhdGVyIC0gRGlzc29sdmVkIikNCg0KI1RoaXMgcmVtb3ZlcyB0aGUgaGlnaGVzdCB2YWx1ZXMgDQpvdXRsaWVyVmFscyA8LQ0KICB0b3Bfbihncm91cF9ieShzOGRhdGFbd2hpY2goczhkYXRhJHBhcmFtZXRlciAlaW4lIG91dGxpZXJQYXJhbXMpLCBdLCBwYXJhbWV0ZXIpLCAxLCBjb25jZW50cmF0aW9uKSRjb25jZW50cmF0aW9uDQoNCnM4ZGF0YSA8LSBzOGRhdGEgJT4lDQogIGdyb3VwX2J5KHBhcmFtZXRlcikgJT4lDQogIHNsaWNlKHdoaWNoKCEoDQogICAgcGFyYW1ldGVyICVpbiUgb3V0bGllclBhcmFtcyAmIGNvbmNlbnRyYXRpb24gJWluJSBvdXRsaWVyVmFscw0KICApKSkNCg0Kc2NhdHRlcl9jb2NzKHM4ZGF0YVt3aGljaChzOGRhdGEkcGFyYW1ldGVyICVpbiUgcGFyYW1zKSxdLCdBbGwgT2JzZXJ2YXRpb25zIC0gT3V0bGllcnMgUmVtb3ZlZCcpDQoNCmBgYA0KICAgDQpMb29rcyBiZXR0ZXIuIE1vdmUgb24gdG8gdGhlIG5leHQgQ2h1bmsuIA0KDQojIEdldCBTcGF0aWFsIGRhdGEgYW5kIG1lcmdlICANCg0KYGBge3J9DQoNCiNTcGF0aWFsIHByZWRjaXRvcnMgaGF2ZSBiZWVuIGV4dHJhY3RlZCBhbmQgc2F2ZWQgYXMgYSBjc3YgZmlsZS4gDQoNCnNwYXRpYWxfZGF0YSA8LSByZWFkX2NzdigiQzovVXNlcnMvY25pbHNlbi9Hb29nbGUgRHJpdmUvcmVwb3Mvc3BhdGlhbFByZWRpY3RvcnMuY3N2IikNCiNSRVMgYW5kIENPTSBhcmUgY29tcG9zaXRpb25hbCBkYXRhLiBDaGFuZ2UgdG8gYSByYXRpbw0Kc3BhdGlhbF9kYXRhJExVX3JhdGlvID0gc3BhdGlhbF9kYXRhJENPTS9zcGF0aWFsX2RhdGEkUkVTIA0Kc3BhdGlhbF9kYXRhIDwtIGRwbHlyOjpzZWxlY3Qoc3BhdGlhbF9kYXRhLCAtYyhSRVMsQ09NLC5nZW8pKQ0KI21lcmdlIHNwYXRpYWwgcHJlZGljdG9ycyB3aXRoIG1vbml0b3JpbmcgZGF0YSANCnM4ZGF0YS53UHJlZGljdG9ycyA8PC0gbWVyZ2UoczhkYXRhLCBzcGF0aWFsX2RhdGEpJT4lIA0KICBkcGx5cjo6c2VsZWN0KC1jKGRlcFNwbHVzTikpDQoNCiMgc3BhdGlhbF9kYXRhPC1yZWFkX2NzdigiZGF0YS9zcGF0aWFsUHJlZGljdG9yczQyLmNzdiIsIGNvbF90eXBlcyA9IGNvbHMoWDEgPSBjb2xfc2tpcCgpKSkNCiMgDQojICNtZXJnZSBzcGF0aWFsIHByZWRpY3RvcnMgd2l0aCBtb25pdG9yaW5nIGRhdGEgDQojIHM4ZGF0YS53UHJlZGljdG9ycyA8LSBtZXJnZShzOGRhdGEsIHNwYXRpYWxfZGF0YSkNCg0KYGBgDQoNCg0KIyBQZXJmb3JtIE1DTUMgbW9kZWxpbmcgDQojIyBGdW5jdGlvbnMgDQpTb21lIGhlbHBlciBmdW5jdGlvbnMgdG8gaGVscCANCg0KIyMjIyMgRnVuY3Rpb24gdG8gYWRkIGEgc3Vydml2YWwgb2JqZWN0IHRvIHRoZSBTOCBkYXRhZnJhbWUgDQpgYGB7cn0NCmFkZF9zdXJ2IDwtIGZ1bmN0aW9uKGRmKSB7DQogIGRmJGNlbk1pbiA8LSBpZmVsc2UoZGYkbm9uZGV0ZWN0X2ZsYWcsLUluZiwgKGRmJGNvbmNlbnRyYXRpb24pKQ0KICBkZiRjZW5NYXggIDwtIChkZiRjb25jZW50cmF0aW9uKQ0KICBkZiRjZW5NaW5fbG9nIDwtIGlmZWxzZShkZiRub25kZXRlY3RfZmxhZywtSW5mLCBsb2coZGYkY29uY2VudHJhdGlvbikpDQogIGRmJGNlbk1heF9sb2cgIDwtIGxvZyhkZiRjb25jZW50cmF0aW9uKQ0KICANCiAgcmV0dXJuKGRmKQ0KfQ0KYGBgDQojIyMjIyBGdW5jdGlvbiB0byByZXR1cm4gYSBjaGFydCBvZiBwcmVkaWN0aW9ucyBmcm9tIHRoZSBtb2RlbCANCmBgYHtyfQ0Kc2NhdHRlcl9wcmVkaWN0IDwtIGZ1bmN0aW9uKG1vZGVsX2RmLHByZWRpY3Rpb25zKSB7DQojIG1vZGVsX3RvX3ByZWRpY3QgPC0gQ3VNb2RlbCANCiMgY29jID0gcGFyYW1zWzJdDQojIGRmIDwtIChzdWJzZXQoczhkYXRhLndQcmVkaWN0b3JzLCBwYXJhbWV0ZXIgPT0gY29jKSkgJT4lDQojICAgYWRkX3N1cnYoKQ0KIyBwcmVkaWN0aW9ucyA8LSBwcmVkaWN0KG1vZGVsX3RvX3ByZWRpY3QsIG5ld2RhdGE9ZGYsIA0KIyAgICAgICAgICB0eXBlPSJyZXNwb25zZSIsIGludGVydmFsPSJub25lIiwgbGV2ZWw9MC45NSwgaXQ9TlVMTCwgDQojICAgICAgICAgIHBvc3Rlcmlvcj0iYWxsIiwgdmVyYm9zZT1GQUxTRSwgYXBwcm94PSJudW1lcmljYWwiKQ0KIyANCiBvYnMgPC0gbG9nKG1vZGVsX2RmJGNvbmNlbnRyYXRpb24pDQogDQogZ2dzdGF0c3Bsb3Q6Omdnc2NhdHRlcnN0YXRzKA0KICBkYXRhID10aWJibGUocCA9IHByZWRpY3Rpb25zLCBvYnMgPSBvYnMsTCA9IG1vZGVsX2RmJExvY2F0aW9uKSwNCiAgeCA9IHAsDQogIHkgPSBvYnMsDQogIHR5cGUgPSAiYmYiLA0KICBwb2ludC53aWR0aC5qaXR0ZXIgPSAwLjAyLA0KICAjcG9pbnQuaGVpZ2h0LmppdHRlciA9IDAuMSwNCiAgbWFyZ2luYWwgPSBGQUxTRSwNCiAgeGxhYiA9ICJQcmVkaWN0ZWQgbG9nKMK1Zy9MKSIsDQogIHlsYWIgPSAiT2JzZXJ2ZWQgIGxvZyjCtWcvTCkiLA0KICB0aXRsZSA9IGNvYywNCiAgcmVzdWx0cy5zdWJ0aXRsZSA9IEZBTFNFLA0KICBzdWJ0aXRsZSA9ICJQcmVkaWN0aW9ucyB2cy4gT2JzZXJ2YXRpb25zIiwNCiAgc21vb3RoLmxpbmUuYXJncyA9IGxpc3Qoc2l6ZSA9IDEsIGNvbG9yID0gImJsdWUiKSwNCiAgbWVzc2FnZXMgPSBGQUxTRQ0KKQ0KfQ0KDQpgYGANCg0KIyMjIyMgYWRkIHRpZHkgYW5kIGdsYW5jZSBmdW5jdGlvbnMgdG8gdGhlIG1jbWNnbG1tIG9iamVjdHMgc2luY2UgdGhleSBkb24ndCBwbGF5IG5pY2VseSB3aXRoIHRpZHl2ZXJzZSBvYmplY3RzLiANCmBgYHtyfQ0KIyBhZGQgY3VzdG9tIGZ1bmN0aW9ucyB0byBleHRyYWN0IGVzdGltYXRlcyAodGlkeSkgYW5kIGdvb2RuZXNzLW9mLWZpdCAoZ2xhbmNlKSBpbmZvcm1hdGlvbg0KdGlkeS5NQ01DZ2xtbSA8LSBmdW5jdGlvbihvYmplY3QsIC4uLikgew0KICAgIHMgPC0gc3VtbWFyeShvYmplY3QsIC4uLikNCiAgICByZXQgPC0gdGliYmxlOjp0aWJibGUodGVybSA9IHJvdy5uYW1lcyhzJHNvbHV0aW9ucyksDQogICAgICAgICAgICAgICAgICAgICAgICAgIGVzdGltYXRlID0gcyRzb2x1dGlvbnNbLCAxXSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgY29uZi5sb3cgPSBzJHNvbHV0aW9uc1ssIDJdLA0KICAgICAgICAgICAgICAgICAgICAgICAgICBjb25mLmhpZ2ggPSBzJHNvbHV0aW9uc1ssIDNdKQ0KICAgIHJldA0KfQ0KZ2xhbmNlLk1DTUNnbG1tIDwtIGZ1bmN0aW9uKG9iamVjdCwgLi4uKSB7DQogICAgcmV0IDwtIHRpYmJsZTo6dGliYmxlKGRpYyA9IG9iamVjdCRESUMsDQogICAgICAgICAgICAgICAgICAgICAgICAgIG4gPSBucm93KG9iamVjdCRYKSkNCiAgICByZXQNCn0NCg0KIyBlc3RpbWF0ZSBhIHNpbXBsZSBtb2RlbA0KI21vZGVsIDwtIE1DTUNnbG1tKFBPIH4gMSArIHBsYXRlLCByYW5kb20gPSB+IEZTZmFtaWx5LCBkYXRhID0gUGxvZGlhUE8sIHZlcmJvc2U9RkFMU0UsIHByPVRSVUUpDQpgYGANCg0KIyMgbWNtY19jYWxjIGZ1bmN0aW9uICANCg0KVGhpcyBpcyB0aGUgbWFpbiBmdW5jaXRvbiB0byBydW4gdGhlIG1jbWMgbW9kZWwuIEl0IGRvZXMgdGhlIGZvbGxvd2luZzogICANCjEuIHN1YnNldHMgYSBkYXRhZnJhbWUgdG8gaW5jbHVkZSBvbmx5IHRoZSBwYXJhbWV0ZXIgd2Ugd2FudCB0byBwcmVkaWN0ICANCjIuIGFkZHMgYSBzdXJ2aXZhbCBvYmplY3QgdG8gaGFuZGxlIGNlbnNvcmVkIGRhdGEgIA0KMy4gc2V0cyB1cCBhIHNpbWlwbGUgcHJpb3Igc3RydWN0dXJlICANCjQuIHBlcmZvcm1zIG1jbWMgbW9kZWxpbmcgb24gZWl0aGVyIHRoZSBsb2ctdHJhbnNmb3JtZWQgb3Igbm9uLWxvZyB0cmFuc2Zvcm1lZCByZXNwb25zZXMuICAgDQo1LiByZXR1cm5zIHRoZSByZXN1bHRzICAgDQoNCmBgYHtyfQ0KbWNtY19jYWxjIDwtIGZ1bmN0aW9uKGNvYy5sb2NhbCwgZml4ZWRfbGlzdCwgbGhzKSB7DQogIA0KICBkZiA8LSAoc3Vic2V0KHM4ZGF0YS53UHJlZGljdG9ycywgcGFyYW1ldGVyID09IGNvYy5sb2NhbCkpDQoNCiAgIGRhdGEgPC0NCiAgZGYgJT4lDQogIGFkZF9zdXJ2KCkNCiNtYWtlIHRoZSBwcmlvcl9zdHJ1Y3R1cmVzIA0KcHJpb3IuMTwtbGlzdChSPWxpc3QoVj0xLCBmaXg9MSksIEc9bGlzdChHMT1saXN0KFY9MSwgbnU9MC4wMDIpKSkNCnByaW9yLjI8LWxpc3QoUj1saXN0KFY9MiwgZml4PTEpLCBHPWxpc3QoRzE9bGlzdChWPTEsIG51PTApKSkNCg0KICANCiAgaWYgKGxocyA9PSAnbG9nJykgew0KICAgIG1jbWNfZm9ybXVsYSA8LQ0KICAgICAgYXMuZm9ybXVsYShwYXN0ZSgNCiAgICAgICAgImNiaW5kKGNlbk1pbl9sb2csIGNlbk1heF9sb2cpIH4gIiwNCiAgICAgICAgcGFzdGUwKGZpeGVkX2xpc3QgLCBjb2xsYXBzZSA9ICIrIikNCiAgICAgICkpDQogIH0NCiAgZWxzZSB7DQogICAgbWNtY19mb3JtdWxhIDwtDQogICAgICBhcy5mb3JtdWxhKHBhc3RlKA0KICAgICAgICAiY2JpbmQoY2VuTWluLCBjZW5NYXgpIH4gIiwNCiAgICAgICAgcGFzdGUwKGZpeGVkX2xpc3QgLCBjb2xsYXBzZSA9ICIrIikNCiAgICAgICkpDQogIH0NCiAgDQogIG1jbWNfcmVzdWx0cyA8LQ0KICAgIE1DTUNnbG1tKA0KICAgICAgbWNtY19mb3JtdWxhLA0KICAgICAgcmFuZG9tID0gfiBMb2NhdGlvbiwNCiAgICAgIGRhdGEgPSAgZGF0YSwNCiAgICAgIGZhbWlseSA9ICJjZW5nYXVzc2lhbiIsIA0KICAgICAgdmVyYm9zZSA9IEZBTFNFLCBwcmlvciA9IHByaW9yLjEsIHNpbmd1bGFyLm9rID0gVFJVRSwNCiAgICAgIG5pdHQgPSA2MDAwMCwgdGhpbiA9IDEzLCBidXJuaW4gPSAxMDAwMA0KICAgICkNCiAgICByZXR1cm4oKG1jbWNfcmVzdWx0cykpDQp9DQpgYGANCg0KDQoNCiMjIyMjIGZ1bmN0aW9uIHRoYXQgcmV0dXJucyBiYXllc2lhbiBwbG90cw0KYGBge3J9DQojIERvIHNvbWUgcHJlZGljdGl2ZSBjaGVja3MgDQpsaWJyYXJ5KGJheWVzcGxvdCkNCmNvbG9yX3NjaGVtZV9zZXQoImJsdWUiKQ0KYmF5ZXNfcGxvdHMgPC0gZnVuY3Rpb24oZml0LGNvYyxkZikgew0KI2ZpdCA8LSBUU1NNb2RlbA0KDQojY29jID0gJ1RvdGFsIFN1c3BlbmRlZCBTb2xpZHMgLSBXYXRlciAtIFRvdGFsJw0KDQojZGYgPC0gKHN1YnNldChzOGRhdGEud1ByZWRpY3RvcnMsIHBhcmFtZXRlciA9PSBjb2MpKSAlPiUNCiAjIGFkZF9zdXJ2KCkNCnlyZXBfYyA8LSBwcmVkaWN0KGZpdCwgbmV3ZGF0YT1kZiwgDQogICAgICAgICB0eXBlPSJyZXNwb25zZSIsIGludGVydmFsPSJjb25maWRlbmNlIiwgbGV2ZWw9MC45LCBpdD1OVUxMLCANCiAgICAgICAgIHBvc3Rlcmlvcj0iYWxsIiwgdmVyYm9zZT1GQUxTRSwgYXBwcm94PSJudW1lcmljYWwiKQ0KeXJlcF9wIDwtIHByZWRpY3QoZml0LCBuZXdkYXRhPWRmLCANCiAgICAgICAgIHR5cGU9InJlc3BvbnNlIiwgaW50ZXJ2YWw9InByZWRpY3Rpb24iLCBsZXZlbD0wLjksIGl0PU5VTEwsIA0KICAgICAgICAgcG9zdGVyaW9yPSJhbGwiLCB2ZXJib3NlPUZBTFNFLCBhcHByb3g9Im51bWVyaWNhbCIpDQoNCiNzaG93IHVuY2VydGFpbnR5IGludGVydmFscyB1bmRlciBlc2ltYXRlZCBwb3N0ZXJpb3IgZGVuc2l0eSBjdXJ2ZXMgDQpwbG90LjEgPC0gbWNtY19hcmVhcyhmaXQkU29sLHByb2IgPSAwLjgwLCBwcHJvYl9vdXRlciA9IDAuOTUscG9pbnRfZXN0PSJtZWFuIikrZ2dwbG90Mjo6bGFicyh0aXRsZSA9IGNvYywgc3VidGl0bGUgICA9ICJQb3N0ZXJpb3IgZGlzdHJpYnV0aW9ucyB3aXRoIG1lZGlhbnMgYW5kIDgwJSBpbnRlcnZhbHMiKQ0KDQojZ2VuZXJhdGUgc2NhdHRlciBwbG90IG9mIHByZWRpY3Rpb25zIA0KDQpjb2xuYW1lcyh5cmVwX3ApIDwtICBjKCJmaXQucCIsICJsd3IucCIsICJ1cHIucCIpDQpzY2F0dGVyZGF0YSA8LSBjYmluZChkZiwgeXJlcF9jLCB5cmVwX3ApDQoNCiNnZW5lcmF0ZSBzY2F0dGVyIHBsb3Qgb2YgcHJlZGljdGlvbnMgDQpwbG90LjIgPC0gZ2dwbG90KHNjYXR0ZXJkYXRhKSArIA0KICBnZW9tX3JpYmJvbihhZXMoeW1pbiA9IGx3ci5wLCB5bWF4ID0gdXByLnAsIHggPSBmaXQpLGZpbGw9ImdyZXkiLCBhbHBoYSA9IDAuNSkgKyANCiAgZ2VvbV9yaWJib24oYWVzKHltaW4gPSBsd3IsIHltYXggPSB1cHIsIHggPSBmaXQpLCBmaWxsID0gImdyZXkiLCBhbHBoYSA9IDAuOCkgKyANCiAgZ2VvbV9saW5lKGFlcyh4PWZpdCx5PWZpdCksY29sb3I9ImJsdWUiLGxpbmV0eXBlPTUpKw0KICBnZW9tX3BvaW50KGFlcyh4ID0gZml0LCB5ID0gbG9nKGNvbmNlbnRyYXRpb24pKSwgYWxwaGEgPSAwLjUpKw0KICBnZ3Bsb3QyOjpsYWJzKHg9InlyZXAiLHk9ImZpdCIsdGl0bGUgPSBjb2MsIHN1YnRpdGxlICAgPSAiU2NhdHRlciBwbG90IG9mIG9ic2VydmVkIGRhdGEgdnMgc2ltdWxhdGVkIixjYXB0aW9uPSJkYXJrIHNoYWRlOiBjb25maWRlbmNlIGludGVydmFscyBcbiBsaWdodCBzaGFkZTogcHJlZGljdGlvbiBpbnRlcnZhbHMiKQ0KDQoNCiNzaW11bGF0ZSB3aXRoIDEwMCBkcmF3cyAgDQp5c2ltIDwtIChzaW11bGF0ZShmaXQsbnNpbSA9IDEwMCkpDQoNCg0KI292ZXJsYXkgb2YgcHJlZGljdGlvbnMgDQpwbG90LjM8LSBwcGNfZGVuc19vdmVybGF5KGxvZyhkZiRjb25jZW50cmF0aW9uKSx0KHlzaW0pKStnZ3Bsb3QyOjpsYWJzKHg9ImxvZyBjb25jZW50cmF0aW9uLCDOvGcvTCAiLHRpdGxlID0gY29jLCBzdWJ0aXRsZSAgID0gIk9ic2VydmVkICh5KSB2cy4gc2ltdWxhdGVkIGRyYXdzICh5cmVwKSIpDQoNCg0KcmV0dXJuKGxpc3QocGxvdC4xLHBsb3QuMixwbG90LjMpKQ0KfQ0KYGBgDQoNCmBgYHtyfQ0KI2Z1bmN0aW9uIGZvciBvdGhlciBkaWFnbm9zdGljIHBsb3RzIA0KZGlhZ25vc3RpY19wbG90cyA8LSBmdW5jdGlvbihjaGFpbnMsY29jKSB7DQogIHBsb3RUcmFjZShjaGFpbnMsYXhlcz1UUlVFLHNhbWUubGltaXRzPVRSVUUpDQogIHBsb3REZW5zKGNoYWlucyxtYWluPXBhc3RlKCdQb3N0ZXJpb3IgRGlzdHJpYnV0aW9ucyBcbicsY29jKSxwcm9icz1jKDAuMDUwLDAuOTUwKSxzYW1lLmxpbWl0cz1GQUxTRSl9DQpgYGANCg0KDQojIFJ1biBtb2RlbCANCg0KRm9yIGVhY2ggY2h1bmsgYmVsb3csIHdlIHJ1biB0aGUgbW9kZWwgYW5kIG91dHB1dCBkaWFnbm9zdGljIGFuZCBwcmVkaWN0aW9uIHBsb3RzLiANCg0KIyMgWmluYyAgIA0KYGBge3IgZmlnLmhlaWdodD03fQ0KIyANCmNvYyA8LSBwYXJhbXNbMV0NClpuTW9kZWwgPC0gbWNtY19jYWxjKGNvYyxjKCdpbXBlcnZpb3VzJyksJ2xvZycpDQptb2QgPC0gWm5Nb2RlbA0Kc3VtbWFyeShtb2QpDQpkZiA8LSAoc3Vic2V0KHM4ZGF0YS53UHJlZGljdG9ycywgcGFyYW1ldGVyID09IGNvYykpICU+JQ0KICBhZGRfc3VydigpDQpiYXllc19wbG90cyhtb2QsY29jLGRmKQ0KDQoNCmBgYA0KDQojIyBDb3BwZXIgICAgDQoNCmBgYHtyIGZpZy5oZWlnaHQ9N30NCmNvYyA9ICdDb3BwZXIgLSBXYXRlciAtIFRvdGFsJw0KQ3VNb2RlbCA8LSBtY21jX2NhbGMoY29jLGMoDQoncmV2X2xvZ1RyYWZmaWMnLCdpbXBlcnZpb3VzJyksJ2xvZycpDQptb2QgPC0gQ3VNb2RlbA0Kc3VtbWFyeShtb2QkU29sKQ0Kc3VtbWFyeShtb2QpDQpkZiA8LSAoc3Vic2V0KHM4ZGF0YS53UHJlZGljdG9ycywgcGFyYW1ldGVyID09IGNvYykpICU+JQ0KICBhZGRfc3VydigpDQpiYXllc19wbG90cyhtb2QsY29jLGRmKQ0KDQpgYGANCg0KDQoNCiMjIE5pdHJpdGUtTml0cmF0ZQ0KDQpgYGB7cn0NCmNvYyA9ICdOaXRyaXRlLU5pdHJhdGUgLSBXYXRlciAtIERpc3NvbHZlZCcNCk5OX21vZGVsIDwtIG1jbWNfY2FsYyhjb2MsYygNCidMVV9yYXRpbycpLCdsb2cnKQ0KbW9kIDwtIE5OX21vZGVsDQpzdW1tYXJ5KG1vZCRTb2wpDQpzdW1tYXJ5KG1vZCkNCmRmIDwtIChzdWJzZXQoczhkYXRhLndQcmVkaWN0b3JzLCBwYXJhbWV0ZXIgPT0gY29jKSkgJT4lDQogIGFkZF9zdXJ2KCkNCmJheWVzX3Bsb3RzKG1vZCxjb2MsZGYpDQoNCmBgYA0KDQojIyBMZWFkICAgDQoNCmBgYHtyIGZpZy5oZWlnaHQ9N30NCmNvYyA8LSBwYXJhbXNbNF0NClBiTW9kZWwgPC0gbWNtY19jYWxjKHBhcmFtc1s0XSxjKCdpbXBlcnZpb3VzJyksJ2xvZycpDQoNCm1vZCA8LSBQYk1vZGVsDQpzdW1tYXJ5KG1vZCRTb2wpDQpzdW1tYXJ5KG1vZCkNCg0KZGYgPC0gKHN1YnNldChzOGRhdGEud1ByZWRpY3RvcnMsIHBhcmFtZXRlciA9PSBjb2MpKSAlPiUNCiAgYWRkX3N1cnYoKQ0KYmF5ZXNfcGxvdHMobW9kLGNvYyxkZikNCg0KDQpgYGANCiMjIENhZGl1bSAgIA0KDQpgYGB7ciBmaWcuaGVpZ2h0PTd9DQpjb2MgPC0gJ0NhZG1pdW0gLSBXYXRlciAtIFRvdGFsJw0KQ2RNb2RlbCA8LSBtY21jX2NhbGMoJ0NhZG1pdW0gLSBXYXRlciAtIFRvdGFsJyxjKCdpbXBlcnZpb3VzJywnbG9nUG9wdWxhdGlvbicpLCdub25sb2cnKQ0KbW9kIDwtIENkTW9kZWwNCnN1bW1hcnkobW9kJFNvbCkNCnN1bW1hcnkobW9kKQ0KDQpkZiA8LSAoc3Vic2V0KHM4ZGF0YS53UHJlZGljdG9ycywgcGFyYW1ldGVyID09IGNvYykpICU+JQ0KICBhZGRfc3VydigpDQpiYXllc19wbG90cyhtb2QsY29jLGRmKQ0KDQpgYGANCg0KIyMgVG90YWwgUGhvc3Bob3J1cyAgDQoNCmBgYHtyIGZpZy5oZWlnaHQ9N30NCmNvYyA9ICdUb3RhbCBQaG9zcGhvcnVzIC0gV2F0ZXIgLSBUb3RhbCcNClRQTW9kZWwgPC0gbWNtY19jYWxjKCdUb3RhbCBQaG9zcGhvcnVzIC0gV2F0ZXIgLSBUb3RhbCcsYygncmV2X2xvZ1RyYWZmaWMnKSwnbG9nJykNCm1vZCA8LSBUUE1vZGVsDQpzdW1tYXJ5KG1vZCRTb2wpDQpzdW1tYXJ5KG1vZCkNCg0KZGYgPC0gKHN1YnNldChzOGRhdGEud1ByZWRpY3RvcnMsIHBhcmFtZXRlciA9PSBjb2MpKSAlPiUNCiAgYWRkX3N1cnYoKQ0KYmF5ZXNfcGxvdHMobW9kLGNvYyxkZikNCmBgYA0KDQojIyBUb3RhbCBLamVsZGFobCBOaXRyb2dlbiAgDQoNCmBgYHtyIGZpZy5oZWlnaHQ9N30NCmNvYyA9ICdUb3RhbCBLamVsZGFobCBOaXRyb2dlbiAtIFdhdGVyIC0gVG90YWwnDQpUS05Nb2RlbDwtIG1jbWNfY2FsYygnVG90YWwgS2plbGRhaGwgTml0cm9nZW4gLSBXYXRlciAtIFRvdGFsJywncmV2X2xvZ1RyYWZmaWMnLCdsb2cnKSANCm1vZCA8LSBUS05Nb2RlbA0KZGYgPC0gKHN1YnNldChzOGRhdGEud1ByZWRpY3RvcnMsIHBhcmFtZXRlciA9PSBjb2MpKSAlPiUNCiAgYWRkX3N1cnYoKQ0KYmF5ZXNfcGxvdHMobW9kLGNvYyxkZikNCmBgYA0KIyMgVFNTICANCg0KYGBge3J9DQpjb2MgPSAnVG90YWwgU3VzcGVuZGVkIFNvbGlkcyAtIFdhdGVyIC0gVG90YWwnDQpUU1NNb2RlbCA8LSBtY21jX2NhbGMoJ1RvdGFsIFN1c3BlbmRlZCBTb2xpZHMgLSBXYXRlciAtIFRvdGFsJywncmV2X2xvZ1RyYWZmaWMnLCdsb2cnKQ0KZGYgPC0gKHN1YnNldChzOGRhdGEud1ByZWRpY3RvcnMsIHBhcmFtZXRlciA9PSBjb2MpKSAlPiUNCiAgYWRkX3N1cnYoKQ0KYmF5ZXNfcGxvdHMoVFNTTW9kZWwsY29jLGRmKQ0KDQoNCmBgYA0KJ1RvdGFsIFBBSCAtIFdhdGVyIC0gVG90YWwnLA0KYGBge3J9DQpjb2MgPSAnVG90YWwgUEFIIC0gV2F0ZXIgLSBUb3RhbCcNClBBSE1vZGVsPC0gbWNtY19jYWxjKCdUb3RhbCBQQUggLSBXYXRlciAtIFRvdGFsJywnTFVfcmF0aW8nLCdsb2cnKSANCm1vZCA8LSBQQUhNb2RlbA0KZGYgPC0gKHN1YnNldChzOGRhdGEud1ByZWRpY3RvcnMsIHBhcmFtZXRlciA9PSBjb2MpKSAlPiUNCiAgYWRkX3N1cnYoKQ0KYmF5ZXNfcGxvdHMobW9kLGNvYyxkZikNCmBgYA0KDQonQ1BBSCAtIFdhdGVyIC0gVG90YWwnDQpgYGB7cn0NCmNvYyA9ICdDUEFIIC0gV2F0ZXIgLSBUb3RhbCcNCkNQQUhNb2RlbDwtIG1jbWNfY2FsYygnQ1BBSCAtIFdhdGVyIC0gVG90YWwnLGMoJ2xvZ1BvcHVsYXRpb24nLCdyZXZfbG9nVHJhZmZpYycpLCdsb2cnKSANCm1vZCA8LSBDUEFITW9kZWwNCmRmIDwtIChzdWJzZXQoczhkYXRhLndQcmVkaWN0b3JzLCBwYXJhbWV0ZXIgPT0gY29jKSkgJT4lDQogIGFkZF9zdXJ2KCkNCmJheWVzX3Bsb3RzKG1vZCxjb2MsZGYpDQpgYGANCg0KJ0hQQUggLSBXYXRlciAtIFRvdGFsJyANCmBgYHtyfQ0KY29jID0gJ0hQQUggLSBXYXRlciAtIFRvdGFsJw0KSFBBSE1vZGVsPC0gbWNtY19jYWxjKCdIUEFIIC0gV2F0ZXIgLSBUb3RhbCcsYygnTFVfcmF0aW8nKSwnbG9nJykgDQptb2QgPC0gSFBBSE1vZGVsDQpkZiA8LSAoc3Vic2V0KHM4ZGF0YS53UHJlZGljdG9ycywgcGFyYW1ldGVyID09IGNvYykpICU+JQ0KICBhZGRfc3VydigpDQpiYXllc19wbG90cyhtb2QsY29jLGRmKQ0KYGBgDQoNCg0KIyBTdW1tYXJ5ICANCg0KU3VtbWFyaXplIHBvc3RlcmlvciByZXN1bHRzIGZvciB1c2UgaW4gaGVhdG1hcC4gDQoNCmBgYHtyfQ0KI3N1bW1hcml6ZSANCm1ldGFscyA8LSBsaXN0KCkNCm1ldGFsc1tbJ1RvdGFsIENvcHBlciddXSA8LSAoQ3VNb2RlbCkNCm1ldGFsc1tbJ1RvdGFsIFppbmMnXV0gPC0gKFpuTW9kZWwpDQptZXRhbHNbWydUb3RhbCBDYWRtaXVtJ11dPC0gKENkTW9kZWwpDQptZXRhbHNbWydUb3RhbCBMZWFkJ11dPC0gKFBiTW9kZWwpDQoNCm90aGVycyA8LSBsaXN0KCkgDQpvdGhlcnNbWydUb3RhbCBQaG9zcGhvcnVzJ11dIDwtIFRQTW9kZWwNCm90aGVyc1tbJ1RvdGFsIEtqZWxkYWhsIE5pdHJvZ2VuJ11dIDwtIFRLTk1vZGVsDQpvdGhlcnNbWydUb3RhbCBTdXNwZW5kZWQgU2VkaW1lbnQnXV0gPC0gIFRTU01vZGVsDQojb3RoZXJzWydGQyddIDwtIEZDTW9kZWwgDQoNCg0KDQoNCg0KbXN1bW1hcnkobWV0YWxzLHRpdGxlPSdUb3RhbCBNZXRhbHMnLHN0YXRpc3RpY192ZXJ0aWNhbCA9IFRSVUUsc3RhdGlzdGljID0gJ2NvbmYuaW50JywgY29uZl9sZXZlbCA9IDAuOTUpDQptc3VtbWFyeShvdGhlcnMsdGl0bGU9J051dHJpZW50cyBhbmQgVFNTJyxzdGF0aXN0aWNfdmVydGljYWwgPSBUUlVFLHN0YXRpc3RpYyA9ICdjb25mLmludCcsIGNvbmZfbGV2ZWwgPSAwLjk1KQ0KYGBgDQoNCg0KUHJpbnQgb3V0IGEgbWVzc3kgbGlzdCBvZiByZXN1bHRzIGZvciB0aGUgaGVhdG1hcCANCmBgYHtyfQ0Kc29scyA8LSBsaXN0KCkNCnNvbHNbWydDb3BwZXInXV0gPC1zdW1tYXJ5KEN1TW9kZWwpJHNvbHV0aW9ucyAgDQpzb2xzW1snWmluYyddXSA8LXN1bW1hcnkoWm5Nb2RlbCkkc29sdXRpb25zICANCnNvbHNbWydDYWRtaXVtJ11dIDwtc3VtbWFyeShDZE1vZGVsKSRzb2x1dGlvbnMgIA0Kc29sc1tbJ0xlYWQnXV0gPC1zdW1tYXJ5KFBiTW9kZWwpJHNvbHV0aW9ucyAgDQpzb2xzW1snVFAnXV0gPC1zdW1tYXJ5KFRQTW9kZWwpJHNvbHV0aW9ucyAgDQpzb2xzW1snVEtOJ11dIDwtc3VtbWFyeShUS05Nb2RlbCkkc29sdXRpb25zICANCnNvbHNbWydUU1MnXV0gPC1zdW1tYXJ5KFRTU01vZGVsKSRzb2x1dGlvbnMgIA0KDQpmb3IgKG4gaW4gMTpsZW5ndGgoc29scykpew0KICBwcmludChrYWJsZShzb2xzW1tuXV0scm93Lm5hbWVzID0gVFJVRSwgY2FwdGlvbiA9IHBhc3RlKG5hbWVzKHNvbHMpW25dLCIgLSBTdW1tYXJ5IG9mIE1DTUMgbW9kZWwiKSkpDQp9DQpgYGANCg0KDQoNCg0K