Feedback should be send to goran.milovanovic_ext@wikimedia.de.

The campaign is run from 2017/10/05 to 2017/10/13.

CURRENT UPDATE: Complete dataset, collected on 2017/10/14.

0. Preliminaries

0. 1 Data Acquisiton

NOTE: the Data Acquisition code chunk is not fully reproducible from this Report. The data are collected by running the script abc2017_PROD_OverallDailyUpdate.R on stat1005.eqiad.wmnet, collecting the data as .tsv and .csv files, copying manually, and processing locally. Run from stat1005 stat box by executing Rscript /home/goransm/RScripts/abc2017/abc2017_PROD_OverallDailyUpdate.R.

### --- Script: abc2017_PROD_OverallDailyUpdate.R
### --- the following runs on stat1005.eqiad.wmnet
### --- Rscript /home/goransm/RScripts/abc2017/abc2017_PROD_OverallDailyUpdate.R

### --- The script collects and wrangles all datasets
### --- for the WMDE Autumn Banner Campaign 2017.

### --- Goran S. Milovanovic, Data Analyst, WMDE
### --- September 26, 2017.

### -----------------------------------------------------------------------------
### 0. Setup
### -----------------------------------------------------------------------------

rm(list = ls())
library(dplyr)
library(tidyr)
library(stringr)
library(data.table)
startDate <- '2017-10-05'
endDate <- '2017-10-14'
bannerImpressionsDir <- '/home/goransm/_miscWMDE/abc2017_DataOUT/abc2017_OfficialDatasets/abc2017BannerImpressions/'
bannerClicksDir <- '/home/goransm/_miscWMDE/abc2017_DataOUT/abc2017_OfficialDatasets/abc2017BannerClicksLandingPages/'
dailyUpdateDir <- '/home/goransm/_miscWMDE/abc2017_DataOUT/abc2017_OfficialDatasets/abc2017_DailyUpdate/' 

### -----------------------------------------------------------------------------
### 1. Banner Impressions
### -----------------------------------------------------------------------------

### --- Campaign Banner Tags:
# - (1) ?campaign=wmde_abc2017_bt1 - banner for Specific Task 1;
# - (2) ?campaign=wmde_abc2017_bt2 - banner for Specific Task 2;
# - (3) ?campaign=wmde_abc2017_bt3 - banner for Specific Task 3;
# - (4) ?campaign=wmde_abc2017_gib_lp - banner for the General Invitation
# - which leads to the Landing Page upon click;
# - (5) ?campaign=wmde_abc2017_gib_rg - banner for the General Invitation
# which leads directly to Registration upon click.

### --- HiveQL for everything from
### --- uri_host = 'de.wikipedia.org' and
### --- uri_path = '/beacon/impression'
### --- and then look up the desired tags.

### --- loop over date range, create query, fetch, and store

dateRange <- seq.POSIXt(from = as.POSIXlt(startDate, tz = "CET"),
                        to = as.POSIXlt(endDate, tz = "CET"),
                        by = 'hour')
dateRange <- dateRange[-length(dateRange)]
cetDateRange <- as.character(dateRange)
cetDateRange <- sapply(cetDateRange, function(x) {
  strsplit(x, split = " ", fixed = T)[[1]][1]
})
names(dateRange) <- cetDateRange
dateRange <- as.POSIXlt(dateRange, tz = "UTC")
# - up to today:
today <- as.POSIXlt(Sys.time(), tz = "UTC")
w <- which(dateRange > today)
if (length(w) > 0) {
  dateRange <- dateRange[-w]
}
dR <- list()
for (i in 1:length(dateRange)) {
  dR[[i]] <- data.frame(
    cetName = names(dateRange[i]),
    utcYear = year(dateRange[i]),
    utcMonth = month(dateRange[i]),
    utcDay = mday(dateRange[i]),
    utcHour = hour(dateRange[i])
  )
}
dR <- rbindlist(dR)
dR <- dR %>%
  group_by(cetName, utcYear, utcMonth, utcDay) %>%
  summarise(utcHour = paste("hour=", utcHour, collapse = " OR ", sep = ""))

# - set outDir
outDir <- bannerImpressionsDir
setwd(outDir)
# - set HiveQL query dir:
for (i in 1:length(unique(dR$cetName))) {

  wCetName <- which(dR$cetName %in% unique(dR$cetName)[i])

  for (j in 1:length(wCetName)) {

    # - construct HiveQL query:
    y <- dR$utcYear[wCetName[j]]
    m <- dR$utcMonth[wCetName[j]]
    d <- dR$utcDay[wCetName[j]]
    hour <- dR$utcHour[wCetName[j]]
    q <- paste(
      "USE wmf;
      SELECT uri_query FROM webrequest
      WHERE uri_host = 'de.wikipedia.org'
      AND uri_path = '/beacon/impression'
      AND year = ", y,
      " AND month = ", m,
      " AND day = ", d,
      " AND (", hour, ");",
      sep = "")
    # - write hql
    write(q, 'abc2017_BannerImpressions.hql')
    # - prepare output file:
    fileName <- "abc2017_BannerImpressions_"
    fileName <- paste0(fileName,
                       as.character(unique(dR$cetName)[i]),
                       "_", j,
                       ".tsv")
    fileName <- paste0(outDir, fileName)
    # - execute hql script:
    hiveArgs <-
      'beeline -f'
    hiveInput <- paste0('abc2017_BannerImpressions.hql > ',
                        fileName)
    # - command:
    hiveCommand <- paste(hiveArgs, hiveInput)
    system(command = hiveCommand, wait = TRUE)

  }

}

### --- wrangle this dataSet
lF <- list.files()
lF <- lF[grepl(".tsv", lF, fixed = T)]
lF <- lF[grepl("Impressions", lF, fixed = T)]
### --- load Dataset:
# - count non-empty files:
c <- 0
dataSet <- list()
for (i in 1:length(lF)) {
  dS <- readLines(lF[i], n = -1)
  dS <- dS[8:(length(dS) - 1)]
  if (length(dS) > 0) {
    c <- c + 1
    dS <- data.frame(query = dS,
                     date = strsplit(lF[i], split = "_", fixed = T)[[1]][3],
                     stringsAsFactors = F)
    dataSet[[c]] <- dS
    rm(dS); gc()
  }
}
dataSet <- rbindlist(dataSet)
dataSet <- filter(dataSet,
                  grepl("WMDE_editor_campaign_autumn17",
                        query)
)

# - produce analytics dataset
banner <- str_extract(dataSet$query, "banner=(_|[[:alnum:]])+&")
banner <- gsub("banner=", "", banner, fixed = T)
banner <- gsub("&", "", banner, fixed = T)
impressionRate <- str_extract(dataSet$query, "recordImpressionSampleRate=([[:digit:]]|\\.)+&")
impressionRate <- gsub("recordImpressionSampleRate=", "", impressionRate, fixed = T)
impressionRate <- gsub("&", "", impressionRate, fixed = T)
impressionRate <- as.numeric(impressionRate)
status <- str_extract(dataSet$query, "status=([[:alnum:]]|[[:punct:]])+&")
status <- gsub("status=", "", status)
status <- gsub("&", "", status)
statusCode <- str_extract(dataSet$query, "statusCode=[[:digit:]]&")
statusCode <- gsub("statusCode=", "", statusCode)
statusCode <- gsub("&", "", statusCode)
campaignCategory <- str_extract(dataSet$query, "campaignCategory=[[:alnum:]]+&")
campaignCategory <- gsub("campaignCategory=", "", campaignCategory)
campaignCategory <- gsub("&", "", campaignCategory)
result <- str_extract(dataSet$query, "result=[[:alnum:]]+")
result <- gsub("result=", "", result)
result <- gsub("&", "", result)
qdate <- dataSet$date
# - as.data.frame()
dataSet <- data.frame(banner = banner,
                      impressionRate = impressionRate,
                      status = status,
                      statusCode = statusCode,
                      campaignCategory = campaignCategory,
                      result = result,
                      date = qdate,
                      stringsAsFactors = F)

# - store analytics dataset:
setwd(dailyUpdateDir)
dataSet <- dataSet[!is.na(dataSet$banner), ]
write.csv(dataSet, 'abc_BannerImpressions_update.csv')

### -----------------------------------------------------------------------------
### 2. Banner Clicks and Landing Page Views
### -----------------------------------------------------------------------------

### --- Landing/Registration pages:
# - Landing Page, Specific Tasks, Banners bt1, bt2, bt3
# - https://de.wikipedia.org/wiki/Wikipedia:Wikimedia_Deutschland/JetztMitmachen
# - Specific bt banner anchors:
# -  bt1 - #Bebildern, bt2 - Aktualisieren, bt3 - #Belegen
# - Landing Page, General, Banner gib_lp
# - https://de.wikipedia.org/wiki/Wikipedia:Wikimedia_Deutschland/Mach_mit
# - Registration Page, banner gib_rg
# - https://de.wikipedia.org/wiki/Spezial:Benutzerkonto_anlegen

# - set outDir
outDir <- bannerClicksDir
setwd(outDir)

for (i in 1:length(unique(dR$cetName))) {

  wCetName <- which(dR$cetName %in% unique(dR$cetName)[i])

  for (j in 1:length(wCetName)) {

    # - construct HiveQL query:
    y <- dR$utcYear[wCetName[j]]
    m <- dR$utcMonth[wCetName[j]]
    d <- dR$utcDay[wCetName[j]]
    hour <- dR$utcHour[wCetName[j]]
    q <- paste(
      "USE wmf;
      SELECT uri_path, uri_query, referer FROM webrequest
      WHERE uri_host = 'de.wikipedia.org'
      AND (uri_path = '/wiki/Wikipedia:Wikimedia_Deutschland/JetztMitmachen' OR uri_path = '/wiki/Wikipedia:Wikimedia_Deutschland/Mach_mit' OR uri_path = '/wiki/Spezial:Benutzerkonto_anlegen')
      AND year = ", y,
      " AND month = ", m,
      " AND day = ", d,
      " AND (", hour, ");",
      sep = "")
    # - write hql
    write(q, 'abc2017_BannerClicks.hql')
    # - prepare output file:
    fileName <- "abc2017_BannerClicks_"
    fileName <- paste0(fileName,
                       as.character(unique(dR$cetName)[i]),
                       "_", j,
                       ".tsv")
    fileName <- paste0(outDir, fileName)
    # - execute hql script:
    hiveArgs <-
      'beeline -f'
    hiveInput <- paste0('abc2017_BannerClicks.hql > ',
                        fileName)
    # - command:
    hiveCommand <- paste(hiveArgs, hiveInput)
    system(command = hiveCommand, wait = TRUE)

  }

}

### --- Wrangle this dataset:

### --- Landing pages:
specTaskPage <- '/wiki/Wikipedia:Wikimedia_Deutschland/JetztMitmachen'
genInvPage <- '/wiki/Wikipedia:Wikimedia_Deutschland/Mach_mit'
regPage <- '/wiki/Spezial:Benutzerkonto_anlegen'

### --- Banner tags:
specTaskBanner1 <- '?campaign=wmde_abc2017_bt1'
specTaskBanner2 <- '?campaign=wmde_abc2017_bt2'
specTaskBanner3 <- '?campaign=wmde_abc2017_bt3'
genInvPage_rg <- '?campaign=wmde_abc2017_gib_rg'
genInvPage_lp <- '?campaign=wmde_abc2017_gib_lp'

### --- Dataset:
# - count non-empty files:
c <- 0
lF <- list.files()
lF <- lF[grepl('.tsv', lF, fixed = T)]
lF <- lF[grepl('Clicks', lF, fixed = T)]
dataSet <- list()
for (i in 1:length(lF)) {
  dS <- readLines(lF[i], n = -1)
  dS <- dS[8:(length(dS) - 2)]
  if (length(dS > 0)) {
    c <- c + 1
    dS <- lapply(dS, function(x) {
      dat <- strsplit(x, split = "\t", fixed = T)[[1]]
      data.frame(page = dat[1], banner = dat[2], refer = dat[3], stringsAsFactors = F)
    })
  }
  dS <- rbindlist(dS)
  dS$date <- strsplit(lF[i], split = "_", fixed = T)[[1]][3]
  dataSet[[c]] <- dS
  rm(dS); gc()
}
dataSet <- rbindlist(dataSet)

# - replace values:
dataSet$page <- sapply(dataSet$page, function(x) {
  strsplit(x, split = "/", fixed = T)[[1]][length(strsplit(x, split = "/", fixed = T)[[1]])]
})
dataSet$banner[which(dataSet$banner %in% specTaskBanner1)] <- 'BT1'
dataSet$banner[which(dataSet$banner %in% specTaskBanner2)] <- 'BT2'
dataSet$banner[which(dataSet$banner %in% specTaskBanner3)] <- 'BT3'
dataSet$banner[which(dataSet$banner %in% genInvPage_rg)] <- 'GIP_RG'
dataSet$banner[which(dataSet$banner %in% genInvPage_lp)] <- 'GIP_LP'
dataSet$banner <- paste(dataSet$banner, "_click", sep = "")
dataSet$banner[which(!(dataSet$banner %in% c('BT1_click',
                                             'BT2_click',
                                             'BT3_click',
                                             'GIP_RG_click',
                                             'GIP_LP_click')))] <- 'Other'
colnames(dataSet) <- c('Page', 'Source', 'Referer', 'Date')

### --- store abc_BannerClicksPageViews_Update.csv
write.csv(dataSet, file = "abc_BannerClicksPageViews_Non-Refined.csv")

dataSet$Source[dataSet$Page %in% 'Spezial:Benutzerkonto_anlegen' & dataSet$Source == 'Other'] <-
  str_extract(dataSet$Referer[dataSet$Page %in% 'Spezial:Benutzerkonto_anlegen' & dataSet$Source == 'Other'],
              "campaign=wmde_abc(.)+$")
dataSet$Source[dataSet$Page %in% 'Spezial:Benutzerkonto_anlegen' & grepl("wmde_abc2017_bt1", dataSet$Source)] <- "JetztMitmachen_BT1"
dataSet$Source[dataSet$Page %in% 'Spezial:Benutzerkonto_anlegen' & grepl("wmde_abc2017_bt2", dataSet$Source)] <- "JetztMitmachen_BT2"
dataSet$Source[dataSet$Page %in% 'Spezial:Benutzerkonto_anlegen' & grepl("wmde_abc2017_bt3", dataSet$Source)] <- "JetztMitmachen_BT3"
dataSet$Source[dataSet$Page %in% 'Spezial:Benutzerkonto_anlegen' & grepl("wmde_abc2017_gib_rg", dataSet$Source)] <- "GIP_RG_click"
dataSet$Source[dataSet$Page %in% 'Spezial:Benutzerkonto_anlegen' & grepl("wmde_abc2017_gib_lp", dataSet$Source)] <- "Mach_mit"
dataSet$Source[dataSet$Page %in% 'Spezial:Benutzerkonto_anlegen' & dataSet$Referer %in% '-'] <- "Unknown"
dataSet$Source[dataSet$Page %in% 'Mach_mit' & dataSet$Referer %in% '-'] <- "Unknown"
dataSet$Source[dataSet$Page %in% 'JetztMitmachen' & dataSet$Referer %in% '-'] <- "Unknown"
dataSet$Source[is.na(dataSet$Source)] <- 'Other'
dataSet$Referer <- NULL

### --- store abc_BannerClicksPageViews_Update.csv
setwd(dailyUpdateDir)
write.csv(dataSet, file = "abc_BannerClicksPageViews_Update.csv")

### -----------------------------------------------------------------------------
### 3. User Registration Data
### -----------------------------------------------------------------------------

# - NOTE: UTC timestamps - adjustment for CE(S)T introduced. 
# - ServerSideAccountCreation_5487345
qCommand <- "mysql --defaults-file=/etc/mysql/conf.d/analytics-research-client.cnf -h analytics-store.eqiad.wmnet -A -e \"select * from log.ServerSideAccountCreation_5487345 where ((webHost = 'de.wikipedia.org') and (timestamp >= 20171004220000));\" > /home/goransm/_miscWMDE/abc2017_DataOUT/abc2017_OfficialDatasets/abc2017_DailyUpdate/abc2017_userRegistrations.tsv"
system(command = qCommand, wait = TRUE)

### -----------------------------------------------------------------------------
### 4. Guided Tour Data
### -----------------------------------------------------------------------------

# - NOTE: UTC timestamps - adjustment for CE(S)T introduced. 

# - ServerSideAccountCreation_5487345
qCommand <- "mysql --defaults-file=/etc/mysql/conf.d/analytics-research-client.cnf -h analytics-store.eqiad.wmnet -A -e \"select * from log.GuidedTourExited_8690566 where ((webHost = 'de.wikipedia.org') and (timestamp >= 20171004220000));\" > /home/goransm/_miscWMDE/abc2017_DataOUT/abc2017_OfficialDatasets/abc2017_DailyUpdate/abc2017_guidedTours.tsv"
system(command = qCommand, wait = TRUE)

### -----------------------------------------------------------------------------
### 5. User Edits Data
### -----------------------------------------------------------------------------
# - get user IDs from registered:
lF <- list.files()
lF <- lF[grepl('userRegistrations', lF, fixed = T)]
userReg <- read.table(lF, 
                      quote = "",
                      sep = "\t",
                      header = T,
                      check.names = F,
                      stringsAsFactors = F)
userReg <- userReg %>% 
  dplyr::select(event_userId, event_isSelfMade) %>% 
  filter(event_isSelfMade == 1)
# - uids:
uid <- userReg$event_userId
# - sql query
sqlQuery <- paste('SELECT COUNT(*) as edits, rev_user FROM revision WHERE rev_user IN (',
                  paste(uid, collapse = ", "),
                  ') GROUP BY rev_user;',
                  sep = "")
mySqlCommand <- paste('mysql -h analytics-store.eqiad.wmnet dewiki -e ',
                      paste('"', sqlQuery, '" > ', sep = ""),
                      '/home/goransm/_miscWMDE/abc2017_DataOUT/abc2017_OfficialDatasets/abc2017_DailyUpdate/abc2017_userEdits.tsv', sep = "")
system(command = mySqlCommand, 
       wait = TRUE)

0. 2 Abbreviations used in this Report

  • BT1 Specific Task Banner Campaign 1 - “You can make Wikipedia more vivid! CTA: Learn how to add pictures to articles”
  • BT2 Specific Task Banner Campaign 2 - “You can improve the accuracy of Wikipedia! CTA: Learn how to improve articles”
  • BT3 Specific Task Banner Campaign 3 - “You can improve the reliability of Wikipedia! CTA: Learn how to add citations”
  • GIB General Invitation Banner - “Contribute to Wikipedia CTA: Create a user account”
  • GIB_LP a version of the General Invitation Banner that leads to the Mach_ mit landing page
  • GIB_RG a version of the General Invitation Banner that leads directly to the registration page
  • Mach_ mit the landing page for the General Invitation Banner
  • Jetz_Mitmachen_ the landing page for the Specific Task Banners

1. Campaign Banners and Pages

This section presents all data and statistics on the campaign banners and pages.

1.2.0 The Dataset

dataSet <- read.csv(paste('./_dailyUpdateDATA/', 'abc_BannerClicksPageViews_Update.csv', sep = ""),
                    header = T,
                    check.names = F,
                    row.names = 1,
                    stringsAsFactors = F) %>% 
  filter(Page %in% c('JetztMitmachen', 'Spezial:Benutzerkonto_anlegen', 'Mach_mit'))
# - fix 'GIP' -> 'GIB' in the dataset:
dataSet$Source <- gsub('GIP', 'GIB', dataSet$Source)
# - NOTE (TEMPORARY):
dataSet <- dataSet[1:(dim(dataSet)[1] - 2), ]
dataSet <- filter(dataSet, 
                  !is.na(Page) & !is.na(Source) & !is.na(Date) & !(Source == "<NA>"))
# - Chart colorsgit 
chartCols <- c('indianred1', 'indianred2', 'indianred3',
               'cadetblue', 'cadetblue2', 
               'deepskyblue', 'violetred1', 'violetred2', 'violetred3',
               'lightslategrey', 'lightgrey')
names(chartCols) <- c('BT1_click', 'BT2_click', 'BT3_click',
                                                    'GIB_LP_click', 'GIB_RG_click',
                                                    'Mach_mit', 'JetztMitmachen_BT1', 'JetztMitmachen_BT2', 'JetztMitmachen_BT3',
                                                    'Other', 'Unknown')
dataSet$Source <- factor(dataSet$Source, levels = c('BT1_click', 'BT2_click', 'BT3_click',
                                                    'GIB_LP_click', 'GIB_RG_click',
                                                    'Mach_mit', 'JetztMitmachen_BT1', 'JetztMitmachen_BT2', 'JetztMitmachen_BT3', 'Other', 'Unknown'))
# - Page Chart Colors
pageChartColors <- c('orange', 'deepskyblue', 'lightgreen')
names(pageChartColors) <- c('JetztMitmachen', 'Spezial:Benutzerkonto_anlegen', 'Mach_mit')
dataSet$Page <- factor(dataSet$Page, 
                       levels = c('JetztMitmachen', 'Spezial:Benutzerkonto_anlegen', 'Mach_mit'))
# - Campaign Chart Colors
campaignChartColors <- c('indianred1', 'indianred2', 'indianred3',
               'cadetblue', 'cadetblue2')
names(campaignChartColors) <- c('BT1', 'BT2', 'BT3', 'GIB_LP', 'GIB_RG')

1.2.1A Landing Pages: Referers Overview

The following charts represents the breakdown of referers (i.e. sources) for the campaign pages: one registration page, and two landing pages.

### --- Banner clicks and Landing Page Views
# - Table Report
tableSet <- dataSet %>%
  dplyr::group_by(Page, Source, Date) %>% 
  dplyr::summarise(Count = n()) %>% 
  dplyr::arrange(Date, Page, Source)
ggplot(tableSet, aes(x = Page,
                    y = Count,
                    group = Source,
                    color = Source,
                    fill = Source,
                    label = Count)) +
  geom_bar(stat = "identity", 
           position = "dodge", 
           width = .35) +
  scale_fill_manual("legend", values = chartCols) +
  scale_color_manual("legend", values = chartCols) + 
  scale_y_continuous(labels = comma) +
  ggtitle('Autumn Banner Campaign 2017:\nOverview of Landing Page Views sources') +
  theme_minimal() + 
  theme(axis.text.x = element_text(angle = 90, size = 8, hjust = 1)) +
  theme(plot.title = element_text(size = 10)) +
  theme(legend.title = element_blank()) +
  theme(panel.grid.major.x = element_blank()) +
  theme(panel.grid.minor.x = element_blank()) +
  theme(panel.background = element_blank())

1.2.1B Page Views/Banner Clicks Dataset

The Page column refers to either one of the two campaign landing pages or the registration page. The Source column encompasses both campaign banner clicks and campaign pages as referers of the Page. The Count data have a daily resolution.

### --- Full Dataset (Table Report)
datatable(tableSet)

1.2.2 Landing Pages: Referer Breakdown

The following three pie charts present a breakdown of referers (i.e. sources) for the Campaign pages (two landing pages and one registration page.

### --- Page Views: Sources
# - Spezial:Benutzerkonto_anlegen
pageSource <- dataSet %>% 
  dplyr::count(Page, Source) %>%
  dplyr::group_by(Page) %>% 
  dplyr::mutate(Percent = n/sum(n))
pageSource$Percent <- paste(round(pageSource$Percent*100, 2), "%", sep = "")
pageSourcePlot <- filter(pageSource, Page %in% 'Spezial:Benutzerkonto_anlegen')
if (dim(pageSourcePlot)[1] > 0) {
  ggplot(pageSourcePlot, aes(x = '',
                             y = n,
                             color = Source,
                             fill = Source,
                             label = Percent)) +
    geom_bar(aes(x = '',
                 y = n,
                 color = Source,
                 fill = Source), 
             stat = "identity", 
             width = 1) +
    coord_polar("y", start = 0) +
    geom_text(aes(x = 1),
              colour = "white",
              fontface = "bold",
              position = position_stack(vjust = 0.5),
              size = 3,
              show.legend = F) +
    scale_fill_manual("legend", values = chartCols) +
    scale_color_manual("legend", values = chartCols) + 
    scale_y_continuous(labels = comma) +
    ggtitle('Autumn Banner Campaign 2017:\nPage Views Sources for Spezial:Benutzerkonto_anlegen') +
    xlab("Outter = Count") + ylab("") +
    theme_minimal() + 
    # theme(axis.text.x = element_blank()) +
    theme(plot.title = element_text(size = 10)) +
    theme(legend.title = element_blank()) +
    theme(panel.grid.major.y = element_blank()) +
    theme(panel.grid.minor.y = element_blank())
}

# - Spezial:Benutzerkonto_anlegen - Unknown/Other
pageSource <- dataSet %>% 
  filter(!(dataSet$Source %in% 'Other' | dataSet$Source %in% 'Unknown')) %>%
  dplyr::count(Page, Source) %>%
  dplyr::group_by(Page) %>% 
  dplyr::mutate(Percent = n/sum(n))
pageSource$Percent <- paste(round(pageSource$Percent*100, 2), "%", sep = "")
pageSourcePlot <- filter(pageSource, Page %in% 'Spezial:Benutzerkonto_anlegen')
if (dim(pageSourcePlot)[1] > 0) {
  ggplot(pageSourcePlot, aes(x = '',
                             y = n,
                             color = Source,
                             fill = Source,
                             label = Percent)) +
    geom_bar(aes(x = '',
                 y = n,
                 color = Source,
                 fill = Source), 
             stat = "identity", 
             width = 1) +
    coord_polar("y", start = 0) +
    geom_text(aes(x = 1),
              colour = "white",
              fontface = "bold",
              position = position_stack(vjust = 0.5),
              size = 3,
              show.legend = F) +
    scale_fill_manual("legend", values = chartCols) +
    scale_color_manual("legend", values = chartCols) +
    scale_y_continuous(labels = comma) +
    ggtitle('Autumn Banner Campaign 2017:\nPage Views Sources for Spezial:Benutzerkonto_anlegen (Campaign only)') +
    xlab("Outter = Count") + ylab("") +
    theme_minimal() + 
    # theme(axis.text.x = element_blank()) +
    theme(plot.title = element_text(size = 10)) +
    theme(legend.title = element_blank()) +
    theme(panel.grid.major.y = element_blank()) +
    theme(panel.grid.minor.y = element_blank())
}

The following table presents the data in respect to the Campaign sources only:

### --- Full Dataset (Table Report)
datatable(pageSourcePlot)
# - JetztMitmachen
pageSource <- dataSet %>% 
  dplyr::count(Page, Source) %>%
  dplyr::group_by(Page) %>% 
  dplyr::mutate(Percent = n/sum(n))
pageSource$Percent <- paste(round(pageSource$Percent*100, 2), "%", sep = "")
pageSourcePlot <- filter(pageSource, Page %in% 'JetztMitmachen')
if (dim(pageSourcePlot)[1] > 0) {
  ggplot(pageSourcePlot, aes(x = '',
                             y = n,
                             color = Source,
                             fill = Source,
                             label = Percent)) +
    geom_bar(aes(x = '',
                 y = n,
                 color = Source,
                 fill = Source), 
             stat = "identity", 
             width = 1) +
    coord_polar("y", start = 0) +
    geom_text(aes(x = 1),
              colour = "white",
              fontface = "bold",
              position = position_stack(vjust = 0.5),
              size = 3,
              show.legend = F) +
    scale_fill_manual("legend", values = chartCols) +
    scale_color_manual("legend", values = chartCols) +
    scale_y_continuous(labels = comma) +
    ggtitle('Autumn Banner Campaign 2017:\nPage Views Sources for JetztMitmachen') +
    xlab("Outter = Count") + ylab("") +
    theme_minimal() + 
    # theme(axis.text.x = element_blank()) +
    theme(plot.title = element_text(size = 10)) +
    theme(legend.title = element_blank()) +
    theme(panel.grid.major.y = element_blank()) +
    theme(panel.grid.minor.y = element_blank()) +
    theme(panel.background = element_blank())
}

# - JetztMitmachen - minus Unknown/Other
pageSource <- dataSet %>%
  filter(!(dataSet$Source %in% 'Other' | dataSet$Source %in% 'Unknown')) %>%
  dplyr::count(Page, Source) %>%
  dplyr::group_by(Page) %>% 
  dplyr::mutate(Percent = n/sum(n))
pageSource$Percent <- paste(round(pageSource$Percent*100, 2), "%", sep = "")
pageSourcePlot <- filter(pageSource, Page %in% 'JetztMitmachen')
if (dim(pageSourcePlot)[1] > 0) {
  ggplot(pageSourcePlot, aes(x = '',
                             y = n,
                             color = Source,
                             fill = Source,
                             label = Percent)) +
    geom_bar(aes(x = '',
                 y = n,
                 color = Source,
                 fill = Source), 
             stat = "identity", 
             width = 1) +
    coord_polar("y", start = 0) +
    geom_text(aes(x = 1),
              colour = "white",
              fontface = "bold",
              position = position_stack(vjust = 0.5),
              size = 3,
              show.legend = F) +
    scale_fill_manual("legend", values = chartCols) +
    scale_color_manual("legend", values = chartCols) +
    scale_y_continuous(labels = comma) +
    ggtitle('Autumn Banner Campaign 2017:\nPage Views Sources for JetztMitmachen (Campaign only)') +
    xlab("Outter = Count") + ylab("") +
    theme_minimal() + 
    # theme(axis.text.x = element_blank()) +
    theme(plot.title = element_text(size = 10)) +
    theme(legend.title = element_blank()) +
    theme(panel.grid.major.y = element_blank()) +
    theme(panel.grid.minor.y = element_blank()) +
    theme(panel.background = element_blank())
}

The following table presents the data in respect to the Campaign sources only:

### --- Full Dataset (Table Report)
datatable(pageSourcePlot)
# - Mach_mit
pageSource <- dataSet %>% 
  dplyr::count(Page, Source) %>%
  dplyr::group_by(Page) %>% 
  dplyr::mutate(Percent = n/sum(n))
pageSource$Percent <- paste(round(pageSource$Percent*100, 2), "%", sep = "")
pageSourcePlot <- filter(pageSource, Page %in% 'Mach_mit')
if (dim(pageSourcePlot)[1] > 0) {
  ggplot(pageSourcePlot, aes(x = '',
                             y = n,
                             color = Source,
                             fill = Source,
                             label = Percent)) +
    geom_bar(aes(x = '',
                 y = n,
                 color = Source,
                 fill = Source), 
             stat = "identity", 
             width = 1) +
    coord_polar("y", start = 0) +
    geom_text(aes(x = 1),
              colour = "white",
              fontface = "bold",
              position = position_stack(vjust = 0.5),
              size = 3,
              show.legend = F) +
    scale_fill_manual("legend", values = chartCols) +
    scale_color_manual("legend", values = chartCols) +
    scale_y_continuous(labels = comma) +
    ggtitle('Autumn Banner Campaign 2017:\nPage Views Sources for Mach_mit') +
    xlab("Outter = Count") + ylab("") +
    theme_minimal() + 
    # theme(axis.text.x = element_blank()) +
    theme(plot.title = element_text(size = 10)) +
    theme(legend.title = element_blank()) +
    theme(panel.grid.major.y = element_blank()) +
    theme(panel.grid.minor.y = element_blank()) +
    theme(panel.background = element_blank())
}

# - Mach_mit - minus Unknown/Other
pageSource <- dataSet %>% 
  filter(!(dataSet$Source %in% 'Other' | dataSet$Source %in% 'Unknown')) %>%
  dplyr::count(Page, Source) %>%
  dplyr::group_by(Page) %>% 
  dplyr::mutate(Percent = n/sum(n))
pageSource$Percent <- paste(round(pageSource$Percent*100, 2), "%", sep = "")
pageSourcePlot <- filter(pageSource, Page %in% 'Mach_mit')
if (dim(pageSourcePlot)[1] > 0) {
  ggplot(pageSourcePlot, aes(x = '',
                             y = n,
                             color = Source,
                             fill = Source,
                             label = Percent)) +
    geom_bar(aes(x = '',
                 y = n,
                 color = Source,
                 fill = Source), 
             stat = "identity", 
             width = 1) +
    coord_polar("y", start = 0) +
    geom_text(aes(x = 1),
              colour = "white",
              fontface = "bold",
              position = position_stack(vjust = 0.5),
              size = 3,
              show.legend = F) +
    scale_fill_manual("legend", values = chartCols) +
    scale_color_manual("legend", values = chartCols) + 
    scale_y_continuous(labels = comma) +
    ggtitle('Autumn Banner Campaign 2017:\nPage Views Sources for Mach_mit (Campaign only)') +
    xlab("Outter = Count") + ylab("") +
    theme_minimal() + 
    # theme(axis.text.x = element_blank()) +
    theme(plot.title = element_text(size = 10)) +
    theme(legend.title = element_blank()) +
    theme(panel.grid.major.y = element_blank()) +
    theme(panel.grid.minor.y = element_blank()) +
    theme(panel.background = element_blank())
}

The following table presents the data in respect to the Campaign sources only:

### --- Full Dataset (Table Report)
datatable(pageSourcePlot)

1.2.4 Page Views: Campaign Total

The following charts presents (a) the number of page views for the two landing pages and one registration page during the course of the campaign, and then (b) encompassing only page views that were generated from the campaign.

### --- Temporal Page Views
pagePlotSet <- dataSet %>% 
  dplyr::select(Page, Date) %>%
  dplyr::group_by(Page, Date) %>% 
  dplyr::summarise(Count = n()) %>% 
  dplyr::arrange(Date)
ggplot(pagePlotSet, aes(x = Date,
                        y = Count,
                        group = Page,
                        color = Page,
                        fill = Page,
                        label = Count)) +
  geom_bar(stat = "identity", 
           position = "dodge", 
           width = .2) +
  scale_fill_manual("legend", values = pageChartColors) +
  scale_color_manual("legend", values = pageChartColors) + 
  scale_y_continuous(labels = comma) +
  ggtitle('Autumn Banner Campaign 2017: Page Views') +
  theme_minimal() + 
  theme(axis.text.x = element_text(angle = 90, size = 8, hjust = 1)) +
  theme(plot.title = element_text(size = 10)) +
  theme(legend.title = element_blank()) +
  theme(panel.grid.major.x = element_blank()) +
  theme(panel.grid.minor.x = element_blank()) +
  theme(panel.background = element_blank())

### --- Temporal Page Views: Campaign only
pagePlotSet <- dataSet %>% 
  filter(!(dataSet$Source %in% 'Other' | dataSet$Source %in% 'Unknown')) %>%
  dplyr::select(Page, Date) %>%
  dplyr::group_by(Page, Date) %>% 
  dplyr::summarise(Count = n()) %>% 
  dplyr::arrange(Date)
ggplot(pagePlotSet, aes(x = Date,
                        y = Count,
                        group = Page,
                        color = Page,
                        fill = Page,
                        label = Count)) +
  geom_bar(stat = "identity", 
           position = "dodge", 
           width = .2) +
  scale_fill_manual("legend", values = pageChartColors) +
  scale_color_manual("legend", values = pageChartColors) + 
  scale_y_continuous(labels = comma) +
  ggtitle('Autumn Banner Campaign 2017: Page Views (Campaign only)') +
  theme_minimal() + 
  theme(axis.text.x = element_text(angle = 90, size = 8, hjust = 1)) +
  theme(plot.title = element_text(size = 10)) +
  theme(legend.title = element_blank()) +
  theme(panel.grid.major.x = element_blank()) +
  theme(panel.grid.minor.x = element_blank()) +
  theme(panel.background = element_blank())

The following table presents the data in respect to the Campaign sources only:

### --- Full Dataset (Table Report)
datatable(pagePlotSet)

2. Campaign User Registrations

2. 0 Registrations

### --- Campaign User Registrations
lF <- list.files(path = "./_dailyUpdateDATA/")
lF <- lF[grepl('userRegistrations', lF, fixed = T)]
userReg <- read.table(paste("./_dailyUpdateDATA/", lF, sep = ""),
                      quote = "",
                      sep = "\t",
                      header = T,
                      check.names = F,
                      stringsAsFactors = F)
userReg$timestamp <- as.character(userReg$timestamp)
userReg$timestamp <- sapply(userReg$timestamp, function(x) {
  y <- substr(x, 1, 4)
  m <- substr(x, 5, 6)
  d <- substr(x, 7, 8)
  part1Date <- paste(y, m, d, sep = "-")
  hr <- substr(x, 9, 10)
  mi <- substr(x, 11, 12)
  se <- substr(x, 13, 14)
  part2Date <- paste(hr, mi, se, sep = ":")
  paste(part1Date, part2Date, sep = " ")
})
userReg$timestamp <- as.POSIXct(userReg$timestamp, tz = "UTC")
timeDiff <- 
  as.POSIXct(as.character(Sys.time()), tz = "UTC") - as.POSIXct(as.character(Sys.time()), tz = "Europe/Berlin")
userReg$timestamp <- as.character(userReg$timestamp + timeDiff)
userReg$timestamp <- sapply(userReg$timestamp, function(x) {
  y <- substr(x, 1, 4)
  m <- substr(x, 6, 7)
  d <- substr(x, 9, 10) 
  paste(y, m, d, sep = "-")
})
userReg <- userReg %>% 
  dplyr::select(id, event_userId, timestamp, event_isSelfMade, event_campaign) %>% 
  filter(event_isSelfMade == 1 & grepl("wmde_abc2017", event_campaign))
print(paste(dim(userReg)[1], " users have registered via the Campaign."))
[1] "1054  users have registered via the Campaign."

2. 1A User Registrations per Campaign (daily)

regPlotSet <- userReg %>% 
  group_by(event_campaign, timestamp) %>% 
  summarise(Registrations = n()) %>% 
  arrange(timestamp)
colnames(regPlotSet) <- c('Campaign', 'Date', 'Registrations')
regPlotSet$Campaign <- factor(toupper(gsub("wmde_abc2017_", "", regPlotSet$Campaign)))
ggplot(regPlotSet, aes(x = Date,
                       y = Registrations,
                       group = Campaign,
                       color = Campaign,
                       fill = Campaign)) +
  geom_bar(stat = "identity", 
           position = "dodge", 
           width = .5) +
  scale_fill_manual("legend", values = campaignChartColors) +
  scale_color_manual("legend", values = campaignChartColors) + 
  scale_y_continuous(labels = comma) +
  ggtitle('Autumn Banner Campaign 2017: User Registrations (daily)') +
  theme_minimal() + 
  theme(axis.text.x = element_text(angle = 90, size = 8, hjust = 1)) +
  theme(plot.title = element_text(size = 10)) +
  theme(legend.title = element_blank()) +
  theme(panel.grid.major.x = element_blank()) +
  theme(panel.grid.minor.x = element_blank()) +
  theme(panel.background = element_blank())

### --- Full Dataset (Table Report)
datatable(regPlotSet)

2. 1B User Registrations per Campaign (totals)

regPlotSetTotal <- regPlotSet %>% 
  group_by(Campaign) %>% 
  summarise(Registrations = sum(Registrations)) %>% 
  arrange(Campaign)
ggplot(regPlotSetTotal, aes(x = Campaign,
                            y = Registrations,
                            group = Campaign,
                            color = Campaign,
                            fill = Campaign,
                            label = Registrations)) +
  geom_bar(stat = "identity", 
           position = "dodge", 
           width = .5) + 
  geom_label(fill = "white", color = "black") +
  scale_fill_manual("legend", values = campaignChartColors) +
  scale_color_manual("legend", values = campaignChartColors) + 
  scale_y_continuous(labels = comma) +
  ggtitle('Autumn Banner Campaign 2017: User Registrations (totals)') +
  theme_minimal() + 
  theme(axis.text.x = element_text(angle = 90, size = 8, hjust = 1)) +
  theme(plot.title = element_text(size = 10)) +
  theme(legend.title = element_blank()) +
  theme(panel.grid.major.x = element_blank()) +
  theme(panel.grid.minor.x = element_blank()) +
  theme(panel.background = element_blank())

2. 2 Total User Registrations daily

regPlotSetDaily <- userReg %>% 
  dplyr::filter(event_isSelfMade == 1 & grepl("wmde_abc2017", event_campaign)) %>% 
  group_by(timestamp) %>% 
  summarise(Registrations = n()) %>% 
  arrange(timestamp)
colnames(regPlotSetDaily) <- c('Date', 'Registrations')
ggplot(regPlotSetDaily, aes(x = Date,
                       y = Registrations, 
                       label = Registrations)) +
  geom_bar(stat = "identity", 
           position = "dodge", 
           width = .5, 
           fill = "darkblue", 
           color = "darkblue") + 
  geom_label() +
  scale_y_continuous(labels = comma) +
  ggtitle('Autumn Banner Campaign 2017: User Registrations') +
  theme_minimal() + 
  theme(axis.text.x = element_text(angle = 90, size = 8, hjust = 1)) +
  theme(plot.title = element_text(size = 10)) +
  theme(legend.title = element_blank()) +
  theme(panel.grid.major.x = element_blank()) +
  theme(panel.grid.minor.x = element_blank()) +
  theme(panel.background = element_blank())

### --- Full Dataset (Table Report)
datatable(regPlotSet)

3. Campaign Guided Tour

3. 1A Guided Tour Point of Exit (daily)

### --- Campaign User Registrations
gTourData <- read.table("./_dailyUpdateDATA/abc2017_guidedTours.tsv",
                        quote = "",
                        sep = "\t",
                        header = T,
                        check.names = F,
                        stringsAsFactors = F)
# - clean up: gTourData
gTourData <- gTourData[which(!duplicated(gTourData$event_userId)), ]
gTourData <- gTourData[which(!(gTourData$event_userId == 0)), ]
gTourData <- gTourData[which(gTourData$event_userId %in% userReg$event_userId), ]
gTourData$timestamp <- as.character(gTourData$timestamp)
gTourData$timestamp <- sapply(gTourData$timestamp, function(x) {
  y <- substr(x, 1, 4)
  m <- substr(x, 5, 6)
  d <- substr(x, 7, 8)
  part1Date <- paste(y, m, d, sep = "-")
  hr <- substr(x, 9, 10)
  mi <- substr(x, 11, 12)
  se <- substr(x, 13, 14)
  part2Date <- paste(hr, mi, se, sep = ":")
  paste(part1Date, part2Date, sep = " ")
})
gTourData$timestamp <- as.POSIXct(gTourData$timestamp, tz = "UTC")
timeDiff <- 
  as.POSIXct(as.character(Sys.time()), tz = "UTC") - as.POSIXct(as.character(Sys.time()), tz = "Europe/Berlin")
gTourData$timestamp <- as.character(gTourData$timestamp + timeDiff)
gTourData$timestamp <- sapply(gTourData$timestamp, function(x) {
  y <- substr(x, 1, 4)
  m <- substr(x, 6, 7)
  d <- substr(x, 9, 10) 
  paste(y, m, d, sep = "-")
})
gTourData <- gTourData %>%
   filter(event_tour %in% 'einfuhrung')
plotGTourData <- gTourData %>% 
  group_by(event_step, timestamp) %>% 
  summarise(Count = n())
colnames(plotGTourData) <- c('Tour Step', 'Date', 'Count')
ggplot(plotGTourData, aes(x = Date,
                      y = Count,
                       group = `Tour Step`,
                       color = `Tour Step`,
                       fill = `Tour Step`)) +
  geom_bar(stat = "identity", 
           position = "dodge", 
           width = .5) +
  scale_y_continuous(labels = comma) +
  ggtitle('Autumn Banner Campaign 2017: Guided Tour Steps (daily)') +
  theme_minimal() + 
  theme(axis.text.x = element_text(angle = 90, size = 8, hjust = 1)) +
  theme(plot.title = element_text(size = 10)) +
  theme(legend.title = element_blank()) +
  theme(panel.grid.major.x = element_blank()) +
  theme(panel.grid.minor.x = element_blank()) +
  theme(panel.background = element_blank())

### --- Full Dataset (Table Report)
datatable(plotGTourData)

3. 1B Guided Tour Point of Exit (totals)

### --- Campaign User Registrations
plotGTourDataTotal <- plotGTourData %>% 
  group_by(`Tour Step`)  %>%
  summarise(Count = sum(Count)) %>% 
  arrange(desc(Count))
plotGTourDataTotal$`Tour Step` <- 
  factor(plotGTourDataTotal$`Tour Step`, 
         levels = plotGTourDataTotal$`Tour Step`[order(plotGTourDataTotal$Count)])
ggplot(plotGTourDataTotal, aes(x = `Tour Step`,
                               y = Count,
                               group = `Tour Step`,
                               color = `Tour Step`,
                               fill = `Tour Step`,
                               label = Count)) +
  geom_bar(stat = "identity", 
           position = "dodge", 
           width = .5) +
  scale_y_continuous(labels = comma) + 
  geom_label(fill = "white", color = "black") +
  ggtitle('Autumn Banner Campaign 2017: Guided Tour Steps (totals)') +
  theme_minimal() + 
  theme(axis.text.x = element_text(angle = 90, size = 8, hjust = 1)) +
  theme(plot.title = element_text(size = 10)) +
  theme(legend.title = element_blank()) +
  theme(panel.grid.major.x = element_blank()) +
  theme(panel.grid.minor.x = element_blank()) +
  theme(panel.background = element_blank()) +
  theme(legend.position = 'None')

Number of users not exiting the Guided Tour:

nRegistered <- dim(userReg)[1]
nExitedGT <- dim(gTourData)[1]
print(paste(nRegistered - nExitedGT, 
            " users out of ", 
            nRegistered, 
            " (", round((nRegistered - nExitedGT)/nRegistered*100, 2), "%) did not exit the Campaign Guided Tour", 
            sep = ""))
[1] "667 users out of 1054 (63.28%) did not exit the Campaign Guided Tour"

3. 2 Exiting the Guided Tour at the Initial Step

How many users exit the Guided Tour at the initial step? NOTE: The Others category encompasses all users who did not exit at the initial step; they have either exited the Guided Tour later on or completed the tour.

exGTdata <- plotGTourData %>% 
  group_by(`Tour Step`) %>% 
  summarise(Count = sum(Count))
exGT1 <- exGTdata$Count[exGTdata$`Tour Step` %in% 'willkommen']
exGT2 <- nRegistered - exGT1
exGTourStep1 <- paste(exGT1, " (", round(exGT1/(exGT1 + exGT2)*100, 2), "%)", sep = "")
exGTourStep2 <- paste(exGT2, " (", round(exGT2/(exGT1 + exGT2)*100, 2), "%)", sep = "")
exGTour1 <- data.frame(`Users who exited at Step 1` = exGTourStep1, 
                       `Others` = exGTourStep2,
                       check.names = F,
                       stringsAsFactors = F)
knitr::kable(exGTour1, format = "html") %>% 
  kable_styling(full_width = F, position = "left")
Users who exited at Step 1 Others
129 (12.24%) 925 (87.76%)

4. User Edits

4. 0 Proportion of Active Users

# - determine userIDs
userReg <- userReg %>% 
  dplyr::select(id, event_userId, timestamp, event_isSelfMade, event_campaign) %>% 
  filter(event_isSelfMade == 1 & grepl("wmde_abc2017", event_campaign))
userIDs <- userReg$event_userId
editData <- read.table("./_dailyUpdateDATA/abc2017_userEdits.tsv",
                       sep = "\t",
                       quote = "",
                       header = T,
                       check.names = F,
                       stringsAsFactors = F) %>% 
  filter(rev_user %in% userIDs)
plEditData <- editData %>% 
  group_by(edits) %>% 
  summarise(Count = n())
colnames(plEditData) <- c('Num.Edits', 'Count')
print(paste(sum(plEditData$Count),
            " out of ",
            dim(userReg)[1],
            " registered users (",
            round(sum(plEditData$Count)/dim(userReg)[1]*100, 2),
            "%) have made at least one edit.", 
            sep = "")
      )
[1] "223 out of 1054 registered users (21.16%) have made at least one edit."

4. 1 User Edits Distribution

The y-axis represents log(Number of users) to make the line plot more readable, while the data labels present exact user counts alongside the number of edits made.

ggplot(plEditData, aes(x = `Num.Edits`,
                      y = log(Count), 
                      label = paste(Count, " (", `Num.Edits`, " edits)", sep = ""))
       ) +
  geom_path(size = .25, color = "darkblue") +
  geom_point(size = 1.5, color = "darkblue") +
  geom_point(size = 1, color = "white") + 
  geom_text_repel(size = 3) +
  scale_y_continuous(labels = comma) +
  ylab('log(Num. of Users)') + xlab('Number of Edits') +
  ggtitle('Autumn Banner Campaign 2017: User Edits Distribution') +
  theme_minimal() + 
  theme(axis.text.x = element_text(angle = 0, size = 8, hjust = 1)) +
  theme(plot.title = element_text(size = 10)) +
  theme(legend.title = element_blank()) +
  theme(panel.grid.major.x = element_blank()) +
  theme(panel.grid.minor.x = element_blank()) +
  theme(panel.background = element_blank())

4. 2 User Edits per Campaign

editCampaign <- left_join(editData, userReg, 
                          by = c("rev_user" = "event_userId")) %>% 
  group_by(event_campaign) %>% 
  summarise(Edits = sum(edits))
colnames(editCampaign) <- c('Campaign', 'Edits')
# - recode:
editCampaign$Campaign <- recode(editCampaign$Campaign,
                                'wmde_abc2017_bt1' = 'BT1',
                                'wmde_abc2017_bt2' = 'BT2',
                                'wmde_abc2017_bt3' = 'BT3',
                                'wmde_abc2017_gib_rg' = 'GIB_RG',
                                'wmde_abc2017_gib_lp' = 'GIB_LP'
                                )
editCampaign$Campaign <- factor(editCampaign$Campaign, 
                                levels = names(campaignChartColors))
ggplot(editCampaign, aes(x = Campaign,
                         y = Edits, 
                         fill = Campaign, 
                         color = Campaign, 
                         label = Edits)) +
  geom_bar(stat = "identity", 
           position = "dodge", 
           width = .5) + 
  geom_label(fill = "white", color = "black") +
  scale_y_continuous(labels = comma) + 
  scale_fill_manual("legend", values = campaignChartColors) + 
  scale_color_manual("legend", values = campaignChartColors) + 
  ggtitle('Autumn Banner Campaign 2017: User Edits per Campaign') +
  theme_minimal() + 
  theme(axis.text.x = element_text(angle = 0, size = 8, hjust = 1)) +
  theme(plot.title = element_text(size = 10)) +
  theme(legend.title = element_blank()) +
  theme(panel.grid.major.x = element_blank()) +
  theme(panel.grid.minor.x = element_blank()) +
  theme(panel.background = element_blank())

### --- Full Dataset (Table Report)
datatable(editCampaign)

4. 3 Percent of Active Users per Campaign

The percent of users who made any edits at all per campaign:

# - the dataset
editsMade <- left_join(userReg, editData, 
                       by = c('event_userId' = 'rev_user'))
editsMade$event_campaign <- toupper(gsub("wmde_abc2017_", "", editsMade$event_campaign, fixed = T))
editsMade$edits[is.na(editsMade$edits)] <- 0
editsMade$Edit <-  ifelse(editsMade$edits > 0, 'Edited', 'No edits')
editsMade <- dplyr::select(editsMade, 
                           event_campaign, Edit)
colnames(editsMade)[1] <- 'Campaign'
editsMade <- editsMade %>% 
  group_by(Campaign, Edit) %>% 
  summarise(Count = n())
editsMade <- editsMade %>% 
  group_by(Campaign) %>% 
  mutate(Count = round(Count/sum(Count)*100, 2))
editsMade$Edit <- factor(editsMade$Edit, levels = c('Edited', 'No edits'))
ggplot(editsMade, aes(x = '', y = Count,
                      fill = Edit,
                      color = Edit,
                      group = Edit,
                      label = Count)) + 
  geom_bar(position = "stack", 
           stat = "identity", 
           width = 1, 
           color = "black") + 
  coord_polar("y", start = 0) + 
  facet_wrap(~ Campaign) +
  scale_fill_manual("legend", values = c('firebrick', 'white')) + 
  scale_color_manual("legend", values = c('firebrick', 'white')) + 
  ggtitle('Autumn Banner Campaign 2017: User Edits Distributions per Campaign') + 
  xlab("") + ylab("Percent Edited") +
  theme_minimal() + 
  theme(axis.text.x = element_text(angle = 0, size = 8, hjust = 1)) +
  theme(plot.title = element_text(size = 10)) +
  theme(legend.title = element_blank()) +
  theme(panel.grid.major.x = element_blank()) +
  theme(panel.grid.minor.x = element_blank()) +
  theme(panel.background = element_blank()) 

4. 4 User Edits Breakdown

In the following table: No edits: users with zero edits, Edited: number of user who made any edits at all, 1 - 4 edits: number of users with 1 - 4 edits, 5 - 10 edits: number of users with 5 - 10 edits, and >10 edits: number of user with more than ten edits.

### --- Full Dataset (Table Report)
pltEdits <- as.tbl(editData) %>% 
  dplyr::group_by(edits) %>% 
  count()
edits0 <- dim(userReg)[1] - sum(plEditData$Count)
edits <- sum(pltEdits$n[pltEdits$edits > 0])
edits1_4 <- sum(pltEdits$n[pltEdits$edits >= 1 & pltEdits$edits <= 4])
edits5_10 <- sum(pltEdits$n[pltEdits$edits >= 5 & pltEdits$edits <= 10])
edits10 <- sum(pltEdits$n[pltEdits$edits > 10])
editClasses <- data.frame(`No edits` = edits0,
                          `Edited` = edits,
                          `1 - 4 edits` = edits1_4,
                          `5 - 10 edits` = edits5_10, 
                          `> 10 edits` = edits10,
                          check.names = F,
                          stringsAsFactors = F)
knitr::kable(editClasses, format = "html") %>%
  kable_styling(full_width = F, position = "left")
No edits Edited 1 - 4 edits 5 - 10 edits > 10 edits
831 223 194 22 7

4. 5 User Edits and Guided Tour Exits

How many edits where made by users who did and did not exit the Campaign Guided Tour?

editGTData <- left_join(editData, userReg, by = c('rev_user' = 'event_userId'))
editGTData <- left_join(editGTData, gTourData, by = c('rev_user' = 'event_userId'))
exTourEdits <- sum(editGTData$edits[!is.na(editGTData$event_tour)])
notExTourEdits <- sum(editGTData$edits[is.na(editGTData$event_tour)])
exitedTourEdits <- paste(exTourEdits, 
                         " (", round(exTourEdits/(exTourEdits + notExTourEdits)*100, 2), "%)",
                         sep = "")
notExitedTourEdits <- paste(notExTourEdits, 
                         " (", round(notExTourEdits/(exTourEdits + notExTourEdits)*100, 2), "%)",
                         sep = "")
gtEdits <- data.frame(`Exited GT` = exitedTourEdits, 
                      `Did not exit GT` = notExitedTourEdits, 
                      check.names = F,
                      stringsAsFactors = F)
knitr::kable(gtEdits, format = "html") %>%
  kable_styling(full_width = F, position = "left")
Exited GT Did not exit GT
292 (48.5%) 310 (51.5%)

4. 6 The Causal Effect of the Guided Tour Upon Editing

How does exiting vs. not exiting the Campaign Guided Tour influence whether the new user will make at least one edit or not? The following contingency table presents the number of registered users who made any edits at all (vs. those did not edit) separately for those who did and did not exit the Guided Tour.

userRegGT <- left_join(userReg, gTourData, 
                       by = 'event_userId')
userRegGT <- left_join(userRegGT, editData, 
                       by = c('event_userId' = 'rev_user'))
# - Contingency Table:
a <- length(userRegGT$event_userId[!is.na(userRegGT$edits) & is.na(userRegGT$event_tour)])
b <- length(userRegGT$event_userId[is.na(userRegGT$edits) & is.na(userRegGT$event_tour)])
c <- length(userRegGT$event_userId[!is.na(userRegGT$edits) & !is.na(userRegGT$event_tour)])
d <- length(userRegGT$event_userId[is.na(userRegGT$edits) & !is.na(userRegGT$event_tour)])
ct <- data.frame(`Edited` = c(a, c),
                 `No edits` = c(b, d),
                 check.names = F)
rownames(ct) <- c('GT Completed', 'GT Exited')
# - deltaP:
deltaP <- a/(a+b) - c/(c+d)
if (deltaP >= 0) {
  causalP <- deltaP/(1 - c/(c+d))
} else {
  causalP <- -deltaP/(c/(c+d))
}
knitr::kable(ct, format = "html") %>%
  kable_styling(full_width = F, position = "left")
Edited No edits
GT Completed 103 564
GT Exited 120 267

The estimate of the Causal Power (it can range from 0 = no causal influence at all, to 1 = a cause completelly sufficient to bring about its effect) of the Guided Tour to bring about any edits at all is:

ceffect <- ifelse(deltaP >= 0, 'generative effect', 'preventive effect')
print(paste('Guided Tour Causal Power: ', round(causalP, 2), ' (', ceffect, ')', sep = ""))
[1] "Guided Tour Causal Power: 0.5 (preventive effect)"
print(paste('(NOTE: with a value of a probabilistic contrast deltaP of): ', round(deltaP, 2), sep = ""))
[1] "(NOTE: with a value of a probabilistic contrast deltaP of): -0.16"

SUGGESTION: remove the Guided Tour from our future campaigns; it has an preventive effect upon the number of new user edits.

4. 7 Guided Tour and the number of user edits

How does exiting vs. not exiting the Campaign Guided Tour influence how many edits will a new user make?

userRegGT1 <- filter(userRegGT, !is.na(edits))
userRegGT1$event_tour <- ifelse(is.na(userRegGT1$event_tour), "Completed", "Exited")
dataGTEdits <- dplyr::select(userRegGT1, event_tour, edits) %>% 
  group_by(event_tour) %>% 
  summarise(`Num.edits` = sum(edits))
knitr::kable(dataGTEdits, format = "html") %>%
  kable_styling(full_width = F, position = "left")
event_tour Num.edits
Completed 310
Exited 292

In conclusion, those new users who had completed the Guided Tour have also made a slightly higher number of edits.

5. Campaign Evaluation

5.1 A/B Testing: Campaign Banners

5.1A User Registrations

Prepare priors and data.

regData <- regPlotSet %>% 
  group_by(Campaign) %>% 
  summarise(Registrations = sum(Registrations))
viewData <- clickPlotSet %>% 
  group_by(Source) %>% 
  summarise(Clicks = sum(Count))
viewData$Source <- gsub("_click", "", viewData$Source)
regData <- left_join(regData, viewData, by = c('Campaign' = 'Source'))
# - Uninformative prior:
priorAlpha <- 1
priorBeta <- 1
# - Data:
BT1Data <- c(rep(1, regData$Registrations[1]), rep(0, regData$Clicks[1] - regData$Registrations[1]))
BT2Data <- c(rep(1, regData$Registrations[2]), rep(0, regData$Clicks[2] - regData$Registrations[2]))
BT3Data <- c(rep(1, regData$Registrations[3]), rep(0, regData$Clicks[3] - regData$Registrations[3]))
GIB_LPData <- c(rep(1, regData$Registrations[4]), rep(0, regData$Clicks[4] - regData$Registrations[4]))
GIB_RGData <- c(rep(1, regData$Registrations[5]), rep(0, regData$Clicks[5] - regData$Registrations[5]))
# - Posteriors:
postB1Alpha <- priorAlpha + sum(BT1Data)
postB1Beta <- priorBeta + length(BT1Data) - sum(BT1Data)
postB2Alpha <- priorAlpha + sum(BT2Data)
postB2Beta <- priorBeta + length(BT2Data) - sum(BT2Data)
postB3Alpha <- priorAlpha + sum(BT3Data)
postB3Beta <- priorBeta + length(BT3Data) - sum(BT3Data)
postGIB_LPAlpha <- priorAlpha + sum(GIB_LPData)
postGIB_LPBeta <- priorBeta + length(GIB_LPData) - sum(GIB_LPData)
postGIB_RGAlpha <- priorAlpha + sum(GIB_RGData)
postGIB_RGBeta <- priorBeta + length(GIB_RGData) - sum(GIB_RGData)
# - Number of Monte Carlo samples:
mcN <- 1e6
Summary

The GIB_RG banner dominates all other in terms of the probability of user registration. As of the BT campaigns: BT2 performs relativelly better than BT1 and BT2 which do not differ (or differ only slightly) between each other. The most important findings are:

  • the dominance of the GIB banners over the BT banners,
  • the dominance of GIB_RG over any other banner.

NOTE. I use the term campaign lift in the following sections to refer to a difference in the probability of user registration in respect to any pair of banners that were used during the ABC2017 campaign. Theoretically, a campaign lift would be the difference (in some KPI) between a group of users who were exposed to the campaign and the control group (no exposure). If we would assess the ABC2017 campaign in this manner then it would be natural to take GIB_RG as a control, and compare all other groups (BT1, BT2, BT3, and GIB_LP) to it; in that case, we would observe that ABC2017 campaign had no lift in respect to the control group in terms of user registrations.

The following sections provide the results of pairwise Bayesian A/B tests across the campaign banners. TECHNICAL NOTE. Uniform Beta(1, 1) priors (assuming no prior knowlegde on the probability of a banner click leading to a registration) and 1,000,000 Monte Carlo samples from the posteriors were used.

Evaluation: BT1 vs. BT2
BT1Samples <- rbeta(mcN, postB1Alpha, postB1Beta)
BT2Samples <- rbeta(mcN, postB2Alpha, postB2Beta)
t_percentDiff <- (mean(BT1Samples) - mean(BT2Samples))/mean(BT2Samples)*100
pBT1_BT2 <- mean((BT1Samples > BT2Samples))
# - Probability of v1 better than v2:
print(paste('The probability of BT1 having more user registrations than BT2 is: ', pBT1_BT2))
[1] "The probability of BT1 having more user registrations than BT2 is:  0"
# - v1 Campaign Lift
percentDiff <- (BT1Samples - BT2Samples)/BT2Samples*100
percentDiff <- data.frame(percentDiff = percentDiff,
                          area = ifelse(percentDiff <= 0, '<= 0', '> 0'),
                          stringsAsFactors = T)
print(paste('The percent lift that BT1 has over BT2 (',
            round(t_percentDiff, 2),
            '%) lies in the interval (', 
            as.character(round(quantile(percentDiff$percentDiff, .025)), 2), 
            '%, ', 
            as.character(round(quantile(percentDiff$percentDiff, .975)), 2),
            '%) with 95% certainty.',
            sep = ""))
[1] "The percent lift that BT1 has over BT2 (-54.62%) lies in the interval (-63%, -44%) with 95% certainty."
ggplot(percentDiff, aes(x = percentDiff,
                        fill = area)) + 
  geom_histogram(binwidth = .1, alpha = .5) + 
  scale_y_continuous(labels = comma) +
  xlab('(BT1 - BT2)/BT1') + ylab('Density') + 
  ggtitle('BT1/BT2 Campaign Lift') +
  theme_minimal() + 
  theme(plot.title = element_text(size = 10))

Evaluation: BT1 vs. BT3
mcN <- 1e5
BT1Samples <- rbeta(mcN, postB1Alpha, postB1Beta)
BT3Samples <- rbeta(mcN, postB3Alpha, postB3Beta)
t_percentDiff <- (mean(BT1Samples) - mean(BT3Samples))/mean(BT3Samples)*100
pBT1_BT3 <- mean((BT1Samples > BT3Samples))
# - Probability of v1 better than v2:
print(paste('The probability of BT1 having more user registrations than BT3 is: ', pBT1_BT3))
[1] "The probability of BT1 having more user registrations than BT3 is:  0.3196"
# - v1 Campaign Lift
percentDiff <- (BT1Samples - BT3Samples)/BT3Samples*100
percentDiff <- data.frame(percentDiff = percentDiff,
                          area = ifelse(percentDiff <= 0, '<= 0', '> 0'),
                          stringsAsFactors = T)
print(paste('The percent lift that BT1 has over BT3 (',
            round(t_percentDiff, 2),
            '%) lies in the interval (', 
            as.character(round(quantile(percentDiff$percentDiff, .025)), 2), 
            '%, ', 
            as.character(round(quantile(percentDiff$percentDiff, .975)), 2),
            '%) with 95% certainty.',
            sep = ""))
[1] "The percent lift that BT1 has over BT3 (-5.69%) lies in the interval (-26%, 20%) with 95% certainty."
ggplot(percentDiff, aes(x = percentDiff,
                        fill = area)) + 
  geom_histogram(binwidth = .1, alpha = .5) + 
  scale_y_continuous(labels = comma) +
  xlab('(BT1 - BT3)/BT3') + ylab('Density') + 
  ggtitle('BT1/BT3 Campaign Lift') +
  theme_minimal() + 
  theme(plot.title = element_text(size = 10))

Evaluation: BT1 vs. GIB_LP
mcN <- 1e5
BT1Samples <- rbeta(mcN, postB1Alpha, postB1Beta)
GIB_LPSamples <- rbeta(mcN, postGIB_LPAlpha, postGIB_LPBeta)
t_percentDiff <- (mean(BT1Samples) - mean(GIB_LPSamples))/mean(GIB_LPSamples)*100
pBT1_GIB_LP <- mean((BT1Samples > GIB_LPSamples))
# - Probability of v1 better than v2:
print(paste('The probability of BT1 having more user registrations than GIB_LP is: ', pBT1_GIB_LP))
[1] "The probability of BT1 having more user registrations than GIB_LP is:  0"
# - v1 Campaign Lift
percentDiff <- (BT1Samples - GIB_LPSamples)/GIB_LPSamples*100
percentDiff <- data.frame(percentDiff = percentDiff,
                          area = ifelse(percentDiff <= 0, '<= 0', '> 0'),
                          stringsAsFactors = T)
print(paste('The percent lift that BT1 has over GIB_LP (',
            round(t_percentDiff, 2),
            '%) lies in the interval (', 
            as.character(round(quantile(percentDiff$percentDiff, .025)), 2), 
            '%, ', 
            as.character(round(quantile(percentDiff$percentDiff, .975)), 2),
            '%) with 95% certainty.',
            sep = ""))
[1] "The percent lift that BT1 has over GIB_LP (-66.78%) lies in the interval (-74%, -58%) with 95% certainty."
ggplot(percentDiff, aes(x = percentDiff,
                        fill = area)) + 
  geom_histogram(binwidth = .1, alpha = .5) + 
  scale_y_continuous(labels = comma) +
  xlab('(BT1 - GIB_LP)/GIB_LP') + ylab('Density') + 
  ggtitle('BT1/GIB_LP Campaign Lift') +
  theme_minimal() + 
  theme(plot.title = element_text(size = 10))

Evaluation: BT1 vs. GIB_RG
mcN <- 1e5
BT1Samples <- rbeta(mcN, postB1Alpha, postB1Beta)
GIB_RGSamples <- rbeta(mcN, postGIB_LPAlpha, postGIB_RGBeta)
t_percentDiff <- (mean(BT1Samples) - mean(GIB_RGSamples))/mean(GIB_RGSamples)*100
pBT1_GIB_RG <- mean((BT1Samples > GIB_RGSamples))
# - Probability of v1 better than v2:
print(paste('The probability of BT1 having more user registrations than GIB_RG is: ', pBT1_GIB_RG))
[1] "The probability of BT1 having more user registrations than GIB_RG is:  0"
# - v1 Campaign Lift
percentDiff <- (BT1Samples - GIB_RGSamples)/GIB_RGSamples*100
percentDiff <- data.frame(percentDiff = percentDiff,
                          area = ifelse(percentDiff <= 0, '<= 0', '> 0'),
                          stringsAsFactors = T)
print(paste('The percent lift that BT1 has over GIB_RG (',
            round(t_percentDiff, 2),
            '%) lies in the interval (', 
            as.character(round(quantile(percentDiff$percentDiff, .025)), 2), 
            '%, ', 
            as.character(round(quantile(percentDiff$percentDiff, .975)), 2),
            '%) with 95% certainty.',
            sep = ""))
[1] "The percent lift that BT1 has over GIB_RG (-66.33%) lies in the interval (-73%, -58%) with 95% certainty."
ggplot(percentDiff, aes(x = percentDiff,
                        fill = area)) + 
  geom_histogram(binwidth = .1, alpha = .5) + 
  scale_y_continuous(labels = comma) +
  xlab('(BT1 - GIB_RG)/GIB_RG') + ylab('Density') + 
  ggtitle('BT1/GIB_RG Campaign Lift') +
  theme_minimal() + 
  theme(plot.title = element_text(size = 10))

Evaluation: BT2 vs. BT3
mcN <- 1e5
BT2Samples <- rbeta(mcN, postB2Alpha, postB2Beta)
BT3Samples <- rbeta(mcN, postB3Alpha, postB3Beta)
t_percentDiff <- (mean(BT2Samples) - mean(BT3Samples))/mean(BT3Samples)*100
pBT2_BT3 <- mean((BT2Samples > BT3Samples))
# - Probability of v1 better than v2:
print(paste('The probability of BT2 having more user registrations than BT3 is: ', pBT2_BT3))
[1] "The probability of BT2 having more user registrations than BT3 is:  1"
# - v1 Campaign Lift
percentDiff <- (BT2Samples - BT3Samples)/BT3Samples*100
percentDiff <- data.frame(percentDiff = percentDiff,
                          area = ifelse(percentDiff <= 0, '<= 0', '> 0'),
                          stringsAsFactors = T)
print(paste('The percent lift that BT2 has over BT3 (',
            round(t_percentDiff, 2),
            '%) lies in the interval (', 
            as.character(round(quantile(percentDiff$percentDiff, .025)), 2), 
            '%, ', 
            as.character(round(quantile(percentDiff$percentDiff, .975)), 2),
            '%) with 95% certainty.',
            sep = ""))
[1] "The percent lift that BT2 has over BT3 (107.7%) lies in the interval (69%, 158%) with 95% certainty."
ggplot(percentDiff, aes(x = percentDiff,
                        fill = area)) + 
  geom_histogram(binwidth = .1, alpha = .5) + 
  scale_y_continuous(labels = comma) +
  xlab('(BT2 - BT3)/BT3') + ylab('Density') + 
  ggtitle('BT2/BT3 Campaign Lift') +
  theme_minimal() + 
  theme(plot.title = element_text(size = 10))

Evaluation: BT2 vs. GIB_LP
mcN <- 1e5
BT2Samples <- rbeta(mcN, postB2Alpha, postB2Beta)
GIB_LPSamples <- rbeta(mcN, postGIB_LPAlpha, postGIB_LPBeta)
t_percentDiff <- (mean(BT2Samples) - mean(GIB_LPSamples))/mean(GIB_LPSamples)*100
pBT2_GIB_LP <- mean((BT2Samples > GIB_LPSamples))
# - Probability of v1 better than v2:
print(paste('The probability of BT2 having more user registrations than GIB_LP is: ', pBT2_GIB_LP))
[1] "The probability of BT2 having more user registrations than GIB_LP is:  7e-04"
# - v1 Campaign Lift
percentDiff <- (BT2Samples - GIB_LPSamples)/GIB_LPSamples*100
percentDiff <- data.frame(percentDiff = percentDiff,
                          area = ifelse(percentDiff <= 0, '<= 0', '> 0'),
                          stringsAsFactors = T)
print(paste('The percent lift that BT2 has over GIB_LP (',
            round(t_percentDiff, 2),
            '%) lies in the interval (', 
            as.character(round(quantile(percentDiff$percentDiff, .025)), 2), 
            '%, ', 
            as.character(round(quantile(percentDiff$percentDiff, .975)), 2),
            '%) with 95% certainty.',
            sep = ""))
[1] "The percent lift that BT2 has over GIB_LP (-26.79%) lies in the interval (-39%, -11%) with 95% certainty."
ggplot(percentDiff, aes(x = percentDiff,
                        fill = area)) + 
  geom_histogram(binwidth = .1, alpha = .5) + 
  scale_y_continuous(labels = comma) +
  xlab('(BT2 - GIB_LP)/GIB_LP') + ylab('Density') + 
  ggtitle('BT2/GIB_LP Campaign Lift') +
  theme_minimal() + 
  theme(plot.title = element_text(size = 10))

Evaluation: BT2 vs. GIB_RG
mcN <- 1e5
BT2Samples <- rbeta(mcN, postB2Alpha, postB2Beta)
GIB_RGSamples <- rbeta(mcN, postGIB_RGAlpha, postGIB_RGBeta)
t_percentDiff <- (mean(BT2Samples) - mean(GIB_RGSamples))/mean(GIB_RGSamples)*100
pBT2_GIB_RG <- mean((BT2Samples > GIB_RGSamples))
# - Probability of v1 better than v2:
print(paste('The probability of BT2 having more user registrations than GIB_RG is: ', pBT2_GIB_RG))
[1] "The probability of BT2 having more user registrations than GIB_RG is:  0"
# - v1 Campaign Lift
percentDiff <- (BT2Samples - GIB_RGSamples)/GIB_RGSamples*100
percentDiff <- data.frame(percentDiff = percentDiff,
                          area = ifelse(percentDiff <= 0, '<= 0', '> 0'),
                          stringsAsFactors = T)
print(paste('The percent lift that BT2 has over GIB_RG (',
            round(t_percentDiff, 2),
            '%) lies in the interval (', 
            as.character(round(quantile(percentDiff$percentDiff, .025)), 2), 
            '%, ', 
            as.character(round(quantile(percentDiff$percentDiff, .975)), 2),
            '%) with 95% certainty.',
            sep = ""))
[1] "The percent lift that BT2 has over GIB_RG (-62.91%) lies in the interval (-68%, -57%) with 95% certainty."
ggplot(percentDiff, aes(x = percentDiff,
                        fill = area)) + 
  geom_histogram(binwidth = .1, alpha = .5) + 
  scale_y_continuous(labels = comma) +
  xlab('(BT2 - GIB_RG)/GIB_RG') + ylab('Density') + 
  ggtitle('BT2/GIB_RG Campaign Lift') +
  theme_minimal() + 
  theme(plot.title = element_text(size = 10))

Evaluation: BT3 vs. GIB_LP
mcN <- 1e5
BT3Samples <- rbeta(mcN, postB3Alpha, postB3Beta)
GIB_LPSamples <- rbeta(mcN, postGIB_LPAlpha, postGIB_LPBeta)
t_percentDiff <- (mean(BT3Samples) - mean(GIB_LPSamples))/mean(GIB_LPSamples)*100
pBT3_GIB_LP <- mean((BT3Samples > GIB_LPSamples))
# - Probability of v1 better than v2:
print(paste('The probability of BT3 having more user registrations than GIB_LP is: ', pBT3_GIB_LP))
[1] "The probability of BT3 having more user registrations than GIB_LP is:  0"
# - v1 Campaign Lift
percentDiff <- (BT3Samples - GIB_LPSamples)/GIB_LPSamples*100
percentDiff <- data.frame(percentDiff = percentDiff,
                          area = ifelse(percentDiff <= 0, '<= 0', '> 0'),
                          stringsAsFactors = T)
print(paste('The percent lift that BT3 has over GIB_LP (',
            round(t_percentDiff, 2),
            '%) lies in the interval (', 
            as.character(round(quantile(percentDiff$percentDiff, .025)), 2), 
            '%, ', 
            as.character(round(quantile(percentDiff$percentDiff, .975)), 2),
            '%) with 95% certainty.',
            sep = ""))
[1] "The percent lift that BT3 has over GIB_LP (-64.79%) lies in the interval (-72%, -56%) with 95% certainty."
ggplot(percentDiff, aes(x = percentDiff,
                        fill = area)) + 
  geom_histogram(binwidth = .1, alpha = .5) + 
  scale_y_continuous(labels = comma) +
  xlab('(BT3 - GIB_LP)/GIB_LP') + ylab('Density') + 
  ggtitle('BT3/GIB_LP Campaign Lift') +
  theme_minimal() + 
  theme(plot.title = element_text(size = 10))

Evaluation: BT3 vs. GIB_RG
mcN <- 1e5
BT3Samples <- rbeta(mcN, postB2Alpha, postB2Beta)
GIB_RGSamples <- rbeta(mcN, postGIB_RGAlpha, postGIB_RGBeta)
t_percentDiff <- (mean(BT3Samples) - mean(GIB_RGSamples))/mean(GIB_RGSamples)*100
pBT3_GIB_RG <- mean((BT3Samples > GIB_RGSamples))
# - Probability of v1 better than v2:
print(paste('The probability of BT3 having more user registrations than GIB_RG is: ', pBT3_GIB_RG))
[1] "The probability of BT3 having more user registrations than GIB_RG is:  0"
# - v1 Campaign Lift
percentDiff <- (BT3Samples - GIB_RGSamples)/GIB_RGSamples*100
percentDiff <- data.frame(percentDiff = percentDiff,
                          area = ifelse(percentDiff <= 0, '<= 0', '> 0'),
                          stringsAsFactors = T)
print(paste('The percent lift that BT3 has over GIB_RG (',
            round(t_percentDiff, 2),
            '%) lies in the interval (', 
            as.character(round(quantile(percentDiff$percentDiff, .025)), 2), 
            '%, ', 
            as.character(round(quantile(percentDiff$percentDiff, .975)), 2),
            '%) with 95% certainty.',
            sep = ""))
[1] "The percent lift that BT3 has over GIB_RG (-62.92%) lies in the interval (-68%, -57%) with 95% certainty."
ggplot(percentDiff, aes(x = percentDiff,
                        fill = area)) + 
  geom_histogram(binwidth = .1, alpha = .5) + 
  scale_y_continuous(labels = comma) +
  xlab('(BT3 - GIB_RG)/GIB_RG') + ylab('Density') + 
  ggtitle('BT3/GIB_RG Campaign Lift') +
  theme_minimal() + 
  theme(plot.title = element_text(size = 10))

Evaluation: GIB_LP vs. GIB_RG
mcN <- 1e5
GIB_LPSamples <- rbeta(mcN, postB2Alpha, postB2Beta)
GIB_RGSamples <- rbeta(mcN, postGIB_RGAlpha, postGIB_RGBeta)
t_percentDiff <- (mean(GIB_LPSamples) - mean(GIB_RGSamples))/mean(GIB_RGSamples)*100
pGIB_LP_GIB_RG <- mean((GIB_LPSamples > GIB_RGSamples))
# - Probability of v1 better than v2:
print(paste('The probability of GIB_LP having more user registrations than GIB_RG is: ', pGIB_LP_GIB_RG))
[1] "The probability of GIB_LP having more user registrations than GIB_RG is:  0"
# - v1 Campaign Lift
percentDiff <- (GIB_LPSamples - GIB_RGSamples)/GIB_RGSamples*100
percentDiff <- data.frame(percentDiff = percentDiff,
                          area = ifelse(percentDiff <= 0, '<= 0', '> 0'),
                          stringsAsFactors = T)
print(paste('The percent lift that GIB_LP has over GIB_RG (',
            round(t_percentDiff, 2),
            '%) lies in the interval (', 
            as.character(round(quantile(percentDiff$percentDiff, .025)), 2), 
            '%, ', 
            as.character(round(quantile(percentDiff$percentDiff, .975)), 2),
            '%) with 95% certainty.',
            sep = ""))
[1] "The percent lift that GIB_LP has over GIB_RG (-62.91%) lies in the interval (-68%, -57%) with 95% certainty."
ggplot(percentDiff, aes(x = percentDiff,
                        fill = area)) + 
  geom_histogram(binwidth = .1, alpha = .5) + 
  scale_y_continuous(labels = comma) +
  xlab('(GIB_LP - GIB_RG)/GIB_RG') + ylab('Density') + 
  ggtitle('GIB_LP/GIB_RG Campaign Lift') +
  theme_minimal() + 
  theme(plot.title = element_text(size = 10))

5.1B User Edits

Summary

What we know almost certainly is that, in general, the BT banners dominate the GIB_LP and GIB_RG banners in terms of the expected number of user edits. The BT1 and BT3 banners beat the BT2 banner in this respect, while the finding on the difference between BT1 and BT3 is inconclusive. BT2 is the only banner from the BT group that performs worse than the GIB_LP and GIB_RG banners in this respect. Also, the GIB_LP and GIB_RG banners do not differ signficantly in the number of expected user edits that they influence.

NOTE. I use the term campaign lift in the following sections to refer to a difference in the probability of user registration in respect to any pair of banners that were used during the ABC2017 campaign. Theoretically, a campaign lift would be the difference (in some KPI) between a group of users who were exposed to the campaign and the control group (no exposure). If we would assess the ABC2017 campaign in this manner then it would be natural to take GIB_RG as a control, and compare all other groups (BT1, BT2, BT3, and GIB_LP) to it; in that case, we would observe that ABC2017 campaign had a lift in respect to the control group in terms of the expected number of user edits.

The following sections provide the results of pairwise Bayesian A/B tests across the campaign banners. TECHNICAL NOTE. Uniform Dirichlet() priors with a concentration parameter of 1 were used to derive the expected user edit distributions, while uniform Beta(1, 1) uninformative priors were used for A/B testing; 1,000,000 Monte Carlo samples from the posteriors were used.

Prepare priors, data, and test functions.

# - Number of Monte Carlo samples:
mcN <- 1e6
# - the dataset
edData <- left_join(editData, userReg, 
                    by = c("rev_user" = "event_userId")) %>% 
  group_by(event_campaign, edits) %>% 
  summarise(Count = n())
colnames(edData) <- c('Campaign', 'Edits', 'Count')
# - edData$Match
edData$Match <- edData$Edits
# - max. observed Edits:
maxEdits <- max(edData$Edits)
# - fill in missing edits
campaigns <- unique(edData$Campaign)
nCampaigns <- length(campaigns)
campaigns <- unlist(lapply(campaigns, function(x){
  return(rep(x, maxEdits + 1))
}))
edDataCopy <- data.frame(Campaign = campaigns, 
                         Match = rep(seq(0, maxEdits, by = 1), nCampaigns),
                         stringsAsFactors = F)
edDataCopy <- left_join(edDataCopy, edData, 
                        by = c("Campaign" = "Campaign", "Match" = "Match")
                        )
edData <- edDataCopy
rm(edDataCopy);
edData$Count[is.na(edData$Count)] <- 0
edData$Edits <- edData$Match
edData$Match <- NULL
# - banner edit probability
bannerEdProb <- edData %>% 
  group_by(Campaign) %>% 
  mutate(Prob = Count/sum(Count)) %>% 
  mutate(Expect = Prob * Edits)
bannerEdProb$Campaign <- toupper(gsub("wmde_abc2017_", "", bannerEdProb$Campaign, fixed = T))
# - true expected edit per banner
campaignTER <- edData %>% 
  mutate(Ex = Edits * Count) %>%
  group_by(Campaign) %>% 
  summarise(TER = Edits %*% (Count/sum(Count)), SD = sd(Ex))
campaignTER$Campaign <- toupper(gsub("wmde_abc2017_", "", campaignTER$Campaign, fixed = T))
# - N user registrations per Banner
bannerNUser <- regPlotSet %>% 
  group_by(Campaign) %>% 
  summarise(Registration = sum(Registrations))
# - posterior expected edit samples:
posteriorEEditSample <- function(alpha, counts, values, samples) {
  dirichletSample <- rdirichlet(samples, counts + alpha)
  dirichletSample %*% values
}
# - posterior expected edit A/B test:
posterior_EEdit_AB <- function(data, campaignA, campaignB, mcN) {
  if (!(campaignA %in% unique(data$Campaign)) | 
                              !(campaignB %in% unique(data$Campaign))) {
    stop("Campaign not found.", 
         call. = TRUE)
  } else {
    
    # - prepare res:
    res <- list()
    
    # - Uninformative priors
    priorA <- rep(1, length(which(data$Campaign %in% campaignA)))
    priorB <- rep(1, length(which(data$Campaign %in% campaignB)))
    
    # - Simulate banners:
    countsA <- data$Count[which(data$Campaign %in% campaignA)]
    countsB <- data$Count[which(data$Campaign %in% campaignB)]
    
    # - edit values:
    editValuesA <- data$Edits[which(data$Campaign %in% campaignA)]
    editValuesB <- data$Edits[which(data$Campaign %in% campaignB)]
    
    # - posterior expected edits:
    posteriorA <- posteriorEEditSample(alpha = priorA,
                                       counts = countsA,
                                       values = editValuesA,
                                       samples = mcN) 
    posteriorA <- data.frame(posterior = posteriorA)
    posteriorB <- posteriorEEditSample(alpha = priorB,
                                       counts = countsB,
                                       values = editValuesB,
                                       samples = mcN) 
    posteriorB <- data.frame(posterior = posteriorB)
    
    # - res: posteriors
    res$posteriorA <- posteriorA
    res$posteriorB <- posteriorB
    # - res: probability A/B
    res$probability <- mean(posteriorA > posteriorB)
    
    # - res: percent difference (campaign Lift)
    res$percentDiff <- (posteriorA$posterior - posteriorB$posterior)/posteriorB$posterior*100
    area = ifelse(res$percentDiff <= 0, '<= 0', '> 0')
    res$percentDiff <- data.frame(percentDiff = res$percentDiff,
                                  area = area,
                                  stringsAsFactors = T)
 
    # - res: true percent difference:
    res$t_percentDiff <- 
      (mean(res$posteriorA$posterior) - mean(res$posteriorB$posterior))/mean(res$posteriorB$posterior)*100
    
    # - out:
    return(res)
    
  }
}
Expected User Edits per Campaign

The average number of edits per user vs. the campaign via they have registered:

campaignTER$Campaign <- factor(campaignTER$Campaign, 
                                levels = names(campaignChartColors))
colnames(campaignTER) <- c('Campaign', 'Expected', 'S.D.')
ggplot(campaignTER, aes(x = Campaign,
                        y = Expected,
                        fill = Campaign,
                        color = Campaign, 
                        label = round(Expected, 2))) +
  geom_bar(stat = "identity", 
           position = "dodge", 
           width = .5) +
  scale_y_continuous(labels = comma) + 
  scale_fill_manual("legend", values = campaignChartColors) + 
  scale_color_manual("legend", values = campaignChartColors) + 
  geom_label(fill = "white", color = "black") +
  ggtitle('Autumn Banner Campaign 2017: User Edits per Campaign') +
  theme_minimal() + 
  theme(axis.text.x = element_text(angle = 0, size = 8, hjust = 1)) +
  theme(plot.title = element_text(size = 10)) +
  theme(legend.title = element_blank()) +
  theme(panel.grid.major.x = element_blank()) +
  theme(panel.grid.minor.x = element_blank()) +
  theme(panel.background = element_blank())

Expected edits and the respective standard deviations:

campaignTER$Expected <- round(campaignTER$Expected, 2)
campaignTER$`S.D.` <- round(campaignTER$`S.D.`, 2)
knitr::kable(campaignTER, format = "html") %>% 
  kable_styling(full_width = F, position = "left")
Campaign Expected S.D.
BT1 3.03 5.01
BT2 2.63 8.67
BT3 4.10 8.29
GIB_LP 1.80 4.29
GIB_RG 2.45 4.61

Let’s take a closer look upon the distributions of user edits per campaign:

# - the dataset
edDataDist <- left_join(editData, userReg,
                        by = c("rev_user" = "event_userId")) %>% 
  dplyr::select(event_campaign, edits)
colnames(edDataDist) <- c('Campaign', 'Edits')
edDataDist$Campaign <- toupper(gsub("wmde_abc2017_", "", edDataDist$Campaign, fixed = T))
edDataDist$Alpha = edDataDist$Edits/max(edDataDist$Edits)
ggplot(edDataDist, aes(x = Campaign, y = Edits, 
                       group = Campaign, 
                       fill = Campaign,
                       color = Campaign)) + 
  geom_point(aes(alpha = edDataDist$Alpha), 
             position = "jitter", 
             size = 1.5) + 
  scale_y_continuous(labels = comma) + 
  scale_fill_manual("legend", values = campaignChartColors) + 
  scale_color_manual("legend", values = campaignChartColors) + 
  ggtitle('Autumn Banner Campaign 2017: User Edits Distributions per Campaign') +
  theme_minimal() + 
  theme(axis.text.x = element_text(angle = 0, size = 8, hjust = 1)) +
  theme(plot.title = element_text(size = 10)) +
  theme(legend.title = element_blank()) +
  theme(panel.grid.major.x = element_blank()) +
  theme(panel.grid.minor.x = element_blank()) +
  theme(panel.background = element_blank()) + 
  theme(legend.position = 'None')

Note that not too many new users have made any significant number of edits. This fact - the scarcity of avilable data - imposes several constraints upon the present analysis. Please read through carefully and do not jump to conclusions before more data become available.

Evaluation: BT1 vs. BT2
campA <- 'BT1'
campB <- 'BT2'
testAB <- posterior_EEdit_AB(bannerEdProb, campA, campB, mcN = mcN)
# - Probability of campA better than campB:
print(paste('The probability of ', 
            campA,  
            ' influencing more user edits than ', 
            campB, ' is : ', 
            round(testAB$probability, 2), 
            sep = ""))
[1] "The probability of BT1 influencing more user edits than BT2 is : 0.99"
# - lift:
print(paste('The percent lift that ', 
            campA, 
            ' has over ', 
            campB, 
            ' (',
            round(testAB$t_percentDiff, 2),
            '%) lies in the interval (', 
            as.character(round(quantile(testAB$percentDiff$percentDiff, .025)), 2), 
            '%, ', 
            as.character(round(quantile(testAB$percentDiff$percentDiff, .975)), 2),
            '%) with 95% certainty.',
            sep = ""))
[1] "The percent lift that BT1 has over BT2 (43.68%) lies in the interval (9%, 90%) with 95% certainty."
ggplot(testAB$percentDiff, 
       aes(x = percentDiff,
           group = area,
           fill = area)) + 
  geom_histogram(bins = 1000, alpha = .5) + 
  scale_y_continuous(labels = comma) +
  xlab(paste('(', campA, '-', campB, ')/', campB, sep = "")) + ylab('Density') + 
  ggtitle(paste(campA, '/', campB, ' Campaign Lift', sep = "")) +
  theme_minimal() + 
  theme(plot.title = element_text(size = 10))

Evaluation: BT1 vs. BT3
campA <- 'BT1'
campB <- 'BT3'
testAB <- posterior_EEdit_AB(bannerEdProb, campA, campB, mcN = mcN)
# - Probability of campA better than campB:
print(paste('The probability of ', 
            campA,  
            ' influencing more user edits than ', 
            campB, ' is : ', 
            round(testAB$probability, 2), 
            sep = ""))
[1] "The probability of BT1 influencing more user edits than BT3 is : 0.43"
# - lift:
print(paste('The percent lift that ', 
            campA, 
            ' has over ', 
            campB, 
            ' (',
            round(testAB$t_percentDiff, 2),
            '%) lies in the interval (', 
            as.character(round(quantile(testAB$percentDiff$percentDiff, .025)), 2), 
            '%, ', 
            as.character(round(quantile(testAB$percentDiff$percentDiff, .975)), 2),
            '%) with 95% certainty.',
            sep = ""))
[1] "The percent lift that BT1 has over BT3 (-2.53%) lies in the interval (-25%, 28%) with 95% certainty."
ggplot(testAB$percentDiff, 
       aes(x = percentDiff,
           group = area,
           fill = area)) + 
  geom_histogram(bins = 1000, alpha = .5) + 
  scale_y_continuous(labels = comma) +
  xlab(paste('(', campA, '-', campB, ')/', campB, sep = "")) + ylab('Density') + 
  ggtitle(paste(campA, '/', campB, ' Campaign Lift', sep = "")) +
  theme_minimal() + 
  theme(plot.title = element_text(size = 10))

Evaluation: BT1 vs. GIB_LP
campA <- 'BT1'
campB <- 'GIB_LP'
testAB <- posterior_EEdit_AB(bannerEdProb, campA, campB, mcN = mcN)
# - Probability of campA better than campB:
print(paste('The probability of ', 
            campA,  
            ' influencing more user edits than ', 
            campB, ' is : ', 
            round(testAB$probability, 2), 
            sep = ""))
[1] "The probability of BT1 influencing more user edits than GIB_LP is : 0.79"
# - lift:
print(paste('The percent lift that ', 
            campA, 
            ' has over ', 
            campB, 
            ' (',
            round(testAB$t_percentDiff, 2),
            '%) lies in the interval (', 
            as.character(round(quantile(testAB$percentDiff$percentDiff, .025)), 2), 
            '%, ', 
            as.character(round(quantile(testAB$percentDiff$percentDiff, .975)), 2),
            '%) with 95% certainty.',
            sep = ""))
[1] "The percent lift that BT1 has over GIB_LP (12.05%) lies in the interval (-15%, 48%) with 95% certainty."
ggplot(testAB$percentDiff, 
       aes(x = percentDiff,
           group = area,
           fill = area)) + 
  geom_histogram(bins = 1000, alpha = .5) + 
  scale_y_continuous(labels = comma) +
  xlab(paste('(', campA, '-', campB, ')/', campB, sep = "")) + ylab('Density') + 
  ggtitle(paste(campA, '/', campB, ' Campaign Lift', sep = "")) +
  theme_minimal() + 
  theme(plot.title = element_text(size = 10))

Evaluation: BT1 vs. GIB_RG
campA <- 'BT1'
campB <- 'GIB_RG'
testAB <- posterior_EEdit_AB(bannerEdProb, campA, campB, mcN = mcN)
# - Probability of campA better than campB:
print(paste('The probability of ', 
            campA,  
            ' influencing more user edits than ', 
            campB, ' is : ', 
            round(testAB$probability, 2), 
            sep = ""))
[1] "The probability of BT1 influencing more user edits than GIB_RG is : 0.78"
# - lift:
print(paste('The percent lift that ', 
            campA, 
            ' has over ', 
            campB, 
            ' (',
            round(testAB$t_percentDiff, 2),
            '%) lies in the interval (', 
            as.character(round(quantile(testAB$percentDiff$percentDiff, .025)), 2), 
            '%, ', 
            as.character(round(quantile(testAB$percentDiff$percentDiff, .975)), 2),
            '%) with 95% certainty.',
            sep = ""))
[1] "The percent lift that BT1 has over GIB_RG (11.47%) lies in the interval (-15%, 47%) with 95% certainty."
ggplot(testAB$percentDiff, 
       aes(x = percentDiff,
           group = area,
           fill = area)) + 
  geom_histogram(bins = 1000, alpha = .5) + 
  scale_y_continuous(labels = comma) +
  xlab(paste('(', campA, '-', campB, ')/', campB, sep = "")) + ylab('Density') + 
  ggtitle(paste(campA, '/', campB, ' Campaign Lift', sep = "")) +
  theme_minimal() + 
  theme(plot.title = element_text(size = 10))

Evaluation: BT2 vs. BT3
campA <- 'BT2'
campB <- 'BT3'
testAB <- posterior_EEdit_AB(bannerEdProb, campA, campB, mcN = mcN)
# - Probability of campA better than campB:
print(paste('The probability of ', 
            campA,  
            ' influencing more user edits than ', 
            campB, ' is : ', 
            round(testAB$probability, 2), 
            sep = ""))
[1] "The probability of BT2 influencing more user edits than BT3 is : 0"
# - lift:
print(paste('The percent lift that ', 
            campA, 
            ' has over ', 
            campB, 
            ' (',
            round(testAB$t_percentDiff, 2),
            '%) lies in the interval (', 
            as.character(round(quantile(testAB$percentDiff$percentDiff, .025)), 2), 
            '%, ', 
            as.character(round(quantile(testAB$percentDiff$percentDiff, .975)), 2),
            '%) with 95% certainty.',
            sep = ""))
[1] "The percent lift that BT2 has over BT3 (-32.17%) lies in the interval (-49%, -10%) with 95% certainty."
ggplot(testAB$percentDiff, 
       aes(x = percentDiff,
           group = area,
           fill = area)) + 
  geom_histogram(bins = 1000, alpha = .5) + 
  scale_y_continuous(labels = comma) +
  xlab(paste('(', campA, '-', campB, ')/', campB, sep = "")) + ylab('Density') + 
  ggtitle(paste(campA, '/', campB, ' Campaign Lift', sep = "")) +
  theme_minimal() + 
  theme(plot.title = element_text(size = 10))

Evaluation: BT2 vs. GIB_LP
campA <- 'BT2'
campB <- 'GIB_LP'
testAB <- posterior_EEdit_AB(bannerEdProb, campA, campB, mcN = mcN)
# - Probability of campA better than campB:
print(paste('The probability of ', 
            campA,  
            ' influencing more user edits than ', 
            campB, ' is : ', 
            round(testAB$probability, 2), 
            sep = ""))
[1] "The probability of BT2 influencing more user edits than GIB_LP is : 0.05"
# - lift:
print(paste('The percent lift that ', 
            campA, 
            ' has over ', 
            campB, 
            ' (',
            round(testAB$t_percentDiff, 2),
            '%) lies in the interval (', 
            as.character(round(quantile(testAB$percentDiff$percentDiff, .025)), 2), 
            '%, ', 
            as.character(round(quantile(testAB$percentDiff$percentDiff, .975)), 2),
            '%) with 95% certainty.',
            sep = ""))
[1] "The percent lift that BT2 has over GIB_LP (-22.01%) lies in the interval (-42%, 4%) with 95% certainty."
ggplot(testAB$percentDiff, 
       aes(x = percentDiff,
           group = area,
           fill = area)) + 
  geom_histogram(bins = 1000, alpha = .5) + 
  scale_y_continuous(labels = comma) +
  xlab(paste('(', campA, '-', campB, ')/', campB, sep = "")) + ylab('Density') + 
  ggtitle(paste(campA, '/', campB, ' Campaign Lift', sep = "")) +
  theme_minimal() + 
  theme(plot.title = element_text(size = 10))

Evaluation: BT2 vs. GIB_RG
campA <- 'BT2'
campB <- 'GIB_RG'
testAB <- posterior_EEdit_AB(bannerEdProb, campA, campB, mcN = mcN)
# - Probability of campA better than campB:
print(paste('The probability of ', 
            campA,  
            ' influencing more user edits than ', 
            campB, ' is : ', 
            round(testAB$probability, 2), 
            sep = ""))
[1] "The probability of BT2 influencing more user edits than GIB_RG is : 0.04"
# - lift:
print(paste('The percent lift that ', 
            campA, 
            ' has over ', 
            campB, 
            ' (',
            round(testAB$t_percentDiff, 2),
            '%) lies in the interval (', 
            as.character(round(quantile(testAB$percentDiff$percentDiff, .025)), 2), 
            '%, ', 
            as.character(round(quantile(testAB$percentDiff$percentDiff, .975)), 2),
            '%) with 95% certainty.',
            sep = ""))
[1] "The percent lift that BT2 has over GIB_RG (-22.43%) lies in the interval (-42%, 3%) with 95% certainty."
ggplot(testAB$percentDiff, 
       aes(x = percentDiff,
           group = area,
           fill = area)) + 
  geom_histogram(bins = 1000, alpha = .5) + 
  scale_y_continuous(labels = comma) +
  xlab(paste('(', campA, '-', campB, ')/', campB, sep = "")) + ylab('Density') + 
  ggtitle(paste(campA, '/', campB, ' Campaign Lift', sep = "")) +
  theme_minimal() + 
  theme(plot.title = element_text(size = 10))

Evaluation: BT3 vs. GIB_LP
campA <- 'BT3'
campB <- 'GIB_LP'
testAB <- posterior_EEdit_AB(bannerEdProb, campA, campB, mcN = mcN)
# - Probability of campA better than campB:
print(paste('The probability of ', 
            campA,  
            ' influencing more user edits than ', 
            campB, ' is : ', 
            round(testAB$probability, 2), 
            sep = ""))
[1] "The probability of BT3 influencing more user edits than GIB_LP is : 0.84"
# - lift:
print(paste('The percent lift that ', 
            campA, 
            ' has over ', 
            campB, 
            ' (',
            round(testAB$t_percentDiff, 2),
            '%) lies in the interval (', 
            as.character(round(quantile(testAB$percentDiff$percentDiff, .025)), 2), 
            '%, ', 
            as.character(round(quantile(testAB$percentDiff$percentDiff, .975)), 2),
            '%) with 95% certainty.',
            sep = ""))
[1] "The percent lift that BT3 has over GIB_LP (14.98%) lies in the interval (-13%, 52%) with 95% certainty."
ggplot(testAB$percentDiff, 
       aes(x = percentDiff,
           group = area,
           fill = area)) + 
  geom_histogram(bins = 1000, alpha = .5) + 
  scale_y_continuous(labels = comma) +
  xlab(paste('(', campA, '-', campB, ')/', campB, sep = "")) + ylab('Density') + 
  ggtitle(paste(campA, '/', campB, ' Campaign Lift', sep = "")) +
  theme_minimal() + 
  theme(plot.title = element_text(size = 10))

Evaluation: BT3 vs. GIB_RG
campA <- 'BT3'
campB <- 'GIB_RG'
testAB <- posterior_EEdit_AB(bannerEdProb, campA, campB, mcN = mcN)
# - Probability of campA better than campB:
print(paste('The probability of ', 
            campA,  
            ' influencing more user edits than ', 
            campB, ' is : ', 
            round(testAB$probability, 2), 
            sep = ""))
[1] "The probability of BT3 influencing more user edits than GIB_RG is : 0.83"
# - lift:
print(paste('The percent lift that ', 
            campA, 
            ' has over ', 
            campB, 
            ' (',
            round(testAB$t_percentDiff, 2),
            '%) lies in the interval (', 
            as.character(round(quantile(testAB$percentDiff$percentDiff, .025)), 2), 
            '%, ', 
            as.character(round(quantile(testAB$percentDiff$percentDiff, .975)), 2),
            '%) with 95% certainty.',
            sep = ""))
[1] "The percent lift that BT3 has over GIB_RG (14.35%) lies in the interval (-13%, 51%) with 95% certainty."
ggplot(testAB$percentDiff, 
       aes(x = percentDiff,
           group = area,
           fill = area)) + 
  geom_histogram(bins = 1000, alpha = .5) + 
  scale_y_continuous(labels = comma) +
  xlab(paste('(', campA, '-', campB, ')/', campB, sep = "")) + ylab('Density') + 
  ggtitle(paste(campA, '/', campB, ' Campaign Lift', sep = "")) +
  theme_minimal() + 
  theme(plot.title = element_text(size = 10))

Evaluation: GIB_LP vs. GIB_RG
campA <- 'GIB_LP'
campB <- 'GIB_RG'
testAB <- posterior_EEdit_AB(bannerEdProb, campA, campB, mcN = mcN)
# - Probability of campA better than campB:
print(paste('The probability of ', 
            campA,  
            ' influencing more user edits than ', 
            campB, ' is : ', 
            round(testAB$probability, 2), 
            sep = ""))
[1] "The probability of GIB_LP influencing more user edits than GIB_RG is : 0.48"
# - lift:
print(paste('The percent lift that ', 
            campA, 
            ' has over ', 
            campB, 
            ' (',
            round(testAB$t_percentDiff, 2),
            '%) lies in the interval (', 
            as.character(round(quantile(testAB$percentDiff$percentDiff, .025)), 2), 
            '%, ', 
            as.character(round(quantile(testAB$percentDiff$percentDiff, .975)), 2),
            '%) with 95% certainty.',
            sep = ""))
[1] "The percent lift that GIB_LP has over GIB_RG (-0.55%) lies in the interval (-25%, 32%) with 95% certainty."
ggplot(testAB$percentDiff, 
       aes(x = percentDiff,
           group = area,
           fill = area)) + 
  geom_histogram(bins = 1000, alpha = .5) + 
  scale_y_continuous(labels = comma) +
  xlab(paste('(', campA, '-', campB, ')/', campB, sep = "")) + ylab('Density') + 
  ggtitle(paste(campA, '/', campB, ' Campaign Lift', sep = "")) +
  theme_minimal() + 
  theme(plot.title = element_text(size = 10))

5.2 Campaign Multi-Channel Attribution Model: Making an Edit

The following model provides for the removal effects of the Campaign channels in respect to whether a user has made any edits at all or not. This procedure instantiates a model of a particular campaign as a directed graph in which every node represents a campaign channel (e.g. a banner, a page view, an act of a user doing something, etc), and then computes the probabilities of transition from one to another channel. In other words, the model estimates the probabilities of taking any of the possible user journeys in the campaign. Once the model is ready, the procedure simulates a large number of user journeys to produce an estimate of the probability of conversion for each of them. In the case of our campaign we consider the event of a user making at least one edit as a conversion. When this step is completed, the procedure starts removing one by one campaign channel from the model, and each time it re-computes the conversion probability to estimate how many conversions would be lost due to the removal of a particular channel. The larger the drop in probability of conversion due to the removal of a particular channel, the larger the removal effect for that channel. Channels with larger removal effects are considered to be more important. The value of the removal effect, being a probability in itself, can vary from 0 to 1.

In this case, the campaign channels are the following events:

  • BT1 - Specific Task Banner wmde_abc2017_bt1 is presented;
  • BT2 - Specific Task Banner wmde_abc2017_bt2 is presented;
  • BT3 - Specific Task Banner wmde_abc2017_bt3 is presented;
  • GIB - General Inviation Banner - wmde_abc2017_gib_lp or wmde_abc2017_gib_rg is presented;
  • TLP - Specific Task Page JetztMitmachen is viewed (note: the same as a banner click on any of the following banners: BT1, BT2, BT3);
  • GLP - General Page Mach_mit is viewed; (note: the same as a banner click on GIB_LP);
  • RP - Registration Page Benutzerkonto_anlegen is viewed; (note: encompasses users who transit from JetztMitmachen or Mach_mit, as well as banner clicks on GIB_RG);
  • Reg - The act of user registration;
  • GT - The act of completing the Guided Tour.

Important: unlike in the Bayesian A/B tests that are presented above, where the criterion for pair-wise comparisons among the campaign banners was either the number of users registered (Section 5.1A), or the number of edits made (Section 5.1B), here the criterion (i.e. the definition of conversion, if you prefer) is whether a user has made any edits at all. The reason that motivates this criterion, and not a more strict criterion of making >= 10 edits, is simply because there are only several users who have registered via this campaign and made more than ten edits until now. Removal Effects. The Removal Effect for a campaign channel represents the change in probability that a conversion would obtain if the respective channel was removed from the campaign. Once again, given that conversion here means a user making at least one edit, the removal effects tells us how much would the probability of obtaining at least one edit from a user drop if the respective campaign channel was removed. TECHNICAL NOTE: a Markov model of order 4 was used, with 1e8 total simulation runs from the transition matrix.

Removal Effects

### --- Banner -> Exit paths --- ###
### --- Definition: N(Banner Impressions) - N(BannerClicks == Landing Page Views)
# - define: N(Banner Impressions)
bImp <- banImpSet %>% 
  group_by(Banner) %>% 
  summarise(Count = sum(Count))
nBT1 <- bImp$Count[which(bImp$Banner %in% 'BT1')] 
nBT2 <- bImp$Count[which(bImp$Banner %in% 'BT2')]
nBT3 <- bImp$Count[which(bImp$Banner %in% 'BT3')]
nGIB <- bImp$Count[which(bImp$Banner %in% 'GIB_LP')] + bImp$Count[which(bImp$Banner %in% 'GIB_RG')]
# - define: N(BannerClicks/PageViews)
bClick <- clickPlotSet %>% 
  group_by(Source) %>% 
  summarise(Count = sum(Count))
bClick$Source <- gsub("_click", "", bClick$Source, fixed = T)
# - define: N(BannerImpressions) - N(BannerClicks/PageViews)
nBT1 <- nBT1 - bClick$Count[which(bClick$Source %in% 'BT1')] 
nBT2 <- nBT2 - bClick$Count[which(bClick$Source %in% 'BT2')]
nBT3 <- nBT3 - bClick$Count[which(bClick$Source %in% 'BT3')]
nGIB <- nGIB - bClick$Count[which(bClick$Source %in% 'GIB_LP')] + bClick$Count[which(bClick$Source %in% 'GIB_RG')]
### --- Banner -> Landing Page -> Exit paths --- ###
### --- N(Banner Clicks == Landing Page Views) - N(Registration Page Views)
### --- NOTE: TLP == Task Landing Page (JetztMitmachen), GLP == General Landing Page (Mach mit)
nBT1_TLP <- bClick$Count[which(bClick$Source %in% 'BT1')] - 
  pageSource$n[pageSource$Page %in% 'Spezial:Benutzerkonto_anlegen' & pageSource$Source %in% 'JetztMitmachen_BT1']
nBT2_TLP <- bClick$Count[which(bClick$Source %in% 'BT2')] - 
  pageSource$n[pageSource$Page %in% 'Spezial:Benutzerkonto_anlegen' & pageSource$Source %in% 'JetztMitmachen_BT2']
nBT3_TLP <- bClick$Count[which(bClick$Source %in% 'BT3')] - 
  pageSource$n[pageSource$Page %in% 'Spezial:Benutzerkonto_anlegen' & pageSource$Source %in% 'JetztMitmachen_BT3']
nGIB_GLP <- bClick$Count[which(bClick$Source %in% 'GIB_LP')] - 
  pageSource$n[pageSource$Page %in% 'Spezial:Benutzerkonto_anlegen' & pageSource$Source %in% 'Mach_mit']
### --- Banner (-> Landing Page) -> Registration Page -> Exit paths --- ###
### --- N(Registration Page Views) - N(User Registrations)
bUserReg <- userReg %>% 
  group_by(event_campaign) %>% 
  summarise(Count = n())
bUserReg$event_campaign <- toupper(gsub("wmde_abc2017_", "", bUserReg$event_campaign, fixed = T))
colnames(bUserReg)[1] <- 'Banner'
nBT1_TLP_RP <- pageSource$n[pageSource$Page %in% 'Spezial:Benutzerkonto_anlegen' & pageSource$Source %in% 'JetztMitmachen_BT1'] - 
  bUserReg$Count[bUserReg$Banner %in% 'BT1']
nBT2_TLP_RP <- pageSource$n[pageSource$Page %in% 'Spezial:Benutzerkonto_anlegen' & pageSource$Source %in% 'JetztMitmachen_BT2'] - 
  bUserReg$Count[bUserReg$Banner %in% 'BT2']
nBT3_TLP_RP <- pageSource$n[pageSource$Page %in% 'Spezial:Benutzerkonto_anlegen' & pageSource$Source %in% 'JetztMitmachen_BT3'] - 
  bUserReg$Count[bUserReg$Banner %in% 'BT3']
nGIB_GLP_RP <- pageSource$n[pageSource$Page %in% 'Mach_mit' & pageSource$Source %in% 'GIB_LP_click'] - 
  bUserReg$Count[bUserReg$Banner %in% 'GIB_LP']
nGIB_RP <- pageSource$n[pageSource$Page %in% 'Spezial:Benutzerkonto_anlegen' & pageSource$Source %in% 'GIB_RG_click'] - 
  bUserReg$Count[bUserReg$Banner %in% 'GIB_RG']
### --- Banner (-> Landing Page) -> Registration Page -> Registration -> Exit --- ###
### --- N(User Registrations) - N(Edited) - N(Completed GT and Not Edited)
userRegGT <- left_join(userReg, gTourData, 
                       by = 'event_userId')
userRegGT <- left_join(userRegGT, editData, 
                       by = c('event_userId' = 'rev_user'))
nBT1_TLP_RP_Reg <- bUserReg$Count[bUserReg$Banner %in% 'BT1'] - 
  sum((userRegGT$event_campaign %in% 'wmde_abc2017_bt1' & is.na(userRegGT$event_tour)) | 
        (userRegGT$event_campaign %in% 'wmde_abc2017_bt1' & !is.na(userRegGT$event_tour) & !is.na(userRegGT$edits)))
nBT2_TLP_RP_Reg <- bUserReg$Count[bUserReg$Banner %in% 'BT2'] - 
  sum((userRegGT$event_campaign %in% 'wmde_abc2017_bt2' & is.na(userRegGT$event_tour)) | 
        (userRegGT$event_campaign %in% 'wmde_abc2017_bt2' & !is.na(userRegGT$event_tour) & !is.na(userRegGT$edits)))
nBT3_TLP_RP_Reg <- bUserReg$Count[bUserReg$Banner %in% 'BT3'] - 
  sum((userRegGT$event_campaign %in% 'wmde_abc2017_bt3' & is.na(userRegGT$event_tour)) | 
        (userRegGT$event_campaign %in% 'wmde_abc2017_bt3' & !is.na(userRegGT$event_tour) & !is.na(userRegGT$edits)))
nGIB_GLP_RP_Reg <- bUserReg$Count[bUserReg$Banner %in% 'GIB_LP'] - 
  sum((userRegGT$event_campaign %in% 'wmde_abc2017_gib_lp' & is.na(userRegGT$event_tour)) | 
        (userRegGT$event_campaign %in% 'wmde_abc2017_gib_lp' & !is.na(userRegGT$event_tour) & !is.na(userRegGT$edits)))
nGIB_RP_Reg <- bUserReg$Count[bUserReg$Banner %in% 'GIB_RG'] - 
  sum((userRegGT$event_campaign %in% 'wmde_abc2017_gib_rg' & is.na(userRegGT$event_tour)) | 
        (userRegGT$event_campaign %in% 'wmde_abc2017_gib_rg' & !is.na(userRegGT$event_tour) & !is.na(userRegGT$edits)))
### --- Banner (-> Landing Page) -> Registration Page -> Registration -> GT -> Exit --- ###
### --- N(User Registrations) - N(Edited) - N(Completed GT and Not Edited)
nBT1_TLP_RP_Reg_GT <- length(userRegGT$event_userId[(userRegGT$event_campaign %in% 'wmde_abc2017_bt1') & 
                                                      is.na(userRegGT$event_tour) & 
                                                      is.na(userRegGT$edits)])
nBT2_TLP_RP_Reg_GT <- length(userRegGT$event_userId[(userRegGT$event_campaign %in% 'wmde_abc2017_bt2') & 
                                                      is.na(userRegGT$event_tour) & 
                                                      is.na(userRegGT$edits)])
nBT3_TLP_RP_Reg_GT <- length(userRegGT$event_userId[(userRegGT$event_campaign.x %in% 'wmde_abc2017_bt3') & 
                                                      is.na(userRegGT$event_tour) & 
                                                      is.na(userRegGT$edits)])
nGIB_GLP_RP_Reg_GT <- length(userRegGT$event_userId[(userRegGT$event_campaign %in% 'wmde_abc2017_gib_lp') & 
                                                      is.na(userRegGT$event_tour) & 
                                                      is.na(userRegGT$edits)])
nGIB_RP_Reg_GT <- length(userRegGT$event_userId[(userRegGT$event_campaign %in% 'wmde_abc2017_gib_rg') &
                                                  is.na(userRegGT$event_tour) &
                                                  is.na(userRegGT$edits)])
### --- Banner (-> Landing Page) -> Registration Page -> Registration -> GT -> EDIT --- ###
nBT1_TLP_RP_Reg_GT_EDIT <- length(userRegGT$event_userId[(userRegGT$event_campaign %in% 'wmde_abc2017_bt1') & 
                                                      is.na(userRegGT$event_tour) & 
                                                      !is.na(userRegGT$edits)])
nBT2_TLP_RP_Reg_GT_EDIT <- length(userRegGT$event_userId[(userRegGT$event_campaign %in% 'wmde_abc2017_bt2') & 
                                                      is.na(userRegGT$event_tour) & 
                                                      !is.na(userRegGT$edits)])
nBT3_TLP_RP_Reg_GT_EDIT <- length(userRegGT$event_userId[(userRegGT$event_campaign.x %in% 'wmde_abc2017_bt3') & 
                                                      is.na(userRegGT$event_tour) & 
                                                      !is.na(userRegGT$edits)])
nGIB_GLP_RP_Reg_GT_EDIT <- length(userRegGT$event_userId[(userRegGT$event_campaign %in% 'wmde_abc2017_gib_lp') & 
                                                      is.na(userRegGT$event_tour) & 
                                                      !is.na(userRegGT$edits)])
nGIB_RP_Reg_GT_EDIT <- length(userRegGT$event_userId[(userRegGT$event_campaign %in% 'wmde_abc2017_gib_rg') &
                                                  is.na(userRegGT$event_tour) &
                                                  !is.na(userRegGT$edits)])
### --- Banner (-> Landing Page) -> Registration Page -> Registration -> EDIT --- ###
nBT1_TLP_RP_Reg_EDIT <- length(userRegGT$event_userId[(userRegGT$event_campaign %in% 'wmde_abc2017_bt1') & 
                                                      !is.na(userRegGT$event_tour) & 
                                                      !is.na(userRegGT$edits)])
nBT2_TLP_RP_Reg_EDIT <- length(userRegGT$event_userId[(userRegGT$event_campaign %in% 'wmde_abc2017_bt2') & 
                                                      !is.na(userRegGT$event_tour) & 
                                                      !is.na(userRegGT$edits)])
nBT3_TLP_RP_Reg_EDIT <- length(userRegGT$event_userId[(userRegGT$event_campaign %in% 'wmde_abc2017_bt3') & 
                                                      !is.na(userRegGT$event_tour) & 
                                                      !is.na(userRegGT$edits)])
nGIB_GLP_RP_Reg_EDIT <- length(userRegGT$event_userId[(userRegGT$event_campaign %in% 'wmde_abc2017_gib_lp') & 
                                                      !is.na(userRegGT$event_tour) & 
                                                      !is.na(userRegGT$edits)])
nGIB_RP_Reg_EDIT <- length(userRegGT$event_userId[(userRegGT$event_campaign %in% 'wmde_abc2017_gib_rg') &
                                                  !is.na(userRegGT$event_tour) &
                                                  !is.na(userRegGT$edits)])
### --- dataset
mcaData <- data.frame(path = c(deparse(substitute(nBT1)),
                               deparse(substitute(nBT2)),
                               deparse(substitute(nBT3)),
                               deparse(substitute(nGIB)),
                               deparse(substitute(nBT1_TLP)),
                               deparse(substitute(nBT2_TLP)),
                               deparse(substitute(nBT3_TLP)),
                               deparse(substitute(nGIB_GLP)),
                               deparse(substitute(nBT1_TLP_RP)),
                               deparse(substitute(nBT2_TLP_RP)),
                               deparse(substitute(nBT3_TLP_RP)),
                               deparse(substitute(nGIB_GLP_RP)),
                               deparse(substitute(nGIB_RP)),
                               deparse(substitute(nBT1_TLP_RP_Reg)),
                               deparse(substitute(nBT2_TLP_RP_Reg)),
                               deparse(substitute(nBT3_TLP_RP_Reg)),
                               deparse(substitute(nGIB_GLP_RP_Reg)),
                               deparse(substitute(nGIB_RP_Reg)),
                               deparse(substitute(nBT1_TLP_RP_Reg_GT)), 
                               deparse(substitute(nBT2_TLP_RP_Reg_GT)),
                               deparse(substitute(nBT3_TLP_RP_Reg_GT)),
                               deparse(substitute(nGIB_GLP_RP_Reg_GT)),
                               deparse(substitute(nGIB_RP_Reg_GT)),
                               deparse(substitute(nBT1_TLP_RP_Reg_GT_EDIT)),
                               deparse(substitute(nBT2_TLP_RP_Reg_GT_EDIT)),
                               deparse(substitute(nBT3_TLP_RP_Reg_GT_EDIT)),
                               deparse(substitute(nGIB_GLP_RP_Reg_GT_EDIT)),
                               deparse(substitute(nGIB_RP_Reg_GT_EDIT)),
                               deparse(substitute(nBT1_TLP_RP_Reg_EDIT)),
                               deparse(substitute(nBT2_TLP_RP_Reg_EDIT)),
                               deparse(substitute(nBT3_TLP_RP_Reg_EDIT)),
                               deparse(substitute(nGIB_GLP_RP_Reg_EDIT)),
                               deparse(substitute(nGIB_RP_Reg_EDIT))
                               ),
                      total_conversions = c(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
                                            nBT1_TLP_RP_Reg_GT_EDIT, nBT2_TLP_RP_Reg_GT_EDIT, nBT3_TLP_RP_Reg_GT_EDIT, 
                                            nGIB_GLP_RP_Reg_GT_EDIT, nGIB_RP_Reg_GT_EDIT, nBT1_TLP_RP_Reg_EDIT, 
                                            nBT2_TLP_RP_Reg_EDIT, nBT3_TLP_RP_Reg_EDIT, nGIB_GLP_RP_Reg_EDIT, 
                                            nGIB_RP_Reg_EDIT
                                            ),
                      total_null = c(nBT1, nBT2, nBT3, nGIB, nBT1_TLP, nBT2_TLP, nBT3_TLP, nGIB_GLP, nBT1_TLP_RP,
                                     nBT2_TLP_RP, nBT3_TLP_RP, nGIB_GLP_RP, nGIB_RP, 
                                     nBT1_TLP_RP_Reg, nBT2_TLP_RP_Reg, nBT3_TLP_RP_Reg, nGIB_GLP_RP_Reg, nGIB_RP_Reg,
                                     nBT1_TLP_RP_Reg_GT, nBT2_TLP_RP_Reg_GT, nBT3_TLP_RP_Reg_GT, nGIB_GLP_RP_Reg_GT,
                                     nGIB_RP_Reg_GT, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
                                     ),
                      stringsAsFactors = F)
# - correct paths:
mcaData$path <- gsub("n", "", mcaData$path, fixed = T)
mcaData$path <- gsub("_", " > ", mcaData$path, fixed = T)
editEnds <- which(grepl("EDIT", mcaData$path, fixed = T))
for (i in 1:length(editEnds)) {
  wPath <- which(mcaData$path %in% gsub(" > EDIT", "", mcaData$path[editEnds[i]], fixed = T))
  wOut <- which(mcaData$path == mcaData$path[editEnds[i]])
  wPath <- setdiff(wPath, wOut)
  mcaData$total_conversions[wPath] <- mcaData$total_conversions[wOut]
}
mcaData <- mcaData[-which(grepl("EDIT", mcaData$path, fixed = T)), ]
### --- MCA model
abc2017Model <- markov_model(mcaData,
                             var_path = "path",
                             var_conv = "total_conversions",
                             var_null = "total_null",
                             order = 4,
                             nsim = 1e8,
                             out_more = T)
# - collect removal effects for the next plot:
re4order <- abc2017Model$removal_effects$removal_effects
### --- Removal Effects:
re <- as.data.frame(abc2017Model$removal_effects)
colnames(re) <- c('Channel', 'Removal Effect')
re$Channel <- factor(re$Channel, levels = as.character(abc2017Model$removal_effects$channel_name))
gplot <- ggplot(data = re, 
                aes(x = Channel,
                    y = `Removal Effect`,
                    label = round(`Removal Effect`, 2))
                ) + 
  geom_bar(width = .1, color = "darkblue", fill = "white", stat = "identity") + 
  geom_label(size = 3) + 
  scale_y_continuous(labels = comma) + 
  xlab('Campaign Channel') + ylab('Removal Effect') + 
  ylim(c(0, 1)) + 
  ggtitle('Campaign Multi-Channel Attribution: Removal Effects') + 
  theme_minimal() + 
  theme(plot.title = element_text(size = 10))
Scale for 'y' is already present. Adding another scale for 'y', which will replace the existing scale.
suppressWarnings(print(gplot))

Campaign Transition Graph

Each node in the following graph represents a particular campaign channel. The edges of the graph are labeled by the respective transition probabilities between the channels. The size of the node corresponds to its removal effect. TECHNICAL NOTE: the removal effects are derived from a Markov model of order 4, while the transitional probabilities are derived directly from the 1st order model.

### --- MCA model: 1st order for channel-to-channel transitions
abc2017Model <- markov_model(mcaData,
                             var_path = "path",
                             var_conv = "total_conversions",
                             var_null = "total_null",
                             order = 1,
                             out_more = T)
### --- plot w. {igraph}
abc2017Net <- data.frame(ougoing = abc2017Model$transition_matrix$channel_from,
                         incoming = abc2017Model$transition_matrix$channel_to,
                         stringsAsFactors = F)
abc2017Net$ougoing <- sapply(abc2017Net$ougoing, function(x) {
  ch <- gsub("(start)", "START", fixed = T, x)
  ch <- gsub("(null)", "EXIT", fixed = T, ch)
  ch <- gsub("(conversion)", "EDIT", fixed = T, ch)
  ch
})
abc2017Net$incoming <- sapply(abc2017Net$incoming, function(x) {
  ch <- gsub("(start)", "START", fixed = T, x)
  ch <- gsub("(null)", "EXIT", fixed = T, ch)
  ch <- gsub("(conversion)", "EDIT", fixed = T, ch)
  ch
})
abc2017Net <- graph.data.frame(abc2017Net, 
                               directed = T)
E(abc2017Net)$label <- round(abc2017Model$transition_matrix$transition_probability, 2)
V(abc2017Net)$color <- c('white', 
                         'indianred1', 'indianred2', 'indianred3', 'cadetblue',
                         'red', 'blue', 'yellow', 'orange', 'green',
                         'white', 'white')
V(abc2017Net)$size <- c(20, re4order*40, 20, 20)
V(abc2017Net)$frame.color <- 'white'
# - plot w. {igraph}
coords <- layout_(abc2017Net, as_tree())
par(mai=c(rep(0,4)))
plot(abc2017Net,
     layout = coords,
     edge.width = .75,
     edge.color = "grey",
     edge.arrow.size = 0.35,
     edge.curved = 0.6,
     edge.label.family = "sans",
     edge.label.color = "black",
     edge.label.cex = .6,
     vertex.shape = "circle",
     vertex.label.color = "black",
     vertex.label.font = 1,
     vertex.label.family = "sans",
     vertex.label.cex = .75,
     vertex.label.dist = .25,
     vertex.label.dist = .45,
     rescale = F,
     xlim = c(-1, 1),
     ylim = c(0, 4),
     margin = c(rep(0,4)))

5.3 Campaign Multi-Channel Attribution Model: User Registration

TECHNICAL NOTE: a Markov model of order 4 was used, with 1e8 total simulation runs from the transition matrix.

Removal Effects

### --- Banner -> Exit paths --- ###
### --- Definition: N(Banner Impressions) - N(BannerClicks == Landing Page Views)
# - define: N(Banner Impressions)
bImp <- banImpSet %>% 
  group_by(Banner) %>% 
  summarise(Count = sum(Count))
nBT1 <- bImp$Count[which(bImp$Banner %in% 'BT1')] 
nBT2 <- bImp$Count[which(bImp$Banner %in% 'BT2')]
nBT3 <- bImp$Count[which(bImp$Banner %in% 'BT3')]
nGIB <- bImp$Count[which(bImp$Banner %in% 'GIB_LP')] + bImp$Count[which(bImp$Banner %in% 'GIB_RG')]
# - define: N(BannerClicks/PageViews)
bClick <- clickPlotSet %>% 
  group_by(Source) %>% 
  summarise(Count = sum(Count))
bClick$Source <- gsub("_click", "", bClick$Source, fixed = T)
# - define: N(BannerImpressions) - N(BannerClicks/PageViews)
nBT1 <- nBT1 - bClick$Count[which(bClick$Source %in% 'BT1')] 
nBT2 <- nBT2 - bClick$Count[which(bClick$Source %in% 'BT2')]
nBT3 <- nBT3 - bClick$Count[which(bClick$Source %in% 'BT3')]
nGIB <- nGIB - bClick$Count[which(bClick$Source %in% 'GIB_LP')] + bClick$Count[which(bClick$Source %in% 'GIB_RG')]
### --- Banner -> Landing Page -> Exit paths --- ###
### --- N(Banner Clicks == Landing Page Views) - N(Registration Page Views)
### --- NOTE: TLP == Task Landing Page (JetztMitmachen), GLP == General Landing Page (Mach mit)
nBT1_TLP <- bClick$Count[which(bClick$Source %in% 'BT1')] - 
  pageSource$n[pageSource$Page %in% 'Spezial:Benutzerkonto_anlegen' & pageSource$Source %in% 'JetztMitmachen_BT1']
nBT2_TLP <- bClick$Count[which(bClick$Source %in% 'BT2')] - 
  pageSource$n[pageSource$Page %in% 'Spezial:Benutzerkonto_anlegen' & pageSource$Source %in% 'JetztMitmachen_BT2']
nBT3_TLP <- bClick$Count[which(bClick$Source %in% 'BT3')] - 
  pageSource$n[pageSource$Page %in% 'Spezial:Benutzerkonto_anlegen' & pageSource$Source %in% 'JetztMitmachen_BT3']
nGIB_GLP <- bClick$Count[which(bClick$Source %in% 'GIB_LP')] - 
  pageSource$n[pageSource$Page %in% 'Spezial:Benutzerkonto_anlegen' & pageSource$Source %in% 'Mach_mit']
### --- Banner (-> Landing Page) -> Registration Page -> Exit paths --- ###
### --- N(Registration Page Views) - N(User Registrations)
bUserReg <- userReg %>% 
  group_by(event_campaign) %>% 
  summarise(Count = n())
bUserReg$event_campaign <- toupper(gsub("wmde_abc2017_", "", bUserReg$event_campaign, fixed = T))
colnames(bUserReg)[1] <- 'Banner'
nBT1_TLP_RP <- pageSource$n[pageSource$Page %in% 'Spezial:Benutzerkonto_anlegen' & pageSource$Source %in% 'JetztMitmachen_BT1'] - 
  bUserReg$Count[bUserReg$Banner %in% 'BT1']
nBT2_TLP_RP <- pageSource$n[pageSource$Page %in% 'Spezial:Benutzerkonto_anlegen' & pageSource$Source %in% 'JetztMitmachen_BT2'] - 
  bUserReg$Count[bUserReg$Banner %in% 'BT2']
nBT3_TLP_RP <- pageSource$n[pageSource$Page %in% 'Spezial:Benutzerkonto_anlegen' & pageSource$Source %in% 'JetztMitmachen_BT3'] - 
  bUserReg$Count[bUserReg$Banner %in% 'BT3']
nGIB_GLP_RP <- pageSource$n[pageSource$Page %in% 'Mach_mit' & pageSource$Source %in% 'GIB_LP_click'] - 
  bUserReg$Count[bUserReg$Banner %in% 'GIB_LP']
nGIB_RP <- pageSource$n[pageSource$Page %in% 'Spezial:Benutzerkonto_anlegen' & pageSource$Source %in% 'GIB_RG_click'] - 
  bUserReg$Count[bUserReg$Banner %in% 'GIB_RG']
### --- Banner (-> Landing Page) -> Registration Page -> Registration
nBT1_TLP_RP_Reg <- regData$Registrations[which(regData$Campaign %in% 'BT1')]
nBT2_TLP_RP_Reg <- regData$Registrations[which(regData$Campaign %in% 'BT2')]
nBT3_TLP_RP_Reg <- regData$Registrations[which(regData$Campaign %in% 'BT3')]
nGIB_GLP_RP_Reg <- regData$Registrations[which(regData$Campaign %in% 'GIB_LP')]
nGIB_RP_Reg <- regData$Registrations[which(regData$Campaign %in% 'GIB_RG')]
### --- dataset
mcaData <- data.frame(path = c(deparse(substitute(nBT1)),
                               deparse(substitute(nBT2)),
                               deparse(substitute(nBT3)),
                               deparse(substitute(nGIB)),
                               deparse(substitute(nBT1_TLP)),
                               deparse(substitute(nBT2_TLP)),
                               deparse(substitute(nBT3_TLP)),
                               deparse(substitute(nGIB_GLP)),
                               deparse(substitute(nBT1_TLP_RP)),
                               deparse(substitute(nBT2_TLP_RP)),
                               deparse(substitute(nBT3_TLP_RP)),
                               deparse(substitute(nGIB_GLP_RP)),
                               deparse(substitute(nGIB_RP)),
                               deparse(substitute(nBT1_TLP_RP_Reg)),
                               deparse(substitute(nBT2_TLP_RP_Reg)),
                               deparse(substitute(nBT3_TLP_RP_Reg)),
                               deparse(substitute(nGIB_GLP_RP_Reg)),
                               deparse(substitute(nGIB_RP_Reg))
                               ),
                      total_conversions = c(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
                                            nBT1_TLP_RP_Reg, nBT2_TLP_RP_Reg, nBT1_TLP_RP_Reg, nGIB_GLP_RP_Reg, nGIB_RP_Reg
                                            ),
                      total_null = c(nBT1, nBT2, nBT3, nGIB, nBT1_TLP, nBT2_TLP, nBT3_TLP, nGIB_GLP, nBT1_TLP_RP,
                                     nBT2_TLP_RP, nBT3_TLP_RP, nGIB_GLP_RP, nGIB_RP,
                                     0, 0, 0, 0, 0
                                     ),
                      stringsAsFactors = F)
# - correct paths:
mcaData$path <- gsub("n", "", mcaData$path, fixed = T)
mcaData$path <- gsub("_", " > ", mcaData$path, fixed = T)
editEnds <- which(grepl("Reg", mcaData$path, fixed = T))
for (i in 1:length(editEnds)) {
  wPath <- which(mcaData$path %in% gsub(" > Reg", "", mcaData$path[editEnds[i]], fixed = T))
  wOut <- which(mcaData$path == mcaData$path[editEnds[i]])
  wPath <- setdiff(wPath, wOut)
  mcaData$total_conversions[wPath] <- mcaData$total_conversions[wOut]
}
mcaData <- mcaData[-which(grepl("Reg", mcaData$path, fixed = T)), ]
### --- MCA model
abc2017Model <- markov_model(mcaData,
                             var_path = "path",
                             var_conv = "total_conversions",
                             var_null = "total_null",
                             order = 4,
                             nsim = 1e8,
                             out_more = T)
# - collect removal effects for the next plot:
re4order <- abc2017Model$removal_effects$removal_effects
### --- Removal Effects:
re <- as.data.frame(abc2017Model$removal_effects)
colnames(re) <- c('Channel', 'Removal Effect')
re$Channel <- factor(re$Channel, levels = as.character(abc2017Model$removal_effects$channel_name))
gplot <- ggplot(data = re, 
                aes(x = Channel,
                    y = `Removal Effect`,
                    label = round(`Removal Effect`, 2))
                ) + 
  geom_bar(width = .1, color = "darkblue", fill = "white", stat = "identity") + 
  geom_label(size = 3) + 
  scale_y_continuous(labels = comma) + 
  xlab('Campaign Channel') + ylab('Removal Effect') + 
  ylim(c(0, 1)) + 
  ggtitle('Campaign Multi-Channel Attribution: Removal Effects') + 
  theme_minimal() + 
  theme(plot.title = element_text(size = 10))
Scale for 'y' is already present. Adding another scale for 'y', which will replace the existing scale.
suppressWarnings(print(gplot))

Campaign Transition Graph

Each node in the following graph represents a particular campaign channel. The edges of the graph are labeled by the respective transition probabilities between the channels. The size of the node corresponds to its removal effect. TECHNICAL NOTE: the removal effects are derived from a Markov model of order 4, while the transitional probabilities are derived directly from the 1st order model.

### --- MCA model: 1st order for channel-to-channel transitions
abc2017Model <- markov_model(mcaData,
                             var_path = "path",
                             var_conv = "total_conversions",
                             var_null = "total_null",
                             order = 1,
                             out_more = T)
### --- plot w. {igraph}
abc2017Net <- data.frame(ougoing = abc2017Model$transition_matrix$channel_from,
                         incoming = abc2017Model$transition_matrix$channel_to,
                         stringsAsFactors = F)
abc2017Net$ougoing <- sapply(abc2017Net$ougoing, function(x) {
  ch <- gsub("(start)", "START", fixed = T, x)
  ch <- gsub("(null)", "EXIT", fixed = T, ch)
  ch <- gsub("(conversion)", "REGISTRATION", fixed = T, ch)
  ch
})
abc2017Net$incoming <- sapply(abc2017Net$incoming, function(x) {
  ch <- gsub("(start)", "START", fixed = T, x)
  ch <- gsub("(null)", "EXIT", fixed = T, ch)
  ch <- gsub("(conversion)", "REGISTRATION", fixed = T, ch)
  ch
})
abc2017Net <- graph.data.frame(abc2017Net, 
                               directed = T)
E(abc2017Net)$label <- round(abc2017Model$transition_matrix$transition_probability, 2)
V(abc2017Net)$color <- c('white', 
                         'indianred1', 'indianred2', 'indianred3', 'cadetblue',
                         'red', 'blue', 'yellow', 
                         'white', 'white')
V(abc2017Net)$size <- c(20, re4order*40, 20, 20)
V(abc2017Net)$frame.color <- 'white'
# - plot w. {igraph}
coords <- layout_(abc2017Net, as_tree())
par(mai=c(rep(0,4)))
plot(abc2017Net,
     layout = coords,
     edge.width = .75,
     edge.color = "grey",
     edge.arrow.size = 0.35,
     edge.curved = 0.6,
     edge.label.family = "sans",
     edge.label.color = "black",
     edge.label.cex = .6,
     vertex.shape = "circle",
     vertex.label.color = "black",
     vertex.label.font = 1,
     vertex.label.family = "sans",
     vertex.label.cex = .75,
     vertex.label.dist = .25,
     vertex.label.dist = .45,
     rescale = F,
     xlim = c(-1, 1),
     ylim = c(0, 4),
     margin = c(rep(0,4)))

Summary

The landing page for specific tasks (JetztMitmachen, the TLP channel in the graph) and the GIB campaign are essentially no different in respect to how much they influence user registration. We have learned from the A/B tests that no individual BT (i.e. specific task) banner compares to the performance of GIB_RG which leads directly to the registraion page. However, when considered together, the banners leading to the JetztMitmachen have a performance comparable to GIB_RG. The General Invitation landing page Mach_mit lacks such an effect.

5.3 Campaign Evaluation Summary

ASSUMPTIONS as stated in the Campaign KickOff Presentation:

  • Assumption 1: More users register when given a clear and low level entry task. RESULTS: When comparing individual banner campaigns, A/B testing shows that more users register via the General Invitation Banner campaign, especially when given no intermediate landing page prior to the registration page. However, the JetztMitmachen campagin in general has a performance comparable to the GIB campaign, while the JetztMitmachen page was certainly more important for user registration than the general Mach_mit page - as we have learned from the campaign Multi-Channel Attribution model.

  • Assumption 2: A landing page with more information before registration is necessary. RESULTS: A/B testing shows that more users register via GIB_RG banner campaign that leads directly to the registration page than via the GIB_LP banner campaign that has an intermediate landing page. However, Assumption2 is supported by a high removal effect of the JetztMitmachen page.

  • Assumption 3: A general invitation has a lower conversion rate than specific invitations to register. RESULTS: The total number of registered users via the JetztMitmachen campagin (BT1, BT2, and BT3 banner campaigns taken together) is 535, while the total number of users registered via the General Invitation campaign is 519, an almost 50-50 split.

NOTE: All these assumptions are valid if we consider the criterion of making an edit at all instead.

SUGGESTIONS

  • Suggestion No. 1. Remove the GIB_RG banner campaign from future campaigns. It drives almost 90% of the traffic towards the registration page while being the least efficient in terms of influencing new user edits at the same time (NOTE: least efficient in terms of the expected number of user edits, not in terms of making any edits at all). That would probably mean that dewiki would acquire less new users during the campaign, but again the goal is probably for it to acquire new editors. Or, even better, take a look at my Suggestion No. 2.

  • Suggestion No. 2. Think about the possibility to integrate the campaign content (e.g. what is on the landing pages now) to the registration page directly. Ratio: the GIB_RG banner campaign has no intermediate landing page between banner presentation and registration, leading to the highest number of registered new users; on the other hand, those banner campaigns that instantiate a specific task lead to having more user edits on the average than it (in general; this is not valid for BT2). Maybe integrating the campaign content with the registration page can provide a more powerful combination that would affect positively both registration and future editing.

  • Suggestion No. 3. Remove the Guided Tour from our future campaigns; the analysis of its causal power suggests that it has a negative influence towards making at least one edit on behalf of a newly registered user.

6. Post-Campaign Analytics

This section provides several insights that were sought on the behalf of the campaign management team following the end of the Autumn Banner Campaign 2017.

6. 1 10. October 2017: a change in banners occurs. Did it influence (a) the number of user registrations and (b) the number of user edits?

First, let’s have a look at the total number of user registrations daily:

regPlotSetDaily$Date <- factor(regPlotSetDaily$Date, levels = sort(regPlotSetDaily$Date))
ggplot(regPlotSetDaily, aes(x = Date, y = Registrations)) +
  geom_bar(stat = "identity", 
           position = "dodge", 
           width = .2) +
  scale_y_continuous(labels = comma) +
  ggtitle('Autumn Banner Campaign 2017: Total User Registrations Daily') +
  theme_minimal() + 
  theme(axis.text.x = element_text(angle = 90, size = 8, hjust = 1)) +
  theme(plot.title = element_text(size = 10)) +
  theme(legend.title = element_blank()) +
  theme(panel.grid.major.x = element_blank()) +
  theme(panel.grid.minor.x = element_blank()) +
  theme(panel.background = element_blank())

From the chart only we can see a sharp drop in the number of user registrations following 10/09/2017. The drop is obvious even given that there is a noticeable negative trend since the beginning of the campaign (a rather expected finding). However, let’s perform a simple Chi-Square test to compare the number of user registrations: the first five days of the campaign vs. the last four days (using a 5:4 ratio for the expected distribution split).

populationP <- c(5/9, 4/9)
n <- sum(regPlotSetDaily$Registrations)
expectedCounts <- n*populationP
s <- c(sum(regPlotSetDaily$Registrations[1:5]), sum(regPlotSetDaily$Registrations[6:9]))
print(paste("Expected: ", paste(round(expectedCounts, 2), collapse = ", ")))
[1] "Expected:  585.56, 468.44"
print(paste("Dataset: ", paste(s, collapse = ", ")))
[1] "Dataset:  828, 226"
chiSq <- sum(((s - expectedCounts)^2)/expectedCounts)
print(paste("Chi-Square Statistics:", chiSq, sep = " "))
[1] "Chi-Square Statistics: 225.859772296015"
# - degrees of freedom
df <- 2 - 1 # k == 2 == number of categories
print(paste("D.F.:", df, sep = " "))
[1] "D.F.: 1"
# - Test significance, alpha == .05
sig <- pchisq(chiSq, df, lower.tail=F) # upper tail
print(paste("Type I Error Prob.:", sig, sep = " "))
[1] "Type I Error Prob.: 4.7675165058739e-51"

The change in banners that took place on 10/10/2017 has probably influenced the number of user registrations in a negative way.

Let’s perform the same check for the number of user edits.

editsDaily <- editGTData %>% 
  dplyr::select(edits, `timestamp.x`) %>% 
  group_by(`timestamp.x`) %>% 
  summarise(Edits = sum(edits))
colnames(editsDaily) <- c('Date', 'Edits')
editsDaily$Date <-factor(editsDaily$Date, levels = sort(editsDaily$Date))
ggplot(editsDaily, aes(x = Date, y = Edits)) +
  geom_bar(stat = "identity", 
           position = "dodge", 
           width = .2) +
  scale_y_continuous(labels = comma) +
  ggtitle('Autumn Banner Campaign 2017: Total User Edits Daily') +
  theme_minimal() + 
  theme(axis.text.x = element_text(angle = 90, size = 8, hjust = 1)) +
  theme(plot.title = element_text(size = 10)) +
  theme(legend.title = element_blank()) +
  theme(panel.grid.major.x = element_blank()) +
  theme(panel.grid.minor.x = element_blank()) +
  theme(panel.background = element_blank())

The situation here is more complicated given the (a) presence of the general negative trend since the onset of the campaign and (b) a local increase in the number of user edits after 10/10/2017. The appropriate strategy would probably call for a time-series de-trending first, followed by the analysis of the random component only; however, we have only nine points in the data set.

populationP <- c(5/9, 4/9)
n <- sum(editsDaily$Edits)
expectedCounts <- n*populationP
s <- c(sum(editsDaily$Edits[1:5]), sum(editsDaily$Edits[6:9]))
print(paste("Expected: ", paste(round(expectedCounts, 2), collapse = ", ")))
[1] "Expected:  334.44, 267.56"
print(paste("Dataset: ", paste(s, collapse = ", ")))
[1] "Dataset:  473, 129"
chiSq <- sum(((s - expectedCounts)^2)/expectedCounts)
print(paste("Chi-Square Statistic:", chiSq, sep = " "))
[1] "Chi-Square Statistic: 129.153571428571"
# - degrees of freedom
df <- 2 - 1 # k == 2 == number of categories
print(paste("D.F.:", df, sep = " "))
[1] "D.F.: 1"
# - Test significance, alpha == .05
sig <- pchisq(chiSq, df, lower.tail=F) # upper tail
print(paste("Type I Error Prob.:", sig, sep = " "))
[1] "Type I Error Prob.: 6.27690084044491e-30"

The chi-square test indicates that much less user edits occurring since 10/10/2017. Given the local increase in the number of edits following 10/10/2017, which is probably unusual given the presence of the general negative trend since the onset of the campaign, we cannot rule out the possibility that the banner change on 10/10/2017 has influenced the number of user edits in a positive way.

6. 2 Did the registered users really followed the instructions as provided in the Specific Task Banner Campaigns in their edits?

6. 3 How many reverted edits there were (a) per campaign, and (b) per user?

NOTE: the following Data Acquisition code chunk is not fully reproducible from this Report. The data are collected by running the script abc2017_PROD_RevertedEdits.R on stat1005.eqiad.wmnet, collecting the data as .tsv files, copying manually, and processing locally. Run from stat1005 stat box by executing Rscript /home/goransm/RScripts/abc2017/abc2017_PROD_RevertedEdits.R.

### --- Script: abc2017_PROD_OverallDailyUpdate.R
### --- the following runs on stat1005.eqiad.wmnet
### --- Rscript /home/goransm/RScripts/abc2017/abc2017_PROD_RevertedEdits.R

### --- The script collects and wrangles a dataset for ABC 2017 post-campaign analytics
### --- WMDE Autumn Banner Campaign 2017.

### --- Goran S. Milovanovic, Data Scientist, WMDE
### --- November 06, 2017.

### -----------------------------------------------------------------------------
### 0. Setup
### -----------------------------------------------------------------------------
rm(list = ls())
library(dplyr)

# - get user registration data: abc2017_userRegistrations.tsv
# - then get user IDs from registered:
setwd('/home/goransm/_miscWMDE/abc2017_DataOUT/abc2017_OfficialDatasets/abc2017_DailyUpdate/')
lF <- list.files()
lF <- lF[grepl('userRegistrations', lF, fixed = T)]
userReg <- read.table(lF, 
                      quote = "",
                      sep = "\t",
                      header = T,
                      check.names = F,
                      stringsAsFactors = F)
userReg <- userReg %>% 
  dplyr::select(event_userId, event_isSelfMade) %>% 
  filter(event_isSelfMade == 1)
# - uids:
uid <- userReg$event_userId
# - sql query
sqlQuery <- paste('SELECT rev_user, rev_id, rev_page, rev_timestamp, rev_sha1, rev_content_model, rev_content_format FROM revision WHERE rev_user IN (',
                  paste(uid, collapse = ", "),
                  ') AND (rev_timestamp >= 20171004220000) AND (rev_timestamp <= 20171014220000);',
                  sep = "")
mySqlCommand <- paste('mysql -h analytics-store.eqiad.wmnet dewiki -e ',
                      paste('"', sqlQuery, '" > ', sep = ""),
                      '/home/goransm/_miscWMDE/abc2017_DataOUT/abc2017_OfficialDatasets/abc2017_DailyUpdate/abc2017_completeUserRevisions.tsv', sep = "")
system(command = mySqlCommand, 
       wait = TRUE)

Analyse reverted edits locally:

userRevisions <- read.table('./_dailyUpdateDATA/abc2017_completeUserRevisions.tsv',
                            quote = "",
                            sep = "\t",
                            header = T,
                            check.names = F,
                            stringsAsFactors = F)
userRevisions <- left_join(userRevisions, 
                           userReg, 
                           by = c("rev_user" = "event_userId"))
userRevisions <- userRevisions %>% 
  filter(!is.na(event_campaign))
# - keep only those users who made any edits at all:
userRevisions <- userRevisions %>% 
  filter(rev_user %in% editData$rev_user)
# - Note: UTC times, conversion to CET is not necessary here
userRevisions$rev_timestamp <- as.character(userRevisions$rev_timestamp)
revertsPerUser <- lapply(unique(userRevisions$rev_id), function(x) {
  dataset <- dplyr::arrange(userRevisions[userRevisions$rev_user == x, ], rev_timestamp)
  return(data.frame(userId = x, 
                    revCount = sum(table(dataset$rev_sha1) - 1), 
                    stringsAsFactors = F))
})
revertsPerUser <- rbindlist(revertsPerUser)
sum(revertsPerUser$revCount)
[1] 0

In conclusion, no edits were reverted.

LS0tCnRpdGxlOiAnQXV0dW1uIEJhbm5lciBDYW1wYWlnbiAyMDE3OiBSZXBvcnQnCmF1dGhvcjogIkdvcmFuIFMuIE1pbG92YW5vdmljLCBEYXRhIEFuYWx5c3QsIFdNREUiCmRhdGU6ICJPY3RvYmVyLCAyMDE3IgpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazoKICAgIGNvZGVfZm9sZGluZzogaGlkZQogICAgdGhlbWU6IHNpbXBsZXgKICAgIHRvYzogeWVzCiAgICB0b2NfZmxvYXQ6IHllcwogICAgdG9jX2RlcHRoOiA1CiAgaHRtbF9kb2N1bWVudDoKICAgIHRvYzogeWVzCiAgICB0b2NfZGVwdGg6IDUKLS0tCgoKKipGZWVkYmFjayoqIHNob3VsZCBiZSBzZW5kIHRvIGBnb3Jhbi5taWxvdmFub3ZpY19leHRAd2lraW1lZGlhLmRlYC4gCgpUaGUgY2FtcGFpZ24gaXMgcnVuIGZyb20gMjAxNy8xMC8wNSB0byAyMDE3LzEwLzEzLgoKKipDVVJSRU5UIFVQREFURToqKiBDb21wbGV0ZSBkYXRhc2V0LCBjb2xsZWN0ZWQgb24gMjAxNy8xMC8xNC4KCmBgYHtyLCBlY2hvID0gRiwgd2FybmluZyA9IEYsIG1lc3NhZ2UgPSBGLCByZXN1bHRzID0gJ2hpZGUnfQojICFkaWFnbm9zdGljcyBvZmYKIyMjIC0tLSBTZXR1cAprbml0cjo6b3B0c19jaHVuayRzZXQoZmlnLndpZHRoID0gMTUsIGZpZy5oZWlnaHQgPSA4KSAKcm0obGlzdCA9IGxzKCkpCmxpYnJhcnkoc3RyaW5ncikKbGlicmFyeShkcGx5cikKbGlicmFyeSh0aWR5cikKbGlicmFyeShkYXRhLnRhYmxlKQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkoZ2dyZXBlbCkKbGlicmFyeShzY2FsZXMpCmxpYnJhcnkoUkNvbG9yQnJld2VyKQpsaWJyYXJ5KGthYmxlRXh0cmEpCmxpYnJhcnkocm1hcmtkb3duKQpsaWJyYXJ5KGtuaXRyKQpsaWJyYXJ5KERUKQpsaWJyYXJ5KHJlc2hhcGUyKQpsaWJyYXJ5KENoYW5uZWxBdHRyaWJ1dGlvbikKbGlicmFyeShpZ3JhcGgpCmxpYnJhcnkoem9vKQpsaWJyYXJ5KENhdXNhbEltcGFjdCkKYGBgCgojIyAwLiBQcmVsaW1pbmFyaWVzCgojIyMgMC4gMSBEYXRhIEFjcXVpc2l0b24KCioqTk9URToqKiB0aGUgRGF0YSBBY3F1aXNpdGlvbiBjb2RlIGNodW5rIGlzIG5vdCBmdWxseSByZXByb2R1Y2libGUgZnJvbSB0aGlzIFJlcG9ydC4gVGhlIGRhdGEgYXJlIGNvbGxlY3RlZCBieSBydW5uaW5nIHRoZSBzY3JpcHQgYGFiYzIwMTdfUFJPRF9PdmVyYWxsRGFpbHlVcGRhdGUuUmAgb24gc3RhdDEwMDUuZXFpYWQud21uZXQsIGNvbGxlY3RpbmcgdGhlIGRhdGEgYXMgYC50c3ZgIGFuZCBgLmNzdmAgZmlsZXMsIGNvcHlpbmcgbWFudWFsbHksIGFuZCBwcm9jZXNzaW5nIGxvY2FsbHkuIFJ1biBmcm9tIHN0YXQxMDA1IHN0YXQgYm94IGJ5IGV4ZWN1dGluZyBgUnNjcmlwdCAvaG9tZS9nb3JhbnNtL1JTY3JpcHRzL2FiYzIwMTcvYWJjMjAxN19QUk9EX092ZXJhbGxEYWlseVVwZGF0ZS5SYC4KCmBgYHtyLCBlY2hvID0gVCwgZXZhbCA9IEZ9CiMjIyAtLS0gU2NyaXB0OiBhYmMyMDE3X1BST0RfT3ZlcmFsbERhaWx5VXBkYXRlLlIKIyMjIC0tLSB0aGUgZm9sbG93aW5nIHJ1bnMgb24gc3RhdDEwMDUuZXFpYWQud21uZXQKIyMjIC0tLSBSc2NyaXB0IC9ob21lL2dvcmFuc20vUlNjcmlwdHMvYWJjMjAxNy9hYmMyMDE3X1BST0RfT3ZlcmFsbERhaWx5VXBkYXRlLlIKCiMjIyAtLS0gVGhlIHNjcmlwdCBjb2xsZWN0cyBhbmQgd3JhbmdsZXMgYWxsIGRhdGFzZXRzCiMjIyAtLS0gZm9yIHRoZSBXTURFIEF1dHVtbiBCYW5uZXIgQ2FtcGFpZ24gMjAxNy4KCiMjIyAtLS0gR29yYW4gUy4gTWlsb3Zhbm92aWMsIERhdGEgQW5hbHlzdCwgV01ERQojIyMgLS0tIFNlcHRlbWJlciAyNiwgMjAxNy4KCiMjIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQojIyMgMC4gU2V0dXAKIyMjIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCgpybShsaXN0ID0gbHMoKSkKbGlicmFyeShkcGx5cikKbGlicmFyeSh0aWR5cikKbGlicmFyeShzdHJpbmdyKQpsaWJyYXJ5KGRhdGEudGFibGUpCnN0YXJ0RGF0ZSA8LSAnMjAxNy0xMC0wNScKZW5kRGF0ZSA8LSAnMjAxNy0xMC0xNCcKYmFubmVySW1wcmVzc2lvbnNEaXIgPC0gJy9ob21lL2dvcmFuc20vX21pc2NXTURFL2FiYzIwMTdfRGF0YU9VVC9hYmMyMDE3X09mZmljaWFsRGF0YXNldHMvYWJjMjAxN0Jhbm5lckltcHJlc3Npb25zLycKYmFubmVyQ2xpY2tzRGlyIDwtICcvaG9tZS9nb3JhbnNtL19taXNjV01ERS9hYmMyMDE3X0RhdGFPVVQvYWJjMjAxN19PZmZpY2lhbERhdGFzZXRzL2FiYzIwMTdCYW5uZXJDbGlja3NMYW5kaW5nUGFnZXMvJwpkYWlseVVwZGF0ZURpciA8LSAnL2hvbWUvZ29yYW5zbS9fbWlzY1dNREUvYWJjMjAxN19EYXRhT1VUL2FiYzIwMTdfT2ZmaWNpYWxEYXRhc2V0cy9hYmMyMDE3X0RhaWx5VXBkYXRlLycgCgojIyMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KIyMjIDEuIEJhbm5lciBJbXByZXNzaW9ucwojIyMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KCiMjIyAtLS0gQ2FtcGFpZ24gQmFubmVyIFRhZ3M6CiMgLSAoMSkgP2NhbXBhaWduPXdtZGVfYWJjMjAxN19idDEgLSBiYW5uZXIgZm9yIFNwZWNpZmljIFRhc2sgMTsKIyAtICgyKSA/Y2FtcGFpZ249d21kZV9hYmMyMDE3X2J0MiAtIGJhbm5lciBmb3IgU3BlY2lmaWMgVGFzayAyOwojIC0gKDMpID9jYW1wYWlnbj13bWRlX2FiYzIwMTdfYnQzIC0gYmFubmVyIGZvciBTcGVjaWZpYyBUYXNrIDM7CiMgLSAoNCkgP2NhbXBhaWduPXdtZGVfYWJjMjAxN19naWJfbHAgLSBiYW5uZXIgZm9yIHRoZSBHZW5lcmFsIEludml0YXRpb24KIyAtIHdoaWNoIGxlYWRzIHRvIHRoZSBMYW5kaW5nIFBhZ2UgdXBvbiBjbGljazsKIyAtICg1KSA/Y2FtcGFpZ249d21kZV9hYmMyMDE3X2dpYl9yZyAtIGJhbm5lciBmb3IgdGhlIEdlbmVyYWwgSW52aXRhdGlvbgojIHdoaWNoIGxlYWRzIGRpcmVjdGx5IHRvIFJlZ2lzdHJhdGlvbiB1cG9uIGNsaWNrLgoKIyMjIC0tLSBIaXZlUUwgZm9yIGV2ZXJ5dGhpbmcgZnJvbQojIyMgLS0tIHVyaV9ob3N0ID0gJ2RlLndpa2lwZWRpYS5vcmcnIGFuZAojIyMgLS0tIHVyaV9wYXRoID0gJy9iZWFjb24vaW1wcmVzc2lvbicKIyMjIC0tLSBhbmQgdGhlbiBsb29rIHVwIHRoZSBkZXNpcmVkIHRhZ3MuCgojIyMgLS0tIGxvb3Agb3ZlciBkYXRlIHJhbmdlLCBjcmVhdGUgcXVlcnksIGZldGNoLCBhbmQgc3RvcmUKCmRhdGVSYW5nZSA8LSBzZXEuUE9TSVh0KGZyb20gPSBhcy5QT1NJWGx0KHN0YXJ0RGF0ZSwgdHogPSAiQ0VUIiksCiAgICAgICAgICAgICAgICAgICAgICAgIHRvID0gYXMuUE9TSVhsdChlbmREYXRlLCB0eiA9ICJDRVQiKSwKICAgICAgICAgICAgICAgICAgICAgICAgYnkgPSAnaG91cicpCmRhdGVSYW5nZSA8LSBkYXRlUmFuZ2VbLWxlbmd0aChkYXRlUmFuZ2UpXQpjZXREYXRlUmFuZ2UgPC0gYXMuY2hhcmFjdGVyKGRhdGVSYW5nZSkKY2V0RGF0ZVJhbmdlIDwtIHNhcHBseShjZXREYXRlUmFuZ2UsIGZ1bmN0aW9uKHgpIHsKICBzdHJzcGxpdCh4LCBzcGxpdCA9ICIgIiwgZml4ZWQgPSBUKVtbMV1dWzFdCn0pCm5hbWVzKGRhdGVSYW5nZSkgPC0gY2V0RGF0ZVJhbmdlCmRhdGVSYW5nZSA8LSBhcy5QT1NJWGx0KGRhdGVSYW5nZSwgdHogPSAiVVRDIikKIyAtIHVwIHRvIHRvZGF5Ogp0b2RheSA8LSBhcy5QT1NJWGx0KFN5cy50aW1lKCksIHR6ID0gIlVUQyIpCncgPC0gd2hpY2goZGF0ZVJhbmdlID4gdG9kYXkpCmlmIChsZW5ndGgodykgPiAwKSB7CiAgZGF0ZVJhbmdlIDwtIGRhdGVSYW5nZVstd10KfQpkUiA8LSBsaXN0KCkKZm9yIChpIGluIDE6bGVuZ3RoKGRhdGVSYW5nZSkpIHsKICBkUltbaV1dIDwtIGRhdGEuZnJhbWUoCiAgICBjZXROYW1lID0gbmFtZXMoZGF0ZVJhbmdlW2ldKSwKICAgIHV0Y1llYXIgPSB5ZWFyKGRhdGVSYW5nZVtpXSksCiAgICB1dGNNb250aCA9IG1vbnRoKGRhdGVSYW5nZVtpXSksCiAgICB1dGNEYXkgPSBtZGF5KGRhdGVSYW5nZVtpXSksCiAgICB1dGNIb3VyID0gaG91cihkYXRlUmFuZ2VbaV0pCiAgKQp9CmRSIDwtIHJiaW5kbGlzdChkUikKZFIgPC0gZFIgJT4lCiAgZ3JvdXBfYnkoY2V0TmFtZSwgdXRjWWVhciwgdXRjTW9udGgsIHV0Y0RheSkgJT4lCiAgc3VtbWFyaXNlKHV0Y0hvdXIgPSBwYXN0ZSgiaG91cj0iLCB1dGNIb3VyLCBjb2xsYXBzZSA9ICIgT1IgIiwgc2VwID0gIiIpKQoKIyAtIHNldCBvdXREaXIKb3V0RGlyIDwtIGJhbm5lckltcHJlc3Npb25zRGlyCnNldHdkKG91dERpcikKIyAtIHNldCBIaXZlUUwgcXVlcnkgZGlyOgpmb3IgKGkgaW4gMTpsZW5ndGgodW5pcXVlKGRSJGNldE5hbWUpKSkgewoKICB3Q2V0TmFtZSA8LSB3aGljaChkUiRjZXROYW1lICVpbiUgdW5pcXVlKGRSJGNldE5hbWUpW2ldKQoKICBmb3IgKGogaW4gMTpsZW5ndGgod0NldE5hbWUpKSB7CgogICAgIyAtIGNvbnN0cnVjdCBIaXZlUUwgcXVlcnk6CiAgICB5IDwtIGRSJHV0Y1llYXJbd0NldE5hbWVbal1dCiAgICBtIDwtIGRSJHV0Y01vbnRoW3dDZXROYW1lW2pdXQogICAgZCA8LSBkUiR1dGNEYXlbd0NldE5hbWVbal1dCiAgICBob3VyIDwtIGRSJHV0Y0hvdXJbd0NldE5hbWVbal1dCiAgICBxIDwtIHBhc3RlKAogICAgICAiVVNFIHdtZjsKICAgICAgU0VMRUNUIHVyaV9xdWVyeSBGUk9NIHdlYnJlcXVlc3QKICAgICAgV0hFUkUgdXJpX2hvc3QgPSAnZGUud2lraXBlZGlhLm9yZycKICAgICAgQU5EIHVyaV9wYXRoID0gJy9iZWFjb24vaW1wcmVzc2lvbicKICAgICAgQU5EIHllYXIgPSAiLCB5LAogICAgICAiIEFORCBtb250aCA9ICIsIG0sCiAgICAgICIgQU5EIGRheSA9ICIsIGQsCiAgICAgICIgQU5EICgiLCBob3VyLCAiKTsiLAogICAgICBzZXAgPSAiIikKICAgICMgLSB3cml0ZSBocWwKICAgIHdyaXRlKHEsICdhYmMyMDE3X0Jhbm5lckltcHJlc3Npb25zLmhxbCcpCiAgICAjIC0gcHJlcGFyZSBvdXRwdXQgZmlsZToKICAgIGZpbGVOYW1lIDwtICJhYmMyMDE3X0Jhbm5lckltcHJlc3Npb25zXyIKICAgIGZpbGVOYW1lIDwtIHBhc3RlMChmaWxlTmFtZSwKICAgICAgICAgICAgICAgICAgICAgICBhcy5jaGFyYWN0ZXIodW5pcXVlKGRSJGNldE5hbWUpW2ldKSwKICAgICAgICAgICAgICAgICAgICAgICAiXyIsIGosCiAgICAgICAgICAgICAgICAgICAgICAgIi50c3YiKQogICAgZmlsZU5hbWUgPC0gcGFzdGUwKG91dERpciwgZmlsZU5hbWUpCiAgICAjIC0gZXhlY3V0ZSBocWwgc2NyaXB0OgogICAgaGl2ZUFyZ3MgPC0KICAgICAgJ2JlZWxpbmUgLWYnCiAgICBoaXZlSW5wdXQgPC0gcGFzdGUwKCdhYmMyMDE3X0Jhbm5lckltcHJlc3Npb25zLmhxbCA+ICcsCiAgICAgICAgICAgICAgICAgICAgICAgIGZpbGVOYW1lKQogICAgIyAtIGNvbW1hbmQ6CiAgICBoaXZlQ29tbWFuZCA8LSBwYXN0ZShoaXZlQXJncywgaGl2ZUlucHV0KQogICAgc3lzdGVtKGNvbW1hbmQgPSBoaXZlQ29tbWFuZCwgd2FpdCA9IFRSVUUpCgogIH0KCn0KCiMjIyAtLS0gd3JhbmdsZSB0aGlzIGRhdGFTZXQKbEYgPC0gbGlzdC5maWxlcygpCmxGIDwtIGxGW2dyZXBsKCIudHN2IiwgbEYsIGZpeGVkID0gVCldCmxGIDwtIGxGW2dyZXBsKCJJbXByZXNzaW9ucyIsIGxGLCBmaXhlZCA9IFQpXQojIyMgLS0tIGxvYWQgRGF0YXNldDoKIyAtIGNvdW50IG5vbi1lbXB0eSBmaWxlczoKYyA8LSAwCmRhdGFTZXQgPC0gbGlzdCgpCmZvciAoaSBpbiAxOmxlbmd0aChsRikpIHsKICBkUyA8LSByZWFkTGluZXMobEZbaV0sIG4gPSAtMSkKICBkUyA8LSBkU1s4OihsZW5ndGgoZFMpIC0gMSldCiAgaWYgKGxlbmd0aChkUykgPiAwKSB7CiAgICBjIDwtIGMgKyAxCiAgICBkUyA8LSBkYXRhLmZyYW1lKHF1ZXJ5ID0gZFMsCiAgICAgICAgICAgICAgICAgICAgIGRhdGUgPSBzdHJzcGxpdChsRltpXSwgc3BsaXQgPSAiXyIsIGZpeGVkID0gVClbWzFdXVszXSwKICAgICAgICAgICAgICAgICAgICAgc3RyaW5nc0FzRmFjdG9ycyA9IEYpCiAgICBkYXRhU2V0W1tjXV0gPC0gZFMKICAgIHJtKGRTKTsgZ2MoKQogIH0KfQpkYXRhU2V0IDwtIHJiaW5kbGlzdChkYXRhU2V0KQpkYXRhU2V0IDwtIGZpbHRlcihkYXRhU2V0LAogICAgICAgICAgICAgICAgICBncmVwbCgiV01ERV9lZGl0b3JfY2FtcGFpZ25fYXV0dW1uMTciLAogICAgICAgICAgICAgICAgICAgICAgICBxdWVyeSkKKQoKIyAtIHByb2R1Y2UgYW5hbHl0aWNzIGRhdGFzZXQKYmFubmVyIDwtIHN0cl9leHRyYWN0KGRhdGFTZXQkcXVlcnksICJiYW5uZXI9KF98W1s6YWxudW06XV0pKyYiKQpiYW5uZXIgPC0gZ3N1YigiYmFubmVyPSIsICIiLCBiYW5uZXIsIGZpeGVkID0gVCkKYmFubmVyIDwtIGdzdWIoIiYiLCAiIiwgYmFubmVyLCBmaXhlZCA9IFQpCmltcHJlc3Npb25SYXRlIDwtIHN0cl9leHRyYWN0KGRhdGFTZXQkcXVlcnksICJyZWNvcmRJbXByZXNzaW9uU2FtcGxlUmF0ZT0oW1s6ZGlnaXQ6XV18XFwuKSsmIikKaW1wcmVzc2lvblJhdGUgPC0gZ3N1YigicmVjb3JkSW1wcmVzc2lvblNhbXBsZVJhdGU9IiwgIiIsIGltcHJlc3Npb25SYXRlLCBmaXhlZCA9IFQpCmltcHJlc3Npb25SYXRlIDwtIGdzdWIoIiYiLCAiIiwgaW1wcmVzc2lvblJhdGUsIGZpeGVkID0gVCkKaW1wcmVzc2lvblJhdGUgPC0gYXMubnVtZXJpYyhpbXByZXNzaW9uUmF0ZSkKc3RhdHVzIDwtIHN0cl9leHRyYWN0KGRhdGFTZXQkcXVlcnksICJzdGF0dXM9KFtbOmFsbnVtOl1dfFtbOnB1bmN0Ol1dKSsmIikKc3RhdHVzIDwtIGdzdWIoInN0YXR1cz0iLCAiIiwgc3RhdHVzKQpzdGF0dXMgPC0gZ3N1YigiJiIsICIiLCBzdGF0dXMpCnN0YXR1c0NvZGUgPC0gc3RyX2V4dHJhY3QoZGF0YVNldCRxdWVyeSwgInN0YXR1c0NvZGU9W1s6ZGlnaXQ6XV0mIikKc3RhdHVzQ29kZSA8LSBnc3ViKCJzdGF0dXNDb2RlPSIsICIiLCBzdGF0dXNDb2RlKQpzdGF0dXNDb2RlIDwtIGdzdWIoIiYiLCAiIiwgc3RhdHVzQ29kZSkKY2FtcGFpZ25DYXRlZ29yeSA8LSBzdHJfZXh0cmFjdChkYXRhU2V0JHF1ZXJ5LCAiY2FtcGFpZ25DYXRlZ29yeT1bWzphbG51bTpdXSsmIikKY2FtcGFpZ25DYXRlZ29yeSA8LSBnc3ViKCJjYW1wYWlnbkNhdGVnb3J5PSIsICIiLCBjYW1wYWlnbkNhdGVnb3J5KQpjYW1wYWlnbkNhdGVnb3J5IDwtIGdzdWIoIiYiLCAiIiwgY2FtcGFpZ25DYXRlZ29yeSkKcmVzdWx0IDwtIHN0cl9leHRyYWN0KGRhdGFTZXQkcXVlcnksICJyZXN1bHQ9W1s6YWxudW06XV0rIikKcmVzdWx0IDwtIGdzdWIoInJlc3VsdD0iLCAiIiwgcmVzdWx0KQpyZXN1bHQgPC0gZ3N1YigiJiIsICIiLCByZXN1bHQpCnFkYXRlIDwtIGRhdGFTZXQkZGF0ZQojIC0gYXMuZGF0YS5mcmFtZSgpCmRhdGFTZXQgPC0gZGF0YS5mcmFtZShiYW5uZXIgPSBiYW5uZXIsCiAgICAgICAgICAgICAgICAgICAgICBpbXByZXNzaW9uUmF0ZSA9IGltcHJlc3Npb25SYXRlLAogICAgICAgICAgICAgICAgICAgICAgc3RhdHVzID0gc3RhdHVzLAogICAgICAgICAgICAgICAgICAgICAgc3RhdHVzQ29kZSA9IHN0YXR1c0NvZGUsCiAgICAgICAgICAgICAgICAgICAgICBjYW1wYWlnbkNhdGVnb3J5ID0gY2FtcGFpZ25DYXRlZ29yeSwKICAgICAgICAgICAgICAgICAgICAgIHJlc3VsdCA9IHJlc3VsdCwKICAgICAgICAgICAgICAgICAgICAgIGRhdGUgPSBxZGF0ZSwKICAgICAgICAgICAgICAgICAgICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGKQoKIyAtIHN0b3JlIGFuYWx5dGljcyBkYXRhc2V0OgpzZXR3ZChkYWlseVVwZGF0ZURpcikKZGF0YVNldCA8LSBkYXRhU2V0WyFpcy5uYShkYXRhU2V0JGJhbm5lciksIF0Kd3JpdGUuY3N2KGRhdGFTZXQsICdhYmNfQmFubmVySW1wcmVzc2lvbnNfdXBkYXRlLmNzdicpCgojIyMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KIyMjIDIuIEJhbm5lciBDbGlja3MgYW5kIExhbmRpbmcgUGFnZSBWaWV3cwojIyMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KCiMjIyAtLS0gTGFuZGluZy9SZWdpc3RyYXRpb24gcGFnZXM6CiMgLSBMYW5kaW5nIFBhZ2UsIFNwZWNpZmljIFRhc2tzLCBCYW5uZXJzIGJ0MSwgYnQyLCBidDMKIyAtIGh0dHBzOi8vZGUud2lraXBlZGlhLm9yZy93aWtpL1dpa2lwZWRpYTpXaWtpbWVkaWFfRGV1dHNjaGxhbmQvSmV0enRNaXRtYWNoZW4KIyAtIFNwZWNpZmljIGJ0IGJhbm5lciBhbmNob3JzOgojIC0gIGJ0MSAtICNCZWJpbGRlcm4sIGJ0MiAtIEFrdHVhbGlzaWVyZW4sIGJ0MyAtICNCZWxlZ2VuCiMgLSBMYW5kaW5nIFBhZ2UsIEdlbmVyYWwsIEJhbm5lciBnaWJfbHAKIyAtIGh0dHBzOi8vZGUud2lraXBlZGlhLm9yZy93aWtpL1dpa2lwZWRpYTpXaWtpbWVkaWFfRGV1dHNjaGxhbmQvTWFjaF9taXQKIyAtIFJlZ2lzdHJhdGlvbiBQYWdlLCBiYW5uZXIgZ2liX3JnCiMgLSBodHRwczovL2RlLndpa2lwZWRpYS5vcmcvd2lraS9TcGV6aWFsOkJlbnV0emVya29udG9fYW5sZWdlbgoKIyAtIHNldCBvdXREaXIKb3V0RGlyIDwtIGJhbm5lckNsaWNrc0RpcgpzZXR3ZChvdXREaXIpCgpmb3IgKGkgaW4gMTpsZW5ndGgodW5pcXVlKGRSJGNldE5hbWUpKSkgewoKICB3Q2V0TmFtZSA8LSB3aGljaChkUiRjZXROYW1lICVpbiUgdW5pcXVlKGRSJGNldE5hbWUpW2ldKQoKICBmb3IgKGogaW4gMTpsZW5ndGgod0NldE5hbWUpKSB7CgogICAgIyAtIGNvbnN0cnVjdCBIaXZlUUwgcXVlcnk6CiAgICB5IDwtIGRSJHV0Y1llYXJbd0NldE5hbWVbal1dCiAgICBtIDwtIGRSJHV0Y01vbnRoW3dDZXROYW1lW2pdXQogICAgZCA8LSBkUiR1dGNEYXlbd0NldE5hbWVbal1dCiAgICBob3VyIDwtIGRSJHV0Y0hvdXJbd0NldE5hbWVbal1dCiAgICBxIDwtIHBhc3RlKAogICAgICAiVVNFIHdtZjsKICAgICAgU0VMRUNUIHVyaV9wYXRoLCB1cmlfcXVlcnksIHJlZmVyZXIgRlJPTSB3ZWJyZXF1ZXN0CiAgICAgIFdIRVJFIHVyaV9ob3N0ID0gJ2RlLndpa2lwZWRpYS5vcmcnCiAgICAgIEFORCAodXJpX3BhdGggPSAnL3dpa2kvV2lraXBlZGlhOldpa2ltZWRpYV9EZXV0c2NobGFuZC9KZXR6dE1pdG1hY2hlbicgT1IgdXJpX3BhdGggPSAnL3dpa2kvV2lraXBlZGlhOldpa2ltZWRpYV9EZXV0c2NobGFuZC9NYWNoX21pdCcgT1IgdXJpX3BhdGggPSAnL3dpa2kvU3BlemlhbDpCZW51dHplcmtvbnRvX2FubGVnZW4nKQogICAgICBBTkQgeWVhciA9ICIsIHksCiAgICAgICIgQU5EIG1vbnRoID0gIiwgbSwKICAgICAgIiBBTkQgZGF5ID0gIiwgZCwKICAgICAgIiBBTkQgKCIsIGhvdXIsICIpOyIsCiAgICAgIHNlcCA9ICIiKQogICAgIyAtIHdyaXRlIGhxbAogICAgd3JpdGUocSwgJ2FiYzIwMTdfQmFubmVyQ2xpY2tzLmhxbCcpCiAgICAjIC0gcHJlcGFyZSBvdXRwdXQgZmlsZToKICAgIGZpbGVOYW1lIDwtICJhYmMyMDE3X0Jhbm5lckNsaWNrc18iCiAgICBmaWxlTmFtZSA8LSBwYXN0ZTAoZmlsZU5hbWUsCiAgICAgICAgICAgICAgICAgICAgICAgYXMuY2hhcmFjdGVyKHVuaXF1ZShkUiRjZXROYW1lKVtpXSksCiAgICAgICAgICAgICAgICAgICAgICAgIl8iLCBqLAogICAgICAgICAgICAgICAgICAgICAgICIudHN2IikKICAgIGZpbGVOYW1lIDwtIHBhc3RlMChvdXREaXIsIGZpbGVOYW1lKQogICAgIyAtIGV4ZWN1dGUgaHFsIHNjcmlwdDoKICAgIGhpdmVBcmdzIDwtCiAgICAgICdiZWVsaW5lIC1mJwogICAgaGl2ZUlucHV0IDwtIHBhc3RlMCgnYWJjMjAxN19CYW5uZXJDbGlja3MuaHFsID4gJywKICAgICAgICAgICAgICAgICAgICAgICAgZmlsZU5hbWUpCiAgICAjIC0gY29tbWFuZDoKICAgIGhpdmVDb21tYW5kIDwtIHBhc3RlKGhpdmVBcmdzLCBoaXZlSW5wdXQpCiAgICBzeXN0ZW0oY29tbWFuZCA9IGhpdmVDb21tYW5kLCB3YWl0ID0gVFJVRSkKCiAgfQoKfQoKIyMjIC0tLSBXcmFuZ2xlIHRoaXMgZGF0YXNldDoKCiMjIyAtLS0gTGFuZGluZyBwYWdlczoKc3BlY1Rhc2tQYWdlIDwtICcvd2lraS9XaWtpcGVkaWE6V2lraW1lZGlhX0RldXRzY2hsYW5kL0pldHp0TWl0bWFjaGVuJwpnZW5JbnZQYWdlIDwtICcvd2lraS9XaWtpcGVkaWE6V2lraW1lZGlhX0RldXRzY2hsYW5kL01hY2hfbWl0JwpyZWdQYWdlIDwtICcvd2lraS9TcGV6aWFsOkJlbnV0emVya29udG9fYW5sZWdlbicKCiMjIyAtLS0gQmFubmVyIHRhZ3M6CnNwZWNUYXNrQmFubmVyMSA8LSAnP2NhbXBhaWduPXdtZGVfYWJjMjAxN19idDEnCnNwZWNUYXNrQmFubmVyMiA8LSAnP2NhbXBhaWduPXdtZGVfYWJjMjAxN19idDInCnNwZWNUYXNrQmFubmVyMyA8LSAnP2NhbXBhaWduPXdtZGVfYWJjMjAxN19idDMnCmdlbkludlBhZ2VfcmcgPC0gJz9jYW1wYWlnbj13bWRlX2FiYzIwMTdfZ2liX3JnJwpnZW5JbnZQYWdlX2xwIDwtICc/Y2FtcGFpZ249d21kZV9hYmMyMDE3X2dpYl9scCcKCiMjIyAtLS0gRGF0YXNldDoKIyAtIGNvdW50IG5vbi1lbXB0eSBmaWxlczoKYyA8LSAwCmxGIDwtIGxpc3QuZmlsZXMoKQpsRiA8LSBsRltncmVwbCgnLnRzdicsIGxGLCBmaXhlZCA9IFQpXQpsRiA8LSBsRltncmVwbCgnQ2xpY2tzJywgbEYsIGZpeGVkID0gVCldCmRhdGFTZXQgPC0gbGlzdCgpCmZvciAoaSBpbiAxOmxlbmd0aChsRikpIHsKICBkUyA8LSByZWFkTGluZXMobEZbaV0sIG4gPSAtMSkKICBkUyA8LSBkU1s4OihsZW5ndGgoZFMpIC0gMildCiAgaWYgKGxlbmd0aChkUyA+IDApKSB7CiAgICBjIDwtIGMgKyAxCiAgICBkUyA8LSBsYXBwbHkoZFMsIGZ1bmN0aW9uKHgpIHsKICAgICAgZGF0IDwtIHN0cnNwbGl0KHgsIHNwbGl0ID0gIlx0IiwgZml4ZWQgPSBUKVtbMV1dCiAgICAgIGRhdGEuZnJhbWUocGFnZSA9IGRhdFsxXSwgYmFubmVyID0gZGF0WzJdLCByZWZlciA9IGRhdFszXSwgc3RyaW5nc0FzRmFjdG9ycyA9IEYpCiAgICB9KQogIH0KICBkUyA8LSByYmluZGxpc3QoZFMpCiAgZFMkZGF0ZSA8LSBzdHJzcGxpdChsRltpXSwgc3BsaXQgPSAiXyIsIGZpeGVkID0gVClbWzFdXVszXQogIGRhdGFTZXRbW2NdXSA8LSBkUwogIHJtKGRTKTsgZ2MoKQp9CmRhdGFTZXQgPC0gcmJpbmRsaXN0KGRhdGFTZXQpCgojIC0gcmVwbGFjZSB2YWx1ZXM6CmRhdGFTZXQkcGFnZSA8LSBzYXBwbHkoZGF0YVNldCRwYWdlLCBmdW5jdGlvbih4KSB7CiAgc3Ryc3BsaXQoeCwgc3BsaXQgPSAiLyIsIGZpeGVkID0gVClbWzFdXVtsZW5ndGgoc3Ryc3BsaXQoeCwgc3BsaXQgPSAiLyIsIGZpeGVkID0gVClbWzFdXSldCn0pCmRhdGFTZXQkYmFubmVyW3doaWNoKGRhdGFTZXQkYmFubmVyICVpbiUgc3BlY1Rhc2tCYW5uZXIxKV0gPC0gJ0JUMScKZGF0YVNldCRiYW5uZXJbd2hpY2goZGF0YVNldCRiYW5uZXIgJWluJSBzcGVjVGFza0Jhbm5lcjIpXSA8LSAnQlQyJwpkYXRhU2V0JGJhbm5lclt3aGljaChkYXRhU2V0JGJhbm5lciAlaW4lIHNwZWNUYXNrQmFubmVyMyldIDwtICdCVDMnCmRhdGFTZXQkYmFubmVyW3doaWNoKGRhdGFTZXQkYmFubmVyICVpbiUgZ2VuSW52UGFnZV9yZyldIDwtICdHSVBfUkcnCmRhdGFTZXQkYmFubmVyW3doaWNoKGRhdGFTZXQkYmFubmVyICVpbiUgZ2VuSW52UGFnZV9scCldIDwtICdHSVBfTFAnCmRhdGFTZXQkYmFubmVyIDwtIHBhc3RlKGRhdGFTZXQkYmFubmVyLCAiX2NsaWNrIiwgc2VwID0gIiIpCmRhdGFTZXQkYmFubmVyW3doaWNoKCEoZGF0YVNldCRiYW5uZXIgJWluJSBjKCdCVDFfY2xpY2snLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnQlQyX2NsaWNrJywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ0JUM19jbGljaycsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdHSVBfUkdfY2xpY2snLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnR0lQX0xQX2NsaWNrJykpKV0gPC0gJ090aGVyJwpjb2xuYW1lcyhkYXRhU2V0KSA8LSBjKCdQYWdlJywgJ1NvdXJjZScsICdSZWZlcmVyJywgJ0RhdGUnKQoKIyMjIC0tLSBzdG9yZSBhYmNfQmFubmVyQ2xpY2tzUGFnZVZpZXdzX1VwZGF0ZS5jc3YKd3JpdGUuY3N2KGRhdGFTZXQsIGZpbGUgPSAiYWJjX0Jhbm5lckNsaWNrc1BhZ2VWaWV3c19Ob24tUmVmaW5lZC5jc3YiKQoKZGF0YVNldCRTb3VyY2VbZGF0YVNldCRQYWdlICVpbiUgJ1NwZXppYWw6QmVudXR6ZXJrb250b19hbmxlZ2VuJyAmIGRhdGFTZXQkU291cmNlID09ICdPdGhlciddIDwtCiAgc3RyX2V4dHJhY3QoZGF0YVNldCRSZWZlcmVyW2RhdGFTZXQkUGFnZSAlaW4lICdTcGV6aWFsOkJlbnV0emVya29udG9fYW5sZWdlbicgJiBkYXRhU2V0JFNvdXJjZSA9PSAnT3RoZXInXSwKICAgICAgICAgICAgICAiY2FtcGFpZ249d21kZV9hYmMoLikrJCIpCmRhdGFTZXQkU291cmNlW2RhdGFTZXQkUGFnZSAlaW4lICdTcGV6aWFsOkJlbnV0emVya29udG9fYW5sZWdlbicgJiBncmVwbCgid21kZV9hYmMyMDE3X2J0MSIsIGRhdGFTZXQkU291cmNlKV0gPC0gIkpldHp0TWl0bWFjaGVuX0JUMSIKZGF0YVNldCRTb3VyY2VbZGF0YVNldCRQYWdlICVpbiUgJ1NwZXppYWw6QmVudXR6ZXJrb250b19hbmxlZ2VuJyAmIGdyZXBsKCJ3bWRlX2FiYzIwMTdfYnQyIiwgZGF0YVNldCRTb3VyY2UpXSA8LSAiSmV0enRNaXRtYWNoZW5fQlQyIgpkYXRhU2V0JFNvdXJjZVtkYXRhU2V0JFBhZ2UgJWluJSAnU3BlemlhbDpCZW51dHplcmtvbnRvX2FubGVnZW4nICYgZ3JlcGwoIndtZGVfYWJjMjAxN19idDMiLCBkYXRhU2V0JFNvdXJjZSldIDwtICJKZXR6dE1pdG1hY2hlbl9CVDMiCmRhdGFTZXQkU291cmNlW2RhdGFTZXQkUGFnZSAlaW4lICdTcGV6aWFsOkJlbnV0emVya29udG9fYW5sZWdlbicgJiBncmVwbCgid21kZV9hYmMyMDE3X2dpYl9yZyIsIGRhdGFTZXQkU291cmNlKV0gPC0gIkdJUF9SR19jbGljayIKZGF0YVNldCRTb3VyY2VbZGF0YVNldCRQYWdlICVpbiUgJ1NwZXppYWw6QmVudXR6ZXJrb250b19hbmxlZ2VuJyAmIGdyZXBsKCJ3bWRlX2FiYzIwMTdfZ2liX2xwIiwgZGF0YVNldCRTb3VyY2UpXSA8LSAiTWFjaF9taXQiCmRhdGFTZXQkU291cmNlW2RhdGFTZXQkUGFnZSAlaW4lICdTcGV6aWFsOkJlbnV0emVya29udG9fYW5sZWdlbicgJiBkYXRhU2V0JFJlZmVyZXIgJWluJSAnLSddIDwtICJVbmtub3duIgpkYXRhU2V0JFNvdXJjZVtkYXRhU2V0JFBhZ2UgJWluJSAnTWFjaF9taXQnICYgZGF0YVNldCRSZWZlcmVyICVpbiUgJy0nXSA8LSAiVW5rbm93biIKZGF0YVNldCRTb3VyY2VbZGF0YVNldCRQYWdlICVpbiUgJ0pldHp0TWl0bWFjaGVuJyAmIGRhdGFTZXQkUmVmZXJlciAlaW4lICctJ10gPC0gIlVua25vd24iCmRhdGFTZXQkU291cmNlW2lzLm5hKGRhdGFTZXQkU291cmNlKV0gPC0gJ090aGVyJwpkYXRhU2V0JFJlZmVyZXIgPC0gTlVMTAoKIyMjIC0tLSBzdG9yZSBhYmNfQmFubmVyQ2xpY2tzUGFnZVZpZXdzX1VwZGF0ZS5jc3YKc2V0d2QoZGFpbHlVcGRhdGVEaXIpCndyaXRlLmNzdihkYXRhU2V0LCBmaWxlID0gImFiY19CYW5uZXJDbGlja3NQYWdlVmlld3NfVXBkYXRlLmNzdiIpCgojIyMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KIyMjIDMuIFVzZXIgUmVnaXN0cmF0aW9uIERhdGEKIyMjIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCgojIC0gTk9URTogVVRDIHRpbWVzdGFtcHMgLSBhZGp1c3RtZW50IGZvciBDRShTKVQgaW50cm9kdWNlZC4gCiMgLSBTZXJ2ZXJTaWRlQWNjb3VudENyZWF0aW9uXzU0ODczNDUKcUNvbW1hbmQgPC0gIm15c3FsIC0tZGVmYXVsdHMtZmlsZT0vZXRjL215c3FsL2NvbmYuZC9hbmFseXRpY3MtcmVzZWFyY2gtY2xpZW50LmNuZiAtaCBhbmFseXRpY3Mtc3RvcmUuZXFpYWQud21uZXQgLUEgLWUgXCJzZWxlY3QgKiBmcm9tIGxvZy5TZXJ2ZXJTaWRlQWNjb3VudENyZWF0aW9uXzU0ODczNDUgd2hlcmUgKCh3ZWJIb3N0ID0gJ2RlLndpa2lwZWRpYS5vcmcnKSBhbmQgKHRpbWVzdGFtcCA+PSAyMDE3MTAwNDIyMDAwMCkpO1wiID4gL2hvbWUvZ29yYW5zbS9fbWlzY1dNREUvYWJjMjAxN19EYXRhT1VUL2FiYzIwMTdfT2ZmaWNpYWxEYXRhc2V0cy9hYmMyMDE3X0RhaWx5VXBkYXRlL2FiYzIwMTdfdXNlclJlZ2lzdHJhdGlvbnMudHN2IgpzeXN0ZW0oY29tbWFuZCA9IHFDb21tYW5kLCB3YWl0ID0gVFJVRSkKCiMjIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQojIyMgNC4gR3VpZGVkIFRvdXIgRGF0YQojIyMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KCiMgLSBOT1RFOiBVVEMgdGltZXN0YW1wcyAtIGFkanVzdG1lbnQgZm9yIENFKFMpVCBpbnRyb2R1Y2VkLiAKCiMgLSBTZXJ2ZXJTaWRlQWNjb3VudENyZWF0aW9uXzU0ODczNDUKcUNvbW1hbmQgPC0gIm15c3FsIC0tZGVmYXVsdHMtZmlsZT0vZXRjL215c3FsL2NvbmYuZC9hbmFseXRpY3MtcmVzZWFyY2gtY2xpZW50LmNuZiAtaCBhbmFseXRpY3Mtc3RvcmUuZXFpYWQud21uZXQgLUEgLWUgXCJzZWxlY3QgKiBmcm9tIGxvZy5HdWlkZWRUb3VyRXhpdGVkXzg2OTA1NjYgd2hlcmUgKCh3ZWJIb3N0ID0gJ2RlLndpa2lwZWRpYS5vcmcnKSBhbmQgKHRpbWVzdGFtcCA+PSAyMDE3MTAwNDIyMDAwMCkpO1wiID4gL2hvbWUvZ29yYW5zbS9fbWlzY1dNREUvYWJjMjAxN19EYXRhT1VUL2FiYzIwMTdfT2ZmaWNpYWxEYXRhc2V0cy9hYmMyMDE3X0RhaWx5VXBkYXRlL2FiYzIwMTdfZ3VpZGVkVG91cnMudHN2IgpzeXN0ZW0oY29tbWFuZCA9IHFDb21tYW5kLCB3YWl0ID0gVFJVRSkKCiMjIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQojIyMgNS4gVXNlciBFZGl0cyBEYXRhCiMjIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQojIC0gZ2V0IHVzZXIgSURzIGZyb20gcmVnaXN0ZXJlZDoKbEYgPC0gbGlzdC5maWxlcygpCmxGIDwtIGxGW2dyZXBsKCd1c2VyUmVnaXN0cmF0aW9ucycsIGxGLCBmaXhlZCA9IFQpXQp1c2VyUmVnIDwtIHJlYWQudGFibGUobEYsIAogICAgICAgICAgICAgICAgICAgICAgcXVvdGUgPSAiIiwKICAgICAgICAgICAgICAgICAgICAgIHNlcCA9ICJcdCIsCiAgICAgICAgICAgICAgICAgICAgICBoZWFkZXIgPSBULAogICAgICAgICAgICAgICAgICAgICAgY2hlY2submFtZXMgPSBGLAogICAgICAgICAgICAgICAgICAgICAgc3RyaW5nc0FzRmFjdG9ycyA9IEYpCnVzZXJSZWcgPC0gdXNlclJlZyAlPiUgCiAgZHBseXI6OnNlbGVjdChldmVudF91c2VySWQsIGV2ZW50X2lzU2VsZk1hZGUpICU+JSAKICBmaWx0ZXIoZXZlbnRfaXNTZWxmTWFkZSA9PSAxKQojIC0gdWlkczoKdWlkIDwtIHVzZXJSZWckZXZlbnRfdXNlcklkCiMgLSBzcWwgcXVlcnkKc3FsUXVlcnkgPC0gcGFzdGUoJ1NFTEVDVCBDT1VOVCgqKSBhcyBlZGl0cywgcmV2X3VzZXIgRlJPTSByZXZpc2lvbiBXSEVSRSByZXZfdXNlciBJTiAoJywKICAgICAgICAgICAgICAgICAgcGFzdGUodWlkLCBjb2xsYXBzZSA9ICIsICIpLAogICAgICAgICAgICAgICAgICAnKSBHUk9VUCBCWSByZXZfdXNlcjsnLAogICAgICAgICAgICAgICAgICBzZXAgPSAiIikKbXlTcWxDb21tYW5kIDwtIHBhc3RlKCdteXNxbCAtaCBhbmFseXRpY3Mtc3RvcmUuZXFpYWQud21uZXQgZGV3aWtpIC1lICcsCiAgICAgICAgICAgICAgICAgICAgICBwYXN0ZSgnIicsIHNxbFF1ZXJ5LCAnIiA+ICcsIHNlcCA9ICIiKSwKICAgICAgICAgICAgICAgICAgICAgICcvaG9tZS9nb3JhbnNtL19taXNjV01ERS9hYmMyMDE3X0RhdGFPVVQvYWJjMjAxN19PZmZpY2lhbERhdGFzZXRzL2FiYzIwMTdfRGFpbHlVcGRhdGUvYWJjMjAxN191c2VyRWRpdHMudHN2Jywgc2VwID0gIiIpCnN5c3RlbShjb21tYW5kID0gbXlTcWxDb21tYW5kLCAKICAgICAgIHdhaXQgPSBUUlVFKQpgYGAKCiMjIyAwLiAyIEFiYnJldmlhdGlvbnMgdXNlZCBpbiB0aGlzIFJlcG9ydAoKLSBgQlQxYCBTcGVjaWZpYyBUYXNrIEJhbm5lciBDYW1wYWlnbiAxIC0gIllvdSBjYW4gbWFrZSBXaWtpcGVkaWEgbW9yZSB2aXZpZCEgQ1RBOiBMZWFybiBob3cgdG8gYWRkIHBpY3R1cmVzIHRvIGFydGljbGVzIgotIGBCVDJgIFNwZWNpZmljIFRhc2sgQmFubmVyIENhbXBhaWduIDIgLSAiWW91IGNhbiBpbXByb3ZlIHRoZSBhY2N1cmFjeSBvZiBXaWtpcGVkaWEhIENUQTogTGVhcm4gaG93IHRvIGltcHJvdmUgYXJ0aWNsZXMiCi0gYEJUM2AgU3BlY2lmaWMgVGFzayBCYW5uZXIgQ2FtcGFpZ24gMyAtICJZb3UgY2FuIGltcHJvdmUgdGhlIHJlbGlhYmlsaXR5IG9mIFdpa2lwZWRpYSEgQ1RBOiBMZWFybiBob3cgdG8gYWRkIGNpdGF0aW9ucyIKLSBgR0lCYCBHZW5lcmFsIEludml0YXRpb24gQmFubmVyIC0gIkNvbnRyaWJ1dGUgdG8gV2lraXBlZGlhIENUQTogQ3JlYXRlIGEgdXNlciBhY2NvdW50IgotIGBHSUJfTFBgIGEgdmVyc2lvbiBvZiB0aGUgR2VuZXJhbCBJbnZpdGF0aW9uIEJhbm5lciB0aGF0IGxlYWRzIHRvIHRoZSBgTWFjaF8gbWl0YCBsYW5kaW5nIHBhZ2UKLSBgR0lCX1JHYCBhIHZlcnNpb24gb2YgdGhlIEdlbmVyYWwgSW52aXRhdGlvbiBCYW5uZXIgdGhhdCBsZWFkcyBkaXJlY3RseSB0byB0aGUgcmVnaXN0cmF0aW9uIHBhZ2UKLSBgTWFjaF8gbWl0YCB0aGUgbGFuZGluZyBwYWdlIGZvciB0aGUgR2VuZXJhbCBJbnZpdGF0aW9uIEJhbm5lcgotIGBKZXR6X01pdG1hY2hlbl9gIHRoZSBsYW5kaW5nIHBhZ2UgZm9yIHRoZSBTcGVjaWZpYyBUYXNrIEJhbm5lcnMKCiMjIDEuIENhbXBhaWduIEJhbm5lcnMgYW5kIFBhZ2VzCgpUaGlzIHNlY3Rpb24gcHJlc2VudHMgYWxsIGRhdGEgYW5kIHN0YXRpc3RpY3Mgb24gdGhlIGNhbXBhaWduIGJhbm5lcnMgYW5kIHBhZ2VzLgoKIyMjIDEuMSBCYW5uZXIgSW1wcmVzc2lvbnMKCmBgYHtyIGVjaG8gPSBULCBldmFsID0gVH0KIyMjIC0tLSBleHRyYWN0IG9ubHkgY2FtcGFpZ24gcmVsZXZhbnQgZGF0YQojIC0gY2FtcGFpZ24gYmFubmVyczoKQlQxIDwtICdXTURFX2VkaXRvcl9jYW1wYWlnbl9hdXR1bW4xN19hJwpCVDIgPC0gJ1dNREVfZWRpdG9yX2NhbXBhaWduX2F1dHVtbjE3X2InCkJUMyA8LSAnV01ERV9lZGl0b3JfY2FtcGFpZ25fYXV0dW1uMTdfYycKR0lCX1JHIDwtICdXTURFX2VkaXRvcl9jYW1wYWlnbl9hdXR1bW4xN19lJwpHSUJfTFAgPC0gJ1dNREVfZWRpdG9yX2NhbXBhaWduX2F1dHVtbjE3X2QnCgpkYXRhU2V0IDwtIHJlYWQuY3N2KCcuL19kYWlseVVwZGF0ZURBVEEvYWJjX0Jhbm5lckltcHJlc3Npb25zX3VwZGF0ZS5jc3YnLAogICAgICAgICAgICAgICAgICAgIGhlYWRlciA9IFQsCiAgICAgICAgICAgICAgICAgICAgcm93Lm5hbWVzID0gMSwKICAgICAgICAgICAgICAgICAgICBjaGVjay5uYW1lcyA9IEYsCiAgICAgICAgICAgICAgICAgICAgc3RyaW5nc0FzRmFjdG9ycyA9IEYpCiMgLSByZWNvZGU6CmRhdGFTZXQkYmFubmVyIDwtIHJlY29kZShkYXRhU2V0JGJhbm5lciwKICAgICAgICAgICAgICAgICAgICAgICAgICdXTURFX2VkaXRvcl9jYW1wYWlnbl9hdXR1bW4xN19hJyA9ICdCVDEnLAogICAgICAgICAgICAgICAgICAgICAgICAgJ1dNREVfZWRpdG9yX2NhbXBhaWduX2F1dHVtbjE3X2InID0gJ0JUMicsCiAgICAgICAgICAgICAgICAgICAgICAgICAnV01ERV9lZGl0b3JfY2FtcGFpZ25fYXV0dW1uMTdfYycgPSAnQlQzJywKICAgICAgICAgICAgICAgICAgICAgICAgICdXTURFX2VkaXRvcl9jYW1wYWlnbl9hdXR1bW4xN19lJyA9ICdHSUJfUkcnLAogICAgICAgICAgICAgICAgICAgICAgICAgJ1dNREVfZWRpdG9yX2NhbXBhaWduX2F1dHVtbjE3X2QnID0gJ0dJQl9MUCcsCiAgICAgICAgICAgICAgICAgICAgICAgICAnV01ERV9lZGl0b3JfY2FtcGFpZ25fYXV0dW1uMTdfYTInID0gJ0JUMScsCiAgICAgICAgICAgICAgICAgICAgICAgICAnV01ERV9lZGl0b3JfY2FtcGFpZ25fYXV0dW1uMTdfYjInID0gJ0JUMicsCiAgICAgICAgICAgICAgICAgICAgICAgICAnV01ERV9lZGl0b3JfY2FtcGFpZ25fYXV0dW1uMTdfYzInID0gJ0JUMycsCiAgICAgICAgICAgICAgICAgICAgICAgICAnV01ERV9lZGl0b3JfY2FtcGFpZ25fYXV0dW1uMTdfZTInID0gJ0dJQl9SRycsCiAgICAgICAgICAgICAgICAgICAgICAgICAnV01ERV9lZGl0b3JfY2FtcGFpZ25fYXV0dW1uMTdfZDInID0gJ0dJQl9MUCcKICAgICAgICAgICAgICAgICAgICAgICAgICkKIyMjIC0tLSBDb3VudCBkYWlseSBiYW5uZXIgaW1wcmVzc2lvbnM6CmJhbkltcFNldCA8LSBkYXRhU2V0ICU+JSAKICBmaWx0ZXIocmVzdWx0ICVpbiUgJ3Nob3cnKSAlPiUgCiAgbXV0YXRlKGltcHJlc3Npb25SYXRlID0gMS9pbXByZXNzaW9uUmF0ZSkgICU+JQogIGdyb3VwX2J5KGJhbm5lciwgZGF0ZSkgJT4lIAogIHN1bW1hcmlzZShDb3VudCA9IHN1bShpbXByZXNzaW9uUmF0ZSkpCmNvbG5hbWVzKGJhbkltcFNldCkgPC0gYygnQmFubmVyJywgJ0RhdGUnLCAnQ291bnQnKQojIC0gQ2FtcGFpZ24gQ2hhcnQgQ29sb3JzCmNhbXBhaWduQ2hhcnRDb2xvcnMgPC0gYygnaW5kaWFucmVkMScsICdpbmRpYW5yZWQyJywgJ2luZGlhbnJlZDMnLAogICAgICAgICAgICAgICAnY2FkZXRibHVlJywgJ2NhZGV0Ymx1ZTInKQpuYW1lcyhjYW1wYWlnbkNoYXJ0Q29sb3JzKSA8LSBjKCdCVDEnLCAnQlQyJywgJ0JUMycsICdHSUJfTFAnLCAnR0lCX1JHJykKCiMgLSBWaXN1YWxpemUgdy4ge2dncGxvdDJ9CmdncGxvdChiYW5JbXBTZXQsIGFlcyh4ID0gRGF0ZSwKICAgICAgICAgICAgICAgICAgICAgIHkgPSBDb3VudCwKICAgICAgICAgICAgICAgICAgICAgIGdyb3VwID0gQmFubmVyLAogICAgICAgICAgICAgICAgICAgICAgY29sb3IgPSBCYW5uZXIsCiAgICAgICAgICAgICAgICAgICAgICBmaWxsID0gQmFubmVyLAogICAgICAgICAgICAgICAgICAgICAgbGFiZWwgPSBDb3VudCkpICsgCiAgZ2VvbV9wYXRoKHNpemUgPSAuNSkgKyAKICBzY2FsZV9maWxsX21hbnVhbCgibGVnZW5kIiwgdmFsdWVzID0gY2FtcGFpZ25DaGFydENvbG9ycykgKwogIHNjYWxlX2NvbG9yX21hbnVhbCgibGVnZW5kIiwgdmFsdWVzID0gY2FtcGFpZ25DaGFydENvbG9ycykgKyAKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gY29tbWEpICsKICBnZ3RpdGxlKCdBdXR1bW4gQmFubmVyIENhbXBhaWduIDIwMTc6IEJhbm5lciBJbXByZXNzaW9ucycpICsKICB0aGVtZV9taW5pbWFsKCkgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIHNpemUgPSA4KSkgKwogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSkgKwogIHRoZW1lKGxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSkKYGBgCgpgYGB7ciBlY2hvID0gVCwgd2FybmluZyA9ICdoaWRlJywgbWVzc2FnZSA9IEZ9CiMjIyAtLS0gRnVsbCBEYXRhc2V0IChUYWJsZSBSZXBvcnQpCmRhdGF0YWJsZShiYW5JbXBTZXQpCmBgYAoKIyMjIDEuMiBCYW5uZXIgQ2xpY2tzIGFuZCBMYW5kaW5nIFBhZ2UgVmlld3MKCiMjIyAxLjIuMCBUaGUgRGF0YXNldAoKYGBge3IgZWNobyA9IFR9CmRhdGFTZXQgPC0gcmVhZC5jc3YocGFzdGUoJy4vX2RhaWx5VXBkYXRlREFUQS8nLCAnYWJjX0Jhbm5lckNsaWNrc1BhZ2VWaWV3c19VcGRhdGUuY3N2Jywgc2VwID0gIiIpLAogICAgICAgICAgICAgICAgICAgIGhlYWRlciA9IFQsCiAgICAgICAgICAgICAgICAgICAgY2hlY2submFtZXMgPSBGLAogICAgICAgICAgICAgICAgICAgIHJvdy5uYW1lcyA9IDEsCiAgICAgICAgICAgICAgICAgICAgc3RyaW5nc0FzRmFjdG9ycyA9IEYpICU+JSAKICBmaWx0ZXIoUGFnZSAlaW4lIGMoJ0pldHp0TWl0bWFjaGVuJywgJ1NwZXppYWw6QmVudXR6ZXJrb250b19hbmxlZ2VuJywgJ01hY2hfbWl0JykpCiMgLSBmaXggJ0dJUCcgLT4gJ0dJQicgaW4gdGhlIGRhdGFzZXQ6CmRhdGFTZXQkU291cmNlIDwtIGdzdWIoJ0dJUCcsICdHSUInLCBkYXRhU2V0JFNvdXJjZSkKIyAtIE5PVEUgKFRFTVBPUkFSWSk6CmRhdGFTZXQgPC0gZGF0YVNldFsxOihkaW0oZGF0YVNldClbMV0gLSAyKSwgXQpkYXRhU2V0IDwtIGZpbHRlcihkYXRhU2V0LCAKICAgICAgICAgICAgICAgICAgIWlzLm5hKFBhZ2UpICYgIWlzLm5hKFNvdXJjZSkgJiAhaXMubmEoRGF0ZSkgJiAhKFNvdXJjZSA9PSAiPE5BPiIpKQojIC0gQ2hhcnQgY29sb3JzZ2l0IApjaGFydENvbHMgPC0gYygnaW5kaWFucmVkMScsICdpbmRpYW5yZWQyJywgJ2luZGlhbnJlZDMnLAogICAgICAgICAgICAgICAnY2FkZXRibHVlJywgJ2NhZGV0Ymx1ZTInLCAKICAgICAgICAgICAgICAgJ2RlZXBza3libHVlJywgJ3Zpb2xldHJlZDEnLCAndmlvbGV0cmVkMicsICd2aW9sZXRyZWQzJywKICAgICAgICAgICAgICAgJ2xpZ2h0c2xhdGVncmV5JywgJ2xpZ2h0Z3JleScpCm5hbWVzKGNoYXJ0Q29scykgPC0gYygnQlQxX2NsaWNrJywgJ0JUMl9jbGljaycsICdCVDNfY2xpY2snLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ0dJQl9MUF9jbGljaycsICdHSUJfUkdfY2xpY2snLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ01hY2hfbWl0JywgJ0pldHp0TWl0bWFjaGVuX0JUMScsICdKZXR6dE1pdG1hY2hlbl9CVDInLCAnSmV0enRNaXRtYWNoZW5fQlQzJywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdPdGhlcicsICdVbmtub3duJykKZGF0YVNldCRTb3VyY2UgPC0gZmFjdG9yKGRhdGFTZXQkU291cmNlLCBsZXZlbHMgPSBjKCdCVDFfY2xpY2snLCAnQlQyX2NsaWNrJywgJ0JUM19jbGljaycsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnR0lCX0xQX2NsaWNrJywgJ0dJQl9SR19jbGljaycsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnTWFjaF9taXQnLCAnSmV0enRNaXRtYWNoZW5fQlQxJywgJ0pldHp0TWl0bWFjaGVuX0JUMicsICdKZXR6dE1pdG1hY2hlbl9CVDMnLCAnT3RoZXInLCAnVW5rbm93bicpKQoKIyAtIFBhZ2UgQ2hhcnQgQ29sb3JzCnBhZ2VDaGFydENvbG9ycyA8LSBjKCdvcmFuZ2UnLCAnZGVlcHNreWJsdWUnLCAnbGlnaHRncmVlbicpCm5hbWVzKHBhZ2VDaGFydENvbG9ycykgPC0gYygnSmV0enRNaXRtYWNoZW4nLCAnU3BlemlhbDpCZW51dHplcmtvbnRvX2FubGVnZW4nLCAnTWFjaF9taXQnKQpkYXRhU2V0JFBhZ2UgPC0gZmFjdG9yKGRhdGFTZXQkUGFnZSwgCiAgICAgICAgICAgICAgICAgICAgICAgbGV2ZWxzID0gYygnSmV0enRNaXRtYWNoZW4nLCAnU3BlemlhbDpCZW51dHplcmtvbnRvX2FubGVnZW4nLCAnTWFjaF9taXQnKSkKCiMgLSBDYW1wYWlnbiBDaGFydCBDb2xvcnMKY2FtcGFpZ25DaGFydENvbG9ycyA8LSBjKCdpbmRpYW5yZWQxJywgJ2luZGlhbnJlZDInLCAnaW5kaWFucmVkMycsCiAgICAgICAgICAgICAgICdjYWRldGJsdWUnLCAnY2FkZXRibHVlMicpCm5hbWVzKGNhbXBhaWduQ2hhcnRDb2xvcnMpIDwtIGMoJ0JUMScsICdCVDInLCAnQlQzJywgJ0dJQl9MUCcsICdHSUJfUkcnKQpgYGAKCiMjIyMgMS4yLjFBIExhbmRpbmcgUGFnZXM6IFJlZmVyZXJzIE92ZXJ2aWV3CgpUaGUgZm9sbG93aW5nIGNoYXJ0cyByZXByZXNlbnRzIHRoZSBicmVha2Rvd24gb2YgcmVmZXJlcnMgKGkuZS4gc291cmNlcykgZm9yIHRoZSBjYW1wYWlnbiBwYWdlczogb25lIHJlZ2lzdHJhdGlvbiBwYWdlLCBhbmQgdHdvIGxhbmRpbmcgcGFnZXMuCgpgYGB7ciBlY2hvID0gVCwgd2FybmluZyA9ICdoaWRlJywgbWVzc2FnZSA9IEZ9CiMjIyAtLS0gQmFubmVyIGNsaWNrcyBhbmQgTGFuZGluZyBQYWdlIFZpZXdzCiMgLSBUYWJsZSBSZXBvcnQKdGFibGVTZXQgPC0gZGF0YVNldCAlPiUKICBkcGx5cjo6Z3JvdXBfYnkoUGFnZSwgU291cmNlLCBEYXRlKSAlPiUgCiAgZHBseXI6OnN1bW1hcmlzZShDb3VudCA9IG4oKSkgJT4lIAogIGRwbHlyOjphcnJhbmdlKERhdGUsIFBhZ2UsIFNvdXJjZSkKZ2dwbG90KHRhYmxlU2V0LCBhZXMoeCA9IFBhZ2UsCiAgICAgICAgICAgICAgICAgICAgeSA9IENvdW50LAogICAgICAgICAgICAgICAgICAgIGdyb3VwID0gU291cmNlLAogICAgICAgICAgICAgICAgICAgIGNvbG9yID0gU291cmNlLAogICAgICAgICAgICAgICAgICAgIGZpbGwgPSBTb3VyY2UsCiAgICAgICAgICAgICAgICAgICAgbGFiZWwgPSBDb3VudCkpICsKICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IiwgCiAgICAgICAgICAgcG9zaXRpb24gPSAiZG9kZ2UiLCAKICAgICAgICAgICB3aWR0aCA9IC4zNSkgKwogIHNjYWxlX2ZpbGxfbWFudWFsKCJsZWdlbmQiLCB2YWx1ZXMgPSBjaGFydENvbHMpICsKICBzY2FsZV9jb2xvcl9tYW51YWwoImxlZ2VuZCIsIHZhbHVlcyA9IGNoYXJ0Q29scykgKyAKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gY29tbWEpICsKICBnZ3RpdGxlKCdBdXR1bW4gQmFubmVyIENhbXBhaWduIDIwMTc6XG5PdmVydmlldyBvZiBMYW5kaW5nIFBhZ2UgVmlld3Mgc291cmNlcycpICsKICB0aGVtZV9taW5pbWFsKCkgKyAKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCBzaXplID0gOCwgaGp1c3QgPSAxKSkgKwogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSkgKwogIHRoZW1lKGxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSkgKwogIHRoZW1lKHBhbmVsLmdyaWQubWFqb3IueCA9IGVsZW1lbnRfYmxhbmsoKSkgKwogIHRoZW1lKHBhbmVsLmdyaWQubWlub3IueCA9IGVsZW1lbnRfYmxhbmsoKSkgKwogIHRoZW1lKHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCkpCmBgYAoKIyMjIyAxLjIuMUIgUGFnZSBWaWV3cy9CYW5uZXIgQ2xpY2tzIERhdGFzZXQgIAoKVGhlIGBQYWdlYCBjb2x1bW4gcmVmZXJzIHRvIGVpdGhlciBvbmUgb2YgdGhlIHR3byBjYW1wYWlnbiBsYW5kaW5nIHBhZ2VzIG9yIHRoZSByZWdpc3RyYXRpb24gcGFnZS4gVGhlIGBTb3VyY2VgIGNvbHVtbiBlbmNvbXBhc3NlcyBib3RoIGNhbXBhaWduIGJhbm5lciBjbGlja3MgYW5kIGNhbXBhaWduIHBhZ2VzIGFzIHJlZmVyZXJzIG9mIHRoZSBgUGFnZWAuIFRoZSBgQ291bnRgIGRhdGEgaGF2ZSBhIGRhaWx5IHJlc29sdXRpb24uICAKCmBgYHtyIGVjaG8gPSBULCB3YXJuaW5nID0gJ2hpZGUnLCBtZXNzYWdlID0gRn0KIyMjIC0tLSBGdWxsIERhdGFzZXQgKFRhYmxlIFJlcG9ydCkKZGF0YXRhYmxlKHRhYmxlU2V0KQpgYGAKCiMjIyMgMS4yLjIgTGFuZGluZyBQYWdlczogUmVmZXJlciBCcmVha2Rvd24gCgpUaGUgZm9sbG93aW5nIHRocmVlIHBpZSBjaGFydHMgcHJlc2VudCBhIGJyZWFrZG93biBvZiByZWZlcmVycyAoaS5lLiBzb3VyY2VzKSBmb3IgdGhlIENhbXBhaWduIHBhZ2VzICh0d28gbGFuZGluZyBwYWdlcyBhbmQgb25lIHJlZ2lzdHJhdGlvbiBwYWdlLgoKYGBge3IgZWNobyA9IFQsIHdhcm5pbmcgPSAnaGlkZScsIG1lc3NhZ2UgPSBGfQojIyMgLS0tIFBhZ2UgVmlld3M6IFNvdXJjZXMKCiMgLSBTcGV6aWFsOkJlbnV0emVya29udG9fYW5sZWdlbgpwYWdlU291cmNlIDwtIGRhdGFTZXQgJT4lIAogIGRwbHlyOjpjb3VudChQYWdlLCBTb3VyY2UpICU+JQogIGRwbHlyOjpncm91cF9ieShQYWdlKSAlPiUgCiAgZHBseXI6Om11dGF0ZShQZXJjZW50ID0gbi9zdW0obikpCnBhZ2VTb3VyY2UkUGVyY2VudCA8LSBwYXN0ZShyb3VuZChwYWdlU291cmNlJFBlcmNlbnQqMTAwLCAyKSwgIiUiLCBzZXAgPSAiIikKcGFnZVNvdXJjZVBsb3QgPC0gZmlsdGVyKHBhZ2VTb3VyY2UsIFBhZ2UgJWluJSAnU3BlemlhbDpCZW51dHplcmtvbnRvX2FubGVnZW4nKQppZiAoZGltKHBhZ2VTb3VyY2VQbG90KVsxXSA+IDApIHsKICBnZ3Bsb3QocGFnZVNvdXJjZVBsb3QsIGFlcyh4ID0gJycsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeSA9IG4sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sb3IgPSBTb3VyY2UsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmlsbCA9IFNvdXJjZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsYWJlbCA9IFBlcmNlbnQpKSArCiAgICBnZW9tX2JhcihhZXMoeCA9ICcnLAogICAgICAgICAgICAgICAgIHkgPSBuLAogICAgICAgICAgICAgICAgIGNvbG9yID0gU291cmNlLAogICAgICAgICAgICAgICAgIGZpbGwgPSBTb3VyY2UpLCAKICAgICAgICAgICAgIHN0YXQgPSAiaWRlbnRpdHkiLCAKICAgICAgICAgICAgIHdpZHRoID0gMSkgKwogICAgY29vcmRfcG9sYXIoInkiLCBzdGFydCA9IDApICsKICAgIGdlb21fdGV4dChhZXMoeCA9IDEpLAogICAgICAgICAgICAgIGNvbG91ciA9ICJ3aGl0ZSIsCiAgICAgICAgICAgICAgZm9udGZhY2UgPSAiYm9sZCIsCiAgICAgICAgICAgICAgcG9zaXRpb24gPSBwb3NpdGlvbl9zdGFjayh2anVzdCA9IDAuNSksCiAgICAgICAgICAgICAgc2l6ZSA9IDMsCiAgICAgICAgICAgICAgc2hvdy5sZWdlbmQgPSBGKSArCiAgICBzY2FsZV9maWxsX21hbnVhbCgibGVnZW5kIiwgdmFsdWVzID0gY2hhcnRDb2xzKSArCiAgICBzY2FsZV9jb2xvcl9tYW51YWwoImxlZ2VuZCIsIHZhbHVlcyA9IGNoYXJ0Q29scykgKyAKICAgIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBjb21tYSkgKwogICAgZ2d0aXRsZSgnQXV0dW1uIEJhbm5lciBDYW1wYWlnbiAyMDE3OlxuUGFnZSBWaWV3cyBTb3VyY2VzIGZvciBTcGV6aWFsOkJlbnV0emVya29udG9fYW5sZWdlbicpICsKICAgIHhsYWIoIk91dHRlciA9IENvdW50IikgKyB5bGFiKCIiKSArCiAgICB0aGVtZV9taW5pbWFsKCkgKyAKICAgICMgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCkpICsKICAgIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSkgKwogICAgdGhlbWUobGVnZW5kLnRpdGxlID0gZWxlbWVudF9ibGFuaygpKSArCiAgICB0aGVtZShwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2JsYW5rKCkpICsKICAgIHRoZW1lKHBhbmVsLmdyaWQubWlub3IueSA9IGVsZW1lbnRfYmxhbmsoKSkKfQoKIyAtIFNwZXppYWw6QmVudXR6ZXJrb250b19hbmxlZ2VuIC0gVW5rbm93bi9PdGhlcgpwYWdlU291cmNlIDwtIGRhdGFTZXQgJT4lIAogIGZpbHRlcighKGRhdGFTZXQkU291cmNlICVpbiUgJ090aGVyJyB8IGRhdGFTZXQkU291cmNlICVpbiUgJ1Vua25vd24nKSkgJT4lCiAgZHBseXI6OmNvdW50KFBhZ2UsIFNvdXJjZSkgJT4lCiAgZHBseXI6Omdyb3VwX2J5KFBhZ2UpICU+JSAKICBkcGx5cjo6bXV0YXRlKFBlcmNlbnQgPSBuL3N1bShuKSkKcGFnZVNvdXJjZSRQZXJjZW50IDwtIHBhc3RlKHJvdW5kKHBhZ2VTb3VyY2UkUGVyY2VudCoxMDAsIDIpLCAiJSIsIHNlcCA9ICIiKQpwYWdlU291cmNlUGxvdCA8LSBmaWx0ZXIocGFnZVNvdXJjZSwgUGFnZSAlaW4lICdTcGV6aWFsOkJlbnV0emVya29udG9fYW5sZWdlbicpCmlmIChkaW0ocGFnZVNvdXJjZVBsb3QpWzFdID4gMCkgewogIGdncGxvdChwYWdlU291cmNlUGxvdCwgYWVzKHggPSAnJywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICB5ID0gbiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xvciA9IFNvdXJjZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmaWxsID0gU291cmNlLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxhYmVsID0gUGVyY2VudCkpICsKICAgIGdlb21fYmFyKGFlcyh4ID0gJycsCiAgICAgICAgICAgICAgICAgeSA9IG4sCiAgICAgICAgICAgICAgICAgY29sb3IgPSBTb3VyY2UsCiAgICAgICAgICAgICAgICAgZmlsbCA9IFNvdXJjZSksIAogICAgICAgICAgICAgc3RhdCA9ICJpZGVudGl0eSIsIAogICAgICAgICAgICAgd2lkdGggPSAxKSArCiAgICBjb29yZF9wb2xhcigieSIsIHN0YXJ0ID0gMCkgKwogICAgZ2VvbV90ZXh0KGFlcyh4ID0gMSksCiAgICAgICAgICAgICAgY29sb3VyID0gIndoaXRlIiwKICAgICAgICAgICAgICBmb250ZmFjZSA9ICJib2xkIiwKICAgICAgICAgICAgICBwb3NpdGlvbiA9IHBvc2l0aW9uX3N0YWNrKHZqdXN0ID0gMC41KSwKICAgICAgICAgICAgICBzaXplID0gMywKICAgICAgICAgICAgICBzaG93LmxlZ2VuZCA9IEYpICsKICAgIHNjYWxlX2ZpbGxfbWFudWFsKCJsZWdlbmQiLCB2YWx1ZXMgPSBjaGFydENvbHMpICsKICAgIHNjYWxlX2NvbG9yX21hbnVhbCgibGVnZW5kIiwgdmFsdWVzID0gY2hhcnRDb2xzKSArCiAgICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gY29tbWEpICsKICAgIGdndGl0bGUoJ0F1dHVtbiBCYW5uZXIgQ2FtcGFpZ24gMjAxNzpcblBhZ2UgVmlld3MgU291cmNlcyBmb3IgU3BlemlhbDpCZW51dHplcmtvbnRvX2FubGVnZW4gKENhbXBhaWduIG9ubHkpJykgKwogICAgeGxhYigiT3V0dGVyID0gQ291bnQiKSArIHlsYWIoIiIpICsKICAgIHRoZW1lX21pbmltYWwoKSArIAogICAgIyB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfYmxhbmsoKSkgKwogICAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApKSArCiAgICB0aGVtZShsZWdlbmQudGl0bGUgPSBlbGVtZW50X2JsYW5rKCkpICsKICAgIHRoZW1lKHBhbmVsLmdyaWQubWFqb3IueSA9IGVsZW1lbnRfYmxhbmsoKSkgKwogICAgdGhlbWUocGFuZWwuZ3JpZC5taW5vci55ID0gZWxlbWVudF9ibGFuaygpKQp9CmBgYAoKVGhlIGZvbGxvd2luZyB0YWJsZSBwcmVzZW50cyB0aGUgZGF0YSBpbiByZXNwZWN0IHRvIHRoZSBDYW1wYWlnbiBzb3VyY2VzIG9ubHk6CgpgYGB7ciBlY2hvID0gVCwgd2FybmluZyA9ICdoaWRlJywgbWVzc2FnZSA9IEZ9CiMjIyAtLS0gRnVsbCBEYXRhc2V0IChUYWJsZSBSZXBvcnQpCmRhdGF0YWJsZShwYWdlU291cmNlUGxvdCkKYGBgCgpgYGB7ciBlY2hvID0gVCwgd2FybmluZyA9ICdoaWRlJywgbWVzc2FnZSA9IEZ9CiMgLSBKZXR6dE1pdG1hY2hlbgpwYWdlU291cmNlIDwtIGRhdGFTZXQgJT4lIAogIGRwbHlyOjpjb3VudChQYWdlLCBTb3VyY2UpICU+JQogIGRwbHlyOjpncm91cF9ieShQYWdlKSAlPiUgCiAgZHBseXI6Om11dGF0ZShQZXJjZW50ID0gbi9zdW0obikpCnBhZ2VTb3VyY2UkUGVyY2VudCA8LSBwYXN0ZShyb3VuZChwYWdlU291cmNlJFBlcmNlbnQqMTAwLCAyKSwgIiUiLCBzZXAgPSAiIikKcGFnZVNvdXJjZVBsb3QgPC0gZmlsdGVyKHBhZ2VTb3VyY2UsIFBhZ2UgJWluJSAnSmV0enRNaXRtYWNoZW4nKQppZiAoZGltKHBhZ2VTb3VyY2VQbG90KVsxXSA+IDApIHsKICBnZ3Bsb3QocGFnZVNvdXJjZVBsb3QsIGFlcyh4ID0gJycsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeSA9IG4sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sb3IgPSBTb3VyY2UsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmlsbCA9IFNvdXJjZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsYWJlbCA9IFBlcmNlbnQpKSArCiAgICBnZW9tX2JhcihhZXMoeCA9ICcnLAogICAgICAgICAgICAgICAgIHkgPSBuLAogICAgICAgICAgICAgICAgIGNvbG9yID0gU291cmNlLAogICAgICAgICAgICAgICAgIGZpbGwgPSBTb3VyY2UpLCAKICAgICAgICAgICAgIHN0YXQgPSAiaWRlbnRpdHkiLCAKICAgICAgICAgICAgIHdpZHRoID0gMSkgKwogICAgY29vcmRfcG9sYXIoInkiLCBzdGFydCA9IDApICsKICAgIGdlb21fdGV4dChhZXMoeCA9IDEpLAogICAgICAgICAgICAgIGNvbG91ciA9ICJ3aGl0ZSIsCiAgICAgICAgICAgICAgZm9udGZhY2UgPSAiYm9sZCIsCiAgICAgICAgICAgICAgcG9zaXRpb24gPSBwb3NpdGlvbl9zdGFjayh2anVzdCA9IDAuNSksCiAgICAgICAgICAgICAgc2l6ZSA9IDMsCiAgICAgICAgICAgICAgc2hvdy5sZWdlbmQgPSBGKSArCiAgICBzY2FsZV9maWxsX21hbnVhbCgibGVnZW5kIiwgdmFsdWVzID0gY2hhcnRDb2xzKSArCiAgICBzY2FsZV9jb2xvcl9tYW51YWwoImxlZ2VuZCIsIHZhbHVlcyA9IGNoYXJ0Q29scykgKwogICAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IGNvbW1hKSArCiAgICBnZ3RpdGxlKCdBdXR1bW4gQmFubmVyIENhbXBhaWduIDIwMTc6XG5QYWdlIFZpZXdzIFNvdXJjZXMgZm9yIEpldHp0TWl0bWFjaGVuJykgKwogICAgeGxhYigiT3V0dGVyID0gQ291bnQiKSArIHlsYWIoIiIpICsKICAgIHRoZW1lX21pbmltYWwoKSArIAogICAgIyB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfYmxhbmsoKSkgKwogICAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApKSArCiAgICB0aGVtZShsZWdlbmQudGl0bGUgPSBlbGVtZW50X2JsYW5rKCkpICsKICAgIHRoZW1lKHBhbmVsLmdyaWQubWFqb3IueSA9IGVsZW1lbnRfYmxhbmsoKSkgKwogICAgdGhlbWUocGFuZWwuZ3JpZC5taW5vci55ID0gZWxlbWVudF9ibGFuaygpKSArCiAgICB0aGVtZShwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpKQp9CgojIC0gSmV0enRNaXRtYWNoZW4gLSBtaW51cyBVbmtub3duL090aGVyCnBhZ2VTb3VyY2UgPC0gZGF0YVNldCAlPiUKICBmaWx0ZXIoIShkYXRhU2V0JFNvdXJjZSAlaW4lICdPdGhlcicgfCBkYXRhU2V0JFNvdXJjZSAlaW4lICdVbmtub3duJykpICU+JQogIGRwbHlyOjpjb3VudChQYWdlLCBTb3VyY2UpICU+JQogIGRwbHlyOjpncm91cF9ieShQYWdlKSAlPiUgCiAgZHBseXI6Om11dGF0ZShQZXJjZW50ID0gbi9zdW0obikpCnBhZ2VTb3VyY2UkUGVyY2VudCA8LSBwYXN0ZShyb3VuZChwYWdlU291cmNlJFBlcmNlbnQqMTAwLCAyKSwgIiUiLCBzZXAgPSAiIikKcGFnZVNvdXJjZVBsb3QgPC0gZmlsdGVyKHBhZ2VTb3VyY2UsIFBhZ2UgJWluJSAnSmV0enRNaXRtYWNoZW4nKQppZiAoZGltKHBhZ2VTb3VyY2VQbG90KVsxXSA+IDApIHsKICBnZ3Bsb3QocGFnZVNvdXJjZVBsb3QsIGFlcyh4ID0gJycsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeSA9IG4sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sb3IgPSBTb3VyY2UsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmlsbCA9IFNvdXJjZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsYWJlbCA9IFBlcmNlbnQpKSArCiAgICBnZW9tX2JhcihhZXMoeCA9ICcnLAogICAgICAgICAgICAgICAgIHkgPSBuLAogICAgICAgICAgICAgICAgIGNvbG9yID0gU291cmNlLAogICAgICAgICAgICAgICAgIGZpbGwgPSBTb3VyY2UpLCAKICAgICAgICAgICAgIHN0YXQgPSAiaWRlbnRpdHkiLCAKICAgICAgICAgICAgIHdpZHRoID0gMSkgKwogICAgY29vcmRfcG9sYXIoInkiLCBzdGFydCA9IDApICsKICAgIGdlb21fdGV4dChhZXMoeCA9IDEpLAogICAgICAgICAgICAgIGNvbG91ciA9ICJ3aGl0ZSIsCiAgICAgICAgICAgICAgZm9udGZhY2UgPSAiYm9sZCIsCiAgICAgICAgICAgICAgcG9zaXRpb24gPSBwb3NpdGlvbl9zdGFjayh2anVzdCA9IDAuNSksCiAgICAgICAgICAgICAgc2l6ZSA9IDMsCiAgICAgICAgICAgICAgc2hvdy5sZWdlbmQgPSBGKSArCiAgICBzY2FsZV9maWxsX21hbnVhbCgibGVnZW5kIiwgdmFsdWVzID0gY2hhcnRDb2xzKSArCiAgICBzY2FsZV9jb2xvcl9tYW51YWwoImxlZ2VuZCIsIHZhbHVlcyA9IGNoYXJ0Q29scykgKwogICAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IGNvbW1hKSArCiAgICBnZ3RpdGxlKCdBdXR1bW4gQmFubmVyIENhbXBhaWduIDIwMTc6XG5QYWdlIFZpZXdzIFNvdXJjZXMgZm9yIEpldHp0TWl0bWFjaGVuIChDYW1wYWlnbiBvbmx5KScpICsKICAgIHhsYWIoIk91dHRlciA9IENvdW50IikgKyB5bGFiKCIiKSArCiAgICB0aGVtZV9taW5pbWFsKCkgKyAKICAgICMgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCkpICsKICAgIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSkgKwogICAgdGhlbWUobGVnZW5kLnRpdGxlID0gZWxlbWVudF9ibGFuaygpKSArCiAgICB0aGVtZShwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2JsYW5rKCkpICsKICAgIHRoZW1lKHBhbmVsLmdyaWQubWlub3IueSA9IGVsZW1lbnRfYmxhbmsoKSkgKwogICAgdGhlbWUocGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSkKfQpgYGAKClRoZSBmb2xsb3dpbmcgdGFibGUgcHJlc2VudHMgdGhlIGRhdGEgaW4gcmVzcGVjdCB0byB0aGUgQ2FtcGFpZ24gc291cmNlcyBvbmx5OgoKYGBge3IgZWNobyA9IFQsIHdhcm5pbmcgPSAnaGlkZScsIG1lc3NhZ2UgPSBGfQojIyMgLS0tIEZ1bGwgRGF0YXNldCAoVGFibGUgUmVwb3J0KQpkYXRhdGFibGUocGFnZVNvdXJjZVBsb3QpCmBgYAoKYGBge3IgZWNobyA9IFQsIHdhcm5pbmcgPSAnaGlkZScsIG1lc3NhZ2UgPSBGfQojIC0gTWFjaF9taXQKcGFnZVNvdXJjZSA8LSBkYXRhU2V0ICU+JSAKICBkcGx5cjo6Y291bnQoUGFnZSwgU291cmNlKSAlPiUKICBkcGx5cjo6Z3JvdXBfYnkoUGFnZSkgJT4lIAogIGRwbHlyOjptdXRhdGUoUGVyY2VudCA9IG4vc3VtKG4pKQpwYWdlU291cmNlJFBlcmNlbnQgPC0gcGFzdGUocm91bmQocGFnZVNvdXJjZSRQZXJjZW50KjEwMCwgMiksICIlIiwgc2VwID0gIiIpCnBhZ2VTb3VyY2VQbG90IDwtIGZpbHRlcihwYWdlU291cmNlLCBQYWdlICVpbiUgJ01hY2hfbWl0JykKaWYgKGRpbShwYWdlU291cmNlUGxvdClbMV0gPiAwKSB7CiAgZ2dwbG90KHBhZ2VTb3VyY2VQbG90LCBhZXMoeCA9ICcnLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHkgPSBuLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbG9yID0gU291cmNlLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZpbGwgPSBTb3VyY2UsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFiZWwgPSBQZXJjZW50KSkgKwogICAgZ2VvbV9iYXIoYWVzKHggPSAnJywKICAgICAgICAgICAgICAgICB5ID0gbiwKICAgICAgICAgICAgICAgICBjb2xvciA9IFNvdXJjZSwKICAgICAgICAgICAgICAgICBmaWxsID0gU291cmNlKSwgCiAgICAgICAgICAgICBzdGF0ID0gImlkZW50aXR5IiwgCiAgICAgICAgICAgICB3aWR0aCA9IDEpICsKICAgIGNvb3JkX3BvbGFyKCJ5Iiwgc3RhcnQgPSAwKSArCiAgICBnZW9tX3RleHQoYWVzKHggPSAxKSwKICAgICAgICAgICAgICBjb2xvdXIgPSAid2hpdGUiLAogICAgICAgICAgICAgIGZvbnRmYWNlID0gImJvbGQiLAogICAgICAgICAgICAgIHBvc2l0aW9uID0gcG9zaXRpb25fc3RhY2sodmp1c3QgPSAwLjUpLAogICAgICAgICAgICAgIHNpemUgPSAzLAogICAgICAgICAgICAgIHNob3cubGVnZW5kID0gRikgKwogICAgc2NhbGVfZmlsbF9tYW51YWwoImxlZ2VuZCIsIHZhbHVlcyA9IGNoYXJ0Q29scykgKwogICAgc2NhbGVfY29sb3JfbWFudWFsKCJsZWdlbmQiLCB2YWx1ZXMgPSBjaGFydENvbHMpICsKICAgIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBjb21tYSkgKwogICAgZ2d0aXRsZSgnQXV0dW1uIEJhbm5lciBDYW1wYWlnbiAyMDE3OlxuUGFnZSBWaWV3cyBTb3VyY2VzIGZvciBNYWNoX21pdCcpICsKICAgIHhsYWIoIk91dHRlciA9IENvdW50IikgKyB5bGFiKCIiKSArCiAgICB0aGVtZV9taW5pbWFsKCkgKyAKICAgICMgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCkpICsKICAgIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSkgKwogICAgdGhlbWUobGVnZW5kLnRpdGxlID0gZWxlbWVudF9ibGFuaygpKSArCiAgICB0aGVtZShwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2JsYW5rKCkpICsKICAgIHRoZW1lKHBhbmVsLmdyaWQubWlub3IueSA9IGVsZW1lbnRfYmxhbmsoKSkgKwogICAgdGhlbWUocGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSkKfQoKIyAtIE1hY2hfbWl0IC0gbWludXMgVW5rbm93bi9PdGhlcgpwYWdlU291cmNlIDwtIGRhdGFTZXQgJT4lIAogIGZpbHRlcighKGRhdGFTZXQkU291cmNlICVpbiUgJ090aGVyJyB8IGRhdGFTZXQkU291cmNlICVpbiUgJ1Vua25vd24nKSkgJT4lCiAgZHBseXI6OmNvdW50KFBhZ2UsIFNvdXJjZSkgJT4lCiAgZHBseXI6Omdyb3VwX2J5KFBhZ2UpICU+JSAKICBkcGx5cjo6bXV0YXRlKFBlcmNlbnQgPSBuL3N1bShuKSkKcGFnZVNvdXJjZSRQZXJjZW50IDwtIHBhc3RlKHJvdW5kKHBhZ2VTb3VyY2UkUGVyY2VudCoxMDAsIDIpLCAiJSIsIHNlcCA9ICIiKQpwYWdlU291cmNlUGxvdCA8LSBmaWx0ZXIocGFnZVNvdXJjZSwgUGFnZSAlaW4lICdNYWNoX21pdCcpCmlmIChkaW0ocGFnZVNvdXJjZVBsb3QpWzFdID4gMCkgewogIGdncGxvdChwYWdlU291cmNlUGxvdCwgYWVzKHggPSAnJywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICB5ID0gbiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xvciA9IFNvdXJjZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmaWxsID0gU291cmNlLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxhYmVsID0gUGVyY2VudCkpICsKICAgIGdlb21fYmFyKGFlcyh4ID0gJycsCiAgICAgICAgICAgICAgICAgeSA9IG4sCiAgICAgICAgICAgICAgICAgY29sb3IgPSBTb3VyY2UsCiAgICAgICAgICAgICAgICAgZmlsbCA9IFNvdXJjZSksIAogICAgICAgICAgICAgc3RhdCA9ICJpZGVudGl0eSIsIAogICAgICAgICAgICAgd2lkdGggPSAxKSArCiAgICBjb29yZF9wb2xhcigieSIsIHN0YXJ0ID0gMCkgKwogICAgZ2VvbV90ZXh0KGFlcyh4ID0gMSksCiAgICAgICAgICAgICAgY29sb3VyID0gIndoaXRlIiwKICAgICAgICAgICAgICBmb250ZmFjZSA9ICJib2xkIiwKICAgICAgICAgICAgICBwb3NpdGlvbiA9IHBvc2l0aW9uX3N0YWNrKHZqdXN0ID0gMC41KSwKICAgICAgICAgICAgICBzaXplID0gMywKICAgICAgICAgICAgICBzaG93LmxlZ2VuZCA9IEYpICsKICAgIHNjYWxlX2ZpbGxfbWFudWFsKCJsZWdlbmQiLCB2YWx1ZXMgPSBjaGFydENvbHMpICsKICAgIHNjYWxlX2NvbG9yX21hbnVhbCgibGVnZW5kIiwgdmFsdWVzID0gY2hhcnRDb2xzKSArIAogICAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IGNvbW1hKSArCiAgICBnZ3RpdGxlKCdBdXR1bW4gQmFubmVyIENhbXBhaWduIDIwMTc6XG5QYWdlIFZpZXdzIFNvdXJjZXMgZm9yIE1hY2hfbWl0IChDYW1wYWlnbiBvbmx5KScpICsKICAgIHhsYWIoIk91dHRlciA9IENvdW50IikgKyB5bGFiKCIiKSArCiAgICB0aGVtZV9taW5pbWFsKCkgKyAKICAgICMgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCkpICsKICAgIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSkgKwogICAgdGhlbWUobGVnZW5kLnRpdGxlID0gZWxlbWVudF9ibGFuaygpKSArCiAgICB0aGVtZShwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2JsYW5rKCkpICsKICAgIHRoZW1lKHBhbmVsLmdyaWQubWlub3IueSA9IGVsZW1lbnRfYmxhbmsoKSkgKwogICAgdGhlbWUocGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSkKfQpgYGAKClRoZSBmb2xsb3dpbmcgdGFibGUgcHJlc2VudHMgdGhlIGRhdGEgaW4gcmVzcGVjdCB0byB0aGUgQ2FtcGFpZ24gc291cmNlcyBvbmx5OgoKYGBge3IgZWNobyA9IFQsIHdhcm5pbmcgPSAnaGlkZScsIG1lc3NhZ2UgPSBGfQojIyMgLS0tIEZ1bGwgRGF0YXNldCAoVGFibGUgUmVwb3J0KQpkYXRhdGFibGUocGFnZVNvdXJjZVBsb3QpCmBgYAoKIyMjIyAxLjIuMyBCYW5uZXIgQ2xpY2tzOiBDYW1wYWlnbiBUb3RhbCAgCgpUaGUgZm9sbG93aW5nIGNoYXJ0cyByZXByZXNlbnRzIHRoZSBudW1iZXIgb2YgYmFubmVyIGNsaWNrcyBmb3IgZWFjaCBjYW1wYWlnbiBiYW5uZXIgZHVyaW5nIHRoZSBjb3Vyc2Ugb2YgdGhlIGNhbXBhaWduLgoKYGBge3IgZWNobyA9IFQsIHdhcm5pbmcgPSAnaGlkZScsIG1lc3NhZ2UgPSBGfQojIyMgLS0tIFRlbXBvcmFsIEJhbm5lciBDbGlja3MKIyAtIENoYXJ0CmNsaWNrUGxvdFNldCA8LSBkYXRhU2V0ICU+JSAKICBkcGx5cjo6c2VsZWN0KFNvdXJjZSwgRGF0ZSkgJT4lCiAgZHBseXI6OmZpbHRlcihncmVwbCgiX2NsaWNrIiwgU291cmNlKSkgJT4lCiAgZHBseXI6Omdyb3VwX2J5KFNvdXJjZSwgRGF0ZSkgJT4lIAogIGRwbHlyOjpzdW1tYXJpc2UoQ291bnQgPSBuKCkpICU+JQogIGRwbHlyOjphcnJhbmdlKERhdGUpCmdncGxvdChjbGlja1Bsb3RTZXQsIGFlcyh4ID0gRGF0ZSwKICAgICAgICAgICAgICAgICAgICAgICAgIHkgPSBDb3VudCwKICAgICAgICAgICAgICAgICAgICAgICAgIGdyb3VwID0gU291cmNlLAogICAgICAgICAgICAgICAgICAgICAgICAgY29sb3IgPSBTb3VyY2UsCiAgICAgICAgICAgICAgICAgICAgICAgICBmaWxsID0gU291cmNlLAogICAgICAgICAgICAgICAgICAgICAgICAgbGFiZWwgPSBDb3VudCkpICsgCiAgZ2VvbV9wYXRoKHNpemUgPSAuNSkgKwogIHNjYWxlX2ZpbGxfbWFudWFsKCJsZWdlbmQiLCB2YWx1ZXMgPSBjaGFydENvbHMpICsKICBzY2FsZV9jb2xvcl9tYW51YWwoImxlZ2VuZCIsIHZhbHVlcyA9IGNoYXJ0Q29scykgKyAKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gY29tbWEpICsKICBnZ3RpdGxlKCdBdXR1bW4gQmFubmVyIENhbXBhaWduIDIwMTc6IEJhbm5lciBDbGlja3MnKSArCiAgdGhlbWVfbWluaW1hbCgpICsgCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgc2l6ZSA9IDgsIGhqdXN0ID0gMSkpICsKICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCkpICsKICB0aGVtZShsZWdlbmQudGl0bGUgPSBlbGVtZW50X2JsYW5rKCkpICsKICB0aGVtZShwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpKQpgYGAKCmBgYHtyIGVjaG8gPSBULCB3YXJuaW5nID0gJ2hpZGUnLCBtZXNzYWdlID0gRn0KIyMjIC0tLSBGdWxsIERhdGFzZXQgKFRhYmxlIFJlcG9ydCkKZGF0YXRhYmxlKGNsaWNrUGxvdFNldCkKYGBgCgojIyMjIDEuMi40IFBhZ2UgVmlld3M6IENhbXBhaWduIFRvdGFsICAKClRoZSBmb2xsb3dpbmcgY2hhcnRzIHByZXNlbnRzIChhKSB0aGUgbnVtYmVyIG9mIHBhZ2Ugdmlld3MgZm9yIHRoZSB0d28gbGFuZGluZyBwYWdlcyBhbmQgb25lIHJlZ2lzdHJhdGlvbiBwYWdlIGR1cmluZyB0aGUgY291cnNlIG9mIHRoZSBjYW1wYWlnbiwgYW5kIHRoZW4gKGIpIGVuY29tcGFzc2luZyBvbmx5IHBhZ2Ugdmlld3MgdGhhdCB3ZXJlIGdlbmVyYXRlZCBmcm9tIHRoZSBjYW1wYWlnbi4KCmBgYHtyIGVjaG8gPSBULCB3YXJuaW5nID0gJ2hpZGUnLCBtZXNzYWdlID0gRn0KIyMjIC0tLSBUZW1wb3JhbCBQYWdlIFZpZXdzCnBhZ2VQbG90U2V0IDwtIGRhdGFTZXQgJT4lIAogIGRwbHlyOjpzZWxlY3QoUGFnZSwgRGF0ZSkgJT4lCiAgZHBseXI6Omdyb3VwX2J5KFBhZ2UsIERhdGUpICU+JSAKICBkcGx5cjo6c3VtbWFyaXNlKENvdW50ID0gbigpKSAlPiUgCiAgZHBseXI6OmFycmFuZ2UoRGF0ZSkKZ2dwbG90KHBhZ2VQbG90U2V0LCBhZXMoeCA9IERhdGUsCiAgICAgICAgICAgICAgICAgICAgICAgIHkgPSBDb3VudCwKICAgICAgICAgICAgICAgICAgICAgICAgZ3JvdXAgPSBQYWdlLAogICAgICAgICAgICAgICAgICAgICAgICBjb2xvciA9IFBhZ2UsCiAgICAgICAgICAgICAgICAgICAgICAgIGZpbGwgPSBQYWdlLAogICAgICAgICAgICAgICAgICAgICAgICBsYWJlbCA9IENvdW50KSkgKwogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLCAKICAgICAgICAgICBwb3NpdGlvbiA9ICJkb2RnZSIsIAogICAgICAgICAgIHdpZHRoID0gLjIpICsKICBzY2FsZV9maWxsX21hbnVhbCgibGVnZW5kIiwgdmFsdWVzID0gcGFnZUNoYXJ0Q29sb3JzKSArCiAgc2NhbGVfY29sb3JfbWFudWFsKCJsZWdlbmQiLCB2YWx1ZXMgPSBwYWdlQ2hhcnRDb2xvcnMpICsgCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IGNvbW1hKSArCiAgZ2d0aXRsZSgnQXV0dW1uIEJhbm5lciBDYW1wYWlnbiAyMDE3OiBQYWdlIFZpZXdzJykgKwogIHRoZW1lX21pbmltYWwoKSArIAogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIHNpemUgPSA4LCBoanVzdCA9IDEpKSArCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApKSArCiAgdGhlbWUobGVnZW5kLnRpdGxlID0gZWxlbWVudF9ibGFuaygpKSArCiAgdGhlbWUocGFuZWwuZ3JpZC5tYWpvci54ID0gZWxlbWVudF9ibGFuaygpKSArCiAgdGhlbWUocGFuZWwuZ3JpZC5taW5vci54ID0gZWxlbWVudF9ibGFuaygpKSArCiAgdGhlbWUocGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSkKCiMjIyAtLS0gVGVtcG9yYWwgUGFnZSBWaWV3czogQ2FtcGFpZ24gb25seQpwYWdlUGxvdFNldCA8LSBkYXRhU2V0ICU+JSAKICBmaWx0ZXIoIShkYXRhU2V0JFNvdXJjZSAlaW4lICdPdGhlcicgfCBkYXRhU2V0JFNvdXJjZSAlaW4lICdVbmtub3duJykpICU+JQogIGRwbHlyOjpzZWxlY3QoUGFnZSwgRGF0ZSkgJT4lCiAgZHBseXI6Omdyb3VwX2J5KFBhZ2UsIERhdGUpICU+JSAKICBkcGx5cjo6c3VtbWFyaXNlKENvdW50ID0gbigpKSAlPiUgCiAgZHBseXI6OmFycmFuZ2UoRGF0ZSkKZ2dwbG90KHBhZ2VQbG90U2V0LCBhZXMoeCA9IERhdGUsCiAgICAgICAgICAgICAgICAgICAgICAgIHkgPSBDb3VudCwKICAgICAgICAgICAgICAgICAgICAgICAgZ3JvdXAgPSBQYWdlLAogICAgICAgICAgICAgICAgICAgICAgICBjb2xvciA9IFBhZ2UsCiAgICAgICAgICAgICAgICAgICAgICAgIGZpbGwgPSBQYWdlLAogICAgICAgICAgICAgICAgICAgICAgICBsYWJlbCA9IENvdW50KSkgKwogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLCAKICAgICAgICAgICBwb3NpdGlvbiA9ICJkb2RnZSIsIAogICAgICAgICAgIHdpZHRoID0gLjIpICsKICBzY2FsZV9maWxsX21hbnVhbCgibGVnZW5kIiwgdmFsdWVzID0gcGFnZUNoYXJ0Q29sb3JzKSArCiAgc2NhbGVfY29sb3JfbWFudWFsKCJsZWdlbmQiLCB2YWx1ZXMgPSBwYWdlQ2hhcnRDb2xvcnMpICsgCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IGNvbW1hKSArCiAgZ2d0aXRsZSgnQXV0dW1uIEJhbm5lciBDYW1wYWlnbiAyMDE3OiBQYWdlIFZpZXdzIChDYW1wYWlnbiBvbmx5KScpICsKICB0aGVtZV9taW5pbWFsKCkgKyAKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCBzaXplID0gOCwgaGp1c3QgPSAxKSkgKwogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSkgKwogIHRoZW1lKGxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSkgKwogIHRoZW1lKHBhbmVsLmdyaWQubWFqb3IueCA9IGVsZW1lbnRfYmxhbmsoKSkgKwogIHRoZW1lKHBhbmVsLmdyaWQubWlub3IueCA9IGVsZW1lbnRfYmxhbmsoKSkgKwogIHRoZW1lKHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCkpCmBgYAoKVGhlIGZvbGxvd2luZyB0YWJsZSBwcmVzZW50cyB0aGUgZGF0YSBpbiByZXNwZWN0IHRvIHRoZSBDYW1wYWlnbiBzb3VyY2VzIG9ubHk6CgpgYGB7ciBlY2hvID0gVCwgd2FybmluZyA9ICdoaWRlJywgbWVzc2FnZSA9IEZ9CiMjIyAtLS0gRnVsbCBEYXRhc2V0IChUYWJsZSBSZXBvcnQpCmRhdGF0YWJsZShwYWdlUGxvdFNldCkKYGBgCgojIyAyLiBDYW1wYWlnbiBVc2VyIFJlZ2lzdHJhdGlvbnMKCiMjIyAyLiAwIFJlZ2lzdHJhdGlvbnMKCmBgYHtyIGVjaG8gPSBULCB3YXJuaW5nID0gJ2hpZGUnLCBtZXNzYWdlID0gRn0KIyMjIC0tLSBDYW1wYWlnbiBVc2VyIFJlZ2lzdHJhdGlvbnMKbEYgPC0gbGlzdC5maWxlcyhwYXRoID0gIi4vX2RhaWx5VXBkYXRlREFUQS8iKQpsRiA8LSBsRltncmVwbCgndXNlclJlZ2lzdHJhdGlvbnMnLCBsRiwgZml4ZWQgPSBUKV0KdXNlclJlZyA8LSByZWFkLnRhYmxlKHBhc3RlKCIuL19kYWlseVVwZGF0ZURBVEEvIiwgbEYsIHNlcCA9ICIiKSwKICAgICAgICAgICAgICAgICAgICAgIHF1b3RlID0gIiIsCiAgICAgICAgICAgICAgICAgICAgICBzZXAgPSAiXHQiLAogICAgICAgICAgICAgICAgICAgICAgaGVhZGVyID0gVCwKICAgICAgICAgICAgICAgICAgICAgIGNoZWNrLm5hbWVzID0gRiwKICAgICAgICAgICAgICAgICAgICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGKQp1c2VyUmVnJHRpbWVzdGFtcCA8LSBhcy5jaGFyYWN0ZXIodXNlclJlZyR0aW1lc3RhbXApCnVzZXJSZWckdGltZXN0YW1wIDwtIHNhcHBseSh1c2VyUmVnJHRpbWVzdGFtcCwgZnVuY3Rpb24oeCkgewogIHkgPC0gc3Vic3RyKHgsIDEsIDQpCiAgbSA8LSBzdWJzdHIoeCwgNSwgNikKICBkIDwtIHN1YnN0cih4LCA3LCA4KQogIHBhcnQxRGF0ZSA8LSBwYXN0ZSh5LCBtLCBkLCBzZXAgPSAiLSIpCiAgaHIgPC0gc3Vic3RyKHgsIDksIDEwKQogIG1pIDwtIHN1YnN0cih4LCAxMSwgMTIpCiAgc2UgPC0gc3Vic3RyKHgsIDEzLCAxNCkKICBwYXJ0MkRhdGUgPC0gcGFzdGUoaHIsIG1pLCBzZSwgc2VwID0gIjoiKQogIHBhc3RlKHBhcnQxRGF0ZSwgcGFydDJEYXRlLCBzZXAgPSAiICIpCn0pCnVzZXJSZWckdGltZXN0YW1wIDwtIGFzLlBPU0lYY3QodXNlclJlZyR0aW1lc3RhbXAsIHR6ID0gIlVUQyIpCnRpbWVEaWZmIDwtIAogIGFzLlBPU0lYY3QoYXMuY2hhcmFjdGVyKFN5cy50aW1lKCkpLCB0eiA9ICJVVEMiKSAtIGFzLlBPU0lYY3QoYXMuY2hhcmFjdGVyKFN5cy50aW1lKCkpLCB0eiA9ICJFdXJvcGUvQmVybGluIikKdXNlclJlZyR0aW1lc3RhbXAgPC0gYXMuY2hhcmFjdGVyKHVzZXJSZWckdGltZXN0YW1wICsgdGltZURpZmYpCnVzZXJSZWckdGltZXN0YW1wIDwtIHNhcHBseSh1c2VyUmVnJHRpbWVzdGFtcCwgZnVuY3Rpb24oeCkgewogIHkgPC0gc3Vic3RyKHgsIDEsIDQpCiAgbSA8LSBzdWJzdHIoeCwgNiwgNykKICBkIDwtIHN1YnN0cih4LCA5LCAxMCkgCiAgcGFzdGUoeSwgbSwgZCwgc2VwID0gIi0iKQp9KQp1c2VyUmVnIDwtIHVzZXJSZWcgJT4lIAogIGRwbHlyOjpzZWxlY3QoaWQsIGV2ZW50X3VzZXJJZCwgdGltZXN0YW1wLCBldmVudF9pc1NlbGZNYWRlLCBldmVudF9jYW1wYWlnbikgJT4lIAogIGZpbHRlcihldmVudF9pc1NlbGZNYWRlID09IDEgJiBncmVwbCgid21kZV9hYmMyMDE3IiwgZXZlbnRfY2FtcGFpZ24pKQpwcmludChwYXN0ZShkaW0odXNlclJlZylbMV0sICIgdXNlcnMgaGF2ZSByZWdpc3RlcmVkIHZpYSB0aGUgQ2FtcGFpZ24uIikpCmBgYAoKIyMjIDIuIDFBIFVzZXIgUmVnaXN0cmF0aW9ucyBwZXIgQ2FtcGFpZ24gKGRhaWx5KQoKYGBge3IgZWNobyA9IFQsIHdhcm5pbmcgPSAnaGlkZScsIG1lc3NhZ2UgPSBGfQpyZWdQbG90U2V0IDwtIHVzZXJSZWcgJT4lIAogIGdyb3VwX2J5KGV2ZW50X2NhbXBhaWduLCB0aW1lc3RhbXApICU+JSAKICBzdW1tYXJpc2UoUmVnaXN0cmF0aW9ucyA9IG4oKSkgJT4lIAogIGFycmFuZ2UodGltZXN0YW1wKQpjb2xuYW1lcyhyZWdQbG90U2V0KSA8LSBjKCdDYW1wYWlnbicsICdEYXRlJywgJ1JlZ2lzdHJhdGlvbnMnKQpyZWdQbG90U2V0JENhbXBhaWduIDwtIGZhY3Rvcih0b3VwcGVyKGdzdWIoIndtZGVfYWJjMjAxN18iLCAiIiwgcmVnUGxvdFNldCRDYW1wYWlnbikpKQpnZ3Bsb3QocmVnUGxvdFNldCwgYWVzKHggPSBEYXRlLAogICAgICAgICAgICAgICAgICAgICAgIHkgPSBSZWdpc3RyYXRpb25zLAogICAgICAgICAgICAgICAgICAgICAgIGdyb3VwID0gQ2FtcGFpZ24sCiAgICAgICAgICAgICAgICAgICAgICAgY29sb3IgPSBDYW1wYWlnbiwKICAgICAgICAgICAgICAgICAgICAgICBmaWxsID0gQ2FtcGFpZ24pKSArCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIAogICAgICAgICAgIHBvc2l0aW9uID0gImRvZGdlIiwgCiAgICAgICAgICAgd2lkdGggPSAuNSkgKwogIHNjYWxlX2ZpbGxfbWFudWFsKCJsZWdlbmQiLCB2YWx1ZXMgPSBjYW1wYWlnbkNoYXJ0Q29sb3JzKSArCiAgc2NhbGVfY29sb3JfbWFudWFsKCJsZWdlbmQiLCB2YWx1ZXMgPSBjYW1wYWlnbkNoYXJ0Q29sb3JzKSArIAogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBjb21tYSkgKwogIGdndGl0bGUoJ0F1dHVtbiBCYW5uZXIgQ2FtcGFpZ24gMjAxNzogVXNlciBSZWdpc3RyYXRpb25zIChkYWlseSknKSArCiAgdGhlbWVfbWluaW1hbCgpICsgCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgc2l6ZSA9IDgsIGhqdXN0ID0gMSkpICsKICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCkpICsKICB0aGVtZShsZWdlbmQudGl0bGUgPSBlbGVtZW50X2JsYW5rKCkpICsKICB0aGVtZShwYW5lbC5ncmlkLm1ham9yLnggPSBlbGVtZW50X2JsYW5rKCkpICsKICB0aGVtZShwYW5lbC5ncmlkLm1pbm9yLnggPSBlbGVtZW50X2JsYW5rKCkpICsKICB0aGVtZShwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpKQpgYGAKCmBgYHtyIGVjaG8gPSBULCB3YXJuaW5nID0gJ2hpZGUnLCBtZXNzYWdlID0gRn0KIyMjIC0tLSBGdWxsIERhdGFzZXQgKFRhYmxlIFJlcG9ydCkKZGF0YXRhYmxlKHJlZ1Bsb3RTZXQpCmBgYAoKIyMjIDIuIDFCIFVzZXIgUmVnaXN0cmF0aW9ucyBwZXIgQ2FtcGFpZ24gKHRvdGFscykKCmBgYHtyIGVjaG8gPSBULCB3YXJuaW5nID0gJ2hpZGUnLCBtZXNzYWdlID0gRn0KcmVnUGxvdFNldFRvdGFsIDwtIHJlZ1Bsb3RTZXQgJT4lIAogIGdyb3VwX2J5KENhbXBhaWduKSAlPiUgCiAgc3VtbWFyaXNlKFJlZ2lzdHJhdGlvbnMgPSBzdW0oUmVnaXN0cmF0aW9ucykpICU+JSAKICBhcnJhbmdlKENhbXBhaWduKQpnZ3Bsb3QocmVnUGxvdFNldFRvdGFsLCBhZXMoeCA9IENhbXBhaWduLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgeSA9IFJlZ2lzdHJhdGlvbnMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBncm91cCA9IENhbXBhaWduLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sb3IgPSBDYW1wYWlnbiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZpbGwgPSBDYW1wYWlnbiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxhYmVsID0gUmVnaXN0cmF0aW9ucykpICsKICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IiwgCiAgICAgICAgICAgcG9zaXRpb24gPSAiZG9kZ2UiLCAKICAgICAgICAgICB3aWR0aCA9IC41KSArIAogIGdlb21fbGFiZWwoZmlsbCA9ICJ3aGl0ZSIsIGNvbG9yID0gImJsYWNrIikgKwogIHNjYWxlX2ZpbGxfbWFudWFsKCJsZWdlbmQiLCB2YWx1ZXMgPSBjYW1wYWlnbkNoYXJ0Q29sb3JzKSArCiAgc2NhbGVfY29sb3JfbWFudWFsKCJsZWdlbmQiLCB2YWx1ZXMgPSBjYW1wYWlnbkNoYXJ0Q29sb3JzKSArIAogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBjb21tYSkgKwogIGdndGl0bGUoJ0F1dHVtbiBCYW5uZXIgQ2FtcGFpZ24gMjAxNzogVXNlciBSZWdpc3RyYXRpb25zICh0b3RhbHMpJykgKwogIHRoZW1lX21pbmltYWwoKSArIAogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIHNpemUgPSA4LCBoanVzdCA9IDEpKSArCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApKSArCiAgdGhlbWUobGVnZW5kLnRpdGxlID0gZWxlbWVudF9ibGFuaygpKSArCiAgdGhlbWUocGFuZWwuZ3JpZC5tYWpvci54ID0gZWxlbWVudF9ibGFuaygpKSArCiAgdGhlbWUocGFuZWwuZ3JpZC5taW5vci54ID0gZWxlbWVudF9ibGFuaygpKSArCiAgdGhlbWUocGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSkKYGBgCgojIyMgMi4gMiBUb3RhbCBVc2VyIFJlZ2lzdHJhdGlvbnMgZGFpbHkKCmBgYHtyIGVjaG8gPSBULCB3YXJuaW5nID0gJ2hpZGUnLCBtZXNzYWdlID0gRn0KcmVnUGxvdFNldERhaWx5IDwtIHVzZXJSZWcgJT4lIAogIGRwbHlyOjpmaWx0ZXIoZXZlbnRfaXNTZWxmTWFkZSA9PSAxICYgZ3JlcGwoIndtZGVfYWJjMjAxNyIsIGV2ZW50X2NhbXBhaWduKSkgJT4lIAogIGdyb3VwX2J5KHRpbWVzdGFtcCkgJT4lIAogIHN1bW1hcmlzZShSZWdpc3RyYXRpb25zID0gbigpKSAlPiUgCiAgYXJyYW5nZSh0aW1lc3RhbXApCmNvbG5hbWVzKHJlZ1Bsb3RTZXREYWlseSkgPC0gYygnRGF0ZScsICdSZWdpc3RyYXRpb25zJykKZ2dwbG90KHJlZ1Bsb3RTZXREYWlseSwgYWVzKHggPSBEYXRlLAogICAgICAgICAgICAgICAgICAgICAgIHkgPSBSZWdpc3RyYXRpb25zLCAKICAgICAgICAgICAgICAgICAgICAgICBsYWJlbCA9IFJlZ2lzdHJhdGlvbnMpKSArCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIAogICAgICAgICAgIHBvc2l0aW9uID0gImRvZGdlIiwgCiAgICAgICAgICAgd2lkdGggPSAuNSwgCiAgICAgICAgICAgZmlsbCA9ICJkYXJrYmx1ZSIsIAogICAgICAgICAgIGNvbG9yID0gImRhcmtibHVlIikgKyAKICBnZW9tX2xhYmVsKCkgKwogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBjb21tYSkgKwogIGdndGl0bGUoJ0F1dHVtbiBCYW5uZXIgQ2FtcGFpZ24gMjAxNzogVXNlciBSZWdpc3RyYXRpb25zJykgKwogIHRoZW1lX21pbmltYWwoKSArIAogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIHNpemUgPSA4LCBoanVzdCA9IDEpKSArCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApKSArCiAgdGhlbWUobGVnZW5kLnRpdGxlID0gZWxlbWVudF9ibGFuaygpKSArCiAgdGhlbWUocGFuZWwuZ3JpZC5tYWpvci54ID0gZWxlbWVudF9ibGFuaygpKSArCiAgdGhlbWUocGFuZWwuZ3JpZC5taW5vci54ID0gZWxlbWVudF9ibGFuaygpKSArCiAgdGhlbWUocGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSkKYGBgCgpgYGB7ciBlY2hvID0gVCwgd2FybmluZyA9ICdoaWRlJywgbWVzc2FnZSA9IEZ9CiMjIyAtLS0gRnVsbCBEYXRhc2V0IChUYWJsZSBSZXBvcnQpCmRhdGF0YWJsZShyZWdQbG90U2V0KQpgYGAKCiMjIDMuIENhbXBhaWduIEd1aWRlZCBUb3VyCgojIyMgMy4gMUEgR3VpZGVkIFRvdXIgUG9pbnQgb2YgRXhpdCAoZGFpbHkpCgpgYGB7ciBlY2hvID0gVCwgd2FybmluZyA9ICdoaWRlJywgbWVzc2FnZSA9IEZ9CiMjIyAtLS0gQ2FtcGFpZ24gVXNlciBSZWdpc3RyYXRpb25zCmdUb3VyRGF0YSA8LSByZWFkLnRhYmxlKCIuL19kYWlseVVwZGF0ZURBVEEvYWJjMjAxN19ndWlkZWRUb3Vycy50c3YiLAogICAgICAgICAgICAgICAgICAgICAgICBxdW90ZSA9ICIiLAogICAgICAgICAgICAgICAgICAgICAgICBzZXAgPSAiXHQiLAogICAgICAgICAgICAgICAgICAgICAgICBoZWFkZXIgPSBULAogICAgICAgICAgICAgICAgICAgICAgICBjaGVjay5uYW1lcyA9IEYsCiAgICAgICAgICAgICAgICAgICAgICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGKQojIC0gY2xlYW4gdXA6IGdUb3VyRGF0YQpnVG91ckRhdGEgPC0gZ1RvdXJEYXRhW3doaWNoKCFkdXBsaWNhdGVkKGdUb3VyRGF0YSRldmVudF91c2VySWQpKSwgXQpnVG91ckRhdGEgPC0gZ1RvdXJEYXRhW3doaWNoKCEoZ1RvdXJEYXRhJGV2ZW50X3VzZXJJZCA9PSAwKSksIF0KCmdUb3VyRGF0YSA8LSBnVG91ckRhdGFbd2hpY2goZ1RvdXJEYXRhJGV2ZW50X3VzZXJJZCAlaW4lIHVzZXJSZWckZXZlbnRfdXNlcklkKSwgXQoKZ1RvdXJEYXRhJHRpbWVzdGFtcCA8LSBhcy5jaGFyYWN0ZXIoZ1RvdXJEYXRhJHRpbWVzdGFtcCkKZ1RvdXJEYXRhJHRpbWVzdGFtcCA8LSBzYXBwbHkoZ1RvdXJEYXRhJHRpbWVzdGFtcCwgZnVuY3Rpb24oeCkgewogIHkgPC0gc3Vic3RyKHgsIDEsIDQpCiAgbSA8LSBzdWJzdHIoeCwgNSwgNikKICBkIDwtIHN1YnN0cih4LCA3LCA4KQogIHBhcnQxRGF0ZSA8LSBwYXN0ZSh5LCBtLCBkLCBzZXAgPSAiLSIpCiAgaHIgPC0gc3Vic3RyKHgsIDksIDEwKQogIG1pIDwtIHN1YnN0cih4LCAxMSwgMTIpCiAgc2UgPC0gc3Vic3RyKHgsIDEzLCAxNCkKICBwYXJ0MkRhdGUgPC0gcGFzdGUoaHIsIG1pLCBzZSwgc2VwID0gIjoiKQogIHBhc3RlKHBhcnQxRGF0ZSwgcGFydDJEYXRlLCBzZXAgPSAiICIpCn0pCmdUb3VyRGF0YSR0aW1lc3RhbXAgPC0gYXMuUE9TSVhjdChnVG91ckRhdGEkdGltZXN0YW1wLCB0eiA9ICJVVEMiKQp0aW1lRGlmZiA8LSAKICBhcy5QT1NJWGN0KGFzLmNoYXJhY3RlcihTeXMudGltZSgpKSwgdHogPSAiVVRDIikgLSBhcy5QT1NJWGN0KGFzLmNoYXJhY3RlcihTeXMudGltZSgpKSwgdHogPSAiRXVyb3BlL0JlcmxpbiIpCmdUb3VyRGF0YSR0aW1lc3RhbXAgPC0gYXMuY2hhcmFjdGVyKGdUb3VyRGF0YSR0aW1lc3RhbXAgKyB0aW1lRGlmZikKZ1RvdXJEYXRhJHRpbWVzdGFtcCA8LSBzYXBwbHkoZ1RvdXJEYXRhJHRpbWVzdGFtcCwgZnVuY3Rpb24oeCkgewogIHkgPC0gc3Vic3RyKHgsIDEsIDQpCiAgbSA8LSBzdWJzdHIoeCwgNiwgNykKICBkIDwtIHN1YnN0cih4LCA5LCAxMCkgCiAgcGFzdGUoeSwgbSwgZCwgc2VwID0gIi0iKQp9KQpnVG91ckRhdGEgPC0gZ1RvdXJEYXRhICU+JQogICBmaWx0ZXIoZXZlbnRfdG91ciAlaW4lICdlaW5mdWhydW5nJykKcGxvdEdUb3VyRGF0YSA8LSBnVG91ckRhdGEgJT4lIAogIGdyb3VwX2J5KGV2ZW50X3N0ZXAsIHRpbWVzdGFtcCkgJT4lIAogIHN1bW1hcmlzZShDb3VudCA9IG4oKSkKY29sbmFtZXMocGxvdEdUb3VyRGF0YSkgPC0gYygnVG91ciBTdGVwJywgJ0RhdGUnLCAnQ291bnQnKQpnZ3Bsb3QocGxvdEdUb3VyRGF0YSwgYWVzKHggPSBEYXRlLAogICAgICAgICAgICAgICAgICAgICAgeSA9IENvdW50LAogICAgICAgICAgICAgICAgICAgICAgIGdyb3VwID0gYFRvdXIgU3RlcGAsCiAgICAgICAgICAgICAgICAgICAgICAgY29sb3IgPSBgVG91ciBTdGVwYCwKICAgICAgICAgICAgICAgICAgICAgICBmaWxsID0gYFRvdXIgU3RlcGApKSArCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIAogICAgICAgICAgIHBvc2l0aW9uID0gImRvZGdlIiwgCiAgICAgICAgICAgd2lkdGggPSAuNSkgKwogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBjb21tYSkgKwogIGdndGl0bGUoJ0F1dHVtbiBCYW5uZXIgQ2FtcGFpZ24gMjAxNzogR3VpZGVkIFRvdXIgU3RlcHMgKGRhaWx5KScpICsKICB0aGVtZV9taW5pbWFsKCkgKyAKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCBzaXplID0gOCwgaGp1c3QgPSAxKSkgKwogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSkgKwogIHRoZW1lKGxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSkgKwogIHRoZW1lKHBhbmVsLmdyaWQubWFqb3IueCA9IGVsZW1lbnRfYmxhbmsoKSkgKwogIHRoZW1lKHBhbmVsLmdyaWQubWlub3IueCA9IGVsZW1lbnRfYmxhbmsoKSkgKwogIHRoZW1lKHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCkpCmBgYAoKYGBge3IgZWNobyA9IFQsIHdhcm5pbmcgPSAnaGlkZScsIG1lc3NhZ2UgPSBGfQojIyMgLS0tIEZ1bGwgRGF0YXNldCAoVGFibGUgUmVwb3J0KQpkYXRhdGFibGUocGxvdEdUb3VyRGF0YSkKYGBgCgojIyMgMy4gMUIgR3VpZGVkIFRvdXIgUG9pbnQgb2YgRXhpdCAodG90YWxzKQoKYGBge3IgZWNobyA9IFQsIHdhcm5pbmcgPSAnaGlkZScsIG1lc3NhZ2UgPSBGfQojIyMgLS0tIENhbXBhaWduIFVzZXIgUmVnaXN0cmF0aW9ucwpwbG90R1RvdXJEYXRhVG90YWwgPC0gcGxvdEdUb3VyRGF0YSAlPiUgCiAgZ3JvdXBfYnkoYFRvdXIgU3RlcGApICAlPiUKICBzdW1tYXJpc2UoQ291bnQgPSBzdW0oQ291bnQpKSAlPiUgCiAgYXJyYW5nZShkZXNjKENvdW50KSkKcGxvdEdUb3VyRGF0YVRvdGFsJGBUb3VyIFN0ZXBgIDwtIAogIGZhY3RvcihwbG90R1RvdXJEYXRhVG90YWwkYFRvdXIgU3RlcGAsIAogICAgICAgICBsZXZlbHMgPSBwbG90R1RvdXJEYXRhVG90YWwkYFRvdXIgU3RlcGBbb3JkZXIocGxvdEdUb3VyRGF0YVRvdGFsJENvdW50KV0pCmdncGxvdChwbG90R1RvdXJEYXRhVG90YWwsIGFlcyh4ID0gYFRvdXIgU3RlcGAsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB5ID0gQ291bnQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBncm91cCA9IGBUb3VyIFN0ZXBgLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sb3IgPSBgVG91ciBTdGVwYCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZpbGwgPSBgVG91ciBTdGVwYCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxhYmVsID0gQ291bnQpKSArCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIAogICAgICAgICAgIHBvc2l0aW9uID0gImRvZGdlIiwgCiAgICAgICAgICAgd2lkdGggPSAuNSkgKwogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBjb21tYSkgKyAKICBnZW9tX2xhYmVsKGZpbGwgPSAid2hpdGUiLCBjb2xvciA9ICJibGFjayIpICsKICBnZ3RpdGxlKCdBdXR1bW4gQmFubmVyIENhbXBhaWduIDIwMTc6IEd1aWRlZCBUb3VyIFN0ZXBzICh0b3RhbHMpJykgKwogIHRoZW1lX21pbmltYWwoKSArIAogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIHNpemUgPSA4LCBoanVzdCA9IDEpKSArCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApKSArCiAgdGhlbWUobGVnZW5kLnRpdGxlID0gZWxlbWVudF9ibGFuaygpKSArCiAgdGhlbWUocGFuZWwuZ3JpZC5tYWpvci54ID0gZWxlbWVudF9ibGFuaygpKSArCiAgdGhlbWUocGFuZWwuZ3JpZC5taW5vci54ID0gZWxlbWVudF9ibGFuaygpKSArCiAgdGhlbWUocGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSkgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICdOb25lJykKYGBgCgpOdW1iZXIgb2YgdXNlcnMgbm90IGV4aXRpbmcgdGhlIEd1aWRlZCBUb3VyOgoKYGBge3IgZWNobyA9IFR9Cm5SZWdpc3RlcmVkIDwtIGRpbSh1c2VyUmVnKVsxXQpuRXhpdGVkR1QgPC0gZGltKGdUb3VyRGF0YSlbMV0KcHJpbnQocGFzdGUoblJlZ2lzdGVyZWQgLSBuRXhpdGVkR1QsIAogICAgICAgICAgICAiIHVzZXJzIG91dCBvZiAiLCAKICAgICAgICAgICAgblJlZ2lzdGVyZWQsIAogICAgICAgICAgICAiICgiLCByb3VuZCgoblJlZ2lzdGVyZWQgLSBuRXhpdGVkR1QpL25SZWdpc3RlcmVkKjEwMCwgMiksICIlKSBkaWQgbm90IGV4aXQgdGhlIENhbXBhaWduIEd1aWRlZCBUb3VyIiwgCiAgICAgICAgICAgIHNlcCA9ICIiKSkKYGBgCgojIyMgMy4gMiBFeGl0aW5nIHRoZSBHdWlkZWQgVG91ciBhdCB0aGUgSW5pdGlhbCBTdGVwCgpIb3cgbWFueSB1c2VycyBleGl0IHRoZSBHdWlkZWQgVG91ciBhdCB0aGUgaW5pdGlhbCBzdGVwPwoqKk5PVEU6KiogVGhlIGBPdGhlcnNgIGNhdGVnb3J5IGVuY29tcGFzc2VzIGFsbCB1c2VycyB3aG8gZGlkIG5vdCBleGl0IGF0IHRoZSBpbml0aWFsIHN0ZXA7IHRoZXkgaGF2ZSBlaXRoZXIgZXhpdGVkIHRoZSBHdWlkZWQgVG91ciBsYXRlciBvbiBvciBjb21wbGV0ZWQgdGhlIHRvdXIuCgpgYGB7ciBlY2hvID0gVCwgd2FybmluZyA9ICdoaWRlJywgbWVzc2FnZSA9IEZ9CmV4R1RkYXRhIDwtIHBsb3RHVG91ckRhdGEgJT4lIAogIGdyb3VwX2J5KGBUb3VyIFN0ZXBgKSAlPiUgCiAgc3VtbWFyaXNlKENvdW50ID0gc3VtKENvdW50KSkKZXhHVDEgPC0gZXhHVGRhdGEkQ291bnRbZXhHVGRhdGEkYFRvdXIgU3RlcGAgJWluJSAnd2lsbGtvbW1lbiddCmV4R1QyIDwtIG5SZWdpc3RlcmVkIC0gZXhHVDEKZXhHVG91clN0ZXAxIDwtIHBhc3RlKGV4R1QxLCAiICgiLCByb3VuZChleEdUMS8oZXhHVDEgKyBleEdUMikqMTAwLCAyKSwgIiUpIiwgc2VwID0gIiIpCmV4R1RvdXJTdGVwMiA8LSBwYXN0ZShleEdUMiwgIiAoIiwgcm91bmQoZXhHVDIvKGV4R1QxICsgZXhHVDIpKjEwMCwgMiksICIlKSIsIHNlcCA9ICIiKQpleEdUb3VyMSA8LSBkYXRhLmZyYW1lKGBVc2VycyB3aG8gZXhpdGVkIGF0IFN0ZXAgMWAgPSBleEdUb3VyU3RlcDEsIAogICAgICAgICAgICAgICAgICAgICAgIGBPdGhlcnNgID0gZXhHVG91clN0ZXAyLAogICAgICAgICAgICAgICAgICAgICAgIGNoZWNrLm5hbWVzID0gRiwKICAgICAgICAgICAgICAgICAgICAgICBzdHJpbmdzQXNGYWN0b3JzID0gRikKa25pdHI6OmthYmxlKGV4R1RvdXIxLCBmb3JtYXQgPSAiaHRtbCIpICU+JSAKICBrYWJsZV9zdHlsaW5nKGZ1bGxfd2lkdGggPSBGLCBwb3NpdGlvbiA9ICJsZWZ0IikKYGBgCgojIyA0LiBVc2VyIEVkaXRzCgojIyMgNC4gMCBQcm9wb3J0aW9uIG9mIEFjdGl2ZSBVc2VycwoKYGBge3IgZWNobyA9IFQsIHdhcm5pbmcgPSAnaGlkZScsIG1lc3NhZ2UgPSBGfQojIC0gZGV0ZXJtaW5lIHVzZXJJRHMKdXNlclJlZyA8LSB1c2VyUmVnICU+JSAKICBkcGx5cjo6c2VsZWN0KGlkLCBldmVudF91c2VySWQsIHRpbWVzdGFtcCwgZXZlbnRfaXNTZWxmTWFkZSwgZXZlbnRfY2FtcGFpZ24pICU+JSAKICBmaWx0ZXIoZXZlbnRfaXNTZWxmTWFkZSA9PSAxICYgZ3JlcGwoIndtZGVfYWJjMjAxNyIsIGV2ZW50X2NhbXBhaWduKSkKdXNlcklEcyA8LSB1c2VyUmVnJGV2ZW50X3VzZXJJZAplZGl0RGF0YSA8LSByZWFkLnRhYmxlKCIuL19kYWlseVVwZGF0ZURBVEEvYWJjMjAxN191c2VyRWRpdHMudHN2IiwKICAgICAgICAgICAgICAgICAgICAgICBzZXAgPSAiXHQiLAogICAgICAgICAgICAgICAgICAgICAgIHF1b3RlID0gIiIsCiAgICAgICAgICAgICAgICAgICAgICAgaGVhZGVyID0gVCwKICAgICAgICAgICAgICAgICAgICAgICBjaGVjay5uYW1lcyA9IEYsCiAgICAgICAgICAgICAgICAgICAgICAgc3RyaW5nc0FzRmFjdG9ycyA9IEYpICU+JSAKICBmaWx0ZXIocmV2X3VzZXIgJWluJSB1c2VySURzKQpwbEVkaXREYXRhIDwtIGVkaXREYXRhICU+JSAKICBncm91cF9ieShlZGl0cykgJT4lIAogIHN1bW1hcmlzZShDb3VudCA9IG4oKSkKY29sbmFtZXMocGxFZGl0RGF0YSkgPC0gYygnTnVtLkVkaXRzJywgJ0NvdW50JykKcHJpbnQocGFzdGUoc3VtKHBsRWRpdERhdGEkQ291bnQpLAogICAgICAgICAgICAiIG91dCBvZiAiLAogICAgICAgICAgICBkaW0odXNlclJlZylbMV0sCiAgICAgICAgICAgICIgcmVnaXN0ZXJlZCB1c2VycyAoIiwKICAgICAgICAgICAgcm91bmQoc3VtKHBsRWRpdERhdGEkQ291bnQpL2RpbSh1c2VyUmVnKVsxXSoxMDAsIDIpLAogICAgICAgICAgICAiJSkgaGF2ZSBtYWRlIGF0IGxlYXN0IG9uZSBlZGl0LiIsIAogICAgICAgICAgICBzZXAgPSAiIikKICAgICAgKQpgYGAKCiMjIyA0LiAxIFVzZXIgRWRpdHMgRGlzdHJpYnV0aW9uCgpUaGUgeS1heGlzIHJlcHJlc2VudHMgYGxvZyhOdW1iZXIgb2YgdXNlcnMpYCB0byBtYWtlIHRoZSBsaW5lIHBsb3QgbW9yZSByZWFkYWJsZSwgd2hpbGUgdGhlIGRhdGEgbGFiZWxzIHByZXNlbnQgZXhhY3QgdXNlciBjb3VudHMgYWxvbmdzaWRlIHRoZSBudW1iZXIgb2YgZWRpdHMgbWFkZS4gCgpgYGB7ciBlY2hvID0gVCwgd2FybmluZyA9ICdoaWRlJywgbWVzc2FnZSA9IEZ9CmdncGxvdChwbEVkaXREYXRhLCBhZXMoeCA9IGBOdW0uRWRpdHNgLAogICAgICAgICAgICAgICAgICAgICAgeSA9IGxvZyhDb3VudCksIAogICAgICAgICAgICAgICAgICAgICAgbGFiZWwgPSBwYXN0ZShDb3VudCwgIiAoIiwgYE51bS5FZGl0c2AsICIgZWRpdHMpIiwgc2VwID0gIiIpKQogICAgICAgKSArCiAgZ2VvbV9wYXRoKHNpemUgPSAuMjUsIGNvbG9yID0gImRhcmtibHVlIikgKwogIGdlb21fcG9pbnQoc2l6ZSA9IDEuNSwgY29sb3IgPSAiZGFya2JsdWUiKSArCiAgZ2VvbV9wb2ludChzaXplID0gMSwgY29sb3IgPSAid2hpdGUiKSArIAogIGdlb21fdGV4dF9yZXBlbChzaXplID0gMykgKwogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBjb21tYSkgKwogIHlsYWIoJ2xvZyhOdW0uIG9mIFVzZXJzKScpICsgeGxhYignTnVtYmVyIG9mIEVkaXRzJykgKwogIGdndGl0bGUoJ0F1dHVtbiBCYW5uZXIgQ2FtcGFpZ24gMjAxNzogVXNlciBFZGl0cyBEaXN0cmlidXRpb24nKSArCiAgdGhlbWVfbWluaW1hbCgpICsgCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSAwLCBzaXplID0gOCwgaGp1c3QgPSAxKSkgKwogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSkgKwogIHRoZW1lKGxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSkgKwogIHRoZW1lKHBhbmVsLmdyaWQubWFqb3IueCA9IGVsZW1lbnRfYmxhbmsoKSkgKwogIHRoZW1lKHBhbmVsLmdyaWQubWlub3IueCA9IGVsZW1lbnRfYmxhbmsoKSkgKwogIHRoZW1lKHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCkpCmBgYAoKIyMjIDQuIDIgVXNlciBFZGl0cyBwZXIgQ2FtcGFpZ24KCmBgYHtyIGVjaG8gPSBULCB3YXJuaW5nID0gJ2hpZGUnLCBtZXNzYWdlID0gRn0KZWRpdENhbXBhaWduIDwtIGxlZnRfam9pbihlZGl0RGF0YSwgdXNlclJlZywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgYnkgPSBjKCJyZXZfdXNlciIgPSAiZXZlbnRfdXNlcklkIikpICU+JSAKICBncm91cF9ieShldmVudF9jYW1wYWlnbikgJT4lIAogIHN1bW1hcmlzZShFZGl0cyA9IHN1bShlZGl0cykpCmNvbG5hbWVzKGVkaXRDYW1wYWlnbikgPC0gYygnQ2FtcGFpZ24nLCAnRWRpdHMnKQojIC0gcmVjb2RlOgplZGl0Q2FtcGFpZ24kQ2FtcGFpZ24gPC0gcmVjb2RlKGVkaXRDYW1wYWlnbiRDYW1wYWlnbiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnd21kZV9hYmMyMDE3X2J0MScgPSAnQlQxJywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnd21kZV9hYmMyMDE3X2J0MicgPSAnQlQyJywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnd21kZV9hYmMyMDE3X2J0MycgPSAnQlQzJywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnd21kZV9hYmMyMDE3X2dpYl9yZycgPSAnR0lCX1JHJywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnd21kZV9hYmMyMDE3X2dpYl9scCcgPSAnR0lCX0xQJwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICkKZWRpdENhbXBhaWduJENhbXBhaWduIDwtIGZhY3RvcihlZGl0Q2FtcGFpZ24kQ2FtcGFpZ24sIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxldmVscyA9IG5hbWVzKGNhbXBhaWduQ2hhcnRDb2xvcnMpKQpnZ3Bsb3QoZWRpdENhbXBhaWduLCBhZXMoeCA9IENhbXBhaWduLAogICAgICAgICAgICAgICAgICAgICAgICAgeSA9IEVkaXRzLCAKICAgICAgICAgICAgICAgICAgICAgICAgIGZpbGwgPSBDYW1wYWlnbiwgCiAgICAgICAgICAgICAgICAgICAgICAgICBjb2xvciA9IENhbXBhaWduLCAKICAgICAgICAgICAgICAgICAgICAgICAgIGxhYmVsID0gRWRpdHMpKSArCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIAogICAgICAgICAgIHBvc2l0aW9uID0gImRvZGdlIiwgCiAgICAgICAgICAgd2lkdGggPSAuNSkgKyAKICBnZW9tX2xhYmVsKGZpbGwgPSAid2hpdGUiLCBjb2xvciA9ICJibGFjayIpICsKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gY29tbWEpICsgCiAgc2NhbGVfZmlsbF9tYW51YWwoImxlZ2VuZCIsIHZhbHVlcyA9IGNhbXBhaWduQ2hhcnRDb2xvcnMpICsgCiAgc2NhbGVfY29sb3JfbWFudWFsKCJsZWdlbmQiLCB2YWx1ZXMgPSBjYW1wYWlnbkNoYXJ0Q29sb3JzKSArIAogIGdndGl0bGUoJ0F1dHVtbiBCYW5uZXIgQ2FtcGFpZ24gMjAxNzogVXNlciBFZGl0cyBwZXIgQ2FtcGFpZ24nKSArCiAgdGhlbWVfbWluaW1hbCgpICsgCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSAwLCBzaXplID0gOCwgaGp1c3QgPSAxKSkgKwogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSkgKwogIHRoZW1lKGxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSkgKwogIHRoZW1lKHBhbmVsLmdyaWQubWFqb3IueCA9IGVsZW1lbnRfYmxhbmsoKSkgKwogIHRoZW1lKHBhbmVsLmdyaWQubWlub3IueCA9IGVsZW1lbnRfYmxhbmsoKSkgKwogIHRoZW1lKHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCkpCmBgYAoKYGBge3IgZWNobyA9IFQsIHdhcm5pbmcgPSAnaGlkZScsIG1lc3NhZ2UgPSBGfQojIyMgLS0tIEZ1bGwgRGF0YXNldCAoVGFibGUgUmVwb3J0KQpkYXRhdGFibGUoZWRpdENhbXBhaWduKQpgYGAKCgojIyMgNC4gMyBQZXJjZW50IG9mIEFjdGl2ZSBVc2VycyBwZXIgQ2FtcGFpZ24KClRoZSBwZXJjZW50IG9mIHVzZXJzIHdobyBtYWRlIGFueSBlZGl0cyBhdCBhbGwgcGVyIGNhbXBhaWduOgoKYGBge3IgZWNobyA9IFQsIHdhcm5pbmcgPSAnaGlkZScsIG1lc3NhZ2UgPSBGfQojIC0gdGhlIGRhdGFzZXQKZWRpdHNNYWRlIDwtIGxlZnRfam9pbih1c2VyUmVnLCBlZGl0RGF0YSwgCiAgICAgICAgICAgICAgICAgICAgICAgYnkgPSBjKCdldmVudF91c2VySWQnID0gJ3Jldl91c2VyJykpCmVkaXRzTWFkZSRldmVudF9jYW1wYWlnbiA8LSB0b3VwcGVyKGdzdWIoIndtZGVfYWJjMjAxN18iLCAiIiwgZWRpdHNNYWRlJGV2ZW50X2NhbXBhaWduLCBmaXhlZCA9IFQpKQplZGl0c01hZGUkZWRpdHNbaXMubmEoZWRpdHNNYWRlJGVkaXRzKV0gPC0gMAplZGl0c01hZGUkRWRpdCA8LSAgaWZlbHNlKGVkaXRzTWFkZSRlZGl0cyA+IDAsICdFZGl0ZWQnLCAnTm8gZWRpdHMnKQplZGl0c01hZGUgPC0gZHBseXI6OnNlbGVjdChlZGl0c01hZGUsIAogICAgICAgICAgICAgICAgICAgICAgICAgICBldmVudF9jYW1wYWlnbiwgRWRpdCkKY29sbmFtZXMoZWRpdHNNYWRlKVsxXSA8LSAnQ2FtcGFpZ24nCmVkaXRzTWFkZSA8LSBlZGl0c01hZGUgJT4lIAogIGdyb3VwX2J5KENhbXBhaWduLCBFZGl0KSAlPiUgCiAgc3VtbWFyaXNlKENvdW50ID0gbigpKQplZGl0c01hZGUgPC0gZWRpdHNNYWRlICU+JSAKICBncm91cF9ieShDYW1wYWlnbikgJT4lIAogIG11dGF0ZShDb3VudCA9IHJvdW5kKENvdW50L3N1bShDb3VudCkqMTAwLCAyKSkKZWRpdHNNYWRlJEVkaXQgPC0gZmFjdG9yKGVkaXRzTWFkZSRFZGl0LCBsZXZlbHMgPSBjKCdFZGl0ZWQnLCAnTm8gZWRpdHMnKSkKZ2dwbG90KGVkaXRzTWFkZSwgYWVzKHggPSAnJywgeSA9IENvdW50LAogICAgICAgICAgICAgICAgICAgICAgZmlsbCA9IEVkaXQsCiAgICAgICAgICAgICAgICAgICAgICBjb2xvciA9IEVkaXQsCiAgICAgICAgICAgICAgICAgICAgICBncm91cCA9IEVkaXQsCiAgICAgICAgICAgICAgICAgICAgICBsYWJlbCA9IENvdW50KSkgKyAKICBnZW9tX2Jhcihwb3NpdGlvbiA9ICJzdGFjayIsIAogICAgICAgICAgIHN0YXQgPSAiaWRlbnRpdHkiLCAKICAgICAgICAgICB3aWR0aCA9IDEsIAogICAgICAgICAgIGNvbG9yID0gImJsYWNrIikgKyAKICBjb29yZF9wb2xhcigieSIsIHN0YXJ0ID0gMCkgKyAKICBmYWNldF93cmFwKH4gQ2FtcGFpZ24pICsKICBzY2FsZV9maWxsX21hbnVhbCgibGVnZW5kIiwgdmFsdWVzID0gYygnZmlyZWJyaWNrJywgJ3doaXRlJykpICsgCiAgc2NhbGVfY29sb3JfbWFudWFsKCJsZWdlbmQiLCB2YWx1ZXMgPSBjKCdmaXJlYnJpY2snLCAnd2hpdGUnKSkgKyAKICBnZ3RpdGxlKCdBdXR1bW4gQmFubmVyIENhbXBhaWduIDIwMTc6IFVzZXIgRWRpdHMgRGlzdHJpYnV0aW9ucyBwZXIgQ2FtcGFpZ24nKSArIAogIHhsYWIoIiIpICsgeWxhYigiUGVyY2VudCBFZGl0ZWQiKSArCiAgdGhlbWVfbWluaW1hbCgpICsgCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSAwLCBzaXplID0gOCwgaGp1c3QgPSAxKSkgKwogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSkgKwogIHRoZW1lKGxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSkgKwogIHRoZW1lKHBhbmVsLmdyaWQubWFqb3IueCA9IGVsZW1lbnRfYmxhbmsoKSkgKwogIHRoZW1lKHBhbmVsLmdyaWQubWlub3IueCA9IGVsZW1lbnRfYmxhbmsoKSkgKwogIHRoZW1lKHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCkpIApgYGAKCiMjIyA0LiA0IFVzZXIgRWRpdHMgQnJlYWtkb3duCgpJbiB0aGUgZm9sbG93aW5nIHRhYmxlOiBgTm8gZWRpdHNgOiB1c2VycyB3aXRoIHplcm8gZWRpdHMsIGBFZGl0ZWRgOiBudW1iZXIgb2YgdXNlciB3aG8gbWFkZSBhbnkgZWRpdHMgYXQgYWxsLCBgMSAtIDQgZWRpdHNgOiBudW1iZXIgb2YgdXNlcnMgd2l0aCAxIC0gNCBlZGl0cywgYDUgLSAxMCBlZGl0c2A6IG51bWJlciBvZiB1c2VycyB3aXRoIDUgLSAxMCBlZGl0cywgYW5kIGA+MTAgZWRpdHNgOiBudW1iZXIgb2YgdXNlciB3aXRoIG1vcmUgdGhhbiB0ZW4gZWRpdHMuCgpgYGB7ciBlY2hvID0gVCwgd2FybmluZyA9ICdoaWRlJywgbWVzc2FnZSA9IEZ9CiMjIyAtLS0gRnVsbCBEYXRhc2V0IChUYWJsZSBSZXBvcnQpCnBsdEVkaXRzIDwtIGFzLnRibChlZGl0RGF0YSkgJT4lIAogIGRwbHlyOjpncm91cF9ieShlZGl0cykgJT4lIAogIGNvdW50KCkKZWRpdHMwIDwtIGRpbSh1c2VyUmVnKVsxXSAtIHN1bShwbEVkaXREYXRhJENvdW50KQplZGl0cyA8LSBzdW0ocGx0RWRpdHMkbltwbHRFZGl0cyRlZGl0cyA+IDBdKQplZGl0czFfNCA8LSBzdW0ocGx0RWRpdHMkbltwbHRFZGl0cyRlZGl0cyA+PSAxICYgcGx0RWRpdHMkZWRpdHMgPD0gNF0pCmVkaXRzNV8xMCA8LSBzdW0ocGx0RWRpdHMkbltwbHRFZGl0cyRlZGl0cyA+PSA1ICYgcGx0RWRpdHMkZWRpdHMgPD0gMTBdKQplZGl0czEwIDwtIHN1bShwbHRFZGl0cyRuW3BsdEVkaXRzJGVkaXRzID4gMTBdKQplZGl0Q2xhc3NlcyA8LSBkYXRhLmZyYW1lKGBObyBlZGl0c2AgPSBlZGl0czAsCiAgICAgICAgICAgICAgICAgICAgICAgICAgYEVkaXRlZGAgPSBlZGl0cywKICAgICAgICAgICAgICAgICAgICAgICAgICBgMSAtIDQgZWRpdHNgID0gZWRpdHMxXzQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgYDUgLSAxMCBlZGl0c2AgPSBlZGl0czVfMTAsIAogICAgICAgICAgICAgICAgICAgICAgICAgIGA+IDEwIGVkaXRzYCA9IGVkaXRzMTAsCiAgICAgICAgICAgICAgICAgICAgICAgICAgY2hlY2submFtZXMgPSBGLAogICAgICAgICAgICAgICAgICAgICAgICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGKQprbml0cjo6a2FibGUoZWRpdENsYXNzZXMsIGZvcm1hdCA9ICJodG1sIikgJT4lCiAga2FibGVfc3R5bGluZyhmdWxsX3dpZHRoID0gRiwgcG9zaXRpb24gPSAibGVmdCIpCmBgYAoKIyMjIDQuIDUgVXNlciBFZGl0cyBhbmQgR3VpZGVkIFRvdXIgRXhpdHMKCkhvdyBtYW55IGVkaXRzIHdoZXJlIG1hZGUgYnkgdXNlcnMgd2hvIGRpZCBhbmQgZGlkIG5vdCBleGl0IHRoZSBDYW1wYWlnbiBHdWlkZWQgVG91cj8KCmBgYHtyIGVjaG8gPSBULCB3YXJuaW5nID0gJ2hpZGUnLCBtZXNzYWdlID0gRn0KZWRpdEdURGF0YSA8LSBsZWZ0X2pvaW4oZWRpdERhdGEsIHVzZXJSZWcsIGJ5ID0gYygncmV2X3VzZXInID0gJ2V2ZW50X3VzZXJJZCcpKQplZGl0R1REYXRhIDwtIGxlZnRfam9pbihlZGl0R1REYXRhLCBnVG91ckRhdGEsIGJ5ID0gYygncmV2X3VzZXInID0gJ2V2ZW50X3VzZXJJZCcpKQpleFRvdXJFZGl0cyA8LSBzdW0oZWRpdEdURGF0YSRlZGl0c1shaXMubmEoZWRpdEdURGF0YSRldmVudF90b3VyKV0pCm5vdEV4VG91ckVkaXRzIDwtIHN1bShlZGl0R1REYXRhJGVkaXRzW2lzLm5hKGVkaXRHVERhdGEkZXZlbnRfdG91cildKQpleGl0ZWRUb3VyRWRpdHMgPC0gcGFzdGUoZXhUb3VyRWRpdHMsIAogICAgICAgICAgICAgICAgICAgICAgICAgIiAoIiwgcm91bmQoZXhUb3VyRWRpdHMvKGV4VG91ckVkaXRzICsgbm90RXhUb3VyRWRpdHMpKjEwMCwgMiksICIlKSIsCiAgICAgICAgICAgICAgICAgICAgICAgICBzZXAgPSAiIikKbm90RXhpdGVkVG91ckVkaXRzIDwtIHBhc3RlKG5vdEV4VG91ckVkaXRzLCAKICAgICAgICAgICAgICAgICAgICAgICAgICIgKCIsIHJvdW5kKG5vdEV4VG91ckVkaXRzLyhleFRvdXJFZGl0cyArIG5vdEV4VG91ckVkaXRzKSoxMDAsIDIpLCAiJSkiLAogICAgICAgICAgICAgICAgICAgICAgICAgc2VwID0gIiIpCmd0RWRpdHMgPC0gZGF0YS5mcmFtZShgRXhpdGVkIEdUYCA9IGV4aXRlZFRvdXJFZGl0cywgCiAgICAgICAgICAgICAgICAgICAgICBgRGlkIG5vdCBleGl0IEdUYCA9IG5vdEV4aXRlZFRvdXJFZGl0cywgCiAgICAgICAgICAgICAgICAgICAgICBjaGVjay5uYW1lcyA9IEYsCiAgICAgICAgICAgICAgICAgICAgICBzdHJpbmdzQXNGYWN0b3JzID0gRikKa25pdHI6OmthYmxlKGd0RWRpdHMsIGZvcm1hdCA9ICJodG1sIikgJT4lCiAga2FibGVfc3R5bGluZyhmdWxsX3dpZHRoID0gRiwgcG9zaXRpb24gPSAibGVmdCIpCmBgYAoKCiMjIyA0LiA2IFRoZSBDYXVzYWwgRWZmZWN0IG9mIHRoZSBHdWlkZWQgVG91ciBVcG9uIEVkaXRpbmcKCkhvdyBkb2VzIGV4aXRpbmcgdnMuIG5vdCBleGl0aW5nIHRoZSBDYW1wYWlnbiBHdWlkZWQgVG91ciBpbmZsdWVuY2Ugd2hldGhlciB0aGUgbmV3IHVzZXIgd2lsbCBtYWtlIGF0IGxlYXN0IG9uZSBlZGl0IG9yIG5vdD8gVGhlIGZvbGxvd2luZyBjb250aW5nZW5jeSB0YWJsZSBwcmVzZW50cyB0aGUgbnVtYmVyIG9mIHJlZ2lzdGVyZWQgdXNlcnMgd2hvIG1hZGUgYW55IGVkaXRzIGF0IGFsbCAodnMuIHRob3NlIGRpZCBub3QgZWRpdCkgc2VwYXJhdGVseSBmb3IgdGhvc2Ugd2hvIGRpZCBhbmQgZGlkIG5vdCBleGl0IHRoZSBHdWlkZWQgVG91ci4KCmBgYHtyIGVjaG8gPSBULCB3YXJuaW5nID0gJ2hpZGUnLCBtZXNzYWdlID0gRn0KdXNlclJlZ0dUIDwtIGxlZnRfam9pbih1c2VyUmVnLCBnVG91ckRhdGEsIAogICAgICAgICAgICAgICAgICAgICAgIGJ5ID0gJ2V2ZW50X3VzZXJJZCcpCnVzZXJSZWdHVCA8LSBsZWZ0X2pvaW4odXNlclJlZ0dULCBlZGl0RGF0YSwgCiAgICAgICAgICAgICAgICAgICAgICAgYnkgPSBjKCdldmVudF91c2VySWQnID0gJ3Jldl91c2VyJykpCiMgLSBDb250aW5nZW5jeSBUYWJsZToKYSA8LSBsZW5ndGgodXNlclJlZ0dUJGV2ZW50X3VzZXJJZFshaXMubmEodXNlclJlZ0dUJGVkaXRzKSAmIGlzLm5hKHVzZXJSZWdHVCRldmVudF90b3VyKV0pCmIgPC0gbGVuZ3RoKHVzZXJSZWdHVCRldmVudF91c2VySWRbaXMubmEodXNlclJlZ0dUJGVkaXRzKSAmIGlzLm5hKHVzZXJSZWdHVCRldmVudF90b3VyKV0pCmMgPC0gbGVuZ3RoKHVzZXJSZWdHVCRldmVudF91c2VySWRbIWlzLm5hKHVzZXJSZWdHVCRlZGl0cykgJiAhaXMubmEodXNlclJlZ0dUJGV2ZW50X3RvdXIpXSkKZCA8LSBsZW5ndGgodXNlclJlZ0dUJGV2ZW50X3VzZXJJZFtpcy5uYSh1c2VyUmVnR1QkZWRpdHMpICYgIWlzLm5hKHVzZXJSZWdHVCRldmVudF90b3VyKV0pCmN0IDwtIGRhdGEuZnJhbWUoYEVkaXRlZGAgPSBjKGEsIGMpLAogICAgICAgICAgICAgICAgIGBObyBlZGl0c2AgPSBjKGIsIGQpLAogICAgICAgICAgICAgICAgIGNoZWNrLm5hbWVzID0gRikKcm93bmFtZXMoY3QpIDwtIGMoJ0dUIENvbXBsZXRlZCcsICdHVCBFeGl0ZWQnKQojIC0gZGVsdGFQOgpkZWx0YVAgPC0gYS8oYStiKSAtIGMvKGMrZCkKaWYgKGRlbHRhUCA+PSAwKSB7CiAgY2F1c2FsUCA8LSBkZWx0YVAvKDEgLSBjLyhjK2QpKQp9IGVsc2UgewogIGNhdXNhbFAgPC0gLWRlbHRhUC8oYy8oYytkKSkKfQprbml0cjo6a2FibGUoY3QsIGZvcm1hdCA9ICJodG1sIikgJT4lCiAga2FibGVfc3R5bGluZyhmdWxsX3dpZHRoID0gRiwgcG9zaXRpb24gPSAibGVmdCIpCmBgYAoKVGhlIGVzdGltYXRlIG9mIHRoZSBDYXVzYWwgUG93ZXIgKGl0IGNhbiByYW5nZSBmcm9tIDAgPSBubyBjYXVzYWwgaW5mbHVlbmNlIGF0IGFsbCwgdG8gMSA9IGEgY2F1c2UgY29tcGxldGVsbHkgc3VmZmljaWVudCB0byBicmluZyBhYm91dCBpdHMgZWZmZWN0KSBvZiB0aGUgR3VpZGVkIFRvdXIgdG8gYnJpbmcgYWJvdXQgYW55IGVkaXRzIGF0IGFsbCBpczoKCmBgYHtyIGVjaG8gPSBULCB3YXJuaW5nID0gJ2hpZGUnLCBtZXNzYWdlID0gRn0KY2VmZmVjdCA8LSBpZmVsc2UoZGVsdGFQID49IDAsICdnZW5lcmF0aXZlIGVmZmVjdCcsICdwcmV2ZW50aXZlIGVmZmVjdCcpCnByaW50KHBhc3RlKCdHdWlkZWQgVG91ciBDYXVzYWwgUG93ZXI6ICcsIHJvdW5kKGNhdXNhbFAsIDIpLCAnICgnLCBjZWZmZWN0LCAnKScsIHNlcCA9ICIiKSkKcHJpbnQocGFzdGUoJyhOT1RFOiB3aXRoIGEgdmFsdWUgb2YgYSBwcm9iYWJpbGlzdGljIGNvbnRyYXN0IGRlbHRhUCBvZik6ICcsIHJvdW5kKGRlbHRhUCwgMiksIHNlcCA9ICIiKSkKYGBgCgoqKlNVR0dFU1RJT046KiogcmVtb3ZlIHRoZSBHdWlkZWQgVG91ciBmcm9tIG91ciBmdXR1cmUgY2FtcGFpZ25zOyBpdCBoYXMgYW4gcHJldmVudGl2ZSBlZmZlY3QgdXBvbiB0aGUgbnVtYmVyIG9mIG5ldyB1c2VyIGVkaXRzLiAKCiMjIyA0LiA3IEd1aWRlZCBUb3VyIGFuZCB0aGUgbnVtYmVyIG9mIHVzZXIgZWRpdHMKCkhvdyBkb2VzIGV4aXRpbmcgdnMuIG5vdCBleGl0aW5nIHRoZSBDYW1wYWlnbiBHdWlkZWQgVG91ciBpbmZsdWVuY2UgaG93ICBtYW55IGVkaXRzIHdpbGwgYSBuZXcgdXNlciBtYWtlPwoKYGBge3IgZWNobyA9IFQsIHdhcm5pbmcgPSAnaGlkZScsIG1lc3NhZ2UgPSBGfQp1c2VyUmVnR1QxIDwtIGZpbHRlcih1c2VyUmVnR1QsICFpcy5uYShlZGl0cykpCnVzZXJSZWdHVDEkZXZlbnRfdG91ciA8LSBpZmVsc2UoaXMubmEodXNlclJlZ0dUMSRldmVudF90b3VyKSwgIkNvbXBsZXRlZCIsICJFeGl0ZWQiKQpkYXRhR1RFZGl0cyA8LSBkcGx5cjo6c2VsZWN0KHVzZXJSZWdHVDEsIGV2ZW50X3RvdXIsIGVkaXRzKSAlPiUgCiAgZ3JvdXBfYnkoZXZlbnRfdG91cikgJT4lIAogIHN1bW1hcmlzZShgTnVtLmVkaXRzYCA9IHN1bShlZGl0cykpCmtuaXRyOjprYWJsZShkYXRhR1RFZGl0cywgZm9ybWF0ID0gImh0bWwiKSAlPiUKICBrYWJsZV9zdHlsaW5nKGZ1bGxfd2lkdGggPSBGLCBwb3NpdGlvbiA9ICJsZWZ0IikKYGBgCgpJbiBjb25jbHVzaW9uLCB0aG9zZSBuZXcgdXNlcnMgd2hvIGhhZCBjb21wbGV0ZWQgdGhlIEd1aWRlZCBUb3VyIGhhdmUgYWxzbyBtYWRlIGEgc2xpZ2h0bHkgaGlnaGVyIG51bWJlciBvZiBlZGl0cy4KCiMjIDUuIENhbXBhaWduIEV2YWx1YXRpb24KCiMjIyA1LjEgQS9CIFRlc3Rpbmc6IENhbXBhaWduIEJhbm5lcnMKCiMjIyMgNS4xQSBVc2VyIFJlZ2lzdHJhdGlvbnMKClByZXBhcmUgcHJpb3JzIGFuZCBkYXRhLgoKYGBge3IgZWNobyA9IFQsIHdhcm5pbmcgPSAnaGlkZScsIG1lc3NhZ2UgPSBGLCByZXN1bHRzID0gJ2hpZGUnfQpyZWdEYXRhIDwtIHJlZ1Bsb3RTZXQgJT4lIAogIGdyb3VwX2J5KENhbXBhaWduKSAlPiUgCiAgc3VtbWFyaXNlKFJlZ2lzdHJhdGlvbnMgPSBzdW0oUmVnaXN0cmF0aW9ucykpCnZpZXdEYXRhIDwtIGNsaWNrUGxvdFNldCAlPiUgCiAgZ3JvdXBfYnkoU291cmNlKSAlPiUgCiAgc3VtbWFyaXNlKENsaWNrcyA9IHN1bShDb3VudCkpCnZpZXdEYXRhJFNvdXJjZSA8LSBnc3ViKCJfY2xpY2siLCAiIiwgdmlld0RhdGEkU291cmNlKQpyZWdEYXRhIDwtIGxlZnRfam9pbihyZWdEYXRhLCB2aWV3RGF0YSwgYnkgPSBjKCdDYW1wYWlnbicgPSAnU291cmNlJykpCgojIC0gVW5pbmZvcm1hdGl2ZSBwcmlvcjoKcHJpb3JBbHBoYSA8LSAxCnByaW9yQmV0YSA8LSAxCgojIC0gRGF0YToKQlQxRGF0YSA8LSBjKHJlcCgxLCByZWdEYXRhJFJlZ2lzdHJhdGlvbnNbMV0pLCByZXAoMCwgcmVnRGF0YSRDbGlja3NbMV0gLSByZWdEYXRhJFJlZ2lzdHJhdGlvbnNbMV0pKQpCVDJEYXRhIDwtIGMocmVwKDEsIHJlZ0RhdGEkUmVnaXN0cmF0aW9uc1syXSksIHJlcCgwLCByZWdEYXRhJENsaWNrc1syXSAtIHJlZ0RhdGEkUmVnaXN0cmF0aW9uc1syXSkpCkJUM0RhdGEgPC0gYyhyZXAoMSwgcmVnRGF0YSRSZWdpc3RyYXRpb25zWzNdKSwgcmVwKDAsIHJlZ0RhdGEkQ2xpY2tzWzNdIC0gcmVnRGF0YSRSZWdpc3RyYXRpb25zWzNdKSkKR0lCX0xQRGF0YSA8LSBjKHJlcCgxLCByZWdEYXRhJFJlZ2lzdHJhdGlvbnNbNF0pLCByZXAoMCwgcmVnRGF0YSRDbGlja3NbNF0gLSByZWdEYXRhJFJlZ2lzdHJhdGlvbnNbNF0pKQpHSUJfUkdEYXRhIDwtIGMocmVwKDEsIHJlZ0RhdGEkUmVnaXN0cmF0aW9uc1s1XSksIHJlcCgwLCByZWdEYXRhJENsaWNrc1s1XSAtIHJlZ0RhdGEkUmVnaXN0cmF0aW9uc1s1XSkpCgojIC0gUG9zdGVyaW9yczoKcG9zdEIxQWxwaGEgPC0gcHJpb3JBbHBoYSArIHN1bShCVDFEYXRhKQpwb3N0QjFCZXRhIDwtIHByaW9yQmV0YSArIGxlbmd0aChCVDFEYXRhKSAtIHN1bShCVDFEYXRhKQpwb3N0QjJBbHBoYSA8LSBwcmlvckFscGhhICsgc3VtKEJUMkRhdGEpCnBvc3RCMkJldGEgPC0gcHJpb3JCZXRhICsgbGVuZ3RoKEJUMkRhdGEpIC0gc3VtKEJUMkRhdGEpCnBvc3RCM0FscGhhIDwtIHByaW9yQWxwaGEgKyBzdW0oQlQzRGF0YSkKcG9zdEIzQmV0YSA8LSBwcmlvckJldGEgKyBsZW5ndGgoQlQzRGF0YSkgLSBzdW0oQlQzRGF0YSkKcG9zdEdJQl9MUEFscGhhIDwtIHByaW9yQWxwaGEgKyBzdW0oR0lCX0xQRGF0YSkKcG9zdEdJQl9MUEJldGEgPC0gcHJpb3JCZXRhICsgbGVuZ3RoKEdJQl9MUERhdGEpIC0gc3VtKEdJQl9MUERhdGEpCnBvc3RHSUJfUkdBbHBoYSA8LSBwcmlvckFscGhhICsgc3VtKEdJQl9SR0RhdGEpCnBvc3RHSUJfUkdCZXRhIDwtIHByaW9yQmV0YSArIGxlbmd0aChHSUJfUkdEYXRhKSAtIHN1bShHSUJfUkdEYXRhKQoKIyAtIE51bWJlciBvZiBNb250ZSBDYXJsbyBzYW1wbGVzOgptY04gPC0gMWU2CmBgYAoKIyMjIyMgU3VtbWFyeQoKVGhlIGBHSUJfUkdgIGJhbm5lciBkb21pbmF0ZXMgYWxsIG90aGVyIGluIHRlcm1zIG9mIHRoZSBwcm9iYWJpbGl0eSBvZiB1c2VyIHJlZ2lzdHJhdGlvbi4gQXMgb2YgdGhlIGBCVGAgY2FtcGFpZ25zOiBgQlQyYCBwZXJmb3JtcyByZWxhdGl2ZWxseSBiZXR0ZXIgdGhhbiBgQlQxYCBhbmQgYEJUMmAgd2hpY2ggZG8gbm90IGRpZmZlciAob3IgZGlmZmVyIG9ubHkgc2xpZ2h0bHkpIGJldHdlZW4gZWFjaCBvdGhlci4gVGhlIG1vc3QgaW1wb3J0YW50IGZpbmRpbmdzIGFyZTogCgotIHRoZSBkb21pbmFuY2Ugb2YgdGhlIGBHSUJgIGJhbm5lcnMgb3ZlciB0aGUgYEJUYCBiYW5uZXJzLCAKLSB0aGUgZG9taW5hbmNlIG9mIGBHSUJfUkdgIG92ZXIgYW55IG90aGVyIGJhbm5lci4gCgoqKk5PVEUuKiogSSB1c2UgdGhlIHRlcm0gYGNhbXBhaWduIGxpZnRgIGluIHRoZSBmb2xsb3dpbmcgc2VjdGlvbnMgdG8gcmVmZXIgdG8gYSBkaWZmZXJlbmNlIGluIHRoZSBwcm9iYWJpbGl0eSBvZiB1c2VyIHJlZ2lzdHJhdGlvbiBpbiByZXNwZWN0IHRvICphbnkqIHBhaXIgb2YgYmFubmVycyB0aGF0IHdlcmUgdXNlZCBkdXJpbmcgdGhlIGBBQkMyMDE3YCBjYW1wYWlnbi4gVGhlb3JldGljYWxseSwgYSBjYW1wYWlnbiBsaWZ0IHdvdWxkIGJlIHRoZSBkaWZmZXJlbmNlIChpbiBzb21lIEtQSSkgYmV0d2VlbiBhIGdyb3VwIG9mIHVzZXJzIHdobyB3ZXJlIGV4cG9zZWQgdG8gdGhlIGNhbXBhaWduIGFuZCB0aGUgY29udHJvbCBncm91cCAobm8gZXhwb3N1cmUpLiBJZiB3ZSB3b3VsZCBhc3Nlc3MgdGhlIGBBQkMyMDE3YCBjYW1wYWlnbiBpbiB0aGlzIG1hbm5lciB0aGVuIGl0IHdvdWxkIGJlIG5hdHVyYWwgdG8gdGFrZSBgR0lCX1JHYCBhcyBhIGNvbnRyb2wsIGFuZCBjb21wYXJlIGFsbCBvdGhlciBncm91cHMgKGBCVDFgLCBgQlQyYCwgYEJUM2AsIGFuZCBgR0lCX0xQYCkgdG8gaXQ7IGluIHRoYXQgY2FzZSwgd2Ugd291bGQgb2JzZXJ2ZSB0aGF0IGBBQkMyMDE3YCBjYW1wYWlnbiBoYWQgbm8gbGlmdCBpbiByZXNwZWN0IHRvIHRoZSBjb250cm9sIGdyb3VwICoqaW4gdGVybXMgb2YgdXNlciByZWdpc3RyYXRpb25zKiouCgpUaGUgZm9sbG93aW5nIHNlY3Rpb25zIHByb3ZpZGUgdGhlIHJlc3VsdHMgb2YgcGFpcndpc2UgQmF5ZXNpYW4gQS9CIHRlc3RzIGFjcm9zcyB0aGUgY2FtcGFpZ24gYmFubmVycy4gKipURUNITklDQUwgTk9URS4qKiBVbmlmb3JtIGBCZXRhKDEsIDEpYCBwcmlvcnMgKGFzc3VtaW5nIG5vIHByaW9yIGtub3dsZWdkZSBvbiB0aGUgcHJvYmFiaWxpdHkgb2YgYSBiYW5uZXIgY2xpY2sgbGVhZGluZyB0byBhIHJlZ2lzdHJhdGlvbikgYW5kIGAxLDAwMCwwMDBgIE1vbnRlIENhcmxvIHNhbXBsZXMgZnJvbSB0aGUgcG9zdGVyaW9ycyB3ZXJlIHVzZWQuCgojIyMjIyBFdmFsdWF0aW9uOiBCVDEgdnMuIEJUMgoKYGBge3IgZWNobyA9IFR9CkJUMVNhbXBsZXMgPC0gcmJldGEobWNOLCBwb3N0QjFBbHBoYSwgcG9zdEIxQmV0YSkKQlQyU2FtcGxlcyA8LSByYmV0YShtY04sIHBvc3RCMkFscGhhLCBwb3N0QjJCZXRhKQp0X3BlcmNlbnREaWZmIDwtIChtZWFuKEJUMVNhbXBsZXMpIC0gbWVhbihCVDJTYW1wbGVzKSkvbWVhbihCVDJTYW1wbGVzKSoxMDAKcEJUMV9CVDIgPC0gbWVhbigoQlQxU2FtcGxlcyA+IEJUMlNhbXBsZXMpKQojIC0gUHJvYmFiaWxpdHkgb2YgdjEgYmV0dGVyIHRoYW4gdjI6CnByaW50KHBhc3RlKCdUaGUgcHJvYmFiaWxpdHkgb2YgQlQxIGhhdmluZyBtb3JlIHVzZXIgcmVnaXN0cmF0aW9ucyB0aGFuIEJUMiBpczogJywgcEJUMV9CVDIpKQojIC0gdjEgQ2FtcGFpZ24gTGlmdApwZXJjZW50RGlmZiA8LSAoQlQxU2FtcGxlcyAtIEJUMlNhbXBsZXMpL0JUMlNhbXBsZXMqMTAwCnBlcmNlbnREaWZmIDwtIGRhdGEuZnJhbWUocGVyY2VudERpZmYgPSBwZXJjZW50RGlmZiwKICAgICAgICAgICAgICAgICAgICAgICAgICBhcmVhID0gaWZlbHNlKHBlcmNlbnREaWZmIDw9IDAsICc8PSAwJywgJz4gMCcpLAogICAgICAgICAgICAgICAgICAgICAgICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBUKQpwcmludChwYXN0ZSgnVGhlIHBlcmNlbnQgbGlmdCB0aGF0IEJUMSBoYXMgb3ZlciBCVDIgKCcsCiAgICAgICAgICAgIHJvdW5kKHRfcGVyY2VudERpZmYsIDIpLAogICAgICAgICAgICAnJSkgbGllcyBpbiB0aGUgaW50ZXJ2YWwgKCcsIAogICAgICAgICAgICBhcy5jaGFyYWN0ZXIocm91bmQocXVhbnRpbGUocGVyY2VudERpZmYkcGVyY2VudERpZmYsIC4wMjUpKSwgMiksIAogICAgICAgICAgICAnJSwgJywgCiAgICAgICAgICAgIGFzLmNoYXJhY3Rlcihyb3VuZChxdWFudGlsZShwZXJjZW50RGlmZiRwZXJjZW50RGlmZiwgLjk3NSkpLCAyKSwKICAgICAgICAgICAgJyUpIHdpdGggOTUlIGNlcnRhaW50eS4nLAogICAgICAgICAgICBzZXAgPSAiIikpCmdncGxvdChwZXJjZW50RGlmZiwgYWVzKHggPSBwZXJjZW50RGlmZiwKICAgICAgICAgICAgICAgICAgICAgICAgZmlsbCA9IGFyZWEpKSArIAogIGdlb21faGlzdG9ncmFtKGJpbndpZHRoID0gLjEsIGFscGhhID0gLjUpICsgCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IGNvbW1hKSArCiAgeGxhYignKEJUMSAtIEJUMikvQlQxJykgKyB5bGFiKCdEZW5zaXR5JykgKyAKICBnZ3RpdGxlKCdCVDEvQlQyIENhbXBhaWduIExpZnQnKSArCiAgdGhlbWVfbWluaW1hbCgpICsgCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApKQpgYGAKCiMjIyMjIEV2YWx1YXRpb246IEJUMSB2cy4gQlQzCgpgYGB7ciBlY2hvID0gVH0KbWNOIDwtIDFlNQpCVDFTYW1wbGVzIDwtIHJiZXRhKG1jTiwgcG9zdEIxQWxwaGEsIHBvc3RCMUJldGEpCkJUM1NhbXBsZXMgPC0gcmJldGEobWNOLCBwb3N0QjNBbHBoYSwgcG9zdEIzQmV0YSkKdF9wZXJjZW50RGlmZiA8LSAobWVhbihCVDFTYW1wbGVzKSAtIG1lYW4oQlQzU2FtcGxlcykpL21lYW4oQlQzU2FtcGxlcykqMTAwCnBCVDFfQlQzIDwtIG1lYW4oKEJUMVNhbXBsZXMgPiBCVDNTYW1wbGVzKSkKIyAtIFByb2JhYmlsaXR5IG9mIHYxIGJldHRlciB0aGFuIHYyOgpwcmludChwYXN0ZSgnVGhlIHByb2JhYmlsaXR5IG9mIEJUMSBoYXZpbmcgbW9yZSB1c2VyIHJlZ2lzdHJhdGlvbnMgdGhhbiBCVDMgaXM6ICcsIHBCVDFfQlQzKSkKIyAtIHYxIENhbXBhaWduIExpZnQKcGVyY2VudERpZmYgPC0gKEJUMVNhbXBsZXMgLSBCVDNTYW1wbGVzKS9CVDNTYW1wbGVzKjEwMApwZXJjZW50RGlmZiA8LSBkYXRhLmZyYW1lKHBlcmNlbnREaWZmID0gcGVyY2VudERpZmYsCiAgICAgICAgICAgICAgICAgICAgICAgICAgYXJlYSA9IGlmZWxzZShwZXJjZW50RGlmZiA8PSAwLCAnPD0gMCcsICc+IDAnKSwKICAgICAgICAgICAgICAgICAgICAgICAgICBzdHJpbmdzQXNGYWN0b3JzID0gVCkKcHJpbnQocGFzdGUoJ1RoZSBwZXJjZW50IGxpZnQgdGhhdCBCVDEgaGFzIG92ZXIgQlQzICgnLAogICAgICAgICAgICByb3VuZCh0X3BlcmNlbnREaWZmLCAyKSwKICAgICAgICAgICAgJyUpIGxpZXMgaW4gdGhlIGludGVydmFsICgnLCAKICAgICAgICAgICAgYXMuY2hhcmFjdGVyKHJvdW5kKHF1YW50aWxlKHBlcmNlbnREaWZmJHBlcmNlbnREaWZmLCAuMDI1KSksIDIpLCAKICAgICAgICAgICAgJyUsICcsIAogICAgICAgICAgICBhcy5jaGFyYWN0ZXIocm91bmQocXVhbnRpbGUocGVyY2VudERpZmYkcGVyY2VudERpZmYsIC45NzUpKSwgMiksCiAgICAgICAgICAgICclKSB3aXRoIDk1JSBjZXJ0YWludHkuJywKICAgICAgICAgICAgc2VwID0gIiIpKQpnZ3Bsb3QocGVyY2VudERpZmYsIGFlcyh4ID0gcGVyY2VudERpZmYsCiAgICAgICAgICAgICAgICAgICAgICAgIGZpbGwgPSBhcmVhKSkgKyAKICBnZW9tX2hpc3RvZ3JhbShiaW53aWR0aCA9IC4xLCBhbHBoYSA9IC41KSArIAogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBjb21tYSkgKwogIHhsYWIoJyhCVDEgLSBCVDMpL0JUMycpICsgeWxhYignRGVuc2l0eScpICsgCiAgZ2d0aXRsZSgnQlQxL0JUMyBDYW1wYWlnbiBMaWZ0JykgKwogIHRoZW1lX21pbmltYWwoKSArIAogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSkKYGBgCgojIyMjIyBFdmFsdWF0aW9uOiBCVDEgdnMuIEdJQl9MUAoKYGBge3IgZWNobyA9IFR9Cm1jTiA8LSAxZTUKQlQxU2FtcGxlcyA8LSByYmV0YShtY04sIHBvc3RCMUFscGhhLCBwb3N0QjFCZXRhKQpHSUJfTFBTYW1wbGVzIDwtIHJiZXRhKG1jTiwgcG9zdEdJQl9MUEFscGhhLCBwb3N0R0lCX0xQQmV0YSkKdF9wZXJjZW50RGlmZiA8LSAobWVhbihCVDFTYW1wbGVzKSAtIG1lYW4oR0lCX0xQU2FtcGxlcykpL21lYW4oR0lCX0xQU2FtcGxlcykqMTAwCnBCVDFfR0lCX0xQIDwtIG1lYW4oKEJUMVNhbXBsZXMgPiBHSUJfTFBTYW1wbGVzKSkKIyAtIFByb2JhYmlsaXR5IG9mIHYxIGJldHRlciB0aGFuIHYyOgpwcmludChwYXN0ZSgnVGhlIHByb2JhYmlsaXR5IG9mIEJUMSBoYXZpbmcgbW9yZSB1c2VyIHJlZ2lzdHJhdGlvbnMgdGhhbiBHSUJfTFAgaXM6ICcsIHBCVDFfR0lCX0xQKSkKIyAtIHYxIENhbXBhaWduIExpZnQKcGVyY2VudERpZmYgPC0gKEJUMVNhbXBsZXMgLSBHSUJfTFBTYW1wbGVzKS9HSUJfTFBTYW1wbGVzKjEwMApwZXJjZW50RGlmZiA8LSBkYXRhLmZyYW1lKHBlcmNlbnREaWZmID0gcGVyY2VudERpZmYsCiAgICAgICAgICAgICAgICAgICAgICAgICAgYXJlYSA9IGlmZWxzZShwZXJjZW50RGlmZiA8PSAwLCAnPD0gMCcsICc+IDAnKSwKICAgICAgICAgICAgICAgICAgICAgICAgICBzdHJpbmdzQXNGYWN0b3JzID0gVCkKcHJpbnQocGFzdGUoJ1RoZSBwZXJjZW50IGxpZnQgdGhhdCBCVDEgaGFzIG92ZXIgR0lCX0xQICgnLAogICAgICAgICAgICByb3VuZCh0X3BlcmNlbnREaWZmLCAyKSwKICAgICAgICAgICAgJyUpIGxpZXMgaW4gdGhlIGludGVydmFsICgnLCAKICAgICAgICAgICAgYXMuY2hhcmFjdGVyKHJvdW5kKHF1YW50aWxlKHBlcmNlbnREaWZmJHBlcmNlbnREaWZmLCAuMDI1KSksIDIpLCAKICAgICAgICAgICAgJyUsICcsIAogICAgICAgICAgICBhcy5jaGFyYWN0ZXIocm91bmQocXVhbnRpbGUocGVyY2VudERpZmYkcGVyY2VudERpZmYsIC45NzUpKSwgMiksCiAgICAgICAgICAgICclKSB3aXRoIDk1JSBjZXJ0YWludHkuJywKICAgICAgICAgICAgc2VwID0gIiIpKQpnZ3Bsb3QocGVyY2VudERpZmYsIGFlcyh4ID0gcGVyY2VudERpZmYsCiAgICAgICAgICAgICAgICAgICAgICAgIGZpbGwgPSBhcmVhKSkgKyAKICBnZW9tX2hpc3RvZ3JhbShiaW53aWR0aCA9IC4xLCBhbHBoYSA9IC41KSArIAogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBjb21tYSkgKwogIHhsYWIoJyhCVDEgLSBHSUJfTFApL0dJQl9MUCcpICsgeWxhYignRGVuc2l0eScpICsgCiAgZ2d0aXRsZSgnQlQxL0dJQl9MUCBDYW1wYWlnbiBMaWZ0JykgKwogIHRoZW1lX21pbmltYWwoKSArIAogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSkKYGBgCgojIyMjIyBFdmFsdWF0aW9uOiBCVDEgdnMuIEdJQl9SRwoKYGBge3IgZWNobyA9IFR9Cm1jTiA8LSAxZTUKQlQxU2FtcGxlcyA8LSByYmV0YShtY04sIHBvc3RCMUFscGhhLCBwb3N0QjFCZXRhKQpHSUJfUkdTYW1wbGVzIDwtIHJiZXRhKG1jTiwgcG9zdEdJQl9MUEFscGhhLCBwb3N0R0lCX1JHQmV0YSkKdF9wZXJjZW50RGlmZiA8LSAobWVhbihCVDFTYW1wbGVzKSAtIG1lYW4oR0lCX1JHU2FtcGxlcykpL21lYW4oR0lCX1JHU2FtcGxlcykqMTAwCnBCVDFfR0lCX1JHIDwtIG1lYW4oKEJUMVNhbXBsZXMgPiBHSUJfUkdTYW1wbGVzKSkKIyAtIFByb2JhYmlsaXR5IG9mIHYxIGJldHRlciB0aGFuIHYyOgpwcmludChwYXN0ZSgnVGhlIHByb2JhYmlsaXR5IG9mIEJUMSBoYXZpbmcgbW9yZSB1c2VyIHJlZ2lzdHJhdGlvbnMgdGhhbiBHSUJfUkcgaXM6ICcsIHBCVDFfR0lCX1JHKSkKIyAtIHYxIENhbXBhaWduIExpZnQKcGVyY2VudERpZmYgPC0gKEJUMVNhbXBsZXMgLSBHSUJfUkdTYW1wbGVzKS9HSUJfUkdTYW1wbGVzKjEwMApwZXJjZW50RGlmZiA8LSBkYXRhLmZyYW1lKHBlcmNlbnREaWZmID0gcGVyY2VudERpZmYsCiAgICAgICAgICAgICAgICAgICAgICAgICAgYXJlYSA9IGlmZWxzZShwZXJjZW50RGlmZiA8PSAwLCAnPD0gMCcsICc+IDAnKSwKICAgICAgICAgICAgICAgICAgICAgICAgICBzdHJpbmdzQXNGYWN0b3JzID0gVCkKcHJpbnQocGFzdGUoJ1RoZSBwZXJjZW50IGxpZnQgdGhhdCBCVDEgaGFzIG92ZXIgR0lCX1JHICgnLAogICAgICAgICAgICByb3VuZCh0X3BlcmNlbnREaWZmLCAyKSwKICAgICAgICAgICAgJyUpIGxpZXMgaW4gdGhlIGludGVydmFsICgnLCAKICAgICAgICAgICAgYXMuY2hhcmFjdGVyKHJvdW5kKHF1YW50aWxlKHBlcmNlbnREaWZmJHBlcmNlbnREaWZmLCAuMDI1KSksIDIpLCAKICAgICAgICAgICAgJyUsICcsIAogICAgICAgICAgICBhcy5jaGFyYWN0ZXIocm91bmQocXVhbnRpbGUocGVyY2VudERpZmYkcGVyY2VudERpZmYsIC45NzUpKSwgMiksCiAgICAgICAgICAgICclKSB3aXRoIDk1JSBjZXJ0YWludHkuJywKICAgICAgICAgICAgc2VwID0gIiIpKQpnZ3Bsb3QocGVyY2VudERpZmYsIGFlcyh4ID0gcGVyY2VudERpZmYsCiAgICAgICAgICAgICAgICAgICAgICAgIGZpbGwgPSBhcmVhKSkgKyAKICBnZW9tX2hpc3RvZ3JhbShiaW53aWR0aCA9IC4xLCBhbHBoYSA9IC41KSArIAogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBjb21tYSkgKwogIHhsYWIoJyhCVDEgLSBHSUJfUkcpL0dJQl9SRycpICsgeWxhYignRGVuc2l0eScpICsgCiAgZ2d0aXRsZSgnQlQxL0dJQl9SRyBDYW1wYWlnbiBMaWZ0JykgKwogIHRoZW1lX21pbmltYWwoKSArIAogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSkKYGBgCgojIyMjIyBFdmFsdWF0aW9uOiBCVDIgdnMuIEJUMwoKYGBge3IgZWNobyA9IFR9Cm1jTiA8LSAxZTUKQlQyU2FtcGxlcyA8LSByYmV0YShtY04sIHBvc3RCMkFscGhhLCBwb3N0QjJCZXRhKQpCVDNTYW1wbGVzIDwtIHJiZXRhKG1jTiwgcG9zdEIzQWxwaGEsIHBvc3RCM0JldGEpCnRfcGVyY2VudERpZmYgPC0gKG1lYW4oQlQyU2FtcGxlcykgLSBtZWFuKEJUM1NhbXBsZXMpKS9tZWFuKEJUM1NhbXBsZXMpKjEwMApwQlQyX0JUMyA8LSBtZWFuKChCVDJTYW1wbGVzID4gQlQzU2FtcGxlcykpCiMgLSBQcm9iYWJpbGl0eSBvZiB2MSBiZXR0ZXIgdGhhbiB2MjoKcHJpbnQocGFzdGUoJ1RoZSBwcm9iYWJpbGl0eSBvZiBCVDIgaGF2aW5nIG1vcmUgdXNlciByZWdpc3RyYXRpb25zIHRoYW4gQlQzIGlzOiAnLCBwQlQyX0JUMykpCiMgLSB2MSBDYW1wYWlnbiBMaWZ0CnBlcmNlbnREaWZmIDwtIChCVDJTYW1wbGVzIC0gQlQzU2FtcGxlcykvQlQzU2FtcGxlcyoxMDAKcGVyY2VudERpZmYgPC0gZGF0YS5mcmFtZShwZXJjZW50RGlmZiA9IHBlcmNlbnREaWZmLAogICAgICAgICAgICAgICAgICAgICAgICAgIGFyZWEgPSBpZmVsc2UocGVyY2VudERpZmYgPD0gMCwgJzw9IDAnLCAnPiAwJyksCiAgICAgICAgICAgICAgICAgICAgICAgICAgc3RyaW5nc0FzRmFjdG9ycyA9IFQpCnByaW50KHBhc3RlKCdUaGUgcGVyY2VudCBsaWZ0IHRoYXQgQlQyIGhhcyBvdmVyIEJUMyAoJywKICAgICAgICAgICAgcm91bmQodF9wZXJjZW50RGlmZiwgMiksCiAgICAgICAgICAgICclKSBsaWVzIGluIHRoZSBpbnRlcnZhbCAoJywgCiAgICAgICAgICAgIGFzLmNoYXJhY3Rlcihyb3VuZChxdWFudGlsZShwZXJjZW50RGlmZiRwZXJjZW50RGlmZiwgLjAyNSkpLCAyKSwgCiAgICAgICAgICAgICclLCAnLCAKICAgICAgICAgICAgYXMuY2hhcmFjdGVyKHJvdW5kKHF1YW50aWxlKHBlcmNlbnREaWZmJHBlcmNlbnREaWZmLCAuOTc1KSksIDIpLAogICAgICAgICAgICAnJSkgd2l0aCA5NSUgY2VydGFpbnR5LicsCiAgICAgICAgICAgIHNlcCA9ICIiKSkKZ2dwbG90KHBlcmNlbnREaWZmLCBhZXMoeCA9IHBlcmNlbnREaWZmLAogICAgICAgICAgICAgICAgICAgICAgICBmaWxsID0gYXJlYSkpICsgCiAgZ2VvbV9oaXN0b2dyYW0oYmlud2lkdGggPSAuMSwgYWxwaGEgPSAuNSkgKyAKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gY29tbWEpICsKICB4bGFiKCcoQlQyIC0gQlQzKS9CVDMnKSArIHlsYWIoJ0RlbnNpdHknKSArIAogIGdndGl0bGUoJ0JUMi9CVDMgQ2FtcGFpZ24gTGlmdCcpICsKICB0aGVtZV9taW5pbWFsKCkgKyAKICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCkpCmBgYAoKIyMjIyMgRXZhbHVhdGlvbjogQlQyIHZzLiBHSUJfTFAKCmBgYHtyIGVjaG8gPSBUfQptY04gPC0gMWU1CkJUMlNhbXBsZXMgPC0gcmJldGEobWNOLCBwb3N0QjJBbHBoYSwgcG9zdEIyQmV0YSkKR0lCX0xQU2FtcGxlcyA8LSByYmV0YShtY04sIHBvc3RHSUJfTFBBbHBoYSwgcG9zdEdJQl9MUEJldGEpCnRfcGVyY2VudERpZmYgPC0gKG1lYW4oQlQyU2FtcGxlcykgLSBtZWFuKEdJQl9MUFNhbXBsZXMpKS9tZWFuKEdJQl9MUFNhbXBsZXMpKjEwMApwQlQyX0dJQl9MUCA8LSBtZWFuKChCVDJTYW1wbGVzID4gR0lCX0xQU2FtcGxlcykpCiMgLSBQcm9iYWJpbGl0eSBvZiB2MSBiZXR0ZXIgdGhhbiB2MjoKcHJpbnQocGFzdGUoJ1RoZSBwcm9iYWJpbGl0eSBvZiBCVDIgaGF2aW5nIG1vcmUgdXNlciByZWdpc3RyYXRpb25zIHRoYW4gR0lCX0xQIGlzOiAnLCBwQlQyX0dJQl9MUCkpCiMgLSB2MSBDYW1wYWlnbiBMaWZ0CnBlcmNlbnREaWZmIDwtIChCVDJTYW1wbGVzIC0gR0lCX0xQU2FtcGxlcykvR0lCX0xQU2FtcGxlcyoxMDAKcGVyY2VudERpZmYgPC0gZGF0YS5mcmFtZShwZXJjZW50RGlmZiA9IHBlcmNlbnREaWZmLAogICAgICAgICAgICAgICAgICAgICAgICAgIGFyZWEgPSBpZmVsc2UocGVyY2VudERpZmYgPD0gMCwgJzw9IDAnLCAnPiAwJyksCiAgICAgICAgICAgICAgICAgICAgICAgICAgc3RyaW5nc0FzRmFjdG9ycyA9IFQpCnByaW50KHBhc3RlKCdUaGUgcGVyY2VudCBsaWZ0IHRoYXQgQlQyIGhhcyBvdmVyIEdJQl9MUCAoJywKICAgICAgICAgICAgcm91bmQodF9wZXJjZW50RGlmZiwgMiksCiAgICAgICAgICAgICclKSBsaWVzIGluIHRoZSBpbnRlcnZhbCAoJywgCiAgICAgICAgICAgIGFzLmNoYXJhY3Rlcihyb3VuZChxdWFudGlsZShwZXJjZW50RGlmZiRwZXJjZW50RGlmZiwgLjAyNSkpLCAyKSwgCiAgICAgICAgICAgICclLCAnLCAKICAgICAgICAgICAgYXMuY2hhcmFjdGVyKHJvdW5kKHF1YW50aWxlKHBlcmNlbnREaWZmJHBlcmNlbnREaWZmLCAuOTc1KSksIDIpLAogICAgICAgICAgICAnJSkgd2l0aCA5NSUgY2VydGFpbnR5LicsCiAgICAgICAgICAgIHNlcCA9ICIiKSkKZ2dwbG90KHBlcmNlbnREaWZmLCBhZXMoeCA9IHBlcmNlbnREaWZmLAogICAgICAgICAgICAgICAgICAgICAgICBmaWxsID0gYXJlYSkpICsgCiAgZ2VvbV9oaXN0b2dyYW0oYmlud2lkdGggPSAuMSwgYWxwaGEgPSAuNSkgKyAKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gY29tbWEpICsKICB4bGFiKCcoQlQyIC0gR0lCX0xQKS9HSUJfTFAnKSArIHlsYWIoJ0RlbnNpdHknKSArIAogIGdndGl0bGUoJ0JUMi9HSUJfTFAgQ2FtcGFpZ24gTGlmdCcpICsKICB0aGVtZV9taW5pbWFsKCkgKyAKICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCkpCmBgYAoKIyMjIyMgRXZhbHVhdGlvbjogQlQyIHZzLiBHSUJfUkcKCmBgYHtyIGVjaG8gPSBUfQptY04gPC0gMWU1CkJUMlNhbXBsZXMgPC0gcmJldGEobWNOLCBwb3N0QjJBbHBoYSwgcG9zdEIyQmV0YSkKR0lCX1JHU2FtcGxlcyA8LSByYmV0YShtY04sIHBvc3RHSUJfUkdBbHBoYSwgcG9zdEdJQl9SR0JldGEpCnRfcGVyY2VudERpZmYgPC0gKG1lYW4oQlQyU2FtcGxlcykgLSBtZWFuKEdJQl9SR1NhbXBsZXMpKS9tZWFuKEdJQl9SR1NhbXBsZXMpKjEwMApwQlQyX0dJQl9SRyA8LSBtZWFuKChCVDJTYW1wbGVzID4gR0lCX1JHU2FtcGxlcykpCiMgLSBQcm9iYWJpbGl0eSBvZiB2MSBiZXR0ZXIgdGhhbiB2MjoKcHJpbnQocGFzdGUoJ1RoZSBwcm9iYWJpbGl0eSBvZiBCVDIgaGF2aW5nIG1vcmUgdXNlciByZWdpc3RyYXRpb25zIHRoYW4gR0lCX1JHIGlzOiAnLCBwQlQyX0dJQl9SRykpCiMgLSB2MSBDYW1wYWlnbiBMaWZ0CnBlcmNlbnREaWZmIDwtIChCVDJTYW1wbGVzIC0gR0lCX1JHU2FtcGxlcykvR0lCX1JHU2FtcGxlcyoxMDAKcGVyY2VudERpZmYgPC0gZGF0YS5mcmFtZShwZXJjZW50RGlmZiA9IHBlcmNlbnREaWZmLAogICAgICAgICAgICAgICAgICAgICAgICAgIGFyZWEgPSBpZmVsc2UocGVyY2VudERpZmYgPD0gMCwgJzw9IDAnLCAnPiAwJyksCiAgICAgICAgICAgICAgICAgICAgICAgICAgc3RyaW5nc0FzRmFjdG9ycyA9IFQpCnByaW50KHBhc3RlKCdUaGUgcGVyY2VudCBsaWZ0IHRoYXQgQlQyIGhhcyBvdmVyIEdJQl9SRyAoJywKICAgICAgICAgICAgcm91bmQodF9wZXJjZW50RGlmZiwgMiksCiAgICAgICAgICAgICclKSBsaWVzIGluIHRoZSBpbnRlcnZhbCAoJywgCiAgICAgICAgICAgIGFzLmNoYXJhY3Rlcihyb3VuZChxdWFudGlsZShwZXJjZW50RGlmZiRwZXJjZW50RGlmZiwgLjAyNSkpLCAyKSwgCiAgICAgICAgICAgICclLCAnLCAKICAgICAgICAgICAgYXMuY2hhcmFjdGVyKHJvdW5kKHF1YW50aWxlKHBlcmNlbnREaWZmJHBlcmNlbnREaWZmLCAuOTc1KSksIDIpLAogICAgICAgICAgICAnJSkgd2l0aCA5NSUgY2VydGFpbnR5LicsCiAgICAgICAgICAgIHNlcCA9ICIiKSkKZ2dwbG90KHBlcmNlbnREaWZmLCBhZXMoeCA9IHBlcmNlbnREaWZmLAogICAgICAgICAgICAgICAgICAgICAgICBmaWxsID0gYXJlYSkpICsgCiAgZ2VvbV9oaXN0b2dyYW0oYmlud2lkdGggPSAuMSwgYWxwaGEgPSAuNSkgKyAKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gY29tbWEpICsKICB4bGFiKCcoQlQyIC0gR0lCX1JHKS9HSUJfUkcnKSArIHlsYWIoJ0RlbnNpdHknKSArIAogIGdndGl0bGUoJ0JUMi9HSUJfUkcgQ2FtcGFpZ24gTGlmdCcpICsKICB0aGVtZV9taW5pbWFsKCkgKyAKICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCkpCmBgYAoKIyMjIyMgRXZhbHVhdGlvbjogQlQzIHZzLiBHSUJfTFAKCmBgYHtyIGVjaG8gPSBUfQptY04gPC0gMWU1CkJUM1NhbXBsZXMgPC0gcmJldGEobWNOLCBwb3N0QjNBbHBoYSwgcG9zdEIzQmV0YSkKR0lCX0xQU2FtcGxlcyA8LSByYmV0YShtY04sIHBvc3RHSUJfTFBBbHBoYSwgcG9zdEdJQl9MUEJldGEpCnRfcGVyY2VudERpZmYgPC0gKG1lYW4oQlQzU2FtcGxlcykgLSBtZWFuKEdJQl9MUFNhbXBsZXMpKS9tZWFuKEdJQl9MUFNhbXBsZXMpKjEwMApwQlQzX0dJQl9MUCA8LSBtZWFuKChCVDNTYW1wbGVzID4gR0lCX0xQU2FtcGxlcykpCiMgLSBQcm9iYWJpbGl0eSBvZiB2MSBiZXR0ZXIgdGhhbiB2MjoKcHJpbnQocGFzdGUoJ1RoZSBwcm9iYWJpbGl0eSBvZiBCVDMgaGF2aW5nIG1vcmUgdXNlciByZWdpc3RyYXRpb25zIHRoYW4gR0lCX0xQIGlzOiAnLCBwQlQzX0dJQl9MUCkpCiMgLSB2MSBDYW1wYWlnbiBMaWZ0CnBlcmNlbnREaWZmIDwtIChCVDNTYW1wbGVzIC0gR0lCX0xQU2FtcGxlcykvR0lCX0xQU2FtcGxlcyoxMDAKcGVyY2VudERpZmYgPC0gZGF0YS5mcmFtZShwZXJjZW50RGlmZiA9IHBlcmNlbnREaWZmLAogICAgICAgICAgICAgICAgICAgICAgICAgIGFyZWEgPSBpZmVsc2UocGVyY2VudERpZmYgPD0gMCwgJzw9IDAnLCAnPiAwJyksCiAgICAgICAgICAgICAgICAgICAgICAgICAgc3RyaW5nc0FzRmFjdG9ycyA9IFQpCnByaW50KHBhc3RlKCdUaGUgcGVyY2VudCBsaWZ0IHRoYXQgQlQzIGhhcyBvdmVyIEdJQl9MUCAoJywKICAgICAgICAgICAgcm91bmQodF9wZXJjZW50RGlmZiwgMiksCiAgICAgICAgICAgICclKSBsaWVzIGluIHRoZSBpbnRlcnZhbCAoJywgCiAgICAgICAgICAgIGFzLmNoYXJhY3Rlcihyb3VuZChxdWFudGlsZShwZXJjZW50RGlmZiRwZXJjZW50RGlmZiwgLjAyNSkpLCAyKSwgCiAgICAgICAgICAgICclLCAnLCAKICAgICAgICAgICAgYXMuY2hhcmFjdGVyKHJvdW5kKHF1YW50aWxlKHBlcmNlbnREaWZmJHBlcmNlbnREaWZmLCAuOTc1KSksIDIpLAogICAgICAgICAgICAnJSkgd2l0aCA5NSUgY2VydGFpbnR5LicsCiAgICAgICAgICAgIHNlcCA9ICIiKSkKZ2dwbG90KHBlcmNlbnREaWZmLCBhZXMoeCA9IHBlcmNlbnREaWZmLAogICAgICAgICAgICAgICAgICAgICAgICBmaWxsID0gYXJlYSkpICsgCiAgZ2VvbV9oaXN0b2dyYW0oYmlud2lkdGggPSAuMSwgYWxwaGEgPSAuNSkgKyAKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gY29tbWEpICsKICB4bGFiKCcoQlQzIC0gR0lCX0xQKS9HSUJfTFAnKSArIHlsYWIoJ0RlbnNpdHknKSArIAogIGdndGl0bGUoJ0JUMy9HSUJfTFAgQ2FtcGFpZ24gTGlmdCcpICsKICB0aGVtZV9taW5pbWFsKCkgKyAKICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCkpCmBgYAoKIyMjIyMgRXZhbHVhdGlvbjogQlQzIHZzLiBHSUJfUkcKCmBgYHtyIGVjaG8gPSBUfQptY04gPC0gMWU1CkJUM1NhbXBsZXMgPC0gcmJldGEobWNOLCBwb3N0QjJBbHBoYSwgcG9zdEIyQmV0YSkKR0lCX1JHU2FtcGxlcyA8LSByYmV0YShtY04sIHBvc3RHSUJfUkdBbHBoYSwgcG9zdEdJQl9SR0JldGEpCnRfcGVyY2VudERpZmYgPC0gKG1lYW4oQlQzU2FtcGxlcykgLSBtZWFuKEdJQl9SR1NhbXBsZXMpKS9tZWFuKEdJQl9SR1NhbXBsZXMpKjEwMApwQlQzX0dJQl9SRyA8LSBtZWFuKChCVDNTYW1wbGVzID4gR0lCX1JHU2FtcGxlcykpCiMgLSBQcm9iYWJpbGl0eSBvZiB2MSBiZXR0ZXIgdGhhbiB2MjoKcHJpbnQocGFzdGUoJ1RoZSBwcm9iYWJpbGl0eSBvZiBCVDMgaGF2aW5nIG1vcmUgdXNlciByZWdpc3RyYXRpb25zIHRoYW4gR0lCX1JHIGlzOiAnLCBwQlQzX0dJQl9SRykpCiMgLSB2MSBDYW1wYWlnbiBMaWZ0CnBlcmNlbnREaWZmIDwtIChCVDNTYW1wbGVzIC0gR0lCX1JHU2FtcGxlcykvR0lCX1JHU2FtcGxlcyoxMDAKcGVyY2VudERpZmYgPC0gZGF0YS5mcmFtZShwZXJjZW50RGlmZiA9IHBlcmNlbnREaWZmLAogICAgICAgICAgICAgICAgICAgICAgICAgIGFyZWEgPSBpZmVsc2UocGVyY2VudERpZmYgPD0gMCwgJzw9IDAnLCAnPiAwJyksCiAgICAgICAgICAgICAgICAgICAgICAgICAgc3RyaW5nc0FzRmFjdG9ycyA9IFQpCnByaW50KHBhc3RlKCdUaGUgcGVyY2VudCBsaWZ0IHRoYXQgQlQzIGhhcyBvdmVyIEdJQl9SRyAoJywKICAgICAgICAgICAgcm91bmQodF9wZXJjZW50RGlmZiwgMiksCiAgICAgICAgICAgICclKSBsaWVzIGluIHRoZSBpbnRlcnZhbCAoJywgCiAgICAgICAgICAgIGFzLmNoYXJhY3Rlcihyb3VuZChxdWFudGlsZShwZXJjZW50RGlmZiRwZXJjZW50RGlmZiwgLjAyNSkpLCAyKSwgCiAgICAgICAgICAgICclLCAnLCAKICAgICAgICAgICAgYXMuY2hhcmFjdGVyKHJvdW5kKHF1YW50aWxlKHBlcmNlbnREaWZmJHBlcmNlbnREaWZmLCAuOTc1KSksIDIpLAogICAgICAgICAgICAnJSkgd2l0aCA5NSUgY2VydGFpbnR5LicsCiAgICAgICAgICAgIHNlcCA9ICIiKSkKZ2dwbG90KHBlcmNlbnREaWZmLCBhZXMoeCA9IHBlcmNlbnREaWZmLAogICAgICAgICAgICAgICAgICAgICAgICBmaWxsID0gYXJlYSkpICsgCiAgZ2VvbV9oaXN0b2dyYW0oYmlud2lkdGggPSAuMSwgYWxwaGEgPSAuNSkgKyAKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gY29tbWEpICsKICB4bGFiKCcoQlQzIC0gR0lCX1JHKS9HSUJfUkcnKSArIHlsYWIoJ0RlbnNpdHknKSArIAogIGdndGl0bGUoJ0JUMy9HSUJfUkcgQ2FtcGFpZ24gTGlmdCcpICsKICB0aGVtZV9taW5pbWFsKCkgKyAKICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCkpCmBgYAoKIyMjIyMgRXZhbHVhdGlvbjogR0lCX0xQIHZzLiBHSUJfUkcKCmBgYHtyIGVjaG8gPSBUfQptY04gPC0gMWU1CkdJQl9MUFNhbXBsZXMgPC0gcmJldGEobWNOLCBwb3N0QjJBbHBoYSwgcG9zdEIyQmV0YSkKR0lCX1JHU2FtcGxlcyA8LSByYmV0YShtY04sIHBvc3RHSUJfUkdBbHBoYSwgcG9zdEdJQl9SR0JldGEpCnRfcGVyY2VudERpZmYgPC0gKG1lYW4oR0lCX0xQU2FtcGxlcykgLSBtZWFuKEdJQl9SR1NhbXBsZXMpKS9tZWFuKEdJQl9SR1NhbXBsZXMpKjEwMApwR0lCX0xQX0dJQl9SRyA8LSBtZWFuKChHSUJfTFBTYW1wbGVzID4gR0lCX1JHU2FtcGxlcykpCiMgLSBQcm9iYWJpbGl0eSBvZiB2MSBiZXR0ZXIgdGhhbiB2MjoKcHJpbnQocGFzdGUoJ1RoZSBwcm9iYWJpbGl0eSBvZiBHSUJfTFAgaGF2aW5nIG1vcmUgdXNlciByZWdpc3RyYXRpb25zIHRoYW4gR0lCX1JHIGlzOiAnLCBwR0lCX0xQX0dJQl9SRykpCiMgLSB2MSBDYW1wYWlnbiBMaWZ0CnBlcmNlbnREaWZmIDwtIChHSUJfTFBTYW1wbGVzIC0gR0lCX1JHU2FtcGxlcykvR0lCX1JHU2FtcGxlcyoxMDAKcGVyY2VudERpZmYgPC0gZGF0YS5mcmFtZShwZXJjZW50RGlmZiA9IHBlcmNlbnREaWZmLAogICAgICAgICAgICAgICAgICAgICAgICAgIGFyZWEgPSBpZmVsc2UocGVyY2VudERpZmYgPD0gMCwgJzw9IDAnLCAnPiAwJyksCiAgICAgICAgICAgICAgICAgICAgICAgICAgc3RyaW5nc0FzRmFjdG9ycyA9IFQpCnByaW50KHBhc3RlKCdUaGUgcGVyY2VudCBsaWZ0IHRoYXQgR0lCX0xQIGhhcyBvdmVyIEdJQl9SRyAoJywKICAgICAgICAgICAgcm91bmQodF9wZXJjZW50RGlmZiwgMiksCiAgICAgICAgICAgICclKSBsaWVzIGluIHRoZSBpbnRlcnZhbCAoJywgCiAgICAgICAgICAgIGFzLmNoYXJhY3Rlcihyb3VuZChxdWFudGlsZShwZXJjZW50RGlmZiRwZXJjZW50RGlmZiwgLjAyNSkpLCAyKSwgCiAgICAgICAgICAgICclLCAnLCAKICAgICAgICAgICAgYXMuY2hhcmFjdGVyKHJvdW5kKHF1YW50aWxlKHBlcmNlbnREaWZmJHBlcmNlbnREaWZmLCAuOTc1KSksIDIpLAogICAgICAgICAgICAnJSkgd2l0aCA5NSUgY2VydGFpbnR5LicsCiAgICAgICAgICAgIHNlcCA9ICIiKSkKZ2dwbG90KHBlcmNlbnREaWZmLCBhZXMoeCA9IHBlcmNlbnREaWZmLAogICAgICAgICAgICAgICAgICAgICAgICBmaWxsID0gYXJlYSkpICsgCiAgZ2VvbV9oaXN0b2dyYW0oYmlud2lkdGggPSAuMSwgYWxwaGEgPSAuNSkgKyAKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gY29tbWEpICsKICB4bGFiKCcoR0lCX0xQIC0gR0lCX1JHKS9HSUJfUkcnKSArIHlsYWIoJ0RlbnNpdHknKSArIAogIGdndGl0bGUoJ0dJQl9MUC9HSUJfUkcgQ2FtcGFpZ24gTGlmdCcpICsKICB0aGVtZV9taW5pbWFsKCkgKyAKICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCkpCmBgYAoKIyMjIyA1LjFCIFVzZXIgRWRpdHMKCiMjIyMjIFN1bW1hcnkKCldoYXQgd2Uga25vdyAqYWxtb3N0IGNlcnRhaW5seSogaXMgdGhhdCwgaW4gZ2VuZXJhbCwgdGhlIGBCVGAgYmFubmVycyBkb21pbmF0ZSB0aGUgYEdJQl9MUGAgYW5kIGBHSUJfUkdgIGJhbm5lcnMgaW4gdGVybXMgb2YgdGhlICpleHBlY3RlZCBudW1iZXIgb2YgdXNlciBlZGl0cyouIFRoZSBgQlQxYCBhbmQgYEJUM2AgYmFubmVycyBiZWF0IHRoZSBgQlQyYCBiYW5uZXIgaW4gdGhpcyByZXNwZWN0LCB3aGlsZSB0aGUgZmluZGluZyBvbiB0aGUgZGlmZmVyZW5jZSBiZXR3ZWVuIGBCVDFgIGFuZCBgQlQzYCBpcyBpbmNvbmNsdXNpdmUuIGBCVDJgIGlzIHRoZSBvbmx5IGJhbm5lciBmcm9tIHRoZSBgQlRgIGdyb3VwIHRoYXQgcGVyZm9ybXMgd29yc2UgdGhhbiB0aGUgYEdJQl9MUGAgYW5kIGBHSUJfUkdgIGJhbm5lcnMgaW4gdGhpcyByZXNwZWN0LiBBbHNvLCB0aGUgYEdJQl9MUGAgYW5kIGBHSUJfUkdgIGJhbm5lcnMgZG8gbm90IGRpZmZlciBzaWduZmljYW50bHkgaW4gdGhlIG51bWJlciBvZiBleHBlY3RlZCB1c2VyIGVkaXRzIHRoYXQgdGhleSBpbmZsdWVuY2UuCgoqKk5PVEUuKiogSSB1c2UgdGhlIHRlcm0gYGNhbXBhaWduIGxpZnRgIGluIHRoZSBmb2xsb3dpbmcgc2VjdGlvbnMgdG8gcmVmZXIgdG8gYSBkaWZmZXJlbmNlIGluIHRoZSBwcm9iYWJpbGl0eSBvZiB1c2VyIHJlZ2lzdHJhdGlvbiBpbiByZXNwZWN0IHRvICphbnkqIHBhaXIgb2YgYmFubmVycyB0aGF0IHdlcmUgdXNlZCBkdXJpbmcgdGhlIGBBQkMyMDE3YCBjYW1wYWlnbi4gVGhlb3JldGljYWxseSwgYSBjYW1wYWlnbiBsaWZ0IHdvdWxkIGJlIHRoZSBkaWZmZXJlbmNlIChpbiBzb21lIEtQSSkgYmV0d2VlbiBhIGdyb3VwIG9mIHVzZXJzIHdobyB3ZXJlIGV4cG9zZWQgdG8gdGhlIGNhbXBhaWduIGFuZCB0aGUgY29udHJvbCBncm91cCAobm8gZXhwb3N1cmUpLiBJZiB3ZSB3b3VsZCBhc3Nlc3MgdGhlIGBBQkMyMDE3YCBjYW1wYWlnbiBpbiB0aGlzIG1hbm5lciB0aGVuIGl0IHdvdWxkIGJlIG5hdHVyYWwgdG8gdGFrZSBgR0lCX1JHYCBhcyBhIGNvbnRyb2wsIGFuZCBjb21wYXJlIGFsbCBvdGhlciBncm91cHMgKGBCVDFgLCBgQlQyYCwgYEJUM2AsIGFuZCBgR0lCX0xQYCkgdG8gaXQ7IGluIHRoYXQgY2FzZSwgd2Ugd291bGQgb2JzZXJ2ZSB0aGF0IGBBQkMyMDE3YCBjYW1wYWlnbiBoYWQgYSBsaWZ0IGluIHJlc3BlY3QgdG8gdGhlIGNvbnRyb2wgZ3JvdXAgKippbiB0ZXJtcyBvZiB0aGUgZXhwZWN0ZWQgbnVtYmVyIG9mIHVzZXIgZWRpdHMqKi4KClRoZSBmb2xsb3dpbmcgc2VjdGlvbnMgcHJvdmlkZSB0aGUgcmVzdWx0cyBvZiBwYWlyd2lzZSBCYXllc2lhbiBBL0IgdGVzdHMgYWNyb3NzIHRoZSBjYW1wYWlnbiBiYW5uZXJzLiAqKlRFQ0hOSUNBTCBOT1RFLioqIFVuaWZvcm0gYERpcmljaGxldCgpYCBwcmlvcnMgd2l0aCBhIGNvbmNlbnRyYXRpb24gcGFyYW1ldGVyIG9mIGAxYCB3ZXJlIHVzZWQgdG8gZGVyaXZlIHRoZSBleHBlY3RlZCB1c2VyIGVkaXQgZGlzdHJpYnV0aW9ucywgd2hpbGUgdW5pZm9ybSBgQmV0YSgxLCAxKWAgdW5pbmZvcm1hdGl2ZSBwcmlvcnMgd2VyZSB1c2VkIGZvciBBL0IgdGVzdGluZzsgYDEsMDAwLDAwMGAgTW9udGUgQ2FybG8gc2FtcGxlcyBmcm9tIHRoZSBwb3N0ZXJpb3JzIHdlcmUgdXNlZC4KClByZXBhcmUgcHJpb3JzLCBkYXRhLCBhbmQgdGVzdCBmdW5jdGlvbnMuCgpgYGB7ciBlY2hvID0gVCwgd2FybmluZyA9ICdoaWRlJywgbWVzc2FnZSA9IEZ9CiMgLSBOdW1iZXIgb2YgTW9udGUgQ2FybG8gc2FtcGxlczoKbWNOIDwtIDFlNgoKIyAtIHRoZSBkYXRhc2V0CmVkRGF0YSA8LSBsZWZ0X2pvaW4oZWRpdERhdGEsIHVzZXJSZWcsIAogICAgICAgICAgICAgICAgICAgIGJ5ID0gYygicmV2X3VzZXIiID0gImV2ZW50X3VzZXJJZCIpKSAlPiUgCiAgZ3JvdXBfYnkoZXZlbnRfY2FtcGFpZ24sIGVkaXRzKSAlPiUgCiAgc3VtbWFyaXNlKENvdW50ID0gbigpKQpjb2xuYW1lcyhlZERhdGEpIDwtIGMoJ0NhbXBhaWduJywgJ0VkaXRzJywgJ0NvdW50JykKCiMgLSBlZERhdGEkTWF0Y2gKZWREYXRhJE1hdGNoIDwtIGVkRGF0YSRFZGl0cwoKIyAtIG1heC4gb2JzZXJ2ZWQgRWRpdHM6Cm1heEVkaXRzIDwtIG1heChlZERhdGEkRWRpdHMpCgojIC0gZmlsbCBpbiBtaXNzaW5nIGVkaXRzCmNhbXBhaWducyA8LSB1bmlxdWUoZWREYXRhJENhbXBhaWduKQpuQ2FtcGFpZ25zIDwtIGxlbmd0aChjYW1wYWlnbnMpCmNhbXBhaWducyA8LSB1bmxpc3QobGFwcGx5KGNhbXBhaWducywgZnVuY3Rpb24oeCl7CiAgcmV0dXJuKHJlcCh4LCBtYXhFZGl0cyArIDEpKQp9KSkKZWREYXRhQ29weSA8LSBkYXRhLmZyYW1lKENhbXBhaWduID0gY2FtcGFpZ25zLCAKICAgICAgICAgICAgICAgICAgICAgICAgIE1hdGNoID0gcmVwKHNlcSgwLCBtYXhFZGl0cywgYnkgPSAxKSwgbkNhbXBhaWducyksCiAgICAgICAgICAgICAgICAgICAgICAgICBzdHJpbmdzQXNGYWN0b3JzID0gRikKZWREYXRhQ29weSA8LSBsZWZ0X2pvaW4oZWREYXRhQ29weSwgZWREYXRhLCAKICAgICAgICAgICAgICAgICAgICAgICAgYnkgPSBjKCJDYW1wYWlnbiIgPSAiQ2FtcGFpZ24iLCAiTWF0Y2giID0gIk1hdGNoIikKICAgICAgICAgICAgICAgICAgICAgICAgKQplZERhdGEgPC0gZWREYXRhQ29weQpybShlZERhdGFDb3B5KTsKZWREYXRhJENvdW50W2lzLm5hKGVkRGF0YSRDb3VudCldIDwtIDAKZWREYXRhJEVkaXRzIDwtIGVkRGF0YSRNYXRjaAplZERhdGEkTWF0Y2ggPC0gTlVMTAoKIyAtIGJhbm5lciBlZGl0IHByb2JhYmlsaXR5CmJhbm5lckVkUHJvYiA8LSBlZERhdGEgJT4lIAogIGdyb3VwX2J5KENhbXBhaWduKSAlPiUgCiAgbXV0YXRlKFByb2IgPSBDb3VudC9zdW0oQ291bnQpKSAlPiUgCiAgbXV0YXRlKEV4cGVjdCA9IFByb2IgKiBFZGl0cykKYmFubmVyRWRQcm9iJENhbXBhaWduIDwtIHRvdXBwZXIoZ3N1Yigid21kZV9hYmMyMDE3XyIsICIiLCBiYW5uZXJFZFByb2IkQ2FtcGFpZ24sIGZpeGVkID0gVCkpCgojIC0gdHJ1ZSBleHBlY3RlZCBlZGl0IHBlciBiYW5uZXIKY2FtcGFpZ25URVIgPC0gZWREYXRhICU+JSAKICBtdXRhdGUoRXggPSBFZGl0cyAqIENvdW50KSAlPiUKICBncm91cF9ieShDYW1wYWlnbikgJT4lIAogIHN1bW1hcmlzZShURVIgPSBFZGl0cyAlKiUgKENvdW50L3N1bShDb3VudCkpLCBTRCA9IHNkKEV4KSkKY2FtcGFpZ25URVIkQ2FtcGFpZ24gPC0gdG91cHBlcihnc3ViKCJ3bWRlX2FiYzIwMTdfIiwgIiIsIGNhbXBhaWduVEVSJENhbXBhaWduLCBmaXhlZCA9IFQpKQoKIyAtIE4gdXNlciByZWdpc3RyYXRpb25zIHBlciBCYW5uZXIKYmFubmVyTlVzZXIgPC0gcmVnUGxvdFNldCAlPiUgCiAgZ3JvdXBfYnkoQ2FtcGFpZ24pICU+JSAKICBzdW1tYXJpc2UoUmVnaXN0cmF0aW9uID0gc3VtKFJlZ2lzdHJhdGlvbnMpKQoKIyAtIHBvc3RlcmlvciBleHBlY3RlZCBlZGl0IHNhbXBsZXM6CnBvc3RlcmlvckVFZGl0U2FtcGxlIDwtIGZ1bmN0aW9uKGFscGhhLCBjb3VudHMsIHZhbHVlcywgc2FtcGxlcykgewogIGRpcmljaGxldFNhbXBsZSA8LSByZGlyaWNobGV0KHNhbXBsZXMsIGNvdW50cyArIGFscGhhKQogIGRpcmljaGxldFNhbXBsZSAlKiUgdmFsdWVzCn0KCiMgLSBwb3N0ZXJpb3IgZXhwZWN0ZWQgZWRpdCBBL0IgdGVzdDoKcG9zdGVyaW9yX0VFZGl0X0FCIDwtIGZ1bmN0aW9uKGRhdGEsIGNhbXBhaWduQSwgY2FtcGFpZ25CLCBtY04pIHsKICBpZiAoIShjYW1wYWlnbkEgJWluJSB1bmlxdWUoZGF0YSRDYW1wYWlnbikpIHwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICEoY2FtcGFpZ25CICVpbiUgdW5pcXVlKGRhdGEkQ2FtcGFpZ24pKSkgewogICAgc3RvcCgiQ2FtcGFpZ24gbm90IGZvdW5kLiIsIAogICAgICAgICBjYWxsLiA9IFRSVUUpCiAgfSBlbHNlIHsKICAgIAogICAgIyAtIHByZXBhcmUgcmVzOgogICAgcmVzIDwtIGxpc3QoKQogICAgCiAgICAjIC0gVW5pbmZvcm1hdGl2ZSBwcmlvcnMKICAgIHByaW9yQSA8LSByZXAoMSwgbGVuZ3RoKHdoaWNoKGRhdGEkQ2FtcGFpZ24gJWluJSBjYW1wYWlnbkEpKSkKICAgIHByaW9yQiA8LSByZXAoMSwgbGVuZ3RoKHdoaWNoKGRhdGEkQ2FtcGFpZ24gJWluJSBjYW1wYWlnbkIpKSkKICAgIAogICAgIyAtIFNpbXVsYXRlIGJhbm5lcnM6CiAgICBjb3VudHNBIDwtIGRhdGEkQ291bnRbd2hpY2goZGF0YSRDYW1wYWlnbiAlaW4lIGNhbXBhaWduQSldCiAgICBjb3VudHNCIDwtIGRhdGEkQ291bnRbd2hpY2goZGF0YSRDYW1wYWlnbiAlaW4lIGNhbXBhaWduQildCiAgICAKICAgICMgLSBlZGl0IHZhbHVlczoKICAgIGVkaXRWYWx1ZXNBIDwtIGRhdGEkRWRpdHNbd2hpY2goZGF0YSRDYW1wYWlnbiAlaW4lIGNhbXBhaWduQSldCiAgICBlZGl0VmFsdWVzQiA8LSBkYXRhJEVkaXRzW3doaWNoKGRhdGEkQ2FtcGFpZ24gJWluJSBjYW1wYWlnbkIpXQogICAgCiAgICAjIC0gcG9zdGVyaW9yIGV4cGVjdGVkIGVkaXRzOgogICAgcG9zdGVyaW9yQSA8LSBwb3N0ZXJpb3JFRWRpdFNhbXBsZShhbHBoYSA9IHByaW9yQSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY291bnRzID0gY291bnRzQSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdmFsdWVzID0gZWRpdFZhbHVlc0EsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNhbXBsZXMgPSBtY04pIAogICAgcG9zdGVyaW9yQSA8LSBkYXRhLmZyYW1lKHBvc3RlcmlvciA9IHBvc3RlcmlvckEpCiAgICBwb3N0ZXJpb3JCIDwtIHBvc3RlcmlvckVFZGl0U2FtcGxlKGFscGhhID0gcHJpb3JCLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb3VudHMgPSBjb3VudHNCLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB2YWx1ZXMgPSBlZGl0VmFsdWVzQiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2FtcGxlcyA9IG1jTikgCiAgICBwb3N0ZXJpb3JCIDwtIGRhdGEuZnJhbWUocG9zdGVyaW9yID0gcG9zdGVyaW9yQikKICAgIAogICAgIyAtIHJlczogcG9zdGVyaW9ycwogICAgcmVzJHBvc3RlcmlvckEgPC0gcG9zdGVyaW9yQQogICAgcmVzJHBvc3RlcmlvckIgPC0gcG9zdGVyaW9yQgoKICAgICMgLSByZXM6IHByb2JhYmlsaXR5IEEvQgogICAgcmVzJHByb2JhYmlsaXR5IDwtIG1lYW4ocG9zdGVyaW9yQSA+IHBvc3RlcmlvckIpCiAgICAKICAgICMgLSByZXM6IHBlcmNlbnQgZGlmZmVyZW5jZSAoY2FtcGFpZ24gTGlmdCkKICAgIHJlcyRwZXJjZW50RGlmZiA8LSAocG9zdGVyaW9yQSRwb3N0ZXJpb3IgLSBwb3N0ZXJpb3JCJHBvc3RlcmlvcikvcG9zdGVyaW9yQiRwb3N0ZXJpb3IqMTAwCiAgICBhcmVhID0gaWZlbHNlKHJlcyRwZXJjZW50RGlmZiA8PSAwLCAnPD0gMCcsICc+IDAnKQogICAgcmVzJHBlcmNlbnREaWZmIDwtIGRhdGEuZnJhbWUocGVyY2VudERpZmYgPSByZXMkcGVyY2VudERpZmYsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhcmVhID0gYXJlYSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBUKQogCiAgICAjIC0gcmVzOiB0cnVlIHBlcmNlbnQgZGlmZmVyZW5jZToKICAgIHJlcyR0X3BlcmNlbnREaWZmIDwtIAogICAgICAobWVhbihyZXMkcG9zdGVyaW9yQSRwb3N0ZXJpb3IpIC0gbWVhbihyZXMkcG9zdGVyaW9yQiRwb3N0ZXJpb3IpKS9tZWFuKHJlcyRwb3N0ZXJpb3JCJHBvc3RlcmlvcikqMTAwCiAgICAKICAgICMgLSBvdXQ6CiAgICByZXR1cm4ocmVzKQogICAgCiAgfQp9CmBgYAoKIyMjIyMgRXhwZWN0ZWQgVXNlciBFZGl0cyBwZXIgQ2FtcGFpZ24KClRoZSBhdmVyYWdlIG51bWJlciBvZiBlZGl0cyBwZXIgdXNlciB2cy4gdGhlIGNhbXBhaWduIHZpYSB0aGV5IGhhdmUgcmVnaXN0ZXJlZDoKCmBgYHtyIGVjaG8gPSBULCB3YXJuaW5nID0gJ2hpZGUnLCBtZXNzYWdlID0gRn0KY2FtcGFpZ25URVIkQ2FtcGFpZ24gPC0gZmFjdG9yKGNhbXBhaWduVEVSJENhbXBhaWduLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsZXZlbHMgPSBuYW1lcyhjYW1wYWlnbkNoYXJ0Q29sb3JzKSkKY29sbmFtZXMoY2FtcGFpZ25URVIpIDwtIGMoJ0NhbXBhaWduJywgJ0V4cGVjdGVkJywgJ1MuRC4nKQpnZ3Bsb3QoY2FtcGFpZ25URVIsIGFlcyh4ID0gQ2FtcGFpZ24sCiAgICAgICAgICAgICAgICAgICAgICAgIHkgPSBFeHBlY3RlZCwKICAgICAgICAgICAgICAgICAgICAgICAgZmlsbCA9IENhbXBhaWduLAogICAgICAgICAgICAgICAgICAgICAgICBjb2xvciA9IENhbXBhaWduLCAKICAgICAgICAgICAgICAgICAgICAgICAgbGFiZWwgPSByb3VuZChFeHBlY3RlZCwgMikpKSArCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIAogICAgICAgICAgIHBvc2l0aW9uID0gImRvZGdlIiwgCiAgICAgICAgICAgd2lkdGggPSAuNSkgKwogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBjb21tYSkgKyAKICBzY2FsZV9maWxsX21hbnVhbCgibGVnZW5kIiwgdmFsdWVzID0gY2FtcGFpZ25DaGFydENvbG9ycykgKyAKICBzY2FsZV9jb2xvcl9tYW51YWwoImxlZ2VuZCIsIHZhbHVlcyA9IGNhbXBhaWduQ2hhcnRDb2xvcnMpICsgCiAgZ2VvbV9sYWJlbChmaWxsID0gIndoaXRlIiwgY29sb3IgPSAiYmxhY2siKSArCiAgZ2d0aXRsZSgnQXV0dW1uIEJhbm5lciBDYW1wYWlnbiAyMDE3OiBVc2VyIEVkaXRzIHBlciBDYW1wYWlnbicpICsKICB0aGVtZV9taW5pbWFsKCkgKyAKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDAsIHNpemUgPSA4LCBoanVzdCA9IDEpKSArCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApKSArCiAgdGhlbWUobGVnZW5kLnRpdGxlID0gZWxlbWVudF9ibGFuaygpKSArCiAgdGhlbWUocGFuZWwuZ3JpZC5tYWpvci54ID0gZWxlbWVudF9ibGFuaygpKSArCiAgdGhlbWUocGFuZWwuZ3JpZC5taW5vci54ID0gZWxlbWVudF9ibGFuaygpKSArCiAgdGhlbWUocGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSkKYGBgCgpFeHBlY3RlZCBlZGl0cyBhbmQgdGhlIHJlc3BlY3RpdmUgc3RhbmRhcmQgZGV2aWF0aW9uczoKCmBgYHtyIGVjaG8gPSBULCB3YXJuaW5nID0gJ2hpZGUnLCBtZXNzYWdlID0gRn0KY2FtcGFpZ25URVIkRXhwZWN0ZWQgPC0gcm91bmQoY2FtcGFpZ25URVIkRXhwZWN0ZWQsIDIpCmNhbXBhaWduVEVSJGBTLkQuYCA8LSByb3VuZChjYW1wYWlnblRFUiRgUy5ELmAsIDIpCmtuaXRyOjprYWJsZShjYW1wYWlnblRFUiwgZm9ybWF0ID0gImh0bWwiKSAlPiUgCiAga2FibGVfc3R5bGluZyhmdWxsX3dpZHRoID0gRiwgcG9zaXRpb24gPSAibGVmdCIpCmBgYAoKTGV0J3MgdGFrZSBhIGNsb3NlciBsb29rIHVwb24gdGhlIGRpc3RyaWJ1dGlvbnMgb2YgdXNlciBlZGl0cyBwZXIgY2FtcGFpZ246CgpgYGB7ciBlY2hvID0gVCwgd2FybmluZyA9ICdoaWRlJywgbWVzc2FnZSA9IEZ9CiMgLSB0aGUgZGF0YXNldAplZERhdGFEaXN0IDwtIGxlZnRfam9pbihlZGl0RGF0YSwgdXNlclJlZywKICAgICAgICAgICAgICAgICAgICAgICAgYnkgPSBjKCJyZXZfdXNlciIgPSAiZXZlbnRfdXNlcklkIikpICU+JSAKICBkcGx5cjo6c2VsZWN0KGV2ZW50X2NhbXBhaWduLCBlZGl0cykKY29sbmFtZXMoZWREYXRhRGlzdCkgPC0gYygnQ2FtcGFpZ24nLCAnRWRpdHMnKQplZERhdGFEaXN0JENhbXBhaWduIDwtIHRvdXBwZXIoZ3N1Yigid21kZV9hYmMyMDE3XyIsICIiLCBlZERhdGFEaXN0JENhbXBhaWduLCBmaXhlZCA9IFQpKQplZERhdGFEaXN0JEFscGhhID0gZWREYXRhRGlzdCRFZGl0cy9tYXgoZWREYXRhRGlzdCRFZGl0cykKZ2dwbG90KGVkRGF0YURpc3QsIGFlcyh4ID0gQ2FtcGFpZ24sIHkgPSBFZGl0cywgCiAgICAgICAgICAgICAgICAgICAgICAgZ3JvdXAgPSBDYW1wYWlnbiwgCiAgICAgICAgICAgICAgICAgICAgICAgZmlsbCA9IENhbXBhaWduLAogICAgICAgICAgICAgICAgICAgICAgIGNvbG9yID0gQ2FtcGFpZ24pKSArIAogIGdlb21fcG9pbnQoYWVzKGFscGhhID0gZWREYXRhRGlzdCRBbHBoYSksIAogICAgICAgICAgICAgcG9zaXRpb24gPSAiaml0dGVyIiwgCiAgICAgICAgICAgICBzaXplID0gMS41KSArIAogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBjb21tYSkgKyAKICBzY2FsZV9maWxsX21hbnVhbCgibGVnZW5kIiwgdmFsdWVzID0gY2FtcGFpZ25DaGFydENvbG9ycykgKyAKICBzY2FsZV9jb2xvcl9tYW51YWwoImxlZ2VuZCIsIHZhbHVlcyA9IGNhbXBhaWduQ2hhcnRDb2xvcnMpICsgCiAgZ2d0aXRsZSgnQXV0dW1uIEJhbm5lciBDYW1wYWlnbiAyMDE3OiBVc2VyIEVkaXRzIERpc3RyaWJ1dGlvbnMgcGVyIENhbXBhaWduJykgKwogIHRoZW1lX21pbmltYWwoKSArIAogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gMCwgc2l6ZSA9IDgsIGhqdXN0ID0gMSkpICsKICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCkpICsKICB0aGVtZShsZWdlbmQudGl0bGUgPSBlbGVtZW50X2JsYW5rKCkpICsKICB0aGVtZShwYW5lbC5ncmlkLm1ham9yLnggPSBlbGVtZW50X2JsYW5rKCkpICsKICB0aGVtZShwYW5lbC5ncmlkLm1pbm9yLnggPSBlbGVtZW50X2JsYW5rKCkpICsKICB0aGVtZShwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpKSArIAogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICdOb25lJykKYGBgCgpOb3RlIHRoYXQgbm90IHRvbyBtYW55IG5ldyB1c2VycyBoYXZlIG1hZGUgYW55IHNpZ25pZmljYW50IG51bWJlciBvZiBlZGl0cy4gVGhpcyBmYWN0IC0gdGhlIHNjYXJjaXR5IG9mIGF2aWxhYmxlIGRhdGEgLSBpbXBvc2VzIHNldmVyYWwgY29uc3RyYWludHMgdXBvbiB0aGUgcHJlc2VudCBhbmFseXNpcy4gUGxlYXNlIHJlYWQgdGhyb3VnaCBjYXJlZnVsbHkgYW5kIGRvIG5vdCBqdW1wIHRvIGNvbmNsdXNpb25zIGJlZm9yZSBtb3JlIGRhdGEgYmVjb21lIGF2YWlsYWJsZS4KCiMjIyMjIEV2YWx1YXRpb246IEJUMSB2cy4gQlQyCgpgYGB7ciBlY2hvID0gVCwgd2FybmluZyA9ICdoaWRlJywgbWVzc2FnZSA9IEZ9CmNhbXBBIDwtICdCVDEnCmNhbXBCIDwtICdCVDInCnRlc3RBQiA8LSBwb3N0ZXJpb3JfRUVkaXRfQUIoYmFubmVyRWRQcm9iLCBjYW1wQSwgY2FtcEIsIG1jTiA9IG1jTikKIyAtIFByb2JhYmlsaXR5IG9mIGNhbXBBIGJldHRlciB0aGFuIGNhbXBCOgpwcmludChwYXN0ZSgnVGhlIHByb2JhYmlsaXR5IG9mICcsIAogICAgICAgICAgICBjYW1wQSwgIAogICAgICAgICAgICAnIGluZmx1ZW5jaW5nIG1vcmUgdXNlciBlZGl0cyB0aGFuICcsIAogICAgICAgICAgICBjYW1wQiwgJyBpcyA6ICcsIAogICAgICAgICAgICByb3VuZCh0ZXN0QUIkcHJvYmFiaWxpdHksIDIpLCAKICAgICAgICAgICAgc2VwID0gIiIpKQojIC0gbGlmdDoKcHJpbnQocGFzdGUoJ1RoZSBwZXJjZW50IGxpZnQgdGhhdCAnLCAKICAgICAgICAgICAgY2FtcEEsIAogICAgICAgICAgICAnIGhhcyBvdmVyICcsIAogICAgICAgICAgICBjYW1wQiwgCiAgICAgICAgICAgICcgKCcsCiAgICAgICAgICAgIHJvdW5kKHRlc3RBQiR0X3BlcmNlbnREaWZmLCAyKSwKICAgICAgICAgICAgJyUpIGxpZXMgaW4gdGhlIGludGVydmFsICgnLCAKICAgICAgICAgICAgYXMuY2hhcmFjdGVyKHJvdW5kKHF1YW50aWxlKHRlc3RBQiRwZXJjZW50RGlmZiRwZXJjZW50RGlmZiwgLjAyNSkpLCAyKSwgCiAgICAgICAgICAgICclLCAnLCAKICAgICAgICAgICAgYXMuY2hhcmFjdGVyKHJvdW5kKHF1YW50aWxlKHRlc3RBQiRwZXJjZW50RGlmZiRwZXJjZW50RGlmZiwgLjk3NSkpLCAyKSwKICAgICAgICAgICAgJyUpIHdpdGggOTUlIGNlcnRhaW50eS4nLAogICAgICAgICAgICBzZXAgPSAiIikpCmdncGxvdCh0ZXN0QUIkcGVyY2VudERpZmYsIAogICAgICAgYWVzKHggPSBwZXJjZW50RGlmZiwKICAgICAgICAgICBncm91cCA9IGFyZWEsCiAgICAgICAgICAgZmlsbCA9IGFyZWEpKSArIAogIGdlb21faGlzdG9ncmFtKGJpbnMgPSAxMDAwLCBhbHBoYSA9IC41KSArIAogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBjb21tYSkgKwogIHhsYWIocGFzdGUoJygnLCBjYW1wQSwgJy0nLCBjYW1wQiwgJykvJywgY2FtcEIsIHNlcCA9ICIiKSkgKyB5bGFiKCdEZW5zaXR5JykgKyAKICBnZ3RpdGxlKHBhc3RlKGNhbXBBLCAnLycsIGNhbXBCLCAnIENhbXBhaWduIExpZnQnLCBzZXAgPSAiIikpICsKICB0aGVtZV9taW5pbWFsKCkgKyAKICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCkpCmBgYAoKIyMjIyMgRXZhbHVhdGlvbjogQlQxIHZzLiBCVDMKCmBgYHtyIGVjaG8gPSBULCB3YXJuaW5nID0gJ2hpZGUnLCBtZXNzYWdlID0gRn0KY2FtcEEgPC0gJ0JUMScKY2FtcEIgPC0gJ0JUMycKdGVzdEFCIDwtIHBvc3Rlcmlvcl9FRWRpdF9BQihiYW5uZXJFZFByb2IsIGNhbXBBLCBjYW1wQiwgbWNOID0gbWNOKQojIC0gUHJvYmFiaWxpdHkgb2YgY2FtcEEgYmV0dGVyIHRoYW4gY2FtcEI6CnByaW50KHBhc3RlKCdUaGUgcHJvYmFiaWxpdHkgb2YgJywgCiAgICAgICAgICAgIGNhbXBBLCAgCiAgICAgICAgICAgICcgaW5mbHVlbmNpbmcgbW9yZSB1c2VyIGVkaXRzIHRoYW4gJywgCiAgICAgICAgICAgIGNhbXBCLCAnIGlzIDogJywgCiAgICAgICAgICAgIHJvdW5kKHRlc3RBQiRwcm9iYWJpbGl0eSwgMiksIAogICAgICAgICAgICBzZXAgPSAiIikpCiMgLSBsaWZ0OgpwcmludChwYXN0ZSgnVGhlIHBlcmNlbnQgbGlmdCB0aGF0ICcsIAogICAgICAgICAgICBjYW1wQSwgCiAgICAgICAgICAgICcgaGFzIG92ZXIgJywgCiAgICAgICAgICAgIGNhbXBCLCAKICAgICAgICAgICAgJyAoJywKICAgICAgICAgICAgcm91bmQodGVzdEFCJHRfcGVyY2VudERpZmYsIDIpLAogICAgICAgICAgICAnJSkgbGllcyBpbiB0aGUgaW50ZXJ2YWwgKCcsIAogICAgICAgICAgICBhcy5jaGFyYWN0ZXIocm91bmQocXVhbnRpbGUodGVzdEFCJHBlcmNlbnREaWZmJHBlcmNlbnREaWZmLCAuMDI1KSksIDIpLCAKICAgICAgICAgICAgJyUsICcsIAogICAgICAgICAgICBhcy5jaGFyYWN0ZXIocm91bmQocXVhbnRpbGUodGVzdEFCJHBlcmNlbnREaWZmJHBlcmNlbnREaWZmLCAuOTc1KSksIDIpLAogICAgICAgICAgICAnJSkgd2l0aCA5NSUgY2VydGFpbnR5LicsCiAgICAgICAgICAgIHNlcCA9ICIiKSkKZ2dwbG90KHRlc3RBQiRwZXJjZW50RGlmZiwgCiAgICAgICBhZXMoeCA9IHBlcmNlbnREaWZmLAogICAgICAgICAgIGdyb3VwID0gYXJlYSwKICAgICAgICAgICBmaWxsID0gYXJlYSkpICsgCiAgZ2VvbV9oaXN0b2dyYW0oYmlucyA9IDEwMDAsIGFscGhhID0gLjUpICsgCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IGNvbW1hKSArCiAgeGxhYihwYXN0ZSgnKCcsIGNhbXBBLCAnLScsIGNhbXBCLCAnKS8nLCBjYW1wQiwgc2VwID0gIiIpKSArIHlsYWIoJ0RlbnNpdHknKSArIAogIGdndGl0bGUocGFzdGUoY2FtcEEsICcvJywgY2FtcEIsICcgQ2FtcGFpZ24gTGlmdCcsIHNlcCA9ICIiKSkgKwogIHRoZW1lX21pbmltYWwoKSArIAogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSkKYGBgCgojIyMjIyBFdmFsdWF0aW9uOiBCVDEgdnMuIEdJQl9MUAoKYGBge3IgZWNobyA9IFQsIHdhcm5pbmcgPSAnaGlkZScsIG1lc3NhZ2UgPSBGfQpjYW1wQSA8LSAnQlQxJwpjYW1wQiA8LSAnR0lCX0xQJwp0ZXN0QUIgPC0gcG9zdGVyaW9yX0VFZGl0X0FCKGJhbm5lckVkUHJvYiwgY2FtcEEsIGNhbXBCLCBtY04gPSBtY04pCiMgLSBQcm9iYWJpbGl0eSBvZiBjYW1wQSBiZXR0ZXIgdGhhbiBjYW1wQjoKcHJpbnQocGFzdGUoJ1RoZSBwcm9iYWJpbGl0eSBvZiAnLCAKICAgICAgICAgICAgY2FtcEEsICAKICAgICAgICAgICAgJyBpbmZsdWVuY2luZyBtb3JlIHVzZXIgZWRpdHMgdGhhbiAnLCAKICAgICAgICAgICAgY2FtcEIsICcgaXMgOiAnLCAKICAgICAgICAgICAgcm91bmQodGVzdEFCJHByb2JhYmlsaXR5LCAyKSwgCiAgICAgICAgICAgIHNlcCA9ICIiKSkKIyAtIGxpZnQ6CnByaW50KHBhc3RlKCdUaGUgcGVyY2VudCBsaWZ0IHRoYXQgJywgCiAgICAgICAgICAgIGNhbXBBLCAKICAgICAgICAgICAgJyBoYXMgb3ZlciAnLCAKICAgICAgICAgICAgY2FtcEIsIAogICAgICAgICAgICAnICgnLAogICAgICAgICAgICByb3VuZCh0ZXN0QUIkdF9wZXJjZW50RGlmZiwgMiksCiAgICAgICAgICAgICclKSBsaWVzIGluIHRoZSBpbnRlcnZhbCAoJywgCiAgICAgICAgICAgIGFzLmNoYXJhY3Rlcihyb3VuZChxdWFudGlsZSh0ZXN0QUIkcGVyY2VudERpZmYkcGVyY2VudERpZmYsIC4wMjUpKSwgMiksIAogICAgICAgICAgICAnJSwgJywgCiAgICAgICAgICAgIGFzLmNoYXJhY3Rlcihyb3VuZChxdWFudGlsZSh0ZXN0QUIkcGVyY2VudERpZmYkcGVyY2VudERpZmYsIC45NzUpKSwgMiksCiAgICAgICAgICAgICclKSB3aXRoIDk1JSBjZXJ0YWludHkuJywKICAgICAgICAgICAgc2VwID0gIiIpKQpnZ3Bsb3QodGVzdEFCJHBlcmNlbnREaWZmLCAKICAgICAgIGFlcyh4ID0gcGVyY2VudERpZmYsCiAgICAgICAgICAgZ3JvdXAgPSBhcmVhLAogICAgICAgICAgIGZpbGwgPSBhcmVhKSkgKyAKICBnZW9tX2hpc3RvZ3JhbShiaW5zID0gMTAwMCwgYWxwaGEgPSAuNSkgKyAKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gY29tbWEpICsKICB4bGFiKHBhc3RlKCcoJywgY2FtcEEsICctJywgY2FtcEIsICcpLycsIGNhbXBCLCBzZXAgPSAiIikpICsgeWxhYignRGVuc2l0eScpICsgCiAgZ2d0aXRsZShwYXN0ZShjYW1wQSwgJy8nLCBjYW1wQiwgJyBDYW1wYWlnbiBMaWZ0Jywgc2VwID0gIiIpKSArCiAgdGhlbWVfbWluaW1hbCgpICsgCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApKQpgYGAKCiMjIyMjIEV2YWx1YXRpb246IEJUMSB2cy4gR0lCX1JHCgpgYGB7ciBlY2hvID0gVCwgd2FybmluZyA9ICdoaWRlJywgbWVzc2FnZSA9IEZ9CmNhbXBBIDwtICdCVDEnCmNhbXBCIDwtICdHSUJfUkcnCnRlc3RBQiA8LSBwb3N0ZXJpb3JfRUVkaXRfQUIoYmFubmVyRWRQcm9iLCBjYW1wQSwgY2FtcEIsIG1jTiA9IG1jTikKIyAtIFByb2JhYmlsaXR5IG9mIGNhbXBBIGJldHRlciB0aGFuIGNhbXBCOgpwcmludChwYXN0ZSgnVGhlIHByb2JhYmlsaXR5IG9mICcsIAogICAgICAgICAgICBjYW1wQSwgIAogICAgICAgICAgICAnIGluZmx1ZW5jaW5nIG1vcmUgdXNlciBlZGl0cyB0aGFuICcsIAogICAgICAgICAgICBjYW1wQiwgJyBpcyA6ICcsIAogICAgICAgICAgICByb3VuZCh0ZXN0QUIkcHJvYmFiaWxpdHksIDIpLCAKICAgICAgICAgICAgc2VwID0gIiIpKQojIC0gbGlmdDoKcHJpbnQocGFzdGUoJ1RoZSBwZXJjZW50IGxpZnQgdGhhdCAnLCAKICAgICAgICAgICAgY2FtcEEsIAogICAgICAgICAgICAnIGhhcyBvdmVyICcsIAogICAgICAgICAgICBjYW1wQiwgCiAgICAgICAgICAgICcgKCcsCiAgICAgICAgICAgIHJvdW5kKHRlc3RBQiR0X3BlcmNlbnREaWZmLCAyKSwKICAgICAgICAgICAgJyUpIGxpZXMgaW4gdGhlIGludGVydmFsICgnLCAKICAgICAgICAgICAgYXMuY2hhcmFjdGVyKHJvdW5kKHF1YW50aWxlKHRlc3RBQiRwZXJjZW50RGlmZiRwZXJjZW50RGlmZiwgLjAyNSkpLCAyKSwgCiAgICAgICAgICAgICclLCAnLCAKICAgICAgICAgICAgYXMuY2hhcmFjdGVyKHJvdW5kKHF1YW50aWxlKHRlc3RBQiRwZXJjZW50RGlmZiRwZXJjZW50RGlmZiwgLjk3NSkpLCAyKSwKICAgICAgICAgICAgJyUpIHdpdGggOTUlIGNlcnRhaW50eS4nLAogICAgICAgICAgICBzZXAgPSAiIikpCmdncGxvdCh0ZXN0QUIkcGVyY2VudERpZmYsIAogICAgICAgYWVzKHggPSBwZXJjZW50RGlmZiwKICAgICAgICAgICBncm91cCA9IGFyZWEsCiAgICAgICAgICAgZmlsbCA9IGFyZWEpKSArIAogIGdlb21faGlzdG9ncmFtKGJpbnMgPSAxMDAwLCBhbHBoYSA9IC41KSArIAogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBjb21tYSkgKwogIHhsYWIocGFzdGUoJygnLCBjYW1wQSwgJy0nLCBjYW1wQiwgJykvJywgY2FtcEIsIHNlcCA9ICIiKSkgKyB5bGFiKCdEZW5zaXR5JykgKyAKICBnZ3RpdGxlKHBhc3RlKGNhbXBBLCAnLycsIGNhbXBCLCAnIENhbXBhaWduIExpZnQnLCBzZXAgPSAiIikpICsKICB0aGVtZV9taW5pbWFsKCkgKyAKICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCkpCmBgYAoKIyMjIyMgRXZhbHVhdGlvbjogQlQyIHZzLiBCVDMKCmBgYHtyIGVjaG8gPSBULCB3YXJuaW5nID0gJ2hpZGUnLCBtZXNzYWdlID0gRn0KY2FtcEEgPC0gJ0JUMicKY2FtcEIgPC0gJ0JUMycKdGVzdEFCIDwtIHBvc3Rlcmlvcl9FRWRpdF9BQihiYW5uZXJFZFByb2IsIGNhbXBBLCBjYW1wQiwgbWNOID0gbWNOKQojIC0gUHJvYmFiaWxpdHkgb2YgY2FtcEEgYmV0dGVyIHRoYW4gY2FtcEI6CnByaW50KHBhc3RlKCdUaGUgcHJvYmFiaWxpdHkgb2YgJywgCiAgICAgICAgICAgIGNhbXBBLCAgCiAgICAgICAgICAgICcgaW5mbHVlbmNpbmcgbW9yZSB1c2VyIGVkaXRzIHRoYW4gJywgCiAgICAgICAgICAgIGNhbXBCLCAnIGlzIDogJywgCiAgICAgICAgICAgIHJvdW5kKHRlc3RBQiRwcm9iYWJpbGl0eSwgMiksIAogICAgICAgICAgICBzZXAgPSAiIikpCiMgLSBsaWZ0OgpwcmludChwYXN0ZSgnVGhlIHBlcmNlbnQgbGlmdCB0aGF0ICcsIAogICAgICAgICAgICBjYW1wQSwgCiAgICAgICAgICAgICcgaGFzIG92ZXIgJywgCiAgICAgICAgICAgIGNhbXBCLCAKICAgICAgICAgICAgJyAoJywKICAgICAgICAgICAgcm91bmQodGVzdEFCJHRfcGVyY2VudERpZmYsIDIpLAogICAgICAgICAgICAnJSkgbGllcyBpbiB0aGUgaW50ZXJ2YWwgKCcsIAogICAgICAgICAgICBhcy5jaGFyYWN0ZXIocm91bmQocXVhbnRpbGUodGVzdEFCJHBlcmNlbnREaWZmJHBlcmNlbnREaWZmLCAuMDI1KSksIDIpLCAKICAgICAgICAgICAgJyUsICcsIAogICAgICAgICAgICBhcy5jaGFyYWN0ZXIocm91bmQocXVhbnRpbGUodGVzdEFCJHBlcmNlbnREaWZmJHBlcmNlbnREaWZmLCAuOTc1KSksIDIpLAogICAgICAgICAgICAnJSkgd2l0aCA5NSUgY2VydGFpbnR5LicsCiAgICAgICAgICAgIHNlcCA9ICIiKSkKZ2dwbG90KHRlc3RBQiRwZXJjZW50RGlmZiwgCiAgICAgICBhZXMoeCA9IHBlcmNlbnREaWZmLAogICAgICAgICAgIGdyb3VwID0gYXJlYSwKICAgICAgICAgICBmaWxsID0gYXJlYSkpICsgCiAgZ2VvbV9oaXN0b2dyYW0oYmlucyA9IDEwMDAsIGFscGhhID0gLjUpICsgCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IGNvbW1hKSArCiAgeGxhYihwYXN0ZSgnKCcsIGNhbXBBLCAnLScsIGNhbXBCLCAnKS8nLCBjYW1wQiwgc2VwID0gIiIpKSArIHlsYWIoJ0RlbnNpdHknKSArIAogIGdndGl0bGUocGFzdGUoY2FtcEEsICcvJywgY2FtcEIsICcgQ2FtcGFpZ24gTGlmdCcsIHNlcCA9ICIiKSkgKwogIHRoZW1lX21pbmltYWwoKSArIAogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSkKYGBgCgojIyMjIyBFdmFsdWF0aW9uOiBCVDIgdnMuIEdJQl9MUAoKYGBge3IgZWNobyA9IFQsIHdhcm5pbmcgPSAnaGlkZScsIG1lc3NhZ2UgPSBGfQpjYW1wQSA8LSAnQlQyJwpjYW1wQiA8LSAnR0lCX0xQJwp0ZXN0QUIgPC0gcG9zdGVyaW9yX0VFZGl0X0FCKGJhbm5lckVkUHJvYiwgY2FtcEEsIGNhbXBCLCBtY04gPSBtY04pCiMgLSBQcm9iYWJpbGl0eSBvZiBjYW1wQSBiZXR0ZXIgdGhhbiBjYW1wQjoKcHJpbnQocGFzdGUoJ1RoZSBwcm9iYWJpbGl0eSBvZiAnLCAKICAgICAgICAgICAgY2FtcEEsICAKICAgICAgICAgICAgJyBpbmZsdWVuY2luZyBtb3JlIHVzZXIgZWRpdHMgdGhhbiAnLCAKICAgICAgICAgICAgY2FtcEIsICcgaXMgOiAnLCAKICAgICAgICAgICAgcm91bmQodGVzdEFCJHByb2JhYmlsaXR5LCAyKSwgCiAgICAgICAgICAgIHNlcCA9ICIiKSkKIyAtIGxpZnQ6CnByaW50KHBhc3RlKCdUaGUgcGVyY2VudCBsaWZ0IHRoYXQgJywgCiAgICAgICAgICAgIGNhbXBBLCAKICAgICAgICAgICAgJyBoYXMgb3ZlciAnLCAKICAgICAgICAgICAgY2FtcEIsIAogICAgICAgICAgICAnICgnLAogICAgICAgICAgICByb3VuZCh0ZXN0QUIkdF9wZXJjZW50RGlmZiwgMiksCiAgICAgICAgICAgICclKSBsaWVzIGluIHRoZSBpbnRlcnZhbCAoJywgCiAgICAgICAgICAgIGFzLmNoYXJhY3Rlcihyb3VuZChxdWFudGlsZSh0ZXN0QUIkcGVyY2VudERpZmYkcGVyY2VudERpZmYsIC4wMjUpKSwgMiksIAogICAgICAgICAgICAnJSwgJywgCiAgICAgICAgICAgIGFzLmNoYXJhY3Rlcihyb3VuZChxdWFudGlsZSh0ZXN0QUIkcGVyY2VudERpZmYkcGVyY2VudERpZmYsIC45NzUpKSwgMiksCiAgICAgICAgICAgICclKSB3aXRoIDk1JSBjZXJ0YWludHkuJywKICAgICAgICAgICAgc2VwID0gIiIpKQpnZ3Bsb3QodGVzdEFCJHBlcmNlbnREaWZmLCAKICAgICAgIGFlcyh4ID0gcGVyY2VudERpZmYsCiAgICAgICAgICAgZ3JvdXAgPSBhcmVhLAogICAgICAgICAgIGZpbGwgPSBhcmVhKSkgKyAKICBnZW9tX2hpc3RvZ3JhbShiaW5zID0gMTAwMCwgYWxwaGEgPSAuNSkgKyAKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gY29tbWEpICsKICB4bGFiKHBhc3RlKCcoJywgY2FtcEEsICctJywgY2FtcEIsICcpLycsIGNhbXBCLCBzZXAgPSAiIikpICsgeWxhYignRGVuc2l0eScpICsgCiAgZ2d0aXRsZShwYXN0ZShjYW1wQSwgJy8nLCBjYW1wQiwgJyBDYW1wYWlnbiBMaWZ0Jywgc2VwID0gIiIpKSArCiAgdGhlbWVfbWluaW1hbCgpICsgCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApKQpgYGAKCiMjIyMjIEV2YWx1YXRpb246IEJUMiB2cy4gR0lCX1JHCgpgYGB7ciBlY2hvID0gVCwgd2FybmluZyA9ICdoaWRlJywgbWVzc2FnZSA9IEZ9CmNhbXBBIDwtICdCVDInCmNhbXBCIDwtICdHSUJfUkcnCnRlc3RBQiA8LSBwb3N0ZXJpb3JfRUVkaXRfQUIoYmFubmVyRWRQcm9iLCBjYW1wQSwgY2FtcEIsIG1jTiA9IG1jTikKIyAtIFByb2JhYmlsaXR5IG9mIGNhbXBBIGJldHRlciB0aGFuIGNhbXBCOgpwcmludChwYXN0ZSgnVGhlIHByb2JhYmlsaXR5IG9mICcsIAogICAgICAgICAgICBjYW1wQSwgIAogICAgICAgICAgICAnIGluZmx1ZW5jaW5nIG1vcmUgdXNlciBlZGl0cyB0aGFuICcsIAogICAgICAgICAgICBjYW1wQiwgJyBpcyA6ICcsIAogICAgICAgICAgICByb3VuZCh0ZXN0QUIkcHJvYmFiaWxpdHksIDIpLCAKICAgICAgICAgICAgc2VwID0gIiIpKQojIC0gbGlmdDoKcHJpbnQocGFzdGUoJ1RoZSBwZXJjZW50IGxpZnQgdGhhdCAnLCAKICAgICAgICAgICAgY2FtcEEsIAogICAgICAgICAgICAnIGhhcyBvdmVyICcsIAogICAgICAgICAgICBjYW1wQiwgCiAgICAgICAgICAgICcgKCcsCiAgICAgICAgICAgIHJvdW5kKHRlc3RBQiR0X3BlcmNlbnREaWZmLCAyKSwKICAgICAgICAgICAgJyUpIGxpZXMgaW4gdGhlIGludGVydmFsICgnLCAKICAgICAgICAgICAgYXMuY2hhcmFjdGVyKHJvdW5kKHF1YW50aWxlKHRlc3RBQiRwZXJjZW50RGlmZiRwZXJjZW50RGlmZiwgLjAyNSkpLCAyKSwgCiAgICAgICAgICAgICclLCAnLCAKICAgICAgICAgICAgYXMuY2hhcmFjdGVyKHJvdW5kKHF1YW50aWxlKHRlc3RBQiRwZXJjZW50RGlmZiRwZXJjZW50RGlmZiwgLjk3NSkpLCAyKSwKICAgICAgICAgICAgJyUpIHdpdGggOTUlIGNlcnRhaW50eS4nLAogICAgICAgICAgICBzZXAgPSAiIikpCmdncGxvdCh0ZXN0QUIkcGVyY2VudERpZmYsIAogICAgICAgYWVzKHggPSBwZXJjZW50RGlmZiwKICAgICAgICAgICBncm91cCA9IGFyZWEsCiAgICAgICAgICAgZmlsbCA9IGFyZWEpKSArIAogIGdlb21faGlzdG9ncmFtKGJpbnMgPSAxMDAwLCBhbHBoYSA9IC41KSArIAogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBjb21tYSkgKwogIHhsYWIocGFzdGUoJygnLCBjYW1wQSwgJy0nLCBjYW1wQiwgJykvJywgY2FtcEIsIHNlcCA9ICIiKSkgKyB5bGFiKCdEZW5zaXR5JykgKyAKICBnZ3RpdGxlKHBhc3RlKGNhbXBBLCAnLycsIGNhbXBCLCAnIENhbXBhaWduIExpZnQnLCBzZXAgPSAiIikpICsKICB0aGVtZV9taW5pbWFsKCkgKyAKICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCkpCmBgYAoKIyMjIyMgRXZhbHVhdGlvbjogQlQzIHZzLiBHSUJfTFAKCmBgYHtyIGVjaG8gPSBULCB3YXJuaW5nID0gJ2hpZGUnLCBtZXNzYWdlID0gRn0KY2FtcEEgPC0gJ0JUMycKY2FtcEIgPC0gJ0dJQl9MUCcKdGVzdEFCIDwtIHBvc3Rlcmlvcl9FRWRpdF9BQihiYW5uZXJFZFByb2IsIGNhbXBBLCBjYW1wQiwgbWNOID0gbWNOKQojIC0gUHJvYmFiaWxpdHkgb2YgY2FtcEEgYmV0dGVyIHRoYW4gY2FtcEI6CnByaW50KHBhc3RlKCdUaGUgcHJvYmFiaWxpdHkgb2YgJywgCiAgICAgICAgICAgIGNhbXBBLCAgCiAgICAgICAgICAgICcgaW5mbHVlbmNpbmcgbW9yZSB1c2VyIGVkaXRzIHRoYW4gJywgCiAgICAgICAgICAgIGNhbXBCLCAnIGlzIDogJywgCiAgICAgICAgICAgIHJvdW5kKHRlc3RBQiRwcm9iYWJpbGl0eSwgMiksIAogICAgICAgICAgICBzZXAgPSAiIikpCiMgLSBsaWZ0OgpwcmludChwYXN0ZSgnVGhlIHBlcmNlbnQgbGlmdCB0aGF0ICcsIAogICAgICAgICAgICBjYW1wQSwgCiAgICAgICAgICAgICcgaGFzIG92ZXIgJywgCiAgICAgICAgICAgIGNhbXBCLCAKICAgICAgICAgICAgJyAoJywKICAgICAgICAgICAgcm91bmQodGVzdEFCJHRfcGVyY2VudERpZmYsIDIpLAogICAgICAgICAgICAnJSkgbGllcyBpbiB0aGUgaW50ZXJ2YWwgKCcsIAogICAgICAgICAgICBhcy5jaGFyYWN0ZXIocm91bmQocXVhbnRpbGUodGVzdEFCJHBlcmNlbnREaWZmJHBlcmNlbnREaWZmLCAuMDI1KSksIDIpLCAKICAgICAgICAgICAgJyUsICcsIAogICAgICAgICAgICBhcy5jaGFyYWN0ZXIocm91bmQocXVhbnRpbGUodGVzdEFCJHBlcmNlbnREaWZmJHBlcmNlbnREaWZmLCAuOTc1KSksIDIpLAogICAgICAgICAgICAnJSkgd2l0aCA5NSUgY2VydGFpbnR5LicsCiAgICAgICAgICAgIHNlcCA9ICIiKSkKZ2dwbG90KHRlc3RBQiRwZXJjZW50RGlmZiwgCiAgICAgICBhZXMoeCA9IHBlcmNlbnREaWZmLAogICAgICAgICAgIGdyb3VwID0gYXJlYSwKICAgICAgICAgICBmaWxsID0gYXJlYSkpICsgCiAgZ2VvbV9oaXN0b2dyYW0oYmlucyA9IDEwMDAsIGFscGhhID0gLjUpICsgCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IGNvbW1hKSArCiAgeGxhYihwYXN0ZSgnKCcsIGNhbXBBLCAnLScsIGNhbXBCLCAnKS8nLCBjYW1wQiwgc2VwID0gIiIpKSArIHlsYWIoJ0RlbnNpdHknKSArIAogIGdndGl0bGUocGFzdGUoY2FtcEEsICcvJywgY2FtcEIsICcgQ2FtcGFpZ24gTGlmdCcsIHNlcCA9ICIiKSkgKwogIHRoZW1lX21pbmltYWwoKSArIAogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSkKYGBgCgojIyMjIyBFdmFsdWF0aW9uOiBCVDMgdnMuIEdJQl9SRwoKYGBge3IgZWNobyA9IFQsIHdhcm5pbmcgPSAnaGlkZScsIG1lc3NhZ2UgPSBGfQpjYW1wQSA8LSAnQlQzJwpjYW1wQiA8LSAnR0lCX1JHJwp0ZXN0QUIgPC0gcG9zdGVyaW9yX0VFZGl0X0FCKGJhbm5lckVkUHJvYiwgY2FtcEEsIGNhbXBCLCBtY04gPSBtY04pCiMgLSBQcm9iYWJpbGl0eSBvZiBjYW1wQSBiZXR0ZXIgdGhhbiBjYW1wQjoKcHJpbnQocGFzdGUoJ1RoZSBwcm9iYWJpbGl0eSBvZiAnLCAKICAgICAgICAgICAgY2FtcEEsICAKICAgICAgICAgICAgJyBpbmZsdWVuY2luZyBtb3JlIHVzZXIgZWRpdHMgdGhhbiAnLCAKICAgICAgICAgICAgY2FtcEIsICcgaXMgOiAnLCAKICAgICAgICAgICAgcm91bmQodGVzdEFCJHByb2JhYmlsaXR5LCAyKSwgCiAgICAgICAgICAgIHNlcCA9ICIiKSkKIyAtIGxpZnQ6CnByaW50KHBhc3RlKCdUaGUgcGVyY2VudCBsaWZ0IHRoYXQgJywgCiAgICAgICAgICAgIGNhbXBBLCAKICAgICAgICAgICAgJyBoYXMgb3ZlciAnLCAKICAgICAgICAgICAgY2FtcEIsIAogICAgICAgICAgICAnICgnLAogICAgICAgICAgICByb3VuZCh0ZXN0QUIkdF9wZXJjZW50RGlmZiwgMiksCiAgICAgICAgICAgICclKSBsaWVzIGluIHRoZSBpbnRlcnZhbCAoJywgCiAgICAgICAgICAgIGFzLmNoYXJhY3Rlcihyb3VuZChxdWFudGlsZSh0ZXN0QUIkcGVyY2VudERpZmYkcGVyY2VudERpZmYsIC4wMjUpKSwgMiksIAogICAgICAgICAgICAnJSwgJywgCiAgICAgICAgICAgIGFzLmNoYXJhY3Rlcihyb3VuZChxdWFudGlsZSh0ZXN0QUIkcGVyY2VudERpZmYkcGVyY2VudERpZmYsIC45NzUpKSwgMiksCiAgICAgICAgICAgICclKSB3aXRoIDk1JSBjZXJ0YWludHkuJywKICAgICAgICAgICAgc2VwID0gIiIpKQpnZ3Bsb3QodGVzdEFCJHBlcmNlbnREaWZmLCAKICAgICAgIGFlcyh4ID0gcGVyY2VudERpZmYsCiAgICAgICAgICAgZ3JvdXAgPSBhcmVhLAogICAgICAgICAgIGZpbGwgPSBhcmVhKSkgKyAKICBnZW9tX2hpc3RvZ3JhbShiaW5zID0gMTAwMCwgYWxwaGEgPSAuNSkgKyAKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gY29tbWEpICsKICB4bGFiKHBhc3RlKCcoJywgY2FtcEEsICctJywgY2FtcEIsICcpLycsIGNhbXBCLCBzZXAgPSAiIikpICsgeWxhYignRGVuc2l0eScpICsgCiAgZ2d0aXRsZShwYXN0ZShjYW1wQSwgJy8nLCBjYW1wQiwgJyBDYW1wYWlnbiBMaWZ0Jywgc2VwID0gIiIpKSArCiAgdGhlbWVfbWluaW1hbCgpICsgCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApKQpgYGAKCiMjIyMjIEV2YWx1YXRpb246IEdJQl9MUCB2cy4gR0lCX1JHCgpgYGB7ciBlY2hvID0gVCwgd2FybmluZyA9ICdoaWRlJywgbWVzc2FnZSA9IEZ9CmNhbXBBIDwtICdHSUJfTFAnCmNhbXBCIDwtICdHSUJfUkcnCnRlc3RBQiA8LSBwb3N0ZXJpb3JfRUVkaXRfQUIoYmFubmVyRWRQcm9iLCBjYW1wQSwgY2FtcEIsIG1jTiA9IG1jTikKIyAtIFByb2JhYmlsaXR5IG9mIGNhbXBBIGJldHRlciB0aGFuIGNhbXBCOgpwcmludChwYXN0ZSgnVGhlIHByb2JhYmlsaXR5IG9mICcsIAogICAgICAgICAgICBjYW1wQSwgIAogICAgICAgICAgICAnIGluZmx1ZW5jaW5nIG1vcmUgdXNlciBlZGl0cyB0aGFuICcsIAogICAgICAgICAgICBjYW1wQiwgJyBpcyA6ICcsIAogICAgICAgICAgICByb3VuZCh0ZXN0QUIkcHJvYmFiaWxpdHksIDIpLCAKICAgICAgICAgICAgc2VwID0gIiIpKQojIC0gbGlmdDoKcHJpbnQocGFzdGUoJ1RoZSBwZXJjZW50IGxpZnQgdGhhdCAnLCAKICAgICAgICAgICAgY2FtcEEsIAogICAgICAgICAgICAnIGhhcyBvdmVyICcsIAogICAgICAgICAgICBjYW1wQiwgCiAgICAgICAgICAgICcgKCcsCiAgICAgICAgICAgIHJvdW5kKHRlc3RBQiR0X3BlcmNlbnREaWZmLCAyKSwKICAgICAgICAgICAgJyUpIGxpZXMgaW4gdGhlIGludGVydmFsICgnLCAKICAgICAgICAgICAgYXMuY2hhcmFjdGVyKHJvdW5kKHF1YW50aWxlKHRlc3RBQiRwZXJjZW50RGlmZiRwZXJjZW50RGlmZiwgLjAyNSkpLCAyKSwgCiAgICAgICAgICAgICclLCAnLCAKICAgICAgICAgICAgYXMuY2hhcmFjdGVyKHJvdW5kKHF1YW50aWxlKHRlc3RBQiRwZXJjZW50RGlmZiRwZXJjZW50RGlmZiwgLjk3NSkpLCAyKSwKICAgICAgICAgICAgJyUpIHdpdGggOTUlIGNlcnRhaW50eS4nLAogICAgICAgICAgICBzZXAgPSAiIikpCmdncGxvdCh0ZXN0QUIkcGVyY2VudERpZmYsIAogICAgICAgYWVzKHggPSBwZXJjZW50RGlmZiwKICAgICAgICAgICBncm91cCA9IGFyZWEsCiAgICAgICAgICAgZmlsbCA9IGFyZWEpKSArIAogIGdlb21faGlzdG9ncmFtKGJpbnMgPSAxMDAwLCBhbHBoYSA9IC41KSArIAogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBjb21tYSkgKwogIHhsYWIocGFzdGUoJygnLCBjYW1wQSwgJy0nLCBjYW1wQiwgJykvJywgY2FtcEIsIHNlcCA9ICIiKSkgKyB5bGFiKCdEZW5zaXR5JykgKyAKICBnZ3RpdGxlKHBhc3RlKGNhbXBBLCAnLycsIGNhbXBCLCAnIENhbXBhaWduIExpZnQnLCBzZXAgPSAiIikpICsKICB0aGVtZV9taW5pbWFsKCkgKyAKICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCkpCmBgYAoKIyMjIDUuMiBDYW1wYWlnbiBNdWx0aS1DaGFubmVsIEF0dHJpYnV0aW9uIE1vZGVsOiBNYWtpbmcgYW4gRWRpdAoKVGhlIGZvbGxvd2luZyBtb2RlbCBwcm92aWRlcyBmb3IgdGhlICpyZW1vdmFsIGVmZmVjdHMqIG9mIHRoZSBDYW1wYWlnbiBjaGFubmVscyAqaW4gcmVzcGVjdCB0byB3aGV0aGVyIGEgdXNlciBoYXMgbWFkZSBhbnkgZWRpdHMgYXQgYWxsIG9yIG5vdCouIFRoaXMgcHJvY2VkdXJlIGluc3RhbnRpYXRlcyBhIG1vZGVsIG9mIGEgcGFydGljdWxhciBjYW1wYWlnbiBhcyBhIGRpcmVjdGVkIGdyYXBoIGluIHdoaWNoIGV2ZXJ5IG5vZGUgcmVwcmVzZW50cyBhIGNhbXBhaWduIGNoYW5uZWwgKGUuZy4gYSBiYW5uZXIsIGEgcGFnZSB2aWV3LCBhbiBhY3Qgb2YgYSB1c2VyIGRvaW5nIHNvbWV0aGluZywgZXRjKSwgYW5kIHRoZW4gY29tcHV0ZXMgdGhlIHByb2JhYmlsaXRpZXMgb2YgdHJhbnNpdGlvbiBmcm9tIG9uZSB0byBhbm90aGVyIGNoYW5uZWwuIEluIG90aGVyIHdvcmRzLCB0aGUgbW9kZWwgZXN0aW1hdGVzIHRoZSBwcm9iYWJpbGl0aWVzIG9mIHRha2luZyBhbnkgb2YgdGhlIHBvc3NpYmxlIHVzZXIgam91cm5leXMgaW4gdGhlIGNhbXBhaWduLiBPbmNlIHRoZSBtb2RlbCBpcyByZWFkeSwgdGhlIHByb2NlZHVyZSBzaW11bGF0ZXMgYSBsYXJnZSBudW1iZXIgb2YgdXNlciBqb3VybmV5cyB0byBwcm9kdWNlIGFuIGVzdGltYXRlIG9mIHRoZSBwcm9iYWJpbGl0eSBvZiBjb252ZXJzaW9uIGZvciBlYWNoIG9mIHRoZW0uIEluIHRoZSBjYXNlIG9mIG91ciBjYW1wYWlnbiB3ZSBjb25zaWRlciB0aGUgZXZlbnQgb2YgYSB1c2VyIG1ha2luZyBhdCBsZWFzdCBvbmUgZWRpdCBhcyBhIGNvbnZlcnNpb24uIFdoZW4gdGhpcyBzdGVwIGlzIGNvbXBsZXRlZCwgdGhlIHByb2NlZHVyZSBzdGFydHMgKnJlbW92aW5nKiBvbmUgYnkgb25lIGNhbXBhaWduIGNoYW5uZWwgZnJvbSB0aGUgbW9kZWwsIGFuZCBlYWNoIHRpbWUgaXQgcmUtY29tcHV0ZXMgdGhlIGNvbnZlcnNpb24gcHJvYmFiaWxpdHkgdG8gZXN0aW1hdGUgaG93IG1hbnkgY29udmVyc2lvbnMgd291bGQgYmUgbG9zdCBkdWUgdG8gdGhlIHJlbW92YWwgb2YgYSBwYXJ0aWN1bGFyIGNoYW5uZWwuIFRoZSBsYXJnZXIgdGhlIGRyb3AgaW4gcHJvYmFiaWxpdHkgb2YgY29udmVyc2lvbiBkdWUgdG8gdGhlIHJlbW92YWwgb2YgYSBwYXJ0aWN1bGFyIGNoYW5uZWwsIHRoZSBsYXJnZXIgdGhlIHJlbW92YWwgZWZmZWN0IGZvciB0aGF0IGNoYW5uZWwuIENoYW5uZWxzIHdpdGggbGFyZ2VyIHJlbW92YWwgZWZmZWN0cyBhcmUgY29uc2lkZXJlZCB0byBiZSBtb3JlIGltcG9ydGFudC4gVGhlIHZhbHVlIG9mIHRoZSByZW1vdmFsIGVmZmVjdCwgYmVpbmcgYSBwcm9iYWJpbGl0eSBpbiBpdHNlbGYsIGNhbiB2YXJ5IGZyb20gMCB0byAxLgoKSW4gdGhpcyBjYXNlLCB0aGUgY2FtcGFpZ24gY2hhbm5lbHMgYXJlIHRoZSBmb2xsb3dpbmcgZXZlbnRzOgoKLSBgQlQxYCAtIFNwZWNpZmljIFRhc2sgQmFubmVyIGB3bWRlX2FiYzIwMTdfYnQxYCBpcyBwcmVzZW50ZWQ7Ci0gYEJUMmAgLSBTcGVjaWZpYyBUYXNrIEJhbm5lciBgd21kZV9hYmMyMDE3X2J0MmAgaXMgcHJlc2VudGVkOwotIGBCVDNgIC0gU3BlY2lmaWMgVGFzayBCYW5uZXIgYHdtZGVfYWJjMjAxN19idDNgIGlzIHByZXNlbnRlZDsKLSBgR0lCYCAtIEdlbmVyYWwgSW52aWF0aW9uIEJhbm5lciAtIGB3bWRlX2FiYzIwMTdfZ2liX2xwYCBvciBgd21kZV9hYmMyMDE3X2dpYl9yZ2AgaXMgcHJlc2VudGVkOwotIGBUTFBgIC0gU3BlY2lmaWMgVGFzayBQYWdlIGBKZXR6dE1pdG1hY2hlbmAgaXMgdmlld2VkIChub3RlOiB0aGUgc2FtZSBhcyBhIGJhbm5lciBjbGljayBvbiBhbnkgb2YgdGhlIGZvbGxvd2luZyBiYW5uZXJzOiBgQlQxYCwgYEJUMmAsIGBCVDNgKTsgCi0gYEdMUGAgLSBHZW5lcmFsIFBhZ2UgYE1hY2hfbWl0YCBpcyB2aWV3ZWQ7IChub3RlOiB0aGUgc2FtZSBhcyBhIGJhbm5lciBjbGljayBvbiBgR0lCX0xQYCk7Ci0gYFJQYCAgLSBSZWdpc3RyYXRpb24gUGFnZSBgQmVudXR6ZXJrb250b19hbmxlZ2VuYCBpcyB2aWV3ZWQ7IChub3RlOiBlbmNvbXBhc3NlcyB1c2VycyB3aG8gdHJhbnNpdCBmcm9tIGBKZXR6dE1pdG1hY2hlbmAgb3IgYE1hY2hfbWl0YCwgYXMgd2VsbCBhcyBiYW5uZXIgY2xpY2tzIG9uIGBHSUJfUkdgKTsKLSBgUmVnYCAtIFRoZSBhY3Qgb2YgdXNlciByZWdpc3RyYXRpb247Ci0gYEdUYCAgLSBUaGUgYWN0IG9mIGNvbXBsZXRpbmcgdGhlIEd1aWRlZCBUb3VyLgoKKipJbXBvcnRhbnQqKjogdW5saWtlIGluIHRoZSBCYXllc2lhbiBBL0IgdGVzdHMgdGhhdCBhcmUgcHJlc2VudGVkIGFib3ZlLCB3aGVyZSB0aGUgY3JpdGVyaW9uIGZvciBwYWlyLXdpc2UgY29tcGFyaXNvbnMgYW1vbmcgdGhlIGNhbXBhaWduIGJhbm5lcnMgd2FzIGVpdGhlciB0aGUgbnVtYmVyIG9mIHVzZXJzIHJlZ2lzdGVyZWQgKGBTZWN0aW9uIDUuMUFgKSwgb3IgdGhlIG51bWJlciBvZiBlZGl0cyBtYWRlIChgU2VjdGlvbiA1LjFCYCksIGhlcmUgdGhlIGNyaXRlcmlvbiAoaS5lLiB0aGUgZGVmaW5pdGlvbiBvZiAqY29udmVyc2lvbiosIGlmIHlvdSBwcmVmZXIpIGlzIHdoZXRoZXIgYSB1c2VyIGhhcyBtYWRlIGFueSBlZGl0cyBhdCBhbGwuIFRoZSByZWFzb24gdGhhdCBtb3RpdmF0ZXMgdGhpcyBjcml0ZXJpb24sIGFuZCBub3QgYSBtb3JlIHN0cmljdCBjcml0ZXJpb24gb2YgbWFraW5nIGA+PSAxMCBlZGl0c2AsIGlzIHNpbXBseSBiZWNhdXNlIHRoZXJlIGFyZSBvbmx5IHNldmVyYWwgdXNlcnMgd2hvIGhhdmUgcmVnaXN0ZXJlZCB2aWEgdGhpcyBjYW1wYWlnbiBhbmQgbWFkZSBtb3JlIHRoYW4gdGVuIGVkaXRzIHVudGlsIG5vdy4gCioqUmVtb3ZhbCBFZmZlY3RzKiouIFRoZSBSZW1vdmFsIEVmZmVjdCBmb3IgYSBjYW1wYWlnbiBjaGFubmVsIHJlcHJlc2VudHMgKnRoZSBjaGFuZ2UgaW4gcHJvYmFiaWxpdHkgdGhhdCBhIGNvbnZlcnNpb24gd291bGQgb2J0YWluIGlmIHRoZSByZXNwZWN0aXZlIGNoYW5uZWwgd2FzIHJlbW92ZWQgZnJvbSB0aGUgY2FtcGFpZ24qLiBPbmNlIGFnYWluLCBnaXZlbiB0aGF0IGNvbnZlcnNpb24gaGVyZSBtZWFucyBhIHVzZXIgbWFraW5nIGF0IGxlYXN0IG9uZSBlZGl0LCB0aGUgcmVtb3ZhbCBlZmZlY3RzIHRlbGxzIHVzICpob3cgbXVjaCB3b3VsZCB0aGUgcHJvYmFiaWxpdHkgb2Ygb2J0YWluaW5nIGF0IGxlYXN0IG9uZSBlZGl0IGZyb20gYSB1c2VyIGRyb3AqIGlmIHRoZSByZXNwZWN0aXZlIGNhbXBhaWduIGNoYW5uZWwgd2FzIHJlbW92ZWQuCioqVEVDSE5JQ0FMIE5PVEUqKjogYSBNYXJrb3YgbW9kZWwgb2YgKm9yZGVyIDQqIHdhcyB1c2VkLCB3aXRoIGAxZThgIHRvdGFsIHNpbXVsYXRpb24gcnVucyBmcm9tIHRoZSB0cmFuc2l0aW9uIG1hdHJpeC4KCiMjIyMgUmVtb3ZhbCBFZmZlY3RzCgpgYGB7ciBlY2hvID0gVCwgd2FybmluZyA9ICdoaWRlJywgbWVzc2FnZSA9IEYsIGV2YWwgPSBUfQojIyMgLS0tIEJhbm5lciAtPiBFeGl0IHBhdGhzIC0tLSAjIyMKIyMjIC0tLSBEZWZpbml0aW9uOiBOKEJhbm5lciBJbXByZXNzaW9ucykgLSBOKEJhbm5lckNsaWNrcyA9PSBMYW5kaW5nIFBhZ2UgVmlld3MpCiMgLSBkZWZpbmU6IE4oQmFubmVyIEltcHJlc3Npb25zKQpiSW1wIDwtIGJhbkltcFNldCAlPiUgCiAgZ3JvdXBfYnkoQmFubmVyKSAlPiUgCiAgc3VtbWFyaXNlKENvdW50ID0gc3VtKENvdW50KSkKbkJUMSA8LSBiSW1wJENvdW50W3doaWNoKGJJbXAkQmFubmVyICVpbiUgJ0JUMScpXSAKbkJUMiA8LSBiSW1wJENvdW50W3doaWNoKGJJbXAkQmFubmVyICVpbiUgJ0JUMicpXQpuQlQzIDwtIGJJbXAkQ291bnRbd2hpY2goYkltcCRCYW5uZXIgJWluJSAnQlQzJyldCm5HSUIgPC0gYkltcCRDb3VudFt3aGljaChiSW1wJEJhbm5lciAlaW4lICdHSUJfTFAnKV0gKyBiSW1wJENvdW50W3doaWNoKGJJbXAkQmFubmVyICVpbiUgJ0dJQl9SRycpXQojIC0gZGVmaW5lOiBOKEJhbm5lckNsaWNrcy9QYWdlVmlld3MpCmJDbGljayA8LSBjbGlja1Bsb3RTZXQgJT4lIAogIGdyb3VwX2J5KFNvdXJjZSkgJT4lIAogIHN1bW1hcmlzZShDb3VudCA9IHN1bShDb3VudCkpCmJDbGljayRTb3VyY2UgPC0gZ3N1YigiX2NsaWNrIiwgIiIsIGJDbGljayRTb3VyY2UsIGZpeGVkID0gVCkKIyAtIGRlZmluZTogTihCYW5uZXJJbXByZXNzaW9ucykgLSBOKEJhbm5lckNsaWNrcy9QYWdlVmlld3MpCm5CVDEgPC0gbkJUMSAtIGJDbGljayRDb3VudFt3aGljaChiQ2xpY2skU291cmNlICVpbiUgJ0JUMScpXSAKbkJUMiA8LSBuQlQyIC0gYkNsaWNrJENvdW50W3doaWNoKGJDbGljayRTb3VyY2UgJWluJSAnQlQyJyldCm5CVDMgPC0gbkJUMyAtIGJDbGljayRDb3VudFt3aGljaChiQ2xpY2skU291cmNlICVpbiUgJ0JUMycpXQpuR0lCIDwtIG5HSUIgLSBiQ2xpY2skQ291bnRbd2hpY2goYkNsaWNrJFNvdXJjZSAlaW4lICdHSUJfTFAnKV0gKyBiQ2xpY2skQ291bnRbd2hpY2goYkNsaWNrJFNvdXJjZSAlaW4lICdHSUJfUkcnKV0KCiMjIyAtLS0gQmFubmVyIC0+IExhbmRpbmcgUGFnZSAtPiBFeGl0IHBhdGhzIC0tLSAjIyMKIyMjIC0tLSBOKEJhbm5lciBDbGlja3MgPT0gTGFuZGluZyBQYWdlIFZpZXdzKSAtIE4oUmVnaXN0cmF0aW9uIFBhZ2UgVmlld3MpCiMjIyAtLS0gTk9URTogVExQID09IFRhc2sgTGFuZGluZyBQYWdlIChKZXR6dE1pdG1hY2hlbiksIEdMUCA9PSBHZW5lcmFsIExhbmRpbmcgUGFnZSAoTWFjaCBtaXQpCm5CVDFfVExQIDwtIGJDbGljayRDb3VudFt3aGljaChiQ2xpY2skU291cmNlICVpbiUgJ0JUMScpXSAtIAogIHBhZ2VTb3VyY2UkbltwYWdlU291cmNlJFBhZ2UgJWluJSAnU3BlemlhbDpCZW51dHplcmtvbnRvX2FubGVnZW4nICYgcGFnZVNvdXJjZSRTb3VyY2UgJWluJSAnSmV0enRNaXRtYWNoZW5fQlQxJ10KbkJUMl9UTFAgPC0gYkNsaWNrJENvdW50W3doaWNoKGJDbGljayRTb3VyY2UgJWluJSAnQlQyJyldIC0gCiAgcGFnZVNvdXJjZSRuW3BhZ2VTb3VyY2UkUGFnZSAlaW4lICdTcGV6aWFsOkJlbnV0emVya29udG9fYW5sZWdlbicgJiBwYWdlU291cmNlJFNvdXJjZSAlaW4lICdKZXR6dE1pdG1hY2hlbl9CVDInXQpuQlQzX1RMUCA8LSBiQ2xpY2skQ291bnRbd2hpY2goYkNsaWNrJFNvdXJjZSAlaW4lICdCVDMnKV0gLSAKICBwYWdlU291cmNlJG5bcGFnZVNvdXJjZSRQYWdlICVpbiUgJ1NwZXppYWw6QmVudXR6ZXJrb250b19hbmxlZ2VuJyAmIHBhZ2VTb3VyY2UkU291cmNlICVpbiUgJ0pldHp0TWl0bWFjaGVuX0JUMyddCm5HSUJfR0xQIDwtIGJDbGljayRDb3VudFt3aGljaChiQ2xpY2skU291cmNlICVpbiUgJ0dJQl9MUCcpXSAtIAogIHBhZ2VTb3VyY2UkbltwYWdlU291cmNlJFBhZ2UgJWluJSAnU3BlemlhbDpCZW51dHplcmtvbnRvX2FubGVnZW4nICYgcGFnZVNvdXJjZSRTb3VyY2UgJWluJSAnTWFjaF9taXQnXQoKIyMjIC0tLSBCYW5uZXIgKC0+IExhbmRpbmcgUGFnZSkgLT4gUmVnaXN0cmF0aW9uIFBhZ2UgLT4gRXhpdCBwYXRocyAtLS0gIyMjCiMjIyAtLS0gTihSZWdpc3RyYXRpb24gUGFnZSBWaWV3cykgLSBOKFVzZXIgUmVnaXN0cmF0aW9ucykKYlVzZXJSZWcgPC0gdXNlclJlZyAlPiUgCiAgZ3JvdXBfYnkoZXZlbnRfY2FtcGFpZ24pICU+JSAKICBzdW1tYXJpc2UoQ291bnQgPSBuKCkpCmJVc2VyUmVnJGV2ZW50X2NhbXBhaWduIDwtIHRvdXBwZXIoZ3N1Yigid21kZV9hYmMyMDE3XyIsICIiLCBiVXNlclJlZyRldmVudF9jYW1wYWlnbiwgZml4ZWQgPSBUKSkKY29sbmFtZXMoYlVzZXJSZWcpWzFdIDwtICdCYW5uZXInCm5CVDFfVExQX1JQIDwtIHBhZ2VTb3VyY2UkbltwYWdlU291cmNlJFBhZ2UgJWluJSAnU3BlemlhbDpCZW51dHplcmtvbnRvX2FubGVnZW4nICYgcGFnZVNvdXJjZSRTb3VyY2UgJWluJSAnSmV0enRNaXRtYWNoZW5fQlQxJ10gLSAKICBiVXNlclJlZyRDb3VudFtiVXNlclJlZyRCYW5uZXIgJWluJSAnQlQxJ10KbkJUMl9UTFBfUlAgPC0gcGFnZVNvdXJjZSRuW3BhZ2VTb3VyY2UkUGFnZSAlaW4lICdTcGV6aWFsOkJlbnV0emVya29udG9fYW5sZWdlbicgJiBwYWdlU291cmNlJFNvdXJjZSAlaW4lICdKZXR6dE1pdG1hY2hlbl9CVDInXSAtIAogIGJVc2VyUmVnJENvdW50W2JVc2VyUmVnJEJhbm5lciAlaW4lICdCVDInXQpuQlQzX1RMUF9SUCA8LSBwYWdlU291cmNlJG5bcGFnZVNvdXJjZSRQYWdlICVpbiUgJ1NwZXppYWw6QmVudXR6ZXJrb250b19hbmxlZ2VuJyAmIHBhZ2VTb3VyY2UkU291cmNlICVpbiUgJ0pldHp0TWl0bWFjaGVuX0JUMyddIC0gCiAgYlVzZXJSZWckQ291bnRbYlVzZXJSZWckQmFubmVyICVpbiUgJ0JUMyddCm5HSUJfR0xQX1JQIDwtIHBhZ2VTb3VyY2UkbltwYWdlU291cmNlJFBhZ2UgJWluJSAnTWFjaF9taXQnICYgcGFnZVNvdXJjZSRTb3VyY2UgJWluJSAnR0lCX0xQX2NsaWNrJ10gLSAKICBiVXNlclJlZyRDb3VudFtiVXNlclJlZyRCYW5uZXIgJWluJSAnR0lCX0xQJ10KbkdJQl9SUCA8LSBwYWdlU291cmNlJG5bcGFnZVNvdXJjZSRQYWdlICVpbiUgJ1NwZXppYWw6QmVudXR6ZXJrb250b19hbmxlZ2VuJyAmIHBhZ2VTb3VyY2UkU291cmNlICVpbiUgJ0dJQl9SR19jbGljayddIC0gCiAgYlVzZXJSZWckQ291bnRbYlVzZXJSZWckQmFubmVyICVpbiUgJ0dJQl9SRyddCgojIyMgLS0tIEJhbm5lciAoLT4gTGFuZGluZyBQYWdlKSAtPiBSZWdpc3RyYXRpb24gUGFnZSAtPiBSZWdpc3RyYXRpb24gLT4gRXhpdCAtLS0gIyMjCiMjIyAtLS0gTihVc2VyIFJlZ2lzdHJhdGlvbnMpIC0gTihFZGl0ZWQpIC0gTihDb21wbGV0ZWQgR1QgYW5kIE5vdCBFZGl0ZWQpCnVzZXJSZWdHVCA8LSBsZWZ0X2pvaW4odXNlclJlZywgZ1RvdXJEYXRhLCAKICAgICAgICAgICAgICAgICAgICAgICBieSA9ICdldmVudF91c2VySWQnKQp1c2VyUmVnR1QgPC0gbGVmdF9qb2luKHVzZXJSZWdHVCwgZWRpdERhdGEsIAogICAgICAgICAgICAgICAgICAgICAgIGJ5ID0gYygnZXZlbnRfdXNlcklkJyA9ICdyZXZfdXNlcicpKQoKbkJUMV9UTFBfUlBfUmVnIDwtIGJVc2VyUmVnJENvdW50W2JVc2VyUmVnJEJhbm5lciAlaW4lICdCVDEnXSAtIAogIHN1bSgodXNlclJlZ0dUJGV2ZW50X2NhbXBhaWduICVpbiUgJ3dtZGVfYWJjMjAxN19idDEnICYgaXMubmEodXNlclJlZ0dUJGV2ZW50X3RvdXIpKSB8IAogICAgICAgICh1c2VyUmVnR1QkZXZlbnRfY2FtcGFpZ24gJWluJSAnd21kZV9hYmMyMDE3X2J0MScgJiAhaXMubmEodXNlclJlZ0dUJGV2ZW50X3RvdXIpICYgIWlzLm5hKHVzZXJSZWdHVCRlZGl0cykpKQoKbkJUMl9UTFBfUlBfUmVnIDwtIGJVc2VyUmVnJENvdW50W2JVc2VyUmVnJEJhbm5lciAlaW4lICdCVDInXSAtIAogIHN1bSgodXNlclJlZ0dUJGV2ZW50X2NhbXBhaWduICVpbiUgJ3dtZGVfYWJjMjAxN19idDInICYgaXMubmEodXNlclJlZ0dUJGV2ZW50X3RvdXIpKSB8IAogICAgICAgICh1c2VyUmVnR1QkZXZlbnRfY2FtcGFpZ24gJWluJSAnd21kZV9hYmMyMDE3X2J0MicgJiAhaXMubmEodXNlclJlZ0dUJGV2ZW50X3RvdXIpICYgIWlzLm5hKHVzZXJSZWdHVCRlZGl0cykpKQoKbkJUM19UTFBfUlBfUmVnIDwtIGJVc2VyUmVnJENvdW50W2JVc2VyUmVnJEJhbm5lciAlaW4lICdCVDMnXSAtIAogIHN1bSgodXNlclJlZ0dUJGV2ZW50X2NhbXBhaWduICVpbiUgJ3dtZGVfYWJjMjAxN19idDMnICYgaXMubmEodXNlclJlZ0dUJGV2ZW50X3RvdXIpKSB8IAogICAgICAgICh1c2VyUmVnR1QkZXZlbnRfY2FtcGFpZ24gJWluJSAnd21kZV9hYmMyMDE3X2J0MycgJiAhaXMubmEodXNlclJlZ0dUJGV2ZW50X3RvdXIpICYgIWlzLm5hKHVzZXJSZWdHVCRlZGl0cykpKQoKbkdJQl9HTFBfUlBfUmVnIDwtIGJVc2VyUmVnJENvdW50W2JVc2VyUmVnJEJhbm5lciAlaW4lICdHSUJfTFAnXSAtIAogIHN1bSgodXNlclJlZ0dUJGV2ZW50X2NhbXBhaWduICVpbiUgJ3dtZGVfYWJjMjAxN19naWJfbHAnICYgaXMubmEodXNlclJlZ0dUJGV2ZW50X3RvdXIpKSB8IAogICAgICAgICh1c2VyUmVnR1QkZXZlbnRfY2FtcGFpZ24gJWluJSAnd21kZV9hYmMyMDE3X2dpYl9scCcgJiAhaXMubmEodXNlclJlZ0dUJGV2ZW50X3RvdXIpICYgIWlzLm5hKHVzZXJSZWdHVCRlZGl0cykpKQoKbkdJQl9SUF9SZWcgPC0gYlVzZXJSZWckQ291bnRbYlVzZXJSZWckQmFubmVyICVpbiUgJ0dJQl9SRyddIC0gCiAgc3VtKCh1c2VyUmVnR1QkZXZlbnRfY2FtcGFpZ24gJWluJSAnd21kZV9hYmMyMDE3X2dpYl9yZycgJiBpcy5uYSh1c2VyUmVnR1QkZXZlbnRfdG91cikpIHwgCiAgICAgICAgKHVzZXJSZWdHVCRldmVudF9jYW1wYWlnbiAlaW4lICd3bWRlX2FiYzIwMTdfZ2liX3JnJyAmICFpcy5uYSh1c2VyUmVnR1QkZXZlbnRfdG91cikgJiAhaXMubmEodXNlclJlZ0dUJGVkaXRzKSkpCgojIyMgLS0tIEJhbm5lciAoLT4gTGFuZGluZyBQYWdlKSAtPiBSZWdpc3RyYXRpb24gUGFnZSAtPiBSZWdpc3RyYXRpb24gLT4gR1QgLT4gRXhpdCAtLS0gIyMjCiMjIyAtLS0gTihVc2VyIFJlZ2lzdHJhdGlvbnMpIC0gTihFZGl0ZWQpIC0gTihDb21wbGV0ZWQgR1QgYW5kIE5vdCBFZGl0ZWQpCm5CVDFfVExQX1JQX1JlZ19HVCA8LSBsZW5ndGgodXNlclJlZ0dUJGV2ZW50X3VzZXJJZFsodXNlclJlZ0dUJGV2ZW50X2NhbXBhaWduICVpbiUgJ3dtZGVfYWJjMjAxN19idDEnKSAmIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpcy5uYSh1c2VyUmVnR1QkZXZlbnRfdG91cikgJiAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaXMubmEodXNlclJlZ0dUJGVkaXRzKV0pCm5CVDJfVExQX1JQX1JlZ19HVCA8LSBsZW5ndGgodXNlclJlZ0dUJGV2ZW50X3VzZXJJZFsodXNlclJlZ0dUJGV2ZW50X2NhbXBhaWduICVpbiUgJ3dtZGVfYWJjMjAxN19idDInKSAmIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpcy5uYSh1c2VyUmVnR1QkZXZlbnRfdG91cikgJiAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaXMubmEodXNlclJlZ0dUJGVkaXRzKV0pCm5CVDNfVExQX1JQX1JlZ19HVCA8LSBsZW5ndGgodXNlclJlZ0dUJGV2ZW50X3VzZXJJZFsodXNlclJlZ0dUJGV2ZW50X2NhbXBhaWduLnggJWluJSAnd21kZV9hYmMyMDE3X2J0MycpICYgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlzLm5hKHVzZXJSZWdHVCRldmVudF90b3VyKSAmIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpcy5uYSh1c2VyUmVnR1QkZWRpdHMpXSkKbkdJQl9HTFBfUlBfUmVnX0dUIDwtIGxlbmd0aCh1c2VyUmVnR1QkZXZlbnRfdXNlcklkWyh1c2VyUmVnR1QkZXZlbnRfY2FtcGFpZ24gJWluJSAnd21kZV9hYmMyMDE3X2dpYl9scCcpICYgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlzLm5hKHVzZXJSZWdHVCRldmVudF90b3VyKSAmIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpcy5uYSh1c2VyUmVnR1QkZWRpdHMpXSkKbkdJQl9SUF9SZWdfR1QgPC0gbGVuZ3RoKHVzZXJSZWdHVCRldmVudF91c2VySWRbKHVzZXJSZWdHVCRldmVudF9jYW1wYWlnbiAlaW4lICd3bWRlX2FiYzIwMTdfZ2liX3JnJykgJgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlzLm5hKHVzZXJSZWdHVCRldmVudF90b3VyKSAmCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaXMubmEodXNlclJlZ0dUJGVkaXRzKV0pCgojIyMgLS0tIEJhbm5lciAoLT4gTGFuZGluZyBQYWdlKSAtPiBSZWdpc3RyYXRpb24gUGFnZSAtPiBSZWdpc3RyYXRpb24gLT4gR1QgLT4gRURJVCAtLS0gIyMjCm5CVDFfVExQX1JQX1JlZ19HVF9FRElUIDwtIGxlbmd0aCh1c2VyUmVnR1QkZXZlbnRfdXNlcklkWyh1c2VyUmVnR1QkZXZlbnRfY2FtcGFpZ24gJWluJSAnd21kZV9hYmMyMDE3X2J0MScpICYgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlzLm5hKHVzZXJSZWdHVCRldmVudF90b3VyKSAmIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAhaXMubmEodXNlclJlZ0dUJGVkaXRzKV0pCm5CVDJfVExQX1JQX1JlZ19HVF9FRElUIDwtIGxlbmd0aCh1c2VyUmVnR1QkZXZlbnRfdXNlcklkWyh1c2VyUmVnR1QkZXZlbnRfY2FtcGFpZ24gJWluJSAnd21kZV9hYmMyMDE3X2J0MicpICYgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlzLm5hKHVzZXJSZWdHVCRldmVudF90b3VyKSAmIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAhaXMubmEodXNlclJlZ0dUJGVkaXRzKV0pCm5CVDNfVExQX1JQX1JlZ19HVF9FRElUIDwtIGxlbmd0aCh1c2VyUmVnR1QkZXZlbnRfdXNlcklkWyh1c2VyUmVnR1QkZXZlbnRfY2FtcGFpZ24ueCAlaW4lICd3bWRlX2FiYzIwMTdfYnQzJykgJiAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaXMubmEodXNlclJlZ0dUJGV2ZW50X3RvdXIpICYgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICFpcy5uYSh1c2VyUmVnR1QkZWRpdHMpXSkKbkdJQl9HTFBfUlBfUmVnX0dUX0VESVQgPC0gbGVuZ3RoKHVzZXJSZWdHVCRldmVudF91c2VySWRbKHVzZXJSZWdHVCRldmVudF9jYW1wYWlnbiAlaW4lICd3bWRlX2FiYzIwMTdfZ2liX2xwJykgJiAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaXMubmEodXNlclJlZ0dUJGV2ZW50X3RvdXIpICYgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICFpcy5uYSh1c2VyUmVnR1QkZWRpdHMpXSkKbkdJQl9SUF9SZWdfR1RfRURJVCA8LSBsZW5ndGgodXNlclJlZ0dUJGV2ZW50X3VzZXJJZFsodXNlclJlZ0dUJGV2ZW50X2NhbXBhaWduICVpbiUgJ3dtZGVfYWJjMjAxN19naWJfcmcnKSAmCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaXMubmEodXNlclJlZ0dUJGV2ZW50X3RvdXIpICYKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAhaXMubmEodXNlclJlZ0dUJGVkaXRzKV0pCgojIyMgLS0tIEJhbm5lciAoLT4gTGFuZGluZyBQYWdlKSAtPiBSZWdpc3RyYXRpb24gUGFnZSAtPiBSZWdpc3RyYXRpb24gLT4gRURJVCAtLS0gIyMjCm5CVDFfVExQX1JQX1JlZ19FRElUIDwtIGxlbmd0aCh1c2VyUmVnR1QkZXZlbnRfdXNlcklkWyh1c2VyUmVnR1QkZXZlbnRfY2FtcGFpZ24gJWluJSAnd21kZV9hYmMyMDE3X2J0MScpICYgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICFpcy5uYSh1c2VyUmVnR1QkZXZlbnRfdG91cikgJiAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIWlzLm5hKHVzZXJSZWdHVCRlZGl0cyldKQpuQlQyX1RMUF9SUF9SZWdfRURJVCA8LSBsZW5ndGgodXNlclJlZ0dUJGV2ZW50X3VzZXJJZFsodXNlclJlZ0dUJGV2ZW50X2NhbXBhaWduICVpbiUgJ3dtZGVfYWJjMjAxN19idDInKSAmIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAhaXMubmEodXNlclJlZ0dUJGV2ZW50X3RvdXIpICYgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICFpcy5uYSh1c2VyUmVnR1QkZWRpdHMpXSkKbkJUM19UTFBfUlBfUmVnX0VESVQgPC0gbGVuZ3RoKHVzZXJSZWdHVCRldmVudF91c2VySWRbKHVzZXJSZWdHVCRldmVudF9jYW1wYWlnbiAlaW4lICd3bWRlX2FiYzIwMTdfYnQzJykgJiAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIWlzLm5hKHVzZXJSZWdHVCRldmVudF90b3VyKSAmIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAhaXMubmEodXNlclJlZ0dUJGVkaXRzKV0pCm5HSUJfR0xQX1JQX1JlZ19FRElUIDwtIGxlbmd0aCh1c2VyUmVnR1QkZXZlbnRfdXNlcklkWyh1c2VyUmVnR1QkZXZlbnRfY2FtcGFpZ24gJWluJSAnd21kZV9hYmMyMDE3X2dpYl9scCcpICYgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICFpcy5uYSh1c2VyUmVnR1QkZXZlbnRfdG91cikgJiAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIWlzLm5hKHVzZXJSZWdHVCRlZGl0cyldKQpuR0lCX1JQX1JlZ19FRElUIDwtIGxlbmd0aCh1c2VyUmVnR1QkZXZlbnRfdXNlcklkWyh1c2VyUmVnR1QkZXZlbnRfY2FtcGFpZ24gJWluJSAnd21kZV9hYmMyMDE3X2dpYl9yZycpICYKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAhaXMubmEodXNlclJlZ0dUJGV2ZW50X3RvdXIpICYKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAhaXMubmEodXNlclJlZ0dUJGVkaXRzKV0pCiMjIyAtLS0gZGF0YXNldAptY2FEYXRhIDwtIGRhdGEuZnJhbWUocGF0aCA9IGMoZGVwYXJzZShzdWJzdGl0dXRlKG5CVDEpKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRlcGFyc2Uoc3Vic3RpdHV0ZShuQlQyKSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkZXBhcnNlKHN1YnN0aXR1dGUobkJUMykpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGVwYXJzZShzdWJzdGl0dXRlKG5HSUIpKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRlcGFyc2Uoc3Vic3RpdHV0ZShuQlQxX1RMUCkpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGVwYXJzZShzdWJzdGl0dXRlKG5CVDJfVExQKSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkZXBhcnNlKHN1YnN0aXR1dGUobkJUM19UTFApKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRlcGFyc2Uoc3Vic3RpdHV0ZShuR0lCX0dMUCkpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGVwYXJzZShzdWJzdGl0dXRlKG5CVDFfVExQX1JQKSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkZXBhcnNlKHN1YnN0aXR1dGUobkJUMl9UTFBfUlApKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRlcGFyc2Uoc3Vic3RpdHV0ZShuQlQzX1RMUF9SUCkpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGVwYXJzZShzdWJzdGl0dXRlKG5HSUJfR0xQX1JQKSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkZXBhcnNlKHN1YnN0aXR1dGUobkdJQl9SUCkpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGVwYXJzZShzdWJzdGl0dXRlKG5CVDFfVExQX1JQX1JlZykpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGVwYXJzZShzdWJzdGl0dXRlKG5CVDJfVExQX1JQX1JlZykpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGVwYXJzZShzdWJzdGl0dXRlKG5CVDNfVExQX1JQX1JlZykpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGVwYXJzZShzdWJzdGl0dXRlKG5HSUJfR0xQX1JQX1JlZykpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGVwYXJzZShzdWJzdGl0dXRlKG5HSUJfUlBfUmVnKSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkZXBhcnNlKHN1YnN0aXR1dGUobkJUMV9UTFBfUlBfUmVnX0dUKSksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGVwYXJzZShzdWJzdGl0dXRlKG5CVDJfVExQX1JQX1JlZ19HVCkpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGVwYXJzZShzdWJzdGl0dXRlKG5CVDNfVExQX1JQX1JlZ19HVCkpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGVwYXJzZShzdWJzdGl0dXRlKG5HSUJfR0xQX1JQX1JlZ19HVCkpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGVwYXJzZShzdWJzdGl0dXRlKG5HSUJfUlBfUmVnX0dUKSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkZXBhcnNlKHN1YnN0aXR1dGUobkJUMV9UTFBfUlBfUmVnX0dUX0VESVQpKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRlcGFyc2Uoc3Vic3RpdHV0ZShuQlQyX1RMUF9SUF9SZWdfR1RfRURJVCkpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGVwYXJzZShzdWJzdGl0dXRlKG5CVDNfVExQX1JQX1JlZ19HVF9FRElUKSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkZXBhcnNlKHN1YnN0aXR1dGUobkdJQl9HTFBfUlBfUmVnX0dUX0VESVQpKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRlcGFyc2Uoc3Vic3RpdHV0ZShuR0lCX1JQX1JlZ19HVF9FRElUKSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkZXBhcnNlKHN1YnN0aXR1dGUobkJUMV9UTFBfUlBfUmVnX0VESVQpKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRlcGFyc2Uoc3Vic3RpdHV0ZShuQlQyX1RMUF9SUF9SZWdfRURJVCkpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGVwYXJzZShzdWJzdGl0dXRlKG5CVDNfVExQX1JQX1JlZ19FRElUKSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkZXBhcnNlKHN1YnN0aXR1dGUobkdJQl9HTFBfUlBfUmVnX0VESVQpKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRlcGFyc2Uoc3Vic3RpdHV0ZShuR0lCX1JQX1JlZ19FRElUKSkKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICksCiAgICAgICAgICAgICAgICAgICAgICB0b3RhbF9jb252ZXJzaW9ucyA9IGMoMCwgMCwgMCwgMCwgMCwgMCwgMCwgMCwgMCwgMCwgMCwgMCwgMCwgMCwgMCwgMCwgMCwgMCwgMCwgMCwgMCwgMCwgMCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbkJUMV9UTFBfUlBfUmVnX0dUX0VESVQsIG5CVDJfVExQX1JQX1JlZ19HVF9FRElULCBuQlQzX1RMUF9SUF9SZWdfR1RfRURJVCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbkdJQl9HTFBfUlBfUmVnX0dUX0VESVQsIG5HSUJfUlBfUmVnX0dUX0VESVQsIG5CVDFfVExQX1JQX1JlZ19FRElULCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuQlQyX1RMUF9SUF9SZWdfRURJVCwgbkJUM19UTFBfUlBfUmVnX0VESVQsIG5HSUJfR0xQX1JQX1JlZ19FRElULCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuR0lCX1JQX1JlZ19FRElUCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKSwKICAgICAgICAgICAgICAgICAgICAgIHRvdGFsX251bGwgPSBjKG5CVDEsIG5CVDIsIG5CVDMsIG5HSUIsIG5CVDFfVExQLCBuQlQyX1RMUCwgbkJUM19UTFAsIG5HSUJfR0xQLCBuQlQxX1RMUF9SUCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5CVDJfVExQX1JQLCBuQlQzX1RMUF9SUCwgbkdJQl9HTFBfUlAsIG5HSUJfUlAsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbkJUMV9UTFBfUlBfUmVnLCBuQlQyX1RMUF9SUF9SZWcsIG5CVDNfVExQX1JQX1JlZywgbkdJQl9HTFBfUlBfUmVnLCBuR0lCX1JQX1JlZywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5CVDFfVExQX1JQX1JlZ19HVCwgbkJUMl9UTFBfUlBfUmVnX0dULCBuQlQzX1RMUF9SUF9SZWdfR1QsIG5HSUJfR0xQX1JQX1JlZ19HVCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5HSUJfUlBfUmVnX0dULCAwLCAwLCAwLCAwLCAwLCAwLCAwLCAwLCAwLCAwCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICApLAogICAgICAgICAgICAgICAgICAgICAgc3RyaW5nc0FzRmFjdG9ycyA9IEYpCgojIC0gY29ycmVjdCBwYXRoczoKbWNhRGF0YSRwYXRoIDwtIGdzdWIoIm4iLCAiIiwgbWNhRGF0YSRwYXRoLCBmaXhlZCA9IFQpCm1jYURhdGEkcGF0aCA8LSBnc3ViKCJfIiwgIiA+ICIsIG1jYURhdGEkcGF0aCwgZml4ZWQgPSBUKQplZGl0RW5kcyA8LSB3aGljaChncmVwbCgiRURJVCIsIG1jYURhdGEkcGF0aCwgZml4ZWQgPSBUKSkKZm9yIChpIGluIDE6bGVuZ3RoKGVkaXRFbmRzKSkgewogIHdQYXRoIDwtIHdoaWNoKG1jYURhdGEkcGF0aCAlaW4lIGdzdWIoIiA+IEVESVQiLCAiIiwgbWNhRGF0YSRwYXRoW2VkaXRFbmRzW2ldXSwgZml4ZWQgPSBUKSkKICB3T3V0IDwtIHdoaWNoKG1jYURhdGEkcGF0aCA9PSBtY2FEYXRhJHBhdGhbZWRpdEVuZHNbaV1dKQogIHdQYXRoIDwtIHNldGRpZmYod1BhdGgsIHdPdXQpCiAgbWNhRGF0YSR0b3RhbF9jb252ZXJzaW9uc1t3UGF0aF0gPC0gbWNhRGF0YSR0b3RhbF9jb252ZXJzaW9uc1t3T3V0XQp9Cm1jYURhdGEgPC0gbWNhRGF0YVstd2hpY2goZ3JlcGwoIkVESVQiLCBtY2FEYXRhJHBhdGgsIGZpeGVkID0gVCkpLCBdCgojIyMgLS0tIE1DQSBtb2RlbAphYmMyMDE3TW9kZWwgPC0gbWFya292X21vZGVsKG1jYURhdGEsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdmFyX3BhdGggPSAicGF0aCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdmFyX2NvbnYgPSAidG90YWxfY29udmVyc2lvbnMiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZhcl9udWxsID0gInRvdGFsX251bGwiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIG9yZGVyID0gNCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuc2ltID0gMWU4LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIG91dF9tb3JlID0gVCkKIyAtIGNvbGxlY3QgcmVtb3ZhbCBlZmZlY3RzIGZvciB0aGUgbmV4dCBwbG90OgpyZTRvcmRlciA8LSBhYmMyMDE3TW9kZWwkcmVtb3ZhbF9lZmZlY3RzJHJlbW92YWxfZWZmZWN0cwojIyMgLS0tIFJlbW92YWwgRWZmZWN0czoKcmUgPC0gYXMuZGF0YS5mcmFtZShhYmMyMDE3TW9kZWwkcmVtb3ZhbF9lZmZlY3RzKQpjb2xuYW1lcyhyZSkgPC0gYygnQ2hhbm5lbCcsICdSZW1vdmFsIEVmZmVjdCcpCnJlJENoYW5uZWwgPC0gZmFjdG9yKHJlJENoYW5uZWwsIGxldmVscyA9IGFzLmNoYXJhY3RlcihhYmMyMDE3TW9kZWwkcmVtb3ZhbF9lZmZlY3RzJGNoYW5uZWxfbmFtZSkpCmdwbG90IDwtIGdncGxvdChkYXRhID0gcmUsIAogICAgICAgICAgICAgICAgYWVzKHggPSBDaGFubmVsLAogICAgICAgICAgICAgICAgICAgIHkgPSBgUmVtb3ZhbCBFZmZlY3RgLAogICAgICAgICAgICAgICAgICAgIGxhYmVsID0gcm91bmQoYFJlbW92YWwgRWZmZWN0YCwgMikpCiAgICAgICAgICAgICAgICApICsgCiAgZ2VvbV9iYXIod2lkdGggPSAuMSwgY29sb3IgPSAiZGFya2JsdWUiLCBmaWxsID0gIndoaXRlIiwgc3RhdCA9ICJpZGVudGl0eSIpICsgCiAgZ2VvbV9sYWJlbChzaXplID0gMykgKyAKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gY29tbWEpICsgCiAgeGxhYignQ2FtcGFpZ24gQ2hhbm5lbCcpICsgeWxhYignUmVtb3ZhbCBFZmZlY3QnKSArIAogIHlsaW0oYygwLCAxKSkgKyAKICBnZ3RpdGxlKCdDYW1wYWlnbiBNdWx0aS1DaGFubmVsIEF0dHJpYnV0aW9uOiBSZW1vdmFsIEVmZmVjdHMnKSArIAogIHRoZW1lX21pbmltYWwoKSArIAogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSkKc3VwcHJlc3NXYXJuaW5ncyhwcmludChncGxvdCkpCmBgYAoKIyMjIyBDYW1wYWlnbiBUcmFuc2l0aW9uIEdyYXBoCgpFYWNoIG5vZGUgaW4gdGhlIGZvbGxvd2luZyBncmFwaCByZXByZXNlbnRzIGEgcGFydGljdWxhciBjYW1wYWlnbiBjaGFubmVsLiBUaGUgZWRnZXMgb2YgdGhlIGdyYXBoIGFyZSBsYWJlbGVkIGJ5IHRoZSByZXNwZWN0aXZlIHRyYW5zaXRpb24gcHJvYmFiaWxpdGllcyBiZXR3ZWVuIHRoZSBjaGFubmVscy4gVGhlIHNpemUgb2YgdGhlIG5vZGUgY29ycmVzcG9uZHMgdG8gaXRzIHJlbW92YWwgZWZmZWN0LiAqKlRFQ0hOSUNBTCBOT1RFOioqIHRoZSByZW1vdmFsIGVmZmVjdHMgYXJlIGRlcml2ZWQgZnJvbSBhIE1hcmtvdiBtb2RlbCBvZiBvcmRlciA0LCB3aGlsZSB0aGUgdHJhbnNpdGlvbmFsIHByb2JhYmlsaXRpZXMgYXJlIGRlcml2ZWQgZGlyZWN0bHkgZnJvbSB0aGUgMXN0IG9yZGVyIG1vZGVsLgoKYGBge3IgZWNobyA9IFQsIHdhcm5pbmcgPSAnaGlkZScsIG1lc3NhZ2UgPSBGLCBldmFsID0gVH0KIyMjIC0tLSBNQ0EgbW9kZWw6IDFzdCBvcmRlciBmb3IgY2hhbm5lbC10by1jaGFubmVsIHRyYW5zaXRpb25zCmFiYzIwMTdNb2RlbCA8LSBtYXJrb3ZfbW9kZWwobWNhRGF0YSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICB2YXJfcGF0aCA9ICJwYXRoIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICB2YXJfY29udiA9ICJ0b3RhbF9jb252ZXJzaW9ucyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdmFyX251bGwgPSAidG90YWxfbnVsbCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgb3JkZXIgPSAxLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIG91dF9tb3JlID0gVCkKIyMjIC0tLSBwbG90IHcuIHtpZ3JhcGh9CmFiYzIwMTdOZXQgPC0gZGF0YS5mcmFtZShvdWdvaW5nID0gYWJjMjAxN01vZGVsJHRyYW5zaXRpb25fbWF0cml4JGNoYW5uZWxfZnJvbSwKICAgICAgICAgICAgICAgICAgICAgICAgIGluY29taW5nID0gYWJjMjAxN01vZGVsJHRyYW5zaXRpb25fbWF0cml4JGNoYW5uZWxfdG8sCiAgICAgICAgICAgICAgICAgICAgICAgICBzdHJpbmdzQXNGYWN0b3JzID0gRikKYWJjMjAxN05ldCRvdWdvaW5nIDwtIHNhcHBseShhYmMyMDE3TmV0JG91Z29pbmcsIGZ1bmN0aW9uKHgpIHsKICBjaCA8LSBnc3ViKCIoc3RhcnQpIiwgIlNUQVJUIiwgZml4ZWQgPSBULCB4KQogIGNoIDwtIGdzdWIoIihudWxsKSIsICJFWElUIiwgZml4ZWQgPSBULCBjaCkKICBjaCA8LSBnc3ViKCIoY29udmVyc2lvbikiLCAiRURJVCIsIGZpeGVkID0gVCwgY2gpCiAgY2gKfSkKYWJjMjAxN05ldCRpbmNvbWluZyA8LSBzYXBwbHkoYWJjMjAxN05ldCRpbmNvbWluZywgZnVuY3Rpb24oeCkgewogIGNoIDwtIGdzdWIoIihzdGFydCkiLCAiU1RBUlQiLCBmaXhlZCA9IFQsIHgpCiAgY2ggPC0gZ3N1YigiKG51bGwpIiwgIkVYSVQiLCBmaXhlZCA9IFQsIGNoKQogIGNoIDwtIGdzdWIoIihjb252ZXJzaW9uKSIsICJFRElUIiwgZml4ZWQgPSBULCBjaCkKICBjaAp9KQoKYWJjMjAxN05ldCA8LSBncmFwaC5kYXRhLmZyYW1lKGFiYzIwMTdOZXQsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGlyZWN0ZWQgPSBUKQpFKGFiYzIwMTdOZXQpJGxhYmVsIDwtIHJvdW5kKGFiYzIwMTdNb2RlbCR0cmFuc2l0aW9uX21hdHJpeCR0cmFuc2l0aW9uX3Byb2JhYmlsaXR5LCAyKQpWKGFiYzIwMTdOZXQpJGNvbG9yIDwtIGMoJ3doaXRlJywgCiAgICAgICAgICAgICAgICAgICAgICAgICAnaW5kaWFucmVkMScsICdpbmRpYW5yZWQyJywgJ2luZGlhbnJlZDMnLCAnY2FkZXRibHVlJywKICAgICAgICAgICAgICAgICAgICAgICAgICdyZWQnLCAnYmx1ZScsICd5ZWxsb3cnLCAnb3JhbmdlJywgJ2dyZWVuJywKICAgICAgICAgICAgICAgICAgICAgICAgICd3aGl0ZScsICd3aGl0ZScpClYoYWJjMjAxN05ldCkkc2l6ZSA8LSBjKDIwLCByZTRvcmRlcio0MCwgMjAsIDIwKQpWKGFiYzIwMTdOZXQpJGZyYW1lLmNvbG9yIDwtICd3aGl0ZScKCiMgLSBwbG90IHcuIHtpZ3JhcGh9CmNvb3JkcyA8LSBsYXlvdXRfKGFiYzIwMTdOZXQsIGFzX3RyZWUoKSkKcGFyKG1haT1jKHJlcCgwLDQpKSkKcGxvdChhYmMyMDE3TmV0LAogICAgIGxheW91dCA9IGNvb3JkcywKICAgICBlZGdlLndpZHRoID0gLjc1LAogICAgIGVkZ2UuY29sb3IgPSAiZ3JleSIsCiAgICAgZWRnZS5hcnJvdy5zaXplID0gMC4zNSwKICAgICBlZGdlLmN1cnZlZCA9IDAuNiwKICAgICBlZGdlLmxhYmVsLmZhbWlseSA9ICJzYW5zIiwKICAgICBlZGdlLmxhYmVsLmNvbG9yID0gImJsYWNrIiwKICAgICBlZGdlLmxhYmVsLmNleCA9IC42LAogICAgIHZlcnRleC5zaGFwZSA9ICJjaXJjbGUiLAogICAgIHZlcnRleC5sYWJlbC5jb2xvciA9ICJibGFjayIsCiAgICAgdmVydGV4LmxhYmVsLmZvbnQgPSAxLAogICAgIHZlcnRleC5sYWJlbC5mYW1pbHkgPSAic2FucyIsCiAgICAgdmVydGV4LmxhYmVsLmNleCA9IC43NSwKICAgICB2ZXJ0ZXgubGFiZWwuZGlzdCA9IC4yNSwKICAgICB2ZXJ0ZXgubGFiZWwuZGlzdCA9IC40NSwKICAgICByZXNjYWxlID0gRiwKICAgICB4bGltID0gYygtMSwgMSksCiAgICAgeWxpbSA9IGMoMCwgNCksCiAgICAgbWFyZ2luID0gYyhyZXAoMCw0KSkpCmBgYAoKCiMjIyA1LjMgQ2FtcGFpZ24gTXVsdGktQ2hhbm5lbCBBdHRyaWJ1dGlvbiBNb2RlbDogVXNlciBSZWdpc3RyYXRpb24KCioqVEVDSE5JQ0FMIE5PVEUqKjogYSBNYXJrb3YgbW9kZWwgb2YgKm9yZGVyIDQqIHdhcyB1c2VkLCB3aXRoIGAxZThgIHRvdGFsIHNpbXVsYXRpb24gcnVucyBmcm9tIHRoZSB0cmFuc2l0aW9uIG1hdHJpeC4KCiMjIyMgUmVtb3ZhbCBFZmZlY3RzCgpgYGB7ciBlY2hvID0gVCwgd2FybmluZyA9ICdoaWRlJywgbWVzc2FnZSA9IEYsIGV2YWwgPSBUfQojIyMgLS0tIEJhbm5lciAtPiBFeGl0IHBhdGhzIC0tLSAjIyMKIyMjIC0tLSBEZWZpbml0aW9uOiBOKEJhbm5lciBJbXByZXNzaW9ucykgLSBOKEJhbm5lckNsaWNrcyA9PSBMYW5kaW5nIFBhZ2UgVmlld3MpCiMgLSBkZWZpbmU6IE4oQmFubmVyIEltcHJlc3Npb25zKQpiSW1wIDwtIGJhbkltcFNldCAlPiUgCiAgZ3JvdXBfYnkoQmFubmVyKSAlPiUgCiAgc3VtbWFyaXNlKENvdW50ID0gc3VtKENvdW50KSkKbkJUMSA8LSBiSW1wJENvdW50W3doaWNoKGJJbXAkQmFubmVyICVpbiUgJ0JUMScpXSAKbkJUMiA8LSBiSW1wJENvdW50W3doaWNoKGJJbXAkQmFubmVyICVpbiUgJ0JUMicpXQpuQlQzIDwtIGJJbXAkQ291bnRbd2hpY2goYkltcCRCYW5uZXIgJWluJSAnQlQzJyldCm5HSUIgPC0gYkltcCRDb3VudFt3aGljaChiSW1wJEJhbm5lciAlaW4lICdHSUJfTFAnKV0gKyBiSW1wJENvdW50W3doaWNoKGJJbXAkQmFubmVyICVpbiUgJ0dJQl9SRycpXQojIC0gZGVmaW5lOiBOKEJhbm5lckNsaWNrcy9QYWdlVmlld3MpCmJDbGljayA8LSBjbGlja1Bsb3RTZXQgJT4lIAogIGdyb3VwX2J5KFNvdXJjZSkgJT4lIAogIHN1bW1hcmlzZShDb3VudCA9IHN1bShDb3VudCkpCmJDbGljayRTb3VyY2UgPC0gZ3N1YigiX2NsaWNrIiwgIiIsIGJDbGljayRTb3VyY2UsIGZpeGVkID0gVCkKIyAtIGRlZmluZTogTihCYW5uZXJJbXByZXNzaW9ucykgLSBOKEJhbm5lckNsaWNrcy9QYWdlVmlld3MpCm5CVDEgPC0gbkJUMSAtIGJDbGljayRDb3VudFt3aGljaChiQ2xpY2skU291cmNlICVpbiUgJ0JUMScpXSAKbkJUMiA8LSBuQlQyIC0gYkNsaWNrJENvdW50W3doaWNoKGJDbGljayRTb3VyY2UgJWluJSAnQlQyJyldCm5CVDMgPC0gbkJUMyAtIGJDbGljayRDb3VudFt3aGljaChiQ2xpY2skU291cmNlICVpbiUgJ0JUMycpXQpuR0lCIDwtIG5HSUIgLSBiQ2xpY2skQ291bnRbd2hpY2goYkNsaWNrJFNvdXJjZSAlaW4lICdHSUJfTFAnKV0gKyBiQ2xpY2skQ291bnRbd2hpY2goYkNsaWNrJFNvdXJjZSAlaW4lICdHSUJfUkcnKV0KCiMjIyAtLS0gQmFubmVyIC0+IExhbmRpbmcgUGFnZSAtPiBFeGl0IHBhdGhzIC0tLSAjIyMKIyMjIC0tLSBOKEJhbm5lciBDbGlja3MgPT0gTGFuZGluZyBQYWdlIFZpZXdzKSAtIE4oUmVnaXN0cmF0aW9uIFBhZ2UgVmlld3MpCiMjIyAtLS0gTk9URTogVExQID09IFRhc2sgTGFuZGluZyBQYWdlIChKZXR6dE1pdG1hY2hlbiksIEdMUCA9PSBHZW5lcmFsIExhbmRpbmcgUGFnZSAoTWFjaCBtaXQpCm5CVDFfVExQIDwtIGJDbGljayRDb3VudFt3aGljaChiQ2xpY2skU291cmNlICVpbiUgJ0JUMScpXSAtIAogIHBhZ2VTb3VyY2UkbltwYWdlU291cmNlJFBhZ2UgJWluJSAnU3BlemlhbDpCZW51dHplcmtvbnRvX2FubGVnZW4nICYgcGFnZVNvdXJjZSRTb3VyY2UgJWluJSAnSmV0enRNaXRtYWNoZW5fQlQxJ10KbkJUMl9UTFAgPC0gYkNsaWNrJENvdW50W3doaWNoKGJDbGljayRTb3VyY2UgJWluJSAnQlQyJyldIC0gCiAgcGFnZVNvdXJjZSRuW3BhZ2VTb3VyY2UkUGFnZSAlaW4lICdTcGV6aWFsOkJlbnV0emVya29udG9fYW5sZWdlbicgJiBwYWdlU291cmNlJFNvdXJjZSAlaW4lICdKZXR6dE1pdG1hY2hlbl9CVDInXQpuQlQzX1RMUCA8LSBiQ2xpY2skQ291bnRbd2hpY2goYkNsaWNrJFNvdXJjZSAlaW4lICdCVDMnKV0gLSAKICBwYWdlU291cmNlJG5bcGFnZVNvdXJjZSRQYWdlICVpbiUgJ1NwZXppYWw6QmVudXR6ZXJrb250b19hbmxlZ2VuJyAmIHBhZ2VTb3VyY2UkU291cmNlICVpbiUgJ0pldHp0TWl0bWFjaGVuX0JUMyddCm5HSUJfR0xQIDwtIGJDbGljayRDb3VudFt3aGljaChiQ2xpY2skU291cmNlICVpbiUgJ0dJQl9MUCcpXSAtIAogIHBhZ2VTb3VyY2UkbltwYWdlU291cmNlJFBhZ2UgJWluJSAnU3BlemlhbDpCZW51dHplcmtvbnRvX2FubGVnZW4nICYgcGFnZVNvdXJjZSRTb3VyY2UgJWluJSAnTWFjaF9taXQnXQoKIyMjIC0tLSBCYW5uZXIgKC0+IExhbmRpbmcgUGFnZSkgLT4gUmVnaXN0cmF0aW9uIFBhZ2UgLT4gRXhpdCBwYXRocyAtLS0gIyMjCiMjIyAtLS0gTihSZWdpc3RyYXRpb24gUGFnZSBWaWV3cykgLSBOKFVzZXIgUmVnaXN0cmF0aW9ucykKYlVzZXJSZWcgPC0gdXNlclJlZyAlPiUgCiAgZ3JvdXBfYnkoZXZlbnRfY2FtcGFpZ24pICU+JSAKICBzdW1tYXJpc2UoQ291bnQgPSBuKCkpCmJVc2VyUmVnJGV2ZW50X2NhbXBhaWduIDwtIHRvdXBwZXIoZ3N1Yigid21kZV9hYmMyMDE3XyIsICIiLCBiVXNlclJlZyRldmVudF9jYW1wYWlnbiwgZml4ZWQgPSBUKSkKY29sbmFtZXMoYlVzZXJSZWcpWzFdIDwtICdCYW5uZXInCm5CVDFfVExQX1JQIDwtIHBhZ2VTb3VyY2UkbltwYWdlU291cmNlJFBhZ2UgJWluJSAnU3BlemlhbDpCZW51dHplcmtvbnRvX2FubGVnZW4nICYgcGFnZVNvdXJjZSRTb3VyY2UgJWluJSAnSmV0enRNaXRtYWNoZW5fQlQxJ10gLSAKICBiVXNlclJlZyRDb3VudFtiVXNlclJlZyRCYW5uZXIgJWluJSAnQlQxJ10KbkJUMl9UTFBfUlAgPC0gcGFnZVNvdXJjZSRuW3BhZ2VTb3VyY2UkUGFnZSAlaW4lICdTcGV6aWFsOkJlbnV0emVya29udG9fYW5sZWdlbicgJiBwYWdlU291cmNlJFNvdXJjZSAlaW4lICdKZXR6dE1pdG1hY2hlbl9CVDInXSAtIAogIGJVc2VyUmVnJENvdW50W2JVc2VyUmVnJEJhbm5lciAlaW4lICdCVDInXQpuQlQzX1RMUF9SUCA8LSBwYWdlU291cmNlJG5bcGFnZVNvdXJjZSRQYWdlICVpbiUgJ1NwZXppYWw6QmVudXR6ZXJrb250b19hbmxlZ2VuJyAmIHBhZ2VTb3VyY2UkU291cmNlICVpbiUgJ0pldHp0TWl0bWFjaGVuX0JUMyddIC0gCiAgYlVzZXJSZWckQ291bnRbYlVzZXJSZWckQmFubmVyICVpbiUgJ0JUMyddCm5HSUJfR0xQX1JQIDwtIHBhZ2VTb3VyY2UkbltwYWdlU291cmNlJFBhZ2UgJWluJSAnTWFjaF9taXQnICYgcGFnZVNvdXJjZSRTb3VyY2UgJWluJSAnR0lCX0xQX2NsaWNrJ10gLSAKICBiVXNlclJlZyRDb3VudFtiVXNlclJlZyRCYW5uZXIgJWluJSAnR0lCX0xQJ10KbkdJQl9SUCA8LSBwYWdlU291cmNlJG5bcGFnZVNvdXJjZSRQYWdlICVpbiUgJ1NwZXppYWw6QmVudXR6ZXJrb250b19hbmxlZ2VuJyAmIHBhZ2VTb3VyY2UkU291cmNlICVpbiUgJ0dJQl9SR19jbGljayddIC0gCiAgYlVzZXJSZWckQ291bnRbYlVzZXJSZWckQmFubmVyICVpbiUgJ0dJQl9SRyddCgojIyMgLS0tIEJhbm5lciAoLT4gTGFuZGluZyBQYWdlKSAtPiBSZWdpc3RyYXRpb24gUGFnZSAtPiBSZWdpc3RyYXRpb24KbkJUMV9UTFBfUlBfUmVnIDwtIHJlZ0RhdGEkUmVnaXN0cmF0aW9uc1t3aGljaChyZWdEYXRhJENhbXBhaWduICVpbiUgJ0JUMScpXQpuQlQyX1RMUF9SUF9SZWcgPC0gcmVnRGF0YSRSZWdpc3RyYXRpb25zW3doaWNoKHJlZ0RhdGEkQ2FtcGFpZ24gJWluJSAnQlQyJyldCm5CVDNfVExQX1JQX1JlZyA8LSByZWdEYXRhJFJlZ2lzdHJhdGlvbnNbd2hpY2gocmVnRGF0YSRDYW1wYWlnbiAlaW4lICdCVDMnKV0KbkdJQl9HTFBfUlBfUmVnIDwtIHJlZ0RhdGEkUmVnaXN0cmF0aW9uc1t3aGljaChyZWdEYXRhJENhbXBhaWduICVpbiUgJ0dJQl9MUCcpXQpuR0lCX1JQX1JlZyA8LSByZWdEYXRhJFJlZ2lzdHJhdGlvbnNbd2hpY2gocmVnRGF0YSRDYW1wYWlnbiAlaW4lICdHSUJfUkcnKV0KCiMjIyAtLS0gZGF0YXNldAptY2FEYXRhIDwtIGRhdGEuZnJhbWUocGF0aCA9IGMoZGVwYXJzZShzdWJzdGl0dXRlKG5CVDEpKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRlcGFyc2Uoc3Vic3RpdHV0ZShuQlQyKSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkZXBhcnNlKHN1YnN0aXR1dGUobkJUMykpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGVwYXJzZShzdWJzdGl0dXRlKG5HSUIpKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRlcGFyc2Uoc3Vic3RpdHV0ZShuQlQxX1RMUCkpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGVwYXJzZShzdWJzdGl0dXRlKG5CVDJfVExQKSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkZXBhcnNlKHN1YnN0aXR1dGUobkJUM19UTFApKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRlcGFyc2Uoc3Vic3RpdHV0ZShuR0lCX0dMUCkpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGVwYXJzZShzdWJzdGl0dXRlKG5CVDFfVExQX1JQKSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkZXBhcnNlKHN1YnN0aXR1dGUobkJUMl9UTFBfUlApKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRlcGFyc2Uoc3Vic3RpdHV0ZShuQlQzX1RMUF9SUCkpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGVwYXJzZShzdWJzdGl0dXRlKG5HSUJfR0xQX1JQKSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkZXBhcnNlKHN1YnN0aXR1dGUobkdJQl9SUCkpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGVwYXJzZShzdWJzdGl0dXRlKG5CVDFfVExQX1JQX1JlZykpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGVwYXJzZShzdWJzdGl0dXRlKG5CVDJfVExQX1JQX1JlZykpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGVwYXJzZShzdWJzdGl0dXRlKG5CVDNfVExQX1JQX1JlZykpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGVwYXJzZShzdWJzdGl0dXRlKG5HSUJfR0xQX1JQX1JlZykpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGVwYXJzZShzdWJzdGl0dXRlKG5HSUJfUlBfUmVnKSkKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICksCiAgICAgICAgICAgICAgICAgICAgICB0b3RhbF9jb252ZXJzaW9ucyA9IGMoMCwgMCwgMCwgMCwgMCwgMCwgMCwgMCwgMCwgMCwgMCwgMCwgMCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbkJUMV9UTFBfUlBfUmVnLCBuQlQyX1RMUF9SUF9SZWcsIG5CVDFfVExQX1JQX1JlZywgbkdJQl9HTFBfUlBfUmVnLCBuR0lCX1JQX1JlZwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICksCiAgICAgICAgICAgICAgICAgICAgICB0b3RhbF9udWxsID0gYyhuQlQxLCBuQlQyLCBuQlQzLCBuR0lCLCBuQlQxX1RMUCwgbkJUMl9UTFAsIG5CVDNfVExQLCBuR0lCX0dMUCwgbkJUMV9UTFBfUlAsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuQlQyX1RMUF9SUCwgbkJUM19UTFBfUlAsIG5HSUJfR0xQX1JQLCBuR0lCX1JQLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgMCwgMCwgMCwgMCwgMAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKSwKICAgICAgICAgICAgICAgICAgICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGKQoKIyAtIGNvcnJlY3QgcGF0aHM6Cm1jYURhdGEkcGF0aCA8LSBnc3ViKCJuIiwgIiIsIG1jYURhdGEkcGF0aCwgZml4ZWQgPSBUKQptY2FEYXRhJHBhdGggPC0gZ3N1YigiXyIsICIgPiAiLCBtY2FEYXRhJHBhdGgsIGZpeGVkID0gVCkKZWRpdEVuZHMgPC0gd2hpY2goZ3JlcGwoIlJlZyIsIG1jYURhdGEkcGF0aCwgZml4ZWQgPSBUKSkKZm9yIChpIGluIDE6bGVuZ3RoKGVkaXRFbmRzKSkgewogIHdQYXRoIDwtIHdoaWNoKG1jYURhdGEkcGF0aCAlaW4lIGdzdWIoIiA+IFJlZyIsICIiLCBtY2FEYXRhJHBhdGhbZWRpdEVuZHNbaV1dLCBmaXhlZCA9IFQpKQogIHdPdXQgPC0gd2hpY2gobWNhRGF0YSRwYXRoID09IG1jYURhdGEkcGF0aFtlZGl0RW5kc1tpXV0pCiAgd1BhdGggPC0gc2V0ZGlmZih3UGF0aCwgd091dCkKICBtY2FEYXRhJHRvdGFsX2NvbnZlcnNpb25zW3dQYXRoXSA8LSBtY2FEYXRhJHRvdGFsX2NvbnZlcnNpb25zW3dPdXRdCn0KbWNhRGF0YSA8LSBtY2FEYXRhWy13aGljaChncmVwbCgiUmVnIiwgbWNhRGF0YSRwYXRoLCBmaXhlZCA9IFQpKSwgXQoKIyMjIC0tLSBNQ0EgbW9kZWwKYWJjMjAxN01vZGVsIDwtIG1hcmtvdl9tb2RlbChtY2FEYXRhLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZhcl9wYXRoID0gInBhdGgiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZhcl9jb252ID0gInRvdGFsX2NvbnZlcnNpb25zIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICB2YXJfbnVsbCA9ICJ0b3RhbF9udWxsIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBvcmRlciA9IDQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbnNpbSA9IDFlOCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBvdXRfbW9yZSA9IFQpCiMgLSBjb2xsZWN0IHJlbW92YWwgZWZmZWN0cyBmb3IgdGhlIG5leHQgcGxvdDoKcmU0b3JkZXIgPC0gYWJjMjAxN01vZGVsJHJlbW92YWxfZWZmZWN0cyRyZW1vdmFsX2VmZmVjdHMKIyMjIC0tLSBSZW1vdmFsIEVmZmVjdHM6CnJlIDwtIGFzLmRhdGEuZnJhbWUoYWJjMjAxN01vZGVsJHJlbW92YWxfZWZmZWN0cykKY29sbmFtZXMocmUpIDwtIGMoJ0NoYW5uZWwnLCAnUmVtb3ZhbCBFZmZlY3QnKQpyZSRDaGFubmVsIDwtIGZhY3RvcihyZSRDaGFubmVsLCBsZXZlbHMgPSBhcy5jaGFyYWN0ZXIoYWJjMjAxN01vZGVsJHJlbW92YWxfZWZmZWN0cyRjaGFubmVsX25hbWUpKQpncGxvdCA8LSBnZ3Bsb3QoZGF0YSA9IHJlLCAKICAgICAgICAgICAgICAgIGFlcyh4ID0gQ2hhbm5lbCwKICAgICAgICAgICAgICAgICAgICB5ID0gYFJlbW92YWwgRWZmZWN0YCwKICAgICAgICAgICAgICAgICAgICBsYWJlbCA9IHJvdW5kKGBSZW1vdmFsIEVmZmVjdGAsIDIpKQogICAgICAgICAgICAgICAgKSArIAogIGdlb21fYmFyKHdpZHRoID0gLjEsIGNvbG9yID0gImRhcmtibHVlIiwgZmlsbCA9ICJ3aGl0ZSIsIHN0YXQgPSAiaWRlbnRpdHkiKSArIAogIGdlb21fbGFiZWwoc2l6ZSA9IDMpICsgCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IGNvbW1hKSArIAogIHhsYWIoJ0NhbXBhaWduIENoYW5uZWwnKSArIHlsYWIoJ1JlbW92YWwgRWZmZWN0JykgKyAKICB5bGltKGMoMCwgMSkpICsgCiAgZ2d0aXRsZSgnQ2FtcGFpZ24gTXVsdGktQ2hhbm5lbCBBdHRyaWJ1dGlvbjogUmVtb3ZhbCBFZmZlY3RzJykgKyAKICB0aGVtZV9taW5pbWFsKCkgKyAKICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCkpCnN1cHByZXNzV2FybmluZ3MocHJpbnQoZ3Bsb3QpKQpgYGAKCiMjIyMgQ2FtcGFpZ24gVHJhbnNpdGlvbiBHcmFwaAoKRWFjaCBub2RlIGluIHRoZSBmb2xsb3dpbmcgZ3JhcGggcmVwcmVzZW50cyBhIHBhcnRpY3VsYXIgY2FtcGFpZ24gY2hhbm5lbC4gVGhlIGVkZ2VzIG9mIHRoZSBncmFwaCBhcmUgbGFiZWxlZCBieSB0aGUgcmVzcGVjdGl2ZSB0cmFuc2l0aW9uIHByb2JhYmlsaXRpZXMgYmV0d2VlbiB0aGUgY2hhbm5lbHMuIFRoZSBzaXplIG9mIHRoZSBub2RlIGNvcnJlc3BvbmRzIHRvIGl0cyByZW1vdmFsIGVmZmVjdC4gKipURUNITklDQUwgTk9URToqKiB0aGUgcmVtb3ZhbCBlZmZlY3RzIGFyZSBkZXJpdmVkIGZyb20gYSBNYXJrb3YgbW9kZWwgb2Ygb3JkZXIgNCwgd2hpbGUgdGhlIHRyYW5zaXRpb25hbCBwcm9iYWJpbGl0aWVzIGFyZSBkZXJpdmVkIGRpcmVjdGx5IGZyb20gdGhlIDFzdCBvcmRlciBtb2RlbC4KCmBgYHtyIGVjaG8gPSBULCB3YXJuaW5nID0gJ2hpZGUnLCBtZXNzYWdlID0gRiwgZXZhbCA9IFR9CiMjIyAtLS0gTUNBIG1vZGVsOiAxc3Qgb3JkZXIgZm9yIGNoYW5uZWwtdG8tY2hhbm5lbCB0cmFuc2l0aW9ucwphYmMyMDE3TW9kZWwgPC0gbWFya292X21vZGVsKG1jYURhdGEsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdmFyX3BhdGggPSAicGF0aCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdmFyX2NvbnYgPSAidG90YWxfY29udmVyc2lvbnMiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZhcl9udWxsID0gInRvdGFsX251bGwiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIG9yZGVyID0gMSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBvdXRfbW9yZSA9IFQpCiMjIyAtLS0gcGxvdCB3LiB7aWdyYXBofQphYmMyMDE3TmV0IDwtIGRhdGEuZnJhbWUob3Vnb2luZyA9IGFiYzIwMTdNb2RlbCR0cmFuc2l0aW9uX21hdHJpeCRjaGFubmVsX2Zyb20sCiAgICAgICAgICAgICAgICAgICAgICAgICBpbmNvbWluZyA9IGFiYzIwMTdNb2RlbCR0cmFuc2l0aW9uX21hdHJpeCRjaGFubmVsX3RvLAogICAgICAgICAgICAgICAgICAgICAgICAgc3RyaW5nc0FzRmFjdG9ycyA9IEYpCmFiYzIwMTdOZXQkb3Vnb2luZyA8LSBzYXBwbHkoYWJjMjAxN05ldCRvdWdvaW5nLCBmdW5jdGlvbih4KSB7CiAgY2ggPC0gZ3N1YigiKHN0YXJ0KSIsICJTVEFSVCIsIGZpeGVkID0gVCwgeCkKICBjaCA8LSBnc3ViKCIobnVsbCkiLCAiRVhJVCIsIGZpeGVkID0gVCwgY2gpCiAgY2ggPC0gZ3N1YigiKGNvbnZlcnNpb24pIiwgIlJFR0lTVFJBVElPTiIsIGZpeGVkID0gVCwgY2gpCiAgY2gKfSkKYWJjMjAxN05ldCRpbmNvbWluZyA8LSBzYXBwbHkoYWJjMjAxN05ldCRpbmNvbWluZywgZnVuY3Rpb24oeCkgewogIGNoIDwtIGdzdWIoIihzdGFydCkiLCAiU1RBUlQiLCBmaXhlZCA9IFQsIHgpCiAgY2ggPC0gZ3N1YigiKG51bGwpIiwgIkVYSVQiLCBmaXhlZCA9IFQsIGNoKQogIGNoIDwtIGdzdWIoIihjb252ZXJzaW9uKSIsICJSRUdJU1RSQVRJT04iLCBmaXhlZCA9IFQsIGNoKQogIGNoCn0pCgphYmMyMDE3TmV0IDwtIGdyYXBoLmRhdGEuZnJhbWUoYWJjMjAxN05ldCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkaXJlY3RlZCA9IFQpCkUoYWJjMjAxN05ldCkkbGFiZWwgPC0gcm91bmQoYWJjMjAxN01vZGVsJHRyYW5zaXRpb25fbWF0cml4JHRyYW5zaXRpb25fcHJvYmFiaWxpdHksIDIpClYoYWJjMjAxN05ldCkkY29sb3IgPC0gYygnd2hpdGUnLCAKICAgICAgICAgICAgICAgICAgICAgICAgICdpbmRpYW5yZWQxJywgJ2luZGlhbnJlZDInLCAnaW5kaWFucmVkMycsICdjYWRldGJsdWUnLAogICAgICAgICAgICAgICAgICAgICAgICAgJ3JlZCcsICdibHVlJywgJ3llbGxvdycsIAogICAgICAgICAgICAgICAgICAgICAgICAgJ3doaXRlJywgJ3doaXRlJykKVihhYmMyMDE3TmV0KSRzaXplIDwtIGMoMjAsIHJlNG9yZGVyKjQwLCAyMCwgMjApClYoYWJjMjAxN05ldCkkZnJhbWUuY29sb3IgPC0gJ3doaXRlJwoKIyAtIHBsb3Qgdy4ge2lncmFwaH0KY29vcmRzIDwtIGxheW91dF8oYWJjMjAxN05ldCwgYXNfdHJlZSgpKQpwYXIobWFpPWMocmVwKDAsNCkpKQpwbG90KGFiYzIwMTdOZXQsCiAgICAgbGF5b3V0ID0gY29vcmRzLAogICAgIGVkZ2Uud2lkdGggPSAuNzUsCiAgICAgZWRnZS5jb2xvciA9ICJncmV5IiwKICAgICBlZGdlLmFycm93LnNpemUgPSAwLjM1LAogICAgIGVkZ2UuY3VydmVkID0gMC42LAogICAgIGVkZ2UubGFiZWwuZmFtaWx5ID0gInNhbnMiLAogICAgIGVkZ2UubGFiZWwuY29sb3IgPSAiYmxhY2siLAogICAgIGVkZ2UubGFiZWwuY2V4ID0gLjYsCiAgICAgdmVydGV4LnNoYXBlID0gImNpcmNsZSIsCiAgICAgdmVydGV4LmxhYmVsLmNvbG9yID0gImJsYWNrIiwKICAgICB2ZXJ0ZXgubGFiZWwuZm9udCA9IDEsCiAgICAgdmVydGV4LmxhYmVsLmZhbWlseSA9ICJzYW5zIiwKICAgICB2ZXJ0ZXgubGFiZWwuY2V4ID0gLjc1LAogICAgIHZlcnRleC5sYWJlbC5kaXN0ID0gLjI1LAogICAgIHZlcnRleC5sYWJlbC5kaXN0ID0gLjQ1LAogICAgIHJlc2NhbGUgPSBGLAogICAgIHhsaW0gPSBjKC0xLCAxKSwKICAgICB5bGltID0gYygwLCA0KSwKICAgICBtYXJnaW4gPSBjKHJlcCgwLDQpKSkKYGBgCgojIyMjIFN1bW1hcnkKClRoZSBsYW5kaW5nIHBhZ2UgZm9yIHNwZWNpZmljIHRhc2tzIChgSmV0enRNaXRtYWNoZW5gLCB0aGUgYFRMUGAgY2hhbm5lbCBpbiB0aGUgZ3JhcGgpIGFuZCB0aGUgYEdJQmAgY2FtcGFpZ24gYXJlIGVzc2VudGlhbGx5IG5vIGRpZmZlcmVudCBpbiByZXNwZWN0IHRvIGhvdyBtdWNoIHRoZXkgaW5mbHVlbmNlIHVzZXIgcmVnaXN0cmF0aW9uLiBXZSBoYXZlIGxlYXJuZWQgZnJvbSB0aGUgQS9CIHRlc3RzIHRoYXQgbm8gaW5kaXZpZHVhbCBgQlRgIChpLmUuIHNwZWNpZmljIHRhc2spIGJhbm5lciBjb21wYXJlcyB0byB0aGUgcGVyZm9ybWFuY2Ugb2YgYEdJQl9SR2Agd2hpY2ggbGVhZHMgZGlyZWN0bHkgdG8gdGhlIHJlZ2lzdHJhaW9uIHBhZ2UuIEhvd2V2ZXIsIHdoZW4gY29uc2lkZXJlZCB0b2dldGhlciwgdGhlIGJhbm5lcnMgbGVhZGluZyB0byB0aGUgYEpldHp0TWl0bWFjaGVuYCBoYXZlIGEgcGVyZm9ybWFuY2UgY29tcGFyYWJsZSB0byBgR0lCX1JHYC4gVGhlIEdlbmVyYWwgSW52aXRhdGlvbiBsYW5kaW5nIHBhZ2UgYE1hY2hfbWl0YCBsYWNrcyBzdWNoIGFuIGVmZmVjdC4KCgojIyMgNS4zIENhbXBhaWduIEV2YWx1YXRpb24gU3VtbWFyeQoKKipBU1NVTVBUSU9OUyoqIGFzIHN0YXRlZCBpbiB0aGUgW0NhbXBhaWduIEtpY2tPZmYgUHJlc2VudGF0aW9uXShodHRwczovL2RvY3MuZ29vZ2xlLmNvbS9wcmVzZW50YXRpb24vZC8xOEhxN1VMa1dWb1F0TXY1RUkySlBjWUVkSWxUYzJoOGJnZE1Rd0MzNWdFOC9lZGl0I3NsaWRlPWlkLmcyMjgzZDM5ZDlkXzBfMjMxKToKCi0gKipBc3N1bXB0aW9uIDE6KiogTW9yZSB1c2VycyByZWdpc3RlciB3aGVuIGdpdmVuIGEgY2xlYXIgYW5kIGxvdyBsZXZlbCBlbnRyeSB0YXNrLiAqKlJFU1VMVFM6KiogV2hlbiBjb21wYXJpbmcgaW5kaXZpZHVhbCBiYW5uZXIgY2FtcGFpZ25zLCBBL0IgdGVzdGluZyBzaG93cyB0aGF0IG1vcmUgdXNlcnMgcmVnaXN0ZXIgdmlhIHRoZSBHZW5lcmFsIEludml0YXRpb24gQmFubmVyIGNhbXBhaWduLCBlc3BlY2lhbGx5IHdoZW4gZ2l2ZW4gbm8gaW50ZXJtZWRpYXRlIGxhbmRpbmcgcGFnZSBwcmlvciB0byB0aGUgcmVnaXN0cmF0aW9uIHBhZ2UuIEhvd2V2ZXIsIHRoZSBgSmV0enRNaXRtYWNoZW5gIGNhbXBhZ2luIGluIGdlbmVyYWwgaGFzIGEgcGVyZm9ybWFuY2UgY29tcGFyYWJsZSB0byB0aGUgYEdJQmAgY2FtcGFpZ24sIHdoaWxlIHRoZSBgSmV0enRNaXRtYWNoZW5gIHBhZ2Ugd2FzIGNlcnRhaW5seSBtb3JlIGltcG9ydGFudCBmb3IgdXNlciByZWdpc3RyYXRpb24gdGhhbiB0aGUgZ2VuZXJhbCBgTWFjaF9taXRgIHBhZ2UgLSBhcyB3ZSBoYXZlIGxlYXJuZWQgZnJvbSB0aGUgY2FtcGFpZ24gTXVsdGktQ2hhbm5lbCBBdHRyaWJ1dGlvbiBtb2RlbC4KCi0gKipBc3N1bXB0aW9uIDI6KiogQSBsYW5kaW5nIHBhZ2Ugd2l0aCBtb3JlIGluZm9ybWF0aW9uIGJlZm9yZSByZWdpc3RyYXRpb24gaXMgbmVjZXNzYXJ5LiAqKlJFU1VMVFM6KiogQS9CIHRlc3Rpbmcgc2hvd3MgdGhhdCBtb3JlIHVzZXJzIHJlZ2lzdGVyIHZpYSBgR0lCX1JHYCBiYW5uZXIgY2FtcGFpZ24gdGhhdCBsZWFkcyBkaXJlY3RseSB0byB0aGUgcmVnaXN0cmF0aW9uIHBhZ2UgdGhhbiB2aWEgdGhlIGBHSUJfTFBgIGJhbm5lciBjYW1wYWlnbiB0aGF0IGhhcyBhbiBpbnRlcm1lZGlhdGUgbGFuZGluZyBwYWdlLiBIb3dldmVyLCBgQXNzdW1wdGlvbjIgYCBpcyBzdXBwb3J0ZWQgYnkgYSBoaWdoIHJlbW92YWwgZWZmZWN0IG9mIHRoZSBgSmV0enRNaXRtYWNoZW5gIHBhZ2UuIAoKLSAqKkFzc3VtcHRpb24gMzoqKiBBIGdlbmVyYWwgaW52aXRhdGlvbiBoYXMgYSBsb3dlciBjb252ZXJzaW9uIHJhdGUgdGhhbiBzcGVjaWZpYyBpbnZpdGF0aW9ucyB0byByZWdpc3Rlci4gKipSRVNVTFRTOioqIFRoZSB0b3RhbCBudW1iZXIgb2YgcmVnaXN0ZXJlZCB1c2VycyB2aWEgdGhlIGBKZXR6dE1pdG1hY2hlbmAgY2FtcGFnaW4gKGBCVDFgLCBgQlQyYCwgYW5kIGBCVDNgIGJhbm5lciBjYW1wYWlnbnMgdGFrZW4gdG9nZXRoZXIpIGlzIDUzNSwgd2hpbGUgdGhlIHRvdGFsIG51bWJlciBvZiB1c2VycyByZWdpc3RlcmVkIHZpYSB0aGUgR2VuZXJhbCBJbnZpdGF0aW9uIGNhbXBhaWduIGlzIDUxOSwgYW4gYWxtb3N0IDUwLTUwIHNwbGl0LgoKKipOT1RFOioqICoqQWxsIHRoZXNlIGFzc3VtcHRpb25zIGFyZSB2YWxpZCBpZiB3ZSBjb25zaWRlciB0aGUgY3JpdGVyaW9uIG9mIG1ha2luZyBhbiBlZGl0IGF0IGFsbCBpbnN0ZWFkLioqIAoKKipTVUdHRVNUSU9OUyoqCgotICoqU3VnZ2VzdGlvbiBOby4gMS4qKiBSZW1vdmUgdGhlIGBHSUJfUkdgIGJhbm5lciBjYW1wYWlnbiBmcm9tIGZ1dHVyZSBjYW1wYWlnbnMuIEl0IGRyaXZlcyBhbG1vc3QgOTAlIG9mIHRoZSB0cmFmZmljIHRvd2FyZHMgdGhlIHJlZ2lzdHJhdGlvbiBwYWdlIHdoaWxlIGJlaW5nIHRoZSBsZWFzdCBlZmZpY2llbnQgaW4gdGVybXMgb2YgaW5mbHVlbmNpbmcgbmV3IHVzZXIgZWRpdHMgYXQgdGhlIHNhbWUgdGltZSAoKipOT1RFKio6IGxlYXN0IGVmZmljaWVudCBpbiB0ZXJtcyBvZiB0aGUgZXhwZWN0ZWQgbnVtYmVyIG9mIHVzZXIgZWRpdHMsIG5vdCBpbiB0ZXJtcyBvZiBtYWtpbmcgYW55IGVkaXRzIGF0IGFsbCkuIFRoYXQgd291bGQgcHJvYmFibHkgbWVhbiB0aGF0IGBkZXdpa2lgIHdvdWxkIGFjcXVpcmUgbGVzcyBuZXcgdXNlcnMgZHVyaW5nIHRoZSBjYW1wYWlnbiwgYnV0IGFnYWluIHRoZSBnb2FsIGlzIHByb2JhYmx5IGZvciBpdCB0byBhY3F1aXJlIG5ldyBlZGl0b3JzLiBPciwgZXZlbiBiZXR0ZXIsIHRha2UgYSBsb29rIGF0IG15IGBTdWdnZXN0aW9uIE5vLiAyYC4KCi0gKipTdWdnZXN0aW9uIE5vLiAyLioqIFRoaW5rIGFib3V0IHRoZSBwb3NzaWJpbGl0eSB0byBpbnRlZ3JhdGUgdGhlIGNhbXBhaWduIGNvbnRlbnQgKGUuZy4gd2hhdCBpcyBvbiB0aGUgbGFuZGluZyBwYWdlcyBub3cpIHRvIHRoZSByZWdpc3RyYXRpb24gcGFnZSAqZGlyZWN0bHkqLiBSYXRpbzogdGhlIGBHSUJfUkdgIGJhbm5lciBjYW1wYWlnbiBoYXMgbm8gaW50ZXJtZWRpYXRlIGxhbmRpbmcgcGFnZSBiZXR3ZWVuIGJhbm5lciBwcmVzZW50YXRpb24gYW5kIHJlZ2lzdHJhdGlvbiwgbGVhZGluZyB0byB0aGUgaGlnaGVzdCBudW1iZXIgb2YgcmVnaXN0ZXJlZCBuZXcgdXNlcnM7IG9uIHRoZSBvdGhlciBoYW5kLCB0aG9zZSBiYW5uZXIgY2FtcGFpZ25zIHRoYXQgaW5zdGFudGlhdGUgYSBzcGVjaWZpYyB0YXNrIGxlYWQgdG8gaGF2aW5nIG1vcmUgdXNlciBlZGl0cyBvbiB0aGUgYXZlcmFnZSB0aGFuIGl0IChpbiBnZW5lcmFsOyB0aGlzIGlzIG5vdCB2YWxpZCBmb3IgYEJUMmApLiAqKk1heWJlIGludGVncmF0aW5nIHRoZSBjYW1wYWlnbiBjb250ZW50IHdpdGggdGhlIHJlZ2lzdHJhdGlvbiBwYWdlIGNhbiBwcm92aWRlIGEgbW9yZSBwb3dlcmZ1bCBjb21iaW5hdGlvbiB0aGF0IHdvdWxkIGFmZmVjdCBwb3NpdGl2ZWx5IGJvdGggcmVnaXN0cmF0aW9uIGFuZCBmdXR1cmUgZWRpdGluZy4qKgoKLSAqKlN1Z2dlc3Rpb24gTm8uIDMuKiogUmVtb3ZlIHRoZSBgR3VpZGVkIFRvdXJgIGZyb20gb3VyIGZ1dHVyZSBjYW1wYWlnbnM7IHRoZSBhbmFseXNpcyBvZiBpdHMgY2F1c2FsIHBvd2VyIHN1Z2dlc3RzIHRoYXQgaXQgaGFzIGEgbmVnYXRpdmUgaW5mbHVlbmNlIHRvd2FyZHMgbWFraW5nIGF0IGxlYXN0IG9uZSBlZGl0IG9uIGJlaGFsZiBvZiBhIG5ld2x5IHJlZ2lzdGVyZWQgdXNlci4KCgojIyA2LiBQb3N0LUNhbXBhaWduIEFuYWx5dGljcwoKVGhpcyBzZWN0aW9uIHByb3ZpZGVzIHNldmVyYWwgaW5zaWdodHMgdGhhdCB3ZXJlIHNvdWdodCBvbiB0aGUgYmVoYWxmIG9mIHRoZSBjYW1wYWlnbiBtYW5hZ2VtZW50IHRlYW0gZm9sbG93aW5nIHRoZSBlbmQgb2YgdGhlIEF1dHVtbiBCYW5uZXIgQ2FtcGFpZ24gMjAxNy4KCiMjIyA2LiAxIDEwLiBPY3RvYmVyIDIwMTc6IGEgY2hhbmdlIGluIGJhbm5lcnMgb2NjdXJzLiBEaWQgaXQgaW5mbHVlbmNlIChhKSB0aGUgbnVtYmVyIG9mIHVzZXIgcmVnaXN0cmF0aW9ucyBhbmQgKGIpIHRoZSBudW1iZXIgb2YgdXNlciBlZGl0cz8KCkZpcnN0LCBsZXQncyBoYXZlIGEgbG9vayBhdCB0aGUgdG90YWwgbnVtYmVyIG9mIHVzZXIgcmVnaXN0cmF0aW9ucyBkYWlseToKCmBgYHtyIGVjaG8gPSBULCB3YXJuaW5nID0gJ2hpZGUnLCBtZXNzYWdlID0gRiwgZXZhbCA9IFR9CnJlZ1Bsb3RTZXREYWlseSREYXRlIDwtIGZhY3RvcihyZWdQbG90U2V0RGFpbHkkRGF0ZSwgbGV2ZWxzID0gc29ydChyZWdQbG90U2V0RGFpbHkkRGF0ZSkpCmdncGxvdChyZWdQbG90U2V0RGFpbHksIGFlcyh4ID0gRGF0ZSwgeSA9IFJlZ2lzdHJhdGlvbnMpKSArCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIAogICAgICAgICAgIHBvc2l0aW9uID0gImRvZGdlIiwgCiAgICAgICAgICAgd2lkdGggPSAuMikgKwogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBjb21tYSkgKwogIGdndGl0bGUoJ0F1dHVtbiBCYW5uZXIgQ2FtcGFpZ24gMjAxNzogVG90YWwgVXNlciBSZWdpc3RyYXRpb25zIERhaWx5JykgKwogIHRoZW1lX21pbmltYWwoKSArIAogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIHNpemUgPSA4LCBoanVzdCA9IDEpKSArCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApKSArCiAgdGhlbWUobGVnZW5kLnRpdGxlID0gZWxlbWVudF9ibGFuaygpKSArCiAgdGhlbWUocGFuZWwuZ3JpZC5tYWpvci54ID0gZWxlbWVudF9ibGFuaygpKSArCiAgdGhlbWUocGFuZWwuZ3JpZC5taW5vci54ID0gZWxlbWVudF9ibGFuaygpKSArCiAgdGhlbWUocGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSkKYGBgCgpGcm9tIHRoZSBjaGFydCBvbmx5IHdlIGNhbiBzZWUgYSBzaGFycCBkcm9wIGluIHRoZSBudW1iZXIgb2YgdXNlciByZWdpc3RyYXRpb25zIGZvbGxvd2luZyAxMC8wOS8yMDE3LiBUaGUgZHJvcCBpcyBvYnZpb3VzIGV2ZW4gZ2l2ZW4gdGhhdCB0aGVyZSBpcyBhIG5vdGljZWFibGUgbmVnYXRpdmUgdHJlbmQgc2luY2UgdGhlIGJlZ2lubmluZyBvZiB0aGUgY2FtcGFpZ24gKGEgcmF0aGVyIGV4cGVjdGVkIGZpbmRpbmcpLiBIb3dldmVyLCBsZXQncyBwZXJmb3JtIGEgc2ltcGxlIENoaS1TcXVhcmUgdGVzdCB0byBjb21wYXJlIHRoZSBudW1iZXIgb2YgdXNlciByZWdpc3RyYXRpb25zOiB0aGUgZmlyc3QgZml2ZSBkYXlzIG9mIHRoZSBjYW1wYWlnbiB2cy4gdGhlIGxhc3QgZm91ciBkYXlzICh1c2luZyBhIDU6NCByYXRpbyBmb3IgdGhlIGV4cGVjdGVkIGRpc3RyaWJ1dGlvbiBzcGxpdCkuCgpgYGB7ciBlY2hvID0gVCwgd2FybmluZyA9ICdoaWRlJywgbWVzc2FnZSA9IEYsIGV2YWwgPSBUfQpwb3B1bGF0aW9uUCA8LSBjKDUvOSwgNC85KQpuIDwtIHN1bShyZWdQbG90U2V0RGFpbHkkUmVnaXN0cmF0aW9ucykKZXhwZWN0ZWRDb3VudHMgPC0gbipwb3B1bGF0aW9uUApzIDwtIGMoc3VtKHJlZ1Bsb3RTZXREYWlseSRSZWdpc3RyYXRpb25zWzE6NV0pLCBzdW0ocmVnUGxvdFNldERhaWx5JFJlZ2lzdHJhdGlvbnNbNjo5XSkpCnByaW50KHBhc3RlKCJFeHBlY3RlZDogIiwgcGFzdGUocm91bmQoZXhwZWN0ZWRDb3VudHMsIDIpLCBjb2xsYXBzZSA9ICIsICIpKSkKcHJpbnQocGFzdGUoIkRhdGFzZXQ6ICIsIHBhc3RlKHMsIGNvbGxhcHNlID0gIiwgIikpKQpjaGlTcSA8LSBzdW0oKChzIC0gZXhwZWN0ZWRDb3VudHMpXjIpL2V4cGVjdGVkQ291bnRzKQpwcmludChwYXN0ZSgiQ2hpLVNxdWFyZSBTdGF0aXN0aWNzOiIsIGNoaVNxLCBzZXAgPSAiICIpKQojIC0gZGVncmVlcyBvZiBmcmVlZG9tCmRmIDwtIDIgLSAxICMgayA9PSAyID09IG51bWJlciBvZiBjYXRlZ29yaWVzCnByaW50KHBhc3RlKCJELkYuOiIsIGRmLCBzZXAgPSAiICIpKQojIC0gVGVzdCBzaWduaWZpY2FuY2UsIGFscGhhID09IC4wNQpzaWcgPC0gcGNoaXNxKGNoaVNxLCBkZiwgbG93ZXIudGFpbD1GKSAjIHVwcGVyIHRhaWwKcHJpbnQocGFzdGUoIlR5cGUgSSBFcnJvciBQcm9iLjoiLCBzaWcsIHNlcCA9ICIgIikpCmBgYAoKVGhlIGNoYW5nZSBpbiBiYW5uZXJzIHRoYXQgdG9vayBwbGFjZSBvbiBgMTAvMTAvMjAxN2AgaGFzIHByb2JhYmx5IGluZmx1ZW5jZWQgdGhlIG51bWJlciBvZiB1c2VyIHJlZ2lzdHJhdGlvbnMgaW4gYSBuZWdhdGl2ZSB3YXkuCgpMZXQncyBwZXJmb3JtIHRoZSBzYW1lIGNoZWNrIGZvciB0aGUgbnVtYmVyIG9mIHVzZXIgZWRpdHMuCgpgYGB7ciBlY2hvID0gVCwgd2FybmluZyA9ICdoaWRlJywgbWVzc2FnZSA9IEYsIGV2YWwgPSBUfQplZGl0c0RhaWx5IDwtIGVkaXRHVERhdGEgJT4lIAogIGRwbHlyOjpzZWxlY3QoZWRpdHMsIGB0aW1lc3RhbXAueGApICU+JSAKICBncm91cF9ieShgdGltZXN0YW1wLnhgKSAlPiUgCiAgc3VtbWFyaXNlKEVkaXRzID0gc3VtKGVkaXRzKSkKY29sbmFtZXMoZWRpdHNEYWlseSkgPC0gYygnRGF0ZScsICdFZGl0cycpCmVkaXRzRGFpbHkkRGF0ZSA8LWZhY3RvcihlZGl0c0RhaWx5JERhdGUsIGxldmVscyA9IHNvcnQoZWRpdHNEYWlseSREYXRlKSkKZ2dwbG90KGVkaXRzRGFpbHksIGFlcyh4ID0gRGF0ZSwgeSA9IEVkaXRzKSkgKwogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLCAKICAgICAgICAgICBwb3NpdGlvbiA9ICJkb2RnZSIsIAogICAgICAgICAgIHdpZHRoID0gLjIpICsKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gY29tbWEpICsKICBnZ3RpdGxlKCdBdXR1bW4gQmFubmVyIENhbXBhaWduIDIwMTc6IFRvdGFsIFVzZXIgRWRpdHMgRGFpbHknKSArCiAgdGhlbWVfbWluaW1hbCgpICsgCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgc2l6ZSA9IDgsIGhqdXN0ID0gMSkpICsKICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCkpICsKICB0aGVtZShsZWdlbmQudGl0bGUgPSBlbGVtZW50X2JsYW5rKCkpICsKICB0aGVtZShwYW5lbC5ncmlkLm1ham9yLnggPSBlbGVtZW50X2JsYW5rKCkpICsKICB0aGVtZShwYW5lbC5ncmlkLm1pbm9yLnggPSBlbGVtZW50X2JsYW5rKCkpICsKICB0aGVtZShwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpKQpgYGAKClRoZSBzaXR1YXRpb24gaGVyZSBpcyBtb3JlIGNvbXBsaWNhdGVkIGdpdmVuIHRoZSAoYSkgcHJlc2VuY2Ugb2YgdGhlIGdlbmVyYWwgbmVnYXRpdmUgdHJlbmQgc2luY2UgdGhlIG9uc2V0IG9mIHRoZSBjYW1wYWlnbiBhbmQgKGIpIGEgbG9jYWwgaW5jcmVhc2UgaW4gdGhlIG51bWJlciBvZiB1c2VyIGVkaXRzIGFmdGVyIGAxMC8xMC8yMDE3YC4gVGhlIGFwcHJvcHJpYXRlIHN0cmF0ZWd5IHdvdWxkIHByb2JhYmx5IGNhbGwgZm9yIGEgdGltZS1zZXJpZXMgZGUtdHJlbmRpbmcgZmlyc3QsIGZvbGxvd2VkIGJ5IHRoZSBhbmFseXNpcyBvZiB0aGUgcmFuZG9tIGNvbXBvbmVudCBvbmx5OyBob3dldmVyLCB3ZSBoYXZlIG9ubHkgbmluZSBwb2ludHMgaW4gdGhlIGRhdGEgc2V0LgoKYGBge3IgZWNobyA9IFQsIHdhcm5pbmcgPSAnaGlkZScsIG1lc3NhZ2UgPSBGLCBldmFsID0gVH0KcG9wdWxhdGlvblAgPC0gYyg1LzksIDQvOSkKbiA8LSBzdW0oZWRpdHNEYWlseSRFZGl0cykKZXhwZWN0ZWRDb3VudHMgPC0gbipwb3B1bGF0aW9uUApzIDwtIGMoc3VtKGVkaXRzRGFpbHkkRWRpdHNbMTo1XSksIHN1bShlZGl0c0RhaWx5JEVkaXRzWzY6OV0pKQpwcmludChwYXN0ZSgiRXhwZWN0ZWQ6ICIsIHBhc3RlKHJvdW5kKGV4cGVjdGVkQ291bnRzLCAyKSwgY29sbGFwc2UgPSAiLCAiKSkpCnByaW50KHBhc3RlKCJEYXRhc2V0OiAiLCBwYXN0ZShzLCBjb2xsYXBzZSA9ICIsICIpKSkKY2hpU3EgPC0gc3VtKCgocyAtIGV4cGVjdGVkQ291bnRzKV4yKS9leHBlY3RlZENvdW50cykKcHJpbnQocGFzdGUoIkNoaS1TcXVhcmUgU3RhdGlzdGljOiIsIGNoaVNxLCBzZXAgPSAiICIpKQojIC0gZGVncmVlcyBvZiBmcmVlZG9tCmRmIDwtIDIgLSAxICMgayA9PSAyID09IG51bWJlciBvZiBjYXRlZ29yaWVzCnByaW50KHBhc3RlKCJELkYuOiIsIGRmLCBzZXAgPSAiICIpKQojIC0gVGVzdCBzaWduaWZpY2FuY2UsIGFscGhhID09IC4wNQpzaWcgPC0gcGNoaXNxKGNoaVNxLCBkZiwgbG93ZXIudGFpbD1GKSAjIHVwcGVyIHRhaWwKcHJpbnQocGFzdGUoIlR5cGUgSSBFcnJvciBQcm9iLjoiLCBzaWcsIHNlcCA9ICIgIikpCmBgYAoKVGhlIGNoaS1zcXVhcmUgdGVzdCBpbmRpY2F0ZXMgdGhhdCBtdWNoIGxlc3MgdXNlciBlZGl0cyBvY2N1cnJpbmcgc2luY2UgYDEwLzEwLzIwMTdgLiBHaXZlbiB0aGUgbG9jYWwgaW5jcmVhc2UgaW4gdGhlIG51bWJlciBvZiBlZGl0cyBmb2xsb3dpbmcgYDEwLzEwLzIwMTdgLCB3aGljaCBpcyBwcm9iYWJseSB1bnVzdWFsIGdpdmVuIHRoZSBwcmVzZW5jZSBvZiB0aGUgZ2VuZXJhbCBuZWdhdGl2ZSB0cmVuZCBzaW5jZSB0aGUgb25zZXQgb2YgdGhlIGNhbXBhaWduLCB3ZSBjYW5ub3QgcnVsZSBvdXQgdGhlIHBvc3NpYmlsaXR5IHRoYXQgdGhlIGJhbm5lciBjaGFuZ2Ugb24gYDEwLzEwLzIwMTdgIGhhcyBpbmZsdWVuY2VkIHRoZSBudW1iZXIgb2YgdXNlciBlZGl0cyBpbiBhIHBvc2l0aXZlIHdheS4KCiMjIyA2LiAyIERpZCB0aGUgcmVnaXN0ZXJlZCB1c2VycyByZWFsbHkgZm9sbG93ZWQgdGhlIGluc3RydWN0aW9ucyBhcyBwcm92aWRlZCBpbiB0aGUgU3BlY2lmaWMgVGFzayBCYW5uZXIgQ2FtcGFpZ25zIGluIHRoZWlyIGVkaXRzPwoKYGBge3IgZWNobyA9IFQsIHdhcm5pbmcgPSAnaGlkZScsIG1lc3NhZ2UgPSBGLCBldmFsID0gVH0KCmBgYAoKIyMjIDYuIDMgSG93IG1hbnkgcmV2ZXJ0ZWQgZWRpdHMgdGhlcmUgd2VyZSAoYSkgcGVyIGNhbXBhaWduLCBhbmQgKGIpIHBlciB1c2VyPwoKKipOT1RFOioqIHRoZSBmb2xsb3dpbmcgRGF0YSBBY3F1aXNpdGlvbiBjb2RlIGNodW5rIGlzIG5vdCBmdWxseSByZXByb2R1Y2libGUgZnJvbSB0aGlzIFJlcG9ydC4gVGhlIGRhdGEgYXJlIGNvbGxlY3RlZCBieSBydW5uaW5nIHRoZSBzY3JpcHQgYGFiYzIwMTdfUFJPRF9SZXZlcnRlZEVkaXRzLlJgIG9uIHN0YXQxMDA1LmVxaWFkLndtbmV0LCBjb2xsZWN0aW5nIHRoZSBkYXRhIGFzIGAudHN2YCBmaWxlcywgY29weWluZyBtYW51YWxseSwgYW5kIHByb2Nlc3NpbmcgbG9jYWxseS4gUnVuIGZyb20gc3RhdDEwMDUgc3RhdCBib3ggYnkgZXhlY3V0aW5nIGBSc2NyaXB0IC9ob21lL2dvcmFuc20vUlNjcmlwdHMvYWJjMjAxNy9hYmMyMDE3X1BST0RfUmV2ZXJ0ZWRFZGl0cy5SYC4KCmBgYHtyIGVjaG8gPSBULCBldmFsID0gRn0KIyMjIC0tLSBTY3JpcHQ6IGFiYzIwMTdfUFJPRF9PdmVyYWxsRGFpbHlVcGRhdGUuUgojIyMgLS0tIHRoZSBmb2xsb3dpbmcgcnVucyBvbiBzdGF0MTAwNS5lcWlhZC53bW5ldAojIyMgLS0tIFJzY3JpcHQgL2hvbWUvZ29yYW5zbS9SU2NyaXB0cy9hYmMyMDE3L2FiYzIwMTdfUFJPRF9SZXZlcnRlZEVkaXRzLlIKCiMjIyAtLS0gVGhlIHNjcmlwdCBjb2xsZWN0cyBhbmQgd3JhbmdsZXMgYSBkYXRhc2V0IGZvciBBQkMgMjAxNyBwb3N0LWNhbXBhaWduIGFuYWx5dGljcwojIyMgLS0tIFdNREUgQXV0dW1uIEJhbm5lciBDYW1wYWlnbiAyMDE3LgoKIyMjIC0tLSBHb3JhbiBTLiBNaWxvdmFub3ZpYywgRGF0YSBTY2llbnRpc3QsIFdNREUKIyMjIC0tLSBOb3ZlbWJlciAwNiwgMjAxNy4KCiMjIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQojIyMgMC4gU2V0dXAKIyMjIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCnJtKGxpc3QgPSBscygpKQpsaWJyYXJ5KGRwbHlyKQoKIyAtIGdldCB1c2VyIHJlZ2lzdHJhdGlvbiBkYXRhOiBhYmMyMDE3X3VzZXJSZWdpc3RyYXRpb25zLnRzdgojIC0gdGhlbiBnZXQgdXNlciBJRHMgZnJvbSByZWdpc3RlcmVkOgpzZXR3ZCgnL2hvbWUvZ29yYW5zbS9fbWlzY1dNREUvYWJjMjAxN19EYXRhT1VUL2FiYzIwMTdfT2ZmaWNpYWxEYXRhc2V0cy9hYmMyMDE3X0RhaWx5VXBkYXRlLycpCmxGIDwtIGxpc3QuZmlsZXMoKQpsRiA8LSBsRltncmVwbCgndXNlclJlZ2lzdHJhdGlvbnMnLCBsRiwgZml4ZWQgPSBUKV0KdXNlclJlZyA8LSByZWFkLnRhYmxlKGxGLCAKICAgICAgICAgICAgICAgICAgICAgIHF1b3RlID0gIiIsCiAgICAgICAgICAgICAgICAgICAgICBzZXAgPSAiXHQiLAogICAgICAgICAgICAgICAgICAgICAgaGVhZGVyID0gVCwKICAgICAgICAgICAgICAgICAgICAgIGNoZWNrLm5hbWVzID0gRiwKICAgICAgICAgICAgICAgICAgICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGKQp1c2VyUmVnIDwtIHVzZXJSZWcgJT4lIAogIGRwbHlyOjpzZWxlY3QoZXZlbnRfdXNlcklkLCBldmVudF9pc1NlbGZNYWRlKSAlPiUgCiAgZmlsdGVyKGV2ZW50X2lzU2VsZk1hZGUgPT0gMSkKIyAtIHVpZHM6CnVpZCA8LSB1c2VyUmVnJGV2ZW50X3VzZXJJZAojIC0gc3FsIHF1ZXJ5CnNxbFF1ZXJ5IDwtIHBhc3RlKCdTRUxFQ1QgcmV2X3VzZXIsIHJldl9pZCwgcmV2X3BhZ2UsIHJldl90aW1lc3RhbXAsIHJldl9zaGExLCByZXZfY29udGVudF9tb2RlbCwgcmV2X2NvbnRlbnRfZm9ybWF0IEZST00gcmV2aXNpb24gV0hFUkUgcmV2X3VzZXIgSU4gKCcsCiAgICAgICAgICAgICAgICAgIHBhc3RlKHVpZCwgY29sbGFwc2UgPSAiLCAiKSwKICAgICAgICAgICAgICAgICAgJykgQU5EIChyZXZfdGltZXN0YW1wID49IDIwMTcxMDA0MjIwMDAwKSBBTkQgKHJldl90aW1lc3RhbXAgPD0gMjAxNzEwMTQyMjAwMDApOycsCiAgICAgICAgICAgICAgICAgIHNlcCA9ICIiKQpteVNxbENvbW1hbmQgPC0gcGFzdGUoJ215c3FsIC1oIGFuYWx5dGljcy1zdG9yZS5lcWlhZC53bW5ldCBkZXdpa2kgLWUgJywKICAgICAgICAgICAgICAgICAgICAgIHBhc3RlKCciJywgc3FsUXVlcnksICciID4gJywgc2VwID0gIiIpLAogICAgICAgICAgICAgICAgICAgICAgJy9ob21lL2dvcmFuc20vX21pc2NXTURFL2FiYzIwMTdfRGF0YU9VVC9hYmMyMDE3X09mZmljaWFsRGF0YXNldHMvYWJjMjAxN19EYWlseVVwZGF0ZS9hYmMyMDE3X2NvbXBsZXRlVXNlclJldmlzaW9ucy50c3YnLCBzZXAgPSAiIikKc3lzdGVtKGNvbW1hbmQgPSBteVNxbENvbW1hbmQsIAogICAgICAgd2FpdCA9IFRSVUUpCmBgYAoKQW5hbHlzZSByZXZlcnRlZCBlZGl0cyBsb2NhbGx5OgoKYGBge3IgZWNobyA9IFQsIHdhcm5pbmcgPSAnaGlkZScsIG1lc3NhZ2UgPSBGLCBldmFsID0gVH0KdXNlclJldmlzaW9ucyA8LSByZWFkLnRhYmxlKCcuL19kYWlseVVwZGF0ZURBVEEvYWJjMjAxN19jb21wbGV0ZVVzZXJSZXZpc2lvbnMudHN2JywKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHF1b3RlID0gIiIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZXAgPSAiXHQiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgaGVhZGVyID0gVCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNoZWNrLm5hbWVzID0gRiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGKQp1c2VyUmV2aXNpb25zIDwtIGxlZnRfam9pbih1c2VyUmV2aXNpb25zLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgdXNlclJlZywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGJ5ID0gYygicmV2X3VzZXIiID0gImV2ZW50X3VzZXJJZCIpKQp1c2VyUmV2aXNpb25zIDwtIHVzZXJSZXZpc2lvbnMgJT4lIAogIGZpbHRlcighaXMubmEoZXZlbnRfY2FtcGFpZ24pKQojIC0ga2VlcCBvbmx5IHRob3NlIHVzZXJzIHdobyBtYWRlIGFueSBlZGl0cyBhdCBhbGw6CnVzZXJSZXZpc2lvbnMgPC0gdXNlclJldmlzaW9ucyAlPiUgCiAgZmlsdGVyKHJldl91c2VyICVpbiUgZWRpdERhdGEkcmV2X3VzZXIpCiMgLSBOb3RlOiBVVEMgdGltZXMsIGNvbnZlcnNpb24gdG8gQ0VUIGlzIG5vdCBuZWNlc3NhcnkgaGVyZQp1c2VyUmV2aXNpb25zJHJldl90aW1lc3RhbXAgPC0gYXMuY2hhcmFjdGVyKHVzZXJSZXZpc2lvbnMkcmV2X3RpbWVzdGFtcCkKcmV2ZXJ0c1BlclVzZXIgPC0gbGFwcGx5KHVuaXF1ZSh1c2VyUmV2aXNpb25zJHJldl9pZCksIGZ1bmN0aW9uKHgpIHsKICBkYXRhc2V0IDwtIGRwbHlyOjphcnJhbmdlKHVzZXJSZXZpc2lvbnNbdXNlclJldmlzaW9ucyRyZXZfdXNlciA9PSB4LCBdLCByZXZfdGltZXN0YW1wKQogIHJldHVybihkYXRhLmZyYW1lKHVzZXJJZCA9IHgsIAogICAgICAgICAgICAgICAgICAgIHJldkNvdW50ID0gc3VtKHRhYmxlKGRhdGFzZXQkcmV2X3NoYTEpIC0gMSksIAogICAgICAgICAgICAgICAgICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGKSkKfSkKcmV2ZXJ0c1BlclVzZXIgPC0gcmJpbmRsaXN0KHJldmVydHNQZXJVc2VyKQpzdW0ocmV2ZXJ0c1BlclVzZXIkcmV2Q291bnQpCmBgYAoKSW4gY29uY2x1c2lvbiwgbm8gZWRpdHMgd2VyZSByZXZlcnRlZC4K