THIS WORKS IS YET TO BE REVIEWED

Introduction

In this paper contains estimates for the reproductive number $R_{t} over time. This is done as described in [1]. These have been implemented in R using EpiEstim package [2] which is what is used here. This report should be updated roughly daily and is available online.

Updates

As this paper is updated over time this section will summarise significant changes. The code producing this paper is tracked using Git. The Git commit hash for this project at the time of generating this paper was 43c938c362fee38df7d97ae1a08fbc1d9189601c.

2020-06-12

Libraries

The project uses the following libraries.

require(EpiEstim)
require(EnvStats)
require(ggplot2)
require(ggpubr)
require(lubridate)
require(utils)
require(httr)
require(dplyr)
require(tidyr)
require(scales)

Data

Download

Data is downloaded from the Git repository associated with [3].

# Provincial Deaths
GET(url = "https://raw.githubusercontent.com/dsfsi/covid19za/master/data/covid19za_provincial_cumulative_timeline_deaths.csv", 
    write_disk("covid19za_provincial_cumulative_timeline_deaths.csv", overwrite = TRUE))
Response [https://raw.githubusercontent.com/dsfsi/covid19za/master/data/covid19za_provincial_cumulative_timeline_deaths.csv]
  Date: 2020-06-12 19:58
  Status: 200
  Content-Type: text/plain; charset=utf-8
  Size: 7.19 kB
<ON DISK>  C:\Users\lrossou\Desktop\COVID-19\rt_estimates\covid19za_provincial_cumulative_timeline_deaths.csv
# Provincial Cases
GET(url = "https://raw.githubusercontent.com/dsfsi/covid19za/master/data/covid19za_provincial_cumulative_timeline_confirmed.csv", 
    write_disk("covid19za_provincial_cumulative_timeline_confirmed.csv", overwrite = TRUE))
Response [https://raw.githubusercontent.com/dsfsi/covid19za/master/data/covid19za_provincial_cumulative_timeline_confirmed.csv]
  Date: 2020-06-12 19:58
  Status: 200
  Content-Type: text/plain; charset=utf-8
  Size: 9.08 kB
<ON DISK>  C:\Users\lrossou\Desktop\COVID-19\rt_estimates\covid19za_provincial_cumulative_timeline_confirmed.csv

Data preperation

First read in the data from the downloaded comma-separated values text file.

# Read from CSVs above
data_cases <- read.csv("covid19za_provincial_cumulative_timeline_confirmed.csv", 
    stringsAsFactors = FALSE)
data_deaths <- read.csv("covid19za_provincial_cumulative_timeline_deaths.csv", stringsAsFactors = FALSE)

In the case data file row 21 and 32 contain no provincial details. We estimated it by spreading the national total to the provinces in proportion to a mixture of the prior day and the next day.

data_cases[21, c("EC", "FS", "GP", "KZN", "LP", "MP", "NC", "NW", "WC", "UNKNOWN")] <- colSums(data_cases[c(20, 
    22), c("EC", "FS", "GP", "KZN", "LP", "MP", "NC", "NW", "WC", "UNKNOWN")])/sum(data_cases[c(20, 
    22), ]$total) * data_cases[21, ]$total
data_cases[32, c("EC", "FS", "GP", "KZN", "LP", "MP", "NC", "NW", "WC", "UNKNOWN")] <- colSums(data_cases[c(31, 
    33), c("EC", "FS", "GP", "KZN", "LP", "MP", "NC", "NW", "WC", "UNKNOWN")])/sum(data_cases[c(31, 
    33), ]$total) * data_cases[32, ]$total

The following function will be applied to case and death data. In this function the following occurs:

  1. Scale up the per province data for unknown values.
  2. This results in provincial data which are not whole numbers. These are rounded to the nearest whole number.
  3. A SA column is added as the sum of the new per province data.
  4. Data is formatted and disaggregated such that item represents the incremental cases or deaths rather than cumulative figures.
  5. Data is filled with data (albeit with 0 cases or deaths) for all dates in the range.
  6. Any incremental case or death counts that are negative are zeroeised.
  7. New cumulative figures are calculated.
fix_data <- function(data, start_date = as.Date("2020-03-01"), end_date = as.Date("2020-03-31")) {
    # Scale provinces by scale factor (assume unknown are in proportion)
    data[, c("EC", "FS", "GP", "KZN", "LP", "MP", "NC", "NW", "WC")] <- data[, c("EC", 
        "FS", "GP", "KZN", "LP", "MP", "NC", "NW", "WC")] * (1 + data$UNKNOWN/rowSums(data[, 
        c("EC", "FS", "GP", "KZN", "LP", "MP", "NC", "NW", "WC")]))
    
    # Only select columns we need
    data <- data %>% select("date", "EC", "FS", "GP", "KZN", "LP", "MP", "NC", "NW", 
        "WC")
    data$date <- as.Date(data$date, "%d-%m-%Y")
    
    # Round data so we have integer cases
    data[, c("EC", "FS", "GP", "KZN", "LP", "MP", "NC", "NW", "WC")] <- round(data[, 
        c("EC", "FS", "GP", "KZN", "LP", "MP", "NC", "NW", "WC")], 0)
    
    # Calculate a new SA column
    data$SA <- rowSums(data[, c("EC", "FS", "GP", "KZN", "LP", "MP", "NC", "NW", 
        "WC")])
    
    # 'Melt' the data
    data <- pivot_longer(data, cols = c("EC", "FS", "GP", "KZN", "LP", "MP", "NC", 
        "NW", "WC", "SA"), names_to = "province", values_to = "count")
    
    # Getting daily data from the cumulative data set
    data = data %>% group_by(province) %>% arrange(date) %>% mutate(count = count - 
        lag(count, default = 0)) %>% ungroup()
    
    # add missing dates
    all_dates <- expand_grid(date = seq(start_date, end_date, 1), province = levels(as.factor(data$province)))
    
    # join
    data <- left_join(all_dates, data, by = c("date", "province"))
    
    # province factor
    data$province <- as.factor(data$province)
    
    # 0 for NAs
    data$count <- ifelse(is.na(data$count), 0, data$count)
    
    # remove negatives
    data$count <- ifelse(data$count < 0, 0, data$count)
    
    data <- data %>% group_by(province) %>% mutate(cumulative_count = cumsum(count)) %>% 
        ungroup()
    
    return(data)
}

Below we use the function above to process deaths and cases and then combine them into a single dataset.

start_date <- min(as.Date(data_cases$date, "%d-%m-%Y"))
end_date <- max(as.Date(data_cases$date, "%d-%m-%Y"))
data_cases <- fix_data(data_cases, start_date = start_date, end_date = end_date)
data_deaths <- fix_data(data_deaths, start_date = start_date, end_date = end_date)

data_cases <- cbind("cases", data_cases)
data_deaths <- cbind("deaths", data_deaths)
colnames(data_cases)[1] <- "type"
colnames(data_deaths)[1] <- "type"

# combined
data <- rbind(data_cases, data_deaths)

# remove data sets no longer needed
rm("data_cases", "data_deaths", "start_date", "end_date")

Basic Exploration

Below we plot cumulative case count on a log scale by province:

ggplot(data %>% filter(type == "cases"), aes(x = date, y = cumulative_count)) + geom_line(aes(color = province), 
    size = 1) + scale_y_log10(labels = comma) + ggtitle("Cumulative Cases by Province") + 
    theme_bw() + theme(axis.text.x = element_text(angle = 45, hjust = 1), legend.position = "right") + 
    guides(fill = guide_legend(ncol = 1)) + scale_color_hue(l = 50)

Below we plot the cumulative deaths by province on a log scale:

ggplot(data %>% filter(type == "deaths"), aes(x = date, y = cumulative_count)) + 
    geom_line(aes(color = province), size = 1) + scale_y_log10(labels = comma) + 
    ggtitle("Cumulative Deaths by Province") + theme_bw() + theme(axis.text.x = element_text(angle = 45, 
    hjust = 1), legend.position = "right") + guides(fill = guide_legend(ncol = 1)) + 
    scale_color_hue(l = 50)

Serial Interval

To do further analysis an serial interval assumption is needed. The serial interval is taken from [4]. It’s assumed to be Gamma distributed with mean of 6.48 and standard deviation of 3.83. This corresponds to the effective infectiousness of an individual since acquiring the infection themselves.

We plot this serial distribution below:

mean1 <- 6.48
sd1 <- 3.83
cv1 <- sd1/mean1

serial_interval = rep(0, 1)
serial_interval[1] = (pgammaAlt(1.5, mean1, cv1) - pgammaAlt(0, mean1, cv1))
for (i in 2:50) {
    serial_interval[i] = (pgammaAlt(i + 0.5, mean1, cv1) - pgammaAlt(i - 0.5, mean1, 
        cv1))
}
ggplot(data.frame(days = seq(1, 20, 1), serial_interval = serial_interval[1:20]), 
    aes(y = serial_interval, x = days)) + geom_line(size = 1) + theme_bw() + theme(axis.text.x = element_text(angle = 45, 
    hjust = 1), legend.position = "right") + guides(fill = guide_legend(ncol = 1)) + 
    scale_color_hue(l = 50)

Estimate \(R_{t}\) values by week

The following code works backwards and fits \(R_{t}\) values for whole weeks (ending on the last date in the data) using the EpiEstim package. Using deaths or infection the as the cases in the puts the estimate of \(R_{t}\) as at the date of the cases being tracked.

Two values are estimated for each province (including South Africa as a whole):

  1. \(R_{t,m}^{cases}\) is the reproductive number implied by the cases reported at time \(t\) in province \(m\).
  2. \(R_{t,m}^{deaths}\) is the reproductive number implied by the deaths reported at time \(t\) in province \(m\).

Note that the time periods are left unadjusted, though in reality the \(R_{t,m}^{deaths}\) should be shifted back approximately 2 weeks relative to \(R_{t,m}^{cases}\).

Rt_data <- NULL
for (p in levels(data$province)) {
    for (t in levels(data$type)) {
        # filter out province data of type t
        p_data <- data %>% filter(province == p & type == t)
        
        # vector of count of cases/deaths
        I <- p_data$count
        
        # t=1 corresponds to this date:
        t1_date <- min(p_data$date)
        
        # the day after the first case/death:
        t_start_date <- min((p_data %>% filter(count > 0))$date) + 1
        t_start <- min(seq(1, length(I))[I > 0]) + 1
        
        # last day of cases/deaths
        t_end <- length(I)
        
        # how many full weeks do we have
        full_weeks <- floor((t_end - t_start)/7)
        
        # only continue if we have 1 full week or more
        if (full_weeks > 0) {
            # then divide period into weeks
            T.Start <- seq(t_end - 7 * full_weeks, t_end - 7, by = 7)
            T.End <- T.Start + 7
            
            # estimate $R_{t,m}^{type}$ for each week:
            p_Rt <- EstimateR(I, T.Start = T.Start, T.End = T.End, Mean.SI = mean1, 
                Std.SI = sd1, method = "ParametricSI")$R
            
            # add province and type designations
            p_Rt <- cbind(p, t, p_Rt)
            
            # and add dates
            p_Rt$date_start <- t1_date + p_Rt$T.Start
            p_Rt$date_end <- t1_date + p_Rt$T.End
            
            # combine the results
            if (is.null(Rt_data)) {
                Rt_data <- p_Rt
            } else {
                Rt_data <- rbind(Rt_data, p_Rt)
            }
        }
    }
}

Below we prep column headings and prepare CI data:

# Column names
colnames(Rt_data) <- c("province", "type", "t_start", "t_end", "Rt_mean", "Rt_std", 
    "Rt_li_95", "Rt_li_90", "Rt_li_50", "Rt_median", "Rt_ui_50", "Rt_ui_90", "Rt_ui_95", 
    "date_start", "date_end")

# mid week data point
Rt_data$date_mid <- Rt_data$date_start + (Rt_data$date_end - Rt_data$date_start)/2

# split out CI data into different dataset
Rt_ci_data <- NULL
for (ci in c("50", "90", "95")) {
    r <- data.frame(province = Rt_data$province, type = Rt_data$type, ci = rep(paste0(ci, 
        "%"), nrow(Rt_data)), date_mid = Rt_data$date_mid, Rt_mean = Rt_data$Rt_mean, 
        Rt_li = Rt_data[[paste0("Rt_li_", ci)]], Rt_ui = Rt_data[[paste0("Rt_ui_", 
            ci)]])
    Rt_ci_data <- rbind(r, Rt_ci_data)
}

Let’s generate our plots for each province in a list:

province_plots <- list()
for (p in levels(Rt_data$province)) {
    p1 <- ggplot(Rt_ci_data %>% filter(province == p & ci == "95%"), aes(x = date_mid, 
        y = Rt_mean)) + geom_crossbar(position = position_dodge2(padding = 0), aes(ymin = Rt_li, 
        ymax = Rt_ui, colour = type, fill = type), width = 7) + scale_fill_manual(values = c(alpha("deepskyblue4", 
        0.45), alpha("#8b0068", 0.45))) + scale_colour_manual(values = c(alpha("deepskyblue4"), 
        alpha("#8b0068"))) + theme_bw() + theme(axis.text.x = element_text(angle = 45, 
        hjust = 1), legend.position = "right") + guides(fill = guide_legend(ncol = 1)) + 
        xlab("Week") + ylab(expression(R[t, m])) + ggtitle(p)
    province_plots[[p]] <- p1
}

Let’s look at SA below. In blue we plot the reproductive number estimated from cases \(R_{t,m}^{cases}\) and in red we plot the reproductive number estimated from deaths \(R_{t,m}^{deaths}\). It’s clear that the \(R_{t,m}^{deaths}\) shows a delay when compared to \(R_{t,m}^{cases}\). During April \(R_{t,m}^{deaths}\) remains high while \(R_{t,m}^{cases}\) has dropped (presumably to the lockdown). For some periods no death data is available and we only estimate using case data.

province_plots[["SA"]] + ggtitle("Reproductive number in SA with 95% confidence intervals")

Below we plot all the provinces:

ggarrange(province_plots[["EC"]], province_plots[["FS"]], province_plots[["GP"]], 
    province_plots[["KZN"]], province_plots[["LP"]], province_plots[["MP"]], province_plots[["NC"]], 
    province_plots[["NW"]], province_plots[["WC"]], ncol = 2, nrow = 5) + ggtitle("Reproductive number in SA, by province, with 95% confidence intervals")

Discussion

The above shows estimates for reproductive number using cases and deaths (\(R_{t,m}^{cases}\) and \(R_{t,m}^{deaths}\)) for each province \(m\) over time \(t\) in weeks.

It’s clear that:

It appears that the \(R_{t,m}^{cases}\) and \(R_{t,m}^{deaths}\) would be useful as a monitoring tool, especially the \(R_{t,m}^{deaths}\) as testing backlogs and issues may bias \(R_{t,m}^{cases}\) derived. This report may be useful in this regard.

Author

This report was prepared by Louis Rossouw. Please get in contact with Louis Rossouw if you have comments or wish to receive this regularly.

Louis Rossouw
Head of Research & Analytics
Gen Re | Life/Health Canada, South Africa, Australia, NZ, UK & Ireland
Email: LRossouw@GenRe.com Mobile: +27 71 355 2550

The views in this document represents that of the author and may not represent those of Gen Re. Also note that given the significant uncertainty involved with the parameters, data and methodology care should be taken with these numbers and any use of these numbers.

References

[1] A. Cori, N. M. Ferguson, C. Fraser, and S. Cauchemez, “A new framework and software to estimate time-varying reproduction numbers during epidemics,” American Journal of Epidemiology, vol. 178, no. 9, pp. 1505–1512, Sep. 2013, doi: 10.1093/aje/kwt133. [Online]. Available: https://doi.org/10.1093/aje/kwt133

[2] A. Cori, EpiEstim: EpiEstim: A package to estimate time varying reproduction numbers from epidemic curves. 2013 [Online]. Available: https://CRAN.R-project.org/package=EpiEstim

[3] V. Marivate et al., “Coronavirus disease (COVID-19) case data - South Africa.” Zenodo, 2020 [Online]. Available: https://zenodo.org/record/3888499

[4] N. Ferguson et al., “Report 9: Impact of non-pharmaceutical interventions (NPIs) to reduce COVID19 mortality and healthcare demand,” Imperial College London, 2020 [Online]. Available: https://www.imperial.ac.uk/mrc-global-infectious-disease-analysis/covid-19/report-9-impact-of-npis-on-covid-19/

LS0tDQp0aXRsZTogIkVzdGltYXRpbmcgdGhlIFJlcHJvZHVjdGl2ZSBOdW1iZXIgb2YgQ09WSUQtMTkgaW4gU291dGggQWZyaWNhIg0KYXV0aG9yOiAiTG91aXMgUm9zc291dyINCmRhdGU6ICJgciBTeXMuRGF0ZSgpYCINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KY3NsOiBpZWVlX3dpdGhfdXJsLmNzbA0KYmlibGlvZ3JhcGh5OiByZWZlcmVuY2VzLmJpYg0KLS0tDQoNCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG89VFJVRSwgd2FybmluZyA9IEZBTFNFLCB0aWR5PVRSVUUsIGRwaT02MDApDQpgYGANCg0KKioqVEhJUyBXT1JLUyBJUyBZRVQgVE8gQkUgUkVWSUVXRUQqKioNCg0KIyMgSW50cm9kdWN0aW9uDQoNCkluIHRoaXMgcGFwZXIgY29udGFpbnMgZXN0aW1hdGVzIGZvciB0aGUgcmVwcm9kdWN0aXZlIG51bWJlciAkUl97dH0gb3ZlciB0aW1lLiAgVGhpcyBpcyBkb25lIGFzIGRlc2NyaWJlZCBpbiBbQENvcmkyMDEzXS4gIFRoZXNlIGhhdmUgYmVlbiBpbXBsZW1lbnRlZCBpbiBSIHVzaW5nIGBFcGlFc3RpbWAgcGFja2FnZSBbQENvcmkyMDEzYV0gd2hpY2ggaXMgd2hhdCBpcyB1c2VkIGhlcmUuICBUaGlzIHJlcG9ydCBzaG91bGQgYmUgdXBkYXRlZCByb3VnaGx5IGRhaWx5IGFuZCBpcyBhdmFpbGFibGUgb25saW5lLg0KDQojIyBVcGRhdGVzDQoNCkFzIHRoaXMgcGFwZXIgaXMgdXBkYXRlZCBvdmVyIHRpbWUgdGhpcyBzZWN0aW9uIHdpbGwgc3VtbWFyaXNlIHNpZ25pZmljYW50IGNoYW5nZXMuICBUaGUgY29kZSBwcm9kdWNpbmcgdGhpcyBwYXBlciBpcyB0cmFja2VkIHVzaW5nIEdpdC4gVGhlIEdpdCBjb21taXQgaGFzaCBmb3IgdGhpcyBwcm9qZWN0IGF0IHRoZSB0aW1lIG9mIGdlbmVyYXRpbmcgdGhpcyBwYXBlciB3YXMgYHIgc3lzdGVtKCJnaXQgcmV2LXBhcnNlIEhFQUQiLCBpbnRlcm49VFJVRSlgLg0KDQoqKjIwMjAtMDYtMTIqKg0KDQoqIEluaXRpYWwgdmVyc2lvbi4NCg0KIyMgTGlicmFyaWVzDQoNClRoZSBwcm9qZWN0IHVzZXMgdGhlIGZvbGxvd2luZyBsaWJyYXJpZXMuDQoNCmBgYHtyIGxpYnJhcmllcywgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0NCnJlcXVpcmUoRXBpRXN0aW0pDQpyZXF1aXJlKEVudlN0YXRzKQ0KcmVxdWlyZShnZ3Bsb3QyKQ0KcmVxdWlyZShnZ3B1YnIpDQpyZXF1aXJlKGx1YnJpZGF0ZSkNCnJlcXVpcmUodXRpbHMpDQpyZXF1aXJlKGh0dHIpDQpyZXF1aXJlKGRwbHlyKQ0KcmVxdWlyZSh0aWR5cikNCnJlcXVpcmUoc2NhbGVzKQ0KYGBgDQoNCiMjIERhdGENCg0KIyMjIERvd25sb2FkDQpEYXRhIGlzIGRvd25sb2FkZWQgZnJvbSB0aGUgR2l0IHJlcG9zaXRvcnkgYXNzb2NpYXRlZCB3aXRoIFtATWFyaXZhdGUyMDIwXS4NCg0KYGBge3IgZG93bmxvYWRfZGF0YSwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0NCiMgUHJvdmluY2lhbCBEZWF0aHMNCkdFVCgNCiAgdXJsID0gImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9kc2ZzaS9jb3ZpZDE5emEvbWFzdGVyL2RhdGEvY292aWQxOXphX3Byb3ZpbmNpYWxfY3VtdWxhdGl2ZV90aW1lbGluZV9kZWF0aHMuY3N2IiwNCiAgd3JpdGVfZGlzaygNCiAgICAiY292aWQxOXphX3Byb3ZpbmNpYWxfY3VtdWxhdGl2ZV90aW1lbGluZV9kZWF0aHMuY3N2IiwNCiAgICBvdmVyd3JpdGUgPSBUUlVFDQogICkNCikNCg0KIyBQcm92aW5jaWFsIENhc2VzDQpHRVQoDQogIHVybCA9ICJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vZHNmc2kvY292aWQxOXphL21hc3Rlci9kYXRhL2NvdmlkMTl6YV9wcm92aW5jaWFsX2N1bXVsYXRpdmVfdGltZWxpbmVfY29uZmlybWVkLmNzdiIsDQogIHdyaXRlX2Rpc2soDQogICAgImNvdmlkMTl6YV9wcm92aW5jaWFsX2N1bXVsYXRpdmVfdGltZWxpbmVfY29uZmlybWVkLmNzdiIsDQogICAgb3ZlcndyaXRlID0gVFJVRQ0KICApDQopDQpgYGANCg0KIyMjIERhdGEgcHJlcGVyYXRpb24NCg0KRmlyc3QgcmVhZCBpbiB0aGUgZGF0YSBmcm9tIHRoZSBkb3dubG9hZGVkIGNvbW1hLXNlcGFyYXRlZCB2YWx1ZXMgdGV4dCBmaWxlLg0KDQpgYGB7cn0NCiMgUmVhZCBmcm9tIENTVnMgYWJvdmUNCmRhdGFfY2FzZXMgPC0NCiAgcmVhZC5jc3YoDQogICAgImNvdmlkMTl6YV9wcm92aW5jaWFsX2N1bXVsYXRpdmVfdGltZWxpbmVfY29uZmlybWVkLmNzdiIsDQogICAgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFDQogICkNCmRhdGFfZGVhdGhzIDwtDQogIHJlYWQuY3N2KCJjb3ZpZDE5emFfcHJvdmluY2lhbF9jdW11bGF0aXZlX3RpbWVsaW5lX2RlYXRocy5jc3YiLA0KICAgICAgICAgICBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UpDQpgYGANCg0KSW4gdGhlIGNhc2UgZGF0YSBmaWxlIHJvdyAyMSBhbmQgMzIgY29udGFpbiBubyBwcm92aW5jaWFsIGRldGFpbHMuIFdlIGVzdGltYXRlZCBpdCBieSBzcHJlYWRpbmcgdGhlIG5hdGlvbmFsIHRvdGFsIHRvIHRoZSBwcm92aW5jZXMgaW4gcHJvcG9ydGlvbiB0byBhIG1peHR1cmUgb2YgdGhlIHByaW9yIGRheSBhbmQgdGhlIG5leHQgZGF5Lg0KDQpgYGB7cn0NCmRhdGFfY2FzZXNbMjEsIGMoIkVDIiwgIkZTIiwgIkdQIiwgIktaTiIsICJMUCIsICJNUCIsICJOQyIsICJOVyIsICJXQyIsICJVTktOT1dOIildIDwtDQogIGNvbFN1bXMoZGF0YV9jYXNlc1tjKDIwLCAyMiksIGMoIkVDIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIkZTIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIkdQIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIktaTiIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJMUCIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJNUCIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJOQyIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJOVyIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJXQyIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJVTktOT1dOIildKSAvIHN1bShkYXRhX2Nhc2VzW2MoMjAsIDIyKSwgXSR0b3RhbCkgKiBkYXRhX2Nhc2VzWzIxLCBdJHRvdGFsDQpkYXRhX2Nhc2VzWzMyLCBjKCJFQyIsICJGUyIsICJHUCIsICJLWk4iLCAiTFAiLCAiTVAiLCAiTkMiLCAiTlciLCAiV0MiLCAiVU5LTk9XTiIpXSA8LQ0KICBjb2xTdW1zKGRhdGFfY2FzZXNbYygzMSwgMzMpLCBjKCJFQyIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJGUyIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJHUCIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJLWk4iLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiTFAiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiTVAiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiTkMiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiTlciLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiV0MiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiVU5LTk9XTiIpXSkgLyBzdW0oZGF0YV9jYXNlc1tjKDMxLCAzMyksIF0kdG90YWwpICogZGF0YV9jYXNlc1szMiwgXSR0b3RhbA0KDQpgYGANCg0KVGhlIGZvbGxvd2luZyBmdW5jdGlvbiB3aWxsIGJlIGFwcGxpZWQgdG8gY2FzZSBhbmQgZGVhdGggZGF0YS4gIEluIHRoaXMgZnVuY3Rpb24gdGhlIGZvbGxvd2luZyBvY2N1cnM6DQoNCjEuIFNjYWxlIHVwIHRoZSBwZXIgcHJvdmluY2UgZGF0YSBmb3IgdW5rbm93biB2YWx1ZXMuDQoyLiBUaGlzIHJlc3VsdHMgaW4gcHJvdmluY2lhbCBkYXRhIHdoaWNoIGFyZSBub3Qgd2hvbGUgbnVtYmVycy4gIFRoZXNlIGFyZSByb3VuZGVkIHRvIHRoZSBuZWFyZXN0IHdob2xlIG51bWJlci4NCjMuIEEgYFNBYCBjb2x1bW4gaXMgYWRkZWQgYXMgdGhlIHN1bSBvZiB0aGUgbmV3IHBlciBwcm92aW5jZSBkYXRhLg0KNC4gRGF0YSBpcyBmb3JtYXR0ZWQgYW5kIGRpc2FnZ3JlZ2F0ZWQgc3VjaCB0aGF0IGl0ZW0gcmVwcmVzZW50cyB0aGUgaW5jcmVtZW50YWwgY2FzZXMgb3IgZGVhdGhzIHJhdGhlciB0aGFuIGN1bXVsYXRpdmUgZmlndXJlcy4NCjUuIERhdGEgaXMgZmlsbGVkIHdpdGggZGF0YSAoYWxiZWl0IHdpdGggMCBjYXNlcyBvciBkZWF0aHMpIGZvciBhbGwgZGF0ZXMgaW4gdGhlIHJhbmdlLg0KNi4gQW55IGluY3JlbWVudGFsIGNhc2Ugb3IgZGVhdGggY291bnRzIHRoYXQgYXJlIG5lZ2F0aXZlIGFyZSB6ZXJvZWlzZWQuDQo2LiBOZXcgY3VtdWxhdGl2ZSBmaWd1cmVzIGFyZSBjYWxjdWxhdGVkLg0KDQoNCmBgYHtyfQ0KZml4X2RhdGEgPC0NCiAgZnVuY3Rpb24oZGF0YSwNCiAgICAgICAgICAgc3RhcnRfZGF0ZSA9IGFzLkRhdGUoIjIwMjAtMDMtMDEiKSwNCiAgICAgICAgICAgZW5kX2RhdGUgPSBhcy5EYXRlKCIyMDIwLTAzLTMxIikpIHsNCiAgICAjIFNjYWxlIHByb3ZpbmNlcyBieSBzY2FsZSBmYWN0b3IgKGFzc3VtZSB1bmtub3duIGFyZSBpbiBwcm9wb3J0aW9uKQ0KICAgIGRhdGFbLCBjKCJFQyIsICJGUyIsICJHUCIsICJLWk4iLCAiTFAiLCAiTVAiLCAiTkMiLCAiTlciLCAiV0MiKV0gPC0NCiAgICAgIGRhdGFbLCBjKCJFQyIsICJGUyIsICJHUCIsICJLWk4iLCAiTFAiLCAiTVAiLCAiTkMiLCAiTlciLCAiV0MiKV0gKg0KICAgICAgKDEgKyBkYXRhJFVOS05PV04gLw0KICAgICAgICAgcm93U3VtcyhkYXRhWywgYygiRUMiLCAiRlMiLCAiR1AiLCAiS1pOIiwgIkxQIiwgIk1QIiwgIk5DIiwgIk5XIiwgIldDIildKSkNCiAgICANCiAgICAjIE9ubHkgc2VsZWN0IGNvbHVtbnMgd2UgbmVlZA0KICAgIGRhdGEgPC0gZGF0YSAlPiUNCiAgICAgIHNlbGVjdCgiZGF0ZSIsICJFQyIsICJGUyIsICJHUCIsICJLWk4iLCAiTFAiLCAiTVAiLCAiTkMiLCAiTlciLCAiV0MiKQ0KICAgIGRhdGEkZGF0ZSA8LSBhcy5EYXRlKGRhdGEkZGF0ZSwgIiVkLSVtLSVZIikNCiAgICANCiAgICAjIFJvdW5kIGRhdGEgc28gd2UgaGF2ZSBpbnRlZ2VyIGNhc2VzDQogICAgZGF0YVssIGMoIkVDIiwgIkZTIiwgIkdQIiwgIktaTiIsICJMUCIsICJNUCIsICJOQyIsICJOVyIsICJXQyIpXSA8LQ0KICAgICAgcm91bmQoZGF0YVssIGMoIkVDIiwgIkZTIiwgIkdQIiwgIktaTiIsICJMUCIsICJNUCIsICJOQyIsICJOVyIsICJXQyIpXSAsIDApDQogICAgDQogICAgIyBDYWxjdWxhdGUgYSBuZXcgU0EgY29sdW1uDQogICAgZGF0YSRTQSA8LQ0KICAgICAgcm93U3VtcyhkYXRhWywgYygiRUMiLCAiRlMiLCAiR1AiLCAiS1pOIiwgIkxQIiwgIk1QIiwgIk5DIiwgIk5XIiwgIldDIildKQ0KICAgIA0KICAgICMgIk1lbHQiIHRoZSBkYXRhDQogICAgZGF0YSA8LQ0KICAgICAgcGl2b3RfbG9uZ2VyKA0KICAgICAgICBkYXRhLA0KICAgICAgICBjb2xzID0gYygiRUMiLCAiRlMiLCAiR1AiLCAiS1pOIiwgIkxQIiwgIk1QIiwgIk5DIiwgIk5XIiwgIldDIiwgIlNBIiksDQogICAgICAgIG5hbWVzX3RvID0gInByb3ZpbmNlIiwNCiAgICAgICAgdmFsdWVzX3RvID0gImNvdW50Ig0KICAgICAgKQ0KICAgIA0KICAgICMgR2V0dGluZyBkYWlseSBkYXRhIGZyb20gdGhlIGN1bXVsYXRpdmUgZGF0YSBzZXQNCiAgICBkYXRhID0gIGRhdGEgJT4lDQogICAgICBncm91cF9ieShwcm92aW5jZSkgJT4lDQogICAgICBhcnJhbmdlKGRhdGUpICU+JQ0KICAgICAgbXV0YXRlKGNvdW50ID0gY291bnQgLSBsYWcoY291bnQsIGRlZmF1bHQgPSAwKSkgJT4lDQogICAgICB1bmdyb3VwKCkNCg0KICAgICMgYWRkIG1pc3NpbmcgZGF0ZXMNCiAgICBhbGxfZGF0ZXMgPC0gZXhwYW5kX2dyaWQoZGF0ZSA9IHNlcShzdGFydF9kYXRlLCBlbmRfZGF0ZSwgMSksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHByb3ZpbmNlID0gbGV2ZWxzKGFzLmZhY3RvcihkYXRhJHByb3ZpbmNlKSkpDQogICAgDQogICAgIyBqb2luDQogICAgZGF0YSA8LSBsZWZ0X2pvaW4oYWxsX2RhdGVzLCBkYXRhLCBieSA9YygiZGF0ZSIsInByb3ZpbmNlIikpDQogICAgDQogICAgI3Byb3ZpbmNlIGZhY3Rvcg0KICAgIGRhdGEkcHJvdmluY2UgPC0gYXMuZmFjdG9yKGRhdGEkcHJvdmluY2UpDQoNCiAgICAjIDAgZm9yIE5Bcw0KICAgIGRhdGEkY291bnQgPC0gaWZlbHNlKGlzLm5hKGRhdGEkY291bnQpLCAwLCBkYXRhJGNvdW50KQ0KICAgIA0KICAgICMgcmVtb3ZlIG5lZ2F0aXZlcw0KICAgIGRhdGEkY291bnQgPC0gaWZlbHNlKGRhdGEkY291bnQgPCAwLCAwLCBkYXRhJGNvdW50KQ0KICAgIA0KICAgIGRhdGEgPC0gZGF0YSAlPiUNCiAgICAgIGdyb3VwX2J5KHByb3ZpbmNlKSAlPiUNCiAgICAgIG11dGF0ZShjdW11bGF0aXZlX2NvdW50ID0gY3Vtc3VtKGNvdW50KSkgJT4lDQogICAgICB1bmdyb3VwKCkNCiAgICANCiAgICByZXR1cm4oZGF0YSkNCiAgfQ0KYGBgDQoNCkJlbG93IHdlIHVzZSB0aGUgZnVuY3Rpb24gYWJvdmUgdG8gcHJvY2VzcyBkZWF0aHMgYW5kIGNhc2VzIGFuZCB0aGVuIGNvbWJpbmUgdGhlbSBpbnRvIGEgc2luZ2xlIGRhdGFzZXQuDQoNCmBgYHtyfQ0Kc3RhcnRfZGF0ZSA8LSBtaW4oYXMuRGF0ZShkYXRhX2Nhc2VzJGRhdGUsICIlZC0lbS0lWSIpKQ0KZW5kX2RhdGUgPC0gbWF4KGFzLkRhdGUoZGF0YV9jYXNlcyRkYXRlLCAiJWQtJW0tJVkiKSkNCmRhdGFfY2FzZXMgPC0NCiAgZml4X2RhdGEoZGF0YV9jYXNlcywgc3RhcnRfZGF0ZSA9IHN0YXJ0X2RhdGUsIGVuZF9kYXRlID0gZW5kX2RhdGUpDQpkYXRhX2RlYXRocyA8LQ0KICBmaXhfZGF0YShkYXRhX2RlYXRocywgc3RhcnRfZGF0ZSA9IHN0YXJ0X2RhdGUsIGVuZF9kYXRlID0gZW5kX2RhdGUpDQoNCmRhdGFfY2FzZXM8LWNiaW5kKCJjYXNlcyIsZGF0YV9jYXNlcykNCmRhdGFfZGVhdGhzPC1jYmluZCgiZGVhdGhzIixkYXRhX2RlYXRocykNCmNvbG5hbWVzKGRhdGFfY2FzZXMpWzFdPC0idHlwZSINCmNvbG5hbWVzKGRhdGFfZGVhdGhzKVsxXTwtInR5cGUiDQoNCiMgY29tYmluZWQNCmRhdGEgPC0NCiAgcmJpbmQoZGF0YV9jYXNlcyxkYXRhX2RlYXRocykNCg0KIyByZW1vdmUgZGF0YSBzZXRzIG5vIGxvbmdlciBuZWVkZWQNCnJtKCJkYXRhX2Nhc2VzIiwgImRhdGFfZGVhdGhzIiwgInN0YXJ0X2RhdGUiLCAiZW5kX2RhdGUiKQ0KYGBgDQoNCiMjIEJhc2ljIEV4cGxvcmF0aW9uDQoNCkJlbG93IHdlIHBsb3QgY3VtdWxhdGl2ZSBjYXNlIGNvdW50IG9uIGEgbG9nIHNjYWxlIGJ5IHByb3ZpbmNlOg0KDQpgYGB7cn0NCmdncGxvdChkYXRhICU+JSBmaWx0ZXIodHlwZT09ImNhc2VzIiksIGFlcyh4ID0gZGF0ZSwgeSA9IGN1bXVsYXRpdmVfY291bnQpKSArIA0KICBnZW9tX2xpbmUoYWVzKGNvbG9yID0gcHJvdmluY2UpLCBzaXplID0gMSkgKw0KICBzY2FsZV95X2xvZzEwKGxhYmVscz1jb21tYSkrIA0KICBnZ3RpdGxlKCJDdW11bGF0aXZlIENhc2VzIGJ5IFByb3ZpbmNlIikgKw0KICB0aGVtZV9idygpICsNCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSwNCiAgICAgICAgbGVnZW5kLnBvc2l0aW9uID0gInJpZ2h0IikgKw0KICBndWlkZXMoZmlsbCA9IGd1aWRlX2xlZ2VuZChuY29sID0gMSkpICsNCiAgc2NhbGVfY29sb3JfaHVlKGwgPSA1MCkNCmBgYA0KQmVsb3cgd2UgcGxvdCB0aGUgY3VtdWxhdGl2ZSBkZWF0aHMgYnkgcHJvdmluY2Ugb24gYSBsb2cgc2NhbGU6DQoNCmBgYHtyfQ0KZ2dwbG90KGRhdGEgJT4lIGZpbHRlcih0eXBlID09ICJkZWF0aHMiKSwNCiAgICAgICBhZXMoeCA9IGRhdGUsIHkgPSBjdW11bGF0aXZlX2NvdW50KSkgKw0KICBnZW9tX2xpbmUoYWVzKGNvbG9yID0gcHJvdmluY2UpLCBzaXplID0gMSkgKw0KICBzY2FsZV95X2xvZzEwKGxhYmVscyA9IGNvbW1hKSArDQogIGdndGl0bGUoIkN1bXVsYXRpdmUgRGVhdGhzIGJ5IFByb3ZpbmNlIikgKw0KICB0aGVtZV9idygpICsNCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSwNCiAgICAgICAgbGVnZW5kLnBvc2l0aW9uID0gInJpZ2h0IikgKw0KICBndWlkZXMoZmlsbCA9IGd1aWRlX2xlZ2VuZChuY29sID0gMSkpICsNCiAgc2NhbGVfY29sb3JfaHVlKGwgPSA1MCkNCmBgYA0KDQojIyBTZXJpYWwgSW50ZXJ2YWwNCg0KVG8gZG8gZnVydGhlciBhbmFseXNpcyBhbiBzZXJpYWwgaW50ZXJ2YWwgYXNzdW1wdGlvbiBpcyBuZWVkZWQuICBUaGUgc2VyaWFsIGludGVydmFsIGlzIHRha2VuIGZyb20gW0BGZXJndXNvbjIwMjBdLiAgSXQncyBhc3N1bWVkIHRvIGJlIEdhbW1hIGRpc3RyaWJ1dGVkIHdpdGggbWVhbiBvZiA2LjQ4IGFuZCBzdGFuZGFyZCBkZXZpYXRpb24gb2YgMy44My4gIFRoaXMgY29ycmVzcG9uZHMgdG8gdGhlIGVmZmVjdGl2ZSBpbmZlY3Rpb3VzbmVzcyBvZiBhbiBpbmRpdmlkdWFsIHNpbmNlIGFjcXVpcmluZyB0aGUgaW5mZWN0aW9uIHRoZW1zZWx2ZXMuICANCg0KV2UgcGxvdCB0aGlzIHNlcmlhbCBkaXN0cmlidXRpb24gYmVsb3c6DQoNCmBgYHtyfQ0KbWVhbjEgPC0gNi40OA0Kc2QxIDwtIDMuODMNCmN2MSA8LSBzZDEgLyBtZWFuMQ0KDQpzZXJpYWxfaW50ZXJ2YWwgPSByZXAoMCwgMSkNCnNlcmlhbF9pbnRlcnZhbFsxXSA9IChwZ2FtbWFBbHQoMS41LCBtZWFuMSwgY3YxKSAtIHBnYW1tYUFsdCgwLCBtZWFuMSwgY3YxKSkNCmZvciAoaSBpbiAyOjUwKSB7DQogIHNlcmlhbF9pbnRlcnZhbFtpXSA9IChwZ2FtbWFBbHQoaSArIC41LCBtZWFuMSwgY3YxKSAtIHBnYW1tYUFsdChpIC0gLjUsIG1lYW4xLCBjdjEpKQ0KfQ0KZ2dwbG90KA0KICBkYXRhLmZyYW1lKGRheXMgPSBzZXEoMSwgMjAsIDEpLCBzZXJpYWxfaW50ZXJ2YWwgPSBzZXJpYWxfaW50ZXJ2YWxbMToyMF0pLA0KICBhZXMoeSA9IHNlcmlhbF9pbnRlcnZhbCwgeCA9IGRheXMpDQopICsgZ2VvbV9saW5lKHNpemUgPSAxKSArDQogIHRoZW1lX2J3KCkgKw0KICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpLA0KICAgICAgICBsZWdlbmQucG9zaXRpb24gPSAicmlnaHQiKSArDQogIGd1aWRlcyhmaWxsID0gZ3VpZGVfbGVnZW5kKG5jb2wgPSAxKSkgKw0KICBzY2FsZV9jb2xvcl9odWUobCA9IDUwKQ0KDQpgYGANCg0KIyMgRXN0aW1hdGUgJFJfe3R9JCB2YWx1ZXMgYnkgd2Vlaw0KDQpUaGUgZm9sbG93aW5nIGNvZGUgd29ya3MgYmFja3dhcmRzIGFuZCBmaXRzICRSX3t0fSQgdmFsdWVzIGZvciB3aG9sZSB3ZWVrcyAoZW5kaW5nIG9uIHRoZSBsYXN0IGRhdGUgaW4gdGhlIGRhdGEpIHVzaW5nIHRoZSBgRXBpRXN0aW1gIHBhY2thZ2UuIFVzaW5nIGRlYXRocyBvciBpbmZlY3Rpb24gdGhlIGFzIHRoZSBjYXNlcyBpbiB0aGUgcHV0cyB0aGUgZXN0aW1hdGUgb2YgJFJfe3R9JCBhcyBhdCB0aGUgZGF0ZSBvZiB0aGUgY2FzZXMgYmVpbmcgdHJhY2tlZC4NCg0KVHdvIHZhbHVlcyBhcmUgZXN0aW1hdGVkIGZvciBlYWNoIHByb3ZpbmNlIChpbmNsdWRpbmcgU291dGggQWZyaWNhIGFzIGEgd2hvbGUpOg0KDQoxLiAkUl97dCxtfV57Y2FzZXN9JCBpcyB0aGUgcmVwcm9kdWN0aXZlIG51bWJlciBpbXBsaWVkIGJ5IHRoZSBjYXNlcyByZXBvcnRlZCBhdCB0aW1lICR0JCBpbiBwcm92aW5jZSAkbSQuDQoyLiAkUl97dCxtfV57ZGVhdGhzfSQgaXMgdGhlIHJlcHJvZHVjdGl2ZSBudW1iZXIgaW1wbGllZCBieSB0aGUgZGVhdGhzIHJlcG9ydGVkIGF0IHRpbWUgJHQkIGluIHByb3ZpbmNlICRtJC4NCg0KTm90ZSB0aGF0IHRoZSB0aW1lIHBlcmlvZHMgYXJlIGxlZnQgdW5hZGp1c3RlZCwgdGhvdWdoIGluIHJlYWxpdHkgdGhlICRSX3t0LG19XntkZWF0aHN9JCBzaG91bGQgYmUgc2hpZnRlZCBiYWNrIGFwcHJveGltYXRlbHkgMiB3ZWVrcyByZWxhdGl2ZSB0byAkUl97dCxtfV57Y2FzZXN9JC4NCg0KYGBge3J9DQpSdF9kYXRhIDwtIE5VTEwNCmZvciAocCBpbiBsZXZlbHMoZGF0YSRwcm92aW5jZSkpIHsNCiAgZm9yICh0IGluIGxldmVscyhkYXRhJHR5cGUpKSB7DQogICAgIyBmaWx0ZXIgb3V0IHByb3ZpbmNlIGRhdGEgb2YgdHlwZSB0DQogICAgcF9kYXRhIDwtIGRhdGEgJT4lIGZpbHRlcihwcm92aW5jZSA9PSBwICYgdHlwZSA9PSB0KQ0KICAgIA0KICAgICMgdmVjdG9yIG9mIGNvdW50IG9mIGNhc2VzL2RlYXRocw0KICAgIEkgPC0gcF9kYXRhJGNvdW50DQogICAgDQogICAgIyB0PTEgY29ycmVzcG9uZHMgdG8gdGhpcyBkYXRlOg0KICAgIHQxX2RhdGUgPC0gbWluKHBfZGF0YSRkYXRlKQ0KICAgIA0KICAgICMgdGhlIGRheSBhZnRlciB0aGUgZmlyc3QgY2FzZS9kZWF0aDoNCiAgICB0X3N0YXJ0X2RhdGUgPC0gbWluKChwX2RhdGEgJT4lIGZpbHRlcihjb3VudCA+IDApKSRkYXRlKSArIDENCiAgICB0X3N0YXJ0IDwtIG1pbihzZXEoMSwgbGVuZ3RoKEkpKVtJID4gMF0pICsgMQ0KICAgIA0KICAgICMgbGFzdCBkYXkgb2YgY2FzZXMvZGVhdGhzDQogICAgdF9lbmQgPC0gbGVuZ3RoKEkpDQogICAgDQogICAgIyBob3cgbWFueSBmdWxsIHdlZWtzIGRvIHdlIGhhdmUNCiAgICBmdWxsX3dlZWtzIDwtIGZsb29yKCh0X2VuZCAtIHRfc3RhcnQpIC8gNykNCiAgICANCiAgICAjIG9ubHkgY29udGludWUgaWYgd2UgaGF2ZSAxIGZ1bGwgd2VlayBvciBtb3JlDQogICAgaWYgKGZ1bGxfd2Vla3MgPiAwKSB7DQogICAgICAjIHRoZW4gZGl2aWRlIHBlcmlvZCBpbnRvIHdlZWtzDQogICAgICBULlN0YXJ0IDwtIHNlcSh0X2VuZCAtIDcgKiBmdWxsX3dlZWtzLCB0X2VuZCAtIDcsIGJ5ID0gNykNCiAgICAgIFQuRW5kIDwtIFQuU3RhcnQgKyA3DQogICAgICANCiAgICAgICMgZXN0aW1hdGUgJFJfe3QsbX1ee3R5cGV9JCBmb3IgZWFjaCB3ZWVrOg0KICAgICAgcF9SdCA8LSBFc3RpbWF0ZVIoDQogICAgICAgIEksDQogICAgICAgIFQuU3RhcnQgPSBULlN0YXJ0LA0KICAgICAgICBULkVuZCA9IFQuRW5kLA0KICAgICAgICBNZWFuLlNJID0gbWVhbjEsDQogICAgICAgIFN0ZC5TSSA9IHNkMSwNCiAgICAgICAgbWV0aG9kID0gIlBhcmFtZXRyaWNTSSINCiAgICAgICkkUg0KICAgICAgDQogICAgICAjIGFkZCBwcm92aW5jZSBhbmQgdHlwZSBkZXNpZ25hdGlvbnMNCiAgICAgIHBfUnQgPC0gY2JpbmQocCwgdCwgcF9SdCkNCiAgICAgIA0KICAgICAgIyBhbmQgYWRkIGRhdGVzDQogICAgICBwX1J0JGRhdGVfc3RhcnQgPC0gdDFfZGF0ZSArIHBfUnQkVC5TdGFydA0KICAgICAgcF9SdCRkYXRlX2VuZCA8LSB0MV9kYXRlICsgcF9SdCRULkVuZA0KICAgICAgDQogICAgICAjIGNvbWJpbmUgdGhlIHJlc3VsdHMNCiAgICAgIGlmIChpcy5udWxsKFJ0X2RhdGEpKSB7DQogICAgICAgIFJ0X2RhdGEgPC0gcF9SdA0KICAgICAgfSBlbHNlIHsNCiAgICAgICAgUnRfZGF0YSA8LSByYmluZChSdF9kYXRhLCBwX1J0KQ0KICAgICAgfQ0KICAgIH0NCiAgfQ0KfQ0KYGBgDQoNCkJlbG93IHdlIHByZXAgY29sdW1uIGhlYWRpbmdzIGFuZCBwcmVwYXJlIENJIGRhdGE6DQoNCmBgYHtyfQ0KIyBDb2x1bW4gbmFtZXMNCmNvbG5hbWVzKFJ0X2RhdGEpIDwtIGMoDQogICJwcm92aW5jZSIsDQogICJ0eXBlIiwNCiAgInRfc3RhcnQiLA0KICAidF9lbmQiLA0KICAiUnRfbWVhbiIsDQogICJSdF9zdGQiLA0KICAiUnRfbGlfOTUiLA0KICAiUnRfbGlfOTAiLA0KICAiUnRfbGlfNTAiLA0KICAiUnRfbWVkaWFuIiwNCiAgIlJ0X3VpXzUwIiwNCiAgIlJ0X3VpXzkwIiwNCiAgIlJ0X3VpXzk1IiwNCiAgImRhdGVfc3RhcnQiLA0KICAiZGF0ZV9lbmQiDQopDQoNCiMgbWlkIHdlZWsgZGF0YSBwb2ludA0KUnRfZGF0YSRkYXRlX21pZCA8LSBSdF9kYXRhJGRhdGVfc3RhcnQrKFJ0X2RhdGEkZGF0ZV9lbmQtUnRfZGF0YSRkYXRlX3N0YXJ0KS8yDQoNCiMgc3BsaXQgb3V0IENJIGRhdGEgaW50byBkaWZmZXJlbnQgZGF0YXNldA0KUnRfY2lfZGF0YTwtIE5VTEwNCmZvciAoY2kgaW4gYygiNTAiLCI5MCIsICI5NSIpKSB7DQogIHIgPC0gZGF0YS5mcmFtZSgNCiAgICBwcm92aW5jZT1SdF9kYXRhJHByb3ZpbmNlLA0KICAgIHR5cGU9UnRfZGF0YSR0eXBlLA0KICAgIGNpPXJlcChwYXN0ZTAoY2ksIiUiKSxucm93KFJ0X2RhdGEpKSwNCiAgICBkYXRlX21pZD1SdF9kYXRhJGRhdGVfbWlkLA0KICAgIFJ0X21lYW49UnRfZGF0YSRSdF9tZWFuLA0KICAgIFJ0X2xpPVJ0X2RhdGFbW3Bhc3RlMCgiUnRfbGlfIixjaSldXSwNCiAgICBSdF91aT1SdF9kYXRhW1twYXN0ZTAoIlJ0X3VpXyIsY2kpXV0NCiAgKQ0KICBSdF9jaV9kYXRhPC1yYmluZChyLFJ0X2NpX2RhdGEpDQp9DQpgYGANCg0KTGV0J3MgZ2VuZXJhdGUgb3VyIHBsb3RzIGZvciBlYWNoIHByb3ZpbmNlIGluIGEgbGlzdDoNCg0KYGBge3J9DQpwcm92aW5jZV9wbG90cyA8LSBsaXN0KCkNCmZvciAocCBpbiBsZXZlbHMoUnRfZGF0YSRwcm92aW5jZSkpIHsNCiAgcDEgPC0gZ2dwbG90KFJ0X2NpX2RhdGEgJT4lIGZpbHRlcihwcm92aW5jZSA9PSBwICYNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNpID09ICI5NSUiKSwNCiAgICAgICAgICAgICAgIGFlcyh4ID0gZGF0ZV9taWQsIHkgPSBSdF9tZWFuKSkgKw0KICAgIGdlb21fY3Jvc3NiYXIoDQogICAgICBwb3NpdGlvbiA9IHBvc2l0aW9uX2RvZGdlMihwYWRkaW5nID0gMCksDQogICAgICBhZXMoDQogICAgICAgIHltaW4gPSBSdF9saSwNCiAgICAgICAgeW1heCA9IFJ0X3VpLA0KICAgICAgICBjb2xvdXIgPSB0eXBlLA0KICAgICAgICBmaWxsID0gdHlwZQ0KICAgICAgKSwNCiAgICAgIHdpZHRoID0gNw0KICAgICkgKw0KICAgIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGMoYWxwaGEoImRlZXBza3libHVlNCIsIDAuNDUpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYWxwaGEoIiM4YjAwNjgiLCAwLjQ1KSkpICsNCiAgICBzY2FsZV9jb2xvdXJfbWFudWFsKHZhbHVlcyA9IGMoYWxwaGEoImRlZXBza3libHVlNCIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhbHBoYSgiIzhiMDA2OCIpKSkgKw0KICAgIHRoZW1lX2J3KCkgKw0KICAgIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSksDQogICAgICAgICAgbGVnZW5kLnBvc2l0aW9uID0gInJpZ2h0IikgKw0KICAgIGd1aWRlcyhmaWxsID0gZ3VpZGVfbGVnZW5kKG5jb2wgPSAxKSkgKw0KICAgIHhsYWIoIldlZWsiKSArDQogICAgeWxhYihleHByZXNzaW9uKFJbdCxtXSkpICArDQogICAgZ2d0aXRsZShwKQ0KICBwcm92aW5jZV9wbG90c1tbcF1dIDwtIHAxDQp9DQpgYGANCg0KTGV0J3MgbG9vayBhdCBTQSBiZWxvdy4gIEluICBibHVlIHdlIHBsb3QgdGhlIHJlcHJvZHVjdGl2ZSBudW1iZXIgZXN0aW1hdGVkIGZyb20gY2FzZXMgJFJfe3QsbX1ee2Nhc2VzfSQgYW5kIGluIHJlZCB3ZSBwbG90IHRoZSByZXByb2R1Y3RpdmUgbnVtYmVyIGVzdGltYXRlZCBmcm9tIGRlYXRocyAkUl97dCxtfV57ZGVhdGhzfSQuICBJdCdzIGNsZWFyIHRoYXQgdGhlICRSX3t0LG19XntkZWF0aHN9JCBzaG93cyBhIGRlbGF5IHdoZW4gY29tcGFyZWQgdG8gJFJfe3QsbX1ee2Nhc2VzfSQuICBEdXJpbmcgQXByaWwgJFJfe3QsbX1ee2RlYXRoc30kIHJlbWFpbnMgaGlnaCB3aGlsZSAkUl97dCxtfV57Y2FzZXN9JCBoYXMgZHJvcHBlZCAocHJlc3VtYWJseSB0byB0aGUgbG9ja2Rvd24pLiBGb3Igc29tZSBwZXJpb2RzIG5vIGRlYXRoIGRhdGEgaXMgYXZhaWxhYmxlIGFuZCB3ZSBvbmx5IGVzdGltYXRlIHVzaW5nIGNhc2UgZGF0YS4NCg0KYGBge3J9DQpwcm92aW5jZV9wbG90c1tbIlNBIl1dK2dndGl0bGUoIlJlcHJvZHVjdGl2ZSBudW1iZXIgaW4gU0Egd2l0aCA5NSUgY29uZmlkZW5jZSBpbnRlcnZhbHMiKQ0KYGBgDQoNCkJlbG93IHdlIHBsb3QgYWxsIHRoZSBwcm92aW5jZXM6DQoNCmBgYHtyIGZpZy5oZWlnaHQ9MjQsIGZpZy53aWR0aD0xMn0NCmdnYXJyYW5nZShwcm92aW5jZV9wbG90c1tbIkVDIl1dLHByb3ZpbmNlX3Bsb3RzW1siRlMiXV0sDQogICAgICAgICAgcHJvdmluY2VfcGxvdHNbWyJHUCJdXSxwcm92aW5jZV9wbG90c1tbIktaTiJdXSwNCiAgICAgICAgICBwcm92aW5jZV9wbG90c1tbIkxQIl1dLHByb3ZpbmNlX3Bsb3RzW1siTVAiXV0sDQogICAgICAgICAgcHJvdmluY2VfcGxvdHNbWyJOQyJdXSxwcm92aW5jZV9wbG90c1tbIk5XIl1dLA0KICAgICAgICAgIHByb3ZpbmNlX3Bsb3RzW1siV0MiXV0sDQogICAgICAgICAgbmNvbD0yLG5yb3c9NSkgKw0KICBnZ3RpdGxlKCJSZXByb2R1Y3RpdmUgbnVtYmVyIGluIFNBLCBieSBwcm92aW5jZSwgd2l0aCA5NSUgY29uZmlkZW5jZSBpbnRlcnZhbHMiKQ0KYGBgDQojIyBEaXNjdXNzaW9uDQoNClRoZSBhYm92ZSBzaG93cyBlc3RpbWF0ZXMgZm9yIHJlcHJvZHVjdGl2ZSBudW1iZXIgdXNpbmcgY2FzZXMgYW5kIGRlYXRocyAoJFJfe3QsbX1ee2Nhc2VzfSQgYW5kICRSX3t0LG19XntkZWF0aHN9JCkgZm9yIGVhY2ggcHJvdmluY2UgJG0kIG92ZXIgdGltZSAkdCQgaW4gd2Vla3MuDQoNCkl0J3MgY2xlYXIgdGhhdDoNCg0KKiBJbml0aWFsbHkgJFJfe3QsbX0kIHdhcyBoaWdoIGFuZCBpdCBkcm9wcGVkICh3aGVyZSB0aGVyZSB3YXMgZW5vdWdoIGRhdGEpDQoqIFRoZSBjb25maWRlbmNlIGludGVydmFscyBmb3IgZGF0YSBiYXNlZCBvbiBkZWF0aHMgaXMgY29uc2lkZXJhYmx5IHdpZGVyIGFzIHRoZXJlIGlzIGxlc3MgZGF0YS4NCiogSXQgZG9lcyBzZWVtIHRoYXQgcHJvdmluY2VzIHRoYXQgJFJfe3QsbX1ee2RlYXRoc30kIGhhdmUgaW5jcmVhc2VkIHdpdGhvdXQgY29ycmVzcG9uZGluZyBpbmNyZWFzZSBpbiAkUl97dCxtfV57Y2FzZXN9JCB0aG91Z2h0IGhlIGNvbmZpZGVuY2UgaW50ZXJ2YWxzIGZvciB0aGlzIGlzIHdpZGUuICBUaGlzIGNvdWxkIHBvdGVudGlhbGx5IGJlIHRoZSByZXN1bHQgb2YgbG93ZXIgbnVtYmVyIG9mIHRlc3RzIGJlaW5nIGNvbXBsZXRlZC4NCiogRm9yIHRoZSBXZXN0ZXJuIENhcGUgdGhlICRSX3t0LG19XntjYXNlc30kIGFuZCAkUl97dCxtfV57ZGVhdGhzfSQgZXN0aW1hdGVzIGFwcGVhciBnZW5lcmFsbHkgY29uc2lzdGVudC4NCiogRm9yIEdhdXRlbmcgYW5kIEVhc3Rlcm4gQ2FwZSBpdCBhcHBlYXJzIHRoYXQgdGhlICRSX3t0LG19XntjYXNlc30kIGlzIGluY3JlYXNpbmcgb3ZlciB0aGUgbGFzdCBudW1iZXIgb2Ygd2Vla3MuIEVhcmx5IGluZGljYXRpb25zIGFyZSB0aGF0IHRoZSAkUl97dCxtfV57ZGVhdGhzfSQgbWF5IGFsc28gYmUgaW5jcmVhc2luZy4NCg0KSXQgYXBwZWFycyB0aGF0IHRoZSAkUl97dCxtfV57Y2FzZXN9JCBhbmQgJFJfe3QsbX1ee2RlYXRoc30kIHdvdWxkIGJlIHVzZWZ1bCBhcyBhIG1vbml0b3JpbmcgdG9vbCwgZXNwZWNpYWxseSB0aGUgJFJfe3QsbX1ee2RlYXRoc30kIGFzIHRlc3RpbmcgYmFja2xvZ3MgYW5kIGlzc3VlcyBtYXkgYmlhcyAkUl97dCxtfV57Y2FzZXN9JCBkZXJpdmVkLiAgVGhpcyByZXBvcnQgbWF5IGJlIHVzZWZ1bCBpbiB0aGlzIHJlZ2FyZC4NCg0KIyMgQXV0aG9yDQoNClRoaXMgcmVwb3J0IHdhcyBwcmVwYXJlZCBieSBMb3VpcyBSb3Nzb3V3LiAgUGxlYXNlIGdldCBpbiBjb250YWN0IHdpdGggTG91aXMgUm9zc291dyBpZiB5b3UgaGF2ZSBjb21tZW50cyBvciB3aXNoIHRvIHJlY2VpdmUgdGhpcyByZWd1bGFybHkuDQoNCioqTG91aXMgUm9zc291dyoqICANCkhlYWQgb2YgUmVzZWFyY2ggJiBBbmFseXRpY3MgIA0KR2VuIFJlIHwgTGlmZS9IZWFsdGggQ2FuYWRhLCBTb3V0aCBBZnJpY2EsIEF1c3RyYWxpYSwgTlosIFVLICYgSXJlbGFuZCAgDQpFbWFpbDogW0xSb3Nzb3V3QEdlblJlLmNvbV0obWFpbHRvOkxSb3Nzb3V3QGdlbnJlLmNvbSkgTW9iaWxlOiArMjcgNzEgMzU1IDI1NTANCg0KKioqVGhlIHZpZXdzIGluIHRoaXMgZG9jdW1lbnQgcmVwcmVzZW50cyB0aGF0IG9mIHRoZSBhdXRob3IgYW5kIG1heSBub3QgcmVwcmVzZW50IHRob3NlIG9mIEdlbiBSZS4gIEFsc28gbm90ZSB0aGF0IGdpdmVuIHRoZSBzaWduaWZpY2FudCB1bmNlcnRhaW50eSBpbnZvbHZlZCB3aXRoIHRoZSBwYXJhbWV0ZXJzLCBkYXRhIGFuZCBtZXRob2RvbG9neSBjYXJlIHNob3VsZCBiZSB0YWtlbiB3aXRoIHRoZXNlIG51bWJlcnMgYW5kIGFueSB1c2Ugb2YgdGhlc2UgbnVtYmVycy4qKioNCg0KIyMgUmVmZXJlbmNlcw0K