Introduction

A central goal of vision science is to understand how signals from photoreceptors are transformed into sight and the limitations each stage of processing imposes on perception. Photoreceptors provide an organism with real-time information about the environment. However, the signals conveyed by individual neurons are noisy and ambiguous. One strategy for reducing uncertainty is to pool signals across multiple detectors. Under low light conditions, for example, the visual system combines signals from many hundreds of rod and cone photoreceptors in order to boost sensitivity . One drawback of signal pooling is a loss in spatial resolution: both acuity and contrast sensitivity are reduced under low-light levels . We studied the influence of spatial pooling on the color appearance of cone-targeted spots.

Results

Load data and import libraries.

First load in data and set some parameters that will be used during plotting later.

# setwd("~/R/Schmidt-Boehm-Tuten-Roorda_2019")
library(tidyverse)
library(ggthemes)
library(gridExtra)
library(corrplot)
library(car)
# set the colors that will be used in plots below.
red <- "#e84040"
green <-"#40c631"
blue <- "#4042e8"
yellow <- "#e8df40"
gray <- "#595959"
# make sure a figures directory exists
dir.create('figures', showWarnings=FALSE)
trial_data <- read_csv('filtered_data.csv')
head(trial_data)

Example session

In a previous experiment, the one and two cone conditions were equated for detectability. We subsequently collected appearance measurements. All other stimulus conditions were identical to the detection task. Three cones were selected for study in each session (Fig~A). On each trial either a single cone or a pair was targeted. After each flash, the subject judged the color of the spot using a hue and saturation scaling paradigm . Each cone and pair was tested twelve times (72 trials per session). A total of 198 pairs were tested across three subjects. Hue and saturation scaling data were transformed into a color opponent representation. For each trial, the degree of perceived greenness versus redness and yellowness versus blueness was computed from percentage ratings as follows: \(gr = (green\% - red\%) / 100\%\) and \(yb = (yellow\% - blue\%) / 100\%\). In this representation, saturation is expressed as the distance from the origin (in city block metric). A pure white response falls at the origin and a maximally saturated report falls along the outer diamond.

The results of one session are plotted in Fig~B. In this example, Cone 1 was an M-cone and had a bias towards green. Cone 2 was an L-cone and elicited predominantly white reports. Cone 3, also an L-cone, was rated reddish-yellow (orange) with medium saturation. The motivation for this study was to understand the algorithm the visual system uses when combining information across cone pairs. In the example, when Cone 1 was targeted together with either Cone 2 or Cone 3, the average report was desaturated. In comparison, when Cone 2 and 3 were targeted they elicited a medium saturated orange report. Below, we analyze the results from all sessions and subjects.

example <- trial_data %>%
  filter(., subject == '20076R', session== 4) %>%
  group_by(., masterID1, masterID2, isPair) %>% 
  summarise(., yb_mean=signif(mean(yb), 3), 
            yb_sem=signif(sd(yb) / sqrt(length(yb)), 3), 
            gr_mean=signif(mean(gr), 3), 
            gr_sem=signif(sd(gr) / sqrt(length(gr)), 3), N=length(gr),
            typeIDstr=typeIDstr[1])
boundary <- data.frame(x=c(-1, 0, 1, 0, -1), y=c(0, 1, 0, -1, 0))
(examplePlot <- ggplot(data=example, 
                       aes(x=yb_mean, 
                           y=gr_mean,
                           color=interaction(typeIDstr, isPair),
                           shape=isPair)) +
    geom_point(size=3) + 
    geom_errorbar(aes(x=yb_mean, ymin=gr_mean - gr_sem, ymax=gr_mean + gr_sem)) +
    geom_errorbarh(aes(y=gr_mean, xmin=yb_mean - yb_sem, xmax=yb_mean + yb_sem, height=0.01)) + 
    coord_equal(xlim=c(-1, 1), ylim=c(-1, 1)) + 
    xlab('(yellow - blue) / total') + 
    ylab('(green - red) / total') + 
    scale_color_manual(
      values = c(red, green, red, yellow), 
      name="spectral type") +
    geom_text(label=c('1', '1+2', '1+3', '2', '2+3', '3'),
              nudge_x=-0.15) +
    geom_path(data = boundary, aes(x=x, y=y), colour='gray', linetype=2, inherit.aes = F) +
    theme_bw(base_size = 15) + 
    theme(legend.position="none")
  )

ggsave("figures/example_session.pdf")

Variability in sensations from cones with the same sensitivity

The motivating question behind these experiments was how does the visual system combine inputs from cones when making color appearance judgments? To begin to answer this question, we grouped each trial based on which cone or pair was probed. The results are reported in a table below. In both subjects with classified cones, individual and pairs of M-cones produced gr means that were greater than zero, while L-cone conditions led to negative values. This observation is consistent with a predictive relationship between cone type and color report, as found previously (Sabesan et al. 2006; Schmidt et al. 2018).

Each point in these plots represents the mean response measured from a single cone or a pair. This plot illustrates the variability in responses across cones/pairs and between subjects. There are a few features to note. Firstly, within a single subject, there was considerable variability between cones and pairs with the same spectral sensitivity. Similar variability in sensations from single cones has been reported previously (Hofer et al. 2005; Sabesan et al. 2016; Schmidt et al. 2018). This is the first report of variability in sensations elicited from pairs of cones. Secondly, there were individual differences in color responses: S20075 used blue more frequently than the two other subjects and S10001 did not report yellow on any trials. However, the general patterns are similar. Most of the variance was found along the green-red dimension; there were few points that fell in the blueish-red or greenish-yellow quadrants. In the two subjects with classified mosaics, we additionally found L-cone targeted trials tended produce a reddish bias, while M-cones were biased towards green. These patterns were similar to previous reports from single-cone (Sabesan et al. 2016; Schmidt et al. 2018) and large-field studies (DeValois et al. 1997).

To better appreciate the influence of cone type and number of cones targeted on color reports, data was pooled across subjects and grouped according to the type of cone or pair probed. The mean and standard error for each group is shown in the right most plot below. When an individual or pair of M-cones was targeted the average \(gr\) response was greater than zero indicating a bias towards green. In comparison, the average L-cone(s) elicited biases towards red and yellow. Together these cone type specific differences in color reports were consistent with a predictive relationship between cone type and color report, as previously reported (Sabesan et al. 2016; Schmidt et al. 2018). Two cones with the same photopigment tended to elicit slightly more saturated reports than single cone trials. On the other hand, one L- and one M-cone targeted together tended to produce desaturated reports.

(p <- grid.arrange(ind, avg, widths=2.5:1))
TableGrob (1 x 2) "arrange": 2 grobs
  z     cells    name           grob
1 1 (1-1,1-1) arrange gtable[layout]
2 2 (1-1,2-2) arrange gtable[layout]

ggsave("figures/UAD_combined.pdf", p)

Mosaic parameters do not predict responses

The plot above demonstrates that color reports varied even between cones with the same photopigment (Schmidt et al. 2018). Some L-cones, for instance, elicited highly saturated red sensations, while a majority produce white or desaturated pink reports. We next asked whether the variability in sensations between cones with the same sensitivity could be explained by low level features of the mosaic. Specifically, can we predict whether an L-cone will produce a saturated red or a desaturated pink based on the surrounding cone types or the delivery error? And in the case of cone pairs, did the distance between the two cones influence color appearance? To answer these question, we grouped responses according to the number of cones targeted (one or two), the session in which trial occurred and the specific cone(s) that was targeted. The mean and count was then computed for each cone(s). Only those cones pairs which had at least four good trials were analyzed.

minTrials <- 4
singleConesSummary <- trial_data %>% 
  select(., -typeIDstr) %>%
  filter(., isPair==FALSE) %>% 
  group_by(., subject, session, masterID1) %>%   
  summarise_all(., c("mean", "length")) %>%
  filter(., yb_length >= minTrials) %>%
  select(., -contains("_length")) %>%
  rename_(.dots=setNames(names(.), gsub("_mean", "", names(.))))
twoConesSummary <- trial_data %>%   
  select(., -typeIDstr) %>%
  filter(., isPair==TRUE) %>% 
  group_by(., subject, session, masterID1, masterID2) %>%
  summarise_all(., c("mean", "length")) %>%
  filter(., yb_length >= minTrials) %>%
  select(., -contains("_length")) %>%
  rename_(.dots=setNames(names(.), gsub("_mean", "", names(.))))

Below is a matrix of correlation plots for the single cone condition.

ptest <- function(mat, ...) {
  # stolen from: http://www.sthda.com/english/wiki/visualize-correlation-matrix-using-correlogram
    mat <- as.matrix(mat)
    n <- ncol(mat)
    p.mat<- matrix(NA, n, n)
    diag(p.mat) <- 0
    for (i in 1:(n - 1)) {
        for (j in (i + 1):n) {
            tmp <- cor.test(mat[, i], mat[, j], ...)
            p.mat[i, j] <- p.mat[j, i] <- tmp$p.value
        }
    }
  colnames(p.mat) <- rownames(p.mat) <- colnames(mat)
  p.mat
}
corr_analysis <- function (data, predictors) {
  # scatterplotMatrix with car package
  scatterplotMatrix(data[ , predictors], 
                    diagonal=list(method ="histogram"),
                    ellipse=FALSE)
  
  M<-cor(data[ , predictors], use="complete.obs")
  head(round(M,2))
  cor.mtest <- ptest
  # matrix of the p-value of the correlation
  p.mat <- cor.mtest(data[ , predictors])
  print(format(p.mat, digits=4))
col <- colorRampPalette(c("#BB4444", "#EE9988", "#FFFFFF", "#77AADD", "#4477AA"))
corrplot(M, method="color", col=col(200),  
         type="upper", 
         #order="hclust", 
         addCoef.col = "black", # Add coefficient of correlation
         tl.col="black", tl.srt=45, #Text label color and rotation
         # Combine with significance
         p.mat = p.mat, sig.level = 0.01, insig = "blank", 
         # hide correlation coefficient on the principal diagonal
         diag=FALSE 
         )
}
predictors = c("yb", "gr", "saturation", "lConeNeighbors", 
               "distance_to_Scone_1", "distance_to_Scone_2")
corr_analysis(singleConesSummary, predictors)
                    yb          gr          saturation  lConeNeighbors distance_to_Scone_1 distance_to_Scone_2
yb                  "0.000e+00" "2.820e-26" "1.138e-03" "2.000e-01"    "5.135e-01"         "5.398e-01"        
gr                  "2.820e-26" "0.000e+00" "1.101e-01" "2.578e-01"    "9.136e-01"         "9.673e-01"        
saturation          "1.138e-03" "1.101e-01" "0.000e+00" "1.255e-01"    "2.386e-01"         "9.307e-01"        
lConeNeighbors      "2.000e-01" "2.578e-01" "1.255e-01" "0.000e+00"    "1.122e-04"         "1.244e-03"        
distance_to_Scone_1 "5.135e-01" "9.136e-01" "2.386e-01" "1.122e-04"    "0.000e+00"         "4.281e-18"        
distance_to_Scone_2 "5.398e-01" "9.673e-01" "9.307e-01" "1.244e-03"    "4.281e-18"         "0.000e+00"        

Two cone condition:

predictors = c("yb", "gr", "saturation", "lConeNeighbors",
               "distance_btwn_cones_arcmin",  "distance_to_Scone_1", 
               "distance_to_Scone_2")
corr_analysis(twoConesSummary, predictors)
                           yb          gr          saturation  lConeNeighbors distance_btwn_cones_arcmin
yb                         "0.000e+00" "1.963e-31" "9.787e-04" "3.704e-01"    "3.019e-01"               
gr                         "1.963e-31" "0.000e+00" "1.655e-01" "2.613e-02"    "8.605e-01"               
saturation                 "9.787e-04" "1.655e-01" "0.000e+00" "9.544e-01"    "7.302e-03"               
lConeNeighbors             "3.704e-01" "2.613e-02" "9.544e-01" "0.000e+00"    "9.895e-01"               
distance_btwn_cones_arcmin "3.019e-01" "8.605e-01" "7.302e-03" "9.895e-01"    "0.000e+00"               
distance_to_Scone_1        "7.549e-02" "6.574e-01" "7.046e-02" "2.741e-05"    "5.186e-01"               
distance_to_Scone_2        "2.468e-01" "4.834e-01" "6.474e-01" "3.262e-04"    "1.137e-01"               
                           distance_to_Scone_1 distance_to_Scone_2
yb                         "7.549e-02"         "2.468e-01"        
gr                         "6.574e-01"         "4.834e-01"        
saturation                 "7.046e-02"         "6.474e-01"        
lConeNeighbors             "2.741e-05"         "3.262e-04"        
distance_btwn_cones_arcmin "5.186e-01"         "1.137e-01"        
distance_to_Scone_1        "0.000e+00"         "7.036e-17"        
distance_to_Scone_2        "7.036e-17"         "0.000e+00"        

None of the above correlations were statistically significant with the exception of relationships that were known to be dependent, such as between gr and yb. The local neighborhood surrounding a cone is typically thought to be an important factor in generating color sensations. However, we did not find a statistical relationship. The distance between cones may also be an important factor influencing appearance. Neither color nor saturation judgments were correlated with the distance between targeted cones (plotted here in pixels). Cone pairs were never separated by more than one cone, which may explain why we did not detect a relationship. Moreover, the subjects verbally reported that the flashes always appeared as a single uniformly colored dot. Over these very small spatial distances, the visual system appears to be yoking neuronal activities together. In the future, systematically varying the distance between stimulated pairs will be an informative exercise. At a certain critical distance, the points of light will be seen as two spatially distinct dots. It is less clear at what distance the points will be perceived as two distinct colors.

Two cone responses are an average of individual reports

While features of the mosaic and physical stimulus did not predict color reports, we hypothesized that the sensations recorded from individual cones would be predictive of paired stimulation conditions. To address this question, we matched the mean response from each cone pair with the the mean report from each cone tested individually.

merged1 <- merge(twoConesSummary, 
           select(singleConesSummary, subject, 
                  session, masterID1, yb, gr, lConeNeighbors), 
           by=c("subject", "session", "masterID1")) %>% 
  rename(., yb12=yb.x, gr12=gr.x, yb1=yb.y, gr1=gr.y,
                  lConeNeighbors12=lConeNeighbors.x, 
                  lConeNeighbors1=lConeNeighbors.y)
sessionMerge <- merged1 %>% 
  merge(., select(singleConesSummary, subject, session,
                  masterID1, yb, gr, lConeNeighbors), 
        by.x=c("subject", "session", "masterID2"),
        by.y=c("subject", "session", "masterID1")) %>% 
  rename(., yb2=yb, gr2=gr, lConeNeighbors2=lConeNeighbors) %>%
  mutate(., saturation12=abs(yb12) + abs(gr12),
         saturation1=abs(yb1) + abs(gr1),
         saturation2=abs(yb2) + abs(gr2))

We then fit a linear model to the data. Behavioral reports from two cone stimulation were predicted by an average of the individual responses: \(gr_{12} = (gr_1 + gr_2) / 2\). Below is a visualization of the measured responses against the predictions.

sessionMerge['predictedYB'] = (sessionMerge$yb1 + sessionMerge$yb2) / 2
sessionMerge['predictedGR'] = (sessionMerge$gr1 + sessionMerge$gr2) / 2
sessionMerge['predictedSaturation'] = abs(sessionMerge$predictedYB) + abs(sessionMerge$predictedGR)
# Add a column w difference from prediction
sessionMerge <- mutate(sessionMerge, 
                       diff_from_prediction=saturation12 - predictedSaturation)
unity = data.frame(x=c(-1, 1), y=c(-1, 1))
gr_plot <- sessionMerge %>%
  ggplot(., aes(x=predictedGR, y=gr12)) + 
  geom_point() +
  geom_smooth(method=lm) +
  coord_equal(xlim=c(-1, 1), ylim=c(-1, 1)) +
  geom_path(data=unity, aes(x=x, y=y), colour='gray', linetype=1) +
  ggtitle('(green - red) / total') +
  geom_path(data=unity, aes(x=x, y=y), colour='gray', linetype=1) +
  xlab('predicted response') + ylab('') + 
  theme_classic(base_size=15)
unity = data.frame(x=c(-1, 1), y=c(-1, 1))
yb_plot <- sessionMerge %>%
  ggplot(., aes(x=predictedYB, y=yb12)) + 
  geom_point() +
  geom_smooth(method=lm) +
  coord_equal(xlim=c(-1, 1), ylim=c(-1, 1)) +
  geom_path(data=unity, aes(x=x, y=y), colour='gray', linetype=1) +
  ggtitle('(yellow - blue) / total') +
  geom_path(data=unity, aes(x=x, y=y), colour='gray', linetype=1) +
  xlab('predicted response') + 
  ylab('observed pair response') + 
  theme_classic(base_size=15)
p <- grid.arrange(yb_plot, gr_plot, nrow=1)

ggsave("figures/prediction.pdf", p)

Results of linear regression (gr dimension followed by yb):

# A linear model predicts the results based on an average of the individual responses.
mod <- lm(gr12 ~ predictedGR, sessionMerge)
summary.lm(mod)

Call:
lm(formula = gr12 ~ predictedGR, data = sessionMerge)

Residuals:
     Min       1Q   Median       3Q      Max 
-0.45553 -0.09987 -0.00895  0.09816  0.57855 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)    
(Intercept)  0.01772    0.01259   1.407    0.161    
predictedGR  1.12390    0.05041  22.297   <2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 0.1623 on 181 degrees of freedom
Multiple R-squared:  0.7331,    Adjusted R-squared:  0.7316 
F-statistic: 497.2 on 1 and 181 DF,  p-value: < 2.2e-16
mod <- lm(yb12 ~ predictedYB, sessionMerge)
summary.lm(mod)

Call:
lm(formula = yb12 ~ predictedYB, data = sessionMerge)

Residuals:
      Min        1Q    Median        3Q       Max 
-0.203925 -0.017762  0.004318  0.018450  0.236417 

Coefficients:
             Estimate Std. Error t value Pr(>|t|)    
(Intercept) -0.004318   0.004214  -1.025    0.307    
predictedYB  1.002010   0.043201  23.194   <2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 0.05699 on 181 degrees of freedom
Multiple R-squared:  0.7482,    Adjusted R-squared:  0.7469 
F-statistic:   538 on 1 and 181 DF,  p-value: < 2.2e-16

Pairs with the same type are more saturated than predicted by average

A simple linear model captured a large fraction of the variance (>72%). However, there were some pairs that deviated substantially from the best fit line. We wondered whether the deviation from linearity might be predicted by the sub-class of the two cones. For instance, do an L- and M-cone interact in a non-linear manner, while two L or two M-cones sum linearly?

We found the saturation for each pair and subtracted it from the average of the two cones probed alone. Those results are plotted below. A unity line represents the condition where the observed saturation judgment was predicted exactly by the average of individual responses. Notice that the L+L and M+M pairs tend to fall above the unity line – particularly at higher saturation values. In contrast, the L+M pairs fall below the line. These observations indicate that the cones with the same spectral type produced slightly more saturated reports than predicted by the average of their individual responses.

We quantified this trend directly by taking the difference between the observed and predicted saturation judgments. The results are illustrated in a histogram below. Student’s t-test’s confirm that the L+M pairs were significantly less saturated (more white) than a simple average of their individual responses, while the opposite was true for cones with the same spectral type.

diffHist <- sessionMerge %>%
   filter(., typeID == 1 | typeID > 3) %>%
   ggplot(., 
          aes(x=saturation12 - predictedSaturation, 
              color=as.factor(typeID))) + 
  geom_freqpoly(binwidth=0.1, size=1.25) +
  scale_color_manual(
    values = c(gray, green, yellow, red), 
    name="spectral type",
    labels = c("unknown-pair", "M+M", "L+M", "L+L")) + 
  xlab("measured - predicted saturation") +
  theme_classic(base_size=15) +
  theme(legend.position=c(0.75, 0.80))
ggsave('figures/observed-predicted_histogram.pdf')
TableGrob (1 x 2) "arrange": 2 grobs
  z     cells    name           grob
1 1 (1-1,1-1) arrange gtable[layout]
2 2 (1-1,2-2) arrange gtable[layout]

L+M pairs had a mean that was significantly different from zero.

t.test(sessionMerge %>% 
         filter(., typeID == 5) %>% 
         select(., diff_from_prediction)
       )

    One Sample t-test

data:  sessionMerge %>% filter(., typeID == 5) %>% select(., diff_from_prediction)
t = -1.9437, df = 56, p-value = 0.05696
alternative hypothesis: true mean is not equal to 0
95 percent confidence interval:
 -0.068670699  0.001035897
sample estimates:
 mean of x 
-0.0338174 

L+L and M+M pairs had a mean that was significantly different from zero, but in the opposite direction.

t.test(sessionMerge %>% 
         filter(., typeID == 4 | typeID == 6) %>%
         select(., diff_from_prediction)
       )

    One Sample t-test

data:  sessionMerge %>% filter(., typeID == 4 | typeID == 6) %>% select(.,     diff_from_prediction)
t = 4.172, df = 78, p-value = 7.772e-05
alternative hypothesis: true mean is not equal to 0
95 percent confidence interval:
 0.03748812 0.10592157
sample estimates:
 mean of x 
0.07170484 

Unclassified cones were significantly more saturated than predicted.

t.test(sessionMerge %>% 
         filter(., typeID == 1) %>% 
         select(., diff_from_prediction)
       )

    One Sample t-test

data:  sessionMerge %>% filter(., typeID == 1) %>% select(., diff_from_prediction)
t = 4.0435, df = 46, p-value = 0.0001988
alternative hypothesis: true mean is not equal to 0
95 percent confidence interval:
 0.06116617 0.18243140
sample estimates:
mean of x 
0.1217988 

Finally, L+M pairs were significantly different from the population of responses collected from L+L and M+M pairs.

t.test(sessionMerge %>% 
         filter(., typeID == 5) %>% 
         select(., diff_from_prediction),
       sessionMerge %>% 
         filter(., typeID == 4 | typeID == 6) %>%
         select(., diff_from_prediction)
       )

    Welch Two Sample t-test

data:  sessionMerge %>% filter(., typeID == 5) %>% select(., diff_from_prediction) and sessionMerge %>% filter(., typeID == 4 | typeID == 6) %>% select(., sessionMerge %>% filter(., typeID == 5) %>% select(., diff_from_prediction) and     diff_from_prediction)
t = -4.3148, df = 129.85, p-value = 3.139e-05
alternative hypothesis: true difference in means is not equal to 0
95 percent confidence interval:
 -0.15390621 -0.05713828
sample estimates:
  mean of x   mean of y 
-0.03381740  0.07170484 

Conclusions

Color sensations from pairs of cones were predicted by a simple average of individual responses. However, when two cones from the same subclass were probed, there was a systematic deviation from a simple average. These pairs produced significantly more saturated colors than predicted by an average of the colors elicited when probed alone. This observation suggests that the visual system uses a different strategy when combining information within versus across neuronal sub-classes.

LS0tCnRpdGxlOiAnIFNwYXRpYWwgc3VtbWF0aW9uIG9mIGluZGl2aWR1YWwgY29uZXMgaW4gaHVtYW4gY29sb3IgdmlzaW9uJwphdXRob3I6ICdbQnJpYW4gUC4gU2NobWlkdF0oaHR0cHM6Ly9icHMxMC5naXRodWIuaW8pLCBBbGV4YW5kcmEgRS4gQm9laG0sIFdpbGxpYW0gUy4gVHV0ZW4sIEF1c3RpbiBSb29yZGEnCmRhdGU6ICJgciBTeXMuRGF0ZSgpYCIKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6IGRlZmF1bHQKICBwZGZfZG9jdW1lbnQ6IGRlZmF1bHQKICBodG1sX2RvY3VtZW50OgogICAgZGZfcHJpbnQ6IHBhZ2VkCi0tLQoKIyBJbnRyb2R1Y3Rpb24KCkEgY2VudHJhbCBnb2FsIG9mIHZpc2lvbiBzY2llbmNlIGlzIHRvIHVuZGVyc3RhbmQgaG93IHNpZ25hbHMgZnJvbSBwaG90b3JlY2VwdG9ycyBhcmUgdHJhbnNmb3JtZWQgaW50byBzaWdodCBhbmQgdGhlIGxpbWl0YXRpb25zIGVhY2ggc3RhZ2Ugb2YgcHJvY2Vzc2luZyBpbXBvc2VzIG9uIHBlcmNlcHRpb24uIFBob3RvcmVjZXB0b3JzIHByb3ZpZGUgYW4gb3JnYW5pc20gd2l0aCByZWFsLXRpbWUgaW5mb3JtYXRpb24gYWJvdXQgdGhlIGVudmlyb25tZW50LiBIb3dldmVyLCB0aGUgc2lnbmFscyBjb252ZXllZCBieSBpbmRpdmlkdWFsIG5ldXJvbnMgYXJlIG5vaXN5IGFuZCBhbWJpZ3VvdXMuICBPbmUgc3RyYXRlZ3kgZm9yIHJlZHVjaW5nIHVuY2VydGFpbnR5IGlzIHRvIHBvb2wgc2lnbmFscyBhY3Jvc3MgbXVsdGlwbGUgZGV0ZWN0b3JzLiBVbmRlciBsb3cgbGlnaHQgY29uZGl0aW9ucywgZm9yIGV4YW1wbGUsIHRoZSB2aXN1YWwgc3lzdGVtIGNvbWJpbmVzIHNpZ25hbHMgZnJvbSBtYW55IGh1bmRyZWRzIG9mIHJvZCBhbmQgY29uZSBwaG90b3JlY2VwdG9ycyBpbiBvcmRlciB0byBib29zdCBzZW5zaXRpdml0eSBcY2l0ZXtSaWVrZTIwMDh9LiBPbmUgZHJhd2JhY2sgb2Ygc2lnbmFsIHBvb2xpbmcgaXMgYSBsb3NzIGluIHNwYXRpYWwgcmVzb2x1dGlvbjogYm90aCBhY3VpdHkgYW5kIGNvbnRyYXN0IHNlbnNpdGl2aXR5IGFyZSByZWR1Y2VkIHVuZGVyIGxvdy1saWdodCBsZXZlbHMgXGNpdGV7QmFyYnVyMjAxMH0uIFdlIHN0dWRpZWQgdGhlIGluZmx1ZW5jZSBvZiBzcGF0aWFsIHBvb2xpbmcgb24gdGhlIGNvbG9yIGFwcGVhcmFuY2Ugb2YgY29uZS10YXJnZXRlZCBzcG90cy4KCiMgUmVzdWx0cwoKIyMgTG9hZCBkYXRhIGFuZCBpbXBvcnQgbGlicmFyaWVzLgoKRmlyc3QgbG9hZCBpbiBkYXRhIGFuZCBzZXQgc29tZSBwYXJhbWV0ZXJzIHRoYXQgd2lsbCBiZSB1c2VkIGR1cmluZyBwbG90dGluZyBsYXRlci4KYGBge3IgcmVzdWx0cz0iaGlkZSIsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CiMgc2V0d2QoIn4vUi9TY2htaWR0LUJvZWhtLVR1dGVuLVJvb3JkYV8yMDE5IikKbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkoZ2d0aGVtZXMpCmxpYnJhcnkoZ3JpZEV4dHJhKQpsaWJyYXJ5KGNvcnJwbG90KQpsaWJyYXJ5KGNhcikKCiMgc2V0IHRoZSBjb2xvcnMgdGhhdCB3aWxsIGJlIHVzZWQgaW4gcGxvdHMgYmVsb3cuCnJlZCA8LSAiI2U4NDA0MCIKZ3JlZW4gPC0iIzQwYzYzMSIKYmx1ZSA8LSAiIzQwNDJlOCIKeWVsbG93IDwtICIjZThkZjQwIgpncmF5IDwtICIjNTk1OTU5IgoKIyBtYWtlIHN1cmUgYSBmaWd1cmVzIGRpcmVjdG9yeSBleGlzdHMKZGlyLmNyZWF0ZSgnZmlndXJlcycsIHNob3dXYXJuaW5ncz1GQUxTRSkKYGBgCgpgYGB7ciwgbWVzc2FnZT1GQUxTRX0KdHJpYWxfZGF0YSA8LSByZWFkX2NzdignZmlsdGVyZWRfZGF0YS5jc3YnKQoKaGVhZCh0cmlhbF9kYXRhKQpgYGAKCiMjIEV4YW1wbGUgc2Vzc2lvbgoKSW4gYSBwcmV2aW91cyBleHBlcmltZW50LCB0aGUgb25lIGFuZCB0d28gY29uZSBjb25kaXRpb25zIHdlcmUgZXF1YXRlZCBmb3IgZGV0ZWN0YWJpbGl0eS4gV2Ugc3Vic2VxdWVudGx5IGNvbGxlY3RlZCBhcHBlYXJhbmNlIG1lYXN1cmVtZW50cy4gQWxsIG90aGVyIHN0aW11bHVzIGNvbmRpdGlvbnMgd2VyZSBpZGVudGljYWwgdG8gdGhlIGRldGVjdGlvbiB0YXNrLiBUaHJlZSBjb25lcyB3ZXJlIHNlbGVjdGVkIGZvciBzdHVkeSBpbiBlYWNoIHNlc3Npb24gKEZpZ35ccmVme21ldGhvZHN9QSkuIE9uIGVhY2ggdHJpYWwgZWl0aGVyIGEgc2luZ2xlIGNvbmUgb3IgYSBwYWlyIHdhcyB0YXJnZXRlZC4gQWZ0ZXIgZWFjaCBmbGFzaCwgdGhlIHN1YmplY3QganVkZ2VkIHRoZSBjb2xvciBvZiB0aGUgc3BvdCB1c2luZyBhIGh1ZSBhbmQgc2F0dXJhdGlvbiBzY2FsaW5nIHBhcmFkaWdtIFxjaXRle0dvcmRvbjE5OTQsU2NobWlkdDIwMThhfS4gRWFjaCBjb25lIGFuZCBwYWlyIHdhcyB0ZXN0ZWQgdHdlbHZlIHRpbWVzICg3MiB0cmlhbHMgcGVyIHNlc3Npb24pLiBBIHRvdGFsIG9mIDE5OCBwYWlycyB3ZXJlIHRlc3RlZCBhY3Jvc3MgdGhyZWUgc3ViamVjdHMuIEh1ZSBhbmQgc2F0dXJhdGlvbiBzY2FsaW5nIGRhdGEgd2VyZSB0cmFuc2Zvcm1lZCBpbnRvIGEgY29sb3Igb3Bwb25lbnQgcmVwcmVzZW50YXRpb24uIEZvciBlYWNoIHRyaWFsLCB0aGUgZGVncmVlIG9mIHBlcmNlaXZlZCBncmVlbm5lc3MgdmVyc3VzIHJlZG5lc3MgYW5kIHllbGxvd25lc3MgdmVyc3VzIGJsdWVuZXNzIHdhcyBjb21wdXRlZCBmcm9tIHBlcmNlbnRhZ2UgcmF0aW5ncyBhcyBmb2xsb3dzOiAkZ3IgPSAoZ3JlZW5cJSAtIHJlZFwlKSAvIDEwMFwlJCBhbmQgJHliID0gKHllbGxvd1wlIC0gYmx1ZVwlKSAvIDEwMFwlJC4gSW4gdGhpcyByZXByZXNlbnRhdGlvbiwgc2F0dXJhdGlvbiBpcyBleHByZXNzZWQgYXMgdGhlIGRpc3RhbmNlIGZyb20gdGhlIG9yaWdpbiAoaW4gY2l0eSBibG9jayBtZXRyaWMpLiBBIHB1cmUgd2hpdGUgcmVzcG9uc2UgZmFsbHMgYXQgdGhlIG9yaWdpbiBhbmQgYSBtYXhpbWFsbHkgc2F0dXJhdGVkIHJlcG9ydCBmYWxscyBhbG9uZyB0aGUgb3V0ZXIgZGlhbW9uZC4gCgpUaGUgcmVzdWx0cyBvZiBvbmUgc2Vzc2lvbiBhcmUgcGxvdHRlZCBpbiBGaWd+XHJlZnttZXRob2RzfUIuIEluIHRoaXMgZXhhbXBsZSwgQ29uZSAxIHdhcyBhbiBNLWNvbmUgYW5kIGhhZCBhIGJpYXMgdG93YXJkcyBncmVlbi4gQ29uZSAyIHdhcyBhbiBMLWNvbmUgYW5kIGVsaWNpdGVkIHByZWRvbWluYW50bHkgd2hpdGUgcmVwb3J0cy4gQ29uZSAzLCBhbHNvIGFuIEwtY29uZSwgd2FzIHJhdGVkIHJlZGRpc2gteWVsbG93IChvcmFuZ2UpIHdpdGggbWVkaXVtIHNhdHVyYXRpb24uIFRoZSBtb3RpdmF0aW9uIGZvciB0aGlzIHN0dWR5IHdhcyB0byB1bmRlcnN0YW5kIHRoZSBhbGdvcml0aG0gdGhlIHZpc3VhbCBzeXN0ZW0gdXNlcyB3aGVuIGNvbWJpbmluZyBpbmZvcm1hdGlvbiBhY3Jvc3MgY29uZSBwYWlycy4gSW4gdGhlIGV4YW1wbGUsIHdoZW4gQ29uZSAxIHdhcyB0YXJnZXRlZCB0b2dldGhlciB3aXRoIGVpdGhlciBDb25lIDIgb3IgQ29uZSAzLCB0aGUgYXZlcmFnZSByZXBvcnQgd2FzIGRlc2F0dXJhdGVkLiBJbiBjb21wYXJpc29uLCB3aGVuIENvbmUgMiBhbmQgMyB3ZXJlIHRhcmdldGVkIHRoZXkgZWxpY2l0ZWQgYSBtZWRpdW0gc2F0dXJhdGVkIG9yYW5nZSByZXBvcnQuIEJlbG93LCB3ZSBhbmFseXplIHRoZSByZXN1bHRzIGZyb20gYWxsIHNlc3Npb25zIGFuZCBzdWJqZWN0cy4KCmBgYHtyLCBmaWcuaGVpZ2h0PTMsIGZpZy53aWR0aD00LjUsIG1lc3NhZ2U9RkFMU0UsIHJlc3VsdHM9RkFMU0V9CgpleGFtcGxlIDwtIHRyaWFsX2RhdGEgJT4lCiAgZmlsdGVyKC4sIHN1YmplY3QgPT0gJzIwMDc2UicsIHNlc3Npb249PSA0KSAlPiUKICBncm91cF9ieSguLCBtYXN0ZXJJRDEsIG1hc3RlcklEMiwgaXNQYWlyKSAlPiUgCiAgc3VtbWFyaXNlKC4sIHliX21lYW49c2lnbmlmKG1lYW4oeWIpLCAzKSwgCiAgICAgICAgICAgIHliX3NlbT1zaWduaWYoc2QoeWIpIC8gc3FydChsZW5ndGgoeWIpKSwgMyksIAogICAgICAgICAgICBncl9tZWFuPXNpZ25pZihtZWFuKGdyKSwgMyksIAogICAgICAgICAgICBncl9zZW09c2lnbmlmKHNkKGdyKSAvIHNxcnQobGVuZ3RoKGdyKSksIDMpLCBOPWxlbmd0aChnciksCiAgICAgICAgICAgIHR5cGVJRHN0cj10eXBlSURzdHJbMV0pCgoKYm91bmRhcnkgPC0gZGF0YS5mcmFtZSh4PWMoLTEsIDAsIDEsIDAsIC0xKSwgeT1jKDAsIDEsIDAsIC0xLCAwKSkKCihleGFtcGxlUGxvdCA8LSBnZ3Bsb3QoZGF0YT1leGFtcGxlLCAKICAgICAgICAgICAgICAgICAgICAgICBhZXMoeD15Yl9tZWFuLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgeT1ncl9tZWFuLAogICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xvcj1pbnRlcmFjdGlvbih0eXBlSURzdHIsIGlzUGFpciksCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHNoYXBlPWlzUGFpcikpICsKICAgIGdlb21fcG9pbnQoc2l6ZT0zKSArIAogICAgZ2VvbV9lcnJvcmJhcihhZXMoeD15Yl9tZWFuLCB5bWluPWdyX21lYW4gLSBncl9zZW0sIHltYXg9Z3JfbWVhbiArIGdyX3NlbSkpICsKICAgIGdlb21fZXJyb3JiYXJoKGFlcyh5PWdyX21lYW4sIHhtaW49eWJfbWVhbiAtIHliX3NlbSwgeG1heD15Yl9tZWFuICsgeWJfc2VtLCBoZWlnaHQ9MC4wMSkpICsgCiAgICBjb29yZF9lcXVhbCh4bGltPWMoLTEsIDEpLCB5bGltPWMoLTEsIDEpKSArIAogICAgeGxhYignKHllbGxvdyAtIGJsdWUpIC8gdG90YWwnKSArIAogICAgeWxhYignKGdyZWVuIC0gcmVkKSAvIHRvdGFsJykgKyAKICAgIHNjYWxlX2NvbG9yX21hbnVhbCgKICAgICAgdmFsdWVzID0gYyhyZWQsIGdyZWVuLCByZWQsIHllbGxvdyksIAogICAgICBuYW1lPSJzcGVjdHJhbCB0eXBlIikgKwogICAgZ2VvbV90ZXh0KGxhYmVsPWMoJzEnLCAnMSsyJywgJzErMycsICcyJywgJzIrMycsICczJyksCiAgICAgICAgICAgICAgbnVkZ2VfeD0tMC4xNSkgKwogICAgZ2VvbV9wYXRoKGRhdGEgPSBib3VuZGFyeSwgYWVzKHg9eCwgeT15KSwgY29sb3VyPSdncmF5JywgbGluZXR5cGU9MiwgaW5oZXJpdC5hZXMgPSBGKSArCiAgICB0aGVtZV9idyhiYXNlX3NpemUgPSAxNSkgKyAKICAgIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0ibm9uZSIpCiAgKQoKZ2dzYXZlKCJmaWd1cmVzL2V4YW1wbGVfc2Vzc2lvbi5wZGYiKQoKCmBgYAoKIyMgVmFyaWFiaWxpdHkgaW4gc2Vuc2F0aW9ucyBmcm9tIGNvbmVzIHdpdGggdGhlIHNhbWUgc2Vuc2l0aXZpdHkKClRoZSBtb3RpdmF0aW5nIHF1ZXN0aW9uIGJlaGluZCB0aGVzZSBleHBlcmltZW50cyB3YXMgaG93IGRvZXMgdGhlIHZpc3VhbCBzeXN0ZW0gY29tYmluZSBpbnB1dHMgZnJvbSBjb25lcyB3aGVuIG1ha2luZyBjb2xvciBhcHBlYXJhbmNlIGp1ZGdtZW50cz8gVG8gYmVnaW4gdG8gYW5zd2VyIHRoaXMgcXVlc3Rpb24sIHdlIGdyb3VwZWQgZWFjaCB0cmlhbCBiYXNlZCBvbiB3aGljaCBjb25lIG9yIHBhaXIgd2FzIHByb2JlZC4gVGhlIHJlc3VsdHMgYXJlIHJlcG9ydGVkIGluIGEgdGFibGUgYmVsb3cuIEluIGJvdGggc3ViamVjdHMgd2l0aCBjbGFzc2lmaWVkIGNvbmVzLCBpbmRpdmlkdWFsIGFuZCBwYWlycyBvZiBNLWNvbmVzIHByb2R1Y2VkIGdyIG1lYW5zIHRoYXQgd2VyZSBncmVhdGVyIHRoYW4gemVybywgd2hpbGUgTC1jb25lIGNvbmRpdGlvbnMgbGVkIHRvIG5lZ2F0aXZlIHZhbHVlcy4gVGhpcyBvYnNlcnZhdGlvbiBpcyBjb25zaXN0ZW50IHdpdGggYSBwcmVkaWN0aXZlIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIGNvbmUgdHlwZSBhbmQgY29sb3IgcmVwb3J0LCBhcyBmb3VuZCBwcmV2aW91c2x5IChTYWJlc2FuIGV0IGFsLiAyMDA2OyBTY2htaWR0IGV0IGFsLiAyMDE4KS4KCkVhY2ggcG9pbnQgaW4gdGhlc2UgcGxvdHMgcmVwcmVzZW50cyB0aGUgbWVhbiByZXNwb25zZSBtZWFzdXJlZCBmcm9tIGEgc2luZ2xlIGNvbmUgb3IgYSBwYWlyLiBUaGlzIHBsb3QgaWxsdXN0cmF0ZXMgdGhlIHZhcmlhYmlsaXR5IGluIHJlc3BvbnNlcyBhY3Jvc3MgY29uZXMvcGFpcnMgYW5kIGJldHdlZW4gc3ViamVjdHMuIFRoZXJlIGFyZSBhIGZldyBmZWF0dXJlcyB0byBub3RlLiBGaXJzdGx5LCB3aXRoaW4gYSBzaW5nbGUgc3ViamVjdCwgdGhlcmUgd2FzIGNvbnNpZGVyYWJsZSB2YXJpYWJpbGl0eSBiZXR3ZWVuIGNvbmVzIGFuZCBwYWlycyB3aXRoIHRoZSBzYW1lIHNwZWN0cmFsIHNlbnNpdGl2aXR5LiBTaW1pbGFyIHZhcmlhYmlsaXR5IGluIHNlbnNhdGlvbnMgZnJvbSBzaW5nbGUgY29uZXMgaGFzIGJlZW4gcmVwb3J0ZWQgcHJldmlvdXNseSAoSG9mZXIgZXQgYWwuIDIwMDU7IFNhYmVzYW4gZXQgYWwuIDIwMTY7IFNjaG1pZHQgZXQgYWwuIDIwMTgpLiBUaGlzIGlzIHRoZSBmaXJzdCByZXBvcnQgb2YgdmFyaWFiaWxpdHkgaW4gc2Vuc2F0aW9ucyBlbGljaXRlZCBmcm9tIHBhaXJzIG9mIGNvbmVzLiBTZWNvbmRseSwgdGhlcmUgd2VyZSBpbmRpdmlkdWFsIGRpZmZlcmVuY2VzIGluIGNvbG9yIHJlc3BvbnNlczogUzIwMDc1IHVzZWQgYmx1ZSBtb3JlIGZyZXF1ZW50bHkgdGhhbiB0aGUgdHdvIG90aGVyIHN1YmplY3RzIGFuZCBTMTAwMDEgZGlkIG5vdCByZXBvcnQgeWVsbG93IG9uIGFueSB0cmlhbHMuIEhvd2V2ZXIsIHRoZSBnZW5lcmFsIHBhdHRlcm5zIGFyZSBzaW1pbGFyLiBNb3N0IG9mIHRoZSB2YXJpYW5jZSB3YXMgZm91bmQgYWxvbmcgdGhlIGdyZWVuLXJlZCBkaW1lbnNpb247IHRoZXJlIHdlcmUgZmV3IHBvaW50cyB0aGF0IGZlbGwgaW4gdGhlIGJsdWVpc2gtcmVkIG9yIGdyZWVuaXNoLXllbGxvdyBxdWFkcmFudHMuIEluIHRoZSB0d28gc3ViamVjdHMgd2l0aCBjbGFzc2lmaWVkIG1vc2FpY3MsIHdlIGFkZGl0aW9uYWxseSBmb3VuZCBMLWNvbmUgdGFyZ2V0ZWQgdHJpYWxzIHRlbmRlZCBwcm9kdWNlIGEgcmVkZGlzaCBiaWFzLCB3aGlsZSBNLWNvbmVzIHdlcmUgYmlhc2VkIHRvd2FyZHMgZ3JlZW4uIFRoZXNlIHBhdHRlcm5zIHdlcmUgc2ltaWxhciB0byBwcmV2aW91cyByZXBvcnRzIGZyb20gc2luZ2xlLWNvbmUgKFNhYmVzYW4gZXQgYWwuIDIwMTY7IFNjaG1pZHQgZXQgYWwuIDIwMTgpIGFuZCBsYXJnZS1maWVsZCBzdHVkaWVzIChEZVZhbG9pcyBldCBhbC4gMTk5NykuIAoKYGBge3IsIGZpZy5oZWlnaHQ9MywgZmlnLndpZHRoPTExLCBtZXNzYWdlPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQpzaW5nbGVBbmRQYWlycyA8LSB0cmlhbF9kYXRhICU+JQogIHNlbGVjdCguLCAtdHlwZUlEc3RyKSAlPiUKICBncm91cF9ieSguLCBzdWJqZWN0LCBtYXN0ZXJJRDEsIG1hc3RlcklEMiwgaXNQYWlyKSAlPiUgCiAgc3VtbWFyaXNlX2FsbCguLCBtZWFuKQoKKGluZCA8LSBnZ3Bsb3QoZGF0YT1zaW5nbGVBbmRQYWlycywgYWVzKHg9eWIsIHk9Z3IsIGNvbG9yPWFzLmZhY3Rvcih0eXBlSUQpLCBzaGFwZT1pc1BhaXIpKSArIAogIGdlb21fcG9pbnQoYWxwaGE9MC43Niwgc2l6ZT0wLjgpICsKICBmYWNldF9ncmlkKC4gfiBzdWJqZWN0KSArCiAgZ2VvbV9ydWcoYWxwaGE9MC41KSArIAogIGNvb3JkX2VxdWFsKHhsaW09YygtMSwgMSksIHlsaW09YygtMSwgMSkpICsgCiAgeGxhYignKHllbGxvdyAtIGJsdWUpIC8gdG90YWwnKSArIHlsYWIoJyhncmVlbiAtIHJlZCkgLyB0b3RhbCcpICsgCiAgc2NhbGVfY29sb3JfbWFudWFsKAogICAgdmFsdWVzID0gYyhncmF5LCBncmF5LCBncmVlbiwgcmVkLCBncmVlbiwgeWVsbG93LCByZWQpLCAKICAgIG5hbWU9InNwZWN0cmFsIHR5cGUiLAogICAgbGFiZWxzID0gYygidW5rbm93bi1jb25lIiwgInVua25vd24tcGFpciIsICJNLWNvbmUiLCAiTC1jb25lIiwgIk0rTS1wYWlyIiwgIkwrTS1wYWlyIiwgIkwrTC1wYWlyIikpICsKICAjc2NhbGVfY29sb3JfYnJld2VyKHBhbGV0dGUgPSAiU3BlY3RyYWwiKSArCiAgZ2VvbV9wYXRoKGRhdGEgPSBib3VuZGFyeSwgYWVzKHg9eCwgeT15KSwgY29sb3VyPSdncmF5JywgbGluZXR5cGU9MiwgaW5oZXJpdC5hZXMgPSBGKSArCiAgdGhlbWVfYncoYmFzZV9zaXplID0gMTUpICsgCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJub25lIikpCgpnZ3NhdmUoImZpZ3VyZXMvbG1zX1VBRC5wZGYiKQoKCmBgYAoKVG8gYmV0dGVyIGFwcHJlY2lhdGUgdGhlIGluZmx1ZW5jZSBvZiBjb25lIHR5cGUgYW5kIG51bWJlciBvZiBjb25lcyB0YXJnZXRlZCBvbiBjb2xvciByZXBvcnRzLCBkYXRhIHdhcyBwb29sZWQgYWNyb3NzIHN1YmplY3RzIGFuZCBncm91cGVkIGFjY29yZGluZyB0byB0aGUgdHlwZSBvZiBjb25lIG9yIHBhaXIgcHJvYmVkLiBUaGUgbWVhbiBhbmQgc3RhbmRhcmQgZXJyb3IgZm9yIGVhY2ggZ3JvdXAgaXMgc2hvd24gaW4gdGhlIHJpZ2h0IG1vc3QgcGxvdCBiZWxvdy4gV2hlbiBhbiBpbmRpdmlkdWFsIG9yIHBhaXIgb2YgTS1jb25lcyB3YXMgdGFyZ2V0ZWQgdGhlIGF2ZXJhZ2UgJGdyJCByZXNwb25zZSB3YXMgZ3JlYXRlciB0aGFuIHplcm8gaW5kaWNhdGluZyBhIGJpYXMgdG93YXJkcyBncmVlbi4gSW4gY29tcGFyaXNvbiwgdGhlIGF2ZXJhZ2UgTC1jb25lKHMpIGVsaWNpdGVkIGJpYXNlcyB0b3dhcmRzIHJlZCBhbmQgeWVsbG93LiBUb2dldGhlciB0aGVzZSBjb25lIHR5cGUgc3BlY2lmaWMgZGlmZmVyZW5jZXMgaW4gY29sb3IgcmVwb3J0cyB3ZXJlIGNvbnNpc3RlbnQgd2l0aCBhIHByZWRpY3RpdmUgcmVsYXRpb25zaGlwIGJldHdlZW4gY29uZSB0eXBlIGFuZCBjb2xvciByZXBvcnQsIGFzIHByZXZpb3VzbHkgcmVwb3J0ZWQgKFNhYmVzYW4gZXQgYWwuIDIwMTY7IFNjaG1pZHQgZXQgYWwuIDIwMTgpLiBUd28gY29uZXMgd2l0aCB0aGUgc2FtZSBwaG90b3BpZ21lbnQgdGVuZGVkIHRvIGVsaWNpdCBzbGlnaHRseSBtb3JlIHNhdHVyYXRlZCByZXBvcnRzIHRoYW4gc2luZ2xlIGNvbmUgdHJpYWxzLiBPbiB0aGUgb3RoZXIgaGFuZCwgb25lIEwtIGFuZCBvbmUgTS1jb25lIHRhcmdldGVkIHRvZ2V0aGVyIHRlbmRlZCB0byBwcm9kdWNlIGRlc2F0dXJhdGVkIHJlcG9ydHMuCgpgYGB7ciwgZmlnLmhlaWdodD0zLCBmaWcud2lkdGg9NC41LCBtZXNzYWdlPUZBTFNFLCByZXN1bHRzPSdoaWRlJywgaW5jbHVkZT1GQUxTRX0KKHR5cGVHcm91cCA8LSB0cmlhbF9kYXRhICU+JQogICNmaWx0ZXIoLiwgc3ViamVjdCAhPSAnMjAwNzVMJykgJT4lIAogICBncm91cF9ieSguLCBzdWJqZWN0LCBtYXN0ZXJJRDEsIG1hc3RlcklEMiwgaXNQYWlyLCB0eXBlSUQsIHR5cGVJRHN0cikgJT4lCiAgIHN1bW1hcmlzZSguLCB5Yl9tZWFuQz1zaWduaWYobWVhbih5YiksIDMpLCAKICAgICAgICAgICAgIHliX3NlbUM9c2lnbmlmKHNkKHliKSAvIHNxcnQobGVuZ3RoKHliKSksIDMpLCAKICAgICAgICAgICAgIGdyX21lYW5DPXNpZ25pZihtZWFuKGdyKSwgMyksIAogICAgICAgICAgICAgZ3Jfc2VtQz1zaWduaWYoc2QoZ3IpIC8gc3FydChsZW5ndGgoZ3IpKSwgMyksIE49bGVuZ3RoKGdyKSkgJT4lCiAgIGdyb3VwX2J5KC4sIHR5cGVJRCwgdHlwZUlEc3RyLCBpc1BhaXIpICU+JQogICBzdW1tYXJpc2UoLiwgeWJfbWVhbj1zaWduaWYobWVhbih5Yl9tZWFuQyksIDMpLCAKICAgICAgICAgICAgIHliX3NlbT1zaWduaWYoc2QoeWJfbWVhbkMpIC8gc3FydChsZW5ndGgoTikpLCAzKSwgCiAgICAgICAgICAgICBncl9tZWFuPXNpZ25pZihtZWFuKGdyX21lYW5DKSwgMyksIAogICAgICAgICAgICAgZ3Jfc2VtPXNpZ25pZihzZChncl9tZWFuQykgLyBzcXJ0KGxlbmd0aChOKSksIDMpLCBOPWxlbmd0aChOKSkKKQoKKGF2ZyA8LSBnZ3Bsb3QoZGF0YT10eXBlR3JvdXAsIGFlcyh4PXliX21lYW4sIHk9Z3JfbWVhbiwgY29sb3I9YXMuZmFjdG9yKHR5cGVJRCksIHNoYXBlPWlzUGFpcikpICsKICBnZW9tX3BvaW50KHNpemU9MykgKyAKICBnZW9tX2Vycm9yYmFyKGFlcyh4PXliX21lYW4sIHltaW49Z3JfbWVhbiAtIGdyX3NlbSwgeW1heD1ncl9tZWFuICsgZ3Jfc2VtKSkgKwogIGdlb21fZXJyb3JiYXJoKGFlcyh5PWdyX21lYW4sIHhtaW49eWJfbWVhbiAtIHliX3NlbSwgeG1heD15Yl9tZWFuICsgeWJfc2VtLCBoZWlnaHQ9MC4wMSkpICsgCiAgY29vcmRfZXF1YWwoeGxpbT1jKC0wLjI1LCAwLjI1KSwgeWxpbT1jKC0wLjI1LCAwLjI1KSkgKwogIHhsYWIoJycpICsgCiAgeWxhYignJykgKwogIHNjYWxlX2NvbG9yX21hbnVhbCgKICAgIHZhbHVlcyA9IGMoZ3JheSwgZ3JheSwgZ3JlZW4sIHJlZCwgZ3JlZW4sIHllbGxvdywgcmVkKSwgCiAgICBuYW1lPSJzcGVjdHJhbCB0eXBlIiwKICAgIGxhYmVscyA9IGMoInVua25vd24tY29uZSIsICJ1bmtub3duLXBhaXIiLCAiTS1jb25lIiwgIkwtY29uZSIsICJNK00tcGFpciIsICJMK00tcGFpciIsICJMK0wtcGFpciIpKSArCiAgdGhlbWVfYncoYmFzZV9zaXplPTE1KSkKZ2dzYXZlKCJmaWd1cmVzL2xtc19VQURfbWVhbnMucGRmIikKCmBgYAoKYGBge3IsIGZpZy5oZWlnaHQ9MywgZmlnLndpZHRoPTExLCBtZXNzYWdlPUZBTFNFfQoKKHAgPC0gZ3JpZC5hcnJhbmdlKGluZCwgYXZnLCB3aWR0aHM9Mi41OjEpKQpnZ3NhdmUoImZpZ3VyZXMvVUFEX2NvbWJpbmVkLnBkZiIsIHApCmBgYAoKIyMgTW9zYWljIHBhcmFtZXRlcnMgZG8gbm90IHByZWRpY3QgcmVzcG9uc2VzCgpUaGUgcGxvdCBhYm92ZSBkZW1vbnN0cmF0ZXMgdGhhdCBjb2xvciByZXBvcnRzIHZhcmllZCBldmVuIGJldHdlZW4gY29uZXMgd2l0aCB0aGUgc2FtZSBwaG90b3BpZ21lbnQgKFNjaG1pZHQgZXQgYWwuIDIwMTgpLiBTb21lIEwtY29uZXMsIGZvciBpbnN0YW5jZSwgZWxpY2l0ZWQgaGlnaGx5IHNhdHVyYXRlZCByZWQgc2Vuc2F0aW9ucywgd2hpbGUgYSBtYWpvcml0eSBwcm9kdWNlIHdoaXRlIG9yIGRlc2F0dXJhdGVkIHBpbmsgcmVwb3J0cy4gV2UgbmV4dCBhc2tlZCB3aGV0aGVyIHRoZSB2YXJpYWJpbGl0eSBpbiBzZW5zYXRpb25zIGJldHdlZW4gY29uZXMgd2l0aCB0aGUgc2FtZSBzZW5zaXRpdml0eSBjb3VsZCBiZSBleHBsYWluZWQgYnkgbG93IGxldmVsIGZlYXR1cmVzIG9mIHRoZSBtb3NhaWMuIFNwZWNpZmljYWxseSwgY2FuIHdlIHByZWRpY3Qgd2hldGhlciBhbiBMLWNvbmUgd2lsbCBwcm9kdWNlIGEgc2F0dXJhdGVkIHJlZCBvciBhIGRlc2F0dXJhdGVkIHBpbmsgYmFzZWQgb24gdGhlIHN1cnJvdW5kaW5nIGNvbmUgdHlwZXMgb3IgdGhlIGRlbGl2ZXJ5IGVycm9yPyBBbmQgaW4gdGhlIGNhc2Ugb2YgY29uZSBwYWlycywgZGlkIHRoZSBkaXN0YW5jZSBiZXR3ZWVuIHRoZSB0d28gY29uZXMgaW5mbHVlbmNlIGNvbG9yIGFwcGVhcmFuY2U/IFRvIGFuc3dlciB0aGVzZSBxdWVzdGlvbiwgd2UgZ3JvdXBlZCByZXNwb25zZXMgYWNjb3JkaW5nIHRvIHRoZSBudW1iZXIgb2YgY29uZXMgdGFyZ2V0ZWQgKG9uZSBvciB0d28pLCB0aGUgc2Vzc2lvbiBpbiB3aGljaCB0cmlhbCBvY2N1cnJlZCBhbmQgdGhlIHNwZWNpZmljIGNvbmUocykgdGhhdCB3YXMgdGFyZ2V0ZWQuIFRoZSBtZWFuIGFuZCBjb3VudCB3YXMgdGhlbiBjb21wdXRlZCBmb3IgZWFjaCBjb25lKHMpLiBPbmx5IHRob3NlIGNvbmVzIHBhaXJzIHdoaWNoIGhhZCBhdCBsZWFzdCBmb3VyIGdvb2QgdHJpYWxzIHdlcmUgYW5hbHl6ZWQuCgpgYGB7cn0KbWluVHJpYWxzIDwtIDQKc2luZ2xlQ29uZXNTdW1tYXJ5IDwtIHRyaWFsX2RhdGEgJT4lIAogIHNlbGVjdCguLCAtdHlwZUlEc3RyKSAlPiUKICBmaWx0ZXIoLiwgaXNQYWlyPT1GQUxTRSkgJT4lIAogIGdyb3VwX2J5KC4sIHN1YmplY3QsIHNlc3Npb24sIG1hc3RlcklEMSkgJT4lICAgCiAgc3VtbWFyaXNlX2FsbCguLCBjKCJtZWFuIiwgImxlbmd0aCIpKSAlPiUKICBmaWx0ZXIoLiwgeWJfbGVuZ3RoID49IG1pblRyaWFscykgJT4lCiAgc2VsZWN0KC4sIC1jb250YWlucygiX2xlbmd0aCIpKSAlPiUKICByZW5hbWVfKC5kb3RzPXNldE5hbWVzKG5hbWVzKC4pLCBnc3ViKCJfbWVhbiIsICIiLCBuYW1lcyguKSkpKQoKdHdvQ29uZXNTdW1tYXJ5IDwtIHRyaWFsX2RhdGEgJT4lICAgCiAgc2VsZWN0KC4sIC10eXBlSURzdHIpICU+JQogIGZpbHRlciguLCBpc1BhaXI9PVRSVUUpICU+JSAKICBncm91cF9ieSguLCBzdWJqZWN0LCBzZXNzaW9uLCBtYXN0ZXJJRDEsIG1hc3RlcklEMikgJT4lCiAgc3VtbWFyaXNlX2FsbCguLCBjKCJtZWFuIiwgImxlbmd0aCIpKSAlPiUKICBmaWx0ZXIoLiwgeWJfbGVuZ3RoID49IG1pblRyaWFscykgJT4lCiAgc2VsZWN0KC4sIC1jb250YWlucygiX2xlbmd0aCIpKSAlPiUKICByZW5hbWVfKC5kb3RzPXNldE5hbWVzKG5hbWVzKC4pLCBnc3ViKCJfbWVhbiIsICIiLCBuYW1lcyguKSkpKQpgYGAKCkJlbG93IGlzIGEgbWF0cml4IG9mIGNvcnJlbGF0aW9uIHBsb3RzIGZvciB0aGUgc2luZ2xlIGNvbmUgY29uZGl0aW9uLgoKYGBge3IsbWVzc2FnZT1GQUxTRX0KCnB0ZXN0IDwtIGZ1bmN0aW9uKG1hdCwgLi4uKSB7CiAgIyBzdG9sZW4gZnJvbTogaHR0cDovL3d3dy5zdGhkYS5jb20vZW5nbGlzaC93aWtpL3Zpc3VhbGl6ZS1jb3JyZWxhdGlvbi1tYXRyaXgtdXNpbmctY29ycmVsb2dyYW0KICAgIG1hdCA8LSBhcy5tYXRyaXgobWF0KQogICAgbiA8LSBuY29sKG1hdCkKICAgIHAubWF0PC0gbWF0cml4KE5BLCBuLCBuKQogICAgZGlhZyhwLm1hdCkgPC0gMAogICAgZm9yIChpIGluIDE6KG4gLSAxKSkgewogICAgICAgIGZvciAoaiBpbiAoaSArIDEpOm4pIHsKICAgICAgICAgICAgdG1wIDwtIGNvci50ZXN0KG1hdFssIGldLCBtYXRbLCBqXSwgLi4uKQogICAgICAgICAgICBwLm1hdFtpLCBqXSA8LSBwLm1hdFtqLCBpXSA8LSB0bXAkcC52YWx1ZQogICAgICAgIH0KICAgIH0KICBjb2xuYW1lcyhwLm1hdCkgPC0gcm93bmFtZXMocC5tYXQpIDwtIGNvbG5hbWVzKG1hdCkKICBwLm1hdAp9Cgpjb3JyX2FuYWx5c2lzIDwtIGZ1bmN0aW9uIChkYXRhLCBwcmVkaWN0b3JzKSB7CiAgIyBzY2F0dGVycGxvdE1hdHJpeCB3aXRoIGNhciBwYWNrYWdlCiAgc2NhdHRlcnBsb3RNYXRyaXgoZGF0YVsgLCBwcmVkaWN0b3JzXSwgCiAgICAgICAgICAgICAgICAgICAgZGlhZ29uYWw9bGlzdChtZXRob2QgPSJoaXN0b2dyYW0iKSwKICAgICAgICAgICAgICAgICAgICBlbGxpcHNlPUZBTFNFKQogIAogIE08LWNvcihkYXRhWyAsIHByZWRpY3RvcnNdLCB1c2U9ImNvbXBsZXRlLm9icyIpCiAgaGVhZChyb3VuZChNLDIpKQogIGNvci5tdGVzdCA8LSBwdGVzdAoKICAjIG1hdHJpeCBvZiB0aGUgcC12YWx1ZSBvZiB0aGUgY29ycmVsYXRpb24KICBwLm1hdCA8LSBjb3IubXRlc3QoZGF0YVsgLCBwcmVkaWN0b3JzXSkKICBwcmludChmb3JtYXQocC5tYXQsIGRpZ2l0cz00KSkKCmNvbCA8LSBjb2xvclJhbXBQYWxldHRlKGMoIiNCQjQ0NDQiLCAiI0VFOTk4OCIsICIjRkZGRkZGIiwgIiM3N0FBREQiLCAiIzQ0NzdBQSIpKQpjb3JycGxvdChNLCBtZXRob2Q9ImNvbG9yIiwgY29sPWNvbCgyMDApLCAgCiAgICAgICAgIHR5cGU9InVwcGVyIiwgCiAgICAgICAgICNvcmRlcj0iaGNsdXN0IiwgCiAgICAgICAgIGFkZENvZWYuY29sID0gImJsYWNrIiwgIyBBZGQgY29lZmZpY2llbnQgb2YgY29ycmVsYXRpb24KICAgICAgICAgdGwuY29sPSJibGFjayIsIHRsLnNydD00NSwgI1RleHQgbGFiZWwgY29sb3IgYW5kIHJvdGF0aW9uCiAgICAgICAgICMgQ29tYmluZSB3aXRoIHNpZ25pZmljYW5jZQogICAgICAgICBwLm1hdCA9IHAubWF0LCBzaWcubGV2ZWwgPSAwLjAxLCBpbnNpZyA9ICJibGFuayIsIAogICAgICAgICAjIGhpZGUgY29ycmVsYXRpb24gY29lZmZpY2llbnQgb24gdGhlIHByaW5jaXBhbCBkaWFnb25hbAogICAgICAgICBkaWFnPUZBTFNFIAogICAgICAgICApCgp9CgpwcmVkaWN0b3JzID0gYygieWIiLCAiZ3IiLCAic2F0dXJhdGlvbiIsICJsQ29uZU5laWdoYm9ycyIsIAogICAgICAgICAgICAgICAiZGlzdGFuY2VfdG9fU2NvbmVfMSIsICJkaXN0YW5jZV90b19TY29uZV8yIikKY29ycl9hbmFseXNpcyhzaW5nbGVDb25lc1N1bW1hcnksIHByZWRpY3RvcnMpCgpgYGAKClR3byBjb25lIGNvbmRpdGlvbjoKCmBgYHtyLCBmaWcuaGVpZ2h0PTcsIGZpZy53aWR0aD03LCAgbWVzc2FnZT1GQUxTRX0KCnByZWRpY3RvcnMgPSBjKCJ5YiIsICJnciIsICJzYXR1cmF0aW9uIiwgImxDb25lTmVpZ2hib3JzIiwKICAgICAgICAgICAgICAgImRpc3RhbmNlX2J0d25fY29uZXNfYXJjbWluIiwgICJkaXN0YW5jZV90b19TY29uZV8xIiwgCiAgICAgICAgICAgICAgICJkaXN0YW5jZV90b19TY29uZV8yIikKY29ycl9hbmFseXNpcyh0d29Db25lc1N1bW1hcnksIHByZWRpY3RvcnMpCmBgYAoKTm9uZSBvZiB0aGUgYWJvdmUgY29ycmVsYXRpb25zIHdlcmUgc3RhdGlzdGljYWxseSBzaWduaWZpY2FudCB3aXRoIHRoZSBleGNlcHRpb24gb2YgcmVsYXRpb25zaGlwcyB0aGF0IHdlcmUga25vd24gdG8gYmUgZGVwZW5kZW50LCBzdWNoIGFzIGJldHdlZW4gZ3IgYW5kIHliLiBUaGUgbG9jYWwgbmVpZ2hib3Job29kIHN1cnJvdW5kaW5nIGEgY29uZSBpcyB0eXBpY2FsbHkgdGhvdWdodCB0byBiZSBhbiBpbXBvcnRhbnQgZmFjdG9yIGluIGdlbmVyYXRpbmcgY29sb3Igc2Vuc2F0aW9ucy4gSG93ZXZlciwgd2UgZGlkIG5vdCBmaW5kIGEgc3RhdGlzdGljYWwgcmVsYXRpb25zaGlwLiBUaGUgZGlzdGFuY2UgYmV0d2VlbiBjb25lcyBtYXkgYWxzbyBiZSBhbiBpbXBvcnRhbnQgZmFjdG9yIGluZmx1ZW5jaW5nIGFwcGVhcmFuY2UuIE5laXRoZXIgY29sb3Igbm9yIHNhdHVyYXRpb24ganVkZ21lbnRzIHdlcmUgY29ycmVsYXRlZCB3aXRoIHRoZSBkaXN0YW5jZSBiZXR3ZWVuIHRhcmdldGVkIGNvbmVzIChwbG90dGVkIGhlcmUgaW4gcGl4ZWxzKS4gQ29uZSBwYWlycyB3ZXJlIG5ldmVyIHNlcGFyYXRlZCBieSBtb3JlIHRoYW4gb25lIGNvbmUsIHdoaWNoIG1heSBleHBsYWluIHdoeSB3ZSBkaWQgbm90IGRldGVjdCBhIHJlbGF0aW9uc2hpcC4gTW9yZW92ZXIsIHRoZSBzdWJqZWN0cyB2ZXJiYWxseSByZXBvcnRlZCB0aGF0IHRoZSBmbGFzaGVzIGFsd2F5cyBhcHBlYXJlZCBhcyBhIHNpbmdsZSB1bmlmb3JtbHkgY29sb3JlZCBkb3QuIE92ZXIgdGhlc2UgdmVyeSBzbWFsbCBzcGF0aWFsIGRpc3RhbmNlcywgdGhlIHZpc3VhbCBzeXN0ZW0gYXBwZWFycyB0byBiZSB5b2tpbmcgbmV1cm9uYWwgYWN0aXZpdGllcyB0b2dldGhlci4gSW4gdGhlIGZ1dHVyZSwgc3lzdGVtYXRpY2FsbHkgdmFyeWluZyB0aGUgZGlzdGFuY2UgYmV0d2VlbiBzdGltdWxhdGVkIHBhaXJzIHdpbGwgYmUgYW4gaW5mb3JtYXRpdmUgZXhlcmNpc2UuIEF0IGEgY2VydGFpbiBjcml0aWNhbCBkaXN0YW5jZSwgdGhlIHBvaW50cyBvZiBsaWdodCB3aWxsIGJlIHNlZW4gYXMgdHdvIHNwYXRpYWxseSBkaXN0aW5jdCBkb3RzLiBJdCBpcyBsZXNzIGNsZWFyIGF0IHdoYXQgZGlzdGFuY2UgdGhlIHBvaW50cyB3aWxsIGJlIHBlcmNlaXZlZCBhcyB0d28gZGlzdGluY3QgY29sb3JzLgoKIyMgVHdvIGNvbmUgcmVzcG9uc2VzIGFyZSBhbiBhdmVyYWdlIG9mIGluZGl2aWR1YWwgcmVwb3J0cwoKV2hpbGUgZmVhdHVyZXMgb2YgdGhlIG1vc2FpYyBhbmQgcGh5c2ljYWwgc3RpbXVsdXMgZGlkIG5vdCBwcmVkaWN0IGNvbG9yIHJlcG9ydHMsIHdlIGh5cG90aGVzaXplZCB0aGF0IHRoZSBzZW5zYXRpb25zIHJlY29yZGVkIGZyb20gaW5kaXZpZHVhbCBjb25lcyB3b3VsZCBiZSBwcmVkaWN0aXZlIG9mIHBhaXJlZCBzdGltdWxhdGlvbiBjb25kaXRpb25zLiBUbyBhZGRyZXNzIHRoaXMgcXVlc3Rpb24sIHdlIG1hdGNoZWQgdGhlIG1lYW4gcmVzcG9uc2UgZnJvbSBlYWNoIGNvbmUgcGFpciB3aXRoIHRoZSB0aGUgbWVhbiByZXBvcnQgZnJvbSBlYWNoIGNvbmUgdGVzdGVkIGluZGl2aWR1YWxseS4KCmBgYHtyLCB3YXJuaW5nPUZBTFNFLCAgbWVzc2FnZT1GQUxTRX0KbWVyZ2VkMSA8LSBtZXJnZSh0d29Db25lc1N1bW1hcnksIAogICAgICAgICAgIHNlbGVjdChzaW5nbGVDb25lc1N1bW1hcnksIHN1YmplY3QsIAogICAgICAgICAgICAgICAgICBzZXNzaW9uLCBtYXN0ZXJJRDEsIHliLCBnciwgbENvbmVOZWlnaGJvcnMpLCAKICAgICAgICAgICBieT1jKCJzdWJqZWN0IiwgInNlc3Npb24iLCAibWFzdGVySUQxIikpICU+JSAKICByZW5hbWUoLiwgeWIxMj15Yi54LCBncjEyPWdyLngsIHliMT15Yi55LCBncjE9Z3IueSwKICAgICAgICAgICAgICAgICAgbENvbmVOZWlnaGJvcnMxMj1sQ29uZU5laWdoYm9ycy54LCAKICAgICAgICAgICAgICAgICAgbENvbmVOZWlnaGJvcnMxPWxDb25lTmVpZ2hib3JzLnkpCgpzZXNzaW9uTWVyZ2UgPC0gbWVyZ2VkMSAlPiUgCiAgbWVyZ2UoLiwgc2VsZWN0KHNpbmdsZUNvbmVzU3VtbWFyeSwgc3ViamVjdCwgc2Vzc2lvbiwKICAgICAgICAgICAgICAgICAgbWFzdGVySUQxLCB5YiwgZ3IsIGxDb25lTmVpZ2hib3JzKSwgCiAgICAgICAgYnkueD1jKCJzdWJqZWN0IiwgInNlc3Npb24iLCAibWFzdGVySUQyIiksCiAgICAgICAgYnkueT1jKCJzdWJqZWN0IiwgInNlc3Npb24iLCAibWFzdGVySUQxIikpICU+JSAKICByZW5hbWUoLiwgeWIyPXliLCBncjI9Z3IsIGxDb25lTmVpZ2hib3JzMj1sQ29uZU5laWdoYm9ycykgJT4lCiAgbXV0YXRlKC4sIHNhdHVyYXRpb24xMj1hYnMoeWIxMikgKyBhYnMoZ3IxMiksCiAgICAgICAgIHNhdHVyYXRpb24xPWFicyh5YjEpICsgYWJzKGdyMSksCiAgICAgICAgIHNhdHVyYXRpb24yPWFicyh5YjIpICsgYWJzKGdyMikpCmBgYAoKV2UgdGhlbiBmaXQgYSBsaW5lYXIgbW9kZWwgdG8gdGhlIGRhdGEuIEJlaGF2aW9yYWwgcmVwb3J0cyBmcm9tIHR3byBjb25lIHN0aW11bGF0aW9uIHdlcmUgcHJlZGljdGVkIGJ5IGFuIGF2ZXJhZ2Ugb2YgdGhlIGluZGl2aWR1YWwgcmVzcG9uc2VzOiAkZ3JfezEyfSA9IChncl8xICsgZ3JfMikgLyAyJC4gQmVsb3cgaXMgYSB2aXN1YWxpemF0aW9uIG9mIHRoZSBtZWFzdXJlZCByZXNwb25zZXMgYWdhaW5zdCB0aGUgcHJlZGljdGlvbnMuCgpgYGB7cn0Kc2Vzc2lvbk1lcmdlWydwcmVkaWN0ZWRZQiddID0gKHNlc3Npb25NZXJnZSR5YjEgKyBzZXNzaW9uTWVyZ2UkeWIyKSAvIDIKc2Vzc2lvbk1lcmdlWydwcmVkaWN0ZWRHUiddID0gKHNlc3Npb25NZXJnZSRncjEgKyBzZXNzaW9uTWVyZ2UkZ3IyKSAvIDIKc2Vzc2lvbk1lcmdlWydwcmVkaWN0ZWRTYXR1cmF0aW9uJ10gPSBhYnMoc2Vzc2lvbk1lcmdlJHByZWRpY3RlZFlCKSArIGFicyhzZXNzaW9uTWVyZ2UkcHJlZGljdGVkR1IpCgojIEFkZCBhIGNvbHVtbiB3IGRpZmZlcmVuY2UgZnJvbSBwcmVkaWN0aW9uCnNlc3Npb25NZXJnZSA8LSBtdXRhdGUoc2Vzc2lvbk1lcmdlLCAKICAgICAgICAgICAgICAgICAgICAgICBkaWZmX2Zyb21fcHJlZGljdGlvbj1zYXR1cmF0aW9uMTIgLSBwcmVkaWN0ZWRTYXR1cmF0aW9uKQoKYGBgCgpgYGB7ciwgZmlnLmhlaWdodD00LCBmaWcud2lkdGg9OSwgbWVzc2FnZT1GQUxTRX0KdW5pdHkgPSBkYXRhLmZyYW1lKHg9YygtMSwgMSksIHk9YygtMSwgMSkpCmdyX3Bsb3QgPC0gc2Vzc2lvbk1lcmdlICU+JQogIGdncGxvdCguLCBhZXMoeD1wcmVkaWN0ZWRHUiwgeT1ncjEyKSkgKyAKICBnZW9tX3BvaW50KCkgKwogIGdlb21fc21vb3RoKG1ldGhvZD1sbSkgKwogIGNvb3JkX2VxdWFsKHhsaW09YygtMSwgMSksIHlsaW09YygtMSwgMSkpICsKICBnZW9tX3BhdGgoZGF0YT11bml0eSwgYWVzKHg9eCwgeT15KSwgY29sb3VyPSdncmF5JywgbGluZXR5cGU9MSkgKwogIGdndGl0bGUoJyhncmVlbiAtIHJlZCkgLyB0b3RhbCcpICsKICBnZW9tX3BhdGgoZGF0YT11bml0eSwgYWVzKHg9eCwgeT15KSwgY29sb3VyPSdncmF5JywgbGluZXR5cGU9MSkgKwogIHhsYWIoJ3ByZWRpY3RlZCByZXNwb25zZScpICsgeWxhYignJykgKyAKICB0aGVtZV9jbGFzc2ljKGJhc2Vfc2l6ZT0xNSkKCnVuaXR5ID0gZGF0YS5mcmFtZSh4PWMoLTEsIDEpLCB5PWMoLTEsIDEpKQp5Yl9wbG90IDwtIHNlc3Npb25NZXJnZSAlPiUKICBnZ3Bsb3QoLiwgYWVzKHg9cHJlZGljdGVkWUIsIHk9eWIxMikpICsgCiAgZ2VvbV9wb2ludCgpICsKICBnZW9tX3Ntb290aChtZXRob2Q9bG0pICsKICBjb29yZF9lcXVhbCh4bGltPWMoLTEsIDEpLCB5bGltPWMoLTEsIDEpKSArCiAgZ2VvbV9wYXRoKGRhdGE9dW5pdHksIGFlcyh4PXgsIHk9eSksIGNvbG91cj0nZ3JheScsIGxpbmV0eXBlPTEpICsKICBnZ3RpdGxlKCcoeWVsbG93IC0gYmx1ZSkgLyB0b3RhbCcpICsKICBnZW9tX3BhdGgoZGF0YT11bml0eSwgYWVzKHg9eCwgeT15KSwgY29sb3VyPSdncmF5JywgbGluZXR5cGU9MSkgKwogIHhsYWIoJ3ByZWRpY3RlZCByZXNwb25zZScpICsgCiAgeWxhYignb2JzZXJ2ZWQgcGFpciByZXNwb25zZScpICsgCiAgdGhlbWVfY2xhc3NpYyhiYXNlX3NpemU9MTUpCgpwIDwtIGdyaWQuYXJyYW5nZSh5Yl9wbG90LCBncl9wbG90LCBucm93PTEpCgpnZ3NhdmUoImZpZ3VyZXMvcHJlZGljdGlvbi5wZGYiLCBwKQoKYGBgCgpSZXN1bHRzIG9mIGxpbmVhciByZWdyZXNzaW9uIChnciBkaW1lbnNpb24gZm9sbG93ZWQgYnkgeWIpOgpgYGB7cn0KIyBBIGxpbmVhciBtb2RlbCBwcmVkaWN0cyB0aGUgcmVzdWx0cyBiYXNlZCBvbiBhbiBhdmVyYWdlIG9mIHRoZSBpbmRpdmlkdWFsIHJlc3BvbnNlcy4KbW9kIDwtIGxtKGdyMTIgfiBwcmVkaWN0ZWRHUiwgc2Vzc2lvbk1lcmdlKQpzdW1tYXJ5LmxtKG1vZCkKCm1vZCA8LSBsbSh5YjEyIH4gcHJlZGljdGVkWUIsIHNlc3Npb25NZXJnZSkKc3VtbWFyeS5sbShtb2QpCmBgYAoKCiMjIFBhaXJzIHdpdGggdGhlIHNhbWUgdHlwZSBhcmUgbW9yZSBzYXR1cmF0ZWQgdGhhbiBwcmVkaWN0ZWQgYnkgYXZlcmFnZQoKQSBzaW1wbGUgbGluZWFyIG1vZGVsIGNhcHR1cmVkIGEgbGFyZ2UgZnJhY3Rpb24gb2YgdGhlIHZhcmlhbmNlICg+NzIlKS4gSG93ZXZlciwgdGhlcmUgd2VyZSBzb21lIHBhaXJzIHRoYXQgZGV2aWF0ZWQgc3Vic3RhbnRpYWxseSBmcm9tIHRoZSBiZXN0IGZpdCBsaW5lLiBXZSB3b25kZXJlZCB3aGV0aGVyIHRoZSBkZXZpYXRpb24gZnJvbSBsaW5lYXJpdHkgbWlnaHQgYmUgcHJlZGljdGVkIGJ5IHRoZSBzdWItY2xhc3Mgb2YgdGhlIHR3byBjb25lcy4gRm9yIGluc3RhbmNlLCBkbyBhbiBMLSBhbmQgTS1jb25lIGludGVyYWN0IGluIGEgbm9uLWxpbmVhciBtYW5uZXIsIHdoaWxlIHR3byBMIG9yIHR3byBNLWNvbmVzIHN1bSBsaW5lYXJseT8KCldlIGZvdW5kIHRoZSBzYXR1cmF0aW9uIGZvciBlYWNoIHBhaXIgYW5kIHN1YnRyYWN0ZWQgaXQgZnJvbSB0aGUgYXZlcmFnZSBvZiB0aGUgdHdvIGNvbmVzIHByb2JlZCBhbG9uZS4gVGhvc2UgcmVzdWx0cyBhcmUgcGxvdHRlZCBiZWxvdy4gQSB1bml0eSBsaW5lIHJlcHJlc2VudHMgdGhlIGNvbmRpdGlvbiB3aGVyZSB0aGUgb2JzZXJ2ZWQgc2F0dXJhdGlvbiBqdWRnbWVudCB3YXMgcHJlZGljdGVkIGV4YWN0bHkgYnkgdGhlIGF2ZXJhZ2Ugb2YgaW5kaXZpZHVhbCByZXNwb25zZXMuIE5vdGljZSB0aGF0IHRoZSBMK0wgYW5kIE0rTSBwYWlycyB0ZW5kIHRvIGZhbGwgYWJvdmUgdGhlIHVuaXR5IGxpbmUgLS0gcGFydGljdWxhcmx5IGF0IGhpZ2hlciBzYXR1cmF0aW9uIHZhbHVlcy4gSW4gY29udHJhc3QsIHRoZSBMK00gcGFpcnMgZmFsbCBiZWxvdyB0aGUgbGluZS4gVGhlc2Ugb2JzZXJ2YXRpb25zIGluZGljYXRlIHRoYXQgdGhlIGNvbmVzIHdpdGggdGhlIHNhbWUgc3BlY3RyYWwgdHlwZSBwcm9kdWNlZCBzbGlnaHRseSBtb3JlIHNhdHVyYXRlZCByZXBvcnRzIHRoYW4gcHJlZGljdGVkIGJ5IHRoZSBhdmVyYWdlIG9mIHRoZWlyIGluZGl2aWR1YWwgcmVzcG9uc2VzLgoKYGBge3IsIGZpZy5oZWlnaHQ9MywgZmlnLndpZHRoPTUsIG1lc3NhZ2U9RkFMU0UsIGluY2x1ZGU9RkFMU0V9Cgooc2NhdExNIDwtIHNlc3Npb25NZXJnZSAlPiUKICAgZmlsdGVyKC4sIHR5cGVJRCA9PSAxIHwgdHlwZUlEID4gMykgJT4lCiAgIGdncGxvdCguLCAKICAgICAgIGFlcyh4PXByZWRpY3RlZFNhdHVyYXRpb24sIHk9c2F0dXJhdGlvbjEyLCBjb2xvcj1hcy5mYWN0b3IodHlwZUlEKSkpICsgCiAgZ2VvbV9wb2ludCgpICsgCiAgY29vcmRfZXF1YWwoeGxpbT1jKDAsIDEpLCB5bGltPWMoMCwgMSkpICsKICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gYyhncmF5LCBncmVlbiwgeWVsbG93LCByZWQpLCAKICAgICAgICAgICAgICAgICAgICAgbmFtZT0ic3BlY3RyYWwgdHlwZSIsCiAgICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IGMoInVua25vd24iLCAiTStNIiwgIkwrTSIsICJMK0wiKSkgKwogIGdlb21fcGF0aChkYXRhPXVuaXR5LCBhZXMoeD14LCB5PXkpLCBjb2xvdXI9J2dyYXknLCBsaW5ldHlwZT0xKSArIAogIHhsYWIoJ3ByZWRpY3RlZCBzYXR1cmF0aW9uJykgKwogIHlsYWIoJ21lYXN1cmVkIHNhdHVyYXRpb24nKSArCiAgdGhlbWVfY2xhc3NpYyhiYXNlX3NpemU9MTUpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb249J25vbmUnKQopCgpnZ3NhdmUoJ2ZpZ3VyZXMvb2JzZXJ2ZWQtcHJlZGljdGVkX3NjYXR0ZXIucGRmJykKCnNjYXRVbiA8LSBzZXNzaW9uTWVyZ2UgJT4lCiAgIGZpbHRlciguLCB0eXBlSUQgPT0gMSkgJT4lCiAgIGdncGxvdCguLCBhZXMoeD1wcmVkaWN0ZWRTYXR1cmF0aW9uLCB5PXNhdHVyYXRpb24xMikpICsgIAogICBnZW9tX3BvaW50KGNvbG9yPWdyYXkpICsgIAogICBjb29yZF9lcXVhbCh4bGltPWMoMCwgMSksIHlsaW09YygwLCAxKSkgKwogICAjc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGMoIiMzYWFmNGYiLCAiI2ZmZjI0NyIsICIjYmYzNTM1IiksIG5hbWU9InNwZWN0cmFsIHR5cGUiLAogICAjICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IGMoIk0rTSIsICJMK00iLCAiTCtMIikpICsKICAgZ2VvbV9wYXRoKGRhdGE9dW5pdHksIGFlcyh4PXgsIHk9eSksIGNvbG91cj0nZ3JheScsIGxpbmV0eXBlPTEpICsgCiAgIHhsYWIoJ3ByZWRpY3RlZCBzYXR1cmF0aW9uJykgKwogICB5bGFiKCdtZWFzdXJlZCBzYXR1cmF0aW9uJykgKwogICB0aGVtZV9jbGFzc2ljKGJhc2Vfc2l6ZT0xNSkKCmdnc2F2ZSgnZmlndXJlcy9vYnNlcnZlZC1wcmVkaWN0ZWRfc2NhdHRlcl91bmNsYXNzaWZpZWQucGRmJykKCmBgYAoKV2UgcXVhbnRpZmllZCB0aGlzIHRyZW5kIGRpcmVjdGx5IGJ5IHRha2luZyB0aGUgZGlmZmVyZW5jZSBiZXR3ZWVuIHRoZSBvYnNlcnZlZCBhbmQgcHJlZGljdGVkIHNhdHVyYXRpb24ganVkZ21lbnRzLiBUaGUgcmVzdWx0cyBhcmUgaWxsdXN0cmF0ZWQgaW4gYSBoaXN0b2dyYW0gYmVsb3cuIFN0dWRlbnQncyB0LXRlc3QncyBjb25maXJtIHRoYXQgdGhlIEwrTSBwYWlycyB3ZXJlIHNpZ25pZmljYW50bHkgbGVzcyBzYXR1cmF0ZWQgKG1vcmUgd2hpdGUpIHRoYW4gYSBzaW1wbGUgYXZlcmFnZSBvZiB0aGVpciBpbmRpdmlkdWFsIHJlc3BvbnNlcywgd2hpbGUgdGhlIG9wcG9zaXRlIHdhcyB0cnVlIGZvciBjb25lcyB3aXRoIHRoZSBzYW1lIHNwZWN0cmFsIHR5cGUuCgpgYGB7ciwgZmlnLmhlaWdodD0zLCBmaWcud2lkdGg9NSwgbWVzc2FnZT1GQUxTRX0KZGlmZkhpc3QgPC0gc2Vzc2lvbk1lcmdlICU+JQogICBmaWx0ZXIoLiwgdHlwZUlEID09IDEgfCB0eXBlSUQgPiAzKSAlPiUKICAgZ2dwbG90KC4sIAogICAgICAgICAgYWVzKHg9c2F0dXJhdGlvbjEyIC0gcHJlZGljdGVkU2F0dXJhdGlvbiwgCiAgICAgICAgICAgICAgY29sb3I9YXMuZmFjdG9yKHR5cGVJRCkpKSArIAogIGdlb21fZnJlcXBvbHkoYmlud2lkdGg9MC4xLCBzaXplPTEuMjUpICsKICBzY2FsZV9jb2xvcl9tYW51YWwoCiAgICB2YWx1ZXMgPSBjKGdyYXksIGdyZWVuLCB5ZWxsb3csIHJlZCksIAogICAgbmFtZT0ic3BlY3RyYWwgdHlwZSIsCiAgICBsYWJlbHMgPSBjKCJ1bmtub3duLXBhaXIiLCAiTStNIiwgIkwrTSIsICJMK0wiKSkgKyAKICB4bGFiKCJtZWFzdXJlZCAtIHByZWRpY3RlZCBzYXR1cmF0aW9uIikgKwogIHRoZW1lX2NsYXNzaWMoYmFzZV9zaXplPTE1KSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPWMoMC43NSwgMC44MCkpCgpnZ3NhdmUoJ2ZpZ3VyZXMvb2JzZXJ2ZWQtcHJlZGljdGVkX2hpc3RvZ3JhbS5wZGYnKQpgYGAKCmBgYHtyLGZpZy5oZWlnaHQ9NCwgZmlnLndpZHRoPTksIGVjaG89RkFMU0UsIG1lc3NhZ2U9RkFMU0V9CihwIDwtIGdyaWQuYXJyYW5nZShzY2F0TE0sIGRpZmZIaXN0LCBucm93PTEpKQoKZ2dzYXZlKCdmaWd1cmVzL29ic2VydmVkLXByZWRpY3RlZC5wZGYnLCBwKQoKYGBgCgpMK00gcGFpcnMgaGFkIGEgbWVhbiB0aGF0IHdhcyBzaWduaWZpY2FudGx5IGRpZmZlcmVudCBmcm9tIHplcm8uCgpgYGB7cn0KCnQudGVzdChzZXNzaW9uTWVyZ2UgJT4lIAogICAgICAgICBmaWx0ZXIoLiwgdHlwZUlEID09IDUpICU+JSAKICAgICAgICAgc2VsZWN0KC4sIGRpZmZfZnJvbV9wcmVkaWN0aW9uKQogICAgICAgKQpgYGAKCkwrTCBhbmQgTStNIHBhaXJzIGhhZCBhIG1lYW4gdGhhdCB3YXMgc2lnbmlmaWNhbnRseSBkaWZmZXJlbnQgZnJvbSB6ZXJvLCBidXQgaW4gdGhlIG9wcG9zaXRlIGRpcmVjdGlvbi4KCmBgYHtyfQp0LnRlc3Qoc2Vzc2lvbk1lcmdlICU+JSAKICAgICAgICAgZmlsdGVyKC4sIHR5cGVJRCA9PSA0IHwgdHlwZUlEID09IDYpICU+JQogICAgICAgICBzZWxlY3QoLiwgZGlmZl9mcm9tX3ByZWRpY3Rpb24pCiAgICAgICApCmBgYAoKVW5jbGFzc2lmaWVkIGNvbmVzIHdlcmUgc2lnbmlmaWNhbnRseSBtb3JlIHNhdHVyYXRlZCB0aGFuIHByZWRpY3RlZC4KYGBge3J9Cgp0LnRlc3Qoc2Vzc2lvbk1lcmdlICU+JSAKICAgICAgICAgZmlsdGVyKC4sIHR5cGVJRCA9PSAxKSAlPiUgCiAgICAgICAgIHNlbGVjdCguLCBkaWZmX2Zyb21fcHJlZGljdGlvbikKICAgICAgICkKYGBgCgpGaW5hbGx5LCBMK00gcGFpcnMgd2VyZSBzaWduaWZpY2FudGx5IGRpZmZlcmVudCBmcm9tIHRoZSBwb3B1bGF0aW9uIG9mIHJlc3BvbnNlcyBjb2xsZWN0ZWQgZnJvbSBMK0wgYW5kIE0rTSBwYWlycy4KYGBge3J9CnQudGVzdChzZXNzaW9uTWVyZ2UgJT4lIAogICAgICAgICBmaWx0ZXIoLiwgdHlwZUlEID09IDUpICU+JSAKICAgICAgICAgc2VsZWN0KC4sIGRpZmZfZnJvbV9wcmVkaWN0aW9uKSwKICAgICAgIHNlc3Npb25NZXJnZSAlPiUgCiAgICAgICAgIGZpbHRlciguLCB0eXBlSUQgPT0gNCB8IHR5cGVJRCA9PSA2KSAlPiUKICAgICAgICAgc2VsZWN0KC4sIGRpZmZfZnJvbV9wcmVkaWN0aW9uKQogICAgICAgKQpgYGAKCiMgQ29uY2x1c2lvbnMKCkNvbG9yIHNlbnNhdGlvbnMgZnJvbSBwYWlycyBvZiBjb25lcyB3ZXJlIHByZWRpY3RlZCBieSBhIHNpbXBsZSBhdmVyYWdlIG9mIGluZGl2aWR1YWwgcmVzcG9uc2VzLiBIb3dldmVyLCB3aGVuIHR3byBjb25lcyBmcm9tIHRoZSBzYW1lIHN1YmNsYXNzIHdlcmUgcHJvYmVkLCB0aGVyZSB3YXMgYSBzeXN0ZW1hdGljIGRldmlhdGlvbiBmcm9tIGEgc2ltcGxlIGF2ZXJhZ2UuIFRoZXNlIHBhaXJzIHByb2R1Y2VkIHNpZ25pZmljYW50bHkgbW9yZSBzYXR1cmF0ZWQgY29sb3JzIHRoYW4gcHJlZGljdGVkIGJ5IGFuIGF2ZXJhZ2Ugb2YgdGhlIGNvbG9ycyBlbGljaXRlZCB3aGVuIHByb2JlZCBhbG9uZS4gVGhpcyBvYnNlcnZhdGlvbiBzdWdnZXN0cyB0aGF0IHRoZSB2aXN1YWwgc3lzdGVtIHVzZXMgYSBkaWZmZXJlbnQgc3RyYXRlZ3kgd2hlbiBjb21iaW5pbmcgaW5mb3JtYXRpb24gd2l0aGluIHZlcnN1cyBhY3Jvc3MgbmV1cm9uYWwgc3ViLWNsYXNzZXMuCgo=