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:
- Brownian Motion (BM): A simple model where traits
evolve by random drift with a constant rate
- Ornstein-Uhlenbeck (OU): A model that incorporates
both random drift and selection toward an optimal value
- Selection Matrix (A): In multivariate models, this
matrix describes the strength and direction of selection
- 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
How would you interpret the biological meaning of the selection
strength matrix (A) in the OU models?
What does it mean when the optimal regression and evolutionary
regression are similar or different?
Try fitting models where you switch which trait is dependent and
which is independent in the SLOUCH model. How does this change your
interpretation?
How might you incorporate additional traits or environmental
variables into these analyses?
What are the limitations of these multivariate PCM
approaches?
References
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.
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.
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=