Differential expression analysis


Project: GSE107401


The aim of this document is to present the data and differential expression analysis between samples from the project GSE107401.

The main steps are:

For a quick access to the figures, go to:
- Normalisation > Visualisation for normalised counts
- Results subsections for the pairwise comparisons

All figures are saved in PNG and PDF format and accessible in the ./project_GSE107401/deseq2_figures/ folder. You can download the code behind this analysis through the “Code” button in the top right corner of the document.

1 Settings

The aim of this section is to:

  • load the tools of interest
  • define custom functions
  • load the input parameters

1.1 Environment

We load the tools to handle data and figures.

library(dplyr)
library(ggplot2)

1.2 Custom functions

We further define custom functions to avoid repetitions in the code.

# -----------------------------------------------------------------------------
# buildCountMatrix
# Create a matrix of reads count
#
# Input:
#   files : a vector of files names
#   sampleLabel : a vector of sample names
#   projectPath: path to the project directory
#
# Output:
#   countMatrix : a reads count matrix
#
# Original author : Vivien DESHAIES
# -----------------------------------------------------------------------------
buildCountMatrix = function(files, sampleLabel, expHeader) {
  
  # read first file and create countMatrix
  countMatrix = read.table(files[1],
                           header = expHeader,
                           stringsAsFactors = F,
                           quote = "")
  colnames(countMatrix) = c("id", "count1")
  
  # read and merge all remaining files with the first
  for (i in 2:length(files)) {
    
    # read files
    exp = read.table(files[i],
                     header = expHeader,
                     stringsAsFactors = F,
                     quote = "")
    
    # lowercase exp columns names
    colnames(exp) = c("id", paste0("count", i))
    
    # merge file data to count matrix by id
    countMatrix = merge(countMatrix, exp, by = "id", suffixes = "_")
  }
  
  # name rows
  rownames(countMatrix) = countMatrix[,1]
  
  # delete first row containing row names
  countMatrix = countMatrix[,-1]
  
  # name columns
  colnames(countMatrix) = sampleLabel
  return(countMatrix)
}

###############################################################################
# -----------------------------------------------------------------------------
# saveRawCountMatrix
#
#   A specific treatment is needed for the raw count matrix because the object
#   DESeq2::counts(dds) does not include the names of the samples as column names
#
#   input: dds -> DESeq object
#          fileName -> character (name of the file to output)
#   output: rawCountMatrix -> file
#
# -----------------------------------------------------------------------------

saveRawCountMatrix = function(dds,fileName) {
  
  countMatrix = DESeq2::counts(dds)
  countMatrix = cbind(countMatrix, row.names(countMatrix))
  
  # Add the samples names as column names + add column Id
  colData = SummarizedExperiment::colData(dds)
  colnames(countMatrix) = c(colData$Name, "Id")
  
  # Put Ids on the first column
  countMatrix = countMatrix[, c("Id", colData$Name)]
  
  # Write the count matrix in a file
  write.table(countMatrix,
              file = paste0(fileName),
              sep = "\t",
              row.names = FALSE,
              quote = FALSE)
}

###############################################################################
# -----------------------------------------------------------------------------
# saveTable
#
#   input: dataframe -> data frame (count matrix)
#          fileName -> character (name of the file to output)
#   output: countMatrix -> file
# -----------------------------------------------------------------------------

saveTable = function(dataframe, fileName) {
  # Add column Id
  column_names = colnames(dataframe)
  dataframe = cbind(row.names(dataframe), dataframe)
  colnames(dataframe) = c("Id", column_names)
  
  # Write the table in a file
  write.table(dataframe,
              file = paste0(fileName),
              sep = "\t",
              row.names = FALSE,
              quote = FALSE)
}

# -----------------------------------------------------------------------------
# plotDendrogram
# Create a ggplot object corresponding to a dendrogram
#
# Input:
#   hc_data : object obtained with the ggdendro::dendro_data function
#   fig_title : figure title
#   col_vector: named vector of colors
#
# Output:
#   p : ggplot object
#
# Original author : Audrey ONFROY
# -----------------------------------------------------------------------------
plotDendrogram = function(hc_data,
                          fig_title = "",
                          col_vector = colors_condition) {
  
  p = ggplot2::ggplot() +
    # Draw dendrogram
    ggplot2::geom_segment(data = ggdendro::segment(hcdata), 
                          aes(x = x, y = y, xend = xend, yend = yend)) +
    # Leaf label
    ggplot2::geom_label(data = ggdendro::label(hcdata), 
                        aes(x = x, y = y, label = label),
                        linewidth = 0, fill = NA,
                        size = 3, angle = 90, hjust = 1,
                        nudge_y = -0.02*max(ggdendro::segment(hcdata)$yend)) +
    # Leaf colors
    ggplot2::geom_point(data = ggdendro::label(hcdata),
                        ggplot2::aes(x = x, y = y, col = Condition),
                        size = 3, pch = 19) +
    ggplot2::scale_color_manual(values = col_vector,
                                breaks = names(col_vector)) +
    # Style
    ggplot2::labs(y = "Distance",
                  title = fig_title) +
    ggplot2::coord_cartesian(clip = "off") +
    ggplot2::theme_minimal() +
    ggplot2::theme(axis.text.x = element_blank(),
                   axis.ticks.x = element_blank(),
                   axis.title.x = element_blank(),
                   axis.line.y = element_line(),
                   axis.ticks.y = element_line(),
                   panel.grid = element_blank(),
                   plot.title = element_text(hjust = 0.5),
                   plot.margin = ggplot2::unit(c(0.5, 0.5, max(nchar(hcdata$labels$label))/6, 0.5), "cm"),
                   # Transparent background
                   panel.background = ggplot2::element_blank(),
                   plot.background = ggplot2::element_blank(),
                   legend.background = ggplot2::element_blank())
  
  return(p)
}

# -----------------------------------------------------------------------------
# plotPCA
# Create a ggplot object corresponding to the PCA plot of individuals
#
# Input:
#   pca_df : dataframe
#   fig_title : figure title
#   col_vector: named vector of colors
#
# Output:
#   p : ggplot object
# -----------------------------------------------------------------------------
plotPCA = function(pca_df,
                   fig_title = "",
                   col_vector = colors_condition) {
  
  p = ggplot2::ggplot(pca_df, aes(x = Dim.1, y = Dim.2, col = Condition, label = sample)) +
    ggplot2::geom_hline(yintercept = 0, lty = 2) +
    ggplot2::geom_vline(xintercept = 0, lty = 2) +
    # Samples
    ggplot2::geom_point(size = 3, pch = 19) +
    ggrepel::geom_label_repel(fill = NA, label.size = 0) +
    ggplot2::scale_color_manual(values = col_vector,
                                breaks = names(col_vector)) +
    # Style
    ggplot2::labs(x = paste0("Dim1 (", round(pcaCount$eig[1, 2], 2), "%)"),
                  y = paste0("Dim2 (", round(pcaCount$eig[2, 2], 2), "%)"),
                  title = fig_title) +
    ggplot2::theme_minimal() +
    ggplot2::theme(plot.title = element_text(hjust = 0.5),
                   # Transparent background
                   panel.background = ggplot2::element_blank(),
                   plot.background = ggplot2::element_blank(),
                   legend.background = ggplot2::element_blank())
  
  return(p)
}

# -----------------------------------------------------------------------------
# plotBarplot
# Create a ggplot object corresponding to a barplot
#
# Input:
#   df : dataframe
#   y_column : column in df for the Y-axis
#   y_title : Y-axis title
#   fig_title : figure title
#   col_vector: named vector of colors
#
# Output:
#   p : ggplot object
# -----------------------------------------------------------------------------
plotBarplot = function(df,
                       y_column,
                       y_title = "",
                       fig_title = "",
                       col_vector = colors_condition) {
  df$y_value = df[, y_column]
  
  p = ggplot2::ggplot(df, aes(x = sample, y = y_value, fill = Condition)) +
    ggplot2::geom_bar(stat = "identity", col = "black") +
    ggplot2::scale_fill_manual(values = col_vector,
                               breaks = names(col_vector)) +
    # Style
    ggplot2::labs(x = "",
                  y = y_title,
                  title = fig_title) +
    ggplot2::scale_y_continuous(expand = c(0,0)) +
    ggplot2::theme_classic() +
    ggplot2::theme(plot.title = element_text(hjust = 0.5),
                   axis.text.x = element_text(angle = 90, hjust = 1),
                   # Transparent background
                   panel.background = ggplot2::element_blank(),
                   plot.background = ggplot2::element_blank(),
                   legend.background = ggplot2::element_blank())
  
  return(p)
}

# -----------------------------------------------------------------------------
# plotBoxplot
# Create a ggplot object corresponding to a boxplot
#
# Input:
#   df : dataframe
#   y_column : column in df for the Y-axis
#   y_title : Y-axis title
#   fig_title : figure title
#   colors_condition: named vector of colors
#
# Output:
#   p : ggplot object
# -----------------------------------------------------------------------------
plotBoxplot = function(df,
                       y_column,
                       y_title = "",
                       fig_title = "",
                       col_vector = colors_condition) {
  df$y_value = df[, y_column]
  
  p = ggplot2::ggplot(df, aes(x = sample, y = y_value, fill = Condition)) +
    ggplot2::geom_boxplot(col = "black") +
    ggplot2::scale_fill_manual(values = col_vector,
                               breaks = names(col_vector)) +
    # Style
    ggplot2::labs(x = "",
                  y = y_title,
                  title = fig_title) +
    ggplot2::scale_y_continuous(expand = c(0,0)) +
    ggplot2::theme_classic() +
    ggplot2::theme(plot.title = element_text(hjust = 0.5),
                   axis.text.x = element_text(angle = 90, hjust = 1),
                   # Transparent background
                   panel.background = ggplot2::element_blank(),
                   plot.background = ggplot2::element_blank(),
                   legend.background = ggplot2::element_blank())
  
  return(p)
}

# -----------------------------------------------------------------------------
# expressionColors
# Define a color panel for the heatmap
#
# Input:
#   range_values : a numerical vector with two values (min and max)
#
# Output:
#   expression_colors : a color or a function built with circlize::colorRamp2
# -----------------------------------------------------------------------------
expressionColors = function(range_values) {
  min_value = range_values[1]
  max_value = range_values[2]
  
  # min_value < 0 < max_value
  if (min_value < 0 & 0 < max_value) {
    expression_colors = circlize::colorRamp2(
      breaks = c(min_value, 0, max_value),
      colors = c("#224897", "#F7F7F7", "#D32A24"))
  } else
    
    # min_value < max_value < 0
    if (min_value < max_value & max_value < 0) {
      expression_colors = circlize::colorRamp2(
        breaks = c(min_value, 0),
        colors = c("#224897", "#F7F7F7"))
    } else
      
      # 0 < min_value < max_value
      if (0 < min_value & min_value < max_value) {
        expression_colors = circlize::colorRamp2(
          breaks = c(0, max_value),
          colors = c("#F7F7F7", "#D32A24"))
      } else {
        
        # min_value == max_value
        expression_colors = "#F7F7F7"
      }
  
  return(expression_colors)
}

# -----------------------------------------------------------------------------
# buildContrast
# Define the contrast vector to extract DE results from the dds object
#
# Input:
#   dds       : DESeqDataSet
#   group1    : list of character vectors, for instance:
#                           list(c("sexe", "F"), c("genotype", "HETERO")
#   group2    : list of character vectors, for instance:
#                           list(c("sexe", "F"), c("genotype", "HOMO")
#   weighted  : boolean     whether to weight the contrast matrix by the
#                           number of replicates per condition or not
#
# Output: a list of 3 elements
#   contrast        : a numerical vector
#   samples_group1  : a character vector containing sample names in group 1
#   samples_group2  : a character vector containing sample names in group 2
#
# Source:
# Modified from: https://www.r-bloggers.com/2024/05/a-guide-to-designs-and-contrasts-in-deseq2/
# -----------------------------------------------------------------------------
buildContrast = function(dds,
                         group1,
                         group2,
                         weighted = FALSE){
  #-- Model matrix for each sample 
  mod_mat = model.matrix(DESeq2::design(dds),
                         SummarizedExperiment::colData(dds))
  
  #-- Samples in group1
  # A list of TRUE / FALSE
  # Each element of the list is of length the number of samples
  # The i-th element of the list corresponds to the i-th condition in group1
  grp1_rows = lapply(c(1:length(group1)), FUN = function(group1_i) {
    group1_i_name = group1[[group1_i]][1]
    group1_i_values = group1[[group1_i]][2:length(group1_i)]
    
    samples_in_group1_i = (SummarizedExperiment::colData(dds)[[group1_i_name]] %in% group1_i_values) # TRUE / FALSE
    
    return(samples_in_group1_i)
  })
  grp1_rows = Reduce(function(x, y) x & y, grp1_rows)
  
  grp2_rows = lapply(c(1:length(group2)), FUN = function(group2_i) {
    group2_i_name = group2[[group2_i]][1]
    group2_i_values = group2[[group2_i]][2:length(group2_i)]
    
    samples_in_group2_i = (SummarizedExperiment::colData(dds)[[group2_i_name]] %in% group2_i_values) # TRUE / FALSE
    
    return(samples_in_group2_i)
  })
  grp2_rows = Reduce(function(x, y) x & y, grp2_rows)
  
  #-- Model matrix with only samples of interest
  mod_mat1 = mod_mat[grp1_rows, , drop = FALSE]
  mod_mat2 = mod_mat[grp2_rows, , drop = FALSE]
  
  #-- Consider the weight or not
  if(!weighted){
    mod_mat1 = mod_mat1[!duplicated(mod_mat1), , drop = FALSE]
    mod_mat2 = mod_mat2[!duplicated(mod_mat2), , drop = FALSE]
  }
  
  #-- Output
  contrast = colMeans(mod_mat1)-colMeans(mod_mat2)
  samples_group1 = colnames(dds)[grp1_rows]
  samples_group2 = colnames(dds)[grp2_rows]
  output = list(contrast = contrast,
                samples_group1 = samples_group1,
                samples_group2 = samples_group2)
  
  return(output)
}

These functions are listed below.

  • buildContrast
  • buildCountMatrix
  • current_version
  • expressionColors
  • plotBarplot
  • plotBoxplot
  • plotDendrogram
  • plotPCA
  • saveRawCountMatrix
  • saveTable
  • version_tested

1.3 Parameters

The table below summarizes all the input parameters.

projectName     = params$projectName
designPath      = params$designPath
comparisonPath  = params$comparisonPath
diffanaTest     = as.logical(toupper(params$diffanaTest))
expHeader       = as.logical(toupper(params$expHeader))
deseqModel      = params$deseqModel
sizeFactorType  = params$sizeFactorType
fitType         = params$fitType
statisticTest   = params$statisticTest
weightContrast  = params$weightContrast
prefix          = params$prefix
plotInteractive = params$plotInteractive
leaveOnError    = params$leaveOnError

# Display them as a table
c("Project name (projectName)" = projectName,
  "Path to the design file (designPath)" = designPath,
  "Path to the comparison file (comparisonPath)" = comparisonPath,
  "Whether to perform the differential expression analysis or not (diffanaTest)" = diffanaTest,
  "Whether the design table has a header or not (expHeader)" = expHeader,
  "DESeq2 model (deseqModel)" = deseqModel,
  "Size factor type (sizeFactorType)" = sizeFactorType,
  "Fit type (fitType)" = fitType,
  "Statistic test (statisticTest)" = statisticTest,
  "Whether to weight the contrast vector by the number of samples or not (weightContrast)" = weightContrast,
  "Prefix to save files (prefix)" = prefix,
  "Whether to make interactive plots or not (plotInteractive)" = plotInteractive,
  "Whether to stop the rendering in case of error (leaveOnError)" = leaveOnError) %>%
  data.frame(Parameter = names(.),
             Value     = .,
             row.names = NULL) %>%
  knitr::kable("html", escape = FALSE) %>%
  kableExtra::kable_styling("striped", full_width = FALSE) %>%
  kableExtra::column_spec(1, bold = TRUE)
Parameter Value
Project name (projectName) GSE107401
Path to the design file (designPath) ./project_GSE107401/deseq2_GSE107401-deseq2Design.txt
Path to the comparison file (comparisonPath) ./project_GSE107401/deseq2_GSE107401-comparisonFile.txt
Whether to perform the differential expression analysis or not (diffanaTest) TRUE
Whether the design table has a header or not (expHeader) TRUE
DESeq2 model (deseqModel) ~Condition
Size factor type (sizeFactorType) ratio
Fit type (fitType) parametric
Statistic test (statisticTest) Wald
Whether to weight the contrast vector by the number of samples or not (weightContrast) TRUE
Prefix to save files (prefix) ./project_GSE107401/deseq2_
Whether to make interactive plots or not (plotInteractive) TRUE
Whether to stop the rendering in case of error (leaveOnError) FALSE

2 Data preparation

The purpose of this section is to:

  • load the design file, containing metadata about each sample ;
  • load the individual sample count data, assemble them as a gene-by-sample count matrix, and save it as a TSV file ; and,
  • build a dataset in a format compatible with the differential expression analysis (DESeq2-related format).

2.1 Load metadata

We load the design file and display the 6 first rows.

design = read.table(designPath,
                    sep = "\t",
                    header = TRUE,
                    dec = ".",
                    stringsAsFactors = FALSE)

# About a warning in DESeq2
design$Condition = stringr::str_replace_all(
  design$Condition,
  pattern = "-",
  replacement = "_")

# Add Reference if does not exist
if (!("Reference" %in% colnames(design))) {
  design$Reference = Inf
  complexMode = TRUE
} else {
  complexMode = FALSE
}

# Reorder samples based on Condition
design = design %>%
  dplyr::arrange(Condition, RepTechGroup, Name) %>%
  dplyr::mutate(Condition = factor(Condition, levels = unique(Condition))) %>%
  dplyr::mutate(RepTechGroup = factor(RepTechGroup, levels = unique(RepTechGroup))) %>%
  dplyr::mutate(Name = factor(Name, levels = unique(Name)))

# Remove columns out of interest
design = design[, !(colnames(design) %in% c("SampleId", "Description", "Date", "FastqFormat"))]

# Display
head(design) %>%
  knitr::kable("html") 
Name expressionFile RepTechGroup Condition Reference
GSM2866306 ./project_GSE107401/GSM2866306_expression_output_expression_20130267.tsv KO1 KO 1
GSM2866307 ./project_GSE107401/GSM2866307_expression_output_expression_20130268.tsv KO2 KO 1
GSM2866308 ./project_GSE107401/GSM2866308_expression_output_expression_20130269.tsv KO3 KO 1
GSM2866303 ./project_GSE107401/GSM2866303_expression_output_expression_20130270.tsv WT1 WT 0
GSM2866304 ./project_GSE107401/GSM2866304_expression_output_expression_20130271.tsv WT2 WT 0
GSM2866305 ./project_GSE107401/GSM2866305_expression_output_expression_20130272.tsv WT3 WT 0

Description of the columns:

  • Name: Unique identifier of each technical replicate.
  • expressionFile: File path to the count expression data. The TSV file contains two columns: one for the gene identifiers and one for the number of reads mapping to their genomic location. For compatibility with Excel, please use the XLSX file instead of the TSV file (read more). Furthermore, the XLSX file contains additional columns, such as a description of each gene.
  • RepTechGroup: Samples with the same value in the RepTechGroup column are technical replicates. This occurs when the sequencing library is split over sequencing lanes. Each lane generates a technical replicate from the same biological library.
  • Condition: Samples with the same value in the Condition column are biological replicates. They are produced by the experimenter.
  • Reference: Numerical values used to define the pairwise comparisons between conditions in the differential expression analysis.

We associate a color to each condition.

colors_condition = unique(design$Condition) %>%
  setNames(nm = .,
           grDevices::hcl.colors(length(.), "Spectral"))

data.frame(Color = unname(colors_condition),
           Condition = names(colors_condition)) %>%
  knitr::kable("html",
               col.names = c("Color (hex code)", "Condition")) %>%
  kableExtra::column_spec(1, color = colors_condition) %>%
  kableExtra::kable_styling(full_width = FALSE)
Color (hex code) Condition
#A71B4B KO
#584B9F WT

2.2 Build count matrix

From the individual count data, we build a gene-by-sample count matrix. The count value for a given gene corresponds to the number (integer value) of sequencing reads that map to its genomic region. We display the top left corner of this matrix, meaning 5 genes and 5 samples. The complete matrix is saved as a TSV file (quick access).

count_mat = buildCountMatrix(files = design$expressionFile,
                             sampleLabel = design$Name,
                             expHeader = expHeader)

# Reorder columns
count_mat = count_mat[, levels(design$Name)]

# Display
count_mat[c(1:5), c(1:5)] %>%
  knitr::kable("html") %>%
  kableExtra::kable_styling(full_width = FALSE)
GSM2866306 GSM2866307 GSM2866308 GSM2866303 GSM2866304
ENSMUSG00000000001 10177 7880 10372 7353 8767
ENSMUSG00000000003 0 0 1 0 0
ENSMUSG00000000028 933 915 1184 204 317
ENSMUSG00000000031 66488 44221 88514 72959 70285
ENSMUSG00000000037 89 65 120 119 85

The count matrix contains 46500 genes, 6 samples and 41.94% of null values.

2.3 Create DESeqDataSet object

We create the DESeqDataSet object that includes the count matrix and the design file.

dds = DESeq2::DESeqDataSetFromMatrix(
  countData = count_mat,
  colData = design,
  design = as.formula(deseqModel))

2.4 Save count matrix

We save the count matrix as a TSV file.

filename = paste0(prefix, projectName, "-normalisation_rawCountMatrix.tsv")

saveRawCountMatrix(dds,
                   file = filename)

Output file name:
./project_GSE107401/deseq2_GSE107401-normalisation_rawCountMatrix.tsv

3 Quality control

The aim of this section is to better apprehend the variability between the technical replicates. For information on the correspondence between the identifiers of technical replicates and sample names, please refer to the design file.

3.1 Raw counts dendrogram

We compute euclidean distances between each pair of samples, considering the raw count values.

dist.mat = dist(t(count_mat))
hc = stats::hclust(dist.mat)
hcdata = ggdendro::dendro_data(hc, type = "rectangle")

# Add grouping for colors
hcdata$labels = dplyr::left_join(x = hcdata$labels,
                                 y = design[, c("Name", "Condition")],
                                 by = c("label" = "Name"))

We visualise these distances through a dendrogram. It informs whether technical replicates or closely related conditions are closer together than with other samples.

plotDendrogram(hc_data,
               fig_title = paste0("Dendrogram on the raw count matrix - ",
                                  projectName))

3.2 Raw counts PCA

The 6 samples are represented in a space of 46500 dimensions, corresponding to genes. We perform a Principal Component Analysis (PCA) to visualise samples on a two-dimension space such that it represents the highest variability between samples.

pcaCount = count_mat %>%
  t() %>%
  FactoMineR::PCA(., graph = FALSE)

We visualise the samples on the two-dimensional projection. The coordinates of each sample depend on those of the other samples. This projection captures 69.93% of the total variability between samples. Therefore, it lacks 30.07% of the initial information. In this representation, two points (samples) are close together if they have similar global transcriptomic profiles. We expect the technical and biological replicates to be closer to each other than to the other samples and conditions.

pcaCount$ind$coord %>%
  as.data.frame() %>%
  dplyr::mutate(sample = rownames(.)) %>%
  dplyr::left_join(x = .,
                   y = unique(design[, c("Name", "Condition")]),
                   by = c("sample" = "Name")) %>%
  plotPCA(.,
          fig_title =  paste0("PCA of the raw count matrix - ", projectName))

3.3 Null counts barplot

This figure displays the proportion of null counts per sample, meaning, the genes that are not being expressed or not captured by the protocol, within the biological samples. We expect samples from the same condition to be similar.

colMeans(count_mat == 0) %>%
  data.frame(sample = factor(names(.), levels = levels(design$Name)),
             prop_null_counts = .) %>%
  dplyr::mutate(prop_null_counts = 100*prop_null_counts) %>%
  dplyr::left_join(x = .,
                   y = design[, c("Name", "Condition")],
                   by = c("sample" = "Name")) %>%
  # Plot
  plotBarplot(.,
              y_column = "prop_null_counts",
              y_title = "Proportion of null counts (%)",
              fig_title = paste0("Proportion of null counts per sample - ",
                                 projectName))

3.4 Raw counts barplot

This figure displays the total number of reads per sample, also known as library size. We can assess if some samples have a low library size compared to others, and whether technical replicates share a similar number of reads or not.

colSums(DESeq2::counts(dds)) %>%
  data.frame(sample = factor(names(.), levels = levels(design$Name)),
             read_counts = .) %>%
  dplyr::left_join(x = .,
                   y = design[, c("Name", "Condition")],
                   by = c("sample" = "Name")) %>%
  # Plot
  plotBarplot(.,
              y_column = "read_counts",
              y_title = "Total read counts",
              fig_title = paste0("Number of reads per sample - ", projectName))

3.5 Raw counts boxplot

This figure displays, as a boxplot, the number of raw counts for each gene, per sample. It informs whether the genes show a similar count distribution between samples and conditions.

log2(DESeq2::counts(dds)+1) %>%
  reshape2::melt() %>%
  `colnames<-`(c("gene_ID", "sample", "log2_counts")) %>%
  dplyr::mutate(sample = factor(sample, levels = levels(design$Name))) %>%
  dplyr::left_join(x = .,
                   y = design[, c("Name", "Condition")],
                   by = c("sample" = "Name")) %>%
  # Plot
  plotBoxplot(.,
              y_column = "log2_counts",
              y_title = "log_2 (counts+1)",
              fig_title = paste0("Raw counts distribution per sample - ", projectName))


In this context, there is no technical replicate.

4 Normalisation

The aim of this section is to normalise the count matrix, before performing the differential expression analysis. The normalisation method considers the sequencing depth and RNA composition of each sample to compute normalised count values.

For more details, please visit:

https://hbctraining.github.io/DGE_workshop/lessons/02_DGE_count_normalization.html

4.1 Data handling

We normalise the count matrix. We display the top left corner of this matrix, meaning 5 genes and 5 samples. Unlike count data, normalised count values are not necessarily integers.

dds = DESeq2::estimateSizeFactors(dds,
                                  type = sizeFactorType)

DESeq2::counts(dds, normalized = TRUE)[c(1:5), c(1:5)] %>%
  knitr::kable("html") %>%
  kableExtra::kable_styling(full_width = FALSE)
KO1 KO2 KO3 WT1 WT2
ENSMUSG00000000001 8545.49706 8663.24632 8.367172e+03 8697.1743 7718.40931
ENSMUSG00000000003 0.00000 0.00000 8.067077e-01 0.0000 0.00000
ENSMUSG00000000028 783.42820 1005.94802 9.551419e+02 241.2925 279.08472
ENSMUSG00000000031 55829.12536 48616.42328 7.140492e+04 86296.3607 61878.45308
ENSMUSG00000000037 74.73216 71.46079 9.680492e+01 140.7539 74.83344

We save the normalised count matrix as a TSV file.

filename = paste0(prefix, projectName,
                  "-normalisation_normalisedCountMatrix.tsv")

saveTable(DESeq2::counts(dds, normalized = TRUE),
          fileName = filename)

Output file name:
./project_GSE107401/deseq2_GSE107401-normalisation_normalisedCountMatrix.tsv

4.2 Visualisation

4.2.1 Normalised counts dendrogram

We compute euclidean distances between each pair of samples, considering the normalised count values.

ddsStabilized = dds %>%
  DESeq2::varianceStabilizingTransformation() %>%
  SummarizedExperiment::assay()
dist.mat = dist(t(ddsStabilized))
hc = stats::hclust(dist.mat)
hcdata = ggdendro::dendro_data(hc, type = "rectangle")

# Add grouping for colors
hcdata$labels = dplyr::left_join(
  x = hcdata$labels,
  y = unique(design[, c("RepTechGroup", "Condition")]),
  by = c("label" = "RepTechGroup"))

We visualise these distances through a dendrogram. It informs whether the samples share a similar transcriptomic profile per condition or not.

plotDendrogram(
  hc_data,
  fig_title = paste0("Dendrogram on the normalised count matrix - ", projectName))

4.2.2 Normalised counts PCA

We perform a PCA for all samples using the normalised count matrix.

pcaCount = dds %>%
  DESeq2::counts(., normalized = TRUE) %>%
  t() %>%
  FactoMineR::PCA(., graph = FALSE)

We visualise the samples on the two-dimensional projection. The coordinates of each sample depend on those of the other samples. This projection captures 65.42% of the total variability between samples. Therefore, it lacks 34.58% of the initial information. In this representation, two points (samples) are close together if they have similar global transcriptomic profiles. We expect samples from the same biological condition to be closer to each other than to samples from other conditions.

pcaCount$ind$coord %>%
  as.data.frame() %>%
  dplyr::mutate(sample = rownames(.)) %>%
  dplyr::left_join(x = .,
                   y = unique(design[, c("RepTechGroup", "Condition")]),
                   by = c("sample" = "RepTechGroup")) %>%
  plotPCA(.,
          fig_title =  paste0("PCA of the normalised count matrix - ", projectName))

4.2.3 Normalised counts boxplot

This figure displays, as a boxplot, the number of normalised counts for each gene, per sample.

log2(DESeq2::counts(dds, normalized = TRUE)+1) %>%
  reshape2::melt() %>%
  `colnames<-`(c("gene_ID", "sample", "log2_counts")) %>%
  dplyr::mutate(sample = factor(sample, levels = levels(design$RepTechGroup))) %>%
  dplyr::left_join(x = .,
                   y = unique(design[, c("RepTechGroup", "Condition")]),
                   by = c("sample" = "RepTechGroup")) %>%
  # Plot
  plotBoxplot(
    .,
    y_column = "log2_counts",
    y_title = "log_2 (counts+1)",
    fig_title = paste0("Normalised counts distribution per sample - ", projectName))

4.2.4 Most expressed genes plot

We identified the most expressed gene in each sample.

# preparation of 2 data frame with the same number of column than
# the dds count matrix
maxCounts = DESeq2::counts(dds)[1,]
transcriptNames = DESeq2::counts(dds)[1,]

# for each sample (column)
for (i in 1:ncol(DESeq2::counts(dds))) {
  
  # selection of the maximum number of count
  maxCounts[i] = (max(DESeq2::counts(dds, normalized = TRUE)[,i])/
                    sum(DESeq2::counts(dds, normalized = TRUE)[,i]))*100
  
  # selection of the name of the genes this the maximum of count
  transcriptNames[i] = row.names(
    subset(DESeq2::counts(dds, normalized = TRUE),
           DESeq2::counts(dds, normalized = TRUE)[,i] == 
             max(DESeq2::counts(dds, normalized = TRUE)[,i])))
}

We represent the information as a barplot.

dplyr::left_join(x = data.frame(sample = names(maxCounts),
                                max_counts = maxCounts),
                 y = data.frame(sample = names(transcriptNames),
                                gene_id = transcriptNames),
                 by = "sample") %>%
  dplyr::mutate(sample = factor(sample, levels = levels(design$RepTechGroup))) %>%
  dplyr::left_join(x = .,
                   y = unique(design[, c("RepTechGroup", "Condition")]),
                   by = c("sample" = "RepTechGroup")) %>%
  # Plot
  ggplot2::ggplot(., aes(x = sample, y = max_counts,
                         fill = Condition, label = gene_id)) +
  ggplot2::geom_bar(stat = "identity", col = "black") +
  ggplot2::geom_label(mapping = aes(x = sample, y = 0),
                      linewidth = 0, fill = NA, hjust = 0) +
  ggplot2::scale_fill_manual(values = colors_condition,
                             breaks = names(colors_condition)) +
  # Style
  ggplot2::labs(y = "Proportion of reads (%)",
                title = paste0("Most expressed genes - ", projectName)) +
  ggplot2::scale_y_continuous(expand = c(0,0)) +
  ggplot2::coord_flip() +
  ggplot2::theme_classic() +
  ggplot2::theme(plot.title = element_text(hjust = 0.5),
                 axis.title.y = element_blank(),
                 axis.text.x = element_text(angle = 90, hjust = 1),
                 # Transparent background
                 panel.background = ggplot2::element_blank(),
                 plot.background = ggplot2::element_blank(),
                 legend.background = ggplot2::element_blank())

5 Differential analysis

The aim of this section is to perform the differential expression analysis.

The code behind this section can be downloaded at:

5.1 Dispersion estimates

After the normalisation, the first step of the differential expression analysis is the estimation of gene-wise dispersion. The dispersion estimates reflect the variability in the expression of a given gene across samples.

dds = DESeq2::estimateDispersions(dds,
                                  fitType = fitType)

5.2 Dispersion plot

We visualise the dispersion estimates of each gene (black dots) as a function of the mean of normalised counts across samples. The red curve corresponds to the expected dispersion value for genes, based on their mean normalised counts. Then, a shrinkage of the gene-wise dispersion estimates consists in computed a final dispersion value for each gene (blue dots). This is important to reduce the false positives in the differential expression analysis.

DESeq2::plotDispEsts(
  dds,
  main = paste0("Dispersion estimation scatter plot\n", projectName))

For more details, please visit:

https://hbctraining.github.io/DGE_workshop/lessons/04_DGE_DESeq2_analysis.html

5.3 Generalized linear model

The last step of the differential expression analysis pipeline is to fit a model to each gene to compute their log2 fold change. A Wald statistical test is then used to evaluate if the values are distinct from 0, meaning if the genes are differentially expressed or not.

dds = DESeq2::DESeq(dds,
                    test = statisticTest,
                    fitType = fitType,
                    betaPrior = FALSE)

6 Results

The aim of this section is to extract the results for each paired comparison and build relevant figures.

List of comparisons

The table below is a summary of the conditions of interest.

Condition Reference
WT 0
KO 1

The values in the Reference column are used to define the pairwise comparisons:

  • Reference < 0: the samples were considered by the normalisation step but not by the differential expression analysis
  • Reference = 0: the condition associated with these samples is considered as a reference in the differential expression analysis
  • Reference > 0: the condition associated with these samples is compared to each condition having a lower reference value

The table below summarised the paired comparisons (condition1 vs condition2) of interest.

condition1 condition2
Comparison 1 KO WT

The code behind each section below can be downloaded at:

6.1 KO vs_WT

Details:

  • number of samples in the KO condition: 3
  • number of samples in the WT condition: 3

6.1.1 Results table

We extract the dataframe containing the differential analysis results between condition KO and condition WT. We display the 6 first rows of this table. The complete table is saved as a TSV file (quick access).

res = DESeq2::results(dds,
                      contrast = comparison_contrast) %>%
  as.data.frame() %>%
  dplyr::mutate(ID = rownames(.)) %>%
  dplyr::relocate(ID)

head(res[, -1]) %>%
  knitr::kable("html") %>%
  kableExtra::kable_styling(full_width = FALSE)
baseMean log2FoldChange lfcSE stat pvalue padj
ENSMUSG00000000001 8.545201e+03 -0.0065238 0.1155344 -0.0564664 0.9549703 0.9707938
ENSMUSG00000000003 1.344513e-01 0.6813419 4.0804729 0.1669762 0.8673888 NA
ENSMUSG00000000028 5.881430e+02 1.8048132 0.1745103 10.3421607 0.0000000 0.0000000
ENSMUSG00000000031 6.747721e+04 -0.3810503 0.1980244 -1.9242593 0.0543221 0.1064735
ENSMUSG00000000037 9.885491e+01 -0.5189149 0.3569505 -1.4537448 0.1460171 0.2411026
ENSMUSG00000000049 1.637230e+02 -2.6679215 0.3835931 -6.9550819 0.0000000 0.0000000

Description of the columns:

  • The rownames correspond to the gene identifiers.
  • baseMean: Average normalised expression of the gene across all samples, after correcting for sequencing depth (size factors). It informs about how much the gene is expressed overall.
  • log2FoldChange: log2 of the ratio of the average expression of samples from the condition KO to samples from the condition WT. Positive values correspond to a higher expression in the condition KO, and conversely. A value of 1 means that the gene is twice more expressed in the condition KO compared to the condition WT.
  • lfcSE: Standard error of the log2 fold change. It reflects the uncertainty in the estimated fold change.
  • stat: Test statistic, corresponding to the ratio between log2FoldChange and lfcSE.
  • pvalue: Raw p-value assessing if the log2 fold change is different from 0 or not, meaning if the gene is differentially expressed or not.
  • padj: Adjusted p-value, corrected for multiple testing using the Benjamini-Hochberg method. It controls the false discovery rate and must be used to decide significance.

6.1.2 p-value distribution

Among 46500 genes, there are 31405 (68%) genes with an available p-value and 15095 (32%) with a non-available (NA) p-value. Below is the summary for the available values and a figure depicting the distribution of the raw p-values.

Value
Min. 0.0000000
1st Qu. 0.0011700
Median 0.1772734
Mean 0.3184053
3rd Qu. 0.6320702
Max. 0.9999217
ggplot2::ggplot(res, aes(x = pvalue)) +
  ggplot2::geom_histogram(col = "black", fill = "#A20F58", bins = 50) +
  # Style
  ggplot2::labs(x = "Adjusted p-value",
                y = "Number of genes",
                title = "Raw p-value distribution",
                subtitle = comparison_subtitle) +
  ggplot2::scale_y_continuous(expand = c(0,0)) +
  ggplot2::theme_classic() +
  ggplot2::theme(plot.title = element_text(hjust = 0.5),
                 plot.subtitle = element_text(hjust = 0.5),
                 # Transparent background
                 panel.background = ggplot2::element_blank(),
                 plot.background = ggplot2::element_blank(),
                 legend.background = ggplot2::element_blank())

6.1.3 Adjusted p-value distribution

Among 46500 genes, there are 24771 (53%) genes with an available adjusted p-value and 21729 (47%) with a non-available (NA) adjusted p-value. Below is the summary for the available values and a figure depicting the distribution of the adjusted p-values.

Value
Min. 0.0000000
1st Qu. 0.0003975
Median 0.0948678
Mean 0.2706592
3rd Qu. 0.5215269
Max. 0.9999217
ggplot2::ggplot(res, aes(x = padj)) +
  ggplot2::geom_histogram(col = "black", fill = "#77B241", bins = 50) +
  # Style
  ggplot2::labs(x = "Adjusted p-value",
                y = "Number of genes",
                title = "Adjusted p-value distribution",
                subtitle = comparison_subtitle) +
  ggplot2::scale_y_continuous(expand = c(0,0)) +
  ggplot2::theme_classic() +
  ggplot2::theme(plot.title = element_text(hjust = 0.5),
                 plot.subtitle = element_text(hjust = 0.5),
                 # Transparent background
                 panel.background = ggplot2::element_blank(),
                 plot.background = ggplot2::element_blank(),
                 legend.background = ggplot2::element_blank())

6.1.4 MA plot

The MA plot represents the log2 fold change of each gene (dot) as a function of their mean normalised expression value across all samples. Dots are colored based on the significance of the statistical test, using a significant threshold of 0.05 for the adjusted p-value (padj).

ggplot2::ggplot(res, aes(x = baseMean, y = log2FoldChange,
                         col = padj < 0.05)) +
  ggplot2::geom_point(size = 1) +
  ggplot2::geom_hline(yintercept = 0, lty = 1, col = "lightgray") +
  ggplot2::scale_color_manual(name = "padj < 0.05",
                              breaks = c(FALSE, TRUE, NA),
                              values = c("black", "#D32A24", "lightgray"),
                              na.value = "lightgray") +
  # Style
  ggplot2::scale_x_log10() +
  ggplot2::labs(x = "Mean of normalised counts",
                y = "Log_2 fold change",
                title = "MA plot",
                subtitle = comparison_subtitle) +
  ggplot2::theme_minimal() +
  ggplot2::theme(plot.title = element_text(hjust = 0.5),
                 plot.subtitle = element_text(hjust = 0.5),
                 # Transparent background
                 panel.background = ggplot2::element_blank(),
                 plot.background = ggplot2::element_blank(),
                 legend.background = ggplot2::element_blank())

For more details, please visit:

https://hbctraining.github.io/DGE_workshop/lessons/05_DGE_DESeq2_analysis2.html

6.1.5 Number of differentially expressed genes

We compute the number of significantly differentially expressed genes for varying significant adjusted p-value thresholds.

signif_limit_vec = c(1e-6, 1e-5, 1e-4, 1e-3, 0.01, 0.05)
nb_DE_genes = lapply(signif_limit_vec, FUN = function(signif_limit) {
  nb_genes = res %>%
    # Differentially expressed genes
    dplyr::filter(abs(log2FoldChange) > 1) %>%
    # Significantly, based on the threshold
    dplyr::filter(padj < signif_limit) %>%
    # Number of genes
    nrow()
  
  return(nb_genes)
}) %>% unlist() %>%
  data.frame(signif_limit = signif_limit_vec,
             nb_genes = .)

The barplot indicates the number of significantly differentially expressed genes according to the significant adjusted p-value threshold. Only genes with a log2 fold change greater than 1 or smaller than -1 are considered.

Considering an adjusted p-value threshold of 1e-6, 2822 genes are significantly differentially expressed. When considering an adjusted p-value threshold of 0.05, 5525 genes are significantly differentially expressed, meaning, 2703 additional genes.

p = ggplot2::ggplot(nb_DE_genes, aes(x = signif_limit, y = nb_genes)) +
  ggplot2::geom_bar(stat = "identity", col = "black", fill = "#A3ADB8") +
  ggplot2::geom_label(aes(x = signif_limit,
                          y = nb_genes + 0.03*nb_DE_genes[6, 2],
                          label = nb_genes),
                      linewidth = 0, size = 5, fill = NA) +
  # Style
  ggplot2::scale_x_log10(breaks = signif_limit_vec,
                         labels = signif_limit_vec) +
  ggplot2::labs(x = "Adjusted p-value threshold",
                y = "Number of differentially expressed genes",
                title = "Impact of the adjusted p-value threshold on the number of differentially expressed genes",
                subtitle = comparison_subtitle) +
  ggplot2::theme_classic() +
  ggplot2::theme(plot.title = element_text(hjust = 0.5),
                 plot.subtitle = element_text(hjust = 0.5),
                 # Transparent background
                 panel.background = ggplot2::element_blank(),
                 plot.background = ggplot2::element_blank(),
                 legend.background = ggplot2::element_blank())

if (nb_DE_genes[6, 2] != 0) {
  p = p +
    ggplot2::scale_y_continuous(expand = c(0,0),
                                limits = c(0, 1.1*nb_DE_genes[6, 2]))
}

p

6.1.6 Volcano plot

The volcano plot represents the adjusted p-value as a function of the log2 fold change between condition KO and condition WT. We label a maximum of 10 genes in both directions. We identify such genes.

padj_threshold = 0.05
log2FC_threshold = 2

genes_up = res %>%
  dplyr::filter(padj < padj_threshold) %>%
  dplyr::filter(log2FoldChange > 0) %>%
  dplyr::top_n(n = 10, wt = log2FoldChange) %>%
  dplyr::pull(ID)

genes_dn = res %>%
  dplyr::filter(padj < padj_threshold) %>%
  dplyr::filter(log2FoldChange < 0) %>%
  dplyr::top_n(n = 10, wt = -log2FoldChange) %>%
  dplyr::pull(ID)

We label 10 significantly over-regulated and 10 significantly down-regulated genes, considering an adjusted p-value of 0.05.

non_zero_min = min(res$padj[res$padj != 0], na.rm = TRUE)

p = ggplot2::ggplot(res %>% dplyr::filter(!is.na(padj)),
                    aes(x = log2FoldChange,
                        y = ifelse(padj == 0, # -log10(0) = Inf
                                   yes = -log10(non_zero_min),
                                   no = -log10(padj)),
                        label = ID,
                        text = paste0(ID, "<br>",
                                      "log_2 (fold change): ", round(log2FoldChange, 2), "<br>",
                                      "adjusted p-value: ", round(padj, 4)))) +
  # Lines
  ggplot2::geom_hline(yintercept = 0, col = "lightgray") +
  ggplot2::geom_hline(yintercept = -log10(padj_threshold), lty = 2, col = "#A3ADB8") +
  ggplot2::geom_vline(xintercept = log2FC_threshold, lty = 2, col = "#A3ADB8") +
  ggplot2::geom_vline(xintercept = -log2FC_threshold, lty = 2, col = "#A3ADB8") +
  # Points
  ggplot2::geom_point(data = res %>%
                        dplyr::filter(padj >= padj_threshold),
                      mapping = aes(col = "Others")) +
  ggplot2::geom_point(data = res %>%
                        dplyr::filter(log2FoldChange > -log2FC_threshold &
                                        log2FoldChange < log2FC_threshold),
                      mapping = aes(col = "Others")) +
  ggplot2::geom_point(data = res %>%
                        dplyr::filter(padj < padj_threshold) %>%
                        dplyr::filter(log2FoldChange >= log2FC_threshold),
                      mapping = aes(col = "Significantly up-regulated")) +
  ggplot2::geom_point(data = res %>%
                        dplyr::filter(padj < padj_threshold) %>%
                        dplyr::filter(log2FoldChange <= -log2FC_threshold),
                      mapping = aes(col = "Significantly down-regulated")) +
  ggplot2::scale_color_manual(name = "genes",
                              values = c("#D32A24", "#224897", "#A3ADB8"),
                              breaks = c("Significantly up-regulated",
                                         "Significantly down-regulated",
                                         "Others")) +
  # Labels
  ggplot2::labs(x = "log_2 (fold change)",
                y = "-log10 (adjusted p-value)",
                title = "Volcano plot",
                subtitle = comparison_subtitle) +
  # Style
  ggplot2::theme_minimal() +
  ggplot2::theme(plot.title = element_text(hjust = 0.5),
                 plot.subtitle = element_text(hjust = 0.5),
                 legend.position = "bottom",
                 legend.direction = "horizontal",
                 legend.text = element_text(margin = margin(l = 0.5)),
                 # Transparent background
                 panel.background = ggplot2::element_blank(),
                 plot.background = ggplot2::element_blank(),
                 legend.background = ggplot2::element_blank())

Static

p + ggrepel::geom_label_repel(data = res %>% dplyr::filter(ID %in% c(genes_up, genes_dn)),
                              fill = NA, label.size = 0,
                              size = 3, max.overlaps = 50)

Interactive

p = p + ggplot2::theme(legend.position = "none")

plotly::ggplotly(p, tooltip = "text") %>%
  plotly::layout(paper_bgcolor = "rgba(0,0,0,0)",
                 plot_bgcolor  = "rgba(0,0,0,0)")

6.1.7 Heatmap

We draw a heatmap for the 20 gene(s) presented above. We compute Z-scores from the normalised count matrix and visualise the top left corner of the resulting table, meaning the 20 gene(s) and their Z-scores in two samples. The Z-scores are only used for visualisation. The Z-score matrix is not saved.

samples_of_interest = c(comparison_samples_group1,
                        comparison_samples_group2)

# if-else in case on 0 differential expression gene
nb_genes = length(c(genes_up, genes_dn))
if (nb_genes < 1) {
  zscore_mat = matrix(data = 0,
                      nrow = 1,
                      ncol = length(samples_of_interest),
                      dimnames = list(c(""),
                                      samples_of_interest))
} else {
  zscore_mat = DESeq2::counts(dds, normalized = TRUE)[c(genes_up, genes_dn), samples_of_interest]
  
  # Special case if there is only one gene: vector instead of matrix
  if (nb_genes == 1) {
    zscore_mat = matrix(zscore_mat,
                        nrow = nb_genes,
                        dimnames = list(row_names = c(genes_up, genes_dn),
                                        col_names = samples_of_interest))
  }
  
  # Z-score
  zscore_mat = zscore_mat %>%
    t() %>% scale() %>% t()
}

# The code below is compatible with 1 gene
# Otherwise, zscore_mat[, c(1:2)] would have sufficed and been much simpler
matrix(zscore_mat[, c(1:2)],
       nrow = max(1, nb_genes),
       dimnames = list(row_names = c(genes_up, genes_dn),
                       col_names = samples_of_interest[c(1:2)])) %>%
  knitr::kable("html") %>%
  kableExtra::kable_styling(full_width = FALSE)
KO1 KO2
ENSMUSG00000005220 0.1308313 1.6997768
ENSMUSG00000005994 1.0693969 1.2403233
ENSMUSG00000015437 -0.4848211 1.9206515
ENSMUSG00000042514 -0.5728235 0.3946229
ENSMUSG00000043168 -0.6032827 0.6301209
ENSMUSG00000043549 1.2225999 -0.0009235
ENSMUSG00000046160 0.4559807 1.6655504
ENSMUSG00000067860 0.0741459 1.6942920
ENSMUSG00000075217 0.0315710 1.9744592
ENSMUSG00000093400 0.0829010 1.7700690
ENSMUSG00000001672 -0.9057268 -0.9057268
ENSMUSG00000026535 -0.8900501 -0.8931796
ENSMUSG00000026824 -0.5341863 -0.5341863
ENSMUSG00000028148 -0.8708964 -0.8627911
ENSMUSG00000032517 -0.7686147 -0.7701786
ENSMUSG00000046317 -0.9120461 -0.9120461
ENSMUSG00000048377 -0.9095362 -0.9095362
ENSMUSG00000050917 -0.9002409 -0.9012911
ENSMUSG00000059213 -0.9022808 -0.9011421
ENSMUSG00000100444 -0.7429518 -0.7429518

On the heatmap, each gene is one row and each sample is one column. Samples are annotated for their condition. The sample dendrogram is calculated based on the distances between samples considering only the genes represented on the heatmap. The condition associated to each sample is not considered by the method (unsupervised method). The genes dendrogram is calculated based on the Z-score of that gene among all samples.

# Heatmap top annotation
sub_design = design %>%
  dplyr::select(RepTechGroup, Condition) %>%
  dplyr::filter(RepTechGroup %in% samples_of_interest) %>%
  unique() %>%
  `rownames<-`(.$RepTechGroup)

if (complexMode) {
  sub_design = sub_design %>%
    # Add the complex condition
    `colnames<-`(c("RepTechGroup", "SubCondition")) %>%
    dplyr::mutate(Condition = ifelse(RepTechGroup %in% comparison_samples_group1,
                                     yes = condition1,
                                     no = condition2)) %>%
    dplyr::select(Condition, SubCondition)
  sub_design = sub_design[samples_of_interest, ] # re-order
  
  # Annotation
  ha_top = ComplexHeatmap::HeatmapAnnotation(
    df = sub_design,
    col = list(Condition = setNames(nm = c(condition1, condition2),
                                    c("#3AA5C1", "#FF6800")),
               SubCondition = colors_condition[unique(sub_design$SubCondition)]),
    na_col = "#F7F7F7")
} else {
  # Re-order
  sub_design = sub_design %>%
    dplyr::select(Condition)
  sub_design = sub_design[samples_of_interest, ] # re-order
  
  # Annotation
  ha_top = ComplexHeatmap::HeatmapAnnotation(
    df = sub_design,
    col = list(Condition = colors_condition[c(condition1, condition2)]),
    na_col = "#F7F7F7")
}

# Heatmap title
if (nb_genes > 1) {
  ht_title = paste0("Heatmap of ", nb_genes,
                    " differentially expressed genes\n",
                    comparison_subtitle)
} else {
  ht_title = paste0("Heatmap of ", nb_genes,
                    " differentially expressed gene\n",
                    comparison_subtitle)
}

# Heatmap main
ht = ComplexHeatmap::Heatmap(
  as.matrix(zscore_mat),
  # Expression colors
  col = expressionColors(range(zscore_mat)),
  heatmap_legend_param = list(title = "Z-score"),
  # Annotation
  top_annotation = ha_top,
  # Sample order
  cluster_columns = TRUE,
  # gene order
  cluster_rows = TRUE,
  # Visual aspect
  show_heatmap_legend = TRUE,
  border = TRUE,
  use_raster = FALSE,
  # Title
  column_title = ht_title)

# Draw
ComplexHeatmap::draw(ht,
                     merge_legend = TRUE,
                     heatmap_legend_side = "left",
                     annotation_legend_side = "left",
                     padding = unit(c(samples_padding, 2, 2, 5), "mm"),
                     background = "transparent")

Note: The clustering of samples may change, depending on the genes that are considered.

6.1.8 Save

We re-order the genes based on the adjusted p-value and edit the column names for enhanced readability. We display the 6 first rows of this table.

res = res %>%
  dplyr::mutate(dispersions.dds. = DESeq2::dispersions(dds)) %>%
  dplyr::arrange(padj) %>%
  `colnames<-`( c("ID",
                  "baseMean",
                  paste0("log2foldchange ", condition1, " vs ", condition2),
                  "standard error",
                  "Wald statistic",
                  "Wald test p-value",
                  "BH adjusted p-values",
                  "dispersions.dds.") )

head(res[, -1]) %>%
  knitr::kable("html") %>%
  kableExtra::kable_styling(full_width = FALSE)
baseMean log2foldchange KO vs WT standard error Wald statistic Wald test p-value BH adjusted p-values dispersions.dds.
ENSMUSG00000000223 69095.112 -8.825746 0.2039640 -43.27110 0 0 0.0284741
ENSMUSG00000021061 11643.299 -7.241691 0.1319283 -54.89112 0 0 0.0095483
ENSMUSG00000059213 10761.636 -9.379653 0.2173631 -43.15200 0 0 0.0200004
ENSMUSG00000056569 2192423.490 -4.989391 0.1308831 -38.12096 0 0 0.0123420
ENSMUSG00000033361 24278.624 -6.175509 0.1627914 -37.93510 0 0 0.0183975
ENSMUSG00000022456 5115.165 -6.712565 0.1814406 -36.99593 0 0 0.0189653

We save the table as a TSV file.

filename = paste0(prefix, projectName, "-diffana_",
                  condition1, "_vs_", condition2,".tsv")

saveTable(dataframe = res,
          fileName = filename)

Output file name:
./project_GSE107401/deseq2_GSE107401-diffana_KO_vs_WT.tsv


7 Versions

▼ show
## [1] "Packages location:"
## [1] "/usr/local/lib/R/library"
## [1] ""
## [1] "Session Information:"
## R version 4.5.1 (2025-06-13)
## Platform: x86_64-pc-linux-gnu
## Running under: Ubuntu 24.04.3 LTS
## 
## Matrix products: default
## BLAS:   /usr/local/lib/R/lib/libRblas.so 
## LAPACK: /usr/local/lib/R/lib/libRlapack.so;  LAPACK version 3.12.1
## 
## locale:
## [1] C
## 
## time zone: Etc/UTC
## tzcode source: system (glibc)
## 
## attached base packages:
## [1] stats     graphics  grDevices utils     datasets  methods   base     
## 
## other attached packages:
## [1] ggplot2_4.0.0 dplyr_1.1.4  
## 
## loaded via a namespace (and not attached):
##   [1] rlang_1.1.6                 magrittr_2.0.3             
##   [3] clue_0.3-66                 GetoptLong_1.0.5           
##   [5] matrixStats_1.5.0           compiler_4.5.1             
##   [7] png_0.1-8                   systemfonts_1.3.1          
##   [9] vctrs_0.6.5                 reshape2_1.4.4             
##  [11] stringr_1.5.2               shape_1.4.6.1              
##  [13] pkgconfig_2.0.3             crayon_1.5.3               
##  [15] fastmap_1.2.0               XVector_0.48.0             
##  [17] rmdformats_1.0.4            labeling_0.4.3             
##  [19] rmarkdown_2.29              UCSC.utils_1.4.0           
##  [21] purrr_1.0.4                 xfun_0.52                  
##  [23] cachem_1.1.0                GenomeInfoDb_1.44.0        
##  [25] jsonlite_2.0.0              flashClust_1.01-2          
##  [27] DelayedArray_0.34.1         BiocParallel_1.42.0        
##  [29] parallel_4.5.1              cluster_2.1.8.1            
##  [31] R6_2.6.1                    bslib_0.9.0                
##  [33] stringi_1.8.7               RColorBrewer_1.1-3         
##  [35] GenomicRanges_1.60.0        jquerylib_0.1.4            
##  [37] estimability_1.5.1          Rcpp_1.0.14                
##  [39] bookdown_0.46               SummarizedExperiment_1.38.0
##  [41] iterators_1.0.14            knitr_1.50                 
##  [43] base64enc_0.1-3             IRanges_2.42.0             
##  [45] Matrix_1.7-3                tidyselect_1.2.1           
##  [47] rstudioapi_0.17.1           abind_1.4-8                
##  [49] yaml_2.3.10                 doParallel_1.0.17          
##  [51] codetools_0.2-20            lattice_0.22-7             
##  [53] tibble_3.2.1                plyr_1.8.9                 
##  [55] Biobase_2.68.0              withr_3.0.2                
##  [57] S7_0.2.0                    evaluate_1.0.3             
##  [59] xml2_1.3.8                  circlize_0.4.16            
##  [61] pillar_1.10.2               MatrixGenerics_1.20.0      
##  [63] DT_0.34.0                   foreach_1.5.2              
##  [65] stats4_4.5.1                plotly_4.11.0              
##  [67] generics_0.1.3              S4Vectors_0.46.0           
##  [69] scales_1.4.0                leaps_3.2                  
##  [71] glue_1.8.0                  emmeans_2.0.0              
##  [73] scatterplot3d_0.3-44        lazyeval_0.2.2             
##  [75] tools_4.5.1                 data.table_1.17.0          
##  [77] locfit_1.5-9.12             mvtnorm_1.3-3              
##  [79] grid_4.5.1                  tidyr_1.3.1                
##  [81] crosstalk_1.2.1             colorspace_2.1-1           
##  [83] GenomeInfoDbData_1.2.14     cli_3.6.5                  
##  [85] kableExtra_1.4.0            textshaping_1.0.0          
##  [87] S4Arrays_1.8.0              viridisLite_0.4.2          
##  [89] svglite_2.2.2               ComplexHeatmap_2.24.0      
##  [91] ggdendro_0.2.0              gtable_0.3.6               
##  [93] DESeq2_1.48.0               sass_0.4.10                
##  [95] digest_0.6.37               BiocGenerics_0.54.0        
##  [97] SparseArray_1.8.0           ggrepel_0.9.6              
##  [99] rjson_0.2.23                FactoMineR_2.12            
## [101] htmlwidgets_1.6.4           farver_2.1.2               
## [103] htmltools_0.5.8.1           lifecycle_1.0.4            
## [105] httr_1.4.7                  multcompView_0.1-10        
## [107] GlobalOptions_0.1.2         MASS_7.3-65

8 Reference

The differential expression analysis uses the DESeq2 R package (vignette).

Love, M.I., Huber, W. & Anders, S., Moderated estimation of fold change and dispersion for RNA-seq data with DESeq2. Genome Biol 15, 550 (2014). DOI: 10.1186/s13059-014-0550-8

LS0tCnBhcmFtczoKICBwcm9qZWN0TmFtZTogIkdTRTEwNzQwMSIKICBkZXNpZ25QYXRoOiAiLi9wcm9qZWN0X0dTRTEwNzQwMS9kZXNlcTJfR1NFMTA3NDAxLWRlc2VxMkRlc2lnbi50eHQiCiAgY29tcGFyaXNvblBhdGg6ICIuL3Byb2plY3RfR1NFMTA3NDAxL2Rlc2VxMl9HU0UxMDc0MDEtY29tcGFyaXNvbkZpbGUudHh0IgogIGRpZmZhbmFUZXN0OiBUUlVFCiAgZXhwSGVhZGVyOiBUUlVFCiAgZGVzZXFNb2RlbDogIn5Db25kaXRpb24iCiAgc2l6ZUZhY3RvclR5cGU6ICJyYXRpbyIKICBmaXRUeXBlOiAicGFyYW1ldHJpYyIKICBzdGF0aXN0aWNUZXN0OiAiV2FsZCIKICB3ZWlnaHRDb250cmFzdDogRkFMU0UKICBwcmVmaXg6ICIuL3Byb2plY3RfR1NFMTA3NDAxL2Rlc2VxMl8iCiAgcGxvdEludGVyYWN0aXZlOiBUUlVFCiAgbG9nb1VybDogImh0dHBzOi8vd3d3Lm91dGlscy5nZW5vbWlxdWUuYmlvbG9naWUuZW5zLmZyL2VvdWxzYW4vaW1hZ2VzL2VvdWxzYW4ucG5nIgogIGF1dGhvck5hbWU6ICJFb3Vsc2FuIgogIGF1dGhvckVtYWlsOiAiZW91bHNhbkBiaW8uZW5zLnBzbC5ldSIKICBsZWF2ZU9uRXJyb3I6IFRSVUUKdGl0bGU6ICI8Y2VudGVyPkRpZmZlcmVudGlhbCBleHByZXNzaW9uIGFuYWx5c2lzPC9jZW50ZXI+IgpzdWJ0aXRsZTogIjxjZW50ZXI+YHIgcGFzdGUwKCdQcm9qZWN0OiAnLCBwYXJhbXMkcHJvamVjdE5hbWUpYDwvY2VudGVyPiIKZGF0ZTogImByIGludmlzaWJsZShTeXMuc2V0bG9jYWxlKCdMQ19USU1FJywgJ0MnKSkgOyBwYXN0ZTAoJ0FuYWx5c2lzIHJ1biBvbjogJywgZm9ybWF0KFN5cy50aW1lKCksICclZCAlQiAlWScpKWAKPGJyPjxzcGFuIGNsYXNzPSdnbHlwaGljb24gZ2x5cGhpY29uLXB1c2hwaW4nPjwvc3Bhbj5gciBwYXN0ZTAoJyBQcm9qZWN0OiAnLCBwYXJhbXMkcHJvamVjdE5hbWUpYAo8YnI+PHNwYW4gY2xhc3M9J2dseXBoaWNvbiBnbHlwaGljb24tbWVudS1yaWdodCc+PC9zcGFuPiBWZXJzaW9uIDIuMQo8YnI+PHNwYW4gY2xhc3M9J2dseXBoaWNvbiBnbHlwaGljb24tbGluayc+PC9zcGFuPiBHZW5lcmF0ZWQgYnkgPGEgaHJlZj0naHR0cHM6Ly93d3cub3V0aWxzLmdlbm9taXF1ZS5iaW9sb2dpZS5lbnMuZnIvZW91bHNhbi8nPkVvdWxzYW48L2E+IgojIGFib3ZlIGlzIGEgc21hbGwgaGFjayB0byBhZGQgbmV3IGxpbmVzIGluIHRoZSBwb3N0YW1wbGUgd2l0aG91dCBlZGl0aW5nIHRoZSB0ZW1wbGF0ZQphdXRob3I6CiAgLSBuYW1lOiAiYHIgcGFyYW1zJGF1dGhvck5hbWVgIgogICAgZW1haWw6ICJgciBwYXJhbXMkYXV0aG9yRW1haWxgIgpvdXRwdXQ6CiAgcm1kZm9ybWF0czo6cmVhZHRoZWRvd246CiAgICBjb2RlX2ZvbGRpbmc6IHNob3cKICAgIGNvZGVfZG93bmxvYWQ6IHRydWUKICAgIG51bWJlcl9zZWN0aW9uczogdHJ1ZQogICAgdG9jX2RlcHRoOiAzCi0tLQoKPCEtLSBDU1MgdG8gY3VzdG9taXplIHRoZSBjb2xvcnMgLS0+CmBgYHtjc3Mgc3R5bGVfc2VjdGlvbjEsIGVjaG8gPSBGQUxTRX0KYm9keSB7CnRleHQtYWxpZ246IGp1c3RpZnkKfQoKLyogV2lkdGggb2YgdGhlIG1haW4gZGl2ICovCiNjb250ZW50IHsKbWF4LXdpZHRoOiA4MCU7Cn0KYGBgCgpgYGB7ciBzdHlsZV9zZWN0aW9uMiwgZWNobyA9IEZBTFNFLCByZXN1bHRzID0gImFzaXMifQojIE5PVEU6IEEgQ1NTIGNodW5rIGNhbm5vdCBpbnRlcnByZXQgdGhlIFIgaW5wdXQgcGFyYW1ldGVyLiBJbiBvcmRlciB0byBwYXJzZSB0aGUgJ3BhcmFtcyRsb2dvVXJsJyBwYXJhbWV0ZXIsIHdlIHJlcXVpcmUgYW4gUiBjaHVuay4gVGhlcmVmb3JlLCB0aGUgQ1NTIGNvbnRlbnQgaXMgZGlzcGxheWVkIHRocm91Z2ggYW4gUiBjaHVuay4KCiMgSGVhZGVyIGluIHRoZSBzaWRlYmFyCmNhdChwYXN0ZTAoIgo8c3R5bGU+CiNzaWRlYmFyIGgyIHsKYmFja2dyb3VuZC1jb2xvcjojQTNBREI4OwpiYWNrZ3JvdW5kLXNpemU6IDMyJTsKYmFja2dyb3VuZC1yZXBlYXQ6IG5vLXJlcGVhdDsKYmFja2dyb3VuZC1wb3NpdGlvbjogY2VudGVyIGJvdHRvbTsKYmFja2dyb3VuZC1pbWFnZTp1cmwoJyIsIHBhcmFtcyRsb2dvVXJsLCAiJyk7CmJhY2tncm91bmQtb3JpZ2luOiBjb250ZW50LWJveDsKaGVpZ2h0OiAxMyU7Cn0KPC9zdHlsZT4KIikpCmBgYAoKYGBge2NzcyBzdHlsZV9zZWN0aW9uMywgZWNobyA9IEZBTFNFfQovKiBTaWRlYmFyIGJvZHkgKi8KI3NpZGViYXIgewpiYWNrZ3JvdW5kOiM1MzVGNkE7Cn0KCi8qIFNtYWxsIHNwYWNlIGFib3V0IHRoZSBhdXRob3Jpbmcgc2VjdGlvbiAqLwojcG9zdGFtYmxlIHsKYm9yZGVyLXRvcDpzb2xpZCAxMHB4ICNBM0FEQjg7Cn0KCi8qIFRpdGxlcyAqLwpoMSwgaDIsIGgzLCBoNCwgaDUsIGg2LCBsZWdlbmQgewpjb2xvcjogIzAwMDAzODsKfQoKLyogVHdvLWNvbHVtbiBtb2RlICovCi5jb2xzIHsKZGlzcGxheTogZmxleDsKYWxpZ24taXRlbXM6IGNlbnRlcjsKfQoKLmNvbC1jZW50ZXIgewpqdXN0aWZ5LWNvbnRlbnQ6IGNlbnRlcjsKfQoKLmNvbC1yaWdodCB7Cm1hcmdpbi1sZWZ0OiBhdXRvOwp9CgovKiBCdXR0b24gKi8KLmJ0bi1jdXN0b20gewpmb250LWZhbWlseTogaW5oZXJpdDsKZm9udC1zaXplOiBpbmhlcml0Owpjb2xvcjogaW5oZXJpdDsKYmFja2dyb3VuZC1jb2xvcjogI0UzRTNFMzsKYm9yZGVyLWNvbG9yOiAjRTNFM0UzOwpwYWRkaW5nOiAxcHggMnB4Owpib3JkZXItcmFkaXVzOiA1cHg7CmJvcmRlci1zdHlsZTogc29saWQ7CmNvbG9yOiAjMDAwMDM4Owp9CgouYnRuLWN1c3RvbTpob3ZlciB7CmJhY2tncm91bmQtY29sb3I6ICM1MzVGNkE7CmJvcmRlci1jb2xvcjogIzUzNUY2QTsKY29sb3I6ICNGQ0ZDRkM7Cn0KYGBgCgo8IS0tIEF1dG9tYXRpY2FsbHkgY29tcHV0ZXMgYW5kIHByaW50cyBpbiB0aGUgb3V0cHV0IHRoZSBydW5uaW5nIHRpbWUgZm9yIGFueSBjb2RlIGNodW5rIC0tPgpgYGB7ciwgZWNobyA9IEZBTFNFfQpob29rcyA9IGtuaXRyOjprbml0X2hvb2tzJGdldCgpCmhvb2tfZm9sZGFibGUgPSBmdW5jdGlvbih0eXBlKSB7CiAgZm9yY2UodHlwZSkKICBmdW5jdGlvbih4LCBvcHRpb25zKSB7CiAgICByZXMgPSBob29rc1tbdHlwZV1dKHgsIG9wdGlvbnMpCiAgICAKICAgIGlmIChpc0ZBTFNFKG9wdGlvbnNbW3Bhc3RlMCgiZm9sZF8iLCB0eXBlKV1dKSkgcmV0dXJuKHJlcykKICAgIAogICAgcGFzdGUwKAogICAgICAiPGRldGFpbHM+PHN1bW1hcnk+IiwgIlxVMjVCQyBzaG93IiwgIjwvc3VtbWFyeT5cblxuIiwKICAgICAgcmVzLAogICAgICAiXG5cbjwvZGV0YWlscz4iCiAgICApCiAgfQp9CmtuaXRyOjprbml0X2hvb2tzJHNldCgKICBvdXRwdXQgPSBob29rX2ZvbGRhYmxlKCJvdXRwdXQiKSwKICBwbG90ID0gaG9va19mb2xkYWJsZSgicGxvdCIpCikKYGBgCgo8IS0tIGtuaXQgc2V0dXAgLS0+CmBgYHtyIGtuaXRfc2V0dXAsIGVjaG8gPSBGQUxTRX0KZmlnX3NhdmVfZm9sZGVyID0gcGFzdGUwKHBhcmFtcyRwcmVmaXgsICJmaWd1cmVzLyIpCgprbml0cjo6b3B0c19jaHVuayRzZXQoCiAgZWNobyA9IFRSVUUsCiAgZXZhbCA9IFRSVUUsCiAgY2FjaGUubGF6eSA9IEZBTFNFLAogIG1lc3NhZ2UgPSBGQUxTRSwKICB3YXJuaW5nID0gRkFMU0UsCiAgZm9sZF9vdXRwdXQgPSBGQUxTRSwKICBmb2xkX3Bsb3QgPSBGQUxTRSwKICBmaWcuYWxpZ24gPSAiY2VudGVyIiwKICBjbGFzcy5zb3VyY2UgPSAiZm9sZC1oaWRlIiwKICAjIFIgYmFzZSBwbG90cyBvbiB0cmFuc3BhcmVudCBiYWNrZ3JvdW5kCiAgZGV2LmFyZ3MgPSBsaXN0KGJnID0gJ3RyYW5zcGFyZW50JyksCiAgIyBFeHBvcnQgZmlndXJlcwogIGRldiA9IGMoJ3BuZycsICdwZGYnKSwKICBmaWcucGF0aCA9IGZpZ19zYXZlX2ZvbGRlciwKICBwZGYub3B0aW9ucyhlbmNvZGluZyA9ICJJU09MYXRpbjkuZW5jIikpCmBgYAoKLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCgpUaGUgYWltIG9mIHRoaXMgZG9jdW1lbnQgaXMgdG8gcHJlc2VudCB0aGUgZGF0YSBhbmQgZGlmZmVyZW50aWFsIGV4cHJlc3Npb24gYW5hbHlzaXMgYmV0d2VlbiBzYW1wbGVzIGZyb20gdGhlIHByb2plY3QgKipgciBwYXJhbXMkcHJvamVjdE5hbWVgKiouCgpUaGUgbWFpbiBzdGVwcyBhcmU6CgotIERhdGEgcHJlcGFyYXRpb24KLSBRdWFsaXR5IGNvbnRyb2wKLSBOb3JtYWxpc2F0aW9uCi0gRGlmZmVyZW50aWFsIGV4cHJlc3Npb24gYW5hbHlzaXMKCmBgYHtyIGVjaG8gPSBGQUxTRSwgcmVzdWx0cyA9ICJhc2lzIn0KIyBOT1RFOiBUaGlzIHNlY3Rpb24gaXMgdmVyeSB1Z2x5LiBEdWUgdG8gdGhlIGNvbmRpdGlvbmFsIHNlY3Rpb24sIHdlIG5lZWQgdG8gZXZhbHVhdGUgaW4gYWR2YW5jZSB3aGV0aGVyIHRoZSBzZWN0aW9uIHdpbGwgYmUgZXhlY3V0ZWQgb3Igbm90LCBpbiBvcmRlciB0byBjcmVhdGUgYSBzaG9ydCBsaW5rIHRvIHRoZSBjb25kaXRpb25hbGx5LWV4ZWN1dGVkIHNlY3Rpb24uIFRoaXMgcmVxdWlyZXMgdGhlIGRlc2lnbiBmaWxlIHRvIGJlIGxvYWRlZC4gVGhpcyBzZWN0aW9uIGlzIGhpZGRlbiBpbiB0aGUgZmluYWwgcmVuZGVyZWQgbm90ZWJvb2suIFdlIG9ubHkgbWFrZSBjYWxjdWxhdGlvbnMgaGVyZSB0byBwcmludCBhIG1lc3NhZ2UuIEJ1dCwgY2FsY3VsYXRpb25zIGFyZSBwZXJmb3JtZWQgYWdhaW4gaW4gYSB2aXNpYmxlIHdheSBpbiB0aGUgbmV4dCBzZWN0aW9ucy4gTm90IGVsZWdhbnQgYW5kIChsb3cpIHJlc291cmNlLWNvbnN1bWluZy4KCnRleHRfdG9fcHJpbnQgPSAiRm9yIGEgcXVpY2sgYWNjZXNzIHRvIHRoZSBmaWd1cmVzLCBnbyB0byIKCmRlc2lnbl90bXAgPSByZWFkLnRhYmxlKHBhcmFtcyRkZXNpZ25QYXRoLAogICAgICAgICAgICAgICAgICAgICAgICBzZXAgPSAiXHQiLAogICAgICAgICAgICAgICAgICAgICAgICBoZWFkZXIgPSBUUlVFLAogICAgICAgICAgICAgICAgICAgICAgICBkZWMgPSAiLiIsCiAgICAgICAgICAgICAgICAgICAgICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRSkKCmlmICghKCJSZWZlcmVuY2UiICVpbiUgY29sbmFtZXMoZGVzaWduX3RtcCkpKSB7CiAgIyBDb21wbGV4IGRlc2lnbiwgbm8gcmVmZXJlbmNlIGNvbHVtbgogIGRlc2lnbl90bXAgPSBjKDAsMSkKfSBlbHNlIHsKICAjIFNpbXBsZSBkZXNpZ24sIGNoZWNrIGlmIHRoZXJlIGFyZSBkaXZlcnNlIFJlZmVyZW5jZSB2YWx1ZXMgYWJvdmUgMAogIGRlc2lnbl90bXAgPSBkZXNpZ25fdG1wIHw+CiAgICBkcGx5cjo6ZmlsdGVyKFJlZmVyZW5jZSA+PSAwKSB8PgogICAgZHBseXI6OnB1bGwoUmVmZXJlbmNlKSB8PgogICAgdW5pcXVlKCkKfQoKaWYgKHBhcmFtcyRkaWZmYW5hVGVzdCAmIGxlbmd0aChkZXNpZ25fdG1wKSA+IDEpIHsKICB0ZXh0X3RvX3ByaW50ID0gcGFzdGUwKAogICAgdGV4dF90b19wcmludCwgIjo8YnI+IiwKICAgICItIFtOb3JtYWxpc2F0aW9uID4gVmlzdWFsaXNhdGlvbl0oI25vcm1hbGlzYXRpb25fdmlzdSkgZm9yIG5vcm1hbGlzZWQgY291bnRzPGJyPiIsCiAgICAiLSBbUmVzdWx0cyBzdWJzZWN0aW9uc10oI3Jlc3VsdHNfc3Vic2VjdGlvbikgZm9yIHRoZSBwYWlyd2lzZSBjb21wYXJpc29ucyIpCn0gZWxzZSB7CiAgdGV4dF90b19wcmludCA9IHBhc3RlMCgKICAgIHRleHRfdG9fcHJpbnQsICIgdGhlIHNlY3Rpb24gW05vcm1hbGlzYXRpb24gPiBWaXN1YWxpc2F0aW9uXSgjbm9ybWFsaXNhdGlvbl92aXN1KS4iKQp9CgpjYXQocGFzdGUwKHRleHRfdG9fcHJpbnQsICJcbiIpKQpgYGAKCkFsbCBmaWd1cmVzIGFyZSBzYXZlZCBpbiBQTkcgYW5kIFBERiBmb3JtYXQgYW5kIGFjY2Vzc2libGUgaW4gdGhlICpgciBmaWdfc2F2ZV9mb2xkZXJgKiBmb2xkZXIuIFlvdSBjYW4gZG93bmxvYWQgdGhlIGNvZGUgYmVoaW5kIHRoaXMgYW5hbHlzaXMgdGhyb3VnaCB0aGUgIkNvZGUiIGJ1dHRvbiBpbiB0aGUgdG9wIHJpZ2h0IGNvcm5lciBvZiB0aGUgZG9jdW1lbnQuCgojIFNldHRpbmdzCgpUaGUgYWltIG9mIHRoaXMgc2VjdGlvbiBpcyB0bzoKCi0gbG9hZCB0aGUgdG9vbHMgb2YgaW50ZXJlc3QKLSBkZWZpbmUgY3VzdG9tIGZ1bmN0aW9ucwotIGxvYWQgdGhlIGlucHV0IHBhcmFtZXRlcnMKCiMjIEVudmlyb25tZW50CgpXZSBsb2FkIHRoZSB0b29scyB0byBoYW5kbGUgZGF0YSBhbmQgZmlndXJlcy4KCmBgYHtyIGxpYnJhcnksIGZvbGRfb3V0cHV0PVRSVUV9CmxpYnJhcnkoZHBseXIpCmxpYnJhcnkoZ2dwbG90MikKYGBgCgpgYGB7ciBjaGVja19kZXNlcTJfdmVyc2lvbiwgZWNobyA9IEZBTFNFfQp2ZXJzaW9uX3Rlc3RlZCA9IGMoMSwgNDgsIDApICMgdmVyc2lvbiAxLjQ4LjAKCmN1cnJlbnRfdmVyc2lvbiA9IHV0aWxzOjpwYWNrYWdlVmVyc2lvbigiREVTZXEyIikgJT4lICMgQS5CLkMKICBhcy5jaGFyYWN0ZXIoKSAlPiUKICBiYXNlOjpzdHJzcGxpdCguLCBzcGxpdCA9ICJbXjAtOV0rIikgJT4lCiAgdW5saXN0KCkgJT4lCiAgYXMubnVtZXJpYygpCgppZiAoY3VycmVudF92ZXJzaW9uW1sxXV0gPCB2ZXJzaW9uX3Rlc3RlZFtbMV1dIHwKICAgIChjdXJyZW50X3ZlcnNpb25bWzFdXSA8PSB2ZXJzaW9uX3Rlc3RlZFtbMV1dICYKICAgICBjdXJyZW50X3ZlcnNpb25bWzJdXSA8IHZlcnNpb25fdGVzdGVkW1syXV0pKSB7CiAgd2FybmluZygiVGhlIG5vdGVib29rIGhhcyBiZWVuIHRlc3RlZCB1c2luZyB2ZXJzaW9uIDEuNDguMCBvZiBERVNlcTIiLAogICAgICAgICAgIiBZb3UgYXJlIHVzaW5nIGFuIG9sZGVyIHZlcnNpb24gKCIsCiAgICAgICAgICB1dGlsczo6cGFja2FnZVZlcnNpb24oIkRFU2VxMiIpLAogICAgICAgICAgIikgc28gaXQgY2Fubm90IGJlIGd1YXJhbnRlZWQgdGhhdCB0aGUgc2NyaXB0IHdpbGwgd29yay4uLiIpCn0KYGBgCgojIyBDdXN0b20gZnVuY3Rpb25zCgpXZSBmdXJ0aGVyIGRlZmluZSBjdXN0b20gZnVuY3Rpb25zIHRvIGF2b2lkIHJlcGV0aXRpb25zIGluIHRoZSBjb2RlLgoKYGBge3IgY3VzdG9tX2Z1bmN0aW9ucywgY2xhc3Muc291cmNlID0gImZvbGQtaGlkZSJ9CiMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KIyBidWlsZENvdW50TWF0cml4CiMgQ3JlYXRlIGEgbWF0cml4IG9mIHJlYWRzIGNvdW50CiMKIyBJbnB1dDoKIyAgIGZpbGVzIDogYSB2ZWN0b3Igb2YgZmlsZXMgbmFtZXMKIyAgIHNhbXBsZUxhYmVsIDogYSB2ZWN0b3Igb2Ygc2FtcGxlIG5hbWVzCiMgICBwcm9qZWN0UGF0aDogcGF0aCB0byB0aGUgcHJvamVjdCBkaXJlY3RvcnkKIwojIE91dHB1dDoKIyAgIGNvdW50TWF0cml4IDogYSByZWFkcyBjb3VudCBtYXRyaXgKIwojIE9yaWdpbmFsIGF1dGhvciA6IFZpdmllbiBERVNIQUlFUwojIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCmJ1aWxkQ291bnRNYXRyaXggPSBmdW5jdGlvbihmaWxlcywgc2FtcGxlTGFiZWwsIGV4cEhlYWRlcikgewogIAogICMgcmVhZCBmaXJzdCBmaWxlIGFuZCBjcmVhdGUgY291bnRNYXRyaXgKICBjb3VudE1hdHJpeCA9IHJlYWQudGFibGUoZmlsZXNbMV0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGhlYWRlciA9IGV4cEhlYWRlciwKICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RyaW5nc0FzRmFjdG9ycyA9IEYsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHF1b3RlID0gIiIpCiAgY29sbmFtZXMoY291bnRNYXRyaXgpID0gYygiaWQiLCAiY291bnQxIikKICAKICAjIHJlYWQgYW5kIG1lcmdlIGFsbCByZW1haW5pbmcgZmlsZXMgd2l0aCB0aGUgZmlyc3QKICBmb3IgKGkgaW4gMjpsZW5ndGgoZmlsZXMpKSB7CiAgICAKICAgICMgcmVhZCBmaWxlcwogICAgZXhwID0gcmVhZC50YWJsZShmaWxlc1tpXSwKICAgICAgICAgICAgICAgICAgICAgaGVhZGVyID0gZXhwSGVhZGVyLAogICAgICAgICAgICAgICAgICAgICBzdHJpbmdzQXNGYWN0b3JzID0gRiwKICAgICAgICAgICAgICAgICAgICAgcXVvdGUgPSAiIikKICAgIAogICAgIyBsb3dlcmNhc2UgZXhwIGNvbHVtbnMgbmFtZXMKICAgIGNvbG5hbWVzKGV4cCkgPSBjKCJpZCIsIHBhc3RlMCgiY291bnQiLCBpKSkKICAgIAogICAgIyBtZXJnZSBmaWxlIGRhdGEgdG8gY291bnQgbWF0cml4IGJ5IGlkCiAgICBjb3VudE1hdHJpeCA9IG1lcmdlKGNvdW50TWF0cml4LCBleHAsIGJ5ID0gImlkIiwgc3VmZml4ZXMgPSAiXyIpCiAgfQogIAogICMgbmFtZSByb3dzCiAgcm93bmFtZXMoY291bnRNYXRyaXgpID0gY291bnRNYXRyaXhbLDFdCiAgCiAgIyBkZWxldGUgZmlyc3Qgcm93IGNvbnRhaW5pbmcgcm93IG5hbWVzCiAgY291bnRNYXRyaXggPSBjb3VudE1hdHJpeFssLTFdCiAgCiAgIyBuYW1lIGNvbHVtbnMKICBjb2xuYW1lcyhjb3VudE1hdHJpeCkgPSBzYW1wbGVMYWJlbAogIHJldHVybihjb3VudE1hdHJpeCkKfQoKIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIwojIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCiMgc2F2ZVJhd0NvdW50TWF0cml4CiMKIyAgIEEgc3BlY2lmaWMgdHJlYXRtZW50IGlzIG5lZWRlZCBmb3IgdGhlIHJhdyBjb3VudCBtYXRyaXggYmVjYXVzZSB0aGUgb2JqZWN0CiMgICBERVNlcTI6OmNvdW50cyhkZHMpIGRvZXMgbm90IGluY2x1ZGUgdGhlIG5hbWVzIG9mIHRoZSBzYW1wbGVzIGFzIGNvbHVtbiBuYW1lcwojCiMgICBpbnB1dDogZGRzIC0+IERFU2VxIG9iamVjdAojICAgICAgICAgIGZpbGVOYW1lIC0+IGNoYXJhY3RlciAobmFtZSBvZiB0aGUgZmlsZSB0byBvdXRwdXQpCiMgICBvdXRwdXQ6IHJhd0NvdW50TWF0cml4IC0+IGZpbGUKIwojIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCgpzYXZlUmF3Q291bnRNYXRyaXggPSBmdW5jdGlvbihkZHMsZmlsZU5hbWUpIHsKICAKICBjb3VudE1hdHJpeCA9IERFU2VxMjo6Y291bnRzKGRkcykKICBjb3VudE1hdHJpeCA9IGNiaW5kKGNvdW50TWF0cml4LCByb3cubmFtZXMoY291bnRNYXRyaXgpKQogIAogICMgQWRkIHRoZSBzYW1wbGVzIG5hbWVzIGFzIGNvbHVtbiBuYW1lcyArIGFkZCBjb2x1bW4gSWQKICBjb2xEYXRhID0gU3VtbWFyaXplZEV4cGVyaW1lbnQ6OmNvbERhdGEoZGRzKQogIGNvbG5hbWVzKGNvdW50TWF0cml4KSA9IGMoY29sRGF0YSROYW1lLCAiSWQiKQogIAogICMgUHV0IElkcyBvbiB0aGUgZmlyc3QgY29sdW1uCiAgY291bnRNYXRyaXggPSBjb3VudE1hdHJpeFssIGMoIklkIiwgY29sRGF0YSROYW1lKV0KICAKICAjIFdyaXRlIHRoZSBjb3VudCBtYXRyaXggaW4gYSBmaWxlCiAgd3JpdGUudGFibGUoY291bnRNYXRyaXgsCiAgICAgICAgICAgICAgZmlsZSA9IHBhc3RlMChmaWxlTmFtZSksCiAgICAgICAgICAgICAgc2VwID0gIlx0IiwKICAgICAgICAgICAgICByb3cubmFtZXMgPSBGQUxTRSwKICAgICAgICAgICAgICBxdW90ZSA9IEZBTFNFKQp9CgojIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjCiMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KIyBzYXZlVGFibGUKIwojICAgaW5wdXQ6IGRhdGFmcmFtZSAtPiBkYXRhIGZyYW1lIChjb3VudCBtYXRyaXgpCiMgICAgICAgICAgZmlsZU5hbWUgLT4gY2hhcmFjdGVyIChuYW1lIG9mIHRoZSBmaWxlIHRvIG91dHB1dCkKIyAgIG91dHB1dDogY291bnRNYXRyaXggLT4gZmlsZQojIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCgpzYXZlVGFibGUgPSBmdW5jdGlvbihkYXRhZnJhbWUsIGZpbGVOYW1lKSB7CiAgIyBBZGQgY29sdW1uIElkCiAgY29sdW1uX25hbWVzID0gY29sbmFtZXMoZGF0YWZyYW1lKQogIGRhdGFmcmFtZSA9IGNiaW5kKHJvdy5uYW1lcyhkYXRhZnJhbWUpLCBkYXRhZnJhbWUpCiAgY29sbmFtZXMoZGF0YWZyYW1lKSA9IGMoIklkIiwgY29sdW1uX25hbWVzKQogIAogICMgV3JpdGUgdGhlIHRhYmxlIGluIGEgZmlsZQogIHdyaXRlLnRhYmxlKGRhdGFmcmFtZSwKICAgICAgICAgICAgICBmaWxlID0gcGFzdGUwKGZpbGVOYW1lKSwKICAgICAgICAgICAgICBzZXAgPSAiXHQiLAogICAgICAgICAgICAgIHJvdy5uYW1lcyA9IEZBTFNFLAogICAgICAgICAgICAgIHF1b3RlID0gRkFMU0UpCn0KCiMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KIyBwbG90RGVuZHJvZ3JhbQojIENyZWF0ZSBhIGdncGxvdCBvYmplY3QgY29ycmVzcG9uZGluZyB0byBhIGRlbmRyb2dyYW0KIwojIElucHV0OgojICAgaGNfZGF0YSA6IG9iamVjdCBvYnRhaW5lZCB3aXRoIHRoZSBnZ2RlbmRybzo6ZGVuZHJvX2RhdGEgZnVuY3Rpb24KIyAgIGZpZ190aXRsZSA6IGZpZ3VyZSB0aXRsZQojICAgY29sX3ZlY3RvcjogbmFtZWQgdmVjdG9yIG9mIGNvbG9ycwojCiMgT3V0cHV0OgojICAgcCA6IGdncGxvdCBvYmplY3QKIwojIE9yaWdpbmFsIGF1dGhvciA6IEF1ZHJleSBPTkZST1kKIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQpwbG90RGVuZHJvZ3JhbSA9IGZ1bmN0aW9uKGhjX2RhdGEsCiAgICAgICAgICAgICAgICAgICAgICAgICAgZmlnX3RpdGxlID0gIiIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgY29sX3ZlY3RvciA9IGNvbG9yc19jb25kaXRpb24pIHsKICAKICBwID0gZ2dwbG90Mjo6Z2dwbG90KCkgKwogICAgIyBEcmF3IGRlbmRyb2dyYW0KICAgIGdncGxvdDI6Omdlb21fc2VnbWVudChkYXRhID0gZ2dkZW5kcm86OnNlZ21lbnQoaGNkYXRhKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgYWVzKHggPSB4LCB5ID0geSwgeGVuZCA9IHhlbmQsIHllbmQgPSB5ZW5kKSkgKwogICAgIyBMZWFmIGxhYmVsCiAgICBnZ3Bsb3QyOjpnZW9tX2xhYmVsKGRhdGEgPSBnZ2RlbmRybzo6bGFiZWwoaGNkYXRhKSwgCiAgICAgICAgICAgICAgICAgICAgICAgIGFlcyh4ID0geCwgeSA9IHksIGxhYmVsID0gbGFiZWwpLAogICAgICAgICAgICAgICAgICAgICAgICBsaW5ld2lkdGggPSAwLCBmaWxsID0gTkEsCiAgICAgICAgICAgICAgICAgICAgICAgIHNpemUgPSAzLCBhbmdsZSA9IDkwLCBoanVzdCA9IDEsCiAgICAgICAgICAgICAgICAgICAgICAgIG51ZGdlX3kgPSAtMC4wMiptYXgoZ2dkZW5kcm86OnNlZ21lbnQoaGNkYXRhKSR5ZW5kKSkgKwogICAgIyBMZWFmIGNvbG9ycwogICAgZ2dwbG90Mjo6Z2VvbV9wb2ludChkYXRhID0gZ2dkZW5kcm86OmxhYmVsKGhjZGF0YSksCiAgICAgICAgICAgICAgICAgICAgICAgIGdncGxvdDI6OmFlcyh4ID0geCwgeSA9IHksIGNvbCA9IENvbmRpdGlvbiksCiAgICAgICAgICAgICAgICAgICAgICAgIHNpemUgPSAzLCBwY2ggPSAxOSkgKwogICAgZ2dwbG90Mjo6c2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGNvbF92ZWN0b3IsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYnJlYWtzID0gbmFtZXMoY29sX3ZlY3RvcikpICsKICAgICMgU3R5bGUKICAgIGdncGxvdDI6OmxhYnMoeSA9ICJEaXN0YW5jZSIsCiAgICAgICAgICAgICAgICAgIHRpdGxlID0gZmlnX3RpdGxlKSArCiAgICBnZ3Bsb3QyOjpjb29yZF9jYXJ0ZXNpYW4oY2xpcCA9ICJvZmYiKSArCiAgICBnZ3Bsb3QyOjp0aGVtZV9taW5pbWFsKCkgKwogICAgZ2dwbG90Mjo6dGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgICAgICBheGlzLnRpY2tzLnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgICAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgICAgICBheGlzLmxpbmUueSA9IGVsZW1lbnRfbGluZSgpLAogICAgICAgICAgICAgICAgICAgYXhpcy50aWNrcy55ID0gZWxlbWVudF9saW5lKCksCiAgICAgICAgICAgICAgICAgICBwYW5lbC5ncmlkID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgICAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSksCiAgICAgICAgICAgICAgICAgICBwbG90Lm1hcmdpbiA9IGdncGxvdDI6OnVuaXQoYygwLjUsIDAuNSwgbWF4KG5jaGFyKGhjZGF0YSRsYWJlbHMkbGFiZWwpKS82LCAwLjUpLCAiY20iKSwKICAgICAgICAgICAgICAgICAgICMgVHJhbnNwYXJlbnQgYmFja2dyb3VuZAogICAgICAgICAgICAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGdncGxvdDI6OmVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICAgICAgICAgIHBsb3QuYmFja2dyb3VuZCA9IGdncGxvdDI6OmVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICAgICAgICAgIGxlZ2VuZC5iYWNrZ3JvdW5kID0gZ2dwbG90Mjo6ZWxlbWVudF9ibGFuaygpKQogIAogIHJldHVybihwKQp9CgojIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCiMgcGxvdFBDQQojIENyZWF0ZSBhIGdncGxvdCBvYmplY3QgY29ycmVzcG9uZGluZyB0byB0aGUgUENBIHBsb3Qgb2YgaW5kaXZpZHVhbHMKIwojIElucHV0OgojICAgcGNhX2RmIDogZGF0YWZyYW1lCiMgICBmaWdfdGl0bGUgOiBmaWd1cmUgdGl0bGUKIyAgIGNvbF92ZWN0b3I6IG5hbWVkIHZlY3RvciBvZiBjb2xvcnMKIwojIE91dHB1dDoKIyAgIHAgOiBnZ3Bsb3Qgb2JqZWN0CiMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KcGxvdFBDQSA9IGZ1bmN0aW9uKHBjYV9kZiwKICAgICAgICAgICAgICAgICAgIGZpZ190aXRsZSA9ICIiLAogICAgICAgICAgICAgICAgICAgY29sX3ZlY3RvciA9IGNvbG9yc19jb25kaXRpb24pIHsKICAKICBwID0gZ2dwbG90Mjo6Z2dwbG90KHBjYV9kZiwgYWVzKHggPSBEaW0uMSwgeSA9IERpbS4yLCBjb2wgPSBDb25kaXRpb24sIGxhYmVsID0gc2FtcGxlKSkgKwogICAgZ2dwbG90Mjo6Z2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgbHR5ID0gMikgKwogICAgZ2dwbG90Mjo6Z2VvbV92bGluZSh4aW50ZXJjZXB0ID0gMCwgbHR5ID0gMikgKwogICAgIyBTYW1wbGVzCiAgICBnZ3Bsb3QyOjpnZW9tX3BvaW50KHNpemUgPSAzLCBwY2ggPSAxOSkgKwogICAgZ2dyZXBlbDo6Z2VvbV9sYWJlbF9yZXBlbChmaWxsID0gTkEsIGxhYmVsLnNpemUgPSAwKSArCiAgICBnZ3Bsb3QyOjpzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gY29sX3ZlY3RvciwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBicmVha3MgPSBuYW1lcyhjb2xfdmVjdG9yKSkgKwogICAgIyBTdHlsZQogICAgZ2dwbG90Mjo6bGFicyh4ID0gcGFzdGUwKCJEaW0xICgiLCByb3VuZChwY2FDb3VudCRlaWdbMSwgMl0sIDIpLCAiJSkiKSwKICAgICAgICAgICAgICAgICAgeSA9IHBhc3RlMCgiRGltMiAoIiwgcm91bmQocGNhQ291bnQkZWlnWzIsIDJdLCAyKSwgIiUpIiksCiAgICAgICAgICAgICAgICAgIHRpdGxlID0gZmlnX3RpdGxlKSArCiAgICBnZ3Bsb3QyOjp0aGVtZV9taW5pbWFsKCkgKwogICAgZ2dwbG90Mjo6dGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSksCiAgICAgICAgICAgICAgICAgICAjIFRyYW5zcGFyZW50IGJhY2tncm91bmQKICAgICAgICAgICAgICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBnZ3Bsb3QyOjplbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgICAgICBwbG90LmJhY2tncm91bmQgPSBnZ3Bsb3QyOjplbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgICAgICBsZWdlbmQuYmFja2dyb3VuZCA9IGdncGxvdDI6OmVsZW1lbnRfYmxhbmsoKSkKICAKICByZXR1cm4ocCkKfQoKIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQojIHBsb3RCYXJwbG90CiMgQ3JlYXRlIGEgZ2dwbG90IG9iamVjdCBjb3JyZXNwb25kaW5nIHRvIGEgYmFycGxvdAojCiMgSW5wdXQ6CiMgICBkZiA6IGRhdGFmcmFtZQojICAgeV9jb2x1bW4gOiBjb2x1bW4gaW4gZGYgZm9yIHRoZSBZLWF4aXMKIyAgIHlfdGl0bGUgOiBZLWF4aXMgdGl0bGUKIyAgIGZpZ190aXRsZSA6IGZpZ3VyZSB0aXRsZQojICAgY29sX3ZlY3RvcjogbmFtZWQgdmVjdG9yIG9mIGNvbG9ycwojCiMgT3V0cHV0OgojICAgcCA6IGdncGxvdCBvYmplY3QKIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQpwbG90QmFycGxvdCA9IGZ1bmN0aW9uKGRmLAogICAgICAgICAgICAgICAgICAgICAgIHlfY29sdW1uLAogICAgICAgICAgICAgICAgICAgICAgIHlfdGl0bGUgPSAiIiwKICAgICAgICAgICAgICAgICAgICAgICBmaWdfdGl0bGUgPSAiIiwKICAgICAgICAgICAgICAgICAgICAgICBjb2xfdmVjdG9yID0gY29sb3JzX2NvbmRpdGlvbikgewogIGRmJHlfdmFsdWUgPSBkZlssIHlfY29sdW1uXQogIAogIHAgPSBnZ3Bsb3QyOjpnZ3Bsb3QoZGYsIGFlcyh4ID0gc2FtcGxlLCB5ID0geV92YWx1ZSwgZmlsbCA9IENvbmRpdGlvbikpICsKICAgIGdncGxvdDI6Omdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLCBjb2wgPSAiYmxhY2siKSArCiAgICBnZ3Bsb3QyOjpzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjb2xfdmVjdG9yLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYnJlYWtzID0gbmFtZXMoY29sX3ZlY3RvcikpICsKICAgICMgU3R5bGUKICAgIGdncGxvdDI6OmxhYnMoeCA9ICIiLAogICAgICAgICAgICAgICAgICB5ID0geV90aXRsZSwKICAgICAgICAgICAgICAgICAgdGl0bGUgPSBmaWdfdGl0bGUpICsKICAgIGdncGxvdDI6OnNjYWxlX3lfY29udGludW91cyhleHBhbmQgPSBjKDAsMCkpICsKICAgIGdncGxvdDI6OnRoZW1lX2NsYXNzaWMoKSArCiAgICBnZ3Bsb3QyOjp0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41KSwKICAgICAgICAgICAgICAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIGhqdXN0ID0gMSksCiAgICAgICAgICAgICAgICAgICAjIFRyYW5zcGFyZW50IGJhY2tncm91bmQKICAgICAgICAgICAgICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBnZ3Bsb3QyOjplbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgICAgICBwbG90LmJhY2tncm91bmQgPSBnZ3Bsb3QyOjplbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgICAgICBsZWdlbmQuYmFja2dyb3VuZCA9IGdncGxvdDI6OmVsZW1lbnRfYmxhbmsoKSkKICAKICByZXR1cm4ocCkKfQoKIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQojIHBsb3RCb3hwbG90CiMgQ3JlYXRlIGEgZ2dwbG90IG9iamVjdCBjb3JyZXNwb25kaW5nIHRvIGEgYm94cGxvdAojCiMgSW5wdXQ6CiMgICBkZiA6IGRhdGFmcmFtZQojICAgeV9jb2x1bW4gOiBjb2x1bW4gaW4gZGYgZm9yIHRoZSBZLWF4aXMKIyAgIHlfdGl0bGUgOiBZLWF4aXMgdGl0bGUKIyAgIGZpZ190aXRsZSA6IGZpZ3VyZSB0aXRsZQojICAgY29sb3JzX2NvbmRpdGlvbjogbmFtZWQgdmVjdG9yIG9mIGNvbG9ycwojCiMgT3V0cHV0OgojICAgcCA6IGdncGxvdCBvYmplY3QKIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQpwbG90Qm94cGxvdCA9IGZ1bmN0aW9uKGRmLAogICAgICAgICAgICAgICAgICAgICAgIHlfY29sdW1uLAogICAgICAgICAgICAgICAgICAgICAgIHlfdGl0bGUgPSAiIiwKICAgICAgICAgICAgICAgICAgICAgICBmaWdfdGl0bGUgPSAiIiwKICAgICAgICAgICAgICAgICAgICAgICBjb2xfdmVjdG9yID0gY29sb3JzX2NvbmRpdGlvbikgewogIGRmJHlfdmFsdWUgPSBkZlssIHlfY29sdW1uXQogIAogIHAgPSBnZ3Bsb3QyOjpnZ3Bsb3QoZGYsIGFlcyh4ID0gc2FtcGxlLCB5ID0geV92YWx1ZSwgZmlsbCA9IENvbmRpdGlvbikpICsKICAgIGdncGxvdDI6Omdlb21fYm94cGxvdChjb2wgPSAiYmxhY2siKSArCiAgICBnZ3Bsb3QyOjpzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjb2xfdmVjdG9yLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYnJlYWtzID0gbmFtZXMoY29sX3ZlY3RvcikpICsKICAgICMgU3R5bGUKICAgIGdncGxvdDI6OmxhYnMoeCA9ICIiLAogICAgICAgICAgICAgICAgICB5ID0geV90aXRsZSwKICAgICAgICAgICAgICAgICAgdGl0bGUgPSBmaWdfdGl0bGUpICsKICAgIGdncGxvdDI6OnNjYWxlX3lfY29udGludW91cyhleHBhbmQgPSBjKDAsMCkpICsKICAgIGdncGxvdDI6OnRoZW1lX2NsYXNzaWMoKSArCiAgICBnZ3Bsb3QyOjp0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41KSwKICAgICAgICAgICAgICAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIGhqdXN0ID0gMSksCiAgICAgICAgICAgICAgICAgICAjIFRyYW5zcGFyZW50IGJhY2tncm91bmQKICAgICAgICAgICAgICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBnZ3Bsb3QyOjplbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgICAgICBwbG90LmJhY2tncm91bmQgPSBnZ3Bsb3QyOjplbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgICAgICBsZWdlbmQuYmFja2dyb3VuZCA9IGdncGxvdDI6OmVsZW1lbnRfYmxhbmsoKSkKICAKICByZXR1cm4ocCkKfQoKIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQojIGV4cHJlc3Npb25Db2xvcnMKIyBEZWZpbmUgYSBjb2xvciBwYW5lbCBmb3IgdGhlIGhlYXRtYXAKIwojIElucHV0OgojICAgcmFuZ2VfdmFsdWVzIDogYSBudW1lcmljYWwgdmVjdG9yIHdpdGggdHdvIHZhbHVlcyAobWluIGFuZCBtYXgpCiMKIyBPdXRwdXQ6CiMgICBleHByZXNzaW9uX2NvbG9ycyA6IGEgY29sb3Igb3IgYSBmdW5jdGlvbiBidWlsdCB3aXRoIGNpcmNsaXplOjpjb2xvclJhbXAyCiMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KZXhwcmVzc2lvbkNvbG9ycyA9IGZ1bmN0aW9uKHJhbmdlX3ZhbHVlcykgewogIG1pbl92YWx1ZSA9IHJhbmdlX3ZhbHVlc1sxXQogIG1heF92YWx1ZSA9IHJhbmdlX3ZhbHVlc1syXQogIAogICMgbWluX3ZhbHVlIDwgMCA8IG1heF92YWx1ZQogIGlmIChtaW5fdmFsdWUgPCAwICYgMCA8IG1heF92YWx1ZSkgewogICAgZXhwcmVzc2lvbl9jb2xvcnMgPSBjaXJjbGl6ZTo6Y29sb3JSYW1wMigKICAgICAgYnJlYWtzID0gYyhtaW5fdmFsdWUsIDAsIG1heF92YWx1ZSksCiAgICAgIGNvbG9ycyA9IGMoIiMyMjQ4OTciLCAiI0Y3RjdGNyIsICIjRDMyQTI0IikpCiAgfSBlbHNlCiAgICAKICAgICMgbWluX3ZhbHVlIDwgbWF4X3ZhbHVlIDwgMAogICAgaWYgKG1pbl92YWx1ZSA8IG1heF92YWx1ZSAmIG1heF92YWx1ZSA8IDApIHsKICAgICAgZXhwcmVzc2lvbl9jb2xvcnMgPSBjaXJjbGl6ZTo6Y29sb3JSYW1wMigKICAgICAgICBicmVha3MgPSBjKG1pbl92YWx1ZSwgMCksCiAgICAgICAgY29sb3JzID0gYygiIzIyNDg5NyIsICIjRjdGN0Y3IikpCiAgICB9IGVsc2UKICAgICAgCiAgICAgICMgMCA8IG1pbl92YWx1ZSA8IG1heF92YWx1ZQogICAgICBpZiAoMCA8IG1pbl92YWx1ZSAmIG1pbl92YWx1ZSA8IG1heF92YWx1ZSkgewogICAgICAgIGV4cHJlc3Npb25fY29sb3JzID0gY2lyY2xpemU6OmNvbG9yUmFtcDIoCiAgICAgICAgICBicmVha3MgPSBjKDAsIG1heF92YWx1ZSksCiAgICAgICAgICBjb2xvcnMgPSBjKCIjRjdGN0Y3IiwgIiNEMzJBMjQiKSkKICAgICAgfSBlbHNlIHsKICAgICAgICAKICAgICAgICAjIG1pbl92YWx1ZSA9PSBtYXhfdmFsdWUKICAgICAgICBleHByZXNzaW9uX2NvbG9ycyA9ICIjRjdGN0Y3IgogICAgICB9CiAgCiAgcmV0dXJuKGV4cHJlc3Npb25fY29sb3JzKQp9CgojIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCiMgYnVpbGRDb250cmFzdAojIERlZmluZSB0aGUgY29udHJhc3QgdmVjdG9yIHRvIGV4dHJhY3QgREUgcmVzdWx0cyBmcm9tIHRoZSBkZHMgb2JqZWN0CiMKIyBJbnB1dDoKIyAgIGRkcyAgICAgICA6IERFU2VxRGF0YVNldAojICAgZ3JvdXAxICAgIDogbGlzdCBvZiBjaGFyYWN0ZXIgdmVjdG9ycywgZm9yIGluc3RhbmNlOgojICAgICAgICAgICAgICAgICAgICAgICAgICAgbGlzdChjKCJzZXhlIiwgIkYiKSwgYygiZ2Vub3R5cGUiLCAiSEVURVJPIikKIyAgIGdyb3VwMiAgICA6IGxpc3Qgb2YgY2hhcmFjdGVyIHZlY3RvcnMsIGZvciBpbnN0YW5jZToKIyAgICAgICAgICAgICAgICAgICAgICAgICAgIGxpc3QoYygic2V4ZSIsICJGIiksIGMoImdlbm90eXBlIiwgIkhPTU8iKQojICAgd2VpZ2h0ZWQgIDogYm9vbGVhbiAgICAgd2hldGhlciB0byB3ZWlnaHQgdGhlIGNvbnRyYXN0IG1hdHJpeCBieSB0aGUKIyAgICAgICAgICAgICAgICAgICAgICAgICAgIG51bWJlciBvZiByZXBsaWNhdGVzIHBlciBjb25kaXRpb24gb3Igbm90CiMKIyBPdXRwdXQ6IGEgbGlzdCBvZiAzIGVsZW1lbnRzCiMgICBjb250cmFzdCAgICAgICAgOiBhIG51bWVyaWNhbCB2ZWN0b3IKIyAgIHNhbXBsZXNfZ3JvdXAxICA6IGEgY2hhcmFjdGVyIHZlY3RvciBjb250YWluaW5nIHNhbXBsZSBuYW1lcyBpbiBncm91cCAxCiMgICBzYW1wbGVzX2dyb3VwMiAgOiBhIGNoYXJhY3RlciB2ZWN0b3IgY29udGFpbmluZyBzYW1wbGUgbmFtZXMgaW4gZ3JvdXAgMgojCiMgU291cmNlOgojIE1vZGlmaWVkIGZyb206IGh0dHBzOi8vd3d3LnItYmxvZ2dlcnMuY29tLzIwMjQvMDUvYS1ndWlkZS10by1kZXNpZ25zLWFuZC1jb250cmFzdHMtaW4tZGVzZXEyLwojIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCmJ1aWxkQ29udHJhc3QgPSBmdW5jdGlvbihkZHMsCiAgICAgICAgICAgICAgICAgICAgICAgICBncm91cDEsCiAgICAgICAgICAgICAgICAgICAgICAgICBncm91cDIsCiAgICAgICAgICAgICAgICAgICAgICAgICB3ZWlnaHRlZCA9IEZBTFNFKXsKICAjLS0gTW9kZWwgbWF0cml4IGZvciBlYWNoIHNhbXBsZSAKICBtb2RfbWF0ID0gbW9kZWwubWF0cml4KERFU2VxMjo6ZGVzaWduKGRkcyksCiAgICAgICAgICAgICAgICAgICAgICAgICBTdW1tYXJpemVkRXhwZXJpbWVudDo6Y29sRGF0YShkZHMpKQogIAogICMtLSBTYW1wbGVzIGluIGdyb3VwMQogICMgQSBsaXN0IG9mIFRSVUUgLyBGQUxTRQogICMgRWFjaCBlbGVtZW50IG9mIHRoZSBsaXN0IGlzIG9mIGxlbmd0aCB0aGUgbnVtYmVyIG9mIHNhbXBsZXMKICAjIFRoZSBpLXRoIGVsZW1lbnQgb2YgdGhlIGxpc3QgY29ycmVzcG9uZHMgdG8gdGhlIGktdGggY29uZGl0aW9uIGluIGdyb3VwMQogIGdycDFfcm93cyA9IGxhcHBseShjKDE6bGVuZ3RoKGdyb3VwMSkpLCBGVU4gPSBmdW5jdGlvbihncm91cDFfaSkgewogICAgZ3JvdXAxX2lfbmFtZSA9IGdyb3VwMVtbZ3JvdXAxX2ldXVsxXQogICAgZ3JvdXAxX2lfdmFsdWVzID0gZ3JvdXAxW1tncm91cDFfaV1dWzI6bGVuZ3RoKGdyb3VwMV9pKV0KICAgIAogICAgc2FtcGxlc19pbl9ncm91cDFfaSA9IChTdW1tYXJpemVkRXhwZXJpbWVudDo6Y29sRGF0YShkZHMpW1tncm91cDFfaV9uYW1lXV0gJWluJSBncm91cDFfaV92YWx1ZXMpICMgVFJVRSAvIEZBTFNFCiAgICAKICAgIHJldHVybihzYW1wbGVzX2luX2dyb3VwMV9pKQogIH0pCiAgZ3JwMV9yb3dzID0gUmVkdWNlKGZ1bmN0aW9uKHgsIHkpIHggJiB5LCBncnAxX3Jvd3MpCiAgCiAgZ3JwMl9yb3dzID0gbGFwcGx5KGMoMTpsZW5ndGgoZ3JvdXAyKSksIEZVTiA9IGZ1bmN0aW9uKGdyb3VwMl9pKSB7CiAgICBncm91cDJfaV9uYW1lID0gZ3JvdXAyW1tncm91cDJfaV1dWzFdCiAgICBncm91cDJfaV92YWx1ZXMgPSBncm91cDJbW2dyb3VwMl9pXV1bMjpsZW5ndGgoZ3JvdXAyX2kpXQogICAgCiAgICBzYW1wbGVzX2luX2dyb3VwMl9pID0gKFN1bW1hcml6ZWRFeHBlcmltZW50Ojpjb2xEYXRhKGRkcylbW2dyb3VwMl9pX25hbWVdXSAlaW4lIGdyb3VwMl9pX3ZhbHVlcykgIyBUUlVFIC8gRkFMU0UKICAgIAogICAgcmV0dXJuKHNhbXBsZXNfaW5fZ3JvdXAyX2kpCiAgfSkKICBncnAyX3Jvd3MgPSBSZWR1Y2UoZnVuY3Rpb24oeCwgeSkgeCAmIHksIGdycDJfcm93cykKICAKICAjLS0gTW9kZWwgbWF0cml4IHdpdGggb25seSBzYW1wbGVzIG9mIGludGVyZXN0CiAgbW9kX21hdDEgPSBtb2RfbWF0W2dycDFfcm93cywgLCBkcm9wID0gRkFMU0VdCiAgbW9kX21hdDIgPSBtb2RfbWF0W2dycDJfcm93cywgLCBkcm9wID0gRkFMU0VdCiAgCiAgIy0tIENvbnNpZGVyIHRoZSB3ZWlnaHQgb3Igbm90CiAgaWYoIXdlaWdodGVkKXsKICAgIG1vZF9tYXQxID0gbW9kX21hdDFbIWR1cGxpY2F0ZWQobW9kX21hdDEpLCAsIGRyb3AgPSBGQUxTRV0KICAgIG1vZF9tYXQyID0gbW9kX21hdDJbIWR1cGxpY2F0ZWQobW9kX21hdDIpLCAsIGRyb3AgPSBGQUxTRV0KICB9CiAgCiAgIy0tIE91dHB1dAogIGNvbnRyYXN0ID0gY29sTWVhbnMobW9kX21hdDEpLWNvbE1lYW5zKG1vZF9tYXQyKQogIHNhbXBsZXNfZ3JvdXAxID0gY29sbmFtZXMoZGRzKVtncnAxX3Jvd3NdCiAgc2FtcGxlc19ncm91cDIgPSBjb2xuYW1lcyhkZHMpW2dycDJfcm93c10KICBvdXRwdXQgPSBsaXN0KGNvbnRyYXN0ID0gY29udHJhc3QsCiAgICAgICAgICAgICAgICBzYW1wbGVzX2dyb3VwMSA9IHNhbXBsZXNfZ3JvdXAxLAogICAgICAgICAgICAgICAgc2FtcGxlc19ncm91cDIgPSBzYW1wbGVzX2dyb3VwMikKICAKICByZXR1cm4ob3V0cHV0KQp9CmBgYAoKVGhlc2UgZnVuY3Rpb25zIGFyZSBsaXN0ZWQgYmVsb3cuCgpgYGB7ciBwcmludF9mdW5jdGlvbnMsIGVjaG8gPSBGQUxTRSwgcmVzdWx0cyA9ICdhc2lzJ30KY2F0KHBhc3RlMCgiLSAiLCBzZXRkaWZmKGxzKCksIGMoImhvb2tzIiwgInBhcmFtcyIsICJob29rX2ZvbGRhYmxlIiwgImRlc2lnbl90bXAiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiZmlnX3NhdmVfZm9sZGVyIiwgInRleHRfdG9fcHJpbnQiKSkpLAogICAgc2VwID0gIlxuIikKYGBgCgojIyBQYXJhbWV0ZXJzCgpUaGUgdGFibGUgYmVsb3cgc3VtbWFyaXplcyBhbGwgdGhlIGlucHV0IHBhcmFtZXRlcnMuCgpgYGB7ciBpbnB1dF9wYXJhbWV0ZXIsIGNsYXNzLnNvdXJjZSA9ICJmb2xkLWhpZGUifQpwcm9qZWN0TmFtZSAgICAgPSBwYXJhbXMkcHJvamVjdE5hbWUKZGVzaWduUGF0aCAgICAgID0gcGFyYW1zJGRlc2lnblBhdGgKY29tcGFyaXNvblBhdGggID0gcGFyYW1zJGNvbXBhcmlzb25QYXRoCmRpZmZhbmFUZXN0ICAgICA9IGFzLmxvZ2ljYWwodG91cHBlcihwYXJhbXMkZGlmZmFuYVRlc3QpKQpleHBIZWFkZXIgICAgICAgPSBhcy5sb2dpY2FsKHRvdXBwZXIocGFyYW1zJGV4cEhlYWRlcikpCmRlc2VxTW9kZWwgICAgICA9IHBhcmFtcyRkZXNlcU1vZGVsCnNpemVGYWN0b3JUeXBlICA9IHBhcmFtcyRzaXplRmFjdG9yVHlwZQpmaXRUeXBlICAgICAgICAgPSBwYXJhbXMkZml0VHlwZQpzdGF0aXN0aWNUZXN0ICAgPSBwYXJhbXMkc3RhdGlzdGljVGVzdAp3ZWlnaHRDb250cmFzdCAgPSBwYXJhbXMkd2VpZ2h0Q29udHJhc3QKcHJlZml4ICAgICAgICAgID0gcGFyYW1zJHByZWZpeApwbG90SW50ZXJhY3RpdmUgPSBwYXJhbXMkcGxvdEludGVyYWN0aXZlCmxlYXZlT25FcnJvciAgICA9IHBhcmFtcyRsZWF2ZU9uRXJyb3IKCiMgRGlzcGxheSB0aGVtIGFzIGEgdGFibGUKYygiUHJvamVjdCBuYW1lIChwcm9qZWN0TmFtZSkiID0gcHJvamVjdE5hbWUsCiAgIlBhdGggdG8gdGhlIGRlc2lnbiBmaWxlIChkZXNpZ25QYXRoKSIgPSBkZXNpZ25QYXRoLAogICJQYXRoIHRvIHRoZSBjb21wYXJpc29uIGZpbGUgKGNvbXBhcmlzb25QYXRoKSIgPSBjb21wYXJpc29uUGF0aCwKICAiV2hldGhlciB0byBwZXJmb3JtIHRoZSBkaWZmZXJlbnRpYWwgZXhwcmVzc2lvbiBhbmFseXNpcyBvciBub3QgKGRpZmZhbmFUZXN0KSIgPSBkaWZmYW5hVGVzdCwKICAiV2hldGhlciB0aGUgZGVzaWduIHRhYmxlIGhhcyBhIGhlYWRlciBvciBub3QgKGV4cEhlYWRlcikiID0gZXhwSGVhZGVyLAogICJERVNlcTIgbW9kZWwgKGRlc2VxTW9kZWwpIiA9IGRlc2VxTW9kZWwsCiAgIlNpemUgZmFjdG9yIHR5cGUgKHNpemVGYWN0b3JUeXBlKSIgPSBzaXplRmFjdG9yVHlwZSwKICAiRml0IHR5cGUgKGZpdFR5cGUpIiA9IGZpdFR5cGUsCiAgIlN0YXRpc3RpYyB0ZXN0IChzdGF0aXN0aWNUZXN0KSIgPSBzdGF0aXN0aWNUZXN0LAogICJXaGV0aGVyIHRvIHdlaWdodCB0aGUgY29udHJhc3QgdmVjdG9yIGJ5IHRoZSBudW1iZXIgb2Ygc2FtcGxlcyBvciBub3QgKHdlaWdodENvbnRyYXN0KSIgPSB3ZWlnaHRDb250cmFzdCwKICAiUHJlZml4IHRvIHNhdmUgZmlsZXMgKHByZWZpeCkiID0gcHJlZml4LAogICJXaGV0aGVyIHRvIG1ha2UgaW50ZXJhY3RpdmUgcGxvdHMgb3Igbm90IChwbG90SW50ZXJhY3RpdmUpIiA9IHBsb3RJbnRlcmFjdGl2ZSwKICAiV2hldGhlciB0byBzdG9wIHRoZSByZW5kZXJpbmcgaW4gY2FzZSBvZiBlcnJvciAobGVhdmVPbkVycm9yKSIgPSBsZWF2ZU9uRXJyb3IpICU+JQogIGRhdGEuZnJhbWUoUGFyYW1ldGVyID0gbmFtZXMoLiksCiAgICAgICAgICAgICBWYWx1ZSAgICAgPSAuLAogICAgICAgICAgICAgcm93Lm5hbWVzID0gTlVMTCkgJT4lCiAga25pdHI6OmthYmxlKCJodG1sIiwgZXNjYXBlID0gRkFMU0UpICU+JQogIGthYmxlRXh0cmE6OmthYmxlX3N0eWxpbmcoInN0cmlwZWQiLCBmdWxsX3dpZHRoID0gRkFMU0UpICU+JQogIGthYmxlRXh0cmE6OmNvbHVtbl9zcGVjKDEsIGJvbGQgPSBUUlVFKQpgYGAKCiMgRGF0YSBwcmVwYXJhdGlvbgoKVGhlIHB1cnBvc2Ugb2YgdGhpcyBzZWN0aW9uIGlzIHRvOgoKKiBsb2FkIHRoZSBkZXNpZ24gZmlsZSwgY29udGFpbmluZyBtZXRhZGF0YSBhYm91dCBlYWNoIHNhbXBsZSA7CiogbG9hZCB0aGUgaW5kaXZpZHVhbCBzYW1wbGUgY291bnQgZGF0YSwgYXNzZW1ibGUgdGhlbSBhcyBhIGdlbmUtYnktc2FtcGxlIGNvdW50IG1hdHJpeCwgYW5kIHNhdmUgaXQgYXMgYSBUU1YgZmlsZSA7IGFuZCwKKiBidWlsZCBhIGRhdGFzZXQgaW4gYSBmb3JtYXQgY29tcGF0aWJsZSB3aXRoIHRoZSBkaWZmZXJlbnRpYWwgZXhwcmVzc2lvbiBhbmFseXNpcyAoREVTZXEyLXJlbGF0ZWQgZm9ybWF0KS4KCiMjIExvYWQgbWV0YWRhdGEKCldlIGxvYWQgdGhlIGRlc2lnbiBmaWxlIGFuZCBkaXNwbGF5IHRoZSA2IGZpcnN0IHJvd3MuIAoKYGBge3IgbG9hZF9kZXNpZ259CmRlc2lnbiA9IHJlYWQudGFibGUoZGVzaWduUGF0aCwKICAgICAgICAgICAgICAgICAgICBzZXAgPSAiXHQiLAogICAgICAgICAgICAgICAgICAgIGhlYWRlciA9IFRSVUUsCiAgICAgICAgICAgICAgICAgICAgZGVjID0gIi4iLAogICAgICAgICAgICAgICAgICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRSkKCiMgQWJvdXQgYSB3YXJuaW5nIGluIERFU2VxMgpkZXNpZ24kQ29uZGl0aW9uID0gc3RyaW5ncjo6c3RyX3JlcGxhY2VfYWxsKAogIGRlc2lnbiRDb25kaXRpb24sCiAgcGF0dGVybiA9ICItIiwKICByZXBsYWNlbWVudCA9ICJfIikKCiMgQWRkIFJlZmVyZW5jZSBpZiBkb2VzIG5vdCBleGlzdAppZiAoISgiUmVmZXJlbmNlIiAlaW4lIGNvbG5hbWVzKGRlc2lnbikpKSB7CiAgZGVzaWduJFJlZmVyZW5jZSA9IEluZgogIGNvbXBsZXhNb2RlID0gVFJVRQp9IGVsc2UgewogIGNvbXBsZXhNb2RlID0gRkFMU0UKfQoKIyBSZW9yZGVyIHNhbXBsZXMgYmFzZWQgb24gQ29uZGl0aW9uCmRlc2lnbiA9IGRlc2lnbiAlPiUKICBkcGx5cjo6YXJyYW5nZShDb25kaXRpb24sIFJlcFRlY2hHcm91cCwgTmFtZSkgJT4lCiAgZHBseXI6Om11dGF0ZShDb25kaXRpb24gPSBmYWN0b3IoQ29uZGl0aW9uLCBsZXZlbHMgPSB1bmlxdWUoQ29uZGl0aW9uKSkpICU+JQogIGRwbHlyOjptdXRhdGUoUmVwVGVjaEdyb3VwID0gZmFjdG9yKFJlcFRlY2hHcm91cCwgbGV2ZWxzID0gdW5pcXVlKFJlcFRlY2hHcm91cCkpKSAlPiUKICBkcGx5cjo6bXV0YXRlKE5hbWUgPSBmYWN0b3IoTmFtZSwgbGV2ZWxzID0gdW5pcXVlKE5hbWUpKSkKCiMgUmVtb3ZlIGNvbHVtbnMgb3V0IG9mIGludGVyZXN0CmRlc2lnbiA9IGRlc2lnblssICEoY29sbmFtZXMoZGVzaWduKSAlaW4lIGMoIlNhbXBsZUlkIiwgIkRlc2NyaXB0aW9uIiwgIkRhdGUiLCAiRmFzdHFGb3JtYXQiKSldCgojIERpc3BsYXkKaGVhZChkZXNpZ24pICU+JQogIGtuaXRyOjprYWJsZSgiaHRtbCIpIApgYGAKCgpEZXNjcmlwdGlvbiBvZiB0aGUgY29sdW1uczoKCmBgYHtyIHByaW50X21ldGFkYXRhX2hlbHAsIGVjaG8gPSBGQUxTRSwgcmVzdWx0cyA9ICdhc2lzJ30KdGhlX21lc3NhZ2UgPSBjKE5hbWUgPSAiLSAqKmBOYW1lYCoqOiBVbmlxdWUgaWRlbnRpZmllciBvZiBlYWNoIHRlY2huaWNhbCByZXBsaWNhdGUuIiwKICAgICAgICAgICAgICAgIGV4cHJlc3Npb25GaWxlID0gIi0gKipgZXhwcmVzc2lvbkZpbGVgKio6IEZpbGUgcGF0aCB0byB0aGUgY291bnQgZXhwcmVzc2lvbiBkYXRhLiBUaGUgKipUU1YqKiBmaWxlIGNvbnRhaW5zIHR3byBjb2x1bW5zOiBvbmUgZm9yIHRoZSBnZW5lIGlkZW50aWZpZXJzIGFuZCBvbmUgZm9yIHRoZSBudW1iZXIgb2YgcmVhZHMgbWFwcGluZyB0byB0aGVpciBnZW5vbWljIGxvY2F0aW9uLiBGb3IgY29tcGF0aWJpbGl0eSB3aXRoIEV4Y2VsLCBwbGVhc2UgdXNlIHRoZSAqKlhMU1gqKiBmaWxlIGluc3RlYWQgb2YgdGhlIFRTViBmaWxlIChbcmVhZCBtb3JlXShodHRwczovL3BtYy5uY2JpLm5sbS5uaWguZ292L2FydGljbGVzL1BNQzgzNTcxNDAvKSkuIEZ1cnRoZXJtb3JlLCB0aGUgWExTWCBmaWxlIGNvbnRhaW5zIGFkZGl0aW9uYWwgY29sdW1ucywgc3VjaCBhcyBhIGRlc2NyaXB0aW9uIG9mIGVhY2ggZ2VuZS4iLAogICAgICAgICAgICAgICAgUmVhZHMgPSAiLSAqKmBSZWFkc2AqKjogRmlsZSBwYXRoIHRvIHRoZSBzZXF1ZW5jaW5nIHJlYWRzLCBpbiBGQVNUUSBmb3JtYXQgKFtyZWFkIG1vcmVdKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL0ZBU1RRX2Zvcm1hdCkpLiIsCiAgICAgICAgICAgICAgICBSZXBUZWNoR3JvdXAgPSAiLSAqKmBSZXBUZWNoR3JvdXBgKio6IFNhbXBsZXMgd2l0aCB0aGUgc2FtZSB2YWx1ZSBpbiB0aGUgYFJlcFRlY2hHcm91cGAgY29sdW1uIGFyZSAqKnRlY2huaWNhbCByZXBsaWNhdGVzKiouIFRoaXMgb2NjdXJzIHdoZW4gdGhlIHNlcXVlbmNpbmcgbGlicmFyeSBpcyBzcGxpdCBvdmVyIHNlcXVlbmNpbmcgbGFuZXMuIEVhY2ggbGFuZSBnZW5lcmF0ZXMgYSB0ZWNobmljYWwgcmVwbGljYXRlIGZyb20gdGhlIHNhbWUgYmlvbG9naWNhbCBsaWJyYXJ5LiIsCiAgICAgICAgICAgICAgICBDb25kaXRpb24gPSAiLSAqKmBDb25kaXRpb25gKio6IFNhbXBsZXMgd2l0aCB0aGUgc2FtZSB2YWx1ZSBpbiB0aGUgYENvbmRpdGlvbmAgY29sdW1uIGFyZSAqKmJpb2xvZ2ljYWwgcmVwbGljYXRlcyoqLiBUaGV5IGFyZSBwcm9kdWNlZCBieSB0aGUgZXhwZXJpbWVudGVyLiIsCiAgICAgICAgICAgICAgICBSZWZlcmVuY2UgPSAiLSAqKmBSZWZlcmVuY2VgKio6IE51bWVyaWNhbCB2YWx1ZXMgdXNlZCB0byBkZWZpbmUgdGhlIHBhaXJ3aXNlIGNvbXBhcmlzb25zIGJldHdlZW4gY29uZGl0aW9ucyBpbiB0aGUgZGlmZmVyZW50aWFsIGV4cHJlc3Npb24gYW5hbHlzaXMuIikKdGhlX21lc3NhZ2UgPSB0aGVfbWVzc2FnZVtuYW1lcyh0aGVfbWVzc2FnZSkgJWluJSBjb2xuYW1lcyhkZXNpZ24pXQoKY2F0KHBhc3RlMCh0aGVfbWVzc2FnZSksIHNlcCA9ICJcbiIpCmBgYAoKV2UgYXNzb2NpYXRlIGEgY29sb3IgdG8gZWFjaCBjb25kaXRpb24uCgpgYGB7ciBhZGRfY29sb3JzfQpjb2xvcnNfY29uZGl0aW9uID0gdW5pcXVlKGRlc2lnbiRDb25kaXRpb24pICU+JQogIHNldE5hbWVzKG5tID0gLiwKICAgICAgICAgICBnckRldmljZXM6OmhjbC5jb2xvcnMobGVuZ3RoKC4pLCAiU3BlY3RyYWwiKSkKCmRhdGEuZnJhbWUoQ29sb3IgPSB1bm5hbWUoY29sb3JzX2NvbmRpdGlvbiksCiAgICAgICAgICAgQ29uZGl0aW9uID0gbmFtZXMoY29sb3JzX2NvbmRpdGlvbikpICU+JQogIGtuaXRyOjprYWJsZSgiaHRtbCIsCiAgICAgICAgICAgICAgIGNvbC5uYW1lcyA9IGMoIkNvbG9yIChoZXggY29kZSkiLCAiQ29uZGl0aW9uIikpICU+JQogIGthYmxlRXh0cmE6OmNvbHVtbl9zcGVjKDEsIGNvbG9yID0gY29sb3JzX2NvbmRpdGlvbikgJT4lCiAga2FibGVFeHRyYTo6a2FibGVfc3R5bGluZyhmdWxsX3dpZHRoID0gRkFMU0UpCmBgYAoKIyMgQnVpbGQgY291bnQgbWF0cml4CgpGcm9tIHRoZSBpbmRpdmlkdWFsIGNvdW50IGRhdGEsIHdlIGJ1aWxkIGEgZ2VuZS1ieS1zYW1wbGUgY291bnQgbWF0cml4LiBUaGUgY291bnQgdmFsdWUgZm9yIGEgZ2l2ZW4gZ2VuZSBjb3JyZXNwb25kcyB0byB0aGUgbnVtYmVyIChpbnRlZ2VyIHZhbHVlKSBvZiBzZXF1ZW5jaW5nIHJlYWRzIHRoYXQgbWFwIHRvIGl0cyBnZW5vbWljIHJlZ2lvbi4gV2UgZGlzcGxheSB0aGUgdG9wIGxlZnQgY29ybmVyIG9mIHRoaXMgbWF0cml4LCBtZWFuaW5nIDUgZ2VuZXMgYW5kIDUgc2FtcGxlcy4gVGhlIGNvbXBsZXRlIG1hdHJpeCBpcyBzYXZlZCBhcyBhIFRTViBmaWxlIChbcXVpY2sgYWNjZXNzXSgjc2F2ZV9jb3VudF9tYXRyaXgpKS4KCmBgYHtyIGNvdW50X21hdH0KY291bnRfbWF0ID0gYnVpbGRDb3VudE1hdHJpeChmaWxlcyA9IGRlc2lnbiRleHByZXNzaW9uRmlsZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzYW1wbGVMYWJlbCA9IGRlc2lnbiROYW1lLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGV4cEhlYWRlciA9IGV4cEhlYWRlcikKCiMgUmVvcmRlciBjb2x1bW5zCmNvdW50X21hdCA9IGNvdW50X21hdFssIGxldmVscyhkZXNpZ24kTmFtZSldCgojIERpc3BsYXkKY291bnRfbWF0W2MoMTo1KSwgYygxOjUpXSAlPiUKICBrbml0cjo6a2FibGUoImh0bWwiKSAlPiUKICBrYWJsZUV4dHJhOjprYWJsZV9zdHlsaW5nKGZ1bGxfd2lkdGggPSBGQUxTRSkKYGBgCgpUaGUgY291bnQgbWF0cml4IGNvbnRhaW5zICoqYHIgbnJvdyhjb3VudF9tYXQpYCoqIGdlbmVzLCAqKmByIG5jb2woY291bnRfbWF0KWAqKiBzYW1wbGVzIGFuZCAqKmByIHJvdW5kKDEwMCptZWFuKGNvdW50X21hdCA9PSAwKSwyKWAqKiUgb2YgbnVsbCB2YWx1ZXMuCgojIyBDcmVhdGUgREVTZXFEYXRhU2V0IG9iamVjdAoKV2UgY3JlYXRlIHRoZSBERVNlcURhdGFTZXQgb2JqZWN0IHRoYXQgaW5jbHVkZXMgdGhlIGNvdW50IG1hdHJpeCBhbmQgdGhlIGRlc2lnbiBmaWxlLgoKYGBge3IgZGRzX29iamVjdH0KZGRzID0gREVTZXEyOjpERVNlcURhdGFTZXRGcm9tTWF0cml4KAogIGNvdW50RGF0YSA9IGNvdW50X21hdCwKICBjb2xEYXRhID0gZGVzaWduLAogIGRlc2lnbiA9IGFzLmZvcm11bGEoZGVzZXFNb2RlbCkpCmBgYAoKIyMgU2F2ZSBjb3VudCBtYXRyaXggeyNzYXZlX2NvdW50X21hdHJpeH0KCldlIHNhdmUgdGhlIGNvdW50IG1hdHJpeCBhcyBhIFRTViBmaWxlLgoKYGBge3Igc2F2ZV9jb3VudF9tYXRyaXh9CmZpbGVuYW1lID0gcGFzdGUwKHByZWZpeCwgcHJvamVjdE5hbWUsICItbm9ybWFsaXNhdGlvbl9yYXdDb3VudE1hdHJpeC50c3YiKQoKc2F2ZVJhd0NvdW50TWF0cml4KGRkcywKICAgICAgICAgICAgICAgICAgIGZpbGUgPSBmaWxlbmFtZSkKYGBgCgpPdXRwdXQgZmlsZSBuYW1lOjxicj4qYHIgZmlsZW5hbWVgKgoKIyBRdWFsaXR5IGNvbnRyb2wKClRoZSBhaW0gb2YgdGhpcyBzZWN0aW9uIGlzIHRvIGJldHRlciBhcHByZWhlbmQgdGhlIHZhcmlhYmlsaXR5IGJldHdlZW4gdGhlIHRlY2huaWNhbCByZXBsaWNhdGVzLiBGb3IgaW5mb3JtYXRpb24gb24gdGhlIGNvcnJlc3BvbmRlbmNlIGJldHdlZW4gdGhlIGlkZW50aWZpZXJzIG9mIHRlY2huaWNhbCByZXBsaWNhdGVzIGFuZCBzYW1wbGUgbmFtZXMsIHBsZWFzZSByZWZlciB0byB0aGUgZGVzaWduIGZpbGUuCgojIyBSYXcgY291bnRzIGRlbmRyb2dyYW0KCldlIGNvbXB1dGUgZXVjbGlkZWFuIGRpc3RhbmNlcyBiZXR3ZWVuIGVhY2ggcGFpciBvZiBzYW1wbGVzLCBjb25zaWRlcmluZyB0aGUgcmF3IGNvdW50IHZhbHVlcy4KCmBgYHtyIGRlbmRyb2dyYW1fcmF3X2NvdW50X21hdHJpeH0KZGlzdC5tYXQgPSBkaXN0KHQoY291bnRfbWF0KSkKaGMgPSBzdGF0czo6aGNsdXN0KGRpc3QubWF0KQpoY2RhdGEgPSBnZ2RlbmRybzo6ZGVuZHJvX2RhdGEoaGMsIHR5cGUgPSAicmVjdGFuZ2xlIikKCiMgQWRkIGdyb3VwaW5nIGZvciBjb2xvcnMKaGNkYXRhJGxhYmVscyA9IGRwbHlyOjpsZWZ0X2pvaW4oeCA9IGhjZGF0YSRsYWJlbHMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHkgPSBkZXNpZ25bLCBjKCJOYW1lIiwgIkNvbmRpdGlvbiIpXSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYnkgPSBjKCJsYWJlbCIgPSAiTmFtZSIpKQpgYGAKCldlIHZpc3VhbGlzZSB0aGVzZSBkaXN0YW5jZXMgdGhyb3VnaCBhIGRlbmRyb2dyYW0uIEl0IGluZm9ybXMgd2hldGhlciB0ZWNobmljYWwgcmVwbGljYXRlcyBvciBjbG9zZWx5IHJlbGF0ZWQgY29uZGl0aW9ucyBhcmUgY2xvc2VyIHRvZ2V0aGVyIHRoYW4gd2l0aCBvdGhlciBzYW1wbGVzLgoKYGBge3IgZmlnLW5vcm1hbGlzYXRpb25fdW5wb29sZWRfY2x1c3RlcmluZywgZmlnLndpZHRoID0gMTAsIGZpZy5oZWlnaHQgPSA2fQpwbG90RGVuZHJvZ3JhbShoY19kYXRhLAogICAgICAgICAgICAgICBmaWdfdGl0bGUgPSBwYXN0ZTAoIkRlbmRyb2dyYW0gb24gdGhlIHJhdyBjb3VudCBtYXRyaXggLSAiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcHJvamVjdE5hbWUpKQpgYGAKCiMjIFJhdyBjb3VudHMgUENBCgpUaGUgKipgciBuY29sKGNvdW50X21hdClgKiogc2FtcGxlcyBhcmUgcmVwcmVzZW50ZWQgaW4gYSBzcGFjZSBvZiAqKmByIG5yb3coY291bnRfbWF0KWAqKiBkaW1lbnNpb25zLCBjb3JyZXNwb25kaW5nIHRvIGdlbmVzLiBXZSBwZXJmb3JtIGEgUHJpbmNpcGFsIENvbXBvbmVudCBBbmFseXNpcyAoUENBKSB0byB2aXN1YWxpc2Ugc2FtcGxlcyBvbiBhIHR3by1kaW1lbnNpb24gc3BhY2Ugc3VjaCB0aGF0IGl0IHJlcHJlc2VudHMgdGhlIGhpZ2hlc3QgdmFyaWFiaWxpdHkgYmV0d2VlbiBzYW1wbGVzLgoKYGBge3IgcnVuX3BjYX0KcGNhQ291bnQgPSBjb3VudF9tYXQgJT4lCiAgdCgpICU+JQogIEZhY3RvTWluZVI6OlBDQSguLCBncmFwaCA9IEZBTFNFKQpgYGAKCldlIHZpc3VhbGlzZSB0aGUgc2FtcGxlcyBvbiB0aGUgdHdvLWRpbWVuc2lvbmFsIHByb2plY3Rpb24uIFRoZSBjb29yZGluYXRlcyBvZiBlYWNoIHNhbXBsZSBkZXBlbmQgb24gdGhvc2Ugb2YgdGhlIG90aGVyIHNhbXBsZXMuIFRoaXMgcHJvamVjdGlvbiBjYXB0dXJlcyAqKmByIHJvdW5kKHBjYUNvdW50JGVpZ1syLCAzXSwgMilgKiolIG9mIHRoZSB0b3RhbCB2YXJpYWJpbGl0eSBiZXR3ZWVuIHNhbXBsZXMuIFRoZXJlZm9yZSwgaXQgbGFja3MgKipgciAxMDAtcm91bmQocGNhQ291bnQkZWlnWzIsIDNdLCAyKWAqKiUgb2YgdGhlIGluaXRpYWwgaW5mb3JtYXRpb24uIEluIHRoaXMgcmVwcmVzZW50YXRpb24sIHR3byBwb2ludHMgKHNhbXBsZXMpIGFyZSBjbG9zZSB0b2dldGhlciBpZiB0aGV5IGhhdmUgc2ltaWxhciBnbG9iYWwgdHJhbnNjcmlwdG9taWMgcHJvZmlsZXMuIFdlIGV4cGVjdCB0aGUgdGVjaG5pY2FsIGFuZCBiaW9sb2dpY2FsIHJlcGxpY2F0ZXMgdG8gYmUgY2xvc2VyIHRvIGVhY2ggb3RoZXIgdGhhbiB0byB0aGUgb3RoZXIgc2FtcGxlcyBhbmQgY29uZGl0aW9ucy4KCmBgYHtyIGZpZy1ub3JtYWxpc2F0aW9uX3VucG9vbGVkX1BDQSwgZmlnLndpZHRoID0gMTAsIGZpZy5oZWlnaHQgPSA2fQpwY2FDb3VudCRpbmQkY29vcmQgJT4lCiAgYXMuZGF0YS5mcmFtZSgpICU+JQogIGRwbHlyOjptdXRhdGUoc2FtcGxlID0gcm93bmFtZXMoLikpICU+JQogIGRwbHlyOjpsZWZ0X2pvaW4oeCA9IC4sCiAgICAgICAgICAgICAgICAgICB5ID0gdW5pcXVlKGRlc2lnblssIGMoIk5hbWUiLCAiQ29uZGl0aW9uIildKSwKICAgICAgICAgICAgICAgICAgIGJ5ID0gYygic2FtcGxlIiA9ICJOYW1lIikpICU+JQogIHBsb3RQQ0EoLiwKICAgICAgICAgIGZpZ190aXRsZSA9ICBwYXN0ZTAoIlBDQSBvZiB0aGUgcmF3IGNvdW50IG1hdHJpeCAtICIsIHByb2plY3ROYW1lKSkKYGBgCgojIyBOdWxsIGNvdW50cyBiYXJwbG90CgpUaGlzIGZpZ3VyZSBkaXNwbGF5cyB0aGUgcHJvcG9ydGlvbiBvZiBudWxsIGNvdW50cyBwZXIgc2FtcGxlLCBtZWFuaW5nLCB0aGUgZ2VuZXMgdGhhdCBhcmUgbm90IGJlaW5nIGV4cHJlc3NlZCBvciBub3QgY2FwdHVyZWQgYnkgdGhlIHByb3RvY29sLCB3aXRoaW4gdGhlIGJpb2xvZ2ljYWwgc2FtcGxlcy4gV2UgZXhwZWN0IHNhbXBsZXMgZnJvbSB0aGUgc2FtZSBjb25kaXRpb24gdG8gYmUgc2ltaWxhci4KCmBgYHtyIGZpZy1ub3JtYWxpc2F0aW9uX251bGxfY291bnRzLCBmaWcud2lkdGggPSAxMCwgZmlnLmhlaWdodCA9IDZ9CmNvbE1lYW5zKGNvdW50X21hdCA9PSAwKSAlPiUKICBkYXRhLmZyYW1lKHNhbXBsZSA9IGZhY3RvcihuYW1lcyguKSwgbGV2ZWxzID0gbGV2ZWxzKGRlc2lnbiROYW1lKSksCiAgICAgICAgICAgICBwcm9wX251bGxfY291bnRzID0gLikgJT4lCiAgZHBseXI6Om11dGF0ZShwcm9wX251bGxfY291bnRzID0gMTAwKnByb3BfbnVsbF9jb3VudHMpICU+JQogIGRwbHlyOjpsZWZ0X2pvaW4oeCA9IC4sCiAgICAgICAgICAgICAgICAgICB5ID0gZGVzaWduWywgYygiTmFtZSIsICJDb25kaXRpb24iKV0sCiAgICAgICAgICAgICAgICAgICBieSA9IGMoInNhbXBsZSIgPSAiTmFtZSIpKSAlPiUKICAjIFBsb3QKICBwbG90QmFycGxvdCguLAogICAgICAgICAgICAgIHlfY29sdW1uID0gInByb3BfbnVsbF9jb3VudHMiLAogICAgICAgICAgICAgIHlfdGl0bGUgPSAiUHJvcG9ydGlvbiBvZiBudWxsIGNvdW50cyAoJSkiLAogICAgICAgICAgICAgIGZpZ190aXRsZSA9IHBhc3RlMCgiUHJvcG9ydGlvbiBvZiBudWxsIGNvdW50cyBwZXIgc2FtcGxlIC0gIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcHJvamVjdE5hbWUpKQpgYGAKCiMjIFJhdyBjb3VudHMgYmFycGxvdAoKVGhpcyBmaWd1cmUgZGlzcGxheXMgdGhlIHRvdGFsIG51bWJlciBvZiByZWFkcyBwZXIgc2FtcGxlLCBhbHNvIGtub3duIGFzIGxpYnJhcnkgc2l6ZS4gV2UgY2FuIGFzc2VzcyBpZiBzb21lIHNhbXBsZXMgaGF2ZSBhIGxvdyBsaWJyYXJ5IHNpemUgY29tcGFyZWQgdG8gb3RoZXJzLCBhbmQgd2hldGhlciB0ZWNobmljYWwgcmVwbGljYXRlcyBzaGFyZSBhIHNpbWlsYXIgbnVtYmVyIG9mIHJlYWRzIG9yIG5vdC4KCmBgYHtyIGZpZy1ub3JtYWxpc2F0aW9uX2JhcnBsb3RfY291bnRzLCBmaWcud2lkdGggPSAxMCwgZmlnLmhlaWdodCA9IDZ9CmNvbFN1bXMoREVTZXEyOjpjb3VudHMoZGRzKSkgJT4lCiAgZGF0YS5mcmFtZShzYW1wbGUgPSBmYWN0b3IobmFtZXMoLiksIGxldmVscyA9IGxldmVscyhkZXNpZ24kTmFtZSkpLAogICAgICAgICAgICAgcmVhZF9jb3VudHMgPSAuKSAlPiUKICBkcGx5cjo6bGVmdF9qb2luKHggPSAuLAogICAgICAgICAgICAgICAgICAgeSA9IGRlc2lnblssIGMoIk5hbWUiLCAiQ29uZGl0aW9uIildLAogICAgICAgICAgICAgICAgICAgYnkgPSBjKCJzYW1wbGUiID0gIk5hbWUiKSkgJT4lCiAgIyBQbG90CiAgcGxvdEJhcnBsb3QoLiwKICAgICAgICAgICAgICB5X2NvbHVtbiA9ICJyZWFkX2NvdW50cyIsCiAgICAgICAgICAgICAgeV90aXRsZSA9ICJUb3RhbCByZWFkIGNvdW50cyIsCiAgICAgICAgICAgICAgZmlnX3RpdGxlID0gcGFzdGUwKCJOdW1iZXIgb2YgcmVhZHMgcGVyIHNhbXBsZSAtICIsIHByb2plY3ROYW1lKSkKYGBgCgojIyBSYXcgY291bnRzIGJveHBsb3QKClRoaXMgZmlndXJlIGRpc3BsYXlzLCBhcyBhIGJveHBsb3QsIHRoZSBudW1iZXIgb2YgcmF3IGNvdW50cyBmb3IgZWFjaCBnZW5lLCBwZXIgc2FtcGxlLiBJdCBpbmZvcm1zIHdoZXRoZXIgdGhlIGdlbmVzIHNob3cgYSBzaW1pbGFyIGNvdW50IGRpc3RyaWJ1dGlvbiBiZXR3ZWVuIHNhbXBsZXMgYW5kIGNvbmRpdGlvbnMuCgpgYGB7ciBmaWctbm9ybWFsaXNhdGlvbl9ib3hwbG90X2NvdW50LCBmaWcud2lkdGggPSAxMCwgZmlnLmhlaWdodCA9IDZ9CmxvZzIoREVTZXEyOjpjb3VudHMoZGRzKSsxKSAlPiUKICByZXNoYXBlMjo6bWVsdCgpICU+JQogIGBjb2xuYW1lczwtYChjKCJnZW5lX0lEIiwgInNhbXBsZSIsICJsb2cyX2NvdW50cyIpKSAlPiUKICBkcGx5cjo6bXV0YXRlKHNhbXBsZSA9IGZhY3RvcihzYW1wbGUsIGxldmVscyA9IGxldmVscyhkZXNpZ24kTmFtZSkpKSAlPiUKICBkcGx5cjo6bGVmdF9qb2luKHggPSAuLAogICAgICAgICAgICAgICAgICAgeSA9IGRlc2lnblssIGMoIk5hbWUiLCAiQ29uZGl0aW9uIildLAogICAgICAgICAgICAgICAgICAgYnkgPSBjKCJzYW1wbGUiID0gIk5hbWUiKSkgJT4lCiAgIyBQbG90CiAgcGxvdEJveHBsb3QoLiwKICAgICAgICAgICAgICB5X2NvbHVtbiA9ICJsb2cyX2NvdW50cyIsCiAgICAgICAgICAgICAgeV90aXRsZSA9ICJsb2dfMiAoY291bnRzKzEpIiwKICAgICAgICAgICAgICBmaWdfdGl0bGUgPSBwYXN0ZTAoIlJhdyBjb3VudHMgZGlzdHJpYnV0aW9uIHBlciBzYW1wbGUgLSAiLCBwcm9qZWN0TmFtZSkpCmBgYAoKCjwhLS0gQ29uZGl0aW9uYWwgZXhlY3V0aW9uIGluIGNhc2UgdGVjaG5pY2FsIHJlcGxpY2F0ZXMgYXJlIHByZXNlbnQgLS0+Cjxicj4KCmBgYHtyIHRlY2hfcmVwX3ByZXNlbnQsIGVjaG8gPSBGQUxTRX0KdGVjaF9yZXBfcHJlc2VudCA9IChtYXgodGFibGUoZGVzaWduJFJlcFRlY2hHcm91cCwgZGVzaWduJENvbmRpdGlvbikpID4gMSkKCmlmICh0ZWNoX3JlcF9wcmVzZW50KSB7CiAgdGhlX21lc3NhZ2UgPSAiSW4gdGhpcyBjb250ZXh0LCB0aGVyZSBhcmUgdGVjaG5pY2FsIHJlcGxpY2F0ZXMuIgp9IGVsc2UgewogIHRoZV9tZXNzYWdlID0gIkluIHRoaXMgY29udGV4dCwgdGhlcmUgaXMgbm8gdGVjaG5pY2FsIHJlcGxpY2F0ZS4iCiAgCiAgIyBBbnl3YXksIHdlIGNoYW5nZSB0aGUgc2FtcGxlIGxhYmVscyB0byB0aGUgYmlvbG9naWNhbCBtZWFuaW5nZnVsIG9uZXMuCiAgZGRzID0gREVTZXEyOjpjb2xsYXBzZVJlcGxpY2F0ZXMoZGRzLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdyb3VwYnkgPSBkZHMkUmVwVGVjaEdyb3VwKQp9CmBgYAoKYHIgdGhlX21lc3NhZ2VgCgpgYGB7ciBjb2xsYXBzZV9yZXBsaWNhdGVzLCBlY2hvID0gRkFMU0UsIHJlc3VsdHMgPSAnYXNpcycsIGV2YWwgPSB0ZWNoX3JlcF9wcmVzZW50fQpyZXMgPSBrbml0cjo6a25pdF9jaGlsZCgKICBpbnB1dCA9ICcwMl9jaGlsZF9jb2xsYXBzZV9yZXBsaWNhdGVzLlJtZCcsCiAgcXVpZXQgPSBUUlVFKQoKY2F0KHJlcywgc2VwID0gJ1xuJykKYGBgCgojIE5vcm1hbGlzYXRpb24KClRoZSBhaW0gb2YgdGhpcyBzZWN0aW9uIGlzIHRvIG5vcm1hbGlzZSB0aGUgY291bnQgbWF0cml4LCBiZWZvcmUgcGVyZm9ybWluZyB0aGUgZGlmZmVyZW50aWFsIGV4cHJlc3Npb24gYW5hbHlzaXMuIFRoZSBub3JtYWxpc2F0aW9uIG1ldGhvZCBjb25zaWRlcnMgdGhlIHNlcXVlbmNpbmcgZGVwdGggYW5kIFJOQSBjb21wb3NpdGlvbiBvZiBlYWNoIHNhbXBsZSB0byBjb21wdXRlIG5vcm1hbGlzZWQgY291bnQgdmFsdWVzLgoKRm9yIG1vcmUgZGV0YWlscywgcGxlYXNlIHZpc2l0OgoKW2h0dHBzOi8vaGJjdHJhaW5pbmcuZ2l0aHViLmlvL0RHRV93b3Jrc2hvcC9sZXNzb25zLzAyX0RHRV9jb3VudF9ub3JtYWxpemF0aW9uLmh0bWxdKGh0dHBzOi8vaGJjdHJhaW5pbmcuZ2l0aHViLmlvL0RHRV93b3Jrc2hvcC9sZXNzb25zLzAyX0RHRV9jb3VudF9ub3JtYWxpemF0aW9uLmh0bWwpCgojIyBEYXRhIGhhbmRsaW5nCgpXZSBub3JtYWxpc2UgdGhlIGNvdW50IG1hdHJpeC4gV2UgZGlzcGxheSB0aGUgdG9wIGxlZnQgY29ybmVyIG9mIHRoaXMgbWF0cml4LCBtZWFuaW5nIDUgZ2VuZXMgYW5kIDUgc2FtcGxlcy4gVW5saWtlIGNvdW50IGRhdGEsIG5vcm1hbGlzZWQgY291bnQgdmFsdWVzIGFyZSBub3QgbmVjZXNzYXJpbHkgaW50ZWdlcnMuCgpgYGB7ciBlc3RpbWF0ZVNpemVGYWN0b3JzfQpkZHMgPSBERVNlcTI6OmVzdGltYXRlU2l6ZUZhY3RvcnMoZGRzLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdHlwZSA9IHNpemVGYWN0b3JUeXBlKQoKREVTZXEyOjpjb3VudHMoZGRzLCBub3JtYWxpemVkID0gVFJVRSlbYygxOjUpLCBjKDE6NSldICU+JQogIGtuaXRyOjprYWJsZSgiaHRtbCIpICU+JQogIGthYmxlRXh0cmE6OmthYmxlX3N0eWxpbmcoZnVsbF93aWR0aCA9IEZBTFNFKQpgYGAKCldlIHNhdmUgdGhlIG5vcm1hbGlzZWQgY291bnQgbWF0cml4IGFzIGEgVFNWIGZpbGUuCgpgYGB7ciBzYXZlX25vcm1fY291bnRfbWF0cml4fQpmaWxlbmFtZSA9IHBhc3RlMChwcmVmaXgsIHByb2plY3ROYW1lLAogICAgICAgICAgICAgICAgICAiLW5vcm1hbGlzYXRpb25fbm9ybWFsaXNlZENvdW50TWF0cml4LnRzdiIpCgpzYXZlVGFibGUoREVTZXEyOjpjb3VudHMoZGRzLCBub3JtYWxpemVkID0gVFJVRSksCiAgICAgICAgICBmaWxlTmFtZSA9IGZpbGVuYW1lKQpgYGAKCk91dHB1dCBmaWxlIG5hbWU6PGJyPipgciBmaWxlbmFtZWAqCgojIyBWaXN1YWxpc2F0aW9uIHsjbm9ybWFsaXNhdGlvbl92aXN1fQoKIyMjIE5vcm1hbGlzZWQgY291bnRzIGRlbmRyb2dyYW0KCldlIGNvbXB1dGUgZXVjbGlkZWFuIGRpc3RhbmNlcyBiZXR3ZWVuIGVhY2ggcGFpciBvZiBzYW1wbGVzLCBjb25zaWRlcmluZyB0aGUgbm9ybWFsaXNlZCBjb3VudCB2YWx1ZXMuCgpgYGB7ciBkZW5kcm9ncmFtX3Bvb2xlZF9ub3JtYWxpc2VkX2NvdW50X21hdHJpeH0KZGRzU3RhYmlsaXplZCA9IGRkcyAlPiUKICBERVNlcTI6OnZhcmlhbmNlU3RhYmlsaXppbmdUcmFuc2Zvcm1hdGlvbigpICU+JQogIFN1bW1hcml6ZWRFeHBlcmltZW50Ojphc3NheSgpCmRpc3QubWF0ID0gZGlzdCh0KGRkc1N0YWJpbGl6ZWQpKQpoYyA9IHN0YXRzOjpoY2x1c3QoZGlzdC5tYXQpCmhjZGF0YSA9IGdnZGVuZHJvOjpkZW5kcm9fZGF0YShoYywgdHlwZSA9ICJyZWN0YW5nbGUiKQoKIyBBZGQgZ3JvdXBpbmcgZm9yIGNvbG9ycwpoY2RhdGEkbGFiZWxzID0gZHBseXI6OmxlZnRfam9pbigKICB4ID0gaGNkYXRhJGxhYmVscywKICB5ID0gdW5pcXVlKGRlc2lnblssIGMoIlJlcFRlY2hHcm91cCIsICJDb25kaXRpb24iKV0pLAogIGJ5ID0gYygibGFiZWwiID0gIlJlcFRlY2hHcm91cCIpKQpgYGAKCldlIHZpc3VhbGlzZSB0aGVzZSBkaXN0YW5jZXMgdGhyb3VnaCBhIGRlbmRyb2dyYW0uIEl0IGluZm9ybXMgd2hldGhlciB0aGUgc2FtcGxlcyBzaGFyZSBhIHNpbWlsYXIgdHJhbnNjcmlwdG9taWMgcHJvZmlsZSBwZXIgY29uZGl0aW9uIG9yIG5vdC4KCmBgYHtyIGZpZy1ub3JtYWxpc2F0aW9uX3Bvb2xlZF9jbHVzdGVyaW5nLCBmaWcud2lkdGggPSAxMCwgZmlnLmhlaWdodCA9IDZ9CnBsb3REZW5kcm9ncmFtKAogIGhjX2RhdGEsCiAgZmlnX3RpdGxlID0gcGFzdGUwKCJEZW5kcm9ncmFtIG9uIHRoZSBub3JtYWxpc2VkIGNvdW50IG1hdHJpeCAtICIsIHByb2plY3ROYW1lKSkKYGBgCgojIyMgTm9ybWFsaXNlZCBjb3VudHMgUENBCgpXZSBwZXJmb3JtIGEgUENBIGZvciBhbGwgc2FtcGxlcyB1c2luZyB0aGUgbm9ybWFsaXNlZCBjb3VudCBtYXRyaXguCgpgYGB7ciBydW5fcGNhMn0KcGNhQ291bnQgPSBkZHMgJT4lCiAgREVTZXEyOjpjb3VudHMoLiwgbm9ybWFsaXplZCA9IFRSVUUpICU+JQogIHQoKSAlPiUKICBGYWN0b01pbmVSOjpQQ0EoLiwgZ3JhcGggPSBGQUxTRSkKYGBgCgpXZSB2aXN1YWxpc2UgdGhlIHNhbXBsZXMgb24gdGhlIHR3by1kaW1lbnNpb25hbCBwcm9qZWN0aW9uLiBUaGUgY29vcmRpbmF0ZXMgb2YgZWFjaCBzYW1wbGUgZGVwZW5kIG9uIHRob3NlIG9mIHRoZSBvdGhlciBzYW1wbGVzLiBUaGlzIHByb2plY3Rpb24gY2FwdHVyZXMgKipgciByb3VuZChwY2FDb3VudCRlaWdbMiwgM10sIDIpYCoqJSBvZiB0aGUgdG90YWwgdmFyaWFiaWxpdHkgYmV0d2VlbiBzYW1wbGVzLiBUaGVyZWZvcmUsIGl0IGxhY2tzICoqYHIgMTAwLXJvdW5kKHBjYUNvdW50JGVpZ1syLCAzXSwgMilgKiolIG9mIHRoZSBpbml0aWFsIGluZm9ybWF0aW9uLiBJbiB0aGlzIHJlcHJlc2VudGF0aW9uLCB0d28gcG9pbnRzIChzYW1wbGVzKSBhcmUgY2xvc2UgdG9nZXRoZXIgaWYgdGhleSBoYXZlIHNpbWlsYXIgZ2xvYmFsIHRyYW5zY3JpcHRvbWljIHByb2ZpbGVzLiBXZSBleHBlY3Qgc2FtcGxlcyBmcm9tIHRoZSBzYW1lIGJpb2xvZ2ljYWwgY29uZGl0aW9uIHRvIGJlIGNsb3NlciB0byBlYWNoIG90aGVyIHRoYW4gdG8gc2FtcGxlcyBmcm9tIG90aGVyIGNvbmRpdGlvbnMuCgpgYGB7ciBmaWctbm9ybWFsaXNhdGlvbl9wb29sZWRfUENBLCBmaWcud2lkdGggPSAxMCwgZmlnLmhlaWdodCA9IDZ9CnBjYUNvdW50JGluZCRjb29yZCAlPiUKICBhcy5kYXRhLmZyYW1lKCkgJT4lCiAgZHBseXI6Om11dGF0ZShzYW1wbGUgPSByb3duYW1lcyguKSkgJT4lCiAgZHBseXI6OmxlZnRfam9pbih4ID0gLiwKICAgICAgICAgICAgICAgICAgIHkgPSB1bmlxdWUoZGVzaWduWywgYygiUmVwVGVjaEdyb3VwIiwgIkNvbmRpdGlvbiIpXSksCiAgICAgICAgICAgICAgICAgICBieSA9IGMoInNhbXBsZSIgPSAiUmVwVGVjaEdyb3VwIikpICU+JQogIHBsb3RQQ0EoLiwKICAgICAgICAgIGZpZ190aXRsZSA9ICBwYXN0ZTAoIlBDQSBvZiB0aGUgbm9ybWFsaXNlZCBjb3VudCBtYXRyaXggLSAiLCBwcm9qZWN0TmFtZSkpCmBgYAoKIyMjIE5vcm1hbGlzZWQgY291bnRzIGJveHBsb3QKClRoaXMgZmlndXJlIGRpc3BsYXlzLCBhcyBhIGJveHBsb3QsIHRoZSBudW1iZXIgb2Ygbm9ybWFsaXNlZCBjb3VudHMgZm9yIGVhY2ggZ2VuZSwgcGVyIHNhbXBsZS4KCmBgYHtyIGZpZy1ub3JtYWxpc2F0aW9uX25vcm1hbGlzZWRfYm94cGxvdF9jb3VudCwgZmlnLndpZHRoID0gMTAsIGZpZy5oZWlnaHQgPSA2fQpsb2cyKERFU2VxMjo6Y291bnRzKGRkcywgbm9ybWFsaXplZCA9IFRSVUUpKzEpICU+JQogIHJlc2hhcGUyOjptZWx0KCkgJT4lCiAgYGNvbG5hbWVzPC1gKGMoImdlbmVfSUQiLCAic2FtcGxlIiwgImxvZzJfY291bnRzIikpICU+JQogIGRwbHlyOjptdXRhdGUoc2FtcGxlID0gZmFjdG9yKHNhbXBsZSwgbGV2ZWxzID0gbGV2ZWxzKGRlc2lnbiRSZXBUZWNoR3JvdXApKSkgJT4lCiAgZHBseXI6OmxlZnRfam9pbih4ID0gLiwKICAgICAgICAgICAgICAgICAgIHkgPSB1bmlxdWUoZGVzaWduWywgYygiUmVwVGVjaEdyb3VwIiwgIkNvbmRpdGlvbiIpXSksCiAgICAgICAgICAgICAgICAgICBieSA9IGMoInNhbXBsZSIgPSAiUmVwVGVjaEdyb3VwIikpICU+JQogICMgUGxvdAogIHBsb3RCb3hwbG90KAogICAgLiwKICAgIHlfY29sdW1uID0gImxvZzJfY291bnRzIiwKICAgIHlfdGl0bGUgPSAibG9nXzIgKGNvdW50cysxKSIsCiAgICBmaWdfdGl0bGUgPSBwYXN0ZTAoIk5vcm1hbGlzZWQgY291bnRzIGRpc3RyaWJ1dGlvbiBwZXIgc2FtcGxlIC0gIiwgcHJvamVjdE5hbWUpKQpgYGAKCiMjIyBNb3N0IGV4cHJlc3NlZCBnZW5lcyBwbG90CgpXZSBpZGVudGlmaWVkIHRoZSBtb3N0IGV4cHJlc3NlZCBnZW5lIGluIGVhY2ggc2FtcGxlLgoKYGBge3IgaWRlbnRpZnlfbW9zdF9leHByZXNzZWRfZ2VuZXN9CiMgcHJlcGFyYXRpb24gb2YgMiBkYXRhIGZyYW1lIHdpdGggdGhlIHNhbWUgbnVtYmVyIG9mIGNvbHVtbiB0aGFuCiMgdGhlIGRkcyBjb3VudCBtYXRyaXgKbWF4Q291bnRzID0gREVTZXEyOjpjb3VudHMoZGRzKVsxLF0KdHJhbnNjcmlwdE5hbWVzID0gREVTZXEyOjpjb3VudHMoZGRzKVsxLF0KCiMgZm9yIGVhY2ggc2FtcGxlIChjb2x1bW4pCmZvciAoaSBpbiAxOm5jb2woREVTZXEyOjpjb3VudHMoZGRzKSkpIHsKICAKICAjIHNlbGVjdGlvbiBvZiB0aGUgbWF4aW11bSBudW1iZXIgb2YgY291bnQKICBtYXhDb3VudHNbaV0gPSAobWF4KERFU2VxMjo6Y291bnRzKGRkcywgbm9ybWFsaXplZCA9IFRSVUUpWyxpXSkvCiAgICAgICAgICAgICAgICAgICAgc3VtKERFU2VxMjo6Y291bnRzKGRkcywgbm9ybWFsaXplZCA9IFRSVUUpWyxpXSkpKjEwMAogIAogICMgc2VsZWN0aW9uIG9mIHRoZSBuYW1lIG9mIHRoZSBnZW5lcyB0aGlzIHRoZSBtYXhpbXVtIG9mIGNvdW50CiAgdHJhbnNjcmlwdE5hbWVzW2ldID0gcm93Lm5hbWVzKAogICAgc3Vic2V0KERFU2VxMjo6Y291bnRzKGRkcywgbm9ybWFsaXplZCA9IFRSVUUpLAogICAgICAgICAgIERFU2VxMjo6Y291bnRzKGRkcywgbm9ybWFsaXplZCA9IFRSVUUpWyxpXSA9PSAKICAgICAgICAgICAgIG1heChERVNlcTI6OmNvdW50cyhkZHMsIG5vcm1hbGl6ZWQgPSBUUlVFKVssaV0pKSkKfQpgYGAKCldlIHJlcHJlc2VudCB0aGUgaW5mb3JtYXRpb24gYXMgYSBiYXJwbG90LgoKYGBge3IgZmlnLW5vcm1hbGlzYXRpb25fbW9zdF9leHByZXNzZWRfZ2VuZXMsIGZpZy53aWR0aCA9IDEwLCBmaWcuaGVpZ2h0ID0gMituY29sKGNvdW50X21hdCkvNH0KZHBseXI6OmxlZnRfam9pbih4ID0gZGF0YS5mcmFtZShzYW1wbGUgPSBuYW1lcyhtYXhDb3VudHMpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1heF9jb3VudHMgPSBtYXhDb3VudHMpLAogICAgICAgICAgICAgICAgIHkgPSBkYXRhLmZyYW1lKHNhbXBsZSA9IG5hbWVzKHRyYW5zY3JpcHROYW1lcyksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZ2VuZV9pZCA9IHRyYW5zY3JpcHROYW1lcyksCiAgICAgICAgICAgICAgICAgYnkgPSAic2FtcGxlIikgJT4lCiAgZHBseXI6Om11dGF0ZShzYW1wbGUgPSBmYWN0b3Ioc2FtcGxlLCBsZXZlbHMgPSBsZXZlbHMoZGVzaWduJFJlcFRlY2hHcm91cCkpKSAlPiUKICBkcGx5cjo6bGVmdF9qb2luKHggPSAuLAogICAgICAgICAgICAgICAgICAgeSA9IHVuaXF1ZShkZXNpZ25bLCBjKCJSZXBUZWNoR3JvdXAiLCAiQ29uZGl0aW9uIildKSwKICAgICAgICAgICAgICAgICAgIGJ5ID0gYygic2FtcGxlIiA9ICJSZXBUZWNoR3JvdXAiKSkgJT4lCiAgIyBQbG90CiAgZ2dwbG90Mjo6Z2dwbG90KC4sIGFlcyh4ID0gc2FtcGxlLCB5ID0gbWF4X2NvdW50cywKICAgICAgICAgICAgICAgICAgICAgICAgIGZpbGwgPSBDb25kaXRpb24sIGxhYmVsID0gZ2VuZV9pZCkpICsKICBnZ3Bsb3QyOjpnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IiwgY29sID0gImJsYWNrIikgKwogIGdncGxvdDI6Omdlb21fbGFiZWwobWFwcGluZyA9IGFlcyh4ID0gc2FtcGxlLCB5ID0gMCksCiAgICAgICAgICAgICAgICAgICAgICBsaW5ld2lkdGggPSAwLCBmaWxsID0gTkEsIGhqdXN0ID0gMCkgKwogIGdncGxvdDI6OnNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGNvbG9yc19jb25kaXRpb24sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYnJlYWtzID0gbmFtZXMoY29sb3JzX2NvbmRpdGlvbikpICsKICAjIFN0eWxlCiAgZ2dwbG90Mjo6bGFicyh5ID0gIlByb3BvcnRpb24gb2YgcmVhZHMgKCUpIiwKICAgICAgICAgICAgICAgIHRpdGxlID0gcGFzdGUwKCJNb3N0IGV4cHJlc3NlZCBnZW5lcyAtICIsIHByb2plY3ROYW1lKSkgKwogIGdncGxvdDI6OnNjYWxlX3lfY29udGludW91cyhleHBhbmQgPSBjKDAsMCkpICsKICBnZ3Bsb3QyOjpjb29yZF9mbGlwKCkgKwogIGdncGxvdDI6OnRoZW1lX2NsYXNzaWMoKSArCiAgZ2dwbG90Mjo6dGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSksCiAgICAgICAgICAgICAgICAgYXhpcy50aXRsZS55ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgICAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIGhqdXN0ID0gMSksCiAgICAgICAgICAgICAgICAgIyBUcmFuc3BhcmVudCBiYWNrZ3JvdW5kCiAgICAgICAgICAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGdncGxvdDI6OmVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICAgICAgICBwbG90LmJhY2tncm91bmQgPSBnZ3Bsb3QyOjplbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgICAgbGVnZW5kLmJhY2tncm91bmQgPSBnZ3Bsb3QyOjplbGVtZW50X2JsYW5rKCkpCmBgYAoKCjwhLS0gQ29uZGl0aW9uYWwgZXhlY3V0aW9uIGluIGNhc2UgZGlmZmVyZW50aWFsIGV4cHJlc3Npb24gYW5hbHlzaXMgc2hvdWxkIGJlIGRvbmUgLS0+CgpgYGB7ciBkaWZmYW5hLCBlY2hvID0gRkFMU0UsIHJlc3VsdHMgPSAnYXNpcycsIGV2YWwgPSBkaWZmYW5hVGVzdH0KcmVzID0ga25pdHI6OmtuaXRfY2hpbGQoCiAgaW5wdXQgPSAnMDNfY2hpbGRfZGlmZmVyZW50aWFsX2V4cHJlc3Npb24uUm1kJywKICBxdWlldCA9IFRSVUUpCgpjYXQocmVzLCBzZXAgPSAnXG4nKQpgYGAKCgojIFZlcnNpb25zCgpgYGB7ciBzZXNzaW9uaW5mbywgZWNobyA9IEZBTFNFLCBmb2xkX291dHB1dCA9IFRSVUUsIHJlc3VsdHMgPSAiaG9sZCJ9CnByaW50KCJQYWNrYWdlcyBsb2NhdGlvbjoiKQoubGliUGF0aHMoKQpwcmludCgiIikKcHJpbnQoIlNlc3Npb24gSW5mb3JtYXRpb246IikKc2Vzc2lvbkluZm8oKQpgYGAKCiMgUmVmZXJlbmNlCgpUaGUgZGlmZmVyZW50aWFsIGV4cHJlc3Npb24gYW5hbHlzaXMgdXNlcyB0aGUgYERFU2VxMmAgUiBwYWNrYWdlIChbdmlnbmV0dGVdKGh0dHBzOi8vYmlvY29uZHVjdG9yLm9yZy9wYWNrYWdlcy9kZXZlbC9iaW9jL3ZpZ25ldHRlcy9ERVNlcTIvaW5zdC9kb2MvREVTZXEyLmh0bWwpKS4KCj4gTG92ZSwgTS5JLiwgSHViZXIsIFcuICYgQW5kZXJzLCBTLiwgTW9kZXJhdGVkIGVzdGltYXRpb24gb2YgZm9sZCBjaGFuZ2UgYW5kIGRpc3BlcnNpb24gZm9yIFJOQS1zZXEgZGF0YSB3aXRoIERFU2VxMi4gR2Vub21lIEJpb2wgMTUsIDU1MCAoMjAxNCkuIERPSTogWzEwLjExODYvczEzMDU5LTAxNC0wNTUwLThdKGh0dHBzOi8vZG9pLm9yZy8xMC4xMTg2L3MxMzA1OS0wMTQtMDU1MC04KQo=