Here we run an analysis pipeline in R for the identification of circRNA. The pipeline attempts to recreate parts of the KNIFE pipeline described in the paper by Szabo et al. (2015). The code provided here uses the systemPipeR package with functions designed to run the pipleline on a local desktop. Consult the systemPipeR documentation to modify the code so it can be run on a cluster.

Prepartory steps

To install and resolve bioinformatics software dependecies, you can use the Bioconda package manager. First download Miniconda, then create an environment, activate it, add bioconda channels, and install the required software. For example, here we create a local environment named rnaseq and install bowtie2 and trim-galore.

conda create -n rnaseq python=2 anaconda
source activate rnaseq
conda config --add channels conda-forge
conda config --add channels defaults
conda config --add channels r
conda config --add channels bioconda
conda install bowtie2
conda install trim-galore

1. Create project directories

First start a project and create subdirectories under the project’s working directory

folders <- c(
  "parameters", "src",
  "data/genome_data/hg19/fasta",
  "data/genome_data/hg19/bowtie2_index",
  "data/biosamples",
  "results/trimmed",
  "results/aligned/genome",
  "results/aligned/ribosomal",
  "results/aligned/junctions_reg",
  "results/aligned/junctions_scrambled",
  "/results/analysis/reports"
)

for (i in 1:length(folders))  {
  dir.create(paste(getwd(), folders[i], sep = "/"), recursive = TRUE)
}

2. Download genome data

To get started, we need to download the hg19_fastas.tar.gz archive that contains fasta files for the ribosome, the linear junctions, the scrambled junctions, and the reference genome. The compressed tar file is available at this link

3. Build index files

We first load the systemPipeR package and other required packages. We also create external files for the command-line software. These are the param files that specify the parameters and the target files that contain the relative path locations of any required data files.

#source("http://bioconductor.org/biocLite.R")
#biocLite("tgirke/systemPipeR", dependencies = TRUE)
library(systemPipeR)
library(dplyr)

The index files are large and might take a long time to download on a regular connection. It could be faster to just run the Bowtie index builder to create the index files. These two lines build the indices. The param and target files are available here.

args_bowtie2_index <- systemArgs(sysma = "./parameters/bowtie2_index.param",
                                 mytargets = "./parameters/hg19_fasta_filepaths.txt")
runCommandline(args = args_bowtie2_index)

4. Download sample data

We use the same HER2 Positive Breast Tumor dataset used in the KNIFE as sample data. The data is made up of paired-end reads from a total RNA rRNA-depleted RNA-seq library. More details about the experiment can be found here. To ensure reproducibility and data provenance, we download the SRA data files directly from the GEO database.

download.file("ftp://ftp-trace.ncbi.nlm.nih.gov/sra/sra-instant/reads/ByExp/sra/SRX/SRX374/SRX374866/SRR1027187/SRR1027187.sra", "./data/biosamples/SRR1027187.sra")

Next we convert the SRA file to fastq using fastq-dump. The options provided specify the following: split the paired reads into two files, dump the biological reads only, and compress the output using gzip. We also keep the defaults by not adding read ids and leaving the quality encoding as ASCII+33.

system("fastq-dump --split-files --skip-technical --gzip ./data/biosamples/SRR1027187.sra --outdir ./data/biosamples/")

5. Trim data

Reads in the raw data can contain low quality bases and partial adapter sequences that needs to be removed. Trimmed reads that are too short should also be filtered in order to obtain good alignment.

args_trim <- systemArgs(sysma = "./parameters/trim_galore.param",
                           mytargets = "./parameters/data_filepaths.txt")
trim_command <- paste(sysargs(args_trim)[1],
                      unlist(strsplit(sysargs(args_trim)[2], " "))[8],
                      sep = " ")
(trim_command)
[1] "trim_galore --length 50 --paired --output_dir /hd2/KNIFE_project_01/results/trimmed/  /hd2/KNIFE_project_01/data/biosamples/SRR1027187_1.fastq.gz  /hd2/KNIFE_project_01/data/biosamples/SRR1027187_2.fastq.gz"

Although we are treating each paired end read independently, we add the the –paired option argument in the trim_galore command to peform a validation test that ensures that both sequence pairs have a certain minimum length specified by the option --length. To run trim-galore we use the system function

system(trim_command)

6. Run data quality control checks

Lastly, we generate and plot FASTQ quality summary statistics. The fastqc manual provides a good explantion of the fastqc plots. First we define a helper function

runFastqPlots <- function(file_list, report_name) {
  fqlist <- bplapply(seq_along(file_list),
                     function(x)
                       seeFastq(
                         fastq = file_list[x],
                         batchsize = 100000,
                         klength = 8
                       ),
                     BPPARAM = MulticoreParam(workers = 4))
  png(
    sprintf(
      "./results/analysis/reports/fastq_report_%s.png",
      report_name
    ),
    height = 800,
    width = 200* length(fqlist),
  )
  seeFastqPlot(unlist(fqlist, recursive = FALSE))
  dev.off()
}

then we run it on the raw data

runFastqPlots(file_list = infile1(args_trim),
              report_name = "SRR1027187")

and on the trimmed data

data_trimmed_filepaths <- paste0(outfile1(args_trim),
                                 SampleName(args_trim),
                                 c("_val_1.fq.gz", "_val_2.fq.gz"))
names(data_trimmed_filepaths) <- paste0(SampleName(args_trim),"_trimmed")

runFastqPlots(file_list = data_trimmed_filepaths,
              report_name = "SRR1027187_trimmed")

Next we plot the reports side by side

fastqcplots fastqc_trimmed_plots

The plots for the trimmed data look much better. Although the mean quality distribution for the reads is bimodal, there are no low quality peaks.

Alignment Steps

Now we independently align each paired-end read dataset to four separate Bowtie2 indices: for the genome, ribosomal RNA, linear exon–exon junctions, and scrambled exon–exon junctions. First we load Bowtie2 parameter files and filepaths

args_align_genome <- systemArgs(sysma = "./parameters/bowtie2_align_genome.param",
                                mytargets = "./parameters/data_trimmed_filepaths.txt")
args_align_ribo <- systemArgs(sysma = "./parameters/bowtie2_align_ribosomal.param",
                              mytargets = "./parameters/data_trimmed_filepaths.txt")
args_align_juncts_reg <- systemArgs(sysma = "./parameters/bowtie2_align_junctions_reg.param",
                                    mytargets = "./parameters/data_trimmed_filepaths.txt")
args_align_juncts_scram <- systemArgs(sysma = "./parameters/bowtie2_align_junctions_scrambled.param",
                                      mytargets = "./parameters/data_trimmed_filepaths.txt")

then we run the Bowtie2 alignment commands through systemPipeR

# align to genome
genome_bam_files <- runCommandline(args = args_align_genome)
# align to ribosome
ribo_bam_files <- runCommandline(args = args_align_ribo)
# align to linear junctions
juncts_reg_bam_files <- runCommandline(args = args_align_juncts_reg)
# align to srambled junctions
juncts_scram_files <- runCommandline(args = args_align_juncts_scram)

We can open “submitargs01_log” for each alignment to display the alignment summary and the system command used. For the genome alignment, we can open the file inside R like this

readLines(file.path("./results/aligned/genome","submitargs01_log"))
 [1] "bowtie2 -p 8 --no-unal --score-min L,0,-0.24 --rdg 50,50 --rfg 50,50 -x /hd2/KNIFE_project_01/data/genome_data/hg19/bowtie2_index/hg19_genome -U /hd2/KNIFE_project_01/results/trimmed/SRR1027187_1_val_1.fq.gz  -S /hd2/KNIFE_project_01/results/aligned/genome/SRR1027187_1_val_1.sam"
 [2] "19308315 reads; of these:"
 [3] "  19308315 (100.00%) were unpaired; of these:"
 [4] "    6692805 (34.66%) aligned 0 times"
 [5] "    8628824 (44.69%) aligned exactly 1 time"
 [6] "    3986686 (20.65%) aligned >1 times"
 [7] "65.34% overall alignment rate"
 [8] "bowtie2 -p 8 --no-unal --score-min L,0,-0.24 --rdg 50,50 --rfg 50,50 -x /hd2/KNIFE_project_01/data/genome_data/hg19/bowtie2_index/hg19_genome -U /hd2/KNIFE_project_01/results/trimmed/SRR1027187_2_val_2.fq.gz  -S /hd2/KNIFE_project_01/results/aligned/genome/SRR1027187_2_val_2.sam"
 [9] "19308315 reads; of these:"
[10] "  19308315 (100.00%) were unpaired; of these:"
[11] "    6753734 (34.98%) aligned 0 times"
[12] "    8563704 (44.35%) aligned exactly 1 time"
[13] "    3990877 (20.67%) aligned >1 times"
[14] "65.02% overall alignment rate"                                                                                                                                                                                                                                                          

Preprocessing steps

The previous alignment step produced 8 BAM files. Two paired-end BAM files for each of the genome, ribosome, linear junctions, and scrambled junctions alignments. We are interested in junctional reads, those reads from the 8 files that align to a scrambled or linear junctions. Since the majority of the reads in the genome and ribosome BAM files are not junctional, we need to filter them out.

1. Get read IDs for genomic mate pairs

Since the next preprocessing steps are memory intensive, we can intially filter out reads in which both pair mates align to the genome or ribosome. We can also filter out unaligned reads and return lists of read IDs in which both pairs align to the indicies, or in which one pair mate aligns but the other doesn’t. To proceed we need to get the names of those reads that are found in both R1 and R2 genomic alignment files.

getReadIds <- function(bamfile) {
  scanBam(bamfile,
          param = ScanBamParam(flag = scanBamFlag(isUnmappedQuery = FALSE),
                               what = "qname"))[[1]][[1]]
  }
getCommonGenomicPairedReads <- function(genome_bamfiles, ribo_bamfiles) {

  genome_reads <- bplapply(genome_bamfiles,
                           getReadIds,
                           BPPARAM = MulticoreParam(workers = 2))
  ribosomal_reads <- bplapply(ribo_bamfiles,
                              getReadIds,
                              BPPARAM = MulticoreParam(workers = 2))

  genomic_reads_1 <- union(genome_reads[[1]], ribosomal_reads[[1]])
  genomic_reads_2 <- union(genome_reads[[2]], ribosomal_reads[[2]])
  common_reads <- intersect(genomic_reads_1, genomic_reads_2)
  r1_not_r2 <- setdiff(genomic_reads_1, genomic_reads_2)
  r2_not_r1 <- setdiff(genomic_reads_2, genomic_reads_1)

  return(
    list(
      common_reads = common_reads,
      r1_not_r2 = r1_not_r2,
      r1_not_r2 = r2_not_r1
    )
  )
}

Note that code above uses the parallel lapply function bplapply. If you system has less than 32G RAM, you can modify the code to run with one worker or use lapply instead. Here we retrieve the location of the aligned files and feed them into the function defined above.

genome_aligned <- outpaths(args_align_genome)
ribo_aligned <- outpaths(args_align_ribo)
reads_to_ignore <- getCommonGenomicPairedReads(genome_aligned, ribo_aligned)

2. Read alignment data and run initial filtering

Now we use these read IDs to create two filters. The first one filters out genomic reads in which both mates (R1 and R2) align. The second one is a junctional filter that keeps only junctional reads that overlap a particular junction by at least 8 nucleotides and that also satify the first filter.

genomic_reads_filter <-
  FilterRules(list(
    include = function(x) !(mcols(x)$qname %in% reads_to_ignore$common_reads)
  ))
MIDPOINT <- 150
JUNC_OVERLAP <- 8
junction_reads_filter <-
  FilterRules(list(
    include = function(x)
      (
        (MIDPOINT + JUNC_OVERLAP + 1 - width(x) <= start(x)) &
        (start(x) <= MIDPOINT - JUNC_OVERLAP + 1) &
        !(mcols(x)$qname %in% reads_to_ignore$common_reads)
      )
  ))

As part of the filtering step, we also update the alignment score for the junction reads such that the N-penalty is corrected.

countAmbigousBases <- function(align_obj) {
  sapply(gregexpr("N0|0N", mcols(align_obj)$MD),
         function(x)
           if (attributes(x)$match.length[1] == -1)
             return(0)
         else
           return(length(x))
  )
}
#MD and XN do agree
filterAlignments <-
  function(bamfile, param, filter, updateScore = TRUE) {
    x <- readGAlignments(file = bamfile, param = param)
    x <- subsetByFilter(x, filter)
    seqlevels(x) <- seqlevelsInUse(x)
    # correct for N-penalty in junction alignments and update alignment score
    if (updateScore) {
      mcols(x)$AS <- mcols(x)$AS + countAmbigousBases(x)
    }
    return(x)
  }
# get file paths
linear_junctions_aligned <- outpaths(args_align_juncts_reg)
scrambled_junctions_aligned <- outpaths(args_align_juncts_scram)
# run filters
param <-
  ScanBamParam(
    what = c("qname","mapq"), # get the read name and the mapping quality
    flag = scanBamFlag(isUnmappedQuery = FALSE),
    tag = c("AS", "XS", "XN", "MD") # get tags
  )
genomic_filtered <- bplapply(
  genome_aligned,
  filterAlignments,
  param = param,
  filter = genomic_reads_filter,
  updateScore = FALSE,
  BPPARAM = MulticoreParam(workers = 2)
)
linear_junctions_filtered <- bplapply(
  linear_junctions_aligned,
  filterAlignments,
  param = param,
  filter = junction_reads_filter,
  BPPARAM = MulticoreParam(workers = 2)
)
scrambled_junctions_filtered <- bplapply(
  scrambled_junctions_aligned,
  filterAlignments,
  param = param,
  filter = junction_reads_filter,
  BPPARAM = MulticoreParam(workers = 2)
)

Let’s take a look at first few rows of the GAlignment object for the linear junctions R1 aligned reads

linear_junctions_filtered[[1]][1:3,] %>% as.data.frame()

We can also examine the frequency of the cigar strings

linear_junctions_filtered[[1]] %>%
  cigar() %>%
  table %>%
  sort(decreasing = TRUE) %>%
  head() 
.
   60M    59M    58M    57M    56M    55M
397762 138477  45942  13278   3761   3041 

3. Convert GAlignment objects into dataframes

toDataFrame <- function(x) {
  x <- x %>%
    as.data.frame() %>%
    subset(select = -c(end, cigar, qwidth, njunc, XS, MD))
  colnames(x)[1:8] <-   c("junction", "str", "pos", "len",
                         "id", "MQ", "AS", "XN")
  x$junction <- as.character(x$junction)
  x$str <- as.character(x$str)
  return(x)
}
appendJunctionTags <- function(x) {
  df <-  x %>%
    seqnames() %>%
    as.character() %>%
    lapply(., function(y) unlist(strsplit(y, "[:]|[|]"))) %>%
    do.call(rbind, .) %>%
    data.frame(stringsAsFactors = FALSE)
  colnames(df) <- c("jChr", "jGene1", "jPos1", "jGene2",
                    "jPos2", "jType", "jStr")
  df$jPos1 <- as.integer(df$jPos1)
  df$jPos2 <- as.integer(df$jPos2)
  mcols(x) <- cbind(mcols(x), df)
  return(toDataFrame(x))
}
genomic_filtered_dt <- bplapply(
  genomic_filtered,
  toDataFrame,
  BPPARAM = MulticoreParam(workers = 2)
)
linear_junctions_filtered_dt <- bplapply(
  linear_junctions_filtered,
  appendJunctionTags,
  BPPARAM = MulticoreParam(workers = 2)
)
scrambled_junctions_filtered_dt <- bplapply(
  scrambled_junctions_filtered,
  appendJunctionTags,
  BPPARAM = MulticoreParam(workers = 2)
)

Let’s take a look at the dataframe for the linear junctions R1 aligned reads

linear_junctions_filtered_dt[[1]][1:3,]

4. Filter R1 junctional reads and pair them with R2 mates

A read (R1) is a linear junction read if it aligns to a linear junction and does not align to the genome or ribosome. In the code these reads are stored in linear_1. A read R1 is a scrambled junction read if it aligns to a scrambled junction and does not align to the genome or ribosome or to the linear junction. Scrambled junction reads are stored in scrambled_1. The read mate R2 can be a genomic, linear junction, or a scrambled junction read as determined by whehter the read name can be found in the R2 aligned reads. An R1 read can have a single or multiple R2 mates. Some have none at all. These are the unmapped reads.

createJunctionsReadsDF <- function(genomic,
                                   linear,
                                   scrambled,
                                   r1_not_r2,
                                   suffixes = c(".R1", ".R2g", ".R2l", ".R2s")) {

  linear_1 <- linear[[1]][!(linear[[1]]$id %in% r1_not_r2), ]
  scrambled_1 <- scrambled[[1]][!(scrambled[[1]]$id %in% c(r1_not_r2, linear_1$id)), ]

  genomic_mate <- genomic[[2]][(genomic[[2]]$id %in% c(linear_1$id,scrambled_1$id)), ]
  linear_mate <- linear[[2]][(linear[[2]]$id %in% c(linear_1$id,scrambled_1$id)), ]
  scrambled_mate <- scrambled[[2]][(scrambled[[2]]$id %in% c(linear_1$id, scrambled_1$id)), ]

  linear_genomic <- left_join(linear_1,
                              genomic_mate,
                              by = "id",
                              suffix = suffixes[c(1, 2)])

  linear_linear <- left_join(linear_1,
                             linear_mate ,
                             by = "id",
                             suffix = suffixes[c(1, 3)])

  linear_scrambled <- left_join(linear_1,
                                scrambled_mate,
                                by = "id",
                                suffix = suffixes[c(1, 4)])

  scrambled_genomic <- left_join(scrambled_1,
                                 genomic_mate,
                                 by = "id",
                                 suffix = suffixes[c(1, 2)])

  scrambled_linear <- left_join(scrambled_1,
                                linear_mate ,
                                by = "id",
                                suffix = suffixes[c(1, 3)])

  scrambled_scrambled <- left_join(scrambled_1,
                                   scrambled_mate,
                                   by = "id",
                                   suffix = suffixes[c(1, 4)])

  linear_df <- cbind(linear_linear[, 1:15],
                     linear_genomic[, 16:22],
                     linear_linear[, 16:29],
                     linear_scrambled[, 16:29])

  scrambled_df <- cbind(scrambled_linear[, 1:15],
                        scrambled_genomic[, 16:22],
                        scrambled_linear[, 16:29],
                        scrambled_scrambled[, 16:29])

  return(list(linear_df = linear_df, scrambled_df = scrambled_df))
}
R1R2MatesJunctionReads <- createJunctionsReadsDF(genomic_filtered_dt,
                                                 linear_junctions_filtered_dt,
                                                 scrambled_junctions_filtered_dt,
                                                 reads_to_ignore$r1_not_r2)

5. Classify Mate Reads

Here we classify mate reads based on alignment score. We also create a new variable ascore that averages the alignment scores of the two matched reads.

classsifyMateReads <- function(df, suffixes = c(".R1", ".R2g", ".R2l", ".R2s")) {

    scores_df <- df[, paste0("AS", suffixes)]
    nMates <- apply(scores_df[, 2:4] , 1, function(x) sum(!is.na(x)))
    max_score <- apply(scores_df[, 2:4] , 1, which.max)

    mate <- sapply(max_score, function(x)
      if (is.null(attributes(x)))
        {return(0)}
      else
        {return(as.integer(x))}
      )

    matescore <- scores_df[cbind(1:nrow(scores_df), (mate + 1))]
    ASmean <- 0.5 * (scores_df$AS.R1 + matescore)
    df <- cbind(df,
                ASmean = ASmean,
                nMates = nMates,
                mate = mate)
    return(df)
  }
linear_df <-
  R1R2MatesJunctionReads$linear_df %>%
  classsifyMateReads()
scrambled_df <-
  R1R2MatesJunctionReads$scrambled_df %>%
  classsifyMateReads()
cat( " number of linear junction reads",
     NROW(linear_df), "\n",
     "number of scrambled junction reads",
     NROW(scrambled_df), "\n",
  "total number of junctional reads:",
    NROW(linear_df) + NROW(scrambled_df), "\n",
    "number of junctions with aligned reads:",
    length(unique(linear_df$junction.R1)) +
      length(unique(scrambled_df$junction.R1)))
 number of linear junction reads 600658
 number of scrambled junction reads 1599
 total number of junctional reads: 602257
 number of junctions with aligned reads: 108949

Let’s take a look at the dataframe for the linear junctions R1 aligned reads

scrambled_df[1:3, ]  

Let’s also output the frequency of R1 reads that have 0, 1, 2, or 3 potential paired-end mate reads (R2)

linear_junction_mapcateg <-
  linear_df$nMates %>%
  table  %>%
  as.data.frame()
scrambled_junction_mapcateg <-
  scrambled_df$nMates %>%
  table %>%
  as.data.frame()
names_df <- c("Unmapped","OneMateMatch", "TwoMateMatchs", "ThreeMateMatchs")
rownames(linear_junction_mapcateg) <- names_df
rownames(scrambled_junction_mapcateg) <- names_df
linear_junction_mapcateg 
scrambled_junction_mapcateg 

6. Classify Junction Reads

We split the reads into reads that have a mapped mate R2 and those that do not. We intially assign all reads as uncategorized (i.e. class=0).

linear_reads_split <- split(linear_df, linear_df$nMates == 0)
scrambled_reads_split <- split(scrambled_df, scrambled_df$nMates == 0)
unmapped_reads <- rbind(linear_reads_split[[2]], scrambled_reads_split[[2]])[,1:15]
linear_reads_mapped <- cbind(linear_reads_split[[1]] , class = 0)
scrambled_reads_mapped <- cbind(scrambled_reads_split[[1]] , class = 0)

Let’s take a look at the unmapped reads

unmapped_reads[1:3,]

Now we proceed to classify scrambled junctional reads as circular or decoy and linear junctional reads as linear or anomalous.

classifyCircularPairedMates <- function(x) {

  cond1 <- x$str.R1 != x[, c("str.R2g", "str.R2l", "str.R2s")][cbind(1:nrow(x), x$mate)]
  cond2 <- x$jChr.R1 == x[, c("junction.R2g", "jChr.R2l", "jChr.R2s")][cbind(1:nrow(x), x$mate)]
  cond3 <- x$mate == 3
  cond4 <- x$junction.R1 == x$junction.R2s
  x$class[(cond1 & cond2 & cond3 & cond4)] <- 1 #(circ)

  return(x)
}
classifyCircularLinearMates <- function(x, BUFFER = 15, MIDPOINT = 150) {

  r1_start <- pmin(x$jPos1.R1, x$jPos2.R1)
  r1_end <- pmax(x$jPos1.R1, x$jPos2.R1)
  r2_start <- pmin(x$jPos1.R2l, x$jPos2.R2l)  - MIDPOINT +  x$pos.R2l
  r2_end <- pmin(x$jPos1.R2l, x$jPos2.R2l)  - MIDPOINT +  x$pos.R2l +  x$len.R2l - 1

  cond1 <- x$str.R1 != x[, c("str.R2g", "str.R2l", "str.R2s")][cbind(1:nrow(x), x$mate)]
  cond2 <- x$jChr.R1 == x[, c("junction.R2g", "jChr.R2l", "jChr.R2s")][cbind(1:nrow(x), x$mate)]
  cond3 <- r2_start >=  r1_start - BUFFER
  cond4 <- r2_start <=  r1_end + BUFFER
  cond5 <- r2_end >= r1_start - BUFFER
  cond6 <- r2_end <= r1_end + BUFFER
  cond7 <- x$mate == 2
  cond8 <- !(x$class %in% c(1))

  x$class[(cond1 & cond2 & cond3 & cond4 & cond5 & cond6 & cond7 & cond8)] <- 2 #(circ_lin)
  return(x)
}
classifyCircularGenomicMates <- function(x, BUFFER = 15, MIDPOINT = 150) {

  r1_start <- pmin(x$pos.R1, x$jPos2.R1)
  r1_end <- pmax(x$jPos1.R1, x$jPos2.R1)
  r2_start <- x$pos.R2g
  r2_end <- x$pos.R2g + x$len.R2g - 1

  cond1 <- x$str.R1 != x[, c("str.R2g", "str.R2l", "str.R2s")][cbind(1:nrow(x), x$mate)]
  cond2 <- x$jChr.R1 == x[, c("junction.R2g", "jChr.R2l", "jChr.R2s")][cbind(1:nrow(x), x$mate)]
  cond3 <- r2_start >=  r1_start - BUFFER
  cond4 <- r2_start <=  r1_end + BUFFER
  cond5 <- r2_end >= r1_start - BUFFER
  cond6 <- r2_end <= r1_end + BUFFER
  cond7 <- x$mate == 1
  cond8 <- !(x$class %in% c(1, 2))

  x$class[(cond1 & cond2 & cond3 & cond4 & cond5 & cond6 & cond7 & cond8 )] <- 3 #(circ_gen)
  return(x)
}
classifyCircularIgnore <- function(x, BUFFER = 15, MIDPOINT = 150) {

  r1_start <- pmin(x$jPos1.R1, x$jPos2.R1) - MIDPOINT + x$pos.R1
  r1_end <- r1_start + x$len.R1 - 1

  cond1 <- x$str.R1 != x[, c("str.R2g", "str.R2l", "str.R2s")][cbind(1:nrow(x), x$mate)]
  cond2 <- x$jChr.R1 == x[, c("junction.R2g", "jChr.R2l", "jChr.R2s")][cbind(1:nrow(x), x$mate)]
  cond3 <- r1_start >= pmin(x$jPos1.R2s, x$jPos2.R2s) - BUFFER
  cond4 <- r1_start <= pmax(x$jPos1.R2s, x$jPos2.R2s) + BUFFER
  cond5 <- r1_end >= pmin(x$jPos1.R2s, x$jPos2.R2s) - BUFFER
  cond6 <- r1_end <= pmax(x$jPos1.R2s, x$jPos2.R2s) + BUFFER
  cond7 <- x$mate == 3
  cond8 <- !(x$class %in% c(1, 2, 3))
  x$class[(cond1 & cond2 & cond3 & cond4 & cond5 & cond6 & cond7 & cond8)] <- 4
  return(x)
}
classifyCircularDecoy <- function(x) {
  cond1 <- x$str.R1 != x[, c("str.R2g", "str.R2l", "str.R2s")][cbind(1:nrow(x), x$mate)]
  cond2 <- x$jChr.R1 == x[, c("junction.R2g", "jChr.R2l", "jChr.R2s")][cbind(1:nrow(x), x$mate)]
  cond3 <- !(x$class %in% c(1, 2, 3, 4))
  cond4 <- x$mate == 3
  x$class[(cond1 & cond2 & cond3) ] <- 5
  x$class[!(cond1 & cond2) ] <- 5

  return(x)
}
classifyLinearPairedMates <- function(x, BUFFER = 15, MIDPOINT = 150) {

  r1_start <- pmin(x$jPos1.R1, x$jPos2.R1) - MIDPOINT + x$pos.R1 # myCoord
  r2_start <- pmin(x$jPos1.R2l, x$jPos2.R2l)  - MIDPOINT +  x$pos.R2l #mateCoord

  cond1 <- x$str.R1 != x[, c("str.R2g", "str.R2l", "str.R2s")][cbind(1:nrow(x), x$mate)]
  cond2 <- x$jChr.R1 == x[, c("junction.R2g", "jChr.R2l", "jChr.R2s")][cbind(1:nrow(x), x$mate)]
  cond3_p <- r2_start >= r1_start - BUFFER
  cond3_m <- r1_start >= r2_start - BUFFER
  cond3 <- ifelse(x$str.R1 == "+", cond3_p, cond3_m)
  cond4 <- x$mate == 2
  x$class[(cond1 & cond2 & cond3 & cond4)] <- 1
  return(x)

}
classifyLinearGenomicMates <- function(x, BUFFER = 15, MIDPOINT = 150) {

  r1_start <- pmin(x$jPos1.R1, x$jPos2.R1) - MIDPOINT + x$pos.R1
  r2_start <- x$pos.R2g

  cond1 <- x$str.R1 != x[, c("str.R2g", "str.R2l", "str.R2s")][cbind(1:nrow(x), x$mate)]
  cond2 <- x$jChr.R1 == x[, c("junction.R2g", "jChr.R2l", "jChr.R2s")][cbind(1:nrow(x), x$mate)]
  cond3_p <- r2_start >= r1_start - BUFFER
  cond3_m <- r1_start >= r2_start - BUFFER
  cond3 <- ifelse(x$str.R1 == "+", cond3_p, cond3_m)
  cond4 <- x$mate == 1
  cond5 <- !(x$class %in% c(1))
  x$class[(cond1 & cond2 & cond3 & cond4 & cond5)] <- 2
  return(x)
}
classifyLinearIgnore <- function(x) {

  cond1 <- x$str.R1 != x[, c("str.R2g", "str.R2l", "str.R2s")][cbind(1:nrow(x), x$mate)]
  cond2 <- x$jChr.R1 == x[, c("junction.R2g", "jChr.R2l", "jChr.R2s")][cbind(1:nrow(x), x$mate)]
  cond3 <- x$mate == 3
  x$class[(cond1 & cond2 & cond3 )] <- 3
  return(x)
}
classifyLinearAnomaly <- function(x) {

  cond1 <- x$str.R1 != x[, c("str.R2g", "str.R2l", "str.R2s")][cbind(1:nrow(x), x$mate)]
  cond2 <- x$jChr.R1 == x[, c("junction.R2g", "jChr.R2l", "jChr.R2s")][cbind(1:nrow(x), x$mate)]
  x$class[!(cond1 & cond2)] <- 4
  x$class[x$class == 0] <- 4
  return(x)
}
linear_reads_mapped <-
  linear_reads_mapped %>%
  classifyLinearPairedMates() %>%
  classifyLinearGenomicMates() %>%
  classifyLinearIgnore()  %>%
  classifyLinearAnomaly()
scrambled_reads_mapped <-
  scrambled_reads_mapped %>%
  classifyCircularPairedMates() %>%
  classifyCircularLinearMates() %>%
  classifyCircularGenomicMates  %>%
  classifyCircularIgnore  %>%
  classifyCircularDecoy
linear_reads_mapped[]

The frequency of read categories are as follows:

linear_junction_reads <-
  linear_reads_mapped$class %>%
  tabulate  %>%
  as.data.frame()
scrambled_junction_reads <-
  scrambled_reads_mapped$class %>%
  tabulate %>%
  as.data.frame()
rownames(linear_junction_reads) <- c("Linear.l",
                                     "Linear.g",
                                     "Ignore",
                                     "Anomaly")
rownames(scrambled_junction_reads) <- c("Circular.s",
                                        "Circular.l",
                                        "Circular.g",
                                        "Ignore",
                                        "Decoy")
linear_junction_reads 
scrambled_junction_reads
cat( " number of linear reads:",
     sum(linear_junction_reads[1:2,]), "\n",
     "number of circular reads:",
     sum(scrambled_junction_reads[1:3,]), "\n",
  "total number of anomaly reads:",
    sum(linear_junction_reads[4,]), "\n",
    "number of  decoy reads:",
    sum(scrambled_junction_reads[5,]))
 number of linear reads: 540360
 number of circular reads: 1265
 total number of anomaly reads: 14474
 number of  decoy reads: 117

Reorder columns

refcols <- c("id","class", "mate")
linear_reads_mapped <- linear_reads_mapped[, c(refcols,
                                               setdiff(names(linear_reads_mapped),
                                                       refcols))]
scrambled_reads_mapped <- scrambled_reads_mapped[, c(refcols,
                                                     setdiff(names(scrambled_reads_mapped),
                                                             refcols))]
scrambled_reads_mapped

Save data

write.table(linear_reads_mapped,
            "linear_reads_mapped.txt",
            row.names = FALSE,
            sep = "\t")
write.table(scrambled_reads_mapped,
            "scrambled_reads_mapped.txt",
            row.names = FALSE,
            sep = "\t")
saveRDS(scrambled_reads_mapped, "scrambled_reads_mapped.rds")
saveRDS(linear_reads_mapped, "linear_reads_mapped.rds")
#scrambled_reads_mapped <- readRDS("scrambled_reads_mapped.rds")
#linear_reads_mapped <- readRDS("linear_reads_mapped.rds")
sessionInfo()
R version 3.3.2 (2016-10-31)
Platform: x86_64-pc-linux-gnu (64-bit)
Running under: Ubuntu 16.04.1 LTS

locale:
 [1] LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C               LC_TIME=en_US.UTF-8        LC_COLLATE=en_US.UTF-8     LC_MONETARY=en_US.UTF-8    LC_MESSAGES=en_US.UTF-8
 [7] LC_PAPER=en_US.UTF-8       LC_NAME=C                  LC_ADDRESS=C               LC_TELEPHONE=C             LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C

attached base packages:
[1] stats4    parallel  stats     graphics  grDevices utils     datasets  methods   base

other attached packages:
 [1] dplyr_0.5.0                systemPipeR_1.9.2          ShortRead_1.32.0           GenomicAlignments_1.10.0   SummarizedExperiment_1.4.0 Biobase_2.34.0
 [7] BiocParallel_1.8.1         Rsamtools_1.26.1           Biostrings_2.42.1          XVector_0.14.0             GenomicRanges_1.26.1       GenomeInfoDb_1.10.2
[13] IRanges_2.8.1              S4Vectors_0.12.1           BiocGenerics_0.20.0

loaded via a namespace (and not attached):
 [1] Rcpp_0.12.8            locfit_1.5-9.1         lattice_0.20-34        GO.db_3.4.0            assertthat_0.1         digest_0.6.10          R6_2.2.0
 [8] plyr_1.8.4             BatchJobs_1.6          backports_1.0.4        RSQLite_1.1-1          ggplot2_2.2.1          zlibbioc_1.20.0        GenomicFeatures_1.26.2
[15] lazyeval_0.2.0         annotate_1.52.1        Matrix_1.2-7.1         checkmate_1.8.2        GOstats_2.40.0         splines_3.3.2          stringr_1.1.0
[22] pheatmap_1.0.8         RCurl_1.95-4.8         biomaRt_2.30.0         munsell_0.4.3          sendmailR_1.2-1        rtracklayer_1.34.1     base64enc_0.1-3
[29] BBmisc_1.10            fail_1.3               tibble_1.2             edgeR_3.16.5           XML_3.98-1.5           AnnotationForge_1.16.0 bitops_1.0-6
[36] grid_3.3.2             RBGL_1.50.0            xtable_1.8-2           GSEABase_1.36.0        gtable_0.2.0           DBI_0.5-1              magrittr_1.5
[43] scales_0.4.1           graph_1.52.0           stringi_1.1.2          hwriter_1.3.2          genefilter_1.56.0      limma_3.30.7           latticeExtra_0.6-28
[50] brew_1.0-6             rjson_0.2.15           RColorBrewer_1.1-2     tools_3.3.2            Category_2.40.0        survival_2.40-1        AnnotationDbi_1.36.0
[57] colorspace_1.3-2       memoise_1.0.0          knitr_1.15.1          

References

Szabo, Linda, Robert Morey, Nathan J Palpant, Peter L Wang, Nastaran Afari, Chuan Jiang, Mana M Parast, Charles E Murry, Louise C Laurent, and Julia Salzman. 2015. “Statistically based splicing detection reveals neural enrichment and tissue-specific induction of circular RNA during human fetal development.” Genome Biology 16. Genome Biology: 126. doi:10.1186/s13059-015-0690-5.

LS0tCnRpdGxlOiAiSWRlbnRpZmljYXRpb24gYW5kIHF1YW50aWZpY2F0aW9uIG9mIGNpcmN1bGFyIFJOQSBmcm9tIFJOQS1TZXEgZGF0YSIKYXV0aG9yOiAiUmljayBGYXJvdW5pIgpkYXRlOiAiSmFudWFyeSA0LCAyMDE3IgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKYmlibGlvZ3JhcGh5OiBiaWJmaWxlLmJpYgotLS0KCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUsIGNhY2hlID0gVFJVRSwgY29tbWVudCA9ICIiLCB3aWR0aCA9IDEyMCApCmBgYAoKSGVyZSB3ZSBydW4gYW4gYW5hbHlzaXMgcGlwZWxpbmUgaW4gUiBmb3IgdGhlIGlkZW50aWZpY2F0aW9uIG9mIGNpcmNSTkEuIFRoZSBwaXBlbGluZSBhdHRlbXB0cyB0byByZWNyZWF0ZSBwYXJ0cyBvZiB0aGUgW0tOSUZFIHBpcGVsaW5lXShodHRwczovL2dpdGh1Yi5jb20vbGluZGFzemFiby9LTklGRSkgZGVzY3JpYmVkIGluIHRoZSBwYXBlciBieSBAU3phYm8yMDE1LiBUaGUgY29kZSBwcm92aWRlZCBoZXJlIHVzZXMgdGhlICpzeXN0ZW1QaXBlUiogcGFja2FnZSB3aXRoIGZ1bmN0aW9ucyBkZXNpZ25lZCB0byBydW4gdGhlIHBpcGxlbGluZSBvbiBhIGxvY2FsIGRlc2t0b3AuIENvbnN1bHQgdGhlICpzeXN0ZW1QaXBlUiogZG9jdW1lbnRhdGlvbiB0byBtb2RpZnkgdGhlIGNvZGUgc28gaXQgY2FuIGJlIHJ1biBvbiBhIGNsdXN0ZXIuCgoKIyMgUHJlcGFydG9yeSBzdGVwcwoKVG8gaW5zdGFsbCBhbmQgcmVzb2x2ZSBiaW9pbmZvcm1hdGljcyBzb2Z0d2FyZSBkZXBlbmRlY2llcywgeW91IGNhbiB1c2UgdGhlIFtCaW9jb25kYV0oaHR0cDovL2Jpb2NvbmRhLmdpdGh1Yi5pby8pIHBhY2thZ2UgbWFuYWdlci4gRmlyc3QgZG93bmxvYWQgW01pbmljb25kYV0oaHR0cDovL2NvbmRhLnB5ZGF0YS5vcmcvbWluaWNvbmRhLmh0bWwpLCB0aGVuIGNyZWF0ZSBhbiBlbnZpcm9ubWVudCwgYWN0aXZhdGUgaXQsIGFkZCBiaW9jb25kYSBjaGFubmVscywgYW5kIGluc3RhbGwgdGhlIHJlcXVpcmVkIHNvZnR3YXJlLiBGb3IgZXhhbXBsZSwgaGVyZSB3ZSBjcmVhdGUgYSBsb2NhbCBlbnZpcm9ubWVudCBuYW1lZCAqcm5hc2VxKiBhbmQgaW5zdGFsbCAqYm93dGllMiogYW5kICp0cmltLWdhbG9yZSouCgpgYGAKY29uZGEgY3JlYXRlIC1uIHJuYXNlcSBweXRob249MiBhbmFjb25kYQpzb3VyY2UgYWN0aXZhdGUgcm5hc2VxCmNvbmRhIGNvbmZpZyAtLWFkZCBjaGFubmVscyBjb25kYS1mb3JnZQpjb25kYSBjb25maWcgLS1hZGQgY2hhbm5lbHMgZGVmYXVsdHMKY29uZGEgY29uZmlnIC0tYWRkIGNoYW5uZWxzIHIKY29uZGEgY29uZmlnIC0tYWRkIGNoYW5uZWxzIGJpb2NvbmRhCmNvbmRhIGluc3RhbGwgYm93dGllMgpjb25kYSBpbnN0YWxsIHRyaW0tZ2Fsb3JlCmBgYAojIyMgMS4gQ3JlYXRlIHByb2plY3QgZGlyZWN0b3JpZXMgCgpGaXJzdCBzdGFydCBhIHByb2plY3QgYW5kIGNyZWF0ZSBzdWJkaXJlY3RvcmllcyB1bmRlciB0aGUgcHJvamVjdCdzIHdvcmtpbmcgZGlyZWN0b3J5CgpgYGB7ciAsIGV2YWw9RkFMU0V9CmZvbGRlcnMgPC0gYygKICAicGFyYW1ldGVycyIsICJzcmMiLAogICJkYXRhL2dlbm9tZV9kYXRhL2hnMTkvZmFzdGEiLAogICJkYXRhL2dlbm9tZV9kYXRhL2hnMTkvYm93dGllMl9pbmRleCIsCiAgImRhdGEvYmlvc2FtcGxlcyIsCiAgInJlc3VsdHMvdHJpbW1lZCIsCiAgInJlc3VsdHMvYWxpZ25lZC9nZW5vbWUiLAogICJyZXN1bHRzL2FsaWduZWQvcmlib3NvbWFsIiwKICAicmVzdWx0cy9hbGlnbmVkL2p1bmN0aW9uc19yZWciLAogICJyZXN1bHRzL2FsaWduZWQvanVuY3Rpb25zX3NjcmFtYmxlZCIsCiAgIi9yZXN1bHRzL2FuYWx5c2lzL3JlcG9ydHMiCikgCgpmb3IgKGkgaW4gMTpsZW5ndGgoZm9sZGVycykpICB7IAogIGRpci5jcmVhdGUocGFzdGUoZ2V0d2QoKSwgZm9sZGVyc1tpXSwgc2VwID0gIi8iKSwgcmVjdXJzaXZlID0gVFJVRSkgCn0KCmBgYAoKIyMjIDIuIERvd25sb2FkIGdlbm9tZSBkYXRhCgpUbyBnZXQgc3RhcnRlZCwgd2UgbmVlZCB0byBkb3dubG9hZCB0aGUgKmhnMTlfZmFzdGFzLnRhci5neiogYXJjaGl2ZSB0aGF0IGNvbnRhaW5zIGZhc3RhIGZpbGVzIGZvciB0aGUgcmlib3NvbWUsIHRoZSBsaW5lYXIganVuY3Rpb25zLCB0aGUgc2NyYW1ibGVkIGp1bmN0aW9ucywgYW5kIHRoZSByZWZlcmVuY2UgZ2Vub21lLiBUaGUgY29tcHJlc3NlZCB0YXIgZmlsZSBpcyBhdmFpbGFibGUgYXQgdGhpcyBbbGlua10oaHR0cHM6Ly9tZWdhLm56LyNGIVJ0c0NIQ1FiIWZ5eFlOV2pvQ2VmNUllMzYxdlV4aUEhSjRkSDFicUwpCgojIyMgMy4gQnVpbGQgaW5kZXggZmlsZXMKCldlIGZpcnN0IGxvYWQgdGhlICpzeXN0ZW1QaXBlUiogcGFja2FnZSBhbmQgb3RoZXIgcmVxdWlyZWQgcGFja2FnZXMuIFdlIGFsc28gY3JlYXRlIGV4dGVybmFsIGZpbGVzIGZvciB0aGUgY29tbWFuZC1saW5lIHNvZnR3YXJlLiBUaGVzZSBhcmUgdGhlICpwYXJhbSogZmlsZXMgdGhhdCBzcGVjaWZ5IHRoZSBwYXJhbWV0ZXJzIGFuZCB0aGUgKnRhcmdldCogZmlsZXMgdGhhdCBjb250YWluIHRoZSByZWxhdGl2ZSBwYXRoIGxvY2F0aW9ucyBvZiBhbnkgcmVxdWlyZWQgZGF0YSBmaWxlcy4KCmBgYHtyICwgbWVzc2FnZT1GQUxTRX0gCiNzb3VyY2UoImh0dHA6Ly9iaW9jb25kdWN0b3Iub3JnL2Jpb2NMaXRlLlIiKQojYmlvY0xpdGUoInRnaXJrZS9zeXN0ZW1QaXBlUiIsIGRlcGVuZGVuY2llcyA9IFRSVUUpCmxpYnJhcnkoc3lzdGVtUGlwZVIpCmxpYnJhcnkoZHBseXIpCmBgYAoKVGhlIGluZGV4IGZpbGVzIGFyZSBsYXJnZSBhbmQgbWlnaHQgdGFrZSBhIGxvbmcgdGltZSB0byBkb3dubG9hZCBvbiBhIHJlZ3VsYXIgY29ubmVjdGlvbi4gSXQgY291bGQgYmUgZmFzdGVyIHRvIGp1c3QgcnVuIHRoZSBCb3d0aWUgaW5kZXggYnVpbGRlciB0byBjcmVhdGUgdGhlIGluZGV4IGZpbGVzLiBUaGVzZSB0d28gbGluZXMgYnVpbGQgdGhlIGluZGljZXMuIFRoZSBwYXJhbSBhbmQgdGFyZ2V0IGZpbGVzIGFyZSBhdmFpbGFibGUgW2hlcmVdKGh0dHBzOi8vZ2l0aHViLmNvbS9yZmFyb3VuaS9yZmFyb3VuaS5naXRodWIuaW8vdHJlZS9tYXN0ZXIvYXNzZXRzL3Byb2plY3RzL2NpcmNSTkEpLgoKYGBge3IgLCBldmFsPUZBTFNFfSAKYXJnc19ib3d0aWUyX2luZGV4IDwtIHN5c3RlbUFyZ3Moc3lzbWEgPSAiLi9wYXJhbWV0ZXJzL2Jvd3RpZTJfaW5kZXgucGFyYW0iLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbXl0YXJnZXRzID0gIi4vcGFyYW1ldGVycy9oZzE5X2Zhc3RhX2ZpbGVwYXRocy50eHQiKQpydW5Db21tYW5kbGluZShhcmdzID0gYXJnc19ib3d0aWUyX2luZGV4KQpgYGAKCiMjIyA0LiBEb3dubG9hZCBzYW1wbGUgZGF0YQoKV2UgdXNlIHRoZSBzYW1lIEhFUjIgUG9zaXRpdmUgQnJlYXN0IFR1bW9yIGRhdGFzZXQgdXNlZCBpbiB0aGUgS05JRkUgYXMgc2FtcGxlIGRhdGEuIFRoZSBkYXRhIGlzIG1hZGUgdXAgb2YgcGFpcmVkLWVuZCByZWFkcyBmcm9tIGEgdG90YWwgUk5BIHJSTkEtZGVwbGV0ZWQgUk5BLXNlcSBsaWJyYXJ5LiBNb3JlIGRldGFpbHMgYWJvdXQgdGhlIGV4cGVyaW1lbnQgY2FuIGJlIGZvdW5kIFtoZXJlXShodHRwczovL3d3dy5uY2JpLm5sbS5uaWguZ292L2dlby9xdWVyeS9hY2MuY2dpP2FjYz1HU00xMjYxMDMyKS4gVG8gZW5zdXJlIHJlcHJvZHVjaWJpbGl0eSBhbmQgZGF0YSBwcm92ZW5hbmNlLCB3ZSBkb3dubG9hZCB0aGUgU1JBIGRhdGEgZmlsZXMgZGlyZWN0bHkgZnJvbSB0aGUgR0VPIGRhdGFiYXNlLiAKCmBgYHtyICwgZXZhbD1GQUxTRX0KZG93bmxvYWQuZmlsZSgiZnRwOi8vZnRwLXRyYWNlLm5jYmkubmxtLm5paC5nb3Yvc3JhL3NyYS1pbnN0YW50L3JlYWRzL0J5RXhwL3NyYS9TUlgvU1JYMzc0L1NSWDM3NDg2Ni9TUlIxMDI3MTg3L1NSUjEwMjcxODcuc3JhIiwgIi4vZGF0YS9iaW9zYW1wbGVzL1NSUjEwMjcxODcuc3JhIikKYGBgCgpOZXh0IHdlIGNvbnZlcnQgdGhlIFNSQSBmaWxlIHRvIGZhc3RxIHVzaW5nICpmYXN0cS1kdW1wKi4gVGhlIG9wdGlvbnMgcHJvdmlkZWQgc3BlY2lmeSB0aGUgZm9sbG93aW5nOiBzcGxpdCB0aGUgcGFpcmVkIHJlYWRzIGludG8gdHdvIGZpbGVzLCBkdW1wIHRoZSBiaW9sb2dpY2FsIHJlYWRzIG9ubHksIGFuZCBjb21wcmVzcyB0aGUgb3V0cHV0IHVzaW5nIGd6aXAuIFdlIGFsc28ga2VlcCB0aGUgZGVmYXVsdHMgYnkgbm90IGFkZGluZyByZWFkIGlkcyBhbmQgbGVhdmluZyB0aGUgcXVhbGl0eSBlbmNvZGluZyBhcyBBU0NJSSszMy4KCmBgYHtyICwgZXZhbD1GQUxTRX0Kc3lzdGVtKCJmYXN0cS1kdW1wIC0tc3BsaXQtZmlsZXMgLS1za2lwLXRlY2huaWNhbCAtLWd6aXAgLi9kYXRhL2Jpb3NhbXBsZXMvU1JSMTAyNzE4Ny5zcmEgLS1vdXRkaXIgLi9kYXRhL2Jpb3NhbXBsZXMvIikKYGBgCgojIyMgNS4gVHJpbSBkYXRhCgpSZWFkcyBpbiB0aGUgcmF3IGRhdGEgY2FuIGNvbnRhaW4gbG93IHF1YWxpdHkgYmFzZXMgYW5kIHBhcnRpYWwgYWRhcHRlciBzZXF1ZW5jZXMgdGhhdCBuZWVkcyB0byBiZSByZW1vdmVkLiBUcmltbWVkIHJlYWRzIHRoYXQgYXJlIHRvbyBzaG9ydCBzaG91bGQgYWxzbyBiZSBmaWx0ZXJlZCBpbiBvcmRlciB0byBvYnRhaW4gZ29vZCBhbGlnbm1lbnQuICAgIAoKYGBge3IgfQoKYXJnc190cmltIDwtIHN5c3RlbUFyZ3Moc3lzbWEgPSAiLi9wYXJhbWV0ZXJzL3RyaW1fZ2Fsb3JlLnBhcmFtIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgIG15dGFyZ2V0cyA9ICIuL3BhcmFtZXRlcnMvZGF0YV9maWxlcGF0aHMudHh0IikKdHJpbV9jb21tYW5kIDwtIHBhc3RlKHN5c2FyZ3MoYXJnc190cmltKVsxXSwgCiAgICAgICAgICAgICAgICAgICAgICB1bmxpc3Qoc3Ryc3BsaXQoc3lzYXJncyhhcmdzX3RyaW0pWzJdLCAiICIpKVs4XSwKICAgICAgICAgICAgICAgICAgICAgIHNlcCA9ICIgIikKKHRyaW1fY29tbWFuZCkKYGBgCgpBbHRob3VnaCB3ZSBhcmUgdHJlYXRpbmcgZWFjaCBwYWlyZWQgZW5kIHJlYWQgaW5kZXBlbmRlbnRseSwgd2UgYWRkIHRoZSB0aGUgKi0tcGFpcmVkKiBvcHRpb24gYXJndW1lbnQgaW4gdGhlIHRyaW1fZ2Fsb3JlIGNvbW1hbmQgdG8gcGVmb3JtIGEgdmFsaWRhdGlvbiB0ZXN0IHRoYXQgZW5zdXJlcyB0aGF0IGJvdGggc2VxdWVuY2UgcGFpcnMgaGF2ZSBhIGNlcnRhaW4gbWluaW11bSBsZW5ndGggc3BlY2lmaWVkIGJ5IHRoZSBvcHRpb24gLSotbGVuZ3RoKi4gVG8gcnVuIHRyaW0tZ2Fsb3JlIHdlIHVzZSB0aGUgKnN5c3RlbSogZnVuY3Rpb24gCgpgYGB7ciAsIGV2YWw9RkFMU0V9CnN5c3RlbSh0cmltX2NvbW1hbmQpCmBgYAoKIyMjIDYuIFJ1biBkYXRhIHF1YWxpdHkgY29udHJvbCBjaGVja3MKCkxhc3RseSwgd2UgZ2VuZXJhdGUgYW5kIHBsb3QgRkFTVFEgcXVhbGl0eSBzdW1tYXJ5IHN0YXRpc3RpY3MuICBUaGUgW2Zhc3RxYyBtYW51YWxdKGh0dHBzOi8vYmlvZi1lZHUuY29sb3JhZG8uZWR1L3ZpZGVvcy9kb3dlbGwtc2hvcnQtcmVhZC1jbGFzcy9kYXktNC9mYXN0cWMtbWFudWFsKSBwcm92aWRlcyBhIGdvb2QgZXhwbGFudGlvbiBvZiB0aGUgZmFzdHFjIHBsb3RzLiBGaXJzdCB3ZSBkZWZpbmUgYSBoZWxwZXIgZnVuY3Rpb24KCmBgYHtyICwgZXZhbD1GQUxTRX0KcnVuRmFzdHFQbG90cyA8LSBmdW5jdGlvbihmaWxlX2xpc3QsIHJlcG9ydF9uYW1lKSB7CiAgZnFsaXN0IDwtIGJwbGFwcGx5KHNlcV9hbG9uZyhmaWxlX2xpc3QpLAogICAgICAgICAgICAgICAgICAgICBmdW5jdGlvbih4KQogICAgICAgICAgICAgICAgICAgICAgIHNlZUZhc3RxKAogICAgICAgICAgICAgICAgICAgICAgICAgZmFzdHEgPSBmaWxlX2xpc3RbeF0sCiAgICAgICAgICAgICAgICAgICAgICAgICBiYXRjaHNpemUgPSAxMDAwMDAsCiAgICAgICAgICAgICAgICAgICAgICAgICBrbGVuZ3RoID0gOAogICAgICAgICAgICAgICAgICAgICAgICksCiAgICAgICAgICAgICAgICAgICAgIEJQUEFSQU0gPSBNdWx0aWNvcmVQYXJhbSh3b3JrZXJzID0gNCkpCiAgcG5nKAogICAgc3ByaW50ZigKICAgICAgIi4vcmVzdWx0cy9hbmFseXNpcy9yZXBvcnRzL2Zhc3RxX3JlcG9ydF8lcy5wbmciLAogICAgICByZXBvcnRfbmFtZQogICAgKSwKICAgIGhlaWdodCA9IDgwMCwKICAgIHdpZHRoID0gMjAwKiBsZW5ndGgoZnFsaXN0KSwKICApCiAgc2VlRmFzdHFQbG90KHVubGlzdChmcWxpc3QsIHJlY3Vyc2l2ZSA9IEZBTFNFKSkKICBkZXYub2ZmKCkKfQpgYGAKCnRoZW4gd2UgcnVuIGl0IG9uIHRoZSByYXcgZGF0YQoKYGBge3IgLCBldmFsPUZBTFNFfSAKcnVuRmFzdHFQbG90cyhmaWxlX2xpc3QgPSBpbmZpbGUxKGFyZ3NfdHJpbSksCiAgICAgICAgICAgICAgcmVwb3J0X25hbWUgPSAiU1JSMTAyNzE4NyIpCmBgYAoKYW5kIG9uIHRoZSB0cmltbWVkIGRhdGEKCmBgYHtyICwgZXZhbD1GQUxTRX0KZGF0YV90cmltbWVkX2ZpbGVwYXRocyA8LSBwYXN0ZTAob3V0ZmlsZTEoYXJnc190cmltKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgU2FtcGxlTmFtZShhcmdzX3RyaW0pLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYygiX3ZhbF8xLmZxLmd6IiwgIl92YWxfMi5mcS5neiIpKQpuYW1lcyhkYXRhX3RyaW1tZWRfZmlsZXBhdGhzKSA8LSBwYXN0ZTAoU2FtcGxlTmFtZShhcmdzX3RyaW0pLCJfdHJpbW1lZCIpCgpydW5GYXN0cVBsb3RzKGZpbGVfbGlzdCA9IGRhdGFfdHJpbW1lZF9maWxlcGF0aHMsCiAgICAgICAgICAgICAgcmVwb3J0X25hbWUgPSAiU1JSMTAyNzE4N190cmltbWVkIikKYGBgCgpOZXh0IHdlIHBsb3QgdGhlIHJlcG9ydHMgc2lkZSBieSBzaWRlCgohW2Zhc3RxY3Bsb3RzXSguL3Jlc3VsdHMvYW5hbHlzaXMvcmVwb3J0cy9mYXN0cV9yZXBvcnRfU1JSMTAyNzE4Ny5wbmcpICFbZmFzdHFjX3RyaW1tZWRfcGxvdHNdKC4vcmVzdWx0cy9hbmFseXNpcy9yZXBvcnRzL2Zhc3RxX3JlcG9ydF9TUlIxMDI3MTg3X3RyaW1tZWQucG5nKQoKVGhlIHBsb3RzIGZvciB0aGUgdHJpbW1lZCBkYXRhIGxvb2sgbXVjaCBiZXR0ZXIuIEFsdGhvdWdoIHRoZSBtZWFuIHF1YWxpdHkgZGlzdHJpYnV0aW9uIGZvciB0aGUgcmVhZHMgaXMgYmltb2RhbCwgdGhlcmUgYXJlIG5vIGxvdyBxdWFsaXR5IHBlYWtzLiAKCiMjIEFsaWdubWVudCBTdGVwcwoKTm93IHdlIGluZGVwZW5kZW50bHkgYWxpZ24gZWFjaCBwYWlyZWQtZW5kIHJlYWQgZGF0YXNldCB0byBmb3VyIHNlcGFyYXRlIEJvd3RpZTIgaW5kaWNlczogZm9yIHRoZSBnZW5vbWUsIHJpYm9zb21hbCBSTkEsIGxpbmVhciBleG9u4oCTZXhvbiBqdW5jdGlvbnMsIGFuZCBzY3JhbWJsZWQgZXhvbuKAk2V4b24ganVuY3Rpb25zLiBGaXJzdCB3ZSBsb2FkIEJvd3RpZTIgcGFyYW1ldGVyIGZpbGVzIGFuZCBmaWxlcGF0aHMgCgpgYGB7ciB9IAphcmdzX2FsaWduX2dlbm9tZSA8LSBzeXN0ZW1BcmdzKHN5c21hID0gIi4vcGFyYW1ldGVycy9ib3d0aWUyX2FsaWduX2dlbm9tZS5wYXJhbSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbXl0YXJnZXRzID0gIi4vcGFyYW1ldGVycy9kYXRhX3RyaW1tZWRfZmlsZXBhdGhzLnR4dCIpCmFyZ3NfYWxpZ25fcmlibyA8LSBzeXN0ZW1BcmdzKHN5c21hID0gIi4vcGFyYW1ldGVycy9ib3d0aWUyX2FsaWduX3JpYm9zb21hbC5wYXJhbSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG15dGFyZ2V0cyA9ICIuL3BhcmFtZXRlcnMvZGF0YV90cmltbWVkX2ZpbGVwYXRocy50eHQiKQphcmdzX2FsaWduX2p1bmN0c19yZWcgPC0gc3lzdGVtQXJncyhzeXNtYSA9ICIuL3BhcmFtZXRlcnMvYm93dGllMl9hbGlnbl9qdW5jdGlvbnNfcmVnLnBhcmFtIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbXl0YXJnZXRzID0gIi4vcGFyYW1ldGVycy9kYXRhX3RyaW1tZWRfZmlsZXBhdGhzLnR4dCIpCmFyZ3NfYWxpZ25fanVuY3RzX3NjcmFtIDwtIHN5c3RlbUFyZ3Moc3lzbWEgPSAiLi9wYXJhbWV0ZXJzL2Jvd3RpZTJfYWxpZ25fanVuY3Rpb25zX3NjcmFtYmxlZC5wYXJhbSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbXl0YXJnZXRzID0gIi4vcGFyYW1ldGVycy9kYXRhX3RyaW1tZWRfZmlsZXBhdGhzLnR4dCIpCmBgYAoKdGhlbiB3ZSBydW4gdGhlIEJvd3RpZTIgYWxpZ25tZW50IGNvbW1hbmRzIHRocm91Z2ggKnN5c3RlbVBpcGVSKgpgYGB7ciAsIGV2YWw9RkFMU0V9IAojIGFsaWduIHRvIGdlbm9tZQpnZW5vbWVfYmFtX2ZpbGVzIDwtIHJ1bkNvbW1hbmRsaW5lKGFyZ3MgPSBhcmdzX2FsaWduX2dlbm9tZSkKIyBhbGlnbiB0byByaWJvc29tZQpyaWJvX2JhbV9maWxlcyA8LSBydW5Db21tYW5kbGluZShhcmdzID0gYXJnc19hbGlnbl9yaWJvKQojIGFsaWduIHRvIGxpbmVhciBqdW5jdGlvbnMKanVuY3RzX3JlZ19iYW1fZmlsZXMgPC0gcnVuQ29tbWFuZGxpbmUoYXJncyA9IGFyZ3NfYWxpZ25fanVuY3RzX3JlZykKIyBhbGlnbiB0byBzcmFtYmxlZCBqdW5jdGlvbnMKanVuY3RzX3NjcmFtX2ZpbGVzIDwtIHJ1bkNvbW1hbmRsaW5lKGFyZ3MgPSBhcmdzX2FsaWduX2p1bmN0c19zY3JhbSkKYGBgCgpXZSBjYW4gb3BlbiAic3VibWl0YXJnczAxX2xvZyIgZm9yIGVhY2ggYWxpZ25tZW50IHRvIGRpc3BsYXkgdGhlIGFsaWdubWVudCBzdW1tYXJ5IGFuZCB0aGUgc3lzdGVtIGNvbW1hbmQgdXNlZC4gRm9yIHRoZSBnZW5vbWUgYWxpZ25tZW50LCB3ZSBjYW4gb3BlbiB0aGUgZmlsZSBpbnNpZGUgUiBsaWtlIHRoaXMKYGBge3IsIHNpemU9J2Zvb3Rub3Rlc2l6ZSd9CnJlYWRMaW5lcyhmaWxlLnBhdGgoIi4vcmVzdWx0cy9hbGlnbmVkL2dlbm9tZSIsInN1Ym1pdGFyZ3MwMV9sb2ciKSkKYGBgCiMjIFByZXByb2Nlc3Npbmcgc3RlcHMKClRoZSBwcmV2aW91cyBhbGlnbm1lbnQgc3RlcCBwcm9kdWNlZCA4IEJBTSBmaWxlcy4gVHdvIHBhaXJlZC1lbmQgQkFNIGZpbGVzIGZvciBlYWNoIG9mIHRoZSBnZW5vbWUsIHJpYm9zb21lLCBsaW5lYXIganVuY3Rpb25zLCBhbmQgc2NyYW1ibGVkIGp1bmN0aW9ucyBhbGlnbm1lbnRzLiBXZSBhcmUgaW50ZXJlc3RlZCBpbiBqdW5jdGlvbmFsIHJlYWRzLCB0aG9zZSByZWFkcyBmcm9tIHRoZSA4IGZpbGVzIHRoYXQgYWxpZ24gdG8gYSBzY3JhbWJsZWQgb3IgbGluZWFyIGp1bmN0aW9ucy4gU2luY2UgdGhlIG1ham9yaXR5IG9mIHRoZSByZWFkcyBpbiB0aGUgZ2Vub21lIGFuZCByaWJvc29tZSBCQU0gZmlsZXMgYXJlIG5vdCBqdW5jdGlvbmFsLCB3ZSBuZWVkIHRvIGZpbHRlciB0aGVtIG91dC4KCiMjIyAgMS4gR2V0IHJlYWQgSURzIGZvciBnZW5vbWljIG1hdGUgcGFpcnMKClNpbmNlIHRoZSBuZXh0IHByZXByb2Nlc3Npbmcgc3RlcHMgYXJlIG1lbW9yeSBpbnRlbnNpdmUsIHdlIGNhbiBpbnRpYWxseSBmaWx0ZXIgb3V0IHJlYWRzIGluIHdoaWNoIGJvdGggcGFpciBtYXRlcyBhbGlnbiB0byB0aGUgZ2Vub21lIG9yIHJpYm9zb21lLiBXZSBjYW4gYWxzbyBmaWx0ZXIgb3V0IHVuYWxpZ25lZCByZWFkcyBhbmQgcmV0dXJuIGxpc3RzIG9mIHJlYWQgSURzIGluIHdoaWNoIGJvdGggcGFpcnMgYWxpZ24gdG8gdGhlIGluZGljaWVzLCBvciBpbiB3aGljaCBvbmUgcGFpciBtYXRlIGFsaWducyBidXQgdGhlIG90aGVyIGRvZXNuJ3QuIFRvIHByb2NlZWQgd2UgbmVlZCB0byBnZXQgdGhlIG5hbWVzIG9mIHRob3NlIHJlYWRzIHRoYXQgYXJlIGZvdW5kIGluIGJvdGggUjEgYW5kIFIyICBnZW5vbWljIGFsaWdubWVudCBmaWxlcy4gCgpgYGB7ciB9IAoKZ2V0UmVhZElkcyA8LSBmdW5jdGlvbihiYW1maWxlKSB7CiAgc2NhbkJhbShiYW1maWxlLAogICAgICAgICAgcGFyYW0gPSBTY2FuQmFtUGFyYW0oZmxhZyA9IHNjYW5CYW1GbGFnKGlzVW5tYXBwZWRRdWVyeSA9IEZBTFNFKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHdoYXQgPSAicW5hbWUiKSlbWzFdXVtbMV1dCiAgfQoKZ2V0Q29tbW9uR2Vub21pY1BhaXJlZFJlYWRzIDwtIGZ1bmN0aW9uKGdlbm9tZV9iYW1maWxlcywgcmlib19iYW1maWxlcykgewogIAogIGdlbm9tZV9yZWFkcyA8LSBicGxhcHBseShnZW5vbWVfYmFtZmlsZXMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGdldFJlYWRJZHMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIEJQUEFSQU0gPSBNdWx0aWNvcmVQYXJhbSh3b3JrZXJzID0gMikpCiAgcmlib3NvbWFsX3JlYWRzIDwtIGJwbGFwcGx5KHJpYm9fYmFtZmlsZXMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdldFJlYWRJZHMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEJQUEFSQU0gPSBNdWx0aWNvcmVQYXJhbSh3b3JrZXJzID0gMikpCiAgCiAgZ2Vub21pY19yZWFkc18xIDwtIHVuaW9uKGdlbm9tZV9yZWFkc1tbMV1dLCByaWJvc29tYWxfcmVhZHNbWzFdXSkKICBnZW5vbWljX3JlYWRzXzIgPC0gdW5pb24oZ2Vub21lX3JlYWRzW1syXV0sIHJpYm9zb21hbF9yZWFkc1tbMl1dKQogIGNvbW1vbl9yZWFkcyA8LSBpbnRlcnNlY3QoZ2Vub21pY19yZWFkc18xLCBnZW5vbWljX3JlYWRzXzIpCiAgcjFfbm90X3IyIDwtIHNldGRpZmYoZ2Vub21pY19yZWFkc18xLCBnZW5vbWljX3JlYWRzXzIpCiAgcjJfbm90X3IxIDwtIHNldGRpZmYoZ2Vub21pY19yZWFkc18yLCBnZW5vbWljX3JlYWRzXzEpCiAgCiAgcmV0dXJuKAogICAgbGlzdCgKICAgICAgY29tbW9uX3JlYWRzID0gY29tbW9uX3JlYWRzLAogICAgICByMV9ub3RfcjIgPSByMV9ub3RfcjIsCiAgICAgIHIxX25vdF9yMiA9IHIyX25vdF9yMQogICAgKQogICkKfQpgYGAgIAoKTm90ZSB0aGF0IGNvZGUgYWJvdmUgdXNlcyB0aGUgcGFyYWxsZWwgbGFwcGx5IGZ1bmN0aW9uICpicGxhcHBseSouIElmIHlvdSBzeXN0ZW0gaGFzIGxlc3MgdGhhbiAzMkcgUkFNLCB5b3UgY2FuIG1vZGlmeSB0aGUgY29kZSB0byBydW4gd2l0aCBvbmUgd29ya2VyIG9yIHVzZSAqbGFwcGx5KiBpbnN0ZWFkLiBIZXJlIHdlIHJldHJpZXZlIHRoZSBsb2NhdGlvbiBvZiB0aGUgYWxpZ25lZCBmaWxlcyBhbmQgZmVlZCB0aGVtIGludG8gdGhlIGZ1bmN0aW9uIGRlZmluZWQgYWJvdmUuCmBgYHtyIH0gCmdlbm9tZV9hbGlnbmVkIDwtIG91dHBhdGhzKGFyZ3NfYWxpZ25fZ2Vub21lKQpyaWJvX2FsaWduZWQgPC0gb3V0cGF0aHMoYXJnc19hbGlnbl9yaWJvKQoKcmVhZHNfdG9faWdub3JlIDwtIGdldENvbW1vbkdlbm9taWNQYWlyZWRSZWFkcyhnZW5vbWVfYWxpZ25lZCwgcmlib19hbGlnbmVkKQpgYGAKCiMjIyAgMi4gUmVhZCBhbGlnbm1lbnQgZGF0YSBhbmQgcnVuIGluaXRpYWwgZmlsdGVyaW5nCgpOb3cgd2UgdXNlIHRoZXNlIHJlYWQgSURzIHRvIGNyZWF0ZSB0d28gZmlsdGVycy4gVGhlIGZpcnN0IG9uZSBmaWx0ZXJzIG91dCBnZW5vbWljIHJlYWRzIGluIHdoaWNoIGJvdGggbWF0ZXMgKFIxIGFuZCBSMikgYWxpZ24uIFRoZSBzZWNvbmQgb25lIGlzIGEganVuY3Rpb25hbCBmaWx0ZXIgdGhhdCBrZWVwcyBvbmx5IGp1bmN0aW9uYWwgcmVhZHMgdGhhdCBvdmVybGFwIGEgcGFydGljdWxhciBqdW5jdGlvbiBieSBhdCBsZWFzdCA4IG51Y2xlb3RpZGVzIGFuZCB0aGF0IGFsc28gc2F0aWZ5IHRoZSBmaXJzdCBmaWx0ZXIuIAogCmBgYHtyfQpnZW5vbWljX3JlYWRzX2ZpbHRlciA8LQogIEZpbHRlclJ1bGVzKGxpc3QoCiAgICBpbmNsdWRlID0gZnVuY3Rpb24oeCkgIShtY29scyh4KSRxbmFtZSAlaW4lIHJlYWRzX3RvX2lnbm9yZSRjb21tb25fcmVhZHMpCiAgKSkKCk1JRFBPSU5UIDwtIDE1MApKVU5DX09WRVJMQVAgPC0gOAoKanVuY3Rpb25fcmVhZHNfZmlsdGVyIDwtCiAgRmlsdGVyUnVsZXMobGlzdCgKICAgIGluY2x1ZGUgPSBmdW5jdGlvbih4KQogICAgICAoCiAgICAgICAgKE1JRFBPSU5UICsgSlVOQ19PVkVSTEFQICsgMSAtIHdpZHRoKHgpIDw9IHN0YXJ0KHgpKSAmCiAgICAgICAgKHN0YXJ0KHgpIDw9IE1JRFBPSU5UIC0gSlVOQ19PVkVSTEFQICsgMSkgJgogICAgICAgICEobWNvbHMoeCkkcW5hbWUgJWluJSByZWFkc190b19pZ25vcmUkY29tbW9uX3JlYWRzKQogICAgICApCiAgKSkKYGBgCgpBcyBwYXJ0IG9mIHRoZSBmaWx0ZXJpbmcgc3RlcCwgd2UgYWxzbyB1cGRhdGUgdGhlIGFsaWdubWVudCBzY29yZSBmb3IgdGhlIGp1bmN0aW9uIHJlYWRzIHN1Y2ggdGhhdCB0aGUgTi1wZW5hbHR5IGlzIGNvcnJlY3RlZC4KYGBge3J9Cgpjb3VudEFtYmlnb3VzQmFzZXMgPC0gZnVuY3Rpb24oYWxpZ25fb2JqKSB7CiAgc2FwcGx5KGdyZWdleHByKCJOMHwwTiIsIG1jb2xzKGFsaWduX29iaikkTUQpLCAKICAgICAgICAgZnVuY3Rpb24oeCkgCiAgICAgICAgICAgaWYgKGF0dHJpYnV0ZXMoeCkkbWF0Y2gubGVuZ3RoWzFdID09IC0xKSAKICAgICAgICAgICAgIHJldHVybigwKSAKICAgICAgICAgZWxzZSAKICAgICAgICAgICByZXR1cm4obGVuZ3RoKHgpKQogICkKfQoKI01EIGFuZCBYTiBkbyBhZ3JlZSAKZmlsdGVyQWxpZ25tZW50cyA8LQogIGZ1bmN0aW9uKGJhbWZpbGUsIHBhcmFtLCBmaWx0ZXIsIHVwZGF0ZVNjb3JlID0gVFJVRSkgewogICAgeCA8LSByZWFkR0FsaWdubWVudHMoZmlsZSA9IGJhbWZpbGUsIHBhcmFtID0gcGFyYW0pCiAgICB4IDwtIHN1YnNldEJ5RmlsdGVyKHgsIGZpbHRlcikKICAgIHNlcWxldmVscyh4KSA8LSBzZXFsZXZlbHNJblVzZSh4KQogICAgIyBjb3JyZWN0IGZvciBOLXBlbmFsdHkgaW4ganVuY3Rpb24gYWxpZ25tZW50cyBhbmQgdXBkYXRlIGFsaWdubWVudCBzY29yZQogICAgaWYgKHVwZGF0ZVNjb3JlKSB7CiAgICAgIG1jb2xzKHgpJEFTIDwtIG1jb2xzKHgpJEFTICsgY291bnRBbWJpZ291c0Jhc2VzKHgpCiAgICB9CiAgICByZXR1cm4oeCkKICB9CgojIGdldCBmaWxlIHBhdGhzCmxpbmVhcl9qdW5jdGlvbnNfYWxpZ25lZCA8LSBvdXRwYXRocyhhcmdzX2FsaWduX2p1bmN0c19yZWcpCnNjcmFtYmxlZF9qdW5jdGlvbnNfYWxpZ25lZCA8LSBvdXRwYXRocyhhcmdzX2FsaWduX2p1bmN0c19zY3JhbSkKCiMgcnVuIGZpbHRlcnMKCnBhcmFtIDwtCiAgU2NhbkJhbVBhcmFtKAogICAgd2hhdCA9IGMoInFuYW1lIiwibWFwcSIpLCAjIGdldCB0aGUgcmVhZCBuYW1lIGFuZCB0aGUgbWFwcGluZyBxdWFsaXR5IAogICAgZmxhZyA9IHNjYW5CYW1GbGFnKGlzVW5tYXBwZWRRdWVyeSA9IEZBTFNFKSwKICAgIHRhZyA9IGMoIkFTIiwgIlhTIiwgIlhOIiwgIk1EIikgIyBnZXQgdGFncwogICkKCmdlbm9taWNfZmlsdGVyZWQgPC0gYnBsYXBwbHkoCiAgZ2Vub21lX2FsaWduZWQsCiAgZmlsdGVyQWxpZ25tZW50cywKICBwYXJhbSA9IHBhcmFtLAogIGZpbHRlciA9IGdlbm9taWNfcmVhZHNfZmlsdGVyLAogIHVwZGF0ZVNjb3JlID0gRkFMU0UsCiAgQlBQQVJBTSA9IE11bHRpY29yZVBhcmFtKHdvcmtlcnMgPSAyKQopCgpsaW5lYXJfanVuY3Rpb25zX2ZpbHRlcmVkIDwtIGJwbGFwcGx5KAogIGxpbmVhcl9qdW5jdGlvbnNfYWxpZ25lZCwKICBmaWx0ZXJBbGlnbm1lbnRzLAogIHBhcmFtID0gcGFyYW0sCiAgZmlsdGVyID0ganVuY3Rpb25fcmVhZHNfZmlsdGVyLAogIEJQUEFSQU0gPSBNdWx0aWNvcmVQYXJhbSh3b3JrZXJzID0gMikKKQoKc2NyYW1ibGVkX2p1bmN0aW9uc19maWx0ZXJlZCA8LSBicGxhcHBseSgKICBzY3JhbWJsZWRfanVuY3Rpb25zX2FsaWduZWQsCiAgZmlsdGVyQWxpZ25tZW50cywKICBwYXJhbSA9IHBhcmFtLAogIGZpbHRlciA9IGp1bmN0aW9uX3JlYWRzX2ZpbHRlciwKICBCUFBBUkFNID0gTXVsdGljb3JlUGFyYW0od29ya2VycyA9IDIpCikKYGBgCgpMZXQncyB0YWtlIGEgbG9vayBhdCBmaXJzdCBmZXcgcm93cyBvZiB0aGUgR0FsaWdubWVudCBvYmplY3QgZm9yIHRoZSBsaW5lYXIganVuY3Rpb25zIFIxIGFsaWduZWQgcmVhZHMKYGBge3J9CmxpbmVhcl9qdW5jdGlvbnNfZmlsdGVyZWRbWzFdXVsxOjMsXSAlPiUgYXMuZGF0YS5mcmFtZSgpCmBgYApXZSBjYW4gYWxzbyBleGFtaW5lIHRoZSBmcmVxdWVuY3kgb2YgdGhlIGNpZ2FyIHN0cmluZ3MgCmBgYHtyfSAKbGluZWFyX2p1bmN0aW9uc19maWx0ZXJlZFtbMV1dICU+JSAKICBjaWdhcigpICU+JSAKICB0YWJsZSAlPiUgCiAgc29ydChkZWNyZWFzaW5nID0gVFJVRSkgJT4lIAogIGhlYWQoKSAKYGBgCgojIyMgIDMuIENvbnZlcnQgR0FsaWdubWVudCBvYmplY3RzIGludG8gZGF0YWZyYW1lcwoKCmBgYHtyfQp0b0RhdGFGcmFtZSA8LSBmdW5jdGlvbih4KSB7CiAgeCA8LSB4ICU+JSAgCiAgICBhcy5kYXRhLmZyYW1lKCkgJT4lIAogICAgc3Vic2V0KHNlbGVjdCA9IC1jKGVuZCwgY2lnYXIsIHF3aWR0aCwgbmp1bmMsIFhTLCBNRCkpCiAgY29sbmFtZXMoeClbMTo4XSA8LSAJYygianVuY3Rpb24iLCAic3RyIiwgInBvcyIsICJsZW4iLAogICAgICAgICAgICAgICAgICAgICAgICAgImlkIiwgIk1RIiwgIkFTIiwgIlhOIikJCQogIHgkanVuY3Rpb24gPC0gYXMuY2hhcmFjdGVyKHgkanVuY3Rpb24pCiAgeCRzdHIgPC0gYXMuY2hhcmFjdGVyKHgkc3RyKQogIHJldHVybih4KQp9CgphcHBlbmRKdW5jdGlvblRhZ3MgPC0gZnVuY3Rpb24oeCkgewogIGRmIDwtICB4ICU+JSAKICAgIHNlcW5hbWVzKCkgJT4lIAogICAgYXMuY2hhcmFjdGVyKCkgJT4lIAogICAgbGFwcGx5KC4sIGZ1bmN0aW9uKHkpIHVubGlzdChzdHJzcGxpdCh5LCAiWzpdfFt8XSIpKSkgJT4lCiAgICBkby5jYWxsKHJiaW5kLCAuKSAlPiUKICAgIGRhdGEuZnJhbWUoc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFKQogIGNvbG5hbWVzKGRmKSA8LSBjKCJqQ2hyIiwgImpHZW5lMSIsICJqUG9zMSIsICJqR2VuZTIiLCAKICAgICAgICAgICAgICAgICAgICAialBvczIiLCAialR5cGUiLCAialN0ciIpCiAgZGYkalBvczEgPC0gYXMuaW50ZWdlcihkZiRqUG9zMSkKICBkZiRqUG9zMiA8LSBhcy5pbnRlZ2VyKGRmJGpQb3MyKQogIG1jb2xzKHgpIDwtIGNiaW5kKG1jb2xzKHgpLCBkZikKICByZXR1cm4odG9EYXRhRnJhbWUoeCkpCn0KCmdlbm9taWNfZmlsdGVyZWRfZHQgPC0gYnBsYXBwbHkoCiAgZ2Vub21pY19maWx0ZXJlZCwKICB0b0RhdGFGcmFtZSwKICBCUFBBUkFNID0gTXVsdGljb3JlUGFyYW0od29ya2VycyA9IDIpCikKCmxpbmVhcl9qdW5jdGlvbnNfZmlsdGVyZWRfZHQgPC0gYnBsYXBwbHkoCiAgbGluZWFyX2p1bmN0aW9uc19maWx0ZXJlZCwKICBhcHBlbmRKdW5jdGlvblRhZ3MsCiAgQlBQQVJBTSA9IE11bHRpY29yZVBhcmFtKHdvcmtlcnMgPSAyKQopCgpzY3JhbWJsZWRfanVuY3Rpb25zX2ZpbHRlcmVkX2R0IDwtIGJwbGFwcGx5KAogIHNjcmFtYmxlZF9qdW5jdGlvbnNfZmlsdGVyZWQsCiAgYXBwZW5kSnVuY3Rpb25UYWdzLAogIEJQUEFSQU0gPSBNdWx0aWNvcmVQYXJhbSh3b3JrZXJzID0gMikKKQoKYGBgCgpMZXQncyB0YWtlIGEgbG9vayBhdCB0aGUgZGF0YWZyYW1lIGZvciB0aGUgbGluZWFyIGp1bmN0aW9ucyBSMSBhbGlnbmVkIHJlYWRzCmBgYHtyfQpsaW5lYXJfanVuY3Rpb25zX2ZpbHRlcmVkX2R0W1sxXV1bMTozLF0KYGBgCgoKIyMjICA0LiAgRmlsdGVyIFIxIGp1bmN0aW9uYWwgcmVhZHMgYW5kIHBhaXIgdGhlbSB3aXRoIFIyIG1hdGVzCgpBIHJlYWQgKFIxKSBpcyBhIGxpbmVhciBqdW5jdGlvbiByZWFkIGlmIGl0IGFsaWducyB0byBhIGxpbmVhciBqdW5jdGlvbiBhbmQgZG9lcyBub3QgYWxpZ24gdG8gdGhlIGdlbm9tZSBvciByaWJvc29tZS4gSW4gdGhlIGNvZGUgdGhlc2UgcmVhZHMgYXJlIHN0b3JlZCBpbiAqbGluZWFyXzEqLiBBIHJlYWQgUjEgaXMgYSBzY3JhbWJsZWQganVuY3Rpb24gcmVhZCBpZiBpdCBhbGlnbnMgdG8gYSBzY3JhbWJsZWQganVuY3Rpb24gYW5kIGRvZXMgbm90IGFsaWduIHRvIHRoZSBnZW5vbWUgb3Igcmlib3NvbWUgb3IgdG8gdGhlIGxpbmVhciBqdW5jdGlvbi4gU2NyYW1ibGVkIGp1bmN0aW9uIHJlYWRzIGFyZSBzdG9yZWQgaW4gKnNjcmFtYmxlZF8xKi4gVGhlIHJlYWQgbWF0ZSBSMiBjYW4gYmUgYSBnZW5vbWljLCBsaW5lYXIganVuY3Rpb24sIG9yIGEgc2NyYW1ibGVkIGp1bmN0aW9uIHJlYWQgYXMgZGV0ZXJtaW5lZCBieSB3aGVodGVyIHRoZSByZWFkIG5hbWUgY2FuIGJlIGZvdW5kIGluIHRoZSBSMiBhbGlnbmVkIHJlYWRzLiBBbiBSMSByZWFkIGNhbiBoYXZlIGEgc2luZ2xlIG9yIG11bHRpcGxlIFIyIG1hdGVzLiBTb21lIGhhdmUgbm9uZSBhdCBhbGwuIFRoZXNlIGFyZSB0aGUgdW5tYXBwZWQgcmVhZHMuCmBgYHtyfQpjcmVhdGVKdW5jdGlvbnNSZWFkc0RGIDwtIGZ1bmN0aW9uKGdlbm9taWMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGluZWFyLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNjcmFtYmxlZCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByMV9ub3RfcjIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3VmZml4ZXMgPSBjKCIuUjEiLCAiLlIyZyIsICIuUjJsIiwgIi5SMnMiKSkgewogCiAgbGluZWFyXzEgPC0gbGluZWFyW1sxXV1bIShsaW5lYXJbWzFdXSRpZCAlaW4lIHIxX25vdF9yMiksIF0KICBzY3JhbWJsZWRfMSA8LSBzY3JhbWJsZWRbWzFdXVshKHNjcmFtYmxlZFtbMV1dJGlkICVpbiUgYyhyMV9ub3RfcjIsIGxpbmVhcl8xJGlkKSksIF0KICAKICBnZW5vbWljX21hdGUgPC0gZ2Vub21pY1tbMl1dWyhnZW5vbWljW1syXV0kaWQgJWluJSBjKGxpbmVhcl8xJGlkLHNjcmFtYmxlZF8xJGlkKSksIF0KICBsaW5lYXJfbWF0ZSA8LSBsaW5lYXJbWzJdXVsobGluZWFyW1syXV0kaWQgJWluJSBjKGxpbmVhcl8xJGlkLHNjcmFtYmxlZF8xJGlkKSksIF0KICBzY3JhbWJsZWRfbWF0ZSA8LSBzY3JhbWJsZWRbWzJdXVsoc2NyYW1ibGVkW1syXV0kaWQgJWluJSBjKGxpbmVhcl8xJGlkLCBzY3JhbWJsZWRfMSRpZCkpLCBdCiAgCiAgbGluZWFyX2dlbm9taWMgPC0gbGVmdF9qb2luKGxpbmVhcl8xLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBnZW5vbWljX21hdGUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJ5ID0gImlkIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN1ZmZpeCA9IHN1ZmZpeGVzW2MoMSwgMildKQogIAogIGxpbmVhcl9saW5lYXIgPC0gbGVmdF9qb2luKGxpbmVhcl8xLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxpbmVhcl9tYXRlICwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBieSA9ICJpZCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3VmZml4ID0gc3VmZml4ZXNbYygxLCAzKV0pCiAgCiAgbGluZWFyX3NjcmFtYmxlZCA8LSBsZWZ0X2pvaW4obGluZWFyXzEsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNjcmFtYmxlZF9tYXRlLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJ5ID0gImlkIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdWZmaXggPSBzdWZmaXhlc1tjKDEsIDQpXSkKICAKICBzY3JhbWJsZWRfZ2Vub21pYyA8LSBsZWZ0X2pvaW4oc2NyYW1ibGVkXzEsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBnZW5vbWljX21hdGUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJ5ID0gImlkIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3VmZml4ID0gc3VmZml4ZXNbYygxLCAyKV0pCiAgCiAgc2NyYW1ibGVkX2xpbmVhciA8LSBsZWZ0X2pvaW4oc2NyYW1ibGVkXzEsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGluZWFyX21hdGUgLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJ5ID0gImlkIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdWZmaXggPSBzdWZmaXhlc1tjKDEsIDMpXSkKICAKICBzY3JhbWJsZWRfc2NyYW1ibGVkIDwtIGxlZnRfam9pbihzY3JhbWJsZWRfMSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzY3JhbWJsZWRfbWF0ZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBieSA9ICJpZCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3VmZml4ID0gc3VmZml4ZXNbYygxLCA0KV0pCiAgCiAgbGluZWFyX2RmIDwtIGNiaW5kKGxpbmVhcl9saW5lYXJbLCAxOjE1XSwgCiAgICAgICAgICAgICAgICAgICAgIGxpbmVhcl9nZW5vbWljWywgMTY6MjJdLAogICAgICAgICAgICAgICAgICAgICBsaW5lYXJfbGluZWFyWywgMTY6MjldLCAKICAgICAgICAgICAgICAgICAgICAgbGluZWFyX3NjcmFtYmxlZFssIDE2OjI5XSkKICAKICBzY3JhbWJsZWRfZGYgPC0gY2JpbmQoc2NyYW1ibGVkX2xpbmVhclssIDE6MTVdLAogICAgICAgICAgICAgICAgICAgICAgICBzY3JhbWJsZWRfZ2Vub21pY1ssIDE2OjIyXSwKICAgICAgICAgICAgICAgICAgICAgICAgc2NyYW1ibGVkX2xpbmVhclssIDE2OjI5XSwKICAgICAgICAgICAgICAgICAgICAgICAgc2NyYW1ibGVkX3NjcmFtYmxlZFssIDE2OjI5XSkKICAKICByZXR1cm4obGlzdChsaW5lYXJfZGYgPSBsaW5lYXJfZGYsIHNjcmFtYmxlZF9kZiA9IHNjcmFtYmxlZF9kZikpCn0KClIxUjJNYXRlc0p1bmN0aW9uUmVhZHMgPC0gY3JlYXRlSnVuY3Rpb25zUmVhZHNERihnZW5vbWljX2ZpbHRlcmVkX2R0LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGluZWFyX2p1bmN0aW9uc19maWx0ZXJlZF9kdCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNjcmFtYmxlZF9qdW5jdGlvbnNfZmlsdGVyZWRfZHQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZWFkc190b19pZ25vcmUkcjFfbm90X3IyKQpgYGAKCiMjIyAgNS4gIENsYXNzaWZ5IE1hdGUgUmVhZHMKCkhlcmUgd2UgY2xhc3NpZnkgbWF0ZSByZWFkcyBiYXNlZCBvbiBhbGlnbm1lbnQgc2NvcmUuIFdlIGFsc28gY3JlYXRlIGEgbmV3IHZhcmlhYmxlICphc2NvcmUqIHRoYXQgYXZlcmFnZXMgdGhlIGFsaWdubWVudCBzY29yZXMgb2YgdGhlIHR3byBtYXRjaGVkIHJlYWRzLgpgYGB7cn0KY2xhc3NzaWZ5TWF0ZVJlYWRzIDwtIGZ1bmN0aW9uKGRmLCBzdWZmaXhlcyA9IGMoIi5SMSIsICIuUjJnIiwgIi5SMmwiLCAiLlIycyIpKSB7CiAgICAKICAgIHNjb3Jlc19kZiA8LSBkZlssIHBhc3RlMCgiQVMiLCBzdWZmaXhlcyldCiAgICBuTWF0ZXMgPC0gYXBwbHkoc2NvcmVzX2RmWywgMjo0XSAsIDEsIGZ1bmN0aW9uKHgpIHN1bSghaXMubmEoeCkpKQogICAgbWF4X3Njb3JlIDwtIGFwcGx5KHNjb3Jlc19kZlssIDI6NF0gLCAxLCB3aGljaC5tYXgpCiAgICAgIAogICAgbWF0ZSA8LSBzYXBwbHkobWF4X3Njb3JlLCBmdW5jdGlvbih4KSAKICAgICAgaWYgKGlzLm51bGwoYXR0cmlidXRlcyh4KSkpIAogICAgICAgIHtyZXR1cm4oMCl9IAogICAgICBlbHNlIAogICAgICAgIHtyZXR1cm4oYXMuaW50ZWdlcih4KSl9CiAgICAgICkKICAgIAogICAgbWF0ZXNjb3JlIDwtIHNjb3Jlc19kZltjYmluZCgxOm5yb3coc2NvcmVzX2RmKSwgKG1hdGUgKyAxKSldCiAgICBBU21lYW4gPC0gMC41ICogKHNjb3Jlc19kZiRBUy5SMSArIG1hdGVzY29yZSkKICAgIGRmIDwtIGNiaW5kKGRmLCAKICAgICAgICAgICAgICAgIEFTbWVhbiA9IEFTbWVhbiwKICAgICAgICAgICAgICAgIG5NYXRlcyA9IG5NYXRlcywKICAgICAgICAgICAgICAgIG1hdGUgPSBtYXRlKQogICAgcmV0dXJuKGRmKQogIH0KCmxpbmVhcl9kZiA8LSAKICBSMVIyTWF0ZXNKdW5jdGlvblJlYWRzJGxpbmVhcl9kZiAlPiUKICBjbGFzc3NpZnlNYXRlUmVhZHMoKQpzY3JhbWJsZWRfZGYgPC0gCiAgUjFSMk1hdGVzSnVuY3Rpb25SZWFkcyRzY3JhbWJsZWRfZGYgJT4lCiAgY2xhc3NzaWZ5TWF0ZVJlYWRzKCkKCmNhdCggIiBudW1iZXIgb2YgbGluZWFyIGp1bmN0aW9uIHJlYWRzIiwKICAgICBOUk9XKGxpbmVhcl9kZiksICJcbiIsCiAgICAgIm51bWJlciBvZiBzY3JhbWJsZWQganVuY3Rpb24gcmVhZHMiLAogICAgIE5ST1coc2NyYW1ibGVkX2RmKSwgIlxuIiwKICAidG90YWwgbnVtYmVyIG9mIGp1bmN0aW9uYWwgcmVhZHM6IiwgCiAgICBOUk9XKGxpbmVhcl9kZikgKyBOUk9XKHNjcmFtYmxlZF9kZiksICJcbiIsCiAgICAibnVtYmVyIG9mIGp1bmN0aW9ucyB3aXRoIGFsaWduZWQgcmVhZHM6IiwKICAgIGxlbmd0aCh1bmlxdWUobGluZWFyX2RmJGp1bmN0aW9uLlIxKSkgKwogICAgICBsZW5ndGgodW5pcXVlKHNjcmFtYmxlZF9kZiRqdW5jdGlvbi5SMSkpKQpgYGAgICAgCiAgICAgIApMZXQncyB0YWtlIGEgbG9vayBhdCB0aGUgZGF0YWZyYW1lIGZvciB0aGUgbGluZWFyIGp1bmN0aW9ucyBSMSBhbGlnbmVkIHJlYWRzCmBgYHtyfQpzY3JhbWJsZWRfZGZbMTozLCBdICAKYGBgCgpMZXQncyBhbHNvIG91dHB1dCB0aGUgZnJlcXVlbmN5IG9mIFIxIHJlYWRzIHRoYXQgaGF2ZSAwLCAxLCAyLCBvciAzIHBvdGVudGlhbCBwYWlyZWQtZW5kIG1hdGUgcmVhZHMgKFIyKQpgYGB7cn0KbGluZWFyX2p1bmN0aW9uX21hcGNhdGVnIDwtIAogIGxpbmVhcl9kZiRuTWF0ZXMgJT4lCiAgdGFibGUgICU+JQogIGFzLmRhdGEuZnJhbWUoKQoKc2NyYW1ibGVkX2p1bmN0aW9uX21hcGNhdGVnIDwtIAogIHNjcmFtYmxlZF9kZiRuTWF0ZXMgJT4lICAKICB0YWJsZSAlPiUgIAogIGFzLmRhdGEuZnJhbWUoKSAKCm5hbWVzX2RmIDwtIGMoIlVubWFwcGVkIiwiT25lTWF0ZU1hdGNoIiwgIlR3b01hdGVNYXRjaHMiLCAiVGhyZWVNYXRlTWF0Y2hzIikKcm93bmFtZXMobGluZWFyX2p1bmN0aW9uX21hcGNhdGVnKSA8LSBuYW1lc19kZgpyb3duYW1lcyhzY3JhbWJsZWRfanVuY3Rpb25fbWFwY2F0ZWcpIDwtIG5hbWVzX2RmCmxpbmVhcl9qdW5jdGlvbl9tYXBjYXRlZyAKc2NyYW1ibGVkX2p1bmN0aW9uX21hcGNhdGVnIApgYGAKCiMjIyAgNi4gIENsYXNzaWZ5IEp1bmN0aW9uIFJlYWRzCgpXZSBzcGxpdCB0aGUgcmVhZHMgaW50byByZWFkcyB0aGF0IGhhdmUgYSBtYXBwZWQgbWF0ZSBSMiBhbmQgdGhvc2UgdGhhdCBkbyBub3QuIFdlIGludGlhbGx5IGFzc2lnbiBhbGwgcmVhZHMgYXMgdW5jYXRlZ29yaXplZCAoaS5lLiBjbGFzcz0wKS4KYGBge3J9ICAgICAgICAgICAgICAgICAgICAKbGluZWFyX3JlYWRzX3NwbGl0IDwtIHNwbGl0KGxpbmVhcl9kZiwgbGluZWFyX2RmJG5NYXRlcyA9PSAwKQpzY3JhbWJsZWRfcmVhZHNfc3BsaXQgPC0gc3BsaXQoc2NyYW1ibGVkX2RmLCBzY3JhbWJsZWRfZGYkbk1hdGVzID09IDApCnVubWFwcGVkX3JlYWRzIDwtIHJiaW5kKGxpbmVhcl9yZWFkc19zcGxpdFtbMl1dLCBzY3JhbWJsZWRfcmVhZHNfc3BsaXRbWzJdXSlbLDE6MTVdCgpsaW5lYXJfcmVhZHNfbWFwcGVkIDwtIGNiaW5kKGxpbmVhcl9yZWFkc19zcGxpdFtbMV1dICwgY2xhc3MgPSAwKQpzY3JhbWJsZWRfcmVhZHNfbWFwcGVkIDwtIGNiaW5kKHNjcmFtYmxlZF9yZWFkc19zcGxpdFtbMV1dICwgY2xhc3MgPSAwKQpgYGAKCkxldCdzIHRha2UgYSBsb29rIGF0IHRoZSB1bm1hcHBlZCByZWFkcwpgYGB7cn0KdW5tYXBwZWRfcmVhZHNbMTozLF0KYGBgCgpOb3cgd2UgcHJvY2VlZCB0byBjbGFzc2lmeSBzY3JhbWJsZWQganVuY3Rpb25hbCByZWFkcyBhcyBjaXJjdWxhciBvciBkZWNveSBhbmQgbGluZWFyIGp1bmN0aW9uYWwgcmVhZHMgYXMgbGluZWFyIG9yIGFub21hbG91cy4KYGBge3J9CgpjbGFzc2lmeUNpcmN1bGFyUGFpcmVkTWF0ZXMgPC0gZnVuY3Rpb24oeCkgewogIAogIGNvbmQxIDwtIHgkc3RyLlIxICE9IHhbLCBjKCJzdHIuUjJnIiwgInN0ci5SMmwiLCAic3RyLlIycyIpXVtjYmluZCgxOm5yb3coeCksIHgkbWF0ZSldCiAgY29uZDIgPC0geCRqQ2hyLlIxID09IHhbLCBjKCJqdW5jdGlvbi5SMmciLCAiakNoci5SMmwiLCAiakNoci5SMnMiKV1bY2JpbmQoMTpucm93KHgpLCB4JG1hdGUpXQogIGNvbmQzIDwtIHgkbWF0ZSA9PSAzIAogIGNvbmQ0IDwtIHgkanVuY3Rpb24uUjEgPT0geCRqdW5jdGlvbi5SMnMKICB4JGNsYXNzWyhjb25kMSAmIGNvbmQyICYgY29uZDMgJiBjb25kNCldIDwtIDEgIyhjaXJjKQogIAogIHJldHVybih4KQp9CgpjbGFzc2lmeUNpcmN1bGFyTGluZWFyTWF0ZXMgPC0gZnVuY3Rpb24oeCwgQlVGRkVSID0gMTUsIE1JRFBPSU5UID0gMTUwKSB7CiAgCiAgcjFfc3RhcnQgPC0gcG1pbih4JGpQb3MxLlIxLCB4JGpQb3MyLlIxKSAKICByMV9lbmQgPC0gcG1heCh4JGpQb3MxLlIxLCB4JGpQb3MyLlIxKSAKICByMl9zdGFydCA8LSBwbWluKHgkalBvczEuUjJsLCB4JGpQb3MyLlIybCkgIC0gTUlEUE9JTlQgKyAgeCRwb3MuUjJsCiAgcjJfZW5kIDwtIHBtaW4oeCRqUG9zMS5SMmwsIHgkalBvczIuUjJsKSAgLSBNSURQT0lOVCArICB4JHBvcy5SMmwgKyAgeCRsZW4uUjJsIC0gMQogIAogIGNvbmQxIDwtIHgkc3RyLlIxICE9IHhbLCBjKCJzdHIuUjJnIiwgInN0ci5SMmwiLCAic3RyLlIycyIpXVtjYmluZCgxOm5yb3coeCksIHgkbWF0ZSldCiAgY29uZDIgPC0geCRqQ2hyLlIxID09IHhbLCBjKCJqdW5jdGlvbi5SMmciLCAiakNoci5SMmwiLCAiakNoci5SMnMiKV1bY2JpbmQoMTpucm93KHgpLCB4JG1hdGUpXQogIGNvbmQzIDwtIHIyX3N0YXJ0ID49ICByMV9zdGFydCAtIEJVRkZFUgogIGNvbmQ0IDwtIHIyX3N0YXJ0IDw9ICByMV9lbmQgKyBCVUZGRVIKICBjb25kNSA8LSByMl9lbmQgPj0gcjFfc3RhcnQgLSBCVUZGRVIKICBjb25kNiA8LSByMl9lbmQgPD0gcjFfZW5kICsgQlVGRkVSIAogIGNvbmQ3IDwtIHgkbWF0ZSA9PSAyIAogIGNvbmQ4IDwtICEoeCRjbGFzcyAlaW4lIGMoMSkpCiAgCiAgeCRjbGFzc1soY29uZDEgJiBjb25kMiAmIGNvbmQzICYgY29uZDQgJiBjb25kNSAmIGNvbmQ2ICYgY29uZDcgJiBjb25kOCldIDwtIDIgIyhjaXJjX2xpbikKICByZXR1cm4oeCkKfQoKY2xhc3NpZnlDaXJjdWxhckdlbm9taWNNYXRlcyA8LSBmdW5jdGlvbih4LCBCVUZGRVIgPSAxNSwgTUlEUE9JTlQgPSAxNTApIHsKICAKICByMV9zdGFydCA8LSBwbWluKHgkcG9zLlIxLCB4JGpQb3MyLlIxKSAKICByMV9lbmQgPC0gcG1heCh4JGpQb3MxLlIxLCB4JGpQb3MyLlIxKSAKICByMl9zdGFydCA8LSB4JHBvcy5SMmcKICByMl9lbmQgPC0geCRwb3MuUjJnICsgeCRsZW4uUjJnIC0gMQogIAogIGNvbmQxIDwtIHgkc3RyLlIxICE9IHhbLCBjKCJzdHIuUjJnIiwgInN0ci5SMmwiLCAic3RyLlIycyIpXVtjYmluZCgxOm5yb3coeCksIHgkbWF0ZSldCiAgY29uZDIgPC0geCRqQ2hyLlIxID09IHhbLCBjKCJqdW5jdGlvbi5SMmciLCAiakNoci5SMmwiLCAiakNoci5SMnMiKV1bY2JpbmQoMTpucm93KHgpLCB4JG1hdGUpXQogIGNvbmQzIDwtIHIyX3N0YXJ0ID49ICByMV9zdGFydCAtIEJVRkZFUgogIGNvbmQ0IDwtIHIyX3N0YXJ0IDw9ICByMV9lbmQgKyBCVUZGRVIKICBjb25kNSA8LSByMl9lbmQgPj0gcjFfc3RhcnQgLSBCVUZGRVIKICBjb25kNiA8LSByMl9lbmQgPD0gcjFfZW5kICsgQlVGRkVSCiAgY29uZDcgPC0geCRtYXRlID09IDEgCiAgY29uZDggPC0gISh4JGNsYXNzICVpbiUgYygxLCAyKSkKICAKICB4JGNsYXNzWyhjb25kMSAmIGNvbmQyICYgY29uZDMgJiBjb25kNCAmIGNvbmQ1ICYgY29uZDYgJiBjb25kNyAmIGNvbmQ4ICldIDwtIDMgIyhjaXJjX2dlbikKICByZXR1cm4oeCkKfQoKY2xhc3NpZnlDaXJjdWxhcklnbm9yZSA8LSBmdW5jdGlvbih4LCBCVUZGRVIgPSAxNSwgTUlEUE9JTlQgPSAxNTApIHsKICAKICByMV9zdGFydCA8LSBwbWluKHgkalBvczEuUjEsIHgkalBvczIuUjEpIC0gTUlEUE9JTlQgKyB4JHBvcy5SMSAKICByMV9lbmQgPC0gcjFfc3RhcnQgKyB4JGxlbi5SMSAtIDEKICAKICBjb25kMSA8LSB4JHN0ci5SMSAhPSB4WywgYygic3RyLlIyZyIsICJzdHIuUjJsIiwgInN0ci5SMnMiKV1bY2JpbmQoMTpucm93KHgpLCB4JG1hdGUpXQogIGNvbmQyIDwtIHgkakNoci5SMSA9PSB4WywgYygianVuY3Rpb24uUjJnIiwgImpDaHIuUjJsIiwgImpDaHIuUjJzIildW2NiaW5kKDE6bnJvdyh4KSwgeCRtYXRlKV0KICBjb25kMyA8LSByMV9zdGFydCA+PSBwbWluKHgkalBvczEuUjJzLCB4JGpQb3MyLlIycykgLSBCVUZGRVIKICBjb25kNCA8LSByMV9zdGFydCA8PSBwbWF4KHgkalBvczEuUjJzLCB4JGpQb3MyLlIycykgKyBCVUZGRVIgCiAgY29uZDUgPC0gcjFfZW5kID49IHBtaW4oeCRqUG9zMS5SMnMsIHgkalBvczIuUjJzKSAtIEJVRkZFUgogIGNvbmQ2IDwtIHIxX2VuZCA8PSBwbWF4KHgkalBvczEuUjJzLCB4JGpQb3MyLlIycykgKyBCVUZGRVIKICBjb25kNyA8LSB4JG1hdGUgPT0gMwogIGNvbmQ4IDwtICEoeCRjbGFzcyAlaW4lIGMoMSwgMiwgMykpCgogIHgkY2xhc3NbKGNvbmQxICYgY29uZDIgJiBjb25kMyAmIGNvbmQ0ICYgY29uZDUgJiBjb25kNiAmIGNvbmQ3ICYgY29uZDgpXSA8LSA0CiAgcmV0dXJuKHgpCn0KCmNsYXNzaWZ5Q2lyY3VsYXJEZWNveSA8LSBmdW5jdGlvbih4KSB7CiAgY29uZDEgPC0geCRzdHIuUjEgIT0geFssIGMoInN0ci5SMmciLCAic3RyLlIybCIsICJzdHIuUjJzIildW2NiaW5kKDE6bnJvdyh4KSwgeCRtYXRlKV0KICBjb25kMiA8LSB4JGpDaHIuUjEgPT0geFssIGMoImp1bmN0aW9uLlIyZyIsICJqQ2hyLlIybCIsICJqQ2hyLlIycyIpXVtjYmluZCgxOm5yb3coeCksIHgkbWF0ZSldCiAgY29uZDMgPC0gISh4JGNsYXNzICVpbiUgYygxLCAyLCAzLCA0KSkKICBjb25kNCA8LSB4JG1hdGUgPT0gMwoKICB4JGNsYXNzWyhjb25kMSAmIGNvbmQyICYgY29uZDMpIF0gPC0gNSAKICB4JGNsYXNzWyEoY29uZDEgJiBjb25kMikgXSA8LSA1IAogIAogIHJldHVybih4KQp9CgpjbGFzc2lmeUxpbmVhclBhaXJlZE1hdGVzIDwtIGZ1bmN0aW9uKHgsIEJVRkZFUiA9IDE1LCBNSURQT0lOVCA9IDE1MCkgewogIAogIHIxX3N0YXJ0IDwtIHBtaW4oeCRqUG9zMS5SMSwgeCRqUG9zMi5SMSkgLSBNSURQT0lOVCArIHgkcG9zLlIxICMgbXlDb29yZAogIHIyX3N0YXJ0IDwtIHBtaW4oeCRqUG9zMS5SMmwsIHgkalBvczIuUjJsKSAgLSBNSURQT0lOVCArICB4JHBvcy5SMmwgI21hdGVDb29yZAogIAogIGNvbmQxIDwtIHgkc3RyLlIxICE9IHhbLCBjKCJzdHIuUjJnIiwgInN0ci5SMmwiLCAic3RyLlIycyIpXVtjYmluZCgxOm5yb3coeCksIHgkbWF0ZSldCiAgY29uZDIgPC0geCRqQ2hyLlIxID09IHhbLCBjKCJqdW5jdGlvbi5SMmciLCAiakNoci5SMmwiLCAiakNoci5SMnMiKV1bY2JpbmQoMTpucm93KHgpLCB4JG1hdGUpXQogIGNvbmQzX3AgPC0gcjJfc3RhcnQgPj0gcjFfc3RhcnQgLSBCVUZGRVIKICBjb25kM19tIDwtIHIxX3N0YXJ0ID49IHIyX3N0YXJ0IC0gQlVGRkVSCiAgY29uZDMgPC0gaWZlbHNlKHgkc3RyLlIxID09ICIrIiwgY29uZDNfcCwgY29uZDNfbSkKICBjb25kNCA8LSB4JG1hdGUgPT0gMiAKICB4JGNsYXNzWyhjb25kMSAmIGNvbmQyICYgY29uZDMgJiBjb25kNCldIDwtIDEgCiAgCiAgcmV0dXJuKHgpCn0KCmNsYXNzaWZ5TGluZWFyR2Vub21pY01hdGVzIDwtIGZ1bmN0aW9uKHgsIEJVRkZFUiA9IDE1LCBNSURQT0lOVCA9IDE1MCkgewogIAogIHIxX3N0YXJ0IDwtIHBtaW4oeCRqUG9zMS5SMSwgeCRqUG9zMi5SMSkgLSBNSURQT0lOVCArIHgkcG9zLlIxCiAgcjJfc3RhcnQgPC0geCRwb3MuUjJnCiAgCiAgY29uZDEgPC0geCRzdHIuUjEgIT0geFssIGMoInN0ci5SMmciLCAic3RyLlIybCIsICJzdHIuUjJzIildW2NiaW5kKDE6bnJvdyh4KSwgeCRtYXRlKV0KICBjb25kMiA8LSB4JGpDaHIuUjEgPT0geFssIGMoImp1bmN0aW9uLlIyZyIsICJqQ2hyLlIybCIsICJqQ2hyLlIycyIpXVtjYmluZCgxOm5yb3coeCksIHgkbWF0ZSldCiAgY29uZDNfcCA8LSByMl9zdGFydCA+PSByMV9zdGFydCAtIEJVRkZFUgogIGNvbmQzX20gPC0gcjFfc3RhcnQgPj0gcjJfc3RhcnQgLSBCVUZGRVIKICBjb25kMyA8LSBpZmVsc2UoeCRzdHIuUjEgPT0gIisiLCBjb25kM19wLCBjb25kM19tKQogIGNvbmQ0IDwtIHgkbWF0ZSA9PSAxIAogIGNvbmQ1IDwtICEoeCRjbGFzcyAlaW4lIGMoMSkpCiAgeCRjbGFzc1soY29uZDEgJiBjb25kMiAmIGNvbmQzICYgY29uZDQgJiBjb25kNSldIDwtIDIgCiAgcmV0dXJuKHgpCn0KCmNsYXNzaWZ5TGluZWFySWdub3JlIDwtIGZ1bmN0aW9uKHgpIHsKICAKICBjb25kMSA8LSB4JHN0ci5SMSAhPSB4WywgYygic3RyLlIyZyIsICJzdHIuUjJsIiwgInN0ci5SMnMiKV1bY2JpbmQoMTpucm93KHgpLCB4JG1hdGUpXQogIGNvbmQyIDwtIHgkakNoci5SMSA9PSB4WywgYygianVuY3Rpb24uUjJnIiwgImpDaHIuUjJsIiwgImpDaHIuUjJzIildW2NiaW5kKDE6bnJvdyh4KSwgeCRtYXRlKV0KICBjb25kMyA8LSB4JG1hdGUgPT0gMyAKICB4JGNsYXNzWyhjb25kMSAmIGNvbmQyICYgY29uZDMgKV0gPC0gMyAKICByZXR1cm4oeCkKfQoKY2xhc3NpZnlMaW5lYXJBbm9tYWx5IDwtIGZ1bmN0aW9uKHgpIHsKICAKICBjb25kMSA8LSB4JHN0ci5SMSAhPSB4WywgYygic3RyLlIyZyIsICJzdHIuUjJsIiwgInN0ci5SMnMiKV1bY2JpbmQoMTpucm93KHgpLCB4JG1hdGUpXQogIGNvbmQyIDwtIHgkakNoci5SMSA9PSB4WywgYygianVuY3Rpb24uUjJnIiwgImpDaHIuUjJsIiwgImpDaHIuUjJzIildW2NiaW5kKDE6bnJvdyh4KSwgeCRtYXRlKV0KICB4JGNsYXNzWyEoY29uZDEgJiBjb25kMildIDwtIDQgCiAgeCRjbGFzc1t4JGNsYXNzID09IDBdIDwtIDQKCiAgcmV0dXJuKHgpCn0KCmxpbmVhcl9yZWFkc19tYXBwZWQgPC0gIAogIGxpbmVhcl9yZWFkc19tYXBwZWQgJT4lIAogIGNsYXNzaWZ5TGluZWFyUGFpcmVkTWF0ZXMoKSAlPiUKICBjbGFzc2lmeUxpbmVhckdlbm9taWNNYXRlcygpICU+JQogIGNsYXNzaWZ5TGluZWFySWdub3JlKCkgICU+JSAKICBjbGFzc2lmeUxpbmVhckFub21hbHkoKSAgCgpzY3JhbWJsZWRfcmVhZHNfbWFwcGVkIDwtICAKICBzY3JhbWJsZWRfcmVhZHNfbWFwcGVkICU+JSAKICBjbGFzc2lmeUNpcmN1bGFyUGFpcmVkTWF0ZXMoKSAlPiUKICBjbGFzc2lmeUNpcmN1bGFyTGluZWFyTWF0ZXMoKSAlPiUKICBjbGFzc2lmeUNpcmN1bGFyR2Vub21pY01hdGVzICAlPiUKICBjbGFzc2lmeUNpcmN1bGFySWdub3JlICAlPiUKICBjbGFzc2lmeUNpcmN1bGFyRGVjb3kKCmBgYCAKVGhlIGZyZXF1ZW5jeSBvZiByZWFkIGNhdGVnb3JpZXMgYXJlIGFzIGZvbGxvd3M6CmBgYHtyfQpsaW5lYXJfanVuY3Rpb25fcmVhZHMgPC0gCiAgbGluZWFyX3JlYWRzX21hcHBlZCRjbGFzcyAlPiUKICB0YWJ1bGF0ZSAgJT4lCiAgYXMuZGF0YS5mcmFtZSgpCgpzY3JhbWJsZWRfanVuY3Rpb25fcmVhZHMgPC0gCiAgc2NyYW1ibGVkX3JlYWRzX21hcHBlZCRjbGFzcyAlPiUKICB0YWJ1bGF0ZSAlPiUKICBhcy5kYXRhLmZyYW1lKCkgCgpyb3duYW1lcyhsaW5lYXJfanVuY3Rpb25fcmVhZHMpIDwtIGMoIkxpbmVhci5sIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJMaW5lYXIuZyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiSWdub3JlIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJBbm9tYWx5IikKCnJvd25hbWVzKHNjcmFtYmxlZF9qdW5jdGlvbl9yZWFkcykgPC0gYygiQ2lyY3VsYXIucyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiQ2lyY3VsYXIubCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiQ2lyY3VsYXIuZyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiSWdub3JlIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJEZWNveSIpCmxpbmVhcl9qdW5jdGlvbl9yZWFkcyAKc2NyYW1ibGVkX2p1bmN0aW9uX3JlYWRzCmBgYApgYGB7cn0KY2F0KCAiIG51bWJlciBvZiBsaW5lYXIgcmVhZHM6IiwKICAgICBzdW0obGluZWFyX2p1bmN0aW9uX3JlYWRzWzE6MixdKSwgIlxuIiwKICAgICAibnVtYmVyIG9mIGNpcmN1bGFyIHJlYWRzOiIsCiAgICAgc3VtKHNjcmFtYmxlZF9qdW5jdGlvbl9yZWFkc1sxOjMsXSksICJcbiIsCiAgInRvdGFsIG51bWJlciBvZiBhbm9tYWx5IHJlYWRzOiIsIAogICAgc3VtKGxpbmVhcl9qdW5jdGlvbl9yZWFkc1s0LF0pLCAiXG4iLAogICAgIm51bWJlciBvZiAgZGVjb3kgcmVhZHM6IiwKICAgIHN1bShzY3JhbWJsZWRfanVuY3Rpb25fcmVhZHNbNSxdKSkKYGBgCgpSZW9yZGVyIGNvbHVtbnMgCmBgYHtyfQpyZWZjb2xzIDwtIGMoImlkIiwiY2xhc3MiLCAibWF0ZSIpCmxpbmVhcl9yZWFkc19tYXBwZWQgPC0gbGluZWFyX3JlYWRzX21hcHBlZFssIGMocmVmY29scywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZXRkaWZmKG5hbWVzKGxpbmVhcl9yZWFkc19tYXBwZWQpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVmY29scykpXQpzY3JhbWJsZWRfcmVhZHNfbWFwcGVkIDwtIHNjcmFtYmxlZF9yZWFkc19tYXBwZWRbLCBjKHJlZmNvbHMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2V0ZGlmZihuYW1lcyhzY3JhbWJsZWRfcmVhZHNfbWFwcGVkKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlZmNvbHMpKV0KCnNjcmFtYmxlZF9yZWFkc19tYXBwZWQKYGBgCgpTYXZlIGRhdGEKYGBge3J9CndyaXRlLnRhYmxlKGxpbmVhcl9yZWFkc19tYXBwZWQsCiAgICAgICAgICAgICJsaW5lYXJfcmVhZHNfbWFwcGVkLnR4dCIsIAogICAgICAgICAgICByb3cubmFtZXMgPSBGQUxTRSwKICAgICAgICAgICAgc2VwID0gIlx0IikKCndyaXRlLnRhYmxlKHNjcmFtYmxlZF9yZWFkc19tYXBwZWQsCiAgICAgICAgICAgICJzY3JhbWJsZWRfcmVhZHNfbWFwcGVkLnR4dCIsIAogICAgICAgICAgICByb3cubmFtZXMgPSBGQUxTRSwKICAgICAgICAgICAgc2VwID0gIlx0IikKc2F2ZVJEUyhzY3JhbWJsZWRfcmVhZHNfbWFwcGVkLCAic2NyYW1ibGVkX3JlYWRzX21hcHBlZC5yZHMiKQpzYXZlUkRTKGxpbmVhcl9yZWFkc19tYXBwZWQsICJsaW5lYXJfcmVhZHNfbWFwcGVkLnJkcyIpCgojc2NyYW1ibGVkX3JlYWRzX21hcHBlZCA8LSByZWFkUkRTKCJzY3JhbWJsZWRfcmVhZHNfbWFwcGVkLnJkcyIpCiNsaW5lYXJfcmVhZHNfbWFwcGVkIDwtIHJlYWRSRFMoImxpbmVhcl9yZWFkc19tYXBwZWQucmRzIikKCmBgYAoKYGBge3J9CnNlc3Npb25JbmZvKCkKYGBgCgojIFJlZmVyZW5jZXMK