Wrangling
- tidyr makes data tidy
- 2 workhouse functions
- gather: Makes wide data long
- spread: Makes long data wide
- 2 supplemental functions
- separate/unite: breaks/merges columns into multiple/one column
- extract: takes groups within a column to make new columns
Example Dataset
- Longitudinal data of trust over 5-time points (game plays) with the
within-subject variable of vision
- Do you trust your partner in the game of “trouble” when you can or
cannot see them
- Covariate (how “judging” personality type they are 1-3 scale)
- Data is in typical wide SPSS-like format
- Download
Data
TrustWide<-read.csv("Mixed/TrustWideData.csv")
Subject
|
NoSee_0
|
NoSee_1
|
NoSee_2
|
NoSee_3
|
NoSee_4
|
See_0
|
See_1
|
See_2
|
See_3
|
See_4
|
Personality
|
1
|
0.1301
|
0.1504
|
0.1209
|
0.1216
|
0.1525
|
0.295
|
0.44
|
0.475
|
0.42
|
0.265
|
0.0222995
|
2
|
0.2051
|
0.2004
|
0.1959
|
0.2316
|
0.2575
|
0.460
|
0.59
|
0.660
|
0.66
|
0.530
|
0.6669353
|
3
|
0.0601
|
0.0604
|
0.1309
|
0.2316
|
0.3425
|
0.430
|
0.61
|
0.720
|
0.77
|
0.750
|
0.7457592
|
4
|
0.2101
|
0.2404
|
0.2209
|
0.2416
|
0.2425
|
0.435
|
0.57
|
0.625
|
0.59
|
0.425
|
0.8527015
|
5
|
0.1451
|
0.1304
|
0.1359
|
0.1716
|
0.1975
|
0.420
|
0.54
|
0.610
|
0.60
|
0.490
|
0.6066250
|
6
|
0.1101
|
0.1404
|
0.1909
|
0.2616
|
0.3625
|
0.440
|
0.57
|
0.640
|
0.67
|
0.650
|
2.1633908
|
- This is not so useful for taking means and doing stats, so we will
convert it to long format
Convert to Long
- Process in words:
- We will use piping %>% to help us pass the
dataset along to each function (so you ignore when the function asks for
dataframe first)
- gather(new variable name, new value name, variables to merge)
library(tidyr)
library(dplyr)
TrustLong<-TrustWide %>% gather(Condition, TrustFeeling,NoSee_0:See_4)
str(TrustLong)
## 'data.frame': 120 obs. of 4 variables:
## $ Subject : int 1 2 3 4 5 6 7 8 9 10 ...
## $ Personality : num 0.0223 0.6669 0.7458 0.8527 0.6066 ...
## $ Condition : chr "NoSee_0" "NoSee_0" "NoSee_0" "NoSee_0" ...
## $ TrustFeeling: num 0.1301 0.2051 0.0601 0.2101 0.1451 ...
Subject
|
Personality
|
Condition
|
TrustFeeling
|
1
|
0.0222995
|
NoSee_0
|
0.1301
|
2
|
0.6669353
|
NoSee_0
|
0.2051
|
3
|
0.7457592
|
NoSee_0
|
0.0601
|
4
|
0.8527015
|
NoSee_0
|
0.2101
|
5
|
0.6066250
|
NoSee_0
|
0.1451
|
6
|
2.1633908
|
NoSee_0
|
0.1101
|
- Personality applies to the subject, so every time we see the subject
names, we need their Personality score
- Our two variables (Vision & Time) are all stuck together. Good
for SPSS RM anova, but not helpful for us.
- separate(variable to split, c(“what do we call them”), what
separates them, convert them to back to numeric/integers if they are
numbers)
TrustLong.Final<-TrustLong %>% separate(Condition,c("Vision","Time"),sep="_", convert = TRUE)
Subject
|
Personality
|
Vision
|
Time
|
TrustFeeling
|
1
|
0.0222995
|
NoSee
|
0
|
0.1301
|
2
|
0.6669353
|
NoSee
|
0
|
0.2051
|
3
|
0.7457592
|
NoSee
|
0
|
0.0601
|
4
|
0.8527015
|
NoSee
|
0
|
0.2101
|
5
|
0.6066250
|
NoSee
|
0
|
0.1451
|
6
|
2.1633908
|
NoSee
|
0
|
0.1101
|
- You can do this all at once!
TrustLong.Final<-
TrustWide %>%
gather(Condition, TrustFeeling,NoSee_0:See_4) %>%
separate(Condition,c("Vision","Time"),sep="_", convert = TRUE)
Convert back to wide
- You just have to inverse the process. First, unite the variables
into one column and then spread them out
TrustWide.Again<-
TrustLong.Final %>%
unite(Condition,c("Vision","Time"),sep="_") %>%
spread(Condition, TrustFeeling)
Subject
|
Personality
|
NoSee_0
|
NoSee_1
|
NoSee_2
|
NoSee_3
|
NoSee_4
|
See_0
|
See_1
|
See_2
|
See_3
|
See_4
|
1
|
0.0222995
|
0.1301
|
0.1504
|
0.1209
|
0.1216
|
0.1525
|
0.295
|
0.44
|
0.475
|
0.42
|
0.265
|
2
|
0.6669353
|
0.2051
|
0.2004
|
0.1959
|
0.2316
|
0.2575
|
0.460
|
0.59
|
0.660
|
0.66
|
0.530
|
3
|
0.7457592
|
0.0601
|
0.0604
|
0.1309
|
0.2316
|
0.3425
|
0.430
|
0.61
|
0.720
|
0.77
|
0.750
|
4
|
0.8527015
|
0.2101
|
0.2404
|
0.2209
|
0.2416
|
0.2425
|
0.435
|
0.57
|
0.625
|
0.59
|
0.425
|
5
|
0.6066250
|
0.1451
|
0.1304
|
0.1359
|
0.1716
|
0.1975
|
0.420
|
0.54
|
0.610
|
0.60
|
0.490
|
6
|
2.1633908
|
0.1101
|
0.1404
|
0.1909
|
0.2616
|
0.3625
|
0.440
|
0.57
|
0.640
|
0.67
|
0.650
|
Data Manipulation
- dplyr lets you calculate means, sd or any other statistic you may
want on the data based on how the data was wrangled.
- 3 useful functions
- group_by (how to cut data)
- filter (subset on the fly)
- summarise (what descriptive to conduct)
- do (do more complex stats) [with the help of broom package which
converts regressions to tibbles]
- mutate (add stats back to data frame on the fly)
Group and summarise
- We can calculate means and sd per group
Means<-TrustLong.Final %>%
group_by(Vision,Time) %>%
summarise(MeanTrust=mean(TrustFeeling),
SDTrust=sd(TrustFeeling))
Vision
|
Time
|
MeanTrust
|
SDTrust
|
NoSee
|
0
|
0.1451000
|
0.0596200
|
NoSee
|
1
|
0.1512333
|
0.0751312
|
NoSee
|
2
|
0.1717333
|
0.0662582
|
NoSee
|
3
|
0.2091000
|
0.0988226
|
NoSee
|
4
|
0.2591667
|
0.1212685
|
See
|
0
|
0.4141667
|
0.0504450
|
See
|
1
|
0.5525000
|
0.0525919
|
See
|
2
|
0.6191667
|
0.0801088
|
See
|
3
|
0.6183333
|
0.1228327
|
See
|
4
|
0.5150000
|
0.1749026
|
- we can also calculate a correlation per subject separately per
vision condition
library(broom)
CorrResult<-TrustLong.Final %>%
group_by(Vision,Subject) %>%
do(tidy(cor.test(.$Time, .$TrustFeeling)))
Vision
|
Subject
|
estimate
|
statistic
|
p.value
|
parameter
|
conf.low
|
conf.high
|
method
|
alternative
|
NoSee
|
1
|
0.1645258
|
0.2889041
|
0.7914681
|
3
|
-0.8396155
|
0.9141048
|
Pearson’s product-moment correlation
|
two.sided
|
NoSee
|
2
|
0.8261808
|
2.5398903
|
0.0846876
|
3
|
-0.2068905
|
0.9881634
|
Pearson’s product-moment correlation
|
two.sided
|
NoSee
|
3
|
0.9577867
|
5.7706177
|
0.0103452
|
3
|
0.4873004
|
0.9973063
|
Pearson’s product-moment correlation
|
two.sided
|
NoSee
|
4
|
0.7068877
|
1.7309782
|
0.1818874
|
3
|
-0.4660154
|
0.9787461
|
Pearson’s product-moment correlation
|
two.sided
|
NoSee
|
5
|
0.8234354
|
2.5135828
|
0.0866641
|
3
|
-0.2150959
|
0.9879596
|
Pearson’s product-moment correlation
|
two.sided
|
NoSee
|
6
|
0.9769363
|
7.9243892
|
0.0041901
|
3
|
0.6856064
|
0.9985416
|
Pearson’s product-moment correlation
|
two.sided
|
- or we can also run regression per subject separately per vision
condition
RegresResult<-TrustLong.Final %>%
group_by(Vision,Subject) %>%
do(tidy(lm(TrustFeeling ~ Time, data=.)))
Vision
|
Subject
|
term
|
estimate
|
std.error
|
statistic
|
p.value
|
NoSee
|
1
|
(Intercept)
|
0.1319
|
0.0135657
|
9.7230572
|
0.0023108
|
NoSee
|
1
|
Time
|
0.0016
|
0.0055382
|
0.2889041
|
0.7914681
|
NoSee
|
2
|
(Intercept)
|
0.1909
|
0.0131159
|
14.5548039
|
0.0007033
|
NoSee
|
2
|
Time
|
0.0136
|
0.0053546
|
2.5398903
|
0.0846876
|
NoSee
|
3
|
(Intercept)
|
0.0179
|
0.0312414
|
0.5729568
|
0.6068013
|
NoSee
|
3
|
Time
|
0.0736
|
0.0127543
|
5.7706177
|
0.0103452
|
- You mutate the dataset you just created and add a column created by
another column
RegresResult <-
RegresResult %>%
mutate(Sig = if_else(p.value < .05,1,0))
Vision
|
Subject
|
term
|
estimate
|
std.error
|
statistic
|
p.value
|
Sig
|
NoSee
|
1
|
(Intercept)
|
0.1319
|
0.0135657
|
9.7230572
|
0.0023108
|
1
|
NoSee
|
1
|
Time
|
0.0016
|
0.0055382
|
0.2889041
|
0.7914681
|
0
|
NoSee
|
2
|
(Intercept)
|
0.1909
|
0.0131159
|
14.5548039
|
0.0007033
|
1
|
NoSee
|
2
|
Time
|
0.0136
|
0.0053546
|
2.5398903
|
0.0846876
|
0
|
NoSee
|
3
|
(Intercept)
|
0.0179
|
0.0312414
|
0.5729568
|
0.6068013
|
0
|
NoSee
|
3
|
Time
|
0.0736
|
0.0127543
|
5.7706177
|
0.0103452
|
1
|
- Now you can take that new dataset and summarize it (mean slope
estimates just for Time by significant vs nonsignificant results)
[Note: dplyr::filter is used because its conflicts with function in
another package. Thus this forces filter function to come from the dplyr
package.]
RSum<-RegresResult %>%
group_by(Vision,Sig) %>%
dplyr::filter(term=="Time") %>%
summarise(N=length(Sig),
MeanSlope=mean(estimate))
Vision
|
Sig
|
N
|
MeanSlope
|
NoSee
|
0
|
8
|
0.0099750
|
NoSee
|
1
|
4
|
0.0658500
|
See
|
0
|
9
|
0.0071111
|
See
|
1
|
3
|
0.0856667
|
- Note: You can do this all at once because its conduct in the order
you call it
RegresFinal<-TrustLong.Final %>%
group_by(Vision,Subject) %>%
do(tidy(lm(TrustFeeling ~ Time, data=.))) %>%
mutate(Sig = if_else(p.value < .05,1,0)) %>%
group_by(Vision,Sig) %>%
dplyr::filter(term=="Time") %>%
summarise(N=length(Sig),
MeanSlope=mean(estimate))
- You can pipe all these results into a ggplot
ggplot
Inspired by “The Grammar of Graphics” Leland Wilkinson 1999
“Destined to become a landmark in statistical graphics, this book
provides a formal description of graphics, particularly static graphics,
playing much the same role for graphics as probability theory played for
statistics.”Journal of the American Statistical Association Former VP at
SPSS Inc. Founder of SYSTAT. Adjunct Professor of Statistics at
Northwestern University. He is also affiliated with the Computer Science
department at The University of Illinois at Chicago.
Grammar
- Data (data = ): Data.Frame to be
mapped
- Aesthetic mapping (aes( )): x = , y = ,
group = from your data. Can also add additional mapping into
the aes:
- (color = , shape = , size = , fill =
, alpha =, etc): These can be fixed values are variables
- Note: if place these calls into aes(x=IV, y=DV, color=subjectID),
then the subject will appear in your legend with those colors. You can
move some these maps into geom call to avoid that
- Geometric object (geom_): bar, point,
line, ribbons, shapes you want to graph based on your
as
- Position adjustments (position = ): goes with
geom_ call such as position_dodge (don’t overlap),
position_identity (leave as read in), position_jitter
(jitters data points in scatterplot)
- Statistical transformations (stats_): On the fly transforms
(such as averaging): can be used instead of geoms (Note: I
prefer to calculate stats outside of the plot when possible as its
easier to see what you are doing)
- Coordinate system (coord_): do you want to add
coord_cartesian(), coord_polar(), etc
- Scales (scale_) or simply (xlim, ylim). Override defaults
to control many aspects of the graphs
- Faceting (facet_): visual subsets: two options
grid or wrap
Layering
- First, you put on your jacket; then you put on your shoes, next
underwear, finally you shower, right?
- ggplot has very specific order that you should generally follow.
- what is my data, what are my mappings, what are geoms, how should I
position them, and last what do I want to do with the look
- The order of which you add calls is the order they appear. So later
calls will override earlier calls
Walkthrough
library(ggplot2)
G1<-ggplot(data = TrustLong.Final, aes(x = Time , y = TrustFeeling))
G1
- Step 2: add geom
- This can help you look up all the types
help.search("geom_", package = "ggplot2")
G2<-G1+geom_point()
G2
G3<-G2+geom_smooth()
G3
- This line is loess, let’s make it lm with second order polynomial:
\(y = x+x^2\), which we can write at
poly(X,2)
[orthogonal power polynomials]. [Note: you
will not always need to call stats::poly, normally you can write just
poly, but it is conflicting with another function]
?geom_smooth
G3a<-G2+geom_smooth(method='lm', formula = y ~ stats::poly(x,2))
G3a
- Step 4: facet grid by Vision condition
G4a<-G3a+facet_grid(~Vision)
G4a
G4b<-G4a+theme_bw()
G4b
- Modify the theme to change the minor grid lines
G4c<-G4b+theme(panel.grid.minor = element_blank())
G4c
- Change the x and y labels
G4d<-G4c+xlab("Time Step")+ylab("Trust Score")
G4d
- Override the geom_smooth with a different color line and set SE to
false
G4e<-G4d+geom_smooth(method='lm', se=FALSE, formula = y ~ stats::poly(x,2), color='red')
G4e
Poly.Plot<-ggplot(data = TrustLong.Final, aes(x = Time , y = TrustFeeling))+
facet_grid(~Vision)+
geom_point()+
geom_smooth(method='lm', se=FALSE, formula = y ~ stats::poly(x,2), color='red')+
xlab("Time Step")+ylab("Trust Score")+
theme_bw()+
theme(panel.grid.minor = element_blank())
Poly.Plot
Speggetti Plot
- We can add best fit line per subject, we just need to make the
subject a grouping variable
- aes(group=Subject)
- This can be added either at geom_smooth if you just want it to apply
to that geom such as below
Speg.Plot<-ggplot(data = TrustLong.Final, aes(x = Time , y = TrustFeeling))+
facet_grid(~Vision)+
geom_point()+
geom_smooth(aes(group=Subject),method='lm', se=FALSE, formula = y ~ stats::poly(x,2), color='red')+
xlab("Time Step")+ylab("Trust Score")+
theme_bw()+
theme(panel.grid.minor = element_blank())+
ggtitle("Trust Study")
Speg.Plot
- or you can apply it the over-arching aes
Speg.Plot.2<-ggplot(data = TrustLong.Final,
aes(x = Time , y = TrustFeeling,group=Subject))+
facet_grid(~Vision)+
geom_point()+
geom_smooth(method='lm', se=FALSE, formula = y ~ stats::poly(x,2), color='red')+
xlab("Time Step")+ylab("Trust Score")+
theme_bw()+
theme(panel.grid.minor = element_blank())+
ggtitle("Trust Study")
Speg.Plot.2
- now you applied to the over-arching aes you can do other fun stuff,
like color each data point and line relative the subject
- Notice below where I added: aes(color=Subject)
Speg.Plot.3<-ggplot(data = TrustLong.Final,
aes(x = Time, y = TrustFeeling, group=Subject, color=Subject))+
facet_grid(~Vision)+
geom_point()+
geom_smooth(method='lm', se=FALSE, formula = y ~ stats::poly(x,2))+
xlab("Time Step")+ylab("Trust Score")+
theme_bw()+
theme(panel.grid.minor = element_blank())
Speg.Plot.3
- What why is the color continuous?
str(TrustLong.Final)
## 'data.frame': 120 obs. of 5 variables:
## $ Subject : int 1 2 3 4 5 6 7 8 9 10 ...
## $ Personality : num 0.0223 0.6669 0.7458 0.8527 0.6066 ...
## $ Vision : chr "NoSee" "NoSee" "NoSee" "NoSee" ...
## $ Time : int 0 0 0 0 0 0 0 0 0 0 ...
## $ TrustFeeling: num 0.1301 0.2051 0.0601 0.2101 0.1451 ...
- Because Subject is interger
TrustLong.Final$Subject.F<-as.factor(TrustLong.Final$Subject)
- Remake the plot and replace Subject with Subject.F
- also we can add Shape and linetype by Subject (in case we have to
print in black and white)
Speg.Plot.4<-ggplot(data = TrustLong.Final,
aes(x = Time, y = TrustFeeling, group=Subject.F, color=Subject.F,shape=Subject.F,linetype=Subject.F))+
facet_grid(~Vision)+
geom_point()+
geom_smooth(method='lm', se=FALSE, formula = y ~ stats::poly(x,2))+
xlab("Time Step")+ylab("Trust Score")+
theme_bw()+
theme(panel.grid.minor = element_blank())
Speg.Plot.4
Speg.Plot.4a<-Speg.Plot.4+theme(legend.position = "none")
Speg.Plot.4a
- And you can make it totally “far out”
- You can use HTML or color labels
Speg.Plot.4a + theme(plot.background = element_rect(size = 1, color = "blue", fill = "purple"),
text=element_text(size = 12, family = "Serif", color = "ivory"),
axis.text.y = element_text(colour = "magenta"),
axis.text.x = element_text(colour = "green"),
panel.background = element_rect(fill = "pink"),
strip.background = element_rect(fill = "#ccff66"))
Tidyr & ggplot fun time
- Just for fun lets run the polynomial regressions and extract the
\(R^2\) per subject for Vision
TrustLong.Final$Subject.F<-as.factor(TrustLong.Final$Subject)
PolyRegress<-TrustLong.Final %>%
group_by(Vision, Subject.F) %>%
do(glance(lm(TrustFeeling ~ stats::poly(Time,2), data=.))) %>%
select(Vision,r.squared)
Subject.F
|
Vision
|
r.squared
|
1
|
NoSee
|
0.2266071
|
2
|
NoSee
|
0.9536065
|
3
|
NoSee
|
0.9938067
|
4
|
NoSee
|
0.5280375
|
5
|
NoSee
|
0.9600112
|
6
|
NoSee
|
0.9997217
|
- Let’s extract the Personality score and join it to that new
dataset
PersonalityScore<-TrustLong.Final %>%
dplyr::filter(Time==0) %>%
select(Subject.F,Personality, Vision)
MergedData<-left_join(PolyRegress,PersonalityScore)
Subject.F
|
Vision
|
r.squared
|
Personality
|
1
|
NoSee
|
0.2266071
|
0.0222995
|
2
|
NoSee
|
0.9536065
|
0.6669353
|
3
|
NoSee
|
0.9938067
|
0.7457592
|
4
|
NoSee
|
0.5280375
|
0.8527015
|
5
|
NoSee
|
0.9600112
|
0.6066250
|
6
|
NoSee
|
0.9997217
|
2.1633908
|
- Scatter plot by Vision with fancy labels
Scatter.plot<-ggplot(data = MergedData, aes(Personality,r.squared))+
geom_point(aes(shape=Vision))+
xlab("Personaility Score")+
ylab(expression(paste("Polynomial Regression, ", R^{2},' Result Per Subject')))+
theme_minimal()
Scatter.plot
- Reorder and rename labels
MergedData$Vision.O <- factor(MergedData$Vision,
levels = c("See","NoSee"),
labels = c("Partner is Visable","Partner is obscured"))
- Add new labels and fix up legend
Scatter.plot2<-ggplot(data = MergedData, aes(Personality,r.squared))+
geom_point(aes(shape=Vision.O),size = 2.5, stroke = 1.25)+
xlab("Personaility Score")+
ylab(expression(paste("Polynomial Regression, ", R^{2},' Result Per Subject')))+
theme_minimal()+theme(legend.position = "top",
legend.text = element_text(size = 11, color = "gray50"),
legend.title=element_blank())
Scatter.plot2
Scatter.plot3<-Scatter.plot2+scale_shape_manual(values=c(21,24))
Scatter.plot3
- Let’s add labels so we know which dots are which subjects
library("ggrepel")
Scatter.plot3 + scale_shape_manual(values=c(21,24))+
geom_text_repel(aes(label=Subject.F), size = 3)
- Label only a hand full of subjects
pointsToLabel <- c("1","6","12")
Scatter.plot4<-Scatter.plot3 + scale_shape_manual(values=c(21,24))+
geom_text_repel(aes(label = Subject.F),
color = "gray20",
data = subset(MergedData, Subject.F %in% pointsToLabel),
force = 10)
Scatter.plot4
Scatter.plot5<- Scatter.plot4+scale_x_continuous(name = "Personality Score, Judging (1=Most)",
limits = c(0.0, 2.5),
breaks = seq(0.0, 2.5, by = 0.25))
Scatter.plot5
- Add complex regression to the plot plot
Scatter.plot6 <-Scatter.plot5 +
geom_smooth(aes(linetype = Vision.O, group=Vision.O),
method = "lm",
formula = y ~ log(x), se = FALSE,
color = "green")
Scatter.plot6
LS0tDQp0aXRsZTogJ1RpZHl2ZXJzZSBPdmVydmlldycNCm91dHB1dDoNCiAgaHRtbF9kb2N1bWVudDoNCiAgICBjb2RlX2Rvd25sb2FkOiB5ZXMNCiAgICBmb250c2l6ZTogOHB0DQogICAgaGlnaGxpZ2h0OiB0ZXh0bWF0ZQ0KICAgIG51bWJlcl9zZWN0aW9uczogbm8NCiAgICB0aGVtZTogZmxhdGx5DQogICAgdG9jOiB5ZXMNCiAgICB0b2NfZmxvYXQ6DQogICAgICBjb2xsYXBzZWQ6IG5vDQotLS0NCg0KYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9DQprbml0cjo6b3B0c19jaHVuayRzZXQoY2FjaGUgPSBUUlVFKQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFKQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KG1lc3NhZ2UgPSBGQUxTRSkNCmtuaXRyOjpvcHRzX2NodW5rJHNldCh3YXJuaW5nID0gIEZBTFNFKQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGZpZy53aWR0aD01LjI1KQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGZpZy5oZWlnaHQ9NC4wKQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGZpZy5hbGlnbj0nY2VudGVyJykgDQprbml0cjo6b3B0c19jaHVuayRzZXQocmVzdWx0cz0naG9sZCcpIA0KYGBgDQoNCg0KIyBUaWR5dmVyc2UgDQotIEZhbWlseSBvZiBwYWNrYWdlcyBkZXZlbG9wZWQgdG8gc3RyZWFtbGluZSBncmFwaGluZywgZGF0YSBtYW5pcHVsYXRpb24sIGRhdGEgd3JhbmdsaW5nLCBhbmQgcHJvZ3JhbW1pbmcNCi0gTWFpbiBwYWNrYWdlcyBvZiBpbnRlcmVzdCBmb3IgdXMgdG9kYXk6IGdncGxvdDIsIGRwbHlyLCB0aWR5ciAodGhlc2UgaGF2ZSBldm9sdmVkIHRvIHRoZSBwb2ludCB0aGF0IHRoZXkgaGF2ZSByZXBsYWNlZCBwYWNrYWdlcyB3ZSBjb252ZXJ0ZWQgaW4gdGhlIHIgY2xhc3M6IHJlc2hhcGUyIGFuZCBwbHlyKQ0KLSBUaGVzZSBwYWNrYWdlcyBjYW4gd29yayBvbiBhIG1vZGVybiB0eXBlIG9mIGRhdGEgZnJhbWUgY2FsbGVkIGEgdGliYmxlIA0KLSBnZ3Bsb3QyIGlzIGZvciBncmFwaGluZw0KLSBkcGx5ciBpcyBmb3IgZGF0YSBtYW5pcHVsYXRpb24gKHRoaW5rIGV4Y2VsIHBpdm90IHRhYmxlcyBvbiBzdGVyb2lkcykNCi0gdGlkeXIgaXMgZm9yIGRhdGEgd3JhbmdsaW5nIChsb25nIHRvIHdpZGUgZm9ybWF0IGFuZCBiYWNrIGFnYWluKQ0KLSBUaGVzZSBwYWNrYWdlcyBjYW4gdXNlIHBpcGluZyAoaW4gZHBseXIgd2hpY2ggY2FsbHMgcHVycnIpIHdoaWNoIG1ha2VzIGl0IGVhc2llciB0byBzdHJpbmcgdG9nZXRoZXIgY29tbWFuZHMgDQoNCg0KIyBXcmFuZ2xpbmcgDQotIHRpZHlyIG1ha2VzIGRhdGEgdGlkeQ0KLSAyIHdvcmtob3VzZSBmdW5jdGlvbnMNCiAgICAtIGdhdGhlcjogTWFrZXMgd2lkZSBkYXRhIGxvbmcNCiAgICAtIHNwcmVhZDogTWFrZXMgbG9uZyBkYXRhIHdpZGUNCi0gMiBzdXBwbGVtZW50YWwgZnVuY3Rpb25zDQogICAgLSBzZXBhcmF0ZS91bml0ZTogYnJlYWtzL21lcmdlcyBjb2x1bW5zIGludG8gbXVsdGlwbGUvb25lIGNvbHVtbg0KICAgIC0gZXh0cmFjdDogdGFrZXMgZ3JvdXBzIHdpdGhpbiBhIGNvbHVtbiB0byBtYWtlIG5ldyBjb2x1bW5zIA0KDQojIyBFeGFtcGxlIERhdGFzZXQNCi0gTG9uZ2l0dWRpbmFsIGRhdGEgb2YgdHJ1c3Qgb3ZlciA1LXRpbWUgcG9pbnRzIChnYW1lIHBsYXlzKSB3aXRoIHRoZSB3aXRoaW4tc3ViamVjdCB2YXJpYWJsZSBvZiB2aXNpb24NCi0gRG8geW91IHRydXN0IHlvdXIgcGFydG5lciBpbiB0aGUgZ2FtZSBvZiAidHJvdWJsZSIgd2hlbiB5b3UgY2FuIG9yIGNhbm5vdCBzZWUgdGhlbSANCi0gQ292YXJpYXRlIChob3cgImp1ZGdpbmciIHBlcnNvbmFsaXR5IHR5cGUgdGhleSBhcmUgMS0zIHNjYWxlKQ0KLSBEYXRhIGlzIGluIHR5cGljYWwgd2lkZSBTUFNTLWxpa2UgZm9ybWF0DQotIFtEb3dubG9hZCBEYXRhXSh3d3cuYWxleGFuZGVyZGVtb3Mub3JnL01peGVkL1RydXN0V2lkZURhdGEuY3N2KQ0KDQpgYGB7cn0NClRydXN0V2lkZTwtcmVhZC5jc3YoIk1peGVkL1RydXN0V2lkZURhdGEuY3N2IikNCmBgYA0KDQpgYGB7ciwgZWNobz1GQUxTRX0NCmxpYnJhcnkoa25pdHIpOyBsaWJyYXJ5KGthYmxlRXh0cmEpIA0Ka2FibGUoaGVhZChUcnVzdFdpZGUpLCAiaHRtbCIsIGJvb2t0YWJzID0gVCkgJT4lDQogIGthYmxlX3N0eWxpbmcoYm9vdHN0cmFwX29wdGlvbnMgPSAic3RyaXBlZCIsIGZ1bGxfd2lkdGggPSBGKQ0KYGBgDQoNCg0KLSBUaGlzIGlzIG5vdCBzbyB1c2VmdWwgZm9yIHRha2luZyBtZWFucyBhbmQgZG9pbmcgc3RhdHMsIHNvIHdlIHdpbGwgY29udmVydCBpdCB0byBsb25nIGZvcm1hdA0KDQojIyBDb252ZXJ0IHRvIExvbmcNCi0gUHJvY2VzcyBpbiB3b3JkczogDQogICAgLSBXZSB3aWxsIHVzZSBwaXBpbmcgJT4lIHRvIGhlbHAgdXMgKipwYXNzKiogdGhlIGRhdGFzZXQgYWxvbmcgdG8gZWFjaCBmdW5jdGlvbiAoc28geW91IGlnbm9yZSB3aGVuIHRoZSBmdW5jdGlvbiBhc2tzIGZvciBkYXRhZnJhbWUgZmlyc3QpDQogICAgLSBnYXRoZXIobmV3IHZhcmlhYmxlIG5hbWUsIG5ldyB2YWx1ZSBuYW1lLCB2YXJpYWJsZXMgdG8gbWVyZ2UpDQoNCmBgYHtyfQ0KbGlicmFyeSh0aWR5cikNCmxpYnJhcnkoZHBseXIpDQpUcnVzdExvbmc8LVRydXN0V2lkZSAlPiUgZ2F0aGVyKENvbmRpdGlvbiwgVHJ1c3RGZWVsaW5nLE5vU2VlXzA6U2VlXzQpDQpzdHIoVHJ1c3RMb25nKQ0KYGBgDQoNCg0KYGBge3IsIHJlc3VsdHM9J2FzaXMnLGVjaG89RkFMU0V9DQprYWJsZShoZWFkKFRydXN0TG9uZyksICJodG1sIiwgYm9va3RhYnMgPSBUKSAlPiUNCiAga2FibGVfc3R5bGluZyhib290c3RyYXBfb3B0aW9ucyA9ICJzdHJpcGVkIiwgZnVsbF93aWR0aCA9IEYpDQpgYGANCg0KDQotIFBlcnNvbmFsaXR5IGFwcGxpZXMgdG8gdGhlIHN1YmplY3QsIHNvIGV2ZXJ5IHRpbWUgd2Ugc2VlIHRoZSBzdWJqZWN0IG5hbWVzLCB3ZSBuZWVkIHRoZWlyIFBlcnNvbmFsaXR5IHNjb3JlDQotIE91ciB0d28gdmFyaWFibGVzIChWaXNpb24gJiBUaW1lKSBhcmUgYWxsIHN0dWNrIHRvZ2V0aGVyLiBHb29kIGZvciBTUFNTIFJNIGFub3ZhLCBidXQgbm90IGhlbHBmdWwgZm9yIHVzLg0KICAgIC0gc2VwYXJhdGUodmFyaWFibGUgdG8gc3BsaXQsIGMoIndoYXQgZG8gd2UgY2FsbCB0aGVtIiksIHdoYXQgc2VwYXJhdGVzIHRoZW0sIGNvbnZlcnQgdGhlbSB0byBiYWNrIHRvIG51bWVyaWMvaW50ZWdlcnMgaWYgdGhleSBhcmUgbnVtYmVycykgDQoNCmBgYHtyfQ0KVHJ1c3RMb25nLkZpbmFsPC1UcnVzdExvbmcgJT4lIHNlcGFyYXRlKENvbmRpdGlvbixjKCJWaXNpb24iLCJUaW1lIiksc2VwPSJfIiwgY29udmVydCA9IFRSVUUpDQpgYGANCg0KYGBge3IsIHJlc3VsdHM9J2FzaXMnLGVjaG89RkFMU0V9DQprYWJsZShoZWFkKFRydXN0TG9uZy5GaW5hbCksICJodG1sIiwgYm9va3RhYnMgPSBUKSAlPiUNCiAga2FibGVfc3R5bGluZyhib290c3RyYXBfb3B0aW9ucyA9ICJzdHJpcGVkIiwgZnVsbF93aWR0aCA9IEYpDQpgYGANCg0KLSBZb3UgY2FuIGRvIHRoaXMgYWxsIGF0IG9uY2UhDQoNCmBgYHtyfQ0KVHJ1c3RMb25nLkZpbmFsPC0NCiAgVHJ1c3RXaWRlICU+JSANCiAgZ2F0aGVyKENvbmRpdGlvbiwgVHJ1c3RGZWVsaW5nLE5vU2VlXzA6U2VlXzQpICU+JSANCiAgc2VwYXJhdGUoQ29uZGl0aW9uLGMoIlZpc2lvbiIsIlRpbWUiKSxzZXA9Il8iLCBjb252ZXJ0ID0gVFJVRSkNCmBgYA0KDQojIyMgQ29udmVydCBiYWNrIHRvIHdpZGUNCi0gWW91IGp1c3QgaGF2ZSB0byBpbnZlcnNlIHRoZSBwcm9jZXNzLiBGaXJzdCwgdW5pdGUgdGhlIHZhcmlhYmxlcyBpbnRvIG9uZSBjb2x1bW4gYW5kIHRoZW4gc3ByZWFkIHRoZW0gb3V0DQoNCmBgYHtyfQ0KVHJ1c3RXaWRlLkFnYWluPC0NCiAgVHJ1c3RMb25nLkZpbmFsICU+JSANCiAgdW5pdGUoQ29uZGl0aW9uLGMoIlZpc2lvbiIsIlRpbWUiKSxzZXA9Il8iKSAlPiUgDQogIHNwcmVhZChDb25kaXRpb24sIFRydXN0RmVlbGluZykNCmBgYA0KDQpgYGB7ciwgcmVzdWx0cz0nYXNpcycsZWNobz1GQUxTRX0NCmthYmxlKGhlYWQoVHJ1c3RXaWRlLkFnYWluKSwgImh0bWwiLCBib29rdGFicyA9IFQpICU+JQ0KICBrYWJsZV9zdHlsaW5nKGJvb3RzdHJhcF9vcHRpb25zID0gInN0cmlwZWQiLCBmdWxsX3dpZHRoID0gRikNCmBgYA0KDQojIERhdGEgTWFuaXB1bGF0aW9uIA0KLSBkcGx5ciBsZXRzIHlvdSBjYWxjdWxhdGUgbWVhbnMsIHNkIG9yIGFueSBvdGhlciBzdGF0aXN0aWMgeW91IG1heSB3YW50IG9uIHRoZSBkYXRhIGJhc2VkIG9uIGhvdyB0aGUgZGF0YSB3YXMgd3JhbmdsZWQuDQotIDMgdXNlZnVsIGZ1bmN0aW9ucw0KICAtIGdyb3VwX2J5IChob3cgdG8gY3V0IGRhdGEpDQogIC0gZmlsdGVyIChzdWJzZXQgb24gdGhlIGZseSkNCiAgLSBzdW1tYXJpc2UgKHdoYXQgZGVzY3JpcHRpdmUgdG8gY29uZHVjdCkNCiAgLSBkbyAoZG8gbW9yZSBjb21wbGV4IHN0YXRzKSBbd2l0aCB0aGUgaGVscCBvZiBicm9vbSBwYWNrYWdlIHdoaWNoIGNvbnZlcnRzIHJlZ3Jlc3Npb25zIHRvIHRpYmJsZXNdDQogIC0gbXV0YXRlIChhZGQgc3RhdHMgYmFjayB0byBkYXRhIGZyYW1lIG9uIHRoZSBmbHkpDQogIA0KIyMgR3JvdXAgYW5kIHN1bW1hcmlzZQ0KLSBXZSBjYW4gY2FsY3VsYXRlIG1lYW5zIGFuZCBzZCBwZXIgZ3JvdXANCmBgYHtyfQ0KTWVhbnM8LVRydXN0TG9uZy5GaW5hbCAlPiUNCiAgZ3JvdXBfYnkoVmlzaW9uLFRpbWUpICU+JQ0KICBzdW1tYXJpc2UoTWVhblRydXN0PW1lYW4oVHJ1c3RGZWVsaW5nKSwNCiAgICAgICAgICAgIFNEVHJ1c3Q9c2QoVHJ1c3RGZWVsaW5nKSkNCmBgYA0KDQpgYGB7ciwgcmVzdWx0cz0nYXNpcycsZWNobz1GQUxTRX0NCmthYmxlKE1lYW5zLCAiaHRtbCIsIGJvb2t0YWJzID0gVCkgJT4lDQogIGthYmxlX3N0eWxpbmcoYm9vdHN0cmFwX29wdGlvbnMgPSAic3RyaXBlZCIsIGZ1bGxfd2lkdGggPSBGKQ0KYGBgDQoNCi0gd2UgY2FuIGFsc28gY2FsY3VsYXRlIGEgY29ycmVsYXRpb24gcGVyIHN1YmplY3Qgc2VwYXJhdGVseSBwZXIgdmlzaW9uIGNvbmRpdGlvbg0KDQpgYGB7cn0NCmxpYnJhcnkoYnJvb20pDQpDb3JyUmVzdWx0PC1UcnVzdExvbmcuRmluYWwgJT4lDQogIGdyb3VwX2J5KFZpc2lvbixTdWJqZWN0KSAlPiUNCiAgZG8odGlkeShjb3IudGVzdCguJFRpbWUsIC4kVHJ1c3RGZWVsaW5nKSkpDQpgYGANCg0KYGBge3IsIHJlc3VsdHM9J2FzaXMnLGVjaG89RkFMU0V9DQprYWJsZShoZWFkKENvcnJSZXN1bHQpLCAiaHRtbCIsIGJvb2t0YWJzID0gVCkgJT4lDQogIGthYmxlX3N0eWxpbmcoYm9vdHN0cmFwX29wdGlvbnMgPSAic3RyaXBlZCIsIGZ1bGxfd2lkdGggPSBGKQ0KYGBgDQoNCi0gb3Igd2UgY2FuIGFsc28gcnVuIHJlZ3Jlc3Npb24gcGVyIHN1YmplY3Qgc2VwYXJhdGVseSBwZXIgdmlzaW9uIGNvbmRpdGlvbiANCg0KYGBge3J9DQpSZWdyZXNSZXN1bHQ8LVRydXN0TG9uZy5GaW5hbCAlPiUNCiAgZ3JvdXBfYnkoVmlzaW9uLFN1YmplY3QpICU+JQ0KICBkbyh0aWR5KGxtKFRydXN0RmVlbGluZyB+IFRpbWUsIGRhdGE9LikpKQ0KDQpgYGANCg0KYGBge3IsIHJlc3VsdHM9J2FzaXMnLGVjaG89RkFMU0V9DQprYWJsZShoZWFkKFJlZ3Jlc1Jlc3VsdCksICJodG1sIiwgYm9va3RhYnMgPSBUKSAlPiUNCiAga2FibGVfc3R5bGluZyhib290c3RyYXBfb3B0aW9ucyA9ICJzdHJpcGVkIiwgZnVsbF93aWR0aCA9IEYpDQpgYGANCg0KLSBZb3UgbXV0YXRlIHRoZSBkYXRhc2V0IHlvdSBqdXN0IGNyZWF0ZWQgYW5kIGFkZCBhIGNvbHVtbiBjcmVhdGVkIGJ5IGFub3RoZXIgY29sdW1uDQoNCmBgYHtyfQ0KUmVncmVzUmVzdWx0IDwtDQogIFJlZ3Jlc1Jlc3VsdCAlPiUNCiAgbXV0YXRlKFNpZyA9IGlmX2Vsc2UocC52YWx1ZSA8IC4wNSwxLDApKQ0KYGBgDQoNCmBgYHtyLCByZXN1bHRzPSdhc2lzJyxlY2hvPUZBTFNFfQ0Ka2FibGUoaGVhZChSZWdyZXNSZXN1bHQpLCAiaHRtbCIsIGJvb2t0YWJzID0gVCkgJT4lDQogIGthYmxlX3N0eWxpbmcoYm9vdHN0cmFwX29wdGlvbnMgPSAic3RyaXBlZCIsIGZ1bGxfd2lkdGggPSBGKQ0KYGBgDQoNCi0gTm93IHlvdSBjYW4gdGFrZSB0aGF0IG5ldyBkYXRhc2V0IGFuZCBzdW1tYXJpemUgaXQgKG1lYW4gc2xvcGUgZXN0aW1hdGVzIGp1c3QgZm9yIFRpbWUgYnkgc2lnbmlmaWNhbnQgdnMgbm9uc2lnbmlmaWNhbnQgcmVzdWx0cykgWypOb3RlOiBkcGx5cjo6ZmlsdGVyIGlzIHVzZWQgYmVjYXVzZSBpdHMgY29uZmxpY3RzIHdpdGggZnVuY3Rpb24gaW4gYW5vdGhlciBwYWNrYWdlLiBUaHVzIHRoaXMgZm9yY2VzIGZpbHRlciBmdW5jdGlvbiB0byBjb21lIGZyb20gdGhlIGRwbHlyIHBhY2thZ2UuKl0NCg0KYGBge3J9DQpSU3VtPC1SZWdyZXNSZXN1bHQgICU+JQ0KICBncm91cF9ieShWaXNpb24sU2lnKSAlPiUNCiAgZHBseXI6OmZpbHRlcih0ZXJtPT0iVGltZSIpICU+JQ0KICBzdW1tYXJpc2UoTj1sZW5ndGgoU2lnKSwNCiAgICAgICAgICAgIE1lYW5TbG9wZT1tZWFuKGVzdGltYXRlKSkNCmBgYA0KDQpgYGB7ciwgcmVzdWx0cz0nYXNpcycsZWNobz1GQUxTRX0NCmthYmxlKFJTdW0sICJodG1sIiwgYm9va3RhYnMgPSBUKSAlPiUNCiAga2FibGVfc3R5bGluZyhib290c3RyYXBfb3B0aW9ucyA9ICJzdHJpcGVkIiwgZnVsbF93aWR0aCA9IEYpDQpgYGANCg0KLSBOb3RlOiBZb3UgY2FuIGRvIHRoaXMgYWxsIGF0IG9uY2UgYmVjYXVzZSBpdHMgY29uZHVjdCBpbiB0aGUgb3JkZXIgeW91IGNhbGwgaXQNCg0KYGBge3J9DQpSZWdyZXNGaW5hbDwtVHJ1c3RMb25nLkZpbmFsICU+JQ0KICBncm91cF9ieShWaXNpb24sU3ViamVjdCkgJT4lDQogIGRvKHRpZHkobG0oVHJ1c3RGZWVsaW5nIH4gVGltZSwgZGF0YT0uKSkpICU+JQ0KICBtdXRhdGUoU2lnID0gaWZfZWxzZShwLnZhbHVlIDwgLjA1LDEsMCkpICU+JQ0KICBncm91cF9ieShWaXNpb24sU2lnKSAlPiUNCiAgZHBseXI6OmZpbHRlcih0ZXJtPT0iVGltZSIpICU+JQ0KICBzdW1tYXJpc2UoTj1sZW5ndGgoU2lnKSwNCiAgICAgICAgICAgIE1lYW5TbG9wZT1tZWFuKGVzdGltYXRlKSkNCmBgYA0KDQotIFlvdSBjYW4gcGlwZSBhbGwgdGhlc2UgcmVzdWx0cyBpbnRvIGEgZ2dwbG90DQoNCiMgZ2dwbG90DQpJbnNwaXJlZCBieSAiVGhlIEdyYW1tYXIgb2YgR3JhcGhpY3MiIExlbGFuZCBXaWxraW5zb24gMTk5OSANCg0KPiAiRGVzdGluZWQgdG8gYmVjb21lIGEgbGFuZG1hcmsgaW4gc3RhdGlzdGljYWwgZ3JhcGhpY3MsIHRoaXMgYm9vayBwcm92aWRlcyBhIGZvcm1hbCBkZXNjcmlwdGlvbiBvZiBncmFwaGljcywgcGFydGljdWxhcmx5IHN0YXRpYyBncmFwaGljcywgcGxheWluZyBtdWNoIHRoZSBzYW1lIHJvbGUgZm9yIGdyYXBoaWNzIGFzIHByb2JhYmlsaXR5IHRoZW9yeSBwbGF5ZWQgZm9yIHN0YXRpc3RpY3MuIkpvdXJuYWwgb2YgdGhlIEFtZXJpY2FuIFN0YXRpc3RpY2FsIEFzc29jaWF0aW9uIEZvcm1lciBWUCBhdCBTUFNTIEluYy4gRm91bmRlciBvZiBTWVNUQVQuIEFkanVuY3QgUHJvZmVzc29yIG9mIFN0YXRpc3RpY3MgYXQgTm9ydGh3ZXN0ZXJuIFVuaXZlcnNpdHkuIEhlIGlzIGFsc28gYWZmaWxpYXRlZCB3aXRoIHRoZSBDb21wdXRlciBTY2llbmNlIGRlcGFydG1lbnQgYXQgVGhlIFVuaXZlcnNpdHkgb2YgSWxsaW5vaXMgYXQgQ2hpY2Fnby4NCg0KLSBNYW51YWw6IGh0dHA6Ly9nZ3Bsb3QyLnRpZHl2ZXJzZS5vcmcvcmVmZXJlbmNlLw0KDQojIyBHcmFtbWFyIA0KLSBEYXRhICgqZGF0YSA9ICopOiAqKkRhdGEuRnJhbWUqKiB0byBiZSBtYXBwZWQgDQotIEFlc3RoZXRpYyBtYXBwaW5nICgqYWVzKCApKik6ICp4ID0gKiwgKnkgPSAqLCAqZ3JvdXAgPSogIGZyb20geW91ciBkYXRhLiBDYW4gYWxzbyBhZGQgYWRkaXRpb25hbCBtYXBwaW5nIGludG8gdGhlIGFlczoNCiAgICAtICgqY29sb3IgPSAqLCAqc2hhcGUgPSAqLCAqc2l6ZSA9ICosICpmaWxsID0gKiwgKmFscGhhID0qLCBldGMpOiBUaGVzZSBjYW4gYmUgZml4ZWQgdmFsdWVzIGFyZSB2YXJpYWJsZXMNCiAgICAgICAgLSBOb3RlOiBpZiBwbGFjZSB0aGVzZSBjYWxscyBpbnRvIGFlcyh4PUlWLCB5PURWLCBjb2xvcj1zdWJqZWN0SUQpLCB0aGVuIHRoZSBzdWJqZWN0IHdpbGwgYXBwZWFyIGluIHlvdXIgbGVnZW5kIHdpdGggdGhvc2UgY29sb3JzLiBZb3UgY2FuIG1vdmUgc29tZSB0aGVzZSBtYXBzIGludG8gZ2VvbSBjYWxsIHRvIGF2b2lkIHRoYXQNCi0gR2VvbWV0cmljIG9iamVjdCAoKmdlb21fKik6ICpiYXIqLCAqcG9pbnQqLCAqbGluZSosICpyaWJib25zKiwgc2hhcGVzIHlvdSB3YW50IHRvIGdyYXBoIGJhc2VkIG9uIHlvdXIgKmFzKiANCiAgICAtIFBvc2l0aW9uIGFkanVzdG1lbnRzICgqcG9zaXRpb24gPSAqKTogZ29lcyB3aXRoICpnZW9tXyogY2FsbCBzdWNoIGFzICpwb3NpdGlvbl9kb2RnZSogKGRvbid0IG92ZXJsYXApLCAqcG9zaXRpb25faWRlbnRpdHkqIChsZWF2ZSBhcyByZWFkIGluKSwgKnBvc2l0aW9uX2ppdHRlciogIChqaXR0ZXJzIGRhdGEgcG9pbnRzIGluIHNjYXR0ZXJwbG90KQ0KLSBTdGF0aXN0aWNhbCB0cmFuc2Zvcm1hdGlvbnMgKCpzdGF0c18qKTogT24gdGhlIGZseSB0cmFuc2Zvcm1zIChzdWNoIGFzIGF2ZXJhZ2luZyk6IGNhbiBiZSB1c2VkIGluc3RlYWQgb2YgZ2VvbXMgKCpOb3RlKjogSSBwcmVmZXIgdG8gY2FsY3VsYXRlIHN0YXRzIG91dHNpZGUgb2YgdGhlIHBsb3Qgd2hlbiBwb3NzaWJsZSBhcyBpdHMgZWFzaWVyIHRvIHNlZSB3aGF0IHlvdSBhcmUgZG9pbmcpDQotIENvb3JkaW5hdGUgc3lzdGVtICgqY29vcmRfKik6IGRvIHlvdSB3YW50IHRvIGFkZCAqY29vcmRfY2FydGVzaWFuKCkqLCAqY29vcmRfcG9sYXIoKSosIGV0Yw0KLSBTY2FsZXMgKCpzY2FsZV8qKSBvciBzaW1wbHkgKHhsaW0sIHlsaW0pLiAgT3ZlcnJpZGUgZGVmYXVsdHMgdG8gY29udHJvbCBtYW55IGFzcGVjdHMgb2YgdGhlIGdyYXBocw0KLSBGYWNldGluZyAoKmZhY2V0XyopOiB2aXN1YWwgc3Vic2V0czogdHdvIG9wdGlvbnMgKmdyaWQqIG9yICp3cmFwKg0KDQojIyMgTGF5ZXJpbmcNCi0gRmlyc3QsIHlvdSBwdXQgb24geW91ciBqYWNrZXQ7IHRoZW4geW91IHB1dCBvbiB5b3VyIHNob2VzLCBuZXh0IHVuZGVyd2VhciwgZmluYWxseSB5b3Ugc2hvd2VyLCByaWdodD8NCi0gZ2dwbG90IGhhcyB2ZXJ5IHNwZWNpZmljIG9yZGVyIHRoYXQgeW91IHNob3VsZCBnZW5lcmFsbHkgZm9sbG93LiANCiAgICAtIHdoYXQgaXMgbXkgZGF0YSwgd2hhdCBhcmUgbXkgbWFwcGluZ3MsIHdoYXQgYXJlIGdlb21zLCBob3cgc2hvdWxkIEkgcG9zaXRpb24gdGhlbSwgYW5kIGxhc3Qgd2hhdCBkbyBJIHdhbnQgdG8gZG8gd2l0aCB0aGUgbG9vayANCi0gVGhlIG9yZGVyIG9mIHdoaWNoIHlvdSBhZGQgY2FsbHMgaXMgdGhlIG9yZGVyIHRoZXkgYXBwZWFyLiBTbyBsYXRlciBjYWxscyB3aWxsIG92ZXJyaWRlIGVhcmxpZXIgY2FsbHMNCg0KIyMgV2Fsa3Rocm91Z2gNCg0KLSBTdGVwIDE6IGdncGxvdCBhbmQgYWVzDQpgYGB7cn0NCmxpYnJhcnkoZ2dwbG90MikNCkcxPC1nZ3Bsb3QoZGF0YSA9IFRydXN0TG9uZy5GaW5hbCwgYWVzKHggPSBUaW1lICwgeSA9IFRydXN0RmVlbGluZykpDQpHMQ0KYGBgDQoNCi0gU3RlcCAyOiBhZGQgZ2VvbQ0KLSBUaGlzIGNhbiBoZWxwIHlvdSBsb29rIHVwIGFsbCB0aGUgdHlwZXMNCg0KYGBge3IsIGV2YWw9RkFMU0V9DQpoZWxwLnNlYXJjaCgiZ2VvbV8iLCBwYWNrYWdlID0gImdncGxvdDIiKQ0KYGBgDQoNCi0gbGV0J3Mgc3RhcnQgd2l0aCBwb2ludHMNCg0KYGBge3J9DQpHMjwtRzErZ2VvbV9wb2ludCgpDQpHMg0KYGBgDQoNCi0gU3RlcCAzOiBCZXN0IGZpdCBsaW5lDQoNCmBgYHtyfQ0KRzM8LUcyK2dlb21fc21vb3RoKCkNCkczDQpgYGANCg0KLSBUaGlzIGxpbmUgaXMgbG9lc3MsIGxldCdzIG1ha2UgaXQgbG0gd2l0aCBzZWNvbmQgb3JkZXIgcG9seW5vbWlhbDogJHkgPSB4K3heMiQsIHdoaWNoIHdlIGNhbiB3cml0ZSBhdCBgcG9seShYLDIpYFtvcnRob2dvbmFsIHBvd2VyIHBvbHlub21pYWxzXS4gWypOb3RlOiB5b3Ugd2lsbCBub3QgYWx3YXlzIG5lZWQgdG8gY2FsbCBzdGF0czo6cG9seSwgbm9ybWFsbHkgeW91IGNhbiB3cml0ZSBqdXN0IHBvbHksIGJ1dCBpdCBpcyBjb25mbGljdGluZyB3aXRoIGFub3RoZXIgZnVuY3Rpb24qXQ0KDQpgYGB7ciwgZXZhbD1GQUxTRX0NCj9nZW9tX3Ntb290aA0KYGBgDQoNCmBgYHtyIHBvbHkxfQ0KRzNhPC1HMitnZW9tX3Ntb290aChtZXRob2Q9J2xtJywgZm9ybXVsYSA9IHkgfiBzdGF0czo6cG9seSh4LDIpKQ0KRzNhDQpgYGANCg0KLSBTdGVwIDQ6IGZhY2V0IGdyaWQgYnkgVmlzaW9uIGNvbmRpdGlvbg0KDQpgYGB7cn0NCkc0YTwtRzNhK2ZhY2V0X2dyaWQoflZpc2lvbikNCkc0YQ0KYGBgDQoNCg0KLSBTdGVwIDQ6IEZhbmN5IHRoZSBwbG90IHVwDQoNCi0gQWRkIGEgdGhlbWUNCmBgYHtyfQ0KRzRiPC1HNGErdGhlbWVfYncoKQ0KRzRiDQpgYGANCg0KLSBNb2RpZnkgdGhlIHRoZW1lIHRvIGNoYW5nZSB0aGUgbWlub3IgZ3JpZCBsaW5lcw0KDQpgYGB7cn0NCkc0YzwtRzRiK3RoZW1lKHBhbmVsLmdyaWQubWlub3IgPSBlbGVtZW50X2JsYW5rKCkpDQpHNGMNCmBgYA0KDQotIENoYW5nZSB0aGUgeCBhbmQgeSBsYWJlbHMNCmBgYHtyfQ0KRzRkPC1HNGMreGxhYigiVGltZSBTdGVwIikreWxhYigiVHJ1c3QgU2NvcmUiKQ0KRzRkDQpgYGANCg0KDQotIE92ZXJyaWRlIHRoZSBnZW9tX3Ntb290aCB3aXRoIGEgZGlmZmVyZW50IGNvbG9yIGxpbmUgYW5kIHNldCBTRSB0byBmYWxzZQ0KDQpgYGB7ciBwb2x5Mn0NCkc0ZTwtRzRkK2dlb21fc21vb3RoKG1ldGhvZD0nbG0nLCBzZT1GQUxTRSwgZm9ybXVsYSA9IHkgfiBzdGF0czo6cG9seSh4LDIpLCBjb2xvcj0ncmVkJykNCkc0ZQ0KYGBgDQoNCi0gV2FpdCB3aHkgZG8gSSBzdGlsbCBzZWUgU0UgcmliYm9ucz8gQmVjYXVzZSB0aGF0IGxheWVyIGlzIHN0aWxsIHRoZXJlIQ0KDQotIExldCdzIG1ha2UgdGhlIGdyYXBoIGFsbCBhdCBvbmNlDQoNCmBgYHtyfQ0KUG9seS5QbG90PC1nZ3Bsb3QoZGF0YSA9IFRydXN0TG9uZy5GaW5hbCwgYWVzKHggPSBUaW1lICwgeSA9IFRydXN0RmVlbGluZykpKw0KICBmYWNldF9ncmlkKH5WaXNpb24pKw0KICBnZW9tX3BvaW50KCkrDQogIGdlb21fc21vb3RoKG1ldGhvZD0nbG0nLCBzZT1GQUxTRSwgZm9ybXVsYSA9IHkgfiBzdGF0czo6cG9seSh4LDIpLCBjb2xvcj0ncmVkJykrDQogIHhsYWIoIlRpbWUgU3RlcCIpK3lsYWIoIlRydXN0IFNjb3JlIikrDQogIHRoZW1lX2J3KCkrDQogIHRoZW1lKHBhbmVsLmdyaWQubWlub3IgPSBlbGVtZW50X2JsYW5rKCkpDQpQb2x5LlBsb3QNCmBgYA0KDQojIyMgU3BlZ2dldHRpIFBsb3QNCi0gV2UgY2FuIGFkZCBiZXN0IGZpdCBsaW5lIHBlciBzdWJqZWN0LCB3ZSBqdXN0IG5lZWQgdG8gbWFrZSB0aGUgc3ViamVjdCBhIGdyb3VwaW5nIHZhcmlhYmxlDQogICAgLSBhZXMoZ3JvdXA9U3ViamVjdCkNCiAgICAgICAgLSBUaGlzIGNhbiBiZSBhZGRlZCBlaXRoZXIgYXQgZ2VvbV9zbW9vdGggaWYgeW91IGp1c3Qgd2FudCBpdCB0byBhcHBseSB0byB0aGF0IGdlb20gc3VjaCBhcyBiZWxvdyANCg0KYGBge3Igc3BlZzF9DQpTcGVnLlBsb3Q8LWdncGxvdChkYXRhID0gVHJ1c3RMb25nLkZpbmFsLCBhZXMoeCA9IFRpbWUgLCB5ID0gVHJ1c3RGZWVsaW5nKSkrDQogIGZhY2V0X2dyaWQoflZpc2lvbikrDQogIGdlb21fcG9pbnQoKSsNCiAgZ2VvbV9zbW9vdGgoYWVzKGdyb3VwPVN1YmplY3QpLG1ldGhvZD0nbG0nLCBzZT1GQUxTRSwgZm9ybXVsYSA9IHkgfiBzdGF0czo6cG9seSh4LDIpLCBjb2xvcj0ncmVkJykrDQogIHhsYWIoIlRpbWUgU3RlcCIpK3lsYWIoIlRydXN0IFNjb3JlIikrDQogIHRoZW1lX2J3KCkrDQogIHRoZW1lKHBhbmVsLmdyaWQubWlub3IgPSBlbGVtZW50X2JsYW5rKCkpKw0KICBnZ3RpdGxlKCJUcnVzdCBTdHVkeSIpDQpTcGVnLlBsb3QNCmBgYA0KDQotIG9yIHlvdSBjYW4gYXBwbHkgaXQgdGhlIG92ZXItYXJjaGluZyBhZXMgDQpgYGB7cn0NClNwZWcuUGxvdC4yPC1nZ3Bsb3QoZGF0YSA9IFRydXN0TG9uZy5GaW5hbCwgDQogICAgICAgICAgICAgICAgICAgIGFlcyh4ID0gVGltZSAsIHkgPSBUcnVzdEZlZWxpbmcsZ3JvdXA9U3ViamVjdCkpKw0KICBmYWNldF9ncmlkKH5WaXNpb24pKw0KICBnZW9tX3BvaW50KCkrDQogIGdlb21fc21vb3RoKG1ldGhvZD0nbG0nLCBzZT1GQUxTRSwgZm9ybXVsYSA9IHkgfiBzdGF0czo6cG9seSh4LDIpLCBjb2xvcj0ncmVkJykrDQogIHhsYWIoIlRpbWUgU3RlcCIpK3lsYWIoIlRydXN0IFNjb3JlIikrDQogIHRoZW1lX2J3KCkrDQogIHRoZW1lKHBhbmVsLmdyaWQubWlub3IgPSBlbGVtZW50X2JsYW5rKCkpKw0KICBnZ3RpdGxlKCJUcnVzdCBTdHVkeSIpDQpTcGVnLlBsb3QuMg0KYGBgDQoNCi0gbm93IHlvdSBhcHBsaWVkIHRvIHRoZSBvdmVyLWFyY2hpbmcgYWVzIHlvdSBjYW4gZG8gb3RoZXIgZnVuIHN0dWZmLCBsaWtlIGNvbG9yIGVhY2ggZGF0YSBwb2ludCBhbmQgbGluZSByZWxhdGl2ZSB0aGUgc3ViamVjdA0KICAgIC0gTm90aWNlIGJlbG93IHdoZXJlIEkgYWRkZWQ6IGFlcyhjb2xvcj1TdWJqZWN0KSANCiAgICANCmBgYHtyIHNwZWdjb2xvcn0NClNwZWcuUGxvdC4zPC1nZ3Bsb3QoZGF0YSA9IFRydXN0TG9uZy5GaW5hbCwgDQogICAgICAgICAgICAgICAgICAgIGFlcyh4ID0gVGltZSwgeSA9IFRydXN0RmVlbGluZywgZ3JvdXA9U3ViamVjdCwgY29sb3I9U3ViamVjdCkpKw0KICBmYWNldF9ncmlkKH5WaXNpb24pKw0KICBnZW9tX3BvaW50KCkrDQogIGdlb21fc21vb3RoKG1ldGhvZD0nbG0nLCBzZT1GQUxTRSwgZm9ybXVsYSA9IHkgfiBzdGF0czo6cG9seSh4LDIpKSsNCiAgeGxhYigiVGltZSBTdGVwIikreWxhYigiVHJ1c3QgU2NvcmUiKSsNCiAgdGhlbWVfYncoKSsNCiAgdGhlbWUocGFuZWwuZ3JpZC5taW5vciA9IGVsZW1lbnRfYmxhbmsoKSkNClNwZWcuUGxvdC4zDQpgYGANCg0KDQotIFdoYXQgd2h5IGlzIHRoZSBjb2xvciBjb250aW51b3VzPyANCg0KYGBge3J9DQpzdHIoVHJ1c3RMb25nLkZpbmFsKQ0KYGBgDQoNCi0gQmVjYXVzZSBTdWJqZWN0IGlzIGludGVyZ2VyDQoNCmBgYHtyfQ0KVHJ1c3RMb25nLkZpbmFsJFN1YmplY3QuRjwtYXMuZmFjdG9yKFRydXN0TG9uZy5GaW5hbCRTdWJqZWN0KQ0KYGBgDQoNCi0gUmVtYWtlIHRoZSBwbG90IGFuZCByZXBsYWNlIFN1YmplY3Qgd2l0aCBTdWJqZWN0LkYNCi0gYWxzbyB3ZSBjYW4gYWRkIFNoYXBlIGFuZCBsaW5ldHlwZSBieSBTdWJqZWN0IChpbiBjYXNlIHdlIGhhdmUgdG8gcHJpbnQgaW4gYmxhY2sgYW5kIHdoaXRlKQ0KDQpgYGB7ciBzZWc0fQ0KU3BlZy5QbG90LjQ8LWdncGxvdChkYXRhID0gVHJ1c3RMb25nLkZpbmFsLCANCiAgICAgICAgICAgICAgICAgICAgYWVzKHggPSBUaW1lLCB5ID0gVHJ1c3RGZWVsaW5nLCBncm91cD1TdWJqZWN0LkYsIGNvbG9yPVN1YmplY3QuRixzaGFwZT1TdWJqZWN0LkYsbGluZXR5cGU9U3ViamVjdC5GKSkrDQogIGZhY2V0X2dyaWQoflZpc2lvbikrDQogIGdlb21fcG9pbnQoKSsNCiAgZ2VvbV9zbW9vdGgobWV0aG9kPSdsbScsIHNlPUZBTFNFLCBmb3JtdWxhID0geSB+IHN0YXRzOjpwb2x5KHgsMikpKw0KICB4bGFiKCJUaW1lIFN0ZXAiKSt5bGFiKCJUcnVzdCBTY29yZSIpKw0KICB0aGVtZV9idygpKw0KICB0aGVtZShwYW5lbC5ncmlkLm1pbm9yID0gZWxlbWVudF9ibGFuaygpKQ0KU3BlZy5QbG90LjQNCmBgYA0KDQotIFJlbW92ZSB0aGUgbGVnZW5kDQoNCmBgYHtyLCBldmFsPUZBTFNFfQ0KU3BlZy5QbG90LjRhPC1TcGVnLlBsb3QuNCt0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpDQpTcGVnLlBsb3QuNGENCmBgYA0KDQpgYGB7ciwgZWNobz1GQUxTRX0NClNwZWcuUGxvdC40YTwtZ2dwbG90KGRhdGEgPSBUcnVzdExvbmcuRmluYWwsIA0KICAgICAgICAgICAgICAgICAgICBhZXMoeCA9IFRpbWUsIHkgPSBUcnVzdEZlZWxpbmcsIGdyb3VwPVN1YmplY3QuRiwgY29sb3I9U3ViamVjdC5GLHNoYXBlPVN1YmplY3QuRixsaW5ldHlwZT1TdWJqZWN0LkYpKSsNCiAgZmFjZXRfZ3JpZCh+VmlzaW9uKSsNCiAgZ2VvbV9wb2ludCgpKw0KICBnZW9tX3Ntb290aChtZXRob2Q9J2xtJywgc2U9RkFMU0UsIGZvcm11bGEgPSB5IH4gc3RhdHM6OnBvbHkoeCwyKSkrDQogIHhsYWIoIlRpbWUgU3RlcCIpK3lsYWIoIlRydXN0IFNjb3JlIikrDQogIHRoZW1lX2J3KCkrDQogIHRoZW1lKHBhbmVsLmdyaWQubWlub3IgPSBlbGVtZW50X2JsYW5rKCkpK3RoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikNClNwZWcuUGxvdC40YQ0KYGBgDQoNCg0KLSBBbmQgeW91IGNhbiBtYWtlIGl0IHRvdGFsbHkgImZhciBvdXQiDQogICAgLSBZb3UgY2FuIHVzZSBIVE1MIG9yIGNvbG9yIGxhYmVscyANCg0KYGBge3J9DQpTcGVnLlBsb3QuNGEgKyB0aGVtZShwbG90LmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3Qoc2l6ZSA9IDEsIGNvbG9yID0gImJsdWUiLCBmaWxsID0gInB1cnBsZSIpLA0KICAgICAgICB0ZXh0PWVsZW1lbnRfdGV4dChzaXplID0gMTIsIGZhbWlseSA9ICJTZXJpZiIsIGNvbG9yID0gIml2b3J5IiksDQogICAgICAgIGF4aXMudGV4dC55ID0gZWxlbWVudF90ZXh0KGNvbG91ciA9ICJtYWdlbnRhIiksDQogICAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGNvbG91ciA9ICJncmVlbiIpLA0KICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9yZWN0KGZpbGwgPSAicGluayIpLA0KICAgICAgICBzdHJpcC5iYWNrZ3JvdW5kID0gZWxlbWVudF9yZWN0KGZpbGwgPSAiI2NjZmY2NiIpKQ0KYGBgDQoNCiMgVGlkeXIgJiBnZ3Bsb3QgZnVuIHRpbWUNCi0gSnVzdCBmb3IgZnVuIGxldHMgcnVuIHRoZSBwb2x5bm9taWFsIHJlZ3Jlc3Npb25zIGFuZCBleHRyYWN0IHRoZSAkUl4yJCBwZXIgc3ViamVjdCBmb3IgVmlzaW9uDQoNCmBgYHtyIGRvbG19DQpUcnVzdExvbmcuRmluYWwkU3ViamVjdC5GPC1hcy5mYWN0b3IoVHJ1c3RMb25nLkZpbmFsJFN1YmplY3QpDQpQb2x5UmVncmVzczwtVHJ1c3RMb25nLkZpbmFsICU+JQ0KICBncm91cF9ieShWaXNpb24sIFN1YmplY3QuRikgJT4lDQogIGRvKGdsYW5jZShsbShUcnVzdEZlZWxpbmcgfiBzdGF0czo6cG9seShUaW1lLDIpLCBkYXRhPS4pKSkgICU+JQ0KICBzZWxlY3QoVmlzaW9uLHIuc3F1YXJlZCkgDQpgYGANCg0KDQpgYGB7ciwgcmVzdWx0cz0nYXNpcycsZWNobz1GQUxTRX0NCmthYmxlKGhlYWQoUG9seVJlZ3Jlc3MpLCAiaHRtbCIsIGJvb2t0YWJzID0gVCkgJT4lDQogIGthYmxlX3N0eWxpbmcoYm9vdHN0cmFwX29wdGlvbnMgPSAic3RyaXBlZCIsIGZ1bGxfd2lkdGggPSBGKQ0KYGBgDQoNCi0gTGV0J3MgZXh0cmFjdCB0aGUgUGVyc29uYWxpdHkgc2NvcmUgYW5kIGpvaW4gaXQgdG8gdGhhdCBuZXcgZGF0YXNldCAgDQoNCmBgYHtyfQ0KUGVyc29uYWxpdHlTY29yZTwtVHJ1c3RMb25nLkZpbmFsICU+JSANCiAgZHBseXI6OmZpbHRlcihUaW1lPT0wKSAlPiUgDQogIHNlbGVjdChTdWJqZWN0LkYsUGVyc29uYWxpdHksIFZpc2lvbikNCg0KTWVyZ2VkRGF0YTwtbGVmdF9qb2luKFBvbHlSZWdyZXNzLFBlcnNvbmFsaXR5U2NvcmUpDQpgYGANCg0KYGBge3IsIHJlc3VsdHM9J2FzaXMnLGVjaG89RkFMU0V9DQprYWJsZShoZWFkKE1lcmdlZERhdGEpLCAiaHRtbCIsIGJvb2t0YWJzID0gVCkgJT4lDQogIGthYmxlX3N0eWxpbmcoYm9vdHN0cmFwX29wdGlvbnMgPSAic3RyaXBlZCIsIGZ1bGxfd2lkdGggPSBGKQ0KYGBgDQoNCi0gU2NhdHRlciBwbG90IGJ5IFZpc2lvbiB3aXRoIGZhbmN5IGxhYmVscw0KDQpgYGB7cn0NClNjYXR0ZXIucGxvdDwtZ2dwbG90KGRhdGEgPSBNZXJnZWREYXRhLCBhZXMoUGVyc29uYWxpdHksci5zcXVhcmVkKSkrDQogIGdlb21fcG9pbnQoYWVzKHNoYXBlPVZpc2lvbikpKw0KICB4bGFiKCJQZXJzb25haWxpdHkgU2NvcmUiKSsNCiAgeWxhYihleHByZXNzaW9uKHBhc3RlKCJQb2x5bm9taWFsIFJlZ3Jlc3Npb24sICIsIFJeezJ9LCcgUmVzdWx0IFBlciBTdWJqZWN0JykpKSsNCiAgdGhlbWVfbWluaW1hbCgpDQpTY2F0dGVyLnBsb3QNCmBgYA0KDQotIFJlb3JkZXIgYW5kIHJlbmFtZSBsYWJlbHMNCmBgYHtyfQ0KTWVyZ2VkRGF0YSRWaXNpb24uTyA8LSBmYWN0b3IoTWVyZ2VkRGF0YSRWaXNpb24sDQogICAgICAgICAgICAgICAgICAgICBsZXZlbHMgPSBjKCJTZWUiLCJOb1NlZSIpLA0KICAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gYygiUGFydG5lciBpcyBWaXNhYmxlIiwiUGFydG5lciBpcyBvYnNjdXJlZCIpKQ0KYGBgDQoNCi0gQWRkIG5ldyBsYWJlbHMgYW5kIGZpeCB1cCBsZWdlbmQNCg0KYGBge3J9DQpTY2F0dGVyLnBsb3QyPC1nZ3Bsb3QoZGF0YSA9IE1lcmdlZERhdGEsIGFlcyhQZXJzb25hbGl0eSxyLnNxdWFyZWQpKSsNCiAgZ2VvbV9wb2ludChhZXMoc2hhcGU9VmlzaW9uLk8pLHNpemUgPSAyLjUsIHN0cm9rZSA9IDEuMjUpKw0KICB4bGFiKCJQZXJzb25haWxpdHkgU2NvcmUiKSsNCiAgeWxhYihleHByZXNzaW9uKHBhc3RlKCJQb2x5bm9taWFsIFJlZ3Jlc3Npb24sICIsIFJeezJ9LCcgUmVzdWx0IFBlciBTdWJqZWN0JykpKSsNCiAgdGhlbWVfbWluaW1hbCgpK3RoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJ0b3AiLA0KICAgICAgICAgICAgICAgICAgIGxlZ2VuZC50ZXh0ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMSwgY29sb3IgPSAiZ3JheTUwIiksDQogICAgICAgICAgICAgICAgICAgbGVnZW5kLnRpdGxlPWVsZW1lbnRfYmxhbmsoKSkNClNjYXR0ZXIucGxvdDINCmBgYA0KDQotIExldCdzIGNoYW5nZSB0aGUgc2hhcGUNCiAgICAtIGh0dHA6Ly9zYXBlLmluZi51c2kuY2gvcXVpY2stcmVmZXJlbmNlL2dncGxvdDIvc2hhcGUNCiAgICANCmBgYHtyfQ0KU2NhdHRlci5wbG90MzwtU2NhdHRlci5wbG90MitzY2FsZV9zaGFwZV9tYW51YWwodmFsdWVzPWMoMjEsMjQpKQ0KU2NhdHRlci5wbG90Mw0KYGBgDQoNCi0gTGV0J3MgYWRkIGxhYmVscyBzbyB3ZSBrbm93IHdoaWNoIGRvdHMgYXJlIHdoaWNoIHN1YmplY3RzDQoNCmBgYHtyfQ0KbGlicmFyeSgiZ2dyZXBlbCIpDQpTY2F0dGVyLnBsb3QzICsgc2NhbGVfc2hhcGVfbWFudWFsKHZhbHVlcz1jKDIxLDI0KSkrDQogIGdlb21fdGV4dF9yZXBlbChhZXMobGFiZWw9U3ViamVjdC5GKSwgc2l6ZSA9IDMpDQpgYGANCg0KLSBMYWJlbCBvbmx5IGEgaGFuZCBmdWxsIG9mIHN1YmplY3RzDQoNCmBgYHtyfQ0KcG9pbnRzVG9MYWJlbCA8LSBjKCIxIiwiNiIsIjEyIikNClNjYXR0ZXIucGxvdDQ8LVNjYXR0ZXIucGxvdDMgKyBzY2FsZV9zaGFwZV9tYW51YWwodmFsdWVzPWMoMjEsMjQpKSsNCiAgICBnZW9tX3RleHRfcmVwZWwoYWVzKGxhYmVsID0gU3ViamVjdC5GKSwNCiAgICAgICAgICAgICAgICAgICAgY29sb3IgPSAiZ3JheTIwIiwNCiAgICAgICAgICAgICAgICAgICAgZGF0YSA9IHN1YnNldChNZXJnZWREYXRhLCBTdWJqZWN0LkYgJWluJSBwb2ludHNUb0xhYmVsKSwNCiAgICAgICAgICAgICAgICAgICAgZm9yY2UgPSAxMCkNClNjYXR0ZXIucGxvdDQNCmBgYA0KDQotIEZpeCB1cCBYIGF4aXMNCg0KYGBge3J9DQpTY2F0dGVyLnBsb3Q1PC0gU2NhdHRlci5wbG90NCtzY2FsZV94X2NvbnRpbnVvdXMobmFtZSA9ICJQZXJzb25hbGl0eSBTY29yZSwgSnVkZ2luZyAoMT1Nb3N0KSIsDQogICAgICAgICAgICAgICAgICAgICAgIGxpbWl0cyA9IGMoMC4wLCAyLjUpLA0KICAgICAgICAgICAgICAgICAgICAgICBicmVha3MgPSBzZXEoMC4wLCAyLjUsIGJ5ID0gMC4yNSkpIA0KU2NhdHRlci5wbG90NQ0KYGBgDQoNCi0gQWRkIGNvbXBsZXggcmVncmVzc2lvbiB0byB0aGUgcGxvdCBwbG90DQoNCmBgYHtyfQ0KU2NhdHRlci5wbG90NiA8LVNjYXR0ZXIucGxvdDUgKyAgDQogIGdlb21fc21vb3RoKGFlcyhsaW5ldHlwZSA9IFZpc2lvbi5PLCBncm91cD1WaXNpb24uTyksDQogICAgICAgICAgICAgIG1ldGhvZCA9ICJsbSIsDQogICAgICAgICAgICAgIGZvcm11bGEgPSB5IH4gbG9nKHgpLCBzZSA9IEZBTFNFLA0KICAgICAgICAgICAgICBjb2xvciA9ICJncmVlbiIpDQpTY2F0dGVyLnBsb3Q2DQpgYGANCg0KPHNjcmlwdD4NCiAgKGZ1bmN0aW9uKGkscyxvLGcscixhLG0pe2lbJ0dvb2dsZUFuYWx5dGljc09iamVjdCddPXI7aVtyXT1pW3JdfHxmdW5jdGlvbigpew0KICAoaVtyXS5xPWlbcl0ucXx8W10pLnB1c2goYXJndW1lbnRzKX0saVtyXS5sPTEqbmV3IERhdGUoKTthPXMuY3JlYXRlRWxlbWVudChvKSwNCiAgbT1zLmdldEVsZW1lbnRzQnlUYWdOYW1lKG8pWzBdO2EuYXN5bmM9MTthLnNyYz1nO20ucGFyZW50Tm9kZS5pbnNlcnRCZWZvcmUoYSxtKQ0KICB9KSh3aW5kb3csZG9jdW1lbnQsJ3NjcmlwdCcsJ2h0dHBzOi8vd3d3Lmdvb2dsZS1hbmFseXRpY3MuY29tL2FuYWx5dGljcy5qcycsJ2dhJyk7DQoNCiAgZ2EoJ2NyZWF0ZScsICdVQS05MDQxNTE2MC0xJywgJ2F1dG8nKTsNCiAgZ2EoJ3NlbmQnLCAncGFnZXZpZXcnKTsNCg0KPC9zY3JpcHQ+