This R Notebook is the complement to my blog post Analyzing IMDb Data The Intended Way, with R and ggplot2.

This notebook is licensed under the MIT License. If you use the code or data visualization designs contained within this notebook, it would be greatly appreciated if proper attribution is given back to this notebook and/or myself. Thanks! :)

IMDb data retrieved on July 4th 2018.

Information courtesy of IMDb (http://www.imdb.com). Used with permission.

library(tidyverse)
── Attaching packages ───────────────────────────────────── tidyverse 1.2.1 ──
✔ ggplot2 3.0.0     ✔ purrr   0.2.5
✔ tibble  1.4.2     ✔ dplyr   0.7.6
✔ tidyr   0.8.1     ✔ stringr 1.3.1
✔ readr   1.1.1     ✔ forcats 0.3.0
package ‘dplyr’ was built under R version 3.5.1── Conflicts ──────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag()    masks stats::lag()
library(ggridges)   # unused in final blog post

Attaching package: ‘ggridges’

The following object is masked from ‘package:ggplot2’:

    scale_discrete_manual
library(tidytext)   # unused in final blog post
library(scales)

Attaching package: ‘scales’

The following object is masked from ‘package:purrr’:

    discard

The following object is masked from ‘package:readr’:

    col_factor
sessionInfo()
R version 3.5.0 (2018-04-23)
Platform: x86_64-apple-darwin15.6.0 (64-bit)
Running under: macOS High Sierra 10.13.5

Matrix products: default
BLAS: /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libBLAS.dylib
LAPACK: /Library/Frameworks/R.framework/Versions/3.5/Resources/lib/libRlapack.dylib

locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8

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

other attached packages:
 [1] scales_0.5.0    tidytext_0.1.9  ggridges_0.5.0  forcats_0.3.0  
 [5] stringr_1.3.1   dplyr_0.7.6     purrr_0.2.5     readr_1.1.1    
 [9] tidyr_0.8.1     tibble_1.4.2    ggplot2_3.0.0   tidyverse_1.2.1

loaded via a namespace (and not attached):
 [1] tidyselect_0.2.4  reshape2_1.4.3    haven_1.1.2       lattice_0.20-35  
 [5] colorspace_1.3-2  SnowballC_0.5.1   yaml_2.1.19       rlang_0.2.1      
 [9] pillar_1.2.3      foreign_0.8-70    glue_1.2.0        withr_2.1.2      
[13] modelr_0.1.2      readxl_1.1.0      bindrcpp_0.2.2    bindr_0.1.1      
[17] plyr_1.8.4        munsell_0.5.0     gtable_0.2.0      cellranger_1.1.0 
[21] rvest_0.3.2       psych_1.8.4       knitr_1.20        parallel_3.5.0   
[25] broom_0.4.5       tokenizers_0.2.1  Rcpp_0.12.17      jsonlite_1.5     
[29] mnormt_1.5-5      hms_0.4.2         stringi_1.2.3     grid_3.5.0       
[33] cli_1.0.0         tools_3.5.0       magrittr_1.5      lazyeval_0.2.1   
[37] janeaustenr_0.1.5 crayon_1.3.4      pkgconfig_2.0.1   Matrix_1.2-14    
[41] xml2_1.2.0        lubridate_1.7.4   assertthat_0.2.0  httr_1.3.1       
[45] rstudioapi_0.7    R6_2.2.2          nlme_3.1-137      compiler_3.5.0   

Helper function to read IMDB files given filename.

read_imdb <- function(data_path) {
  path <- "/Volumes/Extreme 510/Data/imdb/"
  read_tsv(paste0(path, data_path), na = "\\N", quote='', progress=F)
}

Helper function to pretty print the size of a dataframe for charts/notebook.

ppdf <- function(df) {
  df %>% nrow() %>% comma()
}

1 Ratings

df_ratings <- read_imdb("title.ratings.tsv")
Parsed with column specification:
cols(
  tconst = col_character(),
  averageRating = col_double(),
  numVotes = col_integer()
)
df_ratings %>% head()

There are 847,394 ratings in the dataset.

Plot every point. (note: very slow!)

plot <- ggplot(df_ratings, aes(x = numVotes, y = averageRating)) +
          geom_point()

ggsave("imdb-0.png", plot, width=4, height=3)

Plot a 2D histogram and clean up axes.

plot <- ggplot(df_ratings, aes(x = numVotes, y = averageRating)) +
          geom_bin2d() +
          scale_x_log10(labels = comma) +
          scale_y_continuous(breaks = 1:10) +
          scale_fill_viridis_c(labels = comma)
ggsave("imdb-1.png", plot, width=4, height=3)

2 Title Basics

df_basics <- read_imdb("title.basics.tsv")
Parsed with column specification:
cols(
  tconst = col_character(),
  titleType = col_character(),
  primaryTitle = col_character(),
  originalTitle = col_character(),
  isAdult = col_integer(),
  startYear = col_integer(),
  endYear = col_character(),
  runtimeMinutes = col_integer(),
  genres = col_character()
)
df_basics %>% head()

There are 5,114,944 titles in the dataset.

Merge df_ratings and df_basics to perform ratings/vote analysis using more metadata.

df_ratings <- df_ratings %>% left_join(df_basics)
Joining, by = "tconst"
df_ratings %>% head()
plot <- ggplot(df_ratings, aes(x = runtimeMinutes, y = averageRating)) +
          geom_bin2d() +
          scale_x_continuous(labels = comma) +
          scale_y_continuous(breaks = 1:10) +
          scale_fill_viridis_c()
ggsave("imdb-2a.png", plot, width=4, height=3)

Which movies have the superhigh runtimes?

df_ratings %>% arrange(desc(runtimeMinutes)) %>% head(10)
plot <- ggplot(df_ratings %>% filter(runtimeMinutes < 180, titleType=="movie", numVotes >= 10), aes(x = runtimeMinutes, y = averageRating)) +
          geom_bin2d() +
          scale_x_continuous(breaks = seq(0, 180, 60), labels = 0:3) +
          scale_y_continuous(breaks = 0:10) +
          scale_fill_viridis_c(option = "inferno", labels = comma) +
          theme_minimal(base_family = "Source Sans Pro", base_size=8) +
          labs(title="Relationship between Movie Runtime and Average Movie Rating",
               subtitle="Data from IMDb retrieved July 4th, 2018",
               x="Runtime (Hours)",
               y="Average User Rating",
               caption="Max Woolf — minimaxir.com",
               fill="# Movies")
ggsave("imdb-2b.png", plot, width=4, height=3)

## Unnesting Genres

How to facet by genre; this was removed from the post since a little complicated.

NB: you cannot use default tokenization on unnest_tokens since some tokens have dashes. (e.g. film-noir)

df_ratings_unnest <- df_ratings %>%
                        filter(runtimeMinutes < 180, titleType=="movie", numVotes >= 10) %>%
                        select(runtimeMinutes, averageRating, genres) %>%
                        unnest_tokens(genre, genres, token = str_split, pattern = ",")
df_ratings_unnest %>% head(10)
plot <- ggplot(df_ratings_unnest, aes(x = runtimeMinutes, y = averageRating)) +
          geom_bin2d() +
          scale_x_continuous(breaks = seq(0, 180, 60), labels = 0:3) +
          scale_y_continuous(breaks = 1:10) +
          scale_fill_viridis_c(option = "inferno", labels = comma) +
          theme_minimal(base_family = "Source Sans Pro", base_size=9) +
          labs(title="Relationship between Movie Runtime and Average Mobie Rating",
               subtitle="Data from IMDb retrieved July 4th, 2018",
               x="Runtime (Hours)",
               y="Average User Rating",
               caption="Max Woolf — minimaxir.com",
               fill="# Movies") +
          facet_wrap(~ genre)
ggsave("imdb-3.png", plot, width=6, height=6)

Normalize by facet. There are two approaches:

  1. Do a weighted sum of the points in the spatial area, where the weight is the reciprocol of the # of points in the facet.
  2. Manually calculate bins/counts and scale to [0, 1]

Option 1 is a bit easier to implement. Additionally, remove facets with little data.

squish trick via https://stackoverflow.com/a/23655697/9314418

df_temp <- df_ratings_unnest %>%
            filter(!(genre %in% c("game-show", "reality-tv", "short", "talk-show", NA))) %>%
            group_by(genre) %>%
            mutate(prop = 1/n())
plot <- ggplot(df_temp, aes(x = runtimeMinutes, y = averageRating, z=prop)) +
          stat_summary_2d(fun=sum) +
          scale_x_continuous(breaks = seq(0, 180, 60), labels = 0:3) +
          scale_y_continuous(breaks = 1:10) +
          scale_fill_viridis_c(option = "inferno", labels = comma, limits=c(0, 0.02), oob=squish, guide=F) +
          theme_minimal(base_family = "Source Sans Pro", base_size=9) +
          labs(title="Relationship between Movie Runtime and Average Mobie Rating",
               subtitle="Data from IMDb retrieved July 4th, 2018",
               x="Runtime (Hours)",
               y="Average User Rating",
               caption="Max Woolf — minimaxir.com") +
          facet_wrap(~ genre)
ggsave("imdb-3b.png", plot, width=6, height=6)

3 Rating vs. Movie Year

Set theme to custom theme based on theme_minimal for the rest of the notebook.

theme_set(theme_minimal(base_size=9, base_family="Source Sans Pro") +
            theme(plot.title = element_text(size=8, family="Source Sans Pro Bold", margin=margin(t = -0.1, b = 0.1, unit='cm')),
                  axis.title.x = element_text(size=8),
                  axis.title.y = element_text(size=8),
                  plot.subtitle = element_text(family="Source Sans Pro Semibold", color="#969696", size=6),
                  plot.caption = element_text(size=6, color="#969696"),
                  legend.title = element_text(size=8),
                  legend.key.width = unit(0.25, unit='cm')))
plot <- ggplot(df_ratings %>% filter(titleType=="movie", numVotes >= 10), aes(x = startYear, y = averageRating)) +
          geom_bin2d() +
          geom_smooth(color="black") +
          scale_x_continuous() +
          scale_y_continuous(breaks = 1:10) +
          scale_fill_viridis_c(option = "plasma", labels = comma, trans='log10') +
          labs(title="Relationship between Movie Release Year and Average Rating",
               subtitle=sprintf("For %s Movies/Ratings. Data from IMDb retrieved 7/4/2018", df_ratings %>% filter(titleType=="movie", numVotes >= 10) %>% ppdf),
               x="Year Movie was Released",
               y="Average User Rating For Movie",
               caption="Max Woolf — minimaxir.com",
               fill="# Movies")
ggsave("imdb-4.png", plot, width=4, height=3)

Work with Ridge plots; not included in post because it doesn’t offer much insight different from the chart above.

NB: For ridge plots, the y-axis must be a factor, not a numeric; this is what tripped me up in the stream.

plot <- ggplot(df_ratings %>% filter(startYear >= 2000, titleType=="movie",  numVotes >= 10) %>% mutate(startYear = factor(startYear)), aes(x = averageRating, y = startYear, fill=startYear)) +
          geom_density_ridges() +
          scale_fill_hue(guide=F) +
          scale_x_continuous(breaks = 1:10) +
          theme_minimal(base_family = "Source Sans Pro", base_size=9)
ggsave("imdb-5.png", plot, width=4, height=3)

Bucket by decades.

df_ratings_decades <- df_ratings %>%
                        filter(startYear>=1950, titleType=="movie",  numVotes >= 10) %>%
  mutate(decade = fct_rev(factor(cut_width(startYear, 10, boundary=0), labels = paste0(seq(1950, 2010, 10), "s"))))
df_ratings_decades %>% head()
plot <- ggplot(df_ratings_decades, aes(x = averageRating, y = decade, fill=0.5 - abs(0.5-..ecdf..))) +
          geom_density_ridges_gradient(calc_ecdf=T, quantile_lines=T) +
          scale_fill_viridis_c(option = "plasma", guide=F) +
          scale_x_continuous(breaks = 1:10) +
          #scale_y_discrete(expand = c(0,01, 0)) +
          theme_minimal(base_family = "Source Sans Pro", base_size=9)
ggsave("imdb-6.png", plot, width=4, height=3)
plot <- ggplot(df_ratings_decades, aes(x = runtimeMinutes, y = decade, fill=0.5 - abs(0.5-..ecdf..))) +
          geom_density_ridges_gradient(calc_ecdf=T, quantile_lines=T) +
          scale_fill_viridis_c(option = "plasma", guide=F) +
          scale_x_continuous(breaks = seq(0, 180, 60), limits=c(0,180), labels = 0:3) +
          theme_minimal(base_family = "Source Sans Pro", base_size=9)
ggsave("imdb-7.png", plot, width=4, height=3)

3.1 Episode Analysis

For reference; not included in post. (Too much bad data to clean.)

df_episode <- read_imdb("title.episode.tsv") %>% filter(!is.na(seasonNumber))
Parsed with column specification:
cols(
  tconst = col_character(),
  parentTconst = col_character(),
  seasonNumber = col_integer(),
  episodeNumber = col_integer()
)
df_episode %>% head()

There are 2,664,673 episodes in the dataset.

df_episode_count <- df_episode %>%
                group_by(parentTconst, seasonNumber) %>%
                tally() %>%
                left_join(df_basics,  c("parentTconst" = "tconst"))
df_episode_count %>% head()

4 Actor Information

str_detect is vectorized and much faster than using a loop/lapply. Using a regular expression to search for actor or actress is another speed increase.

df_actors <- read_imdb("name.basics.tsv") %>%
                filter(str_detect(primaryProfession, "actor|actress"))  %>%
                select(nconst, primaryName,  birthYear)
Parsed with column specification:
cols(
  nconst = col_character(),
  primaryName = col_character(),
  birthYear = col_integer(),
  deathYear = col_integer(),
  primaryProfession = col_character(),
  knownForTitles = col_character()
)
df_actors %>% head()

There are 3,351,010 actors in the dataset.

df_principals <- read_imdb("title.principals.tsv") %>%
  filter(str_detect(category, "actor|actress")) %>%
  select(tconst, ordering, nconst, category) %>%
  group_by(tconst) %>%
  filter(ordering == min(ordering))
Parsed with column specification:
cols(
  tconst = col_character(),
  ordering = col_integer(),
  nconst = col_character(),
  category = col_character(),
  job = col_character(),
  characters = col_character()
)
df_principals %>% head()

There are 2,813,299 principals/rows in the dataset.

Join the 2 dataframes. (onto principals, since Many-to-One)

df_principals <- df_principals %>% left_join(df_actors)
Joining, by = "nconst"
df_principals %>% head()

5 Putting It All Together

Merge actor information onto the full ratings dataframe.

df_ratings <- df_ratings %>% left_join(df_principals)
Joining, by = "tconst"
df_ratings %>% head()

Filter down to movies w/ actor info. (only if the birth year is present in the data)

df_ratings_movies <- df_ratings %>%
                        filter(titleType == "movie", !is.na(birthYear), numVotes >= 10) %>%
                        mutate(age_lead = startYear - birthYear) %>%
                        arrange(desc(numVotes))
df_ratings_movies %>% head(100)

Aggregate lead-actor/actress ages by movie year w/ percentiles.

df_actor_ages <- df_ratings_movies %>%
                  group_by(startYear) %>%
                  summarize(low_age = quantile(age_lead, 0.25, na.rm=T),
                            med_age = quantile(age_lead, 0.50, na.rm=T),
                            high_age = quantile(age_lead, 0.75, na.rm=T)) %>%
                  arrange(startYear)
df_actor_ages %>% head()

Create a ribbon plot.

NB: Plot the ribbon before the line, so the line is on top.

plot <- ggplot(df_actor_ages %>% filter(startYear >= 1920) , aes(x = startYear)) +
          geom_ribbon(aes(ymin=low_age, ymax=high_age), alpha=0.2) +
          geom_line(aes(y=med_age)) +
          labs(title="Change in Ages of Movie Lead Actors/Actress Over Time",
               subtitle=sprintf("For %s Actors. Line represents median age.\nRibbon bounds represent 25th — 75th Percentiles. Data from IMDb retrieved 7/4/2018",df_ratings_movies %>% filter(startYear >= 1920) %>% ppdf()),
               x="Year Movie was Released",
               y="Age of Lead Actor/Actress",
               caption="Max Woolf — minimaxir.com",
               fill="# Movies")
ggsave("imdb-8.png", plot, width=4, height=3)

Create a plot comparing actors/actresses. Same code, except adding an aggregation and aestetic on category.

df_actor_ages_lead <- df_ratings_movies %>%
                  group_by(startYear, category) %>%
                  summarize(low_age = quantile(age_lead, 0.25, na.rm=T),
                            med_age = quantile(age_lead, 0.50, na.rm=T),
                            high_age = quantile(age_lead, 0.75, na.rm=T)) %>%
                  arrange(startYear)
df_actor_ages_lead %>% head()
plot <- ggplot(df_actor_ages_lead %>% filter(startYear >= 1920), aes(x = startYear, fill=category, color=category)) +
          geom_ribbon(aes(ymin=low_age, ymax=high_age), alpha=0.2, size=0) +
          geom_line(aes(y=med_age)) +
          scale_fill_brewer(palette="Set1") +
          scale_color_brewer(palette="Set1") +
          labs(title="Change in Ages of Movie Lead Actors/Actress Over Time",
               subtitle=sprintf("For %s Actors. Line represents median age.\nRibbon bounds represent 25th — 75th Percentiles. Data from IMDb retrieved 7/4/2018",df_ratings_movies %>% filter(startYear >= 1920) %>% ppdf()),
               x="Year Movie was Released",
               y="Age of Lead Actor/Actress",
               caption="Max Woolf — minimaxir.com",
               fill='',
               color='')
ggsave("imdb-9.png", plot, width=4, height=3)

Same plot, but facet. (unused in final post since may not be enough data/similar accross all genres)

df_actor_ages_lead <- df_ratings_movies %>%
                  select(startYear, category, genres, age_lead) %>%
                  unnest_tokens(genre, genres, token = str_split, pattern = ",") %>%
                  filter(!(genre %in% c("game-show", "reality-tv", "short", "talk-show", "film-noir", NA))) %>%
                  group_by(startYear, category, genre) %>%
                  summarize(low_age = quantile(age_lead, 0.25, na.rm=T),
                            med_age = quantile(age_lead, 0.50, na.rm=T),
                            high_age = quantile(age_lead, 0.75, na.rm=T)) %>%
                  arrange(startYear)
df_actor_ages_lead %>% head()
plot <- ggplot(df_actor_ages_lead %>% filter(startYear >= 1950), aes(x = startYear, fill=category, color=category)) +
          geom_ribbon(aes(ymin=low_age, ymax=high_age), alpha=0.2, size=0) +
          geom_line(aes(y=med_age), size=0.5) +
          theme_minimal(base_family = "Source Sans Pro", base_size=9) +
          scale_fill_brewer(palette="Set1") +
          scale_color_brewer(palette="Set1") +
          facet_wrap(~ genre)
ggsave("imdb-10.png", plot, width=6, height=6)

6 Lead Gender Balance

Unused in post since a bit more complicated to explain and results need double-checking.

plot <- ggplot(df_ratings_movies %>% filter(startYear >= 1950), aes(x = startYear, fill=category)) +
          geom_bar(position="fill", width=1) +
          theme_minimal(base_family = "Source Sans Pro", base_size=9) +
          scale_fill_brewer(palette="Set1") +
          scale_color_brewer(palette="Set1")
ggsave("imdb-11.png", plot, width=4, height=3)

7 nth time lead

df_ratings_movies_nth <- df_ratings_movies %>%
                      group_by(nconst) %>%
                      arrange(startYear) %>%
                      mutate(nth_lead = row_number()) %>%
                      #filter(nth_lead <= 50) %>%
                      ungroup() %>%
                      arrange(desc(startYear), desc(numVotes))
df_ratings_movies_nth %>% select(primaryTitle, primaryName, nth_lead) %>% head(100)
df_actor_ages <- df_ratings_movies_nth %>%
                  group_by(startYear) %>%
                  summarize(low_nth = quantile(nth_lead, 0.25),
                            med_nth = quantile(nth_lead, 0.50),
                            high_nth = quantile(nth_lead, 0.75)) %>%
                  arrange(startYear)
df_actor_ages %>% head()
plot <- ggplot(df_actor_ages %>% filter(startYear >= 1950) , aes(x = startYear)) +
          geom_ribbon(aes(ymin=low_nth, ymax=high_nth), alpha=0.2) +
          geom_line(aes(y=med_nth)) +
          scale_y_continuous(breaks=c(1:5, 10)) +
          labs(title="#th Time Lead Actor of Movie Was A Lead Actor, Over Time",
               subtitle=sprintf("For %s Lead Actors. Line represents median #.\nRibbon bounds represent 25th — 75th Percentiles. Data from IMDb retrieved 7/4/2018",df_ratings_movies_nth %>% filter(startYear >= 1950) %>% ppdf()),
               x="Year",
               y="#th Time Lead Actor was a Lead Actor",
               caption="Max Woolf — minimaxir.com",
               fill="# Movies") +
          theme(panel.grid.minor = element_blank())
ggsave("imdb-12.png", plot, width=4, height=3)

8 LICENSE

The MIT License (MIT)

Copyright (c) 2018 Max Woolf

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

LS0tCnRpdGxlOiAiQW5hbHl6aW5nIElNRGIgRGF0YSBUaGUgSW50ZW5kZWQgV2F5LCB3aXRoIFIgYW5kIGdncGxvdDIiCmF1dGhvcjogIk1heCBXb29sZiAoQG1pbmltYXhpcikiCmRhdGU6ICIyMDE4LTA3LTE1IgpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazoKICAgIGhpZ2hsaWdodDogdGFuZ28KICAgIG1hdGhqYXg6IG51bGwKICAgIG51bWJlcl9zZWN0aW9uczogeWVzCiAgICB0aGVtZTogc3BhY2VsYWIKICAgIHRvYzogVHJ1ZQotLS0KClRoaXMgUiBOb3RlYm9vayBpcyB0aGUgY29tcGxlbWVudCB0byBteSBibG9nIHBvc3QgW0FuYWx5emluZyBJTURiIERhdGEgVGhlIEludGVuZGVkIFdheSwgd2l0aCBSIGFuZCBnZ3Bsb3QyXShodHRwOi8vbWluaW1heGlyLmNvbS8yMDE4LzA3L2ltZGItZGF0YS1hbmFseXNpcy8pLgoKVGhpcyBub3RlYm9vayBpcyBsaWNlbnNlZCB1bmRlciB0aGUgTUlUIExpY2Vuc2UuIElmIHlvdSB1c2UgdGhlIGNvZGUgb3IgZGF0YSB2aXN1YWxpemF0aW9uIGRlc2lnbnMgY29udGFpbmVkIHdpdGhpbiB0aGlzIG5vdGVib29rLCBpdCB3b3VsZCBiZSBncmVhdGx5IGFwcHJlY2lhdGVkIGlmIHByb3BlciBhdHRyaWJ1dGlvbiBpcyBnaXZlbiBiYWNrIHRvIHRoaXMgbm90ZWJvb2sgYW5kL29yIG15c2VsZi4gVGhhbmtzISA6KQoKSU1EYiBkYXRhIHJldHJpZXZlZCBvbiBKdWx5IDR0aCAyMDE4LgoKCioqSW5mb3JtYXRpb24gY291cnRlc3kgb2YKSU1EYgooaHR0cDovL3d3dy5pbWRiLmNvbSkuClVzZWQgd2l0aCBwZXJtaXNzaW9uLioqCgoKYGBge3J9CmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KGdncmlkZ2VzKSAgICMgdW51c2VkIGluIGZpbmFsIGJsb2cgcG9zdApsaWJyYXJ5KHRpZHl0ZXh0KSAgICMgdW51c2VkIGluIGZpbmFsIGJsb2cgcG9zdApsaWJyYXJ5KHNjYWxlcykKCnNlc3Npb25JbmZvKCkKYGBgCgpIZWxwZXIgZnVuY3Rpb24gdG8gcmVhZCBJTURCIGZpbGVzIGdpdmVuIGZpbGVuYW1lLgoKYGBge3J9CnJlYWRfaW1kYiA8LSBmdW5jdGlvbihkYXRhX3BhdGgpIHsKICBwYXRoIDwtICIvVm9sdW1lcy9FeHRyZW1lIDUxMC9EYXRhL2ltZGIvIgogIHJlYWRfdHN2KHBhc3RlMChwYXRoLCBkYXRhX3BhdGgpLCBuYSA9ICJcXE4iLCBxdW90ZT0nJywgcHJvZ3Jlc3M9RikKfQpgYGAKCkhlbHBlciBmdW5jdGlvbiB0byBwcmV0dHkgcHJpbnQgdGhlIHNpemUgb2YgYSBkYXRhZnJhbWUgZm9yIGNoYXJ0cy9ub3RlYm9vay4KCmBgYHtyfQpwcGRmIDwtIGZ1bmN0aW9uKGRmKSB7CiAgZGYgJT4lIG5yb3coKSAlPiUgY29tbWEoKQp9CmBgYAoKCiMgUmF0aW5ncwoKYGBge3J9CmRmX3JhdGluZ3MgPC0gcmVhZF9pbWRiKCJ0aXRsZS5yYXRpbmdzLnRzdiIpCmRmX3JhdGluZ3MgJT4lIGhlYWQoKQpgYGAKClRoZXJlIGFyZSAqKmByIGRmX3JhdGluZ3MgJT4lIHBwZGYoKWAqKiByYXRpbmdzIGluIHRoZSBkYXRhc2V0LgoKUGxvdCBldmVyeSBwb2ludC4gKG5vdGU6IHZlcnkgc2xvdyEpCgpgYGB7ciBldmFsPUZBTFNFfQpwbG90IDwtIGdncGxvdChkZl9yYXRpbmdzLCBhZXMoeCA9IG51bVZvdGVzLCB5ID0gYXZlcmFnZVJhdGluZykpICsKICAgICAgICAgIGdlb21fcG9pbnQoKQoKZ2dzYXZlKCJpbWRiLTAucG5nIiwgcGxvdCwgd2lkdGg9NCwgaGVpZ2h0PTMpCmBgYAoKIVtdKGltZGItMC5wbmcpCgpQbG90IGEgMkQgaGlzdG9ncmFtIGFuZCBjbGVhbiB1cCBheGVzLgoKYGBge3J9CnBsb3QgPC0gZ2dwbG90KGRmX3JhdGluZ3MsIGFlcyh4ID0gbnVtVm90ZXMsIHkgPSBhdmVyYWdlUmF0aW5nKSkgKwogICAgICAgICAgZ2VvbV9iaW4yZCgpICsKICAgICAgICAgIHNjYWxlX3hfbG9nMTAobGFiZWxzID0gY29tbWEpICsKICAgICAgICAgIHNjYWxlX3lfY29udGludW91cyhicmVha3MgPSAxOjEwKSArCiAgICAgICAgICBzY2FsZV9maWxsX3ZpcmlkaXNfYyhsYWJlbHMgPSBjb21tYSkKCmdnc2F2ZSgiaW1kYi0xLnBuZyIsIHBsb3QsIHdpZHRoPTQsIGhlaWdodD0zKQpgYGAKCiFbXShpbWRiLTEucG5nKQoKCgojIFRpdGxlIEJhc2ljcwoKYGBge3J9CmRmX2Jhc2ljcyA8LSByZWFkX2ltZGIoInRpdGxlLmJhc2ljcy50c3YiKQpkZl9iYXNpY3MgJT4lIGhlYWQoKQpgYGAKClRoZXJlIGFyZSAqKmByIGRmX2Jhc2ljcyAlPiUgcHBkZigpYCoqIHRpdGxlcyBpbiB0aGUgZGF0YXNldC4KCk1lcmdlIGBkZl9yYXRpbmdzYCBhbmQgYGRmX2Jhc2ljc2AgdG8gcGVyZm9ybSByYXRpbmdzL3ZvdGUgYW5hbHlzaXMgdXNpbmcgbW9yZSBtZXRhZGF0YS4KCmBgYHtyfQpkZl9yYXRpbmdzIDwtIGRmX3JhdGluZ3MgJT4lIGxlZnRfam9pbihkZl9iYXNpY3MpCgpkZl9yYXRpbmdzICU+JSBoZWFkKCkKYGBgCgpgYGB7cn0KcGxvdCA8LSBnZ3Bsb3QoZGZfcmF0aW5ncywgYWVzKHggPSBydW50aW1lTWludXRlcywgeSA9IGF2ZXJhZ2VSYXRpbmcpKSArCiAgICAgICAgICBnZW9tX2JpbjJkKCkgKwogICAgICAgICAgc2NhbGVfeF9jb250aW51b3VzKGxhYmVscyA9IGNvbW1hKSArCiAgICAgICAgICBzY2FsZV95X2NvbnRpbnVvdXMoYnJlYWtzID0gMToxMCkgKwogICAgICAgICAgc2NhbGVfZmlsbF92aXJpZGlzX2MoKQoKZ2dzYXZlKCJpbWRiLTJhLnBuZyIsIHBsb3QsIHdpZHRoPTQsIGhlaWdodD0zKQpgYGAKCiFbXShpbWRiLTJhLnBuZykKCldoaWNoIG1vdmllcyBoYXZlIHRoZSBzdXBlcmhpZ2ggcnVudGltZXM/CgpgYGB7cn0KZGZfcmF0aW5ncyAlPiUgYXJyYW5nZShkZXNjKHJ1bnRpbWVNaW51dGVzKSkgJT4lIGhlYWQoMTApCmBgYAoKCmBgYHtyfQpwbG90IDwtIGdncGxvdChkZl9yYXRpbmdzICU+JSBmaWx0ZXIocnVudGltZU1pbnV0ZXMgPCAxODAsIHRpdGxlVHlwZT09Im1vdmllIiwgbnVtVm90ZXMgPj0gMTApLCBhZXMoeCA9IHJ1bnRpbWVNaW51dGVzLCB5ID0gYXZlcmFnZVJhdGluZykpICsKICAgICAgICAgIGdlb21fYmluMmQoKSArCiAgICAgICAgICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gc2VxKDAsIDE4MCwgNjApLCBsYWJlbHMgPSAwOjMpICsKICAgICAgICAgIHNjYWxlX3lfY29udGludW91cyhicmVha3MgPSAwOjEwKSArCiAgICAgICAgICBzY2FsZV9maWxsX3ZpcmlkaXNfYyhvcHRpb24gPSAiaW5mZXJubyIsIGxhYmVscyA9IGNvbW1hKSArCiAgICAgICAgICB0aGVtZV9taW5pbWFsKGJhc2VfZmFtaWx5ID0gIlNvdXJjZSBTYW5zIFBybyIsIGJhc2Vfc2l6ZT04KSArCiAgICAgICAgICBsYWJzKHRpdGxlPSJSZWxhdGlvbnNoaXAgYmV0d2VlbiBNb3ZpZSBSdW50aW1lIGFuZCBBdmVyYWdlIE1vdmllIFJhdGluZyIsCiAgICAgICAgICAgICAgIHN1YnRpdGxlPSJEYXRhIGZyb20gSU1EYiByZXRyaWV2ZWQgSnVseSA0dGgsIDIwMTgiLAogICAgICAgICAgICAgICB4PSJSdW50aW1lIChIb3VycykiLAogICAgICAgICAgICAgICB5PSJBdmVyYWdlIFVzZXIgUmF0aW5nIiwKICAgICAgICAgICAgICAgY2FwdGlvbj0iTWF4IFdvb2xmIOKAlCBtaW5pbWF4aXIuY29tIiwKICAgICAgICAgICAgICAgZmlsbD0iIyBNb3ZpZXMiKQoKZ2dzYXZlKCJpbWRiLTJiLnBuZyIsIHBsb3QsIHdpZHRoPTQsIGhlaWdodD0zKQpgYGAKCiFbXShpbWRiLTJiLnBuZykKIyMgVW5uZXN0aW5nIEdlbnJlcwoKSG93IHRvIGZhY2V0IGJ5IGdlbnJlOyB0aGlzIHdhcyByZW1vdmVkIGZyb20gdGhlIHBvc3Qgc2luY2UgYSBsaXR0bGUgY29tcGxpY2F0ZWQuCgpOQjogeW91IGNhbm5vdCB1c2UgZGVmYXVsdCB0b2tlbml6YXRpb24gb24gYHVubmVzdF90b2tlbnNgIHNpbmNlIHNvbWUgdG9rZW5zIGhhdmUgZGFzaGVzLiAoZS5nLiBgZmlsbS1ub2lyYCkKCmBgYHtyfQpkZl9yYXRpbmdzX3VubmVzdCA8LSBkZl9yYXRpbmdzICU+JQogICAgICAgICAgICAgICAgICAgICAgICBmaWx0ZXIocnVudGltZU1pbnV0ZXMgPCAxODAsIHRpdGxlVHlwZT09Im1vdmllIiwgbnVtVm90ZXMgPj0gMTApICU+JQogICAgICAgICAgICAgICAgICAgICAgICBzZWxlY3QocnVudGltZU1pbnV0ZXMsIGF2ZXJhZ2VSYXRpbmcsIGdlbnJlcykgJT4lCiAgICAgICAgICAgICAgICAgICAgICAgIHVubmVzdF90b2tlbnMoZ2VucmUsIGdlbnJlcywgdG9rZW4gPSBzdHJfc3BsaXQsIHBhdHRlcm4gPSAiLCIpCgpkZl9yYXRpbmdzX3VubmVzdCAlPiUgaGVhZCgxMCkKYGBgCgpgYGB7cn0KcGxvdCA8LSBnZ3Bsb3QoZGZfcmF0aW5nc191bm5lc3QsIGFlcyh4ID0gcnVudGltZU1pbnV0ZXMsIHkgPSBhdmVyYWdlUmF0aW5nKSkgKwogICAgICAgICAgZ2VvbV9iaW4yZCgpICsKICAgICAgICAgIHNjYWxlX3hfY29udGludW91cyhicmVha3MgPSBzZXEoMCwgMTgwLCA2MCksIGxhYmVscyA9IDA6MykgKwogICAgICAgICAgc2NhbGVfeV9jb250aW51b3VzKGJyZWFrcyA9IDE6MTApICsKICAgICAgICAgIHNjYWxlX2ZpbGxfdmlyaWRpc19jKG9wdGlvbiA9ICJpbmZlcm5vIiwgbGFiZWxzID0gY29tbWEpICsKICAgICAgICAgIHRoZW1lX21pbmltYWwoYmFzZV9mYW1pbHkgPSAiU291cmNlIFNhbnMgUHJvIiwgYmFzZV9zaXplPTkpICsKICAgICAgICAgIGxhYnModGl0bGU9IlJlbGF0aW9uc2hpcCBiZXR3ZWVuIE1vdmllIFJ1bnRpbWUgYW5kIEF2ZXJhZ2UgTW9iaWUgUmF0aW5nIiwKICAgICAgICAgICAgICAgc3VidGl0bGU9IkRhdGEgZnJvbSBJTURiIHJldHJpZXZlZCBKdWx5IDR0aCwgMjAxOCIsCiAgICAgICAgICAgICAgIHg9IlJ1bnRpbWUgKEhvdXJzKSIsCiAgICAgICAgICAgICAgIHk9IkF2ZXJhZ2UgVXNlciBSYXRpbmciLAogICAgICAgICAgICAgICBjYXB0aW9uPSJNYXggV29vbGYg4oCUIG1pbmltYXhpci5jb20iLAogICAgICAgICAgICAgICBmaWxsPSIjIE1vdmllcyIpICsKICAgICAgICAgIGZhY2V0X3dyYXAofiBnZW5yZSkKCmdnc2F2ZSgiaW1kYi0zLnBuZyIsIHBsb3QsIHdpZHRoPTYsIGhlaWdodD02KQpgYGAKCiFbXShpbWRiLTMucG5nKQoKTm9ybWFsaXplIGJ5IGZhY2V0LiBUaGVyZSBhcmUgdHdvIGFwcHJvYWNoZXM6CgoxLiBEbyBhIHdlaWdodGVkIHN1bSBvZiB0aGUgcG9pbnRzIGluIHRoZSBzcGF0aWFsIGFyZWEsIHdoZXJlIHRoZSB3ZWlnaHQgaXMgdGhlIHJlY2lwcm9jb2wgb2YgdGhlICMgb2YgcG9pbnRzIGluIHRoZSBmYWNldC4KMi4gTWFudWFsbHkgY2FsY3VsYXRlIGJpbnMvY291bnRzIGFuZCBzY2FsZSB0byBgWzAsIDFdYAoKT3B0aW9uIDEgaXMgYSBiaXQgZWFzaWVyIHRvIGltcGxlbWVudC4gQWRkaXRpb25hbGx5LCByZW1vdmUgZmFjZXRzIHdpdGggbGl0dGxlIGRhdGEuCgpgc3F1aXNoYCB0cmljayB2aWEgaHR0cHM6Ly9zdGFja292ZXJmbG93LmNvbS9hLzIzNjU1Njk3LzkzMTQ0MTgKCmBgYHtyfQpkZl90ZW1wIDwtIGRmX3JhdGluZ3NfdW5uZXN0ICU+JQogICAgICAgICAgICBmaWx0ZXIoIShnZW5yZSAlaW4lIGMoImdhbWUtc2hvdyIsICJyZWFsaXR5LXR2IiwgInNob3J0IiwgInRhbGstc2hvdyIsIE5BKSkpICU+JQogICAgICAgICAgICBncm91cF9ieShnZW5yZSkgJT4lCiAgICAgICAgICAgIG11dGF0ZShwcm9wID0gMS9uKCkpCgpwbG90IDwtIGdncGxvdChkZl90ZW1wLCBhZXMoeCA9IHJ1bnRpbWVNaW51dGVzLCB5ID0gYXZlcmFnZVJhdGluZywgej1wcm9wKSkgKwogICAgICAgICAgc3RhdF9zdW1tYXJ5XzJkKGZ1bj1zdW0pICsKICAgICAgICAgIHNjYWxlX3hfY29udGludW91cyhicmVha3MgPSBzZXEoMCwgMTgwLCA2MCksIGxhYmVscyA9IDA6MykgKwogICAgICAgICAgc2NhbGVfeV9jb250aW51b3VzKGJyZWFrcyA9IDE6MTApICsKICAgICAgICAgIHNjYWxlX2ZpbGxfdmlyaWRpc19jKG9wdGlvbiA9ICJpbmZlcm5vIiwgbGFiZWxzID0gY29tbWEsIGxpbWl0cz1jKDAsIDAuMDIpLCBvb2I9c3F1aXNoLCBndWlkZT1GKSArCiAgICAgICAgICB0aGVtZV9taW5pbWFsKGJhc2VfZmFtaWx5ID0gIlNvdXJjZSBTYW5zIFBybyIsIGJhc2Vfc2l6ZT05KSArCiAgICAgICAgICBsYWJzKHRpdGxlPSJSZWxhdGlvbnNoaXAgYmV0d2VlbiBNb3ZpZSBSdW50aW1lIGFuZCBBdmVyYWdlIE1vYmllIFJhdGluZyIsCiAgICAgICAgICAgICAgIHN1YnRpdGxlPSJEYXRhIGZyb20gSU1EYiByZXRyaWV2ZWQgSnVseSA0dGgsIDIwMTgiLAogICAgICAgICAgICAgICB4PSJSdW50aW1lIChIb3VycykiLAogICAgICAgICAgICAgICB5PSJBdmVyYWdlIFVzZXIgUmF0aW5nIiwKICAgICAgICAgICAgICAgY2FwdGlvbj0iTWF4IFdvb2xmIOKAlCBtaW5pbWF4aXIuY29tIikgKwogICAgICAgICAgZmFjZXRfd3JhcCh+IGdlbnJlKQoKZ2dzYXZlKCJpbWRiLTNiLnBuZyIsIHBsb3QsIHdpZHRoPTYsIGhlaWdodD02KQpgYGAKCiFbXShpbWRiLTNiLnBuZykKCiMgUmF0aW5nIHZzLiBNb3ZpZSBZZWFyCgpTZXQgdGhlbWUgdG8gY3VzdG9tIHRoZW1lIGJhc2VkIG9uIGB0aGVtZV9taW5pbWFsYCBmb3IgdGhlIHJlc3Qgb2YgdGhlIG5vdGVib29rLgoKYGBge3J9CnRoZW1lX3NldCh0aGVtZV9taW5pbWFsKGJhc2Vfc2l6ZT05LCBiYXNlX2ZhbWlseT0iU291cmNlIFNhbnMgUHJvIikgKwogICAgICAgICAgICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemU9OCwgZmFtaWx5PSJTb3VyY2UgU2FucyBQcm8gQm9sZCIsIG1hcmdpbj1tYXJnaW4odCA9IC0wLjEsIGIgPSAwLjEsIHVuaXQ9J2NtJykpLAogICAgICAgICAgICAgICAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X3RleHQoc2l6ZT04KSwKICAgICAgICAgICAgICAgICAgYXhpcy50aXRsZS55ID0gZWxlbWVudF90ZXh0KHNpemU9OCksCiAgICAgICAgICAgICAgICAgIHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoZmFtaWx5PSJTb3VyY2UgU2FucyBQcm8gU2VtaWJvbGQiLCBjb2xvcj0iIzk2OTY5NiIsIHNpemU9NiksCiAgICAgICAgICAgICAgICAgIHBsb3QuY2FwdGlvbiA9IGVsZW1lbnRfdGV4dChzaXplPTYsIGNvbG9yPSIjOTY5Njk2IiksCiAgICAgICAgICAgICAgICAgIGxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplPTgpLAogICAgICAgICAgICAgICAgICBsZWdlbmQua2V5LndpZHRoID0gdW5pdCgwLjI1LCB1bml0PSdjbScpKSkKYGBgCgoKYGBge3J9CnBsb3QgPC0gZ2dwbG90KGRmX3JhdGluZ3MgJT4lIGZpbHRlcih0aXRsZVR5cGU9PSJtb3ZpZSIsIG51bVZvdGVzID49IDEwKSwgYWVzKHggPSBzdGFydFllYXIsIHkgPSBhdmVyYWdlUmF0aW5nKSkgKwogICAgICAgICAgZ2VvbV9iaW4yZCgpICsKICAgICAgICAgIGdlb21fc21vb3RoKGNvbG9yPSJibGFjayIpICsKICAgICAgICAgIHNjYWxlX3hfY29udGludW91cygpICsKICAgICAgICAgIHNjYWxlX3lfY29udGludW91cyhicmVha3MgPSAxOjEwKSArCiAgICAgICAgICBzY2FsZV9maWxsX3ZpcmlkaXNfYyhvcHRpb24gPSAicGxhc21hIiwgbGFiZWxzID0gY29tbWEsIHRyYW5zPSdsb2cxMCcpICsKICAgICAgICAgIGxhYnModGl0bGU9IlJlbGF0aW9uc2hpcCBiZXR3ZWVuIE1vdmllIFJlbGVhc2UgWWVhciBhbmQgQXZlcmFnZSBSYXRpbmciLAogICAgICAgICAgICAgICBzdWJ0aXRsZT1zcHJpbnRmKCJGb3IgJXMgTW92aWVzL1JhdGluZ3MuIERhdGEgZnJvbSBJTURiIHJldHJpZXZlZCA3LzQvMjAxOCIsIGRmX3JhdGluZ3MgJT4lIGZpbHRlcih0aXRsZVR5cGU9PSJtb3ZpZSIsIG51bVZvdGVzID49IDEwKSAlPiUgcHBkZiksCiAgICAgICAgICAgICAgIHg9IlllYXIgTW92aWUgd2FzIFJlbGVhc2VkIiwKICAgICAgICAgICAgICAgeT0iQXZlcmFnZSBVc2VyIFJhdGluZyBGb3IgTW92aWUiLAogICAgICAgICAgICAgICBjYXB0aW9uPSJNYXggV29vbGYg4oCUIG1pbmltYXhpci5jb20iLAogICAgICAgICAgICAgICBmaWxsPSIjIE1vdmllcyIpCgpnZ3NhdmUoImltZGItNC5wbmciLCBwbG90LCB3aWR0aD00LCBoZWlnaHQ9MykKYGBgCgohW10oaW1kYi00LnBuZykKCldvcmsgd2l0aCBSaWRnZSBwbG90czsgbm90IGluY2x1ZGVkIGluIHBvc3QgYmVjYXVzZSBpdCBkb2Vzbid0IG9mZmVyIG11Y2ggaW5zaWdodCBkaWZmZXJlbnQgZnJvbSB0aGUgY2hhcnQgYWJvdmUuCgpOQjogRm9yIHJpZGdlIHBsb3RzLCB0aGUgeS1heGlzIG11c3QgYmUgYSBgZmFjdG9yYCwgbm90IGEgYG51bWVyaWNgOyB0aGlzIGlzIHdoYXQgdHJpcHBlZCBtZSB1cCBpbiB0aGUgc3RyZWFtLgoKYGBge3J9CnBsb3QgPC0gZ2dwbG90KGRmX3JhdGluZ3MgJT4lIGZpbHRlcihzdGFydFllYXIgPj0gMjAwMCwgdGl0bGVUeXBlPT0ibW92aWUiLCAgbnVtVm90ZXMgPj0gMTApICU+JSBtdXRhdGUoc3RhcnRZZWFyID0gZmFjdG9yKHN0YXJ0WWVhcikpLCBhZXMoeCA9IGF2ZXJhZ2VSYXRpbmcsIHkgPSBzdGFydFllYXIsIGZpbGw9c3RhcnRZZWFyKSkgKwogICAgICAgICAgZ2VvbV9kZW5zaXR5X3JpZGdlcygpICsKICAgICAgICAgIHNjYWxlX2ZpbGxfaHVlKGd1aWRlPUYpICsKICAgICAgICAgIHNjYWxlX3hfY29udGludW91cyhicmVha3MgPSAxOjEwKSArCiAgICAgICAgICB0aGVtZV9taW5pbWFsKGJhc2VfZmFtaWx5ID0gIlNvdXJjZSBTYW5zIFBybyIsIGJhc2Vfc2l6ZT05KQoKZ2dzYXZlKCJpbWRiLTUucG5nIiwgcGxvdCwgd2lkdGg9NCwgaGVpZ2h0PTMpCmBgYAoKIVtdKGltZGItNS5wbmcpCgpCdWNrZXQgYnkgZGVjYWRlcy4KCmBgYHtyfQpkZl9yYXRpbmdzX2RlY2FkZXMgPC0gZGZfcmF0aW5ncyAlPiUKICAgICAgICAgICAgICAgICAgICAgICAgZmlsdGVyKHN0YXJ0WWVhcj49MTk1MCwgdGl0bGVUeXBlPT0ibW92aWUiLCAgbnVtVm90ZXMgPj0gMTApICU+JQogIG11dGF0ZShkZWNhZGUgPSBmY3RfcmV2KGZhY3RvcihjdXRfd2lkdGgoc3RhcnRZZWFyLCAxMCwgYm91bmRhcnk9MCksIGxhYmVscyA9IHBhc3RlMChzZXEoMTk1MCwgMjAxMCwgMTApLCAicyIpKSkpCgpkZl9yYXRpbmdzX2RlY2FkZXMgJT4lIGhlYWQoKQpgYGAKCmBgYHtyfQpwbG90IDwtIGdncGxvdChkZl9yYXRpbmdzX2RlY2FkZXMsIGFlcyh4ID0gYXZlcmFnZVJhdGluZywgeSA9IGRlY2FkZSwgZmlsbD0wLjUgLSBhYnMoMC41LS4uZWNkZi4uKSkpICsKICAgICAgICAgIGdlb21fZGVuc2l0eV9yaWRnZXNfZ3JhZGllbnQoY2FsY19lY2RmPVQsIHF1YW50aWxlX2xpbmVzPVQpICsKICAgICAgICAgIHNjYWxlX2ZpbGxfdmlyaWRpc19jKG9wdGlvbiA9ICJwbGFzbWEiLCBndWlkZT1GKSArCiAgICAgICAgICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gMToxMCkgKwogICAgICAgICAgI3NjYWxlX3lfZGlzY3JldGUoZXhwYW5kID0gYygwLDAxLCAwKSkgKwogICAgICAgICAgdGhlbWVfbWluaW1hbChiYXNlX2ZhbWlseSA9ICJTb3VyY2UgU2FucyBQcm8iLCBiYXNlX3NpemU9OSkKCmdnc2F2ZSgiaW1kYi02LnBuZyIsIHBsb3QsIHdpZHRoPTQsIGhlaWdodD0zKQpgYGAKCiFbXShpbWRiLTYucG5nKQoKYGBge3J9CnBsb3QgPC0gZ2dwbG90KGRmX3JhdGluZ3NfZGVjYWRlcywgYWVzKHggPSBydW50aW1lTWludXRlcywgeSA9IGRlY2FkZSwgZmlsbD0wLjUgLSBhYnMoMC41LS4uZWNkZi4uKSkpICsKICAgICAgICAgIGdlb21fZGVuc2l0eV9yaWRnZXNfZ3JhZGllbnQoY2FsY19lY2RmPVQsIHF1YW50aWxlX2xpbmVzPVQpICsKICAgICAgICAgIHNjYWxlX2ZpbGxfdmlyaWRpc19jKG9wdGlvbiA9ICJwbGFzbWEiLCBndWlkZT1GKSArCiAgICAgICAgICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gc2VxKDAsIDE4MCwgNjApLCBsaW1pdHM9YygwLDE4MCksIGxhYmVscyA9IDA6MykgKwogICAgICAgICAgdGhlbWVfbWluaW1hbChiYXNlX2ZhbWlseSA9ICJTb3VyY2UgU2FucyBQcm8iLCBiYXNlX3NpemU9OSkKCmdnc2F2ZSgiaW1kYi03LnBuZyIsIHBsb3QsIHdpZHRoPTQsIGhlaWdodD0zKQpgYGAKCiFbXShpbWRiLTcucG5nKQoKIyMgRXBpc29kZSBBbmFseXNpcwoKRm9yIHJlZmVyZW5jZTsgbm90IGluY2x1ZGVkIGluIHBvc3QuIChUb28gbXVjaCBiYWQgZGF0YSB0byBjbGVhbi4pCgpgYGB7cn0KZGZfZXBpc29kZSA8LSByZWFkX2ltZGIoInRpdGxlLmVwaXNvZGUudHN2IikgJT4lIGZpbHRlcighaXMubmEoc2Vhc29uTnVtYmVyKSkKZGZfZXBpc29kZSAlPiUgaGVhZCgpCmBgYAoKVGhlcmUgYXJlIGByIGRmX2VwaXNvZGUgJT4lIHBwZGYoKWAgZXBpc29kZXMgaW4gdGhlIGRhdGFzZXQuCgpgYGB7cn0KZGZfZXBpc29kZV9jb3VudCA8LSBkZl9lcGlzb2RlICU+JQogICAgICAgICAgICAgICAgZ3JvdXBfYnkocGFyZW50VGNvbnN0LCBzZWFzb25OdW1iZXIpICU+JQogICAgICAgICAgICAgICAgdGFsbHkoKSAlPiUKICAgICAgICAgICAgICAgIGxlZnRfam9pbihkZl9iYXNpY3MsICBjKCJwYXJlbnRUY29uc3QiID0gInRjb25zdCIpKQoKZGZfZXBpc29kZV9jb3VudCAlPiUgaGVhZCgpCmBgYAoKCiMgQWN0b3IgSW5mb3JtYXRpb24KCmBzdHJfZGV0ZWN0YCBpcyB2ZWN0b3JpemVkIGFuZCAqbXVjaCogZmFzdGVyIHRoYW4gdXNpbmcgYSBsb29wL2BsYXBwbHlgLiBVc2luZyBhIHJlZ3VsYXIgZXhwcmVzc2lvbiB0byBzZWFyY2ggZm9yIGFjdG9yICpvciogYWN0cmVzcyBpcyBhbm90aGVyIHNwZWVkIGluY3JlYXNlLgoKYGBge3J9CmRmX2FjdG9ycyA8LSByZWFkX2ltZGIoIm5hbWUuYmFzaWNzLnRzdiIpICU+JQogICAgICAgICAgICAgICAgZmlsdGVyKHN0cl9kZXRlY3QocHJpbWFyeVByb2Zlc3Npb24sICJhY3RvcnxhY3RyZXNzIikpICAlPiUKICAgICAgICAgICAgICAgIHNlbGVjdChuY29uc3QsIHByaW1hcnlOYW1lLCBiaXJ0aFllYXIpCgpkZl9hY3RvcnMgJT4lIGhlYWQoKQpgYGAKClRoZXJlIGFyZSAqKmByIGRmX2FjdG9ycyAlPiUgcHBkZigpYCoqIGFjdG9ycyBpbiB0aGUgZGF0YXNldC4KCmBgYHtyfQpkZl9wcmluY2lwYWxzIDwtIHJlYWRfaW1kYigidGl0bGUucHJpbmNpcGFscy50c3YiKSAlPiUKICBmaWx0ZXIoc3RyX2RldGVjdChjYXRlZ29yeSwgImFjdG9yfGFjdHJlc3MiKSkgJT4lCiAgc2VsZWN0KHRjb25zdCwgb3JkZXJpbmcsIG5jb25zdCwgY2F0ZWdvcnkpICU+JQogIGdyb3VwX2J5KHRjb25zdCkgJT4lCiAgZmlsdGVyKG9yZGVyaW5nID09IG1pbihvcmRlcmluZykpCgpkZl9wcmluY2lwYWxzICU+JSBoZWFkKCkKYGBgCgpUaGVyZSBhcmUgKipgciBkZl9wcmluY2lwYWxzICU+JSBwcGRmKClgKiogcHJpbmNpcGFscy9yb3dzIGluIHRoZSBkYXRhc2V0LgoKSm9pbiB0aGUgMiBkYXRhZnJhbWVzLiAgKG9udG8gYHByaW5jaXBhbHNgLCBzaW5jZSBNYW55LXRvLU9uZSkKCmBgYHtyfQpkZl9wcmluY2lwYWxzIDwtIGRmX3ByaW5jaXBhbHMgJT4lIGxlZnRfam9pbihkZl9hY3RvcnMpCgpkZl9wcmluY2lwYWxzICU+JSBoZWFkKCkKYGBgCgojIFB1dHRpbmcgSXQgQWxsIFRvZ2V0aGVyCgpNZXJnZSBhY3RvciBpbmZvcm1hdGlvbiBvbnRvIHRoZSBmdWxsIHJhdGluZ3MgZGF0YWZyYW1lLgoKYGBge3J9CmRmX3JhdGluZ3MgPC0gZGZfcmF0aW5ncyAlPiUgbGVmdF9qb2luKGRmX3ByaW5jaXBhbHMpCgpkZl9yYXRpbmdzICU+JSBoZWFkKCkKYGBgCgpGaWx0ZXIgZG93biB0byBtb3ZpZXMgdy8gYWN0b3IgaW5mby4gKG9ubHkgaWYgdGhlIGJpcnRoIHllYXIgaXMgcHJlc2VudCBpbiB0aGUgZGF0YSkKCmBgYHtyfQpkZl9yYXRpbmdzX21vdmllcyA8LSBkZl9yYXRpbmdzICU+JQogICAgICAgICAgICAgICAgICAgICAgICBmaWx0ZXIodGl0bGVUeXBlID09ICJtb3ZpZSIsICFpcy5uYShiaXJ0aFllYXIpLCBudW1Wb3RlcyA+PSAxMCkgJT4lCiAgICAgICAgICAgICAgICAgICAgICAgIG11dGF0ZShhZ2VfbGVhZCA9IHN0YXJ0WWVhciAtIGJpcnRoWWVhcikgJT4lCiAgICAgICAgICAgICAgICAgICAgICAgIGFycmFuZ2UoZGVzYyhudW1Wb3RlcykpCgpkZl9yYXRpbmdzX21vdmllcyAlPiUgaGVhZCgxMDApCmBgYAoKQWdncmVnYXRlIGxlYWQtYWN0b3IvYWN0cmVzcyBhZ2VzIGJ5IG1vdmllIHllYXIgdy8gcGVyY2VudGlsZXMuCgpgYGB7cn0KZGZfYWN0b3JfYWdlcyA8LSBkZl9yYXRpbmdzX21vdmllcyAlPiUKICAgICAgICAgICAgICAgICAgZ3JvdXBfYnkoc3RhcnRZZWFyKSAlPiUKICAgICAgICAgICAgICAgICAgc3VtbWFyaXplKGxvd19hZ2UgPSBxdWFudGlsZShhZ2VfbGVhZCwgMC4yNSwgbmEucm09VCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBtZWRfYWdlID0gcXVhbnRpbGUoYWdlX2xlYWQsIDAuNTAsIG5hLnJtPVQpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgaGlnaF9hZ2UgPSBxdWFudGlsZShhZ2VfbGVhZCwgMC43NSwgbmEucm09VCkpICU+JQogICAgICAgICAgICAgICAgICBhcnJhbmdlKHN0YXJ0WWVhcikKCmRmX2FjdG9yX2FnZXMgJT4lIGhlYWQoKQpgYGAKCkNyZWF0ZSBhIHJpYmJvbiBwbG90LgoKTkI6IFBsb3QgdGhlIHJpYmJvbiBiZWZvcmUgdGhlIGxpbmUsIHNvIHRoZSBsaW5lIGlzIG9uIHRvcC4KCmBgYHtyfQpwbG90IDwtIGdncGxvdChkZl9hY3Rvcl9hZ2VzICU+JSBmaWx0ZXIoc3RhcnRZZWFyID49IDE5MjApICwgYWVzKHggPSBzdGFydFllYXIpKSArCiAgICAgICAgICBnZW9tX3JpYmJvbihhZXMoeW1pbj1sb3dfYWdlLCB5bWF4PWhpZ2hfYWdlKSwgYWxwaGE9MC4yKSArCiAgICAgICAgICBnZW9tX2xpbmUoYWVzKHk9bWVkX2FnZSkpICsKICAgICAgICAgIGxhYnModGl0bGU9IkNoYW5nZSBpbiBBZ2VzIG9mIE1vdmllIExlYWQgQWN0b3JzL0FjdHJlc3MgT3ZlciBUaW1lIiwKICAgICAgICAgICAgICAgc3VidGl0bGU9c3ByaW50ZigiRm9yICVzIEFjdG9ycy4gTGluZSByZXByZXNlbnRzIG1lZGlhbiBhZ2UuXG5SaWJib24gYm91bmRzIHJlcHJlc2VudCAyNXRoIOKAlCA3NXRoIFBlcmNlbnRpbGVzLiBEYXRhIGZyb20gSU1EYiByZXRyaWV2ZWQgNy80LzIwMTgiLGRmX3JhdGluZ3NfbW92aWVzICU+JSBmaWx0ZXIoc3RhcnRZZWFyID49IDE5MjApICU+JSBwcGRmKCkpLAogICAgICAgICAgICAgICB4PSJZZWFyIE1vdmllIHdhcyBSZWxlYXNlZCIsCiAgICAgICAgICAgICAgIHk9IkFnZSBvZiBMZWFkIEFjdG9yL0FjdHJlc3MiLAogICAgICAgICAgICAgICBjYXB0aW9uPSJNYXggV29vbGYg4oCUIG1pbmltYXhpci5jb20iLAogICAgICAgICAgICAgICBmaWxsPSIjIE1vdmllcyIpCgpnZ3NhdmUoImltZGItOC5wbmciLCBwbG90LCB3aWR0aD00LCBoZWlnaHQ9MykKYGBgCgohW10oaW1kYi04LnBuZykKCkNyZWF0ZSBhIHBsb3QgY29tcGFyaW5nIGFjdG9ycy9hY3RyZXNzZXMuIFNhbWUgY29kZSwgZXhjZXB0IGFkZGluZyBhbiBhZ2dyZWdhdGlvbiBhbmQgYWVzdGV0aWMgb24gYGNhdGVnb3J5YC4KCmBgYHtyfQpkZl9hY3Rvcl9hZ2VzX2xlYWQgPC0gZGZfcmF0aW5nc19tb3ZpZXMgJT4lCiAgICAgICAgICAgICAgICAgIGdyb3VwX2J5KHN0YXJ0WWVhciwgY2F0ZWdvcnkpICU+JQogICAgICAgICAgICAgICAgICBzdW1tYXJpemUobG93X2FnZSA9IHF1YW50aWxlKGFnZV9sZWFkLCAwLjI1LCBuYS5ybT1UKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1lZF9hZ2UgPSBxdWFudGlsZShhZ2VfbGVhZCwgMC41MCwgbmEucm09VCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBoaWdoX2FnZSA9IHF1YW50aWxlKGFnZV9sZWFkLCAwLjc1LCBuYS5ybT1UKSkgJT4lCiAgICAgICAgICAgICAgICAgIGFycmFuZ2Uoc3RhcnRZZWFyKQoKZGZfYWN0b3JfYWdlc19sZWFkICU+JSBoZWFkKCkKCnBsb3QgPC0gZ2dwbG90KGRmX2FjdG9yX2FnZXNfbGVhZCAlPiUgZmlsdGVyKHN0YXJ0WWVhciA+PSAxOTIwKSwgYWVzKHggPSBzdGFydFllYXIsIGZpbGw9Y2F0ZWdvcnksIGNvbG9yPWNhdGVnb3J5KSkgKwogICAgICAgICAgZ2VvbV9yaWJib24oYWVzKHltaW49bG93X2FnZSwgeW1heD1oaWdoX2FnZSksIGFscGhhPTAuMiwgc2l6ZT0wKSArCiAgICAgICAgICBnZW9tX2xpbmUoYWVzKHk9bWVkX2FnZSkpICsKICAgICAgICAgIHNjYWxlX2ZpbGxfYnJld2VyKHBhbGV0dGU9IlNldDEiKSArCiAgICAgICAgICBzY2FsZV9jb2xvcl9icmV3ZXIocGFsZXR0ZT0iU2V0MSIpICsKICAgICAgICAgIGxhYnModGl0bGU9IkNoYW5nZSBpbiBBZ2VzIG9mIE1vdmllIExlYWQgQWN0b3JzL0FjdHJlc3MgT3ZlciBUaW1lIiwKICAgICAgICAgICAgICAgc3VidGl0bGU9c3ByaW50ZigiRm9yICVzIEFjdG9ycy4gTGluZSByZXByZXNlbnRzIG1lZGlhbiBhZ2UuXG5SaWJib24gYm91bmRzIHJlcHJlc2VudCAyNXRoIOKAlCA3NXRoIFBlcmNlbnRpbGVzLiBEYXRhIGZyb20gSU1EYiByZXRyaWV2ZWQgNy80LzIwMTgiLGRmX3JhdGluZ3NfbW92aWVzICU+JSBmaWx0ZXIoc3RhcnRZZWFyID49IDE5MjApICU+JSBwcGRmKCkpLAogICAgICAgICAgICAgICB4PSJZZWFyIE1vdmllIHdhcyBSZWxlYXNlZCIsCiAgICAgICAgICAgICAgIHk9IkFnZSBvZiBMZWFkIEFjdG9yL0FjdHJlc3MiLAogICAgICAgICAgICAgICBjYXB0aW9uPSJNYXggV29vbGYg4oCUIG1pbmltYXhpci5jb20iLAogICAgICAgICAgICAgICBmaWxsPScnLAogICAgICAgICAgICAgICBjb2xvcj0nJykKCmdnc2F2ZSgiaW1kYi05LnBuZyIsIHBsb3QsIHdpZHRoPTQsIGhlaWdodD0zKQpgYGAKCiFbXShpbWRiLTkucG5nKQoKU2FtZSBwbG90LCBidXQgZmFjZXQuICh1bnVzZWQgaW4gZmluYWwgcG9zdCBzaW5jZSBtYXkgbm90IGJlIGVub3VnaCBkYXRhL3NpbWlsYXIgYWNjcm9zcyBhbGwgZ2VucmVzKQoKYGBge3J9CmRmX2FjdG9yX2FnZXNfbGVhZCA8LSBkZl9yYXRpbmdzX21vdmllcyAlPiUKICAgICAgICAgICAgICAgICAgc2VsZWN0KHN0YXJ0WWVhciwgY2F0ZWdvcnksIGdlbnJlcywgYWdlX2xlYWQpICU+JQogICAgICAgICAgICAgICAgICB1bm5lc3RfdG9rZW5zKGdlbnJlLCBnZW5yZXMsIHRva2VuID0gc3RyX3NwbGl0LCBwYXR0ZXJuID0gIiwiKSAlPiUKICAgICAgICAgICAgICAgICAgZmlsdGVyKCEoZ2VucmUgJWluJSBjKCJnYW1lLXNob3ciLCAicmVhbGl0eS10diIsICJzaG9ydCIsICJ0YWxrLXNob3ciLCAiZmlsbS1ub2lyIiwgTkEpKSkgJT4lCiAgICAgICAgICAgICAgICAgIGdyb3VwX2J5KHN0YXJ0WWVhciwgY2F0ZWdvcnksIGdlbnJlKSAlPiUKICAgICAgICAgICAgICAgICAgc3VtbWFyaXplKGxvd19hZ2UgPSBxdWFudGlsZShhZ2VfbGVhZCwgMC4yNSwgbmEucm09VCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBtZWRfYWdlID0gcXVhbnRpbGUoYWdlX2xlYWQsIDAuNTAsIG5hLnJtPVQpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgaGlnaF9hZ2UgPSBxdWFudGlsZShhZ2VfbGVhZCwgMC43NSwgbmEucm09VCkpICU+JQogICAgICAgICAgICAgICAgICBhcnJhbmdlKHN0YXJ0WWVhcikKCmRmX2FjdG9yX2FnZXNfbGVhZCAlPiUgaGVhZCgpCgpwbG90IDwtIGdncGxvdChkZl9hY3Rvcl9hZ2VzX2xlYWQgJT4lIGZpbHRlcihzdGFydFllYXIgPj0gMTk1MCksIGFlcyh4ID0gc3RhcnRZZWFyLCBmaWxsPWNhdGVnb3J5LCBjb2xvcj1jYXRlZ29yeSkpICsKICAgICAgICAgIGdlb21fcmliYm9uKGFlcyh5bWluPWxvd19hZ2UsIHltYXg9aGlnaF9hZ2UpLCBhbHBoYT0wLjIsIHNpemU9MCkgKwogICAgICAgICAgZ2VvbV9saW5lKGFlcyh5PW1lZF9hZ2UpLCBzaXplPTAuNSkgKwogICAgICAgICAgdGhlbWVfbWluaW1hbChiYXNlX2ZhbWlseSA9ICJTb3VyY2UgU2FucyBQcm8iLCBiYXNlX3NpemU9OSkgKwogICAgICAgICAgc2NhbGVfZmlsbF9icmV3ZXIocGFsZXR0ZT0iU2V0MSIpICsKICAgICAgICAgIHNjYWxlX2NvbG9yX2JyZXdlcihwYWxldHRlPSJTZXQxIikgKwogICAgICAgICAgZmFjZXRfd3JhcCh+IGdlbnJlKQoKZ2dzYXZlKCJpbWRiLTEwLnBuZyIsIHBsb3QsIHdpZHRoPTYsIGhlaWdodD02KQpgYGAKCiFbXShpbWRiLTEwLnBuZykKCiMgTGVhZCBHZW5kZXIgQmFsYW5jZQoKVW51c2VkIGluIHBvc3Qgc2luY2UgYSBiaXQgbW9yZSBjb21wbGljYXRlZCB0byBleHBsYWluIGFuZCByZXN1bHRzIG5lZWQgZG91YmxlLWNoZWNraW5nLgoKYGBge3J9CnBsb3QgPC0gZ2dwbG90KGRmX3JhdGluZ3NfbW92aWVzICU+JSBmaWx0ZXIoc3RhcnRZZWFyID49IDE5NTApLCBhZXMoeCA9IHN0YXJ0WWVhciwgZmlsbD1jYXRlZ29yeSkpICsKICAgICAgICAgIGdlb21fYmFyKHBvc2l0aW9uPSJmaWxsIiwgd2lkdGg9MSkgKwogICAgICAgICAgdGhlbWVfbWluaW1hbChiYXNlX2ZhbWlseSA9ICJTb3VyY2UgU2FucyBQcm8iLCBiYXNlX3NpemU9OSkgKwogICAgICAgICAgc2NhbGVfZmlsbF9icmV3ZXIocGFsZXR0ZT0iU2V0MSIpICsKICAgICAgICAgIHNjYWxlX2NvbG9yX2JyZXdlcihwYWxldHRlPSJTZXQxIikKCmdnc2F2ZSgiaW1kYi0xMS5wbmciLCBwbG90LCB3aWR0aD00LCBoZWlnaHQ9MykKYGBgCgohW10oaW1kYi0xMS5wbmcpCgojIG50aCB0aW1lIGxlYWQKCmBgYHtyfQpkZl9yYXRpbmdzX21vdmllc19udGggPC0gZGZfcmF0aW5nc19tb3ZpZXMgJT4lCiAgICAgICAgICAgICAgICAgICAgICBncm91cF9ieShuY29uc3QpICU+JQogICAgICAgICAgICAgICAgICAgICAgYXJyYW5nZShzdGFydFllYXIpICU+JQogICAgICAgICAgICAgICAgICAgICAgbXV0YXRlKG50aF9sZWFkID0gcm93X251bWJlcigpKSAlPiUKICAgICAgICAgICAgICAgICAgICAgIHVuZ3JvdXAoKSAlPiUKICAgICAgICAgICAgICAgICAgICAgIGFycmFuZ2UoZGVzYyhzdGFydFllYXIpLCBkZXNjKG51bVZvdGVzKSkKCmRmX3JhdGluZ3NfbW92aWVzX250aCAlPiUgc2VsZWN0KHByaW1hcnlUaXRsZSwgcHJpbWFyeU5hbWUsIG50aF9sZWFkKSAlPiUgaGVhZCgxMDApCmBgYAoKYGBge3J9CmRmX2FjdG9yX2FnZXMgPC0gZGZfcmF0aW5nc19tb3ZpZXNfbnRoICU+JQogICAgICAgICAgICAgICAgICBncm91cF9ieShzdGFydFllYXIpICU+JQogICAgICAgICAgICAgICAgICBzdW1tYXJpemUobG93X250aCA9IHF1YW50aWxlKG50aF9sZWFkLCAwLjI1KSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1lZF9udGggPSBxdWFudGlsZShudGhfbGVhZCwgMC41MCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBoaWdoX250aCA9IHF1YW50aWxlKG50aF9sZWFkLCAwLjc1KSkgJT4lCiAgICAgICAgICAgICAgICAgIGFycmFuZ2Uoc3RhcnRZZWFyKQoKZGZfYWN0b3JfYWdlcyAlPiUgaGVhZCgpCgpwbG90IDwtIGdncGxvdChkZl9hY3Rvcl9hZ2VzICU+JSBmaWx0ZXIoc3RhcnRZZWFyID49IDE5NTApICwgYWVzKHggPSBzdGFydFllYXIpKSArCiAgICAgICAgICBnZW9tX3JpYmJvbihhZXMoeW1pbj1sb3dfbnRoLCB5bWF4PWhpZ2hfbnRoKSwgYWxwaGE9MC4yKSArCiAgICAgICAgICBnZW9tX2xpbmUoYWVzKHk9bWVkX250aCkpICsKICAgICAgICAgIHNjYWxlX3lfY29udGludW91cyhicmVha3M9YygxOjUsIDEwKSkgKwogICAgICAgICAgbGFicyh0aXRsZT0iI3RoIFRpbWUgTGVhZCBBY3RvciBvZiBNb3ZpZSBXYXMgQSBMZWFkIEFjdG9yLCBPdmVyIFRpbWUiLAogICAgICAgICAgICAgICBzdWJ0aXRsZT1zcHJpbnRmKCJGb3IgJXMgTGVhZCBBY3RvcnMuIExpbmUgcmVwcmVzZW50cyBtZWRpYW4gIy5cblJpYmJvbiBib3VuZHMgcmVwcmVzZW50IDI1dGgg4oCUIDc1dGggUGVyY2VudGlsZXMuIERhdGEgZnJvbSBJTURiIHJldHJpZXZlZCA3LzQvMjAxOCIsZGZfcmF0aW5nc19tb3ZpZXNfbnRoICU+JSBmaWx0ZXIoc3RhcnRZZWFyID49IDE5NTApICU+JSBwcGRmKCkpLAogICAgICAgICAgICAgICB4PSJZZWFyIiwKICAgICAgICAgICAgICAgeT0iI3RoIFRpbWUgTGVhZCBBY3RvciB3YXMgYSBMZWFkIEFjdG9yIiwKICAgICAgICAgICAgICAgY2FwdGlvbj0iTWF4IFdvb2xmIOKAlCBtaW5pbWF4aXIuY29tIiwKICAgICAgICAgICAgICAgZmlsbD0iIyBNb3ZpZXMiKSArCiAgICAgICAgICB0aGVtZShwYW5lbC5ncmlkLm1pbm9yID0gZWxlbWVudF9ibGFuaygpKQoKZ2dzYXZlKCJpbWRiLTEyLnBuZyIsIHBsb3QsIHdpZHRoPTQsIGhlaWdodD0zKQpgYGAKCiFbXShpbWRiLTEyLnBuZykKCiMgTElDRU5TRQoKVGhlIE1JVCBMaWNlbnNlIChNSVQpCgpDb3B5cmlnaHQgKGMpIDIwMTggTWF4IFdvb2xmCgpQZXJtaXNzaW9uIGlzIGhlcmVieSBncmFudGVkLCBmcmVlIG9mIGNoYXJnZSwgdG8gYW55IHBlcnNvbiBvYnRhaW5pbmcgYSBjb3B5IG9mIHRoaXMgc29mdHdhcmUgYW5kIGFzc29jaWF0ZWQgZG9jdW1lbnRhdGlvbiBmaWxlcyAodGhlICJTb2Z0d2FyZSIpLCB0byBkZWFsIGluIHRoZSBTb2Z0d2FyZSB3aXRob3V0IHJlc3RyaWN0aW9uLCBpbmNsdWRpbmcgd2l0aG91dCBsaW1pdGF0aW9uIHRoZSByaWdodHMgdG8gdXNlLCBjb3B5LCBtb2RpZnksIG1lcmdlLCBwdWJsaXNoLCBkaXN0cmlidXRlLCBzdWJsaWNlbnNlLCBhbmQvb3Igc2VsbCBjb3BpZXMgb2YgdGhlIFNvZnR3YXJlLCBhbmQgdG8gcGVybWl0IHBlcnNvbnMgdG8gd2hvbSB0aGUgU29mdHdhcmUgaXMgZnVybmlzaGVkIHRvIGRvIHNvLCBzdWJqZWN0IHRvIHRoZSBmb2xsb3dpbmcgY29uZGl0aW9uczoKClRoZSBhYm92ZSBjb3B5cmlnaHQgbm90aWNlIGFuZCB0aGlzIHBlcm1pc3Npb24gbm90aWNlIHNoYWxsIGJlIGluY2x1ZGVkIGluIGFsbCBjb3BpZXMgb3Igc3Vic3RhbnRpYWwgcG9ydGlvbnMgb2YgdGhlIFNvZnR3YXJlLgoKVEhFIFNPRlRXQVJFIElTIFBST1ZJREVEICJBUyBJUyIsIFdJVEhPVVQgV0FSUkFOVFkgT0YgQU5ZIEtJTkQsIEVYUFJFU1MgT1IgSU1QTElFRCwgSU5DTFVESU5HIEJVVCBOT1QgTElNSVRFRCBUTyBUSEUgV0FSUkFOVElFUyBPRiBNRVJDSEFOVEFCSUxJVFksIEZJVE5FU1MgRk9SIEEgUEFSVElDVUxBUiBQVVJQT1NFIEFORCBOT05JTkZSSU5HRU1FTlQuIElOIE5PIEVWRU5UIFNIQUxMIFRIRSBBVVRIT1JTIE9SIENPUFlSSUdIVCBIT0xERVJTIEJFIExJQUJMRSBGT1IgQU5ZIENMQUlNLCBEQU1BR0VTIE9SIE9USEVSIExJQUJJTElUWSwgV0hFVEhFUiBJTiBBTiBBQ1RJT04gT0YgQ09OVFJBQ1QsIFRPUlQgT1IgT1RIRVJXSVNFLCBBUklTSU5HIEZST00sIE9VVCBPRiBPUiBJTiBDT05ORUNUSU9OIFdJVEggVEhFIFNPRlRXQVJFIE9SIFRIRSBVU0UgT1IgT1RIRVIgREVBTElOR1MgSU4gVEhFIFNPRlRXQVJFLg==