This is an R Markdown Notebook to accompany the paper, “Microhaplotypes provide increased power from short-read DNA sequences for relationship inference.”

The data

We begin with filtered haplotype data for 144 kelp rockfish at 165 loci that was generated on a MiSeq instrument with paired-end 150-cycle sequencing and prepared using GT-seq amplicon sequencing (Campbell et al. 2015).

Data were filtered using MICROHAPLOT this repository according to the following criteria:

  1. 20 reads per haplotype
  2. 0.2 allelic ratio

Included here are data processing steps and the code necessary to make each figure (Figs 1-4).

Let’s start off establishing some nomenclature for our sets of SNPs and microhaps:

# first load up CKMRsim
devtools::install_github("eriqande/CKMRsim")
Skipping install of 'CKMRsim' from a github remote, the SHA1 (37a598b3) has not changed since last install.
  Use `force = TRUE` to force installation
library(tidyverse)
Loading tidyverse: ggplot2
Loading tidyverse: tibble
Loading tidyverse: tidyr
Loading tidyverse: readr
Loading tidyverse: purrr
Loading tidyverse: dplyr
Conflicts with tidy packages -----------------------------------------------------------------
filter(): dplyr, stats
lag():    dplyr, stats
library(readxl)
library(CKMRsim)
library(ggplot2)

Let’s grab the curated dataset

# filtered data 
hapkept <- readRDS(file = "data/kelp_165_microhaps.rds")
# take a look at that
hapkept

Computing allele frequencies

To compute allele freqs we need to just count up the occurrences of the different types amongst haplotype.1 and haplotype.2. So, we need to get them into a single column, and just for the extra challenge we will keep their read depths there as well.

haptidy <- hapkept %>%
  unite(col = hap1, haplotype.1, read.depth.1) %>%
  unite(col = hap2, haplotype.2, read.depth.2) %>%
  gather(key = gene_copy, value = H, hap1, hap2) %>%
  separate(H, into = c("Allele", "read_depth")) %>%
  arrange(panel, locus, Indiv.ID, gene_copy)

And that looks like this

haptidy

So, now we just need to compute the frequencies for each of the haplotypes

hapfreqs <- haptidy %>%
  group_by(locus, Allele) %>%
  summarise(count = n()) %>%
  mutate(Freq = count / sum(count))

And the result looks like this

hapfreqs 

Do the CKMR sim analyses

Get it in the right format

First we have to get that data frame in the right format and reindex the markers and make something that CKMRsim is expecting to be able to work with (i.e., it has haplotypes in descending frequeny at each locus and it has locus and allele indices in there). To get loci to be ordered as they are, I have to throw Pos in there, even though they are not known to have a position on any Chrom.

mhaps <- hapfreqs %>%
  ungroup() %>%
  mutate(Chrom = "GTseq") %>%
  rename(Locus = locus) %>%
  select(-count) %>%
  mutate(Pos = as.integer(factor(Locus, levels = unique(Locus)))) %>%
  mutate(LocIdx = 0,
         AlleIdx = 0) %>%
  CKMRsim::reindex_markers() %>%
  select(Chrom, Locus, Pos, Allele, LocIdx, AlleIdx, Freq)

Here is what that looks like:

mhaps

That shows us that our data set has 825 distinct alleles in it.

Make Figure 1.

While we are at it, let’s look at the distribution of the number of alleles across loci:

m <- mhaps %>%
  group_by(Locus) %>%
  summarise(num_haplotypes = n()) %>%
  group_by(num_haplotypes) %>%
  summarise(num_loci = n()) %>% # plot this for figure 1
  ggplot(., aes(num_haplotypes, num_loci)) +
  geom_histogram(stat = "identity") +
  theme_bw() +
  xlab("Number of Haplotypes") +
  ylab("Number of Loci") + 
  scale_x_continuous(breaks = c(3,6,9,12))
Ignoring unknown parameters: binwidth, bins, pad
fig1 <- m + theme(
  axis.text.x=element_text(size=14),
  axis.title.x=element_text(size=14, face="bold"),
  axis.text.y=element_text(size=14),
  axis.title.y=element_text(size=14, face="bold")
)
fig1
# save that plot for Fig 1.
ggsave("output/Fig1.pdf")
Saving 6.95 x 4.29 in image

Running through CKMRsim

First we create a CKMR object. In the current version of the CKMRsim, this assumes an error model that is appropriate to microhaps and SNPs (0.005 per gene copy per snp, scaled by the number of SNPs).

CK <- create_ckmr(mhaps, kappa_matrix = kappas[c("PO", "FS", "HS", "U"), ])

Then we can simulate some Q values:

Qs <- simulate_Qij(C = CK, froms = c("PO", "FS", "HS", "U"), tos = c("PO", "FS", "HS", "U"), reps = 10^4)
Simulating unlinked markers from Y_l_true matrices for relationship: PO
Simulating unlinked markers from Y_l_true matrices for relationship: FS
Simulating unlinked markers from Y_l_true matrices for relationship: HS
Simulating unlinked markers from Y_l_true matrices for relationship: U
# then do the  sampling to get the FPRs
mc_sample_simple(Qs, nu = "PO", de = c("U", "FS"), tr = c("U", "FS"), method = "both")

If we want to plot the actual distributions, we can extract them and plot them. For example, to plot the PO/U Lambdas we can do:

extract_logls(Qs, numer = c(PO = 1), denom = c(U = 1)) %>%
  ggplot(aes(x = logl_ratio, fill = true_relat)) +
  geom_density(alpha = 0.3)

NA

Ranking and Selecting microhaps and SNPs

Getting SNP frequencies

To find the SNP frequencies, we are going to need to explode those haplotypes into constituent SNPs and estimate their frequencies, and then take the best SNPs from each microhaplotype to then select subsets of them. We are going to operate on mhaps for this, and then get a data frame called best_snps_165 which are the SNP allele allele frequencies. We will filter that data frame later to get our different subsets of SNPs.

# get all the SNP freqs
snp_freqs <- mhaps %>%
  split(f = mhaps$Locus) %>%
  lapply(function(x) {
    x$exploded = sapply(strsplit(x$Allele, split = ""), function(y) paste(y, collapse = "."))
    x
  }) %>%
  lapply(., function(x) {
    separate(x, exploded, into = paste("snp", 1:nchar(x$Allele[1]), sep = "_"))
  }) %>%
  lapply(., function(x) {
    gather(x, key = "SNP", value = "base", contains("snp_"))
  }) %>%
  bind_rows %>%
  group_by(Chrom, Locus, Pos, LocIdx, SNP, base) %>% 
  summarise(Freq = sum(Freq))
# now, get the best (MAF closest to 0.5)
best_snps_165 <- snp_freqs %>%
  group_by(Locus, SNP) %>%
  filter(n() > 1) %>%  # toss SNPs that are monomorphic---for some reason there are some...
  mutate(maf = min(Freq)) %>%  # since there are only two alleles, this gets the MAF at that locus
  group_by(Locus) %>%
  filter(near(maf, max(maf)))  %>%  # this almost does it, but some snps at a locus might have the same MAF at different snps
  mutate(tmp = 1:n()) %>%
  filter(tmp < 3)  %>% # this gets rid of those same MAF cases.
  select(-tmp, -maf) %>%
  rename(Allele = base) %>%
  mutate(AlleIdx = 0) %>%
  CKMRsim::reindex_markers() %>%
  select(Chrom, Locus, Pos, Allele, LocIdx, AlleIdx, Freq)

Now, let’s just make a quick plot to confirm that we have gotten the highest minor allele frequency SNPs for each locus.

all_mafs <- snp_freqs %>%
  group_by(Locus, SNP) %>%
  summarise(maf = min(Freq)) %>%
  filter(maf <= 0.5)  # this gets rid of monomorphic ones
best_mafs <- best_snps_165 %>%
  group_by(Locus) %>%
  summarise(maf = min(Freq))
ggplot(all_mafs, aes(y = Locus, x = maf)) +
  geom_point() +
  geom_point(data = best_mafs, colour = "red")

We will come back to these to grab the allele frequencies for further analysis.

And using some of the results from above, get the 96 SNPs that are the best from among all 165.

top_snps_96_all <- best_mafs %>%
  arrange(desc(maf)) %>%
  slice(1:96)

Selecting the best microhaps

First we are going to find the microhaps with the highest heterozygosity, and the SNPs with the high MAF

mhap_hz <- mhaps %>%
  group_by(Locus) %>% 
  summarise(hz = 1 - sum(Freq^2), nHaps = n()) %>%
  arrange(desc(hz)) 
top_mhaps <- mhap_hz %>%
  slice(1:96)

Make Figure 2.

Here we create Fig. 2 - the plot with mhap heterozygosity and best snp minor allele frequency per locus.

# snps = best_mafs
# mhaps = mhap_hz
# add a column that designates marker type
best_snp_mafs <- best_mafs %>%
  mutate(., Marker_type = "SNPs") 
names(best_snp_mafs) <- c("Locus", "hz", "Marker_type")
best_mhap_hz <- mhap_hz %>%
  mutate(., Marker_type = "mhaps")
# need to join these tibbles together and then sort by highest hz
combo_hz <- best_mhap_hz %>%
  bind_rows(., best_snp_mafs) %>% 
  group_by(Locus) %>%
  arrange(desc(hz))
combo_hz$Locus <- factor(combo_hz$Locus, levels = combo_hz$Locus)
duplicated levels in factors are deprecated
combo_plot <- combo_hz %>%
  ggplot(., aes(x = Locus, y = hz, color = Marker_type)) +
  geom_point() +
  scale_color_manual(values = c("red", "dark blue"),
                     labels = paste(c("microhaps", "SNPs"))) +
  theme_bw() +
  ylab("Heterozygosity") + 
  guides(color = guide_legend(title="Marker Type")) +
  theme(
    axis.text.x = element_blank()
  )
# more formatting
combo_plot <- combo_plot + theme(
  axis.text.x=element_blank(),
  axis.title.x=element_text(size=14, face="bold"),
  axis.text.y=element_text(size=14),
  axis.title.y=element_text(size=14, face="bold"),
  legend.text=element_text(size=14),
  legend.title=element_text(size=14, face="bold"))
fig2 <- combo_plot +
   theme(legend.position = c(0.85, 0.85))
fig2
# save that to a pdf
ggsave("output/Fig2.pdf")
Saving 6.95 x 4.29 in image

Now, we are going to make our CKMR-ready allele frequencies for each of our four data sets in a named list:

Making a list of data sets

fourData_list <- list(
  m165 = mhaps,
  s165 = best_snps_165,
  m96 = mhaps %>% filter(Locus %in% top_mhaps$Locus),
  s96_top = best_snps_165 %>% filter(Locus %in% top_snps_96_all$Locus)
)

Doing CKMR calcs on each data set

We can do each step, lapplying over things:

CK_list <- lapply(fourData_list, function(x) 
  create_ckmr(x, kappa_matrix = kappas[c("PO", "FS", "HS", "U"), ])
)

And simulate the Qij values. Do 10^5…

Qs_list <- lapply(CK_list, function(x) 
  simulate_Qij(C = x, froms = c("PO", "FS", "HS", "U"), tos = c("PO", "FS", "HS", "U"), reps = 10^5)
)

And once that is done, we can collect samples from it:

FPRs_etc <- lapply(Qs_list, function(x) mc_sample_simple(x, nu = c("PO", "FS", "HS"), method = "IS", FNRs = seq(0.01, 0.30, by = 0.01))) %>%
  bind_rows(.id = "marker_set")
         

And now, let us spread that into a data set that is easier to read:

FPRs_etc %>%
  rename(relationship = pstar) %>%
  select(relationship, FNR, marker_set, FPR) %>%
  tidyr::spread(data = ., key = marker_set, value = FPR)

Make Figure 3.

Let’s plot the FPRs_etc:

FPR_ses <- FPRs_etc %>%
  mutate(se_lo = FPR - 2 * se,
       se_hi = FPR + 2 * se)
# create a factor for ordering the relationship type
FPRs_etc$pstar_f = factor(FPRs_etc$pstar, levels=c("PO","FS", "HS"))
# remove the HS rows and then plot that
f3 <- FPRs_etc %>%
  filter(., pstar_f != "HS") %>%
  ggplot(., aes(x = FNR, y = FPR, shape = marker_set)) +
  geom_point() +
 #geom_segment(aes(x = FNR, y = se_lo, xend = FNR, yend = se_hi)) +  # these are basically invisible because they are so small
  facet_grid(. ~ pstar_f) +
  scale_y_continuous(trans = "log10") +
  xlab("False Negative Rate") + 
  ylab("False Positive Rate (log)") +
  theme_bw()
fig3 <- f3 +
  guides(shape=guide_legend(title="Marker Set")) +
  theme(
  axis.text.x=element_text(size=14),
  axis.title.x=element_text(size=14, face="bold"),
  axis.text.y=element_text(size=14),
  axis.title.y=element_text(size=14, face="bold"),
  legend.text=element_text(size=14),
  legend.title=element_text(size=14, face="bold"))
fig3 <- fig3 +
  theme(legend.position = c(0.6, 0.15))
fig3
# and save that plot as Fig 3.
ggsave("output/Fig3.pdf", width = 9, height = 7, units = "in")

Expanding data assuming things are unlinked or linked

Note: for this section, you need to download and install Mendel (Lange et al. 2013).

First, we are going to need to assume a map—i.e. a collection of chromosomes and lengths. For now, I am just going to assume 25 chromosomes that vary in length from 200 to 100 Mb, and we assume 1 cM per megabase.

fakeChroms <- tibble(Chrom = 1:25,
                     length = seq(200e06, 100e06, length.out = 25))

That is a pretty “generous” genome, in terms of chances for recombination, I think.

Let’s just do this as simply as possible and duplicate our data 1X, 2X, 4X, 8X, 16X, 32X, 64X.

Before I required that we replicate everything contiguously, but now we want to be able to just go straight to 32X, or, actually, we would like to multiply things by a factor or 2 each time.

Of course, before that, we want to have something that assigns chromosomes and positions to each marker.

#' @param D a data frame of Chrom Locus Pos, Allele, LocIdx, AlleIdx and Freq
#' @param FC a data frame of fake chromosomes with chrom names and lenths
#' @details This randomly assigns each Locus to a random position within a 
#' randomly chosen chrom.
sprinkle_positions <- function(D, FC) {
  loci <- tibble::tibble(Locus = unique(D$Locus))
  L <- nrow(loci)  # the number of loci we are doing here
  
  # now, choose which chroms those are on.  Weight it by their length, of course
  # and also simulate a position in it. Then bind it to the Locus names
  new_pos <- FC %>%
    sample_n(size = L, replace = TRUE, weight = length) %>%
    mutate(Pos = floor(runif(n(), min = 1, max = length))) %>%
    mutate(Locus = loci$Locus) %>%
    select(Chrom, Locus, Pos)
  
  # now, just left join that onto D by Locus.
  # and we might as well reindex them, although if we duplicate the data 
  # set we will have to reindex them again
  D %>%
    select(-Chrom, -Pos) %>%
    left_join(new_pos, ., by = "Locus") %>%
    reindex_markers()
}

And now we just need a function that will return a set of data just like the previous, but with locus names that are slightly different, and with the duplicated ones having new positions, while the old ones keep the same old positions.

data_duplicator <- function(D, FC, suffix = "_x2") {
  D2 <- D %>%
    mutate(Locus = paste0(Locus, suffix)) %>%
    sprinkle_positions(., FC)
  
  reindex_markers(bind_rows(D, D2))
}

Now, here is a function which will return a list of data sets, each one representing a 1X, or 2X, or 4X duplication…

make_dupies <- function(D, FC) {
  
  ret <- list()
  ret[[1]] <- sprinkle_positions(D, FC)
  
  for (i in 1:6) {
    idx <- 1 + i
    suff <- paste0("x", 2^i)
    ret[[idx]] <- data_duplicator(ret[[i]], FC, suffix = suff)
  }
  
  names(ret) <- paste0("x", 2 ^ (0:6))
  ret
}

And here we create duplicate versions of the 96 microhaps and the 96 SNPs. Note that positions are not the same in each, but are just randomly sprinkled for each marker type, but that should be fine…

set.seed(555)
mhap_dupie_list <- make_dupies(fourData_list$m96, fakeChroms)
snp_dupie_list <- make_dupies(fourData_list$s96_top, fakeChroms)

And, finally, all we need is a function that will do the linked and unlinked simulation for each of these. I am going to do it for one relationship at a time…

sim_linked_and_unlinked_hs <- function(D) {
  CK <- create_ckmr(D)
  QU_unlinked <- simulate_Qij(CK, froms = c("U", "HS"), tos = c("U", "HS"), reps = 1e4)
  QU_linked <- simulate_Qij(CK, froms = c("HS"), tos = c("U", "HS"), reps = 1e04, unlinked = FALSE, pedigree_list = pedigrees)
  
  link <- mc_sample_simple(Q = QU_unlinked, nu = "HS", de = "U", method = "IS", FNRs = seq(0.05, 0.3, by = 0.05), Q_for_fnrs = QU_linked) %>%
    mutate(sim_type = "linked")
  unlink <- mc_sample_simple(Q = QU_unlinked, nu = "HS", de = "U", method = "IS", FNRs = seq(0.05, 0.3, by = 0.05)) %>%
    mutate(sim_type = "unlinked")
  
  bind_rows(link, unlink)
}

And now, fire it up for half-sibs and SNPs:

snps_hs_reslist <- lapply(snp_dupie_list, sim_linked_and_unlinked_hs)

And, after doing that, we can plot the results at a constant false negative rate, let’s say 0.05:

hs05 <- bind_rows(snps_hs_reslist, .id = "data_reps") %>%
  filter(near(FNR, 0.05)) %>%
  mutate(data_reps = parse_number(data_reps),
         num_markers = 96 * data_reps)

ggplot(hs05, aes(x = num_markers, y = FPR, colour = sim_type)) +
  geom_line() +
  geom_point() +
  scale_y_continuous(trans = "log10")

OK, so the linkage doesn’t make it plateau…it just increases the number of necessary markers by a predictable amount. Basically, it changes the slope of the relationship.

Now, let’s have a look at how things work with the microhaplotypes, but only do the first five (up to 16X).

mhaps_hs_reslist <- lapply(mhap_dupie_list[1:5], sim_linked_and_unlinked_hs)
hs05_mh <- bind_rows(mhaps_hs_reslist, .id = "data_reps") %>%
  filter(near(FNR, 0.05)) %>%
  mutate(data_reps = parse_number(data_reps),
         num_markers = 96 * data_reps) %>%
  mutate(marker_type = "mhap")

combo_df <- hs05 %>%
  filter(data_reps <= 16) %>%
  mutate(marker_type = "SNP") %>%
  bind_rows(., hs05_mh)
  

gg <- ggplot(combo_df, aes(x = num_markers, y = FPR, colour = marker_type, linetype = sim_type, shape = marker_type)) +
  geom_point() +
  geom_line() +
  scale_y_continuous(trans = "log10", breaks = 10 ^ (-(seq(3,28, by = 2))))

# print it
gg

Now, zoom in on the left part of the graph:

gg +
  coord_cartesian(xlim = c(0, 1200), ylim = c(1e-00, 1e-17))

Now let’s look at FNR = 0.01

Turns out that we might want to use the 0.01 FNR cutoff for the paper. So, let’s get all the results and make an RDS so I don’t have to redo them!!

full_results <- list(
  mhap = bind_rows(mhaps_hs_reslist, .id = "data_reps"),
  SNP = bind_rows(snps_hs_reslist, .id = "data_reps")
) %>%
  bind_rows(.id = "marker_type") %>% 
  mutate(data_reps = parse_number(data_reps)) %>%
  mutate(num_markers = 96 * data_reps) %>%
  select(marker_type, data_reps, num_markers, sim_type, everything())

full_results

And now, let’s save that as well:

saveRDS(full_results, file = "../outputs/full_results_snps_v_mhaps_half_sibs_linked_v_unlinked.rds")

And now we can look at things with an FNR of 0.01:

full_results %>%
  filter(near(FNR, 0.01))

Make Figure 4.

# We can use the saved output for this.
# get the results and filter just to the points we want to keep
fig4res <- readRDS("data/full_results_snps_v_mhaps_half_sibs_linked_v_unlinked.rds") %>%
  filter(near(FNR, 0.01)) %>%
  filter((marker_type == "mhap" & data_reps <= 8) | (marker_type == "SNP" & data_reps <= 16))

# then plot it
f4 <- ggplot(fig4res, aes(x = num_markers, y = FPR, color = marker_type, linetype = sim_type, shape = marker_type)) +
  geom_point() +
  geom_line() +
  scale_y_continuous(trans = "log10", breaks = 10^(-seq(0, 20, by = 2)))
 
f4 <- f4 + theme_bw() +
  ylab("False Positive Rate") + 
  xlab("Number of Markers") +
  guides(color = guide_legend(title="Marker Type"), shape = guide_legend(title = "Marker Type"), linetype = guide_legend(title = "Simulation Type"))
    
fig4 <- f4 + scale_color_manual(values = c("red", "dark blue"),
                     labels = paste(c("microhaps", "SNPs"))) +
  scale_shape_manual(values = c(16, 17), 
                     labels = paste(c("microhaps", "SNPs"))) + 
  theme(
  axis.text.x=element_text(size=14),
  axis.title.x=element_text(size=14, face="bold"),
  axis.text.y=element_text(size=14),
  axis.title.y=element_text(size=14, face="bold"),
  legend.text=element_text(size=14),
  legend.title=element_text(size=14, face="bold"))

fig4 <- fig4 +
   theme(legend.position = c(0.85, 0.80))

fig4 <- fig4 + theme(plot.margin = unit(c(1,1,1,1), "cm"))

fig4

# save that as Fig. 4
ggsave("output/Fig4.pdf", width = 10, height = 7, units = "in")

Basically, if we want FPR’s on the order of 1e-09 we are going to want about 400 microhaps (taking account of likely patterns of linkage). To get the same sort of power, we would need 1100 SNPs.

So, the issue of physical linkage is not a total deal-breaker for SNPs, but we still do better with microhaps. And the microhap numbers there put us in the realm in which one could still be doing amplicon sequencing, as opposed to having to resort to somewhat less reliable and more expensive methods.

LS0tCnRpdGxlOiAiRGF0YSBwcm9jZXNzaW5nIGZvciBtaWNyb2hhcCBwYXBlciIKYXV0aG9yOiAiRGlhbmEgQmFldHNjaGVyIgpkYXRlOiAiT2N0b2JlciAyMywgMjAxNyIKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6IAogICAgdG9jOiB0cnVlCiAgaHRtbF9kb2N1bWVudDogCiAgICBkZWZhdWx0Ci0tLQoKYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9CmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSkKYGBgCgoKVGhpcyBpcyBhbiBbUiBNYXJrZG93bl0oaHR0cDovL3JtYXJrZG93bi5yc3R1ZGlvLmNvbSkgTm90ZWJvb2sgdG8gYWNjb21wYW55IHRoZSBwYXBlciwgIk1pY3JvaGFwbG90eXBlcyBwcm92aWRlIGluY3JlYXNlZCBwb3dlciBmcm9tIHNob3J0LXJlYWQgRE5BIHNlcXVlbmNlcyBmb3IgcmVsYXRpb25zaGlwIGluZmVyZW5jZS4iIAoKCiMjIFRoZSBkYXRhCgpXZSBiZWdpbiB3aXRoIGZpbHRlcmVkIGhhcGxvdHlwZSBkYXRhIGZvciAxNDQga2VscCByb2NrZmlzaCBhdCAxNjUgbG9jaSB0aGF0IHdhcyBnZW5lcmF0ZWQgb24gYSBNaVNlcSBpbnN0cnVtZW50IHdpdGggcGFpcmVkLWVuZCAxNTAtY3ljbGUgc2VxdWVuY2luZyBhbmQgcHJlcGFyZWQgdXNpbmcgR1Qtc2VxIGFtcGxpY29uIHNlcXVlbmNpbmcgKENhbXBiZWxsIGV0IGFsLiAyMDE1KS4KCkRhdGEgd2VyZSBmaWx0ZXJlZCB1c2luZyBNSUNST0hBUExPVCBbdGhpcyByZXBvc2l0b3J5XShodHRwczovL2dpdGh1Yi5jb20vbmd0aG9tYXMvY2FsbEJheWVzKSAgYWNjb3JkaW5nIHRvIHRoZSBmb2xsb3dpbmcgY3JpdGVyaWE6CgoxLiAyMCByZWFkcyBwZXIgaGFwbG90eXBlCjIuIDAuMiBhbGxlbGljIHJhdGlvCgoKSW5jbHVkZWQgaGVyZSBhcmUgZGF0YSBwcm9jZXNzaW5nIHN0ZXBzIGFuZCB0aGUgY29kZSBuZWNlc3NhcnkgdG8gbWFrZSBlYWNoIGZpZ3VyZSAoRmlncyAxLTQpLgoKTGV0J3Mgc3RhcnQgb2ZmIGVzdGFibGlzaGluZyBzb21lIG5vbWVuY2xhdHVyZSBmb3Igb3VyIHNldHMgb2YgU05QcyBhbmQgbWljcm9oYXBzOgoKLSAqKm0xNjUqKjogYWxsIDE2NSBtaWNyb2hhcHMuIChOb3RlIHRoYXQgYWZ0ZXIgZmlsdGVyaW5nIHdlIGhhdmUgMTY1IG1pY3JvaGFwcykKLSAqKnMxNjUqKjogdGhlIGhpZ2hlc3QgTUFGIFNOUCBmcm9tIGVhY2ggb2YgdGhlIDE2NSBsb2NpLgotICoqbTk2Kio6IHRoZSA5NiBtaWNyb2hhcHMgd2l0aCB0aGUgaGlnaGVzdCBoZXRlcm96eWdvc2l0eSBhbW9uZ3N0IHRoZSAxNjUuCi0gKipzOTZfdG9wKio6IHRoZSA5NiBTTlBzIHdpdGggdGhlIGhpZ2hlc3QgTUFGcyBmcm9tIGFtb25nc3QgdGhlIHMxNjUuCi0gKipzOTZfbSoqOiA5NiBTTlBzLCBlYWNoIGJlaW5nIHRoZSBoaWdoZXN0IE1BRiBTTlAgZnJvbSBlYWNoIG9mIHRoZSBtOTYuCgoKYGBge3IgbG9hZC1saWJyYXJpZXN9CiMgZmlyc3QgbG9hZCB1cCBDS01Sc2ltCmRldnRvb2xzOjppbnN0YWxsX2dpdGh1YigiZXJpcWFuZGUvQ0tNUnNpbSIpCgpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShyZWFkeGwpCmxpYnJhcnkoQ0tNUnNpbSkKbGlicmFyeShnZ3Bsb3QyKQpgYGAKCkxldCdzIGdyYWIgdGhlIGN1cmF0ZWQgZGF0YXNldApgYGB7ciByZWFkLWRhdGF9CiMgZmlsdGVyZWQgZGF0YSAKaGFwa2VwdCA8LSByZWFkUkRTKGZpbGUgPSAiZGF0YS9rZWxwXzE2NV9taWNyb2hhcHMucmRzIikKCiMgdGFrZSBhIGxvb2sgYXQgdGhhdApoYXBrZXB0CmBgYAoKIyMjIENvbXB1dGluZyBhbGxlbGUgZnJlcXVlbmNpZXMKVG8gY29tcHV0ZSBhbGxlbGUgZnJlcXMgd2UgbmVlZCB0byBqdXN0IGNvdW50IHVwIHRoZSBvY2N1cnJlbmNlcyBvZiB0aGUgZGlmZmVyZW50IHR5cGVzCmFtb25nc3QgaGFwbG90eXBlLjEgYW5kIGhhcGxvdHlwZS4yLiAgU28sIHdlIG5lZWQgdG8gZ2V0IHRoZW0gaW50byBhIHNpbmdsZSBjb2x1bW4sIGFuZApqdXN0IGZvciB0aGUgZXh0cmEgY2hhbGxlbmdlIHdlIHdpbGwga2VlcCB0aGVpciByZWFkIGRlcHRocyB0aGVyZSBhcyB3ZWxsLgpgYGB7ciB0aWR5aGFwc30KaGFwdGlkeSA8LSBoYXBrZXB0ICU+JQogIHVuaXRlKGNvbCA9IGhhcDEsIGhhcGxvdHlwZS4xLCByZWFkLmRlcHRoLjEpICU+JQogIHVuaXRlKGNvbCA9IGhhcDIsIGhhcGxvdHlwZS4yLCByZWFkLmRlcHRoLjIpICU+JQogIGdhdGhlcihrZXkgPSBnZW5lX2NvcHksIHZhbHVlID0gSCwgaGFwMSwgaGFwMikgJT4lCiAgc2VwYXJhdGUoSCwgaW50byA9IGMoIkFsbGVsZSIsICJyZWFkX2RlcHRoIikpICU+JQogIGFycmFuZ2UocGFuZWwsIGxvY3VzLCBJbmRpdi5JRCwgZ2VuZV9jb3B5KQpgYGAKQW5kIHRoYXQgbG9va3MgbGlrZSB0aGlzCmBgYHtyIHZpZXd0aWR5aGFwc30KaGFwdGlkeQpgYGAKCgpTbywgbm93IHdlIGp1c3QgbmVlZCB0byBjb21wdXRlIHRoZSBmcmVxdWVuY2llcyBmb3IgZWFjaCBvZiB0aGUgaGFwbG90eXBlcwpgYGB7ciBoYXBmcmVxc30KaGFwZnJlcXMgPC0gaGFwdGlkeSAlPiUKICBncm91cF9ieShsb2N1cywgQWxsZWxlKSAlPiUKICBzdW1tYXJpc2UoY291bnQgPSBuKCkpICU+JQogIG11dGF0ZShGcmVxID0gY291bnQgLyBzdW0oY291bnQpKQpgYGAKCkFuZCB0aGUgcmVzdWx0IGxvb2tzIGxpa2UgdGhpcwpgYGB7ciB2aWV3aGFwZnJlcXN9CmhhcGZyZXFzIAoKYGBgCgojIyBEbyB0aGUgQ0tNUiBzaW0gYW5hbHlzZXMKCiMjIyBHZXQgaXQgaW4gdGhlIHJpZ2h0IGZvcm1hdApGaXJzdCB3ZSBoYXZlIHRvIGdldCB0aGF0IGRhdGEgZnJhbWUgaW4gdGhlIHJpZ2h0IGZvcm1hdCBhbmQgcmVpbmRleCB0aGUgbWFya2VycwphbmQgbWFrZSBzb21ldGhpbmcgdGhhdCBgQ0tNUnNpbWAgaXMgZXhwZWN0aW5nIHRvIGJlIGFibGUgdG8gd29yayB3aXRoIChpLmUuLCBpdCBoYXMgCmhhcGxvdHlwZXMgaW4gZGVzY2VuZGluZyBmcmVxdWVueSBhdCBlYWNoIGxvY3VzIGFuZCBpdCBoYXMgbG9jdXMgYW5kIGFsbGVsZSBpbmRpY2VzCmluIHRoZXJlKS4gVG8gZ2V0IGxvY2kgdG8gYmUgb3JkZXJlZCBhcyB0aGV5IGFyZSwgSSBoYXZlIHRvIHRocm93IGBQb3NgIGluIHRoZXJlLCBldmVuIHRob3VnaCB0aGV5IGFyZSBub3Qga25vd24gdG8gaGF2ZSBhIHBvc2l0aW9uIG9uIGFueSBDaHJvbS4KYGBge3IgcHJlcDRja21yfQptaGFwcyA8LSBoYXBmcmVxcyAlPiUKICB1bmdyb3VwKCkgJT4lCiAgbXV0YXRlKENocm9tID0gIkdUc2VxIikgJT4lCiAgcmVuYW1lKExvY3VzID0gbG9jdXMpICU+JQogIHNlbGVjdCgtY291bnQpICU+JQogIG11dGF0ZShQb3MgPSBhcy5pbnRlZ2VyKGZhY3RvcihMb2N1cywgbGV2ZWxzID0gdW5pcXVlKExvY3VzKSkpKSAlPiUKICBtdXRhdGUoTG9jSWR4ID0gMCwKICAgICAgICAgQWxsZUlkeCA9IDApICU+JQogIENLTVJzaW06OnJlaW5kZXhfbWFya2VycygpICU+JQogIHNlbGVjdChDaHJvbSwgTG9jdXMsIFBvcywgQWxsZWxlLCBMb2NJZHgsIEFsbGVJZHgsIEZyZXEpCgpgYGAKCkhlcmUgaXMgd2hhdCB0aGF0IGxvb2tzIGxpa2U6CmBgYHtyIHZpZXdtaGFwc30KbWhhcHMKYGBgClRoYXQgc2hvd3MgdXMgdGhhdCBvdXIgZGF0YSBzZXQgaGFzIGByIG5yb3cobWhhcHMpYCBkaXN0aW5jdCBhbGxlbGVzIGluIGl0LiAKCgojIyMgTWFrZSBGaWd1cmUgMS4KCldoaWxlIHdlIGFyZSBhdCBpdCwgbGV0J3MgbG9vayBhdCB0aGUgZGlzdHJpYnV0aW9uIG9mIHRoZSBudW1iZXIgb2YgYWxsZWxlcyBhY3Jvc3MgbG9jaToKYGBge3IgbWhhcHMtZGlzdC1mb3ItZmlnLTF9Cm0gPC0gbWhhcHMgJT4lCiAgZ3JvdXBfYnkoTG9jdXMpICU+JQogIHN1bW1hcmlzZShudW1faGFwbG90eXBlcyA9IG4oKSkgJT4lCiAgZ3JvdXBfYnkobnVtX2hhcGxvdHlwZXMpICU+JQogIHN1bW1hcmlzZShudW1fbG9jaSA9IG4oKSkgJT4lICMgcGxvdCB0aGlzIGZvciBmaWd1cmUgMQogIGdncGxvdCguLCBhZXMobnVtX2hhcGxvdHlwZXMsIG51bV9sb2NpKSkgKwogIGdlb21faGlzdG9ncmFtKHN0YXQgPSAiaWRlbnRpdHkiKSArCiAgdGhlbWVfYncoKSArCiAgeGxhYigiTnVtYmVyIG9mIEhhcGxvdHlwZXMiKSArCiAgeWxhYigiTnVtYmVyIG9mIExvY2kiKSArIAogIHNjYWxlX3hfY29udGludW91cyhicmVha3MgPSBjKDMsNiw5LDEyKSkKCmZpZzEgPC0gbSArIHRoZW1lKAogIGF4aXMudGV4dC54PWVsZW1lbnRfdGV4dChzaXplPTE0KSwKICBheGlzLnRpdGxlLng9ZWxlbWVudF90ZXh0KHNpemU9MTQsIGZhY2U9ImJvbGQiKSwKICBheGlzLnRleHQueT1lbGVtZW50X3RleHQoc2l6ZT0xNCksCiAgYXhpcy50aXRsZS55PWVsZW1lbnRfdGV4dChzaXplPTE0LCBmYWNlPSJib2xkIikKKQoKZmlnMQoKIyBzYXZlIHRoYXQgcGxvdCBmb3IgRmlnIDEuCgpnZ3NhdmUoIm91dHB1dC9GaWcxLnBkZiIpCgpgYGAKCgojIyMgUnVubmluZyB0aHJvdWdoIENLTVJzaW0KCkZpcnN0IHdlIGNyZWF0ZSBhIENLTVIgb2JqZWN0LiBJbiB0aGUgY3VycmVudCB2ZXJzaW9uIG9mIHRoZSBDS01Sc2ltLCB0aGlzIGFzc3VtZXMgYW4gZXJyb3IgbW9kZWwKdGhhdCBpcyBhcHByb3ByaWF0ZSB0byBtaWNyb2hhcHMgYW5kIFNOUHMgKDAuMDA1IHBlciBnZW5lIGNvcHkgcGVyIHNucCwgc2NhbGVkIGJ5IHRoZSBudW1iZXIgb2YgU05QcykuCgpgYGB7cn0KQ0sgPC0gY3JlYXRlX2NrbXIobWhhcHMsIGthcHBhX21hdHJpeCA9IGthcHBhc1tjKCJQTyIsICJGUyIsICJIUyIsICJVIiksIF0pCmBgYAoKVGhlbiB3ZSBjYW4gc2ltdWxhdGUgc29tZSBRIHZhbHVlczoKYGBge3J9ClFzIDwtIHNpbXVsYXRlX1FpaihDID0gQ0ssIGZyb21zID0gYygiUE8iLCAiRlMiLCAiSFMiLCAiVSIpLCB0b3MgPSBjKCJQTyIsICJGUyIsICJIUyIsICJVIiksIHJlcHMgPSAxMF40KQoKIyB0aGVuIGRvIHRoZSAgc2FtcGxpbmcgdG8gZ2V0IHRoZSBGUFJzCm1jX3NhbXBsZV9zaW1wbGUoUXMsIG51ID0gIlBPIiwgZGUgPSBjKCJVIiwgIkZTIiksIHRyID0gYygiVSIsICJGUyIpLCBtZXRob2QgPSAiYm90aCIpCmBgYAoKSWYgd2Ugd2FudCB0byBwbG90IHRoZSBhY3R1YWwgZGlzdHJpYnV0aW9ucywgd2UgY2FuIGV4dHJhY3QgdGhlbSBhbmQgcGxvdCB0aGVtLiBGb3IgZXhhbXBsZSwKdG8gcGxvdCB0aGUgUE8vVSBMYW1iZGFzIHdlIGNhbiBkbzoKYGBge3J9CmV4dHJhY3RfbG9nbHMoUXMsIG51bWVyID0gYyhQTyA9IDEpLCBkZW5vbSA9IGMoVSA9IDEpKSAlPiUKICBnZ3Bsb3QoYWVzKHggPSBsb2dsX3JhdGlvLCBmaWxsID0gdHJ1ZV9yZWxhdCkpICsKICBnZW9tX2RlbnNpdHkoYWxwaGEgPSAwLjMpCiAgCmBgYAoKCiMjIFJhbmtpbmcgYW5kIFNlbGVjdGluZyBtaWNyb2hhcHMgYW5kIFNOUHMKCiMjIyBHZXR0aW5nIFNOUCBmcmVxdWVuY2llcwpUbyBmaW5kIHRoZSBTTlAgZnJlcXVlbmNpZXMsIHdlIGFyZSBnb2luZyB0byBuZWVkIHRvIGV4cGxvZGUgdGhvc2UgaGFwbG90eXBlcyBpbnRvIGNvbnN0aXR1ZW50IFNOUHMgYW5kIGVzdGltYXRlIHRoZWlyIGZyZXF1ZW5jaWVzLCBhbmQgdGhlbiB0YWtlIHRoZSBiZXN0IFNOUHMgZnJvbSBlYWNoIG1pY3JvaGFwbG90eXBlIHRvIHRoZW4gc2VsZWN0IHN1YnNldHMgb2YgdGhlbS4gIFdlIGFyZSBnb2luZyB0byBvcGVyYXRlIG9uIGBtaGFwc2AgZm9yIHRoaXMsIGFuZCB0aGVuIGdldCBhIGRhdGEgZnJhbWUgY2FsbGVkIGBiZXN0X3NucHNfMTY1YCB3aGljaCBhcmUgdGhlIFNOUCBhbGxlbGUgYWxsZWxlIGZyZXF1ZW5jaWVzLiBXZSB3aWxsIGZpbHRlciB0aGF0IGRhdGEgZnJhbWUgbGF0ZXIgdG8gZ2V0IG91ciBkaWZmZXJlbnQgc3Vic2V0cyBvZiBTTlBzLgpgYGB7cn0KIyBnZXQgYWxsIHRoZSBTTlAgZnJlcXMKc25wX2ZyZXFzIDwtIG1oYXBzICU+JQogIHNwbGl0KGYgPSBtaGFwcyRMb2N1cykgJT4lCiAgbGFwcGx5KGZ1bmN0aW9uKHgpIHsKICAgIHgkZXhwbG9kZWQgPSBzYXBwbHkoc3Ryc3BsaXQoeCRBbGxlbGUsIHNwbGl0ID0gIiIpLCBmdW5jdGlvbih5KSBwYXN0ZSh5LCBjb2xsYXBzZSA9ICIuIikpCiAgICB4CiAgfSkgJT4lCiAgbGFwcGx5KC4sIGZ1bmN0aW9uKHgpIHsKICAgIHNlcGFyYXRlKHgsIGV4cGxvZGVkLCBpbnRvID0gcGFzdGUoInNucCIsIDE6bmNoYXIoeCRBbGxlbGVbMV0pLCBzZXAgPSAiXyIpKQogIH0pICU+JQogIGxhcHBseSguLCBmdW5jdGlvbih4KSB7CiAgICBnYXRoZXIoeCwga2V5ID0gIlNOUCIsIHZhbHVlID0gImJhc2UiLCBjb250YWlucygic25wXyIpKQogIH0pICU+JQogIGJpbmRfcm93cyAlPiUKICBncm91cF9ieShDaHJvbSwgTG9jdXMsIFBvcywgTG9jSWR4LCBTTlAsIGJhc2UpICU+JSAKICBzdW1tYXJpc2UoRnJlcSA9IHN1bShGcmVxKSkKCiMgbm93LCBnZXQgdGhlIGJlc3QgKE1BRiBjbG9zZXN0IHRvIDAuNSkKYmVzdF9zbnBzXzE2NSA8LSBzbnBfZnJlcXMgJT4lCiAgZ3JvdXBfYnkoTG9jdXMsIFNOUCkgJT4lCiAgZmlsdGVyKG4oKSA+IDEpICU+JSAgIyB0b3NzIFNOUHMgdGhhdCBhcmUgbW9ub21vcnBoaWMtLS1mb3Igc29tZSByZWFzb24gdGhlcmUgYXJlIHNvbWUuLi4KICBtdXRhdGUobWFmID0gbWluKEZyZXEpKSAlPiUgICMgc2luY2UgdGhlcmUgYXJlIG9ubHkgdHdvIGFsbGVsZXMsIHRoaXMgZ2V0cyB0aGUgTUFGIGF0IHRoYXQgbG9jdXMKICBncm91cF9ieShMb2N1cykgJT4lCiAgZmlsdGVyKG5lYXIobWFmLCBtYXgobWFmKSkpICAlPiUgICMgdGhpcyBhbG1vc3QgZG9lcyBpdCwgYnV0IHNvbWUgc25wcyBhdCBhIGxvY3VzIG1pZ2h0IGhhdmUgdGhlIHNhbWUgTUFGIGF0IGRpZmZlcmVudCBzbnBzCiAgbXV0YXRlKHRtcCA9IDE6bigpKSAlPiUKICBmaWx0ZXIodG1wIDwgMykgICU+JSAjIHRoaXMgZ2V0cyByaWQgb2YgdGhvc2Ugc2FtZSBNQUYgY2FzZXMuCiAgc2VsZWN0KC10bXAsIC1tYWYpICU+JQogIHJlbmFtZShBbGxlbGUgPSBiYXNlKSAlPiUKICBtdXRhdGUoQWxsZUlkeCA9IDApICU+JQogIENLTVJzaW06OnJlaW5kZXhfbWFya2VycygpICU+JQogIHNlbGVjdChDaHJvbSwgTG9jdXMsIFBvcywgQWxsZWxlLCBMb2NJZHgsIEFsbGVJZHgsIEZyZXEpCgpgYGAKCgpOb3csIGxldCdzIGp1c3QgbWFrZSBhIHF1aWNrIHBsb3QgdG8gY29uZmlybSB0aGF0IHdlIGhhdmUgZ290dGVuIHRoZSBoaWdoZXN0IG1pbm9yIGFsbGVsZSBmcmVxdWVuY3kgU05QcyBmb3IgZWFjaCBsb2N1cy4KYGBge3J9CmFsbF9tYWZzIDwtIHNucF9mcmVxcyAlPiUKICBncm91cF9ieShMb2N1cywgU05QKSAlPiUKICBzdW1tYXJpc2UobWFmID0gbWluKEZyZXEpKSAlPiUKICBmaWx0ZXIobWFmIDw9IDAuNSkgICMgdGhpcyBnZXRzIHJpZCBvZiBtb25vbW9ycGhpYyBvbmVzCmJlc3RfbWFmcyA8LSBiZXN0X3NucHNfMTY1ICU+JQogIGdyb3VwX2J5KExvY3VzKSAlPiUKICBzdW1tYXJpc2UobWFmID0gbWluKEZyZXEpKQoKZ2dwbG90KGFsbF9tYWZzLCBhZXMoeSA9IExvY3VzLCB4ID0gbWFmKSkgKwogIGdlb21fcG9pbnQoKSArCiAgZ2VvbV9wb2ludChkYXRhID0gYmVzdF9tYWZzLCBjb2xvdXIgPSAicmVkIikKYGBgCldlIHdpbGwgY29tZSBiYWNrIHRvIHRoZXNlIHRvIGdyYWIgdGhlIGFsbGVsZSBmcmVxdWVuY2llcyBmb3IgZnVydGhlciBhbmFseXNpcy4KCkFuZCB1c2luZyBzb21lIG9mIHRoZSByZXN1bHRzIGZyb20gYWJvdmUsIGdldCB0aGUgOTYgU05QcyB0aGF0IGFyZSB0aGUgYmVzdCBmcm9tIGFtb25nIGFsbCAxNjUuCmBgYHtyfQp0b3Bfc25wc185Nl9hbGwgPC0gYmVzdF9tYWZzICU+JQogIGFycmFuZ2UoZGVzYyhtYWYpKSAlPiUKICBzbGljZSgxOjk2KQpgYGAKCgojIyMgU2VsZWN0aW5nIHRoZSBiZXN0IG1pY3JvaGFwcwoKRmlyc3Qgd2UgYXJlIGdvaW5nIHRvIGZpbmQgdGhlIG1pY3JvaGFwcyB3aXRoIHRoZSBoaWdoZXN0IGhldGVyb3p5Z29zaXR5LCBhbmQgdGhlIFNOUHMgd2l0aCB0aGUgaGlnaCBNQUYKYGBge3J9Cm1oYXBfaHogPC0gbWhhcHMgJT4lCiAgZ3JvdXBfYnkoTG9jdXMpICU+JSAKICBzdW1tYXJpc2UoaHogPSAxIC0gc3VtKEZyZXFeMiksIG5IYXBzID0gbigpKSAlPiUKICBhcnJhbmdlKGRlc2MoaHopKSAKCnRvcF9taGFwcyA8LSBtaGFwX2h6ICU+JQogIHNsaWNlKDE6OTYpCmBgYAoKCiMjIE1ha2UgRmlndXJlIDIuCgpIZXJlIHdlIGNyZWF0ZSBGaWcuIDIgLSB0aGUgcGxvdCB3aXRoIG1oYXAgaGV0ZXJvenlnb3NpdHkgYW5kIGJlc3Qgc25wIG1pbm9yIGFsbGVsZSBmcmVxdWVuY3kgcGVyIGxvY3VzLgpgYGB7ciBoei12cy1tYWYtcGVyLWxvY3VzfQojIHNucHMgPSBiZXN0X21hZnMKIyBtaGFwcyA9IG1oYXBfaHoKIyBhZGQgYSBjb2x1bW4gdGhhdCBkZXNpZ25hdGVzIG1hcmtlciB0eXBlCmJlc3Rfc25wX21hZnMgPC0gYmVzdF9tYWZzICU+JQogIG11dGF0ZSguLCBNYXJrZXJfdHlwZSA9ICJTTlBzIikgCgpuYW1lcyhiZXN0X3NucF9tYWZzKSA8LSBjKCJMb2N1cyIsICJoeiIsICJNYXJrZXJfdHlwZSIpCgpiZXN0X21oYXBfaHogPC0gbWhhcF9oeiAlPiUKICBtdXRhdGUoLiwgTWFya2VyX3R5cGUgPSAibWhhcHMiKQoKIyBuZWVkIHRvIGpvaW4gdGhlc2UgdGliYmxlcyB0b2dldGhlciBhbmQgdGhlbiBzb3J0IGJ5IGhpZ2hlc3QgaHoKY29tYm9faHogPC0gYmVzdF9taGFwX2h6ICU+JQogIGJpbmRfcm93cyguLCBiZXN0X3NucF9tYWZzKSAlPiUgCiAgZ3JvdXBfYnkoTG9jdXMpICU+JQogIGFycmFuZ2UoZGVzYyhoeikpCgpjb21ib19oeiRMb2N1cyA8LSBmYWN0b3IoY29tYm9faHokTG9jdXMsIGxldmVscyA9IGNvbWJvX2h6JExvY3VzKQoKY29tYm9fcGxvdCA8LSBjb21ib19oeiAlPiUKICBnZ3Bsb3QoLiwgYWVzKHggPSBMb2N1cywgeSA9IGh6LCBjb2xvciA9IE1hcmtlcl90eXBlKSkgKwogIGdlb21fcG9pbnQoKSArCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGMoInJlZCIsICJkYXJrIGJsdWUiKSwKICAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gcGFzdGUoYygibWljcm9oYXBzIiwgIlNOUHMiKSkpICsKICB0aGVtZV9idygpICsKICB5bGFiKCJIZXRlcm96eWdvc2l0eSIpICsgCiAgZ3VpZGVzKGNvbG9yID0gZ3VpZGVfbGVnZW5kKHRpdGxlPSJNYXJrZXIgVHlwZSIpKSArCiAgdGhlbWUoCiAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfYmxhbmsoKQogICkKCiMgbW9yZSBmb3JtYXR0aW5nCmNvbWJvX3Bsb3QgPC0gY29tYm9fcGxvdCArIHRoZW1lKAogIGF4aXMudGV4dC54PWVsZW1lbnRfYmxhbmsoKSwKICBheGlzLnRpdGxlLng9ZWxlbWVudF90ZXh0KHNpemU9MTQsIGZhY2U9ImJvbGQiKSwKICBheGlzLnRleHQueT1lbGVtZW50X3RleHQoc2l6ZT0xNCksCiAgYXhpcy50aXRsZS55PWVsZW1lbnRfdGV4dChzaXplPTE0LCBmYWNlPSJib2xkIiksCiAgbGVnZW5kLnRleHQ9ZWxlbWVudF90ZXh0KHNpemU9MTQpLAogIGxlZ2VuZC50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT0xNCwgZmFjZT0iYm9sZCIpKQoKZmlnMiA8LSBjb21ib19wbG90ICsKICAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gYygwLjg1LCAwLjg1KSkKCmZpZzIKCiMgc2F2ZSB0aGF0IHRvIGEgcGRmCmdnc2F2ZSgib3V0cHV0L0ZpZzIucGRmIikKCmBgYAoKTm93LCB3ZSBhcmUgZ29pbmcgdG8gbWFrZSBvdXIgQ0tNUi1yZWFkeSBhbGxlbGUgZnJlcXVlbmNpZXMgZm9yIGVhY2ggb2Ygb3VyIApmb3VyIGRhdGEgc2V0cyBpbiBhIG5hbWVkIGxpc3Q6CgojIyMgTWFraW5nIGEgbGlzdCBvZiBkYXRhIHNldHMKCmBgYHtyfQpmb3VyRGF0YV9saXN0IDwtIGxpc3QoCiAgbTE2NSA9IG1oYXBzLAogIHMxNjUgPSBiZXN0X3NucHNfMTY1LAogIG05NiA9IG1oYXBzICU+JSBmaWx0ZXIoTG9jdXMgJWluJSB0b3BfbWhhcHMkTG9jdXMpLAogIHM5Nl90b3AgPSBiZXN0X3NucHNfMTY1ICU+JSBmaWx0ZXIoTG9jdXMgJWluJSB0b3Bfc25wc185Nl9hbGwkTG9jdXMpCikKYGBgCgoKIyMgRG9pbmcgQ0tNUiBjYWxjcyBvbiBlYWNoIGRhdGEgc2V0CgpXZSBjYW4gZG8gZWFjaCBzdGVwLCBsYXBwbHlpbmcgb3ZlciB0aGluZ3M6CmBgYHtyfQpDS19saXN0IDwtIGxhcHBseShmb3VyRGF0YV9saXN0LCBmdW5jdGlvbih4KSAKICBjcmVhdGVfY2ttcih4LCBrYXBwYV9tYXRyaXggPSBrYXBwYXNbYygiUE8iLCAiRlMiLCAiSFMiLCAiVSIpLCBdKQopCmBgYAoKQW5kIHNpbXVsYXRlIHRoZSBRaWogdmFsdWVzLiAgRG8gMTBeNS4uLgpgYGB7ciwgY2FjaGU9VFJVRX0KUXNfbGlzdCA8LSBsYXBwbHkoQ0tfbGlzdCwgZnVuY3Rpb24oeCkgCiAgc2ltdWxhdGVfUWlqKEMgPSB4LCBmcm9tcyA9IGMoIlBPIiwgIkZTIiwgIkhTIiwgIlUiKSwgdG9zID0gYygiUE8iLCAiRlMiLCAiSFMiLCAiVSIpLCByZXBzID0gMTBeNSkKKQpgYGAKCkFuZCBvbmNlIHRoYXQgaXMgZG9uZSwgd2UgY2FuIGNvbGxlY3Qgc2FtcGxlcyBmcm9tIGl0OgpgYGB7cn0KRlBSc19ldGMgPC0gbGFwcGx5KFFzX2xpc3QsIGZ1bmN0aW9uKHgpIG1jX3NhbXBsZV9zaW1wbGUoeCwgbnUgPSBjKCJQTyIsICJGUyIsICJIUyIpLCBtZXRob2QgPSAiSVMiLCBGTlJzID0gc2VxKDAuMDEsIDAuMzAsIGJ5ID0gMC4wMSkpKSAlPiUKICBiaW5kX3Jvd3MoLmlkID0gIm1hcmtlcl9zZXQiKQogICAgICAgICAKYGBgCgpBbmQgbm93LCBsZXQgdXMgc3ByZWFkIHRoYXQgaW50byBhIGRhdGEgc2V0IHRoYXQgaXMgZWFzaWVyIHRvIHJlYWQ6CmBgYHtyfQpGUFJzX2V0YyAlPiUKICByZW5hbWUocmVsYXRpb25zaGlwID0gcHN0YXIpICU+JQogIHNlbGVjdChyZWxhdGlvbnNoaXAsIEZOUiwgbWFya2VyX3NldCwgRlBSKSAlPiUKICB0aWR5cjo6c3ByZWFkKGRhdGEgPSAuLCBrZXkgPSBtYXJrZXJfc2V0LCB2YWx1ZSA9IEZQUikKYGBgCgoKIyMjIE1ha2UgRmlndXJlIDMuCgpMZXQncyBwbG90IHRoZSBGUFJzX2V0YzoKYGBge3IgcGxvdF9GUFJzfQpGUFJfc2VzIDwtIEZQUnNfZXRjICU+JQogIG11dGF0ZShzZV9sbyA9IEZQUiAtIDIgKiBzZSwKICAgICAgIHNlX2hpID0gRlBSICsgMiAqIHNlKQoKIyBjcmVhdGUgYSBmYWN0b3IgZm9yIG9yZGVyaW5nIHRoZSByZWxhdGlvbnNoaXAgdHlwZQpGUFJzX2V0YyRwc3Rhcl9mID0gZmFjdG9yKEZQUnNfZXRjJHBzdGFyLCBsZXZlbHM9YygiUE8iLCJGUyIsICJIUyIpKQoKIyByZW1vdmUgdGhlIEhTIHJvd3MgYW5kIHRoZW4gcGxvdCB0aGF0CmYzIDwtIEZQUnNfZXRjICU+JQogIGZpbHRlciguLCBwc3Rhcl9mICE9ICJIUyIpICU+JQogIGdncGxvdCguLCBhZXMoeCA9IEZOUiwgeSA9IEZQUiwgc2hhcGUgPSBtYXJrZXJfc2V0KSkgKwogIGdlb21fcG9pbnQoKSArCiAjZ2VvbV9zZWdtZW50KGFlcyh4ID0gRk5SLCB5ID0gc2VfbG8sIHhlbmQgPSBGTlIsIHllbmQgPSBzZV9oaSkpICsgICMgdGhlc2UgYXJlIGJhc2ljYWxseSBpbnZpc2libGUgYmVjYXVzZSB0aGV5IGFyZSBzbyBzbWFsbAogIGZhY2V0X2dyaWQoLiB+IHBzdGFyX2YpICsKICBzY2FsZV95X2NvbnRpbnVvdXModHJhbnMgPSAibG9nMTAiKSArCiAgeGxhYigiRmFsc2UgTmVnYXRpdmUgUmF0ZSIpICsgCiAgeWxhYigiRmFsc2UgUG9zaXRpdmUgUmF0ZSAobG9nKSIpICsKICB0aGVtZV9idygpCgpmaWczIDwtIGYzICsKICBndWlkZXMoc2hhcGU9Z3VpZGVfbGVnZW5kKHRpdGxlPSJNYXJrZXIgU2V0IikpICsKICB0aGVtZSgKICBheGlzLnRleHQueD1lbGVtZW50X3RleHQoc2l6ZT0xNCksCiAgYXhpcy50aXRsZS54PWVsZW1lbnRfdGV4dChzaXplPTE0LCBmYWNlPSJib2xkIiksCiAgYXhpcy50ZXh0Lnk9ZWxlbWVudF90ZXh0KHNpemU9MTQpLAogIGF4aXMudGl0bGUueT1lbGVtZW50X3RleHQoc2l6ZT0xNCwgZmFjZT0iYm9sZCIpLAogIGxlZ2VuZC50ZXh0PWVsZW1lbnRfdGV4dChzaXplPTE0KSwKICBsZWdlbmQudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9MTQsIGZhY2U9ImJvbGQiKSkKCmZpZzMgPC0gZmlnMyArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gYygwLjYsIDAuMTUpKQoKZmlnMwoKIyBhbmQgc2F2ZSB0aGF0IHBsb3QgYXMgRmlnIDMuCmdnc2F2ZSgib3V0cHV0L0ZpZzMucGRmIiwgd2lkdGggPSA5LCBoZWlnaHQgPSA3LCB1bml0cyA9ICJpbiIpCgpgYGAKCgojIyBFeHBhbmRpbmcgZGF0YSBhc3N1bWluZyB0aGluZ3MgYXJlIHVubGlua2VkIG9yIGxpbmtlZAoKTm90ZTogZm9yIHRoaXMgc2VjdGlvbiwgeW91IG5lZWQgdG8gZG93bmxvYWQgYW5kIGluc3RhbGwgTWVuZGVsIChMYW5nZSBldCBhbC4gMjAxMykuCgpGaXJzdCwgd2UgYXJlIGdvaW5nIHRvIG5lZWQgdG8gYXNzdW1lIGEgbWFwLS0taS5lLiBhIGNvbGxlY3Rpb24gb2YgY2hyb21vc29tZXMgYW5kIGxlbmd0aHMuICBGb3Igbm93LCBJIGFtIGp1c3QgZ29pbmcgdG8gYXNzdW1lIDI1IGNocm9tb3NvbWVzIHRoYXQgdmFyeSBpbiBsZW5ndGggZnJvbSAyMDAgdG8gMTAwIE1iLCBhbmQgd2UgYXNzdW1lIDEgY00gcGVyIG1lZ2FiYXNlLgpgYGB7cn0KZmFrZUNocm9tcyA8LSB0aWJibGUoQ2hyb20gPSAxOjI1LAogICAgICAgICAgICAgICAgICAgICBsZW5ndGggPSBzZXEoMjAwZTA2LCAxMDBlMDYsIGxlbmd0aC5vdXQgPSAyNSkpCmBgYApUaGF0IGlzIGEgcHJldHR5ICJnZW5lcm91cyIgZ2Vub21lLCBpbiB0ZXJtcyBvZiBjaGFuY2VzIGZvciByZWNvbWJpbmF0aW9uLCBJIHRoaW5rLgoKTGV0J3MganVzdCBkbyB0aGlzIGFzIHNpbXBseSBhcyBwb3NzaWJsZSBhbmQgZHVwbGljYXRlIG91ciBkYXRhIDFYLCAyWCwgNFgsIDhYLCAxNlgsIDMyWCwgNjRYLgoKQmVmb3JlIEkgcmVxdWlyZWQgdGhhdCB3ZSByZXBsaWNhdGUgZXZlcnl0aGluZyBjb250aWd1b3VzbHksIGJ1dCBub3cgd2Ugd2FudCB0byBiZSBhYmxlIHRvCmp1c3QgZ28gc3RyYWlnaHQgdG8gMzJYLCBvciwgYWN0dWFsbHksIHdlIHdvdWxkIGxpa2UgdG8gbXVsdGlwbHkgdGhpbmdzIGJ5IGEgZmFjdG9yIG9yIDIgZWFjaCAKdGltZS4gIAoKT2YgY291cnNlLCBiZWZvcmUgdGhhdCwgd2Ugd2FudCB0byBoYXZlIHNvbWV0aGluZyB0aGF0IGFzc2lnbnMgY2hyb21vc29tZXMgYW5kIHBvc2l0aW9ucyB0byBlYWNoIG1hcmtlci4gCmBgYHtyfQojJyBAcGFyYW0gRCBhIGRhdGEgZnJhbWUgb2YgQ2hyb20gTG9jdXMgUG9zLCBBbGxlbGUsIExvY0lkeCwgQWxsZUlkeCBhbmQgRnJlcQojJyBAcGFyYW0gRkMgYSBkYXRhIGZyYW1lIG9mIGZha2UgY2hyb21vc29tZXMgd2l0aCBjaHJvbSBuYW1lcyBhbmQgbGVudGhzCiMnIEBkZXRhaWxzIFRoaXMgcmFuZG9tbHkgYXNzaWducyBlYWNoIExvY3VzIHRvIGEgcmFuZG9tIHBvc2l0aW9uIHdpdGhpbiBhIAojJyByYW5kb21seSBjaG9zZW4gY2hyb20uCnNwcmlua2xlX3Bvc2l0aW9ucyA8LSBmdW5jdGlvbihELCBGQykgewogIGxvY2kgPC0gdGliYmxlOjp0aWJibGUoTG9jdXMgPSB1bmlxdWUoRCRMb2N1cykpCiAgTCA8LSBucm93KGxvY2kpICAjIHRoZSBudW1iZXIgb2YgbG9jaSB3ZSBhcmUgZG9pbmcgaGVyZQogIAogICMgbm93LCBjaG9vc2Ugd2hpY2ggY2hyb21zIHRob3NlIGFyZSBvbi4gIFdlaWdodCBpdCBieSB0aGVpciBsZW5ndGgsIG9mIGNvdXJzZQogICMgYW5kIGFsc28gc2ltdWxhdGUgYSBwb3NpdGlvbiBpbiBpdC4gVGhlbiBiaW5kIGl0IHRvIHRoZSBMb2N1cyBuYW1lcwogIG5ld19wb3MgPC0gRkMgJT4lCiAgICBzYW1wbGVfbihzaXplID0gTCwgcmVwbGFjZSA9IFRSVUUsIHdlaWdodCA9IGxlbmd0aCkgJT4lCiAgICBtdXRhdGUoUG9zID0gZmxvb3IocnVuaWYobigpLCBtaW4gPSAxLCBtYXggPSBsZW5ndGgpKSkgJT4lCiAgICBtdXRhdGUoTG9jdXMgPSBsb2NpJExvY3VzKSAlPiUKICAgIHNlbGVjdChDaHJvbSwgTG9jdXMsIFBvcykKICAKICAjIG5vdywganVzdCBsZWZ0IGpvaW4gdGhhdCBvbnRvIEQgYnkgTG9jdXMuCiAgIyBhbmQgd2UgbWlnaHQgYXMgd2VsbCByZWluZGV4IHRoZW0sIGFsdGhvdWdoIGlmIHdlIGR1cGxpY2F0ZSB0aGUgZGF0YSAKICAjIHNldCB3ZSB3aWxsIGhhdmUgdG8gcmVpbmRleCB0aGVtIGFnYWluCiAgRCAlPiUKICAgIHNlbGVjdCgtQ2hyb20sIC1Qb3MpICU+JQogICAgbGVmdF9qb2luKG5ld19wb3MsIC4sIGJ5ID0gIkxvY3VzIikgJT4lCiAgICByZWluZGV4X21hcmtlcnMoKQp9CmBgYAoKQW5kIG5vdyB3ZSBqdXN0IG5lZWQgYSBmdW5jdGlvbiB0aGF0IHdpbGwgcmV0dXJuIGEgc2V0IG9mIGRhdGEganVzdCBsaWtlIHRoZSBwcmV2aW91cywKYnV0IHdpdGggbG9jdXMgbmFtZXMgdGhhdCBhcmUgc2xpZ2h0bHkgZGlmZmVyZW50LCBhbmQgd2l0aCB0aGUgZHVwbGljYXRlZCBvbmVzIGhhdmluZwpuZXcgcG9zaXRpb25zLCB3aGlsZSB0aGUgb2xkIG9uZXMga2VlcCB0aGUgc2FtZSBvbGQgcG9zaXRpb25zLgpgYGB7cn0KZGF0YV9kdXBsaWNhdG9yIDwtIGZ1bmN0aW9uKEQsIEZDLCBzdWZmaXggPSAiX3gyIikgewogIEQyIDwtIEQgJT4lCiAgICBtdXRhdGUoTG9jdXMgPSBwYXN0ZTAoTG9jdXMsIHN1ZmZpeCkpICU+JQogICAgc3ByaW5rbGVfcG9zaXRpb25zKC4sIEZDKQogIAogIHJlaW5kZXhfbWFya2VycyhiaW5kX3Jvd3MoRCwgRDIpKQp9CmBgYAoKTm93LCBoZXJlIGlzIGEgZnVuY3Rpb24gd2hpY2ggd2lsbCByZXR1cm4gYSBsaXN0IG9mIGRhdGEgc2V0cywgZWFjaCBvbmUgcmVwcmVzZW50aW5nCmEgMVgsIG9yIDJYLCBvciA0WCBkdXBsaWNhdGlvbi4uLgpgYGB7cn0KbWFrZV9kdXBpZXMgPC0gZnVuY3Rpb24oRCwgRkMpIHsKICAKICByZXQgPC0gbGlzdCgpCiAgcmV0W1sxXV0gPC0gc3ByaW5rbGVfcG9zaXRpb25zKEQsIEZDKQogIAogIGZvciAoaSBpbiAxOjYpIHsKICAgIGlkeCA8LSAxICsgaQogICAgc3VmZiA8LSBwYXN0ZTAoIngiLCAyXmkpCiAgICByZXRbW2lkeF1dIDwtIGRhdGFfZHVwbGljYXRvcihyZXRbW2ldXSwgRkMsIHN1ZmZpeCA9IHN1ZmYpCiAgfQogIAogIG5hbWVzKHJldCkgPC0gcGFzdGUwKCJ4IiwgMiBeICgwOjYpKQogIHJldAp9CmBgYAoKQW5kIGhlcmUgd2UgY3JlYXRlIGR1cGxpY2F0ZSB2ZXJzaW9ucyBvZiB0aGUgOTYgbWljcm9oYXBzIGFuZCB0aGUgOTYgU05Qcy4gIE5vdGUgdGhhdApwb3NpdGlvbnMgYXJlIG5vdCB0aGUgc2FtZSBpbiBlYWNoLCBidXQgYXJlIGp1c3QgcmFuZG9tbHkgc3ByaW5rbGVkIGZvciBlYWNoIG1hcmtlcgp0eXBlLCBidXQgdGhhdCBzaG91bGQgYmUgZmluZS4uLgpgYGB7cn0Kc2V0LnNlZWQoNTU1KQptaGFwX2R1cGllX2xpc3QgPC0gbWFrZV9kdXBpZXMoZm91ckRhdGFfbGlzdCRtOTYsIGZha2VDaHJvbXMpCnNucF9kdXBpZV9saXN0IDwtIG1ha2VfZHVwaWVzKGZvdXJEYXRhX2xpc3Qkczk2X3RvcCwgZmFrZUNocm9tcykKYGBgCgpBbmQsIGZpbmFsbHksIGFsbCB3ZSBuZWVkIGlzIGEgZnVuY3Rpb24gdGhhdCB3aWxsIGRvIHRoZSBsaW5rZWQgYW5kIHVubGlua2VkIHNpbXVsYXRpb24gZm9yIGVhY2ggb2YgdGhlc2UuCkkgYW0gZ29pbmcgdG8gZG8gaXQgZm9yIG9uZSByZWxhdGlvbnNoaXAgYXQgYSB0aW1lLi4uCmBgYHtyfQpzaW1fbGlua2VkX2FuZF91bmxpbmtlZF9ocyA8LSBmdW5jdGlvbihEKSB7CiAgQ0sgPC0gY3JlYXRlX2NrbXIoRCkKICBRVV91bmxpbmtlZCA8LSBzaW11bGF0ZV9RaWooQ0ssIGZyb21zID0gYygiVSIsICJIUyIpLCB0b3MgPSBjKCJVIiwgIkhTIiksIHJlcHMgPSAxZTQpCiAgUVVfbGlua2VkIDwtIHNpbXVsYXRlX1FpaihDSywgZnJvbXMgPSBjKCJIUyIpLCB0b3MgPSBjKCJVIiwgIkhTIiksIHJlcHMgPSAxZTA0LCB1bmxpbmtlZCA9IEZBTFNFLCBwZWRpZ3JlZV9saXN0ID0gcGVkaWdyZWVzKQogIAogIGxpbmsgPC0gbWNfc2FtcGxlX3NpbXBsZShRID0gUVVfdW5saW5rZWQsIG51ID0gIkhTIiwgZGUgPSAiVSIsIG1ldGhvZCA9ICJJUyIsIEZOUnMgPSBzZXEoMC4wNSwgMC4zLCBieSA9IDAuMDUpLCBRX2Zvcl9mbnJzID0gUVVfbGlua2VkKSAlPiUKICAgIG11dGF0ZShzaW1fdHlwZSA9ICJsaW5rZWQiKQogIHVubGluayA8LSBtY19zYW1wbGVfc2ltcGxlKFEgPSBRVV91bmxpbmtlZCwgbnUgPSAiSFMiLCBkZSA9ICJVIiwgbWV0aG9kID0gIklTIiwgRk5ScyA9IHNlcSgwLjA1LCAwLjMsIGJ5ID0gMC4wNSkpICU+JQogICAgbXV0YXRlKHNpbV90eXBlID0gInVubGlua2VkIikKICAKICBiaW5kX3Jvd3MobGluaywgdW5saW5rKQp9CmBgYAoKCkFuZCBub3csIGZpcmUgaXQgdXAgZm9yIGhhbGYtc2licyBhbmQgU05QczoKYGBge3J9CnNucHNfaHNfcmVzbGlzdCA8LSBsYXBwbHkoc25wX2R1cGllX2xpc3QsIHNpbV9saW5rZWRfYW5kX3VubGlua2VkX2hzKQpgYGAKCkFuZCwgYWZ0ZXIgZG9pbmcgdGhhdCwgd2UgY2FuIHBsb3QgdGhlIHJlc3VsdHMgYXQgYSBjb25zdGFudCBmYWxzZSBuZWdhdGl2ZSByYXRlLCBsZXQncyBzYXkgMC4wNToKYGBge3J9CmhzMDUgPC0gYmluZF9yb3dzKHNucHNfaHNfcmVzbGlzdCwgLmlkID0gImRhdGFfcmVwcyIpICU+JQogIGZpbHRlcihuZWFyKEZOUiwgMC4wNSkpICU+JQogIG11dGF0ZShkYXRhX3JlcHMgPSBwYXJzZV9udW1iZXIoZGF0YV9yZXBzKSwKICAgICAgICAgbnVtX21hcmtlcnMgPSA5NiAqIGRhdGFfcmVwcykKCmdncGxvdChoczA1LCBhZXMoeCA9IG51bV9tYXJrZXJzLCB5ID0gRlBSLCBjb2xvdXIgPSBzaW1fdHlwZSkpICsKICBnZW9tX2xpbmUoKSArCiAgZ2VvbV9wb2ludCgpICsKICBzY2FsZV95X2NvbnRpbnVvdXModHJhbnMgPSAibG9nMTAiKQpgYGAKCk9LLCBzbyB0aGUgbGlua2FnZSBkb2Vzbid0IG1ha2UgaXQgcGxhdGVhdS4uLml0IGp1c3QgaW5jcmVhc2VzIHRoZSBudW1iZXIgb2YgbmVjZXNzYXJ5Cm1hcmtlcnMgYnkgYSBwcmVkaWN0YWJsZSBhbW91bnQuICBCYXNpY2FsbHksIGl0IGNoYW5nZXMgdGhlIHNsb3BlIG9mIHRoZSByZWxhdGlvbnNoaXAuCgpOb3csIGxldCdzIGhhdmUgYSBsb29rIGF0IGhvdyB0aGluZ3Mgd29yayB3aXRoIHRoZSBtaWNyb2hhcGxvdHlwZXMsIGJ1dCBvbmx5IGRvIHRoZSBmaXJzdCBmaXZlIAoodXAgdG8gMTZYKS4gIApgYGB7cn0KbWhhcHNfaHNfcmVzbGlzdCA8LSBsYXBwbHkobWhhcF9kdXBpZV9saXN0WzE6NV0sIHNpbV9saW5rZWRfYW5kX3VubGlua2VkX2hzKQpgYGAKCmBgYHtyfQpoczA1X21oIDwtIGJpbmRfcm93cyhtaGFwc19oc19yZXNsaXN0LCAuaWQgPSAiZGF0YV9yZXBzIikgJT4lCiAgZmlsdGVyKG5lYXIoRk5SLCAwLjA1KSkgJT4lCiAgbXV0YXRlKGRhdGFfcmVwcyA9IHBhcnNlX251bWJlcihkYXRhX3JlcHMpLAogICAgICAgICBudW1fbWFya2VycyA9IDk2ICogZGF0YV9yZXBzKSAlPiUKICBtdXRhdGUobWFya2VyX3R5cGUgPSAibWhhcCIpCgpjb21ib19kZiA8LSBoczA1ICU+JQogIGZpbHRlcihkYXRhX3JlcHMgPD0gMTYpICU+JQogIG11dGF0ZShtYXJrZXJfdHlwZSA9ICJTTlAiKSAlPiUKICBiaW5kX3Jvd3MoLiwgaHMwNV9taCkKICAKCmdnIDwtIGdncGxvdChjb21ib19kZiwgYWVzKHggPSBudW1fbWFya2VycywgeSA9IEZQUiwgY29sb3VyID0gbWFya2VyX3R5cGUsIGxpbmV0eXBlID0gc2ltX3R5cGUsIHNoYXBlID0gbWFya2VyX3R5cGUpKSArCiAgZ2VvbV9wb2ludCgpICsKICBnZW9tX2xpbmUoKSArCiAgc2NhbGVfeV9jb250aW51b3VzKHRyYW5zID0gImxvZzEwIiwgYnJlYWtzID0gMTAgXiAoLShzZXEoMywyOCwgYnkgPSAyKSkpKQoKIyBwcmludCBpdApnZwpgYGAKCk5vdywgem9vbSBpbiBvbiB0aGUgbGVmdCBwYXJ0IG9mIHRoZSBncmFwaDoKYGBge3J9CmdnICsKICBjb29yZF9jYXJ0ZXNpYW4oeGxpbSA9IGMoMCwgMTIwMCksIHlsaW0gPSBjKDFlLTAwLCAxZS0xNykpCgpgYGAKCiMjIyBOb3cgbGV0J3MgbG9vayBhdCBGTlIgPSAwLjAxCgpUdXJucyBvdXQgdGhhdCB3ZSBtaWdodCB3YW50IHRvIHVzZSB0aGUgMC4wMSBGTlIgY3V0b2ZmIGZvciB0aGUgcGFwZXIuIFNvLCBsZXQncyBnZXQgYWxsIHRoZSByZXN1bHRzIGFuZCBtYWtlIGFuIFJEUyBzbyBJIGRvbid0IGhhdmUgdG8gcmVkbyB0aGVtISEKYGBge3IgZnVsbC1yZXN1bHRzfQpmdWxsX3Jlc3VsdHMgPC0gbGlzdCgKICBtaGFwID0gYmluZF9yb3dzKG1oYXBzX2hzX3Jlc2xpc3QsIC5pZCA9ICJkYXRhX3JlcHMiKSwKICBTTlAgPSBiaW5kX3Jvd3Moc25wc19oc19yZXNsaXN0LCAuaWQgPSAiZGF0YV9yZXBzIikKKSAlPiUKICBiaW5kX3Jvd3MoLmlkID0gIm1hcmtlcl90eXBlIikgJT4lIAogIG11dGF0ZShkYXRhX3JlcHMgPSBwYXJzZV9udW1iZXIoZGF0YV9yZXBzKSkgJT4lCiAgbXV0YXRlKG51bV9tYXJrZXJzID0gOTYgKiBkYXRhX3JlcHMpICU+JQogIHNlbGVjdChtYXJrZXJfdHlwZSwgZGF0YV9yZXBzLCBudW1fbWFya2Vycywgc2ltX3R5cGUsIGV2ZXJ5dGhpbmcoKSkKCmZ1bGxfcmVzdWx0cwpgYGAKQW5kIG5vdywgbGV0J3Mgc2F2ZSB0aGF0IGFzIHdlbGw6CmBgYHtyIHNhdmVfZnVsbH0Kc2F2ZVJEUyhmdWxsX3Jlc3VsdHMsIGZpbGUgPSAiLi4vb3V0cHV0cy9mdWxsX3Jlc3VsdHNfc25wc192X21oYXBzX2hhbGZfc2lic19saW5rZWRfdl91bmxpbmtlZC5yZHMiKQpgYGAKQW5kIG5vdyB3ZSBjYW4gbG9vayBhdCB0aGluZ3Mgd2l0aCBhbiBGTlIgb2YgMC4wMToKYGBge3J9CmZ1bGxfcmVzdWx0cyAlPiUKICBmaWx0ZXIobmVhcihGTlIsIDAuMDEpKQpgYGAKCiMjIE1ha2UgRmlndXJlIDQuCgpgYGB7ciBtYWtlLWZpZy1mb3VyfQojIFdlIGNhbiB1c2UgdGhlIHNhdmVkIG91dHB1dCBmb3IgdGhpcy4KIyBnZXQgdGhlIHJlc3VsdHMgYW5kIGZpbHRlciBqdXN0IHRvIHRoZSBwb2ludHMgd2Ugd2FudCB0byBrZWVwCmZpZzRyZXMgPC0gcmVhZFJEUygiZGF0YS9mdWxsX3Jlc3VsdHNfc25wc192X21oYXBzX2hhbGZfc2lic19saW5rZWRfdl91bmxpbmtlZC5yZHMiKSAlPiUKICBmaWx0ZXIobmVhcihGTlIsIDAuMDEpKSAlPiUKICBmaWx0ZXIoKG1hcmtlcl90eXBlID09ICJtaGFwIiAmIGRhdGFfcmVwcyA8PSA4KSB8IChtYXJrZXJfdHlwZSA9PSAiU05QIiAmIGRhdGFfcmVwcyA8PSAxNikpCgojIHRoZW4gcGxvdCBpdApmNCA8LSBnZ3Bsb3QoZmlnNHJlcywgYWVzKHggPSBudW1fbWFya2VycywgeSA9IEZQUiwgY29sb3IgPSBtYXJrZXJfdHlwZSwgbGluZXR5cGUgPSBzaW1fdHlwZSwgc2hhcGUgPSBtYXJrZXJfdHlwZSkpICsKICBnZW9tX3BvaW50KCkgKwogIGdlb21fbGluZSgpICsKICBzY2FsZV95X2NvbnRpbnVvdXModHJhbnMgPSAibG9nMTAiLCBicmVha3MgPSAxMF4oLXNlcSgwLCAyMCwgYnkgPSAyKSkpCiAKZjQgPC0gZjQgKyB0aGVtZV9idygpICsKICB5bGFiKCJGYWxzZSBQb3NpdGl2ZSBSYXRlIikgKyAKICB4bGFiKCJOdW1iZXIgb2YgTWFya2VycyIpICsKICBndWlkZXMoY29sb3IgPSBndWlkZV9sZWdlbmQodGl0bGU9Ik1hcmtlciBUeXBlIiksIHNoYXBlID0gZ3VpZGVfbGVnZW5kKHRpdGxlID0gIk1hcmtlciBUeXBlIiksIGxpbmV0eXBlID0gZ3VpZGVfbGVnZW5kKHRpdGxlID0gIlNpbXVsYXRpb24gVHlwZSIpKQogICAgCmZpZzQgPC0gZjQgKyBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gYygicmVkIiwgImRhcmsgYmx1ZSIpLAogICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBwYXN0ZShjKCJtaWNyb2hhcHMiLCAiU05QcyIpKSkgKwogIHNjYWxlX3NoYXBlX21hbnVhbCh2YWx1ZXMgPSBjKDE2LCAxNyksIAogICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBwYXN0ZShjKCJtaWNyb2hhcHMiLCAiU05QcyIpKSkgKyAKICB0aGVtZSgKICBheGlzLnRleHQueD1lbGVtZW50X3RleHQoc2l6ZT0xNCksCiAgYXhpcy50aXRsZS54PWVsZW1lbnRfdGV4dChzaXplPTE0LCBmYWNlPSJib2xkIiksCiAgYXhpcy50ZXh0Lnk9ZWxlbWVudF90ZXh0KHNpemU9MTQpLAogIGF4aXMudGl0bGUueT1lbGVtZW50X3RleHQoc2l6ZT0xNCwgZmFjZT0iYm9sZCIpLAogIGxlZ2VuZC50ZXh0PWVsZW1lbnRfdGV4dChzaXplPTE0KSwKICBsZWdlbmQudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9MTQsIGZhY2U9ImJvbGQiKSkKCmZpZzQgPC0gZmlnNCArCiAgIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9IGMoMC44NSwgMC44MCkpCgpmaWc0IDwtIGZpZzQgKyB0aGVtZShwbG90Lm1hcmdpbiA9IHVuaXQoYygxLDEsMSwxKSwgImNtIikpCgpmaWc0CgojIHNhdmUgdGhhdCBhcyBGaWcuIDQKZ2dzYXZlKCJvdXRwdXQvRmlnNC5wZGYiLCB3aWR0aCA9IDEwLCBoZWlnaHQgPSA3LCB1bml0cyA9ICJpbiIpCmBgYApCYXNpY2FsbHksIGlmIHdlIHdhbnQgRlBSJ3Mgb24gdGhlIG9yZGVyIG9mIDFlLTA5IHdlIGFyZSBnb2luZyB0byB3YW50IGFib3V0IDQwMCBtaWNyb2hhcHMgKHRha2luZyBhY2NvdW50IG9mIGxpa2VseSBwYXR0ZXJucyBvZiBsaW5rYWdlKS4gVG8gZ2V0IHRoZSBzYW1lIHNvcnQgb2YgcG93ZXIsIHdlIHdvdWxkIG5lZWQgMTEwMCBTTlBzLgoKU28sIHRoZSBpc3N1ZSBvZiBwaHlzaWNhbCBsaW5rYWdlIGlzIG5vdCBhIHRvdGFsIGRlYWwtYnJlYWtlciBmb3IgU05QcywgYnV0IHdlIHN0aWxsIGRvIGJldHRlcgp3aXRoIG1pY3JvaGFwcy4gQW5kIHRoZSBtaWNyb2hhcCBudW1iZXJzIHRoZXJlIHB1dCB1cyBpbiB0aGUgcmVhbG0gaW4gd2hpY2ggb25lIGNvdWxkIHN0aWxsCmJlIGRvaW5nIGFtcGxpY29uIHNlcXVlbmNpbmcsIGFzIG9wcG9zZWQgdG8gaGF2aW5nIHRvIHJlc29ydCB0byBzb21ld2hhdCBsZXNzIHJlbGlhYmxlIGFuZCBtb3JlIGV4cGVuc2l2ZSBtZXRob2RzLgoKCg==