Goals:
DGP
Consider a DGP with linear heterogeneous treatment effects, but
nonlinear propensity score and outcome:
\(p=10\) independent covariates
\(X_1,...,X_k,...,X_{10}\) drawn from a
uniform distribution: \(X_k \sim
uniform(-\pi,\pi)\)
The treatment model is \(W \sim
Bernoulli(\underbrace{\Phi(sin(X_1))}_{e(X)})\), where \(\Phi(\cdot)\) is the standard normal
cumulative density function
The potential outcome model of the controls is \(Y(0) = \underbrace{sin(X_1)}_{m_0(X)} +
\varepsilon\), with \(\varepsilon \sim
N(0,1)\)
The CATE function is a linear function of the first three
covariates \(\tau(X) =
\underbrace{0.3}_{\rho_1} X_1 + \underbrace{0.2}_{\rho_2} X_2 +
\underbrace{0.1}_{\rho_3} X_3\)
The potential outcome model of the treated is \(Y(1) = m_0(X) + \tau(X) + \varepsilon\),
with \(\varepsilon \sim
N(0,1)\)
We draw a sample of 1000 observations to begin with:
if (!require("grf")) install.packages("grf", dependencies = TRUE); library(grf)
if (!require("tidyverse")) install.packages("tidyverse", dependencies = TRUE); library(tidyverse)
if (!require("glmnet")) install.packages("glmnet", dependencies = TRUE); library(glmnet)
if (!require("psych")) install.packages("psych", dependencies = TRUE); library(psych)
if (!require("causalDML")) {
if (!require("devtools")) install.packages("devtools", dependencies = TRUE); library(devtools)
install_github(repo="MCKnaus/causalDML")
}; library(causalDML)
if (!require("rlearner")) {
if (!require("devtools")) install.packages("devtools", dependencies = TRUE); library(devtools)
install_github("xnie/rlearner")
}; library(rlearner)
set.seed(1234)
# Set parameters
n = 1000
p = 10
# Correct parameters
rho = c(0.3,0.2,0.1,rep(0,p-3))
# Draw sample
x = matrix(runif(n*p,-pi,pi),ncol=p)
e = function(x){pnorm(sin(x))}
m0 = function(x){sin(x)}
tau = x %*% rho
w = rbinom(n,1,e(x[,1]))
y = m0(x[,1]) + w*tau + rnorm(n,0,1)
Handcoded R-learner with OLS last step
For illustration purposes we handcode an R-learner with cross-fitted
nuisance parameters via honest Random Forest and heterogeneity
estimation via OLS. This should work well because the nuisance
parameters are nonlinear and heterogeneity is actually linear.
First, we get the nuisance parameters \(e(X)=E[W|X]\) and \(m(X)=E[Y|X]\) via self-tuned honest Random
Forest. We handcode 2-fold cross-validation in the familiar way:
# R-learner with OLS last stage
# 2-fold cross-fitting
mhat = ehat = rep(NA,n)
# Draw random indices for sample 1
index_s1 = sample(1:n,n/2)
# Create S1
x1 = x[index_s1,]
w1 = w[index_s1]
y1 = y[index_s1]
# Create sample 2 with those not in S1
x2 = x[-index_s1,]
w2 = w[-index_s1]
y2 = y[-index_s1]
# Model in S1, predict in S2
rf = regression_forest(x1,w1,tune.parameters = "all")
ehat[-index_s1] = predict(rf,newdata=x2)$predictions
rf = regression_forest(x1,y1,tune.parameters = "all")
mhat[-index_s1] = predict(rf,newdata=x2)$predictions
# Model in S2, predict in S1
rf = regression_forest(x2,w2,tune.parameters = "all")
ehat[index_s1] = predict(rf,newdata=x1)$predictions
rf = regression_forest(x2,y2,tune.parameters = "all")
mhat[index_s1] = predict(rf,newdata=x1)$predictions
We have now two ways to implement the R-learner
1. Modify the covariates
Recall that we can rewrite the R-learner as minimizing a least
squares problem with outcome residual as pseudo-outcome and modified
covariates:
\[\hat{\beta}^{rl} = argmin_{\beta}
\sum_{i=1}^N \big(Y_i - \hat{m}(X_i) - X_i^* \beta \big)^2\]
where \(X_i^* = X_i(W_i -
\hat{e}(X_i))\) are the modified/pseudo-covariates.
Note that \(X_i\) includes the
constant such that the first column of the modified covariates equals
the treatment residuals and we run a regression without a “real”
constant of ones:
# Create residuals
res_y = y-mhat
res_w = w-ehat
# Modify covariates (multiply each column including constant with residual)
x_wc = cbind(rep(1,n),x)
colnames(x_wc) = c("Intercept",paste0("X",1:p))
xstar = x_wc * res_w
# Regress outcome residual on modified covariates
summary(lm(res_y ~ 0 + xstar))
Call:
lm(formula = res_y ~ 0 + xstar)
Residuals:
Min 1Q Median 3Q Max
-2.8599 -0.6829 0.0280 0.6451 3.1853
Coefficients:
Estimate Std. Error t value Pr(>|t|)
xstarIntercept 0.088067 0.071056 1.239 0.2155
xstarX1 0.226436 0.038016 5.956 3.58e-09 ***
xstarX2 0.210385 0.039627 5.309 1.36e-07 ***
xstarX3 0.083184 0.039557 2.103 0.0357 *
xstarX4 0.007685 0.039217 0.196 0.8447
xstarX5 0.026108 0.038243 0.683 0.4950
xstarX6 0.083009 0.039672 2.092 0.0367 *
xstarX7 -0.018537 0.039428 -0.470 0.6384
xstarX8 0.034299 0.039734 0.863 0.3882
xstarX9 -0.031037 0.040560 -0.765 0.4443
xstarX10 0.023241 0.038538 0.603 0.5466
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
Residual standard error: 0.9976 on 989 degrees of freedom
Multiple R-squared: 0.07587, Adjusted R-squared: 0.06559
F-statistic: 7.381 on 11 and 989 DF, p-value: 3.027e-12
The estimated coefficients are relatively close to their true
values.
2. Pseudo-outcome and weights
The more generic alternative is to use the unmodified covariates in a
weighted regression with pseudo outcomes: \[\hat{\beta}^{rl} = argmin_{\beta} \sum_{i=1}^N
\underbrace{(W_i - \hat{e}(X_i))^2}_{\text{weight}}
\left(\underbrace{\frac{Y_i - \hat{m}(X_i)}{W_i -
\hat{e}(X_i)}}_{\text{pseudo-outcome}} - X_i'\beta
\right)^2\]
# Create pseudo-outcome (outcome res divided by treatment res)
pseudo_rl = res_y / res_w
# Create weights
weights_rl = res_w^2
# Weighted regression of pseudo-outcome on covariates
rols_fit = lm(pseudo_rl ~ x, weights=weights_rl)
summary(rols_fit)
Call:
lm(formula = pseudo_rl ~ x, weights = weights_rl)
Weighted Residuals:
Min 1Q Median 3Q Max
-3.1853 -0.6460 0.0353 0.6870 2.7264
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) 0.088067 0.071056 1.239 0.2155
x1 0.226436 0.038016 5.956 3.58e-09 ***
x2 0.210385 0.039627 5.309 1.36e-07 ***
x3 0.083184 0.039557 2.103 0.0357 *
x4 0.007685 0.039217 0.196 0.8447
x5 0.026108 0.038243 0.683 0.4950
x6 0.083009 0.039672 2.092 0.0367 *
x7 -0.018537 0.039428 -0.470 0.6384
x8 0.034299 0.039734 0.863 0.3882
x9 -0.031037 0.040560 -0.765 0.4443
x10 0.023241 0.038538 0.603 0.5466
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
Residual standard error: 0.9976 on 989 degrees of freedom
Multiple R-squared: 0.07497, Adjusted R-squared: 0.06562
F-statistic: 8.016 on 10 and 989 DF, p-value: 1.632e-12
r_ols_est = predict(rols_fit)
This produces the same results as the modified covariate as the
equality of all coefficients shows:
# test if all values are equal
all.equal(as.numeric(rols_fit$coefficients), as.numeric(lm(res_y ~ 0 + xstar)$coefficients))
[1] TRUE
We store the fitted values \(\hat{\tau}(x)
= x'\hat{\beta}^{rl}\) as estimates of the CATEs to compare
them later with the truth and other methods.
# Estimate CATEs
r_ols_est = predict(rols_fit)
Handcoded R-learner with Lasso last step
The previous exercise should have convinced you that these different
transformations work. I prefer the second option, but this is a matter
of taste. Instead of OLS we can similarly estimate a weighted Lasso with
the pseudo-outcomes where we again save the estimated CATEs for later
comparison:
# R-learner with Lasso
rlasso_hand = cv.glmnet(x,pseudo_rl,weights=weights_rl)
plot(rlasso_hand)
rlasso_hand = predict(rlasso_hand,newx = x, s = "lambda.min")
rlearner
package
The R-learner can be more conveniently used via the
rlearner
package. It provides several options, but we focus
on Lasso today, because the Boosting and Kernel options take quite some
time to run. Note that this implementation uses now Lasso to estimate
the nonlinear nuisance functions, which could deteriorate the
performance. We store the predictions and will check below:
# Using the rlearner package
rlasso_fit = rlasso(x, w, y)
rlasso_est = predict(rlasso_fit, x)
DR-learner via causalDML
package
The DR-learner uses the hopefully by now familiar pseudo-outcome of
the AIPW ATE estimator and we will not handcode it again (see notebook
SNB_GATE
and replace the final OLS or Kernel regression step with the supervised
ML method of your choice).
The causalDML
package implements the DR-learner with the
required cross-fitting procedure. It is a bit more complicated than what
we discussed so far, but uses the same ideas we have learned. For
details see the Appendix of Knaus (2020).
The default version of the dr_learner
is implemented
with an untuned honest Random Forest at all stages. This means it is
well-suited for the nuisance parameters, but should have a harder time
with the linear heterogeneous effects.
# DR-learner
dr_est = dr_learner(y,w,x)
Now it is time to compare the estimated CATEs of the estimators used
so far to the, here known, true CATEs:
# Store and plot predictions
results1k = cbind(tau,r_ols_est,rlasso_hand,rlasso_est,dr_est$cates)
colnames(results1k) = c("True","RL OLS","RL Lasso hand","rlasso","DR RF")
pairs.panels(results1k,method = "pearson")
All estimators come quite close, but especially the DR-learner shows
a lower correlation with the true CATEs. This is not unexpected because
it uses Random Forest to approximate a linear CATE in its final step,
while the other methods use OLS / Lasso that are expected to perform
well with linear CATE functions.
Interestingly when checking the MSE, we see that the seemingly high
performing rlasso
implementation performs much worse in
terms of MSE than the high correlation would suggest. It gets the
sorting right, but not the levels as a look at the axes indicates.
# Compare MSE
data.frame(MSE = colMeans( (results1k[,-1]-c(tau))^2 ) ,
Method = factor(colnames(results1k)[-1]) ) %>%
ggplot(aes(x=Method,y=MSE)) + geom_point(size = 2) +
ggtitle(paste(toString(n),"observations")) + geom_hline(yintercept = 0)
With ensemble/SuperLearner
A computationally more expensive, but agnostic approach is to create
an ensemble or SuperLearner. The idea is that we form predictions via
several different supervised ML methods and use a weighted average of
their predictions as final prediction. These weights are chosen in a
data-driven way via cross-validation to figure out which predictors work
best for the prediction model at hand (see Naimi
and Balzer (2018) for a nice introduction with examples in R).
I am not aware of such an implementation for the R-learner. However,
the dr_learner
allows to use such an ensemble of methods.
We specify it to use ensembles of several methods.
As the treatment is binary, we include the following methods to
estimate the propensity score:
the mean (would be relevant if no selection into
treatment)
self-tuned Random Forest
logistic Ridge regression
logistic Lasso regression
For outcome nuisances and the heterogeneous effect, we use
## Create components of ensemble
# General methods
mean = create_method("mean")
forest = create_method("forest_grf",args=list(tune.parameters = "all",honesty=F))
# Pscore specific components
ridge_bin = create_method("ridge",args=list(family = "binomial"))
lasso_bin = create_method("lasso",args=list(family = "binomial"))
# Outcome specific components
ols = create_method("ols")
ridge = create_method("ridge")
lasso = create_method("lasso")
# DR-learner with ensemble
dr_ens = dr_learner(y,w,x,ml_w=list(mean,forest,ridge_bin,lasso_bin),
ml_y = list(mean,forest,ols,ridge,lasso),
ml_tau = list(mean,forest,ols,ridge,lasso),quiet=T)
Let’s check how this performs.
# Add and plot predictions
label_method = c("RL OLS","RL Lasso hand","rlasso","DR RF","DR Ens")
results1k = cbind(tau,r_ols_est,rlasso_hand,rlasso_est,dr_est$cates,dr_ens$cates)
colnames(results1k) = c("True",label_method)
pairs.panels(results1k,method = "pearson")
# Compare MSE
data.frame(MSE = colMeans( (results1k[,-1]-c(tau))^2 ) ,
Method = factor(label_method,levels=label_method) ) %>%
ggplot(aes(x=Method,y=MSE)) + geom_point(size = 2) +
ggtitle(paste(toString(n),"observations")) + geom_hline(yintercept = 0)
It does not perform as well as the handcoded R-learner that uses
suitable methods for each component. However, it improves already upon
the only Lasso R-learner and the only Random Forest DR-learner in terms
of MSE. I think this is impressive given that only 1000 observations are
available to figure out which methods work best. However this should
become better and better if we increase the sample size.
Thus we rerun the analysis with a draw of 4000 and of 16000
observations, which runs over two hours on my laptop.
4000 observations
# Increase sample size
n = 4000
# Draw sample
x = matrix(runif(n*p,-pi,pi),ncol=p)
tau = x %*% rho
w = rbinom(n,1,e(x[,1]))
y = m0(x[,1]) + w*tau + rnorm(n,0,1)
# Handcoded R-learner with OLS last stage
# C&P w/o comments from above
mhat = ehat = rep(NA,n)
index_s1 = sample(1:n,n/2)
x1 = x[index_s1,]
w1 = w[index_s1]
y1 = y[index_s1]
x2 = x[-index_s1,]
w2 = w[-index_s1]
y2 = y[-index_s1]
rf = regression_forest(x1,w1,tune.parameters = "all")
ehat[-index_s1] = predict(rf,newdata=x2)$predictions
rf = regression_forest(x1,y1,tune.parameters = "all")
mhat[-index_s1] = predict(rf,newdata=x2)$predictions
rf = regression_forest(x2,w2,tune.parameters = "all")
ehat[index_s1] = predict(rf,newdata=x1)$predictions
rf = regression_forest(x2,y2,tune.parameters = "all")
mhat[index_s1] = predict(rf,newdata=x1)$predictions
res_y = y-mhat
res_w = w-ehat
pseudo_rl = res_y / res_w
weights_rl = res_w^2
rols_fit = lm(pseudo_rl ~ x, weights=weights_rl)
r_ols_est = predict(rols_fit)
# Handcoded R-learner with Lasso last stage
rlasso_hand = cv.glmnet(x,pseudo_rl,weights=weights_rl)
rlasso_hand = predict(rlasso_hand,newx = x, s = "lambda.min")
# Using the rlearner package
rlasso_fit = rlasso(x, w, y)
rlasso_est = predict(rlasso_fit, x)
# DR-learner
dr_est = dr_learner(y,w,x)
# DR-learner with ensemble
dr_ens = dr_learner(y,w,x,ml_w=list(mean,forest,ridge_bin,lasso_bin),
ml_y = list(mean,forest,ols,ridge,lasso),
ml_tau = list(mean,forest,ols,ridge,lasso),quiet=T)
# Add and plot predictions
label_method = c("RL OLS","RL Lasso hand","rlasso","DR RF","DR Ens")
results4k = cbind(tau,r_ols_est,rlasso_hand,rlasso_est,dr_est$cates,dr_ens$cates)
colnames(results4k) = c("True",label_method)
pairs.panels(results4k,method = "pearson")
# Compare MSE
data.frame(MSE = colMeans( (results4k[,-1]-c(tau))^2 ) ,
Method = factor(label_method,levels=label_method) ) %>%
ggplot(aes(x=Method,y=MSE)) + geom_point(size = 2) +
ggtitle(paste(toString(n),"observations")) + geom_hline(yintercept = 0)
16000 observations
# Increase sample size
n = 16000
# Draw sample
x = matrix(runif(n*p,-pi,pi),ncol=p)
tau = x %*% rho
w = rbinom(n,1,e(x[,1]))
y = m0(x[,1]) + w*tau + rnorm(n,0,1)
# Handcoded R-learner with OLS last stage
# C&P w/o comments from above
mhat = ehat = rep(NA,n)
index_s1 = sample(1:n,n/2)
x1 = x[index_s1,]
w1 = w[index_s1]
y1 = y[index_s1]
x2 = x[-index_s1,]
w2 = w[-index_s1]
y2 = y[-index_s1]
rf = regression_forest(x1,w1,tune.parameters = "all")
ehat[-index_s1] = predict(rf,newdata=x2)$predictions
rf = regression_forest(x1,y1,tune.parameters = "all")
mhat[-index_s1] = predict(rf,newdata=x2)$predictions
rf = regression_forest(x2,w2,tune.parameters = "all")
ehat[index_s1] = predict(rf,newdata=x1)$predictions
rf = regression_forest(x2,y2,tune.parameters = "all")
mhat[index_s1] = predict(rf,newdata=x1)$predictions
res_y = y-mhat
res_w = w-ehat
pseudo_rl = res_y / res_w
weights_rl = res_w^2
rols_fit = lm(pseudo_rl ~ x, weights=weights_rl)
r_ols_est = predict(rols_fit)
# Handcoded R-learner with Lasso last stage
rlasso_hand = cv.glmnet(x,pseudo_rl,weights=weights_rl)
rlasso_hand = predict(rlasso_hand,newx = x, s = "lambda.min")
# Using the rlearner package
rlasso_fit = rlasso(x, w, y)
rlasso_est = predict(rlasso_fit, x)
# DR-learner
dr_est = dr_learner(y,w,x)
# DR-learner with ensemble
dr_ens = dr_learner(y,w,x,ml_w=list(mean,forest,ridge_bin,lasso_bin),
ml_y = list(mean,forest,ols,ridge,lasso),
ml_tau = list(mean,forest,ols,ridge,lasso),quiet=T)
# Add and plot predictions
label_method = c("RL OLS","RL Lasso hand","rlasso","DR RF","DR Ens")
results16k = cbind(tau,r_ols_est,rlasso_hand,rlasso_est,dr_est$cates,dr_ens$cates)
colnames(results16k) = c("True",label_method)
pairs.panels(results16k,method = "pearson")
# Compare MSE
data.frame(MSE = colMeans( (results16k[,-1]-c(tau))^2 ) ,
Method = factor(label_method,levels=label_method) ) %>%
ggplot(aes(x=Method,y=MSE)) + geom_point(size = 2) +
ggtitle(paste(toString(n),"observations")) + geom_hline(yintercept = 0)
With increasing sample size, the DR-learner with ensemble methods
catches up with the methods tailored for the specific DGP.
rlasso
and the purely Random Forest based DR-learner
continuously improve but are not competitive.
This highlights that using ensemble learners as an agnostic way to
manage the prediction tasks in meta-learners works quite well.
Especially in practice this can be a big advantage because it is a
priori not clear which methods best approximate the nuisance
parameters and heterogeneity. The big downside is of course that it
takes much much longer to compute.
Take-away
Meta-learners are just combinations of different standard
prediction problems \(\Rightarrow\)
combine standard functions in a modular way
Using ensemble methods that figure out in a data-driven way which
methods work for which prediction problem is an interesting option if we
have time and no idea which single method works best in our
setting
Suggestions to play with the toy model
Some suggestions:
Add Causal Forest to the comparison
Create a non-linear CATE and linear nuisance functions
or
Change the treatment shares
LS0tDQp0aXRsZTogIkNhdXNhbCBNTDogTWV0YS1sZWFybmVyIg0Kc3VidGl0bGU6ICJTaW11bGF0aW9uIG5vdGVib29rIg0KYXV0aG9yOiAiTWljaGFlbCBLbmF1cyINCmRhdGU6ICJgciBmb3JtYXQoU3lzLnRpbWUoKSwgJyVtLyV5JylgIg0Kb3V0cHV0OiANCiAgaHRtbF9ub3RlYm9vazoNCiAgICB0b2M6IHRydWUNCiAgICB0b2NfZmxvYXQ6IHRydWUNCiAgICBjb2RlX2ZvbGRpbmc6IHNob3cNCi0tLQ0KDQoNCkdvYWxzOg0KDQotIEhhbmRjb2RlIFItbGVhcm5lcg0KDQotIElsbHVzdHJhdGUgdXNlIG9mIFItbGVhcm5lciBhbmQgRFItbGVhcm5lcg0KDQo8YnI+DQoNCg0KIyMgREdQDQoNCkNvbnNpZGVyIGEgREdQIHdpdGggbGluZWFyIGhldGVyb2dlbmVvdXMgdHJlYXRtZW50IGVmZmVjdHMsIGJ1dCBub25saW5lYXIgcHJvcGVuc2l0eSBzY29yZSBhbmQgb3V0Y29tZToNCg0KLSAkcD0xMCQgaW5kZXBlbmRlbnQgY292YXJpYXRlcyAkWF8xLC4uLixYX2ssLi4uLFhfezEwfSQgZHJhd24gZnJvbSBhIHVuaWZvcm0gZGlzdHJpYnV0aW9uOiAkWF9rIFxzaW0gdW5pZm9ybSgtXHBpLFxwaSkkDQoNCi0gVGhlIHRyZWF0bWVudCBtb2RlbCBpcyAkVyBcc2ltIEJlcm5vdWxsaShcdW5kZXJicmFjZXtcUGhpKHNpbihYXzEpKX1fe2UoWCl9KSQsIHdoZXJlICRcUGhpKFxjZG90KSQgaXMgdGhlIHN0YW5kYXJkIG5vcm1hbCBjdW11bGF0aXZlIGRlbnNpdHkgZnVuY3Rpb24NCg0KLSBUaGUgcG90ZW50aWFsIG91dGNvbWUgbW9kZWwgb2YgdGhlIGNvbnRyb2xzIGlzICRZKDApID0gXHVuZGVyYnJhY2V7c2luKFhfMSl9X3ttXzAoWCl9ICsgXHZhcmVwc2lsb24kLCB3aXRoICRcdmFyZXBzaWxvbiBcc2ltIE4oMCwxKSQNCg0KLSBUaGUgQ0FURSBmdW5jdGlvbiBpcyBhIGxpbmVhciBmdW5jdGlvbiBvZiB0aGUgZmlyc3QgdGhyZWUgY292YXJpYXRlcyAkXHRhdShYKSA9IFx1bmRlcmJyYWNlezAuM31fe1xyaG9fMX0gWF8xICsgXHVuZGVyYnJhY2V7MC4yfV97XHJob18yfSBYXzIgKyBcdW5kZXJicmFjZXswLjF9X3tccmhvXzN9IFhfMyQNCg0KLSBUaGUgcG90ZW50aWFsIG91dGNvbWUgbW9kZWwgb2YgdGhlIHRyZWF0ZWQgaXMgJFkoMSkgPSBtXzAoWCkgKyBcdGF1KFgpICsgXHZhcmVwc2lsb24kLCB3aXRoICRcdmFyZXBzaWxvbiBcc2ltIE4oMCwxKSQNCg0KV2UgZHJhdyBhIHNhbXBsZSBvZiAxMDAwIG9ic2VydmF0aW9ucyB0byBiZWdpbiB3aXRoOg0KDQpgYGB7ciwgd2FybmluZyA9IEYsIG1lc3NhZ2UgPSBGfQ0KaWYgKCFyZXF1aXJlKCJncmYiKSkgaW5zdGFsbC5wYWNrYWdlcygiZ3JmIiwgZGVwZW5kZW5jaWVzID0gVFJVRSk7IGxpYnJhcnkoZ3JmKQ0KaWYgKCFyZXF1aXJlKCJ0aWR5dmVyc2UiKSkgaW5zdGFsbC5wYWNrYWdlcygidGlkeXZlcnNlIiwgZGVwZW5kZW5jaWVzID0gVFJVRSk7IGxpYnJhcnkodGlkeXZlcnNlKQ0KaWYgKCFyZXF1aXJlKCJnbG1uZXQiKSkgaW5zdGFsbC5wYWNrYWdlcygiZ2xtbmV0IiwgZGVwZW5kZW5jaWVzID0gVFJVRSk7IGxpYnJhcnkoZ2xtbmV0KQ0KaWYgKCFyZXF1aXJlKCJwc3ljaCIpKSBpbnN0YWxsLnBhY2thZ2VzKCJwc3ljaCIsIGRlcGVuZGVuY2llcyA9IFRSVUUpOyBsaWJyYXJ5KHBzeWNoKQ0KaWYgKCFyZXF1aXJlKCJjYXVzYWxETUwiKSkgew0KICBpZiAoIXJlcXVpcmUoImRldnRvb2xzIikpIGluc3RhbGwucGFja2FnZXMoImRldnRvb2xzIiwgZGVwZW5kZW5jaWVzID0gVFJVRSk7IGxpYnJhcnkoZGV2dG9vbHMpDQogIGluc3RhbGxfZ2l0aHViKHJlcG89Ik1DS25hdXMvY2F1c2FsRE1MIikgDQp9OyBsaWJyYXJ5KGNhdXNhbERNTCkNCmlmICghcmVxdWlyZSgicmxlYXJuZXIiKSkgew0KICBpZiAoIXJlcXVpcmUoImRldnRvb2xzIikpIGluc3RhbGwucGFja2FnZXMoImRldnRvb2xzIiwgZGVwZW5kZW5jaWVzID0gVFJVRSk7IGxpYnJhcnkoZGV2dG9vbHMpDQogIGluc3RhbGxfZ2l0aHViKCJ4bmllL3JsZWFybmVyIikNCn07IGxpYnJhcnkocmxlYXJuZXIpDQoNCnNldC5zZWVkKDEyMzQpDQoNCiMgU2V0IHBhcmFtZXRlcnMNCm4gPSAxMDAwDQpwID0gMTANCg0KIyBDb3JyZWN0IHBhcmFtZXRlcnMNCnJobyA9IGMoMC4zLDAuMiwwLjEscmVwKDAscC0zKSkNCg0KIyBEcmF3IHNhbXBsZQ0KeCA9IG1hdHJpeChydW5pZihuKnAsLXBpLHBpKSxuY29sPXApDQplID0gZnVuY3Rpb24oeCl7cG5vcm0oc2luKHgpKX0NCm0wID0gZnVuY3Rpb24oeCl7c2luKHgpfQ0KdGF1ID0geCAlKiUgcmhvDQp3ID0gcmJpbm9tKG4sMSxlKHhbLDFdKSkNCnkgPSBtMCh4WywxXSkgKyB3KnRhdSArIHJub3JtKG4sMCwxKQ0KYGBgDQoNCjxicj4NCg0KIyMgSGFuZGNvZGVkIFItbGVhcm5lciB3aXRoIE9MUyBsYXN0IHN0ZXANCg0KRm9yIGlsbHVzdHJhdGlvbiBwdXJwb3NlcyB3ZSBoYW5kY29kZSBhbiBSLWxlYXJuZXIgd2l0aCBjcm9zcy1maXR0ZWQgbnVpc2FuY2UgcGFyYW1ldGVycyB2aWEgaG9uZXN0IFJhbmRvbSBGb3Jlc3QgYW5kIGhldGVyb2dlbmVpdHkgZXN0aW1hdGlvbiB2aWEgT0xTLiBUaGlzIHNob3VsZCB3b3JrIHdlbGwgYmVjYXVzZSB0aGUgbnVpc2FuY2UgcGFyYW1ldGVycyBhcmUgbm9ubGluZWFyIGFuZCBoZXRlcm9nZW5laXR5IGlzIGFjdHVhbGx5IGxpbmVhci4NCg0KRmlyc3QsIHdlIGdldCB0aGUgbnVpc2FuY2UgcGFyYW1ldGVycyAkZShYKT1FW1d8WF0kIGFuZCAkbShYKT1FW1l8WF0kIHZpYSBzZWxmLXR1bmVkIGhvbmVzdCBSYW5kb20gRm9yZXN0LiBXZSBoYW5kY29kZSAyLWZvbGQgY3Jvc3MtdmFsaWRhdGlvbiBpbiB0aGUgZmFtaWxpYXIgd2F5Og0KDQoNCmBgYHtyfQ0KIyBSLWxlYXJuZXIgd2l0aCBPTFMgbGFzdCBzdGFnZQ0KIyAyLWZvbGQgY3Jvc3MtZml0dGluZw0KbWhhdCA9IGVoYXQgPSByZXAoTkEsbikNCiMgRHJhdyByYW5kb20gaW5kaWNlcyBmb3Igc2FtcGxlIDENCmluZGV4X3MxID0gc2FtcGxlKDE6bixuLzIpDQojIENyZWF0ZSBTMQ0KeDEgPSB4W2luZGV4X3MxLF0NCncxID0gd1tpbmRleF9zMV0NCnkxID0geVtpbmRleF9zMV0NCiMgQ3JlYXRlIHNhbXBsZSAyIHdpdGggdGhvc2Ugbm90IGluIFMxDQp4MiA9IHhbLWluZGV4X3MxLF0NCncyID0gd1staW5kZXhfczFdDQp5MiA9IHlbLWluZGV4X3MxXQ0KIyBNb2RlbCBpbiBTMSwgcHJlZGljdCBpbiBTMg0KcmYgPSByZWdyZXNzaW9uX2ZvcmVzdCh4MSx3MSx0dW5lLnBhcmFtZXRlcnMgPSAiYWxsIikNCmVoYXRbLWluZGV4X3MxXSA9IHByZWRpY3QocmYsbmV3ZGF0YT14MikkcHJlZGljdGlvbnMNCnJmID0gcmVncmVzc2lvbl9mb3Jlc3QoeDEseTEsdHVuZS5wYXJhbWV0ZXJzID0gImFsbCIpDQptaGF0Wy1pbmRleF9zMV0gPSBwcmVkaWN0KHJmLG5ld2RhdGE9eDIpJHByZWRpY3Rpb25zDQojIE1vZGVsIGluIFMyLCBwcmVkaWN0IGluIFMxDQpyZiA9IHJlZ3Jlc3Npb25fZm9yZXN0KHgyLHcyLHR1bmUucGFyYW1ldGVycyA9ICJhbGwiKQ0KZWhhdFtpbmRleF9zMV0gPSBwcmVkaWN0KHJmLG5ld2RhdGE9eDEpJHByZWRpY3Rpb25zDQpyZiA9IHJlZ3Jlc3Npb25fZm9yZXN0KHgyLHkyLHR1bmUucGFyYW1ldGVycyA9ICJhbGwiKQ0KbWhhdFtpbmRleF9zMV0gPSBwcmVkaWN0KHJmLG5ld2RhdGE9eDEpJHByZWRpY3Rpb25zDQpgYGANCg0KV2UgaGF2ZSBub3cgdHdvIHdheXMgdG8gaW1wbGVtZW50IHRoZSBSLWxlYXJuZXINCg0KPGJyPg0KDQojIyMgMS4gTW9kaWZ5IHRoZSBjb3ZhcmlhdGVzDQoNClJlY2FsbCB0aGF0IHdlIGNhbiByZXdyaXRlIHRoZSBSLWxlYXJuZXIgYXMgbWluaW1pemluZyBhIGxlYXN0IHNxdWFyZXMgcHJvYmxlbSB3aXRoIG91dGNvbWUgcmVzaWR1YWwgYXMgcHNldWRvLW91dGNvbWUgYW5kIG1vZGlmaWVkIGNvdmFyaWF0ZXM6DQoNCiQkXGhhdHtcYmV0YX1ee3JsfSA9IGFyZ21pbl97XGJldGF9IFxzdW1fe2k9MX1eTiBcYmlnKFlfaSAtIFxoYXR7bX0oWF9pKSAtICBYX2leKiBcYmV0YSBcYmlnKV4yJCQNCg0Kd2hlcmUgJFhfaV4qID0gWF9pKFdfaSAtIFxoYXR7ZX0oWF9pKSkkIGFyZSB0aGUgbW9kaWZpZWQvcHNldWRvLWNvdmFyaWF0ZXMuDQoNCk5vdGUgdGhhdCAkWF9pJCBpbmNsdWRlcyB0aGUgY29uc3RhbnQgc3VjaCB0aGF0IHRoZSBmaXJzdCBjb2x1bW4gb2YgdGhlIG1vZGlmaWVkIGNvdmFyaWF0ZXMgZXF1YWxzIHRoZSB0cmVhdG1lbnQgcmVzaWR1YWxzIGFuZCB3ZSBydW4gYSByZWdyZXNzaW9uIHdpdGhvdXQgYSAicmVhbCIgY29uc3RhbnQgb2Ygb25lczoNCg0KYGBge3J9DQojIENyZWF0ZSByZXNpZHVhbHMNCnJlc195ID0geS1taGF0DQpyZXNfdyA9IHctZWhhdA0KDQojIE1vZGlmeSBjb3ZhcmlhdGVzIChtdWx0aXBseSBlYWNoIGNvbHVtbiBpbmNsdWRpbmcgY29uc3RhbnQgd2l0aCByZXNpZHVhbCkNCnhfd2MgPSBjYmluZChyZXAoMSxuKSx4KQ0KY29sbmFtZXMoeF93YykgPSBjKCJJbnRlcmNlcHQiLHBhc3RlMCgiWCIsMTpwKSkNCnhzdGFyID0geF93YyAqIHJlc193DQojIFJlZ3Jlc3Mgb3V0Y29tZSByZXNpZHVhbCBvbiBtb2RpZmllZCBjb3ZhcmlhdGVzDQpzdW1tYXJ5KGxtKHJlc195IH4gMCArIHhzdGFyKSkNCmBgYA0KDQpUaGUgZXN0aW1hdGVkIGNvZWZmaWNpZW50cyBhcmUgcmVsYXRpdmVseSBjbG9zZSB0byB0aGVpciB0cnVlIHZhbHVlcy4NCg0KPGJyPg0KDQojIyMgMi4gUHNldWRvLW91dGNvbWUgYW5kIHdlaWdodHMNCg0KVGhlIG1vcmUgZ2VuZXJpYyBhbHRlcm5hdGl2ZSBpcyB0byB1c2UgdGhlIHVubW9kaWZpZWQgY292YXJpYXRlcyBpbiBhIHdlaWdodGVkIHJlZ3Jlc3Npb24gd2l0aCBwc2V1ZG8gb3V0Y29tZXM6DQokJFxoYXR7XGJldGF9XntybH0gID0gYXJnbWluX3tcYmV0YX0gXHN1bV97aT0xfV5OIFx1bmRlcmJyYWNleyhXX2kgLSBcaGF0e2V9KFhfaSkpXjJ9X3tcdGV4dHt3ZWlnaHR9fSBcbGVmdChcdW5kZXJicmFjZXtcZnJhY3tZX2kgLSBcaGF0e219KFhfaSl9e1dfaSAtIFxoYXR7ZX0oWF9pKX19X3tcdGV4dHtwc2V1ZG8tb3V0Y29tZX19IC0gIFhfaSdcYmV0YSBccmlnaHQpXjIkJA0KDQpgYGB7cn0NCiMgQ3JlYXRlIHBzZXVkby1vdXRjb21lIChvdXRjb21lIHJlcyBkaXZpZGVkIGJ5IHRyZWF0bWVudCByZXMpDQpwc2V1ZG9fcmwgPSByZXNfeSAvIHJlc193DQoNCiMgQ3JlYXRlIHdlaWdodHMNCndlaWdodHNfcmwgPSByZXNfd14yDQoNCiMgV2VpZ2h0ZWQgcmVncmVzc2lvbiBvZiBwc2V1ZG8tb3V0Y29tZSBvbiBjb3ZhcmlhdGVzDQpyb2xzX2ZpdCA9IGxtKHBzZXVkb19ybCB+IHgsIHdlaWdodHM9d2VpZ2h0c19ybCkNCnN1bW1hcnkocm9sc19maXQpDQpyX29sc19lc3QgPSBwcmVkaWN0KHJvbHNfZml0KQ0KYGBgDQoNClRoaXMgcHJvZHVjZXMgdGhlIHNhbWUgcmVzdWx0cyBhcyB0aGUgbW9kaWZpZWQgY292YXJpYXRlIGFzIHRoZSBlcXVhbGl0eSBvZiBhbGwgY29lZmZpY2llbnRzIHNob3dzOg0KDQpgYGB7cn0NCiMgdGVzdCBpZiBhbGwgdmFsdWVzIGFyZSBlcXVhbA0KYWxsLmVxdWFsKGFzLm51bWVyaWMocm9sc19maXQkY29lZmZpY2llbnRzKSwgYXMubnVtZXJpYyhsbShyZXNfeSB+IDAgKyB4c3RhcikkY29lZmZpY2llbnRzKSkNCmBgYA0KDQpXZSBzdG9yZSB0aGUgZml0dGVkIHZhbHVlcyAkXGhhdHtcdGF1fSh4KSA9IHgnXGhhdHtcYmV0YX1ee3JsfSQgYXMgZXN0aW1hdGVzIG9mIHRoZSBDQVRFcyB0byBjb21wYXJlIHRoZW0gbGF0ZXIgd2l0aCB0aGUgdHJ1dGggYW5kIG90aGVyIG1ldGhvZHMuDQoNCmBgYHtyfQ0KIyBFc3RpbWF0ZSBDQVRFcw0Kcl9vbHNfZXN0ID0gcHJlZGljdChyb2xzX2ZpdCkNCmBgYA0KDQo8YnI+DQoNCiMjIEhhbmRjb2RlZCBSLWxlYXJuZXIgd2l0aCBMYXNzbyBsYXN0IHN0ZXANCg0KVGhlIHByZXZpb3VzIGV4ZXJjaXNlIHNob3VsZCBoYXZlIGNvbnZpbmNlZCB5b3UgdGhhdCB0aGVzZSBkaWZmZXJlbnQgdHJhbnNmb3JtYXRpb25zIHdvcmsuIEkgcHJlZmVyIHRoZSBzZWNvbmQgb3B0aW9uLCBidXQgdGhpcyBpcyBhIG1hdHRlciBvZiB0YXN0ZS4gSW5zdGVhZCBvZiBPTFMgd2UgY2FuIHNpbWlsYXJseSBlc3RpbWF0ZSBhIHdlaWdodGVkIExhc3NvIHdpdGggdGhlIHBzZXVkby1vdXRjb21lcyB3aGVyZSB3ZSBhZ2FpbiBzYXZlIHRoZSBlc3RpbWF0ZWQgQ0FURXMgZm9yIGxhdGVyIGNvbXBhcmlzb246DQoNCmBgYHtyfQ0KIyBSLWxlYXJuZXIgd2l0aCBMYXNzbw0Kcmxhc3NvX2hhbmQgPSBjdi5nbG1uZXQoeCxwc2V1ZG9fcmwsd2VpZ2h0cz13ZWlnaHRzX3JsKQ0KcGxvdChybGFzc29faGFuZCkNCnJsYXNzb19oYW5kID0gcHJlZGljdChybGFzc29faGFuZCxuZXd4ID0geCwgcyA9ICJsYW1iZGEubWluIikNCmBgYA0KPGJyPg0KDQojIyBgcmxlYXJuZXJgIHBhY2thZ2UNCg0KVGhlIFItbGVhcm5lciBjYW4gYmUgbW9yZSBjb252ZW5pZW50bHkgdXNlZCB2aWEgdGhlIGBybGVhcm5lcmAgcGFja2FnZS4gSXQgcHJvdmlkZXMgc2V2ZXJhbCBvcHRpb25zLCBidXQgd2UgZm9jdXMgb24gTGFzc28gdG9kYXksIGJlY2F1c2UgdGhlIEJvb3N0aW5nIGFuZCBLZXJuZWwgb3B0aW9ucyB0YWtlIHF1aXRlIHNvbWUgdGltZSB0byBydW4uIE5vdGUgdGhhdCB0aGlzIGltcGxlbWVudGF0aW9uIHVzZXMgbm93IExhc3NvIHRvIGVzdGltYXRlIHRoZSBub25saW5lYXIgbnVpc2FuY2UgZnVuY3Rpb25zLCB3aGljaCBjb3VsZCBkZXRlcmlvcmF0ZSB0aGUgcGVyZm9ybWFuY2UuIFdlIHN0b3JlIHRoZSBwcmVkaWN0aW9ucyBhbmQgd2lsbCBjaGVjayBiZWxvdzoNCg0KYGBge3IsIHdhcm5pbmcgPSBGfQ0KIyBVc2luZyB0aGUgcmxlYXJuZXIgcGFja2FnZQ0Kcmxhc3NvX2ZpdCA9IHJsYXNzbyh4LCB3LCB5KQ0Kcmxhc3NvX2VzdCA9IHByZWRpY3Qocmxhc3NvX2ZpdCwgeCkNCmBgYA0KDQo8YnI+DQo8YnI+DQoNCiMjIERSLWxlYXJuZXIgdmlhIGBjYXVzYWxETUxgIHBhY2thZ2UNCg0KVGhlIERSLWxlYXJuZXIgdXNlcyB0aGUgaG9wZWZ1bGx5IGJ5IG5vdyBmYW1pbGlhciBwc2V1ZG8tb3V0Y29tZSBvZiB0aGUgQUlQVyBBVEUgZXN0aW1hdG9yIGFuZCB3ZSB3aWxsIG5vdCBoYW5kY29kZSBpdCBhZ2FpbiAoc2VlIG5vdGVib29rIFtTTkJfR0FURV0oaHR0cHM6Ly9tY2tuYXVzLmdpdGh1Yi5pby9hc3NldHMvbm90ZWJvb2tzL1NOQi9TTkJfR0FURS5uYi5odG1sKSBhbmQgcmVwbGFjZSB0aGUgZmluYWwgT0xTIG9yIEtlcm5lbCByZWdyZXNzaW9uIHN0ZXAgd2l0aCB0aGUgc3VwZXJ2aXNlZCBNTCBtZXRob2Qgb2YgeW91ciBjaG9pY2UpLg0KDQpUaGUgYGNhdXNhbERNTGAgcGFja2FnZSBpbXBsZW1lbnRzIHRoZSBEUi1sZWFybmVyIHdpdGggdGhlIHJlcXVpcmVkIGNyb3NzLWZpdHRpbmcgcHJvY2VkdXJlLiBJdCBpcyBhIGJpdCBtb3JlIGNvbXBsaWNhdGVkIHRoYW4gd2hhdCB3ZSBkaXNjdXNzZWQgc28gZmFyLCBidXQgdXNlcyB0aGUgc2FtZSBpZGVhcyB3ZSBoYXZlIGxlYXJuZWQuIEZvciBkZXRhaWxzIHNlZSB0aGUgQXBwZW5kaXggb2YgW0tuYXVzICgyMDIwKV0oaHR0cHM6Ly9hcnhpdi5vcmcvYWJzLzIwMDMuMDMxOTEpLg0KDQpUaGUgZGVmYXVsdCB2ZXJzaW9uIG9mIHRoZSBgZHJfbGVhcm5lcmAgaXMgaW1wbGVtZW50ZWQgd2l0aCBhbiB1bnR1bmVkIGhvbmVzdCBSYW5kb20gRm9yZXN0IGF0IGFsbCBzdGFnZXMuIFRoaXMgbWVhbnMgaXQgaXMgd2VsbC1zdWl0ZWQgZm9yIHRoZSBudWlzYW5jZSBwYXJhbWV0ZXJzLCBidXQgc2hvdWxkIGhhdmUgYSBoYXJkZXIgdGltZSB3aXRoIHRoZSBsaW5lYXIgaGV0ZXJvZ2VuZW91cyBlZmZlY3RzLg0KDQoNCmBgYHtyfQ0KIyBEUi1sZWFybmVyDQpkcl9lc3QgPSBkcl9sZWFybmVyKHksdyx4KQ0KYGBgDQoNCg0KTm93IGl0IGlzIHRpbWUgdG8gY29tcGFyZSB0aGUgZXN0aW1hdGVkIENBVEVzIG9mIHRoZSBlc3RpbWF0b3JzIHVzZWQgc28gZmFyIHRvIHRoZSwgaGVyZSBrbm93biwgdHJ1ZSBDQVRFczoNCg0KYGBge3J9DQojIFN0b3JlIGFuZCBwbG90IHByZWRpY3Rpb25zDQpyZXN1bHRzMWsgPSBjYmluZCh0YXUscl9vbHNfZXN0LHJsYXNzb19oYW5kLHJsYXNzb19lc3QsZHJfZXN0JGNhdGVzKQ0KY29sbmFtZXMocmVzdWx0czFrKSA9IGMoIlRydWUiLCJSTCBPTFMiLCJSTCBMYXNzbyBoYW5kIiwicmxhc3NvIiwiRFIgUkYiKQ0KcGFpcnMucGFuZWxzKHJlc3VsdHMxayxtZXRob2QgPSAicGVhcnNvbiIpDQpgYGANCg0KQWxsIGVzdGltYXRvcnMgY29tZSBxdWl0ZSBjbG9zZSwgYnV0IGVzcGVjaWFsbHkgdGhlIERSLWxlYXJuZXIgc2hvd3MgYSBsb3dlciBjb3JyZWxhdGlvbiB3aXRoIHRoZSB0cnVlIENBVEVzLiBUaGlzIGlzIG5vdCB1bmV4cGVjdGVkIGJlY2F1c2UgaXQgdXNlcyBSYW5kb20gRm9yZXN0IHRvIGFwcHJveGltYXRlIGEgbGluZWFyIENBVEUgaW4gaXRzIGZpbmFsIHN0ZXAsIHdoaWxlIHRoZSBvdGhlciBtZXRob2RzIHVzZSBPTFMgLyBMYXNzbyB0aGF0IGFyZSBleHBlY3RlZCB0byBwZXJmb3JtIHdlbGwgd2l0aCBsaW5lYXIgQ0FURSBmdW5jdGlvbnMuDQoNCkludGVyZXN0aW5nbHkgd2hlbiBjaGVja2luZyB0aGUgTVNFLCB3ZSBzZWUgdGhhdCB0aGUgc2VlbWluZ2x5IGhpZ2ggcGVyZm9ybWluZyBgcmxhc3NvYCBpbXBsZW1lbnRhdGlvbiBwZXJmb3JtcyBtdWNoIHdvcnNlIGluIHRlcm1zIG9mIE1TRSB0aGFuIHRoZSBoaWdoIGNvcnJlbGF0aW9uIHdvdWxkIHN1Z2dlc3QuIEl0IGdldHMgdGhlIHNvcnRpbmcgcmlnaHQsIGJ1dCBub3QgdGhlIGxldmVscyBhcyBhIGxvb2sgYXQgdGhlIGF4ZXMgaW5kaWNhdGVzLg0KDQpgYGB7cn0NCiMgQ29tcGFyZSBNU0UNCmRhdGEuZnJhbWUoTVNFID0gY29sTWVhbnMoIChyZXN1bHRzMWtbLC0xXS1jKHRhdSkpXjIgKSAsDQogICAgICAgICAgIE1ldGhvZCA9IGZhY3Rvcihjb2xuYW1lcyhyZXN1bHRzMWspWy0xXSkgKSAlPiUNCiAgZ2dwbG90KGFlcyh4PU1ldGhvZCx5PU1TRSkpICsgZ2VvbV9wb2ludChzaXplID0gMikgKyANCiAgZ2d0aXRsZShwYXN0ZSh0b1N0cmluZyhuKSwib2JzZXJ2YXRpb25zIikpICsgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCkNCmBgYA0KDQoNCjxicj4NCg0KIyMgV2l0aCBlbnNlbWJsZS9TdXBlckxlYXJuZXINCg0KQSBjb21wdXRhdGlvbmFsbHkgbW9yZSBleHBlbnNpdmUsIGJ1dCBhZ25vc3RpYyBhcHByb2FjaCBpcyB0byBjcmVhdGUgYW4gZW5zZW1ibGUgb3IgU3VwZXJMZWFybmVyLiBUaGUgaWRlYSBpcyB0aGF0IHdlIGZvcm0gcHJlZGljdGlvbnMgdmlhIHNldmVyYWwgZGlmZmVyZW50IHN1cGVydmlzZWQgTUwgbWV0aG9kcyBhbmQgdXNlIGEgd2VpZ2h0ZWQgYXZlcmFnZSBvZiB0aGVpciBwcmVkaWN0aW9ucyBhcyBmaW5hbCBwcmVkaWN0aW9uLiBUaGVzZSB3ZWlnaHRzIGFyZSBjaG9zZW4gaW4gYSBkYXRhLWRyaXZlbiB3YXkgdmlhIGNyb3NzLXZhbGlkYXRpb24gdG8gZmlndXJlIG91dCB3aGljaCBwcmVkaWN0b3JzIHdvcmsgYmVzdCBmb3IgdGhlIHByZWRpY3Rpb24gbW9kZWwgYXQgaGFuZCAoc2VlIFtOYWltaSBhbmQgQmFsemVyICgyMDE4KV0oaHR0cHM6Ly9saW5rLnNwcmluZ2VyLmNvbS9hcnRpY2xlLzEwLjEwMDcvczEwNjU0LTAxOC0wMzkwLXopIGZvciBhIG5pY2UgaW50cm9kdWN0aW9uIHdpdGggZXhhbXBsZXMgaW4gUikuDQoNCkkgYW0gbm90IGF3YXJlIG9mIHN1Y2ggYW4gaW1wbGVtZW50YXRpb24gZm9yIHRoZSBSLWxlYXJuZXIuIEhvd2V2ZXIsIHRoZSBgZHJfbGVhcm5lcmAgYWxsb3dzIHRvIHVzZSBzdWNoIGFuIGVuc2VtYmxlIG9mIG1ldGhvZHMuIFdlIHNwZWNpZnkgaXQgdG8gdXNlIGVuc2VtYmxlcyBvZiBzZXZlcmFsIG1ldGhvZHMuDQoNCkFzIHRoZSB0cmVhdG1lbnQgaXMgYmluYXJ5LCB3ZSBpbmNsdWRlIHRoZSBmb2xsb3dpbmcgbWV0aG9kcyB0byBlc3RpbWF0ZSB0aGUgcHJvcGVuc2l0eSBzY29yZToNCg0KLSB0aGUgbWVhbiAod291bGQgYmUgcmVsZXZhbnQgaWYgbm8gc2VsZWN0aW9uIGludG8gdHJlYXRtZW50KQ0KDQotIHNlbGYtdHVuZWQgUmFuZG9tIEZvcmVzdA0KDQotIGxvZ2lzdGljIFJpZGdlIHJlZ3Jlc3Npb24NCg0KLSBsb2dpc3RpYyBMYXNzbyByZWdyZXNzaW9uDQoNCkZvciBvdXRjb21lIG51aXNhbmNlcyBhbmQgdGhlIGhldGVyb2dlbmVvdXMgZWZmZWN0LCB3ZSB1c2UNCg0KLSB0aGUgbWVhbiAod291bGQgYmUgcmVsZXZhbnQgaW4gY2FzZSBvZiBlZmZlY3QgaGV0ZXJvZ2VuZWl0eSkNCg0KLSBzZWxmLXR1bmVkIFJhbmRvbSBGb3Jlc3QNCg0KLSBPTFMgcmVncmVzc2lvbg0KDQotIFJpZGdlIHJlZ3Jlc3Npb24NCg0KLSBMYXNzbyByZWdyZXNzaW9uDQoNCg0KDQpgYGB7cn0NCiMjIENyZWF0ZSBjb21wb25lbnRzIG9mIGVuc2VtYmxlDQojIEdlbmVyYWwgbWV0aG9kcw0KbWVhbiA9IGNyZWF0ZV9tZXRob2QoIm1lYW4iKQ0KZm9yZXN0ID0gIGNyZWF0ZV9tZXRob2QoImZvcmVzdF9ncmYiLGFyZ3M9bGlzdCh0dW5lLnBhcmFtZXRlcnMgPSAiYWxsIixob25lc3R5PUYpKQ0KDQojIFBzY29yZSBzcGVjaWZpYyBjb21wb25lbnRzDQpyaWRnZV9iaW4gPSBjcmVhdGVfbWV0aG9kKCJyaWRnZSIsYXJncz1saXN0KGZhbWlseSA9ICJiaW5vbWlhbCIpKQ0KbGFzc29fYmluID0gY3JlYXRlX21ldGhvZCgibGFzc28iLGFyZ3M9bGlzdChmYW1pbHkgPSAiYmlub21pYWwiKSkNCg0KIyBPdXRjb21lIHNwZWNpZmljIGNvbXBvbmVudHMNCm9scyA9IGNyZWF0ZV9tZXRob2QoIm9scyIpDQpyaWRnZSA9IGNyZWF0ZV9tZXRob2QoInJpZGdlIikNCmxhc3NvID0gY3JlYXRlX21ldGhvZCgibGFzc28iKQ0KDQojIERSLWxlYXJuZXIgd2l0aCBlbnNlbWJsZQ0KZHJfZW5zID0gZHJfbGVhcm5lcih5LHcseCxtbF93PWxpc3QobWVhbixmb3Jlc3QscmlkZ2VfYmluLGxhc3NvX2JpbiksDQogICAgICAgICAgICAgICAgICAgIG1sX3kgPSBsaXN0KG1lYW4sZm9yZXN0LG9scyxyaWRnZSxsYXNzbyksDQogICAgICAgICAgICAgICAgICAgIG1sX3RhdSA9IGxpc3QobWVhbixmb3Jlc3Qsb2xzLHJpZGdlLGxhc3NvKSxxdWlldD1UKQ0KYGBgDQoNCg0KTGV0J3MgY2hlY2sgaG93IHRoaXMgcGVyZm9ybXMuDQoNCg0KYGBge3J9DQojIEFkZCBhbmQgcGxvdCBwcmVkaWN0aW9ucw0KbGFiZWxfbWV0aG9kID0gYygiUkwgT0xTIiwiUkwgTGFzc28gaGFuZCIsInJsYXNzbyIsIkRSIFJGIiwiRFIgRW5zIikNCnJlc3VsdHMxayA9IGNiaW5kKHRhdSxyX29sc19lc3Qscmxhc3NvX2hhbmQscmxhc3NvX2VzdCxkcl9lc3QkY2F0ZXMsZHJfZW5zJGNhdGVzKQ0KY29sbmFtZXMocmVzdWx0czFrKSA9IGMoIlRydWUiLGxhYmVsX21ldGhvZCkNCnBhaXJzLnBhbmVscyhyZXN1bHRzMWssbWV0aG9kID0gInBlYXJzb24iKQ0KDQojIENvbXBhcmUgTVNFDQpkYXRhLmZyYW1lKE1TRSA9IGNvbE1lYW5zKCAocmVzdWx0czFrWywtMV0tYyh0YXUpKV4yICkgLA0KICAgICAgICAgICBNZXRob2QgPSBmYWN0b3IobGFiZWxfbWV0aG9kLGxldmVscz1sYWJlbF9tZXRob2QpICkgJT4lDQogIGdncGxvdChhZXMoeD1NZXRob2QseT1NU0UpKSArIGdlb21fcG9pbnQoc2l6ZSA9IDIpICsgDQogIGdndGl0bGUocGFzdGUodG9TdHJpbmcobiksIm9ic2VydmF0aW9ucyIpKSArIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDApDQpgYGANCg0KSXQgZG9lcyBub3QgcGVyZm9ybSBhcyB3ZWxsIGFzIHRoZSBoYW5kY29kZWQgUi1sZWFybmVyIHRoYXQgdXNlcyBzdWl0YWJsZSBtZXRob2RzIGZvciBlYWNoIGNvbXBvbmVudC4gSG93ZXZlciwgaXQgaW1wcm92ZXMgYWxyZWFkeSB1cG9uIHRoZSBvbmx5IExhc3NvIFItbGVhcm5lciBhbmQgdGhlIG9ubHkgUmFuZG9tIEZvcmVzdCBEUi1sZWFybmVyIGluIHRlcm1zIG9mIE1TRS4gSSB0aGluayB0aGlzIGlzIGltcHJlc3NpdmUgZ2l2ZW4gdGhhdCBvbmx5IDEwMDAgb2JzZXJ2YXRpb25zIGFyZSBhdmFpbGFibGUgdG8gZmlndXJlIG91dCB3aGljaCBtZXRob2RzIHdvcmsgYmVzdC4gSG93ZXZlciB0aGlzIHNob3VsZCBiZWNvbWUgYmV0dGVyIGFuZCBiZXR0ZXIgaWYgd2UgaW5jcmVhc2UgdGhlIHNhbXBsZSBzaXplLg0KDQpUaHVzIHdlIHJlcnVuIHRoZSBhbmFseXNpcyB3aXRoIGEgZHJhdyBvZiA0MDAwIGFuZCBvZiAxNjAwMCBvYnNlcnZhdGlvbnMsIHdoaWNoIHJ1bnMgb3ZlciB0d28gaG91cnMgb24gbXkgbGFwdG9wLg0KDQo8YnI+DQoNCiMjIDQwMDAgb2JzZXJ2YXRpb25zDQoNCg0KYGBge3J9DQojIEluY3JlYXNlIHNhbXBsZSBzaXplDQpuID0gNDAwMA0KDQojIERyYXcgc2FtcGxlDQp4ID0gbWF0cml4KHJ1bmlmKG4qcCwtcGkscGkpLG5jb2w9cCkNCnRhdSA9IHggJSolIHJobw0KdyA9IHJiaW5vbShuLDEsZSh4WywxXSkpDQp5ID0gbTAoeFssMV0pICsgdyp0YXUgKyBybm9ybShuLDAsMSkNCg0KIyBIYW5kY29kZWQgUi1sZWFybmVyIHdpdGggT0xTIGxhc3Qgc3RhZ2UNCiMgQyZQIHcvbyBjb21tZW50cyBmcm9tIGFib3ZlDQptaGF0ID0gZWhhdCA9IHJlcChOQSxuKQ0KaW5kZXhfczEgPSBzYW1wbGUoMTpuLG4vMikNCngxID0geFtpbmRleF9zMSxdDQp3MSA9IHdbaW5kZXhfczFdDQp5MSA9IHlbaW5kZXhfczFdDQp4MiA9IHhbLWluZGV4X3MxLF0NCncyID0gd1staW5kZXhfczFdDQp5MiA9IHlbLWluZGV4X3MxXQ0KcmYgPSByZWdyZXNzaW9uX2ZvcmVzdCh4MSx3MSx0dW5lLnBhcmFtZXRlcnMgPSAiYWxsIikNCmVoYXRbLWluZGV4X3MxXSA9IHByZWRpY3QocmYsbmV3ZGF0YT14MikkcHJlZGljdGlvbnMNCnJmID0gcmVncmVzc2lvbl9mb3Jlc3QoeDEseTEsdHVuZS5wYXJhbWV0ZXJzID0gImFsbCIpDQptaGF0Wy1pbmRleF9zMV0gPSBwcmVkaWN0KHJmLG5ld2RhdGE9eDIpJHByZWRpY3Rpb25zDQpyZiA9IHJlZ3Jlc3Npb25fZm9yZXN0KHgyLHcyLHR1bmUucGFyYW1ldGVycyA9ICJhbGwiKQ0KZWhhdFtpbmRleF9zMV0gPSBwcmVkaWN0KHJmLG5ld2RhdGE9eDEpJHByZWRpY3Rpb25zDQpyZiA9IHJlZ3Jlc3Npb25fZm9yZXN0KHgyLHkyLHR1bmUucGFyYW1ldGVycyA9ICJhbGwiKQ0KbWhhdFtpbmRleF9zMV0gPSBwcmVkaWN0KHJmLG5ld2RhdGE9eDEpJHByZWRpY3Rpb25zDQpyZXNfeSA9IHktbWhhdA0KcmVzX3cgPSB3LWVoYXQNCnBzZXVkb19ybCA9IHJlc195IC8gcmVzX3cNCndlaWdodHNfcmwgPSByZXNfd14yDQpyb2xzX2ZpdCA9IGxtKHBzZXVkb19ybCB+IHgsIHdlaWdodHM9d2VpZ2h0c19ybCkNCnJfb2xzX2VzdCA9IHByZWRpY3Qocm9sc19maXQpDQoNCiMgSGFuZGNvZGVkIFItbGVhcm5lciB3aXRoIExhc3NvIGxhc3Qgc3RhZ2UNCnJsYXNzb19oYW5kID0gY3YuZ2xtbmV0KHgscHNldWRvX3JsLHdlaWdodHM9d2VpZ2h0c19ybCkNCnJsYXNzb19oYW5kID0gcHJlZGljdChybGFzc29faGFuZCxuZXd4ID0geCwgcyA9ICJsYW1iZGEubWluIikNCg0KIyBVc2luZyB0aGUgcmxlYXJuZXIgcGFja2FnZQ0Kcmxhc3NvX2ZpdCA9IHJsYXNzbyh4LCB3LCB5KQ0Kcmxhc3NvX2VzdCA9IHByZWRpY3Qocmxhc3NvX2ZpdCwgeCkNCg0KIyBEUi1sZWFybmVyDQpkcl9lc3QgPSBkcl9sZWFybmVyKHksdyx4KQ0KDQojIERSLWxlYXJuZXIgd2l0aCBlbnNlbWJsZQ0KZHJfZW5zID0gZHJfbGVhcm5lcih5LHcseCxtbF93PWxpc3QobWVhbixmb3Jlc3QscmlkZ2VfYmluLGxhc3NvX2JpbiksDQogICAgICAgICAgICAgICAgICAgIG1sX3kgPSBsaXN0KG1lYW4sZm9yZXN0LG9scyxyaWRnZSxsYXNzbyksDQogICAgICAgICAgICAgICAgICAgIG1sX3RhdSA9IGxpc3QobWVhbixmb3Jlc3Qsb2xzLHJpZGdlLGxhc3NvKSxxdWlldD1UKQ0KIyBBZGQgYW5kIHBsb3QgcHJlZGljdGlvbnMNCmxhYmVsX21ldGhvZCA9IGMoIlJMIE9MUyIsIlJMIExhc3NvIGhhbmQiLCJybGFzc28iLCJEUiBSRiIsIkRSIEVucyIpDQpyZXN1bHRzNGsgPSBjYmluZCh0YXUscl9vbHNfZXN0LHJsYXNzb19oYW5kLHJsYXNzb19lc3QsZHJfZXN0JGNhdGVzLGRyX2VucyRjYXRlcykNCmNvbG5hbWVzKHJlc3VsdHM0aykgPSBjKCJUcnVlIixsYWJlbF9tZXRob2QpDQpwYWlycy5wYW5lbHMocmVzdWx0czRrLG1ldGhvZCA9ICJwZWFyc29uIikNCmBgYA0KDQpgYGB7cn0NCiMgQ29tcGFyZSBNU0UNCmRhdGEuZnJhbWUoTVNFID0gY29sTWVhbnMoIChyZXN1bHRzNGtbLC0xXS1jKHRhdSkpXjIgKSAsDQogICAgICAgICAgIE1ldGhvZCA9IGZhY3RvcihsYWJlbF9tZXRob2QsbGV2ZWxzPWxhYmVsX21ldGhvZCkgKSAlPiUNCiAgZ2dwbG90KGFlcyh4PU1ldGhvZCx5PU1TRSkpICsgZ2VvbV9wb2ludChzaXplID0gMikgKyANCiAgZ2d0aXRsZShwYXN0ZSh0b1N0cmluZyhuKSwib2JzZXJ2YXRpb25zIikpICsgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCkNCg0KYGBgDQoNCjxicj4NCg0KIyMgMTYwMDAgb2JzZXJ2YXRpb25zDQoNCmBgYHtyfQ0KIyBJbmNyZWFzZSBzYW1wbGUgc2l6ZQ0KbiA9IDE2MDAwDQoNCiMgRHJhdyBzYW1wbGUNCnggPSBtYXRyaXgocnVuaWYobipwLC1waSxwaSksbmNvbD1wKQ0KdGF1ID0geCAlKiUgcmhvDQp3ID0gcmJpbm9tKG4sMSxlKHhbLDFdKSkNCnkgPSBtMCh4WywxXSkgKyB3KnRhdSArIHJub3JtKG4sMCwxKQ0KDQojIEhhbmRjb2RlZCBSLWxlYXJuZXIgd2l0aCBPTFMgbGFzdCBzdGFnZQ0KIyBDJlAgdy9vIGNvbW1lbnRzIGZyb20gYWJvdmUNCm1oYXQgPSBlaGF0ID0gcmVwKE5BLG4pDQppbmRleF9zMSA9IHNhbXBsZSgxOm4sbi8yKQ0KeDEgPSB4W2luZGV4X3MxLF0NCncxID0gd1tpbmRleF9zMV0NCnkxID0geVtpbmRleF9zMV0NCngyID0geFstaW5kZXhfczEsXQ0KdzIgPSB3Wy1pbmRleF9zMV0NCnkyID0geVstaW5kZXhfczFdDQpyZiA9IHJlZ3Jlc3Npb25fZm9yZXN0KHgxLHcxLHR1bmUucGFyYW1ldGVycyA9ICJhbGwiKQ0KZWhhdFstaW5kZXhfczFdID0gcHJlZGljdChyZixuZXdkYXRhPXgyKSRwcmVkaWN0aW9ucw0KcmYgPSByZWdyZXNzaW9uX2ZvcmVzdCh4MSx5MSx0dW5lLnBhcmFtZXRlcnMgPSAiYWxsIikNCm1oYXRbLWluZGV4X3MxXSA9IHByZWRpY3QocmYsbmV3ZGF0YT14MikkcHJlZGljdGlvbnMNCnJmID0gcmVncmVzc2lvbl9mb3Jlc3QoeDIsdzIsdHVuZS5wYXJhbWV0ZXJzID0gImFsbCIpDQplaGF0W2luZGV4X3MxXSA9IHByZWRpY3QocmYsbmV3ZGF0YT14MSkkcHJlZGljdGlvbnMNCnJmID0gcmVncmVzc2lvbl9mb3Jlc3QoeDIseTIsdHVuZS5wYXJhbWV0ZXJzID0gImFsbCIpDQptaGF0W2luZGV4X3MxXSA9IHByZWRpY3QocmYsbmV3ZGF0YT14MSkkcHJlZGljdGlvbnMNCnJlc195ID0geS1taGF0DQpyZXNfdyA9IHctZWhhdA0KcHNldWRvX3JsID0gcmVzX3kgLyByZXNfdw0Kd2VpZ2h0c19ybCA9IHJlc193XjINCnJvbHNfZml0ID0gbG0ocHNldWRvX3JsIH4geCwgd2VpZ2h0cz13ZWlnaHRzX3JsKQ0Kcl9vbHNfZXN0ID0gcHJlZGljdChyb2xzX2ZpdCkNCg0KIyBIYW5kY29kZWQgUi1sZWFybmVyIHdpdGggTGFzc28gbGFzdCBzdGFnZQ0Kcmxhc3NvX2hhbmQgPSBjdi5nbG1uZXQoeCxwc2V1ZG9fcmwsd2VpZ2h0cz13ZWlnaHRzX3JsKQ0Kcmxhc3NvX2hhbmQgPSBwcmVkaWN0KHJsYXNzb19oYW5kLG5ld3ggPSB4LCBzID0gImxhbWJkYS5taW4iKQ0KDQojIFVzaW5nIHRoZSBybGVhcm5lciBwYWNrYWdlDQpybGFzc29fZml0ID0gcmxhc3NvKHgsIHcsIHkpDQpybGFzc29fZXN0ID0gcHJlZGljdChybGFzc29fZml0LCB4KQ0KDQojIERSLWxlYXJuZXINCmRyX2VzdCA9IGRyX2xlYXJuZXIoeSx3LHgpDQoNCiMgRFItbGVhcm5lciB3aXRoIGVuc2VtYmxlDQpkcl9lbnMgPSBkcl9sZWFybmVyKHksdyx4LG1sX3c9bGlzdChtZWFuLGZvcmVzdCxyaWRnZV9iaW4sbGFzc29fYmluKSwNCiAgICAgICAgICAgICAgICAgICAgbWxfeSA9IGxpc3QobWVhbixmb3Jlc3Qsb2xzLHJpZGdlLGxhc3NvKSwNCiAgICAgICAgICAgICAgICAgICAgbWxfdGF1ID0gbGlzdChtZWFuLGZvcmVzdCxvbHMscmlkZ2UsbGFzc28pLHF1aWV0PVQpDQojIEFkZCBhbmQgcGxvdCBwcmVkaWN0aW9ucw0KbGFiZWxfbWV0aG9kID0gYygiUkwgT0xTIiwiUkwgTGFzc28gaGFuZCIsInJsYXNzbyIsIkRSIFJGIiwiRFIgRW5zIikNCnJlc3VsdHMxNmsgPSBjYmluZCh0YXUscl9vbHNfZXN0LHJsYXNzb19oYW5kLHJsYXNzb19lc3QsZHJfZXN0JGNhdGVzLGRyX2VucyRjYXRlcykNCmNvbG5hbWVzKHJlc3VsdHMxNmspID0gYygiVHJ1ZSIsbGFiZWxfbWV0aG9kKQ0KcGFpcnMucGFuZWxzKHJlc3VsdHMxNmssbWV0aG9kID0gInBlYXJzb24iKQ0KYGBgDQoNCmBgYHtyfQ0KIyBDb21wYXJlIE1TRQ0KZGF0YS5mcmFtZShNU0UgPSBjb2xNZWFucyggKHJlc3VsdHMxNmtbLC0xXS1jKHRhdSkpXjIgKSAsDQogICAgICAgICAgIE1ldGhvZCA9IGZhY3RvcihsYWJlbF9tZXRob2QsbGV2ZWxzPWxhYmVsX21ldGhvZCkgKSAlPiUNCiAgZ2dwbG90KGFlcyh4PU1ldGhvZCx5PU1TRSkpICsgZ2VvbV9wb2ludChzaXplID0gMikgKyANCiAgZ2d0aXRsZShwYXN0ZSh0b1N0cmluZyhuKSwib2JzZXJ2YXRpb25zIikpICsgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCkNCmBgYA0KDQpXaXRoIGluY3JlYXNpbmcgc2FtcGxlIHNpemUsIHRoZSBEUi1sZWFybmVyIHdpdGggZW5zZW1ibGUgbWV0aG9kcyBjYXRjaGVzIHVwIHdpdGggdGhlIG1ldGhvZHMgdGFpbG9yZWQgZm9yIHRoZSBzcGVjaWZpYyBER1AuIGBybGFzc29gIGFuZCB0aGUgcHVyZWx5IFJhbmRvbSBGb3Jlc3QgYmFzZWQgRFItbGVhcm5lciBjb250aW51b3VzbHkgaW1wcm92ZSBidXQgYXJlIG5vdCBjb21wZXRpdGl2ZS4NCg0KVGhpcyBoaWdobGlnaHRzIHRoYXQgdXNpbmcgZW5zZW1ibGUgbGVhcm5lcnMgYXMgYW4gYWdub3N0aWMgd2F5IHRvIG1hbmFnZSB0aGUgcHJlZGljdGlvbiB0YXNrcyBpbiBtZXRhLWxlYXJuZXJzIHdvcmtzIHF1aXRlIHdlbGwuIEVzcGVjaWFsbHkgaW4gcHJhY3RpY2UgdGhpcyBjYW4gYmUgYSBiaWcgYWR2YW50YWdlIGJlY2F1c2UgaXQgaXMgKmEgcHJpb3JpKiBub3QgY2xlYXIgd2hpY2ggbWV0aG9kcyBiZXN0IGFwcHJveGltYXRlIHRoZSBudWlzYW5jZSBwYXJhbWV0ZXJzIGFuZCBoZXRlcm9nZW5laXR5LiBUaGUgYmlnIGRvd25zaWRlIGlzIG9mIGNvdXJzZSB0aGF0IGl0IHRha2VzIG11Y2ggbXVjaCBsb25nZXIgdG8gY29tcHV0ZS4NCg0KPGJyPg0KPGJyPg0KDQoNCg0KIyMgVGFrZS1hd2F5DQogDQogLSBNZXRhLWxlYXJuZXJzIGFyZSBqdXN0IGNvbWJpbmF0aW9ucyBvZiBkaWZmZXJlbnQgc3RhbmRhcmQgcHJlZGljdGlvbiBwcm9ibGVtcyAkXFJpZ2h0YXJyb3ckIGNvbWJpbmUgc3RhbmRhcmQgZnVuY3Rpb25zIGluIGEgbW9kdWxhciB3YXkNCiANCiAtIFVzaW5nIGVuc2VtYmxlIG1ldGhvZHMgdGhhdCBmaWd1cmUgb3V0IGluIGEgZGF0YS1kcml2ZW4gd2F5IHdoaWNoIG1ldGhvZHMgd29yayBmb3Igd2hpY2ggcHJlZGljdGlvbiBwcm9ibGVtIGlzIGFuIGludGVyZXN0aW5nIG9wdGlvbiBpZiB3ZSBoYXZlIHRpbWUgYW5kIG5vIGlkZWEgd2hpY2ggc2luZ2xlIG1ldGhvZCB3b3JrcyBiZXN0IGluIG91ciBzZXR0aW5nDQogDQo8YnI+DQo8YnI+DQogDQogDQojIyBTdWdnZXN0aW9ucyB0byBwbGF5IHdpdGggdGhlIHRveSBtb2RlbA0KDQpTb21lIHN1Z2dlc3Rpb25zOg0KIA0KLSBBZGQgQ2F1c2FsIEZvcmVzdCB0byB0aGUgY29tcGFyaXNvbg0KDQotIENyZWF0ZSBhIG5vbi1saW5lYXIgQ0FURSBhbmQgbGluZWFyIG51aXNhbmNlIGZ1bmN0aW9ucyBvciANCg0KLSBDaGFuZ2UgdGhlIHRyZWF0bWVudCBzaGFyZXMNCg0KIA==