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. 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)

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.

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())

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"
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())

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. 2. 1 Specific Task 1 (BT3 Campaign): Multimedia

Note: we’ve looked for the usage of the [[Datei:]] syntax in user’s revisions (taking only the text of the difference to the previous page revisions, of course).

### -- read all revisions:
allRevs <- read.delim(paste(getwd(), 
                            '/_dailyUpdateDATA/abc2017_completeUserRevisions.tsv',
                            sep = ''),
                      sep = '\t',
                      header = T,
                      check.names = F,
                      stringsAsFactors = F)
allRevs$campaign = sapply(allRevs$rev_user, function(x) {
  userReg$event_campaign[which(userReg$event_userId %in% x)]
})
allRevs <- filter(allRevs, 
                  campaign == 'wmde_abc2017_bt1')
allRevs$countFile <- numeric(dim(allRevs)[1])                      
# - access the API for each user, collect all user revisions:
apiURL <- "https://de.wikipedia.org/w/api.php?action=query&format=json&prop=revisions&rvprop=content&revids="
apiURL <- "https://de.wikipedia.org/w/api.php?action=compare&format=json&torelative=prev&fromrev="
for (i in 1:dim(allRevs)[1]) {
  query = paste(apiURL,
                allRevs$rev_id[i],
                sep = "")
  revisions <- getURL(URLencode(query))
  # - REGEX HERE:
  allRevs$countFile <- str_count(revisions, fixed("[Datei:"))
}
print(paste('The percent of revisions that have used a file from Commons is: ',
            round(sum(allRevs$countFile>0)/length(allRevs$countFile)*100, 2),
            "%",
            sep = ""))
[1] "The percent of revisions that have used a file from Commons is: 0%"

6. 2. 2 Specific Task 2 (BT2 Campaign): Kategorie:Wikipedia:Ãœberarbeiten

### --- Script: abc2017_PROD_RevisionContent2.R
### --- the following runs on stat1005.eqiad.wmnet
### --- Rscript /home/goransm/RScripts/abc2017/abc2017_PROD_RevisionContent2.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

### --- Collect data from Link tables on all revisions made 
### --- by abc2017 registered users to find out whether
### --- the user edits where in line with the campaign
### --- specific tasks

# - sql query
sqlQuery <- paste('SELECT revision.rev_user, revision.rev_id, revision.rev_page, revision.rev_timestamp, categorylinks.cl_from, categorylinks.cl_to 
                   FROM 
                    revision 
                      LEFT JOIN 
                    categorylinks ON revision.rev_page = categorylinks.cl_from
                   WHERE revision.rev_user IN (', paste(uid, collapse = ", ", sep = ""), ') 
                   AND revision.rev_timestamp >= 20171004220000 
                   AND revision.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_userContentRevisions2.tsv', sep = "")
system(command = mySqlCommand, 
       wait = TRUE)

Analyse:

contentRevisions <- read.table('./_dailyUpdateDATA/abc2017_userContentRevisions2.tsv',
                            quote = "",
                            sep = "\t",
                            header = T,
                            check.names = F,
                            stringsAsFactors = F)
# - keep only BT2 registrations:
bt2Reg <- userReg$event_userId[which(userReg$event_campaign %in% 'wmde_abc2017_bt2')]
contentRevisions$rev_timestamp <- as.character(contentRevisions$rev_timestamp)
contentRevisions <- contentRevisions %>% 
  filter(rev_user %in% bt2Reg)
pagesEdited <- length(unique(contentRevisions$rev_page))
pagesEditedInCategory <- contentRevisions %>% 
  group_by(rev_page, rev_timestamp) %>% 
  summarise(edit = paste(cl_to, collapse = ", ", sep = ""))
targetEdits <- sum(grepl('Wikipedia:Ãœberarbeiten', pagesEditedInCategory$edit, fixed = T))
print(paste(
  round(targetEdits/length(pagesEditedInCategory$edit)*100, 2),
  "% of edits made by BT2 registered users were made on the pages in the Wikipedia:Ãœberarbeiten category.", 
  sep = ""
  ))
[1] "9.68% of edits made by BT2 registered users were made on the pages in the Wikipedia:Ãœberarbeiten category."

6. 2. 3 Specific Task 3 (BT3 Campaign): Citations

Note: we’ve looked for the usage of citations in user’s revisions (taking only the text of the difference to the previous page revisions, of course) by inspecting whether the tag was used.

### -- read all revisions:
allRevs <- read.delim(paste(getwd(), 
                            '/_dailyUpdateDATA/abc2017_completeUserRevisions.tsv',
                            sep = ''),
                      sep = '\t',
                      header = T,
                      check.names = F,
                      stringsAsFactors = F)
allRevs$campaign = sapply(allRevs$rev_user, function(x) {
  userReg$event_campaign[which(userReg$event_userId %in% x)]
})
allRevs <- filter(allRevs, 
                  campaign == 'wmde_abc2017_bt3')
allRevs$countRefTags <- numeric(dim(allRevs)[1])                      
# - access the API for each user, collect all user revisions:
apiURL <- "https://de.wikipedia.org/w/api.php?action=query&format=json&prop=revisions&rvprop=content&revids="
apiURL <- "https://de.wikipedia.org/w/api.php?action=compare&format=json&torelative=prev&fromrev="
for (i in 1:dim(allRevs)[1]) {
  query = paste(apiURL,
                allRevs$rev_id[i],
                sep = "")
  revisions <- getURL(URLencode(query))
  allRevs$countRefTags <- str_count(revisions, fixed("<ref>"))
}
print(paste('The percent of revisions that have used the ref tag is: ',
            round(sum(allRevs$countRefTags>0)/length(allRevs$countRefTags)*100, 2),
            "%",
            sep = ""))
[1] "The percent of revisions that have used the ref tag is: 0%"

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_user), 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)
print(paste(sum(revertsPerUser$revCount), " edits were reverted.", sep = ""))
[1] "4 edits were reverted."

6. 4 Exiting the Guided Tour vs Registration Campaign

The percent of registered users who did and did not complete the Guided Tour, per banner campaign:

uRGT <- userRegGT %>% 
  group_by(event_campaign) %>% 
  summarise(Complete = sum(is.na(event_tour)), `Incomplete` = sum(!is.na(event_tour)))
uRGT$event_campaign <- toupper(gsub("wmde_abc2017_", "", uRGT$event_campaign, fixed = T))
uRGT$`% Complete` = round(uRGT$Complete/(uRGT$Complete + uRGT$Incomplete)*100, 2)
uRGT$`% Incomplete` = round(uRGT$Incomplete/(uRGT$Complete + uRGT$Incomplete)*100, 2)
uRGT$Complete <- NULL
uRGT$Incomplete <- NULL
colnames(uRGT)[1] <- 'Campaign'
knitr::kable(uRGT, format = "html") %>% 
  kable_styling(full_width = F, position = "left")
Campaign % Complete % Incomplete
BT1 52.76 47.24
BT2 63.83 36.17
BT3 68.25 31.75
GIB_LP 58.72 41.28
GIB_RG 67.15 32.85

6. 5 Point of Guided Tour Exit per Registration Campaign

The number of users exiting the Guided Tour at a particular step, per banner campaign.

gtexitPlot  <- userRegGT %>% 
  filter(!is.na(event_tour)) %>% 
  dplyr::select(event_campaign, event_step) %>% 
  group_by(event_campaign, event_step) %>% 
  summarise(Count = n())
colnames(gtexitPlot) <- c('Campaign', 'Exit Point', 'Count')
gtexitPlot$Campaign <- toupper(gsub("wmde_abc2017_", "", gtexitPlot$Campaign, fixed = T))
ggplot(gtexitPlot, aes(x = '', y = Count,
                      fill = `Exit Point`,
                      color = `Exit Point`,
                      group = `Exit Point`,
                      label = Count)) + 
  geom_bar(position = "dodge", 
           stat = "identity", 
           width = 1, 
           color = "black") + 
  facet_wrap(~ Campaign) +
  ggtitle('Autumn Banner Campaign 2017: \nPoint of Guided Tour Exit per Registration Campaign') + 
  xlab("") + ylab("Num. Users") +
  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()) 

LS0tCnRpdGxlOiAnQXV0dW1uIEJhbm5lciBDYW1wYWlnbiAyMDE3OiBSZXBvcnQnCmF1dGhvcjogIkdvcmFuIFMuIE1pbG92YW5vdmljLCBEYXRhIEFuYWx5c3QsIFdNREUiCmRhdGU6ICJPY3RvYmVyLCAyMDE3IgpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazoKICAgIGNvZGVfZm9sZGluZzogaGlkZQogICAgdGhlbWU6IHNpbXBsZXgKICAgIHRvYzogeWVzCiAgICB0b2NfZmxvYXQ6IHllcwogICAgdG9jX2RlcHRoOiA1CiAgaHRtbF9kb2N1bWVudDoKICAgIHRvYzogeWVzCiAgICB0b2NfZGVwdGg6IDUKLS0tCgoKKipGZWVkYmFjayoqIHNob3VsZCBiZSBzZW5kIHRvIGBnb3Jhbi5taWxvdmFub3ZpY19leHRAd2lraW1lZGlhLmRlYC4gCgpUaGUgY2FtcGFpZ24gaXMgcnVuIGZyb20gMjAxNy8xMC8wNSB0byAyMDE3LzEwLzEzLgoKKipDVVJSRU5UIFVQREFURToqKiBDb21wbGV0ZSBkYXRhc2V0LCBjb2xsZWN0ZWQgb24gMjAxNy8xMC8xNC4KCmBgYHtyLCBlY2hvID0gRiwgd2FybmluZyA9IEYsIG1lc3NhZ2UgPSBGLCByZXN1bHRzID0gJ2hpZGUnfQojICFkaWFnbm9zdGljcyBvZmYKIyMjIC0tLSBTZXR1cAprbml0cjo6b3B0c19jaHVuayRzZXQoZmlnLndpZHRoID0gMTUsIGZpZy5oZWlnaHQgPSA4KSAKcm0obGlzdCA9IGxzKCkpCmxpYnJhcnkoc3RyaW5ncikKbGlicmFyeShkcGx5cikKbGlicmFyeSh0aWR5cikKbGlicmFyeShkYXRhLnRhYmxlKQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkoZ2dyZXBlbCkKbGlicmFyeShzY2FsZXMpCmxpYnJhcnkoUkNvbG9yQnJld2VyKQpsaWJyYXJ5KGthYmxlRXh0cmEpCmxpYnJhcnkocm1hcmtkb3duKQpsaWJyYXJ5KGtuaXRyKQpsaWJyYXJ5KERUKQpsaWJyYXJ5KHJlc2hhcGUyKQpsaWJyYXJ5KENoYW5uZWxBdHRyaWJ1dGlvbikKbGlicmFyeShpZ3JhcGgpCmxpYnJhcnkoem9vKQpsaWJyYXJ5KENhdXNhbEltcGFjdCkKbGlicmFyeShSQ3VybCkKbGlicmFyeShqc29ubGl0ZSkKYGBgCgojIyAwLiBEYXRhIEFjcXVpc2l0b24KCioqTk9URToqKiB0aGUgRGF0YSBBY3F1aXNpdGlvbiBjb2RlIGNodW5rIGlzIG5vdCBmdWxseSByZXByb2R1Y2libGUgZnJvbSB0aGlzIFJlcG9ydC4gVGhlIGRhdGEgYXJlIGNvbGxlY3RlZCBieSBydW5uaW5nIHRoZSBzY3JpcHQgYGFiYzIwMTdfUFJPRF9PdmVyYWxsRGFpbHlVcGRhdGUuUmAgb24gc3RhdDEwMDUuZXFpYWQud21uZXQsIGNvbGxlY3RpbmcgdGhlIGRhdGEgYXMgYC50c3ZgIGFuZCBgLmNzdmAgZmlsZXMsIGNvcHlpbmcgbWFudWFsbHksIGFuZCBwcm9jZXNzaW5nIGxvY2FsbHkuIFJ1biBmcm9tIHN0YXQxMDA1IHN0YXQgYm94IGJ5IGV4ZWN1dGluZyBgUnNjcmlwdCAvaG9tZS9nb3JhbnNtL1JTY3JpcHRzL2FiYzIwMTcvYWJjMjAxN19QUk9EX092ZXJhbGxEYWlseVVwZGF0ZS5SYC4KCmBgYHtyLCBlY2hvID0gVCwgZXZhbCA9IEZ9CiMjIyAtLS0gU2NyaXB0OiBhYmMyMDE3X1BST0RfT3ZlcmFsbERhaWx5VXBkYXRlLlIKIyMjIC0tLSB0aGUgZm9sbG93aW5nIHJ1bnMgb24gc3RhdDEwMDUuZXFpYWQud21uZXQKIyMjIC0tLSBSc2NyaXB0IC9ob21lL2dvcmFuc20vUlNjcmlwdHMvYWJjMjAxNy9hYmMyMDE3X1BST0RfT3ZlcmFsbERhaWx5VXBkYXRlLlIKCiMjIyAtLS0gVGhlIHNjcmlwdCBjb2xsZWN0cyBhbmQgd3JhbmdsZXMgYWxsIGRhdGFzZXRzCiMjIyAtLS0gZm9yIHRoZSBXTURFIEF1dHVtbiBCYW5uZXIgQ2FtcGFpZ24gMjAxNy4KCiMjIyAtLS0gR29yYW4gUy4gTWlsb3Zhbm92aWMsIERhdGEgQW5hbHlzdCwgV01ERQojIyMgLS0tIFNlcHRlbWJlciAyNiwgMjAxNy4KCiMjIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQojIyMgMC4gU2V0dXAKIyMjIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCnJtKGxpc3QgPSBscygpKQpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KHRpZHlyKQpsaWJyYXJ5KHN0cmluZ3IpCmxpYnJhcnkoZGF0YS50YWJsZSkKc3RhcnREYXRlIDwtICcyMDE3LTEwLTA1JwplbmREYXRlIDwtICcyMDE3LTEwLTE0JwpiYW5uZXJJbXByZXNzaW9uc0RpciA8LSAnL2hvbWUvZ29yYW5zbS9fbWlzY1dNREUvYWJjMjAxN19EYXRhT1VUL2FiYzIwMTdfT2ZmaWNpYWxEYXRhc2V0cy9hYmMyMDE3QmFubmVySW1wcmVzc2lvbnMvJwpiYW5uZXJDbGlja3NEaXIgPC0gJy9ob21lL2dvcmFuc20vX21pc2NXTURFL2FiYzIwMTdfRGF0YU9VVC9hYmMyMDE3X09mZmljaWFsRGF0YXNldHMvYWJjMjAxN0Jhbm5lckNsaWNrc0xhbmRpbmdQYWdlcy8nCmRhaWx5VXBkYXRlRGlyIDwtICcvaG9tZS9nb3JhbnNtL19taXNjV01ERS9hYmMyMDE3X0RhdGFPVVQvYWJjMjAxN19PZmZpY2lhbERhdGFzZXRzL2FiYzIwMTdfRGFpbHlVcGRhdGUvJyAKCiMjIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQojIyMgMS4gQmFubmVyIEltcHJlc3Npb25zCiMjIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQoKIyMjIC0tLSBDYW1wYWlnbiBCYW5uZXIgVGFnczoKIyAtICgxKSA/Y2FtcGFpZ249d21kZV9hYmMyMDE3X2J0MSAtIGJhbm5lciBmb3IgU3BlY2lmaWMgVGFzayAxOwojIC0gKDIpID9jYW1wYWlnbj13bWRlX2FiYzIwMTdfYnQyIC0gYmFubmVyIGZvciBTcGVjaWZpYyBUYXNrIDI7CiMgLSAoMykgP2NhbXBhaWduPXdtZGVfYWJjMjAxN19idDMgLSBiYW5uZXIgZm9yIFNwZWNpZmljIFRhc2sgMzsKIyAtICg0KSA/Y2FtcGFpZ249d21kZV9hYmMyMDE3X2dpYl9scCAtIGJhbm5lciBmb3IgdGhlIEdlbmVyYWwgSW52aXRhdGlvbgojIC0gd2hpY2ggbGVhZHMgdG8gdGhlIExhbmRpbmcgUGFnZSB1cG9uIGNsaWNrOwojIC0gKDUpID9jYW1wYWlnbj13bWRlX2FiYzIwMTdfZ2liX3JnIC0gYmFubmVyIGZvciB0aGUgR2VuZXJhbCBJbnZpdGF0aW9uCiMgd2hpY2ggbGVhZHMgZGlyZWN0bHkgdG8gUmVnaXN0cmF0aW9uIHVwb24gY2xpY2suCgojIyMgLS0tIEhpdmVRTCBmb3IgZXZlcnl0aGluZyBmcm9tCiMjIyAtLS0gdXJpX2hvc3QgPSAnZGUud2lraXBlZGlhLm9yZycgYW5kCiMjIyAtLS0gdXJpX3BhdGggPSAnL2JlYWNvbi9pbXByZXNzaW9uJwojIyMgLS0tIGFuZCB0aGVuIGxvb2sgdXAgdGhlIGRlc2lyZWQgdGFncy4KCiMjIyAtLS0gbG9vcCBvdmVyIGRhdGUgcmFuZ2UsIGNyZWF0ZSBxdWVyeSwgZmV0Y2gsIGFuZCBzdG9yZQoKZGF0ZVJhbmdlIDwtIHNlcS5QT1NJWHQoZnJvbSA9IGFzLlBPU0lYbHQoc3RhcnREYXRlLCB0eiA9ICJDRVQiKSwKICAgICAgICAgICAgICAgICAgICAgICAgdG8gPSBhcy5QT1NJWGx0KGVuZERhdGUsIHR6ID0gIkNFVCIpLAogICAgICAgICAgICAgICAgICAgICAgICBieSA9ICdob3VyJykKZGF0ZVJhbmdlIDwtIGRhdGVSYW5nZVstbGVuZ3RoKGRhdGVSYW5nZSldCmNldERhdGVSYW5nZSA8LSBhcy5jaGFyYWN0ZXIoZGF0ZVJhbmdlKQpjZXREYXRlUmFuZ2UgPC0gc2FwcGx5KGNldERhdGVSYW5nZSwgZnVuY3Rpb24oeCkgewogIHN0cnNwbGl0KHgsIHNwbGl0ID0gIiAiLCBmaXhlZCA9IFQpW1sxXV1bMV0KfSkKbmFtZXMoZGF0ZVJhbmdlKSA8LSBjZXREYXRlUmFuZ2UKZGF0ZVJhbmdlIDwtIGFzLlBPU0lYbHQoZGF0ZVJhbmdlLCB0eiA9ICJVVEMiKQojIC0gdXAgdG8gdG9kYXk6CnRvZGF5IDwtIGFzLlBPU0lYbHQoU3lzLnRpbWUoKSwgdHogPSAiVVRDIikKdyA8LSB3aGljaChkYXRlUmFuZ2UgPiB0b2RheSkKaWYgKGxlbmd0aCh3KSA+IDApIHsKICBkYXRlUmFuZ2UgPC0gZGF0ZVJhbmdlWy13XQp9CmRSIDwtIGxpc3QoKQpmb3IgKGkgaW4gMTpsZW5ndGgoZGF0ZVJhbmdlKSkgewogIGRSW1tpXV0gPC0gZGF0YS5mcmFtZSgKICAgIGNldE5hbWUgPSBuYW1lcyhkYXRlUmFuZ2VbaV0pLAogICAgdXRjWWVhciA9IHllYXIoZGF0ZVJhbmdlW2ldKSwKICAgIHV0Y01vbnRoID0gbW9udGgoZGF0ZVJhbmdlW2ldKSwKICAgIHV0Y0RheSA9IG1kYXkoZGF0ZVJhbmdlW2ldKSwKICAgIHV0Y0hvdXIgPSBob3VyKGRhdGVSYW5nZVtpXSkKICApCn0KZFIgPC0gcmJpbmRsaXN0KGRSKQpkUiA8LSBkUiAlPiUKICBncm91cF9ieShjZXROYW1lLCB1dGNZZWFyLCB1dGNNb250aCwgdXRjRGF5KSAlPiUKICBzdW1tYXJpc2UodXRjSG91ciA9IHBhc3RlKCJob3VyPSIsIHV0Y0hvdXIsIGNvbGxhcHNlID0gIiBPUiAiLCBzZXAgPSAiIikpCgojIC0gc2V0IG91dERpcgpvdXREaXIgPC0gYmFubmVySW1wcmVzc2lvbnNEaXIKc2V0d2Qob3V0RGlyKQojIC0gc2V0IEhpdmVRTCBxdWVyeSBkaXI6CmZvciAoaSBpbiAxOmxlbmd0aCh1bmlxdWUoZFIkY2V0TmFtZSkpKSB7CgogIHdDZXROYW1lIDwtIHdoaWNoKGRSJGNldE5hbWUgJWluJSB1bmlxdWUoZFIkY2V0TmFtZSlbaV0pCgogIGZvciAoaiBpbiAxOmxlbmd0aCh3Q2V0TmFtZSkpIHsKCiAgICAjIC0gY29uc3RydWN0IEhpdmVRTCBxdWVyeToKICAgIHkgPC0gZFIkdXRjWWVhclt3Q2V0TmFtZVtqXV0KICAgIG0gPC0gZFIkdXRjTW9udGhbd0NldE5hbWVbal1dCiAgICBkIDwtIGRSJHV0Y0RheVt3Q2V0TmFtZVtqXV0KICAgIGhvdXIgPC0gZFIkdXRjSG91clt3Q2V0TmFtZVtqXV0KICAgIHEgPC0gcGFzdGUoCiAgICAgICJVU0Ugd21mOwogICAgICBTRUxFQ1QgdXJpX3F1ZXJ5IEZST00gd2VicmVxdWVzdAogICAgICBXSEVSRSB1cmlfaG9zdCA9ICdkZS53aWtpcGVkaWEub3JnJwogICAgICBBTkQgdXJpX3BhdGggPSAnL2JlYWNvbi9pbXByZXNzaW9uJwogICAgICBBTkQgeWVhciA9ICIsIHksCiAgICAgICIgQU5EIG1vbnRoID0gIiwgbSwKICAgICAgIiBBTkQgZGF5ID0gIiwgZCwKICAgICAgIiBBTkQgKCIsIGhvdXIsICIpOyIsCiAgICAgIHNlcCA9ICIiKQogICAgIyAtIHdyaXRlIGhxbAogICAgd3JpdGUocSwgJ2FiYzIwMTdfQmFubmVySW1wcmVzc2lvbnMuaHFsJykKICAgICMgLSBwcmVwYXJlIG91dHB1dCBmaWxlOgogICAgZmlsZU5hbWUgPC0gImFiYzIwMTdfQmFubmVySW1wcmVzc2lvbnNfIgogICAgZmlsZU5hbWUgPC0gcGFzdGUwKGZpbGVOYW1lLAogICAgICAgICAgICAgICAgICAgICAgIGFzLmNoYXJhY3Rlcih1bmlxdWUoZFIkY2V0TmFtZSlbaV0pLAogICAgICAgICAgICAgICAgICAgICAgICJfIiwgaiwKICAgICAgICAgICAgICAgICAgICAgICAiLnRzdiIpCiAgICBmaWxlTmFtZSA8LSBwYXN0ZTAob3V0RGlyLCBmaWxlTmFtZSkKICAgICMgLSBleGVjdXRlIGhxbCBzY3JpcHQ6CiAgICBoaXZlQXJncyA8LQogICAgICAnYmVlbGluZSAtZicKICAgIGhpdmVJbnB1dCA8LSBwYXN0ZTAoJ2FiYzIwMTdfQmFubmVySW1wcmVzc2lvbnMuaHFsID4gJywKICAgICAgICAgICAgICAgICAgICAgICAgZmlsZU5hbWUpCiAgICAjIC0gY29tbWFuZDoKICAgIGhpdmVDb21tYW5kIDwtIHBhc3RlKGhpdmVBcmdzLCBoaXZlSW5wdXQpCiAgICBzeXN0ZW0oY29tbWFuZCA9IGhpdmVDb21tYW5kLCB3YWl0ID0gVFJVRSkKCiAgfQoKfQoKIyMjIC0tLSB3cmFuZ2xlIHRoaXMgZGF0YVNldApsRiA8LSBsaXN0LmZpbGVzKCkKbEYgPC0gbEZbZ3JlcGwoIi50c3YiLCBsRiwgZml4ZWQgPSBUKV0KbEYgPC0gbEZbZ3JlcGwoIkltcHJlc3Npb25zIiwgbEYsIGZpeGVkID0gVCldCiMjIyAtLS0gbG9hZCBEYXRhc2V0OgojIC0gY291bnQgbm9uLWVtcHR5IGZpbGVzOgpjIDwtIDAKZGF0YVNldCA8LSBsaXN0KCkKZm9yIChpIGluIDE6bGVuZ3RoKGxGKSkgewogIGRTIDwtIHJlYWRMaW5lcyhsRltpXSwgbiA9IC0xKQogIGRTIDwtIGRTWzg6KGxlbmd0aChkUykgLSAxKV0KICBpZiAobGVuZ3RoKGRTKSA+IDApIHsKICAgIGMgPC0gYyArIDEKICAgIGRTIDwtIGRhdGEuZnJhbWUocXVlcnkgPSBkUywKICAgICAgICAgICAgICAgICAgICAgZGF0ZSA9IHN0cnNwbGl0KGxGW2ldLCBzcGxpdCA9ICJfIiwgZml4ZWQgPSBUKVtbMV1dWzNdLAogICAgICAgICAgICAgICAgICAgICBzdHJpbmdzQXNGYWN0b3JzID0gRikKICAgIGRhdGFTZXRbW2NdXSA8LSBkUwogICAgcm0oZFMpOyBnYygpCiAgfQp9CmRhdGFTZXQgPC0gcmJpbmRsaXN0KGRhdGFTZXQpCmRhdGFTZXQgPC0gZmlsdGVyKGRhdGFTZXQsCiAgICAgICAgICAgICAgICAgIGdyZXBsKCJXTURFX2VkaXRvcl9jYW1wYWlnbl9hdXR1bW4xNyIsCiAgICAgICAgICAgICAgICAgICAgICAgIHF1ZXJ5KQopCgojIC0gcHJvZHVjZSBhbmFseXRpY3MgZGF0YXNldApiYW5uZXIgPC0gc3RyX2V4dHJhY3QoZGF0YVNldCRxdWVyeSwgImJhbm5lcj0oX3xbWzphbG51bTpdXSkrJiIpCmJhbm5lciA8LSBnc3ViKCJiYW5uZXI9IiwgIiIsIGJhbm5lciwgZml4ZWQgPSBUKQpiYW5uZXIgPC0gZ3N1YigiJiIsICIiLCBiYW5uZXIsIGZpeGVkID0gVCkKaW1wcmVzc2lvblJhdGUgPC0gc3RyX2V4dHJhY3QoZGF0YVNldCRxdWVyeSwgInJlY29yZEltcHJlc3Npb25TYW1wbGVSYXRlPShbWzpkaWdpdDpdXXxcXC4pKyYiKQppbXByZXNzaW9uUmF0ZSA8LSBnc3ViKCJyZWNvcmRJbXByZXNzaW9uU2FtcGxlUmF0ZT0iLCAiIiwgaW1wcmVzc2lvblJhdGUsIGZpeGVkID0gVCkKaW1wcmVzc2lvblJhdGUgPC0gZ3N1YigiJiIsICIiLCBpbXByZXNzaW9uUmF0ZSwgZml4ZWQgPSBUKQppbXByZXNzaW9uUmF0ZSA8LSBhcy5udW1lcmljKGltcHJlc3Npb25SYXRlKQpzdGF0dXMgPC0gc3RyX2V4dHJhY3QoZGF0YVNldCRxdWVyeSwgInN0YXR1cz0oW1s6YWxudW06XV18W1s6cHVuY3Q6XV0pKyYiKQpzdGF0dXMgPC0gZ3N1Yigic3RhdHVzPSIsICIiLCBzdGF0dXMpCnN0YXR1cyA8LSBnc3ViKCImIiwgIiIsIHN0YXR1cykKc3RhdHVzQ29kZSA8LSBzdHJfZXh0cmFjdChkYXRhU2V0JHF1ZXJ5LCAic3RhdHVzQ29kZT1bWzpkaWdpdDpdXSYiKQpzdGF0dXNDb2RlIDwtIGdzdWIoInN0YXR1c0NvZGU9IiwgIiIsIHN0YXR1c0NvZGUpCnN0YXR1c0NvZGUgPC0gZ3N1YigiJiIsICIiLCBzdGF0dXNDb2RlKQpjYW1wYWlnbkNhdGVnb3J5IDwtIHN0cl9leHRyYWN0KGRhdGFTZXQkcXVlcnksICJjYW1wYWlnbkNhdGVnb3J5PVtbOmFsbnVtOl1dKyYiKQpjYW1wYWlnbkNhdGVnb3J5IDwtIGdzdWIoImNhbXBhaWduQ2F0ZWdvcnk9IiwgIiIsIGNhbXBhaWduQ2F0ZWdvcnkpCmNhbXBhaWduQ2F0ZWdvcnkgPC0gZ3N1YigiJiIsICIiLCBjYW1wYWlnbkNhdGVnb3J5KQpyZXN1bHQgPC0gc3RyX2V4dHJhY3QoZGF0YVNldCRxdWVyeSwgInJlc3VsdD1bWzphbG51bTpdXSsiKQpyZXN1bHQgPC0gZ3N1YigicmVzdWx0PSIsICIiLCByZXN1bHQpCnJlc3VsdCA8LSBnc3ViKCImIiwgIiIsIHJlc3VsdCkKcWRhdGUgPC0gZGF0YVNldCRkYXRlCiMgLSBhcy5kYXRhLmZyYW1lKCkKZGF0YVNldCA8LSBkYXRhLmZyYW1lKGJhbm5lciA9IGJhbm5lciwKICAgICAgICAgICAgICAgICAgICAgIGltcHJlc3Npb25SYXRlID0gaW1wcmVzc2lvblJhdGUsCiAgICAgICAgICAgICAgICAgICAgICBzdGF0dXMgPSBzdGF0dXMsCiAgICAgICAgICAgICAgICAgICAgICBzdGF0dXNDb2RlID0gc3RhdHVzQ29kZSwKICAgICAgICAgICAgICAgICAgICAgIGNhbXBhaWduQ2F0ZWdvcnkgPSBjYW1wYWlnbkNhdGVnb3J5LAogICAgICAgICAgICAgICAgICAgICAgcmVzdWx0ID0gcmVzdWx0LAogICAgICAgICAgICAgICAgICAgICAgZGF0ZSA9IHFkYXRlLAogICAgICAgICAgICAgICAgICAgICAgc3RyaW5nc0FzRmFjdG9ycyA9IEYpCgojIC0gc3RvcmUgYW5hbHl0aWNzIGRhdGFzZXQ6CnNldHdkKGRhaWx5VXBkYXRlRGlyKQpkYXRhU2V0IDwtIGRhdGFTZXRbIWlzLm5hKGRhdGFTZXQkYmFubmVyKSwgXQp3cml0ZS5jc3YoZGF0YVNldCwgJ2FiY19CYW5uZXJJbXByZXNzaW9uc191cGRhdGUuY3N2JykKCiMjIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQojIyMgMi4gQmFubmVyIENsaWNrcyBhbmQgTGFuZGluZyBQYWdlIFZpZXdzCiMjIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQoKIyMjIC0tLSBMYW5kaW5nL1JlZ2lzdHJhdGlvbiBwYWdlczoKIyAtIExhbmRpbmcgUGFnZSwgU3BlY2lmaWMgVGFza3MsIEJhbm5lcnMgYnQxLCBidDIsIGJ0MwojIC0gaHR0cHM6Ly9kZS53aWtpcGVkaWEub3JnL3dpa2kvV2lraXBlZGlhOldpa2ltZWRpYV9EZXV0c2NobGFuZC9KZXR6dE1pdG1hY2hlbgojIC0gU3BlY2lmaWMgYnQgYmFubmVyIGFuY2hvcnM6CiMgLSAgYnQxIC0gI0JlYmlsZGVybiwgYnQyIC0gQWt0dWFsaXNpZXJlbiwgYnQzIC0gI0JlbGVnZW4KIyAtIExhbmRpbmcgUGFnZSwgR2VuZXJhbCwgQmFubmVyIGdpYl9scAojIC0gaHR0cHM6Ly9kZS53aWtpcGVkaWEub3JnL3dpa2kvV2lraXBlZGlhOldpa2ltZWRpYV9EZXV0c2NobGFuZC9NYWNoX21pdAojIC0gUmVnaXN0cmF0aW9uIFBhZ2UsIGJhbm5lciBnaWJfcmcKIyAtIGh0dHBzOi8vZGUud2lraXBlZGlhLm9yZy93aWtpL1NwZXppYWw6QmVudXR6ZXJrb250b19hbmxlZ2VuCgojIC0gc2V0IG91dERpcgpvdXREaXIgPC0gYmFubmVyQ2xpY2tzRGlyCnNldHdkKG91dERpcikKCmZvciAoaSBpbiAxOmxlbmd0aCh1bmlxdWUoZFIkY2V0TmFtZSkpKSB7CgogIHdDZXROYW1lIDwtIHdoaWNoKGRSJGNldE5hbWUgJWluJSB1bmlxdWUoZFIkY2V0TmFtZSlbaV0pCgogIGZvciAoaiBpbiAxOmxlbmd0aCh3Q2V0TmFtZSkpIHsKCiAgICAjIC0gY29uc3RydWN0IEhpdmVRTCBxdWVyeToKICAgIHkgPC0gZFIkdXRjWWVhclt3Q2V0TmFtZVtqXV0KICAgIG0gPC0gZFIkdXRjTW9udGhbd0NldE5hbWVbal1dCiAgICBkIDwtIGRSJHV0Y0RheVt3Q2V0TmFtZVtqXV0KICAgIGhvdXIgPC0gZFIkdXRjSG91clt3Q2V0TmFtZVtqXV0KICAgIHEgPC0gcGFzdGUoCiAgICAgICJVU0Ugd21mOwogICAgICBTRUxFQ1QgdXJpX3BhdGgsIHVyaV9xdWVyeSwgcmVmZXJlciBGUk9NIHdlYnJlcXVlc3QKICAgICAgV0hFUkUgdXJpX2hvc3QgPSAnZGUud2lraXBlZGlhLm9yZycKICAgICAgQU5EICh1cmlfcGF0aCA9ICcvd2lraS9XaWtpcGVkaWE6V2lraW1lZGlhX0RldXRzY2hsYW5kL0pldHp0TWl0bWFjaGVuJyBPUiB1cmlfcGF0aCA9ICcvd2lraS9XaWtpcGVkaWE6V2lraW1lZGlhX0RldXRzY2hsYW5kL01hY2hfbWl0JyBPUiB1cmlfcGF0aCA9ICcvd2lraS9TcGV6aWFsOkJlbnV0emVya29udG9fYW5sZWdlbicpCiAgICAgIEFORCB5ZWFyID0gIiwgeSwKICAgICAgIiBBTkQgbW9udGggPSAiLCBtLAogICAgICAiIEFORCBkYXkgPSAiLCBkLAogICAgICAiIEFORCAoIiwgaG91ciwgIik7IiwKICAgICAgc2VwID0gIiIpCiAgICAjIC0gd3JpdGUgaHFsCiAgICB3cml0ZShxLCAnYWJjMjAxN19CYW5uZXJDbGlja3MuaHFsJykKICAgICMgLSBwcmVwYXJlIG91dHB1dCBmaWxlOgogICAgZmlsZU5hbWUgPC0gImFiYzIwMTdfQmFubmVyQ2xpY2tzXyIKICAgIGZpbGVOYW1lIDwtIHBhc3RlMChmaWxlTmFtZSwKICAgICAgICAgICAgICAgICAgICAgICBhcy5jaGFyYWN0ZXIodW5pcXVlKGRSJGNldE5hbWUpW2ldKSwKICAgICAgICAgICAgICAgICAgICAgICAiXyIsIGosCiAgICAgICAgICAgICAgICAgICAgICAgIi50c3YiKQogICAgZmlsZU5hbWUgPC0gcGFzdGUwKG91dERpciwgZmlsZU5hbWUpCiAgICAjIC0gZXhlY3V0ZSBocWwgc2NyaXB0OgogICAgaGl2ZUFyZ3MgPC0KICAgICAgJ2JlZWxpbmUgLWYnCiAgICBoaXZlSW5wdXQgPC0gcGFzdGUwKCdhYmMyMDE3X0Jhbm5lckNsaWNrcy5ocWwgPiAnLAogICAgICAgICAgICAgICAgICAgICAgICBmaWxlTmFtZSkKICAgICMgLSBjb21tYW5kOgogICAgaGl2ZUNvbW1hbmQgPC0gcGFzdGUoaGl2ZUFyZ3MsIGhpdmVJbnB1dCkKICAgIHN5c3RlbShjb21tYW5kID0gaGl2ZUNvbW1hbmQsIHdhaXQgPSBUUlVFKQoKICB9Cgp9CgojIyMgLS0tIFdyYW5nbGUgdGhpcyBkYXRhc2V0OgoKIyMjIC0tLSBMYW5kaW5nIHBhZ2VzOgpzcGVjVGFza1BhZ2UgPC0gJy93aWtpL1dpa2lwZWRpYTpXaWtpbWVkaWFfRGV1dHNjaGxhbmQvSmV0enRNaXRtYWNoZW4nCmdlbkludlBhZ2UgPC0gJy93aWtpL1dpa2lwZWRpYTpXaWtpbWVkaWFfRGV1dHNjaGxhbmQvTWFjaF9taXQnCnJlZ1BhZ2UgPC0gJy93aWtpL1NwZXppYWw6QmVudXR6ZXJrb250b19hbmxlZ2VuJwoKIyMjIC0tLSBCYW5uZXIgdGFnczoKc3BlY1Rhc2tCYW5uZXIxIDwtICc/Y2FtcGFpZ249d21kZV9hYmMyMDE3X2J0MScKc3BlY1Rhc2tCYW5uZXIyIDwtICc/Y2FtcGFpZ249d21kZV9hYmMyMDE3X2J0MicKc3BlY1Rhc2tCYW5uZXIzIDwtICc/Y2FtcGFpZ249d21kZV9hYmMyMDE3X2J0MycKZ2VuSW52UGFnZV9yZyA8LSAnP2NhbXBhaWduPXdtZGVfYWJjMjAxN19naWJfcmcnCmdlbkludlBhZ2VfbHAgPC0gJz9jYW1wYWlnbj13bWRlX2FiYzIwMTdfZ2liX2xwJwoKIyMjIC0tLSBEYXRhc2V0OgojIC0gY291bnQgbm9uLWVtcHR5IGZpbGVzOgpjIDwtIDAKbEYgPC0gbGlzdC5maWxlcygpCmxGIDwtIGxGW2dyZXBsKCcudHN2JywgbEYsIGZpeGVkID0gVCldCmxGIDwtIGxGW2dyZXBsKCdDbGlja3MnLCBsRiwgZml4ZWQgPSBUKV0KZGF0YVNldCA8LSBsaXN0KCkKZm9yIChpIGluIDE6bGVuZ3RoKGxGKSkgewogIGRTIDwtIHJlYWRMaW5lcyhsRltpXSwgbiA9IC0xKQogIGRTIDwtIGRTWzg6KGxlbmd0aChkUykgLSAyKV0KICBpZiAobGVuZ3RoKGRTID4gMCkpIHsKICAgIGMgPC0gYyArIDEKICAgIGRTIDwtIGxhcHBseShkUywgZnVuY3Rpb24oeCkgewogICAgICBkYXQgPC0gc3Ryc3BsaXQoeCwgc3BsaXQgPSAiXHQiLCBmaXhlZCA9IFQpW1sxXV0KICAgICAgZGF0YS5mcmFtZShwYWdlID0gZGF0WzFdLCBiYW5uZXIgPSBkYXRbMl0sIHJlZmVyID0gZGF0WzNdLCBzdHJpbmdzQXNGYWN0b3JzID0gRikKICAgIH0pCiAgfQogIGRTIDwtIHJiaW5kbGlzdChkUykKICBkUyRkYXRlIDwtIHN0cnNwbGl0KGxGW2ldLCBzcGxpdCA9ICJfIiwgZml4ZWQgPSBUKVtbMV1dWzNdCiAgZGF0YVNldFtbY11dIDwtIGRTCiAgcm0oZFMpOyBnYygpCn0KZGF0YVNldCA8LSByYmluZGxpc3QoZGF0YVNldCkKCiMgLSByZXBsYWNlIHZhbHVlczoKZGF0YVNldCRwYWdlIDwtIHNhcHBseShkYXRhU2V0JHBhZ2UsIGZ1bmN0aW9uKHgpIHsKICBzdHJzcGxpdCh4LCBzcGxpdCA9ICIvIiwgZml4ZWQgPSBUKVtbMV1dW2xlbmd0aChzdHJzcGxpdCh4LCBzcGxpdCA9ICIvIiwgZml4ZWQgPSBUKVtbMV1dKV0KfSkKZGF0YVNldCRiYW5uZXJbd2hpY2goZGF0YVNldCRiYW5uZXIgJWluJSBzcGVjVGFza0Jhbm5lcjEpXSA8LSAnQlQxJwpkYXRhU2V0JGJhbm5lclt3aGljaChkYXRhU2V0JGJhbm5lciAlaW4lIHNwZWNUYXNrQmFubmVyMildIDwtICdCVDInCmRhdGFTZXQkYmFubmVyW3doaWNoKGRhdGFTZXQkYmFubmVyICVpbiUgc3BlY1Rhc2tCYW5uZXIzKV0gPC0gJ0JUMycKZGF0YVNldCRiYW5uZXJbd2hpY2goZGF0YVNldCRiYW5uZXIgJWluJSBnZW5JbnZQYWdlX3JnKV0gPC0gJ0dJUF9SRycKZGF0YVNldCRiYW5uZXJbd2hpY2goZGF0YVNldCRiYW5uZXIgJWluJSBnZW5JbnZQYWdlX2xwKV0gPC0gJ0dJUF9MUCcKZGF0YVNldCRiYW5uZXIgPC0gcGFzdGUoZGF0YVNldCRiYW5uZXIsICJfY2xpY2siLCBzZXAgPSAiIikKZGF0YVNldCRiYW5uZXJbd2hpY2goIShkYXRhU2V0JGJhbm5lciAlaW4lIGMoJ0JUMV9jbGljaycsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdCVDJfY2xpY2snLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnQlQzX2NsaWNrJywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ0dJUF9SR19jbGljaycsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdHSVBfTFBfY2xpY2snKSkpXSA8LSAnT3RoZXInCmNvbG5hbWVzKGRhdGFTZXQpIDwtIGMoJ1BhZ2UnLCAnU291cmNlJywgJ1JlZmVyZXInLCAnRGF0ZScpCgojIyMgLS0tIHN0b3JlIGFiY19CYW5uZXJDbGlja3NQYWdlVmlld3NfVXBkYXRlLmNzdgp3cml0ZS5jc3YoZGF0YVNldCwgZmlsZSA9ICJhYmNfQmFubmVyQ2xpY2tzUGFnZVZpZXdzX05vbi1SZWZpbmVkLmNzdiIpCgpkYXRhU2V0JFNvdXJjZVtkYXRhU2V0JFBhZ2UgJWluJSAnU3BlemlhbDpCZW51dHplcmtvbnRvX2FubGVnZW4nICYgZGF0YVNldCRTb3VyY2UgPT0gJ090aGVyJ10gPC0KICBzdHJfZXh0cmFjdChkYXRhU2V0JFJlZmVyZXJbZGF0YVNldCRQYWdlICVpbiUgJ1NwZXppYWw6QmVudXR6ZXJrb250b19hbmxlZ2VuJyAmIGRhdGFTZXQkU291cmNlID09ICdPdGhlciddLAogICAgICAgICAgICAgICJjYW1wYWlnbj13bWRlX2FiYyguKSskIikKZGF0YVNldCRTb3VyY2VbZGF0YVNldCRQYWdlICVpbiUgJ1NwZXppYWw6QmVudXR6ZXJrb250b19hbmxlZ2VuJyAmIGdyZXBsKCJ3bWRlX2FiYzIwMTdfYnQxIiwgZGF0YVNldCRTb3VyY2UpXSA8LSAiSmV0enRNaXRtYWNoZW5fQlQxIgpkYXRhU2V0JFNvdXJjZVtkYXRhU2V0JFBhZ2UgJWluJSAnU3BlemlhbDpCZW51dHplcmtvbnRvX2FubGVnZW4nICYgZ3JlcGwoIndtZGVfYWJjMjAxN19idDIiLCBkYXRhU2V0JFNvdXJjZSldIDwtICJKZXR6dE1pdG1hY2hlbl9CVDIiCmRhdGFTZXQkU291cmNlW2RhdGFTZXQkUGFnZSAlaW4lICdTcGV6aWFsOkJlbnV0emVya29udG9fYW5sZWdlbicgJiBncmVwbCgid21kZV9hYmMyMDE3X2J0MyIsIGRhdGFTZXQkU291cmNlKV0gPC0gIkpldHp0TWl0bWFjaGVuX0JUMyIKZGF0YVNldCRTb3VyY2VbZGF0YVNldCRQYWdlICVpbiUgJ1NwZXppYWw6QmVudXR6ZXJrb250b19hbmxlZ2VuJyAmIGdyZXBsKCJ3bWRlX2FiYzIwMTdfZ2liX3JnIiwgZGF0YVNldCRTb3VyY2UpXSA8LSAiR0lQX1JHX2NsaWNrIgpkYXRhU2V0JFNvdXJjZVtkYXRhU2V0JFBhZ2UgJWluJSAnU3BlemlhbDpCZW51dHplcmtvbnRvX2FubGVnZW4nICYgZ3JlcGwoIndtZGVfYWJjMjAxN19naWJfbHAiLCBkYXRhU2V0JFNvdXJjZSldIDwtICJNYWNoX21pdCIKZGF0YVNldCRTb3VyY2VbZGF0YVNldCRQYWdlICVpbiUgJ1NwZXppYWw6QmVudXR6ZXJrb250b19hbmxlZ2VuJyAmIGRhdGFTZXQkUmVmZXJlciAlaW4lICctJ10gPC0gIlVua25vd24iCmRhdGFTZXQkU291cmNlW2RhdGFTZXQkUGFnZSAlaW4lICdNYWNoX21pdCcgJiBkYXRhU2V0JFJlZmVyZXIgJWluJSAnLSddIDwtICJVbmtub3duIgpkYXRhU2V0JFNvdXJjZVtkYXRhU2V0JFBhZ2UgJWluJSAnSmV0enRNaXRtYWNoZW4nICYgZGF0YVNldCRSZWZlcmVyICVpbiUgJy0nXSA8LSAiVW5rbm93biIKZGF0YVNldCRTb3VyY2VbaXMubmEoZGF0YVNldCRTb3VyY2UpXSA8LSAnT3RoZXInCmRhdGFTZXQkUmVmZXJlciA8LSBOVUxMCgojIyMgLS0tIHN0b3JlIGFiY19CYW5uZXJDbGlja3NQYWdlVmlld3NfVXBkYXRlLmNzdgpzZXR3ZChkYWlseVVwZGF0ZURpcikKd3JpdGUuY3N2KGRhdGFTZXQsIGZpbGUgPSAiYWJjX0Jhbm5lckNsaWNrc1BhZ2VWaWV3c19VcGRhdGUuY3N2IikKCiMjIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQojIyMgMy4gVXNlciBSZWdpc3RyYXRpb24gRGF0YQojIyMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KCiMgLSBOT1RFOiBVVEMgdGltZXN0YW1wcyAtIGFkanVzdG1lbnQgZm9yIENFKFMpVCBpbnRyb2R1Y2VkLiAKIyAtIFNlcnZlclNpZGVBY2NvdW50Q3JlYXRpb25fNTQ4NzM0NQpxQ29tbWFuZCA8LSAibXlzcWwgLS1kZWZhdWx0cy1maWxlPS9ldGMvbXlzcWwvY29uZi5kL2FuYWx5dGljcy1yZXNlYXJjaC1jbGllbnQuY25mIC1oIGFuYWx5dGljcy1zdG9yZS5lcWlhZC53bW5ldCAtQSAtZSBcInNlbGVjdCAqIGZyb20gbG9nLlNlcnZlclNpZGVBY2NvdW50Q3JlYXRpb25fNTQ4NzM0NSB3aGVyZSAoKHdlYkhvc3QgPSAnZGUud2lraXBlZGlhLm9yZycpIGFuZCAodGltZXN0YW1wID49IDIwMTcxMDA0MjIwMDAwKSk7XCIgPiAvaG9tZS9nb3JhbnNtL19taXNjV01ERS9hYmMyMDE3X0RhdGFPVVQvYWJjMjAxN19PZmZpY2lhbERhdGFzZXRzL2FiYzIwMTdfRGFpbHlVcGRhdGUvYWJjMjAxN191c2VyUmVnaXN0cmF0aW9ucy50c3YiCnN5c3RlbShjb21tYW5kID0gcUNvbW1hbmQsIHdhaXQgPSBUUlVFKQoKIyMjIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCiMjIyA0LiBHdWlkZWQgVG91ciBEYXRhCiMjIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQoKIyAtIE5PVEU6IFVUQyB0aW1lc3RhbXBzIC0gYWRqdXN0bWVudCBmb3IgQ0UoUylUIGludHJvZHVjZWQuIAoKIyAtIFNlcnZlclNpZGVBY2NvdW50Q3JlYXRpb25fNTQ4NzM0NQpxQ29tbWFuZCA8LSAibXlzcWwgLS1kZWZhdWx0cy1maWxlPS9ldGMvbXlzcWwvY29uZi5kL2FuYWx5dGljcy1yZXNlYXJjaC1jbGllbnQuY25mIC1oIGFuYWx5dGljcy1zdG9yZS5lcWlhZC53bW5ldCAtQSAtZSBcInNlbGVjdCAqIGZyb20gbG9nLkd1aWRlZFRvdXJFeGl0ZWRfODY5MDU2NiB3aGVyZSAoKHdlYkhvc3QgPSAnZGUud2lraXBlZGlhLm9yZycpIGFuZCAodGltZXN0YW1wID49IDIwMTcxMDA0MjIwMDAwKSk7XCIgPiAvaG9tZS9nb3JhbnNtL19taXNjV01ERS9hYmMyMDE3X0RhdGFPVVQvYWJjMjAxN19PZmZpY2lhbERhdGFzZXRzL2FiYzIwMTdfRGFpbHlVcGRhdGUvYWJjMjAxN19ndWlkZWRUb3Vycy50c3YiCnN5c3RlbShjb21tYW5kID0gcUNvbW1hbmQsIHdhaXQgPSBUUlVFKQoKIyMjIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCiMjIyA1LiBVc2VyIEVkaXRzIERhdGEKIyMjIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCiMgLSBnZXQgdXNlciBJRHMgZnJvbSByZWdpc3RlcmVkOgpsRiA8LSBsaXN0LmZpbGVzKCkKbEYgPC0gbEZbZ3JlcGwoJ3VzZXJSZWdpc3RyYXRpb25zJywgbEYsIGZpeGVkID0gVCldCnVzZXJSZWcgPC0gcmVhZC50YWJsZShsRiwgCiAgICAgICAgICAgICAgICAgICAgICBxdW90ZSA9ICIiLAogICAgICAgICAgICAgICAgICAgICAgc2VwID0gIlx0IiwKICAgICAgICAgICAgICAgICAgICAgIGhlYWRlciA9IFQsCiAgICAgICAgICAgICAgICAgICAgICBjaGVjay5uYW1lcyA9IEYsCiAgICAgICAgICAgICAgICAgICAgICBzdHJpbmdzQXNGYWN0b3JzID0gRikKdXNlclJlZyA8LSB1c2VyUmVnICU+JSAKICBkcGx5cjo6c2VsZWN0KGV2ZW50X3VzZXJJZCwgZXZlbnRfaXNTZWxmTWFkZSkgJT4lIAogIGZpbHRlcihldmVudF9pc1NlbGZNYWRlID09IDEpCiMgLSB1aWRzOgp1aWQgPC0gdXNlclJlZyRldmVudF91c2VySWQKIyAtIHNxbCBxdWVyeQpzcWxRdWVyeSA8LSBwYXN0ZSgnU0VMRUNUIENPVU5UKCopIGFzIGVkaXRzLCByZXZfdXNlciBGUk9NIHJldmlzaW9uIFdIRVJFIHJldl91c2VyIElOICgnLAogICAgICAgICAgICAgICAgICBwYXN0ZSh1aWQsIGNvbGxhcHNlID0gIiwgIiksCiAgICAgICAgICAgICAgICAgICcpIEdST1VQIEJZIHJldl91c2VyOycsCiAgICAgICAgICAgICAgICAgIHNlcCA9ICIiKQpteVNxbENvbW1hbmQgPC0gcGFzdGUoJ215c3FsIC1oIGFuYWx5dGljcy1zdG9yZS5lcWlhZC53bW5ldCBkZXdpa2kgLWUgJywKICAgICAgICAgICAgICAgICAgICAgIHBhc3RlKCciJywgc3FsUXVlcnksICciID4gJywgc2VwID0gIiIpLAogICAgICAgICAgICAgICAgICAgICAgJy9ob21lL2dvcmFuc20vX21pc2NXTURFL2FiYzIwMTdfRGF0YU9VVC9hYmMyMDE3X09mZmljaWFsRGF0YXNldHMvYWJjMjAxN19EYWlseVVwZGF0ZS9hYmMyMDE3X3VzZXJFZGl0cy50c3YnLCBzZXAgPSAiIikKc3lzdGVtKGNvbW1hbmQgPSBteVNxbENvbW1hbmQsIAogICAgICAgd2FpdCA9IFRSVUUpCmBgYAoKIyMgMS4gQ2FtcGFpZ24gQmFubmVycyBhbmQgUGFnZXMKClRoaXMgc2VjdGlvbiBwcmVzZW50cyBhbGwgZGF0YSBhbmQgc3RhdGlzdGljcyBvbiB0aGUgY2FtcGFpZ24gYmFubmVycyBhbmQgcGFnZXMuCgojIyMgMS4xIEJhbm5lciBJbXByZXNzaW9ucwoKYGBge3IgZWNobyA9IFQsIGV2YWwgPSBUfQojIyMgLS0tIGV4dHJhY3Qgb25seSBjYW1wYWlnbiByZWxldmFudCBkYXRhCiMgLSBjYW1wYWlnbiBiYW5uZXJzOgpCVDEgPC0gJ1dNREVfZWRpdG9yX2NhbXBhaWduX2F1dHVtbjE3X2EnCkJUMiA8LSAnV01ERV9lZGl0b3JfY2FtcGFpZ25fYXV0dW1uMTdfYicKQlQzIDwtICdXTURFX2VkaXRvcl9jYW1wYWlnbl9hdXR1bW4xN19jJwpHSUJfUkcgPC0gJ1dNREVfZWRpdG9yX2NhbXBhaWduX2F1dHVtbjE3X2UnCkdJQl9MUCA8LSAnV01ERV9lZGl0b3JfY2FtcGFpZ25fYXV0dW1uMTdfZCcKCmRhdGFTZXQgPC0gcmVhZC5jc3YoJy4vX2RhaWx5VXBkYXRlREFUQS9hYmNfQmFubmVySW1wcmVzc2lvbnNfdXBkYXRlLmNzdicsCiAgICAgICAgICAgICAgICAgICAgaGVhZGVyID0gVCwKICAgICAgICAgICAgICAgICAgICByb3cubmFtZXMgPSAxLAogICAgICAgICAgICAgICAgICAgIGNoZWNrLm5hbWVzID0gRiwKICAgICAgICAgICAgICAgICAgICBzdHJpbmdzQXNGYWN0b3JzID0gRikKIyAtIHJlY29kZToKZGF0YVNldCRiYW5uZXIgPC0gcmVjb2RlKGRhdGFTZXQkYmFubmVyLAogICAgICAgICAgICAgICAgICAgICAgICAgJ1dNREVfZWRpdG9yX2NhbXBhaWduX2F1dHVtbjE3X2EnID0gJ0JUMScsCiAgICAgICAgICAgICAgICAgICAgICAgICAnV01ERV9lZGl0b3JfY2FtcGFpZ25fYXV0dW1uMTdfYicgPSAnQlQyJywKICAgICAgICAgICAgICAgICAgICAgICAgICdXTURFX2VkaXRvcl9jYW1wYWlnbl9hdXR1bW4xN19jJyA9ICdCVDMnLAogICAgICAgICAgICAgICAgICAgICAgICAgJ1dNREVfZWRpdG9yX2NhbXBhaWduX2F1dHVtbjE3X2UnID0gJ0dJQl9SRycsCiAgICAgICAgICAgICAgICAgICAgICAgICAnV01ERV9lZGl0b3JfY2FtcGFpZ25fYXV0dW1uMTdfZCcgPSAnR0lCX0xQJywKICAgICAgICAgICAgICAgICAgICAgICAgICdXTURFX2VkaXRvcl9jYW1wYWlnbl9hdXR1bW4xN19hMicgPSAnQlQxJywKICAgICAgICAgICAgICAgICAgICAgICAgICdXTURFX2VkaXRvcl9jYW1wYWlnbl9hdXR1bW4xN19iMicgPSAnQlQyJywKICAgICAgICAgICAgICAgICAgICAgICAgICdXTURFX2VkaXRvcl9jYW1wYWlnbl9hdXR1bW4xN19jMicgPSAnQlQzJywKICAgICAgICAgICAgICAgICAgICAgICAgICdXTURFX2VkaXRvcl9jYW1wYWlnbl9hdXR1bW4xN19lMicgPSAnR0lCX1JHJywKICAgICAgICAgICAgICAgICAgICAgICAgICdXTURFX2VkaXRvcl9jYW1wYWlnbl9hdXR1bW4xN19kMicgPSAnR0lCX0xQJwogICAgICAgICAgICAgICAgICAgICAgICAgKQojIyMgLS0tIENvdW50IGRhaWx5IGJhbm5lciBpbXByZXNzaW9uczoKYmFuSW1wU2V0IDwtIGRhdGFTZXQgJT4lIAogIGZpbHRlcihyZXN1bHQgJWluJSAnc2hvdycpICU+JSAKICBtdXRhdGUoaW1wcmVzc2lvblJhdGUgPSAxL2ltcHJlc3Npb25SYXRlKSAgJT4lCiAgZ3JvdXBfYnkoYmFubmVyLCBkYXRlKSAlPiUgCiAgc3VtbWFyaXNlKENvdW50ID0gc3VtKGltcHJlc3Npb25SYXRlKSkKY29sbmFtZXMoYmFuSW1wU2V0KSA8LSBjKCdCYW5uZXInLCAnRGF0ZScsICdDb3VudCcpCiMgLSBDYW1wYWlnbiBDaGFydCBDb2xvcnMKY2FtcGFpZ25DaGFydENvbG9ycyA8LSBjKCdpbmRpYW5yZWQxJywgJ2luZGlhbnJlZDInLCAnaW5kaWFucmVkMycsCiAgICAgICAgICAgICAgICdjYWRldGJsdWUnLCAnY2FkZXRibHVlMicpCm5hbWVzKGNhbXBhaWduQ2hhcnRDb2xvcnMpIDwtIGMoJ0JUMScsICdCVDInLCAnQlQzJywgJ0dJQl9MUCcsICdHSUJfUkcnKQoKIyAtIFZpc3VhbGl6ZSB3LiB7Z2dwbG90Mn0KZ2dwbG90KGJhbkltcFNldCwgYWVzKHggPSBEYXRlLAogICAgICAgICAgICAgICAgICAgICAgeSA9IENvdW50LAogICAgICAgICAgICAgICAgICAgICAgZ3JvdXAgPSBCYW5uZXIsCiAgICAgICAgICAgICAgICAgICAgICBjb2xvciA9IEJhbm5lciwKICAgICAgICAgICAgICAgICAgICAgIGZpbGwgPSBCYW5uZXIsCiAgICAgICAgICAgICAgICAgICAgICBsYWJlbCA9IENvdW50KSkgKyAKICBnZW9tX3BhdGgoc2l6ZSA9IC41KSArIAogIHNjYWxlX2ZpbGxfbWFudWFsKCJsZWdlbmQiLCB2YWx1ZXMgPSBjYW1wYWlnbkNoYXJ0Q29sb3JzKSArCiAgc2NhbGVfY29sb3JfbWFudWFsKCJsZWdlbmQiLCB2YWx1ZXMgPSBjYW1wYWlnbkNoYXJ0Q29sb3JzKSArIAogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBjb21tYSkgKwogIGdndGl0bGUoJ0F1dHVtbiBCYW5uZXIgQ2FtcGFpZ24gMjAxNzogQmFubmVyIEltcHJlc3Npb25zJykgKwogIHRoZW1lX21pbmltYWwoKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgc2l6ZSA9IDgpKSArCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApKSArCiAgdGhlbWUobGVnZW5kLnRpdGxlID0gZWxlbWVudF9ibGFuaygpKQpgYGAKCmBgYHtyIGVjaG8gPSBULCB3YXJuaW5nID0gJ2hpZGUnLCBtZXNzYWdlID0gRn0KIyMjIC0tLSBGdWxsIERhdGFzZXQgKFRhYmxlIFJlcG9ydCkKZGF0YXRhYmxlKGJhbkltcFNldCkKYGBgCgojIyMgMS4yIEJhbm5lciBDbGlja3MgYW5kIExhbmRpbmcgUGFnZSBWaWV3cwoKIyMjIDEuMi4wIFRoZSBEYXRhc2V0CgpgYGB7ciBlY2hvID0gVH0KZGF0YVNldCA8LSByZWFkLmNzdihwYXN0ZSgnLi9fZGFpbHlVcGRhdGVEQVRBLycsICdhYmNfQmFubmVyQ2xpY2tzUGFnZVZpZXdzX1VwZGF0ZS5jc3YnLCBzZXAgPSAiIiksCiAgICAgICAgICAgICAgICAgICAgaGVhZGVyID0gVCwKICAgICAgICAgICAgICAgICAgICBjaGVjay5uYW1lcyA9IEYsCiAgICAgICAgICAgICAgICAgICAgcm93Lm5hbWVzID0gMSwKICAgICAgICAgICAgICAgICAgICBzdHJpbmdzQXNGYWN0b3JzID0gRikgJT4lIAogIGZpbHRlcihQYWdlICVpbiUgYygnSmV0enRNaXRtYWNoZW4nLCAnU3BlemlhbDpCZW51dHplcmtvbnRvX2FubGVnZW4nLCAnTWFjaF9taXQnKSkKIyAtIGZpeCAnR0lQJyAtPiAnR0lCJyBpbiB0aGUgZGF0YXNldDoKZGF0YVNldCRTb3VyY2UgPC0gZ3N1YignR0lQJywgJ0dJQicsIGRhdGFTZXQkU291cmNlKQojIC0gTk9URSAoVEVNUE9SQVJZKToKZGF0YVNldCA8LSBkYXRhU2V0WzE6KGRpbShkYXRhU2V0KVsxXSAtIDIpLCBdCmRhdGFTZXQgPC0gZmlsdGVyKGRhdGFTZXQsIAogICAgICAgICAgICAgICAgICAhaXMubmEoUGFnZSkgJiAhaXMubmEoU291cmNlKSAmICFpcy5uYShEYXRlKSAmICEoU291cmNlID09ICI8TkE+IikpCiMgLSBDaGFydCBjb2xvcnNnaXQgCmNoYXJ0Q29scyA8LSBjKCdpbmRpYW5yZWQxJywgJ2luZGlhbnJlZDInLCAnaW5kaWFucmVkMycsCiAgICAgICAgICAgICAgICdjYWRldGJsdWUnLCAnY2FkZXRibHVlMicsIAogICAgICAgICAgICAgICAnZGVlcHNreWJsdWUnLCAndmlvbGV0cmVkMScsICd2aW9sZXRyZWQyJywgJ3Zpb2xldHJlZDMnLAogICAgICAgICAgICAgICAnbGlnaHRzbGF0ZWdyZXknLCAnbGlnaHRncmV5JykKbmFtZXMoY2hhcnRDb2xzKSA8LSBjKCdCVDFfY2xpY2snLCAnQlQyX2NsaWNrJywgJ0JUM19jbGljaycsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnR0lCX0xQX2NsaWNrJywgJ0dJQl9SR19jbGljaycsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnTWFjaF9taXQnLCAnSmV0enRNaXRtYWNoZW5fQlQxJywgJ0pldHp0TWl0bWFjaGVuX0JUMicsICdKZXR6dE1pdG1hY2hlbl9CVDMnLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ090aGVyJywgJ1Vua25vd24nKQpkYXRhU2V0JFNvdXJjZSA8LSBmYWN0b3IoZGF0YVNldCRTb3VyY2UsIGxldmVscyA9IGMoJ0JUMV9jbGljaycsICdCVDJfY2xpY2snLCAnQlQzX2NsaWNrJywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdHSUJfTFBfY2xpY2snLCAnR0lCX1JHX2NsaWNrJywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdNYWNoX21pdCcsICdKZXR6dE1pdG1hY2hlbl9CVDEnLCAnSmV0enRNaXRtYWNoZW5fQlQyJywgJ0pldHp0TWl0bWFjaGVuX0JUMycsICdPdGhlcicsICdVbmtub3duJykpCgojIC0gUGFnZSBDaGFydCBDb2xvcnMKcGFnZUNoYXJ0Q29sb3JzIDwtIGMoJ29yYW5nZScsICdkZWVwc2t5Ymx1ZScsICdsaWdodGdyZWVuJykKbmFtZXMocGFnZUNoYXJ0Q29sb3JzKSA8LSBjKCdKZXR6dE1pdG1hY2hlbicsICdTcGV6aWFsOkJlbnV0emVya29udG9fYW5sZWdlbicsICdNYWNoX21pdCcpCmRhdGFTZXQkUGFnZSA8LSBmYWN0b3IoZGF0YVNldCRQYWdlLCAKICAgICAgICAgICAgICAgICAgICAgICBsZXZlbHMgPSBjKCdKZXR6dE1pdG1hY2hlbicsICdTcGV6aWFsOkJlbnV0emVya29udG9fYW5sZWdlbicsICdNYWNoX21pdCcpKQoKIyAtIENhbXBhaWduIENoYXJ0IENvbG9ycwpjYW1wYWlnbkNoYXJ0Q29sb3JzIDwtIGMoJ2luZGlhbnJlZDEnLCAnaW5kaWFucmVkMicsICdpbmRpYW5yZWQzJywKICAgICAgICAgICAgICAgJ2NhZGV0Ymx1ZScsICdjYWRldGJsdWUyJykKbmFtZXMoY2FtcGFpZ25DaGFydENvbG9ycykgPC0gYygnQlQxJywgJ0JUMicsICdCVDMnLCAnR0lCX0xQJywgJ0dJQl9SRycpCmBgYAoKIyMjIyAxLjIuMUEgTGFuZGluZyBQYWdlczogUmVmZXJlcnMgT3ZlcnZpZXcKClRoZSBmb2xsb3dpbmcgY2hhcnRzIHJlcHJlc2VudHMgdGhlIGJyZWFrZG93biBvZiByZWZlcmVycyAoaS5lLiBzb3VyY2VzKSBmb3IgdGhlIGNhbXBhaWduIHBhZ2VzOiBvbmUgcmVnaXN0cmF0aW9uIHBhZ2UsIGFuZCB0d28gbGFuZGluZyBwYWdlcy4KCmBgYHtyIGVjaG8gPSBULCB3YXJuaW5nID0gJ2hpZGUnLCBtZXNzYWdlID0gRn0KIyMjIC0tLSBCYW5uZXIgY2xpY2tzIGFuZCBMYW5kaW5nIFBhZ2UgVmlld3MKIyAtIFRhYmxlIFJlcG9ydAp0YWJsZVNldCA8LSBkYXRhU2V0ICU+JQogIGRwbHlyOjpncm91cF9ieShQYWdlLCBTb3VyY2UsIERhdGUpICU+JSAKICBkcGx5cjo6c3VtbWFyaXNlKENvdW50ID0gbigpKSAlPiUgCiAgZHBseXI6OmFycmFuZ2UoRGF0ZSwgUGFnZSwgU291cmNlKQpnZ3Bsb3QodGFibGVTZXQsIGFlcyh4ID0gUGFnZSwKICAgICAgICAgICAgICAgICAgICB5ID0gQ291bnQsCiAgICAgICAgICAgICAgICAgICAgZ3JvdXAgPSBTb3VyY2UsCiAgICAgICAgICAgICAgICAgICAgY29sb3IgPSBTb3VyY2UsCiAgICAgICAgICAgICAgICAgICAgZmlsbCA9IFNvdXJjZSwKICAgICAgICAgICAgICAgICAgICBsYWJlbCA9IENvdW50KSkgKwogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLCAKICAgICAgICAgICBwb3NpdGlvbiA9ICJkb2RnZSIsIAogICAgICAgICAgIHdpZHRoID0gLjM1KSArCiAgc2NhbGVfZmlsbF9tYW51YWwoImxlZ2VuZCIsIHZhbHVlcyA9IGNoYXJ0Q29scykgKwogIHNjYWxlX2NvbG9yX21hbnVhbCgibGVnZW5kIiwgdmFsdWVzID0gY2hhcnRDb2xzKSArIAogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBjb21tYSkgKwogIGdndGl0bGUoJ0F1dHVtbiBCYW5uZXIgQ2FtcGFpZ24gMjAxNzpcbk92ZXJ2aWV3IG9mIExhbmRpbmcgUGFnZSBWaWV3cyBzb3VyY2VzJykgKwogIHRoZW1lX21pbmltYWwoKSArIAogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIHNpemUgPSA4LCBoanVzdCA9IDEpKSArCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApKSArCiAgdGhlbWUobGVnZW5kLnRpdGxlID0gZWxlbWVudF9ibGFuaygpKSArCiAgdGhlbWUocGFuZWwuZ3JpZC5tYWpvci54ID0gZWxlbWVudF9ibGFuaygpKSArCiAgdGhlbWUocGFuZWwuZ3JpZC5taW5vci54ID0gZWxlbWVudF9ibGFuaygpKSArCiAgdGhlbWUocGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSkKYGBgCgojIyMjIDEuMi4xQiBQYWdlIFZpZXdzL0Jhbm5lciBDbGlja3MgRGF0YXNldCAgCgpUaGUgYFBhZ2VgIGNvbHVtbiByZWZlcnMgdG8gZWl0aGVyIG9uZSBvZiB0aGUgdHdvIGNhbXBhaWduIGxhbmRpbmcgcGFnZXMgb3IgdGhlIHJlZ2lzdHJhdGlvbiBwYWdlLiBUaGUgYFNvdXJjZWAgY29sdW1uIGVuY29tcGFzc2VzIGJvdGggY2FtcGFpZ24gYmFubmVyIGNsaWNrcyBhbmQgY2FtcGFpZ24gcGFnZXMgYXMgcmVmZXJlcnMgb2YgdGhlIGBQYWdlYC4gVGhlIGBDb3VudGAgZGF0YSBoYXZlIGEgZGFpbHkgcmVzb2x1dGlvbi4gIAoKYGBge3IgZWNobyA9IFQsIHdhcm5pbmcgPSAnaGlkZScsIG1lc3NhZ2UgPSBGfQojIyMgLS0tIEZ1bGwgRGF0YXNldCAoVGFibGUgUmVwb3J0KQpkYXRhdGFibGUodGFibGVTZXQpCmBgYAoKIyMjIyAxLjIuMiBMYW5kaW5nIFBhZ2VzOiBSZWZlcmVyIEJyZWFrZG93biAKClRoZSBmb2xsb3dpbmcgdGhyZWUgcGllIGNoYXJ0cyBwcmVzZW50IGEgYnJlYWtkb3duIG9mIHJlZmVyZXJzIChpLmUuIHNvdXJjZXMpIGZvciB0aGUgQ2FtcGFpZ24gcGFnZXMgKHR3byBsYW5kaW5nIHBhZ2VzIGFuZCBvbmUgcmVnaXN0cmF0aW9uIHBhZ2UuCgpgYGB7ciBlY2hvID0gVCwgd2FybmluZyA9ICdoaWRlJywgbWVzc2FnZSA9IEZ9CiMjIyAtLS0gUGFnZSBWaWV3czogU291cmNlcwoKIyAtIFNwZXppYWw6QmVudXR6ZXJrb250b19hbmxlZ2VuCnBhZ2VTb3VyY2UgPC0gZGF0YVNldCAlPiUgCiAgZHBseXI6OmNvdW50KFBhZ2UsIFNvdXJjZSkgJT4lCiAgZHBseXI6Omdyb3VwX2J5KFBhZ2UpICU+JSAKICBkcGx5cjo6bXV0YXRlKFBlcmNlbnQgPSBuL3N1bShuKSkKcGFnZVNvdXJjZSRQZXJjZW50IDwtIHBhc3RlKHJvdW5kKHBhZ2VTb3VyY2UkUGVyY2VudCoxMDAsIDIpLCAiJSIsIHNlcCA9ICIiKQpwYWdlU291cmNlUGxvdCA8LSBmaWx0ZXIocGFnZVNvdXJjZSwgUGFnZSAlaW4lICdTcGV6aWFsOkJlbnV0emVya29udG9fYW5sZWdlbicpCmlmIChkaW0ocGFnZVNvdXJjZVBsb3QpWzFdID4gMCkgewogIGdncGxvdChwYWdlU291cmNlUGxvdCwgYWVzKHggPSAnJywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICB5ID0gbiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xvciA9IFNvdXJjZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmaWxsID0gU291cmNlLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxhYmVsID0gUGVyY2VudCkpICsKICAgIGdlb21fYmFyKGFlcyh4ID0gJycsCiAgICAgICAgICAgICAgICAgeSA9IG4sCiAgICAgICAgICAgICAgICAgY29sb3IgPSBTb3VyY2UsCiAgICAgICAgICAgICAgICAgZmlsbCA9IFNvdXJjZSksIAogICAgICAgICAgICAgc3RhdCA9ICJpZGVudGl0eSIsIAogICAgICAgICAgICAgd2lkdGggPSAxKSArCiAgICBjb29yZF9wb2xhcigieSIsIHN0YXJ0ID0gMCkgKwogICAgZ2VvbV90ZXh0KGFlcyh4ID0gMSksCiAgICAgICAgICAgICAgY29sb3VyID0gIndoaXRlIiwKICAgICAgICAgICAgICBmb250ZmFjZSA9ICJib2xkIiwKICAgICAgICAgICAgICBwb3NpdGlvbiA9IHBvc2l0aW9uX3N0YWNrKHZqdXN0ID0gMC41KSwKICAgICAgICAgICAgICBzaXplID0gMywKICAgICAgICAgICAgICBzaG93LmxlZ2VuZCA9IEYpICsKICAgIHNjYWxlX2ZpbGxfbWFudWFsKCJsZWdlbmQiLCB2YWx1ZXMgPSBjaGFydENvbHMpICsKICAgIHNjYWxlX2NvbG9yX21hbnVhbCgibGVnZW5kIiwgdmFsdWVzID0gY2hhcnRDb2xzKSArIAogICAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IGNvbW1hKSArCiAgICBnZ3RpdGxlKCdBdXR1bW4gQmFubmVyIENhbXBhaWduIDIwMTc6XG5QYWdlIFZpZXdzIFNvdXJjZXMgZm9yIFNwZXppYWw6QmVudXR6ZXJrb250b19hbmxlZ2VuJykgKwogICAgeGxhYigiT3V0dGVyID0gQ291bnQiKSArIHlsYWIoIiIpICsKICAgIHRoZW1lX21pbmltYWwoKSArIAogICAgIyB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfYmxhbmsoKSkgKwogICAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApKSArCiAgICB0aGVtZShsZWdlbmQudGl0bGUgPSBlbGVtZW50X2JsYW5rKCkpICsKICAgIHRoZW1lKHBhbmVsLmdyaWQubWFqb3IueSA9IGVsZW1lbnRfYmxhbmsoKSkgKwogICAgdGhlbWUocGFuZWwuZ3JpZC5taW5vci55ID0gZWxlbWVudF9ibGFuaygpKQp9CgojIC0gU3BlemlhbDpCZW51dHplcmtvbnRvX2FubGVnZW4gLSBVbmtub3duL090aGVyCnBhZ2VTb3VyY2UgPC0gZGF0YVNldCAlPiUgCiAgZmlsdGVyKCEoZGF0YVNldCRTb3VyY2UgJWluJSAnT3RoZXInIHwgZGF0YVNldCRTb3VyY2UgJWluJSAnVW5rbm93bicpKSAlPiUKICBkcGx5cjo6Y291bnQoUGFnZSwgU291cmNlKSAlPiUKICBkcGx5cjo6Z3JvdXBfYnkoUGFnZSkgJT4lIAogIGRwbHlyOjptdXRhdGUoUGVyY2VudCA9IG4vc3VtKG4pKQpwYWdlU291cmNlJFBlcmNlbnQgPC0gcGFzdGUocm91bmQocGFnZVNvdXJjZSRQZXJjZW50KjEwMCwgMiksICIlIiwgc2VwID0gIiIpCnBhZ2VTb3VyY2VQbG90IDwtIGZpbHRlcihwYWdlU291cmNlLCBQYWdlICVpbiUgJ1NwZXppYWw6QmVudXR6ZXJrb250b19hbmxlZ2VuJykKaWYgKGRpbShwYWdlU291cmNlUGxvdClbMV0gPiAwKSB7CiAgZ2dwbG90KHBhZ2VTb3VyY2VQbG90LCBhZXMoeCA9ICcnLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHkgPSBuLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbG9yID0gU291cmNlLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZpbGwgPSBTb3VyY2UsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFiZWwgPSBQZXJjZW50KSkgKwogICAgZ2VvbV9iYXIoYWVzKHggPSAnJywKICAgICAgICAgICAgICAgICB5ID0gbiwKICAgICAgICAgICAgICAgICBjb2xvciA9IFNvdXJjZSwKICAgICAgICAgICAgICAgICBmaWxsID0gU291cmNlKSwgCiAgICAgICAgICAgICBzdGF0ID0gImlkZW50aXR5IiwgCiAgICAgICAgICAgICB3aWR0aCA9IDEpICsKICAgIGNvb3JkX3BvbGFyKCJ5Iiwgc3RhcnQgPSAwKSArCiAgICBnZW9tX3RleHQoYWVzKHggPSAxKSwKICAgICAgICAgICAgICBjb2xvdXIgPSAid2hpdGUiLAogICAgICAgICAgICAgIGZvbnRmYWNlID0gImJvbGQiLAogICAgICAgICAgICAgIHBvc2l0aW9uID0gcG9zaXRpb25fc3RhY2sodmp1c3QgPSAwLjUpLAogICAgICAgICAgICAgIHNpemUgPSAzLAogICAgICAgICAgICAgIHNob3cubGVnZW5kID0gRikgKwogICAgc2NhbGVfZmlsbF9tYW51YWwoImxlZ2VuZCIsIHZhbHVlcyA9IGNoYXJ0Q29scykgKwogICAgc2NhbGVfY29sb3JfbWFudWFsKCJsZWdlbmQiLCB2YWx1ZXMgPSBjaGFydENvbHMpICsKICAgIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBjb21tYSkgKwogICAgZ2d0aXRsZSgnQXV0dW1uIEJhbm5lciBDYW1wYWlnbiAyMDE3OlxuUGFnZSBWaWV3cyBTb3VyY2VzIGZvciBTcGV6aWFsOkJlbnV0emVya29udG9fYW5sZWdlbiAoQ2FtcGFpZ24gb25seSknKSArCiAgICB4bGFiKCJPdXR0ZXIgPSBDb3VudCIpICsgeWxhYigiIikgKwogICAgdGhlbWVfbWluaW1hbCgpICsgCiAgICAjIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpKSArCiAgICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCkpICsKICAgIHRoZW1lKGxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSkgKwogICAgdGhlbWUocGFuZWwuZ3JpZC5tYWpvci55ID0gZWxlbWVudF9ibGFuaygpKSArCiAgICB0aGVtZShwYW5lbC5ncmlkLm1pbm9yLnkgPSBlbGVtZW50X2JsYW5rKCkpCn0KYGBgCgpUaGUgZm9sbG93aW5nIHRhYmxlIHByZXNlbnRzIHRoZSBkYXRhIGluIHJlc3BlY3QgdG8gdGhlIENhbXBhaWduIHNvdXJjZXMgb25seToKCmBgYHtyIGVjaG8gPSBULCB3YXJuaW5nID0gJ2hpZGUnLCBtZXNzYWdlID0gRn0KIyMjIC0tLSBGdWxsIERhdGFzZXQgKFRhYmxlIFJlcG9ydCkKZGF0YXRhYmxlKHBhZ2VTb3VyY2VQbG90KQpgYGAKCmBgYHtyIGVjaG8gPSBULCB3YXJuaW5nID0gJ2hpZGUnLCBtZXNzYWdlID0gRn0KIyAtIEpldHp0TWl0bWFjaGVuCnBhZ2VTb3VyY2UgPC0gZGF0YVNldCAlPiUgCiAgZHBseXI6OmNvdW50KFBhZ2UsIFNvdXJjZSkgJT4lCiAgZHBseXI6Omdyb3VwX2J5KFBhZ2UpICU+JSAKICBkcGx5cjo6bXV0YXRlKFBlcmNlbnQgPSBuL3N1bShuKSkKcGFnZVNvdXJjZSRQZXJjZW50IDwtIHBhc3RlKHJvdW5kKHBhZ2VTb3VyY2UkUGVyY2VudCoxMDAsIDIpLCAiJSIsIHNlcCA9ICIiKQpwYWdlU291cmNlUGxvdCA8LSBmaWx0ZXIocGFnZVNvdXJjZSwgUGFnZSAlaW4lICdKZXR6dE1pdG1hY2hlbicpCmlmIChkaW0ocGFnZVNvdXJjZVBsb3QpWzFdID4gMCkgewogIGdncGxvdChwYWdlU291cmNlUGxvdCwgYWVzKHggPSAnJywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICB5ID0gbiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xvciA9IFNvdXJjZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmaWxsID0gU291cmNlLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxhYmVsID0gUGVyY2VudCkpICsKICAgIGdlb21fYmFyKGFlcyh4ID0gJycsCiAgICAgICAgICAgICAgICAgeSA9IG4sCiAgICAgICAgICAgICAgICAgY29sb3IgPSBTb3VyY2UsCiAgICAgICAgICAgICAgICAgZmlsbCA9IFNvdXJjZSksIAogICAgICAgICAgICAgc3RhdCA9ICJpZGVudGl0eSIsIAogICAgICAgICAgICAgd2lkdGggPSAxKSArCiAgICBjb29yZF9wb2xhcigieSIsIHN0YXJ0ID0gMCkgKwogICAgZ2VvbV90ZXh0KGFlcyh4ID0gMSksCiAgICAgICAgICAgICAgY29sb3VyID0gIndoaXRlIiwKICAgICAgICAgICAgICBmb250ZmFjZSA9ICJib2xkIiwKICAgICAgICAgICAgICBwb3NpdGlvbiA9IHBvc2l0aW9uX3N0YWNrKHZqdXN0ID0gMC41KSwKICAgICAgICAgICAgICBzaXplID0gMywKICAgICAgICAgICAgICBzaG93LmxlZ2VuZCA9IEYpICsKICAgIHNjYWxlX2ZpbGxfbWFudWFsKCJsZWdlbmQiLCB2YWx1ZXMgPSBjaGFydENvbHMpICsKICAgIHNjYWxlX2NvbG9yX21hbnVhbCgibGVnZW5kIiwgdmFsdWVzID0gY2hhcnRDb2xzKSArCiAgICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gY29tbWEpICsKICAgIGdndGl0bGUoJ0F1dHVtbiBCYW5uZXIgQ2FtcGFpZ24gMjAxNzpcblBhZ2UgVmlld3MgU291cmNlcyBmb3IgSmV0enRNaXRtYWNoZW4nKSArCiAgICB4bGFiKCJPdXR0ZXIgPSBDb3VudCIpICsgeWxhYigiIikgKwogICAgdGhlbWVfbWluaW1hbCgpICsgCiAgICAjIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpKSArCiAgICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCkpICsKICAgIHRoZW1lKGxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSkgKwogICAgdGhlbWUocGFuZWwuZ3JpZC5tYWpvci55ID0gZWxlbWVudF9ibGFuaygpKSArCiAgICB0aGVtZShwYW5lbC5ncmlkLm1pbm9yLnkgPSBlbGVtZW50X2JsYW5rKCkpICsKICAgIHRoZW1lKHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCkpCn0KCiMgLSBKZXR6dE1pdG1hY2hlbiAtIG1pbnVzIFVua25vd24vT3RoZXIKcGFnZVNvdXJjZSA8LSBkYXRhU2V0ICU+JQogIGZpbHRlcighKGRhdGFTZXQkU291cmNlICVpbiUgJ090aGVyJyB8IGRhdGFTZXQkU291cmNlICVpbiUgJ1Vua25vd24nKSkgJT4lCiAgZHBseXI6OmNvdW50KFBhZ2UsIFNvdXJjZSkgJT4lCiAgZHBseXI6Omdyb3VwX2J5KFBhZ2UpICU+JSAKICBkcGx5cjo6bXV0YXRlKFBlcmNlbnQgPSBuL3N1bShuKSkKcGFnZVNvdXJjZSRQZXJjZW50IDwtIHBhc3RlKHJvdW5kKHBhZ2VTb3VyY2UkUGVyY2VudCoxMDAsIDIpLCAiJSIsIHNlcCA9ICIiKQpwYWdlU291cmNlUGxvdCA8LSBmaWx0ZXIocGFnZVNvdXJjZSwgUGFnZSAlaW4lICdKZXR6dE1pdG1hY2hlbicpCmlmIChkaW0ocGFnZVNvdXJjZVBsb3QpWzFdID4gMCkgewogIGdncGxvdChwYWdlU291cmNlUGxvdCwgYWVzKHggPSAnJywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICB5ID0gbiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xvciA9IFNvdXJjZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmaWxsID0gU291cmNlLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxhYmVsID0gUGVyY2VudCkpICsKICAgIGdlb21fYmFyKGFlcyh4ID0gJycsCiAgICAgICAgICAgICAgICAgeSA9IG4sCiAgICAgICAgICAgICAgICAgY29sb3IgPSBTb3VyY2UsCiAgICAgICAgICAgICAgICAgZmlsbCA9IFNvdXJjZSksIAogICAgICAgICAgICAgc3RhdCA9ICJpZGVudGl0eSIsIAogICAgICAgICAgICAgd2lkdGggPSAxKSArCiAgICBjb29yZF9wb2xhcigieSIsIHN0YXJ0ID0gMCkgKwogICAgZ2VvbV90ZXh0KGFlcyh4ID0gMSksCiAgICAgICAgICAgICAgY29sb3VyID0gIndoaXRlIiwKICAgICAgICAgICAgICBmb250ZmFjZSA9ICJib2xkIiwKICAgICAgICAgICAgICBwb3NpdGlvbiA9IHBvc2l0aW9uX3N0YWNrKHZqdXN0ID0gMC41KSwKICAgICAgICAgICAgICBzaXplID0gMywKICAgICAgICAgICAgICBzaG93LmxlZ2VuZCA9IEYpICsKICAgIHNjYWxlX2ZpbGxfbWFudWFsKCJsZWdlbmQiLCB2YWx1ZXMgPSBjaGFydENvbHMpICsKICAgIHNjYWxlX2NvbG9yX21hbnVhbCgibGVnZW5kIiwgdmFsdWVzID0gY2hhcnRDb2xzKSArCiAgICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gY29tbWEpICsKICAgIGdndGl0bGUoJ0F1dHVtbiBCYW5uZXIgQ2FtcGFpZ24gMjAxNzpcblBhZ2UgVmlld3MgU291cmNlcyBmb3IgSmV0enRNaXRtYWNoZW4gKENhbXBhaWduIG9ubHkpJykgKwogICAgeGxhYigiT3V0dGVyID0gQ291bnQiKSArIHlsYWIoIiIpICsKICAgIHRoZW1lX21pbmltYWwoKSArIAogICAgIyB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfYmxhbmsoKSkgKwogICAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApKSArCiAgICB0aGVtZShsZWdlbmQudGl0bGUgPSBlbGVtZW50X2JsYW5rKCkpICsKICAgIHRoZW1lKHBhbmVsLmdyaWQubWFqb3IueSA9IGVsZW1lbnRfYmxhbmsoKSkgKwogICAgdGhlbWUocGFuZWwuZ3JpZC5taW5vci55ID0gZWxlbWVudF9ibGFuaygpKSArCiAgICB0aGVtZShwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpKQp9CmBgYAoKVGhlIGZvbGxvd2luZyB0YWJsZSBwcmVzZW50cyB0aGUgZGF0YSBpbiByZXNwZWN0IHRvIHRoZSBDYW1wYWlnbiBzb3VyY2VzIG9ubHk6CgpgYGB7ciBlY2hvID0gVCwgd2FybmluZyA9ICdoaWRlJywgbWVzc2FnZSA9IEZ9CiMjIyAtLS0gRnVsbCBEYXRhc2V0IChUYWJsZSBSZXBvcnQpCmRhdGF0YWJsZShwYWdlU291cmNlUGxvdCkKYGBgCgpgYGB7ciBlY2hvID0gVCwgd2FybmluZyA9ICdoaWRlJywgbWVzc2FnZSA9IEZ9CiMgLSBNYWNoX21pdApwYWdlU291cmNlIDwtIGRhdGFTZXQgJT4lIAogIGRwbHlyOjpjb3VudChQYWdlLCBTb3VyY2UpICU+JQogIGRwbHlyOjpncm91cF9ieShQYWdlKSAlPiUgCiAgZHBseXI6Om11dGF0ZShQZXJjZW50ID0gbi9zdW0obikpCnBhZ2VTb3VyY2UkUGVyY2VudCA8LSBwYXN0ZShyb3VuZChwYWdlU291cmNlJFBlcmNlbnQqMTAwLCAyKSwgIiUiLCBzZXAgPSAiIikKcGFnZVNvdXJjZVBsb3QgPC0gZmlsdGVyKHBhZ2VTb3VyY2UsIFBhZ2UgJWluJSAnTWFjaF9taXQnKQppZiAoZGltKHBhZ2VTb3VyY2VQbG90KVsxXSA+IDApIHsKICBnZ3Bsb3QocGFnZVNvdXJjZVBsb3QsIGFlcyh4ID0gJycsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeSA9IG4sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sb3IgPSBTb3VyY2UsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmlsbCA9IFNvdXJjZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsYWJlbCA9IFBlcmNlbnQpKSArCiAgICBnZW9tX2JhcihhZXMoeCA9ICcnLAogICAgICAgICAgICAgICAgIHkgPSBuLAogICAgICAgICAgICAgICAgIGNvbG9yID0gU291cmNlLAogICAgICAgICAgICAgICAgIGZpbGwgPSBTb3VyY2UpLCAKICAgICAgICAgICAgIHN0YXQgPSAiaWRlbnRpdHkiLCAKICAgICAgICAgICAgIHdpZHRoID0gMSkgKwogICAgY29vcmRfcG9sYXIoInkiLCBzdGFydCA9IDApICsKICAgIGdlb21fdGV4dChhZXMoeCA9IDEpLAogICAgICAgICAgICAgIGNvbG91ciA9ICJ3aGl0ZSIsCiAgICAgICAgICAgICAgZm9udGZhY2UgPSAiYm9sZCIsCiAgICAgICAgICAgICAgcG9zaXRpb24gPSBwb3NpdGlvbl9zdGFjayh2anVzdCA9IDAuNSksCiAgICAgICAgICAgICAgc2l6ZSA9IDMsCiAgICAgICAgICAgICAgc2hvdy5sZWdlbmQgPSBGKSArCiAgICBzY2FsZV9maWxsX21hbnVhbCgibGVnZW5kIiwgdmFsdWVzID0gY2hhcnRDb2xzKSArCiAgICBzY2FsZV9jb2xvcl9tYW51YWwoImxlZ2VuZCIsIHZhbHVlcyA9IGNoYXJ0Q29scykgKwogICAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IGNvbW1hKSArCiAgICBnZ3RpdGxlKCdBdXR1bW4gQmFubmVyIENhbXBhaWduIDIwMTc6XG5QYWdlIFZpZXdzIFNvdXJjZXMgZm9yIE1hY2hfbWl0JykgKwogICAgeGxhYigiT3V0dGVyID0gQ291bnQiKSArIHlsYWIoIiIpICsKICAgIHRoZW1lX21pbmltYWwoKSArIAogICAgIyB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfYmxhbmsoKSkgKwogICAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApKSArCiAgICB0aGVtZShsZWdlbmQudGl0bGUgPSBlbGVtZW50X2JsYW5rKCkpICsKICAgIHRoZW1lKHBhbmVsLmdyaWQubWFqb3IueSA9IGVsZW1lbnRfYmxhbmsoKSkgKwogICAgdGhlbWUocGFuZWwuZ3JpZC5taW5vci55ID0gZWxlbWVudF9ibGFuaygpKSArCiAgICB0aGVtZShwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpKQp9CgojIC0gTWFjaF9taXQgLSBtaW51cyBVbmtub3duL090aGVyCnBhZ2VTb3VyY2UgPC0gZGF0YVNldCAlPiUgCiAgZmlsdGVyKCEoZGF0YVNldCRTb3VyY2UgJWluJSAnT3RoZXInIHwgZGF0YVNldCRTb3VyY2UgJWluJSAnVW5rbm93bicpKSAlPiUKICBkcGx5cjo6Y291bnQoUGFnZSwgU291cmNlKSAlPiUKICBkcGx5cjo6Z3JvdXBfYnkoUGFnZSkgJT4lIAogIGRwbHlyOjptdXRhdGUoUGVyY2VudCA9IG4vc3VtKG4pKQpwYWdlU291cmNlJFBlcmNlbnQgPC0gcGFzdGUocm91bmQocGFnZVNvdXJjZSRQZXJjZW50KjEwMCwgMiksICIlIiwgc2VwID0gIiIpCnBhZ2VTb3VyY2VQbG90IDwtIGZpbHRlcihwYWdlU291cmNlLCBQYWdlICVpbiUgJ01hY2hfbWl0JykKaWYgKGRpbShwYWdlU291cmNlUGxvdClbMV0gPiAwKSB7CiAgZ2dwbG90KHBhZ2VTb3VyY2VQbG90LCBhZXMoeCA9ICcnLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHkgPSBuLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbG9yID0gU291cmNlLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZpbGwgPSBTb3VyY2UsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFiZWwgPSBQZXJjZW50KSkgKwogICAgZ2VvbV9iYXIoYWVzKHggPSAnJywKICAgICAgICAgICAgICAgICB5ID0gbiwKICAgICAgICAgICAgICAgICBjb2xvciA9IFNvdXJjZSwKICAgICAgICAgICAgICAgICBmaWxsID0gU291cmNlKSwgCiAgICAgICAgICAgICBzdGF0ID0gImlkZW50aXR5IiwgCiAgICAgICAgICAgICB3aWR0aCA9IDEpICsKICAgIGNvb3JkX3BvbGFyKCJ5Iiwgc3RhcnQgPSAwKSArCiAgICBnZW9tX3RleHQoYWVzKHggPSAxKSwKICAgICAgICAgICAgICBjb2xvdXIgPSAid2hpdGUiLAogICAgICAgICAgICAgIGZvbnRmYWNlID0gImJvbGQiLAogICAgICAgICAgICAgIHBvc2l0aW9uID0gcG9zaXRpb25fc3RhY2sodmp1c3QgPSAwLjUpLAogICAgICAgICAgICAgIHNpemUgPSAzLAogICAgICAgICAgICAgIHNob3cubGVnZW5kID0gRikgKwogICAgc2NhbGVfZmlsbF9tYW51YWwoImxlZ2VuZCIsIHZhbHVlcyA9IGNoYXJ0Q29scykgKwogICAgc2NhbGVfY29sb3JfbWFudWFsKCJsZWdlbmQiLCB2YWx1ZXMgPSBjaGFydENvbHMpICsgCiAgICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gY29tbWEpICsKICAgIGdndGl0bGUoJ0F1dHVtbiBCYW5uZXIgQ2FtcGFpZ24gMjAxNzpcblBhZ2UgVmlld3MgU291cmNlcyBmb3IgTWFjaF9taXQgKENhbXBhaWduIG9ubHkpJykgKwogICAgeGxhYigiT3V0dGVyID0gQ291bnQiKSArIHlsYWIoIiIpICsKICAgIHRoZW1lX21pbmltYWwoKSArIAogICAgIyB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfYmxhbmsoKSkgKwogICAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApKSArCiAgICB0aGVtZShsZWdlbmQudGl0bGUgPSBlbGVtZW50X2JsYW5rKCkpICsKICAgIHRoZW1lKHBhbmVsLmdyaWQubWFqb3IueSA9IGVsZW1lbnRfYmxhbmsoKSkgKwogICAgdGhlbWUocGFuZWwuZ3JpZC5taW5vci55ID0gZWxlbWVudF9ibGFuaygpKSArCiAgICB0aGVtZShwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpKQp9CmBgYAoKVGhlIGZvbGxvd2luZyB0YWJsZSBwcmVzZW50cyB0aGUgZGF0YSBpbiByZXNwZWN0IHRvIHRoZSBDYW1wYWlnbiBzb3VyY2VzIG9ubHk6CgpgYGB7ciBlY2hvID0gVCwgd2FybmluZyA9ICdoaWRlJywgbWVzc2FnZSA9IEZ9CiMjIyAtLS0gRnVsbCBEYXRhc2V0IChUYWJsZSBSZXBvcnQpCmRhdGF0YWJsZShwYWdlU291cmNlUGxvdCkKYGBgCgojIyMjIDEuMi4zIEJhbm5lciBDbGlja3M6IENhbXBhaWduIFRvdGFsICAKClRoZSBmb2xsb3dpbmcgY2hhcnRzIHJlcHJlc2VudHMgdGhlIG51bWJlciBvZiBiYW5uZXIgY2xpY2tzIGZvciBlYWNoIGNhbXBhaWduIGJhbm5lciBkdXJpbmcgdGhlIGNvdXJzZSBvZiB0aGUgY2FtcGFpZ24uCgpgYGB7ciBlY2hvID0gVCwgd2FybmluZyA9ICdoaWRlJywgbWVzc2FnZSA9IEZ9CiMjIyAtLS0gVGVtcG9yYWwgQmFubmVyIENsaWNrcwojIC0gQ2hhcnQKY2xpY2tQbG90U2V0IDwtIGRhdGFTZXQgJT4lIAogIGRwbHlyOjpzZWxlY3QoU291cmNlLCBEYXRlKSAlPiUKICBkcGx5cjo6ZmlsdGVyKGdyZXBsKCJfY2xpY2siLCBTb3VyY2UpKSAlPiUKICBkcGx5cjo6Z3JvdXBfYnkoU291cmNlLCBEYXRlKSAlPiUgCiAgZHBseXI6OnN1bW1hcmlzZShDb3VudCA9IG4oKSkgJT4lCiAgZHBseXI6OmFycmFuZ2UoRGF0ZSkKZ2dwbG90KGNsaWNrUGxvdFNldCwgYWVzKHggPSBEYXRlLAogICAgICAgICAgICAgICAgICAgICAgICAgeSA9IENvdW50LAogICAgICAgICAgICAgICAgICAgICAgICAgZ3JvdXAgPSBTb3VyY2UsCiAgICAgICAgICAgICAgICAgICAgICAgICBjb2xvciA9IFNvdXJjZSwKICAgICAgICAgICAgICAgICAgICAgICAgIGZpbGwgPSBTb3VyY2UsCiAgICAgICAgICAgICAgICAgICAgICAgICBsYWJlbCA9IENvdW50KSkgKyAKICBnZW9tX3BhdGgoc2l6ZSA9IC41KSArCiAgc2NhbGVfZmlsbF9tYW51YWwoImxlZ2VuZCIsIHZhbHVlcyA9IGNoYXJ0Q29scykgKwogIHNjYWxlX2NvbG9yX21hbnVhbCgibGVnZW5kIiwgdmFsdWVzID0gY2hhcnRDb2xzKSArIAogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBjb21tYSkgKwogIGdndGl0bGUoJ0F1dHVtbiBCYW5uZXIgQ2FtcGFpZ24gMjAxNzogQmFubmVyIENsaWNrcycpICsKICB0aGVtZV9taW5pbWFsKCkgKyAKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCBzaXplID0gOCwgaGp1c3QgPSAxKSkgKwogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSkgKwogIHRoZW1lKGxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSkgKwogIHRoZW1lKHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCkpCmBgYAoKYGBge3IgZWNobyA9IFQsIHdhcm5pbmcgPSAnaGlkZScsIG1lc3NhZ2UgPSBGfQojIyMgLS0tIEZ1bGwgRGF0YXNldCAoVGFibGUgUmVwb3J0KQpkYXRhdGFibGUoY2xpY2tQbG90U2V0KQpgYGAKCiMjIyMgMS4yLjQgUGFnZSBWaWV3czogQ2FtcGFpZ24gVG90YWwgIAoKVGhlIGZvbGxvd2luZyBjaGFydHMgcHJlc2VudHMgKGEpIHRoZSBudW1iZXIgb2YgcGFnZSB2aWV3cyBmb3IgdGhlIHR3byBsYW5kaW5nIHBhZ2VzIGFuZCBvbmUgcmVnaXN0cmF0aW9uIHBhZ2UgZHVyaW5nIHRoZSBjb3Vyc2Ugb2YgdGhlIGNhbXBhaWduLCBhbmQgdGhlbiAoYikgZW5jb21wYXNzaW5nIG9ubHkgcGFnZSB2aWV3cyB0aGF0IHdlcmUgZ2VuZXJhdGVkIGZyb20gdGhlIGNhbXBhaWduLgoKYGBge3IgZWNobyA9IFQsIHdhcm5pbmcgPSAnaGlkZScsIG1lc3NhZ2UgPSBGfQojIyMgLS0tIFRlbXBvcmFsIFBhZ2UgVmlld3MKcGFnZVBsb3RTZXQgPC0gZGF0YVNldCAlPiUgCiAgZHBseXI6OnNlbGVjdChQYWdlLCBEYXRlKSAlPiUKICBkcGx5cjo6Z3JvdXBfYnkoUGFnZSwgRGF0ZSkgJT4lIAogIGRwbHlyOjpzdW1tYXJpc2UoQ291bnQgPSBuKCkpICU+JSAKICBkcGx5cjo6YXJyYW5nZShEYXRlKQpnZ3Bsb3QocGFnZVBsb3RTZXQsIGFlcyh4ID0gRGF0ZSwKICAgICAgICAgICAgICAgICAgICAgICAgeSA9IENvdW50LAogICAgICAgICAgICAgICAgICAgICAgICBncm91cCA9IFBhZ2UsCiAgICAgICAgICAgICAgICAgICAgICAgIGNvbG9yID0gUGFnZSwKICAgICAgICAgICAgICAgICAgICAgICAgZmlsbCA9IFBhZ2UsCiAgICAgICAgICAgICAgICAgICAgICAgIGxhYmVsID0gQ291bnQpKSArCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIAogICAgICAgICAgIHBvc2l0aW9uID0gImRvZGdlIiwgCiAgICAgICAgICAgd2lkdGggPSAuMikgKwogIHNjYWxlX2ZpbGxfbWFudWFsKCJsZWdlbmQiLCB2YWx1ZXMgPSBwYWdlQ2hhcnRDb2xvcnMpICsKICBzY2FsZV9jb2xvcl9tYW51YWwoImxlZ2VuZCIsIHZhbHVlcyA9IHBhZ2VDaGFydENvbG9ycykgKyAKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gY29tbWEpICsKICBnZ3RpdGxlKCdBdXR1bW4gQmFubmVyIENhbXBhaWduIDIwMTc6IFBhZ2UgVmlld3MnKSArCiAgdGhlbWVfbWluaW1hbCgpICsgCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgc2l6ZSA9IDgsIGhqdXN0ID0gMSkpICsKICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCkpICsKICB0aGVtZShsZWdlbmQudGl0bGUgPSBlbGVtZW50X2JsYW5rKCkpICsKICB0aGVtZShwYW5lbC5ncmlkLm1ham9yLnggPSBlbGVtZW50X2JsYW5rKCkpICsKICB0aGVtZShwYW5lbC5ncmlkLm1pbm9yLnggPSBlbGVtZW50X2JsYW5rKCkpICsKICB0aGVtZShwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpKQoKIyMjIC0tLSBUZW1wb3JhbCBQYWdlIFZpZXdzOiBDYW1wYWlnbiBvbmx5CnBhZ2VQbG90U2V0IDwtIGRhdGFTZXQgJT4lIAogIGZpbHRlcighKGRhdGFTZXQkU291cmNlICVpbiUgJ090aGVyJyB8IGRhdGFTZXQkU291cmNlICVpbiUgJ1Vua25vd24nKSkgJT4lCiAgZHBseXI6OnNlbGVjdChQYWdlLCBEYXRlKSAlPiUKICBkcGx5cjo6Z3JvdXBfYnkoUGFnZSwgRGF0ZSkgJT4lIAogIGRwbHlyOjpzdW1tYXJpc2UoQ291bnQgPSBuKCkpICU+JSAKICBkcGx5cjo6YXJyYW5nZShEYXRlKQpnZ3Bsb3QocGFnZVBsb3RTZXQsIGFlcyh4ID0gRGF0ZSwKICAgICAgICAgICAgICAgICAgICAgICAgeSA9IENvdW50LAogICAgICAgICAgICAgICAgICAgICAgICBncm91cCA9IFBhZ2UsCiAgICAgICAgICAgICAgICAgICAgICAgIGNvbG9yID0gUGFnZSwKICAgICAgICAgICAgICAgICAgICAgICAgZmlsbCA9IFBhZ2UsCiAgICAgICAgICAgICAgICAgICAgICAgIGxhYmVsID0gQ291bnQpKSArCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIAogICAgICAgICAgIHBvc2l0aW9uID0gImRvZGdlIiwgCiAgICAgICAgICAgd2lkdGggPSAuMikgKwogIHNjYWxlX2ZpbGxfbWFudWFsKCJsZWdlbmQiLCB2YWx1ZXMgPSBwYWdlQ2hhcnRDb2xvcnMpICsKICBzY2FsZV9jb2xvcl9tYW51YWwoImxlZ2VuZCIsIHZhbHVlcyA9IHBhZ2VDaGFydENvbG9ycykgKyAKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gY29tbWEpICsKICBnZ3RpdGxlKCdBdXR1bW4gQmFubmVyIENhbXBhaWduIDIwMTc6IFBhZ2UgVmlld3MgKENhbXBhaWduIG9ubHkpJykgKwogIHRoZW1lX21pbmltYWwoKSArIAogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIHNpemUgPSA4LCBoanVzdCA9IDEpKSArCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApKSArCiAgdGhlbWUobGVnZW5kLnRpdGxlID0gZWxlbWVudF9ibGFuaygpKSArCiAgdGhlbWUocGFuZWwuZ3JpZC5tYWpvci54ID0gZWxlbWVudF9ibGFuaygpKSArCiAgdGhlbWUocGFuZWwuZ3JpZC5taW5vci54ID0gZWxlbWVudF9ibGFuaygpKSArCiAgdGhlbWUocGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSkKYGBgCgpUaGUgZm9sbG93aW5nIHRhYmxlIHByZXNlbnRzIHRoZSBkYXRhIGluIHJlc3BlY3QgdG8gdGhlIENhbXBhaWduIHNvdXJjZXMgb25seToKCmBgYHtyIGVjaG8gPSBULCB3YXJuaW5nID0gJ2hpZGUnLCBtZXNzYWdlID0gRn0KIyMjIC0tLSBGdWxsIERhdGFzZXQgKFRhYmxlIFJlcG9ydCkKZGF0YXRhYmxlKHBhZ2VQbG90U2V0KQpgYGAKCiMjIDIuIENhbXBhaWduIFVzZXIgUmVnaXN0cmF0aW9ucwoKIyMjIDIuIDAgUmVnaXN0cmF0aW9ucwoKYGBge3IgZWNobyA9IFQsIHdhcm5pbmcgPSAnaGlkZScsIG1lc3NhZ2UgPSBGfQojIyMgLS0tIENhbXBhaWduIFVzZXIgUmVnaXN0cmF0aW9ucwpsRiA8LSBsaXN0LmZpbGVzKHBhdGggPSAiLi9fZGFpbHlVcGRhdGVEQVRBLyIpCmxGIDwtIGxGW2dyZXBsKCd1c2VyUmVnaXN0cmF0aW9ucycsIGxGLCBmaXhlZCA9IFQpXQp1c2VyUmVnIDwtIHJlYWQudGFibGUocGFzdGUoIi4vX2RhaWx5VXBkYXRlREFUQS8iLCBsRiwgc2VwID0gIiIpLAogICAgICAgICAgICAgICAgICAgICAgcXVvdGUgPSAiIiwKICAgICAgICAgICAgICAgICAgICAgIHNlcCA9ICJcdCIsCiAgICAgICAgICAgICAgICAgICAgICBoZWFkZXIgPSBULAogICAgICAgICAgICAgICAgICAgICAgY2hlY2submFtZXMgPSBGLAogICAgICAgICAgICAgICAgICAgICAgc3RyaW5nc0FzRmFjdG9ycyA9IEYpCnVzZXJSZWckdGltZXN0YW1wIDwtIGFzLmNoYXJhY3Rlcih1c2VyUmVnJHRpbWVzdGFtcCkKdXNlclJlZyR0aW1lc3RhbXAgPC0gc2FwcGx5KHVzZXJSZWckdGltZXN0YW1wLCBmdW5jdGlvbih4KSB7CiAgeSA8LSBzdWJzdHIoeCwgMSwgNCkKICBtIDwtIHN1YnN0cih4LCA1LCA2KQogIGQgPC0gc3Vic3RyKHgsIDcsIDgpCiAgcGFydDFEYXRlIDwtIHBhc3RlKHksIG0sIGQsIHNlcCA9ICItIikKICBociA8LSBzdWJzdHIoeCwgOSwgMTApCiAgbWkgPC0gc3Vic3RyKHgsIDExLCAxMikKICBzZSA8LSBzdWJzdHIoeCwgMTMsIDE0KQogIHBhcnQyRGF0ZSA8LSBwYXN0ZShociwgbWksIHNlLCBzZXAgPSAiOiIpCiAgcGFzdGUocGFydDFEYXRlLCBwYXJ0MkRhdGUsIHNlcCA9ICIgIikKfSkKdXNlclJlZyR0aW1lc3RhbXAgPC0gYXMuUE9TSVhjdCh1c2VyUmVnJHRpbWVzdGFtcCwgdHogPSAiVVRDIikKdGltZURpZmYgPC0gCiAgYXMuUE9TSVhjdChhcy5jaGFyYWN0ZXIoU3lzLnRpbWUoKSksIHR6ID0gIlVUQyIpIC0gYXMuUE9TSVhjdChhcy5jaGFyYWN0ZXIoU3lzLnRpbWUoKSksIHR6ID0gIkV1cm9wZS9CZXJsaW4iKQp1c2VyUmVnJHRpbWVzdGFtcCA8LSBhcy5jaGFyYWN0ZXIodXNlclJlZyR0aW1lc3RhbXAgKyB0aW1lRGlmZikKdXNlclJlZyR0aW1lc3RhbXAgPC0gc2FwcGx5KHVzZXJSZWckdGltZXN0YW1wLCBmdW5jdGlvbih4KSB7CiAgeSA8LSBzdWJzdHIoeCwgMSwgNCkKICBtIDwtIHN1YnN0cih4LCA2LCA3KQogIGQgPC0gc3Vic3RyKHgsIDksIDEwKSAKICBwYXN0ZSh5LCBtLCBkLCBzZXAgPSAiLSIpCn0pCnVzZXJSZWcgPC0gdXNlclJlZyAlPiUgCiAgZHBseXI6OnNlbGVjdChpZCwgZXZlbnRfdXNlcklkLCB0aW1lc3RhbXAsIGV2ZW50X2lzU2VsZk1hZGUsIGV2ZW50X2NhbXBhaWduKSAlPiUgCiAgZmlsdGVyKGV2ZW50X2lzU2VsZk1hZGUgPT0gMSAmIGdyZXBsKCJ3bWRlX2FiYzIwMTciLCBldmVudF9jYW1wYWlnbikpCnByaW50KHBhc3RlKGRpbSh1c2VyUmVnKVsxXSwgIiB1c2VycyBoYXZlIHJlZ2lzdGVyZWQgdmlhIHRoZSBDYW1wYWlnbi4iKSkKYGBgCgojIyMgMi4gMUEgVXNlciBSZWdpc3RyYXRpb25zIHBlciBDYW1wYWlnbiAoZGFpbHkpCgpgYGB7ciBlY2hvID0gVCwgd2FybmluZyA9ICdoaWRlJywgbWVzc2FnZSA9IEZ9CnJlZ1Bsb3RTZXQgPC0gdXNlclJlZyAlPiUgCiAgZ3JvdXBfYnkoZXZlbnRfY2FtcGFpZ24sIHRpbWVzdGFtcCkgJT4lIAogIHN1bW1hcmlzZShSZWdpc3RyYXRpb25zID0gbigpKSAlPiUgCiAgYXJyYW5nZSh0aW1lc3RhbXApCmNvbG5hbWVzKHJlZ1Bsb3RTZXQpIDwtIGMoJ0NhbXBhaWduJywgJ0RhdGUnLCAnUmVnaXN0cmF0aW9ucycpCnJlZ1Bsb3RTZXQkQ2FtcGFpZ24gPC0gZmFjdG9yKHRvdXBwZXIoZ3N1Yigid21kZV9hYmMyMDE3XyIsICIiLCByZWdQbG90U2V0JENhbXBhaWduKSkpCmdncGxvdChyZWdQbG90U2V0LCBhZXMoeCA9IERhdGUsCiAgICAgICAgICAgICAgICAgICAgICAgeSA9IFJlZ2lzdHJhdGlvbnMsCiAgICAgICAgICAgICAgICAgICAgICAgZ3JvdXAgPSBDYW1wYWlnbiwKICAgICAgICAgICAgICAgICAgICAgICBjb2xvciA9IENhbXBhaWduLAogICAgICAgICAgICAgICAgICAgICAgIGZpbGwgPSBDYW1wYWlnbikpICsKICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IiwgCiAgICAgICAgICAgcG9zaXRpb24gPSAiZG9kZ2UiLCAKICAgICAgICAgICB3aWR0aCA9IC41KSArCiAgc2NhbGVfZmlsbF9tYW51YWwoImxlZ2VuZCIsIHZhbHVlcyA9IGNhbXBhaWduQ2hhcnRDb2xvcnMpICsKICBzY2FsZV9jb2xvcl9tYW51YWwoImxlZ2VuZCIsIHZhbHVlcyA9IGNhbXBhaWduQ2hhcnRDb2xvcnMpICsgCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IGNvbW1hKSArCiAgZ2d0aXRsZSgnQXV0dW1uIEJhbm5lciBDYW1wYWlnbiAyMDE3OiBVc2VyIFJlZ2lzdHJhdGlvbnMgKGRhaWx5KScpICsKICB0aGVtZV9taW5pbWFsKCkgKyAKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCBzaXplID0gOCwgaGp1c3QgPSAxKSkgKwogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSkgKwogIHRoZW1lKGxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSkgKwogIHRoZW1lKHBhbmVsLmdyaWQubWFqb3IueCA9IGVsZW1lbnRfYmxhbmsoKSkgKwogIHRoZW1lKHBhbmVsLmdyaWQubWlub3IueCA9IGVsZW1lbnRfYmxhbmsoKSkgKwogIHRoZW1lKHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCkpCmBgYAoKYGBge3IgZWNobyA9IFQsIHdhcm5pbmcgPSAnaGlkZScsIG1lc3NhZ2UgPSBGfQojIyMgLS0tIEZ1bGwgRGF0YXNldCAoVGFibGUgUmVwb3J0KQpkYXRhdGFibGUocmVnUGxvdFNldCkKYGBgCgojIyMgMi4gMUIgVXNlciBSZWdpc3RyYXRpb25zIHBlciBDYW1wYWlnbiAodG90YWxzKQoKYGBge3IgZWNobyA9IFQsIHdhcm5pbmcgPSAnaGlkZScsIG1lc3NhZ2UgPSBGfQpyZWdQbG90U2V0VG90YWwgPC0gcmVnUGxvdFNldCAlPiUgCiAgZ3JvdXBfYnkoQ2FtcGFpZ24pICU+JSAKICBzdW1tYXJpc2UoUmVnaXN0cmF0aW9ucyA9IHN1bShSZWdpc3RyYXRpb25zKSkgJT4lIAogIGFycmFuZ2UoQ2FtcGFpZ24pCmdncGxvdChyZWdQbG90U2V0VG90YWwsIGFlcyh4ID0gQ2FtcGFpZ24sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICB5ID0gUmVnaXN0cmF0aW9ucywKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdyb3VwID0gQ2FtcGFpZ24sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xvciA9IENhbXBhaWduLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgZmlsbCA9IENhbXBhaWduLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFiZWwgPSBSZWdpc3RyYXRpb25zKSkgKwogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLCAKICAgICAgICAgICBwb3NpdGlvbiA9ICJkb2RnZSIsIAogICAgICAgICAgIHdpZHRoID0gLjUpICsgCiAgZ2VvbV9sYWJlbChmaWxsID0gIndoaXRlIiwgY29sb3IgPSAiYmxhY2siKSArCiAgc2NhbGVfZmlsbF9tYW51YWwoImxlZ2VuZCIsIHZhbHVlcyA9IGNhbXBhaWduQ2hhcnRDb2xvcnMpICsKICBzY2FsZV9jb2xvcl9tYW51YWwoImxlZ2VuZCIsIHZhbHVlcyA9IGNhbXBhaWduQ2hhcnRDb2xvcnMpICsgCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IGNvbW1hKSArCiAgZ2d0aXRsZSgnQXV0dW1uIEJhbm5lciBDYW1wYWlnbiAyMDE3OiBVc2VyIFJlZ2lzdHJhdGlvbnMgKHRvdGFscyknKSArCiAgdGhlbWVfbWluaW1hbCgpICsgCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgc2l6ZSA9IDgsIGhqdXN0ID0gMSkpICsKICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCkpICsKICB0aGVtZShsZWdlbmQudGl0bGUgPSBlbGVtZW50X2JsYW5rKCkpICsKICB0aGVtZShwYW5lbC5ncmlkLm1ham9yLnggPSBlbGVtZW50X2JsYW5rKCkpICsKICB0aGVtZShwYW5lbC5ncmlkLm1pbm9yLnggPSBlbGVtZW50X2JsYW5rKCkpICsKICB0aGVtZShwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpKQpgYGAKCiMjIyAyLiAyIFRvdGFsIFVzZXIgUmVnaXN0cmF0aW9ucyBkYWlseQoKYGBge3IgZWNobyA9IFQsIHdhcm5pbmcgPSAnaGlkZScsIG1lc3NhZ2UgPSBGfQpyZWdQbG90U2V0RGFpbHkgPC0gdXNlclJlZyAlPiUgCiAgZHBseXI6OmZpbHRlcihldmVudF9pc1NlbGZNYWRlID09IDEgJiBncmVwbCgid21kZV9hYmMyMDE3IiwgZXZlbnRfY2FtcGFpZ24pKSAlPiUgCiAgZ3JvdXBfYnkodGltZXN0YW1wKSAlPiUgCiAgc3VtbWFyaXNlKFJlZ2lzdHJhdGlvbnMgPSBuKCkpICU+JSAKICBhcnJhbmdlKHRpbWVzdGFtcCkKY29sbmFtZXMocmVnUGxvdFNldERhaWx5KSA8LSBjKCdEYXRlJywgJ1JlZ2lzdHJhdGlvbnMnKQpnZ3Bsb3QocmVnUGxvdFNldERhaWx5LCBhZXMoeCA9IERhdGUsCiAgICAgICAgICAgICAgICAgICAgICAgeSA9IFJlZ2lzdHJhdGlvbnMsIAogICAgICAgICAgICAgICAgICAgICAgIGxhYmVsID0gUmVnaXN0cmF0aW9ucykpICsKICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IiwgCiAgICAgICAgICAgcG9zaXRpb24gPSAiZG9kZ2UiLCAKICAgICAgICAgICB3aWR0aCA9IC41LCAKICAgICAgICAgICBmaWxsID0gImRhcmtibHVlIiwgCiAgICAgICAgICAgY29sb3IgPSAiZGFya2JsdWUiKSArIAogIGdlb21fbGFiZWwoKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IGNvbW1hKSArCiAgZ2d0aXRsZSgnQXV0dW1uIEJhbm5lciBDYW1wYWlnbiAyMDE3OiBVc2VyIFJlZ2lzdHJhdGlvbnMnKSArCiAgdGhlbWVfbWluaW1hbCgpICsgCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgc2l6ZSA9IDgsIGhqdXN0ID0gMSkpICsKICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCkpICsKICB0aGVtZShsZWdlbmQudGl0bGUgPSBlbGVtZW50X2JsYW5rKCkpICsKICB0aGVtZShwYW5lbC5ncmlkLm1ham9yLnggPSBlbGVtZW50X2JsYW5rKCkpICsKICB0aGVtZShwYW5lbC5ncmlkLm1pbm9yLnggPSBlbGVtZW50X2JsYW5rKCkpICsKICB0aGVtZShwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpKQpgYGAKCmBgYHtyIGVjaG8gPSBULCB3YXJuaW5nID0gJ2hpZGUnLCBtZXNzYWdlID0gRn0KIyMjIC0tLSBGdWxsIERhdGFzZXQgKFRhYmxlIFJlcG9ydCkKZGF0YXRhYmxlKHJlZ1Bsb3RTZXQpCmBgYAoKIyMgMy4gQ2FtcGFpZ24gR3VpZGVkIFRvdXIKCiMjIyAzLiAxQSBHdWlkZWQgVG91ciBQb2ludCBvZiBFeGl0IChkYWlseSkKCmBgYHtyIGVjaG8gPSBULCB3YXJuaW5nID0gJ2hpZGUnLCBtZXNzYWdlID0gRn0KIyMjIC0tLSBDYW1wYWlnbiBVc2VyIFJlZ2lzdHJhdGlvbnMKZ1RvdXJEYXRhIDwtIHJlYWQudGFibGUoIi4vX2RhaWx5VXBkYXRlREFUQS9hYmMyMDE3X2d1aWRlZFRvdXJzLnRzdiIsCiAgICAgICAgICAgICAgICAgICAgICAgIHF1b3RlID0gIiIsCiAgICAgICAgICAgICAgICAgICAgICAgIHNlcCA9ICJcdCIsCiAgICAgICAgICAgICAgICAgICAgICAgIGhlYWRlciA9IFQsCiAgICAgICAgICAgICAgICAgICAgICAgIGNoZWNrLm5hbWVzID0gRiwKICAgICAgICAgICAgICAgICAgICAgICAgc3RyaW5nc0FzRmFjdG9ycyA9IEYpCiMgLSBjbGVhbiB1cDogZ1RvdXJEYXRhCmdUb3VyRGF0YSA8LSBnVG91ckRhdGFbd2hpY2goIWR1cGxpY2F0ZWQoZ1RvdXJEYXRhJGV2ZW50X3VzZXJJZCkpLCBdCmdUb3VyRGF0YSA8LSBnVG91ckRhdGFbd2hpY2goIShnVG91ckRhdGEkZXZlbnRfdXNlcklkID09IDApKSwgXQoKZ1RvdXJEYXRhIDwtIGdUb3VyRGF0YVt3aGljaChnVG91ckRhdGEkZXZlbnRfdXNlcklkICVpbiUgdXNlclJlZyRldmVudF91c2VySWQpLCBdCgpnVG91ckRhdGEkdGltZXN0YW1wIDwtIGFzLmNoYXJhY3RlcihnVG91ckRhdGEkdGltZXN0YW1wKQpnVG91ckRhdGEkdGltZXN0YW1wIDwtIHNhcHBseShnVG91ckRhdGEkdGltZXN0YW1wLCBmdW5jdGlvbih4KSB7CiAgeSA8LSBzdWJzdHIoeCwgMSwgNCkKICBtIDwtIHN1YnN0cih4LCA1LCA2KQogIGQgPC0gc3Vic3RyKHgsIDcsIDgpCiAgcGFydDFEYXRlIDwtIHBhc3RlKHksIG0sIGQsIHNlcCA9ICItIikKICBociA8LSBzdWJzdHIoeCwgOSwgMTApCiAgbWkgPC0gc3Vic3RyKHgsIDExLCAxMikKICBzZSA8LSBzdWJzdHIoeCwgMTMsIDE0KQogIHBhcnQyRGF0ZSA8LSBwYXN0ZShociwgbWksIHNlLCBzZXAgPSAiOiIpCiAgcGFzdGUocGFydDFEYXRlLCBwYXJ0MkRhdGUsIHNlcCA9ICIgIikKfSkKZ1RvdXJEYXRhJHRpbWVzdGFtcCA8LSBhcy5QT1NJWGN0KGdUb3VyRGF0YSR0aW1lc3RhbXAsIHR6ID0gIlVUQyIpCnRpbWVEaWZmIDwtIAogIGFzLlBPU0lYY3QoYXMuY2hhcmFjdGVyKFN5cy50aW1lKCkpLCB0eiA9ICJVVEMiKSAtIGFzLlBPU0lYY3QoYXMuY2hhcmFjdGVyKFN5cy50aW1lKCkpLCB0eiA9ICJFdXJvcGUvQmVybGluIikKZ1RvdXJEYXRhJHRpbWVzdGFtcCA8LSBhcy5jaGFyYWN0ZXIoZ1RvdXJEYXRhJHRpbWVzdGFtcCArIHRpbWVEaWZmKQpnVG91ckRhdGEkdGltZXN0YW1wIDwtIHNhcHBseShnVG91ckRhdGEkdGltZXN0YW1wLCBmdW5jdGlvbih4KSB7CiAgeSA8LSBzdWJzdHIoeCwgMSwgNCkKICBtIDwtIHN1YnN0cih4LCA2LCA3KQogIGQgPC0gc3Vic3RyKHgsIDksIDEwKSAKICBwYXN0ZSh5LCBtLCBkLCBzZXAgPSAiLSIpCn0pCmdUb3VyRGF0YSA8LSBnVG91ckRhdGEgJT4lCiAgIGZpbHRlcihldmVudF90b3VyICVpbiUgJ2VpbmZ1aHJ1bmcnKQpwbG90R1RvdXJEYXRhIDwtIGdUb3VyRGF0YSAlPiUgCiAgZ3JvdXBfYnkoZXZlbnRfc3RlcCwgdGltZXN0YW1wKSAlPiUgCiAgc3VtbWFyaXNlKENvdW50ID0gbigpKQpjb2xuYW1lcyhwbG90R1RvdXJEYXRhKSA8LSBjKCdUb3VyIFN0ZXAnLCAnRGF0ZScsICdDb3VudCcpCmdncGxvdChwbG90R1RvdXJEYXRhLCBhZXMoeCA9IERhdGUsCiAgICAgICAgICAgICAgICAgICAgICB5ID0gQ291bnQsCiAgICAgICAgICAgICAgICAgICAgICAgZ3JvdXAgPSBgVG91ciBTdGVwYCwKICAgICAgICAgICAgICAgICAgICAgICBjb2xvciA9IGBUb3VyIFN0ZXBgLAogICAgICAgICAgICAgICAgICAgICAgIGZpbGwgPSBgVG91ciBTdGVwYCkpICsKICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IiwgCiAgICAgICAgICAgcG9zaXRpb24gPSAiZG9kZ2UiLCAKICAgICAgICAgICB3aWR0aCA9IC41KSArCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IGNvbW1hKSArCiAgZ2d0aXRsZSgnQXV0dW1uIEJhbm5lciBDYW1wYWlnbiAyMDE3OiBHdWlkZWQgVG91ciBTdGVwcyAoZGFpbHkpJykgKwogIHRoZW1lX21pbmltYWwoKSArIAogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIHNpemUgPSA4LCBoanVzdCA9IDEpKSArCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApKSArCiAgdGhlbWUobGVnZW5kLnRpdGxlID0gZWxlbWVudF9ibGFuaygpKSArCiAgdGhlbWUocGFuZWwuZ3JpZC5tYWpvci54ID0gZWxlbWVudF9ibGFuaygpKSArCiAgdGhlbWUocGFuZWwuZ3JpZC5taW5vci54ID0gZWxlbWVudF9ibGFuaygpKSArCiAgdGhlbWUocGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSkKYGBgCgpgYGB7ciBlY2hvID0gVCwgd2FybmluZyA9ICdoaWRlJywgbWVzc2FnZSA9IEZ9CiMjIyAtLS0gRnVsbCBEYXRhc2V0IChUYWJsZSBSZXBvcnQpCmRhdGF0YWJsZShwbG90R1RvdXJEYXRhKQpgYGAKCiMjIyAzLiAxQiBHdWlkZWQgVG91ciBQb2ludCBvZiBFeGl0ICh0b3RhbHMpCgpgYGB7ciBlY2hvID0gVCwgd2FybmluZyA9ICdoaWRlJywgbWVzc2FnZSA9IEZ9CiMjIyAtLS0gQ2FtcGFpZ24gVXNlciBSZWdpc3RyYXRpb25zCnBsb3RHVG91ckRhdGFUb3RhbCA8LSBwbG90R1RvdXJEYXRhICU+JSAKICBncm91cF9ieShgVG91ciBTdGVwYCkgICU+JQogIHN1bW1hcmlzZShDb3VudCA9IHN1bShDb3VudCkpICU+JSAKICBhcnJhbmdlKGRlc2MoQ291bnQpKQpwbG90R1RvdXJEYXRhVG90YWwkYFRvdXIgU3RlcGAgPC0gCiAgZmFjdG9yKHBsb3RHVG91ckRhdGFUb3RhbCRgVG91ciBTdGVwYCwgCiAgICAgICAgIGxldmVscyA9IHBsb3RHVG91ckRhdGFUb3RhbCRgVG91ciBTdGVwYFtvcmRlcihwbG90R1RvdXJEYXRhVG90YWwkQ291bnQpXSkKZ2dwbG90KHBsb3RHVG91ckRhdGFUb3RhbCwgYWVzKHggPSBgVG91ciBTdGVwYCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHkgPSBDb3VudCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdyb3VwID0gYFRvdXIgU3RlcGAsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xvciA9IGBUb3VyIFN0ZXBgLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmlsbCA9IGBUb3VyIFN0ZXBgLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFiZWwgPSBDb3VudCkpICsKICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IiwgCiAgICAgICAgICAgcG9zaXRpb24gPSAiZG9kZ2UiLCAKICAgICAgICAgICB3aWR0aCA9IC41KSArCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IGNvbW1hKSArIAogIGdlb21fbGFiZWwoZmlsbCA9ICJ3aGl0ZSIsIGNvbG9yID0gImJsYWNrIikgKwogIGdndGl0bGUoJ0F1dHVtbiBCYW5uZXIgQ2FtcGFpZ24gMjAxNzogR3VpZGVkIFRvdXIgU3RlcHMgKHRvdGFscyknKSArCiAgdGhlbWVfbWluaW1hbCgpICsgCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgc2l6ZSA9IDgsIGhqdXN0ID0gMSkpICsKICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCkpICsKICB0aGVtZShsZWdlbmQudGl0bGUgPSBlbGVtZW50X2JsYW5rKCkpICsKICB0aGVtZShwYW5lbC5ncmlkLm1ham9yLnggPSBlbGVtZW50X2JsYW5rKCkpICsKICB0aGVtZShwYW5lbC5ncmlkLm1pbm9yLnggPSBlbGVtZW50X2JsYW5rKCkpICsKICB0aGVtZShwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gJ05vbmUnKQpgYGAKCk51bWJlciBvZiB1c2VycyBub3QgZXhpdGluZyB0aGUgR3VpZGVkIFRvdXI6CgpgYGB7ciBlY2hvID0gVH0KblJlZ2lzdGVyZWQgPC0gZGltKHVzZXJSZWcpWzFdCm5FeGl0ZWRHVCA8LSBkaW0oZ1RvdXJEYXRhKVsxXQpwcmludChwYXN0ZShuUmVnaXN0ZXJlZCAtIG5FeGl0ZWRHVCwgCiAgICAgICAgICAgICIgdXNlcnMgb3V0IG9mICIsIAogICAgICAgICAgICBuUmVnaXN0ZXJlZCwgCiAgICAgICAgICAgICIgKCIsIHJvdW5kKChuUmVnaXN0ZXJlZCAtIG5FeGl0ZWRHVCkvblJlZ2lzdGVyZWQqMTAwLCAyKSwgIiUpIGRpZCBub3QgZXhpdCB0aGUgQ2FtcGFpZ24gR3VpZGVkIFRvdXIiLCAKICAgICAgICAgICAgc2VwID0gIiIpKQpgYGAKCiMjIyAzLiAyIEV4aXRpbmcgdGhlIEd1aWRlZCBUb3VyIGF0IHRoZSBJbml0aWFsIFN0ZXAKCkhvdyBtYW55IHVzZXJzIGV4aXQgdGhlIEd1aWRlZCBUb3VyIGF0IHRoZSBpbml0aWFsIHN0ZXA/CioqTk9URToqKiBUaGUgYE90aGVyc2AgY2F0ZWdvcnkgZW5jb21wYXNzZXMgYWxsIHVzZXJzIHdobyBkaWQgbm90IGV4aXQgYXQgdGhlIGluaXRpYWwgc3RlcDsgdGhleSBoYXZlIGVpdGhlciBleGl0ZWQgdGhlIEd1aWRlZCBUb3VyIGxhdGVyIG9uIG9yIGNvbXBsZXRlZCB0aGUgdG91ci4KCmBgYHtyIGVjaG8gPSBULCB3YXJuaW5nID0gJ2hpZGUnLCBtZXNzYWdlID0gRn0KZXhHVGRhdGEgPC0gcGxvdEdUb3VyRGF0YSAlPiUgCiAgZ3JvdXBfYnkoYFRvdXIgU3RlcGApICU+JSAKICBzdW1tYXJpc2UoQ291bnQgPSBzdW0oQ291bnQpKQpleEdUMSA8LSBleEdUZGF0YSRDb3VudFtleEdUZGF0YSRgVG91ciBTdGVwYCAlaW4lICd3aWxsa29tbWVuJ10KZXhHVDIgPC0gblJlZ2lzdGVyZWQgLSBleEdUMQpleEdUb3VyU3RlcDEgPC0gcGFzdGUoZXhHVDEsICIgKCIsIHJvdW5kKGV4R1QxLyhleEdUMSArIGV4R1QyKSoxMDAsIDIpLCAiJSkiLCBzZXAgPSAiIikKZXhHVG91clN0ZXAyIDwtIHBhc3RlKGV4R1QyLCAiICgiLCByb3VuZChleEdUMi8oZXhHVDEgKyBleEdUMikqMTAwLCAyKSwgIiUpIiwgc2VwID0gIiIpCmV4R1RvdXIxIDwtIGRhdGEuZnJhbWUoYFVzZXJzIHdobyBleGl0ZWQgYXQgU3RlcCAxYCA9IGV4R1RvdXJTdGVwMSwgCiAgICAgICAgICAgICAgICAgICAgICAgYE90aGVyc2AgPSBleEdUb3VyU3RlcDIsCiAgICAgICAgICAgICAgICAgICAgICAgY2hlY2submFtZXMgPSBGLAogICAgICAgICAgICAgICAgICAgICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGKQprbml0cjo6a2FibGUoZXhHVG91cjEsIGZvcm1hdCA9ICJodG1sIikgJT4lIAogIGthYmxlX3N0eWxpbmcoZnVsbF93aWR0aCA9IEYsIHBvc2l0aW9uID0gImxlZnQiKQpgYGAKCiMjIDQuIFVzZXIgRWRpdHMKCiMjIyA0LiAwIFByb3BvcnRpb24gb2YgQWN0aXZlIFVzZXJzCgpgYGB7ciBlY2hvID0gVCwgd2FybmluZyA9ICdoaWRlJywgbWVzc2FnZSA9IEZ9CiMgLSBkZXRlcm1pbmUgdXNlcklEcwp1c2VyUmVnIDwtIHVzZXJSZWcgJT4lIAogIGRwbHlyOjpzZWxlY3QoaWQsIGV2ZW50X3VzZXJJZCwgdGltZXN0YW1wLCBldmVudF9pc1NlbGZNYWRlLCBldmVudF9jYW1wYWlnbikgJT4lIAogIGZpbHRlcihldmVudF9pc1NlbGZNYWRlID09IDEgJiBncmVwbCgid21kZV9hYmMyMDE3IiwgZXZlbnRfY2FtcGFpZ24pKQp1c2VySURzIDwtIHVzZXJSZWckZXZlbnRfdXNlcklkCmVkaXREYXRhIDwtIHJlYWQudGFibGUoIi4vX2RhaWx5VXBkYXRlREFUQS9hYmMyMDE3X3VzZXJFZGl0cy50c3YiLAogICAgICAgICAgICAgICAgICAgICAgIHNlcCA9ICJcdCIsCiAgICAgICAgICAgICAgICAgICAgICAgcXVvdGUgPSAiIiwKICAgICAgICAgICAgICAgICAgICAgICBoZWFkZXIgPSBULAogICAgICAgICAgICAgICAgICAgICAgIGNoZWNrLm5hbWVzID0gRiwKICAgICAgICAgICAgICAgICAgICAgICBzdHJpbmdzQXNGYWN0b3JzID0gRikgJT4lIAogIGZpbHRlcihyZXZfdXNlciAlaW4lIHVzZXJJRHMpCnBsRWRpdERhdGEgPC0gZWRpdERhdGEgJT4lIAogIGdyb3VwX2J5KGVkaXRzKSAlPiUgCiAgc3VtbWFyaXNlKENvdW50ID0gbigpKQpjb2xuYW1lcyhwbEVkaXREYXRhKSA8LSBjKCdOdW0uRWRpdHMnLCAnQ291bnQnKQpwcmludChwYXN0ZShzdW0ocGxFZGl0RGF0YSRDb3VudCksCiAgICAgICAgICAgICIgb3V0IG9mICIsCiAgICAgICAgICAgIGRpbSh1c2VyUmVnKVsxXSwKICAgICAgICAgICAgIiByZWdpc3RlcmVkIHVzZXJzICgiLAogICAgICAgICAgICByb3VuZChzdW0ocGxFZGl0RGF0YSRDb3VudCkvZGltKHVzZXJSZWcpWzFdKjEwMCwgMiksCiAgICAgICAgICAgICIlKSBoYXZlIG1hZGUgYXQgbGVhc3Qgb25lIGVkaXQuIiwgCiAgICAgICAgICAgIHNlcCA9ICIiKQogICAgICApCmBgYAoKIyMjIDQuIDEgVXNlciBFZGl0cyBEaXN0cmlidXRpb24KClRoZSB5LWF4aXMgcmVwcmVzZW50cyBgbG9nKE51bWJlciBvZiB1c2VycylgIHRvIG1ha2UgdGhlIGxpbmUgcGxvdCBtb3JlIHJlYWRhYmxlLCB3aGlsZSB0aGUgZGF0YSBsYWJlbHMgcHJlc2VudCBleGFjdCB1c2VyIGNvdW50cyBhbG9uZ3NpZGUgdGhlIG51bWJlciBvZiBlZGl0cyBtYWRlLiAKCmBgYHtyIGVjaG8gPSBULCB3YXJuaW5nID0gJ2hpZGUnLCBtZXNzYWdlID0gRn0KZ2dwbG90KHBsRWRpdERhdGEsIGFlcyh4ID0gYE51bS5FZGl0c2AsCiAgICAgICAgICAgICAgICAgICAgICB5ID0gbG9nKENvdW50KSwgCiAgICAgICAgICAgICAgICAgICAgICBsYWJlbCA9IHBhc3RlKENvdW50LCAiICgiLCBgTnVtLkVkaXRzYCwgIiBlZGl0cykiLCBzZXAgPSAiIikpCiAgICAgICApICsKICBnZW9tX3BhdGgoc2l6ZSA9IC4yNSwgY29sb3IgPSAiZGFya2JsdWUiKSArCiAgZ2VvbV9wb2ludChzaXplID0gMS41LCBjb2xvciA9ICJkYXJrYmx1ZSIpICsKICBnZW9tX3BvaW50KHNpemUgPSAxLCBjb2xvciA9ICJ3aGl0ZSIpICsgCiAgZ2VvbV90ZXh0X3JlcGVsKHNpemUgPSAzKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IGNvbW1hKSArCiAgeWxhYignbG9nKE51bS4gb2YgVXNlcnMpJykgKyB4bGFiKCdOdW1iZXIgb2YgRWRpdHMnKSArCiAgZ2d0aXRsZSgnQXV0dW1uIEJhbm5lciBDYW1wYWlnbiAyMDE3OiBVc2VyIEVkaXRzIERpc3RyaWJ1dGlvbicpICsKICB0aGVtZV9taW5pbWFsKCkgKyAKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDAsIHNpemUgPSA4LCBoanVzdCA9IDEpKSArCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApKSArCiAgdGhlbWUobGVnZW5kLnRpdGxlID0gZWxlbWVudF9ibGFuaygpKSArCiAgdGhlbWUocGFuZWwuZ3JpZC5tYWpvci54ID0gZWxlbWVudF9ibGFuaygpKSArCiAgdGhlbWUocGFuZWwuZ3JpZC5taW5vci54ID0gZWxlbWVudF9ibGFuaygpKSArCiAgdGhlbWUocGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSkKYGBgCgojIyMgNC4gMiBVc2VyIEVkaXRzIHBlciBDYW1wYWlnbgoKYGBge3IgZWNobyA9IFQsIHdhcm5pbmcgPSAnaGlkZScsIG1lc3NhZ2UgPSBGfQplZGl0Q2FtcGFpZ24gPC0gbGVmdF9qb2luKGVkaXREYXRhLCB1c2VyUmVnLCAKICAgICAgICAgICAgICAgICAgICAgICAgICBieSA9IGMoInJldl91c2VyIiA9ICJldmVudF91c2VySWQiKSkgJT4lIAogIGdyb3VwX2J5KGV2ZW50X2NhbXBhaWduKSAlPiUgCiAgc3VtbWFyaXNlKEVkaXRzID0gc3VtKGVkaXRzKSkKY29sbmFtZXMoZWRpdENhbXBhaWduKSA8LSBjKCdDYW1wYWlnbicsICdFZGl0cycpCiMgLSByZWNvZGU6CmVkaXRDYW1wYWlnbiRDYW1wYWlnbiA8LSByZWNvZGUoZWRpdENhbXBhaWduJENhbXBhaWduLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICd3bWRlX2FiYzIwMTdfYnQxJyA9ICdCVDEnLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICd3bWRlX2FiYzIwMTdfYnQyJyA9ICdCVDInLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICd3bWRlX2FiYzIwMTdfYnQzJyA9ICdCVDMnLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICd3bWRlX2FiYzIwMTdfZ2liX3JnJyA9ICdHSUJfUkcnLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICd3bWRlX2FiYzIwMTdfZ2liX2xwJyA9ICdHSUJfTFAnCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKQplZGl0Q2FtcGFpZ24kQ2FtcGFpZ24gPC0gZmFjdG9yKGVkaXRDYW1wYWlnbiRDYW1wYWlnbiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGV2ZWxzID0gbmFtZXMoY2FtcGFpZ25DaGFydENvbG9ycykpCmdncGxvdChlZGl0Q2FtcGFpZ24sIGFlcyh4ID0gQ2FtcGFpZ24sCiAgICAgICAgICAgICAgICAgICAgICAgICB5ID0gRWRpdHMsIAogICAgICAgICAgICAgICAgICAgICAgICAgZmlsbCA9IENhbXBhaWduLCAKICAgICAgICAgICAgICAgICAgICAgICAgIGNvbG9yID0gQ2FtcGFpZ24sIAogICAgICAgICAgICAgICAgICAgICAgICAgbGFiZWwgPSBFZGl0cykpICsKICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IiwgCiAgICAgICAgICAgcG9zaXRpb24gPSAiZG9kZ2UiLCAKICAgICAgICAgICB3aWR0aCA9IC41KSArIAogIGdlb21fbGFiZWwoZmlsbCA9ICJ3aGl0ZSIsIGNvbG9yID0gImJsYWNrIikgKwogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBjb21tYSkgKyAKICBzY2FsZV9maWxsX21hbnVhbCgibGVnZW5kIiwgdmFsdWVzID0gY2FtcGFpZ25DaGFydENvbG9ycykgKyAKICBzY2FsZV9jb2xvcl9tYW51YWwoImxlZ2VuZCIsIHZhbHVlcyA9IGNhbXBhaWduQ2hhcnRDb2xvcnMpICsgCiAgZ2d0aXRsZSgnQXV0dW1uIEJhbm5lciBDYW1wYWlnbiAyMDE3OiBVc2VyIEVkaXRzIHBlciBDYW1wYWlnbicpICsKICB0aGVtZV9taW5pbWFsKCkgKyAKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDAsIHNpemUgPSA4LCBoanVzdCA9IDEpKSArCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApKSArCiAgdGhlbWUobGVnZW5kLnRpdGxlID0gZWxlbWVudF9ibGFuaygpKSArCiAgdGhlbWUocGFuZWwuZ3JpZC5tYWpvci54ID0gZWxlbWVudF9ibGFuaygpKSArCiAgdGhlbWUocGFuZWwuZ3JpZC5taW5vci54ID0gZWxlbWVudF9ibGFuaygpKSArCiAgdGhlbWUocGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSkKYGBgCgpgYGB7ciBlY2hvID0gVCwgd2FybmluZyA9ICdoaWRlJywgbWVzc2FnZSA9IEZ9CiMjIyAtLS0gRnVsbCBEYXRhc2V0IChUYWJsZSBSZXBvcnQpCmRhdGF0YWJsZShlZGl0Q2FtcGFpZ24pCmBgYAoKCiMjIyA0LiAzIFBlcmNlbnQgb2YgQWN0aXZlIFVzZXJzIHBlciBDYW1wYWlnbgoKVGhlIHBlcmNlbnQgb2YgdXNlcnMgd2hvIG1hZGUgYW55IGVkaXRzIGF0IGFsbCBwZXIgY2FtcGFpZ246CgpgYGB7ciBlY2hvID0gVCwgd2FybmluZyA9ICdoaWRlJywgbWVzc2FnZSA9IEZ9CiMgLSB0aGUgZGF0YXNldAplZGl0c01hZGUgPC0gbGVmdF9qb2luKHVzZXJSZWcsIGVkaXREYXRhLCAKICAgICAgICAgICAgICAgICAgICAgICBieSA9IGMoJ2V2ZW50X3VzZXJJZCcgPSAncmV2X3VzZXInKSkKZWRpdHNNYWRlJGV2ZW50X2NhbXBhaWduIDwtIHRvdXBwZXIoZ3N1Yigid21kZV9hYmMyMDE3XyIsICIiLCBlZGl0c01hZGUkZXZlbnRfY2FtcGFpZ24sIGZpeGVkID0gVCkpCmVkaXRzTWFkZSRlZGl0c1tpcy5uYShlZGl0c01hZGUkZWRpdHMpXSA8LSAwCmVkaXRzTWFkZSRFZGl0IDwtICBpZmVsc2UoZWRpdHNNYWRlJGVkaXRzID4gMCwgJ0VkaXRlZCcsICdObyBlZGl0cycpCmVkaXRzTWFkZSA8LSBkcGx5cjo6c2VsZWN0KGVkaXRzTWFkZSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGV2ZW50X2NhbXBhaWduLCBFZGl0KQpjb2xuYW1lcyhlZGl0c01hZGUpWzFdIDwtICdDYW1wYWlnbicKZWRpdHNNYWRlIDwtIGVkaXRzTWFkZSAlPiUgCiAgZ3JvdXBfYnkoQ2FtcGFpZ24sIEVkaXQpICU+JSAKICBzdW1tYXJpc2UoQ291bnQgPSBuKCkpCmVkaXRzTWFkZSA8LSBlZGl0c01hZGUgJT4lIAogIGdyb3VwX2J5KENhbXBhaWduKSAlPiUgCiAgbXV0YXRlKENvdW50ID0gcm91bmQoQ291bnQvc3VtKENvdW50KSoxMDAsIDIpKQplZGl0c01hZGUkRWRpdCA8LSBmYWN0b3IoZWRpdHNNYWRlJEVkaXQsIGxldmVscyA9IGMoJ0VkaXRlZCcsICdObyBlZGl0cycpKQpnZ3Bsb3QoZWRpdHNNYWRlLCBhZXMoeCA9ICcnLCB5ID0gQ291bnQsCiAgICAgICAgICAgICAgICAgICAgICBmaWxsID0gRWRpdCwKICAgICAgICAgICAgICAgICAgICAgIGNvbG9yID0gRWRpdCwKICAgICAgICAgICAgICAgICAgICAgIGdyb3VwID0gRWRpdCwKICAgICAgICAgICAgICAgICAgICAgIGxhYmVsID0gQ291bnQpKSArIAogIGdlb21fYmFyKHBvc2l0aW9uID0gInN0YWNrIiwgCiAgICAgICAgICAgc3RhdCA9ICJpZGVudGl0eSIsIAogICAgICAgICAgIHdpZHRoID0gMSwgCiAgICAgICAgICAgY29sb3IgPSAiYmxhY2siKSArIAogIGNvb3JkX3BvbGFyKCJ5Iiwgc3RhcnQgPSAwKSArIAogIGZhY2V0X3dyYXAofiBDYW1wYWlnbikgKwogIHNjYWxlX2ZpbGxfbWFudWFsKCJsZWdlbmQiLCB2YWx1ZXMgPSBjKCdmaXJlYnJpY2snLCAnd2hpdGUnKSkgKyAKICBzY2FsZV9jb2xvcl9tYW51YWwoImxlZ2VuZCIsIHZhbHVlcyA9IGMoJ2ZpcmVicmljaycsICd3aGl0ZScpKSArIAogIGdndGl0bGUoJ0F1dHVtbiBCYW5uZXIgQ2FtcGFpZ24gMjAxNzogVXNlciBFZGl0cyBEaXN0cmlidXRpb25zIHBlciBDYW1wYWlnbicpICsgCiAgeGxhYigiIikgKyB5bGFiKCJQZXJjZW50IEVkaXRlZCIpICsKICB0aGVtZV9taW5pbWFsKCkgKyAKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDAsIHNpemUgPSA4LCBoanVzdCA9IDEpKSArCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApKSArCiAgdGhlbWUobGVnZW5kLnRpdGxlID0gZWxlbWVudF9ibGFuaygpKSArCiAgdGhlbWUocGFuZWwuZ3JpZC5tYWpvci54ID0gZWxlbWVudF9ibGFuaygpKSArCiAgdGhlbWUocGFuZWwuZ3JpZC5taW5vci54ID0gZWxlbWVudF9ibGFuaygpKSArCiAgdGhlbWUocGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSkgCmBgYAoKIyMjIDQuIDQgVXNlciBFZGl0cyBCcmVha2Rvd24KCkluIHRoZSBmb2xsb3dpbmcgdGFibGU6IGBObyBlZGl0c2A6IHVzZXJzIHdpdGggemVybyBlZGl0cywgYEVkaXRlZGA6IG51bWJlciBvZiB1c2VyIHdobyBtYWRlIGFueSBlZGl0cyBhdCBhbGwsIGAxIC0gNCBlZGl0c2A6IG51bWJlciBvZiB1c2VycyB3aXRoIDEgLSA0IGVkaXRzLCBgNSAtIDEwIGVkaXRzYDogbnVtYmVyIG9mIHVzZXJzIHdpdGggNSAtIDEwIGVkaXRzLCBhbmQgYD4xMCBlZGl0c2A6IG51bWJlciBvZiB1c2VyIHdpdGggbW9yZSB0aGFuIHRlbiBlZGl0cy4KCmBgYHtyIGVjaG8gPSBULCB3YXJuaW5nID0gJ2hpZGUnLCBtZXNzYWdlID0gRn0KIyMjIC0tLSBGdWxsIERhdGFzZXQgKFRhYmxlIFJlcG9ydCkKcGx0RWRpdHMgPC0gYXMudGJsKGVkaXREYXRhKSAlPiUgCiAgZHBseXI6Omdyb3VwX2J5KGVkaXRzKSAlPiUgCiAgY291bnQoKQplZGl0czAgPC0gZGltKHVzZXJSZWcpWzFdIC0gc3VtKHBsRWRpdERhdGEkQ291bnQpCmVkaXRzIDwtIHN1bShwbHRFZGl0cyRuW3BsdEVkaXRzJGVkaXRzID4gMF0pCmVkaXRzMV80IDwtIHN1bShwbHRFZGl0cyRuW3BsdEVkaXRzJGVkaXRzID49IDEgJiBwbHRFZGl0cyRlZGl0cyA8PSA0XSkKZWRpdHM1XzEwIDwtIHN1bShwbHRFZGl0cyRuW3BsdEVkaXRzJGVkaXRzID49IDUgJiBwbHRFZGl0cyRlZGl0cyA8PSAxMF0pCmVkaXRzMTAgPC0gc3VtKHBsdEVkaXRzJG5bcGx0RWRpdHMkZWRpdHMgPiAxMF0pCmVkaXRDbGFzc2VzIDwtIGRhdGEuZnJhbWUoYE5vIGVkaXRzYCA9IGVkaXRzMCwKICAgICAgICAgICAgICAgICAgICAgICAgICBgRWRpdGVkYCA9IGVkaXRzLAogICAgICAgICAgICAgICAgICAgICAgICAgIGAxIC0gNCBlZGl0c2AgPSBlZGl0czFfNCwKICAgICAgICAgICAgICAgICAgICAgICAgICBgNSAtIDEwIGVkaXRzYCA9IGVkaXRzNV8xMCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgYD4gMTAgZWRpdHNgID0gZWRpdHMxMCwKICAgICAgICAgICAgICAgICAgICAgICAgICBjaGVjay5uYW1lcyA9IEYsCiAgICAgICAgICAgICAgICAgICAgICAgICAgc3RyaW5nc0FzRmFjdG9ycyA9IEYpCmtuaXRyOjprYWJsZShlZGl0Q2xhc3NlcywgZm9ybWF0ID0gImh0bWwiKSAlPiUKICBrYWJsZV9zdHlsaW5nKGZ1bGxfd2lkdGggPSBGLCBwb3NpdGlvbiA9ICJsZWZ0IikKYGBgCgojIyMgNC4gNSBVc2VyIEVkaXRzIGFuZCBHdWlkZWQgVG91ciBFeGl0cwoKSG93IG1hbnkgZWRpdHMgd2hlcmUgbWFkZSBieSB1c2VycyB3aG8gZGlkIGFuZCBkaWQgbm90IGV4aXQgdGhlIENhbXBhaWduIEd1aWRlZCBUb3VyPwoKYGBge3IgZWNobyA9IFQsIHdhcm5pbmcgPSAnaGlkZScsIG1lc3NhZ2UgPSBGfQplZGl0R1REYXRhIDwtIGxlZnRfam9pbihlZGl0RGF0YSwgdXNlclJlZywgYnkgPSBjKCdyZXZfdXNlcicgPSAnZXZlbnRfdXNlcklkJykpCmVkaXRHVERhdGEgPC0gbGVmdF9qb2luKGVkaXRHVERhdGEsIGdUb3VyRGF0YSwgYnkgPSBjKCdyZXZfdXNlcicgPSAnZXZlbnRfdXNlcklkJykpCmV4VG91ckVkaXRzIDwtIHN1bShlZGl0R1REYXRhJGVkaXRzWyFpcy5uYShlZGl0R1REYXRhJGV2ZW50X3RvdXIpXSkKbm90RXhUb3VyRWRpdHMgPC0gc3VtKGVkaXRHVERhdGEkZWRpdHNbaXMubmEoZWRpdEdURGF0YSRldmVudF90b3VyKV0pCmV4aXRlZFRvdXJFZGl0cyA8LSBwYXN0ZShleFRvdXJFZGl0cywgCiAgICAgICAgICAgICAgICAgICAgICAgICAiICgiLCByb3VuZChleFRvdXJFZGl0cy8oZXhUb3VyRWRpdHMgKyBub3RFeFRvdXJFZGl0cykqMTAwLCAyKSwgIiUpIiwKICAgICAgICAgICAgICAgICAgICAgICAgIHNlcCA9ICIiKQpub3RFeGl0ZWRUb3VyRWRpdHMgPC0gcGFzdGUobm90RXhUb3VyRWRpdHMsIAogICAgICAgICAgICAgICAgICAgICAgICAgIiAoIiwgcm91bmQobm90RXhUb3VyRWRpdHMvKGV4VG91ckVkaXRzICsgbm90RXhUb3VyRWRpdHMpKjEwMCwgMiksICIlKSIsCiAgICAgICAgICAgICAgICAgICAgICAgICBzZXAgPSAiIikKZ3RFZGl0cyA8LSBkYXRhLmZyYW1lKGBFeGl0ZWQgR1RgID0gZXhpdGVkVG91ckVkaXRzLCAKICAgICAgICAgICAgICAgICAgICAgIGBEaWQgbm90IGV4aXQgR1RgID0gbm90RXhpdGVkVG91ckVkaXRzLCAKICAgICAgICAgICAgICAgICAgICAgIGNoZWNrLm5hbWVzID0gRiwKICAgICAgICAgICAgICAgICAgICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGKQprbml0cjo6a2FibGUoZ3RFZGl0cywgZm9ybWF0ID0gImh0bWwiKSAlPiUKICBrYWJsZV9zdHlsaW5nKGZ1bGxfd2lkdGggPSBGLCBwb3NpdGlvbiA9ICJsZWZ0IikKYGBgCgoKIyMjIDQuIDYgVGhlIENhdXNhbCBFZmZlY3Qgb2YgdGhlIEd1aWRlZCBUb3VyIFVwb24gRWRpdGluZwoKSG93IGRvZXMgZXhpdGluZyB2cy4gbm90IGV4aXRpbmcgdGhlIENhbXBhaWduIEd1aWRlZCBUb3VyIGluZmx1ZW5jZSB3aGV0aGVyIHRoZSBuZXcgdXNlciB3aWxsIG1ha2UgYXQgbGVhc3Qgb25lIGVkaXQgb3Igbm90PyBUaGUgZm9sbG93aW5nIGNvbnRpbmdlbmN5IHRhYmxlIHByZXNlbnRzIHRoZSBudW1iZXIgb2YgcmVnaXN0ZXJlZCB1c2VycyB3aG8gbWFkZSBhbnkgZWRpdHMgYXQgYWxsICh2cy4gdGhvc2UgZGlkIG5vdCBlZGl0KSBzZXBhcmF0ZWx5IGZvciB0aG9zZSB3aG8gZGlkIGFuZCBkaWQgbm90IGV4aXQgdGhlIEd1aWRlZCBUb3VyLgoKYGBge3IgZWNobyA9IFQsIHdhcm5pbmcgPSAnaGlkZScsIG1lc3NhZ2UgPSBGfQp1c2VyUmVnR1QgPC0gbGVmdF9qb2luKHVzZXJSZWcsIGdUb3VyRGF0YSwgCiAgICAgICAgICAgICAgICAgICAgICAgYnkgPSAnZXZlbnRfdXNlcklkJykKdXNlclJlZ0dUIDwtIGxlZnRfam9pbih1c2VyUmVnR1QsIGVkaXREYXRhLCAKICAgICAgICAgICAgICAgICAgICAgICBieSA9IGMoJ2V2ZW50X3VzZXJJZCcgPSAncmV2X3VzZXInKSkKIyAtIENvbnRpbmdlbmN5IFRhYmxlOgphIDwtIGxlbmd0aCh1c2VyUmVnR1QkZXZlbnRfdXNlcklkWyFpcy5uYSh1c2VyUmVnR1QkZWRpdHMpICYgaXMubmEodXNlclJlZ0dUJGV2ZW50X3RvdXIpXSkKYiA8LSBsZW5ndGgodXNlclJlZ0dUJGV2ZW50X3VzZXJJZFtpcy5uYSh1c2VyUmVnR1QkZWRpdHMpICYgaXMubmEodXNlclJlZ0dUJGV2ZW50X3RvdXIpXSkKYyA8LSBsZW5ndGgodXNlclJlZ0dUJGV2ZW50X3VzZXJJZFshaXMubmEodXNlclJlZ0dUJGVkaXRzKSAmICFpcy5uYSh1c2VyUmVnR1QkZXZlbnRfdG91cildKQpkIDwtIGxlbmd0aCh1c2VyUmVnR1QkZXZlbnRfdXNlcklkW2lzLm5hKHVzZXJSZWdHVCRlZGl0cykgJiAhaXMubmEodXNlclJlZ0dUJGV2ZW50X3RvdXIpXSkKY3QgPC0gZGF0YS5mcmFtZShgRWRpdGVkYCA9IGMoYSwgYyksCiAgICAgICAgICAgICAgICAgYE5vIGVkaXRzYCA9IGMoYiwgZCksCiAgICAgICAgICAgICAgICAgY2hlY2submFtZXMgPSBGKQpyb3duYW1lcyhjdCkgPC0gYygnR1QgQ29tcGxldGVkJywgJ0dUIEV4aXRlZCcpCiMgLSBkZWx0YVA6CmRlbHRhUCA8LSBhLyhhK2IpIC0gYy8oYytkKQppZiAoZGVsdGFQID49IDApIHsKICBjYXVzYWxQIDwtIGRlbHRhUC8oMSAtIGMvKGMrZCkpCn0gZWxzZSB7CiAgY2F1c2FsUCA8LSAtZGVsdGFQLyhjLyhjK2QpKQp9CmtuaXRyOjprYWJsZShjdCwgZm9ybWF0ID0gImh0bWwiKSAlPiUKICBrYWJsZV9zdHlsaW5nKGZ1bGxfd2lkdGggPSBGLCBwb3NpdGlvbiA9ICJsZWZ0IikKYGBgCgpUaGUgZXN0aW1hdGUgb2YgdGhlIENhdXNhbCBQb3dlciAoaXQgY2FuIHJhbmdlIGZyb20gMCA9IG5vIGNhdXNhbCBpbmZsdWVuY2UgYXQgYWxsLCB0byAxID0gYSBjYXVzZSBjb21wbGV0ZWxseSBzdWZmaWNpZW50IHRvIGJyaW5nIGFib3V0IGl0cyBlZmZlY3QpIG9mIHRoZSBHdWlkZWQgVG91ciB0byBicmluZyBhYm91dCBhbnkgZWRpdHMgYXQgYWxsIGlzOgoKYGBge3IgZWNobyA9IFQsIHdhcm5pbmcgPSAnaGlkZScsIG1lc3NhZ2UgPSBGfQpjZWZmZWN0IDwtIGlmZWxzZShkZWx0YVAgPj0gMCwgJ2dlbmVyYXRpdmUgZWZmZWN0JywgJ3ByZXZlbnRpdmUgZWZmZWN0JykKcHJpbnQocGFzdGUoJ0d1aWRlZCBUb3VyIENhdXNhbCBQb3dlcjogJywgcm91bmQoY2F1c2FsUCwgMiksICcgKCcsIGNlZmZlY3QsICcpJywgc2VwID0gIiIpKQpwcmludChwYXN0ZSgnKE5PVEU6IHdpdGggYSB2YWx1ZSBvZiBhIHByb2JhYmlsaXN0aWMgY29udHJhc3QgZGVsdGFQIG9mKTogJywgcm91bmQoZGVsdGFQLCAyKSwgc2VwID0gIiIpKQpgYGAKCioqU1VHR0VTVElPTjoqKiByZW1vdmUgdGhlIEd1aWRlZCBUb3VyIGZyb20gb3VyIGZ1dHVyZSBjYW1wYWlnbnM7IGl0IGhhcyBhbiBwcmV2ZW50aXZlIGVmZmVjdCB1cG9uIHRoZSBudW1iZXIgb2YgbmV3IHVzZXIgZWRpdHMuIAoKIyMgNS4gQ2FtcGFpZ24gRXZhbHVhdGlvbgoKIyMjIDUuMSBBL0IgVGVzdGluZzogQ2FtcGFpZ24gQmFubmVycwoKIyMjIyA1LjFBIFVzZXIgUmVnaXN0cmF0aW9ucwoKUHJlcGFyZSBwcmlvcnMgYW5kIGRhdGEuCgpgYGB7ciBlY2hvID0gVCwgd2FybmluZyA9ICdoaWRlJywgbWVzc2FnZSA9IEYsIHJlc3VsdHMgPSAnaGlkZSd9CnJlZ0RhdGEgPC0gcmVnUGxvdFNldCAlPiUgCiAgZ3JvdXBfYnkoQ2FtcGFpZ24pICU+JSAKICBzdW1tYXJpc2UoUmVnaXN0cmF0aW9ucyA9IHN1bShSZWdpc3RyYXRpb25zKSkKdmlld0RhdGEgPC0gY2xpY2tQbG90U2V0ICU+JSAKICBncm91cF9ieShTb3VyY2UpICU+JSAKICBzdW1tYXJpc2UoQ2xpY2tzID0gc3VtKENvdW50KSkKdmlld0RhdGEkU291cmNlIDwtIGdzdWIoIl9jbGljayIsICIiLCB2aWV3RGF0YSRTb3VyY2UpCnJlZ0RhdGEgPC0gbGVmdF9qb2luKHJlZ0RhdGEsIHZpZXdEYXRhLCBieSA9IGMoJ0NhbXBhaWduJyA9ICdTb3VyY2UnKSkKCiMgLSBVbmluZm9ybWF0aXZlIHByaW9yOgpwcmlvckFscGhhIDwtIDEKcHJpb3JCZXRhIDwtIDEKCiMgLSBEYXRhOgpCVDFEYXRhIDwtIGMocmVwKDEsIHJlZ0RhdGEkUmVnaXN0cmF0aW9uc1sxXSksIHJlcCgwLCByZWdEYXRhJENsaWNrc1sxXSAtIHJlZ0RhdGEkUmVnaXN0cmF0aW9uc1sxXSkpCkJUMkRhdGEgPC0gYyhyZXAoMSwgcmVnRGF0YSRSZWdpc3RyYXRpb25zWzJdKSwgcmVwKDAsIHJlZ0RhdGEkQ2xpY2tzWzJdIC0gcmVnRGF0YSRSZWdpc3RyYXRpb25zWzJdKSkKQlQzRGF0YSA8LSBjKHJlcCgxLCByZWdEYXRhJFJlZ2lzdHJhdGlvbnNbM10pLCByZXAoMCwgcmVnRGF0YSRDbGlja3NbM10gLSByZWdEYXRhJFJlZ2lzdHJhdGlvbnNbM10pKQpHSUJfTFBEYXRhIDwtIGMocmVwKDEsIHJlZ0RhdGEkUmVnaXN0cmF0aW9uc1s0XSksIHJlcCgwLCByZWdEYXRhJENsaWNrc1s0XSAtIHJlZ0RhdGEkUmVnaXN0cmF0aW9uc1s0XSkpCkdJQl9SR0RhdGEgPC0gYyhyZXAoMSwgcmVnRGF0YSRSZWdpc3RyYXRpb25zWzVdKSwgcmVwKDAsIHJlZ0RhdGEkQ2xpY2tzWzVdIC0gcmVnRGF0YSRSZWdpc3RyYXRpb25zWzVdKSkKCiMgLSBQb3N0ZXJpb3JzOgpwb3N0QjFBbHBoYSA8LSBwcmlvckFscGhhICsgc3VtKEJUMURhdGEpCnBvc3RCMUJldGEgPC0gcHJpb3JCZXRhICsgbGVuZ3RoKEJUMURhdGEpIC0gc3VtKEJUMURhdGEpCnBvc3RCMkFscGhhIDwtIHByaW9yQWxwaGEgKyBzdW0oQlQyRGF0YSkKcG9zdEIyQmV0YSA8LSBwcmlvckJldGEgKyBsZW5ndGgoQlQyRGF0YSkgLSBzdW0oQlQyRGF0YSkKcG9zdEIzQWxwaGEgPC0gcHJpb3JBbHBoYSArIHN1bShCVDNEYXRhKQpwb3N0QjNCZXRhIDwtIHByaW9yQmV0YSArIGxlbmd0aChCVDNEYXRhKSAtIHN1bShCVDNEYXRhKQpwb3N0R0lCX0xQQWxwaGEgPC0gcHJpb3JBbHBoYSArIHN1bShHSUJfTFBEYXRhKQpwb3N0R0lCX0xQQmV0YSA8LSBwcmlvckJldGEgKyBsZW5ndGgoR0lCX0xQRGF0YSkgLSBzdW0oR0lCX0xQRGF0YSkKcG9zdEdJQl9SR0FscGhhIDwtIHByaW9yQWxwaGEgKyBzdW0oR0lCX1JHRGF0YSkKcG9zdEdJQl9SR0JldGEgPC0gcHJpb3JCZXRhICsgbGVuZ3RoKEdJQl9SR0RhdGEpIC0gc3VtKEdJQl9SR0RhdGEpCgojIC0gTnVtYmVyIG9mIE1vbnRlIENhcmxvIHNhbXBsZXM6Cm1jTiA8LSAxZTYKYGBgCgojIyMjIyBTdW1tYXJ5CgpUaGUgYEdJQl9SR2AgYmFubmVyIGRvbWluYXRlcyBhbGwgb3RoZXIgaW4gdGVybXMgb2YgdGhlIHByb2JhYmlsaXR5IG9mIHVzZXIgcmVnaXN0cmF0aW9uLiBBcyBvZiB0aGUgYEJUYCBjYW1wYWlnbnM6IGBCVDJgIHBlcmZvcm1zIHJlbGF0aXZlbGx5IGJldHRlciB0aGFuIGBCVDFgIGFuZCBgQlQyYCB3aGljaCBkbyBub3QgZGlmZmVyIChvciBkaWZmZXIgb25seSBzbGlnaHRseSkgYmV0d2VlbiBlYWNoIG90aGVyLiBUaGUgbW9zdCBpbXBvcnRhbnQgZmluZGluZ3MgYXJlOiAKCi0gdGhlIGRvbWluYW5jZSBvZiB0aGUgYEdJQmAgYmFubmVycyBvdmVyIHRoZSBgQlRgIGJhbm5lcnMsIAotIHRoZSBkb21pbmFuY2Ugb2YgYEdJQl9SR2Agb3ZlciBhbnkgb3RoZXIgYmFubmVyLiAKCioqTk9URS4qKiBJIHVzZSB0aGUgdGVybSBgY2FtcGFpZ24gbGlmdGAgaW4gdGhlIGZvbGxvd2luZyBzZWN0aW9ucyB0byByZWZlciB0byBhIGRpZmZlcmVuY2UgaW4gdGhlIHByb2JhYmlsaXR5IG9mIHVzZXIgcmVnaXN0cmF0aW9uIGluIHJlc3BlY3QgdG8gKmFueSogcGFpciBvZiBiYW5uZXJzIHRoYXQgd2VyZSB1c2VkIGR1cmluZyB0aGUgYEFCQzIwMTdgIGNhbXBhaWduLiBUaGVvcmV0aWNhbGx5LCBhIGNhbXBhaWduIGxpZnQgd291bGQgYmUgdGhlIGRpZmZlcmVuY2UgKGluIHNvbWUgS1BJKSBiZXR3ZWVuIGEgZ3JvdXAgb2YgdXNlcnMgd2hvIHdlcmUgZXhwb3NlZCB0byB0aGUgY2FtcGFpZ24gYW5kIHRoZSBjb250cm9sIGdyb3VwIChubyBleHBvc3VyZSkuIElmIHdlIHdvdWxkIGFzc2VzcyB0aGUgYEFCQzIwMTdgIGNhbXBhaWduIGluIHRoaXMgbWFubmVyIHRoZW4gaXQgd291bGQgYmUgbmF0dXJhbCB0byB0YWtlIGBHSUJfUkdgIGFzIGEgY29udHJvbCwgYW5kIGNvbXBhcmUgYWxsIG90aGVyIGdyb3VwcyAoYEJUMWAsIGBCVDJgLCBgQlQzYCwgYW5kIGBHSUJfTFBgKSB0byBpdDsgaW4gdGhhdCBjYXNlLCB3ZSB3b3VsZCBvYnNlcnZlIHRoYXQgYEFCQzIwMTdgIGNhbXBhaWduIGhhZCBubyBsaWZ0IGluIHJlc3BlY3QgdG8gdGhlIGNvbnRyb2wgZ3JvdXAgKippbiB0ZXJtcyBvZiB1c2VyIHJlZ2lzdHJhdGlvbnMqKi4KClRoZSBmb2xsb3dpbmcgc2VjdGlvbnMgcHJvdmlkZSB0aGUgcmVzdWx0cyBvZiBwYWlyd2lzZSBCYXllc2lhbiBBL0IgdGVzdHMgYWNyb3NzIHRoZSBjYW1wYWlnbiBiYW5uZXJzLiAqKlRFQ0hOSUNBTCBOT1RFLioqIFVuaWZvcm0gYEJldGEoMSwgMSlgIHByaW9ycyAoYXNzdW1pbmcgbm8gcHJpb3Iga25vd2xlZ2RlIG9uIHRoZSBwcm9iYWJpbGl0eSBvZiBhIGJhbm5lciBjbGljayBsZWFkaW5nIHRvIGEgcmVnaXN0cmF0aW9uKSBhbmQgYDEsMDAwLDAwMGAgTW9udGUgQ2FybG8gc2FtcGxlcyBmcm9tIHRoZSBwb3N0ZXJpb3JzIHdlcmUgdXNlZC4KCiMjIyMjIEV2YWx1YXRpb246IEJUMSB2cy4gQlQyCgpgYGB7ciBlY2hvID0gVH0KQlQxU2FtcGxlcyA8LSByYmV0YShtY04sIHBvc3RCMUFscGhhLCBwb3N0QjFCZXRhKQpCVDJTYW1wbGVzIDwtIHJiZXRhKG1jTiwgcG9zdEIyQWxwaGEsIHBvc3RCMkJldGEpCnRfcGVyY2VudERpZmYgPC0gKG1lYW4oQlQxU2FtcGxlcykgLSBtZWFuKEJUMlNhbXBsZXMpKS9tZWFuKEJUMlNhbXBsZXMpKjEwMApwQlQxX0JUMiA8LSBtZWFuKChCVDFTYW1wbGVzID4gQlQyU2FtcGxlcykpCiMgLSBQcm9iYWJpbGl0eSBvZiB2MSBiZXR0ZXIgdGhhbiB2MjoKcHJpbnQocGFzdGUoJ1RoZSBwcm9iYWJpbGl0eSBvZiBCVDEgaGF2aW5nIG1vcmUgdXNlciByZWdpc3RyYXRpb25zIHRoYW4gQlQyIGlzOiAnLCBwQlQxX0JUMikpCiMgLSB2MSBDYW1wYWlnbiBMaWZ0CnBlcmNlbnREaWZmIDwtIChCVDFTYW1wbGVzIC0gQlQyU2FtcGxlcykvQlQyU2FtcGxlcyoxMDAKcGVyY2VudERpZmYgPC0gZGF0YS5mcmFtZShwZXJjZW50RGlmZiA9IHBlcmNlbnREaWZmLAogICAgICAgICAgICAgICAgICAgICAgICAgIGFyZWEgPSBpZmVsc2UocGVyY2VudERpZmYgPD0gMCwgJzw9IDAnLCAnPiAwJyksCiAgICAgICAgICAgICAgICAgICAgICAgICAgc3RyaW5nc0FzRmFjdG9ycyA9IFQpCnByaW50KHBhc3RlKCdUaGUgcGVyY2VudCBsaWZ0IHRoYXQgQlQxIGhhcyBvdmVyIEJUMiAoJywKICAgICAgICAgICAgcm91bmQodF9wZXJjZW50RGlmZiwgMiksCiAgICAgICAgICAgICclKSBsaWVzIGluIHRoZSBpbnRlcnZhbCAoJywgCiAgICAgICAgICAgIGFzLmNoYXJhY3Rlcihyb3VuZChxdWFudGlsZShwZXJjZW50RGlmZiRwZXJjZW50RGlmZiwgLjAyNSkpLCAyKSwgCiAgICAgICAgICAgICclLCAnLCAKICAgICAgICAgICAgYXMuY2hhcmFjdGVyKHJvdW5kKHF1YW50aWxlKHBlcmNlbnREaWZmJHBlcmNlbnREaWZmLCAuOTc1KSksIDIpLAogICAgICAgICAgICAnJSkgd2l0aCA5NSUgY2VydGFpbnR5LicsCiAgICAgICAgICAgIHNlcCA9ICIiKSkKZ2dwbG90KHBlcmNlbnREaWZmLCBhZXMoeCA9IHBlcmNlbnREaWZmLAogICAgICAgICAgICAgICAgICAgICAgICBmaWxsID0gYXJlYSkpICsgCiAgZ2VvbV9oaXN0b2dyYW0oYmlud2lkdGggPSAuMSwgYWxwaGEgPSAuNSkgKyAKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gY29tbWEpICsKICB4bGFiKCcoQlQxIC0gQlQyKS9CVDEnKSArIHlsYWIoJ0RlbnNpdHknKSArIAogIGdndGl0bGUoJ0JUMS9CVDIgQ2FtcGFpZ24gTGlmdCcpICsKICB0aGVtZV9taW5pbWFsKCkgKyAKICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCkpCmBgYAoKIyMjIyMgRXZhbHVhdGlvbjogQlQxIHZzLiBCVDMKCmBgYHtyIGVjaG8gPSBUfQptY04gPC0gMWU1CkJUMVNhbXBsZXMgPC0gcmJldGEobWNOLCBwb3N0QjFBbHBoYSwgcG9zdEIxQmV0YSkKQlQzU2FtcGxlcyA8LSByYmV0YShtY04sIHBvc3RCM0FscGhhLCBwb3N0QjNCZXRhKQp0X3BlcmNlbnREaWZmIDwtIChtZWFuKEJUMVNhbXBsZXMpIC0gbWVhbihCVDNTYW1wbGVzKSkvbWVhbihCVDNTYW1wbGVzKSoxMDAKcEJUMV9CVDMgPC0gbWVhbigoQlQxU2FtcGxlcyA+IEJUM1NhbXBsZXMpKQojIC0gUHJvYmFiaWxpdHkgb2YgdjEgYmV0dGVyIHRoYW4gdjI6CnByaW50KHBhc3RlKCdUaGUgcHJvYmFiaWxpdHkgb2YgQlQxIGhhdmluZyBtb3JlIHVzZXIgcmVnaXN0cmF0aW9ucyB0aGFuIEJUMyBpczogJywgcEJUMV9CVDMpKQojIC0gdjEgQ2FtcGFpZ24gTGlmdApwZXJjZW50RGlmZiA8LSAoQlQxU2FtcGxlcyAtIEJUM1NhbXBsZXMpL0JUM1NhbXBsZXMqMTAwCnBlcmNlbnREaWZmIDwtIGRhdGEuZnJhbWUocGVyY2VudERpZmYgPSBwZXJjZW50RGlmZiwKICAgICAgICAgICAgICAgICAgICAgICAgICBhcmVhID0gaWZlbHNlKHBlcmNlbnREaWZmIDw9IDAsICc8PSAwJywgJz4gMCcpLAogICAgICAgICAgICAgICAgICAgICAgICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBUKQpwcmludChwYXN0ZSgnVGhlIHBlcmNlbnQgbGlmdCB0aGF0IEJUMSBoYXMgb3ZlciBCVDMgKCcsCiAgICAgICAgICAgIHJvdW5kKHRfcGVyY2VudERpZmYsIDIpLAogICAgICAgICAgICAnJSkgbGllcyBpbiB0aGUgaW50ZXJ2YWwgKCcsIAogICAgICAgICAgICBhcy5jaGFyYWN0ZXIocm91bmQocXVhbnRpbGUocGVyY2VudERpZmYkcGVyY2VudERpZmYsIC4wMjUpKSwgMiksIAogICAgICAgICAgICAnJSwgJywgCiAgICAgICAgICAgIGFzLmNoYXJhY3Rlcihyb3VuZChxdWFudGlsZShwZXJjZW50RGlmZiRwZXJjZW50RGlmZiwgLjk3NSkpLCAyKSwKICAgICAgICAgICAgJyUpIHdpdGggOTUlIGNlcnRhaW50eS4nLAogICAgICAgICAgICBzZXAgPSAiIikpCmdncGxvdChwZXJjZW50RGlmZiwgYWVzKHggPSBwZXJjZW50RGlmZiwKICAgICAgICAgICAgICAgICAgICAgICAgZmlsbCA9IGFyZWEpKSArIAogIGdlb21faGlzdG9ncmFtKGJpbndpZHRoID0gLjEsIGFscGhhID0gLjUpICsgCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IGNvbW1hKSArCiAgeGxhYignKEJUMSAtIEJUMykvQlQzJykgKyB5bGFiKCdEZW5zaXR5JykgKyAKICBnZ3RpdGxlKCdCVDEvQlQzIENhbXBhaWduIExpZnQnKSArCiAgdGhlbWVfbWluaW1hbCgpICsgCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApKQpgYGAKCiMjIyMjIEV2YWx1YXRpb246IEJUMSB2cy4gR0lCX0xQCgpgYGB7ciBlY2hvID0gVH0KbWNOIDwtIDFlNQpCVDFTYW1wbGVzIDwtIHJiZXRhKG1jTiwgcG9zdEIxQWxwaGEsIHBvc3RCMUJldGEpCkdJQl9MUFNhbXBsZXMgPC0gcmJldGEobWNOLCBwb3N0R0lCX0xQQWxwaGEsIHBvc3RHSUJfTFBCZXRhKQp0X3BlcmNlbnREaWZmIDwtIChtZWFuKEJUMVNhbXBsZXMpIC0gbWVhbihHSUJfTFBTYW1wbGVzKSkvbWVhbihHSUJfTFBTYW1wbGVzKSoxMDAKcEJUMV9HSUJfTFAgPC0gbWVhbigoQlQxU2FtcGxlcyA+IEdJQl9MUFNhbXBsZXMpKQojIC0gUHJvYmFiaWxpdHkgb2YgdjEgYmV0dGVyIHRoYW4gdjI6CnByaW50KHBhc3RlKCdUaGUgcHJvYmFiaWxpdHkgb2YgQlQxIGhhdmluZyBtb3JlIHVzZXIgcmVnaXN0cmF0aW9ucyB0aGFuIEdJQl9MUCBpczogJywgcEJUMV9HSUJfTFApKQojIC0gdjEgQ2FtcGFpZ24gTGlmdApwZXJjZW50RGlmZiA8LSAoQlQxU2FtcGxlcyAtIEdJQl9MUFNhbXBsZXMpL0dJQl9MUFNhbXBsZXMqMTAwCnBlcmNlbnREaWZmIDwtIGRhdGEuZnJhbWUocGVyY2VudERpZmYgPSBwZXJjZW50RGlmZiwKICAgICAgICAgICAgICAgICAgICAgICAgICBhcmVhID0gaWZlbHNlKHBlcmNlbnREaWZmIDw9IDAsICc8PSAwJywgJz4gMCcpLAogICAgICAgICAgICAgICAgICAgICAgICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBUKQpwcmludChwYXN0ZSgnVGhlIHBlcmNlbnQgbGlmdCB0aGF0IEJUMSBoYXMgb3ZlciBHSUJfTFAgKCcsCiAgICAgICAgICAgIHJvdW5kKHRfcGVyY2VudERpZmYsIDIpLAogICAgICAgICAgICAnJSkgbGllcyBpbiB0aGUgaW50ZXJ2YWwgKCcsIAogICAgICAgICAgICBhcy5jaGFyYWN0ZXIocm91bmQocXVhbnRpbGUocGVyY2VudERpZmYkcGVyY2VudERpZmYsIC4wMjUpKSwgMiksIAogICAgICAgICAgICAnJSwgJywgCiAgICAgICAgICAgIGFzLmNoYXJhY3Rlcihyb3VuZChxdWFudGlsZShwZXJjZW50RGlmZiRwZXJjZW50RGlmZiwgLjk3NSkpLCAyKSwKICAgICAgICAgICAgJyUpIHdpdGggOTUlIGNlcnRhaW50eS4nLAogICAgICAgICAgICBzZXAgPSAiIikpCmdncGxvdChwZXJjZW50RGlmZiwgYWVzKHggPSBwZXJjZW50RGlmZiwKICAgICAgICAgICAgICAgICAgICAgICAgZmlsbCA9IGFyZWEpKSArIAogIGdlb21faGlzdG9ncmFtKGJpbndpZHRoID0gLjEsIGFscGhhID0gLjUpICsgCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IGNvbW1hKSArCiAgeGxhYignKEJUMSAtIEdJQl9MUCkvR0lCX0xQJykgKyB5bGFiKCdEZW5zaXR5JykgKyAKICBnZ3RpdGxlKCdCVDEvR0lCX0xQIENhbXBhaWduIExpZnQnKSArCiAgdGhlbWVfbWluaW1hbCgpICsgCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApKQpgYGAKCiMjIyMjIEV2YWx1YXRpb246IEJUMSB2cy4gR0lCX1JHCgpgYGB7ciBlY2hvID0gVH0KbWNOIDwtIDFlNQpCVDFTYW1wbGVzIDwtIHJiZXRhKG1jTiwgcG9zdEIxQWxwaGEsIHBvc3RCMUJldGEpCkdJQl9SR1NhbXBsZXMgPC0gcmJldGEobWNOLCBwb3N0R0lCX0xQQWxwaGEsIHBvc3RHSUJfUkdCZXRhKQp0X3BlcmNlbnREaWZmIDwtIChtZWFuKEJUMVNhbXBsZXMpIC0gbWVhbihHSUJfUkdTYW1wbGVzKSkvbWVhbihHSUJfUkdTYW1wbGVzKSoxMDAKcEJUMV9HSUJfUkcgPC0gbWVhbigoQlQxU2FtcGxlcyA+IEdJQl9SR1NhbXBsZXMpKQojIC0gUHJvYmFiaWxpdHkgb2YgdjEgYmV0dGVyIHRoYW4gdjI6CnByaW50KHBhc3RlKCdUaGUgcHJvYmFiaWxpdHkgb2YgQlQxIGhhdmluZyBtb3JlIHVzZXIgcmVnaXN0cmF0aW9ucyB0aGFuIEdJQl9SRyBpczogJywgcEJUMV9HSUJfUkcpKQojIC0gdjEgQ2FtcGFpZ24gTGlmdApwZXJjZW50RGlmZiA8LSAoQlQxU2FtcGxlcyAtIEdJQl9SR1NhbXBsZXMpL0dJQl9SR1NhbXBsZXMqMTAwCnBlcmNlbnREaWZmIDwtIGRhdGEuZnJhbWUocGVyY2VudERpZmYgPSBwZXJjZW50RGlmZiwKICAgICAgICAgICAgICAgICAgICAgICAgICBhcmVhID0gaWZlbHNlKHBlcmNlbnREaWZmIDw9IDAsICc8PSAwJywgJz4gMCcpLAogICAgICAgICAgICAgICAgICAgICAgICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBUKQpwcmludChwYXN0ZSgnVGhlIHBlcmNlbnQgbGlmdCB0aGF0IEJUMSBoYXMgb3ZlciBHSUJfUkcgKCcsCiAgICAgICAgICAgIHJvdW5kKHRfcGVyY2VudERpZmYsIDIpLAogICAgICAgICAgICAnJSkgbGllcyBpbiB0aGUgaW50ZXJ2YWwgKCcsIAogICAgICAgICAgICBhcy5jaGFyYWN0ZXIocm91bmQocXVhbnRpbGUocGVyY2VudERpZmYkcGVyY2VudERpZmYsIC4wMjUpKSwgMiksIAogICAgICAgICAgICAnJSwgJywgCiAgICAgICAgICAgIGFzLmNoYXJhY3Rlcihyb3VuZChxdWFudGlsZShwZXJjZW50RGlmZiRwZXJjZW50RGlmZiwgLjk3NSkpLCAyKSwKICAgICAgICAgICAgJyUpIHdpdGggOTUlIGNlcnRhaW50eS4nLAogICAgICAgICAgICBzZXAgPSAiIikpCmdncGxvdChwZXJjZW50RGlmZiwgYWVzKHggPSBwZXJjZW50RGlmZiwKICAgICAgICAgICAgICAgICAgICAgICAgZmlsbCA9IGFyZWEpKSArIAogIGdlb21faGlzdG9ncmFtKGJpbndpZHRoID0gLjEsIGFscGhhID0gLjUpICsgCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IGNvbW1hKSArCiAgeGxhYignKEJUMSAtIEdJQl9SRykvR0lCX1JHJykgKyB5bGFiKCdEZW5zaXR5JykgKyAKICBnZ3RpdGxlKCdCVDEvR0lCX1JHIENhbXBhaWduIExpZnQnKSArCiAgdGhlbWVfbWluaW1hbCgpICsgCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApKQpgYGAKCiMjIyMjIEV2YWx1YXRpb246IEJUMiB2cy4gQlQzCgpgYGB7ciBlY2hvID0gVH0KbWNOIDwtIDFlNQpCVDJTYW1wbGVzIDwtIHJiZXRhKG1jTiwgcG9zdEIyQWxwaGEsIHBvc3RCMkJldGEpCkJUM1NhbXBsZXMgPC0gcmJldGEobWNOLCBwb3N0QjNBbHBoYSwgcG9zdEIzQmV0YSkKdF9wZXJjZW50RGlmZiA8LSAobWVhbihCVDJTYW1wbGVzKSAtIG1lYW4oQlQzU2FtcGxlcykpL21lYW4oQlQzU2FtcGxlcykqMTAwCnBCVDJfQlQzIDwtIG1lYW4oKEJUMlNhbXBsZXMgPiBCVDNTYW1wbGVzKSkKIyAtIFByb2JhYmlsaXR5IG9mIHYxIGJldHRlciB0aGFuIHYyOgpwcmludChwYXN0ZSgnVGhlIHByb2JhYmlsaXR5IG9mIEJUMiBoYXZpbmcgbW9yZSB1c2VyIHJlZ2lzdHJhdGlvbnMgdGhhbiBCVDMgaXM6ICcsIHBCVDJfQlQzKSkKIyAtIHYxIENhbXBhaWduIExpZnQKcGVyY2VudERpZmYgPC0gKEJUMlNhbXBsZXMgLSBCVDNTYW1wbGVzKS9CVDNTYW1wbGVzKjEwMApwZXJjZW50RGlmZiA8LSBkYXRhLmZyYW1lKHBlcmNlbnREaWZmID0gcGVyY2VudERpZmYsCiAgICAgICAgICAgICAgICAgICAgICAgICAgYXJlYSA9IGlmZWxzZShwZXJjZW50RGlmZiA8PSAwLCAnPD0gMCcsICc+IDAnKSwKICAgICAgICAgICAgICAgICAgICAgICAgICBzdHJpbmdzQXNGYWN0b3JzID0gVCkKcHJpbnQocGFzdGUoJ1RoZSBwZXJjZW50IGxpZnQgdGhhdCBCVDIgaGFzIG92ZXIgQlQzICgnLAogICAgICAgICAgICByb3VuZCh0X3BlcmNlbnREaWZmLCAyKSwKICAgICAgICAgICAgJyUpIGxpZXMgaW4gdGhlIGludGVydmFsICgnLCAKICAgICAgICAgICAgYXMuY2hhcmFjdGVyKHJvdW5kKHF1YW50aWxlKHBlcmNlbnREaWZmJHBlcmNlbnREaWZmLCAuMDI1KSksIDIpLCAKICAgICAgICAgICAgJyUsICcsIAogICAgICAgICAgICBhcy5jaGFyYWN0ZXIocm91bmQocXVhbnRpbGUocGVyY2VudERpZmYkcGVyY2VudERpZmYsIC45NzUpKSwgMiksCiAgICAgICAgICAgICclKSB3aXRoIDk1JSBjZXJ0YWludHkuJywKICAgICAgICAgICAgc2VwID0gIiIpKQpnZ3Bsb3QocGVyY2VudERpZmYsIGFlcyh4ID0gcGVyY2VudERpZmYsCiAgICAgICAgICAgICAgICAgICAgICAgIGZpbGwgPSBhcmVhKSkgKyAKICBnZW9tX2hpc3RvZ3JhbShiaW53aWR0aCA9IC4xLCBhbHBoYSA9IC41KSArIAogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBjb21tYSkgKwogIHhsYWIoJyhCVDIgLSBCVDMpL0JUMycpICsgeWxhYignRGVuc2l0eScpICsgCiAgZ2d0aXRsZSgnQlQyL0JUMyBDYW1wYWlnbiBMaWZ0JykgKwogIHRoZW1lX21pbmltYWwoKSArIAogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSkKYGBgCgojIyMjIyBFdmFsdWF0aW9uOiBCVDIgdnMuIEdJQl9MUAoKYGBge3IgZWNobyA9IFR9Cm1jTiA8LSAxZTUKQlQyU2FtcGxlcyA8LSByYmV0YShtY04sIHBvc3RCMkFscGhhLCBwb3N0QjJCZXRhKQpHSUJfTFBTYW1wbGVzIDwtIHJiZXRhKG1jTiwgcG9zdEdJQl9MUEFscGhhLCBwb3N0R0lCX0xQQmV0YSkKdF9wZXJjZW50RGlmZiA8LSAobWVhbihCVDJTYW1wbGVzKSAtIG1lYW4oR0lCX0xQU2FtcGxlcykpL21lYW4oR0lCX0xQU2FtcGxlcykqMTAwCnBCVDJfR0lCX0xQIDwtIG1lYW4oKEJUMlNhbXBsZXMgPiBHSUJfTFBTYW1wbGVzKSkKIyAtIFByb2JhYmlsaXR5IG9mIHYxIGJldHRlciB0aGFuIHYyOgpwcmludChwYXN0ZSgnVGhlIHByb2JhYmlsaXR5IG9mIEJUMiBoYXZpbmcgbW9yZSB1c2VyIHJlZ2lzdHJhdGlvbnMgdGhhbiBHSUJfTFAgaXM6ICcsIHBCVDJfR0lCX0xQKSkKIyAtIHYxIENhbXBhaWduIExpZnQKcGVyY2VudERpZmYgPC0gKEJUMlNhbXBsZXMgLSBHSUJfTFBTYW1wbGVzKS9HSUJfTFBTYW1wbGVzKjEwMApwZXJjZW50RGlmZiA8LSBkYXRhLmZyYW1lKHBlcmNlbnREaWZmID0gcGVyY2VudERpZmYsCiAgICAgICAgICAgICAgICAgICAgICAgICAgYXJlYSA9IGlmZWxzZShwZXJjZW50RGlmZiA8PSAwLCAnPD0gMCcsICc+IDAnKSwKICAgICAgICAgICAgICAgICAgICAgICAgICBzdHJpbmdzQXNGYWN0b3JzID0gVCkKcHJpbnQocGFzdGUoJ1RoZSBwZXJjZW50IGxpZnQgdGhhdCBCVDIgaGFzIG92ZXIgR0lCX0xQICgnLAogICAgICAgICAgICByb3VuZCh0X3BlcmNlbnREaWZmLCAyKSwKICAgICAgICAgICAgJyUpIGxpZXMgaW4gdGhlIGludGVydmFsICgnLCAKICAgICAgICAgICAgYXMuY2hhcmFjdGVyKHJvdW5kKHF1YW50aWxlKHBlcmNlbnREaWZmJHBlcmNlbnREaWZmLCAuMDI1KSksIDIpLCAKICAgICAgICAgICAgJyUsICcsIAogICAgICAgICAgICBhcy5jaGFyYWN0ZXIocm91bmQocXVhbnRpbGUocGVyY2VudERpZmYkcGVyY2VudERpZmYsIC45NzUpKSwgMiksCiAgICAgICAgICAgICclKSB3aXRoIDk1JSBjZXJ0YWludHkuJywKICAgICAgICAgICAgc2VwID0gIiIpKQpnZ3Bsb3QocGVyY2VudERpZmYsIGFlcyh4ID0gcGVyY2VudERpZmYsCiAgICAgICAgICAgICAgICAgICAgICAgIGZpbGwgPSBhcmVhKSkgKyAKICBnZW9tX2hpc3RvZ3JhbShiaW53aWR0aCA9IC4xLCBhbHBoYSA9IC41KSArIAogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBjb21tYSkgKwogIHhsYWIoJyhCVDIgLSBHSUJfTFApL0dJQl9MUCcpICsgeWxhYignRGVuc2l0eScpICsgCiAgZ2d0aXRsZSgnQlQyL0dJQl9MUCBDYW1wYWlnbiBMaWZ0JykgKwogIHRoZW1lX21pbmltYWwoKSArIAogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSkKYGBgCgojIyMjIyBFdmFsdWF0aW9uOiBCVDIgdnMuIEdJQl9SRwoKYGBge3IgZWNobyA9IFR9Cm1jTiA8LSAxZTUKQlQyU2FtcGxlcyA8LSByYmV0YShtY04sIHBvc3RCMkFscGhhLCBwb3N0QjJCZXRhKQpHSUJfUkdTYW1wbGVzIDwtIHJiZXRhKG1jTiwgcG9zdEdJQl9SR0FscGhhLCBwb3N0R0lCX1JHQmV0YSkKdF9wZXJjZW50RGlmZiA8LSAobWVhbihCVDJTYW1wbGVzKSAtIG1lYW4oR0lCX1JHU2FtcGxlcykpL21lYW4oR0lCX1JHU2FtcGxlcykqMTAwCnBCVDJfR0lCX1JHIDwtIG1lYW4oKEJUMlNhbXBsZXMgPiBHSUJfUkdTYW1wbGVzKSkKIyAtIFByb2JhYmlsaXR5IG9mIHYxIGJldHRlciB0aGFuIHYyOgpwcmludChwYXN0ZSgnVGhlIHByb2JhYmlsaXR5IG9mIEJUMiBoYXZpbmcgbW9yZSB1c2VyIHJlZ2lzdHJhdGlvbnMgdGhhbiBHSUJfUkcgaXM6ICcsIHBCVDJfR0lCX1JHKSkKIyAtIHYxIENhbXBhaWduIExpZnQKcGVyY2VudERpZmYgPC0gKEJUMlNhbXBsZXMgLSBHSUJfUkdTYW1wbGVzKS9HSUJfUkdTYW1wbGVzKjEwMApwZXJjZW50RGlmZiA8LSBkYXRhLmZyYW1lKHBlcmNlbnREaWZmID0gcGVyY2VudERpZmYsCiAgICAgICAgICAgICAgICAgICAgICAgICAgYXJlYSA9IGlmZWxzZShwZXJjZW50RGlmZiA8PSAwLCAnPD0gMCcsICc+IDAnKSwKICAgICAgICAgICAgICAgICAgICAgICAgICBzdHJpbmdzQXNGYWN0b3JzID0gVCkKcHJpbnQocGFzdGUoJ1RoZSBwZXJjZW50IGxpZnQgdGhhdCBCVDIgaGFzIG92ZXIgR0lCX1JHICgnLAogICAgICAgICAgICByb3VuZCh0X3BlcmNlbnREaWZmLCAyKSwKICAgICAgICAgICAgJyUpIGxpZXMgaW4gdGhlIGludGVydmFsICgnLCAKICAgICAgICAgICAgYXMuY2hhcmFjdGVyKHJvdW5kKHF1YW50aWxlKHBlcmNlbnREaWZmJHBlcmNlbnREaWZmLCAuMDI1KSksIDIpLCAKICAgICAgICAgICAgJyUsICcsIAogICAgICAgICAgICBhcy5jaGFyYWN0ZXIocm91bmQocXVhbnRpbGUocGVyY2VudERpZmYkcGVyY2VudERpZmYsIC45NzUpKSwgMiksCiAgICAgICAgICAgICclKSB3aXRoIDk1JSBjZXJ0YWludHkuJywKICAgICAgICAgICAgc2VwID0gIiIpKQpnZ3Bsb3QocGVyY2VudERpZmYsIGFlcyh4ID0gcGVyY2VudERpZmYsCiAgICAgICAgICAgICAgICAgICAgICAgIGZpbGwgPSBhcmVhKSkgKyAKICBnZW9tX2hpc3RvZ3JhbShiaW53aWR0aCA9IC4xLCBhbHBoYSA9IC41KSArIAogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBjb21tYSkgKwogIHhsYWIoJyhCVDIgLSBHSUJfUkcpL0dJQl9SRycpICsgeWxhYignRGVuc2l0eScpICsgCiAgZ2d0aXRsZSgnQlQyL0dJQl9SRyBDYW1wYWlnbiBMaWZ0JykgKwogIHRoZW1lX21pbmltYWwoKSArIAogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSkKYGBgCgojIyMjIyBFdmFsdWF0aW9uOiBCVDMgdnMuIEdJQl9MUAoKYGBge3IgZWNobyA9IFR9Cm1jTiA8LSAxZTUKQlQzU2FtcGxlcyA8LSByYmV0YShtY04sIHBvc3RCM0FscGhhLCBwb3N0QjNCZXRhKQpHSUJfTFBTYW1wbGVzIDwtIHJiZXRhKG1jTiwgcG9zdEdJQl9MUEFscGhhLCBwb3N0R0lCX0xQQmV0YSkKdF9wZXJjZW50RGlmZiA8LSAobWVhbihCVDNTYW1wbGVzKSAtIG1lYW4oR0lCX0xQU2FtcGxlcykpL21lYW4oR0lCX0xQU2FtcGxlcykqMTAwCnBCVDNfR0lCX0xQIDwtIG1lYW4oKEJUM1NhbXBsZXMgPiBHSUJfTFBTYW1wbGVzKSkKIyAtIFByb2JhYmlsaXR5IG9mIHYxIGJldHRlciB0aGFuIHYyOgpwcmludChwYXN0ZSgnVGhlIHByb2JhYmlsaXR5IG9mIEJUMyBoYXZpbmcgbW9yZSB1c2VyIHJlZ2lzdHJhdGlvbnMgdGhhbiBHSUJfTFAgaXM6ICcsIHBCVDNfR0lCX0xQKSkKIyAtIHYxIENhbXBhaWduIExpZnQKcGVyY2VudERpZmYgPC0gKEJUM1NhbXBsZXMgLSBHSUJfTFBTYW1wbGVzKS9HSUJfTFBTYW1wbGVzKjEwMApwZXJjZW50RGlmZiA8LSBkYXRhLmZyYW1lKHBlcmNlbnREaWZmID0gcGVyY2VudERpZmYsCiAgICAgICAgICAgICAgICAgICAgICAgICAgYXJlYSA9IGlmZWxzZShwZXJjZW50RGlmZiA8PSAwLCAnPD0gMCcsICc+IDAnKSwKICAgICAgICAgICAgICAgICAgICAgICAgICBzdHJpbmdzQXNGYWN0b3JzID0gVCkKcHJpbnQocGFzdGUoJ1RoZSBwZXJjZW50IGxpZnQgdGhhdCBCVDMgaGFzIG92ZXIgR0lCX0xQICgnLAogICAgICAgICAgICByb3VuZCh0X3BlcmNlbnREaWZmLCAyKSwKICAgICAgICAgICAgJyUpIGxpZXMgaW4gdGhlIGludGVydmFsICgnLCAKICAgICAgICAgICAgYXMuY2hhcmFjdGVyKHJvdW5kKHF1YW50aWxlKHBlcmNlbnREaWZmJHBlcmNlbnREaWZmLCAuMDI1KSksIDIpLCAKICAgICAgICAgICAgJyUsICcsIAogICAgICAgICAgICBhcy5jaGFyYWN0ZXIocm91bmQocXVhbnRpbGUocGVyY2VudERpZmYkcGVyY2VudERpZmYsIC45NzUpKSwgMiksCiAgICAgICAgICAgICclKSB3aXRoIDk1JSBjZXJ0YWludHkuJywKICAgICAgICAgICAgc2VwID0gIiIpKQpnZ3Bsb3QocGVyY2VudERpZmYsIGFlcyh4ID0gcGVyY2VudERpZmYsCiAgICAgICAgICAgICAgICAgICAgICAgIGZpbGwgPSBhcmVhKSkgKyAKICBnZW9tX2hpc3RvZ3JhbShiaW53aWR0aCA9IC4xLCBhbHBoYSA9IC41KSArIAogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBjb21tYSkgKwogIHhsYWIoJyhCVDMgLSBHSUJfTFApL0dJQl9MUCcpICsgeWxhYignRGVuc2l0eScpICsgCiAgZ2d0aXRsZSgnQlQzL0dJQl9MUCBDYW1wYWlnbiBMaWZ0JykgKwogIHRoZW1lX21pbmltYWwoKSArIAogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSkKYGBgCgojIyMjIyBFdmFsdWF0aW9uOiBCVDMgdnMuIEdJQl9SRwoKYGBge3IgZWNobyA9IFR9Cm1jTiA8LSAxZTUKQlQzU2FtcGxlcyA8LSByYmV0YShtY04sIHBvc3RCMkFscGhhLCBwb3N0QjJCZXRhKQpHSUJfUkdTYW1wbGVzIDwtIHJiZXRhKG1jTiwgcG9zdEdJQl9SR0FscGhhLCBwb3N0R0lCX1JHQmV0YSkKdF9wZXJjZW50RGlmZiA8LSAobWVhbihCVDNTYW1wbGVzKSAtIG1lYW4oR0lCX1JHU2FtcGxlcykpL21lYW4oR0lCX1JHU2FtcGxlcykqMTAwCnBCVDNfR0lCX1JHIDwtIG1lYW4oKEJUM1NhbXBsZXMgPiBHSUJfUkdTYW1wbGVzKSkKIyAtIFByb2JhYmlsaXR5IG9mIHYxIGJldHRlciB0aGFuIHYyOgpwcmludChwYXN0ZSgnVGhlIHByb2JhYmlsaXR5IG9mIEJUMyBoYXZpbmcgbW9yZSB1c2VyIHJlZ2lzdHJhdGlvbnMgdGhhbiBHSUJfUkcgaXM6ICcsIHBCVDNfR0lCX1JHKSkKIyAtIHYxIENhbXBhaWduIExpZnQKcGVyY2VudERpZmYgPC0gKEJUM1NhbXBsZXMgLSBHSUJfUkdTYW1wbGVzKS9HSUJfUkdTYW1wbGVzKjEwMApwZXJjZW50RGlmZiA8LSBkYXRhLmZyYW1lKHBlcmNlbnREaWZmID0gcGVyY2VudERpZmYsCiAgICAgICAgICAgICAgICAgICAgICAgICAgYXJlYSA9IGlmZWxzZShwZXJjZW50RGlmZiA8PSAwLCAnPD0gMCcsICc+IDAnKSwKICAgICAgICAgICAgICAgICAgICAgICAgICBzdHJpbmdzQXNGYWN0b3JzID0gVCkKcHJpbnQocGFzdGUoJ1RoZSBwZXJjZW50IGxpZnQgdGhhdCBCVDMgaGFzIG92ZXIgR0lCX1JHICgnLAogICAgICAgICAgICByb3VuZCh0X3BlcmNlbnREaWZmLCAyKSwKICAgICAgICAgICAgJyUpIGxpZXMgaW4gdGhlIGludGVydmFsICgnLCAKICAgICAgICAgICAgYXMuY2hhcmFjdGVyKHJvdW5kKHF1YW50aWxlKHBlcmNlbnREaWZmJHBlcmNlbnREaWZmLCAuMDI1KSksIDIpLCAKICAgICAgICAgICAgJyUsICcsIAogICAgICAgICAgICBhcy5jaGFyYWN0ZXIocm91bmQocXVhbnRpbGUocGVyY2VudERpZmYkcGVyY2VudERpZmYsIC45NzUpKSwgMiksCiAgICAgICAgICAgICclKSB3aXRoIDk1JSBjZXJ0YWludHkuJywKICAgICAgICAgICAgc2VwID0gIiIpKQpnZ3Bsb3QocGVyY2VudERpZmYsIGFlcyh4ID0gcGVyY2VudERpZmYsCiAgICAgICAgICAgICAgICAgICAgICAgIGZpbGwgPSBhcmVhKSkgKyAKICBnZW9tX2hpc3RvZ3JhbShiaW53aWR0aCA9IC4xLCBhbHBoYSA9IC41KSArIAogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBjb21tYSkgKwogIHhsYWIoJyhCVDMgLSBHSUJfUkcpL0dJQl9SRycpICsgeWxhYignRGVuc2l0eScpICsgCiAgZ2d0aXRsZSgnQlQzL0dJQl9SRyBDYW1wYWlnbiBMaWZ0JykgKwogIHRoZW1lX21pbmltYWwoKSArIAogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSkKYGBgCgojIyMjIyBFdmFsdWF0aW9uOiBHSUJfTFAgdnMuIEdJQl9SRwoKYGBge3IgZWNobyA9IFR9Cm1jTiA8LSAxZTUKR0lCX0xQU2FtcGxlcyA8LSByYmV0YShtY04sIHBvc3RCMkFscGhhLCBwb3N0QjJCZXRhKQpHSUJfUkdTYW1wbGVzIDwtIHJiZXRhKG1jTiwgcG9zdEdJQl9SR0FscGhhLCBwb3N0R0lCX1JHQmV0YSkKdF9wZXJjZW50RGlmZiA8LSAobWVhbihHSUJfTFBTYW1wbGVzKSAtIG1lYW4oR0lCX1JHU2FtcGxlcykpL21lYW4oR0lCX1JHU2FtcGxlcykqMTAwCnBHSUJfTFBfR0lCX1JHIDwtIG1lYW4oKEdJQl9MUFNhbXBsZXMgPiBHSUJfUkdTYW1wbGVzKSkKIyAtIFByb2JhYmlsaXR5IG9mIHYxIGJldHRlciB0aGFuIHYyOgpwcmludChwYXN0ZSgnVGhlIHByb2JhYmlsaXR5IG9mIEdJQl9MUCBoYXZpbmcgbW9yZSB1c2VyIHJlZ2lzdHJhdGlvbnMgdGhhbiBHSUJfUkcgaXM6ICcsIHBHSUJfTFBfR0lCX1JHKSkKIyAtIHYxIENhbXBhaWduIExpZnQKcGVyY2VudERpZmYgPC0gKEdJQl9MUFNhbXBsZXMgLSBHSUJfUkdTYW1wbGVzKS9HSUJfUkdTYW1wbGVzKjEwMApwZXJjZW50RGlmZiA8LSBkYXRhLmZyYW1lKHBlcmNlbnREaWZmID0gcGVyY2VudERpZmYsCiAgICAgICAgICAgICAgICAgICAgICAgICAgYXJlYSA9IGlmZWxzZShwZXJjZW50RGlmZiA8PSAwLCAnPD0gMCcsICc+IDAnKSwKICAgICAgICAgICAgICAgICAgICAgICAgICBzdHJpbmdzQXNGYWN0b3JzID0gVCkKcHJpbnQocGFzdGUoJ1RoZSBwZXJjZW50IGxpZnQgdGhhdCBHSUJfTFAgaGFzIG92ZXIgR0lCX1JHICgnLAogICAgICAgICAgICByb3VuZCh0X3BlcmNlbnREaWZmLCAyKSwKICAgICAgICAgICAgJyUpIGxpZXMgaW4gdGhlIGludGVydmFsICgnLCAKICAgICAgICAgICAgYXMuY2hhcmFjdGVyKHJvdW5kKHF1YW50aWxlKHBlcmNlbnREaWZmJHBlcmNlbnREaWZmLCAuMDI1KSksIDIpLCAKICAgICAgICAgICAgJyUsICcsIAogICAgICAgICAgICBhcy5jaGFyYWN0ZXIocm91bmQocXVhbnRpbGUocGVyY2VudERpZmYkcGVyY2VudERpZmYsIC45NzUpKSwgMiksCiAgICAgICAgICAgICclKSB3aXRoIDk1JSBjZXJ0YWludHkuJywKICAgICAgICAgICAgc2VwID0gIiIpKQpnZ3Bsb3QocGVyY2VudERpZmYsIGFlcyh4ID0gcGVyY2VudERpZmYsCiAgICAgICAgICAgICAgICAgICAgICAgIGZpbGwgPSBhcmVhKSkgKyAKICBnZW9tX2hpc3RvZ3JhbShiaW53aWR0aCA9IC4xLCBhbHBoYSA9IC41KSArIAogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBjb21tYSkgKwogIHhsYWIoJyhHSUJfTFAgLSBHSUJfUkcpL0dJQl9SRycpICsgeWxhYignRGVuc2l0eScpICsgCiAgZ2d0aXRsZSgnR0lCX0xQL0dJQl9SRyBDYW1wYWlnbiBMaWZ0JykgKwogIHRoZW1lX21pbmltYWwoKSArIAogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSkKYGBgCgojIyMjIDUuMUIgVXNlciBFZGl0cwoKIyMjIyMgU3VtbWFyeQoKV2hhdCB3ZSBrbm93ICphbG1vc3QgY2VydGFpbmx5KiBpcyB0aGF0LCBpbiBnZW5lcmFsLCB0aGUgYEJUYCBiYW5uZXJzIGRvbWluYXRlIHRoZSBgR0lCX0xQYCBhbmQgYEdJQl9SR2AgYmFubmVycyBpbiB0ZXJtcyBvZiB0aGUgKmV4cGVjdGVkIG51bWJlciBvZiB1c2VyIGVkaXRzKi4gVGhlIGBCVDFgIGFuZCBgQlQzYCBiYW5uZXJzIGJlYXQgdGhlIGBCVDJgIGJhbm5lciBpbiB0aGlzIHJlc3BlY3QsIHdoaWxlIHRoZSBmaW5kaW5nIG9uIHRoZSBkaWZmZXJlbmNlIGJldHdlZW4gYEJUMWAgYW5kIGBCVDNgIGlzIGluY29uY2x1c2l2ZS4gYEJUMmAgaXMgdGhlIG9ubHkgYmFubmVyIGZyb20gdGhlIGBCVGAgZ3JvdXAgdGhhdCBwZXJmb3JtcyB3b3JzZSB0aGFuIHRoZSBgR0lCX0xQYCBhbmQgYEdJQl9SR2AgYmFubmVycyBpbiB0aGlzIHJlc3BlY3QuIEFsc28sIHRoZSBgR0lCX0xQYCBhbmQgYEdJQl9SR2AgYmFubmVycyBkbyBub3QgZGlmZmVyIHNpZ25maWNhbnRseSBpbiB0aGUgbnVtYmVyIG9mIGV4cGVjdGVkIHVzZXIgZWRpdHMgdGhhdCB0aGV5IGluZmx1ZW5jZS4KCioqTk9URS4qKiBJIHVzZSB0aGUgdGVybSBgY2FtcGFpZ24gbGlmdGAgaW4gdGhlIGZvbGxvd2luZyBzZWN0aW9ucyB0byByZWZlciB0byBhIGRpZmZlcmVuY2UgaW4gdGhlIHByb2JhYmlsaXR5IG9mIHVzZXIgcmVnaXN0cmF0aW9uIGluIHJlc3BlY3QgdG8gKmFueSogcGFpciBvZiBiYW5uZXJzIHRoYXQgd2VyZSB1c2VkIGR1cmluZyB0aGUgYEFCQzIwMTdgIGNhbXBhaWduLiBUaGVvcmV0aWNhbGx5LCBhIGNhbXBhaWduIGxpZnQgd291bGQgYmUgdGhlIGRpZmZlcmVuY2UgKGluIHNvbWUgS1BJKSBiZXR3ZWVuIGEgZ3JvdXAgb2YgdXNlcnMgd2hvIHdlcmUgZXhwb3NlZCB0byB0aGUgY2FtcGFpZ24gYW5kIHRoZSBjb250cm9sIGdyb3VwIChubyBleHBvc3VyZSkuIElmIHdlIHdvdWxkIGFzc2VzcyB0aGUgYEFCQzIwMTdgIGNhbXBhaWduIGluIHRoaXMgbWFubmVyIHRoZW4gaXQgd291bGQgYmUgbmF0dXJhbCB0byB0YWtlIGBHSUJfUkdgIGFzIGEgY29udHJvbCwgYW5kIGNvbXBhcmUgYWxsIG90aGVyIGdyb3VwcyAoYEJUMWAsIGBCVDJgLCBgQlQzYCwgYW5kIGBHSUJfTFBgKSB0byBpdDsgaW4gdGhhdCBjYXNlLCB3ZSB3b3VsZCBvYnNlcnZlIHRoYXQgYEFCQzIwMTdgIGNhbXBhaWduIGhhZCBhIGxpZnQgaW4gcmVzcGVjdCB0byB0aGUgY29udHJvbCBncm91cCAqKmluIHRlcm1zIG9mIHRoZSBleHBlY3RlZCBudW1iZXIgb2YgdXNlciBlZGl0cyoqLgoKVGhlIGZvbGxvd2luZyBzZWN0aW9ucyBwcm92aWRlIHRoZSByZXN1bHRzIG9mIHBhaXJ3aXNlIEJheWVzaWFuIEEvQiB0ZXN0cyBhY3Jvc3MgdGhlIGNhbXBhaWduIGJhbm5lcnMuICoqVEVDSE5JQ0FMIE5PVEUuKiogVW5pZm9ybSBgRGlyaWNobGV0KClgIHByaW9ycyB3aXRoIGEgY29uY2VudHJhdGlvbiBwYXJhbWV0ZXIgb2YgYDFgIHdlcmUgdXNlZCB0byBkZXJpdmUgdGhlIGV4cGVjdGVkIHVzZXIgZWRpdCBkaXN0cmlidXRpb25zLCB3aGlsZSB1bmlmb3JtIGBCZXRhKDEsIDEpYCB1bmluZm9ybWF0aXZlIHByaW9ycyB3ZXJlIHVzZWQgZm9yIEEvQiB0ZXN0aW5nOyBgMSwwMDAsMDAwYCBNb250ZSBDYXJsbyBzYW1wbGVzIGZyb20gdGhlIHBvc3RlcmlvcnMgd2VyZSB1c2VkLgoKUHJlcGFyZSBwcmlvcnMsIGRhdGEsIGFuZCB0ZXN0IGZ1bmN0aW9ucy4KCmBgYHtyIGVjaG8gPSBULCB3YXJuaW5nID0gJ2hpZGUnLCBtZXNzYWdlID0gRn0KIyAtIE51bWJlciBvZiBNb250ZSBDYXJsbyBzYW1wbGVzOgptY04gPC0gMWU2CgojIC0gdGhlIGRhdGFzZXQKZWREYXRhIDwtIGxlZnRfam9pbihlZGl0RGF0YSwgdXNlclJlZywgCiAgICAgICAgICAgICAgICAgICAgYnkgPSBjKCJyZXZfdXNlciIgPSAiZXZlbnRfdXNlcklkIikpICU+JSAKICBncm91cF9ieShldmVudF9jYW1wYWlnbiwgZWRpdHMpICU+JSAKICBzdW1tYXJpc2UoQ291bnQgPSBuKCkpCmNvbG5hbWVzKGVkRGF0YSkgPC0gYygnQ2FtcGFpZ24nLCAnRWRpdHMnLCAnQ291bnQnKQoKIyAtIGVkRGF0YSRNYXRjaAplZERhdGEkTWF0Y2ggPC0gZWREYXRhJEVkaXRzCgojIC0gbWF4LiBvYnNlcnZlZCBFZGl0czoKbWF4RWRpdHMgPC0gbWF4KGVkRGF0YSRFZGl0cykKCiMgLSBmaWxsIGluIG1pc3NpbmcgZWRpdHMKY2FtcGFpZ25zIDwtIHVuaXF1ZShlZERhdGEkQ2FtcGFpZ24pCm5DYW1wYWlnbnMgPC0gbGVuZ3RoKGNhbXBhaWducykKY2FtcGFpZ25zIDwtIHVubGlzdChsYXBwbHkoY2FtcGFpZ25zLCBmdW5jdGlvbih4KXsKICByZXR1cm4ocmVwKHgsIG1heEVkaXRzICsgMSkpCn0pKQplZERhdGFDb3B5IDwtIGRhdGEuZnJhbWUoQ2FtcGFpZ24gPSBjYW1wYWlnbnMsIAogICAgICAgICAgICAgICAgICAgICAgICAgTWF0Y2ggPSByZXAoc2VxKDAsIG1heEVkaXRzLCBieSA9IDEpLCBuQ2FtcGFpZ25zKSwKICAgICAgICAgICAgICAgICAgICAgICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGKQplZERhdGFDb3B5IDwtIGxlZnRfam9pbihlZERhdGFDb3B5LCBlZERhdGEsIAogICAgICAgICAgICAgICAgICAgICAgICBieSA9IGMoIkNhbXBhaWduIiA9ICJDYW1wYWlnbiIsICJNYXRjaCIgPSAiTWF0Y2giKQogICAgICAgICAgICAgICAgICAgICAgICApCmVkRGF0YSA8LSBlZERhdGFDb3B5CnJtKGVkRGF0YUNvcHkpOwplZERhdGEkQ291bnRbaXMubmEoZWREYXRhJENvdW50KV0gPC0gMAplZERhdGEkRWRpdHMgPC0gZWREYXRhJE1hdGNoCmVkRGF0YSRNYXRjaCA8LSBOVUxMCgojIC0gYmFubmVyIGVkaXQgcHJvYmFiaWxpdHkKYmFubmVyRWRQcm9iIDwtIGVkRGF0YSAlPiUgCiAgZ3JvdXBfYnkoQ2FtcGFpZ24pICU+JSAKICBtdXRhdGUoUHJvYiA9IENvdW50L3N1bShDb3VudCkpICU+JSAKICBtdXRhdGUoRXhwZWN0ID0gUHJvYiAqIEVkaXRzKQpiYW5uZXJFZFByb2IkQ2FtcGFpZ24gPC0gdG91cHBlcihnc3ViKCJ3bWRlX2FiYzIwMTdfIiwgIiIsIGJhbm5lckVkUHJvYiRDYW1wYWlnbiwgZml4ZWQgPSBUKSkKCiMgLSB0cnVlIGV4cGVjdGVkIGVkaXQgcGVyIGJhbm5lcgpjYW1wYWlnblRFUiA8LSBlZERhdGEgJT4lIAogIG11dGF0ZShFeCA9IEVkaXRzICogQ291bnQpICU+JQogIGdyb3VwX2J5KENhbXBhaWduKSAlPiUgCiAgc3VtbWFyaXNlKFRFUiA9IEVkaXRzICUqJSAoQ291bnQvc3VtKENvdW50KSksIFNEID0gc2QoRXgpKQpjYW1wYWlnblRFUiRDYW1wYWlnbiA8LSB0b3VwcGVyKGdzdWIoIndtZGVfYWJjMjAxN18iLCAiIiwgY2FtcGFpZ25URVIkQ2FtcGFpZ24sIGZpeGVkID0gVCkpCgojIC0gTiB1c2VyIHJlZ2lzdHJhdGlvbnMgcGVyIEJhbm5lcgpiYW5uZXJOVXNlciA8LSByZWdQbG90U2V0ICU+JSAKICBncm91cF9ieShDYW1wYWlnbikgJT4lIAogIHN1bW1hcmlzZShSZWdpc3RyYXRpb24gPSBzdW0oUmVnaXN0cmF0aW9ucykpCgojIC0gcG9zdGVyaW9yIGV4cGVjdGVkIGVkaXQgc2FtcGxlczoKcG9zdGVyaW9yRUVkaXRTYW1wbGUgPC0gZnVuY3Rpb24oYWxwaGEsIGNvdW50cywgdmFsdWVzLCBzYW1wbGVzKSB7CiAgZGlyaWNobGV0U2FtcGxlIDwtIHJkaXJpY2hsZXQoc2FtcGxlcywgY291bnRzICsgYWxwaGEpCiAgZGlyaWNobGV0U2FtcGxlICUqJSB2YWx1ZXMKfQoKIyAtIHBvc3RlcmlvciBleHBlY3RlZCBlZGl0IEEvQiB0ZXN0Ogpwb3N0ZXJpb3JfRUVkaXRfQUIgPC0gZnVuY3Rpb24oZGF0YSwgY2FtcGFpZ25BLCBjYW1wYWlnbkIsIG1jTikgewogIGlmICghKGNhbXBhaWduQSAlaW4lIHVuaXF1ZShkYXRhJENhbXBhaWduKSkgfCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIShjYW1wYWlnbkIgJWluJSB1bmlxdWUoZGF0YSRDYW1wYWlnbikpKSB7CiAgICBzdG9wKCJDYW1wYWlnbiBub3QgZm91bmQuIiwgCiAgICAgICAgIGNhbGwuID0gVFJVRSkKICB9IGVsc2UgewogICAgCiAgICAjIC0gcHJlcGFyZSByZXM6CiAgICByZXMgPC0gbGlzdCgpCiAgICAKICAgICMgLSBVbmluZm9ybWF0aXZlIHByaW9ycwogICAgcHJpb3JBIDwtIHJlcCgxLCBsZW5ndGgod2hpY2goZGF0YSRDYW1wYWlnbiAlaW4lIGNhbXBhaWduQSkpKQogICAgcHJpb3JCIDwtIHJlcCgxLCBsZW5ndGgod2hpY2goZGF0YSRDYW1wYWlnbiAlaW4lIGNhbXBhaWduQikpKQogICAgCiAgICAjIC0gU2ltdWxhdGUgYmFubmVyczoKICAgIGNvdW50c0EgPC0gZGF0YSRDb3VudFt3aGljaChkYXRhJENhbXBhaWduICVpbiUgY2FtcGFpZ25BKV0KICAgIGNvdW50c0IgPC0gZGF0YSRDb3VudFt3aGljaChkYXRhJENhbXBhaWduICVpbiUgY2FtcGFpZ25CKV0KICAgIAogICAgIyAtIGVkaXQgdmFsdWVzOgogICAgZWRpdFZhbHVlc0EgPC0gZGF0YSRFZGl0c1t3aGljaChkYXRhJENhbXBhaWduICVpbiUgY2FtcGFpZ25BKV0KICAgIGVkaXRWYWx1ZXNCIDwtIGRhdGEkRWRpdHNbd2hpY2goZGF0YSRDYW1wYWlnbiAlaW4lIGNhbXBhaWduQildCiAgICAKICAgICMgLSBwb3N0ZXJpb3IgZXhwZWN0ZWQgZWRpdHM6CiAgICBwb3N0ZXJpb3JBIDwtIHBvc3RlcmlvckVFZGl0U2FtcGxlKGFscGhhID0gcHJpb3JBLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb3VudHMgPSBjb3VudHNBLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB2YWx1ZXMgPSBlZGl0VmFsdWVzQSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2FtcGxlcyA9IG1jTikgCiAgICBwb3N0ZXJpb3JBIDwtIGRhdGEuZnJhbWUocG9zdGVyaW9yID0gcG9zdGVyaW9yQSkKICAgIHBvc3RlcmlvckIgPC0gcG9zdGVyaW9yRUVkaXRTYW1wbGUoYWxwaGEgPSBwcmlvckIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvdW50cyA9IGNvdW50c0IsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZhbHVlcyA9IGVkaXRWYWx1ZXNCLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzYW1wbGVzID0gbWNOKSAKICAgIHBvc3RlcmlvckIgPC0gZGF0YS5mcmFtZShwb3N0ZXJpb3IgPSBwb3N0ZXJpb3JCKQogICAgCiAgICAjIC0gcmVzOiBwb3N0ZXJpb3JzCiAgICByZXMkcG9zdGVyaW9yQSA8LSBwb3N0ZXJpb3JBCiAgICByZXMkcG9zdGVyaW9yQiA8LSBwb3N0ZXJpb3JCCgogICAgIyAtIHJlczogcHJvYmFiaWxpdHkgQS9CCiAgICByZXMkcHJvYmFiaWxpdHkgPC0gbWVhbihwb3N0ZXJpb3JBID4gcG9zdGVyaW9yQikKICAgIAogICAgIyAtIHJlczogcGVyY2VudCBkaWZmZXJlbmNlIChjYW1wYWlnbiBMaWZ0KQogICAgcmVzJHBlcmNlbnREaWZmIDwtIChwb3N0ZXJpb3JBJHBvc3RlcmlvciAtIHBvc3RlcmlvckIkcG9zdGVyaW9yKS9wb3N0ZXJpb3JCJHBvc3RlcmlvcioxMDAKICAgIGFyZWEgPSBpZmVsc2UocmVzJHBlcmNlbnREaWZmIDw9IDAsICc8PSAwJywgJz4gMCcpCiAgICByZXMkcGVyY2VudERpZmYgPC0gZGF0YS5mcmFtZShwZXJjZW50RGlmZiA9IHJlcyRwZXJjZW50RGlmZiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFyZWEgPSBhcmVhLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RyaW5nc0FzRmFjdG9ycyA9IFQpCiAKICAgICMgLSByZXM6IHRydWUgcGVyY2VudCBkaWZmZXJlbmNlOgogICAgcmVzJHRfcGVyY2VudERpZmYgPC0gCiAgICAgIChtZWFuKHJlcyRwb3N0ZXJpb3JBJHBvc3RlcmlvcikgLSBtZWFuKHJlcyRwb3N0ZXJpb3JCJHBvc3RlcmlvcikpL21lYW4ocmVzJHBvc3RlcmlvckIkcG9zdGVyaW9yKSoxMDAKICAgIAogICAgIyAtIG91dDoKICAgIHJldHVybihyZXMpCiAgICAKICB9Cn0KYGBgCgojIyMjIyBFeHBlY3RlZCBVc2VyIEVkaXRzIHBlciBDYW1wYWlnbgoKVGhlIGF2ZXJhZ2UgbnVtYmVyIG9mIGVkaXRzIHBlciB1c2VyIHZzLiB0aGUgY2FtcGFpZ24gdmlhIHRoZXkgaGF2ZSByZWdpc3RlcmVkOgoKYGBge3IgZWNobyA9IFQsIHdhcm5pbmcgPSAnaGlkZScsIG1lc3NhZ2UgPSBGfQpjYW1wYWlnblRFUiRDYW1wYWlnbiA8LSBmYWN0b3IoY2FtcGFpZ25URVIkQ2FtcGFpZ24sIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxldmVscyA9IG5hbWVzKGNhbXBhaWduQ2hhcnRDb2xvcnMpKQpjb2xuYW1lcyhjYW1wYWlnblRFUikgPC0gYygnQ2FtcGFpZ24nLCAnRXhwZWN0ZWQnLCAnUy5ELicpCmdncGxvdChjYW1wYWlnblRFUiwgYWVzKHggPSBDYW1wYWlnbiwKICAgICAgICAgICAgICAgICAgICAgICAgeSA9IEV4cGVjdGVkLAogICAgICAgICAgICAgICAgICAgICAgICBmaWxsID0gQ2FtcGFpZ24sCiAgICAgICAgICAgICAgICAgICAgICAgIGNvbG9yID0gQ2FtcGFpZ24sIAogICAgICAgICAgICAgICAgICAgICAgICBsYWJlbCA9IHJvdW5kKEV4cGVjdGVkLCAyKSkpICsKICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IiwgCiAgICAgICAgICAgcG9zaXRpb24gPSAiZG9kZ2UiLCAKICAgICAgICAgICB3aWR0aCA9IC41KSArCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IGNvbW1hKSArIAogIHNjYWxlX2ZpbGxfbWFudWFsKCJsZWdlbmQiLCB2YWx1ZXMgPSBjYW1wYWlnbkNoYXJ0Q29sb3JzKSArIAogIHNjYWxlX2NvbG9yX21hbnVhbCgibGVnZW5kIiwgdmFsdWVzID0gY2FtcGFpZ25DaGFydENvbG9ycykgKyAKICBnZW9tX2xhYmVsKGZpbGwgPSAid2hpdGUiLCBjb2xvciA9ICJibGFjayIpICsKICBnZ3RpdGxlKCdBdXR1bW4gQmFubmVyIENhbXBhaWduIDIwMTc6IFVzZXIgRWRpdHMgcGVyIENhbXBhaWduJykgKwogIHRoZW1lX21pbmltYWwoKSArIAogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gMCwgc2l6ZSA9IDgsIGhqdXN0ID0gMSkpICsKICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCkpICsKICB0aGVtZShsZWdlbmQudGl0bGUgPSBlbGVtZW50X2JsYW5rKCkpICsKICB0aGVtZShwYW5lbC5ncmlkLm1ham9yLnggPSBlbGVtZW50X2JsYW5rKCkpICsKICB0aGVtZShwYW5lbC5ncmlkLm1pbm9yLnggPSBlbGVtZW50X2JsYW5rKCkpICsKICB0aGVtZShwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpKQpgYGAKCkV4cGVjdGVkIGVkaXRzIGFuZCB0aGUgcmVzcGVjdGl2ZSBzdGFuZGFyZCBkZXZpYXRpb25zOgoKYGBge3IgZWNobyA9IFQsIHdhcm5pbmcgPSAnaGlkZScsIG1lc3NhZ2UgPSBGfQpjYW1wYWlnblRFUiRFeHBlY3RlZCA8LSByb3VuZChjYW1wYWlnblRFUiRFeHBlY3RlZCwgMikKY2FtcGFpZ25URVIkYFMuRC5gIDwtIHJvdW5kKGNhbXBhaWduVEVSJGBTLkQuYCwgMikKa25pdHI6OmthYmxlKGNhbXBhaWduVEVSLCBmb3JtYXQgPSAiaHRtbCIpICU+JSAKICBrYWJsZV9zdHlsaW5nKGZ1bGxfd2lkdGggPSBGLCBwb3NpdGlvbiA9ICJsZWZ0IikKYGBgCgpMZXQncyB0YWtlIGEgY2xvc2VyIGxvb2sgdXBvbiB0aGUgZGlzdHJpYnV0aW9ucyBvZiB1c2VyIGVkaXRzIHBlciBjYW1wYWlnbjoKCmBgYHtyIGVjaG8gPSBULCB3YXJuaW5nID0gJ2hpZGUnLCBtZXNzYWdlID0gRn0KIyAtIHRoZSBkYXRhc2V0CmVkRGF0YURpc3QgPC0gbGVmdF9qb2luKGVkaXREYXRhLCB1c2VyUmVnLAogICAgICAgICAgICAgICAgICAgICAgICBieSA9IGMoInJldl91c2VyIiA9ICJldmVudF91c2VySWQiKSkgJT4lIAogIGRwbHlyOjpzZWxlY3QoZXZlbnRfY2FtcGFpZ24sIGVkaXRzKQpjb2xuYW1lcyhlZERhdGFEaXN0KSA8LSBjKCdDYW1wYWlnbicsICdFZGl0cycpCmVkRGF0YURpc3QkQ2FtcGFpZ24gPC0gdG91cHBlcihnc3ViKCJ3bWRlX2FiYzIwMTdfIiwgIiIsIGVkRGF0YURpc3QkQ2FtcGFpZ24sIGZpeGVkID0gVCkpCmVkRGF0YURpc3QkQWxwaGEgPSBlZERhdGFEaXN0JEVkaXRzL21heChlZERhdGFEaXN0JEVkaXRzKQpnZ3Bsb3QoZWREYXRhRGlzdCwgYWVzKHggPSBDYW1wYWlnbiwgeSA9IEVkaXRzLCAKICAgICAgICAgICAgICAgICAgICAgICBncm91cCA9IENhbXBhaWduLCAKICAgICAgICAgICAgICAgICAgICAgICBmaWxsID0gQ2FtcGFpZ24sCiAgICAgICAgICAgICAgICAgICAgICAgY29sb3IgPSBDYW1wYWlnbikpICsgCiAgZ2VvbV9wb2ludChhZXMoYWxwaGEgPSBlZERhdGFEaXN0JEFscGhhKSwgCiAgICAgICAgICAgICBwb3NpdGlvbiA9ICJqaXR0ZXIiLCAKICAgICAgICAgICAgIHNpemUgPSAxLjUpICsgCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IGNvbW1hKSArIAogIHNjYWxlX2ZpbGxfbWFudWFsKCJsZWdlbmQiLCB2YWx1ZXMgPSBjYW1wYWlnbkNoYXJ0Q29sb3JzKSArIAogIHNjYWxlX2NvbG9yX21hbnVhbCgibGVnZW5kIiwgdmFsdWVzID0gY2FtcGFpZ25DaGFydENvbG9ycykgKyAKICBnZ3RpdGxlKCdBdXR1bW4gQmFubmVyIENhbXBhaWduIDIwMTc6IFVzZXIgRWRpdHMgRGlzdHJpYnV0aW9ucyBwZXIgQ2FtcGFpZ24nKSArCiAgdGhlbWVfbWluaW1hbCgpICsgCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSAwLCBzaXplID0gOCwgaGp1c3QgPSAxKSkgKwogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSkgKwogIHRoZW1lKGxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSkgKwogIHRoZW1lKHBhbmVsLmdyaWQubWFqb3IueCA9IGVsZW1lbnRfYmxhbmsoKSkgKwogIHRoZW1lKHBhbmVsLmdyaWQubWlub3IueCA9IGVsZW1lbnRfYmxhbmsoKSkgKwogIHRoZW1lKHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCkpICsgCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gJ05vbmUnKQpgYGAKCk5vdGUgdGhhdCBub3QgdG9vIG1hbnkgbmV3IHVzZXJzIGhhdmUgbWFkZSBhbnkgc2lnbmlmaWNhbnQgbnVtYmVyIG9mIGVkaXRzLiBUaGlzIGZhY3QgLSB0aGUgc2NhcmNpdHkgb2YgYXZpbGFibGUgZGF0YSAtIGltcG9zZXMgc2V2ZXJhbCBjb25zdHJhaW50cyB1cG9uIHRoZSBwcmVzZW50IGFuYWx5c2lzLiBQbGVhc2UgcmVhZCB0aHJvdWdoIGNhcmVmdWxseSBhbmQgZG8gbm90IGp1bXAgdG8gY29uY2x1c2lvbnMgYmVmb3JlIG1vcmUgZGF0YSBiZWNvbWUgYXZhaWxhYmxlLgoKIyMjIyMgRXZhbHVhdGlvbjogQlQxIHZzLiBCVDIKCmBgYHtyIGVjaG8gPSBULCB3YXJuaW5nID0gJ2hpZGUnLCBtZXNzYWdlID0gRn0KY2FtcEEgPC0gJ0JUMScKY2FtcEIgPC0gJ0JUMicKdGVzdEFCIDwtIHBvc3Rlcmlvcl9FRWRpdF9BQihiYW5uZXJFZFByb2IsIGNhbXBBLCBjYW1wQiwgbWNOID0gbWNOKQojIC0gUHJvYmFiaWxpdHkgb2YgY2FtcEEgYmV0dGVyIHRoYW4gY2FtcEI6CnByaW50KHBhc3RlKCdUaGUgcHJvYmFiaWxpdHkgb2YgJywgCiAgICAgICAgICAgIGNhbXBBLCAgCiAgICAgICAgICAgICcgaW5mbHVlbmNpbmcgbW9yZSB1c2VyIGVkaXRzIHRoYW4gJywgCiAgICAgICAgICAgIGNhbXBCLCAnIGlzIDogJywgCiAgICAgICAgICAgIHJvdW5kKHRlc3RBQiRwcm9iYWJpbGl0eSwgMiksIAogICAgICAgICAgICBzZXAgPSAiIikpCiMgLSBsaWZ0OgpwcmludChwYXN0ZSgnVGhlIHBlcmNlbnQgbGlmdCB0aGF0ICcsIAogICAgICAgICAgICBjYW1wQSwgCiAgICAgICAgICAgICcgaGFzIG92ZXIgJywgCiAgICAgICAgICAgIGNhbXBCLCAKICAgICAgICAgICAgJyAoJywKICAgICAgICAgICAgcm91bmQodGVzdEFCJHRfcGVyY2VudERpZmYsIDIpLAogICAgICAgICAgICAnJSkgbGllcyBpbiB0aGUgaW50ZXJ2YWwgKCcsIAogICAgICAgICAgICBhcy5jaGFyYWN0ZXIocm91bmQocXVhbnRpbGUodGVzdEFCJHBlcmNlbnREaWZmJHBlcmNlbnREaWZmLCAuMDI1KSksIDIpLCAKICAgICAgICAgICAgJyUsICcsIAogICAgICAgICAgICBhcy5jaGFyYWN0ZXIocm91bmQocXVhbnRpbGUodGVzdEFCJHBlcmNlbnREaWZmJHBlcmNlbnREaWZmLCAuOTc1KSksIDIpLAogICAgICAgICAgICAnJSkgd2l0aCA5NSUgY2VydGFpbnR5LicsCiAgICAgICAgICAgIHNlcCA9ICIiKSkKZ2dwbG90KHRlc3RBQiRwZXJjZW50RGlmZiwgCiAgICAgICBhZXMoeCA9IHBlcmNlbnREaWZmLAogICAgICAgICAgIGdyb3VwID0gYXJlYSwKICAgICAgICAgICBmaWxsID0gYXJlYSkpICsgCiAgZ2VvbV9oaXN0b2dyYW0oYmlucyA9IDEwMDAsIGFscGhhID0gLjUpICsgCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IGNvbW1hKSArCiAgeGxhYihwYXN0ZSgnKCcsIGNhbXBBLCAnLScsIGNhbXBCLCAnKS8nLCBjYW1wQiwgc2VwID0gIiIpKSArIHlsYWIoJ0RlbnNpdHknKSArIAogIGdndGl0bGUocGFzdGUoY2FtcEEsICcvJywgY2FtcEIsICcgQ2FtcGFpZ24gTGlmdCcsIHNlcCA9ICIiKSkgKwogIHRoZW1lX21pbmltYWwoKSArIAogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSkKYGBgCgojIyMjIyBFdmFsdWF0aW9uOiBCVDEgdnMuIEJUMwoKYGBge3IgZWNobyA9IFQsIHdhcm5pbmcgPSAnaGlkZScsIG1lc3NhZ2UgPSBGfQpjYW1wQSA8LSAnQlQxJwpjYW1wQiA8LSAnQlQzJwp0ZXN0QUIgPC0gcG9zdGVyaW9yX0VFZGl0X0FCKGJhbm5lckVkUHJvYiwgY2FtcEEsIGNhbXBCLCBtY04gPSBtY04pCiMgLSBQcm9iYWJpbGl0eSBvZiBjYW1wQSBiZXR0ZXIgdGhhbiBjYW1wQjoKcHJpbnQocGFzdGUoJ1RoZSBwcm9iYWJpbGl0eSBvZiAnLCAKICAgICAgICAgICAgY2FtcEEsICAKICAgICAgICAgICAgJyBpbmZsdWVuY2luZyBtb3JlIHVzZXIgZWRpdHMgdGhhbiAnLCAKICAgICAgICAgICAgY2FtcEIsICcgaXMgOiAnLCAKICAgICAgICAgICAgcm91bmQodGVzdEFCJHByb2JhYmlsaXR5LCAyKSwgCiAgICAgICAgICAgIHNlcCA9ICIiKSkKIyAtIGxpZnQ6CnByaW50KHBhc3RlKCdUaGUgcGVyY2VudCBsaWZ0IHRoYXQgJywgCiAgICAgICAgICAgIGNhbXBBLCAKICAgICAgICAgICAgJyBoYXMgb3ZlciAnLCAKICAgICAgICAgICAgY2FtcEIsIAogICAgICAgICAgICAnICgnLAogICAgICAgICAgICByb3VuZCh0ZXN0QUIkdF9wZXJjZW50RGlmZiwgMiksCiAgICAgICAgICAgICclKSBsaWVzIGluIHRoZSBpbnRlcnZhbCAoJywgCiAgICAgICAgICAgIGFzLmNoYXJhY3Rlcihyb3VuZChxdWFudGlsZSh0ZXN0QUIkcGVyY2VudERpZmYkcGVyY2VudERpZmYsIC4wMjUpKSwgMiksIAogICAgICAgICAgICAnJSwgJywgCiAgICAgICAgICAgIGFzLmNoYXJhY3Rlcihyb3VuZChxdWFudGlsZSh0ZXN0QUIkcGVyY2VudERpZmYkcGVyY2VudERpZmYsIC45NzUpKSwgMiksCiAgICAgICAgICAgICclKSB3aXRoIDk1JSBjZXJ0YWludHkuJywKICAgICAgICAgICAgc2VwID0gIiIpKQpnZ3Bsb3QodGVzdEFCJHBlcmNlbnREaWZmLCAKICAgICAgIGFlcyh4ID0gcGVyY2VudERpZmYsCiAgICAgICAgICAgZ3JvdXAgPSBhcmVhLAogICAgICAgICAgIGZpbGwgPSBhcmVhKSkgKyAKICBnZW9tX2hpc3RvZ3JhbShiaW5zID0gMTAwMCwgYWxwaGEgPSAuNSkgKyAKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gY29tbWEpICsKICB4bGFiKHBhc3RlKCcoJywgY2FtcEEsICctJywgY2FtcEIsICcpLycsIGNhbXBCLCBzZXAgPSAiIikpICsgeWxhYignRGVuc2l0eScpICsgCiAgZ2d0aXRsZShwYXN0ZShjYW1wQSwgJy8nLCBjYW1wQiwgJyBDYW1wYWlnbiBMaWZ0Jywgc2VwID0gIiIpKSArCiAgdGhlbWVfbWluaW1hbCgpICsgCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApKQpgYGAKCiMjIyMjIEV2YWx1YXRpb246IEJUMSB2cy4gR0lCX0xQCgpgYGB7ciBlY2hvID0gVCwgd2FybmluZyA9ICdoaWRlJywgbWVzc2FnZSA9IEZ9CmNhbXBBIDwtICdCVDEnCmNhbXBCIDwtICdHSUJfTFAnCnRlc3RBQiA8LSBwb3N0ZXJpb3JfRUVkaXRfQUIoYmFubmVyRWRQcm9iLCBjYW1wQSwgY2FtcEIsIG1jTiA9IG1jTikKIyAtIFByb2JhYmlsaXR5IG9mIGNhbXBBIGJldHRlciB0aGFuIGNhbXBCOgpwcmludChwYXN0ZSgnVGhlIHByb2JhYmlsaXR5IG9mICcsIAogICAgICAgICAgICBjYW1wQSwgIAogICAgICAgICAgICAnIGluZmx1ZW5jaW5nIG1vcmUgdXNlciBlZGl0cyB0aGFuICcsIAogICAgICAgICAgICBjYW1wQiwgJyBpcyA6ICcsIAogICAgICAgICAgICByb3VuZCh0ZXN0QUIkcHJvYmFiaWxpdHksIDIpLCAKICAgICAgICAgICAgc2VwID0gIiIpKQojIC0gbGlmdDoKcHJpbnQocGFzdGUoJ1RoZSBwZXJjZW50IGxpZnQgdGhhdCAnLCAKICAgICAgICAgICAgY2FtcEEsIAogICAgICAgICAgICAnIGhhcyBvdmVyICcsIAogICAgICAgICAgICBjYW1wQiwgCiAgICAgICAgICAgICcgKCcsCiAgICAgICAgICAgIHJvdW5kKHRlc3RBQiR0X3BlcmNlbnREaWZmLCAyKSwKICAgICAgICAgICAgJyUpIGxpZXMgaW4gdGhlIGludGVydmFsICgnLCAKICAgICAgICAgICAgYXMuY2hhcmFjdGVyKHJvdW5kKHF1YW50aWxlKHRlc3RBQiRwZXJjZW50RGlmZiRwZXJjZW50RGlmZiwgLjAyNSkpLCAyKSwgCiAgICAgICAgICAgICclLCAnLCAKICAgICAgICAgICAgYXMuY2hhcmFjdGVyKHJvdW5kKHF1YW50aWxlKHRlc3RBQiRwZXJjZW50RGlmZiRwZXJjZW50RGlmZiwgLjk3NSkpLCAyKSwKICAgICAgICAgICAgJyUpIHdpdGggOTUlIGNlcnRhaW50eS4nLAogICAgICAgICAgICBzZXAgPSAiIikpCmdncGxvdCh0ZXN0QUIkcGVyY2VudERpZmYsIAogICAgICAgYWVzKHggPSBwZXJjZW50RGlmZiwKICAgICAgICAgICBncm91cCA9IGFyZWEsCiAgICAgICAgICAgZmlsbCA9IGFyZWEpKSArIAogIGdlb21faGlzdG9ncmFtKGJpbnMgPSAxMDAwLCBhbHBoYSA9IC41KSArIAogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBjb21tYSkgKwogIHhsYWIocGFzdGUoJygnLCBjYW1wQSwgJy0nLCBjYW1wQiwgJykvJywgY2FtcEIsIHNlcCA9ICIiKSkgKyB5bGFiKCdEZW5zaXR5JykgKyAKICBnZ3RpdGxlKHBhc3RlKGNhbXBBLCAnLycsIGNhbXBCLCAnIENhbXBhaWduIExpZnQnLCBzZXAgPSAiIikpICsKICB0aGVtZV9taW5pbWFsKCkgKyAKICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCkpCmBgYAoKIyMjIyMgRXZhbHVhdGlvbjogQlQxIHZzLiBHSUJfUkcKCmBgYHtyIGVjaG8gPSBULCB3YXJuaW5nID0gJ2hpZGUnLCBtZXNzYWdlID0gRn0KY2FtcEEgPC0gJ0JUMScKY2FtcEIgPC0gJ0dJQl9SRycKdGVzdEFCIDwtIHBvc3Rlcmlvcl9FRWRpdF9BQihiYW5uZXJFZFByb2IsIGNhbXBBLCBjYW1wQiwgbWNOID0gbWNOKQojIC0gUHJvYmFiaWxpdHkgb2YgY2FtcEEgYmV0dGVyIHRoYW4gY2FtcEI6CnByaW50KHBhc3RlKCdUaGUgcHJvYmFiaWxpdHkgb2YgJywgCiAgICAgICAgICAgIGNhbXBBLCAgCiAgICAgICAgICAgICcgaW5mbHVlbmNpbmcgbW9yZSB1c2VyIGVkaXRzIHRoYW4gJywgCiAgICAgICAgICAgIGNhbXBCLCAnIGlzIDogJywgCiAgICAgICAgICAgIHJvdW5kKHRlc3RBQiRwcm9iYWJpbGl0eSwgMiksIAogICAgICAgICAgICBzZXAgPSAiIikpCiMgLSBsaWZ0OgpwcmludChwYXN0ZSgnVGhlIHBlcmNlbnQgbGlmdCB0aGF0ICcsIAogICAgICAgICAgICBjYW1wQSwgCiAgICAgICAgICAgICcgaGFzIG92ZXIgJywgCiAgICAgICAgICAgIGNhbXBCLCAKICAgICAgICAgICAgJyAoJywKICAgICAgICAgICAgcm91bmQodGVzdEFCJHRfcGVyY2VudERpZmYsIDIpLAogICAgICAgICAgICAnJSkgbGllcyBpbiB0aGUgaW50ZXJ2YWwgKCcsIAogICAgICAgICAgICBhcy5jaGFyYWN0ZXIocm91bmQocXVhbnRpbGUodGVzdEFCJHBlcmNlbnREaWZmJHBlcmNlbnREaWZmLCAuMDI1KSksIDIpLCAKICAgICAgICAgICAgJyUsICcsIAogICAgICAgICAgICBhcy5jaGFyYWN0ZXIocm91bmQocXVhbnRpbGUodGVzdEFCJHBlcmNlbnREaWZmJHBlcmNlbnREaWZmLCAuOTc1KSksIDIpLAogICAgICAgICAgICAnJSkgd2l0aCA5NSUgY2VydGFpbnR5LicsCiAgICAgICAgICAgIHNlcCA9ICIiKSkKZ2dwbG90KHRlc3RBQiRwZXJjZW50RGlmZiwgCiAgICAgICBhZXMoeCA9IHBlcmNlbnREaWZmLAogICAgICAgICAgIGdyb3VwID0gYXJlYSwKICAgICAgICAgICBmaWxsID0gYXJlYSkpICsgCiAgZ2VvbV9oaXN0b2dyYW0oYmlucyA9IDEwMDAsIGFscGhhID0gLjUpICsgCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IGNvbW1hKSArCiAgeGxhYihwYXN0ZSgnKCcsIGNhbXBBLCAnLScsIGNhbXBCLCAnKS8nLCBjYW1wQiwgc2VwID0gIiIpKSArIHlsYWIoJ0RlbnNpdHknKSArIAogIGdndGl0bGUocGFzdGUoY2FtcEEsICcvJywgY2FtcEIsICcgQ2FtcGFpZ24gTGlmdCcsIHNlcCA9ICIiKSkgKwogIHRoZW1lX21pbmltYWwoKSArIAogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSkKYGBgCgojIyMjIyBFdmFsdWF0aW9uOiBCVDIgdnMuIEJUMwoKYGBge3IgZWNobyA9IFQsIHdhcm5pbmcgPSAnaGlkZScsIG1lc3NhZ2UgPSBGfQpjYW1wQSA8LSAnQlQyJwpjYW1wQiA8LSAnQlQzJwp0ZXN0QUIgPC0gcG9zdGVyaW9yX0VFZGl0X0FCKGJhbm5lckVkUHJvYiwgY2FtcEEsIGNhbXBCLCBtY04gPSBtY04pCiMgLSBQcm9iYWJpbGl0eSBvZiBjYW1wQSBiZXR0ZXIgdGhhbiBjYW1wQjoKcHJpbnQocGFzdGUoJ1RoZSBwcm9iYWJpbGl0eSBvZiAnLCAKICAgICAgICAgICAgY2FtcEEsICAKICAgICAgICAgICAgJyBpbmZsdWVuY2luZyBtb3JlIHVzZXIgZWRpdHMgdGhhbiAnLCAKICAgICAgICAgICAgY2FtcEIsICcgaXMgOiAnLCAKICAgICAgICAgICAgcm91bmQodGVzdEFCJHByb2JhYmlsaXR5LCAyKSwgCiAgICAgICAgICAgIHNlcCA9ICIiKSkKIyAtIGxpZnQ6CnByaW50KHBhc3RlKCdUaGUgcGVyY2VudCBsaWZ0IHRoYXQgJywgCiAgICAgICAgICAgIGNhbXBBLCAKICAgICAgICAgICAgJyBoYXMgb3ZlciAnLCAKICAgICAgICAgICAgY2FtcEIsIAogICAgICAgICAgICAnICgnLAogICAgICAgICAgICByb3VuZCh0ZXN0QUIkdF9wZXJjZW50RGlmZiwgMiksCiAgICAgICAgICAgICclKSBsaWVzIGluIHRoZSBpbnRlcnZhbCAoJywgCiAgICAgICAgICAgIGFzLmNoYXJhY3Rlcihyb3VuZChxdWFudGlsZSh0ZXN0QUIkcGVyY2VudERpZmYkcGVyY2VudERpZmYsIC4wMjUpKSwgMiksIAogICAgICAgICAgICAnJSwgJywgCiAgICAgICAgICAgIGFzLmNoYXJhY3Rlcihyb3VuZChxdWFudGlsZSh0ZXN0QUIkcGVyY2VudERpZmYkcGVyY2VudERpZmYsIC45NzUpKSwgMiksCiAgICAgICAgICAgICclKSB3aXRoIDk1JSBjZXJ0YWludHkuJywKICAgICAgICAgICAgc2VwID0gIiIpKQpnZ3Bsb3QodGVzdEFCJHBlcmNlbnREaWZmLCAKICAgICAgIGFlcyh4ID0gcGVyY2VudERpZmYsCiAgICAgICAgICAgZ3JvdXAgPSBhcmVhLAogICAgICAgICAgIGZpbGwgPSBhcmVhKSkgKyAKICBnZW9tX2hpc3RvZ3JhbShiaW5zID0gMTAwMCwgYWxwaGEgPSAuNSkgKyAKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gY29tbWEpICsKICB4bGFiKHBhc3RlKCcoJywgY2FtcEEsICctJywgY2FtcEIsICcpLycsIGNhbXBCLCBzZXAgPSAiIikpICsgeWxhYignRGVuc2l0eScpICsgCiAgZ2d0aXRsZShwYXN0ZShjYW1wQSwgJy8nLCBjYW1wQiwgJyBDYW1wYWlnbiBMaWZ0Jywgc2VwID0gIiIpKSArCiAgdGhlbWVfbWluaW1hbCgpICsgCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApKQpgYGAKCiMjIyMjIEV2YWx1YXRpb246IEJUMiB2cy4gR0lCX0xQCgpgYGB7ciBlY2hvID0gVCwgd2FybmluZyA9ICdoaWRlJywgbWVzc2FnZSA9IEZ9CmNhbXBBIDwtICdCVDInCmNhbXBCIDwtICdHSUJfTFAnCnRlc3RBQiA8LSBwb3N0ZXJpb3JfRUVkaXRfQUIoYmFubmVyRWRQcm9iLCBjYW1wQSwgY2FtcEIsIG1jTiA9IG1jTikKIyAtIFByb2JhYmlsaXR5IG9mIGNhbXBBIGJldHRlciB0aGFuIGNhbXBCOgpwcmludChwYXN0ZSgnVGhlIHByb2JhYmlsaXR5IG9mICcsIAogICAgICAgICAgICBjYW1wQSwgIAogICAgICAgICAgICAnIGluZmx1ZW5jaW5nIG1vcmUgdXNlciBlZGl0cyB0aGFuICcsIAogICAgICAgICAgICBjYW1wQiwgJyBpcyA6ICcsIAogICAgICAgICAgICByb3VuZCh0ZXN0QUIkcHJvYmFiaWxpdHksIDIpLCAKICAgICAgICAgICAgc2VwID0gIiIpKQojIC0gbGlmdDoKcHJpbnQocGFzdGUoJ1RoZSBwZXJjZW50IGxpZnQgdGhhdCAnLCAKICAgICAgICAgICAgY2FtcEEsIAogICAgICAgICAgICAnIGhhcyBvdmVyICcsIAogICAgICAgICAgICBjYW1wQiwgCiAgICAgICAgICAgICcgKCcsCiAgICAgICAgICAgIHJvdW5kKHRlc3RBQiR0X3BlcmNlbnREaWZmLCAyKSwKICAgICAgICAgICAgJyUpIGxpZXMgaW4gdGhlIGludGVydmFsICgnLCAKICAgICAgICAgICAgYXMuY2hhcmFjdGVyKHJvdW5kKHF1YW50aWxlKHRlc3RBQiRwZXJjZW50RGlmZiRwZXJjZW50RGlmZiwgLjAyNSkpLCAyKSwgCiAgICAgICAgICAgICclLCAnLCAKICAgICAgICAgICAgYXMuY2hhcmFjdGVyKHJvdW5kKHF1YW50aWxlKHRlc3RBQiRwZXJjZW50RGlmZiRwZXJjZW50RGlmZiwgLjk3NSkpLCAyKSwKICAgICAgICAgICAgJyUpIHdpdGggOTUlIGNlcnRhaW50eS4nLAogICAgICAgICAgICBzZXAgPSAiIikpCmdncGxvdCh0ZXN0QUIkcGVyY2VudERpZmYsIAogICAgICAgYWVzKHggPSBwZXJjZW50RGlmZiwKICAgICAgICAgICBncm91cCA9IGFyZWEsCiAgICAgICAgICAgZmlsbCA9IGFyZWEpKSArIAogIGdlb21faGlzdG9ncmFtKGJpbnMgPSAxMDAwLCBhbHBoYSA9IC41KSArIAogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBjb21tYSkgKwogIHhsYWIocGFzdGUoJygnLCBjYW1wQSwgJy0nLCBjYW1wQiwgJykvJywgY2FtcEIsIHNlcCA9ICIiKSkgKyB5bGFiKCdEZW5zaXR5JykgKyAKICBnZ3RpdGxlKHBhc3RlKGNhbXBBLCAnLycsIGNhbXBCLCAnIENhbXBhaWduIExpZnQnLCBzZXAgPSAiIikpICsKICB0aGVtZV9taW5pbWFsKCkgKyAKICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCkpCmBgYAoKIyMjIyMgRXZhbHVhdGlvbjogQlQyIHZzLiBHSUJfUkcKCmBgYHtyIGVjaG8gPSBULCB3YXJuaW5nID0gJ2hpZGUnLCBtZXNzYWdlID0gRn0KY2FtcEEgPC0gJ0JUMicKY2FtcEIgPC0gJ0dJQl9SRycKdGVzdEFCIDwtIHBvc3Rlcmlvcl9FRWRpdF9BQihiYW5uZXJFZFByb2IsIGNhbXBBLCBjYW1wQiwgbWNOID0gbWNOKQojIC0gUHJvYmFiaWxpdHkgb2YgY2FtcEEgYmV0dGVyIHRoYW4gY2FtcEI6CnByaW50KHBhc3RlKCdUaGUgcHJvYmFiaWxpdHkgb2YgJywgCiAgICAgICAgICAgIGNhbXBBLCAgCiAgICAgICAgICAgICcgaW5mbHVlbmNpbmcgbW9yZSB1c2VyIGVkaXRzIHRoYW4gJywgCiAgICAgICAgICAgIGNhbXBCLCAnIGlzIDogJywgCiAgICAgICAgICAgIHJvdW5kKHRlc3RBQiRwcm9iYWJpbGl0eSwgMiksIAogICAgICAgICAgICBzZXAgPSAiIikpCiMgLSBsaWZ0OgpwcmludChwYXN0ZSgnVGhlIHBlcmNlbnQgbGlmdCB0aGF0ICcsIAogICAgICAgICAgICBjYW1wQSwgCiAgICAgICAgICAgICcgaGFzIG92ZXIgJywgCiAgICAgICAgICAgIGNhbXBCLCAKICAgICAgICAgICAgJyAoJywKICAgICAgICAgICAgcm91bmQodGVzdEFCJHRfcGVyY2VudERpZmYsIDIpLAogICAgICAgICAgICAnJSkgbGllcyBpbiB0aGUgaW50ZXJ2YWwgKCcsIAogICAgICAgICAgICBhcy5jaGFyYWN0ZXIocm91bmQocXVhbnRpbGUodGVzdEFCJHBlcmNlbnREaWZmJHBlcmNlbnREaWZmLCAuMDI1KSksIDIpLCAKICAgICAgICAgICAgJyUsICcsIAogICAgICAgICAgICBhcy5jaGFyYWN0ZXIocm91bmQocXVhbnRpbGUodGVzdEFCJHBlcmNlbnREaWZmJHBlcmNlbnREaWZmLCAuOTc1KSksIDIpLAogICAgICAgICAgICAnJSkgd2l0aCA5NSUgY2VydGFpbnR5LicsCiAgICAgICAgICAgIHNlcCA9ICIiKSkKZ2dwbG90KHRlc3RBQiRwZXJjZW50RGlmZiwgCiAgICAgICBhZXMoeCA9IHBlcmNlbnREaWZmLAogICAgICAgICAgIGdyb3VwID0gYXJlYSwKICAgICAgICAgICBmaWxsID0gYXJlYSkpICsgCiAgZ2VvbV9oaXN0b2dyYW0oYmlucyA9IDEwMDAsIGFscGhhID0gLjUpICsgCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IGNvbW1hKSArCiAgeGxhYihwYXN0ZSgnKCcsIGNhbXBBLCAnLScsIGNhbXBCLCAnKS8nLCBjYW1wQiwgc2VwID0gIiIpKSArIHlsYWIoJ0RlbnNpdHknKSArIAogIGdndGl0bGUocGFzdGUoY2FtcEEsICcvJywgY2FtcEIsICcgQ2FtcGFpZ24gTGlmdCcsIHNlcCA9ICIiKSkgKwogIHRoZW1lX21pbmltYWwoKSArIAogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSkKYGBgCgojIyMjIyBFdmFsdWF0aW9uOiBCVDMgdnMuIEdJQl9MUAoKYGBge3IgZWNobyA9IFQsIHdhcm5pbmcgPSAnaGlkZScsIG1lc3NhZ2UgPSBGfQpjYW1wQSA8LSAnQlQzJwpjYW1wQiA8LSAnR0lCX0xQJwp0ZXN0QUIgPC0gcG9zdGVyaW9yX0VFZGl0X0FCKGJhbm5lckVkUHJvYiwgY2FtcEEsIGNhbXBCLCBtY04gPSBtY04pCiMgLSBQcm9iYWJpbGl0eSBvZiBjYW1wQSBiZXR0ZXIgdGhhbiBjYW1wQjoKcHJpbnQocGFzdGUoJ1RoZSBwcm9iYWJpbGl0eSBvZiAnLCAKICAgICAgICAgICAgY2FtcEEsICAKICAgICAgICAgICAgJyBpbmZsdWVuY2luZyBtb3JlIHVzZXIgZWRpdHMgdGhhbiAnLCAKICAgICAgICAgICAgY2FtcEIsICcgaXMgOiAnLCAKICAgICAgICAgICAgcm91bmQodGVzdEFCJHByb2JhYmlsaXR5LCAyKSwgCiAgICAgICAgICAgIHNlcCA9ICIiKSkKIyAtIGxpZnQ6CnByaW50KHBhc3RlKCdUaGUgcGVyY2VudCBsaWZ0IHRoYXQgJywgCiAgICAgICAgICAgIGNhbXBBLCAKICAgICAgICAgICAgJyBoYXMgb3ZlciAnLCAKICAgICAgICAgICAgY2FtcEIsIAogICAgICAgICAgICAnICgnLAogICAgICAgICAgICByb3VuZCh0ZXN0QUIkdF9wZXJjZW50RGlmZiwgMiksCiAgICAgICAgICAgICclKSBsaWVzIGluIHRoZSBpbnRlcnZhbCAoJywgCiAgICAgICAgICAgIGFzLmNoYXJhY3Rlcihyb3VuZChxdWFudGlsZSh0ZXN0QUIkcGVyY2VudERpZmYkcGVyY2VudERpZmYsIC4wMjUpKSwgMiksIAogICAgICAgICAgICAnJSwgJywgCiAgICAgICAgICAgIGFzLmNoYXJhY3Rlcihyb3VuZChxdWFudGlsZSh0ZXN0QUIkcGVyY2VudERpZmYkcGVyY2VudERpZmYsIC45NzUpKSwgMiksCiAgICAgICAgICAgICclKSB3aXRoIDk1JSBjZXJ0YWludHkuJywKICAgICAgICAgICAgc2VwID0gIiIpKQpnZ3Bsb3QodGVzdEFCJHBlcmNlbnREaWZmLCAKICAgICAgIGFlcyh4ID0gcGVyY2VudERpZmYsCiAgICAgICAgICAgZ3JvdXAgPSBhcmVhLAogICAgICAgICAgIGZpbGwgPSBhcmVhKSkgKyAKICBnZW9tX2hpc3RvZ3JhbShiaW5zID0gMTAwMCwgYWxwaGEgPSAuNSkgKyAKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gY29tbWEpICsKICB4bGFiKHBhc3RlKCcoJywgY2FtcEEsICctJywgY2FtcEIsICcpLycsIGNhbXBCLCBzZXAgPSAiIikpICsgeWxhYignRGVuc2l0eScpICsgCiAgZ2d0aXRsZShwYXN0ZShjYW1wQSwgJy8nLCBjYW1wQiwgJyBDYW1wYWlnbiBMaWZ0Jywgc2VwID0gIiIpKSArCiAgdGhlbWVfbWluaW1hbCgpICsgCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApKQpgYGAKCiMjIyMjIEV2YWx1YXRpb246IEJUMyB2cy4gR0lCX1JHCgpgYGB7ciBlY2hvID0gVCwgd2FybmluZyA9ICdoaWRlJywgbWVzc2FnZSA9IEZ9CmNhbXBBIDwtICdCVDMnCmNhbXBCIDwtICdHSUJfUkcnCnRlc3RBQiA8LSBwb3N0ZXJpb3JfRUVkaXRfQUIoYmFubmVyRWRQcm9iLCBjYW1wQSwgY2FtcEIsIG1jTiA9IG1jTikKIyAtIFByb2JhYmlsaXR5IG9mIGNhbXBBIGJldHRlciB0aGFuIGNhbXBCOgpwcmludChwYXN0ZSgnVGhlIHByb2JhYmlsaXR5IG9mICcsIAogICAgICAgICAgICBjYW1wQSwgIAogICAgICAgICAgICAnIGluZmx1ZW5jaW5nIG1vcmUgdXNlciBlZGl0cyB0aGFuICcsIAogICAgICAgICAgICBjYW1wQiwgJyBpcyA6ICcsIAogICAgICAgICAgICByb3VuZCh0ZXN0QUIkcHJvYmFiaWxpdHksIDIpLCAKICAgICAgICAgICAgc2VwID0gIiIpKQojIC0gbGlmdDoKcHJpbnQocGFzdGUoJ1RoZSBwZXJjZW50IGxpZnQgdGhhdCAnLCAKICAgICAgICAgICAgY2FtcEEsIAogICAgICAgICAgICAnIGhhcyBvdmVyICcsIAogICAgICAgICAgICBjYW1wQiwgCiAgICAgICAgICAgICcgKCcsCiAgICAgICAgICAgIHJvdW5kKHRlc3RBQiR0X3BlcmNlbnREaWZmLCAyKSwKICAgICAgICAgICAgJyUpIGxpZXMgaW4gdGhlIGludGVydmFsICgnLCAKICAgICAgICAgICAgYXMuY2hhcmFjdGVyKHJvdW5kKHF1YW50aWxlKHRlc3RBQiRwZXJjZW50RGlmZiRwZXJjZW50RGlmZiwgLjAyNSkpLCAyKSwgCiAgICAgICAgICAgICclLCAnLCAKICAgICAgICAgICAgYXMuY2hhcmFjdGVyKHJvdW5kKHF1YW50aWxlKHRlc3RBQiRwZXJjZW50RGlmZiRwZXJjZW50RGlmZiwgLjk3NSkpLCAyKSwKICAgICAgICAgICAgJyUpIHdpdGggOTUlIGNlcnRhaW50eS4nLAogICAgICAgICAgICBzZXAgPSAiIikpCmdncGxvdCh0ZXN0QUIkcGVyY2VudERpZmYsIAogICAgICAgYWVzKHggPSBwZXJjZW50RGlmZiwKICAgICAgICAgICBncm91cCA9IGFyZWEsCiAgICAgICAgICAgZmlsbCA9IGFyZWEpKSArIAogIGdlb21faGlzdG9ncmFtKGJpbnMgPSAxMDAwLCBhbHBoYSA9IC41KSArIAogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBjb21tYSkgKwogIHhsYWIocGFzdGUoJygnLCBjYW1wQSwgJy0nLCBjYW1wQiwgJykvJywgY2FtcEIsIHNlcCA9ICIiKSkgKyB5bGFiKCdEZW5zaXR5JykgKyAKICBnZ3RpdGxlKHBhc3RlKGNhbXBBLCAnLycsIGNhbXBCLCAnIENhbXBhaWduIExpZnQnLCBzZXAgPSAiIikpICsKICB0aGVtZV9taW5pbWFsKCkgKyAKICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCkpCmBgYAoKIyMjIyMgRXZhbHVhdGlvbjogR0lCX0xQIHZzLiBHSUJfUkcKCmBgYHtyIGVjaG8gPSBULCB3YXJuaW5nID0gJ2hpZGUnLCBtZXNzYWdlID0gRn0KY2FtcEEgPC0gJ0dJQl9MUCcKY2FtcEIgPC0gJ0dJQl9SRycKdGVzdEFCIDwtIHBvc3Rlcmlvcl9FRWRpdF9BQihiYW5uZXJFZFByb2IsIGNhbXBBLCBjYW1wQiwgbWNOID0gbWNOKQojIC0gUHJvYmFiaWxpdHkgb2YgY2FtcEEgYmV0dGVyIHRoYW4gY2FtcEI6CnByaW50KHBhc3RlKCdUaGUgcHJvYmFiaWxpdHkgb2YgJywgCiAgICAgICAgICAgIGNhbXBBLCAgCiAgICAgICAgICAgICcgaW5mbHVlbmNpbmcgbW9yZSB1c2VyIGVkaXRzIHRoYW4gJywgCiAgICAgICAgICAgIGNhbXBCLCAnIGlzIDogJywgCiAgICAgICAgICAgIHJvdW5kKHRlc3RBQiRwcm9iYWJpbGl0eSwgMiksIAogICAgICAgICAgICBzZXAgPSAiIikpCiMgLSBsaWZ0OgpwcmludChwYXN0ZSgnVGhlIHBlcmNlbnQgbGlmdCB0aGF0ICcsIAogICAgICAgICAgICBjYW1wQSwgCiAgICAgICAgICAgICcgaGFzIG92ZXIgJywgCiAgICAgICAgICAgIGNhbXBCLCAKICAgICAgICAgICAgJyAoJywKICAgICAgICAgICAgcm91bmQodGVzdEFCJHRfcGVyY2VudERpZmYsIDIpLAogICAgICAgICAgICAnJSkgbGllcyBpbiB0aGUgaW50ZXJ2YWwgKCcsIAogICAgICAgICAgICBhcy5jaGFyYWN0ZXIocm91bmQocXVhbnRpbGUodGVzdEFCJHBlcmNlbnREaWZmJHBlcmNlbnREaWZmLCAuMDI1KSksIDIpLCAKICAgICAgICAgICAgJyUsICcsIAogICAgICAgICAgICBhcy5jaGFyYWN0ZXIocm91bmQocXVhbnRpbGUodGVzdEFCJHBlcmNlbnREaWZmJHBlcmNlbnREaWZmLCAuOTc1KSksIDIpLAogICAgICAgICAgICAnJSkgd2l0aCA5NSUgY2VydGFpbnR5LicsCiAgICAgICAgICAgIHNlcCA9ICIiKSkKZ2dwbG90KHRlc3RBQiRwZXJjZW50RGlmZiwgCiAgICAgICBhZXMoeCA9IHBlcmNlbnREaWZmLAogICAgICAgICAgIGdyb3VwID0gYXJlYSwKICAgICAgICAgICBmaWxsID0gYXJlYSkpICsgCiAgZ2VvbV9oaXN0b2dyYW0oYmlucyA9IDEwMDAsIGFscGhhID0gLjUpICsgCiAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IGNvbW1hKSArCiAgeGxhYihwYXN0ZSgnKCcsIGNhbXBBLCAnLScsIGNhbXBCLCAnKS8nLCBjYW1wQiwgc2VwID0gIiIpKSArIHlsYWIoJ0RlbnNpdHknKSArIAogIGdndGl0bGUocGFzdGUoY2FtcEEsICcvJywgY2FtcEIsICcgQ2FtcGFpZ24gTGlmdCcsIHNlcCA9ICIiKSkgKwogIHRoZW1lX21pbmltYWwoKSArIAogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSkKYGBgCgojIyMgNS4yIENhbXBhaWduIE11bHRpLUNoYW5uZWwgQXR0cmlidXRpb24gTW9kZWw6IE1ha2luZyBhbiBFZGl0CgpUaGUgZm9sbG93aW5nIG1vZGVsIHByb3ZpZGVzIGZvciB0aGUgKnJlbW92YWwgZWZmZWN0cyogb2YgdGhlIENhbXBhaWduIGNoYW5uZWxzICppbiByZXNwZWN0IHRvIHdoZXRoZXIgYSB1c2VyIGhhcyBtYWRlIGFueSBlZGl0cyBhdCBhbGwgb3Igbm90Ki4gVGhpcyBwcm9jZWR1cmUgaW5zdGFudGlhdGVzIGEgbW9kZWwgb2YgYSBwYXJ0aWN1bGFyIGNhbXBhaWduIGFzIGEgZGlyZWN0ZWQgZ3JhcGggaW4gd2hpY2ggZXZlcnkgbm9kZSByZXByZXNlbnRzIGEgY2FtcGFpZ24gY2hhbm5lbCAoZS5nLiBhIGJhbm5lciwgYSBwYWdlIHZpZXcsIGFuIGFjdCBvZiBhIHVzZXIgZG9pbmcgc29tZXRoaW5nLCBldGMpLCBhbmQgdGhlbiBjb21wdXRlcyB0aGUgcHJvYmFiaWxpdGllcyBvZiB0cmFuc2l0aW9uIGZyb20gb25lIHRvIGFub3RoZXIgY2hhbm5lbC4gSW4gb3RoZXIgd29yZHMsIHRoZSBtb2RlbCBlc3RpbWF0ZXMgdGhlIHByb2JhYmlsaXRpZXMgb2YgdGFraW5nIGFueSBvZiB0aGUgcG9zc2libGUgdXNlciBqb3VybmV5cyBpbiB0aGUgY2FtcGFpZ24uIE9uY2UgdGhlIG1vZGVsIGlzIHJlYWR5LCB0aGUgcHJvY2VkdXJlIHNpbXVsYXRlcyBhIGxhcmdlIG51bWJlciBvZiB1c2VyIGpvdXJuZXlzIHRvIHByb2R1Y2UgYW4gZXN0aW1hdGUgb2YgdGhlIHByb2JhYmlsaXR5IG9mIGNvbnZlcnNpb24gZm9yIGVhY2ggb2YgdGhlbS4gSW4gdGhlIGNhc2Ugb2Ygb3VyIGNhbXBhaWduIHdlIGNvbnNpZGVyIHRoZSBldmVudCBvZiBhIHVzZXIgbWFraW5nIGF0IGxlYXN0IG9uZSBlZGl0IGFzIGEgY29udmVyc2lvbi4gV2hlbiB0aGlzIHN0ZXAgaXMgY29tcGxldGVkLCB0aGUgcHJvY2VkdXJlIHN0YXJ0cyAqcmVtb3ZpbmcqIG9uZSBieSBvbmUgY2FtcGFpZ24gY2hhbm5lbCBmcm9tIHRoZSBtb2RlbCwgYW5kIGVhY2ggdGltZSBpdCByZS1jb21wdXRlcyB0aGUgY29udmVyc2lvbiBwcm9iYWJpbGl0eSB0byBlc3RpbWF0ZSBob3cgbWFueSBjb252ZXJzaW9ucyB3b3VsZCBiZSBsb3N0IGR1ZSB0byB0aGUgcmVtb3ZhbCBvZiBhIHBhcnRpY3VsYXIgY2hhbm5lbC4gVGhlIGxhcmdlciB0aGUgZHJvcCBpbiBwcm9iYWJpbGl0eSBvZiBjb252ZXJzaW9uIGR1ZSB0byB0aGUgcmVtb3ZhbCBvZiBhIHBhcnRpY3VsYXIgY2hhbm5lbCwgdGhlIGxhcmdlciB0aGUgcmVtb3ZhbCBlZmZlY3QgZm9yIHRoYXQgY2hhbm5lbC4gQ2hhbm5lbHMgd2l0aCBsYXJnZXIgcmVtb3ZhbCBlZmZlY3RzIGFyZSBjb25zaWRlcmVkIHRvIGJlIG1vcmUgaW1wb3J0YW50LiBUaGUgdmFsdWUgb2YgdGhlIHJlbW92YWwgZWZmZWN0LCBiZWluZyBhIHByb2JhYmlsaXR5IGluIGl0c2VsZiwgY2FuIHZhcnkgZnJvbSAwIHRvIDEuCgpJbiB0aGlzIGNhc2UsIHRoZSBjYW1wYWlnbiBjaGFubmVscyBhcmUgdGhlIGZvbGxvd2luZyBldmVudHM6CgotIGBCVDFgIC0gU3BlY2lmaWMgVGFzayBCYW5uZXIgYHdtZGVfYWJjMjAxN19idDFgIGlzIHByZXNlbnRlZDsKLSBgQlQyYCAtIFNwZWNpZmljIFRhc2sgQmFubmVyIGB3bWRlX2FiYzIwMTdfYnQyYCBpcyBwcmVzZW50ZWQ7Ci0gYEJUM2AgLSBTcGVjaWZpYyBUYXNrIEJhbm5lciBgd21kZV9hYmMyMDE3X2J0M2AgaXMgcHJlc2VudGVkOwotIGBHSUJgIC0gR2VuZXJhbCBJbnZpYXRpb24gQmFubmVyIC0gYHdtZGVfYWJjMjAxN19naWJfbHBgIG9yIGB3bWRlX2FiYzIwMTdfZ2liX3JnYCBpcyBwcmVzZW50ZWQ7Ci0gYFRMUGAgLSBTcGVjaWZpYyBUYXNrIFBhZ2UgYEpldHp0TWl0bWFjaGVuYCBpcyB2aWV3ZWQgKG5vdGU6IHRoZSBzYW1lIGFzIGEgYmFubmVyIGNsaWNrIG9uIGFueSBvZiB0aGUgZm9sbG93aW5nIGJhbm5lcnM6IGBCVDFgLCBgQlQyYCwgYEJUM2ApOyAKLSBgR0xQYCAtIEdlbmVyYWwgUGFnZSBgTWFjaF9taXRgIGlzIHZpZXdlZDsgKG5vdGU6IHRoZSBzYW1lIGFzIGEgYmFubmVyIGNsaWNrIG9uIGBHSUJfTFBgKTsKLSBgUlBgICAtIFJlZ2lzdHJhdGlvbiBQYWdlIGBCZW51dHplcmtvbnRvX2FubGVnZW5gIGlzIHZpZXdlZDsgKG5vdGU6IGVuY29tcGFzc2VzIHVzZXJzIHdobyB0cmFuc2l0IGZyb20gYEpldHp0TWl0bWFjaGVuYCBvciBgTWFjaF9taXRgLCBhcyB3ZWxsIGFzIGJhbm5lciBjbGlja3Mgb24gYEdJQl9SR2ApOwotIGBSZWdgIC0gVGhlIGFjdCBvZiB1c2VyIHJlZ2lzdHJhdGlvbjsKLSBgR1RgICAtIFRoZSBhY3Qgb2YgY29tcGxldGluZyB0aGUgR3VpZGVkIFRvdXIuCgoqKkltcG9ydGFudCoqOiB1bmxpa2UgaW4gdGhlIEJheWVzaWFuIEEvQiB0ZXN0cyB0aGF0IGFyZSBwcmVzZW50ZWQgYWJvdmUsIHdoZXJlIHRoZSBjcml0ZXJpb24gZm9yIHBhaXItd2lzZSBjb21wYXJpc29ucyBhbW9uZyB0aGUgY2FtcGFpZ24gYmFubmVycyB3YXMgZWl0aGVyIHRoZSBudW1iZXIgb2YgdXNlcnMgcmVnaXN0ZXJlZCAoYFNlY3Rpb24gNS4xQWApLCBvciB0aGUgbnVtYmVyIG9mIGVkaXRzIG1hZGUgKGBTZWN0aW9uIDUuMUJgKSwgaGVyZSB0aGUgY3JpdGVyaW9uIChpLmUuIHRoZSBkZWZpbml0aW9uIG9mICpjb252ZXJzaW9uKiwgaWYgeW91IHByZWZlcikgaXMgd2hldGhlciBhIHVzZXIgaGFzIG1hZGUgYW55IGVkaXRzIGF0IGFsbC4gVGhlIHJlYXNvbiB0aGF0IG1vdGl2YXRlcyB0aGlzIGNyaXRlcmlvbiwgYW5kIG5vdCBhIG1vcmUgc3RyaWN0IGNyaXRlcmlvbiBvZiBtYWtpbmcgYD49IDEwIGVkaXRzYCwgaXMgc2ltcGx5IGJlY2F1c2UgdGhlcmUgYXJlIG9ubHkgc2V2ZXJhbCB1c2VycyB3aG8gaGF2ZSByZWdpc3RlcmVkIHZpYSB0aGlzIGNhbXBhaWduIGFuZCBtYWRlIG1vcmUgdGhhbiB0ZW4gZWRpdHMgdW50aWwgbm93LiAKKipSZW1vdmFsIEVmZmVjdHMqKi4gVGhlIFJlbW92YWwgRWZmZWN0IGZvciBhIGNhbXBhaWduIGNoYW5uZWwgcmVwcmVzZW50cyAqdGhlIGNoYW5nZSBpbiBwcm9iYWJpbGl0eSB0aGF0IGEgY29udmVyc2lvbiB3b3VsZCBvYnRhaW4gaWYgdGhlIHJlc3BlY3RpdmUgY2hhbm5lbCB3YXMgcmVtb3ZlZCBmcm9tIHRoZSBjYW1wYWlnbiouIE9uY2UgYWdhaW4sIGdpdmVuIHRoYXQgY29udmVyc2lvbiBoZXJlIG1lYW5zIGEgdXNlciBtYWtpbmcgYXQgbGVhc3Qgb25lIGVkaXQsIHRoZSByZW1vdmFsIGVmZmVjdHMgdGVsbHMgdXMgKmhvdyBtdWNoIHdvdWxkIHRoZSBwcm9iYWJpbGl0eSBvZiBvYnRhaW5pbmcgYXQgbGVhc3Qgb25lIGVkaXQgZnJvbSBhIHVzZXIgZHJvcCogaWYgdGhlIHJlc3BlY3RpdmUgY2FtcGFpZ24gY2hhbm5lbCB3YXMgcmVtb3ZlZC4KKipURUNITklDQUwgTk9URSoqOiBhIE1hcmtvdiBtb2RlbCBvZiAqb3JkZXIgNCogd2FzIHVzZWQsIHdpdGggYDFlOGAgdG90YWwgc2ltdWxhdGlvbiBydW5zIGZyb20gdGhlIHRyYW5zaXRpb24gbWF0cml4LgoKIyMjIyBSZW1vdmFsIEVmZmVjdHMKCmBgYHtyIGVjaG8gPSBULCB3YXJuaW5nID0gJ2hpZGUnLCBtZXNzYWdlID0gRiwgZXZhbCA9IFR9CiMjIyAtLS0gQmFubmVyIC0+IEV4aXQgcGF0aHMgLS0tICMjIwojIyMgLS0tIERlZmluaXRpb246IE4oQmFubmVyIEltcHJlc3Npb25zKSAtIE4oQmFubmVyQ2xpY2tzID09IExhbmRpbmcgUGFnZSBWaWV3cykKIyAtIGRlZmluZTogTihCYW5uZXIgSW1wcmVzc2lvbnMpCmJJbXAgPC0gYmFuSW1wU2V0ICU+JSAKICBncm91cF9ieShCYW5uZXIpICU+JSAKICBzdW1tYXJpc2UoQ291bnQgPSBzdW0oQ291bnQpKQpuQlQxIDwtIGJJbXAkQ291bnRbd2hpY2goYkltcCRCYW5uZXIgJWluJSAnQlQxJyldIApuQlQyIDwtIGJJbXAkQ291bnRbd2hpY2goYkltcCRCYW5uZXIgJWluJSAnQlQyJyldCm5CVDMgPC0gYkltcCRDb3VudFt3aGljaChiSW1wJEJhbm5lciAlaW4lICdCVDMnKV0KbkdJQiA8LSBiSW1wJENvdW50W3doaWNoKGJJbXAkQmFubmVyICVpbiUgJ0dJQl9MUCcpXSArIGJJbXAkQ291bnRbd2hpY2goYkltcCRCYW5uZXIgJWluJSAnR0lCX1JHJyldCiMgLSBkZWZpbmU6IE4oQmFubmVyQ2xpY2tzL1BhZ2VWaWV3cykKYkNsaWNrIDwtIGNsaWNrUGxvdFNldCAlPiUgCiAgZ3JvdXBfYnkoU291cmNlKSAlPiUgCiAgc3VtbWFyaXNlKENvdW50ID0gc3VtKENvdW50KSkKYkNsaWNrJFNvdXJjZSA8LSBnc3ViKCJfY2xpY2siLCAiIiwgYkNsaWNrJFNvdXJjZSwgZml4ZWQgPSBUKQojIC0gZGVmaW5lOiBOKEJhbm5lckltcHJlc3Npb25zKSAtIE4oQmFubmVyQ2xpY2tzL1BhZ2VWaWV3cykKbkJUMSA8LSBuQlQxIC0gYkNsaWNrJENvdW50W3doaWNoKGJDbGljayRTb3VyY2UgJWluJSAnQlQxJyldIApuQlQyIDwtIG5CVDIgLSBiQ2xpY2skQ291bnRbd2hpY2goYkNsaWNrJFNvdXJjZSAlaW4lICdCVDInKV0KbkJUMyA8LSBuQlQzIC0gYkNsaWNrJENvdW50W3doaWNoKGJDbGljayRTb3VyY2UgJWluJSAnQlQzJyldCm5HSUIgPC0gbkdJQiAtIGJDbGljayRDb3VudFt3aGljaChiQ2xpY2skU291cmNlICVpbiUgJ0dJQl9MUCcpXSArIGJDbGljayRDb3VudFt3aGljaChiQ2xpY2skU291cmNlICVpbiUgJ0dJQl9SRycpXQoKIyMjIC0tLSBCYW5uZXIgLT4gTGFuZGluZyBQYWdlIC0+IEV4aXQgcGF0aHMgLS0tICMjIwojIyMgLS0tIE4oQmFubmVyIENsaWNrcyA9PSBMYW5kaW5nIFBhZ2UgVmlld3MpIC0gTihSZWdpc3RyYXRpb24gUGFnZSBWaWV3cykKIyMjIC0tLSBOT1RFOiBUTFAgPT0gVGFzayBMYW5kaW5nIFBhZ2UgKEpldHp0TWl0bWFjaGVuKSwgR0xQID09IEdlbmVyYWwgTGFuZGluZyBQYWdlIChNYWNoIG1pdCkKbkJUMV9UTFAgPC0gYkNsaWNrJENvdW50W3doaWNoKGJDbGljayRTb3VyY2UgJWluJSAnQlQxJyldIC0gCiAgcGFnZVNvdXJjZSRuW3BhZ2VTb3VyY2UkUGFnZSAlaW4lICdTcGV6aWFsOkJlbnV0emVya29udG9fYW5sZWdlbicgJiBwYWdlU291cmNlJFNvdXJjZSAlaW4lICdKZXR6dE1pdG1hY2hlbl9CVDEnXQpuQlQyX1RMUCA8LSBiQ2xpY2skQ291bnRbd2hpY2goYkNsaWNrJFNvdXJjZSAlaW4lICdCVDInKV0gLSAKICBwYWdlU291cmNlJG5bcGFnZVNvdXJjZSRQYWdlICVpbiUgJ1NwZXppYWw6QmVudXR6ZXJrb250b19hbmxlZ2VuJyAmIHBhZ2VTb3VyY2UkU291cmNlICVpbiUgJ0pldHp0TWl0bWFjaGVuX0JUMiddCm5CVDNfVExQIDwtIGJDbGljayRDb3VudFt3aGljaChiQ2xpY2skU291cmNlICVpbiUgJ0JUMycpXSAtIAogIHBhZ2VTb3VyY2UkbltwYWdlU291cmNlJFBhZ2UgJWluJSAnU3BlemlhbDpCZW51dHplcmtvbnRvX2FubGVnZW4nICYgcGFnZVNvdXJjZSRTb3VyY2UgJWluJSAnSmV0enRNaXRtYWNoZW5fQlQzJ10KbkdJQl9HTFAgPC0gYkNsaWNrJENvdW50W3doaWNoKGJDbGljayRTb3VyY2UgJWluJSAnR0lCX0xQJyldIC0gCiAgcGFnZVNvdXJjZSRuW3BhZ2VTb3VyY2UkUGFnZSAlaW4lICdTcGV6aWFsOkJlbnV0emVya29udG9fYW5sZWdlbicgJiBwYWdlU291cmNlJFNvdXJjZSAlaW4lICdNYWNoX21pdCddCgojIyMgLS0tIEJhbm5lciAoLT4gTGFuZGluZyBQYWdlKSAtPiBSZWdpc3RyYXRpb24gUGFnZSAtPiBFeGl0IHBhdGhzIC0tLSAjIyMKIyMjIC0tLSBOKFJlZ2lzdHJhdGlvbiBQYWdlIFZpZXdzKSAtIE4oVXNlciBSZWdpc3RyYXRpb25zKQpiVXNlclJlZyA8LSB1c2VyUmVnICU+JSAKICBncm91cF9ieShldmVudF9jYW1wYWlnbikgJT4lIAogIHN1bW1hcmlzZShDb3VudCA9IG4oKSkKYlVzZXJSZWckZXZlbnRfY2FtcGFpZ24gPC0gdG91cHBlcihnc3ViKCJ3bWRlX2FiYzIwMTdfIiwgIiIsIGJVc2VyUmVnJGV2ZW50X2NhbXBhaWduLCBmaXhlZCA9IFQpKQpjb2xuYW1lcyhiVXNlclJlZylbMV0gPC0gJ0Jhbm5lcicKbkJUMV9UTFBfUlAgPC0gcGFnZVNvdXJjZSRuW3BhZ2VTb3VyY2UkUGFnZSAlaW4lICdTcGV6aWFsOkJlbnV0emVya29udG9fYW5sZWdlbicgJiBwYWdlU291cmNlJFNvdXJjZSAlaW4lICdKZXR6dE1pdG1hY2hlbl9CVDEnXSAtIAogIGJVc2VyUmVnJENvdW50W2JVc2VyUmVnJEJhbm5lciAlaW4lICdCVDEnXQpuQlQyX1RMUF9SUCA8LSBwYWdlU291cmNlJG5bcGFnZVNvdXJjZSRQYWdlICVpbiUgJ1NwZXppYWw6QmVudXR6ZXJrb250b19hbmxlZ2VuJyAmIHBhZ2VTb3VyY2UkU291cmNlICVpbiUgJ0pldHp0TWl0bWFjaGVuX0JUMiddIC0gCiAgYlVzZXJSZWckQ291bnRbYlVzZXJSZWckQmFubmVyICVpbiUgJ0JUMiddCm5CVDNfVExQX1JQIDwtIHBhZ2VTb3VyY2UkbltwYWdlU291cmNlJFBhZ2UgJWluJSAnU3BlemlhbDpCZW51dHplcmtvbnRvX2FubGVnZW4nICYgcGFnZVNvdXJjZSRTb3VyY2UgJWluJSAnSmV0enRNaXRtYWNoZW5fQlQzJ10gLSAKICBiVXNlclJlZyRDb3VudFtiVXNlclJlZyRCYW5uZXIgJWluJSAnQlQzJ10KbkdJQl9HTFBfUlAgPC0gcGFnZVNvdXJjZSRuW3BhZ2VTb3VyY2UkUGFnZSAlaW4lICdNYWNoX21pdCcgJiBwYWdlU291cmNlJFNvdXJjZSAlaW4lICdHSUJfTFBfY2xpY2snXSAtIAogIGJVc2VyUmVnJENvdW50W2JVc2VyUmVnJEJhbm5lciAlaW4lICdHSUJfTFAnXQpuR0lCX1JQIDwtIHBhZ2VTb3VyY2UkbltwYWdlU291cmNlJFBhZ2UgJWluJSAnU3BlemlhbDpCZW51dHplcmtvbnRvX2FubGVnZW4nICYgcGFnZVNvdXJjZSRTb3VyY2UgJWluJSAnR0lCX1JHX2NsaWNrJ10gLSAKICBiVXNlclJlZyRDb3VudFtiVXNlclJlZyRCYW5uZXIgJWluJSAnR0lCX1JHJ10KCiMjIyAtLS0gQmFubmVyICgtPiBMYW5kaW5nIFBhZ2UpIC0+IFJlZ2lzdHJhdGlvbiBQYWdlIC0+IFJlZ2lzdHJhdGlvbiAtPiBFeGl0IC0tLSAjIyMKIyMjIC0tLSBOKFVzZXIgUmVnaXN0cmF0aW9ucykgLSBOKEVkaXRlZCkgLSBOKENvbXBsZXRlZCBHVCBhbmQgTm90IEVkaXRlZCkKdXNlclJlZ0dUIDwtIGxlZnRfam9pbih1c2VyUmVnLCBnVG91ckRhdGEsIAogICAgICAgICAgICAgICAgICAgICAgIGJ5ID0gJ2V2ZW50X3VzZXJJZCcpCnVzZXJSZWdHVCA8LSBsZWZ0X2pvaW4odXNlclJlZ0dULCBlZGl0RGF0YSwgCiAgICAgICAgICAgICAgICAgICAgICAgYnkgPSBjKCdldmVudF91c2VySWQnID0gJ3Jldl91c2VyJykpCgpuQlQxX1RMUF9SUF9SZWcgPC0gYlVzZXJSZWckQ291bnRbYlVzZXJSZWckQmFubmVyICVpbiUgJ0JUMSddIC0gCiAgc3VtKCh1c2VyUmVnR1QkZXZlbnRfY2FtcGFpZ24gJWluJSAnd21kZV9hYmMyMDE3X2J0MScgJiBpcy5uYSh1c2VyUmVnR1QkZXZlbnRfdG91cikpIHwgCiAgICAgICAgKHVzZXJSZWdHVCRldmVudF9jYW1wYWlnbiAlaW4lICd3bWRlX2FiYzIwMTdfYnQxJyAmICFpcy5uYSh1c2VyUmVnR1QkZXZlbnRfdG91cikgJiAhaXMubmEodXNlclJlZ0dUJGVkaXRzKSkpCgpuQlQyX1RMUF9SUF9SZWcgPC0gYlVzZXJSZWckQ291bnRbYlVzZXJSZWckQmFubmVyICVpbiUgJ0JUMiddIC0gCiAgc3VtKCh1c2VyUmVnR1QkZXZlbnRfY2FtcGFpZ24gJWluJSAnd21kZV9hYmMyMDE3X2J0MicgJiBpcy5uYSh1c2VyUmVnR1QkZXZlbnRfdG91cikpIHwgCiAgICAgICAgKHVzZXJSZWdHVCRldmVudF9jYW1wYWlnbiAlaW4lICd3bWRlX2FiYzIwMTdfYnQyJyAmICFpcy5uYSh1c2VyUmVnR1QkZXZlbnRfdG91cikgJiAhaXMubmEodXNlclJlZ0dUJGVkaXRzKSkpCgpuQlQzX1RMUF9SUF9SZWcgPC0gYlVzZXJSZWckQ291bnRbYlVzZXJSZWckQmFubmVyICVpbiUgJ0JUMyddIC0gCiAgc3VtKCh1c2VyUmVnR1QkZXZlbnRfY2FtcGFpZ24gJWluJSAnd21kZV9hYmMyMDE3X2J0MycgJiBpcy5uYSh1c2VyUmVnR1QkZXZlbnRfdG91cikpIHwgCiAgICAgICAgKHVzZXJSZWdHVCRldmVudF9jYW1wYWlnbiAlaW4lICd3bWRlX2FiYzIwMTdfYnQzJyAmICFpcy5uYSh1c2VyUmVnR1QkZXZlbnRfdG91cikgJiAhaXMubmEodXNlclJlZ0dUJGVkaXRzKSkpCgpuR0lCX0dMUF9SUF9SZWcgPC0gYlVzZXJSZWckQ291bnRbYlVzZXJSZWckQmFubmVyICVpbiUgJ0dJQl9MUCddIC0gCiAgc3VtKCh1c2VyUmVnR1QkZXZlbnRfY2FtcGFpZ24gJWluJSAnd21kZV9hYmMyMDE3X2dpYl9scCcgJiBpcy5uYSh1c2VyUmVnR1QkZXZlbnRfdG91cikpIHwgCiAgICAgICAgKHVzZXJSZWdHVCRldmVudF9jYW1wYWlnbiAlaW4lICd3bWRlX2FiYzIwMTdfZ2liX2xwJyAmICFpcy5uYSh1c2VyUmVnR1QkZXZlbnRfdG91cikgJiAhaXMubmEodXNlclJlZ0dUJGVkaXRzKSkpCgpuR0lCX1JQX1JlZyA8LSBiVXNlclJlZyRDb3VudFtiVXNlclJlZyRCYW5uZXIgJWluJSAnR0lCX1JHJ10gLSAKICBzdW0oKHVzZXJSZWdHVCRldmVudF9jYW1wYWlnbiAlaW4lICd3bWRlX2FiYzIwMTdfZ2liX3JnJyAmIGlzLm5hKHVzZXJSZWdHVCRldmVudF90b3VyKSkgfCAKICAgICAgICAodXNlclJlZ0dUJGV2ZW50X2NhbXBhaWduICVpbiUgJ3dtZGVfYWJjMjAxN19naWJfcmcnICYgIWlzLm5hKHVzZXJSZWdHVCRldmVudF90b3VyKSAmICFpcy5uYSh1c2VyUmVnR1QkZWRpdHMpKSkKCiMjIyAtLS0gQmFubmVyICgtPiBMYW5kaW5nIFBhZ2UpIC0+IFJlZ2lzdHJhdGlvbiBQYWdlIC0+IFJlZ2lzdHJhdGlvbiAtPiBHVCAtPiBFeGl0IC0tLSAjIyMKIyMjIC0tLSBOKFVzZXIgUmVnaXN0cmF0aW9ucykgLSBOKEVkaXRlZCkgLSBOKENvbXBsZXRlZCBHVCBhbmQgTm90IEVkaXRlZCkKbkJUMV9UTFBfUlBfUmVnX0dUIDwtIGxlbmd0aCh1c2VyUmVnR1QkZXZlbnRfdXNlcklkWyh1c2VyUmVnR1QkZXZlbnRfY2FtcGFpZ24gJWluJSAnd21kZV9hYmMyMDE3X2J0MScpICYgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlzLm5hKHVzZXJSZWdHVCRldmVudF90b3VyKSAmIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpcy5uYSh1c2VyUmVnR1QkZWRpdHMpXSkKbkJUMl9UTFBfUlBfUmVnX0dUIDwtIGxlbmd0aCh1c2VyUmVnR1QkZXZlbnRfdXNlcklkWyh1c2VyUmVnR1QkZXZlbnRfY2FtcGFpZ24gJWluJSAnd21kZV9hYmMyMDE3X2J0MicpICYgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlzLm5hKHVzZXJSZWdHVCRldmVudF90b3VyKSAmIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpcy5uYSh1c2VyUmVnR1QkZWRpdHMpXSkKbkJUM19UTFBfUlBfUmVnX0dUIDwtIGxlbmd0aCh1c2VyUmVnR1QkZXZlbnRfdXNlcklkWyh1c2VyUmVnR1QkZXZlbnRfY2FtcGFpZ24ueCAlaW4lICd3bWRlX2FiYzIwMTdfYnQzJykgJiAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaXMubmEodXNlclJlZ0dUJGV2ZW50X3RvdXIpICYgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlzLm5hKHVzZXJSZWdHVCRlZGl0cyldKQpuR0lCX0dMUF9SUF9SZWdfR1QgPC0gbGVuZ3RoKHVzZXJSZWdHVCRldmVudF91c2VySWRbKHVzZXJSZWdHVCRldmVudF9jYW1wYWlnbiAlaW4lICd3bWRlX2FiYzIwMTdfZ2liX2xwJykgJiAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaXMubmEodXNlclJlZ0dUJGV2ZW50X3RvdXIpICYgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlzLm5hKHVzZXJSZWdHVCRlZGl0cyldKQpuR0lCX1JQX1JlZ19HVCA8LSBsZW5ndGgodXNlclJlZ0dUJGV2ZW50X3VzZXJJZFsodXNlclJlZ0dUJGV2ZW50X2NhbXBhaWduICVpbiUgJ3dtZGVfYWJjMjAxN19naWJfcmcnKSAmCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaXMubmEodXNlclJlZ0dUJGV2ZW50X3RvdXIpICYKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpcy5uYSh1c2VyUmVnR1QkZWRpdHMpXSkKCiMjIyAtLS0gQmFubmVyICgtPiBMYW5kaW5nIFBhZ2UpIC0+IFJlZ2lzdHJhdGlvbiBQYWdlIC0+IFJlZ2lzdHJhdGlvbiAtPiBHVCAtPiBFRElUIC0tLSAjIyMKbkJUMV9UTFBfUlBfUmVnX0dUX0VESVQgPC0gbGVuZ3RoKHVzZXJSZWdHVCRldmVudF91c2VySWRbKHVzZXJSZWdHVCRldmVudF9jYW1wYWlnbiAlaW4lICd3bWRlX2FiYzIwMTdfYnQxJykgJiAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaXMubmEodXNlclJlZ0dUJGV2ZW50X3RvdXIpICYgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICFpcy5uYSh1c2VyUmVnR1QkZWRpdHMpXSkKbkJUMl9UTFBfUlBfUmVnX0dUX0VESVQgPC0gbGVuZ3RoKHVzZXJSZWdHVCRldmVudF91c2VySWRbKHVzZXJSZWdHVCRldmVudF9jYW1wYWlnbiAlaW4lICd3bWRlX2FiYzIwMTdfYnQyJykgJiAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaXMubmEodXNlclJlZ0dUJGV2ZW50X3RvdXIpICYgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICFpcy5uYSh1c2VyUmVnR1QkZWRpdHMpXSkKbkJUM19UTFBfUlBfUmVnX0dUX0VESVQgPC0gbGVuZ3RoKHVzZXJSZWdHVCRldmVudF91c2VySWRbKHVzZXJSZWdHVCRldmVudF9jYW1wYWlnbi54ICVpbiUgJ3dtZGVfYWJjMjAxN19idDMnKSAmIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpcy5uYSh1c2VyUmVnR1QkZXZlbnRfdG91cikgJiAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIWlzLm5hKHVzZXJSZWdHVCRlZGl0cyldKQpuR0lCX0dMUF9SUF9SZWdfR1RfRURJVCA8LSBsZW5ndGgodXNlclJlZ0dUJGV2ZW50X3VzZXJJZFsodXNlclJlZ0dUJGV2ZW50X2NhbXBhaWduICVpbiUgJ3dtZGVfYWJjMjAxN19naWJfbHAnKSAmIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpcy5uYSh1c2VyUmVnR1QkZXZlbnRfdG91cikgJiAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIWlzLm5hKHVzZXJSZWdHVCRlZGl0cyldKQpuR0lCX1JQX1JlZ19HVF9FRElUIDwtIGxlbmd0aCh1c2VyUmVnR1QkZXZlbnRfdXNlcklkWyh1c2VyUmVnR1QkZXZlbnRfY2FtcGFpZ24gJWluJSAnd21kZV9hYmMyMDE3X2dpYl9yZycpICYKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpcy5uYSh1c2VyUmVnR1QkZXZlbnRfdG91cikgJgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICFpcy5uYSh1c2VyUmVnR1QkZWRpdHMpXSkKCiMjIyAtLS0gQmFubmVyICgtPiBMYW5kaW5nIFBhZ2UpIC0+IFJlZ2lzdHJhdGlvbiBQYWdlIC0+IFJlZ2lzdHJhdGlvbiAtPiBFRElUIC0tLSAjIyMKbkJUMV9UTFBfUlBfUmVnX0VESVQgPC0gbGVuZ3RoKHVzZXJSZWdHVCRldmVudF91c2VySWRbKHVzZXJSZWdHVCRldmVudF9jYW1wYWlnbiAlaW4lICd3bWRlX2FiYzIwMTdfYnQxJykgJiAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIWlzLm5hKHVzZXJSZWdHVCRldmVudF90b3VyKSAmIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAhaXMubmEodXNlclJlZ0dUJGVkaXRzKV0pCm5CVDJfVExQX1JQX1JlZ19FRElUIDwtIGxlbmd0aCh1c2VyUmVnR1QkZXZlbnRfdXNlcklkWyh1c2VyUmVnR1QkZXZlbnRfY2FtcGFpZ24gJWluJSAnd21kZV9hYmMyMDE3X2J0MicpICYgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICFpcy5uYSh1c2VyUmVnR1QkZXZlbnRfdG91cikgJiAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIWlzLm5hKHVzZXJSZWdHVCRlZGl0cyldKQpuQlQzX1RMUF9SUF9SZWdfRURJVCA8LSBsZW5ndGgodXNlclJlZ0dUJGV2ZW50X3VzZXJJZFsodXNlclJlZ0dUJGV2ZW50X2NhbXBhaWduICVpbiUgJ3dtZGVfYWJjMjAxN19idDMnKSAmIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAhaXMubmEodXNlclJlZ0dUJGV2ZW50X3RvdXIpICYgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICFpcy5uYSh1c2VyUmVnR1QkZWRpdHMpXSkKbkdJQl9HTFBfUlBfUmVnX0VESVQgPC0gbGVuZ3RoKHVzZXJSZWdHVCRldmVudF91c2VySWRbKHVzZXJSZWdHVCRldmVudF9jYW1wYWlnbiAlaW4lICd3bWRlX2FiYzIwMTdfZ2liX2xwJykgJiAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIWlzLm5hKHVzZXJSZWdHVCRldmVudF90b3VyKSAmIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAhaXMubmEodXNlclJlZ0dUJGVkaXRzKV0pCm5HSUJfUlBfUmVnX0VESVQgPC0gbGVuZ3RoKHVzZXJSZWdHVCRldmVudF91c2VySWRbKHVzZXJSZWdHVCRldmVudF9jYW1wYWlnbiAlaW4lICd3bWRlX2FiYzIwMTdfZ2liX3JnJykgJgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICFpcy5uYSh1c2VyUmVnR1QkZXZlbnRfdG91cikgJgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICFpcy5uYSh1c2VyUmVnR1QkZWRpdHMpXSkKIyMjIC0tLSBkYXRhc2V0Cm1jYURhdGEgPC0gZGF0YS5mcmFtZShwYXRoID0gYyhkZXBhcnNlKHN1YnN0aXR1dGUobkJUMSkpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGVwYXJzZShzdWJzdGl0dXRlKG5CVDIpKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRlcGFyc2Uoc3Vic3RpdHV0ZShuQlQzKSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkZXBhcnNlKHN1YnN0aXR1dGUobkdJQikpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGVwYXJzZShzdWJzdGl0dXRlKG5CVDFfVExQKSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkZXBhcnNlKHN1YnN0aXR1dGUobkJUMl9UTFApKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRlcGFyc2Uoc3Vic3RpdHV0ZShuQlQzX1RMUCkpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGVwYXJzZShzdWJzdGl0dXRlKG5HSUJfR0xQKSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkZXBhcnNlKHN1YnN0aXR1dGUobkJUMV9UTFBfUlApKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRlcGFyc2Uoc3Vic3RpdHV0ZShuQlQyX1RMUF9SUCkpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGVwYXJzZShzdWJzdGl0dXRlKG5CVDNfVExQX1JQKSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkZXBhcnNlKHN1YnN0aXR1dGUobkdJQl9HTFBfUlApKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRlcGFyc2Uoc3Vic3RpdHV0ZShuR0lCX1JQKSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkZXBhcnNlKHN1YnN0aXR1dGUobkJUMV9UTFBfUlBfUmVnKSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkZXBhcnNlKHN1YnN0aXR1dGUobkJUMl9UTFBfUlBfUmVnKSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkZXBhcnNlKHN1YnN0aXR1dGUobkJUM19UTFBfUlBfUmVnKSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkZXBhcnNlKHN1YnN0aXR1dGUobkdJQl9HTFBfUlBfUmVnKSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkZXBhcnNlKHN1YnN0aXR1dGUobkdJQl9SUF9SZWcpKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRlcGFyc2Uoc3Vic3RpdHV0ZShuQlQxX1RMUF9SUF9SZWdfR1QpKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkZXBhcnNlKHN1YnN0aXR1dGUobkJUMl9UTFBfUlBfUmVnX0dUKSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkZXBhcnNlKHN1YnN0aXR1dGUobkJUM19UTFBfUlBfUmVnX0dUKSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkZXBhcnNlKHN1YnN0aXR1dGUobkdJQl9HTFBfUlBfUmVnX0dUKSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkZXBhcnNlKHN1YnN0aXR1dGUobkdJQl9SUF9SZWdfR1QpKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRlcGFyc2Uoc3Vic3RpdHV0ZShuQlQxX1RMUF9SUF9SZWdfR1RfRURJVCkpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGVwYXJzZShzdWJzdGl0dXRlKG5CVDJfVExQX1JQX1JlZ19HVF9FRElUKSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkZXBhcnNlKHN1YnN0aXR1dGUobkJUM19UTFBfUlBfUmVnX0dUX0VESVQpKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRlcGFyc2Uoc3Vic3RpdHV0ZShuR0lCX0dMUF9SUF9SZWdfR1RfRURJVCkpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGVwYXJzZShzdWJzdGl0dXRlKG5HSUJfUlBfUmVnX0dUX0VESVQpKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRlcGFyc2Uoc3Vic3RpdHV0ZShuQlQxX1RMUF9SUF9SZWdfRURJVCkpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGVwYXJzZShzdWJzdGl0dXRlKG5CVDJfVExQX1JQX1JlZ19FRElUKSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkZXBhcnNlKHN1YnN0aXR1dGUobkJUM19UTFBfUlBfUmVnX0VESVQpKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRlcGFyc2Uoc3Vic3RpdHV0ZShuR0lCX0dMUF9SUF9SZWdfRURJVCkpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGVwYXJzZShzdWJzdGl0dXRlKG5HSUJfUlBfUmVnX0VESVQpKQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKSwKICAgICAgICAgICAgICAgICAgICAgIHRvdGFsX2NvbnZlcnNpb25zID0gYygwLCAwLCAwLCAwLCAwLCAwLCAwLCAwLCAwLCAwLCAwLCAwLCAwLCAwLCAwLCAwLCAwLCAwLCAwLCAwLCAwLCAwLCAwLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuQlQxX1RMUF9SUF9SZWdfR1RfRURJVCwgbkJUMl9UTFBfUlBfUmVnX0dUX0VESVQsIG5CVDNfVExQX1JQX1JlZ19HVF9FRElULCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuR0lCX0dMUF9SUF9SZWdfR1RfRURJVCwgbkdJQl9SUF9SZWdfR1RfRURJVCwgbkJUMV9UTFBfUlBfUmVnX0VESVQsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5CVDJfVExQX1JQX1JlZ19FRElULCBuQlQzX1RMUF9SUF9SZWdfRURJVCwgbkdJQl9HTFBfUlBfUmVnX0VESVQsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5HSUJfUlBfUmVnX0VESVQKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICApLAogICAgICAgICAgICAgICAgICAgICAgdG90YWxfbnVsbCA9IGMobkJUMSwgbkJUMiwgbkJUMywgbkdJQiwgbkJUMV9UTFAsIG5CVDJfVExQLCBuQlQzX1RMUCwgbkdJQl9HTFAsIG5CVDFfVExQX1JQLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbkJUMl9UTFBfUlAsIG5CVDNfVExQX1JQLCBuR0lCX0dMUF9SUCwgbkdJQl9SUCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuQlQxX1RMUF9SUF9SZWcsIG5CVDJfVExQX1JQX1JlZywgbkJUM19UTFBfUlBfUmVnLCBuR0lCX0dMUF9SUF9SZWcsIG5HSUJfUlBfUmVnLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbkJUMV9UTFBfUlBfUmVnX0dULCBuQlQyX1RMUF9SUF9SZWdfR1QsIG5CVDNfVExQX1JQX1JlZ19HVCwgbkdJQl9HTFBfUlBfUmVnX0dULAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbkdJQl9SUF9SZWdfR1QsIDAsIDAsIDAsIDAsIDAsIDAsIDAsIDAsIDAsIDAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICksCiAgICAgICAgICAgICAgICAgICAgICBzdHJpbmdzQXNGYWN0b3JzID0gRikKCiMgLSBjb3JyZWN0IHBhdGhzOgptY2FEYXRhJHBhdGggPC0gZ3N1YigibiIsICIiLCBtY2FEYXRhJHBhdGgsIGZpeGVkID0gVCkKbWNhRGF0YSRwYXRoIDwtIGdzdWIoIl8iLCAiID4gIiwgbWNhRGF0YSRwYXRoLCBmaXhlZCA9IFQpCmVkaXRFbmRzIDwtIHdoaWNoKGdyZXBsKCJFRElUIiwgbWNhRGF0YSRwYXRoLCBmaXhlZCA9IFQpKQpmb3IgKGkgaW4gMTpsZW5ndGgoZWRpdEVuZHMpKSB7CiAgd1BhdGggPC0gd2hpY2gobWNhRGF0YSRwYXRoICVpbiUgZ3N1YigiID4gRURJVCIsICIiLCBtY2FEYXRhJHBhdGhbZWRpdEVuZHNbaV1dLCBmaXhlZCA9IFQpKQogIHdPdXQgPC0gd2hpY2gobWNhRGF0YSRwYXRoID09IG1jYURhdGEkcGF0aFtlZGl0RW5kc1tpXV0pCiAgd1BhdGggPC0gc2V0ZGlmZih3UGF0aCwgd091dCkKICBtY2FEYXRhJHRvdGFsX2NvbnZlcnNpb25zW3dQYXRoXSA8LSBtY2FEYXRhJHRvdGFsX2NvbnZlcnNpb25zW3dPdXRdCn0KbWNhRGF0YSA8LSBtY2FEYXRhWy13aGljaChncmVwbCgiRURJVCIsIG1jYURhdGEkcGF0aCwgZml4ZWQgPSBUKSksIF0KCiMjIyAtLS0gTUNBIG1vZGVsCmFiYzIwMTdNb2RlbCA8LSBtYXJrb3ZfbW9kZWwobWNhRGF0YSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICB2YXJfcGF0aCA9ICJwYXRoIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICB2YXJfY29udiA9ICJ0b3RhbF9jb252ZXJzaW9ucyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdmFyX251bGwgPSAidG90YWxfbnVsbCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgb3JkZXIgPSA0LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5zaW0gPSAxZTgsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgb3V0X21vcmUgPSBUKQojIC0gY29sbGVjdCByZW1vdmFsIGVmZmVjdHMgZm9yIHRoZSBuZXh0IHBsb3Q6CnJlNG9yZGVyIDwtIGFiYzIwMTdNb2RlbCRyZW1vdmFsX2VmZmVjdHMkcmVtb3ZhbF9lZmZlY3RzCiMjIyAtLS0gUmVtb3ZhbCBFZmZlY3RzOgpyZSA8LSBhcy5kYXRhLmZyYW1lKGFiYzIwMTdNb2RlbCRyZW1vdmFsX2VmZmVjdHMpCmNvbG5hbWVzKHJlKSA8LSBjKCdDaGFubmVsJywgJ1JlbW92YWwgRWZmZWN0JykKcmUkQ2hhbm5lbCA8LSBmYWN0b3IocmUkQ2hhbm5lbCwgbGV2ZWxzID0gYXMuY2hhcmFjdGVyKGFiYzIwMTdNb2RlbCRyZW1vdmFsX2VmZmVjdHMkY2hhbm5lbF9uYW1lKSkKZ3Bsb3QgPC0gZ2dwbG90KGRhdGEgPSByZSwgCiAgICAgICAgICAgICAgICBhZXMoeCA9IENoYW5uZWwsCiAgICAgICAgICAgICAgICAgICAgeSA9IGBSZW1vdmFsIEVmZmVjdGAsCiAgICAgICAgICAgICAgICAgICAgbGFiZWwgPSByb3VuZChgUmVtb3ZhbCBFZmZlY3RgLCAyKSkKICAgICAgICAgICAgICAgICkgKyAKICBnZW9tX2Jhcih3aWR0aCA9IC4xLCBjb2xvciA9ICJkYXJrYmx1ZSIsIGZpbGwgPSAid2hpdGUiLCBzdGF0ID0gImlkZW50aXR5IikgKyAKICBnZW9tX2xhYmVsKHNpemUgPSAzKSArIAogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBjb21tYSkgKyAKICB4bGFiKCdDYW1wYWlnbiBDaGFubmVsJykgKyB5bGFiKCdSZW1vdmFsIEVmZmVjdCcpICsgCiAgeWxpbShjKDAsIDEpKSArIAogIGdndGl0bGUoJ0NhbXBhaWduIE11bHRpLUNoYW5uZWwgQXR0cmlidXRpb246IFJlbW92YWwgRWZmZWN0cycpICsgCiAgdGhlbWVfbWluaW1hbCgpICsgCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApKQpzdXBwcmVzc1dhcm5pbmdzKHByaW50KGdwbG90KSkKYGBgCgojIyMjIENhbXBhaWduIFRyYW5zaXRpb24gR3JhcGgKCkVhY2ggbm9kZSBpbiB0aGUgZm9sbG93aW5nIGdyYXBoIHJlcHJlc2VudHMgYSBwYXJ0aWN1bGFyIGNhbXBhaWduIGNoYW5uZWwuIFRoZSBlZGdlcyBvZiB0aGUgZ3JhcGggYXJlIGxhYmVsZWQgYnkgdGhlIHJlc3BlY3RpdmUgdHJhbnNpdGlvbiBwcm9iYWJpbGl0aWVzIGJldHdlZW4gdGhlIGNoYW5uZWxzLiBUaGUgc2l6ZSBvZiB0aGUgbm9kZSBjb3JyZXNwb25kcyB0byBpdHMgcmVtb3ZhbCBlZmZlY3QuICoqVEVDSE5JQ0FMIE5PVEU6KiogdGhlIHJlbW92YWwgZWZmZWN0cyBhcmUgZGVyaXZlZCBmcm9tIGEgTWFya292IG1vZGVsIG9mIG9yZGVyIDQsIHdoaWxlIHRoZSB0cmFuc2l0aW9uYWwgcHJvYmFiaWxpdGllcyBhcmUgZGVyaXZlZCBkaXJlY3RseSBmcm9tIHRoZSAxc3Qgb3JkZXIgbW9kZWwuCgpgYGB7ciBlY2hvID0gVCwgd2FybmluZyA9ICdoaWRlJywgbWVzc2FnZSA9IEYsIGV2YWwgPSBUfQojIyMgLS0tIE1DQSBtb2RlbDogMXN0IG9yZGVyIGZvciBjaGFubmVsLXRvLWNoYW5uZWwgdHJhbnNpdGlvbnMKYWJjMjAxN01vZGVsIDwtIG1hcmtvdl9tb2RlbChtY2FEYXRhLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZhcl9wYXRoID0gInBhdGgiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZhcl9jb252ID0gInRvdGFsX2NvbnZlcnNpb25zIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICB2YXJfbnVsbCA9ICJ0b3RhbF9udWxsIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBvcmRlciA9IDEsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgb3V0X21vcmUgPSBUKQojIyMgLS0tIHBsb3Qgdy4ge2lncmFwaH0KYWJjMjAxN05ldCA8LSBkYXRhLmZyYW1lKG91Z29pbmcgPSBhYmMyMDE3TW9kZWwkdHJhbnNpdGlvbl9tYXRyaXgkY2hhbm5lbF9mcm9tLAogICAgICAgICAgICAgICAgICAgICAgICAgaW5jb21pbmcgPSBhYmMyMDE3TW9kZWwkdHJhbnNpdGlvbl9tYXRyaXgkY2hhbm5lbF90bywKICAgICAgICAgICAgICAgICAgICAgICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGKQphYmMyMDE3TmV0JG91Z29pbmcgPC0gc2FwcGx5KGFiYzIwMTdOZXQkb3Vnb2luZywgZnVuY3Rpb24oeCkgewogIGNoIDwtIGdzdWIoIihzdGFydCkiLCAiU1RBUlQiLCBmaXhlZCA9IFQsIHgpCiAgY2ggPC0gZ3N1YigiKG51bGwpIiwgIkVYSVQiLCBmaXhlZCA9IFQsIGNoKQogIGNoIDwtIGdzdWIoIihjb252ZXJzaW9uKSIsICJFRElUIiwgZml4ZWQgPSBULCBjaCkKICBjaAp9KQphYmMyMDE3TmV0JGluY29taW5nIDwtIHNhcHBseShhYmMyMDE3TmV0JGluY29taW5nLCBmdW5jdGlvbih4KSB7CiAgY2ggPC0gZ3N1YigiKHN0YXJ0KSIsICJTVEFSVCIsIGZpeGVkID0gVCwgeCkKICBjaCA8LSBnc3ViKCIobnVsbCkiLCAiRVhJVCIsIGZpeGVkID0gVCwgY2gpCiAgY2ggPC0gZ3N1YigiKGNvbnZlcnNpb24pIiwgIkVESVQiLCBmaXhlZCA9IFQsIGNoKQogIGNoCn0pCgphYmMyMDE3TmV0IDwtIGdyYXBoLmRhdGEuZnJhbWUoYWJjMjAxN05ldCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkaXJlY3RlZCA9IFQpCkUoYWJjMjAxN05ldCkkbGFiZWwgPC0gcm91bmQoYWJjMjAxN01vZGVsJHRyYW5zaXRpb25fbWF0cml4JHRyYW5zaXRpb25fcHJvYmFiaWxpdHksIDIpClYoYWJjMjAxN05ldCkkY29sb3IgPC0gYygnd2hpdGUnLCAKICAgICAgICAgICAgICAgICAgICAgICAgICdpbmRpYW5yZWQxJywgJ2luZGlhbnJlZDInLCAnaW5kaWFucmVkMycsICdjYWRldGJsdWUnLAogICAgICAgICAgICAgICAgICAgICAgICAgJ3JlZCcsICdibHVlJywgJ3llbGxvdycsICdvcmFuZ2UnLCAnZ3JlZW4nLAogICAgICAgICAgICAgICAgICAgICAgICAgJ3doaXRlJywgJ3doaXRlJykKVihhYmMyMDE3TmV0KSRzaXplIDwtIGMoMjAsIHJlNG9yZGVyKjQwLCAyMCwgMjApClYoYWJjMjAxN05ldCkkZnJhbWUuY29sb3IgPC0gJ3doaXRlJwoKIyAtIHBsb3Qgdy4ge2lncmFwaH0KY29vcmRzIDwtIGxheW91dF8oYWJjMjAxN05ldCwgYXNfdHJlZSgpKQpwYXIobWFpPWMocmVwKDAsNCkpKQpwbG90KGFiYzIwMTdOZXQsCiAgICAgbGF5b3V0ID0gY29vcmRzLAogICAgIGVkZ2Uud2lkdGggPSAuNzUsCiAgICAgZWRnZS5jb2xvciA9ICJncmV5IiwKICAgICBlZGdlLmFycm93LnNpemUgPSAwLjM1LAogICAgIGVkZ2UuY3VydmVkID0gMC42LAogICAgIGVkZ2UubGFiZWwuZmFtaWx5ID0gInNhbnMiLAogICAgIGVkZ2UubGFiZWwuY29sb3IgPSAiYmxhY2siLAogICAgIGVkZ2UubGFiZWwuY2V4ID0gLjYsCiAgICAgdmVydGV4LnNoYXBlID0gImNpcmNsZSIsCiAgICAgdmVydGV4LmxhYmVsLmNvbG9yID0gImJsYWNrIiwKICAgICB2ZXJ0ZXgubGFiZWwuZm9udCA9IDEsCiAgICAgdmVydGV4LmxhYmVsLmZhbWlseSA9ICJzYW5zIiwKICAgICB2ZXJ0ZXgubGFiZWwuY2V4ID0gLjc1LAogICAgIHZlcnRleC5sYWJlbC5kaXN0ID0gLjI1LAogICAgIHZlcnRleC5sYWJlbC5kaXN0ID0gLjQ1LAogICAgIHJlc2NhbGUgPSBGLAogICAgIHhsaW0gPSBjKC0xLCAxKSwKICAgICB5bGltID0gYygwLCA0KSwKICAgICBtYXJnaW4gPSBjKHJlcCgwLDQpKSkKYGBgCgoKIyMjIDUuMyBDYW1wYWlnbiBNdWx0aS1DaGFubmVsIEF0dHJpYnV0aW9uIE1vZGVsOiBVc2VyIFJlZ2lzdHJhdGlvbgoKKipURUNITklDQUwgTk9URSoqOiBhIE1hcmtvdiBtb2RlbCBvZiAqb3JkZXIgNCogd2FzIHVzZWQsIHdpdGggYDFlOGAgdG90YWwgc2ltdWxhdGlvbiBydW5zIGZyb20gdGhlIHRyYW5zaXRpb24gbWF0cml4LgoKIyMjIyBSZW1vdmFsIEVmZmVjdHMKCmBgYHtyIGVjaG8gPSBULCB3YXJuaW5nID0gJ2hpZGUnLCBtZXNzYWdlID0gRiwgZXZhbCA9IFR9CiMjIyAtLS0gQmFubmVyIC0+IEV4aXQgcGF0aHMgLS0tICMjIwojIyMgLS0tIERlZmluaXRpb246IE4oQmFubmVyIEltcHJlc3Npb25zKSAtIE4oQmFubmVyQ2xpY2tzID09IExhbmRpbmcgUGFnZSBWaWV3cykKIyAtIGRlZmluZTogTihCYW5uZXIgSW1wcmVzc2lvbnMpCmJJbXAgPC0gYmFuSW1wU2V0ICU+JSAKICBncm91cF9ieShCYW5uZXIpICU+JSAKICBzdW1tYXJpc2UoQ291bnQgPSBzdW0oQ291bnQpKQpuQlQxIDwtIGJJbXAkQ291bnRbd2hpY2goYkltcCRCYW5uZXIgJWluJSAnQlQxJyldIApuQlQyIDwtIGJJbXAkQ291bnRbd2hpY2goYkltcCRCYW5uZXIgJWluJSAnQlQyJyldCm5CVDMgPC0gYkltcCRDb3VudFt3aGljaChiSW1wJEJhbm5lciAlaW4lICdCVDMnKV0KbkdJQiA8LSBiSW1wJENvdW50W3doaWNoKGJJbXAkQmFubmVyICVpbiUgJ0dJQl9MUCcpXSArIGJJbXAkQ291bnRbd2hpY2goYkltcCRCYW5uZXIgJWluJSAnR0lCX1JHJyldCiMgLSBkZWZpbmU6IE4oQmFubmVyQ2xpY2tzL1BhZ2VWaWV3cykKYkNsaWNrIDwtIGNsaWNrUGxvdFNldCAlPiUgCiAgZ3JvdXBfYnkoU291cmNlKSAlPiUgCiAgc3VtbWFyaXNlKENvdW50ID0gc3VtKENvdW50KSkKYkNsaWNrJFNvdXJjZSA8LSBnc3ViKCJfY2xpY2siLCAiIiwgYkNsaWNrJFNvdXJjZSwgZml4ZWQgPSBUKQojIC0gZGVmaW5lOiBOKEJhbm5lckltcHJlc3Npb25zKSAtIE4oQmFubmVyQ2xpY2tzL1BhZ2VWaWV3cykKbkJUMSA8LSBuQlQxIC0gYkNsaWNrJENvdW50W3doaWNoKGJDbGljayRTb3VyY2UgJWluJSAnQlQxJyldIApuQlQyIDwtIG5CVDIgLSBiQ2xpY2skQ291bnRbd2hpY2goYkNsaWNrJFNvdXJjZSAlaW4lICdCVDInKV0KbkJUMyA8LSBuQlQzIC0gYkNsaWNrJENvdW50W3doaWNoKGJDbGljayRTb3VyY2UgJWluJSAnQlQzJyldCm5HSUIgPC0gbkdJQiAtIGJDbGljayRDb3VudFt3aGljaChiQ2xpY2skU291cmNlICVpbiUgJ0dJQl9MUCcpXSArIGJDbGljayRDb3VudFt3aGljaChiQ2xpY2skU291cmNlICVpbiUgJ0dJQl9SRycpXQoKIyMjIC0tLSBCYW5uZXIgLT4gTGFuZGluZyBQYWdlIC0+IEV4aXQgcGF0aHMgLS0tICMjIwojIyMgLS0tIE4oQmFubmVyIENsaWNrcyA9PSBMYW5kaW5nIFBhZ2UgVmlld3MpIC0gTihSZWdpc3RyYXRpb24gUGFnZSBWaWV3cykKIyMjIC0tLSBOT1RFOiBUTFAgPT0gVGFzayBMYW5kaW5nIFBhZ2UgKEpldHp0TWl0bWFjaGVuKSwgR0xQID09IEdlbmVyYWwgTGFuZGluZyBQYWdlIChNYWNoIG1pdCkKbkJUMV9UTFAgPC0gYkNsaWNrJENvdW50W3doaWNoKGJDbGljayRTb3VyY2UgJWluJSAnQlQxJyldIC0gCiAgcGFnZVNvdXJjZSRuW3BhZ2VTb3VyY2UkUGFnZSAlaW4lICdTcGV6aWFsOkJlbnV0emVya29udG9fYW5sZWdlbicgJiBwYWdlU291cmNlJFNvdXJjZSAlaW4lICdKZXR6dE1pdG1hY2hlbl9CVDEnXQpuQlQyX1RMUCA8LSBiQ2xpY2skQ291bnRbd2hpY2goYkNsaWNrJFNvdXJjZSAlaW4lICdCVDInKV0gLSAKICBwYWdlU291cmNlJG5bcGFnZVNvdXJjZSRQYWdlICVpbiUgJ1NwZXppYWw6QmVudXR6ZXJrb250b19hbmxlZ2VuJyAmIHBhZ2VTb3VyY2UkU291cmNlICVpbiUgJ0pldHp0TWl0bWFjaGVuX0JUMiddCm5CVDNfVExQIDwtIGJDbGljayRDb3VudFt3aGljaChiQ2xpY2skU291cmNlICVpbiUgJ0JUMycpXSAtIAogIHBhZ2VTb3VyY2UkbltwYWdlU291cmNlJFBhZ2UgJWluJSAnU3BlemlhbDpCZW51dHplcmtvbnRvX2FubGVnZW4nICYgcGFnZVNvdXJjZSRTb3VyY2UgJWluJSAnSmV0enRNaXRtYWNoZW5fQlQzJ10KbkdJQl9HTFAgPC0gYkNsaWNrJENvdW50W3doaWNoKGJDbGljayRTb3VyY2UgJWluJSAnR0lCX0xQJyldIC0gCiAgcGFnZVNvdXJjZSRuW3BhZ2VTb3VyY2UkUGFnZSAlaW4lICdTcGV6aWFsOkJlbnV0emVya29udG9fYW5sZWdlbicgJiBwYWdlU291cmNlJFNvdXJjZSAlaW4lICdNYWNoX21pdCddCgojIyMgLS0tIEJhbm5lciAoLT4gTGFuZGluZyBQYWdlKSAtPiBSZWdpc3RyYXRpb24gUGFnZSAtPiBFeGl0IHBhdGhzIC0tLSAjIyMKIyMjIC0tLSBOKFJlZ2lzdHJhdGlvbiBQYWdlIFZpZXdzKSAtIE4oVXNlciBSZWdpc3RyYXRpb25zKQpiVXNlclJlZyA8LSB1c2VyUmVnICU+JSAKICBncm91cF9ieShldmVudF9jYW1wYWlnbikgJT4lIAogIHN1bW1hcmlzZShDb3VudCA9IG4oKSkKYlVzZXJSZWckZXZlbnRfY2FtcGFpZ24gPC0gdG91cHBlcihnc3ViKCJ3bWRlX2FiYzIwMTdfIiwgIiIsIGJVc2VyUmVnJGV2ZW50X2NhbXBhaWduLCBmaXhlZCA9IFQpKQpjb2xuYW1lcyhiVXNlclJlZylbMV0gPC0gJ0Jhbm5lcicKbkJUMV9UTFBfUlAgPC0gcGFnZVNvdXJjZSRuW3BhZ2VTb3VyY2UkUGFnZSAlaW4lICdTcGV6aWFsOkJlbnV0emVya29udG9fYW5sZWdlbicgJiBwYWdlU291cmNlJFNvdXJjZSAlaW4lICdKZXR6dE1pdG1hY2hlbl9CVDEnXSAtIAogIGJVc2VyUmVnJENvdW50W2JVc2VyUmVnJEJhbm5lciAlaW4lICdCVDEnXQpuQlQyX1RMUF9SUCA8LSBwYWdlU291cmNlJG5bcGFnZVNvdXJjZSRQYWdlICVpbiUgJ1NwZXppYWw6QmVudXR6ZXJrb250b19hbmxlZ2VuJyAmIHBhZ2VTb3VyY2UkU291cmNlICVpbiUgJ0pldHp0TWl0bWFjaGVuX0JUMiddIC0gCiAgYlVzZXJSZWckQ291bnRbYlVzZXJSZWckQmFubmVyICVpbiUgJ0JUMiddCm5CVDNfVExQX1JQIDwtIHBhZ2VTb3VyY2UkbltwYWdlU291cmNlJFBhZ2UgJWluJSAnU3BlemlhbDpCZW51dHplcmtvbnRvX2FubGVnZW4nICYgcGFnZVNvdXJjZSRTb3VyY2UgJWluJSAnSmV0enRNaXRtYWNoZW5fQlQzJ10gLSAKICBiVXNlclJlZyRDb3VudFtiVXNlclJlZyRCYW5uZXIgJWluJSAnQlQzJ10KbkdJQl9HTFBfUlAgPC0gcGFnZVNvdXJjZSRuW3BhZ2VTb3VyY2UkUGFnZSAlaW4lICdNYWNoX21pdCcgJiBwYWdlU291cmNlJFNvdXJjZSAlaW4lICdHSUJfTFBfY2xpY2snXSAtIAogIGJVc2VyUmVnJENvdW50W2JVc2VyUmVnJEJhbm5lciAlaW4lICdHSUJfTFAnXQpuR0lCX1JQIDwtIHBhZ2VTb3VyY2UkbltwYWdlU291cmNlJFBhZ2UgJWluJSAnU3BlemlhbDpCZW51dHplcmtvbnRvX2FubGVnZW4nICYgcGFnZVNvdXJjZSRTb3VyY2UgJWluJSAnR0lCX1JHX2NsaWNrJ10gLSAKICBiVXNlclJlZyRDb3VudFtiVXNlclJlZyRCYW5uZXIgJWluJSAnR0lCX1JHJ10KCiMjIyAtLS0gQmFubmVyICgtPiBMYW5kaW5nIFBhZ2UpIC0+IFJlZ2lzdHJhdGlvbiBQYWdlIC0+IFJlZ2lzdHJhdGlvbgpuQlQxX1RMUF9SUF9SZWcgPC0gcmVnRGF0YSRSZWdpc3RyYXRpb25zW3doaWNoKHJlZ0RhdGEkQ2FtcGFpZ24gJWluJSAnQlQxJyldCm5CVDJfVExQX1JQX1JlZyA8LSByZWdEYXRhJFJlZ2lzdHJhdGlvbnNbd2hpY2gocmVnRGF0YSRDYW1wYWlnbiAlaW4lICdCVDInKV0KbkJUM19UTFBfUlBfUmVnIDwtIHJlZ0RhdGEkUmVnaXN0cmF0aW9uc1t3aGljaChyZWdEYXRhJENhbXBhaWduICVpbiUgJ0JUMycpXQpuR0lCX0dMUF9SUF9SZWcgPC0gcmVnRGF0YSRSZWdpc3RyYXRpb25zW3doaWNoKHJlZ0RhdGEkQ2FtcGFpZ24gJWluJSAnR0lCX0xQJyldCm5HSUJfUlBfUmVnIDwtIHJlZ0RhdGEkUmVnaXN0cmF0aW9uc1t3aGljaChyZWdEYXRhJENhbXBhaWduICVpbiUgJ0dJQl9SRycpXQoKIyMjIC0tLSBkYXRhc2V0Cm1jYURhdGEgPC0gZGF0YS5mcmFtZShwYXRoID0gYyhkZXBhcnNlKHN1YnN0aXR1dGUobkJUMSkpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGVwYXJzZShzdWJzdGl0dXRlKG5CVDIpKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRlcGFyc2Uoc3Vic3RpdHV0ZShuQlQzKSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkZXBhcnNlKHN1YnN0aXR1dGUobkdJQikpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGVwYXJzZShzdWJzdGl0dXRlKG5CVDFfVExQKSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkZXBhcnNlKHN1YnN0aXR1dGUobkJUMl9UTFApKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRlcGFyc2Uoc3Vic3RpdHV0ZShuQlQzX1RMUCkpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGVwYXJzZShzdWJzdGl0dXRlKG5HSUJfR0xQKSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkZXBhcnNlKHN1YnN0aXR1dGUobkJUMV9UTFBfUlApKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRlcGFyc2Uoc3Vic3RpdHV0ZShuQlQyX1RMUF9SUCkpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGVwYXJzZShzdWJzdGl0dXRlKG5CVDNfVExQX1JQKSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkZXBhcnNlKHN1YnN0aXR1dGUobkdJQl9HTFBfUlApKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRlcGFyc2Uoc3Vic3RpdHV0ZShuR0lCX1JQKSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkZXBhcnNlKHN1YnN0aXR1dGUobkJUMV9UTFBfUlBfUmVnKSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkZXBhcnNlKHN1YnN0aXR1dGUobkJUMl9UTFBfUlBfUmVnKSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkZXBhcnNlKHN1YnN0aXR1dGUobkJUM19UTFBfUlBfUmVnKSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkZXBhcnNlKHN1YnN0aXR1dGUobkdJQl9HTFBfUlBfUmVnKSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkZXBhcnNlKHN1YnN0aXR1dGUobkdJQl9SUF9SZWcpKQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKSwKICAgICAgICAgICAgICAgICAgICAgIHRvdGFsX2NvbnZlcnNpb25zID0gYygwLCAwLCAwLCAwLCAwLCAwLCAwLCAwLCAwLCAwLCAwLCAwLCAwLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuQlQxX1RMUF9SUF9SZWcsIG5CVDJfVExQX1JQX1JlZywgbkJUMV9UTFBfUlBfUmVnLCBuR0lCX0dMUF9SUF9SZWcsIG5HSUJfUlBfUmVnCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKSwKICAgICAgICAgICAgICAgICAgICAgIHRvdGFsX251bGwgPSBjKG5CVDEsIG5CVDIsIG5CVDMsIG5HSUIsIG5CVDFfVExQLCBuQlQyX1RMUCwgbkJUM19UTFAsIG5HSUJfR0xQLCBuQlQxX1RMUF9SUCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5CVDJfVExQX1JQLCBuQlQzX1RMUF9SUCwgbkdJQl9HTFBfUlAsIG5HSUJfUlAsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAwLCAwLCAwLCAwLCAwCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICApLAogICAgICAgICAgICAgICAgICAgICAgc3RyaW5nc0FzRmFjdG9ycyA9IEYpCgojIC0gY29ycmVjdCBwYXRoczoKbWNhRGF0YSRwYXRoIDwtIGdzdWIoIm4iLCAiIiwgbWNhRGF0YSRwYXRoLCBmaXhlZCA9IFQpCm1jYURhdGEkcGF0aCA8LSBnc3ViKCJfIiwgIiA+ICIsIG1jYURhdGEkcGF0aCwgZml4ZWQgPSBUKQplZGl0RW5kcyA8LSB3aGljaChncmVwbCgiUmVnIiwgbWNhRGF0YSRwYXRoLCBmaXhlZCA9IFQpKQpmb3IgKGkgaW4gMTpsZW5ndGgoZWRpdEVuZHMpKSB7CiAgd1BhdGggPC0gd2hpY2gobWNhRGF0YSRwYXRoICVpbiUgZ3N1YigiID4gUmVnIiwgIiIsIG1jYURhdGEkcGF0aFtlZGl0RW5kc1tpXV0sIGZpeGVkID0gVCkpCiAgd091dCA8LSB3aGljaChtY2FEYXRhJHBhdGggPT0gbWNhRGF0YSRwYXRoW2VkaXRFbmRzW2ldXSkKICB3UGF0aCA8LSBzZXRkaWZmKHdQYXRoLCB3T3V0KQogIG1jYURhdGEkdG90YWxfY29udmVyc2lvbnNbd1BhdGhdIDwtIG1jYURhdGEkdG90YWxfY29udmVyc2lvbnNbd091dF0KfQptY2FEYXRhIDwtIG1jYURhdGFbLXdoaWNoKGdyZXBsKCJSZWciLCBtY2FEYXRhJHBhdGgsIGZpeGVkID0gVCkpLCBdCgojIyMgLS0tIE1DQSBtb2RlbAphYmMyMDE3TW9kZWwgPC0gbWFya292X21vZGVsKG1jYURhdGEsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdmFyX3BhdGggPSAicGF0aCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdmFyX2NvbnYgPSAidG90YWxfY29udmVyc2lvbnMiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZhcl9udWxsID0gInRvdGFsX251bGwiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIG9yZGVyID0gNCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuc2ltID0gMWU4LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIG91dF9tb3JlID0gVCkKIyAtIGNvbGxlY3QgcmVtb3ZhbCBlZmZlY3RzIGZvciB0aGUgbmV4dCBwbG90OgpyZTRvcmRlciA8LSBhYmMyMDE3TW9kZWwkcmVtb3ZhbF9lZmZlY3RzJHJlbW92YWxfZWZmZWN0cwojIyMgLS0tIFJlbW92YWwgRWZmZWN0czoKcmUgPC0gYXMuZGF0YS5mcmFtZShhYmMyMDE3TW9kZWwkcmVtb3ZhbF9lZmZlY3RzKQpjb2xuYW1lcyhyZSkgPC0gYygnQ2hhbm5lbCcsICdSZW1vdmFsIEVmZmVjdCcpCnJlJENoYW5uZWwgPC0gZmFjdG9yKHJlJENoYW5uZWwsIGxldmVscyA9IGFzLmNoYXJhY3RlcihhYmMyMDE3TW9kZWwkcmVtb3ZhbF9lZmZlY3RzJGNoYW5uZWxfbmFtZSkpCmdwbG90IDwtIGdncGxvdChkYXRhID0gcmUsIAogICAgICAgICAgICAgICAgYWVzKHggPSBDaGFubmVsLAogICAgICAgICAgICAgICAgICAgIHkgPSBgUmVtb3ZhbCBFZmZlY3RgLAogICAgICAgICAgICAgICAgICAgIGxhYmVsID0gcm91bmQoYFJlbW92YWwgRWZmZWN0YCwgMikpCiAgICAgICAgICAgICAgICApICsgCiAgZ2VvbV9iYXIod2lkdGggPSAuMSwgY29sb3IgPSAiZGFya2JsdWUiLCBmaWxsID0gIndoaXRlIiwgc3RhdCA9ICJpZGVudGl0eSIpICsgCiAgZ2VvbV9sYWJlbChzaXplID0gMykgKyAKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gY29tbWEpICsgCiAgeGxhYignQ2FtcGFpZ24gQ2hhbm5lbCcpICsgeWxhYignUmVtb3ZhbCBFZmZlY3QnKSArIAogIHlsaW0oYygwLCAxKSkgKyAKICBnZ3RpdGxlKCdDYW1wYWlnbiBNdWx0aS1DaGFubmVsIEF0dHJpYnV0aW9uOiBSZW1vdmFsIEVmZmVjdHMnKSArIAogIHRoZW1lX21pbmltYWwoKSArIAogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSkKc3VwcHJlc3NXYXJuaW5ncyhwcmludChncGxvdCkpCmBgYAoKIyMjIyBDYW1wYWlnbiBUcmFuc2l0aW9uIEdyYXBoCgpFYWNoIG5vZGUgaW4gdGhlIGZvbGxvd2luZyBncmFwaCByZXByZXNlbnRzIGEgcGFydGljdWxhciBjYW1wYWlnbiBjaGFubmVsLiBUaGUgZWRnZXMgb2YgdGhlIGdyYXBoIGFyZSBsYWJlbGVkIGJ5IHRoZSByZXNwZWN0aXZlIHRyYW5zaXRpb24gcHJvYmFiaWxpdGllcyBiZXR3ZWVuIHRoZSBjaGFubmVscy4gVGhlIHNpemUgb2YgdGhlIG5vZGUgY29ycmVzcG9uZHMgdG8gaXRzIHJlbW92YWwgZWZmZWN0LiAqKlRFQ0hOSUNBTCBOT1RFOioqIHRoZSByZW1vdmFsIGVmZmVjdHMgYXJlIGRlcml2ZWQgZnJvbSBhIE1hcmtvdiBtb2RlbCBvZiBvcmRlciA0LCB3aGlsZSB0aGUgdHJhbnNpdGlvbmFsIHByb2JhYmlsaXRpZXMgYXJlIGRlcml2ZWQgZGlyZWN0bHkgZnJvbSB0aGUgMXN0IG9yZGVyIG1vZGVsLgoKYGBge3IgZWNobyA9IFQsIHdhcm5pbmcgPSAnaGlkZScsIG1lc3NhZ2UgPSBGLCBldmFsID0gVH0KIyMjIC0tLSBNQ0EgbW9kZWw6IDFzdCBvcmRlciBmb3IgY2hhbm5lbC10by1jaGFubmVsIHRyYW5zaXRpb25zCmFiYzIwMTdNb2RlbCA8LSBtYXJrb3ZfbW9kZWwobWNhRGF0YSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICB2YXJfcGF0aCA9ICJwYXRoIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICB2YXJfY29udiA9ICJ0b3RhbF9jb252ZXJzaW9ucyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdmFyX251bGwgPSAidG90YWxfbnVsbCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgb3JkZXIgPSAxLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIG91dF9tb3JlID0gVCkKIyMjIC0tLSBwbG90IHcuIHtpZ3JhcGh9CmFiYzIwMTdOZXQgPC0gZGF0YS5mcmFtZShvdWdvaW5nID0gYWJjMjAxN01vZGVsJHRyYW5zaXRpb25fbWF0cml4JGNoYW5uZWxfZnJvbSwKICAgICAgICAgICAgICAgICAgICAgICAgIGluY29taW5nID0gYWJjMjAxN01vZGVsJHRyYW5zaXRpb25fbWF0cml4JGNoYW5uZWxfdG8sCiAgICAgICAgICAgICAgICAgICAgICAgICBzdHJpbmdzQXNGYWN0b3JzID0gRikKYWJjMjAxN05ldCRvdWdvaW5nIDwtIHNhcHBseShhYmMyMDE3TmV0JG91Z29pbmcsIGZ1bmN0aW9uKHgpIHsKICBjaCA8LSBnc3ViKCIoc3RhcnQpIiwgIlNUQVJUIiwgZml4ZWQgPSBULCB4KQogIGNoIDwtIGdzdWIoIihudWxsKSIsICJFWElUIiwgZml4ZWQgPSBULCBjaCkKICBjaCA8LSBnc3ViKCIoY29udmVyc2lvbikiLCAiUkVHSVNUUkFUSU9OIiwgZml4ZWQgPSBULCBjaCkKICBjaAp9KQphYmMyMDE3TmV0JGluY29taW5nIDwtIHNhcHBseShhYmMyMDE3TmV0JGluY29taW5nLCBmdW5jdGlvbih4KSB7CiAgY2ggPC0gZ3N1YigiKHN0YXJ0KSIsICJTVEFSVCIsIGZpeGVkID0gVCwgeCkKICBjaCA8LSBnc3ViKCIobnVsbCkiLCAiRVhJVCIsIGZpeGVkID0gVCwgY2gpCiAgY2ggPC0gZ3N1YigiKGNvbnZlcnNpb24pIiwgIlJFR0lTVFJBVElPTiIsIGZpeGVkID0gVCwgY2gpCiAgY2gKfSkKCmFiYzIwMTdOZXQgPC0gZ3JhcGguZGF0YS5mcmFtZShhYmMyMDE3TmV0LCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRpcmVjdGVkID0gVCkKRShhYmMyMDE3TmV0KSRsYWJlbCA8LSByb3VuZChhYmMyMDE3TW9kZWwkdHJhbnNpdGlvbl9tYXRyaXgkdHJhbnNpdGlvbl9wcm9iYWJpbGl0eSwgMikKVihhYmMyMDE3TmV0KSRjb2xvciA8LSBjKCd3aGl0ZScsIAogICAgICAgICAgICAgICAgICAgICAgICAgJ2luZGlhbnJlZDEnLCAnaW5kaWFucmVkMicsICdpbmRpYW5yZWQzJywgJ2NhZGV0Ymx1ZScsCiAgICAgICAgICAgICAgICAgICAgICAgICAncmVkJywgJ2JsdWUnLCAneWVsbG93JywgCiAgICAgICAgICAgICAgICAgICAgICAgICAnd2hpdGUnLCAnd2hpdGUnKQpWKGFiYzIwMTdOZXQpJHNpemUgPC0gYygyMCwgcmU0b3JkZXIqNDAsIDIwLCAyMCkKVihhYmMyMDE3TmV0KSRmcmFtZS5jb2xvciA8LSAnd2hpdGUnCgojIC0gcGxvdCB3LiB7aWdyYXBofQpjb29yZHMgPC0gbGF5b3V0XyhhYmMyMDE3TmV0LCBhc190cmVlKCkpCnBhcihtYWk9YyhyZXAoMCw0KSkpCnBsb3QoYWJjMjAxN05ldCwKICAgICBsYXlvdXQgPSBjb29yZHMsCiAgICAgZWRnZS53aWR0aCA9IC43NSwKICAgICBlZGdlLmNvbG9yID0gImdyZXkiLAogICAgIGVkZ2UuYXJyb3cuc2l6ZSA9IDAuMzUsCiAgICAgZWRnZS5jdXJ2ZWQgPSAwLjYsCiAgICAgZWRnZS5sYWJlbC5mYW1pbHkgPSAic2FucyIsCiAgICAgZWRnZS5sYWJlbC5jb2xvciA9ICJibGFjayIsCiAgICAgZWRnZS5sYWJlbC5jZXggPSAuNiwKICAgICB2ZXJ0ZXguc2hhcGUgPSAiY2lyY2xlIiwKICAgICB2ZXJ0ZXgubGFiZWwuY29sb3IgPSAiYmxhY2siLAogICAgIHZlcnRleC5sYWJlbC5mb250ID0gMSwKICAgICB2ZXJ0ZXgubGFiZWwuZmFtaWx5ID0gInNhbnMiLAogICAgIHZlcnRleC5sYWJlbC5jZXggPSAuNzUsCiAgICAgdmVydGV4LmxhYmVsLmRpc3QgPSAuMjUsCiAgICAgdmVydGV4LmxhYmVsLmRpc3QgPSAuNDUsCiAgICAgcmVzY2FsZSA9IEYsCiAgICAgeGxpbSA9IGMoLTEsIDEpLAogICAgIHlsaW0gPSBjKDAsIDQpLAogICAgIG1hcmdpbiA9IGMocmVwKDAsNCkpKQpgYGAKCiMjIyMgU3VtbWFyeQoKVGhlIGxhbmRpbmcgcGFnZSBmb3Igc3BlY2lmaWMgdGFza3MgKGBKZXR6dE1pdG1hY2hlbmAsIHRoZSBgVExQYCBjaGFubmVsIGluIHRoZSBncmFwaCkgYW5kIHRoZSBgR0lCYCBjYW1wYWlnbiBhcmUgZXNzZW50aWFsbHkgbm8gZGlmZmVyZW50IGluIHJlc3BlY3QgdG8gaG93IG11Y2ggdGhleSBpbmZsdWVuY2UgdXNlciByZWdpc3RyYXRpb24uIFdlIGhhdmUgbGVhcm5lZCBmcm9tIHRoZSBBL0IgdGVzdHMgdGhhdCBubyBpbmRpdmlkdWFsIGBCVGAgKGkuZS4gc3BlY2lmaWMgdGFzaykgYmFubmVyIGNvbXBhcmVzIHRvIHRoZSBwZXJmb3JtYW5jZSBvZiBgR0lCX1JHYCB3aGljaCBsZWFkcyBkaXJlY3RseSB0byB0aGUgcmVnaXN0cmFpb24gcGFnZS4gSG93ZXZlciwgd2hlbiBjb25zaWRlcmVkIHRvZ2V0aGVyLCB0aGUgYmFubmVycyBsZWFkaW5nIHRvIHRoZSBgSmV0enRNaXRtYWNoZW5gIGhhdmUgYSBwZXJmb3JtYW5jZSBjb21wYXJhYmxlIHRvIGBHSUJfUkdgLiBUaGUgR2VuZXJhbCBJbnZpdGF0aW9uIGxhbmRpbmcgcGFnZSBgTWFjaF9taXRgIGxhY2tzIHN1Y2ggYW4gZWZmZWN0LgoKCiMjIyA1LjMgQ2FtcGFpZ24gRXZhbHVhdGlvbiBTdW1tYXJ5CgoqKkFTU1VNUFRJT05TKiogYXMgc3RhdGVkIGluIHRoZSBbQ2FtcGFpZ24gS2lja09mZiBQcmVzZW50YXRpb25dKGh0dHBzOi8vZG9jcy5nb29nbGUuY29tL3ByZXNlbnRhdGlvbi9kLzE4SHE3VUxrV1ZvUXRNdjVFSTJKUGNZRWRJbFRjMmg4YmdkTVF3QzM1Z0U4L2VkaXQjc2xpZGU9aWQuZzIyODNkMzlkOWRfMF8yMzEpOgoKLSAqKkFzc3VtcHRpb24gMToqKiBNb3JlIHVzZXJzIHJlZ2lzdGVyIHdoZW4gZ2l2ZW4gYSBjbGVhciBhbmQgbG93IGxldmVsIGVudHJ5IHRhc2suICoqUkVTVUxUUzoqKiBXaGVuIGNvbXBhcmluZyBpbmRpdmlkdWFsIGJhbm5lciBjYW1wYWlnbnMsIEEvQiB0ZXN0aW5nIHNob3dzIHRoYXQgbW9yZSB1c2VycyByZWdpc3RlciB2aWEgdGhlIEdlbmVyYWwgSW52aXRhdGlvbiBCYW5uZXIgY2FtcGFpZ24sIGVzcGVjaWFsbHkgd2hlbiBnaXZlbiBubyBpbnRlcm1lZGlhdGUgbGFuZGluZyBwYWdlIHByaW9yIHRvIHRoZSByZWdpc3RyYXRpb24gcGFnZS4gSG93ZXZlciwgdGhlIGBKZXR6dE1pdG1hY2hlbmAgY2FtcGFnaW4gaW4gZ2VuZXJhbCBoYXMgYSBwZXJmb3JtYW5jZSBjb21wYXJhYmxlIHRvIHRoZSBgR0lCYCBjYW1wYWlnbiwgd2hpbGUgdGhlIGBKZXR6dE1pdG1hY2hlbmAgcGFnZSB3YXMgY2VydGFpbmx5IG1vcmUgaW1wb3J0YW50IGZvciB1c2VyIHJlZ2lzdHJhdGlvbiB0aGFuIHRoZSBnZW5lcmFsIGBNYWNoX21pdGAgcGFnZSAtIGFzIHdlIGhhdmUgbGVhcm5lZCBmcm9tIHRoZSBjYW1wYWlnbiBNdWx0aS1DaGFubmVsIEF0dHJpYnV0aW9uIG1vZGVsLgoKLSAqKkFzc3VtcHRpb24gMjoqKiBBIGxhbmRpbmcgcGFnZSB3aXRoIG1vcmUgaW5mb3JtYXRpb24gYmVmb3JlIHJlZ2lzdHJhdGlvbiBpcyBuZWNlc3NhcnkuICoqUkVTVUxUUzoqKiBBL0IgdGVzdGluZyBzaG93cyB0aGF0IG1vcmUgdXNlcnMgcmVnaXN0ZXIgdmlhIGBHSUJfUkdgIGJhbm5lciBjYW1wYWlnbiB0aGF0IGxlYWRzIGRpcmVjdGx5IHRvIHRoZSByZWdpc3RyYXRpb24gcGFnZSB0aGFuIHZpYSB0aGUgYEdJQl9MUGAgYmFubmVyIGNhbXBhaWduIHRoYXQgaGFzIGFuIGludGVybWVkaWF0ZSBsYW5kaW5nIHBhZ2UuIEhvd2V2ZXIsIGBBc3N1bXB0aW9uMiBgIGlzIHN1cHBvcnRlZCBieSBhIGhpZ2ggcmVtb3ZhbCBlZmZlY3Qgb2YgdGhlIGBKZXR6dE1pdG1hY2hlbmAgcGFnZS4gCgotICoqQXNzdW1wdGlvbiAzOioqIEEgZ2VuZXJhbCBpbnZpdGF0aW9uIGhhcyBhIGxvd2VyIGNvbnZlcnNpb24gcmF0ZSB0aGFuIHNwZWNpZmljIGludml0YXRpb25zIHRvIHJlZ2lzdGVyLiAqKlJFU1VMVFM6KiogVGhlIHRvdGFsIG51bWJlciBvZiByZWdpc3RlcmVkIHVzZXJzIHZpYSB0aGUgYEpldHp0TWl0bWFjaGVuYCBjYW1wYWdpbiAoYEJUMWAsIGBCVDJgLCBhbmQgYEJUM2AgYmFubmVyIGNhbXBhaWducyB0YWtlbiB0b2dldGhlcikgaXMgNTM1LCB3aGlsZSB0aGUgdG90YWwgbnVtYmVyIG9mIHVzZXJzIHJlZ2lzdGVyZWQgdmlhIHRoZSBHZW5lcmFsIEludml0YXRpb24gY2FtcGFpZ24gaXMgNTE5LCBhbiBhbG1vc3QgNTAtNTAgc3BsaXQuCgoqKk5PVEU6KiogKipBbGwgdGhlc2UgYXNzdW1wdGlvbnMgYXJlIHZhbGlkIGlmIHdlIGNvbnNpZGVyIHRoZSBjcml0ZXJpb24gb2YgbWFraW5nIGFuIGVkaXQgYXQgYWxsIGluc3RlYWQuKiogCgoqKlNVR0dFU1RJT05TKioKCi0gKipTdWdnZXN0aW9uIE5vLiAxLioqIFJlbW92ZSB0aGUgYEdJQl9SR2AgYmFubmVyIGNhbXBhaWduIGZyb20gZnV0dXJlIGNhbXBhaWducy4gSXQgZHJpdmVzIGFsbW9zdCA5MCUgb2YgdGhlIHRyYWZmaWMgdG93YXJkcyB0aGUgcmVnaXN0cmF0aW9uIHBhZ2Ugd2hpbGUgYmVpbmcgdGhlIGxlYXN0IGVmZmljaWVudCBpbiB0ZXJtcyBvZiBpbmZsdWVuY2luZyBuZXcgdXNlciBlZGl0cyBhdCB0aGUgc2FtZSB0aW1lICgqKk5PVEUqKjogbGVhc3QgZWZmaWNpZW50IGluIHRlcm1zIG9mIHRoZSBleHBlY3RlZCBudW1iZXIgb2YgdXNlciBlZGl0cywgbm90IGluIHRlcm1zIG9mIG1ha2luZyBhbnkgZWRpdHMgYXQgYWxsKS4gVGhhdCB3b3VsZCBwcm9iYWJseSBtZWFuIHRoYXQgYGRld2lraWAgd291bGQgYWNxdWlyZSBsZXNzIG5ldyB1c2VycyBkdXJpbmcgdGhlIGNhbXBhaWduLCBidXQgYWdhaW4gdGhlIGdvYWwgaXMgcHJvYmFibHkgZm9yIGl0IHRvIGFjcXVpcmUgbmV3IGVkaXRvcnMuIE9yLCBldmVuIGJldHRlciwgdGFrZSBhIGxvb2sgYXQgbXkgYFN1Z2dlc3Rpb24gTm8uIDJgLgoKLSAqKlN1Z2dlc3Rpb24gTm8uIDIuKiogVGhpbmsgYWJvdXQgdGhlIHBvc3NpYmlsaXR5IHRvIGludGVncmF0ZSB0aGUgY2FtcGFpZ24gY29udGVudCAoZS5nLiB3aGF0IGlzIG9uIHRoZSBsYW5kaW5nIHBhZ2VzIG5vdykgdG8gdGhlIHJlZ2lzdHJhdGlvbiBwYWdlICpkaXJlY3RseSouIFJhdGlvOiB0aGUgYEdJQl9SR2AgYmFubmVyIGNhbXBhaWduIGhhcyBubyBpbnRlcm1lZGlhdGUgbGFuZGluZyBwYWdlIGJldHdlZW4gYmFubmVyIHByZXNlbnRhdGlvbiBhbmQgcmVnaXN0cmF0aW9uLCBsZWFkaW5nIHRvIHRoZSBoaWdoZXN0IG51bWJlciBvZiByZWdpc3RlcmVkIG5ldyB1c2Vyczsgb24gdGhlIG90aGVyIGhhbmQsIHRob3NlIGJhbm5lciBjYW1wYWlnbnMgdGhhdCBpbnN0YW50aWF0ZSBhIHNwZWNpZmljIHRhc2sgbGVhZCB0byBoYXZpbmcgbW9yZSB1c2VyIGVkaXRzIG9uIHRoZSBhdmVyYWdlIHRoYW4gaXQgKGluIGdlbmVyYWw7IHRoaXMgaXMgbm90IHZhbGlkIGZvciBgQlQyYCkuICoqTWF5YmUgaW50ZWdyYXRpbmcgdGhlIGNhbXBhaWduIGNvbnRlbnQgd2l0aCB0aGUgcmVnaXN0cmF0aW9uIHBhZ2UgY2FuIHByb3ZpZGUgYSBtb3JlIHBvd2VyZnVsIGNvbWJpbmF0aW9uIHRoYXQgd291bGQgYWZmZWN0IHBvc2l0aXZlbHkgYm90aCByZWdpc3RyYXRpb24gYW5kIGZ1dHVyZSBlZGl0aW5nLioqCgotICoqU3VnZ2VzdGlvbiBOby4gMy4qKiBSZW1vdmUgdGhlIGBHdWlkZWQgVG91cmAgZnJvbSBvdXIgZnV0dXJlIGNhbXBhaWduczsgdGhlIGFuYWx5c2lzIG9mIGl0cyBjYXVzYWwgcG93ZXIgc3VnZ2VzdHMgdGhhdCBpdCBoYXMgYSBuZWdhdGl2ZSBpbmZsdWVuY2UgdG93YXJkcyBtYWtpbmcgYXQgbGVhc3Qgb25lIGVkaXQgb24gYmVoYWxmIG9mIGEgbmV3bHkgcmVnaXN0ZXJlZCB1c2VyLgoKIyMgNi4gUG9zdC1DYW1wYWlnbiBBbmFseXRpY3MKClRoaXMgc2VjdGlvbiBwcm92aWRlcyBzZXZlcmFsIGluc2lnaHRzIHRoYXQgd2VyZSBzb3VnaHQgb24gdGhlIGJlaGFsZiBvZiB0aGUgY2FtcGFpZ24gbWFuYWdlbWVudCB0ZWFtIGZvbGxvd2luZyB0aGUgZW5kIG9mIHRoZSBBdXR1bW4gQmFubmVyIENhbXBhaWduIDIwMTcuCgojIyMgNi4gMSAxMC4gT2N0b2JlciAyMDE3OiBhIGNoYW5nZSBpbiBiYW5uZXJzIG9jY3Vycy4gRGlkIGl0IGluZmx1ZW5jZSAoYSkgdGhlIG51bWJlciBvZiB1c2VyIHJlZ2lzdHJhdGlvbnMgYW5kIChiKSB0aGUgbnVtYmVyIG9mIHVzZXIgZWRpdHM/CgpGaXJzdCwgbGV04oCZcyBoYXZlIGEgbG9vayBhdCB0aGUgdG90YWwgbnVtYmVyIG9mIHVzZXIgcmVnaXN0cmF0aW9ucyBkYWlseToKCmBgYHtyIGVjaG8gPSBULCB3YXJuaW5nID0gJ2hpZGUnLCBtZXNzYWdlID0gRiwgZXZhbCA9IFR9CnJlZ1Bsb3RTZXREYWlseSREYXRlIDwtIGZhY3RvcihyZWdQbG90U2V0RGFpbHkkRGF0ZSwgbGV2ZWxzID0gc29ydChyZWdQbG90U2V0RGFpbHkkRGF0ZSkpCmdncGxvdChyZWdQbG90U2V0RGFpbHksIGFlcyh4ID0gRGF0ZSwgeSA9IFJlZ2lzdHJhdGlvbnMpKSArCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIAogICAgICAgICAgIHBvc2l0aW9uID0gImRvZGdlIiwgCiAgICAgICAgICAgd2lkdGggPSAuMikgKwogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBjb21tYSkgKwogIGdndGl0bGUoJ0F1dHVtbiBCYW5uZXIgQ2FtcGFpZ24gMjAxNzogVG90YWwgVXNlciBSZWdpc3RyYXRpb25zIERhaWx5JykgKwogIHRoZW1lX21pbmltYWwoKSArIAogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIHNpemUgPSA4LCBoanVzdCA9IDEpKSArCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApKSArCiAgdGhlbWUobGVnZW5kLnRpdGxlID0gZWxlbWVudF9ibGFuaygpKSArCiAgdGhlbWUocGFuZWwuZ3JpZC5tYWpvci54ID0gZWxlbWVudF9ibGFuaygpKSArCiAgdGhlbWUocGFuZWwuZ3JpZC5taW5vci54ID0gZWxlbWVudF9ibGFuaygpKSArCiAgdGhlbWUocGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSkKYGBgCgpgYGB7ciBlY2hvID0gVCwgd2FybmluZyA9ICdoaWRlJywgbWVzc2FnZSA9IEYsIGV2YWwgPSBUfQpwb3B1bGF0aW9uUCA8LSBjKDUvOSwgNC85KQpuIDwtIHN1bShyZWdQbG90U2V0RGFpbHkkUmVnaXN0cmF0aW9ucykKZXhwZWN0ZWRDb3VudHMgPC0gbipwb3B1bGF0aW9uUApzIDwtIGMoc3VtKHJlZ1Bsb3RTZXREYWlseSRSZWdpc3RyYXRpb25zWzE6NV0pLCBzdW0ocmVnUGxvdFNldERhaWx5JFJlZ2lzdHJhdGlvbnNbNjo5XSkpCnByaW50KHBhc3RlKCJFeHBlY3RlZDogIiwgcGFzdGUocm91bmQoZXhwZWN0ZWRDb3VudHMsIDIpLCBjb2xsYXBzZSA9ICIsICIpKSkKYGBgCgpgYGB7ciBlY2hvID0gVCwgd2FybmluZyA9ICdoaWRlJywgbWVzc2FnZSA9IEYsIGV2YWwgPSBUfQpwcmludChwYXN0ZSgiRGF0YXNldDogIiwgcGFzdGUocywgY29sbGFwc2UgPSAiLCAiKSkpCmBgYAoKYGBge3IgZWNobyA9IFQsIHdhcm5pbmcgPSAnaGlkZScsIG1lc3NhZ2UgPSBGLCBldmFsID0gVH0KY2hpU3EgPC0gc3VtKCgocyAtIGV4cGVjdGVkQ291bnRzKV4yKS9leHBlY3RlZENvdW50cykKcHJpbnQocGFzdGUoIkNoaS1TcXVhcmUgU3RhdGlzdGljczoiLCBjaGlTcSwgc2VwID0gIiAiKSkKYGBgCgpgYGB7ciBlY2hvID0gVCwgd2FybmluZyA9ICdoaWRlJywgbWVzc2FnZSA9IEYsIGV2YWwgPSBUfQojIC0gZGVncmVlcyBvZiBmcmVlZG9tCmRmIDwtIDIgLSAxICMgayA9PSAyID09IG51bWJlciBvZiBjYXRlZ29yaWVzCnByaW50KHBhc3RlKCJELkYuOiIsIGRmLCBzZXAgPSAiICIpKQpgYGAKCmBgYHtyIGVjaG8gPSBULCB3YXJuaW5nID0gJ2hpZGUnLCBtZXNzYWdlID0gRiwgZXZhbCA9IFR9CiMgLSBUZXN0IHNpZ25pZmljYW5jZSwgYWxwaGEgPT0gLjA1CnNpZyA8LSBwY2hpc3EoY2hpU3EsIGRmLCBsb3dlci50YWlsPUYpICMgdXBwZXIgdGFpbApwcmludChwYXN0ZSgiVHlwZSBJIEVycm9yIFByb2IuOiIsIHNpZywgc2VwID0gIiAiKSkKYGBgCgpgYGB7ciBlY2hvID0gVCwgd2FybmluZyA9ICdoaWRlJywgbWVzc2FnZSA9IEYsIGV2YWwgPSBUfQplZGl0c0RhaWx5IDwtIGVkaXRHVERhdGEgJT4lIAogIGRwbHlyOjpzZWxlY3QoZWRpdHMsIGB0aW1lc3RhbXAueGApICU+JSAKICBncm91cF9ieShgdGltZXN0YW1wLnhgKSAlPiUgCiAgc3VtbWFyaXNlKEVkaXRzID0gc3VtKGVkaXRzKSkKY29sbmFtZXMoZWRpdHNEYWlseSkgPC0gYygnRGF0ZScsICdFZGl0cycpCmVkaXRzRGFpbHkkRGF0ZSA8LWZhY3RvcihlZGl0c0RhaWx5JERhdGUsIGxldmVscyA9IHNvcnQoZWRpdHNEYWlseSREYXRlKSkKZ2dwbG90KGVkaXRzRGFpbHksIGFlcyh4ID0gRGF0ZSwgeSA9IEVkaXRzKSkgKwogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLCAKICAgICAgICAgICBwb3NpdGlvbiA9ICJkb2RnZSIsIAogICAgICAgICAgIHdpZHRoID0gLjIpICsKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gY29tbWEpICsKICBnZ3RpdGxlKCdBdXR1bW4gQmFubmVyIENhbXBhaWduIDIwMTc6IFRvdGFsIFVzZXIgRWRpdHMgRGFpbHknKSArCiAgdGhlbWVfbWluaW1hbCgpICsgCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgc2l6ZSA9IDgsIGhqdXN0ID0gMSkpICsKICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCkpICsKICB0aGVtZShsZWdlbmQudGl0bGUgPSBlbGVtZW50X2JsYW5rKCkpICsKICB0aGVtZShwYW5lbC5ncmlkLm1ham9yLnggPSBlbGVtZW50X2JsYW5rKCkpICsKICB0aGVtZShwYW5lbC5ncmlkLm1pbm9yLnggPSBlbGVtZW50X2JsYW5rKCkpICsKICB0aGVtZShwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpKQpgYGAKCmBgYHtyIGVjaG8gPSBULCB3YXJuaW5nID0gJ2hpZGUnLCBtZXNzYWdlID0gRiwgZXZhbCA9IFR9CnBvcHVsYXRpb25QIDwtIGMoNS85LCA0LzkpCm4gPC0gc3VtKGVkaXRzRGFpbHkkRWRpdHMpCmV4cGVjdGVkQ291bnRzIDwtIG4qcG9wdWxhdGlvblAKcyA8LSBjKHN1bShlZGl0c0RhaWx5JEVkaXRzWzE6NV0pLCBzdW0oZWRpdHNEYWlseSRFZGl0c1s2OjldKSkKcHJpbnQocGFzdGUoIkV4cGVjdGVkOiAiLCBwYXN0ZShyb3VuZChleHBlY3RlZENvdW50cywgMiksIGNvbGxhcHNlID0gIiwgIikpKQpgYGAKCmBgYHtyIGVjaG8gPSBULCB3YXJuaW5nID0gJ2hpZGUnLCBtZXNzYWdlID0gRiwgZXZhbCA9IFR9CnByaW50KHBhc3RlKCJEYXRhc2V0OiAiLCBwYXN0ZShzLCBjb2xsYXBzZSA9ICIsICIpKSkKYGBgCgpgYGB7ciBlY2hvID0gVCwgd2FybmluZyA9ICdoaWRlJywgbWVzc2FnZSA9IEYsIGV2YWwgPSBUfQpjaGlTcSA8LSBzdW0oKChzIC0gZXhwZWN0ZWRDb3VudHMpXjIpL2V4cGVjdGVkQ291bnRzKQpwcmludChwYXN0ZSgiQ2hpLVNxdWFyZSBTdGF0aXN0aWM6IiwgY2hpU3EsIHNlcCA9ICIgIikpCmBgYAoKYGBge3IgZWNobyA9IFQsIHdhcm5pbmcgPSAnaGlkZScsIG1lc3NhZ2UgPSBGLCBldmFsID0gVH0KIyAtIGRlZ3JlZXMgb2YgZnJlZWRvbQpkZiA8LSAyIC0gMSAjIGsgPT0gMiA9PSBudW1iZXIgb2YgY2F0ZWdvcmllcwpwcmludChwYXN0ZSgiRC5GLjoiLCBkZiwgc2VwID0gIiAiKSkKYGBgCgpgYGB7ciBlY2hvID0gVCwgd2FybmluZyA9ICdoaWRlJywgbWVzc2FnZSA9IEYsIGV2YWwgPSBUfQojIC0gVGVzdCBzaWduaWZpY2FuY2UsIGFscGhhID09IC4wNQpzaWcgPC0gcGNoaXNxKGNoaVNxLCBkZiwgbG93ZXIudGFpbD1GKSAjIHVwcGVyIHRhaWwKcHJpbnQocGFzdGUoIlR5cGUgSSBFcnJvciBQcm9iLjoiLCBzaWcsIHNlcCA9ICIgIikpCmBgYAoKVGhlIGNoaS1zcXVhcmUgdGVzdCBpbmRpY2F0ZXMgdGhhdCBtdWNoIGxlc3MgdXNlciBlZGl0cyBvY2N1cnJpbmcgc2luY2UgMTAvMTAvMjAxNy4gR2l2ZW4gdGhlIGxvY2FsIGluY3JlYXNlIGluIHRoZSBudW1iZXIgb2YgZWRpdHMgZm9sbG93aW5nIDEwLzEwLzIwMTcsIHdoaWNoIGlzIHByb2JhYmx5IHVudXN1YWwgZ2l2ZW4gdGhlIHByZXNlbmNlIG9mIHRoZSBnZW5lcmFsIG5lZ2F0aXZlIHRyZW5kIHNpbmNlIHRoZSBvbnNldCBvZiB0aGUgY2FtcGFpZ24sIHdlIGNhbm5vdCBydWxlIG91dCB0aGUgcG9zc2liaWxpdHkgdGhhdCB0aGUgYmFubmVyIGNoYW5nZSBvbiAxMC8xMC8yMDE3IGhhcyBpbmZsdWVuY2VkIHRoZSBudW1iZXIgb2YgdXNlciBlZGl0cyBpbiBhIHBvc2l0aXZlIHdheS4KCiMjIyA2LiAyIERpZCB0aGUgcmVnaXN0ZXJlZCB1c2VycyByZWFsbHkgZm9sbG93ZWQgdGhlIGluc3RydWN0aW9ucyBhcyBwcm92aWRlZCBpbiB0aGUgU3BlY2lmaWMgVGFzayBCYW5uZXIgQ2FtcGFpZ25zIGluIHRoZWlyIGVkaXRzPwoKIyMjIyA2LiAyLiAxIFNwZWNpZmljIFRhc2sgMSAoQlQzIENhbXBhaWduKTogTXVsdGltZWRpYQoKTm90ZTogd2XigJl2ZSBsb29rZWQgZm9yIHRoZSB1c2FnZSBvZiB0aGUgW1tEYXRlaTpdXSBzeW50YXggaW4gdXNlcuKAmXMgcmV2aXNpb25zICh0YWtpbmcgb25seSB0aGUgdGV4dCBvZiB0aGUgZGlmZmVyZW5jZSB0byB0aGUgcHJldmlvdXMgcGFnZSByZXZpc2lvbnMsIG9mIGNvdXJzZSkuCgpgYGB7ciBlY2hvID0gVCwgd2FybmluZyA9ICdoaWRlJywgbWVzc2FnZSA9IEYsIGV2YWwgPSBUfQojIyMgLS0gcmVhZCBhbGwgcmV2aXNpb25zOgphbGxSZXZzIDwtIHJlYWQuZGVsaW0ocGFzdGUoZ2V0d2QoKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAnL19kYWlseVVwZGF0ZURBVEEvYWJjMjAxN19jb21wbGV0ZVVzZXJSZXZpc2lvbnMudHN2JywKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNlcCA9ICcnKSwKICAgICAgICAgICAgICAgICAgICAgIHNlcCA9ICdcdCcsCiAgICAgICAgICAgICAgICAgICAgICBoZWFkZXIgPSBULAogICAgICAgICAgICAgICAgICAgICAgY2hlY2submFtZXMgPSBGLAogICAgICAgICAgICAgICAgICAgICAgc3RyaW5nc0FzRmFjdG9ycyA9IEYpCmFsbFJldnMkY2FtcGFpZ24gPSBzYXBwbHkoYWxsUmV2cyRyZXZfdXNlciwgZnVuY3Rpb24oeCkgewogIHVzZXJSZWckZXZlbnRfY2FtcGFpZ25bd2hpY2godXNlclJlZyRldmVudF91c2VySWQgJWluJSB4KV0KfSkKYWxsUmV2cyA8LSBmaWx0ZXIoYWxsUmV2cywgCiAgICAgICAgICAgICAgICAgIGNhbXBhaWduID09ICd3bWRlX2FiYzIwMTdfYnQxJykKYWxsUmV2cyRjb3VudEZpbGUgPC0gbnVtZXJpYyhkaW0oYWxsUmV2cylbMV0pICAgICAgICAgICAgICAgICAgICAgIAojIC0gYWNjZXNzIHRoZSBBUEkgZm9yIGVhY2ggdXNlciwgY29sbGVjdCBhbGwgdXNlciByZXZpc2lvbnM6CmFwaVVSTCA8LSAiaHR0cHM6Ly9kZS53aWtpcGVkaWEub3JnL3cvYXBpLnBocD9hY3Rpb249cXVlcnkmZm9ybWF0PWpzb24mcHJvcD1yZXZpc2lvbnMmcnZwcm9wPWNvbnRlbnQmcmV2aWRzPSIKYXBpVVJMIDwtICJodHRwczovL2RlLndpa2lwZWRpYS5vcmcvdy9hcGkucGhwP2FjdGlvbj1jb21wYXJlJmZvcm1hdD1qc29uJnRvcmVsYXRpdmU9cHJldiZmcm9tcmV2PSIKZm9yIChpIGluIDE6ZGltKGFsbFJldnMpWzFdKSB7CiAgcXVlcnkgPSBwYXN0ZShhcGlVUkwsCiAgICAgICAgICAgICAgICBhbGxSZXZzJHJldl9pZFtpXSwKICAgICAgICAgICAgICAgIHNlcCA9ICIiKQogIHJldmlzaW9ucyA8LSBnZXRVUkwoVVJMZW5jb2RlKHF1ZXJ5KSkKICAjIC0gUkVHRVggSEVSRToKICBhbGxSZXZzJGNvdW50RmlsZSA8LSBzdHJfY291bnQocmV2aXNpb25zLCBmaXhlZCgiW0RhdGVpOiIpKQp9CnByaW50KHBhc3RlKCdUaGUgcGVyY2VudCBvZiByZXZpc2lvbnMgdGhhdCBoYXZlIHVzZWQgYSBmaWxlIGZyb20gQ29tbW9ucyBpczogJywKICAgICAgICAgICAgcm91bmQoc3VtKGFsbFJldnMkY291bnRGaWxlPjApL2xlbmd0aChhbGxSZXZzJGNvdW50RmlsZSkqMTAwLCAyKSwKICAgICAgICAgICAgIiUiLAogICAgICAgICAgICBzZXAgPSAiIikpCmBgYAoKIyMjIyA2LiAyLiAyIFNwZWNpZmljIFRhc2sgMiAoQlQyIENhbXBhaWduKTogS2F0ZWdvcmllOldpa2lwZWRpYTrDnGJlcmFyYmVpdGVuCgpgYGB7ciBlY2hvID0gVCwgZXZhbCA9IEZ9CiMjIyAtLS0gU2NyaXB0OiBhYmMyMDE3X1BST0RfUmV2aXNpb25Db250ZW50Mi5SCiMjIyAtLS0gdGhlIGZvbGxvd2luZyBydW5zIG9uIHN0YXQxMDA1LmVxaWFkLndtbmV0CiMjIyAtLS0gUnNjcmlwdCAvaG9tZS9nb3JhbnNtL1JTY3JpcHRzL2FiYzIwMTcvYWJjMjAxN19QUk9EX1JldmlzaW9uQ29udGVudDIuUgoKIyMjIC0tLSBUaGUgc2NyaXB0IGNvbGxlY3RzIGFuZCB3cmFuZ2xlcyBhIGRhdGFzZXQgZm9yIEFCQyAyMDE3IHBvc3QtY2FtcGFpZ24gYW5hbHl0aWNzCiMjIyAtLS0gV01ERSBBdXR1bW4gQmFubmVyIENhbXBhaWduIDIwMTcuCgojIyMgLS0tIEdvcmFuIFMuIE1pbG92YW5vdmljLCBEYXRhIFNjaWVudGlzdCwgV01ERQojIyMgLS0tIE5vdmVtYmVyIDA2LCAyMDE3LgoKIyMjIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCiMjIyAwLiBTZXR1cAojIyMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0Kcm0obGlzdCA9IGxzKCkpCmxpYnJhcnkoZHBseXIpCgojIC0gZ2V0IHVzZXIgcmVnaXN0cmF0aW9uIGRhdGE6IGFiYzIwMTdfdXNlclJlZ2lzdHJhdGlvbnMudHN2CiMgLSB0aGVuIGdldCB1c2VyIElEcyBmcm9tIHJlZ2lzdGVyZWQ6CnNldHdkKCcvaG9tZS9nb3JhbnNtL19taXNjV01ERS9hYmMyMDE3X0RhdGFPVVQvYWJjMjAxN19PZmZpY2lhbERhdGFzZXRzL2FiYzIwMTdfRGFpbHlVcGRhdGUvJykKbEYgPC0gbGlzdC5maWxlcygpCmxGIDwtIGxGW2dyZXBsKCd1c2VyUmVnaXN0cmF0aW9ucycsIGxGLCBmaXhlZCA9IFQpXQp1c2VyUmVnIDwtIHJlYWQudGFibGUobEYsIAogICAgICAgICAgICAgICAgICAgICAgcXVvdGUgPSAiIiwKICAgICAgICAgICAgICAgICAgICAgIHNlcCA9ICJcdCIsCiAgICAgICAgICAgICAgICAgICAgICBoZWFkZXIgPSBULAogICAgICAgICAgICAgICAgICAgICAgY2hlY2submFtZXMgPSBGLAogICAgICAgICAgICAgICAgICAgICAgc3RyaW5nc0FzRmFjdG9ycyA9IEYpCnVzZXJSZWcgPC0gdXNlclJlZyAlPiUgCiAgZHBseXI6OnNlbGVjdChldmVudF91c2VySWQsIGV2ZW50X2lzU2VsZk1hZGUpICU+JSAKICBmaWx0ZXIoZXZlbnRfaXNTZWxmTWFkZSA9PSAxKQojIC0gdWlkczoKdWlkIDwtIHVzZXJSZWckZXZlbnRfdXNlcklkCgojIyMgLS0tIENvbGxlY3QgZGF0YSBmcm9tIExpbmsgdGFibGVzIG9uIGFsbCByZXZpc2lvbnMgbWFkZSAKIyMjIC0tLSBieSBhYmMyMDE3IHJlZ2lzdGVyZWQgdXNlcnMgdG8gZmluZCBvdXQgd2hldGhlcgojIyMgLS0tIHRoZSB1c2VyIGVkaXRzIHdoZXJlIGluIGxpbmUgd2l0aCB0aGUgY2FtcGFpZ24KIyMjIC0tLSBzcGVjaWZpYyB0YXNrcwoKIyAtIHNxbCBxdWVyeQpzcWxRdWVyeSA8LSBwYXN0ZSgnU0VMRUNUIHJldmlzaW9uLnJldl91c2VyLCByZXZpc2lvbi5yZXZfaWQsIHJldmlzaW9uLnJldl9wYWdlLCByZXZpc2lvbi5yZXZfdGltZXN0YW1wLCBjYXRlZ29yeWxpbmtzLmNsX2Zyb20sIGNhdGVnb3J5bGlua3MuY2xfdG8gCiAgICAgICAgICAgICAgICAgICBGUk9NIAogICAgICAgICAgICAgICAgICAgIHJldmlzaW9uIAogICAgICAgICAgICAgICAgICAgICAgTEVGVCBKT0lOIAogICAgICAgICAgICAgICAgICAgIGNhdGVnb3J5bGlua3MgT04gcmV2aXNpb24ucmV2X3BhZ2UgPSBjYXRlZ29yeWxpbmtzLmNsX2Zyb20KICAgICAgICAgICAgICAgICAgIFdIRVJFIHJldmlzaW9uLnJldl91c2VyIElOICgnLCBwYXN0ZSh1aWQsIGNvbGxhcHNlID0gIiwgIiwgc2VwID0gIiIpLCAnKSAKICAgICAgICAgICAgICAgICAgIEFORCByZXZpc2lvbi5yZXZfdGltZXN0YW1wID49IDIwMTcxMDA0MjIwMDAwIAogICAgICAgICAgICAgICAgICAgQU5EIHJldmlzaW9uLnJldl90aW1lc3RhbXAgPD0gMjAxNzEwMTQyMjAwMDA7JywKICAgICAgICAgICAgICAgICAgc2VwID0gIiIpCm15U3FsQ29tbWFuZCA8LSBwYXN0ZSgnbXlzcWwgLWggYW5hbHl0aWNzLXN0b3JlLmVxaWFkLndtbmV0IGRld2lraSAtZSAnLAogICAgICAgICAgICAgICAgICAgICAgcGFzdGUoJyInLCBzcWxRdWVyeSwgJyIgPiAnLCBzZXAgPSAiIiksCiAgICAgICAgICAgICAgICAgICAgICAnL2hvbWUvZ29yYW5zbS9fbWlzY1dNREUvYWJjMjAxN19EYXRhT1VUL2FiYzIwMTdfT2ZmaWNpYWxEYXRhc2V0cy9hYmMyMDE3X0RhaWx5VXBkYXRlL2FiYzIwMTdfdXNlckNvbnRlbnRSZXZpc2lvbnMyLnRzdicsIHNlcCA9ICIiKQpzeXN0ZW0oY29tbWFuZCA9IG15U3FsQ29tbWFuZCwgCiAgICAgICB3YWl0ID0gVFJVRSkKYGBgCgpBbmFseXNlOgoKYGBge3IgZWNobyA9IFQsIHdhcm5pbmcgPSAnaGlkZScsIG1lc3NhZ2UgPSBGLCBldmFsID0gVH0KY29udGVudFJldmlzaW9ucyA8LSByZWFkLnRhYmxlKCcuL19kYWlseVVwZGF0ZURBVEEvYWJjMjAxN191c2VyQ29udGVudFJldmlzaW9uczIudHN2JywKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHF1b3RlID0gIiIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZXAgPSAiXHQiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgaGVhZGVyID0gVCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNoZWNrLm5hbWVzID0gRiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGKQojIC0ga2VlcCBvbmx5IEJUMiByZWdpc3RyYXRpb25zOgpidDJSZWcgPC0gdXNlclJlZyRldmVudF91c2VySWRbd2hpY2godXNlclJlZyRldmVudF9jYW1wYWlnbiAlaW4lICd3bWRlX2FiYzIwMTdfYnQyJyldCmNvbnRlbnRSZXZpc2lvbnMkcmV2X3RpbWVzdGFtcCA8LSBhcy5jaGFyYWN0ZXIoY29udGVudFJldmlzaW9ucyRyZXZfdGltZXN0YW1wKQpjb250ZW50UmV2aXNpb25zIDwtIGNvbnRlbnRSZXZpc2lvbnMgJT4lIAogIGZpbHRlcihyZXZfdXNlciAlaW4lIGJ0MlJlZykKcGFnZXNFZGl0ZWQgPC0gbGVuZ3RoKHVuaXF1ZShjb250ZW50UmV2aXNpb25zJHJldl9wYWdlKSkKcGFnZXNFZGl0ZWRJbkNhdGVnb3J5IDwtIGNvbnRlbnRSZXZpc2lvbnMgJT4lIAogIGdyb3VwX2J5KHJldl9wYWdlLCByZXZfdGltZXN0YW1wKSAlPiUgCiAgc3VtbWFyaXNlKGVkaXQgPSBwYXN0ZShjbF90bywgY29sbGFwc2UgPSAiLCAiLCBzZXAgPSAiIikpCnRhcmdldEVkaXRzIDwtIHN1bShncmVwbCgnV2lraXBlZGlhOsOcYmVyYXJiZWl0ZW4nLCBwYWdlc0VkaXRlZEluQ2F0ZWdvcnkkZWRpdCwgZml4ZWQgPSBUKSkKcHJpbnQocGFzdGUoCiAgcm91bmQodGFyZ2V0RWRpdHMvbGVuZ3RoKHBhZ2VzRWRpdGVkSW5DYXRlZ29yeSRlZGl0KSoxMDAsIDIpLAogICIlIG9mIGVkaXRzIG1hZGUgYnkgQlQyIHJlZ2lzdGVyZWQgdXNlcnMgd2VyZSBtYWRlIG9uIHRoZSBwYWdlcyBpbiB0aGUgV2lraXBlZGlhOsOcYmVyYXJiZWl0ZW4gY2F0ZWdvcnkuIiwgCiAgc2VwID0gIiIKICApKQpgYGAKCiMjIyMgNi4gMi4gMyBTcGVjaWZpYyBUYXNrIDMgKEJUMyBDYW1wYWlnbik6IENpdGF0aW9ucwoKTm90ZTogd2XigJl2ZSBsb29rZWQgZm9yIHRoZSB1c2FnZSBvZiBjaXRhdGlvbnMgaW4gdXNlcuKAmXMgcmV2aXNpb25zICh0YWtpbmcgb25seSB0aGUgdGV4dCBvZiB0aGUgZGlmZmVyZW5jZSB0byB0aGUgcHJldmlvdXMgcGFnZSByZXZpc2lvbnMsIG9mIGNvdXJzZSkgYnkgaW5zcGVjdGluZyB3aGV0aGVyIHRoZSA8cmVmPiB0YWcgd2FzIHVzZWQuCgpgYGB7ciBlY2hvID0gVCwgd2FybmluZyA9ICdoaWRlJywgbWVzc2FnZSA9IEYsIGV2YWwgPSBUfQojIyMgLS0gcmVhZCBhbGwgcmV2aXNpb25zOgphbGxSZXZzIDwtIHJlYWQuZGVsaW0ocGFzdGUoZ2V0d2QoKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAnL19kYWlseVVwZGF0ZURBVEEvYWJjMjAxN19jb21wbGV0ZVVzZXJSZXZpc2lvbnMudHN2JywKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNlcCA9ICcnKSwKICAgICAgICAgICAgICAgICAgICAgIHNlcCA9ICdcdCcsCiAgICAgICAgICAgICAgICAgICAgICBoZWFkZXIgPSBULAogICAgICAgICAgICAgICAgICAgICAgY2hlY2submFtZXMgPSBGLAogICAgICAgICAgICAgICAgICAgICAgc3RyaW5nc0FzRmFjdG9ycyA9IEYpCmFsbFJldnMkY2FtcGFpZ24gPSBzYXBwbHkoYWxsUmV2cyRyZXZfdXNlciwgZnVuY3Rpb24oeCkgewogIHVzZXJSZWckZXZlbnRfY2FtcGFpZ25bd2hpY2godXNlclJlZyRldmVudF91c2VySWQgJWluJSB4KV0KfSkKYWxsUmV2cyA8LSBmaWx0ZXIoYWxsUmV2cywgCiAgICAgICAgICAgICAgICAgIGNhbXBhaWduID09ICd3bWRlX2FiYzIwMTdfYnQzJykKYWxsUmV2cyRjb3VudFJlZlRhZ3MgPC0gbnVtZXJpYyhkaW0oYWxsUmV2cylbMV0pICAgICAgICAgICAgICAgICAgICAgIAoKIyAtIGFjY2VzcyB0aGUgQVBJIGZvciBlYWNoIHVzZXIsIGNvbGxlY3QgYWxsIHVzZXIgcmV2aXNpb25zOgphcGlVUkwgPC0gImh0dHBzOi8vZGUud2lraXBlZGlhLm9yZy93L2FwaS5waHA/YWN0aW9uPXF1ZXJ5JmZvcm1hdD1qc29uJnByb3A9cmV2aXNpb25zJnJ2cHJvcD1jb250ZW50JnJldmlkcz0iCgphcGlVUkwgPC0gImh0dHBzOi8vZGUud2lraXBlZGlhLm9yZy93L2FwaS5waHA/YWN0aW9uPWNvbXBhcmUmZm9ybWF0PWpzb24mdG9yZWxhdGl2ZT1wcmV2JmZyb21yZXY9Igpmb3IgKGkgaW4gMTpkaW0oYWxsUmV2cylbMV0pIHsKICBxdWVyeSA9IHBhc3RlKGFwaVVSTCwKICAgICAgICAgICAgICAgIGFsbFJldnMkcmV2X2lkW2ldLAogICAgICAgICAgICAgICAgc2VwID0gIiIpCiAgcmV2aXNpb25zIDwtIGdldFVSTChVUkxlbmNvZGUocXVlcnkpKQogIGFsbFJldnMkY291bnRSZWZUYWdzIDwtIHN0cl9jb3VudChyZXZpc2lvbnMsIGZpeGVkKCI8cmVmPiIpKQp9CnByaW50KHBhc3RlKCdUaGUgcGVyY2VudCBvZiByZXZpc2lvbnMgdGhhdCBoYXZlIHVzZWQgdGhlIHJlZiB0YWcgaXM6ICcsCiAgICAgICAgICAgIHJvdW5kKHN1bShhbGxSZXZzJGNvdW50UmVmVGFncz4wKS9sZW5ndGgoYWxsUmV2cyRjb3VudFJlZlRhZ3MpKjEwMCwgMiksCiAgICAgICAgICAgICIlIiwKICAgICAgICAgICAgc2VwID0gIiIpKQpgYGAKCiMjIyA2LiAzIEhvdyBtYW55IHJldmVydGVkIGVkaXRzIHRoZXJlIHdlcmUgKGEpIHBlciBjYW1wYWlnbiwgYW5kIChiKSBwZXIgdXNlcj8KCioqTk9URToqKiB0aGUgZm9sbG93aW5nIERhdGEgQWNxdWlzaXRpb24gY29kZSBjaHVuayBpcyBub3QgZnVsbHkgcmVwcm9kdWNpYmxlIGZyb20gdGhpcyBSZXBvcnQuIFRoZSBkYXRhIGFyZSBjb2xsZWN0ZWQgYnkgcnVubmluZyB0aGUgc2NyaXB0IGFiYzIwMTdfUFJPRF9SZXZlcnRlZEVkaXRzLlIgb24gc3RhdDEwMDUuZXFpYWQud21uZXQsIGNvbGxlY3RpbmcgdGhlIGRhdGEgYXMgLnRzdiBmaWxlcywgY29weWluZyBtYW51YWxseSwgYW5kIHByb2Nlc3NpbmcgbG9jYWxseS4gUnVuIGZyb20gc3RhdDEwMDUgc3RhdCBib3ggYnkgZXhlY3V0aW5nICBgUnNjcmlwdCAvaG9tZS9nb3JhbnNtL1JTY3JpcHRzL2FiYzIwMTcvYWJjMjAxN19QUk9EX1JldmVydGVkRWRpdHMuUmAuCgpgYGB7ciBlY2hvID0gVCwgZXZhbCA9IEZ9CiMjIyAtLS0gU2NyaXB0OiBhYmMyMDE3X1BST0RfT3ZlcmFsbERhaWx5VXBkYXRlLlIKIyMjIC0tLSB0aGUgZm9sbG93aW5nIHJ1bnMgb24gc3RhdDEwMDUuZXFpYWQud21uZXQKIyMjIC0tLSBSc2NyaXB0IC9ob21lL2dvcmFuc20vUlNjcmlwdHMvYWJjMjAxNy9hYmMyMDE3X1BST0RfUmV2ZXJ0ZWRFZGl0cy5SCgojIyMgLS0tIFRoZSBzY3JpcHQgY29sbGVjdHMgYW5kIHdyYW5nbGVzIGEgZGF0YXNldCBmb3IgQUJDIDIwMTcgcG9zdC1jYW1wYWlnbiBhbmFseXRpY3MKIyMjIC0tLSBXTURFIEF1dHVtbiBCYW5uZXIgQ2FtcGFpZ24gMjAxNy4KCiMjIyAtLS0gR29yYW4gUy4gTWlsb3Zhbm92aWMsIERhdGEgU2NpZW50aXN0LCBXTURFCiMjIyAtLS0gTm92ZW1iZXIgMDYsIDIwMTcuCgojIyMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KIyMjIDAuIFNldHVwCiMjIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQpybShsaXN0ID0gbHMoKSkKbGlicmFyeShkcGx5cikKCiMgLSBnZXQgdXNlciByZWdpc3RyYXRpb24gZGF0YTogYWJjMjAxN191c2VyUmVnaXN0cmF0aW9ucy50c3YKIyAtIHRoZW4gZ2V0IHVzZXIgSURzIGZyb20gcmVnaXN0ZXJlZDoKc2V0d2QoJy9ob21lL2dvcmFuc20vX21pc2NXTURFL2FiYzIwMTdfRGF0YU9VVC9hYmMyMDE3X09mZmljaWFsRGF0YXNldHMvYWJjMjAxN19EYWlseVVwZGF0ZS8nKQpsRiA8LSBsaXN0LmZpbGVzKCkKbEYgPC0gbEZbZ3JlcGwoJ3VzZXJSZWdpc3RyYXRpb25zJywgbEYsIGZpeGVkID0gVCldCnVzZXJSZWcgPC0gcmVhZC50YWJsZShsRiwgCiAgICAgICAgICAgICAgICAgICAgICBxdW90ZSA9ICIiLAogICAgICAgICAgICAgICAgICAgICAgc2VwID0gIlx0IiwKICAgICAgICAgICAgICAgICAgICAgIGhlYWRlciA9IFQsCiAgICAgICAgICAgICAgICAgICAgICBjaGVjay5uYW1lcyA9IEYsCiAgICAgICAgICAgICAgICAgICAgICBzdHJpbmdzQXNGYWN0b3JzID0gRikKdXNlclJlZyA8LSB1c2VyUmVnICU+JSAKICBkcGx5cjo6c2VsZWN0KGV2ZW50X3VzZXJJZCwgZXZlbnRfaXNTZWxmTWFkZSkgJT4lIAogIGZpbHRlcihldmVudF9pc1NlbGZNYWRlID09IDEpCiMgLSB1aWRzOgp1aWQgPC0gdXNlclJlZyRldmVudF91c2VySWQKIyAtIHNxbCBxdWVyeQpzcWxRdWVyeSA8LSBwYXN0ZSgnU0VMRUNUIHJldl91c2VyLCByZXZfaWQsIHJldl9wYWdlLCByZXZfdGltZXN0YW1wLCByZXZfc2hhMSwgcmV2X2NvbnRlbnRfbW9kZWwsIHJldl9jb250ZW50X2Zvcm1hdCBGUk9NIHJldmlzaW9uIFdIRVJFIHJldl91c2VyIElOICgnLAogICAgICAgICAgICAgICAgICBwYXN0ZSh1aWQsIGNvbGxhcHNlID0gIiwgIiksCiAgICAgICAgICAgICAgICAgICcpIEFORCAocmV2X3RpbWVzdGFtcCA+PSAyMDE3MTAwNDIyMDAwMCkgQU5EIChyZXZfdGltZXN0YW1wIDw9IDIwMTcxMDE0MjIwMDAwKTsnLAogICAgICAgICAgICAgICAgICBzZXAgPSAiIikKbXlTcWxDb21tYW5kIDwtIHBhc3RlKCdteXNxbCAtaCBhbmFseXRpY3Mtc3RvcmUuZXFpYWQud21uZXQgZGV3aWtpIC1lICcsCiAgICAgICAgICAgICAgICAgICAgICBwYXN0ZSgnIicsIHNxbFF1ZXJ5LCAnIiA+ICcsIHNlcCA9ICIiKSwKICAgICAgICAgICAgICAgICAgICAgICcvaG9tZS9nb3JhbnNtL19taXNjV01ERS9hYmMyMDE3X0RhdGFPVVQvYWJjMjAxN19PZmZpY2lhbERhdGFzZXRzL2FiYzIwMTdfRGFpbHlVcGRhdGUvYWJjMjAxN19jb21wbGV0ZVVzZXJSZXZpc2lvbnMudHN2Jywgc2VwID0gIiIpCnN5c3RlbShjb21tYW5kID0gbXlTcWxDb21tYW5kLCAKICAgICAgIHdhaXQgPSBUUlVFKQpgYGAKCkFuYWx5c2UgcmV2ZXJ0ZWQgZWRpdHMgbG9jYWxseToKCmBgYHtyIGVjaG8gPSBULCB3YXJuaW5nID0gJ2hpZGUnLCBtZXNzYWdlID0gRiwgZXZhbCA9IFR9CnVzZXJSZXZpc2lvbnMgPC0gcmVhZC50YWJsZSgnLi9fZGFpbHlVcGRhdGVEQVRBL2FiYzIwMTdfY29tcGxldGVVc2VyUmV2aXNpb25zLnRzdicsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBxdW90ZSA9ICIiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgc2VwID0gIlx0IiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhlYWRlciA9IFQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBjaGVjay5uYW1lcyA9IEYsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdHJpbmdzQXNGYWN0b3JzID0gRikKdXNlclJldmlzaW9ucyA8LSBsZWZ0X2pvaW4odXNlclJldmlzaW9ucywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHVzZXJSZWcsIAogICAgICAgICAgICAgICAgICAgICAgICAgICBieSA9IGMoInJldl91c2VyIiA9ICJldmVudF91c2VySWQiKSkKdXNlclJldmlzaW9ucyA8LSB1c2VyUmV2aXNpb25zICU+JSAKICBmaWx0ZXIoIWlzLm5hKGV2ZW50X2NhbXBhaWduKSkKIyAtIGtlZXAgb25seSB0aG9zZSB1c2VycyB3aG8gbWFkZSBhbnkgZWRpdHMgYXQgYWxsOgp1c2VyUmV2aXNpb25zIDwtIHVzZXJSZXZpc2lvbnMgJT4lIAogIGZpbHRlcihyZXZfdXNlciAlaW4lIGVkaXREYXRhJHJldl91c2VyKQojIC0gTm90ZTogVVRDIHRpbWVzLCBjb252ZXJzaW9uIHRvIENFVCBpcyBub3QgbmVjZXNzYXJ5IGhlcmUKdXNlclJldmlzaW9ucyRyZXZfdGltZXN0YW1wIDwtIGFzLmNoYXJhY3Rlcih1c2VyUmV2aXNpb25zJHJldl90aW1lc3RhbXApCnJldmVydHNQZXJVc2VyIDwtIGxhcHBseSh1bmlxdWUodXNlclJldmlzaW9ucyRyZXZfdXNlciksIGZ1bmN0aW9uKHgpIHsKICBkYXRhc2V0IDwtIGRwbHlyOjphcnJhbmdlKHVzZXJSZXZpc2lvbnNbdXNlclJldmlzaW9ucyRyZXZfdXNlciA9PSB4LCBdLCByZXZfdGltZXN0YW1wKQogIHJldHVybihkYXRhLmZyYW1lKHVzZXJJZCA9IHgsIAogICAgICAgICAgICAgICAgICAgIHJldkNvdW50ID0gc3VtKHRhYmxlKGRhdGFzZXQkcmV2X3NoYTEpIC0gMSksIAogICAgICAgICAgICAgICAgICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGKSkKfSkKcmV2ZXJ0c1BlclVzZXIgPC0gcmJpbmRsaXN0KHJldmVydHNQZXJVc2VyKQpwcmludChwYXN0ZShzdW0ocmV2ZXJ0c1BlclVzZXIkcmV2Q291bnQpLCAiIGVkaXRzIHdlcmUgcmV2ZXJ0ZWQuIiwgc2VwID0gIiIpKQpgYGAKCiMjIyA2LiA0IEV4aXRpbmcgdGhlIEd1aWRlZCBUb3VyIHZzIFJlZ2lzdHJhdGlvbiBDYW1wYWlnbgoKVGhlIHBlcmNlbnQgb2YgcmVnaXN0ZXJlZCB1c2VycyB3aG8gZGlkIGFuZCBkaWQgbm90IGNvbXBsZXRlIHRoZSBHdWlkZWQgVG91ciwgcGVyIGJhbm5lciBjYW1wYWlnbjoKCmBgYHtyIGVjaG8gPSBULCB3YXJuaW5nID0gJ2hpZGUnLCBtZXNzYWdlID0gRiwgZXZhbCA9IFR9CnVSR1QgPC0gdXNlclJlZ0dUICU+JSAKICBncm91cF9ieShldmVudF9jYW1wYWlnbikgJT4lIAogIHN1bW1hcmlzZShDb21wbGV0ZSA9IHN1bShpcy5uYShldmVudF90b3VyKSksIGBJbmNvbXBsZXRlYCA9IHN1bSghaXMubmEoZXZlbnRfdG91cikpKQp1UkdUJGV2ZW50X2NhbXBhaWduIDwtIHRvdXBwZXIoZ3N1Yigid21kZV9hYmMyMDE3XyIsICIiLCB1UkdUJGV2ZW50X2NhbXBhaWduLCBmaXhlZCA9IFQpKQp1UkdUJGAlIENvbXBsZXRlYCA9IHJvdW5kKHVSR1QkQ29tcGxldGUvKHVSR1QkQ29tcGxldGUgKyB1UkdUJEluY29tcGxldGUpKjEwMCwgMikKdVJHVCRgJSBJbmNvbXBsZXRlYCA9IHJvdW5kKHVSR1QkSW5jb21wbGV0ZS8odVJHVCRDb21wbGV0ZSArIHVSR1QkSW5jb21wbGV0ZSkqMTAwLCAyKQp1UkdUJENvbXBsZXRlIDwtIE5VTEwKdVJHVCRJbmNvbXBsZXRlIDwtIE5VTEwKY29sbmFtZXModVJHVClbMV0gPC0gJ0NhbXBhaWduJwprbml0cjo6a2FibGUodVJHVCwgZm9ybWF0ID0gImh0bWwiKSAlPiUgCiAga2FibGVfc3R5bGluZyhmdWxsX3dpZHRoID0gRiwgcG9zaXRpb24gPSAibGVmdCIpCmBgYAoKIyMjIDYuIDUgUG9pbnQgb2YgR3VpZGVkIFRvdXIgRXhpdCBwZXIgUmVnaXN0cmF0aW9uIENhbXBhaWduCgpUaGUgbnVtYmVyIG9mIHVzZXJzIGV4aXRpbmcgdGhlIEd1aWRlZCBUb3VyIGF0IGEgcGFydGljdWxhciBzdGVwLCBwZXIgYmFubmVyIGNhbXBhaWduLgoKYGBge3IgZWNobyA9IFQsIHdhcm5pbmcgPSAnaGlkZScsIG1lc3NhZ2UgPSBGLCBldmFsID0gVH0KZ3RleGl0UGxvdCAgPC0gdXNlclJlZ0dUICU+JSAKICBmaWx0ZXIoIWlzLm5hKGV2ZW50X3RvdXIpKSAlPiUgCiAgZHBseXI6OnNlbGVjdChldmVudF9jYW1wYWlnbiwgZXZlbnRfc3RlcCkgJT4lIAogIGdyb3VwX2J5KGV2ZW50X2NhbXBhaWduLCBldmVudF9zdGVwKSAlPiUgCiAgc3VtbWFyaXNlKENvdW50ID0gbigpKQpjb2xuYW1lcyhndGV4aXRQbG90KSA8LSBjKCdDYW1wYWlnbicsICdFeGl0IFBvaW50JywgJ0NvdW50JykKZ3RleGl0UGxvdCRDYW1wYWlnbiA8LSB0b3VwcGVyKGdzdWIoIndtZGVfYWJjMjAxN18iLCAiIiwgZ3RleGl0UGxvdCRDYW1wYWlnbiwgZml4ZWQgPSBUKSkKZ2dwbG90KGd0ZXhpdFBsb3QsIGFlcyh4ID0gJycsIHkgPSBDb3VudCwKICAgICAgICAgICAgICAgICAgICAgIGZpbGwgPSBgRXhpdCBQb2ludGAsCiAgICAgICAgICAgICAgICAgICAgICBjb2xvciA9IGBFeGl0IFBvaW50YCwKICAgICAgICAgICAgICAgICAgICAgIGdyb3VwID0gYEV4aXQgUG9pbnRgLAogICAgICAgICAgICAgICAgICAgICAgbGFiZWwgPSBDb3VudCkpICsgCiAgZ2VvbV9iYXIocG9zaXRpb24gPSAiZG9kZ2UiLCAKICAgICAgICAgICBzdGF0ID0gImlkZW50aXR5IiwgCiAgICAgICAgICAgd2lkdGggPSAxLCAKICAgICAgICAgICBjb2xvciA9ICJibGFjayIpICsgCiAgZmFjZXRfd3JhcCh+IENhbXBhaWduKSArCiAgZ2d0aXRsZSgnQXV0dW1uIEJhbm5lciBDYW1wYWlnbiAyMDE3OiBcblBvaW50IG9mIEd1aWRlZCBUb3VyIEV4aXQgcGVyIFJlZ2lzdHJhdGlvbiBDYW1wYWlnbicpICsgCiAgeGxhYigiIikgKyB5bGFiKCJOdW0uIFVzZXJzIikgKwogIHRoZW1lX21pbmltYWwoKSArIAogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gMCwgc2l6ZSA9IDgsIGhqdXN0ID0gMSkpICsKICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCkpICsKICB0aGVtZShsZWdlbmQudGl0bGUgPSBlbGVtZW50X2JsYW5rKCkpICsKICB0aGVtZShwYW5lbC5ncmlkLm1ham9yLnggPSBlbGVtZW50X2JsYW5rKCkpICsKICB0aGVtZShwYW5lbC5ncmlkLm1pbm9yLnggPSBlbGVtZW50X2JsYW5rKCkpICsKICB0aGVtZShwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpKSAKYGBgCg==