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:
- 20 reads per haplotype
- 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:
- m165: all 165 microhaps. (Note that after filtering we have 165 microhaps)
- s165: the highest MAF SNP from each of the 165 loci.
- m96: the 96 microhaps with the highest heterozygosity amongst the 165.
- s96_top: the 96 SNPs with the highest MAFs from amongst the s165.
- s96_m: 96 SNPs, each being the highest MAF SNP from each of the m96.
# 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
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)
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==