Tidyverse

  • Family of packages developed to streamline graphing, data manipulation, data wrangling, and programming
  • Main packages of interest for us today: ggplot2, dplyr, tidyr (these have evolved to the point that they have replaced packages we converted in the r class: reshape2 and plyr)
  • These packages can work on a modern type of data frame called a tibble
  • ggplot2 is for graphing
  • dplyr is for data manipulation (think excel pivot tables on steroids)
  • tidyr is for data wrangling (long to wide format and back again)
  • These packages can use piping (in dplyr which calls purrr) which makes it easier to string together commands

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

  • Step 1: ggplot and aes
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")
  • let’s start with points
G2<-G1+geom_point()
G2

  • Step 3: Best fit line
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

  • Step 4: Fancy the plot up

  • Add a theme

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

  • Wait why do I still see SE ribbons? Because that layer is still there!

  • Let’s make the graph all at once

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

  • Remove the legend
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

  • Fix up X axis
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+