S.pyogenes 5448 pdxR RNASeq version: 202204
Preprocessing
I used my cyoa tool to process these samples by doing the following:
- Copying the data from the sequencer into the directory ‘preprocessing/’
- Used a slightly involved shell command to create a directory for each sample and copy the reads for it to the ‘unprocessed/’ subdirectory within it.
- invoked the following:
cd preprocessing
start=$(pwd)
for i in $(/bin/ls -d ./*)
do
cd $i
rm -rf outputs scripts
cyoa --task pipe --method prnas --species spyogenes_5448 \
--gff_type gene --gff_tag locus_tag \
--input $(/bin/ls unprocessed/* | tr '\n' ':' | sed 's/:$//g')
cd $start
done
The above for loop goes into each sample and does the following:
- Trims the data, heavily compresses the outputs.
- Runs fastqc
- Runs hisat2 using my spyogenes_5448 indices.
- Converts the sam alignment to sorted/indexed bam.
- Makes a couple of extra copies of it with some filters.
- Compresses the aligned/unaligned reads.
- Runs htseq-count on the alignments to count reads/gene.
Note the following steps were not actually run because I had a speeling error. But since they are not necessary for the explicitly RNASeq analyses I first want to do, I ignored it. I am curious though to see if there are other mutations in these strains, so I will likely run those portions manually.
- Runs freebayes on the alignments to look for variants.
- Sorts/compresses the freebayes output.
- Does some parsing of the freebayes output and provides some tables about where mutations were observed.
Collect annotation information
Same two primary annotation sources, the gff file used for mapping/counting, and microbesonline.org. Note that since I moved to just downloading the material from the web interface, I no longer have a handy method to get the taxon ID, so I go there and hunt down the taxId manually.
Now that I am thinking about it, my 5448 genome/annotations are kind of old, I will ask and check to see if there is anything newer.
Also, 5448 does not have an entry at microbesonline.org, a fact which I forgot. I need to go poking in my notes to reconnect 5005 and 5448.
gff_annot <- load_gff_annotations("reference/spyogenes_5448.gff", type = "gene")
## Trying attempt: rtracklayer::import.gff3(gff, sequenceRegionsAsSeqinfo = TRUE)
## Trying attempt: rtracklayer::import.gff3(gff, sequenceRegionsAsSeqinfo = FALSE)
## Had a successful gff import with rtracklayer::import.gff3(gff, sequenceRegionsAsSeqinfo = FALSE)
## Returning a df with 14 columns and 1814 rows.
rownames(gff_annot) <- gff_annot[["locus_tag"]]
head(gff_annot)
## seqnames start end width strand source type score
## SP5448_00005 CP008776 232 1587 1356 + EMBL/GenBank/SwissProt gene NA
## SP5448_00010 CP008776 1742 2878 1137 + EMBL/GenBank/SwissProt gene NA
## SP5448_00015 CP008776 2953 3150 198 + EMBL/GenBank/SwissProt gene NA
## SP5448_00020 CP008776 3480 4595 1116 + EMBL/GenBank/SwissProt gene NA
## SP5448_00025 CP008776 4665 5234 570 + EMBL/GenBank/SwissProt gene NA
## SP5448_00030 CP008776 5237 8740 3504 + EMBL/GenBank/SwissProt gene NA
## phase locus_tag gene gene_synonym note pseudo
## SP5448_00005 1 SP5448_00005 <NA> <NA> <NA>
## SP5448_00010 1 SP5448_00010 <NA> <NA> <NA>
## SP5448_00015 1 SP5448_00015 <NA> <NA> <NA>
## SP5448_00020 1 SP5448_00020 <NA> <NA> <NA>
## SP5448_00025 1 SP5448_00025 <NA> <NA> <NA>
## SP5448_00030 1 SP5448_00030 <NA> <NA> <NA>
mgas_data <- load_genbank_annotations(accession="CP008776")
## Loading required namespace: rentrez
## Done Parsing raw GenBank file text. [ 13.601 seconds ]
## 2022-04-26 15:23:50 Starting creation of gene GRanges
## 2022-04-26 15:23:53 Starting creation of CDS GRanges
## 2022-04-26 15:24:00 Starting creation of exon GRanges
## No exons read from genbank file. Assuming sections of CDS are full exons
## 2022-04-26 15:24:02 Starting creation of variant VRanges
## 2022-04-26 15:24:02 Starting creation of transcript GRanges
## No transcript features (mRNA) found, using spans of CDSs
## 2022-04-26 15:24:03 Starting creation of misc feature GRanges
## Warning in fill_stack_df(feats[!typs %in% c("gene", "exon", "CDS",
## "variation", : Got unexpected multi-value field(s) [ inference ]. The resulting
## column(s) will be of class CharacterList, rather than vector(s). Please contact
## the maintainer if multi-valuedness is expected/meaningful for the listed
## field(s).
## 2022-04-26 15:24:03 - Done creating GenBankRecord object [ 13.042 seconds ]
genome_size <- GenomicRanges::width(mgas_data$seq)
## Error in (function (classes, fdef, mtable) : unable to find an inherited method for function 'width' for signature '"NULL"'
mgas_cds <- as.data.frame(mgas_data$cds)
rownames(mgas_cds) <- mgas_cds[["locus_tag"]]
wanted <- ! colnames(mgas_cds) %in% c("translation", "type", "strand", "seqnames", "start", "end", "locus_tag", "note", "gene", "gene_synonym", "width")
mgas_cds <- mgas_cds[, wanted]
mgas_annot <- merge(mgas_cds, gff_annot, by="row.names")
rownames(mgas_annot) <- mgas_annot[["Row.names"]]
mgas_annot[["Row.names"]] <- NULL
ref_gff_annot <- load_gff_annotations("reference/spyogenes_5005.gff", type="CDS")
## Trying attempt: rtracklayer::import.gff3(gff, sequenceRegionsAsSeqinfo = TRUE)
## Trying attempt: rtracklayer::import.gff3(gff, sequenceRegionsAsSeqinfo = FALSE)
## Had a successful gff import with rtracklayer::import.gff3(gff, sequenceRegionsAsSeqinfo = FALSE)
## Returning a df with 19 columns and 1841 rows.
microbes_annot <- load_microbesonline_annotations(species="5005")
## Found 1 entry.
## Streptococcus pyogenes MGAS5005Firmicutesyes2005-08-25yes101950293653
## The species being downloaded is: Streptococcus pyogenes MGAS5005
## Downloading: http://www.microbesonline.org/cgi-bin/genomeInfo.cgi?tId=293653;export=tab
Rerunning my fasta36 mapper across strains
Once upon a time I wrote a little tool to compare closely species/strains. It is essentially a poor-man’s ortholog search. It has been a very long time since last I used it, and it required some modifications in order to work properly. The most notable problem is that the pathnames it uses are too long for fasta36. I shortened them and fixed a couple of old errors and it appears to work again.
single_hits <- readr::read_tsv("single_multi/outputs/fasta_spyogenes_5448_cds_spyogenes_5005_cds/spyogenes_5448_cds_singles.txt")
## Rows: 1340 Columns: 2
## ── Column specification ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
## Delimiter: "\t"
## chr (2): AKK69518.1, AAZ50620.1:1.000:0
##
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
colnames(single_hits) <- c("from", "to")
single_hits[["to"]] <- gsub(pattern="(^.*?):.*$", replacement="\\1",
x=single_hits[["to"]], perl=TRUE)
single_both <- merge(mgas_annot, single_hits, by.x="protein_id", by.y="from", all.x=TRUE)
single_both <- merge(single_both, ref_gff_annot, by.x="to", by.y="protein_id", all.x=TRUE)
missing_ids <- is.na(single_both[["old_locus_tag"]])
new_ids <- single_both[missing_ids, "locus_tag.x"]
single_both[missing_ids, "old_locus_tag"] <- new_ids
single_both <- merge(single_both, microbes_annot, by.x="old_locus_tag", by.y="sysName", all.x=TRUE)
rownames(single_both) <- make.names(single_both[["gene_id"]], unique=TRUE)
Create expressionSet
Note that I did the following to the sample sheet provided by Dr. McIver:
- Changed the dP_R20_4 sample to dP_R20_2 (The original sample name is still there are the original column).
- Added columns at the end containing the count table locations.
- Added columns ‘short_media’, ‘growth_phase’, ‘genotype’ which hopefully contain the relevant metadata extracted from the sample descriptions.
- Added a column ‘Experiment’ which is either ‘rofA’ or ‘pdxR’.
pdxr_expt <- create_expt(metadata = "sample_sheets/all_samples.xlsx",
gene_info = single_both, file_column = "spyogenes5448genecounts") %>%
subset_expt(subset="experiment=='pdxR'") %>%
set_expt_conditions(fact="genotype") %>%
set_expt_batches(fact="growthphase")
## Reading the sample metadata.
## Did not find the condition column in the sample sheet.
## Filling it in as undefined.
## Did not find the batch column in the sample sheet.
## Filling it in as undefined.
## The sample definitions comprises: 72 rows(samples) and 27 columns(metadata fields).
## Matched 1723 annotations and counts.
## Bringing together the count matrix and gene information.
## Some annotations were lost in merging, setting them to 'undefined'.
## Saving the expressionset to 'expt.rda'.
## The final expressionset has 1814 rows and 72 columns.
## subset_expt(): There were 72, now there are 36 samples.
I have 36 samples to play with, let us see what they look like.
Poke expressionSet
pdxr_libsize <- plot_libsize(pdxr_expt)
pdxr_libsize$plot

pdxr_filter_plot <- plot_libsize_prepost(pdxr_expt)
pdxr_filter_plot$count_plot

pdxr_filter_plot$lowgene_plot
## Warning: Using alpha for a discrete variable is not advised.

pdxr_nonzero <- plot_nonzero(pdxr_expt)
pdxr_nonzero$plot
## Warning: ggrepel: 6 unlabeled data points (too many overlaps). Consider
## increasing max.overlaps

written <- write_expt(pdxr_expt, excel = glue::glue("excel/pdxr_expt-v{ver}.xlsx"))
## Deleting the file excel/pdxr_expt-v202204.xlsx before writing the tables.
## Writing the first sheet, containing a legend and some summary data.
## `geom_smooth()` using formula 'y ~ x'
## `geom_smooth()` using formula 'y ~ x'
## Loading required package: Matrix
##
## Attaching package: 'Matrix'
## The following object is masked from 'package:S4Vectors':
##
## expand
##
## Total:13 s
## `geom_smooth()` using formula 'y ~ x'
## `geom_smooth()` using formula 'y ~ x'
##
## Total:15 s
Quick visualizations
Without considering time
Let us start with some views of the data without thinking about batch effects.
pdxr_norm <- normalize_expt(pdxr_expt, filter=TRUE, norm="quant", convert="cpm", transform="log2")
## Removing 31 low-count genes (1783 remaining).
## transform_counts: Found 12 values equal to 0, adding 1 to the matrix.
pdxr_pca <- plot_pca(pdxr_norm)
pdxr_pca$plot

pdxr_heatmap <- plot_disheat(pdxr_norm)
pdxr_heatmap$plot

pdxr_sm <- plot_sm(pdxr_norm)
## Performing correlation.

Considering time
Repeat the previous plots, but this time using limma’s batch removal method (which is just a residuals).
pdxr_nb <- normalize_expt(pdxr_expt, filter=TRUE, norm="quant", convert="cpm",
transform="log2", batch="limma")
## Removing 31 low-count genes (1783 remaining).
## If you receive a warning: 'NANs produced', one potential reason is that the data was quantile normalized.
## Setting 29 low elements to zero.
## transform_counts: Found 29 values equal to 0, adding 1 to the matrix.
pdxr_nb_pca <- plot_pca(pdxr_nb)
pdxr_nb_pca$plot

pdxr_nb_heatmap <- plot_corheat(pdxr_nb)
pdxr_nb_heatmap$plot

Well, it is pretty clear that time is the dominant factor.
Differential Expression analyses
I am going to do the DE in 3 separate pieces:
- Only compare strains
- Only compare times
- Compare the concatenation of strains and times.
Strain only comparisons
strain_de <- all_pairwise(pdxr_expt, model_batch=TRUE, filter=TRUE)
## Using limma's removeBatchEffect to visualize with(out) batch inclusion.
## Finished running DE analyses, collecting outputs.
## Comparing analyses.

strain_keepers <- list(
"delta_wt" = c("delta", "WT"),
"complement_wt" = c("complement", "WT"),
"delta_complement" = c("delta", "complement"))
strain_tables <- combine_de_tables(
strain_de, keepers = strain_keepers,
excel = glue::glue("excel/pdxr_strain_tables-v{ver}.xlsx"))
## Deleting the file excel/pdxr_strain_tables-v202204.xlsx before writing the tables.
strain_sig <- extract_significant_genes(
strain_tables,
excel = glue::glue("excel/pdxr_strain_sig-v{ver}.xlsx"))
## Deleting the file excel/pdxr_strain_sig-v202204.xlsx before writing the tables.
Time only comparisons
pdxr_time <- set_expt_conditions(pdxr_expt, fact="growthphase") %>%
set_expt_batches(fact="genotype")
time_de <- all_pairwise(pdxr_time, model_batch=TRUE)
## Using limma's removeBatchEffect to visualize with(out) batch inclusion.
## Finished running DE analyses, collecting outputs.
## Comparing analyses.

time_keepers <- list(
"e20m_start" = c("e20m", "start"),
"e60m_e20m" = c("e60m", "e20m"),
"e60m_start" = c("e60m", "start"))
time_tables <- combine_de_tables(
time_de, keepers = time_keepers,
excel = glue::glue("excel/pdxr_time_tables-v{ver}.xlsx"))
## Deleting the file excel/pdxr_time_tables-v202204.xlsx before writing the tables.
time_sig <- extract_significant_genes(
time_tables,
excel = glue::glue("excel/pdxr_time_sig-v{ver}.xlsx"))
## Deleting the file excel/pdxr_time_sig-v202204.xlsx before writing the tables.
Compare times vs strains
I always worry that comparing a data set across multiple conditions results in not what I think it will. Let us therefore plot the logFCs of likely contrasts against each other.
x_axis <- time_tables[["data"]][["start_e20m"]][, c("deseq_logfc", "deseq_adjp")]
y_axis <- strain_tables[["data"]][["delta_wt"]][, c("deseq_logfc", "deseq_adjp")]
both <- merge(x_axis, y_axis, by="row.names")
rownames(both) <- both[["Row.names"]]
both[["Row.names"]] <- NULL
cor.test(both[["deseq_logfc.x"]], both[["deseq_logfc.y"]], method="spearman")
## Error in cor.test.default(both[["deseq_logfc.x"]], both[["deseq_logfc.y"]], : 'x' must be a numeric vector
plotted <- plot_linear_scatter(both[, c("deseq_logfc.x", "deseq_logfc.y")])
## Error in `[.data.frame`(both, , c("deseq_logfc.x", "deseq_logfc.y")): undefined columns selected
## Error in eval(expr, envir, enclos): object 'plotted' not found
Interaction model
I want to make sure that my methods of performing interaction models work as I think it does, and this data set looks to me to be a perfect place to test that.
combined_factors <- paste0(pData(pdxr_expt)[["genotype"]], "_",
pData(pdxr_expt)[["growthphase"]])
combined_expt <- set_expt_conditions(pdxr_expt, fact=combined_factors) %>%
combined_de <- all_pairwise(combined_expt, model_batch="svaseq", filter=TRUE)
combined_keepers <- list(
"e20m_delta_vs_wt" = c("deltae20m", "WTe20m"),
"e20m_delta_vs_complement" = c("deltae20m", "complemente20m"),
"e60m_delta_vs_wt" = c("deltae60m", "WTe60m"),
"e60m_delta_vs_complement" = c("deltae60m", "complemente60m"),
"start_delta_vs_wt" = c("deltastart", "WTstart"),
"start_delta_vs_complement" = c("deltastart", "complementstart"),
"WT_e20m_vs_start" = c("WTe20m", "WTstart"),
"delta_e20m_vs_start" = c("deltae20m", "deltastart"),
"complement_e20m_vs_start" = c("complemente20m", "complementstart"),
"WT_e60m_vs_start" = c("WTe60m", "WTstart"),
"delta_e60m_vs_start" = c("deltae60m", "deltastart"),
"complement_e60m_vs_start" = c("complemente60m", "complementstart"),
"WT_e60m_vs_e20m" = c("WTe60m", "WTe20m"),
"delta_e60m_vs_e20m" = c("deltae60m", "deltae20m"),
"complement_e60m_vs_e20m" = c("complemente60m", "complemente20m"))
combined_tables <- combine_de_tables(
combined_de, keepers = combined_keepers,
excel = glue::glue("excel/pdxr_combined_tables-v{ver}.xlsx"))
combined_sig <- extract_significant_genes(
combined_tables,
excel = glue::glue("excel/pdxr_combined_sig-v{ver}.xlsx"))
if (!isTRUE(get0("skip_load"))) {
pander::pander(sessionInfo())
message(paste0("This is hpgltools commit: ", get_git_commit()))
this_save <- paste0(gsub(pattern = "\\.Rmd", replace = "", x = rmd_file), "-v", ver, ".rda.xz")
message(paste0("Saving to ", this_save))
tmp <- sm(saveme(filename = this_save))
}
## If you wish to reproduce this exact build of hpgltools, invoke the following:
## > git clone http://github.com/abelew/hpgltools.git
## > git reset 09b2f72b4cb5e7f7f74b7a7970f5b2f7f3609e41
## This is hpgltools commit: Tue Apr 19 18:55:02 2022 -0400: 09b2f72b4cb5e7f7f74b7a7970f5b2f7f3609e41
## Saving to index_pdxR-v202204.rda.xz
LS0tCnRpdGxlOiAiUy5weW9nZW5lcyA1NDQ4IHJuYXNlcSBjb21wYXJpbmcgcGR4UiBzdHJhaW5zIGFjcm9zcyB0aW1lLiIKYXV0aG9yOiAiYXRiIGFiZWxld0BnbWFpbC5jb20iCmRhdGU6ICJgciBTeXMuRGF0ZSgpYCIKb3V0cHV0OgogaHRtbF9kb2N1bWVudDoKICBjb2RlX2Rvd25sb2FkOiB0cnVlCiAgY29kZV9mb2xkaW5nOiBzaG93CiAgZmlnX2NhcHRpb246IHRydWUKICBmaWdfaGVpZ2h0OiA3CiAgZmlnX3dpZHRoOiA3CiAgaGlnaGxpZ2h0OiBkZWZhdWx0CiAga2VlcF9tZDogZmFsc2UKICBtb2RlOiBzZWxmY29udGFpbmVkCiAgbnVtYmVyX3NlY3Rpb25zOiB0cnVlCiAgc2VsZl9jb250YWluZWQ6IHRydWUKICB0aGVtZTogcmVhZGFibGUKICB0b2M6IHRydWUKICB0b2NfZmxvYXQ6CiAgICBjb2xsYXBzZWQ6IGZhbHNlCiAgICBzbW9vdGhfc2Nyb2xsOiBmYWxzZQotLS0KCjxzdHlsZT4KICBib2R5IC5tYWluLWNvbnRhaW5lciB7CiAgICBtYXgtd2lkdGg6IDE2MDBweDsKICB9Cjwvc3R5bGU+CgpgYGB7ciBvcHRpb25zLCBpbmNsdWRlID0gRkFMU0V9CmxpYnJhcnkoaHBnbHRvb2xzKQp0dCA8LSBkZXZ0b29sczo6bG9hZF9hbGwoIn4vaHBnbHRvb2xzIikKa25pdHI6Om9wdHNfa25pdCRzZXQocHJvZ3Jlc3MgPSBUUlVFLAogICAgICAgICAgICAgICAgICAgICB2ZXJib3NlID0gVFJVRSwKICAgICAgICAgICAgICAgICAgICAgd2lkdGggPSA5MCwKICAgICAgICAgICAgICAgICAgICAgZWNobyA9IFRSVUUpCmtuaXRyOjpvcHRzX2NodW5rJHNldChlcnJvciA9IFRSVUUsCiAgICAgICAgICAgICAgICAgICAgICBmaWcud2lkdGggPSA4LAogICAgICAgICAgICAgICAgICAgICAgZmlnLmhlaWdodCA9IDgsCiAgICAgICAgICAgICAgICAgICAgICBkcGkgPSA5NikKb2xkX29wdGlvbnMgPC0gb3B0aW9ucyhkaWdpdHMgPSA0LAogICAgICAgICAgICAgICAgICAgICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRSwKICAgICAgICAgICAgICAgICAgICAgICBrbml0ci5kdXBsaWNhdGUubGFiZWwgPSAiYWxsb3ciKQpnZ3Bsb3QyOjp0aGVtZV9zZXQoZ2dwbG90Mjo6dGhlbWVfYncoYmFzZV9zaXplID0gMTApKQpzZXQuc2VlZCgxKQp2ZXIgPC0gIjIwMjIwNCIKcm1kX2ZpbGUgPC0gImluZGV4X3BkeFIuUm1kIgpgYGAKCiMgSW1wb3J0YW50IG5vdGUKCkkgY29waWVkIG15IGluZGV4X3JvZkEgYW5kIGp1c3QgcGVyZm9ybWVkIHJlcGxhY2Utc3RyaW5nIG9uIGl0IGZvciB0aGUgdGltZXMgYW5kIHN0cmFpbnM6CgoxLiAgcy9yZXZlcnQvY29tcGxlbWVudC9nCjIuICBzL3RyYW5zaXRpb24vc3RhcnQvZwozLiAgcy9leHBvbmVudGlhbC9lMjBtL2cKNC4gIHMvc3RhdGlvbmFyeS9lNjBtL2cKCiMgUy5weW9nZW5lcyA1NDQ4IHBkeFIgUk5BU2VxIHZlcnNpb246IGByIHZlcmAKCiMjIFByZXByb2Nlc3NpbmcKCkkgdXNlZCBteSBjeW9hIHRvb2wgdG8gcHJvY2VzcyB0aGVzZSBzYW1wbGVzIGJ5IGRvaW5nIHRoZSBmb2xsb3dpbmc6CgoxLiAgQ29weWluZyB0aGUgZGF0YSBmcm9tIHRoZSBzZXF1ZW5jZXIgaW50byB0aGUgZGlyZWN0b3J5ICdwcmVwcm9jZXNzaW5nLycKMi4gIFVzZWQgYSBzbGlnaHRseSBpbnZvbHZlZCBzaGVsbCBjb21tYW5kIHRvIGNyZWF0ZSBhIGRpcmVjdG9yeSBmb3IKICAgIGVhY2ggc2FtcGxlIGFuZCBjb3B5IHRoZSByZWFkcyBmb3IgaXQgdG8gdGhlICd1bnByb2Nlc3NlZC8nCiAgICBzdWJkaXJlY3Rvcnkgd2l0aGluIGl0LgozLiAgaW52b2tlZCB0aGUgZm9sbG93aW5nOgoKYGBge2Jhc2gsIGV2YWw9RkFMU0V9CmNkIHByZXByb2Nlc3NpbmcKc3RhcnQ9JChwd2QpCmZvciBpIGluICQoL2Jpbi9scyAtZCAuLyopCmRvCiAgY2QgJGkKICBybSAtcmYgb3V0cHV0cyBzY3JpcHRzCiAgY3lvYSAtLXRhc2sgcGlwZSAtLW1ldGhvZCBwcm5hcyAtLXNwZWNpZXMgc3B5b2dlbmVzXzU0NDggXAogICAgICAgLS1nZmZfdHlwZSBnZW5lIC0tZ2ZmX3RhZyBsb2N1c190YWcgXAogICAgICAgLS1pbnB1dCAkKC9iaW4vbHMgdW5wcm9jZXNzZWQvKiB8IHRyICdcbicgJzonIHwgc2VkICdzLzokLy9nJykKICBjZCAkc3RhcnQKZG9uZQpgYGAKClRoZSBhYm92ZSBmb3IgbG9vcCBnb2VzIGludG8gZWFjaCBzYW1wbGUgYW5kIGRvZXMgdGhlIGZvbGxvd2luZzoKCjEuICBUcmltcyB0aGUgZGF0YSwgaGVhdmlseSBjb21wcmVzc2VzIHRoZSBvdXRwdXRzLgoyLiAgUnVucyBmYXN0cWMKMy4gIFJ1bnMgaGlzYXQyIHVzaW5nIG15IHNweW9nZW5lc181NDQ4IGluZGljZXMuCjQuICBDb252ZXJ0cyB0aGUgc2FtIGFsaWdubWVudCB0byBzb3J0ZWQvaW5kZXhlZCBiYW0uCjUuICBNYWtlcyBhIGNvdXBsZSBvZiBleHRyYSBjb3BpZXMgb2YgaXQgd2l0aCBzb21lIGZpbHRlcnMuCjYuICBDb21wcmVzc2VzIHRoZSBhbGlnbmVkL3VuYWxpZ25lZCByZWFkcy4KNy4gIFJ1bnMgaHRzZXEtY291bnQgb24gdGhlIGFsaWdubWVudHMgdG8gY291bnQgcmVhZHMvZ2VuZS4KCiAgTm90ZSB0aGUgZm9sbG93aW5nIHN0ZXBzIHdlcmUgbm90IGFjdHVhbGx5IHJ1biBiZWNhdXNlIEkgaGFkIGEKICBzcGVlbGluZyBlcnJvci4gIEJ1dCBzaW5jZSB0aGV5IGFyZSBub3QgbmVjZXNzYXJ5IGZvciB0aGUgZXhwbGljaXRseQogIFJOQVNlcSBhbmFseXNlcyBJIGZpcnN0IHdhbnQgdG8gZG8sIEkgaWdub3JlZCBpdC4gIEkgYW0gY3VyaW91cwogIHRob3VnaCB0byBzZWUgaWYgdGhlcmUgYXJlIG90aGVyIG11dGF0aW9ucyBpbiB0aGVzZSBzdHJhaW5zLCBzbyBJCiAgd2lsbCBsaWtlbHkgcnVuIHRob3NlIHBvcnRpb25zIG1hbnVhbGx5LgoKOC4gIFJ1bnMgZnJlZWJheWVzIG9uIHRoZSBhbGlnbm1lbnRzIHRvIGxvb2sgZm9yIHZhcmlhbnRzLgo5LiAgU29ydHMvY29tcHJlc3NlcyB0aGUgZnJlZWJheWVzIG91dHB1dC4KMTAuIERvZXMgc29tZSBwYXJzaW5nIG9mIHRoZSBmcmVlYmF5ZXMgb3V0cHV0IGFuZCBwcm92aWRlcyBzb21lIHRhYmxlcwogICAgYWJvdXQgd2hlcmUgbXV0YXRpb25zIHdlcmUgb2JzZXJ2ZWQuCgojIyBDb2xsZWN0IGFubm90YXRpb24gaW5mb3JtYXRpb24KClNhbWUgdHdvIHByaW1hcnkgYW5ub3RhdGlvbiBzb3VyY2VzLCB0aGUgZ2ZmIGZpbGUgdXNlZCBmb3IgbWFwcGluZy9jb3VudGluZywKYW5kIG1pY3JvYmVzb25saW5lLm9yZy4gIE5vdGUgdGhhdCBzaW5jZSBJIG1vdmVkIHRvIGp1c3QgZG93bmxvYWRpbmcgdGhlCm1hdGVyaWFsIGZyb20gdGhlIHdlYiBpbnRlcmZhY2UsIEkgbm8gbG9uZ2VyIGhhdmUgYSBoYW5keSBtZXRob2QgdG8gZ2V0IHRoZQp0YXhvbiBJRCwgc28gSSBnbyB0aGVyZSBhbmQgaHVudCBkb3duIHRoZSB0YXhJZCBtYW51YWxseS4KCk5vdyB0aGF0IEkgYW0gdGhpbmtpbmcgYWJvdXQgaXQsIG15IDU0NDggZ2Vub21lL2Fubm90YXRpb25zIGFyZSBraW5kCm9mIG9sZCwgSSB3aWxsIGFzayBhbmQgY2hlY2sgdG8gc2VlIGlmIHRoZXJlIGlzIGFueXRoaW5nIG5ld2VyLgoKQWxzbywgNTQ0OCBkb2VzIG5vdCBoYXZlIGFuIGVudHJ5IGF0IG1pY3JvYmVzb25saW5lLm9yZywgYSBmYWN0IHdoaWNoCkkgZm9yZ290LiAgSSBuZWVkIHRvIGdvIHBva2luZyBpbiBteSBub3RlcyB0byByZWNvbm5lY3QgNTAwNSBhbmQgNTQ0OC4KCmBgYHtyIGFubm90YXRpb259CmdmZl9hbm5vdCA8LSBsb2FkX2dmZl9hbm5vdGF0aW9ucygicmVmZXJlbmNlL3NweW9nZW5lc181NDQ4LmdmZiIsIHR5cGUgPSAiZ2VuZSIpCnJvd25hbWVzKGdmZl9hbm5vdCkgPC0gZ2ZmX2Fubm90W1sibG9jdXNfdGFnIl1dCmhlYWQoZ2ZmX2Fubm90KQoKbWdhc19kYXRhIDwtIGxvYWRfZ2VuYmFua19hbm5vdGF0aW9ucyhhY2Nlc3Npb249IkNQMDA4Nzc2IikKZ2Vub21lX3NpemUgPC0gR2Vub21pY1Jhbmdlczo6d2lkdGgobWdhc19kYXRhJHNlcSkgICMjIFRoaXMgZmFpbHMgb24gdHJhdmlzPwptZ2FzX2NkcyA8LSBhcy5kYXRhLmZyYW1lKG1nYXNfZGF0YSRjZHMpCiMjIEdldCByaWQgb2YgYW1pbm8gYWNpZCBzZXF1ZW5jZQpyb3duYW1lcyhtZ2FzX2NkcykgPC0gbWdhc19jZHNbWyJsb2N1c190YWciXV0Kd2FudGVkIDwtICEgY29sbmFtZXMobWdhc19jZHMpICVpbiUgYygidHJhbnNsYXRpb24iLCAidHlwZSIsICJzdHJhbmQiLCAic2VxbmFtZXMiLCAic3RhcnQiLCAiZW5kIiwgImxvY3VzX3RhZyIsICJub3RlIiwgImdlbmUiLCAiZ2VuZV9zeW5vbnltIiwgIndpZHRoIikKbWdhc19jZHMgPC0gbWdhc19jZHNbLCB3YW50ZWRdCiMjIEFuZCBFQ19udW1iZXIgYmVjYXVzZSB3dGYgaXMgdGhhdD8KbWdhc19hbm5vdCA8LSBtZXJnZShtZ2FzX2NkcywgZ2ZmX2Fubm90LCBieT0icm93Lm5hbWVzIikKcm93bmFtZXMobWdhc19hbm5vdCkgPC0gbWdhc19hbm5vdFtbIlJvdy5uYW1lcyJdXQptZ2FzX2Fubm90W1siUm93Lm5hbWVzIl1dIDwtIE5VTEwKCnJlZl9nZmZfYW5ub3QgPC0gbG9hZF9nZmZfYW5ub3RhdGlvbnMoInJlZmVyZW5jZS9zcHlvZ2VuZXNfNTAwNS5nZmYiLCB0eXBlPSJDRFMiKQptaWNyb2Jlc19hbm5vdCA8LSBsb2FkX21pY3JvYmVzb25saW5lX2Fubm90YXRpb25zKHNwZWNpZXM9IjUwMDUiKQpgYGAKCiMjIFJlcnVubmluZyBteSBmYXN0YTM2IG1hcHBlciBhY3Jvc3Mgc3RyYWlucwoKT25jZSB1cG9uIGEgdGltZSBJIHdyb3RlIGEgbGl0dGxlIHRvb2wgdG8gY29tcGFyZSBjbG9zZWx5CnNwZWNpZXMvc3RyYWlucy4gIEl0IGlzIGVzc2VudGlhbGx5IGEgcG9vci1tYW4ncyBvcnRob2xvZyBzZWFyY2guICBJdApoYXMgYmVlbiBhIHZlcnkgbG9uZyB0aW1lIHNpbmNlIGxhc3QgSSB1c2VkIGl0LCBhbmQgaXQgcmVxdWlyZWQgc29tZQptb2RpZmljYXRpb25zIGluIG9yZGVyIHRvIHdvcmsgcHJvcGVybHkuICBUaGUgbW9zdCBub3RhYmxlIHByb2JsZW0gaXMKdGhhdCB0aGUgcGF0aG5hbWVzIGl0IHVzZXMgYXJlIHRvbyBsb25nIGZvciBmYXN0YTM2LiAgSSBzaG9ydGVuZWQgdGhlbQphbmQgZml4ZWQgYSBjb3VwbGUgb2Ygb2xkIGVycm9ycyBhbmQgaXQgYXBwZWFycyB0byB3b3JrIGFnYWluLgoKYGBge3Igc2luZ2xlX211bHRpfQpzaW5nbGVfaGl0cyA8LSByZWFkcjo6cmVhZF90c3YoInNpbmdsZV9tdWx0aS9vdXRwdXRzL2Zhc3RhX3NweW9nZW5lc181NDQ4X2Nkc19zcHlvZ2VuZXNfNTAwNV9jZHMvc3B5b2dlbmVzXzU0NDhfY2RzX3NpbmdsZXMudHh0IikKY29sbmFtZXMoc2luZ2xlX2hpdHMpIDwtIGMoImZyb20iLCAidG8iKQpzaW5nbGVfaGl0c1tbInRvIl1dIDwtIGdzdWIocGF0dGVybj0iKF4uKj8pOi4qJCIsIHJlcGxhY2VtZW50PSJcXDEiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgeD1zaW5nbGVfaGl0c1tbInRvIl1dLCBwZXJsPVRSVUUpCiMjIFRoZSBnZmYgYW5ub3RhdGlvbiBoYXMgdGhlIHByb3RlaW5faWQgY29sdW1uIG1hdGNoaW5nIHRoaXMgY2RzIGVudHJ5LgoKc2luZ2xlX2JvdGggPC0gbWVyZ2UobWdhc19hbm5vdCwgc2luZ2xlX2hpdHMsIGJ5Lng9InByb3RlaW5faWQiLCBieS55PSJmcm9tIiwgYWxsLng9VFJVRSkKc2luZ2xlX2JvdGggPC0gbWVyZ2Uoc2luZ2xlX2JvdGgsIHJlZl9nZmZfYW5ub3QsIGJ5Lng9InRvIiwgYnkueT0icHJvdGVpbl9pZCIsIGFsbC54PVRSVUUpCgojIyBOb3cgcGljayB1cCB0aGUgbWlzc2luZyBvbGRfbG9jdXNfdGFncyBhbmQgcHV0IGluIHRoZSA1NDQ4IElEcwptaXNzaW5nX2lkcyA8LSBpcy5uYShzaW5nbGVfYm90aFtbIm9sZF9sb2N1c190YWciXV0pCm5ld19pZHMgPC0gc2luZ2xlX2JvdGhbbWlzc2luZ19pZHMsICJsb2N1c190YWcueCJdCnNpbmdsZV9ib3RoW21pc3NpbmdfaWRzLCAib2xkX2xvY3VzX3RhZyJdIDwtIG5ld19pZHMKCnNpbmdsZV9ib3RoIDwtIG1lcmdlKHNpbmdsZV9ib3RoLCBtaWNyb2Jlc19hbm5vdCwgYnkueD0ib2xkX2xvY3VzX3RhZyIsIGJ5Lnk9InN5c05hbWUiLCBhbGwueD1UUlVFKQpyb3duYW1lcyhzaW5nbGVfYm90aCkgPC0gbWFrZS5uYW1lcyhzaW5nbGVfYm90aFtbImdlbmVfaWQiXV0sIHVuaXF1ZT1UUlVFKQpgYGAKCiMjIENyZWF0ZSBleHByZXNzaW9uU2V0CgpOb3RlIHRoYXQgSSBkaWQgdGhlIGZvbGxvd2luZyB0byB0aGUgc2FtcGxlIHNoZWV0IHByb3ZpZGVkIGJ5CkRyLiBNY0l2ZXI6CgoxLiAgQ2hhbmdlZCB0aGUgZFBfUjIwXzQgc2FtcGxlIHRvIGRQX1IyMF8yIChUaGUgb3JpZ2luYWwgc2FtcGxlIG5hbWUKICAgIGlzIHN0aWxsIHRoZXJlIGFyZSB0aGUgb3JpZ2luYWwgY29sdW1uKS4KMi4gIEFkZGVkIGNvbHVtbnMgYXQgdGhlIGVuZCBjb250YWluaW5nIHRoZSBjb3VudCB0YWJsZSBsb2NhdGlvbnMuCjMuICBBZGRlZCBjb2x1bW5zICdzaG9ydF9tZWRpYScsICdncm93dGhfcGhhc2UnLCAnZ2Vub3R5cGUnIHdoaWNoCiAgICBob3BlZnVsbHkgY29udGFpbiB0aGUgcmVsZXZhbnQgbWV0YWRhdGEgZXh0cmFjdGVkIGZyb20gdGhlIHNhbXBsZQogICAgZGVzY3JpcHRpb25zLgo0LiAgQWRkZWQgYSBjb2x1bW4gJ0V4cGVyaW1lbnQnIHdoaWNoIGlzIGVpdGhlciAncm9mQScgb3IgJ3BkeFInLgoKYGBge3IgZXhwdH0KcGR4cl9leHB0IDwtIGNyZWF0ZV9leHB0KG1ldGFkYXRhID0gInNhbXBsZV9zaGVldHMvYWxsX3NhbXBsZXMueGxzeCIsCiAgICAgICAgICAgICAgICAgICAgICAgIGdlbmVfaW5mbyA9IHNpbmdsZV9ib3RoLCBmaWxlX2NvbHVtbiA9ICJzcHlvZ2VuZXM1NDQ4Z2VuZWNvdW50cyIpICU+JQogIHN1YnNldF9leHB0KHN1YnNldD0iZXhwZXJpbWVudD09J3BkeFInIikgJT4lCiAgc2V0X2V4cHRfY29uZGl0aW9ucyhmYWN0PSJnZW5vdHlwZSIpICU+JQogIHNldF9leHB0X2JhdGNoZXMoZmFjdD0iZ3Jvd3RocGhhc2UiKQpgYGAKCkkgaGF2ZSAzNiBzYW1wbGVzIHRvIHBsYXkgd2l0aCwgbGV0IHVzIHNlZSB3aGF0IHRoZXkgbG9vayBsaWtlLgoKIyMgUG9rZSBleHByZXNzaW9uU2V0CgpgYGB7ciBwb2tlfQpwZHhyX2xpYnNpemUgPC0gcGxvdF9saWJzaXplKHBkeHJfZXhwdCkKcGR4cl9saWJzaXplJHBsb3QKCnBkeHJfZmlsdGVyX3Bsb3QgPC0gcGxvdF9saWJzaXplX3ByZXBvc3QocGR4cl9leHB0KQpwZHhyX2ZpbHRlcl9wbG90JGNvdW50X3Bsb3QKcGR4cl9maWx0ZXJfcGxvdCRsb3dnZW5lX3Bsb3QKCnBkeHJfbm9uemVybyA8LSBwbG90X25vbnplcm8ocGR4cl9leHB0KQpwZHhyX25vbnplcm8kcGxvdAojIyBhd2Vzb21lLCB0aGVyZSBpcyBhIHJhbmdlIG9mIH4gMTAgZ2VuZXMhCgp3cml0dGVuIDwtIHdyaXRlX2V4cHQocGR4cl9leHB0LCBleGNlbCA9IGdsdWU6OmdsdWUoImV4Y2VsL3BkeHJfZXhwdC12e3Zlcn0ueGxzeCIpKQpgYGAKCiMjIFF1aWNrIHZpc3VhbGl6YXRpb25zCgojIyMgV2l0aG91dCBjb25zaWRlcmluZyB0aW1lCgpMZXQgdXMgc3RhcnQgd2l0aCBzb21lIHZpZXdzIG9mIHRoZSBkYXRhIHdpdGhvdXQgdGhpbmtpbmcgYWJvdXQgYmF0Y2ggZWZmZWN0cy4KCmBgYHtyIHZpZXdfbm9iYXRjaH0KcGR4cl9ub3JtIDwtIG5vcm1hbGl6ZV9leHB0KHBkeHJfZXhwdCwgZmlsdGVyPVRSVUUsIG5vcm09InF1YW50IiwgY29udmVydD0iY3BtIiwgdHJhbnNmb3JtPSJsb2cyIikKcGR4cl9wY2EgPC0gcGxvdF9wY2EocGR4cl9ub3JtKQpwZHhyX3BjYSRwbG90CgpwZHhyX2hlYXRtYXAgPC0gcGxvdF9kaXNoZWF0KHBkeHJfbm9ybSkKcGR4cl9oZWF0bWFwJHBsb3QKCnBkeHJfc20gPC0gcGxvdF9zbShwZHhyX25vcm0pCnBkeHJfc20kcGxvdApgYGAKCiMjIyBDb25zaWRlcmluZyB0aW1lCgpSZXBlYXQgdGhlIHByZXZpb3VzIHBsb3RzLCBidXQgdGhpcyB0aW1lIHVzaW5nIGxpbW1hJ3MgYmF0Y2ggcmVtb3ZhbAptZXRob2QgKHdoaWNoIGlzIGp1c3QgYSByZXNpZHVhbHMpLgoKYGBge3IgbmJfdmlld30KcGR4cl9uYiA8LSBub3JtYWxpemVfZXhwdChwZHhyX2V4cHQsIGZpbHRlcj1UUlVFLCBub3JtPSJxdWFudCIsIGNvbnZlcnQ9ImNwbSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdHJhbnNmb3JtPSJsb2cyIiwgYmF0Y2g9ImxpbW1hIikKcGR4cl9uYl9wY2EgPC0gcGxvdF9wY2EocGR4cl9uYikKcGR4cl9uYl9wY2EkcGxvdAoKcGR4cl9uYl9oZWF0bWFwIDwtIHBsb3RfY29yaGVhdChwZHhyX25iKQpwZHhyX25iX2hlYXRtYXAkcGxvdApgYGAKCldlbGwsIGl0IGlzIHByZXR0eSBjbGVhciB0aGF0IHRpbWUgaXMgdGhlIGRvbWluYW50IGZhY3Rvci4KCiMjIERpZmZlcmVudGlhbCBFeHByZXNzaW9uIGFuYWx5c2VzCgpJIGFtIGdvaW5nIHRvIGRvIHRoZSBERSBpbiAzIHNlcGFyYXRlIHBpZWNlczoKCjEuICBPbmx5IGNvbXBhcmUgc3RyYWlucwoyLiAgT25seSBjb21wYXJlIHRpbWVzCjMuICBDb21wYXJlIHRoZSBjb25jYXRlbmF0aW9uIG9mIHN0cmFpbnMgYW5kIHRpbWVzLgoKIyMjIFN0cmFpbiBvbmx5IGNvbXBhcmlzb25zCgpgYGB7ciBwYWlyd2lzZV9zdHJhaW59CnN0cmFpbl9kZSA8LSBhbGxfcGFpcndpc2UocGR4cl9leHB0LCBtb2RlbF9iYXRjaD1UUlVFLCBmaWx0ZXI9VFJVRSkKc3RyYWluX2tlZXBlcnMgPC0gbGlzdCgKICAgICJkZWx0YV93dCIgPSBjKCJkZWx0YSIsICJXVCIpLAogICAgImNvbXBsZW1lbnRfd3QiID0gYygiY29tcGxlbWVudCIsICJXVCIpLAogICAgImRlbHRhX2NvbXBsZW1lbnQiID0gYygiZGVsdGEiLCAiY29tcGxlbWVudCIpKQpzdHJhaW5fdGFibGVzIDwtIGNvbWJpbmVfZGVfdGFibGVzKAogICAgc3RyYWluX2RlLCBrZWVwZXJzID0gc3RyYWluX2tlZXBlcnMsCiAgICBleGNlbCA9IGdsdWU6OmdsdWUoImV4Y2VsL3BkeHJfc3RyYWluX3RhYmxlcy12e3Zlcn0ueGxzeCIpKQpzdHJhaW5fc2lnIDwtIGV4dHJhY3Rfc2lnbmlmaWNhbnRfZ2VuZXMoCiAgICBzdHJhaW5fdGFibGVzLAogICAgZXhjZWwgPSBnbHVlOjpnbHVlKCJleGNlbC9wZHhyX3N0cmFpbl9zaWctdnt2ZXJ9Lnhsc3giKSkKYGBgCgojIyMgVGltZSBvbmx5IGNvbXBhcmlzb25zCgpgYGB7ciBwYWlyd2lzZV90aW1lfQpwZHhyX3RpbWUgPC0gc2V0X2V4cHRfY29uZGl0aW9ucyhwZHhyX2V4cHQsIGZhY3Q9Imdyb3d0aHBoYXNlIikgJT4lCiAgc2V0X2V4cHRfYmF0Y2hlcyhmYWN0PSJnZW5vdHlwZSIpCnRpbWVfZGUgPC0gYWxsX3BhaXJ3aXNlKHBkeHJfdGltZSwgbW9kZWxfYmF0Y2g9VFJVRSkKdGltZV9rZWVwZXJzIDwtIGxpc3QoCiAgICAiZTIwbV9zdGFydCIgPSBjKCJlMjBtIiwgInN0YXJ0IiksCiAgICAiZTYwbV9lMjBtIiA9IGMoImU2MG0iLCAiZTIwbSIpLAogICAgImU2MG1fc3RhcnQiID0gYygiZTYwbSIsICJzdGFydCIpKQp0aW1lX3RhYmxlcyA8LSBjb21iaW5lX2RlX3RhYmxlcygKICAgIHRpbWVfZGUsIGtlZXBlcnMgPSB0aW1lX2tlZXBlcnMsCiAgICBleGNlbCA9IGdsdWU6OmdsdWUoImV4Y2VsL3BkeHJfdGltZV90YWJsZXMtdnt2ZXJ9Lnhsc3giKSkKdGltZV9zaWcgPC0gZXh0cmFjdF9zaWduaWZpY2FudF9nZW5lcygKICAgIHRpbWVfdGFibGVzLAogICAgZXhjZWwgPSBnbHVlOjpnbHVlKCJleGNlbC9wZHhyX3RpbWVfc2lnLXZ7dmVyfS54bHN4IikpCmBgYAoKIyMjIyBDb21wYXJlIHRpbWVzIHZzIHN0cmFpbnMKCkkgYWx3YXlzIHdvcnJ5IHRoYXQgY29tcGFyaW5nIGEgZGF0YSBzZXQgYWNyb3NzIG11bHRpcGxlIGNvbmRpdGlvbnMKcmVzdWx0cyBpbiBub3Qgd2hhdCBJIHRoaW5rIGl0IHdpbGwuICBMZXQgdXMgdGhlcmVmb3JlIHBsb3QgdGhlIGxvZ0ZDcwpvZiBsaWtlbHkgY29udHJhc3RzIGFnYWluc3QgZWFjaCBvdGhlci4KCmBgYHtyIGNvbXBhcmVfdGltZXNfdnNfc3RyYWluc30KeF9heGlzIDwtIHRpbWVfdGFibGVzW1siZGF0YSJdXVtbInN0YXJ0X2UyMG0iXV1bLCBjKCJkZXNlcV9sb2dmYyIsICJkZXNlcV9hZGpwIildCnlfYXhpcyA8LSBzdHJhaW5fdGFibGVzW1siZGF0YSJdXVtbImRlbHRhX3d0Il1dWywgYygiZGVzZXFfbG9nZmMiLCAiZGVzZXFfYWRqcCIpXQpib3RoIDwtIG1lcmdlKHhfYXhpcywgeV9heGlzLCBieT0icm93Lm5hbWVzIikKcm93bmFtZXMoYm90aCkgPC0gYm90aFtbIlJvdy5uYW1lcyJdXQpib3RoW1siUm93Lm5hbWVzIl1dIDwtIE5VTEwKY29yLnRlc3QoYm90aFtbImRlc2VxX2xvZ2ZjLngiXV0sIGJvdGhbWyJkZXNlcV9sb2dmYy55Il1dLCBtZXRob2Q9InNwZWFybWFuIikKcGxvdHRlZCA8LSBwbG90X2xpbmVhcl9zY2F0dGVyKGJvdGhbLCBjKCJkZXNlcV9sb2dmYy54IiwgImRlc2VxX2xvZ2ZjLnkiKV0pCnBsb3R0ZWQkc2NhdHRlcgpgYGAKCiMjIyBJbnRlcmFjdGlvbiBtb2RlbAoKSSB3YW50IHRvIG1ha2Ugc3VyZSB0aGF0IG15IG1ldGhvZHMgb2YgcGVyZm9ybWluZyBpbnRlcmFjdGlvbiBtb2RlbHMKd29yayBhcyBJIHRoaW5rIGl0IGRvZXMsIGFuZCB0aGlzIGRhdGEgc2V0IGxvb2tzIHRvIG1lIHRvIGJlIGEgcGVyZmVjdApwbGFjZSB0byB0ZXN0IHRoYXQuCgpgYGB7ciBjb21iaW5lZF9kZSwgZXZhbD1GQUxTRX0KY29tYmluZWRfZmFjdG9ycyA8LSBwYXN0ZTAocERhdGEocGR4cl9leHB0KVtbImdlbm90eXBlIl1dLCAiXyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHBEYXRhKHBkeHJfZXhwdClbWyJncm93dGhwaGFzZSJdXSkKY29tYmluZWRfZXhwdCA8LSBzZXRfZXhwdF9jb25kaXRpb25zKHBkeHJfZXhwdCwgZmFjdD1jb21iaW5lZF9mYWN0b3JzKSAlPiUKCmNvbWJpbmVkX2RlIDwtIGFsbF9wYWlyd2lzZShjb21iaW5lZF9leHB0LCBtb2RlbF9iYXRjaD0ic3Zhc2VxIiwgZmlsdGVyPVRSVUUpCgpjb21iaW5lZF9rZWVwZXJzIDwtIGxpc3QoCiAgICAiZTIwbV9kZWx0YV92c193dCIgPSBjKCJkZWx0YWUyMG0iLCAiV1RlMjBtIiksCiAgICAiZTIwbV9kZWx0YV92c19jb21wbGVtZW50IiA9IGMoImRlbHRhZTIwbSIsICJjb21wbGVtZW50ZTIwbSIpLAogICAgImU2MG1fZGVsdGFfdnNfd3QiID0gYygiZGVsdGFlNjBtIiwgIldUZTYwbSIpLAogICAgImU2MG1fZGVsdGFfdnNfY29tcGxlbWVudCIgPSBjKCJkZWx0YWU2MG0iLCAiY29tcGxlbWVudGU2MG0iKSwKICAgICJzdGFydF9kZWx0YV92c193dCIgPSBjKCJkZWx0YXN0YXJ0IiwgIldUc3RhcnQiKSwKICAgICJzdGFydF9kZWx0YV92c19jb21wbGVtZW50IiA9IGMoImRlbHRhc3RhcnQiLCAiY29tcGxlbWVudHN0YXJ0IiksCiAgICAiV1RfZTIwbV92c19zdGFydCIgPSBjKCJXVGUyMG0iLCAiV1RzdGFydCIpLAogICAgImRlbHRhX2UyMG1fdnNfc3RhcnQiID0gYygiZGVsdGFlMjBtIiwgImRlbHRhc3RhcnQiKSwKICAgICJjb21wbGVtZW50X2UyMG1fdnNfc3RhcnQiID0gYygiY29tcGxlbWVudGUyMG0iLCAiY29tcGxlbWVudHN0YXJ0IiksCiAgICAiV1RfZTYwbV92c19zdGFydCIgPSBjKCJXVGU2MG0iLCAiV1RzdGFydCIpLAogICAgImRlbHRhX2U2MG1fdnNfc3RhcnQiID0gYygiZGVsdGFlNjBtIiwgImRlbHRhc3RhcnQiKSwKICAgICJjb21wbGVtZW50X2U2MG1fdnNfc3RhcnQiID0gYygiY29tcGxlbWVudGU2MG0iLCAiY29tcGxlbWVudHN0YXJ0IiksCiAgICAiV1RfZTYwbV92c19lMjBtIiA9IGMoIldUZTYwbSIsICJXVGUyMG0iKSwKICAgICJkZWx0YV9lNjBtX3ZzX2UyMG0iID0gYygiZGVsdGFlNjBtIiwgImRlbHRhZTIwbSIpLAogICAgImNvbXBsZW1lbnRfZTYwbV92c19lMjBtIiA9IGMoImNvbXBsZW1lbnRlNjBtIiwgImNvbXBsZW1lbnRlMjBtIikpCgoKY29tYmluZWRfdGFibGVzIDwtIGNvbWJpbmVfZGVfdGFibGVzKAogICAgY29tYmluZWRfZGUsIGtlZXBlcnMgPSBjb21iaW5lZF9rZWVwZXJzLAogICAgZXhjZWwgPSBnbHVlOjpnbHVlKCJleGNlbC9wZHhyX2NvbWJpbmVkX3RhYmxlcy12e3Zlcn0ueGxzeCIpKQpjb21iaW5lZF9zaWcgPC0gZXh0cmFjdF9zaWduaWZpY2FudF9nZW5lcygKICAgIGNvbWJpbmVkX3RhYmxlcywKICAgIGV4Y2VsID0gZ2x1ZTo6Z2x1ZSgiZXhjZWwvcGR4cl9jb21iaW5lZF9zaWctdnt2ZXJ9Lnhsc3giKSkKYGBgCgoKYGBge3Igc2F2ZW1lfQppZiAoIWlzVFJVRShnZXQwKCJza2lwX2xvYWQiKSkpIHsKICBwYW5kZXI6OnBhbmRlcihzZXNzaW9uSW5mbygpKQogIG1lc3NhZ2UocGFzdGUwKCJUaGlzIGlzIGhwZ2x0b29scyBjb21taXQ6ICIsIGdldF9naXRfY29tbWl0KCkpKQogIHRoaXNfc2F2ZSA8LSBwYXN0ZTAoZ3N1YihwYXR0ZXJuID0gIlxcLlJtZCIsIHJlcGxhY2UgPSAiIiwgeCA9IHJtZF9maWxlKSwgIi12IiwgdmVyLCAiLnJkYS54eiIpCiAgbWVzc2FnZShwYXN0ZTAoIlNhdmluZyB0byAiLCB0aGlzX3NhdmUpKQogIHRtcCA8LSBzbShzYXZlbWUoZmlsZW5hbWUgPSB0aGlzX3NhdmUpKQp9CmBgYAo=