Acknowledgements

Material created by Peter Mac Data Science.

Objectives

  • Demonstrate how to create stripcharts with ggplot2’s geom_jitter()
  • Demonstrate how faceting can be used to easily create multiple plots with facet_wrap()
  • Demonstrate how tidyr’s gather() can be used to convert from wide to long (tidy) format
  • Demonstrate dplyr’s select() to choose columns and filter() to choose rows
  • Demonstrate use of dplyr’s case_when() instead of multiple ifelse()s
  • Demonstrate the powerful pipe %>%

Introduction

In this tutorial, we will learn some R through creating stripcharts to visualise results from an RNA-seq experiment.

Stripcharts can be used to visualise the expression of genes by groups. They are used in RNA-seq analyses, for example, to check the expression of the top differentially expressed genes (or favourite genes). They enable us to see if the replicate samples within groups have similar expression values for genes and to compare expression values between groups. In RNA-seq we plot the normalised count values.

RNA-seq dataset

We will again use the published RNA-seq data from the Nature Cell Biology paper by Fu et al. 2015. This study examined expression in basal and luminal cells from mice at different stages (virgin, pregnant and lactating).

Here we will work with the normalised counts for all 12 samples.

Packages

We have already seen some tidyverse packages: readr for reading in files, ggplot2 for plotting and dplyr for manipulating tables. Today we will use those packages again, aswell as another really useful tidyverse package called tidyr that can be used to tidy data, such as convert from wide format into long (tidy) format.

Loading the data

First let’s open a new R script. From the top menu in RStudio: File > New File > R Script. Let’s save it as stripcharts.R.

library(tidyverse)

Next we load in our data, the RNA-seq normalised counts. The file we will use is tab-separated, so again we will use the read_tsv() function from the tidyverse readr package to read it in. We will store the contents of our file in an object called norm_counts.

norm_counts <- read_tsv("data/limma-voom_normalised_counts.tsv.gz")

Let’s take a look at the norm_counts data.

norm_counts

How many rows and columns are in norm_counts?

Stripcharts of multiple genes

We can make stripcharts to view the expression of multiple genes. Let’s plot the expression of the top 10 most significantly differentially expressed genes in luminal cells from the pregnant mice versus the luminal cells from the lactating mice. These are the genes with the smallest adjusted P values (adj.P.value). In the volcano plot tutorial we showed how to get the symbols for these top 10 genes. Here we will show how to create an object with the symbols manually. Remember, in R we use c() to combine multiple values. This top10_syms is an R data structure called a vector.

top10_syms <- c("Csn1s2b", "Slc25a1", "Slc34a2", "Atp2b2", "Acacb", "Slc30a2", "Elovl5", "Egf", "Ceacam10", "Pmvk")

Then we extract the normalised counts information for these genes from the norm_counts file.

Filtering rows with filter()

We can use dplyr’s filter() to filter rows. Let’s take a look at how filter() works.

To use filter() we specify the data and the column(s) we want to use to filter with our criteria.

If we wanted to filter for rows (genes) in the MCL1.DG sample that have expression above a threshold (e.g. 5) we would write below.

filter(norm_counts, MCL1.DG > 5)

If we wanted to filter for genes that have an expression value above 5 in both the basal virgin samples (MCL1.DG and MCL1.DH) we specify both columns and the criteria. Note that we need to specify the threshold for each column e.g MCL1.DG > 5 & MCL1.DH > 5 and not MCL1.DG & MCL1.DH > 5.

filter(norm_counts, MCL1.DG > 5 & MCL1.DH > 5)

To filter this dataset for the rows that contain the Csn1s2b gene we would write below. Note that we use a == when we want to test if a value matches exactly.

filter(norm_counts, SYMBOL == "Csn1s2b")

If we wanted to filter for 2 genes we could write that as below. Here we use “|” which means or as we want the row if the SYMBOL column contains Csn1s2b or Slc25a1.

filter(norm_counts, SYMBOL == "Csn1s2b" | SYMBOL == "Slc25a1")

Exercise

  • Try to filter for all rows containing casein in the GENENAME column. Hint: Take a look at the help for str_detect() a function from the tidyverse stringr package.

If we want to filter the rows for our top 10 genes. We use SYMBOL %in% top10_syms which means we want all the rows where the SYMBOL column contains one of the gene symbols in top10_syms. Remember, in R we use %in% to test if a value is in a set of values.

top10_counts <- filter(norm_counts, SYMBOL %in% top10_syms)

Take a look.

top10_counts

Selecting columns with select()

We don’t need the ENTREZID and GENENAME columns, we are only going to use the SYMBOL column and the counts so we can use select() to remove these columns.

filter() is used to choose rows, to choose columns we use select(). Let’s take a look at how select() works.

If we wanted to select the gene symbol column we would write below.

select(top10_counts, SYMBOL)

If we wanted the SYMBOL column and the MCL1.DG sample column we would write below.

select(top10_counts, SYMBOL, MCL1.DG)

We can select column ranges with :.

select(top10_counts, ENTREZID:GENENAME)

There are also useful helper functions you can use inside select(), such as contains(), starts_with() and ends_with().

Exercise

  • Try to select all (and only) the counts columns. Hint: There is more than one way to do it.

We can also use select() to remove columns by specifying a “-”" before the column name(s).

Let’s remove the ENTREZID and GENENAME columns so we only keep the SYMBOL column and the sample expression values.

top10_counts <- select(top10_counts, -ENTREZID, -GENENAME)
top10_counts

The base R way of selecting columns

As mentioned in the tidyverse tutorial here, in base R we can select columns using $ or [], for example, to select the SYMBOL column we could use top10_counts$SYMBOL or top10_counts[, "SYMBOL"]. The $ operator works well for single columns, but for multiple columns it quickly starts to get cumbersome as we need to use the [] operator and c() for combining the required columns. The column names also need quotation marks. For example, to access both the SYMBOL and GENENAME columns we would use top10_counts[, c("SYMBOL", "GENENAME")] whereas with tidyverse’s dplyr it is a lot more intuitive select(top10_counts, SYMBOL, GENENAME).

Converting wide into tidy format with gather()

To more easily plot with ggplot2 we need to change the data into “tidy data”. There are 3 rules of tidy data:

  1. Each variable must have its own column.
  2. Each observation must have its own row.
  3. Each value must have its own cell.

In our expression table there should be just one column containing all expression values instead of multiple columns with counts for each sample. We can use tidyr’s gather() to easily change the format into long format.

gather() will reformat specified columns into two new columns, “key” and “value”. The “key” column will contain the specified column names, and the “value” column will contain the specified column values. For our data, our sample ids are the column names we will use and the expression values are the values in these columns. We tell gather what we want the new key and value columns to be called. We will give the key column the name “Sample” and the value column the name “Norm_counts” (as they are our normalised count values). gather() uses the same methods as select() to choose the columns so to say we want to reformat all the sample columns we could write below.

# change to tidy data format (all expression values in one column)
top10_counts <- gather(top10_counts, key=Sample, value=Norm_counts, starts_with("MCL"))

Take a look.

top10_counts

Take a closer look with View()

View(top10_counts)

We can also specify the columns we don’t want gather to reformat if that’s easier and let gather format the rest. For example, the code below would reformat all columns except SYMBOL i.e. all the MCL columns. It would produce the same result as when we specified the columns we wanted with starts_with("MCL").

gather(top10_counts, key=Sample, value=Norm_counts, -SYMBOL)

Exercise

  • Try running gather() on the norm_counts object and save it as an object called testing (i.e. run testing <- gather(norm_counts). What do you think of the output? Can you improve it so that there is a column with sample ids and a column with counts.
  • spread() is the opposite of gather(). Try running spread() on the top10_counts object and see if you can regenerate the table with samples in columns.

Adding column with mutate() and case_when()

We want to plot and compare the expression in the groups, so we use mutate()to add a column called “Group” to say what group each sample belongs to. Here we use dplyr’s case_when() to say if the same id matches are conditions then add the appropriate group name into the Group column. We could use ifelse() as we did in the volcano plot tutorial. However, when there are many conditions to test, as there are here, case_when() is easier to use.

top10_counts <- mutate(top10_counts, Group=case_when(
        Sample %in% c("MCL1.DG", "MCL1.DH")  ~ "basal virgin",
        Sample %in% c("MCL1.DI", "MCL1.DJ")  ~ "basal pregnant",
        Sample %in% c("MCL1.DK", "MCL1.DL")  ~ "basal lactate",
        Sample %in% c("MCL1.LA", "MCL1.LB")  ~ "luminal virgin",
        Sample %in% c("MCL1.LC", "MCL1.LD")  ~ "luminal pregnant",
        Sample %in% c("MCL1.LE", "MCL1.LF")  ~ "luminal lactate"
       ))

Take a look at the data again with View()

View(top10_counts)

Exercise

  • Try to use mutate() and case_when() to add a column called CellType. This column should contain the value basal if the Group column contains the word basal, or luminal if the Group contains luminal. Save it as an object called testing. Hint: Use str_detect() inside case_when().

Creating stripcharts with geom_jitter()

Now we can make a stripchart. We plot the Group on the X axis and the Norm_counts on the y axis. We will use + geom_jitter() to create a jitter plot. A jitter plot is similar to a scatter plot. Why do we not just use a scatter plot? Let’s take a look.

ggplot(data=top10_counts, mapping=aes(x=Group, y=Norm_counts)) +
  geom_point()

Some of the points are overlapping so we use geom_jitter() to add a small amount of random variation to the location of each point so they don’t overlap.

ggplot(data=top10_counts, mapping=aes(x=Group, y=Norm_counts)) +
  geom_jitter()

Creating multiple plots with facet_wrap()

The points are no longer overlapping, however, this is all the genes in one plot. ggplot2 has a really useful feature called faceting that we can use. facet_wrap() will create plots for every value in a column in our data. We would like a stripchart of expression values for each gene so we add + facet_wrap(~SYMBOL) to say we want to a plot for each value in the SYMBOL column. There is also facet_grid() which is most useful when you have two discrete variables, and all combinations of the variables exist in the data.

ggplot(data=top10_counts, mapping=aes(x=Group, y=Norm_counts)) +
  geom_jitter() +
  facet_wrap(~SYMBOL)

We can change the number of rows (nrows) or columns (ncols) to balance the plot. Let’s try 2 rows (nrow=2).

ggplot(data=top10_counts, mapping=aes(x=Group, y=Norm_counts)) +
  geom_jitter() +
  facet_wrap(~SYMBOL, nrow=2)

We can add colour=Group to say we want to colour by the groups (the Group column we added).

ggplot(data=top10_counts, mapping=aes(x=Group, y=Norm_counts, colour=Group)) +
  geom_jitter() +
  facet_wrap(~SYMBOL, nrow=2)

We can add + theme(axis.text.x = element_text(angle = 90) to make the x axis labels vertical so they don’t overlap.

ggplot(data=top10_counts, mapping=aes(x=Group, y=Norm_counts, colour=Group)) +
  geom_jitter() +
  facet_wrap(~SYMBOL, nrow=2) +
  theme(axis.text.x = element_text(angle = 90)) 

Ordering categories along an axis

The groups have been plotted in alphabetical order on the x axis, however, we may want to change the order. We may prefer to plot the groups in order of stage, for example, basal virgin, basal pregnant, basal lactate, luminal virgin, luminal pregnant, luminal lactate. In the volcano plot tutorial we showed how to change the order of items in the legend with breaks= into the scale layer. Let’s try that here.

First let’s make an object with the group order that we want.

group_order <- c("basal virgin", "basal pregnant", "basal lactate", "luminal virgin", "luminal pregnant", "luminal lactate")

Then let’s add this group_order into breaks=.

ggplot(data=top10_counts, mapping=aes(x=Group, y=Norm_counts, colour=Group)) +
  geom_jitter() +
  facet_wrap(~SYMBOL, nrow=2) +
  theme(axis.text.x = element_text(angle = 90)) +
  scale_colour_discrete(breaks=group_order)

That reordered the legend but notice that it didn’t reorder the groups on the x axis. If we want to reorder groups along an axis with ggplot we need to make the column with the groups into an R data type called a factor. Factors in R are a special data type used to specify categories. The names of the categories are called the factor levels. Factors are the only R data type with levels, other data types, such as character and numeric, do not. For information on R data types see here

We’ll add another column called “Group_f” where we’ll make the Group column into a factor and specify what order we want the levels of the factor.

top10_counts <- mutate(top10_counts, Group_f=factor(Group, levels=group_order))

Take a look with View().

View(top10_counts)

The Group and the Group_f column look the same but take a look by typing top10_counts.

top10_counts

Notice that the Group column has <chr> under the heading, that indicates is a character data type, while the Group_f column has <fct> under the heading, indicating it is a factor data type. The str() command that we saw in first plots tutorial is useful to check the data types in objects.

str(top10_counts)
Classes ‘tbl_df’, ‘tbl’ and 'data.frame':   120 obs. of  5 variables:
 $ SYMBOL     : chr  "Slc25a1" "Pmvk" "Egf" "Slc30a2" ...
 $ Sample     : chr  "MCL1.DG" "MCL1.DG" "MCL1.DG" "MCL1.DG" ...
 $ Norm_counts: num  4.881 3.618 -0.89 0.951 -0.229 ...
 $ Group      : chr  "basal virgin" "basal virgin" "basal virgin" "basal virgin" ...
 $ Group_f    : Factor w/ 6 levels "basal virgin",..: 1 1 1 1 1 1 1 1 1 1 ...

str() shows us Group_f column is a Factor with 6 levels (categories).

How can we see what these levels are and what order they’re in? Are they in the order that we want?

To see the levels and their order we can use levels(). But we can’t use levels() on our top10_counts object as if we do we get “NULL”.

levels(top10_counts)
NULL

We need to give levels() just the values in the Group_f column.

Maybe we might think we could use select() to select the Group_f column and then check the levels. We’ll save the output in an object called testing so we can test what happens.

# Extract the Group_f column
testing <- select(top10_counts, Group_f)
testing
levels(testing)
NULL

We still get “NULL” with levels(). Instead of extracting the column, we need to extract the values out of column format and give just the values to levels(). We can do that with dplyr’s pull().

# Pull the values out of the Group_f column
testing <- pull(top10_counts, Group_f)
testing
  [1] basal virgin     basal virgin     basal virgin     basal virgin     basal virgin     basal virgin    
  [7] basal virgin     basal virgin     basal virgin     basal virgin     basal virgin     basal virgin    
 [13] basal virgin     basal virgin     basal virgin     basal virgin     basal virgin     basal virgin    
 [19] basal virgin     basal virgin     basal pregnant   basal pregnant   basal pregnant   basal pregnant  
 [25] basal pregnant   basal pregnant   basal pregnant   basal pregnant   basal pregnant   basal pregnant  
 [31] basal pregnant   basal pregnant   basal pregnant   basal pregnant   basal pregnant   basal pregnant  
 [37] basal pregnant   basal pregnant   basal pregnant   basal pregnant   basal lactate    basal lactate   
 [43] basal lactate    basal lactate    basal lactate    basal lactate    basal lactate    basal lactate   
 [49] basal lactate    basal lactate    basal lactate    basal lactate    basal lactate    basal lactate   
 [55] basal lactate    basal lactate    basal lactate    basal lactate    basal lactate    basal lactate   
 [61] luminal virgin   luminal virgin   luminal virgin   luminal virgin   luminal virgin   luminal virgin  
 [67] luminal virgin   luminal virgin   luminal virgin   luminal virgin   luminal virgin   luminal virgin  
 [73] luminal virgin   luminal virgin   luminal virgin   luminal virgin   luminal virgin   luminal virgin  
 [79] luminal virgin   luminal virgin   luminal pregnant luminal pregnant luminal pregnant luminal pregnant
 [85] luminal pregnant luminal pregnant luminal pregnant luminal pregnant luminal pregnant luminal pregnant
 [91] luminal pregnant luminal pregnant luminal pregnant luminal pregnant luminal pregnant luminal pregnant
 [97] luminal pregnant luminal pregnant luminal pregnant luminal pregnant luminal lactate  luminal lactate 
[103] luminal lactate  luminal lactate  luminal lactate  luminal lactate  luminal lactate  luminal lactate 
[109] luminal lactate  luminal lactate  luminal lactate  luminal lactate  luminal lactate  luminal lactate 
[115] luminal lactate  luminal lactate  luminal lactate  luminal lactate  luminal lactate  luminal lactate 
Levels: basal virgin basal pregnant basal lactate luminal virgin luminal pregnant luminal lactate
# Check the factor levels
levels(testing)
[1] "basal virgin"     "basal pregnant"   "basal lactate"    "luminal virgin"   "luminal pregnant"
[6] "luminal lactate" 

Now we can see what the factor levels are and their order.

However, in this case, to check the factor levels, it might be simpler to use the base R method, as we can use $ to accessing the values in a column. This takes the format object$columnname e.g. top10_counts$Group_f

top10_counts$Group_f
  [1] basal virgin     basal virgin     basal virgin     basal virgin     basal virgin     basal virgin    
  [7] basal virgin     basal virgin     basal virgin     basal virgin     basal virgin     basal virgin    
 [13] basal virgin     basal virgin     basal virgin     basal virgin     basal virgin     basal virgin    
 [19] basal virgin     basal virgin     basal pregnant   basal pregnant   basal pregnant   basal pregnant  
 [25] basal pregnant   basal pregnant   basal pregnant   basal pregnant   basal pregnant   basal pregnant  
 [31] basal pregnant   basal pregnant   basal pregnant   basal pregnant   basal pregnant   basal pregnant  
 [37] basal pregnant   basal pregnant   basal pregnant   basal pregnant   basal lactate    basal lactate   
 [43] basal lactate    basal lactate    basal lactate    basal lactate    basal lactate    basal lactate   
 [49] basal lactate    basal lactate    basal lactate    basal lactate    basal lactate    basal lactate   
 [55] basal lactate    basal lactate    basal lactate    basal lactate    basal lactate    basal lactate   
 [61] luminal virgin   luminal virgin   luminal virgin   luminal virgin   luminal virgin   luminal virgin  
 [67] luminal virgin   luminal virgin   luminal virgin   luminal virgin   luminal virgin   luminal virgin  
 [73] luminal virgin   luminal virgin   luminal virgin   luminal virgin   luminal virgin   luminal virgin  
 [79] luminal virgin   luminal virgin   luminal pregnant luminal pregnant luminal pregnant luminal pregnant
 [85] luminal pregnant luminal pregnant luminal pregnant luminal pregnant luminal pregnant luminal pregnant
 [91] luminal pregnant luminal pregnant luminal pregnant luminal pregnant luminal pregnant luminal pregnant
 [97] luminal pregnant luminal pregnant luminal pregnant luminal pregnant luminal lactate  luminal lactate 
[103] luminal lactate  luminal lactate  luminal lactate  luminal lactate  luminal lactate  luminal lactate 
[109] luminal lactate  luminal lactate  luminal lactate  luminal lactate  luminal lactate  luminal lactate 
[115] luminal lactate  luminal lactate  luminal lactate  luminal lactate  luminal lactate  luminal lactate 
Levels: basal virgin basal pregnant basal lactate luminal virgin luminal pregnant luminal lactate

So levels(top10_counts$Group_f) will access the values in the Group_f column directly, giving us the same output as we would get with levels(pull(top10_counts, Group_f)), but it is easier to read.

levels(top10_counts$Group_f)
[1] "basal virgin"     "basal pregnant"   "basal lactate"    "luminal virgin"   "luminal pregnant"
[6] "luminal lactate" 

The levels are in the order that we want, so we can now change our plot to use the “Group_f” column instead of Group column (change x= and colour=).

ggplot(data=top10_counts, mapping=aes(x=Group_f, y=Norm_counts, colour=Group_f)) +
  geom_jitter() +
  facet_wrap(~SYMBOL, nrow=2) +
  theme(axis.text.x = element_text(angle = 90)) 

Note that both the legend and the x axis now have the groups in the order that we want.

These are the top genes in the comparison of luminal cells from pregnant vs lactating mice and this type of plot enables us to see what the expression values look like in all the groups. We can see that some genes, such as Pmvk, have more similar expression across all the groups than others, such as Csn1s2b.

Notice that the genes have also been plotted in alphabetical order in the facets. If we wanted to plot these genes in the order of most signficant, then we need to make symbol column into a factor as we did for the groups.

Exercise

  • Plot the genes in order of the significance. Hint: Use mutate() to add a column called SYMBOL_f containing SYMBOL as a factor with the levels in the order in top10_syms. Then remake the plot using the new SYMBOL_f column in facet_wrap() instead of SYMBOL.

Creating workflows using %>%

One of the most useful and powerful things about the tidyverse is dplyr’s pipe operator %>%. This enables you to chain commmands together into workflows. For example, we could chain together the commands we ran earlier on our top10_counts object. Note that like the + we use to add layers to a ggplot, %>% goes at the end of the line and this ‘pipes’ the output into the next command. If we use the %>% we also don’t need to specify the data inside the individual commands, we only need to specify it at the beginning e.g. instead of filter(norm_counts, SYMBOL %in% top10_syms) we use norm_counts %>% filter(SYMBOL %in% top10_syms). We can also pipe outputs directly into ggplot(). The pipe is very useful but some advice on when not to use the pipe is provided by the tidyverse creator Hadley Wickham in the excellent R for Data Science book.

norm_counts %>%
  filter(SYMBOL %in% top10_syms) %>%                             # filter rows for the top 10 genes
  select(-ENTREZID, -GENENAME) %>%                               # remove ENTREZID and GENENAME columns
  gather(key=Sample, value=Norm_counts, starts_with("MCL"))  %>%                    # convert from wide to long (tidy) format
  mutate(Group=case_when(                                        # add a column to specify groups
        Sample %in% c("MCL1.DG", "MCL1.DH")  ~ "basal virgin",
        Sample %in% c("MCL1.DI", "MCL1.DJ")  ~ "basal pregnant",
        Sample %in% c("MCL1.DK", "MCL1.DL")  ~ "basal lactate",
        Sample %in% c("MCL1.LA", "MCL1.LB")  ~ "luminal virgin",
        Sample %in% c("MCL1.LC", "MCL1.LD")  ~ "luminal pregnant",
        Sample %in% c("MCL1.LE", "MCL1.LF")  ~ "luminal lactate"
       )) %>%
  mutate(Group_f=factor(Group, levels=group_order)) %>%           # convert Group column to factor data type to specify ordering
  ggplot(mapping=aes(x=Group_f, y=Norm_counts, colour=Group_f)) + # make stripcharts faceted by gene
  geom_jitter() +
  facet_wrap(~SYMBOL, nrow=2) +
  theme(axis.text.x = element_text(angle = 90)) 

We only have two points per group here but if there were more points you could overlay an error bar with geom_errorbar() or combine a geom_boxplot() with the geom_jitter(), as shown for the stripcharts in the tutorial here.

Exercise

  • Starting from norm_counts make a workflow using %>% that creates stripcharts for the genes Trp53, Brca1, Brca2.

Key Points

  • We can choose rows with dplyr’s filter()
  • We can choose columns with dplyr’s select()
  • We can test for multiple conditions with dplyr’s case_when()
  • We can convert from wide to long (tidy) format with tidyr’s gather()
  • We can use geom_jitter() to make stripcharts
  • We can make multiple plots based on values in a column with facet_wrap()
  • We can create workflows with dplyr’s pipe %>%
LS0tCnRpdGxlOiAiSW50cm9kdWN0aW9uIHRvIFIiCmF1dGhvcjogIk1hcmlhIERveWxlIgpkYXRlOiAiYHIgZm9ybWF0KFN5cy50aW1lKCksICclZCAlQiAlWScpYCIKb3V0cHV0OiAKICBodG1sX25vdGVib29rOgogICAgdG9jOiB5ZXMKICAgIHRvY19mbG9hdDogeWVzCiAgICB0b2NfZGVwdGg6IDQKc3VidGl0bGU6IHZpc3VhbGlzaW5nIFJOQS1zZXEgZGF0YSB3aXRoIHN0cmlwY2hhcnRzCi0tLQoKIyMjIyBBY2tub3dsZWRnZW1lbnRzCk1hdGVyaWFsIGNyZWF0ZWQgYnkgUGV0ZXIgTWFjIERhdGEgU2NpZW5jZS4KCiMjIE9iamVjdGl2ZXMKICAKKiBEZW1vbnN0cmF0ZSBob3cgdG8gY3JlYXRlIHN0cmlwY2hhcnRzIHdpdGggZ2dwbG90MidzIGBnZW9tX2ppdHRlcigpYAoqIERlbW9uc3RyYXRlIGhvdyBmYWNldGluZyBjYW4gYmUgdXNlZCB0byBlYXNpbHkgY3JlYXRlIG11bHRpcGxlIHBsb3RzIHdpdGggYGZhY2V0X3dyYXAoKWAgCiogRGVtb25zdHJhdGUgaG93IHRpZHlyJ3MgYGdhdGhlcigpYCBjYW4gYmUgdXNlZCB0byBjb252ZXJ0IGZyb20gd2lkZSB0byBsb25nICh0aWR5KSBmb3JtYXQKKiBEZW1vbnN0cmF0ZSBkcGx5cidzIGBzZWxlY3QoKWAgdG8gY2hvb3NlIGNvbHVtbnMgYW5kIGBmaWx0ZXIoKWAgdG8gY2hvb3NlIHJvd3MKKiBEZW1vbnN0cmF0ZSB1c2Ugb2YgZHBseXIncyBgY2FzZV93aGVuKClgIGluc3RlYWQgb2YgbXVsdGlwbGUgYGlmZWxzZSgpYHMKKiBEZW1vbnN0cmF0ZSB0aGUgcG93ZXJmdWwgcGlwZSBgJT4lYAoKCiMjIEludHJvZHVjdGlvbgoKSW4gdGhpcyB0dXRvcmlhbCwgd2Ugd2lsbCBsZWFybiBzb21lIFIgdGhyb3VnaCBjcmVhdGluZyBzdHJpcGNoYXJ0cyB0byB2aXN1YWxpc2UgcmVzdWx0cyBmcm9tIGFuIFJOQS1zZXEgZXhwZXJpbWVudC4KClN0cmlwY2hhcnRzIGNhbiBiZSB1c2VkIHRvIHZpc3VhbGlzZSB0aGUgZXhwcmVzc2lvbiBvZiBnZW5lcyBieSBncm91cHMuIFRoZXkgYXJlIHVzZWQgaW4gUk5BLXNlcSBhbmFseXNlcywgZm9yIGV4YW1wbGUsIHRvIGNoZWNrIHRoZSBleHByZXNzaW9uIG9mIHRoZSB0b3AgZGlmZmVyZW50aWFsbHkgZXhwcmVzc2VkIGdlbmVzIChvciBmYXZvdXJpdGUgZ2VuZXMpLiBUaGV5IGVuYWJsZSB1cyB0byBzZWUgaWYgdGhlIHJlcGxpY2F0ZSBzYW1wbGVzIHdpdGhpbiBncm91cHMgaGF2ZSBzaW1pbGFyIGV4cHJlc3Npb24gdmFsdWVzIGZvciBnZW5lcyBhbmQgdG8gY29tcGFyZSBleHByZXNzaW9uIHZhbHVlcyBiZXR3ZWVuIGdyb3Vwcy4gSW4gUk5BLXNlcSB3ZSBwbG90IHRoZSBub3JtYWxpc2VkIGNvdW50IHZhbHVlcy4KCiMjIyBSTkEtc2VxIGRhdGFzZXQKCldlIHdpbGwgYWdhaW4gdXNlIHRoZSBwdWJsaXNoZWQgUk5BLXNlcSBkYXRhIGZyb20gdGhlIE5hdHVyZSBDZWxsIEJpb2xvZ3kgcGFwZXIgYnkgW0Z1IGV0IGFsLiAyMDE1XShodHRwczovL3d3dy5uY2JpLm5sbS5uaWguZ292L3B1Ym1lZC8yNTczMDQ3MikuIFRoaXMgc3R1ZHkgZXhhbWluZWQgZXhwcmVzc2lvbiBpbiBiYXNhbCBhbmQgbHVtaW5hbCBjZWxscyBmcm9tIG1pY2UgYXQgZGlmZmVyZW50IHN0YWdlcyAodmlyZ2luLCBwcmVnbmFudCBhbmQgbGFjdGF0aW5nKS4KCkhlcmUgd2Ugd2lsbCB3b3JrIHdpdGggdGhlIG5vcm1hbGlzZWQgY291bnRzIGZvciBhbGwgMTIgc2FtcGxlcy4KIVtdKGltYWdlcy9tb3VzZV9leHAucG5nKQoKIyMjIFBhY2thZ2VzCgpXZSBoYXZlIGFscmVhZHkgc2VlbiBzb21lICoqdGlkeXZlcnNlKiogcGFja2FnZXM6ICoqcmVhZHIqKiBmb3IgcmVhZGluZyBpbiBmaWxlcywgKipnZ3Bsb3QyKiogZm9yIHBsb3R0aW5nIGFuZCAqKmRwbHlyKiogZm9yIG1hbmlwdWxhdGluZyB0YWJsZXMuIFRvZGF5IHdlIHdpbGwgdXNlIHRob3NlIHBhY2thZ2VzIGFnYWluLCBhc3dlbGwgYXMgYW5vdGhlciByZWFsbHkgdXNlZnVsIHRpZHl2ZXJzZSBwYWNrYWdlIGNhbGxlZCAqKnRpZHlyKiogdGhhdCBjYW4gYmUgdXNlZCB0byB0aWR5IGRhdGEsIHN1Y2ggYXMgY29udmVydCBmcm9tIHdpZGUgZm9ybWF0IGludG8gbG9uZyAodGlkeSkgZm9ybWF0LgoKCiMjIExvYWRpbmcgdGhlIGRhdGEKCkZpcnN0IGxldCdzIG9wZW4gYSBuZXcgUiBzY3JpcHQuIEZyb20gdGhlIHRvcCBtZW51IGluIFJTdHVkaW86IGBGaWxlID4gTmV3IEZpbGUgPiBSIFNjcmlwdGAuCkxldCdzIHNhdmUgaXQgYXMgYHN0cmlwY2hhcnRzLlJgLgoKYGBge3IsIG1lc3NhZ2U9RkFMU0V9CmxpYnJhcnkodGlkeXZlcnNlKQpgYGAKCk5leHQgd2UgbG9hZCBpbiBvdXIgZGF0YSwgdGhlIFJOQS1zZXEgbm9ybWFsaXNlZCBjb3VudHMuIFRoZSBmaWxlIHdlIHdpbGwgdXNlIGlzIHRhYi1zZXBhcmF0ZWQsIHNvIGFnYWluIHdlIHdpbGwgdXNlIHRoZSBgcmVhZF90c3YoKWAgZnVuY3Rpb24gZnJvbSB0aGUgdGlkeXZlcnNlIHJlYWRyIHBhY2thZ2UgdG8gcmVhZCBpdCBpbi4gV2Ugd2lsbCBzdG9yZSB0aGUgY29udGVudHMgb2Ygb3VyIGZpbGUgaW4gYW4gb2JqZWN0IGNhbGxlZCBgbm9ybV9jb3VudHNgLgoKYGBge3IgcmVzdWx0cyA9ICdoaWRlJ30Kbm9ybV9jb3VudHMgPC0gcmVhZF90c3YoImRhdGEvbGltbWEtdm9vbV9ub3JtYWxpc2VkX2NvdW50cy50c3YuZ3oiKQpgYGAKCkxldCdzIHRha2UgYSBsb29rIGF0IHRoZSBgbm9ybV9jb3VudHNgIGRhdGEuCgpgYGB7cn0Kbm9ybV9jb3VudHMKYGBgCgpIb3cgbWFueSByb3dzIGFuZCBjb2x1bW5zIGFyZSBpbiBgbm9ybV9jb3VudHNgPwoKIyMgU3RyaXBjaGFydHMgb2YgbXVsdGlwbGUgZ2VuZXMKCldlIGNhbiBtYWtlIHN0cmlwY2hhcnRzIHRvIHZpZXcgdGhlIGV4cHJlc3Npb24gb2YgbXVsdGlwbGUgZ2VuZXMuIExldCdzIHBsb3QgdGhlIGV4cHJlc3Npb24gb2YgdGhlIHRvcCAxMCBtb3N0IHNpZ25pZmljYW50bHkgZGlmZmVyZW50aWFsbHkgZXhwcmVzc2VkIGdlbmVzIGluIGx1bWluYWwgY2VsbHMgZnJvbSB0aGUgcHJlZ25hbnQgbWljZSB2ZXJzdXMgdGhlIGx1bWluYWwgY2VsbHMgZnJvbSB0aGUgbGFjdGF0aW5nIG1pY2UuIFRoZXNlIGFyZSB0aGUgZ2VuZXMgd2l0aCB0aGUgc21hbGxlc3QgYWRqdXN0ZWQgUCB2YWx1ZXMgKGFkai5QLnZhbHVlKS4gSW4gdGhlIHZvbGNhbm8gcGxvdCB0dXRvcmlhbCB3ZSBzaG93ZWQgaG93IHRvIGdldCB0aGUgc3ltYm9scyBmb3IgdGhlc2UgdG9wIDEwIGdlbmVzLiBIZXJlIHdlIHdpbGwgc2hvdyBob3cgdG8gY3JlYXRlIGFuIG9iamVjdCB3aXRoIHRoZSBzeW1ib2xzIG1hbnVhbGx5LiBSZW1lbWJlciwgaW4gUiB3ZSB1c2UgYGMoKWAgdG8gY29tYmluZSBtdWx0aXBsZSB2YWx1ZXMuIFRoaXMgYHRvcDEwX3N5bXNgIGlzIGFuIFIgZGF0YSBzdHJ1Y3R1cmUgY2FsbGVkIGEgKip2ZWN0b3IqKi4KCmBgYHtyfQp0b3AxMF9zeW1zIDwtIGMoIkNzbjFzMmIiLCAiU2xjMjVhMSIsICJTbGMzNGEyIiwgIkF0cDJiMiIsICJBY2FjYiIsICJTbGMzMGEyIiwgIkVsb3ZsNSIsICJFZ2YiLCAiQ2VhY2FtMTAiLCAiUG12ayIpCmBgYAoKVGhlbiB3ZSBleHRyYWN0IHRoZSBub3JtYWxpc2VkIGNvdW50cyBpbmZvcm1hdGlvbiBmb3IgdGhlc2UgZ2VuZXMgZnJvbSB0aGUgYG5vcm1fY291bnRzYCBmaWxlLgoKIyMgRmlsdGVyaW5nIHJvd3Mgd2l0aCBgZmlsdGVyKClgCgpXZSBjYW4gdXNlIGRwbHlyJ3MgYGZpbHRlcigpYCB0byBmaWx0ZXIgcm93cy4gTGV0J3MgdGFrZSBhIGxvb2sgYXQgaG93IGBmaWx0ZXIoKWAgd29ya3MuIAoKVG8gdXNlIGBmaWx0ZXIoKWAgd2Ugc3BlY2lmeSB0aGUgZGF0YSBhbmQgdGhlIGNvbHVtbihzKSB3ZSB3YW50IHRvIHVzZSB0byBmaWx0ZXIgd2l0aCBvdXIgY3JpdGVyaWEuIAoKSWYgd2Ugd2FudGVkIHRvIGZpbHRlciBmb3Igcm93cyAoZ2VuZXMpIGluIHRoZSBNQ0wxLkRHIHNhbXBsZSB0aGF0IGhhdmUgZXhwcmVzc2lvbiBhYm92ZSBhIHRocmVzaG9sZCAoZS5nLiA1KSB3ZSB3b3VsZCB3cml0ZSBiZWxvdy4KYGBge3J9CmZpbHRlcihub3JtX2NvdW50cywgTUNMMS5ERyA+IDUpCmBgYAoKSWYgd2Ugd2FudGVkIHRvIGZpbHRlciBmb3IgZ2VuZXMgdGhhdCBoYXZlIGFuIGV4cHJlc3Npb24gdmFsdWUgYWJvdmUgNSBpbiAqYm90aCogdGhlIGJhc2FsIHZpcmdpbiBzYW1wbGVzIChNQ0wxLkRHIGFuZCBNQ0wxLkRIKSB3ZSBzcGVjaWZ5IGJvdGggY29sdW1ucyBhbmQgdGhlIGNyaXRlcmlhLiBOb3RlIHRoYXQgd2UgbmVlZCB0byBzcGVjaWZ5IHRoZSB0aHJlc2hvbGQgZm9yICplYWNoKiBjb2x1bW4gZS5nIGBNQ0wxLkRHID4gNSAmIE1DTDEuREggPiA1YCBhbmQgbm90IGBNQ0wxLkRHICYgTUNMMS5ESCA+IDVgLgpgYGB7cn0KZmlsdGVyKG5vcm1fY291bnRzLCBNQ0wxLkRHID4gNSAmIE1DTDEuREggPiA1KQpgYGAKCgpUbyBmaWx0ZXIgdGhpcyBkYXRhc2V0IGZvciB0aGUgcm93cyB0aGF0IGNvbnRhaW4gdGhlIENzbjFzMmIgZ2VuZSB3ZSB3b3VsZCB3cml0ZSBiZWxvdy4gTm90ZSB0aGF0IHdlIHVzZSBhIGA9PWAgd2hlbiB3ZSB3YW50IHRvIHRlc3QgaWYgYSB2YWx1ZSBtYXRjaGVzIGV4YWN0bHkuCmBgYHtyfQpmaWx0ZXIobm9ybV9jb3VudHMsIFNZTUJPTCA9PSAiQ3NuMXMyYiIpCmBgYApJZiB3ZSB3YW50ZWQgdG8gZmlsdGVyIGZvciAyIGdlbmVzIHdlIGNvdWxkIHdyaXRlIHRoYXQgYXMgYmVsb3cuIEhlcmUgd2UgdXNlICJ8IiB3aGljaCBtZWFucyBvciBhcyB3ZSB3YW50IHRoZSByb3cgaWYgdGhlIFNZTUJPTCBjb2x1bW4gY29udGFpbnMgQ3NuMXMyYiBvciBTbGMyNWExLgoKYGBge3J9CmZpbHRlcihub3JtX2NvdW50cywgU1lNQk9MID09ICJDc24xczJiIiB8IFNZTUJPTCA9PSAiU2xjMjVhMSIpCmBgYAoKIyMjIyBFeGVyY2lzZQoKKiBUcnkgdG8gZmlsdGVyIGZvciBhbGwgcm93cyBjb250YWluaW5nIGNhc2VpbiBpbiB0aGUgR0VORU5BTUUgY29sdW1uLiBIaW50OiBUYWtlIGEgbG9vayBhdCB0aGUgaGVscCBmb3IgYHN0cl9kZXRlY3QoKWAgYSBmdW5jdGlvbiBmcm9tIHRoZSB0aWR5dmVyc2UgKipzdHJpbmdyKiogcGFja2FnZS4KCklmIHdlIHdhbnQgdG8gZmlsdGVyIHRoZSByb3dzIGZvciBvdXIgdG9wIDEwIGdlbmVzLiBXZSB1c2UgYFNZTUJPTCAlaW4lIHRvcDEwX3N5bXNgIHdoaWNoIG1lYW5zIHdlIHdhbnQgYWxsIHRoZSByb3dzIHdoZXJlIHRoZSBTWU1CT0wgY29sdW1uIGNvbnRhaW5zIG9uZSBvZiB0aGUgZ2VuZSBzeW1ib2xzIGluIGB0b3AxMF9zeW1zYC4gUmVtZW1iZXIsIGluIFIgd2UgdXNlIGAlaW4lYCB0byB0ZXN0IGlmIGEgdmFsdWUgaXMgaW4gYSBzZXQgb2YgdmFsdWVzLgoKYGBge3J9CnRvcDEwX2NvdW50cyA8LSBmaWx0ZXIobm9ybV9jb3VudHMsIFNZTUJPTCAlaW4lIHRvcDEwX3N5bXMpCmBgYAoKVGFrZSBhIGxvb2suCgpgYGB7cn0KdG9wMTBfY291bnRzCmBgYAoKCiMjIFNlbGVjdGluZyBjb2x1bW5zIHdpdGggYHNlbGVjdCgpYAoKV2UgZG9uJ3QgbmVlZCB0aGUgRU5UUkVaSUQgYW5kIEdFTkVOQU1FIGNvbHVtbnMsIHdlIGFyZSBvbmx5IGdvaW5nIHRvIHVzZSB0aGUgU1lNQk9MIGNvbHVtbiBhbmQgdGhlIGNvdW50cyBzbyB3ZSBjYW4gdXNlIGBzZWxlY3QoKWAgdG8gcmVtb3ZlIHRoZXNlIGNvbHVtbnMuCgpgZmlsdGVyKClgIGlzIHVzZWQgdG8gY2hvb3NlIHJvd3MsIHRvIGNob29zZSBjb2x1bW5zIHdlIHVzZSBgc2VsZWN0KClgLiBMZXQncyB0YWtlIGEgbG9vayBhdCBob3cgYHNlbGVjdCgpYCB3b3Jrcy4KCklmIHdlIHdhbnRlZCB0byBzZWxlY3QgdGhlIGdlbmUgc3ltYm9sIGNvbHVtbiB3ZSB3b3VsZCB3cml0ZSBiZWxvdy4KCmBgYHtyfQpzZWxlY3QodG9wMTBfY291bnRzLCBTWU1CT0wpCmBgYAoKSWYgd2Ugd2FudGVkIHRoZSBTWU1CT0wgY29sdW1uIGFuZCB0aGUgTUNMMS5ERyBzYW1wbGUgY29sdW1uIHdlIHdvdWxkIHdyaXRlIGJlbG93LgoKYGBge3J9CnNlbGVjdCh0b3AxMF9jb3VudHMsIFNZTUJPTCwgTUNMMS5ERykKYGBgCgpXZSBjYW4gc2VsZWN0IGNvbHVtbiByYW5nZXMgd2l0aCBgOmAuCgpgYGB7cn0Kc2VsZWN0KHRvcDEwX2NvdW50cywgRU5UUkVaSUQ6R0VORU5BTUUpCmBgYAoKVGhlcmUgYXJlIGFsc28gdXNlZnVsIGhlbHBlciBmdW5jdGlvbnMgeW91IGNhbiB1c2UgaW5zaWRlIGBzZWxlY3QoKWAsIHN1Y2ggYXMgYGNvbnRhaW5zKClgLCBgc3RhcnRzX3dpdGgoKWAgYW5kIGBlbmRzX3dpdGgoKWAuCgojIyMjIEV4ZXJjaXNlCgoqIFRyeSB0byBzZWxlY3QgYWxsIChhbmQgb25seSkgdGhlIGNvdW50cyBjb2x1bW5zLiBIaW50OiBUaGVyZSBpcyBtb3JlIHRoYW4gb25lIHdheSB0byBkbyBpdC4KCldlIGNhbiBhbHNvIHVzZSBgc2VsZWN0KClgIHRvIHJlbW92ZSBjb2x1bW5zIGJ5IHNwZWNpZnlpbmcgYSAiLSIiIGJlZm9yZSB0aGUgY29sdW1uIG5hbWUocykuCgpMZXQncyByZW1vdmUgdGhlIEVOVFJFWklEIGFuZCBHRU5FTkFNRSBjb2x1bW5zIHNvIHdlIG9ubHkga2VlcCB0aGUgU1lNQk9MIGNvbHVtbiBhbmQgdGhlIHNhbXBsZSBleHByZXNzaW9uIHZhbHVlcy4KCmBgYHtyfQp0b3AxMF9jb3VudHMgPC0gc2VsZWN0KHRvcDEwX2NvdW50cywgLUVOVFJFWklELCAtR0VORU5BTUUpCnRvcDEwX2NvdW50cwpgYGAKCj4gVGhlIGJhc2UgUiB3YXkgb2Ygc2VsZWN0aW5nIGNvbHVtbnMgCj4gCj4gQXMgbWVudGlvbmVkIGluIHRoZSB0aWR5dmVyc2UgdHV0b3JpYWwgCj4gW2hlcmVdKGh0dHBzOi8vcmF3Z2l0LmNvbS9iaW9pbmZvcm1hdGljcy1jb3JlLXNoYXJlZC10cmFpbmluZy9yLWludGVybWVkaWF0ZS9tYXN0ZXIvMi5kcGx5ci1pbnRyby1saXZlLWNvZGluZy1zY3JpcHQuaHRtbCksIAo+IGluIGJhc2UgUiB3ZSBjYW4gc2VsZWN0IGNvbHVtbnMgdXNpbmcgYCRgIG9yIGBbXWAsIGZvciBleGFtcGxlLCB0byBzZWxlY3QgdGhlIFNZTUJPTCBjb2x1bW4gd2UgY291bGQgdXNlIAo+IGB0b3AxMF9jb3VudHMkU1lNQk9MYCBvciBgdG9wMTBfY291bnRzWywgIlNZTUJPTCJdYC4gVGhlIGAkYCBvcGVyYXRvciB3b3JrcyB3ZWxsIGZvciBzaW5nbGUgY29sdW1ucywgYnV0IGZvciBtdWx0aXBsZSBjb2x1bW5zCj4gaXQgcXVpY2tseSBzdGFydHMgdG8gZ2V0IGN1bWJlcnNvbWUgYXMgd2UgbmVlZCB0byB1c2UgdGhlIGBbXWAgb3BlcmF0b3IgYW5kIGBjKClgIGZvciBjb21iaW5pbmcgdGhlIHJlcXVpcmVkIGNvbHVtbnMuIFRoZSBjb2x1bW4gCj4gbmFtZXMgYWxzbyBuZWVkIHF1b3RhdGlvbiBtYXJrcy4gRm9yIGV4YW1wbGUsIHRvIGFjY2VzcyBib3RoIHRoZSBTWU1CT0wgYW5kIEdFTkVOQU1FIAo+IGNvbHVtbnMgd2Ugd291bGQgdXNlIGB0b3AxMF9jb3VudHNbLCBjKCJTWU1CT0wiLCAiR0VORU5BTUUiKV1gIHdoZXJlYXMgd2l0aCB0aWR5dmVyc2UncyBkcGx5ciBpdCBpcyBhIGxvdCBtb3JlIGludHVpdGl2ZSAKPiBgc2VsZWN0KHRvcDEwX2NvdW50cywgU1lNQk9MLCBHRU5FTkFNRSlgLgoKCiMjIENvbnZlcnRpbmcgd2lkZSBpbnRvIHRpZHkgZm9ybWF0IHdpdGggYGdhdGhlcigpYAoKVG8gbW9yZSBlYXNpbHkgcGxvdCB3aXRoIGdncGxvdDIgd2UgbmVlZCB0byBjaGFuZ2UgdGhlIGRhdGEgaW50byBbInRpZHkgZGF0YSJdKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL1RpZHlfZGF0YSkuIFRoZXJlIGFyZSAzIHJ1bGVzIG9mIHRpZHkgZGF0YToKCiAgMS4gRWFjaCB2YXJpYWJsZSBtdXN0IGhhdmUgaXRzIG93biBjb2x1bW4uCiAgMi4gRWFjaCBvYnNlcnZhdGlvbiBtdXN0IGhhdmUgaXRzIG93biByb3cuCiAgMy4gRWFjaCB2YWx1ZSBtdXN0IGhhdmUgaXRzIG93biBjZWxsLiAKICAKSW4gb3VyIGV4cHJlc3Npb24gdGFibGUgdGhlcmUgc2hvdWxkIGJlIGp1c3QgKm9uZSBjb2x1bW4gY29udGFpbmluZyBhbGwgZXhwcmVzc2lvbiB2YWx1ZXMqIGluc3RlYWQgb2YgbXVsdGlwbGUgY29sdW1ucyB3aXRoIGNvdW50cyBmb3IgZWFjaCBzYW1wbGUuIFdlIGNhbiB1c2UgdGlkeXIncyBgZ2F0aGVyKClgIHRvIGVhc2lseSBjaGFuZ2UgdGhlIGZvcm1hdCBpbnRvIGxvbmcgZm9ybWF0LgoKYGdhdGhlcigpYCB3aWxsIHJlZm9ybWF0IHNwZWNpZmllZCBjb2x1bW5zIGludG8gdHdvIG5ldyBjb2x1bW5zLCAia2V5IiBhbmQgInZhbHVlIi4gVGhlICJrZXkiIGNvbHVtbiB3aWxsIGNvbnRhaW4gdGhlICpzcGVjaWZpZWQgY29sdW1uIG5hbWVzKiwgYW5kIHRoZSAidmFsdWUiIGNvbHVtbiB3aWxsIGNvbnRhaW4gdGhlICpzcGVjaWZpZWQgY29sdW1uIHZhbHVlcyouIEZvciBvdXIgZGF0YSwgb3VyIHNhbXBsZSBpZHMgYXJlIHRoZSBjb2x1bW4gbmFtZXMgd2Ugd2lsbCB1c2UgYW5kIHRoZSBleHByZXNzaW9uIHZhbHVlcyBhcmUgdGhlIHZhbHVlcyBpbiB0aGVzZSBjb2x1bW5zLiBXZSB0ZWxsIGdhdGhlciB3aGF0IHdlIHdhbnQgdGhlIG5ldyBrZXkgYW5kIHZhbHVlIGNvbHVtbnMgdG8gYmUgY2FsbGVkLiBXZSB3aWxsIGdpdmUgdGhlIGtleSBjb2x1bW4gdGhlIG5hbWUgIlNhbXBsZSIgYW5kIHRoZSB2YWx1ZSBjb2x1bW4gdGhlIG5hbWUgIk5vcm1fY291bnRzIiAoYXMgdGhleSBhcmUgb3VyIG5vcm1hbGlzZWQgY291bnQgdmFsdWVzKS4gYGdhdGhlcigpYCB1c2VzIHRoZSBzYW1lIG1ldGhvZHMgYXMgYHNlbGVjdCgpYCB0byBjaG9vc2UgdGhlIGNvbHVtbnMgc28gdG8gc2F5IHdlIHdhbnQgdG8gcmVmb3JtYXQgYWxsIHRoZSBzYW1wbGUgY29sdW1ucyB3ZSBjb3VsZCB3cml0ZSBiZWxvdy4KCmBgYHtyfQojIGNoYW5nZSB0byB0aWR5IGRhdGEgZm9ybWF0IChhbGwgZXhwcmVzc2lvbiB2YWx1ZXMgaW4gb25lIGNvbHVtbikKdG9wMTBfY291bnRzIDwtIGdhdGhlcih0b3AxMF9jb3VudHMsIGtleT1TYW1wbGUsIHZhbHVlPU5vcm1fY291bnRzLCBzdGFydHNfd2l0aCgiTUNMIikpCmBgYAoKVGFrZSBhIGxvb2suCmBgYHtyfQp0b3AxMF9jb3VudHMKYGBgCgpUYWtlIGEgY2xvc2VyIGxvb2sgd2l0aCBgVmlldygpYApgYGB7ciBldmFsPUZBTFNFfQpWaWV3KHRvcDEwX2NvdW50cykKYGBgCgpXZSBjYW4gYWxzbyBzcGVjaWZ5IHRoZSBjb2x1bW5zIHdlICpkb24ndCogd2FudCBnYXRoZXIgdG8gcmVmb3JtYXQgaWYgdGhhdCdzIGVhc2llciBhbmQgbGV0IGdhdGhlciBmb3JtYXQgdGhlIHJlc3QuIEZvciBleGFtcGxlLCB0aGUgY29kZSBiZWxvdyB3b3VsZCByZWZvcm1hdCBhbGwgY29sdW1ucyBleGNlcHQgU1lNQk9MIGkuZS4gYWxsIHRoZSBNQ0wgY29sdW1ucy4gSXQgd291bGQgcHJvZHVjZSB0aGUgc2FtZSByZXN1bHQgYXMgd2hlbiB3ZSBzcGVjaWZpZWQgdGhlIGNvbHVtbnMgd2Ugd2FudGVkIHdpdGggYHN0YXJ0c193aXRoKCJNQ0wiKWAuCgpgYGB7ciBldmFsPUZBTFNFfQpnYXRoZXIodG9wMTBfY291bnRzLCBrZXk9U2FtcGxlLCB2YWx1ZT1Ob3JtX2NvdW50cywgLVNZTUJPTCkKYGBgCgojIyMjIEV4ZXJjaXNlCgogKiBUcnkgcnVubmluZyBgZ2F0aGVyKClgIG9uIHRoZSBgbm9ybV9jb3VudHNgIG9iamVjdCBhbmQgc2F2ZSBpdCBhcyBhbiBvYmplY3QgY2FsbGVkIGB0ZXN0aW5nYCAoaS5lLiBydW4gYHRlc3RpbmcgPC0gZ2F0aGVyKG5vcm1fY291bnRzKWAuIFdoYXQgZG8geW91IHRoaW5rIG9mIHRoZSBvdXRwdXQ/IENhbiB5b3UgaW1wcm92ZSBpdCBzbyB0aGF0IHRoZXJlIGlzIGEgY29sdW1uIHdpdGggc2FtcGxlIGlkcyBhbmQgYSBjb2x1bW4gd2l0aCBjb3VudHMuCiAqIGBzcHJlYWQoKWAgaXMgdGhlIG9wcG9zaXRlIG9mIGBnYXRoZXIoKWAuIFRyeSBydW5uaW5nIGBzcHJlYWQoKWAgb24gdGhlIGB0b3AxMF9jb3VudHNgIG9iamVjdCBhbmQgc2VlIGlmIHlvdSBjYW4gcmVnZW5lcmF0ZSB0aGUgdGFibGUgd2l0aCBzYW1wbGVzIGluIGNvbHVtbnMuCgojIyBBZGRpbmcgY29sdW1uIHdpdGggYG11dGF0ZSgpYCBhbmQgYGNhc2Vfd2hlbigpYAoKV2Ugd2FudCB0byBwbG90IGFuZCBjb21wYXJlIHRoZSBleHByZXNzaW9uIGluIHRoZSBncm91cHMsIHNvIHdlIHVzZSBgbXV0YXRlKClgdG8gYWRkIGEgY29sdW1uIGNhbGxlZCAiR3JvdXAiIHRvIHNheSB3aGF0IGdyb3VwIGVhY2ggc2FtcGxlIGJlbG9uZ3MgdG8uICBIZXJlIHdlIHVzZSBkcGx5cidzIGBjYXNlX3doZW4oKWAgdG8gc2F5IGlmIHRoZSBzYW1lIGlkIG1hdGNoZXMgYXJlIGNvbmRpdGlvbnMgdGhlbiBhZGQgdGhlIGFwcHJvcHJpYXRlIGdyb3VwIG5hbWUgaW50byB0aGUgR3JvdXAgY29sdW1uLiBXZSBjb3VsZCB1c2UgYGlmZWxzZSgpYCBhcyB3ZSBkaWQgaW4gdGhlIHZvbGNhbm8gcGxvdCB0dXRvcmlhbC4gSG93ZXZlciwgd2hlbiB0aGVyZSBhcmUgbWFueSBjb25kaXRpb25zIHRvIHRlc3QsIGFzIHRoZXJlIGFyZSBoZXJlLCBgY2FzZV93aGVuKClgIGlzIGVhc2llciB0byB1c2UuCgpgYGB7cn0KdG9wMTBfY291bnRzIDwtIG11dGF0ZSh0b3AxMF9jb3VudHMsIEdyb3VwPWNhc2Vfd2hlbigKICAgICAgICBTYW1wbGUgJWluJSBjKCJNQ0wxLkRHIiwgIk1DTDEuREgiKSAgfiAiYmFzYWwgdmlyZ2luIiwKICAgICAgICBTYW1wbGUgJWluJSBjKCJNQ0wxLkRJIiwgIk1DTDEuREoiKSAgfiAiYmFzYWwgcHJlZ25hbnQiLAogICAgICAgIFNhbXBsZSAlaW4lIGMoIk1DTDEuREsiLCAiTUNMMS5ETCIpICB+ICJiYXNhbCBsYWN0YXRlIiwKICAgICAgICBTYW1wbGUgJWluJSBjKCJNQ0wxLkxBIiwgIk1DTDEuTEIiKSAgfiAibHVtaW5hbCB2aXJnaW4iLAogICAgICAgIFNhbXBsZSAlaW4lIGMoIk1DTDEuTEMiLCAiTUNMMS5MRCIpICB+ICJsdW1pbmFsIHByZWduYW50IiwKICAgICAgICBTYW1wbGUgJWluJSBjKCJNQ0wxLkxFIiwgIk1DTDEuTEYiKSAgfiAibHVtaW5hbCBsYWN0YXRlIgogICAgICAgKSkKYGBgCgpUYWtlIGEgbG9vayBhdCB0aGUgZGF0YSBhZ2FpbiB3aXRoIGBWaWV3KClgCmBgYHtyIGV2YWw9RkFMU0V9ClZpZXcodG9wMTBfY291bnRzKQpgYGAKCgojIyMjIEV4ZXJjaXNlCgoqIFRyeSB0byB1c2UgYG11dGF0ZSgpYCBhbmQgYGNhc2Vfd2hlbigpYCB0byBhZGQgYSBjb2x1bW4gY2FsbGVkIENlbGxUeXBlLiBUaGlzIGNvbHVtbiBzaG91bGQgY29udGFpbiB0aGUgdmFsdWUgYmFzYWwgaWYgdGhlIEdyb3VwIGNvbHVtbiBjb250YWlucyB0aGUgd29yZCBiYXNhbCwgb3IgbHVtaW5hbCBpZiB0aGUgR3JvdXAgY29udGFpbnMgbHVtaW5hbC4gU2F2ZSBpdCBhcyBhbiBvYmplY3QgY2FsbGVkIGB0ZXN0aW5nYC4gSGludDogVXNlIGBzdHJfZGV0ZWN0KClgIGluc2lkZSBgY2FzZV93aGVuKClgLgoKCiMjIENyZWF0aW5nIHN0cmlwY2hhcnRzIHdpdGggYGdlb21faml0dGVyKClgCgpOb3cgd2UgY2FuIG1ha2UgYSBzdHJpcGNoYXJ0LiBXZSBwbG90IHRoZSBHcm91cCBvbiB0aGUgWCBheGlzIGFuZCB0aGUgTm9ybV9jb3VudHMgb24gdGhlIHkgYXhpcy4gV2Ugd2lsbCB1c2UgYCsgZ2VvbV9qaXR0ZXIoKWAgdG8gY3JlYXRlIGEgaml0dGVyIHBsb3QuIEEgaml0dGVyIHBsb3QgaXMgc2ltaWxhciB0byBhIHNjYXR0ZXIgcGxvdC4gV2h5IGRvIHdlIG5vdCBqdXN0IHVzZSBhIHNjYXR0ZXIgcGxvdD8gTGV0J3MgdGFrZSBhIGxvb2suIAoKYGBge3J9CmdncGxvdChkYXRhPXRvcDEwX2NvdW50cywgbWFwcGluZz1hZXMoeD1Hcm91cCwgeT1Ob3JtX2NvdW50cykpICsKICBnZW9tX3BvaW50KCkKYGBgCgpTb21lIG9mIHRoZSBwb2ludHMgYXJlIG92ZXJsYXBwaW5nIHNvIHdlIHVzZSBgZ2VvbV9qaXR0ZXIoKWAgdG8gYWRkIGEgc21hbGwgYW1vdW50IG9mIHJhbmRvbSB2YXJpYXRpb24gdG8gdGhlIGxvY2F0aW9uIG9mIGVhY2ggcG9pbnQgc28gdGhleSBkb24ndCBvdmVybGFwLgoKYGBge3J9CmdncGxvdChkYXRhPXRvcDEwX2NvdW50cywgbWFwcGluZz1hZXMoeD1Hcm91cCwgeT1Ob3JtX2NvdW50cykpICsKICBnZW9tX2ppdHRlcigpCmBgYAoKIyMgQ3JlYXRpbmcgbXVsdGlwbGUgcGxvdHMgd2l0aCBgZmFjZXRfd3JhcCgpYAoKVGhlIHBvaW50cyBhcmUgbm8gbG9uZ2VyIG92ZXJsYXBwaW5nLCBob3dldmVyLCB0aGlzIGlzIGFsbCB0aGUgZ2VuZXMgaW4gb25lIHBsb3QuIGdncGxvdDIgaGFzIGEgcmVhbGx5IHVzZWZ1bCBmZWF0dXJlIGNhbGxlZCBmYWNldGluZyB0aGF0IHdlIGNhbiB1c2UuIGBmYWNldF93cmFwKClgIHdpbGwgY3JlYXRlIHBsb3RzIGZvciBldmVyeSB2YWx1ZSBpbiBhIGNvbHVtbiBpbiBvdXIgZGF0YS4gV2Ugd291bGQgbGlrZSBhIHN0cmlwY2hhcnQgb2YgZXhwcmVzc2lvbiB2YWx1ZXMgZm9yIGVhY2ggZ2VuZSBzbyB3ZSBhZGQgYCArIGZhY2V0X3dyYXAoflNZTUJPTClgIHRvIHNheSB3ZSB3YW50IHRvIGEgcGxvdCBmb3IgZWFjaCB2YWx1ZSBpbiB0aGUgU1lNQk9MIGNvbHVtbi4gVGhlcmUgaXMgYWxzbyBgZmFjZXRfZ3JpZCgpYCB3aGljaCBpcyBtb3N0IHVzZWZ1bCB3aGVuIHlvdSBoYXZlIHR3byBkaXNjcmV0ZSB2YXJpYWJsZXMsIGFuZCBhbGwgY29tYmluYXRpb25zIG9mIHRoZSB2YXJpYWJsZXMgZXhpc3QgaW4gdGhlIGRhdGEuCgpgYGB7cn0KZ2dwbG90KGRhdGE9dG9wMTBfY291bnRzLCBtYXBwaW5nPWFlcyh4PUdyb3VwLCB5PU5vcm1fY291bnRzKSkgKwogIGdlb21faml0dGVyKCkgKwogIGZhY2V0X3dyYXAoflNZTUJPTCkKYGBgCgpXZSBjYW4gY2hhbmdlIHRoZSBudW1iZXIgb2Ygcm93cyAoYG5yb3dzYCkgb3IgY29sdW1ucyAoYG5jb2xzYCkgdG8gYmFsYW5jZSB0aGUgcGxvdC4gTGV0J3MgdHJ5IDIgcm93cyAoYG5yb3c9MmApLgoKYGBge3J9CmdncGxvdChkYXRhPXRvcDEwX2NvdW50cywgbWFwcGluZz1hZXMoeD1Hcm91cCwgeT1Ob3JtX2NvdW50cykpICsKICBnZW9tX2ppdHRlcigpICsKICBmYWNldF93cmFwKH5TWU1CT0wsIG5yb3c9MikKYGBgCgoKV2UgY2FuIGFkZCBgY29sb3VyPUdyb3VwYCB0byBzYXkgd2Ugd2FudCB0byBjb2xvdXIgYnkgdGhlIGdyb3VwcyAodGhlIEdyb3VwIGNvbHVtbiB3ZSBhZGRlZCkuCgpgYGB7cn0KZ2dwbG90KGRhdGE9dG9wMTBfY291bnRzLCBtYXBwaW5nPWFlcyh4PUdyb3VwLCB5PU5vcm1fY291bnRzLCBjb2xvdXI9R3JvdXApKSArCiAgZ2VvbV9qaXR0ZXIoKSArCiAgZmFjZXRfd3JhcCh+U1lNQk9MLCBucm93PTIpCmBgYAoKV2UgY2FuIGFkZCBgKyB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwKWAgdG8gbWFrZSB0aGUgeCBheGlzIGxhYmVscyB2ZXJ0aWNhbCBzbyB0aGV5IGRvbid0IG92ZXJsYXAuCgpgYGB7cn0KZ2dwbG90KGRhdGE9dG9wMTBfY291bnRzLCBtYXBwaW5nPWFlcyh4PUdyb3VwLCB5PU5vcm1fY291bnRzLCBjb2xvdXI9R3JvdXApKSArCiAgZ2VvbV9qaXR0ZXIoKSArCiAgZmFjZXRfd3JhcCh+U1lNQk9MLCBucm93PTIpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwKSkgCmBgYAoKIyMgT3JkZXJpbmcgY2F0ZWdvcmllcyBhbG9uZyBhbiBheGlzCgpUaGUgZ3JvdXBzIGhhdmUgYmVlbiBwbG90dGVkIGluIGFscGhhYmV0aWNhbCBvcmRlciBvbiB0aGUgeCBheGlzLCBob3dldmVyLCB3ZSBtYXkgd2FudCB0byBjaGFuZ2UgdGhlIG9yZGVyLiBXZSBtYXkgcHJlZmVyIHRvIHBsb3QgdGhlIGdyb3VwcyBpbiBvcmRlciBvZiBzdGFnZSwgZm9yIGV4YW1wbGUsIGJhc2FsIHZpcmdpbiwgYmFzYWwgcHJlZ25hbnQsIGJhc2FsIGxhY3RhdGUsIGx1bWluYWwgdmlyZ2luLCBsdW1pbmFsIHByZWduYW50LCBsdW1pbmFsIGxhY3RhdGUuIEluIHRoZSB2b2xjYW5vIHBsb3QgdHV0b3JpYWwgd2Ugc2hvd2VkIGhvdyB0byBjaGFuZ2UgdGhlIG9yZGVyIG9mIGl0ZW1zIGluIHRoZSBsZWdlbmQgd2l0aCBgYnJlYWtzPWAgaW50byB0aGUgc2NhbGUgbGF5ZXIuIExldCdzIHRyeSB0aGF0IGhlcmUuCgpGaXJzdCBsZXQncyBtYWtlIGFuIG9iamVjdCB3aXRoIHRoZSBncm91cCBvcmRlciB0aGF0IHdlIHdhbnQuCmBgYHtyfQpncm91cF9vcmRlciA8LSBjKCJiYXNhbCB2aXJnaW4iLCAiYmFzYWwgcHJlZ25hbnQiLCAiYmFzYWwgbGFjdGF0ZSIsICJsdW1pbmFsIHZpcmdpbiIsICJsdW1pbmFsIHByZWduYW50IiwgImx1bWluYWwgbGFjdGF0ZSIpCmBgYAoKVGhlbiBsZXQncyBhZGQgdGhpcyBgZ3JvdXBfb3JkZXJgIGludG8gYGJyZWFrcz1gLgoKYGBge3J9CmdncGxvdChkYXRhPXRvcDEwX2NvdW50cywgbWFwcGluZz1hZXMoeD1Hcm91cCwgeT1Ob3JtX2NvdW50cywgY29sb3VyPUdyb3VwKSkgKwogIGdlb21faml0dGVyKCkgKwogIGZhY2V0X3dyYXAoflNZTUJPTCwgbnJvdz0yKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCkpICsKICBzY2FsZV9jb2xvdXJfZGlzY3JldGUoYnJlYWtzPWdyb3VwX29yZGVyKQpgYGAKClRoYXQgcmVvcmRlcmVkIHRoZSBsZWdlbmQgYnV0IG5vdGljZSB0aGF0IGl0IGRpZG4ndCByZW9yZGVyIHRoZSBncm91cHMgb24gdGhlIHggYXhpcy4gSWYgd2Ugd2FudCB0byByZW9yZGVyIGdyb3VwcyBhbG9uZyBhbiBheGlzIHdpdGggZ2dwbG90IHdlIG5lZWQgdG8gbWFrZSB0aGUgY29sdW1uIHdpdGggdGhlIGdyb3VwcyBpbnRvIGFuIFIgZGF0YSB0eXBlIGNhbGxlZCBhICoqZmFjdG9yKiouIEZhY3RvcnMgaW4gUiBhcmUgYSBzcGVjaWFsIGRhdGEgdHlwZSB1c2VkIHRvIHNwZWNpZnkgY2F0ZWdvcmllcy4gVGhlIG5hbWVzIG9mIHRoZSBjYXRlZ29yaWVzIGFyZSBjYWxsZWQgdGhlIGZhY3RvciAqKmxldmVscyoqLiBGYWN0b3JzIGFyZSB0aGUgb25seSBSIGRhdGEgdHlwZSB3aXRoIGxldmVscywgb3RoZXIgZGF0YSB0eXBlcywgc3VjaCBhcyBjaGFyYWN0ZXIgYW5kIG51bWVyaWMsIGRvIG5vdC4gRm9yIGluZm9ybWF0aW9uIG9uIFIgZGF0YSB0eXBlcyBzZWUgW2hlcmVdKGh0dHBzOi8vbHVjeWxlZW93LmdpdGh1Yi5pby9CYXNlUl9JbnRyby9zZXNzaW9uLTEuaHRtbCNkYXRhLXR5cGVzKQoKV2UnbGwgYWRkIGFub3RoZXIgY29sdW1uIGNhbGxlZCAiR3JvdXBfZiIgd2hlcmUgd2UnbGwgbWFrZSB0aGUgR3JvdXAgY29sdW1uIGludG8gYSBmYWN0b3IgYW5kIHNwZWNpZnkgd2hhdCBvcmRlciB3ZSB3YW50IHRoZSBsZXZlbHMgb2YgdGhlIGZhY3Rvci4KCmBgYHtyfQp0b3AxMF9jb3VudHMgPC0gbXV0YXRlKHRvcDEwX2NvdW50cywgR3JvdXBfZj1mYWN0b3IoR3JvdXAsIGxldmVscz1ncm91cF9vcmRlcikpCmBgYAoKVGFrZSBhIGxvb2sgd2l0aCBgVmlldygpYC4KYGBge3IgZXZhbD1GQUxTRX0KVmlldyh0b3AxMF9jb3VudHMpCmBgYAoKVGhlIEdyb3VwIGFuZCB0aGUgR3JvdXBfZiBjb2x1bW4gbG9vayB0aGUgc2FtZSBidXQgdGFrZSBhIGxvb2sgYnkgdHlwaW5nIGB0b3AxMF9jb3VudHNgLgoKYGBge3J9CnRvcDEwX2NvdW50cwpgYGAKCk5vdGljZSB0aGF0IHRoZSBHcm91cCBjb2x1bW4gaGFzIGA8Y2hyPmAgdW5kZXIgdGhlIGhlYWRpbmcsIHRoYXQgaW5kaWNhdGVzIGlzIGEgY2hhcmFjdGVyIGRhdGEgdHlwZSwgd2hpbGUgdGhlIEdyb3VwX2YgY29sdW1uIGhhcyBgPGZjdD5gIHVuZGVyIHRoZSBoZWFkaW5nLCBpbmRpY2F0aW5nIGl0IGlzIGEgZmFjdG9yIGRhdGEgdHlwZS4gVGhlIGBzdHIoKWAgY29tbWFuZCB0aGF0IHdlIHNhdyBpbiBmaXJzdCBwbG90cyB0dXRvcmlhbCBpcyB1c2VmdWwgdG8gY2hlY2sgdGhlIGRhdGEgdHlwZXMgaW4gb2JqZWN0cy4KCmBgYHtyfQpzdHIodG9wMTBfY291bnRzKQpgYGAKCmBzdHIoKWAgc2hvd3MgdXMgR3JvdXBfZiBjb2x1bW4gaXMgYSBGYWN0b3Igd2l0aCA2IGxldmVscyAoY2F0ZWdvcmllcykuIAoKSG93IGNhbiB3ZSBzZWUgd2hhdCB0aGVzZSBsZXZlbHMgYXJlIGFuZCB3aGF0IG9yZGVyIHRoZXkncmUgaW4/IEFyZSB0aGV5IGluIHRoZSBvcmRlciB0aGF0IHdlIHdhbnQ/IAoKVG8gc2VlIHRoZSBsZXZlbHMgYW5kIHRoZWlyIG9yZGVyIHdlIGNhbiB1c2UgYGxldmVscygpYC4gQnV0IHdlIGNhbid0IHVzZSBgbGV2ZWxzKClgIG9uIG91ciBgdG9wMTBfY291bnRzYCBvYmplY3QgYXMgaWYgd2UgZG8gd2UgZ2V0ICJOVUxMIi4KYGBge3J9CmxldmVscyh0b3AxMF9jb3VudHMpCmBgYAoKV2UgbmVlZCB0byBnaXZlIGBsZXZlbHMoKWAganVzdCB0aGUgdmFsdWVzIGluIHRoZSBHcm91cF9mIGNvbHVtbi4KCk1heWJlIHdlIG1pZ2h0IHRoaW5rIHdlIGNvdWxkIHVzZSBgc2VsZWN0KClgIHRvIHNlbGVjdCB0aGUgR3JvdXBfZiBjb2x1bW4gYW5kIHRoZW4gY2hlY2sgdGhlIGxldmVscy4gV2UnbGwgc2F2ZSB0aGUgb3V0cHV0IGluIGFuIG9iamVjdCBjYWxsZWQgYHRlc3RpbmdgIHNvIHdlIGNhbiB0ZXN0IHdoYXQgaGFwcGVucy4KCmBgYHtyfQojIEV4dHJhY3QgdGhlIEdyb3VwX2YgY29sdW1uCnRlc3RpbmcgPC0gc2VsZWN0KHRvcDEwX2NvdW50cywgR3JvdXBfZikKdGVzdGluZwpgYGAKCgpgYGB7cn0KbGV2ZWxzKHRlc3RpbmcpCmBgYAoKV2Ugc3RpbGwgZ2V0ICJOVUxMIiB3aXRoIGBsZXZlbHMoKWAuIEluc3RlYWQgb2YgZXh0cmFjdGluZyB0aGUgY29sdW1uLCB3ZSBuZWVkIHRvIGV4dHJhY3QgdGhlIHZhbHVlcyBvdXQgb2YgY29sdW1uIGZvcm1hdCBhbmQgZ2l2ZSBqdXN0IHRoZSB2YWx1ZXMgdG8gYGxldmVscygpYC4gV2UgY2FuIGRvIHRoYXQgd2l0aCBkcGx5cidzIGBwdWxsKClgLgoKYGBge3J9CiMgUHVsbCB0aGUgdmFsdWVzIG91dCBvZiB0aGUgR3JvdXBfZiBjb2x1bW4KdGVzdGluZyA8LSBwdWxsKHRvcDEwX2NvdW50cywgR3JvdXBfZikKdGVzdGluZwpgYGAKCmBgYHtyfQojIENoZWNrIHRoZSBmYWN0b3IgbGV2ZWxzCmxldmVscyh0ZXN0aW5nKQpgYGAKCk5vdyB3ZSBjYW4gc2VlIHdoYXQgdGhlIGZhY3RvciBsZXZlbHMgYXJlIGFuZCB0aGVpciBvcmRlci4KCkhvd2V2ZXIsIGluIHRoaXMgY2FzZSwgdG8gY2hlY2sgdGhlIGZhY3RvciBsZXZlbHMsIGl0IG1pZ2h0IGJlIHNpbXBsZXIgdG8gdXNlIHRoZSBiYXNlIFIgbWV0aG9kLCBhcyB3ZSBjYW4gdXNlIGAkYCB0byBhY2Nlc3NpbmcgdGhlIHZhbHVlcyBpbiBhIGNvbHVtbi4gVGhpcyB0YWtlcyB0aGUgZm9ybWF0IGBvYmplY3QkY29sdW1ubmFtZWAgZS5nLiBgdG9wMTBfY291bnRzJEdyb3VwX2ZgCgpgYGB7cn0KdG9wMTBfY291bnRzJEdyb3VwX2YKYGBgCgpTbyBgbGV2ZWxzKHRvcDEwX2NvdW50cyRHcm91cF9mKWAgd2lsbCBhY2Nlc3MgdGhlIHZhbHVlcyBpbiB0aGUgR3JvdXBfZiBjb2x1bW4gZGlyZWN0bHksIGdpdmluZyB1cyB0aGUgc2FtZSBvdXRwdXQgYXMgd2Ugd291bGQgZ2V0IHdpdGggYGxldmVscyhwdWxsKHRvcDEwX2NvdW50cywgR3JvdXBfZikpYCwgYnV0IGl0IGlzIGVhc2llciB0byByZWFkLgoKYGBge3J9CmxldmVscyh0b3AxMF9jb3VudHMkR3JvdXBfZikKYGBgCgpUaGUgbGV2ZWxzIGFyZSBpbiB0aGUgb3JkZXIgdGhhdCB3ZSB3YW50LCBzbyB3ZSBjYW4gbm93IGNoYW5nZSBvdXIgcGxvdCB0byB1c2UgdGhlICJHcm91cF9mIiBjb2x1bW4gaW5zdGVhZCBvZiBHcm91cCBjb2x1bW4gKGNoYW5nZSBgeD1gIGFuZCBgY29sb3VyPWApLgoKYGBge3J9CmdncGxvdChkYXRhPXRvcDEwX2NvdW50cywgbWFwcGluZz1hZXMoeD1Hcm91cF9mLCB5PU5vcm1fY291bnRzLCBjb2xvdXI9R3JvdXBfZikpICsKICBnZW9tX2ppdHRlcigpICsKICBmYWNldF93cmFwKH5TWU1CT0wsIG5yb3c9MikgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTApKSAKYGBgCgpOb3RlIHRoYXQgYm90aCB0aGUgbGVnZW5kIGFuZCB0aGUgeCBheGlzIG5vdyBoYXZlIHRoZSBncm91cHMgaW4gdGhlIG9yZGVyIHRoYXQgd2Ugd2FudC4KClRoZXNlIGFyZSB0aGUgdG9wIGdlbmVzIGluIHRoZSBjb21wYXJpc29uIG9mIGx1bWluYWwgY2VsbHMgZnJvbSBwcmVnbmFudCB2cyBsYWN0YXRpbmcgbWljZSBhbmQgdGhpcyB0eXBlIG9mIHBsb3QgZW5hYmxlcyB1cyB0byBzZWUgd2hhdCB0aGUgZXhwcmVzc2lvbiB2YWx1ZXMgbG9vayBsaWtlIGluIGFsbCB0aGUgZ3JvdXBzLiBXZSBjYW4gc2VlIHRoYXQgc29tZSBnZW5lcywgc3VjaCBhcyBQbXZrLCBoYXZlIG1vcmUgc2ltaWxhciBleHByZXNzaW9uIGFjcm9zcyBhbGwgdGhlIGdyb3VwcyB0aGFuIG90aGVycywgc3VjaCBhcyBDc24xczJiLgoKTm90aWNlIHRoYXQgdGhlIGdlbmVzIGhhdmUgYWxzbyBiZWVuIHBsb3R0ZWQgaW4gYWxwaGFiZXRpY2FsIG9yZGVyIGluIHRoZSBmYWNldHMuIElmIHdlIHdhbnRlZCB0byBwbG90IHRoZXNlIGdlbmVzIGluIHRoZSBvcmRlciBvZiBtb3N0IHNpZ25maWNhbnQsIHRoZW4gd2UgbmVlZCB0byBtYWtlIHN5bWJvbCBjb2x1bW4gaW50byBhIGZhY3RvciBhcyB3ZSBkaWQgZm9yIHRoZSBncm91cHMuCgoKIyMjIyBFeGVyY2lzZQoKKiBQbG90IHRoZSBnZW5lcyBpbiBvcmRlciBvZiB0aGUgc2lnbmlmaWNhbmNlLiBIaW50OiBVc2UgYG11dGF0ZSgpYCB0byBhZGQgYSBjb2x1bW4gY2FsbGVkIFNZTUJPTF9mIGNvbnRhaW5pbmcgU1lNQk9MIGFzIGEgZmFjdG9yIHdpdGggdGhlIGxldmVscyBpbiB0aGUgb3JkZXIgaW4gYHRvcDEwX3N5bXNgLiBUaGVuIHJlbWFrZSB0aGUgcGxvdCB1c2luZyB0aGUgbmV3IFNZTUJPTF9mIGNvbHVtbiBpbiBgZmFjZXRfd3JhcCgpYCBpbnN0ZWFkIG9mIFNZTUJPTC4KCgojIyBDcmVhdGluZyB3b3JrZmxvd3MgdXNpbmcgYCU+JWAKCk9uZSBvZiB0aGUgbW9zdCB1c2VmdWwgYW5kIHBvd2VyZnVsIHRoaW5ncyBhYm91dCB0aGUgdGlkeXZlcnNlIGlzIGRwbHlyJ3MgcGlwZSBvcGVyYXRvciBgJT4lYC4gVGhpcyBlbmFibGVzIHlvdSB0byBjaGFpbiBjb21tbWFuZHMgdG9nZXRoZXIgaW50byB3b3JrZmxvd3MuIEZvciBleGFtcGxlLCB3ZSBjb3VsZCBjaGFpbiB0b2dldGhlciB0aGUgY29tbWFuZHMgd2UgcmFuIGVhcmxpZXIgb24gb3VyIGB0b3AxMF9jb3VudHNgIG9iamVjdC4gTm90ZSB0aGF0IGxpa2UgdGhlIGArYCB3ZSB1c2UgdG8gYWRkIGxheWVycyB0byBhIGdncGxvdCwgYCU+JWAgZ29lcyBhdCB0aGUgZW5kIG9mIHRoZSBsaW5lIGFuZCB0aGlzICdwaXBlcycgdGhlIG91dHB1dCBpbnRvIHRoZSBuZXh0IGNvbW1hbmQuIElmIHdlIHVzZSB0aGUgYCU+JWAgd2UgYWxzbyBkb24ndCBuZWVkIHRvIHNwZWNpZnkgdGhlIGRhdGEgaW5zaWRlIHRoZSBpbmRpdmlkdWFsIGNvbW1hbmRzLCB3ZSBvbmx5IG5lZWQgdG8gc3BlY2lmeSBpdCBhdCB0aGUgYmVnaW5uaW5nIGUuZy4gaW5zdGVhZCBvZiBgZmlsdGVyKG5vcm1fY291bnRzLCBTWU1CT0wgJWluJSB0b3AxMF9zeW1zKWAgd2UgdXNlIGBub3JtX2NvdW50cyAlPiUgZmlsdGVyKFNZTUJPTCAlaW4lIHRvcDEwX3N5bXMpYC4gV2UgY2FuIGFsc28gcGlwZSBvdXRwdXRzIGRpcmVjdGx5IGludG8gYGdncGxvdCgpYC4gVGhlIHBpcGUgaXMgdmVyeSB1c2VmdWwgYnV0IHNvbWUgYWR2aWNlIG9uIHdoZW4gbm90IHRvIHVzZSB0aGUgcGlwZSBpcyBwcm92aWRlZCBieSB0aGUgdGlkeXZlcnNlIGNyZWF0b3IgSGFkbGV5IFdpY2toYW0gaW4gdGhlIGV4Y2VsbGVudCBbUiBmb3IgRGF0YSBTY2llbmNlIGJvb2tdKGh0dHBzOi8vcjRkcy5oYWQuY28ubnovcGlwZXMuaHRtbCN3aGVuLW5vdC10by11c2UtdGhlLXBpcGUpLgoKYGBge3J9Cm5vcm1fY291bnRzICU+JQogIGZpbHRlcihTWU1CT0wgJWluJSB0b3AxMF9zeW1zKSAlPiUgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgZmlsdGVyIHJvd3MgZm9yIHRoZSB0b3AgMTAgZ2VuZXMKICBzZWxlY3QoLUVOVFJFWklELCAtR0VORU5BTUUpICU+JSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIHJlbW92ZSBFTlRSRVpJRCBhbmQgR0VORU5BTUUgY29sdW1ucwogIGdhdGhlcihrZXk9U2FtcGxlLCB2YWx1ZT1Ob3JtX2NvdW50cywgc3RhcnRzX3dpdGgoIk1DTCIpKSAgJT4lICAgICAgICAgICAgICAgICAgICAjIGNvbnZlcnQgZnJvbSB3aWRlIHRvIGxvbmcgKHRpZHkpIGZvcm1hdAogIG11dGF0ZShHcm91cD1jYXNlX3doZW4oICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgYWRkIGEgY29sdW1uIHRvIHNwZWNpZnkgZ3JvdXBzCiAgICAgICAgU2FtcGxlICVpbiUgYygiTUNMMS5ERyIsICJNQ0wxLkRIIikgIH4gImJhc2FsIHZpcmdpbiIsCiAgICAgICAgU2FtcGxlICVpbiUgYygiTUNMMS5ESSIsICJNQ0wxLkRKIikgIH4gImJhc2FsIHByZWduYW50IiwKICAgICAgICBTYW1wbGUgJWluJSBjKCJNQ0wxLkRLIiwgIk1DTDEuREwiKSAgfiAiYmFzYWwgbGFjdGF0ZSIsCiAgICAgICAgU2FtcGxlICVpbiUgYygiTUNMMS5MQSIsICJNQ0wxLkxCIikgIH4gImx1bWluYWwgdmlyZ2luIiwKICAgICAgICBTYW1wbGUgJWluJSBjKCJNQ0wxLkxDIiwgIk1DTDEuTEQiKSAgfiAibHVtaW5hbCBwcmVnbmFudCIsCiAgICAgICAgU2FtcGxlICVpbiUgYygiTUNMMS5MRSIsICJNQ0wxLkxGIikgIH4gImx1bWluYWwgbGFjdGF0ZSIKICAgICAgICkpICU+JQogIG11dGF0ZShHcm91cF9mPWZhY3RvcihHcm91cCwgbGV2ZWxzPWdyb3VwX29yZGVyKSkgJT4lICAgICAgICAgICAjIGNvbnZlcnQgR3JvdXAgY29sdW1uIHRvIGZhY3RvciBkYXRhIHR5cGUgdG8gc3BlY2lmeSBvcmRlcmluZwogIGdncGxvdChtYXBwaW5nPWFlcyh4PUdyb3VwX2YsIHk9Tm9ybV9jb3VudHMsIGNvbG91cj1Hcm91cF9mKSkgKyAjIG1ha2Ugc3RyaXBjaGFydHMgZmFjZXRlZCBieSBnZW5lCiAgZ2VvbV9qaXR0ZXIoKSArCiAgZmFjZXRfd3JhcCh+U1lNQk9MLCBucm93PTIpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwKSkgCmBgYApXZSBvbmx5IGhhdmUgdHdvIHBvaW50cyBwZXIgZ3JvdXAgaGVyZSBidXQgaWYgdGhlcmUgd2VyZSBtb3JlIHBvaW50cyB5b3UgY291bGQgb3ZlcmxheSBhbiBlcnJvciBiYXIgd2l0aCBgZ2VvbV9lcnJvcmJhcigpYCBvciBjb21iaW5lIGEgYGdlb21fYm94cGxvdCgpYCB3aXRoIHRoZSBgZ2VvbV9qaXR0ZXIoKWAsIGFzIHNob3duIGZvciB0aGUgc3RyaXBjaGFydHMgaW4gdGhlIHR1dG9yaWFsIFtoZXJlXShodHRwczovL3d3dy5iaW9pbmZvcm1hdGljcy5iYWJyYWhhbS5hYy51ay90cmFpbmluZy9nZ3Bsb3RfY291cnNlL0ludHJvZHVjdGlvbiUyMHRvJTIwZ2dwbG90LnBkZikuCgojIyMjIEV4ZXJjaXNlCgoqIFN0YXJ0aW5nIGZyb20gYG5vcm1fY291bnRzYCBtYWtlIGEgd29ya2Zsb3cgdXNpbmcgYCU+JWAgdGhhdCBjcmVhdGVzIHN0cmlwY2hhcnRzIGZvciB0aGUgZ2VuZXMgVHJwNTMsIEJyY2ExLCBCcmNhMi4KCgojIyBLZXkgUG9pbnRzCi0gV2UgY2FuIGNob29zZSByb3dzIHdpdGggZHBseXIncyBgZmlsdGVyKClgCi0gV2UgY2FuIGNob29zZSBjb2x1bW5zIHdpdGggZHBseXIncyBgc2VsZWN0KClgCi0gV2UgY2FuIHRlc3QgZm9yIG11bHRpcGxlIGNvbmRpdGlvbnMgd2l0aCBkcGx5cidzIGBjYXNlX3doZW4oKWAKLSBXZSBjYW4gY29udmVydCBmcm9tIHdpZGUgdG8gbG9uZyAodGlkeSkgZm9ybWF0IHdpdGggdGlkeXIncyBgZ2F0aGVyKClgCi0gV2UgY2FuIHVzZSBgZ2VvbV9qaXR0ZXIoKWAgdG8gbWFrZSBzdHJpcGNoYXJ0cwotIFdlIGNhbiBtYWtlIG11bHRpcGxlIHBsb3RzIGJhc2VkIG9uIHZhbHVlcyBpbiBhIGNvbHVtbiB3aXRoIGBmYWNldF93cmFwKClgCi0gV2UgY2FuIGNyZWF0ZSB3b3JrZmxvd3Mgd2l0aCBkcGx5cidzIHBpcGUgYCU+JWAK