suppressPackageStartupMessages(require(plotly))
suppressPackageStartupMessages(require(tidyverse))
suppressPackageStartupMessages(require(cowplot))

Goal

  • Analyze the latest batches of flow cytometry data to determine the contribution of different (matching) parts of ScPho4 and CgPho4 to their difference in Pho2 dependence.
  • Develop an analysis pipeline to perform QC, correction (if needed) and plotting the results.

Data

Merge the three batches

raw <- list(
  "03/15/22" = read_tsv("../data/20220315-EO-chimera-batch-1/20220329-gated-median-out.txt", show_col_types = FALSE),
  "03/22/22" = read_tsv("../data/20220322-EO-chimera-batch-2/20220327-gated-median-out.txt", show_col_types = FALSE),
  "03/29/22" = read_tsv("../data/20220329-EO-chimera-batch-3/20220330-gated-median-out.txt", show_col_types = FALSE)
)
dat <- bind_rows(raw, .id = "date") %>% 
  #separate(col = well, sep = 1, into = c("row", "col")) %>% 
  separate(col = sample, sep = "-", into = c("plasmid", "host", "rep"))

QC

1. Number of events per sample

dat %>% ggplot(aes(x = n_induction)) + geom_histogram(bins = 30, fill = "forestgreen") + facet_grid(host~date) + theme_bw(base_size = 14) #+ panel_border()

Majority of the experimental wells (not blank) have > 10000 events, with the exception of a few samples

dat %>% filter(host != "blank", n_induction < 10000)

Given the distribution of event counts, we will label all samples with < 7000 counts, which would exclude 2 samples. The rationale is that even though some samples had between 7000 - 10000 events, their mean fluorescence values appear in line with the other replicates of the same genotype. I also removed the blank wells and add a flag to mark the plasmids that Emily remade because the original one (used for this batch of data) contains nonsynonymous mutations.

event.th <- 7000

list.mutations <- as.character(c(215, 209, 211, 210, 217, 218, 220, 221, 223, 224, 222, 227, 230, 229, 231, 233, 240, 241, 250:255, 257, 258, 265, 266))

expt <- dat %>% 
  filter(host != "blank") %>% 
  group_by(date, plasmid, host) %>% 
  summarize(n = n(), n_filter = sum(n_induction >= event.th), .groups = "drop") %>% 
  mutate(flag = ifelse(plasmid %in% list.mutations, "mutation", ""))
dat <- dat %>% 
  mutate(
    low_event_count = n_induction < event.th,
    mutation = ifelse(plasmid %in% list.mutations, TRUE, FALSE)
  ) %>% 
  filter(host != "blank")

How many plasmid-host combination have < 3 replicates after removing samples with low event counts?

dat %>% filter(!low_event_count) %>% 
  count(plasmid, host) %>% 
  filter(n < 3)

2. Background subtraction

Check the background fluorescence levels across different days.

dat %>% filter(host == "156") %>% 
  pivot_longer(BL1.H:nRFP, names_to = "parameter", values_to = "intensity") %>% 
  mutate(parameter = ordered(parameter, levels = c("BL1.H", "YL2.H", "nGFP", "nRFP"))) %>% 
  ggplot(aes(x = date, y = intensity)) + geom_point(aes(shape = well)) +
  stat_summary(fun.data = "mean_se", geom = "pointrange", color = "red", position = position_nudge(x = 0.1)) +
  facet_wrap(~parameter, scale = "free_y") +
  theme_bw(base_size = 14)

03/29/22 D9 showed higher background fluorescence than the others. after checking the gating result, I found that NA-156-2 sample in D9 had a slightly wider distribution, and the 2d cluster gate is also wider than the other two samples. The cell size normalized intensity doesn’t show the same pattern, suggesting that at least some of the difference could be due to cell size variation. Try just averaging the three blank wells and perform the background removal.

Subtract the background

bg <- dat %>% 
  filter(host == "156") %>% 
  group_by(date) %>% 
  summarize(across(FSC.H:nRFP, ~ round(mean(.x),1))) %>% 
  column_to_rownames(var = "date")

dat1 <- dat %>% 
  select(date:host, FSC.H:nRFP, low_event_count, mutation) %>%
  mutate(
    BL1.H = BL1.H - bg[date, "BL1.H"],
    YL2.H = YL2.H - bg[date, "YL2.H"],
    nGFP = nGFP - bg[date, "nGFP"],
    nRFP = nRFP - bg[date, "nRFP"],
  )

Double check that the subtraction worked correctly

dat1 %>% filter(host == "156") %>% 
  pivot_longer(BL1.H:nRFP, names_to = "parameter", values_to = "intensity") %>% 
  mutate(parameter = ordered(parameter, levels = c("BL1.H", "YL2.H", "nGFP", "nRFP"))) %>% 
  ggplot(aes(x = date, y = intensity)) + geom_point(aes(shape = well)) +
  stat_summary(fun.data = "mean_se", geom = "pointrange", color = "red", position = position_nudge(x = 0.1)) +
  facet_wrap(~parameter, scale = "free_y") +
  theme_bw(base_size = 14)


# remove the yH156 samples
dat1 <- dat1 %>% 
  filter(host != "156") %>% 
  mutate(host = ordered(host, levels = c("555", "373"), labels = c("PHO2", "pho2∆")))

Export the background-subtracted values for later use with the shinyapp

write_tsv(dat1, "../output/20220614-mar22-batch-bg-sub-data.tsv")

3. Consistency across plates.

Check the background-subtracted fluorescence values for the host strains (yH373 and yH555) as well as the positive control strain (pH188 in yH373 or yH555 backgrounds), both of which are present on each plate.

dat1 %>% filter(plasmid %in% c("188", "NA")) %>% 
  pivot_longer(BL1.H:YL2.H, names_to = "parameter", values_to = "intensity") %>% 
  #mutate(parameter = ordered(parameter, levels = c("BL1.H", "YL2.H", "nGFP", "nRFP"))) %>% 
  ggplot(aes(x = plasmid, y = intensity, color = date)) + 
  geom_point(position = position_dodge(0.5)) + 
  scale_color_brewer(type = "qual", palette = 2) +
  facet_grid(parameter~host, scale = "free_y") +
  theme_bw(base_size = 14) +
  theme(axis.text.x = element_text(face = 3))

  1. The host strain fluorescence levels appear to be consistent across the three days. Additionally, their GFP levels are zero, as expected.
  2. The positive control strain has strong BL1.H and correspondingly has strong YL2.H.

The size normalized values show slightly lower variance:

dat1 %>% filter(plasmid %in% c("188", "NA")) %>% 
  pivot_longer(nGFP:nRFP, names_to = "parameter", values_to = "intensity") %>% 
  #mutate(parameter = ordered(parameter, levels = c("BL1.H", "YL2.H", "nGFP", "nRFP"))) %>% 
  ggplot(aes(x = plasmid, y = intensity, color = date)) + 
  geom_point(position = position_dodge(0.5)) + 
  scale_color_brewer(type = "qual", palette = 2) +
  facet_grid(parameter~host, scale = "free_y") +
  theme_bw(base_size = 14) +
  theme(axis.text.x = element_text(face = 3))

4. Well position

Rationale

  • In my original plate design, I placed a positive control strain (envisioned CgPho4-mNeon in PHO2 background for example) in the control columns (1, 5, 9) on every other row (A, C, E, G). The reason for this is to use it to identify any well position effect on the fluorescence readings.
  • In Emily’s implementation, she instead put a pair of strains, namely pH188-yH323 and pH188-yH555 in these wells. This means I cannot use the positive control wells exactly the way as I designed them. But I can still use them to spot any trend.
dat1 %>% 
  filter(plasmid == "188") %>% 
  separate(well, into = c("row", "col"), sep = 1) %>% 
  pivot_longer(BL1.H:YL2.H, names_to = "parameter", values_to = "intensity") %>% 
  ggplot(aes(x = row, y = intensity, group = date)) + 
  geom_point(aes(color = date), size = 1) + 
  #geom_line(aes(color = date), size = 0.4) +
  stat_summary(aes(color = date), geom = "line", fun = mean) +
  scale_color_brewer(type = "qual", palette = 2) +
  facet_grid(parameter~host, scales = "free_y") +
  theme_bw(base_size = 14)

  • no obivous trend between the rows (or columns, not shown)
  • a clear correlation between YL2.H and BL1.H. at least part of this is due to the cell size differences – see size normalized data below:
dat1 %>% 
  filter(plasmid == "188") %>% 
  separate(well, into = c("row", "col"), sep = 1) %>% 
  pivot_longer(c(YL2.H, nRFP), names_to = "parameter", values_to = "intensity") %>% 
  ggplot(aes(x = row, y = intensity, group = date)) + 
  geom_point(aes(color = date), size = 1) + 
  #geom_line(aes(color = date), size = 0.4) +
  stat_summary(aes(color = date), geom = "line", fun = mean) +
  scale_color_brewer(type = "qual", palette = 2) +
  facet_grid(parameter~host, scales = "free_y") +
  theme_bw(base_size = 14)

  • What we care about is the ratio between Pho4-mNeon and PHO5pr-mCherry
dat1 %>% 
  filter(plasmid == "188") %>% 
  separate(well, into = c("row", "col"), sep = 1) %>% 
  ggplot(aes(x = BL1.H, y = YL2.H)) + 
  geom_point(aes(shape = date, color = row), size = 3) + 
  stat_smooth(method = "lm", se = FALSE, color = "gray50", size = 0.5) +
  scale_color_viridis_d() +
  scale_shape_manual(values = 15:17) +
  #scale_x_log10() + scale_y_log10() +
  #facet_grid(parameter~host, scales = "free_y") +
  theme_bw(base_size = 14)
`geom_smooth()` using formula 'y ~ x'

  • There is variation in BL1.H and correspondingly in YL2.H, but the ratio between the two are very consistent

Let’s check both ScPho4 (pH194) and CgPho4 (pH188) to see if their behaviors are consistent across the days.

dat1 %>% 
  filter(plasmid %in% c("188", "194")) %>% 
  mutate(`YL2.H/BL1.H` = YL2.H/BL1.H,
  #mutate(`YL2.H/BL1.H` = nRFP/nGFP,
         Pho4 = factor(plasmid, levels = c("194", "188"), 
                       labels = c("ScPho4", "CgPho4"))) %>% 
  separate(well, into = c("row", "col"), sep = 1) %>% 
  ggplot(aes(x = host, y = `YL2.H/BL1.H`)) + 
  geom_point(color = alpha("gray50", 0.9), position = position_jitter(0.2)) + 
  stat_summary(fun.data = "mean_se", geom = "pointrange", color = "red") +
  facet_grid(Pho4~date) +
  theme_bw(base_size = 14)

5. High variance samples

Summarize the background subtracted data by calculating the means and cv for each strain.

dat2 <- dat1 %>% 
  select(-nGFP, -nRFP) %>% 
  pivot_longer(FSC.H:YL2.H, names_to = "parameter", values_to = "intensity") %>% 
  group_by(date, plasmid, host, parameter) %>% 
  summarize(
    n = n(),
    mean = num(mean(intensity), digits = 0),
    cv = num(sd(intensity)/mean(intensity), digits = 2)
  ) %>% 
  arrange(desc(cv))
`summarise()` has grouped output by 'date', 'plasmid', 'host'. You can override using the `.groups` argument.
dat2 %>% 
  filter(plasmid != "NA", parameter != "FSC.H") %>% 
  ggplot(aes(x = cv)) + geom_histogram(aes(y = ..density../20)) + stat_ecdf() + 
  geom_hline(yintercept = 0.8, linetype = 2) +
  ylab("cumulative density") + 
  facet_wrap(~parameter) +
  theme_cowplot()
`stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

The histogram’s y-axis is not shown. The line graph represents the empirical CDF, and the dotted horizontal line is at 80%. GFP is more variable than RFP, likely because the absolute values of the former is lower. For both, ~80% of the samples have a CV < 20%.

Do the same for the cell size-normalized values

ndat2 <- dat1 %>% 
  select(-BL1.H, -YL2.H) %>% 
  pivot_longer(nGFP:nRFP, names_to = "parameter", values_to = "intensity") %>% 
  group_by(date, plasmid, host, parameter) %>% 
  summarize(
    n = n(),
    mean = num(mean(intensity), digits = 0),
    cv = num(sd(intensity)/mean(intensity), digits = 2)
  ) %>% 
  arrange(desc(cv))
`summarise()` has grouped output by 'date', 'plasmid', 'host'. You can override using the `.groups` argument.
ndat2 %>% 
  filter(plasmid != "NA", parameter != "FSC.H") %>% 
  ggplot(aes(x = cv)) + geom_histogram(aes(y = ..density../20)) + stat_ecdf() + 
  geom_hline(yintercept = 0.8, linetype = 2) +
  ylab("cumulative density") + 
  facet_wrap(~parameter) +
  theme_cowplot()
`stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

CV is smaller on the cell size normalized fluorescence values.

Main

Plotting functions

Here I’d like to develop a series of plotting functions that take the names or any part of the Pho4 chimera annotation as input and plot their results in a variety of ways Load the Pho4 plasmid information and merge with the reshaped data

meta <- read_tsv("../data/20220330-chimera-Pho4-makeup.tsv", col_types = "cccc")

Plotting function

# extract ximera names
refs <- c("CCCCC","SSSSS")
ximeras <- setdiff(meta$symbol, refs)
# make a test set
test <- sample(ximeras, 6)
# subset data
tmp <- meta %>% 
  filter(symbol %in% c(refs,test)) %>% 
  inner_join(dat1, by = "plasmid") %>% 
  mutate(symbol = factor(symbol, levels = c(refs, test)))

Plot individual components

tmp %>% 
  pivot_longer(nGFP:nRFP, names_to = "parameter", values_to = "intensity") %>% 
  mutate(parameter = ordered(parameter, levels = c("nRFP", "nGFP"))) %>% 
  #pivot_longer(BL1.H:YL2.H, names_to = "parameter", values_to = "intensity") %>% 
  #mutate(parameter = ordered(parameter, levels = c("YL2.H", "BL1.H"))) %>% 
  ggplot(aes(x = symbol, y = intensity, group = host)) + 
  geom_bar(aes(fill = host), width = 0.5, alpha = 0.8,
           stat = "summary", fun = "mean", position = position_dodge(0.5)) +
  geom_point(aes(color = host), size = 1,
             position = position_jitterdodge(dodge.width = 0.5)) + 
  scale_color_manual(values = c("PHO2" = "gray20", "pho2∆" = "gray40")) +
  scale_fill_viridis_d(begin = 0.2, end = 0.6) +
  #stat_summary(fun = "mean", geom = "crossbar", color = "red", width = 0.25,
  #             position = position_dodge(0.75), ) +
  facet_wrap(~parameter, scale = "free_y", ncol = 1) +
  xlab("Pho4 chimera") + expand_limits(y = 0) +
  theme_gray(base_size = 14) +
  theme(axis.text.x = element_text(angle = 30, hjust = 1, family = "mono"))

tmp %>% 
  #mutate(`YL2.H/BL1.H` = YL2.H/BL1.H,
  mutate(`R/G` = nRFP/nGFP) %>% 
  ggplot(aes(x = symbol, y = `R/G`, group = host)) + 
  geom_bar(aes(fill = host), width = 0.5, alpha = 0.8,
           stat = "summary", fun = "mean", position = position_dodge(0.5)) +
  geom_point(aes(color = host), position = position_jitterdodge(dodge.width = 0.5)) + 
  scale_color_manual(values = c("PHO2" = "gray20", "pho2∆" = "gray40")) +
  scale_fill_viridis_d(begin = 0.2, end = 0.6) +
  #stat_summary(fun = "mean", color = "red", geom = "crossbar", width = 0.2,
  #             position = position_dodge(0.75), ) +
  theme_gray(base_size = 14) + xlab("Pho4 chimera") +
  theme(axis.text.x = element_text(angle = 30, hjust = 1, family = "mono"))

Next idea is to plot the ratio between the mean R/G for PHO2 and mean R/G for pho2∆, over the mean R/G for PHO2

First summarize the data.

datsum <- dat1 %>%
  filter(plasmid != "NA") %>% 
  mutate(`R/G` = YL2.H/BL1.H, `nR/G` = nRFP/nGFP) %>% 
  group_by(date, plasmid, host) %>% 
  summarize(across(c(BL1.H, nGFP, YL2.H, nRFP, `R/G`, `nR/G`), mean), .groups = "drop") %>% 
  pivot_wider(names_from = host, values_from = BL1.H:`nR/G`) %>% 
  mutate(`pho2∆/PHO2` = `R/G_pho2∆`/`R/G_PHO2`,
         `n.pho2∆/PHO2` = `nR/G_pho2∆`/`nR/G_PHO2`)

# useful to set a flag for low activity mutants
low.act.th <- datsum %>% filter(plasmid == "194") %>% summarize(across(.cols = where(is.numeric), .fns = mean)) %>% unlist()

Then extract the subset for plotting

tmpsum <- meta %>% 
  filter(symbol %in% c(refs,test)) %>% 
  inner_join(datsum, by = "plasmid") %>% 
  mutate(symbol = factor(symbol, levels = c(refs, test)))

Design the plot

tmpsum %>% 
  mutate(Activity = ifelse(`R/G_PHO2` < 2*low.act.th["R/G_pho2∆"], "low", "pass")) %>% 
  ggplot(aes(x = symbol, y = `pho2∆/PHO2`)) + 
  geom_col(aes(group = date, fill = `R/G_PHO2`), width = 0.75, color = "gray50",
           position = position_dodge(0.9)) +
  #geom_point(aes(color = host), position = position_jitterdodge(dodge.width = 0.5)) + 
  scale_fill_gradient2("Activity") +
  #scale_color_manual(values = c(alpha("black",0), "red3")) +
  #stat_summary(fun = "mean", color = "red", geom = "crossbar", width = 0.2,
  #             position = position_dodge(0.75), ) +
  facet_grid(.~Activity, scales = "free_x", space = "free_x", labeller = "label_both") +
  theme_bw(base_size = 14) + xlab("Pho4 chimera") +
  theme(axis.text.x = element_text(angle = 30, hjust = 1, family = "mono"))

X-Y plot

p3 <- tmpsum %>% 
  mutate(`nR/G_PHO2` = signif(`nR/G_PHO2`, digits = 2),
         `nR/G_pho2∆` = signif(`nR/G_pho2∆`, digits = 2)) %>% 
  ggplot(aes(x = `nR/G_PHO2`, y = `nR/G_pho2∆`, label = symbol)) + 
  geom_point(size = 2.5) + geom_abline(slope = 1) +
  theme_gray(base_size = 14)
ggplotly(p3, tooltip = c("label", "x", "y"))
Warning: `gather_()` was deprecated in tidyr 1.2.0.
Please use `gather()` instead.
This warning is displayed once every 8 hours.
Call `lifecycle::last_lifecycle_warnings()` to see where this warning was generated.
LS0tCnRpdGxlOiAiRTAxMyBQaG80IGNoaW1lcmEgYWN0aXZpdHkgYW5hbHlzaXMsIG5ldyBob3N0IgphdXRob3I6IEJpbiBIZQpkYXRlOiAiMjAyMi0wMy0zMCB1cGRhdGVkIGByIFN5cy5EYXRlKClgIgpvdXRwdXQ6IAogIGh0bWxfbm90ZWJvb2s6CiAgICB0b2M6IHRydWUKICAgIHRvY19mbG9hdDogdHJ1ZQogICAgY29kZV9mb2xkaW5nOiBoaWRlCi0tLQoKYGBge3J9CnN1cHByZXNzUGFja2FnZVN0YXJ0dXBNZXNzYWdlcyhyZXF1aXJlKHBsb3RseSkpCnN1cHByZXNzUGFja2FnZVN0YXJ0dXBNZXNzYWdlcyhyZXF1aXJlKHRpZHl2ZXJzZSkpCnN1cHByZXNzUGFja2FnZVN0YXJ0dXBNZXNzYWdlcyhyZXF1aXJlKGNvd3Bsb3QpKQpgYGAKCiMgR29hbAotIEFuYWx5emUgdGhlIGxhdGVzdCBiYXRjaGVzIG9mIGZsb3cgY3l0b21ldHJ5IGRhdGEgdG8gZGV0ZXJtaW5lIHRoZSBjb250cmlidXRpb24gb2YgZGlmZmVyZW50IChtYXRjaGluZykgcGFydHMgb2YgU2NQaG80IGFuZCBDZ1BobzQgdG8gdGhlaXIgZGlmZmVyZW5jZSBpbiBQaG8yIGRlcGVuZGVuY2UuIAotIERldmVsb3AgYW4gYW5hbHlzaXMgcGlwZWxpbmUgdG8gcGVyZm9ybSBRQywgY29ycmVjdGlvbiAoaWYgbmVlZGVkKSBhbmQgcGxvdHRpbmcgdGhlIHJlc3VsdHMuCgojIERhdGEKTWVyZ2UgdGhlIHRocmVlIGJhdGNoZXMKYGBge3J9CnJhdyA8LSBsaXN0KAogICIwMy8xNS8yMiIgPSByZWFkX3RzdigiLi4vZGF0YS8yMDIyMDMxNS1FTy1jaGltZXJhLWJhdGNoLTEvMjAyMjAzMjktZ2F0ZWQtbWVkaWFuLW91dC50eHQiLCBzaG93X2NvbF90eXBlcyA9IEZBTFNFKSwKICAiMDMvMjIvMjIiID0gcmVhZF90c3YoIi4uL2RhdGEvMjAyMjAzMjItRU8tY2hpbWVyYS1iYXRjaC0yLzIwMjIwMzI3LWdhdGVkLW1lZGlhbi1vdXQudHh0Iiwgc2hvd19jb2xfdHlwZXMgPSBGQUxTRSksCiAgIjAzLzI5LzIyIiA9IHJlYWRfdHN2KCIuLi9kYXRhLzIwMjIwMzI5LUVPLWNoaW1lcmEtYmF0Y2gtMy8yMDIyMDMzMC1nYXRlZC1tZWRpYW4tb3V0LnR4dCIsIHNob3dfY29sX3R5cGVzID0gRkFMU0UpCikKZGF0IDwtIGJpbmRfcm93cyhyYXcsIC5pZCA9ICJkYXRlIikgJT4lIAogICNzZXBhcmF0ZShjb2wgPSB3ZWxsLCBzZXAgPSAxLCBpbnRvID0gYygicm93IiwgImNvbCIpKSAlPiUgCiAgc2VwYXJhdGUoY29sID0gc2FtcGxlLCBzZXAgPSAiLSIsIGludG8gPSBjKCJwbGFzbWlkIiwgImhvc3QiLCAicmVwIikpCmBgYAoKCiMgUUMKIyMgMS4gTnVtYmVyIG9mIGV2ZW50cyBwZXIgc2FtcGxlCmBgYHtyfQpkYXQgJT4lIGdncGxvdChhZXMoeCA9IG5faW5kdWN0aW9uKSkgKyBnZW9tX2hpc3RvZ3JhbShiaW5zID0gMzAsIGZpbGwgPSAiZm9yZXN0Z3JlZW4iKSArIGZhY2V0X2dyaWQoaG9zdH5kYXRlKSArIHRoZW1lX2J3KGJhc2Vfc2l6ZSA9IDE0KSAjKyBwYW5lbF9ib3JkZXIoKQpgYGAKPiBNYWpvcml0eSBvZiB0aGUgZXhwZXJpbWVudGFsIHdlbGxzIChub3QgYmxhbmspIGhhdmUgPiAxMDAwMCBldmVudHMsIHdpdGggdGhlIGV4Y2VwdGlvbiBvZiBhIGZldyBzYW1wbGVzCgpgYGB7cn0KZGF0ICU+JSBmaWx0ZXIoaG9zdCAhPSAiYmxhbmsiLCBuX2luZHVjdGlvbiA8IDEwMDAwKQpgYGAKR2l2ZW4gdGhlIGRpc3RyaWJ1dGlvbiBvZiBldmVudCBjb3VudHMsIHdlIHdpbGwgbGFiZWwgYWxsIHNhbXBsZXMgd2l0aCA8IDcwMDAgY291bnRzLCB3aGljaCB3b3VsZCBleGNsdWRlIDIgc2FtcGxlcy4gVGhlIHJhdGlvbmFsZSBpcyB0aGF0IGV2ZW4gdGhvdWdoIHNvbWUgc2FtcGxlcyBoYWQgYmV0d2VlbiA3MDAwIC0gMTAwMDAgZXZlbnRzLCB0aGVpciBtZWFuIGZsdW9yZXNjZW5jZSB2YWx1ZXMgYXBwZWFyIGluIGxpbmUgd2l0aCB0aGUgb3RoZXIgcmVwbGljYXRlcyBvZiB0aGUgc2FtZSBnZW5vdHlwZS4gSSBhbHNvIHJlbW92ZWQgdGhlIGJsYW5rIHdlbGxzIGFuZCBhZGQgYSBmbGFnIHRvIG1hcmsgdGhlIHBsYXNtaWRzIHRoYXQgRW1pbHkgcmVtYWRlIGJlY2F1c2UgdGhlIG9yaWdpbmFsIG9uZSAodXNlZCBmb3IgdGhpcyBiYXRjaCBvZiBkYXRhKSBjb250YWlucyBub25zeW5vbnltb3VzIG11dGF0aW9ucy4KYGBge3J9CmV2ZW50LnRoIDwtIDcwMDAKCmxpc3QubXV0YXRpb25zIDwtIGFzLmNoYXJhY3RlcihjKDIxNSwgMjA5LCAyMTEsIDIxMCwgMjE3LCAyMTgsIDIyMCwgMjIxLCAyMjMsIDIyNCwgMjIyLCAyMjcsIDIzMCwgMjI5LCAyMzEsIDIzMywgMjQwLCAyNDEsIDI1MDoyNTUsIDI1NywgMjU4LCAyNjUsIDI2NikpCgpleHB0IDwtIGRhdCAlPiUgCiAgZmlsdGVyKGhvc3QgIT0gImJsYW5rIikgJT4lIAogIGdyb3VwX2J5KGRhdGUsIHBsYXNtaWQsIGhvc3QpICU+JSAKICBzdW1tYXJpemUobiA9IG4oKSwgbl9maWx0ZXIgPSBzdW0obl9pbmR1Y3Rpb24gPj0gZXZlbnQudGgpLCAuZ3JvdXBzID0gImRyb3AiKSAlPiUgCiAgbXV0YXRlKGZsYWcgPSBpZmVsc2UocGxhc21pZCAlaW4lIGxpc3QubXV0YXRpb25zLCAibXV0YXRpb24iLCAiIikpCmBgYAoKYGBge3J9CmRhdCA8LSBkYXQgJT4lIAogIG11dGF0ZSgKICAgIGxvd19ldmVudF9jb3VudCA9IG5faW5kdWN0aW9uIDwgZXZlbnQudGgsCiAgICBtdXRhdGlvbiA9IGlmZWxzZShwbGFzbWlkICVpbiUgbGlzdC5tdXRhdGlvbnMsIFRSVUUsIEZBTFNFKQogICkgJT4lIAogIGZpbHRlcihob3N0ICE9ICJibGFuayIpCmBgYAoKSG93IG1hbnkgcGxhc21pZC1ob3N0IGNvbWJpbmF0aW9uIGhhdmUgPCAzIHJlcGxpY2F0ZXMgYWZ0ZXIgcmVtb3Zpbmcgc2FtcGxlcyB3aXRoIGxvdyBldmVudCBjb3VudHM/CmBgYHtyfQpkYXQgJT4lIGZpbHRlcighbG93X2V2ZW50X2NvdW50KSAlPiUgCiAgY291bnQocGxhc21pZCwgaG9zdCkgJT4lIAogIGZpbHRlcihuIDwgMykKYGBgCgoKIyMgMi4gQmFja2dyb3VuZCBzdWJ0cmFjdGlvbgoKQ2hlY2sgdGhlIGJhY2tncm91bmQgZmx1b3Jlc2NlbmNlIGxldmVscyBhY3Jvc3MgZGlmZmVyZW50IGRheXMuCmBgYHtyfQpkYXQgJT4lIGZpbHRlcihob3N0ID09ICIxNTYiKSAlPiUgCiAgcGl2b3RfbG9uZ2VyKEJMMS5IOm5SRlAsIG5hbWVzX3RvID0gInBhcmFtZXRlciIsIHZhbHVlc190byA9ICJpbnRlbnNpdHkiKSAlPiUgCiAgbXV0YXRlKHBhcmFtZXRlciA9IG9yZGVyZWQocGFyYW1ldGVyLCBsZXZlbHMgPSBjKCJCTDEuSCIsICJZTDIuSCIsICJuR0ZQIiwgIm5SRlAiKSkpICU+JSAKICBnZ3Bsb3QoYWVzKHggPSBkYXRlLCB5ID0gaW50ZW5zaXR5KSkgKyBnZW9tX3BvaW50KGFlcyhzaGFwZSA9IHdlbGwpKSArCiAgc3RhdF9zdW1tYXJ5KGZ1bi5kYXRhID0gIm1lYW5fc2UiLCBnZW9tID0gInBvaW50cmFuZ2UiLCBjb2xvciA9ICJyZWQiLCBwb3NpdGlvbiA9IHBvc2l0aW9uX251ZGdlKHggPSAwLjEpKSArCiAgZmFjZXRfd3JhcCh+cGFyYW1ldGVyLCBzY2FsZSA9ICJmcmVlX3kiKSArCiAgdGhlbWVfYncoYmFzZV9zaXplID0gMTQpCmBgYAoKPiAwMy8yOS8yMiBEOSBzaG93ZWQgaGlnaGVyIGJhY2tncm91bmQgZmx1b3Jlc2NlbmNlIHRoYW4gdGhlIG90aGVycy4gYWZ0ZXIgY2hlY2tpbmcgdGhlIGdhdGluZyByZXN1bHQsIEkgZm91bmQgdGhhdCBOQS0xNTYtMiBzYW1wbGUgaW4gRDkgaGFkIGEgc2xpZ2h0bHkgd2lkZXIgZGlzdHJpYnV0aW9uLCBhbmQgdGhlIDJkIGNsdXN0ZXIgZ2F0ZSBpcyBhbHNvIHdpZGVyIHRoYW4gdGhlIG90aGVyIHR3byBzYW1wbGVzLiBUaGUgY2VsbCBzaXplIG5vcm1hbGl6ZWQgaW50ZW5zaXR5IGRvZXNuJ3Qgc2hvdyB0aGUgc2FtZSBwYXR0ZXJuLCBzdWdnZXN0aW5nIHRoYXQgYXQgbGVhc3Qgc29tZSBvZiB0aGUgZGlmZmVyZW5jZSBjb3VsZCBiZSBkdWUgdG8gY2VsbCBzaXplIHZhcmlhdGlvbi4gVHJ5IGp1c3QgYXZlcmFnaW5nIHRoZSB0aHJlZSBibGFuayB3ZWxscyBhbmQgcGVyZm9ybSB0aGUgYmFja2dyb3VuZCByZW1vdmFsLgoKU3VidHJhY3QgdGhlIGJhY2tncm91bmQKYGBge3J9CmJnIDwtIGRhdCAlPiUgCiAgZmlsdGVyKGhvc3QgPT0gIjE1NiIpICU+JSAKICBncm91cF9ieShkYXRlKSAlPiUgCiAgc3VtbWFyaXplKGFjcm9zcyhGU0MuSDpuUkZQLCB+IHJvdW5kKG1lYW4oLngpLDEpKSkgJT4lIAogIGNvbHVtbl90b19yb3duYW1lcyh2YXIgPSAiZGF0ZSIpCgpkYXQxIDwtIGRhdCAlPiUgCiAgc2VsZWN0KGRhdGU6aG9zdCwgRlNDLkg6blJGUCwgbG93X2V2ZW50X2NvdW50LCBtdXRhdGlvbikgJT4lCiAgbXV0YXRlKAogICAgQkwxLkggPSBCTDEuSCAtIGJnW2RhdGUsICJCTDEuSCJdLAogICAgWUwyLkggPSBZTDIuSCAtIGJnW2RhdGUsICJZTDIuSCJdLAogICAgbkdGUCA9IG5HRlAgLSBiZ1tkYXRlLCAibkdGUCJdLAogICAgblJGUCA9IG5SRlAgLSBiZ1tkYXRlLCAiblJGUCJdLAogICkKYGBgCgpEb3VibGUgY2hlY2sgdGhhdCB0aGUgc3VidHJhY3Rpb24gd29ya2VkIGNvcnJlY3RseQpgYGB7cn0KZGF0MSAlPiUgZmlsdGVyKGhvc3QgPT0gIjE1NiIpICU+JSAKICBwaXZvdF9sb25nZXIoQkwxLkg6blJGUCwgbmFtZXNfdG8gPSAicGFyYW1ldGVyIiwgdmFsdWVzX3RvID0gImludGVuc2l0eSIpICU+JSAKICBtdXRhdGUocGFyYW1ldGVyID0gb3JkZXJlZChwYXJhbWV0ZXIsIGxldmVscyA9IGMoIkJMMS5IIiwgIllMMi5IIiwgIm5HRlAiLCAiblJGUCIpKSkgJT4lIAogIGdncGxvdChhZXMoeCA9IGRhdGUsIHkgPSBpbnRlbnNpdHkpKSArIGdlb21fcG9pbnQoYWVzKHNoYXBlID0gd2VsbCkpICsKICBzdGF0X3N1bW1hcnkoZnVuLmRhdGEgPSAibWVhbl9zZSIsIGdlb20gPSAicG9pbnRyYW5nZSIsIGNvbG9yID0gInJlZCIsIHBvc2l0aW9uID0gcG9zaXRpb25fbnVkZ2UoeCA9IDAuMSkpICsKICBmYWNldF93cmFwKH5wYXJhbWV0ZXIsIHNjYWxlID0gImZyZWVfeSIpICsKICB0aGVtZV9idyhiYXNlX3NpemUgPSAxNCkKCiMgcmVtb3ZlIHRoZSB5SDE1NiBzYW1wbGVzCmRhdDEgPC0gZGF0MSAlPiUgCiAgZmlsdGVyKGhvc3QgIT0gIjE1NiIpICU+JSAKICBtdXRhdGUoaG9zdCA9IG9yZGVyZWQoaG9zdCwgbGV2ZWxzID0gYygiNTU1IiwgIjM3MyIpLCBsYWJlbHMgPSBjKCJQSE8yIiwgInBobzLiiIYiKSkpCmBgYAoKRXhwb3J0IHRoZSBiYWNrZ3JvdW5kLXN1YnRyYWN0ZWQgdmFsdWVzIGZvciBsYXRlciB1c2Ugd2l0aCB0aGUgc2hpbnlhcHAKYGBge3J9CndyaXRlX3RzdihkYXQxLCAiLi4vb3V0cHV0LzIwMjIwNjE0LW1hcjIyLWJhdGNoLWJnLXN1Yi1kYXRhLnRzdiIpCmBgYAoKCiMjIDMuIENvbnNpc3RlbmN5IGFjcm9zcyBwbGF0ZXMuCgpDaGVjayB0aGUgYmFja2dyb3VuZC1zdWJ0cmFjdGVkIGZsdW9yZXNjZW5jZSB2YWx1ZXMgZm9yIHRoZSBob3N0IHN0cmFpbnMgKHlIMzczIGFuZCB5SDU1NSkgYXMgd2VsbCBhcyB0aGUgcG9zaXRpdmUgY29udHJvbCBzdHJhaW4gKHBIMTg4IGluIHlIMzczIG9yIHlINTU1IGJhY2tncm91bmRzKSwgYm90aCBvZiB3aGljaCBhcmUgcHJlc2VudCBvbiBlYWNoIHBsYXRlLgoKYGBge3J9CmRhdDEgJT4lIGZpbHRlcihwbGFzbWlkICVpbiUgYygiMTg4IiwgIk5BIikpICU+JSAKICBwaXZvdF9sb25nZXIoQkwxLkg6WUwyLkgsIG5hbWVzX3RvID0gInBhcmFtZXRlciIsIHZhbHVlc190byA9ICJpbnRlbnNpdHkiKSAlPiUgCiAgI211dGF0ZShwYXJhbWV0ZXIgPSBvcmRlcmVkKHBhcmFtZXRlciwgbGV2ZWxzID0gYygiQkwxLkgiLCAiWUwyLkgiLCAibkdGUCIsICJuUkZQIikpKSAlPiUgCiAgZ2dwbG90KGFlcyh4ID0gcGxhc21pZCwgeSA9IGludGVuc2l0eSwgY29sb3IgPSBkYXRlKSkgKyAKICBnZW9tX3BvaW50KHBvc2l0aW9uID0gcG9zaXRpb25fZG9kZ2UoMC41KSkgKyAKICBzY2FsZV9jb2xvcl9icmV3ZXIodHlwZSA9ICJxdWFsIiwgcGFsZXR0ZSA9IDIpICsKICBmYWNldF9ncmlkKHBhcmFtZXRlcn5ob3N0LCBzY2FsZSA9ICJmcmVlX3kiKSArCiAgdGhlbWVfYncoYmFzZV9zaXplID0gMTQpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChmYWNlID0gMykpCmBgYAoKPiAxLiBUaGUgaG9zdCBzdHJhaW4gZmx1b3Jlc2NlbmNlIGxldmVscyBhcHBlYXIgdG8gYmUgY29uc2lzdGVudCBhY3Jvc3MgdGhlIHRocmVlIGRheXMuIEFkZGl0aW9uYWxseSwgdGhlaXIgR0ZQIGxldmVscyBhcmUgemVybywgYXMgZXhwZWN0ZWQuCj4gMS4gVGhlIHBvc2l0aXZlIGNvbnRyb2wgc3RyYWluIGhhcyBzdHJvbmcgQkwxLkggYW5kIGNvcnJlc3BvbmRpbmdseSBoYXMgc3Ryb25nIFlMMi5ILgoKVGhlIHNpemUgbm9ybWFsaXplZCB2YWx1ZXMgc2hvdyBzbGlnaHRseSBsb3dlciB2YXJpYW5jZToKYGBge3J9CmRhdDEgJT4lIGZpbHRlcihwbGFzbWlkICVpbiUgYygiMTg4IiwgIk5BIikpICU+JSAKICBwaXZvdF9sb25nZXIobkdGUDpuUkZQLCBuYW1lc190byA9ICJwYXJhbWV0ZXIiLCB2YWx1ZXNfdG8gPSAiaW50ZW5zaXR5IikgJT4lIAogICNtdXRhdGUocGFyYW1ldGVyID0gb3JkZXJlZChwYXJhbWV0ZXIsIGxldmVscyA9IGMoIkJMMS5IIiwgIllMMi5IIiwgIm5HRlAiLCAiblJGUCIpKSkgJT4lIAogIGdncGxvdChhZXMoeCA9IHBsYXNtaWQsIHkgPSBpbnRlbnNpdHksIGNvbG9yID0gZGF0ZSkpICsgCiAgZ2VvbV9wb2ludChwb3NpdGlvbiA9IHBvc2l0aW9uX2RvZGdlKDAuNSkpICsgCiAgc2NhbGVfY29sb3JfYnJld2VyKHR5cGUgPSAicXVhbCIsIHBhbGV0dGUgPSAyKSArCiAgZmFjZXRfZ3JpZChwYXJhbWV0ZXJ+aG9zdCwgc2NhbGUgPSAiZnJlZV95IikgKwogIHRoZW1lX2J3KGJhc2Vfc2l6ZSA9IDE0KSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoZmFjZSA9IDMpKQpgYGAKIyMgNC4gV2VsbCBwb3NpdGlvbgoKKipfUmF0aW9uYWxlXyoqCgotIEluIG15IG9yaWdpbmFsIHBsYXRlIGRlc2lnbiwgSSBwbGFjZWQgYSBwb3NpdGl2ZSBjb250cm9sIHN0cmFpbiAoZW52aXNpb25lZCBDZ1BobzQtbU5lb24gaW4gX1BITzJfIGJhY2tncm91bmQgZm9yIGV4YW1wbGUpIGluIHRoZSBjb250cm9sIGNvbHVtbnMgKDEsIDUsIDkpIG9uIGV2ZXJ5IG90aGVyIHJvdyAoQSwgQywgRSwgRykuIFRoZSByZWFzb24gZm9yIHRoaXMgaXMgdG8gdXNlIGl0IHRvIGlkZW50aWZ5IGFueSB3ZWxsIHBvc2l0aW9uIGVmZmVjdCBvbiB0aGUgZmx1b3Jlc2NlbmNlIHJlYWRpbmdzLgotIEluIEVtaWx5J3MgaW1wbGVtZW50YXRpb24sIHNoZSBpbnN0ZWFkIHB1dCAqKmEgcGFpcioqIG9mIHN0cmFpbnMsIG5hbWVseSBwSDE4OC15SDMyMyBhbmQgcEgxODgteUg1NTUgaW4gdGhlc2Ugd2VsbHMuIFRoaXMgbWVhbnMgSSBjYW5ub3QgdXNlIHRoZSBwb3NpdGl2ZSBjb250cm9sIHdlbGxzIGV4YWN0bHkgdGhlIHdheSBhcyBJIGRlc2lnbmVkIHRoZW0uIEJ1dCBJIGNhbiBzdGlsbCB1c2UgdGhlbSB0byBzcG90IGFueSB0cmVuZC4KICAgIApgYGB7cn0KZGF0MSAlPiUgCiAgZmlsdGVyKHBsYXNtaWQgPT0gIjE4OCIpICU+JSAKICBzZXBhcmF0ZSh3ZWxsLCBpbnRvID0gYygicm93IiwgImNvbCIpLCBzZXAgPSAxKSAlPiUgCiAgcGl2b3RfbG9uZ2VyKEJMMS5IOllMMi5ILCBuYW1lc190byA9ICJwYXJhbWV0ZXIiLCB2YWx1ZXNfdG8gPSAiaW50ZW5zaXR5IikgJT4lIAogIGdncGxvdChhZXMoeCA9IHJvdywgeSA9IGludGVuc2l0eSwgZ3JvdXAgPSBkYXRlKSkgKyAKICBnZW9tX3BvaW50KGFlcyhjb2xvciA9IGRhdGUpLCBzaXplID0gMSkgKyAKICAjZ2VvbV9saW5lKGFlcyhjb2xvciA9IGRhdGUpLCBzaXplID0gMC40KSArCiAgc3RhdF9zdW1tYXJ5KGFlcyhjb2xvciA9IGRhdGUpLCBnZW9tID0gImxpbmUiLCBmdW4gPSBtZWFuKSArCiAgc2NhbGVfY29sb3JfYnJld2VyKHR5cGUgPSAicXVhbCIsIHBhbGV0dGUgPSAyKSArCiAgZmFjZXRfZ3JpZChwYXJhbWV0ZXJ+aG9zdCwgc2NhbGVzID0gImZyZWVfeSIpICsKICB0aGVtZV9idyhiYXNlX3NpemUgPSAxNCkKYGBgCgo+IC0gbm8gb2Jpdm91cyB0cmVuZCBiZXR3ZWVuIHRoZSByb3dzIChvciBjb2x1bW5zLCBub3Qgc2hvd24pCj4gLSBhIGNsZWFyIGNvcnJlbGF0aW9uIGJldHdlZW4gWUwyLkggYW5kIEJMMS5ILiBhdCBsZWFzdCBwYXJ0IG9mIHRoaXMgaXMgZHVlIHRvIHRoZSBjZWxsIHNpemUgZGlmZmVyZW5jZXMgLS0gc2VlIHNpemUgbm9ybWFsaXplZCBkYXRhIGJlbG93OgoKYGBge3J9CmRhdDEgJT4lIAogIGZpbHRlcihwbGFzbWlkID09ICIxODgiKSAlPiUgCiAgc2VwYXJhdGUod2VsbCwgaW50byA9IGMoInJvdyIsICJjb2wiKSwgc2VwID0gMSkgJT4lIAogIHBpdm90X2xvbmdlcihjKFlMMi5ILCBuUkZQKSwgbmFtZXNfdG8gPSAicGFyYW1ldGVyIiwgdmFsdWVzX3RvID0gImludGVuc2l0eSIpICU+JSAKICBnZ3Bsb3QoYWVzKHggPSByb3csIHkgPSBpbnRlbnNpdHksIGdyb3VwID0gZGF0ZSkpICsgCiAgZ2VvbV9wb2ludChhZXMoY29sb3IgPSBkYXRlKSwgc2l6ZSA9IDEpICsgCiAgI2dlb21fbGluZShhZXMoY29sb3IgPSBkYXRlKSwgc2l6ZSA9IDAuNCkgKwogIHN0YXRfc3VtbWFyeShhZXMoY29sb3IgPSBkYXRlKSwgZ2VvbSA9ICJsaW5lIiwgZnVuID0gbWVhbikgKwogIHNjYWxlX2NvbG9yX2JyZXdlcih0eXBlID0gInF1YWwiLCBwYWxldHRlID0gMikgKwogIGZhY2V0X2dyaWQocGFyYW1ldGVyfmhvc3QsIHNjYWxlcyA9ICJmcmVlX3kiKSArCiAgdGhlbWVfYncoYmFzZV9zaXplID0gMTQpCmBgYAoKLSBXaGF0IHdlIGNhcmUgYWJvdXQgaXMgdGhlIHJhdGlvIGJldHdlZW4gUGhvNC1tTmVvbiBhbmQgX1BITzVwcl8tbUNoZXJyeQoKYGBge3J9CmRhdDEgJT4lIAogIGZpbHRlcihwbGFzbWlkID09ICIxODgiKSAlPiUgCiAgc2VwYXJhdGUod2VsbCwgaW50byA9IGMoInJvdyIsICJjb2wiKSwgc2VwID0gMSkgJT4lIAogIGdncGxvdChhZXMoeCA9IEJMMS5ILCB5ID0gWUwyLkgpKSArIAogIGdlb21fcG9pbnQoYWVzKHNoYXBlID0gZGF0ZSwgY29sb3IgPSByb3cpLCBzaXplID0gMykgKyAKICBzdGF0X3Ntb290aChtZXRob2QgPSAibG0iLCBzZSA9IEZBTFNFLCBjb2xvciA9ICJncmF5NTAiLCBzaXplID0gMC41KSArCiAgc2NhbGVfY29sb3JfdmlyaWRpc19kKCkgKwogIHNjYWxlX3NoYXBlX21hbnVhbCh2YWx1ZXMgPSAxNToxNykgKwogICNzY2FsZV94X2xvZzEwKCkgKyBzY2FsZV95X2xvZzEwKCkgKwogICNmYWNldF9ncmlkKHBhcmFtZXRlcn5ob3N0LCBzY2FsZXMgPSAiZnJlZV95IikgKwogIHRoZW1lX2J3KGJhc2Vfc2l6ZSA9IDE0KQpgYGAKCj4gLSBUaGVyZSBpcyB2YXJpYXRpb24gaW4gQkwxLkggYW5kIGNvcnJlc3BvbmRpbmdseSBpbiBZTDIuSCwgYnV0IHRoZSByYXRpbyBiZXR3ZWVuIHRoZSB0d28gYXJlIHZlcnkgY29uc2lzdGVudAoKTGV0J3MgY2hlY2sgYm90aCBTY1BobzQgKHBIMTk0KSBhbmQgQ2dQaG80IChwSDE4OCkgdG8gc2VlIGlmIHRoZWlyIGJlaGF2aW9ycyBhcmUgY29uc2lzdGVudCBhY3Jvc3MgdGhlIGRheXMuCmBgYHtyfQpkYXQxICU+JSAKICBmaWx0ZXIocGxhc21pZCAlaW4lIGMoIjE4OCIsICIxOTQiKSkgJT4lIAogIG11dGF0ZShgWUwyLkgvQkwxLkhgID0gWUwyLkgvQkwxLkgsCiAgI211dGF0ZShgWUwyLkgvQkwxLkhgID0gblJGUC9uR0ZQLAogICAgICAgICBQaG80ID0gZmFjdG9yKHBsYXNtaWQsIGxldmVscyA9IGMoIjE5NCIsICIxODgiKSwgCiAgICAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gYygiU2NQaG80IiwgIkNnUGhvNCIpKSkgJT4lIAogIHNlcGFyYXRlKHdlbGwsIGludG8gPSBjKCJyb3ciLCAiY29sIiksIHNlcCA9IDEpICU+JSAKICBnZ3Bsb3QoYWVzKHggPSBob3N0LCB5ID0gYFlMMi5IL0JMMS5IYCkpICsgCiAgZ2VvbV9wb2ludChjb2xvciA9IGFscGhhKCJncmF5NTAiLCAwLjkpLCBwb3NpdGlvbiA9IHBvc2l0aW9uX2ppdHRlcigwLjIpKSArIAogIHN0YXRfc3VtbWFyeShmdW4uZGF0YSA9ICJtZWFuX3NlIiwgZ2VvbSA9ICJwb2ludHJhbmdlIiwgY29sb3IgPSAicmVkIikgKwogIGZhY2V0X2dyaWQoUGhvNH5kYXRlKSArCiAgdGhlbWVfYncoYmFzZV9zaXplID0gMTQpCmBgYAoKIyMgNS4gSGlnaCB2YXJpYW5jZSBzYW1wbGVzCgpTdW1tYXJpemUgdGhlIGJhY2tncm91bmQgc3VidHJhY3RlZCBkYXRhIGJ5IGNhbGN1bGF0aW5nIHRoZSBtZWFucyBhbmQgY3YgZm9yIGVhY2ggc3RyYWluLgpgYGB7cn0KZGF0MiA8LSBkYXQxICU+JSAKICBzZWxlY3QoLW5HRlAsIC1uUkZQKSAlPiUgCiAgcGl2b3RfbG9uZ2VyKEZTQy5IOllMMi5ILCBuYW1lc190byA9ICJwYXJhbWV0ZXIiLCB2YWx1ZXNfdG8gPSAiaW50ZW5zaXR5IikgJT4lIAogIGdyb3VwX2J5KGRhdGUsIHBsYXNtaWQsIGhvc3QsIHBhcmFtZXRlcikgJT4lIAogIHN1bW1hcml6ZSgKICAgIG4gPSBuKCksCiAgICBtZWFuID0gbnVtKG1lYW4oaW50ZW5zaXR5KSwgZGlnaXRzID0gMCksCiAgICBjdiA9IG51bShzZChpbnRlbnNpdHkpL21lYW4oaW50ZW5zaXR5KSwgZGlnaXRzID0gMikKICApICU+JSAKICBhcnJhbmdlKGRlc2MoY3YpKQpgYGAKCmBgYHtyfQpkYXQyICU+JSAKICBmaWx0ZXIocGxhc21pZCAhPSAiTkEiLCBwYXJhbWV0ZXIgIT0gIkZTQy5IIikgJT4lIAogIGdncGxvdChhZXMoeCA9IGN2KSkgKyBnZW9tX2hpc3RvZ3JhbShhZXMoeSA9IC4uZGVuc2l0eS4uLzIwKSkgKyBzdGF0X2VjZGYoKSArIAogIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDAuOCwgbGluZXR5cGUgPSAyKSArCiAgeWxhYigiY3VtdWxhdGl2ZSBkZW5zaXR5IikgKyAKICBmYWNldF93cmFwKH5wYXJhbWV0ZXIpICsKICB0aGVtZV9jb3dwbG90KCkKYGBgCgo+IFRoZSBoaXN0b2dyYW0ncyB5LWF4aXMgaXMgbm90IHNob3duLiBUaGUgbGluZSBncmFwaCByZXByZXNlbnRzIHRoZSBlbXBpcmljYWwgQ0RGLCBhbmQgdGhlIGRvdHRlZCBob3Jpem9udGFsIGxpbmUgaXMgYXQgODAlLiBHRlAgaXMgbW9yZSB2YXJpYWJsZSB0aGFuIFJGUCwgbGlrZWx5IGJlY2F1c2UgdGhlIGFic29sdXRlIHZhbHVlcyBvZiB0aGUgZm9ybWVyIGlzIGxvd2VyLiBGb3IgYm90aCwgfjgwJSBvZiB0aGUgc2FtcGxlcyBoYXZlIGEgQ1YgPCAyMCUuCgpEbyB0aGUgc2FtZSBmb3IgdGhlIGNlbGwgc2l6ZS1ub3JtYWxpemVkIHZhbHVlcwpgYGB7cn0KbmRhdDIgPC0gZGF0MSAlPiUgCiAgc2VsZWN0KC1CTDEuSCwgLVlMMi5IKSAlPiUgCiAgcGl2b3RfbG9uZ2VyKG5HRlA6blJGUCwgbmFtZXNfdG8gPSAicGFyYW1ldGVyIiwgdmFsdWVzX3RvID0gImludGVuc2l0eSIpICU+JSAKICBncm91cF9ieShkYXRlLCBwbGFzbWlkLCBob3N0LCBwYXJhbWV0ZXIpICU+JSAKICBzdW1tYXJpemUoCiAgICBuID0gbigpLAogICAgbWVhbiA9IG51bShtZWFuKGludGVuc2l0eSksIGRpZ2l0cyA9IDApLAogICAgY3YgPSBudW0oc2QoaW50ZW5zaXR5KS9tZWFuKGludGVuc2l0eSksIGRpZ2l0cyA9IDIpCiAgKSAlPiUgCiAgYXJyYW5nZShkZXNjKGN2KSkKYGBgCgpgYGB7cn0KbmRhdDIgJT4lIAogIGZpbHRlcihwbGFzbWlkICE9ICJOQSIsIHBhcmFtZXRlciAhPSAiRlNDLkgiKSAlPiUgCiAgZ2dwbG90KGFlcyh4ID0gY3YpKSArIGdlb21faGlzdG9ncmFtKGFlcyh5ID0gLi5kZW5zaXR5Li4vMjApKSArIHN0YXRfZWNkZigpICsgCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMC44LCBsaW5ldHlwZSA9IDIpICsKICB5bGFiKCJjdW11bGF0aXZlIGRlbnNpdHkiKSArIAogIGZhY2V0X3dyYXAofnBhcmFtZXRlcikgKwogIHRoZW1lX2Nvd3Bsb3QoKQpgYGAKCj4gQ1YgaXMgc21hbGxlciBvbiB0aGUgY2VsbCBzaXplIG5vcm1hbGl6ZWQgZmx1b3Jlc2NlbmNlIHZhbHVlcy4KCiMgTWFpbgojIyBQbG90dGluZyBmdW5jdGlvbnMKSGVyZSBJJ2QgbGlrZSB0byBkZXZlbG9wIGEgc2VyaWVzIG9mIHBsb3R0aW5nIGZ1bmN0aW9ucyB0aGF0IHRha2UgdGhlIG5hbWVzIG9yIGFueSBwYXJ0IG9mIHRoZSBQaG80IGNoaW1lcmEgYW5ub3RhdGlvbiBhcyBpbnB1dCBhbmQgcGxvdCB0aGVpciByZXN1bHRzIGluIGEgdmFyaWV0eSBvZiB3YXlzCkxvYWQgdGhlIFBobzQgcGxhc21pZCBpbmZvcm1hdGlvbiBhbmQgbWVyZ2Ugd2l0aCB0aGUgcmVzaGFwZWQgZGF0YQpgYGB7cn0KbWV0YSA8LSByZWFkX3RzdigiLi4vZGF0YS8yMDIyMDYyMS1jaGltZXJhLVBobzQtbWFrZXVwLnRzdiIsIGNvbF90eXBlcyA9ICJjY2NjYyIpCmBgYAoKUGxvdHRpbmcgZnVuY3Rpb24KYGBge3J9CiMgZXh0cmFjdCB4aW1lcmEgbmFtZXMKcmVmcyA8LSBjKCJDQ0NDQyIsIlNTU1NTIikKeGltZXJhcyA8LSBzZXRkaWZmKG1ldGEkc3ltYm9sLCByZWZzKQojIG1ha2UgYSB0ZXN0IHNldAp0ZXN0IDwtIHNhbXBsZSh4aW1lcmFzLCA2KQojIHN1YnNldCBkYXRhCnRtcCA8LSBtZXRhICU+JSAKICBmaWx0ZXIoc3ltYm9sICVpbiUgYyhyZWZzLHRlc3QpKSAlPiUgCiAgaW5uZXJfam9pbihkYXQxLCBieSA9ICJwbGFzbWlkIikgJT4lIAogIG11dGF0ZShzeW1ib2wgPSBmYWN0b3Ioc3ltYm9sLCBsZXZlbHMgPSBjKHJlZnMsIHRlc3QpKSkKYGBgCgpQbG90IGluZGl2aWR1YWwgY29tcG9uZW50cwpgYGB7cn0KdG1wICU+JSAKICBwaXZvdF9sb25nZXIobkdGUDpuUkZQLCBuYW1lc190byA9ICJwYXJhbWV0ZXIiLCB2YWx1ZXNfdG8gPSAiaW50ZW5zaXR5IikgJT4lIAogIG11dGF0ZShwYXJhbWV0ZXIgPSBvcmRlcmVkKHBhcmFtZXRlciwgbGV2ZWxzID0gYygiblJGUCIsICJuR0ZQIikpKSAlPiUgCiAgI3Bpdm90X2xvbmdlcihCTDEuSDpZTDIuSCwgbmFtZXNfdG8gPSAicGFyYW1ldGVyIiwgdmFsdWVzX3RvID0gImludGVuc2l0eSIpICU+JSAKICAjbXV0YXRlKHBhcmFtZXRlciA9IG9yZGVyZWQocGFyYW1ldGVyLCBsZXZlbHMgPSBjKCJZTDIuSCIsICJCTDEuSCIpKSkgJT4lIAogIGdncGxvdChhZXMoeCA9IHN5bWJvbCwgeSA9IGludGVuc2l0eSwgZ3JvdXAgPSBob3N0KSkgKyAKICBnZW9tX2JhcihhZXMoZmlsbCA9IGhvc3QpLCB3aWR0aCA9IDAuNSwgYWxwaGEgPSAwLjgsCiAgICAgICAgICAgc3RhdCA9ICJzdW1tYXJ5IiwgZnVuID0gIm1lYW4iLCBwb3NpdGlvbiA9IHBvc2l0aW9uX2RvZGdlKDAuNSkpICsKICBnZW9tX3BvaW50KGFlcyhjb2xvciA9IGhvc3QpLCBzaXplID0gMSwKICAgICAgICAgICAgIHBvc2l0aW9uID0gcG9zaXRpb25faml0dGVyZG9kZ2UoZG9kZ2Uud2lkdGggPSAwLjUpKSArIAogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBjKCJQSE8yIiA9ICJncmF5MjAiLCAicGhvMuKIhiIgPSAiZ3JheTQwIikpICsKICBzY2FsZV9maWxsX3ZpcmlkaXNfZChiZWdpbiA9IDAuMiwgZW5kID0gMC42KSArCiAgI3N0YXRfc3VtbWFyeShmdW4gPSAibWVhbiIsIGdlb20gPSAiY3Jvc3NiYXIiLCBjb2xvciA9ICJyZWQiLCB3aWR0aCA9IDAuMjUsCiAgIyAgICAgICAgICAgICBwb3NpdGlvbiA9IHBvc2l0aW9uX2RvZGdlKDAuNzUpLCApICsKICBmYWNldF93cmFwKH5wYXJhbWV0ZXIsIHNjYWxlID0gImZyZWVfeSIsIG5jb2wgPSAxKSArCiAgeGxhYigiUGhvNCBjaGltZXJhIikgKyBleHBhbmRfbGltaXRzKHkgPSAwKSArCiAgdGhlbWVfZ3JheShiYXNlX3NpemUgPSAxNCkgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gMzAsIGhqdXN0ID0gMSwgZmFtaWx5ID0gIm1vbm8iKSkKYGBgCmBgYHtyfQp0bXAgJT4lIAogICNtdXRhdGUoYFlMMi5IL0JMMS5IYCA9IFlMMi5IL0JMMS5ILAogIG11dGF0ZShgUi9HYCA9IG5SRlAvbkdGUCkgJT4lIAogIGdncGxvdChhZXMoeCA9IHN5bWJvbCwgeSA9IGBSL0dgLCBncm91cCA9IGhvc3QpKSArIAogIGdlb21fYmFyKGFlcyhmaWxsID0gaG9zdCksIHdpZHRoID0gMC41LCBhbHBoYSA9IDAuOCwKICAgICAgICAgICBzdGF0ID0gInN1bW1hcnkiLCBmdW4gPSAibWVhbiIsIHBvc2l0aW9uID0gcG9zaXRpb25fZG9kZ2UoMC41KSkgKwogIGdlb21fcG9pbnQoYWVzKGNvbG9yID0gaG9zdCksIHBvc2l0aW9uID0gcG9zaXRpb25faml0dGVyZG9kZ2UoZG9kZ2Uud2lkdGggPSAwLjUpKSArIAogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBjKCJQSE8yIiA9ICJncmF5MjAiLCAicGhvMuKIhiIgPSAiZ3JheTQwIikpICsKICBzY2FsZV9maWxsX3ZpcmlkaXNfZChiZWdpbiA9IDAuMiwgZW5kID0gMC42KSArCiAgI3N0YXRfc3VtbWFyeShmdW4gPSAibWVhbiIsIGNvbG9yID0gInJlZCIsIGdlb20gPSAiY3Jvc3NiYXIiLCB3aWR0aCA9IDAuMiwKICAjICAgICAgICAgICAgIHBvc2l0aW9uID0gcG9zaXRpb25fZG9kZ2UoMC43NSksICkgKwogIHRoZW1lX2dyYXkoYmFzZV9zaXplID0gMTQpICsgeGxhYigiUGhvNCBjaGltZXJhIikgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gMzAsIGhqdXN0ID0gMSwgZmFtaWx5ID0gIm1vbm8iKSkKYGBgCgpOZXh0IGlkZWEgaXMgdG8gcGxvdCB0aGUgcmF0aW8gYmV0d2VlbiB0aGUgbWVhbiBSL0cgZm9yIF9QSE8yXyBhbmQgbWVhbiBSL0cgZm9yIF9waG8y4oiGXywgb3ZlciB0aGUgbWVhbiBSL0cgZm9yIF9QSE8yXwoKRmlyc3Qgc3VtbWFyaXplIHRoZSBkYXRhLgpgYGB7cn0KZGF0c3VtIDwtIGRhdDEgJT4lCiAgZmlsdGVyKHBsYXNtaWQgIT0gIk5BIikgJT4lIAogIG11dGF0ZShgUi9HYCA9IFlMMi5IL0JMMS5ILCBgblIvR2AgPSBuUkZQL25HRlApICU+JSAKICBncm91cF9ieShkYXRlLCBwbGFzbWlkLCBob3N0KSAlPiUgCiAgc3VtbWFyaXplKGFjcm9zcyhjKEJMMS5ILCBuR0ZQLCBZTDIuSCwgblJGUCwgYFIvR2AsIGBuUi9HYCksIG1lYW4pLCAuZ3JvdXBzID0gImRyb3AiKSAlPiUgCiAgcGl2b3Rfd2lkZXIobmFtZXNfZnJvbSA9IGhvc3QsIHZhbHVlc19mcm9tID0gQkwxLkg6YG5SL0dgKSAlPiUgCiAgbXV0YXRlKGBwaG8y4oiGL1BITzJgID0gYFIvR19waG8y4oiGYC9gUi9HX1BITzJgLAogICAgICAgICBgbi5waG8y4oiGL1BITzJgID0gYG5SL0dfcGhvMuKIhmAvYG5SL0dfUEhPMmApCgojIHVzZWZ1bCB0byBzZXQgYSBmbGFnIGZvciBsb3cgYWN0aXZpdHkgbXV0YW50cwpsb3cuYWN0LnRoIDwtIGRhdHN1bSAlPiUgZmlsdGVyKHBsYXNtaWQgPT0gIjE5NCIpICU+JSBzdW1tYXJpemUoYWNyb3NzKC5jb2xzID0gd2hlcmUoaXMubnVtZXJpYyksIC5mbnMgPSBtZWFuKSkgJT4lIHVubGlzdCgpCmBgYAoKVGhlbiBleHRyYWN0IHRoZSBzdWJzZXQgZm9yIHBsb3R0aW5nCmBgYHtyfQp0bXBzdW0gPC0gbWV0YSAlPiUgCiAgZmlsdGVyKHN5bWJvbCAlaW4lIGMocmVmcyx0ZXN0KSkgJT4lIAogIGlubmVyX2pvaW4oZGF0c3VtLCBieSA9ICJwbGFzbWlkIikgJT4lIAogIG11dGF0ZShzeW1ib2wgPSBmYWN0b3Ioc3ltYm9sLCBsZXZlbHMgPSBjKHJlZnMsIHRlc3QpKSkKCmBgYAoKRGVzaWduIHRoZSBwbG90CmBgYHtyfQp0bXBzdW0gJT4lIAogIG11dGF0ZShBY3Rpdml0eSA9IGlmZWxzZShgUi9HX1BITzJgIDwgMipsb3cuYWN0LnRoWyJSL0dfcGhvMuKIhiJdLCAibG93IiwgInBhc3MiKSkgJT4lIAogIGdncGxvdChhZXMoeCA9IHN5bWJvbCwgeSA9IGBwaG8y4oiGL1BITzJgKSkgKyAKICBnZW9tX2NvbChhZXMoZ3JvdXAgPSBkYXRlLCBmaWxsID0gYFIvR19QSE8yYCksIHdpZHRoID0gMC43NSwgY29sb3IgPSAiZ3JheTUwIiwKICAgICAgICAgICBwb3NpdGlvbiA9IHBvc2l0aW9uX2RvZGdlKDAuOSkpICsKICAjZ2VvbV9wb2ludChhZXMoY29sb3IgPSBob3N0KSwgcG9zaXRpb24gPSBwb3NpdGlvbl9qaXR0ZXJkb2RnZShkb2RnZS53aWR0aCA9IDAuNSkpICsgCiAgc2NhbGVfZmlsbF9ncmFkaWVudDIoIkFjdGl2aXR5IikgKwogICNzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gYyhhbHBoYSgiYmxhY2siLDApLCAicmVkMyIpKSArCiAgI3N0YXRfc3VtbWFyeShmdW4gPSAibWVhbiIsIGNvbG9yID0gInJlZCIsIGdlb20gPSAiY3Jvc3NiYXIiLCB3aWR0aCA9IDAuMiwKICAjICAgICAgICAgICAgIHBvc2l0aW9uID0gcG9zaXRpb25fZG9kZ2UoMC43NSksICkgKwogIGZhY2V0X2dyaWQoLn5BY3Rpdml0eSwgc2NhbGVzID0gImZyZWVfeCIsIHNwYWNlID0gImZyZWVfeCIsIGxhYmVsbGVyID0gImxhYmVsX2JvdGgiKSArCiAgdGhlbWVfYncoYmFzZV9zaXplID0gMTQpICsgeGxhYigiUGhvNCBjaGltZXJhIikgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gMzAsIGhqdXN0ID0gMSwgZmFtaWx5ID0gIm1vbm8iKSkKYGBgCgpYLVkgcGxvdApgYGB7cn0KcDMgPC0gdG1wc3VtICU+JSAKICBtdXRhdGUoYG5SL0dfUEhPMmAgPSBzaWduaWYoYG5SL0dfUEhPMmAsIGRpZ2l0cyA9IDIpLAogICAgICAgICBgblIvR19waG8y4oiGYCA9IHNpZ25pZihgblIvR19waG8y4oiGYCwgZGlnaXRzID0gMikpICU+JSAKICBnZ3Bsb3QoYWVzKHggPSBgblIvR19QSE8yYCwgeSA9IGBuUi9HX3BobzLiiIZgLCBsYWJlbCA9IHN5bWJvbCkpICsgCiAgZ2VvbV9wb2ludChzaXplID0gMi41KSArIGdlb21fYWJsaW5lKHNsb3BlID0gMSkgKwogIHRoZW1lX2dyYXkoYmFzZV9zaXplID0gMTQpCmdncGxvdGx5KHAzLCB0b29sdGlwID0gYygibGFiZWwiLCAieCIsICJ5IikpCmBgYAoK