Introduction

In this tutorial, we will investigate the relationship between two traits in a multivariate setting, specifically the co-evolution between ovipositor length in wasps and style length in the flowers they use to oviposit. We will treat the flower style length as part of the wasp’s niche, and therefore as a trait of the wasp’s biology (which is debatable but useful for this example). This simple two-trait system will help us explore multivariate phylogenetic comparative methods (PCMs).

The mvSLOUCH package will be our primary tool, as it allows for a flexible array of multiple evolutionary models. This package uses the PCMbase optimization algorithm that is further enhanced by PCMbaseRcpp, which significantly increases computation speed. While mvSLOUCH will run without PCMbaseRcpp, it will be considerably slower. Therefore, ensure that all mentioned packages are installed, even if they are not being explicitly called.

Key Concepts in Multivariate PCMs

Before diving into the analysis, it’s important to understand some key concepts:

  1. Brownian Motion (BM): A simple model where traits evolve by random drift with a constant rate
  2. Ornstein-Uhlenbeck (OU): A model that incorporates both random drift and selection toward an optimal value
  3. Selection Matrix (A): In multivariate models, this matrix describes the strength and direction of selection
  4. Rate Matrix (Σ): Describes the rate and correlation of random changes in traits

Loading Required Packages

First, let’s load all the required packages for our analysis:

# Load core phylogenetic packages
library(ape)        # For phylogenetic tree manipulation
library(slouch)     # For univariate OU models
library(mvSLOUCH)   # For multivariate OU models

# Load data manipulation packages
library(dplyr)      # For data manipulation
library(plyr)       # Additional data manipulation tools

# Additional packages for visualization
library(ggplot2)    # For enhanced plotting
library(phytools)   # For phylogenetic simulations and tools
library(ape)        # For basic tree manipulation

Loading and Exploring the Phylogenetic Tree

Next, we’ll load our phylogenetic tree data and examine its basic structure:

# Load phylogenetic tree
tree <- read.tree("waspTree.tre")
print(tree)

Phylogenetic tree with 39 tips and 38 internal nodes.

Tip labels:
  Blastophaga_intermedia, Blastophaga_malayana, Eupristina_verticillata, Waterstoniella_brevigena, Platyscapa_corneri, Platyscapa_fischeri, ...

Rooted; includes branch length(s).

The phylogeny contains 39 species with a total of 38 internal nodes. Let’s visualize this tree to better understand its structure:

# Plot the tree with smaller labels for better visibility
plot(tree, cex = 0.5, main = "Wasp Phylogenetic Tree")

Loading and Preparing Trait Data

Now we’ll import our trait data and prepare it for analysis:

# Import trait data and convert to matrix format
Traits <- read.csv("Traits.csv", row.names = 1) %>% as.matrix()
head(Traits)
                           PollOvip  PlantStyle
Blastophaga_intermedia   -2.9957323 -0.91629073
Blastophaga_malayana     -2.1202635 -0.91629073
Eupristina_verticillata  -0.1863296 -0.01005034
Waterstoniella_brevigena  0.1133287  0.44468582
Platyscapa_corneri       -0.3011051  0.12221763
Platyscapa_fischeri      -0.5798185 -0.10536052

The data is stored as a matrix with species names as row names. The first column represents the ovipositor length and the second column represents the style length. Let’s perform a quick summary of these traits:

# Basic statistics for our traits
data.frame(
  Trait = c("Ovipositor_Length", "Style_Length"),
  Mean = apply(Traits, 2, mean),
  SD = apply(Traits, 2, sd),
  Min = apply(Traits, 2, min),
  Max = apply(Traits, 2, max)
)

It’s crucial to verify that all species in our tree are represented in our trait data, and vice versa:

# Check if species names match between tree and trait data
all_match <- all(rownames(Traits) %in% tree$tip.label, 
                tree$tip.label %in% rownames(Traits))
all_match
[1] TRUE

In this case, all data have been pre-processed to match.

If you get a “FALSE” in the above logical operation, you should trim your tree and/or dataset to ensure they match each other. There are multiple ways to do this, with the packages geiger and treeplyr offering helpful functions for this purpose.

Visualizing Trait Data

Let’s visualize the relationship between our two traits:

# Create a more informative scatter plot
ggplot(data.frame(Traits), aes(x = PlantStyle, y = PollOvip)) +
  geom_point() +
  labs(
    x = "Ovipositor Length",
    y = "Style Length",
    title = "Relationship Between Wasp Ovipositor Length and Flower Style Length"
  ) +
  theme_bw()

The plot shows the raw correlation between traits without accounting for phylogenetic relationships. Our subsequent analyses will properly account for the shared evolutionary history.

Fitting Multivariate Evolutionary Models

With our traits and tree properly aligned, we can now fit various multivariate models to our data.

Multivariate Brownian Motion Model

Let’s start with the simplest model: a multivariate Brownian Motion (BM), which assumes traits evolve by random drift with a constant rate:

# Fit multivariate Brownian Motion model
bm_out <- BrownianMotionModel(tree, Traits)

You may notice that the Sxx parameter estimate, which corresponds to the rate matrix of stochastic evolution, is not symmetric. To get the correct result, we need to take the transpose cross-product of that entry:

# Calculate the correct rate matrix by transpose cross-product
tcrossprod(bm_out$ParamsInModel$Sxx)
           PollOvip PlantStyle
PollOvip   3.715832   1.675935
PlantStyle 1.675935   1.455431

This result is also stored in bm_out$ParamSummary. The rate matrix tells us about the variance in evolutionary rates for each trait (diagonal elements) and the covariance between traits (off-diagonal elements).

Multivariate Ornstein-Uhlenbeck Models

Now, let’s fit a more complex model, an Ornstein-Uhlenbeck (OU) model. The OU model incorporates both random drift and selection toward an optimal value. In mvSLOUCH, we can specify the shape of the multivariate rate matrix (Σ) and the selection strength matrix (A).

Let’s start with a full OU model:

# Fit full OU model with diagonal stochastic matrix
ou_out <- ouchModel(tree, Traits, Syytype = "Diagonal")

Let’s examine the key parameters:

# Selection strength matrix
ou_out$FinalFound$ParamSummary$expmtA
                PollOvip  PlantStyle
PollOvip    0.3636448237 0.623861612
PlantStyle -0.0008654139 0.003923432
# Rate matrix
ou_out$FinalFound$ParamSummary$StS
           PollOvip PlantStyle
PollOvip   3.315415   0.000000
PlantStyle 0.000000   2.477881
# Optimal trait values
ou_out$FinalFound$ParamSummary$mPsi.rotated
                reg.1
PollOvip   -0.2772541
PlantStyle -0.3379831

The selection strength matrix (A) indicates how strongly traits are pulled toward their optimal values. The rate matrix (Syy) describes the rate and pattern of random changes. The optimal values represent the trait values that selection favors.

Now, let’s fit additional models that specify different shapes for the A matrix:

# Fit OU model with diagonal A matrix (independent trait selection)
ou_out_d <- ouchModel(tree, Traits, 
                    Atype = "Diagonal",
                    Syytype = "Diagonal")

# Fit OU model with lower triangular A matrix
ou_out_l <- ouchModel(tree, Traits, 
                    Atype = "LowerTri",
                    Syytype = "Diagonal")

# Fit OU model with upper triangular A matrix
ou_out_u <- ouchModel(tree, Traits, 
                    Atype = "UpperTri",
                    Syytype = "Diagonal")

These models represent different assumptions about trait evolution:

  • Diagonal A matrix: Assumes that selection acts independently on each trait
  • Lower triangular A matrix: Assumes that trait 2 (style length) influences the trait 1 (ovipositor length)
  • Upper triangular A matrix: Assumes that evolution of trait 1 (ovipositor length) influences the evolution of trait 2 (style length)

Style optimum affects ovipositor optimum

Multivariate SLOUCH Model

Finally, let’s explore a regression model in which ovipositor length is modeled as a function of style length using the mvSLOUCH approach:

# Fit mvSLOUCH model (regression with OU process)
slouch_out <- mvslouchModel(tree, Traits, 
                          kY = 1, 
                          Atype = "DecomposablePositive",
                          Syytype = "Diagonal",
                          diagA = NULL)

Let’s visualize the relationship between traits with regression lines:

# Enhanced plot of the relationship with regression lines
trait_data <- data.frame(
  OvipositorLength = Traits[, 1],
  StyleLength = Traits[, 2]
)

# Calculate regression lines
opt_intercept <- slouch_out$FinalFound$ParamSummary$mPsi.rotated
opt_slope <- slouch_out$FinalFound$ParamSummary$optimal.regression
evol_slope <- slouch_out$FinalFound$ParamSummary$evolutionary.regression

# Create plot
ggplot(trait_data, aes(x = StyleLength, y = OvipositorLength)) +
  geom_point(size = 3, alpha = 0.7) +
  geom_abline(intercept = opt_intercept, slope = opt_slope, 
              color = "blue", size = 1.2) +
  geom_abline(intercept = opt_intercept, slope = evol_slope, 
              color = "red", linetype = "dashed", size = 1.2) +
  labs(
    x = "Style Length",
    y = "Ovipositor Length",
    title = "Relationship Between Traits with Evolutionary Regressions",
    subtitle = paste("Optimal regression (blue):", round(opt_slope, 3), 
                    "| Evolutionary regression (red):", round(evol_slope, 3))
  ) +
  theme_bw() +
  annotate("text", x = min(trait_data$StyleLength), 
          y = max(trait_data$OvipositorLength), 
          label = "Optimal regression", 
          color = "blue", hjust = 0, vjust = 1) +
  annotate("text", x = min(trait_data$StyleLength), 
          y = max(trait_data$OvipositorLength) - 0.5, 
          label = "Evolutionary regression", 
          color = "red", hjust = 0, vjust = 1)

Notice that the optimal regression (solid blue line) and the evolutionary regression (dashed red line) are very close to each other. This similarity suggests that the evolutionary process has had sufficient time to reach the optimal state, or that the strength of selection is strong relative to the phylogenetic signal.

Adding Phylogenetic Signal to Simulated Data

Let’s explore what happens when we add phylogenetic signal to our data:

# Set seed for reproducibility
set.seed(123)

# Create a copy of the original traits
Traits2 <- Traits

# Add phylogenetic signal to the first trait (ovipositor length)
Traits2[, 1] <- Traits2[, 1] + phytools::fastBM(tree, sig2 = 0.5)

# Fit mvSLOUCH model with data containing phylogenetic signal
slouch_out2 <- mvslouchModel(tree, Traits2, 
                           kY = 1, 
                           Atype = "DecomposablePositive",
                           Syytype = "Diagonal",
                           diagA = NULL)

trait_data2 <- data.frame(
  OvipositorLength = Traits2[, 1],
  StyleLength = Traits2[, 2]
)

opt_intercept <- slouch_out2$FinalFound$ParamSummary$mPsi.rotated
opt_slope <- slouch_out2$FinalFound$ParamSummary$optimal.regression
evol_slope <- slouch_out2$FinalFound$ParamSummary$evolutionary.regression


ggplot(trait_data2, aes(x = StyleLength, y = OvipositorLength)) +
  geom_point(size = 3, alpha = 0.7) +
  geom_abline(intercept = opt_intercept, slope = opt_slope, 
              color = "blue", size = 1.2) +
  geom_abline(intercept = opt_intercept, slope = evol_slope, 
              color = "red", linetype = "dashed", size = 1.2) +
  labs(
    x = "Style Length",
    y = "Ovipositor Length",
    title = "Relationship Between Traits with Evolutionary Regressions",
    subtitle = paste("Optimal regression (blue):", round(opt_slope, 3), 
                    "| Evolutionary regression (red):", round(evol_slope, 3))
  ) +
  theme_bw() +
  annotate("text", x = min(trait_data$StyleLength), 
          y = max(trait_data$OvipositorLength), 
          label = "Optimal regression", 
          color = "blue", hjust = 0, vjust = 1) +
  annotate("text", x = min(trait_data$StyleLength), 
          y = max(trait_data$OvipositorLength) - 0.5, 
          label = "Evolutionary regression", 
          color = "red", hjust = 0, vjust = 1)

The difference between optimal and evolutionary regression is more pronounced in the modified data. This illustrates how phylogenetic signal can affect our interpretation of trait relationships.

Model Comparison

Now, let’s compare all models using the penalized AICc (Akaike Information Criterion corrected for small sample sizes) to determine which model best fits our data:

# Create dataframe for model comparison
model_comparison <- data.frame(
  Model = c("BM", "OU_full", "OU_diag", "OU_lowertri", "OU_uppertri", "slOUCH"),
  AICc = c(bm_out$ParamSummary$aic.c,
          ou_out$FinalFound$ParamSummary$aic.c,
          ou_out_d$FinalFound$ParamSummary$aic.c,
          ou_out_l$FinalFound$ParamSummary$aic.c,
          ou_out_u$FinalFound$ParamSummary$aic.c,
          slouch_out$FinalFound$ParamSummary$aic.c)
)

# Calculate delta AICc and Akaike weights
model_comparison <- model_comparison %>%
  arrange(AICc) %>%
  mutate(
    Delta_AICc = AICc - min(AICc),
    Akaike_Weight = exp(-0.5 * Delta_AICc) / sum(exp(-0.5 * Delta_AICc))
  )

# Format table for better display
model_comparison

The model with the lowest AICc value provides the best fit to our data, taking into account both goodness-of-fit and model complexity. Akaike weights can be interpreted as the probability that a given model is the best among the set of models considered.

Based on the model comparison results, we can interpret the evolutionary process that shaped the relationship between wasp ovipositor length and flower style length. The best-fitting model suggests that these traits evolve primarily through random drift without strong directional selection.

Accounting for Measurement Error

In reality, trait measurements often contain some error. Let’s redo our analyses taking measurement error into consideration. We’ll assume a measurement error of about 10% of the total variation:

# Calculate approximate error values (10% of trait variance)
error_values <- c(
  var(Traits[, 1]) * 0.1,
  var(Traits[, 2]) * 0.1
)

# Fit Brownian Motion model with measurement error
bm_out_e <- BrownianMotionModel(tree, Traits, M.error = error_values)

# Compare BM models with and without error
bm_comparison <- data.frame(
  Model = c("BM without error", "BM with error"),
  AICc = c(bm_out$ParamSummary$aic.c, bm_out_e$ParamSummary$aic.c),
  LogLik = c(bm_out$ParamSummary$LogLik, bm_out_e$ParamSummary$LogLik)
)

bm_comparison

Let’s also fit OU models with measurement error:

# Fit OU model with measurement error
ou_out_e <- ouchModel(tree, Traits, 
                    Syytype = "Diagonal", 
                    M.error = error_values)

Adding measurement error improves our models. This highlights the importance of accounting for measurement error in phylogenetic comparative analyses. Now try adding error to all other models and observe if your conclusions change.

Exercise Questions

  1. How would you interpret the biological meaning of the selection strength matrix (A) in the OU models?

  2. What does it mean when the optimal regression and evolutionary regression are similar or different?

  3. Try fitting models where you switch which trait is dependent and which is independent in the SLOUCH model. How does this change your interpretation?

  4. How might you incorporate additional traits or environmental variables into these analyses?

  5. What are the limitations of these multivariate PCM approaches?

References

  1. Bartoszek, K., Pienaar, J., Mostad, P., Andersson, S., & Hansen, T. F. (2012). A phylogenetic comparative method for studying multivariate adaptation. Journal of Theoretical Biology, 314, 204-215.

  2. Hansen, T. F., Pienaar, J., & Orzack, S. H. (2008). A comparative method for studying adaptation to a randomly evolving environment. Evolution, 62(8), 1965-1977.

  3. Mitov, V., & Bartoszek, K. (2021). PCMBase: A framework for phylogenetic comparative methods. Methods in Ecology and Evolution, 12(11), 2314-2329.

LS0tCnRpdGxlOiAiTXVsdGl2YXJpYXRlIFBoeWxvZ2VuZXRpYyBDb21wYXJhdGl2ZSBNZXRob2RzIgphdXRob3I6ICJFdm9sdXRpb25hcnkgUXVhbnRpdGF0aXZlIEdlbmV0aWNzIFdvcmtzaG9wIgpkYXRlOiAiYHIgU3lzLkRhdGUoKWAiCm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgdG9jOiB5ZXMKICAgIHRvY19mbG9hdDogeWVzCiAgICB0b2NfZGVwdGg6IDMKICAgIHRoZW1lOiBjb3NtbwogICAgaGlnaGxpZ2h0OiB0YW5nbwogICAgY29kZV9mb2xkaW5nOiBzaG93CiAgICBmaWdfd2lkdGg6IDgKICAgIGZpZ19oZWlnaHQ6IDYKICAgIGRmX3ByaW50OiBwYWdlZAogIHBkZl9kb2N1bWVudDoKICAgIHRvYzogeWVzCiAgICB0b2NfZGVwdGg6IDMKICBodG1sX2RvY3VtZW50OgogICAgdG9jOiB5ZXMKICAgIHRvY19kZXB0aDogJzMnCiAgICBkZl9wcmludDogcGFnZWQKLS0tCgpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KAogIGVjaG8gPSBUUlVFLAogIHdhcm5pbmcgPSBGQUxTRSwKICBtZXNzYWdlID0gRkFMU0UsCiAgY29tbWVudCA9ICIjPiIsCiAgZmlnLmFsaWduID0gImNlbnRlciIsCiAgb3V0LndpZHRoID0gIjgwJSIKKQojIFNldCBzZWVkIGZvciByZXByb2R1Y2liaWxpdHkKc2V0LnNlZWQoNDIpCmBgYAoKIyMgSW50cm9kdWN0aW9uCgpJbiB0aGlzIHR1dG9yaWFsLCB3ZSB3aWxsIGludmVzdGlnYXRlIHRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiB0d28gdHJhaXRzIGluIGEgbXVsdGl2YXJpYXRlIHNldHRpbmcsIHNwZWNpZmljYWxseSB0aGUgY28tZXZvbHV0aW9uIGJldHdlZW4gb3ZpcG9zaXRvciBsZW5ndGggaW4gd2FzcHMgYW5kIHN0eWxlIGxlbmd0aCBpbiB0aGUgZmxvd2VycyB0aGV5IHVzZSB0byBvdmlwb3NpdC4gV2Ugd2lsbCB0cmVhdCB0aGUgZmxvd2VyIHN0eWxlIGxlbmd0aCBhcyBwYXJ0IG9mIHRoZSB3YXNwJ3MgbmljaGUsIGFuZCB0aGVyZWZvcmUgYXMgYSB0cmFpdCBvZiB0aGUgd2FzcCdzIGJpb2xvZ3kgKHdoaWNoIGlzIGRlYmF0YWJsZSBidXQgdXNlZnVsIGZvciB0aGlzIGV4YW1wbGUpLiBUaGlzIHNpbXBsZSB0d28tdHJhaXQgc3lzdGVtIHdpbGwgaGVscCB1cyBleHBsb3JlIG11bHRpdmFyaWF0ZSBwaHlsb2dlbmV0aWMgY29tcGFyYXRpdmUgbWV0aG9kcyAoUENNcykuCgpUaGUgKiptdlNMT1VDSCoqIHBhY2thZ2Ugd2lsbCBiZSBvdXIgcHJpbWFyeSB0b29sLCBhcyBpdCBhbGxvd3MgZm9yIGEgZmxleGlibGUgYXJyYXkgb2YgbXVsdGlwbGUgZXZvbHV0aW9uYXJ5IG1vZGVscy4gVGhpcyBwYWNrYWdlIHVzZXMgdGhlICoqUENNYmFzZSoqIG9wdGltaXphdGlvbiBhbGdvcml0aG0gdGhhdCBpcyBmdXJ0aGVyIGVuaGFuY2VkIGJ5ICoqUENNYmFzZVJjcHAqKiwgd2hpY2ggc2lnbmlmaWNhbnRseSBpbmNyZWFzZXMgY29tcHV0YXRpb24gc3BlZWQuIFdoaWxlICoqbXZTTE9VQ0gqKiB3aWxsIHJ1biB3aXRob3V0ICoqUENNYmFzZVJjcHAqKiwgaXQgd2lsbCBiZSBjb25zaWRlcmFibHkgc2xvd2VyLiBUaGVyZWZvcmUsIGVuc3VyZSB0aGF0IGFsbCBtZW50aW9uZWQgcGFja2FnZXMgYXJlIGluc3RhbGxlZCwgZXZlbiBpZiB0aGV5IGFyZSBub3QgYmVpbmcgZXhwbGljaXRseSBjYWxsZWQuCgojIyMgS2V5IENvbmNlcHRzIGluIE11bHRpdmFyaWF0ZSBQQ01zCgpCZWZvcmUgZGl2aW5nIGludG8gdGhlIGFuYWx5c2lzLCBpdCdzIGltcG9ydGFudCB0byB1bmRlcnN0YW5kIHNvbWUga2V5IGNvbmNlcHRzOgoKMS4gKipCcm93bmlhbiBNb3Rpb24gKEJNKSoqOiBBIHNpbXBsZSBtb2RlbCB3aGVyZSB0cmFpdHMgZXZvbHZlIGJ5IHJhbmRvbSBkcmlmdCB3aXRoIGEgY29uc3RhbnQgcmF0ZQoyLiAqKk9ybnN0ZWluLVVobGVuYmVjayAoT1UpKio6IEEgbW9kZWwgdGhhdCBpbmNvcnBvcmF0ZXMgYm90aCByYW5kb20gZHJpZnQgYW5kIHNlbGVjdGlvbiB0b3dhcmQgYW4gb3B0aW1hbCB2YWx1ZQozLiAqKlNlbGVjdGlvbiBNYXRyaXggKEEpKio6IEluIG11bHRpdmFyaWF0ZSBtb2RlbHMsIHRoaXMgbWF0cml4IGRlc2NyaWJlcyB0aGUgc3RyZW5ndGggYW5kIGRpcmVjdGlvbiBvZiBzZWxlY3Rpb24KNC4gKipSYXRlIE1hdHJpeCAozqMpKio6IERlc2NyaWJlcyB0aGUgcmF0ZSBhbmQgY29ycmVsYXRpb24gb2YgcmFuZG9tIGNoYW5nZXMgaW4gdHJhaXRzCgojIyBMb2FkaW5nIFJlcXVpcmVkIFBhY2thZ2VzCgpGaXJzdCwgbGV0J3MgbG9hZCBhbGwgdGhlIHJlcXVpcmVkIHBhY2thZ2VzIGZvciBvdXIgYW5hbHlzaXM6CgpgYGB7ciBsb2FkLXBhY2thZ2VzfQojIExvYWQgY29yZSBwaHlsb2dlbmV0aWMgcGFja2FnZXMKbGlicmFyeShhcGUpICAgICAgICAjIEZvciBwaHlsb2dlbmV0aWMgdHJlZSBtYW5pcHVsYXRpb24KbGlicmFyeShzbG91Y2gpICAgICAjIEZvciB1bml2YXJpYXRlIE9VIG1vZGVscwpsaWJyYXJ5KG12U0xPVUNIKSAgICMgRm9yIG11bHRpdmFyaWF0ZSBPVSBtb2RlbHMKCiMgTG9hZCBkYXRhIG1hbmlwdWxhdGlvbiBwYWNrYWdlcwpsaWJyYXJ5KGRwbHlyKSAgICAgICMgRm9yIGRhdGEgbWFuaXB1bGF0aW9uCmxpYnJhcnkocGx5cikgICAgICAgIyBBZGRpdGlvbmFsIGRhdGEgbWFuaXB1bGF0aW9uIHRvb2xzCgojIEFkZGl0aW9uYWwgcGFja2FnZXMgZm9yIHZpc3VhbGl6YXRpb24KbGlicmFyeShnZ3Bsb3QyKSAgICAjIEZvciBlbmhhbmNlZCBwbG90dGluZwpsaWJyYXJ5KHBoeXRvb2xzKSAgICMgRm9yIHBoeWxvZ2VuZXRpYyBzaW11bGF0aW9ucyBhbmQgdG9vbHMKbGlicmFyeShhcGUpICAgICAgICAjIEZvciBiYXNpYyB0cmVlIG1hbmlwdWxhdGlvbgpgYGAKCiMjIExvYWRpbmcgYW5kIEV4cGxvcmluZyB0aGUgUGh5bG9nZW5ldGljIFRyZWUKCk5leHQsIHdlJ2xsIGxvYWQgb3VyIHBoeWxvZ2VuZXRpYyB0cmVlIGRhdGEgYW5kIGV4YW1pbmUgaXRzIGJhc2ljIHN0cnVjdHVyZToKCmBgYHtyIGxvYWQtdHJlZX0KIyBMb2FkIHBoeWxvZ2VuZXRpYyB0cmVlCnRyZWUgPC0gcmVhZC50cmVlKCJ3YXNwVHJlZS50cmUiKQpwcmludCh0cmVlKQpgYGAKClRoZSBwaHlsb2dlbnkgY29udGFpbnMgYHIgbGVuZ3RoKHRyZWUkdGlwLmxhYmVsKWAgc3BlY2llcyB3aXRoIGEgdG90YWwgb2YgYHIgdHJlZSRObm9kZWAgaW50ZXJuYWwgbm9kZXMuIExldCdzIHZpc3VhbGl6ZSB0aGlzIHRyZWUgdG8gYmV0dGVyIHVuZGVyc3RhbmQgaXRzIHN0cnVjdHVyZToKCmBgYHtyIHBsb3QtdHJlZX0KIyBQbG90IHRoZSB0cmVlIHdpdGggc21hbGxlciBsYWJlbHMgZm9yIGJldHRlciB2aXNpYmlsaXR5CnBsb3QodHJlZSwgY2V4ID0gMC41LCBtYWluID0gIldhc3AgUGh5bG9nZW5ldGljIFRyZWUiKQpgYGAKCiMjIExvYWRpbmcgYW5kIFByZXBhcmluZyBUcmFpdCBEYXRhCgpOb3cgd2UnbGwgaW1wb3J0IG91ciB0cmFpdCBkYXRhIGFuZCBwcmVwYXJlIGl0IGZvciBhbmFseXNpczoKCmBgYHtyIGxvYWQtdHJhaXRzfQojIEltcG9ydCB0cmFpdCBkYXRhIGFuZCBjb252ZXJ0IHRvIG1hdHJpeCBmb3JtYXQKVHJhaXRzIDwtIHJlYWQuY3N2KCJUcmFpdHMuY3N2Iiwgcm93Lm5hbWVzID0gMSkgJT4lIGFzLm1hdHJpeCgpCmhlYWQoVHJhaXRzKQpgYGAKClRoZSBkYXRhIGlzIHN0b3JlZCBhcyBhIG1hdHJpeCB3aXRoIHNwZWNpZXMgbmFtZXMgYXMgcm93IG5hbWVzLiBUaGUgZmlyc3QgY29sdW1uIHJlcHJlc2VudHMgdGhlIG92aXBvc2l0b3IgbGVuZ3RoIGFuZCB0aGUgc2Vjb25kIGNvbHVtbiByZXByZXNlbnRzIHRoZSBzdHlsZSBsZW5ndGguIExldCdzIHBlcmZvcm0gYSBxdWljayBzdW1tYXJ5IG9mIHRoZXNlIHRyYWl0czoKCmBgYHtyIHRyYWl0LXN1bW1hcnl9CiMgQmFzaWMgc3RhdGlzdGljcyBmb3Igb3VyIHRyYWl0cwpkYXRhLmZyYW1lKAogIFRyYWl0ID0gYygiT3ZpcG9zaXRvcl9MZW5ndGgiLCAiU3R5bGVfTGVuZ3RoIiksCiAgTWVhbiA9IGFwcGx5KFRyYWl0cywgMiwgbWVhbiksCiAgU0QgPSBhcHBseShUcmFpdHMsIDIsIHNkKSwKICBNaW4gPSBhcHBseShUcmFpdHMsIDIsIG1pbiksCiAgTWF4ID0gYXBwbHkoVHJhaXRzLCAyLCBtYXgpCikKYGBgCgpJdCdzIGNydWNpYWwgdG8gdmVyaWZ5IHRoYXQgYWxsIHNwZWNpZXMgaW4gb3VyIHRyZWUgYXJlIHJlcHJlc2VudGVkIGluIG91ciB0cmFpdCBkYXRhLCBhbmQgdmljZSB2ZXJzYToKCmBgYHtyIGNoZWNrLXNwZWNpZXMtbWF0Y2h9CiMgQ2hlY2sgaWYgc3BlY2llcyBuYW1lcyBtYXRjaCBiZXR3ZWVuIHRyZWUgYW5kIHRyYWl0IGRhdGEKYWxsX21hdGNoIDwtIGFsbChyb3duYW1lcyhUcmFpdHMpICVpbiUgdHJlZSR0aXAubGFiZWwsIAogICAgICAgICAgICAgICAgdHJlZSR0aXAubGFiZWwgJWluJSByb3duYW1lcyhUcmFpdHMpKQphbGxfbWF0Y2gKYGBgCgpgciBpZihhbGxfbWF0Y2gpeyJJbiB0aGlzIGNhc2UsIGFsbCBkYXRhIGhhdmUgYmVlbiBwcmUtcHJvY2Vzc2VkIHRvIG1hdGNoLiJ9IGVsc2UgeyJXQVJOSU5HOiBUaGUgc3BlY2llcyBpbiB0aGUgdHJlZSBhbmQgdHJhaXQgZGF0YSBkbyBub3QgbWF0Y2ggY29tcGxldGVseSEifWAKCklmIHlvdSBnZXQgYSAiRkFMU0UiIGluIHRoZSBhYm92ZSBsb2dpY2FsIG9wZXJhdGlvbiwgeW91IHNob3VsZCB0cmltIHlvdXIgdHJlZSBhbmQvb3IgZGF0YXNldCB0byBlbnN1cmUgdGhleSBtYXRjaCBlYWNoIG90aGVyLiBUaGVyZSBhcmUgbXVsdGlwbGUgd2F5cyB0byBkbyB0aGlzLCB3aXRoIHRoZSBwYWNrYWdlcyAqKmdlaWdlcioqIGFuZCAqKnRyZWVwbHlyKiogb2ZmZXJpbmcgaGVscGZ1bCBmdW5jdGlvbnMgZm9yIHRoaXMgcHVycG9zZS4KCiMjIyBWaXN1YWxpemluZyBUcmFpdCBEYXRhCgpMZXQncyB2aXN1YWxpemUgdGhlIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIG91ciB0d28gdHJhaXRzOgoKYGBge3IgcGxvdC10cmFpdHMsIGZpZy5oZWlnaHQ9NSwgZmlnLndpZHRoPTd9CiMgQ3JlYXRlIGEgbW9yZSBpbmZvcm1hdGl2ZSBzY2F0dGVyIHBsb3QKZ2dwbG90KGRhdGEuZnJhbWUoVHJhaXRzKSwgYWVzKHggPSBQbGFudFN0eWxlLCB5ID0gUG9sbE92aXApKSArCiAgZ2VvbV9wb2ludCgpICsKICBsYWJzKAogICAgeCA9ICJPdmlwb3NpdG9yIExlbmd0aCIsCiAgICB5ID0gIlN0eWxlIExlbmd0aCIsCiAgICB0aXRsZSA9ICJSZWxhdGlvbnNoaXAgQmV0d2VlbiBXYXNwIE92aXBvc2l0b3IgTGVuZ3RoIGFuZCBGbG93ZXIgU3R5bGUgTGVuZ3RoIgogICkgKwogIHRoZW1lX2J3KCkKYGBgCgpUaGUgcGxvdCBzaG93cyB0aGUgcmF3IGNvcnJlbGF0aW9uIGJldHdlZW4gdHJhaXRzIHdpdGhvdXQgYWNjb3VudGluZyBmb3IgcGh5bG9nZW5ldGljIHJlbGF0aW9uc2hpcHMuIE91ciBzdWJzZXF1ZW50IGFuYWx5c2VzIHdpbGwgcHJvcGVybHkgYWNjb3VudCBmb3IgdGhlIHNoYXJlZCBldm9sdXRpb25hcnkgaGlzdG9yeS4KCiMjIEZpdHRpbmcgTXVsdGl2YXJpYXRlIEV2b2x1dGlvbmFyeSBNb2RlbHMKCldpdGggb3VyIHRyYWl0cyBhbmQgdHJlZSBwcm9wZXJseSBhbGlnbmVkLCB3ZSBjYW4gbm93IGZpdCB2YXJpb3VzIG11bHRpdmFyaWF0ZSBtb2RlbHMgdG8gb3VyIGRhdGEuCgojIyMgTXVsdGl2YXJpYXRlIEJyb3duaWFuIE1vdGlvbiBNb2RlbAoKTGV0J3Mgc3RhcnQgd2l0aCB0aGUgc2ltcGxlc3QgbW9kZWw6IGEgbXVsdGl2YXJpYXRlIEJyb3duaWFuIE1vdGlvbiAoQk0pLCB3aGljaCBhc3N1bWVzIHRyYWl0cyBldm9sdmUgYnkgcmFuZG9tIGRyaWZ0IHdpdGggYSBjb25zdGFudCByYXRlOgoKYGBge3IgYm0tbW9kZWx9CiMgRml0IG11bHRpdmFyaWF0ZSBCcm93bmlhbiBNb3Rpb24gbW9kZWwKYm1fb3V0IDwtIEJyb3duaWFuTW90aW9uTW9kZWwodHJlZSwgVHJhaXRzKQpgYGAKCllvdSBtYXkgbm90aWNlIHRoYXQgdGhlIFN4eCBwYXJhbWV0ZXIgZXN0aW1hdGUsIHdoaWNoIGNvcnJlc3BvbmRzIHRvIHRoZSByYXRlIG1hdHJpeCBvZiBzdG9jaGFzdGljIGV2b2x1dGlvbiwgaXMgbm90IHN5bW1ldHJpYy4gVG8gZ2V0IHRoZSBjb3JyZWN0IHJlc3VsdCwgd2UgbmVlZCB0byB0YWtlIHRoZSB0cmFuc3Bvc2UgY3Jvc3MtcHJvZHVjdCBvZiB0aGF0IGVudHJ5OgoKYGBge3IgYm0tcmF0ZS1tYXRyaXh9CiMgQ2FsY3VsYXRlIHRoZSBjb3JyZWN0IHJhdGUgbWF0cml4IGJ5IHRyYW5zcG9zZSBjcm9zcy1wcm9kdWN0CnRjcm9zc3Byb2QoYm1fb3V0JFBhcmFtc0luTW9kZWwkU3h4KQpgYGAKClRoaXMgcmVzdWx0IGlzIGFsc28gc3RvcmVkIGluIGBibV9vdXQkUGFyYW1TdW1tYXJ5YC4gVGhlIHJhdGUgbWF0cml4IHRlbGxzIHVzIGFib3V0IHRoZSB2YXJpYW5jZSBpbiBldm9sdXRpb25hcnkgcmF0ZXMgZm9yIGVhY2ggdHJhaXQgKGRpYWdvbmFsIGVsZW1lbnRzKSBhbmQgdGhlIGNvdmFyaWFuY2UgYmV0d2VlbiB0cmFpdHMgKG9mZi1kaWFnb25hbCBlbGVtZW50cykuCgojIyMgTXVsdGl2YXJpYXRlIE9ybnN0ZWluLVVobGVuYmVjayBNb2RlbHMKCk5vdywgbGV0J3MgZml0IGEgbW9yZSBjb21wbGV4IG1vZGVsLCBhbiBPcm5zdGVpbi1VaGxlbmJlY2sgKE9VKSBtb2RlbC4gVGhlIE9VIG1vZGVsIGluY29ycG9yYXRlcyBib3RoIHJhbmRvbSBkcmlmdCBhbmQgc2VsZWN0aW9uIHRvd2FyZCBhbiBvcHRpbWFsIHZhbHVlLiBJbiBtdlNMT1VDSCwgd2UgY2FuIHNwZWNpZnkgdGhlIHNoYXBlIG9mIHRoZSBtdWx0aXZhcmlhdGUgcmF0ZSBtYXRyaXggKM6jKSBhbmQgdGhlIHNlbGVjdGlvbiBzdHJlbmd0aCBtYXRyaXggKEEpLgoKTGV0J3Mgc3RhcnQgd2l0aCBhIGZ1bGwgT1UgbW9kZWw6CgpgYGB7ciBvdS1mdWxsLW1vZGVsfQojIEZpdCBmdWxsIE9VIG1vZGVsIHdpdGggZGlhZ29uYWwgc3RvY2hhc3RpYyBtYXRyaXgKb3Vfb3V0IDwtIG91Y2hNb2RlbCh0cmVlLCBUcmFpdHMsIFN5eXR5cGUgPSAiRGlhZ29uYWwiKQpgYGAKCkxldCdzIGV4YW1pbmUgdGhlIGtleSBwYXJhbWV0ZXJzOgoKYGBge3Igb3UtcGFyYW1ldGVyc30KIyBTZWxlY3Rpb24gc3RyZW5ndGggbWF0cml4Cm91X291dCRGaW5hbEZvdW5kJFBhcmFtU3VtbWFyeSRleHBtdEEKIyBSYXRlIG1hdHJpeApvdV9vdXQkRmluYWxGb3VuZCRQYXJhbVN1bW1hcnkkU3RTCiMgT3B0aW1hbCB0cmFpdCB2YWx1ZXMKb3Vfb3V0JEZpbmFsRm91bmQkUGFyYW1TdW1tYXJ5JG1Qc2kucm90YXRlZApgYGAKClRoZSBzZWxlY3Rpb24gc3RyZW5ndGggbWF0cml4IChBKSBpbmRpY2F0ZXMgaG93IHN0cm9uZ2x5IHRyYWl0cyBhcmUgcHVsbGVkIHRvd2FyZCB0aGVpciBvcHRpbWFsIHZhbHVlcy4gVGhlIHJhdGUgbWF0cml4IChTeXkpIGRlc2NyaWJlcyB0aGUgcmF0ZSBhbmQgcGF0dGVybiBvZiByYW5kb20gY2hhbmdlcy4gVGhlIG9wdGltYWwgdmFsdWVzIHJlcHJlc2VudCB0aGUgdHJhaXQgdmFsdWVzIHRoYXQgc2VsZWN0aW9uIGZhdm9ycy4KCk5vdywgbGV0J3MgZml0IGFkZGl0aW9uYWwgbW9kZWxzIHRoYXQgc3BlY2lmeSBkaWZmZXJlbnQgc2hhcGVzIGZvciB0aGUgQSBtYXRyaXg6CgpgYGB7ciBvdS12YXJpYW50c30KIyBGaXQgT1UgbW9kZWwgd2l0aCBkaWFnb25hbCBBIG1hdHJpeCAoaW5kZXBlbmRlbnQgdHJhaXQgc2VsZWN0aW9uKQpvdV9vdXRfZCA8LSBvdWNoTW9kZWwodHJlZSwgVHJhaXRzLCAKICAgICAgICAgICAgICAgICAgICBBdHlwZSA9ICJEaWFnb25hbCIsCiAgICAgICAgICAgICAgICAgICAgU3l5dHlwZSA9ICJEaWFnb25hbCIpCgojIEZpdCBPVSBtb2RlbCB3aXRoIGxvd2VyIHRyaWFuZ3VsYXIgQSBtYXRyaXgKb3Vfb3V0X2wgPC0gb3VjaE1vZGVsKHRyZWUsIFRyYWl0cywgCiAgICAgICAgICAgICAgICAgICAgQXR5cGUgPSAiTG93ZXJUcmkiLAogICAgICAgICAgICAgICAgICAgIFN5eXR5cGUgPSAiRGlhZ29uYWwiKQoKIyBGaXQgT1UgbW9kZWwgd2l0aCB1cHBlciB0cmlhbmd1bGFyIEEgbWF0cml4Cm91X291dF91IDwtIG91Y2hNb2RlbCh0cmVlLCBUcmFpdHMsIAogICAgICAgICAgICAgICAgICAgIEF0eXBlID0gIlVwcGVyVHJpIiwKICAgICAgICAgICAgICAgICAgICBTeXl0eXBlID0gIkRpYWdvbmFsIikKYGBgCgpUaGVzZSBtb2RlbHMgcmVwcmVzZW50IGRpZmZlcmVudCBhc3N1bXB0aW9ucyBhYm91dCB0cmFpdCBldm9sdXRpb246CgotICoqRGlhZ29uYWwgQSBtYXRyaXgqKjogQXNzdW1lcyB0aGF0IHNlbGVjdGlvbiBhY3RzIGluZGVwZW5kZW50bHkgb24gZWFjaCB0cmFpdAotICoqTG93ZXIgdHJpYW5ndWxhciBBIG1hdHJpeCoqOiBBc3N1bWVzIHRoYXQgdHJhaXQgMiAoc3R5bGUgbGVuZ3RoKSBpbmZsdWVuY2VzIHRoZSB0cmFpdCAxIChvdmlwb3NpdG9yIGxlbmd0aCkKLSAqKlVwcGVyIHRyaWFuZ3VsYXIgQSBtYXRyaXgqKjogQXNzdW1lcyB0aGF0IGV2b2x1dGlvbiBvZiB0cmFpdCAxIChvdmlwb3NpdG9yIGxlbmd0aCkgaW5mbHVlbmNlcyB0aGUgZXZvbHV0aW9uIG9mIHRyYWl0IDIgKHN0eWxlIGxlbmd0aCkKClN0eWxlIG9wdGltdW0gYWZmZWN0cwpvdmlwb3NpdG9yIG9wdGltdW0KCiMjIyBNdWx0aXZhcmlhdGUgU0xPVUNIIE1vZGVsCgpGaW5hbGx5LCBsZXQncyBleHBsb3JlIGEgcmVncmVzc2lvbiBtb2RlbCBpbiB3aGljaCBvdmlwb3NpdG9yIGxlbmd0aCBpcyBtb2RlbGVkIGFzIGEgZnVuY3Rpb24gb2Ygc3R5bGUgbGVuZ3RoIHVzaW5nIHRoZSBtdlNMT1VDSCBhcHByb2FjaDoKCmBgYHtyIHNsb3VjaC1tb2RlbH0KIyBGaXQgbXZTTE9VQ0ggbW9kZWwgKHJlZ3Jlc3Npb24gd2l0aCBPVSBwcm9jZXNzKQpzbG91Y2hfb3V0IDwtIG12c2xvdWNoTW9kZWwodHJlZSwgVHJhaXRzLCAKICAgICAgICAgICAgICAgICAgICAgICAgICBrWSA9IDEsIAogICAgICAgICAgICAgICAgICAgICAgICAgIEF0eXBlID0gIkRlY29tcG9zYWJsZVBvc2l0aXZlIiwKICAgICAgICAgICAgICAgICAgICAgICAgICBTeXl0eXBlID0gIkRpYWdvbmFsIiwKICAgICAgICAgICAgICAgICAgICAgICAgICBkaWFnQSA9IE5VTEwpCmBgYAoKTGV0J3MgdmlzdWFsaXplIHRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiB0cmFpdHMgd2l0aCByZWdyZXNzaW9uIGxpbmVzOgoKYGBge3Igc2xvdWNoLXBsb3QsIGZpZy5oZWlnaHQ9NiwgZmlnLndpZHRoPTh9CiMgRW5oYW5jZWQgcGxvdCBvZiB0aGUgcmVsYXRpb25zaGlwIHdpdGggcmVncmVzc2lvbiBsaW5lcwp0cmFpdF9kYXRhIDwtIGRhdGEuZnJhbWUoCiAgT3ZpcG9zaXRvckxlbmd0aCA9IFRyYWl0c1ssIDFdLAogIFN0eWxlTGVuZ3RoID0gVHJhaXRzWywgMl0KKQoKIyBDYWxjdWxhdGUgcmVncmVzc2lvbiBsaW5lcwpvcHRfaW50ZXJjZXB0IDwtIHNsb3VjaF9vdXQkRmluYWxGb3VuZCRQYXJhbVN1bW1hcnkkbVBzaS5yb3RhdGVkCm9wdF9zbG9wZSA8LSBzbG91Y2hfb3V0JEZpbmFsRm91bmQkUGFyYW1TdW1tYXJ5JG9wdGltYWwucmVncmVzc2lvbgpldm9sX3Nsb3BlIDwtIHNsb3VjaF9vdXQkRmluYWxGb3VuZCRQYXJhbVN1bW1hcnkkZXZvbHV0aW9uYXJ5LnJlZ3Jlc3Npb24KCiMgQ3JlYXRlIHBsb3QKZ2dwbG90KHRyYWl0X2RhdGEsIGFlcyh4ID0gU3R5bGVMZW5ndGgsIHkgPSBPdmlwb3NpdG9yTGVuZ3RoKSkgKwogIGdlb21fcG9pbnQoc2l6ZSA9IDMsIGFscGhhID0gMC43KSArCiAgZ2VvbV9hYmxpbmUoaW50ZXJjZXB0ID0gb3B0X2ludGVyY2VwdCwgc2xvcGUgPSBvcHRfc2xvcGUsIAogICAgICAgICAgICAgIGNvbG9yID0gImJsdWUiLCBzaXplID0gMS4yKSArCiAgZ2VvbV9hYmxpbmUoaW50ZXJjZXB0ID0gb3B0X2ludGVyY2VwdCwgc2xvcGUgPSBldm9sX3Nsb3BlLCAKICAgICAgICAgICAgICBjb2xvciA9ICJyZWQiLCBsaW5ldHlwZSA9ICJkYXNoZWQiLCBzaXplID0gMS4yKSArCiAgbGFicygKICAgIHggPSAiU3R5bGUgTGVuZ3RoIiwKICAgIHkgPSAiT3ZpcG9zaXRvciBMZW5ndGgiLAogICAgdGl0bGUgPSAiUmVsYXRpb25zaGlwIEJldHdlZW4gVHJhaXRzIHdpdGggRXZvbHV0aW9uYXJ5IFJlZ3Jlc3Npb25zIiwKICAgIHN1YnRpdGxlID0gcGFzdGUoIk9wdGltYWwgcmVncmVzc2lvbiAoYmx1ZSk6Iiwgcm91bmQob3B0X3Nsb3BlLCAzKSwgCiAgICAgICAgICAgICAgICAgICAgInwgRXZvbHV0aW9uYXJ5IHJlZ3Jlc3Npb24gKHJlZCk6Iiwgcm91bmQoZXZvbF9zbG9wZSwgMykpCiAgKSArCiAgdGhlbWVfYncoKSArCiAgYW5ub3RhdGUoInRleHQiLCB4ID0gbWluKHRyYWl0X2RhdGEkU3R5bGVMZW5ndGgpLCAKICAgICAgICAgIHkgPSBtYXgodHJhaXRfZGF0YSRPdmlwb3NpdG9yTGVuZ3RoKSwgCiAgICAgICAgICBsYWJlbCA9ICJPcHRpbWFsIHJlZ3Jlc3Npb24iLCAKICAgICAgICAgIGNvbG9yID0gImJsdWUiLCBoanVzdCA9IDAsIHZqdXN0ID0gMSkgKwogIGFubm90YXRlKCJ0ZXh0IiwgeCA9IG1pbih0cmFpdF9kYXRhJFN0eWxlTGVuZ3RoKSwgCiAgICAgICAgICB5ID0gbWF4KHRyYWl0X2RhdGEkT3ZpcG9zaXRvckxlbmd0aCkgLSAwLjUsIAogICAgICAgICAgbGFiZWwgPSAiRXZvbHV0aW9uYXJ5IHJlZ3Jlc3Npb24iLCAKICAgICAgICAgIGNvbG9yID0gInJlZCIsIGhqdXN0ID0gMCwgdmp1c3QgPSAxKQpgYGAKCk5vdGljZSB0aGF0IHRoZSBvcHRpbWFsIHJlZ3Jlc3Npb24gKHNvbGlkIGJsdWUgbGluZSkgYW5kIHRoZSBldm9sdXRpb25hcnkgcmVncmVzc2lvbiAoZGFzaGVkIHJlZCBsaW5lKSBhcmUgdmVyeSBjbG9zZSB0byBlYWNoIG90aGVyLiBUaGlzIHNpbWlsYXJpdHkgc3VnZ2VzdHMgdGhhdCB0aGUgZXZvbHV0aW9uYXJ5IHByb2Nlc3MgaGFzIGhhZCBzdWZmaWNpZW50IHRpbWUgdG8gcmVhY2ggdGhlIG9wdGltYWwgc3RhdGUsIG9yIHRoYXQgdGhlIHN0cmVuZ3RoIG9mIHNlbGVjdGlvbiBpcyBzdHJvbmcgcmVsYXRpdmUgdG8gdGhlIHBoeWxvZ2VuZXRpYyBzaWduYWwuCgojIyBBZGRpbmcgUGh5bG9nZW5ldGljIFNpZ25hbCB0byBTaW11bGF0ZWQgRGF0YQoKTGV0J3MgZXhwbG9yZSB3aGF0IGhhcHBlbnMgd2hlbiB3ZSBhZGQgcGh5bG9nZW5ldGljIHNpZ25hbCB0byBvdXIgZGF0YToKCmBgYHtyIGFkZC1waHlsby1zaWduYWx9CiMgU2V0IHNlZWQgZm9yIHJlcHJvZHVjaWJpbGl0eQpzZXQuc2VlZCgxMjMpCgojIENyZWF0ZSBhIGNvcHkgb2YgdGhlIG9yaWdpbmFsIHRyYWl0cwpUcmFpdHMyIDwtIFRyYWl0cwoKIyBBZGQgcGh5bG9nZW5ldGljIHNpZ25hbCB0byB0aGUgZmlyc3QgdHJhaXQgKG92aXBvc2l0b3IgbGVuZ3RoKQpUcmFpdHMyWywgMV0gPC0gVHJhaXRzMlssIDFdICsgcGh5dG9vbHM6OmZhc3RCTSh0cmVlLCBzaWcyID0gMC41KQoKIyBGaXQgbXZTTE9VQ0ggbW9kZWwgd2l0aCBkYXRhIGNvbnRhaW5pbmcgcGh5bG9nZW5ldGljIHNpZ25hbApzbG91Y2hfb3V0MiA8LSBtdnNsb3VjaE1vZGVsKHRyZWUsIFRyYWl0czIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICBrWSA9IDEsIAogICAgICAgICAgICAgICAgICAgICAgICAgICBBdHlwZSA9ICJEZWNvbXBvc2FibGVQb3NpdGl2ZSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIFN5eXR5cGUgPSAiRGlhZ29uYWwiLAogICAgICAgICAgICAgICAgICAgICAgICAgICBkaWFnQSA9IE5VTEwpCgp0cmFpdF9kYXRhMiA8LSBkYXRhLmZyYW1lKAogIE92aXBvc2l0b3JMZW5ndGggPSBUcmFpdHMyWywgMV0sCiAgU3R5bGVMZW5ndGggPSBUcmFpdHMyWywgMl0KKQoKb3B0X2ludGVyY2VwdCA8LSBzbG91Y2hfb3V0MiRGaW5hbEZvdW5kJFBhcmFtU3VtbWFyeSRtUHNpLnJvdGF0ZWQKb3B0X3Nsb3BlIDwtIHNsb3VjaF9vdXQyJEZpbmFsRm91bmQkUGFyYW1TdW1tYXJ5JG9wdGltYWwucmVncmVzc2lvbgpldm9sX3Nsb3BlIDwtIHNsb3VjaF9vdXQyJEZpbmFsRm91bmQkUGFyYW1TdW1tYXJ5JGV2b2x1dGlvbmFyeS5yZWdyZXNzaW9uCgoKZ2dwbG90KHRyYWl0X2RhdGEyLCBhZXMoeCA9IFN0eWxlTGVuZ3RoLCB5ID0gT3ZpcG9zaXRvckxlbmd0aCkpICsKICBnZW9tX3BvaW50KHNpemUgPSAzLCBhbHBoYSA9IDAuNykgKwogIGdlb21fYWJsaW5lKGludGVyY2VwdCA9IG9wdF9pbnRlcmNlcHQsIHNsb3BlID0gb3B0X3Nsb3BlLCAKICAgICAgICAgICAgICBjb2xvciA9ICJibHVlIiwgc2l6ZSA9IDEuMikgKwogIGdlb21fYWJsaW5lKGludGVyY2VwdCA9IG9wdF9pbnRlcmNlcHQsIHNsb3BlID0gZXZvbF9zbG9wZSwgCiAgICAgICAgICAgICAgY29sb3IgPSAicmVkIiwgbGluZXR5cGUgPSAiZGFzaGVkIiwgc2l6ZSA9IDEuMikgKwogIGxhYnMoCiAgICB4ID0gIlN0eWxlIExlbmd0aCIsCiAgICB5ID0gIk92aXBvc2l0b3IgTGVuZ3RoIiwKICAgIHRpdGxlID0gIlJlbGF0aW9uc2hpcCBCZXR3ZWVuIFRyYWl0cyB3aXRoIEV2b2x1dGlvbmFyeSBSZWdyZXNzaW9ucyIsCiAgICBzdWJ0aXRsZSA9IHBhc3RlKCJPcHRpbWFsIHJlZ3Jlc3Npb24gKGJsdWUpOiIsIHJvdW5kKG9wdF9zbG9wZSwgMyksIAogICAgICAgICAgICAgICAgICAgICJ8IEV2b2x1dGlvbmFyeSByZWdyZXNzaW9uIChyZWQpOiIsIHJvdW5kKGV2b2xfc2xvcGUsIDMpKQogICkgKwogIHRoZW1lX2J3KCkgKwogIGFubm90YXRlKCJ0ZXh0IiwgeCA9IG1pbih0cmFpdF9kYXRhJFN0eWxlTGVuZ3RoKSwgCiAgICAgICAgICB5ID0gbWF4KHRyYWl0X2RhdGEkT3ZpcG9zaXRvckxlbmd0aCksIAogICAgICAgICAgbGFiZWwgPSAiT3B0aW1hbCByZWdyZXNzaW9uIiwgCiAgICAgICAgICBjb2xvciA9ICJibHVlIiwgaGp1c3QgPSAwLCB2anVzdCA9IDEpICsKICBhbm5vdGF0ZSgidGV4dCIsIHggPSBtaW4odHJhaXRfZGF0YSRTdHlsZUxlbmd0aCksIAogICAgICAgICAgeSA9IG1heCh0cmFpdF9kYXRhJE92aXBvc2l0b3JMZW5ndGgpIC0gMC41LCAKICAgICAgICAgIGxhYmVsID0gIkV2b2x1dGlvbmFyeSByZWdyZXNzaW9uIiwgCiAgICAgICAgICBjb2xvciA9ICJyZWQiLCBoanVzdCA9IDAsIHZqdXN0ID0gMSkKCmBgYAoKVGhlIGRpZmZlcmVuY2UgYmV0d2VlbiBvcHRpbWFsIGFuZCBldm9sdXRpb25hcnkgcmVncmVzc2lvbiBpcyBtb3JlIHByb25vdW5jZWQgaW4gdGhlIG1vZGlmaWVkIGRhdGEuIFRoaXMgaWxsdXN0cmF0ZXMgaG93IHBoeWxvZ2VuZXRpYyBzaWduYWwgY2FuIGFmZmVjdCBvdXIgaW50ZXJwcmV0YXRpb24gb2YgdHJhaXQgcmVsYXRpb25zaGlwcy4KCiMjIE1vZGVsIENvbXBhcmlzb24KCk5vdywgbGV0J3MgY29tcGFyZSBhbGwgbW9kZWxzIHVzaW5nIHRoZSBwZW5hbGl6ZWQgQUlDYyAoQWthaWtlIEluZm9ybWF0aW9uIENyaXRlcmlvbiBjb3JyZWN0ZWQgZm9yIHNtYWxsIHNhbXBsZSBzaXplcykgdG8gZGV0ZXJtaW5lIHdoaWNoIG1vZGVsIGJlc3QgZml0cyBvdXIgZGF0YToKCmBgYHtyIG1vZGVsLWNvbXBhcmlzb259CiMgQ3JlYXRlIGRhdGFmcmFtZSBmb3IgbW9kZWwgY29tcGFyaXNvbgptb2RlbF9jb21wYXJpc29uIDwtIGRhdGEuZnJhbWUoCiAgTW9kZWwgPSBjKCJCTSIsICJPVV9mdWxsIiwgIk9VX2RpYWciLCAiT1VfbG93ZXJ0cmkiLCAiT1VfdXBwZXJ0cmkiLCAic2xPVUNIIiksCiAgQUlDYyA9IGMoYm1fb3V0JFBhcmFtU3VtbWFyeSRhaWMuYywKICAgICAgICAgIG91X291dCRGaW5hbEZvdW5kJFBhcmFtU3VtbWFyeSRhaWMuYywKICAgICAgICAgIG91X291dF9kJEZpbmFsRm91bmQkUGFyYW1TdW1tYXJ5JGFpYy5jLAogICAgICAgICAgb3Vfb3V0X2wkRmluYWxGb3VuZCRQYXJhbVN1bW1hcnkkYWljLmMsCiAgICAgICAgICBvdV9vdXRfdSRGaW5hbEZvdW5kJFBhcmFtU3VtbWFyeSRhaWMuYywKICAgICAgICAgIHNsb3VjaF9vdXQkRmluYWxGb3VuZCRQYXJhbVN1bW1hcnkkYWljLmMpCikKCiMgQ2FsY3VsYXRlIGRlbHRhIEFJQ2MgYW5kIEFrYWlrZSB3ZWlnaHRzCm1vZGVsX2NvbXBhcmlzb24gPC0gbW9kZWxfY29tcGFyaXNvbiAlPiUKICBhcnJhbmdlKEFJQ2MpICU+JQogIG11dGF0ZSgKICAgIERlbHRhX0FJQ2MgPSBBSUNjIC0gbWluKEFJQ2MpLAogICAgQWthaWtlX1dlaWdodCA9IGV4cCgtMC41ICogRGVsdGFfQUlDYykgLyBzdW0oZXhwKC0wLjUgKiBEZWx0YV9BSUNjKSkKICApCgojIEZvcm1hdCB0YWJsZSBmb3IgYmV0dGVyIGRpc3BsYXkKbW9kZWxfY29tcGFyaXNvbgpgYGAKClRoZSBtb2RlbCB3aXRoIHRoZSBsb3dlc3QgQUlDYyB2YWx1ZSBwcm92aWRlcyB0aGUgYmVzdCBmaXQgdG8gb3VyIGRhdGEsIHRha2luZyBpbnRvIGFjY291bnQgYm90aCBnb29kbmVzcy1vZi1maXQgYW5kIG1vZGVsIGNvbXBsZXhpdHkuIEFrYWlrZSB3ZWlnaHRzIGNhbiBiZSBpbnRlcnByZXRlZCBhcyB0aGUgcHJvYmFiaWxpdHkgdGhhdCBhIGdpdmVuIG1vZGVsIGlzIHRoZSBiZXN0IGFtb25nIHRoZSBzZXQgb2YgbW9kZWxzIGNvbnNpZGVyZWQuCgpCYXNlZCBvbiB0aGUgbW9kZWwgY29tcGFyaXNvbiByZXN1bHRzLCB3ZSBjYW4gaW50ZXJwcmV0IHRoZSBldm9sdXRpb25hcnkgcHJvY2VzcyB0aGF0IHNoYXBlZCB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gd2FzcCBvdmlwb3NpdG9yIGxlbmd0aCBhbmQgZmxvd2VyIHN0eWxlIGxlbmd0aC4gVGhlIGJlc3QtZml0dGluZyBtb2RlbCBzdWdnZXN0cyBgciBpZih3aGljaC5taW4obW9kZWxfY29tcGFyaXNvbiRBSUNjKSA9PSA2KXsidGhhdCB0aGVyZSBpcyBhbiBhZGFwdGl2ZSByZWxhdGlvbnNoaXAgYmV0d2VlbiB0aGVzZSB0cmFpdHMsIHdpdGggc2VsZWN0aW9uIGRyaXZpbmcgdGhlbSB0b3dhcmQgb3B0aW1hbCB2YWx1ZXMuIn0gZWxzZSBpZih3aGljaC5taW4obW9kZWxfY29tcGFyaXNvbiRBSUNjKSA9PSAxKXsidGhhdCB0aGVzZSB0cmFpdHMgZXZvbHZlIHByaW1hcmlseSB0aHJvdWdoIHJhbmRvbSBkcmlmdCB3aXRob3V0IHN0cm9uZyBkaXJlY3Rpb25hbCBzZWxlY3Rpb24uIn0gZWxzZSB7InRoYXQgdGhlcmUgaXMgYSBjb21wbGV4IGludGVycGxheSBiZXR3ZWVuIGRyaWZ0IGFuZCBzZWxlY3Rpb24gaW4gdGhlIGV2b2x1dGlvbiBvZiB0aGVzZSB0cmFpdHMuIn1gCgojIyBBY2NvdW50aW5nIGZvciBNZWFzdXJlbWVudCBFcnJvcgoKSW4gcmVhbGl0eSwgdHJhaXQgbWVhc3VyZW1lbnRzIG9mdGVuIGNvbnRhaW4gc29tZSBlcnJvci4gTGV0J3MgcmVkbyBvdXIgYW5hbHlzZXMgdGFraW5nIG1lYXN1cmVtZW50IGVycm9yIGludG8gY29uc2lkZXJhdGlvbi4gV2UnbGwgYXNzdW1lIGEgbWVhc3VyZW1lbnQgZXJyb3Igb2YgYWJvdXQgMTAlIG9mIHRoZSB0b3RhbCB2YXJpYXRpb246CgpgYGB7ciBibS13aXRoLWVycm9yfQojIENhbGN1bGF0ZSBhcHByb3hpbWF0ZSBlcnJvciB2YWx1ZXMgKDEwJSBvZiB0cmFpdCB2YXJpYW5jZSkKZXJyb3JfdmFsdWVzIDwtIGMoCiAgdmFyKFRyYWl0c1ssIDFdKSAqIDAuMSwKICB2YXIoVHJhaXRzWywgMl0pICogMC4xCikKCiMgRml0IEJyb3duaWFuIE1vdGlvbiBtb2RlbCB3aXRoIG1lYXN1cmVtZW50IGVycm9yCmJtX291dF9lIDwtIEJyb3duaWFuTW90aW9uTW9kZWwodHJlZSwgVHJhaXRzLCBNLmVycm9yID0gZXJyb3JfdmFsdWVzKQoKIyBDb21wYXJlIEJNIG1vZGVscyB3aXRoIGFuZCB3aXRob3V0IGVycm9yCmJtX2NvbXBhcmlzb24gPC0gZGF0YS5mcmFtZSgKICBNb2RlbCA9IGMoIkJNIHdpdGhvdXQgZXJyb3IiLCAiQk0gd2l0aCBlcnJvciIpLAogIEFJQ2MgPSBjKGJtX291dCRQYXJhbVN1bW1hcnkkYWljLmMsIGJtX291dF9lJFBhcmFtU3VtbWFyeSRhaWMuYyksCiAgTG9nTGlrID0gYyhibV9vdXQkUGFyYW1TdW1tYXJ5JExvZ0xpaywgYm1fb3V0X2UkUGFyYW1TdW1tYXJ5JExvZ0xpaykKKQoKYm1fY29tcGFyaXNvbgpgYGAKCkxldCdzIGFsc28gZml0IE9VIG1vZGVscyB3aXRoIG1lYXN1cmVtZW50IGVycm9yOgoKYGBge3Igb3Utd2l0aC1lcnJvcn0KIyBGaXQgT1UgbW9kZWwgd2l0aCBtZWFzdXJlbWVudCBlcnJvcgpvdV9vdXRfZSA8LSBvdWNoTW9kZWwodHJlZSwgVHJhaXRzLCAKICAgICAgICAgICAgICAgICAgICBTeXl0eXBlID0gIkRpYWdvbmFsIiwgCiAgICAgICAgICAgICAgICAgICAgTS5lcnJvciA9IGVycm9yX3ZhbHVlcykKCiMgQ29tcGFyZSBPVSBtb2RlbHMgd2l0aCBhbmQgd2l0aG91dCBlcnJvcgpvdV9jb21wYXJpc29uIDwtIGRhdGEuZnJhbWUoCiAgTW9kZWwgPSBjKCJPVSB3aXRob3V0IGVycm9yIiwgIk9VIHdpdGggZXJyb3IiKSwKICBBSUNjID0gYyhvdV9vdXQkRmluYWxGb3VuZCRQYXJhbVN1bW1hcnkkYWljLmMsIAogICAgICAgICAgb3Vfb3V0X2UkRmluYWxGb3VuZCRQYXJhbVN1bW1hcnkkYWljLmMpLAogIExvZ0xpayA9IGMob3Vfb3V0JEZpbmFsRm91bmQkUGFyYW1TdW1tYXJ5JExvZ0xpaywgCiAgICAgICAgICAgIG91X291dF9lJEZpbmFsRm91bmQkUGFyYW1TdW1tYXJ5JExvZ0xpaykKKQoKb3VfY29tcGFyaXNvbgpgYGAKCkFkZGluZyBtZWFzdXJlbWVudCBlcnJvciBpbXByb3ZlcyBvdXIgbW9kZWxzLiBUaGlzIGhpZ2hsaWdodHMgdGhlIGltcG9ydGFuY2Ugb2YgYWNjb3VudGluZyBmb3IgbWVhc3VyZW1lbnQgZXJyb3IgaW4gcGh5bG9nZW5ldGljIGNvbXBhcmF0aXZlIGFuYWx5c2VzLiBOb3cgdHJ5IGFkZGluZyBlcnJvciB0byBhbGwgb3RoZXIgbW9kZWxzIGFuZCBvYnNlcnZlIGlmIHlvdXIgY29uY2x1c2lvbnMgY2hhbmdlLgoKIyMgRXhlcmNpc2UgUXVlc3Rpb25zCgoxLiBIb3cgd291bGQgeW91IGludGVycHJldCB0aGUgYmlvbG9naWNhbCBtZWFuaW5nIG9mIHRoZSBzZWxlY3Rpb24gc3RyZW5ndGggbWF0cml4IChBKSBpbiB0aGUgT1UgbW9kZWxzPwoKMi4gV2hhdCBkb2VzIGl0IG1lYW4gd2hlbiB0aGUgb3B0aW1hbCByZWdyZXNzaW9uIGFuZCBldm9sdXRpb25hcnkgcmVncmVzc2lvbiBhcmUgc2ltaWxhciBvciBkaWZmZXJlbnQ/CgozLiBUcnkgZml0dGluZyBtb2RlbHMgd2hlcmUgeW91IHN3aXRjaCB3aGljaCB0cmFpdCBpcyBkZXBlbmRlbnQgYW5kIHdoaWNoIGlzIGluZGVwZW5kZW50IGluIHRoZSBTTE9VQ0ggbW9kZWwuIEhvdyBkb2VzIHRoaXMgY2hhbmdlIHlvdXIgaW50ZXJwcmV0YXRpb24/Cgo0LiBIb3cgbWlnaHQgeW91IGluY29ycG9yYXRlIGFkZGl0aW9uYWwgdHJhaXRzIG9yIGVudmlyb25tZW50YWwgdmFyaWFibGVzIGludG8gdGhlc2UgYW5hbHlzZXM/Cgo1LiBXaGF0IGFyZSB0aGUgbGltaXRhdGlvbnMgb2YgdGhlc2UgbXVsdGl2YXJpYXRlIFBDTSBhcHByb2FjaGVzPwoKCiMjIFJlZmVyZW5jZXMKCjEuIEJhcnRvc3playwgSy4sIFBpZW5hYXIsIEouLCBNb3N0YWQsIFAuLCBBbmRlcnNzb24sIFMuLCAmIEhhbnNlbiwgVC4gRi4gKDIwMTIpLiBBIHBoeWxvZ2VuZXRpYyBjb21wYXJhdGl2ZSBtZXRob2QgZm9yIHN0dWR5aW5nIG11bHRpdmFyaWF0ZSBhZGFwdGF0aW9uLiBKb3VybmFsIG9mIFRoZW9yZXRpY2FsIEJpb2xvZ3ksIDMxNCwgMjA0LTIxNS4KCjIuIEhhbnNlbiwgVC4gRi4sIFBpZW5hYXIsIEouLCAmIE9yemFjaywgUy4gSC4gKDIwMDgpLiBBIGNvbXBhcmF0aXZlIG1ldGhvZCBmb3Igc3R1ZHlpbmcgYWRhcHRhdGlvbiB0byBhIHJhbmRvbWx5IGV2b2x2aW5nIGVudmlyb25tZW50LiBFdm9sdXRpb24sIDYyKDgpLCAxOTY1LTE5NzcuCgozLiBNaXRvdiwgVi4sICYgQmFydG9zemVrLCBLLiAoMjAyMSkuIFBDTUJhc2U6IEEgZnJhbWV3b3JrIGZvciBwaHlsb2dlbmV0aWMgY29tcGFyYXRpdmUgbWV0aG9kcy4gTWV0aG9kcyBpbiBFY29sb2d5IGFuZCBFdm9sdXRpb24sIDEyKDExKSwgMjMxNC0yMzI5Lgo=