This workflow should serve as step-by-step guidance starting from downloading a dataset from Neotoma and processing it, to estimating ecosystem property (diversity in this case).

:warning: This workflow is only meant as an example: There are several additional steps for data preparation which should be done for any fossil pollen dataset from Neotoma!

See FOSSILPOL, an R-based modular workflow to process multiple fossil pollen records to create a comprehensive, standardised dataset compilation, ready for multi-record and multi-proxy analyses at various spatial and temporal scales.

Install packages

Please follow the pre-workshop instructions to make sure all packages are installed.

Attach packages

library(tidyverse) # general data wrangling and visualisation ✨
library(pander) # nice tables 😍
library(neotoma2) # access to the Neotoma database 🌿
library(Bchron) # age-depth modelling 🕰️
library(mgcv) # GAM fitting 📈
library(marginaleffects) # predicting trends 📈
library(janitor) # string cleaning 🧹
library(here) # for working directory 🗺️
ggplot2::theme_set(
  ggplot2::theme_bw() +
    ggplot2::theme(
      axis.title = ggplot2::element_text(size = 25),
      axis.text = ggplot2::element_text(size = 15),
      strip.text = ggplot2::element_text(size = 15),
      panel.grid = ggplot2::element_blank()
    )
)

Download a dataset from Neotoma

Here we have selected the Lingua d’Oca (ID = 52162) record by Feredico Di Rita.

Reference paper: Di Rita, F., A. Celant, and D. Magri. 2010. Holocene environmental instability in the wetland north of the Tiber delta (Rome, Italy): sea-lake-man interactions. Journal of Paleolimnology 44:51-67.

sel_dataset_download <-
  neotoma2::get_downloads(52162)

Prepare the pollen counts

# get samples
sel_counts <-
  neotoma2::samples(sel_dataset_download)

# select only "pollen" taxa
sel_taxon_list_selected <-
  neotoma2::taxa(sel_dataset_download) %>%
  dplyr::filter(element == "pollen") %>%
  purrr::pluck("variablename")

# prepare taxa table
sel_counts_selected <-
  sel_counts %>%
  as.data.frame() %>%
  dplyr::mutate(sample_id = as.character(sampleid)) %>%
  tibble::as_tibble() %>%
  dplyr::select("sample_id", "value", "variablename") %>%
  # only include selected taxons
  dplyr::filter(
    variablename %in% sel_taxon_list_selected
  ) %>%
  # turn into the wider format
  tidyr::pivot_wider(
    names_from = "variablename",
    values_from = "value",
    values_fill = 0
  ) %>%
  # clean names
  janitor::clean_names()

head(sel_counts_selected)[, 1:5]
sample_id fagus picea rumex salix
510392 1 1 1 1
510393 3 0 0 0
510394 6 0 1 0
510396 5 0 0 0
510391 8 0 0 0
510395 6 0 0 0

Here, we strongly advocate that attention should be paid to the selection of the ecological groups, the selection of depositional environments, as well as the harmonisation of the pollen taxa. However, that is not the subject of this workflow, but any analysis to be published needs careful preparation of the fossil pollen datasets!

We can now try to visualise the taxa per sample_id

sel_counts_selected %>%
  tibble::rowid_to_column("ID") %>%
  tidyr::pivot_longer(
    cols = -c(sample_id, ID),
    names_to = "taxa",
    values_to = "n_grains"
  ) %>%
  ggplot2::ggplot(
    mapping = ggplot2::aes(
      x = ID,
      y = n_grains,
      fill = taxa
    ),
  ) +
  ggplot2::geom_bar(
    stat = "identity",
    position = "fill"
  ) +
  ggplot2::labs(
    x = "sample_id",
    y = "proportion of pollen grains"
  ) +
  ggplot2::theme(
    axis.text.x = ggplot2::element_blank(),
    legend.position = "none"
  )

Preparation of the levels

Sample depth

Extract depth for each level

sel_level <-
  neotoma2::samples(sel_dataset_download) %>%
  tibble::as_tibble() %>%
  dplyr::mutate(sample_id = as.character(sampleid)) %>%
  dplyr::distinct(sample_id, depth) %>%
  dplyr::relocate(sample_id)

head(sel_level)
sample_id depth
510392 5
510393 10
510394 15
510396 20
510391 25
510395 30

Age-depth modelling

We highly recommend recalculating the age-depth model ‘de Novo’ as different methodologies might have been applied to each record. However, age-depth modelling is a very complicated topic, which we will not dig into today. Just note that many parts of the age-depth modelling need attention (selection of chronology control points, using correct calibration curves, etc.).

# Here we only present a few of the important steps of preparation of the
#   chronology control table. There are many more potential issues, but
#   solving those is not the focus of this workflow.

# First, get the chronologies and check which we want to use used
sel_chron_control_table_download <-
  neotoma2::chroncontrols(sel_dataset_download)

# prepare the table
sel_chron_control_table <-
  sel_chron_control_table_download %>%
  # Here select the ID of one of the chronology
  dplyr::filter(chronologyid == 37228) %>%
  tibble::as_tibble() %>%
  # Here we calculate the error as the average of the age `limitolder` and
  #   `agelimityounger`
  dplyr::mutate(
    error = round((agelimitolder - agelimityounger) / 2)
  ) %>%
  # As Bchron cannot accept an error of 0, we need to replace the value with 1
  dplyr::mutate(
    error = replace(error, error == 0, 1),
    error = ifelse(is.na(error), 1, error)
  ) %>%
  # As Bchron cannot accept an thickness of 0, we need to replace the value with 1
  dplyr::mutate(
    thickness = ifelse(is.na(thickness), 1, thickness)
  ) %>%
  # We need to specify which calibration curve should be used for what point
  dplyr::mutate(
    curve = ifelse(as.data.frame(sel_dataset_download)["lat"] > 0, "intcal20", "shcal20"),
    curve = ifelse(chroncontroltype != "Radiocarbon", "normal", curve)
  ) %>%
  tibble::column_to_rownames("chroncontrolid") %>%
  dplyr::arrange(depth) %>%
  dplyr::select(
    chroncontrolage, error, depth, thickness, chroncontroltype, curve
  )

i_multiplier <- 0.1 # increase to 5

# Those are default values suggested by the Bchron package
n_iteration_default <- 10e3
n_burn_default <- 2e3
n_thin_default <- 8

# Let's multiply them by our i_multiplier
n_iteration <- n_iteration_default * i_multiplier
n_burn <- n_burn_default * i_multiplier
n_thin <- max(c(1, n_thin_default * i_multiplier))

# run Bchron
ad_model <-
  Bchron::Bchronology(
    ages = sel_chron_control_table$chroncontrolage,
    ageSds = sel_chron_control_table$error,
    positions = sel_chron_control_table$depth,
    calCurves = sel_chron_control_table$curve,
    positionThicknesses = sel_chron_control_table$thickness,
    iterations = n_iteration,
    burn = n_burn,
    thin = n_thin
  )

Visually check the age-depth models

plot(ad_model)

Predict ages

age_position <-
  Bchron:::predict.BchronologyRun(object = ad_model, newPositions = sel_level$depth)

age_uncertainties <-
  age_position %>%
  as.data.frame() %>%
  dplyr::mutate_all(., as.integer) %>%
  as.matrix()

colnames(age_uncertainties) <- sel_level$sample_id

# Let's take the median age of all possible ages (i.e. the estimated age
#   from each age-depth model run) as our default.
sel_level_predicted <-
  sel_level %>%
  dplyr::mutate(
    age = apply(
      age_uncertainties, 2,
      stats::quantile,
      probs = 0.5
    )
  )

Visualisation of our data

Let’s now make a simple pollen diagram with proportions of the main pollen taxa (x-axis) against our estimated ages along depth (y-axis).

data_rownames <-
  sel_counts_selected %>%
  tibble::column_to_rownames("sample_id")

data_percentages <-
  (data_rownames / rowSums(data_rownames)) * 100

col_sum_non_zero <-
  colSums(data_percentages) > 0

data_filtered <-
  data_percentages %>%
  dplyr::select(
    dplyr::any_of(
      names(col_sum_non_zero)[col_sum_non_zero]
    )
  ) %>%
  tibble::rownames_to_column("sample_id") %>%
  dplyr::relocate(sample_id) %>%
  tibble::tibble() 

data_filtered %>%
  dplyr::inner_join(
    sel_level_predicted,
    by = dplyr::join_by(sample_id)
  ) %>%
  tidyr::pivot_longer(
    cols = -c(sample_id, depth, age),
    names_to = "taxa",
    values_to = "proportion_of_grains"
  ) %>%
  dplyr::group_by(taxa) %>%
  # Calculate the average proportion of grains
  dplyr::mutate(
    avg_prop = mean(proportion_of_grains)
  ) %>%
  # Only keep the main taxa (on average >= 1% pollen grains)
  dplyr::filter(avg_prop >= 1) %>%
  dplyr::ungroup() %>%
  ggplot2::ggplot(
    mapping = ggplot2::aes(
      y = age,
      x = proportion_of_grains,
      xmax = proportion_of_grains,
      xmin = 0,
      fill = taxa,
      col = taxa
    ),
  ) +
  ggplot2::geom_ribbon() +
  ggplot2::scale_y_continuous(trans = "reverse") +
  ggplot2::scale_x_continuous(breaks = c(0, 1)) +
  ggplot2::facet_wrap(~taxa, nrow = 1) +
  ggplot2::theme(
    legend.position = "none",
    panel.border = ggplot2::element_blank(),
    strip.background = ggplot2::element_blank(),
    strip.text = ggplot2::element_blank(),
    axis.ticks.x = ggplot2::element_blank(),
    axis.text.x = ggplot2::element_blank()
  ) +
  ggplot2::labs(
    x = "Proportion of grains",
    y = "Age (cal yr BP)"
  )

Estimation of ecosystem property

Now we will use our prepared fossil pollen data to estimate the diversity. We will use {REcopol} package, which has easy-to-use functions to analyse fossil pollen data. See package website for more information. Specifically, we will estimate rarefied values of Hill numbers.

# function to estimate diversity
get_diversity <- function(data_source, round = TRUE, sel_method = "") {
  # helper function
  get_diversity_taxonomic <- function(data_matrix, sample_size) {
    hill0 <- function(data, sample_size) {
      data_sub <- data[data > 0]
      data_sum <- sum(data_sub)
      if (sample_size <= data_sum) {
        res <- sum(1 - exp(lchoose(data_sum - data_sub, sample_size) -
          lchoose(data_sum, sample_size)))
        return(res)
      } else {
        return(0)
      }
    }
    fk_hat <- function(data, sample_size) {
      data_sub <- data[data > 0]
      data_sum <- sum(data_sub)
      if (sample_size <= data_sum) {
        sub <- function(k) {
          sum(exp(lchoose(data_sub, k) + lchoose(data_sum -
            data_sub, sample_size - k) - lchoose(
            data_sum,
            sample_size
          )))
        }
        res <- sapply(1:sample_size, sub)
        return(res)
      } else {
        return(0)
      }
    }
    hill1 <- function(data, sample_size) {
      data_sub <- data[data > 0]
      data_sum <- sum(data_sub)
      if (sample_size <= data_sum) {
        k <- 1:sample_size
        res <- exp(-sum(k / sample_size * log(k / sample_size) *
          fk_hat(data_sub, sample_size)))
        return(res)
      } else {
        return(0)
      }
    }
    hill2 <- function(data, sample_size) {
      data_sub <- data[data > 0]
      data_sum <- sum(data_sub)
      if (sample_size <= data_sum) {
        res <- 1 / (1 / sample_size + (1 - 1 / sample_size) * sum(data_sub *
          (data_sub - 1) / data_sum / (data_sum - 1)))
        return(res)
      } else {
        return(0)
      }
    }
    est_n_0 <-
      sapply(sample_size, function(n) {
        apply(data_matrix, 1, hill0, sample_size = n)
      })
    est_n_1 <-
      sapply(sample_size, function(n) {
        apply(data_matrix, 1, hill1, sample_size = n)
      })
    est_n_2 <-
      sapply(sample_size, function(n) {
        apply(data_matrix, 1, hill2, sample_size = n)
      })
    hill_diversity <-
      cbind(est_n_0, est_n_1, est_n_2, est_n_1 -
        est_n_2, est_n_2 / est_n_1, est_n_1 / est_n_0) %>%
      as.data.frame() %>%
      tibble::as_tibble() %>%
      dplyr::mutate(sample_id = row.names(data_matrix)) %>%
      tibble::column_to_rownames("sample_id") %>%
      purrr::set_names(
        nm = c(
          "n0",
          "n1", "n2", "n1_minus_n2", "n2_divided_by_n1", "n1_divided_by_n0"
        )
      )
    return(hill_diversity)
  }

  if (
    isTRUE(round)
  ) {
    data_matrix <-
      data_source %>%
      tibble::column_to_rownames("sample_id") %>%
      dplyr::mutate_all(., .f = floor) %>%
      as.matrix() %>%
      round()
  } else {
    data_matrix <-
      data_source %>%
      tibble::column_to_rownames("sample_id") %>%
      as.matrix()
  }

  sample_size <-
    apply(data_matrix, 1, sum) %>%
    floor() %>%
    min()

  div <-
    get_diversity_taxonomic(
      data_matrix = data_matrix,
      sample_size = sample_size
    )

  res <-
    div %>%
    tibble::rownames_to_column("sample_id") %>%
    dplyr::relocate(sample_id) %>%
    as.data.frame() %>%
    tibble::as_tibble()
  return(res)
}
data_diversity <-
  get_diversity(
    data_source = sel_counts_selected,
    sel_method = "taxonomic"
  )

head(data_diversity)
Table continues below
sample_id n0 n1 n2 n1_minus_n2 n2_divided_by_n1
510392 25.17 12.76 8.095 4.664 0.6344
510393 24.39 11.66 6.795 4.867 0.5826
510394 24.33 11.13 6.707 4.426 0.6024
510396 20.29 7.68 3.952 3.728 0.5146
510391 26.27 13.12 7.43 5.694 0.5661
510395 22.78 11.89 7.615 4.279 0.6402
n1_divided_by_n0
0.5068
0.4782
0.4576
0.3784
0.4995
0.522

Now we can fit a temporal trend as a GAM model.

data_to_fit <-
  dplyr::inner_join(
    data_diversity,
    sel_level_predicted,
    by = "sample_id"
  )

mod_n0 <-
  mgcv::gam(
    n0 ~ s(age, k = 25, bs = "tp"),
    data = data_to_fit,
    method = "REML",
    family = mgcv::tw(link = "log")
  )

summary(mod_n0)
#> 
#> Family: Tweedie(p=1.01) 
#> Link function: log 
#> 
#> Formula:
#> n0 ~ s(age, k = 25, bs = "tp")
#> 
#> Parametric coefficients:
#>             Estimate Std. Error t value Pr(>|t|)    
#> (Intercept)   2.9714     0.0221   134.5   <2e-16 ***
#> ---
#> Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#> 
#> Approximate significance of smooth terms:
#>          edf Ref.df     F p-value    
#> s(age) 4.675  5.839 14.26  <2e-16 ***
#> ---
#> Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#> 
#> R-sq.(adj) =  0.597   Deviance explained = 60.1%
#> -REML = 178.14  Scale est. = 0.58249   n = 64

# mgcv::gam.check(mod_n0, k.sample = 10e3, k.rep = 1e3)

Now we can visualise the results.

age_dummy <-
  tibble::tibble(
    age = seq(
      from = min(data_to_fit$age),
      to = max(data_to_fit$age),
      length.out = 100
    )
  )

data_predicted <-
  marginaleffects::predictions(
    model = mod_n0,
    newdata = age_dummy
  ) %>%
  tibble::as_tibble() %>%
  dplyr::rename(
    fit = estimate,
    lwr = conf.low,
    upr = conf.high,
    sd_error = std.error
  ) %>%
  dplyr::select(
    dplyr::all_of(
      c(
        "age",
        "fit",
        "sd_error",
        "lwr",
        "upr"
      )
    )
  )

data_predicted %>%
  ggplot2::ggplot(
    mapping = ggplot2::aes(
      x = age,
      y = fit
    )
  ) +
  ggplot2::geom_ribbon(
    mapping = ggplot2::aes(
      ymin = lwr,
      ymax = upr
    ),
    alpha = 0.2
  ) +
  ggplot2::geom_line() +
  geom_point(
    data = data_to_fit,
    mapping = ggplot2::aes(
      y = n0
    )
  ) +
  ggplot2::scale_x_continuous(
    trans = "reverse"
  ) +
  ggplot2::labs(
    y = "Hill's N0",
    x = "Age (cal yr BP)"
  )

LS0tCnRpdGxlOiBTdGVwLWJ5LXN0ZXAgZ3VpZGUKZm9ybWF0OiAKICBnZm06CiAgICBmaWctd2lkdGg6IDcKICAgIGZpZy1oZWlnaHQ6IDYKICAgIHdyYXA6IG5vbmUKLS0tCgpgYGB7ciBjaHVuay1zZXR1cCwgaW5jbHVkZT1GQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KAogIGNvbGxhcHNlID0gVFJVRSwKICBjb21tZW50ID0gIiM+IgopCmBgYAoKVGhpcyB3b3JrZmxvdyBzaG91bGQgc2VydmUgYXMgc3RlcC1ieS1zdGVwIGd1aWRhbmNlIHN0YXJ0aW5nIGZyb20gZG93bmxvYWRpbmcgYSBkYXRhc2V0IGZyb20gTmVvdG9tYSBhbmQgcHJvY2Vzc2luZyBpdCwgdG8gZXN0aW1hdGluZyBlY29zeXN0ZW0gcHJvcGVydHkgKGRpdmVyc2l0eSBpbiB0aGlzIGNhc2UpLgoKOndhcm5pbmc6ICoqVGhpcyB3b3JrZmxvdyBpcyBvbmx5IG1lYW50IGFzIGFuIGV4YW1wbGUqKjogVGhlcmUgYXJlIHNldmVyYWwgYWRkaXRpb25hbCBzdGVwcyBmb3IgZGF0YSBwcmVwYXJhdGlvbiB3aGljaCBzaG91bGQgYmUgZG9uZSBmb3IgYW55IGZvc3NpbCBwb2xsZW4gZGF0YXNldCBmcm9tIE5lb3RvbWEhCgpTZWUgWyoqRk9TU0lMUE9MKipdKGh0dHBzOi8vaG9wZS11aWItYmlvLmdpdGh1Yi5pby9GT1NTSUxQT0wtd2Vic2l0ZS8pLCBhbiBSLWJhc2VkIG1vZHVsYXIgd29ya2Zsb3cgdG8gcHJvY2VzcyBtdWx0aXBsZSBmb3NzaWwgcG9sbGVuIHJlY29yZHMgdG8gY3JlYXRlIGEgY29tcHJlaGVuc2l2ZSwgc3RhbmRhcmRpc2VkIGRhdGFzZXQgY29tcGlsYXRpb24sIHJlYWR5IGZvciBtdWx0aS1yZWNvcmQgYW5kIG11bHRpLXByb3h5IGFuYWx5c2VzIGF0IHZhcmlvdXMgc3BhdGlhbCBhbmQgdGVtcG9yYWwgc2NhbGVzLgoKIyMgSW5zdGFsbCBwYWNrYWdlcwoKUGxlYXNlIGZvbGxvdyB0aGUgW3ByZS13b3Jrc2hvcCBpbnN0cnVjdGlvbnNdKC9kb2NzL3ByZV93b3Jrc2hvcC5odG1sKSB0byBtYWtlIHN1cmUgYWxsIHBhY2thZ2VzIGFyZSBpbnN0YWxsZWQuCgojIyBBdHRhY2ggcGFja2FnZXMKCmBgYHtyIHBrZy1hdHRhY2gsIHJlc3VsdHM9J2hpZGUnLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQpsaWJyYXJ5KHRpZHl2ZXJzZSkgIyBnZW5lcmFsIGRhdGEgd3JhbmdsaW5nIGFuZCB2aXN1YWxpc2F0aW9uIOKcqApsaWJyYXJ5KHBhbmRlcikgIyBuaWNlIHRhYmxlcyDwn5iNCmxpYnJhcnkobmVvdG9tYTIpICMgYWNjZXNzIHRvIHRoZSBOZW90b21hIGRhdGFiYXNlIPCfjL8KbGlicmFyeShCY2hyb24pICMgYWdlLWRlcHRoIG1vZGVsbGluZyDwn5Ww77iPCmxpYnJhcnkobWdjdikgIyBHQU0gZml0dGluZyDwn5OICmxpYnJhcnkobWFyZ2luYWxlZmZlY3RzKSAjIHByZWRpY3RpbmcgdHJlbmRzIPCfk4gKbGlicmFyeShqYW5pdG9yKSAjIHN0cmluZyBjbGVhbmluZyDwn6e5CmxpYnJhcnkoaGVyZSkgIyBmb3Igd29ya2luZyBkaXJlY3Rvcnkg8J+Xuu+4jwpgYGAKCmBgYHtyIHRoZW1lLXNldHVwLCBpbmNsdWRlPVRSVUV9CmdncGxvdDI6OnRoZW1lX3NldCgKICBnZ3Bsb3QyOjp0aGVtZV9idygpICsKICAgIGdncGxvdDI6OnRoZW1lKAogICAgICBheGlzLnRpdGxlID0gZ2dwbG90Mjo6ZWxlbWVudF90ZXh0KHNpemUgPSAyNSksCiAgICAgIGF4aXMudGV4dCA9IGdncGxvdDI6OmVsZW1lbnRfdGV4dChzaXplID0gMTUpLAogICAgICBzdHJpcC50ZXh0ID0gZ2dwbG90Mjo6ZWxlbWVudF90ZXh0KHNpemUgPSAxNSksCiAgICAgIHBhbmVsLmdyaWQgPSBnZ3Bsb3QyOjplbGVtZW50X2JsYW5rKCkKICAgICkKKQpgYGAKCiMjIERvd25sb2FkIGEgZGF0YXNldCBmcm9tIE5lb3RvbWEKCkhlcmUgd2UgaGF2ZSBzZWxlY3RlZCB0aGUgKipMaW5ndWEgZCdPY2EqKiAoSUQgPSA1MjE2MikgcmVjb3JkIGJ5IEZlcmVkaWNvIERpIFJpdGEuCgpSZWZlcmVuY2UgcGFwZXI6IERpIFJpdGEsIEYuLCBBLiBDZWxhbnQsIGFuZCBELiBNYWdyaS4gMjAxMC4gSG9sb2NlbmUgZW52aXJvbm1lbnRhbCBpbnN0YWJpbGl0eSBpbiB0aGUgd2V0bGFuZCBub3J0aCBvZiB0aGUgVGliZXIgZGVsdGEgKFJvbWUsIEl0YWx5KTogc2VhLWxha2UtbWFuIGludGVyYWN0aW9ucy4gKkpvdXJuYWwgb2YgUGFsZW9saW1ub2xvZ3kqIDQ0OjUxLTY3LgoKYGBge3IgZG93bmxvYWRfb2ZfZGF0YSwgcmVzdWx0cz0naGlkZScsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9CnNlbF9kYXRhc2V0X2Rvd25sb2FkIDwtCiAgbmVvdG9tYTI6OmdldF9kb3dubG9hZHMoNTIxNjIpCmBgYAoKIyMgUHJlcGFyZSB0aGUgcG9sbGVuIGNvdW50cwoKYGBge3IgY291bnRfcHJlcGFyYXRpb24sIHJlc3VsdHM9J2hpZGUnLCB3YXJuaW5nPUZBTFNFfQojIGdldCBzYW1wbGVzCnNlbF9jb3VudHMgPC0KICBuZW90b21hMjo6c2FtcGxlcyhzZWxfZGF0YXNldF9kb3dubG9hZCkKCiMgc2VsZWN0IG9ubHkgInBvbGxlbiIgdGF4YQpzZWxfdGF4b25fbGlzdF9zZWxlY3RlZCA8LQogIG5lb3RvbWEyOjp0YXhhKHNlbF9kYXRhc2V0X2Rvd25sb2FkKSAlPiUKICBkcGx5cjo6ZmlsdGVyKGVsZW1lbnQgPT0gInBvbGxlbiIpICU+JQogIHB1cnJyOjpwbHVjaygidmFyaWFibGVuYW1lIikKCiMgcHJlcGFyZSB0YXhhIHRhYmxlCnNlbF9jb3VudHNfc2VsZWN0ZWQgPC0KICBzZWxfY291bnRzICU+JQogIGFzLmRhdGEuZnJhbWUoKSAlPiUKICBkcGx5cjo6bXV0YXRlKHNhbXBsZV9pZCA9IGFzLmNoYXJhY3RlcihzYW1wbGVpZCkpICU+JQogIHRpYmJsZTo6YXNfdGliYmxlKCkgJT4lCiAgZHBseXI6OnNlbGVjdCgic2FtcGxlX2lkIiwgInZhbHVlIiwgInZhcmlhYmxlbmFtZSIpICU+JQogICMgb25seSBpbmNsdWRlIHNlbGVjdGVkIHRheG9ucwogIGRwbHlyOjpmaWx0ZXIoCiAgICB2YXJpYWJsZW5hbWUgJWluJSBzZWxfdGF4b25fbGlzdF9zZWxlY3RlZAogICkgJT4lCiAgIyB0dXJuIGludG8gdGhlIHdpZGVyIGZvcm1hdAogIHRpZHlyOjpwaXZvdF93aWRlcigKICAgIG5hbWVzX2Zyb20gPSAidmFyaWFibGVuYW1lIiwKICAgIHZhbHVlc19mcm9tID0gInZhbHVlIiwKICAgIHZhbHVlc19maWxsID0gMAogICkgJT4lCiAgIyBjbGVhbiBuYW1lcwogIGphbml0b3I6OmNsZWFuX25hbWVzKCkKCmhlYWQoc2VsX2NvdW50c19zZWxlY3RlZClbLCAxOjVdCmBgYAoKYGBge3IgY291bnRfZGlwbGF5LCBlY2hvPUZBTFNFLCByZXN1bHRzPSdhc2lzJywgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0KcGFuZGVyOjpwYW5kb2MudGFibGUoaGVhZChzZWxfY291bnRzX3NlbGVjdGVkKVssIDE6NV0pCmBgYAoKSGVyZSwgd2Ugc3Ryb25nbHkgYWR2b2NhdGUgdGhhdCBhdHRlbnRpb24gc2hvdWxkIGJlIHBhaWQgdG8gdGhlIHNlbGVjdGlvbiBvZiB0aGUgZWNvbG9naWNhbCBncm91cHMsIHRoZSBzZWxlY3Rpb24gb2YgZGVwb3NpdGlvbmFsIGVudmlyb25tZW50cywgYXMgd2VsbCBhcyB0aGUgaGFybW9uaXNhdGlvbiBvZiB0aGUgcG9sbGVuIHRheGEuIEhvd2V2ZXIsIHRoYXQgaXMgbm90IHRoZSBzdWJqZWN0IG9mIHRoaXMgd29ya2Zsb3csIGJ1dCBhbnkgYW5hbHlzaXMgdG8gYmUgcHVibGlzaGVkIG5lZWRzIGNhcmVmdWwgcHJlcGFyYXRpb24gb2YgdGhlIGZvc3NpbCBwb2xsZW4gZGF0YXNldHMhCgpXZSBjYW4gbm93IHRyeSB0byB2aXN1YWxpc2UgdGhlIHRheGEgcGVyIHNhbXBsZV9pZAoKYGBge3IgY291bnRfdmlzfQpzZWxfY291bnRzX3NlbGVjdGVkICU+JQogIHRpYmJsZTo6cm93aWRfdG9fY29sdW1uKCJJRCIpICU+JQogIHRpZHlyOjpwaXZvdF9sb25nZXIoCiAgICBjb2xzID0gLWMoc2FtcGxlX2lkLCBJRCksCiAgICBuYW1lc190byA9ICJ0YXhhIiwKICAgIHZhbHVlc190byA9ICJuX2dyYWlucyIKICApICU+JQogIGdncGxvdDI6OmdncGxvdCgKICAgIG1hcHBpbmcgPSBnZ3Bsb3QyOjphZXMoCiAgICAgIHggPSBJRCwKICAgICAgeSA9IG5fZ3JhaW5zLAogICAgICBmaWxsID0gdGF4YQogICAgKSwKICApICsKICBnZ3Bsb3QyOjpnZW9tX2JhcigKICAgIHN0YXQgPSAiaWRlbnRpdHkiLAogICAgcG9zaXRpb24gPSAiZmlsbCIKICApICsKICBnZ3Bsb3QyOjpsYWJzKAogICAgeCA9ICJzYW1wbGVfaWQiLAogICAgeSA9ICJwcm9wb3J0aW9uIG9mIHBvbGxlbiBncmFpbnMiCiAgKSArCiAgZ2dwbG90Mjo6dGhlbWUoCiAgICBheGlzLnRleHQueCA9IGdncGxvdDI6OmVsZW1lbnRfYmxhbmsoKSwKICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIgogICkKYGBgCgojIyBQcmVwYXJhdGlvbiBvZiB0aGUgbGV2ZWxzCgojIyMgU2FtcGxlIGRlcHRoCgpFeHRyYWN0IGRlcHRoIGZvciBlYWNoIGxldmVsCgpgYGB7ciBsZXZlbF9wcmVwYXJpb24sIHJlc3VsdHM9J2hpZGUnLCB3YXJuaW5nPUZBTFNFfQpzZWxfbGV2ZWwgPC0KICBuZW90b21hMjo6c2FtcGxlcyhzZWxfZGF0YXNldF9kb3dubG9hZCkgJT4lCiAgdGliYmxlOjphc190aWJibGUoKSAlPiUKICBkcGx5cjo6bXV0YXRlKHNhbXBsZV9pZCA9IGFzLmNoYXJhY3RlcihzYW1wbGVpZCkpICU+JQogIGRwbHlyOjpkaXN0aW5jdChzYW1wbGVfaWQsIGRlcHRoKSAlPiUKICBkcGx5cjo6cmVsb2NhdGUoc2FtcGxlX2lkKQoKaGVhZChzZWxfbGV2ZWwpCmBgYAoKYGBge3IgbGV2ZWxfZGlwbGF5LCBlY2hvPUZBTFNFLCByZXN1bHRzPSdhc2lzJywgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0KcGFuZGVyOjpwYW5kb2MudGFibGUoaGVhZChzZWxfbGV2ZWwpKQpgYGAKCiMjIyBBZ2UtZGVwdGggbW9kZWxsaW5nCgpXZSBoaWdobHkgcmVjb21tZW5kIHJlY2FsY3VsYXRpbmcgdGhlIGFnZS1kZXB0aCBtb2RlbCAnZGUgTm92bycgYXMgZGlmZmVyZW50IG1ldGhvZG9sb2dpZXMgbWlnaHQgaGF2ZSBiZWVuIGFwcGxpZWQgdG8gZWFjaCByZWNvcmQuIEhvd2V2ZXIsIGFnZS1kZXB0aCBtb2RlbGxpbmcgaXMgYSB2ZXJ5IGNvbXBsaWNhdGVkIHRvcGljLCB3aGljaCB3ZSB3aWxsIG5vdCBkaWcgaW50byB0b2RheS4gSnVzdCBub3RlIHRoYXQgbWFueSBwYXJ0cyBvZiB0aGUgYWdlLWRlcHRoIG1vZGVsbGluZyBuZWVkIGF0dGVudGlvbiAoc2VsZWN0aW9uIG9mIGNocm9ub2xvZ3kgY29udHJvbCBwb2ludHMsIHVzaW5nIGNvcnJlY3QgY2FsaWJyYXRpb24gY3VydmVzLCBldGMuKS4KCmBgYHtyIHJ1bl9iY2hyb24sIHJlc3VsdHM9J2hpZGUnLCB3YXJuaW5nPUZBTFNFfQojIEhlcmUgd2Ugb25seSBwcmVzZW50IGEgZmV3IG9mIHRoZSBpbXBvcnRhbnQgc3RlcHMgb2YgcHJlcGFyYXRpb24gb2YgdGhlCiMgICBjaHJvbm9sb2d5IGNvbnRyb2wgdGFibGUuIFRoZXJlIGFyZSBtYW55IG1vcmUgcG90ZW50aWFsIGlzc3VlcywgYnV0CiMgICBzb2x2aW5nIHRob3NlIGlzIG5vdCB0aGUgZm9jdXMgb2YgdGhpcyB3b3JrZmxvdy4KCiMgRmlyc3QsIGdldCB0aGUgY2hyb25vbG9naWVzIGFuZCBjaGVjayB3aGljaCB3ZSB3YW50IHRvIHVzZSB1c2VkCnNlbF9jaHJvbl9jb250cm9sX3RhYmxlX2Rvd25sb2FkIDwtCiAgbmVvdG9tYTI6OmNocm9uY29udHJvbHMoc2VsX2RhdGFzZXRfZG93bmxvYWQpCgojIHByZXBhcmUgdGhlIHRhYmxlCnNlbF9jaHJvbl9jb250cm9sX3RhYmxlIDwtCiAgc2VsX2Nocm9uX2NvbnRyb2xfdGFibGVfZG93bmxvYWQgJT4lCiAgIyBIZXJlIHNlbGVjdCB0aGUgSUQgb2Ygb25lIG9mIHRoZSBjaHJvbm9sb2d5CiAgZHBseXI6OmZpbHRlcihjaHJvbm9sb2d5aWQgPT0gMzcyMjgpICU+JQogIHRpYmJsZTo6YXNfdGliYmxlKCkgJT4lCiAgIyBIZXJlIHdlIGNhbGN1bGF0ZSB0aGUgZXJyb3IgYXMgdGhlIGF2ZXJhZ2Ugb2YgdGhlIGFnZSBgbGltaXRvbGRlcmAgYW5kCiAgIyAgIGBhZ2VsaW1pdHlvdW5nZXJgCiAgZHBseXI6Om11dGF0ZSgKICAgIGVycm9yID0gcm91bmQoKGFnZWxpbWl0b2xkZXIgLSBhZ2VsaW1pdHlvdW5nZXIpIC8gMikKICApICU+JQogICMgQXMgQmNocm9uIGNhbm5vdCBhY2NlcHQgYW4gZXJyb3Igb2YgMCwgd2UgbmVlZCB0byByZXBsYWNlIHRoZSB2YWx1ZSB3aXRoIDEKICBkcGx5cjo6bXV0YXRlKAogICAgZXJyb3IgPSByZXBsYWNlKGVycm9yLCBlcnJvciA9PSAwLCAxKSwKICAgIGVycm9yID0gaWZlbHNlKGlzLm5hKGVycm9yKSwgMSwgZXJyb3IpCiAgKSAlPiUKICAjIEFzIEJjaHJvbiBjYW5ub3QgYWNjZXB0IGFuIHRoaWNrbmVzcyBvZiAwLCB3ZSBuZWVkIHRvIHJlcGxhY2UgdGhlIHZhbHVlIHdpdGggMQogIGRwbHlyOjptdXRhdGUoCiAgICB0aGlja25lc3MgPSBpZmVsc2UoaXMubmEodGhpY2tuZXNzKSwgMSwgdGhpY2tuZXNzKQogICkgJT4lCiAgIyBXZSBuZWVkIHRvIHNwZWNpZnkgd2hpY2ggY2FsaWJyYXRpb24gY3VydmUgc2hvdWxkIGJlIHVzZWQgZm9yIHdoYXQgcG9pbnQKICBkcGx5cjo6bXV0YXRlKAogICAgY3VydmUgPSBpZmVsc2UoYXMuZGF0YS5mcmFtZShzZWxfZGF0YXNldF9kb3dubG9hZClbImxhdCJdID4gMCwgImludGNhbDIwIiwgInNoY2FsMjAiKSwKICAgIGN1cnZlID0gaWZlbHNlKGNocm9uY29udHJvbHR5cGUgIT0gIlJhZGlvY2FyYm9uIiwgIm5vcm1hbCIsIGN1cnZlKQogICkgJT4lCiAgdGliYmxlOjpjb2x1bW5fdG9fcm93bmFtZXMoImNocm9uY29udHJvbGlkIikgJT4lCiAgZHBseXI6OmFycmFuZ2UoZGVwdGgpICU+JQogIGRwbHlyOjpzZWxlY3QoCiAgICBjaHJvbmNvbnRyb2xhZ2UsIGVycm9yLCBkZXB0aCwgdGhpY2tuZXNzLCBjaHJvbmNvbnRyb2x0eXBlLCBjdXJ2ZQogICkKCmlfbXVsdGlwbGllciA8LSAwLjEgIyBpbmNyZWFzZSB0byA1CgojIFRob3NlIGFyZSBkZWZhdWx0IHZhbHVlcyBzdWdnZXN0ZWQgYnkgdGhlIEJjaHJvbiBwYWNrYWdlCm5faXRlcmF0aW9uX2RlZmF1bHQgPC0gMTBlMwpuX2J1cm5fZGVmYXVsdCA8LSAyZTMKbl90aGluX2RlZmF1bHQgPC0gOAoKIyBMZXQncyBtdWx0aXBseSB0aGVtIGJ5IG91ciBpX211bHRpcGxpZXIKbl9pdGVyYXRpb24gPC0gbl9pdGVyYXRpb25fZGVmYXVsdCAqIGlfbXVsdGlwbGllcgpuX2J1cm4gPC0gbl9idXJuX2RlZmF1bHQgKiBpX211bHRpcGxpZXIKbl90aGluIDwtIG1heChjKDEsIG5fdGhpbl9kZWZhdWx0ICogaV9tdWx0aXBsaWVyKSkKCiMgcnVuIEJjaHJvbgphZF9tb2RlbCA8LQogIEJjaHJvbjo6QmNocm9ub2xvZ3koCiAgICBhZ2VzID0gc2VsX2Nocm9uX2NvbnRyb2xfdGFibGUkY2hyb25jb250cm9sYWdlLAogICAgYWdlU2RzID0gc2VsX2Nocm9uX2NvbnRyb2xfdGFibGUkZXJyb3IsCiAgICBwb3NpdGlvbnMgPSBzZWxfY2hyb25fY29udHJvbF90YWJsZSRkZXB0aCwKICAgIGNhbEN1cnZlcyA9IHNlbF9jaHJvbl9jb250cm9sX3RhYmxlJGN1cnZlLAogICAgcG9zaXRpb25UaGlja25lc3NlcyA9IHNlbF9jaHJvbl9jb250cm9sX3RhYmxlJHRoaWNrbmVzcywKICAgIGl0ZXJhdGlvbnMgPSBuX2l0ZXJhdGlvbiwKICAgIGJ1cm4gPSBuX2J1cm4sCiAgICB0aGluID0gbl90aGluCiAgKQpgYGAKClZpc3VhbGx5IGNoZWNrIHRoZSBhZ2UtZGVwdGggbW9kZWxzCgpgYGB7ciBiY2hyb25fZmlndXJlLCByZXN1bHRzPSdtYXJrdXAnLCB3YXJuaW5nPUZBTFNFfQpwbG90KGFkX21vZGVsKQpgYGAKCiMjIyMgUHJlZGljdCBhZ2VzCgpgYGB7ciBhZ2VfdW5jZXJ0YWludGllcywgcmVzdWx0cz0naGlkZScsIHdhcm5pbmc9RkFMU0V9CmFnZV9wb3NpdGlvbiA8LQogIEJjaHJvbjo6OnByZWRpY3QuQmNocm9ub2xvZ3lSdW4ob2JqZWN0ID0gYWRfbW9kZWwsIG5ld1Bvc2l0aW9ucyA9IHNlbF9sZXZlbCRkZXB0aCkKCmFnZV91bmNlcnRhaW50aWVzIDwtCiAgYWdlX3Bvc2l0aW9uICU+JQogIGFzLmRhdGEuZnJhbWUoKSAlPiUKICBkcGx5cjo6bXV0YXRlX2FsbCguLCBhcy5pbnRlZ2VyKSAlPiUKICBhcy5tYXRyaXgoKQoKY29sbmFtZXMoYWdlX3VuY2VydGFpbnRpZXMpIDwtIHNlbF9sZXZlbCRzYW1wbGVfaWQKCiMgTGV0J3MgdGFrZSB0aGUgbWVkaWFuIGFnZSBvZiBhbGwgcG9zc2libGUgYWdlcyAoaS5lLiB0aGUgZXN0aW1hdGVkIGFnZQojICAgZnJvbSBlYWNoIGFnZS1kZXB0aCBtb2RlbCBydW4pIGFzIG91ciBkZWZhdWx0LgpzZWxfbGV2ZWxfcHJlZGljdGVkIDwtCiAgc2VsX2xldmVsICU+JQogIGRwbHlyOjptdXRhdGUoCiAgICBhZ2UgPSBhcHBseSgKICAgICAgYWdlX3VuY2VydGFpbnRpZXMsIDIsCiAgICAgIHN0YXRzOjpxdWFudGlsZSwKICAgICAgcHJvYnMgPSAwLjUKICAgICkKICApCmBgYAoKIyMjIFZpc3VhbGlzYXRpb24gb2Ygb3VyIGRhdGEKCkxldCdzIG5vdyBtYWtlIGEgc2ltcGxlIHBvbGxlbiBkaWFncmFtIHdpdGggcHJvcG9ydGlvbnMgb2YgdGhlIG1haW4gcG9sbGVuIHRheGEgKHgtYXhpcykgYWdhaW5zdCBvdXIgZXN0aW1hdGVkIGFnZXMgYWxvbmcgZGVwdGggKHktYXhpcykuCgpgYGB7ciB2aXNfZGF0YV93aXRoX2FnZXN9CmRhdGFfcm93bmFtZXMgPC0KICBzZWxfY291bnRzX3NlbGVjdGVkICU+JQogIHRpYmJsZTo6Y29sdW1uX3RvX3Jvd25hbWVzKCJzYW1wbGVfaWQiKQoKZGF0YV9wZXJjZW50YWdlcyA8LQogIChkYXRhX3Jvd25hbWVzIC8gcm93U3VtcyhkYXRhX3Jvd25hbWVzKSkgKiAxMDAKCmNvbF9zdW1fbm9uX3plcm8gPC0KICBjb2xTdW1zKGRhdGFfcGVyY2VudGFnZXMpID4gMAoKZGF0YV9maWx0ZXJlZCA8LQogIGRhdGFfcGVyY2VudGFnZXMgJT4lCiAgZHBseXI6OnNlbGVjdCgKICAgIGRwbHlyOjphbnlfb2YoCiAgICAgIG5hbWVzKGNvbF9zdW1fbm9uX3plcm8pW2NvbF9zdW1fbm9uX3plcm9dCiAgICApCiAgKSAlPiUKICB0aWJibGU6OnJvd25hbWVzX3RvX2NvbHVtbigic2FtcGxlX2lkIikgJT4lCiAgZHBseXI6OnJlbG9jYXRlKHNhbXBsZV9pZCkgJT4lCiAgdGliYmxlOjp0aWJibGUoKSAKCmRhdGFfZmlsdGVyZWQgJT4lCiAgZHBseXI6OmlubmVyX2pvaW4oCiAgICBzZWxfbGV2ZWxfcHJlZGljdGVkLAogICAgYnkgPSBkcGx5cjo6am9pbl9ieShzYW1wbGVfaWQpCiAgKSAlPiUKICB0aWR5cjo6cGl2b3RfbG9uZ2VyKAogICAgY29scyA9IC1jKHNhbXBsZV9pZCwgZGVwdGgsIGFnZSksCiAgICBuYW1lc190byA9ICJ0YXhhIiwKICAgIHZhbHVlc190byA9ICJwcm9wb3J0aW9uX29mX2dyYWlucyIKICApICU+JQogIGRwbHlyOjpncm91cF9ieSh0YXhhKSAlPiUKICAjIENhbGN1bGF0ZSB0aGUgYXZlcmFnZSBwcm9wb3J0aW9uIG9mIGdyYWlucwogIGRwbHlyOjptdXRhdGUoCiAgICBhdmdfcHJvcCA9IG1lYW4ocHJvcG9ydGlvbl9vZl9ncmFpbnMpCiAgKSAlPiUKICAjIE9ubHkga2VlcCB0aGUgbWFpbiB0YXhhIChvbiBhdmVyYWdlID49IDElIHBvbGxlbiBncmFpbnMpCiAgZHBseXI6OmZpbHRlcihhdmdfcHJvcCA+PSAxKSAlPiUKICBkcGx5cjo6dW5ncm91cCgpICU+JQogIGdncGxvdDI6OmdncGxvdCgKICAgIG1hcHBpbmcgPSBnZ3Bsb3QyOjphZXMoCiAgICAgIHkgPSBhZ2UsCiAgICAgIHggPSBwcm9wb3J0aW9uX29mX2dyYWlucywKICAgICAgeG1heCA9IHByb3BvcnRpb25fb2ZfZ3JhaW5zLAogICAgICB4bWluID0gMCwKICAgICAgZmlsbCA9IHRheGEsCiAgICAgIGNvbCA9IHRheGEKICAgICksCiAgKSArCiAgZ2dwbG90Mjo6Z2VvbV9yaWJib24oKSArCiAgZ2dwbG90Mjo6c2NhbGVfeV9jb250aW51b3VzKHRyYW5zID0gInJldmVyc2UiKSArCiAgZ2dwbG90Mjo6c2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IGMoMCwgMSkpICsKICBnZ3Bsb3QyOjpmYWNldF93cmFwKH50YXhhLCBucm93ID0gMSkgKwogIGdncGxvdDI6OnRoZW1lKAogICAgbGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiLAogICAgcGFuZWwuYm9yZGVyID0gZ2dwbG90Mjo6ZWxlbWVudF9ibGFuaygpLAogICAgc3RyaXAuYmFja2dyb3VuZCA9IGdncGxvdDI6OmVsZW1lbnRfYmxhbmsoKSwKICAgIHN0cmlwLnRleHQgPSBnZ3Bsb3QyOjplbGVtZW50X2JsYW5rKCksCiAgICBheGlzLnRpY2tzLnggPSBnZ3Bsb3QyOjplbGVtZW50X2JsYW5rKCksCiAgICBheGlzLnRleHQueCA9IGdncGxvdDI6OmVsZW1lbnRfYmxhbmsoKQogICkgKwogIGdncGxvdDI6OmxhYnMoCiAgICB4ID0gIlByb3BvcnRpb24gb2YgZ3JhaW5zIiwKICAgIHkgPSAiQWdlIChjYWwgeXIgQlApIgogICkKYGBgCgojIyBFc3RpbWF0aW9uIG9mIGVjb3N5c3RlbSBwcm9wZXJ0eQoKTm93IHdlIHdpbGwgdXNlIG91ciBwcmVwYXJlZCBmb3NzaWwgcG9sbGVuIGRhdGEgdG8gZXN0aW1hdGUgdGhlIGRpdmVyc2l0eS4gV2Ugd2lsbCB1c2Uge1JFY29wb2x9IHBhY2thZ2UsIHdoaWNoIGhhcyBlYXN5LXRvLXVzZSBmdW5jdGlvbnMgdG8gYW5hbHlzZSBmb3NzaWwgcG9sbGVuIGRhdGEuIFNlZSBwYWNrYWdlIFt3ZWJzaXRlXShodHRwczovL2hvcGUtdWliLWJpby5naXRodWIuaW8vUi1FY29wb2wtcGFja2FnZS8pIGZvciBtb3JlIGluZm9ybWF0aW9uLiBTcGVjaWZpY2FsbHksIHdlIHdpbGwgZXN0aW1hdGUgcmFyZWZpZWQgdmFsdWVzIG9mIFtIaWxsIG51bWJlcnNdKGh0dHBzOi8vZXNham91cm5hbHMub25saW5lbGlicmFyeS53aWxleS5jb20vZG9pL2Ficy8xMC4yMzA3LzE5MzQzNTIpLgoKYGBge3Igd29ya2Fyb3VuZCwgcmVzdWx0cz0naGlkZScsIHdhcm5pbmc9RkFMU0V9CiMgZnVuY3Rpb24gdG8gZXN0aW1hdGUgZGl2ZXJzaXR5CmdldF9kaXZlcnNpdHkgPC0gZnVuY3Rpb24oZGF0YV9zb3VyY2UsIHJvdW5kID0gVFJVRSwgc2VsX21ldGhvZCA9ICIiKSB7CiAgIyBoZWxwZXIgZnVuY3Rpb24KICBnZXRfZGl2ZXJzaXR5X3RheG9ub21pYyA8LSBmdW5jdGlvbihkYXRhX21hdHJpeCwgc2FtcGxlX3NpemUpIHsKICAgIGhpbGwwIDwtIGZ1bmN0aW9uKGRhdGEsIHNhbXBsZV9zaXplKSB7CiAgICAgIGRhdGFfc3ViIDwtIGRhdGFbZGF0YSA+IDBdCiAgICAgIGRhdGFfc3VtIDwtIHN1bShkYXRhX3N1YikKICAgICAgaWYgKHNhbXBsZV9zaXplIDw9IGRhdGFfc3VtKSB7CiAgICAgICAgcmVzIDwtIHN1bSgxIC0gZXhwKGxjaG9vc2UoZGF0YV9zdW0gLSBkYXRhX3N1Yiwgc2FtcGxlX3NpemUpIC0KICAgICAgICAgIGxjaG9vc2UoZGF0YV9zdW0sIHNhbXBsZV9zaXplKSkpCiAgICAgICAgcmV0dXJuKHJlcykKICAgICAgfSBlbHNlIHsKICAgICAgICByZXR1cm4oMCkKICAgICAgfQogICAgfQogICAgZmtfaGF0IDwtIGZ1bmN0aW9uKGRhdGEsIHNhbXBsZV9zaXplKSB7CiAgICAgIGRhdGFfc3ViIDwtIGRhdGFbZGF0YSA+IDBdCiAgICAgIGRhdGFfc3VtIDwtIHN1bShkYXRhX3N1YikKICAgICAgaWYgKHNhbXBsZV9zaXplIDw9IGRhdGFfc3VtKSB7CiAgICAgICAgc3ViIDwtIGZ1bmN0aW9uKGspIHsKICAgICAgICAgIHN1bShleHAobGNob29zZShkYXRhX3N1YiwgaykgKyBsY2hvb3NlKGRhdGFfc3VtIC0KICAgICAgICAgICAgZGF0YV9zdWIsIHNhbXBsZV9zaXplIC0gaykgLSBsY2hvb3NlKAogICAgICAgICAgICBkYXRhX3N1bSwKICAgICAgICAgICAgc2FtcGxlX3NpemUKICAgICAgICAgICkpKQogICAgICAgIH0KICAgICAgICByZXMgPC0gc2FwcGx5KDE6c2FtcGxlX3NpemUsIHN1YikKICAgICAgICByZXR1cm4ocmVzKQogICAgICB9IGVsc2UgewogICAgICAgIHJldHVybigwKQogICAgICB9CiAgICB9CiAgICBoaWxsMSA8LSBmdW5jdGlvbihkYXRhLCBzYW1wbGVfc2l6ZSkgewogICAgICBkYXRhX3N1YiA8LSBkYXRhW2RhdGEgPiAwXQogICAgICBkYXRhX3N1bSA8LSBzdW0oZGF0YV9zdWIpCiAgICAgIGlmIChzYW1wbGVfc2l6ZSA8PSBkYXRhX3N1bSkgewogICAgICAgIGsgPC0gMTpzYW1wbGVfc2l6ZQogICAgICAgIHJlcyA8LSBleHAoLXN1bShrIC8gc2FtcGxlX3NpemUgKiBsb2coayAvIHNhbXBsZV9zaXplKSAqCiAgICAgICAgICBma19oYXQoZGF0YV9zdWIsIHNhbXBsZV9zaXplKSkpCiAgICAgICAgcmV0dXJuKHJlcykKICAgICAgfSBlbHNlIHsKICAgICAgICByZXR1cm4oMCkKICAgICAgfQogICAgfQogICAgaGlsbDIgPC0gZnVuY3Rpb24oZGF0YSwgc2FtcGxlX3NpemUpIHsKICAgICAgZGF0YV9zdWIgPC0gZGF0YVtkYXRhID4gMF0KICAgICAgZGF0YV9zdW0gPC0gc3VtKGRhdGFfc3ViKQogICAgICBpZiAoc2FtcGxlX3NpemUgPD0gZGF0YV9zdW0pIHsKICAgICAgICByZXMgPC0gMSAvICgxIC8gc2FtcGxlX3NpemUgKyAoMSAtIDEgLyBzYW1wbGVfc2l6ZSkgKiBzdW0oZGF0YV9zdWIgKgogICAgICAgICAgKGRhdGFfc3ViIC0gMSkgLyBkYXRhX3N1bSAvIChkYXRhX3N1bSAtIDEpKSkKICAgICAgICByZXR1cm4ocmVzKQogICAgICB9IGVsc2UgewogICAgICAgIHJldHVybigwKQogICAgICB9CiAgICB9CiAgICBlc3Rfbl8wIDwtCiAgICAgIHNhcHBseShzYW1wbGVfc2l6ZSwgZnVuY3Rpb24obikgewogICAgICAgIGFwcGx5KGRhdGFfbWF0cml4LCAxLCBoaWxsMCwgc2FtcGxlX3NpemUgPSBuKQogICAgICB9KQogICAgZXN0X25fMSA8LQogICAgICBzYXBwbHkoc2FtcGxlX3NpemUsIGZ1bmN0aW9uKG4pIHsKICAgICAgICBhcHBseShkYXRhX21hdHJpeCwgMSwgaGlsbDEsIHNhbXBsZV9zaXplID0gbikKICAgICAgfSkKICAgIGVzdF9uXzIgPC0KICAgICAgc2FwcGx5KHNhbXBsZV9zaXplLCBmdW5jdGlvbihuKSB7CiAgICAgICAgYXBwbHkoZGF0YV9tYXRyaXgsIDEsIGhpbGwyLCBzYW1wbGVfc2l6ZSA9IG4pCiAgICAgIH0pCiAgICBoaWxsX2RpdmVyc2l0eSA8LQogICAgICBjYmluZChlc3Rfbl8wLCBlc3Rfbl8xLCBlc3Rfbl8yLCBlc3Rfbl8xIC0KICAgICAgICBlc3Rfbl8yLCBlc3Rfbl8yIC8gZXN0X25fMSwgZXN0X25fMSAvIGVzdF9uXzApICU+JQogICAgICBhcy5kYXRhLmZyYW1lKCkgJT4lCiAgICAgIHRpYmJsZTo6YXNfdGliYmxlKCkgJT4lCiAgICAgIGRwbHlyOjptdXRhdGUoc2FtcGxlX2lkID0gcm93Lm5hbWVzKGRhdGFfbWF0cml4KSkgJT4lCiAgICAgIHRpYmJsZTo6Y29sdW1uX3RvX3Jvd25hbWVzKCJzYW1wbGVfaWQiKSAlPiUKICAgICAgcHVycnI6OnNldF9uYW1lcygKICAgICAgICBubSA9IGMoCiAgICAgICAgICAibjAiLAogICAgICAgICAgIm4xIiwgIm4yIiwgIm4xX21pbnVzX24yIiwgIm4yX2RpdmlkZWRfYnlfbjEiLCAibjFfZGl2aWRlZF9ieV9uMCIKICAgICAgICApCiAgICAgICkKICAgIHJldHVybihoaWxsX2RpdmVyc2l0eSkKICB9CgogIGlmICgKICAgIGlzVFJVRShyb3VuZCkKICApIHsKICAgIGRhdGFfbWF0cml4IDwtCiAgICAgIGRhdGFfc291cmNlICU+JQogICAgICB0aWJibGU6OmNvbHVtbl90b19yb3duYW1lcygic2FtcGxlX2lkIikgJT4lCiAgICAgIGRwbHlyOjptdXRhdGVfYWxsKC4sIC5mID0gZmxvb3IpICU+JQogICAgICBhcy5tYXRyaXgoKSAlPiUKICAgICAgcm91bmQoKQogIH0gZWxzZSB7CiAgICBkYXRhX21hdHJpeCA8LQogICAgICBkYXRhX3NvdXJjZSAlPiUKICAgICAgdGliYmxlOjpjb2x1bW5fdG9fcm93bmFtZXMoInNhbXBsZV9pZCIpICU+JQogICAgICBhcy5tYXRyaXgoKQogIH0KCiAgc2FtcGxlX3NpemUgPC0KICAgIGFwcGx5KGRhdGFfbWF0cml4LCAxLCBzdW0pICU+JQogICAgZmxvb3IoKSAlPiUKICAgIG1pbigpCgogIGRpdiA8LQogICAgZ2V0X2RpdmVyc2l0eV90YXhvbm9taWMoCiAgICAgIGRhdGFfbWF0cml4ID0gZGF0YV9tYXRyaXgsCiAgICAgIHNhbXBsZV9zaXplID0gc2FtcGxlX3NpemUKICAgICkKCiAgcmVzIDwtCiAgICBkaXYgJT4lCiAgICB0aWJibGU6OnJvd25hbWVzX3RvX2NvbHVtbigic2FtcGxlX2lkIikgJT4lCiAgICBkcGx5cjo6cmVsb2NhdGUoc2FtcGxlX2lkKSAlPiUKICAgIGFzLmRhdGEuZnJhbWUoKSAlPiUKICAgIHRpYmJsZTo6YXNfdGliYmxlKCkKICByZXR1cm4ocmVzKQp9CgpgYGAKCmBgYHtyIGRpdmVyc2l0eSwgcmVzdWx0cz0naGlkZScsIHdhcm5pbmc9RkFMU0V9CmRhdGFfZGl2ZXJzaXR5IDwtCiAgZ2V0X2RpdmVyc2l0eSgKICAgIGRhdGFfc291cmNlID0gc2VsX2NvdW50c19zZWxlY3RlZCwKICAgIHNlbF9tZXRob2QgPSAidGF4b25vbWljIgogICkKCmhlYWQoZGF0YV9kaXZlcnNpdHkpCmBgYAoKYGBge3IgZGl2ZXJzaXR5X2RpcGxheSwgZWNobz1GQUxTRSwgcmVzdWx0cz0nYXNpcycsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9CnBhbmRlcjo6cGFuZG9jLnRhYmxlKGhlYWQoZGF0YV9kaXZlcnNpdHkpKQpgYGAKCk5vdyB3ZSBjYW4gZml0IGEgdGVtcG9yYWwgdHJlbmQgYXMgYSBHQU0gbW9kZWwuCgpgYGB7ciB0ZW1wb3JhbF90cmVuZH0KZGF0YV90b19maXQgPC0KICBkcGx5cjo6aW5uZXJfam9pbigKICAgIGRhdGFfZGl2ZXJzaXR5LAogICAgc2VsX2xldmVsX3ByZWRpY3RlZCwKICAgIGJ5ID0gInNhbXBsZV9pZCIKICApCgptb2RfbjAgPC0KICBtZ2N2OjpnYW0oCiAgICBuMCB+IHMoYWdlLCBrID0gMjUsIGJzID0gInRwIiksCiAgICBkYXRhID0gZGF0YV90b19maXQsCiAgICBtZXRob2QgPSAiUkVNTCIsCiAgICBmYW1pbHkgPSBtZ2N2Ojp0dyhsaW5rID0gImxvZyIpCiAgKQoKc3VtbWFyeShtb2RfbjApCgojIG1nY3Y6OmdhbS5jaGVjayhtb2RfbjAsIGsuc2FtcGxlID0gMTBlMywgay5yZXAgPSAxZTMpCmBgYAoKTm93IHdlIGNhbiB2aXN1YWxpc2UgdGhlIHJlc3VsdHMuCgpgYGB7ciBwbG90X2dhbX0KYWdlX2R1bW15IDwtCiAgdGliYmxlOjp0aWJibGUoCiAgICBhZ2UgPSBzZXEoCiAgICAgIGZyb20gPSBtaW4oZGF0YV90b19maXQkYWdlKSwKICAgICAgdG8gPSBtYXgoZGF0YV90b19maXQkYWdlKSwKICAgICAgbGVuZ3RoLm91dCA9IDEwMAogICAgKQogICkKCmRhdGFfcHJlZGljdGVkIDwtCiAgbWFyZ2luYWxlZmZlY3RzOjpwcmVkaWN0aW9ucygKICAgIG1vZGVsID0gbW9kX24wLAogICAgbmV3ZGF0YSA9IGFnZV9kdW1teQogICkgJT4lCiAgdGliYmxlOjphc190aWJibGUoKSAlPiUKICBkcGx5cjo6cmVuYW1lKAogICAgZml0ID0gZXN0aW1hdGUsCiAgICBsd3IgPSBjb25mLmxvdywKICAgIHVwciA9IGNvbmYuaGlnaCwKICAgIHNkX2Vycm9yID0gc3RkLmVycm9yCiAgKSAlPiUKICBkcGx5cjo6c2VsZWN0KAogICAgZHBseXI6OmFsbF9vZigKICAgICAgYygKICAgICAgICAiYWdlIiwKICAgICAgICAiZml0IiwKICAgICAgICAic2RfZXJyb3IiLAogICAgICAgICJsd3IiLAogICAgICAgICJ1cHIiCiAgICAgICkKICAgICkKICApCgpkYXRhX3ByZWRpY3RlZCAlPiUKICBnZ3Bsb3QyOjpnZ3Bsb3QoCiAgICBtYXBwaW5nID0gZ2dwbG90Mjo6YWVzKAogICAgICB4ID0gYWdlLAogICAgICB5ID0gZml0CiAgICApCiAgKSArCiAgZ2dwbG90Mjo6Z2VvbV9yaWJib24oCiAgICBtYXBwaW5nID0gZ2dwbG90Mjo6YWVzKAogICAgICB5bWluID0gbHdyLAogICAgICB5bWF4ID0gdXByCiAgICApLAogICAgYWxwaGEgPSAwLjIKICApICsKICBnZ3Bsb3QyOjpnZW9tX2xpbmUoKSArCiAgZ2VvbV9wb2ludCgKICAgIGRhdGEgPSBkYXRhX3RvX2ZpdCwKICAgIG1hcHBpbmcgPSBnZ3Bsb3QyOjphZXMoCiAgICAgIHkgPSBuMAogICAgKQogICkgKwogIGdncGxvdDI6OnNjYWxlX3hfY29udGludW91cygKICAgIHRyYW5zID0gInJldmVyc2UiCiAgKSArCiAgZ2dwbG90Mjo6bGFicygKICAgIHkgPSAiSGlsbCdzIE4wIiwKICAgIHggPSAiQWdlIChjYWwgeXIgQlApIgogICkKYGBgCg==