Introduction

Clustering algorithms have several parameters which can be varied, leading to different clustering results. A key question when clustering, therefore, is how to identify a set of parameters that lead to robust and reliable clusters that can be used in downstream analysis.

This notebook provides examples of how to use the rOpenScPCA package to:

  • Calculate several versions of clustering results across several different parameterizations
  • Calculate QC metrics on across clustering results

Please refer to the 01_perform-evaluate-clustering.Rmd notebook for a tutorial on using rOpenScPCA functions to:

  • Calculate clusters from a single parameterization
  • Calculate QC metrics on a single set of clusters, as well as explanations of the metrics themselves

This notebook will use the sample SCPCS000001 from project SCPCP000001, which is assumed present in the OpenScPCA-analysis/data/current/SCPCP000001 directory, for all examples. Please see this documentation for more information about obtaining ScPCA data.

Setup

Packages

library(rOpenScPCA)

suppressPackageStartupMessages({
  library(SingleCellExperiment)
  library(ggplot2)
  library(patchwork)
})
Warning: package 'S4Vectors' was built under R version 4.4.1
Warning: package 'IRanges' was built under R version 4.4.1
# Set ggplot theme for plots
theme_set(theme_bw())

Paths

# The base path for the OpenScPCA repository
repository_base <- rprojroot::find_root(rprojroot::is_git_root)

# The current data directory, found within the repository base directory
data_dir <- file.path(repository_base, "data", "current")

# The path to this module
module_base <- file.path(repository_base, "analyses", "hello-clusters")
# Path to processed SCE file for sample SCPCS000001
input_sce_file <- file.path(data_dir, "SCPCP000001", "SCPCS000001", "SCPCL000001_processed.rds")

Set the random seed

Because clustering involves random sampling, it is important to set the random seed at the top of your analysis script or notebook to ensure reproducibility.

set.seed(2024)

Read in and prepare data

To begin, we’ll read in the SingleCellExperiment (SCE) object.

# Read the SCE file
sce <- readRDS(input_sce_file)

For the initial cluster calculations and evaluations, we will use the PCA matrix extracted from the SCE object. As shown in 01_perform-evaluate-clustering.Rmd, it is also possible to use an SCE object or a Seurat object directly.

# Extract the PCA matrix from an SCE object
pca_matrix <- reducedDim(sce, "PCA")

Varying a single clustering parameter

This section will show how to perform clustering across a set of parameters (aka, “sweep” a set of parameters) with rOpenScPCA::sweep_clusters().

This function takes a PCA matrix with row names representing unique cell ids (e.g., barcodes) as its primary argument, with additional arguments for cluster parameters. This function wraps the rOpenScPCA::calculate_clusters() function but allows you to provide a vector of parameter values to perform clustering across, as listed below. Clusters will be calculated for all combinations of parameters values (where applicable); default values that the function will use for any unspecified parameter values are shown in parentheses

  • algorithm: Which clustering algorithm to use (Louvain)
  • weighting: Which weighting scheme to use (Jaccard)
  • nn: The number of nearest neighbors (10)
  • resolution: The resolution parameter (1; used only with Louvain and Leiden clustering)
  • objective_function: The objective function to optimize clusters (CPM; used only with Leiden clustering)

rOpenScPCA::sweep_clusters() does allow you to specify values for any other parameters.

This function will return a list of data frames of clustering results. Each data frame will have the following columns:

  • cell_id: Unique cell identifiers, obtained from the PCA matrix’s row names
  • cluster: A factor column with the cluster identities
  • There will be one column for each clustering parameter used

To demonstrate this function, we’ll calculate clusters using the Louvain algorithm while varying the nn parameter:

# Define nn parameter values of interest
nn_values <- seq(10, 30, 10)

# Calculate clusters varying nn, but leaving other parameters at their default values
cluster_results_list <- rOpenScPCA::sweep_clusters(
  pca_matrix,
  nn = nn_values
)

The resulting list has a length of three, one data frame for each nn parameter tested:

length(cluster_results_list)
[1] 3

It can be helpful (although it is not strictly necessary to keep track) to name this list by the varied nn parameter:

names(cluster_results_list) <- glue::glue("nn_{nn_values}")

We can look at the first few rows of each data frame using purrr::map() to iterate over the list:

cluster_results_list |>
  purrr::map(head)
$nn_10
           cell_id cluster algorithm weighting nn resolution
1 GGTTAACTCCTCACTG       1   louvain   jaccard 10          1
2 TGATGGTGTTGGATCT       2   louvain   jaccard 10          1
3 AGATGCTAGAGCACTG       2   louvain   jaccard 10          1
4 TGGATGTTCAACTTTC       2   louvain   jaccard 10          1
5 TTATTGCGTGAGTGAC       2   louvain   jaccard 10          1
6 AATGACCCAAGGTTGG       1   louvain   jaccard 10          1

$nn_20
           cell_id cluster algorithm weighting nn resolution
1 GGTTAACTCCTCACTG       1   louvain   jaccard 20          1
2 TGATGGTGTTGGATCT       1   louvain   jaccard 20          1
3 AGATGCTAGAGCACTG       1   louvain   jaccard 20          1
4 TGGATGTTCAACTTTC       1   louvain   jaccard 20          1
5 TTATTGCGTGAGTGAC       1   louvain   jaccard 20          1
6 AATGACCCAAGGTTGG       1   louvain   jaccard 20          1

$nn_30
           cell_id cluster algorithm weighting nn resolution
1 GGTTAACTCCTCACTG       1   louvain   jaccard 30          1
2 TGATGGTGTTGGATCT       1   louvain   jaccard 30          1
3 AGATGCTAGAGCACTG       1   louvain   jaccard 30          1
4 TGGATGTTCAACTTTC       1   louvain   jaccard 30          1
5 TTATTGCGTGAGTGAC       1   louvain   jaccard 30          1
6 AATGACCCAAGGTTGG       1   louvain   jaccard 30          1

Generally speaking, purrr::map() can be used to iterate over this list to visualize or analyze each clustering result on its own; we’ll use this approach in the following sections.

Visualizing clustering results

When comparing clustering results, it’s important to first visualize the different clusterings to build context for interpreting QC metrics.

As one example of why this is important, we generally expect that more robust clusters will have higher values for metrics like silhouette width and neighborhood purity. However, we also expect that having fewer clusters in the first place will also lead to higher metrics, regardless of cluster quality: When there are fewer clusters, it is more likely that clusters overlap less with one another just because there aren’t many clusters in the first place. This means that, when interpreting cluster quality metrics, you should be careful to take more context about the data into consideration and not only rely on the metric values.

We’ll therefore visualize these results as UMAPs by iterating over cluster_results_list and combining plots with patchwork::wrap_plots(). We’ll specifically use purrr::imap() to iterate so that we can assign this list’s names as plot titles.

For this, we’ll begin by extracting a table of UMAP coordinates from our SCE object.

umap_df <- reducedDim(sce, "UMAP") |>
  as.data.frame()

Next, we’ll iterate over cluster_results_list to plot the UMAPs.

umap_plots <- cluster_results_list |>
  purrr::imap(
    \(cluster_df, clustering_name) {
      # Add a column with cluster assignments to umap_df
      umap_df_plot <- umap_df |>
        dplyr::mutate(cluster = cluster_df$cluster)

      # Plot the UMAP, colored by the new cluster variable
      ggplot(umap_df_plot, aes(x = UMAP1, y = UMAP2, color = cluster)) +
        geom_point(alpha = 0.6) +
        labs(title = glue::glue("nearest neighbors: {clustering_name}")) +
        # We'll add a couple UMAP plot settings here, including equal axes and
        # turning off the axis ticks and text since UMAP coordinates are not meaningful
        coord_equal() +
        theme(
          axis.ticks = element_blank(),
          axis.text = element_blank()
        )
    }
  )

# Print the plots with patchwork::wrap_plots()
patchwork::wrap_plots(umap_plots)

These plots show that the number of clusters decreases as the nearest neighbors parameter increases, with between 9-13 clusters.

Evaluating clustering results

This section will use purrr::map() to iterate over each clustering result data frame to calculate silhouette width, neighborhood purity, and stability, and then visualize results. The goal of this code is to identify whether one clustering parameterization produces more reliable clusters.

Silhouette width and neighborhood purity

Both silhouette width and neighborhood purity are cell-level quantities, so we can calculate them together in the same call to purrr::map(). Below, we’ll iterate over each data frame in cluster_results_list to calculate these quantities.

cell_metric_list <- cluster_results_list |>
  purrr::map(
    \(cluster_df) {
      # calculate silhouette width
      silhouette_df <- rOpenScPCA::calculate_silhouette(pca_matrix, cluster_df)

      # calculate neighbhorhood purity
      purity_df <- rOpenScPCA::calculate_purity(pca_matrix, cluster_df)

      # Combine into a single data frame
      dplyr::left_join(silhouette_df, purity_df)
    }
  )
Joining with `by = join_by(cell_id, cluster, algorithm, weighting, nn,
resolution)`
Joining with `by = join_by(cell_id, cluster, algorithm, weighting, nn,
resolution)`
Joining with `by = join_by(cell_id, cluster, algorithm, weighting, nn,
resolution)`
# View the first six rows of each clustering result's cell-level QC metrics
purrr::map(cell_metric_list, head)
$nn_10
           cell_id cluster algorithm weighting nn resolution silhouette_other
1 GGTTAACTCCTCACTG       1   louvain   jaccard 10          1                4
2 TGATGGTGTTGGATCT       2   louvain   jaccard 10          1                1
3 AGATGCTAGAGCACTG       2   louvain   jaccard 10          1                1
4 TGGATGTTCAACTTTC       2   louvain   jaccard 10          1                1
5 TTATTGCGTGAGTGAC       2   louvain   jaccard 10          1                4
6 AATGACCCAAGGTTGG       1   louvain   jaccard 10          1                2
  silhouette_width    purity maximum_neighbor
1       0.11974670 0.5954806                1
2       0.16684933 0.8703055                2
3      -0.01994652 0.4088789                1
4      -0.00389170 0.3559577                1
5       0.10148714 0.6340306                2
6       0.29900703 0.8408082                1

$nn_20
           cell_id cluster algorithm weighting nn resolution silhouette_other
1 GGTTAACTCCTCACTG       1   louvain   jaccard 20          1                3
2 TGATGGTGTTGGATCT       1   louvain   jaccard 20          1                3
3 AGATGCTAGAGCACTG       1   louvain   jaccard 20          1                3
4 TGGATGTTCAACTTTC       1   louvain   jaccard 20          1                2
5 TTATTGCGTGAGTGAC       1   louvain   jaccard 20          1                3
6 AATGACCCAAGGTTGG       1   louvain   jaccard 20          1                3
  silhouette_width    purity maximum_neighbor
1      -0.01856650 0.6014493                1
2       0.16054653 0.9672087                1
3       0.17129677 1.0000000                1
4       0.09659459 0.8061952                1
5       0.03631923 0.8797615                1
6       0.08324109 1.0000000                1

$nn_30
           cell_id cluster algorithm weighting nn resolution silhouette_other
1 GGTTAACTCCTCACTG       1   louvain   jaccard 30          1                4
2 TGATGGTGTTGGATCT       1   louvain   jaccard 30          1                4
3 AGATGCTAGAGCACTG       1   louvain   jaccard 30          1                4
4 TGGATGTTCAACTTTC       1   louvain   jaccard 30          1                4
5 TTATTGCGTGAGTGAC       1   louvain   jaccard 30          1                4
6 AATGACCCAAGGTTGG       1   louvain   jaccard 30          1                4
  silhouette_width    purity maximum_neighbor
1      -0.01294996 0.6588463                1
2       0.15391710 1.0000000                1
3       0.16327444 1.0000000                1
4       0.13532673 0.9382301                1
5       0.03019309 0.9093376                1
6       0.07406343 1.0000000                1

To visualize these results, we can combine all data frames in this list into a single overall data frame, where the existing nn column will distinguish among conditions.

cell_metrics_df <- dplyr::bind_rows(cell_metric_list)

We can visualize silhouette width and neighborhood purity each with boxplots, for example, and use the patchwork package to print them together:

# Plot silhouette width
silhouette_plot <- ggplot(cell_metrics_df) +
  aes(x = as.factor(nn), y = silhouette_width, fill = as.factor(nn)) +
  geom_boxplot() +
  scale_fill_brewer(palette = "Pastel2") +
  labs(
    x = "Number of nearest neighbors",
    y = "Silhouette width"
  )


# Plot neighborhood purity width
purity_plot <- ggplot(cell_metrics_df) +
  aes(x = as.factor(nn), y = purity, fill = as.factor(nn)) +
  geom_boxplot() +
  scale_fill_brewer(palette = "Pastel2") +
  labs(
    x = "Number of nearest neighbors",
    y = "Neighborhood purity"
  )


# Add together using the patchwork library, without a legend
silhouette_plot + purity_plot & theme(legend.position = "none")

While there does not appear to be a salient difference among silhouette width distributions, it does appear that purity is higher with a higher nearest neighbors parameter.

Stability

Next, we’ll calculate stability on the clusters using rOpenScPCA::calculate_stability(), specifying the same parameter used for the original cluster calculation at each iteration.

stability_list <- cluster_results_list |>
  purrr::map(
    \(cluster_df) {
      nn <- unique(cluster_df$nn)

      # calculate stability, passing in the parameter value used for this iteration
      rOpenScPCA::calculate_stability(pca_matrix, cluster_df, nn = nn)
    }
  )

We’ll again combine the output of stability_list into a single data frame and plot ari values across nn parameterizations.

stability_df <- dplyr::bind_rows(stability_list)

ggplot(stability_df) +
  aes(x = as.factor(nn), y = ari, fill = as.factor(nn)) +
  geom_boxplot() +
  scale_fill_brewer(palette = "Pastel2") +
  labs(
    x = "Number of nearest neighbors",
    y = "Adjusted Rand Index"
  ) +
  theme(legend.position = "none")

Here, we see that a nearest neighbors value of 20 or 30 leads to more stable clustering results compared to 10.

Varying multiple clustering parameters

The previous section demonstrated how to calculate clusters and QC metrics when varying one parameter, but it is possible to vary multiple parameters at once with rOpenScPCA::sweep_clusters(). In this section, we’ll show an overview of how you might write code to vary two parameters at once (here, nearest neighbors and resolution as examples) and visualize results.

# Define vectors of parameters to vary
nn_values <- seq(10, 30, 10)
res_values <- seq(5, 15, 5)/10


cluster_results_list <- rOpenScPCA::sweep_clusters(
  pca_matrix,
  nn = nn_values,
  resolution = res_values
)

This resulting list now has 9 different clustering results, for each combination of nn_values and res_values:

length(cluster_results_list)
[1] 9

Visualize clusters

Next, we’ll iterate over cluster_results_list to plot the UMAPs. This time, we’ll use purrr::map() and pull out parameters from each iteration’s cluster_df to form the UMAP panel title.

umap_plots <- cluster_results_list |>
  purrr::map(
    \(cluster_df) {

      # Add a column with cluster assignments to umap_df
      umap_df_plot <- umap_df |>
        dplyr::mutate(cluster = cluster_df$cluster)

      # Create a title for the UMAP with both parameters
      umap_title <- glue::glue(
        "nn: {unique(cluster_df$nn)}; res: {unique(cluster_df$resolution)}"
      )

      # Plot the UMAP, colored by the new cluster variable
      ggplot(umap_df_plot, aes(x = UMAP1, y = UMAP2, color = cluster)) +
        geom_point(alpha = 0.6) +
        labs(title = umap_title) +
        # We'll add a couple UMAP-specific plot settings 
        coord_equal() +
        theme(
          axis.ticks = element_blank(),
          axis.text = element_blank(),
          legend.position = "bottom"
        )
  }
)

# Print the plots with patchwork::wrap_plots()
patchwork::wrap_plots(umap_plots)

Calculate and visualize QC metrics

This section presents one coding strategy to calculate and visualize results when varying two clustering parameters. In particular, we use faceting to help display all information in one plot, by placing nearest neighbor values on the X-axis and faceting by resolution values. Since silhouette width and neighhorbood purity calculations using generally similar code, we’ll just show neighborhood purity here.

Neighborhood purity

First, we’ll calculate neighborhood purity and combine results into a single data frame.

purity_df <- cluster_results_list |>
  purrr::map(
    \(cluster_df) {
      rOpenScPCA::calculate_purity(pca_matrix, cluster_df)
    }
  ) |>
  dplyr::bind_rows()

We’ll add a column resolution_label which we’ll use to have informative panel titles in the faceted ggplot we make next.

purity_df <- purity_df |>
  dplyr::mutate(resolution_label = glue::glue("Resolution: {resolution}"))
ggplot(purity_df) +
  aes(x = as.factor(nn), y = purity, fill = as.factor(nn)) +
  geom_boxplot() +
  scale_fill_brewer(palette = "Pastel2") +
  # facet by resolution
  facet_wrap(vars(resolution_label)) +
  labs(
    x = "Number of nearest neighbors",
    y = "Neighborhood purity"
  ) +
  theme(legend.position = "none")

Stability

Similar to above, we’ll calculate stability, combine results into a single data frame, add a resolution_label column to support plot interpretation, and finally make our plot.

stability_df <- cluster_results_list |>
  purrr::map(
    \(cluster_df) {

      # Extract parameters for this clustering result
      nn <- unique(cluster_df$nn)
      resolution <- unique(cluster_df$resolution)

      rOpenScPCA::calculate_stability(
        pca_matrix,
        cluster_df,
        nn = nn,
        resolution = resolution
      )
    }
  ) |>
  dplyr::bind_rows()

stability_df <- stability_df |>
  dplyr::mutate(resolution_label = glue::glue("Resolution: {resolution}"))
ggplot(stability_df) +
  aes(x = as.factor(nn), y = ari, fill = as.factor(nn)) +
  geom_boxplot() +
  scale_fill_brewer(palette = "Pastel2") +
  # facet by resolution
  facet_wrap(vars(resolution_label)) +
  labs(
    x = "Number of nearest neighbors",
    y = "Adjusted Rand Index"
  ) +
  theme(legend.position = "none")

Session Info

# record the versions of the packages used in this analysis and other environment information
sessionInfo()
R version 4.4.0 (2024-04-24)
Platform: aarch64-apple-darwin20
Running under: macOS 15.2

Matrix products: default
BLAS:   /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/lib/libRblas.0.dylib 
LAPACK: /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/lib/libRlapack.dylib;  LAPACK version 3.12.0

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

time zone: America/New_York
tzcode source: internal

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

other attached packages:
 [1] patchwork_1.3.0             ggplot2_3.5.1              
 [3] SingleCellExperiment_1.26.0 SummarizedExperiment_1.34.0
 [5] Biobase_2.64.0              GenomicRanges_1.56.1       
 [7] GenomeInfoDb_1.40.1         IRanges_2.38.1             
 [9] S4Vectors_0.42.1            BiocGenerics_0.50.0        
[11] MatrixGenerics_1.16.0       matrixStats_1.4.1          
[13] rOpenScPCA_0.1.0           

loaded via a namespace (and not attached):
 [1] gtable_0.3.5            xfun_0.48               bslib_0.8.0            
 [4] lattice_0.22-6          pdfCluster_1.0-4        vctrs_0.6.5            
 [7] tools_4.4.0             generics_0.1.3          parallel_4.4.0         
[10] tibble_3.2.1            fansi_1.0.6             highr_0.11             
[13] cluster_2.1.6           BiocNeighbors_1.22.0    pkgconfig_2.0.3        
[16] Matrix_1.7-0            RColorBrewer_1.1-3      lifecycle_1.0.4        
[19] GenomeInfoDbData_1.2.12 compiler_4.4.0          farver_2.1.2           
[22] munsell_0.5.1           bluster_1.14.0          codetools_0.2-20       
[25] htmltools_0.5.8.1       sass_0.4.9              yaml_2.3.10            
[28] pillar_1.9.0            crayon_1.5.3            jquerylib_0.1.4        
[31] tidyr_1.3.1             BiocParallel_1.38.0     DelayedArray_0.30.1    
[34] cachem_1.1.0            abind_1.4-8             tidyselect_1.2.1       
[37] digest_0.6.37           dplyr_1.1.4             purrr_1.0.2            
[40] magic_1.6-1             labeling_0.4.3          rprojroot_2.0.4        
[43] fastmap_1.2.0           grid_4.4.0              colorspace_2.1-1       
[46] cli_3.6.3               SparseArray_1.4.8       magrittr_2.0.3         
[49] S4Arrays_1.4.1          utf8_1.2.4              withr_3.0.1            
[52] scales_1.3.0            UCSC.utils_1.0.0        rmarkdown_2.28         
[55] XVector_0.44.0          httr_1.4.7              igraph_2.0.3           
[58] evaluate_1.0.0          knitr_1.48              geometry_0.5.0         
[61] rlang_1.1.4             Rcpp_1.0.13             glue_1.8.0             
[64] BiocManager_1.30.25     renv_1.0.10             jsonlite_1.8.9         
[67] R6_2.5.1                zlibbioc_1.50.0        
LS0tCnRpdGxlOiAiQ29tcGFyaW5nIGNsdXN0ZXJpbmcgcGFyYW1ldGVycyB3aXRoIHJPcGVuU2NQQ0EiCmRhdGU6ICJgciBTeXMuRGF0ZSgpYCIKYXV0aG9yOiAiRGF0YSBMYWIiCm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgdG9jOiB5ZXMKICAgIHRvY19mbG9hdDogeWVzCiAgICBkZl9wcmludDogcGFnZWQKLS0tCgojIyBJbnRyb2R1Y3Rpb24KCkNsdXN0ZXJpbmcgYWxnb3JpdGhtcyBoYXZlIHNldmVyYWwgcGFyYW1ldGVycyB3aGljaCBjYW4gYmUgdmFyaWVkLCBsZWFkaW5nIHRvIGRpZmZlcmVudCBjbHVzdGVyaW5nIHJlc3VsdHMuCkEga2V5IHF1ZXN0aW9uIHdoZW4gY2x1c3RlcmluZywgdGhlcmVmb3JlLCBpcyBob3cgdG8gaWRlbnRpZnkgYSBzZXQgb2YgcGFyYW1ldGVycyB0aGF0IGxlYWQgdG8gcm9idXN0IGFuZCByZWxpYWJsZSBjbHVzdGVycyB0aGF0IGNhbiBiZSB1c2VkIGluIGRvd25zdHJlYW0gYW5hbHlzaXMuIAoKVGhpcyBub3RlYm9vayBwcm92aWRlcyBleGFtcGxlcyBvZiBob3cgdG8gdXNlIHRoZSBgck9wZW5TY1BDQWAgcGFja2FnZSB0bzoKCiogQ2FsY3VsYXRlIHNldmVyYWwgdmVyc2lvbnMgb2YgY2x1c3RlcmluZyByZXN1bHRzIGFjcm9zcyBzZXZlcmFsIGRpZmZlcmVudCBwYXJhbWV0ZXJpemF0aW9ucwoqIENhbGN1bGF0ZSBRQyBtZXRyaWNzIG9uIGFjcm9zcyBjbHVzdGVyaW5nIHJlc3VsdHMKClBsZWFzZSByZWZlciB0byB0aGUgW2AwMV9wZXJmb3JtLWV2YWx1YXRlLWNsdXN0ZXJpbmcuUm1kYF0oMDFfcGVyZm9ybS1ldmFsdWF0ZS1jbHVzdGVyaW5nLlJtZCkgbm90ZWJvb2sgZm9yIGEgdHV0b3JpYWwgb24gdXNpbmcgYHJPcGVuU2NQQ0FgIGZ1bmN0aW9ucyB0bzoKCiogQ2FsY3VsYXRlIGNsdXN0ZXJzIGZyb20gYSBzaW5nbGUgcGFyYW1ldGVyaXphdGlvbgoqIENhbGN1bGF0ZSBRQyBtZXRyaWNzIG9uIGEgc2luZ2xlIHNldCBvZiBjbHVzdGVycywgYXMgd2VsbCBhcyBleHBsYW5hdGlvbnMgb2YgdGhlIG1ldHJpY3MgdGhlbXNlbHZlcwoKVGhpcyBub3RlYm9vayB3aWxsIHVzZSB0aGUgc2FtcGxlIGBTQ1BDUzAwMDAwMWAgZnJvbSBwcm9qZWN0IGBTQ1BDUDAwMDAwMWAsIHdoaWNoIGlzIGFzc3VtZWQgcHJlc2VudCBpbiB0aGUgYE9wZW5TY1BDQS1hbmFseXNpcy9kYXRhL2N1cnJlbnQvU0NQQ1AwMDAwMDFgIGRpcmVjdG9yeSwgZm9yIGFsbCBleGFtcGxlcy4KUGxlYXNlIFtzZWUgdGhpcyBkb2N1bWVudGF0aW9uXShodHRwczovL29wZW5zY3BjYS5yZWFkdGhlZG9jcy5pby9lbi9sYXRlc3QvZ2V0dGluZy1zdGFydGVkL2FjY2Vzc2luZy1yZXNvdXJjZXMvZ2V0dGluZy1hY2Nlc3MtdG8tZGF0YS8pIGZvciBtb3JlIGluZm9ybWF0aW9uIGFib3V0IG9idGFpbmluZyBTY1BDQSBkYXRhLgoKIyMgU2V0dXAKCiMjIyBQYWNrYWdlcwoKCmBgYHtyIHBhY2thZ2VzfQpsaWJyYXJ5KHJPcGVuU2NQQ0EpCgpzdXBwcmVzc1BhY2thZ2VTdGFydHVwTWVzc2FnZXMoewogIGxpYnJhcnkoU2luZ2xlQ2VsbEV4cGVyaW1lbnQpCiAgbGlicmFyeShnZ3Bsb3QyKQogIGxpYnJhcnkocGF0Y2h3b3JrKQp9KQoKIyBTZXQgZ2dwbG90IHRoZW1lIGZvciBwbG90cwp0aGVtZV9zZXQodGhlbWVfYncoKSkKYGBgCgoKIyMjIFBhdGhzCgpgYGB7ciBiYXNlIHBhdGhzfQojIFRoZSBiYXNlIHBhdGggZm9yIHRoZSBPcGVuU2NQQ0EgcmVwb3NpdG9yeQpyZXBvc2l0b3J5X2Jhc2UgPC0gcnByb2pyb290OjpmaW5kX3Jvb3QocnByb2pyb290Ojppc19naXRfcm9vdCkKCiMgVGhlIGN1cnJlbnQgZGF0YSBkaXJlY3RvcnksIGZvdW5kIHdpdGhpbiB0aGUgcmVwb3NpdG9yeSBiYXNlIGRpcmVjdG9yeQpkYXRhX2RpciA8LSBmaWxlLnBhdGgocmVwb3NpdG9yeV9iYXNlLCAiZGF0YSIsICJjdXJyZW50IikKCiMgVGhlIHBhdGggdG8gdGhpcyBtb2R1bGUKbW9kdWxlX2Jhc2UgPC0gZmlsZS5wYXRoKHJlcG9zaXRvcnlfYmFzZSwgImFuYWx5c2VzIiwgImhlbGxvLWNsdXN0ZXJzIikKYGBgCgpgYGB7ciBpbnB1dCBmaWxlIHBhdGh9CiMgUGF0aCB0byBwcm9jZXNzZWQgU0NFIGZpbGUgZm9yIHNhbXBsZSBTQ1BDUzAwMDAwMQppbnB1dF9zY2VfZmlsZSA8LSBmaWxlLnBhdGgoZGF0YV9kaXIsICJTQ1BDUDAwMDAwMSIsICJTQ1BDUzAwMDAwMSIsICJTQ1BDTDAwMDAwMV9wcm9jZXNzZWQucmRzIikKYGBgCgoKIyMjIFNldCB0aGUgcmFuZG9tIHNlZWQKCkJlY2F1c2UgY2x1c3RlcmluZyBpbnZvbHZlcyByYW5kb20gc2FtcGxpbmcsIGl0IGlzIGltcG9ydGFudCB0byBzZXQgdGhlIHJhbmRvbSBzZWVkIGF0IHRoZSB0b3Agb2YgeW91ciBhbmFseXNpcyBzY3JpcHQgb3Igbm90ZWJvb2sgdG8gZW5zdXJlIHJlcHJvZHVjaWJpbGl0eS4KCmBgYHtyIHNldCBzZWVkfQpzZXQuc2VlZCgyMDI0KQpgYGAKCiMjIFJlYWQgaW4gYW5kIHByZXBhcmUgZGF0YQoKVG8gYmVnaW4sIHdlJ2xsIHJlYWQgaW4gdGhlIGBTaW5nbGVDZWxsRXhwZXJpbWVudGAgKFNDRSkgb2JqZWN0LgoKYGBge3IgcmVhZCBkYXRhfQojIFJlYWQgdGhlIFNDRSBmaWxlCnNjZSA8LSByZWFkUkRTKGlucHV0X3NjZV9maWxlKQpgYGAKCkZvciB0aGUgaW5pdGlhbCBjbHVzdGVyIGNhbGN1bGF0aW9ucyBhbmQgZXZhbHVhdGlvbnMsIHdlIHdpbGwgdXNlIHRoZSBQQ0EgbWF0cml4IGV4dHJhY3RlZCBmcm9tIHRoZSBTQ0Ugb2JqZWN0LgpBcyBzaG93biBpbiBbYDAxX3BlcmZvcm0tZXZhbHVhdGUtY2x1c3RlcmluZy5SbWRgXSgwMV9wZXJmb3JtLWV2YWx1YXRlLWNsdXN0ZXJpbmcuUm1kKSwgaXQgaXMgYWxzbyBwb3NzaWJsZSB0byB1c2UgYW4gU0NFIG9iamVjdCBvciBhIFNldXJhdCBvYmplY3QgZGlyZWN0bHkuCgoKYGBge3IgZXh0cmFjdCBwY2EgZGF0YX0KIyBFeHRyYWN0IHRoZSBQQ0EgbWF0cml4IGZyb20gYW4gU0NFIG9iamVjdApwY2FfbWF0cml4IDwtIHJlZHVjZWREaW0oc2NlLCAiUENBIikKYGBgCgojIyBWYXJ5aW5nIGEgc2luZ2xlIGNsdXN0ZXJpbmcgcGFyYW1ldGVyCgpUaGlzIHNlY3Rpb24gd2lsbCBzaG93IGhvdyB0byBwZXJmb3JtIGNsdXN0ZXJpbmcgYWNyb3NzIGEgc2V0IG9mIHBhcmFtZXRlcnMgKGFrYSwgInN3ZWVwIiBhIHNldCBvZiBwYXJhbWV0ZXJzKSB3aXRoIGByT3BlblNjUENBOjpzd2VlcF9jbHVzdGVycygpYC4gCgpUaGlzIGZ1bmN0aW9uIHRha2VzIGEgUENBIG1hdHJpeCB3aXRoIHJvdyBuYW1lcyByZXByZXNlbnRpbmcgdW5pcXVlIGNlbGwgaWRzIChlLmcuLCBiYXJjb2RlcykgYXMgaXRzIHByaW1hcnkgYXJndW1lbnQsIHdpdGggYWRkaXRpb25hbCBhcmd1bWVudHMgZm9yIGNsdXN0ZXIgcGFyYW1ldGVycy4gClRoaXMgZnVuY3Rpb24gd3JhcHMgdGhlIGByT3BlblNjUENBOjpjYWxjdWxhdGVfY2x1c3RlcnMoKWAgZnVuY3Rpb24gYnV0IGFsbG93cyB5b3UgdG8gcHJvdmlkZSBhIHZlY3RvciBvZiBwYXJhbWV0ZXIgdmFsdWVzIHRvIHBlcmZvcm0gY2x1c3RlcmluZyBhY3Jvc3MsIGFzIGxpc3RlZCBiZWxvdy4KQ2x1c3RlcnMgd2lsbCBiZSBjYWxjdWxhdGVkIGZvciBhbGwgY29tYmluYXRpb25zIG9mIHBhcmFtZXRlcnMgdmFsdWVzICh3aGVyZSBhcHBsaWNhYmxlKTsgZGVmYXVsdCB2YWx1ZXMgdGhhdCB0aGUgZnVuY3Rpb24gd2lsbCB1c2UgZm9yIGFueSB1bnNwZWNpZmllZCBwYXJhbWV0ZXIgdmFsdWVzIGFyZSBzaG93biBpbiBwYXJlbnRoZXNlcwoKKiBgYWxnb3JpdGhtYDogV2hpY2ggY2x1c3RlcmluZyBhbGdvcml0aG0gdG8gdXNlIChMb3V2YWluKQoqIGB3ZWlnaHRpbmdgOiBXaGljaCB3ZWlnaHRpbmcgc2NoZW1lIHRvIHVzZSAoSmFjY2FyZCkKKiBgbm5gOiBUaGUgbnVtYmVyIG9mIG5lYXJlc3QgbmVpZ2hib3JzICgxMCkKKiBgcmVzb2x1dGlvbmA6IFRoZSByZXNvbHV0aW9uIHBhcmFtZXRlciAoMTsgdXNlZCBvbmx5IHdpdGggTG91dmFpbiBhbmQgTGVpZGVuIGNsdXN0ZXJpbmcpCiogYG9iamVjdGl2ZV9mdW5jdGlvbmA6IFRoZSBvYmplY3RpdmUgZnVuY3Rpb24gdG8gb3B0aW1pemUgY2x1c3RlcnMgKENQTTsgdXNlZCBvbmx5IHdpdGggTGVpZGVuIGNsdXN0ZXJpbmcpCgpgck9wZW5TY1BDQTo6c3dlZXBfY2x1c3RlcnMoKWAgZG9lcyBhbGxvdyB5b3UgdG8gc3BlY2lmeSB2YWx1ZXMgZm9yIGFueSBvdGhlciBwYXJhbWV0ZXJzLiAKCgpUaGlzIGZ1bmN0aW9uIHdpbGwgcmV0dXJuIGEgbGlzdCBvZiBkYXRhIGZyYW1lcyBvZiBjbHVzdGVyaW5nIHJlc3VsdHMuIApFYWNoIGRhdGEgZnJhbWUgd2lsbCBoYXZlIHRoZSBmb2xsb3dpbmcgY29sdW1uczoKCiogYGNlbGxfaWRgOiBVbmlxdWUgY2VsbCBpZGVudGlmaWVycywgb2J0YWluZWQgZnJvbSB0aGUgUENBIG1hdHJpeCdzIHJvdyBuYW1lcwoqIGBjbHVzdGVyYDogQSBmYWN0b3IgY29sdW1uIHdpdGggdGhlIGNsdXN0ZXIgaWRlbnRpdGllcwoqIFRoZXJlIHdpbGwgYmUgb25lIGNvbHVtbiBmb3IgZWFjaCBjbHVzdGVyaW5nIHBhcmFtZXRlciB1c2VkCgpUbyBkZW1vbnN0cmF0ZSB0aGlzIGZ1bmN0aW9uLCB3ZSdsbCBjYWxjdWxhdGUgY2x1c3RlcnMgdXNpbmcgdGhlIExvdXZhaW4gYWxnb3JpdGhtIHdoaWxlIHZhcnlpbmcgdGhlIGBubmAgcGFyYW1ldGVyOgoKYGBge3Igc3dlZXAgY2x1c3RlcnN9CiMgRGVmaW5lIG5uIHBhcmFtZXRlciB2YWx1ZXMgb2YgaW50ZXJlc3QKbm5fdmFsdWVzIDwtIHNlcSgxMCwgMzAsIDEwKQoKIyBDYWxjdWxhdGUgY2x1c3RlcnMgdmFyeWluZyBubiwgYnV0IGxlYXZpbmcgb3RoZXIgcGFyYW1ldGVycyBhdCB0aGVpciBkZWZhdWx0IHZhbHVlcwpjbHVzdGVyX3Jlc3VsdHNfbGlzdCA8LSByT3BlblNjUENBOjpzd2VlcF9jbHVzdGVycygKICBwY2FfbWF0cml4LAogIG5uID0gbm5fdmFsdWVzCikKYGBgCgpUaGUgcmVzdWx0aW5nIGxpc3QgaGFzIGEgbGVuZ3RoIG9mIHRocmVlLCBvbmUgZGF0YSBmcmFtZSBmb3IgZWFjaCBgbm5gIHBhcmFtZXRlciB0ZXN0ZWQ6CgpgYGB7ciBsZW5ndGggY2x1c3Rlcl9yZXN1bHRzX2xpc3R9Cmxlbmd0aChjbHVzdGVyX3Jlc3VsdHNfbGlzdCkKYGBgCgpJdCBjYW4gYmUgaGVscGZ1bCAoYWx0aG91Z2ggaXQgaXMgbm90IHN0cmljdGx5IG5lY2Vzc2FyeSB0byBrZWVwIHRyYWNrKSB0byBuYW1lIHRoaXMgbGlzdCBieSB0aGUgdmFyaWVkIGBubmAgcGFyYW1ldGVyOgoKYGBge3Igc2V0IGxpc3QgbmFtZXN9Cm5hbWVzKGNsdXN0ZXJfcmVzdWx0c19saXN0KSA8LSBnbHVlOjpnbHVlKCJubl97bm5fdmFsdWVzfSIpCmBgYAoKCldlIGNhbiBsb29rIGF0IHRoZSBmaXJzdCBmZXcgcm93cyBvZiBlYWNoIGRhdGEgZnJhbWUgdXNpbmcgW2BwdXJycjo6bWFwKClgXShodHRwczovL3B1cnJyLnRpZHl2ZXJzZS5vcmcvcmVmZXJlbmNlL21hcC5odG1sKSB0byBpdGVyYXRlIG92ZXIgdGhlIGxpc3Q6CgoKYGBge3IgbWFwIGNsdXN0ZXJfcmVzdWx0c19saXN0fQpjbHVzdGVyX3Jlc3VsdHNfbGlzdCB8PgogIHB1cnJyOjptYXAoaGVhZCkKYGBgCgpHZW5lcmFsbHkgc3BlYWtpbmcsIGBwdXJycjo6bWFwKClgIGNhbiBiZSB1c2VkIHRvIGl0ZXJhdGUgb3ZlciB0aGlzIGxpc3QgdG8gdmlzdWFsaXplIG9yIGFuYWx5emUgZWFjaCBjbHVzdGVyaW5nIHJlc3VsdCBvbiBpdHMgb3duOyB3ZSdsbCB1c2UgdGhpcyBhcHByb2FjaCBpbiB0aGUgZm9sbG93aW5nIHNlY3Rpb25zLiAKCiMjIyBWaXN1YWxpemluZyBjbHVzdGVyaW5nIHJlc3VsdHMKCldoZW4gY29tcGFyaW5nIGNsdXN0ZXJpbmcgcmVzdWx0cywgaXQncyBpbXBvcnRhbnQgdG8gZmlyc3QgdmlzdWFsaXplIHRoZSBkaWZmZXJlbnQgY2x1c3RlcmluZ3MgdG8gYnVpbGQgY29udGV4dCBmb3IgaW50ZXJwcmV0aW5nIFFDIG1ldHJpY3MuCgpBcyBvbmUgZXhhbXBsZSBvZiB3aHkgdGhpcyBpcyBpbXBvcnRhbnQsIHdlIGdlbmVyYWxseSBleHBlY3QgdGhhdCBtb3JlIHJvYnVzdCBjbHVzdGVycyB3aWxsIGhhdmUgaGlnaGVyIHZhbHVlcyBmb3IgbWV0cmljcyBsaWtlIHNpbGhvdWV0dGUgd2lkdGggYW5kIG5laWdoYm9yaG9vZCBwdXJpdHkuCkhvd2V2ZXIsIHdlIGFsc28gZXhwZWN0IHRoYXQgaGF2aW5nIGZld2VyIGNsdXN0ZXJzIGluIHRoZSBmaXJzdCBwbGFjZSB3aWxsIGFsc28gbGVhZCB0byBoaWdoZXIgbWV0cmljcywgcmVnYXJkbGVzcyBvZiBjbHVzdGVyIHF1YWxpdHk6IFdoZW4gdGhlcmUgYXJlIGZld2VyIGNsdXN0ZXJzLCBpdCBpcyBtb3JlIGxpa2VseSB0aGF0IGNsdXN0ZXJzIG92ZXJsYXAgbGVzcyB3aXRoIG9uZSBhbm90aGVyIGp1c3QgYmVjYXVzZSB0aGVyZSBhcmVuJ3QgbWFueSBjbHVzdGVycyBpbiB0aGUgZmlyc3QgcGxhY2UuClRoaXMgbWVhbnMgdGhhdCwgd2hlbiBpbnRlcnByZXRpbmcgY2x1c3RlciBxdWFsaXR5IG1ldHJpY3MsIHlvdSBzaG91bGQgYmUgY2FyZWZ1bCB0byB0YWtlIG1vcmUgY29udGV4dCBhYm91dCB0aGUgZGF0YSBpbnRvIGNvbnNpZGVyYXRpb24gYW5kIG5vdCBvbmx5IHJlbHkgb24gdGhlIG1ldHJpYyB2YWx1ZXMuCgpXZSdsbCB0aGVyZWZvcmUgdmlzdWFsaXplIHRoZXNlIHJlc3VsdHMgYXMgVU1BUHMgYnkgaXRlcmF0aW5nIG92ZXIgYGNsdXN0ZXJfcmVzdWx0c19saXN0YCBhbmQgY29tYmluaW5nIHBsb3RzIHdpdGggW2BwYXRjaHdvcms6OndyYXBfcGxvdHMoKWBdKGh0dHBzOi8vcGF0Y2h3b3JrLmRhdGEtaW1hZ2luaXN0LmNvbS9yZWZlcmVuY2Uvd3JhcF9wbG90cy5odG1sKS4KV2UnbGwgc3BlY2lmaWNhbGx5IHVzZSBbYHB1cnJyOjppbWFwKClgXShodHRwczovL3B1cnJyLnRpZHl2ZXJzZS5vcmcvcmVmZXJlbmNlL2ltYXAuaHRtbCkgdG8gaXRlcmF0ZSBzbyB0aGF0IHdlIGNhbiBhc3NpZ24gdGhpcyBsaXN0J3MgbmFtZXMgYXMgcGxvdCB0aXRsZXMuCgpGb3IgdGhpcywgd2UnbGwgYmVnaW4gYnkgZXh0cmFjdGluZyBhIHRhYmxlIG9mIFVNQVAgY29vcmRpbmF0ZXMgZnJvbSBvdXIgU0NFIG9iamVjdC4KCmBgYHtyIGNyZWF0ZSB1bWFwX2RmfQp1bWFwX2RmIDwtIHJlZHVjZWREaW0oc2NlLCAiVU1BUCIpIHw+CiAgYXMuZGF0YS5mcmFtZSgpCmBgYAoKTmV4dCwgd2UnbGwgaXRlcmF0ZSBvdmVyIGBjbHVzdGVyX3Jlc3VsdHNfbGlzdGAgdG8gcGxvdCB0aGUgVU1BUHMuCgpgYGB7ciBwbG90IG5uIHVtYXBzLCBmaWcud2lkdGggPSAxMn0KdW1hcF9wbG90cyA8LSBjbHVzdGVyX3Jlc3VsdHNfbGlzdCB8PgogIHB1cnJyOjppbWFwKAogICAgXChjbHVzdGVyX2RmLCBjbHVzdGVyaW5nX25hbWUpIHsKICAgICAgIyBBZGQgYSBjb2x1bW4gd2l0aCBjbHVzdGVyIGFzc2lnbm1lbnRzIHRvIHVtYXBfZGYKICAgICAgdW1hcF9kZl9wbG90IDwtIHVtYXBfZGYgfD4KICAgICAgICBkcGx5cjo6bXV0YXRlKGNsdXN0ZXIgPSBjbHVzdGVyX2RmJGNsdXN0ZXIpCgogICAgICAjIFBsb3QgdGhlIFVNQVAsIGNvbG9yZWQgYnkgdGhlIG5ldyBjbHVzdGVyIHZhcmlhYmxlCiAgICAgIGdncGxvdCh1bWFwX2RmX3Bsb3QsIGFlcyh4ID0gVU1BUDEsIHkgPSBVTUFQMiwgY29sb3IgPSBjbHVzdGVyKSkgKwogICAgICAgIGdlb21fcG9pbnQoYWxwaGEgPSAwLjYpICsKICAgICAgICBsYWJzKHRpdGxlID0gZ2x1ZTo6Z2x1ZSgibmVhcmVzdCBuZWlnaGJvcnM6IHtjbHVzdGVyaW5nX25hbWV9IikpICsKICAgICAgICAjIFdlJ2xsIGFkZCBhIGNvdXBsZSBVTUFQIHBsb3Qgc2V0dGluZ3MgaGVyZSwgaW5jbHVkaW5nIGVxdWFsIGF4ZXMgYW5kCiAgICAgICAgIyB0dXJuaW5nIG9mZiB0aGUgYXhpcyB0aWNrcyBhbmQgdGV4dCBzaW5jZSBVTUFQIGNvb3JkaW5hdGVzIGFyZSBub3QgbWVhbmluZ2Z1bAogICAgICAgIGNvb3JkX2VxdWFsKCkgKwogICAgICAgIHRoZW1lKAogICAgICAgICAgYXhpcy50aWNrcyA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgIGF4aXMudGV4dCA9IGVsZW1lbnRfYmxhbmsoKQogICAgICAgICkKICAgIH0KICApCgojIFByaW50IHRoZSBwbG90cyB3aXRoIHBhdGNod29yazo6d3JhcF9wbG90cygpCnBhdGNod29yazo6d3JhcF9wbG90cyh1bWFwX3Bsb3RzKQpgYGAKClRoZXNlIHBsb3RzIHNob3cgdGhhdCB0aGUgbnVtYmVyIG9mIGNsdXN0ZXJzIGRlY3JlYXNlcyBhcyB0aGUgbmVhcmVzdCBuZWlnaGJvcnMgcGFyYW1ldGVyIGluY3JlYXNlcywgd2l0aCBiZXR3ZWVuIDktMTMgY2x1c3RlcnMuCgoKCgojIyMgRXZhbHVhdGluZyBjbHVzdGVyaW5nIHJlc3VsdHMKClRoaXMgc2VjdGlvbiB3aWxsIHVzZSBgcHVycnI6Om1hcCgpYCB0byBpdGVyYXRlIG92ZXIgZWFjaCBjbHVzdGVyaW5nIHJlc3VsdCBkYXRhIGZyYW1lIHRvIGNhbGN1bGF0ZSBzaWxob3VldHRlIHdpZHRoLCBuZWlnaGJvcmhvb2QgcHVyaXR5LCBhbmQgc3RhYmlsaXR5LCBhbmQgdGhlbiB2aXN1YWxpemUgcmVzdWx0cy4KVGhlIGdvYWwgb2YgdGhpcyBjb2RlIGlzIHRvIGlkZW50aWZ5IHdoZXRoZXIgb25lIGNsdXN0ZXJpbmcgcGFyYW1ldGVyaXphdGlvbiBwcm9kdWNlcyBtb3JlIHJlbGlhYmxlIGNsdXN0ZXJzLiAKCgojIyMjIFNpbGhvdWV0dGUgd2lkdGggYW5kIG5laWdoYm9yaG9vZCBwdXJpdHkKCkJvdGggc2lsaG91ZXR0ZSB3aWR0aCBhbmQgbmVpZ2hib3Job29kIHB1cml0eSBhcmUgY2VsbC1sZXZlbCBxdWFudGl0aWVzLCBzbyB3ZSBjYW4gY2FsY3VsYXRlIHRoZW0gdG9nZXRoZXIgaW4gdGhlIHNhbWUgY2FsbCB0byBgcHVycnI6Om1hcCgpYC4KQmVsb3csIHdlJ2xsIGl0ZXJhdGUgb3ZlciBlYWNoIGRhdGEgZnJhbWUgaW4gYGNsdXN0ZXJfcmVzdWx0c19saXN0YCB0byBjYWxjdWxhdGUgdGhlc2UgcXVhbnRpdGllcy4KCmBgYHtyIGNhbGN1bGF0ZSBjZWxsIGxldmVsIG1ldHJpY3N9CmNlbGxfbWV0cmljX2xpc3QgPC0gY2x1c3Rlcl9yZXN1bHRzX2xpc3QgfD4KICBwdXJycjo6bWFwKAogICAgXChjbHVzdGVyX2RmKSB7CiAgICAgICMgY2FsY3VsYXRlIHNpbGhvdWV0dGUgd2lkdGgKICAgICAgc2lsaG91ZXR0ZV9kZiA8LSByT3BlblNjUENBOjpjYWxjdWxhdGVfc2lsaG91ZXR0ZShwY2FfbWF0cml4LCBjbHVzdGVyX2RmKQoKICAgICAgIyBjYWxjdWxhdGUgbmVpZ2hiaG9yaG9vZCBwdXJpdHkKICAgICAgcHVyaXR5X2RmIDwtIHJPcGVuU2NQQ0E6OmNhbGN1bGF0ZV9wdXJpdHkocGNhX21hdHJpeCwgY2x1c3Rlcl9kZikKCiAgICAgICMgQ29tYmluZSBpbnRvIGEgc2luZ2xlIGRhdGEgZnJhbWUKICAgICAgZHBseXI6OmxlZnRfam9pbihzaWxob3VldHRlX2RmLCBwdXJpdHlfZGYpCiAgICB9CiAgKQoKIyBWaWV3IHRoZSBmaXJzdCBzaXggcm93cyBvZiBlYWNoIGNsdXN0ZXJpbmcgcmVzdWx0J3MgY2VsbC1sZXZlbCBRQyBtZXRyaWNzCnB1cnJyOjptYXAoY2VsbF9tZXRyaWNfbGlzdCwgaGVhZCkKYGBgCgoKVG8gdmlzdWFsaXplIHRoZXNlIHJlc3VsdHMsIHdlIGNhbiBjb21iaW5lIGFsbCBkYXRhIGZyYW1lcyBpbiB0aGlzIGxpc3QgaW50byBhIHNpbmdsZSBvdmVyYWxsIGRhdGEgZnJhbWUsIHdoZXJlIHRoZSBleGlzdGluZyBgbm5gIGNvbHVtbiB3aWxsIGRpc3Rpbmd1aXNoIGFtb25nIGNvbmRpdGlvbnMuCgpgYGB7ciBjb21iaW5lIGNlbGwgbWV0cmljcyBsaXN0fQpjZWxsX21ldHJpY3NfZGYgPC0gZHBseXI6OmJpbmRfcm93cyhjZWxsX21ldHJpY19saXN0KQpgYGAKCldlIGNhbiB2aXN1YWxpemUgc2lsaG91ZXR0ZSB3aWR0aCBhbmQgbmVpZ2hib3Job29kIHB1cml0eSBlYWNoIHdpdGggYm94cGxvdHMsIGZvciBleGFtcGxlLCBhbmQgdXNlIHRoZSBbYHBhdGNod29ya2BdKGh0dHBzOi8vcGF0Y2h3b3JrLmRhdGEtaW1hZ2luaXN0LmNvbS8pIHBhY2thZ2UgdG8gcHJpbnQgdGhlbSB0b2dldGhlcjoKCgpgYGB7cn0KIyBQbG90IHNpbGhvdWV0dGUgd2lkdGgKc2lsaG91ZXR0ZV9wbG90IDwtIGdncGxvdChjZWxsX21ldHJpY3NfZGYpICsKICBhZXMoeCA9IGFzLmZhY3RvcihubiksIHkgPSBzaWxob3VldHRlX3dpZHRoLCBmaWxsID0gYXMuZmFjdG9yKG5uKSkgKwogIGdlb21fYm94cGxvdCgpICsKICBzY2FsZV9maWxsX2JyZXdlcihwYWxldHRlID0gIlBhc3RlbDIiKSArCiAgbGFicygKICAgIHggPSAiTnVtYmVyIG9mIG5lYXJlc3QgbmVpZ2hib3JzIiwKICAgIHkgPSAiU2lsaG91ZXR0ZSB3aWR0aCIKICApCgoKIyBQbG90IG5laWdoYm9yaG9vZCBwdXJpdHkgd2lkdGgKcHVyaXR5X3Bsb3QgPC0gZ2dwbG90KGNlbGxfbWV0cmljc19kZikgKwogIGFlcyh4ID0gYXMuZmFjdG9yKG5uKSwgeSA9IHB1cml0eSwgZmlsbCA9IGFzLmZhY3RvcihubikpICsKICBnZW9tX2JveHBsb3QoKSArCiAgc2NhbGVfZmlsbF9icmV3ZXIocGFsZXR0ZSA9ICJQYXN0ZWwyIikgKwogIGxhYnMoCiAgICB4ID0gIk51bWJlciBvZiBuZWFyZXN0IG5laWdoYm9ycyIsCiAgICB5ID0gIk5laWdoYm9yaG9vZCBwdXJpdHkiCiAgKQoKCiMgQWRkIHRvZ2V0aGVyIHVzaW5nIHRoZSBwYXRjaHdvcmsgbGlicmFyeSwgd2l0aG91dCBhIGxlZ2VuZApzaWxob3VldHRlX3Bsb3QgKyBwdXJpdHlfcGxvdCAmIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikKYGBgCgpXaGlsZSB0aGVyZSBkb2VzIG5vdCBhcHBlYXIgdG8gYmUgYSBzYWxpZW50IGRpZmZlcmVuY2UgYW1vbmcgc2lsaG91ZXR0ZSB3aWR0aCBkaXN0cmlidXRpb25zLCBpdCBkb2VzIGFwcGVhciB0aGF0IHB1cml0eSBpcyBoaWdoZXIgd2l0aCBhIGhpZ2hlciBuZWFyZXN0IG5laWdoYm9ycyBwYXJhbWV0ZXIuCgoKIyMjIyBTdGFiaWxpdHkKCk5leHQsIHdlJ2xsIGNhbGN1bGF0ZSBzdGFiaWxpdHkgb24gdGhlIGNsdXN0ZXJzIHVzaW5nIGByT3BlblNjUENBOjpjYWxjdWxhdGVfc3RhYmlsaXR5KClgLCBzcGVjaWZ5aW5nIHRoZSBzYW1lIHBhcmFtZXRlciB1c2VkIGZvciB0aGUgb3JpZ2luYWwgY2x1c3RlciBjYWxjdWxhdGlvbiBhdCBlYWNoIGl0ZXJhdGlvbi4gCgpgYGB7ciBjYWxjdWxhdGUgc3RhYmlsaXR5fQpzdGFiaWxpdHlfbGlzdCA8LSBjbHVzdGVyX3Jlc3VsdHNfbGlzdCB8PgogIHB1cnJyOjptYXAoCiAgICBcKGNsdXN0ZXJfZGYpIHsKICAgICAgbm4gPC0gdW5pcXVlKGNsdXN0ZXJfZGYkbm4pCgogICAgICAjIGNhbGN1bGF0ZSBzdGFiaWxpdHksIHBhc3NpbmcgaW4gdGhlIHBhcmFtZXRlciB2YWx1ZSB1c2VkIGZvciB0aGlzIGl0ZXJhdGlvbgogICAgICByT3BlblNjUENBOjpjYWxjdWxhdGVfc3RhYmlsaXR5KHBjYV9tYXRyaXgsIGNsdXN0ZXJfZGYsIG5uID0gbm4pCiAgICB9CiAgKQpgYGAKCldlJ2xsIGFnYWluIGNvbWJpbmUgdGhlIG91dHB1dCBvZiBgc3RhYmlsaXR5X2xpc3RgIGludG8gYSBzaW5nbGUgZGF0YSBmcmFtZSBhbmQgcGxvdCBgYXJpYCB2YWx1ZXMgYWNyb3NzIGBubmAgcGFyYW1ldGVyaXphdGlvbnMuCgoKYGBge3IgY29tYmluZSBwbG90IHN0YWJpbGl0eX0Kc3RhYmlsaXR5X2RmIDwtIGRwbHlyOjpiaW5kX3Jvd3Moc3RhYmlsaXR5X2xpc3QpCgpnZ3Bsb3Qoc3RhYmlsaXR5X2RmKSArCiAgYWVzKHggPSBhcy5mYWN0b3Iobm4pLCB5ID0gYXJpLCBmaWxsID0gYXMuZmFjdG9yKG5uKSkgKwogIGdlb21fYm94cGxvdCgpICsKICBzY2FsZV9maWxsX2JyZXdlcihwYWxldHRlID0gIlBhc3RlbDIiKSArCiAgbGFicygKICAgIHggPSAiTnVtYmVyIG9mIG5lYXJlc3QgbmVpZ2hib3JzIiwKICAgIHkgPSAiQWRqdXN0ZWQgUmFuZCBJbmRleCIKICApICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpCmBgYAoKSGVyZSwgd2Ugc2VlIHRoYXQgYSBuZWFyZXN0IG5laWdoYm9ycyB2YWx1ZSBvZiAyMCBvciAzMCBsZWFkcyB0byBtb3JlIHN0YWJsZSBjbHVzdGVyaW5nIHJlc3VsdHMgY29tcGFyZWQgdG8gMTAuIAoKCiMjIFZhcnlpbmcgbXVsdGlwbGUgY2x1c3RlcmluZyBwYXJhbWV0ZXJzCgpUaGUgcHJldmlvdXMgc2VjdGlvbiBkZW1vbnN0cmF0ZWQgaG93IHRvIGNhbGN1bGF0ZSBjbHVzdGVycyBhbmQgUUMgbWV0cmljcyB3aGVuIHZhcnlpbmcgb25lIHBhcmFtZXRlciwgYnV0IGl0IGlzIHBvc3NpYmxlIHRvIHZhcnkgbXVsdGlwbGUgcGFyYW1ldGVycyBhdCBvbmNlIHdpdGggYHJPcGVuU2NQQ0E6OnN3ZWVwX2NsdXN0ZXJzKClgLgpJbiB0aGlzIHNlY3Rpb24sIHdlJ2xsIHNob3cgYW4gb3ZlcnZpZXcgb2YgaG93IHlvdSBtaWdodCB3cml0ZSBjb2RlIHRvIHZhcnkgdHdvIHBhcmFtZXRlcnMgYXQgb25jZSAoaGVyZSwgbmVhcmVzdCBuZWlnaGJvcnMgYW5kIHJlc29sdXRpb24gYXMgZXhhbXBsZXMpIGFuZCB2aXN1YWxpemUgcmVzdWx0cy4KCgpgYGB7ciBzd2VlcCB0d28gcGFyYW1ldGVyc30KIyBEZWZpbmUgdmVjdG9ycyBvZiBwYXJhbWV0ZXJzIHRvIHZhcnkKbm5fdmFsdWVzIDwtIHNlcSgxMCwgMzAsIDEwKQpyZXNfdmFsdWVzIDwtIHNlcSg1LCAxNSwgNSkvMTAKCgpjbHVzdGVyX3Jlc3VsdHNfbGlzdCA8LSByT3BlblNjUENBOjpzd2VlcF9jbHVzdGVycygKICBwY2FfbWF0cml4LAogIG5uID0gbm5fdmFsdWVzLAogIHJlc29sdXRpb24gPSByZXNfdmFsdWVzCikKYGBgCgpUaGlzIHJlc3VsdGluZyBsaXN0IG5vdyBoYXMgOSBkaWZmZXJlbnQgY2x1c3RlcmluZyByZXN1bHRzLCBmb3IgZWFjaCBjb21iaW5hdGlvbiBvZiBgbm5fdmFsdWVzYCBhbmQgYHJlc192YWx1ZXNgOgoKCmBgYHtyIGxlbmd0aCBjbHVzdGVyX3Jlc3VsdHNfbGlzdCB0d28gcGFyYW1ldGVyc30KbGVuZ3RoKGNsdXN0ZXJfcmVzdWx0c19saXN0KQpgYGAKCgojIyMgVmlzdWFsaXplIGNsdXN0ZXJzCgpOZXh0LCB3ZSdsbCBpdGVyYXRlIG92ZXIgYGNsdXN0ZXJfcmVzdWx0c19saXN0YCB0byBwbG90IHRoZSBVTUFQcy4KVGhpcyB0aW1lLCB3ZSdsbCB1c2UgYHB1cnJyOjptYXAoKWAgYW5kIHB1bGwgb3V0IHBhcmFtZXRlcnMgZnJvbSBlYWNoIGl0ZXJhdGlvbidzIGBjbHVzdGVyX2RmYCB0byBmb3JtIHRoZSBVTUFQIHBhbmVsIHRpdGxlLgoKYGBge3IgcGxvdCBubiByZXMgdW1hcHMsIGZpZy5oZWlnaHQgPSAxNH0KdW1hcF9wbG90cyA8LSBjbHVzdGVyX3Jlc3VsdHNfbGlzdCB8PgogIHB1cnJyOjptYXAoCiAgICBcKGNsdXN0ZXJfZGYpIHsKCiAgICAgICMgQWRkIGEgY29sdW1uIHdpdGggY2x1c3RlciBhc3NpZ25tZW50cyB0byB1bWFwX2RmCiAgICAgIHVtYXBfZGZfcGxvdCA8LSB1bWFwX2RmIHw+CiAgICAgICAgZHBseXI6Om11dGF0ZShjbHVzdGVyID0gY2x1c3Rlcl9kZiRjbHVzdGVyKQoKICAgICAgIyBDcmVhdGUgYSB0aXRsZSBmb3IgdGhlIFVNQVAgd2l0aCBib3RoIHBhcmFtZXRlcnMKICAgICAgdW1hcF90aXRsZSA8LSBnbHVlOjpnbHVlKAogICAgICAgICJubjoge3VuaXF1ZShjbHVzdGVyX2RmJG5uKX07IHJlczoge3VuaXF1ZShjbHVzdGVyX2RmJHJlc29sdXRpb24pfSIKICAgICAgKQoKICAgICAgIyBQbG90IHRoZSBVTUFQLCBjb2xvcmVkIGJ5IHRoZSBuZXcgY2x1c3RlciB2YXJpYWJsZQogICAgICBnZ3Bsb3QodW1hcF9kZl9wbG90LCBhZXMoeCA9IFVNQVAxLCB5ID0gVU1BUDIsIGNvbG9yID0gY2x1c3RlcikpICsKICAgICAgICBnZW9tX3BvaW50KGFscGhhID0gMC42KSArCiAgICAgICAgbGFicyh0aXRsZSA9IHVtYXBfdGl0bGUpICsKICAgICAgICAjIFdlJ2xsIGFkZCBhIGNvdXBsZSBVTUFQLXNwZWNpZmljIHBsb3Qgc2V0dGluZ3MgCiAgICAgICAgY29vcmRfZXF1YWwoKSArCiAgICAgICAgdGhlbWUoCiAgICAgICAgICBheGlzLnRpY2tzID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgYXhpcy50ZXh0ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgbGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIKICAgICAgICApCiAgfQopCgojIFByaW50IHRoZSBwbG90cyB3aXRoIHBhdGNod29yazo6d3JhcF9wbG90cygpCnBhdGNod29yazo6d3JhcF9wbG90cyh1bWFwX3Bsb3RzKQpgYGAKCgoKIyMjIENhbGN1bGF0ZSBhbmQgdmlzdWFsaXplIFFDIG1ldHJpY3MKClRoaXMgc2VjdGlvbiBwcmVzZW50cyBvbmUgY29kaW5nIHN0cmF0ZWd5IHRvIGNhbGN1bGF0ZSBhbmQgdmlzdWFsaXplIHJlc3VsdHMgd2hlbiB2YXJ5aW5nIHR3byBjbHVzdGVyaW5nIHBhcmFtZXRlcnMuCkluIHBhcnRpY3VsYXIsIHdlIHVzZSBmYWNldGluZyB0byBoZWxwIGRpc3BsYXkgYWxsIGluZm9ybWF0aW9uIGluIG9uZSBwbG90LCBieSBwbGFjaW5nIG5lYXJlc3QgbmVpZ2hib3IgdmFsdWVzIG9uIHRoZSBYLWF4aXMgYW5kIGZhY2V0aW5nIGJ5IHJlc29sdXRpb24gdmFsdWVzLgpTaW5jZSBzaWxob3VldHRlIHdpZHRoIGFuZCBuZWlnaGhvcmJvb2QgcHVyaXR5IGNhbGN1bGF0aW9ucyB1c2luZyBnZW5lcmFsbHkgc2ltaWxhciBjb2RlLCB3ZSdsbCBqdXN0IHNob3cgbmVpZ2hib3Job29kIHB1cml0eSBoZXJlLgoKIyMjIyBOZWlnaGJvcmhvb2QgcHVyaXR5CgpGaXJzdCwgd2UnbGwgY2FsY3VsYXRlIG5laWdoYm9yaG9vZCBwdXJpdHkgYW5kIGNvbWJpbmUgcmVzdWx0cyBpbnRvIGEgc2luZ2xlIGRhdGEgZnJhbWUuCgpgYGB7ciBjYWxjdWxhdGUgcHVyaXR5IHR3byBwYXJhbWV0ZXJzfQpwdXJpdHlfZGYgPC0gY2x1c3Rlcl9yZXN1bHRzX2xpc3QgfD4KICBwdXJycjo6bWFwKAogICAgXChjbHVzdGVyX2RmKSB7CiAgICAgIHJPcGVuU2NQQ0E6OmNhbGN1bGF0ZV9wdXJpdHkocGNhX21hdHJpeCwgY2x1c3Rlcl9kZikKICAgIH0KICApIHw+CiAgZHBseXI6OmJpbmRfcm93cygpCmBgYAoKCldlJ2xsIGFkZCBhIGNvbHVtbiBgcmVzb2x1dGlvbl9sYWJlbGAgd2hpY2ggd2UnbGwgdXNlIHRvIGhhdmUgaW5mb3JtYXRpdmUgcGFuZWwgdGl0bGVzIGluIHRoZSBmYWNldGVkIGdncGxvdCB3ZSBtYWtlIG5leHQuCgpgYGB7ciBhZGQgcmVzb2x1dGlvbl9sYWJlbCBjb2x1bW59CnB1cml0eV9kZiA8LSBwdXJpdHlfZGYgfD4KICBkcGx5cjo6bXV0YXRlKHJlc29sdXRpb25fbGFiZWwgPSBnbHVlOjpnbHVlKCJSZXNvbHV0aW9uOiB7cmVzb2x1dGlvbn0iKSkKYGBgCgpgYGB7ciB2aXN1YWxpemUgcHVyaXR5IHR3byBwYXJhbWV0ZXJzfQpnZ3Bsb3QocHVyaXR5X2RmKSArCiAgYWVzKHggPSBhcy5mYWN0b3Iobm4pLCB5ID0gcHVyaXR5LCBmaWxsID0gYXMuZmFjdG9yKG5uKSkgKwogIGdlb21fYm94cGxvdCgpICsKICBzY2FsZV9maWxsX2JyZXdlcihwYWxldHRlID0gIlBhc3RlbDIiKSArCiAgIyBmYWNldCBieSByZXNvbHV0aW9uCiAgZmFjZXRfd3JhcCh2YXJzKHJlc29sdXRpb25fbGFiZWwpKSArCiAgbGFicygKICAgIHggPSAiTnVtYmVyIG9mIG5lYXJlc3QgbmVpZ2hib3JzIiwKICAgIHkgPSAiTmVpZ2hib3Job29kIHB1cml0eSIKICApICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpCmBgYAoKIyMjIFN0YWJpbGl0eQoKU2ltaWxhciB0byBhYm92ZSwgd2UnbGwgY2FsY3VsYXRlIHN0YWJpbGl0eSwgY29tYmluZSByZXN1bHRzIGludG8gYSBzaW5nbGUgZGF0YSBmcmFtZSwgYWRkIGEgYHJlc29sdXRpb25fbGFiZWxgIGNvbHVtbiB0byBzdXBwb3J0IHBsb3QgaW50ZXJwcmV0YXRpb24sIGFuZCBmaW5hbGx5IG1ha2Ugb3VyIHBsb3QuCgpgYGB7ciBjYWxjdWxhdGUgc3RhYmlsaXR5IHR3byBwYXJhbWV0ZXJzfQpzdGFiaWxpdHlfZGYgPC0gY2x1c3Rlcl9yZXN1bHRzX2xpc3QgfD4KICBwdXJycjo6bWFwKAogICAgXChjbHVzdGVyX2RmKSB7CgogICAgICAjIEV4dHJhY3QgcGFyYW1ldGVycyBmb3IgdGhpcyBjbHVzdGVyaW5nIHJlc3VsdAogICAgICBubiA8LSB1bmlxdWUoY2x1c3Rlcl9kZiRubikKICAgICAgcmVzb2x1dGlvbiA8LSB1bmlxdWUoY2x1c3Rlcl9kZiRyZXNvbHV0aW9uKQoKICAgICAgck9wZW5TY1BDQTo6Y2FsY3VsYXRlX3N0YWJpbGl0eSgKICAgICAgICBwY2FfbWF0cml4LAogICAgICAgIGNsdXN0ZXJfZGYsCiAgICAgICAgbm4gPSBubiwKICAgICAgICByZXNvbHV0aW9uID0gcmVzb2x1dGlvbgogICAgICApCiAgICB9CiAgKSB8PgogIGRwbHlyOjpiaW5kX3Jvd3MoKQoKc3RhYmlsaXR5X2RmIDwtIHN0YWJpbGl0eV9kZiB8PgogIGRwbHlyOjptdXRhdGUocmVzb2x1dGlvbl9sYWJlbCA9IGdsdWU6OmdsdWUoIlJlc29sdXRpb246IHtyZXNvbHV0aW9ufSIpKQoKYGBgCgoKYGBge3IgdmlzdWFsaXplIHN0YWJpbGl0eSB0d28gcGFyYW1ldGVyc30KZ2dwbG90KHN0YWJpbGl0eV9kZikgKwogIGFlcyh4ID0gYXMuZmFjdG9yKG5uKSwgeSA9IGFyaSwgZmlsbCA9IGFzLmZhY3RvcihubikpICsKICBnZW9tX2JveHBsb3QoKSArCiAgc2NhbGVfZmlsbF9icmV3ZXIocGFsZXR0ZSA9ICJQYXN0ZWwyIikgKwogICMgZmFjZXQgYnkgcmVzb2x1dGlvbgogIGZhY2V0X3dyYXAodmFycyhyZXNvbHV0aW9uX2xhYmVsKSkgKwogIGxhYnMoCiAgICB4ID0gIk51bWJlciBvZiBuZWFyZXN0IG5laWdoYm9ycyIsCiAgICB5ID0gIkFkanVzdGVkIFJhbmQgSW5kZXgiCiAgKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKQpgYGAKCgoKIyMgU2Vzc2lvbiBJbmZvCgpgYGB7ciBzZXNzaW9uIGluZm99CiMgcmVjb3JkIHRoZSB2ZXJzaW9ucyBvZiB0aGUgcGFja2FnZXMgdXNlZCBpbiB0aGlzIGFuYWx5c2lzIGFuZCBvdGhlciBlbnZpcm9ubWVudCBpbmZvcm1hdGlvbgpzZXNzaW9uSW5mbygpCmBgYAo=