!manual add time zero to “days on hops” experiment!
Anton Paar data were collected at time zero for kinetics experiment (triplicate samples run immediately after sample collection, while the remainder were being dry-hopped), but were inadvertently excluded from the original dataset. Here they are: Date time sample no. ABV ABW OE (P) Er Ea SG 20/20 RDF ADF sample_id 07/27/17 3:26 PM 03_ 6.68 5.2 15.95 6.08 3.74 1.01462 63.88 76.58 JK1-20-41 07/27/17 3:30 PM 04_ 6.68 5.2 15.95 6.08 3.73 1.01461 63.89 76.6 JK1-20-50 07/27/17 3:34 PM 05_ 6.68 5.2 15.95 6.08 3.74 1.01462 63.88 76.58 JK1-20-87
## fill in MISSING DATA :( add time zero data for time series analysis)
## nested ifelse to add time zero plato values for each expt:
mydata<- mydata %>% mutate(., initial_plato = ifelse(expt==" 2A", 3.74, ifelse(expt==" 1A", 3.69, ifelse(expt==" 1B", 3.53, ifelse(expt==" 3B", 3.54, ifelse(expt==" 4B", 4.66, NA))))))
`as_dictionary()` is soft-deprecated as of rlang 0.3.0.
Please use `as_data_pronoun()` instead
[90mThis warning is displayed once per session.[39m`new_overscope()` is soft-deprecated as of rlang 0.2.0.
Please use `new_data_mask()` instead
[90mThis warning is displayed once per session.[39mThe `parent` argument of `new_data_mask()` is deprecated.
The parent of the data mask is determined from either:
* The `env` argument of `eval_tidy()`
* Quosure environments when applicable
[90mThis warning is displayed once per session.[39m`overscope_clean()` is soft-deprecated as of rlang 0.2.0.
[90mThis warning is displayed once per session.[39m
## add complete time-zero anton paar data for time series (expt2A)
## quadruple-up each triplicate time-zero measurement (one copy for each NH/DH & rouse/still combo)
## note: this practice is of questionable statistical rigor. In retrospect it would have been better (and a lot easier) to just run 12 samples through Anton on day0!
timezero <- mydata %>% filter(expt==" 2A") %>% arrange(special_group) %>% slice(1:12)
#data.entry(timezero)
timezero$Test_Date <- rep(c("7/27/2017","7/27/2017","7/27/2017"),4)
timezero$Test.time <- rep(c("3:26 PM", "3:30 PM", "3:34 PM"),4)
timezero$ABV <- 6.68
timezero$ABW <- 5.2
timezero$OE <- 15.95
timezero$Er <- 6.08
timezero$Ea <- rep(c(3.74, 3.73, 3.74),4)
timezero$SG <- rep(c(1.01462, 1.01461, 1.01462),4)
timezero$RDF <- rep(c(63.88, 63.89, 63.88),4)
timezero$ADF <- rep(c(76.58, 76.60, 76.58),4)
timezero$Calories <- rep(c(215.37,215.34,215.35),4)
timezero[timezero$hop=="DH",]$contact_days <-0
timezero[,c(28:31)] <- 0
mydata <- rbind(timezero,mydata)
write.csv(mydata, "quickie.csv", row.names = FALSE)
mydata<-read.csv("quickie.csv", stringsAsFactors = FALSE)
### this is a routine for inspecting data ###
mydatatypes<- as.data.frame(cbind(sapply(mydata, class))) ## table of datatypes
mydatatypes$headers <- rownames(mydatatypes) ## convert rownames to values
na_count <-as.data.frame(sapply(mydata, function(y) sum(length(which(is.na(y)))))) ## count NAs by column
mydataOverview<-as.data.frame(cbind(na_count,mydatatypes)) ## table of NA counts and datatypes
names(mydataOverview) <- c("NA_count","Data_Class", "Header")
#sum(is.na(mydata)) ## total count of NA values in entire sheet (231 in this case)
#mydataOverview %>% filter(NA_count>0) ## breakdown of NA values
tidy & transform
note: this is a base R +dplyr data wrangling exercise! in general it’s best to use lubridate package for date/timestamps!
- create a date column. Here we create new date columns in POSIXct format, based on the particular date format used in the source data (ujbc_a_1469081_sm5496.txt; see ?as.POSIXct). First an example of how NOT to create new variables in our dataframe:
test_df<- mydata # (original dataframe to be used for testing/illustration)
x_brew_date.POSIXct<- as.POSIXct(mydata$brew_date, format = "%m/%d/%Y") # as a standalone vector (USELESS here), or:
test_df$brew_date.POSIXct <-as.POSIXct(mydata$brew_date, format = "%m/%d/%Y") # as a "new column" in our dataframe
- ADVANCED data wrangling:
- create multiple date columns using FORLOOP (here we take advantage of the pattern that of our (character) date/time columns have the string “date” in the headername. First make character vector of all column names containing string “date”, then run FORLOOP over each date column). This forloop will convert datecols into POSIXct format (R/universal datetime format). They say it’s best to avoid forloops in general, and in this case you would probably use functions from lubridate package - and in general be aware there’s probably a specific tool for the task at hand)!!
datecols<- dput(names(dplyr::select(mydata, matches("date")))) ## headers containing string "date" => c("brew_date", "sample_collection_date", "dryhop_date", "Test_Date")
c("brew_date", "sample_collection_date", "dryhop_date", "Test_Date"
)
## FORLOOP
for (icol in datecols) {
newcol = paste0(icol,".POSIXct")
# print(newcol)
mydata[, newcol] = as.POSIXct(mydata[, icol],format = "%m/%d/%Y") ### CREATE NEW columns with POSIXct
# mydata[, newcol] = as.POSIXct(as.numeric(mydata[, icol]) * (60*60*24), origin="1899-12-30") ### microsoft times
}
- create dayofaddition, daysonhops, hops_g_100mL, pounds_bbl variables with dplyr “mutate”
mydata<- mydata %>%
mutate(dayofaddition = as.integer(as.numeric(difftime(dryhop_date.POSIXct,brew_date.POSIXct))),
daysonhops = as.integer(as.numeric(difftime(Test_Date.POSIXct,dryhop_date.POSIXct))/(60*60*24)),
hops_g_100mL = (mg_hops/volume_mL)/10,
pounds_bbl = (mg_hops/volume_mL)*117/454
)
- ADVANCED data wrangling: manually unpack overloaded columns with grepl
mydata$OX<-grepl("OX", mydata$Hop_type) ## create logical "OX" column
mydata$rouse<-grepl("rouse", mydata$special_group)## create logical column
mydata$Grind<-grepl("Grind", mydata$Hop_type) ## create logical column
mydata$Cone<-grepl("Cone", mydata$Hop_type) ## create logical column
mydata$harvest2014<-grepl("14", mydata$Hop_type) ## create logical column
mydata$harvest2015<-grepl("15", mydata$Hop_type) ## create logical column
mydata$harvest2017<-grepl("17", mydata$Hop_type) ## create logical column
- ADVANCED data wrangling: ifelse statements for harvestyear, form_of_hops, and DH_temp
## ifelse statement for harvestyear (if neither 2014 nor 2015 nor 2017, then 2016)
mydata$harvestYear <- ifelse(
mydata$harvest2014==TRUE, 2014,
ifelse(mydata$harvest2015==TRUE, 2015,
ifelse(mydata$harvest2017==TRUE, 2017, 2016)))
dput(levels(as.factor(mydata$harvestYear)))
c("2014", "2015", "2016", "2017")
## ifelse statement for form of hops (if neither cone nor ground nor NH, then pellet)
mydata$form_of_hops <- ifelse(
mydata$Cone==TRUE, "cone",
ifelse(mydata$Grind==TRUE, "ground",
ifelse(mydata$Hop_type=="NH", "NH", "pellet")))
dput(levels(as.factor(mydata$form_of_hops)))
c("cone", "ground", "NH", "pellet")
## ifelse statement for temperature greater or less than 10
mydata$DH_temp <- ifelse(
mydata$temp.C<10, "cold",
ifelse(mydata$temp.C>10, "warm", "something else"))
dput(levels(as.factor(mydata$DH_temp)))
c("cold", "warm")
- ADVANCED data wrangling: create “variety” column starting with overloaded “Hop_type” column, then stripping away all the non-variety information
mydata$variety<-mydata$Hop_type
mydata$variety<- gsub("[0-9]+","", mydata$variety) ## remove all numbers
mydata$variety<- gsub("OX","", mydata$variety) ## remove specific text
mydata$variety<- gsub("Grind","", mydata$variety)
mydata$variety<- gsub("Cone","", mydata$variety)
mydata$variety<- gsub(" ","", mydata$variety) ## remove spaces
dput(levels(as.factor(mydata$variety)))
c("AMAR", "CASC", "CENT", "CIT", "NH", "SIM")
- ADVANCED data wrangling: create “EXPTnew” (each unique experimental group designated in a single variable)
mydata<- mydata %>% mutate(EXPTnew=paste0("group",expt, substr(special_group, 1,2),as.character(rouse)))
# clean it up by removing "NA" and any spaces due to canarycode bug
mydata$EXPTnew <- gsub(" ","", mydata$EXPTnew) ## remove any spaces
mydata$EXPTnew <- gsub("NA","", mydata$EXPTnew) ## remove "NA"
dput(levels(as.factor(mydata$expt)))
c(" 1A", " 1B", " 2A", " 3A", " 3B", " 4B")
dput(levels(as.factor(mydata$EXPTnew)))
c("group1AFALSE", "group1BFALSE", "group2A01FALSE", "group2A01TRUE",
"group2A05FALSE", "group2A05TRUE", "group2A12FALSE", "group2A12TRUE",
"group2A19FALSE", "group2A19TRUE", "group2A25FALSE", "group2A25TRUE",
"group2A33FALSE", "group2A33TRUE", "group2A42FALSE", "group2A42TRUE",
"group3AFALSE", "group3BFALSE", "group4BFALSE")
select and rearrange columns
(experimental factors on the left, then measurements, followed by calculations and then all date columns on the right):
mydata <- mydata %>%
dplyr::select(sample_id,expt, EXPTnew, hop, BINhop, variety, OX, harvestYear, form_of_hops,special_conditions, rouse, daysonhops, dayofaddition, DH_temp, temp.C, hops_g_100mL, pounds_bbl,
ABV, ABW, OE, Er, Ea, SG, RDF, ADF, Calories,
dhop_day, contact_days, REF_NH, ABV_increase,
brew_date.POSIXct, sample_collection_date.POSIXct, dryhop_date.POSIXct,Test_Date.POSIXct, initial_plato)
## remove ".POSIXct" suffix. Leaving it as-is will only add to confusion if/when these data are saved and re-imported (and become 'character' format again!)
colnames(mydata) = gsub(".POSIXct", "", colnames(mydata))
compute mean NH (control) values
(NH = not dry-hopped)
## mean NH (control) values for each EXPTnew group
mean.control_NH<- mydata %>%
group_by(EXPTnew) %>%
filter(hop=="NH") %>%
summarise_at(vars(ABV, ABW, Ea, SG),funs(mean, n()))
## replace "_mean" with ".control_NH" in column names
colnames(mean.control_NH) = gsub("_mean", ".control_NH", colnames(mean.control_NH))
compute \(\Delta\) values (differences relative to NH controls) using objects created above…
- difference of each ABV,ABW,Ea, and SG data point from corresponding mean.control_NH values (unhopped samples in same EXPTnew group)
## first join our data with mean ABV for unhopped samples in given experiment (mean.control_NH; calculated above)
FPHcalc<- left_join(mydata, mean.control_NH, by="EXPTnew")
## calculate ABW_increase by subtracting each individual ABW measurement from respective mean.control_NH:
FPHcalc$delta.ABV <- FPHcalc$ABV - FPHcalc$ABV.control_NH
FPHcalc$delta.ABW <- FPHcalc$ABW - FPHcalc$ABW.control_NH
FPHcalc$delta.plato <- FPHcalc$Ea - FPHcalc$Ea.control_NH
FPHcalc$delta.SG <- FPHcalc$SG - FPHcalc$SG.control_NH
## the control samples have served their purpose, now remove them from dataset. The following calculations are only meaningful for dry-hopped samples.
FPHcalc<- FPHcalc %>% filter(hop=="DH")
calculate corresponding CO2 production
- following Bamforth (describing Balling equation) “…more realistically, the ethanol yield is more like 0.46 g and carbon dioxide 0.44 g from 1 g sugar” (p. 137 in Brewing Materials and Processes: A Practical Approach to Beer Excellence, Edited by Charles Bamforth Academic Press, 2016)
FPHcalc$calcCO2_increase <- FPHcalc$delta.ABW*(0.44/0.46)
##convert calcCO2_increase (in g/100mL) to calculated CO2 volumes added
## g/L = 10* g/100mL
## The conversion factor from volumes of CO2 to CO2 by weight (g/L) is 1.96. For example: 2.5 volumes x 1.96 = 4.9 g/l.
FPHcalc$calcCO2vols_increase <- FPHcalc$calcCO2_increase*10/1.96
define “FPH” as amount produced per amount dry-hops added (all in g/100mL):
## FPH = Fold Production due to Hops (fold-increase by mass: amount of given endpoint relative to amount of hops added)
##
FPHcalc$FPH_EtOH = FPHcalc$delta.ABW/FPHcalc$hops_g_100mL
FPHcalc$FPH_CO2 = FPHcalc$calcCO2_increase/FPHcalc$hops_g_100mL
FPHcalc$FPH_plato = FPHcalc$delta.plato/FPHcalc$hops_g_100mL
## replacing time-zero time values with a very small number (rather than exactly zero) will prevent issues with analysis of nonlinear models
FPHcalc[FPHcalc$daysonhops==0,]$daysonhops <- 0.01
## and save the transformed data to csv:
write.csv(FPHcalc,"FPHcalc.csv", row.names = FALSE)
FPHcalc <- read.csv("FPHcalc.csv", stringsAsFactors = TRUE)
df<- FPHcalc %>% group_by(EXPTnew) %>%
summarise_at(vars(hops_g_100mL,pounds_bbl,delta.ABV, delta.ABW, delta.plato, FPH_plato, FPH_EtOH, FPH_CO2, calcCO2vols_increase),funs(round(mean(.), 2))) %>%
arrange(desc(FPH_EtOH))
df
visualize
vector~vector*vector plots
df <-FPHcalc
p1<- ggplot(df, aes(y=FPH_EtOH,x=OE, color=contact_days)) + geom_point(size=2)
p2<- ggplot(df, aes(y=FPH_EtOH,x=ADF, color=contact_days)) + geom_point(size=2)
p3<- ggplot(df, aes(y=FPH_EtOH,x=ABW, color=contact_days)) + geom_point(size=2)
p4<- ggplot(df, aes(y=FPH_EtOH,x=Ea, color=contact_days)) + geom_point(size=2)
grid.arrange(p1, p2, p3, p4, ncol = 2)

x~y*(4 vectors) by color
df <- FPHcalc
x<- df$Ea
y<- df$ABW #FPH_EtOH
p1<- ggplot(df, aes(x,y, color=form_of_hops)) + geom_point(size=2)
p2<- ggplot(df, aes(x,y, color=brew_date)) + geom_point(size=2)
p3<- ggplot(df, aes(x,y, color=rouse)) + geom_point(size=2)
p4<- ggplot(df, aes(x,y, color=pounds_bbl)) + geom_point(size=2)
grid.arrange(p1, p2, p3, p4, ncol = 2)

3D plots for html with library(plotly)
see https://plotly-r.com/the-plotly-cookbook.html for overview and some visualization inspiration.
# interactive 3D plots with library(plotly)
df <- FPHcalc # %>% filter(expt==" 2A")
#plot_ly(df, x = ~daysonhops, y = ~ABW, z = ~Ea) %>% add_markers(color = ~expt)
Amunategui “Using Correlations To Understand Your Data”
mydata<-FPHcalc %>% dplyr::select(expt,variety,form_of_hops,pounds_bbl,special_conditions,rouse,daysonhops,DH_temp,brew_date,ABW,SG,OE,ADF,RDF,calcCO2vols_increase,FPH_CO2, FPH_EtOH) ## last one is 100% in j
## note use of "dplyr::select" because one of these packages is conflicting with dplyr commands :()
## following Manuel Amunategui https://www.youtube.com/watch?v=igPQ-pI8Bjo
## Using Correlations To Understand Your Data: Machine Learning With R
##functions for flattenSquareMatrix
cor.prob <- function (X, dfr=nrow(X) -2) {
R<- cor(X, use="pairwise.complete.obs")
above<- row(R) < col(R)
r2 <- R[above]^2
Fstat<- r2 * dfr/(1-r2)
R[above] <- 1- pf(Fstat, 1, dfr)
R[row(R) == col(R)] <- NA
R
}
flattenSquareMatrix <- function(m) {
if( (class(m) != "matrix") | (nrow(m)!=ncol(m))) stop("Must be a square matrix.")
if(!identical(rownames(m), colnames(m))) stop("Row and column names must be equal.")
ut <- upper.tri(m)
data.frame(i = rownames(m)[row(m)[ut]],
j = rownames(m)[col(m)[ut]],
cor=t(m)[ut],
p=m[ut])
}
## library(caret) to dummify everything (turn all characters&factors into columns; ignores numbers and integers)
dmy<- dummyVars(" ~ .",data = mydata)
mydummifieddata<- data.frame(predict(dmy, newdata = mydata))
corMat = cor(mydummifieddata)
corMasterList<- flattenSquareMatrix(cor.prob(mydummifieddata)) ## list of all correlations
## order by strength of correlation
corlist<- corMasterList %>% arrange(-abs(corMasterList$cor))
write.csv(corlist,paste0("FLAT correlation matrix_.csv"))
corlist <- corlist %>% dplyr::filter(j=="FPH_EtOH") ## filter specific endpoint
head(corlist,50)
pairs.panels correlation matrix from library(psych)
## specify interesting variables:
interestingvariables<-c("ABW", "pounds_bbl", "daysonhops", "calcCO2vols_increase")
pairs.panels(mydummifieddata[c(interestingvariables, "FPH_EtOH")])

model
Observing the impacts of dryhopping in the presence of live yeast has led many brewing professionals to understand that FPH is a function of many of the variables above including hop variety,form_of_hops,harvestYear,OX,DH_temp,daysonhops,rouse,pounds_bbl…. Many have intuitively created a model in their heads (without necessarily thinking of it as such) and skillfully adjust process when necessary to account for this phenomenon. In linear modeling, our function will take on the form: \(FPH = intercept + \beta_{1}X_{1} + \beta_{2}X_{2} + ... + \beta_{n}X_{n}\) where \(\beta\) values are what we’re attempting to derive in this modeling exercise, and X values are (collectively) a particular set of conditions.
ActionItem: add link to modeling overview
linear models 1 (create models)
df<-FPHcalc
lm1<-lm(df$SG~df$OE)
lm2<-lm(df$SG~df$ADF)
lm3<-lm(df$SG~df$ABW)
lm4<-lm(df$SG~df$Ea)
par(mfrow = c(2, 2), oma = c(0, 0, 0, 0))
plot(df$SG~df$OE)
abline(lm1)
plot(df$SG~df$ADF)
abline(lm2)
plot(df$SG~df$ABW)
abline(lm3)
plot(df$SG~df$Ea)
abline(lm4)

linear models 2 (view residual plots)
df<-FPHcalc
lm1<-lm(df$SG~df$OE)
lm2<-lm(df$SG~df$ADF)
lm3<-lm(df$SG~df$ABW)
lm4<-lm(df$SG~df$Ea)
par(mfrow = c(2, 2), oma = c(0, 0, 0, 0))
plot(lm1$residuals)
plot(lm2$residuals)
plot(lm3$residuals)
plot(lm4$residuals)

#summary(lm4)
compare linear models using memisc package
df<-FPHcalc
lm1<-lm(df$SG~df$OE)
lm2<-lm(df$SG~df$ADF)
lm3<-lm(df$SG~df$ABW)
lm4<-lm(df$SG~df$Ea)
mtable1234 <- mtable("Model 1"=lm1,"Model 2"=lm2,"Model 3"=lm3, "Model 4"=lm4,
summary.stats=c("sigma","R-squared","F","p","N"),show.eqnames=T)
mtable1234b <- relabel(mtable1234,
"(Intercept)" = "Constant",
x1 = "OE = Original Extract (g/100mL)",
x2 = "ADF = Apparent Degree of Fermentation (%)",
x3 = "ABW = Ethanol (w/w)",
x4 = "Er = Residual Extract (g/100mL)"
)
mtable1234
Calls:
Model 1: lm(formula = df$SG ~ df$OE)
Model 2: lm(formula = df$SG ~ df$ADF)
Model 3: lm(formula = df$SG ~ df$ABW)
Model 4: lm(formula = df$SG ~ df$Ea)
======================================================================
Model 1 Model 2 Model 3 Model 4
----------- ------------- ------------ ---------------
df$SG df$SG df$SG df$SG
----------------------------------------------------------------------
(Intercept) 1.071*** 1.063*** 1.056*** 1.000***
(0.020) (0.000) (0.001) (0.000)
df$OE -0.004**
(0.001)
df$ADF -0.001***
(0.000)
df$ABW -0.008***
(0.000)
df$Ea 0.004***
(0.000)
----------------------------------------------------------------------
sigma 0.002 0.000 0.001 0.000
R-squared 0.066 0.997 0.934 1.000
F 8.553 48038.147 1700.301 3532408.297
p 0.004 0.000 0.000 0.000
N 123 123 123 123
======================================================================
#show_html(mtable1234b)
linear models of FPH
df <- FPHcalc
lm1<-lm(FPH_EtOH~daysonhops, data=df)
lm2<-lm(FPH_EtOH~daysonhops*form_of_hops, data=df)
lm3<-lm(FPH_EtOH~daysonhops*rouse, data=df)
lm4<-lm(FPH_EtOH~daysonhops*form_of_hops*rouse, data=df)
mtable1234 <- mtable("Model 1"=lm1,"Model 2"=lm2,"Model 3"=lm3, "Model 4"=lm4,
summary.stats=c("sigma","R-squared","F","p","N"),show.eqnames=T)
mtable1234b <- relabel(mtable1234,
"(Intercept)" = "Constant",
SG = "Specific Gravity",
ABW = "ABW = Ethanol (w/w)",
Er = "Er = Residual Extract (g/100mL)"
)
mtable1234
Calls:
Model 1: lm(formula = FPH_EtOH ~ daysonhops, data = df)
Model 2: lm(formula = FPH_EtOH ~ daysonhops * form_of_hops, data = df)
Model 3: lm(formula = FPH_EtOH ~ daysonhops * rouse, data = df)
Model 4: lm(formula = FPH_EtOH ~ daysonhops * form_of_hops * rouse, data = df)
=============================================================================
Model 1 Model 2 Model 3 Model 4
----------- ----------- ----------- -----------
FPH_EtOH FPH_EtOH FPH_EtOH FPH_EtOH
-----------------------------------------------------------------------------
(Intercept) 0.498*** 0.306 0.537*** 0.319
(0.044) (0.197) (0.049) (0.197)
daysonhops 0.044*** 0.044*** 0.041*** 0.041***
(0.003) (0.003) (0.004) (0.004)
form_of_hops: ground/cone 0.849** 0.849**
(0.278) (0.277)
form_of_hops: pellet/cone 0.174 0.199
(0.200) (0.199)
rouse -0.227 -0.208
(0.122) (0.118)
daysonhops x rouseTRUE 0.010 0.009
(0.006) (0.006)
-----------------------------------------------------------------------------
sigma 0.355 0.340 0.352 0.339
R-squared 0.634 0.669 0.645 0.678
F 210.023 80.145 72.038 49.193
p 0.000 0.000 0.000 0.000
N 123 123 123 123
=============================================================================
#show_html(mtable1234b)
The R Book (Crawley) Table 20.1: nonlinear functions useful in biology
Table 20.1. Useful non-linear functions EXPANDED:
Asymptotic functions |
Michaelis–Menten |
\(y =\frac{ax}{1+bx}\) |
nls(bone~aage/(1+bage),start=list(a=8,b=0.08))) |
enzyme reactions |
|
|
|
nls(rate~SSmicmen(conc,a,b)) |
tbd |
|
2-parameter asymptotic exponential |
\(y = a(1 − e^{−bx} )\) |
nls(bone~a(1-exp(-cage)),start=list(a=120,c=0.064)) |
tbd |
|
3-parameter asymptotic exponential |
\(y = a − be^{−cx}\) |
nls(bone~a-bexp(-cage),start=list(a=120,b=110,c=0.064)) |
tbd |
|
|
|
nls(bone~SSasymp(age,a,b,c)) |
tbd |
|
|
|
nls(density ~ SSlogis(log(concentration), a, b, c)) |
tbd |
S-shaped functions |
2-parameter logistic |
\(y = \frac{e^{a+bx}}{1 + e^{a+bx}}\) |
|
tbd |
|
3-parameter logistic |
\(y = \frac{a}{1 + be^{−cx}}\) |
|
tbd |
|
4-parameter logistic |
\(y = a + \frac{b-a}{1 + e^{(c−x)/d}}\) |
nls(weight~SSfpl(Time, a, b, c, d)) |
tbd |
|
Weibull |
\(y = a − be^{−(cx^d)}\) |
nls(weight ~ SSweibull(time, Asym, Drop, lrc, pwr)) |
tbd |
|
Gompertz |
\(y = ae^{−be^{−cx}}\) |
|
tbd |
Humped curves |
Ricker curve |
\(y = axe^{−bx}\) |
|
tbd |
|
First-order compartment |
\(y = k exp(−exp(a)x) − exp(−exp(b)x)\) |
nls(conc~SSfol(Dose, Time, a, b, c)) |
tbd |
|
Bell-shaped |
\(y = a exp(−ABS(bx)^2)\) |
|
tbd |
|
Biexponential |
\(y = ae^{bx} − ce^{−dx}\) |
|
tbd |
base R nls function for Michaelis-Menton model
\(y =\frac{ax}{1+bx}\)
expt2 <- FPHcalc %>% dplyr::filter(expt==" 2A") %>% dplyr::select(FPH_EtOH, rouse,special_conditions, daysonhops,brew_date, pounds_bbl,form_of_hops,ABW.control_NH,dayofaddition)
myfactor<- as.factor(expt2$special_conditions) #rouse
cols <- as.numeric(myfactor)
legend.cols <- as.numeric(as.factor(levels(myfactor)))
my.MM.model <- nls(FPH_EtOH~a*daysonhops/(1+b*daysonhops),myfactor, data=expt2,start=list(a=8, b=0.08))
summary(my.MM.model)
Formula: FPH_EtOH ~ a * daysonhops/(1 + b * daysonhops)
Parameters:
Estimate Std. Error t value Pr(>|t|)
a 0.186159 0.010109 18.41 < 2e-16 ***
b 0.067820 0.005728 11.84 1.45e-15 ***
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
Residual standard error: 0.09307 on 46 degrees of freedom
Number of iterations to convergence: 6
Achieved convergence tolerance: 1.686e-06
So using the Michaelis-Menten equation as our model, the relationship between hop-induced ethanol production (FPH_EtOH) and hop contact time (daysonhops) would be expressed as: \(FPH_EtOH = \frac{0.183572*daysonhops}{1+0.066388*daysonhops}\) with a Residual standard error of 0.09307. As detailed below, logistic models do a better job than this, and using library(drc) makes it easier to introduce a multilevel factor.
predict and plot model
x <- seq(0,50,0.1)
yv <- predict(my.MM.model, list(daysonhops=x))
{plot(expt2$daysonhops, expt2$FPH_EtOH, pch=21, col="purple", bg="green", main = "Michaelis-Menten model of ethanol production induced by dry-hopping")
lines(x,yv,col="blue")}

Speers2003: Non‐Linear Modelling of Industrial Brewing Fermentations
R. Alex Speers, Peter Rogers, Bruce Smith J. Inst. Brew. 109(3), 229–235, 2003 https://doi.org/10.1002/j.2050-0416.2003.tb00163.x Free Access from the Institute of Brewing! https://onlinelibrary.wiley.com/doi/10.1002/j.2050-0416.2003.tb00163.x
Following Speers2003, the relationship between plato and time in primary fermentations is well modeled by a four parameter logistic model:
PLATO = PINF + (P_D / (1+ (EXP(-B*(TIME-M)))))
or
\(PLATO = P_{inf}+ \frac{P_D}{1+e^{-B(t-M)}}\)
where
- t = time into fermentation
- P_D = change in Plato during fermentation (OE - P_{inf})
- B ~ maximum fermentation rate
- M = fermenation midpoint
- P_inf = final gravity
so then for our case
\(PLATO = P_{inf}+ \frac{OE-P_{inf}}{1+e^{-F_{max}(daysonhops-M)}}\)
PLATO = PINF + (P0 / (1+ (EXP(B*(HOURS-M)))))
MacIntosh2016: An Examination of Substrate and Product Kinetics During Brewing Fermentations
Andrew J. MacIntosh, Maria Josey, R. Alex Speers J. Am. Soc. Brew. Chem. 74(4), 250-257, 2016
“is only statistically advantageous to apply the five-parameter model when many data points are available… (72 from this experiment as opposed to 10 when using Yeast-14).”
MacIntosh five-parameter logistic: \(P_{(t)} =\frac{P_i - P_e}{(1+s * e^{-B(t-M)})^{1/s}}\)
library(drc) getMeanFunctions()
- drc LL.4: \(f(x) = c + \frac{d-c}{1+\exp(b(\log(x)-\tilde{e}))}\) #4param logistic
- drc LL.5: \(f(x) = c + \frac{d-c}{(1+\exp(b(\log(x)-e)))^f}\) #5param logistic
- drc G.4 \(f(x) = c + (d-c)(\exp(-\exp(b(x-e))))\) #4param Gompertz
- drc W1.4 \(f(x) = c + (d-c) \exp(-\exp(b(\log(x)-\log(e))))\) #4param Weibull1
- drc W2.4 \(f(x) = c + (d-c) (1 - \exp(-\exp(b(\log(x)-\log(e)))))\) #4param Weibull2
explore nonlinear models of hop-induced Plato drop over time with library(drc)


{plot(x , y, col=cols, main="raw data")
legend("topright", legend=levels(group), pch=16, col=legend.cols)}
#plot(m.LL.3, type='all',col=cols, main="LL.3 (lower limit at 0)")
plot(m.LL.4, type='all',col=cols, main="LL.4 four-parameter log-logistic")
plot(m.L.4, type='all',col=cols, main="L.4")

plot(m.LL.5, type='all',col=cols, main="LL.5")

plot(m.LL2.4, type='all',col=cols, main="LL2.4 four-parameter log-logistic")

plot(m.LL2.5, type='all',col=cols, main="Generalised log-logistic")

plot(m.W1.4, type='all',col=cols, main="Weibull1")

plot(m.W2.4, type='all',col=cols, main="Weibull2")

plot(m.BC.5, type='all',col=cols, main="Brain-Cousens (hormesis)")

plot(m.AR.3, type='all',col=cols, main="Shifted asymptotic regression")

#plot(m.MM.2, type='all',col=cols, main="2-parameter Michaelis-Menten")
plot(m.MM.3, type='all',col=cols, main="3-parameter Michaelis-Menten")

multilevel logistic models with library(drc)
df <- FPHcalc %>% filter(expt==" 2A")
df$PLATO.hopdrop <- df$initial_plato + df$delta.plato
x <- df$daysonhops
y <- df$PLATO.hopdrop
myfactor<- as.factor(df$special_conditions) #rouse
cols <- as.numeric(myfactor)
legend.cols <- as.numeric(as.factor(levels(myfactor)))
## create models
#m.LL.4<-drm(y ~x,myfactor, fct = LL.4(names = c("Slope", "Lower", "Upper", "Midpoint or ED50"))) #4-parameter log-logistic (general parameters)
#m.LL.4<-drm(y ~x,myfactor, fct = LL.4(names = c("Slope", "Lower", "Upper", "ED50"))) #4-parameter log-logistic (Dose-Response parameters)
m.LL.4<-drm(y ~x,myfactor, fct = LL.4(names = c("F_max", "P_inf", "OE", "M"))) #4-parameter log-logistic
m.L.4 <-drm(y ~x,myfactor, fct = L.4()) #changing the fct = LL.4() to fct = L.4() allows plotting on a log10 scale
m.LL2.4<-drm(y ~x,myfactor, fct = LL2.4()) #4-parameter log-logistic with log(e) rather than e as a parameter
m.LL2.5<-drm(y ~x,myfactor, fct = LL2.5()) #Generalised log-logistic
m.LL.5<-drm(y ~x,myfactor, fct = LL.5()) #5-parameter logistic
#plot a few side by side
par(mfrow = c(1, 2), oma = c(0, 0, 0, 0))
plot(m.LL.4,log="", broken = TRUE, bcontrol = list(style = "slash"), col = c(2,6,3,23,56), main = "4-parameter logistic")
plot(m.LL2.5,log="", broken = TRUE, bcontrol = list(style = "slash"), col = c(2,6,3,23,56), main = "5-parameter logistic")

plot residuals of nonlinear models generated with library(drc)
par(mfrow = c(3, 2), oma = c(0, 0, 0, 0))
{plot(m.MM.3$predres, main = "Michaelis-Menten")
abline(0,0)}
{plot(m.W2.4$predres, main = "Weibull2")
abline(0,0)}
{plot(m.LL.4$predres, main = "4-parameter logistic")
abline(0,0)}
{plot(m.LL.5$predres, main = "5-parameter logistic")
abline(0,0)}
{plot(m.BC.5$predres, main = "Brain-Cousens (hormesis)")
abline(0,0)}
{plot(m.LL2.5$predres, main = "Generalised log-logistic")
abline(0,0)}

from ??BC.5: The model function for the Brain-Cousens model (Brain and Cousens, 1989) is \(f(x, b,c,d,e,f) = c + \frac{d-c+fx}{1+\exp(b(\log(x)-\log(e)))}\) (doesn’t look any better than LL.4, not sure why I’m even looking…)
#pick one model to be mymodel
mymodel <- m.LL.4
mymodel
A 'drc' model.
Call:
drm(formula = y ~ x, curveid = myfactor, fct = LL.4(names = c("F_max", "P_inf", "OE", "M")))
Coefficients:
F_max:rouse F_max:still P_inf:rouse P_inf:still OE:rouse OE:still
1.392 1.800 1.809 2.149 3.749 3.751
M:rouse M:still
12.274 7.958
confint(mymodel)
2.5 % 97.5 %
F_max:rouse 1.186734 1.597836
F_max:still 1.537194 2.063586
P_inf:rouse 1.653949 1.963275
P_inf:still 2.079132 2.219179
OE:rouse 3.710521 3.787753
OE:still 3.714969 3.786819
M:rouse 10.616221 13.931883
M:still 7.316189 8.600129
summary(mymodel)
Model fitted: Log-logistic (ED50 as parameter) (4 parms)
Parameter estimates:
Estimate Std. Error t-value p-value
F_max:rouse 1.392285 0.101704 13.690 < 2.2e-16 ***
F_max:still 1.800390 0.130226 13.825 < 2.2e-16 ***
P_inf:rouse 1.808612 0.076525 23.634 < 2.2e-16 ***
P_inf:still 2.149156 0.034647 62.030 < 2.2e-16 ***
OE:rouse 3.749137 0.019107 196.219 < 2.2e-16 ***
OE:still 3.750894 0.017775 211.018 < 2.2e-16 ***
M:rouse 12.274052 0.820272 14.963 < 2.2e-16 ***
M:still 7.958159 0.317638 25.054 < 2.2e-16 ***
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
Residual standard error:
0.03875737 (40 degrees of freedom)
Each level (rouse and still) has a unique curve shape thereby resulting in different parameters for a best-fit log-logistic model. Now that we know this, it is practical to address each set of conditions separately. Based on the 4-parameter logistic model LL.4 we have for roused samples:
\(PLATO_{roused} = P_{inf}+ \frac{OE-P_{inf}}{1+e^{-F_{max}(daysonhops-M)}}\)
or
\(PLATO_{roused} = 1.809 + \frac{1.94}{1+e^{-1.3923(daysonhops-12.274)}}\)
and for untouched (still) samples:
\(PLATO_{still} = 2.149 + \frac{1.60}{1+e^{-1.8004(daysonhops-7.958)}}\)
Plotting and analysis options for multilevel models are limited. By filtering the data to focus on one set of conditions at a time we create ‘simple’ y~x logistic models. Many wonderful visualization tools are available for such x~y models. Here’s just a few:
single level logistic model with confidence intervals
(specify eval=false in R notebook codechunk header {r eval=FALSE} to prevent multiple messages regarding “Recycling array of length 1 in array-vector arithmetic is deprecated” from appearing in html)
df <- FPHcalc %>% filter(expt==" 2A" & rouse==TRUE)
df$PLATO.hopdrop <- df$initial_plato + df$delta.plato
x <- as.numeric(df$daysonhops)
y <- df$PLATO.hopdrop
## create model
mymodel<-drm(y ~x, fct = LL.4()) #4-parameter logistic with library(drc)
plot(mymodel, main = "default (logarithmic) x-axis")
plot(mymodel, log="", main = "non-logarithmic x-axis")
# create prediction intervals
newdata = data.frame(y = y, x = x)
conf95 <- as.data.frame(predict(mymodel, newdata,interval = "confidence", level = 0.95))
conf95$x <- x
pred95 <- as.data.frame(predict(mymodel, newdata, interval = "prediction", level = 0.95))
pred95$x <- x
pred999 <- as.data.frame(predict(mymodel, newdata, interval = "prediction", level = 0.999))
pred999$x <- x
mymodel
Note: If you compare the multi-level model parameter estimates for rouse=TRUE with those for unilevel model based on rouse=TRUE subset, the results are similar, but not identical!
plot prediction confidence intervals
# Asymmetric ribbons based on prediction intervals
#newdata <- data.frame(y = c(3.5, 3, 2.9, 2.5,1.8,1.7), x = c(1,5,10,20,30,40))
#newdata <- tidyfermi %>% filter(batch = SELECTBATCH) %>% select(days,plato)
qplot(data=pred95, x=x, y=Prediction, ymin=Lower, ymax=Prediction, geom="ribbon", fill=I("red"), alpha=I(0.2)) +
geom_ribbon(data=pred95, aes(x=x, ymin=Prediction, ymax=Upper), fill=I("blue"), alpha=I(0.2)) +
geom_line(data=pred95, aes(x=x, y=Prediction), color=I("green"), lwd=1)+
geom_point(data=newdata, aes(x=x, y=y, ymin=NULL, ymax=NULL), size=1, col="blue")+
ylab("y")

# Visualise intervals
# based on Maurits Evers (https://stackoverflow.com/questions/49444489/nonlinear-regression-prediction-in-r?rq=1)
data.frame(x=x, y=y) %>% ggplot(aes(x, y)) +
geom_point() +
geom_line(data = pred95, aes(x = x, y = Prediction)) +
geom_ribbon(data = pred999, aes(x = x, ymin = Lower, ymax = Upper),fill=I("pink"),alpha = 0.4)+
geom_ribbon(data = pred95, aes(x = x, ymin = Lower, ymax = Upper),fill=I("green"),alpha = 0.4)+
geom_ribbon(data = conf95, aes(x = x, ymin = Lower, ymax = Upper),fill=I("purple"),alpha = 0.4);

LS0tDQp0aXRsZTogIlIgTm90ZWJvb2s6IE1vZGVsaW5nIHRoZSBGcmVzaGVuaW5nIFBvd2VyIG9mIHRoZSBIb3AiDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQo+ICpTdGVwMTogIE9wZW4gdGhpcyAuUk1EIGZpbGUgaW4gUiBTdHVkaW8gYW5kIGNsaWNrIHRoZSAiUHJldmlldyIgYnV0dG9uIGFib3ZlLiAgQSBmb3JtYXR0ZWQgdmVyc2lvbiBzaG91bGQgcG9wIHVwIGluIGEgYnJvd3NlciB3aW5kb3cuICBJRiBub3QsIHRyb3VibGVzaG9vdCB0aGF0ISoNCg0KDQojIG1hbnVhbCBvdXRsaW5lDQoNCjEuIEhvdXNla2VlcGluZw0KICAgIDEuICpTRVQgV09SS0lORyBESVJFQ1RPUlkqIA0KICAgIDEuIHRoaXMgKipSIE1hcmtkb3duICguUk1EKSoqIGRvY3VtZW50IHdhcyBjcmVhdGVkIHVzaW5nIFIgU3R1ZGlvDQogICAgICAgIDEuIHBsYWluIHRleHQNCiAgICAgICAgMS4gIlJlcHJvZHVjaWJsZSBSZXNlYXJjaCIgTW92ZW1lbnQNCiAgICAxLiAqKipSKioqIGZyb20gPGh0dHBzOi8vd3d3LnItcHJvamVjdC5vcmcvPg0KICAgIDEuICoqKlIgU3R1ZGlvKioqIGZyb20gPGh0dHBzOi8vd3d3LnJzdHVkaW8uY29tL3Byb2R1Y3RzL3JzdHVkaW8vZG93bmxvYWQvPg0KICAgIDEuIEVudmlyb25tZW50IHRhYiAoUnN0dWRpbykNCiAgICAxLiBGaWxlcy9QbG90cy9QYWNrYWdlcy9IZWxwL1ZpZXdlciAgdGFiIChSc3R1ZGlvKQ0KICAgIDEuIFByZXZpZXcvdmlldyBhcyBIVE1MICAoUiBNYXJrZG93biBpbiBSc3R1ZGlvKQ0KICAgIDEuIE91dGxpbmUgdGFiIChSIE1hcmtkb3duIGluIFJzdHVkaW8pDQogICAgDQoqIEludHJvDQogICAgKyBTdGF0ZW1lbnQgb2YgUHJvYmxlbQ0KICAgICsgT3ZlcnZpZXcgb2YgRXhwZXJpbWVudGFsIERhdGENCiAgICArIE1lYXN1cmluZyB0aGUgRnJlc2hlbmluZyBQb3dlciBvZiB0aGUgSG9wIChyZWxhdGl2ZSB0byBub24tZHJ5aG9wcGVkIGNvbnRyb2xzKQ0KICAgICsgKmNhbGN1bGF0ZWQgbWV0cmljcyoNCiAgICAgICAgLSAkXERlbHRhJEFCViANCiAgICAgICAgLSAkXERlbHRhJEFCVw0KICAgICAgICAtICRcRGVsdGF7Y2FsY3VsYXRlZH0kQ08kXzIkDQogICAgICAgIC0gJFxEZWx0YSRTRyReezIwLzIwfSQNCiAgICAgICAgLSBmb2xkLWluY3JlYXNlIGV0aGFub2wgZnJvbSBob3AgYWRkaXRpb24gKGluICRcZnJhY3twb3VuZHN9e2JibH0kIGFuZCB3dCUgb3IgJFxmcmFje2dyYW1zfXsxMDAgbUx9JCkNCiAgICAgICAgLSBmb2xkLWluY3JlYXNlIENPJF8yJCBmcm9tIGhvcCBhZGRpdGlvbiAoaW4gZy9MIGFuZCBDTyRfMiQgdm9sdW1lcykNCiAgICAgICAgLSAkXERlbHRhJCAmZGVnO3BsYXRvDQoqIExvYWQgTGlicmFyaWVzDQoqIERhdGEgSW1wb3J0LCBUaWR5IGFuZCBUcmFuc2Zvcm0NCiogRGF0YSBWaXN1YWxpemF0aW9uDQogICAgKyBzY2F0dGVyIHBsb3RzDQogICAgKyBzY2F0dGVyIHBsb3RzIHdpdGggYWRkZWQgZGltZW5zaW9ucw0KKiBNb2RlbGluZyBvdmVydmlldw0KICAgICsgbGluZWFyIHJlZ3Jlc3Npb24NCiAgICArIHJlc2lkdWFscw0KKiBNb2RlbGluZyBGUEgNCg0KDQojIGludHJvDQoqIHRoaXMgaXMgbm90IGEgc3RhdHMgY291cnNlLiAgdXNlIGF0IHlvdXIgb3duIHJpc2suDQoqIFsqIkRhdGEgaXMgbm90IGluZm9ybWF0aW9uLiBJbmZvcm1hdGlvbiBpcyBub3Qga25vd2xlZGdlLiBBbmQga25vd2xlZGdlDQppcyBjZXJ0YWlubHkgbm90IHdpc2RvbS4iKl0oaHR0cDovL3d3dy5kYXRhZ292ZXJuYW5jZS5jb20vcXVvdGVzL2tub3dsZWRnZS1xdW90ZXMvICJDbGlmZm9yZCBTdG9sbCIpDQoqIFtXaWNraGFtJ3MgZm91ciBwaWxsYXJzIG9mIGRhdGEgYW5hbHlzaXM6XShodHRwczovL3I0ZHMuaGFkLmNvLm56LyAiR3JvbGVtdW5kIGFuZCBXaWNraGFtJ3MgUiBmb3IgRGF0YSBTY2llbmNlIikNCiAgICAqIHRpZHkNCiAgICAqIHRyYW5zZm9ybQ0KICAgICogdmlzdWFsaXplDQogICAgKiBtb2RlbA0KICAgIA0KVGhpcyBpcyBwcmltYXJpbHkgYW4gZXhjZXJjaXNlIGluIGRhdGEgd3JhbmdsaW5nICh0aWR5K3RyYW5zZm9ybSkgYW5kIG1hcmtkb3duIGZvcm1hdHRpbmcgdXNpbmcgUiBTdHVkaW8uICBJbiB0aGF0IGNvbnRleHQsIHRoaXMgYW5hbHlzaXMgaXMgYSAqcHJlbGltaW5hcnkqIGV4cGxvcmF0aW9uIG9mIHBvdGVudGlhbCBtZXRyaWNzIGZvciAqSG9wIEZyZXNoZW5pbmcgUG93ZXIqIChzeW5vbnltczogJ2RyeWhvcCBjcmVlcCcsICdBQlYgY3JlZXAnLCAnZHJ5LWhvcCBjcmVlcCcpIGFuZCB0byBtb2RlbCB0aGlzIGFzcGVjdCBvZiB0aGUgd2FybS1kcnlob3BwaW5nIHByb2Nlc3MuIA0KDQpgYGB7ciBlY2hvPVRSVUV9DQojICAgICAgICAgICAgICAgICAgICAgICBfXyAgLl9fICAgIC5fX18gICAgICANCiMgICAgICAgICAgICAgICAgICAgICBfLyAgfF98X198IF9ffCBfL19fLl9fLg0KIyAgICAgICAgICAgICAgICAgICAgIFwgICBfX1wgIHwvIF9fIDwgICB8ICB8DQojICAgICAgICAgICAgICAgICAgICAgIHwgIHwgfCAgLyAvXy8gfFxfX18gIHwNCiMgICAgICAgICAgICAgICAgICAgICAgfF9ffCB8X19cX19fXyB8LyBfX19ffA0KIyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXC9cLyAgICAgDQojICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXC8NCiMgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8fA0KIyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHx8DQojICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfHwNCiMgICAgICAgICAgICAgIF8gICAgICAgICAgICAgICAgICAgICAvXCAgICAgICAgICAgICAgIA0KIyAgICAgICAgICAgICB8IHxfIF8gXyBfXyBfIF8gXyAgX19fLyBffF9fXyBfIF8gXyBfXyAgDQojICAgICAgICAgICAgIHwgIF98ICdfLyBfYCB8ICcgXChfLTwgIF8vIF8gXCAnX3wgJyAgXCANCiMgICAgICAgICAgICAgIFxfX3xffCBcX18sX3xffHxfL19fL198IFxfX18vX3wgfF98X3xffA0KIyAgICAgICAgICAgICAgICAgICAgICAgIC8vICAgICAgICAgICAgICAgICAgICAgIFxcDQojICAgICAgICAgICAgICAgICAgICAgICAvLyAgICAgICAgICAgICAgICAgICAgICAgIFxcDQojICAgICAgICAgICAgICAgICAgICAgIC8vICAgICAgICAgICAgICAgICAgICAgICAgICBcXA0KIyAgICAgICAgICAgICAgICAgICAgIC8vICAgICAgICAgICAgICAgICAgICAgICAgICAgIFxcDQojICAgICAgICAgIF8gICAgICAgICAvLyAgIF8gXyAgICAgICAgICAgICAgICAgICAgICAgIFxcICAgXyAgICAgXyANCiMgICAgIF9fIF8oXylfX19fICBfIF9fIF98IChfKV9fX19fXyAgICAgICAgXyBfXyAgX19fICBfX3wgfF9fX3wgfA0KIyAgICAgXCBWIC8gKF8tPCB8fCAvIF9gIHwgfCB8XyAvIC1fKS0tLS0tLXwgJyAgXC8gXyBcLyBfYCAvIC1fKSB8DQojICAgICAgXF8vfF8vX18vXF8sX1xfXyxffF98Xy9fX1xfX198LS0tLS0tfF98X3xfXF9fXy9cX18sX1xfX198X3wNCiMNCiNhc2NpaSBmb250L2FydCBmcm9tIDxodHRwOi8vd3d3Lm5ldHdvcmstc2NpZW5jZS5kZS9hc2NpaS8+DQpgYGANCg0KDQojIyBTdGF0ZW1lbnQgb2YgUHJvYmxlbQ0KVGhlIHdhcm0tZHJ5aG9wcGluZyBwcm9jZXNzIGNhbiBwcm9mb3VuZGx5IGltcGFjdCB0aGUgYm9keSwgYWxjb2hvbCBhbmQgdmljaW5hbCBkaWtldG9uZSBjb250ZW50IG9mIHRoZSBiZWVyLiAgQ29tbW9uIG1ldHJpY3MgYW5kIG1vZGVscyBmb3IgdGhpcyBwaGVub21lbm9uIGhhdmUgbm90IGJlZW4gZXN0YWJsaXNoZWQuDQoNCiMjIE92ZXJ2aWV3IG9mIEV4cGVyaW1lbnRhbCBEYXRhDQpUaHJvdWdoIGV4cGVyaWVuY2UgaW4gYnJld2luZyB3ZSBrbm93IHRoYXQgKiplbmRwb2ludHMqKiBvZiBkcnlob3BwaW5nIGluY2x1ZGU6DQoNCiogZmxhdm9yIGltcGFjdCAoYWx3YXlzKQ0KKiBpbXBhY3Qgb24gdmlzdWFsL3ByZXNlbnRhdGlvbiAoc29tZXRpbWVzKQ0KKiAqZXRoYW5vbCBpbmNyZWFzZSogYWNjb21wYW5pZWQgYnkgZGVjcmVhc2UgaW4gc3BlY2lmaWMgZ3Jhdml0eSAoc29tZXRpbWVzKQ0KKiBDTzIgaW5jcmVhc2UgKHNvbWV0aW1lcykNCiogZGlhY2V0eWwvVkRLIGluY3JlYXNlIChzb21ldGltZXMpDQoqIGltcGFjdCBvbiB5ZWFzdCBxdWFsaXR5LCBhbmQgc28gb24uLi4NCg0KRXhwZXJpbWVudHMgd2VyZSBjYXJyaWVkIG91dCB3aGVyZSANCg0KMS4gbXVsdGlwbGUgcmVwbGljYXRlIDI1MC1tTCBzYW1wbGVzIG9mIFtuZWFybHldIGVuZC1mZXJtZW50ZWQgYmVlciB3ZXJlIGNvbGxlY3RlZCBmcm9tIGZlcm1lbnRlcnMgaW50byAxMm96IGFtYmVyIGJvdHRsZXMNCjEuIHRoZSBzYW1wbGVzIHdlcmUgcmFuZG9taXplZCBpbnRvIGdyb3VwcyBvZiB0aHJlZSwgdGhlbiBlYWNoIGdyb3VwIHdhcyBlaXRoZXINCiAgICAqIGRyeS1ob3BwZWQgYXQgfjEuMCBwb3VuZHMvYmJsICh0cmVhdG1lbnQpLCBvcg0KICAgICogbm90IGRyeS1ob3BwZWQgKGNvbnRyb2wpDQoxLiBhbGwgd2VyZSBoYW5kLWNyaW1wZWQgd2l0aCBmb2lsLCBzdG9yZWQgZm9yIHZhcmlvdXMgdGltZXMgKG1vc3RseSBhdCByb29tIHRlbXAsIGEgZmV3IGluIHRoZSBjb2xkKSwgdGhlbiB0ZXN0ZWQgb24gYW4gQW50b24gUGFhciBETUE0NTAwL0FsY29seXplciBjbGFzc2ljDQogICAgICAgIA0KRm9jdXNpbmcgb24gKmV0aGFub2wgaW5jcmVhc2UgYXMgdGhlIGVuZHBvaW50Kiwgd2Uga25vdyB0aGVzZSB0byBiZSAqKnJlbGV2YW50IGZhY3RvcnMqKjoNCg0KKiB2YXJpZXR5IG9mIGhvcHMgKGZpdmUgZGlmZmVyZW50IHZhcmlldGllcykNCiogcHJlc2VuY2Ugb2YgbGl2ZSB5ZWFzdCAodHJ1ZSBpbiBhbGwgY2FzZXMgZm9yIHRoZXNlIGRhdGEpDQoqIHRlbXBlcmF0dXJlIGR1cmluZyBkcnlob3BwaW5nIChtb3N0bHkgd2FybSwgd2l0aCBhIGZldyBpbiB0aGUgY29sZCkNCiogYW1vdW50IG9mIGNvbnRhY3QgdGltZSBiZXR3ZWVuIGhvcHMgYW5kIGJlZXIgKHVwIHRvIDcgd2Vla3MpDQoNCipGb3IgbW9yZSBkZXRhaWxzIG9uIHRoaXMgZGF0YXNldCBzZWUgdGhlIFttYW51c2NyaXB0XShodHRwczovL3d3dy50YW5kZm9ubGluZS5jb20vZG9pL2Z1bGwvMTAuMTA4MC8wMzYxMDQ3MC4yMDE4LjE0NjkwODE/c2Nyb2xsPXRvcCZuZWVkQWNjZXNzPXRydWUgIktpcmtlbmRhbGwyMDE4IikuICANCiogcGxlYXNlIHNlbmQgY29tbWVudHMsIGNvbXBsYWludHMgYW5kIGNvcnJlY3Rpb25zIHRvIGx1a2UuY2hhZHdpY2tAZ21haWwuY29tIQ0KKiBEYXRhIGFyZSBmcm9tIHRoZSBmaWxlICJ1amJjX2FfMTQ2OTA4MV9zbTU0OTYudHh0IiAoc3VwcGxlbWVudGFyeSBkYXRhIGZyb20gSmFjb2IgQS4gS2lya2VuZGFsbCwgQ2FydGVyIEEuIE1pdGNoZWxsICYgTHVjYXMgUi4gQ2hhZHdpY2sgKDIwMTgpOiBUaGUgRnJlc2hlbmluZyBQb3dlciBvZiBDZW50ZW5uaWFsIEhvcHMsICpKb3VybmFsIG9mIHRoZSBBbWVyaWNhbiBTb2NpZXR5IG9mIEJyZXdpbmcgQ2hlbWlzdHMqIFZvbHVtZSA3NiwgSXNzdWUgMywgUGFnZXMgMTc4LTE4NCAoKioyMDE4KiopIERPSTogMTAuMTA4MC8wMzYxMDQ3MC4yMDE4LjE0NjkwODEqIGF2YWlsYWJsZSBmcm9tIDxodHRwczovL3d3dy50YW5kZm9ubGluZS5jb20vZG9pL2Z1bGwvMTAuMTA4MC8wMzYxMDQ3MC4yMDE4LjE0NjkwODE/c2Nyb2xsPXRvcCZuZWVkQWNjZXNzPXRydWU+DQoNCg0KIyBsb2FkIGxpYnJhcmllcw0KYGBge3IgcGFja2FnZS5sb2FkLCByZXN1bHRzPSdoaWRlJywgZWNobz1UUlVFfSANCiMucnMucmVzdGFydFIoKSAgIyByZXN0YXJ0IFIgDQpzZXNzaW9uSW5mbygpDQoNCiNkYXRhIHdyYW5nbGluZw0KI2luc3RhbGwucGFja2FnZXMoImRwbHlyIikgICMgZHBseXIgImRhdGEgcGx5ZXIiICANCmxpYnJhcnkoZHBseXIpDQpsaWJyYXJ5KGdncGxvdDIpDQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCg0KbGlicmFyeShjYXJldCkgIyBmb3IgZHVtbXlWYXJzIGZ1bmN0aW9uDQpsaWJyYXJ5KHBzeWNoKSAjIGZvciBwYWlycy5wYW5lbHMgZnVuY3Rpb24NCmxpYnJhcnkobWVtaXNjKSAgIyBjb21wYXJlIG1vZGVscw0KDQpsaWJyYXJ5KGRyYykgICMgImRyYyIgPSBkb3NlIHJlc3BvbnNlIGN1cnZlcyAoY29udGFpbnMgYSBzdWl0ZSBvZiBmbGV4aWJsZSBhbmQgdmVyc2F0aWxlIG1vZGVsIGZpdHRpbmcgYW5kIGFmdGVyLWZpdHRpbmcgZnVuY3Rpb25zKQ0KbGlicmFyeShwbG90bHkpDQpsaWJyYXJ5KGdyaWRFeHRyYSkgICMgZm9yIGdyaWQuYXJyYW5nZSBpbiBSIG1hcmtkb3duDQpgYGANCg0KDQoNCj4gZmxhc2ggZm9yd2FyZCEgc2Nyb2xsIHRvIHRoZSBib3R0b20gYW5kIHJ1biBiaWdjaHVua190aWR5dHJhbnNmb3JtDQoNCg0KI2ltcG9ydCBkYXRhDQpgYGB7cn0NCnJtKGxpc3QgPSBscygpKSAjIGNsZWFyIHdvcmtzcGFjZQ0KbXlkYXRhIDwtcmVhZC5jc3YoInVqYmNfYV8xNDY5MDgxX3NtNTQ5Ni50eHQiLHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRSkgICMjIFNQRUNJRlkgZmlsZW5hbWUNCg0KZHB1dChuYW1lcyhteWRhdGEpKQ0KYGBgDQoNCg0KIyAhbWFudWFsIGFkZCB0aW1lIHplcm8gdG8gImRheXMgb24gaG9wcyIgZXhwZXJpbWVudCENCkFudG9uIFBhYXIgZGF0YSB3ZXJlIGNvbGxlY3RlZCBhdCB0aW1lIHplcm8gZm9yIGtpbmV0aWNzIGV4cGVyaW1lbnQgKHRyaXBsaWNhdGUgc2FtcGxlcyBydW4gaW1tZWRpYXRlbHkgYWZ0ZXIgc2FtcGxlIGNvbGxlY3Rpb24sIHdoaWxlIHRoZSByZW1haW5kZXIgd2VyZSBiZWluZyBkcnktaG9wcGVkKSwgYnV0IHdlcmUgaW5hZHZlcnRlbnRseSBleGNsdWRlZCBmcm9tIHRoZSBvcmlnaW5hbCBkYXRhc2V0LiAgSGVyZSB0aGV5IGFyZToNCkRhdGUJdGltZQlzYW1wbGUgbm8uCUFCVglBQlcJT0UgKFApCUVyCUVhCVNHIDIwLzIwCVJERglBREYgc2FtcGxlX2lkDQowNy8yNy8xNwkzOjI2IFBNCTAzXwk2LjY4CTUuMgkxNS45NQk2LjA4CTMuNzQJMS4wMTQ2Mgk2My44OAk3Ni41OAlKSzEtMjAtNDENCjA3LzI3LzE3CTM6MzAgUE0JMDRfCTYuNjgJNS4yCTE1Ljk1CTYuMDgJMy43MwkxLjAxNDYxCTYzLjg5CTc2LjYJSksxLTIwLTUwDQowNy8yNy8xNwkzOjM0IFBNCTA1Xwk2LjY4CTUuMgkxNS45NQk2LjA4CTMuNzQJMS4wMTQ2Mgk2My44OAk3Ni41OAlKSzEtMjAtODcNCg0KYGBge3J9DQojIyBmaWxsIGluIE1JU1NJTkcgREFUQSA6KCAgYWRkIHRpbWUgemVybyBkYXRhIGZvciB0aW1lIHNlcmllcyBhbmFseXNpcykNCg0KIyMgbmVzdGVkIGlmZWxzZSB0byBhZGQgdGltZSB6ZXJvIHBsYXRvIHZhbHVlcyBmb3IgZWFjaCBleHB0Og0KbXlkYXRhPC0gbXlkYXRhICU+JSBtdXRhdGUoLiwgaW5pdGlhbF9wbGF0byA9IGlmZWxzZShleHB0PT0iIDJBIiwgMy43NCwgaWZlbHNlKGV4cHQ9PSIgMUEiLCAzLjY5LCBpZmVsc2UoZXhwdD09IiAxQiIsIDMuNTMsIGlmZWxzZShleHB0PT0iIDNCIiwgMy41NCwgaWZlbHNlKGV4cHQ9PSIgNEIiLCA0LjY2LCBOQSkpKSkpKQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgDQojIyBhZGQgY29tcGxldGUgdGltZS16ZXJvIGFudG9uIHBhYXIgZGF0YSBmb3IgdGltZSBzZXJpZXMgKGV4cHQyQSkNCiMjIHF1YWRydXBsZS11cCBlYWNoIHRyaXBsaWNhdGUgdGltZS16ZXJvIG1lYXN1cmVtZW50IChvbmUgY29weSBmb3IgZWFjaCBOSC9ESCAmIHJvdXNlL3N0aWxsIGNvbWJvKQ0KIyMgbm90ZTogIHRoaXMgcHJhY3RpY2UgaXMgb2YgcXVlc3Rpb25hYmxlIHN0YXRpc3RpY2FsIHJpZ29yLiAgSW4gcmV0cm9zcGVjdCBpdCB3b3VsZCBoYXZlIGJlZW4gYmV0dGVyIChhbmQgYSBsb3QgZWFzaWVyKSB0byBqdXN0IHJ1biAxMiBzYW1wbGVzIHRocm91Z2ggQW50b24gb24gZGF5MCENCnRpbWV6ZXJvIDwtIG15ZGF0YSAlPiUgZmlsdGVyKGV4cHQ9PSIgMkEiKSAlPiUgYXJyYW5nZShzcGVjaWFsX2dyb3VwKSAlPiUgc2xpY2UoMToxMikNCiNkYXRhLmVudHJ5KHRpbWV6ZXJvKQ0KdGltZXplcm8kVGVzdF9EYXRlIDwtIHJlcChjKCI3LzI3LzIwMTciLCI3LzI3LzIwMTciLCI3LzI3LzIwMTciKSw0KQ0KdGltZXplcm8kVGVzdC50aW1lIDwtIHJlcChjKCIzOjI2IFBNIiwgIjM6MzAgUE0iLCAiMzozNCBQTSIpLDQpDQp0aW1lemVybyRBQlYgPC0gNi42OA0KdGltZXplcm8kQUJXIDwtIDUuMg0KdGltZXplcm8kT0UgPC0gMTUuOTUNCnRpbWV6ZXJvJEVyIDwtIDYuMDgNCnRpbWV6ZXJvJEVhIDwtIHJlcChjKDMuNzQsIDMuNzMsIDMuNzQpLDQpDQp0aW1lemVybyRTRyA8LSByZXAoYygxLjAxNDYyLCAxLjAxNDYxLCAxLjAxNDYyKSw0KQ0KdGltZXplcm8kUkRGIDwtIHJlcChjKDYzLjg4LCA2My44OSwgNjMuODgpLDQpDQp0aW1lemVybyRBREYgPC0gcmVwKGMoNzYuNTgsIDc2LjYwLCA3Ni41OCksNCkNCnRpbWV6ZXJvJENhbG9yaWVzIDwtIHJlcChjKDIxNS4zNywyMTUuMzQsMjE1LjM1KSw0KQ0KdGltZXplcm9bdGltZXplcm8kaG9wPT0iREgiLF0kY29udGFjdF9kYXlzIDwtMA0KdGltZXplcm9bLGMoMjg6MzEpXSA8LSAwDQpteWRhdGEgPC0gcmJpbmQodGltZXplcm8sbXlkYXRhKQ0Kd3JpdGUuY3N2KG15ZGF0YSwgInF1aWNraWUuY3N2Iiwgcm93Lm5hbWVzID0gRkFMU0UpDQpteWRhdGE8LXJlYWQuY3N2KCJxdWlja2llLmNzdiIsIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRSkNCg0KIyMjICB0aGlzIGlzIGEgcm91dGluZSBmb3IgaW5zcGVjdGluZyBkYXRhICMjIw0KbXlkYXRhdHlwZXM8LSBhcy5kYXRhLmZyYW1lKGNiaW5kKHNhcHBseShteWRhdGEsIGNsYXNzKSkpICAjIyB0YWJsZSBvZiBkYXRhdHlwZXMNCm15ZGF0YXR5cGVzJGhlYWRlcnMgPC0gcm93bmFtZXMobXlkYXRhdHlwZXMpICAgICMjIGNvbnZlcnQgcm93bmFtZXMgdG8gdmFsdWVzDQpuYV9jb3VudCA8LWFzLmRhdGEuZnJhbWUoc2FwcGx5KG15ZGF0YSwgZnVuY3Rpb24oeSkgc3VtKGxlbmd0aCh3aGljaChpcy5uYSh5KSkpKSkpICMjIGNvdW50IE5BcyBieSBjb2x1bW4NCm15ZGF0YU92ZXJ2aWV3PC1hcy5kYXRhLmZyYW1lKGNiaW5kKG5hX2NvdW50LG15ZGF0YXR5cGVzKSkgICMjIHRhYmxlIG9mIE5BIGNvdW50cyBhbmQgZGF0YXR5cGVzDQpuYW1lcyhteWRhdGFPdmVydmlldykgPC0gYygiTkFfY291bnQiLCJEYXRhX0NsYXNzIiwgIkhlYWRlciIpDQojc3VtKGlzLm5hKG15ZGF0YSkpICAjIyB0b3RhbCBjb3VudCBvZiBOQSB2YWx1ZXMgaW4gZW50aXJlIHNoZWV0ICgyMzEgaW4gdGhpcyBjYXNlKQ0KI215ZGF0YU92ZXJ2aWV3ICU+JSBmaWx0ZXIoTkFfY291bnQ+MCkgICAgIyMgYnJlYWtkb3duIG9mIE5BIHZhbHVlcw0KYGBgDQoNCg0KIyB0aWR5ICYgdHJhbnNmb3JtIA0KDQo+IG5vdGU6IHRoaXMgaXMgYSBiYXNlIFIgK2RwbHlyIGRhdGEgd3JhbmdsaW5nIGV4ZXJjaXNlISBpbiBnZW5lcmFsIGl0J3MgYmVzdCB0byB1c2UgbHVicmlkYXRlIHBhY2thZ2UgZm9yIGRhdGUvdGltZXN0YW1wcyEgDQoNCiogY3JlYXRlIGEgZGF0ZSBjb2x1bW4uICBIZXJlIHdlIGNyZWF0ZSBuZXcgZGF0ZSBjb2x1bW5zIGluIFBPU0lYY3QgZm9ybWF0LCBiYXNlZCBvbiB0aGUgcGFydGljdWxhciBkYXRlIGZvcm1hdCB1c2VkIGluIHRoZSBzb3VyY2UgZGF0YSAodWpiY19hXzE0NjkwODFfc201NDk2LnR4dDsgc2VlID9hcy5QT1NJWGN0KS4gIEZpcnN0IGFuIGV4YW1wbGUgb2YgaG93IE5PVCB0byBjcmVhdGUgbmV3IHZhcmlhYmxlcyBpbiBvdXIgZGF0YWZyYW1lOg0KYGBge3J9DQp0ZXN0X2RmPC0gbXlkYXRhICMgKG9yaWdpbmFsIGRhdGFmcmFtZSB0byBiZSB1c2VkIGZvciB0ZXN0aW5nL2lsbHVzdHJhdGlvbikNCg0KeF9icmV3X2RhdGUuUE9TSVhjdDwtIGFzLlBPU0lYY3QobXlkYXRhJGJyZXdfZGF0ZSwgZm9ybWF0ID0gIiVtLyVkLyVZIikgIyBhcyBhIHN0YW5kYWxvbmUgdmVjdG9yIChVU0VMRVNTIGhlcmUpLCBvcjoNCnRlc3RfZGYkYnJld19kYXRlLlBPU0lYY3QgPC1hcy5QT1NJWGN0KG15ZGF0YSRicmV3X2RhdGUsIGZvcm1hdCA9ICIlbS8lZC8lWSIpICAjIGFzIGEgIm5ldyBjb2x1bW4iIGluIG91ciBkYXRhZnJhbWUNCmBgYA0KDQoNCiogQURWQU5DRUQgZGF0YSB3cmFuZ2xpbmc6IA0KICAgICogY3JlYXRlIG11bHRpcGxlIGRhdGUgY29sdW1ucyB1c2luZyBGT1JMT09QDQooaGVyZSB3ZSB0YWtlIGFkdmFudGFnZSBvZiB0aGUgcGF0dGVybiB0aGF0IG9mIG91ciAoY2hhcmFjdGVyKSBkYXRlL3RpbWUgY29sdW1ucyBoYXZlIHRoZSBzdHJpbmcgImRhdGUiIGluIHRoZSBoZWFkZXJuYW1lLiAgRmlyc3QgbWFrZSBjaGFyYWN0ZXIgdmVjdG9yIG9mIGFsbCBjb2x1bW4gbmFtZXMgY29udGFpbmluZyBzdHJpbmcgImRhdGUiLCB0aGVuIHJ1biBGT1JMT09QIG92ZXIgZWFjaCBkYXRlIGNvbHVtbikuICBUaGlzIGZvcmxvb3Agd2lsbCBjb252ZXJ0IGRhdGVjb2xzIGludG8gUE9TSVhjdCBmb3JtYXQgKFIvdW5pdmVyc2FsIGRhdGV0aW1lIGZvcm1hdCkuICBUaGV5IHNheSBpdCdzICAqKmJlc3QgdG8gYXZvaWQgZm9ybG9vcHMqKiBpbiBnZW5lcmFsLCBhbmQgKippbiB0aGlzIGNhc2UgeW91IHdvdWxkIHByb2JhYmx5IHVzZSBmdW5jdGlvbnMgZnJvbSBsdWJyaWRhdGUgcGFja2FnZSoqIC0gYW5kIGluIGdlbmVyYWwgYmUgYXdhcmUgdGhlcmUncyBwcm9iYWJseSBhIHNwZWNpZmljIHRvb2wgZm9yIHRoZSB0YXNrIGF0IGhhbmQpISENCmBgYHtyfQ0KZGF0ZWNvbHM8LSBkcHV0KG5hbWVzKGRwbHlyOjpzZWxlY3QobXlkYXRhLCBtYXRjaGVzKCJkYXRlIikpKSkgICMjIGhlYWRlcnMgY29udGFpbmluZyBzdHJpbmcgImRhdGUiID0+IGMoImJyZXdfZGF0ZSIsICJzYW1wbGVfY29sbGVjdGlvbl9kYXRlIiwgImRyeWhvcF9kYXRlIiwgIlRlc3RfRGF0ZSIpDQojIyBGT1JMT09QDQpmb3IgKGljb2wgaW4gZGF0ZWNvbHMpIHsNCiAgbmV3Y29sID0gcGFzdGUwKGljb2wsIi5QT1NJWGN0IikNCiAjIHByaW50KG5ld2NvbCkNCiAgbXlkYXRhWywgbmV3Y29sXSA9IGFzLlBPU0lYY3QobXlkYXRhWywgaWNvbF0sZm9ybWF0ID0gIiVtLyVkLyVZIikgIyMjICBDUkVBVEUgTkVXIGNvbHVtbnMgd2l0aCBQT1NJWGN0ICAgDQojICBteWRhdGFbLCBuZXdjb2xdID0gYXMuUE9TSVhjdChhcy5udW1lcmljKG15ZGF0YVssIGljb2xdKSAgKiAoNjAqNjAqMjQpLCBvcmlnaW49IjE4OTktMTItMzAiKSAjIyMgIG1pY3Jvc29mdCB0aW1lcw0KfQ0KYGBgDQoNCg0KKiBjcmVhdGUgZGF5b2ZhZGRpdGlvbiwgZGF5c29uaG9wcywgaG9wc19nXzEwMG1MLCBwb3VuZHNfYmJsIHZhcmlhYmxlcyB3aXRoIGRwbHlyICJtdXRhdGUiDQpgYGB7cn0NCm15ZGF0YTwtIG15ZGF0YSAlPiUgDQogIG11dGF0ZShkYXlvZmFkZGl0aW9uID0gYXMuaW50ZWdlcihhcy5udW1lcmljKGRpZmZ0aW1lKGRyeWhvcF9kYXRlLlBPU0lYY3QsYnJld19kYXRlLlBPU0lYY3QpKSksDQogICAgICAgICBkYXlzb25ob3BzID0gYXMuaW50ZWdlcihhcy5udW1lcmljKGRpZmZ0aW1lKFRlc3RfRGF0ZS5QT1NJWGN0LGRyeWhvcF9kYXRlLlBPU0lYY3QpKS8oNjAqNjAqMjQpKSwNCiAgICAgICAgIGhvcHNfZ18xMDBtTCA9IChtZ19ob3BzL3ZvbHVtZV9tTCkvMTAsDQogICAgICAgICBwb3VuZHNfYmJsID0gKG1nX2hvcHMvdm9sdW1lX21MKSoxMTcvNDU0DQogICAgICAgICApDQpgYGANCg0KDQoqIEFEVkFOQ0VEIGRhdGEgd3JhbmdsaW5nOiBtYW51YWxseSB1bnBhY2sgb3ZlcmxvYWRlZCBjb2x1bW5zIHdpdGggZ3JlcGwNCmBgYHtyfQ0KbXlkYXRhJE9YPC1ncmVwbCgiT1giLCBteWRhdGEkSG9wX3R5cGUpICAgICAgICMjICBjcmVhdGUgbG9naWNhbCAiT1giIGNvbHVtbg0KbXlkYXRhJHJvdXNlPC1ncmVwbCgicm91c2UiLCBteWRhdGEkc3BlY2lhbF9ncm91cCkjIyAgY3JlYXRlIGxvZ2ljYWwgY29sdW1uDQpteWRhdGEkR3JpbmQ8LWdyZXBsKCJHcmluZCIsIG15ZGF0YSRIb3BfdHlwZSkgICAgICMjICBjcmVhdGUgbG9naWNhbCBjb2x1bW4NCm15ZGF0YSRDb25lPC1ncmVwbCgiQ29uZSIsIG15ZGF0YSRIb3BfdHlwZSkgICAgICAgIyMgIGNyZWF0ZSBsb2dpY2FsIGNvbHVtbg0KbXlkYXRhJGhhcnZlc3QyMDE0PC1ncmVwbCgiMTQiLCBteWRhdGEkSG9wX3R5cGUpICAjIyAgY3JlYXRlIGxvZ2ljYWwgY29sdW1uDQpteWRhdGEkaGFydmVzdDIwMTU8LWdyZXBsKCIxNSIsIG15ZGF0YSRIb3BfdHlwZSkgICMjICBjcmVhdGUgbG9naWNhbCBjb2x1bW4NCm15ZGF0YSRoYXJ2ZXN0MjAxNzwtZ3JlcGwoIjE3IiwgbXlkYXRhJEhvcF90eXBlKSAgIyMgIGNyZWF0ZSBsb2dpY2FsIGNvbHVtbg0KYGBgDQoNCiogQURWQU5DRUQgZGF0YSB3cmFuZ2xpbmc6IGlmZWxzZSBzdGF0ZW1lbnRzIGZvciBoYXJ2ZXN0eWVhciwgZm9ybV9vZl9ob3BzLCBhbmQgREhfdGVtcA0KYGBge3J9DQojIyBpZmVsc2Ugc3RhdGVtZW50IGZvciBoYXJ2ZXN0eWVhciAoaWYgbmVpdGhlciAyMDE0IG5vciAyMDE1IG5vciAyMDE3LCB0aGVuIDIwMTYpDQpteWRhdGEkaGFydmVzdFllYXIgPC0gaWZlbHNlKA0KICBteWRhdGEkaGFydmVzdDIwMTQ9PVRSVUUsIDIwMTQsDQogIGlmZWxzZShteWRhdGEkaGFydmVzdDIwMTU9PVRSVUUsIDIwMTUsIA0KICAgICAgICAgaWZlbHNlKG15ZGF0YSRoYXJ2ZXN0MjAxNz09VFJVRSwgMjAxNywgMjAxNikpKQ0KZHB1dChsZXZlbHMoYXMuZmFjdG9yKG15ZGF0YSRoYXJ2ZXN0WWVhcikpKQ0KIyMgaWZlbHNlIHN0YXRlbWVudCBmb3IgZm9ybSBvZiBob3BzIChpZiBuZWl0aGVyIGNvbmUgbm9yIGdyb3VuZCBub3IgTkgsIHRoZW4gcGVsbGV0KSANCm15ZGF0YSRmb3JtX29mX2hvcHMgPC0gaWZlbHNlKA0KICBteWRhdGEkQ29uZT09VFJVRSwgImNvbmUiLA0KICBpZmVsc2UobXlkYXRhJEdyaW5kPT1UUlVFLCAiZ3JvdW5kIiwNCiAgICAgICAgIGlmZWxzZShteWRhdGEkSG9wX3R5cGU9PSJOSCIsICJOSCIsICJwZWxsZXQiKSkpDQpkcHV0KGxldmVscyhhcy5mYWN0b3IobXlkYXRhJGZvcm1fb2ZfaG9wcykpKQ0KIyMgaWZlbHNlIHN0YXRlbWVudCBmb3IgdGVtcGVyYXR1cmUgZ3JlYXRlciBvciBsZXNzIHRoYW4gMTAgDQpteWRhdGEkREhfdGVtcCA8LSBpZmVsc2UoDQogIG15ZGF0YSR0ZW1wLkM8MTAsICJjb2xkIiwNCiAgaWZlbHNlKG15ZGF0YSR0ZW1wLkM+MTAsICJ3YXJtIiwgInNvbWV0aGluZyBlbHNlIikpDQpkcHV0KGxldmVscyhhcy5mYWN0b3IobXlkYXRhJERIX3RlbXApKSkNCmBgYA0KDQoqIEFEVkFOQ0VEIGRhdGEgd3JhbmdsaW5nOiBjcmVhdGUgInZhcmlldHkiIGNvbHVtbiBzdGFydGluZyB3aXRoIG92ZXJsb2FkZWQgIkhvcF90eXBlIiBjb2x1bW4sIHRoZW4gc3RyaXBwaW5nIGF3YXkgYWxsIHRoZSBub24tdmFyaWV0eSBpbmZvcm1hdGlvbg0KYGBge3J9DQpteWRhdGEkdmFyaWV0eTwtbXlkYXRhJEhvcF90eXBlDQpteWRhdGEkdmFyaWV0eTwtIGdzdWIoIlswLTldKyIsIiIsIG15ZGF0YSR2YXJpZXR5KSAjIyByZW1vdmUgYWxsIG51bWJlcnMNCm15ZGF0YSR2YXJpZXR5PC0gZ3N1YigiT1giLCIiLCBteWRhdGEkdmFyaWV0eSkgICAgICMjIHJlbW92ZSBzcGVjaWZpYyB0ZXh0DQpteWRhdGEkdmFyaWV0eTwtIGdzdWIoIkdyaW5kIiwiIiwgbXlkYXRhJHZhcmlldHkpDQpteWRhdGEkdmFyaWV0eTwtIGdzdWIoIkNvbmUiLCIiLCBteWRhdGEkdmFyaWV0eSkNCm15ZGF0YSR2YXJpZXR5PC0gZ3N1YigiICIsIiIsIG15ZGF0YSR2YXJpZXR5KSAgICAgICMjIHJlbW92ZSBzcGFjZXMNCmRwdXQobGV2ZWxzKGFzLmZhY3RvcihteWRhdGEkdmFyaWV0eSkpKQ0KYGBgDQoNCiogQURWQU5DRUQgZGF0YSB3cmFuZ2xpbmc6IGNyZWF0ZSAiRVhQVG5ldyIgKGVhY2ggdW5pcXVlIGV4cGVyaW1lbnRhbCBncm91cCBkZXNpZ25hdGVkIGluIGEgc2luZ2xlIHZhcmlhYmxlKQ0KYGBge3J9DQpteWRhdGE8LSBteWRhdGEgJT4lIG11dGF0ZShFWFBUbmV3PXBhc3RlMCgiZ3JvdXAiLGV4cHQsIHN1YnN0cihzcGVjaWFsX2dyb3VwLCAxLDIpLGFzLmNoYXJhY3Rlcihyb3VzZSkpKQ0KIyBjbGVhbiBpdCB1cCBieSByZW1vdmluZyAiTkEiIGFuZCBhbnkgc3BhY2VzIGR1ZSB0byBjYW5hcnljb2RlIGJ1Zw0KbXlkYXRhJEVYUFRuZXcgPC0gZ3N1YigiICIsIiIsIG15ZGF0YSRFWFBUbmV3KSAgIyMgcmVtb3ZlIGFueSBzcGFjZXMNCm15ZGF0YSRFWFBUbmV3IDwtIGdzdWIoIk5BIiwiIiwgbXlkYXRhJEVYUFRuZXcpICMjIHJlbW92ZSAiTkEiDQpkcHV0KGxldmVscyhhcy5mYWN0b3IobXlkYXRhJGV4cHQpKSkNCmRwdXQobGV2ZWxzKGFzLmZhY3RvcihteWRhdGEkRVhQVG5ldykpKQ0KYGBgDQoNCg0KIyMgc2VsZWN0IGFuZCByZWFycmFuZ2UgY29sdW1ucyANCihleHBlcmltZW50YWwgZmFjdG9ycyBvbiB0aGUgbGVmdCwgdGhlbiBtZWFzdXJlbWVudHMsIGZvbGxvd2VkIGJ5IGNhbGN1bGF0aW9ucyBhbmQgdGhlbiBhbGwgZGF0ZSBjb2x1bW5zIG9uIHRoZSByaWdodCk6DQpgYGB7cn0NCm15ZGF0YSA8LSBteWRhdGEgJT4lIA0KICBkcGx5cjo6c2VsZWN0KHNhbXBsZV9pZCxleHB0LCBFWFBUbmV3LCBob3AsIEJJTmhvcCwgdmFyaWV0eSwgT1gsIGhhcnZlc3RZZWFyLCBmb3JtX29mX2hvcHMsc3BlY2lhbF9jb25kaXRpb25zLCByb3VzZSwgZGF5c29uaG9wcywgZGF5b2ZhZGRpdGlvbiwgREhfdGVtcCwgdGVtcC5DLCBob3BzX2dfMTAwbUwsIHBvdW5kc19iYmwsDQogICAgICAgICBBQlYsIEFCVywgT0UsIEVyLCBFYSwgU0csIFJERiwgQURGLCBDYWxvcmllcywgDQogICAgICAgICBkaG9wX2RheSwgY29udGFjdF9kYXlzLCBSRUZfTkgsIEFCVl9pbmNyZWFzZSwgDQogICAgICAgICBicmV3X2RhdGUuUE9TSVhjdCwgc2FtcGxlX2NvbGxlY3Rpb25fZGF0ZS5QT1NJWGN0LCBkcnlob3BfZGF0ZS5QT1NJWGN0LFRlc3RfRGF0ZS5QT1NJWGN0LCBpbml0aWFsX3BsYXRvKQ0KIyMgcmVtb3ZlICIuUE9TSVhjdCIgc3VmZml4LiAgTGVhdmluZyBpdCBhcy1pcyB3aWxsIG9ubHkgYWRkIHRvIGNvbmZ1c2lvbiBpZi93aGVuIHRoZXNlIGRhdGEgYXJlIHNhdmVkIGFuZCByZS1pbXBvcnRlZCAoYW5kIGJlY29tZSAnY2hhcmFjdGVyJyBmb3JtYXQgYWdhaW4hKQ0KY29sbmFtZXMobXlkYXRhKSA9IGdzdWIoIi5QT1NJWGN0IiwgIiIsIGNvbG5hbWVzKG15ZGF0YSkpDQpgYGANCg0KDQoNCiMjY29tcHV0ZSBtZWFuIE5IIChjb250cm9sKSB2YWx1ZXMgDQooTkggPSBub3QgZHJ5LWhvcHBlZCkgDQpgYGB7cn0NCiMjIG1lYW4gTkggKGNvbnRyb2wpIHZhbHVlcyBmb3IgZWFjaCBFWFBUbmV3IGdyb3VwDQptZWFuLmNvbnRyb2xfTkg8LSBteWRhdGEgJT4lIA0KICBncm91cF9ieShFWFBUbmV3KSAlPiUNCiAgZmlsdGVyKGhvcD09Ik5IIikgJT4lDQogIHN1bW1hcmlzZV9hdCh2YXJzKEFCViwgQUJXLCBFYSwgU0cpLGZ1bnMobWVhbiwgbigpKSkNCiMjIHJlcGxhY2UgIl9tZWFuIiB3aXRoICIuY29udHJvbF9OSCIgaW4gY29sdW1uIG5hbWVzDQpjb2xuYW1lcyhtZWFuLmNvbnRyb2xfTkgpID0gZ3N1YigiX21lYW4iLCAiLmNvbnRyb2xfTkgiLCBjb2xuYW1lcyhtZWFuLmNvbnRyb2xfTkgpKQ0KYGBgDQoNCg0KIyNjb21wdXRlICRcRGVsdGEkIHZhbHVlcyAoZGlmZmVyZW5jZXMgcmVsYXRpdmUgdG8gTkggY29udHJvbHMpIHVzaW5nIG9iamVjdHMgY3JlYXRlZCBhYm92ZS4uLg0KKiBkaWZmZXJlbmNlIG9mIGVhY2ggQUJWLEFCVyxFYSwgYW5kIFNHIGRhdGEgcG9pbnQgZnJvbSBjb3JyZXNwb25kaW5nIG1lYW4uY29udHJvbF9OSCB2YWx1ZXMgKHVuaG9wcGVkIHNhbXBsZXMgaW4gc2FtZSBFWFBUbmV3IGdyb3VwKQ0KYGBge3J9DQojIyBmaXJzdCBqb2luIG91ciBkYXRhIHdpdGggbWVhbiBBQlYgZm9yIHVuaG9wcGVkIHNhbXBsZXMgaW4gZ2l2ZW4gZXhwZXJpbWVudCAobWVhbi5jb250cm9sX05IOyBjYWxjdWxhdGVkIGFib3ZlKQ0KDQpGUEhjYWxjPC0gbGVmdF9qb2luKG15ZGF0YSwgbWVhbi5jb250cm9sX05ILCBieT0iRVhQVG5ldyIpDQoNCiMjIGNhbGN1bGF0ZSBBQldfaW5jcmVhc2UgYnkgc3VidHJhY3RpbmcgZWFjaCBpbmRpdmlkdWFsIEFCVyBtZWFzdXJlbWVudCBmcm9tIHJlc3BlY3RpdmUgbWVhbi5jb250cm9sX05IOg0KRlBIY2FsYyRkZWx0YS5BQlYgPC0gRlBIY2FsYyRBQlYgLSBGUEhjYWxjJEFCVi5jb250cm9sX05IDQpGUEhjYWxjJGRlbHRhLkFCVyA8LSBGUEhjYWxjJEFCVyAtIEZQSGNhbGMkQUJXLmNvbnRyb2xfTkgNCkZQSGNhbGMkZGVsdGEucGxhdG8gPC0gRlBIY2FsYyRFYSAtIEZQSGNhbGMkRWEuY29udHJvbF9OSA0KRlBIY2FsYyRkZWx0YS5TRyA8LSBGUEhjYWxjJFNHIC0gRlBIY2FsYyRTRy5jb250cm9sX05IDQoNCiMjIHRoZSBjb250cm9sIHNhbXBsZXMgaGF2ZSBzZXJ2ZWQgdGhlaXIgcHVycG9zZSwgbm93IHJlbW92ZSB0aGVtIGZyb20gZGF0YXNldC4gVGhlIGZvbGxvd2luZyBjYWxjdWxhdGlvbnMgYXJlIG9ubHkgbWVhbmluZ2Z1bCBmb3IgZHJ5LWhvcHBlZCBzYW1wbGVzLiAgDQpGUEhjYWxjPC0gRlBIY2FsYyAlPiUgZmlsdGVyKGhvcD09IkRIIikNCmBgYA0KDQoNCiMjICpjYWxjdWxhdGUqIGNvcnJlc3BvbmRpbmcgQ08yIHByb2R1Y3Rpb24gDQoqIGZvbGxvd2luZyBCYW1mb3J0aCAoZGVzY3JpYmluZyBCYWxsaW5nIGVxdWF0aW9uKSAiLi4ubW9yZSByZWFsaXN0aWNhbGx5LCB0aGUgZXRoYW5vbCB5aWVsZCBpcyBtb3JlIGxpa2UgMC40NiBnIGFuZCBjYXJib24gZGlveGlkZSAwLjQ0IGcgZnJvbSAxIGcgc3VnYXIiICAocC4gMTM3IGluIEJyZXdpbmcgTWF0ZXJpYWxzIGFuZCBQcm9jZXNzZXM6IEEgUHJhY3RpY2FsIEFwcHJvYWNoIHRvIEJlZXIgRXhjZWxsZW5jZSwgRWRpdGVkIGJ5IENoYXJsZXMgQmFtZm9ydGggQWNhZGVtaWMgUHJlc3MsIDIwMTYpDQpgYGB7cn0NCkZQSGNhbGMkY2FsY0NPMl9pbmNyZWFzZSA8LSBGUEhjYWxjJGRlbHRhLkFCVyooMC40NC8wLjQ2KQ0KIyNjb252ZXJ0IGNhbGNDTzJfaW5jcmVhc2UgKGluIGcvMTAwbUwpIHRvIGNhbGN1bGF0ZWQgQ08yIHZvbHVtZXMgYWRkZWQNCiMjIGcvTCA9IDEwKiBnLzEwMG1MDQojIyBUaGUgY29udmVyc2lvbiBmYWN0b3IgZnJvbSB2b2x1bWVzIG9mIENPMiB0byBDTzIgYnkgd2VpZ2h0IChnL0wpIGlzIDEuOTYuIEZvciBleGFtcGxlOiAyLjUgdm9sdW1lcyB4IDEuOTYgPSA0LjkgZy9sLg0KRlBIY2FsYyRjYWxjQ08ydm9sc19pbmNyZWFzZSA8LSBGUEhjYWxjJGNhbGNDTzJfaW5jcmVhc2UqMTAvMS45Ng0KYGBgDQoNCg0KDQojIyBkZWZpbmUgIkZQSCIgYXMgYW1vdW50IHByb2R1Y2VkIHBlciBhbW91bnQgZHJ5LWhvcHMgYWRkZWQgKGFsbCBpbiBnLzEwMG1MKToNCmBgYHtyfQ0KIyMgIEZQSCA9IEZvbGQgUHJvZHVjdGlvbiBkdWUgdG8gSG9wcyAoZm9sZC1pbmNyZWFzZSBieSBtYXNzOiBhbW91bnQgb2YgZ2l2ZW4gZW5kcG9pbnQgcmVsYXRpdmUgdG8gYW1vdW50IG9mIGhvcHMgYWRkZWQpDQojIyAgDQpGUEhjYWxjJEZQSF9FdE9IID0gRlBIY2FsYyRkZWx0YS5BQlcvRlBIY2FsYyRob3BzX2dfMTAwbUwNCkZQSGNhbGMkRlBIX0NPMiA9IEZQSGNhbGMkY2FsY0NPMl9pbmNyZWFzZS9GUEhjYWxjJGhvcHNfZ18xMDBtTA0KRlBIY2FsYyRGUEhfcGxhdG8gPSBGUEhjYWxjJGRlbHRhLnBsYXRvL0ZQSGNhbGMkaG9wc19nXzEwMG1MDQoNCiMjIHJlcGxhY2luZyB0aW1lLXplcm8gdGltZSB2YWx1ZXMgd2l0aCBhIHZlcnkgc21hbGwgbnVtYmVyIChyYXRoZXIgdGhhbiBleGFjdGx5IHplcm8pIHdpbGwgcHJldmVudCBpc3N1ZXMgd2l0aCBhbmFseXNpcyBvZiBub25saW5lYXIgbW9kZWxzDQpGUEhjYWxjW0ZQSGNhbGMkZGF5c29uaG9wcz09MCxdJGRheXNvbmhvcHMgPC0gMC4wMQ0KDQoNCg0KIyMgYW5kIHNhdmUgdGhlIHRyYW5zZm9ybWVkIGRhdGEgdG8gY3N2Og0Kd3JpdGUuY3N2KEZQSGNhbGMsIkZQSGNhbGMuY3N2Iiwgcm93Lm5hbWVzID0gRkFMU0UpDQpGUEhjYWxjIDwtIHJlYWQuY3N2KCJGUEhjYWxjLmNzdiIsIHN0cmluZ3NBc0ZhY3RvcnMgPSBUUlVFKQ0KDQpkZjwtIEZQSGNhbGMgJT4lIGdyb3VwX2J5KEVYUFRuZXcpICU+JQ0KICBzdW1tYXJpc2VfYXQodmFycyhob3BzX2dfMTAwbUwscG91bmRzX2JibCxkZWx0YS5BQlYsIGRlbHRhLkFCVywgZGVsdGEucGxhdG8sIEZQSF9wbGF0bywgRlBIX0V0T0gsIEZQSF9DTzIsIGNhbGNDTzJ2b2xzX2luY3JlYXNlKSxmdW5zKHJvdW5kKG1lYW4oLiksIDIpKSkgJT4lDQogIGFycmFuZ2UoZGVzYyhGUEhfRXRPSCkpIA0KZGYNCmBgYA0KDQoNCg0KDQoNCg0KIyBpbnRlcm1pc3Npb24/DQoNCg0KDQoNCg0KDQojIHZpc3VhbGl6ZQ0KIyMgdmVjdG9yfnZlY3Rvcip2ZWN0b3IgcGxvdHMNCmBgYHtyIHJlZ3Jlc3Npb24uMn0NCmRmIDwtRlBIY2FsYw0KcDE8LSBnZ3Bsb3QoZGYsIGFlcyh5PUZQSF9FdE9ILHg9T0UsIGNvbG9yPWNvbnRhY3RfZGF5cykpICsgZ2VvbV9wb2ludChzaXplPTIpDQpwMjwtIGdncGxvdChkZiwgYWVzKHk9RlBIX0V0T0gseD1BREYsIGNvbG9yPWNvbnRhY3RfZGF5cykpICsgZ2VvbV9wb2ludChzaXplPTIpDQpwMzwtIGdncGxvdChkZiwgYWVzKHk9RlBIX0V0T0gseD1BQlcsIGNvbG9yPWNvbnRhY3RfZGF5cykpICsgZ2VvbV9wb2ludChzaXplPTIpDQpwNDwtIGdncGxvdChkZiwgYWVzKHk9RlBIX0V0T0gseD1FYSwgY29sb3I9Y29udGFjdF9kYXlzKSkgKyBnZW9tX3BvaW50KHNpemU9MikNCmdyaWQuYXJyYW5nZShwMSwgcDIsIHAzLCBwNCwgbmNvbCA9IDIpDQpgYGANCg0KDQoNCiMjIHh+eSooNCB2ZWN0b3JzKSBieSBjb2xvcg0KYGBge3IgcmVncmVzc2lvbi40cGxvdC5jb2xvcn0NCmRmIDwtIEZQSGNhbGMNCng8LSBkZiRFYQ0KeTwtIGRmJEFCVyAgICAjRlBIX0V0T0gNCg0KcDE8LSBnZ3Bsb3QoZGYsIGFlcyh4LHksIGNvbG9yPWZvcm1fb2ZfaG9wcykpICsgZ2VvbV9wb2ludChzaXplPTIpDQpwMjwtIGdncGxvdChkZiwgYWVzKHgseSwgY29sb3I9YnJld19kYXRlKSkgKyBnZW9tX3BvaW50KHNpemU9MikNCnAzPC0gZ2dwbG90KGRmLCBhZXMoeCx5LCBjb2xvcj1yb3VzZSkpICsgZ2VvbV9wb2ludChzaXplPTIpDQpwNDwtIGdncGxvdChkZiwgYWVzKHgseSwgY29sb3I9cG91bmRzX2JibCkpICsgZ2VvbV9wb2ludChzaXplPTIpDQpncmlkLmFycmFuZ2UocDEsIHAyLCBwMywgcDQsIG5jb2wgPSAyKQ0KYGBgDQoNCiMjIDNEIHBsb3RzIGZvciBodG1sIHdpdGggbGlicmFyeShwbG90bHkpDQpzZWUgaHR0cHM6Ly9wbG90bHktci5jb20vdGhlLXBsb3RseS1jb29rYm9vay5odG1sIGZvciBvdmVydmlldyBhbmQgc29tZSB2aXN1YWxpemF0aW9uIGluc3BpcmF0aW9uLiANCmBgYHtyfQ0KIyBpbnRlcmFjdGl2ZSAzRCBwbG90cyB3aXRoIGxpYnJhcnkocGxvdGx5KQ0KZGYgPC0gRlBIY2FsYyAjICU+JSBmaWx0ZXIoZXhwdD09IiAyQSIpDQoNCiMgbWFrZXMgaHRtbCB2ZXJ5IHNsb3cgdG8gcmVuZGVyLi4uY29tbWVudCBvdXQNCiNwbG90X2x5KGRmLCB4ID0gfmRheXNvbmhvcHMsIHkgPSB+QUJXLCB6ID0gfkVhKSAlPiUgYWRkX21hcmtlcnMoY29sb3IgPSB+ZXhwdCkNCmBgYA0KDQojIyBBbXVuYXRlZ3VpICJVc2luZyBDb3JyZWxhdGlvbnMgVG8gVW5kZXJzdGFuZCBZb3VyIERhdGEiDQpgYGB7ciBBbXVuYXRlZ3VpfQ0KbXlkYXRhPC1GUEhjYWxjICU+JSBkcGx5cjo6c2VsZWN0KGV4cHQsdmFyaWV0eSxmb3JtX29mX2hvcHMscG91bmRzX2JibCxzcGVjaWFsX2NvbmRpdGlvbnMscm91c2UsZGF5c29uaG9wcyxESF90ZW1wLGJyZXdfZGF0ZSxBQlcsU0csT0UsQURGLFJERixjYWxjQ08ydm9sc19pbmNyZWFzZSxGUEhfQ08yLCBGUEhfRXRPSCkgICMjIGxhc3Qgb25lIGlzIDEwMCUgaW4gag0KDQojIyBub3RlIHVzZSBvZiAiZHBseXI6OnNlbGVjdCIgYmVjYXVzZSBvbmUgb2YgdGhlc2UgcGFja2FnZXMgaXMgY29uZmxpY3Rpbmcgd2l0aCBkcGx5ciBjb21tYW5kcyA6KCkNCg0KIyMgZm9sbG93aW5nICBNYW51ZWwgQW11bmF0ZWd1aSAgaHR0cHM6Ly93d3cueW91dHViZS5jb20vd2F0Y2g/dj1pZ1BRLXBJOEJqbw0KIyMgVXNpbmcgQ29ycmVsYXRpb25zIFRvIFVuZGVyc3RhbmQgWW91ciBEYXRhOiBNYWNoaW5lIExlYXJuaW5nIFdpdGggUiANCiMjZnVuY3Rpb25zIGZvciBmbGF0dGVuU3F1YXJlTWF0cml4DQpjb3IucHJvYiA8LSBmdW5jdGlvbiAoWCwgZGZyPW5yb3coWCkgLTIpIHsNCiAgUjwtIGNvcihYLCB1c2U9InBhaXJ3aXNlLmNvbXBsZXRlLm9icyIpDQogIGFib3ZlPC0gcm93KFIpIDwgY29sKFIpDQogIHIyIDwtIFJbYWJvdmVdXjINCiAgRnN0YXQ8LSByMiAqIGRmci8oMS1yMikNCiAgUlthYm92ZV0gPC0gMS0gcGYoRnN0YXQsIDEsIGRmcikNCiAgUltyb3coUikgPT0gY29sKFIpXSA8LSBOQQ0KICBSDQp9DQpmbGF0dGVuU3F1YXJlTWF0cml4IDwtIGZ1bmN0aW9uKG0pIHsNCiAgaWYoIChjbGFzcyhtKSAhPSAibWF0cml4IikgfCAobnJvdyhtKSE9bmNvbChtKSkpIHN0b3AoIk11c3QgYmUgYSBzcXVhcmUgbWF0cml4LiIpDQogIGlmKCFpZGVudGljYWwocm93bmFtZXMobSksIGNvbG5hbWVzKG0pKSkgc3RvcCgiUm93IGFuZCBjb2x1bW4gbmFtZXMgbXVzdCBiZSBlcXVhbC4iKQ0KICB1dCA8LSB1cHBlci50cmkobSkNCiAgZGF0YS5mcmFtZShpID0gcm93bmFtZXMobSlbcm93KG0pW3V0XV0sDQogICAgICAgICAgICAgaiA9IHJvd25hbWVzKG0pW2NvbChtKVt1dF1dLA0KICAgICAgICAgICAgIGNvcj10KG0pW3V0XSwNCiAgICAgICAgICAgICBwPW1bdXRdKQ0KfQ0KDQojIyBsaWJyYXJ5KGNhcmV0KSB0byBkdW1taWZ5IGV2ZXJ5dGhpbmcgKHR1cm4gYWxsIGNoYXJhY3RlcnMmZmFjdG9ycyBpbnRvIGNvbHVtbnM7ICBpZ25vcmVzIG51bWJlcnMgYW5kIGludGVnZXJzKQ0KZG15PC0gZHVtbXlWYXJzKCIgfiAuIixkYXRhID0gbXlkYXRhKQ0KbXlkdW1taWZpZWRkYXRhPC0gZGF0YS5mcmFtZShwcmVkaWN0KGRteSwgbmV3ZGF0YSA9IG15ZGF0YSkpDQpjb3JNYXQgPSBjb3IobXlkdW1taWZpZWRkYXRhKQ0KY29yTWFzdGVyTGlzdDwtIGZsYXR0ZW5TcXVhcmVNYXRyaXgoY29yLnByb2IobXlkdW1taWZpZWRkYXRhKSkgIyMgbGlzdCBvZiBhbGwgY29ycmVsYXRpb25zDQojIyBvcmRlciBieSBzdHJlbmd0aCBvZiBjb3JyZWxhdGlvbg0KY29ybGlzdDwtIGNvck1hc3Rlckxpc3QgJT4lIGFycmFuZ2UoLWFicyhjb3JNYXN0ZXJMaXN0JGNvcikpIA0Kd3JpdGUuY3N2KGNvcmxpc3QscGFzdGUwKCJGTEFUIGNvcnJlbGF0aW9uIG1hdHJpeF8uY3N2IikpDQpjb3JsaXN0IDwtIGNvcmxpc3QgJT4lIGRwbHlyOjpmaWx0ZXIoaj09IkZQSF9FdE9IIikgICAjIyBmaWx0ZXIgc3BlY2lmaWMgZW5kcG9pbnQNCmhlYWQoY29ybGlzdCw1MCkNCmBgYA0KDQojIyBwYWlycy5wYW5lbHMgY29ycmVsYXRpb24gbWF0cml4IGZyb20gbGlicmFyeShwc3ljaCkNCmBgYHtyfQ0KIyMgc3BlY2lmeSBpbnRlcmVzdGluZyB2YXJpYWJsZXM6ICANCmludGVyZXN0aW5ndmFyaWFibGVzPC1jKCJBQlciLCAicG91bmRzX2JibCIsICJkYXlzb25ob3BzIiwgImNhbGNDTzJ2b2xzX2luY3JlYXNlIikgDQpwYWlycy5wYW5lbHMobXlkdW1taWZpZWRkYXRhW2MoaW50ZXJlc3Rpbmd2YXJpYWJsZXMsICJGUEhfRXRPSCIpXSkNCmBgYA0KDQoNCiNtb2RlbA0KT2JzZXJ2aW5nIHRoZSBpbXBhY3RzIG9mIGRyeWhvcHBpbmcgaW4gdGhlIHByZXNlbmNlIG9mIGxpdmUgeWVhc3QgaGFzIGxlZCBtYW55IGJyZXdpbmcgcHJvZmVzc2lvbmFscyB0byB1bmRlcnN0YW5kIHRoYXQgRlBIIGlzIGEgZnVuY3Rpb24gb2YgbWFueSBvZiB0aGUgdmFyaWFibGVzIGFib3ZlIGluY2x1ZGluZyBob3AgdmFyaWV0eSxmb3JtX29mX2hvcHMsaGFydmVzdFllYXIsT1gsREhfdGVtcCxkYXlzb25ob3BzLHJvdXNlLHBvdW5kc19iYmwuLi4uIE1hbnkgaGF2ZSBpbnR1aXRpdmVseSBjcmVhdGVkIGEgbW9kZWwgaW4gdGhlaXIgaGVhZHMgKHdpdGhvdXQgbmVjZXNzYXJpbHkgdGhpbmtpbmcgb2YgaXQgYXMgc3VjaCkgYW5kIHNraWxsZnVsbHkgYWRqdXN0IHByb2Nlc3Mgd2hlbiBuZWNlc3NhcnkgdG8gYWNjb3VudCBmb3IgdGhpcyBwaGVub21lbm9uLiAgSW4gbGluZWFyIG1vZGVsaW5nLCBvdXIgZnVuY3Rpb24gd2lsbCB0YWtlIG9uIHRoZSBmb3JtOg0KJEZQSCA9ICBpbnRlcmNlcHQgKyBcYmV0YV97MX1YX3sxfSArIFxiZXRhX3syfVhfezJ9ICsgLi4uICsgXGJldGFfe259WF97bn0kIHdoZXJlICRcYmV0YSQgdmFsdWVzIGFyZSB3aGF0IHdlJ3JlIGF0dGVtcHRpbmcgdG8gZGVyaXZlIGluIHRoaXMgbW9kZWxpbmcgZXhlcmNpc2UsIGFuZCBYIHZhbHVlcyBhcmUgKGNvbGxlY3RpdmVseSkgYSBwYXJ0aWN1bGFyIHNldCBvZiBjb25kaXRpb25zLg0KDQo+PiBBY3Rpb25JdGVtOiAgYWRkIGxpbmsgdG8gbW9kZWxpbmcgb3ZlcnZpZXcNCg0KDQojIyBsaW5lYXIgbW9kZWxzIDEgKGNyZWF0ZSBtb2RlbHMpDQpgYGB7cn0NCmRmPC1GUEhjYWxjDQoNCmxtMTwtbG0oZGYkU0d+ZGYkT0UpDQpsbTI8LWxtKGRmJFNHfmRmJEFERikNCmxtMzwtbG0oZGYkU0d+ZGYkQUJXKQ0KbG00PC1sbShkZiRTR35kZiRFYSkNCnBhcihtZnJvdyA9IGMoMiwgMiksIG9tYSA9IGMoMCwgMCwgMCwgMCkpDQpwbG90KGRmJFNHfmRmJE9FKQ0KYWJsaW5lKGxtMSkNCnBsb3QoZGYkU0d+ZGYkQURGKQ0KYWJsaW5lKGxtMikNCnBsb3QoZGYkU0d+ZGYkQUJXKQ0KYWJsaW5lKGxtMykNCnBsb3QoZGYkU0d+ZGYkRWEpDQphYmxpbmUobG00KQ0KYGBgDQoNCiMjIGxpbmVhciBtb2RlbHMgMiAodmlldyByZXNpZHVhbCBwbG90cykNCmBgYHtyfQ0KZGY8LUZQSGNhbGMNCg0KbG0xPC1sbShkZiRTR35kZiRPRSkNCmxtMjwtbG0oZGYkU0d+ZGYkQURGKQ0KbG0zPC1sbShkZiRTR35kZiRBQlcpDQpsbTQ8LWxtKGRmJFNHfmRmJEVhKQ0KDQpwYXIobWZyb3cgPSBjKDIsIDIpLCBvbWEgPSBjKDAsIDAsIDAsIDApKQ0KcGxvdChsbTEkcmVzaWR1YWxzKQ0KcGxvdChsbTIkcmVzaWR1YWxzKQ0KcGxvdChsbTMkcmVzaWR1YWxzKQ0KcGxvdChsbTQkcmVzaWR1YWxzKQ0KI3N1bW1hcnkobG00KQ0KYGBgDQoNCg0KIyMgY29tcGFyZSBsaW5lYXIgbW9kZWxzIHVzaW5nIG1lbWlzYyBwYWNrYWdlDQpgYGB7cn0NCmRmPC1GUEhjYWxjDQoNCmxtMTwtbG0oZGYkU0d+ZGYkT0UpDQpsbTI8LWxtKGRmJFNHfmRmJEFERikNCmxtMzwtbG0oZGYkU0d+ZGYkQUJXKQ0KbG00PC1sbShkZiRTR35kZiRFYSkNCg0KbXRhYmxlMTIzNCA8LSBtdGFibGUoIk1vZGVsIDEiPWxtMSwiTW9kZWwgMiI9bG0yLCJNb2RlbCAzIj1sbTMsICJNb2RlbCA0Ij1sbTQsDQogICAgICAgICAgICAgICAgICAgIHN1bW1hcnkuc3RhdHM9Yygic2lnbWEiLCJSLXNxdWFyZWQiLCJGIiwicCIsIk4iKSxzaG93LmVxbmFtZXM9VCkNCg0KbXRhYmxlMTIzNGIgPC0gcmVsYWJlbChtdGFibGUxMjM0LA0KICAgICAgICAgICAgICAgICAgICAgICIoSW50ZXJjZXB0KSIgPSAiQ29uc3RhbnQiLA0KICAgICAgICAgICAgICAgICAgICAgIHgxID0gIk9FID0gT3JpZ2luYWwgRXh0cmFjdCAoZy8xMDBtTCkiLA0KICAgICAgICAgICAgICAgICAgICAgIHgyID0gIkFERiA9IEFwcGFyZW50IERlZ3JlZSBvZiBGZXJtZW50YXRpb24gKCUpIiwNCiAgICAgICAgICAgICAgICAgICAgICB4MyA9ICJBQlcgPSBFdGhhbm9sICh3L3cpIiwNCiAgICAgICAgICAgICAgICAgICAgICB4NCA9ICJFciA9IFJlc2lkdWFsIEV4dHJhY3QgKGcvMTAwbUwpIg0KICAgICAgICAgICAgICAgICAgICAgICkNCm10YWJsZTEyMzQNCiNzaG93X2h0bWwobXRhYmxlMTIzNGIpDQoNCg0KYGBgDQoNCg0KDQoNCiMjIGxpbmVhciBtb2RlbHMgb2YgRlBIDQpgYGB7cn0NCmRmIDwtIEZQSGNhbGMNCmxtMTwtbG0oRlBIX0V0T0h+ZGF5c29uaG9wcywgZGF0YT1kZikNCmxtMjwtbG0oRlBIX0V0T0h+ZGF5c29uaG9wcypmb3JtX29mX2hvcHMsIGRhdGE9ZGYpDQpsbTM8LWxtKEZQSF9FdE9IfmRheXNvbmhvcHMqcm91c2UsIGRhdGE9ZGYpDQpsbTQ8LWxtKEZQSF9FdE9IfmRheXNvbmhvcHMqZm9ybV9vZl9ob3BzKnJvdXNlLCBkYXRhPWRmKQ0KDQptdGFibGUxMjM0IDwtIG10YWJsZSgiTW9kZWwgMSI9bG0xLCJNb2RlbCAyIj1sbTIsIk1vZGVsIDMiPWxtMywgIk1vZGVsIDQiPWxtNCwNCiAgICAgICAgICAgICAgICAgICAgc3VtbWFyeS5zdGF0cz1jKCJzaWdtYSIsIlItc3F1YXJlZCIsIkYiLCJwIiwiTiIpLHNob3cuZXFuYW1lcz1UKQ0KbXRhYmxlMTIzNGIgPC0gcmVsYWJlbChtdGFibGUxMjM0LA0KICAgICAgICAgICAgICAgICAgICAgICIoSW50ZXJjZXB0KSIgPSAiQ29uc3RhbnQiLA0KICAgICAgICAgICAgICAgICAgICAgIFNHID0gIlNwZWNpZmljIEdyYXZpdHkiLA0KICAgICAgICAgICAgICAgICAgICAgIEFCVyA9ICJBQlcgPSBFdGhhbm9sICh3L3cpIiwNCiAgICAgICAgICAgICAgICAgICAgICBFciA9ICJFciA9IFJlc2lkdWFsIEV4dHJhY3QgKGcvMTAwbUwpIg0KICAgICAgICAgICAgICAgICAgICAgICkNCm10YWJsZTEyMzQNCiNzaG93X2h0bWwobXRhYmxlMTIzNGIpDQpgYGANCg0KDQoNCiMjIFRoZSBSIEJvb2sgKENyYXdsZXkpIFRhYmxlIDIwLjE6IG5vbmxpbmVhciBmdW5jdGlvbnMgdXNlZnVsIGluIGJpb2xvZ3kgICANClRhYmxlIDIwLjEuIFtVc2VmdWwgbm9uLWxpbmVhciBmdW5jdGlvbnNdKGh0dHBzOi8vd3d3LmNzLnVwYy5lZHUvfnJvYmVydC90ZWFjaGluZy9lc3RhZGlzdGljYS9UaGVSQm9vay5wZGYgIk1pY2hhZWwgSi4gQ3Jhd2xleS4gIFRoZSBSIGJvb2suICBwLiA3MzgiKSBFWFBBTkRFRDoNCg0KDQp8IEZ1bmN0aW9uIENsYXNzIHwgbmFtZSB8IGVxdWF0aW9uIHwgZXhhbXBsZSBjb2RlIHxleGFtcGxlIGFwcGxpY2F0aW9uc3wNCnw6LS0tLS0tLS0tLXw6LS0tLS06fDotLS0tLS0tfDotLS0tLXw6LS0tLS18DQp8ICoqQXN5bXB0b3RpYyBmdW5jdGlvbnMqKnxNaWNoYWVsaXPigJNNZW50ZW58JHkgPVxmcmFje2F4fXsxK2J4fSR8bmxzKGJvbmV+YSphZ2UvKDErYiphZ2UpLHN0YXJ0PWxpc3QoYT04LGI9MC4wOCkpKSB8IGVuenltZSByZWFjdGlvbnMgfA0KfCB8IHwgfCBubHMocmF0ZX5TU21pY21lbihjb25jLGEsYikpIHwgdGJkfA0KfCB8Mi1wYXJhbWV0ZXIgYXN5bXB0b3RpYyBleHBvbmVudGlhbCB8ICR5ID0gYSgxIOKIkiBlXnviiJJieH0gKSQgfG5scyhib25lfmEqKDEtZXhwKC1jKmFnZSkpLHN0YXJ0PWxpc3QoYT0xMjAsYz0wLjA2NCkpIHwgdGJkIHwNCnwgfDMtcGFyYW1ldGVyIGFzeW1wdG90aWMgZXhwb25lbnRpYWwgfCAkeSA9IGEg4oiSIGJlXnviiJJjeH0kIHwgbmxzKGJvbmV+YS1iKmV4cCgtYyphZ2UpLHN0YXJ0PWxpc3QoYT0xMjAsYj0xMTAsYz0wLjA2NCkpIHwgdGJkIHwNCnwgfCB8IHwgbmxzKGJvbmV+U1Nhc3ltcChhZ2UsYSxiLGMpKSB8IHRiZCB8DQp8IHwgfCB8IG5scyhkZW5zaXR5IH4gU1Nsb2dpcyhsb2coY29uY2VudHJhdGlvbiksIGEsIGIsIGMpKSB8IHRiZCB8DQp8KipTLXNoYXBlZCBmdW5jdGlvbnMqKiB8Mi1wYXJhbWV0ZXIgbG9naXN0aWMgfCR5ID0gXGZyYWN7ZV57YStieH19ezEgKyBlXnthK2J4fX0kIHwgfCB0YmQgfA0KfCB8IDMtcGFyYW1ldGVyIGxvZ2lzdGljIHwgJHkgPSBcZnJhY3thfXsxICsgYmVee+KIkmN4fX0kIHwgfCB0YmQgfA0KfCB8IDQtcGFyYW1ldGVyIGxvZ2lzdGljIHwgJHkgPSBhICsgXGZyYWN7Yi1hfXsxICsgZV57KGPiiJJ4KS9kfX0kIHwgbmxzKHdlaWdodH5TU2ZwbChUaW1lLCBhLCBiLCBjLCBkKSkgfCB0YmQgfA0KfCB8IFdlaWJ1bGwgfCAkeSA9IGEg4oiSIGJlXnviiJIoY3heZCl9JCB8IG5scyh3ZWlnaHQgfiBTU3dlaWJ1bGwodGltZSwgQXN5bSwgRHJvcCwgbHJjLCBwd3IpKSB8IHRiZCB8DQp8IHwgR29tcGVydHogfCAkeSA9IGFlXnviiJJiZV574oiSY3h9fSQgfCB8IHRiZCB8DQp8ICoqSHVtcGVkIGN1cnZlcyoqIHwgUmlja2VyIGN1cnZlIHwgJHkgPSBheGVee+KIkmJ4fSQgfCB8IHRiZCB8DQp8IHwgRmlyc3Qtb3JkZXIgY29tcGFydG1lbnQgfCAkeSA9IGsgZXhwKOKIkmV4cChhKXgpIOKIkiBleHAo4oiSZXhwKGIpeCkkIHwgbmxzKGNvbmN+U1Nmb2woRG9zZSwgVGltZSwgYSwgYiwgYykpIHwgdGJkIHwNCnwgfCBCZWxsLXNoYXBlZCB8ICR5ID0gYSBleHAo4oiSQUJTKGJ4KV4yKSQgfCB8IHRiZCB8DQp8IHwgQmlleHBvbmVudGlhbCB8ICR5ID0gYWVee2J4fSDiiJIgY2Vee+KIkmR4fSQgIHwgfCB0YmQgfA0KDQoNCg0KIyMgYmFzZSBSIG5scyBmdW5jdGlvbiBmb3IgTWljaGFlbGlzLU1lbnRvbiBtb2RlbA0KJHkgPVxmcmFje2F4fXsxK2J4fSQNCg0KYGBge3J9DQoNCmV4cHQyIDwtIEZQSGNhbGMgJT4lIGRwbHlyOjpmaWx0ZXIoZXhwdD09IiAyQSIpICU+JSBkcGx5cjo6c2VsZWN0KEZQSF9FdE9ILCByb3VzZSxzcGVjaWFsX2NvbmRpdGlvbnMsIGRheXNvbmhvcHMsYnJld19kYXRlLCBwb3VuZHNfYmJsLGZvcm1fb2ZfaG9wcyxBQlcuY29udHJvbF9OSCxkYXlvZmFkZGl0aW9uKQ0KDQpteWZhY3RvcjwtIGFzLmZhY3RvcihleHB0MiRzcGVjaWFsX2NvbmRpdGlvbnMpICAjcm91c2UNCmNvbHMgPC0gYXMubnVtZXJpYyhteWZhY3RvcikNCmxlZ2VuZC5jb2xzIDwtIGFzLm51bWVyaWMoYXMuZmFjdG9yKGxldmVscyhteWZhY3RvcikpKQ0KDQpteS5NTS5tb2RlbCA8LSBubHMoRlBIX0V0T0h+YSpkYXlzb25ob3BzLygxK2IqZGF5c29uaG9wcyksbXlmYWN0b3IsIGRhdGE9ZXhwdDIsc3RhcnQ9bGlzdChhPTgsIGI9MC4wOCkpDQoNCnN1bW1hcnkobXkuTU0ubW9kZWwpDQoNCmBgYA0KU28gdXNpbmcgdGhlIE1pY2hhZWxpcy1NZW50ZW4gZXF1YXRpb24gYXMgb3VyIG1vZGVsLCB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gaG9wLWluZHVjZWQgZXRoYW5vbCBwcm9kdWN0aW9uIChGUEhfRXRPSCkgYW5kIGhvcCBjb250YWN0IHRpbWUgKGRheXNvbmhvcHMpIHdvdWxkIGJlIGV4cHJlc3NlZCBhczoNCiRGUEhfRXRPSCA9IFxmcmFjezAuMTgzNTcyKmRheXNvbmhvcHN9ezErMC4wNjYzODgqZGF5c29uaG9wc30kDQp3aXRoIGEgUmVzaWR1YWwgc3RhbmRhcmQgZXJyb3Igb2YgMC4wOTMwNy4gQXMgZGV0YWlsZWQgYmVsb3csIGxvZ2lzdGljIG1vZGVscyBkbyBhIGJldHRlciBqb2IgdGhhbiB0aGlzLCBhbmQgdXNpbmcgbGlicmFyeShkcmMpIG1ha2VzIGl0IGVhc2llciB0byBpbnRyb2R1Y2UgYSBtdWx0aWxldmVsIGZhY3Rvci4gIA0KDQojIyBwcmVkaWN0IGFuZCBwbG90IG1vZGVsDQpgYGB7cn0NCnggPC0gc2VxKDAsNTAsMC4xKQ0KeXYgPC0gcHJlZGljdChteS5NTS5tb2RlbCwgbGlzdChkYXlzb25ob3BzPXgpKQ0Ke3Bsb3QoZXhwdDIkZGF5c29uaG9wcywgZXhwdDIkRlBIX0V0T0gsIHBjaD0yMSwgY29sPSJwdXJwbGUiLCBiZz0iZ3JlZW4iLCBtYWluID0gIk1pY2hhZWxpcy1NZW50ZW4gbW9kZWwgb2YgZXRoYW5vbCBwcm9kdWN0aW9uIGluZHVjZWQgYnkgZHJ5LWhvcHBpbmciKQ0KICBsaW5lcyh4LHl2LGNvbD0iYmx1ZSIpfQ0KDQpgYGANCg0KDQoNCiMjICBTcGVlcnMyMDAzOiBOb27igJBMaW5lYXIgTW9kZWxsaW5nIG9mIEluZHVzdHJpYWwgQnJld2luZyBGZXJtZW50YXRpb25zDQpSLiBBbGV4IFNwZWVycywgUGV0ZXIgUm9nZXJzLCBCcnVjZSBTbWl0aA0KSi4gSW5zdC4gQnJldy4gMTA5KDMpLCAyMjnigJMyMzUsIDIwMDMgDQo8aHR0cHM6Ly9kb2kub3JnLzEwLjEwMDIvai4yMDUwLTA0MTYuMjAwMy50YjAwMTYzLng+DQpGcmVlIEFjY2VzcyBmcm9tIHRoZSBJbnN0aXR1dGUgb2YgQnJld2luZyENCjxodHRwczovL29ubGluZWxpYnJhcnkud2lsZXkuY29tL2RvaS8xMC4xMDAyL2ouMjA1MC0wNDE2LjIwMDMudGIwMDE2My54Pg0KDQpGb2xsb3dpbmcgU3BlZXJzMjAwMywgdGhlIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIHBsYXRvIGFuZCB0aW1lIGluIHByaW1hcnkgZmVybWVudGF0aW9ucyBpcyB3ZWxsIG1vZGVsZWQgYnkgYSBmb3VyIHBhcmFtZXRlciBsb2dpc3RpYyBtb2RlbDoNCg0KUExBVE8gPSBQSU5GICsgKFBfRCAvICgxKyAoRVhQKC1CKihUSU1FLU0pKSkpKQ0KDQpvcg0KDQokUExBVE8gPSBQX3tpbmZ9KyBcZnJhY3tQX0R9ezErZV57LUIodC1NKX19JA0KDQp3aGVyZQ0KDQoqIHQgPSB0aW1lIGludG8gZmVybWVudGF0aW9uDQoqIFBfRCA9ICBjaGFuZ2UgaW4gUGxhdG8gZHVyaW5nIGZlcm1lbnRhdGlvbiAoT0UgLSBQX3tpbmZ9KQ0KKiBCIH4gbWF4aW11bSBmZXJtZW50YXRpb24gcmF0ZQ0KKiBNID0gZmVybWVuYXRpb24gbWlkcG9pbnQNCiogUF9pbmYgPSBmaW5hbCBncmF2aXR5DQoNCg0Kc28gdGhlbiBmb3Igb3VyIGNhc2UNCg0KJFBMQVRPID0gUF97aW5mfSsgIFxmcmFje09FLVBfe2luZn19ezErZV57LUZfe21heH0oZGF5c29uaG9wcy1NKX19JA0KDQpQTEFUTyA9IFBJTkYgKyAoUDAgLyAoMSsgKEVYUChCKihIT1VSUy1NKSkpKSkNCg0KDQojIyAgTWFjSW50b3NoMjAxNjogQW4gRXhhbWluYXRpb24gb2YgU3Vic3RyYXRlIGFuZCBQcm9kdWN0IEtpbmV0aWNzIER1cmluZyBCcmV3aW5nIEZlcm1lbnRhdGlvbnMNCkFuZHJldyBKLiBNYWNJbnRvc2gsIE1hcmlhIEpvc2V5LCBSLiBBbGV4IFNwZWVycw0KW0ouIEFtLiBTb2MuIEJyZXcuIENoZW0uIDc0KDQpLCAyNTAtMjU3LCAyMDE2XShodHRwczovL3d3dy5hc2JjbmV0Lm9yZy9wdWJsaWNhdGlvbnMvam91cm5hbC92b2wvMjAxNi9QYWdlcy9BU0JDSi0yMDE2LTQ3NTMtMDEuYXNweCkNCg0KImlzIG9ubHkgc3RhdGlzdGljYWxseSBhZHZhbnRhZ2VvdXMgdG8gYXBwbHkgdGhlIGZpdmUtcGFyYW1ldGVyIG1vZGVsIHdoZW4gbWFueSBkYXRhDQpwb2ludHMgYXJlIGF2YWlsYWJsZS4uLiAoNzIgZnJvbSB0aGlzIGV4cGVyaW1lbnQgYXMgb3Bwb3NlZCB0byAxMCB3aGVuIHVzaW5nIFllYXN0LTE0KS4iDQoNCk1hY0ludG9zaCBmaXZlLXBhcmFtZXRlciBsb2dpc3RpYzogICRQX3sodCl9ID1cZnJhY3tQX2kgLSBQX2V9eygxK3MgKiBlXnstQih0LU0pfSleezEvc319JA0KDQpsaWJyYXJ5KGRyYykNCmdldE1lYW5GdW5jdGlvbnMoKQ0KDQoqIGRyYyBMTC40OiAgJGYoeCkgPSBjICsgXGZyYWN7ZC1jfXsxK1xleHAoYihcbG9nKHgpLVx0aWxkZXtlfSkpfSQgICM0cGFyYW0gbG9naXN0aWMNCiogZHJjIExMLjU6ICAkZih4KSA9IGMgKyBcZnJhY3tkLWN9eygxK1xleHAoYihcbG9nKHgpLWUpKSleZn0kICAgICAgIzVwYXJhbSBsb2dpc3RpYw0KKiBkcmMgRy40ICRmKHgpID0gYyArIChkLWMpKFxleHAoLVxleHAoYih4LWUpKSkpJCAgICAgICAgICAgICAgICAgICAjNHBhcmFtIEdvbXBlcnR6ICANCiogZHJjIFcxLjQgJGYoeCkgPSBjICsgKGQtYykgXGV4cCgtXGV4cChiKFxsb2coeCktXGxvZyhlKSkpKSQgICAgICAgIzRwYXJhbSBXZWlidWxsMQ0KKiBkcmMgVzIuNCAkZih4KSA9IGMgKyAoZC1jKSAoMSAtIFxleHAoLVxleHAoYihcbG9nKHgpLVxsb2coZSkpKSkpJCAjNHBhcmFtIFdlaWJ1bGwyDQoNCg0KIyMgZXhwbG9yZSBub25saW5lYXIgbW9kZWxzIG9mIGhvcC1pbmR1Y2VkIFBsYXRvIGRyb3Agb3ZlciB0aW1lIHdpdGggbGlicmFyeShkcmMpIA0KYGBge3IgZHJjLm1vZGVsc30NCmRmIDwtIEZQSGNhbGMgJT4lIGZpbHRlcihleHB0PT0iIDJBIikgDQpkZiRQTEFUTy5ob3Bkcm9wIDwtIGRmJGluaXRpYWxfcGxhdG8gKyBkZiRkZWx0YS5wbGF0bw0KDQp4IDwtIGRmJGRheXNvbmhvcHMNCnkgPC0gZGYkUExBVE8uaG9wZHJvcA0KZ3JvdXA8LSBhcy5mYWN0b3IoZGYkc3BlY2lhbF9jb25kaXRpb25zKSAgICNyb3VzZQ0KY29scyA8LSBhcy5udW1lcmljKGdyb3VwKQ0KbGVnZW5kLmNvbHMgPC0gYXMubnVtZXJpYyhhcy5mYWN0b3IobGV2ZWxzKGdyb3VwKSkpDQoNCiMjIEZpdHRpbmcgbW9kZWxzIHVzaW5nIGZ1bmN0aW9uIGRybSBmcm9tIGxpYnJhcnkoZHJjKS4gc2VlIDxodHRwOi8vcnN0YXRzNGFnLm9yZy9kb3NlLXJlc3BvbnNlLWN1cnZlcy5odG1sPiBmb3Igb3ZlcnZpZXcgYW5kIDxodHRwczovL3d3dy5yZG9jdW1lbnRhdGlvbi5vcmcvcGFja2FnZXMvZHJjLz4gZm9yIGxpc3Qgb2YgbW9kZWxzIChmY3QgdmFsdWVzKSBhbmQgb3RoZXIgZnVuY3Rpb25zIGF2YWlsYWJsZSBpbiBkcmMNCg0KIyMgY3JlYXRlIG1vZGVscw0KI20uTEwuMzwtZHJtKHkgfngsZ3JvdXAsIGZjdCA9IExMLjMoKSkgIzMtcGFyYW1ldGVyIGxvZ2lzdGljIChsb3dlciBsaW1pdCBhdCAwKQ0KI20uTEwuM3U8LWRybSh5IH54LCBmY3QgPSBMTC4zdSgpKSAjMy1wYXJhbWV0ZXIgbG9naXN0aWMgKHVwcGVyIGxpbWl0IGF0IDEpDQptLkxMLjQ8LWRybSh5IH54LGdyb3VwLCBmY3QgPSBMTC40KCkpICM0LXBhcmFtZXRlciBsb2ctbG9naXN0aWMgIA0KIyBmcm9tID8/TEwuNDogIGYoeCkgPSBjICsgXGZyYWN7ZC1jfXsxK1xleHAoYihcbG9nKHgpLVxsb2coZSkpKX0gb3IgaW4gYW5vdGhlciBwYXJhbWV0ZXJpc2F0aW9uIChjb252ZXJ0aW5nIHRoZSB0ZXJtIFxsb2coZSkgaW50byBhIHBhcmFtZXRlcikgZih4KSA9IGMgKyBcZnJhY3tkLWN9ezErXGV4cChiKFxsb2coeCktXHRpbGRle2V9KSl9ICANCm0uTC40IDwtZHJtKHkgfngsZ3JvdXAsIGZjdCA9IEwuNCgpKSAjY2hhbmdpbmcgdGhlIGZjdCA9IExMLjQoKSB0byBmY3QgPSBMLjQoKSBhbGxvd3MgcGxvdHRpbmcgb24gYSBsb2cxMCBzY2FsZQ0KbS5MTDIuNDwtZHJtKHkgfngsZ3JvdXAsIGZjdCA9IExMMi40KCkpICM0LXBhcmFtZXRlciBsb2ctbG9naXN0aWMgd2l0aCBsb2coZSkgcmF0aGVyIHRoYW4gZSBhcyBhIHBhcmFtZXRlciAgDQptLkxMMi41PC1kcm0oeSB+eCxncm91cCwgZmN0ID0gTEwyLjUoKSkgI0dlbmVyYWxpc2VkIGxvZy1sb2dpc3RpYw0KIyBmcm9tID8/TEwuMi41OiAgIGYoeCkgPSBjICsgXGZyYWN7ZC1jfXsoMStcZXhwKGIoXGxvZyh4KS1lKSkpXmZ9DQptLkxMLjU8LWRybSh5IH54LGdyb3VwLCBmY3QgPSBMTC41KCkpICM1LXBhcmFtZXRlciBsb2dpc3RpYyAgDQptLlcxLjQ8LWRybSh5IH54LGdyb3VwLCBmY3QgPSBXMS40KCkpICM0LXBhcmFtZXRlciBXZWlidWxsMSAgDQptLlcyLjQ8LWRybSh5IH54LGdyb3VwLCBmY3QgPSBXMi40KCkpICM0LXBhcmFtZXRlciBXZWlidWxsMiAgDQptLkJDLjU8LWRybSh5IH54LGdyb3VwLCBmY3QgPSBCQy41KCkpICM1LXBhcmFtZXRlciBCcmFpbi1Db3VzZW5zIChob3JtZXNpcykNCm0uQVIuMzwtZHJtKHkgfngsZ3JvdXAsIGZjdCA9IEFSLjMoKSkgIzMtcGFyYW1ldGVyIFNoaWZ0ZWQgYXN5bXB0b3RpYyByZWdyZXNzaW9uDQojbS5NTS4yPC1kcm0oeSB+eCxncm91cCwgZmN0ID0gTU0uMigpKSAjMi1wYXJhbWV0ZXIgTWljaGFlbGlzLU1lbnRlbg0KbS5NTS4zPC1kcm0oeSB+eCxncm91cCwgZmN0ID0gTU0uMygpKSAjMy1wYXJhbWV0ZXIgTWljaGFlbGlzLU1lbnRlbg0KDQoNCiMjcGxvdA0KI3BhcihtZnJvdyA9IGMoMSwyKSwgb21hID0gYygwLCAwLCAwLCAwKSkNCg0Ke3Bsb3QoeCAsIHksIGNvbD1jb2xzLCBtYWluPSJyYXcgZGF0YSIpDQpsZWdlbmQoInRvcHJpZ2h0IiwgbGVnZW5kPWxldmVscyhncm91cCksIHBjaD0xNiwgY29sPWxlZ2VuZC5jb2xzKX0NCg0KI3Bsb3QobS5MTC4zLCB0eXBlPSdhbGwnLGNvbD1jb2xzLCBtYWluPSJMTC4zIChsb3dlciBsaW1pdCBhdCAwKSIpDQpwbG90KG0uTEwuNCwgdHlwZT0nYWxsJyxjb2w9Y29scywgbWFpbj0iTEwuNCBmb3VyLXBhcmFtZXRlciBsb2ctbG9naXN0aWMiKQ0KcGxvdChtLkwuNCwgdHlwZT0nYWxsJyxjb2w9Y29scywgbWFpbj0iTC40IikNCnBsb3QobS5MTC41LCB0eXBlPSdhbGwnLGNvbD1jb2xzLCBtYWluPSJMTC41IikNCnBsb3QobS5MTDIuNCwgdHlwZT0nYWxsJyxjb2w9Y29scywgbWFpbj0iTEwyLjQgZm91ci1wYXJhbWV0ZXIgbG9nLWxvZ2lzdGljIikNCnBsb3QobS5MTDIuNSwgdHlwZT0nYWxsJyxjb2w9Y29scywgbWFpbj0iR2VuZXJhbGlzZWQgbG9nLWxvZ2lzdGljIikNCnBsb3QobS5XMS40LCB0eXBlPSdhbGwnLGNvbD1jb2xzLCBtYWluPSJXZWlidWxsMSIpDQpwbG90KG0uVzIuNCwgdHlwZT0nYWxsJyxjb2w9Y29scywgbWFpbj0iV2VpYnVsbDIiKQ0KcGxvdChtLkJDLjUsIHR5cGU9J2FsbCcsY29sPWNvbHMsIG1haW49IkJyYWluLUNvdXNlbnMgKGhvcm1lc2lzKSIpDQpwbG90KG0uQVIuMywgdHlwZT0nYWxsJyxjb2w9Y29scywgbWFpbj0iU2hpZnRlZCBhc3ltcHRvdGljIHJlZ3Jlc3Npb24iKQ0KI3Bsb3QobS5NTS4yLCB0eXBlPSdhbGwnLGNvbD1jb2xzLCBtYWluPSIyLXBhcmFtZXRlciBNaWNoYWVsaXMtTWVudGVuIikNCnBsb3QobS5NTS4zLCB0eXBlPSdhbGwnLGNvbD1jb2xzLCBtYWluPSIzLXBhcmFtZXRlciBNaWNoYWVsaXMtTWVudGVuIikNCg0KYGBgDQoNCiMjIG11bHRpbGV2ZWwgbG9naXN0aWMgbW9kZWxzIHdpdGggbGlicmFyeShkcmMpDQpgYGB7ciBkcmMubW9kZWxzLmxldmVsc30NCmRmIDwtIEZQSGNhbGMgJT4lIGZpbHRlcihleHB0PT0iIDJBIikgDQpkZiRQTEFUTy5ob3Bkcm9wIDwtIGRmJGluaXRpYWxfcGxhdG8gKyBkZiRkZWx0YS5wbGF0bw0KeCA8LSBkZiRkYXlzb25ob3BzDQp5IDwtIGRmJFBMQVRPLmhvcGRyb3ANCm15ZmFjdG9yPC0gYXMuZmFjdG9yKGRmJHNwZWNpYWxfY29uZGl0aW9ucykgICNyb3VzZQ0KY29scyA8LSBhcy5udW1lcmljKG15ZmFjdG9yKQ0KbGVnZW5kLmNvbHMgPC0gYXMubnVtZXJpYyhhcy5mYWN0b3IobGV2ZWxzKG15ZmFjdG9yKSkpDQoNCiMjIGNyZWF0ZSBtb2RlbHMNCiNtLkxMLjQ8LWRybSh5IH54LG15ZmFjdG9yLCBmY3QgPSBMTC40KG5hbWVzID0gYygiU2xvcGUiLCAiTG93ZXIiLCAiVXBwZXIiLCAiTWlkcG9pbnQgb3IgRUQ1MCIpKSkgIzQtcGFyYW1ldGVyIGxvZy1sb2dpc3RpYyAoZ2VuZXJhbCBwYXJhbWV0ZXJzKQ0KI20uTEwuNDwtZHJtKHkgfngsbXlmYWN0b3IsIGZjdCA9IExMLjQobmFtZXMgPSBjKCJTbG9wZSIsICJMb3dlciIsICJVcHBlciIsICJFRDUwIikpKSAjNC1wYXJhbWV0ZXIgbG9nLWxvZ2lzdGljIChEb3NlLVJlc3BvbnNlIHBhcmFtZXRlcnMpDQptLkxMLjQ8LWRybSh5IH54LG15ZmFjdG9yLCBmY3QgPSBMTC40KG5hbWVzID0gYygiRl9tYXgiLCAiUF9pbmYiLCAiT0UiLCAiTSIpKSkgIzQtcGFyYW1ldGVyIGxvZy1sb2dpc3RpYyAgDQptLkwuNCA8LWRybSh5IH54LG15ZmFjdG9yLCBmY3QgPSBMLjQoKSkgI2NoYW5naW5nIHRoZSBmY3QgPSBMTC40KCkgdG8gZmN0ID0gTC40KCkgYWxsb3dzIHBsb3R0aW5nIG9uIGEgbG9nMTAgc2NhbGUNCm0uTEwyLjQ8LWRybSh5IH54LG15ZmFjdG9yLCBmY3QgPSBMTDIuNCgpKSAjNC1wYXJhbWV0ZXIgbG9nLWxvZ2lzdGljIHdpdGggbG9nKGUpIHJhdGhlciB0aGFuIGUgYXMgYSBwYXJhbWV0ZXIgIA0KbS5MTDIuNTwtZHJtKHkgfngsbXlmYWN0b3IsIGZjdCA9IExMMi41KCkpICNHZW5lcmFsaXNlZCBsb2ctbG9naXN0aWMNCm0uTEwuNTwtZHJtKHkgfngsbXlmYWN0b3IsIGZjdCA9IExMLjUoKSkgIzUtcGFyYW1ldGVyIGxvZ2lzdGljICANCg0KI3Bsb3QgYSBmZXcgc2lkZSBieSBzaWRlDQpwYXIobWZyb3cgPSBjKDEsIDIpLCBvbWEgPSBjKDAsIDAsIDAsIDApKQ0KcGxvdChtLkxMLjQsbG9nPSIiLCAgYnJva2VuID0gVFJVRSwgYmNvbnRyb2wgPSBsaXN0KHN0eWxlID0gInNsYXNoIiksIGNvbCA9IGMoMiw2LDMsMjMsNTYpLCBtYWluID0gIjQtcGFyYW1ldGVyIGxvZ2lzdGljIikNCnBsb3QobS5MTDIuNSxsb2c9IiIsICBicm9rZW4gPSBUUlVFLCBiY29udHJvbCA9IGxpc3Qoc3R5bGUgPSAic2xhc2giKSwgY29sID0gYygyLDYsMywyMyw1NiksIG1haW4gPSAiNS1wYXJhbWV0ZXIgbG9naXN0aWMiKQ0KDQpgYGANCg0KDQojIyBwbG90IHJlc2lkdWFscyBvZiBub25saW5lYXIgbW9kZWxzIGdlbmVyYXRlZCB3aXRoIGxpYnJhcnkoZHJjKQ0KYGBge3J9DQpwYXIobWZyb3cgPSBjKDMsIDIpLCBvbWEgPSBjKDAsIDAsIDAsIDApKQ0KDQp7cGxvdChtLk1NLjMkcHJlZHJlcywgbWFpbiA9ICJNaWNoYWVsaXMtTWVudGVuIikgIA0KICBhYmxpbmUoMCwwKX0NCntwbG90KG0uVzIuNCRwcmVkcmVzLCBtYWluID0gIldlaWJ1bGwyIikgDQogIGFibGluZSgwLDApfQ0Ke3Bsb3QobS5MTC40JHByZWRyZXMsIG1haW4gPSAiNC1wYXJhbWV0ZXIgbG9naXN0aWMiKSANCiAgYWJsaW5lKDAsMCl9DQp7cGxvdChtLkxMLjUkcHJlZHJlcywgbWFpbiA9ICI1LXBhcmFtZXRlciBsb2dpc3RpYyIpIA0KICBhYmxpbmUoMCwwKX0NCntwbG90KG0uQkMuNSRwcmVkcmVzLCBtYWluID0gIkJyYWluLUNvdXNlbnMgKGhvcm1lc2lzKSIpIA0KICBhYmxpbmUoMCwwKX0NCntwbG90KG0uTEwyLjUkcHJlZHJlcywgbWFpbiA9ICJHZW5lcmFsaXNlZCBsb2ctbG9naXN0aWMiKSANCiAgYWJsaW5lKDAsMCl9DQpgYGANCg0KDQpmcm9tID8/QkMuNTogIFRoZSBtb2RlbCBmdW5jdGlvbiBmb3IgdGhlIEJyYWluLUNvdXNlbnMgbW9kZWwgKEJyYWluIGFuZCBDb3VzZW5zLCAxOTg5KSBpcw0KJGYoeCwgYixjLGQsZSxmKSA9IGMgKyBcZnJhY3tkLWMrZnh9ezErXGV4cChiKFxsb2coeCktXGxvZyhlKSkpfSQNCihkb2Vzbid0IGxvb2sgYW55IGJldHRlciB0aGFuIExMLjQsIG5vdCBzdXJlIHdoeSBJJ20gZXZlbiBsb29raW5nLi4uKQ0KDQpgYGB7cn0NCiNwaWNrIG9uZSBtb2RlbCB0byBiZSBteW1vZGVsDQpteW1vZGVsIDwtIG0uTEwuNA0KbXltb2RlbA0KY29uZmludChteW1vZGVsKQ0Kc3VtbWFyeShteW1vZGVsKQ0KDQpgYGANCg0KRWFjaCBsZXZlbCAocm91c2UgYW5kIHN0aWxsKSBoYXMgYSB1bmlxdWUgY3VydmUgc2hhcGUgdGhlcmVieSByZXN1bHRpbmcgaW4gZGlmZmVyZW50IHBhcmFtZXRlcnMgZm9yIGEgYmVzdC1maXQgbG9nLWxvZ2lzdGljIG1vZGVsLiAgTm93IHRoYXQgd2Uga25vdyB0aGlzLCBpdCBpcyBwcmFjdGljYWwgdG8gYWRkcmVzcyBlYWNoIHNldCBvZiBjb25kaXRpb25zIHNlcGFyYXRlbHkuIEJhc2VkIG9uIHRoZSA0LXBhcmFtZXRlciBsb2dpc3RpYyBtb2RlbCBMTC40IHdlIGhhdmUgZm9yIHJvdXNlZCBzYW1wbGVzOg0KDQokUExBVE9fe3JvdXNlZH0gPSBQX3tpbmZ9KyAgXGZyYWN7T0UtUF97aW5mfX17MStlXnstRl97bWF4fShkYXlzb25ob3BzLU0pfX0kDQoNCm9yDQoNCiRQTEFUT197cm91c2VkfSAgPSAxLjgwOSArIFxmcmFjezEuOTR9ezErZV57LTEuMzkyMyhkYXlzb25ob3BzLTEyLjI3NCl9fSQNCg0KYW5kIGZvciB1bnRvdWNoZWQgKHN0aWxsKSBzYW1wbGVzOg0KDQokUExBVE9fe3N0aWxsfSAgPSAyLjE0OSArIFxmcmFjezEuNjB9ezErZV57LTEuODAwNChkYXlzb25ob3BzLTcuOTU4KX19JA0KDQpQbG90dGluZyBhbmQgYW5hbHlzaXMgb3B0aW9ucyBmb3IgbXVsdGlsZXZlbCBtb2RlbHMgYXJlIGxpbWl0ZWQuICBCeSBmaWx0ZXJpbmcgdGhlIGRhdGEgdG8gZm9jdXMgb24gb25lIHNldCBvZiBjb25kaXRpb25zIGF0IGEgdGltZSB3ZSBjcmVhdGUgJ3NpbXBsZScgeX54IGxvZ2lzdGljIG1vZGVscy4gTWFueSB3b25kZXJmdWwgdmlzdWFsaXphdGlvbiB0b29scyBhcmUgYXZhaWxhYmxlIGZvciBzdWNoIHh+eSBtb2RlbHMuICBIZXJlJ3MganVzdCBhIGZldzoNCg0KIyMgc2luZ2xlIGxldmVsIGxvZ2lzdGljIG1vZGVsIHdpdGggY29uZmlkZW5jZSBpbnRlcnZhbHMgDQooc3BlY2lmeSBldmFsPWZhbHNlIGluIFIgIG5vdGVib29rIGNvZGVjaHVuayBoZWFkZXIge3IgZXZhbD1GQUxTRX0gdG8gcHJldmVudCBtdWx0aXBsZSBtZXNzYWdlcyByZWdhcmRpbmcgIlJlY3ljbGluZyBhcnJheSBvZiBsZW5ndGggMSBpbiBhcnJheS12ZWN0b3IgYXJpdGhtZXRpYyBpcyBkZXByZWNhdGVkIiBmcm9tIGFwcGVhcmluZyBpbiBodG1sKQ0KYGBge3IgZXZhbD1GQUxTRX0NCmRmIDwtIEZQSGNhbGMgJT4lIGZpbHRlcihleHB0PT0iIDJBIiAmIHJvdXNlPT1UUlVFKSANCmRmJFBMQVRPLmhvcGRyb3AgPC0gZGYkaW5pdGlhbF9wbGF0byArIGRmJGRlbHRhLnBsYXRvDQp4IDwtIGFzLm51bWVyaWMoZGYkZGF5c29uaG9wcykNCnkgPC0gZGYkUExBVE8uaG9wZHJvcA0KDQojIyBjcmVhdGUgbW9kZWwNCm15bW9kZWw8LWRybSh5IH54LCBmY3QgPSBMTC40KCkpICM0LXBhcmFtZXRlciBsb2dpc3RpYyB3aXRoIGxpYnJhcnkoZHJjKQ0KDQpwbG90KG15bW9kZWwsIG1haW4gPSAiZGVmYXVsdCAobG9nYXJpdGhtaWMpIHgtYXhpcyIpDQpwbG90KG15bW9kZWwsIGxvZz0iIiwgbWFpbiA9ICJub24tbG9nYXJpdGhtaWMgeC1heGlzIikNCg0KIyBjcmVhdGUgcHJlZGljdGlvbiBpbnRlcnZhbHMNCm5ld2RhdGEgPSBkYXRhLmZyYW1lKHkgPSB5LCB4ID0geCkNCmNvbmY5NSA8LSBhcy5kYXRhLmZyYW1lKHByZWRpY3QobXltb2RlbCwgbmV3ZGF0YSxpbnRlcnZhbCA9ICJjb25maWRlbmNlIiwgbGV2ZWwgPSAwLjk1KSkNCmNvbmY5NSR4IDwtIHgNCnByZWQ5NSA8LSBhcy5kYXRhLmZyYW1lKHByZWRpY3QobXltb2RlbCwgbmV3ZGF0YSwgaW50ZXJ2YWwgPSAicHJlZGljdGlvbiIsIGxldmVsID0gMC45NSkpDQpwcmVkOTUkeCA8LSB4DQpwcmVkOTk5IDwtIGFzLmRhdGEuZnJhbWUocHJlZGljdChteW1vZGVsLCBuZXdkYXRhLCBpbnRlcnZhbCA9ICJwcmVkaWN0aW9uIiwgbGV2ZWwgPSAwLjk5OSkpDQpwcmVkOTk5JHggPC0geA0KbXltb2RlbA0KYGBgDQpOb3RlOiAgSWYgeW91IGNvbXBhcmUgdGhlIG11bHRpLWxldmVsIG1vZGVsIHBhcmFtZXRlciBlc3RpbWF0ZXMgZm9yIHJvdXNlPVRSVUUgd2l0aCB0aG9zZSBmb3IgdW5pbGV2ZWwgbW9kZWwgYmFzZWQgb24gcm91c2U9VFJVRSBzdWJzZXQsIHRoZSByZXN1bHRzIGFyZSBzaW1pbGFyLCBidXQgbm90IGlkZW50aWNhbCENCg0KIyMgcGxvdCBwcmVkaWN0aW9uIGNvbmZpZGVuY2UgaW50ZXJ2YWxzDQpgYGB7cn0NCg0KIyBBc3ltbWV0cmljIHJpYmJvbnMgYmFzZWQgb24gcHJlZGljdGlvbiBpbnRlcnZhbHMNCiNuZXdkYXRhIDwtIGRhdGEuZnJhbWUoeSA9IGMoMy41LCAzLCAyLjksIDIuNSwxLjgsMS43KSwgeCA9IGMoMSw1LDEwLDIwLDMwLDQwKSkNCiNuZXdkYXRhIDwtIHRpZHlmZXJtaSAlPiUgZmlsdGVyKGJhdGNoID0gU0VMRUNUQkFUQ0gpICU+JSBzZWxlY3QoZGF5cyxwbGF0bykgDQpxcGxvdChkYXRhPXByZWQ5NSwgeD14LCB5PVByZWRpY3Rpb24sIHltaW49TG93ZXIsIHltYXg9UHJlZGljdGlvbiwgZ2VvbT0icmliYm9uIiwgZmlsbD1JKCJyZWQiKSwgYWxwaGE9SSgwLjIpKSArDQpnZW9tX3JpYmJvbihkYXRhPXByZWQ5NSwgYWVzKHg9eCwgeW1pbj1QcmVkaWN0aW9uLCB5bWF4PVVwcGVyKSwgZmlsbD1JKCJibHVlIiksIGFscGhhPUkoMC4yKSkgKw0KZ2VvbV9saW5lKGRhdGE9cHJlZDk1LCBhZXMoeD14LCB5PVByZWRpY3Rpb24pLCBjb2xvcj1JKCJncmVlbiIpLCBsd2Q9MSkrDQpnZW9tX3BvaW50KGRhdGE9bmV3ZGF0YSwgYWVzKHg9eCwgeT15LCB5bWluPU5VTEwsIHltYXg9TlVMTCksIHNpemU9MSwgY29sPSJibHVlIikrDQogeWxhYigieSIpDQoNCiMgVmlzdWFsaXNlIGludGVydmFscw0KIyBiYXNlZCBvbiBNYXVyaXRzIEV2ZXJzIChodHRwczovL3N0YWNrb3ZlcmZsb3cuY29tL3F1ZXN0aW9ucy80OTQ0NDQ4OS9ub25saW5lYXItcmVncmVzc2lvbi1wcmVkaWN0aW9uLWluLXI/cnE9MSkNCmRhdGEuZnJhbWUoeD14LCB5PXkpICU+JSBnZ3Bsb3QoYWVzKHgsIHkpKSArDQpnZW9tX3BvaW50KCkgKw0KZ2VvbV9saW5lKGRhdGEgPSBwcmVkOTUsIGFlcyh4ID0geCwgeSA9IFByZWRpY3Rpb24pKSArDQpnZW9tX3JpYmJvbihkYXRhID0gcHJlZDk5OSwgYWVzKHggPSB4LCB5bWluID0gTG93ZXIsIHltYXggPSBVcHBlciksZmlsbD1JKCJwaW5rIiksYWxwaGEgPSAwLjQpKw0KZ2VvbV9yaWJib24oZGF0YSA9IHByZWQ5NSwgYWVzKHggPSB4LCB5bWluID0gTG93ZXIsIHltYXggPSBVcHBlciksZmlsbD1JKCJncmVlbiIpLGFscGhhID0gMC40KSsNCmdlb21fcmliYm9uKGRhdGEgPSBjb25mOTUsIGFlcyh4ID0geCwgeW1pbiA9IExvd2VyLCB5bWF4ID0gVXBwZXIpLGZpbGw9SSgicHVycGxlIiksYWxwaGEgPSAwLjQpOw0KDQpgYGANCg0KDQojIHN1bW1hcnkNCg0KRm9yIGNhc2VzIHdoZXJlIDIwMTYgQ2VudGVubmlhbCBob3BzIGFyZSBhZGRlZCB0byB0aGlzIHBhcnRpY3VsYXIgYmFzZSBiZWVyIChzYW1wbGVkIGludG8gMTJveiBib3R0bGVzKSBuZWFyIHRoZSBlbmQgb2YgcHJpbWFyeSBmZXJtZW50YXRpb24gYXQgcm9vbSB0ZW1wZXJhdHVyZSBhbmQgYm90dGxlcyByZW1haW4gdW50b3VjaGVkIChzdGlsbCksIHRoZSA0LXBhcmFtZXRlciBsb2dpc3RpYyBlcXVhdGlvbiB3aXRoIHRoZSBmb2xsb3dpbmcgc2V0cyBvZiBwYXJhbWV0ZXJzIGlzIGEgcmVhc29uYWJsZSBtb2RlbCBvZiB0aGUgc3Vic2VxdWVudCBob3AtaW5kdWNlZCBwbGF0byBkcm9wOg0KJFBMQVRPX3tzdGlsbH0gPSBmKGRheXNvbmhvcHMpICA9IDIuMTQ5ICsgXGZyYWN7MS42MH17MStlXnstMS44MDA0KGRheXNvbmhvcHMtNy45NTgpfX0kDQoNClRoZSBmb2xsb3dpbmcgNC1wYXJhbWV0ZXIgbG9naXN0aWMgZXF1YXRpb24gaXMgYSByZWFzb25hYmxlIG1vZGVsIGZvciBjYXNlcyBpZGVudGljYWwgdG8gdGhlIGFib3ZlLCBidXQgd2hlcmUgYm90dGxlcyBhcmUgcm91c2VkIG9uY2UgZGFpbHk6DQokUExBVE9fe3JvdXNlZH0gPSBmKGRheXNvbmhvcHMpICA9IDEuODA5ICsgXGZyYWN7MS45NH17MStlXnstMS4zOTI1KGRheXNvbmhvcHMtMTIuMjcyKX19JA0KDQpUaGUgcmVzaWR1YWxzIGZvciB0aGUgNS1wYXJhbWV0ZXIgbG9naXN0aWMgbW9kZWxzIGFyZSBzaW1pbGFyIHRvIHRob3NlIG9idGFpbmVkIGZvciB0aGUgNC1wYXJhbWV0ZXIgZXF1YXRpb24sIGJ1dCBpbiB0aGUgY2FzZSBvZiB1bnJvdXNlZCBzYW1wbGVzIGEgdmlzdWFsIGluc3BlY3Rpb24gcmV2ZWFscyB2ZXJ5IGRpZmZlcmVudCBjdXJ2ZXMuICBUaGUgNC1wYXJhbWV0ZXIgbG9naXN0aWMgcHJlZGljdHMgYSBtdWNoIHNob3J0ZXIgImxhZyB0aW1lIiB0aGFuIGRvZXMgdGhlIDUtcGFyYW1ldGVyIGxvZ2lzdGljLiAgTW9yZSBkYXRhIHBvaW50cyBpbiB0aGUgZGF5cyBhZnRlciBkcnlob3BwaW5nIHdvdWxkIHNoZWQgbGlnaHQgb24gdGhlIHF1ZXN0aW9uIGFzIHRvIHdoaWNoIG1vZGVsIGlzIHN1cGVyaW9yLg0KDQpPbmx5IGEgc2luZ2xlIHZhcmlhYmxlIChyb3VzaW5nIHZzLiBzdGlsbCkgYW1vbmcgc2V2ZXJhbCBodW5kcmVkIHZhcmlhYmxlcyBlbmNvbXBhc3NpbmcgcmF3IG1hdGVyaWFscywgbWFjaGluZXMsIHBlb3BsZSBhbmQgcHJvY2VzcyByZXN1bHRlZCBpbiBxdWl0ZSBkaWZmZXJlbnQgZXN0aW1hdGVkIHBhcmFtZXRlcnMgZm9yIGEgNC1wYXJhbWV0ZXIgbG9naXN0aWMgbW9kZWwuICBUaGVzZSByZXN1bHRzIGhpZ2hsaWdodCB0aGUgZmFjdCB0aGF0IGFsbCBwcm9jZXNzIHZhcmlhYmxlcyBrbm93biB0byBzaWduaWZpY2FudGx5IGltcGFjdCB0aGUgZW5kcG9pbnRzIHdlIHdpc2ggdG8gY29udHJvbCBtdXN0IHRoZW1zZWx2ZXMgYmUgZGVmaW5lZCwgY29udHJvbGxlZCBhbmQvb3IgZG9jdW1lbnRlZCBpZiBtb2RlbHMgc3VjaCBhcyB0aGVzZSBhcmUgdG8gYmUgdXNlZnVsIHRvIGNoYXJhY3Rlcml6ZSB0aGUgcHJvY2VzcyBpbiBxdWVzdGlvbi4gIA0KDQojIGJpZ2NodW5rX3RpZHl0cmFuc2Zvcm0NCg0KYGBge3IgYmlnY2h1bmtfdGlkeXRyYW5zZm9ybSwgZWNobz1GQUxTRX0NCm15ZGF0YSA8LXJlYWQuY3N2KCJ1amJjX2FfMTQ2OTA4MV9zbTU0OTYudHh0IixzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UpICAjIyBTUEVDSUZZIGZpbGVuYW1lDQoNCiMjIGZpbGwgaW4gTUlTU0lORyBEQVRBIDooICBhZGQgdGltZSB6ZXJvIGRhdGEgZm9yIHRpbWUgc2VyaWVzIGFuYWx5c2lzKQ0KDQojIyBuZXN0ZWQgaWZlbHNlIHRvIGFkZCB0aW1lIHplcm8gcGxhdG8gdmFsdWVzIGZvciBlYWNoIGV4cHQ6DQpteWRhdGE8LSBteWRhdGEgJT4lIG11dGF0ZSguLCBpbml0aWFsX3BsYXRvID0gaWZlbHNlKGV4cHQ9PSIgMkEiLCAzLjc0LCBpZmVsc2UoZXhwdD09IiAxQSIsIDMuNjksIGlmZWxzZShleHB0PT0iIDFCIiwgMy41MywgaWZlbHNlKGV4cHQ9PSIgM0IiLCAzLjU0LCBpZmVsc2UoZXhwdD09IiA0QiIsIDQuNjYsIE5BKSkpKSkpDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICANCiMjIGFkZCBjb21wbGV0ZSB0aW1lLXplcm8gYW50b24gcGFhciBkYXRhIGZvciB0aW1lIHNlcmllcyAoZXhwdDJBKQ0KIyMgcXVhZHJ1cGxlLXVwIGVhY2ggdHJpcGxpY2F0ZSB0aW1lLXplcm8gbWVhc3VyZW1lbnQgKG9uZSBjb3B5IGZvciBlYWNoIE5IL0RIICYgcm91c2Uvc3RpbGwgY29tYm8pDQojIyBub3RlOiAgdGhpcyBwcmFjdGljZSBpcyBvZiBxdWVzdGlvbmFibGUgc3RhdGlzdGljYWwgcmlnb3IuICBJbiByZXRyb3NwZWN0IGl0IHdvdWxkIGhhdmUgYmVlbiBiZXR0ZXIgKGFuZCBhIGxvdCBlYXNpZXIpIHRvIGp1c3QgcnVuIDEyIHNhbXBsZXMgdGhyb3VnaCBBbnRvbiBvbiBkYXkwIQ0KdGltZXplcm8gPC0gbXlkYXRhICU+JSBmaWx0ZXIoZXhwdD09IiAyQSIpICU+JSBhcnJhbmdlKHNwZWNpYWxfZ3JvdXApICU+JSBzbGljZSgxOjEyKQ0KI2RhdGEuZW50cnkodGltZXplcm8pDQp0aW1lemVybyRUZXN0X0RhdGUgPC0gcmVwKGMoIjcvMjcvMjAxNyIsIjcvMjcvMjAxNyIsIjcvMjcvMjAxNyIpLDQpDQp0aW1lemVybyRUZXN0LnRpbWUgPC0gcmVwKGMoIjM6MjYgUE0iLCAiMzozMCBQTSIsICIzOjM0IFBNIiksNCkNCnRpbWV6ZXJvJEFCViA8LSA2LjY4DQp0aW1lemVybyRBQlcgPC0gNS4yDQp0aW1lemVybyRPRSA8LSAxNS45NQ0KdGltZXplcm8kRXIgPC0gNi4wOA0KdGltZXplcm8kRWEgPC0gcmVwKGMoMy43NCwgMy43MywgMy43NCksNCkNCnRpbWV6ZXJvJFNHIDwtIHJlcChjKDEuMDE0NjIsIDEuMDE0NjEsIDEuMDE0NjIpLDQpDQp0aW1lemVybyRSREYgPC0gcmVwKGMoNjMuODgsIDYzLjg5LCA2My44OCksNCkNCnRpbWV6ZXJvJEFERiA8LSByZXAoYyg3Ni41OCwgNzYuNjAsIDc2LjU4KSw0KQ0KdGltZXplcm8kQ2Fsb3JpZXMgPC0gcmVwKGMoMjE1LjM3LDIxNS4zNCwyMTUuMzUpLDQpDQp0aW1lemVyb1t0aW1lemVybyRob3A9PSJESCIsXSRjb250YWN0X2RheXMgPC0wDQp0aW1lemVyb1ssYygyODozMSldIDwtIDANCm15ZGF0YSA8LSByYmluZCh0aW1lemVybyxteWRhdGEpDQp3cml0ZS5jc3YobXlkYXRhLCAicXVpY2tpZS5jc3YiLCByb3cubmFtZXMgPSBGQUxTRSkNCm15ZGF0YTwtcmVhZC5jc3YoInF1aWNraWUuY3N2Iiwgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFKQ0KDQojIyMgIHRoaXMgaXMgYSByb3V0aW5lIGZvciBpbnNwZWN0aW5nIGRhdGEgIyMjDQojbXlkYXRhdHlwZXM8LSBhcy5kYXRhLmZyYW1lKGNiaW5kKHNhcHBseShteWRhdGEsIGNsYXNzKSkpICAjIyB0YWJsZSBvZiBkYXRhdHlwZXMNCiNteWRhdGF0eXBlcyRoZWFkZXJzIDwtIHJvd25hbWVzKG15ZGF0YXR5cGVzKSAgICAjIyBjb252ZXJ0IHJvd25hbWVzIHRvIHZhbHVlcw0KI25hX2NvdW50IDwtYXMuZGF0YS5mcmFtZShzYXBwbHkobXlkYXRhLCBmdW5jdGlvbih5KSBzdW0obGVuZ3RoKHdoaWNoKGlzLm5hKHkpKSkpKSkgIyMgY291bnQgTkFzIGJ5IGNvbHVtbg0KI215ZGF0YU92ZXJ2aWV3PC1hcy5kYXRhLmZyYW1lKGNiaW5kKG5hX2NvdW50LG15ZGF0YXR5cGVzKSkgICMjIHRhYmxlIG9mIE5BIGNvdW50cyBhbmQgZGF0YXR5cGVzDQojbmFtZXMobXlkYXRhT3ZlcnZpZXcpIDwtIGMoIk5BX2NvdW50IiwiRGF0YV9DbGFzcyIsICJIZWFkZXIiKQ0KI3N1bShpcy5uYShteWRhdGEpKSAgIyMgdG90YWwgY291bnQgb2YgTkEgdmFsdWVzIGluIGVudGlyZSBzaGVldCAoMjMxIGluIHRoaXMgY2FzZSkNCiNteWRhdGFPdmVydmlldyAlPiUgZmlsdGVyKE5BX2NvdW50PjApICAgICMjIGJyZWFrZG93biBvZiBOQSB2YWx1ZXMNCg0KZGF0ZWNvbHM8LSBkcHV0KG5hbWVzKGRwbHlyOjpzZWxlY3QobXlkYXRhLCBtYXRjaGVzKCJkYXRlIikpKSkgICMjIGhlYWRlcnMgY29udGFpbmluZyBzdHJpbmcgImRhdGUiID0+IGMoImJyZXdfZGF0ZSIsICJzYW1wbGVfY29sbGVjdGlvbl9kYXRlIiwgImRyeWhvcF9kYXRlIiwgIlRlc3RfRGF0ZSIpDQojIyBGT1JMT09QDQpmb3IgKGljb2wgaW4gZGF0ZWNvbHMpIHsNCiAgbmV3Y29sID0gcGFzdGUwKGljb2wsIi5QT1NJWGN0IikNCiAjIHByaW50KG5ld2NvbCkNCiAgbXlkYXRhWywgbmV3Y29sXSA9IGFzLlBPU0lYY3QobXlkYXRhWywgaWNvbF0sZm9ybWF0ID0gIiVtLyVkLyVZIikgIyMjICBDUkVBVEUgTkVXIFBPU0lYY3QgY29sdW1ucyAgIA0KIyAgbXlkYXRhWywgbmV3Y29sXSA9IGFzLlBPU0lYY3QoYXMubnVtZXJpYyhteWRhdGFbLCBpY29sXSkgICogKDYwKjYwKjI0KSwgb3JpZ2luPSIxODk5LTEyLTMwIikgIyMjICBtaWNyb3NvZnQgdGltZXMNCn0NCg0KIyMgY3JlYXRlIGRheW9mYWRkaXRpb24sIGRheXNvbmhvcHMsIGhvcHNfZ18xMDBtTCwgcG91bmRzX2JibCB2YXJpYWJsZXMgd2l0aCBkcGx5ciAibXV0YXRlIg0KbXlkYXRhPC0gbXlkYXRhICU+JSANCiAgbXV0YXRlKGRheW9mYWRkaXRpb24gPSBhcy5pbnRlZ2VyKGFzLm51bWVyaWMoZGlmZnRpbWUoZHJ5aG9wX2RhdGUuUE9TSVhjdCxicmV3X2RhdGUuUE9TSVhjdCkpKSwNCiAgICAgICAgIGRheXNvbmhvcHMgPSBhcy5pbnRlZ2VyKGFzLm51bWVyaWMoZGlmZnRpbWUoVGVzdF9EYXRlLlBPU0lYY3QsZHJ5aG9wX2RhdGUuUE9TSVhjdCkpLyg2MCo2MCoyNCkpLA0KICAgICAgICAgaG9wc19nXzEwMG1MID0gKG1nX2hvcHMvdm9sdW1lX21MKS8xMCwNCiAgICAgICAgIHBvdW5kc19iYmwgPSAobWdfaG9wcy92b2x1bWVfbUwpKjExNy80NTQNCiAgICAgICAgICkNCiAgICAgICAgIA0KbXlkYXRhJE9YPC1ncmVwbCgiT1giLCBteWRhdGEkSG9wX3R5cGUpICAgICAgICMjICBjcmVhdGUgbG9naWNhbCAiT1giIGNvbHVtbg0KbXlkYXRhJHJvdXNlPC1ncmVwbCgicm91c2UiLCBteWRhdGEkc3BlY2lhbF9ncm91cCkjIyAgY3JlYXRlIGxvZ2ljYWwgY29sdW1uDQpteWRhdGEkR3JpbmQ8LWdyZXBsKCJHcmluZCIsIG15ZGF0YSRIb3BfdHlwZSkgICAgICMjICBjcmVhdGUgbG9naWNhbCBjb2x1bW4NCm15ZGF0YSRDb25lPC1ncmVwbCgiQ29uZSIsIG15ZGF0YSRIb3BfdHlwZSkgICAgICAgIyMgIGNyZWF0ZSBsb2dpY2FsIGNvbHVtbg0KbXlkYXRhJGhhcnZlc3QyMDE0PC1ncmVwbCgiMTQiLCBteWRhdGEkSG9wX3R5cGUpICAjIyAgY3JlYXRlIGxvZ2ljYWwgY29sdW1uDQpteWRhdGEkaGFydmVzdDIwMTU8LWdyZXBsKCIxNSIsIG15ZGF0YSRIb3BfdHlwZSkgICMjICBjcmVhdGUgbG9naWNhbCBjb2x1bW4NCm15ZGF0YSRoYXJ2ZXN0MjAxNzwtZ3JlcGwoIjE3IiwgbXlkYXRhJEhvcF90eXBlKSAgIyMgIGNyZWF0ZSBsb2dpY2FsIGNvbHVtbg0KDQojIyBpZmVsc2Ugc3RhdGVtZW50IGZvciBoYXJ2ZXN0eWVhciAoaWYgbmVpdGhlciAyMDE0IG5vciAyMDE1IG5vciAyMDE3LCB0aGVuIDIwMTYpDQpteWRhdGEkaGFydmVzdFllYXIgPC0gaWZlbHNlKA0KICBteWRhdGEkaGFydmVzdDIwMTQ9PVRSVUUsIDIwMTQsDQogIGlmZWxzZShteWRhdGEkaGFydmVzdDIwMTU9PVRSVUUsIDIwMTUsIA0KICAgICAgICAgaWZlbHNlKG15ZGF0YSRoYXJ2ZXN0MjAxNz09VFJVRSwgMjAxNywgMjAxNikpKQ0KDQojIyBpZmVsc2Ugc3RhdGVtZW50IGZvciBmb3JtIG9mIGhvcHMgKGlmIG5laXRoZXIgY29uZSBub3IgZ3JvdW5kIG5vciBOSCwgdGhlbiBwZWxsZXQpIA0KbXlkYXRhJGZvcm1fb2ZfaG9wcyA8LSBpZmVsc2UoDQogIG15ZGF0YSRDb25lPT1UUlVFLCAiY29uZSIsDQogIGlmZWxzZShteWRhdGEkR3JpbmQ9PVRSVUUsICJncm91bmQiLA0KICAgICAgICAgaWZlbHNlKG15ZGF0YSRIb3BfdHlwZT09Ik5IIiwgIk5IIiwgInBlbGxldCIpKSkNCg0KIyMgaWZlbHNlIHN0YXRlbWVudCBmb3IgdGVtcGVyYXR1cmUgZ3JlYXRlciBvciBsZXNzIHRoYW4gMTAgDQpteWRhdGEkREhfdGVtcCA8LSBpZmVsc2UoDQogIG15ZGF0YSR0ZW1wLkM8MTAsICJjb2xkIiwNCiAgaWZlbHNlKG15ZGF0YSR0ZW1wLkM+MTAsICJ3YXJtIiwgInNvbWV0aGluZyBlbHNlIikpDQoNCm15ZGF0YSR2YXJpZXR5PC1teWRhdGEkSG9wX3R5cGUNCm15ZGF0YSR2YXJpZXR5PC0gZ3N1YigiWzAtOV0rIiwiIiwgbXlkYXRhJHZhcmlldHkpICMjIHJlbW92ZSBhbGwgbnVtYmVycw0KbXlkYXRhJHZhcmlldHk8LSBnc3ViKCJPWCIsIiIsIG15ZGF0YSR2YXJpZXR5KSAgICAgIyMgcmVtb3ZlIHNwZWNpZmljIHRleHQNCm15ZGF0YSR2YXJpZXR5PC0gZ3N1YigiR3JpbmQiLCIiLCBteWRhdGEkdmFyaWV0eSkNCm15ZGF0YSR2YXJpZXR5PC0gZ3N1YigiQ29uZSIsIiIsIG15ZGF0YSR2YXJpZXR5KQ0KbXlkYXRhJHZhcmlldHk8LSBnc3ViKCIgIiwiIiwgbXlkYXRhJHZhcmlldHkpICAgICAgIyMgcmVtb3ZlIHNwYWNlcw0KDQpteWRhdGE8LSBteWRhdGEgJT4lIG11dGF0ZShFWFBUbmV3PXBhc3RlMCgiZ3JvdXAiLGV4cHQsIHN1YnN0cihzcGVjaWFsX2dyb3VwLCAxLDIpLGFzLmNoYXJhY3Rlcihyb3VzZSkpKQ0KIyBjbGVhbiBpdCB1cCBieSByZW1vdmluZyAiTkEiIGFuZCBhbnkgc3BhY2VzIGR1ZSB0byBjYW5hcnljb2RlIGJ1Zw0KbXlkYXRhJEVYUFRuZXcgPC0gZ3N1YigiICIsIiIsIG15ZGF0YSRFWFBUbmV3KSAgIyMgcmVtb3ZlIGFueSBzcGFjZXMNCm15ZGF0YSRFWFBUbmV3IDwtIGdzdWIoIk5BIiwiIiwgbXlkYXRhJEVYUFRuZXcpICMjIHJlbW92ZSAiTkEiDQoNCiMjIHNlbGVjdCBhbmQgcmVhcnJhbmdlIGNvbHVtbnMgDQpteWRhdGEgPC0gbXlkYXRhICU+JSANCiAgZHBseXI6OnNlbGVjdChzYW1wbGVfaWQsZXhwdCwgRVhQVG5ldywgaG9wLCBCSU5ob3AsIHZhcmlldHksIE9YLCBoYXJ2ZXN0WWVhciwgZm9ybV9vZl9ob3BzLHNwZWNpYWxfY29uZGl0aW9ucywgcm91c2UsIGRheXNvbmhvcHMsIGRheW9mYWRkaXRpb24sIERIX3RlbXAsIHRlbXAuQywgaG9wc19nXzEwMG1MLCBwb3VuZHNfYmJsLA0KICAgICAgICAgQUJWLCBBQlcsIE9FLCBFciwgRWEsIFNHLCBSREYsIEFERiwgQ2Fsb3JpZXMsIA0KICAgICAgICAgZGhvcF9kYXksIGNvbnRhY3RfZGF5cywgUkVGX05ILCBBQlZfaW5jcmVhc2UsIA0KICAgICAgICAgYnJld19kYXRlLlBPU0lYY3QsIHNhbXBsZV9jb2xsZWN0aW9uX2RhdGUuUE9TSVhjdCwgZHJ5aG9wX2RhdGUuUE9TSVhjdCxUZXN0X0RhdGUuUE9TSVhjdCwgaW5pdGlhbF9wbGF0bykNCiMjIHJlbW92ZSAiLlBPU0lYY3QiIHN1ZmZpeC4gIExlYXZpbmcgaXQgYXMtaXMgd2lsbCBvbmx5IGFkZCB0byBjb25mdXNpb24gaWYvd2hlbiB0aGVzZSBkYXRhIGFyZSBzYXZlZCBhbmQgcmUtaW1wb3J0ZWQgKGFuZCBiZWNvbWUgJ2NoYXJhY3RlcicgZm9ybWF0ISkNCmNvbG5hbWVzKG15ZGF0YSkgPSBnc3ViKCIuUE9TSVhjdCIsICIiLCBjb2xuYW1lcyhteWRhdGEpKQ0KIyNjb21wdXRlIG1lYW4gTkggKGNvbnRyb2wpIHZhbHVlcyANCiMjIG1lYW4gTkggKGNvbnRyb2wpIHZhbHVlcyBmb3IgZWFjaCBFWFBUbmV3IGdyb3VwDQptZWFuLmNvbnRyb2xfTkg8LSBteWRhdGEgJT4lIA0KICBncm91cF9ieShFWFBUbmV3KSAlPiUNCiAgZmlsdGVyKGhvcD09Ik5IIikgJT4lDQogIHN1bW1hcmlzZV9hdCh2YXJzKEFCViwgQUJXLCBFYSwgU0cpLGZ1bnMobWVhbiwgbigpKSkNCiMjIHJlcGxhY2UgIl9tZWFuIiB3aXRoICIuY29udHJvbF9OSCIgaW4gY29sdW1uIG5hbWVzDQpjb2xuYW1lcyhtZWFuLmNvbnRyb2xfTkgpID0gZ3N1YigiX21lYW4iLCAiLmNvbnRyb2xfTkgiLCBjb2xuYW1lcyhtZWFuLmNvbnRyb2xfTkgpKQ0KIyNjb21wdXRlICRcRGVsdGEkIHZhbHVlcyAoY2hhbmdlcyByZWxhdGl2ZSB0byB1bi1kcnlob3BwZWQgY29udHJvbHMpIHVzaW5nIG9iamVjdHMgY3JlYXRlZCBhYm92ZS4uLg0KIyMgZmlyc3Qgam9pbiBvdXIgZGF0YSB3aXRoIG1lYW4gQUJWIGZvciB1bmhvcHBlZCBzYW1wbGVzIGluIGdpdmVuIGV4cGVyaW1lbnQgKG1lYW4uY29udHJvbF9OSDsgY2FsY3VsYXRlZCBhYm92ZSkNCg0KRlBIY2FsYzwtIGxlZnRfam9pbihteWRhdGEsIG1lYW4uY29udHJvbF9OSCwgYnk9IkVYUFRuZXciKQ0KDQojIyBjYWxjdWxhdGUgQUJXX2luY3JlYXNlIGJ5IHN1YnRyYWN0aW5nIGVhY2ggaW5kaXZpZHVhbCBBQlcgbWVhc3VyZW1lbnQgZnJvbSByZXNwZWN0aXZlIG1lYW4uY29udHJvbF9OSDoNCkZQSGNhbGMkZGVsdGEuQUJWIDwtIEZQSGNhbGMkQUJWIC0gRlBIY2FsYyRBQlYuY29udHJvbF9OSA0KRlBIY2FsYyRkZWx0YS5BQlcgPC0gRlBIY2FsYyRBQlcgLSBGUEhjYWxjJEFCVy5jb250cm9sX05IDQpGUEhjYWxjJGRlbHRhLnBsYXRvIDwtIEZQSGNhbGMkRWEgLSBGUEhjYWxjJEVhLmNvbnRyb2xfTkgNCkZQSGNhbGMkZGVsdGEuU0cgPC0gRlBIY2FsYyRTRyAtIEZQSGNhbGMkU0cuY29udHJvbF9OSA0KDQojIyB0aGUgY29udHJvbCBzYW1wbGVzIGhhdmUgc2VydmVkIHRoZWlyIHB1cnBvc2UsIG5vdyByZW1vdmUgdGhlbSBmcm9tIGRhdGFzZXQuIFRoZSBmb2xsb3dpbmcgY2FsY3VsYXRpb25zIGFyZSBvbmx5IG1lYW5pbmdmdWwgZm9yIGRyeS1ob3BwZWQgc2FtcGxlcy4gIA0KRlBIY2FsYzwtIEZQSGNhbGMgJT4lIGZpbHRlcihob3A9PSJESCIpDQojIyAqY2FsY2FsYXRlKiBjb3JyZXNwb25kaW5nIENPMiBwcm9kdWN0aW9uIA0KRlBIY2FsYyRjYWxjQ08yX2luY3JlYXNlIDwtIEZQSGNhbGMkZGVsdGEuQUJXKigwLjQ0LzAuNDYpDQojI2NvbnZlcnQgY2FsY0NPMl9pbmNyZWFzZSAoaW4gZy8xMDBtTCkgdG8gY2FsY3VsYXRlZCBDTzIgdm9sdW1lcyBhZGRlZA0KIyMgZy9MID0gMTAqIGcvMTAwbUwNCiMjIFRoZSBjb252ZXJzaW9uIGZhY3RvciBmcm9tIHZvbHVtZXMgb2YgQ08yIHRvIENPMiBieSB3ZWlnaHQgKGcvTCkgaXMgMS45Ni4gRm9yIGV4YW1wbGU6IDIuNSB2b2x1bWVzIHggMS45NiA9IDQuOSBnL2wuDQpGUEhjYWxjJGNhbGNDTzJ2b2xzX2luY3JlYXNlIDwtIEZQSGNhbGMkY2FsY0NPMl9pbmNyZWFzZSoxMC8xLjk2DQojIyBkZWZpbmUgIkZQSCIgYXMgYW1vdW50IHByb2R1Y2VkIHBlciAlIGRyeS1ob3BzIGFkZGVkIChpbiBnLzEwMG1MKToNCiMjICBGUEggPSBGb2xkIFByb2R1Y3Rpb24gZHVlIHRvIEhvcHMgKGZvbGQtaW5jcmVhc2UgYnkgbWFzczogYW1vdW50IG9mIGdpdmVuIGVuZHBvaW50IHJlbGF0aXZlIHRvIGFtb3VudCBvZiBob3BzIGFkZGVkKQ0KIyMgIA0KRlBIY2FsYyRGUEhfRXRPSCA9IEZQSGNhbGMkZGVsdGEuQUJXL0ZQSGNhbGMkaG9wc19nXzEwMG1MDQpGUEhjYWxjJEZQSF9DTzIgPSBGUEhjYWxjJGNhbGNDTzJfaW5jcmVhc2UvRlBIY2FsYyRob3BzX2dfMTAwbUwNCkZQSGNhbGMkRlBIX3BsYXRvID0gRlBIY2FsYyRkZWx0YS5wbGF0by9GUEhjYWxjJGhvcHNfZ18xMDBtTA0KDQojIyByZXBsYWNpbmcgdGltZS16ZXJvIHRpbWUgdmFsdWVzIHdpdGggYSB2ZXJ5IHNtYWxsIG51bWJlciAocmF0aGVyIHRoYW4gZXhhY3RseSB6ZXJvKSB3aWxsIHByZXZlbnQgaXNzdWVzIHdpdGggYW5hbHlzaXMgb2Ygbm9ubGluZWFyIG1vZGVscw0KRlBIY2FsY1tGUEhjYWxjJGRheXNvbmhvcHM9PTAsXSRkYXlzb25ob3BzIDwtIDAuMDENCg0KIyMgYW5kIHNhdmUgdGhlIHRyYW5zZm9ybWVkIGRhdGEgdG8gY3N2Og0Kd3JpdGUuY3N2KEZQSGNhbGMsIkZQSGNhbGMuY3N2Iiwgcm93Lm5hbWVzID0gRkFMU0UpDQpGUEhjYWxjIDwtIHJlYWQuY3N2KCJGUEhjYWxjLmNzdiIsIHN0cmluZ3NBc0ZhY3RvcnMgPSBUUlVFKQ0KDQpkZjwtIEZQSGNhbGMgJT4lIGdyb3VwX2J5KEVYUFRuZXcpICU+JQ0KICBzdW1tYXJpc2VfYXQodmFycyhob3BzX2dfMTAwbUwscG91bmRzX2JibCxkZWx0YS5BQlYsIGRlbHRhLkFCVywgZGVsdGEucGxhdG8sIEZQSF9wbGF0bywgRlBIX0V0T0gsIEZQSF9DTzIsIGNhbGNDTzJ2b2xzX2luY3JlYXNlKSxmdW5zKHJvdW5kKG1lYW4oLiksIDIpKSkgJT4lDQogIGFycmFuZ2UoZGVzYyhGUEhfRXRPSCkpIA0KZGYNCmBgYA0KDQojc2Vzc2lvbiBpbmZvDQpgYGB7cn0NCnNlc3Npb25JbmZvKCkNCmBgYA0KDQo=