Introduction
During the last 30 years, modern epidemiology has
been able to identify significant limitations of classic epidemiologic
methods when the focus is to explain the main effect of a risk factor on
a disease or outcome.
Causal Inference based on the Neyma-Rubin Potential Outcomes
Framework (Rubin, 2011), first
introduced in Social Science by Donal Rubin (Rubin, 1974) and later in Epidemiology and
Biostatistics by James Robins (Greenland and
Robins, 1986), has provided the theory and statistical methods
needed to overcome recurrent problems in observational epidemiologic
research, such as:
- non-collapsibility of the odds and hazard ratios,
- impact of paradoxical effects due to conditioning on colliders,
- selection bias related to the vague understanding of the effect of
time on exposure and outcome and,
- effect of time-dependent confounding and mediators,
- etc.
Causal effects are often formulated regarding comparisons of
potential outcomes, as formalised by Rubin (Rubin, 2011). Let A denote a binary exposure,
W a vector of potential confounders, and Y a binary
outcome. Given A, each individual has a pair of potential outcomes: the
outcome when exposed, denoted \(Y_{1}\), and the outcome when unexposed,
\(Y_{0}\). These quantities are
referred to as potential outcomes since they are
hypothetical, given that it is only possible to observe a single
realisation of the outcome for an individual; we observe \(Y_{1}\) only for those in the exposure
group and \(Y_{0}\) only for those in
the unexposed group (Rubin, 1974). A
common causal estimand is the Average Treatment Effect
(ATE), defined as \(E[Y_{1}\, –
\,Y_{0}]\).
Classical epidemiologic methods use regression adjustment to explain
the main effect of a risk factor measure on a disease or outcome.
Regression adjustment control for confounding but requires making the
assumption that the effect measure is constant across levels of
confounders included in the model. However, in non-randomized
observational studies, the effect measure is not constant across groups
given the different distribution of individual characteristics at
baseline.
James Robins in 1986 demonstrated that using the
G-formula a generalization of the
standardisation, allows obtaining a unconfounded
marginal estimation of the ATE under causal untestable assumptions,
namely conditional mean independence, positivity and consistency or
stable unit treatment value assignment (SUTVA) (Greenland and Robins, 1986), (Robins et al., 2000):
The G-Formula and ATE
estimation
\[\psi(P_{0})\,=\,\sum_{w}\,\left[\sum_{y}\,P(Y=y\mid
A=1,W=w)-\,\sum_{y}\,P(Y = y\mid A=0,W=w)\right]P(W=w)\]
where,
\[P(Y = y \mid A = a, W = w)\,=\,\frac{P(W
= w, A = a, Y = y)}{\sum_{y}\,P(W = w, A = a, Y = y)}\]
is the conditional probability distribution of Y = y, given A = a, W = w
and,
\[P(W = w)\,=\,\sum_{y,a}\,P(W = w, A = a,
Y = y)\]
The ATE can be estimated non-parametrically using
the G-formula. However, the course of dimensionality in
observational studies limits its estimation. Hence, the estimation of
the ATE using the G-formula relies mostly on parametric
modelling and maximum likelihood estimation.
The correct model specification in parametric modelling is crucial to
obtain unbiased estimates of the true ATE (Rubin,
2011). Alternatively, propensity score methods, introduced by
Rosenbaum and Rubin (Rosenbaum and Rubin,
1983), are also commonly used for estimation of the ATE. The
propensity score is a balancing score that can be used to create
statistically equivalent exposure groups to estimate the ATE via
matching, weighting, or stratification (Rosenbaum
and Rubin, 1983).
However, very low or very high propensity scores can lead to very
large weights, resulting in unstable ATE estimates with high variance
and values outside the constraints of the statistical model (Lunceford and Davidian, 2004).
Furthermore, when analyizing observational data with a large number
of variables and potentially complex relationships among them, model
misspecification during estimation is of particular concern. Hence, the
correct model specification in parametric modelling is crucial to obtain
unbiased estimates of the true ATE (Laan and
Rose, 2011).
However, Mark van der Laan and Rubin (Laan and
Rubin, 2006) introduced in 2006 a double-robust
estimation procedure to reduce bias against
misspecification. The targeted maximum likelihood estimation
(TMLE) is a semiparametric, efficient substitution
estimator (Laan and Rose, 2011).
TMLE
Note: for a more formal presentation of the TMLE statistical
framework readers would like to read the published tutorial in
Statistics in Medicine (https://onlinelibrary.wiley.com/doi/full/10.1002/sim.7628).
Readers reading this open source introductory tutorial should gain
sufficient understanding of TMLE to be able to apply the method in
practice. Extensive classic R-code is provided in easy-to-read boxes
throughout the tutorial for replicability. Stata users will find a
testing implementation of TMLE and additional material in the appendix
and at the following GitHub repository https://github.com/migariane/SIM-TMLE-tutorial
TMLE allows for data-adaptive estimation while
obtaining valid statistical inference based on the targeted minimum
loss-based estimation and machine learning algorithms to minimise the
risk of model misspecification (Laan and Rose,
2011). The main characteristics of TMLE are:
TMLE is a general algorithm for the construction
of double-robust, semiparametric, efficient substitution estimators.
TMLE allows for data-adaptive estimation while
obtaining valid statistical inference.
TMLE implementation uses the G-computation
estimand (G-formula). Briefly, the TMLE algorithm uses
information in the estimated exposure mechanism P(A|W) to update the
initial estimator of the conditional expectation of the outcome given
the treatment and the set of covariates W, E\(_{0}\)(Y|A,W).
The targeted estimates are then substituted into the parameter
mapping \(\Psi\). The updating step
achieves a targeted bias reduction for the parameter of interest \(\Psi(P_{0})\) (the true target parameter)
and serves to solve the efficient score equation, namely the Influence
Curve (IC). As a result, TMLE is a
double-robust estimator.
TMLE it will be consistent for \(\Psi(P_{0})\) if either the conditional
expectation E\(_{0}\)(Y|A,W) or the
exposure mechanism P\(_{0}\)(A|W) are
estimated consistently.
TMLE will be efficient if the previous two
functions are consistently estimated achieving the lowest asymptotic
variance among a large class of estimators. These asymptotic properties
typically translate into lower bias and variance in
finite samples (Bühlmann et al.,
2016).
The general formula to estimate the ATE using the TMLE
method:
\[\psi TMLE,n = \Psi(Q_{n}^{*})=
{\frac{1}{n}\sum_{i=1}^{n}\bar{Q}_{n}^{1}\left(1,\
W_{i}\right)-\bar{Q}_{n}^{1}\left(0,\ W_{i}\right)}. (1)\] 7.
The efficient influcence curve (IC) based on the Functional Delta Method
and Empirical Process Theory (Fisher and Kennedy,
2018) is applied for statistical inference using TMLE:
\[IC_{n}(O_{i})=\left(\frac{I\left(A_{i}=1\right)}{g_n\left(1\left|W_{i}\right)\right)}\
-\ \frac{I\left(A_{i}=0\right)}{g_n\left(0\left|W_{i}\right)\right)}\
\right)\left[Y_{i}-\bar{Q}_{n}^{1}\left(A_{i},W_{i}\right)\right]+\bar{Q}_{n}^{1}\left(1,\
W_{i}\right)-\bar{Q}_{n}^{1}\left(0,\ W_{i}\right) - \psi TMLE,n.
(2)\]
where the variance of the ATE:
\[\sigma({\psi_{0}})=\sqrt{\frac{Var(IC_{n})}{n}}. (3)\]
- The procedure is available with standard software such as the
tmle package in R (Gruber and
Laan, 2011).
Structural causal
framework
Direct Acyclic Graph
(DAG)
Figure 1. Direct
Acyclic Graph (DAG)
Source: Miguel Angel Luque-Fernandez
DAG
interpretation
The ATE is interpreted as the population risk difference in one-year
mortality for laryngeal cancer patients treated with chemotherapy versus
radiotherapy. Under causal assumptions, and compared with radiotherapy,
the risk difference of one-year mortality for patients treated with
chemotherapy increases by approximately 20%.
Causal assumptions
To estimate the value of the true causal target parameter \(\psi(P_{0})\) with a model for the true
data generation process \(P_{0}\) under
the counterfactual framework augmented additional untestable cuasal
assumptions have to be considered (Rubin,
2011), (Laan and Rose, 2011):
CMI or
Randomization
(\(Y_{0},Y_{1}\perp\)A|W) or
conditional mean independence (CMI) of the binary treatment effect (A)
on the outcome (Y) given the set of observed covariates (W), where W =
(W1, W2, W3, … , \(\text{W}_{k}\)).
Positivity
a ϵ A: P(A=a | W) > 0
P(A=1|W=w) > 0 and P(A=0| W = w) > 0 for each possible w.
Consistency or
SUTVA
The Stable Unit Treatment Value Assumption (SUTVA) incorporates both
this idea that units do not interfere with one another,
and also the concept that for each unit there is only a single
version of each treatment level.
TMLE flow chart
Figure 2. TMLE flow
chart (Road map)
Adapted from: Mark van der Laan and Sherri Rose.
Targeted learning: causal inference for observational and experimental
dataSpringer Series in Statistics, 2011.
Data generation
Simulation
In R we create a function to generate the data. The function will
have as input number of draws and as output the
generated observed data (ObsData) including the
counterfactuals (Y1, Y0).
The simulated data replicationg the DAG in Figure 1:
- Y: mortality binary indicator (1 death, 0 alive)
- A: binary treatment (1 Chemotherapy, 0 Radiotherapy )
- W1: Gender (1 male; 0 female)
- W2: Age at diagnosis (0 <65; 1 >=65)
- W3: Cancer TNM classification (scale from 1 to 4; 1: early stage no
metastasis; 4: advanced stage with metastasis)
- W4: Comorbidities (scale from 1 to 5)
options(digits=4)
generateData <- function(n){
w1 <- rbinom(n, size=1, prob=0.5)
w2 <- rbinom(n, size=1, prob=0.65)
w3 <- round(runif(n, min=0, max=4), digits=3)
w4 <- round(runif(n, min=0, max=5), digits=3)
A <- rbinom(n, size=1, prob= plogis(-0.4 + 0.2*w2 + 0.15*w3 + 0.2*w4 + 0.15*w2*w4))
# counterfactual
Y.1 <- rbinom(n, size=1, prob= plogis(-1 + 1 -0.1*w1 + 0.3*w2 + 0.25*w3 + 0.2*w4 + 0.15*w2*w4))
Y.0 <- rbinom(n, size=1, prob= plogis(-1 + 0 -0.1*w1 + 0.3*w2 + 0.25*w3 + 0.2*w4 + 0.15*w2*w4))
# Observed outcome
Y <- Y.1*A + Y.0*(1 - A)
# return data.frame
data.frame(w1, w2, w3, w4, A, Y, Y.1, Y.0)
}
set.seed(7777)
ObsData <- generateData(n=10000)
True_Psi <- mean(ObsData$Y.1-ObsData$Y.0);
cat(" True_Psi:", True_Psi)
True_Psi: 0.1993
Bias_Psi <- lm(data=ObsData, Y~ A + w1 + w2 + w3 + w4)
cat("\n")
cat("\n Naive_Biased_Psi:",summary(Bias_Psi)$coef[2, 1])
Naive_Biased_Psi: 0.2128
Naive_Bias <- ((summary(Bias_Psi)$coef[2, 1])-True_Psi); cat("\n Naives bias:", Naive_Bias)
Naives bias: 0.01351
Naive_Relative_Bias <- (((summary(Bias_Psi)$coef[2, 1])-True_Psi)/True_Psi)*100; cat("\n Relative Naives bias:", Naive_Relative_Bias,"%")
Relative Naives bias: 6.78 %
Data
visualization
# DT table = interactive
# install.packages("DT") # install DT first
library(DT)
datatable(head(ObsData, n = nrow(ObsData)), options = list(pageLength = 5, digits = 2))
TMLE simple
implementation
Step 1: \(Q_{0}\)(A,W)
Estimation of the initial probability of the outcome (Y) given the
treatment (A) and the set of covariates (W), denoted as \(Q_{0}\)(A,W). To estimate
\(Q_{0}\)(A,W) we can
use a standard logistic regression model:
\[\text{logit}[P(Y=1|A,W)]\,=\,\beta_{0}\,+\,\beta_{1}A\,+\,\hat{\beta_{2}^{T}}W.\]
Therefore, we can estimate the initial probability as follows:
\[\bar{Q}^{0}(A,W)\,=\,\text{expit}(\hat{\beta_{0}}\,+\,\hat{\beta_{1}}A\,+\,\hat{\beta_{2}^{T}}W).\]
The predicted probability can be estimated using the Super-Learner
library implemented in the R package “Super-Learner” (Van der Laan et al., 2007) to include
any terms that are functions of A or W (e.g., polynomial terms of A and
W, as well as the interaction terms of A and W, can be considered).
Consequently, for each subject, the predicted probabilities for both
potential outcomes \(\bar{Q}^{0}(0,W)\)
and \(\bar{Q}^{0}(1,W)\) can be
estimated by setting A = 0 and A = 1 for everyone respectively: \[\bar{Q}^{0}(0,W)\,=\,\text{expit}(\hat{\beta_{0}}\,+\,\hat{\beta_{2}^{T}}W),\]
and,
\[\bar{Q}^{0}(1,W)\,=\,\text{expit}(\hat{\beta_{0}}\,+\,\hat{\beta_{1}}A\,+\,\hat{\beta_{2}^{T}}W).\]
Note: see appendix one for a short introduction to the
Super-Learner and ensemble learning techniques.
ObsData <-subset(ObsData, select=c(w1,w2,w3,w4,A,Y))
Y <- ObsData$Y
A <- ObsData$A
w1 <- ObsData$w1
w2 <- ObsData$w2
w3 <- ObsData$w3
w4 <- ObsData$w4
m <- glm(Y ~ A + w1 + w2 + w3 + w4, family=binomial, data=ObsData)
Q <- cbind(QAW = predict(m),
Q1W = predict(m, newdata=data.frame(A = 1, w1, w2, w3, w4)),
Q0W = predict(m, newdata=data.frame(A = 0, w1, w2, w3, w4)))
Q0 <- as.data.frame(Q)
Y1 <- Q0$Q1W
Y0 <- Q0$Q0W
QA1 <- exp(Y1)/(1+exp(Y1))
QA0 <- exp(Y0)/(1+exp(Y0))
#Inverse logit (probability scale)
psi <- (exp(Y1)/(1+exp(Y1)) - exp(Y0)/(1+exp(Y0)))
Psi <- mean(exp(Y1)/(1+exp(Y1)) - exp(Y0)/(1+exp(Y0))); cat("\n Q0:", Psi)
Q0: 0.2048
df <- round(cbind(Logit=(Q),Pr.Y1=QA1,Pr.Y0=QA0,Psi=psi), digits= 3)
Visualizing the first step:
datatable(head(df, n = nrow(df)), options = list(pageLength = 5, digits = 3))
Step 2: \(g_{0}(A,W)\)
Estimation of the probability of the treatment (A) given the set of
covariates (W), denoted as \(g_{0}(A,W)\). We can use again a logistic
regression model and to improve the prediction algorithm we can use the
Super-Learner library or any other machine learning strategy:
\[\text{logit}[P(A=1|W)]\,=\,\alpha_{0}\,+\,\alpha_{1}^{T}W.\]
Then, we estimate the predicted probability of P(A|W) = \(\hat{g}(1,W)\) using:
\[\hat{g}(1,W)\,=\,\text{expit}\,(\hat{\alpha_{0}}\,+\,\hat{\alpha_{1}^{T}}W).\]
g <- glm(A ~ w2 + w3 + w4, family = binomial)
g1W = predict(g, type ="response");cat("\n Propensity score = g1W","\n");summary(g1W)
Propensity score = g1W
Min. 1st Qu. Median Mean 3rd Qu. Max.
0.358 0.594 0.681 0.671 0.759 0.875
Step 3: HAW and \(\epsilon\)
This step aims to find a better prediction model targeted at
minimising the mean squared error (MSE) for the potential outcomes. For
the ATE on step convergence is guaranteed given \(\bar{Q}^{0}\) and \(\hat{g}(1,W)\).
The fluctuation parameters \((\hat{\epsilon}_{0}\,,\,\hat{\epsilon}_{1})\)
are estimated using maximum likelihood procedures by setting \(\text{logit}(\bar{Q^{0}}(A,W))\) as an
offset in a intercept-free logistic regression with \(H_{0}\) and \(H_{1}\) as independent variables:
\[\bar{Q^{1}}(A,W)\,=\,\text{expit}\left[\text{logit}\left(\bar{Q^{0}}(A,
W)\right)\,+\,\hat{\epsilon_{0}}H_{0}(A,W)\,+\,\hat{\epsilon_{1}}H_{1}(A,W)\right] (5)\]
\[\bar{Q^{1}}(0,W)\,=\,\text{expit}\left[\text{logit}\left(\bar{Q^{0}}(A,W)\right)\,+\,\hat{\epsilon_{0}}H_{0}(0,W)\right]\]
\[\bar{Q^{1}}(1,W)\,=\,\text{expit}\left[\text{logit}\left(\bar{Q^{0}}(A,W)\right)\,+\,\hat{\epsilon_{1}}H_{1}(1,W)\right]\]
Where, \[H_{0}(A,W)\,=\,-\frac{I(A=0)}{\hat{g}(0|W)}\;\text{and},\;H_{1}(A,W)\,=\,\frac{I(A=1)}{\hat{g}(1|W)}\]
are the stabilized inverse probability of treatment (A) weights (IPTW),
namelly the clever covariates and I
defines an indicator function (note that \(\hat{g}(A|W)\) is estimted from step
2).
#Clever covariate and fluctuating/substitution paramteres
h <- cbind(gAW=(A/g1W - (1 - A) / (1 - g1W)), g1W = (1/g1W), g0W=(-1 / (1 - g1W)))
epsilon <- coef(glm(Y ~ -1 + h[,1] + offset(Q[,"QAW"]), family = binomial));cat("\n Epsilon:",epsilon)
Epsilon: 0.001041
df <- round(cbind(Q0,PS=(g1W),H=(h),epsilon), digits= 4)
Visualizing the 3rd step (PS = propensity score; H =
IPTW or clever covarites):
datatable(head(df, n = nrow(df)), options = list(pageLength = 5, digits = 3))
Step 4 \(\bar{Q_{n}}^{*}:\text{from}\,\bar{Q_{0}}^{0}\,\text{to}\,\bar{Q_{1}}^{1}\)
Afterwards, the estimated probability of the potential outcomes is
updated by the substitution parameters \((\hat{\epsilon_{0}}\,,\,\hat{\epsilon_{1}})\).
The substitution update is performed by setting A = 0 and A = 1 for each
subject in the initial estimate probability of the potential outcomes
\(\bar{Q^{0}}(0,W)\,,\,\bar{Q^{0}}(1,W)\), as
well as in the clever covariates \(H_{0}(0,W)\;\text{and}\; H_{1}(1,W)\).
For the \(\Psi(\bar{Q_{n}}^{*})\),
the updated estimate of the potential outcomes only needs one iteration
\(\Psi(\bar{Q_{n}}^{*})\) from \(\bar{Q}^{0}(A,W)\,=>\bar{Q^{1}}(A,W)\).
Therefore, model (5) targets \(E[\hat{Y}_{A=0}]\;\text{and}\;
E[\hat{Y}_{A=1}]\) simultaneously by including both \(H_{0}(A,W)\,\text{and}\,H_{1}(A,W)\) in the
model. Hence \(\psi\) is finally
estimated as follows:
\[\psi TMLE,n = \Psi(Q_{n}^{*})=
{\frac{1}{n}\sum_{i=1}^{n}\bar{Q}_{n}^{1}\left(1,\
W_{i}\right)-\bar{Q}_{n}^{1}\left(0,\ W_{i}\right)}. (1)\]
Qstar <- plogis(Q + epsilon*h)
psi <- (Qstar[,"Q1W"] - Qstar[,"Q0W"])
Psi <- mean(Qstar[,"Q1W"] - Qstar[,"Q0W"]);
cat("TMLE_Psi:", Psi)
TMLE_Psi: 0.2058
cat("\n TMLE.SI_bias:", abs(True_Psi-Psi))
TMLE.SI_bias: 0.006503
cat("\n Relative_TMLE.SI_bias:",abs(True_Psi-Psi)/True_Psi*100,"%")
Relative_TMLE.SI_bias: 3.263 %
Visualizing the 4th step (H = IPTW or clever
covarites):
df <- round(cbind(Q0=(Q0),H=(h),epsilon,psi), digits= 3)
datatable(head(df, n = nrow(df)), options = list(pageLength = 5, digits = 3))
cat("\n Psi first row:", plogis((0.001*1.239) + (2.395)) - (plogis((0.001*-5.168) + (1.343))))
Psi first row: 0.1244
Step 5:
Inference
Recall that the asymptotic distribution of TMLE estimators has been
studied thoroughly (Laan and Rose,
2011):
\[\psi_n - \psi_0 = (P_n - P_0) \cdot
D(\bar{Q}_n^*, g_n) + R(\hat{P}^*, P_0),\]
which, provided the following two conditions:
- If \(D(\bar{Q}_n^*, g_n)\)
converges to \(D(P_0)\) in \(L_2(P_0)\) norm, and
- the size of the class of functions considered for estimation of
\(\bar{Q}_n^*\) and \(g_n\) is bounded (technically, \(\exists \mathcal{F}\) st \(D(\bar{Q}_n^*, g_n) \in \mathcal{F}\)
whp, where \(\mathcal{F}\) is a Donsker class), readily
admits the conclusion that
\(\psi_n - \psi_0 = (P_n - P_0) \cdot
D(P_0) + R(\hat{P}^*, P_0)\).
Under the additional condition that the remainder term \(R(\hat{P}^*, P_0)\) decays as \(o_P \left( \frac{1}{\sqrt{n}} \right),\) we
have that \[\psi_n - \psi_0 = (P_n - P_0)
\cdot D(P_0) + o_P \left( \frac{1}{\sqrt{n}}
\right),\] which, by a central limit theorem, establishes a
Gaussian limiting distribution for the estimator:
\[\sqrt{n}(\psi_n - \psi) \to N(0,
V(D(P_0))),\]
where \(V(D(P_0))\) is the variance
of the efficient influence curve (canonical gradient) when \(\psi\) admits an asymptotically linear
representation.
The above implies that \(\psi_n\) is
a \(\sqrt{n}\)-consistent estimator of
\(\psi\), that it is asymptotically
normal (as given above), and that it is locally efficient. This allows
us to build Wald-type confidence intervals in a straightforward
manner:
\[\psi_n \pm z_{\alpha} \cdot
\frac{\sigma_n}{\sqrt{n}},\]
where \(\sigma_n^2\) is an estimator
of \(V(D(P_0))\). The estimator \(\sigma_n^2\) may be obtained using the
bootstrap or computed directly via the following
\[\sigma_n^2 = \frac{1}{n} \sum_{i =
1}^{n} D^2(\bar{Q}_n^*, g_n)(O_i)\]
Having now re-examined these facts, let’s simply apply it to the
estimation of the standard errors for \(\psi\). Thus, the efficient influence curve
(EIC) for the ATE-TMLE estimator is:
\[IC_{n}(O_{i})\ \ =\
\left(\frac{I\left(A_{i}=1\right)}{g_n\left(1\left|W_{i}\right)\right)}\
-\ \frac{I\left(A_{i}=0\right)}{g_n\left(0\left|W_{i}\right)\right)}\
\right)\left[Y_{i}-\bar{Q}_{n}^{1}\left(A_{i},W_{i}\right)\right]+\bar{Q}_{n}^{1}\left(1,\
W_{i}\right)-\bar{Q}_{n}^{1}\left(0,\ W_{i}\right) - \psi
TMLE,n.\]
Therefore, the standard deviation for \(\psi\) is estimated as follows:
\[\sigma({\psi_{0}})=\sqrt{\frac{Var(IC_{n})}{n}}.\]
Note: Please see here below the link to a practical
tutorial introducing the computational derivation and use of the
Delta Method in Epidemiology which lay the foundations
for the interpretation and understanding of the functional delta method
and the Influence Curve rooted in both, Robust
Statistics and Empirical Process Theory.
Delta Method in Epidemiology: https://migariane.github.io/DeltaMethodEpi.nb.html
Q <- as.data.frame(Q)
Qstar <- as.data.frame(Qstar)
IC <- h[,1]*(Y-plogis(Q$QAW)) + plogis(Qstar$Q1W - Qstar$Q0W) - Psi;summary(IC)
Min. 1st Qu. Median Mean 3rd Qu. Max.
-1.317 -0.791 0.517 0.346 0.792 6.423
n <- nrow(ObsData)
varHat.IC <- var(IC)/n; varHat.IC
[1] 9.821e-05
#Psi and 95%CI for Psi
cat("\n TMLE_Psi:", Psi)
TMLE_Psi: 0.2058
cat("\n 95%CI:", c(Psi-1.96*sqrt(varHat.IC), Psi+1.96*sqrt(varHat.IC)))
95%CI: 0.1864 0.2252
cat("\n TMLE.SI_bias:", abs(True_Psi-Psi))
TMLE.SI_bias: 0.006503
cat("\n Relative_TMLE.SI_bias:",abs(True_Psi-Psi)/True_Psi*100,"%")
Relative_TMLE.SI_bias: 3.263 %
TMLE vs. AIPTW
The advantages of TMLE have repeatedly been
demonstrated in both simulation studies and applied analyses (Laan and Rose, 2011).
Evidence shows that TMLE provides the less
unbiased ATE estimate compared with other double-robust estimators (Neugebauer and Laan, 2005), (Laan and Rose, 2011) such as the combination of
regression adjustment with inverse probability of treatment weighting
(IPTW-RA) and the augmented inverse probability of treatment weighting
(AIPTW). The AIPTW estimation is a two-step procedure
with two equations (propensity score and mean outcome
equations).
To estimate the ATE using the AIPTW estimator
one can set the estimation equation (EE) (4) equal to zero and use
bootstrap to derive 95% confidence intervals (CI). However, solving the
EE using the generalized method of moments (GMM), stacking both
equations (propensity score and outcome), reduces the estimation and
inference steps to only one. However, given that the propensity score in
equation (4) can easily fall outside the range [0, 1] (if for some
observations \(g_{n}(1|W_{i})\) is
close to 1 or 0) the AIPTW estimation can be unstable
(near violation of the positivity assumption). AIPTW
instability under near violation of the positivity assumption represents
the price of not being a substitution estimator as
TMLE.
\[\psi_{0}^{AIPTW-ATE}\ \ =\
\frac{1}{n}\sum_{i=1}^{n}\left(\frac{I\left(A_{i}=1\right)}{g_n\left(1\left|W_{i}\right)\right)}\
-\ \frac{I\left(A_{i}=0\right)}{g_n\left(0\left|W_{i}\right)\right)}\
\right)\left[Y_{i}-\bar{Q}_{n}^{0}\left(A_{i},W_{i}\right)\right]+\frac{1}{n}\sum_{i=1}^{n}\bar{Q}_{n}^{0}\left(1,\
W_{i}\right)-\bar{Q}_{n}^{0}\left(0,\ W_{i}\right). (4)\]
AIPTW <- mean((h[,1]*(Y - plogis(Q$QAW)) + (plogis(Q$Q1W) - plogis(Q$Q0W))));AIPTW
[1] 0.2058
cat("\n AIPTW_bias:", abs(True_Psi - AIPTW))
AIPTW_bias: 0.006499
cat("\n Relative_AIPTW_bias:",abs(True_Psi - AIPTW) / True_Psi*100,"%")
Relative_AIPTW_bias: 3.261 %
The simple TMLE algorithm shows similar relative bias than AIPTW.
However, here below, we can see that TMLE performance, compared with
AIPTW, improves when calling the Super-Learner and ensemble learning
techniques integrated into the TMLE algorithm.
TMLE using the
Super-Learner
With TMLE we can call the Super-Learner (SL). The SL is a R-package
using V-fold cross-validation and ensembled learning (prediction using
all the predictions of multiple stacked learning algorithms) techniques
to improve model prediction performance (Breiman,
1996).
The basic implementation of TMLE in the R-package
tmle uses by default three algorithms:
1. SL.glm (main terms logistic regression of A and W),
2. SL.step (stepwise forward and backward model selection using AIC
criterion, restricted to second order polynomials) and,
3. SL.glm.interaction (a glm variant that includes second order
polynomials and two by two interactions of the main terms included in
the model).
The principal interest of calling the Super-Learner is to obtain the
less-unbiased estimated for \(\bar
Q_{n}^{0}(A,W)\) and \(g_{0}(A,W)\). It is achieved by obtaining
the smallest expected loss function for Y or A (binary outcomes),
respectively. For instance, the negative logarithmic loss function for Y
is computed as the minimizer of the expected squared error loss:
\[\bar Q_{0}\,=\, \text{arg min}_{\bar
Q}E_{0}L(O, \bar Q),\]
where \(L(O, \bar Q)\) is: \[ (Y \,-\, \bar Q(A, W))^{2}\]
Note: see the appendix for a short introduction to
the Super-Learner and ensemble learning techniques.
- Step One: \(\bar
Q_{n}^{0}(A,W)\) prediction
#E(Y|A,W) prediction
library(SuperLearner)
#Specify SuperLearner libraries
SL.library <- c("SL.glm","SL.step","SL.glm.interaction")
#Data frame with X with baseline covariates and exposure A
X <- subset(ObsData, select = c(A, w1, w2, w3, w4))
n <- nrow(ObsData)
#Create data frames with A=1 and A=0
X1<-X0<-X
X1$A <-1
X0$A <-0
#Create new data by stacking X, X1, and X0
newdata <- rbind(X,X1,X0)
#Call superlearner
Qinit <- SuperLearner(Y = ObsData$Y, X = X, newX = newdata, SL.library=SL.library, family="binomial")
Qinit
Call:
SuperLearner(Y = ObsData$Y, X = X, newX = newdata, family = "binomial",
SL.library = SL.library)
Risk Coef
SL.glm_All 0.1765 0.000
SL.step_All 0.1765 0.581
SL.glm.interaction_All 0.1766 0.419
#Predictions
#Pred prob of mortality (Y) given A, W
QbarAW <- Qinit$SL.predict[1:n]
#Pred prob of dying for each subject given A=1 and w
Qbar1W <- Qinit$SL.predict[(n+1):(2*n)]
#Pred prob of dying for each subject given A=0 and w
Qbar0W <- Qinit$SL.predict[(2*n+1):(3*n)]
#Simple substitution estimator Psi(Q0)
PsiHat.SS <- mean(Qbar1W - Qbar0W);PsiHat.SS
[1] 0.2044
- Step two: \(g_{0}(A,W)\) prediction
#Step 2 g_0(A|W) with SuperLearner
w <- subset(ObsData, select=c(w1,w2,w3,w4))
gHatSL <- SuperLearner(Y=ObsData$A, X = w, SL.library = SL.library, family = binomial)
gHatSL
Call:
SuperLearner(Y = ObsData$A, X = w, family = binomial, SL.library = SL.library)
Risk Coef
SL.glm_All 0.2092 0.0000
SL.step_All 0.2092 0.3602
SL.glm.interaction_All 0.2091 0.6398
#Generate the pred prob of A=1 and, A=0 given covariates
gHat1W <- gHatSL$SL.predict
gHat0W <- 1 - gHat1W
#Step 3: Clever covariate
HAW <- as.numeric(ObsData$A==1)/gHat1W - as.numeric(ObsData$A==0)/gHat0W;mean(HAW)
[1] 0.002813
H1W <- 1/gHat1W
H0W <- -1/gHat0W
- Steps 3 and 4: fluctuation step and substitution
estimation for \(\bar Q_{n}^{0}(A,W)\)
to \(\bar Q_{n}^{1}(A,W)\)
#Step 4: Substitution estimaiton Q* of the ATE.
logitUpdate <- glm(ObsData$Y ~ -1 + offset(qlogis(QbarAW)) + HAW, family='binomial')
eps <- logitUpdate$coef;eps
HAW
-0.0002121
#Calculating the predicted values for each subject under each treatment A=1, A=0
QbarAW.star <- plogis(qlogis(QbarAW)+eps*HAW)
Qbar1W.star <- plogis(qlogis(Qbar1W)+eps*H1W)
Qbar0W.star <- plogis(qlogis(Qbar0W)+eps*H0W)
PsiHat.TMLE.SL <- mean(Qbar1W.star) - mean(Qbar0W.star)
cat("PsiHat.TMLE.SL:", PsiHat.TMLE.SL)
PsiHat.TMLE.SL: 0.2042
cat("\n PsiHat.TMLE.SL_bias:", abs(True_Psi - PsiHat.TMLE.SL))
PsiHat.TMLE.SL_bias: 0.004903
cat("\n Relative_PsiHat.TMLE.SL_bias:",abs(True_Psi - PsiHat.TMLE.SL)/True_Psi*100,"%")
Relative_PsiHat.TMLE.SL_bias: 2.46 %
TMLE with machine learning algorithms decreases bias compared with
the previous AIPTW and TMLE (without Super Learner) estimators.
R-TMLE
Using the R-package tmle.
The basic implementation of TMLE in the R-package
tmle uses by default three algorithms:
1. SL.glm (main terms logistic regression of A and W),
2. SL.step (stepwise forward and backward model selection using AIC
criterion, restricted to second order polynomials) and,
3. SL.glm.interaction (a glm variant that includes second order
polynomials and two by two interactions of the main terms included in
the model).
library(tmle)
set.seed(7777)
#attach(ObsData)
w <- subset(ObsData, select=c(w1,w2,w3,w4))
tmle <- tmle(Y, A, w, family="binomial")
cat("TMLER_Psi:", tmle$estimates[[2]][[1]],";","95%CI(", tmle$estimates[[2]][[3]],")")
TMLER_Psi: 0.2048 ; 95%CI( 0.1855 0.2241 )
cat("\n TMLE_bias:", abs(True_Psi-tmle$estimates[[2]][[1]]))
TMLE_bias: 0.005468
cat("\n Relative_TMLE_bias:",abs(True_Psi-tmle$estimates[[2]][[1]])/True_Psi*100,"%")
Relative_TMLE_bias: 2.744 %
TMLE implementation in the R-package tmle improves
the estimation of the inverse-propability of treatment weights. It
bounds by default the distribution of the weights for the propensity
score to (0.025th and 0.975th percentiles) to decrease the impact of
near-positivity violations. Also, note that at each time that we run the
super-learner the V-fold cross-validation splits the data randomly. So,
we have to set a seed for replicability. It is why the bias using the
tmle R-package decreases from 0.007 to 0.005.
R-TMLE reducing bias
by calling more advanced machine-learning libraries
In addition to the default algorithms implemented in the R-tmle
package, we can even decrease more the bias of our estimation by calling
more efficient machine learing algorithms, such as generalized additive
models, Random Forest, Recursive Partitioning and Regression Trees (it
is highly recomended to include the the highly adaptive Lasso [HAL9001]
in your SL library but for computing efficiency we did not include it
here):
library(hal9001)
hal9001::SL.hal9001
SL.TMLER.Psi <- tmle(Y=Y, A=A, W=w, family="binomial",
Q.SL.library = c("SL.glm", "SL.step", "SL.glm.interaction", "SL.gam", "SL.ranger"),
g.SL.library = c("SL.glm", "SL.step", "SL.glm.interaction", "SL.gam", "SL.ranger"))
cat("SL.TMLER.Psi:", SL.TMLER.Psi$estimates[[2]][[1]],";","95%CI(", SL.TMLER.Psi$estimates[[2]][[3]],")")
cat("\n SL.TMLER.Psi_bias:", abs(True_Psi-SL.TMLER.Psi$estimates[[2]][[1]]))
cat("\n Relative_SL.TMLER.Psi_bias:",abs(True_Psi-SL.TMLER.Psi$estimates[[2]][[1]])/True_Psi*100,"%")
Conclusions
We have demonstrated:
- TMLE excels the AIPTW estimator and,
- TMLE best performance is obtained when calling more
advanced Super-Learner algorithms.
Appendix
With TMLE we can call the R-package Super-Learner
(SL). The SL uses cross-validation
and ensembled learning (using all the predictions of
multiple stacked learning algorithms) techniques to improve model
prediction performance (Breiman,
1996).
The SL algorithm provides a system based on V-fold
cross-validation (Efron and Gong, 1983)
(10-folds) to combine adaptively multiple algorithms into an improved
estimator, and returns a function than can be used for prediction in new
datasets.
Figure 4: 10-fold
cross-validation algorithm.
The basic implementation of TMLE in the R-package tmle
uses by default three algorithms:
1. SL.glm (main terms logistic regression of A and W),
2. SL.step (stepwise forward and backward model selection using AIC
criterion, restricted to second order polynomials) and,
3. SL.glm.interaction (a glm variant that includes second order
polynomials and two by two interactions of the main terms included in
the model).
The principal interest of calling the Super-Learner is to obtain the
less-unbiased estimated for \(\bar
Q_{n}^{0}(A,W)\) and \(g_{0}(A,W)\). It is achieved by obtaining
the smallest expected loss function for Y or A (binary outcomes),
respectively. For instance, the negative logarithmic loss function for Y
is computed as the minimizer of the expected squared error loss:
\[\bar Q_{0}\,=\, \text{arg min}_{\bar
Q}E_{0}L(O, \bar Q),\]
where \(L(O, \bar Q)\) is: \[ (Y \,-\, \bar Q(A, W))^{2}\] The
SL algorithm first split the data into ten blocks and
fits each of the selected algoriths on the training set (non-shaded
blocks), then predicts the estimated probabilities of the outcome (Y)
using the validation set (shaded block) for each algorithm, based on the
corresponding training set. Afterwards, the SL
estimates the the cross-validating risk for each algorithm averaging the
risks across validation sets resulting in one estimated cross-validated
risk for each algorithm. Finally, the SL selects the
combination of Z that minimises the cross-validation risk, defined as
the minimum mean square error for each of the selected algorithms using
Y and Z. A weighted combination of the algorithms (ensemble learning) in
Z is then used to predict the outcome (Y) (see Figure 5).
Figure 5: Flow Diagram
for the Super-Learner algorithm.
Abbreviations
TMLE: Targeted maximum likelihood estimation
SL: Super Learner
IPTW: Inverse probability of treatment weighting
AIPTW: Augmented inverse probability of treatment weighting
MSE: Mean squared error
SE: Standard error
EE: Estimation equations
GMM: Generalised method of moments
O: Observed ordered data structure
W: Vector of covariates
A: Binary treatment or exposure
Y: Binary outcome
\(Y_{1}, Y_{O}\): Counterfactual
outcomes with binary treatment A
\(P_{0}\): True data-generating
distribution
\(\Psi(P_{0})\):True target
parameter
\(\psi_{0}\,=\,\Psi(P_{0})\): True
target parameter value
\(g_{0}\): Propensity score for the
treatment mechanism (A)
\(g_{0}\): Estimate of \(g_{0}\)
\(\epsilon\): Fluctuation
parameter
\(\epsilon_{n}\): Estimate of \(\epsilon\) \(H_{n}^{*}\): Clever covariate estimate
(inverse probability of treatment weight)
\(L(O,\bar Q)\): Example of a loss
function where it is a function of O and \(\bar Q\)
\((Y \,-\, \bar Q(A, W))^{2}\):
Expected squared error loss
\(\bar Q_{0}\): Conditional mean of
outcome given parents; \(E_{0}(Y|A,W)\)
\(\bar Q_{n}\): Estimate of \(\bar Q_{0}\)
\(\bar Q_{n}^{0}\): Initial estimate of
\(\bar Q_{0}\)
\(\bar Q_{n}^{1}\): First updated
estimate of \(\bar Q_{0}\)
\(\bar Q_{n}^{*}\): Targeted estimate
of \(\bar Q_{n}^{0}\) in TMLE
procedure; \(\bar Q_{n}^{*}\) may equal
\(\bar Q_{n}^{1}\)
Session Info
devtools::session_info()
Thank you
Thank you for participating in this tutorial.
If you have updates or changes that you would like to make, please send
me a
pull request. Alternatively, if you have any questions, please e-mail
me. You can cite this repository as:
Luque-Fernandez MA, (2019). Taregeted Maximum Likelihood Estimation for
a Binary Outcome: Tutorial and Guided Implementation. GitHub repository,
http://migariane.github.io/TMLE.nb.html.
Miguel Angel Luque Fernandez
E-mail: miguel-angel.luque at lshtm.ac.uk;
mluquefe at ugr.es Twitter
@WATZILEI
References
Breiman L. (1996). Stacked regressions. Machine learning
24: 49–64.
Bühlmann P, Drineas P, Laan M van der, Kane M. (2016). Handbook of big
data. CRC Press.
Efron B, Gong G. (1983). A leisurely look at the bootstrap, the
jackknife, and cross-validation. The American Statistician
37: 36–48.
Fisher A, Kennedy EH. (2018). Visually communicating and teaching
intuition for influence functions. arXiv preprint
arXiv:181003260.
Greenland S, Robins JM. (1986). Identifiability, exchangeability, and
epidemiological confounding. International journal of
epidemiology 15: 413–419.
Gruber S, Laan M van der. (2011). Tmle: An r package for targeted
maximum likelihood estimation. UC Berkeley Division of Biostatistics
Working Paper Series.
Laan M van der, Rose S. (2011). Targeted learning: Causal inference for
observational and experimental data. Springer Series in Statistics.
Laan MJ van der, Rubin D. (2006). Targeted maximum likelihood learning.
The International Journal of Biostatistics 2.
Neugebauer R, Laan M van der. (2005). Why prefer double robust
estimators in causal inference? Journal of Statistical Planning and
Inference 129: 405–426.
Robins JM, Hernan MA, Brumback B. (2000). Marginal structural models and
causal inference in epidemiology. Epidemiology 550–560.
Rosenbaum PR, Rubin DB. (1983). The central role of the propensity score
in observational studies for causal effects. Biometrika
70: 41–55.
Rubin DB. (2011). Causal inference using potential outcomes. Journal
of the American Statistical Association.
Rubin DB. (1974). Estimating causal effects of treatments in randomized
and nonrandomized studies. Journal of educational Psychology
66: 688.
Van der Laan MJ, Polley EC, Hubbard AE. (2007). Super learner.
Statistical applications in genetics and molecular biology
6.
LS0tCnRpdGxlOiAiVGFyZ2V0ZWQgTWF4aW11bSBMaWtlbGlob29kIEVzdGltYXRpb24gZm9yIGEgQmluYXJ5IE91dGNvbWU6IFR1dG9yaWFsIGFuZCBHdWlkZWQgSW1wbGVtZW50YXRpb24iCmF1dGhvcjogIk1pZ3VlbCBBbmdlbCBMdXF1ZSBGZXJuYW5kZXosIE1BLCBNUEgsIE1TYywgUGguRCBcbiBodHRwczovL21hbHVxdWUubmV0bGlmeS5hcHAvIgpkYXRlOiAiTGFzdCB1cGRhdGU6IDUvNS8yMDE5IFxuIGh0dHBzOi8vc2Nob2xhci5oYXJ2YXJkLmVkdS9tYWxmL2hvbWUiCm91dHB1dDoKICBodG1sX2RvY3VtZW50OgogICAgdG9jOiB5ZXMKICBwZGZfZG9jdW1lbnQ6CiAgICB0b2M6IHllcwogIGh0bWxfbm90ZWJvb2s6CiAgICBjb2RlX2ZvbGRpbmc6IHNob3cKICAgIGhpZ2hsaWdodDogZGVmYXVsdAogICAgbnVtYmVyX3NlY3Rpb25zOiB5ZXMKICAgIHRoZW1lOiBqb3VybmFsCiAgICB0b2M6IHllcwogICAgdG9jX2Zsb2F0OgogICAgICBjb2xsYXBzZWQ6IG5vCiAgICAgIHNtb290aF9zY3JvbGw6IHllcwogICAgICB0b2NfZGVwdGg6IDMKZm9udC1mYW1pbHk6IFJpc3F1ZQpmb250LWltcG9ydDogaHR0cDovL2ZvbnRzLmdvb2dsZWFwaXMuY29tL2Nzcz9mYW1pbHk9UmlzcXVlCmNzbDogcmVmZXJlbmNlcy9pc21lLmNzbApiaWJsaW9ncmFwaHk6IHJlZmVyZW5jZXMvYmlibGlvZ3JhcGh5LmJpYgplZGl0b3Jfb3B0aW9uczogCiAgbWFya2Rvd246IAogICAgd3JhcDogNzIKLS0tCgpgYGB7PWh0bWx9CjxzY3JpcHQ+CiAkKGRvY3VtZW50KS5yZWFkeShmdW5jdGlvbigpIHsKICAkaGVhZCA9ICQoJyNoZWFkZXInKTsKICAkaGVhZC5wcmVwZW5kKCc8aW1nIHNyYyA9ICJGaWd1cmVzL0xvZ28ucG5nIiBzdHlsZSA9XCJmbG9hdDogcmlnaHQ7d2lkdGg6IDE1MHB4O1wiLz4nKQogfSk7Cjwvc2NyaXB0PgpgYGAKPCEtLUJFR0lOOiAgU2V0IHRoZSBnbG9iYWwgb3B0aW9ucyBhbmQgbG9hZCBwYWNrYWdlcy0tPgoKYGBge3Igc2V0LWdsb2JhbC1vcHRpb25zLCBlY2hvID0gRkFMU0UsIGVycm9yPUZBTFNFLCBtZXNzYWdlPSBGQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGV2YWwgPSBUUlVFLCAKICAgICAgICAgICAgICAgICAgICAgIGVjaG8gPSBUUlVFLCAKICAgICAgICAgICAgICAgICAgICAgIGNhY2hlID0gRkFMU0UsCiAgICAgICAgICAgICAgICAgICAgICBpbmNsdWRlID0gVFJVRSwKICAgICAgICAgICAgICAgICAgICAgIGNvbGxhcHNlID0gRkFMU0UsCiAgICAgICAgICAgICAgICAgICAgICBkZXBlbmRzb24gPSBOVUxMLAogICAgICAgICAgICAgICAgICAgICAgZW5naW5lID0gIlIiLCAjIENodW5rcyB3aWxsIGFsd2F5cyBoYXZlIFIgY29kZSwgdW5sZXNzIG5vdGVkCiAgICAgICAgICAgICAgICAgICAgICBlcnJvciA9IEZBTFNFLAogICAgICAgICAgICAgICAgICAgICAgZmlnLnBhdGg9IkZpZ3VyZXMvIiwgICMgU2V0IHRoZSBmaWd1cmUgb3B0aW9ucwogICAgICAgICAgICAgICAgICAgICAgZmlnLmFsaWduID0gImNlbnRlciIsIAogICAgICAgICAgICAgICAgICAgICAgZmlnLndpZHRoID0gNywKICAgICAgICAgICAgICAgICAgICAgIGZpZy5oZWlnaHQgPSA3KQojWW91IG5lZWQgdGhlIHN1Z2dlc3RlZCBwYWNrYWdlcyB0byBydW4gdGhpcyBub3RlYm9vawojaW5zdGFsbC5wYWNrYWdlcygndG1sZScsICdTdXBlckxlYXJuZXInLCAnRFQnKQpsaWJyYXJ5KCd0bWxlJywgJ1N1cGVyTGVhcm5lcicsICdEVCcpCmBgYAoKIyBJbnRyb2R1Y3Rpb24KCkR1cmluZyB0aGUgbGFzdCAzMCB5ZWFycywgKiptb2Rlcm4gZXBpZGVtaW9sb2d5KiogaGFzIGJlZW4gYWJsZSB0bwppZGVudGlmeSBzaWduaWZpY2FudCBsaW1pdGF0aW9ucyBvZiBjbGFzc2ljIGVwaWRlbWlvbG9naWMgbWV0aG9kcyB3aGVuCnRoZSBmb2N1cyBpcyB0byBleHBsYWluIHRoZSBtYWluIGVmZmVjdCBvZiBhIHJpc2sgZmFjdG9yIG9uIGEgZGlzZWFzZSBvcgpvdXRjb21lLgoKQ2F1c2FsIEluZmVyZW5jZSBiYXNlZCBvbiB0aGUgKipOZXltYS1SdWJpbiBQb3RlbnRpYWwgT3V0Y29tZXMKRnJhbWV3b3JrKiogW0BydWJpbjIwMTFdLCBmaXJzdCBpbnRyb2R1Y2VkIGluIFNvY2lhbCBTY2llbmNlIGJ5IERvbmFsClJ1YmluIFtAcnViaW4xOTc0XSBhbmQgbGF0ZXIgaW4gRXBpZGVtaW9sb2d5IGFuZCBCaW9zdGF0aXN0aWNzIGJ5IEphbWVzClJvYmlucyBbQHJvYmluczE5ODZdLCBoYXMgcHJvdmlkZWQgdGhlIHRoZW9yeSBhbmQgc3RhdGlzdGljYWwgbWV0aG9kcwpuZWVkZWQgdG8gb3ZlcmNvbWUgcmVjdXJyZW50IHByb2JsZW1zIGluIG9ic2VydmF0aW9uYWwgZXBpZGVtaW9sb2dpYwpyZXNlYXJjaCwgc3VjaCBhczoKCjEuICBub24tY29sbGFwc2liaWxpdHkgb2YgdGhlIG9kZHMgYW5kIGhhemFyZCByYXRpb3MsCjIuICBpbXBhY3Qgb2YgcGFyYWRveGljYWwgZWZmZWN0cyBkdWUgdG8gY29uZGl0aW9uaW5nIG9uIGNvbGxpZGVycywKMy4gIHNlbGVjdGlvbiBiaWFzIHJlbGF0ZWQgdG8gdGhlIHZhZ3VlIHVuZGVyc3RhbmRpbmcgb2YgdGhlIGVmZmVjdCBvZgogICAgdGltZSBvbiBleHBvc3VyZSBhbmQgb3V0Y29tZSBhbmQsCjQuICBlZmZlY3Qgb2YgdGltZS1kZXBlbmRlbnQgY29uZm91bmRpbmcgYW5kIG1lZGlhdG9ycywKNS4gIGV0Yy4KCkNhdXNhbCBlZmZlY3RzIGFyZSBvZnRlbiBmb3JtdWxhdGVkIHJlZ2FyZGluZyBjb21wYXJpc29ucyBvZiBwb3RlbnRpYWwKb3V0Y29tZXMsIGFzIGZvcm1hbGlzZWQgYnkgUnViaW4gW0BydWJpbjIwMTFdLiBMZXQgQSBkZW5vdGUgYSBiaW5hcnkKZXhwb3N1cmUsICoqVyoqIGEgdmVjdG9yIG9mIHBvdGVudGlhbCBjb25mb3VuZGVycywgYW5kIFkgYSBiaW5hcnkKb3V0Y29tZS4gR2l2ZW4gQSwgZWFjaCBpbmRpdmlkdWFsIGhhcyBhIHBhaXIgb2YgcG90ZW50aWFsIG91dGNvbWVzOiB0aGUKb3V0Y29tZSB3aGVuIGV4cG9zZWQsIGRlbm90ZWQgJFlfezF9JCwgYW5kIHRoZSBvdXRjb21lIHdoZW4gdW5leHBvc2VkLAokWV97MH0kLiBUaGVzZSBxdWFudGl0aWVzIGFyZSByZWZlcnJlZCB0byBhcyAqKnBvdGVudGlhbCBvdXRjb21lcyoqCnNpbmNlIHRoZXkgYXJlIGh5cG90aGV0aWNhbCwgZ2l2ZW4gdGhhdCBpdCBpcyBvbmx5IHBvc3NpYmxlIHRvIG9ic2VydmUgYQpzaW5nbGUgcmVhbGlzYXRpb24gb2YgdGhlIG91dGNvbWUgZm9yIGFuIGluZGl2aWR1YWw7IHdlIG9ic2VydmUgJFlfezF9JApvbmx5IGZvciB0aG9zZSBpbiB0aGUgZXhwb3N1cmUgZ3JvdXAgYW5kICRZX3swfSQgb25seSBmb3IgdGhvc2UgaW4gdGhlCnVuZXhwb3NlZCBncm91cCBbQHJ1YmluMTk3NF0uIEEgY29tbW9uIGNhdXNhbCBlc3RpbWFuZCBpcyB0aGUgKipBdmVyYWdlClRyZWF0bWVudCBFZmZlY3QqKiAoQVRFKSwgZGVmaW5lZCBhcyAkRVtZX3sxfVwsIOKAkyBcLFlfezB9XSQuCgpDbGFzc2ljYWwgZXBpZGVtaW9sb2dpYyBtZXRob2RzIHVzZSByZWdyZXNzaW9uIGFkanVzdG1lbnQgdG8gZXhwbGFpbiB0aGUKbWFpbiBlZmZlY3Qgb2YgYSByaXNrIGZhY3RvciBtZWFzdXJlIG9uIGEgZGlzZWFzZSBvciBvdXRjb21lLiBSZWdyZXNzaW9uCmFkanVzdG1lbnQgY29udHJvbCBmb3IgY29uZm91bmRpbmcgYnV0IHJlcXVpcmVzIG1ha2luZyB0aGUgYXNzdW1wdGlvbgp0aGF0IHRoZSBlZmZlY3QgbWVhc3VyZSBpcyBjb25zdGFudCBhY3Jvc3MgbGV2ZWxzIG9mIGNvbmZvdW5kZXJzCmluY2x1ZGVkIGluIHRoZSBtb2RlbC4gSG93ZXZlciwgaW4gbm9uLXJhbmRvbWl6ZWQgb2JzZXJ2YXRpb25hbCBzdHVkaWVzLAp0aGUgZWZmZWN0IG1lYXN1cmUgaXMgbm90IGNvbnN0YW50IGFjcm9zcyBncm91cHMgZ2l2ZW4gdGhlIGRpZmZlcmVudApkaXN0cmlidXRpb24gb2YgaW5kaXZpZHVhbCBjaGFyYWN0ZXJpc3RpY3MgYXQgYmFzZWxpbmUuCgpKYW1lcyBSb2JpbnMgaW4gMTk4NiBkZW1vbnN0cmF0ZWQgdGhhdCB1c2luZyB0aGUgKipHLWZvcm11bGEqKiBhCmdlbmVyYWxpemF0aW9uIG9mIHRoZSAqKnN0YW5kYXJkaXNhdGlvbioqLCBhbGxvd3Mgb2J0YWluaW5nIGEKdW5jb25mb3VuZGVkIG1hcmdpbmFsIGVzdGltYXRpb24gb2YgdGhlIEFURSB1bmRlciBjYXVzYWwgdW50ZXN0YWJsZQphc3N1bXB0aW9ucywgbmFtZWx5IGNvbmRpdGlvbmFsIG1lYW4gaW5kZXBlbmRlbmNlLCBwb3NpdGl2aXR5IGFuZApjb25zaXN0ZW5jeSBvciBzdGFibGUgdW5pdCB0cmVhdG1lbnQgdmFsdWUgYXNzaWdubWVudCAoU1VUVkEpCltAcm9iaW5zMTk4Nl0sIFtAcm9iaW5zMjAwMF06CgojIFRoZSBHLUZvcm11bGEgYW5kIEFURSBlc3RpbWF0aW9uCgokJFxwc2koUF97MH0pXCw9XCxcc3VtX3t3fVwsXGxlZnRbXHN1bV97eX1cLFAoWT15XG1pZCBBPTEsVz13KS1cLFxzdW1fe3l9XCxQKFkgPSB5XG1pZCBBPTAsVz13KVxyaWdodF1QKFc9dykkJAoKd2hlcmUsCgokJFAoWSA9IHkgXG1pZCBBID0gYSwgVyA9IHcpXCw9XCxcZnJhY3tQKFcgPSB3LCBBID0gYSwgWSA9IHkpfXtcc3VtX3t5fVwsUChXID0gdywgQSA9IGEsIFkgPSB5KX0kJFwKaXMgdGhlIGNvbmRpdGlvbmFsIHByb2JhYmlsaXR5IGRpc3RyaWJ1dGlvbiBvZiBZID0geSwgZ2l2ZW4gQSA9IGEsIFcgPSB3CmFuZCwKCiQkUChXID0gdylcLD1cLFxzdW1fe3ksYX1cLFAoVyA9IHcsIEEgPSBhLCBZID0geSkkJAoKVGhlIEFURSBjYW4gYmUgZXN0aW1hdGVkICoqbm9uLXBhcmFtZXRyaWNhbGx5KiogdXNpbmcgdGhlIEctZm9ybXVsYS4KSG93ZXZlciwgdGhlICoqY291cnNlIG9mIGRpbWVuc2lvbmFsaXR5KiogaW4gb2JzZXJ2YXRpb25hbCBzdHVkaWVzCmxpbWl0cyBpdHMgZXN0aW1hdGlvbi4gSGVuY2UsIHRoZSBlc3RpbWF0aW9uIG9mIHRoZSBBVEUgdXNpbmcgdGhlCkctZm9ybXVsYSByZWxpZXMgbW9zdGx5IG9uICoqcGFyYW1ldHJpYyBtb2RlbGxpbmcqKiBhbmQgbWF4aW11bQpsaWtlbGlob29kIGVzdGltYXRpb24uCgpUaGUgY29ycmVjdCBtb2RlbCBzcGVjaWZpY2F0aW9uIGluIHBhcmFtZXRyaWMgbW9kZWxsaW5nIGlzIGNydWNpYWwgdG8Kb2J0YWluIHVuYmlhc2VkIGVzdGltYXRlcyBvZiB0aGUgdHJ1ZSBBVEUgW0BydWJpbjIwMTFdLiBBbHRlcm5hdGl2ZWx5LApwcm9wZW5zaXR5IHNjb3JlIG1ldGhvZHMsIGludHJvZHVjZWQgYnkgUm9zZW5iYXVtIGFuZCBSdWJpbgpbQHJvc2VuYmF1bTE5ODNdLCBhcmUgYWxzbyBjb21tb25seSB1c2VkIGZvciBlc3RpbWF0aW9uIG9mIHRoZSBBVEUuIFRoZQpwcm9wZW5zaXR5IHNjb3JlIGlzIGEgYmFsYW5jaW5nIHNjb3JlIHRoYXQgY2FuIGJlIHVzZWQgdG8gY3JlYXRlCnN0YXRpc3RpY2FsbHkgZXF1aXZhbGVudCBleHBvc3VyZSBncm91cHMgdG8gZXN0aW1hdGUgdGhlIEFURSB2aWEKbWF0Y2hpbmcsIHdlaWdodGluZywgb3Igc3RyYXRpZmljYXRpb24gW0Byb3NlbmJhdW0xOTgzXS4KCkhvd2V2ZXIsIHZlcnkgbG93IG9yIHZlcnkgaGlnaCBwcm9wZW5zaXR5IHNjb3JlcyBjYW4gbGVhZCB0byB2ZXJ5IGxhcmdlCndlaWdodHMsIHJlc3VsdGluZyBpbiB1bnN0YWJsZSBBVEUgZXN0aW1hdGVzIHdpdGggaGlnaCB2YXJpYW5jZSBhbmQKdmFsdWVzIG91dHNpZGUgdGhlIGNvbnN0cmFpbnRzIG9mIHRoZSBzdGF0aXN0aWNhbCBtb2RlbApbQGx1bmNlZm9yZDIwMDRdLgoKRnVydGhlcm1vcmUsIHdoZW4gYW5hbHlpemluZyBvYnNlcnZhdGlvbmFsIGRhdGEgd2l0aCBhIGxhcmdlIG51bWJlciBvZgp2YXJpYWJsZXMgYW5kIHBvdGVudGlhbGx5IGNvbXBsZXggcmVsYXRpb25zaGlwcyBhbW9uZyB0aGVtLCBtb2RlbAptaXNzcGVjaWZpY2F0aW9uIGR1cmluZyBlc3RpbWF0aW9uIGlzIG9mIHBhcnRpY3VsYXIgY29uY2Vybi4gSGVuY2UsIHRoZQpjb3JyZWN0IG1vZGVsIHNwZWNpZmljYXRpb24gaW4gcGFyYW1ldHJpYyBtb2RlbGxpbmcgaXMgY3J1Y2lhbCB0byBvYnRhaW4KdW5iaWFzZWQgZXN0aW1hdGVzIG9mIHRoZSB0cnVlIEFURSBbQHZhbjIwMTFdLgoKSG93ZXZlciwgTWFyayB2YW4gZGVyIExhYW4gYW5kIFJ1YmluIFtAdmFuMjAwNl0gaW50cm9kdWNlZCBpbiAyMDA2IGEKKipkb3VibGUtcm9idXN0KiogZXN0aW1hdGlvbiBwcm9jZWR1cmUgdG8gKipyZWR1Y2UgYmlhcyoqIGFnYWluc3QKbWlzc3BlY2lmaWNhdGlvbi4gVGhlIHRhcmdldGVkIG1heGltdW0gbGlrZWxpaG9vZCBlc3RpbWF0aW9uICgqKlRNTEUqKikKaXMgYSBzZW1pcGFyYW1ldHJpYywgZWZmaWNpZW50IHN1YnN0aXR1dGlvbiBlc3RpbWF0b3IgW0B2YW4yMDExXS4KCiMgVE1MRQoKTm90ZTogZm9yIGEgbW9yZSBmb3JtYWwgcHJlc2VudGF0aW9uIG9mIHRoZSBUTUxFIHN0YXRpc3RpY2FsIGZyYW1ld29yawpyZWFkZXJzIHdvdWxkIGxpa2UgdG8gcmVhZCB0aGUgcHVibGlzaGVkIHR1dG9yaWFsIGluIFN0YXRpc3RpY3MgaW4KTWVkaWNpbmUgKDxodHRwczovL29ubGluZWxpYnJhcnkud2lsZXkuY29tL2RvaS9mdWxsLzEwLjEwMDIvc2ltLjc2Mjg+KS4KUmVhZGVycyByZWFkaW5nIHRoaXMgb3BlbiBzb3VyY2UgaW50cm9kdWN0b3J5IHR1dG9yaWFsIHNob3VsZCBnYWluCnN1ZmZpY2llbnQgdW5kZXJzdGFuZGluZyBvZiBUTUxFIHRvIGJlIGFibGUgdG8gYXBwbHkgdGhlIG1ldGhvZCBpbgpwcmFjdGljZS4gRXh0ZW5zaXZlIGNsYXNzaWMgUi1jb2RlIGlzIHByb3ZpZGVkIGluIGVhc3ktdG8tcmVhZCBib3hlcwp0aHJvdWdob3V0IHRoZSB0dXRvcmlhbCBmb3IgcmVwbGljYWJpbGl0eS4gU3RhdGEgdXNlcnMgd2lsbCBmaW5kIGEKdGVzdGluZyBpbXBsZW1lbnRhdGlvbiBvZiBUTUxFIGFuZCBhZGRpdGlvbmFsIG1hdGVyaWFsIGluIHRoZSBhcHBlbmRpeAphbmQgYXQgdGhlIGZvbGxvd2luZyBHaXRIdWIgcmVwb3NpdG9yeQo8aHR0cHM6Ly9naXRodWIuY29tL21pZ2FyaWFuZS9TSU0tVE1MRS10dXRvcmlhbD4KCioqVE1MRSoqIGFsbG93cyBmb3IgZGF0YS1hZGFwdGl2ZSBlc3RpbWF0aW9uIHdoaWxlIG9idGFpbmluZyB2YWxpZApzdGF0aXN0aWNhbCBpbmZlcmVuY2UgYmFzZWQgb24gdGhlIHRhcmdldGVkIG1pbmltdW0gbG9zcy1iYXNlZAplc3RpbWF0aW9uIGFuZCBtYWNoaW5lIGxlYXJuaW5nIGFsZ29yaXRobXMgdG8gbWluaW1pc2UgdGhlIHJpc2sgb2YgbW9kZWwKbWlzc3BlY2lmaWNhdGlvbiBbQHZhbjIwMTFdLiBUaGUgbWFpbiBjaGFyYWN0ZXJpc3RpY3Mgb2YgKipUTUxFKiogYXJlOgoKMS4gICoqVE1MRSoqIGlzIGEgZ2VuZXJhbCBhbGdvcml0aG0gZm9yIHRoZSBjb25zdHJ1Y3Rpb24gb2YKICAgIGRvdWJsZS1yb2J1c3QsIHNlbWlwYXJhbWV0cmljLCBlZmZpY2llbnQgc3Vic3RpdHV0aW9uIGVzdGltYXRvcnMuCiAgICAqKlRNTEUqKiBhbGxvd3MgZm9yIGRhdGEtYWRhcHRpdmUgZXN0aW1hdGlvbiB3aGlsZSBvYnRhaW5pbmcgdmFsaWQKICAgIHN0YXRpc3RpY2FsIGluZmVyZW5jZS4KCjIuICAqKlRNTEUqKiBpbXBsZW1lbnRhdGlvbiB1c2VzIHRoZSBHLWNvbXB1dGF0aW9uIGVzdGltYW5kIChHLWZvcm11bGEpLgogICAgQnJpZWZseSwgdGhlICoqVE1MRSoqIGFsZ29yaXRobSB1c2VzIGluZm9ybWF0aW9uIGluIHRoZSBlc3RpbWF0ZWQKICAgIGV4cG9zdXJlIG1lY2hhbmlzbSBQKEFcfFcpIHRvIHVwZGF0ZSB0aGUgaW5pdGlhbCBlc3RpbWF0b3Igb2YgdGhlCiAgICBjb25kaXRpb25hbCBleHBlY3RhdGlvbiBvZiB0aGUgb3V0Y29tZSBnaXZlbiB0aGUgdHJlYXRtZW50IGFuZCB0aGUKICAgIHNldCBvZiBjb3ZhcmlhdGVzIFcsIEUkX3swfSQoWVx8QSxXKS4KCjMuICBUaGUgdGFyZ2V0ZWQgZXN0aW1hdGVzIGFyZSB0aGVuIHN1YnN0aXR1dGVkIGludG8gdGhlIHBhcmFtZXRlcgogICAgbWFwcGluZyAkXFBzaSQuIFRoZSB1cGRhdGluZyBzdGVwIGFjaGlldmVzIGEgdGFyZ2V0ZWQgYmlhcyByZWR1Y3Rpb24KICAgIGZvciB0aGUgcGFyYW1ldGVyIG9mIGludGVyZXN0ICRcUHNpKFBfezB9KSQgKHRoZSB0cnVlIHRhcmdldAogICAgcGFyYW1ldGVyKSBhbmQgc2VydmVzIHRvIHNvbHZlIHRoZSBlZmZpY2llbnQgc2NvcmUgZXF1YXRpb24sIG5hbWVseQogICAgdGhlIEluZmx1ZW5jZSBDdXJ2ZSAoSUMpLiBBcyBhIHJlc3VsdCwgKipUTUxFKiogaXMgYQogICAgKipkb3VibGUtcm9idXN0KiogZXN0aW1hdG9yLgoKNC4gICoqVE1MRSoqIGl0IHdpbGwgYmUgY29uc2lzdGVudCBmb3IgJFxQc2koUF97MH0pJCBpZiBlaXRoZXIgdGhlCiAgICBjb25kaXRpb25hbCBleHBlY3RhdGlvbiBFJF97MH0kKFlcfEEsVykgb3IgdGhlIGV4cG9zdXJlIG1lY2hhbmlzbQogICAgUCRfezB9JChBXHxXKSBhcmUgZXN0aW1hdGVkIGNvbnNpc3RlbnRseS4KCjUuICAqKlRNTEUqKiB3aWxsIGJlIGVmZmljaWVudCBpZiB0aGUgcHJldmlvdXMgdHdvIGZ1bmN0aW9ucyBhcmUKICAgIGNvbnNpc3RlbnRseSBlc3RpbWF0ZWQgYWNoaWV2aW5nIHRoZSBsb3dlc3QgYXN5bXB0b3RpYyB2YXJpYW5jZQogICAgYW1vbmcgYSBsYXJnZSBjbGFzcyBvZiBlc3RpbWF0b3JzLiBUaGVzZSBhc3ltcHRvdGljIHByb3BlcnRpZXMKICAgIHR5cGljYWxseSB0cmFuc2xhdGUgaW50byAqKmxvd2VyIGJpYXMgYW5kIHZhcmlhbmNlKiogaW4gZmluaXRlCiAgICBzYW1wbGVzIFtAYnVoMjAxNl0uCgo2LiAgVGhlIGdlbmVyYWwgZm9ybXVsYSB0byBlc3RpbWF0ZSB0aGUgQVRFIHVzaW5nIHRoZSBUTUxFIG1ldGhvZDoKCiQkXHBzaSBUTUxFLG4gPSBcUHNpKFFfe259XnsqfSk9IHtcZnJhY3sxfXtufVxzdW1fe2k9MX1ee259XGJhcntRfV97bn1eezF9XGxlZnQoMSxcIFdfe2l9XHJpZ2h0KS1cYmFye1F9X3tufV57MX1cbGVmdCgwLFwgV197aX1ccmlnaHQpfS4gICgxKSQkCjcuIFRoZSBlZmZpY2llbnQgaW5mbHVjZW5jZSBjdXJ2ZSAoSUMpIGJhc2VkIG9uIHRoZSBGdW5jdGlvbmFsIERlbHRhCk1ldGhvZCBhbmQgRW1waXJpY2FsIFByb2Nlc3MgVGhlb3J5IFtAZmlzaGVyMjAxOF0gaXMgYXBwbGllZCBmb3IKc3RhdGlzdGljYWwgaW5mZXJlbmNlIHVzaW5nIFRNTEU6CgokJElDX3tufShPX3tpfSk9XGxlZnQoXGZyYWN7SVxsZWZ0KEFfe2l9PTFccmlnaHQpfXtnX25cbGVmdCgxXGxlZnR8V197aX1ccmlnaHQpXHJpZ2h0KX1cIC1cIFxmcmFje0lcbGVmdChBX3tpfT0wXHJpZ2h0KX17Z19uXGxlZnQoMFxsZWZ0fFdfe2l9XHJpZ2h0KVxyaWdodCl9XCBccmlnaHQpXGxlZnRbWV97aX0tXGJhcntRfV97bn1eezF9XGxlZnQoQV97aX0sV197aX1ccmlnaHQpXHJpZ2h0XStcYmFye1F9X3tufV57MX1cbGVmdCgxLFwgV197aX1ccmlnaHQpLVxiYXJ7UX1fe259XnsxfVxsZWZ0KDAsXCBXX3tpfVxyaWdodCkgLSBccHNpIFRNTEUsbi4gKDIpJCRcCndoZXJlIHRoZSB2YXJpYW5jZSBvZiB0aGUgQVRFOgoKJCRcc2lnbWEoe1xwc2lfezB9fSk9XHNxcnR7XGZyYWN7VmFyKElDX3tufSl9e259fS4gICgzKSQkCgo4LiAgVGhlIHByb2NlZHVyZSBpcyBhdmFpbGFibGUgd2l0aCBzdGFuZGFyZCBzb2Z0d2FyZSBzdWNoIGFzIHRoZQogICAgKip0bWxlKiogcGFja2FnZSBpbiBSIFtAZ3J1YmVyMjAxMV0uCgojIFN0cnVjdHVyYWwgY2F1c2FsIGZyYW1ld29yawoKIyMgRGlyZWN0IEFjeWNsaWMgR3JhcGggKERBRykKCiFbXShGaWd1cmVzL0RBRy5wbmcpICoqRmlndXJlIDEqKi4gRGlyZWN0IEFjeWNsaWMgR3JhcGggKERBRylcCioqU291cmNlKio6IE1pZ3VlbCBBbmdlbCBMdXF1ZS1GZXJuYW5kZXoKCiMjIERBRyBpbnRlcnByZXRhdGlvbgoKVGhlIEFURSBpcyBpbnRlcnByZXRlZCBhcyB0aGUgcG9wdWxhdGlvbiByaXNrIGRpZmZlcmVuY2UgaW4gb25lLXllYXIKbW9ydGFsaXR5IGZvciBsYXJ5bmdlYWwgY2FuY2VyIHBhdGllbnRzIHRyZWF0ZWQgd2l0aCBjaGVtb3RoZXJhcHkgdmVyc3VzCnJhZGlvdGhlcmFweS4gVW5kZXIgY2F1c2FsIGFzc3VtcHRpb25zLCBhbmQgY29tcGFyZWQgd2l0aCByYWRpb3RoZXJhcHksCnRoZSByaXNrIGRpZmZlcmVuY2Ugb2Ygb25lLXllYXIgbW9ydGFsaXR5IGZvciBwYXRpZW50cyB0cmVhdGVkIHdpdGgKY2hlbW90aGVyYXB5IGluY3JlYXNlcyBieSBhcHByb3hpbWF0ZWx5ICoqMjAlKiouCgojIENhdXNhbCBhc3N1bXB0aW9ucwoKVG8gZXN0aW1hdGUgdGhlIHZhbHVlIG9mIHRoZSB0cnVlIGNhdXNhbCB0YXJnZXQgcGFyYW1ldGVyICRccHNpKFBfezB9KSQKd2l0aCBhIG1vZGVsIGZvciB0aGUgdHJ1ZSBkYXRhIGdlbmVyYXRpb24gcHJvY2VzcyAkUF97MH0kIHVuZGVyIHRoZQpjb3VudGVyZmFjdHVhbCBmcmFtZXdvcmsgYXVnbWVudGVkIGFkZGl0aW9uYWwgdW50ZXN0YWJsZSBjdWFzYWwKYXNzdW1wdGlvbnMgaGF2ZSB0byBiZSBjb25zaWRlcmVkIFtAcnViaW4yMDExXSwgW0B2YW4yMDExXToKCiMjIENNSSBvciBSYW5kb21pemF0aW9uCgooJFlfezB9LFlfezF9XHBlcnAkQVx8Vykgb3IgY29uZGl0aW9uYWwgbWVhbiBpbmRlcGVuZGVuY2UgKENNSSkgb2YgdGhlCmJpbmFyeSB0cmVhdG1lbnQgZWZmZWN0IChBKSBvbiB0aGUgb3V0Y29tZSAoWSkgZ2l2ZW4gdGhlIHNldCBvZiBvYnNlcnZlZApjb3ZhcmlhdGVzIChXKSwgd2hlcmUgVyA9IChXMSwgVzIsIFczLCDigKYgLCAkXHRleHR7V31fe2t9JCkuCgojIyBQb3NpdGl2aXR5CgphIM+1IEE6IFAoQT1hIFx8IFcpIFw+IDBcClAoQT0xXHxXPXcpIFw+IDAgYW5kIFAoQT0wXHwgVyA9IHcpIFw+IDAgZm9yIGVhY2ggcG9zc2libGUgdy4KCiMjIENvbnNpc3RlbmN5IG9yIFNVVFZBCgpUaGUgU3RhYmxlIFVuaXQgVHJlYXRtZW50IFZhbHVlIEFzc3VtcHRpb24gKFNVVFZBKSBpbmNvcnBvcmF0ZXMgYm90aAp0aGlzIGlkZWEgdGhhdCAqKnVuaXRzIGRvIG5vdCBpbnRlcmZlcmUqKiB3aXRoIG9uZSBhbm90aGVyLCBhbmQgYWxzbyB0aGUKY29uY2VwdCB0aGF0IGZvciBlYWNoIHVuaXQgdGhlcmUgaXMgb25seSBhICoqc2luZ2xlIHZlcnNpb24gb2YgZWFjaAp0cmVhdG1lbnQgbGV2ZWwqKi4KCiMgVE1MRSBmbG93IGNoYXJ0CgohW10oRmlndXJlcy9zdGVwcy5wbmcpICoqRmlndXJlIDIqKi4gVE1MRSBmbG93IGNoYXJ0IChSb2FkIG1hcClcCioqQWRhcHRlZCBmcm9tKio6IE1hcmsgdmFuIGRlciBMYWFuIGFuZCBTaGVycmkgUm9zZS4gVGFyZ2V0ZWQgbGVhcm5pbmc6CmNhdXNhbCBpbmZlcmVuY2UgZm9yIG9ic2VydmF0aW9uYWwgYW5kIGV4cGVyaW1lbnRhbCBkYXRhU3ByaW5nZXIgU2VyaWVzCmluIFN0YXRpc3RpY3MsIDIwMTEuCgojIERhdGEgZ2VuZXJhdGlvbgoKIyMgU2ltdWxhdGlvbgoKSW4gUiB3ZSBjcmVhdGUgYSBmdW5jdGlvbiB0byBnZW5lcmF0ZSB0aGUgZGF0YS4gVGhlIGZ1bmN0aW9uIHdpbGwgaGF2ZQphcyBpbnB1dCAqKm51bWJlciBvZiBkcmF3cyoqIGFuZCBhcyBvdXRwdXQgdGhlIGdlbmVyYXRlZCAqKm9ic2VydmVkCmRhdGEqKiAoT2JzRGF0YSkgaW5jbHVkaW5nIHRoZSBjb3VudGVyZmFjdHVhbHMgKFkxLCBZMCkuCgpUaGUgc2ltdWxhdGVkIGRhdGEgcmVwbGljYXRpb25nIHRoZSBEQUcgaW4gRmlndXJlIDE6CgoxLiAgWTogbW9ydGFsaXR5IGJpbmFyeSBpbmRpY2F0b3IgKDEgZGVhdGgsIDAgYWxpdmUpCjIuICBBOiBiaW5hcnkgdHJlYXRtZW50ICgxIENoZW1vdGhlcmFweSwgMCBSYWRpb3RoZXJhcHkgKVwKMy4gIFcxOiBHZW5kZXIgKDEgbWFsZTsgMCBmZW1hbGUpXAo0LiAgVzI6IEFnZSBhdCBkaWFnbm9zaXMgKDAgXDw2NTsgMSBcPj02NSlcCjUuICBXMzogQ2FuY2VyIFROTSBjbGFzc2lmaWNhdGlvbiAoc2NhbGUgZnJvbSAxIHRvIDQ7IDE6IGVhcmx5IHN0YWdlIG5vCiAgICBtZXRhc3Rhc2lzOyA0OiBhZHZhbmNlZCBzdGFnZSB3aXRoIG1ldGFzdGFzaXMpXAo2LiAgVzQ6IENvbW9yYmlkaXRpZXMgKHNjYWxlIGZyb20gMSB0byA1KQoKYGBge3Igd2FybmluZz1GQUxTRX0Kb3B0aW9ucyhkaWdpdHM9NCkKZ2VuZXJhdGVEYXRhIDwtIGZ1bmN0aW9uKG4pewogIHcxIDwtIHJiaW5vbShuLCBzaXplPTEsIHByb2I9MC41KQogIHcyIDwtIHJiaW5vbShuLCBzaXplPTEsIHByb2I9MC42NSkKICB3MyA8LSByb3VuZChydW5pZihuLCBtaW49MCwgbWF4PTQpLCBkaWdpdHM9MykKICB3NCA8LSByb3VuZChydW5pZihuLCBtaW49MCwgbWF4PTUpLCBkaWdpdHM9MykKICBBICA8LSByYmlub20obiwgc2l6ZT0xLCBwcm9iPSBwbG9naXMoLTAuNCArIDAuMip3MiArIDAuMTUqdzMgKyAwLjIqdzQgKyAwLjE1KncyKnc0KSkKICAjIGNvdW50ZXJmYWN0dWFsCiAgWS4xIDwtIHJiaW5vbShuLCBzaXplPTEsIHByb2I9IHBsb2dpcygtMSArIDEgLTAuMSp3MSArIDAuMyp3MiArIDAuMjUqdzMgKyAwLjIqdzQgKyAwLjE1KncyKnc0KSkKICBZLjAgPC0gcmJpbm9tKG4sIHNpemU9MSwgcHJvYj0gcGxvZ2lzKC0xICsgMCAtMC4xKncxICsgMC4zKncyICsgMC4yNSp3MyArIDAuMip3NCArIDAuMTUqdzIqdzQpKQogICMgT2JzZXJ2ZWQgb3V0Y29tZQogIFkgPC0gWS4xKkEgKyBZLjAqKDEgLSBBKQogICMgcmV0dXJuIGRhdGEuZnJhbWUKICBkYXRhLmZyYW1lKHcxLCB3MiwgdzMsIHc0LCBBLCBZLCBZLjEsIFkuMCkKfQpzZXQuc2VlZCg3Nzc3KQpPYnNEYXRhIDwtIGdlbmVyYXRlRGF0YShuPTEwMDAwKQpUcnVlX1BzaSA8LSBtZWFuKE9ic0RhdGEkWS4xLU9ic0RhdGEkWS4wKTsKY2F0KCIgVHJ1ZV9Qc2k6IiwgVHJ1ZV9Qc2kpCkJpYXNfUHNpIDwtIGxtKGRhdGE9T2JzRGF0YSwgWX4gQSArIHcxICsgdzIgKyB3MyArIHc0KQpjYXQoIlxuIikKY2F0KCJcbiBOYWl2ZV9CaWFzZWRfUHNpOiIsc3VtbWFyeShCaWFzX1BzaSkkY29lZlsyLCAxXSkKTmFpdmVfQmlhcyA8LSAoKHN1bW1hcnkoQmlhc19Qc2kpJGNvZWZbMiwgMV0pLVRydWVfUHNpKTsgY2F0KCJcbiBOYWl2ZXMgYmlhczoiLCBOYWl2ZV9CaWFzKQpOYWl2ZV9SZWxhdGl2ZV9CaWFzIDwtICgoKHN1bW1hcnkoQmlhc19Qc2kpJGNvZWZbMiwgMV0pLVRydWVfUHNpKS9UcnVlX1BzaSkqMTAwOyBjYXQoIlxuIFJlbGF0aXZlIE5haXZlcyBiaWFzOiIsIE5haXZlX1JlbGF0aXZlX0JpYXMsIiUiKQpgYGAKCiMjIERhdGEgdmlzdWFsaXphdGlvbgoKYGBge3J9CiMgRFQgdGFibGUgPSBpbnRlcmFjdGl2ZQojIGluc3RhbGwucGFja2FnZXMoIkRUIikgIyBpbnN0YWxsIERUIGZpcnN0CmxpYnJhcnkoRFQpCmRhdGF0YWJsZShoZWFkKE9ic0RhdGEsIG4gPSBucm93KE9ic0RhdGEpKSwgb3B0aW9ucyA9IGxpc3QocGFnZUxlbmd0aCA9IDUsIGRpZ2l0cyA9IDIpKQpgYGAKCiMgVE1MRSBzaW1wbGUgaW1wbGVtZW50YXRpb24KCiMjIFN0ZXAgMTogJFFfezB9JChBLCoqVyoqKQoKRXN0aW1hdGlvbiBvZiB0aGUgaW5pdGlhbCBwcm9iYWJpbGl0eSBvZiB0aGUgb3V0Y29tZSAoWSkgZ2l2ZW4gdGhlCnRyZWF0bWVudCAoQSkgYW5kIHRoZSBzZXQgb2YgY292YXJpYXRlcyAoVyksIGRlbm90ZWQgYXMKJFFfezB9JChBLCoqVyoqKS4gVG8gZXN0aW1hdGUgJFFfezB9JChBLCoqVyoqKSB3ZSBjYW4gdXNlIGEgc3RhbmRhcmQKbG9naXN0aWMgcmVncmVzc2lvbiBtb2RlbDoKCiQkXHRleHR7bG9naXR9W1AoWT0xfEEsVyldXCw9XCxcYmV0YV97MH1cLCtcLFxiZXRhX3sxfUFcLCtcLFxoYXR7XGJldGFfezJ9XntUfX1XLiQkCgpUaGVyZWZvcmUsIHdlIGNhbiBlc3RpbWF0ZSB0aGUgaW5pdGlhbCBwcm9iYWJpbGl0eSBhcyBmb2xsb3dzOgoKJCRcYmFye1F9XnswfShBLFcpXCw9XCxcdGV4dHtleHBpdH0oXGhhdHtcYmV0YV97MH19XCwrXCxcaGF0e1xiZXRhX3sxfX1BXCwrXCxcaGF0e1xiZXRhX3syfV57VH19VykuJCQKClRoZSBwcmVkaWN0ZWQgcHJvYmFiaWxpdHkgY2FuIGJlIGVzdGltYXRlZCB1c2luZyB0aGUgU3VwZXItTGVhcm5lcgpsaWJyYXJ5IGltcGxlbWVudGVkIGluIHRoZSBSIHBhY2thZ2Ug4oCcU3VwZXItTGVhcm5lcuKAnSBbQHZhbjIwMDddIHRvCmluY2x1ZGUgYW55IHRlcm1zIHRoYXQgYXJlIGZ1bmN0aW9ucyBvZiBBIG9yIFcgKGUuZy4sIHBvbHlub21pYWwgdGVybXMKb2YgQSBhbmQgVywgYXMgd2VsbCBhcyB0aGUgaW50ZXJhY3Rpb24gdGVybXMgb2YgQSBhbmQgVywgY2FuIGJlCmNvbnNpZGVyZWQpLgoKQ29uc2VxdWVudGx5LCBmb3IgZWFjaCBzdWJqZWN0LCB0aGUgcHJlZGljdGVkIHByb2JhYmlsaXRpZXMgZm9yIGJvdGgKcG90ZW50aWFsIG91dGNvbWVzICRcYmFye1F9XnswfSgwLFcpJCBhbmQgJFxiYXJ7UX1eezB9KDEsVykkIGNhbiBiZQplc3RpbWF0ZWQgYnkgc2V0dGluZyBBID0gMCBhbmQgQSA9IDEgZm9yIGV2ZXJ5b25lIHJlc3BlY3RpdmVseToKJCRcYmFye1F9XnswfSgwLFcpXCw9XCxcdGV4dHtleHBpdH0oXGhhdHtcYmV0YV97MH19XCwrXCxcaGF0e1xiZXRhX3syfV57VH19VyksJCQKYW5kLFwKJCRcYmFye1F9XnswfSgxLFcpXCw9XCxcdGV4dHtleHBpdH0oXGhhdHtcYmV0YV97MH19XCwrXCxcaGF0e1xiZXRhX3sxfX1BXCwrXCxcaGF0e1xiZXRhX3syfV57VH19VykuJCQKKipOb3RlKio6IHNlZSBhcHBlbmRpeCBvbmUgZm9yIGEgc2hvcnQgaW50cm9kdWN0aW9uIHRvIHRoZSBTdXBlci1MZWFybmVyCmFuZCBlbnNlbWJsZSBsZWFybmluZyB0ZWNobmlxdWVzLgoKYGBge3J9Ck9ic0RhdGEgPC1zdWJzZXQoT2JzRGF0YSwgc2VsZWN0PWModzEsdzIsdzMsdzQsQSxZKSkKWSAgPC0gT2JzRGF0YSRZCkEgIDwtIE9ic0RhdGEkQQp3MSA8LSBPYnNEYXRhJHcxCncyIDwtIE9ic0RhdGEkdzIKdzMgPC0gT2JzRGF0YSR3Mwp3NCA8LSBPYnNEYXRhJHc0Cm0gIDwtIGdsbShZIH4gQSArIHcxICsgdzIgKyB3MyArIHc0LCBmYW1pbHk9Ymlub21pYWwsIGRhdGE9T2JzRGF0YSkKUSAgPC0gY2JpbmQoUUFXID0gcHJlZGljdChtKSwKICAgICAgICAgICAgUTFXID0gcHJlZGljdChtLCBuZXdkYXRhPWRhdGEuZnJhbWUoQSA9IDEsIHcxLCB3MiwgdzMsIHc0KSksCiAgICAgICAgICAgIFEwVyA9IHByZWRpY3QobSwgbmV3ZGF0YT1kYXRhLmZyYW1lKEEgPSAwLCB3MSwgdzIsIHczLCB3NCkpKQpRMCA8LSBhcy5kYXRhLmZyYW1lKFEpClkxIDwtIFEwJFExVyAKWTAgPC0gUTAkUTBXClFBMSA8LSBleHAoWTEpLygxK2V4cChZMSkpClFBMCA8LSBleHAoWTApLygxK2V4cChZMCkpCiNJbnZlcnNlIGxvZ2l0IChwcm9iYWJpbGl0eSBzY2FsZSkKcHNpIDwtIChleHAoWTEpLygxK2V4cChZMSkpIC0gZXhwKFkwKS8oMStleHAoWTApKSkKUHNpIDwtIG1lYW4oZXhwKFkxKS8oMStleHAoWTEpKSAtIGV4cChZMCkvKDErZXhwKFkwKSkpOyBjYXQoIlxuIFEwOiIsIFBzaSkKZGYgPC0gcm91bmQoY2JpbmQoTG9naXQ9KFEpLFByLlkxPVFBMSxQci5ZMD1RQTAsUHNpPXBzaSksIGRpZ2l0cz0gMykKYGBgCgoqKlZpc3VhbGl6aW5nKiogdGhlIGZpcnN0IHN0ZXA6CgpgYGB7ciwgd2FybmluZz1GQUxTRX0KZGF0YXRhYmxlKGhlYWQoZGYsIG4gPSBucm93KGRmKSksIG9wdGlvbnMgPSBsaXN0KHBhZ2VMZW5ndGggPSA1LCBkaWdpdHMgPSAzKSkKYGBgCgojIyBTdGVwIDI6ICRnX3swfShBLFcpJAoKRXN0aW1hdGlvbiBvZiB0aGUgcHJvYmFiaWxpdHkgb2YgdGhlIHRyZWF0bWVudCAoQSkgZ2l2ZW4gdGhlIHNldCBvZgpjb3ZhcmlhdGVzIChXKSwgZGVub3RlZCBhcyAkZ197MH0oQSxXKSQuIFdlIGNhbiB1c2UgYWdhaW4gYSBsb2dpc3RpYwpyZWdyZXNzaW9uIG1vZGVsIGFuZCB0byBpbXByb3ZlIHRoZSBwcmVkaWN0aW9uIGFsZ29yaXRobSB3ZSBjYW4gdXNlIHRoZQpTdXBlci1MZWFybmVyIGxpYnJhcnkgb3IgYW55IG90aGVyIG1hY2hpbmUgbGVhcm5pbmcgc3RyYXRlZ3k6CgokJFx0ZXh0e2xvZ2l0fVtQKEE9MXxXKV1cLD1cLFxhbHBoYV97MH1cLCtcLFxhbHBoYV97MX1ee1R9Vy4kJCBUaGVuLCB3ZQplc3RpbWF0ZSB0aGUgcHJlZGljdGVkIHByb2JhYmlsaXR5IG9mIFAoQVx8VykgPSAkXGhhdHtnfSgxLFcpJCB1c2luZzoKCiQkXGhhdHtnfSgxLFcpXCw9XCxcdGV4dHtleHBpdH1cLChcaGF0e1xhbHBoYV97MH19XCwrXCxcaGF0e1xhbHBoYV97MX1ee1R9fVcpLiQkCgpgYGB7cn0KZyA8LSBnbG0oQSB+IHcyICsgdzMgKyB3NCwgZmFtaWx5ID0gYmlub21pYWwpCmcxVyA9IHByZWRpY3QoZywgdHlwZSA9InJlc3BvbnNlIik7Y2F0KCJcbiBQcm9wZW5zaXR5IHNjb3JlID0gZzFXIiwiXG4iKTtzdW1tYXJ5KGcxVykKYGBgCgojIyBTdGVwIDM6IEhBVyBhbmQgJFxlcHNpbG9uJAoKVGhpcyBzdGVwIGFpbXMgdG8gZmluZCBhIGJldHRlciBwcmVkaWN0aW9uIG1vZGVsIHRhcmdldGVkIGF0IG1pbmltaXNpbmcKdGhlIG1lYW4gc3F1YXJlZCBlcnJvciAoTVNFKSBmb3IgdGhlIHBvdGVudGlhbCBvdXRjb21lcy4gRm9yIHRoZSBBVEUgb24Kc3RlcCBjb252ZXJnZW5jZSBpcyBndWFyYW50ZWVkIGdpdmVuICRcYmFye1F9XnswfSQgYW5kICRcaGF0e2d9KDEsVykkLgoKVGhlIGZsdWN0dWF0aW9uIHBhcmFtZXRlcnMgJChcaGF0e1xlcHNpbG9ufV97MH1cLCxcLFxoYXR7XGVwc2lsb259X3sxfSkkCmFyZSBlc3RpbWF0ZWQgdXNpbmcgbWF4aW11bSBsaWtlbGlob29kIHByb2NlZHVyZXMgYnkgc2V0dGluZwokXHRleHR7bG9naXR9KFxiYXJ7UV57MH19KEEsVykpJCBhcyBhbiBvZmZzZXQgaW4gYSBpbnRlcmNlcHQtZnJlZQpsb2dpc3RpYyByZWdyZXNzaW9uIHdpdGggJEhfezB9JCBhbmQgJEhfezF9JCBhcyBpbmRlcGVuZGVudCB2YXJpYWJsZXM6CgokJFxiYXJ7UV57MX19KEEsVylcLD1cLFx0ZXh0e2V4cGl0fVxsZWZ0W1x0ZXh0e2xvZ2l0fVxsZWZ0KFxiYXJ7UV57MH19KEEsIFcpXHJpZ2h0KVwsK1wsXGhhdHtcZXBzaWxvbl97MH19SF97MH0oQSxXKVwsK1wsXGhhdHtcZXBzaWxvbl97MX19SF97MX0oQSxXKVxyaWdodF0gICg1KSQkCiQkXGJhcntRXnsxfX0oMCxXKVwsPVwsXHRleHR7ZXhwaXR9XGxlZnRbXHRleHR7bG9naXR9XGxlZnQoXGJhcntRXnswfX0oQSxXKVxyaWdodClcLCtcLFxoYXR7XGVwc2lsb25fezB9fUhfezB9KDAsVylccmlnaHRdJCQKCiQkXGJhcntRXnsxfX0oMSxXKVwsPVwsXHRleHR7ZXhwaXR9XGxlZnRbXHRleHR7bG9naXR9XGxlZnQoXGJhcntRXnswfX0oQSxXKVxyaWdodClcLCtcLFxoYXR7XGVwc2lsb25fezF9fUhfezF9KDEsVylccmlnaHRdJCQKV2hlcmUsCiQkSF97MH0oQSxXKVwsPVwsLVxmcmFje0koQT0wKX17XGhhdHtnfSgwfFcpfVw7XHRleHR7YW5kfSxcO0hfezF9KEEsVylcLD1cLFxmcmFje0koQT0xKX17XGhhdHtnfSgxfFcpfSQkCmFyZSB0aGUgc3RhYmlsaXplZCBpbnZlcnNlIHByb2JhYmlsaXR5IG9mIHRyZWF0bWVudCAoQSkgd2VpZ2h0cyAoSVBUVyksCm5hbWVsbHkgdGhlICoqY2xldmVyIGNvdmFyaWF0ZXMqKiBhbmQgKipJKiogZGVmaW5lcyBhbiBpbmRpY2F0b3IKZnVuY3Rpb24gKG5vdGUgdGhhdCAkXGhhdHtnfShBfFcpJCBpcyBlc3RpbXRlZCBmcm9tIHN0ZXAgMikuCgpgYGB7cn0KI0NsZXZlciBjb3ZhcmlhdGUgYW5kIGZsdWN0dWF0aW5nL3N1YnN0aXR1dGlvbiBwYXJhbXRlcmVzCmggPC0gY2JpbmQoZ0FXPShBL2cxVyAtICgxIC0gQSkgLyAoMSAtIGcxVykpLCBnMVcgPSAoMS9nMVcpLCBnMFc9KC0xIC8gKDEgLSBnMVcpKSkKZXBzaWxvbiA8LSBjb2VmKGdsbShZIH4gLTEgKyBoWywxXSArIG9mZnNldChRWywiUUFXIl0pLCBmYW1pbHkgPSBiaW5vbWlhbCkpO2NhdCgiXG4gRXBzaWxvbjoiLGVwc2lsb24pCmRmIDwtIHJvdW5kKGNiaW5kKFEwLFBTPShnMVcpLEg9KGgpLGVwc2lsb24pLCBkaWdpdHM9IDQpCmBgYAoKKipWaXN1YWxpemluZyoqIHRoZSAzcmQgc3RlcCAoUFMgPSBwcm9wZW5zaXR5IHNjb3JlOyBIID0gSVBUVyBvciBjbGV2ZXIKY292YXJpdGVzKToKCmBgYHtyLCB3YXJuaW5nPUZBTFNFfQpkYXRhdGFibGUoaGVhZChkZiwgbiA9IG5yb3coZGYpKSwgb3B0aW9ucyA9IGxpc3QocGFnZUxlbmd0aCA9IDUsIGRpZ2l0cyA9IDMpKQpgYGAKCiMjIFN0ZXAgNCAkXGJhcntRX3tufX1eeyp9Olx0ZXh0e2Zyb219XCxcYmFye1FfezB9fV57MH1cLFx0ZXh0e3RvfVwsXGJhcntRX3sxfX1eezF9JAoKQWZ0ZXJ3YXJkcywgdGhlIGVzdGltYXRlZCBwcm9iYWJpbGl0eSBvZiB0aGUgcG90ZW50aWFsIG91dGNvbWVzIGlzCnVwZGF0ZWQgYnkgdGhlIHN1YnN0aXR1dGlvbiBwYXJhbWV0ZXJzCiQoXGhhdHtcZXBzaWxvbl97MH19XCwsXCxcaGF0e1xlcHNpbG9uX3sxfX0pJC4gVGhlIHN1YnN0aXR1dGlvbiB1cGRhdGUKaXMgcGVyZm9ybWVkIGJ5IHNldHRpbmcgQSA9IDAgYW5kIEEgPSAxIGZvciBlYWNoIHN1YmplY3QgaW4gdGhlIGluaXRpYWwKZXN0aW1hdGUgcHJvYmFiaWxpdHkgb2YgdGhlIHBvdGVudGlhbCBvdXRjb21lcwokXGJhcntRXnswfX0oMCxXKVwsLFwsXGJhcntRXnswfX0oMSxXKSQsIGFzIHdlbGwgYXMgaW4gdGhlIGNsZXZlcgpjb3ZhcmlhdGVzICRIX3swfSgwLFcpXDtcdGV4dHthbmR9XDsgSF97MX0oMSxXKSQuCgpGb3IgdGhlICRcUHNpKFxiYXJ7UV97bn19XnsqfSkkLCB0aGUgdXBkYXRlZCBlc3RpbWF0ZSBvZiB0aGUgcG90ZW50aWFsCm91dGNvbWVzIG9ubHkgbmVlZHMgb25lIGl0ZXJhdGlvbiAkXFBzaShcYmFye1Ffe259fV57Kn0pJCBmcm9tCiRcYmFye1F9XnswfShBLFcpXCw9PlxiYXJ7UV57MX19KEEsVykkLiBUaGVyZWZvcmUsIG1vZGVsICg1KSB0YXJnZXRzCiRFW1xoYXR7WX1fe0E9MH1dXDtcdGV4dHthbmR9XDsgRVtcaGF0e1l9X3tBPTF9XSQgc2ltdWx0YW5lb3VzbHkgYnkKaW5jbHVkaW5nIGJvdGggJEhfezB9KEEsVylcLFx0ZXh0e2FuZH1cLEhfezF9KEEsVykkIGluIHRoZSBtb2RlbC4gSGVuY2UKJFxwc2kkIGlzIGZpbmFsbHkgZXN0aW1hdGVkIGFzIGZvbGxvd3M6CgokJFxwc2kgVE1MRSxuID0gXFBzaShRX3tufV57Kn0pPSB7XGZyYWN7MX17bn1cc3VtX3tpPTF9XntufVxiYXJ7UX1fe259XnsxfVxsZWZ0KDEsXCBXX3tpfVxyaWdodCktXGJhcntRfV97bn1eezF9XGxlZnQoMCxcIFdfe2l9XHJpZ2h0KX0uICAoMSkkJAoKYGBge3J9ClFzdGFyIDwtIHBsb2dpcyhRICsgZXBzaWxvbipoKQpwc2kgPC0gKFFzdGFyWywiUTFXIl0gLSBRc3RhclssIlEwVyJdKQpQc2kgPC0gbWVhbihRc3RhclssIlExVyJdIC0gUXN0YXJbLCJRMFciXSk7CmNhdCgiVE1MRV9Qc2k6IiwgUHNpKQpjYXQoIlxuIFRNTEUuU0lfYmlhczoiLCBhYnMoVHJ1ZV9Qc2ktUHNpKSkKY2F0KCJcbiBSZWxhdGl2ZV9UTUxFLlNJX2JpYXM6IixhYnMoVHJ1ZV9Qc2ktUHNpKS9UcnVlX1BzaSoxMDAsIiUiKQpgYGAKCioqVmlzdWFsaXppbmcqKiB0aGUgNHRoIHN0ZXAgKEggPSBJUFRXIG9yIGNsZXZlciBjb3Zhcml0ZXMpOgoKYGBge3IsIHdhcm5pbmc9RkFMU0V9CmRmIDwtIHJvdW5kKGNiaW5kKFEwPShRMCksSD0oaCksZXBzaWxvbixwc2kpLCBkaWdpdHM9IDMpCmRhdGF0YWJsZShoZWFkKGRmLCBuID0gbnJvdyhkZikpLCBvcHRpb25zID0gbGlzdChwYWdlTGVuZ3RoID0gNSwgZGlnaXRzID0gMykpCmBgYAoKYGBge3J9CmNhdCgiXG4gUHNpIGZpcnN0IHJvdzoiLCBwbG9naXMoKDAuMDAxKjEuMjM5KSArICgyLjM5NSkpIC0gKHBsb2dpcygoMC4wMDEqLTUuMTY4KSArICgxLjM0MykpKSkKYGBgCgojIyBTdGVwIDU6IEluZmVyZW5jZQoKUmVjYWxsIHRoYXQgdGhlIGFzeW1wdG90aWMgZGlzdHJpYnV0aW9uIG9mIFRNTEUgZXN0aW1hdG9ycyBoYXMgYmVlbgpzdHVkaWVkIHRob3JvdWdobHkgW0B2YW4yMDExXToKCiQkXHBzaV9uIC0gXHBzaV8wID0gKFBfbiAtIFBfMCkgXGNkb3QgRChcYmFye1F9X25eKiwgZ19uKSArIFIoXGhhdHtQfV4qLCBQXzApLCQkCgp3aGljaCwgcHJvdmlkZWQgdGhlIGZvbGxvd2luZyB0d28gY29uZGl0aW9uczoKCjEuICBJZiAkRChcYmFye1F9X25eKiwgZ19uKSQgY29udmVyZ2VzIHRvICREKFBfMCkkIGluICRMXzIoUF8wKSQgbm9ybSwKICAgIGFuZAoyLiAgdGhlIHNpemUgb2YgdGhlIGNsYXNzIG9mIGZ1bmN0aW9ucyBjb25zaWRlcmVkIGZvciBlc3RpbWF0aW9uIG9mCiAgICAkXGJhcntRfV9uXiokIGFuZCAkZ19uJCBpcyBib3VuZGVkICh0ZWNobmljYWxseSwKICAgICRcZXhpc3RzIFxtYXRoY2Fse0Z9JCBzdCAkRChcYmFye1F9X25eKiwgZ19uKSBcaW4gXG1hdGhjYWx7Rn0kCiAgICAqKip3aHAqKiosIHdoZXJlICRcbWF0aGNhbHtGfSQgaXMgYSBEb25za2VyIGNsYXNzKSwgcmVhZGlseSBhZG1pdHMKICAgIHRoZSBjb25jbHVzaW9uIHRoYXQKCiRccHNpX24gLSBccHNpXzAgPSAoUF9uIC0gUF8wKSBcY2RvdCBEKFBfMCkgKyBSKFxoYXR7UH1eKiwgUF8wKSQuCgpVbmRlciB0aGUgYWRkaXRpb25hbCBjb25kaXRpb24gdGhhdCB0aGUgcmVtYWluZGVyIHRlcm0KJFIoXGhhdHtQfV4qLCBQXzApJCBkZWNheXMgYXMgJG9fUCBcbGVmdCggXGZyYWN7MX17XHNxcnR7bn19IFxyaWdodCksJAp3ZSBoYXZlIHRoYXQKJCRccHNpX24gLSBccHNpXzAgPSAoUF9uIC0gUF8wKSBcY2RvdCBEKFBfMCkgKyBvX1AgXGxlZnQoIFxmcmFjezF9e1xzcXJ0e259fQogXHJpZ2h0KSwkJCB3aGljaCwgYnkgYSBjZW50cmFsIGxpbWl0IHRoZW9yZW0sIGVzdGFibGlzaGVzIGEgR2F1c3NpYW4KbGltaXRpbmcgZGlzdHJpYnV0aW9uIGZvciB0aGUgZXN0aW1hdG9yOgoKJCRcc3FydHtufShccHNpX24gLSBccHNpKSBcdG8gTigwLCBWKEQoUF8wKSkpLCQkCgp3aGVyZSAkVihEKFBfMCkpJCBpcyB0aGUgdmFyaWFuY2Ugb2YgdGhlIGVmZmljaWVudCBpbmZsdWVuY2UgY3VydmUKKGNhbm9uaWNhbCBncmFkaWVudCkgd2hlbiAkXHBzaSQgYWRtaXRzIGFuIGFzeW1wdG90aWNhbGx5IGxpbmVhcgpyZXByZXNlbnRhdGlvbi4KClRoZSBhYm92ZSBpbXBsaWVzIHRoYXQgJFxwc2lfbiQgaXMgYSAkXHNxcnR7bn0kLWNvbnNpc3RlbnQgZXN0aW1hdG9yIG9mCiRccHNpJCwgdGhhdCBpdCBpcyBhc3ltcHRvdGljYWxseSBub3JtYWwgKGFzIGdpdmVuIGFib3ZlKSwgYW5kIHRoYXQgaXQKaXMgbG9jYWxseSBlZmZpY2llbnQuIFRoaXMgYWxsb3dzIHVzIHRvIGJ1aWxkIFdhbGQtdHlwZSBjb25maWRlbmNlCmludGVydmFscyBpbiBhIHN0cmFpZ2h0Zm9yd2FyZCBtYW5uZXI6CgokJFxwc2lfbiBccG0gel97XGFscGhhfSBcY2RvdCBcZnJhY3tcc2lnbWFfbn17XHNxcnR7bn19LCQkCgp3aGVyZSAkXHNpZ21hX25eMiQgaXMgYW4gZXN0aW1hdG9yIG9mICRWKEQoUF8wKSkkLiBUaGUgZXN0aW1hdG9yCiRcc2lnbWFfbl4yJCBtYXkgYmUgb2J0YWluZWQgdXNpbmcgdGhlIGJvb3RzdHJhcCBvciBjb21wdXRlZCBkaXJlY3RseQp2aWEgdGhlIGZvbGxvd2luZwoKJCRcc2lnbWFfbl4yID0gXGZyYWN7MX17bn0gXHN1bV97aSA9IDF9XntufSBEXjIoXGJhcntRfV9uXiosIGdfbikoT19pKSQkCgpIYXZpbmcgbm93IHJlLWV4YW1pbmVkIHRoZXNlIGZhY3RzLCBsZXQncyBzaW1wbHkgYXBwbHkgaXQgdG8gdGhlCmVzdGltYXRpb24gb2YgdGhlIHN0YW5kYXJkIGVycm9ycyBmb3IgJFxwc2kkLiBUaHVzLCB0aGUgZWZmaWNpZW50CmluZmx1ZW5jZSBjdXJ2ZSAoRUlDKSBmb3IgdGhlIEFURS1UTUxFIGVzdGltYXRvciBpczoKCiQkSUNfe259KE9fe2l9KVwgXCA9XCBcbGVmdChcZnJhY3tJXGxlZnQoQV97aX09MVxyaWdodCl9e2dfblxsZWZ0KDFcbGVmdHxXX3tpfVxyaWdodClccmlnaHQpfVwgLVwgXGZyYWN7SVxsZWZ0KEFfe2l9PTBccmlnaHQpfXtnX25cbGVmdCgwXGxlZnR8V197aX1ccmlnaHQpXHJpZ2h0KX1cIFxyaWdodClcbGVmdFtZX3tpfS1cYmFye1F9X3tufV57MX1cbGVmdChBX3tpfSxXX3tpfVxyaWdodClccmlnaHRdK1xiYXJ7UX1fe259XnsxfVxsZWZ0KDEsXCBXX3tpfVxyaWdodCktXGJhcntRfV97bn1eezF9XGxlZnQoMCxcIFdfe2l9XHJpZ2h0KSAtIFxwc2kgVE1MRSxuLiQkCgpUaGVyZWZvcmUsIHRoZSBzdGFuZGFyZCBkZXZpYXRpb24gZm9yICRccHNpJCBpcyBlc3RpbWF0ZWQgYXMgZm9sbG93czoKCiQkXHNpZ21hKHtccHNpX3swfX0pPVxzcXJ0e1xmcmFje1ZhcihJQ197bn0pfXtufX0uJCQKCioqTm90ZSoqOiBQbGVhc2Ugc2VlIGhlcmUgYmVsb3cgdGhlIGxpbmsgdG8gYSBwcmFjdGljYWwgdHV0b3JpYWwKaW50cm9kdWNpbmcgdGhlIGNvbXB1dGF0aW9uYWwgZGVyaXZhdGlvbiBhbmQgdXNlIG9mIHRoZSAqKkRlbHRhIE1ldGhvZCoqCmluIEVwaWRlbWlvbG9neSB3aGljaCBsYXkgdGhlIGZvdW5kYXRpb25zIGZvciB0aGUgaW50ZXJwcmV0YXRpb24gYW5kCnVuZGVyc3RhbmRpbmcgb2YgdGhlIGZ1bmN0aW9uYWwgZGVsdGEgbWV0aG9kIGFuZCB0aGUgKipJbmZsdWVuY2UgQ3VydmUqKgpyb290ZWQgaW4gYm90aCwgUm9idXN0IFN0YXRpc3RpY3MgYW5kIEVtcGlyaWNhbCBQcm9jZXNzIFRoZW9yeS4KCkRlbHRhIE1ldGhvZCBpbiBFcGlkZW1pb2xvZ3k6CjxodHRwczovL21pZ2FyaWFuZS5naXRodWIuaW8vRGVsdGFNZXRob2RFcGkubmIuaHRtbD4KCmBgYHtyfQpRICA8LSBhcy5kYXRhLmZyYW1lKFEpClFzdGFyIDwtIGFzLmRhdGEuZnJhbWUoUXN0YXIpCklDIDwtIGhbLDFdKihZLXBsb2dpcyhRJFFBVykpICsgcGxvZ2lzKFFzdGFyJFExVyAtIFFzdGFyJFEwVykgLSBQc2k7c3VtbWFyeShJQykKbiA8LSBucm93KE9ic0RhdGEpCnZhckhhdC5JQyA8LSB2YXIoSUMpL247IHZhckhhdC5JQwoKI1BzaSBhbmQgOTUlQ0kgZm9yIFBzaQpjYXQoIlxuIFRNTEVfUHNpOiIsIFBzaSkKY2F0KCJcbiA5NSVDSToiLCBjKFBzaS0xLjk2KnNxcnQodmFySGF0LklDKSwgIFBzaSsxLjk2KnNxcnQodmFySGF0LklDKSkpCgpjYXQoIlxuIFRNTEUuU0lfYmlhczoiLCBhYnMoVHJ1ZV9Qc2ktUHNpKSkKY2F0KCJcbiBSZWxhdGl2ZV9UTUxFLlNJX2JpYXM6IixhYnMoVHJ1ZV9Qc2ktUHNpKS9UcnVlX1BzaSoxMDAsIiUiKQpgYGAKCiMgVE1MRSB2cy4gQUlQVFcKCjEuICBUaGUgYWR2YW50YWdlcyBvZiAqKlRNTEUqKiBoYXZlIHJlcGVhdGVkbHkgYmVlbiBkZW1vbnN0cmF0ZWQgaW4gYm90aAogICAgc2ltdWxhdGlvbiBzdHVkaWVzIGFuZCBhcHBsaWVkIGFuYWx5c2VzIFtAdmFuMjAxMV0uCgoyLiAgRXZpZGVuY2Ugc2hvd3MgdGhhdCAqKlRNTEUqKiBwcm92aWRlcyB0aGUgbGVzcyB1bmJpYXNlZCBBVEUgZXN0aW1hdGUKICAgIGNvbXBhcmVkIHdpdGggb3RoZXIgZG91YmxlLXJvYnVzdCBlc3RpbWF0b3JzIFtAbmV1MjAwNV0sIFtAdmFuMjAxMV0KICAgIHN1Y2ggYXMgdGhlIGNvbWJpbmF0aW9uIG9mIHJlZ3Jlc3Npb24gYWRqdXN0bWVudCB3aXRoIGludmVyc2UKICAgIHByb2JhYmlsaXR5IG9mIHRyZWF0bWVudCB3ZWlnaHRpbmcgKElQVFctUkEpIGFuZCB0aGUgYXVnbWVudGVkCiAgICBpbnZlcnNlIHByb2JhYmlsaXR5IG9mIHRyZWF0bWVudCB3ZWlnaHRpbmcgKEFJUFRXKS4gVGhlICoqQUlQVFcqKgogICAgZXN0aW1hdGlvbiBpcyBhIHR3by1zdGVwIHByb2NlZHVyZSB3aXRoIHR3byBlcXVhdGlvbnMgKHByb3BlbnNpdHkKICAgIHNjb3JlIGFuZCBtZWFuIG91dGNvbWUgZXF1YXRpb25zKS4KCjMuICBUbyBlc3RpbWF0ZSB0aGUgQVRFIHVzaW5nIHRoZSAqKkFJUFRXKiogZXN0aW1hdG9yIG9uZSBjYW4gc2V0IHRoZQogICAgZXN0aW1hdGlvbiBlcXVhdGlvbiAoRUUpICg0KSBlcXVhbCB0byB6ZXJvIGFuZCB1c2UgYm9vdHN0cmFwIHRvCiAgICBkZXJpdmUgOTUlIGNvbmZpZGVuY2UgaW50ZXJ2YWxzIChDSSkuIEhvd2V2ZXIsIHNvbHZpbmcgdGhlIEVFIHVzaW5nCiAgICB0aGUgZ2VuZXJhbGl6ZWQgbWV0aG9kIG9mIG1vbWVudHMgKEdNTSksIHN0YWNraW5nIGJvdGggZXF1YXRpb25zCiAgICAocHJvcGVuc2l0eSBzY29yZSBhbmQgb3V0Y29tZSksIHJlZHVjZXMgdGhlIGVzdGltYXRpb24gYW5kIGluZmVyZW5jZQogICAgc3RlcHMgdG8gb25seSBvbmUuIEhvd2V2ZXIsIGdpdmVuIHRoYXQgdGhlIHByb3BlbnNpdHkgc2NvcmUgaW4KICAgIGVxdWF0aW9uICg0KSBjYW4gZWFzaWx5IGZhbGwgb3V0c2lkZSB0aGUgcmFuZ2UgWzAsIDFdIChpZiBmb3Igc29tZQogICAgb2JzZXJ2YXRpb25zICRnX3tufSgxfFdfe2l9KSQgaXMgY2xvc2UgdG8gMSBvciAwKSB0aGUgKipBSVBUVyoqCiAgICBlc3RpbWF0aW9uIGNhbiBiZSB1bnN0YWJsZSAobmVhciB2aW9sYXRpb24gb2YgdGhlIHBvc2l0aXZpdHkKICAgIGFzc3VtcHRpb24pLiAqKkFJUFRXKiogaW5zdGFiaWxpdHkgdW5kZXIgbmVhciB2aW9sYXRpb24gb2YgdGhlCiAgICBwb3NpdGl2aXR5IGFzc3VtcHRpb24gcmVwcmVzZW50cyB0aGUgcHJpY2Ugb2Ygbm90IGJlaW5nIGEKICAgIHN1YnN0aXR1dGlvbiBlc3RpbWF0b3IgYXMgKipUTUxFKiouCgokJFxwc2lfezB9XntBSVBUVy1BVEV9XCBcID1cIFxmcmFjezF9e259XHN1bV97aT0xfV57bn1cbGVmdChcZnJhY3tJXGxlZnQoQV97aX09MVxyaWdodCl9e2dfblxsZWZ0KDFcbGVmdHxXX3tpfVxyaWdodClccmlnaHQpfVwgLVwgXGZyYWN7SVxsZWZ0KEFfe2l9PTBccmlnaHQpfXtnX25cbGVmdCgwXGxlZnR8V197aX1ccmlnaHQpXHJpZ2h0KX1cIFxyaWdodClcbGVmdFtZX3tpfS1cYmFye1F9X3tufV57MH1cbGVmdChBX3tpfSxXX3tpfVxyaWdodClccmlnaHRdK1xmcmFjezF9e259XHN1bV97aT0xfV57bn1cYmFye1F9X3tufV57MH1cbGVmdCgxLFwgV197aX1ccmlnaHQpLVxiYXJ7UX1fe259XnswfVxsZWZ0KDAsXCBXX3tpfVxyaWdodCkuICg0KSQkCgpgYGB7cn0KQUlQVFcgPC0gbWVhbigoaFssMV0qKFkgLSBwbG9naXMoUSRRQVcpKSArIChwbG9naXMoUSRRMVcpIC0gcGxvZ2lzKFEkUTBXKSkpKTtBSVBUVwpjYXQoIlxuIEFJUFRXX2JpYXM6IiwgYWJzKFRydWVfUHNpIC0gQUlQVFcpKQpjYXQoIlxuIFJlbGF0aXZlX0FJUFRXX2JpYXM6IixhYnMoVHJ1ZV9Qc2kgLSBBSVBUVykgLyBUcnVlX1BzaSoxMDAsIiUiKQpgYGAKClRoZSBzaW1wbGUgVE1MRSBhbGdvcml0aG0gc2hvd3Mgc2ltaWxhciByZWxhdGl2ZSBiaWFzIHRoYW4gQUlQVFcuCkhvd2V2ZXIsIGhlcmUgYmVsb3csIHdlIGNhbiBzZWUgdGhhdCBUTUxFIHBlcmZvcm1hbmNlLCBjb21wYXJlZCB3aXRoCkFJUFRXLCBpbXByb3ZlcyB3aGVuIGNhbGxpbmcgdGhlIFN1cGVyLUxlYXJuZXIgYW5kIGVuc2VtYmxlIGxlYXJuaW5nCnRlY2huaXF1ZXMgaW50ZWdyYXRlZCBpbnRvIHRoZSBUTUxFIGFsZ29yaXRobS4KCiMgVE1MRSB1c2luZyB0aGUgU3VwZXItTGVhcm5lcgoKV2l0aCBUTUxFIHdlIGNhbiBjYWxsIHRoZSBTdXBlci1MZWFybmVyIChTTCkuIFRoZSBTTCBpcyBhIFItcGFja2FnZQp1c2luZyBWLWZvbGQgY3Jvc3MtdmFsaWRhdGlvbiBhbmQgZW5zZW1ibGVkIGxlYXJuaW5nIChwcmVkaWN0aW9uIHVzaW5nCmFsbCB0aGUgcHJlZGljdGlvbnMgb2YgbXVsdGlwbGUgc3RhY2tlZCBsZWFybmluZyBhbGdvcml0aG1zKSB0ZWNobmlxdWVzCnRvIGltcHJvdmUgbW9kZWwgcHJlZGljdGlvbiBwZXJmb3JtYW5jZSBbQGJyZWltYW4xOTk2XS4KClRoZSBiYXNpYyBpbXBsZW1lbnRhdGlvbiBvZiBUTUxFIGluIHRoZSBSLXBhY2thZ2UgKip0bWxlKiogdXNlcyBieQpkZWZhdWx0IHRocmVlIGFsZ29yaXRobXM6XAoxLiBTTC5nbG0gKG1haW4gdGVybXMgbG9naXN0aWMgcmVncmVzc2lvbiBvZiBBIGFuZCBXKSxcCjIuIFNMLnN0ZXAgKHN0ZXB3aXNlIGZvcndhcmQgYW5kIGJhY2t3YXJkIG1vZGVsIHNlbGVjdGlvbiB1c2luZyBBSUMKY3JpdGVyaW9uLCByZXN0cmljdGVkIHRvIHNlY29uZCBvcmRlciBwb2x5bm9taWFscykgYW5kLFwKMy4gU0wuZ2xtLmludGVyYWN0aW9uIChhIGdsbSB2YXJpYW50IHRoYXQgaW5jbHVkZXMgc2Vjb25kIG9yZGVyCnBvbHlub21pYWxzIGFuZCB0d28gYnkgdHdvIGludGVyYWN0aW9ucyBvZiB0aGUgbWFpbiB0ZXJtcyBpbmNsdWRlZCBpbgp0aGUgbW9kZWwpLgoKVGhlIHByaW5jaXBhbCBpbnRlcmVzdCBvZiBjYWxsaW5nIHRoZSBTdXBlci1MZWFybmVyIGlzIHRvIG9idGFpbiB0aGUKbGVzcy11bmJpYXNlZCBlc3RpbWF0ZWQgZm9yICRcYmFyIFFfe259XnswfShBLFcpJCBhbmQgJGdfezB9KEEsVykkLiBJdAppcyBhY2hpZXZlZCBieSBvYnRhaW5pbmcgdGhlIHNtYWxsZXN0IGV4cGVjdGVkIGxvc3MgZnVuY3Rpb24gZm9yIFkgb3IgQQooYmluYXJ5IG91dGNvbWVzKSwgcmVzcGVjdGl2ZWx5LiBGb3IgaW5zdGFuY2UsIHRoZSBuZWdhdGl2ZSBsb2dhcml0aG1pYwpsb3NzIGZ1bmN0aW9uIGZvciBZIGlzIGNvbXB1dGVkIGFzIHRoZSBtaW5pbWl6ZXIgb2YgdGhlIGV4cGVjdGVkIHNxdWFyZWQKZXJyb3IgbG9zczpcCiQkXGJhciBRX3swfVwsPVwsIFx0ZXh0e2FyZyBtaW59X3tcYmFyIFF9RV97MH1MKE8sIFxiYXIgUSksJCRcCndoZXJlICRMKE8sIFxiYXIgUSkkIGlzOiAkJCAoWSBcLC1cLCBcYmFyIFEoQSwgVykpXnsyfSQkCgoqKk5vdGUqKjogc2VlIHRoZSBhcHBlbmRpeCBmb3IgYSBzaG9ydCBpbnRyb2R1Y3Rpb24gdG8gdGhlIFN1cGVyLUxlYXJuZXIKYW5kIGVuc2VtYmxlIGxlYXJuaW5nIHRlY2huaXF1ZXMuCgoxLiAgKipTdGVwIE9uZSoqOiAkXGJhciBRX3tufV57MH0oQSxXKSQgcHJlZGljdGlvbgoKYGBge3J9CiNFKFl8QSxXKSBwcmVkaWN0aW9uCmxpYnJhcnkoU3VwZXJMZWFybmVyKQojU3BlY2lmeSBTdXBlckxlYXJuZXIgbGlicmFyaWVzClNMLmxpYnJhcnkgPC0gYygiU0wuZ2xtIiwiU0wuc3RlcCIsIlNMLmdsbS5pbnRlcmFjdGlvbiIpCiNEYXRhIGZyYW1lIHdpdGggWCB3aXRoIGJhc2VsaW5lIGNvdmFyaWF0ZXMgYW5kIGV4cG9zdXJlIEEKWCA8LSBzdWJzZXQoT2JzRGF0YSwgc2VsZWN0ID0gYyhBLCB3MSwgdzIsIHczLCB3NCkpCm4gPC0gbnJvdyhPYnNEYXRhKQojQ3JlYXRlIGRhdGEgZnJhbWVzIHdpdGggQT0xIGFuZCBBPTAKWDE8LVgwPC1YClgxJEEgPC0xClgwJEEgPC0wCiNDcmVhdGUgbmV3IGRhdGEgYnkgc3RhY2tpbmcgWCwgWDEsIGFuZCBYMApuZXdkYXRhIDwtIHJiaW5kKFgsWDEsWDApCiNDYWxsIHN1cGVybGVhcm5lcgpRaW5pdCA8LSBTdXBlckxlYXJuZXIoWSA9IE9ic0RhdGEkWSwgWCA9IFgsIG5ld1ggPSBuZXdkYXRhLCBTTC5saWJyYXJ5PVNMLmxpYnJhcnksIGZhbWlseT0iYmlub21pYWwiKQpRaW5pdAojUHJlZGljdGlvbnMKI1ByZWQgcHJvYiBvZiBtb3J0YWxpdHkgKFkpIGdpdmVuIEEsIFcKUWJhckFXIDwtIFFpbml0JFNMLnByZWRpY3RbMTpuXQojUHJlZCBwcm9iIG9mIGR5aW5nIGZvciBlYWNoIHN1YmplY3QgZ2l2ZW4gQT0xIGFuZCB3ClFiYXIxVyA8LSBRaW5pdCRTTC5wcmVkaWN0WyhuKzEpOigyKm4pXQojUHJlZCBwcm9iIG9mIGR5aW5nIGZvciBlYWNoIHN1YmplY3QgZ2l2ZW4gQT0wIGFuZCB3ClFiYXIwVyA8LSBRaW5pdCRTTC5wcmVkaWN0WygyKm4rMSk6KDMqbildCiNTaW1wbGUgc3Vic3RpdHV0aW9uIGVzdGltYXRvciBQc2koUTApClBzaUhhdC5TUyA8LSBtZWFuKFFiYXIxVyAtIFFiYXIwVyk7UHNpSGF0LlNTCmBgYAoKMi4gICoqU3RlcCB0d28qKjogJGdfezB9KEEsVykkIHByZWRpY3Rpb24KCmBgYHtyLCB3YXJuaW5nPUZBTFNFfQojU3RlcCAyIGdfMChBfFcpIHdpdGggU3VwZXJMZWFybmVyCncgPC0gc3Vic2V0KE9ic0RhdGEsIHNlbGVjdD1jKHcxLHcyLHczLHc0KSkKZ0hhdFNMIDwtIFN1cGVyTGVhcm5lcihZPU9ic0RhdGEkQSwgWCA9IHcsIFNMLmxpYnJhcnkgPSBTTC5saWJyYXJ5LCBmYW1pbHkgPSBiaW5vbWlhbCkKZ0hhdFNMCgojR2VuZXJhdGUgdGhlIHByZWQgcHJvYiBvZiBBPTEgYW5kLCBBPTAgZ2l2ZW4gY292YXJpYXRlcwpnSGF0MVcgPC0gZ0hhdFNMJFNMLnByZWRpY3QKZ0hhdDBXIDwtIDEgLSBnSGF0MVcKCiNTdGVwIDM6IENsZXZlciBjb3ZhcmlhdGUKSEFXIDwtIGFzLm51bWVyaWMoT2JzRGF0YSRBPT0xKS9nSGF0MVcgLSBhcy5udW1lcmljKE9ic0RhdGEkQT09MCkvZ0hhdDBXO21lYW4oSEFXKQpIMVcgPC0gIDEvZ0hhdDFXCkgwVyA8LSAtMS9nSGF0MFcKYGBgCgozLiAgKipTdGVwcyAzIGFuZCA0Kio6IGZsdWN0dWF0aW9uIHN0ZXAgYW5kIHN1YnN0aXR1dGlvbiBlc3RpbWF0aW9uIGZvcgogICAgJFxiYXIgUV97bn1eezB9KEEsVykkIHRvICRcYmFyIFFfe259XnsxfShBLFcpJAoKYGBge3J9CiNTdGVwIDQ6IFN1YnN0aXR1dGlvbiBlc3RpbWFpdG9uIFEqIG9mIHRoZSBBVEUuCmxvZ2l0VXBkYXRlIDwtIGdsbShPYnNEYXRhJFkgfiAtMSArIG9mZnNldChxbG9naXMoUWJhckFXKSkgKyBIQVcsIGZhbWlseT0nYmlub21pYWwnKQplcHMgPC0gbG9naXRVcGRhdGUkY29lZjtlcHMKI0NhbGN1bGF0aW5nIHRoZSBwcmVkaWN0ZWQgdmFsdWVzIGZvciBlYWNoIHN1YmplY3QgdW5kZXIgZWFjaCB0cmVhdG1lbnQgQT0xLCBBPTAKUWJhckFXLnN0YXIgPC0gcGxvZ2lzKHFsb2dpcyhRYmFyQVcpK2VwcypIQVcpClFiYXIxVy5zdGFyIDwtIHBsb2dpcyhxbG9naXMoUWJhcjFXKStlcHMqSDFXKQpRYmFyMFcuc3RhciA8LSBwbG9naXMocWxvZ2lzKFFiYXIwVykrZXBzKkgwVykKUHNpSGF0LlRNTEUuU0wgPC0gbWVhbihRYmFyMVcuc3RhcikgLSBtZWFuKFFiYXIwVy5zdGFyKQpjYXQoIlBzaUhhdC5UTUxFLlNMOiIsIFBzaUhhdC5UTUxFLlNMKQpjYXQoIlxuIFBzaUhhdC5UTUxFLlNMX2JpYXM6IiwgYWJzKFRydWVfUHNpIC0gUHNpSGF0LlRNTEUuU0wpKQpjYXQoIlxuIFJlbGF0aXZlX1BzaUhhdC5UTUxFLlNMX2JpYXM6IixhYnMoVHJ1ZV9Qc2kgLSBQc2lIYXQuVE1MRS5TTCkvVHJ1ZV9Qc2kqMTAwLCIlIikKYGBgCgpUTUxFIHdpdGggbWFjaGluZSBsZWFybmluZyBhbGdvcml0aG1zIGRlY3JlYXNlcyBiaWFzIGNvbXBhcmVkIHdpdGggdGhlCnByZXZpb3VzIEFJUFRXIGFuZCBUTUxFICh3aXRob3V0IFN1cGVyIExlYXJuZXIpIGVzdGltYXRvcnMuCgojIFItVE1MRQoKVXNpbmcgdGhlIFItcGFja2FnZSAqKnRtbGUqKi4KClRoZSBiYXNpYyBpbXBsZW1lbnRhdGlvbiBvZiBUTUxFIGluIHRoZSBSLXBhY2thZ2UgKip0bWxlKiogdXNlcyBieQpkZWZhdWx0IHRocmVlIGFsZ29yaXRobXM6XAoxLiBTTC5nbG0gKG1haW4gdGVybXMgbG9naXN0aWMgcmVncmVzc2lvbiBvZiBBIGFuZCBXKSxcCjIuIFNMLnN0ZXAgKHN0ZXB3aXNlIGZvcndhcmQgYW5kIGJhY2t3YXJkIG1vZGVsIHNlbGVjdGlvbiB1c2luZyBBSUMKY3JpdGVyaW9uLCByZXN0cmljdGVkIHRvIHNlY29uZCBvcmRlciBwb2x5bm9taWFscykgYW5kLFwKMy4gU0wuZ2xtLmludGVyYWN0aW9uIChhIGdsbSB2YXJpYW50IHRoYXQgaW5jbHVkZXMgc2Vjb25kIG9yZGVyCnBvbHlub21pYWxzIGFuZCB0d28gYnkgdHdvIGludGVyYWN0aW9ucyBvZiB0aGUgbWFpbiB0ZXJtcyBpbmNsdWRlZCBpbgp0aGUgbW9kZWwpLgoKYGBge3J9CmxpYnJhcnkodG1sZSkKc2V0LnNlZWQoNzc3NykKI2F0dGFjaChPYnNEYXRhKQp3IDwtIHN1YnNldChPYnNEYXRhLCBzZWxlY3Q9Yyh3MSx3Mix3Myx3NCkpCnRtbGUgPC0gdG1sZShZLCBBLCB3LCBmYW1pbHk9ImJpbm9taWFsIikKY2F0KCJUTUxFUl9Qc2k6IiwgdG1sZSRlc3RpbWF0ZXNbWzJdXVtbMV1dLCI7IiwiOTUlQ0koIiwgdG1sZSRlc3RpbWF0ZXNbWzJdXVtbM11dLCIpIikKY2F0KCJcbiBUTUxFX2JpYXM6IiwgYWJzKFRydWVfUHNpLXRtbGUkZXN0aW1hdGVzW1syXV1bWzFdXSkpCmNhdCgiXG4gUmVsYXRpdmVfVE1MRV9iaWFzOiIsYWJzKFRydWVfUHNpLXRtbGUkZXN0aW1hdGVzW1syXV1bWzFdXSkvVHJ1ZV9Qc2kqMTAwLCIlIikKYGBgCgpUTUxFIGltcGxlbWVudGF0aW9uIGluIHRoZSBSLXBhY2thZ2UgKip0bWxlKiogaW1wcm92ZXMgdGhlIGVzdGltYXRpb24gb2YKdGhlIGludmVyc2UtcHJvcGFiaWxpdHkgb2YgdHJlYXRtZW50IHdlaWdodHMuIEl0IGJvdW5kcyBieSBkZWZhdWx0IHRoZQpkaXN0cmlidXRpb24gb2YgdGhlIHdlaWdodHMgZm9yIHRoZSBwcm9wZW5zaXR5IHNjb3JlIHRvICgwLjAyNXRoIGFuZAowLjk3NXRoIHBlcmNlbnRpbGVzKSB0byBkZWNyZWFzZSB0aGUgaW1wYWN0IG9mIG5lYXItcG9zaXRpdml0eQp2aW9sYXRpb25zLiBBbHNvLCBub3RlIHRoYXQgYXQgZWFjaCB0aW1lIHRoYXQgd2UgcnVuIHRoZSBzdXBlci1sZWFybmVyCnRoZSBWLWZvbGQgY3Jvc3MtdmFsaWRhdGlvbiBzcGxpdHMgdGhlIGRhdGEgcmFuZG9tbHkuIFNvLCB3ZSBoYXZlIHRvIHNldAphIHNlZWQgZm9yIHJlcGxpY2FiaWxpdHkuIEl0IGlzIHdoeSB0aGUgYmlhcyB1c2luZyB0aGUgKip0bWxlKioKUi1wYWNrYWdlIGRlY3JlYXNlcyBmcm9tIDAuMDA3IHRvIDAuMDA1LgoKIyBSLVRNTEUgcmVkdWNpbmcgYmlhcyBieSBjYWxsaW5nIG1vcmUgYWR2YW5jZWQgbWFjaGluZS1sZWFybmluZyBsaWJyYXJpZXMKCkluIGFkZGl0aW9uIHRvIHRoZSBkZWZhdWx0IGFsZ29yaXRobXMgaW1wbGVtZW50ZWQgaW4gdGhlIFItdG1sZSBwYWNrYWdlLAp3ZSBjYW4gZXZlbiBkZWNyZWFzZSBtb3JlIHRoZSBiaWFzIG9mIG91ciBlc3RpbWF0aW9uIGJ5IGNhbGxpbmcgbW9yZQplZmZpY2llbnQgbWFjaGluZSBsZWFyaW5nIGFsZ29yaXRobXMsIHN1Y2ggYXMgZ2VuZXJhbGl6ZWQgYWRkaXRpdmUKbW9kZWxzLCBSYW5kb20gRm9yZXN0LCBSZWN1cnNpdmUgUGFydGl0aW9uaW5nIGFuZCBSZWdyZXNzaW9uIFRyZWVzIChpdAppcyBoaWdobHkgcmVjb21lbmRlZCB0byBpbmNsdWRlIHRoZSB0aGUgaGlnaGx5IGFkYXB0aXZlIExhc3NvIFtIQUw5MDAxXQppbiB5b3VyIFNMIGxpYnJhcnkgYnV0IGZvciBjb21wdXRpbmcgZWZmaWNpZW5jeSB3ZSBkaWQgbm90IGluY2x1ZGUgaXQKaGVyZSk6CgpgYGB7ciwgd2FybmluZz1GQUxTRX0KbGlicmFyeShoYWw5MDAxKQpoYWw5MDAxOjpTTC5oYWw5MDAxClNMLlRNTEVSLlBzaSA8LSB0bWxlKFk9WSwgQT1BLCBXPXcsIGZhbWlseT0iYmlub21pYWwiLCAKICAgIFEuU0wubGlicmFyeSA9IGMoIlNMLmdsbSIsICJTTC5zdGVwIiwgIlNMLmdsbS5pbnRlcmFjdGlvbiIsICJTTC5nYW0iLCAiU0wucmFuZ2VyIiksCiAgICBnLlNMLmxpYnJhcnkgPSBjKCJTTC5nbG0iLCAiU0wuc3RlcCIsICJTTC5nbG0uaW50ZXJhY3Rpb24iLCAiU0wuZ2FtIiwgIlNMLnJhbmdlciIpKQoKY2F0KCJTTC5UTUxFUi5Qc2k6IiwgU0wuVE1MRVIuUHNpJGVzdGltYXRlc1tbMl1dW1sxXV0sIjsiLCI5NSVDSSgiLCBTTC5UTUxFUi5Qc2kkZXN0aW1hdGVzW1syXV1bWzNdXSwiKSIpCmNhdCgiXG4gU0wuVE1MRVIuUHNpX2JpYXM6IiwgYWJzKFRydWVfUHNpLVNMLlRNTEVSLlBzaSRlc3RpbWF0ZXNbWzJdXVtbMV1dKSkKY2F0KCJcbiBSZWxhdGl2ZV9TTC5UTUxFUi5Qc2lfYmlhczoiLGFicyhUcnVlX1BzaS1TTC5UTUxFUi5Qc2kkZXN0aW1hdGVzW1syXV1bWzFdXSkvVHJ1ZV9Qc2kqMTAwLCIlIikKYGBgCgojIENvbmNsdXNpb25zCgpXZSBoYXZlIGRlbW9uc3RyYXRlZDoKCjEuICAqKlRNTEUgZXhjZWxzKiogdGhlIEFJUFRXIGVzdGltYXRvciBhbmQsXAoyLiAgVE1MRSAqKmJlc3QgcGVyZm9ybWFuY2UqKiBpcyBvYnRhaW5lZCB3aGVuIGNhbGxpbmcgbW9yZSBhZHZhbmNlZAogICAgKipTdXBlci1MZWFybmVyKiogYWxnb3JpdGhtcy4KCiMgQXBwZW5kaXgKCldpdGggVE1MRSB3ZSBjYW4gY2FsbCB0aGUgUi1wYWNrYWdlICoqU3VwZXItTGVhcm5lciAoU0wpKiouIFRoZSAqU0wqCnVzZXMgKipjcm9zcy12YWxpZGF0aW9uKiogYW5kICoqZW5zZW1ibGVkIGxlYXJuaW5nKiogKHVzaW5nIGFsbCB0aGUKcHJlZGljdGlvbnMgb2YgbXVsdGlwbGUgc3RhY2tlZCBsZWFybmluZyBhbGdvcml0aG1zKSB0ZWNobmlxdWVzIHRvCmltcHJvdmUgbW9kZWwgcHJlZGljdGlvbiBwZXJmb3JtYW5jZSBbQGJyZWltYW4xOTk2XS4KClRoZSAqKlNMKiogYWxnb3JpdGhtIHByb3ZpZGVzIGEgc3lzdGVtIGJhc2VkIG9uIFYtZm9sZCBjcm9zcy12YWxpZGF0aW9uCltAZWZyb24xOTgzXSAoMTAtZm9sZHMpIHRvIGNvbWJpbmUgYWRhcHRpdmVseSBtdWx0aXBsZSBhbGdvcml0aG1zIGludG8KYW4gaW1wcm92ZWQgZXN0aW1hdG9yLCBhbmQgcmV0dXJucyBhIGZ1bmN0aW9uIHRoYW4gY2FuIGJlIHVzZWQgZm9yCnByZWRpY3Rpb24gaW4gbmV3IGRhdGFzZXRzLgoKIVtdKEZpZ3VyZXMvY3YucG5nKSAqKkZpZ3VyZSA0Kio6IDEwLWZvbGQgY3Jvc3MtdmFsaWRhdGlvbiBhbGdvcml0aG0uXApUaGUgYmFzaWMgaW1wbGVtZW50YXRpb24gb2YgVE1MRSBpbiB0aGUgUi1wYWNrYWdlICoqdG1sZSoqIHVzZXMgYnkKZGVmYXVsdCB0aHJlZSBhbGdvcml0aG1zOlwKMS4gU0wuZ2xtIChtYWluIHRlcm1zIGxvZ2lzdGljIHJlZ3Jlc3Npb24gb2YgQSBhbmQgVyksXAoyLiBTTC5zdGVwIChzdGVwd2lzZSBmb3J3YXJkIGFuZCBiYWNrd2FyZCBtb2RlbCBzZWxlY3Rpb24gdXNpbmcgQUlDCmNyaXRlcmlvbiwgcmVzdHJpY3RlZCB0byBzZWNvbmQgb3JkZXIgcG9seW5vbWlhbHMpIGFuZCxcCjMuIFNMLmdsbS5pbnRlcmFjdGlvbiAoYSBnbG0gdmFyaWFudCB0aGF0IGluY2x1ZGVzIHNlY29uZCBvcmRlcgpwb2x5bm9taWFscyBhbmQgdHdvIGJ5IHR3byBpbnRlcmFjdGlvbnMgb2YgdGhlIG1haW4gdGVybXMgaW5jbHVkZWQgaW4KdGhlIG1vZGVsKS4KClRoZSBwcmluY2lwYWwgaW50ZXJlc3Qgb2YgY2FsbGluZyB0aGUgU3VwZXItTGVhcm5lciBpcyB0byBvYnRhaW4gdGhlCmxlc3MtdW5iaWFzZWQgZXN0aW1hdGVkIGZvciAkXGJhciBRX3tufV57MH0oQSxXKSQgYW5kICRnX3swfShBLFcpJC4gSXQKaXMgYWNoaWV2ZWQgYnkgb2J0YWluaW5nIHRoZSBzbWFsbGVzdCBleHBlY3RlZCBsb3NzIGZ1bmN0aW9uIGZvciBZIG9yIEEKKGJpbmFyeSBvdXRjb21lcyksIHJlc3BlY3RpdmVseS4gRm9yIGluc3RhbmNlLCB0aGUgbmVnYXRpdmUgbG9nYXJpdGhtaWMKbG9zcyBmdW5jdGlvbiBmb3IgWSBpcyBjb21wdXRlZCBhcyB0aGUgbWluaW1pemVyIG9mIHRoZSBleHBlY3RlZCBzcXVhcmVkCmVycm9yIGxvc3M6XAokJFxiYXIgUV97MH1cLD1cLCBcdGV4dHthcmcgbWlufV97XGJhciBRfUVfezB9TChPLCBcYmFyIFEpLCQkXAp3aGVyZSAkTChPLCBcYmFyIFEpJCBpczogJCQgKFkgXCwtXCwgXGJhciBRKEEsIFcpKV57Mn0kJCBUaGUgKipTTCoqCmFsZ29yaXRobSBmaXJzdCBzcGxpdCB0aGUgZGF0YSBpbnRvIHRlbiBibG9ja3MgYW5kIGZpdHMgZWFjaCBvZiB0aGUKc2VsZWN0ZWQgYWxnb3JpdGhzIG9uIHRoZSB0cmFpbmluZyBzZXQgKG5vbi1zaGFkZWQgYmxvY2tzKSwgdGhlbgpwcmVkaWN0cyB0aGUgZXN0aW1hdGVkIHByb2JhYmlsaXRpZXMgb2YgdGhlIG91dGNvbWUgKFkpIHVzaW5nIHRoZQp2YWxpZGF0aW9uIHNldCAoc2hhZGVkIGJsb2NrKSBmb3IgZWFjaCBhbGdvcml0aG0sIGJhc2VkIG9uIHRoZQpjb3JyZXNwb25kaW5nIHRyYWluaW5nIHNldC4gQWZ0ZXJ3YXJkcywgdGhlICoqU0wqKiBlc3RpbWF0ZXMgdGhlIHRoZQpjcm9zcy12YWxpZGF0aW5nIHJpc2sgZm9yIGVhY2ggYWxnb3JpdGhtIGF2ZXJhZ2luZyB0aGUgcmlza3MgYWNyb3NzCnZhbGlkYXRpb24gc2V0cyByZXN1bHRpbmcgaW4gb25lIGVzdGltYXRlZCBjcm9zcy12YWxpZGF0ZWQgcmlzayBmb3IgZWFjaAphbGdvcml0aG0uIEZpbmFsbHksIHRoZSAqKlNMKiogc2VsZWN0cyB0aGUgY29tYmluYXRpb24gb2YgWiB0aGF0Cm1pbmltaXNlcyB0aGUgY3Jvc3MtdmFsaWRhdGlvbiByaXNrLCBkZWZpbmVkIGFzIHRoZSBtaW5pbXVtIG1lYW4gc3F1YXJlCmVycm9yIGZvciBlYWNoIG9mIHRoZSBzZWxlY3RlZCBhbGdvcml0aG1zIHVzaW5nIFkgYW5kIFouIEEgd2VpZ2h0ZWQKY29tYmluYXRpb24gb2YgdGhlIGFsZ29yaXRobXMgKGVuc2VtYmxlIGxlYXJuaW5nKSBpbiBaIGlzIHRoZW4gdXNlZCB0bwpwcmVkaWN0IHRoZSBvdXRjb21lIChZKSAoc2VlIEZpZ3VyZSA1KS4KCiFbXShGaWd1cmVzL1NMLnBuZykgKipGaWd1cmUgNSoqOiBGbG93IERpYWdyYW0gZm9yIHRoZSBTdXBlci1MZWFybmVyCmFsZ29yaXRobS4KCiMgQWJicmV2aWF0aW9ucwoKVE1MRTogVGFyZ2V0ZWQgbWF4aW11bSBsaWtlbGlob29kIGVzdGltYXRpb25cClNMOiBTdXBlciBMZWFybmVyXApJUFRXOiBJbnZlcnNlIHByb2JhYmlsaXR5IG9mIHRyZWF0bWVudCB3ZWlnaHRpbmdcCkFJUFRXOiBBdWdtZW50ZWQgaW52ZXJzZSBwcm9iYWJpbGl0eSBvZiB0cmVhdG1lbnQgd2VpZ2h0aW5nXApNU0U6IE1lYW4gc3F1YXJlZCBlcnJvclwKU0U6IFN0YW5kYXJkIGVycm9yXApFRTogRXN0aW1hdGlvbiBlcXVhdGlvbnNcCkdNTTogR2VuZXJhbGlzZWQgbWV0aG9kIG9mIG1vbWVudHNcCk86IE9ic2VydmVkIG9yZGVyZWQgZGF0YSBzdHJ1Y3R1cmVcClc6IFZlY3RvciBvZiBjb3ZhcmlhdGVzXApBOiBCaW5hcnkgdHJlYXRtZW50IG9yIGV4cG9zdXJlXApZOiBCaW5hcnkgb3V0Y29tZVwKJFlfezF9LCBZX3tPfSQ6IENvdW50ZXJmYWN0dWFsIG91dGNvbWVzIHdpdGggYmluYXJ5IHRyZWF0bWVudCBBXAokUF97MH0kOiBUcnVlIGRhdGEtZ2VuZXJhdGluZyBkaXN0cmlidXRpb25cCiRcUHNpKFBfezB9KSQ6VHJ1ZSB0YXJnZXQgcGFyYW1ldGVyXAokXHBzaV97MH1cLD1cLFxQc2koUF97MH0pJDogVHJ1ZSB0YXJnZXQgcGFyYW1ldGVyIHZhbHVlXAokZ197MH0kOiBQcm9wZW5zaXR5IHNjb3JlIGZvciB0aGUgdHJlYXRtZW50IG1lY2hhbmlzbSAoQSlcCiRnX3swfSQ6IEVzdGltYXRlIG9mICRnX3swfSRcCiRcZXBzaWxvbiQ6IEZsdWN0dWF0aW9uIHBhcmFtZXRlclwKJFxlcHNpbG9uX3tufSQ6IEVzdGltYXRlIG9mICRcZXBzaWxvbiQgJEhfe259XnsqfSQ6IENsZXZlciBjb3ZhcmlhdGUKZXN0aW1hdGUgKGludmVyc2UgcHJvYmFiaWxpdHkgb2YgdHJlYXRtZW50IHdlaWdodClcCiRMKE8sXGJhciBRKSQ6IEV4YW1wbGUgb2YgYSBsb3NzIGZ1bmN0aW9uIHdoZXJlIGl0IGlzIGEgZnVuY3Rpb24gb2YgTwphbmQgJFxiYXIgUSRcCiQoWSBcLC1cLCBcYmFyIFEoQSwgVykpXnsyfSQ6IEV4cGVjdGVkIHNxdWFyZWQgZXJyb3IgbG9zc1wKJFxiYXIgUV97MH0kOiBDb25kaXRpb25hbCBtZWFuIG9mIG91dGNvbWUgZ2l2ZW4gcGFyZW50czsgJEVfezB9KFl8QSxXKSRcCiRcYmFyIFFfe259JDogRXN0aW1hdGUgb2YgJFxiYXIgUV97MH0kXAokXGJhciBRX3tufV57MH0kOiBJbml0aWFsIGVzdGltYXRlIG9mICRcYmFyIFFfezB9JFwKJFxiYXIgUV97bn1eezF9JDogRmlyc3QgdXBkYXRlZCBlc3RpbWF0ZSBvZiAkXGJhciBRX3swfSRcCiRcYmFyIFFfe259XnsqfSQ6IFRhcmdldGVkIGVzdGltYXRlIG9mICRcYmFyIFFfe259XnswfSQgaW4gVE1MRQpwcm9jZWR1cmU7ICRcYmFyIFFfe259XnsqfSQgbWF5IGVxdWFsICRcYmFyIFFfe259XnsxfSQKCiMgU2Vzc2lvbiBJbmZvCgpgYGB7ciBzZXNzaW9uLWluZm8sIHdhcm5pbmc9RkFMU0UsIHJlc3VsdHMgPSdtYXJrdXAnLCBlY2hvID0gVFJVRX0KZGV2dG9vbHM6OnNlc3Npb25faW5mbygpCmBgYAoKIyBUaGFuayB5b3UKClRoYW5rIHlvdSBmb3IgcGFydGljaXBhdGluZyBpbiB0aGlzIHR1dG9yaWFsLlwKSWYgeW91IGhhdmUgdXBkYXRlcyBvciBjaGFuZ2VzIHRoYXQgeW91IHdvdWxkIGxpa2UgdG8gbWFrZSwgcGxlYXNlIHNlbmQKPGEgaHJlZj0iaHR0cHM6Ly9naXRodWIuY29tL21pZ2FyaWFuZS9NQUxGIiB0YXJnZXQ9Il9ibGFuayI+bWU8L2E+IGEKcHVsbCByZXF1ZXN0LiBBbHRlcm5hdGl2ZWx5LCBpZiB5b3UgaGF2ZSBhbnkgcXVlc3Rpb25zLCBwbGVhc2UgZS1tYWlsCm1lLiBZb3UgY2FuIGNpdGUgdGhpcyByZXBvc2l0b3J5IGFzOlwKTHVxdWUtRmVybmFuZGV6IE1BLCAoMjAxOSkuIFRhcmVnZXRlZCBNYXhpbXVtIExpa2VsaWhvb2QgRXN0aW1hdGlvbiBmb3IKYSBCaW5hcnkgT3V0Y29tZTogVHV0b3JpYWwgYW5kIEd1aWRlZCBJbXBsZW1lbnRhdGlvbi4gR2l0SHViIHJlcG9zaXRvcnksCjxodHRwOi8vbWlnYXJpYW5lLmdpdGh1Yi5pby9UTUxFLm5iLmh0bWw+LlwKKipNaWd1ZWwgQW5nZWwgTHVxdWUgRmVybmFuZGV6KipcCioqRS1tYWlsOioqICptaWd1ZWwtYW5nZWwubHVxdWUgYXQgbHNodG0uYWMudWsqOyAqbWx1cXVlZmUgYXQgdWdyLmVzKgoqKlR3aXR0ZXIqKiBgQFdBVFpJTEVJYAoKIyBSZWZlcmVuY2VzCg==