Objectives

This notebook will demonstrate how to:

  • Load and use R packages
  • Read in and perform simple manipulations of data frames
  • Use ggplot2 to plot and visualize data
  • Customize plots using features of ggplot2

We’ll use a real gene expression dataset to get comfortable making visualizations using ggplot2. We’ve performed differential expression analyses on a pre-processed astrocytoma microarray dataset. We’ll start by making a volcano plot of differential gene expression results from this experiment. We performed three sets of contrasts:

  1. sex category contrasting: Male vs Female
  2. tissue category contrasting : Pilocytic astrocytoma tumor samples vs normal cerebellum samples
  3. An interaction of both sex and tissue.

More ggplot2 resources:

Set Up

We saved these results to a tab separated values (TSV) file called gene_results_GSE44971.tsv. It’s been saved to the data folder. File paths are relative to where this notebook file (.Rmd) is saved. So we can reference it later, let’s make a variable with our data directory name.

data_dir <- "data"

Let’s declare our output folder name as its own variable.

plots_dir <- "plots"

We can also create a directory if it doesn’t already exist.

There’s a couple ways that we can create that directory from within R. One way is to use the base R function dir.create(), which (as the name suggests) will create a directory. But this function assumes that the directory does not yet exist, and it will throw an error if you try to create a directory that already exists. To avoid this error, we can place the directory creation inside an if statement, so the code will only run if the directory does not yet exist:

# The if statement here tests whether the plot directory exists and
# only executes the expressions between the braces if it does not.
if (!dir.exists(plots_dir)) {
  dir.create(plots_dir)
}

In this notebook we will be using functions from the Tidyverse set of packages, so we need to load in those functions using library(). We could load the individual packages we need one at a time, but it is convenient for now to load them all with the tidyverse “package,” which groups many of them together as a shortcut. Keep a look out for where we tell you which individual package different functions come from.

library(tidyverse)
── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
✔ dplyr     1.1.4     ✔ readr     2.1.5
✔ forcats   1.0.0     ✔ stringr   1.5.1
✔ ggplot2   3.5.1     ✔ tibble    3.2.1
✔ lubridate 1.9.3     ✔ tidyr     1.3.1
✔ purrr     1.0.2     
── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag()    masks stats::lag()
ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors

Read in the differential expression analysis results file

Here we are using a tidyverse function read_tsv() from the readr package. Like we did in the previous notebook, we will store the resulting data frame as stats_df.

# read in the file `gene_results_GSE44971.tsv` from the data directory
stats_df <- read_tsv(file.path(
  data_dir,
  "gene_results_GSE44971.tsv"
))
Rows: 6804 Columns: 8
── Column specification ────────────────────────────────────────────────────────
Delimiter: "\t"
chr (3): ensembl_id, gene_symbol, contrast
dbl (5): log_fold_change, avg_expression, t_statistic, p_value, adj_p_value

ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.

We can take a look at a column individually by using a $. Note we are using head() so the whole thing doesn’t print out.

head(stats_df$contrast)
[1] "male_female" "male_female" "male_female" "male_female" "male_female"
[6] "male_female"
male_female
male_female
male_female
male_female
male_female
male_female

If we want to see a specific set of values, we can use brackets with the indices of the values we’d like returned.

stats_df$avg_expression[6:10]
[1] 19.084011  8.453933  5.116563  6.345609 25.473133

Let’s look at some basic statistics from the data set using summary()

# summary of stats_df
summary(stats_df)
  ensembl_id        gene_symbol          contrast         log_fold_change    
 Length:6804        Length:6804        Length:6804        Min.   :-180.8118  
 Class :character   Class :character   Class :character   1st Qu.:  -1.6703  
 Mode  :character   Mode  :character   Mode  :character   Median :   0.1500  
                                                          Mean   :   0.2608  
                                                          3rd Qu.:   2.1049  
                                                          Max.   : 129.3009  
 avg_expression     t_statistic           p_value         adj_p_value     
 Min.   :  5.003   Min.   :-32.84581   Min.   :0.00000   Min.   :0.00000  
 1st Qu.:  6.304   1st Qu.: -1.16444   1st Qu.:0.01309   1st Qu.:0.05657  
 Median :  8.482   Median :  0.10619   Median :0.18919   Median :0.41354  
 Mean   : 13.847   Mean   : -0.00819   Mean   :0.31223   Mean   :0.44833  
 3rd Qu.: 14.022   3rd Qu.:  1.46589   3rd Qu.:0.57634   3rd Qu.:0.82067  
 Max.   :190.708   Max.   : 10.48302   Max.   :0.99979   Max.   :0.99988  

The statistics for contrast are not very informative, so let’s do that again with just the contrast column after converting it to a factor

# summary of `stats_df$contrast` as a factor
summary(as.factor(stats_df$contrast))
astrocytoma_normal        interaction        male_female 
              2268               2268               2268 

Set up the dataset

Before we make our plot, we want to calculate a set of new values for each row; transformations of the raw statistics in our table. To do this we will use a function from the dplyr package called mutate() to make a new column of -log10 p values.

# add a `neg_log10_p` column to the data frame
stats_df <- mutate(stats_df, # data frame we'd like to add a variable to
  neg_log10_p = -log10(p_value) # column name and values
)

Let’s filter to only male_female contrast data. First let’s try out a logical expression:

stats_df$contrast == "male_female"

Now we can try out the filter() function. Notice that we are not assigning the results to a variable, so this filtered dataset will not be saved to the environment.

# filter stats_df to "male_female" only
filter(stats_df, contrast == "male_female")

Now we can assign the results to a new data frame: male_female_df.

# filter and save to male_female_df
male_female_df <- filter(stats_df, contrast == "male_female")

Plotting this data

Let’s make a volcano plot with this data. First let’s take a look at only the tumor vs. normal comparison. Let’s save this as a separate data frame by assigning it a new name.

tumor_normal_df <- filter(stats_df, contrast == "astrocytoma_normal")

To make this plot we will be using functions from the ggplot2 package, the main plotting package of the tidyverse. We use the first function, ggplot() to define the data that will be plotted. Remember, the name of this package is ggplot2, but the function we use is called ggplot() without the 2. ggplot() takes two main arguments:

  1. data, which is the data frame that contains the data we want to plot.
  2. mapping, which is a special list made with the aes() function to describe which values will be used for each aesthetic component of the plot, such as the x and y coordinates of each point. (If you find calling things like the x and y coordinates “aesthetics” confusing, don’t worry, you are not alone.) Specifically, the aes() function is used to specify that a given column (variable) in your data frame be mapped to a given aesthetic component of the plot.
ggplot(
  tumor_normal_df, # This first argument is the data frame with the data we want to plot
  aes(
    x = log_fold_change, # This is the column name of the values we want to use
    # for the x coordinates
    y = neg_log10_p
  ) # This is the column name of the data we want for the y-axis
)

You’ll notice this plot doesn’t have anything on it because we haven’t specified a plot type yet. To do that, we will add another ggplot layer with + which will specify exactly what we want to plot. A volcano plot is a special kind of scatter plot, so to make that we will want to plot individual points, which we can do with geom_point().

# This first part is the same as before
ggplot(
  tumor_normal_df,
  aes(
    x = log_fold_change,
    y = neg_log10_p
  )
) +
  # Now we are adding on a layer to specify what kind of plot we want
  geom_point()

Here’s a brief summary of ggplot2 structure. ggplot2 structure

Adjust our ggplot

Now that we have a base plot that shows our data, we can add layers on to it and adjust it. We can adjust the color of points using the color aesthetic.

ggplot(
  tumor_normal_df,
  aes(
    x = log_fold_change,
    y = neg_log10_p,
    color = avg_expression
  ) # We added this argument to color code the points!
) +
  geom_point()

Because we have so many points overlapping one another, we will want to adjust the transparency, which we can do with an alpha argument.

ggplot(
  tumor_normal_df,
  aes(
    x = log_fold_change,
    y = neg_log10_p,
    color = avg_expression
  )
) +
  geom_point(alpha = 0.2) # We are using the `alpha` argument to make our points transparent

Notice that we added the alpha within the geom_point() function, not to the aes(). We did this because we want all of the points to have the same level of transparency, and it will not vary depending on any variable in the data. We can also change the background and appearance of the plot as a whole by adding a theme.

ggplot(
  tumor_normal_df,
  aes(
    x = log_fold_change,
    y = neg_log10_p,
    color = avg_expression
  )
) +
  geom_point(alpha = 0.2) +
  # Add on this set of appearance presets to make it pretty
  theme_bw() 

We are not limited to a single plotting layer. For example, if we want to add a horizontal line to indicate a significance cutoff, we can do that with geom_hline(). For now, we will choose the value of 5.5 (that is close to a Bonferroni correction) and add that to the plot.

ggplot(
  tumor_normal_df,
  aes(
    x = log_fold_change,
    y = neg_log10_p,
    color = avg_expression
  )
) +
  geom_point(alpha = 0.2) +
  geom_hline(yintercept = 5.5, color = "darkgreen") # we can specify colors by names here

We can change the x and y labels using a few different strategies. One approach is to use functions xlab() and ylab() individually to set, respectively, the x-axis label and the the y-axis label.

ggplot(
  tumor_normal_df,
  aes(
    x = log_fold_change,
    y = neg_log10_p,
    color = avg_expression
  )
) +
  geom_point(alpha = 0.2) +
  geom_hline(yintercept = 5.5, color = "darkgreen") +
  theme_bw() +
  # Add labels with separate functions:
  xlab("log2 Fold Change Tumor/Normal") +
  ylab("-log10 p value")

Alternatively, we can use the ggplot2 function labs(), which takes individual arguments for each label we want want to set. We can also include the argument title to add an overall plot title.

ggplot(
  tumor_normal_df,
  aes(
    x = log_fold_change,
    y = neg_log10_p,
    color = avg_expression
  )
) +
  geom_point(alpha = 0.2) +
  geom_hline(yintercept = 5.5, color = "darkgreen") +
  theme_bw() +
  # Add x and y labels and overall plot title with arguments to labs():
  labs(
    x = "log2 Fold Change Tumor/Normal",
    y = "-log10 p value",
    title = "Astrocytoma Tumor vs Normal Cerebellum"
  )

Something great about the labs() function is you can also use it to specify labels for your legends derived from certain aesthetics. In this plot, our legend is derived from a color aesthetic, so we can specify the keyword “color” to update the legend title.

ggplot(
  tumor_normal_df,
  aes(
    x = log_fold_change,
    y = neg_log10_p,
    color = avg_expression
  )
) +
  geom_point(alpha = 0.2) +
  geom_hline(yintercept = 5.5, color = "darkgreen") +
  theme_bw() +
  # Add x and y labels and overall plot title (and more!) with arguments to labs():
  labs(
    x = "log2 Fold Change Tumor/Normal",
    y = "-log10 p value",
    title = "Astrocytoma Tumor vs Normal Cerebellum",
    # Use the color keyword to label the color legend
    color = "Average expression"
  )

Use this chunk to make the same kind of plot as the previous chunk but instead plot the male female contrast data, that is stored in male_female_df.

# Use this chunk to make the same kind of volcano plot, but with the male-female contrast data.
ggplot(
  male_female_df,
  aes(
    x = log_fold_change,
    y = neg_log10_p,
    color = avg_expression
  )
) +
  geom_point(alpha = 0.2) +
  geom_hline(yintercept = 5.5, color = "darkgreen") +
  theme_bw() +
  labs(
    x = "log2 Fold Change Male/Female",
    y = "-log10 p value",
    color = "Average expression"
  )

Turns out, we don’t have to plot each contrast separately, instead, we can use the original data frame that contains all three contrasts’ data, stats_df, and add a facet_wrap to make each contrast its own plot.

ggplot(
  stats_df, # Switch to the bigger data frame with all three contrasts' data
  aes(
    x = log_fold_change,
    y = neg_log10_p,
    color = avg_expression
  )
) +
  geom_point(alpha = 0.2) +
  geom_hline(yintercept = 5.5, color = "darkgreen") +
  theme_bw() +
  facet_wrap(vars(contrast)) +
  labs(
    # Now that this includes the other contrasts,
    # we'll make the x-axis label more general
    x  = "log2 Fold Change", 
    y = "-log10 p value",
    color = "Average expression"
  ) +
  coord_cartesian(xlim = c(-25, 25)) # zoom in on the x-axis

We can store the plot as an object in the global environment by using <- operator. Here we will call this volcano_plot.

# We are saving this plot to a variable named `volcano_plot`
volcano_plot <- ggplot(
  stats_df, 
  aes(
    x = log_fold_change,
    y = neg_log10_p,
    color = avg_expression
  )
) +
  geom_point(alpha = 0.2) +
  geom_hline(yintercept = 5.5, color = "darkgreen") +
  theme_bw() +
  facet_wrap(vars(contrast)) +
  labs(
    x = "log2 Fold Change",
    y = "-log10 p value",
    color = "Average expression"
  ) +
  coord_cartesian(xlim = c(-25, 25))

When we are happy with our plot, we can save the plot using ggsave. It’s a good idea to also specify width and height arguments (units in inches) to ensure the saved plot is always the same size every time you run this code. Here, we’ll save a 6”x6” plot.

ggsave(
  plot = volcano_plot,
  filename = file.path(plots_dir, "volcano_plot.png"),
  width = 6,
  height = 6
)

Session Info

# Print out the versions and packages we are using in this session
sessionInfo()
R version 4.4.1 (2024-06-14)
Platform: x86_64-pc-linux-gnu
Running under: Ubuntu 22.04.4 LTS

Matrix products: default
BLAS:   /usr/lib/x86_64-linux-gnu/openblas-pthread/libblas.so.3 
LAPACK: /usr/lib/x86_64-linux-gnu/openblas-pthread/libopenblasp-r0.3.20.so;  LAPACK version 3.10.0

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

time zone: Etc/UTC
tzcode source: system (glibc)

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

other attached packages:
 [1] lubridate_1.9.3 forcats_1.0.0   stringr_1.5.1   dplyr_1.1.4    
 [5] purrr_1.0.2     readr_2.1.5     tidyr_1.3.1     tibble_3.2.1   
 [9] ggplot2_3.5.1   tidyverse_2.0.0 optparse_1.7.5 

loaded via a namespace (and not attached):
 [1] sass_0.4.9        utf8_1.2.4        generics_0.1.3    stringi_1.8.3    
 [5] hms_1.1.3         digest_0.6.35     magrittr_2.0.3    evaluate_0.23    
 [9] grid_4.4.1        timechange_0.3.0  fastmap_1.1.1     jsonlite_1.8.8   
[13] fansi_1.0.6       scales_1.3.0      textshaping_0.3.7 getopt_1.20.4    
[17] jquerylib_0.1.4   cli_3.6.2         crayon_1.5.2      rlang_1.1.3      
[21] bit64_4.0.5       munsell_0.5.1     withr_3.0.0       cachem_1.0.8     
[25] yaml_2.3.8        parallel_4.4.1    tools_4.4.1       tzdb_0.4.0       
[29] colorspace_2.1-0  vctrs_0.6.5       R6_2.5.1          lifecycle_1.0.4  
[33] bit_4.0.5         vroom_1.6.5       ragg_1.3.0        pkgconfig_2.0.3  
[37] pillar_1.9.0      bslib_0.7.0       gtable_0.3.5      glue_1.7.0       
[41] systemfonts_1.0.6 highr_0.10        xfun_0.43         tidyselect_1.2.1 
[45] knitr_1.46        farver_2.1.1      htmltools_0.5.8.1 labeling_0.4.3   
[49] rmarkdown_2.26    compiler_4.4.1   
LS0tCnRpdGxlOiAiSW50cm9kdWN0aW9uIHRvIGdncGxvdDIiCmF1dGhvcjogIkNDREwgZm9yIEFMU0YiCmRhdGU6IDIwMjEKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6CiAgICB0b2M6IHRydWUKICAgIHRvY19mbG9hdDogdHJ1ZQotLS0KCgojIyBPYmplY3RpdmVzCgpUaGlzIG5vdGVib29rIHdpbGwgZGVtb25zdHJhdGUgaG93IHRvOgoKLSBMb2FkIGFuZCB1c2UgUiBwYWNrYWdlcwotIFJlYWQgaW4gYW5kIHBlcmZvcm0gc2ltcGxlIG1hbmlwdWxhdGlvbnMgb2YgZGF0YSBmcmFtZXMKLSBVc2UgYGdncGxvdDJgIHRvIHBsb3QgYW5kIHZpc3VhbGl6ZSBkYXRhCi0gQ3VzdG9taXplIHBsb3RzIHVzaW5nIGZlYXR1cmVzIG9mIGBnZ3Bsb3QyYAoKLS0tCgpXZSdsbCB1c2UgYSByZWFsIGdlbmUgZXhwcmVzc2lvbiBkYXRhc2V0IHRvIGdldCBjb21mb3J0YWJsZSBtYWtpbmcgdmlzdWFsaXphdGlvbnMgdXNpbmcgZ2dwbG90Mi4KV2UndmUgW3BlcmZvcm1lZCBkaWZmZXJlbnRpYWwgZXhwcmVzc2lvbiBhbmFseXNlc10oLi9zY3JpcHRzLzAwLXNldHVwLWludHJvLXRvLVIuUikgb24gYSBwcmUtcHJvY2Vzc2VkIFthc3Ryb2N5dG9tYSBtaWNyb2FycmF5IGRhdGFzZXRdKGh0dHBzOi8vd3d3LnJlZmluZS5iaW8vZXhwZXJpbWVudHMvR1NFNDQ5NzEvZ2VuZS1leHByZXNzaW9uLWRhdGEtZnJvbS1waWxvY3l0aWMtYXN0cm9jeXRvbWEtdHVtb3VyLXNhbXBsZXMtYW5kLW5vcm1hbC1jZXJlYmVsbHVtLWNvbnRyb2xzKS4KV2UnbGwgc3RhcnQgYnkgbWFraW5nIGEgdm9sY2FubyBwbG90IG9mIGRpZmZlcmVudGlhbCBnZW5lIGV4cHJlc3Npb24gcmVzdWx0cyBmcm9tIHRoaXMgZXhwZXJpbWVudC4KV2UgcGVyZm9ybWVkIHRocmVlIHNldHMgb2YgY29udHJhc3RzOgoKMSkgYHNleGAgY2F0ZWdvcnkgY29udHJhc3Rpbmc6IGBNYWxlYCB2cyBgRmVtYWxlYAoyKSBgdGlzc3VlYCBjYXRlZ29yeSBjb250cmFzdGluZyA6IGBQaWxvY3l0aWMgYXN0cm9jeXRvbWEgdHVtb3JgIHNhbXBsZXMgdnMgYG5vcm1hbCBjZXJlYmVsbHVtYCBzYW1wbGVzCjMpIEFuIGludGVyYWN0aW9uIG9mIGJvdGggYHNleGAgYW5kIGB0aXNzdWVgLgoKKipNb3JlIGdncGxvdDIgcmVzb3VyY2VzOioqCgotIFtnZ3Bsb3QyIHdlYnNpdGVdKGh0dHBzOi8vZ2dwbG90Mi50aWR5dmVyc2Uub3JnLykKLSBbSGFuZHkgY2hlYXRzaGVldCBmb3IgZ2dwbG90MiAocGRmKV0oaHR0cHM6Ly9naXRodWIuY29tL3JzdHVkaW8vY2hlYXRzaGVldHMvcmF3L21haW4vZGF0YS12aXN1YWxpemF0aW9uLnBkZikKLSBbX0RhdGEgVmlzdWFsaXphdGlvbiwgQSBwcmFjdGljYWwgaW50cm9kdWN0aW9uX10oaHR0cHM6Ly9zb2N2aXouY28vKQotIFtEYXRhIHZpc3VhbGl6YXRpb24gY2hhcHRlciBvZiBfUiBmb3IgRGF0YSBTY2llbmNlX10oaHR0cHM6Ly9yNGRzLmhhZGxleS5uei9kYXRhLXZpc3VhbGl6ZS5odG1sKQotIFtnZ3Bsb3QyIG9ubGluZSB0dXRvcmlhbF0oaHR0cDovL3Itc3RhdGlzdGljcy5jby9Db21wbGV0ZS1HZ3Bsb3QyLVR1dG9yaWFsLVBhcnQxLVdpdGgtUi1Db2RlLmh0bWwpCgojIyBTZXQgVXAKCldlIHNhdmVkIHRoZXNlIHJlc3VsdHMgdG8gYSB0YWIgc2VwYXJhdGVkIHZhbHVlcyAoVFNWKSBmaWxlIGNhbGxlZCBgZ2VuZV9yZXN1bHRzX0dTRTQ0OTcxLnRzdmAuCkl0J3MgYmVlbiBzYXZlZCB0byB0aGUgYGRhdGFgIGZvbGRlci4KRmlsZSBwYXRocyBhcmUgcmVsYXRpdmUgdG8gd2hlcmUgdGhpcyBub3RlYm9vayBmaWxlICguUm1kKSBpcyBzYXZlZC4KU28gd2UgY2FuIHJlZmVyZW5jZSBpdCBsYXRlciwgbGV0J3MgbWFrZSBhIHZhcmlhYmxlIHdpdGggb3VyIGRhdGEgZGlyZWN0b3J5IG5hbWUuCgpgYGB7cn0KZGF0YV9kaXIgPC0gImRhdGEiCmBgYAoKTGV0J3MgZGVjbGFyZSBvdXIgb3V0cHV0IGZvbGRlciBuYW1lIGFzIGl0cyBvd24gdmFyaWFibGUuCgpgYGB7cn0KcGxvdHNfZGlyIDwtICJwbG90cyIKYGBgCgpXZSBjYW4gYWxzbyBjcmVhdGUgYSBkaXJlY3RvcnkgaWYgaXQgZG9lc24ndCBhbHJlYWR5IGV4aXN0LgoKVGhlcmUncyBhIGNvdXBsZSB3YXlzIHRoYXQgd2UgY2FuIGNyZWF0ZSB0aGF0IGRpcmVjdG9yeSBmcm9tIHdpdGhpbiBSLgpPbmUgd2F5IGlzIHRvIHVzZSB0aGUgYmFzZSBSIGZ1bmN0aW9uIGBkaXIuY3JlYXRlKClgLCB3aGljaCAoYXMgdGhlIG5hbWUgc3VnZ2VzdHMpIHdpbGwgY3JlYXRlIGEgZGlyZWN0b3J5LgpCdXQgdGhpcyBmdW5jdGlvbiBhc3N1bWVzIHRoYXQgdGhlIGRpcmVjdG9yeSBkb2VzIG5vdCB5ZXQgZXhpc3QsIGFuZCBpdCB3aWxsIHRocm93IGFuIGVycm9yIGlmIHlvdSB0cnkgdG8gY3JlYXRlIGEgZGlyZWN0b3J5IHRoYXQgYWxyZWFkeSBleGlzdHMuClRvIGF2b2lkIHRoaXMgZXJyb3IsIHdlIGNhbiBwbGFjZSB0aGUgZGlyZWN0b3J5IGNyZWF0aW9uIGluc2lkZSBhbiBgaWZgIHN0YXRlbWVudCwgc28gdGhlIGNvZGUgd2lsbCBvbmx5IHJ1biBpZiB0aGUgZGlyZWN0b3J5IGRvZXMgbm90IHlldCBleGlzdDoKCmBgYHtyIGNyZWF0ZWlmfQojIFRoZSBpZiBzdGF0ZW1lbnQgaGVyZSB0ZXN0cyB3aGV0aGVyIHRoZSBwbG90IGRpcmVjdG9yeSBleGlzdHMgYW5kCiMgb25seSBleGVjdXRlcyB0aGUgZXhwcmVzc2lvbnMgYmV0d2VlbiB0aGUgYnJhY2VzIGlmIGl0IGRvZXMgbm90LgppZiAoIWRpci5leGlzdHMocGxvdHNfZGlyKSkgewogIGRpci5jcmVhdGUocGxvdHNfZGlyKQp9CmBgYAoKSW4gdGhpcyBub3RlYm9vayB3ZSB3aWxsIGJlIHVzaW5nIGZ1bmN0aW9ucyBmcm9tIHRoZSBUaWR5dmVyc2Ugc2V0IG9mIHBhY2thZ2VzLCBzbyB3ZSBuZWVkIHRvIGxvYWQgaW4gdGhvc2UgZnVuY3Rpb25zIHVzaW5nIGBsaWJyYXJ5KClgLgpXZSBjb3VsZCBsb2FkIHRoZSBpbmRpdmlkdWFsIHBhY2thZ2VzIHdlIG5lZWQgb25lIGF0IGEgdGltZSwgYnV0IGl0IGlzIGNvbnZlbmllbnQgZm9yIG5vdyB0byBsb2FkIHRoZW0gYWxsIHdpdGggdGhlIGB0aWR5dmVyc2VgICJwYWNrYWdlLCIgd2hpY2ggZ3JvdXBzIG1hbnkgb2YgdGhlbSB0b2dldGhlciBhcyBhIHNob3J0Y3V0LgpLZWVwIGEgbG9vayBvdXQgZm9yIHdoZXJlIHdlIHRlbGwgeW91IHdoaWNoIGluZGl2aWR1YWwgcGFja2FnZSBkaWZmZXJlbnQgZnVuY3Rpb25zIGNvbWUgZnJvbS4KCmBgYHtyIHRpZHl2ZXJzZX0KbGlicmFyeSh0aWR5dmVyc2UpCmBgYAoKIyMgUmVhZCBpbiB0aGUgZGlmZmVyZW50aWFsIGV4cHJlc3Npb24gYW5hbHlzaXMgcmVzdWx0cyBmaWxlCgpIZXJlIHdlIGFyZSB1c2luZyBhIGB0aWR5dmVyc2VgIGZ1bmN0aW9uIGByZWFkX3RzdigpYCBmcm9tIHRoZSBgcmVhZHJgIHBhY2thZ2UuCkxpa2Ugd2UgZGlkIGluIHRoZSBwcmV2aW91cyBub3RlYm9vaywgd2Ugd2lsbCBzdG9yZSB0aGUgcmVzdWx0aW5nIGRhdGEgZnJhbWUgYXMgYHN0YXRzX2RmYC4KCmBgYHtyIHJlYWQtc3RhdHN9CiMgcmVhZCBpbiB0aGUgZmlsZSBgZ2VuZV9yZXN1bHRzX0dTRTQ0OTcxLnRzdmAgZnJvbSB0aGUgZGF0YSBkaXJlY3RvcnkKc3RhdHNfZGYgPC0gcmVhZF90c3YoZmlsZS5wYXRoKAogIGRhdGFfZGlyLAogICJnZW5lX3Jlc3VsdHNfR1NFNDQ5NzEudHN2IgopKQpgYGAKCldlIGNhbiB0YWtlIGEgbG9vayBhdCBhIGNvbHVtbiBpbmRpdmlkdWFsbHkgYnkgdXNpbmcgYSBgJGAuCk5vdGUgd2UgYXJlIHVzaW5nIGBoZWFkKClgIHNvIHRoZSB3aG9sZSB0aGluZyBkb2Vzbid0IHByaW50IG91dC4KCmBgYHtyIGNvbHVtbn0KaGVhZChzdGF0c19kZiRjb250cmFzdCkKYGBgCgpJZiB3ZSB3YW50IHRvIHNlZSBhIHNwZWNpZmljIHNldCBvZiB2YWx1ZXMsIHdlIGNhbiB1c2UgYnJhY2tldHMgd2l0aCB0aGUgaW5kaWNlcyBvZiB0aGUgdmFsdWVzIHdlJ2QgbGlrZSByZXR1cm5lZC4KCmBgYHtyfQpzdGF0c19kZiRhdmdfZXhwcmVzc2lvbls2OjEwXQpgYGAKCkxldCdzIGxvb2sgYXQgc29tZSBiYXNpYyBzdGF0aXN0aWNzIGZyb20gdGhlIGRhdGEgc2V0IHVzaW5nIGBzdW1tYXJ5KClgCgpgYGB7ciBzdGF0cy1zdW1tYXJ5LCBsaXZlID0gVFJVRX0KIyBzdW1tYXJ5IG9mIHN0YXRzX2RmCnN1bW1hcnkoc3RhdHNfZGYpCmBgYAoKVGhlIHN0YXRpc3RpY3MgZm9yIGBjb250cmFzdGAgYXJlIG5vdCB2ZXJ5IGluZm9ybWF0aXZlLCBzbyBsZXQncyBkbyB0aGF0IGFnYWluIHdpdGgganVzdCB0aGUgYGNvbnRyYXN0YCBjb2x1bW4gYWZ0ZXIgY29udmVydGluZyBpdCB0byBhIGBmYWN0b3JgCmBgYHtyIGZhY3Rvci1zdW1tYXJ5LCBsaXZlID0gVFJVRX0KIyBzdW1tYXJ5IG9mIGBzdGF0c19kZiRjb250cmFzdGAgYXMgYSBmYWN0b3IKc3VtbWFyeShhcy5mYWN0b3Ioc3RhdHNfZGYkY29udHJhc3QpKQpgYGAKCiMjIFNldCB1cCB0aGUgZGF0YXNldAoKQmVmb3JlIHdlIG1ha2Ugb3VyIHBsb3QsIHdlIHdhbnQgdG8gY2FsY3VsYXRlIGEgc2V0IG9mIG5ldyB2YWx1ZXMgZm9yIGVhY2ggcm93OyB0cmFuc2Zvcm1hdGlvbnMgb2YgdGhlIHJhdyBzdGF0aXN0aWNzIGluIG91ciB0YWJsZS4KVG8gZG8gdGhpcyB3ZSB3aWxsIHVzZSBhIGZ1bmN0aW9uIGZyb20gdGhlIGBkcGx5cmAgcGFja2FnZSBjYWxsZWQgYG11dGF0ZSgpYCB0byBtYWtlIGEgbmV3IGNvbHVtbiBvZiAtbG9nMTAgcCB2YWx1ZXMuCgpgYGB7ciBtdXRhdGV9CiMgYWRkIGEgYG5lZ19sb2cxMF9wYCBjb2x1bW4gdG8gdGhlIGRhdGEgZnJhbWUKc3RhdHNfZGYgPC0gbXV0YXRlKHN0YXRzX2RmLCAjIGRhdGEgZnJhbWUgd2UnZCBsaWtlIHRvIGFkZCBhIHZhcmlhYmxlIHRvCiAgbmVnX2xvZzEwX3AgPSAtbG9nMTAocF92YWx1ZSkgIyBjb2x1bW4gbmFtZSBhbmQgdmFsdWVzCikKYGBgCgpMZXQncyBmaWx0ZXIgdG8gb25seSBgbWFsZV9mZW1hbGVgIGNvbnRyYXN0IGRhdGEuCkZpcnN0IGxldCdzIHRyeSBvdXQgYSBsb2dpY2FsIGV4cHJlc3Npb246CgpgYGB7ciBldmFsID0gRkFMU0V9CnN0YXRzX2RmJGNvbnRyYXN0ID09ICJtYWxlX2ZlbWFsZSIKYGBgCgpOb3cgd2UgY2FuIHRyeSBvdXQgdGhlIGBmaWx0ZXIoKWAgZnVuY3Rpb24uCk5vdGljZSB0aGF0IHdlIGFyZSBub3QgYXNzaWduaW5nIHRoZSByZXN1bHRzIHRvIGEgdmFyaWFibGUsIHNvIHRoaXMgZmlsdGVyZWQgZGF0YXNldCB3aWxsIG5vdCBiZSBzYXZlZCB0byB0aGUgZW52aXJvbm1lbnQuCgpgYGB7ciBmaWx0ZXIsIGxpdmUgPSBUUlVFfQojIGZpbHRlciBzdGF0c19kZiB0byAibWFsZV9mZW1hbGUiIG9ubHkKZmlsdGVyKHN0YXRzX2RmLCBjb250cmFzdCA9PSAibWFsZV9mZW1hbGUiKQpgYGAKCk5vdyB3ZSBjYW4gYXNzaWduIHRoZSByZXN1bHRzIHRvIGEgbmV3IGRhdGEgZnJhbWU6IGBtYWxlX2ZlbWFsZV9kZmAuCgpgYGB7ciBmaWx0ZXItc2F2ZSwgbGl2ZSA9IFRSVUV9CiMgZmlsdGVyIGFuZCBzYXZlIHRvIG1hbGVfZmVtYWxlX2RmCm1hbGVfZmVtYWxlX2RmIDwtIGZpbHRlcihzdGF0c19kZiwgY29udHJhc3QgPT0gIm1hbGVfZmVtYWxlIikKYGBgCgojIyBQbG90dGluZyB0aGlzIGRhdGEKCkxldCdzIG1ha2UgYSB2b2xjYW5vIHBsb3Qgd2l0aCB0aGlzIGRhdGEuCkZpcnN0IGxldCdzIHRha2UgYSBsb29rIGF0IG9ubHkgdGhlIHR1bW9yIHZzLiBub3JtYWwgY29tcGFyaXNvbi4KTGV0J3Mgc2F2ZSB0aGlzIGFzIGEgc2VwYXJhdGUgZGF0YSBmcmFtZSBieSBhc3NpZ25pbmcgaXQgYSBuZXcgbmFtZS4KCmBgYHtyIGZpbHRlci10dW1vcn0KdHVtb3Jfbm9ybWFsX2RmIDwtIGZpbHRlcihzdGF0c19kZiwgY29udHJhc3QgPT0gImFzdHJvY3l0b21hX25vcm1hbCIpCmBgYAoKVG8gbWFrZSB0aGlzIHBsb3Qgd2Ugd2lsbCBiZSB1c2luZyBmdW5jdGlvbnMgZnJvbSB0aGUgYGdncGxvdDJgIHBhY2thZ2UsIHRoZSBtYWluIHBsb3R0aW5nIHBhY2thZ2Ugb2YgdGhlIHRpZHl2ZXJzZS4KV2UgdXNlIHRoZSBmaXJzdCBmdW5jdGlvbiwgYGdncGxvdCgpYCB0byBkZWZpbmUgdGhlIGRhdGEgdGhhdCB3aWxsIGJlIHBsb3R0ZWQuClJlbWVtYmVyLCB0aGUgbmFtZSBvZiB0aGlzIHBhY2thZ2UgaXMgYGdncGxvdDJgLCBidXQgdGhlIGZ1bmN0aW9uIHdlIHVzZSBpcyBjYWxsZWQgYGdncGxvdCgpYCB3aXRob3V0IHRoZSBgMmAuCmBnZ3Bsb3QoKWAgdGFrZXMgdHdvIG1haW4gYXJndW1lbnRzOgoKMS4gYGRhdGFgLCB3aGljaCBpcyB0aGUgZGF0YSBmcmFtZSB0aGF0IGNvbnRhaW5zIHRoZSBkYXRhIHdlIHdhbnQgdG8gcGxvdC4KMi4gYG1hcHBpbmdgLCB3aGljaCBpcyBhIHNwZWNpYWwgbGlzdCBtYWRlIHdpdGggdGhlIGBhZXMoKWAgZnVuY3Rpb24gdG8gZGVzY3JpYmUgd2hpY2ggdmFsdWVzIHdpbGwgYmUgdXNlZCBmb3IgZWFjaCAqKmFlcyoqdGhldGljIGNvbXBvbmVudCBvZiB0aGUgcGxvdCwgc3VjaCBhcyB0aGUgeCBhbmQgeSBjb29yZGluYXRlcyBvZiBlYWNoIHBvaW50LgooSWYgeW91IGZpbmQgY2FsbGluZyB0aGluZ3MgbGlrZSB0aGUgeCBhbmQgeSBjb29yZGluYXRlcyAiYWVzdGhldGljcyIgY29uZnVzaW5nLCBkb24ndCB3b3JyeSwgeW91IGFyZSBub3QgYWxvbmUuKQpTcGVjaWZpY2FsbHksIHRoZSBgYWVzKClgIGZ1bmN0aW9uIGlzIHVzZWQgdG8gc3BlY2lmeSB0aGF0IGEgZ2l2ZW4gY29sdW1uICh2YXJpYWJsZSkgaW4geW91ciBkYXRhIGZyYW1lIGJlIG1hcHBlZCB0byBhIGdpdmVuIGFlc3RoZXRpYyBjb21wb25lbnQgb2YgdGhlIHBsb3QuCgoKYGBge3IgZ2dwbG90LWJhc2V9CmdncGxvdCgKICB0dW1vcl9ub3JtYWxfZGYsICMgVGhpcyBmaXJzdCBhcmd1bWVudCBpcyB0aGUgZGF0YSBmcmFtZSB3aXRoIHRoZSBkYXRhIHdlIHdhbnQgdG8gcGxvdAogIGFlcygKICAgIHggPSBsb2dfZm9sZF9jaGFuZ2UsICMgVGhpcyBpcyB0aGUgY29sdW1uIG5hbWUgb2YgdGhlIHZhbHVlcyB3ZSB3YW50IHRvIHVzZQogICAgIyBmb3IgdGhlIHggY29vcmRpbmF0ZXMKICAgIHkgPSBuZWdfbG9nMTBfcAogICkgIyBUaGlzIGlzIHRoZSBjb2x1bW4gbmFtZSBvZiB0aGUgZGF0YSB3ZSB3YW50IGZvciB0aGUgeS1heGlzCikKYGBgCgpZb3UnbGwgbm90aWNlIHRoaXMgcGxvdCBkb2Vzbid0IGhhdmUgYW55dGhpbmcgb24gaXQgYmVjYXVzZSB3ZSBoYXZlbid0CnNwZWNpZmllZCBhIHBsb3QgdHlwZSB5ZXQuClRvIGRvIHRoYXQsIHdlIHdpbGwgYWRkIGFub3RoZXIgZ2dwbG90IGxheWVyIHdpdGggYCtgIHdoaWNoIHdpbGwgc3BlY2lmeSBleGFjdGx5IHdoYXQgd2Ugd2FudCB0byBwbG90LgpBIHZvbGNhbm8gcGxvdCBpcyBhIHNwZWNpYWwga2luZCBvZiBzY2F0dGVyIHBsb3QsIHNvIHRvIG1ha2UgdGhhdCB3ZSB3aWxsIHdhbnQgdG8gcGxvdCBpbmRpdmlkdWFsIHBvaW50cywgd2hpY2ggd2UgY2FuIGRvIHdpdGggYGdlb21fcG9pbnQoKWAuCgpgYGB7ciBnZ3Bsb3QtcG9pbnRzLCBsaXZlID0gVFJVRX0KIyBUaGlzIGZpcnN0IHBhcnQgaXMgdGhlIHNhbWUgYXMgYmVmb3JlCmdncGxvdCgKICB0dW1vcl9ub3JtYWxfZGYsCiAgYWVzKAogICAgeCA9IGxvZ19mb2xkX2NoYW5nZSwKICAgIHkgPSBuZWdfbG9nMTBfcAogICkKKSArCiAgIyBOb3cgd2UgYXJlIGFkZGluZyBvbiBhIGxheWVyIHRvIHNwZWNpZnkgd2hhdCBraW5kIG9mIHBsb3Qgd2Ugd2FudAogIGdlb21fcG9pbnQoKQpgYGAKCkhlcmUncyBhIGJyaWVmIHN1bW1hcnkgb2YgZ2dwbG90MiBzdHJ1Y3R1cmUuCiFbZ2dwbG90MiBzdHJ1Y3R1cmVdKGRpYWdyYW1zL2dncGxvdF9zdHJ1Y3R1cmUucG5nKQoKIyMjIEFkanVzdCBvdXIgZ2dwbG90CgpOb3cgdGhhdCB3ZSBoYXZlIGEgYmFzZSBwbG90IHRoYXQgc2hvd3Mgb3VyIGRhdGEsIHdlIGNhbiBhZGQgbGF5ZXJzIG9uIHRvIGl0IGFuZCBhZGp1c3QgaXQuCldlIGNhbiBhZGp1c3QgdGhlIGNvbG9yIG9mIHBvaW50cyB1c2luZyB0aGUgYGNvbG9yYCBhZXN0aGV0aWMuCgpgYGB7ciBnZ3Bsb3QtY29sb3IsIGxpdmUgPSBUUlVFfQpnZ3Bsb3QoCiAgdHVtb3Jfbm9ybWFsX2RmLAogIGFlcygKICAgIHggPSBsb2dfZm9sZF9jaGFuZ2UsCiAgICB5ID0gbmVnX2xvZzEwX3AsCiAgICBjb2xvciA9IGF2Z19leHByZXNzaW9uCiAgKSAjIFdlIGFkZGVkIHRoaXMgYXJndW1lbnQgdG8gY29sb3IgY29kZSB0aGUgcG9pbnRzIQopICsKICBnZW9tX3BvaW50KCkKYGBgCgpCZWNhdXNlIHdlIGhhdmUgc28gbWFueSBwb2ludHMgb3ZlcmxhcHBpbmcgb25lIGFub3RoZXIsIHdlIHdpbGwgd2FudCB0byBhZGp1c3QKdGhlIHRyYW5zcGFyZW5jeSwgd2hpY2ggd2UgY2FuIGRvIHdpdGggYW4gYGFscGhhYCBhcmd1bWVudC4KCmBgYHtyIGdncGxvdC1hbHBoYSwgbGl2ZSA9IFRSVUV9CmdncGxvdCgKICB0dW1vcl9ub3JtYWxfZGYsCiAgYWVzKAogICAgeCA9IGxvZ19mb2xkX2NoYW5nZSwKICAgIHkgPSBuZWdfbG9nMTBfcCwKICAgIGNvbG9yID0gYXZnX2V4cHJlc3Npb24KICApCikgKwogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjIpICMgV2UgYXJlIHVzaW5nIHRoZSBgYWxwaGFgIGFyZ3VtZW50IHRvIG1ha2Ugb3VyIHBvaW50cyB0cmFuc3BhcmVudApgYGAKCk5vdGljZSB0aGF0IHdlIGFkZGVkIHRoZSBhbHBoYSB3aXRoaW4gdGhlIGBnZW9tX3BvaW50KClgIGZ1bmN0aW9uLCBub3QgdG8gdGhlIGBhZXMoKWAuCldlIGRpZCB0aGlzIGJlY2F1c2Ugd2Ugd2FudCBhbGwgb2YgdGhlIHBvaW50cyB0byBoYXZlIHRoZSBzYW1lIGxldmVsIG9mIHRyYW5zcGFyZW5jeSwgYW5kIGl0IHdpbGwgbm90IHZhcnkgZGVwZW5kaW5nIG9uIGFueSB2YXJpYWJsZSBpbiB0aGUgZGF0YS4KV2UgY2FuIGFsc28gY2hhbmdlIHRoZSBiYWNrZ3JvdW5kIGFuZCBhcHBlYXJhbmNlIG9mIHRoZSBwbG90IGFzIGEgd2hvbGUgYnkgYWRkaW5nIGEgYHRoZW1lYC4KCmBgYHtyIGdncGxvdC10aGVtZX0KZ2dwbG90KAogIHR1bW9yX25vcm1hbF9kZiwKICBhZXMoCiAgICB4ID0gbG9nX2ZvbGRfY2hhbmdlLAogICAgeSA9IG5lZ19sb2cxMF9wLAogICAgY29sb3IgPSBhdmdfZXhwcmVzc2lvbgogICkKKSArCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuMikgKwogICMgQWRkIG9uIHRoaXMgc2V0IG9mIGFwcGVhcmFuY2UgcHJlc2V0cyB0byBtYWtlIGl0IHByZXR0eQogIHRoZW1lX2J3KCkgCmBgYAoKV2UgYXJlIG5vdCBsaW1pdGVkIHRvIGEgc2luZ2xlIHBsb3R0aW5nIGxheWVyLgpGb3IgZXhhbXBsZSwgaWYgd2Ugd2FudCB0byBhZGQgYSBob3Jpem9udGFsIGxpbmUgdG8gaW5kaWNhdGUgYSBzaWduaWZpY2FuY2UgY3V0b2ZmLCB3ZSBjYW4gZG8gdGhhdCB3aXRoIGBnZW9tX2hsaW5lKClgLgpGb3Igbm93LCB3ZSB3aWxsIGNob29zZSB0aGUgdmFsdWUgb2YgNS41ICh0aGF0IGlzIGNsb3NlIHRvIGEgQm9uZmVycm9uaSBjb3JyZWN0aW9uKSBhbmQgYWRkIHRoYXQgdG8gdGhlIHBsb3QuCgpgYGB7ciBnZ3Bsb3QtaGxpbmUsIGxpdmUgPSBUUlVFfQpnZ3Bsb3QoCiAgdHVtb3Jfbm9ybWFsX2RmLAogIGFlcygKICAgIHggPSBsb2dfZm9sZF9jaGFuZ2UsCiAgICB5ID0gbmVnX2xvZzEwX3AsCiAgICBjb2xvciA9IGF2Z19leHByZXNzaW9uCiAgKQopICsKICBnZW9tX3BvaW50KGFscGhhID0gMC4yKSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gNS41LCBjb2xvciA9ICJkYXJrZ3JlZW4iKSAjIHdlIGNhbiBzcGVjaWZ5IGNvbG9ycyBieSBuYW1lcyBoZXJlCmBgYAoKV2UgY2FuIGNoYW5nZSB0aGUgeCBhbmQgeSBsYWJlbHMgdXNpbmcgYSBmZXcgZGlmZmVyZW50IHN0cmF0ZWdpZXMuCk9uZSBhcHByb2FjaCBpcyB0byB1c2UgZnVuY3Rpb25zIGB4bGFiKClgIGFuZCBgeWxhYigpYCBpbmRpdmlkdWFsbHkgdG8gc2V0LCByZXNwZWN0aXZlbHksIHRoZSB4LWF4aXMgbGFiZWwgYW5kIHRoZSB0aGUgeS1heGlzIGxhYmVsLgoKCmBgYHtyIGdncGxvdC1sYWJlbC0xfQpnZ3Bsb3QoCiAgdHVtb3Jfbm9ybWFsX2RmLAogIGFlcygKICAgIHggPSBsb2dfZm9sZF9jaGFuZ2UsCiAgICB5ID0gbmVnX2xvZzEwX3AsCiAgICBjb2xvciA9IGF2Z19leHByZXNzaW9uCiAgKQopICsKICBnZW9tX3BvaW50KGFscGhhID0gMC4yKSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gNS41LCBjb2xvciA9ICJkYXJrZ3JlZW4iKSArCiAgdGhlbWVfYncoKSArCiAgIyBBZGQgbGFiZWxzIHdpdGggc2VwYXJhdGUgZnVuY3Rpb25zOgogIHhsYWIoImxvZzIgRm9sZCBDaGFuZ2UgVHVtb3IvTm9ybWFsIikgKwogIHlsYWIoIi1sb2cxMCBwIHZhbHVlIikKYGBgCgoKQWx0ZXJuYXRpdmVseSwgd2UgY2FuIHVzZSB0aGUgYGdncGxvdDJgIGZ1bmN0aW9uIGBsYWJzKClgLCB3aGljaCB0YWtlcyBpbmRpdmlkdWFsIGFyZ3VtZW50cyBmb3IgZWFjaCBsYWJlbCB3ZSB3YW50IHdhbnQgdG8gc2V0LgpXZSBjYW4gYWxzbyBpbmNsdWRlIHRoZSBhcmd1bWVudCBgdGl0bGVgIHRvIGFkZCBhbiBvdmVyYWxsIHBsb3QgdGl0bGUuCgpgYGB7ciBnZ3Bsb3QtbGFiZWwtMiwgbGl2ZSA9IFRSVUV9CmdncGxvdCgKICB0dW1vcl9ub3JtYWxfZGYsCiAgYWVzKAogICAgeCA9IGxvZ19mb2xkX2NoYW5nZSwKICAgIHkgPSBuZWdfbG9nMTBfcCwKICAgIGNvbG9yID0gYXZnX2V4cHJlc3Npb24KICApCikgKwogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjIpICsKICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSA1LjUsIGNvbG9yID0gImRhcmtncmVlbiIpICsKICB0aGVtZV9idygpICsKICAjIEFkZCB4IGFuZCB5IGxhYmVscyBhbmQgb3ZlcmFsbCBwbG90IHRpdGxlIHdpdGggYXJndW1lbnRzIHRvIGxhYnMoKToKICBsYWJzKAogICAgeCA9ICJsb2cyIEZvbGQgQ2hhbmdlIFR1bW9yL05vcm1hbCIsCiAgICB5ID0gIi1sb2cxMCBwIHZhbHVlIiwKICAgIHRpdGxlID0gIkFzdHJvY3l0b21hIFR1bW9yIHZzIE5vcm1hbCBDZXJlYmVsbHVtIgogICkKCmBgYAoKU29tZXRoaW5nIGdyZWF0IGFib3V0IHRoZSBgbGFicygpYCBmdW5jdGlvbiBpcyB5b3UgY2FuIGFsc28gdXNlIGl0IHRvIHNwZWNpZnkgbGFiZWxzIGZvciB5b3VyICpsZWdlbmRzKiBkZXJpdmVkIGZyb20gY2VydGFpbiBhZXN0aGV0aWNzLgpJbiB0aGlzIHBsb3QsIG91ciBsZWdlbmQgaXMgZGVyaXZlZCBmcm9tIGEgKmNvbG9yIGFlc3RoZXRpYyosIHNvIHdlIGNhbiBzcGVjaWZ5IHRoZSBrZXl3b3JkICJjb2xvciIgdG8gdXBkYXRlIHRoZSBsZWdlbmQgdGl0bGUuCgpgYGB7ciBnZ3Bsb3QtbGFiZWwtYWVzfQpnZ3Bsb3QoCiAgdHVtb3Jfbm9ybWFsX2RmLAogIGFlcygKICAgIHggPSBsb2dfZm9sZF9jaGFuZ2UsCiAgICB5ID0gbmVnX2xvZzEwX3AsCiAgICBjb2xvciA9IGF2Z19leHByZXNzaW9uCiAgKQopICsKICBnZW9tX3BvaW50KGFscGhhID0gMC4yKSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gNS41LCBjb2xvciA9ICJkYXJrZ3JlZW4iKSArCiAgdGhlbWVfYncoKSArCiAgIyBBZGQgeCBhbmQgeSBsYWJlbHMgYW5kIG92ZXJhbGwgcGxvdCB0aXRsZSAoYW5kIG1vcmUhKSB3aXRoIGFyZ3VtZW50cyB0byBsYWJzKCk6CiAgbGFicygKICAgIHggPSAibG9nMiBGb2xkIENoYW5nZSBUdW1vci9Ob3JtYWwiLAogICAgeSA9ICItbG9nMTAgcCB2YWx1ZSIsCiAgICB0aXRsZSA9ICJBc3Ryb2N5dG9tYSBUdW1vciB2cyBOb3JtYWwgQ2VyZWJlbGx1bSIsCiAgICAjIFVzZSB0aGUgY29sb3Iga2V5d29yZCB0byBsYWJlbCB0aGUgY29sb3IgbGVnZW5kCiAgICBjb2xvciA9ICJBdmVyYWdlIGV4cHJlc3Npb24iCiAgKQoKYGBgCgoKVXNlIHRoaXMgY2h1bmsgdG8gbWFrZSB0aGUgc2FtZSBraW5kIG9mIHBsb3QgYXMgdGhlIHByZXZpb3VzIGNodW5rIGJ1dCBpbnN0ZWFkIHBsb3QgdGhlIG1hbGUgZmVtYWxlIGNvbnRyYXN0IGRhdGEsIHRoYXQgaXMgc3RvcmVkIGluIGBtYWxlX2ZlbWFsZV9kZmAuCgpgYGB7ciBtZi12b2xjYW5vLCBsaXZlID0gVFJVRX0KIyBVc2UgdGhpcyBjaHVuayB0byBtYWtlIHRoZSBzYW1lIGtpbmQgb2Ygdm9sY2FubyBwbG90LCBidXQgd2l0aCB0aGUgbWFsZS1mZW1hbGUgY29udHJhc3QgZGF0YS4KZ2dwbG90KAogIG1hbGVfZmVtYWxlX2RmLAogIGFlcygKICAgIHggPSBsb2dfZm9sZF9jaGFuZ2UsCiAgICB5ID0gbmVnX2xvZzEwX3AsCiAgICBjb2xvciA9IGF2Z19leHByZXNzaW9uCiAgKQopICsKICBnZW9tX3BvaW50KGFscGhhID0gMC4yKSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gNS41LCBjb2xvciA9ICJkYXJrZ3JlZW4iKSArCiAgdGhlbWVfYncoKSArCiAgbGFicygKICAgIHggPSAibG9nMiBGb2xkIENoYW5nZSBNYWxlL0ZlbWFsZSIsCiAgICB5ID0gIi1sb2cxMCBwIHZhbHVlIiwKICAgIGNvbG9yID0gIkF2ZXJhZ2UgZXhwcmVzc2lvbiIKICApCmBgYAoKClR1cm5zIG91dCwgd2UgZG9uJ3QgaGF2ZSB0byBwbG90IGVhY2ggY29udHJhc3Qgc2VwYXJhdGVseSwgaW5zdGVhZCwgd2UgY2FuIHVzZSB0aGUgb3JpZ2luYWwgZGF0YSBmcmFtZSB0aGF0IGNvbnRhaW5zIGFsbCB0aHJlZSBjb250cmFzdHMnIGRhdGEsIGBzdGF0c19kZmAsIGFuZCBhZGQgYSBgZmFjZXRfd3JhcGAgdG8gbWFrZSBlYWNoIGNvbnRyYXN0IGl0cyBvd24gcGxvdC4KCmBgYHtyIGdncGxvdC1mYWNldHN9CmdncGxvdCgKICBzdGF0c19kZiwgIyBTd2l0Y2ggdG8gdGhlIGJpZ2dlciBkYXRhIGZyYW1lIHdpdGggYWxsIHRocmVlIGNvbnRyYXN0cycgZGF0YQogIGFlcygKICAgIHggPSBsb2dfZm9sZF9jaGFuZ2UsCiAgICB5ID0gbmVnX2xvZzEwX3AsCiAgICBjb2xvciA9IGF2Z19leHByZXNzaW9uCiAgKQopICsKICBnZW9tX3BvaW50KGFscGhhID0gMC4yKSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gNS41LCBjb2xvciA9ICJkYXJrZ3JlZW4iKSArCiAgdGhlbWVfYncoKSArCiAgZmFjZXRfd3JhcCh2YXJzKGNvbnRyYXN0KSkgKwogIGxhYnMoCiAgICAjIE5vdyB0aGF0IHRoaXMgaW5jbHVkZXMgdGhlIG90aGVyIGNvbnRyYXN0cywKICAgICMgd2UnbGwgbWFrZSB0aGUgeC1heGlzIGxhYmVsIG1vcmUgZ2VuZXJhbAogICAgeCAgPSAibG9nMiBGb2xkIENoYW5nZSIsIAogICAgeSA9ICItbG9nMTAgcCB2YWx1ZSIsCiAgICBjb2xvciA9ICJBdmVyYWdlIGV4cHJlc3Npb24iCiAgKSArCiAgY29vcmRfY2FydGVzaWFuKHhsaW0gPSBjKC0yNSwgMjUpKSAjIHpvb20gaW4gb24gdGhlIHgtYXhpcwpgYGAKCldlIGNhbiBzdG9yZSB0aGUgcGxvdCBhcyBhbiBvYmplY3QgaW4gdGhlIGdsb2JhbCBlbnZpcm9ubWVudCBieSB1c2luZyBgPC1gIG9wZXJhdG9yLgpIZXJlIHdlIHdpbGwgY2FsbCB0aGlzIGB2b2xjYW5vX3Bsb3RgLgoKYGBge3IgZ2dwbG90LXN0b3JlLW9iamVjdH0KIyBXZSBhcmUgc2F2aW5nIHRoaXMgcGxvdCB0byBhIHZhcmlhYmxlIG5hbWVkIGB2b2xjYW5vX3Bsb3RgCnZvbGNhbm9fcGxvdCA8LSBnZ3Bsb3QoCiAgc3RhdHNfZGYsIAogIGFlcygKICAgIHggPSBsb2dfZm9sZF9jaGFuZ2UsCiAgICB5ID0gbmVnX2xvZzEwX3AsCiAgICBjb2xvciA9IGF2Z19leHByZXNzaW9uCiAgKQopICsKICBnZW9tX3BvaW50KGFscGhhID0gMC4yKSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gNS41LCBjb2xvciA9ICJkYXJrZ3JlZW4iKSArCiAgdGhlbWVfYncoKSArCiAgZmFjZXRfd3JhcCh2YXJzKGNvbnRyYXN0KSkgKwogIGxhYnMoCiAgICB4ID0gImxvZzIgRm9sZCBDaGFuZ2UiLAogICAgeSA9ICItbG9nMTAgcCB2YWx1ZSIsCiAgICBjb2xvciA9ICJBdmVyYWdlIGV4cHJlc3Npb24iCiAgKSArCiAgY29vcmRfY2FydGVzaWFuKHhsaW0gPSBjKC0yNSwgMjUpKQpgYGAKCldoZW4gd2UgYXJlIGhhcHB5IHdpdGggb3VyIHBsb3QsIHdlIGNhbiBzYXZlIHRoZSBwbG90IHVzaW5nIGBnZ3NhdmVgLgpJdCdzIGEgZ29vZCBpZGVhIHRvIGFsc28gc3BlY2lmeSBgd2lkdGhgIGFuZCBgaGVpZ2h0YCBhcmd1bWVudHMgKHVuaXRzIGluIGluY2hlcykKdG8gZW5zdXJlIHRoZSBzYXZlZCBwbG90IGlzIGFsd2F5cyB0aGUgc2FtZSBzaXplIGV2ZXJ5IHRpbWUgeW91IHJ1biB0aGlzIGNvZGUuCkhlcmUsIHdlJ2xsIHNhdmUgYSA2Ing2IiBwbG90LgoKCmBgYHtyIGdnc2F2ZX0KZ2dzYXZlKAogIHBsb3QgPSB2b2xjYW5vX3Bsb3QsCiAgZmlsZW5hbWUgPSBmaWxlLnBhdGgocGxvdHNfZGlyLCAidm9sY2Fub19wbG90LnBuZyIpLAogIHdpZHRoID0gNiwKICBoZWlnaHQgPSA2CikKYGBgCgojIyMgU2Vzc2lvbiBJbmZvCgpgYGB7cn0KIyBQcmludCBvdXQgdGhlIHZlcnNpb25zIGFuZCBwYWNrYWdlcyB3ZSBhcmUgdXNpbmcgaW4gdGhpcyBzZXNzaW9uCnNlc3Npb25JbmZvKCkKYGBgCg==