library(tidyverse)
library(reticulate)
library(forcats)
library(lubridate)
library(knitr)
library(vroom)
library(labelled)
library(gtsummary)
library(ggpubr)
library(RColorBrewer)
library(lemon)
library(paletteer)
library(dbplot)
library(survival)
library(survminer)
library(bshazard)
library(magick)
library(cowplot)
library(rms)

options("encoding" = "UTF-8")
knitr::opts_chunk$set(echo = TRUE, message = FALSE, warning = FALSE)

list("style_number-arg:big.mark" = "") %>%
  set_gtsummary_theme()
trans <- read_csv("D:/MyDocuments/Kaggle/Kkbox/data/trans_sample.csv", 
                  col_types = cols(transaction_date = col_date(),
                                   membership_expire_date = col_date(),
                                   is_cancel = col_logical(),
                                   is_auto_renew = col_logical(),
                                   payment_method_id = col_factor()))
trans <- distinct(trans) %>%
  mutate(payment_method_id = fct_infreq(payment_method_id)) %>%
  arrange(msno, transaction_date)

members <- read_csv("D:/MyDocuments/Kaggle/Kkbox/data/members_sample.csv",
                    col_types = cols(registration_init_time = col_date(),
                                     city = col_factor(),
                                     gender = col_factor(),
                                     registered_via = col_factor()))
members <- members %>%
  mutate(city = fct_infreq(city),
         registered_via = fct_infreq(registered_via),
         gender = fct_explicit_na(factor(gender, c("female", "male"), 
                                         c("Female", "Male")),
                                  "Unknown"),
         year_reg = factor(year(registration_init_time)),
         month_reg = factor(month(registration_init_time),
                            labels = month.abb))

ulogs <- read_csv("D:/MyDocuments/Kaggle/Kkbox/data/ulogs_sample.csv",
                  col_types = cols(date = col_date("%Y%m%d")))
ulogs$num_tot <- pmap_dbl(ulogs[, paste0("num_", c(25,50,75,985,100))], sum)
ulogs <- ulogs %>%
  mutate(av_times_played = num_tot / num_unq,
         num_repeats = num_tot - num_unq, 
         sh_25 = num_25 / num_tot,
         sh_50 = num_50 / num_tot,
         sh_75 = num_75 / num_tot,
         sh_985 = num_985 / num_tot,
         sh_100 = num_100 / num_tot,
         av_pct_played = (num_25*0.125 + num_50*0.375 + num_75*0.625 + num_985*0.8675 + num_100*0.9925)/num_tot*100)

var_label(trans) <- list(payment_method_id = "Payment method",
                         payment_plan_days = "Payment plan days",
                         plan_list_price = "Plan list price",
                         actual_amount_paid = "Actual amount paid",
                         is_auto_renew = "Auto-renew",
                         is_cancel = "Cancellation")

var_label(members) <- list(city = "City",
                           bd = "Age",
                           gender = "Gender",
                           registered_via = "Registration method",
                           year_reg = "Year of registration at the service",
                           month_reg = "Month of registration at the service")

var_label(ulogs) <- list(num_25 = "Number of songs played less than 25% of the song length",
                         num_50 = "Number of songs played between 25% to 50% of the song length", 
                         num_75 = "Number of songs played between 50% to 75% of the song length", 
                         num_985 = "Number of songs played between 75% to 98.5% of the song length",  
                         num_100 = "Number of songs played over 98.5% of the song length",
                         num_tot = "Total number of songs played",
                         num_unq = "Number of unique songs played",
                         av_times_played = "Average number of times of listening to one song",
                         num_repeats = "Number of repeats",
                         sh_25 = "Share of songs played less than 25% of the song length",
                         sh_50 = "Share of songs played between 25% to 50% of the song length", 
                         sh_75 = "Share of songs played between 50% to 75% of the song length", 
                         sh_985 = "Share of songs played between 75% to 98.5% of the song length",  
                         sh_100 = "Share of songs played over 98.5% of the song length",
                         av_pct_played = "Average % of one song's length played",
                         total_secs = "Total seconds played")

1 Introduction


This study project is aimed at prediction of the probability of customers’ attrition (churn) with methods from survival analysis. In comparison with standard classification approaches resulted in prediction of churn as a binary target variable or probability of churn over some fixed period of time, survival analysis can be useful in understanding the dynamic of customer retention and attrition over time after some starting point (usually the start of relationship - contract, subscription, etc.). It can help in answering such questions as when clients churn the most, whether there are periods of increasing, stable or decreasing churn rates, as well as whether these all are different depending on customers’ characteristics, their behavior or some contract options. In addition, it is applied in estimating customer lifetime value for a company.

The dataset for the project was taken from Kaggle competition “WSDM - KKBox’s Churn Prediction Challenge” which was held in 2017. KKBox is an Asian company which offers subscription based music streaming service. The criteria of churn used in the competition is no new valid service subscription within 30 days after the current membership expires. We will use the same definition, although we will predict not just the probability of churn during the next 30 days after membership expiration as a binary outcome, but the distribution of time to churn and retention rates for different time points after the start of subscription. We will use models from both statistical analysis and machine learning, as well as both baseline features (available at the start of subscription) and time-dependent variables (information on user’s behavior during subscription).


2 Literature review: survival analysis


Survival analysis dates back to the 17th century when cross-sectional life tables were developed by John Graunt and Edmond Halley. There were further developments by Daniel Bernoulli in 1760, when he computed life expectancy using life tables. These approaches are still used in demography and epidemiology. Throughout the centuries, survival analysis was solely linked to the investigation of mortality rates; however, in the last few decades, applications of its methods have been extended beyond biomedical research to other fields such as criminology, sociology, marketing, economics, institutional research, health insurance and HR practice (Camilleri 2019).


2.1 Advantages of survival analysis in churn prediction


Our methodology for predicting customers’ churn will be based on the approach of survival analysis, for which the main variable of interest is the (expected) time to some event. In addition, for each observation we have a status variable, taking value 1 if the event happened at the end of the corresponding time period, or 0 otherwise. Latter observations are called censored: it means that we observed them for the whole time period without the event, but we admit that this event can happen for them in the future (after the period of observation), so the time to event for them will be censored on the right and will be denoted as observation time+. If no observations were censored, we could use standard regression models for continuous responses to predict time to event or models for binary classification to predict the probability of event during some preliminary specified period of time (for example, one year after beginning of observation). Besides taking censoring into account, in comparison with these models survival analysis have several advantages (Harrell 2015):

  • Of course, above all, it is a priority option of choice when time to some event is of the main interest. Knowing its value could be also of use in estimating user’s lifetime value. In addition, the probability of surviving past a certain time (for example, the probability that a user will be still active after a month or a year of subscription) is often more relevant than the expected survival time, and survival analysis has a potential in predicting this probability.

  • Time to event can have an unusual distribution: it is restricted to be positive, so it has a skewed distribution and will never be normally distributed. It is not a problem for survival analysis, but can be a problem to standard models with continuous target variable.

  • One of the functions used in survival analysis, the hazard function, helps to understand the mechanism of failure (in our case the mechanism of churn) and how it changes over the survival time (period of subscription). Moreover, we can predict patterns in outcome (churn behavior) over time for individual subjects.

    For example, in dependence of subscription service peculiarities (e.g. prepaid or postpaid, free-trial periods, subscription plans, etc.) there can be different types of hazard functions (Vargas 2018):


2.2 Conceptual framework of survival analysis


In our study the event is a churn, i.e. a membership expiration such that during the next 30 days it is not reactivated. It means that if 30 days after the end of the subscription period haven’t passed yet, we are not able to tell whether this client churned, and we should consider him/her as censored. Our target variable is time to event (T) - the time between the start and the end of subscription. We will define the principles of determining the start and the end points of subscription in more detail below.

We will assume that T is a continuous random variable with probability density function f(t) and cumulative distribution function \(F(t) = P(T < t)\), giving the probability that the event has occurred by time t. In survival analysis the statistical model for time to event (T) is usually defined with the complement of cumulative density function (Rodríguez 2007), called a survival function S(t). Survival function is the probability that an individual survives from the time origin to a specified time t or, equivalently, that the event has not occurred by time t and will (may) occur after it. In our analysis it will be the probability that a user remains active from the start of his/her subscription to a specified time point t (or probability that he/she churn some time after t): \[S(t) = P(T>t) = 1-F(t)=\int_t^\infty f(t)\] Survival function is non-increasing with t and equals 1 at t = 0.

Another important term in survival analysis is hazard h(t) - the probability that an individual who is still observed at time t has an event in small interval after t: \[h(t)=\lim_{u \to 0}\frac{P(t<T<t+u | T>t)}{u} = \frac{f(t)}{S(t)}=-\frac{d}{dt}logS(t)\] Hazard will characterize the intensity of churn over time, i.e. a rate of churn per unit of time. As the width of the interval (u) goes to zero, we obtain an instantaneous rate of churn for some specific time point.

The numerator in the first part of the above expression is the conditional probability that the churn will occur in the small interval after t ([t, t+u)) given that it has not occurred before, and the denominator is the width of the interval (u). The second part of the formula says that the hazard rate of churn at time t equals the density of churns at t, divided by the probability of membership lasting to that point in time without churning. In addition, it can be proven that h(t) equals the minus slope of the log of the survival function. The latter gives us the formula (Rodríguez 2007): \[S(t) = exp(-\int_0^t h(v)dv)\]

At last, the area under the curve for hazard h(t) is a cumulative hazard function (H(t)): \[H(t) = \int_0^t h(v)dv = -logS(t)\] It presents the sum of the risks you face going from duration 0 to t (Rodríguez 2007). From the last equation we have \[S(t) = exp(-H(t))\]

Therefore, knowing any one of the functions S(t), H(t), or h(t) allows one to derive the other two functions. These three functions are different ways of describing the same distribution for the time to event.


One of the problems in survival analysis is that the form of the true population survival distribution function S(t) is almost always unknown. As a consequence, we should use some estimate for it. If there is no censoring, we can use empirical distribution function of time to event, \(F_n(t)\), to nonparametrically estimate survival as \(S_n(t) = 1 - F_n(t) = [\text{number of }T_i > t]/n\) (Harrell 2015). With censoring taking place we can use either other nonparametric estimates (the most frequently used here are Kaplan–Meier product-limit estimator and Nelson-Aalen estimator) or parametrically estimate the survival function (it means that we should specify a functional form for S(t) and estimate its unknown parameters). Parametric models give more precise and smooth estimates for survival, hazard and cumulative hazard, allows to easily compute quantiles for time to event and to predict it, but they require accurate specification of distribution function.

Below the example of Kaplan-Meier survival curve is given (Kazmi 2019):

There are several types of most frequently used parametric models for survival function: Weibull, exponential (it is a Weibull distribution with shape = 1), log-normal, log-logistic and gamma. For more examples of distributions and their characteristics see Herndon (1988). These distributions can be useful for modelling different patterns of hazard behavior: for example, exponential - for the hazard that is constant in time, Weibull, Gompertz, gamma – for monotonically decreasing or increasing hazard, while log-logistic, log-normal, generalized gamma and generalized F can be used for modelling bathtub-shaped hazard functions.

Here are several examples of survival, hazard and density plots for four of these distributions with different parameters (Emmert-Streib and Dehmer 2019):

For gamma distribution:

In more general case we can use any distribution defined for \(t \in [0,\infty)\) or for transformed time to event (usually log transformed) or models derived from spline smoothing for hazard functions.


For estimating the unknown parameters of survival function S(t) maximum likelihood estimation is applied. But is has some peculiarities in the presence of censoring, i.e. unobserved actual time to event for some subjects.

If for the i-th subject we denote the censoring time as \(C_i\) and the time to event as \(T_i\), then we can define the event indicator as: \[e_i = \begin{cases} 1, & if \text{ } T_i \leq C_i\\ 0, & if \text{ } T_i > C_i \end{cases}\] and the observed target variable as: \[Y_i = min(T_i, C_i)\]

Thus, the pair \((Y_i, e_i)\) contains all the information we need to know about the target variable, and it is usually used in realization of survival models in different libraries in Python and R.

If \(Y_i\) is uncensored, observation i contributes a factor to the likelihood equal to the density function for T evaluated at \(Y_i\), \(f(Y_i)\). If \(Y_i\) represents a censored time, it is only known that \(T_i > Y_i\). The contribution to the likelihood function is then the probability that \(T_i > C_i\), which equals to probability that \(T_i > Y_i\). This probability is \(S(Y_i)\). So the joint likelihood over n observations is (Harrell 2015): \[L = \prod_{i:Y_i\text{ uncensored}}^{n} f(Y_i) \prod_{i:Y_i\text{ censored}}^{n} S(Y_i)\] (! in the assumption of non-informative, i.e. independent of the risk of the event, censoring). The model is estimated by maximizing this likelihood or, equivalently, the log likelihood: \[logL = \sum_{i:Y_i\text{ uncensored}}^n log (h(Y_i)) - \sum_{i=1}^n H(Y_i)\]

All observations then contribute an amount to the log likelihood equal to the negative of the cumulative hazard evaluated at the event/ censoring time. In addition, uncensored observations contribute an amount equal to the log of the hazard function evaluated at the time of event. Once L or log L is specified, the general maximum likelihood methods can be used without change in most situations. The principal difference is that censored observations contribute less information to the statistical inference than uncensored ones.


2.3 Prediction models in survival analysis


Of course, if we are to predict a time to event or survival distribution, we usually would like to include some predictors into our analysis. It could be done by generalizing survival model into survival regression model (which is typical for traditional statistical analysis) and estimate it using statistical or machine learning based methods. Statistical methods focus more on characterizing both the distributions of the event times and the statistical properties of the parameter estimation. Machine learning methods are usually less conservative in their assumptions. The taxonomy of all methods developed for survival analysis is presented below (Wang, Li, and Reddy 2019):


Overall, survival models are used to predict one or all of the following measures (Sonabend 2021):

  • The time to event: \[g: X \to T\] where \(X \subseteq \mathbb{R}^m\) is a matrix for m features, \(T\) - predicted time to event, \(g\) - a prediction functional, which is trained on the training set \((X,Y,e)\) with the observed time \(Y \subseteq \mathbb{R}_{\geq 0}\) (some models assume that \(Y \subseteq \mathbb{R}_{> 0}\), so for zero observed time some small value should be added) and an indicator for event (1) and censoring (0) status \(e = \{0,1\}\). It is the deterministic kind of task.

  • The relative risk of an individual experiencing an event: \[X \to \mathcal{R}\] where \(\mathcal{R} \subseteq \mathbb{R}\) is a relative risk. It is a ranking kind of task. By themselves relative risks have not a meaningful interpretation, but can be used to rank and/ or cluster individuals by relative risk. Time-to-event predictions can be seen as a special-case of the ranking problem as an individual with a predicted longer survival time will have a lower overall risk.

  • An individual’s survival distribution: \[X \to \mathcal{S}\] where \(\mathcal{S}\) comes from the convex set of distributions on \(T\): \(\mathcal{S} \subseteq Distr(T)\) . It is a probabilistic kind of tasks. Actually, using different models we can obtain different representations of the survival distribution (survival, or hazard, or cumulative hazard function), but as we know, they are all related to each other. The above prediction tasks can all be solved using the survival distribution prediction.


2.3.1 Survival regression models


All survival regression models can be divided into two main groups, depending on their assumptions of the nature of covariates’ effects, namely: accelerated failure time and proportional hazard models.


1) Accelerated failure time (AFT) models:

Predictors act multiplicatively on the failure time or additively on the log failure time: \[logT = X\beta + \sigma\epsilon \Rightarrow T = exp(X\beta)T_0\] where X is a \(n*m\) matrix of m characteristics for n individuals (may include an intercept), \(\beta\) - a vector of m coefficients (weights), \(epsilon\) - an error term with some prespecified distribution \(\psi\) (see below) or estimated non-parametrically (rarely used), \(\sigma\) - a scale parameter, \(T_0 = exp(\sigma\epsilon)\) (Rodríguez 2007; Harrell 2015).

The value of \(exp(X\beta) = \gamma\) is called an accelerator factor, because the effect of a predictor in AFT models is to alter the rate at which a subject proceeds along the time axis (i.e., to accelerate the time to failure) (Harrell 2015). For example, the failure time for the i-th individual with the j-th characteristic by one unit higher than for the k-th subject (all other things being equal) will be \(exp(\beta_j)\) times the failure time of the k-th subject.

Using AFT models, we can easily predict different representations of the individual survival distribution – of course, if such nature of variables effect on time is typical for the field of study. In addition, AFT models are usually parametric and require accurate assumptions about errors distribution (\(\psi\)) (for details see Abdul-Fatawu (2020) and Harrell (2015)). We do not plan using them in this study.


2) Proportional hazards (PH) models:

While AFT models assumes that predictors have a multiplicative effect on the time to event and additive effect for the log time to event, proportional hazards models assumes the same for the hazard and log hazard, accordingly (Rodríguez 2007): \[h(t|X) = h_0(t)exp(X\beta) \Rightarrow log(h(t|X)) = log(h_0(t)) + X\beta\] where \(h_0(t)\) is the baseline hazard function (when all predictors equal zero), \(X\) - matrix of features (not including a constant!), \(\beta\) - regression coefficients (weights). Here \(exp(x_i^`\beta)\) is the relative risk associated with the set of characteristics x_i for the i-th individual in comparison with the risk of an individual with all characteristics equal zero. So in contrast to AFT models, the hazard is positively related to the risk component, \(exp(X\beta)\).

The alternative way to specify a PH regression is to add a constant to feature matrix, such that: \[h(t|X) = exp([C|X]\beta)\] then baseline hazard will be estimated as \(h_0(t)=exp(\beta_0)\), where \(\beta_0\) is the weight for the intercept.

It is important to emphasize that the relative risk does not depend on time, i.e. it is constant in time for the same pair of values of any feature, so hazards are proportional independent of time.

The same holds for cumulative hazards (Rodríguez 2007): \[H(t|X) = H_0(t)exp(X\beta)\]

The corresponding survival function will be (Rodríguez 2007): \[S(t|X) = S_0(t)^{exp(X\beta)}\] where \(S_0(t)\) is the baseline survival function. Thus, the effect of the set of characteristics \(x_i\) for the i-th individual on the survivor function is to raise it to a power given by the relative risk \(exp(x_i^`\beta)\).

We can use some specific distribution for the baseline survival or hazard function – and then we get a parametric PH model, or do not make such assumptions about the baseline functions – and then get a semi-parametric PH model, often called a Cox PH model after its author.

Concerning parametric PH models, it is worth noticing that there are two distributions which give regression models that are out of both AFT and PH families. They are Weibull and its special case - exponential distributions. So the results of Weibull and exponential regression models can be interpreted in both ways.

In other cases if we plot a log hazard on the log time scale the effect of the PH assumption is to model the change in hazard as a vertical shift, while the effect of the AFT assumption is to model the change in hazard as a horizontal shift (Breheny n.d.a). This approach could be of use in choosing between AFT and PH models, as well as in assessing the fulfillment of their assumptions.

Below a summary description of both AFT and PH parametric models is presented (Mills 2010).

As usual, parametric PH models, require correct specification of distributions for baseline survival, hazard or cumulative hazard function. The model parameters are estimated with maximum likelihood estimation, with the likelihood dependent on the chosen distribution with a possibility of treating the distribution choice as a model hyper-parameter. In the output we get a continuous survival distribution prediction.

Semi-parametric (Cox) PH models are more widely used than parametric PH models, because they do not have parametric assumptions concerning baseline functions - just the effect of covariates on hazard. Cox argued that when the PH assumption holds, information about baseline hazard function is not very useful in estimating the parameters of primary interest (\(\beta\)). By special conditioning in formulating the log likelihood function, Cox showed how to derive a valid estimate of \(\beta\) that does not require estimation of baseline hazard as it is dropped out of the new likelihood function. Cox’s derivation focuses on using the information in the data that relates to the relative hazard function \(\exp(X\beta)\) (Harrell 2015).

As a result, estimation of the Cox PH model is based on the maximization of the partial likelihood: \[L(\beta) = \prod_{i: Y_i uncensored}\frac{exp(X_i\beta)}{\sum_{Y_j \geq Y_i}exp(X_j\beta)}\] or, equivalently, minimization of the negative log partial likelihood \(-logL\). Newton-Rapshon technique for iterative estimation is often used here.

After estimating the Cox PH we can get a linear prediction for each individual as \(x_i^`\hat\beta\) and use it to rank them by relative risk of event.

The Cox model and parametric survival models differ markedly in how one estimates \(S(t|X)\). Since the Cox model does not depend on a choice of the underlying survival function \(S(t)\), fitting a Cox model does not result directly in an estimate of \(S(t|X)\). Several authors have derived secondary estimates for it (for more details see Harrell (2015)). Usually they use some non-parametric estimator for the baseline hazard function. As a result, they give a discrete survival distribution prediction. In contrast, there are models that use splines to fit the baseline hazard function (\(log(H_0(t)\) is estimated by natural cubic splines with coefficients fit by maximum likelihood estimation (Royston and Parmar 2002)). Parametric models give direct functional form for baseline survival function, from which individual survival distribution can easily be obtained using the formula above.


3) Extensions of the basic AFT and PH models:

There are several ways to extend the described basic AFT and PH models to receive more accurate predictions. Although most of them are easier incorporated into PH models.

The common problem for all PH models is a violation of the PH assumption (several methods can be used to check this). If AFT models are not the appropriate alternatives, then stratified Cox regression can be estimated (it assumes different baseline hazard functions for different strata) or Cox regression with time-dependent effects (in this case the assumption of PH is dropped out for some or all covariates: \(h(t|X) = h_0(t)exp(X\beta(t))\)). Different parametric assumptions can be put on \(\beta(t)\) and according to them it can be modeled as a piecewise constant, linear, piecewise linear, spline, polynomial function of time and other approaches (for more details see Breheny (n.d.b)).

Another extension aimed at receiving more accurate predictions is a regularizarion of regression coefficients. Here we can apply any of L1 or L2 or elastic net or other regularization methods (for details see (Simon et al. 2011; Wang, Li, and Reddy 2019)). If there are such covariates that should remain in the model even after regularization, then CoxBoost can be applied (Wang, Li, and Reddy 2019).

At last, we can add time-dependent covariates to Cox regression, i.e. such features which are estimated not only at the time origin, but also after it, during the process of observation. In our case it will be all characteristics of users’ behavior obtained after beginning of the subscription period.

PH models with time-dependent covariates will have the following form: \[h(t, X(t)) = h_0(t)exp(X(t)\beta)\]

Calculation of survival functions in this model is a little bit more complicated, because we need to specify a path or trajectory for each variable (Rodríguez 2007). Usually it is done by splitting the whole period of time into intervals between time points at which the corresponding covariate was measured (or changed its value) - thus, instead of one observation we obtain several pseudo-observations for one individual of type \((time\text{_start}, time\text{_stop}, event)\), which means that at the end of the interval \((time\text{_start}, time\text{_stop}]\) an individual has an \(event\) status (0 or 1), and all time-dependent covariates are measured as of at the end of this interval. Thus, in fact, all these pseudo-observations (except, maybe, the first one) become left-truncated right-censored.

This data form is called an Andersen-Gill reformulation or a counting process form and does not change the form of partial likelihood function - only the interpretation of its elements and results of its maximization (T. Therneau, Crowson, and Atkinson 2017). Baseline functions in this framework are calculated for subject with all covariates equal zero for all t and no longer have a clear interpretation. \(exp(x_{ij}(t)\beta_j)\) for some i-th individual and j-th feature is interpreted as the relative risk of event for the individual with the value of \(x_{ij}\) obtained by time t in comparison with the individual who had a zero value of this feature by the same time. With such interpretation we can say that it is more useful for categorical predictors, while for continuous ones it may require some preliminary centralization of values for all subjects and points.

Non-parametric estimates, including Kaplan-Meier curves are not applicable for time-dependent covariates, as they preserve splitting observations into groups as they are made at the starting point of time. Simon and Makuch proposed a technique that evaluates the covariate status of the individuals remaining at risk (i.e. without event or censoring by that point) at each event time, and their estimator can be used to correctly plot Kaplan-Meier curves for time-dependent variables (although, again, only for categorical ones) (Schultz, Peterson, and Breslau 2002).

Predicting survival function with several covariates, one or more of which are time-dependent, is quite more complicated – both in estimation, prediction and interpretation. Laine and Reyes (2014) describes the process of estimating survival after obtaining hazards from Cox PH regression: for particular covariate trajectory \(x^*(t)\)

\[\hat{H}(t|x^*(t)) =\sum_{i=1}^n \int_0^t\frac{exp(\hat{\beta}x^*(u))dN_i(u)}{\sum_j Y_j(u)exp(\hat{\beta}X_j(u))}\] where \(N_i(t)=\mathbb{I}(T_i \geq t)\). Then

\[\hat{S}(t|x^*(t))=exp(-\hat{H}(t|x^*(t)))\] and now it is not a simple function, as now it requires unique integration to estimate cumulative hazard for every value of \(x^*(t)\).

In addition, Laine and Reyes (2014) note that in the presence of time-dependent covariates, it may not make sense to calculate survival probabilities or predictions. This requires knowledge of \(X(t)\), which may be unknown until time t, at which point its observation frequently implies survival. For example, individuals who have surgery at 1 year are alive at 1 year, by definition. Hence, survival estimation is rarely implemented in this case in medical studies.

Although, in our opinion, it could be useful in the process of observation (after the start of therapy, subscription or other starting points depending on the nature of the study), as we obtain additional information, and using it we can update survival function for individuals or groups. For example, Yao et al. (2020) illustrate how updated and not updated survival curves can be different:


2.3.2 Machine learning models


Machine learning models for survival data in comparison with the same models for “ordinary” continuous or class dependent variables can deal with censored data. Here we have survival trees and their ensembles, including random survival forest (RFS), gradient boosting machines, support vector machines, and neural networks. Usually they do not have such restrictive assumptions as statistical models, although there are some models that are based on the PH assumption. The comprehensive survey of different machine learning models for survival data is presented in Sonabend (2021). The following table was taken from there:

Class: RSF – Random Survival Forest and Decision Trees; GBM – Gradient Boosting Machine; SVM – Support Vector Machine; ANN – Artificial Neural Network. Task: Det. - Deterministic, Prob. - Probabilistic, Rank - Ranking.


The thing is, that for ML models it is still not very common to be adapted for use of the time-dependent covariates. As one of the purposes of our study was to predict customers’ churn with time-varying features from all the variety of ML models we choose random survival forest (RSF), for which, unlike other machine learning methods, the algorithm can be more easily adapted both for working with survival data and for using time-dependent covariates.

As Sonabend (2021) notes, the difference between ordinary random forest and random survival forest lies in different choices of splitting rules and terminal node predictions – all fundamentals remains unchanged. He distinguishes two groups of RSF models by splitting rules being used: those relied on hypothesis tests (primarily the nonparametric log-rank test, which in statistical analysis is used for comparing survival distributions; higher values of the log-rank statistic mean greater dissimilarity between distributions) and those utilise likelihood-based measures (they require likelihood estimation, which means assumption about certain model form, for example Cox PH model).

As far as terminal node predictions are concerned, all RSF models again can be separated into two groups: one of them result in terminal node ranking predictions (these models assume using likelihood-based splitting rule and a PH model form), the other predict survival distribution by estimating the survival function, using the Kaplan-Meier or Nelson-Aalen estimators, on the sample in the terminal node.

There are several approaches to adjust RSF to time-dependent covariates. Overall, they suggest splitting pseudo-observations for one subject across many nodes as a function of time, so in the end, a subject could end up in many different terminal nodes, but at any given time, each subject can be classified into one and only one terminal node (Bou-Hamad, Larocque, and Ben-Ameur 2011).


2.4 Model evaluation


The specific feature for all survival models is that the predicted outcome for the i-th individual – the expected time to event (\(\hat{T_i}\)) or relative risk ((\(\hat{\eta_i}\)) or survival distribution ((\(\hat{S_i}\)) – is a different object than the outcome used for model training – the pair of \((Y_i,e_i)\), as they were defined above. It affects the approaches to model evaluation.

Depending on the task (what is predicted by the model) different loss or score functions can be used (@ Sonabend 2021):

1) For time-to-event predictions:

For this task we can use the same metrics as for regressions with continuous target variable (MSE, RMSE, MAE), except the fact that they are evaluated only for uncensored individuals, i.e. for those for whom the time to event is observable. Of course, they are not very optimal when there is many censored observations.


2) For continuous relative risk (ranks) predictions and time-to-event predictions:

For this kind of tasks usually measures of discrimination are used. It means that the predicted values are considered as a measure of relative risk of event (although not interpretable), according to which observations are ranked, and then these ranks are used to calculate the measure of discrimination. It makes this measure independent of outliers. A model has perfect discrimination if it correctly predicts that patient with higher risk of event will have earlier event.

Here are two types of discrimination measures (Sonabend 2021):

  • Concordance index (or C-index, or simply C) measures the proportion of cases in which the model correctly separates a pair of observations into low and high risk. There are several form of concordance indices, but the most widely used is Harrell’s C:

    For a pair of observations i and j (\(i \neq j\)), arranged in ascending order by the values of Y ($Y_i < \(Y_j\)) with predicted relative risk values \(\eta_i\) and \(\eta_j\), and censoring statuses \(e_i\) and \(e_j\):

    • If both \(T_i\) and \(T_j\) are not censored (\(Y_i = T_i\), \(Y_j = T_j\)), the pair (i, j) is a concordant pair if \(\eta_i > \eta_j\), and it is a discordant pair if \(\eta_i < \eta_j\).
    • If both \(T_i\) and$ \(T_j\) are censored (\(Y_i = C_i\), \(Y_j = C_j\)), this pair is not considered in the computation.
    • If one of \(T_i\) and \(T_j\) is censored, we only observe one event. If we observe the event for individual i, so \(Y_i = T_i\) and \(Y_j = C_j\), then (i, j) is a concordant pair, otherwise we don’t consider this pair in the computation. \[c = \frac{\text{# concordant pairs}}{\text{# concordant pairs + # disconcordant pairs}} = \frac{\sum_{i \neq j}\mathbb{I}(\eta_i < \eta_j) \mathbb{I}(T_i > T_j)e_j}{\sum_{i \neq j} \mathbb{I}(T_i > T_j)e_j}\] \(c \in [0,1]\) with 1 indicating perfect separation, 0.5 indicate that the risk score predictions are no better than a coin flip in determining which individual will have longer time to event, and 0 being separation went in the opposite direction.

    C-index can be reformulated for time-to-event prediction (just replace \(\eta_i\) and \(\eta_j\) with \(\hat{T_i}\) and \(\hat{T_j}\) and change the signs between \(\eta\)s to the opposite ones).

    The other variants of the c-index are different from the Harrell’s C in using some weights in nominator and denominator, calculated based on the non-parametric estimates for survival distribution or survival function for censoring distribution (e.g. Uno’s C-index). They are less affected by censoring than the Harrell’s C, but also less interpretable.

  • Time-dependent AUC and C-index:

    AUC measures for survival analysis have been developed in order to provide a time-dependent measure of discriminatory ability (for more detail see Wang, Li, and Reddy (2019)).


3) For survival distribution prediction:

For this type of tasks several types of measures can be used:

  • Calibration measures, including graphical methods of comparison between predicted and expected distribution (the latter is often approximated by the Kaplan-Meier curve) and modification of \(\chi^2\) test.

  • Scoring rules (loss functions) evaluate probabilistic predictions and (attempt to) measure the overall predictive ability of a model, i.e. both calibration and discrimination.

    Losses in the survival setting compare predicted survival distributions to the observed pairs (\(Y_ic, e_i\)). Usually it also include an estimator of the unknown censoring distribution.

    The most widely used loss here is Brier score (BS): \[BS(t) = \frac{1}{N}\sum_{i=1}^N w_i(t)(\hat{S_i}(t) - S_i(t))^2\] where \(S_i(t)\) is the true survival probability for i-th individual at time t (it equals 0 if \(T_i \leq t\) and 1 otherwise), \(\hat{S_i}(t)\) - predicted survival probability, \(w_i(t)\) - the weight of i-th observation, estimated by incorporating the Kaplan-Meier estimator of the censoring distribution G obtained on the given dataset \((X,Y,1−e)\) (Wang, Li, and Reddy 2019): \(w_i(t) = e_i/G(Y_i)\) if \(Y_i \leq t\) or \(1/G(Y_i)\) otherwise. The weights for the instances that are censored before t will be 0. However, they contribute indirectly to the calculation of the Brier score since they are used for calculating G.

    Integrated Brier score (IBS) is an overall measure for the prediction: \[IBS = \frac{1}{Y_{max}} \int_0^{Y_{max}} BS(t)dt\]


4) For models with time-dependent covariates:

It is a more complicated procedure to estimate prediction performance of models with time-dependent covariates, but here we still can calculate C-indices, as well as time-dependent Brier score and integrated Brier score. You can find more information about it in Terry M. Therneau and Watson (2017).


3 Exploratory data analysis


The whole dataset consists of several files (total size is 32 GB):

  • transactions (transactions.csv for period up to March, 2017 and transactions_v2.csv for period up to April, 2017) - information about buying a subscription, its renewal (auto or manual) and cancellation (manual, by client); we will define our dependent variables (churn and time to churn) using data from these files;

  • members’ characteristics (members_v3.csv) - information about city, age, gender, registration method and date of initial registration in the service,

  • user logs (user_logs.csv for period up to March, 2017 and user_logs_v2.csv for period up to April, 2017) - daily user logs describing listening behaviors of users.

As organizers answered to one of participants’ questions at Kaggle, sometimes they erase members information, but mostly do not erase data from transactions and user logs. Taking it into consideration we’ve decided to choose data only for those users, whose ID occurs in all three files - we’ve found almost 2 million (1 937 764) such IDs.

Further, for more convenience and for initial analysis we’ve decided to sample 1% out of these IDs and exclude those of them for whom all dates in user logs are missing. After that 19200 users remained in our dataset.


3.1 Transactions


Data for 198566 transactions from 2015-01-01 till 2017-03-31. Transactions include subscription, its renewal, changes in subscription plans and cancellations, although there is not any indicator for distinguishing these types of transactions from each other.

trans <- trans %>%
  arrange(msno, transaction_date) %>%
  group_by(msno) %>%
  mutate(time_bw_exp_trans = as.numeric(membership_expire_date - transaction_date, 'days'),
         time_bw_exp_nexttrans = ifelse(is.na(lead(transaction_date)),
                                        as.numeric(as.Date("2017-03-31") - membership_expire_date, 'days'),
                                        as.numeric(lead(transaction_date) - membership_expire_date, 'days')),
         time_bw_trans = as.numeric(lead(transaction_date) - transaction_date, 'days'),
         trans_id = 1:n()) %>%
  ungroup()

trans_df <- trans %>%
  arrange(msno, transaction_date) %>%
  mutate(membership_expire_date_cor = as.Date(ifelse(membership_expire_date < transaction_date, 
                                                     transaction_date, membership_expire_date),
                                              "1970-01-01")) %>%
  group_by(msno) %>%
  mutate(time_bw_exp_trans = as.numeric(membership_expire_date_cor - transaction_date, 'days'),
         time_bw_exp_nexttrans = ifelse(is.na(lead(transaction_date)),
                                        as.numeric(as.Date("2017-03-31") - membership_expire_date_cor, 'days'),
                                        as.numeric(lead(transaction_date) - membership_expire_date_cor, 'days'))) %>%
  ungroup()


Transaction file contains the following information:


  • transaction_date - the date of transaction.
trans_stat <- trans %>%
  group_by(msno) %>%
  summarise(n = n(),
            sum_cancel = sum(is_cancel),
            sum_auto = sum(is_auto_renew),
            ) %>%
  ungroup()

trans_freq_max <- trans %>%
  filter(!is.na(time_bw_trans)) %>%
  group_by(time_bw_trans) %>%
  tally() %>%
  arrange(-n)

p1 <- trans_stat %>%
  filter(n < quantile(trans_stat$n, 0.99)) %>%
 ggplot() +
  aes(x = n) +
  geom_histogram(binwidth = 1, fill = "#4682B4") +
  geom_text(x = 15, y = 4000, label = sprintf("Mean (SD): %.1f (%.1f)", mean(trans_stat$n),
                                              sd(trans_stat$n)),
            hjust = 0, color = "grey20", size = 3.5) +
  geom_text(x = 15, y = 3700, label = sprintf("Median (Q1, Q3): %.0f (%.0f, %.0f)", 
                                              median(trans_stat$n), quantile(trans_stat$n, 0.25),
                                              quantile(trans_stat$n, 0.75)),
            hjust = 0, color = "grey20", size = 3.5) +
  geom_text(x = 15, y = 3400, label = sprintf("Min-Max: %.0f-%.0f", min(trans_stat$n),
                                              max(trans_stat$n)),
            hjust = 0, color = "grey20", size = 3.5) +
  labs(x = "", y = "", title = "Number of records per ID in transactions\n(for values < 99th percentile)") +
  scale_x_continuous(breaks = seq(0, 30, 5), expand = c(0,0),
                     limits = c(0, 30)) +
  scale_y_continuous(expand = c(0,0), 
                     breaks = seq(0, 8000, 1000), labels = c(0, paste0(seq(1,8,1), "K"))) +
  ggthemes::theme_tufte() +
  theme(plot.title = element_text(face = "bold"),
        axis.text.x = element_text(size = 12))

p2 <- trans %>%
  filter(time_bw_trans < quantile(trans$time_bw_trans, 0.99, na.rm = TRUE)) %>%
 ggplot() +
  aes(x = time_bw_trans) +
  geom_histogram(binwidth = 5, fill = "#4682B4") +
  geom_text(x = 90, y = 140000, label = sprintf("Mean (SD): %.1f (%.1f)", mean(trans$time_bw_trans, na.rm = TRUE),
                                              sd(trans$time_bw_trans, na.rm = TRUE)),
            hjust = 0, color = "grey20", size = 3.5) +
  geom_text(x = 90, y = 130000, label = sprintf("Median (Q1, Q3): %.0f (%.0f, %.0f)",
                                              median(trans$time_bw_trans, na.rm = TRUE), 
                                              quantile(trans$time_bw_trans, 0.25, na.rm = TRUE),
                                              quantile(trans$time_bw_trans, 0.75, na.rm = TRUE)),
            hjust = 0, color = "grey20", size = 3.5) +
  geom_text(x = 90, y = 120000, label = sprintf("Min-Max: %.0f-%.0f", min(trans$time_bw_trans, na.rm = TRUE),
                                              max(trans$time_bw_trans, na.rm = TRUE)),
            hjust = 0, color = "grey20", size = 3.5) +
  labs(x = "", y = "", title = "Time between transactions\n(for values < 99th percentile)") +
  scale_x_continuous(breaks = seq(0, 210, 30), expand = c(0,0)) +
  scale_y_continuous(expand = c(0,0), limits = c(0, 150000),
                     breaks = seq(0, 150000, 50000), 
                     labels = c(0, paste0(seq(50,150,50), "K"))) +
  ggthemes::theme_tufte() +
  theme(plot.title = element_text(face = "bold"),
        axis.text.x = element_text(size = 12))

ggarrange(p1, p2, nrow = 1, ncol = 2)


– About 22.7% users have only one transaction in the whole dataset.

– Usually there is about 30 days between two conseсutive transactions of one user. Particularly, among all pairs of consecutive transactions about 25.1% are distanced by 30 days, and 45.9% - by 31, all other periods occur for less than 7% times each.


  • membership_expire_date - subscription expiration date after this transaction. There were 1415 records with membership expiration date less than transaction date. The minimum expiration date for them was 1970-01-01, for 96 transactions it went before 2015-01-01 (the minimum transaction date in the whole dataset), so there are obviously some odd values here, and we should do something with them prior to the analysis.

– Out of all negative values for the time between membership expire date and transaction date listed for the same transaction 92.9% were between -80 and 0 days; moreover, 88.3% equals -1 or -2 days, and 94.8% of all transactions with negative time are with is_cancel=1.

– There were no specific explanations for these cases from organizers, but too negative values look very odd, especially taking into account that most of negative time transactions (71.2%) were not the last for the corresponding user. So we have decided that it will be reasonable to replace membership expiration date in all such transactions into the date of the corresponding transaction: it will hardly bias some further results, as the number of such transactions is quite small, and most of them contains almost equal transaction and expiration dates.


  • is cancel - whether or not the user cancelled the membership in this transaction. Among all records 3.1% concerned cancellation. According to organizers, subscription cancellation does not imply the user has churned, as he/she may cancel subscription due to change of service plans or other reasons. Among 19200 users 27% cancelled their subscription for at least once.


  • is_auto_renew - for transactions not concerning cancellation whether it was auto or manual renewal of subscription. Among all records 83.3% are auto-renewals. All cancellation transactions are marked as auto-renewals, among non-cancellation transactions 82.7% are auto-renewals. Organizers did not give any clarification about what auto-renewal means, but as we will see below, most likely, auto-renewal means automotive renewal of the membership expiration date: for example, when a user cancels his/her subscription or changes a plan from, say, 1-month to 2-months, then membership expiration date changes automatically.

    Among 19200 users 43.2% had only manual renewals, 48.1% had only auto-renuals, the rest (8.7%) have both of them.


  • payment_method_id - payment method. Organizers at Kaggle did not provide any information about these methods. There were 35unique numeric codes for them.
plt_df <- trans_df %>%
  group_by(payment_method_id) %>%
  tally() %>%
  ungroup() %>%
  mutate(pct = n/sum(n)*100,
         lbl = sprintf("%.0f%%", pct),
         lbl = ifelse(lbl == "0%", "<1%", lbl))

trans_df %>%
 ggplot() +
  aes(x = payment_method_id) +
  geom_bar(fill = "#2D559E") +
  geom_text(aes(x = payment_method_id, y = n - 250, label = lbl),
            plt_df %>% filter(pct > 4), angle = 90, hjust = 1, vjust = 0.5,
            color = "white", fontface = "bold") +
  labs(title = "Payment method", y = "", x = "") +
  scale_y_continuous(expand = c(0, 0), breaks = seq(0,100000,20000),
                     labels = c(0, paste0(seq(20,100,20), "K")),
                     limits = c(0, 100000)) +
  ggthemes::theme_tufte() +
  theme(plot.title = element_text(face = "bold"),
        axis.text.x = element_text(size = 10),
        axis.ticks.x = element_blank())


  • payment_plan_days - length of membership plan in days. Not very informative variable, because in most cases it equals 30 days, it is often positive for cancellations and when summed with transaction date does not give membership expiration date. We do not plan to use it.
plt_df <- trans_df %>%
  mutate(payment_plan_days = fct_infreq(factor(payment_plan_days))) %>%
  group_by(payment_plan_days) %>%
  tally() %>%
  ungroup() %>%
  mutate(pct = n/sum(n)*100,
         lbl = sprintf("%.0f%%", pct)) %>% 
  arrange(-n)

trans_df %>%
 ggplot() +
  aes(x = fct_infreq(factor(payment_plan_days))) +
  geom_bar(fill = "#2D559E") +
  geom_text(aes(x = payment_plan_days, y = n - 250, label = lbl),
            plt_df %>% filter(pct > 5), angle = 90, hjust = 1, vjust = 0.5,
            color = "white", fontface = "bold") +
  geom_text(aes(x = payment_plan_days, y = n, label = lbl),
            plt_df %>% filter(pct > 3 & pct < 80), angle = 90, hjust = 0, vjust = 0.5,
            color = "grey20", fontface = "bold") +
  labs(title = "Payment plan days", y = "", x = "Unique values of payment plan days") +
  scale_y_continuous(expand = c(0, 0), breaks = seq(0,175000,25000),
                     labels = c(0, paste0(seq(25,175,25), "K")),
                     limits = c(0, 175000)) +
  ggthemes::theme_tufte() +
  theme(plot.title = element_text(face = "bold"),
        axis.text.x = element_text(size = 10),
        axis.ticks.x = element_blank())

– For cancellation transactions there are only 3 available values of payment plan days: 0, 30 and 31.


  • plan_list_price and actual_amount_paid - both have 39 unique values. For 6.3% of all transactions these values differ. This share is a bit higher for cancellations: 12.9% versus 6.1%.
ggplot(trans_df %>% mutate(is_cancel = factor(is_cancel, labels = c("is_cancel=0", "is_cancel=1")))) +
  aes(x = plan_list_price, y = actual_amount_paid) +
  geom_point(aes(color = is_cancel), shape = "circle", size = 2, alpha = 0.5) +
  scale_color_brewer(palette = "Set1", direction = -1) +
  labs(x = "Plan list price", y = "Actual amount paid") +
  facet_rep_wrap(~ is_cancel, scales = "free") +
  theme_minimal() +
  theme(legend.position = "none")


3.1.1 Churn definition


We will use a definition of churn given by organizers. They define it as a membership expiration such that during the next 30 days it is not reactivated. So the key variable for revealing churns is a period between a membership expiration date given for this particular transaction and the date of the next transaction.

Our approach to churn definition will be based on the following assumptions:

  • As all data for transactions were extracted on March 31, 2017, for all users who had the last transaction before that day we will suppose that there were no more transactions till March 31, 2017.

  • For each user the date of his/her first available transaction in the dataset will be considered as the start of his/her subscription (membership), because we have no earlier data for transactions for those users who registered at KKBox before 2015. In other words, in the analysis of churn we will consider data as if this subscription service was opened at 2015-01-01. Although, in fact, it would be correct to say that for users who registered at the service before 2015 (54.2% of all users), the first time period of their membership should be considered as left censored, because we have no information about it actual date of beginning. But survival models with interval (both left and right) censoring are quite complicated, especially for machine learning (i.e. prediction), that is why for this particular task we decided to follow this simplification.


Starting from the first transaction for each user we can calculate time between membership expiration date in every transaction and the next transaction date (or if there is no further transactions for this user - till March 31, 2017) - time_bw_exp_nexttrans.

– There are negative values. Most likely, these are cases when user changes his/her subscription plan (including cancellation, which is always marked as auto-renewals) before the expiration date given in the previous subscription. Usually it happens in auto-renewed transactions and 1-6 days prior to expiration of the previous plan.

– For the most of consecutive transactions the date of expiration for the first of them coincide with transacton date for the second one, or the latter goes 1 day prior or later than the former.


According to the time between expiration date and the date of the next transaction (or 2017-03-31) we will place a churn indicator for each transaction (i.e. whether a user churned after the end of membership expiration listed in this transaction or not) as follows:

  • If this time exceeds 30 days, then we will consider this user as churned at this membership expiration date (churn = 1).

  • If this time is 30 days or less, then we can say that during the time from this till the next transaction (or till 2017-03-31 for the last user’s transaction) this user did not churn (churn = 0).


Scheme for logic of churn labelling:

# churn
trans_df <- trans_df %>%
  mutate(churn = as.numeric(time_bw_exp_nexttrans > 30))

subscr_periods <- function(churn_seq) {
  cumsum(ifelse(is.na(lag(churn_seq)), 1, lag(churn_seq) == 1))
}

# sequences of churn indicators by msno
trans_periods <- trans_df %>%
  group_by(msno) %>% 
  group_modify(~ subscr_periods(.x$churn) %>%
                 tibble::enframe(name = "trans_id",
                                 value = "msno_subscr_id"))

# joining each transaction with id for sequence of churn indicators
trans_df <- trans_df %>%
  left_join(trans_periods, by = c("msno", "trans_id"))

trans_df <- trans_df %>%
  mutate(subscr_id = group_indices(trans_df, msno, msno_subscr_id)) 

mode <- function(x){
  if (sum(is.na(x)) == length(x)) {
    NA
  } else {
    which.max(tabulate(x))
  }
}

# combining transactions in one sequence into periods
trans_periods <- trans_df %>%
  group_by(msno) %>%
  mutate(time_from_first_trans = as.numeric(transaction_date - transaction_date[1], 'days'),
         time_from_prev_trans = replace_na(as.numeric(transaction_date - lag(transaction_date), 'days'), 0),
         time_from_prev_exp = replace_na(as.numeric(transaction_date - lag(membership_expire_date_cor), 'days'), 0)) %>%
  ungroup() %>%
  group_by(subscr_id) %>%
  summarise(msno = unique(msno),
            msno_subscr_id = unique(msno_subscr_id),
            churn = max(churn),
            start_date = transaction_date[1],
            end_date = pmin(as.Date("2017-03-31"), membership_expire_date_cor[n()]),
            n_trans = n(),
            n_cancel = sum(is_cancel),
            num_auto_renew = sum(is_auto_renew),
            first_payment_method_id = payment_method_id[1],
            first_actual_amount_paid = actual_amount_paid[1],
            first_plan_list_price = plan_list_price[1],
            mode_payment_method_id = mode(as.numeric(as.character(payment_method_id))),
            sum_actual_amount_paid = sum(actual_amount_paid),
            churned_before = as.numeric(msno_subscr_id > 1),
            churned_before_num = msno_subscr_id - 1,
            time_from_first_trans = time_from_first_trans[1],
            time_from_prev_trans = time_from_prev_trans[1],
            time_from_prev_exp = time_from_prev_exp[1]) %>%
  ungroup() %>%
  mutate(time = as.numeric(end_date - start_date, 'days') + 1)

trans_periods <- trans_periods %>%
  mutate(mode_payment_method_id = fct_lump_min(factor(mode_payment_method_id, as.numeric(levels(trans_df$payment_method_id))),
                                               min = 30, other_level = "999"),
         first_payment_method_id = fct_lump_min(factor(first_payment_method_id, as.numeric(levels(trans_df$payment_method_id))),
                                                min = 30, other_level = "999"))

members <- members %>%
  left_join(trans_df %>%
              group_by(msno) %>%
              summarise(transaction_date_first = min(transaction_date),
                        transaction_date_last = max(transaction_date),
                        membership_expire_date_last = min(membership_expire_date_cor[n()], as.Date("2017-03-31")),
                        churned_num = sum(churn == 1),
                        churned_last = as.numeric(churn[n()] == 1),
                        churned_ever = as.numeric(churned_num > 0),
                        time_obs = as.numeric(membership_expire_date_last - transaction_date_first, 'days'),
                        n_trans_tot = n()),
            by = "msno") %>%
  left_join(trans_periods %>% group_by(msno) %>% summarise(n_subscr = n()) %>% ungroup(),
            by = "msno") %>%
  mutate(time_tot = as.numeric(membership_expire_date_last - registration_init_time, 'days') + 1)

Out of all transactions 7.4% were indicated as ended with a user’s churn.

Out of all unique users in our data set 60.2% had at least one churn. 44% of all unique users had churn after their last transaction, 16.2 had at least one churn but then they resubscribed to the service.


plt_df <- members %>%
  group_by(churned_num) %>%
  tally() %>%
  ungroup() %>%
  mutate(pct = n/sum(n)*100,
         lbl = sprintf("%.0f%%", pct),
         lbl = ifelse(lbl == "0%", "<1%", lbl))

p1 <- members %>%
 ggplot() +
  aes(x = churned_num) +
  geom_bar(fill = "#2D559E") +
  geom_text(aes(x = churned_num, y = n - 500, label = lbl), 
            plt_df %>% filter(pct > 2.5), 
            color = "white", fontface = "bold") +
  geom_text(aes(x = churned_num, y = n + 500, label = lbl), 
            plt_df %>% filter(pct < 2.5), 
            color = "grey20", fontface = "bold") +
  labs(title = "Number of churns by one user", y = "", x = "") +
  scale_y_continuous(expand = c(0, 0), breaks = seq(0,10000,2000),
                     labels = c(0, paste0(seq(2,10,2), "K")),
                     limits = c(0, 10000)) +
  scale_x_continuous(expand = c(0,0), breaks = plt_df$churned_num) +
  ggthemes::theme_tufte() +
  theme(plot.title = element_text(face = "bold"),
        axis.text.x = element_text(size = 12),
        axis.ticks.x = element_blank())

plt_df <- members %>%
  group_by(n_subscr) %>%
  tally() %>%
  ungroup() %>%
  mutate(pct = n/sum(n)*100,
         lbl = sprintf("%.0f%%", pct),
         lbl = ifelse(lbl == "0%", "<1%", lbl))

p2 <- members %>%
 ggplot() +
  aes(x = n_subscr) +
  geom_bar(fill = "#2D559E") +
  geom_text(aes(x = n_subscr, y = n - 750, label = lbl), 
            plt_df %>% filter(pct > 3.5), 
            color = "white", fontface = "bold") +
  geom_text(aes(x = n_subscr, y = n + 750, label = lbl), 
            plt_df %>% filter(pct <= 3.5), 
            color = "grey20", fontface = "bold") +
  labs(title = "Number of subscription periods by user", y = "", x = "") +
  scale_y_continuous(expand = c(0, 0), breaks = seq(0,15000,2500),
                     labels = c(0, paste0(seq(2.5,15,2.5), "K")),
                     limits = c(0, 15000)) +
  scale_x_continuous(expand = c(0,0), breaks = plt_df$n_subscr) +
  ggthemes::theme_tufte() +
  theme(plot.title = element_text(face = "bold"),
        axis.text.x = element_text(size = 12),
        axis.ticks.x = element_blank())

ggarrange(p2, p1, nrow = 1, ncol = 2)


Therefore, for all transactions of each user we obtained the sequence of churn indicators (0 or 1). Then we will combine them into periods of uninterrupted membership, i.e. periods inside of which there were no churns (see the Scheme above):

  • If churn is zero for several consecutive transactions of one user, then we can combine the corresponding membership intervals into one, including the last period in this sequence if it has a churn status of 1. If the sequence ends with zero churn (it is possible only when the last user’s transaction has zero churn status) then overall churn for combined intervals will be 0, and time will be a period from the date of the first transaction in this sequence till the minimum of 2017-03-31 and membership expiration date in the last transaction.

  • If the sequence of zeroes ends with 1 or sequence consists of only one transaction with positive churn status (which is possible if this is the first user’s transaction or a transaction preceded by another one with churn = 1), then overall churn for the sequence will be 1, and time to churn will be a period from the date of the first transaction in the sequence till the membership expiration date for the last transaction in the sequence.

In further analysis we will consider this periods of membership by one user as independent from each other, so a user who returned to the service after the churn will be considered as a new client, except the fact that we will know all information about his previous churns, transactions and other behaviour. Such approach is justified by the definition of churn used in KKBox.

Thus, instead of 19200 unique users we will use data on 25353 periods of subscription. Out of them 14603 (57.6%) ended with a churn, the rest are censored.


3.1.2 Descriptive survival analysis


Kaplan-Meier survival curve and spline-estimated hazard curve for KKBox’s users are presented below:

srvfit <- survfit(Surv(time, churn) ~ 1, trans_periods)
med_surv <- surv_median(srvfit)
srvhaz <- bshazard(Surv(time, churn) ~ 1, trans_periods, verbose = FALSE)

p1 <- ggsurvplot(srvfit, size = 1.2, palette = "#006666", conf.int = FALSE,
           ggtheme = theme_minimal() + theme(plot.title = element_text(face = "bold")),
           title = "Probability of active membership (= survival probability)",
           xlab = "Time from the first transaction, months",
           ylab = "Probability of active membership",
           legend = "none", censor = FALSE)

p1 <- p1$plot +
  scale_x_continuous(expand = c(0, 0), breaks = seq(0, 820, 30.4375*2),
                     labels = seq(0, 820/30.4375, 2),
                     limits = c(0, 820)) +
  scale_y_continuous(expand = c(0, 0), breaks = seq(0,1,0.2),
                     labels = scales::percent_format(accuracy = 1)) +
  geom_text(x = 800, y = 0.9, 
            label = sprintf("Median membership time\n(= median survival time):\n%.d days (%.0f months)", med_surv$median, med_surv$median/30.4375),
            hjust = 1, vjust = 1) +
  theme_classic() +
  theme(legend.position = "none",
        plot.title = element_text(face = "bold"),
        panel.grid.major = element_line(size = .2, colour = "grey90"),
        panel.grid.minor = element_line(size = .2, colour = "grey90"))

p2 <- ggplot() +
  geom_line(aes(x = srvhaz$time, y = srvhaz$hazard), color = "#D82632", size = 1.2) +
  scale_x_continuous(expand = c(0, 0), breaks = seq(0, 820, 30.4375*2),
                     labels = seq(0, 820/30.4375, 2), limits = c(0,820)) +
  scale_y_continuous(expand = c(0,0), limits = c(0, 0.0125)) +
  labs(x = "Time from the first transaction, months", y = "Hazard rate", 
       title = "Churn hazard rate") +
  theme_classic() +
  theme(plot.title = element_text(face = "bold"),
        panel.grid.major = element_line(size = .2, colour = "grey90"),
        panel.grid.minor = element_line(size = .2, colour = "grey90"))

ggarrange(p1, p2, nrow = 2, ncol = 1)

– As we can see, the hazard behaviour is closer to the type of early churn when the churn rate is high and sharply falling for the first ~ 2-3 months after subscription initiation, then it stabilizes for some time, have a little peak between 12 and 14 months, and then gradually decreasing.

– There is a noticable drop in active membership at 1 month after subscription - it could be because of users who subscribe just to try a service or maybe there are some trial subscription plans at KKBox.


trans_periods <- trans_periods %>%
  mutate(churned_before_numG = factor(churned_before_num, 0:6,
                                      labels = c(0:3, "4-6", "4-6", "4-6")))

srvfit <- survfit(Surv(time, churn) ~ churned_before_numG, trans_periods)
                  
p1 <- ggsurvplot(srvfit, 
                 palette = paletteer_d("ggsci::light_blue_material")[seq(2,10,2)], 
                 size = 1.2, conf.int = FALSE, 
                 legend.labs = levels(trans_periods$churned_before_numG),
                 legend.title = "",
                 ggtheme = theme_minimal() + theme(plot.title = element_text(face = "bold")),
                 title = "Probability of active membership by\nnumber of previous churns",
                 xlab = "Time from the first transaction, months",
                 ylab = "Probability of active membership",
                 legend = "bottom", censor = FALSE)

p1 <- p1$plot +
  scale_x_continuous(expand = c(0, 0), breaks = seq(0, 820, 30.4375*2),
                     labels = seq(0, 820/30.4375, 2),
                     limits = c(0, 820)) +
  scale_y_continuous(expand = c(0, 0), breaks = seq(0,1,0.2),
                     labels = scales::percent_format(accuracy = 1)) +
  theme_classic() +
  theme(legend.position = c(0.9,0.8),
        legend.background = element_blank(),
        plot.title = element_text(face = "bold"),
        panel.grid.major = element_line(size = .2, colour = "grey90"),
        panel.grid.minor = element_line(size = .2, colour = "grey90"))

plt_df <- NULL

for (lvl in levels(trans_periods$churned_before_numG)) {
  srvhaz <- bshazard(Surv(time, churn) ~ 1, 
                     trans_periods %>% filter(churned_before_numG == lvl), 
                     verbose = FALSE)
  plt_df <- rbind(plt_df,
                  tibble(x = srvhaz$time,
                         y = srvhaz$hazard,
                         lvl = lvl))
}

p2 <- ggplot() +
  geom_line(aes(x = x, y = y, color = lvl), plt_df, size = 1.2) +
  scale_x_continuous(expand = c(0, 0), breaks = seq(0, 820, 30.4375*2),
                     labels = seq(0, 820/30.4375, 2), limits = c(0,820)) +
  scale_y_continuous(expand = c(0,0), limits = c(0, 0.0125)) +
  scale_color_manual(values = paletteer_d("ggsci::light_blue_material")[seq(2,10,2)]) +
  labs(x = "Time from the first transaction, months", y = "Hazard rate", 
       title = "Churn hazard rate by\nnumber of previous churns", color = "") +
  theme_classic() +
  theme(legend.position = c(0.9,0.8),
        legend.background = element_blank(),
        plot.title = element_text(face = "bold"),
        panel.grid.major = element_line(size = .2, colour = "grey90"),
        panel.grid.minor = element_line(size = .2, colour = "grey90"))

ggarrange(p1, p2, nrow = 2, ncol = 1)

– The odd increase in hazard rate between 12 and 14 months is mainly peculiar for users without previous churns. Maybe, it is somehow explained by the characteristics of their subscription plans.

– Hazard rate for users with higher number of churns falls more gradually in the beginning of subscription period, but for all groups we have a huge slump in active membership at 1 months after subscription, and this slump is more considerable with users with more prevous churns.


trans_periods <- trans_periods %>%
  mutate(year_period = factor(year(start_date)),
         month_period = factor(month(start_date), labels = month.abb))

plt_df <- trans_periods %>%
  group_by(year_period) %>%
  tally() %>%
  ungroup() %>%
  mutate(pct = n/sum(n)*100,
         lbl = sprintf("%.1f%%", pct))

trans_periods %>%
 ggplot() +
  aes(x = year_period) +
  geom_bar(aes(fill = year_period)) +
  geom_text(aes(x = year_period, y = n - 500, label = lbl), 
            plt_df %>% filter(year_period != "2015"), 
            color = "white", fontface = "bold") +
  geom_text(aes(x = year_period, y = n - 500, label = lbl), 
            plt_df %>% filter(year_period == "2015"), 
            color = "grey20", fontface = "bold") +
  labs(title = "Year of subscription", y = "", x = "") +
  scale_y_continuous(expand = c(0, 0), breaks = seq(0, 14000, 2000),
                     labels = c(0, paste0(seq(2,14,2), "K")),
                     limits = c(0, 14000)) +
  scale_fill_manual(values = paletteer_c("ggthemes::Blue", 3)) +
  ggthemes::theme_tufte() +
  theme(legend.position = "none",
        plot.title = element_text(face = "bold"),
        axis.text.x = element_text(size = 12),
        axis.ticks.x = element_blank())

srvfit <- survfit(Surv(time, churn) ~ year_period, trans_periods)
                  
p1 <- ggsurvplot(srvfit, 
                 palette = paletteer_c("ggthemes::Blue", 3), 
                 size = 1.2, conf.int = FALSE,
                 legend.labs = levels(trans_periods$year_period),
                 legend.title = "",
                 ggtheme = theme_minimal() + theme(plot.title = element_text(face = "bold")),
                 title = "Probability of active membership by\nyear of subscription",
                 xlab = "Time from the first transaction, months",
                 ylab = "Probability of active membership",
                 legend = "bottom", censor = FALSE)

p1 <- p1$plot +
  scale_x_continuous(expand = c(0, 0), breaks = seq(0, 820, 30.4375*2),
                     labels = seq(0, 820/30.4375, 2),
                     limits = c(0, 820)) +
  scale_y_continuous(expand = c(0, 0), breaks = seq(0,1,0.2),
                     labels = scales::percent_format(accuracy = 1)) +
  theme_classic() +
  theme(legend.position = c(0.9, 0.8),
        legend.title = element_blank(),
        plot.title = element_text(face = "bold"),
        panel.grid.major = element_line(size = .2, colour = "grey90"),
        panel.grid.minor = element_line(size = .2, colour = "grey90"))

p1

– The more considerable attrition during the fisr month of subscription is detected for subscriptions started in 2016, the least one - for 2017 (although in the latter case there were censored observations at 30 and 31 days after the start of subscription - if we knew their real status (observed them for longer period) we could obtain a different form of survival curve for this group of membership periods.


plt_df <- trans_periods %>%
  group_by(month_period) %>%
  tally() %>%
  ungroup() %>%
  mutate(pct = n/sum(n)*100,
         lbl = sprintf("%.1f%%", pct))

trans_periods %>%
 ggplot() +
  aes(x = month_period) +
  geom_bar(aes(fill = month_period)) +
  geom_text(aes(x = month_period, y = n - 250, label = lbl), 
            plt_df, 
            color = "white", fontface = "bold") +
  labs(title = "Month of membership start", y = "", x = "") +
  scale_y_continuous(expand = c(0, 0), breaks = seq(0, 6000, 2000),
                     labels = c(0, paste0(seq(2,6,2), "K")),
                     limits = c(0, 6350)) +
  scale_fill_manual(values = paletteer_c("grDevices::Set 2", 12)) +
  ggthemes::theme_tufte() +
  theme(legend.position = "none",
        plot.title = element_text(face = "bold"),
        axis.text.x = element_text(size = 12),
        axis.ticks.x = element_blank())

– High prevalence of subscription started in January can be partially a result of the fact that transaction dataset starts from January, 2015, so for users who registered at the service before that and have regular transactions, their first one in the data will be in this month.

srvfit <- survfit(Surv(time, churn) ~ month_period, trans_periods)
                  
p1 <- ggsurvplot(srvfit, 
                 palette = paletteer_c("grDevices::Set 2", 12), 
                 size = 1.2, conf.int = FALSE,
                 legend.labs = levels(trans_periods$month_period),
                 legend.title = "",
                 ggtheme = theme_minimal() + theme(plot.title = element_text(face = "bold")),
                 title = "Probability of active membership by\nmonth of subscription",
                 xlab = "Time from the first transaction, months",
                 ylab = "Probability of active membership",
                 legend = "bottom", censor = FALSE)

p1 <- p1$plot +
  scale_x_continuous(expand = c(0, 0), breaks = seq(0, 820, 30.4375*2),
                     labels = seq(0, 820/30.4375, 2),
                     limits = c(0, 820)) +
  scale_y_continuous(expand = c(0, 0), breaks = seq(0,1,0.2),
                     labels = scales::percent_format(accuracy = 1)) +
  guides(color = guide_legend(nrow = 6)) +
  theme_classic() +
  theme(legend.position = c(0.9, 0.8),
        legend.title = element_blank(),
        legend.background = element_blank(),
        plot.title = element_text(face = "bold"),
        panel.grid.major = element_line(size = .2, colour = "grey90"),
        panel.grid.minor = element_line(size = .2, colour = "grey90"))

p1

– Subscriptions started in November and June (one of the most popular months for starting a subscription, after January) have a higher risk of being interrupted during the first month, those started in January and July - the less one.


3.2 Members’ characteristics


Members file contains the following information:


  • city - there is 21 unique numeric codes for cities.
plt_df <- members %>%
  group_by(city) %>%
  tally() %>%
  ungroup() %>%
  mutate(pct = n/sum(n)*100,
         lbl = sprintf("%.0f%%", pct),
         lbl = ifelse(lbl == "0%", "<1%", lbl))

members %>%
 ggplot() +
  aes(x = city) +
  geom_bar(aes(fill = city)) +
  geom_text(aes(x = city, y = n - 250, label = lbl), 
            plt_df %>% filter(pct > 2), 
            color = "white", fontface = "bold") +
  geom_text(aes(x = city, y = n + 300, label = lbl), 
            plt_df %>% filter(pct < 2), 
            color = "grey20", fontface = "bold") +
  labs(title = "City", y = "Number of unique users", x = "") +
  scale_y_continuous(expand = c(0, 0), breaks = seq(0,10000,2000),
                     labels = c(0, paste0(seq(2,10,2), "K"))) +
  scale_fill_manual(values = c(paletteer_d("ggthemes::Tableau_10")[1:8],
                               rep("grey50", length(levels(members$city))-8))) +
  ggthemes::theme_tufte() +
  theme(legend.position = "none",
        plot.title = element_text(face = "bold"),
        axis.text.x = element_text(size = 12),
        axis.ticks.x = element_blank())

trans_periods <- trans_periods %>%
  left_join(members %>% select(msno, city, bd, gender, registered_via, registration_init_time,
                               year_reg, month_reg), by = "msno")

trans_periods <- trans_periods %>%
  mutate(time_from_reg = as.numeric(start_date - registration_init_time, 'days'),
         cityG = fct_lump_n(city, 8, other_level = "Other"))

srvfit <- survfit(Surv(time, churn) ~ cityG, trans_periods)
                  
p1 <- ggsurvplot(srvfit, 
                 palette = paletteer_d("ggthemes::Tableau_10")[1:length(levels(trans_periods$cityG))], 
                 size = 1.2, conf.int = FALSE, 
                 legend.labs = levels(trans_periods$cityG),
                 legend.title = "",
                 ggtheme = theme_minimal() + theme(plot.title = element_text(face = "bold")),
                 title = "Probability of active membership by user's city",
                 xlab = "Time from the first transaction, months",
                 ylab = "Probability of active membership",
                 legend = "bottom", censor = FALSE)

p1 <- p1$plot +
  scale_x_continuous(expand = c(0, 0), breaks = seq(0, 820, 30.4375*2),
                     labels = seq(0, 820/30.4375, 2),
                     limits = c(0, 820)) +
  scale_y_continuous(expand = c(0, 0), breaks = seq(0,1,0.2),
                     labels = scales::percent_format(accuracy = 1)) +
  theme_classic() +
  theme(legend.position = "right",
        legend.background = element_blank(),
        plot.title = element_text(face = "bold"),
        panel.grid.major = element_line(size = .2, colour = "grey90"),
        panel.grid.minor = element_line(size = .2, colour = "grey90"))

p1

– There is no visible difference in patterns of survival probability between users from different cities, except a little bit more profound drop in memership during the first month of subscription for users from city 1 (the most populous in the dataset).


  • bd - age. It is an integer variable with somewhat strange distribution. I suppose users fill age or birth date field at the registration and may give an untruthful or error value here. Also it was not defined for which particular moment the age was calculated. Otherwise, I doubt it is a useful variable itself.
members <- members %>%
  mutate(bd_group = cut(bd, c(min(members$bd)-1, 10, 61, max(members$bd)+1), 
                        c("< 10", "10-60", "> 60"), right = FALSE))

var_label(members) <- list(bd = "Age", bd_group = "Age group")

tbl_summary(
  as.data.frame(select(members, bd, bd_group)),
  type = all_continuous() ~ "continuous2",
  statistic = list(all_continuous() ~ c("{mean} ({sd})", "{median} ({p25}-{p75})",  
                                        "{min}-{max}")),
  digits = list(all_continuous() ~ c(rep(1, 2), rep(0, 5)))) %>%
  bold_labels()
members <- members %>%
  mutate(bd_group = cut(bd, c(min(members$bd)-1, 10, 61, max(members$bd)+1), 
                        c("< 10", "10-60", "> 60"), right = FALSE))

var_label(members) <- list(bd = "Age", bd_group = "Age group")

tbl_summary(
  as.data.frame(select(members, bd, bd_group)),
  type = all_continuous() ~ "continuous2",
  statistic = list(all_continuous() ~ c("{mean} ({sd})", "{median} ({p25}-{p75})",  
                                        "{min}-{max}")),
  digits = list(all_continuous() ~ c(rep(1, 2), rep(0, 5)))) %>%
  bold_labels()
members %>%
 filter(bd_group == "10-60") %>%
 ggplot() +
  aes(x = bd) +
  geom_histogram(binwidth = 2, fill = "#4682B4") +
  labs(x = "Age", y = "Number of unique users", title = "Age (for age in [10, 60])") +
  scale_x_continuous(breaks = seq(10,70,10)) +
  scale_y_continuous(expand = c(0,0), breaks = seq(0,1000,250),
                     limits = c(0, 1000)) +
  ggthemes::theme_tufte() +
  theme(plot.title = element_text(face = "bold"),
        axis.text.x = element_text(size = 12))
Characteristic N = 19200
Age
Mean (SD) 14.8 (17.7)
Median (25%-75%) 0 (0-28)
Minimum-Maximum 0-940
Age group
< 10 9691 (50%)
10-60 9437 (49%)
> 60 72 (0.4%)
trans_periods <- trans_periods %>%
  mutate(bdG = cut(bd, c(min(members$bd)-1, 10, max(members$bd)+1), 
                   c("< 10", ">= 10"), right = FALSE))

srvfit <- survfit(Surv(time, churn) ~ bdG, trans_periods)
                  
p1 <- ggsurvplot(srvfit, 
                 palette = paletteer_d("ggthemes::Jewel_Bright")[1:length(levels(trans_periods$cityG))], 
                 size = 1.2, conf.int = FALSE, 
                 legend.labs = levels(trans_periods$bdG),
                 legend.title = "",
                 ggtheme = theme_minimal() + theme(plot.title = element_text(face = "bold")),
                 title = "Probability of active membership by user's age",
                 xlab = "Time from the first transaction, months",
                 ylab = "Probability of active membership",
                 legend = "bottom", censor = FALSE)

p1 <- p1$plot +
  scale_x_continuous(expand = c(0, 0), breaks = seq(0, 820, 30.4375*2),
                     labels = seq(0, 820/30.4375, 2),
                     limits = c(0, 820)) +
  scale_y_continuous(expand = c(0, 0), breaks = seq(0,1,0.2),
                     labels = scales::percent_format(accuracy = 1)) +
  theme_classic() +
  theme(legend.position = c(0.9, 0.8),
        legend.title = element_blank(),
        legend.background = element_blank(),
        plot.title = element_text(face = "bold"),
        panel.grid.major = element_line(size = .2, colour = "grey90"),
        panel.grid.minor = element_line(size = .2, colour = "grey90"))

p1


  • gender
plt_df <- members %>%
  group_by(gender) %>%
  tally() %>%
  ungroup() %>%
  mutate(pct = n/sum(n)*100,
         lbl = sprintf("%.1f%%", pct))

ggplot(members) +
  aes(x = gender, fill = gender) +
  geom_bar(width = 0.6) +
  geom_text(aes(x = gender, y = n - 500, label = lbl), plt_df, 
            color = "white", fontface = "bold") +
  scale_fill_manual(values = paletteer_d("ggthemes::Superfishel_Stone")[c(3,1,5)]) +
  scale_y_continuous(expand = c(0,0), breaks = seq(0,10000,2000),
                     limits = c(0, 10000), labels = c(0, paste0(seq(2,10,2), "K"))) +
  ggthemes::theme_tufte() +
  labs(x = "", y = "Number of unique users", title = "Gender") +
  theme(legend.position = "none") +
  theme(plot.title = element_text(face = "bold"),
        axis.text.x = element_text(size = 12),
        axis.ticks.x = element_blank())

srvfit <- survfit(Surv(time, churn) ~ gender, trans_periods)
                  
p1 <- ggsurvplot(srvfit, 
                 palette = paletteer_d("ggthemes::Superfishel_Stone")[c(3,1,5)], 
                 size = 1.2, conf.int = FALSE,
                 legend.labs = levels(trans_periods$gender),
                 legend.title = "",
                 ggtheme = theme_minimal() + theme(plot.title = element_text(face = "bold")),
                 title = "Probability of active membership by user's gender",
                 xlab = "Time from the first transaction, months",
                 ylab = "Probability of active membership",
                 legend = "bottom", censor = FALSE)

p1 <- p1$plot +
  scale_x_continuous(expand = c(0, 0), breaks = seq(0, 820, 30.4375*2),
                     labels = seq(0, 820/30.4375, 2),
                     limits = c(0, 820)) +
  scale_y_continuous(expand = c(0, 0), breaks = seq(0,1,0.2),
                     labels = scales::percent_format(accuracy = 1)) +
  theme_classic() +
  theme(legend.position = c(0.9, 0.8),
        legend.title = element_blank(),
        legend.background = element_blank(),
        plot.title = element_text(face = "bold"),
        panel.grid.major = element_line(size = .2, colour = "grey90"),
        panel.grid.minor = element_line(size = .2, colour = "grey90"))

p1

– There are some strange differences between users with known (males and females) and unknown gender.


  • registered_via - registration method. There are 5 unique numeric codes for this variable without any explanations from organizers.
plt_df <- members %>%
  group_by(registered_via) %>%
  tally() %>%
  ungroup() %>%
  mutate(pct = n/sum(n)*100,
         lbl = sprintf("%.1f%%", pct))

members %>%
 ggplot() +
  aes(x = registered_via) +
  geom_bar(aes(fill = registered_via)) +
  geom_text(aes(x = registered_via, y = n - 200, label = lbl), 
            plt_df %>% filter(pct > 1), 
            color = "white", fontface = "bold") +
  geom_text(aes(x = registered_via, y = n + 200, label = lbl), 
            plt_df %>% filter(pct < 1), 
            color = "grey20", fontface = "bold") +
  labs(title = "Registration method", y = "Number of unique users", x = "") +
  scale_y_continuous(expand = c(0, 0), breaks = seq(0, 6000, 1000),
                     labels = c(0, paste0(seq(1,6,1), "K"))) +
  scale_fill_manual(values = paletteer_d("ggthemes::Tableau_10")[1:5]) +
  ggthemes::theme_tufte() +
  theme(legend.position = "none",
        plot.title = element_text(face = "bold"),
        axis.text.x = element_text(size = 12),
        axis.ticks.x = element_blank())

srvfit <- survfit(Surv(time, churn) ~ registered_via, trans_periods)
                  
p1 <- ggsurvplot(srvfit, 
                 palette = paletteer_d("ggthemes::Tableau_10")[1:5], 
                 size = 1.2, conf.int = FALSE, 
                 legend.labs = levels(trans_periods$registered_via),
                 legend.title = "",
                 ggtheme = theme_minimal() + theme(plot.title = element_text(face = "bold")),
                 title = "Probability of active membership by\nregistration method",
                 xlab = "Time from the first transaction, months",
                 ylab = "Probability of active membership",
                 legend = "bottom", censor = FALSE)

p1 <- p1$plot +
  scale_x_continuous(expand = c(0, 0), breaks = seq(0, 820, 30.4375*2),
                     labels = seq(0, 820/30.4375, 2),
                     limits = c(0, 820)) +
  scale_y_continuous(expand = c(0, 0), breaks = seq(0,1,0.2),
                     labels = scales::percent_format(accuracy = 1)) +
  theme_classic() +
  theme(legend.position = c(0.9, 0.8),
        legend.title = element_blank(),
        legend.background = element_blank(),
        plot.title = element_text(face = "bold"),
        panel.grid.major = element_line(size = .2, colour = "grey90"),
        panel.grid.minor = element_line(size = .2, colour = "grey90"))

p1

– There are differences in survival between users with different registration method, although I would say that patterns are similar: a significant drop in membership during and at the end of the first month of subscription followed by gradual decrease. For users registered via 3rd, 4th and 9th methods there is also a drop in subscriptions between 13 and 14 months.


  • registration_init_time - date of initial registration for each client, ranges from 2004-03-26 to 2017-02-28.
reg_days <- members %>%
  mutate(registration_init_time = floor_date(registration_init_time, "month")) %>%
  group_by(registration_init_time) %>%
  tally()

ggplot(reg_days) +
  aes(x = registration_init_time, y = n) +
  geom_line(size = 0.5, colour = "#112446") +
  scale_x_date(date_breaks = "1 year", date_labels =  "%Y") +
  # scale_y_continuous(limits = c(3000, 6000),
  #                    labels = c(paste0(seq(3,6,1), "K"))) +
  labs(title = "Number of registrations by month", x = "", y = "") +
  ggthemes::theme_tufte() +
  theme(plot.title = element_text(face = "bold"),
        axis.text.x = element_text(size = 12, hjust = 0.5, vjust = 0.5))

– There is a noticable trend to decreasing in the number of monthly registrations at KKBox service since 2016. Of course, we do not know for how many users information was deleted from the data by KKBox, but if this number did not depend on the year of registration, then we can suppose that we have here a representation of the monthly dynamics of KKBox popularity.

– Since 2011 there are peaks in registrations at the beginning of each year.


plt_df <- members %>%
  group_by(year_reg) %>%
  tally() %>%
  ungroup() %>%
  mutate(pct = n/sum(n)*100,
         lbl = sprintf("%.1f%%", pct))

members %>%
 ggplot() +
  aes(x = year_reg) +
  geom_bar(aes(fill = year_reg)) +
  geom_text(aes(x = year_reg, y = n - 200, label = lbl), 
            plt_df %>% filter(pct > 1.9), 
            color = "white", fontface = "bold") +
  geom_text(aes(x = year_reg, y = n + 200, label = lbl), 
            plt_df %>% filter(pct < 1.9), 
            color = "grey20", fontface = "bold") +
  labs(title = "Year of registration at the service", y = "Number of unique users", x = "") +
  scale_y_continuous(expand = c(0, 0), breaks = seq(0, 6000, 1000),
                     labels = c(0, paste0(seq(1,6,1), "K"))) +
  scale_fill_manual(values = paletteer_c("ggthemes::Blue", 14)) +
  ggthemes::theme_tufte() +
  theme(legend.position = "none",
        plot.title = element_text(face = "bold"),
        axis.text.x = element_text(size = 12),
        axis.ticks.x = element_blank())

trans_periods <- trans_periods %>%
  mutate(year_regG = fct_collapse(year_reg, 
                                  "2004-2009" = as.character(2004:2009),
                                  "2010-2011" = as.character(2010:2011),
                                  "2012-2013" = as.character(2012:2013)))

srvfit <- survfit(Surv(time, churn) ~ year_regG, trans_periods)
                  
p1 <- ggsurvplot(srvfit, 
                 palette = paletteer_c("ggthemes::Blue", length(levels(trans_periods$year_regG))), 
                 size = 1.2, conf.int = FALSE,
                 legend.labs = levels(trans_periods$year_regG),
                 legend.title = "",
                 ggtheme = theme_minimal() + theme(plot.title = element_text(face = "bold")),
                 title = "Probability of active membership by\nuser's year of registration",
                 xlab = "Time from the first transaction, months",
                 ylab = "Probability of active membership",
                 legend = "bottom", censor = FALSE)

p1 <- p1$plot +
  scale_x_continuous(expand = c(0, 0), breaks = seq(0, 820, 30.4375*2),
                     labels = seq(0, 820/30.4375, 2),
                     limits = c(0, 820)) +
  scale_y_continuous(expand = c(0, 0), breaks = seq(0,1,0.2),
                     labels = scales::percent_format(accuracy = 1)) +
  theme_classic() +
  theme(legend.position = c(0.9, 0.8),
        legend.title = element_blank(),
        legend.background = element_blank(),
        plot.title = element_text(face = "bold"),
        panel.grid.major = element_line(size = .2, colour = "grey90"),
        panel.grid.minor = element_line(size = .2, colour = "grey90"))

p1

– In comparison with users registered in 2010-2013 new ones have a more considerable drop in active membership durng the first month.


trans_periods <- trans_periods %>%
  mutate(time_from_regG = cut(time_from_reg, c(0,0.1, 31, 366, 366*3, 365*5, 366*15),
                              c("0", "1-30 days", "31 days-1year", "1-3 years", "3-5 years", ">5 years"),
                              right = FALSE))

var_label(trans_periods) <- list(time_from_reg = "Time from user's registration at the service to subscription start, days",
                                 time_from_regG = "Time from user's registration at the service to subscription start")

tbl_summary(
  as.data.frame(select(trans_periods, time_from_reg, time_from_regG)),
  type = all_continuous() ~ "continuous2",
  statistic = list(all_continuous() ~ c("{mean} ({sd})", "{median} ({p25}-{p75})",  
                                        "{min}-{max}")),
  digits = list(all_continuous() ~ c(rep(1, 2), rep(0, 5)))) %>%
  bold_labels()
Characteristic N = 25353
Time from user's registration at the service to subscription start, days
Mean (SD) 878.4 (1024.1)
Median (25%-75%) 502 (37-1326)
Minimum-Maximum 0-4610
Time from user's registration at the service to subscription start
0 4404 (17%)
1-30 days 1794 (7.1%)
31 days-1year 5007 (20%)
1-3 years 6436 (25%)
3-5 years 3685 (15%)
>5 years 4027 (16%)
srvfit <- survfit(Surv(time, churn) ~ time_from_regG, trans_periods)
                  
p1 <- ggsurvplot(srvfit, 
                 palette = paletteer_c("ggthemes::Brown", length(levels(trans_periods$time_from_regG))), 
                 size = 1.2, conf.int = FALSE,
                 legend.labs = levels(trans_periods$time_from_regG),
                 legend.title = "",
                 ggtheme = theme_minimal() + theme(plot.title = element_text(face = "bold")),
                 title = "Probability of active membership by\ntime from registration",
                 xlab = "Time from the first transaction, months",
                 ylab = "Probability of active membership",
                 legend = "bottom", censor = FALSE)

p1 <- p1$plot +
  scale_x_continuous(expand = c(0, 0), breaks = seq(0, 820, 30.4375*2),
                     labels = seq(0, 820/30.4375, 2),
                     limits = c(0, 820)) +
  scale_y_continuous(expand = c(0, 0), breaks = seq(0,1,0.2),
                     labels = scales::percent_format(accuracy = 1)) +
  guides(color = guide_legend(nrow = 6)) +
  theme_classic() +
  theme(legend.position = c(0.9, 0.85),
        legend.title = element_blank(),
        legend.background = element_blank(),
        plot.title = element_text(face = "bold"),
        panel.grid.major = element_line(size = .2, colour = "grey90"),
        panel.grid.minor = element_line(size = .2, colour = "grey90"))

p1

– Initial subscriptions (those started at the day of user’s registration) tend to have least risk of attrition, while those which started between 1 month and 1 year from registration - the highest one.


plt_df <- members %>%
  group_by(month_reg) %>%
  tally() %>%
  ungroup() %>%
  mutate(pct = n/sum(n)*100,
         lbl = sprintf("%.1f%%", pct))

members %>%
 ggplot() +
  aes(x = month_reg) +
  geom_bar(aes(fill = month_reg)) +
  geom_text(aes(x = month_reg, y = n - 100, label = lbl), 
            plt_df %>% filter(pct > 1.9), 
            color = "white", fontface = "bold") +
  geom_text(aes(x = month_reg, y = n + 200, label = lbl), 
            plt_df %>% filter(pct < 1.9), 
            color = "grey20", fontface = "bold") +
  labs(title = "Month of registration at the service", y = "Number of unique users", x = "") +
  scale_y_continuous(expand = c(0, 0), breaks = seq(0, 6000, 500),
                     limits = c(0, 2000)) +
  scale_fill_manual(values = paletteer_c("grDevices::Set 2", 12)) +
  ggthemes::theme_tufte() +
  theme(legend.position = "none",
        plot.title = element_text(face = "bold"),
        axis.text.x = element_text(size = 12),
        axis.ticks.x = element_blank())

srvfit <- survfit(Surv(time, churn) ~ month_reg, trans_periods)
                  
p1 <- ggsurvplot(srvfit, 
                 palette = paletteer_c("grDevices::Set 2", 12), 
                 size = 1.2, conf.int = FALSE,
                 legend.labs = levels(trans_periods$month_reg),
                 legend.title = "",
                 ggtheme = theme_minimal() + theme(plot.title = element_text(face = "bold")),
                 title = "Probability of active membership by\nuser's month of registration",
                 xlab = "Time from the first transaction, months",
                 ylab = "Probability of active membership",
                 legend = "bottom", censor = FALSE)

p1 <- p1$plot +
  scale_x_continuous(expand = c(0, 0), breaks = seq(0, 820, 30.4375*2),
                     labels = seq(0, 820/30.4375, 2),
                     limits = c(0, 820)) +
  scale_y_continuous(expand = c(0, 0), breaks = seq(0,1,0.2),
                     labels = scales::percent_format(accuracy = 1)) +
  guides(color = guide_legend(nrow = 6)) +
  theme_classic() +
  theme(legend.position = c(0.9, 0.8),
        legend.title = element_blank(),
        legend.background = element_blank(),
        plot.title = element_text(face = "bold"),
        panel.grid.major = element_line(size = .2, colour = "grey90"),
        panel.grid.minor = element_line(size = .2, colour = "grey90"))

p1

– I would not say that there are considerable differences in pattern of survival curve depending on the month of registration.


3.3 User logs


User log files contain 3959349 records on daily listening behaviors of sampled users from 2015-01-01 to 2017-03-31.


ulogs_days <- ulogs %>%
  group_by(date) %>%
  tally()

var_label(ulogs_days) <- list(n = "Number of records per day in user logs")

tbl_summary(
  as.data.frame(select(ulogs_days, n)),
  type = all_continuous() ~ "continuous2",
  statistic = list(all_continuous() ~ c("{mean} ({sd})", "{median} ({p25}-{p75})",  
                                        "{min}-{max}")),
  digits = list(all_continuous() ~ c(rep(1, 2), rep(0, 5)))) %>%
  bold_labels()
Characteristic N = 821
Number of records per day in user logs
Mean (SD) 4822.6 (658.4)
Median (25%-75%) 4811 (4188-5384)
Minimum-Maximum 3413-5988


ggplot(ulogs_days) +
  aes(x = date, y = n) +
  geom_line(size = 0.5, colour = "#112446") +
  scale_x_date(date_breaks = "1 month", date_labels =  "%Y-%m", 
               limits = as.Date(c('2015-01-01','2017-03-31'))) +
  scale_y_continuous(limits = c(3000, 6000),
                     labels = c(paste0(seq(3,6,1), "K"))) +
  labs(title = "Number of records per day in user logs", x = "", y = "") +
  ggthemes::theme_tufte() +
  theme(plot.title = element_text(face = "bold"),
        axis.text.x = element_text(size = 10, angle = 90, hjust = 0.5, vjust = 0.5))


trans_periods_wide <- trans_periods %>%
  select(msno, msno_subscr_id, start_date, end_date) %>%
  as.data.frame() %>%
  reshape(v.names = c("start_date", "end_date"), timevar = "msno_subscr_id", 
          idvar = "msno", direction = "wide")

ulogs <- ulogs %>%
  left_join(trans_periods_wide, by = "msno")

ulogs <- ulogs %>%
  mutate(msno_subscr_period = case_when(date < start_date.1 ~ 0,
                                        date <= end_date.1 ~ 1,
                                        date < start_date.2 ~ 1.5,
                                        date <= end_date.2 ~ 2,
                                        date < start_date.3 ~ 2.5,
                                        date <= end_date.3 ~ 3,
                                        date < start_date.4 ~ 3.5,
                                        date <= end_date.4 ~ 4,
                                        date < start_date.5 ~ 4.5,
                                        date <= end_date.5 ~ 5,
                                        date < start_date.6 ~ 5.5,
                                        date <= end_date.6 ~ 6,
                                        date < start_date.7 ~ 6.5,
                                        date <= end_date.7 ~ 7,
                                        TRUE ~ 7.5))

ulogs <- ulogs %>%
  mutate(in_subscr = msno_subscr_period %in% 1:7)

ulogs_periods <- ulogs %>%
  filter(in_subscr) %>%
  rename(msno_subscr_id = msno_subscr_period) %>%
  left_join(trans_periods %>% select(msno, msno_subscr_id, subscr_id, start_date, end_date, churn, time,
                                     city, cityG, bd, bdG, gender, registered_via,
                                     registration_init_time, year_reg, year_regG, month_reg,
                                     churned_before, churned_before_num), 
            by = c("msno", "msno_subscr_id"))

trans_periods <- trans_periods %>%
  mutate(subscr_got_logs = subscr_id %in% ulogs_periods$subscr_id)

members <- members %>%
  left_join(trans_periods %>% 
              select(msno, subscr_got_logs) %>%
              group_by(msno) %>%
              summarise(n_subscr_logs = sum(subscr_got_logs),
                        n_subscr_nologs = sum(!subscr_got_logs)) %>%
              ungroup(),
            by = "msno") %>%
  mutate(all_subscr_got_logs = n_subscr_logs == n_subscr)

Statistics above concerning user logs was estimated on all records in user logs file on the sampled 19200 users, but after that for each log we identified to which of the previously constructed membership periods of the corresponding user they belong. There were 5.6% records which were dated earlier than the start of the user’s first subscription period, 0.4% which went after the expiration date for the user’s last subscription period, and 140.7% that fell between user’s subscription periods.

Taking into account the study nature of this project, our assumption that active membership began at the date of the first subscription for each user, as well as all examples that confirm the dirtiness of the data, and relatively small amount of logs in between subscriptions, we have decided to omit all records outside the identified subscription periods.

As a result, 3592849 records remained. They contain data on 18144 unique users (94.5% out of initially sampled) and 23536 unique subscription periods (92.8% of all identified periods). For the rest users and periods we will assume that they were without logging into the service any time.


ulogs_stat <- ulogs %>%
  group_by(msno) %>%
  summarise(n = sum(in_subscr))

var_label(ulogs_stat) <- list(n = "Number of records in user logs per ID")

tbl_summary(
  as.data.frame(select(ulogs_stat, n)),
  type = all_continuous() ~ "continuous2",
  statistic = list(all_continuous() ~ c("{mean} ({sd})", "{median} ({p25}-{p75})",  
                                        "{min}-{max}")),
  digits = list(all_continuous() ~ c(rep(1, 2), rep(0, 5)))) %>%
  bold_labels()
Characteristic N = 19200
Number of records in user logs per ID
Mean (SD) 187.1 (205.0)
Median (25%-75%) 106 (12-315)
Minimum-Maximum 0-807


All statistics on user logs below will concern only records remained after exclusion of records outside of users’ subscription periods.


– For 78.3% of all users’ successive records in user logs the break between them was 1 day.

ulogs_breaks <- ulogs_periods %>%
  filter(!is.na(time_from_prev_date)) %>%
  group_by(msno) %>%
  summarise(mean_break = mean(time_from_prev_date),
            median_break = median(time_from_prev_date)) %>%
  ungroup()

var_label(ulogs_breaks) <- list(mean_break = "Mean break between logs for one ID, days",
                                median_break = "Median break between logs for one ID, days")

tbl_summary(
  as.data.frame(select(ulogs_breaks, mean_break, median_break)),
  type = all_continuous() ~ "continuous2",
  statistic = list(all_continuous() ~ c("{mean} ({sd})", "{median} ({p25}-{p75})",  
                                        "{min}-{max}")),
  digits = list(all_continuous() ~ c(rep(1, 2), rep(0, 5)))) %>%
  bold_labels()
Characteristic N = 17022
Mean break between logs for one ID, days
Mean (SD) 4.5 (15.3)
Median (25%-75%) 1.8 (1.3-3.2)
Minimum-Maximum 1.0-522.0
Median break between logs for one ID, days
Mean (SD) 2.5 (13.4)
Median (25%-75%) 1.0 (1.0-1.0)
Minimum-Maximum 1.0-522.0


  • num_25, num_50, num_75, num_985, num_100 - number of songs played less than 25% of the song length, between 25% to 50%, between 50% to 75%, between 75% to 98.5% and over 98.5% of the song length, correspondingly.

    I’ve summed up all these numbers to obtain the total number of songs listened by a client - num_tot. There are no records with zero values - it confirms that logs were written only for active users.

    In addition, I’ve estimated the share of each songs’ length group in the total number of songs listened (sh_25, sh_50, sh_75, sh_985, sh_100) as \(\text{sh_X} = \text{num_x}/\text{num_tot}\) and average % of one song’s length played (av_pct_played) as \[\text{av_pct_played} = \frac{\text{num_25}*0.125 + \text{num_50}*0.375 + \text{num_75}*0.625 + \\\text{num_985}*0.8675 + \text{num_100}*0.9925}{\text{num_tot}}*100%\]


– Here is an example of several users’ activity during their subscription periods by the total number of songs played:

msno_sample_act <- members %>%
  select(msno, n_subscr, n_subscr_logs) %>%
  group_by(n_subscr, n_subscr_logs) %>%
  filter(row_number() == 1)

msno_sample_act <- trans_periods %>%
  select(msno, subscr_id, start_date, end_date) %>%
  filter(msno %in% msno_sample_act$msno) %>%
  left_join(ulogs_periods %>% select(subscr_id, date, num_tot, churn), by = "subscr_id") %>%
  mutate(msno_id = factor(msno, unique(msno_sample_act$msno), 1:length(unique(msno_sample_act$msno))))

ggplot() +
  geom_segment(aes(x = date, xend = date + 1, y = msno_id, yend = msno_id, color = num_tot), 
               msno_sample_act %>% filter(!is.na(num_tot)), size = 8) +
  scale_color_gradient(low = brewer.pal(9, "Reds")[1], high = brewer.pal(9, "Reds")[9],
                       breaks = seq(0, 250, 50), 
                       labels = seq(0, 250, 50)) +
  geom_point(aes(x = start_date, y = msno_id, shape = "Start of subscr."), msno_sample_act,
             color = "#35978F", size = 1) +
  geom_point(aes(x = end_date, y = msno_id, shape = "End of subscr."), msno_sample_act,
             color = "#FDAE61", size = 1) +
  scale_shape_manual(values = c(19, 15), breaks = c("Start of subscr.", "End of subscr.")) +
  scale_x_date(date_breaks = "1 month", date_labels =  "%Y-%m",
               expand = c(0.01,0)) +
  labs(x = "", y = "User", color = "Tot.num.of songs", title = "Sampled users' daily activity", shape = "") +
  guides(shape = guide_legend(override.aes = list(color = c("#35978F", "#FDAE61"),
                                                  size = 3))) +
  ggthemes::theme_tufte() +
  theme(plot.title = element_text(face = "bold"),
        axis.text.x = element_text(size = 9, angle = 90, hjust = 0.5, vjust = 0.5))


  • num_unq - number of unique songs played.

    I’ve divided the total number of songs by the number of unique songs played to obtain the average number of times of listening to one song (av_times_played). In addition, I’ve calculated the difference between the total number of songs and the number of unique songs played to obtain the number of repeats of the same songs (num_repeats).


p1 <- ulogs_periods %>%
  filter(num_25 < quantile(ulogs_periods$num_25, 0.99)) %>%
  ggplot() +
  aes(x = num_25) +
  geom_histogram(binwidth = 2, fill = "#4682B4") +
  labs(x = "", y = "", title = paste0(var_label(ulogs_periods)$num_25, "\n(observations with values less than 99th percentile)")) +
  scale_x_continuous(breaks = seq(0, 60, 10)) +
  scale_y_continuous(expand = c(0,0), breaks = seq(0, 1500000, 300000),
                     labels = c(0, paste0(seq(300,1500,300), "K"))) +
  ggthemes::theme_tufte() +
  theme(plot.title = element_text(face = "bold"),
        axis.text.x = element_text(size = 12))

p2 <- ulogs_periods %>%
  filter(num_50 < quantile(ulogs_periods$num_50, 0.99)) %>%
  ggplot() +
  aes(x = num_50) +
  geom_histogram(binwidth = 1, fill = "#4682B4") +
  labs(x = "", y = "", title = paste0(var_label(ulogs_periods)$num_50, "\n(observations with values less than 99th percentile)")) +
  scale_x_continuous(breaks = seq(0, 16, 1)) +
  scale_y_continuous(expand = c(0,0), breaks = seq(0,1600000,400000),
                     labels = c(0, paste0(seq(400,1600,400), "K"))) +
  ggthemes::theme_tufte() +
  theme(plot.title = element_text(face = "bold"),
        axis.text.x = element_text(size = 12))

p2a <- ulogs_periods %>%
  filter(num_75 < quantile(ulogs_periods$num_75, 0.99)) %>%
  ggplot() +
  aes(x = num_75) +
  geom_histogram(binwidth = 1, fill = "#4682B4") +
  labs(x = "", y = "", title = paste0(var_label(ulogs_periods)$num_75, "\n(observations with values less than 99th percentile)")) +
  scale_x_continuous(breaks = seq(0, 16, 1)) +
  scale_y_continuous(expand = c(0,0), breaks = seq(0,2000000,500000),
                     labels = c(0, paste0(seq(500,2000,500), "K"))) +
  ggthemes::theme_tufte() +
  theme(plot.title = element_text(face = "bold"),
        axis.text.x = element_text(size = 12))

p3 <- 
ulogs_periods %>%
  filter(num_985 < quantile(ulogs_periods$num_985, 0.99)) %>%
  ggplot() +
  aes(x = num_985) +
  geom_histogram(binwidth = 1, fill = "#4682B4") +
  labs(x = "", y = "", title = paste0(var_label(ulogs_periods)$num_985, "\n(observations with values less than 99th percentile)")) +
  scale_x_continuous(breaks = seq(0, 16, 1)) +
  scale_y_continuous(expand = c(0,0), breaks = seq(0,2000000,500000),
                     labels = c(0, paste0(seq(500,2000,500), "K")),
                     limits = c(0, 2000000)) +
  ggthemes::theme_tufte() +
  theme(plot.title = element_text(face = "bold"),
        axis.text.x = element_text(size = 12))

p4 <- ulogs_periods %>%
  filter(num_100 < quantile(ulogs_periods$num_100, 0.99)) %>%
  ggplot() +
  aes(x = num_100) +
  geom_histogram(binwidth = 5, fill = "#4682B4") +
  labs(x = "", y = "", title = paste0(var_label(ulogs_periods)$num_100, "\n(observations with values less than 99th percentile)")) +
  scale_x_continuous(breaks = seq(0, 180, 20)) +
  scale_y_continuous(expand = c(0,0), breaks = seq(0,1400000,300000),
                     labels = c(0, paste0(seq(300,1400,300), "K"))) +
  ggthemes::theme_tufte() +
  theme(plot.title = element_text(face = "bold"),
        axis.text.x = element_text(size = 12))

p4a <- ulogs_periods %>%
  ggplot() +
  aes(x = av_pct_played) +
  geom_histogram(binwidth = 1, fill = "#4682B4") +
  labs(x = "", y = "", title = var_label(ulogs_periods)$av_pct_played) +
  scale_x_continuous(breaks = seq(0, 100, 20),
                     limits = c(0, 100)) +
  scale_y_continuous(expand = c(0,0), breaks = seq(0,600000,200000),
                     labels = c(0, paste0(seq(200,600,200), "K"))) +
  ggthemes::theme_tufte() +
  theme(plot.title = element_text(face = "bold"),
        axis.text.x = element_text(size = 12))

p5 <- ulogs_periods %>%
  filter(num_tot < quantile(ulogs_periods$num_tot, 0.99)) %>%
  ggplot() +
  aes(x = num_tot) +
  geom_histogram(binwidth = 5, boundary = 0, fill = "#4682B4") +
  labs(x = "", y = "", title = paste0(var_label(ulogs_periods)$num_tot, "\n(observations with values less than 99th percentile)")) +
  scale_x_continuous(breaks = seq(0,200,20)) +
  scale_y_continuous(expand = c(0,0), breaks = seq(0,500000,100000),
                     labels = c(0, paste0(seq(100,500,100), "K")),
                     limits = c(0, 500000)) +
  ggthemes::theme_tufte() +
  theme(plot.title = element_text(face = "bold"),
        axis.text.x = element_text(size = 12))

p6 <- ulogs_periods %>%
  filter(num_unq < quantile(ulogs_periods$num_unq, 0.99)) %>%
  ggplot() +
  aes(x = num_unq) +
  geom_histogram(binwidth = 5, boundary = 0, fill = "#4682B4") +
  labs(x = "", y = "", title = paste0(var_label(ulogs_periods)$num_unq, "\n(observations with values less than 99th percentile)")) +
  scale_x_continuous(breaks = seq(0,160,20)) +
  scale_y_continuous(expand = c(0,0), breaks = seq(0,1000000,200000),
                     labels = c(0, paste0(seq(200,1000,200), "K"))) +
  ggthemes::theme_tufte() +
  theme(plot.title = element_text(face = "bold"),
        axis.text.x = element_text(size = 12))

p7 <- ulogs_periods %>%
  filter(num_repeats < quantile(ulogs_periods$num_repeats, 0.99)) %>%
  ggplot() +
  aes(x = num_repeats) +
  geom_histogram(binwidth = 5, fill = "#4682B4") +
  labs(x = "", y = "", title = paste0(var_label(ulogs_periods)$num_repeats, "\n(observations with values less than 99th percentile)")) +
  scale_x_continuous(breaks = seq(0,120,20)) +
  scale_y_continuous(expand = c(0,0), breaks = seq(0,2000000,500000),
                     labels = c(0, paste0(seq(500,2000,500), "K")),
                     limits = c(0, 2000000)) +
  ggthemes::theme_tufte() +
  theme(plot.title = element_text(face = "bold"),
        axis.text.x = element_text(size = 12))

p8 <- ulogs_periods %>%
  filter(av_times_played < quantile(ulogs_periods$av_times_played, 0.99)) %>%
  ggplot() +
  aes(x = av_times_played) +
  geom_histogram(binwidth = 1, boundary = 0, fill = "#4682B4") +
  labs(x = "", y = "", title = paste0(var_label(ulogs_periods)$av_times_played, "\n(observations with values less than 99th percentile)")) +
  scale_x_continuous(breaks = seq(1,13,1)) +
  scale_y_continuous(expand = c(0,0), breaks = seq(0,3000000,1000000),
                     labels = c(0, paste0(seq(1,3,1), "KK"))) +
  ggthemes::theme_tufte() +
  theme(plot.title = element_text(face = "bold"),
        axis.text.x = element_text(size = 12))

ggarrange(p1, p2, p2a, p3, p4, p4a, p5, p6, p7, p8, ncol = 2, nrow = 5)


ggplot(ulogs_periods) + 
  stat_ecdf(aes(x = sh_100, color = "98.5-100%"), size = 1) + 
  stat_ecdf(aes(x = sh_985, color = "75-98.5%"), size = 1) + 
  stat_ecdf(aes(x = sh_75, color = "50-75%"), size = 1) + 
  stat_ecdf(aes(x = sh_50, color = "25-50%"), size = 1) + 
  stat_ecdf(aes(x = sh_25, color = "0-25%"), size = 1) +
  scale_color_viridis_d(end = 0.9, direction = -1) + 
  scale_x_continuous(expand = c(0.01,0),
                     breaks = seq(0, 1, 0.2),
                     labels = c(0, paste0("<= ", seq(0.2,1,0.2))),
                     sec.axis = sec_axis(trans = ~ .,
                                         name = "For 1-CDF: share of this length songs out of the total number of songs played",
                                         breaks = seq(0, 1, 0.2),
                                         labels = c(paste0(">= ", seq(0,0.8,0.2)), 1))) +
  scale_y_continuous(expand = c(0.01, 0), 
                     breaks = seq(0, 1, 0.2),
                     labels = scales::percent_format(accuracy = 1),
                     sec.axis = sec_axis(trans = ~ 1 - .,
                                         name = "1-CDF",
                                         breaks = seq(0, 1, 0.2),
                                         labels = scales::percent_format(accuracy = 1))) +
  labs(x = "For CDF: share of this length songs out of the total number of songs played",
       y = "CDF", color = "% of song length played",
       title = "Cumulative distribution function for shares of\nthe number of songs played by length") +
  ggthemes::theme_tufte() +
  theme(plot.title = element_text(face = "bold"),
        panel.grid.major = element_line(color = "grey90", size = 0.2),
        panel.grid.minor = element_line(color = "grey90", size = 0.2))

– There are almost no records, when during a day a user has not played any song in the full length. About 15% of records contains only completely played songs. For about 80% of records at least half of songs were listened completely, for 50% of records - at least 8 out of 10.

– Intermediate categories (num_50, num_75 and num_985) are quite rare and for more than 95% of records their share is less than 30% out of the total number of songs played that day.


ggplot(ulogs_periods, aes(y = av_pct_played)) +
  ggdist::stat_halfeye(adjust = 0.4, justification = -0.07, .width = 0, 
                       point_colour = NA, alpha = 0.8,
                       fill = "#4682B4") +
  geom_boxplot(width = 0.1, alpha = 0.5, outlier.colour = NA,
               show.legend = FALSE, fill = "#4682B4", color = "#4682B4") +
  coord_flip() +
  scale_y_continuous(breaks = seq(10,100,10),
                     expand = c(0.01,0),
                     limits = c(10, 100)) +
  scale_x_continuous(breaks = seq(0.06, 1.06, 0.2),
                     labels = seq(0,1,0.2),
                     expand = c(0.01, 0),
                     limits = c(-0.1, 1.1)) +
  labs(y = var_label(ulogs_periods)$av_pct_played, x = "Density",
       title = "Average % of one song's length played") +
  ggthemes::theme_tufte()


ulogs_periods <- ulogs_periods %>%
  mutate(total_secs_na = case_when(total_secs < 0 | total_secs > 60*60*24 ~ NA_real_, 
                                   TRUE ~ total_secs),
         total_hrs_na = total_secs_na/3600,
         av_secs_real_na = total_secs / (num_25/0.125 + num_50/0.375 + num_75/0.625 + num_985/0.8675 + num_100/0.9925))
  • total_secs - total seconds played. This column contains 561 records with negative values - they were replaced by missings. The same was done to 1254 records with values higher than 86400 seconds (24 hours).

    To impute these missings I will use the following approach: I’ve calculated the average real length of one song listened by each user (av_length_real) with the following formula: \[\text{Average real length} = \frac{\text{total_secs}}{\frac{\text{num_25}}{0.125}+\frac{\text{num_50}}{0.375}+\frac{\text{num_75}}{0.625}+\frac{\text{num_985}}{0.8675}+\frac{\text{num_100}}{0.9925}}\]

    Median average real length of one song in the whole dataset is about 114 seconds (median_length). Using it I’ve calculated the imputed value of the total seconds played (total_secs_imp) for observations with missing values in the following way: \[\text{total_secs_imp} = min(24*60*60, \text{median_length}*\text{sh_tot}) = \\= min(24*60*60, \text{median_length}*(\text{num_25}*0.125 + \text{num_50}*0.375 + \\\text{num_75}*0.625 + \text{num_985}*0.8675 + \text{num_100}*0.9925))\]


ulogs_periods <- ulogs_periods %>%
  mutate(total_secs_imp = ifelse(is.na(total_secs_na),
                                 min(round(median(ulogs_periods$av_secs_real_na, na.rm = TRUE),0)*av_pct_played/100,86400),
                                 total_secs_na),
         total_hrs_imp = total_secs_imp/3600,
         av_secs_real = total_secs_imp / (num_25/0.125 + num_50/0.375 + num_75/0.625 + num_985/0.8675 + num_100/0.9925),
         av_secs_played = total_secs_imp / num_tot,
         av_mins_real = av_secs_real/60,
         av_mins_played = av_secs_played/60)

var_label(ulogs_periods) <- list(total_secs_na = "Total seconds played",
                                 total_secs_imp = "Total seconds played (imputed)",
                                 total_hrs_na = "Total hours played",
                                 total_hrs_imp = "Total hours played (imputed)",
                                 av_secs_real = "Average real length of a song, seconds",
                                 av_secs_played = "Average time spent on listening to one song, seconds",
                                 av_mins_real = "Average real length of a song, minutes",
                                 av_mins_played = "Average time spent on listening to one song, minutes")

tbl_summary(
  as.data.frame(select(ulogs_periods, av_mins_played, total_hrs_na, total_hrs_imp)),
  type = all_continuous() ~ "continuous2",
  statistic = list(all_continuous() ~ c("{mean} ({sd})", "{median} ({p25}-{p75})",  
                                        "{min}-{max}")),
  digits = list(all_continuous() ~ c(rep(1, 7)))) %>%
  bold_labels()
Characteristic N = 3592849
Average time spent on listening to one song, minutes
Mean (SD) 3.3 (1.1)
Median (25%-75%) 3.4 (2.7-3.9)
Minimum-Maximum 0.0-575.2
Total hours played
Mean (SD) 2.3 (2.7)
Median (25%-75%) 1.3 (0.6-2.9)
Minimum-Maximum 0.0-24.0
Unknown 1815
Total hours played (imputed)
Mean (SD) 2.3 (2.7)
Median (25%-75%) 1.3 (0.6-2.9)
Minimum-Maximum 0.0-24.0


p1 <- ulogs_periods %>%
  filter(av_mins_played < quantile(ulogs_periods$av_mins_played, 0.99, na.rm = TRUE)) %>%
  ggplot() +
  aes(x = av_mins_played) +
  geom_histogram(binwidth = 1, boundary = 0, fill = "#4682B4") +
  labs(x = "Minutes", y = "", title = paste0(var_label(ulogs_periods)$av_mins_played, "\n(observations with values less than 99th percentile)")) +
  scale_x_continuous(breaks = seq(0, 5, 1)) +
  scale_y_continuous(expand = c(0,0), breaks = seq(0,1500000,500000), 
                     labels = c(0, paste0(seq(500,1500,500), "K"))) +
  ggthemes::theme_tufte() +
  theme(plot.title = element_text(face = "bold"),
        axis.text.x = element_text(size = 12))

p2 <- ulogs_periods %>%
  filter(total_hrs_imp < quantile(ulogs_periods$total_hrs_imp, 0.99, na.rm = TRUE)) %>%
  ggplot() +
  aes(x = total_hrs_imp) +
  geom_histogram(binwidth = 1, boundary = 0, fill = "#4682B4") +
  labs(x = "Hours", y = "", title = paste0(var_label(ulogs_periods)$total_hrs_imp, "\n(observations with values less than 99th percentile)")) +
  scale_x_continuous(breaks = seq(0, 12, 1)) +
  scale_y_continuous(expand = c(0,0), breaks = seq(0,1500000,500000), 
                     labels = c(0, paste0(seq(500,1500,500), "K"))) +
  ggthemes::theme_tufte() +
  theme(plot.title = element_text(face = "bold"),
        axis.text.x = element_text(size = 12))

ggarrange(p1,p2, nrow = 1, ncol = 2)


p1 <- dbplot_raster(ulogs_periods,
              x = num_tot, y = total_hrs_imp) +
  scale_y_continuous(expand = c(0,0), breaks = seq(0,24,3), limits = c(0,24)) +
  scale_x_continuous(expand = c(0,0), breaks = seq(0,6000,1000)) +
  scale_fill_viridis_c() +
  labs(x = "Total number of songs played", y = "Total hours played",
       title = "Total hours played vs. total numer of songs",
       fill = "Number of obs.") +
  ggthemes::theme_tufte() +
  theme(plot.title = element_text(face = "bold"),
        legend.key.width = unit(1,"cm"))

p1


In addition to above statistics we have estimated how much days out of the corresponding subscription periods a user logged into the service (was active) - days_active, and the share of active days in the full length of the subscription period (in %) - pct_active. We did the same for the last 30, 14 and 7 days of each subscription period.

ulogs_last <- ulogs_periods %>%
  group_by(subscr_id) %>%
  summarise(days_active = n(),
            mean_num_unq = sum(num_unq),
            mean_num_tot = sum(num_tot),
            mean_av_pct_played = sum(av_pct_played),
            mean_total_secs_imp = sum(total_secs_imp),
            mean_total_hrs_imp = sum(total_hrs_imp),
            mean_av_secs_played = sum(av_secs_played),
            mean_av_mins_played = sum(av_mins_played),
            mean_sh_25 = sum(sh_25),
            mean_sh_985 = sum(sh_985),
            mean_sh_100 = sum(sh_100),
            last30d = as.numeric(end_date - date, 'days') < 30,
            last14d = as.numeric(end_date - date, 'days') < 14,
            last7d = as.numeric(end_date - date, 'days') < 7,
            days_active30 = sum(last30d),
            mean_num_unq30 = sum(num_unq[last30d]),
            mean_num_tot30 = sum(num_tot[last30d]),
            mean_av_pct_played30 = sum(av_pct_played[last30d]),
            mean_total_secs_imp30 = sum(total_secs_imp[last30d]),
            mean_total_hrs_imp30 = sum(total_hrs_imp[last30d]),
            mean_av_secs_played30 = sum(av_secs_played[last30d]),
            mean_av_mins_played30 = sum(av_mins_played[last30d]),
            mean_sh_2530 = sum(sh_25[last30d]),
            mean_sh_98530 = sum(sh_985[last30d]),
            mean_sh_10030 = sum(sh_100[last30d]),
            days_active14 = sum(last14d),
            mean_num_unq14 = sum(num_unq[last14d]),
            mean_num_tot14 = sum(num_tot[last14d]),
            mean_av_pct_played14 = sum(av_pct_played[last14d]),
            mean_total_secs_imp14 = sum(total_secs_imp[last14d]),
            mean_total_hrs_imp14 = sum(total_hrs_imp[last14d]),
            mean_av_secs_played14 = sum(av_secs_played[last14d]),
            mean_av_mins_played14 = sum(av_mins_played[last14d]),
            mean_sh_2514 = sum(sh_25[last14d]),
            mean_sh_98514 = sum(sh_985[last14d]),
            mean_sh_10014 = sum(sh_100[last14d]),
            days_active7 = sum(last7d),
            mean_num_unq7 = sum(num_unq[last7d]),
            mean_num_tot7 = sum(num_tot[last7d]),
            mean_av_pct_played7 = sum(av_pct_played[last7d]),
            mean_total_secs_imp7 = sum(total_secs_imp[last7d]),
            mean_total_hrs_imp7 = sum(total_hrs_imp[last7d]),
            mean_av_secs_played7 = sum(av_secs_played[last7d]),
            mean_av_mins_played7 = sum(av_mins_played[last7d]),
            mean_sh_257 = sum(sh_25[last7d]),
            mean_sh_9857 = sum(sh_985[last7d]),
            mean_sh_1007 = sum(sh_100[last7d])) %>%
  select(-last30d, -last14d, -last7d) %>%
  distinct()

trans_periods <- trans_periods %>%
  left_join(ulogs_last, by = "subscr_id") 

trans_periods <- trans_periods %>%
  mutate(days_active = replace_na(days_active, 0),
         pct_active = replace_na(days_active / time * 100, 0),
         tot_num_tot = replace_na(mean_num_tot,0),
         tot_total_secs_imp = replace_na(mean_total_secs_imp,0),
         mean_sh_25_act = replace_na(mean_sh_25/days_active, 0),
         mean_sh_25 = replace_na(mean_sh_25/time, 0),
         mean_sh_985_act = replace_na(mean_sh_985/days_active, 0),
         mean_sh_985 = replace_na(mean_sh_985/time, 0),
         mean_sh_100_act = replace_na(mean_sh_100/days_active, 0),
         mean_sh_100 = replace_na(mean_sh_100/time, 0),
         mean_num_unq_act = replace_na(mean_num_unq/days_active, 0),
         mean_num_unq = replace_na(mean_num_unq/time, 0),
         mean_num_tot_act = replace_na(mean_num_tot/days_active, 0),
         mean_num_tot = replace_na(mean_num_tot/time, 0),
         mean_av_pct_played_act = replace_na(mean_av_pct_played/days_active, 0),
         mean_av_pct_played = replace_na(mean_av_pct_played/time, 0),
         mean_total_secs_imp_act = replace_na(mean_total_secs_imp/days_active, 0),
         mean_total_secs_imp = replace_na(mean_total_secs_imp/time, 0),
         mean_total_hrs_imp_act = replace_na(mean_total_hrs_imp/days_active, 0),
         mean_total_hrs_imp = replace_na(mean_total_hrs_imp/time, 0),
         mean_av_secs_played_act = replace_na(mean_av_secs_played/days_active, 0),
         mean_av_secs_played = replace_na(mean_av_secs_played/time, 0),
         mean_av_mins_played_act = replace_na(mean_av_mins_played/days_active, 0),
         mean_av_mins_played = replace_na(mean_av_mins_played/time, 0),
         days_active30 = replace_na(days_active30, 0),
         pct_active30 = replace_na(days_active30 / 30 * 100, 0),
         tot_num_tot30 = replace_na(mean_num_tot30,0),
         tot_total_secs_imp30 = replace_na(mean_total_secs_imp30, 0),
         mean_sh_25_act30 = replace_na(mean_sh_2530/days_active, 0),
         mean_sh_2530 = replace_na(mean_sh_2530/time, 0),
         mean_sh_985_act30 = replace_na(mean_sh_98530/days_active, 0),
         mean_sh_98530 = replace_na(mean_sh_98530/time, 0),
         mean_sh_100_act30 = replace_na(mean_sh_10030/days_active, 0),
         mean_sh_10030 = replace_na(mean_sh_10030/time, 0),
         mean_num_unq_act30 = replace_na(mean_num_unq30/days_active30, 0),
         mean_num_unq30 = replace_na(mean_num_unq30/30, 0),
         mean_num_tot_act30 = replace_na(mean_num_tot30/days_active30, 0),
         mean_num_tot30 = replace_na(mean_num_tot30/30, 0),
         mean_av_pct_played_act30 = replace_na(mean_av_pct_played30/days_active30, 0),
         mean_av_pct_played30 = replace_na(mean_av_pct_played30/30, 0),
         mean_total_secs_imp_act30 = replace_na(mean_total_secs_imp30/days_active30, 0),
         mean_total_secs_imp30 = replace_na(mean_total_secs_imp30/30, 0),
         mean_total_hrs_imp_act30 = replace_na(mean_total_hrs_imp30/days_active30, 0),
         mean_total_hrs_imp30 = replace_na(mean_total_hrs_imp30/30, 0),
         mean_av_secs_played_act30 = replace_na(mean_av_secs_played30/days_active30, 0),
         mean_av_secs_played30 = replace_na(mean_av_secs_played30/30, 0),
         mean_av_mins_played_act30 = replace_na(mean_av_mins_played30/days_active30, 0),
         mean_av_mins_played30 = replace_na(mean_av_mins_played30/30, 0),
         days_active14 = replace_na(days_active14, 0),
         pct_active14 = replace_na(days_active14 / 14 * 100, 0),
         tot_num_tot14 = replace_na(mean_num_tot14,0),
         tot_total_secs_imp14 = replace_na(mean_total_secs_imp14, 0),
         mean_sh_25_act14 = replace_na(mean_sh_2514/days_active, 0),
         mean_sh_2514 = replace_na(mean_sh_2514/time, 0),
         mean_sh_985_act14 = replace_na(mean_sh_98514/days_active, 0),
         mean_sh_98514 = replace_na(mean_sh_98514/time, 0),
         mean_sh_100_act14 = replace_na(mean_sh_10014/days_active, 0),
         mean_sh_10014 = replace_na(mean_sh_10014/time, 0),
         mean_num_unq_act14 = replace_na(mean_num_unq14/days_active14, 0),
         mean_num_unq14 = replace_na(mean_num_unq14/14, 0),
         mean_num_tot_act14 = replace_na(mean_num_tot14/days_active14, 0),
         mean_num_tot14 = replace_na(mean_num_tot14/14, 0),
         mean_av_pct_played_act14 = replace_na(mean_av_pct_played14/days_active14, 0),
         mean_av_pct_played14 = replace_na(mean_av_pct_played14/14, 0),
         mean_total_secs_imp_act14 = replace_na(mean_total_secs_imp14/days_active14, 0),
         mean_total_secs_imp14 = replace_na(mean_total_secs_imp14/14, 0),
         mean_total_hrs_imp_act14 = replace_na(mean_total_hrs_imp14/days_active14, 0),
         mean_total_hrs_imp14 = replace_na(mean_total_hrs_imp14/14, 0),
         mean_av_secs_played_act14 = replace_na(mean_av_secs_played14/days_active14, 0),
         mean_av_secs_played14 = replace_na(mean_av_secs_played14/14, 0),
         mean_av_mins_played_act14 = replace_na(mean_av_mins_played14/days_active14, 0),
         mean_av_mins_played14 = replace_na(mean_av_mins_played14/14, 0),
         days_active7 = replace_na(days_active7, 0),
         pct_active7 = replace_na(days_active7 / 7 * 100, 0),
         tot_num_tot7 = replace_na(mean_num_tot7,0),
         tot_total_secs_imp7 = replace_na(mean_total_secs_imp7, 0),
         mean_sh_25_act7 = replace_na(mean_sh_257/days_active, 0),
         mean_sh_257 = replace_na(mean_sh_257/time, 0),
         mean_sh_985_act7 = replace_na(mean_sh_9857/days_active, 0),
         mean_sh_9857 = replace_na(mean_sh_9857/time, 0),
         mean_sh_100_act7 = replace_na(mean_sh_1007/days_active, 0),
         mean_sh_1007 = replace_na(mean_sh_1007/time, 0),
         mean_num_unq_act7 = replace_na(mean_num_unq7/days_active7, 0),
         mean_num_unq7 = replace_na(mean_num_unq7/7, 0),
         mean_num_tot_act7 = replace_na(mean_num_tot7/days_active7, 0),
         mean_num_tot7 = replace_na(mean_num_tot7/7, 0),
         mean_av_pct_played_act7 = replace_na(mean_av_pct_played7/days_active7, 0),
         mean_av_pct_played7 = replace_na(mean_av_pct_played7/7, 0),
         mean_total_secs_imp_act7 = replace_na(mean_total_secs_imp7/days_active7, 0),
         mean_total_secs_imp7 = replace_na(mean_total_secs_imp7/7, 0),
         mean_total_hrs_imp_act7 = replace_na(mean_total_hrs_imp7/days_active7, 0),
         mean_total_hrs_imp7 = replace_na(mean_total_hrs_imp7/7, 0),
         mean_av_secs_played_act7 = replace_na(mean_av_secs_played7/days_active7, 0),
         mean_av_secs_played7 = replace_na(mean_av_secs_played7/7, 0),
         mean_av_mins_played_act7 = replace_na(mean_av_mins_played7/days_active7, 0),
         mean_av_mins_played7 = replace_na(mean_av_mins_played7/7, 0))

trans_lags <- trans_periods %>%
  arrange(msno, msno_subscr_id) %>%
  group_by(msno) %>%
  filter(n() > 1) %>%
  transmute(subscr_id = unique(subscr_id),
            
            lag_time = replace_na(lag(time), 0),
            prev_time = cumsum(replace_na(lag(time), 0)),
            
            lag_days_active = replace_na(lag(days_active), 0),
            prev_days_active = cumsum(replace_na(lag(days_active), 0)),
            
            lag_pct_active = replace_na(lag(pct_active), 0),
            prev_pct_active = replace_na(prev_days_active/prev_time, 0),
            
            lag_tot_num_tot = replace_na(lag(tot_num_tot), 0),
            prev_tot_num_tot = cumsum(replace_na(lag(tot_num_tot), 0)),
            
            lag_tot_total_secs_imp = replace_na(lag(tot_total_secs_imp), 0),
            prev_tot_total_secs_imp = cumsum(replace_na(lag(tot_total_secs_imp), 0)),
            
            lag_mean_num_tot = replace_na(lag_tot_num_tot/lag_time, 0),
            prev_mean_num_tot = replace_na(prev_tot_num_tot/prev_time, 0),
            
            lag_mean_num_tot_act = replace_na(lag_tot_num_tot/lag_days_active, 0),
            prev_mean_num_tot_act = replace_na(prev_tot_num_tot/prev_days_active, 0),
            
            lag_mean_total_secs_imp = replace_na(lag_tot_total_secs_imp/lag_time, 0),
            prev_mean_total_secs_imp = replace_na(prev_tot_total_secs_imp/prev_time, 0),
            
            lag_mean_total_secs_imp_act = replace_na(lag_tot_total_secs_imp/lag_days_active, 0),
            prev_mean_total_secs_imp_act = replace_na(prev_tot_total_secs_imp/prev_days_active, 0),
            
            lag_mean_av_pct_played = replace_na(lag(mean_av_pct_played), 0),
            prev_mean_av_pct_played = replace_na(cumsum(replace_na(lag(mean_av_pct_played), 0))/(n()-1)),
            
            lag_mean_av_pct_played_act = replace_na(lag(mean_av_pct_played_act), 0),
            prev_mean_av_pct_played_act = replace_na(cumsum(replace_na(lag(mean_av_pct_played_act), 0))/(n()-1)),
            
            lag_mean_num_unq = replace_na(lag(mean_num_unq), 0),
            prev_mean_num_unq = replace_na(cumsum(replace_na(lag(mean_num_unq), 0))/(n()-1)),
            
            lag_mean_num_unq_act = replace_na(lag(mean_num_unq_act), 0),
            prev_mean_num_unq_act = replace_na(cumsum(replace_na(lag(mean_num_unq_act), 0))/(n()-1)),
            
            lag_mode_payment_id = factor(replace_na(lag(as.numeric(as.character(mode_payment_method_id))), 0),
                                         c(0, as.numeric(levels(trans_df$payment_method_id)))),
            prev_mode_payment_id = factor(replace_na(mode(as.numeric(as.character(mode_payment_method_id[-n()]))), 0),
                                          c(0, as.numeric(levels(trans_df$payment_method_id)), 999)),
            
            lag_sum_actual_amount_paid = replace_na(lag(sum_actual_amount_paid), 0),
            prev_sum_actual_amount_paid = cumsum(replace_na(lag(sum_actual_amount_paid), 0)),
            
            lag_mean_actual_amount_paid = replace_na(lag_sum_actual_amount_paid/lag_time, 0),
            prev_mean_actual_amount_paid = replace_na(prev_sum_actual_amount_paid/prev_time, 0),
            
            lag_num_cancel = replace_na(lag(n_cancel), 0),
            prev_num_cancel = cumsum(replace_na(lag(n_cancel), 0))) %>%
  ungroup()

trans_periods <- trans_periods %>%
  left_join(trans_lags, by = c("msno", "subscr_id")) %>%
  mutate_at(vars(contains("lag_"), dplyr::starts_with("prev_")), ~ replace_na(., 0))

trans_periods <- trans_periods %>%
  mutate(lag_mode_payment_id = fct_lump_min(lag_mode_payment_id, min = 30, other_level = "999"),
         prev_mode_payment_id = fct_lump_min(lag_mode_payment_id, min = 30, other_level = "999"))
var_label(trans_periods) <- list(time = "Subscription duration, days",
                                 pct_active = "%of active days in subscription",
                                 pct_active30 = "%of active days in the last 30 days of subscription",
                                 pct_active14 = "%of active days in the last 14 days of subscription",
                                 pct_active7 = "%of active days in the last 7 days of subscription")

tbl_summary(
  as.data.frame(trans_periods %>%
                  select(churn, time, contains("pct_active")) %>%
                  mutate(churn = factor(churn, labels = c("Churn = 0", "Churn = 1")))),
  by = "churn", 
  type = list(all_continuous() ~ "continuous2",
              pct_active7 ~ "continuous2"),
  statistic = list(all_continuous() ~ c("{mean} ({sd})", "{median} ({p25}-{p75})",  
                                        "{min}-{max}")),
  digits = list(all_continuous() ~ c(rep(1, 5), rep(0, 2)),
                time ~ c(rep(1, 2), rep(0, 5)))) %>%
  modify_header(all_stat_cols() ~ "**{level}**<br>N = {n}") %>%
  add_overall(col_label = "**Total**<br>N = {N}", last = TRUE) %>%
  modify_footnote(update = everything() ~ NA) %>%
  bold_labels()
Characteristic Churn = 0
N = 10750
Churn = 1
N = 14603
Total
N = 25353
Subscription duration, days
Mean (SD) 431.3 (262.1) 130.0 (168.4) 257.8 (260.1)
Median (25%-75%) 416 (226-643) 31 (11-196) 172 (31-423)
Minimum-Maximum 1-821 1-787 1-821
%of active days in subscription
Mean (SD) 55.4 (29.3) 45.3 (32.2) 49.6 (31.4)
Median (25%-75%) 59.7 (32.0-80.6) 45.2 (14.3-74.1) 51.6 (21.2-77.1)
Minimum-Maximum 0-100 0-100 0-100
%of active days in the last 30 days of subscription
Mean (SD) 54.4 (34.0) 37.5 (34.2) 44.7 (35.2)
Median (25%-75%) 60.0 (23.3-86.7) 26.7 (3.3-70.0) 43.3 (10.0-76.7)
Minimum-Maximum 0-100 0-100 0-100
%of active days in the last 14 days of subscription
Mean (SD) 55.1 (35.4) 38.1 (34.6) 45.3 (35.9)
Median (25%-75%) 64.3 (21.4-85.7) 28.6 (7.1-71.4) 42.9 (7.1-78.6)
Minimum-Maximum 0-100 0-100 0-100
%of active days in the last 7 days of subscription
Mean (SD) 54.7 (36.9) 38.6 (36.7) 45.5 (37.6)
Median (25%-75%) 57.1 (14.3-85.7) 28.6 (0.0-71.4) 42.9 (0.0-85.7)
Minimum-Maximum 0-100 0-100 0-100
lag_pct_active
Mean (SD) 15.9 (29.8) 11.1 (25.7) 13.2 (27.6)
Median (25%-75%) 0.0 (0.0-13.8) 0.0 (0.0-0.0) 0.0 (0.0-0.0)
Minimum-Maximum 0-100 0-100 0-100
prev_pct_active
Mean (SD) 0.2 (0.3) 0.1 (0.3) 0.1 (0.3)
Median (25%-75%) 0.0 (0.0-0.1) 0.0 (0.0-0.0) 0.0 (0.0-0.0)
Minimum-Maximum 0-1 0-1 0-1


p1 <- trans_periods %>% 
  mutate(churn = factor(churn, labels = c("Churn = 0", "Churn = 1"))) %>%
  ggplot() +
  aes(x = pct_active, fill = churn) +
  geom_density(alpha = 0.4, color = "white") +
  labs(x = "", y = "Density", title = "% of active days in subscription", fill = "") +
  scale_x_continuous(breaks = seq(0, 100, 10)) +
  scale_y_continuous(expand = c(0, 0)) +
  ggthemes::theme_tufte() +
  theme(legend.position = c(0.5, 0.9),
        plot.title = element_text(face = "bold"),
        axis.text.x = element_text(size = 10))

p2 <- trans_periods %>% 
  mutate(churn = factor(churn, labels = c("Churn = 0", "Churn = 1"))) %>%
  ggplot() +
  aes(x = pct_active30, fill = churn) +
  geom_density(alpha = 0.4, color = "white") +
  labs(x = "", y = "Density", title = "% of active days in the last 30 days of subscr.", fill = "") +
  scale_x_continuous(breaks = seq(0, 100, 10)) +
  scale_y_continuous(expand = c(0, 0)) +
  ggthemes::theme_tufte() +
  theme(legend.position = c(0.5, 0.9),
        plot.title = element_text(face = "bold"),
        axis.text.x = element_text(size = 10))

p3 <- trans_periods %>% 
  mutate(churn = factor(churn, labels = c("Churn = 0", "Churn = 1"))) %>%
  ggplot() +
  aes(x = pct_active14, fill = churn) +
  geom_density(alpha = 0.4, color = "white") +
  labs(x = "", y = "Density", title = "% of active days in the last 14 days of subscr.", fill = "") +
  scale_x_continuous(breaks = seq(0, 100, 10)) +
  scale_y_continuous(expand = c(0, 0)) +
  ggthemes::theme_tufte() +
  theme(legend.position = c(0.5, 0.9),
        plot.title = element_text(face = "bold"),
        axis.text.x = element_text(size = 10))

p4 <- trans_periods %>% 
  mutate(churn = factor(churn, labels = c("Churn = 0", "Churn = 1"))) %>%
  ggplot() +
  aes(x = pct_active7, fill = churn) +
  geom_density(alpha = 0.4, color = "white") +
  labs(x = "", y = "Density", title = "% of active days in the last 7 days of subscr.", fill = "") +
  scale_x_continuous(breaks = seq(0, 100, 10)) +
  scale_y_continuous(expand = c(0, 0)) +
  ggthemes::theme_tufte() +
  theme(legend.position = c(0.5, 0.9),
        plot.title = element_text(face = "bold"),
        axis.text.x = element_text(size = 10))

ggarrange(p1,p2,p3,p4, nrow = 2, ncol = 2, common.legend = TRUE)
p1 <- trans_periods %>% 
  mutate(churn = factor(churn, labels = c("Churn = 0", "Churn = 1"))) %>%
  ggplot() +
  aes(x = pct_active, fill = churn) +
  geom_density(alpha = 0.4, color = "white") +
  labs(x = "", y = "Density", title = "% of active days in subscription", fill = "") +
  scale_x_continuous(breaks = seq(0, 100, 10)) +
  scale_y_continuous(expand = c(0, 0)) +
  ggthemes::theme_tufte() +
  theme(legend.position = c(0.5, 0.9),
        plot.title = element_text(face = "bold"),
        axis.text.x = element_text(size = 10))

p2 <- trans_periods %>% 
  mutate(churn = factor(churn, labels = c("Churn = 0", "Churn = 1"))) %>%
  ggplot() +
  aes(x = pct_active30, fill = churn) +
  geom_density(alpha = 0.4, color = "white") +
  labs(x = "", y = "Density", title = "% of active days in the last 30 days of subscr.", fill = "") +
  scale_x_continuous(breaks = seq(0, 100, 10)) +
  scale_y_continuous(expand = c(0, 0)) +
  ggthemes::theme_tufte() +
  theme(legend.position = c(0.5, 0.9),
        plot.title = element_text(face = "bold"),
        axis.text.x = element_text(size = 10))

p3 <- trans_periods %>% 
  mutate(churn = factor(churn, labels = c("Churn = 0", "Churn = 1"))) %>%
  ggplot() +
  aes(x = pct_active14, fill = churn) +
  geom_density(alpha = 0.4, color = "white") +
  labs(x = "", y = "Density", title = "% of active days in the last 14 days of subscr.", fill = "") +
  scale_x_continuous(breaks = seq(0, 100, 10)) +
  scale_y_continuous(expand = c(0, 0)) +
  ggthemes::theme_tufte() +
  theme(legend.position = c(0.5, 0.9),
        plot.title = element_text(face = "bold"),
        axis.text.x = element_text(size = 10))

p4 <- trans_periods %>% 
  mutate(churn = factor(churn, labels = c("Churn = 0", "Churn = 1"))) %>%
  ggplot() +
  aes(x = pct_active7, fill = churn) +
  geom_density(alpha = 0.4, color = "white") +
  labs(x = "", y = "Density", title = "% of active days in the last 7 days of subscr.", fill = "") +
  scale_x_continuous(breaks = seq(0, 100, 10)) +
  scale_y_continuous(expand = c(0, 0)) +
  ggthemes::theme_tufte() +
  theme(legend.position = c(0.5, 0.9),
        plot.title = element_text(face = "bold"),
        axis.text.x = element_text(size = 10))

ggarrange(p1,p2,p3,p4, nrow = 2, ncol = 2)

– Subscriptions ended in churn are a bit less active - as overall, as in the last days of subscription.


The problem with all described user logs characteristics is that they are time dependent, i.e. they change with time of subscription. As a consequence, we cannot use them in basic statistic or ML prediction models without special options and preliminary data restructuring. We also cannot use some average indicators over the whole subscription period, because for validation and test sets, as well as for any new data, we do not know the (future) length of subscription and (future) user’s behaviour, while we would like making predictions at any point of subscription (either start or in process) with all information available prior to that moment.

In addition, we cannot plot Kaplan-Meier curves for time-dependent predictors - instead we should use Simon and Makuch plots. Although, as for Kaplan-Meier curves they are applicable only for categorical variables.


4 Predicting survival distributions for subscriptions


We have splitted out data for subscription periods into train and test sets with 70% of all periods in train.


4.1 Features


Here we start from baseline models without time-dependent covariates and will use only interpretable models or, at least, those which allow estimation of variable importance, so a company will be able to understand what could be a cause for user’s attrition.

From all available features for each subscription period we chose the following:

  • churned_before_num - number of previous churns (set to 0 for the first subscription period of the corresponding user),

  • cityG - all unique numeric codes, with 19 and 20 codes grouped into one category,

  • bdG - age splitted into 2 categories: less than 10 and 10+ (years),

  • gender - female, male, unknown,

  • registered_via - all unique numeric codes without any grouping,

  • month_period - month at which subscription was started,

  • time_from_reg - time from registration at the service till the start of subscription (days),

  • first_payment_method_id - payment method used in the first transaction (at the start of subscription period),

  • first_plan_list_price - plan list price stated in the first transaction (at the start of subscription period),

  • time_from_prev_exp - time from the expiration date of the previous subscription (days, set to 0 for the first subscription of the corresponding user),

  • prev_time - sum of all previous subscription durations (days, set to 0 for the first subscription of the corresponding user),

  • prev_pct_active - share of active days to the total length of all previous subscriptions (in %, set to 0 for the first subscription of the corresponding user),

  • prev_mean_num_tot - average number of songs listened per day for all previous subscriptions (set to 0 for the first subscription of the corresponding user),

  • prev_mean_total_secs_imp - average number of seconds listened per day for all previous subscriptions (set to 0 for the first subscription of the corresponding user; we used values of daily seconds with imputed large values as it was described above),

  • prev_mean_num_unq - average number of unique songs listened per day for all previous subscriptions (set to 0 for the first subscription of the corresponding user),

  • prev_mean_av_pct_played_act - average % of one song’s length played per active day for all previous subscriptions (set to 0 for the first subscription of the corresponding user),

  • prev_mean_actual_amount_paid - mean (daily) actual amount paid for all previous subscriptions (set to 0 for the first subscription of the corresponding user),

  • prev_num_cancel - total number of cancellations for all previous subscriptions (set to 0 for the first subscription of the corresponding user; remember that cancellations include changes in subscription plans as well),

  • prev_mode_payment_id - modal value for payment method for all previous subscriptions (first subscription of the corresponding user was placed in the separate category of this feature; all categories with the number of subscriptions less than 30 were combined into one group with label “999”).

We also tried models with lag values instead of values for all previous subscruptions - on average, they perfomed worse, so we sticked to data on all previous subscriptions.


For models with time-dependent covariates we preliminary restructured our data set. Each subscription period was splitted into several intervals by the dates of users logs or transactions inside them (if they were there; otherwise a period remained unchanged). For each interval we were able to use all information about users, their transactions and user log statistics, but for comparability with models with time-independent covariates we chose the following features (if it is not denoted that the feature was time-independent, it was time-dependent):

  • churned_before_num (time-independent) - number of previous churns (set to 0 for the first subscription period of the corresponding user),

  • cityG (time-independent) - numeric code for city, with 19 and 20 codes grouped into one category,

  • bdG (time-independent) - age splitted into 2 categories: less than 10 and 10+ (years),

  • gender (time-independent) - female, male, unknown,

  • registered_via (time-independent) - all unique numeric codes without any grouping,

  • month_period (time-independent) - month at which subscription was started,

  • time_from_reg (time-independent) - time from registration at the service till the start of subscription (days),

  • time_from_prev_exp (time-independent) - time from the expiration date of the previous subscription (days, set to 0 for the first subscription of the corresponding user),

  • prev_time (time-independent) - sum of all previous subscription durations (days, set to 0 for the first subscription of the corresponding user),

  • pct_active - share of active (with logs) days to the total length of the interval (in %),

  • mean_num_tot - average number of songs listened per day during the interval,

  • mean_total_secs_imp - average number of seconds listened per day during the interval,

  • mean_num_unq - average number of unique songs listened per day during the interval,

  • mean_av_pct_played_act - average % of one song’s length played per active day during the interval,

  • cum_mean_actual_amount_paid - cumulative mean (daily) actual amount paid from the start of subscription to the end of this interval,

  • cum_num_cancel - cumulative number of cancellations from the start of subscription to the end of this interval,

  • last_payment_method_id - last value for payment method from the start of subscription to the end of this interval,

  • last_plan_list_price - last value for plan list price from the start of subscription to the end of this interval.


4.2 Models


I have chosen Cox PH and RSF as models for estimation, because:

  • they allow to predict survival functions,

  • there are packages in R which allow estimation and prediction of these models with time-dependent covariates,

  • in preliminary analysis they outperformed all other models (parametric PH, AFT, xgboost) for time-indepdenent covariates,

  • they are quite different in their assumptions - first of all, RSF does not assume proportionality in hazards, while, for example, xgboost does - thus, we should obtain predictions from two different assumptions fields.

Maximum depth and minimum leaf size for RSF were tuned using grid search with holdout, i.e. train sample was splitted into train and validation sets in proportion 8:2, and performance with different sets of parameters were estimated using Harrell’s C index on the validaion set. 500 trees were estimated (100 trees gave almost the same result).

For overall performance estimation and model comparison (for models with time-indepdendent covariates only!) I used the following metrics:

  • Harrell’s C index,

  • Uno’s C index (in comparison with Harrell’s one it incorporates weights from estimation of censoring distribution),

  • integrated Brier score (IBS) for the period from 1 to 720 days (~ 2 years),

  • time-dependent Brier scores for several time points: 7, 14, 30, 60, 90, 180, 360 and 720 days to estimate how prediction quality changes with time.

For models with time-dependent covariates I used Harrel’s C-index estimated both on train and test sets and IBS for the period from 1 to 720 days estimated on test only.


4.3 Instruments


I’ve used a mix of Python and R for all the project.

From R I used the following packages:

  • survival (Terry M. Therneau 2021) - for Kaplan-Meier estimators (above), Cox regression with time-dependent covariates, predicting survival probabilities and estimating Harrell’C after that,

  • survminer (Kassambara et al. 2021) - for plotting Kaplan-Meier curves (above),

  • bshazard (Rebora, Salim, and Reilly 2014) - for non-parametric estimation and plotting hazard functions (above),

  • rms (Harrell 2021) - for estimating variable importance in Cox regressions by proportion of explainable log-likelihood,

  • LTRCforest (Yao et al. 2021) - for estimating IBS in models with time-dependent covariates and for RSF with time-dependent covariates.


From Python I used the following libraries:

  • scikit-learn(Pedregosa et al. 2011) - for train/test split, grid search CV (tuning hyperparameters for RSF with time-independent covariates),

  • scikit-survival (Pölsterl 2020) - for preprocessing (one-hot encoding), estimating Cox PH and RSF models with time-independent covariates, predicting survival after them and estimates of all evaluation metrics.


4.4 Results


4.4.1 Models with time-independent covariates


X <- trans_periods %>%
  transmute(churned_before_num, 
            cityG = fct_lump_min(city, 30, other_level = "19-20"), 
            bdG, gender, registered_via, month_period,
            time_from_reg, time_from_prev_exp, 
            first_payment_method_id, first_plan_list_price,
            prev_time, prev_pct_active = 100*prev_pct_active, 
            prev_mean_num_tot, prev_mean_total_secs_imp,
            prev_mean_num_unq, prev_mean_av_pct_played_act,
            prev_mean_actual_amount_paid, prev_num_cancel,
            prev_mode_payment_id)

X_base <- X %>% select(-contains("prev_"))

Y_clmns <- trans_periods %>%
  select(churn, time)

sample_X_test_id <- read.csv("sample_X_test_id.csv")

cat_columns <- map_dfc(X, ~ !is.numeric(.x)) %>% t()
num_columns <- map_dfc(X, ~is.numeric(.x)) %>% t()
cat_columns <- names(X)[cat_columns[,1]]
num_columns <- names(X)[num_columns[,1]]

cat_base_columns <- map_dfc(X_base, ~ !is.numeric(.x)) %>% t()
num_base_columns <- map_dfc(X_base, ~is.numeric(.x)) %>% t()
cat_base_columns <- names(X_base)[cat_base_columns[,1]]
num_base_columns <- names(X_base)[num_base_columns[,1]]
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.inspection import permutation_importance
from sklearn.base import TransformerMixin
from sklearn.model_selection import train_test_split, GridSearchCV, KFold
from sksurv.preprocessing import OneHotEncoder
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
from sksurv.nonparametric import kaplan_meier_estimator
from sksurv.linear_model import CoxPHSurvivalAnalysis
from sksurv.ensemble import RandomSurvivalForest
from sksurv.metrics import concordance_index_censored, concordance_index_ipcw, brier_score, integrated_brier_score, as_concordance_index_ipcw_scorer

# Function to get predictions from estimator

def get_predictions(Xtrain, Xtest, ytrain, ytest, estimator):
  pred_train = estimator.predict(Xtrain)
  pred_test = estimator.predict(Xtest)
  pred_surv_train = estimator.predict_survival_function(Xtrain)
  pred_surv_test = estimator.predict_survival_function(Xtest)
  
  return pred_train, pred_test, pred_surv_train, pred_surv_test

# Function to get metrics for model

def get_metrics_df(Xtrain, Xtest, ytrain, ytest, estimator):
  pred_train = estimator.predict(Xtrain)
  pred_test = estimator.predict(Xtest)
  pred_surv_train = estimator.predict_survival_function(Xtrain)
  pred_surv_test = estimator.predict_survival_function(Xtest)

  hc_train = concordance_index_censored(ytrain["Churn"], ytrain["Time"], pred_train)[0]
  hc_test = concordance_index_censored(ytest["Churn"], ytest["Time"], pred_test)[0]
  uc_train = concordance_index_ipcw(ytrain, ytrain, pred_train)[0]
  uc_test = concordance_index_ipcw(ytest, ytest, pred_test)[0]
  
  times = np.array([7, 14, 30, 60, 90, 180, 360, 720])
  preds_train = np.asarray([[fn(t) for t in times] for fn in pred_surv_train])
  preds_test = np.asarray([[fn(t) for t in times] for fn in pred_surv_test])

  bs_train = brier_score(ytrain, ytrain, preds_train, times)
  bs_test = brier_score(ytest, ytest, preds_test, times)
  
  times2 = np.arange(1, 720)
  #preds_train2 = np.asarray([[fn(t) for t in times2] for fn in pred_surv_train])
  preds_test2 = np.asarray([[fn(t) for t in times2] for fn in pred_surv_test])
  
  #ibs_train = integrated_brier_score(ytrain, ytrain, preds_train2, times2)
  ibs_test = integrated_brier_score(ytest, ytest, preds_test2, times2)
  
  results = pd.DataFrame(
    {"Metric": ['Harrel`s C', 'Uno`s C', 'IBS 1-720d', 'BS 7d', 'BS 14d', 'BS 30d', 'BS 60d',
      'BS 90d', 'BS 180d', 'BS 360d', 'BS 720d'],
      "Train": [hc_train, uc_train, 'NULL'] + [i for i in bs_train[1]],
      "Test": [hc_test, uc_test, ibs_test] + [i for i in bs_test[1]]})
  
  return results, bs_train, bs_test

# Function to plot time-dependent Brier score

def plot_td_brier(bs_tuple):
  plt.figure()
  plt.plot([i for i in bs_base_test[0]], [i for i in bs_base_test[1]], marker="o")
  plt.xlabel("Days from start of subscription")
  plt.ylabel("Time-dependent Brier score")
  plt.xticks([7,30,60,90,180,360,720], [7,30,60,90,180,360,720])
  plt.grid()
  plt.show()
X_base = r.X_base
X_base.shape

X = r.X
X.shape

cat_columns = r.cat_columns
num_columns = r.num_columns
cat_base_columns = r.cat_base_columns
num_base_columns = r.num_base_columns

aux = [tuple([bool(i[0]), i[1]]) for i in r.Y_clmns.to_numpy()]
y = np.array(aux, dtype=[('Churn', '?'), ('Time', '<f8')])
y
y.shape

random_state = 42

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=random_state)
X_base_train, X_base_test, _, _ = train_test_split(X_base, y, test_size=0.3, random_state=random_state)

sample_X_test_id = r.sample_X_test_id
sample_X_test = X_test.loc[sample_X_test_id['ID'].values]
sample_X_base_test = X_base_test.loc[sample_X_test_id['ID'].values]

sample_y_churn = [int(i[0]) for i in y[sample_X_test_id['ID'].values]]
sample_y_time = [int(i[1]) for i in y[sample_X_test_id['ID'].values]]

sample_sort = np.argsort(sample_y_time)
sample_sort = sample_sort[::-1]

sample_X_test = sample_X_test.iloc[sample_sort]
sample_X_base_test = sample_X_base_test.iloc[sample_sort]

sample_y_churn = [sample_y_churn[i] for i in sample_sort]
sample_y_time = [sample_y_time[i] for i in sample_sort]

feature_names = X_train.columns.tolist()
feature_names_base = X_base_train.columns.tolist()
# Baseline Cox without data on previous subscription

cox_ph_base = Pipeline(
  [
    ('encode', OneHotEncoder()),
    ("model", CoxPHSurvivalAnalysis())
  ]
)

encoder_names = OneHotEncoder()

cox_ph_base.fit(X_base_train, y_train)

cox_ph_base_coef = pd.Series(cox_ph_base.named_steps['model'].coef_, index=encoder_names.fit_transform(X_base_train).columns)
cox_ph_base_coef = cox_ph_base_coef.reset_index()
cox_ph_base_coef.columns = ['Variable', 'Coef']
cox_ph_base_coef.sort_values(by='Coef', key=abs, ascending=False, inplace=True)
cox_ph_base_coef['HR'] = cox_ph_base_coef['Coef'].apply(lambda x: np.exp(x))
cox_ph_base_coef

cox_base_pred_train, cox_base_pred_test, cox_base_surv_train, cox_base_surv_test = get_predictions(X_base_train, X_base_test, y_train, y_test, cox_ph_base)

cox_base_results, bs_base_train, bs_base_test = get_metrics_df(X_base_train, X_base_test, y_train, y_test, cox_ph_base)

cox_base_results

plot_td_brier(bs_base_test)

pred_surv = cox_ph_base.predict_survival_function(sample_X_base_test)

time_points = np.arange(1, 720)
plt.figure()
for i, surv_func in enumerate(pred_surv):
    plt.step(time_points, surv_func(time_points), where="post",
             label="Churn={churn}, Time={time}d".format(churn=sample_y_churn[i], time=sample_y_time[i]))
plt.ylabel("Est. probability of survival $\hat{S}(t)$")
plt.xlabel("Time $t$")
plt.title('Cox PH baseline')
plt.legend(bbox_to_anchor=(0.7, 0.7))
plt.show()


# Cox with data on previous subscription

cox_ph = Pipeline(
  [
    ('encode', OneHotEncoder()),
    ("model", CoxPHSurvivalAnalysis())
  ]
)

cox_ph.fit(X_train, y_train)

cox_ph_coef = pd.Series(cox_ph.named_steps['model'].coef_, index=encoder_names.fit_transform(X_train).columns)
cox_ph_coef = cox_ph_coef.reset_index()
cox_ph_coef.columns = ['Variable', 'Coef']
cox_ph_coef.sort_values(by='Coef', key=abs, ascending=False, inplace=True)
cox_ph_coef['HR'] = cox_ph_coef['Coef'].apply(lambda x: np.exp(x))
cox_ph_coef

cox_pred_train, cox_pred_test, cox_surv_train, cox_surv_test = get_predictions(X_train, X_test, y_train, y_test, cox_ph)

cox_results, bs_train, bs_test = get_metrics_df(X_train, X_test, y_train, y_test, cox_ph)

cox_results

plot_td_brier(bs_test)


pred_surv = cox_ph.predict_survival_function(sample_X_test)

time_points = np.arange(1, 720)
plt.figure()
for i, surv_func in enumerate(pred_surv):
    plt.step(time_points, surv_func(time_points), where="post",
             label="Churn={churn}, Time={time}d".format(churn=sample_y_churn[i], time=sample_y_time[i]))
plt.ylabel("Est. probability of survival $\hat{S}(t)$")
plt.xlabel("Time $t$")
plt.title('Cox PH prev')
plt.legend(bbox_to_anchor=(0.7, 0.7))
plt.show()
# Train and validation sets

X_base_tt, X_base_val, y_tt, y_val = train_test_split(X_base_train, y_train, test_size=0.2, random_state=random_state)
X_tt, X_val, _, _ = train_test_split(X_train, y_train, test_size=0.2, random_state=random_state)

# RSF tuning

rsf_base_scores = []

for md in np.arange(5, 11, dtype=int):
  rsf = RandomSurvivalForest(n_estimators=500, max_depth=md, max_features="sqrt", min_samples_leaf=10, min_samples_split=10, n_jobs=-1, random_state=random_state)
  rsf_fit = rsf.fit(X_base_tt, y_tt)
  rsf_base_scores += [rsf.score(X_base_val, y_val)]
  print md, rsf_base_scores[-1]

for msl in np.array([5,10,15,20], dtype=int):
  rsf = RandomSurvivalForest(n_estimators=500, max_depth=14, max_features="sqrt", min_samples_leaf=msl, min_samples_split=10, n_jobs=-1, random_state=random_state)
  rsf_fit = rsf.fit(X_base_tt, y_tt)
  rsf_base_scores += [rsf.score(X_base_val, y_val)]
  print(msl, rsf_base_scores[-1])
  

# RSF with baseline data

rsf_base = Pipeline(
  [
    ('encode', OneHotEncoder()),
    ("model", RandomSurvivalForest(n_estimators=500, max_depth=14, max_features="sqrt", min_samples_leaf=5, min_samples_split=10, n_jobs=-1, random_state=random_state))
  ]
)

rsf_base.fit(X_base_train, y_train)

rsf_base_pred_train, rsf_base_pred_test, rsf_base_surv_train, rsf_base_surv_test = get_predictions(X_base_train, X_base_test, y_train, y_test, rsf_base, rf=True)

rsf_base_results, rsf_base_bs_train, rsf_base_bs_test = get_metrics_df(X_base_train, X_base_test, y_train, y_test, rsf_base)

rsf_base_results

pred_surv = rsf_base.predict_survival_function(sample_X_base_test)

time_points = np.arange(1, 720)
plt.figure()
for i, surv_func in enumerate(pred_surv):
    plt.step(time_points, surv_func(time_points), where="post",
             label="Churn={churn}, Time={time}d".format(churn=sample_y_churn[i], time=sample_y_time[i]))
plt.ylabel("Est. probability of survival $\hat{S}(t)$")
plt.xlabel("Time $t$")
plt.legend(bbox_to_anchor=(0.7, 0.7))
plt.title('RSF baseline')
plt.show()

rsf_imp_base = permutation_importance(rsf_base, X_base_test, y_test, n_repeats=10, random_state=random_state)
sorted_idx = rsf_imp_base.importances_mean.argsort()

fig, ax = plt.subplots()
ax.boxplot(
    rsf_imp_base.importances[sorted_idx].T, vert=False, labels=X_base_train.columns[sorted_idx]
)
ax.set_title("RSF baseline, variable importance (permutation)")
fig.tight_layout()
plt.show()


# RSF with data on previous subscriptions

rsf = Pipeline(
  [
    ('encode', OneHotEncoder()),
    ("model", RandomSurvivalForest(n_estimators=500, max_depth=14, max_features="sqrt", min_samples_leaf=5, min_samples_split=10, n_jobs=-1, random_state=random_state))
  ]
)

rsf.fit(X_train, y_train)

rsf_pred_train, rsf_pred_test, rsf_surv_train, rsf_surv_test = get_predictions(X_train, X_test, y_train, y_test, rsf, rf=True)

rsf_results, rsf_bs_train, rsf_bs_test = get_metrics_df(X_train, X_test, y_train, y_test, rsf)

rsf_results

pred_surv = rsf.predict_survival_function(sample_X_test)

time_points = np.arange(1, 720)
plt.figure()
for i, surv_func in enumerate(pred_surv):
    plt.step(time_points, surv_func(time_points), where="post",
             label="Churn={churn}, Time={time}d".format(churn=int(y_test[sample_mask.index[i]][0]), time=int(y_test[sample_mask.index[i]][1])))
plt.ylabel("Est. probability of survival $\hat{S}(t)$")
plt.xlabel("Time $t$")
plt.legend(bbox_to_anchor=(0.7, 0.7))
plt.title('RSF prev')
plt.show()

rsf_imp = permutation_importance(rsf, X_test, y_test, n_repeats=10, random_state=random_state)
sorted_idx = rsf_imp.importances_mean.argsort()

fig, ax = plt.subplots()
ax.boxplot(
    rsf_imp.importances[sorted_idx].T, vert=False, labels=X_train.columns[sorted_idx]
)
ax.set_title("RSF prev, variable importance (permutation)")
fig.tight_layout()
plt.show()

We estimated Cox PH and RSF models in two variants both:

  • with baseline variables only, i.e. with such features which are known at the start of subscription (member’s characteristics and data on his first transaction, from which subscription started), without any information about previous history of this user, except the number of previous churns (on the diagrams and in the tables below these models go with the index ‘baseline’),

  • with baseline characteristics and features concerning all previous subscriptions (on the diagrams and in the tables below these models go with the index ‘prev’).

Overall, RSF is a bit better in prediction performance measured by C-indices (Harrell’s C does not take censoring distribution into account, while Uno’s C does) than Cox PH. And both of them performs a bit better with features on previous subscriptions, although for RSF this gain is almost unnoticeable. In addition, both models give quire comparable results on train and test sets:

metrics <- list(CoxPH_base = read.csv("Cox_base.csv", sep=";"),
                CoxPH = read.csv("Cox.csv", sep=";"),
                RSF_base = read.csv("rsf_base.csv", sep=";"),
                RSF = read.csv("rsf.csv", sep=";"))

cindex <- lapply(metrics, function(x) x[1:2,])
cindex <- lapply(cindex, function(x) {x[,2] <- as.numeric(x[,2]) 
  x})
cindex <- do.call(dplyr::bind_rows, cindex)

cindex$model <- rep(c("Cox PH baseline", "Cox PH prev", "RSF baseline", "RSF prev"), each=2)
cindex <- rbind(cindex[,c(1:2,4)] %>% rename(value=Train) %>% mutate(set="Train"), 
                cindex[,c(1,3:4)] %>% rename(value=Test) %>% mutate(set="Test"))
cindex$Metric <- gsub("Harrel", "Harrell", cindex$Metric)

ggplot() +
  geom_bar(aes(x=Metric, y=value, fill=model), cindex,
           stat = "identity", 
           position = position_dodge(0.8), color = "white") +
  geom_text(aes(x=Metric, y=y, label=lbl, group=model), 
            cindex %>% mutate(y = value + 0.03, lbl = round(value,2)),
            position = position_dodge(0.8), stat = "identity",
            size = 3, fontface = "bold") +
  facet_wrap(~ set) +
  scale_y_continuous(expand = c(0,0), limits = c(0,1)) +
  scale_fill_manual(values = paletteer_d("ggthemes::Tableau_20")[c(2,1,4,3)]) +
  labs(x="", y="", fill="", title="C-index", color="") +
  ggthemes::theme_tufte() +
  theme(plot.title = element_text(face = "bold", hjust = 0.5),
        axis.ticks.x = element_blank(),
        strip.text = element_text(size = 12, face = "bold"),
        axis.line = element_line(size = .2, color = "grey20"),
        axis.text.x = element_text(size = 12, face = "bold"),
        strip.background = element_rect(fill = NA))


For the integrated Brier score (IBS), calculated for the period from 1 to 720 days the disposition is the same:

ibs <- unlist(lapply(metrics, function(x) x[3,3]))
ibs <- tibble(model = c("Cox PH baseline", "Cox PH prev", "RSF baseline", "RSF prev"),
              value = ibs)

ggplot(ibs) +
  geom_bar(aes(x=model, y=value, fill=model), stat = "identity", width = 0.6) +
  geom_text(aes(x=model, y=value-0.01, label=round(value,3)),
            size = 4, color = "grey10", fontface = "bold") +
  scale_y_continuous(expand = c(0,0), limits = c(0,0.16),
                     breaks = seq(0,0.15,0.05)) +
  scale_fill_manual(values = paletteer_d("ggthemes::Tableau_20")[c(2,1,4,3)]) +
  labs(x="", y="", title="IBS, test sample") +
  coord_flip() +
  ggthemes::theme_tufte() +
  theme(legend.position = "none", 
        plot.title = element_text(face = "bold", hjust = 0.5),
        strip.text = element_text(size = 12, face = "bold"),
        axis.line = element_line(size = .2, color = "grey20"),
        axis.text.y = element_text(size = 12, hjust = 0,
                                   color = "grey10", face = "bold",
                                   margin = margin(l = 10, r = -100)),
        axis.ticks.y = element_blank())


But if we turn our attention to the time-dependent Brier score (we calculated it for 7, 14, 30, 60, 90, 180, 360 and 720 days after the start of subscription), then advantages of RSF models over Cox PH are more prominent for intermediate time points:


We sampled 3 subscriptions out of the test sample from both the group of subscriptions actually ended by churn and censored, and predict survival functions for them using all the 4 models above:

Again, there are mode differences in predictions obtained from two Cox models, than between two RSF models. Above all, it could be because of violation of the PH assumption.


For Cox regression variable importance can be estimated using the proportion of log-likelihood that is explained by each variable (for categorical variables it comprises all their categories into so called grouped variable). The plot below displays the Wald \(\chi^2\) statistic minus its degrees of freedom for assessing the partial effect of each variable. According to Harrell (2021), even though this is not a scaled [0,1] statistic, it is probably the best method in general because it penalizes a variable requiring a large number of parameters to achieve the \(\chi^2\).

df_feat <- trans_periods %>%
  transmute(subscr_id, churn, time,
            churned_before_num, 
            cityG = fct_lump_min(city, 30, other_level = "19-20"), 
            bdG, gender, registered_via, month_period,
            time_from_reg, time_from_prev_exp, 
            first_payment_method_id, first_plan_list_price,
            prev_time, prev_pct_active = 100*prev_pct_active, 
            prev_mean_num_tot, prev_mean_total_secs_imp,
            prev_mean_num_unq, prev_mean_av_pct_played_act,
            prev_mean_actual_amount_paid, prev_num_cancel,
            prev_mode_payment_id)

df_feat_base <- df_feat %>% select(-contains("prev_"))

feat_base <- names(df_feat_base)[-c(1:3)]
feat_prev <- names(df_feat)[-c(1:3)]

train_set <- read.csv("X_train_index.csv")
sample_X_test_id <- read.csv("sample_X_test_id.csv")

df_base_tr <- df_feat_base %>%
  filter(subscr_id %in% trans_periods$subscr_id[train_set$ind + 1])

df_base_tst <- df_feat_base %>%
  filter(! subscr_id %in% trans_periods$subscr_id[train_set$ind + 1])

df_tr <- df_feat %>%
  filter(subscr_id %in% trans_periods$subscr_id[train_set$ind + 1])

df_tst <- df_feat %>%
  filter(! subscr_id %in% trans_periods$subscr_id[train_set$ind + 1])

sample_X_subscr_id <- trans_periods$subscr_id[sample_X_test_id$ID + 1]

samp_base_test <- df_feat_base %>%
  filter(subscr_id %in% trans_periods$subscr_id[sample_X_test_id$ID + 1])
samp_test <- df_feat %>%
  filter(subscr_id %in% trans_periods$subscr_id[sample_X_test_id$ID + 1])

fml_base = as.formula(paste0("Surv(time = time, event = churn) ~ ",
                             paste(feat_base, collapse = " + ")))
fml_prev = as.formula(paste0("Surv(time = time, event = churn) ~ ",
                             paste(feat_prev, collapse = " + ")))

cox_base_rms <- cph(fml_base, df_base_tr, x=TRUE, y=TRUE)
cox_base <- coxph(fml_base, df_base_tr)
cox_base_anova_rms <- anova(cox_base_rms)
png("cox_base_imp.png", width = 500, height = 400)
plot(cox_base_anova_rms, sort = "ascending")
dev.off()

cox_rms <- cph(fml_prev, df_tr, x=TRUE, y=TRUE)
cox <- coxph(fml_prev, df_tr)
cox_anova_rms <- anova(cox_rms)
png("cox_imp.png", width = 500, height = 400)
plot(cox_anova_rms, sort = "ascending")
dev.off()

p1 <- ggdraw() + draw_image("cox_base_imp.png", scale = 1)
p2 <- ggdraw() + draw_image("cox_imp.png", scale = 1)
ggarrange(p1,p2,nrow = 1, ncol = 2, 
          labels = c("Cox PH baseline, variable importance",
                     "Cox PH prev, variable importance"), hjust = -0.25)


For RSF models we calculated weights for variable importance using 10 permutations - these weights show the value by which the concordance index on the test data will drop, on average, if the relationship of the according feature to survival time is removed (by random shuffling).


You can see that all models consider the first payment method used as the most important feature, the first plan list price is the second by importance in RSF models, and the third one in Cox PH models. Month of the subscription origin is more important in baseline specifications compared to extended with characteristics of the previous subscriptions, and in Cox regressions compared to RSF. The most considerable difference in ranging features between Cox PH and RSF models is observed for the most frequent payment method used in previous subscriptions: it holds the second place in Cox regression and one of the lasts in RSF. Among other historical features average percent of one song’ length played matters the most.


To reveal how survival probability changes with different values of the most important (and other) variables we can predict and plot survival curves for them in particular, taking all other features equal. In addition, for Cox regression we can interpret exponentiated coefficients as hazard ratios: if hazard ratio > 1 (the corresponding coefficient is negative), then the this feature positively correlated with churn hazard, otherwise the correlation is negative. We won’t plot such survival curves here - just display plots for hazard ratios. All hazard ratios for categorical variables should be interpreted in comparison with baseline category of the corresponding variable.

feature_group <- function(x, feat) {
  for (i in 1:length(x)) {
    for (j in feat) {
      if (grepl(j, x[i])) {
        x[i] <- j
      }
    }
  }
  x
}

cox_base_coef <- summary(cox_base)
cox_base_coef <- tibble(Variable = row.names(cox_base_coef$coefficients),
                        group = feature_group(Variable, feat_base),
                        HR = cox_base_coef$coefficients[,2],
                        se = cox_base_coef$coefficients[,3]) %>%
  arrange(HR, group)

p1 <- ggplot(cox_base_coef, aes(x=Variable, y=HR, color=group)) + 
  geom_hline(yintercept = 1) +
  geom_pointrange(aes(ymin=HR*exp(-1.96*se), ymax=HR*exp(1.96*se))) +
  scale_x_discrete(limits = cox_base_coef$Variable) +
  scale_color_manual(values = rev(paletteer_d("ggthemes::Tableau_10")[1:9])) +
  coord_flip() +
  labs(y = "Hazard Ratio [95% CI]", x = "", title = "Cox PH baseline, hazard ratios") +
  ggthemes::theme_tufte() +
  theme(legend.position = "none",
        plot.title = element_text(face = "bold", hjust = 0.5),
        axis.line = element_line(size = .2, color = "grey20"),
        axis.ticks.y = element_blank(),
        panel.grid.major.x = element_line(size=.2, color="grey90"),
        panel.grid.minor.x = element_line(size=.2, color="grey90"),
        panel.grid.major.y = element_line(size=.2, color="grey90", linetype="dotted"))

cox_coef <- summary(cox)
cox_coef <- tibble(Variable = row.names(cox_coef$coefficients),
                        group = feature_group(Variable, feat_prev),
                        HR = cox_coef$coefficients[,2],
                        se = cox_coef$coefficients[,3]) %>%
  arrange(HR, group)

p2 <- ggplot(cox_coef, aes(x=Variable, y=HR, color=group)) + 
  geom_hline(yintercept = 1) +
  geom_pointrange(aes(ymin=HR*exp(-1.96*se), ymax=HR*exp(1.96*se))) +
  scale_x_discrete(limits = cox_coef$Variable) +
  scale_color_manual(values = paletteer_d("ggthemes::Classic_20")[1:19]) +
  coord_flip() +
  labs(y = "Hazard Ratio [95% CI]", x = "", title = "Cox PH prev, hazard ratios") +
  ggthemes::theme_tufte() +
  theme(legend.position = "none",
        plot.title = element_text(face = "bold", hjust = 0.5),
        axis.line = element_line(size = .2, color = "grey20"),
        axis.ticks.y = element_blank(),
        panel.grid.major.x = element_line(size=.2, color="grey90"),
        panel.grid.minor.x = element_line(size=.2, color="grey90"),
        panel.grid.major.y = element_line(size=.2, color="grey90", linetype="dotted"))

ggarrange(p1,p2,nrow = 1,ncol = 2)


4.4.2 Models with time-dependent covariates


trans_df_date <- trans_df %>%
  group_by(subscr_id, transaction_date) %>%
  summarise(date = unique(transaction_date),
            actual_amount_paid = sum(actual_amount_paid),
            is_cancel = sum(is_cancel), 
            payment_method_id = payment_method_id[n()],
            plan_list_price = max(plan_list_price)) %>%
  ungroup()

ulogs_trans_periods <- ulogs_periods %>%
  select(date, subscr_id, num_tot, num_unq, total_secs_imp, av_pct_played) %>%
  mutate(file = "ulogs") %>%
  full_join(trans_periods %>%
              select(subscr_id, msno, msno_subscr_id, churn, time, start_date, end_date,
                     churned_before_num, city, bdG, gender, registered_via, month_period,
                     first_payment_method_id, first_plan_list_price,
                     time_from_reg, time_from_prev_exp, prev_time,
                     prev_mean_num_tot, prev_mean_total_secs_imp,
                     prev_mean_num_unq, prev_mean_av_pct_played_act,
                     prev_mean_actual_amount_paid, prev_num_cancel,
                     prev_mode_payment_id),
            by = "subscr_id") %>%
  full_join(trans_df_date %>% transmute(subscr_id, 
                                        date = as.Date(ifelse(subscr_id %in% ulogs_periods$subscr_id,
                                                              transaction_date, 
                                                              NA), "1970-01-01"),
                                        transaction_date, actual_amount_paid, plan_list_price,
                                        is_cancel, payment_method_id),
            by = c("subscr_id", "date"))

ulogs_trans_periods <- ulogs_trans_periods %>%
  mutate(date = ifelse(is.na(date), transaction_date, date)) %>%
  mutate_at(vars(num_tot, num_unq, total_secs_imp, av_pct_played,
                 actual_amount_paid, plan_list_price, is_cancel), ~ replace_na(., 0)) %>%
  group_by(subscr_id) %>%
  mutate_at(vars(msno, msno_subscr_id, churn, time, start_date, end_date,
                 churned_before_num, city, bdG, gender, registered_via, month_period,
                 first_payment_method_id, first_plan_list_price,
                 time_from_reg, time_from_prev_exp, prev_time,
                 prev_mean_num_tot, prev_mean_total_secs_imp,
                 prev_mean_num_unq, prev_mean_av_pct_played_act,
                 prev_mean_actual_amount_paid, prev_num_cancel,
                 prev_mode_payment_id),
            ~ unique(.[!is.na(.)])) %>%
  mutate(churn = ifelse(row_number() != n(), 0, churn))%>%
  ungroup()

ulogs_trans_periods <- ulogs_trans_periods %>%
  mutate(date = as.Date(date, "1970-01-01")) %>%
  arrange(subscr_id, date)

ulogs_trans_periods <- suppressWarnings(
  ulogs_trans_periods %>%
    group_by(subscr_id) %>%
    mutate(time_start = replace_na(as.numeric(date - date[1], 'days'), 0),
           time_stop = ifelse(is.na(lead(time_start)),
                              ifelse(is.na(date),
                                     time,
                                     as.numeric(end_date - date[1], 'days') + 1),
                              lead(time_start)),
           time_stop = ifelse(time_stop == 0, 1, time_stop),
           time_int = time_stop - time_start,
           cum_pct_active = cumsum(!is.na(file))/time_stop*100,
           cum_mean_num_tot = cumsum(num_tot)/time_stop,
           cum_mean_num_unq = cumsum(num_unq)/time_stop,
           cum_mean_total_secs_imp = cumsum(total_secs_imp)/time_stop,
           mean_av_pct_played_act = replace_na(cumsum(av_pct_played)/cumsum(!is.na(file)), 0),
           cum_mean_actual_amount_paid = cumsum(actual_amount_paid)/time_stop,
           cum_num_cancel = cumsum(is_cancel),
           last_payment_method_id = zoo::na.locf(payment_method_id),
           last_plan_list_price = zoo::na.locf(plan_list_price)) %>%
    ungroup())

saveRDS(trans_periods, "trans_periods.RDS")
saveRDS(ulogs_trans_periods, "ulogs_trans_periods.RDS")

train_set <- read.csv("X_train_index.csv")
sample_X_test_id <- read.csv("sample_X_test_id.csv")

df_features <- ulogs_trans_periods %>%
  transmute(subscr_id, time_start, time_stop, churn,
            churned_before_num, 
            cityG = fct_lump_min(city, 30, other_level = "19-20"), bdG, gender,
            registered_via, month_period,
            time_from_reg, time_from_prev_exp, prev_time, 
            cum_pct_active, cum_mean_num_tot, cum_mean_total_secs_imp,
            cum_mean_num_unq, mean_av_pct_played_act, cum_mean_actual_amount_paid,
            cum_num_cancel, last_payment_method_id, last_plan_list_price)

df_train <- df_features %>%
  filter(subscr_id %in% trans_periods$subscr_id[train_set$ind + 1])

df_test <- df_features %>%
  filter(! subscr_id %in% trans_periods$subscr_id[train_set$ind + 1])

sample_X_subscr_id <- trans_periods$subscr_id[sample_X_test_id$ID + 1]

sample_test <- df_features %>%
  filter(subscr_id %in% trans_periods$subscr_id[sample_X_test_id$ID + 1])

features_baseline <- c("churned_before_num", 
                       "cityG", "bdG", "gender",
                       "registered_via", "month_period",
                       "time_from_reg", "time_from_prev_exp", "prev_time")
features_timedep <- c("cum_pct_active", "cum_mean_num_tot", "cum_mean_total_secs_imp",
                      "cum_mean_num_unq", "mean_av_pct_played_act", "cum_mean_actual_amount_paid",
                      "cum_num_cancel", "last_payment_method_id", "last_plan_list_price")  

# Predict survival after Cox PH

survest_ind <- function(estimator, id_period) {
  intervals <- df_test %>%
    filter(subscr_id == id_period) %>%
    select(time_start, time_stop, churn)
  
  covs <- data.frame(df_test %>% filter(subscr_id == id_period) %>% select(all_of(features_baseline)) %>% unique())
  covs[, features_timedep] <- 0
  covs$last_payment_method_id <- trans_periods$first_payment_method_id[trans_periods$subscr_id == id_period]
  covs$last_plan_list_price <- trans_periods$first_plan_list_price[trans_periods$subscr_id == id_period]
  
  newdata <- data.frame(covs[1, ], intervals, row.names = NULL)
  out <- survfit(estimator, newdata = newdata)
  
  survests <- list(baseline = tibble(time = out$time, 
                                     surv = out$surv[,1]))
  
  covs <- df_test %>%
    filter(subscr_id == id_period)
  
  newdata <- data.frame(covs %>% select(-subscr_id), row.names = NULL)
  out <- survfit(estimator, newdata = newdata)
  
  survests[["complete"]] <- out$surv
  
  survests
}

# Plot updated and not updated survival curves at #num points

survest_plots <- function(idt, idsurv, num=10) {
  times <- df_test %>%
    filter(subscr_id == idt) %>%
    select(time_stop) %>%
    pull()
  
  if (length(times) >= num) {
    times_select <- seq_along(times)
    times_select <- unique(c(seq(1, max(times_select), floor(max(times_select)/(num-1))), max(times_select)))
    times_select <- times[times_select]
  } else {
    times_select <- times
  }
  
  times_select <- times_select[order(times_select)]
  
  times_lab <- c("Baseline", times_select)
  
  colors <- paletteer_c("grDevices::Zissou 1", 1 + length(times_select))
  
  dfp <- tibble(time = as.numeric(idsurv$baseline$time),
                surv = as.numeric(idsurv$baseline$surv), 
                lbl = sprintf("If not updated after %s%s", times_lab[1], ""))
  p <- ggplot(dfp) +
    geom_line(aes(x = time, y = surv, linetype = lbl),
              size = 1, color = colors[1]) +
    scale_y_continuous(breaks = seq(0,1,0.2),
                       labels = scales::percent_format(accuracy = 1),
                       expand = c(0.02,0),
                       limits = c(0, 1)) +
    scale_x_continuous(breaks = seq(0, 810, 90), expand = c(0.02,0)) +
    labs(x = "Days", y = "Exp. survival probability") +
    theme_classic() 
  plots <- list(p)
  
  surv_upd <- dfp$surv[1]
  
  for (i in seq_along(times_select)) {
    dfp <- tibble(time = as.numeric(idsurv$baseline$time[idsurv$baseline$time >= times_select[i]]),
                  surv = as.numeric(idsurv$complete[idsurv$baseline$time >= times_select[i], 
                                                    which(times == times_select[i])]),
                  lbl = sprintf("If not updated after %s%s", times_lab[i+1], "d."))
    surv_upd <- c(surv_upd, dfp$surv[1])
    
    p <- ggplot(dfp) +
      geom_line(aes(x = time, y = surv, linetype = lbl),
                size = 1, color = colors[i + 1]) +
      scale_y_continuous(breaks = seq(0,1,0.2), 
                         labels = scales::percent_format(accuracy = 1),
                         expand = c(0.02,0), limits = c(0, 1)) +
      scale_x_continuous(breaks = seq(0, 810, 90), expand = c(0.02,0),
                         limits = c(0, 821)) +
      labs(x = "Days", y = "Exp. survival probability", linetype = "") +
      theme_classic()
    
    plots[[i+1]] <- p
  }
  
  dfp <- tibble(time = c(0, times_select),
                surv = surv_upd)
  
  for (i in 1:nrow(dfp)) {
    plots[[i]] <- plots[[i]] +
      geom_line(aes(x = time, y = surv, linetype = "Updated"), dfp,
                size = 0.5, color = "grey20", show.legend = TRUE) +
      geom_point(aes(x = time, y = surv), 
                 dfp[i,], shape = 21,
                 size = 3, color = "black", fill = colors[i],
                 show.legend = FALSE) +
      scale_linetype_manual(values = c("solid", "dotted")) +
      labs(linetype = "") +
      guides(linetype = guide_legend(override.aes = list(color = c(colors[i], "grey50")))) +
      theme(legend.position = c(0.7,0.9),
            legend.background = element_blank())
    
    plots[[i]]$layers <- plots[[i]]$layers[c(2,1,3)]
    
  }
  
  plots
}

Here in the train sample we used all subintervals for those subscription periods which were a part of the training sample with time-independent covariates. The same holds for the test set. Overall, instead of 17747 subscription periods in time-invariant training data and 7606 in time-invariant test data, we obtained, accordingly, 2580019 and 1098763 pseudo-observations after counting process.

# Cox

cox_td <- coxph(Surv(time = time_start, time2 = time_stop, event = churn) ~ ., 
                df_train %>% select(-subscr_id), x = TRUE)

cox_td_sum <- summary(cox_td)

# Plot gifs for updated and not updated survival curves for sampled test periods
# + one additional for illustration

for (idt in c(sample_X_subscr_id, 16739)) {
  
  test <- survest_ind(cox_td, idt)
  test_plots <- survest_plots(idt, test)
  
  dir_out <- file.path(tempdir(), paste0("surv_plots/", idt))
  dir.create(dir_out, recursive = TRUE)
  
  for (i in 1:length(test_plots)) {
    
    fp <- file.path(dir_out, paste0("id", idt, "_", 100+i, ".png"))
    
    ggsave(plot = test_plots[[i]], 
           filename = fp, width = 5, height = 3, dpi = 300,
           device = "png")
  }
  
  imgs <- list.files(dir_out, full.names = TRUE)
  img_list <- lapply(imgs, image_read)
  img_list <- lapply(img_list, image_scale, "x600")
  
  img_joined <- image_join(img_list)
  
  img_animated <- image_animate(img_joined, fps = 1)
  
  image_write(image = img_animated,
              path = paste0("test_plots_", idt, ".gif"))
}  


cox_td_conc_train <- cox_td_sum$concordance[1]
cox_td_conc_test <- concordance(cox_td, newdata = data.frame(df_test))
cox_td_conc_test <- cox_td_conc_test$concordance


cox_td_pred <- survfit(cox_td, newdata = df_test, type = "aalen",
                       id = df_test$subscr_id)

cox_td_pred_df <- data.frame(time = cox_td_pred$time,
                             prob = cox_td_pred$surv)
cox_td_pred_df$id <- cox_td_pred_df$time - replace_na(lag(cox_td_pred_df$time),3)
cox_td_pred_df$id <- cumsum(cox_td_pred_df$id <= 0)

cox_td_pred_list <- split(cox_td_pred_df, cox_td_pred_df$id)
cox_td_pred_list <- setNames(cox_td_pred_list, NULL)

cox_td_pred_list_ibs720 <- lapply(cox_td_pred_list,
                                  function(x) {
                                    x <- x[x$time <= 720, ]
                                    x <- x[,-c(1,3)]
                                    c(1, x)
                                })

cox_td_for_ibs720 <- list(survival.probs = cox_td_pred_list_ibs720,
                          survival.times = 0:720,
                          survival.tau = pmin(taus, 720))

data <- data.frame(df_test)
Survobj = Surv(data$time_start, data$time_stop, data$churn)
cox_ibs_td_pred <- sbrier_ltrc(obj = Survobj, id = data$subscr_id,
                               pred = cox_td_for_ibs720, type = "IBS")
fml = as.formula(paste0("Surv(time = time_start, time2 = time_stop, event = churn, type = 'counting') ~ ",
                        paste(features_baseline, collapse = " + "),
                        " + ",
                        paste(features_timedep, collapse = " + ")))

rrf_fit <- ltrcrrf(formula = fml, ntree = 10, mtry = 6,
                   data = data.frame(df_train), 
                   id = subscr_id)
# test
rrf_pred <- predictProb(object = rrf_fit, 
                        newdata = data.frame(df_test),
                        newdata.id = subscr_id,
                        time.eval = 0:720)
data <- data.frame(df_test)
Survobj = Surv(data$time_start, data$time_stop, data$churn)
rrf_pred_ibs <- sbrier_ltrc(obj = Survobj, id = data$subscr_id, 
                            pred = rrf_pred, type = "IBS")
rrf_pred_bs <- sbrier_ltrc(obj = Survobj, id = data$subscr_id, 
                           pred = rrf_pred, type = "BS")

rrf_pred821 <- predictProb(object = rrf_fit, 
                           newdata = data.frame(df_test),
                           newdata.id = subscr_id,
                           time.eval = 0:821)
rrf_pred_prob <- tibble(subscr_id = rep(X_test_subscr_id, each = 822),
                        time_stop = rep(0:821, times = length(X_test_subscr_id)),
                        pred_prob = as.numeric(rrf_pred821$survival.probs))
rrf_pred_prob <- rrf_pred_prob %>%
  right_join(df_test %>% select(subscr_id, time_stop), by = c("subscr_id", "time_stop"))
data <- data.frame(df_test)
rrf_conc <- concordancefit(Surv(data$time_start, data$time_stop, data$churn), 
                           x = rrf_pred_prob %>% pull(pred_prob))

# train
rrf_pred821_tr <- predictProb(object = rrf_fit, 
                              newdata = data.frame(df_train),
                              newdata.id = subscr_id,
                              time.eval = 0:821)
rrf_pred_tr_prob <- tibble(subscr_id = rep(unique(df_train$subscr_id), each = 822),
                           time_stop = rep(0:821, times = length(unique(df_train$subscr_id))),
                           pred_prob = as.numeric(rrf_pred821_tr$survival.probs))
rrf_pred_tr_prob <- rrf_pred_tr_prob %>%
  right_join(df_train %>% select(subscr_id, time_stop), by = c("subscr_id", "time_stop"))
data <- data.frame(df_train)
rrf_conc_tr <- concordancefit(Surv(data$time_start, data$time_stop, data$churn), 
                              x = rrf_pred_tr_prob %>% pull(pred_prob))

rrf_pred720_tr <- rrf_pred821_tr[2:4]
rrf_pred720_tr$survival.probs <- rrf_pred720_tr$survival.probs[1:721,]
rrf_pred720_tr$survival.times <- rrf_pred720_tr$survival.times[1:721]

data <- data.frame(df_train)
Survobj = Surv(data$time_start, data$time_stop, data$churn)
rrf_pred_bs_tr <- sbrier_ltrc(obj = Survobj, id = data$subscr_id, 
                           pred = rrf_pred720_tr, type = "BS")

Cox PH with time-dependent covariates enabled increasing Harrell’s C-index to 0.878 on the train set and 0.876 on the test set (in comparison with 0.811 and 0.809, accordingly, for Cox PH with baseline variables and data on previous subscriptions), and decreasing IBS from 0.145 to 0.109 on the test set.

cindex_all <- rbind(cindex %>%
  filter(Metric == "Harrell`s C"),
  tibble(Metric = rep("Harrell`s C", 4),
         value = c(0.878,0.917,0.876,0.887),
         model = c("Cox PH time-dep", "RSF time-dep", "Cox PH time-dep", "RSF time-dep"),
         set = c("Train", "Train", "Test", "Test"))) %>%
  mutate(group = factor(grepl("RSF", model), labels = c("Cox PH", "RSF")),
         spec = factor(ifelse(grepl("line", model), 1,
                              ifelse(grepl("prev", model), 2, 3)),
                       1:3, c("Baseline", "Prev", "TimeDep")))

ggplot() +
  geom_bar(aes(x=spec, y=value, fill=model), cindex_all,
           stat = "identity", 
           position = position_dodge(0.8), color = "white") +
  geom_text(aes(x=spec, y=y, label=lbl, group=model), 
            cindex_all %>% mutate(y = value + 0.06, lbl = round(value,2)),
            position = position_dodge(0.8), stat = "identity",
            size = 4, fontface = "bold") +
  facet_rep_grid(set ~ group, switch = "y", repeat.tick.labels = TRUE) +
  scale_y_continuous(expand = c(0,0,0,0.02), limits = c(0,1)) +
  scale_fill_manual(values = paletteer_d("ggthemes::Hue_Circle")[c(17:19, 10:12)]) +
  labs(x="", y="", fill="", title="Harrell's C-index", color="") +
  ggthemes::theme_tufte() +
  theme(legend.position = "none",
        plot.title = element_text(face = "bold", hjust = 0.5),
        axis.ticks.x = element_blank(),
        strip.text = element_text(size = 12, face = "bold"),
        axis.line = element_line(size = .2, color = "grey20"),
        axis.text.x = element_text(size = 12, face = "bold", color = "black"),
        strip.background = element_rect(fill = NA),
        strip.placement = "outside",
        panel.spacing.y=unit(1, "lines"))

bs <- list(CoxPH_base = read.csv("cox_base_bs.csv", sep=";"),
           CoxPH = read.csv("cox_bs.csv", sep=";"),
           RSF_base = read.csv("rsf_base_bs.csv", sep=";"),
           RSF = read.csv("rsf_bs.csv", sep=";"))
bs <- lapply(bs, 
             function(x) {
               x[,2] <- as.numeric(gsub(",",".", x[,2]))
               x[,3] <- as.numeric(gsub(",",".", x[,3]))
               x})
bs <- do.call(dplyr::bind_rows, bs)

bs$model <- rep(c("Cox PH baseline", "Cox PH prev", "RSF baseline", "RSF prev"), each=8)
bs <- rbind(bs[,c(1:2,4)] %>% rename(value=Train) %>% mutate(set="Train"), 
                bs[,c(1,3:4)] %>% rename(value=Test) %>% mutate(set="Test"))

bs_rsf_td <- read.csv("rrf_td_bs.csv")
bs_rsf_td <- rbind(bs_rsf_td %>%
                     filter(Time %in% c(7,14,30,60,90,180,360,720)) %>%
                     rename(value = BScore) %>%
                     mutate(model = "RSF time-dep", set = "Test") %>%
                     select(-rrf_pred_bs_tr...2.),
                   bs_rsf_td %>%
                     filter(Time %in% c(7,14,30,60,90,180,360,720)) %>%
                     rename(value = rrf_pred_bs_tr...2.) %>%
                     mutate(model = "RSF time-dep", set = "Train") %>%
                     select(-BScore))
                   
bs_all <- rbind(bs, bs_rsf_td)  %>%
  mutate(group = factor(grepl("RSF", model), labels = c("Cox PH", "RSF")),
         spec = factor(ifelse(grepl("line", model), 1,
                              ifelse(grepl("prev", model), 2, 3)),
                       1:3, c("Baseline", "Prev", "TimeDep")))

ggplot() +
  geom_line(aes(x=Time, y=value, color=model), bs_all, size = 1) +
  geom_point(aes(x=Time, y=value, color=model), bs_all, size = 2, show.legend = FALSE) +
  facet_rep_grid(~ set, switch = "y", repeat.tick.labels = TRUE) +
  scale_y_continuous(expand = c(0,0), limits = c(0, 0.18)) +
  scale_x_continuous(expand = c(0.02,0), breaks = seq(0,720,90)) +
  scale_color_manual(values = paletteer_d("ggthemes::Hue_Circle")[c(17:18, 10:12)]) +
  labs(x="Days", y="", fill="", title="Time-dependent Brier-score", color="") +
  guides(color = guide_legend(override.aes = list(size = 5))) +
  ggthemes::theme_tufte() +
  theme(legend.position = "bottom",
        plot.title = element_text(face = "bold", hjust = 0.5),
        strip.text = element_text(size = 12, face = "bold"),
        axis.line = element_line(size = .2, color = "grey20"),
        axis.text.x = element_text(size = 10),
        strip.background = element_rect(fill = NA),
        strip.placement = "outside",
        panel.spacing.y=unit(1, "lines"))


The other advantage of the models with time-dependent covariates is that they allow to update survival predictions after the start of subscription period using all new information on user’s behaviour.

For example, using results for Cox PH regression with time-dependent covariates we can estimate “updated”survival curve - it reflects survival distribution predicted using all data obtained on this subscription up to the end of the corresponding subscription period. In addition we predicted survival curve at the zero point (baseline), assuming that a user had just subscribed to the service and had not yet listened to music, so we have only member’s characteristics and data on his/her first transaction, number of previous churns and time from previous expiration date. After that we took the first time interval in subscription and several intermediate points, and predict survival with all data on user’s behavior obtained up to that point. For baseline and other time point “future” parts of predicted survival curves are shown. For the last available for that subscription time point a “future” part is a prolongation of the ‘updated’ survival curve.

Example of updating survival curve for a random user


You can see that with new data being obtained predicted survival curve is approaching to the “updated” one, while at the beginning it could be too pessimistic or too optimistic.

Also we can plot survival curves not from zero time point, but from any time conditional on surviving to this point, so we can predict survival for some next period out of 100% of those customers who remained active. For example, for the first of above users it could be like the following:

newdata <- df_test %>%
  filter(subscr_id == 16739)
newdata <- data.frame(newdata %>% select(-subscr_id), row.names = NULL)
out <- survfit(cox_td, newdata = newdata, se.fit = F, start.time = 0)
out30 <- survfit(cox_td, newdata = newdata, se.fit = F, start.time = 30)
out60 <- survfit(cox_td, newdata = newdata, se.fit = F, start.time = 60)
out90 <- survfit(cox_td, newdata = newdata, se.fit = F, start.time = 90)
out180 <- survfit(cox_td, newdata = newdata, se.fit = F, start.time = 180)
out270 <- survfit(cox_td, newdata = newdata, se.fit = F, start.time = 270)
out360 <- survfit(cox_td, newdata = newdata, se.fit = F, start.time = 360)
out450 <- survfit(cox_td, newdata = newdata, se.fit = F, start.time = 450)
out540 <- survfit(cox_td, newdata = newdata, se.fit = F, start.time = 540)

ggplot() +
  geom_line(aes(x = c(0, out$time), y = c(1, out$surv[,714]), color = "0"), size = 1) +
  geom_line(aes(x = c(30, out30$time), y = c(1, out30$surv[,714]), color = "30d"), size = 1) +
  geom_line(aes(x = c(60, out60$time), y = c(1, out60$surv[,714]), color = "60d"), size = 1) +
  geom_line(aes(x = c(90, out90$time), y = c(1, out90$surv[,714]), color = "90d"), size = 1) +
  geom_line(aes(x = c(180, out180$time), y = c(1, out180$surv[,714]), color = "180d"), size = 1) +
  geom_line(aes(x = c(270, out270$time), y = c(1, out270$surv[,714]), color = "270d"), size = 1) +
  geom_line(aes(x = c(360, out360$time), y = c(1, out360$surv[,714]), color = "360d"), size = 1) +
  geom_line(aes(x = c(450, out450$time), y = c(1, out450$surv[,714]), color = "450d"), size = 1) +
  geom_line(aes(x = c(540, out540$time), y = c(1, out540$surv[,714]), color = "540d"), size = 1) +
  scale_color_manual(values = setNames(paletteer_d("ggsci::deep_orange_material")[10:2],
                                       c("0", paste0(c(30,60,90,180,270,360,450,540), "d")))) +
  scale_y_continuous(expand = c(0,0), limits = c(0,1),
                     breaks = seq(0,1,0.2),
                     labels = scales::percent_format(accuracy = 1)) +
  scale_x_continuous(breaks = seq(0,810,90), expand = c(0,0)) +
  labs(x = "Days", y = "Conditional survival probability",
       title = "Predicted conditional survival probability", color = "") +
  theme_classic() +
  theme(legend.position = "right",
        plot.title = element_text(face = "bold", size = 12),
        panel.grid.major = element_line(size = .2, colour = "grey90"),
        panel.grid.minor = element_line(size = .2, colour = "grey90"))


We also can estimate variable importance using proportion of explainable log-likelihood in the Cox regression.

fml = as.formula(paste0("Surv(time = time_start, time2 = time_stop, event = churn) ~ ",
                        paste(features_baseline, collapse = " + "),
                        " + ",
                        paste(features_timedep, collapse = " + ")))

cox_td_rms <- cph(fml, df_train, x=TRUE, y=TRUE)
cox_td_anova_rms <- anova(cox_td_rms)
plot(cox_td_anova_rms, sort = "ascending")

Variable importance, Cox PH with time-dependent variables

Top contributors to model performance here are time-dependent variables for the cumulative number of cancellations and last payment method used by a client.


As far as coefficients and hazard ratios are concerned, they have not such clear interpretation in the Cox regression with time-dependent covariates, so we will not interpret them here.


5 Conclusion


The task of this study project was to predict churn probability using survival analysis. We found out several advantages of using these methods for this kind of business tasks. Firstly, they allow to predict not only the probability of churn over some predefined period of time, but its probability for different time points after the start of the client-company relationship. Secondly, they give information about the form of churn hazard function, which is useful in the analysis of attrition dynamics (e.g. critical points when a company loses its clients). At last, they have a baggage of helpful visual instruments to illustrate all these results.

We studied existing literature on both statistical models and machine learning techniques used for predicting survival distributions. In comparison with other tasks in machine learning, this one has several peculiarities concerning the definition of the target variable (caused by the phenomenon of censoring), its prediction and performance evaluation. As a consequence, “standard” ML models require adaptation for this task, and, as many results show, sometimes they fall behind statistical models in performance. Additional obstacles arise with the use of time-dependent predictors and time-dependent effects: almost none of ML models can work with them, or, at least, there are no enough instruments available for it.

We used a sample of observations from the dataset on KKBox music subscription service given at Kaggle. We used the same definition of churn as was given in competition, but modified the task such that we were able to use survival analysis to solve it.

After preprocessing we obtained data on subscription periods, some of which ended with user’s attrition, or censored otherwise. We divided our sample on train and test sets. As a baseline, we estimated Cox proportional hazards regression. Then, using test sample, we compared the results of this baseline model with random survival forest which has not an assumption of hazards proportionality – and it just a little outperformed Cox PH. Both models gained after addition of data on users’ behavior in their previous subscription periods, although we would not assert that this gain was significant.

Using the results from these models we predict survival curves for users from the test sample and arrange variables by their importance. We estimated Cox PH and RSF models with time-dependent covariates on users’ logs after the start of each subscription period. We showed that new information about clients gives a considerable gain in prediction performance and allows to update their survival curves and predictions in the process of customer-company relationship.

References

Abdul-Fatawu, Majeed. 2020. “Accelerated Failure Time Models.” The Journal of Risk Management and Insurance 24 (2): 12–35.
Bou-Hamad, Imad, Denis Larocque, and Hatem Ben-Ameur. 2011. “A Review of Survival Trees.” Statistics Surveys 5: 44–71.
Breheny, Patrick. n.d.a. “Accelerated Failure Time Models.” The University of Iowa. Accessed December 4, 2021. https://myweb.uiowa.edu/pbreheny/7210/f15/notes.html.
———. n.d.b. “Time-Dependent Coefficients.” The University of Iowa. Accessed December 4, 2021. https://myweb.uiowa.edu/pbreheny/7210/f15/notes.html.
Camilleri, Liberato. 2019. “History of Survival Analysis.” History of Survival Analysis, March.
Emmert-Streib, Frank, and Matthias Dehmer. 2019. “Introduction to Survival Analysis in Practice.” Machine Learning and Knowledge Extraction 1 (September): 1013–38. https://doi.org/10.3390/make1030058.
Harrell, Frank. 2015. Regression Modeling Strategies with Applications to Linear Models, Logistic and Ordinal Regression, and Survival Analysis. Springer International Publishing.
———. 2021. “Rms: Regression Modeling Strategies.” 2021. https://cran.r-project.org/web/packages/rms.
Herndon, James Emmett. 1988. “A Parametric Survival Model Which Generates Monotonic and Non-Monotonic Hazard Functions and Incorporates Time-Dependent Covariables.” PhD thesis, Citeseer.
Kassambara, Alboukadel, Marcin Kosinski, Fabian Scheipl, and Biecek Przemyslaw. 2021. “Survminer.” 2021. https://cran.r-project.org/web/packages/survminer.
Kazmi, Rashid. 2019. “Survival Analysis to Understand Customer Retention.” November 13, 2019. https://towardsdatascience.com/survival-analysis-to-understand-customer-retention-e3724f3f7ea2.
Laine, Thomas, and Eric M Reyes. 2014. “Tutorial: Survival Estimation for Cox Regression Models with Time-Varying Coefficients Using SAS and r.” Journal of Statistical Software 61 (1): 1–23.
Mills, Melinda. 2010. Introducing Survival and Event History Analysis. Sage.
Pedregosa, F., G. Varoquaux, A. Gramfort, V. Michel, B. Thirion, O. Grisel, M. Blondel, et al. 2011. “Scikit-Learn: Machine Learning in Python.” Journal of Machine Learning Research 12: 2825–30.
Pölsterl, Sebastian. 2020. “Scikit-Survival: A Library for Time-to-Event Analysis Built on Top of Scikit-Learn.” Journal of Machine Learning Research 21 (212): 1–6. http://jmlr.org/papers/v21/20-729.html.
Rebora, Paola, Agus Salim, and Marie Reilly. 2014. “Bshazard: A Flexible Tool for Nonparametric Smoothing of the Hazard Function.” R Journal 6 (2).
Rodríguez, Germán. 2007. “Chapter 7. Survival Models.” In Lecture Notes on Generalized Linear Models. https://data.princeton.edu/wws509/notes/.
Royston, Patrick, and Mahesh KB Parmar. 2002. “Flexible Parametric Proportional-Hazards and Proportional-Odds Models for Censored Survival Data, with Application to Prognostic Modelling and Estimation of Treatment Effects.” Statistics in Medicine 21 (15): 2175–97.
Schultz, Lonni R, Edward L Peterson, and Naomi Breslau. 2002. “Graphing Survival Curve Estimates for Time-Dependent Covariates.” International Journal of Methods in Psychiatric Research 11 (2): 68–74.
Simon, Noah, Jerome Friedman, Trevor Hastie, and Rob Tibshirani. 2011. “Regularization Paths for Cox’s Proportional Hazards Model via Coordinate Descent.” Journal of Statistical Software 39 (5): 1.
Sonabend, Raphael Edward Benjamin. 2021. “A Theoretical and Methodological Framework for Machine Learning in Survival Analysis: Enabling Transparent and Accessible Predictive Modelling on Right-Censored Time-to-Event Data.” PhD thesis, UCL (University College London).
Therneau, Terry M. 2021. A Package for Survival Analysis in r. https://CRAN.R-project.org/package=survival.
Therneau, Terry M, and D Watson. 2017. “The Concordance Statistic and the Cox Model.” Department of Health Science Research Mayo Clinic Technical Report 85: 1–18.
Therneau, Terry, Cindy Crowson, and Elizabeth Atkinson. 2017. “Using Time Dependent Covariates and Time Dependent Coefficients in the Cox Model.” Survival Vignettes 2: 3.
Vargas, Rafael. 2018. “Survival Analysis of Mobile Prepaid Customers Using the Weibull Distribution.”
Wang, Ping, Yan Li, and Chandan K Reddy. 2019. “Machine Learning for Survival Analysis: A Survey.” ACM Computing Surveys (CSUR) 51 (6): 1–36.
Yao, Weichi, Halina Frydman, Denis Larocque, and Jeffrey S Simonoff. 2020. “Ensemble Methods for Survival Data with Time-Varying Covariates.” arXiv Preprint arXiv:2006.00567.
Yao, Weichi, Halina Frydman, Denis Larocque, and Jeffrey S. Simonoff. 2021. LTRCforests: Ensemble Methods for Survival Data with Time-Varying Covariates.” 2021. https://cran.r-project.org/web/packages/LTRCforests.
LS0tDQp0aXRsZTogJyoqUHJlZGljdGluZyBwcm9iYWJpbGl0eSBvZiBjdXN0b21lcnMnJyBjaHVybiB1c2luZyBzdXJ2aXZhbCBtb2RlbHMqKicNCm91dHB1dDoNCiAgaHRtbF9ub3RlYm9vazoNCiAgICB0b2M6IHllcw0KICAgIHRvY19kZXB0aDogMw0KICAgIHRvY19mbG9hdDoNCiAgICAgIGNvbGxhcHNlZDogbm8NCiAgICAgIHNtb290aF9zY3JvbGw6IG5vDQogICAgbnVtYmVyX3NlY3Rpb25zOiB0cnVlDQogICAgZ2xvYmFsX251bWJlcmluZzogdHJ1ZQ0KICAgIHNtYXJ0OiBubw0KICAgIHRoZW1lOiBmbGF0bHkNCiAgICBoaWdobGlnaHQ6IHRhbmdvDQogICAgY29kZV9mb2xkaW5nOiBoaWRlDQogIHBkZl9kb2N1bWVudDoNCiAgICB0b2M6IHllcw0KICAgIHRvY19kZXB0aDogJzMnDQpiaWJsaW9ncmFwaHk6IGJpYmxpby5iaWINCmxpbmstY2l0YXRpb25zOiB5ZXMNCi0tLQ0KDQo8c3R5bGUgdHlwZT0idGV4dC9jc3MiPg0KDQpib2R5LCB0ZCB7DQogICBmb250LXNpemU6IDE2cHg7DQp9DQpoMSB7DQogIGZvbnQtc2l6ZTogMjRwdDsNCn0NCmgyIHsNCiAgZm9udC1zaXplOiAyMHB0Ow0KfQ0KaDMgew0KICBmb250LXNpemU6IDE4cHQ7DQp9DQpoNCB7DQogIGZvbnQtc2l6ZTogMTZwdDsNCn0NCmNvZGUucnsNCiAgZm9udC1zaXplOiAxMnB4Ow0KfQ0KcHJlIHsNCiAgZm9udC1zaXplOiAyMHB4DQp9DQoubWF0aCB7DQogIGZvbnQtc2l6ZTogc21hbGw7DQp9DQo8L3N0eWxlPg0KDQpgYGB7ciBzZXR1cF9yLCBlY2hvPVRSVUUsIGV2YWw9RkFMU0V9DQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkocmV0aWN1bGF0ZSkNCmxpYnJhcnkoZm9yY2F0cykNCmxpYnJhcnkobHVicmlkYXRlKQ0KbGlicmFyeShrbml0cikNCmxpYnJhcnkodnJvb20pDQpsaWJyYXJ5KGxhYmVsbGVkKQ0KbGlicmFyeShndHN1bW1hcnkpDQpsaWJyYXJ5KGdncHVicikNCmxpYnJhcnkoUkNvbG9yQnJld2VyKQ0KbGlicmFyeShsZW1vbikNCmxpYnJhcnkocGFsZXR0ZWVyKQ0KbGlicmFyeShkYnBsb3QpDQpsaWJyYXJ5KHN1cnZpdmFsKQ0KbGlicmFyeShzdXJ2bWluZXIpDQpsaWJyYXJ5KGJzaGF6YXJkKQ0KbGlicmFyeShtYWdpY2spDQpsaWJyYXJ5KGNvd3Bsb3QpDQpsaWJyYXJ5KHJtcykNCg0Kb3B0aW9ucygiZW5jb2RpbmciID0gIlVURi04IikNCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSwgbWVzc2FnZSA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0UpDQoNCmxpc3QoInN0eWxlX251bWJlci1hcmc6YmlnLm1hcmsiID0gIiIpICU+JQ0KICBzZXRfZ3RzdW1tYXJ5X3RoZW1lKCkNCmBgYA0KDQpgYGB7ciBkYXRhX2ltcG9ydH0NCnRyYW5zIDwtIHJlYWRfY3N2KCJEOi9NeURvY3VtZW50cy9LYWdnbGUvS2tib3gvZGF0YS90cmFuc19zYW1wbGUuY3N2IiwgDQogICAgICAgICAgICAgICAgICBjb2xfdHlwZXMgPSBjb2xzKHRyYW5zYWN0aW9uX2RhdGUgPSBjb2xfZGF0ZSgpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtZW1iZXJzaGlwX2V4cGlyZV9kYXRlID0gY29sX2RhdGUoKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaXNfY2FuY2VsID0gY29sX2xvZ2ljYWwoKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaXNfYXV0b19yZW5ldyA9IGNvbF9sb2dpY2FsKCksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBheW1lbnRfbWV0aG9kX2lkID0gY29sX2ZhY3RvcigpKSkNCnRyYW5zIDwtIGRpc3RpbmN0KHRyYW5zKSAlPiUNCiAgbXV0YXRlKHBheW1lbnRfbWV0aG9kX2lkID0gZmN0X2luZnJlcShwYXltZW50X21ldGhvZF9pZCkpICU+JQ0KICBhcnJhbmdlKG1zbm8sIHRyYW5zYWN0aW9uX2RhdGUpDQoNCm1lbWJlcnMgPC0gcmVhZF9jc3YoIkQ6L015RG9jdW1lbnRzL0thZ2dsZS9La2JveC9kYXRhL21lbWJlcnNfc2FtcGxlLmNzdiIsDQogICAgICAgICAgICAgICAgICAgIGNvbF90eXBlcyA9IGNvbHMocmVnaXN0cmF0aW9uX2luaXRfdGltZSA9IGNvbF9kYXRlKCksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2l0eSA9IGNvbF9mYWN0b3IoKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBnZW5kZXIgPSBjb2xfZmFjdG9yKCksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVnaXN0ZXJlZF92aWEgPSBjb2xfZmFjdG9yKCkpKQ0KbWVtYmVycyA8LSBtZW1iZXJzICU+JQ0KICBtdXRhdGUoY2l0eSA9IGZjdF9pbmZyZXEoY2l0eSksDQogICAgICAgICByZWdpc3RlcmVkX3ZpYSA9IGZjdF9pbmZyZXEocmVnaXN0ZXJlZF92aWEpLA0KICAgICAgICAgZ2VuZGVyID0gZmN0X2V4cGxpY2l0X25hKGZhY3RvcihnZW5kZXIsIGMoImZlbWFsZSIsICJtYWxlIiksIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjKCJGZW1hbGUiLCAiTWFsZSIpKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiVW5rbm93biIpLA0KICAgICAgICAgeWVhcl9yZWcgPSBmYWN0b3IoeWVhcihyZWdpc3RyYXRpb25faW5pdF90aW1lKSksDQogICAgICAgICBtb250aF9yZWcgPSBmYWN0b3IobW9udGgocmVnaXN0cmF0aW9uX2luaXRfdGltZSksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gbW9udGguYWJiKSkNCg0KdWxvZ3MgPC0gcmVhZF9jc3YoIkQ6L015RG9jdW1lbnRzL0thZ2dsZS9La2JveC9kYXRhL3Vsb2dzX3NhbXBsZS5jc3YiLA0KICAgICAgICAgICAgICAgICAgY29sX3R5cGVzID0gY29scyhkYXRlID0gY29sX2RhdGUoIiVZJW0lZCIpKSkNCnVsb2dzJG51bV90b3QgPC0gcG1hcF9kYmwodWxvZ3NbLCBwYXN0ZTAoIm51bV8iLCBjKDI1LDUwLDc1LDk4NSwxMDApKV0sIHN1bSkNCnVsb2dzIDwtIHVsb2dzICU+JQ0KICBtdXRhdGUoYXZfdGltZXNfcGxheWVkID0gbnVtX3RvdCAvIG51bV91bnEsDQogICAgICAgICBudW1fcmVwZWF0cyA9IG51bV90b3QgLSBudW1fdW5xLCANCiAgICAgICAgIHNoXzI1ID0gbnVtXzI1IC8gbnVtX3RvdCwNCiAgICAgICAgIHNoXzUwID0gbnVtXzUwIC8gbnVtX3RvdCwNCiAgICAgICAgIHNoXzc1ID0gbnVtXzc1IC8gbnVtX3RvdCwNCiAgICAgICAgIHNoXzk4NSA9IG51bV85ODUgLyBudW1fdG90LA0KICAgICAgICAgc2hfMTAwID0gbnVtXzEwMCAvIG51bV90b3QsDQogICAgICAgICBhdl9wY3RfcGxheWVkID0gKG51bV8yNSowLjEyNSArIG51bV81MCowLjM3NSArIG51bV83NSowLjYyNSArIG51bV85ODUqMC44Njc1ICsgbnVtXzEwMCowLjk5MjUpL251bV90b3QqMTAwKQ0KDQp2YXJfbGFiZWwodHJhbnMpIDwtIGxpc3QocGF5bWVudF9tZXRob2RfaWQgPSAiUGF5bWVudCBtZXRob2QiLA0KICAgICAgICAgICAgICAgICAgICAgICAgIHBheW1lbnRfcGxhbl9kYXlzID0gIlBheW1lbnQgcGxhbiBkYXlzIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICBwbGFuX2xpc3RfcHJpY2UgPSAiUGxhbiBsaXN0IHByaWNlIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICBhY3R1YWxfYW1vdW50X3BhaWQgPSAiQWN0dWFsIGFtb3VudCBwYWlkIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICBpc19hdXRvX3JlbmV3ID0gIkF1dG8tcmVuZXciLA0KICAgICAgICAgICAgICAgICAgICAgICAgIGlzX2NhbmNlbCA9ICJDYW5jZWxsYXRpb24iKQ0KDQp2YXJfbGFiZWwobWVtYmVycykgPC0gbGlzdChjaXR5ID0gIkNpdHkiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgYmQgPSAiQWdlIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGdlbmRlciA9ICJHZW5kZXIiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVnaXN0ZXJlZF92aWEgPSAiUmVnaXN0cmF0aW9uIG1ldGhvZCIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICB5ZWFyX3JlZyA9ICJZZWFyIG9mIHJlZ2lzdHJhdGlvbiBhdCB0aGUgc2VydmljZSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICBtb250aF9yZWcgPSAiTW9udGggb2YgcmVnaXN0cmF0aW9uIGF0IHRoZSBzZXJ2aWNlIikNCg0KdmFyX2xhYmVsKHVsb2dzKSA8LSBsaXN0KG51bV8yNSA9ICJOdW1iZXIgb2Ygc29uZ3MgcGxheWVkIGxlc3MgdGhhbiAyNSUgb2YgdGhlIHNvbmcgbGVuZ3RoIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICBudW1fNTAgPSAiTnVtYmVyIG9mIHNvbmdzIHBsYXllZCBiZXR3ZWVuIDI1JSB0byA1MCUgb2YgdGhlIHNvbmcgbGVuZ3RoIiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgbnVtXzc1ID0gIk51bWJlciBvZiBzb25ncyBwbGF5ZWQgYmV0d2VlbiA1MCUgdG8gNzUlIG9mIHRoZSBzb25nIGxlbmd0aCIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgIG51bV85ODUgPSAiTnVtYmVyIG9mIHNvbmdzIHBsYXllZCBiZXR3ZWVuIDc1JSB0byA5OC41JSBvZiB0aGUgc29uZyBsZW5ndGgiLCAgDQogICAgICAgICAgICAgICAgICAgICAgICAgbnVtXzEwMCA9ICJOdW1iZXIgb2Ygc29uZ3MgcGxheWVkIG92ZXIgOTguNSUgb2YgdGhlIHNvbmcgbGVuZ3RoIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICBudW1fdG90ID0gIlRvdGFsIG51bWJlciBvZiBzb25ncyBwbGF5ZWQiLA0KICAgICAgICAgICAgICAgICAgICAgICAgIG51bV91bnEgPSAiTnVtYmVyIG9mIHVuaXF1ZSBzb25ncyBwbGF5ZWQiLA0KICAgICAgICAgICAgICAgICAgICAgICAgIGF2X3RpbWVzX3BsYXllZCA9ICJBdmVyYWdlIG51bWJlciBvZiB0aW1lcyBvZiBsaXN0ZW5pbmcgdG8gb25lIHNvbmciLA0KICAgICAgICAgICAgICAgICAgICAgICAgIG51bV9yZXBlYXRzID0gIk51bWJlciBvZiByZXBlYXRzIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICBzaF8yNSA9ICJTaGFyZSBvZiBzb25ncyBwbGF5ZWQgbGVzcyB0aGFuIDI1JSBvZiB0aGUgc29uZyBsZW5ndGgiLA0KICAgICAgICAgICAgICAgICAgICAgICAgIHNoXzUwID0gIlNoYXJlIG9mIHNvbmdzIHBsYXllZCBiZXR3ZWVuIDI1JSB0byA1MCUgb2YgdGhlIHNvbmcgbGVuZ3RoIiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgc2hfNzUgPSAiU2hhcmUgb2Ygc29uZ3MgcGxheWVkIGJldHdlZW4gNTAlIHRvIDc1JSBvZiB0aGUgc29uZyBsZW5ndGgiLCANCiAgICAgICAgICAgICAgICAgICAgICAgICBzaF85ODUgPSAiU2hhcmUgb2Ygc29uZ3MgcGxheWVkIGJldHdlZW4gNzUlIHRvIDk4LjUlIG9mIHRoZSBzb25nIGxlbmd0aCIsICANCiAgICAgICAgICAgICAgICAgICAgICAgICBzaF8xMDAgPSAiU2hhcmUgb2Ygc29uZ3MgcGxheWVkIG92ZXIgOTguNSUgb2YgdGhlIHNvbmcgbGVuZ3RoIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICBhdl9wY3RfcGxheWVkID0gIkF2ZXJhZ2UgJSBvZiBvbmUgc29uZydzIGxlbmd0aCBwbGF5ZWQiLA0KICAgICAgICAgICAgICAgICAgICAgICAgIHRvdGFsX3NlY3MgPSAiVG90YWwgc2Vjb25kcyBwbGF5ZWQiKQ0KYGBgDQoNCiMgKipJbnRyb2R1Y3Rpb24qKg0KDQo8YnI+DQoNClRoaXMgc3R1ZHkgcHJvamVjdCBpcyBhaW1lZCBhdCBwcmVkaWN0aW9uIG9mIHRoZSBwcm9iYWJpbGl0eSBvZiBjdXN0b21lcnPigJkgYXR0cml0aW9uIChjaHVybikgd2l0aCBtZXRob2RzIGZyb20gc3Vydml2YWwgYW5hbHlzaXMuIEluIGNvbXBhcmlzb24gd2l0aCBzdGFuZGFyZCBjbGFzc2lmaWNhdGlvbiBhcHByb2FjaGVzIHJlc3VsdGVkIGluIHByZWRpY3Rpb24gb2YgY2h1cm4gYXMgYSBiaW5hcnkgdGFyZ2V0IHZhcmlhYmxlIG9yIHByb2JhYmlsaXR5IG9mIGNodXJuIG92ZXIgc29tZSBmaXhlZCBwZXJpb2Qgb2YgdGltZSwgc3Vydml2YWwgYW5hbHlzaXMgY2FuIGJlIHVzZWZ1bCBpbiB1bmRlcnN0YW5kaW5nIHRoZSBkeW5hbWljIG9mIGN1c3RvbWVyIHJldGVudGlvbiBhbmQgYXR0cml0aW9uIG92ZXIgdGltZSBhZnRlciBzb21lIHN0YXJ0aW5nIHBvaW50ICh1c3VhbGx5IHRoZSBzdGFydCBvZiByZWxhdGlvbnNoaXAgLSBjb250cmFjdCwgc3Vic2NyaXB0aW9uLCBldGMuKS4gSXQgY2FuIGhlbHAgaW4gYW5zd2VyaW5nIHN1Y2ggcXVlc3Rpb25zIGFzIHdoZW4gY2xpZW50cyBjaHVybiB0aGUgbW9zdCwgd2hldGhlciB0aGVyZSBhcmUgcGVyaW9kcyBvZiBpbmNyZWFzaW5nLCBzdGFibGUgb3IgZGVjcmVhc2luZyBjaHVybiByYXRlcywgYXMgd2VsbCBhcyB3aGV0aGVyIHRoZXNlIGFsbCBhcmUgZGlmZmVyZW50IGRlcGVuZGluZyBvbiBjdXN0b21lcnPigJkgY2hhcmFjdGVyaXN0aWNzLCB0aGVpciBiZWhhdmlvciBvciBzb21lIGNvbnRyYWN0IG9wdGlvbnMuIEluIGFkZGl0aW9uLCBpdCBpcyBhcHBsaWVkIGluIGVzdGltYXRpbmcgY3VzdG9tZXIgbGlmZXRpbWUgdmFsdWUgZm9yIGEgY29tcGFueS4NCg0KVGhlIGRhdGFzZXQgZm9yIHRoZSBwcm9qZWN0IHdhcyB0YWtlbiBmcm9tIFtLYWdnbGUgY29tcGV0aXRpb24g4oCcV1NETSAtIEtLQm944oCZcyBDaHVybiBQcmVkaWN0aW9uIENoYWxsZW5nZeKAnV0oaHR0cHM6Ly93d3cua2FnZ2xlLmNvbS9jL2trYm94LWNodXJuLXByZWRpY3Rpb24tY2hhbGxlbmdlLykgd2hpY2ggd2FzIGhlbGQgaW4gMjAxNy4gS0tCb3ggaXMgYW4gQXNpYW4gY29tcGFueSB3aGljaCBvZmZlcnMgc3Vic2NyaXB0aW9uIGJhc2VkIG11c2ljIHN0cmVhbWluZyBzZXJ2aWNlLiBUaGUgY3JpdGVyaWEgb2YgY2h1cm4gdXNlZCBpbiB0aGUgY29tcGV0aXRpb24gaXMgbm8gbmV3IHZhbGlkIHNlcnZpY2Ugc3Vic2NyaXB0aW9uIHdpdGhpbiAzMCBkYXlzIGFmdGVyIHRoZSBjdXJyZW50IG1lbWJlcnNoaXAgZXhwaXJlcy4gV2Ugd2lsbCB1c2UgdGhlIHNhbWUgZGVmaW5pdGlvbiwgYWx0aG91Z2ggd2Ugd2lsbCBwcmVkaWN0IG5vdCBqdXN0IHRoZSBwcm9iYWJpbGl0eSBvZiBjaHVybiBkdXJpbmcgdGhlIG5leHQgMzAgZGF5cyBhZnRlciBtZW1iZXJzaGlwIGV4cGlyYXRpb24gYXMgYSBiaW5hcnkgb3V0Y29tZSwgYnV0IHRoZSBkaXN0cmlidXRpb24gb2YgdGltZSB0byBjaHVybiBhbmQgcmV0ZW50aW9uIHJhdGVzIGZvciBkaWZmZXJlbnQgdGltZSBwb2ludHMgYWZ0ZXIgdGhlIHN0YXJ0IG9mIHN1YnNjcmlwdGlvbi4gV2Ugd2lsbCB1c2UgbW9kZWxzIGZyb20gYm90aCBzdGF0aXN0aWNhbCBhbmFseXNpcyBhbmQgbWFjaGluZSBsZWFybmluZywgYXMgd2VsbCBhcyBib3RoIGJhc2VsaW5lIGZlYXR1cmVzIChhdmFpbGFibGUgYXQgdGhlIHN0YXJ0IG9mIHN1YnNjcmlwdGlvbikgYW5kIHRpbWUtZGVwZW5kZW50IHZhcmlhYmxlcyAoaW5mb3JtYXRpb24gb24gdXNlcuKAmXMgYmVoYXZpb3IgZHVyaW5nIHN1YnNjcmlwdGlvbikuDQoNCg0KPGJyPg0KDQojICoqTGl0ZXJhdHVyZSByZXZpZXc6IHN1cnZpdmFsIGFuYWx5c2lzKioNCg0KPGJyPg0KDQpTdXJ2aXZhbCBhbmFseXNpcyBkYXRlcyBiYWNrIHRvIHRoZSAxN3RoIGNlbnR1cnkgd2hlbiBjcm9zcy1zZWN0aW9uYWwgbGlmZSB0YWJsZXMgd2VyZSBkZXZlbG9wZWQgYnkgSm9obiBHcmF1bnQgYW5kIEVkbW9uZCBIYWxsZXkuIFRoZXJlIHdlcmUgZnVydGhlciBkZXZlbG9wbWVudHMgYnkgRGFuaWVsIEJlcm5vdWxsaSBpbiAxNzYwLCB3aGVuIGhlIGNvbXB1dGVkIGxpZmUgZXhwZWN0YW5jeSB1c2luZyBsaWZlIHRhYmxlcy4gVGhlc2UgYXBwcm9hY2hlcyBhcmUgc3RpbGwgdXNlZCBpbiBkZW1vZ3JhcGh5IGFuZCBlcGlkZW1pb2xvZ3kuDQpUaHJvdWdob3V0IHRoZSBjZW50dXJpZXMsIHN1cnZpdmFsIGFuYWx5c2lzIHdhcyBzb2xlbHkgbGlua2VkIHRvIHRoZSBpbnZlc3RpZ2F0aW9uIG9mIG1vcnRhbGl0eSByYXRlczsgaG93ZXZlciwgaW4gdGhlIGxhc3QgZmV3IGRlY2FkZXMsIGFwcGxpY2F0aW9ucyBvZiBpdHMgbWV0aG9kcyBoYXZlIGJlZW4gZXh0ZW5kZWQgYmV5b25kIGJpb21lZGljYWwgcmVzZWFyY2ggdG8gb3RoZXIgZmllbGRzIHN1Y2ggYXMgY3JpbWlub2xvZ3ksIHNvY2lvbG9neSwgbWFya2V0aW5nLCBlY29ub21pY3MsIGluc3RpdHV0aW9uYWwgcmVzZWFyY2gsIGhlYWx0aCBpbnN1cmFuY2UgYW5kIEhSIHByYWN0aWNlIFtAY2FtaWxsZXJpX2hpc3RvcnlfMjAxOV0uDQoNCjxicj4NCg0KIyMgKipBZHZhbnRhZ2VzIG9mIHN1cnZpdmFsIGFuYWx5c2lzIGluIGNodXJuIHByZWRpY3Rpb24qKg0KDQo8YnI+DQoNCk91ciBtZXRob2RvbG9neSBmb3IgcHJlZGljdGluZyBjdXN0b21lcnPigJkgY2h1cm4gd2lsbCBiZSBiYXNlZCBvbiB0aGUgYXBwcm9hY2ggb2YgKipzdXJ2aXZhbCBhbmFseXNpcyoqLCBmb3Igd2hpY2ggdGhlIG1haW4gdmFyaWFibGUgb2YgaW50ZXJlc3QgaXMgdGhlIChleHBlY3RlZCkgdGltZSB0byBzb21lIGV2ZW50LiBJbiBhZGRpdGlvbiwgZm9yIGVhY2ggb2JzZXJ2YXRpb24gd2UgaGF2ZSBhIHN0YXR1cyB2YXJpYWJsZSwgdGFraW5nIHZhbHVlIDEgaWYgdGhlIGV2ZW50IGhhcHBlbmVkIGF0IHRoZSBlbmQgb2YgdGhlIGNvcnJlc3BvbmRpbmcgdGltZSBwZXJpb2QsIG9yIDAgb3RoZXJ3aXNlLiBMYXR0ZXIgb2JzZXJ2YXRpb25zIGFyZSBjYWxsZWQgKipjZW5zb3JlZCoqOiBpdCBtZWFucyB0aGF0IHdlIG9ic2VydmVkIHRoZW0gZm9yIHRoZSB3aG9sZSB0aW1lIHBlcmlvZCB3aXRob3V0IHRoZSBldmVudCwgYnV0IHdlIGFkbWl0IHRoYXQgdGhpcyBldmVudCBjYW4gaGFwcGVuIGZvciB0aGVtIGluIHRoZSBmdXR1cmUgKGFmdGVyIHRoZSBwZXJpb2Qgb2Ygb2JzZXJ2YXRpb24pLCBzbyB0aGUgdGltZSB0byBldmVudCBmb3IgdGhlbSB3aWxsIGJlIGNlbnNvcmVkIG9uIHRoZSByaWdodCBhbmQgd2lsbCBiZSBkZW5vdGVkIGFzIG9ic2VydmF0aW9uIHRpbWUrLg0KSWYgbm8gb2JzZXJ2YXRpb25zIHdlcmUgY2Vuc29yZWQsIHdlIGNvdWxkIHVzZSBzdGFuZGFyZCByZWdyZXNzaW9uIG1vZGVscyBmb3IgY29udGludW91cyByZXNwb25zZXMgdG8gcHJlZGljdCB0aW1lIHRvIGV2ZW50IG9yIG1vZGVscyBmb3IgYmluYXJ5IGNsYXNzaWZpY2F0aW9uIHRvIHByZWRpY3QgdGhlIHByb2JhYmlsaXR5IG9mIGV2ZW50IGR1cmluZyBzb21lIHByZWxpbWluYXJ5IHNwZWNpZmllZCBwZXJpb2Qgb2YgdGltZSAoZm9yIGV4YW1wbGUsIG9uZSB5ZWFyIGFmdGVyIGJlZ2lubmluZyBvZiBvYnNlcnZhdGlvbikuIEJlc2lkZXMgdGFraW5nIGNlbnNvcmluZyBpbnRvIGFjY291bnQsIGluIGNvbXBhcmlzb24gd2l0aCB0aGVzZSBtb2RlbHMgKipzdXJ2aXZhbCBhbmFseXNpcyBoYXZlIHNldmVyYWwgYWR2YW50YWdlcyoqIFtAaGFycmVsbF9yZWdyZXNzaW9uXzIwMTVdOg0KDQotIE9mIGNvdXJzZSwgYWJvdmUgYWxsLCBpdCBpcyBhIHByaW9yaXR5IG9wdGlvbiBvZiBjaG9pY2Ugd2hlbiB0aW1lIHRvIHNvbWUgZXZlbnQgaXMgb2YgdGhlIG1haW4gaW50ZXJlc3QuIEtub3dpbmcgaXRzIHZhbHVlIGNvdWxkIGJlIGFsc28gb2YgdXNlIGluIGVzdGltYXRpbmcgdXNlcuKAmXMgbGlmZXRpbWUgdmFsdWUuIEluIGFkZGl0aW9uLCB0aGUgcHJvYmFiaWxpdHkgb2Ygc3Vydml2aW5nIHBhc3QgYSBjZXJ0YWluIHRpbWUgKGZvciBleGFtcGxlLCB0aGUgcHJvYmFiaWxpdHkgdGhhdCBhIHVzZXIgd2lsbCBiZSBzdGlsbCBhY3RpdmUgYWZ0ZXIgYSBtb250aCBvciBhIHllYXIgb2Ygc3Vic2NyaXB0aW9uKSBpcyBvZnRlbiBtb3JlIHJlbGV2YW50IHRoYW4gdGhlIGV4cGVjdGVkIHN1cnZpdmFsIHRpbWUsIGFuZCBzdXJ2aXZhbCBhbmFseXNpcyBoYXMgYSBwb3RlbnRpYWwgaW4gcHJlZGljdGluZyB0aGlzIHByb2JhYmlsaXR5Lg0KDQotIFRpbWUgdG8gZXZlbnQgY2FuIGhhdmUgYW4gdW51c3VhbCBkaXN0cmlidXRpb246IGl0IGlzIHJlc3RyaWN0ZWQgdG8gYmUgcG9zaXRpdmUsIHNvIGl0IGhhcyBhIHNrZXdlZCBkaXN0cmlidXRpb24gYW5kIHdpbGwgbmV2ZXIgYmUgbm9ybWFsbHkgZGlzdHJpYnV0ZWQuIEl0IGlzIG5vdCBhIHByb2JsZW0gZm9yIHN1cnZpdmFsIGFuYWx5c2lzLCBidXQgY2FuIGJlIGEgcHJvYmxlbSB0byBzdGFuZGFyZCBtb2RlbHMgd2l0aCBjb250aW51b3VzIHRhcmdldCB2YXJpYWJsZS4NCg0KLSBPbmUgb2YgdGhlIGZ1bmN0aW9ucyB1c2VkIGluIHN1cnZpdmFsIGFuYWx5c2lzLCB0aGUgaGF6YXJkIGZ1bmN0aW9uLCBoZWxwcyB0byB1bmRlcnN0YW5kIHRoZSBtZWNoYW5pc20gb2YgZmFpbHVyZSAoaW4gb3VyIGNhc2UgdGhlIG1lY2hhbmlzbSBvZiBjaHVybikgYW5kIGhvdyBpdCBjaGFuZ2VzIG92ZXIgdGhlIHN1cnZpdmFsIHRpbWUgKHBlcmlvZCBvZiBzdWJzY3JpcHRpb24pLiBNb3Jlb3Zlciwgd2UgY2FuIHByZWRpY3QgcGF0dGVybnMgaW4gb3V0Y29tZSAoY2h1cm4gYmVoYXZpb3IpIG92ZXIgdGltZSBmb3IgaW5kaXZpZHVhbCBzdWJqZWN0cy4NCg0KICAgIEZvciBleGFtcGxlLCBpbiBkZXBlbmRlbmNlIG9mIHN1YnNjcmlwdGlvbiBzZXJ2aWNlIHBlY3VsaWFyaXRpZXMgKGUuZy4gcHJlcGFpZCBvciBwb3N0cGFpZCwgZnJlZS10cmlhbCBwZXJpb2RzLCBzdWJzY3JpcHRpb24gcGxhbnMsIGV0Yy4pIHRoZXJlIGNhbiBiZSBkaWZmZXJlbnQgdHlwZXMgb2YgaGF6YXJkIGZ1bmN0aW9ucyBbQHZhcmdhc19zdXJ2aXZhbF8yMDE4XToNCg0KYGBge3IgY2h1cm5faGF6YXJkcywgZmlnLmNhcD0iSGF6YXJkIHJhdGUgY3VydmVzIiwgZXZhbD1UUlVFLCBvdXQud2lkdGg9IjMwMHB4IiwgZmlnLmFsaWduPSdjZW50ZXInfQ0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoIkQ6L015RG9jdW1lbnRzL0thZ2dsZS9La2JveC9oYXphcmRfY3VydmVzLmpwZyIpDQpgYGANCg0KPGJyPg0KDQojIyAqKkNvbmNlcHR1YWwgZnJhbWV3b3JrIG9mIHN1cnZpdmFsIGFuYWx5c2lzKioNCg0KPGJyPg0KDQpJbiBvdXIgc3R1ZHkgdGhlIGV2ZW50IGlzIGEgY2h1cm4sIGkuZS4gYSBtZW1iZXJzaGlwIGV4cGlyYXRpb24gc3VjaCB0aGF0IGR1cmluZyB0aGUgbmV4dCAzMCBkYXlzIGl0IGlzIG5vdCByZWFjdGl2YXRlZC4gSXQgbWVhbnMgdGhhdCBpZiAzMCBkYXlzIGFmdGVyIHRoZSBlbmQgb2YgdGhlIHN1YnNjcmlwdGlvbiBwZXJpb2QgaGF2ZW4ndCBwYXNzZWQgeWV0LCB3ZSBhcmUgbm90IGFibGUgdG8gdGVsbCB3aGV0aGVyIHRoaXMgY2xpZW50IGNodXJuZWQsIGFuZCB3ZSBzaG91bGQgY29uc2lkZXIgaGltL2hlciBhcyBjZW5zb3JlZC4NCk91ciB0YXJnZXQgdmFyaWFibGUgaXMgdGltZSB0byBldmVudCAoKlQqKSAtIHRoZSB0aW1lIGJldHdlZW4gdGhlIHN0YXJ0IGFuZCB0aGUgZW5kIG9mIHN1YnNjcmlwdGlvbi4gV2Ugd2lsbCBkZWZpbmUgdGhlIHByaW5jaXBsZXMgb2YgZGV0ZXJtaW5pbmcgdGhlIHN0YXJ0IGFuZCB0aGUgZW5kIHBvaW50cyBvZiBzdWJzY3JpcHRpb24gaW4gbW9yZSBkZXRhaWwgYmVsb3cuDQoNCldlIHdpbGwgYXNzdW1lIHRoYXQgKlQqIGlzIGEgY29udGludW91cyByYW5kb20gdmFyaWFibGUgd2l0aCBwcm9iYWJpbGl0eSBkZW5zaXR5IGZ1bmN0aW9uIGYodCkgYW5kIGN1bXVsYXRpdmUgZGlzdHJpYnV0aW9uIGZ1bmN0aW9uICRGKHQpID0gUChUIDwgdCkkLCBnaXZpbmcgdGhlIHByb2JhYmlsaXR5IHRoYXQgdGhlIGV2ZW50IGhhcyBvY2N1cnJlZCBieSB0aW1lIHQuIEluIHN1cnZpdmFsIGFuYWx5c2lzIHRoZSBzdGF0aXN0aWNhbCBtb2RlbCBmb3IgdGltZSB0byBldmVudCAoKlQqKSBpcyB1c3VhbGx5IGRlZmluZWQgd2l0aCB0aGUgY29tcGxlbWVudCBvZiBjdW11bGF0aXZlIGRlbnNpdHkgZnVuY3Rpb24gW0Byb2RyaWd1ZXpfY2hhcHRlcl8yMDA3XSwgY2FsbGVkIGEgKipzdXJ2aXZhbCBmdW5jdGlvbiBTKHQpKiouIFN1cnZpdmFsIGZ1bmN0aW9uIGlzIHRoZSBwcm9iYWJpbGl0eSB0aGF0IGFuIGluZGl2aWR1YWwgc3Vydml2ZXMgZnJvbSB0aGUgdGltZSBvcmlnaW4gdG8gYSBzcGVjaWZpZWQgdGltZSAqdCogb3IsIGVxdWl2YWxlbnRseSwgdGhhdCB0aGUgZXZlbnQgaGFzIG5vdCBvY2N1cnJlZCBieSB0aW1lICp0KiBhbmQgd2lsbCAobWF5KSBvY2N1ciBhZnRlciBpdC4gSW4gb3VyIGFuYWx5c2lzIGl0IHdpbGwgYmUgdGhlIHByb2JhYmlsaXR5IHRoYXQgYSB1c2VyIHJlbWFpbnMgYWN0aXZlIGZyb20gdGhlIHN0YXJ0IG9mIGhpcy9oZXIgc3Vic2NyaXB0aW9uIHRvIGEgc3BlY2lmaWVkIHRpbWUgcG9pbnQgKnQqIChvciBwcm9iYWJpbGl0eSB0aGF0IGhlL3NoZSBjaHVybiBzb21lIHRpbWUgYWZ0ZXIgKnQqKTogJCRTKHQpID0gUChUPnQpID0gMS1GKHQpPVxpbnRfdF5caW5mdHkgZih0KSQkIA0KU3Vydml2YWwgZnVuY3Rpb24gaXMgbm9uLWluY3JlYXNpbmcgd2l0aCAqdCogYW5kIGVxdWFscyAxIGF0ICp0KiA9IDAuDQoNCkFub3RoZXIgaW1wb3J0YW50IHRlcm0gaW4gc3Vydml2YWwgYW5hbHlzaXMgaXMgKipoYXphcmQgaCh0KSoqIC0gdGhlIHByb2JhYmlsaXR5IHRoYXQgYW4gaW5kaXZpZHVhbCB3aG8gaXMgc3RpbGwgb2JzZXJ2ZWQgYXQgdGltZSAqdCogaGFzIGFuIGV2ZW50IGluIHNtYWxsIGludGVydmFsIGFmdGVyICp0KjogJCRoKHQpPVxsaW1fe3UgXHRvIDB9XGZyYWN7UCh0PFQ8dCt1IHwgVD50KX17dX0gPSBcZnJhY3tmKHQpfXtTKHQpfT0tXGZyYWN7ZH17ZHR9bG9nUyh0KSQkDQpIYXphcmQgd2lsbCBjaGFyYWN0ZXJpemUgdGhlIGludGVuc2l0eSBvZiBjaHVybiBvdmVyIHRpbWUsIGkuZS4gYSByYXRlIG9mIGNodXJuIHBlciB1bml0IG9mIHRpbWUuIEFzIHRoZSB3aWR0aCBvZiB0aGUgaW50ZXJ2YWwgKCp1KikgZ29lcyB0byB6ZXJvLCB3ZSBvYnRhaW4gYW4gaW5zdGFudGFuZW91cyByYXRlIG9mIGNodXJuIGZvciBzb21lIHNwZWNpZmljIHRpbWUgcG9pbnQuDQoNClRoZSBudW1lcmF0b3IgaW4gdGhlIGZpcnN0IHBhcnQgb2YgdGhlIGFib3ZlIGV4cHJlc3Npb24gaXMgdGhlIGNvbmRpdGlvbmFsIHByb2JhYmlsaXR5IHRoYXQgdGhlIGNodXJuIHdpbGwgb2NjdXIgaW4gdGhlIHNtYWxsIGludGVydmFsIGFmdGVyICp0KiAoKlt0LCB0K3UpKikgZ2l2ZW4gdGhhdCBpdCBoYXMgbm90IG9jY3VycmVkIGJlZm9yZSwgYW5kIHRoZSBkZW5vbWluYXRvciBpcyB0aGUgd2lkdGggb2YgdGhlIGludGVydmFsICgqdSopLiBUaGUgc2Vjb25kIHBhcnQgb2YgdGhlIGZvcm11bGEgc2F5cyB0aGF0IHRoZSBoYXphcmQgcmF0ZSBvZiBjaHVybiBhdCB0aW1lICp0KiBlcXVhbHMgdGhlIGRlbnNpdHkgb2YgY2h1cm5zIGF0ICp0KiwgZGl2aWRlZCBieSB0aGUgcHJvYmFiaWxpdHkgb2YgbWVtYmVyc2hpcCBsYXN0aW5nIHRvIHRoYXQgcG9pbnQgaW4gdGltZSB3aXRob3V0IGNodXJuaW5nLiBJbiBhZGRpdGlvbiwgaXQgY2FuIGJlIHByb3ZlbiB0aGF0ICpoKHQpKiBlcXVhbHMgdGhlIG1pbnVzIHNsb3BlIG9mIHRoZSBsb2cgb2YgdGhlIHN1cnZpdmFsIGZ1bmN0aW9uLiBUaGUgbGF0dGVyIGdpdmVzIHVzIHRoZSBmb3JtdWxhIFtAcm9kcmlndWV6X2NoYXB0ZXJfMjAwN106ICQkUyh0KSA9IGV4cCgtXGludF8wXnQgaCh2KWR2KSQkDQoNCkF0IGxhc3QsIHRoZSBhcmVhIHVuZGVyIHRoZSBjdXJ2ZSBmb3IgaGF6YXJkICpoKHQpKiBpcyBhICoqY3VtdWxhdGl2ZSBoYXphcmQgZnVuY3Rpb24gKCpIKHQpKikqKjogJCRIKHQpID0gXGludF8wXnQgaCh2KWR2ID0gLWxvZ1ModCkkJCANCkl0IHByZXNlbnRzIHRoZSBzdW0gb2YgdGhlIHJpc2tzIHlvdSBmYWNlIGdvaW5nIGZyb20gZHVyYXRpb24gMCB0byB0IFtAcm9kcmlndWV6X2NoYXB0ZXJfMjAwN10uIEZyb20gdGhlIGxhc3QgZXF1YXRpb24gd2UgaGF2ZSAkJFModCkgPSBleHAoLUgodCkpJCQNCg0KVGhlcmVmb3JlLCBrbm93aW5nIGFueSBvbmUgb2YgdGhlIGZ1bmN0aW9ucyAqUyh0KSosICpIKHQpKiwgb3IgKmgodCkqIGFsbG93cyBvbmUgdG8gZGVyaXZlIHRoZSBvdGhlciB0d28gZnVuY3Rpb25zLiBUaGVzZSB0aHJlZSBmdW5jdGlvbnMgYXJlIGRpZmZlcmVudCB3YXlzIG9mIGRlc2NyaWJpbmcgdGhlIHNhbWUgZGlzdHJpYnV0aW9uIGZvciB0aGUgdGltZSB0byBldmVudC4NCg0KPGJyPg0KDQpPbmUgb2YgdGhlIHByb2JsZW1zIGluIHN1cnZpdmFsIGFuYWx5c2lzIGlzIHRoYXQgdGhlIGZvcm0gb2YgdGhlIHRydWUgcG9wdWxhdGlvbiBzdXJ2aXZhbCBkaXN0cmlidXRpb24gZnVuY3Rpb24gKlModCkqIGlzIGFsbW9zdCBhbHdheXMgdW5rbm93bi4gQXMgYSBjb25zZXF1ZW5jZSwgd2Ugc2hvdWxkIHVzZSBzb21lIGVzdGltYXRlIGZvciBpdC4gSWYgdGhlcmUgaXMgbm8gY2Vuc29yaW5nLCB3ZSBjYW4gdXNlIGVtcGlyaWNhbCBkaXN0cmlidXRpb24gZnVuY3Rpb24gb2YgdGltZSB0byBldmVudCwgJEZfbih0KSQsIHRvIG5vbnBhcmFtZXRyaWNhbGx5IGVzdGltYXRlIHN1cnZpdmFsIGFzICRTX24odCkgPSAxIC0gRl9uKHQpID0gW1x0ZXh0e251bWJlciBvZiB9VF9pID4gdF0vbiQgW0BoYXJyZWxsX3JlZ3Jlc3Npb25fMjAxNV0uIFdpdGggY2Vuc29yaW5nIHRha2luZyBwbGFjZSB3ZSBjYW4gdXNlIGVpdGhlciBvdGhlciBub25wYXJhbWV0cmljIGVzdGltYXRlcyAodGhlIG1vc3QgZnJlcXVlbnRseSB1c2VkIGhlcmUgYXJlICoqS2FwbGFu4oCTTWVpZXIgcHJvZHVjdC1saW1pdCBlc3RpbWF0b3IqKiBhbmQgKipOZWxzb24tQWFsZW4gZXN0aW1hdG9yKiopIG9yICoqcGFyYW1ldHJpY2FsbHkgZXN0aW1hdGUgdGhlIHN1cnZpdmFsIGZ1bmN0aW9uKiogKGl0IG1lYW5zIHRoYXQgd2Ugc2hvdWxkIHNwZWNpZnkgYSBmdW5jdGlvbmFsIGZvcm0gZm9yICpTKHQpKiBhbmQgZXN0aW1hdGUgaXRzIHVua25vd24gcGFyYW1ldGVycykuIFBhcmFtZXRyaWMgbW9kZWxzIGdpdmUgbW9yZSBwcmVjaXNlIGFuZCBzbW9vdGggZXN0aW1hdGVzIGZvciBzdXJ2aXZhbCwgaGF6YXJkIGFuZCBjdW11bGF0aXZlIGhhemFyZCwgYWxsb3dzIHRvIGVhc2lseSBjb21wdXRlIHF1YW50aWxlcyBmb3IgdGltZSB0byBldmVudCBhbmQgdG8gcHJlZGljdCBpdCwgYnV0IHRoZXkgcmVxdWlyZSBhY2N1cmF0ZSBzcGVjaWZpY2F0aW9uIG9mIGRpc3RyaWJ1dGlvbiBmdW5jdGlvbi4NCg0KQmVsb3cgdGhlIGV4YW1wbGUgb2YgS2FwbGFuLU1laWVyIHN1cnZpdmFsIGN1cnZlIGlzIGdpdmVuIFtAa2F6bWlfc3Vydml2YWxfMjAxOV06DQoNCmBgYHtyIGttX2V4YW1wbGUsIGZpZy5jYXA9IkhhemFyZCByYXRlIGN1cnZlcyIsIGV2YWw9VFJVRSwgb3V0LndpZHRoPSIzMDBweCIsIGZpZy5hbGlnbj0nY2VudGVyJ30NCmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKCJEOi9NeURvY3VtZW50cy9LYWdnbGUvS2tib3gvc3VydmN1cnZfZXhhbXBsZS5qcGVnIikNCmBgYA0KDQpUaGVyZSBhcmUgc2V2ZXJhbCB0eXBlcyBvZiBtb3N0IGZyZXF1ZW50bHkgdXNlZCBwYXJhbWV0cmljIG1vZGVscyBmb3Igc3Vydml2YWwgZnVuY3Rpb246IFdlaWJ1bGwsIGV4cG9uZW50aWFsIChpdCBpcyBhIFdlaWJ1bGwgZGlzdHJpYnV0aW9uIHdpdGggc2hhcGUgPSAxKSwgbG9nLW5vcm1hbCwgbG9nLWxvZ2lzdGljIGFuZCBnYW1tYS4gRm9yIG1vcmUgZXhhbXBsZXMgb2YgZGlzdHJpYnV0aW9ucyBhbmQgdGhlaXIgY2hhcmFjdGVyaXN0aWNzIHNlZSBAaGVybmRvbl9wYXJhbWV0cmljXzE5ODguIFRoZXNlIGRpc3RyaWJ1dGlvbnMgY2FuIGJlIHVzZWZ1bCBmb3IgbW9kZWxsaW5nIGRpZmZlcmVudCBwYXR0ZXJucyBvZiBoYXphcmQgYmVoYXZpb3I6IGZvciBleGFtcGxlLCBleHBvbmVudGlhbCAtIGZvciB0aGUgaGF6YXJkIHRoYXQgaXMgY29uc3RhbnQgaW4gdGltZSwgV2VpYnVsbCwgR29tcGVydHosIGdhbW1hIOKAkyBmb3IgbW9ub3RvbmljYWxseSBkZWNyZWFzaW5nIG9yIGluY3JlYXNpbmcgaGF6YXJkLCB3aGlsZSBsb2ctbG9naXN0aWMsIGxvZy1ub3JtYWwsIGdlbmVyYWxpemVkIGdhbW1hIGFuZCBnZW5lcmFsaXplZCBGIGNhbiBiZSB1c2VkIGZvciBtb2RlbGxpbmcgYmF0aHR1Yi1zaGFwZWQgaGF6YXJkIGZ1bmN0aW9ucy4gDQoNCkhlcmUgYXJlIHNldmVyYWwgZXhhbXBsZXMgb2Ygc3Vydml2YWwsIGhhemFyZCBhbmQgZGVuc2l0eSBwbG90cyBmb3IgZm91ciBvZiB0aGVzZSBkaXN0cmlidXRpb25zIHdpdGggZGlmZmVyZW50IHBhcmFtZXRlcnMgW0BlbW1lcnQtc3RyZWliX2ludHJvZHVjdGlvbl8yMDE5XToNCg0KYGBge3IgcGFyYW1fc3VydiwgZmlnLmNhcD0iSGF6YXJkIHJhdGUgY3VydmVzIiwgZXZhbD1UUlVFLCBvdXQud2lkdGg9IjMwMHB4IiwgZmlnLmFsaWduPSdjZW50ZXInfQ0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoIkQ6L015RG9jdW1lbnRzL0thZ2dsZS9La2JveC9wYXJhbV9zdXJ2LmpwZyIpDQpgYGANCg0KRm9yIFtnYW1tYSBkaXN0cmlidXRpb25dKGh0dHBzOi8vd3d3Lml0bC5uaXN0Lmdvdi9kaXY4OTgvaGFuZGJvb2svZWRhL3NlY3Rpb24zL2VkYTM2NmIuaHRtKToNCg0KYGBge3IgZ2FtbWFfc3VydiwgZmlnLmNhcD0iR2FtbWEiLCBmaWcuc2hvdz0naG9sZCcsIGV2YWw9VFJVRSwgb3V0LndpZHRoPSIzMDBweCIsIGZpZy5hbGlnbj0nY2VudGVyJ30NCmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKGMoIkQ6L015RG9jdW1lbnRzL0thZ2dsZS9La2JveC9nYW1oYXouanBnIiwgIkQ6L015RG9jdW1lbnRzL0thZ2dsZS9La2JveC9nYW1zdXJ2LmpwZyIpKQ0KYGBgDQoNCkluIG1vcmUgZ2VuZXJhbCBjYXNlIHdlIGNhbiB1c2UgYW55IGRpc3RyaWJ1dGlvbiBkZWZpbmVkIGZvciAkdCBcaW4gWzAsXGluZnR5KSQgb3IgZm9yIHRyYW5zZm9ybWVkIHRpbWUgdG8gZXZlbnQgKHVzdWFsbHkgbG9nIHRyYW5zZm9ybWVkKSBvciBtb2RlbHMgZGVyaXZlZCBmcm9tIHNwbGluZSBzbW9vdGhpbmcgZm9yIGhhemFyZCBmdW5jdGlvbnMuDQoNCjxicj4NCg0KRm9yIGVzdGltYXRpbmcgdGhlIHVua25vd24gcGFyYW1ldGVycyBvZiBzdXJ2aXZhbCBmdW5jdGlvbiAqUyh0KSogKiptYXhpbXVtIGxpa2VsaWhvb2QgZXN0aW1hdGlvbioqIGlzIGFwcGxpZWQuIEJ1dCBpcyBoYXMgc29tZSBwZWN1bGlhcml0aWVzIGluIHRoZSBwcmVzZW5jZSBvZiBjZW5zb3JpbmcsIGkuZS4gdW5vYnNlcnZlZCBhY3R1YWwgdGltZSB0byBldmVudCBmb3Igc29tZSBzdWJqZWN0cy4NCg0KSWYgZm9yIHRoZSBpLXRoIHN1YmplY3Qgd2UgZGVub3RlIHRoZSBjZW5zb3JpbmcgdGltZSBhcyAkQ19pJCBhbmQgdGhlIHRpbWUgdG8gZXZlbnQgYXMgJFRfaSQsIHRoZW4gd2UgY2FuIGRlZmluZSB0aGUgZXZlbnQgaW5kaWNhdG9yIGFzOiAkJGVfaSA9IFxiZWdpbntjYXNlc30NCiAgMSwgJiBpZiBcdGV4dHsgfSBUX2kgXGxlcSBDX2lcXA0KICAwLCAmIGlmIFx0ZXh0eyB9IFRfaSA+IENfaQ0KXGVuZHtjYXNlc30kJCBhbmQgdGhlIG9ic2VydmVkIHRhcmdldCB2YXJpYWJsZSBhczogJCRZX2kgPSBtaW4oVF9pLCBDX2kpJCQNCg0KVGh1cywgdGhlIHBhaXIgJChZX2ksIGVfaSkkIGNvbnRhaW5zIGFsbCB0aGUgaW5mb3JtYXRpb24gd2UgbmVlZCB0byBrbm93IGFib3V0IHRoZSB0YXJnZXQgdmFyaWFibGUsIGFuZCBpdCBpcyB1c3VhbGx5IHVzZWQgaW4gcmVhbGl6YXRpb24gb2Ygc3Vydml2YWwgbW9kZWxzIGluIGRpZmZlcmVudCBsaWJyYXJpZXMgaW4gUHl0aG9uIGFuZCBSLg0KDQpJZiAkWV9pJCBpcyB1bmNlbnNvcmVkLCBvYnNlcnZhdGlvbiAqaSogY29udHJpYnV0ZXMgYSBmYWN0b3IgdG8gdGhlIGxpa2VsaWhvb2QgZXF1YWwgdG8gdGhlIGRlbnNpdHkgZnVuY3Rpb24gZm9yICpUKiBldmFsdWF0ZWQgYXQgJFlfaSQsICRmKFlfaSkkLiBJZiAkWV9pJCByZXByZXNlbnRzIGEgY2Vuc29yZWQgdGltZSwgaXQgaXMgb25seSBrbm93biB0aGF0ICRUX2kgPiBZX2kkLiBUaGUgY29udHJpYnV0aW9uIHRvIHRoZSBsaWtlbGlob29kIGZ1bmN0aW9uIGlzIHRoZW4gdGhlIHByb2JhYmlsaXR5IHRoYXQgJFRfaSA+IENfaSQsIHdoaWNoIGVxdWFscyB0byBwcm9iYWJpbGl0eSB0aGF0ICRUX2kgPiBZX2kkLiBUaGlzIHByb2JhYmlsaXR5IGlzICRTKFlfaSkkLiBTbyB0aGUgam9pbnQgbGlrZWxpaG9vZCBvdmVyIG4gb2JzZXJ2YXRpb25zIGlzIFtAaGFycmVsbF9yZWdyZXNzaW9uXzIwMTVdOiAkJEwgPSBccHJvZF97aTpZX2lcdGV4dHsgdW5jZW5zb3JlZH19XntufSBmKFlfaSkgXHByb2Rfe2k6WV9pXHRleHR7IGNlbnNvcmVkfX1ee259IFMoWV9pKSQkICghIGluIHRoZSBhc3N1bXB0aW9uIG9mIG5vbi1pbmZvcm1hdGl2ZSwgaS5lLiBpbmRlcGVuZGVudCBvZiB0aGUgcmlzayBvZiB0aGUgZXZlbnQsIGNlbnNvcmluZykuIFRoZSBtb2RlbCBpcyBlc3RpbWF0ZWQgYnkgbWF4aW1pemluZyB0aGlzIGxpa2VsaWhvb2Qgb3IsIGVxdWl2YWxlbnRseSwgdGhlIGxvZyBsaWtlbGlob29kOiAkJGxvZ0wgPSBcc3VtX3tpOllfaVx0ZXh0eyB1bmNlbnNvcmVkfX1ebiBsb2cgKGgoWV9pKSkgLSBcc3VtX3tpPTF9Xm4gSChZX2kpJCQNCg0KQWxsIG9ic2VydmF0aW9ucyB0aGVuIGNvbnRyaWJ1dGUgYW4gYW1vdW50IHRvIHRoZSBsb2cgbGlrZWxpaG9vZCBlcXVhbCB0byB0aGUgbmVnYXRpdmUgb2YgdGhlIGN1bXVsYXRpdmUgaGF6YXJkIGV2YWx1YXRlZCBhdCB0aGUgZXZlbnQvIGNlbnNvcmluZyB0aW1lLiBJbiBhZGRpdGlvbiwgdW5jZW5zb3JlZCBvYnNlcnZhdGlvbnMgY29udHJpYnV0ZSBhbiBhbW91bnQgZXF1YWwgdG8gdGhlIGxvZyBvZiB0aGUgaGF6YXJkIGZ1bmN0aW9uIGV2YWx1YXRlZCBhdCB0aGUgdGltZSBvZiBldmVudC4gT25jZSAqTCogb3IgKmxvZyBMKiBpcyBzcGVjaWZpZWQsIHRoZSBnZW5lcmFsIG1heGltdW0gbGlrZWxpaG9vZCBtZXRob2RzIGNhbiBiZSB1c2VkIHdpdGhvdXQgY2hhbmdlIGluIG1vc3Qgc2l0dWF0aW9ucy4gVGhlIHByaW5jaXBhbCBkaWZmZXJlbmNlIGlzIHRoYXQgY2Vuc29yZWQgb2JzZXJ2YXRpb25zIGNvbnRyaWJ1dGUgbGVzcyBpbmZvcm1hdGlvbiB0byB0aGUgc3RhdGlzdGljYWwgaW5mZXJlbmNlIHRoYW4gdW5jZW5zb3JlZCBvbmVzLg0KDQo8YnI+DQoNCiMjICoqUHJlZGljdGlvbiBtb2RlbHMgaW4gc3Vydml2YWwgYW5hbHlzaXMqKg0KDQo8YnI+DQoNCk9mIGNvdXJzZSwgaWYgd2UgYXJlIHRvIHByZWRpY3QgYSB0aW1lIHRvIGV2ZW50IG9yIHN1cnZpdmFsIGRpc3RyaWJ1dGlvbiwgd2UgdXN1YWxseSB3b3VsZCBsaWtlIHRvIGluY2x1ZGUgc29tZSBwcmVkaWN0b3JzIGludG8gb3VyIGFuYWx5c2lzLiBJdCBjb3VsZCBiZSBkb25lIGJ5IGdlbmVyYWxpemluZyBzdXJ2aXZhbCBtb2RlbCBpbnRvIHN1cnZpdmFsIHJlZ3Jlc3Npb24gbW9kZWwgKHdoaWNoIGlzIHR5cGljYWwgZm9yIHRyYWRpdGlvbmFsIHN0YXRpc3RpY2FsIGFuYWx5c2lzKSBhbmQgZXN0aW1hdGUgaXQgdXNpbmcgc3RhdGlzdGljYWwgb3IgbWFjaGluZSBsZWFybmluZyBiYXNlZCBtZXRob2RzLiBTdGF0aXN0aWNhbCBtZXRob2RzIGZvY3VzIG1vcmUgb24gY2hhcmFjdGVyaXppbmcgYm90aCB0aGUgZGlzdHJpYnV0aW9ucyBvZiB0aGUgZXZlbnQgdGltZXMgYW5kIHRoZSBzdGF0aXN0aWNhbCBwcm9wZXJ0aWVzIG9mIHRoZSBwYXJhbWV0ZXIgZXN0aW1hdGlvbi4gTWFjaGluZSBsZWFybmluZyBtZXRob2RzIGFyZSB1c3VhbGx5IGxlc3MgY29uc2VydmF0aXZlIGluIHRoZWlyIGFzc3VtcHRpb25zLiBUaGUgdGF4b25vbXkgb2YgYWxsIG1ldGhvZHMgZGV2ZWxvcGVkIGZvciBzdXJ2aXZhbCBhbmFseXNpcyBpcyBwcmVzZW50ZWQgYmVsb3cgW0B3YW5nX21hY2hpbmVfMjAxOV06DQoNCmBgYHtyIHN1cnZfbW9kZWxzLCBmaWcuY2FwPSJTdXJ2aXZhbCBBbmFseXNpcyBNb2RlbHMiLCBldmFsPVRSVUUsIG91dC53aWR0aD0iMzAwcHgiLCBmaWcuYWxpZ249J2NlbnRlcid9DQprbml0cjo6aW5jbHVkZV9ncmFwaGljcygiRDovTXlEb2N1bWVudHMvS2FnZ2xlL0trYm94L3N1cnZfbWV0aG9kcy5wbmciKQ0KYGBgDQoNCjxicj4NCg0KT3ZlcmFsbCwgKipzdXJ2aXZhbCBtb2RlbHMgYXJlIHVzZWQgdG8gcHJlZGljdCBvbmUgb3IgYWxsIG9mIHRoZSBmb2xsb3dpbmcgbWVhc3VyZXMqKiBbQHNvbmFiZW5kX3RoZW9yZXRpY2FsXzIwMjFdOg0KDQotIFRoZSB0aW1lIHRvIGV2ZW50OiAkJGc6IFggXHRvIFQkJA0Kd2hlcmUgJFggXHN1YnNldGVxIFxtYXRoYmJ7Un1ebSQgaXMgYSBtYXRyaXggZm9yIG0gZmVhdHVyZXMsICRUJCAtIHByZWRpY3RlZCB0aW1lIHRvIGV2ZW50LCAkZyQgLSBhIHByZWRpY3Rpb24gZnVuY3Rpb25hbCwgd2hpY2ggaXMgdHJhaW5lZCBvbiB0aGUgdHJhaW5pbmcgc2V0ICQoWCxZLGUpJCB3aXRoIHRoZSBvYnNlcnZlZCB0aW1lICRZIFxzdWJzZXRlcSBcbWF0aGJie1J9X3tcZ2VxIDB9JCAoc29tZSBtb2RlbHMgYXNzdW1lIHRoYXQgJFkgXHN1YnNldGVxIFxtYXRoYmJ7Un1fez4gMH0kLCBzbyBmb3IgemVybyBvYnNlcnZlZCB0aW1lIHNvbWUgc21hbGwgdmFsdWUgc2hvdWxkIGJlIGFkZGVkKSBhbmQgYW4gaW5kaWNhdG9yIGZvciBldmVudCAoMSkgYW5kIGNlbnNvcmluZyAoMCkgc3RhdHVzICRlID0gXHswLDFcfSQuIEl0IGlzIHRoZSBkZXRlcm1pbmlzdGljIGtpbmQgb2YgdGFzay4NCg0KLSBUaGUgcmVsYXRpdmUgcmlzayBvZiBhbiBpbmRpdmlkdWFsIGV4cGVyaWVuY2luZyBhbiBldmVudDogJCRYIFx0byBcbWF0aGNhbHtSfSQkDQp3aGVyZSAkXG1hdGhjYWx7Un0gXHN1YnNldGVxIFxtYXRoYmJ7Un0kIGlzIGEgcmVsYXRpdmUgcmlzay4gSXQgaXMgYSByYW5raW5nIGtpbmQgb2YgdGFzay4gQnkgdGhlbXNlbHZlcyByZWxhdGl2ZSByaXNrcyBoYXZlIG5vdCBhIG1lYW5pbmdmdWwgaW50ZXJwcmV0YXRpb24sIGJ1dCBjYW4gYmUgdXNlZCB0byByYW5rIGFuZC8gb3IgY2x1c3RlciBpbmRpdmlkdWFscyBieSByZWxhdGl2ZSByaXNrLiBUaW1lLXRvLWV2ZW50IHByZWRpY3Rpb25zIGNhbiBiZSBzZWVuIGFzIGEgc3BlY2lhbC1jYXNlIG9mIHRoZSByYW5raW5nIHByb2JsZW0gYXMgYW4gaW5kaXZpZHVhbCB3aXRoIGEgcHJlZGljdGVkIGxvbmdlciBzdXJ2aXZhbCB0aW1lIHdpbGwgaGF2ZSBhIGxvd2VyIG92ZXJhbGwgcmlzay4NCg0KLSBBbiBpbmRpdmlkdWFsJ3Mgc3Vydml2YWwgZGlzdHJpYnV0aW9uOiAkJFggXHRvIFxtYXRoY2Fse1N9JCQNCndoZXJlICRcbWF0aGNhbHtTfSQgY29tZXMgZnJvbSB0aGUgY29udmV4IHNldCBvZiBkaXN0cmlidXRpb25zIG9uICRUJDogJFxtYXRoY2Fse1N9IFxzdWJzZXRlcSBEaXN0cihUKSQgLiBJdCBpcyBhIHByb2JhYmlsaXN0aWMga2luZCBvZiB0YXNrcy4gQWN0dWFsbHksIHVzaW5nIGRpZmZlcmVudCBtb2RlbHMgd2UgY2FuIG9idGFpbiBkaWZmZXJlbnQgcmVwcmVzZW50YXRpb25zIG9mIHRoZSBzdXJ2aXZhbCBkaXN0cmlidXRpb24gKHN1cnZpdmFsLCBvciBoYXphcmQsIG9yIGN1bXVsYXRpdmUgaGF6YXJkIGZ1bmN0aW9uKSwgYnV0IGFzIHdlIGtub3csIHRoZXkgYXJlIGFsbCByZWxhdGVkIHRvIGVhY2ggb3RoZXIuIFRoZSBhYm92ZSBwcmVkaWN0aW9uIHRhc2tzIGNhbiBhbGwgYmUgc29sdmVkIHVzaW5nIHRoZSBzdXJ2aXZhbCBkaXN0cmlidXRpb24gcHJlZGljdGlvbi4NCg0KPGJyPg0KDQojIyMgKipTdXJ2aXZhbCByZWdyZXNzaW9uIG1vZGVscyoqDQoNCjxicj4NCg0KQWxsIHN1cnZpdmFsIHJlZ3Jlc3Npb24gbW9kZWxzIGNhbiBiZSBkaXZpZGVkIGludG8gdHdvIG1haW4gZ3JvdXBzLCBkZXBlbmRpbmcgb24gdGhlaXIgYXNzdW1wdGlvbnMgb2YgdGhlIG5hdHVyZSBvZiBjb3ZhcmlhdGVz4oCZIGVmZmVjdHMsIG5hbWVseTogYWNjZWxlcmF0ZWQgZmFpbHVyZSB0aW1lIGFuZCBwcm9wb3J0aW9uYWwgaGF6YXJkIG1vZGVscy4NCg0KPGJyPg0KDQoqKjEpIEFjY2VsZXJhdGVkIGZhaWx1cmUgdGltZSAoQUZUKSBtb2RlbHMqKjoNCg0KUHJlZGljdG9ycyBhY3QgbXVsdGlwbGljYXRpdmVseSBvbiB0aGUgZmFpbHVyZSB0aW1lIG9yIGFkZGl0aXZlbHkgb24gdGhlIGxvZyBmYWlsdXJlIHRpbWU6ICQkbG9nVCA9IFhcYmV0YSArIFxzaWdtYVxlcHNpbG9uIFxSaWdodGFycm93IFQgPSBleHAoWFxiZXRhKVRfMCQkDQp3aGVyZSAqWCogaXMgYSAkbiptJCBtYXRyaXggb2YgKm0qIGNoYXJhY3RlcmlzdGljcyBmb3IgKm4qIGluZGl2aWR1YWxzIChtYXkgaW5jbHVkZSBhbiBpbnRlcmNlcHQpLCANCiRcYmV0YSQgLSBhIHZlY3RvciBvZiAqbSogY29lZmZpY2llbnRzICh3ZWlnaHRzKSwgDQokZXBzaWxvbiQgLSBhbiBlcnJvciB0ZXJtIHdpdGggc29tZSBwcmVzcGVjaWZpZWQgZGlzdHJpYnV0aW9uICRccHNpJCAoc2VlIGJlbG93KSBvciBlc3RpbWF0ZWQgbm9uLXBhcmFtZXRyaWNhbGx5IChyYXJlbHkgdXNlZCksIA0KJFxzaWdtYSQgLSBhIHNjYWxlIHBhcmFtZXRlciwgDQokVF8wID0gZXhwKFxzaWdtYVxlcHNpbG9uKSQgW0Byb2RyaWd1ZXpfY2hhcHRlcl8yMDA3OyBAaGFycmVsbF9yZWdyZXNzaW9uXzIwMTVdLiANCiAgICANClRoZSB2YWx1ZSBvZiAkZXhwKFhcYmV0YSkgPSBcZ2FtbWEkIGlzIGNhbGxlZCBhbiBhY2NlbGVyYXRvciBmYWN0b3IsIGJlY2F1c2UgdGhlIGVmZmVjdCBvZiBhIHByZWRpY3RvciBpbiBBRlQgbW9kZWxzIGlzIHRvIGFsdGVyIHRoZSByYXRlIGF0IHdoaWNoIGEgc3ViamVjdCBwcm9jZWVkcyBhbG9uZyB0aGUgdGltZSBheGlzIChpLmUuLCB0byBhY2NlbGVyYXRlIHRoZSB0aW1lIHRvIGZhaWx1cmUpIFtAaGFycmVsbF9yZWdyZXNzaW9uXzIwMTVdLiBGb3IgZXhhbXBsZSwgdGhlIGZhaWx1cmUgdGltZSBmb3IgdGhlIGktdGggaW5kaXZpZHVhbCB3aXRoIHRoZSBqLXRoIGNoYXJhY3RlcmlzdGljIGJ5IG9uZSB1bml0IGhpZ2hlciB0aGFuIGZvciB0aGUgay10aCBzdWJqZWN0IChhbGwgb3RoZXIgdGhpbmdzIGJlaW5nIGVxdWFsKSB3aWxsIGJlICRleHAoXGJldGFfaikkIHRpbWVzIHRoZSBmYWlsdXJlIHRpbWUgb2YgdGhlIGstdGggc3ViamVjdC4gDQogICAgDQpVc2luZyBBRlQgbW9kZWxzLCB3ZSBjYW4gZWFzaWx5IHByZWRpY3QgZGlmZmVyZW50IHJlcHJlc2VudGF0aW9ucyBvZiB0aGUgaW5kaXZpZHVhbCBzdXJ2aXZhbCBkaXN0cmlidXRpb24g4oCTIG9mIGNvdXJzZSwgaWYgc3VjaCBuYXR1cmUgb2YgdmFyaWFibGVzIGVmZmVjdCBvbiB0aW1lIGlzIHR5cGljYWwgZm9yIHRoZSBmaWVsZCBvZiBzdHVkeS4gSW4gYWRkaXRpb24sIEFGVCBtb2RlbHMgYXJlIHVzdWFsbHkgcGFyYW1ldHJpYyBhbmQgcmVxdWlyZSBhY2N1cmF0ZSBhc3N1bXB0aW9ucyBhYm91dCBlcnJvcnMgZGlzdHJpYnV0aW9uICgkXHBzaSQpIChmb3IgZGV0YWlscyBzZWUgQGFiZHVsLWZhdGF3dV9hY2NlbGVyYXRlZF8yMDIwIGFuZCBAaGFycmVsbF9yZWdyZXNzaW9uXzIwMTUpLiBXZSBkbyBub3QgcGxhbiB1c2luZyB0aGVtIGluIHRoaXMgc3R1ZHkuDQoNCjxicj4NCg0KKioyKSBQcm9wb3J0aW9uYWwgaGF6YXJkcyAoUEgpIG1vZGVscyoqOiANCg0KV2hpbGUgQUZUIG1vZGVscyBhc3N1bWVzIHRoYXQgcHJlZGljdG9ycyBoYXZlIGEgbXVsdGlwbGljYXRpdmUgZWZmZWN0IG9uIHRoZSB0aW1lIHRvIGV2ZW50IGFuZCBhZGRpdGl2ZSBlZmZlY3QgZm9yIHRoZSBsb2cgdGltZSB0byBldmVudCwgcHJvcG9ydGlvbmFsIGhhemFyZHMgbW9kZWxzIGFzc3VtZXMgdGhlIHNhbWUgZm9yIHRoZSBoYXphcmQgYW5kIGxvZyBoYXphcmQsIGFjY29yZGluZ2x5IFtAcm9kcmlndWV6X2NoYXB0ZXJfMjAwN106ICQkaCh0fFgpID0gaF8wKHQpZXhwKFhcYmV0YSkgXFJpZ2h0YXJyb3cgbG9nKGgodHxYKSkgPSBsb2coaF8wKHQpKSArIFhcYmV0YSQkDQp3aGVyZSAkaF8wKHQpJCBpcyB0aGUgYmFzZWxpbmUgaGF6YXJkIGZ1bmN0aW9uICh3aGVuIGFsbCBwcmVkaWN0b3JzIGVxdWFsIHplcm8pLCAkWCQgLSBtYXRyaXggb2YgZmVhdHVyZXMgKG5vdCBpbmNsdWRpbmcgYSBjb25zdGFudCEpLCAkXGJldGEkIC0gcmVncmVzc2lvbiBjb2VmZmljaWVudHMgKHdlaWdodHMpLiBIZXJlICRleHAoeF9pXmBcYmV0YSkkIGlzIHRoZSByZWxhdGl2ZSByaXNrIGFzc29jaWF0ZWQgd2l0aCB0aGUgc2V0IG9mIGNoYXJhY3RlcmlzdGljcyAqeF9pKiBmb3IgdGhlIGktdGggaW5kaXZpZHVhbCBpbiBjb21wYXJpc29uIHdpdGggdGhlIHJpc2sgb2YgYW4gaW5kaXZpZHVhbCB3aXRoIGFsbCBjaGFyYWN0ZXJpc3RpY3MgZXF1YWwgemVyby4gU28gaW4gY29udHJhc3QgdG8gQUZUIG1vZGVscywgdGhlIGhhemFyZCBpcyBfcG9zaXRpdmVseV8gcmVsYXRlZCB0byB0aGUgcmlzayBjb21wb25lbnQsICRleHAoWFxiZXRhKSQuDQoNClRoZSBhbHRlcm5hdGl2ZSB3YXkgdG8gc3BlY2lmeSBhIFBIIHJlZ3Jlc3Npb24gaXMgdG8gYWRkIGEgY29uc3RhbnQgdG8gZmVhdHVyZSBtYXRyaXgsIHN1Y2ggdGhhdDogJCRoKHR8WCkgPSBleHAoW0N8WF1cYmV0YSkkJA0KdGhlbiBiYXNlbGluZSBoYXphcmQgd2lsbCBiZSBlc3RpbWF0ZWQgYXMgJGhfMCh0KT1leHAoXGJldGFfMCkkLCB3aGVyZSAkXGJldGFfMCQgaXMgdGhlIHdlaWdodCBmb3IgdGhlIGludGVyY2VwdC4NCg0KSXQgaXMgaW1wb3J0YW50IHRvIGVtcGhhc2l6ZSB0aGF0IHRoZSByZWxhdGl2ZSByaXNrIGRvZXMgbm90IGRlcGVuZCBvbiB0aW1lLCBpLmUuIGl0IGlzIGNvbnN0YW50IGluIHRpbWUgZm9yIHRoZSBzYW1lIHBhaXIgb2YgdmFsdWVzIG9mIGFueSBmZWF0dXJlLCBzbyBoYXphcmRzIGFyZSBwcm9wb3J0aW9uYWwgaW5kZXBlbmRlbnQgb2YgdGltZS4NCg0KVGhlIHNhbWUgaG9sZHMgZm9yIGN1bXVsYXRpdmUgaGF6YXJkcyBbQHJvZHJpZ3Vlel9jaGFwdGVyXzIwMDddOiAkJEgodHxYKSA9IEhfMCh0KWV4cChYXGJldGEpJCQNCg0KVGhlIGNvcnJlc3BvbmRpbmcgc3Vydml2YWwgZnVuY3Rpb24gd2lsbCBiZSBbQHJvZHJpZ3Vlel9jaGFwdGVyXzIwMDddOiAkJFModHxYKSA9IFNfMCh0KV57ZXhwKFhcYmV0YSl9JCQgDQp3aGVyZSAkU18wKHQpJCBpcyB0aGUgYmFzZWxpbmUgc3Vydml2YWwgZnVuY3Rpb24uIFRodXMsIHRoZSBlZmZlY3Qgb2YgdGhlIHNldCBvZiBjaGFyYWN0ZXJpc3RpY3MgJHhfaSQgZm9yIHRoZSBpLXRoIGluZGl2aWR1YWwgb24gdGhlIHN1cnZpdm9yIGZ1bmN0aW9uIGlzIHRvIHJhaXNlIGl0IHRvIGEgcG93ZXIgZ2l2ZW4gYnkgdGhlIHJlbGF0aXZlIHJpc2sgJGV4cCh4X2leYFxiZXRhKSQuDQoNCldlIGNhbiB1c2Ugc29tZSBzcGVjaWZpYyBkaXN0cmlidXRpb24gZm9yIHRoZSBiYXNlbGluZSBzdXJ2aXZhbCBvciBoYXphcmQgZnVuY3Rpb24g4oCTIGFuZCB0aGVuIHdlIGdldCBhICoqcGFyYW1ldHJpYyBQSCBtb2RlbCoqLCBvciBkbyBub3QgbWFrZSBzdWNoIGFzc3VtcHRpb25zIGFib3V0IHRoZSBiYXNlbGluZSBmdW5jdGlvbnMg4oCTIGFuZCB0aGVuIGdldCBhICoqc2VtaS1wYXJhbWV0cmljIFBIIG1vZGVsKiosIG9mdGVuIGNhbGxlZCBhIENveCBQSCBtb2RlbCBhZnRlciBpdHMgYXV0aG9yLg0KDQpDb25jZXJuaW5nICoqcGFyYW1ldHJpYyBQSCBtb2RlbHMqKiwgaXQgaXMgd29ydGggbm90aWNpbmcgdGhhdCB0aGVyZSBhcmUgdHdvIGRpc3RyaWJ1dGlvbnMgd2hpY2ggZ2l2ZSByZWdyZXNzaW9uIG1vZGVscyB0aGF0IGFyZSBvdXQgb2YgYm90aCBBRlQgYW5kIFBIIGZhbWlsaWVzLiBUaGV5IGFyZSBXZWlidWxsIGFuZCBpdHMgc3BlY2lhbCBjYXNlIC0gZXhwb25lbnRpYWwgZGlzdHJpYnV0aW9ucy4gU28gdGhlIHJlc3VsdHMgb2YgV2VpYnVsbCBhbmQgZXhwb25lbnRpYWwgcmVncmVzc2lvbiBtb2RlbHMgY2FuIGJlIGludGVycHJldGVkIGluIGJvdGggd2F5cy4NCg0KSW4gb3RoZXIgY2FzZXMgaWYgd2UgcGxvdCBhIGxvZyBoYXphcmQgb24gdGhlIGxvZyB0aW1lIHNjYWxlIHRoZSBlZmZlY3Qgb2YgdGhlIFBIIGFzc3VtcHRpb24gaXMgdG8gbW9kZWwgdGhlIGNoYW5nZSBpbiBoYXphcmQgYXMgYSB2ZXJ0aWNhbCBzaGlmdCwgd2hpbGUgdGhlIGVmZmVjdCBvZiB0aGUgQUZUIGFzc3VtcHRpb24gaXMgdG8gbW9kZWwgdGhlIGNoYW5nZSBpbiBoYXphcmQgYXMgYSBob3Jpem9udGFsIHNoaWZ0IFtAYnJlaGVueV9hY2NlbGVyYXRlZF9ub2RhdGVdLiBUaGlzIGFwcHJvYWNoIGNvdWxkIGJlIG9mIHVzZSBpbiBjaG9vc2luZyBiZXR3ZWVuIEFGVCBhbmQgUEggbW9kZWxzLCBhcyB3ZWxsIGFzIGluIGFzc2Vzc2luZyB0aGUgZnVsZmlsbG1lbnQgb2YgdGhlaXIgYXNzdW1wdGlvbnMuIA0KDQpCZWxvdyBhIHN1bW1hcnkgZGVzY3JpcHRpb24gb2YgYm90aCBBRlQgYW5kIFBIIHBhcmFtZXRyaWMgbW9kZWxzIGlzIHByZXNlbnRlZCBbQG1pbGxzX2ludHJvZHVjaW5nXzIwMTBdLg0KDQpgYGB7ciBwYXJhbV9waF9hZnQsIGZpZy5jYXA9IlBhcmFtZXRyaWMgQUZUIGFuZCBQSCBtb2RlbHMiLCBldmFsPVRSVUUsIG91dC53aWR0aD0iMzAwcHgiLCBmaWcuYWxpZ249J2NlbnRlcid9DQprbml0cjo6aW5jbHVkZV9ncmFwaGljcygiRDovTXlEb2N1bWVudHMvS2FnZ2xlL0trYm94L3BhcmFtX3BoX2FmdC5wbmciKQ0KYGBgDQoNCkFzIHVzdWFsLCBwYXJhbWV0cmljIFBIIG1vZGVscywgcmVxdWlyZSBjb3JyZWN0IHNwZWNpZmljYXRpb24gb2YgZGlzdHJpYnV0aW9ucyBmb3IgYmFzZWxpbmUgc3Vydml2YWwsIGhhemFyZCBvciBjdW11bGF0aXZlIGhhemFyZCBmdW5jdGlvbi4gVGhlIG1vZGVsIHBhcmFtZXRlcnMgYXJlIGVzdGltYXRlZCB3aXRoIG1heGltdW0gbGlrZWxpaG9vZCBlc3RpbWF0aW9uLCB3aXRoIHRoZSBsaWtlbGlob29kIGRlcGVuZGVudCBvbiB0aGUgY2hvc2VuIGRpc3RyaWJ1dGlvbiB3aXRoIGEgcG9zc2liaWxpdHkgb2YgdHJlYXRpbmcgdGhlIGRpc3RyaWJ1dGlvbiBjaG9pY2UgYXMgYSBtb2RlbCBoeXBlci1wYXJhbWV0ZXIuIEluIHRoZSBvdXRwdXQgd2UgZ2V0IGEgY29udGludW91cyBzdXJ2aXZhbCBkaXN0cmlidXRpb24gcHJlZGljdGlvbi4NCg0KKipTZW1pLXBhcmFtZXRyaWMgKENveCkgUEggbW9kZWxzKiogYXJlIG1vcmUgd2lkZWx5IHVzZWQgdGhhbiBwYXJhbWV0cmljIFBIIG1vZGVscywgYmVjYXVzZSB0aGV5IGRvIG5vdCBoYXZlIHBhcmFtZXRyaWMgYXNzdW1wdGlvbnMgY29uY2VybmluZyBiYXNlbGluZSBmdW5jdGlvbnMgLSBqdXN0IHRoZSBlZmZlY3Qgb2YgY292YXJpYXRlcyBvbiBoYXphcmQuIENveCBhcmd1ZWQgdGhhdCB3aGVuIHRoZSBQSCBhc3N1bXB0aW9uIGhvbGRzLCBpbmZvcm1hdGlvbiBhYm91dCBiYXNlbGluZSBoYXphcmQgZnVuY3Rpb24gaXMgbm90IHZlcnkgdXNlZnVsIGluIGVzdGltYXRpbmcgdGhlIHBhcmFtZXRlcnMgb2YgcHJpbWFyeSBpbnRlcmVzdCAoJFxiZXRhJCkuIEJ5IHNwZWNpYWwgY29uZGl0aW9uaW5nIGluIGZvcm11bGF0aW5nIHRoZSBsb2cgbGlrZWxpaG9vZCBmdW5jdGlvbiwgQ294IHNob3dlZCBob3cgdG8gZGVyaXZlIGEgdmFsaWQgZXN0aW1hdGUgb2YgJFxiZXRhJCB0aGF0IGRvZXMgbm90IHJlcXVpcmUgZXN0aW1hdGlvbiBvZiBiYXNlbGluZSBoYXphcmQgYXMgaXQgaXMgZHJvcHBlZCBvdXQgb2YgdGhlIG5ldyBsaWtlbGlob29kIGZ1bmN0aW9uLiBDb3jigJlzIGRlcml2YXRpb24gZm9jdXNlcyBvbiB1c2luZyB0aGUgaW5mb3JtYXRpb24gaW4gdGhlIGRhdGEgdGhhdCByZWxhdGVzIHRvIHRoZSByZWxhdGl2ZSBoYXphcmQgZnVuY3Rpb24gJFxleHAoWFxiZXRhKSQgW0BoYXJyZWxsX3JlZ3Jlc3Npb25fMjAxNV0uDQoNCkFzIGEgcmVzdWx0LCBlc3RpbWF0aW9uIG9mIHRoZSBDb3ggUEggbW9kZWwgaXMgYmFzZWQgb24gdGhlIG1heGltaXphdGlvbiBvZiB0aGUgcGFydGlhbCBsaWtlbGlob29kOiAkJEwoXGJldGEpID0gXHByb2Rfe2k6IFlfaSB1bmNlbnNvcmVkfVxmcmFje2V4cChYX2lcYmV0YSl9e1xzdW1fe1lfaiBcZ2VxIFlfaX1leHAoWF9qXGJldGEpfSQkDQpvciwgZXF1aXZhbGVudGx5LCBtaW5pbWl6YXRpb24gb2YgdGhlIG5lZ2F0aXZlIGxvZyBwYXJ0aWFsIGxpa2VsaWhvb2QgJC1sb2dMJC4gTmV3dG9uLVJhcHNob24gdGVjaG5pcXVlIGZvciBpdGVyYXRpdmUgZXN0aW1hdGlvbiBpcyBvZnRlbiB1c2VkIGhlcmUuDQoNCkFmdGVyIGVzdGltYXRpbmcgdGhlIENveCBQSCB3ZSBjYW4gZ2V0IGEgbGluZWFyIHByZWRpY3Rpb24gZm9yIGVhY2ggaW5kaXZpZHVhbCBhcyAkeF9pXmBcaGF0XGJldGEkIGFuZCB1c2UgaXQgdG8gcmFuayB0aGVtIGJ5IHJlbGF0aXZlIHJpc2sgb2YgZXZlbnQuDQoNClRoZSBDb3ggbW9kZWwgYW5kIHBhcmFtZXRyaWMgc3Vydml2YWwgbW9kZWxzIGRpZmZlciBtYXJrZWRseSBpbiBob3cgb25lIGVzdGltYXRlcyAkUyh0fFgpJC4gU2luY2UgdGhlIENveCBtb2RlbCBkb2VzIG5vdCBkZXBlbmQgb24gYSBjaG9pY2Ugb2YgdGhlIHVuZGVybHlpbmcgc3Vydml2YWwgZnVuY3Rpb24gJFModCkkLCBmaXR0aW5nIGEgQ294IG1vZGVsIGRvZXMgbm90IHJlc3VsdCBkaXJlY3RseSBpbiBhbiBlc3RpbWF0ZSBvZiAkUyh0fFgpJC4gU2V2ZXJhbCBhdXRob3JzIGhhdmUgZGVyaXZlZCBzZWNvbmRhcnkgZXN0aW1hdGVzIGZvciBpdCAoZm9yIG1vcmUgZGV0YWlscyBzZWUgQGhhcnJlbGxfcmVncmVzc2lvbl8yMDE1KS4gVXN1YWxseSB0aGV5IHVzZSBzb21lIG5vbi1wYXJhbWV0cmljIGVzdGltYXRvciBmb3IgdGhlIGJhc2VsaW5lIGhhemFyZCBmdW5jdGlvbi4gQXMgYSByZXN1bHQsIHRoZXkgZ2l2ZSBhIGRpc2NyZXRlIHN1cnZpdmFsIGRpc3RyaWJ1dGlvbiBwcmVkaWN0aW9uLiBJbiBjb250cmFzdCwgdGhlcmUgYXJlIG1vZGVscyB0aGF0IHVzZSBzcGxpbmVzIHRvIGZpdCB0aGUgYmFzZWxpbmUgaGF6YXJkIGZ1bmN0aW9uICgkbG9nKEhfMCh0KSQgaXMgZXN0aW1hdGVkIGJ5IG5hdHVyYWwgY3ViaWMgc3BsaW5lcyB3aXRoIGNvZWZmaWNpZW50cyBmaXQgYnkgbWF4aW11bSBsaWtlbGlob29kIGVzdGltYXRpb24gW0Byb3lzdG9uX2ZsZXhpYmxlXzIwMDJdKS4gUGFyYW1ldHJpYyBtb2RlbHMgZ2l2ZSBkaXJlY3QgZnVuY3Rpb25hbCBmb3JtIGZvciBiYXNlbGluZSBzdXJ2aXZhbCBmdW5jdGlvbiwgZnJvbSB3aGljaCBpbmRpdmlkdWFsIHN1cnZpdmFsIGRpc3RyaWJ1dGlvbiBjYW4gZWFzaWx5IGJlIG9idGFpbmVkIHVzaW5nIHRoZSBmb3JtdWxhIGFib3ZlLg0KDQo8YnI+DQoNCioqMykgRXh0ZW5zaW9ucyBvZiB0aGUgYmFzaWMgQUZUIGFuZCBQSCBtb2RlbHMqKjoNCg0KVGhlcmUgYXJlIHNldmVyYWwgd2F5cyB0byBleHRlbmQgdGhlIGRlc2NyaWJlZCBiYXNpYyBBRlQgYW5kIFBIIG1vZGVscyB0byByZWNlaXZlIG1vcmUgYWNjdXJhdGUgcHJlZGljdGlvbnMuIEFsdGhvdWdoIG1vc3Qgb2YgdGhlbSBhcmUgZWFzaWVyIGluY29ycG9yYXRlZCBpbnRvIFBIIG1vZGVscy4NCg0KVGhlIGNvbW1vbiBwcm9ibGVtIGZvciBhbGwgUEggbW9kZWxzIGlzIGEgKip2aW9sYXRpb24gb2YgdGhlIFBIIGFzc3VtcHRpb24qKiAoc2V2ZXJhbCBtZXRob2RzIGNhbiBiZSB1c2VkIHRvIGNoZWNrIHRoaXMpLiBJZiBBRlQgbW9kZWxzIGFyZSBub3QgdGhlIGFwcHJvcHJpYXRlIGFsdGVybmF0aXZlcywgdGhlbiBzdHJhdGlmaWVkIENveCByZWdyZXNzaW9uIGNhbiBiZSBlc3RpbWF0ZWQgKGl0IGFzc3VtZXMgZGlmZmVyZW50IGJhc2VsaW5lIGhhemFyZCBmdW5jdGlvbnMgZm9yIGRpZmZlcmVudCBzdHJhdGEpIG9yIENveCByZWdyZXNzaW9uIHdpdGggdGltZS1kZXBlbmRlbnQgZWZmZWN0cyAoaW4gdGhpcyBjYXNlIHRoZSBhc3N1bXB0aW9uIG9mIFBIIGlzIGRyb3BwZWQgb3V0IGZvciBzb21lIG9yIGFsbCBjb3ZhcmlhdGVzOiAkaCh0fFgpID0gaF8wKHQpZXhwKFhcYmV0YSh0KSkkKS4gRGlmZmVyZW50IHBhcmFtZXRyaWMgYXNzdW1wdGlvbnMgY2FuIGJlIHB1dCBvbiAkXGJldGEodCkkIGFuZCBhY2NvcmRpbmcgdG8gdGhlbSBpdCBjYW4gYmUgbW9kZWxlZCBhcyBhIHBpZWNld2lzZSBjb25zdGFudCwgbGluZWFyLCBwaWVjZXdpc2UgbGluZWFyLCBzcGxpbmUsIHBvbHlub21pYWwgZnVuY3Rpb24gb2YgdGltZSBhbmQgb3RoZXIgYXBwcm9hY2hlcyAoZm9yIG1vcmUgZGV0YWlscyBzZWUgQGJyZWhlbnlfdGltZS1kZXBlbmRlbnRfbm9kYXRlKS4gDQogDQpBbm90aGVyIGV4dGVuc2lvbiBhaW1lZCBhdCByZWNlaXZpbmcgbW9yZSBhY2N1cmF0ZSBwcmVkaWN0aW9ucyBpcyBhICoqcmVndWxhcml6YXJpb24gb2YgcmVncmVzc2lvbiBjb2VmZmljaWVudHMqKi4gSGVyZSB3ZSBjYW4gYXBwbHkgYW55IG9mIEwxIG9yIEwyIG9yIGVsYXN0aWMgbmV0IG9yIG90aGVyIHJlZ3VsYXJpemF0aW9uIG1ldGhvZHMgKGZvciBkZXRhaWxzIHNlZSBbQHNpbW9uX3JlZ3VsYXJpemF0aW9uXzIwMTE7IEB3YW5nX21hY2hpbmVfMjAxOV0pLiBJZiB0aGVyZSBhcmUgc3VjaCBjb3ZhcmlhdGVzIHRoYXQgc2hvdWxkIHJlbWFpbiBpbiB0aGUgbW9kZWwgZXZlbiBhZnRlciByZWd1bGFyaXphdGlvbiwgdGhlbiBDb3hCb29zdCBjYW4gYmUgYXBwbGllZCBbQHdhbmdfbWFjaGluZV8yMDE5XS4gDQoNCkF0IGxhc3QsIHdlIGNhbiBhZGQgKip0aW1lLWRlcGVuZGVudCBjb3ZhcmlhdGVzKiogdG8gQ294IHJlZ3Jlc3Npb24sIGkuZS4gc3VjaCBmZWF0dXJlcyB3aGljaCBhcmUgZXN0aW1hdGVkIG5vdCBvbmx5IGF0IHRoZSB0aW1lIG9yaWdpbiwgYnV0IGFsc28gYWZ0ZXIgaXQsIGR1cmluZyB0aGUgcHJvY2VzcyBvZiBvYnNlcnZhdGlvbi4gSW4gb3VyIGNhc2UgaXQgd2lsbCBiZSBhbGwgY2hhcmFjdGVyaXN0aWNzIG9mIHVzZXJz4oCZIGJlaGF2aW9yIG9idGFpbmVkIGFmdGVyIGJlZ2lubmluZyBvZiB0aGUgc3Vic2NyaXB0aW9uIHBlcmlvZC4NCg0KUEggbW9kZWxzIHdpdGggdGltZS1kZXBlbmRlbnQgY292YXJpYXRlcyB3aWxsIGhhdmUgdGhlIGZvbGxvd2luZyBmb3JtOiAkJGgodCwgWCh0KSkgPSBoXzAodClleHAoWCh0KVxiZXRhKSQkDQoNCkNhbGN1bGF0aW9uIG9mIHN1cnZpdmFsIGZ1bmN0aW9ucyBpbiB0aGlzIG1vZGVsIGlzIGEgbGl0dGxlIGJpdCBtb3JlIGNvbXBsaWNhdGVkLCBiZWNhdXNlIHdlIG5lZWQgdG8gc3BlY2lmeSBhIHBhdGggb3IgdHJhamVjdG9yeSBmb3IgZWFjaCB2YXJpYWJsZSBbQHJvZHJpZ3Vlel9jaGFwdGVyXzIwMDddLiBVc3VhbGx5IGl0IGlzIGRvbmUgYnkgc3BsaXR0aW5nIHRoZSB3aG9sZSBwZXJpb2Qgb2YgdGltZSBpbnRvIGludGVydmFscyBiZXR3ZWVuIHRpbWUgcG9pbnRzIGF0IHdoaWNoIHRoZSBjb3JyZXNwb25kaW5nIGNvdmFyaWF0ZSB3YXMgbWVhc3VyZWQgKG9yIGNoYW5nZWQgaXRzIHZhbHVlKSAtIHRodXMsIGluc3RlYWQgb2Ygb25lIG9ic2VydmF0aW9uIHdlIG9idGFpbiBzZXZlcmFsIHBzZXVkby1vYnNlcnZhdGlvbnMgZm9yIG9uZSBpbmRpdmlkdWFsIG9mIHR5cGUgJCh0aW1lXHRleHR7X3N0YXJ0fSwgdGltZVx0ZXh0e19zdG9wfSwgZXZlbnQpJCwgd2hpY2ggbWVhbnMgdGhhdCBhdCB0aGUgZW5kIG9mIHRoZSBpbnRlcnZhbCAkKHRpbWVcdGV4dHtfc3RhcnR9LCB0aW1lXHRleHR7X3N0b3B9XSQgYW4gaW5kaXZpZHVhbCBoYXMgYW4gJGV2ZW50JCBzdGF0dXMgKDAgb3IgMSksIGFuZCBhbGwgdGltZS1kZXBlbmRlbnQgY292YXJpYXRlcyBhcmUgbWVhc3VyZWQgYXMgb2YgYXQgdGhlIGVuZCBvZiB0aGlzIGludGVydmFsLiBUaHVzLCBpbiBmYWN0LCBhbGwgdGhlc2UgcHNldWRvLW9ic2VydmF0aW9ucyAoZXhjZXB0LCBtYXliZSwgdGhlIGZpcnN0IG9uZSkgYmVjb21lIGxlZnQtdHJ1bmNhdGVkIHJpZ2h0LWNlbnNvcmVkLg0KDQpUaGlzIGRhdGEgZm9ybSBpcyBjYWxsZWQgYW4gQW5kZXJzZW4tR2lsbCByZWZvcm11bGF0aW9uIG9yIGEgY291bnRpbmcgcHJvY2VzcyBmb3JtIGFuZCBkb2VzIG5vdCBjaGFuZ2UgdGhlIGZvcm0gb2YgcGFydGlhbCBsaWtlbGlob29kIGZ1bmN0aW9uIC0gb25seSB0aGUgaW50ZXJwcmV0YXRpb24gb2YgaXRzIGVsZW1lbnRzIGFuZCByZXN1bHRzIG9mIGl0cyBtYXhpbWl6YXRpb24gW0B0aGVybmVhdV91c2luZ18yMDE3XS4gQmFzZWxpbmUgZnVuY3Rpb25zIGluIHRoaXMgZnJhbWV3b3JrIGFyZSBjYWxjdWxhdGVkIGZvciBzdWJqZWN0IHdpdGggYWxsIGNvdmFyaWF0ZXMgZXF1YWwgemVybyBmb3IgYWxsICp0KiBhbmQgbm8gbG9uZ2VyIGhhdmUgYSBjbGVhciBpbnRlcnByZXRhdGlvbi4gJGV4cCh4X3tpan0odClcYmV0YV9qKSQgZm9yIHNvbWUgaS10aCBpbmRpdmlkdWFsIGFuZCBqLXRoIGZlYXR1cmUgaXMgaW50ZXJwcmV0ZWQgYXMgdGhlIHJlbGF0aXZlIHJpc2sgb2YgZXZlbnQgZm9yIHRoZSBpbmRpdmlkdWFsIHdpdGggdGhlIHZhbHVlIG9mICR4X3tpan0kIG9idGFpbmVkIGJ5IHRpbWUgKnQqIGluIGNvbXBhcmlzb24gd2l0aCB0aGUgaW5kaXZpZHVhbCB3aG8gaGFkIGEgemVybyB2YWx1ZSBvZiB0aGlzIGZlYXR1cmUgYnkgdGhlIHNhbWUgdGltZS4gV2l0aCBzdWNoIGludGVycHJldGF0aW9uIHdlIGNhbiBzYXkgdGhhdCBpdCBpcyBtb3JlIHVzZWZ1bCBmb3IgY2F0ZWdvcmljYWwgcHJlZGljdG9ycywgd2hpbGUgZm9yIGNvbnRpbnVvdXMgb25lcyBpdCBtYXkgcmVxdWlyZSBzb21lIHByZWxpbWluYXJ5IGNlbnRyYWxpemF0aW9uIG9mIHZhbHVlcyBmb3IgYWxsIHN1YmplY3RzIGFuZCBwb2ludHMuDQoNCk5vbi1wYXJhbWV0cmljIGVzdGltYXRlcywgaW5jbHVkaW5nIEthcGxhbi1NZWllciBjdXJ2ZXMgYXJlIG5vdCBhcHBsaWNhYmxlIGZvciB0aW1lLWRlcGVuZGVudCBjb3ZhcmlhdGVzLCBhcyB0aGV5IHByZXNlcnZlIHNwbGl0dGluZyBvYnNlcnZhdGlvbnMgaW50byBncm91cHMgYXMgdGhleSBhcmUgbWFkZSBhdCB0aGUgc3RhcnRpbmcgcG9pbnQgb2YgdGltZS4gKipTaW1vbiBhbmQgTWFrdWNoKiogcHJvcG9zZWQgYSB0ZWNobmlxdWUgdGhhdCBldmFsdWF0ZXMgdGhlIGNvdmFyaWF0ZSBzdGF0dXMgb2YgdGhlIGluZGl2aWR1YWxzIHJlbWFpbmluZyBhdCByaXNrIChpLmUuIHdpdGhvdXQgZXZlbnQgb3IgY2Vuc29yaW5nIGJ5IHRoYXQgcG9pbnQpIGF0IGVhY2ggZXZlbnQgdGltZSwgYW5kIHRoZWlyIGVzdGltYXRvciBjYW4gYmUgdXNlZCB0byBjb3JyZWN0bHkgcGxvdCBLYXBsYW4tTWVpZXIgY3VydmVzIGZvciB0aW1lLWRlcGVuZGVudCB2YXJpYWJsZXMgKGFsdGhvdWdoLCBhZ2Fpbiwgb25seSBmb3IgY2F0ZWdvcmljYWwgb25lcykgW0BzY2h1bHR6X2dyYXBoaW5nXzIwMDJdLg0KDQpQcmVkaWN0aW5nIHN1cnZpdmFsIGZ1bmN0aW9uIHdpdGggc2V2ZXJhbCBjb3ZhcmlhdGVzLCBvbmUgb3IgbW9yZSBvZiB3aGljaCBhcmUgdGltZS1kZXBlbmRlbnQsIGlzIHF1aXRlIG1vcmUgY29tcGxpY2F0ZWQg4oCTIGJvdGggaW4gZXN0aW1hdGlvbiwgcHJlZGljdGlvbiBhbmQgaW50ZXJwcmV0YXRpb24uIEBsYWluZV90dXRvcmlhbF8yMDE0IGRlc2NyaWJlcyB0aGUgcHJvY2VzcyBvZiBlc3RpbWF0aW5nIHN1cnZpdmFsIGFmdGVyIG9idGFpbmluZyBoYXphcmRzIGZyb20gQ294IFBIIHJlZ3Jlc3Npb246IGZvciBwYXJ0aWN1bGFyIGNvdmFyaWF0ZSB0cmFqZWN0b3J5ICR4XioodCkkDQoNCiQkXGhhdHtIfSh0fHheKih0KSkgPVxzdW1fe2k9MX1ebiBcaW50XzBedFxmcmFje2V4cChcaGF0e1xiZXRhfXheKih1KSlkTl9pKHUpfXtcc3VtX2ogWV9qKHUpZXhwKFxoYXR7XGJldGF9WF9qKHUpKX0kJA0Kd2hlcmUgJE5faSh0KT1cbWF0aGJie0l9KFRfaSBcZ2VxIHQpJC4gVGhlbg0KDQokJFxoYXR7U30odHx4XioodCkpPWV4cCgtXGhhdHtIfSh0fHheKih0KSkpJCQNCmFuZCBub3cgaXQgaXMgbm90IGEgc2ltcGxlIGZ1bmN0aW9uLCBhcyBub3cgaXQgcmVxdWlyZXMgdW5pcXVlIGludGVncmF0aW9uIHRvIGVzdGltYXRlIGN1bXVsYXRpdmUgaGF6YXJkIGZvciBldmVyeSB2YWx1ZSBvZiAkeF4qKHQpJC4NCg0KSW4gYWRkaXRpb24sIEBsYWluZV90dXRvcmlhbF8yMDE0IG5vdGUgdGhhdCBpbiB0aGUgcHJlc2VuY2Ugb2YgdGltZS1kZXBlbmRlbnQgY292YXJpYXRlcywgaXQgbWF5IG5vdCBtYWtlIHNlbnNlIHRvIGNhbGN1bGF0ZSBzdXJ2aXZhbCBwcm9iYWJpbGl0aWVzIG9yIHByZWRpY3Rpb25zLiBUaGlzIHJlcXVpcmVzIGtub3dsZWRnZSBvZiAkWCh0KSQsIHdoaWNoIG1heSBiZSB1bmtub3duIHVudGlsIHRpbWUgX3RfLCBhdCB3aGljaCBwb2ludCBpdHMgb2JzZXJ2YXRpb24gZnJlcXVlbnRseSBpbXBsaWVzIHN1cnZpdmFsLiBGb3IgZXhhbXBsZSwgaW5kaXZpZHVhbHMgd2hvIGhhdmUgc3VyZ2VyeSBhdCAxIHllYXIgYXJlIGFsaXZlIGF0IDEgeWVhciwgYnkgZGVmaW5pdGlvbi4gSGVuY2UsIHN1cnZpdmFsIGVzdGltYXRpb24gaXMgcmFyZWx5IGltcGxlbWVudGVkIGluIHRoaXMgY2FzZSBpbiBtZWRpY2FsIHN0dWRpZXMuDQoNCkFsdGhvdWdoLCBpbiBvdXIgb3BpbmlvbiwgaXQgY291bGQgYmUgdXNlZnVsIGluIHRoZSBwcm9jZXNzIG9mIG9ic2VydmF0aW9uIChhZnRlciB0aGUgc3RhcnQgb2YgdGhlcmFweSwgc3Vic2NyaXB0aW9uIG9yIG90aGVyIHN0YXJ0aW5nIHBvaW50cyBkZXBlbmRpbmcgb24gdGhlIG5hdHVyZSBvZiB0aGUgc3R1ZHkpLCBhcyB3ZSBvYnRhaW4gYWRkaXRpb25hbCBpbmZvcm1hdGlvbiwgYW5kIHVzaW5nIGl0IHdlIGNhbiB1cGRhdGUgc3Vydml2YWwgZnVuY3Rpb24gZm9yIGluZGl2aWR1YWxzIG9yIGdyb3Vwcy4gRm9yIGV4YW1wbGUsIEB5YW9fZW5zZW1ibGVfMjAyMCBpbGx1c3RyYXRlIGhvdyB1cGRhdGVkIGFuZCBub3QgdXBkYXRlZCBzdXJ2aXZhbCBjdXJ2ZXMgY2FuIGJlIGRpZmZlcmVudDoNCg0KYGBge3Igc3Vydl9jdXJ2X3RkLCBmaWcuY2FwPSJTdXJ2aXZhbCBjdXJ2ZSB3aXRoIHRpbWUtZGVwZW5kZW50IGNvdmFyaWF0ZXMiLCBldmFsPVRSVUUsIG91dC53aWR0aD0iMzAwcHgiLCBmaWcuYWxpZ249J2NlbnRlcid9DQprbml0cjo6aW5jbHVkZV9ncmFwaGljcygiRDovTXlEb2N1bWVudHMvS2FnZ2xlL0trYm94L3N1cnZfY3Vydl90ZC5wbmciKQ0KYGBgDQoNCjxicj4NCg0KIyMjICoqTWFjaGluZSBsZWFybmluZyBtb2RlbHMqKg0KDQo8YnI+DQoNCk1hY2hpbmUgbGVhcm5pbmcgbW9kZWxzIGZvciBzdXJ2aXZhbCBkYXRhIGluIGNvbXBhcmlzb24gd2l0aCB0aGUgc2FtZSBtb2RlbHMgZm9yIOKAnG9yZGluYXJ54oCdIGNvbnRpbnVvdXMgb3IgY2xhc3MgZGVwZW5kZW50IHZhcmlhYmxlcyBjYW4gZGVhbCB3aXRoIGNlbnNvcmVkIGRhdGEuIEhlcmUgd2UgaGF2ZSBzdXJ2aXZhbCB0cmVlcyBhbmQgdGhlaXIgZW5zZW1ibGVzLCBpbmNsdWRpbmcgcmFuZG9tIHN1cnZpdmFsIGZvcmVzdCAoUkZTKSwgZ3JhZGllbnQgYm9vc3RpbmcgbWFjaGluZXMsIHN1cHBvcnQgdmVjdG9yIG1hY2hpbmVzLCBhbmQgbmV1cmFsIG5ldHdvcmtzLiBVc3VhbGx5IHRoZXkgZG8gbm90IGhhdmUgc3VjaCByZXN0cmljdGl2ZSBhc3N1bXB0aW9ucyBhcyBzdGF0aXN0aWNhbCBtb2RlbHMsIGFsdGhvdWdoIHRoZXJlIGFyZSBzb21lIG1vZGVscyB0aGF0IGFyZSBiYXNlZCBvbiB0aGUgUEggYXNzdW1wdGlvbi4gVGhlIGNvbXByZWhlbnNpdmUgc3VydmV5IG9mIGRpZmZlcmVudCBtYWNoaW5lIGxlYXJuaW5nIG1vZGVscyBmb3Igc3Vydml2YWwgZGF0YSBpcyBwcmVzZW50ZWQgaW4gQHNvbmFiZW5kX3RoZW9yZXRpY2FsXzIwMjEuIFRoZSBmb2xsb3dpbmcgdGFibGUgd2FzIHRha2VuIGZyb20gdGhlcmU6DQoNCmBgYHtyIG1sX21vZGVscywgZmlnLmNhcD0iTUwgbW9kZWxzIGZvciBzdXJ2aXZhbCBhbmFseXNpcyIsIGV2YWw9VFJVRSwgb3V0LndpZHRoPSIzMDBweCIsIGZpZy5hbGlnbj0nY2VudGVyJ30NCmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKCJEOi9NeURvY3VtZW50cy9LYWdnbGUvS2tib3gvbWxfbW9kZWxzLmpwZyIpDQpgYGANCkNsYXNzOiBSU0Yg4oCTIFJhbmRvbSBTdXJ2aXZhbCBGb3Jlc3QgYW5kIERlY2lzaW9uIFRyZWVzOyBHQk0g4oCTIEdyYWRpZW50IEJvb3N0aW5nIE1hY2hpbmU7IFNWTSDigJMgU3VwcG9ydCBWZWN0b3IgTWFjaGluZTsgQU5OIOKAkyBBcnRpZmljaWFsIE5ldXJhbCBOZXR3b3JrLg0KVGFzazogRGV0LiAtIERldGVybWluaXN0aWMsIFByb2IuIC0gUHJvYmFiaWxpc3RpYywgUmFuayAtIFJhbmtpbmcuDQoNCjxicj4NCg0KVGhlIHRoaW5nIGlzLCB0aGF0IGZvciBNTCBtb2RlbHMgaXQgaXMgc3RpbGwgbm90IHZlcnkgY29tbW9uIHRvIGJlIGFkYXB0ZWQgZm9yIHVzZSBvZiB0aGUgdGltZS1kZXBlbmRlbnQgY292YXJpYXRlcy4gQXMgb25lIG9mIHRoZSBwdXJwb3NlcyBvZiBvdXIgc3R1ZHkgd2FzIHRvIHByZWRpY3QgY3VzdG9tZXJz4oCZIGNodXJuIHdpdGggdGltZS12YXJ5aW5nIGZlYXR1cmVzIGZyb20gYWxsIHRoZSB2YXJpZXR5IG9mIE1MIG1vZGVscyB3ZSBjaG9vc2UgcmFuZG9tIHN1cnZpdmFsIGZvcmVzdCAoUlNGKSwgZm9yIHdoaWNoLCB1bmxpa2Ugb3RoZXIgbWFjaGluZSBsZWFybmluZyBtZXRob2RzLCB0aGUgYWxnb3JpdGhtIGNhbiBiZSBtb3JlIGVhc2lseSBhZGFwdGVkIGJvdGggZm9yIHdvcmtpbmcgd2l0aCBzdXJ2aXZhbCBkYXRhIGFuZCBmb3IgdXNpbmcgdGltZS1kZXBlbmRlbnQgY292YXJpYXRlcy4NCg0KQXMgQHNvbmFiZW5kX3RoZW9yZXRpY2FsXzIwMjEgbm90ZXMsIHRoZSBkaWZmZXJlbmNlIGJldHdlZW4gb3JkaW5hcnkgcmFuZG9tIGZvcmVzdCBhbmQgcmFuZG9tIHN1cnZpdmFsIGZvcmVzdCBsaWVzIGluIGRpZmZlcmVudCBjaG9pY2VzIG9mIHNwbGl0dGluZyBydWxlcyBhbmQgdGVybWluYWwgbm9kZSBwcmVkaWN0aW9ucyDigJMgYWxsIGZ1bmRhbWVudGFscyByZW1haW5zIHVuY2hhbmdlZC4gSGUgZGlzdGluZ3Vpc2hlcyB0d28gZ3JvdXBzIG9mIFJTRiBtb2RlbHMgYnkgc3BsaXR0aW5nIHJ1bGVzIGJlaW5nIHVzZWQ6IHRob3NlIHJlbGllZCBvbiBoeXBvdGhlc2lzIHRlc3RzIChwcmltYXJpbHkgdGhlIG5vbnBhcmFtZXRyaWMgbG9nLXJhbmsgdGVzdCwgd2hpY2ggaW4gc3RhdGlzdGljYWwgYW5hbHlzaXMgaXMgdXNlZCBmb3IgY29tcGFyaW5nIHN1cnZpdmFsIGRpc3RyaWJ1dGlvbnM7IGhpZ2hlciB2YWx1ZXMgb2YgdGhlIGxvZy1yYW5rIHN0YXRpc3RpYyBtZWFuIGdyZWF0ZXIgZGlzc2ltaWxhcml0eSBiZXR3ZWVuIGRpc3RyaWJ1dGlvbnMpIGFuZCB0aG9zZSB1dGlsaXNlIGxpa2VsaWhvb2QtYmFzZWQgbWVhc3VyZXMgKHRoZXkgcmVxdWlyZSBsaWtlbGlob29kIGVzdGltYXRpb24sIHdoaWNoIG1lYW5zIGFzc3VtcHRpb24gYWJvdXQgY2VydGFpbiBtb2RlbCBmb3JtLCBmb3IgZXhhbXBsZSBDb3ggUEggbW9kZWwpLg0KDQpBcyBmYXIgYXMgdGVybWluYWwgbm9kZSBwcmVkaWN0aW9ucyBhcmUgY29uY2VybmVkLCBhbGwgUlNGIG1vZGVscyBhZ2FpbiBjYW4gYmUgc2VwYXJhdGVkIGludG8gdHdvIGdyb3Vwczogb25lIG9mIHRoZW0gcmVzdWx0IGluIHRlcm1pbmFsIG5vZGUgcmFua2luZyBwcmVkaWN0aW9ucyAodGhlc2UgbW9kZWxzIGFzc3VtZSB1c2luZyBsaWtlbGlob29kLWJhc2VkIHNwbGl0dGluZyBydWxlIGFuZCBhIFBIIG1vZGVsIGZvcm0pLCB0aGUgb3RoZXIgcHJlZGljdCBzdXJ2aXZhbCBkaXN0cmlidXRpb24gYnkgZXN0aW1hdGluZyB0aGUgc3Vydml2YWwgZnVuY3Rpb24sIHVzaW5nIHRoZSBLYXBsYW4tTWVpZXIgb3IgTmVsc29uLUFhbGVuIGVzdGltYXRvcnMsIG9uIHRoZSBzYW1wbGUgaW4gdGhlIHRlcm1pbmFsIG5vZGUuDQoNClRoZXJlIGFyZSBzZXZlcmFsIGFwcHJvYWNoZXMgdG8gYWRqdXN0IFJTRiB0byB0aW1lLWRlcGVuZGVudCBjb3ZhcmlhdGVzLiBPdmVyYWxsLCB0aGV5IHN1Z2dlc3Qgc3BsaXR0aW5nIHBzZXVkby1vYnNlcnZhdGlvbnMgZm9yIG9uZSBzdWJqZWN0IGFjcm9zcyBtYW55IG5vZGVzIGFzIGEgZnVuY3Rpb24gb2YgdGltZSwgc28gaW4gdGhlIGVuZCwgYSBzdWJqZWN0IGNvdWxkIGVuZCB1cCBpbiBtYW55IGRpZmZlcmVudCB0ZXJtaW5hbCBub2RlcywgYnV0IGF0IGFueSBnaXZlbiB0aW1lLCBlYWNoIHN1YmplY3QgY2FuIGJlIGNsYXNzaWZpZWQgaW50byBvbmUgYW5kIG9ubHkgb25lIHRlcm1pbmFsIG5vZGUgW0Bib3UtaGFtYWRfcmV2aWV3XzIwMTFdLg0KDQo8YnI+DQoNCiMjICoqTW9kZWwgZXZhbHVhdGlvbioqDQoNCjxicj4NCg0KVGhlIHNwZWNpZmljIGZlYXR1cmUgZm9yIGFsbCBzdXJ2aXZhbCBtb2RlbHMgaXMgdGhhdCB0aGUgcHJlZGljdGVkIG91dGNvbWUgZm9yIHRoZSBpLXRoIGluZGl2aWR1YWwg4oCTIHRoZSBleHBlY3RlZCB0aW1lIHRvIGV2ZW50ICgkXGhhdHtUX2l9JCkgb3IgcmVsYXRpdmUgcmlzayAoKCRcaGF0e1xldGFfaX0kKSBvciBzdXJ2aXZhbCBkaXN0cmlidXRpb24gKCgkXGhhdHtTX2l9JCkg4oCTIGlzIGEgZGlmZmVyZW50IG9iamVjdCB0aGFuIHRoZSBvdXRjb21lIHVzZWQgZm9yIG1vZGVsIHRyYWluaW5nIOKAkyB0aGUgcGFpciBvZiAkKFlfaSxlX2kpJCwgYXMgdGhleSB3ZXJlIGRlZmluZWQgYWJvdmUuIEl0IGFmZmVjdHMgdGhlIGFwcHJvYWNoZXMgdG8gbW9kZWwgZXZhbHVhdGlvbi4NCg0KRGVwZW5kaW5nIG9uIHRoZSB0YXNrICh3aGF0IGlzIHByZWRpY3RlZCBieSB0aGUgbW9kZWwpIGRpZmZlcmVudCBsb3NzIG9yIHNjb3JlIGZ1bmN0aW9ucyBjYW4gYmUgdXNlZCBbQEBzb25hYmVuZF90aGVvcmV0aWNhbF8yMDIxXToNCg0KKioxKSBGb3IgdGltZS10by1ldmVudCBwcmVkaWN0aW9ucyoqOg0KDQpGb3IgdGhpcyB0YXNrIHdlIGNhbiB1c2UgdGhlIHNhbWUgbWV0cmljcyBhcyBmb3IgcmVncmVzc2lvbnMgd2l0aCBjb250aW51b3VzIHRhcmdldCB2YXJpYWJsZSAoTVNFLCBSTVNFLCBNQUUpLCBleGNlcHQgdGhlIGZhY3QgdGhhdCB0aGV5IGFyZSBldmFsdWF0ZWQgb25seSBmb3IgdW5jZW5zb3JlZCBpbmRpdmlkdWFscywgaS5lLiBmb3IgdGhvc2UgZm9yIHdob20gdGhlIHRpbWUgdG8gZXZlbnQgaXMgb2JzZXJ2YWJsZS4gT2YgY291cnNlLCB0aGV5IGFyZSBub3QgdmVyeSBvcHRpbWFsIHdoZW4gdGhlcmUgaXMgbWFueSBjZW5zb3JlZCBvYnNlcnZhdGlvbnMuDQoNCjxicj4NCg0KKioyKSBGb3IgY29udGludW91cyByZWxhdGl2ZSByaXNrIChyYW5rcykgcHJlZGljdGlvbnMgYW5kIHRpbWUtdG8tZXZlbnQgcHJlZGljdGlvbnMqKjoNCg0KRm9yIHRoaXMga2luZCBvZiB0YXNrcyB1c3VhbGx5IG1lYXN1cmVzIG9mIGRpc2NyaW1pbmF0aW9uIGFyZSB1c2VkLiBJdCBtZWFucyB0aGF0IHRoZSBwcmVkaWN0ZWQgdmFsdWVzIGFyZSBjb25zaWRlcmVkIGFzIGEgbWVhc3VyZSBvZiByZWxhdGl2ZSByaXNrIG9mIGV2ZW50IChhbHRob3VnaCBub3QgaW50ZXJwcmV0YWJsZSksIGFjY29yZGluZyB0byB3aGljaCBvYnNlcnZhdGlvbnMgYXJlIHJhbmtlZCwgYW5kIHRoZW4gdGhlc2UgcmFua3MgYXJlIHVzZWQgdG8gY2FsY3VsYXRlIHRoZSBtZWFzdXJlIG9mIGRpc2NyaW1pbmF0aW9uLiBJdCBtYWtlcyB0aGlzIG1lYXN1cmUgaW5kZXBlbmRlbnQgb2Ygb3V0bGllcnMuIEEgbW9kZWwgaGFzIHBlcmZlY3QgZGlzY3JpbWluYXRpb24gaWYgaXQgY29ycmVjdGx5IHByZWRpY3RzIHRoYXQgcGF0aWVudCB3aXRoIGhpZ2hlciByaXNrIG9mIGV2ZW50IHdpbGwgaGF2ZSBlYXJsaWVyIGV2ZW50Lg0KDQpIZXJlIGFyZSB0d28gdHlwZXMgb2YgZGlzY3JpbWluYXRpb24gbWVhc3VyZXMgW0Bzb25hYmVuZF90aGVvcmV0aWNhbF8yMDIxXToNCg0KLSAqKkNvbmNvcmRhbmNlIGluZGV4KiogKG9yIEMtaW5kZXgsIG9yIHNpbXBseSBDKSBtZWFzdXJlcyB0aGUgcHJvcG9ydGlvbiBvZiBjYXNlcyBpbiB3aGljaCB0aGUgbW9kZWwgY29ycmVjdGx5IHNlcGFyYXRlcyBhIHBhaXIgb2Ygb2JzZXJ2YXRpb25zIGludG8gbG93IGFuZCBoaWdoIHJpc2suIFRoZXJlIGFyZSBzZXZlcmFsIGZvcm0gb2YgY29uY29yZGFuY2UgaW5kaWNlcywgYnV0IHRoZSBtb3N0IHdpZGVseSB1c2VkIGlzIEhhcnJlbGwncyBDOiANCg0KICAgIEZvciBhIHBhaXIgb2Ygb2JzZXJ2YXRpb25zIGkgYW5kIGogKCRpIFxuZXEgaiQpLCBhcnJhbmdlZCBpbiBhc2NlbmRpbmcgb3JkZXIgYnkgdGhlIHZhbHVlcyBvZiBZICgkWV9pIDwgJFlfaiQpIHdpdGggcHJlZGljdGVkIHJlbGF0aXZlIHJpc2sgdmFsdWVzICRcZXRhX2kkIGFuZCAkXGV0YV9qJCwgYW5kIGNlbnNvcmluZyBzdGF0dXNlcyAkZV9pJCBhbmQgJGVfaiQ6DQogICAgDQogICAgLSBJZiBib3RoICRUX2kkIGFuZCAkVF9qJCBhcmUgbm90IGNlbnNvcmVkICgkWV9pID0gVF9pJCwgJFlfaiA9IFRfaiQpLCB0aGUgcGFpciAoaSwgaikgaXMgYSBjb25jb3JkYW50IHBhaXIgaWYgJFxldGFfaSA+IFxldGFfaiQsIGFuZCBpdCBpcyBhIGRpc2NvcmRhbnQgcGFpciBpZiAkXGV0YV9pIDwgXGV0YV9qJC4NCiAgICAtIElmIGJvdGggJFRfaSQgYW5kJCAkVF9qJCBhcmUgY2Vuc29yZWQgKCRZX2kgPSBDX2kkLCAkWV9qID0gQ19qJCksIHRoaXMgcGFpciBpcyBub3QgY29uc2lkZXJlZCBpbiB0aGUgY29tcHV0YXRpb24uDQogICAgLSBJZiBvbmUgb2YgJFRfaSQgYW5kICRUX2okIGlzIGNlbnNvcmVkLCB3ZSBvbmx5IG9ic2VydmUgb25lIGV2ZW50LiBJZiB3ZSBvYnNlcnZlIHRoZSBldmVudCBmb3IgaW5kaXZpZHVhbCBpLCBzbyAkWV9pID0gVF9pJCBhbmQgJFlfaiA9IENfaiQsIHRoZW4gKGksIGopIGlzIGEgY29uY29yZGFudCBwYWlyLCBvdGhlcndpc2Ugd2UgZG9u4oCZdCBjb25zaWRlciB0aGlzIHBhaXIgaW4gdGhlIGNvbXB1dGF0aW9uLiAkJGMgPSBcZnJhY3tcdGV4dHsjIGNvbmNvcmRhbnQgcGFpcnN9fXtcdGV4dHsjIGNvbmNvcmRhbnQgcGFpcnMgKyAjIGRpc2NvbmNvcmRhbnQgcGFpcnN9fSA9IFxmcmFje1xzdW1fe2kgXG5lcSBqfVxtYXRoYmJ7SX0oXGV0YV9pIDwgXGV0YV9qKSBcbWF0aGJie0l9KFRfaSA+IFRfaillX2p9e1xzdW1fe2kgXG5lcSBqfSBcbWF0aGJie0l9KFRfaSA+IFRfaillX2p9JCQgJGMgXGluIFswLDFdJCB3aXRoIDEgaW5kaWNhdGluZyBwZXJmZWN0IHNlcGFyYXRpb24sIDAuNSBpbmRpY2F0ZSB0aGF0IHRoZSByaXNrIHNjb3JlIHByZWRpY3Rpb25zIGFyZSBubyBiZXR0ZXIgdGhhbiBhIGNvaW4gZmxpcCBpbiBkZXRlcm1pbmluZyB3aGljaCBpbmRpdmlkdWFsIHdpbGwgaGF2ZSBsb25nZXIgdGltZSB0byBldmVudCwgYW5kIDAgYmVpbmcgc2VwYXJhdGlvbiB3ZW50IGluIHRoZSBvcHBvc2l0ZSBkaXJlY3Rpb24uDQoNCg0KICAgICBDLWluZGV4IGNhbiBiZSByZWZvcm11bGF0ZWQgZm9yIHRpbWUtdG8tZXZlbnQgcHJlZGljdGlvbiAoanVzdCByZXBsYWNlICRcZXRhX2kkIGFuZCAkXGV0YV9qJCB3aXRoICRcaGF0e1RfaX0kIGFuZCAkXGhhdHtUX2p9JCBhbmQgY2hhbmdlIHRoZSBzaWducyBiZXR3ZWVuICRcZXRhJHMgdG8gdGhlIG9wcG9zaXRlIG9uZXMpLg0KICAgICANCiAgICAgVGhlIG90aGVyIHZhcmlhbnRzIG9mIHRoZSBjLWluZGV4IGFyZSBkaWZmZXJlbnQgZnJvbSB0aGUgSGFycmVsbOKAmXMgQyBpbiB1c2luZyBzb21lIHdlaWdodHMgaW4gbm9taW5hdG9yIGFuZCBkZW5vbWluYXRvciwgY2FsY3VsYXRlZCBiYXNlZCBvbiB0aGUgbm9uLXBhcmFtZXRyaWMgZXN0aW1hdGVzIGZvciBzdXJ2aXZhbCBkaXN0cmlidXRpb24gb3Igc3Vydml2YWwgZnVuY3Rpb24gZm9yIGNlbnNvcmluZyBkaXN0cmlidXRpb24gKGUuZy4gVW5v4oCZcyBDLWluZGV4KS4gVGhleSBhcmUgbGVzcyBhZmZlY3RlZCBieSBjZW5zb3JpbmcgdGhhbiB0aGUgSGFycmVsbOKAmXMgQywgYnV0IGFsc28gbGVzcyBpbnRlcnByZXRhYmxlLg0KDQotICoqVGltZS1kZXBlbmRlbnQgQVVDIGFuZCBDLWluZGV4Kio6DQoNCiAgICBBVUMgbWVhc3VyZXMgZm9yIHN1cnZpdmFsIGFuYWx5c2lzIGhhdmUgYmVlbiBkZXZlbG9wZWQgaW4gb3JkZXIgdG8gcHJvdmlkZSBhIHRpbWUtZGVwZW5kZW50IG1lYXN1cmUgb2YgZGlzY3JpbWluYXRvcnkgYWJpbGl0eSAoZm9yIG1vcmUgZGV0YWlsIHNlZSBAd2FuZ19tYWNoaW5lXzIwMTkpLg0KDQo8YnI+DQogICAgDQoqKjMpIEZvciBzdXJ2aXZhbCBkaXN0cmlidXRpb24gcHJlZGljdGlvbioqOg0KDQpGb3IgdGhpcyB0eXBlIG9mIHRhc2tzIHNldmVyYWwgdHlwZXMgb2YgbWVhc3VyZXMgY2FuIGJlIHVzZWQ6DQoNCi0gKipDYWxpYnJhdGlvbiBtZWFzdXJlcyoqLCBpbmNsdWRpbmcgZ3JhcGhpY2FsIG1ldGhvZHMgb2YgY29tcGFyaXNvbiBiZXR3ZWVuIHByZWRpY3RlZCBhbmQgZXhwZWN0ZWQgZGlzdHJpYnV0aW9uICh0aGUgbGF0dGVyIGlzIG9mdGVuIGFwcHJveGltYXRlZCBieSB0aGUgS2FwbGFuLU1laWVyIGN1cnZlKSBhbmQgbW9kaWZpY2F0aW9uIG9mICRcY2hpXjIkIHRlc3QuDQoNCi0gKipTY29yaW5nIHJ1bGVzKiogKGxvc3MgZnVuY3Rpb25zKSBldmFsdWF0ZSBwcm9iYWJpbGlzdGljIHByZWRpY3Rpb25zIGFuZCAoYXR0ZW1wdCB0bykgbWVhc3VyZSB0aGUgb3ZlcmFsbCBwcmVkaWN0aXZlIGFiaWxpdHkgb2YgYSBtb2RlbCwgaS5lLiBib3RoIGNhbGlicmF0aW9uIGFuZCBkaXNjcmltaW5hdGlvbi4NCg0KICAgIExvc3NlcyBpbiB0aGUgc3Vydml2YWwgc2V0dGluZyBjb21wYXJlIHByZWRpY3RlZCBzdXJ2aXZhbCBkaXN0cmlidXRpb25zIHRvIHRoZSBvYnNlcnZlZCBwYWlycyAoJFlfaWMsIGVfaSQpLiBVc3VhbGx5IGl0IGFsc28gaW5jbHVkZSBhbiBlc3RpbWF0b3Igb2YgdGhlIHVua25vd24gY2Vuc29yaW5nIGRpc3RyaWJ1dGlvbi4gDQogICAgDQogICAgVGhlIG1vc3Qgd2lkZWx5IHVzZWQgbG9zcyBoZXJlIGlzICoqQnJpZXIgc2NvcmUgKEJTKSoqOiAkJEJTKHQpID0gXGZyYWN7MX17Tn1cc3VtX3tpPTF9Xk4gd19pKHQpKFxoYXR7U19pfSh0KSAtIFNfaSh0KSleMiQkIHdoZXJlICRTX2kodCkkIGlzIHRoZSB0cnVlIHN1cnZpdmFsIHByb2JhYmlsaXR5IGZvciBpLXRoIGluZGl2aWR1YWwgYXQgdGltZSAqdCogKGl0IGVxdWFscyAwIGlmICRUX2kgXGxlcSB0JCBhbmQgMSBvdGhlcndpc2UpLCAkXGhhdHtTX2l9KHQpJCAtIHByZWRpY3RlZCBzdXJ2aXZhbCBwcm9iYWJpbGl0eSwgJHdfaSh0KSQgLSB0aGUgd2VpZ2h0IG9mIGktdGggb2JzZXJ2YXRpb24sIGVzdGltYXRlZCBieSBpbmNvcnBvcmF0aW5nIHRoZSBLYXBsYW4tTWVpZXIgZXN0aW1hdG9yIG9mIHRoZSBjZW5zb3JpbmcgZGlzdHJpYnV0aW9uICpHKiBvYnRhaW5lZCBvbiB0aGUgZ2l2ZW4gZGF0YXNldCAkKFgsWSwx4oiSZSkkIFtAd2FuZ19tYWNoaW5lXzIwMTldOiAkd19pKHQpID0gZV9pL0coWV9pKSQgaWYgJFlfaSBcbGVxIHQkIG9yICQxL0coWV9pKSQgb3RoZXJ3aXNlLiBUaGUgd2VpZ2h0cyBmb3IgdGhlIGluc3RhbmNlcyB0aGF0IGFyZSBjZW5zb3JlZCBiZWZvcmUgKnQqIHdpbGwgYmUgMC4gSG93ZXZlciwgdGhleSBjb250cmlidXRlIGluZGlyZWN0bHkgdG8gdGhlIGNhbGN1bGF0aW9uIG9mIHRoZSBCcmllciBzY29yZSBzaW5jZSB0aGV5IGFyZSB1c2VkIGZvciBjYWxjdWxhdGluZyAqRyouDQogICAgDQogICAgKipJbnRlZ3JhdGVkIEJyaWVyIHNjb3JlIChJQlMpKiogaXMgYW4gb3ZlcmFsbCBtZWFzdXJlIGZvciB0aGUgcHJlZGljdGlvbjogJCRJQlMgPSBcZnJhY3sxfXtZX3ttYXh9fSBcaW50XzBee1lfe21heH19IEJTKHQpZHQkJA0KICAgIA0KPGJyPg0KDQoqKjQpIEZvciBtb2RlbHMgd2l0aCB0aW1lLWRlcGVuZGVudCBjb3ZhcmlhdGVzKio6DQoNCkl0IGlzIGEgbW9yZSBjb21wbGljYXRlZCBwcm9jZWR1cmUgdG8gZXN0aW1hdGUgcHJlZGljdGlvbiBwZXJmb3JtYW5jZSBvZiBtb2RlbHMgd2l0aCB0aW1lLWRlcGVuZGVudCBjb3ZhcmlhdGVzLCBidXQgaGVyZSB3ZSBzdGlsbCBjYW4gY2FsY3VsYXRlIEMtaW5kaWNlcywgYXMgd2VsbCBhcyB0aW1lLWRlcGVuZGVudCBCcmllciBzY29yZSBhbmQgaW50ZWdyYXRlZCBCcmllciBzY29yZS4gWW91IGNhbiBmaW5kIG1vcmUgaW5mb3JtYXRpb24gYWJvdXQgaXQgaW4gQHRoZXJuZWF1X2NvbmNvcmRhbmNlXzIwMTcuDQoNCjxicj4NCg0KIyAqKkV4cGxvcmF0b3J5IGRhdGEgYW5hbHlzaXMqKg0KDQo8YnI+DQoNClRoZSB3aG9sZSBkYXRhc2V0IGNvbnNpc3RzIG9mIHNldmVyYWwgZmlsZXMgKHRvdGFsIHNpemUgaXMgMzIgR0IpOg0KDQotICAgdHJhbnNhY3Rpb25zIChgdHJhbnNhY3Rpb25zLmNzdmAgZm9yIHBlcmlvZCB1cCB0byBNYXJjaCwgMjAxNyBhbmQgYHRyYW5zYWN0aW9uc192Mi5jc3ZgIGZvciBwZXJpb2QgdXAgdG8gQXByaWwsIDIwMTcpIC0gaW5mb3JtYXRpb24gYWJvdXQgYnV5aW5nIGEgc3Vic2NyaXB0aW9uLCBpdHMgcmVuZXdhbCAoYXV0byBvciBtYW51YWwpIGFuZCBjYW5jZWxsYXRpb24gKG1hbnVhbCwgYnkgY2xpZW50KTsgd2Ugd2lsbCBkZWZpbmUgb3VyIGRlcGVuZGVudCB2YXJpYWJsZXMgKGNodXJuIGFuZCB0aW1lIHRvIGNodXJuKSB1c2luZyBkYXRhIGZyb20gdGhlc2UgZmlsZXM7DQoNCi0gICBtZW1iZXJzJyBjaGFyYWN0ZXJpc3RpY3MgKGBtZW1iZXJzX3YzLmNzdmApIC0gaW5mb3JtYXRpb24gYWJvdXQgY2l0eSwgYWdlLCBnZW5kZXIsIHJlZ2lzdHJhdGlvbiBtZXRob2QgYW5kIGRhdGUgb2YgaW5pdGlhbCByZWdpc3RyYXRpb24gaW4gdGhlIHNlcnZpY2UsDQoNCi0gICB1c2VyIGxvZ3MgKGB1c2VyX2xvZ3MuY3N2YCBmb3IgcGVyaW9kIHVwIHRvIE1hcmNoLCAyMDE3IGFuZCBgdXNlcl9sb2dzX3YyLmNzdmAgZm9yIHBlcmlvZCB1cCB0byBBcHJpbCwgMjAxNykgLSBkYWlseSB1c2VyIGxvZ3MgZGVzY3JpYmluZyBsaXN0ZW5pbmcgYmVoYXZpb3JzIG9mIHVzZXJzLg0KDQpBcyBvcmdhbml6ZXJzIFthbnN3ZXJlZF0oaHR0cHM6Ly93d3cua2FnZ2xlLmNvbS9jL2trYm94LWNodXJuLXByZWRpY3Rpb24tY2hhbGxlbmdlL2Rpc2N1c3Npb24vNDMxNTMpIHRvIG9uZSBvZiBwYXJ0aWNpcGFudHMnIHF1ZXN0aW9ucyBhdCBLYWdnbGUsIHNvbWV0aW1lcyB0aGV5IGVyYXNlIG1lbWJlcnMgaW5mb3JtYXRpb24sIGJ1dCBtb3N0bHkgZG8gbm90IGVyYXNlIGRhdGEgZnJvbSB0cmFuc2FjdGlvbnMgYW5kIHVzZXIgbG9ncy4gVGFraW5nIGl0IGludG8gY29uc2lkZXJhdGlvbiB3ZSd2ZSBkZWNpZGVkIHRvIGNob29zZSBkYXRhIG9ubHkgZm9yIHRob3NlIHVzZXJzLCB3aG9zZSBJRCBvY2N1cnMgaW4gYWxsIHRocmVlIGZpbGVzIC0gd2UndmUgZm91bmQgYWxtb3N0IDIgbWlsbGlvbiAoMSA5MzcgNzY0KSBzdWNoIElEcy4NCg0KRnVydGhlciwgZm9yIG1vcmUgY29udmVuaWVuY2UgYW5kIGZvciBpbml0aWFsIGFuYWx5c2lzIHdlJ3ZlIGRlY2lkZWQgdG8gc2FtcGxlIDElIG91dCBvZiB0aGVzZSBJRHMgYW5kIGV4Y2x1ZGUgdGhvc2Ugb2YgdGhlbSBmb3Igd2hvbSBhbGwgZGF0ZXMgaW4gdXNlciBsb2dzIGFyZSBtaXNzaW5nLiBBZnRlciB0aGF0IGByIG5yb3cobWVtYmVycylgIHVzZXJzIHJlbWFpbmVkIGluIG91ciBkYXRhc2V0Lg0KDQo8YnI+DQoNCiMjICoqVHJhbnNhY3Rpb25zKioNCg0KPGJyPg0KDQpEYXRhIGZvciBgciBucm93KHRyYW5zKWAgdHJhbnNhY3Rpb25zIGZyb20gYHIgbWluKHRyYW5zJHRyYW5zYWN0aW9uX2RhdGUpYCB0aWxsIGByIG1heCh0cmFucyR0cmFuc2FjdGlvbl9kYXRlKWAuIFRyYW5zYWN0aW9ucyBpbmNsdWRlIHN1YnNjcmlwdGlvbiwgaXRzIHJlbmV3YWwsIGNoYW5nZXMgaW4gc3Vic2NyaXB0aW9uIHBsYW5zIGFuZCBjYW5jZWxsYXRpb25zLCBhbHRob3VnaCB0aGVyZSBpcyBub3QgYW55IGluZGljYXRvciBmb3IgZGlzdGluZ3Vpc2hpbmcgdGhlc2UgdHlwZXMgb2YgdHJhbnNhY3Rpb25zIGZyb20gZWFjaCBvdGhlci4NCg0KYGBge3IgdHJhbnNfdGltZXN9DQp0cmFucyA8LSB0cmFucyAlPiUNCiAgYXJyYW5nZShtc25vLCB0cmFuc2FjdGlvbl9kYXRlKSAlPiUNCiAgZ3JvdXBfYnkobXNubykgJT4lDQogIG11dGF0ZSh0aW1lX2J3X2V4cF90cmFucyA9IGFzLm51bWVyaWMobWVtYmVyc2hpcF9leHBpcmVfZGF0ZSAtIHRyYW5zYWN0aW9uX2RhdGUsICdkYXlzJyksDQogICAgICAgICB0aW1lX2J3X2V4cF9uZXh0dHJhbnMgPSBpZmVsc2UoaXMubmEobGVhZCh0cmFuc2FjdGlvbl9kYXRlKSksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXMubnVtZXJpYyhhcy5EYXRlKCIyMDE3LTAzLTMxIikgLSBtZW1iZXJzaGlwX2V4cGlyZV9kYXRlLCAnZGF5cycpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFzLm51bWVyaWMobGVhZCh0cmFuc2FjdGlvbl9kYXRlKSAtIG1lbWJlcnNoaXBfZXhwaXJlX2RhdGUsICdkYXlzJykpLA0KICAgICAgICAgdGltZV9id190cmFucyA9IGFzLm51bWVyaWMobGVhZCh0cmFuc2FjdGlvbl9kYXRlKSAtIHRyYW5zYWN0aW9uX2RhdGUsICdkYXlzJyksDQogICAgICAgICB0cmFuc19pZCA9IDE6bigpKSAlPiUNCiAgdW5ncm91cCgpDQoNCnRyYW5zX2RmIDwtIHRyYW5zICU+JQ0KICBhcnJhbmdlKG1zbm8sIHRyYW5zYWN0aW9uX2RhdGUpICU+JQ0KICBtdXRhdGUobWVtYmVyc2hpcF9leHBpcmVfZGF0ZV9jb3IgPSBhcy5EYXRlKGlmZWxzZShtZW1iZXJzaGlwX2V4cGlyZV9kYXRlIDwgdHJhbnNhY3Rpb25fZGF0ZSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRyYW5zYWN0aW9uX2RhdGUsIG1lbWJlcnNoaXBfZXhwaXJlX2RhdGUpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICIxOTcwLTAxLTAxIikpICU+JQ0KICBncm91cF9ieShtc25vKSAlPiUNCiAgbXV0YXRlKHRpbWVfYndfZXhwX3RyYW5zID0gYXMubnVtZXJpYyhtZW1iZXJzaGlwX2V4cGlyZV9kYXRlX2NvciAtIHRyYW5zYWN0aW9uX2RhdGUsICdkYXlzJyksDQogICAgICAgICB0aW1lX2J3X2V4cF9uZXh0dHJhbnMgPSBpZmVsc2UoaXMubmEobGVhZCh0cmFuc2FjdGlvbl9kYXRlKSksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXMubnVtZXJpYyhhcy5EYXRlKCIyMDE3LTAzLTMxIikgLSBtZW1iZXJzaGlwX2V4cGlyZV9kYXRlX2NvciwgJ2RheXMnKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhcy5udW1lcmljKGxlYWQodHJhbnNhY3Rpb25fZGF0ZSkgLSBtZW1iZXJzaGlwX2V4cGlyZV9kYXRlX2NvciwgJ2RheXMnKSkpICU+JQ0KICB1bmdyb3VwKCkNCmBgYA0KDQo8YnI+DQoNClRyYW5zYWN0aW9uIGZpbGUgY29udGFpbnMgdGhlIGZvbGxvd2luZyBpbmZvcm1hdGlvbjoNCg0KPGJyPg0KDQotICAgKipgdHJhbnNhY3Rpb25fZGF0ZWAqKiAtIHRoZSBkYXRlIG9mIHRyYW5zYWN0aW9uLg0KDQpgYGB7ciB0cmFuc19zdGF0c30NCnRyYW5zX3N0YXQgPC0gdHJhbnMgJT4lDQogIGdyb3VwX2J5KG1zbm8pICU+JQ0KICBzdW1tYXJpc2UobiA9IG4oKSwNCiAgICAgICAgICAgIHN1bV9jYW5jZWwgPSBzdW0oaXNfY2FuY2VsKSwNCiAgICAgICAgICAgIHN1bV9hdXRvID0gc3VtKGlzX2F1dG9fcmVuZXcpLA0KICAgICAgICAgICAgKSAlPiUNCiAgdW5ncm91cCgpDQoNCnRyYW5zX2ZyZXFfbWF4IDwtIHRyYW5zICU+JQ0KICBmaWx0ZXIoIWlzLm5hKHRpbWVfYndfdHJhbnMpKSAlPiUNCiAgZ3JvdXBfYnkodGltZV9id190cmFucykgJT4lDQogIHRhbGx5KCkgJT4lDQogIGFycmFuZ2UoLW4pDQoNCnAxIDwtIHRyYW5zX3N0YXQgJT4lDQogIGZpbHRlcihuIDwgcXVhbnRpbGUodHJhbnNfc3RhdCRuLCAwLjk5KSkgJT4lDQogZ2dwbG90KCkgKw0KICBhZXMoeCA9IG4pICsNCiAgZ2VvbV9oaXN0b2dyYW0oYmlud2lkdGggPSAxLCBmaWxsID0gIiM0NjgyQjQiKSArDQogIGdlb21fdGV4dCh4ID0gMTUsIHkgPSA0MDAwLCBsYWJlbCA9IHNwcmludGYoIk1lYW4gKFNEKTogJS4xZiAoJS4xZikiLCBtZWFuKHRyYW5zX3N0YXQkbiksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2QodHJhbnNfc3RhdCRuKSksDQogICAgICAgICAgICBoanVzdCA9IDAsIGNvbG9yID0gImdyZXkyMCIsIHNpemUgPSAzLjUpICsNCiAgZ2VvbV90ZXh0KHggPSAxNSwgeSA9IDM3MDAsIGxhYmVsID0gc3ByaW50ZigiTWVkaWFuIChRMSwgUTMpOiAlLjBmICglLjBmLCAlLjBmKSIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1lZGlhbih0cmFuc19zdGF0JG4pLCBxdWFudGlsZSh0cmFuc19zdGF0JG4sIDAuMjUpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHF1YW50aWxlKHRyYW5zX3N0YXQkbiwgMC43NSkpLA0KICAgICAgICAgICAgaGp1c3QgPSAwLCBjb2xvciA9ICJncmV5MjAiLCBzaXplID0gMy41KSArDQogIGdlb21fdGV4dCh4ID0gMTUsIHkgPSAzNDAwLCBsYWJlbCA9IHNwcmludGYoIk1pbi1NYXg6ICUuMGYtJS4wZiIsIG1pbih0cmFuc19zdGF0JG4pLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1heCh0cmFuc19zdGF0JG4pKSwNCiAgICAgICAgICAgIGhqdXN0ID0gMCwgY29sb3IgPSAiZ3JleTIwIiwgc2l6ZSA9IDMuNSkgKw0KICBsYWJzKHggPSAiIiwgeSA9ICIiLCB0aXRsZSA9ICJOdW1iZXIgb2YgcmVjb3JkcyBwZXIgSUQgaW4gdHJhbnNhY3Rpb25zXG4oZm9yIHZhbHVlcyA8IDk5dGggcGVyY2VudGlsZSkiKSArDQogIHNjYWxlX3hfY29udGludW91cyhicmVha3MgPSBzZXEoMCwgMzAsIDUpLCBleHBhbmQgPSBjKDAsMCksDQogICAgICAgICAgICAgICAgICAgICBsaW1pdHMgPSBjKDAsIDMwKSkgKw0KICBzY2FsZV95X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLDApLCANCiAgICAgICAgICAgICAgICAgICAgIGJyZWFrcyA9IHNlcSgwLCA4MDAwLCAxMDAwKSwgbGFiZWxzID0gYygwLCBwYXN0ZTAoc2VxKDEsOCwxKSwgIksiKSkpICsNCiAgZ2d0aGVtZXM6OnRoZW1lX3R1ZnRlKCkgKw0KICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIpLA0KICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIpKQ0KDQpwMiA8LSB0cmFucyAlPiUNCiAgZmlsdGVyKHRpbWVfYndfdHJhbnMgPCBxdWFudGlsZSh0cmFucyR0aW1lX2J3X3RyYW5zLCAwLjk5LCBuYS5ybSA9IFRSVUUpKSAlPiUNCiBnZ3Bsb3QoKSArDQogIGFlcyh4ID0gdGltZV9id190cmFucykgKw0KICBnZW9tX2hpc3RvZ3JhbShiaW53aWR0aCA9IDUsIGZpbGwgPSAiIzQ2ODJCNCIpICsNCiAgZ2VvbV90ZXh0KHggPSA5MCwgeSA9IDE0MDAwMCwgbGFiZWwgPSBzcHJpbnRmKCJNZWFuIChTRCk6ICUuMWYgKCUuMWYpIiwgbWVhbih0cmFucyR0aW1lX2J3X3RyYW5zLCBuYS5ybSA9IFRSVUUpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNkKHRyYW5zJHRpbWVfYndfdHJhbnMsIG5hLnJtID0gVFJVRSkpLA0KICAgICAgICAgICAgaGp1c3QgPSAwLCBjb2xvciA9ICJncmV5MjAiLCBzaXplID0gMy41KSArDQogIGdlb21fdGV4dCh4ID0gOTAsIHkgPSAxMzAwMDAsIGxhYmVsID0gc3ByaW50ZigiTWVkaWFuIChRMSwgUTMpOiAlLjBmICglLjBmLCAlLjBmKSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWVkaWFuKHRyYW5zJHRpbWVfYndfdHJhbnMsIG5hLnJtID0gVFJVRSksIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHF1YW50aWxlKHRyYW5zJHRpbWVfYndfdHJhbnMsIDAuMjUsIG5hLnJtID0gVFJVRSksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcXVhbnRpbGUodHJhbnMkdGltZV9id190cmFucywgMC43NSwgbmEucm0gPSBUUlVFKSksDQogICAgICAgICAgICBoanVzdCA9IDAsIGNvbG9yID0gImdyZXkyMCIsIHNpemUgPSAzLjUpICsNCiAgZ2VvbV90ZXh0KHggPSA5MCwgeSA9IDEyMDAwMCwgbGFiZWwgPSBzcHJpbnRmKCJNaW4tTWF4OiAlLjBmLSUuMGYiLCBtaW4odHJhbnMkdGltZV9id190cmFucywgbmEucm0gPSBUUlVFKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtYXgodHJhbnMkdGltZV9id190cmFucywgbmEucm0gPSBUUlVFKSksDQogICAgICAgICAgICBoanVzdCA9IDAsIGNvbG9yID0gImdyZXkyMCIsIHNpemUgPSAzLjUpICsNCiAgbGFicyh4ID0gIiIsIHkgPSAiIiwgdGl0bGUgPSAiVGltZSBiZXR3ZWVuIHRyYW5zYWN0aW9uc1xuKGZvciB2YWx1ZXMgPCA5OXRoIHBlcmNlbnRpbGUpIikgKw0KICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gc2VxKDAsIDIxMCwgMzApLCBleHBhbmQgPSBjKDAsMCkpICsNCiAgc2NhbGVfeV9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwwKSwgbGltaXRzID0gYygwLCAxNTAwMDApLA0KICAgICAgICAgICAgICAgICAgICAgYnJlYWtzID0gc2VxKDAsIDE1MDAwMCwgNTAwMDApLCANCiAgICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IGMoMCwgcGFzdGUwKHNlcSg1MCwxNTAsNTApLCAiSyIpKSkgKw0KICBnZ3RoZW1lczo6dGhlbWVfdHVmdGUoKSArDQogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiksDQogICAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMikpDQoNCmdnYXJyYW5nZShwMSwgcDIsIG5yb3cgPSAxLCBuY29sID0gMikNCmBgYA0KDQpgYGB7ciB0cmFuc19zdGF0c19wbG90LCBmaWcud2lkdGg9MTAsIGZpZy5oZWlnaHQ9Mywgd2FybmluZz1GQUxTRSwgZXZhbD1UUlVFfQ0KdHJhbnNfc3RhdCA8LSB0cmFucyAlPiUNCiAgZ3JvdXBfYnkobXNubykgJT4lDQogIHN1bW1hcmlzZShuID0gbigpLA0KICAgICAgICAgICAgc3VtX2NhbmNlbCA9IHN1bShpc19jYW5jZWwpLA0KICAgICAgICAgICAgc3VtX2F1dG8gPSBzdW0oaXNfYXV0b19yZW5ldyksDQogICAgICAgICAgICApICU+JQ0KICB1bmdyb3VwKCkNCg0KdHJhbnNfZnJlcV9tYXggPC0gdHJhbnMgJT4lDQogIGZpbHRlcighaXMubmEodGltZV9id190cmFucykpICU+JQ0KICBncm91cF9ieSh0aW1lX2J3X3RyYW5zKSAlPiUNCiAgdGFsbHkoKSAlPiUNCiAgYXJyYW5nZSgtbikNCg0KcDEgPC0gdHJhbnNfc3RhdCAlPiUNCiAgZmlsdGVyKG4gPCBxdWFudGlsZSh0cmFuc19zdGF0JG4sIDAuOTkpKSAlPiUNCiBnZ3Bsb3QoKSArDQogIGFlcyh4ID0gbikgKw0KICBnZW9tX2hpc3RvZ3JhbShiaW53aWR0aCA9IDEsIGZpbGwgPSAiIzQ2ODJCNCIpICsNCiAgZ2VvbV90ZXh0KHggPSAxNSwgeSA9IDQwMDAsIGxhYmVsID0gc3ByaW50ZigiTWVhbiAoU0QpOiAlLjFmICglLjFmKSIsIG1lYW4odHJhbnNfc3RhdCRuKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZCh0cmFuc19zdGF0JG4pKSwNCiAgICAgICAgICAgIGhqdXN0ID0gMCwgY29sb3IgPSAiZ3JleTIwIiwgc2l6ZSA9IDMuNSkgKw0KICBnZW9tX3RleHQoeCA9IDE1LCB5ID0gMzcwMCwgbGFiZWwgPSBzcHJpbnRmKCJNZWRpYW4gKFExLCBRMyk6ICUuMGYgKCUuMGYsICUuMGYpIiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWVkaWFuKHRyYW5zX3N0YXQkbiksIHF1YW50aWxlKHRyYW5zX3N0YXQkbiwgMC4yNSksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcXVhbnRpbGUodHJhbnNfc3RhdCRuLCAwLjc1KSksDQogICAgICAgICAgICBoanVzdCA9IDAsIGNvbG9yID0gImdyZXkyMCIsIHNpemUgPSAzLjUpICsNCiAgZ2VvbV90ZXh0KHggPSAxNSwgeSA9IDM0MDAsIGxhYmVsID0gc3ByaW50ZigiTWluLU1heDogJS4wZi0lLjBmIiwgbWluKHRyYW5zX3N0YXQkbiksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWF4KHRyYW5zX3N0YXQkbikpLA0KICAgICAgICAgICAgaGp1c3QgPSAwLCBjb2xvciA9ICJncmV5MjAiLCBzaXplID0gMy41KSArDQogIGxhYnMoeCA9ICIiLCB5ID0gIiIsIHRpdGxlID0gIk51bWJlciBvZiByZWNvcmRzIHBlciBJRCBpbiB0cmFuc2FjdGlvbnNcbihmb3IgdmFsdWVzIDwgOTl0aCBwZXJjZW50aWxlKSIpICsNCiAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IHNlcSgwLCAzMCwgNSksIGV4cGFuZCA9IGMoMCwwKSwNCiAgICAgICAgICAgICAgICAgICAgIGxpbWl0cyA9IGMoMCwgMzApKSArDQogIHNjYWxlX3lfY29udGludW91cyhleHBhbmQgPSBjKDAsMCksIA0KICAgICAgICAgICAgICAgICAgICAgYnJlYWtzID0gc2VxKDAsIDgwMDAsIDEwMDApLCBsYWJlbHMgPSBjKDAsIHBhc3RlMChzZXEoMSw4LDEpLCAiSyIpKSkgKw0KICBnZ3RoZW1lczo6dGhlbWVfdHVmdGUoKSArDQogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiksDQogICAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMikpDQoNCnAyIDwtIHRyYW5zICU+JQ0KICBmaWx0ZXIodGltZV9id190cmFucyA8IHF1YW50aWxlKHRyYW5zJHRpbWVfYndfdHJhbnMsIDAuOTksIG5hLnJtID0gVFJVRSkpICU+JQ0KIGdncGxvdCgpICsNCiAgYWVzKHggPSB0aW1lX2J3X3RyYW5zKSArDQogIGdlb21faGlzdG9ncmFtKGJpbndpZHRoID0gNSwgZmlsbCA9ICIjNDY4MkI0IikgKw0KICBnZW9tX3RleHQoeCA9IDkwLCB5ID0gMTQwMDAwLCBsYWJlbCA9IHNwcmludGYoIk1lYW4gKFNEKTogJS4xZiAoJS4xZikiLCBtZWFuKHRyYW5zJHRpbWVfYndfdHJhbnMsIG5hLnJtID0gVFJVRSksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2QodHJhbnMkdGltZV9id190cmFucywgbmEucm0gPSBUUlVFKSksDQogICAgICAgICAgICBoanVzdCA9IDAsIGNvbG9yID0gImdyZXkyMCIsIHNpemUgPSAzLjUpICsNCiAgZ2VvbV90ZXh0KHggPSA5MCwgeSA9IDEzMDAwMCwgbGFiZWwgPSBzcHJpbnRmKCJNZWRpYW4gKFExLCBRMyk6ICUuMGYgKCUuMGYsICUuMGYpIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtZWRpYW4odHJhbnMkdGltZV9id190cmFucywgbmEucm0gPSBUUlVFKSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcXVhbnRpbGUodHJhbnMkdGltZV9id190cmFucywgMC4yNSwgbmEucm0gPSBUUlVFKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBxdWFudGlsZSh0cmFucyR0aW1lX2J3X3RyYW5zLCAwLjc1LCBuYS5ybSA9IFRSVUUpKSwNCiAgICAgICAgICAgIGhqdXN0ID0gMCwgY29sb3IgPSAiZ3JleTIwIiwgc2l6ZSA9IDMuNSkgKw0KICBnZW9tX3RleHQoeCA9IDkwLCB5ID0gMTIwMDAwLCBsYWJlbCA9IHNwcmludGYoIk1pbi1NYXg6ICUuMGYtJS4wZiIsIG1pbih0cmFucyR0aW1lX2J3X3RyYW5zLCBuYS5ybSA9IFRSVUUpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1heCh0cmFucyR0aW1lX2J3X3RyYW5zLCBuYS5ybSA9IFRSVUUpKSwNCiAgICAgICAgICAgIGhqdXN0ID0gMCwgY29sb3IgPSAiZ3JleTIwIiwgc2l6ZSA9IDMuNSkgKw0KICBsYWJzKHggPSAiIiwgeSA9ICIiLCB0aXRsZSA9ICJUaW1lIGJldHdlZW4gdHJhbnNhY3Rpb25zXG4oZm9yIHZhbHVlcyA8IDk5dGggcGVyY2VudGlsZSkiKSArDQogIHNjYWxlX3hfY29udGludW91cyhicmVha3MgPSBzZXEoMCwgMjEwLCAzMCksIGV4cGFuZCA9IGMoMCwwKSkgKw0KICBzY2FsZV95X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLDApLCBsaW1pdHMgPSBjKDAsIDE1MDAwMCksDQogICAgICAgICAgICAgICAgICAgICBicmVha3MgPSBzZXEoMCwgMTUwMDAwLCA1MDAwMCksIA0KICAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gYygwLCBwYXN0ZTAoc2VxKDUwLDE1MCw1MCksICJLIikpKSArDQogIGdndGhlbWVzOjp0aGVtZV90dWZ0ZSgpICsNCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiKSwNCiAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyKSkNCg0KZ2dhcnJhbmdlKHAxLCBwMiwgbnJvdyA9IDEsIG5jb2wgPSAyKQ0KYGBgDQoNCjxicj4NCg0KLS0gQWJvdXQgYHIgcm91bmQoc3VtKHRyYW5zX3N0YXQkbiA9PSAxKS9ucm93KHRyYW5zX3N0YXQpKjEwMCwxKWAlIHVzZXJzIGhhdmUgb25seSBvbmUgdHJhbnNhY3Rpb24gaW4gdGhlIHdob2xlIGRhdGFzZXQuDQoNCi0tIFVzdWFsbHkgdGhlcmUgaXMgYWJvdXQgMzAgZGF5cyBiZXR3ZWVuIHR3byBjb25zZdGBdXRpdmUgdHJhbnNhY3Rpb25zIG9mIG9uZSB1c2VyLiBQYXJ0aWN1bGFybHksIGFtb25nIGFsbCBwYWlycyBvZiBjb25zZWN1dGl2ZSB0cmFuc2FjdGlvbnMgYWJvdXQgYHIgcm91bmQodHJhbnNfZnJlcV9tYXgkblt0cmFuc19mcmVxX21heCR0aW1lX2J3X3RyYW5zID09IDMwXS9zdW0odHJhbnNfZnJlcV9tYXgkbikqMTAwLDEpYCUgYXJlIGRpc3RhbmNlZCBieSAzMCBkYXlzLCBhbmQgYHIgcm91bmQodHJhbnNfZnJlcV9tYXgkblt0cmFuc19mcmVxX21heCR0aW1lX2J3X3RyYW5zID09IDMxXS9zdW0odHJhbnNfZnJlcV9tYXgkbikqMTAwLDEpYCUgLSBieSAzMSwgYWxsIG90aGVyIHBlcmlvZHMgb2NjdXIgZm9yIGxlc3MgdGhhbiA3JSB0aW1lcyBlYWNoLg0KDQo8YnI+DQoNCi0gICAqKmBtZW1iZXJzaGlwX2V4cGlyZV9kYXRlYCoqIC0gc3Vic2NyaXB0aW9uIGV4cGlyYXRpb24gZGF0ZSBhZnRlciB0aGlzIHRyYW5zYWN0aW9uLiBUaGVyZSB3ZXJlIGByIHN1bSh0cmFucyRtZW1iZXJzaGlwX2V4cGlyZV9kYXRlIDwgdHJhbnMkdHJhbnNhY3Rpb25fZGF0ZSlgIHJlY29yZHMgd2l0aCBtZW1iZXJzaGlwIGV4cGlyYXRpb24gZGF0ZSBsZXNzIHRoYW4gdHJhbnNhY3Rpb24gZGF0ZS4gVGhlIG1pbmltdW0gZXhwaXJhdGlvbiBkYXRlIGZvciB0aGVtIHdhcyBgciBtaW4odHJhbnMkbWVtYmVyc2hpcF9leHBpcmVfZGF0ZSlgLCBmb3IgYHIgc3VtKHRyYW5zJG1lbWJlcnNoaXBfZXhwaXJlX2RhdGUgPCBhcy5EYXRlKCIyMDE1LTAxLTAxIikpYCB0cmFuc2FjdGlvbnMgaXQgd2VudCBiZWZvcmUgMjAxNS0wMS0wMSAodGhlIG1pbmltdW0gdHJhbnNhY3Rpb24gZGF0ZSBpbiB0aGUgd2hvbGUgZGF0YXNldCksIHNvIHRoZXJlIGFyZSBvYnZpb3VzbHkgc29tZSBvZGQgdmFsdWVzIGhlcmUsIGFuZCB3ZSBzaG91bGQgZG8gc29tZXRoaW5nIHdpdGggdGhlbSBwcmlvciB0byB0aGUgYW5hbHlzaXMuDQoNCi0tIE91dCBvZiBhbGwgbmVnYXRpdmUgdmFsdWVzIGZvciB0aGUgdGltZSBiZXR3ZWVuIG1lbWJlcnNoaXAgZXhwaXJlIGRhdGUgYW5kIHRyYW5zYWN0aW9uIGRhdGUgbGlzdGVkIGZvciB0aGUgc2FtZSB0cmFuc2FjdGlvbiBgciByb3VuZChzdW0odHJhbnMkdGltZV9id19leHBfdHJhbnMgPiAtOTAgJiB0cmFucyR0aW1lX2J3X2V4cF90cmFucyA8IDApL3N1bSh0cmFucyR0aW1lX2J3X2V4cF90cmFucyA8IDApKjEwMCwxKWAlIHdlcmUgYmV0d2VlbiAtODAgYW5kIDAgZGF5czsgbW9yZW92ZXIsIGByIHJvdW5kKHN1bSh0cmFucyR0aW1lX2J3X2V4cF90cmFucyA+IC0zICYgdHJhbnMkdGltZV9id19leHBfdHJhbnMgPCAwKS9zdW0odHJhbnMkdGltZV9id19leHBfdHJhbnMgPCAwKSoxMDAsMSlgJSBlcXVhbHMgLTEgb3IgLTIgZGF5cywgYW5kIGByIHJvdW5kKHN1bSh0cmFucyRpc19jYW5jZWwgJiB0cmFucyR0aW1lX2J3X2V4cF90cmFucyA8IDApL3N1bSh0cmFucyR0aW1lX2J3X2V4cF90cmFucyA8IDApKjEwMCwxKWAlIG9mIGFsbCB0cmFuc2FjdGlvbnMgd2l0aCBuZWdhdGl2ZSB0aW1lIGFyZSB3aXRoIGBpc19jYW5jZWxgPTEuDQoNCi0tIFRoZXJlIHdlcmUgbm8gc3BlY2lmaWMgZXhwbGFuYXRpb25zIGZvciB0aGVzZSBjYXNlcyBmcm9tIG9yZ2FuaXplcnMsIGJ1dCB0b28gbmVnYXRpdmUgdmFsdWVzIGxvb2sgdmVyeSBvZGQsIGVzcGVjaWFsbHkgdGFraW5nIGludG8gYWNjb3VudCB0aGF0IG1vc3Qgb2YgbmVnYXRpdmUgdGltZSB0cmFuc2FjdGlvbnMgKGByIHJvdW5kKHN1bSghaXMubmEodHJhbnMkdGltZV9id190cmFucykgJiB0cmFucyR0aW1lX2J3X2V4cF90cmFucyA8IDApL3N1bSh0cmFucyR0aW1lX2J3X2V4cF90cmFucyA8IDApKjEwMCwxKWAlKSB3ZXJlIG5vdCB0aGUgbGFzdCBmb3IgdGhlIGNvcnJlc3BvbmRpbmcgdXNlci4gU28gd2UgaGF2ZSBkZWNpZGVkIHRoYXQgaXQgd2lsbCBiZSByZWFzb25hYmxlIHRvIHJlcGxhY2UgbWVtYmVyc2hpcCBleHBpcmF0aW9uIGRhdGUgaW4gYWxsIHN1Y2ggdHJhbnNhY3Rpb25zIGludG8gdGhlIGRhdGUgb2YgdGhlIGNvcnJlc3BvbmRpbmcgdHJhbnNhY3Rpb246IGl0IHdpbGwgaGFyZGx5IGJpYXMgc29tZSBmdXJ0aGVyIHJlc3VsdHMsIGFzIHRoZSBudW1iZXIgb2Ygc3VjaCB0cmFuc2FjdGlvbnMgaXMgcXVpdGUgc21hbGwsIGFuZCBtb3N0IG9mIHRoZW0gY29udGFpbnMgYWxtb3N0IGVxdWFsIHRyYW5zYWN0aW9uIGFuZCBleHBpcmF0aW9uIGRhdGVzLg0KDQo8YnI+DQoNCi0gICAqKmBpcyBjYW5jZWxgKiogLSB3aGV0aGVyIG9yIG5vdCB0aGUgdXNlciBjYW5jZWxsZWQgdGhlIG1lbWJlcnNoaXAgaW4gdGhpcyB0cmFuc2FjdGlvbi4gQW1vbmcgYWxsIHJlY29yZHMgYHIgcm91bmQoc3VtKHRyYW5zX2RmJGlzX2NhbmNlbCkvbnJvdyh0cmFuc19kZikqMTAwLDEpYCUgY29uY2VybmVkIGNhbmNlbGxhdGlvbi4gQWNjb3JkaW5nIHRvIG9yZ2FuaXplcnMsIHN1YnNjcmlwdGlvbiBjYW5jZWxsYXRpb24gZG9lcyBub3QgaW1wbHkgdGhlIHVzZXIgaGFzIGNodXJuZWQsIGFzIGhlL3NoZSBtYXkgY2FuY2VsIHN1YnNjcmlwdGlvbiBkdWUgdG8gY2hhbmdlIG9mIHNlcnZpY2UgcGxhbnMgb3Igb3RoZXIgcmVhc29ucy4gQW1vbmcgYHIgbnJvdyh0cmFuc19zdGF0KWAgdXNlcnMgYHIgcm91bmQoc3VtKHRyYW5zX3N0YXQkc3VtX2NhbmNlbCA+IDApL25yb3codHJhbnNfc3RhdCkqMTAwLDEpYCUgY2FuY2VsbGVkIHRoZWlyIHN1YnNjcmlwdGlvbiBmb3IgYXQgbGVhc3Qgb25jZS4NCg0KPGJyPg0KDQotICAgKipgaXNfYXV0b19yZW5ld2AqKiAtIGZvciB0cmFuc2FjdGlvbnMgbm90IGNvbmNlcm5pbmcgY2FuY2VsbGF0aW9uIHdoZXRoZXIgaXQgd2FzIGF1dG8gb3IgbWFudWFsIHJlbmV3YWwgb2Ygc3Vic2NyaXB0aW9uLiBBbW9uZyBhbGwgcmVjb3JkcyBgciByb3VuZChzdW0odHJhbnNfZGYkaXNfYXV0b19yZW5ldykvbnJvdyh0cmFuc19kZikqMTAwLDEpYCUgYXJlIGF1dG8tcmVuZXdhbHMuIEFsbCBjYW5jZWxsYXRpb24gdHJhbnNhY3Rpb25zIGFyZSBtYXJrZWQgYXMgYXV0by1yZW5ld2FscywgYW1vbmcgbm9uLWNhbmNlbGxhdGlvbiB0cmFuc2FjdGlvbnMgYHIgcm91bmQoc3VtKHRyYW5zX2RmJGlzX2F1dG9fcmVuZXcgJiAhdHJhbnNfZGYkaXNfY2FuY2VsKS9zdW0oIXRyYW5zX2RmJGlzX2NhbmNlbCkqMTAwLDEpYCUgYXJlIGF1dG8tcmVuZXdhbHMuIE9yZ2FuaXplcnMgZGlkIG5vdCBnaXZlIGFueSBjbGFyaWZpY2F0aW9uIGFib3V0IHdoYXQgYXV0by1yZW5ld2FsIG1lYW5zLCBidXQgYXMgd2Ugd2lsbCBzZWUgYmVsb3csIG1vc3QgbGlrZWx5LCBhdXRvLXJlbmV3YWwgbWVhbnMgYXV0b21vdGl2ZSByZW5ld2FsIG9mIHRoZSBtZW1iZXJzaGlwIGV4cGlyYXRpb24gZGF0ZTogZm9yIGV4YW1wbGUsIHdoZW4gYSB1c2VyIGNhbmNlbHMgaGlzL2hlciBzdWJzY3JpcHRpb24gb3IgY2hhbmdlcyBhIHBsYW4gZnJvbSwgc2F5LCAxLW1vbnRoIHRvIDItbW9udGhzLCB0aGVuIG1lbWJlcnNoaXAgZXhwaXJhdGlvbiBkYXRlIGNoYW5nZXMgYXV0b21hdGljYWxseS4NCg0KICAgIEFtb25nIGByIG5yb3codHJhbnNfc3RhdClgIHVzZXJzIGByIHJvdW5kKHN1bSh0cmFuc19zdGF0JHN1bV9hdXRvID09IDApL25yb3codHJhbnNfc3RhdCkqMTAwLDEpYCUgaGFkIG9ubHkgbWFudWFsIHJlbmV3YWxzLCBgciByb3VuZChzdW0odHJhbnNfc3RhdCRzdW1fYXV0byA9PSB0cmFuc19zdGF0JG4pL25yb3codHJhbnNfc3RhdCkqMTAwLDEpYCUgaGFkIG9ubHkgYXV0by1yZW51YWxzLCB0aGUgcmVzdCAoYHIgcm91bmQoc3VtKHRyYW5zX3N0YXQkc3VtX2F1dG8gIT0gMCAmIHRyYW5zX3N0YXQkc3VtX2F1dG8gIT0gdHJhbnNfc3RhdCRuKS9ucm93KHRyYW5zX3N0YXQpKjEwMCwxKWAlKSBoYXZlIGJvdGggb2YgdGhlbS4NCg0KPGJyPg0KDQotICAgKipgcGF5bWVudF9tZXRob2RfaWRgKiogLSBwYXltZW50IG1ldGhvZC4gT3JnYW5pemVycyBhdCBLYWdnbGUgZGlkIG5vdCBwcm92aWRlIGFueSBpbmZvcm1hdGlvbiBhYm91dCB0aGVzZSBtZXRob2RzLiBUaGVyZSB3ZXJlIGByIGxlbmd0aCh1bmlxdWUodHJhbnNfZGYkcGF5bWVudF9tZXRob2RfaWQpKWB1bmlxdWUgbnVtZXJpYyBjb2RlcyBmb3IgdGhlbS4NCg0KYGBge3IgcGF5bWVudF9tZXRob2R9DQpwbHRfZGYgPC0gdHJhbnNfZGYgJT4lDQogIGdyb3VwX2J5KHBheW1lbnRfbWV0aG9kX2lkKSAlPiUNCiAgdGFsbHkoKSAlPiUNCiAgdW5ncm91cCgpICU+JQ0KICBtdXRhdGUocGN0ID0gbi9zdW0obikqMTAwLA0KICAgICAgICAgbGJsID0gc3ByaW50ZigiJS4wZiUlIiwgcGN0KSwNCiAgICAgICAgIGxibCA9IGlmZWxzZShsYmwgPT0gIjAlIiwgIjwxJSIsIGxibCkpDQoNCnRyYW5zX2RmICU+JQ0KIGdncGxvdCgpICsNCiAgYWVzKHggPSBwYXltZW50X21ldGhvZF9pZCkgKw0KICBnZW9tX2JhcihmaWxsID0gIiMyRDU1OUUiKSArDQogIGdlb21fdGV4dChhZXMoeCA9IHBheW1lbnRfbWV0aG9kX2lkLCB5ID0gbiAtIDI1MCwgbGFiZWwgPSBsYmwpLA0KICAgICAgICAgICAgcGx0X2RmICU+JSBmaWx0ZXIocGN0ID4gNCksIGFuZ2xlID0gOTAsIGhqdXN0ID0gMSwgdmp1c3QgPSAwLjUsDQogICAgICAgICAgICBjb2xvciA9ICJ3aGl0ZSIsIGZvbnRmYWNlID0gImJvbGQiKSArDQogIGxhYnModGl0bGUgPSAiUGF5bWVudCBtZXRob2QiLCB5ID0gIiIsIHggPSAiIikgKw0KICBzY2FsZV95X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLCAwKSwgYnJlYWtzID0gc2VxKDAsMTAwMDAwLDIwMDAwKSwNCiAgICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IGMoMCwgcGFzdGUwKHNlcSgyMCwxMDAsMjApLCAiSyIpKSwNCiAgICAgICAgICAgICAgICAgICAgIGxpbWl0cyA9IGMoMCwgMTAwMDAwKSkgKw0KICBnZ3RoZW1lczo6dGhlbWVfdHVmdGUoKSArDQogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiksDQogICAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCksDQogICAgICAgIGF4aXMudGlja3MueCA9IGVsZW1lbnRfYmxhbmsoKSkNCmBgYA0KDQpgYGB7ciBwYXltZW50X21ldGhvZF9wbG90LCBmaWcud2lkdGg9OSwgZmlnLmhlaWdodD0zLCBldmFsPVRSVUV9DQpwbHRfZGYgPC0gdHJhbnNfZGYgJT4lDQogIGdyb3VwX2J5KHBheW1lbnRfbWV0aG9kX2lkKSAlPiUNCiAgdGFsbHkoKSAlPiUNCiAgdW5ncm91cCgpICU+JQ0KICBtdXRhdGUocGN0ID0gbi9zdW0obikqMTAwLA0KICAgICAgICAgbGJsID0gc3ByaW50ZigiJS4wZiUlIiwgcGN0KSwNCiAgICAgICAgIGxibCA9IGlmZWxzZShsYmwgPT0gIjAlIiwgIjwxJSIsIGxibCkpDQoNCnRyYW5zX2RmICU+JQ0KIGdncGxvdCgpICsNCiAgYWVzKHggPSBwYXltZW50X21ldGhvZF9pZCkgKw0KICBnZW9tX2JhcihmaWxsID0gIiMyRDU1OUUiKSArDQogIGdlb21fdGV4dChhZXMoeCA9IHBheW1lbnRfbWV0aG9kX2lkLCB5ID0gbiAtIDI1MCwgbGFiZWwgPSBsYmwpLA0KICAgICAgICAgICAgcGx0X2RmICU+JSBmaWx0ZXIocGN0ID4gNCksIGFuZ2xlID0gOTAsIGhqdXN0ID0gMSwgdmp1c3QgPSAwLjUsDQogICAgICAgICAgICBjb2xvciA9ICJ3aGl0ZSIsIGZvbnRmYWNlID0gImJvbGQiKSArDQogIGxhYnModGl0bGUgPSAiUGF5bWVudCBtZXRob2QiLCB5ID0gIiIsIHggPSAiIikgKw0KICBzY2FsZV95X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLCAwKSwgYnJlYWtzID0gc2VxKDAsMTAwMDAwLDIwMDAwKSwNCiAgICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IGMoMCwgcGFzdGUwKHNlcSgyMCwxMDAsMjApLCAiSyIpKSwNCiAgICAgICAgICAgICAgICAgICAgIGxpbWl0cyA9IGMoMCwgMTAwMDAwKSkgKw0KICBnZ3RoZW1lczo6dGhlbWVfdHVmdGUoKSArDQogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiksDQogICAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCksDQogICAgICAgIGF4aXMudGlja3MueCA9IGVsZW1lbnRfYmxhbmsoKSkNCmBgYA0KDQo8YnI+DQoNCi0gICAqKmBwYXltZW50X3BsYW5fZGF5c2AqKiAtIGxlbmd0aCBvZiBtZW1iZXJzaGlwIHBsYW4gaW4gZGF5cy4gTm90IHZlcnkgaW5mb3JtYXRpdmUgdmFyaWFibGUsIGJlY2F1c2UgaW4gbW9zdCBjYXNlcyBpdCBlcXVhbHMgMzAgZGF5cywgaXQgaXMgb2Z0ZW4gcG9zaXRpdmUgZm9yIGNhbmNlbGxhdGlvbnMgYW5kIHdoZW4gc3VtbWVkIHdpdGggdHJhbnNhY3Rpb24gZGF0ZSBkb2VzIG5vdCBnaXZlIG1lbWJlcnNoaXAgZXhwaXJhdGlvbiBkYXRlLiBXZSBkbyBub3QgcGxhbiB0byB1c2UgaXQuDQoNCmBgYHtyIHBheW1lbnRfcGxhbl9kYXlzfQ0KcGx0X2RmIDwtIHRyYW5zX2RmICU+JQ0KICBtdXRhdGUocGF5bWVudF9wbGFuX2RheXMgPSBmY3RfaW5mcmVxKGZhY3RvcihwYXltZW50X3BsYW5fZGF5cykpKSAlPiUNCiAgZ3JvdXBfYnkocGF5bWVudF9wbGFuX2RheXMpICU+JQ0KICB0YWxseSgpICU+JQ0KICB1bmdyb3VwKCkgJT4lDQogIG11dGF0ZShwY3QgPSBuL3N1bShuKSoxMDAsDQogICAgICAgICBsYmwgPSBzcHJpbnRmKCIlLjBmJSUiLCBwY3QpKSAlPiUgDQogIGFycmFuZ2UoLW4pDQoNCnRyYW5zX2RmICU+JQ0KIGdncGxvdCgpICsNCiAgYWVzKHggPSBmY3RfaW5mcmVxKGZhY3RvcihwYXltZW50X3BsYW5fZGF5cykpKSArDQogIGdlb21fYmFyKGZpbGwgPSAiIzJENTU5RSIpICsNCiAgZ2VvbV90ZXh0KGFlcyh4ID0gcGF5bWVudF9wbGFuX2RheXMsIHkgPSBuIC0gMjUwLCBsYWJlbCA9IGxibCksDQogICAgICAgICAgICBwbHRfZGYgJT4lIGZpbHRlcihwY3QgPiA1KSwgYW5nbGUgPSA5MCwgaGp1c3QgPSAxLCB2anVzdCA9IDAuNSwNCiAgICAgICAgICAgIGNvbG9yID0gIndoaXRlIiwgZm9udGZhY2UgPSAiYm9sZCIpICsNCiAgZ2VvbV90ZXh0KGFlcyh4ID0gcGF5bWVudF9wbGFuX2RheXMsIHkgPSBuLCBsYWJlbCA9IGxibCksDQogICAgICAgICAgICBwbHRfZGYgJT4lIGZpbHRlcihwY3QgPiAzICYgcGN0IDwgODApLCBhbmdsZSA9IDkwLCBoanVzdCA9IDAsIHZqdXN0ID0gMC41LA0KICAgICAgICAgICAgY29sb3IgPSAiZ3JleTIwIiwgZm9udGZhY2UgPSAiYm9sZCIpICsNCiAgbGFicyh0aXRsZSA9ICJQYXltZW50IHBsYW4gZGF5cyIsIHkgPSAiIiwgeCA9ICJVbmlxdWUgdmFsdWVzIG9mIHBheW1lbnQgcGxhbiBkYXlzIikgKw0KICBzY2FsZV95X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLCAwKSwgYnJlYWtzID0gc2VxKDAsMTc1MDAwLDI1MDAwKSwNCiAgICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IGMoMCwgcGFzdGUwKHNlcSgyNSwxNzUsMjUpLCAiSyIpKSwNCiAgICAgICAgICAgICAgICAgICAgIGxpbWl0cyA9IGMoMCwgMTc1MDAwKSkgKw0KICBnZ3RoZW1lczo6dGhlbWVfdHVmdGUoKSArDQogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiksDQogICAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCksDQogICAgICAgIGF4aXMudGlja3MueCA9IGVsZW1lbnRfYmxhbmsoKSkNCmBgYA0KDQpgYGB7ciBwYXltZW50X3BsYW5fZGF5c19wbG90LCBmaWcud2lkdGg9OSwgZmlnLmhlaWdodD0zLCBldmFsPVRSVUV9DQpwbHRfZGYgPC0gdHJhbnNfZGYgJT4lDQogIG11dGF0ZShwYXltZW50X3BsYW5fZGF5cyA9IGZjdF9pbmZyZXEoZmFjdG9yKHBheW1lbnRfcGxhbl9kYXlzKSkpICU+JQ0KICBncm91cF9ieShwYXltZW50X3BsYW5fZGF5cykgJT4lDQogIHRhbGx5KCkgJT4lDQogIHVuZ3JvdXAoKSAlPiUNCiAgbXV0YXRlKHBjdCA9IG4vc3VtKG4pKjEwMCwNCiAgICAgICAgIGxibCA9IHNwcmludGYoIiUuMGYlJSIsIHBjdCkpICU+JSANCiAgYXJyYW5nZSgtbikNCg0KdHJhbnNfZGYgJT4lDQogZ2dwbG90KCkgKw0KICBhZXMoeCA9IGZjdF9pbmZyZXEoZmFjdG9yKHBheW1lbnRfcGxhbl9kYXlzKSkpICsNCiAgZ2VvbV9iYXIoZmlsbCA9ICIjMkQ1NTlFIikgKw0KICBnZW9tX3RleHQoYWVzKHggPSBwYXltZW50X3BsYW5fZGF5cywgeSA9IG4gLSAyNTAsIGxhYmVsID0gbGJsKSwNCiAgICAgICAgICAgIHBsdF9kZiAlPiUgZmlsdGVyKHBjdCA+IDUpLCBhbmdsZSA9IDkwLCBoanVzdCA9IDEsIHZqdXN0ID0gMC41LA0KICAgICAgICAgICAgY29sb3IgPSAid2hpdGUiLCBmb250ZmFjZSA9ICJib2xkIikgKw0KICBnZW9tX3RleHQoYWVzKHggPSBwYXltZW50X3BsYW5fZGF5cywgeSA9IG4sIGxhYmVsID0gbGJsKSwNCiAgICAgICAgICAgIHBsdF9kZiAlPiUgZmlsdGVyKHBjdCA+IDMgJiBwY3QgPCA4MCksIGFuZ2xlID0gOTAsIGhqdXN0ID0gMCwgdmp1c3QgPSAwLjUsDQogICAgICAgICAgICBjb2xvciA9ICJncmV5MjAiLCBmb250ZmFjZSA9ICJib2xkIikgKw0KICBsYWJzKHRpdGxlID0gIlBheW1lbnQgcGxhbiBkYXlzIiwgeSA9ICIiLCB4ID0gIlVuaXF1ZSB2YWx1ZXMgb2YgcGF5bWVudCBwbGFuIGRheXMiKSArDQogIHNjYWxlX3lfY29udGludW91cyhleHBhbmQgPSBjKDAsIDApLCBicmVha3MgPSBzZXEoMCwxNzUwMDAsMjUwMDApLA0KICAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gYygwLCBwYXN0ZTAoc2VxKDI1LDE3NSwyNSksICJLIikpLA0KICAgICAgICAgICAgICAgICAgICAgbGltaXRzID0gYygwLCAxNzUwMDApKSArDQogIGdndGhlbWVzOjp0aGVtZV90dWZ0ZSgpICsNCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiKSwNCiAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSwNCiAgICAgICAgYXhpcy50aWNrcy54ID0gZWxlbWVudF9ibGFuaygpKQ0KYGBgDQoNCi0tIEZvciBjYW5jZWxsYXRpb24gdHJhbnNhY3Rpb25zIHRoZXJlIGFyZSBvbmx5IDMgYXZhaWxhYmxlIHZhbHVlcyBvZiBwYXltZW50IHBsYW4gZGF5czogMCwgMzAgYW5kIDMxLg0KDQo8YnI+DQoNCi0gICAqKmBwbGFuX2xpc3RfcHJpY2VgKiogYW5kICoqYGFjdHVhbF9hbW91bnRfcGFpZGAqKiAtIGJvdGggaGF2ZSBgciBsZW5ndGgodW5pcXVlKHRyYW5zX2RmJHBsYW5fbGlzdF9wcmljZSkpYCB1bmlxdWUgdmFsdWVzLiBGb3IgYHIgcm91bmQoc3VtKHRyYW5zX2RmJHBsYW5fbGlzdF9wcmljZSAhPSB0cmFuc19kZiRhY3R1YWxfYW1vdW50X3BhaWQpL25yb3codHJhbnNfZGYpKjEwMCwxKWAlIG9mIGFsbCB0cmFuc2FjdGlvbnMgdGhlc2UgdmFsdWVzIGRpZmZlci4gVGhpcyBzaGFyZSBpcyBhIGJpdCBoaWdoZXIgZm9yIGNhbmNlbGxhdGlvbnM6IGByIHJvdW5kKHN1bSh0cmFuc19kZiRwbGFuX2xpc3RfcHJpY2VbdHJhbnNfZGYkaXNfY2FuY2VsXSAhPSB0cmFuc19kZiRhY3R1YWxfYW1vdW50X3BhaWRbdHJhbnNfZGYkaXNfY2FuY2VsXSkvc3VtKHRyYW5zX2RmJGlzX2NhbmNlbCkqMTAwLDEpYCUgdmVyc3VzIGByIHJvdW5kKHN1bSh0cmFuc19kZiRwbGFuX2xpc3RfcHJpY2VbIXRyYW5zX2RmJGlzX2NhbmNlbF0gIT0gdHJhbnNfZGYkYWN0dWFsX2Ftb3VudF9wYWlkWyF0cmFuc19kZiRpc19jYW5jZWxdKS9zdW0oIXRyYW5zX2RmJGlzX2NhbmNlbCkqMTAwLDEpYCUuDQoNCmBgYHtyIHBsYW5fYWN0dWFsX3ByaWNlfQ0KZ2dwbG90KHRyYW5zX2RmICU+JSBtdXRhdGUoaXNfY2FuY2VsID0gZmFjdG9yKGlzX2NhbmNlbCwgbGFiZWxzID0gYygiaXNfY2FuY2VsPTAiLCAiaXNfY2FuY2VsPTEiKSkpKSArDQogIGFlcyh4ID0gcGxhbl9saXN0X3ByaWNlLCB5ID0gYWN0dWFsX2Ftb3VudF9wYWlkKSArDQogIGdlb21fcG9pbnQoYWVzKGNvbG9yID0gaXNfY2FuY2VsKSwgc2hhcGUgPSAiY2lyY2xlIiwgc2l6ZSA9IDIsIGFscGhhID0gMC41KSArDQogIHNjYWxlX2NvbG9yX2JyZXdlcihwYWxldHRlID0gIlNldDEiLCBkaXJlY3Rpb24gPSAtMSkgKw0KICBsYWJzKHggPSAiUGxhbiBsaXN0IHByaWNlIiwgeSA9ICJBY3R1YWwgYW1vdW50IHBhaWQiKSArDQogIGZhY2V0X3JlcF93cmFwKH4gaXNfY2FuY2VsLCBzY2FsZXMgPSAiZnJlZSIpICsNCiAgdGhlbWVfbWluaW1hbCgpICsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKQ0KYGBgDQoNCmBgYHtyIHBsYW5fYWN0dWFsX3ByaWNlX3Bsb3QsIGZpZy53aWR0aD04LCBmaWcuaGVpZ2h0PTQsIGV2YWw9VFJVRX0NCmdncGxvdCh0cmFuc19kZiAlPiUgbXV0YXRlKGlzX2NhbmNlbCA9IGZhY3Rvcihpc19jYW5jZWwsIGxhYmVscyA9IGMoImlzX2NhbmNlbD0wIiwgImlzX2NhbmNlbD0xIikpKSkgKw0KICBhZXMoeCA9IHBsYW5fbGlzdF9wcmljZSwgeSA9IGFjdHVhbF9hbW91bnRfcGFpZCkgKw0KICBnZW9tX3BvaW50KGFlcyhjb2xvciA9IGlzX2NhbmNlbCksIHNoYXBlID0gImNpcmNsZSIsIHNpemUgPSAyLCBhbHBoYSA9IDAuNSkgKw0KICBzY2FsZV9jb2xvcl9icmV3ZXIocGFsZXR0ZSA9ICJTZXQxIiwgZGlyZWN0aW9uID0gLTEpICsNCiAgbGFicyh4ID0gIlBsYW4gbGlzdCBwcmljZSIsIHkgPSAiQWN0dWFsIGFtb3VudCBwYWlkIikgKw0KICBmYWNldF9yZXBfd3JhcCh+IGlzX2NhbmNlbCwgc2NhbGVzID0gImZyZWUiKSArDQogIHRoZW1lX21pbmltYWwoKSArDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikNCmBgYA0KDQo8YnI+DQoNCiMjIyAqKkNodXJuIGRlZmluaXRpb24qKg0KDQo8YnI+DQoNCldlIHdpbGwgdXNlIGEgZGVmaW5pdGlvbiBvZiBjaHVybiBnaXZlbiBieSBvcmdhbml6ZXJzLiBUaGV5IGRlZmluZSBpdCBhcyBhIG1lbWJlcnNoaXAgZXhwaXJhdGlvbiBzdWNoIHRoYXQgZHVyaW5nIHRoZSBuZXh0IDMwIGRheXMgaXQgaXMgbm90IHJlYWN0aXZhdGVkLiBTbyB0aGUga2V5IHZhcmlhYmxlIGZvciByZXZlYWxpbmcgY2h1cm5zIGlzIGEgcGVyaW9kIGJldHdlZW4gYSBtZW1iZXJzaGlwIGV4cGlyYXRpb24gZGF0ZSBnaXZlbiBmb3IgdGhpcyBwYXJ0aWN1bGFyIHRyYW5zYWN0aW9uIGFuZCB0aGUgZGF0ZSBvZiB0aGUgbmV4dCB0cmFuc2FjdGlvbi4NCg0KT3VyIGFwcHJvYWNoIHRvIGNodXJuIGRlZmluaXRpb24gd2lsbCBiZSBiYXNlZCBvbiB0aGUgZm9sbG93aW5nICoqYXNzdW1wdGlvbnMqKjoNCg0KLSAgIEFzIGFsbCBkYXRhIGZvciB0cmFuc2FjdGlvbnMgd2VyZSBleHRyYWN0ZWQgb24gTWFyY2ggMzEsIDIwMTcsIGZvciBhbGwgdXNlcnMgd2hvIGhhZCB0aGUgbGFzdCB0cmFuc2FjdGlvbiBiZWZvcmUgdGhhdCBkYXkgd2Ugd2lsbCBzdXBwb3NlIHRoYXQgdGhlcmUgd2VyZSBubyBtb3JlIHRyYW5zYWN0aW9ucyB0aWxsIE1hcmNoIDMxLCAyMDE3Lg0KDQotICAgRm9yIGVhY2ggdXNlciB0aGUgZGF0ZSBvZiBoaXMvaGVyIGZpcnN0IGF2YWlsYWJsZSB0cmFuc2FjdGlvbiBpbiB0aGUgZGF0YXNldCB3aWxsIGJlIGNvbnNpZGVyZWQgYXMgdGhlIHN0YXJ0IG9mIGhpcy9oZXIgc3Vic2NyaXB0aW9uIChtZW1iZXJzaGlwKSwgYmVjYXVzZSB3ZSBoYXZlIG5vIGVhcmxpZXIgZGF0YSBmb3IgdHJhbnNhY3Rpb25zIGZvciB0aG9zZSB1c2VycyB3aG8gcmVnaXN0ZXJlZCBhdCBLS0JveCBiZWZvcmUgMjAxNS4gSW4gb3RoZXIgd29yZHMsIGluIHRoZSBhbmFseXNpcyBvZiBjaHVybiB3ZSB3aWxsIGNvbnNpZGVyIGRhdGEgYXMgaWYgdGhpcyBzdWJzY3JpcHRpb24gc2VydmljZSB3YXMgb3BlbmVkIGF0IDIwMTUtMDEtMDEuIEFsdGhvdWdoLCBpbiBmYWN0LCBpdCB3b3VsZCBiZSBjb3JyZWN0IHRvIHNheSB0aGF0IGZvciB1c2VycyB3aG8gcmVnaXN0ZXJlZCBhdCB0aGUgc2VydmljZSBiZWZvcmUgMjAxNSAoYHIgcm91bmQoc3VtKG1lbWJlcnMkcmVnaXN0cmF0aW9uX2luaXRfdGltZSA8IGFzLkRhdGUoIjIwMTUtMDEtMDEiKSkvbnJvdyhtZW1iZXJzKSoxMDAsMSlgJSBvZiBhbGwgdXNlcnMpLCB0aGUgZmlyc3QgdGltZSBwZXJpb2Qgb2YgdGhlaXIgbWVtYmVyc2hpcCBzaG91bGQgYmUgY29uc2lkZXJlZCBhcyBsZWZ0IGNlbnNvcmVkLCBiZWNhdXNlIHdlIGhhdmUgbm8gaW5mb3JtYXRpb24gYWJvdXQgaXQgYWN0dWFsIGRhdGUgb2YgYmVnaW5uaW5nLiBCdXQgc3Vydml2YWwgbW9kZWxzIHdpdGggaW50ZXJ2YWwgKGJvdGggbGVmdCBhbmQgcmlnaHQpIGNlbnNvcmluZyBhcmUgcXVpdGUgY29tcGxpY2F0ZWQsIGVzcGVjaWFsbHkgZm9yIG1hY2hpbmUgbGVhcm5pbmcgKGkuZS4gcHJlZGljdGlvbiksIHRoYXQgaXMgd2h5IGZvciB0aGlzIHBhcnRpY3VsYXIgdGFzayB3ZSBkZWNpZGVkIHRvIGZvbGxvdyB0aGlzIHNpbXBsaWZpY2F0aW9uLg0KDQo8YnI+DQoNClN0YXJ0aW5nIGZyb20gdGhlIGZpcnN0IHRyYW5zYWN0aW9uIGZvciBlYWNoIHVzZXIgd2UgY2FuIGNhbGN1bGF0ZSB0aW1lIGJldHdlZW4gbWVtYmVyc2hpcCBleHBpcmF0aW9uIGRhdGUgaW4gZXZlcnkgdHJhbnNhY3Rpb24gYW5kIHRoZSBuZXh0IHRyYW5zYWN0aW9uIGRhdGUgKG9yIGlmIHRoZXJlIGlzIG5vIGZ1cnRoZXIgdHJhbnNhY3Rpb25zIGZvciB0aGlzIHVzZXIgLSB0aWxsIE1hcmNoIDMxLCAyMDE3KSAtIGB0aW1lX2J3X2V4cF9uZXh0dHJhbnNgLg0KDQotLSBUaGVyZSBhcmUgbmVnYXRpdmUgdmFsdWVzLiBNb3N0IGxpa2VseSwgdGhlc2UgYXJlIGNhc2VzIHdoZW4gdXNlciBjaGFuZ2VzIGhpcy9oZXIgc3Vic2NyaXB0aW9uIHBsYW4gKGluY2x1ZGluZyBjYW5jZWxsYXRpb24sIHdoaWNoIGlzIGFsd2F5cyBtYXJrZWQgYXMgYXV0by1yZW5ld2FscykgYmVmb3JlIHRoZSBleHBpcmF0aW9uIGRhdGUgZ2l2ZW4gaW4gdGhlIHByZXZpb3VzIHN1YnNjcmlwdGlvbi4gVXN1YWxseSBpdCBoYXBwZW5zIGluIGF1dG8tcmVuZXdlZCB0cmFuc2FjdGlvbnMgYW5kIDEtNiBkYXlzIHByaW9yIHRvIGV4cGlyYXRpb24gb2YgdGhlIHByZXZpb3VzIHBsYW4uDQoNCi0tIEZvciB0aGUgbW9zdCBvZiBjb25zZWN1dGl2ZSB0cmFuc2FjdGlvbnMgdGhlIGRhdGUgb2YgZXhwaXJhdGlvbiBmb3IgdGhlIGZpcnN0IG9mIHRoZW0gY29pbmNpZGUgd2l0aCB0cmFuc2FjdG9uIGRhdGUgZm9yIHRoZSBzZWNvbmQgb25lLCBvciB0aGUgbGF0dGVyIGdvZXMgMSBkYXkgcHJpb3Igb3IgbGF0ZXIgdGhhbiB0aGUgZm9ybWVyLg0KDQo8YnI+DQoNCkFjY29yZGluZyB0byB0aGUgdGltZSBiZXR3ZWVuIGV4cGlyYXRpb24gZGF0ZSBhbmQgdGhlIGRhdGUgb2YgdGhlIG5leHQgdHJhbnNhY3Rpb24gKG9yIDIwMTctMDMtMzEpIHdlIHdpbGwgcGxhY2UgYSAqKmNodXJuIGluZGljYXRvciBmb3IgZWFjaCB0cmFuc2FjdGlvbioqIChpLmUuIHdoZXRoZXIgYSB1c2VyIGNodXJuZWQgYWZ0ZXIgdGhlIGVuZCBvZiBtZW1iZXJzaGlwIGV4cGlyYXRpb24gbGlzdGVkIGluIHRoaXMgdHJhbnNhY3Rpb24gb3Igbm90KSBhcyBmb2xsb3dzOg0KDQotIElmIHRoaXMgdGltZSBleGNlZWRzIDMwIGRheXMsIHRoZW4gd2Ugd2lsbCBjb25zaWRlciB0aGlzIHVzZXIgYXMgY2h1cm5lZCBhdCB0aGlzIG1lbWJlcnNoaXAgZXhwaXJhdGlvbiBkYXRlIChgY2h1cm5gID0gMSkuDQoNCi0gSWYgdGhpcyB0aW1lIGlzIDMwIGRheXMgb3IgbGVzcywgdGhlbiB3ZSBjYW4gc2F5IHRoYXQgZHVyaW5nIHRoZSB0aW1lIGZyb20gdGhpcyB0aWxsIHRoZSBuZXh0IHRyYW5zYWN0aW9uIChvciB0aWxsIDIwMTctMDMtMzEgZm9yIHRoZSBsYXN0IHVzZXIncyB0cmFuc2FjdGlvbikgdGhpcyB1c2VyIGRpZCBub3QgY2h1cm4gKGBjaHVybmAgPSAwKS4NCg0KPGJyPg0KDQpTY2hlbWUgZm9yIGxvZ2ljIG9mIGNodXJuIGxhYmVsbGluZzoNCg0KYGBge3IgY2h1cm5fc2NoZW1lLCBmaWcuY2FwPSJDaHVybiBzY2hlbWUiLCBldmFsPVRSVUUsIG91dC53aWR0aD0iMzAwcHgifQ0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoIkQ6L015RG9jdW1lbnRzL0thZ2dsZS9La2JveC9DaHVybl9zY2hlbWVfdjJfMzAuanBnIikNCmBgYA0KDQpgYGB7ciBjaHVybiwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0NCiMgY2h1cm4NCnRyYW5zX2RmIDwtIHRyYW5zX2RmICU+JQ0KICBtdXRhdGUoY2h1cm4gPSBhcy5udW1lcmljKHRpbWVfYndfZXhwX25leHR0cmFucyA+IDMwKSkNCg0Kc3Vic2NyX3BlcmlvZHMgPC0gZnVuY3Rpb24oY2h1cm5fc2VxKSB7DQogIGN1bXN1bShpZmVsc2UoaXMubmEobGFnKGNodXJuX3NlcSkpLCAxLCBsYWcoY2h1cm5fc2VxKSA9PSAxKSkNCn0NCg0KIyBzZXF1ZW5jZXMgb2YgY2h1cm4gaW5kaWNhdG9ycyBieSBtc25vDQp0cmFuc19wZXJpb2RzIDwtIHRyYW5zX2RmICU+JQ0KICBncm91cF9ieShtc25vKSAlPiUgDQogIGdyb3VwX21vZGlmeSh+IHN1YnNjcl9wZXJpb2RzKC54JGNodXJuKSAlPiUNCiAgICAgICAgICAgICAgICAgdGliYmxlOjplbmZyYW1lKG5hbWUgPSAidHJhbnNfaWQiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdmFsdWUgPSAibXNub19zdWJzY3JfaWQiKSkNCg0KIyBqb2luaW5nIGVhY2ggdHJhbnNhY3Rpb24gd2l0aCBpZCBmb3Igc2VxdWVuY2Ugb2YgY2h1cm4gaW5kaWNhdG9ycw0KdHJhbnNfZGYgPC0gdHJhbnNfZGYgJT4lDQogIGxlZnRfam9pbih0cmFuc19wZXJpb2RzLCBieSA9IGMoIm1zbm8iLCAidHJhbnNfaWQiKSkNCg0KdHJhbnNfZGYgPC0gdHJhbnNfZGYgJT4lDQogIG11dGF0ZShzdWJzY3JfaWQgPSBncm91cF9pbmRpY2VzKHRyYW5zX2RmLCBtc25vLCBtc25vX3N1YnNjcl9pZCkpIA0KDQptb2RlIDwtIGZ1bmN0aW9uKHgpew0KICBpZiAoc3VtKGlzLm5hKHgpKSA9PSBsZW5ndGgoeCkpIHsNCiAgICBOQQ0KICB9IGVsc2Ugew0KICAgIHdoaWNoLm1heCh0YWJ1bGF0ZSh4KSkNCiAgfQ0KfQ0KDQojIGNvbWJpbmluZyB0cmFuc2FjdGlvbnMgaW4gb25lIHNlcXVlbmNlIGludG8gcGVyaW9kcw0KdHJhbnNfcGVyaW9kcyA8LSB0cmFuc19kZiAlPiUNCiAgZ3JvdXBfYnkobXNubykgJT4lDQogIG11dGF0ZSh0aW1lX2Zyb21fZmlyc3RfdHJhbnMgPSBhcy5udW1lcmljKHRyYW5zYWN0aW9uX2RhdGUgLSB0cmFuc2FjdGlvbl9kYXRlWzFdLCAnZGF5cycpLA0KICAgICAgICAgdGltZV9mcm9tX3ByZXZfdHJhbnMgPSByZXBsYWNlX25hKGFzLm51bWVyaWModHJhbnNhY3Rpb25fZGF0ZSAtIGxhZyh0cmFuc2FjdGlvbl9kYXRlKSwgJ2RheXMnKSwgMCksDQogICAgICAgICB0aW1lX2Zyb21fcHJldl9leHAgPSByZXBsYWNlX25hKGFzLm51bWVyaWModHJhbnNhY3Rpb25fZGF0ZSAtIGxhZyhtZW1iZXJzaGlwX2V4cGlyZV9kYXRlX2NvciksICdkYXlzJyksIDApKSAlPiUNCiAgdW5ncm91cCgpICU+JQ0KICBncm91cF9ieShzdWJzY3JfaWQpICU+JQ0KICBzdW1tYXJpc2UobXNubyA9IHVuaXF1ZShtc25vKSwNCiAgICAgICAgICAgIG1zbm9fc3Vic2NyX2lkID0gdW5pcXVlKG1zbm9fc3Vic2NyX2lkKSwNCiAgICAgICAgICAgIGNodXJuID0gbWF4KGNodXJuKSwNCiAgICAgICAgICAgIHN0YXJ0X2RhdGUgPSB0cmFuc2FjdGlvbl9kYXRlWzFdLA0KICAgICAgICAgICAgZW5kX2RhdGUgPSBwbWluKGFzLkRhdGUoIjIwMTctMDMtMzEiKSwgbWVtYmVyc2hpcF9leHBpcmVfZGF0ZV9jb3JbbigpXSksDQogICAgICAgICAgICBuX3RyYW5zID0gbigpLA0KICAgICAgICAgICAgbl9jYW5jZWwgPSBzdW0oaXNfY2FuY2VsKSwNCiAgICAgICAgICAgIG51bV9hdXRvX3JlbmV3ID0gc3VtKGlzX2F1dG9fcmVuZXcpLA0KICAgICAgICAgICAgZmlyc3RfcGF5bWVudF9tZXRob2RfaWQgPSBwYXltZW50X21ldGhvZF9pZFsxXSwNCiAgICAgICAgICAgIGZpcnN0X2FjdHVhbF9hbW91bnRfcGFpZCA9IGFjdHVhbF9hbW91bnRfcGFpZFsxXSwNCiAgICAgICAgICAgIGZpcnN0X3BsYW5fbGlzdF9wcmljZSA9IHBsYW5fbGlzdF9wcmljZVsxXSwNCiAgICAgICAgICAgIG1vZGVfcGF5bWVudF9tZXRob2RfaWQgPSBtb2RlKGFzLm51bWVyaWMoYXMuY2hhcmFjdGVyKHBheW1lbnRfbWV0aG9kX2lkKSkpLA0KICAgICAgICAgICAgc3VtX2FjdHVhbF9hbW91bnRfcGFpZCA9IHN1bShhY3R1YWxfYW1vdW50X3BhaWQpLA0KICAgICAgICAgICAgY2h1cm5lZF9iZWZvcmUgPSBhcy5udW1lcmljKG1zbm9fc3Vic2NyX2lkID4gMSksDQogICAgICAgICAgICBjaHVybmVkX2JlZm9yZV9udW0gPSBtc25vX3N1YnNjcl9pZCAtIDEsDQogICAgICAgICAgICB0aW1lX2Zyb21fZmlyc3RfdHJhbnMgPSB0aW1lX2Zyb21fZmlyc3RfdHJhbnNbMV0sDQogICAgICAgICAgICB0aW1lX2Zyb21fcHJldl90cmFucyA9IHRpbWVfZnJvbV9wcmV2X3RyYW5zWzFdLA0KICAgICAgICAgICAgdGltZV9mcm9tX3ByZXZfZXhwID0gdGltZV9mcm9tX3ByZXZfZXhwWzFdKSAlPiUNCiAgdW5ncm91cCgpICU+JQ0KICBtdXRhdGUodGltZSA9IGFzLm51bWVyaWMoZW5kX2RhdGUgLSBzdGFydF9kYXRlLCAnZGF5cycpICsgMSkNCg0KdHJhbnNfcGVyaW9kcyA8LSB0cmFuc19wZXJpb2RzICU+JQ0KICBtdXRhdGUobW9kZV9wYXltZW50X21ldGhvZF9pZCA9IGZjdF9sdW1wX21pbihmYWN0b3IobW9kZV9wYXltZW50X21ldGhvZF9pZCwgYXMubnVtZXJpYyhsZXZlbHModHJhbnNfZGYkcGF5bWVudF9tZXRob2RfaWQpKSksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1pbiA9IDMwLCBvdGhlcl9sZXZlbCA9ICI5OTkiKSwNCiAgICAgICAgIGZpcnN0X3BheW1lbnRfbWV0aG9kX2lkID0gZmN0X2x1bXBfbWluKGZhY3RvcihmaXJzdF9wYXltZW50X21ldGhvZF9pZCwgYXMubnVtZXJpYyhsZXZlbHModHJhbnNfZGYkcGF5bWVudF9tZXRob2RfaWQpKSksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtaW4gPSAzMCwgb3RoZXJfbGV2ZWwgPSAiOTk5IikpDQoNCm1lbWJlcnMgPC0gbWVtYmVycyAlPiUNCiAgbGVmdF9qb2luKHRyYW5zX2RmICU+JQ0KICAgICAgICAgICAgICBncm91cF9ieShtc25vKSAlPiUNCiAgICAgICAgICAgICAgc3VtbWFyaXNlKHRyYW5zYWN0aW9uX2RhdGVfZmlyc3QgPSBtaW4odHJhbnNhY3Rpb25fZGF0ZSksDQogICAgICAgICAgICAgICAgICAgICAgICB0cmFuc2FjdGlvbl9kYXRlX2xhc3QgPSBtYXgodHJhbnNhY3Rpb25fZGF0ZSksDQogICAgICAgICAgICAgICAgICAgICAgICBtZW1iZXJzaGlwX2V4cGlyZV9kYXRlX2xhc3QgPSBtaW4obWVtYmVyc2hpcF9leHBpcmVfZGF0ZV9jb3JbbigpXSwgYXMuRGF0ZSgiMjAxNy0wMy0zMSIpKSwNCiAgICAgICAgICAgICAgICAgICAgICAgIGNodXJuZWRfbnVtID0gc3VtKGNodXJuID09IDEpLA0KICAgICAgICAgICAgICAgICAgICAgICAgY2h1cm5lZF9sYXN0ID0gYXMubnVtZXJpYyhjaHVybltuKCldID09IDEpLA0KICAgICAgICAgICAgICAgICAgICAgICAgY2h1cm5lZF9ldmVyID0gYXMubnVtZXJpYyhjaHVybmVkX251bSA+IDApLA0KICAgICAgICAgICAgICAgICAgICAgICAgdGltZV9vYnMgPSBhcy5udW1lcmljKG1lbWJlcnNoaXBfZXhwaXJlX2RhdGVfbGFzdCAtIHRyYW5zYWN0aW9uX2RhdGVfZmlyc3QsICdkYXlzJyksDQogICAgICAgICAgICAgICAgICAgICAgICBuX3RyYW5zX3RvdCA9IG4oKSksDQogICAgICAgICAgICBieSA9ICJtc25vIikgJT4lDQogIGxlZnRfam9pbih0cmFuc19wZXJpb2RzICU+JSBncm91cF9ieShtc25vKSAlPiUgc3VtbWFyaXNlKG5fc3Vic2NyID0gbigpKSAlPiUgdW5ncm91cCgpLA0KICAgICAgICAgICAgYnkgPSAibXNubyIpICU+JQ0KICBtdXRhdGUodGltZV90b3QgPSBhcy5udW1lcmljKG1lbWJlcnNoaXBfZXhwaXJlX2RhdGVfbGFzdCAtIHJlZ2lzdHJhdGlvbl9pbml0X3RpbWUsICdkYXlzJykgKyAxKQ0KYGBgDQoNCioqT3V0IG9mIGFsbCB0cmFuc2FjdGlvbnMgYHIgcm91bmQoc3VtKHRyYW5zX2RmJGNodXJuID09IDEpL25yb3codHJhbnNfZGYpKjEwMCwxKWAlIHdlcmUgaW5kaWNhdGVkIGFzIGVuZGVkIHdpdGggYSB1c2VyJ3MgY2h1cm4qKi4NCg0KKipPdXQgb2YgYWxsIHVuaXF1ZSB1c2VycyBpbiBvdXIgZGF0YSBzZXQgYHIgcm91bmQoc3VtKG1lbWJlcnMkY2h1cm5lZF9ldmVyID09IDEpL25yb3cobWVtYmVycykqMTAwLDEpYCUgaGFkIGF0IGxlYXN0IG9uZSBjaHVybi4gYHIgcm91bmQoc3VtKG1lbWJlcnMkY2h1cm5lZF9sYXN0ID09IDEpL25yb3cobWVtYmVycykqMTAwLDEpYCUgb2YgYWxsIHVuaXF1ZSB1c2VycyBoYWQgY2h1cm4gYWZ0ZXIgdGhlaXIgbGFzdCB0cmFuc2FjdGlvbiwgYHIgcm91bmQoc3VtKG1lbWJlcnMkY2h1cm5lZF9ldmVyID09IDEgJiBtZW1iZXJzJGNodXJuZWRfbGFzdCA9PSAwKS9ucm93KG1lbWJlcnMpKjEwMCwxKWAgaGFkIGF0IGxlYXN0IG9uZSBjaHVybiBidXQgdGhlbiB0aGV5IHJlc3Vic2NyaWJlZCB0byB0aGUgc2VydmljZS4qKg0KDQo8YnI+DQoNCmBgYHtyIGNodXJuZWRfbnVtfQ0KcGx0X2RmIDwtIG1lbWJlcnMgJT4lDQogIGdyb3VwX2J5KGNodXJuZWRfbnVtKSAlPiUNCiAgdGFsbHkoKSAlPiUNCiAgdW5ncm91cCgpICU+JQ0KICBtdXRhdGUocGN0ID0gbi9zdW0obikqMTAwLA0KICAgICAgICAgbGJsID0gc3ByaW50ZigiJS4wZiUlIiwgcGN0KSwNCiAgICAgICAgIGxibCA9IGlmZWxzZShsYmwgPT0gIjAlIiwgIjwxJSIsIGxibCkpDQoNCnAxIDwtIG1lbWJlcnMgJT4lDQogZ2dwbG90KCkgKw0KICBhZXMoeCA9IGNodXJuZWRfbnVtKSArDQogIGdlb21fYmFyKGZpbGwgPSAiIzJENTU5RSIpICsNCiAgZ2VvbV90ZXh0KGFlcyh4ID0gY2h1cm5lZF9udW0sIHkgPSBuIC0gNTAwLCBsYWJlbCA9IGxibCksIA0KICAgICAgICAgICAgcGx0X2RmICU+JSBmaWx0ZXIocGN0ID4gMi41KSwgDQogICAgICAgICAgICBjb2xvciA9ICJ3aGl0ZSIsIGZvbnRmYWNlID0gImJvbGQiKSArDQogIGdlb21fdGV4dChhZXMoeCA9IGNodXJuZWRfbnVtLCB5ID0gbiArIDUwMCwgbGFiZWwgPSBsYmwpLCANCiAgICAgICAgICAgIHBsdF9kZiAlPiUgZmlsdGVyKHBjdCA8IDIuNSksIA0KICAgICAgICAgICAgY29sb3IgPSAiZ3JleTIwIiwgZm9udGZhY2UgPSAiYm9sZCIpICsNCiAgbGFicyh0aXRsZSA9ICJOdW1iZXIgb2YgY2h1cm5zIGJ5IG9uZSB1c2VyIiwgeSA9ICIiLCB4ID0gIiIpICsNCiAgc2NhbGVfeV9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwgMCksIGJyZWFrcyA9IHNlcSgwLDEwMDAwLDIwMDApLA0KICAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gYygwLCBwYXN0ZTAoc2VxKDIsMTAsMiksICJLIikpLA0KICAgICAgICAgICAgICAgICAgICAgbGltaXRzID0gYygwLCAxMDAwMCkpICsNCiAgc2NhbGVfeF9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwwKSwgYnJlYWtzID0gcGx0X2RmJGNodXJuZWRfbnVtKSArDQogIGdndGhlbWVzOjp0aGVtZV90dWZ0ZSgpICsNCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiKSwNCiAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyKSwNCiAgICAgICAgYXhpcy50aWNrcy54ID0gZWxlbWVudF9ibGFuaygpKQ0KDQpwbHRfZGYgPC0gbWVtYmVycyAlPiUNCiAgZ3JvdXBfYnkobl9zdWJzY3IpICU+JQ0KICB0YWxseSgpICU+JQ0KICB1bmdyb3VwKCkgJT4lDQogIG11dGF0ZShwY3QgPSBuL3N1bShuKSoxMDAsDQogICAgICAgICBsYmwgPSBzcHJpbnRmKCIlLjBmJSUiLCBwY3QpLA0KICAgICAgICAgbGJsID0gaWZlbHNlKGxibCA9PSAiMCUiLCAiPDElIiwgbGJsKSkNCg0KcDIgPC0gbWVtYmVycyAlPiUNCiBnZ3Bsb3QoKSArDQogIGFlcyh4ID0gbl9zdWJzY3IpICsNCiAgZ2VvbV9iYXIoZmlsbCA9ICIjMkQ1NTlFIikgKw0KICBnZW9tX3RleHQoYWVzKHggPSBuX3N1YnNjciwgeSA9IG4gLSA3NTAsIGxhYmVsID0gbGJsKSwgDQogICAgICAgICAgICBwbHRfZGYgJT4lIGZpbHRlcihwY3QgPiAzLjUpLCANCiAgICAgICAgICAgIGNvbG9yID0gIndoaXRlIiwgZm9udGZhY2UgPSAiYm9sZCIpICsNCiAgZ2VvbV90ZXh0KGFlcyh4ID0gbl9zdWJzY3IsIHkgPSBuICsgNzUwLCBsYWJlbCA9IGxibCksIA0KICAgICAgICAgICAgcGx0X2RmICU+JSBmaWx0ZXIocGN0IDw9IDMuNSksIA0KICAgICAgICAgICAgY29sb3IgPSAiZ3JleTIwIiwgZm9udGZhY2UgPSAiYm9sZCIpICsNCiAgbGFicyh0aXRsZSA9ICJOdW1iZXIgb2Ygc3Vic2NyaXB0aW9uIHBlcmlvZHMgYnkgdXNlciIsIHkgPSAiIiwgeCA9ICIiKSArDQogIHNjYWxlX3lfY29udGludW91cyhleHBhbmQgPSBjKDAsIDApLCBicmVha3MgPSBzZXEoMCwxNTAwMCwyNTAwKSwNCiAgICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IGMoMCwgcGFzdGUwKHNlcSgyLjUsMTUsMi41KSwgIksiKSksDQogICAgICAgICAgICAgICAgICAgICBsaW1pdHMgPSBjKDAsIDE1MDAwKSkgKw0KICBzY2FsZV94X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLDApLCBicmVha3MgPSBwbHRfZGYkbl9zdWJzY3IpICsNCiAgZ2d0aGVtZXM6OnRoZW1lX3R1ZnRlKCkgKw0KICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIpLA0KICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIpLA0KICAgICAgICBheGlzLnRpY2tzLnggPSBlbGVtZW50X2JsYW5rKCkpDQoNCmdnYXJyYW5nZShwMiwgcDEsIG5yb3cgPSAxLCBuY29sID0gMikNCmBgYA0KDQpgYGB7ciBjaHVybmVkX251bV9wbG90LCBmaWcud2lkdGg9OCwgZmlnLmhlaWdodD0zLCBldmFsPVRSVUV9DQpwbHRfZGYgPC0gbWVtYmVycyAlPiUNCiAgZ3JvdXBfYnkoY2h1cm5lZF9udW0pICU+JQ0KICB0YWxseSgpICU+JQ0KICB1bmdyb3VwKCkgJT4lDQogIG11dGF0ZShwY3QgPSBuL3N1bShuKSoxMDAsDQogICAgICAgICBsYmwgPSBzcHJpbnRmKCIlLjBmJSUiLCBwY3QpLA0KICAgICAgICAgbGJsID0gaWZlbHNlKGxibCA9PSAiMCUiLCAiPDElIiwgbGJsKSkNCg0KcDEgPC0gbWVtYmVycyAlPiUNCiBnZ3Bsb3QoKSArDQogIGFlcyh4ID0gY2h1cm5lZF9udW0pICsNCiAgZ2VvbV9iYXIoZmlsbCA9ICIjMkQ1NTlFIikgKw0KICBnZW9tX3RleHQoYWVzKHggPSBjaHVybmVkX251bSwgeSA9IG4gLSA1MDAsIGxhYmVsID0gbGJsKSwgDQogICAgICAgICAgICBwbHRfZGYgJT4lIGZpbHRlcihwY3QgPiAyLjUpLCANCiAgICAgICAgICAgIGNvbG9yID0gIndoaXRlIiwgZm9udGZhY2UgPSAiYm9sZCIpICsNCiAgZ2VvbV90ZXh0KGFlcyh4ID0gY2h1cm5lZF9udW0sIHkgPSBuICsgNTAwLCBsYWJlbCA9IGxibCksIA0KICAgICAgICAgICAgcGx0X2RmICU+JSBmaWx0ZXIocGN0IDwgMi41KSwgDQogICAgICAgICAgICBjb2xvciA9ICJncmV5MjAiLCBmb250ZmFjZSA9ICJib2xkIikgKw0KICBsYWJzKHRpdGxlID0gIk51bWJlciBvZiBjaHVybnMgYnkgb25lIHVzZXIiLCB5ID0gIiIsIHggPSAiIikgKw0KICBzY2FsZV95X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLCAwKSwgYnJlYWtzID0gc2VxKDAsMTAwMDAsMjAwMCksDQogICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBjKDAsIHBhc3RlMChzZXEoMiwxMCwyKSwgIksiKSksDQogICAgICAgICAgICAgICAgICAgICBsaW1pdHMgPSBjKDAsIDEwMDAwKSkgKw0KICBzY2FsZV94X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLDApLCBicmVha3MgPSBwbHRfZGYkY2h1cm5lZF9udW0pICsNCiAgZ2d0aGVtZXM6OnRoZW1lX3R1ZnRlKCkgKw0KICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIpLA0KICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIpLA0KICAgICAgICBheGlzLnRpY2tzLnggPSBlbGVtZW50X2JsYW5rKCkpDQoNCnBsdF9kZiA8LSBtZW1iZXJzICU+JQ0KICBncm91cF9ieShuX3N1YnNjcikgJT4lDQogIHRhbGx5KCkgJT4lDQogIHVuZ3JvdXAoKSAlPiUNCiAgbXV0YXRlKHBjdCA9IG4vc3VtKG4pKjEwMCwNCiAgICAgICAgIGxibCA9IHNwcmludGYoIiUuMGYlJSIsIHBjdCksDQogICAgICAgICBsYmwgPSBpZmVsc2UobGJsID09ICIwJSIsICI8MSUiLCBsYmwpKQ0KDQpwMiA8LSBtZW1iZXJzICU+JQ0KIGdncGxvdCgpICsNCiAgYWVzKHggPSBuX3N1YnNjcikgKw0KICBnZW9tX2JhcihmaWxsID0gIiMyRDU1OUUiKSArDQogIGdlb21fdGV4dChhZXMoeCA9IG5fc3Vic2NyLCB5ID0gbiAtIDc1MCwgbGFiZWwgPSBsYmwpLCANCiAgICAgICAgICAgIHBsdF9kZiAlPiUgZmlsdGVyKHBjdCA+IDMuNSksIA0KICAgICAgICAgICAgY29sb3IgPSAid2hpdGUiLCBmb250ZmFjZSA9ICJib2xkIikgKw0KICBnZW9tX3RleHQoYWVzKHggPSBuX3N1YnNjciwgeSA9IG4gKyA3NTAsIGxhYmVsID0gbGJsKSwgDQogICAgICAgICAgICBwbHRfZGYgJT4lIGZpbHRlcihwY3QgPD0gMy41KSwgDQogICAgICAgICAgICBjb2xvciA9ICJncmV5MjAiLCBmb250ZmFjZSA9ICJib2xkIikgKw0KICBsYWJzKHRpdGxlID0gIk51bWJlciBvZiBzdWJzY3JpcHRpb24gcGVyaW9kcyBieSB1c2VyIiwgeSA9ICIiLCB4ID0gIiIpICsNCiAgc2NhbGVfeV9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwgMCksIGJyZWFrcyA9IHNlcSgwLDE1MDAwLDI1MDApLA0KICAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gYygwLCBwYXN0ZTAoc2VxKDIuNSwxNSwyLjUpLCAiSyIpKSwNCiAgICAgICAgICAgICAgICAgICAgIGxpbWl0cyA9IGMoMCwgMTUwMDApKSArDQogIHNjYWxlX3hfY29udGludW91cyhleHBhbmQgPSBjKDAsMCksIGJyZWFrcyA9IHBsdF9kZiRuX3N1YnNjcikgKw0KICBnZ3RoZW1lczo6dGhlbWVfdHVmdGUoKSArDQogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiksDQogICAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiksDQogICAgICAgIGF4aXMudGlja3MueCA9IGVsZW1lbnRfYmxhbmsoKSkNCg0KZ2dhcnJhbmdlKHAyLCBwMSwgbnJvdyA9IDEsIG5jb2wgPSAyKQ0KYGBgDQoNCjxicj4NCg0KVGhlcmVmb3JlLCBmb3IgYWxsIHRyYW5zYWN0aW9ucyBvZiBlYWNoIHVzZXIgd2Ugb2J0YWluZWQgdGhlIHNlcXVlbmNlIG9mIGNodXJuIGluZGljYXRvcnMgKDAgb3IgMSkuIFRoZW4gd2Ugd2lsbCBjb21iaW5lIHRoZW0gaW50byAqKnBlcmlvZHMgb2YgdW5pbnRlcnJ1cHRlZCBtZW1iZXJzaGlwKiosIGkuZS4gcGVyaW9kcyBpbnNpZGUgb2Ygd2hpY2ggdGhlcmUgd2VyZSBubyBjaHVybnMgKHNlZSB0aGUgU2NoZW1lIGFib3ZlKToNCg0KLSAgIElmIGNodXJuIGlzIHplcm8gZm9yIHNldmVyYWwgY29uc2VjdXRpdmUgdHJhbnNhY3Rpb25zIG9mIG9uZSB1c2VyLCB0aGVuIHdlIGNhbiBjb21iaW5lIHRoZSBjb3JyZXNwb25kaW5nIG1lbWJlcnNoaXAgaW50ZXJ2YWxzIGludG8gb25lLCBpbmNsdWRpbmcgdGhlIGxhc3QgcGVyaW9kIGluIHRoaXMgc2VxdWVuY2UgaWYgaXQgaGFzIGEgY2h1cm4gc3RhdHVzIG9mIDEuIElmIHRoZSBzZXF1ZW5jZSBlbmRzIHdpdGggemVybyBjaHVybiAoaXQgaXMgcG9zc2libGUgb25seSB3aGVuIHRoZSBsYXN0IHVzZXIncyB0cmFuc2FjdGlvbiBoYXMgemVybyBjaHVybiBzdGF0dXMpIHRoZW4gb3ZlcmFsbCBjaHVybiBmb3IgY29tYmluZWQgaW50ZXJ2YWxzIHdpbGwgYmUgMCwgYW5kIHRpbWUgd2lsbCBiZSBhIHBlcmlvZCBmcm9tIHRoZSBkYXRlIG9mIHRoZSBmaXJzdCB0cmFuc2FjdGlvbiBpbiB0aGlzIHNlcXVlbmNlIHRpbGwgdGhlIG1pbmltdW0gb2YgMjAxNy0wMy0zMSBhbmQgbWVtYmVyc2hpcCBleHBpcmF0aW9uIGRhdGUgaW4gdGhlIGxhc3QgdHJhbnNhY3Rpb24uDQoNCi0gICBJZiB0aGUgc2VxdWVuY2Ugb2YgemVyb2VzIGVuZHMgd2l0aCAxIG9yIHNlcXVlbmNlIGNvbnNpc3RzIG9mIG9ubHkgb25lIHRyYW5zYWN0aW9uIHdpdGggcG9zaXRpdmUgY2h1cm4gc3RhdHVzICh3aGljaCBpcyBwb3NzaWJsZSBpZiB0aGlzIGlzIHRoZSBmaXJzdCB1c2VyJ3MgdHJhbnNhY3Rpb24gb3IgYSB0cmFuc2FjdGlvbiBwcmVjZWRlZCBieSBhbm90aGVyIG9uZSB3aXRoIGNodXJuID0gMSksIHRoZW4gb3ZlcmFsbCBjaHVybiBmb3IgdGhlIHNlcXVlbmNlIHdpbGwgYmUgMSwgYW5kIHRpbWUgdG8gY2h1cm4gd2lsbCBiZSBhIHBlcmlvZCBmcm9tIHRoZSBkYXRlIG9mIHRoZSBmaXJzdCB0cmFuc2FjdGlvbiBpbiB0aGUgc2VxdWVuY2UgdGlsbCB0aGUgbWVtYmVyc2hpcCBleHBpcmF0aW9uIGRhdGUgZm9yIHRoZSBsYXN0IHRyYW5zYWN0aW9uIGluIHRoZSBzZXF1ZW5jZS4NCg0KSW4gZnVydGhlciBhbmFseXNpcyB3ZSB3aWxsIGNvbnNpZGVyIHRoaXMgcGVyaW9kcyBvZiBtZW1iZXJzaGlwIGJ5IG9uZSB1c2VyIGFzIGluZGVwZW5kZW50IGZyb20gZWFjaCBvdGhlciwgc28gYSB1c2VyIHdobyByZXR1cm5lZCB0byB0aGUgc2VydmljZSBhZnRlciB0aGUgY2h1cm4gd2lsbCBiZSBjb25zaWRlcmVkIGFzIGEgbmV3IGNsaWVudCwgZXhjZXB0IHRoZSBmYWN0IHRoYXQgd2Ugd2lsbCBrbm93IGFsbCBpbmZvcm1hdGlvbiBhYm91dCBoaXMgcHJldmlvdXMgY2h1cm5zLCB0cmFuc2FjdGlvbnMgYW5kIG90aGVyIGJlaGF2aW91ci4gU3VjaCBhcHByb2FjaCBpcyBqdXN0aWZpZWQgYnkgdGhlIGRlZmluaXRpb24gb2YgY2h1cm4gdXNlZCBpbiBLS0JveC4NCg0KVGh1cywgaW5zdGVhZCBvZiBgciBucm93KG1lbWJlcnMpYCB1bmlxdWUgdXNlcnMgKip3ZSB3aWxsIHVzZSBkYXRhIG9uIGByIG5yb3codHJhbnNfcGVyaW9kcylgIHBlcmlvZHMgb2Ygc3Vic2NyaXB0aW9uLiBPdXQgb2YgdGhlbSBgciBzdW0odHJhbnNfcGVyaW9kcyRjaHVybiA9PSAxKWAgKGByIHJvdW5kKHN1bSh0cmFuc19wZXJpb2RzJGNodXJuID09IDEpL25yb3codHJhbnNfcGVyaW9kcykqMTAwLDEpYCUpIGVuZGVkIHdpdGggYSBjaHVybiwgdGhlIHJlc3QgYXJlIGNlbnNvcmVkKiouDQoNCjxicj4NCg0KIyMjICoqRGVzY3JpcHRpdmUgc3Vydml2YWwgYW5hbHlzaXMqKg0KDQo8YnI+DQoNCkthcGxhbi1NZWllciBzdXJ2aXZhbCBjdXJ2ZSBhbmQgc3BsaW5lLWVzdGltYXRlZCBoYXphcmQgY3VydmUgZm9yIEtLQm94J3MgdXNlcnMgYXJlIHByZXNlbnRlZCBiZWxvdzoNCg0KYGBge3Igc3VydnBsb3R9DQpzcnZmaXQgPC0gc3VydmZpdChTdXJ2KHRpbWUsIGNodXJuKSB+IDEsIHRyYW5zX3BlcmlvZHMpDQptZWRfc3VydiA8LSBzdXJ2X21lZGlhbihzcnZmaXQpDQpzcnZoYXogPC0gYnNoYXphcmQoU3Vydih0aW1lLCBjaHVybikgfiAxLCB0cmFuc19wZXJpb2RzLCB2ZXJib3NlID0gRkFMU0UpDQoNCnAxIDwtIGdnc3VydnBsb3Qoc3J2Zml0LCBzaXplID0gMS4yLCBwYWxldHRlID0gIiMwMDY2NjYiLCBjb25mLmludCA9IEZBTFNFLA0KICAgICAgICAgICBnZ3RoZW1lID0gdGhlbWVfbWluaW1hbCgpICsgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiKSksDQogICAgICAgICAgIHRpdGxlID0gIlByb2JhYmlsaXR5IG9mIGFjdGl2ZSBtZW1iZXJzaGlwICg9IHN1cnZpdmFsIHByb2JhYmlsaXR5KSIsDQogICAgICAgICAgIHhsYWIgPSAiVGltZSBmcm9tIHRoZSBmaXJzdCB0cmFuc2FjdGlvbiwgbW9udGhzIiwNCiAgICAgICAgICAgeWxhYiA9ICJQcm9iYWJpbGl0eSBvZiBhY3RpdmUgbWVtYmVyc2hpcCIsDQogICAgICAgICAgIGxlZ2VuZCA9ICJub25lIiwgY2Vuc29yID0gRkFMU0UpDQoNCnAxIDwtIHAxJHBsb3QgKw0KICBzY2FsZV94X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLCAwKSwgYnJlYWtzID0gc2VxKDAsIDgyMCwgMzAuNDM3NSoyKSwNCiAgICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IHNlcSgwLCA4MjAvMzAuNDM3NSwgMiksDQogICAgICAgICAgICAgICAgICAgICBsaW1pdHMgPSBjKDAsIDgyMCkpICsNCiAgc2NhbGVfeV9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwgMCksIGJyZWFrcyA9IHNlcSgwLDEsMC4yKSwNCiAgICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IHNjYWxlczo6cGVyY2VudF9mb3JtYXQoYWNjdXJhY3kgPSAxKSkgKw0KICBnZW9tX3RleHQoeCA9IDgwMCwgeSA9IDAuOSwgDQogICAgICAgICAgICBsYWJlbCA9IHNwcmludGYoIk1lZGlhbiBtZW1iZXJzaGlwIHRpbWVcbig9IG1lZGlhbiBzdXJ2aXZhbCB0aW1lKTpcbiUuZCBkYXlzICglLjBmIG1vbnRocykiLCBtZWRfc3VydiRtZWRpYW4sIG1lZF9zdXJ2JG1lZGlhbi8zMC40Mzc1KSwNCiAgICAgICAgICAgIGhqdXN0ID0gMSwgdmp1c3QgPSAxKSArDQogIHRoZW1lX2NsYXNzaWMoKSArDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIiwNCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiKSwNCiAgICAgICAgcGFuZWwuZ3JpZC5tYWpvciA9IGVsZW1lbnRfbGluZShzaXplID0gLjIsIGNvbG91ciA9ICJncmV5OTAiKSwNCiAgICAgICAgcGFuZWwuZ3JpZC5taW5vciA9IGVsZW1lbnRfbGluZShzaXplID0gLjIsIGNvbG91ciA9ICJncmV5OTAiKSkNCg0KcDIgPC0gZ2dwbG90KCkgKw0KICBnZW9tX2xpbmUoYWVzKHggPSBzcnZoYXokdGltZSwgeSA9IHNydmhheiRoYXphcmQpLCBjb2xvciA9ICIjRDgyNjMyIiwgc2l6ZSA9IDEuMikgKw0KICBzY2FsZV94X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLCAwKSwgYnJlYWtzID0gc2VxKDAsIDgyMCwgMzAuNDM3NSoyKSwNCiAgICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IHNlcSgwLCA4MjAvMzAuNDM3NSwgMiksIGxpbWl0cyA9IGMoMCw4MjApKSArDQogIHNjYWxlX3lfY29udGludW91cyhleHBhbmQgPSBjKDAsMCksIGxpbWl0cyA9IGMoMCwgMC4wMTI1KSkgKw0KICBsYWJzKHggPSAiVGltZSBmcm9tIHRoZSBmaXJzdCB0cmFuc2FjdGlvbiwgbW9udGhzIiwgeSA9ICJIYXphcmQgcmF0ZSIsIA0KICAgICAgIHRpdGxlID0gIkNodXJuIGhhemFyZCByYXRlIikgKw0KICB0aGVtZV9jbGFzc2ljKCkgKw0KICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIpLA0KICAgICAgICBwYW5lbC5ncmlkLm1ham9yID0gZWxlbWVudF9saW5lKHNpemUgPSAuMiwgY29sb3VyID0gImdyZXk5MCIpLA0KICAgICAgICBwYW5lbC5ncmlkLm1pbm9yID0gZWxlbWVudF9saW5lKHNpemUgPSAuMiwgY29sb3VyID0gImdyZXk5MCIpKQ0KDQpnZ2FycmFuZ2UocDEsIHAyLCBucm93ID0gMiwgbmNvbCA9IDEpDQpgYGANCg0KYGBge3IgcGxvdF8wMDksIGV2YWw9VFJVRX0NCmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKCJEOi9NeURvY3VtZW50cy9LYWdnbGUvS2tib3gvcGxvdHMvcGxvdF8wMDkucG5nIikNCmBgYA0KDQotLSBBcyB3ZSBjYW4gc2VlLCB0aGUgaGF6YXJkIGJlaGF2aW91ciBpcyBjbG9zZXIgdG8gdGhlIHR5cGUgb2YgZWFybHkgY2h1cm4gd2hlbiB0aGUgY2h1cm4gcmF0ZSBpcyBoaWdoIGFuZCBzaGFycGx5IGZhbGxpbmcgZm9yIHRoZSBmaXJzdCBcfiAyLTMgbW9udGhzIGFmdGVyIHN1YnNjcmlwdGlvbiBpbml0aWF0aW9uLCB0aGVuIGl0IHN0YWJpbGl6ZXMgZm9yIHNvbWUgdGltZSwgaGF2ZSBhIGxpdHRsZSBwZWFrIGJldHdlZW4gMTIgYW5kIDE0IG1vbnRocywgYW5kIHRoZW4gZ3JhZHVhbGx5IGRlY3JlYXNpbmcuDQoNCi0tIFRoZXJlIGlzIGEgbm90aWNhYmxlIGRyb3AgaW4gYWN0aXZlIG1lbWJlcnNoaXAgYXQgMSBtb250aCBhZnRlciBzdWJzY3JpcHRpb24gLSBpdCBjb3VsZCBiZSBiZWNhdXNlIG9mIHVzZXJzIHdobyBzdWJzY3JpYmUganVzdCB0byB0cnkgYSBzZXJ2aWNlIG9yIG1heWJlIHRoZXJlIGFyZSBzb21lIHRyaWFsIHN1YnNjcmlwdGlvbiBwbGFucyBhdCBLS0JveC4NCg0KPGJyPg0KDQpgYGB7ciBzdXJ2cGxvdF9udW1fY2h1cm5zfQ0KdHJhbnNfcGVyaW9kcyA8LSB0cmFuc19wZXJpb2RzICU+JQ0KICBtdXRhdGUoY2h1cm5lZF9iZWZvcmVfbnVtRyA9IGZhY3RvcihjaHVybmVkX2JlZm9yZV9udW0sIDA6NiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gYygwOjMsICI0LTYiLCAiNC02IiwgIjQtNiIpKSkNCg0Kc3J2Zml0IDwtIHN1cnZmaXQoU3Vydih0aW1lLCBjaHVybikgfiBjaHVybmVkX2JlZm9yZV9udW1HLCB0cmFuc19wZXJpb2RzKQ0KICAgICAgICAgICAgICAgICAgDQpwMSA8LSBnZ3N1cnZwbG90KHNydmZpdCwgDQogICAgICAgICAgICAgICAgIHBhbGV0dGUgPSBwYWxldHRlZXJfZCgiZ2dzY2k6OmxpZ2h0X2JsdWVfbWF0ZXJpYWwiKVtzZXEoMiwxMCwyKV0sIA0KICAgICAgICAgICAgICAgICBzaXplID0gMS4yLCBjb25mLmludCA9IEZBTFNFLCANCiAgICAgICAgICAgICAgICAgbGVnZW5kLmxhYnMgPSBsZXZlbHModHJhbnNfcGVyaW9kcyRjaHVybmVkX2JlZm9yZV9udW1HKSwNCiAgICAgICAgICAgICAgICAgbGVnZW5kLnRpdGxlID0gIiIsDQogICAgICAgICAgICAgICAgIGdndGhlbWUgPSB0aGVtZV9taW5pbWFsKCkgKyB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIpKSwNCiAgICAgICAgICAgICAgICAgdGl0bGUgPSAiUHJvYmFiaWxpdHkgb2YgYWN0aXZlIG1lbWJlcnNoaXAgYnlcbm51bWJlciBvZiBwcmV2aW91cyBjaHVybnMiLA0KICAgICAgICAgICAgICAgICB4bGFiID0gIlRpbWUgZnJvbSB0aGUgZmlyc3QgdHJhbnNhY3Rpb24sIG1vbnRocyIsDQogICAgICAgICAgICAgICAgIHlsYWIgPSAiUHJvYmFiaWxpdHkgb2YgYWN0aXZlIG1lbWJlcnNoaXAiLA0KICAgICAgICAgICAgICAgICBsZWdlbmQgPSAiYm90dG9tIiwgY2Vuc29yID0gRkFMU0UpDQoNCnAxIDwtIHAxJHBsb3QgKw0KICBzY2FsZV94X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLCAwKSwgYnJlYWtzID0gc2VxKDAsIDgyMCwgMzAuNDM3NSoyKSwNCiAgICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IHNlcSgwLCA4MjAvMzAuNDM3NSwgMiksDQogICAgICAgICAgICAgICAgICAgICBsaW1pdHMgPSBjKDAsIDgyMCkpICsNCiAgc2NhbGVfeV9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwgMCksIGJyZWFrcyA9IHNlcSgwLDEsMC4yKSwNCiAgICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IHNjYWxlczo6cGVyY2VudF9mb3JtYXQoYWNjdXJhY3kgPSAxKSkgKw0KICB0aGVtZV9jbGFzc2ljKCkgKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSBjKDAuOSwwLjgpLA0KICAgICAgICBsZWdlbmQuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwNCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiKSwNCiAgICAgICAgcGFuZWwuZ3JpZC5tYWpvciA9IGVsZW1lbnRfbGluZShzaXplID0gLjIsIGNvbG91ciA9ICJncmV5OTAiKSwNCiAgICAgICAgcGFuZWwuZ3JpZC5taW5vciA9IGVsZW1lbnRfbGluZShzaXplID0gLjIsIGNvbG91ciA9ICJncmV5OTAiKSkNCg0KcGx0X2RmIDwtIE5VTEwNCg0KZm9yIChsdmwgaW4gbGV2ZWxzKHRyYW5zX3BlcmlvZHMkY2h1cm5lZF9iZWZvcmVfbnVtRykpIHsNCiAgc3J2aGF6IDwtIGJzaGF6YXJkKFN1cnYodGltZSwgY2h1cm4pIH4gMSwgDQogICAgICAgICAgICAgICAgICAgICB0cmFuc19wZXJpb2RzICU+JSBmaWx0ZXIoY2h1cm5lZF9iZWZvcmVfbnVtRyA9PSBsdmwpLCANCiAgICAgICAgICAgICAgICAgICAgIHZlcmJvc2UgPSBGQUxTRSkNCiAgcGx0X2RmIDwtIHJiaW5kKHBsdF9kZiwNCiAgICAgICAgICAgICAgICAgIHRpYmJsZSh4ID0gc3J2aGF6JHRpbWUsDQogICAgICAgICAgICAgICAgICAgICAgICAgeSA9IHNydmhheiRoYXphcmQsDQogICAgICAgICAgICAgICAgICAgICAgICAgbHZsID0gbHZsKSkNCn0NCg0KcDIgPC0gZ2dwbG90KCkgKw0KICBnZW9tX2xpbmUoYWVzKHggPSB4LCB5ID0geSwgY29sb3IgPSBsdmwpLCBwbHRfZGYsIHNpemUgPSAxLjIpICsNCiAgc2NhbGVfeF9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwgMCksIGJyZWFrcyA9IHNlcSgwLCA4MjAsIDMwLjQzNzUqMiksDQogICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBzZXEoMCwgODIwLzMwLjQzNzUsIDIpLCBsaW1pdHMgPSBjKDAsODIwKSkgKw0KICBzY2FsZV95X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLDApLCBsaW1pdHMgPSBjKDAsIDAuMDEyNSkpICsNCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IHBhbGV0dGVlcl9kKCJnZ3NjaTo6bGlnaHRfYmx1ZV9tYXRlcmlhbCIpW3NlcSgyLDEwLDIpXSkgKw0KICBsYWJzKHggPSAiVGltZSBmcm9tIHRoZSBmaXJzdCB0cmFuc2FjdGlvbiwgbW9udGhzIiwgeSA9ICJIYXphcmQgcmF0ZSIsIA0KICAgICAgIHRpdGxlID0gIkNodXJuIGhhemFyZCByYXRlIGJ5XG5udW1iZXIgb2YgcHJldmlvdXMgY2h1cm5zIiwgY29sb3IgPSAiIikgKw0KICB0aGVtZV9jbGFzc2ljKCkgKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSBjKDAuOSwwLjgpLA0KICAgICAgICBsZWdlbmQuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwNCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiKSwNCiAgICAgICAgcGFuZWwuZ3JpZC5tYWpvciA9IGVsZW1lbnRfbGluZShzaXplID0gLjIsIGNvbG91ciA9ICJncmV5OTAiKSwNCiAgICAgICAgcGFuZWwuZ3JpZC5taW5vciA9IGVsZW1lbnRfbGluZShzaXplID0gLjIsIGNvbG91ciA9ICJncmV5OTAiKSkNCg0KZ2dhcnJhbmdlKHAxLCBwMiwgbnJvdyA9IDIsIG5jb2wgPSAxKQ0KYGBgDQoNCmBgYHtyIHN1cnZwbG90X251bV9jaHVybnNfcGxvdCwgZmlnLmhlaWdodD04LCBmaWcud2lkdGg9NiwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgZXZhbD1UUlVFfQ0KdHJhbnNfcGVyaW9kcyA8LSB0cmFuc19wZXJpb2RzICU+JQ0KICBtdXRhdGUoY2h1cm5lZF9iZWZvcmVfbnVtRyA9IGZhY3RvcihjaHVybmVkX2JlZm9yZV9udW0sIDA6NiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gYygwOjMsICI0LTYiLCAiNC02IiwgIjQtNiIpKSkNCg0Kc3J2Zml0IDwtIHN1cnZmaXQoU3Vydih0aW1lLCBjaHVybikgfiBjaHVybmVkX2JlZm9yZV9udW1HLCB0cmFuc19wZXJpb2RzKQ0KICAgICAgICAgICAgICAgICAgDQpwMSA8LSBnZ3N1cnZwbG90KHNydmZpdCwgDQogICAgICAgICAgICAgICAgIHBhbGV0dGUgPSBwYWxldHRlZXJfZCgiZ2dzY2k6OmxpZ2h0X2JsdWVfbWF0ZXJpYWwiKVtzZXEoMiwxMCwyKV0sIA0KICAgICAgICAgICAgICAgICBzaXplID0gMS4yLCBjb25mLmludCA9IEZBTFNFLCANCiAgICAgICAgICAgICAgICAgbGVnZW5kLmxhYnMgPSBsZXZlbHModHJhbnNfcGVyaW9kcyRjaHVybmVkX2JlZm9yZV9udW1HKSwNCiAgICAgICAgICAgICAgICAgbGVnZW5kLnRpdGxlID0gIiIsDQogICAgICAgICAgICAgICAgIGdndGhlbWUgPSB0aGVtZV9taW5pbWFsKCkgKyB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIpKSwNCiAgICAgICAgICAgICAgICAgdGl0bGUgPSAiUHJvYmFiaWxpdHkgb2YgYWN0aXZlIG1lbWJlcnNoaXAgYnlcbm51bWJlciBvZiBwcmV2aW91cyBjaHVybnMiLA0KICAgICAgICAgICAgICAgICB4bGFiID0gIlRpbWUgZnJvbSB0aGUgZmlyc3QgdHJhbnNhY3Rpb24sIG1vbnRocyIsDQogICAgICAgICAgICAgICAgIHlsYWIgPSAiUHJvYmFiaWxpdHkgb2YgYWN0aXZlIG1lbWJlcnNoaXAiLA0KICAgICAgICAgICAgICAgICBsZWdlbmQgPSAiYm90dG9tIiwgY2Vuc29yID0gRkFMU0UpDQoNCnAxIDwtIHAxJHBsb3QgKw0KICBzY2FsZV94X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLCAwKSwgYnJlYWtzID0gc2VxKDAsIDgyMCwgMzAuNDM3NSoyKSwNCiAgICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IHNlcSgwLCA4MjAvMzAuNDM3NSwgMiksDQogICAgICAgICAgICAgICAgICAgICBsaW1pdHMgPSBjKDAsIDgyMCkpICsNCiAgc2NhbGVfeV9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwgMCksIGJyZWFrcyA9IHNlcSgwLDEsMC4yKSwNCiAgICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IHNjYWxlczo6cGVyY2VudF9mb3JtYXQoYWNjdXJhY3kgPSAxKSkgKw0KICB0aGVtZV9jbGFzc2ljKCkgKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSBjKDAuOSwwLjgpLA0KICAgICAgICBsZWdlbmQuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwNCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiKSwNCiAgICAgICAgcGFuZWwuZ3JpZC5tYWpvciA9IGVsZW1lbnRfbGluZShzaXplID0gLjIsIGNvbG91ciA9ICJncmV5OTAiKSwNCiAgICAgICAgcGFuZWwuZ3JpZC5taW5vciA9IGVsZW1lbnRfbGluZShzaXplID0gLjIsIGNvbG91ciA9ICJncmV5OTAiKSkNCg0KcGx0X2RmIDwtIE5VTEwNCg0KZm9yIChsdmwgaW4gbGV2ZWxzKHRyYW5zX3BlcmlvZHMkY2h1cm5lZF9iZWZvcmVfbnVtRykpIHsNCiAgc3J2aGF6IDwtIGJzaGF6YXJkKFN1cnYodGltZSwgY2h1cm4pIH4gMSwgDQogICAgICAgICAgICAgICAgICAgICB0cmFuc19wZXJpb2RzICU+JSBmaWx0ZXIoY2h1cm5lZF9iZWZvcmVfbnVtRyA9PSBsdmwpLCANCiAgICAgICAgICAgICAgICAgICAgIHZlcmJvc2UgPSBGQUxTRSkNCiAgcGx0X2RmIDwtIHJiaW5kKHBsdF9kZiwNCiAgICAgICAgICAgICAgICAgIHRpYmJsZSh4ID0gc3J2aGF6JHRpbWUsDQogICAgICAgICAgICAgICAgICAgICAgICAgeSA9IHNydmhheiRoYXphcmQsDQogICAgICAgICAgICAgICAgICAgICAgICAgbHZsID0gbHZsKSkNCn0NCg0KcDIgPC0gZ2dwbG90KCkgKw0KICBnZW9tX2xpbmUoYWVzKHggPSB4LCB5ID0geSwgY29sb3IgPSBsdmwpLCBwbHRfZGYsIHNpemUgPSAxLjIpICsNCiAgc2NhbGVfeF9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwgMCksIGJyZWFrcyA9IHNlcSgwLCA4MjAsIDMwLjQzNzUqMiksDQogICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBzZXEoMCwgODIwLzMwLjQzNzUsIDIpLCBsaW1pdHMgPSBjKDAsODIwKSkgKw0KICBzY2FsZV95X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLDApLCBsaW1pdHMgPSBjKDAsIDAuMDEyNSkpICsNCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IHBhbGV0dGVlcl9kKCJnZ3NjaTo6bGlnaHRfYmx1ZV9tYXRlcmlhbCIpW3NlcSgyLDEwLDIpXSkgKw0KICBsYWJzKHggPSAiVGltZSBmcm9tIHRoZSBmaXJzdCB0cmFuc2FjdGlvbiwgbW9udGhzIiwgeSA9ICJIYXphcmQgcmF0ZSIsIA0KICAgICAgIHRpdGxlID0gIkNodXJuIGhhemFyZCByYXRlIGJ5XG5udW1iZXIgb2YgcHJldmlvdXMgY2h1cm5zIiwgY29sb3IgPSAiIikgKw0KICB0aGVtZV9jbGFzc2ljKCkgKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSBjKDAuOSwwLjgpLA0KICAgICAgICBsZWdlbmQuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwNCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiKSwNCiAgICAgICAgcGFuZWwuZ3JpZC5tYWpvciA9IGVsZW1lbnRfbGluZShzaXplID0gLjIsIGNvbG91ciA9ICJncmV5OTAiKSwNCiAgICAgICAgcGFuZWwuZ3JpZC5taW5vciA9IGVsZW1lbnRfbGluZShzaXplID0gLjIsIGNvbG91ciA9ICJncmV5OTAiKSkNCg0KZ2dhcnJhbmdlKHAxLCBwMiwgbnJvdyA9IDIsIG5jb2wgPSAxKQ0KYGBgDQoNCi0tIFRoZSBvZGQgaW5jcmVhc2UgaW4gaGF6YXJkIHJhdGUgYmV0d2VlbiAxMiBhbmQgMTQgbW9udGhzIGlzIG1haW5seSBwZWN1bGlhciBmb3IgdXNlcnMgd2l0aG91dCBwcmV2aW91cyBjaHVybnMuIE1heWJlLCBpdCBpcyBzb21laG93IGV4cGxhaW5lZCBieSB0aGUgY2hhcmFjdGVyaXN0aWNzIG9mIHRoZWlyIHN1YnNjcmlwdGlvbiBwbGFucy4NCg0KLS0gSGF6YXJkIHJhdGUgZm9yIHVzZXJzIHdpdGggaGlnaGVyIG51bWJlciBvZiBjaHVybnMgZmFsbHMgbW9yZSBncmFkdWFsbHkgaW4gdGhlIGJlZ2lubmluZyBvZiBzdWJzY3JpcHRpb24gcGVyaW9kLCBidXQgZm9yIGFsbCBncm91cHMgd2UgaGF2ZSBhIGh1Z2Ugc2x1bXAgaW4gYWN0aXZlIG1lbWJlcnNoaXAgYXQgMSBtb250aHMgYWZ0ZXIgc3Vic2NyaXB0aW9uLCBhbmQgdGhpcyBzbHVtcCBpcyBtb3JlIGNvbnNpZGVyYWJsZSB3aXRoIHVzZXJzIHdpdGggbW9yZSBwcmV2b3VzIGNodXJucy4NCg0KPGJyPg0KDQpgYGB7ciB5ZWFyX3BlcmlvZH0NCnRyYW5zX3BlcmlvZHMgPC0gdHJhbnNfcGVyaW9kcyAlPiUNCiAgbXV0YXRlKHllYXJfcGVyaW9kID0gZmFjdG9yKHllYXIoc3RhcnRfZGF0ZSkpLA0KICAgICAgICAgbW9udGhfcGVyaW9kID0gZmFjdG9yKG1vbnRoKHN0YXJ0X2RhdGUpLCBsYWJlbHMgPSBtb250aC5hYmIpKQ0KDQpwbHRfZGYgPC0gdHJhbnNfcGVyaW9kcyAlPiUNCiAgZ3JvdXBfYnkoeWVhcl9wZXJpb2QpICU+JQ0KICB0YWxseSgpICU+JQ0KICB1bmdyb3VwKCkgJT4lDQogIG11dGF0ZShwY3QgPSBuL3N1bShuKSoxMDAsDQogICAgICAgICBsYmwgPSBzcHJpbnRmKCIlLjFmJSUiLCBwY3QpKQ0KDQp0cmFuc19wZXJpb2RzICU+JQ0KIGdncGxvdCgpICsNCiAgYWVzKHggPSB5ZWFyX3BlcmlvZCkgKw0KICBnZW9tX2JhcihhZXMoZmlsbCA9IHllYXJfcGVyaW9kKSkgKw0KICBnZW9tX3RleHQoYWVzKHggPSB5ZWFyX3BlcmlvZCwgeSA9IG4gLSA1MDAsIGxhYmVsID0gbGJsKSwgDQogICAgICAgICAgICBwbHRfZGYgJT4lIGZpbHRlcih5ZWFyX3BlcmlvZCAhPSAiMjAxNSIpLCANCiAgICAgICAgICAgIGNvbG9yID0gIndoaXRlIiwgZm9udGZhY2UgPSAiYm9sZCIpICsNCiAgZ2VvbV90ZXh0KGFlcyh4ID0geWVhcl9wZXJpb2QsIHkgPSBuIC0gNTAwLCBsYWJlbCA9IGxibCksIA0KICAgICAgICAgICAgcGx0X2RmICU+JSBmaWx0ZXIoeWVhcl9wZXJpb2QgPT0gIjIwMTUiKSwgDQogICAgICAgICAgICBjb2xvciA9ICJncmV5MjAiLCBmb250ZmFjZSA9ICJib2xkIikgKw0KICBsYWJzKHRpdGxlID0gIlllYXIgb2Ygc3Vic2NyaXB0aW9uIiwgeSA9ICIiLCB4ID0gIiIpICsNCiAgc2NhbGVfeV9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwgMCksIGJyZWFrcyA9IHNlcSgwLCAxNDAwMCwgMjAwMCksDQogICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBjKDAsIHBhc3RlMChzZXEoMiwxNCwyKSwgIksiKSksDQogICAgICAgICAgICAgICAgICAgICBsaW1pdHMgPSBjKDAsIDE0MDAwKSkgKw0KICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBwYWxldHRlZXJfYygiZ2d0aGVtZXM6OkJsdWUiLCAzKSkgKw0KICBnZ3RoZW1lczo6dGhlbWVfdHVmdGUoKSArDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIiwNCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiKSwNCiAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyKSwNCiAgICAgICAgYXhpcy50aWNrcy54ID0gZWxlbWVudF9ibGFuaygpKQ0KYGBgDQoNCmBgYHtyIHllYXJfcGVyaW9kX3Bsb3QsIGZpZy53aWR0aD00LCBmaWcuaGVpZ2h0PTMsIGV2YWw9VFJVRX0NCnRyYW5zX3BlcmlvZHMgPC0gdHJhbnNfcGVyaW9kcyAlPiUNCiAgbXV0YXRlKHllYXJfcGVyaW9kID0gZmFjdG9yKHllYXIoc3RhcnRfZGF0ZSkpLA0KICAgICAgICAgbW9udGhfcGVyaW9kID0gZmFjdG9yKG1vbnRoKHN0YXJ0X2RhdGUpLCBsYWJlbHMgPSBtb250aC5hYmIpKQ0KDQpwbHRfZGYgPC0gdHJhbnNfcGVyaW9kcyAlPiUNCiAgZ3JvdXBfYnkoeWVhcl9wZXJpb2QpICU+JQ0KICB0YWxseSgpICU+JQ0KICB1bmdyb3VwKCkgJT4lDQogIG11dGF0ZShwY3QgPSBuL3N1bShuKSoxMDAsDQogICAgICAgICBsYmwgPSBzcHJpbnRmKCIlLjFmJSUiLCBwY3QpKQ0KDQp0cmFuc19wZXJpb2RzICU+JQ0KIGdncGxvdCgpICsNCiAgYWVzKHggPSB5ZWFyX3BlcmlvZCkgKw0KICBnZW9tX2JhcihhZXMoZmlsbCA9IHllYXJfcGVyaW9kKSkgKw0KICBnZW9tX3RleHQoYWVzKHggPSB5ZWFyX3BlcmlvZCwgeSA9IG4gLSA1MDAsIGxhYmVsID0gbGJsKSwgDQogICAgICAgICAgICBwbHRfZGYgJT4lIGZpbHRlcih5ZWFyX3BlcmlvZCAhPSAiMjAxNSIpLCANCiAgICAgICAgICAgIGNvbG9yID0gIndoaXRlIiwgZm9udGZhY2UgPSAiYm9sZCIpICsNCiAgZ2VvbV90ZXh0KGFlcyh4ID0geWVhcl9wZXJpb2QsIHkgPSBuIC0gNTAwLCBsYWJlbCA9IGxibCksIA0KICAgICAgICAgICAgcGx0X2RmICU+JSBmaWx0ZXIoeWVhcl9wZXJpb2QgPT0gIjIwMTUiKSwgDQogICAgICAgICAgICBjb2xvciA9ICJncmV5MjAiLCBmb250ZmFjZSA9ICJib2xkIikgKw0KICBsYWJzKHRpdGxlID0gIlllYXIgb2Ygc3Vic2NyaXB0aW9uIiwgeSA9ICIiLCB4ID0gIiIpICsNCiAgc2NhbGVfeV9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwgMCksIGJyZWFrcyA9IHNlcSgwLCAxNDAwMCwgMjAwMCksDQogICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBjKDAsIHBhc3RlMChzZXEoMiwxNCwyKSwgIksiKSksDQogICAgICAgICAgICAgICAgICAgICBsaW1pdHMgPSBjKDAsIDE0MDAwKSkgKw0KICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBwYWxldHRlZXJfYygiZ2d0aGVtZXM6OkJsdWUiLCAzKSkgKw0KICBnZ3RoZW1lczo6dGhlbWVfdHVmdGUoKSArDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIiwNCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiKSwNCiAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyKSwNCiAgICAgICAgYXhpcy50aWNrcy54ID0gZWxlbWVudF9ibGFuaygpKQ0KYGBgDQoNCmBgYHtyIHN1cnZwbG90X3llYXJfcGVyaW9kfQ0Kc3J2Zml0IDwtIHN1cnZmaXQoU3Vydih0aW1lLCBjaHVybikgfiB5ZWFyX3BlcmlvZCwgdHJhbnNfcGVyaW9kcykNCiAgICAgICAgICAgICAgICAgIA0KcDEgPC0gZ2dzdXJ2cGxvdChzcnZmaXQsIA0KICAgICAgICAgICAgICAgICBwYWxldHRlID0gcGFsZXR0ZWVyX2MoImdndGhlbWVzOjpCbHVlIiwgMyksIA0KICAgICAgICAgICAgICAgICBzaXplID0gMS4yLCBjb25mLmludCA9IEZBTFNFLA0KICAgICAgICAgICAgICAgICBsZWdlbmQubGFicyA9IGxldmVscyh0cmFuc19wZXJpb2RzJHllYXJfcGVyaW9kKSwNCiAgICAgICAgICAgICAgICAgbGVnZW5kLnRpdGxlID0gIiIsDQogICAgICAgICAgICAgICAgIGdndGhlbWUgPSB0aGVtZV9taW5pbWFsKCkgKyB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIpKSwNCiAgICAgICAgICAgICAgICAgdGl0bGUgPSAiUHJvYmFiaWxpdHkgb2YgYWN0aXZlIG1lbWJlcnNoaXAgYnlcbnllYXIgb2Ygc3Vic2NyaXB0aW9uIiwNCiAgICAgICAgICAgICAgICAgeGxhYiA9ICJUaW1lIGZyb20gdGhlIGZpcnN0IHRyYW5zYWN0aW9uLCBtb250aHMiLA0KICAgICAgICAgICAgICAgICB5bGFiID0gIlByb2JhYmlsaXR5IG9mIGFjdGl2ZSBtZW1iZXJzaGlwIiwNCiAgICAgICAgICAgICAgICAgbGVnZW5kID0gImJvdHRvbSIsIGNlbnNvciA9IEZBTFNFKQ0KDQpwMSA8LSBwMSRwbG90ICsNCiAgc2NhbGVfeF9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwgMCksIGJyZWFrcyA9IHNlcSgwLCA4MjAsIDMwLjQzNzUqMiksDQogICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBzZXEoMCwgODIwLzMwLjQzNzUsIDIpLA0KICAgICAgICAgICAgICAgICAgICAgbGltaXRzID0gYygwLCA4MjApKSArDQogIHNjYWxlX3lfY29udGludW91cyhleHBhbmQgPSBjKDAsIDApLCBicmVha3MgPSBzZXEoMCwxLDAuMiksDQogICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBzY2FsZXM6OnBlcmNlbnRfZm9ybWF0KGFjY3VyYWN5ID0gMSkpICsNCiAgdGhlbWVfY2xhc3NpYygpICsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gYygwLjksIDAuOCksDQogICAgICAgIGxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSwNCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiKSwNCiAgICAgICAgcGFuZWwuZ3JpZC5tYWpvciA9IGVsZW1lbnRfbGluZShzaXplID0gLjIsIGNvbG91ciA9ICJncmV5OTAiKSwNCiAgICAgICAgcGFuZWwuZ3JpZC5taW5vciA9IGVsZW1lbnRfbGluZShzaXplID0gLjIsIGNvbG91ciA9ICJncmV5OTAiKSkNCg0KcDENCmBgYA0KDQpgYGB7ciBzdXJ2cGxvdF95ZWFyX3BlcmlvZF9wbG90LCBmaWcuaGVpZ2h0PTQsIGZpZy53aWR0aD02LCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBldmFsPVRSVUV9DQpzcnZmaXQgPC0gc3VydmZpdChTdXJ2KHRpbWUsIGNodXJuKSB+IHllYXJfcGVyaW9kLCB0cmFuc19wZXJpb2RzKQ0KICAgICAgICAgICAgICAgICAgDQpwMSA8LSBnZ3N1cnZwbG90KHNydmZpdCwgDQogICAgICAgICAgICAgICAgIHBhbGV0dGUgPSBwYWxldHRlZXJfYygiZ2d0aGVtZXM6OkJsdWUiLCAzKSwgDQogICAgICAgICAgICAgICAgIHNpemUgPSAxLjIsIGNvbmYuaW50ID0gRkFMU0UsDQogICAgICAgICAgICAgICAgIGxlZ2VuZC5sYWJzID0gbGV2ZWxzKHRyYW5zX3BlcmlvZHMkeWVhcl9wZXJpb2QpLA0KICAgICAgICAgICAgICAgICBsZWdlbmQudGl0bGUgPSAiIiwNCiAgICAgICAgICAgICAgICAgZ2d0aGVtZSA9IHRoZW1lX21pbmltYWwoKSArIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIikpLA0KICAgICAgICAgICAgICAgICB0aXRsZSA9ICJQcm9iYWJpbGl0eSBvZiBhY3RpdmUgbWVtYmVyc2hpcCBieVxueWVhciBvZiBzdWJzY3JpcHRpb24iLA0KICAgICAgICAgICAgICAgICB4bGFiID0gIlRpbWUgZnJvbSB0aGUgZmlyc3QgdHJhbnNhY3Rpb24sIG1vbnRocyIsDQogICAgICAgICAgICAgICAgIHlsYWIgPSAiUHJvYmFiaWxpdHkgb2YgYWN0aXZlIG1lbWJlcnNoaXAiLA0KICAgICAgICAgICAgICAgICBsZWdlbmQgPSAiYm90dG9tIiwgY2Vuc29yID0gRkFMU0UpDQoNCnAxIDwtIHAxJHBsb3QgKw0KICBzY2FsZV94X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLCAwKSwgYnJlYWtzID0gc2VxKDAsIDgyMCwgMzAuNDM3NSoyKSwNCiAgICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IHNlcSgwLCA4MjAvMzAuNDM3NSwgMiksDQogICAgICAgICAgICAgICAgICAgICBsaW1pdHMgPSBjKDAsIDgyMCkpICsNCiAgc2NhbGVfeV9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwgMCksIGJyZWFrcyA9IHNlcSgwLDEsMC4yKSwNCiAgICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IHNjYWxlczo6cGVyY2VudF9mb3JtYXQoYWNjdXJhY3kgPSAxKSkgKw0KICB0aGVtZV9jbGFzc2ljKCkgKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSBjKDAuOSwgMC44KSwNCiAgICAgICAgbGVnZW5kLnRpdGxlID0gZWxlbWVudF9ibGFuaygpLA0KICAgICAgICBsZWdlbmQuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwNCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiKSwNCiAgICAgICAgcGFuZWwuZ3JpZC5tYWpvciA9IGVsZW1lbnRfbGluZShzaXplID0gLjIsIGNvbG91ciA9ICJncmV5OTAiKSwNCiAgICAgICAgcGFuZWwuZ3JpZC5taW5vciA9IGVsZW1lbnRfbGluZShzaXplID0gLjIsIGNvbG91ciA9ICJncmV5OTAiKSkNCg0KcDENCmBgYA0KDQotLSBUaGUgbW9yZSBjb25zaWRlcmFibGUgYXR0cml0aW9uIGR1cmluZyB0aGUgZmlzciBtb250aCBvZiBzdWJzY3JpcHRpb24gaXMgZGV0ZWN0ZWQgZm9yIHN1YnNjcmlwdGlvbnMgc3RhcnRlZCBpbiAyMDE2LCB0aGUgbGVhc3Qgb25lIC0gZm9yIDIwMTcgKGFsdGhvdWdoIGluIHRoZSBsYXR0ZXIgY2FzZSB0aGVyZSB3ZXJlIGNlbnNvcmVkIG9ic2VydmF0aW9ucyBhdCAzMCBhbmQgMzEgZGF5cyBhZnRlciB0aGUgc3RhcnQgb2Ygc3Vic2NyaXB0aW9uIC0gaWYgd2Uga25ldyB0aGVpciByZWFsIHN0YXR1cyAob2JzZXJ2ZWQgdGhlbSBmb3IgbG9uZ2VyIHBlcmlvZCkgd2UgY291bGQgb2J0YWluIGEgZGlmZmVyZW50IGZvcm0gb2Ygc3Vydml2YWwgY3VydmUgZm9yIHRoaXMgZ3JvdXAgb2YgbWVtYmVyc2hpcCBwZXJpb2RzLg0KDQo8YnI+DQoNCmBgYHtyIG1vbnRoX3BlcmlvZH0NCnBsdF9kZiA8LSB0cmFuc19wZXJpb2RzICU+JQ0KICBncm91cF9ieShtb250aF9wZXJpb2QpICU+JQ0KICB0YWxseSgpICU+JQ0KICB1bmdyb3VwKCkgJT4lDQogIG11dGF0ZShwY3QgPSBuL3N1bShuKSoxMDAsDQogICAgICAgICBsYmwgPSBzcHJpbnRmKCIlLjFmJSUiLCBwY3QpKQ0KDQp0cmFuc19wZXJpb2RzICU+JQ0KIGdncGxvdCgpICsNCiAgYWVzKHggPSBtb250aF9wZXJpb2QpICsNCiAgZ2VvbV9iYXIoYWVzKGZpbGwgPSBtb250aF9wZXJpb2QpKSArDQogIGdlb21fdGV4dChhZXMoeCA9IG1vbnRoX3BlcmlvZCwgeSA9IG4gLSAyNTAsIGxhYmVsID0gbGJsKSwgDQogICAgICAgICAgICBwbHRfZGYsIA0KICAgICAgICAgICAgY29sb3IgPSAid2hpdGUiLCBmb250ZmFjZSA9ICJib2xkIikgKw0KICBsYWJzKHRpdGxlID0gIk1vbnRoIG9mIG1lbWJlcnNoaXAgc3RhcnQiLCB5ID0gIiIsIHggPSAiIikgKw0KICBzY2FsZV95X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLCAwKSwgYnJlYWtzID0gc2VxKDAsIDYwMDAsIDIwMDApLA0KICAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gYygwLCBwYXN0ZTAoc2VxKDIsNiwyKSwgIksiKSksDQogICAgICAgICAgICAgICAgICAgICBsaW1pdHMgPSBjKDAsIDYzNTApKSArDQogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IHBhbGV0dGVlcl9jKCJnckRldmljZXM6OlNldCAyIiwgMTIpKSArDQogIGdndGhlbWVzOjp0aGVtZV90dWZ0ZSgpICsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiLA0KICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIpLA0KICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIpLA0KICAgICAgICBheGlzLnRpY2tzLnggPSBlbGVtZW50X2JsYW5rKCkpDQpgYGANCg0KYGBge3IgbW9udGhfcGVyaW9kX3Bsb3QsIGZpZy53aWR0aD04LCBmaWcuaGVpZ2h0PTMsIGV2YWw9VFJVRX0NCnBsdF9kZiA8LSB0cmFuc19wZXJpb2RzICU+JQ0KICBncm91cF9ieShtb250aF9wZXJpb2QpICU+JQ0KICB0YWxseSgpICU+JQ0KICB1bmdyb3VwKCkgJT4lDQogIG11dGF0ZShwY3QgPSBuL3N1bShuKSoxMDAsDQogICAgICAgICBsYmwgPSBzcHJpbnRmKCIlLjFmJSUiLCBwY3QpKQ0KDQp0cmFuc19wZXJpb2RzICU+JQ0KIGdncGxvdCgpICsNCiAgYWVzKHggPSBtb250aF9wZXJpb2QpICsNCiAgZ2VvbV9iYXIoYWVzKGZpbGwgPSBtb250aF9wZXJpb2QpKSArDQogIGdlb21fdGV4dChhZXMoeCA9IG1vbnRoX3BlcmlvZCwgeSA9IG4gLSAyNTAsIGxhYmVsID0gbGJsKSwgDQogICAgICAgICAgICBwbHRfZGYsIA0KICAgICAgICAgICAgY29sb3IgPSAid2hpdGUiLCBmb250ZmFjZSA9ICJib2xkIikgKw0KICBsYWJzKHRpdGxlID0gIk1vbnRoIG9mIG1lbWJlcnNoaXAgc3RhcnQiLCB5ID0gIiIsIHggPSAiIikgKw0KICBzY2FsZV95X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLCAwKSwgYnJlYWtzID0gc2VxKDAsIDYwMDAsIDIwMDApLA0KICAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gYygwLCBwYXN0ZTAoc2VxKDIsNiwyKSwgIksiKSksDQogICAgICAgICAgICAgICAgICAgICBsaW1pdHMgPSBjKDAsIDYzNTApKSArDQogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IHBhbGV0dGVlcl9jKCJnckRldmljZXM6OlNldCAyIiwgMTIpKSArDQogIGdndGhlbWVzOjp0aGVtZV90dWZ0ZSgpICsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiLA0KICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIpLA0KICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIpLA0KICAgICAgICBheGlzLnRpY2tzLnggPSBlbGVtZW50X2JsYW5rKCkpDQpgYGANCg0KLS0gSGlnaCBwcmV2YWxlbmNlIG9mIHN1YnNjcmlwdGlvbiBzdGFydGVkIGluIEphbnVhcnkgY2FuIGJlIHBhcnRpYWxseSBhIHJlc3VsdCBvZiB0aGUgZmFjdCB0aGF0IHRyYW5zYWN0aW9uIGRhdGFzZXQgc3RhcnRzIGZyb20gSmFudWFyeSwgMjAxNSwgc28gZm9yIHVzZXJzIHdobyByZWdpc3RlcmVkIGF0IHRoZSBzZXJ2aWNlIGJlZm9yZSB0aGF0IGFuZCBoYXZlIHJlZ3VsYXIgdHJhbnNhY3Rpb25zLCB0aGVpciBmaXJzdCBvbmUgaW4gdGhlIGRhdGEgd2lsbCBiZSBpbiB0aGlzIG1vbnRoLg0KDQpgYGB7ciBzdXJ2cGxvdF9wZXJpb2RfbW9udGh9DQpzcnZmaXQgPC0gc3VydmZpdChTdXJ2KHRpbWUsIGNodXJuKSB+IG1vbnRoX3BlcmlvZCwgdHJhbnNfcGVyaW9kcykNCiAgICAgICAgICAgICAgICAgIA0KcDEgPC0gZ2dzdXJ2cGxvdChzcnZmaXQsIA0KICAgICAgICAgICAgICAgICBwYWxldHRlID0gcGFsZXR0ZWVyX2MoImdyRGV2aWNlczo6U2V0IDIiLCAxMiksIA0KICAgICAgICAgICAgICAgICBzaXplID0gMS4yLCBjb25mLmludCA9IEZBTFNFLA0KICAgICAgICAgICAgICAgICBsZWdlbmQubGFicyA9IGxldmVscyh0cmFuc19wZXJpb2RzJG1vbnRoX3BlcmlvZCksDQogICAgICAgICAgICAgICAgIGxlZ2VuZC50aXRsZSA9ICIiLA0KICAgICAgICAgICAgICAgICBnZ3RoZW1lID0gdGhlbWVfbWluaW1hbCgpICsgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiKSksDQogICAgICAgICAgICAgICAgIHRpdGxlID0gIlByb2JhYmlsaXR5IG9mIGFjdGl2ZSBtZW1iZXJzaGlwIGJ5XG5tb250aCBvZiBzdWJzY3JpcHRpb24iLA0KICAgICAgICAgICAgICAgICB4bGFiID0gIlRpbWUgZnJvbSB0aGUgZmlyc3QgdHJhbnNhY3Rpb24sIG1vbnRocyIsDQogICAgICAgICAgICAgICAgIHlsYWIgPSAiUHJvYmFiaWxpdHkgb2YgYWN0aXZlIG1lbWJlcnNoaXAiLA0KICAgICAgICAgICAgICAgICBsZWdlbmQgPSAiYm90dG9tIiwgY2Vuc29yID0gRkFMU0UpDQoNCnAxIDwtIHAxJHBsb3QgKw0KICBzY2FsZV94X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLCAwKSwgYnJlYWtzID0gc2VxKDAsIDgyMCwgMzAuNDM3NSoyKSwNCiAgICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IHNlcSgwLCA4MjAvMzAuNDM3NSwgMiksDQogICAgICAgICAgICAgICAgICAgICBsaW1pdHMgPSBjKDAsIDgyMCkpICsNCiAgc2NhbGVfeV9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwgMCksIGJyZWFrcyA9IHNlcSgwLDEsMC4yKSwNCiAgICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IHNjYWxlczo6cGVyY2VudF9mb3JtYXQoYWNjdXJhY3kgPSAxKSkgKw0KICBndWlkZXMoY29sb3IgPSBndWlkZV9sZWdlbmQobnJvdyA9IDYpKSArDQogIHRoZW1lX2NsYXNzaWMoKSArDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9IGMoMC45LCAwLjgpLA0KICAgICAgICBsZWdlbmQudGl0bGUgPSBlbGVtZW50X2JsYW5rKCksDQogICAgICAgIGxlZ2VuZC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLA0KICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIpLA0KICAgICAgICBwYW5lbC5ncmlkLm1ham9yID0gZWxlbWVudF9saW5lKHNpemUgPSAuMiwgY29sb3VyID0gImdyZXk5MCIpLA0KICAgICAgICBwYW5lbC5ncmlkLm1pbm9yID0gZWxlbWVudF9saW5lKHNpemUgPSAuMiwgY29sb3VyID0gImdyZXk5MCIpKQ0KDQpwMQ0KYGBgDQoNCmBgYHtyIHN1cnZwbG90X3BlcmlvZF9tb250aF9wbG90LCBmaWcuaGVpZ2h0PTQsIGZpZy53aWR0aD02LCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBldmFsPVRSVUV9DQpzcnZmaXQgPC0gc3VydmZpdChTdXJ2KHRpbWUsIGNodXJuKSB+IG1vbnRoX3BlcmlvZCwgdHJhbnNfcGVyaW9kcykNCiAgICAgICAgICAgICAgICAgIA0KcDEgPC0gZ2dzdXJ2cGxvdChzcnZmaXQsIA0KICAgICAgICAgICAgICAgICBwYWxldHRlID0gcGFsZXR0ZWVyX2MoImdyRGV2aWNlczo6U2V0IDIiLCAxMiksIA0KICAgICAgICAgICAgICAgICBzaXplID0gMS4yLCBjb25mLmludCA9IEZBTFNFLA0KICAgICAgICAgICAgICAgICBsZWdlbmQubGFicyA9IGxldmVscyh0cmFuc19wZXJpb2RzJG1vbnRoX3BlcmlvZCksDQogICAgICAgICAgICAgICAgIGxlZ2VuZC50aXRsZSA9ICIiLA0KICAgICAgICAgICAgICAgICBnZ3RoZW1lID0gdGhlbWVfbWluaW1hbCgpICsgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiKSksDQogICAgICAgICAgICAgICAgIHRpdGxlID0gIlByb2JhYmlsaXR5IG9mIGFjdGl2ZSBtZW1iZXJzaGlwIGJ5XG5tb250aCBvZiBzdWJzY3JpcHRpb24iLA0KICAgICAgICAgICAgICAgICB4bGFiID0gIlRpbWUgZnJvbSB0aGUgZmlyc3QgdHJhbnNhY3Rpb24sIG1vbnRocyIsDQogICAgICAgICAgICAgICAgIHlsYWIgPSAiUHJvYmFiaWxpdHkgb2YgYWN0aXZlIG1lbWJlcnNoaXAiLA0KICAgICAgICAgICAgICAgICBsZWdlbmQgPSAiYm90dG9tIiwgY2Vuc29yID0gRkFMU0UpDQoNCnAxIDwtIHAxJHBsb3QgKw0KICBzY2FsZV94X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLCAwKSwgYnJlYWtzID0gc2VxKDAsIDgyMCwgMzAuNDM3NSoyKSwNCiAgICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IHNlcSgwLCA4MjAvMzAuNDM3NSwgMiksDQogICAgICAgICAgICAgICAgICAgICBsaW1pdHMgPSBjKDAsIDgyMCkpICsNCiAgc2NhbGVfeV9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwgMCksIGJyZWFrcyA9IHNlcSgwLDEsMC4yKSwNCiAgICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IHNjYWxlczo6cGVyY2VudF9mb3JtYXQoYWNjdXJhY3kgPSAxKSkgKw0KICBndWlkZXMoY29sb3IgPSBndWlkZV9sZWdlbmQobnJvdyA9IDYpKSArDQogIHRoZW1lX2NsYXNzaWMoKSArDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9IGMoMC45LCAwLjgpLA0KICAgICAgICBsZWdlbmQudGl0bGUgPSBlbGVtZW50X2JsYW5rKCksDQogICAgICAgIGxlZ2VuZC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLA0KICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIpLA0KICAgICAgICBwYW5lbC5ncmlkLm1ham9yID0gZWxlbWVudF9saW5lKHNpemUgPSAuMiwgY29sb3VyID0gImdyZXk5MCIpLA0KICAgICAgICBwYW5lbC5ncmlkLm1pbm9yID0gZWxlbWVudF9saW5lKHNpemUgPSAuMiwgY29sb3VyID0gImdyZXk5MCIpKQ0KDQpwMQ0KYGBgDQoNCi0tIFN1YnNjcmlwdGlvbnMgc3RhcnRlZCBpbiBOb3ZlbWJlciBhbmQgSnVuZSAob25lIG9mIHRoZSBtb3N0IHBvcHVsYXIgbW9udGhzIGZvciBzdGFydGluZyBhIHN1YnNjcmlwdGlvbiwgYWZ0ZXIgSmFudWFyeSkgaGF2ZSBhIGhpZ2hlciByaXNrIG9mIGJlaW5nIGludGVycnVwdGVkIGR1cmluZyB0aGUgZmlyc3QgbW9udGgsIHRob3NlIHN0YXJ0ZWQgaW4gSmFudWFyeSBhbmQgSnVseSAtIHRoZSBsZXNzIG9uZS4NCg0KPGJyPg0KDQojIyAqKk1lbWJlcnMnIGNoYXJhY3RlcmlzdGljcyoqDQoNCjxicj4NCg0KTWVtYmVycyBmaWxlIGNvbnRhaW5zIHRoZSBmb2xsb3dpbmcgaW5mb3JtYXRpb246DQoNCjxicj4NCg0KLSAgICoqYGNpdHlgKiogLSB0aGVyZSBpcyBgciBsZW5ndGgodW5pcXVlKG1lbWJlcnMkY2l0eSkpYCB1bmlxdWUgbnVtZXJpYyBjb2RlcyBmb3IgY2l0aWVzLg0KDQpgYGB7ciBjaXR5fQ0KcGx0X2RmIDwtIG1lbWJlcnMgJT4lDQogIGdyb3VwX2J5KGNpdHkpICU+JQ0KICB0YWxseSgpICU+JQ0KICB1bmdyb3VwKCkgJT4lDQogIG11dGF0ZShwY3QgPSBuL3N1bShuKSoxMDAsDQogICAgICAgICBsYmwgPSBzcHJpbnRmKCIlLjBmJSUiLCBwY3QpLA0KICAgICAgICAgbGJsID0gaWZlbHNlKGxibCA9PSAiMCUiLCAiPDElIiwgbGJsKSkNCg0KbWVtYmVycyAlPiUNCiBnZ3Bsb3QoKSArDQogIGFlcyh4ID0gY2l0eSkgKw0KICBnZW9tX2JhcihhZXMoZmlsbCA9IGNpdHkpKSArDQogIGdlb21fdGV4dChhZXMoeCA9IGNpdHksIHkgPSBuIC0gMjUwLCBsYWJlbCA9IGxibCksIA0KICAgICAgICAgICAgcGx0X2RmICU+JSBmaWx0ZXIocGN0ID4gMiksIA0KICAgICAgICAgICAgY29sb3IgPSAid2hpdGUiLCBmb250ZmFjZSA9ICJib2xkIikgKw0KICBnZW9tX3RleHQoYWVzKHggPSBjaXR5LCB5ID0gbiArIDMwMCwgbGFiZWwgPSBsYmwpLCANCiAgICAgICAgICAgIHBsdF9kZiAlPiUgZmlsdGVyKHBjdCA8IDIpLCANCiAgICAgICAgICAgIGNvbG9yID0gImdyZXkyMCIsIGZvbnRmYWNlID0gImJvbGQiKSArDQogIGxhYnModGl0bGUgPSAiQ2l0eSIsIHkgPSAiTnVtYmVyIG9mIHVuaXF1ZSB1c2VycyIsIHggPSAiIikgKw0KICBzY2FsZV95X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLCAwKSwgYnJlYWtzID0gc2VxKDAsMTAwMDAsMjAwMCksDQogICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBjKDAsIHBhc3RlMChzZXEoMiwxMCwyKSwgIksiKSkpICsNCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gYyhwYWxldHRlZXJfZCgiZ2d0aGVtZXM6OlRhYmxlYXVfMTAiKVsxOjhdLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlcCgiZ3JleTUwIiwgbGVuZ3RoKGxldmVscyhtZW1iZXJzJGNpdHkpKS04KSkpICsNCiAgZ2d0aGVtZXM6OnRoZW1lX3R1ZnRlKCkgKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIsDQogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiksDQogICAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiksDQogICAgICAgIGF4aXMudGlja3MueCA9IGVsZW1lbnRfYmxhbmsoKSkNCmBgYA0KDQpgYGB7ciBjaXR5X3Bsb3QsIGZpZy53aWR0aD05LCBmaWcuaGVpZ2h0PTQsIGV2YWw9VFJVRX0NCnBsdF9kZiA8LSBtZW1iZXJzICU+JQ0KICBncm91cF9ieShjaXR5KSAlPiUNCiAgdGFsbHkoKSAlPiUNCiAgdW5ncm91cCgpICU+JQ0KICBtdXRhdGUocGN0ID0gbi9zdW0obikqMTAwLA0KICAgICAgICAgbGJsID0gc3ByaW50ZigiJS4wZiUlIiwgcGN0KSwNCiAgICAgICAgIGxibCA9IGlmZWxzZShsYmwgPT0gIjAlIiwgIjwxJSIsIGxibCkpDQoNCm1lbWJlcnMgJT4lDQogZ2dwbG90KCkgKw0KICBhZXMoeCA9IGNpdHkpICsNCiAgZ2VvbV9iYXIoYWVzKGZpbGwgPSBjaXR5KSkgKw0KICBnZW9tX3RleHQoYWVzKHggPSBjaXR5LCB5ID0gbiAtIDI1MCwgbGFiZWwgPSBsYmwpLCANCiAgICAgICAgICAgIHBsdF9kZiAlPiUgZmlsdGVyKHBjdCA+IDIpLCANCiAgICAgICAgICAgIGNvbG9yID0gIndoaXRlIiwgZm9udGZhY2UgPSAiYm9sZCIpICsNCiAgZ2VvbV90ZXh0KGFlcyh4ID0gY2l0eSwgeSA9IG4gKyAzMDAsIGxhYmVsID0gbGJsKSwgDQogICAgICAgICAgICBwbHRfZGYgJT4lIGZpbHRlcihwY3QgPCAyKSwgDQogICAgICAgICAgICBjb2xvciA9ICJncmV5MjAiLCBmb250ZmFjZSA9ICJib2xkIikgKw0KICBsYWJzKHRpdGxlID0gIkNpdHkiLCB5ID0gIk51bWJlciBvZiB1bmlxdWUgdXNlcnMiLCB4ID0gIiIpICsNCiAgc2NhbGVfeV9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwgMCksIGJyZWFrcyA9IHNlcSgwLDEwMDAwLDIwMDApLA0KICAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gYygwLCBwYXN0ZTAoc2VxKDIsMTAsMiksICJLIikpKSArDQogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGMocGFsZXR0ZWVyX2QoImdndGhlbWVzOjpUYWJsZWF1XzEwIilbMTo4XSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXAoImdyZXk1MCIsIGxlbmd0aChsZXZlbHMobWVtYmVycyRjaXR5KSktOCkpKSArDQogIGdndGhlbWVzOjp0aGVtZV90dWZ0ZSgpICsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiLA0KICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIpLA0KICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIpLA0KICAgICAgICBheGlzLnRpY2tzLnggPSBlbGVtZW50X2JsYW5rKCkpDQpgYGANCg0KYGBge3Igc3VydnBsb3RfY2l0eX0NCnRyYW5zX3BlcmlvZHMgPC0gdHJhbnNfcGVyaW9kcyAlPiUNCiAgbGVmdF9qb2luKG1lbWJlcnMgJT4lIHNlbGVjdChtc25vLCBjaXR5LCBiZCwgZ2VuZGVyLCByZWdpc3RlcmVkX3ZpYSwgcmVnaXN0cmF0aW9uX2luaXRfdGltZSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB5ZWFyX3JlZywgbW9udGhfcmVnKSwgYnkgPSAibXNubyIpDQoNCnRyYW5zX3BlcmlvZHMgPC0gdHJhbnNfcGVyaW9kcyAlPiUNCiAgbXV0YXRlKHRpbWVfZnJvbV9yZWcgPSBhcy5udW1lcmljKHN0YXJ0X2RhdGUgLSByZWdpc3RyYXRpb25faW5pdF90aW1lLCAnZGF5cycpLA0KICAgICAgICAgY2l0eUcgPSBmY3RfbHVtcF9uKGNpdHksIDgsIG90aGVyX2xldmVsID0gIk90aGVyIikpDQoNCnNydmZpdCA8LSBzdXJ2Zml0KFN1cnYodGltZSwgY2h1cm4pIH4gY2l0eUcsIHRyYW5zX3BlcmlvZHMpDQogICAgICAgICAgICAgICAgICANCnAxIDwtIGdnc3VydnBsb3Qoc3J2Zml0LCANCiAgICAgICAgICAgICAgICAgcGFsZXR0ZSA9IHBhbGV0dGVlcl9kKCJnZ3RoZW1lczo6VGFibGVhdV8xMCIpWzE6bGVuZ3RoKGxldmVscyh0cmFuc19wZXJpb2RzJGNpdHlHKSldLCANCiAgICAgICAgICAgICAgICAgc2l6ZSA9IDEuMiwgY29uZi5pbnQgPSBGQUxTRSwgDQogICAgICAgICAgICAgICAgIGxlZ2VuZC5sYWJzID0gbGV2ZWxzKHRyYW5zX3BlcmlvZHMkY2l0eUcpLA0KICAgICAgICAgICAgICAgICBsZWdlbmQudGl0bGUgPSAiIiwNCiAgICAgICAgICAgICAgICAgZ2d0aGVtZSA9IHRoZW1lX21pbmltYWwoKSArIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIikpLA0KICAgICAgICAgICAgICAgICB0aXRsZSA9ICJQcm9iYWJpbGl0eSBvZiBhY3RpdmUgbWVtYmVyc2hpcCBieSB1c2VyJ3MgY2l0eSIsDQogICAgICAgICAgICAgICAgIHhsYWIgPSAiVGltZSBmcm9tIHRoZSBmaXJzdCB0cmFuc2FjdGlvbiwgbW9udGhzIiwNCiAgICAgICAgICAgICAgICAgeWxhYiA9ICJQcm9iYWJpbGl0eSBvZiBhY3RpdmUgbWVtYmVyc2hpcCIsDQogICAgICAgICAgICAgICAgIGxlZ2VuZCA9ICJib3R0b20iLCBjZW5zb3IgPSBGQUxTRSkNCg0KcDEgPC0gcDEkcGxvdCArDQogIHNjYWxlX3hfY29udGludW91cyhleHBhbmQgPSBjKDAsIDApLCBicmVha3MgPSBzZXEoMCwgODIwLCAzMC40Mzc1KjIpLA0KICAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gc2VxKDAsIDgyMC8zMC40Mzc1LCAyKSwNCiAgICAgICAgICAgICAgICAgICAgIGxpbWl0cyA9IGMoMCwgODIwKSkgKw0KICBzY2FsZV95X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLCAwKSwgYnJlYWtzID0gc2VxKDAsMSwwLjIpLA0KICAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gc2NhbGVzOjpwZXJjZW50X2Zvcm1hdChhY2N1cmFjeSA9IDEpKSArDQogIHRoZW1lX2NsYXNzaWMoKSArDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJyaWdodCIsDQogICAgICAgIGxlZ2VuZC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLA0KICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIpLA0KICAgICAgICBwYW5lbC5ncmlkLm1ham9yID0gZWxlbWVudF9saW5lKHNpemUgPSAuMiwgY29sb3VyID0gImdyZXk5MCIpLA0KICAgICAgICBwYW5lbC5ncmlkLm1pbm9yID0gZWxlbWVudF9saW5lKHNpemUgPSAuMiwgY29sb3VyID0gImdyZXk5MCIpKQ0KDQpwMQ0KYGBgDQoNCmBgYHtyIHN1cnZwbG90X2NpdHlfcGxvdCwgZmlnLmhlaWdodD00LCBmaWcud2lkdGg9NiwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgZXZhbD1UUlVFfQ0KdHJhbnNfcGVyaW9kcyA8LSB0cmFuc19wZXJpb2RzICU+JQ0KICBsZWZ0X2pvaW4obWVtYmVycyAlPiUgc2VsZWN0KG1zbm8sIGNpdHksIGJkLCBnZW5kZXIsIHJlZ2lzdGVyZWRfdmlhLCByZWdpc3RyYXRpb25faW5pdF90aW1lLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHllYXJfcmVnLCBtb250aF9yZWcpLCBieSA9ICJtc25vIikNCg0KdHJhbnNfcGVyaW9kcyA8LSB0cmFuc19wZXJpb2RzICU+JQ0KICBtdXRhdGUodGltZV9mcm9tX3JlZyA9IGFzLm51bWVyaWMoc3RhcnRfZGF0ZSAtIHJlZ2lzdHJhdGlvbl9pbml0X3RpbWUsICdkYXlzJyksDQogICAgICAgICBjaXR5RyA9IGZjdF9sdW1wX24oY2l0eSwgOCwgb3RoZXJfbGV2ZWwgPSAiT3RoZXIiKSkNCg0Kc3J2Zml0IDwtIHN1cnZmaXQoU3Vydih0aW1lLCBjaHVybikgfiBjaXR5RywgdHJhbnNfcGVyaW9kcykNCiAgICAgICAgICAgICAgICAgIA0KcDEgPC0gZ2dzdXJ2cGxvdChzcnZmaXQsIA0KICAgICAgICAgICAgICAgICBwYWxldHRlID0gcGFsZXR0ZWVyX2QoImdndGhlbWVzOjpUYWJsZWF1XzEwIilbMTpsZW5ndGgobGV2ZWxzKHRyYW5zX3BlcmlvZHMkY2l0eUcpKV0sIA0KICAgICAgICAgICAgICAgICBzaXplID0gMS4yLCBjb25mLmludCA9IEZBTFNFLCANCiAgICAgICAgICAgICAgICAgbGVnZW5kLmxhYnMgPSBsZXZlbHModHJhbnNfcGVyaW9kcyRjaXR5RyksDQogICAgICAgICAgICAgICAgIGxlZ2VuZC50aXRsZSA9ICIiLA0KICAgICAgICAgICAgICAgICBnZ3RoZW1lID0gdGhlbWVfbWluaW1hbCgpICsgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiKSksDQogICAgICAgICAgICAgICAgIHRpdGxlID0gIlByb2JhYmlsaXR5IG9mIGFjdGl2ZSBtZW1iZXJzaGlwIGJ5IHVzZXIncyBjaXR5IiwNCiAgICAgICAgICAgICAgICAgeGxhYiA9ICJUaW1lIGZyb20gdGhlIGZpcnN0IHRyYW5zYWN0aW9uLCBtb250aHMiLA0KICAgICAgICAgICAgICAgICB5bGFiID0gIlByb2JhYmlsaXR5IG9mIGFjdGl2ZSBtZW1iZXJzaGlwIiwNCiAgICAgICAgICAgICAgICAgbGVnZW5kID0gImJvdHRvbSIsIGNlbnNvciA9IEZBTFNFKQ0KDQpwMSA8LSBwMSRwbG90ICsNCiAgc2NhbGVfeF9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwgMCksIGJyZWFrcyA9IHNlcSgwLCA4MjAsIDMwLjQzNzUqMiksDQogICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBzZXEoMCwgODIwLzMwLjQzNzUsIDIpLA0KICAgICAgICAgICAgICAgICAgICAgbGltaXRzID0gYygwLCA4MjApKSArDQogIHNjYWxlX3lfY29udGludW91cyhleHBhbmQgPSBjKDAsIDApLCBicmVha3MgPSBzZXEoMCwxLDAuMiksDQogICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBzY2FsZXM6OnBlcmNlbnRfZm9ybWF0KGFjY3VyYWN5ID0gMSkpICsNCiAgdGhlbWVfY2xhc3NpYygpICsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gInJpZ2h0IiwNCiAgICAgICAgbGVnZW5kLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksDQogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiksDQogICAgICAgIHBhbmVsLmdyaWQubWFqb3IgPSBlbGVtZW50X2xpbmUoc2l6ZSA9IC4yLCBjb2xvdXIgPSAiZ3JleTkwIiksDQogICAgICAgIHBhbmVsLmdyaWQubWlub3IgPSBlbGVtZW50X2xpbmUoc2l6ZSA9IC4yLCBjb2xvdXIgPSAiZ3JleTkwIikpDQoNCnAxDQpgYGANCg0KLS0gVGhlcmUgaXMgbm8gdmlzaWJsZSBkaWZmZXJlbmNlIGluIHBhdHRlcm5zIG9mIHN1cnZpdmFsIHByb2JhYmlsaXR5IGJldHdlZW4gdXNlcnMgZnJvbSBkaWZmZXJlbnQgY2l0aWVzLCBleGNlcHQgYSBsaXR0bGUgYml0IG1vcmUgcHJvZm91bmQgZHJvcCBpbiBtZW1lcnNoaXAgZHVyaW5nIHRoZSBmaXJzdCBtb250aCBvZiBzdWJzY3JpcHRpb24gZm9yIHVzZXJzIGZyb20gY2l0eSAxICh0aGUgbW9zdCBwb3B1bG91cyBpbiB0aGUgZGF0YXNldCkuDQoNCjxicj4NCg0KLSAgICoqYGJkYCoqIC0gYWdlLiBJdCBpcyBhbiBpbnRlZ2VyIHZhcmlhYmxlIHdpdGggc29tZXdoYXQgc3RyYW5nZSBkaXN0cmlidXRpb24uIEkgc3VwcG9zZSB1c2VycyBmaWxsIGFnZSBvciBiaXJ0aCBkYXRlIGZpZWxkIGF0IHRoZSByZWdpc3RyYXRpb24gYW5kIG1heSBnaXZlIGFuIHVudHJ1dGhmdWwgb3IgZXJyb3IgdmFsdWUgaGVyZS4gQWxzbyBpdCB3YXMgbm90IGRlZmluZWQgZm9yIHdoaWNoIHBhcnRpY3VsYXIgbW9tZW50IHRoZSBhZ2Ugd2FzIGNhbGN1bGF0ZWQuIE90aGVyd2lzZSwgSSBkb3VidCBpdCBpcyBhIHVzZWZ1bCB2YXJpYWJsZSBpdHNlbGYuDQoNCmBgYHtyIGFnZX0NCm1lbWJlcnMgPC0gbWVtYmVycyAlPiUNCiAgbXV0YXRlKGJkX2dyb3VwID0gY3V0KGJkLCBjKG1pbihtZW1iZXJzJGJkKS0xLCAxMCwgNjEsIG1heChtZW1iZXJzJGJkKSsxKSwgDQogICAgICAgICAgICAgICAgICAgICAgICBjKCI8IDEwIiwgIjEwLTYwIiwgIj4gNjAiKSwgcmlnaHQgPSBGQUxTRSkpDQoNCnZhcl9sYWJlbChtZW1iZXJzKSA8LSBsaXN0KGJkID0gIkFnZSIsIGJkX2dyb3VwID0gIkFnZSBncm91cCIpDQoNCnRibF9zdW1tYXJ5KA0KICBhcy5kYXRhLmZyYW1lKHNlbGVjdChtZW1iZXJzLCBiZCwgYmRfZ3JvdXApKSwNCiAgdHlwZSA9IGFsbF9jb250aW51b3VzKCkgfiAiY29udGludW91czIiLA0KICBzdGF0aXN0aWMgPSBsaXN0KGFsbF9jb250aW51b3VzKCkgfiBjKCJ7bWVhbn0gKHtzZH0pIiwgInttZWRpYW59ICh7cDI1fS17cDc1fSkiLCAgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgInttaW59LXttYXh9IikpLA0KICBkaWdpdHMgPSBsaXN0KGFsbF9jb250aW51b3VzKCkgfiBjKHJlcCgxLCAyKSwgcmVwKDAsIDUpKSkpICU+JQ0KICBib2xkX2xhYmVscygpDQpgYGANCg0KYGBge3IgYWdlX3RhYiwgZXZhbD1UUlVFfQ0KbWVtYmVycyA8LSBtZW1iZXJzICU+JQ0KICBtdXRhdGUoYmRfZ3JvdXAgPSBjdXQoYmQsIGMobWluKG1lbWJlcnMkYmQpLTEsIDEwLCA2MSwgbWF4KG1lbWJlcnMkYmQpKzEpLCANCiAgICAgICAgICAgICAgICAgICAgICAgIGMoIjwgMTAiLCAiMTAtNjAiLCAiPiA2MCIpLCByaWdodCA9IEZBTFNFKSkNCg0KdmFyX2xhYmVsKG1lbWJlcnMpIDwtIGxpc3QoYmQgPSAiQWdlIiwgYmRfZ3JvdXAgPSAiQWdlIGdyb3VwIikNCg0KdGJsX3N1bW1hcnkoDQogIGFzLmRhdGEuZnJhbWUoc2VsZWN0KG1lbWJlcnMsIGJkLCBiZF9ncm91cCkpLA0KICB0eXBlID0gYWxsX2NvbnRpbnVvdXMoKSB+ICJjb250aW51b3VzMiIsDQogIHN0YXRpc3RpYyA9IGxpc3QoYWxsX2NvbnRpbnVvdXMoKSB+IGMoInttZWFufSAoe3NkfSkiLCAie21lZGlhbn0gKHtwMjV9LXtwNzV9KSIsICANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAie21pbn0te21heH0iKSksDQogIGRpZ2l0cyA9IGxpc3QoYWxsX2NvbnRpbnVvdXMoKSB+IGMocmVwKDEsIDIpLCByZXAoMCwgNSkpKSkgJT4lDQogIGJvbGRfbGFiZWxzKCkNCmBgYA0KDQpgYGB7ciBhZ2VHfQ0KbWVtYmVycyAlPiUNCiBmaWx0ZXIoYmRfZ3JvdXAgPT0gIjEwLTYwIikgJT4lDQogZ2dwbG90KCkgKw0KICBhZXMoeCA9IGJkKSArDQogIGdlb21faGlzdG9ncmFtKGJpbndpZHRoID0gMiwgZmlsbCA9ICIjNDY4MkI0IikgKw0KICBsYWJzKHggPSAiQWdlIiwgeSA9ICJOdW1iZXIgb2YgdW5pcXVlIHVzZXJzIiwgdGl0bGUgPSAiQWdlIChmb3IgYWdlIGluIFsxMCwgNjBdKSIpICsNCiAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IHNlcSgxMCw3MCwxMCkpICsNCiAgc2NhbGVfeV9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwwKSwgYnJlYWtzID0gc2VxKDAsMTAwMCwyNTApLA0KICAgICAgICAgICAgICAgICAgICAgbGltaXRzID0gYygwLCAxMDAwKSkgKw0KICBnZ3RoZW1lczo6dGhlbWVfdHVmdGUoKSArDQogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiksDQogICAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMikpDQpgYGANCg0KYGBge3IgYWdlR19wbG90LCBmaWcud2lkdGg9NiwgZmlnLmhlaWdodD0zLCBmaWcuYWxpZ249J2NlbnRlcicsIGV2YWw9VFJVRX0NCm1lbWJlcnMgJT4lDQogZmlsdGVyKGJkX2dyb3VwID09ICIxMC02MCIpICU+JQ0KIGdncGxvdCgpICsNCiAgYWVzKHggPSBiZCkgKw0KICBnZW9tX2hpc3RvZ3JhbShiaW53aWR0aCA9IDIsIGZpbGwgPSAiIzQ2ODJCNCIpICsNCiAgbGFicyh4ID0gIkFnZSIsIHkgPSAiTnVtYmVyIG9mIHVuaXF1ZSB1c2VycyIsIHRpdGxlID0gIkFnZSAoZm9yIGFnZSBpbiBbMTAsIDYwXSkiKSArDQogIHNjYWxlX3hfY29udGludW91cyhicmVha3MgPSBzZXEoMTAsNzAsMTApKSArDQogIHNjYWxlX3lfY29udGludW91cyhleHBhbmQgPSBjKDAsMCksIGJyZWFrcyA9IHNlcSgwLDEwMDAsMjUwKSwNCiAgICAgICAgICAgICAgICAgICAgIGxpbWl0cyA9IGMoMCwgMTAwMCkpICsNCiAgZ2d0aGVtZXM6OnRoZW1lX3R1ZnRlKCkgKw0KICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIpLA0KICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIpKQ0KYGBgDQoNCmBgYHtyIHN1cnZwbG90X2FnZX0NCnRyYW5zX3BlcmlvZHMgPC0gdHJhbnNfcGVyaW9kcyAlPiUNCiAgbXV0YXRlKGJkRyA9IGN1dChiZCwgYyhtaW4obWVtYmVycyRiZCktMSwgMTAsIG1heChtZW1iZXJzJGJkKSsxKSwgDQogICAgICAgICAgICAgICAgICAgYygiPCAxMCIsICI+PSAxMCIpLCByaWdodCA9IEZBTFNFKSkNCg0Kc3J2Zml0IDwtIHN1cnZmaXQoU3Vydih0aW1lLCBjaHVybikgfiBiZEcsIHRyYW5zX3BlcmlvZHMpDQogICAgICAgICAgICAgICAgICANCnAxIDwtIGdnc3VydnBsb3Qoc3J2Zml0LCANCiAgICAgICAgICAgICAgICAgcGFsZXR0ZSA9IHBhbGV0dGVlcl9kKCJnZ3RoZW1lczo6SmV3ZWxfQnJpZ2h0IilbMTpsZW5ndGgobGV2ZWxzKHRyYW5zX3BlcmlvZHMkY2l0eUcpKV0sIA0KICAgICAgICAgICAgICAgICBzaXplID0gMS4yLCBjb25mLmludCA9IEZBTFNFLCANCiAgICAgICAgICAgICAgICAgbGVnZW5kLmxhYnMgPSBsZXZlbHModHJhbnNfcGVyaW9kcyRiZEcpLA0KICAgICAgICAgICAgICAgICBsZWdlbmQudGl0bGUgPSAiIiwNCiAgICAgICAgICAgICAgICAgZ2d0aGVtZSA9IHRoZW1lX21pbmltYWwoKSArIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIikpLA0KICAgICAgICAgICAgICAgICB0aXRsZSA9ICJQcm9iYWJpbGl0eSBvZiBhY3RpdmUgbWVtYmVyc2hpcCBieSB1c2VyJ3MgYWdlIiwNCiAgICAgICAgICAgICAgICAgeGxhYiA9ICJUaW1lIGZyb20gdGhlIGZpcnN0IHRyYW5zYWN0aW9uLCBtb250aHMiLA0KICAgICAgICAgICAgICAgICB5bGFiID0gIlByb2JhYmlsaXR5IG9mIGFjdGl2ZSBtZW1iZXJzaGlwIiwNCiAgICAgICAgICAgICAgICAgbGVnZW5kID0gImJvdHRvbSIsIGNlbnNvciA9IEZBTFNFKQ0KDQpwMSA8LSBwMSRwbG90ICsNCiAgc2NhbGVfeF9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwgMCksIGJyZWFrcyA9IHNlcSgwLCA4MjAsIDMwLjQzNzUqMiksDQogICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBzZXEoMCwgODIwLzMwLjQzNzUsIDIpLA0KICAgICAgICAgICAgICAgICAgICAgbGltaXRzID0gYygwLCA4MjApKSArDQogIHNjYWxlX3lfY29udGludW91cyhleHBhbmQgPSBjKDAsIDApLCBicmVha3MgPSBzZXEoMCwxLDAuMiksDQogICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBzY2FsZXM6OnBlcmNlbnRfZm9ybWF0KGFjY3VyYWN5ID0gMSkpICsNCiAgdGhlbWVfY2xhc3NpYygpICsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gYygwLjksIDAuOCksDQogICAgICAgIGxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSwNCiAgICAgICAgbGVnZW5kLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksDQogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiksDQogICAgICAgIHBhbmVsLmdyaWQubWFqb3IgPSBlbGVtZW50X2xpbmUoc2l6ZSA9IC4yLCBjb2xvdXIgPSAiZ3JleTkwIiksDQogICAgICAgIHBhbmVsLmdyaWQubWlub3IgPSBlbGVtZW50X2xpbmUoc2l6ZSA9IC4yLCBjb2xvdXIgPSAiZ3JleTkwIikpDQoNCnAxDQpgYGANCg0KYGBge3Igc3VydnBsb3RfYWdlX3Bsb3QsIGZpZy5oZWlnaHQ9NCwgZmlnLndpZHRoPTYsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGV2YWw9VFJVRX0NCnRyYW5zX3BlcmlvZHMgPC0gdHJhbnNfcGVyaW9kcyAlPiUNCiAgbXV0YXRlKGJkRyA9IGN1dChiZCwgYyhtaW4obWVtYmVycyRiZCktMSwgMTAsIG1heChtZW1iZXJzJGJkKSsxKSwgDQogICAgICAgICAgICAgICAgICAgYygiPCAxMCIsICI+PSAxMCIpLCByaWdodCA9IEZBTFNFKSkNCg0Kc3J2Zml0IDwtIHN1cnZmaXQoU3Vydih0aW1lLCBjaHVybikgfiBiZEcsIHRyYW5zX3BlcmlvZHMpDQogICAgICAgICAgICAgICAgICANCnAxIDwtIGdnc3VydnBsb3Qoc3J2Zml0LCANCiAgICAgICAgICAgICAgICAgcGFsZXR0ZSA9IHBhbGV0dGVlcl9kKCJnZ3RoZW1lczo6SmV3ZWxfQnJpZ2h0IilbMTpsZW5ndGgobGV2ZWxzKHRyYW5zX3BlcmlvZHMkY2l0eUcpKV0sIA0KICAgICAgICAgICAgICAgICBzaXplID0gMS4yLCBjb25mLmludCA9IEZBTFNFLCANCiAgICAgICAgICAgICAgICAgbGVnZW5kLmxhYnMgPSBsZXZlbHModHJhbnNfcGVyaW9kcyRiZEcpLA0KICAgICAgICAgICAgICAgICBsZWdlbmQudGl0bGUgPSAiIiwNCiAgICAgICAgICAgICAgICAgZ2d0aGVtZSA9IHRoZW1lX21pbmltYWwoKSArIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIikpLA0KICAgICAgICAgICAgICAgICB0aXRsZSA9ICJQcm9iYWJpbGl0eSBvZiBhY3RpdmUgbWVtYmVyc2hpcCBieSB1c2VyJ3MgYWdlIiwNCiAgICAgICAgICAgICAgICAgeGxhYiA9ICJUaW1lIGZyb20gdGhlIGZpcnN0IHRyYW5zYWN0aW9uLCBtb250aHMiLA0KICAgICAgICAgICAgICAgICB5bGFiID0gIlByb2JhYmlsaXR5IG9mIGFjdGl2ZSBtZW1iZXJzaGlwIiwNCiAgICAgICAgICAgICAgICAgbGVnZW5kID0gImJvdHRvbSIsIGNlbnNvciA9IEZBTFNFKQ0KDQpwMSA8LSBwMSRwbG90ICsNCiAgc2NhbGVfeF9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwgMCksIGJyZWFrcyA9IHNlcSgwLCA4MjAsIDMwLjQzNzUqMiksDQogICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBzZXEoMCwgODIwLzMwLjQzNzUsIDIpLA0KICAgICAgICAgICAgICAgICAgICAgbGltaXRzID0gYygwLCA4MjApKSArDQogIHNjYWxlX3lfY29udGludW91cyhleHBhbmQgPSBjKDAsIDApLCBicmVha3MgPSBzZXEoMCwxLDAuMiksDQogICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBzY2FsZXM6OnBlcmNlbnRfZm9ybWF0KGFjY3VyYWN5ID0gMSkpICsNCiAgdGhlbWVfY2xhc3NpYygpICsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gYygwLjksIDAuOCksDQogICAgICAgIGxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSwNCiAgICAgICAgbGVnZW5kLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksDQogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiksDQogICAgICAgIHBhbmVsLmdyaWQubWFqb3IgPSBlbGVtZW50X2xpbmUoc2l6ZSA9IC4yLCBjb2xvdXIgPSAiZ3JleTkwIiksDQogICAgICAgIHBhbmVsLmdyaWQubWlub3IgPSBlbGVtZW50X2xpbmUoc2l6ZSA9IC4yLCBjb2xvdXIgPSAiZ3JleTkwIikpDQoNCnAxDQpgYGANCg0KPGJyPg0KDQotICAgKipgZ2VuZGVyYCoqDQoNCmBgYHtyIGdlbmRlcn0NCnBsdF9kZiA8LSBtZW1iZXJzICU+JQ0KICBncm91cF9ieShnZW5kZXIpICU+JQ0KICB0YWxseSgpICU+JQ0KICB1bmdyb3VwKCkgJT4lDQogIG11dGF0ZShwY3QgPSBuL3N1bShuKSoxMDAsDQogICAgICAgICBsYmwgPSBzcHJpbnRmKCIlLjFmJSUiLCBwY3QpKQ0KDQpnZ3Bsb3QobWVtYmVycykgKw0KICBhZXMoeCA9IGdlbmRlciwgZmlsbCA9IGdlbmRlcikgKw0KICBnZW9tX2Jhcih3aWR0aCA9IDAuNikgKw0KICBnZW9tX3RleHQoYWVzKHggPSBnZW5kZXIsIHkgPSBuIC0gNTAwLCBsYWJlbCA9IGxibCksIHBsdF9kZiwgDQogICAgICAgICAgICBjb2xvciA9ICJ3aGl0ZSIsIGZvbnRmYWNlID0gImJvbGQiKSArDQogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IHBhbGV0dGVlcl9kKCJnZ3RoZW1lczo6U3VwZXJmaXNoZWxfU3RvbmUiKVtjKDMsMSw1KV0pICsNCiAgc2NhbGVfeV9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwwKSwgYnJlYWtzID0gc2VxKDAsMTAwMDAsMjAwMCksDQogICAgICAgICAgICAgICAgICAgICBsaW1pdHMgPSBjKDAsIDEwMDAwKSwgbGFiZWxzID0gYygwLCBwYXN0ZTAoc2VxKDIsMTAsMiksICJLIikpKSArDQogIGdndGhlbWVzOjp0aGVtZV90dWZ0ZSgpICsNCiAgbGFicyh4ID0gIiIsIHkgPSAiTnVtYmVyIG9mIHVuaXF1ZSB1c2VycyIsIHRpdGxlID0gIkdlbmRlciIpICsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSArDQogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiksDQogICAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiksDQogICAgICAgIGF4aXMudGlja3MueCA9IGVsZW1lbnRfYmxhbmsoKSkNCmBgYA0KDQpgYGB7ciBnZW5kZXJfcGxvdCwgZmlnLndpZHRoPTQsIGZpZy5oZWlnaHQ9MywgZXZhbD1UUlVFfQ0KcGx0X2RmIDwtIG1lbWJlcnMgJT4lDQogIGdyb3VwX2J5KGdlbmRlcikgJT4lDQogIHRhbGx5KCkgJT4lDQogIHVuZ3JvdXAoKSAlPiUNCiAgbXV0YXRlKHBjdCA9IG4vc3VtKG4pKjEwMCwNCiAgICAgICAgIGxibCA9IHNwcmludGYoIiUuMWYlJSIsIHBjdCkpDQoNCmdncGxvdChtZW1iZXJzKSArDQogIGFlcyh4ID0gZ2VuZGVyLCBmaWxsID0gZ2VuZGVyKSArDQogIGdlb21fYmFyKHdpZHRoID0gMC42KSArDQogIGdlb21fdGV4dChhZXMoeCA9IGdlbmRlciwgeSA9IG4gLSA1MDAsIGxhYmVsID0gbGJsKSwgcGx0X2RmLCANCiAgICAgICAgICAgIGNvbG9yID0gIndoaXRlIiwgZm9udGZhY2UgPSAiYm9sZCIpICsNCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gcGFsZXR0ZWVyX2QoImdndGhlbWVzOjpTdXBlcmZpc2hlbF9TdG9uZSIpW2MoMywxLDUpXSkgKw0KICBzY2FsZV95X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLDApLCBicmVha3MgPSBzZXEoMCwxMDAwMCwyMDAwKSwNCiAgICAgICAgICAgICAgICAgICAgIGxpbWl0cyA9IGMoMCwgMTAwMDApLCBsYWJlbHMgPSBjKDAsIHBhc3RlMChzZXEoMiwxMCwyKSwgIksiKSkpICsNCiAgZ2d0aGVtZXM6OnRoZW1lX3R1ZnRlKCkgKw0KICBsYWJzKHggPSAiIiwgeSA9ICJOdW1iZXIgb2YgdW5pcXVlIHVzZXJzIiwgdGl0bGUgPSAiR2VuZGVyIikgKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpICsNCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiKSwNCiAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyKSwNCiAgICAgICAgYXhpcy50aWNrcy54ID0gZWxlbWVudF9ibGFuaygpKQ0KYGBgDQoNCmBgYHtyIHN1cnZwbG90X2dlbmRlcn0NCnNydmZpdCA8LSBzdXJ2Zml0KFN1cnYodGltZSwgY2h1cm4pIH4gZ2VuZGVyLCB0cmFuc19wZXJpb2RzKQ0KICAgICAgICAgICAgICAgICAgDQpwMSA8LSBnZ3N1cnZwbG90KHNydmZpdCwgDQogICAgICAgICAgICAgICAgIHBhbGV0dGUgPSBwYWxldHRlZXJfZCgiZ2d0aGVtZXM6OlN1cGVyZmlzaGVsX1N0b25lIilbYygzLDEsNSldLCANCiAgICAgICAgICAgICAgICAgc2l6ZSA9IDEuMiwgY29uZi5pbnQgPSBGQUxTRSwNCiAgICAgICAgICAgICAgICAgbGVnZW5kLmxhYnMgPSBsZXZlbHModHJhbnNfcGVyaW9kcyRnZW5kZXIpLA0KICAgICAgICAgICAgICAgICBsZWdlbmQudGl0bGUgPSAiIiwNCiAgICAgICAgICAgICAgICAgZ2d0aGVtZSA9IHRoZW1lX21pbmltYWwoKSArIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIikpLA0KICAgICAgICAgICAgICAgICB0aXRsZSA9ICJQcm9iYWJpbGl0eSBvZiBhY3RpdmUgbWVtYmVyc2hpcCBieSB1c2VyJ3MgZ2VuZGVyIiwNCiAgICAgICAgICAgICAgICAgeGxhYiA9ICJUaW1lIGZyb20gdGhlIGZpcnN0IHRyYW5zYWN0aW9uLCBtb250aHMiLA0KICAgICAgICAgICAgICAgICB5bGFiID0gIlByb2JhYmlsaXR5IG9mIGFjdGl2ZSBtZW1iZXJzaGlwIiwNCiAgICAgICAgICAgICAgICAgbGVnZW5kID0gImJvdHRvbSIsIGNlbnNvciA9IEZBTFNFKQ0KDQpwMSA8LSBwMSRwbG90ICsNCiAgc2NhbGVfeF9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwgMCksIGJyZWFrcyA9IHNlcSgwLCA4MjAsIDMwLjQzNzUqMiksDQogICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBzZXEoMCwgODIwLzMwLjQzNzUsIDIpLA0KICAgICAgICAgICAgICAgICAgICAgbGltaXRzID0gYygwLCA4MjApKSArDQogIHNjYWxlX3lfY29udGludW91cyhleHBhbmQgPSBjKDAsIDApLCBicmVha3MgPSBzZXEoMCwxLDAuMiksDQogICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBzY2FsZXM6OnBlcmNlbnRfZm9ybWF0KGFjY3VyYWN5ID0gMSkpICsNCiAgdGhlbWVfY2xhc3NpYygpICsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gYygwLjksIDAuOCksDQogICAgICAgIGxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSwNCiAgICAgICAgbGVnZW5kLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksDQogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiksDQogICAgICAgIHBhbmVsLmdyaWQubWFqb3IgPSBlbGVtZW50X2xpbmUoc2l6ZSA9IC4yLCBjb2xvdXIgPSAiZ3JleTkwIiksDQogICAgICAgIHBhbmVsLmdyaWQubWlub3IgPSBlbGVtZW50X2xpbmUoc2l6ZSA9IC4yLCBjb2xvdXIgPSAiZ3JleTkwIikpDQoNCnAxDQpgYGANCg0KYGBge3Igc3VydnBsb3RfZ2VuZGVyX3Bsb3QsIGZpZy5oZWlnaHQ9NCwgZmlnLndpZHRoPTYsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGV2YWw9VFJVRX0NCnNydmZpdCA8LSBzdXJ2Zml0KFN1cnYodGltZSwgY2h1cm4pIH4gZ2VuZGVyLCB0cmFuc19wZXJpb2RzKQ0KICAgICAgICAgICAgICAgICAgDQpwMSA8LSBnZ3N1cnZwbG90KHNydmZpdCwgDQogICAgICAgICAgICAgICAgIHBhbGV0dGUgPSBwYWxldHRlZXJfZCgiZ2d0aGVtZXM6OlN1cGVyZmlzaGVsX1N0b25lIilbYygzLDEsNSldLCANCiAgICAgICAgICAgICAgICAgc2l6ZSA9IDEuMiwgY29uZi5pbnQgPSBGQUxTRSwNCiAgICAgICAgICAgICAgICAgbGVnZW5kLmxhYnMgPSBsZXZlbHModHJhbnNfcGVyaW9kcyRnZW5kZXIpLA0KICAgICAgICAgICAgICAgICBsZWdlbmQudGl0bGUgPSAiIiwNCiAgICAgICAgICAgICAgICAgZ2d0aGVtZSA9IHRoZW1lX21pbmltYWwoKSArIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIikpLA0KICAgICAgICAgICAgICAgICB0aXRsZSA9ICJQcm9iYWJpbGl0eSBvZiBhY3RpdmUgbWVtYmVyc2hpcCBieSB1c2VyJ3MgZ2VuZGVyIiwNCiAgICAgICAgICAgICAgICAgeGxhYiA9ICJUaW1lIGZyb20gdGhlIGZpcnN0IHRyYW5zYWN0aW9uLCBtb250aHMiLA0KICAgICAgICAgICAgICAgICB5bGFiID0gIlByb2JhYmlsaXR5IG9mIGFjdGl2ZSBtZW1iZXJzaGlwIiwNCiAgICAgICAgICAgICAgICAgbGVnZW5kID0gImJvdHRvbSIsIGNlbnNvciA9IEZBTFNFKQ0KDQpwMSA8LSBwMSRwbG90ICsNCiAgc2NhbGVfeF9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwgMCksIGJyZWFrcyA9IHNlcSgwLCA4MjAsIDMwLjQzNzUqMiksDQogICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBzZXEoMCwgODIwLzMwLjQzNzUsIDIpLA0KICAgICAgICAgICAgICAgICAgICAgbGltaXRzID0gYygwLCA4MjApKSArDQogIHNjYWxlX3lfY29udGludW91cyhleHBhbmQgPSBjKDAsIDApLCBicmVha3MgPSBzZXEoMCwxLDAuMiksDQogICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBzY2FsZXM6OnBlcmNlbnRfZm9ybWF0KGFjY3VyYWN5ID0gMSkpICsNCiAgdGhlbWVfY2xhc3NpYygpICsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gYygwLjksIDAuOCksDQogICAgICAgIGxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSwNCiAgICAgICAgbGVnZW5kLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksDQogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiksDQogICAgICAgIHBhbmVsLmdyaWQubWFqb3IgPSBlbGVtZW50X2xpbmUoc2l6ZSA9IC4yLCBjb2xvdXIgPSAiZ3JleTkwIiksDQogICAgICAgIHBhbmVsLmdyaWQubWlub3IgPSBlbGVtZW50X2xpbmUoc2l6ZSA9IC4yLCBjb2xvdXIgPSAiZ3JleTkwIikpDQoNCnAxDQpgYGANCg0KLS0gVGhlcmUgYXJlIHNvbWUgc3RyYW5nZSBkaWZmZXJlbmNlcyBiZXR3ZWVuIHVzZXJzIHdpdGgga25vd24gKG1hbGVzIGFuZCBmZW1hbGVzKSBhbmQgdW5rbm93biBnZW5kZXIuDQoNCjxicj4NCg0KLSAgICoqYHJlZ2lzdGVyZWRfdmlhYCoqIC0gcmVnaXN0cmF0aW9uIG1ldGhvZC4gVGhlcmUgYXJlIGByIGxlbmd0aCh1bmlxdWUobWVtYmVycyRyZWdpc3RlcmVkX3ZpYSkpYCB1bmlxdWUgbnVtZXJpYyBjb2RlcyBmb3IgdGhpcyB2YXJpYWJsZSB3aXRob3V0IGFueSBleHBsYW5hdGlvbnMgZnJvbSBvcmdhbml6ZXJzLg0KDQpgYGB7ciByZWdfdmlhfQ0KcGx0X2RmIDwtIG1lbWJlcnMgJT4lDQogIGdyb3VwX2J5KHJlZ2lzdGVyZWRfdmlhKSAlPiUNCiAgdGFsbHkoKSAlPiUNCiAgdW5ncm91cCgpICU+JQ0KICBtdXRhdGUocGN0ID0gbi9zdW0obikqMTAwLA0KICAgICAgICAgbGJsID0gc3ByaW50ZigiJS4xZiUlIiwgcGN0KSkNCg0KbWVtYmVycyAlPiUNCiBnZ3Bsb3QoKSArDQogIGFlcyh4ID0gcmVnaXN0ZXJlZF92aWEpICsNCiAgZ2VvbV9iYXIoYWVzKGZpbGwgPSByZWdpc3RlcmVkX3ZpYSkpICsNCiAgZ2VvbV90ZXh0KGFlcyh4ID0gcmVnaXN0ZXJlZF92aWEsIHkgPSBuIC0gMjAwLCBsYWJlbCA9IGxibCksIA0KICAgICAgICAgICAgcGx0X2RmICU+JSBmaWx0ZXIocGN0ID4gMSksIA0KICAgICAgICAgICAgY29sb3IgPSAid2hpdGUiLCBmb250ZmFjZSA9ICJib2xkIikgKw0KICBnZW9tX3RleHQoYWVzKHggPSByZWdpc3RlcmVkX3ZpYSwgeSA9IG4gKyAyMDAsIGxhYmVsID0gbGJsKSwgDQogICAgICAgICAgICBwbHRfZGYgJT4lIGZpbHRlcihwY3QgPCAxKSwgDQogICAgICAgICAgICBjb2xvciA9ICJncmV5MjAiLCBmb250ZmFjZSA9ICJib2xkIikgKw0KICBsYWJzKHRpdGxlID0gIlJlZ2lzdHJhdGlvbiBtZXRob2QiLCB5ID0gIk51bWJlciBvZiB1bmlxdWUgdXNlcnMiLCB4ID0gIiIpICsNCiAgc2NhbGVfeV9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwgMCksIGJyZWFrcyA9IHNlcSgwLCA2MDAwLCAxMDAwKSwNCiAgICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IGMoMCwgcGFzdGUwKHNlcSgxLDYsMSksICJLIikpKSArDQogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IHBhbGV0dGVlcl9kKCJnZ3RoZW1lczo6VGFibGVhdV8xMCIpWzE6NV0pICsNCiAgZ2d0aGVtZXM6OnRoZW1lX3R1ZnRlKCkgKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIsDQogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiksDQogICAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiksDQogICAgICAgIGF4aXMudGlja3MueCA9IGVsZW1lbnRfYmxhbmsoKSkNCmBgYA0KDQpgYGB7ciByZWdfdmlhX3Bsb3QsIGZpZy53aWR0aD00LCBmaWcuaGVpZ2h0PTMsIGV2YWw9VFJVRX0NCnBsdF9kZiA8LSBtZW1iZXJzICU+JQ0KICBncm91cF9ieShyZWdpc3RlcmVkX3ZpYSkgJT4lDQogIHRhbGx5KCkgJT4lDQogIHVuZ3JvdXAoKSAlPiUNCiAgbXV0YXRlKHBjdCA9IG4vc3VtKG4pKjEwMCwNCiAgICAgICAgIGxibCA9IHNwcmludGYoIiUuMWYlJSIsIHBjdCkpDQoNCm1lbWJlcnMgJT4lDQogZ2dwbG90KCkgKw0KICBhZXMoeCA9IHJlZ2lzdGVyZWRfdmlhKSArDQogIGdlb21fYmFyKGFlcyhmaWxsID0gcmVnaXN0ZXJlZF92aWEpKSArDQogIGdlb21fdGV4dChhZXMoeCA9IHJlZ2lzdGVyZWRfdmlhLCB5ID0gbiAtIDIwMCwgbGFiZWwgPSBsYmwpLCANCiAgICAgICAgICAgIHBsdF9kZiAlPiUgZmlsdGVyKHBjdCA+IDEpLCANCiAgICAgICAgICAgIGNvbG9yID0gIndoaXRlIiwgZm9udGZhY2UgPSAiYm9sZCIpICsNCiAgZ2VvbV90ZXh0KGFlcyh4ID0gcmVnaXN0ZXJlZF92aWEsIHkgPSBuICsgMjAwLCBsYWJlbCA9IGxibCksIA0KICAgICAgICAgICAgcGx0X2RmICU+JSBmaWx0ZXIocGN0IDwgMSksIA0KICAgICAgICAgICAgY29sb3IgPSAiZ3JleTIwIiwgZm9udGZhY2UgPSAiYm9sZCIpICsNCiAgbGFicyh0aXRsZSA9ICJSZWdpc3RyYXRpb24gbWV0aG9kIiwgeSA9ICJOdW1iZXIgb2YgdW5pcXVlIHVzZXJzIiwgeCA9ICIiKSArDQogIHNjYWxlX3lfY29udGludW91cyhleHBhbmQgPSBjKDAsIDApLCBicmVha3MgPSBzZXEoMCwgNjAwMCwgMTAwMCksDQogICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBjKDAsIHBhc3RlMChzZXEoMSw2LDEpLCAiSyIpKSkgKw0KICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBwYWxldHRlZXJfZCgiZ2d0aGVtZXM6OlRhYmxlYXVfMTAiKVsxOjVdKSArDQogIGdndGhlbWVzOjp0aGVtZV90dWZ0ZSgpICsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiLA0KICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIpLA0KICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIpLA0KICAgICAgICBheGlzLnRpY2tzLnggPSBlbGVtZW50X2JsYW5rKCkpDQpgYGANCg0KYGBge3Igc3VydnBsb3RfcmVnX3ZpYX0NCnNydmZpdCA8LSBzdXJ2Zml0KFN1cnYodGltZSwgY2h1cm4pIH4gcmVnaXN0ZXJlZF92aWEsIHRyYW5zX3BlcmlvZHMpDQogICAgICAgICAgICAgICAgICANCnAxIDwtIGdnc3VydnBsb3Qoc3J2Zml0LCANCiAgICAgICAgICAgICAgICAgcGFsZXR0ZSA9IHBhbGV0dGVlcl9kKCJnZ3RoZW1lczo6VGFibGVhdV8xMCIpWzE6NV0sIA0KICAgICAgICAgICAgICAgICBzaXplID0gMS4yLCBjb25mLmludCA9IEZBTFNFLCANCiAgICAgICAgICAgICAgICAgbGVnZW5kLmxhYnMgPSBsZXZlbHModHJhbnNfcGVyaW9kcyRyZWdpc3RlcmVkX3ZpYSksDQogICAgICAgICAgICAgICAgIGxlZ2VuZC50aXRsZSA9ICIiLA0KICAgICAgICAgICAgICAgICBnZ3RoZW1lID0gdGhlbWVfbWluaW1hbCgpICsgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiKSksDQogICAgICAgICAgICAgICAgIHRpdGxlID0gIlByb2JhYmlsaXR5IG9mIGFjdGl2ZSBtZW1iZXJzaGlwIGJ5XG5yZWdpc3RyYXRpb24gbWV0aG9kIiwNCiAgICAgICAgICAgICAgICAgeGxhYiA9ICJUaW1lIGZyb20gdGhlIGZpcnN0IHRyYW5zYWN0aW9uLCBtb250aHMiLA0KICAgICAgICAgICAgICAgICB5bGFiID0gIlByb2JhYmlsaXR5IG9mIGFjdGl2ZSBtZW1iZXJzaGlwIiwNCiAgICAgICAgICAgICAgICAgbGVnZW5kID0gImJvdHRvbSIsIGNlbnNvciA9IEZBTFNFKQ0KDQpwMSA8LSBwMSRwbG90ICsNCiAgc2NhbGVfeF9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwgMCksIGJyZWFrcyA9IHNlcSgwLCA4MjAsIDMwLjQzNzUqMiksDQogICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBzZXEoMCwgODIwLzMwLjQzNzUsIDIpLA0KICAgICAgICAgICAgICAgICAgICAgbGltaXRzID0gYygwLCA4MjApKSArDQogIHNjYWxlX3lfY29udGludW91cyhleHBhbmQgPSBjKDAsIDApLCBicmVha3MgPSBzZXEoMCwxLDAuMiksDQogICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBzY2FsZXM6OnBlcmNlbnRfZm9ybWF0KGFjY3VyYWN5ID0gMSkpICsNCiAgdGhlbWVfY2xhc3NpYygpICsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gYygwLjksIDAuOCksDQogICAgICAgIGxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSwNCiAgICAgICAgbGVnZW5kLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksDQogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiksDQogICAgICAgIHBhbmVsLmdyaWQubWFqb3IgPSBlbGVtZW50X2xpbmUoc2l6ZSA9IC4yLCBjb2xvdXIgPSAiZ3JleTkwIiksDQogICAgICAgIHBhbmVsLmdyaWQubWlub3IgPSBlbGVtZW50X2xpbmUoc2l6ZSA9IC4yLCBjb2xvdXIgPSAiZ3JleTkwIikpDQoNCnAxDQpgYGANCg0KYGBge3Igc3VydnBsb3RfcmVnX3ZpYV9wbG90LCBmaWcuaGVpZ2h0PTQsIGZpZy53aWR0aD02LCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBldmFsPVRSVUV9DQpzcnZmaXQgPC0gc3VydmZpdChTdXJ2KHRpbWUsIGNodXJuKSB+IHJlZ2lzdGVyZWRfdmlhLCB0cmFuc19wZXJpb2RzKQ0KICAgICAgICAgICAgICAgICAgDQpwMSA8LSBnZ3N1cnZwbG90KHNydmZpdCwgDQogICAgICAgICAgICAgICAgIHBhbGV0dGUgPSBwYWxldHRlZXJfZCgiZ2d0aGVtZXM6OlRhYmxlYXVfMTAiKVsxOjVdLCANCiAgICAgICAgICAgICAgICAgc2l6ZSA9IDEuMiwgY29uZi5pbnQgPSBGQUxTRSwgDQogICAgICAgICAgICAgICAgIGxlZ2VuZC5sYWJzID0gbGV2ZWxzKHRyYW5zX3BlcmlvZHMkcmVnaXN0ZXJlZF92aWEpLA0KICAgICAgICAgICAgICAgICBsZWdlbmQudGl0bGUgPSAiIiwNCiAgICAgICAgICAgICAgICAgZ2d0aGVtZSA9IHRoZW1lX21pbmltYWwoKSArIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIikpLA0KICAgICAgICAgICAgICAgICB0aXRsZSA9ICJQcm9iYWJpbGl0eSBvZiBhY3RpdmUgbWVtYmVyc2hpcCBieVxucmVnaXN0cmF0aW9uIG1ldGhvZCIsDQogICAgICAgICAgICAgICAgIHhsYWIgPSAiVGltZSBmcm9tIHRoZSBmaXJzdCB0cmFuc2FjdGlvbiwgbW9udGhzIiwNCiAgICAgICAgICAgICAgICAgeWxhYiA9ICJQcm9iYWJpbGl0eSBvZiBhY3RpdmUgbWVtYmVyc2hpcCIsDQogICAgICAgICAgICAgICAgIGxlZ2VuZCA9ICJib3R0b20iLCBjZW5zb3IgPSBGQUxTRSkNCg0KcDEgPC0gcDEkcGxvdCArDQogIHNjYWxlX3hfY29udGludW91cyhleHBhbmQgPSBjKDAsIDApLCBicmVha3MgPSBzZXEoMCwgODIwLCAzMC40Mzc1KjIpLA0KICAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gc2VxKDAsIDgyMC8zMC40Mzc1LCAyKSwNCiAgICAgICAgICAgICAgICAgICAgIGxpbWl0cyA9IGMoMCwgODIwKSkgKw0KICBzY2FsZV95X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLCAwKSwgYnJlYWtzID0gc2VxKDAsMSwwLjIpLA0KICAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gc2NhbGVzOjpwZXJjZW50X2Zvcm1hdChhY2N1cmFjeSA9IDEpKSArDQogIHRoZW1lX2NsYXNzaWMoKSArDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9IGMoMC45LCAwLjgpLA0KICAgICAgICBsZWdlbmQudGl0bGUgPSBlbGVtZW50X2JsYW5rKCksDQogICAgICAgIGxlZ2VuZC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLA0KICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIpLA0KICAgICAgICBwYW5lbC5ncmlkLm1ham9yID0gZWxlbWVudF9saW5lKHNpemUgPSAuMiwgY29sb3VyID0gImdyZXk5MCIpLA0KICAgICAgICBwYW5lbC5ncmlkLm1pbm9yID0gZWxlbWVudF9saW5lKHNpemUgPSAuMiwgY29sb3VyID0gImdyZXk5MCIpKQ0KDQpwMQ0KYGBgDQoNCi0tIFRoZXJlIGFyZSBkaWZmZXJlbmNlcyBpbiBzdXJ2aXZhbCBiZXR3ZWVuIHVzZXJzIHdpdGggZGlmZmVyZW50IHJlZ2lzdHJhdGlvbiBtZXRob2QsIGFsdGhvdWdoIEkgd291bGQgc2F5IHRoYXQgcGF0dGVybnMgYXJlIHNpbWlsYXI6IGEgc2lnbmlmaWNhbnQgZHJvcCBpbiBtZW1iZXJzaGlwIGR1cmluZyBhbmQgYXQgdGhlIGVuZCBvZiB0aGUgZmlyc3QgbW9udGggb2Ygc3Vic2NyaXB0aW9uIGZvbGxvd2VkIGJ5IGdyYWR1YWwgZGVjcmVhc2UuIEZvciB1c2VycyByZWdpc3RlcmVkIHZpYSAzcmQsIDR0aCBhbmQgOXRoIG1ldGhvZHMgdGhlcmUgaXMgYWxzbyBhIGRyb3AgaW4gc3Vic2NyaXB0aW9ucyBiZXR3ZWVuIDEzIGFuZCAxNCBtb250aHMuDQoNCjxicj4NCg0KLSAgICoqYHJlZ2lzdHJhdGlvbl9pbml0X3RpbWVgKiogLSBkYXRlIG9mIGluaXRpYWwgcmVnaXN0cmF0aW9uIGZvciBlYWNoIGNsaWVudCwgcmFuZ2VzIGZyb20gYHIgbWluKG1lbWJlcnMkcmVnaXN0cmF0aW9uX2luaXRfdGltZSlgIHRvIGByIG1heChtZW1iZXJzJHJlZ2lzdHJhdGlvbl9pbml0X3RpbWUpYC4NCg0KYGBge3IgcmVnX2RhdGV9DQpyZWdfZGF5cyA8LSBtZW1iZXJzICU+JQ0KICBtdXRhdGUocmVnaXN0cmF0aW9uX2luaXRfdGltZSA9IGZsb29yX2RhdGUocmVnaXN0cmF0aW9uX2luaXRfdGltZSwgIm1vbnRoIikpICU+JQ0KICBncm91cF9ieShyZWdpc3RyYXRpb25faW5pdF90aW1lKSAlPiUNCiAgdGFsbHkoKQ0KDQpnZ3Bsb3QocmVnX2RheXMpICsNCiAgYWVzKHggPSByZWdpc3RyYXRpb25faW5pdF90aW1lLCB5ID0gbikgKw0KICBnZW9tX2xpbmUoc2l6ZSA9IDAuNSwgY29sb3VyID0gIiMxMTI0NDYiKSArDQogIHNjYWxlX3hfZGF0ZShkYXRlX2JyZWFrcyA9ICIxIHllYXIiLCBkYXRlX2xhYmVscyA9ICAiJVkiKSArDQogICMgc2NhbGVfeV9jb250aW51b3VzKGxpbWl0cyA9IGMoMzAwMCwgNjAwMCksDQogICMgICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IGMocGFzdGUwKHNlcSgzLDYsMSksICJLIikpKSArDQogIGxhYnModGl0bGUgPSAiTnVtYmVyIG9mIHJlZ2lzdHJhdGlvbnMgYnkgbW9udGgiLCB4ID0gIiIsIHkgPSAiIikgKw0KICBnZ3RoZW1lczo6dGhlbWVfdHVmdGUoKSArDQogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiksDQogICAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgaGp1c3QgPSAwLjUsIHZqdXN0ID0gMC41KSkNCmBgYA0KDQpgYGB7ciByZWdfZGF0ZV9wbG90LCBmaWcud2lkdGg9OSwgZmlnLmhlaWdodD00LCBldmFsPVRSVUV9DQpyZWdfZGF5cyA8LSBtZW1iZXJzICU+JQ0KICBtdXRhdGUocmVnaXN0cmF0aW9uX2luaXRfdGltZSA9IGZsb29yX2RhdGUocmVnaXN0cmF0aW9uX2luaXRfdGltZSwgIm1vbnRoIikpICU+JQ0KICBncm91cF9ieShyZWdpc3RyYXRpb25faW5pdF90aW1lKSAlPiUNCiAgdGFsbHkoKQ0KDQpnZ3Bsb3QocmVnX2RheXMpICsNCiAgYWVzKHggPSByZWdpc3RyYXRpb25faW5pdF90aW1lLCB5ID0gbikgKw0KICBnZW9tX2xpbmUoc2l6ZSA9IDAuNSwgY29sb3VyID0gIiMxMTI0NDYiKSArDQogIHNjYWxlX3hfZGF0ZShkYXRlX2JyZWFrcyA9ICIxIHllYXIiLCBkYXRlX2xhYmVscyA9ICAiJVkiKSArDQogICMgc2NhbGVfeV9jb250aW51b3VzKGxpbWl0cyA9IGMoMzAwMCwgNjAwMCksDQogICMgICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IGMocGFzdGUwKHNlcSgzLDYsMSksICJLIikpKSArDQogIGxhYnModGl0bGUgPSAiTnVtYmVyIG9mIHJlZ2lzdHJhdGlvbnMgYnkgbW9udGgiLCB4ID0gIiIsIHkgPSAiIikgKw0KICBnZ3RoZW1lczo6dGhlbWVfdHVmdGUoKSArDQogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiksDQogICAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgaGp1c3QgPSAwLjUsIHZqdXN0ID0gMC41KSkNCmBgYA0KDQotLSBUaGVyZSBpcyBhIG5vdGljYWJsZSB0cmVuZCB0byBkZWNyZWFzaW5nIGluIHRoZSBudW1iZXIgb2YgbW9udGhseSByZWdpc3RyYXRpb25zIGF0IEtLQm94IHNlcnZpY2Ugc2luY2UgMjAxNi4gT2YgY291cnNlLCB3ZSBkbyBub3Qga25vdyBmb3IgaG93IG1hbnkgdXNlcnMgaW5mb3JtYXRpb24gd2FzIGRlbGV0ZWQgZnJvbSB0aGUgZGF0YSBieSBLS0JveCwgYnV0IGlmIHRoaXMgbnVtYmVyIGRpZCBub3QgZGVwZW5kIG9uIHRoZSB5ZWFyIG9mIHJlZ2lzdHJhdGlvbiwgdGhlbiB3ZSBjYW4gc3VwcG9zZSB0aGF0IHdlIGhhdmUgaGVyZSBhIHJlcHJlc2VudGF0aW9uIG9mIHRoZSBtb250aGx5IGR5bmFtaWNzIG9mIEtLQm94IHBvcHVsYXJpdHkuDQoNCi0tIFNpbmNlIDIwMTEgdGhlcmUgYXJlIHBlYWtzIGluIHJlZ2lzdHJhdGlvbnMgYXQgdGhlIGJlZ2lubmluZyBvZiBlYWNoIHllYXIuDQoNCjxicj4NCg0KYGBge3IgcmVnX3llYXJ9DQpwbHRfZGYgPC0gbWVtYmVycyAlPiUNCiAgZ3JvdXBfYnkoeWVhcl9yZWcpICU+JQ0KICB0YWxseSgpICU+JQ0KICB1bmdyb3VwKCkgJT4lDQogIG11dGF0ZShwY3QgPSBuL3N1bShuKSoxMDAsDQogICAgICAgICBsYmwgPSBzcHJpbnRmKCIlLjFmJSUiLCBwY3QpKQ0KDQptZW1iZXJzICU+JQ0KIGdncGxvdCgpICsNCiAgYWVzKHggPSB5ZWFyX3JlZykgKw0KICBnZW9tX2JhcihhZXMoZmlsbCA9IHllYXJfcmVnKSkgKw0KICBnZW9tX3RleHQoYWVzKHggPSB5ZWFyX3JlZywgeSA9IG4gLSAyMDAsIGxhYmVsID0gbGJsKSwgDQogICAgICAgICAgICBwbHRfZGYgJT4lIGZpbHRlcihwY3QgPiAxLjkpLCANCiAgICAgICAgICAgIGNvbG9yID0gIndoaXRlIiwgZm9udGZhY2UgPSAiYm9sZCIpICsNCiAgZ2VvbV90ZXh0KGFlcyh4ID0geWVhcl9yZWcsIHkgPSBuICsgMjAwLCBsYWJlbCA9IGxibCksIA0KICAgICAgICAgICAgcGx0X2RmICU+JSBmaWx0ZXIocGN0IDwgMS45KSwgDQogICAgICAgICAgICBjb2xvciA9ICJncmV5MjAiLCBmb250ZmFjZSA9ICJib2xkIikgKw0KICBsYWJzKHRpdGxlID0gIlllYXIgb2YgcmVnaXN0cmF0aW9uIGF0IHRoZSBzZXJ2aWNlIiwgeSA9ICJOdW1iZXIgb2YgdW5pcXVlIHVzZXJzIiwgeCA9ICIiKSArDQogIHNjYWxlX3lfY29udGludW91cyhleHBhbmQgPSBjKDAsIDApLCBicmVha3MgPSBzZXEoMCwgNjAwMCwgMTAwMCksDQogICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBjKDAsIHBhc3RlMChzZXEoMSw2LDEpLCAiSyIpKSkgKw0KICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBwYWxldHRlZXJfYygiZ2d0aGVtZXM6OkJsdWUiLCAxNCkpICsNCiAgZ2d0aGVtZXM6OnRoZW1lX3R1ZnRlKCkgKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIsDQogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiksDQogICAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiksDQogICAgICAgIGF4aXMudGlja3MueCA9IGVsZW1lbnRfYmxhbmsoKSkNCmBgYA0KDQpgYGB7ciByZWdfeWVhcl9wbG90LCBmaWcud2lkdGg9OCwgZmlnLmhlaWdodD0zLCBldmFsPVRSVUV9DQpwbHRfZGYgPC0gbWVtYmVycyAlPiUNCiAgZ3JvdXBfYnkoeWVhcl9yZWcpICU+JQ0KICB0YWxseSgpICU+JQ0KICB1bmdyb3VwKCkgJT4lDQogIG11dGF0ZShwY3QgPSBuL3N1bShuKSoxMDAsDQogICAgICAgICBsYmwgPSBzcHJpbnRmKCIlLjFmJSUiLCBwY3QpKQ0KDQptZW1iZXJzICU+JQ0KIGdncGxvdCgpICsNCiAgYWVzKHggPSB5ZWFyX3JlZykgKw0KICBnZW9tX2JhcihhZXMoZmlsbCA9IHllYXJfcmVnKSkgKw0KICBnZW9tX3RleHQoYWVzKHggPSB5ZWFyX3JlZywgeSA9IG4gLSAyMDAsIGxhYmVsID0gbGJsKSwgDQogICAgICAgICAgICBwbHRfZGYgJT4lIGZpbHRlcihwY3QgPiAxLjkpLCANCiAgICAgICAgICAgIGNvbG9yID0gIndoaXRlIiwgZm9udGZhY2UgPSAiYm9sZCIpICsNCiAgZ2VvbV90ZXh0KGFlcyh4ID0geWVhcl9yZWcsIHkgPSBuICsgMjAwLCBsYWJlbCA9IGxibCksIA0KICAgICAgICAgICAgcGx0X2RmICU+JSBmaWx0ZXIocGN0IDwgMS45KSwgDQogICAgICAgICAgICBjb2xvciA9ICJncmV5MjAiLCBmb250ZmFjZSA9ICJib2xkIikgKw0KICBsYWJzKHRpdGxlID0gIlllYXIgb2YgcmVnaXN0cmF0aW9uIGF0IHRoZSBzZXJ2aWNlIiwgeSA9ICJOdW1iZXIgb2YgdW5pcXVlIHVzZXJzIiwgeCA9ICIiKSArDQogIHNjYWxlX3lfY29udGludW91cyhleHBhbmQgPSBjKDAsIDApLCBicmVha3MgPSBzZXEoMCwgNjAwMCwgMTAwMCksDQogICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBjKDAsIHBhc3RlMChzZXEoMSw2LDEpLCAiSyIpKSkgKw0KICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBwYWxldHRlZXJfYygiZ2d0aGVtZXM6OkJsdWUiLCAxNCkpICsNCiAgZ2d0aGVtZXM6OnRoZW1lX3R1ZnRlKCkgKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIsDQogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiksDQogICAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiksDQogICAgICAgIGF4aXMudGlja3MueCA9IGVsZW1lbnRfYmxhbmsoKSkNCmBgYA0KDQpgYGB7ciBzdXJ2cGxvdF9yZWdfeWVhcn0NCnRyYW5zX3BlcmlvZHMgPC0gdHJhbnNfcGVyaW9kcyAlPiUNCiAgbXV0YXRlKHllYXJfcmVnRyA9IGZjdF9jb2xsYXBzZSh5ZWFyX3JlZywgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIjIwMDQtMjAwOSIgPSBhcy5jaGFyYWN0ZXIoMjAwNDoyMDA5KSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiMjAxMC0yMDExIiA9IGFzLmNoYXJhY3RlcigyMDEwOjIwMTEpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICIyMDEyLTIwMTMiID0gYXMuY2hhcmFjdGVyKDIwMTI6MjAxMykpKQ0KDQpzcnZmaXQgPC0gc3VydmZpdChTdXJ2KHRpbWUsIGNodXJuKSB+IHllYXJfcmVnRywgdHJhbnNfcGVyaW9kcykNCiAgICAgICAgICAgICAgICAgIA0KcDEgPC0gZ2dzdXJ2cGxvdChzcnZmaXQsIA0KICAgICAgICAgICAgICAgICBwYWxldHRlID0gcGFsZXR0ZWVyX2MoImdndGhlbWVzOjpCbHVlIiwgbGVuZ3RoKGxldmVscyh0cmFuc19wZXJpb2RzJHllYXJfcmVnRykpKSwgDQogICAgICAgICAgICAgICAgIHNpemUgPSAxLjIsIGNvbmYuaW50ID0gRkFMU0UsDQogICAgICAgICAgICAgICAgIGxlZ2VuZC5sYWJzID0gbGV2ZWxzKHRyYW5zX3BlcmlvZHMkeWVhcl9yZWdHKSwNCiAgICAgICAgICAgICAgICAgbGVnZW5kLnRpdGxlID0gIiIsDQogICAgICAgICAgICAgICAgIGdndGhlbWUgPSB0aGVtZV9taW5pbWFsKCkgKyB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIpKSwNCiAgICAgICAgICAgICAgICAgdGl0bGUgPSAiUHJvYmFiaWxpdHkgb2YgYWN0aXZlIG1lbWJlcnNoaXAgYnlcbnVzZXIncyB5ZWFyIG9mIHJlZ2lzdHJhdGlvbiIsDQogICAgICAgICAgICAgICAgIHhsYWIgPSAiVGltZSBmcm9tIHRoZSBmaXJzdCB0cmFuc2FjdGlvbiwgbW9udGhzIiwNCiAgICAgICAgICAgICAgICAgeWxhYiA9ICJQcm9iYWJpbGl0eSBvZiBhY3RpdmUgbWVtYmVyc2hpcCIsDQogICAgICAgICAgICAgICAgIGxlZ2VuZCA9ICJib3R0b20iLCBjZW5zb3IgPSBGQUxTRSkNCg0KcDEgPC0gcDEkcGxvdCArDQogIHNjYWxlX3hfY29udGludW91cyhleHBhbmQgPSBjKDAsIDApLCBicmVha3MgPSBzZXEoMCwgODIwLCAzMC40Mzc1KjIpLA0KICAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gc2VxKDAsIDgyMC8zMC40Mzc1LCAyKSwNCiAgICAgICAgICAgICAgICAgICAgIGxpbWl0cyA9IGMoMCwgODIwKSkgKw0KICBzY2FsZV95X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLCAwKSwgYnJlYWtzID0gc2VxKDAsMSwwLjIpLA0KICAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gc2NhbGVzOjpwZXJjZW50X2Zvcm1hdChhY2N1cmFjeSA9IDEpKSArDQogIHRoZW1lX2NsYXNzaWMoKSArDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9IGMoMC45LCAwLjgpLA0KICAgICAgICBsZWdlbmQudGl0bGUgPSBlbGVtZW50X2JsYW5rKCksDQogICAgICAgIGxlZ2VuZC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLA0KICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIpLA0KICAgICAgICBwYW5lbC5ncmlkLm1ham9yID0gZWxlbWVudF9saW5lKHNpemUgPSAuMiwgY29sb3VyID0gImdyZXk5MCIpLA0KICAgICAgICBwYW5lbC5ncmlkLm1pbm9yID0gZWxlbWVudF9saW5lKHNpemUgPSAuMiwgY29sb3VyID0gImdyZXk5MCIpKQ0KDQpwMQ0KYGBgDQoNCmBgYHtyIHN1cnZwbG90X3JlZ195ZWFyX3Bsb3QsIGZpZy5oZWlnaHQ9NCwgZmlnLndpZHRoPTYsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGV2YWw9VFJVRX0NCnRyYW5zX3BlcmlvZHMgPC0gdHJhbnNfcGVyaW9kcyAlPiUNCiAgbXV0YXRlKHllYXJfcmVnRyA9IGZjdF9jb2xsYXBzZSh5ZWFyX3JlZywgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIjIwMDQtMjAwOSIgPSBhcy5jaGFyYWN0ZXIoMjAwNDoyMDA5KSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiMjAxMC0yMDExIiA9IGFzLmNoYXJhY3RlcigyMDEwOjIwMTEpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICIyMDEyLTIwMTMiID0gYXMuY2hhcmFjdGVyKDIwMTI6MjAxMykpKQ0KDQpzcnZmaXQgPC0gc3VydmZpdChTdXJ2KHRpbWUsIGNodXJuKSB+IHllYXJfcmVnRywgdHJhbnNfcGVyaW9kcykNCiAgICAgICAgICAgICAgICAgIA0KcDEgPC0gZ2dzdXJ2cGxvdChzcnZmaXQsIA0KICAgICAgICAgICAgICAgICBwYWxldHRlID0gcGFsZXR0ZWVyX2MoImdndGhlbWVzOjpCbHVlIiwgbGVuZ3RoKGxldmVscyh0cmFuc19wZXJpb2RzJHllYXJfcmVnRykpKSwgDQogICAgICAgICAgICAgICAgIHNpemUgPSAxLjIsIGNvbmYuaW50ID0gRkFMU0UsDQogICAgICAgICAgICAgICAgIGxlZ2VuZC5sYWJzID0gbGV2ZWxzKHRyYW5zX3BlcmlvZHMkeWVhcl9yZWdHKSwNCiAgICAgICAgICAgICAgICAgbGVnZW5kLnRpdGxlID0gIiIsDQogICAgICAgICAgICAgICAgIGdndGhlbWUgPSB0aGVtZV9taW5pbWFsKCkgKyB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIpKSwNCiAgICAgICAgICAgICAgICAgdGl0bGUgPSAiUHJvYmFiaWxpdHkgb2YgYWN0aXZlIG1lbWJlcnNoaXAgYnlcbnVzZXIncyB5ZWFyIG9mIHJlZ2lzdHJhdGlvbiIsDQogICAgICAgICAgICAgICAgIHhsYWIgPSAiVGltZSBmcm9tIHRoZSBmaXJzdCB0cmFuc2FjdGlvbiwgbW9udGhzIiwNCiAgICAgICAgICAgICAgICAgeWxhYiA9ICJQcm9iYWJpbGl0eSBvZiBhY3RpdmUgbWVtYmVyc2hpcCIsDQogICAgICAgICAgICAgICAgIGxlZ2VuZCA9ICJib3R0b20iLCBjZW5zb3IgPSBGQUxTRSkNCg0KcDEgPC0gcDEkcGxvdCArDQogIHNjYWxlX3hfY29udGludW91cyhleHBhbmQgPSBjKDAsIDApLCBicmVha3MgPSBzZXEoMCwgODIwLCAzMC40Mzc1KjIpLA0KICAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gc2VxKDAsIDgyMC8zMC40Mzc1LCAyKSwNCiAgICAgICAgICAgICAgICAgICAgIGxpbWl0cyA9IGMoMCwgODIwKSkgKw0KICBzY2FsZV95X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLCAwKSwgYnJlYWtzID0gc2VxKDAsMSwwLjIpLA0KICAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gc2NhbGVzOjpwZXJjZW50X2Zvcm1hdChhY2N1cmFjeSA9IDEpKSArDQogIHRoZW1lX2NsYXNzaWMoKSArDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9IGMoMC45LCAwLjgpLA0KICAgICAgICBsZWdlbmQudGl0bGUgPSBlbGVtZW50X2JsYW5rKCksDQogICAgICAgIGxlZ2VuZC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLA0KICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIpLA0KICAgICAgICBwYW5lbC5ncmlkLm1ham9yID0gZWxlbWVudF9saW5lKHNpemUgPSAuMiwgY29sb3VyID0gImdyZXk5MCIpLA0KICAgICAgICBwYW5lbC5ncmlkLm1pbm9yID0gZWxlbWVudF9saW5lKHNpemUgPSAuMiwgY29sb3VyID0gImdyZXk5MCIpKQ0KDQpwMQ0KYGBgDQoNCi0tIEluIGNvbXBhcmlzb24gd2l0aCB1c2VycyByZWdpc3RlcmVkIGluIDIwMTAtMjAxMyBuZXcgb25lcyBoYXZlIGEgbW9yZSBjb25zaWRlcmFibGUgZHJvcCBpbiBhY3RpdmUgbWVtYmVyc2hpcCBkdXJuZyB0aGUgZmlyc3QgbW9udGguDQoNCjxicj4NCg0KYGBge3IgdGltZV9mcm9tX3JlZ30NCnRyYW5zX3BlcmlvZHMgPC0gdHJhbnNfcGVyaW9kcyAlPiUNCiAgbXV0YXRlKHRpbWVfZnJvbV9yZWdHID0gY3V0KHRpbWVfZnJvbV9yZWcsIGMoMCwwLjEsIDMxLCAzNjYsIDM2NiozLCAzNjUqNSwgMzY2KjE1KSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGMoIjAiLCAiMS0zMCBkYXlzIiwgIjMxIGRheXMtMXllYXIiLCAiMS0zIHllYXJzIiwgIjMtNSB5ZWFycyIsICI+NSB5ZWFycyIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmlnaHQgPSBGQUxTRSkpDQoNCnZhcl9sYWJlbCh0cmFuc19wZXJpb2RzKSA8LSBsaXN0KHRpbWVfZnJvbV9yZWcgPSAiVGltZSBmcm9tIHVzZXIncyByZWdpc3RyYXRpb24gYXQgdGhlIHNlcnZpY2UgdG8gc3Vic2NyaXB0aW9uIHN0YXJ0LCBkYXlzIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRpbWVfZnJvbV9yZWdHID0gIlRpbWUgZnJvbSB1c2VyJ3MgcmVnaXN0cmF0aW9uIGF0IHRoZSBzZXJ2aWNlIHRvIHN1YnNjcmlwdGlvbiBzdGFydCIpDQoNCnRibF9zdW1tYXJ5KA0KICBhcy5kYXRhLmZyYW1lKHNlbGVjdCh0cmFuc19wZXJpb2RzLCB0aW1lX2Zyb21fcmVnLCB0aW1lX2Zyb21fcmVnRykpLA0KICB0eXBlID0gYWxsX2NvbnRpbnVvdXMoKSB+ICJjb250aW51b3VzMiIsDQogIHN0YXRpc3RpYyA9IGxpc3QoYWxsX2NvbnRpbnVvdXMoKSB+IGMoInttZWFufSAoe3NkfSkiLCAie21lZGlhbn0gKHtwMjV9LXtwNzV9KSIsICANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAie21pbn0te21heH0iKSksDQogIGRpZ2l0cyA9IGxpc3QoYWxsX2NvbnRpbnVvdXMoKSB+IGMocmVwKDEsIDIpLCByZXAoMCwgNSkpKSkgJT4lDQogIGJvbGRfbGFiZWxzKCkNCmBgYA0KDQpgYGB7ciB0aW1lX2Zyb21fcmVnX3RhYiwgZXZhbD1UUlVFfQ0KdHJhbnNfcGVyaW9kcyA8LSB0cmFuc19wZXJpb2RzICU+JQ0KICBtdXRhdGUodGltZV9mcm9tX3JlZ0cgPSBjdXQodGltZV9mcm9tX3JlZywgYygwLDAuMSwgMzEsIDM2NiwgMzY2KjMsIDM2NSo1LCAzNjYqMTUpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYygiMCIsICIxLTMwIGRheXMiLCAiMzEgZGF5cy0xeWVhciIsICIxLTMgeWVhcnMiLCAiMy01IHllYXJzIiwgIj41IHllYXJzIiksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICByaWdodCA9IEZBTFNFKSkNCg0KdmFyX2xhYmVsKHRyYW5zX3BlcmlvZHMpIDwtIGxpc3QodGltZV9mcm9tX3JlZyA9ICJUaW1lIGZyb20gdXNlcidzIHJlZ2lzdHJhdGlvbiBhdCB0aGUgc2VydmljZSB0byBzdWJzY3JpcHRpb24gc3RhcnQsIGRheXMiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGltZV9mcm9tX3JlZ0cgPSAiVGltZSBmcm9tIHVzZXIncyByZWdpc3RyYXRpb24gYXQgdGhlIHNlcnZpY2UgdG8gc3Vic2NyaXB0aW9uIHN0YXJ0IikNCg0KdGJsX3N1bW1hcnkoDQogIGFzLmRhdGEuZnJhbWUoc2VsZWN0KHRyYW5zX3BlcmlvZHMsIHRpbWVfZnJvbV9yZWcsIHRpbWVfZnJvbV9yZWdHKSksDQogIHR5cGUgPSBhbGxfY29udGludW91cygpIH4gImNvbnRpbnVvdXMyIiwNCiAgc3RhdGlzdGljID0gbGlzdChhbGxfY29udGludW91cygpIH4gYygie21lYW59ICh7c2R9KSIsICJ7bWVkaWFufSAoe3AyNX0te3A3NX0pIiwgIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJ7bWlufS17bWF4fSIpKSwNCiAgZGlnaXRzID0gbGlzdChhbGxfY29udGludW91cygpIH4gYyhyZXAoMSwgMiksIHJlcCgwLCA1KSkpKSAlPiUNCiAgYm9sZF9sYWJlbHMoKQ0KYGBgDQoNCmBgYHtyIHN1cnZwbG90X3RpbWVfZnJvbV9yZWd9DQpzcnZmaXQgPC0gc3VydmZpdChTdXJ2KHRpbWUsIGNodXJuKSB+IHRpbWVfZnJvbV9yZWdHLCB0cmFuc19wZXJpb2RzKQ0KICAgICAgICAgICAgICAgICAgDQpwMSA8LSBnZ3N1cnZwbG90KHNydmZpdCwgDQogICAgICAgICAgICAgICAgIHBhbGV0dGUgPSBwYWxldHRlZXJfYygiZ2d0aGVtZXM6OkJyb3duIiwgbGVuZ3RoKGxldmVscyh0cmFuc19wZXJpb2RzJHRpbWVfZnJvbV9yZWdHKSkpLCANCiAgICAgICAgICAgICAgICAgc2l6ZSA9IDEuMiwgY29uZi5pbnQgPSBGQUxTRSwNCiAgICAgICAgICAgICAgICAgbGVnZW5kLmxhYnMgPSBsZXZlbHModHJhbnNfcGVyaW9kcyR0aW1lX2Zyb21fcmVnRyksDQogICAgICAgICAgICAgICAgIGxlZ2VuZC50aXRsZSA9ICIiLA0KICAgICAgICAgICAgICAgICBnZ3RoZW1lID0gdGhlbWVfbWluaW1hbCgpICsgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiKSksDQogICAgICAgICAgICAgICAgIHRpdGxlID0gIlByb2JhYmlsaXR5IG9mIGFjdGl2ZSBtZW1iZXJzaGlwIGJ5XG50aW1lIGZyb20gcmVnaXN0cmF0aW9uIiwNCiAgICAgICAgICAgICAgICAgeGxhYiA9ICJUaW1lIGZyb20gdGhlIGZpcnN0IHRyYW5zYWN0aW9uLCBtb250aHMiLA0KICAgICAgICAgICAgICAgICB5bGFiID0gIlByb2JhYmlsaXR5IG9mIGFjdGl2ZSBtZW1iZXJzaGlwIiwNCiAgICAgICAgICAgICAgICAgbGVnZW5kID0gImJvdHRvbSIsIGNlbnNvciA9IEZBTFNFKQ0KDQpwMSA8LSBwMSRwbG90ICsNCiAgc2NhbGVfeF9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwgMCksIGJyZWFrcyA9IHNlcSgwLCA4MjAsIDMwLjQzNzUqMiksDQogICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBzZXEoMCwgODIwLzMwLjQzNzUsIDIpLA0KICAgICAgICAgICAgICAgICAgICAgbGltaXRzID0gYygwLCA4MjApKSArDQogIHNjYWxlX3lfY29udGludW91cyhleHBhbmQgPSBjKDAsIDApLCBicmVha3MgPSBzZXEoMCwxLDAuMiksDQogICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBzY2FsZXM6OnBlcmNlbnRfZm9ybWF0KGFjY3VyYWN5ID0gMSkpICsNCiAgZ3VpZGVzKGNvbG9yID0gZ3VpZGVfbGVnZW5kKG5yb3cgPSA2KSkgKw0KICB0aGVtZV9jbGFzc2ljKCkgKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSBjKDAuOSwgMC44NSksDQogICAgICAgIGxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSwNCiAgICAgICAgbGVnZW5kLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksDQogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiksDQogICAgICAgIHBhbmVsLmdyaWQubWFqb3IgPSBlbGVtZW50X2xpbmUoc2l6ZSA9IC4yLCBjb2xvdXIgPSAiZ3JleTkwIiksDQogICAgICAgIHBhbmVsLmdyaWQubWlub3IgPSBlbGVtZW50X2xpbmUoc2l6ZSA9IC4yLCBjb2xvdXIgPSAiZ3JleTkwIikpDQoNCnAxDQpgYGANCg0KYGBge3Igc3VydnBsb3RfdGltZV9mcm9tX3JlZ19wbG90LCBmaWcuaGVpZ2h0PTQsIGZpZy53aWR0aD02LCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBldmFsPVRSVUV9DQpzcnZmaXQgPC0gc3VydmZpdChTdXJ2KHRpbWUsIGNodXJuKSB+IHRpbWVfZnJvbV9yZWdHLCB0cmFuc19wZXJpb2RzKQ0KICAgICAgICAgICAgICAgICAgDQpwMSA8LSBnZ3N1cnZwbG90KHNydmZpdCwgDQogICAgICAgICAgICAgICAgIHBhbGV0dGUgPSBwYWxldHRlZXJfYygiZ2d0aGVtZXM6OkJyb3duIiwgbGVuZ3RoKGxldmVscyh0cmFuc19wZXJpb2RzJHRpbWVfZnJvbV9yZWdHKSkpLCANCiAgICAgICAgICAgICAgICAgc2l6ZSA9IDEuMiwgY29uZi5pbnQgPSBGQUxTRSwNCiAgICAgICAgICAgICAgICAgbGVnZW5kLmxhYnMgPSBsZXZlbHModHJhbnNfcGVyaW9kcyR0aW1lX2Zyb21fcmVnRyksDQogICAgICAgICAgICAgICAgIGxlZ2VuZC50aXRsZSA9ICIiLA0KICAgICAgICAgICAgICAgICBnZ3RoZW1lID0gdGhlbWVfbWluaW1hbCgpICsgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiKSksDQogICAgICAgICAgICAgICAgIHRpdGxlID0gIlByb2JhYmlsaXR5IG9mIGFjdGl2ZSBtZW1iZXJzaGlwIGJ5XG50aW1lIGZyb20gcmVnaXN0cmF0aW9uIiwNCiAgICAgICAgICAgICAgICAgeGxhYiA9ICJUaW1lIGZyb20gdGhlIGZpcnN0IHRyYW5zYWN0aW9uLCBtb250aHMiLA0KICAgICAgICAgICAgICAgICB5bGFiID0gIlByb2JhYmlsaXR5IG9mIGFjdGl2ZSBtZW1iZXJzaGlwIiwNCiAgICAgICAgICAgICAgICAgbGVnZW5kID0gImJvdHRvbSIsIGNlbnNvciA9IEZBTFNFKQ0KDQpwMSA8LSBwMSRwbG90ICsNCiAgc2NhbGVfeF9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwgMCksIGJyZWFrcyA9IHNlcSgwLCA4MjAsIDMwLjQzNzUqMiksDQogICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBzZXEoMCwgODIwLzMwLjQzNzUsIDIpLA0KICAgICAgICAgICAgICAgICAgICAgbGltaXRzID0gYygwLCA4MjApKSArDQogIHNjYWxlX3lfY29udGludW91cyhleHBhbmQgPSBjKDAsIDApLCBicmVha3MgPSBzZXEoMCwxLDAuMiksDQogICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBzY2FsZXM6OnBlcmNlbnRfZm9ybWF0KGFjY3VyYWN5ID0gMSkpICsNCiAgZ3VpZGVzKGNvbG9yID0gZ3VpZGVfbGVnZW5kKG5yb3cgPSA2KSkgKw0KICB0aGVtZV9jbGFzc2ljKCkgKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSBjKDAuOSwgMC44NSksDQogICAgICAgIGxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSwNCiAgICAgICAgbGVnZW5kLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksDQogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiksDQogICAgICAgIHBhbmVsLmdyaWQubWFqb3IgPSBlbGVtZW50X2xpbmUoc2l6ZSA9IC4yLCBjb2xvdXIgPSAiZ3JleTkwIiksDQogICAgICAgIHBhbmVsLmdyaWQubWlub3IgPSBlbGVtZW50X2xpbmUoc2l6ZSA9IC4yLCBjb2xvdXIgPSAiZ3JleTkwIikpDQoNCnAxDQpgYGANCg0KLS0gSW5pdGlhbCBzdWJzY3JpcHRpb25zICh0aG9zZSBzdGFydGVkIGF0IHRoZSBkYXkgb2YgdXNlcidzIHJlZ2lzdHJhdGlvbikgdGVuZCB0byBoYXZlIGxlYXN0IHJpc2sgb2YgYXR0cml0aW9uLCB3aGlsZSB0aG9zZSB3aGljaCBzdGFydGVkIGJldHdlZW4gMSBtb250aCBhbmQgMSB5ZWFyIGZyb20gcmVnaXN0cmF0aW9uIC0gdGhlIGhpZ2hlc3Qgb25lLg0KDQo8YnI+DQoNCmBgYHtyIHJlZ19tb250aH0NCnBsdF9kZiA8LSBtZW1iZXJzICU+JQ0KICBncm91cF9ieShtb250aF9yZWcpICU+JQ0KICB0YWxseSgpICU+JQ0KICB1bmdyb3VwKCkgJT4lDQogIG11dGF0ZShwY3QgPSBuL3N1bShuKSoxMDAsDQogICAgICAgICBsYmwgPSBzcHJpbnRmKCIlLjFmJSUiLCBwY3QpKQ0KDQptZW1iZXJzICU+JQ0KIGdncGxvdCgpICsNCiAgYWVzKHggPSBtb250aF9yZWcpICsNCiAgZ2VvbV9iYXIoYWVzKGZpbGwgPSBtb250aF9yZWcpKSArDQogIGdlb21fdGV4dChhZXMoeCA9IG1vbnRoX3JlZywgeSA9IG4gLSAxMDAsIGxhYmVsID0gbGJsKSwgDQogICAgICAgICAgICBwbHRfZGYgJT4lIGZpbHRlcihwY3QgPiAxLjkpLCANCiAgICAgICAgICAgIGNvbG9yID0gIndoaXRlIiwgZm9udGZhY2UgPSAiYm9sZCIpICsNCiAgZ2VvbV90ZXh0KGFlcyh4ID0gbW9udGhfcmVnLCB5ID0gbiArIDIwMCwgbGFiZWwgPSBsYmwpLCANCiAgICAgICAgICAgIHBsdF9kZiAlPiUgZmlsdGVyKHBjdCA8IDEuOSksIA0KICAgICAgICAgICAgY29sb3IgPSAiZ3JleTIwIiwgZm9udGZhY2UgPSAiYm9sZCIpICsNCiAgbGFicyh0aXRsZSA9ICJNb250aCBvZiByZWdpc3RyYXRpb24gYXQgdGhlIHNlcnZpY2UiLCB5ID0gIk51bWJlciBvZiB1bmlxdWUgdXNlcnMiLCB4ID0gIiIpICsNCiAgc2NhbGVfeV9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwgMCksIGJyZWFrcyA9IHNlcSgwLCA2MDAwLCA1MDApLA0KICAgICAgICAgICAgICAgICAgICAgbGltaXRzID0gYygwLCAyMDAwKSkgKw0KICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBwYWxldHRlZXJfYygiZ3JEZXZpY2VzOjpTZXQgMiIsIDEyKSkgKw0KICBnZ3RoZW1lczo6dGhlbWVfdHVmdGUoKSArDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIiwNCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiKSwNCiAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyKSwNCiAgICAgICAgYXhpcy50aWNrcy54ID0gZWxlbWVudF9ibGFuaygpKQ0KYGBgDQoNCmBgYHtyIHJlZ19tb250aF9wbG90LCBmaWcud2lkdGg9OCwgZmlnLmhlaWdodD0zLCBldmFsPVRSVUV9DQpwbHRfZGYgPC0gbWVtYmVycyAlPiUNCiAgZ3JvdXBfYnkobW9udGhfcmVnKSAlPiUNCiAgdGFsbHkoKSAlPiUNCiAgdW5ncm91cCgpICU+JQ0KICBtdXRhdGUocGN0ID0gbi9zdW0obikqMTAwLA0KICAgICAgICAgbGJsID0gc3ByaW50ZigiJS4xZiUlIiwgcGN0KSkNCg0KbWVtYmVycyAlPiUNCiBnZ3Bsb3QoKSArDQogIGFlcyh4ID0gbW9udGhfcmVnKSArDQogIGdlb21fYmFyKGFlcyhmaWxsID0gbW9udGhfcmVnKSkgKw0KICBnZW9tX3RleHQoYWVzKHggPSBtb250aF9yZWcsIHkgPSBuIC0gMTAwLCBsYWJlbCA9IGxibCksIA0KICAgICAgICAgICAgcGx0X2RmICU+JSBmaWx0ZXIocGN0ID4gMS45KSwgDQogICAgICAgICAgICBjb2xvciA9ICJ3aGl0ZSIsIGZvbnRmYWNlID0gImJvbGQiKSArDQogIGdlb21fdGV4dChhZXMoeCA9IG1vbnRoX3JlZywgeSA9IG4gKyAyMDAsIGxhYmVsID0gbGJsKSwgDQogICAgICAgICAgICBwbHRfZGYgJT4lIGZpbHRlcihwY3QgPCAxLjkpLCANCiAgICAgICAgICAgIGNvbG9yID0gImdyZXkyMCIsIGZvbnRmYWNlID0gImJvbGQiKSArDQogIGxhYnModGl0bGUgPSAiTW9udGggb2YgcmVnaXN0cmF0aW9uIGF0IHRoZSBzZXJ2aWNlIiwgeSA9ICJOdW1iZXIgb2YgdW5pcXVlIHVzZXJzIiwgeCA9ICIiKSArDQogIHNjYWxlX3lfY29udGludW91cyhleHBhbmQgPSBjKDAsIDApLCBicmVha3MgPSBzZXEoMCwgNjAwMCwgNTAwKSwNCiAgICAgICAgICAgICAgICAgICAgIGxpbWl0cyA9IGMoMCwgMjAwMCkpICsNCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gcGFsZXR0ZWVyX2MoImdyRGV2aWNlczo6U2V0IDIiLCAxMikpICsNCiAgZ2d0aGVtZXM6OnRoZW1lX3R1ZnRlKCkgKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIsDQogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiksDQogICAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiksDQogICAgICAgIGF4aXMudGlja3MueCA9IGVsZW1lbnRfYmxhbmsoKSkNCmBgYA0KDQpgYGB7ciBzdXJ2cGxvdF9yZWdfbW9udGh9DQpzcnZmaXQgPC0gc3VydmZpdChTdXJ2KHRpbWUsIGNodXJuKSB+IG1vbnRoX3JlZywgdHJhbnNfcGVyaW9kcykNCiAgICAgICAgICAgICAgICAgIA0KcDEgPC0gZ2dzdXJ2cGxvdChzcnZmaXQsIA0KICAgICAgICAgICAgICAgICBwYWxldHRlID0gcGFsZXR0ZWVyX2MoImdyRGV2aWNlczo6U2V0IDIiLCAxMiksIA0KICAgICAgICAgICAgICAgICBzaXplID0gMS4yLCBjb25mLmludCA9IEZBTFNFLA0KICAgICAgICAgICAgICAgICBsZWdlbmQubGFicyA9IGxldmVscyh0cmFuc19wZXJpb2RzJG1vbnRoX3JlZyksDQogICAgICAgICAgICAgICAgIGxlZ2VuZC50aXRsZSA9ICIiLA0KICAgICAgICAgICAgICAgICBnZ3RoZW1lID0gdGhlbWVfbWluaW1hbCgpICsgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiKSksDQogICAgICAgICAgICAgICAgIHRpdGxlID0gIlByb2JhYmlsaXR5IG9mIGFjdGl2ZSBtZW1iZXJzaGlwIGJ5XG51c2VyJ3MgbW9udGggb2YgcmVnaXN0cmF0aW9uIiwNCiAgICAgICAgICAgICAgICAgeGxhYiA9ICJUaW1lIGZyb20gdGhlIGZpcnN0IHRyYW5zYWN0aW9uLCBtb250aHMiLA0KICAgICAgICAgICAgICAgICB5bGFiID0gIlByb2JhYmlsaXR5IG9mIGFjdGl2ZSBtZW1iZXJzaGlwIiwNCiAgICAgICAgICAgICAgICAgbGVnZW5kID0gImJvdHRvbSIsIGNlbnNvciA9IEZBTFNFKQ0KDQpwMSA8LSBwMSRwbG90ICsNCiAgc2NhbGVfeF9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwgMCksIGJyZWFrcyA9IHNlcSgwLCA4MjAsIDMwLjQzNzUqMiksDQogICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBzZXEoMCwgODIwLzMwLjQzNzUsIDIpLA0KICAgICAgICAgICAgICAgICAgICAgbGltaXRzID0gYygwLCA4MjApKSArDQogIHNjYWxlX3lfY29udGludW91cyhleHBhbmQgPSBjKDAsIDApLCBicmVha3MgPSBzZXEoMCwxLDAuMiksDQogICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBzY2FsZXM6OnBlcmNlbnRfZm9ybWF0KGFjY3VyYWN5ID0gMSkpICsNCiAgZ3VpZGVzKGNvbG9yID0gZ3VpZGVfbGVnZW5kKG5yb3cgPSA2KSkgKw0KICB0aGVtZV9jbGFzc2ljKCkgKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSBjKDAuOSwgMC44KSwNCiAgICAgICAgbGVnZW5kLnRpdGxlID0gZWxlbWVudF9ibGFuaygpLA0KICAgICAgICBsZWdlbmQuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwNCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiKSwNCiAgICAgICAgcGFuZWwuZ3JpZC5tYWpvciA9IGVsZW1lbnRfbGluZShzaXplID0gLjIsIGNvbG91ciA9ICJncmV5OTAiKSwNCiAgICAgICAgcGFuZWwuZ3JpZC5taW5vciA9IGVsZW1lbnRfbGluZShzaXplID0gLjIsIGNvbG91ciA9ICJncmV5OTAiKSkNCg0KcDENCmBgYA0KDQpgYGB7ciBzdXJ2cGxvdF9yZWdfbW9udGhfcGxvdCwgZmlnLmhlaWdodD00LCBmaWcud2lkdGg9NiwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgZXZhbD1UUlVFfQ0Kc3J2Zml0IDwtIHN1cnZmaXQoU3Vydih0aW1lLCBjaHVybikgfiBtb250aF9yZWcsIHRyYW5zX3BlcmlvZHMpDQogICAgICAgICAgICAgICAgICANCnAxIDwtIGdnc3VydnBsb3Qoc3J2Zml0LCANCiAgICAgICAgICAgICAgICAgcGFsZXR0ZSA9IHBhbGV0dGVlcl9jKCJnckRldmljZXM6OlNldCAyIiwgMTIpLCANCiAgICAgICAgICAgICAgICAgc2l6ZSA9IDEuMiwgY29uZi5pbnQgPSBGQUxTRSwNCiAgICAgICAgICAgICAgICAgbGVnZW5kLmxhYnMgPSBsZXZlbHModHJhbnNfcGVyaW9kcyRtb250aF9yZWcpLA0KICAgICAgICAgICAgICAgICBsZWdlbmQudGl0bGUgPSAiIiwNCiAgICAgICAgICAgICAgICAgZ2d0aGVtZSA9IHRoZW1lX21pbmltYWwoKSArIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIikpLA0KICAgICAgICAgICAgICAgICB0aXRsZSA9ICJQcm9iYWJpbGl0eSBvZiBhY3RpdmUgbWVtYmVyc2hpcCBieVxudXNlcidzIG1vbnRoIG9mIHJlZ2lzdHJhdGlvbiIsDQogICAgICAgICAgICAgICAgIHhsYWIgPSAiVGltZSBmcm9tIHRoZSBmaXJzdCB0cmFuc2FjdGlvbiwgbW9udGhzIiwNCiAgICAgICAgICAgICAgICAgeWxhYiA9ICJQcm9iYWJpbGl0eSBvZiBhY3RpdmUgbWVtYmVyc2hpcCIsDQogICAgICAgICAgICAgICAgIGxlZ2VuZCA9ICJib3R0b20iLCBjZW5zb3IgPSBGQUxTRSkNCg0KcDEgPC0gcDEkcGxvdCArDQogIHNjYWxlX3hfY29udGludW91cyhleHBhbmQgPSBjKDAsIDApLCBicmVha3MgPSBzZXEoMCwgODIwLCAzMC40Mzc1KjIpLA0KICAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gc2VxKDAsIDgyMC8zMC40Mzc1LCAyKSwNCiAgICAgICAgICAgICAgICAgICAgIGxpbWl0cyA9IGMoMCwgODIwKSkgKw0KICBzY2FsZV95X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLCAwKSwgYnJlYWtzID0gc2VxKDAsMSwwLjIpLA0KICAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gc2NhbGVzOjpwZXJjZW50X2Zvcm1hdChhY2N1cmFjeSA9IDEpKSArDQogIGd1aWRlcyhjb2xvciA9IGd1aWRlX2xlZ2VuZChucm93ID0gNikpICsNCiAgdGhlbWVfY2xhc3NpYygpICsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gYygwLjksIDAuOCksDQogICAgICAgIGxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSwNCiAgICAgICAgbGVnZW5kLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksDQogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiksDQogICAgICAgIHBhbmVsLmdyaWQubWFqb3IgPSBlbGVtZW50X2xpbmUoc2l6ZSA9IC4yLCBjb2xvdXIgPSAiZ3JleTkwIiksDQogICAgICAgIHBhbmVsLmdyaWQubWlub3IgPSBlbGVtZW50X2xpbmUoc2l6ZSA9IC4yLCBjb2xvdXIgPSAiZ3JleTkwIikpDQoNCnAxDQpgYGANCg0KLS0gSSB3b3VsZCBub3Qgc2F5IHRoYXQgdGhlcmUgYXJlIGNvbnNpZGVyYWJsZSBkaWZmZXJlbmNlcyBpbiBwYXR0ZXJuIG9mIHN1cnZpdmFsIGN1cnZlIGRlcGVuZGluZyBvbiB0aGUgbW9udGggb2YgcmVnaXN0cmF0aW9uLg0KDQo8YnI+DQoNCiMjICoqVXNlciBsb2dzKioNCg0KPGJyPg0KDQpVc2VyIGxvZyBmaWxlcyBjb250YWluIGByIG5yb3codWxvZ3MpYCByZWNvcmRzIG9uIGRhaWx5IGxpc3RlbmluZyBiZWhhdmlvcnMgb2Ygc2FtcGxlZCB1c2VycyBmcm9tIGByIG1pbih1bG9ncyRkYXRlKWAgdG8gYHIgbWF4KHVsb2dzJGRhdGUpYC4NCg0KPGJyPg0KDQpgYGB7ciByZWNfcGVyX2RheV9zdGF0fQ0KdWxvZ3NfZGF5cyA8LSB1bG9ncyAlPiUNCiAgZ3JvdXBfYnkoZGF0ZSkgJT4lDQogIHRhbGx5KCkNCg0KdmFyX2xhYmVsKHVsb2dzX2RheXMpIDwtIGxpc3QobiA9ICJOdW1iZXIgb2YgcmVjb3JkcyBwZXIgZGF5IGluIHVzZXIgbG9ncyIpDQoNCnRibF9zdW1tYXJ5KA0KICBhcy5kYXRhLmZyYW1lKHNlbGVjdCh1bG9nc19kYXlzLCBuKSksDQogIHR5cGUgPSBhbGxfY29udGludW91cygpIH4gImNvbnRpbnVvdXMyIiwNCiAgc3RhdGlzdGljID0gbGlzdChhbGxfY29udGludW91cygpIH4gYygie21lYW59ICh7c2R9KSIsICJ7bWVkaWFufSAoe3AyNX0te3A3NX0pIiwgIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJ7bWlufS17bWF4fSIpKSwNCiAgZGlnaXRzID0gbGlzdChhbGxfY29udGludW91cygpIH4gYyhyZXAoMSwgMiksIHJlcCgwLCA1KSkpKSAlPiUNCiAgYm9sZF9sYWJlbHMoKQ0KYGBgDQoNCmBgYHtyIHJlY19wZXJfZGF5X3N0YXRfdGFiLCBldmFsPVRSVUV9DQp1bG9nc19kYXlzIDwtIHVsb2dzICU+JQ0KICBncm91cF9ieShkYXRlKSAlPiUNCiAgdGFsbHkoKQ0KDQp2YXJfbGFiZWwodWxvZ3NfZGF5cykgPC0gbGlzdChuID0gIk51bWJlciBvZiByZWNvcmRzIHBlciBkYXkgaW4gdXNlciBsb2dzIikNCg0KdGJsX3N1bW1hcnkoDQogIGFzLmRhdGEuZnJhbWUoc2VsZWN0KHVsb2dzX2RheXMsIG4pKSwNCiAgdHlwZSA9IGFsbF9jb250aW51b3VzKCkgfiAiY29udGludW91czIiLA0KICBzdGF0aXN0aWMgPSBsaXN0KGFsbF9jb250aW51b3VzKCkgfiBjKCJ7bWVhbn0gKHtzZH0pIiwgInttZWRpYW59ICh7cDI1fS17cDc1fSkiLCAgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgInttaW59LXttYXh9IikpLA0KICBkaWdpdHMgPSBsaXN0KGFsbF9jb250aW51b3VzKCkgfiBjKHJlcCgxLCAyKSwgcmVwKDAsIDUpKSkpICU+JQ0KICBib2xkX2xhYmVscygpDQpgYGANCg0KPGJyPg0KDQpgYGB7ciByZWNfcGVyX2RheV9wbHR9DQpnZ3Bsb3QodWxvZ3NfZGF5cykgKw0KICBhZXMoeCA9IGRhdGUsIHkgPSBuKSArDQogIGdlb21fbGluZShzaXplID0gMC41LCBjb2xvdXIgPSAiIzExMjQ0NiIpICsNCiAgc2NhbGVfeF9kYXRlKGRhdGVfYnJlYWtzID0gIjEgbW9udGgiLCBkYXRlX2xhYmVscyA9ICAiJVktJW0iLCANCiAgICAgICAgICAgICAgIGxpbWl0cyA9IGFzLkRhdGUoYygnMjAxNS0wMS0wMScsJzIwMTctMDMtMzEnKSkpICsNCiAgc2NhbGVfeV9jb250aW51b3VzKGxpbWl0cyA9IGMoMzAwMCwgNjAwMCksDQogICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBjKHBhc3RlMChzZXEoMyw2LDEpLCAiSyIpKSkgKw0KICBsYWJzKHRpdGxlID0gIk51bWJlciBvZiByZWNvcmRzIHBlciBkYXkgaW4gdXNlciBsb2dzIiwgeCA9ICIiLCB5ID0gIiIpICsNCiAgZ2d0aGVtZXM6OnRoZW1lX3R1ZnRlKCkgKw0KICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIpLA0KICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTAsIGFuZ2xlID0gOTAsIGhqdXN0ID0gMC41LCB2anVzdCA9IDAuNSkpDQpgYGANCg0KYGBge3IgcmVjX3Blcl9kYXlfcGx0X3Bsb3QsIGZpZy53aWR0aD05LCBmaWcuaGVpZ2h0PTQsIGV2YWw9VFJVRX0NCmdncGxvdCh1bG9nc19kYXlzKSArDQogIGFlcyh4ID0gZGF0ZSwgeSA9IG4pICsNCiAgZ2VvbV9saW5lKHNpemUgPSAwLjUsIGNvbG91ciA9ICIjMTEyNDQ2IikgKw0KICBzY2FsZV94X2RhdGUoZGF0ZV9icmVha3MgPSAiMSBtb250aCIsIGRhdGVfbGFiZWxzID0gICIlWS0lbSIsIA0KICAgICAgICAgICAgICAgbGltaXRzID0gYXMuRGF0ZShjKCcyMDE1LTAxLTAxJywnMjAxNy0wMy0zMScpKSkgKw0KICBzY2FsZV95X2NvbnRpbnVvdXMobGltaXRzID0gYygzMDAwLCA2MDAwKSwNCiAgICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IGMocGFzdGUwKHNlcSgzLDYsMSksICJLIikpKSArDQogIGxhYnModGl0bGUgPSAiTnVtYmVyIG9mIHJlY29yZHMgcGVyIGRheSBpbiB1c2VyIGxvZ3MiLCB4ID0gIiIsIHkgPSAiIikgKw0KICBnZ3RoZW1lczo6dGhlbWVfdHVmdGUoKSArDQogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiksDQogICAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCwgYW5nbGUgPSA5MCwgaGp1c3QgPSAwLjUsIHZqdXN0ID0gMC41KSkNCmBgYA0KDQo8YnI+DQoNCmBgYHtyIHVsb2dzX3BlcmlvZHN9DQp0cmFuc19wZXJpb2RzX3dpZGUgPC0gdHJhbnNfcGVyaW9kcyAlPiUNCiAgc2VsZWN0KG1zbm8sIG1zbm9fc3Vic2NyX2lkLCBzdGFydF9kYXRlLCBlbmRfZGF0ZSkgJT4lDQogIGFzLmRhdGEuZnJhbWUoKSAlPiUNCiAgcmVzaGFwZSh2Lm5hbWVzID0gYygic3RhcnRfZGF0ZSIsICJlbmRfZGF0ZSIpLCB0aW1ldmFyID0gIm1zbm9fc3Vic2NyX2lkIiwgDQogICAgICAgICAgaWR2YXIgPSAibXNubyIsIGRpcmVjdGlvbiA9ICJ3aWRlIikNCg0KdWxvZ3MgPC0gdWxvZ3MgJT4lDQogIGxlZnRfam9pbih0cmFuc19wZXJpb2RzX3dpZGUsIGJ5ID0gIm1zbm8iKQ0KDQp1bG9ncyA8LSB1bG9ncyAlPiUNCiAgbXV0YXRlKG1zbm9fc3Vic2NyX3BlcmlvZCA9IGNhc2Vfd2hlbihkYXRlIDwgc3RhcnRfZGF0ZS4xIH4gMCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRlIDw9IGVuZF9kYXRlLjEgfiAxLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGUgPCBzdGFydF9kYXRlLjIgfiAxLjUsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0ZSA8PSBlbmRfZGF0ZS4yIH4gMiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRlIDwgc3RhcnRfZGF0ZS4zIH4gMi41LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGUgPD0gZW5kX2RhdGUuMyB+IDMsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0ZSA8IHN0YXJ0X2RhdGUuNCB+IDMuNSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRlIDw9IGVuZF9kYXRlLjQgfiA0LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGUgPCBzdGFydF9kYXRlLjUgfiA0LjUsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0ZSA8PSBlbmRfZGF0ZS41IH4gNSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRlIDwgc3RhcnRfZGF0ZS42IH4gNS41LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGUgPD0gZW5kX2RhdGUuNiB+IDYsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0ZSA8IHN0YXJ0X2RhdGUuNyB+IDYuNSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRlIDw9IGVuZF9kYXRlLjcgfiA3LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFRSVUUgfiA3LjUpKQ0KDQp1bG9ncyA8LSB1bG9ncyAlPiUNCiAgbXV0YXRlKGluX3N1YnNjciA9IG1zbm9fc3Vic2NyX3BlcmlvZCAlaW4lIDE6NykNCg0KdWxvZ3NfcGVyaW9kcyA8LSB1bG9ncyAlPiUNCiAgZmlsdGVyKGluX3N1YnNjcikgJT4lDQogIHJlbmFtZShtc25vX3N1YnNjcl9pZCA9IG1zbm9fc3Vic2NyX3BlcmlvZCkgJT4lDQogIGxlZnRfam9pbih0cmFuc19wZXJpb2RzICU+JSBzZWxlY3QobXNubywgbXNub19zdWJzY3JfaWQsIHN1YnNjcl9pZCwgc3RhcnRfZGF0ZSwgZW5kX2RhdGUsIGNodXJuLCB0aW1lLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNpdHksIGNpdHlHLCBiZCwgYmRHLCBnZW5kZXIsIHJlZ2lzdGVyZWRfdmlhLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlZ2lzdHJhdGlvbl9pbml0X3RpbWUsIHllYXJfcmVnLCB5ZWFyX3JlZ0csIG1vbnRoX3JlZywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjaHVybmVkX2JlZm9yZSwgY2h1cm5lZF9iZWZvcmVfbnVtKSwgDQogICAgICAgICAgICBieSA9IGMoIm1zbm8iLCAibXNub19zdWJzY3JfaWQiKSkNCg0KdHJhbnNfcGVyaW9kcyA8LSB0cmFuc19wZXJpb2RzICU+JQ0KICBtdXRhdGUoc3Vic2NyX2dvdF9sb2dzID0gc3Vic2NyX2lkICVpbiUgdWxvZ3NfcGVyaW9kcyRzdWJzY3JfaWQpDQoNCm1lbWJlcnMgPC0gbWVtYmVycyAlPiUNCiAgbGVmdF9qb2luKHRyYW5zX3BlcmlvZHMgJT4lIA0KICAgICAgICAgICAgICBzZWxlY3QobXNubywgc3Vic2NyX2dvdF9sb2dzKSAlPiUNCiAgICAgICAgICAgICAgZ3JvdXBfYnkobXNubykgJT4lDQogICAgICAgICAgICAgIHN1bW1hcmlzZShuX3N1YnNjcl9sb2dzID0gc3VtKHN1YnNjcl9nb3RfbG9ncyksDQogICAgICAgICAgICAgICAgICAgICAgICBuX3N1YnNjcl9ub2xvZ3MgPSBzdW0oIXN1YnNjcl9nb3RfbG9ncykpICU+JQ0KICAgICAgICAgICAgICB1bmdyb3VwKCksDQogICAgICAgICAgICBieSA9ICJtc25vIikgJT4lDQogIG11dGF0ZShhbGxfc3Vic2NyX2dvdF9sb2dzID0gbl9zdWJzY3JfbG9ncyA9PSBuX3N1YnNjcikNCmBgYA0KDQpTdGF0aXN0aWNzIGFib3ZlIGNvbmNlcm5pbmcgdXNlciBsb2dzIHdhcyBlc3RpbWF0ZWQgb24gYWxsIHJlY29yZHMgaW4gdXNlciBsb2dzIGZpbGUgb24gdGhlIHNhbXBsZWQgYHIgbnJvdyhtZW1iZXJzKWAgdXNlcnMsIGJ1dCBhZnRlciB0aGF0IGZvciBlYWNoIGxvZyB3ZSBpZGVudGlmaWVkIHRvIHdoaWNoIG9mIHRoZSBwcmV2aW91c2x5IGNvbnN0cnVjdGVkIG1lbWJlcnNoaXAgcGVyaW9kcyBvZiB0aGUgY29ycmVzcG9uZGluZyB1c2VyIHRoZXkgYmVsb25nLiBUaGVyZSB3ZXJlIGByIHJvdW5kKHN1bSh1bG9ncyRtc25vX3N1YnNjcl9wZXJpb2QgPT0gMCkvbnJvdyh1bG9ncykqMTAwLDEpYCUgcmVjb3JkcyB3aGljaCB3ZXJlIGRhdGVkIGVhcmxpZXIgdGhhbiB0aGUgc3RhcnQgb2YgdGhlIHVzZXIncyBmaXJzdCBzdWJzY3JpcHRpb24gcGVyaW9kLCBgciByb3VuZChzdW0odWxvZ3MkbXNub19zdWJzY3JfcGVyaW9kID09IDcuNSkvbnJvdyh1bG9ncykqMTAwLDEpYCUgd2hpY2ggd2VudCBhZnRlciB0aGUgZXhwaXJhdGlvbiBkYXRlIGZvciB0aGUgdXNlcidzIGxhc3Qgc3Vic2NyaXB0aW9uIHBlcmlvZCwgYW5kIGByIHJvdW5kKHN1bSh1bG9ncyRtc25vX3N1YnNjcl9wZXJpb2QgJWluJSAxOjYgKyAwLjUpL25yb3codWxvZ3MpKjEwMCwxKWAlIHRoYXQgZmVsbCBiZXR3ZWVuIHVzZXIncyBzdWJzY3JpcHRpb24gcGVyaW9kcy4NCg0KVGFraW5nIGludG8gYWNjb3VudCB0aGUgc3R1ZHkgbmF0dXJlIG9mIHRoaXMgcHJvamVjdCwgb3VyIGFzc3VtcHRpb24gdGhhdCBhY3RpdmUgbWVtYmVyc2hpcCBiZWdhbiBhdCB0aGUgZGF0ZSBvZiB0aGUgZmlyc3Qgc3Vic2NyaXB0aW9uIGZvciBlYWNoIHVzZXIsIGFzIHdlbGwgYXMgYWxsIGV4YW1wbGVzIHRoYXQgY29uZmlybSB0aGUgZGlydGluZXNzIG9mIHRoZSBkYXRhLCBhbmQgcmVsYXRpdmVseSBzbWFsbCBhbW91bnQgb2YgbG9ncyBpbiBiZXR3ZWVuIHN1YnNjcmlwdGlvbnMsIHdlIGhhdmUgZGVjaWRlZCB0byBvbWl0IGFsbCByZWNvcmRzIG91dHNpZGUgdGhlIGlkZW50aWZpZWQgc3Vic2NyaXB0aW9uIHBlcmlvZHMuDQoNCkFzIGEgcmVzdWx0LCBgciBucm93KHVsb2dzX3BlcmlvZHMpYCByZWNvcmRzIHJlbWFpbmVkLiBUaGV5IGNvbnRhaW4gZGF0YSBvbiBgciBsZW5ndGgodW5pcXVlKHVsb2dzX3BlcmlvZHMkbXNubykpYCB1bmlxdWUgdXNlcnMgKGByIHJvdW5kKGxlbmd0aCh1bmlxdWUodWxvZ3NfcGVyaW9kcyRtc25vKSkvbnJvdyhtZW1iZXJzKSoxMDAsMSlgJSBvdXQgb2YgaW5pdGlhbGx5IHNhbXBsZWQpIGFuZCBgciBzdW0odHJhbnNfcGVyaW9kcyRzdWJzY3JfZ290X2xvZ3MpYCB1bmlxdWUgc3Vic2NyaXB0aW9uIHBlcmlvZHMgKGByIHJvdW5kKHN1bSh0cmFuc19wZXJpb2RzJHN1YnNjcl9nb3RfbG9ncykvbnJvdyh0cmFuc19wZXJpb2RzKSoxMDAsMSlgJSBvZiBhbGwgaWRlbnRpZmllZCBwZXJpb2RzKS4gRm9yIHRoZSByZXN0IHVzZXJzIGFuZCBwZXJpb2RzIHdlIHdpbGwgYXNzdW1lIHRoYXQgdGhleSB3ZXJlIHdpdGhvdXQgbG9nZ2luZyBpbnRvIHRoZSBzZXJ2aWNlIGFueSB0aW1lLg0KDQo8YnI+DQoNCmBgYHtyIHJlY19wZXJfaWRfc3RhdH0NCnVsb2dzX3N0YXQgPC0gdWxvZ3MgJT4lDQogIGdyb3VwX2J5KG1zbm8pICU+JQ0KICBzdW1tYXJpc2UobiA9IHN1bShpbl9zdWJzY3IpKQ0KDQp2YXJfbGFiZWwodWxvZ3Nfc3RhdCkgPC0gbGlzdChuID0gIk51bWJlciBvZiByZWNvcmRzIGluIHVzZXIgbG9ncyBwZXIgSUQiKQ0KDQp0Ymxfc3VtbWFyeSgNCiAgYXMuZGF0YS5mcmFtZShzZWxlY3QodWxvZ3Nfc3RhdCwgbikpLA0KICB0eXBlID0gYWxsX2NvbnRpbnVvdXMoKSB+ICJjb250aW51b3VzMiIsDQogIHN0YXRpc3RpYyA9IGxpc3QoYWxsX2NvbnRpbnVvdXMoKSB+IGMoInttZWFufSAoe3NkfSkiLCAie21lZGlhbn0gKHtwMjV9LXtwNzV9KSIsICANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAie21pbn0te21heH0iKSksDQogIGRpZ2l0cyA9IGxpc3QoYWxsX2NvbnRpbnVvdXMoKSB+IGMocmVwKDEsIDIpLCByZXAoMCwgNSkpKSkgJT4lDQogIGJvbGRfbGFiZWxzKCkNCmBgYA0KDQpgYGB7ciByZWNfcGVyX2lkX3N0YXRfdGFiLCBldmFsPVRSVUV9DQp1bG9nc19zdGF0IDwtIHVsb2dzICU+JQ0KICBncm91cF9ieShtc25vKSAlPiUNCiAgc3VtbWFyaXNlKG4gPSBzdW0oaW5fc3Vic2NyKSkNCg0KdmFyX2xhYmVsKHVsb2dzX3N0YXQpIDwtIGxpc3QobiA9ICJOdW1iZXIgb2YgcmVjb3JkcyBpbiB1c2VyIGxvZ3MgcGVyIElEIikNCg0KdGJsX3N1bW1hcnkoDQogIGFzLmRhdGEuZnJhbWUoc2VsZWN0KHVsb2dzX3N0YXQsIG4pKSwNCiAgdHlwZSA9IGFsbF9jb250aW51b3VzKCkgfiAiY29udGludW91czIiLA0KICBzdGF0aXN0aWMgPSBsaXN0KGFsbF9jb250aW51b3VzKCkgfiBjKCJ7bWVhbn0gKHtzZH0pIiwgInttZWRpYW59ICh7cDI1fS17cDc1fSkiLCAgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgInttaW59LXttYXh9IikpLA0KICBkaWdpdHMgPSBsaXN0KGFsbF9jb250aW51b3VzKCkgfiBjKHJlcCgxLCAyKSwgcmVwKDAsIDUpKSkpICU+JQ0KICBib2xkX2xhYmVscygpDQpgYGANCg0KPGJyPg0KDQpBbGwgc3RhdGlzdGljcyBvbiB1c2VyIGxvZ3MgYmVsb3cgd2lsbCBjb25jZXJuIG9ubHkgcmVjb3JkcyByZW1haW5lZCBhZnRlciBleGNsdXNpb24gb2YgcmVjb3JkcyBvdXRzaWRlIG9mIHVzZXJzJyBzdWJzY3JpcHRpb24gcGVyaW9kcy4gDQoNCjxicj4NCg0KLS0gRm9yIGByIHJvdW5kKHN1bSh1bG9nc19wZXJpb2RzJHRpbWVfZnJvbV9wcmV2X2RhdGUgPT0gMSwgbmEucm0gPSBUUlVFKS8gc3VtKCFpcy5uYSh1bG9nc19wZXJpb2RzJHRpbWVfZnJvbV9wcmV2X2RhdGUpKSoxMDAsMSlgJSBvZiBhbGwgdXNlcnMnIHN1Y2Nlc3NpdmUgcmVjb3JkcyBpbiB1c2VyIGxvZ3MgdGhlIGJyZWFrIGJldHdlZW4gdGhlbSB3YXMgMSBkYXkuDQoNCmBgYHtyIHVsb2dzX2JyZWFrc19pZH0NCnVsb2dzX2JyZWFrcyA8LSB1bG9nc19wZXJpb2RzICU+JQ0KICBmaWx0ZXIoIWlzLm5hKHRpbWVfZnJvbV9wcmV2X2RhdGUpKSAlPiUNCiAgZ3JvdXBfYnkobXNubykgJT4lDQogIHN1bW1hcmlzZShtZWFuX2JyZWFrID0gbWVhbih0aW1lX2Zyb21fcHJldl9kYXRlKSwNCiAgICAgICAgICAgIG1lZGlhbl9icmVhayA9IG1lZGlhbih0aW1lX2Zyb21fcHJldl9kYXRlKSkgJT4lDQogIHVuZ3JvdXAoKQ0KDQp2YXJfbGFiZWwodWxvZ3NfYnJlYWtzKSA8LSBsaXN0KG1lYW5fYnJlYWsgPSAiTWVhbiBicmVhayBiZXR3ZWVuIGxvZ3MgZm9yIG9uZSBJRCwgZGF5cyIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1lZGlhbl9icmVhayA9ICJNZWRpYW4gYnJlYWsgYmV0d2VlbiBsb2dzIGZvciBvbmUgSUQsIGRheXMiKQ0KDQp0Ymxfc3VtbWFyeSgNCiAgYXMuZGF0YS5mcmFtZShzZWxlY3QodWxvZ3NfYnJlYWtzLCBtZWFuX2JyZWFrLCBtZWRpYW5fYnJlYWspKSwNCiAgdHlwZSA9IGFsbF9jb250aW51b3VzKCkgfiAiY29udGludW91czIiLA0KICBzdGF0aXN0aWMgPSBsaXN0KGFsbF9jb250aW51b3VzKCkgfiBjKCJ7bWVhbn0gKHtzZH0pIiwgInttZWRpYW59ICh7cDI1fS17cDc1fSkiLCAgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgInttaW59LXttYXh9IikpLA0KICBkaWdpdHMgPSBsaXN0KGFsbF9jb250aW51b3VzKCkgfiBjKHJlcCgxLCAyKSwgcmVwKDAsIDUpKSkpICU+JQ0KICBib2xkX2xhYmVscygpDQpgYGANCg0KYGBge3IgdWxvZ3NfYnJlYWtzX2lkX3RhYiwgZXZhbD1UUlVFfQ0KdWxvZ3NfYnJlYWtzIDwtIHVsb2dzX3BlcmlvZHMgJT4lDQogIGZpbHRlcighaXMubmEodGltZV9mcm9tX3ByZXZfZGF0ZSkpICU+JQ0KICBncm91cF9ieShtc25vKSAlPiUNCiAgc3VtbWFyaXNlKG1lYW5fYnJlYWsgPSBtZWFuKHRpbWVfZnJvbV9wcmV2X2RhdGUpLA0KICAgICAgICAgICAgbWVkaWFuX2JyZWFrID0gbWVkaWFuKHRpbWVfZnJvbV9wcmV2X2RhdGUpKSAlPiUNCiAgdW5ncm91cCgpDQoNCnZhcl9sYWJlbCh1bG9nc19icmVha3MpIDwtIGxpc3QobWVhbl9icmVhayA9ICJNZWFuIGJyZWFrIGJldHdlZW4gbG9ncyBmb3Igb25lIElELCBkYXlzIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWVkaWFuX2JyZWFrID0gIk1lZGlhbiBicmVhayBiZXR3ZWVuIGxvZ3MgZm9yIG9uZSBJRCwgZGF5cyIpDQoNCnRibF9zdW1tYXJ5KA0KICBhcy5kYXRhLmZyYW1lKHNlbGVjdCh1bG9nc19icmVha3MsIG1lYW5fYnJlYWssIG1lZGlhbl9icmVhaykpLA0KICB0eXBlID0gYWxsX2NvbnRpbnVvdXMoKSB+ICJjb250aW51b3VzMiIsDQogIHN0YXRpc3RpYyA9IGxpc3QoYWxsX2NvbnRpbnVvdXMoKSB+IGMoInttZWFufSAoe3NkfSkiLCAie21lZGlhbn0gKHtwMjV9LXtwNzV9KSIsICANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAie21pbn0te21heH0iKSksDQogIGRpZ2l0cyA9IGxpc3QoYWxsX2NvbnRpbnVvdXMoKSB+IDEpKSAlPiUNCiAgYm9sZF9sYWJlbHMoKQ0KYGBgDQoNCjxicj4NCg0KLSAgICoqYG51bV8yNWAsIGBudW1fNTBgLCBgbnVtXzc1YCwgYG51bV85ODVgLCBgbnVtXzEwMGAqKiAtIG51bWJlciBvZiBzb25ncyBwbGF5ZWQgbGVzcyB0aGFuIDI1JSBvZiB0aGUgc29uZyBsZW5ndGgsIGJldHdlZW4gMjUlIHRvIDUwJSwgYmV0d2VlbiA1MCUgdG8gNzUlLCBiZXR3ZWVuIDc1JSB0byA5OC41JSBhbmQgb3ZlciA5OC41JSBvZiB0aGUgc29uZyBsZW5ndGgsIGNvcnJlc3BvbmRpbmdseS4NCg0KICAgIEkndmUgc3VtbWVkIHVwIGFsbCB0aGVzZSBudW1iZXJzIHRvIG9idGFpbiB0aGUgdG90YWwgbnVtYmVyIG9mIHNvbmdzIGxpc3RlbmVkIGJ5IGEgY2xpZW50IC0gKipgbnVtX3RvdGAqKi4gVGhlcmUgYXJlIG5vIHJlY29yZHMgd2l0aCB6ZXJvIHZhbHVlcyAtIGl0IGNvbmZpcm1zIHRoYXQgbG9ncyB3ZXJlIHdyaXR0ZW4gb25seSBmb3IgYWN0aXZlIHVzZXJzLg0KDQogICAgSW4gYWRkaXRpb24sIEkndmUgZXN0aW1hdGVkIHRoZSBzaGFyZSBvZiBlYWNoIHNvbmdzJyBsZW5ndGggZ3JvdXAgaW4gdGhlIHRvdGFsIG51bWJlciBvZiBzb25ncyBsaXN0ZW5lZCAoKipgc2hfMjVgLCBgc2hfNTBgLCBgc2hfNzVgLCBgc2hfOTg1YCwgYHNoXzEwMGAqKikgYXMgJFx0ZXh0e3NoX1h9ID0gXHRleHR7bnVtX3h9L1x0ZXh0e251bV90b3R9JCBhbmQgYXZlcmFnZSAlIG9mIG9uZSBzb25nJ3MgbGVuZ3RoIHBsYXllZCAoKipgYXZfcGN0X3BsYXllZGAqKikgYXMgJCRcdGV4dHthdl9wY3RfcGxheWVkfSA9IFxmcmFje1x0ZXh0e251bV8yNX0qMC4xMjUgKyBcdGV4dHtudW1fNTB9KjAuMzc1ICsgXHRleHR7bnVtXzc1fSowLjYyNSArIFxcXHRleHR7bnVtXzk4NX0qMC44Njc1ICsgXHRleHR7bnVtXzEwMH0qMC45OTI1fXtcdGV4dHtudW1fdG90fX0qMTAwJSQkDQoNCjxicj4NCg0KLS0gSGVyZSBpcyBhbiBleGFtcGxlIG9mIHNldmVyYWwgdXNlcnMnIGFjdGl2aXR5IGR1cmluZyB0aGVpciBzdWJzY3JpcHRpb24gcGVyaW9kcyBieSB0aGUgdG90YWwgbnVtYmVyIG9mIHNvbmdzIHBsYXllZDoNCg0KYGBge3Igc2FtcF91c2Vyc19hY3Rpdml0eX0NCm1zbm9fc2FtcGxlX2FjdCA8LSBtZW1iZXJzICU+JQ0KICBzZWxlY3QobXNubywgbl9zdWJzY3IsIG5fc3Vic2NyX2xvZ3MpICU+JQ0KICBncm91cF9ieShuX3N1YnNjciwgbl9zdWJzY3JfbG9ncykgJT4lDQogIGZpbHRlcihyb3dfbnVtYmVyKCkgPT0gMSkNCg0KbXNub19zYW1wbGVfYWN0IDwtIHRyYW5zX3BlcmlvZHMgJT4lDQogIHNlbGVjdChtc25vLCBzdWJzY3JfaWQsIHN0YXJ0X2RhdGUsIGVuZF9kYXRlKSAlPiUNCiAgZmlsdGVyKG1zbm8gJWluJSBtc25vX3NhbXBsZV9hY3QkbXNubykgJT4lDQogIGxlZnRfam9pbih1bG9nc19wZXJpb2RzICU+JSBzZWxlY3Qoc3Vic2NyX2lkLCBkYXRlLCBudW1fdG90LCBjaHVybiksIGJ5ID0gInN1YnNjcl9pZCIpICU+JQ0KICBtdXRhdGUobXNub19pZCA9IGZhY3Rvcihtc25vLCB1bmlxdWUobXNub19zYW1wbGVfYWN0JG1zbm8pLCAxOmxlbmd0aCh1bmlxdWUobXNub19zYW1wbGVfYWN0JG1zbm8pKSkpDQoNCmdncGxvdCgpICsNCiAgZ2VvbV9zZWdtZW50KGFlcyh4ID0gZGF0ZSwgeGVuZCA9IGRhdGUgKyAxLCB5ID0gbXNub19pZCwgeWVuZCA9IG1zbm9faWQsIGNvbG9yID0gbnVtX3RvdCksIA0KICAgICAgICAgICAgICAgbXNub19zYW1wbGVfYWN0ICU+JSBmaWx0ZXIoIWlzLm5hKG51bV90b3QpKSwgc2l6ZSA9IDgpICsNCiAgc2NhbGVfY29sb3JfZ3JhZGllbnQobG93ID0gYnJld2VyLnBhbCg5LCAiUmVkcyIpWzFdLCBoaWdoID0gYnJld2VyLnBhbCg5LCAiUmVkcyIpWzldLA0KICAgICAgICAgICAgICAgICAgICAgICBicmVha3MgPSBzZXEoMCwgMjUwLCA1MCksIA0KICAgICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBzZXEoMCwgMjUwLCA1MCkpICsNCiAgZ2VvbV9wb2ludChhZXMoeCA9IHN0YXJ0X2RhdGUsIHkgPSBtc25vX2lkLCBzaGFwZSA9ICJTdGFydCBvZiBzdWJzY3IuIiksIG1zbm9fc2FtcGxlX2FjdCwNCiAgICAgICAgICAgICBjb2xvciA9ICIjMzU5NzhGIiwgc2l6ZSA9IDEpICsNCiAgZ2VvbV9wb2ludChhZXMoeCA9IGVuZF9kYXRlLCB5ID0gbXNub19pZCwgc2hhcGUgPSAiRW5kIG9mIHN1YnNjci4iKSwgbXNub19zYW1wbGVfYWN0LA0KICAgICAgICAgICAgIGNvbG9yID0gIiNGREFFNjEiLCBzaXplID0gMSkgKw0KICBzY2FsZV9zaGFwZV9tYW51YWwodmFsdWVzID0gYygxOSwgMTUpLCBicmVha3MgPSBjKCJTdGFydCBvZiBzdWJzY3IuIiwgIkVuZCBvZiBzdWJzY3IuIikpICsNCiAgc2NhbGVfeF9kYXRlKGRhdGVfYnJlYWtzID0gIjEgbW9udGgiLCBkYXRlX2xhYmVscyA9ICAiJVktJW0iLA0KICAgICAgICAgICAgICAgZXhwYW5kID0gYygwLjAxLDApKSArDQogIGxhYnMoeCA9ICIiLCB5ID0gIlVzZXIiLCBjb2xvciA9ICJUb3QubnVtLm9mIHNvbmdzIiwgdGl0bGUgPSAiU2FtcGxlZCB1c2VycycgZGFpbHkgYWN0aXZpdHkiLCBzaGFwZSA9ICIiKSArDQogIGd1aWRlcyhzaGFwZSA9IGd1aWRlX2xlZ2VuZChvdmVycmlkZS5hZXMgPSBsaXN0KGNvbG9yID0gYygiIzM1OTc4RiIsICIjRkRBRTYxIiksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNpemUgPSAzKSkpICsNCiAgZ2d0aGVtZXM6OnRoZW1lX3R1ZnRlKCkgKw0KICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIpLA0KICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChzaXplID0gOSwgYW5nbGUgPSA5MCwgaGp1c3QgPSAwLjUsIHZqdXN0ID0gMC41KSkNCmBgYA0KDQpgYGB7ciBzYW1wX3VzZXJzX2FjdGl2aXR5X3Bsb3QsIGZpZy53aWR0aD0xMCwgZmlnLmhlaWdodD02LCBmaWcuYWxpZ249J2NlbnRlcicsIGV2YWw9VFJVRX0NCm1zbm9fc2FtcGxlX2FjdCA8LSBtZW1iZXJzICU+JQ0KICBzZWxlY3QobXNubywgbl9zdWJzY3IsIG5fc3Vic2NyX2xvZ3MpICU+JQ0KICBncm91cF9ieShuX3N1YnNjciwgbl9zdWJzY3JfbG9ncykgJT4lDQogIGZpbHRlcihyb3dfbnVtYmVyKCkgPT0gMSkNCg0KbXNub19zYW1wbGVfYWN0IDwtIHRyYW5zX3BlcmlvZHMgJT4lDQogIHNlbGVjdChtc25vLCBzdWJzY3JfaWQsIHN0YXJ0X2RhdGUsIGVuZF9kYXRlKSAlPiUNCiAgZmlsdGVyKG1zbm8gJWluJSBtc25vX3NhbXBsZV9hY3QkbXNubykgJT4lDQogIGxlZnRfam9pbih1bG9nc19wZXJpb2RzICU+JSBzZWxlY3Qoc3Vic2NyX2lkLCBkYXRlLCBudW1fdG90LCBjaHVybiksIGJ5ID0gInN1YnNjcl9pZCIpICU+JQ0KICBtdXRhdGUobXNub19pZCA9IGZhY3Rvcihtc25vLCB1bmlxdWUobXNub19zYW1wbGVfYWN0JG1zbm8pLCAxOmxlbmd0aCh1bmlxdWUobXNub19zYW1wbGVfYWN0JG1zbm8pKSkpDQoNCmdncGxvdCgpICsNCiAgZ2VvbV9zZWdtZW50KGFlcyh4ID0gZGF0ZSwgeGVuZCA9IGRhdGUgKyAxLCB5ID0gbXNub19pZCwgeWVuZCA9IG1zbm9faWQsIGNvbG9yID0gbnVtX3RvdCksIA0KICAgICAgICAgICAgICAgbXNub19zYW1wbGVfYWN0ICU+JSBmaWx0ZXIoIWlzLm5hKG51bV90b3QpKSwgc2l6ZSA9IDgpICsNCiAgc2NhbGVfY29sb3JfZ3JhZGllbnQobG93ID0gYnJld2VyLnBhbCg5LCAiUmVkcyIpWzFdLCBoaWdoID0gYnJld2VyLnBhbCg5LCAiUmVkcyIpWzldLA0KICAgICAgICAgICAgICAgICAgICAgICBicmVha3MgPSBzZXEoMCwgMjUwLCA1MCksIA0KICAgICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBzZXEoMCwgMjUwLCA1MCkpICsNCiAgZ2VvbV9wb2ludChhZXMoeCA9IHN0YXJ0X2RhdGUsIHkgPSBtc25vX2lkLCBzaGFwZSA9ICJTdGFydCBvZiBzdWJzY3IuIiksIG1zbm9fc2FtcGxlX2FjdCwNCiAgICAgICAgICAgICBjb2xvciA9ICIjMzU5NzhGIiwgc2l6ZSA9IDEpICsNCiAgZ2VvbV9wb2ludChhZXMoeCA9IGVuZF9kYXRlLCB5ID0gbXNub19pZCwgc2hhcGUgPSAiRW5kIG9mIHN1YnNjci4iKSwgbXNub19zYW1wbGVfYWN0LA0KICAgICAgICAgICAgIGNvbG9yID0gIiNGREFFNjEiLCBzaXplID0gMSkgKw0KICBzY2FsZV9zaGFwZV9tYW51YWwodmFsdWVzID0gYygxOSwgMTUpLCBicmVha3MgPSBjKCJTdGFydCBvZiBzdWJzY3IuIiwgIkVuZCBvZiBzdWJzY3IuIikpICsNCiAgc2NhbGVfeF9kYXRlKGRhdGVfYnJlYWtzID0gIjEgbW9udGgiLCBkYXRlX2xhYmVscyA9ICAiJVktJW0iLA0KICAgICAgICAgICAgICAgZXhwYW5kID0gYygwLjAxLDApKSArDQogIGxhYnMoeCA9ICIiLCB5ID0gIlVzZXIiLCBjb2xvciA9ICJUb3QubnVtLm9mIHNvbmdzIiwgdGl0bGUgPSAiU2FtcGxlZCB1c2VycycgZGFpbHkgYWN0aXZpdHkiLCBzaGFwZSA9ICIiKSArDQogIGd1aWRlcyhzaGFwZSA9IGd1aWRlX2xlZ2VuZChvdmVycmlkZS5hZXMgPSBsaXN0KGNvbG9yID0gYygiIzM1OTc4RiIsICIjRkRBRTYxIiksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNpemUgPSAzKSkpICsNCiAgZ2d0aGVtZXM6OnRoZW1lX3R1ZnRlKCkgKw0KICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIpLA0KICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChzaXplID0gOSwgYW5nbGUgPSA5MCwgaGp1c3QgPSAwLjUsIHZqdXN0ID0gMC41KSkNCmBgYA0KDQo8YnI+DQoNCi0gKipgbnVtX3VucWAqKiAtIG51bWJlciBvZiB1bmlxdWUgc29uZ3MgcGxheWVkLg0KDQogICAgSSd2ZSBkaXZpZGVkIHRoZSB0b3RhbCBudW1iZXIgb2Ygc29uZ3MgYnkgdGhlIG51bWJlciBvZiB1bmlxdWUgc29uZ3MgcGxheWVkIHRvIG9idGFpbiB0aGUgYXZlcmFnZSBudW1iZXIgb2YgdGltZXMgb2YgbGlzdGVuaW5nIHRvIG9uZSBzb25nICgqKmBhdl90aW1lc19wbGF5ZWRgKiopLiBJbiBhZGRpdGlvbiwgSSd2ZSBjYWxjdWxhdGVkIHRoZSBkaWZmZXJlbmNlIGJldHdlZW4gdGhlIHRvdGFsIG51bWJlciBvZiBzb25ncyBhbmQgdGhlIG51bWJlciBvZiB1bmlxdWUgc29uZ3MgcGxheWVkIHRvIG9idGFpbiB0aGUgbnVtYmVyIG9mIHJlcGVhdHMgb2YgdGhlIHNhbWUgc29uZ3MgKCoqYG51bV9yZXBlYXRzYCoqKS4NCg0KPGJyPg0KDQpgYGB7ciBudW1fc29uZ3NfcGx0MX0NCnAxIDwtIHVsb2dzX3BlcmlvZHMgJT4lDQogIGZpbHRlcihudW1fMjUgPCBxdWFudGlsZSh1bG9nc19wZXJpb2RzJG51bV8yNSwgMC45OSkpICU+JQ0KICBnZ3Bsb3QoKSArDQogIGFlcyh4ID0gbnVtXzI1KSArDQogIGdlb21faGlzdG9ncmFtKGJpbndpZHRoID0gMiwgZmlsbCA9ICIjNDY4MkI0IikgKw0KICBsYWJzKHggPSAiIiwgeSA9ICIiLCB0aXRsZSA9IHBhc3RlMCh2YXJfbGFiZWwodWxvZ3NfcGVyaW9kcykkbnVtXzI1LCAiXG4ob2JzZXJ2YXRpb25zIHdpdGggdmFsdWVzIGxlc3MgdGhhbiA5OXRoIHBlcmNlbnRpbGUpIikpICsNCiAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IHNlcSgwLCA2MCwgMTApKSArDQogIHNjYWxlX3lfY29udGludW91cyhleHBhbmQgPSBjKDAsMCksIGJyZWFrcyA9IHNlcSgwLCAxNTAwMDAwLCAzMDAwMDApLA0KICAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gYygwLCBwYXN0ZTAoc2VxKDMwMCwxNTAwLDMwMCksICJLIikpKSArDQogIGdndGhlbWVzOjp0aGVtZV90dWZ0ZSgpICsNCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiKSwNCiAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyKSkNCg0KcDIgPC0gdWxvZ3NfcGVyaW9kcyAlPiUNCiAgZmlsdGVyKG51bV81MCA8IHF1YW50aWxlKHVsb2dzX3BlcmlvZHMkbnVtXzUwLCAwLjk5KSkgJT4lDQogIGdncGxvdCgpICsNCiAgYWVzKHggPSBudW1fNTApICsNCiAgZ2VvbV9oaXN0b2dyYW0oYmlud2lkdGggPSAxLCBmaWxsID0gIiM0NjgyQjQiKSArDQogIGxhYnMoeCA9ICIiLCB5ID0gIiIsIHRpdGxlID0gcGFzdGUwKHZhcl9sYWJlbCh1bG9nc19wZXJpb2RzKSRudW1fNTAsICJcbihvYnNlcnZhdGlvbnMgd2l0aCB2YWx1ZXMgbGVzcyB0aGFuIDk5dGggcGVyY2VudGlsZSkiKSkgKw0KICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gc2VxKDAsIDE2LCAxKSkgKw0KICBzY2FsZV95X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLDApLCBicmVha3MgPSBzZXEoMCwxNjAwMDAwLDQwMDAwMCksDQogICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBjKDAsIHBhc3RlMChzZXEoNDAwLDE2MDAsNDAwKSwgIksiKSkpICsNCiAgZ2d0aGVtZXM6OnRoZW1lX3R1ZnRlKCkgKw0KICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIpLA0KICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIpKQ0KDQpwMmEgPC0gdWxvZ3NfcGVyaW9kcyAlPiUNCiAgZmlsdGVyKG51bV83NSA8IHF1YW50aWxlKHVsb2dzX3BlcmlvZHMkbnVtXzc1LCAwLjk5KSkgJT4lDQogIGdncGxvdCgpICsNCiAgYWVzKHggPSBudW1fNzUpICsNCiAgZ2VvbV9oaXN0b2dyYW0oYmlud2lkdGggPSAxLCBmaWxsID0gIiM0NjgyQjQiKSArDQogIGxhYnMoeCA9ICIiLCB5ID0gIiIsIHRpdGxlID0gcGFzdGUwKHZhcl9sYWJlbCh1bG9nc19wZXJpb2RzKSRudW1fNzUsICJcbihvYnNlcnZhdGlvbnMgd2l0aCB2YWx1ZXMgbGVzcyB0aGFuIDk5dGggcGVyY2VudGlsZSkiKSkgKw0KICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gc2VxKDAsIDE2LCAxKSkgKw0KICBzY2FsZV95X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLDApLCBicmVha3MgPSBzZXEoMCwyMDAwMDAwLDUwMDAwMCksDQogICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBjKDAsIHBhc3RlMChzZXEoNTAwLDIwMDAsNTAwKSwgIksiKSkpICsNCiAgZ2d0aGVtZXM6OnRoZW1lX3R1ZnRlKCkgKw0KICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIpLA0KICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIpKQ0KDQpwMyA8LSANCnVsb2dzX3BlcmlvZHMgJT4lDQogIGZpbHRlcihudW1fOTg1IDwgcXVhbnRpbGUodWxvZ3NfcGVyaW9kcyRudW1fOTg1LCAwLjk5KSkgJT4lDQogIGdncGxvdCgpICsNCiAgYWVzKHggPSBudW1fOTg1KSArDQogIGdlb21faGlzdG9ncmFtKGJpbndpZHRoID0gMSwgZmlsbCA9ICIjNDY4MkI0IikgKw0KICBsYWJzKHggPSAiIiwgeSA9ICIiLCB0aXRsZSA9IHBhc3RlMCh2YXJfbGFiZWwodWxvZ3NfcGVyaW9kcykkbnVtXzk4NSwgIlxuKG9ic2VydmF0aW9ucyB3aXRoIHZhbHVlcyBsZXNzIHRoYW4gOTl0aCBwZXJjZW50aWxlKSIpKSArDQogIHNjYWxlX3hfY29udGludW91cyhicmVha3MgPSBzZXEoMCwgMTYsIDEpKSArDQogIHNjYWxlX3lfY29udGludW91cyhleHBhbmQgPSBjKDAsMCksIGJyZWFrcyA9IHNlcSgwLDIwMDAwMDAsNTAwMDAwKSwNCiAgICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IGMoMCwgcGFzdGUwKHNlcSg1MDAsMjAwMCw1MDApLCAiSyIpKSwNCiAgICAgICAgICAgICAgICAgICAgIGxpbWl0cyA9IGMoMCwgMjAwMDAwMCkpICsNCiAgZ2d0aGVtZXM6OnRoZW1lX3R1ZnRlKCkgKw0KICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIpLA0KICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIpKQ0KDQpwNCA8LSB1bG9nc19wZXJpb2RzICU+JQ0KICBmaWx0ZXIobnVtXzEwMCA8IHF1YW50aWxlKHVsb2dzX3BlcmlvZHMkbnVtXzEwMCwgMC45OSkpICU+JQ0KICBnZ3Bsb3QoKSArDQogIGFlcyh4ID0gbnVtXzEwMCkgKw0KICBnZW9tX2hpc3RvZ3JhbShiaW53aWR0aCA9IDUsIGZpbGwgPSAiIzQ2ODJCNCIpICsNCiAgbGFicyh4ID0gIiIsIHkgPSAiIiwgdGl0bGUgPSBwYXN0ZTAodmFyX2xhYmVsKHVsb2dzX3BlcmlvZHMpJG51bV8xMDAsICJcbihvYnNlcnZhdGlvbnMgd2l0aCB2YWx1ZXMgbGVzcyB0aGFuIDk5dGggcGVyY2VudGlsZSkiKSkgKw0KICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gc2VxKDAsIDE4MCwgMjApKSArDQogIHNjYWxlX3lfY29udGludW91cyhleHBhbmQgPSBjKDAsMCksIGJyZWFrcyA9IHNlcSgwLDE0MDAwMDAsMzAwMDAwKSwNCiAgICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IGMoMCwgcGFzdGUwKHNlcSgzMDAsMTQwMCwzMDApLCAiSyIpKSkgKw0KICBnZ3RoZW1lczo6dGhlbWVfdHVmdGUoKSArDQogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiksDQogICAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMikpDQoNCnA0YSA8LSB1bG9nc19wZXJpb2RzICU+JQ0KICBnZ3Bsb3QoKSArDQogIGFlcyh4ID0gYXZfcGN0X3BsYXllZCkgKw0KICBnZW9tX2hpc3RvZ3JhbShiaW53aWR0aCA9IDEsIGZpbGwgPSAiIzQ2ODJCNCIpICsNCiAgbGFicyh4ID0gIiIsIHkgPSAiIiwgdGl0bGUgPSB2YXJfbGFiZWwodWxvZ3NfcGVyaW9kcykkYXZfcGN0X3BsYXllZCkgKw0KICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gc2VxKDAsIDEwMCwgMjApLA0KICAgICAgICAgICAgICAgICAgICAgbGltaXRzID0gYygwLCAxMDApKSArDQogIHNjYWxlX3lfY29udGludW91cyhleHBhbmQgPSBjKDAsMCksIGJyZWFrcyA9IHNlcSgwLDYwMDAwMCwyMDAwMDApLA0KICAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gYygwLCBwYXN0ZTAoc2VxKDIwMCw2MDAsMjAwKSwgIksiKSkpICsNCiAgZ2d0aGVtZXM6OnRoZW1lX3R1ZnRlKCkgKw0KICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIpLA0KICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIpKQ0KDQpwNSA8LSB1bG9nc19wZXJpb2RzICU+JQ0KICBmaWx0ZXIobnVtX3RvdCA8IHF1YW50aWxlKHVsb2dzX3BlcmlvZHMkbnVtX3RvdCwgMC45OSkpICU+JQ0KICBnZ3Bsb3QoKSArDQogIGFlcyh4ID0gbnVtX3RvdCkgKw0KICBnZW9tX2hpc3RvZ3JhbShiaW53aWR0aCA9IDUsIGJvdW5kYXJ5ID0gMCwgZmlsbCA9ICIjNDY4MkI0IikgKw0KICBsYWJzKHggPSAiIiwgeSA9ICIiLCB0aXRsZSA9IHBhc3RlMCh2YXJfbGFiZWwodWxvZ3NfcGVyaW9kcykkbnVtX3RvdCwgIlxuKG9ic2VydmF0aW9ucyB3aXRoIHZhbHVlcyBsZXNzIHRoYW4gOTl0aCBwZXJjZW50aWxlKSIpKSArDQogIHNjYWxlX3hfY29udGludW91cyhicmVha3MgPSBzZXEoMCwyMDAsMjApKSArDQogIHNjYWxlX3lfY29udGludW91cyhleHBhbmQgPSBjKDAsMCksIGJyZWFrcyA9IHNlcSgwLDUwMDAwMCwxMDAwMDApLA0KICAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gYygwLCBwYXN0ZTAoc2VxKDEwMCw1MDAsMTAwKSwgIksiKSksDQogICAgICAgICAgICAgICAgICAgICBsaW1pdHMgPSBjKDAsIDUwMDAwMCkpICsNCiAgZ2d0aGVtZXM6OnRoZW1lX3R1ZnRlKCkgKw0KICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIpLA0KICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIpKQ0KDQpwNiA8LSB1bG9nc19wZXJpb2RzICU+JQ0KICBmaWx0ZXIobnVtX3VucSA8IHF1YW50aWxlKHVsb2dzX3BlcmlvZHMkbnVtX3VucSwgMC45OSkpICU+JQ0KICBnZ3Bsb3QoKSArDQogIGFlcyh4ID0gbnVtX3VucSkgKw0KICBnZW9tX2hpc3RvZ3JhbShiaW53aWR0aCA9IDUsIGJvdW5kYXJ5ID0gMCwgZmlsbCA9ICIjNDY4MkI0IikgKw0KICBsYWJzKHggPSAiIiwgeSA9ICIiLCB0aXRsZSA9IHBhc3RlMCh2YXJfbGFiZWwodWxvZ3NfcGVyaW9kcykkbnVtX3VucSwgIlxuKG9ic2VydmF0aW9ucyB3aXRoIHZhbHVlcyBsZXNzIHRoYW4gOTl0aCBwZXJjZW50aWxlKSIpKSArDQogIHNjYWxlX3hfY29udGludW91cyhicmVha3MgPSBzZXEoMCwxNjAsMjApKSArDQogIHNjYWxlX3lfY29udGludW91cyhleHBhbmQgPSBjKDAsMCksIGJyZWFrcyA9IHNlcSgwLDEwMDAwMDAsMjAwMDAwKSwNCiAgICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IGMoMCwgcGFzdGUwKHNlcSgyMDAsMTAwMCwyMDApLCAiSyIpKSkgKw0KICBnZ3RoZW1lczo6dGhlbWVfdHVmdGUoKSArDQogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiksDQogICAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMikpDQoNCnA3IDwtIHVsb2dzX3BlcmlvZHMgJT4lDQogIGZpbHRlcihudW1fcmVwZWF0cyA8IHF1YW50aWxlKHVsb2dzX3BlcmlvZHMkbnVtX3JlcGVhdHMsIDAuOTkpKSAlPiUNCiAgZ2dwbG90KCkgKw0KICBhZXMoeCA9IG51bV9yZXBlYXRzKSArDQogIGdlb21faGlzdG9ncmFtKGJpbndpZHRoID0gNSwgZmlsbCA9ICIjNDY4MkI0IikgKw0KICBsYWJzKHggPSAiIiwgeSA9ICIiLCB0aXRsZSA9IHBhc3RlMCh2YXJfbGFiZWwodWxvZ3NfcGVyaW9kcykkbnVtX3JlcGVhdHMsICJcbihvYnNlcnZhdGlvbnMgd2l0aCB2YWx1ZXMgbGVzcyB0aGFuIDk5dGggcGVyY2VudGlsZSkiKSkgKw0KICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gc2VxKDAsMTIwLDIwKSkgKw0KICBzY2FsZV95X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLDApLCBicmVha3MgPSBzZXEoMCwyMDAwMDAwLDUwMDAwMCksDQogICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBjKDAsIHBhc3RlMChzZXEoNTAwLDIwMDAsNTAwKSwgIksiKSksDQogICAgICAgICAgICAgICAgICAgICBsaW1pdHMgPSBjKDAsIDIwMDAwMDApKSArDQogIGdndGhlbWVzOjp0aGVtZV90dWZ0ZSgpICsNCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiKSwNCiAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyKSkNCg0KcDggPC0gdWxvZ3NfcGVyaW9kcyAlPiUNCiAgZmlsdGVyKGF2X3RpbWVzX3BsYXllZCA8IHF1YW50aWxlKHVsb2dzX3BlcmlvZHMkYXZfdGltZXNfcGxheWVkLCAwLjk5KSkgJT4lDQogIGdncGxvdCgpICsNCiAgYWVzKHggPSBhdl90aW1lc19wbGF5ZWQpICsNCiAgZ2VvbV9oaXN0b2dyYW0oYmlud2lkdGggPSAxLCBib3VuZGFyeSA9IDAsIGZpbGwgPSAiIzQ2ODJCNCIpICsNCiAgbGFicyh4ID0gIiIsIHkgPSAiIiwgdGl0bGUgPSBwYXN0ZTAodmFyX2xhYmVsKHVsb2dzX3BlcmlvZHMpJGF2X3RpbWVzX3BsYXllZCwgIlxuKG9ic2VydmF0aW9ucyB3aXRoIHZhbHVlcyBsZXNzIHRoYW4gOTl0aCBwZXJjZW50aWxlKSIpKSArDQogIHNjYWxlX3hfY29udGludW91cyhicmVha3MgPSBzZXEoMSwxMywxKSkgKw0KICBzY2FsZV95X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLDApLCBicmVha3MgPSBzZXEoMCwzMDAwMDAwLDEwMDAwMDApLA0KICAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gYygwLCBwYXN0ZTAoc2VxKDEsMywxKSwgIktLIikpKSArDQogIGdndGhlbWVzOjp0aGVtZV90dWZ0ZSgpICsNCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiKSwNCiAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyKSkNCg0KZ2dhcnJhbmdlKHAxLCBwMiwgcDJhLCBwMywgcDQsIHA0YSwgcDUsIHA2LCBwNywgcDgsIG5jb2wgPSAyLCBucm93ID0gNSkNCmBgYA0KDQpgYGB7ciBudW1fc29uZ3NfcGx0MV9wbG90LCBmaWcud2lkdGg9MTIsIGZpZy5oZWlnaHQ9MTUsIHdhcm5pbmc9RkFMU0UsIGV2YWw9VFJVRX0NCnAxIDwtIHVsb2dzX3BlcmlvZHMgJT4lDQogIGZpbHRlcihudW1fMjUgPCBxdWFudGlsZSh1bG9nc19wZXJpb2RzJG51bV8yNSwgMC45OSkpICU+JQ0KICBnZ3Bsb3QoKSArDQogIGFlcyh4ID0gbnVtXzI1KSArDQogIGdlb21faGlzdG9ncmFtKGJpbndpZHRoID0gMiwgZmlsbCA9ICIjNDY4MkI0IikgKw0KICBsYWJzKHggPSAiIiwgeSA9ICIiLCB0aXRsZSA9IHBhc3RlMCh2YXJfbGFiZWwodWxvZ3NfcGVyaW9kcykkbnVtXzI1LCAiXG4ob2JzZXJ2YXRpb25zIHdpdGggdmFsdWVzIGxlc3MgdGhhbiA5OXRoIHBlcmNlbnRpbGUpIikpICsNCiAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IHNlcSgwLCA2MCwgMTApKSArDQogIHNjYWxlX3lfY29udGludW91cyhleHBhbmQgPSBjKDAsMCksIGJyZWFrcyA9IHNlcSgwLCAxNTAwMDAwLCAzMDAwMDApLA0KICAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gYygwLCBwYXN0ZTAoc2VxKDMwMCwxNTAwLDMwMCksICJLIikpKSArDQogIGdndGhlbWVzOjp0aGVtZV90dWZ0ZSgpICsNCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiKSwNCiAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyKSkNCg0KcDIgPC0gdWxvZ3NfcGVyaW9kcyAlPiUNCiAgZmlsdGVyKG51bV81MCA8IHF1YW50aWxlKHVsb2dzX3BlcmlvZHMkbnVtXzUwLCAwLjk5KSkgJT4lDQogIGdncGxvdCgpICsNCiAgYWVzKHggPSBudW1fNTApICsNCiAgZ2VvbV9oaXN0b2dyYW0oYmlud2lkdGggPSAxLCBmaWxsID0gIiM0NjgyQjQiKSArDQogIGxhYnMoeCA9ICIiLCB5ID0gIiIsIHRpdGxlID0gcGFzdGUwKHZhcl9sYWJlbCh1bG9nc19wZXJpb2RzKSRudW1fNTAsICJcbihvYnNlcnZhdGlvbnMgd2l0aCB2YWx1ZXMgbGVzcyB0aGFuIDk5dGggcGVyY2VudGlsZSkiKSkgKw0KICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gc2VxKDAsIDE2LCAxKSkgKw0KICBzY2FsZV95X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLDApLCBicmVha3MgPSBzZXEoMCwxNjAwMDAwLDQwMDAwMCksDQogICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBjKDAsIHBhc3RlMChzZXEoNDAwLDE2MDAsNDAwKSwgIksiKSkpICsNCiAgZ2d0aGVtZXM6OnRoZW1lX3R1ZnRlKCkgKw0KICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIpLA0KICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIpKQ0KDQpwMmEgPC0gdWxvZ3NfcGVyaW9kcyAlPiUNCiAgZmlsdGVyKG51bV83NSA8IHF1YW50aWxlKHVsb2dzX3BlcmlvZHMkbnVtXzc1LCAwLjk5KSkgJT4lDQogIGdncGxvdCgpICsNCiAgYWVzKHggPSBudW1fNzUpICsNCiAgZ2VvbV9oaXN0b2dyYW0oYmlud2lkdGggPSAxLCBmaWxsID0gIiM0NjgyQjQiKSArDQogIGxhYnMoeCA9ICIiLCB5ID0gIiIsIHRpdGxlID0gcGFzdGUwKHZhcl9sYWJlbCh1bG9nc19wZXJpb2RzKSRudW1fNzUsICJcbihvYnNlcnZhdGlvbnMgd2l0aCB2YWx1ZXMgbGVzcyB0aGFuIDk5dGggcGVyY2VudGlsZSkiKSkgKw0KICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gc2VxKDAsIDE2LCAxKSkgKw0KICBzY2FsZV95X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLDApLCBicmVha3MgPSBzZXEoMCwyMDAwMDAwLDUwMDAwMCksDQogICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBjKDAsIHBhc3RlMChzZXEoNTAwLDIwMDAsNTAwKSwgIksiKSkpICsNCiAgZ2d0aGVtZXM6OnRoZW1lX3R1ZnRlKCkgKw0KICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIpLA0KICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIpKQ0KDQpwMyA8LSANCnVsb2dzX3BlcmlvZHMgJT4lDQogIGZpbHRlcihudW1fOTg1IDwgcXVhbnRpbGUodWxvZ3NfcGVyaW9kcyRudW1fOTg1LCAwLjk5KSkgJT4lDQogIGdncGxvdCgpICsNCiAgYWVzKHggPSBudW1fOTg1KSArDQogIGdlb21faGlzdG9ncmFtKGJpbndpZHRoID0gMSwgZmlsbCA9ICIjNDY4MkI0IikgKw0KICBsYWJzKHggPSAiIiwgeSA9ICIiLCB0aXRsZSA9IHBhc3RlMCh2YXJfbGFiZWwodWxvZ3NfcGVyaW9kcykkbnVtXzk4NSwgIlxuKG9ic2VydmF0aW9ucyB3aXRoIHZhbHVlcyBsZXNzIHRoYW4gOTl0aCBwZXJjZW50aWxlKSIpKSArDQogIHNjYWxlX3hfY29udGludW91cyhicmVha3MgPSBzZXEoMCwgMTYsIDEpKSArDQogIHNjYWxlX3lfY29udGludW91cyhleHBhbmQgPSBjKDAsMCksIGJyZWFrcyA9IHNlcSgwLDIwMDAwMDAsNTAwMDAwKSwNCiAgICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IGMoMCwgcGFzdGUwKHNlcSg1MDAsMjAwMCw1MDApLCAiSyIpKSwNCiAgICAgICAgICAgICAgICAgICAgIGxpbWl0cyA9IGMoMCwgMjAwMDAwMCkpICsNCiAgZ2d0aGVtZXM6OnRoZW1lX3R1ZnRlKCkgKw0KICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIpLA0KICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIpKQ0KDQpwNCA8LSB1bG9nc19wZXJpb2RzICU+JQ0KICBmaWx0ZXIobnVtXzEwMCA8IHF1YW50aWxlKHVsb2dzX3BlcmlvZHMkbnVtXzEwMCwgMC45OSkpICU+JQ0KICBnZ3Bsb3QoKSArDQogIGFlcyh4ID0gbnVtXzEwMCkgKw0KICBnZW9tX2hpc3RvZ3JhbShiaW53aWR0aCA9IDUsIGZpbGwgPSAiIzQ2ODJCNCIpICsNCiAgbGFicyh4ID0gIiIsIHkgPSAiIiwgdGl0bGUgPSBwYXN0ZTAodmFyX2xhYmVsKHVsb2dzX3BlcmlvZHMpJG51bV8xMDAsICJcbihvYnNlcnZhdGlvbnMgd2l0aCB2YWx1ZXMgbGVzcyB0aGFuIDk5dGggcGVyY2VudGlsZSkiKSkgKw0KICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gc2VxKDAsIDE4MCwgMjApKSArDQogIHNjYWxlX3lfY29udGludW91cyhleHBhbmQgPSBjKDAsMCksIGJyZWFrcyA9IHNlcSgwLDE0MDAwMDAsMzAwMDAwKSwNCiAgICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IGMoMCwgcGFzdGUwKHNlcSgzMDAsMTQwMCwzMDApLCAiSyIpKSkgKw0KICBnZ3RoZW1lczo6dGhlbWVfdHVmdGUoKSArDQogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiksDQogICAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMikpDQoNCnA0YSA8LSB1bG9nc19wZXJpb2RzICU+JQ0KICBnZ3Bsb3QoKSArDQogIGFlcyh4ID0gYXZfcGN0X3BsYXllZCkgKw0KICBnZW9tX2hpc3RvZ3JhbShiaW53aWR0aCA9IDEsIGZpbGwgPSAiIzQ2ODJCNCIpICsNCiAgbGFicyh4ID0gIiIsIHkgPSAiIiwgdGl0bGUgPSB2YXJfbGFiZWwodWxvZ3NfcGVyaW9kcykkYXZfcGN0X3BsYXllZCkgKw0KICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gc2VxKDAsIDEwMCwgMjApLA0KICAgICAgICAgICAgICAgICAgICAgbGltaXRzID0gYygwLCAxMDApKSArDQogIHNjYWxlX3lfY29udGludW91cyhleHBhbmQgPSBjKDAsMCksIGJyZWFrcyA9IHNlcSgwLDYwMDAwMCwyMDAwMDApLA0KICAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gYygwLCBwYXN0ZTAoc2VxKDIwMCw2MDAsMjAwKSwgIksiKSkpICsNCiAgZ2d0aGVtZXM6OnRoZW1lX3R1ZnRlKCkgKw0KICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIpLA0KICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIpKQ0KDQpwNSA8LSB1bG9nc19wZXJpb2RzICU+JQ0KICBmaWx0ZXIobnVtX3RvdCA8IHF1YW50aWxlKHVsb2dzX3BlcmlvZHMkbnVtX3RvdCwgMC45OSkpICU+JQ0KICBnZ3Bsb3QoKSArDQogIGFlcyh4ID0gbnVtX3RvdCkgKw0KICBnZW9tX2hpc3RvZ3JhbShiaW53aWR0aCA9IDUsIGJvdW5kYXJ5ID0gMCwgZmlsbCA9ICIjNDY4MkI0IikgKw0KICBsYWJzKHggPSAiIiwgeSA9ICIiLCB0aXRsZSA9IHBhc3RlMCh2YXJfbGFiZWwodWxvZ3NfcGVyaW9kcykkbnVtX3RvdCwgIlxuKG9ic2VydmF0aW9ucyB3aXRoIHZhbHVlcyBsZXNzIHRoYW4gOTl0aCBwZXJjZW50aWxlKSIpKSArDQogIHNjYWxlX3hfY29udGludW91cyhicmVha3MgPSBzZXEoMCwyMDAsMjApKSArDQogIHNjYWxlX3lfY29udGludW91cyhleHBhbmQgPSBjKDAsMCksIGJyZWFrcyA9IHNlcSgwLDUwMDAwMCwxMDAwMDApLA0KICAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gYygwLCBwYXN0ZTAoc2VxKDEwMCw1MDAsMTAwKSwgIksiKSksDQogICAgICAgICAgICAgICAgICAgICBsaW1pdHMgPSBjKDAsIDUwMDAwMCkpICsNCiAgZ2d0aGVtZXM6OnRoZW1lX3R1ZnRlKCkgKw0KICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIpLA0KICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIpKQ0KDQpwNiA8LSB1bG9nc19wZXJpb2RzICU+JQ0KICBmaWx0ZXIobnVtX3VucSA8IHF1YW50aWxlKHVsb2dzX3BlcmlvZHMkbnVtX3VucSwgMC45OSkpICU+JQ0KICBnZ3Bsb3QoKSArDQogIGFlcyh4ID0gbnVtX3VucSkgKw0KICBnZW9tX2hpc3RvZ3JhbShiaW53aWR0aCA9IDUsIGJvdW5kYXJ5ID0gMCwgZmlsbCA9ICIjNDY4MkI0IikgKw0KICBsYWJzKHggPSAiIiwgeSA9ICIiLCB0aXRsZSA9IHBhc3RlMCh2YXJfbGFiZWwodWxvZ3NfcGVyaW9kcykkbnVtX3VucSwgIlxuKG9ic2VydmF0aW9ucyB3aXRoIHZhbHVlcyBsZXNzIHRoYW4gOTl0aCBwZXJjZW50aWxlKSIpKSArDQogIHNjYWxlX3hfY29udGludW91cyhicmVha3MgPSBzZXEoMCwxNjAsMjApKSArDQogIHNjYWxlX3lfY29udGludW91cyhleHBhbmQgPSBjKDAsMCksIGJyZWFrcyA9IHNlcSgwLDEwMDAwMDAsMjAwMDAwKSwNCiAgICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IGMoMCwgcGFzdGUwKHNlcSgyMDAsMTAwMCwyMDApLCAiSyIpKSkgKw0KICBnZ3RoZW1lczo6dGhlbWVfdHVmdGUoKSArDQogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiksDQogICAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMikpDQoNCnA3IDwtIHVsb2dzX3BlcmlvZHMgJT4lDQogIGZpbHRlcihudW1fcmVwZWF0cyA8IHF1YW50aWxlKHVsb2dzX3BlcmlvZHMkbnVtX3JlcGVhdHMsIDAuOTkpKSAlPiUNCiAgZ2dwbG90KCkgKw0KICBhZXMoeCA9IG51bV9yZXBlYXRzKSArDQogIGdlb21faGlzdG9ncmFtKGJpbndpZHRoID0gNSwgZmlsbCA9ICIjNDY4MkI0IikgKw0KICBsYWJzKHggPSAiIiwgeSA9ICIiLCB0aXRsZSA9IHBhc3RlMCh2YXJfbGFiZWwodWxvZ3NfcGVyaW9kcykkbnVtX3JlcGVhdHMsICJcbihvYnNlcnZhdGlvbnMgd2l0aCB2YWx1ZXMgbGVzcyB0aGFuIDk5dGggcGVyY2VudGlsZSkiKSkgKw0KICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gc2VxKDAsMTIwLDIwKSkgKw0KICBzY2FsZV95X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLDApLCBicmVha3MgPSBzZXEoMCwyMDAwMDAwLDUwMDAwMCksDQogICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBjKDAsIHBhc3RlMChzZXEoNTAwLDIwMDAsNTAwKSwgIksiKSksDQogICAgICAgICAgICAgICAgICAgICBsaW1pdHMgPSBjKDAsIDIwMDAwMDApKSArDQogIGdndGhlbWVzOjp0aGVtZV90dWZ0ZSgpICsNCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiKSwNCiAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyKSkNCg0KcDggPC0gdWxvZ3NfcGVyaW9kcyAlPiUNCiAgZmlsdGVyKGF2X3RpbWVzX3BsYXllZCA8IHF1YW50aWxlKHVsb2dzX3BlcmlvZHMkYXZfdGltZXNfcGxheWVkLCAwLjk5KSkgJT4lDQogIGdncGxvdCgpICsNCiAgYWVzKHggPSBhdl90aW1lc19wbGF5ZWQpICsNCiAgZ2VvbV9oaXN0b2dyYW0oYmlud2lkdGggPSAxLCBib3VuZGFyeSA9IDAsIGZpbGwgPSAiIzQ2ODJCNCIpICsNCiAgbGFicyh4ID0gIiIsIHkgPSAiIiwgdGl0bGUgPSBwYXN0ZTAodmFyX2xhYmVsKHVsb2dzX3BlcmlvZHMpJGF2X3RpbWVzX3BsYXllZCwgIlxuKG9ic2VydmF0aW9ucyB3aXRoIHZhbHVlcyBsZXNzIHRoYW4gOTl0aCBwZXJjZW50aWxlKSIpKSArDQogIHNjYWxlX3hfY29udGludW91cyhicmVha3MgPSBzZXEoMSwxMywxKSkgKw0KICBzY2FsZV95X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLDApLCBicmVha3MgPSBzZXEoMCwzMDAwMDAwLDEwMDAwMDApLA0KICAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gYygwLCBwYXN0ZTAoc2VxKDEsMywxKSwgIktLIikpKSArDQogIGdndGhlbWVzOjp0aGVtZV90dWZ0ZSgpICsNCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiKSwNCiAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyKSkNCg0KZ2dhcnJhbmdlKHAxLCBwMiwgcDJhLCBwMywgcDQsIHA0YSwgcDUsIHA2LCBwNywgcDgsIG5jb2wgPSAyLCBucm93ID0gNSkNCmBgYA0KDQo8YnI+DQoNCmBgYHtyIHNoX3NvbmdzX2N1bX0NCmdncGxvdCh1bG9nc19wZXJpb2RzKSArIA0KICBzdGF0X2VjZGYoYWVzKHggPSBzaF8xMDAsIGNvbG9yID0gIjk4LjUtMTAwJSIpLCBzaXplID0gMSkgKyANCiAgc3RhdF9lY2RmKGFlcyh4ID0gc2hfOTg1LCBjb2xvciA9ICI3NS05OC41JSIpLCBzaXplID0gMSkgKyANCiAgc3RhdF9lY2RmKGFlcyh4ID0gc2hfNzUsIGNvbG9yID0gIjUwLTc1JSIpLCBzaXplID0gMSkgKyANCiAgc3RhdF9lY2RmKGFlcyh4ID0gc2hfNTAsIGNvbG9yID0gIjI1LTUwJSIpLCBzaXplID0gMSkgKyANCiAgc3RhdF9lY2RmKGFlcyh4ID0gc2hfMjUsIGNvbG9yID0gIjAtMjUlIiksIHNpemUgPSAxKSArDQogIHNjYWxlX2NvbG9yX3ZpcmlkaXNfZChlbmQgPSAwLjksIGRpcmVjdGlvbiA9IC0xKSArIA0KICBzY2FsZV94X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLjAxLDApLA0KICAgICAgICAgICAgICAgICAgICAgYnJlYWtzID0gc2VxKDAsIDEsIDAuMiksDQogICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBjKDAsIHBhc3RlMCgiPD0gIiwgc2VxKDAuMiwxLDAuMikpKSwNCiAgICAgICAgICAgICAgICAgICAgIHNlYy5heGlzID0gc2VjX2F4aXModHJhbnMgPSB+IC4sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5hbWUgPSAiRm9yIDEtQ0RGOiBzaGFyZSBvZiB0aGlzIGxlbmd0aCBzb25ncyBvdXQgb2YgdGhlIHRvdGFsIG51bWJlciBvZiBzb25ncyBwbGF5ZWQiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBicmVha3MgPSBzZXEoMCwgMSwgMC4yKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gYyhwYXN0ZTAoIj49ICIsIHNlcSgwLDAuOCwwLjIpKSwgMSkpKSArDQogIHNjYWxlX3lfY29udGludW91cyhleHBhbmQgPSBjKDAuMDEsIDApLCANCiAgICAgICAgICAgICAgICAgICAgIGJyZWFrcyA9IHNlcSgwLCAxLCAwLjIpLA0KICAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gc2NhbGVzOjpwZXJjZW50X2Zvcm1hdChhY2N1cmFjeSA9IDEpLA0KICAgICAgICAgICAgICAgICAgICAgc2VjLmF4aXMgPSBzZWNfYXhpcyh0cmFucyA9IH4gMSAtIC4sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5hbWUgPSAiMS1DREYiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBicmVha3MgPSBzZXEoMCwgMSwgMC4yKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gc2NhbGVzOjpwZXJjZW50X2Zvcm1hdChhY2N1cmFjeSA9IDEpKSkgKw0KICBsYWJzKHggPSAiRm9yIENERjogc2hhcmUgb2YgdGhpcyBsZW5ndGggc29uZ3Mgb3V0IG9mIHRoZSB0b3RhbCBudW1iZXIgb2Ygc29uZ3MgcGxheWVkIiwNCiAgICAgICB5ID0gIkNERiIsIGNvbG9yID0gIiUgb2Ygc29uZyBsZW5ndGggcGxheWVkIiwNCiAgICAgICB0aXRsZSA9ICJDdW11bGF0aXZlIGRpc3RyaWJ1dGlvbiBmdW5jdGlvbiBmb3Igc2hhcmVzIG9mXG50aGUgbnVtYmVyIG9mIHNvbmdzIHBsYXllZCBieSBsZW5ndGgiKSArDQogIGdndGhlbWVzOjp0aGVtZV90dWZ0ZSgpICsNCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiKSwNCiAgICAgICAgcGFuZWwuZ3JpZC5tYWpvciA9IGVsZW1lbnRfbGluZShjb2xvciA9ICJncmV5OTAiLCBzaXplID0gMC4yKSwNCiAgICAgICAgcGFuZWwuZ3JpZC5taW5vciA9IGVsZW1lbnRfbGluZShjb2xvciA9ICJncmV5OTAiLCBzaXplID0gMC4yKSkNCmBgYA0KDQpgYGB7ciBzaF9zb25nc19jdW1fcGxvdCwgZmlnLndpZHRoPTksIGZpZy5oZWlnaHQ9NiwgZXZhbD1UUlVFfQ0Kcm0ocDEscDIscDJhLHAzLHA0LHA0YSxwNSxwNixwNyxwOCkNCg0KZ2dwbG90KHVsb2dzX3BlcmlvZHMpICsgDQogIHN0YXRfZWNkZihhZXMoeCA9IHNoXzEwMCwgY29sb3IgPSAiOTguNS0xMDAlIiksIHNpemUgPSAxKSArIA0KICBzdGF0X2VjZGYoYWVzKHggPSBzaF85ODUsIGNvbG9yID0gIjc1LTk4LjUlIiksIHNpemUgPSAxKSArIA0KICBzdGF0X2VjZGYoYWVzKHggPSBzaF83NSwgY29sb3IgPSAiNTAtNzUlIiksIHNpemUgPSAxKSArIA0KICBzdGF0X2VjZGYoYWVzKHggPSBzaF81MCwgY29sb3IgPSAiMjUtNTAlIiksIHNpemUgPSAxKSArIA0KICBzdGF0X2VjZGYoYWVzKHggPSBzaF8yNSwgY29sb3IgPSAiMC0yNSUiKSwgc2l6ZSA9IDEpICsNCiAgc2NhbGVfY29sb3JfdmlyaWRpc19kKGVuZCA9IDAuOSwgZGlyZWN0aW9uID0gLTEpICsgDQogIHNjYWxlX3hfY29udGludW91cyhleHBhbmQgPSBjKDAuMDEsMCksDQogICAgICAgICAgICAgICAgICAgICBicmVha3MgPSBzZXEoMCwgMSwgMC4yKSwNCiAgICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IGMoMCwgcGFzdGUwKCI8PSAiLCBzZXEoMC4yLDEsMC4yKSkpLA0KICAgICAgICAgICAgICAgICAgICAgc2VjLmF4aXMgPSBzZWNfYXhpcyh0cmFucyA9IH4gLiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbmFtZSA9ICJGb3IgMS1DREY6IHNoYXJlIG9mIHRoaXMgbGVuZ3RoIHNvbmdzIG91dCBvZiB0aGUgdG90YWwgbnVtYmVyIG9mIHNvbmdzIHBsYXllZCIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJyZWFrcyA9IHNlcSgwLCAxLCAwLjIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBjKHBhc3RlMCgiPj0gIiwgc2VxKDAsMC44LDAuMikpLCAxKSkpICsNCiAgc2NhbGVfeV9jb250aW51b3VzKGV4cGFuZCA9IGMoMC4wMSwgMCksIA0KICAgICAgICAgICAgICAgICAgICAgYnJlYWtzID0gc2VxKDAsIDEsIDAuMiksDQogICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBzY2FsZXM6OnBlcmNlbnRfZm9ybWF0KGFjY3VyYWN5ID0gMSksDQogICAgICAgICAgICAgICAgICAgICBzZWMuYXhpcyA9IHNlY19heGlzKHRyYW5zID0gfiAxIC0gLiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbmFtZSA9ICIxLUNERiIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJyZWFrcyA9IHNlcSgwLCAxLCAwLjIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBzY2FsZXM6OnBlcmNlbnRfZm9ybWF0KGFjY3VyYWN5ID0gMSkpKSArDQogIGxhYnMoeCA9ICJGb3IgQ0RGOiBzaGFyZSBvZiB0aGlzIGxlbmd0aCBzb25ncyBvdXQgb2YgdGhlIHRvdGFsIG51bWJlciBvZiBzb25ncyBwbGF5ZWQiLA0KICAgICAgIHkgPSAiQ0RGIiwgY29sb3IgPSAiJSBvZiBzb25nIGxlbmd0aCBwbGF5ZWQiLA0KICAgICAgIHRpdGxlID0gIkN1bXVsYXRpdmUgZGlzdHJpYnV0aW9uIGZ1bmN0aW9uIGZvciBzaGFyZXMgb2ZcbnRoZSBudW1iZXIgb2Ygc29uZ3MgcGxheWVkIGJ5IGxlbmd0aCIpICsNCiAgZ2d0aGVtZXM6OnRoZW1lX3R1ZnRlKCkgKw0KICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIpLA0KICAgICAgICBwYW5lbC5ncmlkLm1ham9yID0gZWxlbWVudF9saW5lKGNvbG9yID0gImdyZXk5MCIsIHNpemUgPSAwLjIpLA0KICAgICAgICBwYW5lbC5ncmlkLm1pbm9yID0gZWxlbWVudF9saW5lKGNvbG9yID0gImdyZXk5MCIsIHNpemUgPSAwLjIpKQ0KYGBgDQoNCi0tIFRoZXJlIGFyZSBhbG1vc3Qgbm8gcmVjb3Jkcywgd2hlbiBkdXJpbmcgYSBkYXkgYSB1c2VyIGhhcyBub3QgcGxheWVkIGFueSBzb25nIGluIHRoZSBmdWxsIGxlbmd0aC4gQWJvdXQgMTUlIG9mIHJlY29yZHMgY29udGFpbnMgb25seSBjb21wbGV0ZWx5IHBsYXllZCBzb25ncy4gRm9yIGFib3V0IDgwJSBvZiByZWNvcmRzIGF0IGxlYXN0IGhhbGYgb2Ygc29uZ3Mgd2VyZSBsaXN0ZW5lZCBjb21wbGV0ZWx5LCBmb3IgNTAlIG9mIHJlY29yZHMgLSBhdCBsZWFzdCA4IG91dCBvZiAxMC4NCg0KLS0gSW50ZXJtZWRpYXRlIGNhdGVnb3JpZXMgKGBudW1fNTBgLCBgbnVtXzc1YCBhbmQgYG51bV85ODVgKSBhcmUgcXVpdGUgcmFyZSBhbmQgZm9yIG1vcmUgdGhhbiA5NSUgb2YgcmVjb3JkcyB0aGVpciBzaGFyZSBpcyBsZXNzIHRoYW4gMzAlIG91dCBvZiB0aGUgdG90YWwgbnVtYmVyIG9mIHNvbmdzIHBsYXllZCB0aGF0IGRheS4NCg0KPGJyPg0KDQpgYGB7ciBhdl9wY3RfcGxheWVkfQ0KZ2dwbG90KHVsb2dzX3BlcmlvZHMsIGFlcyh5ID0gYXZfcGN0X3BsYXllZCkpICsNCiAgZ2dkaXN0OjpzdGF0X2hhbGZleWUoYWRqdXN0ID0gMC40LCBqdXN0aWZpY2F0aW9uID0gLTAuMDcsIC53aWR0aCA9IDAsIA0KICAgICAgICAgICAgICAgICAgICAgICBwb2ludF9jb2xvdXIgPSBOQSwgYWxwaGEgPSAwLjgsDQogICAgICAgICAgICAgICAgICAgICAgIGZpbGwgPSAiIzQ2ODJCNCIpICsNCiAgZ2VvbV9ib3hwbG90KHdpZHRoID0gMC4xLCBhbHBoYSA9IDAuNSwgb3V0bGllci5jb2xvdXIgPSBOQSwNCiAgICAgICAgICAgICAgIHNob3cubGVnZW5kID0gRkFMU0UsIGZpbGwgPSAiIzQ2ODJCNCIsIGNvbG9yID0gIiM0NjgyQjQiKSArDQogIGNvb3JkX2ZsaXAoKSArDQogIHNjYWxlX3lfY29udGludW91cyhicmVha3MgPSBzZXEoMTAsMTAwLDEwKSwNCiAgICAgICAgICAgICAgICAgICAgIGV4cGFuZCA9IGMoMC4wMSwwKSwNCiAgICAgICAgICAgICAgICAgICAgIGxpbWl0cyA9IGMoMTAsIDEwMCkpICsNCiAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IHNlcSgwLjA2LCAxLjA2LCAwLjIpLA0KICAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gc2VxKDAsMSwwLjIpLA0KICAgICAgICAgICAgICAgICAgICAgZXhwYW5kID0gYygwLjAxLCAwKSwNCiAgICAgICAgICAgICAgICAgICAgIGxpbWl0cyA9IGMoLTAuMSwgMS4xKSkgKw0KICBsYWJzKHkgPSB2YXJfbGFiZWwodWxvZ3NfcGVyaW9kcykkYXZfcGN0X3BsYXllZCwgeCA9ICJEZW5zaXR5IiwNCiAgICAgICB0aXRsZSA9ICJBdmVyYWdlICUgb2Ygb25lIHNvbmcncyBsZW5ndGggcGxheWVkIikgKw0KICBnZ3RoZW1lczo6dGhlbWVfdHVmdGUoKQ0KYGBgDQoNCmBgYHtyIGF2X3BjdF9wbGF5ZWRfcGxvdCwgZmlnLndpZHRoPTYsIGZpZy5oZWlnaHQ9NCwgZXZhbD1UUlVFfQ0KZ2dwbG90KHVsb2dzX3BlcmlvZHMsIGFlcyh5ID0gYXZfcGN0X3BsYXllZCkpICsNCiAgZ2dkaXN0OjpzdGF0X2hhbGZleWUoYWRqdXN0ID0gMC40LCBqdXN0aWZpY2F0aW9uID0gLTAuMDcsIC53aWR0aCA9IDAsIA0KICAgICAgICAgICAgICAgICAgICAgICBwb2ludF9jb2xvdXIgPSBOQSwgYWxwaGEgPSAwLjgsDQogICAgICAgICAgICAgICAgICAgICAgIGZpbGwgPSAiIzQ2ODJCNCIpICsNCiAgZ2VvbV9ib3hwbG90KHdpZHRoID0gMC4xLCBhbHBoYSA9IDAuNSwgb3V0bGllci5jb2xvdXIgPSBOQSwNCiAgICAgICAgICAgICAgIHNob3cubGVnZW5kID0gRkFMU0UsIGZpbGwgPSAiIzQ2ODJCNCIsIGNvbG9yID0gIiM0NjgyQjQiKSArDQogIGNvb3JkX2ZsaXAoKSArDQogIHNjYWxlX3lfY29udGludW91cyhicmVha3MgPSBzZXEoMTAsMTAwLDEwKSwNCiAgICAgICAgICAgICAgICAgICAgIGV4cGFuZCA9IGMoMC4wMSwwKSwNCiAgICAgICAgICAgICAgICAgICAgIGxpbWl0cyA9IGMoMTAsIDEwMCkpICsNCiAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IHNlcSgwLjA2LCAxLjA2LCAwLjIpLA0KICAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gc2VxKDAsMSwwLjIpLA0KICAgICAgICAgICAgICAgICAgICAgZXhwYW5kID0gYygwLjAxLCAwKSwNCiAgICAgICAgICAgICAgICAgICAgIGxpbWl0cyA9IGMoLTAuMSwgMS4xKSkgKw0KICBsYWJzKHkgPSB2YXJfbGFiZWwodWxvZ3NfcGVyaW9kcykkYXZfcGN0X3BsYXllZCwgeCA9ICJEZW5zaXR5IiwNCiAgICAgICB0aXRsZSA9ICJBdmVyYWdlICUgb2Ygb25lIHNvbmcncyBsZW5ndGggcGxheWVkIikgKw0KICBnZ3RoZW1lczo6dGhlbWVfdHVmdGUoKQ0KYGBgDQoNCjxicj4NCg0KYGBge3IgdG90YWxfc2Vjc30NCnVsb2dzX3BlcmlvZHMgPC0gdWxvZ3NfcGVyaW9kcyAlPiUNCiAgbXV0YXRlKHRvdGFsX3NlY3NfbmEgPSBjYXNlX3doZW4odG90YWxfc2VjcyA8IDAgfCB0b3RhbF9zZWNzID4gNjAqNjAqMjQgfiBOQV9yZWFsXywgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFRSVUUgfiB0b3RhbF9zZWNzKSwNCiAgICAgICAgIHRvdGFsX2hyc19uYSA9IHRvdGFsX3NlY3NfbmEvMzYwMCwNCiAgICAgICAgIGF2X3NlY3NfcmVhbF9uYSA9IHRvdGFsX3NlY3MgLyAobnVtXzI1LzAuMTI1ICsgbnVtXzUwLzAuMzc1ICsgbnVtXzc1LzAuNjI1ICsgbnVtXzk4NS8wLjg2NzUgKyBudW1fMTAwLzAuOTkyNSkpDQpgYGANCg0KLSAgICoqYHRvdGFsX3NlY3NgKiogLSB0b3RhbCBzZWNvbmRzIHBsYXllZC4gVGhpcyBjb2x1bW4gY29udGFpbnMgYHIgc3VtKHVsb2dzX3BlcmlvZHMkdG90YWxfc2VjcyA8IDApYCByZWNvcmRzIHdpdGggbmVnYXRpdmUgdmFsdWVzIC0gdGhleSB3ZXJlIHJlcGxhY2VkIGJ5IG1pc3NpbmdzLiBUaGUgc2FtZSB3YXMgZG9uZSB0byBgciBzdW0odWxvZ3NfcGVyaW9kcyR0b3RhbF9zZWNzID4gNjAqNjAqMjQsIG5hLnJtID0gVFJVRSlgIHJlY29yZHMgd2l0aCB2YWx1ZXMgaGlnaGVyIHRoYW4gODY0MDAgc2Vjb25kcyAoMjQgaG91cnMpLg0KDQogICAgVG8gaW1wdXRlIHRoZXNlIG1pc3NpbmdzIEkgd2lsbCB1c2UgdGhlIGZvbGxvd2luZyBhcHByb2FjaDogSSd2ZSBjYWxjdWxhdGVkIHRoZSBhdmVyYWdlIHJlYWwgbGVuZ3RoIG9mIG9uZSBzb25nIGxpc3RlbmVkIGJ5IGVhY2ggdXNlciAoKipgYXZfbGVuZ3RoX3JlYWxgKiopIHdpdGggdGhlIGZvbGxvd2luZyBmb3JtdWxhOiAkJFx0ZXh0e0F2ZXJhZ2UgcmVhbCBsZW5ndGh9ID0gXGZyYWN7XHRleHR7dG90YWxfc2Vjc319e1xmcmFje1x0ZXh0e251bV8yNX19ezAuMTI1fStcZnJhY3tcdGV4dHtudW1fNTB9fXswLjM3NX0rXGZyYWN7XHRleHR7bnVtXzc1fX17MC42MjV9K1xmcmFje1x0ZXh0e251bV85ODV9fXswLjg2NzV9K1xmcmFje1x0ZXh0e251bV8xMDB9fXswLjk5MjV9fSQkDQoNCiAgICBNZWRpYW4gYXZlcmFnZSByZWFsIGxlbmd0aCBvZiBvbmUgc29uZyBpbiB0aGUgd2hvbGUgZGF0YXNldCBpcyBhYm91dCBgciByb3VuZChtZWRpYW4odWxvZ3NfcGVyaW9kcyRhdl9zZWNzX3JlYWxfbmEsIG5hLnJtID0gVFJVRSksMClgIHNlY29uZHMgKGBtZWRpYW5fbGVuZ3RoYCkuIFVzaW5nIGl0IEkndmUgY2FsY3VsYXRlZCB0aGUgaW1wdXRlZCB2YWx1ZSBvZiB0aGUgdG90YWwgc2Vjb25kcyBwbGF5ZWQgKCoqYHRvdGFsX3NlY3NfaW1wYCoqKSBmb3Igb2JzZXJ2YXRpb25zIHdpdGggbWlzc2luZyB2YWx1ZXMgaW4gdGhlIGZvbGxvd2luZyB3YXk6ICQkXHRleHR7dG90YWxfc2Vjc19pbXB9ID0gbWluKDI0KjYwKjYwLCBcdGV4dHttZWRpYW5fbGVuZ3RofSpcdGV4dHtzaF90b3R9KSA9IFxcPSBtaW4oMjQqNjAqNjAsIFx0ZXh0e21lZGlhbl9sZW5ndGh9KihcdGV4dHtudW1fMjV9KjAuMTI1ICsgXHRleHR7bnVtXzUwfSowLjM3NSArIFxcXHRleHR7bnVtXzc1fSowLjYyNSArIFx0ZXh0e251bV85ODV9KjAuODY3NSArIFx0ZXh0e251bV8xMDB9KjAuOTkyNSkpJCQNCg0KPGJyPg0KDQpgYGB7ciB0b3RhbF9zZWNzX2ltcH0NCnVsb2dzX3BlcmlvZHMgPC0gdWxvZ3NfcGVyaW9kcyAlPiUNCiAgbXV0YXRlKHRvdGFsX3NlY3NfaW1wID0gaWZlbHNlKGlzLm5hKHRvdGFsX3NlY3NfbmEpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWluKHJvdW5kKG1lZGlhbih1bG9nc19wZXJpb2RzJGF2X3NlY3NfcmVhbF9uYSwgbmEucm0gPSBUUlVFKSwwKSphdl9wY3RfcGxheWVkLzEwMCw4NjQwMCksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0b3RhbF9zZWNzX25hKSwNCiAgICAgICAgIHRvdGFsX2hyc19pbXAgPSB0b3RhbF9zZWNzX2ltcC8zNjAwLA0KICAgICAgICAgYXZfc2Vjc19yZWFsID0gdG90YWxfc2Vjc19pbXAgLyAobnVtXzI1LzAuMTI1ICsgbnVtXzUwLzAuMzc1ICsgbnVtXzc1LzAuNjI1ICsgbnVtXzk4NS8wLjg2NzUgKyBudW1fMTAwLzAuOTkyNSksDQogICAgICAgICBhdl9zZWNzX3BsYXllZCA9IHRvdGFsX3NlY3NfaW1wIC8gbnVtX3RvdCwNCiAgICAgICAgIGF2X21pbnNfcmVhbCA9IGF2X3NlY3NfcmVhbC82MCwNCiAgICAgICAgIGF2X21pbnNfcGxheWVkID0gYXZfc2Vjc19wbGF5ZWQvNjApDQoNCnZhcl9sYWJlbCh1bG9nc19wZXJpb2RzKSA8LSBsaXN0KHRvdGFsX3NlY3NfbmEgPSAiVG90YWwgc2Vjb25kcyBwbGF5ZWQiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdG90YWxfc2Vjc19pbXAgPSAiVG90YWwgc2Vjb25kcyBwbGF5ZWQgKGltcHV0ZWQpIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRvdGFsX2hyc19uYSA9ICJUb3RhbCBob3VycyBwbGF5ZWQiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdG90YWxfaHJzX2ltcCA9ICJUb3RhbCBob3VycyBwbGF5ZWQgKGltcHV0ZWQpIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGF2X3NlY3NfcmVhbCA9ICJBdmVyYWdlIHJlYWwgbGVuZ3RoIG9mIGEgc29uZywgc2Vjb25kcyIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhdl9zZWNzX3BsYXllZCA9ICJBdmVyYWdlIHRpbWUgc3BlbnQgb24gbGlzdGVuaW5nIHRvIG9uZSBzb25nLCBzZWNvbmRzIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGF2X21pbnNfcmVhbCA9ICJBdmVyYWdlIHJlYWwgbGVuZ3RoIG9mIGEgc29uZywgbWludXRlcyIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhdl9taW5zX3BsYXllZCA9ICJBdmVyYWdlIHRpbWUgc3BlbnQgb24gbGlzdGVuaW5nIHRvIG9uZSBzb25nLCBtaW51dGVzIikNCg0KdGJsX3N1bW1hcnkoDQogIGFzLmRhdGEuZnJhbWUoc2VsZWN0KHVsb2dzX3BlcmlvZHMsIGF2X21pbnNfcGxheWVkLCB0b3RhbF9ocnNfbmEsIHRvdGFsX2hyc19pbXApKSwNCiAgdHlwZSA9IGFsbF9jb250aW51b3VzKCkgfiAiY29udGludW91czIiLA0KICBzdGF0aXN0aWMgPSBsaXN0KGFsbF9jb250aW51b3VzKCkgfiBjKCJ7bWVhbn0gKHtzZH0pIiwgInttZWRpYW59ICh7cDI1fS17cDc1fSkiLCAgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgInttaW59LXttYXh9IikpLA0KICBkaWdpdHMgPSBsaXN0KGFsbF9jb250aW51b3VzKCkgfiBjKHJlcCgxLCA3KSkpKSAlPiUNCiAgYm9sZF9sYWJlbHMoKQ0KYGBgDQoNCmBgYHtyIHRvdGFsX3NlY3NfaW1wX3RhYiwgZXZhbD1UUlVFfQ0KdWxvZ3NfcGVyaW9kcyA8LSB1bG9nc19wZXJpb2RzICU+JQ0KICBtdXRhdGUodG90YWxfc2Vjc19pbXAgPSBpZmVsc2UoaXMubmEodG90YWxfc2Vjc19uYSksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtaW4ocm91bmQobWVkaWFuKHVsb2dzX3BlcmlvZHMkYXZfc2Vjc19yZWFsX25hLCBuYS5ybSA9IFRSVUUpLDApKmF2X3BjdF9wbGF5ZWQvMTAwLDg2NDAwKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRvdGFsX3NlY3NfbmEpLA0KICAgICAgICAgdG90YWxfaHJzX2ltcCA9IHRvdGFsX3NlY3NfaW1wLzM2MDAsDQogICAgICAgICBhdl9zZWNzX3JlYWwgPSB0b3RhbF9zZWNzX2ltcCAvIChudW1fMjUvMC4xMjUgKyBudW1fNTAvMC4zNzUgKyBudW1fNzUvMC42MjUgKyBudW1fOTg1LzAuODY3NSArIG51bV8xMDAvMC45OTI1KSwNCiAgICAgICAgIGF2X3NlY3NfcGxheWVkID0gdG90YWxfc2Vjc19pbXAgLyBudW1fdG90LA0KICAgICAgICAgYXZfbWluc19yZWFsID0gYXZfc2Vjc19yZWFsLzYwLA0KICAgICAgICAgYXZfbWluc19wbGF5ZWQgPSBhdl9zZWNzX3BsYXllZC82MCkNCg0KdmFyX2xhYmVsKHVsb2dzX3BlcmlvZHMpIDwtIGxpc3QodG90YWxfc2Vjc19uYSA9ICJUb3RhbCBzZWNvbmRzIHBsYXllZCIsDQogICAgICAgICAgICAgICAgICAgICAgICAgdG90YWxfc2Vjc19pbXAgPSAiVG90YWwgc2Vjb25kcyBwbGF5ZWQgKGltcHV0ZWQpIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICB0b3RhbF9ocnNfbmEgPSAiVG90YWwgaG91cnMgcGxheWVkIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICB0b3RhbF9ocnNfaW1wID0gIlRvdGFsIGhvdXJzIHBsYXllZCAoaW1wdXRlZCkiLA0KICAgICAgICAgICAgICAgICAgICAgICAgIGF2X3NlY3NfcmVhbCA9ICJBdmVyYWdlIHJlYWwgbGVuZ3RoIG9mIGEgc29uZywgc2Vjb25kcyIsDQogICAgICAgICAgICAgICAgICAgICAgICAgYXZfc2Vjc19wbGF5ZWQgPSAiQXZlcmFnZSB0aW1lIHNwZW50IG9uIGxpc3RlbmluZyB0byBvbmUgc29uZywgc2Vjb25kcyIsDQogICAgICAgICAgICAgICAgICAgICAgICAgYXZfbWluc19yZWFsID0gIkF2ZXJhZ2UgcmVhbCBsZW5ndGggb2YgYSBzb25nLCBtaW51dGVzIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICBhdl9taW5zX3BsYXllZCA9ICJBdmVyYWdlIHRpbWUgc3BlbnQgb24gbGlzdGVuaW5nIHRvIG9uZSBzb25nLCBtaW51dGVzIikNCg0KdGJsX3N1bW1hcnkoDQogIGFzLmRhdGEuZnJhbWUoc2VsZWN0KHVsb2dzX3BlcmlvZHMsIGF2X21pbnNfcGxheWVkLCB0b3RhbF9ocnNfbmEsIHRvdGFsX2hyc19pbXApKSwNCiAgdHlwZSA9IGFsbF9jb250aW51b3VzKCkgfiAiY29udGludW91czIiLA0KICBzdGF0aXN0aWMgPSBsaXN0KGFsbF9jb250aW51b3VzKCkgfiBjKCJ7bWVhbn0gKHtzZH0pIiwgInttZWRpYW59ICh7cDI1fS17cDc1fSkiLCAgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgInttaW59LXttYXh9IikpLA0KICBkaWdpdHMgPSBsaXN0KGFsbF9jb250aW51b3VzKCkgfiBjKHJlcCgxLCA3KSkpKSAlPiUNCiAgYm9sZF9sYWJlbHMoKQ0KYGBgDQoNCjxicj4NCg0KYGBge3IgdG90YWxfc2Vjc19wbHR9DQpwMSA8LSB1bG9nc19wZXJpb2RzICU+JQ0KICBmaWx0ZXIoYXZfbWluc19wbGF5ZWQgPCBxdWFudGlsZSh1bG9nc19wZXJpb2RzJGF2X21pbnNfcGxheWVkLCAwLjk5LCBuYS5ybSA9IFRSVUUpKSAlPiUNCiAgZ2dwbG90KCkgKw0KICBhZXMoeCA9IGF2X21pbnNfcGxheWVkKSArDQogIGdlb21faGlzdG9ncmFtKGJpbndpZHRoID0gMSwgYm91bmRhcnkgPSAwLCBmaWxsID0gIiM0NjgyQjQiKSArDQogIGxhYnMoeCA9ICJNaW51dGVzIiwgeSA9ICIiLCB0aXRsZSA9IHBhc3RlMCh2YXJfbGFiZWwodWxvZ3NfcGVyaW9kcykkYXZfbWluc19wbGF5ZWQsICJcbihvYnNlcnZhdGlvbnMgd2l0aCB2YWx1ZXMgbGVzcyB0aGFuIDk5dGggcGVyY2VudGlsZSkiKSkgKw0KICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gc2VxKDAsIDUsIDEpKSArDQogIHNjYWxlX3lfY29udGludW91cyhleHBhbmQgPSBjKDAsMCksIGJyZWFrcyA9IHNlcSgwLDE1MDAwMDAsNTAwMDAwKSwgDQogICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBjKDAsIHBhc3RlMChzZXEoNTAwLDE1MDAsNTAwKSwgIksiKSkpICsNCiAgZ2d0aGVtZXM6OnRoZW1lX3R1ZnRlKCkgKw0KICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIpLA0KICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIpKQ0KDQpwMiA8LSB1bG9nc19wZXJpb2RzICU+JQ0KICBmaWx0ZXIodG90YWxfaHJzX2ltcCA8IHF1YW50aWxlKHVsb2dzX3BlcmlvZHMkdG90YWxfaHJzX2ltcCwgMC45OSwgbmEucm0gPSBUUlVFKSkgJT4lDQogIGdncGxvdCgpICsNCiAgYWVzKHggPSB0b3RhbF9ocnNfaW1wKSArDQogIGdlb21faGlzdG9ncmFtKGJpbndpZHRoID0gMSwgYm91bmRhcnkgPSAwLCBmaWxsID0gIiM0NjgyQjQiKSArDQogIGxhYnMoeCA9ICJIb3VycyIsIHkgPSAiIiwgdGl0bGUgPSBwYXN0ZTAodmFyX2xhYmVsKHVsb2dzX3BlcmlvZHMpJHRvdGFsX2hyc19pbXAsICJcbihvYnNlcnZhdGlvbnMgd2l0aCB2YWx1ZXMgbGVzcyB0aGFuIDk5dGggcGVyY2VudGlsZSkiKSkgKw0KICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gc2VxKDAsIDEyLCAxKSkgKw0KICBzY2FsZV95X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLDApLCBicmVha3MgPSBzZXEoMCwxNTAwMDAwLDUwMDAwMCksIA0KICAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gYygwLCBwYXN0ZTAoc2VxKDUwMCwxNTAwLDUwMCksICJLIikpKSArDQogIGdndGhlbWVzOjp0aGVtZV90dWZ0ZSgpICsNCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiKSwNCiAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyKSkNCg0KZ2dhcnJhbmdlKHAxLHAyLCBucm93ID0gMSwgbmNvbCA9IDIpDQpgYGANCg0KYGBge3IgdG90YWxfc2Vjc19wbHRfcGxvdCwgZmlnLndpZHRoPTEyLCBmaWcuaGVpZ2h0PTMsIGV2YWw9VFJVRX0NCnAxIDwtIHVsb2dzX3BlcmlvZHMgJT4lDQogIGZpbHRlcihhdl9taW5zX3BsYXllZCA8IHF1YW50aWxlKHVsb2dzX3BlcmlvZHMkYXZfbWluc19wbGF5ZWQsIDAuOTksIG5hLnJtID0gVFJVRSkpICU+JQ0KICBnZ3Bsb3QoKSArDQogIGFlcyh4ID0gYXZfbWluc19wbGF5ZWQpICsNCiAgZ2VvbV9oaXN0b2dyYW0oYmlud2lkdGggPSAxLCBib3VuZGFyeSA9IDAsIGZpbGwgPSAiIzQ2ODJCNCIpICsNCiAgbGFicyh4ID0gIk1pbnV0ZXMiLCB5ID0gIiIsIHRpdGxlID0gcGFzdGUwKHZhcl9sYWJlbCh1bG9nc19wZXJpb2RzKSRhdl9taW5zX3BsYXllZCwgIlxuKG9ic2VydmF0aW9ucyB3aXRoIHZhbHVlcyBsZXNzIHRoYW4gOTl0aCBwZXJjZW50aWxlKSIpKSArDQogIHNjYWxlX3hfY29udGludW91cyhicmVha3MgPSBzZXEoMCwgNSwgMSkpICsNCiAgc2NhbGVfeV9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwwKSwgYnJlYWtzID0gc2VxKDAsMTUwMDAwMCw1MDAwMDApLCANCiAgICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IGMoMCwgcGFzdGUwKHNlcSg1MDAsMTUwMCw1MDApLCAiSyIpKSkgKw0KICBnZ3RoZW1lczo6dGhlbWVfdHVmdGUoKSArDQogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiksDQogICAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMikpDQoNCnAyIDwtIHVsb2dzX3BlcmlvZHMgJT4lDQogIGZpbHRlcih0b3RhbF9ocnNfaW1wIDwgcXVhbnRpbGUodWxvZ3NfcGVyaW9kcyR0b3RhbF9ocnNfaW1wLCAwLjk5LCBuYS5ybSA9IFRSVUUpKSAlPiUNCiAgZ2dwbG90KCkgKw0KICBhZXMoeCA9IHRvdGFsX2hyc19pbXApICsNCiAgZ2VvbV9oaXN0b2dyYW0oYmlud2lkdGggPSAxLCBib3VuZGFyeSA9IDAsIGZpbGwgPSAiIzQ2ODJCNCIpICsNCiAgbGFicyh4ID0gIkhvdXJzIiwgeSA9ICIiLCB0aXRsZSA9IHBhc3RlMCh2YXJfbGFiZWwodWxvZ3NfcGVyaW9kcykkdG90YWxfaHJzX2ltcCwgIlxuKG9ic2VydmF0aW9ucyB3aXRoIHZhbHVlcyBsZXNzIHRoYW4gOTl0aCBwZXJjZW50aWxlKSIpKSArDQogIHNjYWxlX3hfY29udGludW91cyhicmVha3MgPSBzZXEoMCwgMTIsIDEpKSArDQogIHNjYWxlX3lfY29udGludW91cyhleHBhbmQgPSBjKDAsMCksIGJyZWFrcyA9IHNlcSgwLDE1MDAwMDAsNTAwMDAwKSwgDQogICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBjKDAsIHBhc3RlMChzZXEoNTAwLDE1MDAsNTAwKSwgIksiKSkpICsNCiAgZ2d0aGVtZXM6OnRoZW1lX3R1ZnRlKCkgKw0KICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIpLA0KICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIpKQ0KDQpnZ2FycmFuZ2UocDEscDIsIG5yb3cgPSAxLCBuY29sID0gMikNCmBgYA0KDQo8YnI+DQoNCmBgYHtyIHRvdGFsX3NlY3NfbnVtX3NvbmdzfQ0KcDEgPC0gZGJwbG90X3Jhc3Rlcih1bG9nc19wZXJpb2RzLA0KICAgICAgICAgICAgICB4ID0gbnVtX3RvdCwgeSA9IHRvdGFsX2hyc19pbXApICsNCiAgc2NhbGVfeV9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwwKSwgYnJlYWtzID0gc2VxKDAsMjQsMyksIGxpbWl0cyA9IGMoMCwyNCkpICsNCiAgc2NhbGVfeF9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwwKSwgYnJlYWtzID0gc2VxKDAsNjAwMCwxMDAwKSkgKw0KICBzY2FsZV9maWxsX3ZpcmlkaXNfYygpICsNCiAgbGFicyh4ID0gIlRvdGFsIG51bWJlciBvZiBzb25ncyBwbGF5ZWQiLCB5ID0gIlRvdGFsIGhvdXJzIHBsYXllZCIsDQogICAgICAgdGl0bGUgPSAiVG90YWwgaG91cnMgcGxheWVkIHZzLiB0b3RhbCBudW1lciBvZiBzb25ncyIsDQogICAgICAgZmlsbCA9ICJOdW1iZXIgb2Ygb2JzLiIpICsNCiAgZ2d0aGVtZXM6OnRoZW1lX3R1ZnRlKCkgKw0KICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIpLA0KICAgICAgICBsZWdlbmQua2V5LndpZHRoID0gdW5pdCgxLCJjbSIpKQ0KDQpwMQ0KYGBgDQoNCmBgYHtyIHRvdGFsX3NlY3NfbnVtX3NvbmdzX3Bsb3QsIG1lc3NhZ2U9RkFMU0UsIGZpZy5oZWlnaHQ9NCwgZmlnLndpZHRoPTEwLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBldmFsPVRSVUV9DQpybShwMSxwMikNCg0KcDEgPC0gZGJwbG90X3Jhc3Rlcih1bG9nc19wZXJpb2RzLA0KICAgICAgICAgICAgICB4ID0gbnVtX3RvdCwgeSA9IHRvdGFsX2hyc19pbXApICsNCiAgc2NhbGVfeV9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwwKSwgYnJlYWtzID0gc2VxKDAsMjQsMyksIGxpbWl0cyA9IGMoMCwyNCkpICsNCiAgc2NhbGVfeF9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwwKSwgYnJlYWtzID0gc2VxKDAsNjAwMCwxMDAwKSkgKw0KICBzY2FsZV9maWxsX3ZpcmlkaXNfYygpICsNCiAgbGFicyh4ID0gIlRvdGFsIG51bWJlciBvZiBzb25ncyBwbGF5ZWQiLCB5ID0gIlRvdGFsIGhvdXJzIHBsYXllZCIsDQogICAgICAgdGl0bGUgPSAiVG90YWwgaG91cnMgcGxheWVkIHZzLiB0b3RhbCBudW1lciBvZiBzb25ncyIsDQogICAgICAgZmlsbCA9ICJOdW1iZXIgb2Ygb2JzLiIpICsNCiAgZ2d0aGVtZXM6OnRoZW1lX3R1ZnRlKCkgKw0KICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIpLA0KICAgICAgICBsZWdlbmQua2V5LndpZHRoID0gdW5pdCgxLCJjbSIpKQ0KDQpwMQ0KYGBgDQoNCjxicj4NCg0KSW4gYWRkaXRpb24gdG8gYWJvdmUgc3RhdGlzdGljcyB3ZSBoYXZlIGVzdGltYXRlZCBob3cgbXVjaCBkYXlzIG91dCBvZiB0aGUgY29ycmVzcG9uZGluZyBzdWJzY3JpcHRpb24gcGVyaW9kcyBhIHVzZXIgbG9nZ2VkIGludG8gdGhlIHNlcnZpY2UgKHdhcyBhY3RpdmUpIC0gYGRheXNfYWN0aXZlYCwgYW5kIHRoZSBzaGFyZSBvZiBhY3RpdmUgZGF5cyBpbiB0aGUgZnVsbCBsZW5ndGggb2YgdGhlIHN1YnNjcmlwdGlvbiBwZXJpb2QgKGluICUpIC0gYHBjdF9hY3RpdmVgLiBXZSBkaWQgdGhlIHNhbWUgZm9yIHRoZSBsYXN0IDMwLCAxNCBhbmQgNyBkYXlzIG9mIGVhY2ggc3Vic2NyaXB0aW9uIHBlcmlvZC4NCg0KYGBge3IgYWN0aXZpdHlfYWxsX2xhc3R9DQp1bG9nc19sYXN0IDwtIHVsb2dzX3BlcmlvZHMgJT4lDQogIGdyb3VwX2J5KHN1YnNjcl9pZCkgJT4lDQogIHN1bW1hcmlzZShkYXlzX2FjdGl2ZSA9IG4oKSwNCiAgICAgICAgICAgIG1lYW5fbnVtX3VucSA9IHN1bShudW1fdW5xKSwNCiAgICAgICAgICAgIG1lYW5fbnVtX3RvdCA9IHN1bShudW1fdG90KSwNCiAgICAgICAgICAgIG1lYW5fYXZfcGN0X3BsYXllZCA9IHN1bShhdl9wY3RfcGxheWVkKSwNCiAgICAgICAgICAgIG1lYW5fdG90YWxfc2Vjc19pbXAgPSBzdW0odG90YWxfc2Vjc19pbXApLA0KICAgICAgICAgICAgbWVhbl90b3RhbF9ocnNfaW1wID0gc3VtKHRvdGFsX2hyc19pbXApLA0KICAgICAgICAgICAgbWVhbl9hdl9zZWNzX3BsYXllZCA9IHN1bShhdl9zZWNzX3BsYXllZCksDQogICAgICAgICAgICBtZWFuX2F2X21pbnNfcGxheWVkID0gc3VtKGF2X21pbnNfcGxheWVkKSwNCiAgICAgICAgICAgIG1lYW5fc2hfMjUgPSBzdW0oc2hfMjUpLA0KICAgICAgICAgICAgbWVhbl9zaF85ODUgPSBzdW0oc2hfOTg1KSwNCiAgICAgICAgICAgIG1lYW5fc2hfMTAwID0gc3VtKHNoXzEwMCksDQogICAgICAgICAgICBsYXN0MzBkID0gYXMubnVtZXJpYyhlbmRfZGF0ZSAtIGRhdGUsICdkYXlzJykgPCAzMCwNCiAgICAgICAgICAgIGxhc3QxNGQgPSBhcy5udW1lcmljKGVuZF9kYXRlIC0gZGF0ZSwgJ2RheXMnKSA8IDE0LA0KICAgICAgICAgICAgbGFzdDdkID0gYXMubnVtZXJpYyhlbmRfZGF0ZSAtIGRhdGUsICdkYXlzJykgPCA3LA0KICAgICAgICAgICAgZGF5c19hY3RpdmUzMCA9IHN1bShsYXN0MzBkKSwNCiAgICAgICAgICAgIG1lYW5fbnVtX3VucTMwID0gc3VtKG51bV91bnFbbGFzdDMwZF0pLA0KICAgICAgICAgICAgbWVhbl9udW1fdG90MzAgPSBzdW0obnVtX3RvdFtsYXN0MzBkXSksDQogICAgICAgICAgICBtZWFuX2F2X3BjdF9wbGF5ZWQzMCA9IHN1bShhdl9wY3RfcGxheWVkW2xhc3QzMGRdKSwNCiAgICAgICAgICAgIG1lYW5fdG90YWxfc2Vjc19pbXAzMCA9IHN1bSh0b3RhbF9zZWNzX2ltcFtsYXN0MzBkXSksDQogICAgICAgICAgICBtZWFuX3RvdGFsX2hyc19pbXAzMCA9IHN1bSh0b3RhbF9ocnNfaW1wW2xhc3QzMGRdKSwNCiAgICAgICAgICAgIG1lYW5fYXZfc2Vjc19wbGF5ZWQzMCA9IHN1bShhdl9zZWNzX3BsYXllZFtsYXN0MzBkXSksDQogICAgICAgICAgICBtZWFuX2F2X21pbnNfcGxheWVkMzAgPSBzdW0oYXZfbWluc19wbGF5ZWRbbGFzdDMwZF0pLA0KICAgICAgICAgICAgbWVhbl9zaF8yNTMwID0gc3VtKHNoXzI1W2xhc3QzMGRdKSwNCiAgICAgICAgICAgIG1lYW5fc2hfOTg1MzAgPSBzdW0oc2hfOTg1W2xhc3QzMGRdKSwNCiAgICAgICAgICAgIG1lYW5fc2hfMTAwMzAgPSBzdW0oc2hfMTAwW2xhc3QzMGRdKSwNCiAgICAgICAgICAgIGRheXNfYWN0aXZlMTQgPSBzdW0obGFzdDE0ZCksDQogICAgICAgICAgICBtZWFuX251bV91bnExNCA9IHN1bShudW1fdW5xW2xhc3QxNGRdKSwNCiAgICAgICAgICAgIG1lYW5fbnVtX3RvdDE0ID0gc3VtKG51bV90b3RbbGFzdDE0ZF0pLA0KICAgICAgICAgICAgbWVhbl9hdl9wY3RfcGxheWVkMTQgPSBzdW0oYXZfcGN0X3BsYXllZFtsYXN0MTRkXSksDQogICAgICAgICAgICBtZWFuX3RvdGFsX3NlY3NfaW1wMTQgPSBzdW0odG90YWxfc2Vjc19pbXBbbGFzdDE0ZF0pLA0KICAgICAgICAgICAgbWVhbl90b3RhbF9ocnNfaW1wMTQgPSBzdW0odG90YWxfaHJzX2ltcFtsYXN0MTRkXSksDQogICAgICAgICAgICBtZWFuX2F2X3NlY3NfcGxheWVkMTQgPSBzdW0oYXZfc2Vjc19wbGF5ZWRbbGFzdDE0ZF0pLA0KICAgICAgICAgICAgbWVhbl9hdl9taW5zX3BsYXllZDE0ID0gc3VtKGF2X21pbnNfcGxheWVkW2xhc3QxNGRdKSwNCiAgICAgICAgICAgIG1lYW5fc2hfMjUxNCA9IHN1bShzaF8yNVtsYXN0MTRkXSksDQogICAgICAgICAgICBtZWFuX3NoXzk4NTE0ID0gc3VtKHNoXzk4NVtsYXN0MTRkXSksDQogICAgICAgICAgICBtZWFuX3NoXzEwMDE0ID0gc3VtKHNoXzEwMFtsYXN0MTRkXSksDQogICAgICAgICAgICBkYXlzX2FjdGl2ZTcgPSBzdW0obGFzdDdkKSwNCiAgICAgICAgICAgIG1lYW5fbnVtX3VucTcgPSBzdW0obnVtX3VucVtsYXN0N2RdKSwNCiAgICAgICAgICAgIG1lYW5fbnVtX3RvdDcgPSBzdW0obnVtX3RvdFtsYXN0N2RdKSwNCiAgICAgICAgICAgIG1lYW5fYXZfcGN0X3BsYXllZDcgPSBzdW0oYXZfcGN0X3BsYXllZFtsYXN0N2RdKSwNCiAgICAgICAgICAgIG1lYW5fdG90YWxfc2Vjc19pbXA3ID0gc3VtKHRvdGFsX3NlY3NfaW1wW2xhc3Q3ZF0pLA0KICAgICAgICAgICAgbWVhbl90b3RhbF9ocnNfaW1wNyA9IHN1bSh0b3RhbF9ocnNfaW1wW2xhc3Q3ZF0pLA0KICAgICAgICAgICAgbWVhbl9hdl9zZWNzX3BsYXllZDcgPSBzdW0oYXZfc2Vjc19wbGF5ZWRbbGFzdDdkXSksDQogICAgICAgICAgICBtZWFuX2F2X21pbnNfcGxheWVkNyA9IHN1bShhdl9taW5zX3BsYXllZFtsYXN0N2RdKSwNCiAgICAgICAgICAgIG1lYW5fc2hfMjU3ID0gc3VtKHNoXzI1W2xhc3Q3ZF0pLA0KICAgICAgICAgICAgbWVhbl9zaF85ODU3ID0gc3VtKHNoXzk4NVtsYXN0N2RdKSwNCiAgICAgICAgICAgIG1lYW5fc2hfMTAwNyA9IHN1bShzaF8xMDBbbGFzdDdkXSkpICU+JQ0KICBzZWxlY3QoLWxhc3QzMGQsIC1sYXN0MTRkLCAtbGFzdDdkKSAlPiUNCiAgZGlzdGluY3QoKQ0KDQp0cmFuc19wZXJpb2RzIDwtIHRyYW5zX3BlcmlvZHMgJT4lDQogIGxlZnRfam9pbih1bG9nc19sYXN0LCBieSA9ICJzdWJzY3JfaWQiKSANCg0KdHJhbnNfcGVyaW9kcyA8LSB0cmFuc19wZXJpb2RzICU+JQ0KICBtdXRhdGUoZGF5c19hY3RpdmUgPSByZXBsYWNlX25hKGRheXNfYWN0aXZlLCAwKSwNCiAgICAgICAgIHBjdF9hY3RpdmUgPSByZXBsYWNlX25hKGRheXNfYWN0aXZlIC8gdGltZSAqIDEwMCwgMCksDQogICAgICAgICB0b3RfbnVtX3RvdCA9IHJlcGxhY2VfbmEobWVhbl9udW1fdG90LDApLA0KICAgICAgICAgdG90X3RvdGFsX3NlY3NfaW1wID0gcmVwbGFjZV9uYShtZWFuX3RvdGFsX3NlY3NfaW1wLDApLA0KICAgICAgICAgbWVhbl9zaF8yNV9hY3QgPSByZXBsYWNlX25hKG1lYW5fc2hfMjUvZGF5c19hY3RpdmUsIDApLA0KICAgICAgICAgbWVhbl9zaF8yNSA9IHJlcGxhY2VfbmEobWVhbl9zaF8yNS90aW1lLCAwKSwNCiAgICAgICAgIG1lYW5fc2hfOTg1X2FjdCA9IHJlcGxhY2VfbmEobWVhbl9zaF85ODUvZGF5c19hY3RpdmUsIDApLA0KICAgICAgICAgbWVhbl9zaF85ODUgPSByZXBsYWNlX25hKG1lYW5fc2hfOTg1L3RpbWUsIDApLA0KICAgICAgICAgbWVhbl9zaF8xMDBfYWN0ID0gcmVwbGFjZV9uYShtZWFuX3NoXzEwMC9kYXlzX2FjdGl2ZSwgMCksDQogICAgICAgICBtZWFuX3NoXzEwMCA9IHJlcGxhY2VfbmEobWVhbl9zaF8xMDAvdGltZSwgMCksDQogICAgICAgICBtZWFuX251bV91bnFfYWN0ID0gcmVwbGFjZV9uYShtZWFuX251bV91bnEvZGF5c19hY3RpdmUsIDApLA0KICAgICAgICAgbWVhbl9udW1fdW5xID0gcmVwbGFjZV9uYShtZWFuX251bV91bnEvdGltZSwgMCksDQogICAgICAgICBtZWFuX251bV90b3RfYWN0ID0gcmVwbGFjZV9uYShtZWFuX251bV90b3QvZGF5c19hY3RpdmUsIDApLA0KICAgICAgICAgbWVhbl9udW1fdG90ID0gcmVwbGFjZV9uYShtZWFuX251bV90b3QvdGltZSwgMCksDQogICAgICAgICBtZWFuX2F2X3BjdF9wbGF5ZWRfYWN0ID0gcmVwbGFjZV9uYShtZWFuX2F2X3BjdF9wbGF5ZWQvZGF5c19hY3RpdmUsIDApLA0KICAgICAgICAgbWVhbl9hdl9wY3RfcGxheWVkID0gcmVwbGFjZV9uYShtZWFuX2F2X3BjdF9wbGF5ZWQvdGltZSwgMCksDQogICAgICAgICBtZWFuX3RvdGFsX3NlY3NfaW1wX2FjdCA9IHJlcGxhY2VfbmEobWVhbl90b3RhbF9zZWNzX2ltcC9kYXlzX2FjdGl2ZSwgMCksDQogICAgICAgICBtZWFuX3RvdGFsX3NlY3NfaW1wID0gcmVwbGFjZV9uYShtZWFuX3RvdGFsX3NlY3NfaW1wL3RpbWUsIDApLA0KICAgICAgICAgbWVhbl90b3RhbF9ocnNfaW1wX2FjdCA9IHJlcGxhY2VfbmEobWVhbl90b3RhbF9ocnNfaW1wL2RheXNfYWN0aXZlLCAwKSwNCiAgICAgICAgIG1lYW5fdG90YWxfaHJzX2ltcCA9IHJlcGxhY2VfbmEobWVhbl90b3RhbF9ocnNfaW1wL3RpbWUsIDApLA0KICAgICAgICAgbWVhbl9hdl9zZWNzX3BsYXllZF9hY3QgPSByZXBsYWNlX25hKG1lYW5fYXZfc2Vjc19wbGF5ZWQvZGF5c19hY3RpdmUsIDApLA0KICAgICAgICAgbWVhbl9hdl9zZWNzX3BsYXllZCA9IHJlcGxhY2VfbmEobWVhbl9hdl9zZWNzX3BsYXllZC90aW1lLCAwKSwNCiAgICAgICAgIG1lYW5fYXZfbWluc19wbGF5ZWRfYWN0ID0gcmVwbGFjZV9uYShtZWFuX2F2X21pbnNfcGxheWVkL2RheXNfYWN0aXZlLCAwKSwNCiAgICAgICAgIG1lYW5fYXZfbWluc19wbGF5ZWQgPSByZXBsYWNlX25hKG1lYW5fYXZfbWluc19wbGF5ZWQvdGltZSwgMCksDQogICAgICAgICBkYXlzX2FjdGl2ZTMwID0gcmVwbGFjZV9uYShkYXlzX2FjdGl2ZTMwLCAwKSwNCiAgICAgICAgIHBjdF9hY3RpdmUzMCA9IHJlcGxhY2VfbmEoZGF5c19hY3RpdmUzMCAvIDMwICogMTAwLCAwKSwNCiAgICAgICAgIHRvdF9udW1fdG90MzAgPSByZXBsYWNlX25hKG1lYW5fbnVtX3RvdDMwLDApLA0KICAgICAgICAgdG90X3RvdGFsX3NlY3NfaW1wMzAgPSByZXBsYWNlX25hKG1lYW5fdG90YWxfc2Vjc19pbXAzMCwgMCksDQogICAgICAgICBtZWFuX3NoXzI1X2FjdDMwID0gcmVwbGFjZV9uYShtZWFuX3NoXzI1MzAvZGF5c19hY3RpdmUsIDApLA0KICAgICAgICAgbWVhbl9zaF8yNTMwID0gcmVwbGFjZV9uYShtZWFuX3NoXzI1MzAvdGltZSwgMCksDQogICAgICAgICBtZWFuX3NoXzk4NV9hY3QzMCA9IHJlcGxhY2VfbmEobWVhbl9zaF85ODUzMC9kYXlzX2FjdGl2ZSwgMCksDQogICAgICAgICBtZWFuX3NoXzk4NTMwID0gcmVwbGFjZV9uYShtZWFuX3NoXzk4NTMwL3RpbWUsIDApLA0KICAgICAgICAgbWVhbl9zaF8xMDBfYWN0MzAgPSByZXBsYWNlX25hKG1lYW5fc2hfMTAwMzAvZGF5c19hY3RpdmUsIDApLA0KICAgICAgICAgbWVhbl9zaF8xMDAzMCA9IHJlcGxhY2VfbmEobWVhbl9zaF8xMDAzMC90aW1lLCAwKSwNCiAgICAgICAgIG1lYW5fbnVtX3VucV9hY3QzMCA9IHJlcGxhY2VfbmEobWVhbl9udW1fdW5xMzAvZGF5c19hY3RpdmUzMCwgMCksDQogICAgICAgICBtZWFuX251bV91bnEzMCA9IHJlcGxhY2VfbmEobWVhbl9udW1fdW5xMzAvMzAsIDApLA0KICAgICAgICAgbWVhbl9udW1fdG90X2FjdDMwID0gcmVwbGFjZV9uYShtZWFuX251bV90b3QzMC9kYXlzX2FjdGl2ZTMwLCAwKSwNCiAgICAgICAgIG1lYW5fbnVtX3RvdDMwID0gcmVwbGFjZV9uYShtZWFuX251bV90b3QzMC8zMCwgMCksDQogICAgICAgICBtZWFuX2F2X3BjdF9wbGF5ZWRfYWN0MzAgPSByZXBsYWNlX25hKG1lYW5fYXZfcGN0X3BsYXllZDMwL2RheXNfYWN0aXZlMzAsIDApLA0KICAgICAgICAgbWVhbl9hdl9wY3RfcGxheWVkMzAgPSByZXBsYWNlX25hKG1lYW5fYXZfcGN0X3BsYXllZDMwLzMwLCAwKSwNCiAgICAgICAgIG1lYW5fdG90YWxfc2Vjc19pbXBfYWN0MzAgPSByZXBsYWNlX25hKG1lYW5fdG90YWxfc2Vjc19pbXAzMC9kYXlzX2FjdGl2ZTMwLCAwKSwNCiAgICAgICAgIG1lYW5fdG90YWxfc2Vjc19pbXAzMCA9IHJlcGxhY2VfbmEobWVhbl90b3RhbF9zZWNzX2ltcDMwLzMwLCAwKSwNCiAgICAgICAgIG1lYW5fdG90YWxfaHJzX2ltcF9hY3QzMCA9IHJlcGxhY2VfbmEobWVhbl90b3RhbF9ocnNfaW1wMzAvZGF5c19hY3RpdmUzMCwgMCksDQogICAgICAgICBtZWFuX3RvdGFsX2hyc19pbXAzMCA9IHJlcGxhY2VfbmEobWVhbl90b3RhbF9ocnNfaW1wMzAvMzAsIDApLA0KICAgICAgICAgbWVhbl9hdl9zZWNzX3BsYXllZF9hY3QzMCA9IHJlcGxhY2VfbmEobWVhbl9hdl9zZWNzX3BsYXllZDMwL2RheXNfYWN0aXZlMzAsIDApLA0KICAgICAgICAgbWVhbl9hdl9zZWNzX3BsYXllZDMwID0gcmVwbGFjZV9uYShtZWFuX2F2X3NlY3NfcGxheWVkMzAvMzAsIDApLA0KICAgICAgICAgbWVhbl9hdl9taW5zX3BsYXllZF9hY3QzMCA9IHJlcGxhY2VfbmEobWVhbl9hdl9taW5zX3BsYXllZDMwL2RheXNfYWN0aXZlMzAsIDApLA0KICAgICAgICAgbWVhbl9hdl9taW5zX3BsYXllZDMwID0gcmVwbGFjZV9uYShtZWFuX2F2X21pbnNfcGxheWVkMzAvMzAsIDApLA0KICAgICAgICAgZGF5c19hY3RpdmUxNCA9IHJlcGxhY2VfbmEoZGF5c19hY3RpdmUxNCwgMCksDQogICAgICAgICBwY3RfYWN0aXZlMTQgPSByZXBsYWNlX25hKGRheXNfYWN0aXZlMTQgLyAxNCAqIDEwMCwgMCksDQogICAgICAgICB0b3RfbnVtX3RvdDE0ID0gcmVwbGFjZV9uYShtZWFuX251bV90b3QxNCwwKSwNCiAgICAgICAgIHRvdF90b3RhbF9zZWNzX2ltcDE0ID0gcmVwbGFjZV9uYShtZWFuX3RvdGFsX3NlY3NfaW1wMTQsIDApLA0KICAgICAgICAgbWVhbl9zaF8yNV9hY3QxNCA9IHJlcGxhY2VfbmEobWVhbl9zaF8yNTE0L2RheXNfYWN0aXZlLCAwKSwNCiAgICAgICAgIG1lYW5fc2hfMjUxNCA9IHJlcGxhY2VfbmEobWVhbl9zaF8yNTE0L3RpbWUsIDApLA0KICAgICAgICAgbWVhbl9zaF85ODVfYWN0MTQgPSByZXBsYWNlX25hKG1lYW5fc2hfOTg1MTQvZGF5c19hY3RpdmUsIDApLA0KICAgICAgICAgbWVhbl9zaF85ODUxNCA9IHJlcGxhY2VfbmEobWVhbl9zaF85ODUxNC90aW1lLCAwKSwNCiAgICAgICAgIG1lYW5fc2hfMTAwX2FjdDE0ID0gcmVwbGFjZV9uYShtZWFuX3NoXzEwMDE0L2RheXNfYWN0aXZlLCAwKSwNCiAgICAgICAgIG1lYW5fc2hfMTAwMTQgPSByZXBsYWNlX25hKG1lYW5fc2hfMTAwMTQvdGltZSwgMCksDQogICAgICAgICBtZWFuX251bV91bnFfYWN0MTQgPSByZXBsYWNlX25hKG1lYW5fbnVtX3VucTE0L2RheXNfYWN0aXZlMTQsIDApLA0KICAgICAgICAgbWVhbl9udW1fdW5xMTQgPSByZXBsYWNlX25hKG1lYW5fbnVtX3VucTE0LzE0LCAwKSwNCiAgICAgICAgIG1lYW5fbnVtX3RvdF9hY3QxNCA9IHJlcGxhY2VfbmEobWVhbl9udW1fdG90MTQvZGF5c19hY3RpdmUxNCwgMCksDQogICAgICAgICBtZWFuX251bV90b3QxNCA9IHJlcGxhY2VfbmEobWVhbl9udW1fdG90MTQvMTQsIDApLA0KICAgICAgICAgbWVhbl9hdl9wY3RfcGxheWVkX2FjdDE0ID0gcmVwbGFjZV9uYShtZWFuX2F2X3BjdF9wbGF5ZWQxNC9kYXlzX2FjdGl2ZTE0LCAwKSwNCiAgICAgICAgIG1lYW5fYXZfcGN0X3BsYXllZDE0ID0gcmVwbGFjZV9uYShtZWFuX2F2X3BjdF9wbGF5ZWQxNC8xNCwgMCksDQogICAgICAgICBtZWFuX3RvdGFsX3NlY3NfaW1wX2FjdDE0ID0gcmVwbGFjZV9uYShtZWFuX3RvdGFsX3NlY3NfaW1wMTQvZGF5c19hY3RpdmUxNCwgMCksDQogICAgICAgICBtZWFuX3RvdGFsX3NlY3NfaW1wMTQgPSByZXBsYWNlX25hKG1lYW5fdG90YWxfc2Vjc19pbXAxNC8xNCwgMCksDQogICAgICAgICBtZWFuX3RvdGFsX2hyc19pbXBfYWN0MTQgPSByZXBsYWNlX25hKG1lYW5fdG90YWxfaHJzX2ltcDE0L2RheXNfYWN0aXZlMTQsIDApLA0KICAgICAgICAgbWVhbl90b3RhbF9ocnNfaW1wMTQgPSByZXBsYWNlX25hKG1lYW5fdG90YWxfaHJzX2ltcDE0LzE0LCAwKSwNCiAgICAgICAgIG1lYW5fYXZfc2Vjc19wbGF5ZWRfYWN0MTQgPSByZXBsYWNlX25hKG1lYW5fYXZfc2Vjc19wbGF5ZWQxNC9kYXlzX2FjdGl2ZTE0LCAwKSwNCiAgICAgICAgIG1lYW5fYXZfc2Vjc19wbGF5ZWQxNCA9IHJlcGxhY2VfbmEobWVhbl9hdl9zZWNzX3BsYXllZDE0LzE0LCAwKSwNCiAgICAgICAgIG1lYW5fYXZfbWluc19wbGF5ZWRfYWN0MTQgPSByZXBsYWNlX25hKG1lYW5fYXZfbWluc19wbGF5ZWQxNC9kYXlzX2FjdGl2ZTE0LCAwKSwNCiAgICAgICAgIG1lYW5fYXZfbWluc19wbGF5ZWQxNCA9IHJlcGxhY2VfbmEobWVhbl9hdl9taW5zX3BsYXllZDE0LzE0LCAwKSwNCiAgICAgICAgIGRheXNfYWN0aXZlNyA9IHJlcGxhY2VfbmEoZGF5c19hY3RpdmU3LCAwKSwNCiAgICAgICAgIHBjdF9hY3RpdmU3ID0gcmVwbGFjZV9uYShkYXlzX2FjdGl2ZTcgLyA3ICogMTAwLCAwKSwNCiAgICAgICAgIHRvdF9udW1fdG90NyA9IHJlcGxhY2VfbmEobWVhbl9udW1fdG90NywwKSwNCiAgICAgICAgIHRvdF90b3RhbF9zZWNzX2ltcDcgPSByZXBsYWNlX25hKG1lYW5fdG90YWxfc2Vjc19pbXA3LCAwKSwNCiAgICAgICAgIG1lYW5fc2hfMjVfYWN0NyA9IHJlcGxhY2VfbmEobWVhbl9zaF8yNTcvZGF5c19hY3RpdmUsIDApLA0KICAgICAgICAgbWVhbl9zaF8yNTcgPSByZXBsYWNlX25hKG1lYW5fc2hfMjU3L3RpbWUsIDApLA0KICAgICAgICAgbWVhbl9zaF85ODVfYWN0NyA9IHJlcGxhY2VfbmEobWVhbl9zaF85ODU3L2RheXNfYWN0aXZlLCAwKSwNCiAgICAgICAgIG1lYW5fc2hfOTg1NyA9IHJlcGxhY2VfbmEobWVhbl9zaF85ODU3L3RpbWUsIDApLA0KICAgICAgICAgbWVhbl9zaF8xMDBfYWN0NyA9IHJlcGxhY2VfbmEobWVhbl9zaF8xMDA3L2RheXNfYWN0aXZlLCAwKSwNCiAgICAgICAgIG1lYW5fc2hfMTAwNyA9IHJlcGxhY2VfbmEobWVhbl9zaF8xMDA3L3RpbWUsIDApLA0KICAgICAgICAgbWVhbl9udW1fdW5xX2FjdDcgPSByZXBsYWNlX25hKG1lYW5fbnVtX3VucTcvZGF5c19hY3RpdmU3LCAwKSwNCiAgICAgICAgIG1lYW5fbnVtX3VucTcgPSByZXBsYWNlX25hKG1lYW5fbnVtX3VucTcvNywgMCksDQogICAgICAgICBtZWFuX251bV90b3RfYWN0NyA9IHJlcGxhY2VfbmEobWVhbl9udW1fdG90Ny9kYXlzX2FjdGl2ZTcsIDApLA0KICAgICAgICAgbWVhbl9udW1fdG90NyA9IHJlcGxhY2VfbmEobWVhbl9udW1fdG90Ny83LCAwKSwNCiAgICAgICAgIG1lYW5fYXZfcGN0X3BsYXllZF9hY3Q3ID0gcmVwbGFjZV9uYShtZWFuX2F2X3BjdF9wbGF5ZWQ3L2RheXNfYWN0aXZlNywgMCksDQogICAgICAgICBtZWFuX2F2X3BjdF9wbGF5ZWQ3ID0gcmVwbGFjZV9uYShtZWFuX2F2X3BjdF9wbGF5ZWQ3LzcsIDApLA0KICAgICAgICAgbWVhbl90b3RhbF9zZWNzX2ltcF9hY3Q3ID0gcmVwbGFjZV9uYShtZWFuX3RvdGFsX3NlY3NfaW1wNy9kYXlzX2FjdGl2ZTcsIDApLA0KICAgICAgICAgbWVhbl90b3RhbF9zZWNzX2ltcDcgPSByZXBsYWNlX25hKG1lYW5fdG90YWxfc2Vjc19pbXA3LzcsIDApLA0KICAgICAgICAgbWVhbl90b3RhbF9ocnNfaW1wX2FjdDcgPSByZXBsYWNlX25hKG1lYW5fdG90YWxfaHJzX2ltcDcvZGF5c19hY3RpdmU3LCAwKSwNCiAgICAgICAgIG1lYW5fdG90YWxfaHJzX2ltcDcgPSByZXBsYWNlX25hKG1lYW5fdG90YWxfaHJzX2ltcDcvNywgMCksDQogICAgICAgICBtZWFuX2F2X3NlY3NfcGxheWVkX2FjdDcgPSByZXBsYWNlX25hKG1lYW5fYXZfc2Vjc19wbGF5ZWQ3L2RheXNfYWN0aXZlNywgMCksDQogICAgICAgICBtZWFuX2F2X3NlY3NfcGxheWVkNyA9IHJlcGxhY2VfbmEobWVhbl9hdl9zZWNzX3BsYXllZDcvNywgMCksDQogICAgICAgICBtZWFuX2F2X21pbnNfcGxheWVkX2FjdDcgPSByZXBsYWNlX25hKG1lYW5fYXZfbWluc19wbGF5ZWQ3L2RheXNfYWN0aXZlNywgMCksDQogICAgICAgICBtZWFuX2F2X21pbnNfcGxheWVkNyA9IHJlcGxhY2VfbmEobWVhbl9hdl9taW5zX3BsYXllZDcvNywgMCkpDQoNCnRyYW5zX2xhZ3MgPC0gdHJhbnNfcGVyaW9kcyAlPiUNCiAgYXJyYW5nZShtc25vLCBtc25vX3N1YnNjcl9pZCkgJT4lDQogIGdyb3VwX2J5KG1zbm8pICU+JQ0KICBmaWx0ZXIobigpID4gMSkgJT4lDQogIHRyYW5zbXV0ZShzdWJzY3JfaWQgPSB1bmlxdWUoc3Vic2NyX2lkKSwNCiAgICAgICAgICAgIA0KICAgICAgICAgICAgbGFnX3RpbWUgPSByZXBsYWNlX25hKGxhZyh0aW1lKSwgMCksDQogICAgICAgICAgICBwcmV2X3RpbWUgPSBjdW1zdW0ocmVwbGFjZV9uYShsYWcodGltZSksIDApKSwNCiAgICAgICAgICAgIA0KICAgICAgICAgICAgbGFnX2RheXNfYWN0aXZlID0gcmVwbGFjZV9uYShsYWcoZGF5c19hY3RpdmUpLCAwKSwNCiAgICAgICAgICAgIHByZXZfZGF5c19hY3RpdmUgPSBjdW1zdW0ocmVwbGFjZV9uYShsYWcoZGF5c19hY3RpdmUpLCAwKSksDQogICAgICAgICAgICANCiAgICAgICAgICAgIGxhZ19wY3RfYWN0aXZlID0gcmVwbGFjZV9uYShsYWcocGN0X2FjdGl2ZSksIDApLA0KICAgICAgICAgICAgcHJldl9wY3RfYWN0aXZlID0gcmVwbGFjZV9uYShwcmV2X2RheXNfYWN0aXZlL3ByZXZfdGltZSwgMCksDQogICAgICAgICAgICANCiAgICAgICAgICAgIGxhZ190b3RfbnVtX3RvdCA9IHJlcGxhY2VfbmEobGFnKHRvdF9udW1fdG90KSwgMCksDQogICAgICAgICAgICBwcmV2X3RvdF9udW1fdG90ID0gY3Vtc3VtKHJlcGxhY2VfbmEobGFnKHRvdF9udW1fdG90KSwgMCkpLA0KICAgICAgICAgICAgDQogICAgICAgICAgICBsYWdfdG90X3RvdGFsX3NlY3NfaW1wID0gcmVwbGFjZV9uYShsYWcodG90X3RvdGFsX3NlY3NfaW1wKSwgMCksDQogICAgICAgICAgICBwcmV2X3RvdF90b3RhbF9zZWNzX2ltcCA9IGN1bXN1bShyZXBsYWNlX25hKGxhZyh0b3RfdG90YWxfc2Vjc19pbXApLCAwKSksDQogICAgICAgICAgICANCiAgICAgICAgICAgIGxhZ19tZWFuX251bV90b3QgPSByZXBsYWNlX25hKGxhZ190b3RfbnVtX3RvdC9sYWdfdGltZSwgMCksDQogICAgICAgICAgICBwcmV2X21lYW5fbnVtX3RvdCA9IHJlcGxhY2VfbmEocHJldl90b3RfbnVtX3RvdC9wcmV2X3RpbWUsIDApLA0KICAgICAgICAgICAgDQogICAgICAgICAgICBsYWdfbWVhbl9udW1fdG90X2FjdCA9IHJlcGxhY2VfbmEobGFnX3RvdF9udW1fdG90L2xhZ19kYXlzX2FjdGl2ZSwgMCksDQogICAgICAgICAgICBwcmV2X21lYW5fbnVtX3RvdF9hY3QgPSByZXBsYWNlX25hKHByZXZfdG90X251bV90b3QvcHJldl9kYXlzX2FjdGl2ZSwgMCksDQogICAgICAgICAgICANCiAgICAgICAgICAgIGxhZ19tZWFuX3RvdGFsX3NlY3NfaW1wID0gcmVwbGFjZV9uYShsYWdfdG90X3RvdGFsX3NlY3NfaW1wL2xhZ190aW1lLCAwKSwNCiAgICAgICAgICAgIHByZXZfbWVhbl90b3RhbF9zZWNzX2ltcCA9IHJlcGxhY2VfbmEocHJldl90b3RfdG90YWxfc2Vjc19pbXAvcHJldl90aW1lLCAwKSwNCiAgICAgICAgICAgIA0KICAgICAgICAgICAgbGFnX21lYW5fdG90YWxfc2Vjc19pbXBfYWN0ID0gcmVwbGFjZV9uYShsYWdfdG90X3RvdGFsX3NlY3NfaW1wL2xhZ19kYXlzX2FjdGl2ZSwgMCksDQogICAgICAgICAgICBwcmV2X21lYW5fdG90YWxfc2Vjc19pbXBfYWN0ID0gcmVwbGFjZV9uYShwcmV2X3RvdF90b3RhbF9zZWNzX2ltcC9wcmV2X2RheXNfYWN0aXZlLCAwKSwNCiAgICAgICAgICAgIA0KICAgICAgICAgICAgbGFnX21lYW5fYXZfcGN0X3BsYXllZCA9IHJlcGxhY2VfbmEobGFnKG1lYW5fYXZfcGN0X3BsYXllZCksIDApLA0KICAgICAgICAgICAgcHJldl9tZWFuX2F2X3BjdF9wbGF5ZWQgPSByZXBsYWNlX25hKGN1bXN1bShyZXBsYWNlX25hKGxhZyhtZWFuX2F2X3BjdF9wbGF5ZWQpLCAwKSkvKG4oKS0xKSksDQogICAgICAgICAgICANCiAgICAgICAgICAgIGxhZ19tZWFuX2F2X3BjdF9wbGF5ZWRfYWN0ID0gcmVwbGFjZV9uYShsYWcobWVhbl9hdl9wY3RfcGxheWVkX2FjdCksIDApLA0KICAgICAgICAgICAgcHJldl9tZWFuX2F2X3BjdF9wbGF5ZWRfYWN0ID0gcmVwbGFjZV9uYShjdW1zdW0ocmVwbGFjZV9uYShsYWcobWVhbl9hdl9wY3RfcGxheWVkX2FjdCksIDApKS8obigpLTEpKSwNCiAgICAgICAgICAgIA0KICAgICAgICAgICAgbGFnX21lYW5fbnVtX3VucSA9IHJlcGxhY2VfbmEobGFnKG1lYW5fbnVtX3VucSksIDApLA0KICAgICAgICAgICAgcHJldl9tZWFuX251bV91bnEgPSByZXBsYWNlX25hKGN1bXN1bShyZXBsYWNlX25hKGxhZyhtZWFuX251bV91bnEpLCAwKSkvKG4oKS0xKSksDQogICAgICAgICAgICANCiAgICAgICAgICAgIGxhZ19tZWFuX251bV91bnFfYWN0ID0gcmVwbGFjZV9uYShsYWcobWVhbl9udW1fdW5xX2FjdCksIDApLA0KICAgICAgICAgICAgcHJldl9tZWFuX251bV91bnFfYWN0ID0gcmVwbGFjZV9uYShjdW1zdW0ocmVwbGFjZV9uYShsYWcobWVhbl9udW1fdW5xX2FjdCksIDApKS8obigpLTEpKSwNCiAgICAgICAgICAgIA0KICAgICAgICAgICAgbGFnX21vZGVfcGF5bWVudF9pZCA9IGZhY3RvcihyZXBsYWNlX25hKGxhZyhhcy5udW1lcmljKGFzLmNoYXJhY3Rlcihtb2RlX3BheW1lbnRfbWV0aG9kX2lkKSkpLCAwKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYygwLCBhcy5udW1lcmljKGxldmVscyh0cmFuc19kZiRwYXltZW50X21ldGhvZF9pZCkpKSksDQogICAgICAgICAgICBwcmV2X21vZGVfcGF5bWVudF9pZCA9IGZhY3RvcihyZXBsYWNlX25hKG1vZGUoYXMubnVtZXJpYyhhcy5jaGFyYWN0ZXIobW9kZV9wYXltZW50X21ldGhvZF9pZFstbigpXSkpKSwgMCksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjKDAsIGFzLm51bWVyaWMobGV2ZWxzKHRyYW5zX2RmJHBheW1lbnRfbWV0aG9kX2lkKSksIDk5OSkpLA0KICAgICAgICAgICAgDQogICAgICAgICAgICBsYWdfc3VtX2FjdHVhbF9hbW91bnRfcGFpZCA9IHJlcGxhY2VfbmEobGFnKHN1bV9hY3R1YWxfYW1vdW50X3BhaWQpLCAwKSwNCiAgICAgICAgICAgIHByZXZfc3VtX2FjdHVhbF9hbW91bnRfcGFpZCA9IGN1bXN1bShyZXBsYWNlX25hKGxhZyhzdW1fYWN0dWFsX2Ftb3VudF9wYWlkKSwgMCkpLA0KICAgICAgICAgICAgDQogICAgICAgICAgICBsYWdfbWVhbl9hY3R1YWxfYW1vdW50X3BhaWQgPSByZXBsYWNlX25hKGxhZ19zdW1fYWN0dWFsX2Ftb3VudF9wYWlkL2xhZ190aW1lLCAwKSwNCiAgICAgICAgICAgIHByZXZfbWVhbl9hY3R1YWxfYW1vdW50X3BhaWQgPSByZXBsYWNlX25hKHByZXZfc3VtX2FjdHVhbF9hbW91bnRfcGFpZC9wcmV2X3RpbWUsIDApLA0KICAgICAgICAgICAgDQogICAgICAgICAgICBsYWdfbnVtX2NhbmNlbCA9IHJlcGxhY2VfbmEobGFnKG5fY2FuY2VsKSwgMCksDQogICAgICAgICAgICBwcmV2X251bV9jYW5jZWwgPSBjdW1zdW0ocmVwbGFjZV9uYShsYWcobl9jYW5jZWwpLCAwKSkpICU+JQ0KICB1bmdyb3VwKCkNCg0KdHJhbnNfcGVyaW9kcyA8LSB0cmFuc19wZXJpb2RzICU+JQ0KICBsZWZ0X2pvaW4odHJhbnNfbGFncywgYnkgPSBjKCJtc25vIiwgInN1YnNjcl9pZCIpKSAlPiUNCiAgbXV0YXRlX2F0KHZhcnMoY29udGFpbnMoImxhZ18iKSwgZHBseXI6OnN0YXJ0c193aXRoKCJwcmV2XyIpKSwgfiByZXBsYWNlX25hKC4sIDApKQ0KDQp0cmFuc19wZXJpb2RzIDwtIHRyYW5zX3BlcmlvZHMgJT4lDQogIG11dGF0ZShsYWdfbW9kZV9wYXltZW50X2lkID0gZmN0X2x1bXBfbWluKGxhZ19tb2RlX3BheW1lbnRfaWQsIG1pbiA9IDMwLCBvdGhlcl9sZXZlbCA9ICI5OTkiKSwNCiAgICAgICAgIHByZXZfbW9kZV9wYXltZW50X2lkID0gZmN0X2x1bXBfbWluKGxhZ19tb2RlX3BheW1lbnRfaWQsIG1pbiA9IDMwLCBvdGhlcl9sZXZlbCA9ICI5OTkiKSkNCmBgYA0KDQpgYGB7ciBhY3Rpdml0eV9kZXNjfQ0KdmFyX2xhYmVsKHRyYW5zX3BlcmlvZHMpIDwtIGxpc3QodGltZSA9ICJTdWJzY3JpcHRpb24gZHVyYXRpb24sIGRheXMiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGN0X2FjdGl2ZSA9ICIlb2YgYWN0aXZlIGRheXMgaW4gc3Vic2NyaXB0aW9uIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBjdF9hY3RpdmUzMCA9ICIlb2YgYWN0aXZlIGRheXMgaW4gdGhlIGxhc3QgMzAgZGF5cyBvZiBzdWJzY3JpcHRpb24iLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGN0X2FjdGl2ZTE0ID0gIiVvZiBhY3RpdmUgZGF5cyBpbiB0aGUgbGFzdCAxNCBkYXlzIG9mIHN1YnNjcmlwdGlvbiIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwY3RfYWN0aXZlNyA9ICIlb2YgYWN0aXZlIGRheXMgaW4gdGhlIGxhc3QgNyBkYXlzIG9mIHN1YnNjcmlwdGlvbiIpDQoNCnRibF9zdW1tYXJ5KA0KICBhcy5kYXRhLmZyYW1lKHRyYW5zX3BlcmlvZHMgJT4lDQogICAgICAgICAgICAgICAgICBzZWxlY3QoY2h1cm4sIHRpbWUsIGNvbnRhaW5zKCJwY3RfYWN0aXZlIikpICU+JQ0KICAgICAgICAgICAgICAgICAgbXV0YXRlKGNodXJuID0gZmFjdG9yKGNodXJuLCBsYWJlbHMgPSBjKCJDaHVybiA9IDAiLCAiQ2h1cm4gPSAxIikpKSksDQogIGJ5ID0gImNodXJuIiwgDQogIHR5cGUgPSBsaXN0KGFsbF9jb250aW51b3VzKCkgfiAiY29udGludW91czIiLA0KICAgICAgICAgICAgICBwY3RfYWN0aXZlNyB+ICJjb250aW51b3VzMiIpLA0KICBzdGF0aXN0aWMgPSBsaXN0KGFsbF9jb250aW51b3VzKCkgfiBjKCJ7bWVhbn0gKHtzZH0pIiwgInttZWRpYW59ICh7cDI1fS17cDc1fSkiLCAgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgInttaW59LXttYXh9IikpLA0KICBkaWdpdHMgPSBsaXN0KGFsbF9jb250aW51b3VzKCkgfiBjKHJlcCgxLCA1KSwgcmVwKDAsIDIpKSwNCiAgICAgICAgICAgICAgICB0aW1lIH4gYyhyZXAoMSwgMiksIHJlcCgwLCA1KSkpKSAlPiUNCiAgbW9kaWZ5X2hlYWRlcihhbGxfc3RhdF9jb2xzKCkgfiAiKip7bGV2ZWx9Kio8YnI+TiA9IHtufSIpICU+JQ0KICBhZGRfb3ZlcmFsbChjb2xfbGFiZWwgPSAiKipUb3RhbCoqPGJyPk4gPSB7Tn0iLCBsYXN0ID0gVFJVRSkgJT4lDQogIG1vZGlmeV9mb290bm90ZSh1cGRhdGUgPSBldmVyeXRoaW5nKCkgfiBOQSkgJT4lDQogIGJvbGRfbGFiZWxzKCkNCmBgYA0KDQpgYGB7ciBhY3Rpdml0eV9kZXNjX3RhYiwgZXZhbD1UUlVFfQ0KdmFyX2xhYmVsKHRyYW5zX3BlcmlvZHMpIDwtIGxpc3QodGltZSA9ICJTdWJzY3JpcHRpb24gZHVyYXRpb24sIGRheXMiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGN0X2FjdGl2ZSA9ICIlb2YgYWN0aXZlIGRheXMgaW4gc3Vic2NyaXB0aW9uIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBjdF9hY3RpdmUzMCA9ICIlb2YgYWN0aXZlIGRheXMgaW4gdGhlIGxhc3QgMzAgZGF5cyBvZiBzdWJzY3JpcHRpb24iLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGN0X2FjdGl2ZTE0ID0gIiVvZiBhY3RpdmUgZGF5cyBpbiB0aGUgbGFzdCAxNCBkYXlzIG9mIHN1YnNjcmlwdGlvbiIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwY3RfYWN0aXZlNyA9ICIlb2YgYWN0aXZlIGRheXMgaW4gdGhlIGxhc3QgNyBkYXlzIG9mIHN1YnNjcmlwdGlvbiIpDQoNCnRibF9zdW1tYXJ5KA0KICBhcy5kYXRhLmZyYW1lKHRyYW5zX3BlcmlvZHMgJT4lDQogICAgICAgICAgICAgICAgICBzZWxlY3QoY2h1cm4sIHRpbWUsIGNvbnRhaW5zKCJwY3RfYWN0aXZlIikpICU+JQ0KICAgICAgICAgICAgICAgICAgbXV0YXRlKGNodXJuID0gZmFjdG9yKGNodXJuLCBsYWJlbHMgPSBjKCJDaHVybiA9IDAiLCAiQ2h1cm4gPSAxIikpKSksDQogIGJ5ID0gImNodXJuIiwgDQogIHR5cGUgPSBsaXN0KGFsbF9jb250aW51b3VzKCkgfiAiY29udGludW91czIiLA0KICAgICAgICAgICAgICBwY3RfYWN0aXZlNyB+ICJjb250aW51b3VzMiIpLA0KICBzdGF0aXN0aWMgPSBsaXN0KGFsbF9jb250aW51b3VzKCkgfiBjKCJ7bWVhbn0gKHtzZH0pIiwgInttZWRpYW59ICh7cDI1fS17cDc1fSkiLCAgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgInttaW59LXttYXh9IikpLA0KICBkaWdpdHMgPSBsaXN0KGFsbF9jb250aW51b3VzKCkgfiBjKHJlcCgxLCA1KSwgcmVwKDAsIDIpKSwNCiAgICAgICAgICAgICAgICB0aW1lIH4gYyhyZXAoMSwgMiksIHJlcCgwLCA1KSkpKSAlPiUNCiAgbW9kaWZ5X2hlYWRlcihhbGxfc3RhdF9jb2xzKCkgfiAiKip7bGV2ZWx9Kio8YnI+TiA9IHtufSIpICU+JQ0KICBhZGRfb3ZlcmFsbChjb2xfbGFiZWwgPSAiKipUb3RhbCoqPGJyPk4gPSB7Tn0iLCBsYXN0ID0gVFJVRSkgJT4lDQogIG1vZGlmeV9mb290bm90ZSh1cGRhdGUgPSBldmVyeXRoaW5nKCkgfiBOQSkgJT4lDQogIGJvbGRfbGFiZWxzKCkNCmBgYA0KDQo8YnI+DQoNCmBgYHtyIHRyYW5zX3BlcmlvZHNfYWN0aXZpdHlfY2h1cm59DQpwMSA8LSB0cmFuc19wZXJpb2RzICU+JSANCiAgbXV0YXRlKGNodXJuID0gZmFjdG9yKGNodXJuLCBsYWJlbHMgPSBjKCJDaHVybiA9IDAiLCAiQ2h1cm4gPSAxIikpKSAlPiUNCiAgZ2dwbG90KCkgKw0KICBhZXMoeCA9IHBjdF9hY3RpdmUsIGZpbGwgPSBjaHVybikgKw0KICBnZW9tX2RlbnNpdHkoYWxwaGEgPSAwLjQsIGNvbG9yID0gIndoaXRlIikgKw0KICBsYWJzKHggPSAiIiwgeSA9ICJEZW5zaXR5IiwgdGl0bGUgPSAiJSBvZiBhY3RpdmUgZGF5cyBpbiBzdWJzY3JpcHRpb24iLCBmaWxsID0gIiIpICsNCiAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IHNlcSgwLCAxMDAsIDEwKSkgKw0KICBzY2FsZV95X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLCAwKSkgKw0KICBnZ3RoZW1lczo6dGhlbWVfdHVmdGUoKSArDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9IGMoMC41LCAwLjkpLA0KICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIpLA0KICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApKQ0KDQpwMiA8LSB0cmFuc19wZXJpb2RzICU+JSANCiAgbXV0YXRlKGNodXJuID0gZmFjdG9yKGNodXJuLCBsYWJlbHMgPSBjKCJDaHVybiA9IDAiLCAiQ2h1cm4gPSAxIikpKSAlPiUNCiAgZ2dwbG90KCkgKw0KICBhZXMoeCA9IHBjdF9hY3RpdmUzMCwgZmlsbCA9IGNodXJuKSArDQogIGdlb21fZGVuc2l0eShhbHBoYSA9IDAuNCwgY29sb3IgPSAid2hpdGUiKSArDQogIGxhYnMoeCA9ICIiLCB5ID0gIkRlbnNpdHkiLCB0aXRsZSA9ICIlIG9mIGFjdGl2ZSBkYXlzIGluIHRoZSBsYXN0IDMwIGRheXMgb2Ygc3Vic2NyLiIsIGZpbGwgPSAiIikgKw0KICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gc2VxKDAsIDEwMCwgMTApKSArDQogIHNjYWxlX3lfY29udGludW91cyhleHBhbmQgPSBjKDAsIDApKSArDQogIGdndGhlbWVzOjp0aGVtZV90dWZ0ZSgpICsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gYygwLjUsIDAuOSksDQogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiksDQogICAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCkpDQoNCnAzIDwtIHRyYW5zX3BlcmlvZHMgJT4lIA0KICBtdXRhdGUoY2h1cm4gPSBmYWN0b3IoY2h1cm4sIGxhYmVscyA9IGMoIkNodXJuID0gMCIsICJDaHVybiA9IDEiKSkpICU+JQ0KICBnZ3Bsb3QoKSArDQogIGFlcyh4ID0gcGN0X2FjdGl2ZTE0LCBmaWxsID0gY2h1cm4pICsNCiAgZ2VvbV9kZW5zaXR5KGFscGhhID0gMC40LCBjb2xvciA9ICJ3aGl0ZSIpICsNCiAgbGFicyh4ID0gIiIsIHkgPSAiRGVuc2l0eSIsIHRpdGxlID0gIiUgb2YgYWN0aXZlIGRheXMgaW4gdGhlIGxhc3QgMTQgZGF5cyBvZiBzdWJzY3IuIiwgZmlsbCA9ICIiKSArDQogIHNjYWxlX3hfY29udGludW91cyhicmVha3MgPSBzZXEoMCwgMTAwLCAxMCkpICsNCiAgc2NhbGVfeV9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwgMCkpICsNCiAgZ2d0aGVtZXM6OnRoZW1lX3R1ZnRlKCkgKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSBjKDAuNSwgMC45KSwNCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiKSwNCiAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSkNCg0KcDQgPC0gdHJhbnNfcGVyaW9kcyAlPiUgDQogIG11dGF0ZShjaHVybiA9IGZhY3RvcihjaHVybiwgbGFiZWxzID0gYygiQ2h1cm4gPSAwIiwgIkNodXJuID0gMSIpKSkgJT4lDQogIGdncGxvdCgpICsNCiAgYWVzKHggPSBwY3RfYWN0aXZlNywgZmlsbCA9IGNodXJuKSArDQogIGdlb21fZGVuc2l0eShhbHBoYSA9IDAuNCwgY29sb3IgPSAid2hpdGUiKSArDQogIGxhYnMoeCA9ICIiLCB5ID0gIkRlbnNpdHkiLCB0aXRsZSA9ICIlIG9mIGFjdGl2ZSBkYXlzIGluIHRoZSBsYXN0IDcgZGF5cyBvZiBzdWJzY3IuIiwgZmlsbCA9ICIiKSArDQogIHNjYWxlX3hfY29udGludW91cyhicmVha3MgPSBzZXEoMCwgMTAwLCAxMCkpICsNCiAgc2NhbGVfeV9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwgMCkpICsNCiAgZ2d0aGVtZXM6OnRoZW1lX3R1ZnRlKCkgKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSBjKDAuNSwgMC45KSwNCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiKSwNCiAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSkNCg0KZ2dhcnJhbmdlKHAxLHAyLHAzLHA0LCBucm93ID0gMiwgbmNvbCA9IDIsIGNvbW1vbi5sZWdlbmQgPSBUUlVFKQ0KYGBgDQoNCmBgYHtyIHRyYW5zX3BlcmlvZHNfYWN0aXZpdHlfY2h1cm5fcGxvdCwgZmlnLndpZHRoPTEwLCBmaWcuaGVpZ2h0PTYsIGZpZy5hbGlnbj0nY2VudGVyJywgZXZhbD1UUlVFfQ0KcDEgPC0gdHJhbnNfcGVyaW9kcyAlPiUgDQogIG11dGF0ZShjaHVybiA9IGZhY3RvcihjaHVybiwgbGFiZWxzID0gYygiQ2h1cm4gPSAwIiwgIkNodXJuID0gMSIpKSkgJT4lDQogIGdncGxvdCgpICsNCiAgYWVzKHggPSBwY3RfYWN0aXZlLCBmaWxsID0gY2h1cm4pICsNCiAgZ2VvbV9kZW5zaXR5KGFscGhhID0gMC40LCBjb2xvciA9ICJ3aGl0ZSIpICsNCiAgbGFicyh4ID0gIiIsIHkgPSAiRGVuc2l0eSIsIHRpdGxlID0gIiUgb2YgYWN0aXZlIGRheXMgaW4gc3Vic2NyaXB0aW9uIiwgZmlsbCA9ICIiKSArDQogIHNjYWxlX3hfY29udGludW91cyhicmVha3MgPSBzZXEoMCwgMTAwLCAxMCkpICsNCiAgc2NhbGVfeV9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwgMCkpICsNCiAgZ2d0aGVtZXM6OnRoZW1lX3R1ZnRlKCkgKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSBjKDAuNSwgMC45KSwNCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiKSwNCiAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSkNCg0KcDIgPC0gdHJhbnNfcGVyaW9kcyAlPiUgDQogIG11dGF0ZShjaHVybiA9IGZhY3RvcihjaHVybiwgbGFiZWxzID0gYygiQ2h1cm4gPSAwIiwgIkNodXJuID0gMSIpKSkgJT4lDQogIGdncGxvdCgpICsNCiAgYWVzKHggPSBwY3RfYWN0aXZlMzAsIGZpbGwgPSBjaHVybikgKw0KICBnZW9tX2RlbnNpdHkoYWxwaGEgPSAwLjQsIGNvbG9yID0gIndoaXRlIikgKw0KICBsYWJzKHggPSAiIiwgeSA9ICJEZW5zaXR5IiwgdGl0bGUgPSAiJSBvZiBhY3RpdmUgZGF5cyBpbiB0aGUgbGFzdCAzMCBkYXlzIG9mIHN1YnNjci4iLCBmaWxsID0gIiIpICsNCiAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IHNlcSgwLCAxMDAsIDEwKSkgKw0KICBzY2FsZV95X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLCAwKSkgKw0KICBnZ3RoZW1lczo6dGhlbWVfdHVmdGUoKSArDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9IGMoMC41LCAwLjkpLA0KICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIpLA0KICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApKQ0KDQpwMyA8LSB0cmFuc19wZXJpb2RzICU+JSANCiAgbXV0YXRlKGNodXJuID0gZmFjdG9yKGNodXJuLCBsYWJlbHMgPSBjKCJDaHVybiA9IDAiLCAiQ2h1cm4gPSAxIikpKSAlPiUNCiAgZ2dwbG90KCkgKw0KICBhZXMoeCA9IHBjdF9hY3RpdmUxNCwgZmlsbCA9IGNodXJuKSArDQogIGdlb21fZGVuc2l0eShhbHBoYSA9IDAuNCwgY29sb3IgPSAid2hpdGUiKSArDQogIGxhYnMoeCA9ICIiLCB5ID0gIkRlbnNpdHkiLCB0aXRsZSA9ICIlIG9mIGFjdGl2ZSBkYXlzIGluIHRoZSBsYXN0IDE0IGRheXMgb2Ygc3Vic2NyLiIsIGZpbGwgPSAiIikgKw0KICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gc2VxKDAsIDEwMCwgMTApKSArDQogIHNjYWxlX3lfY29udGludW91cyhleHBhbmQgPSBjKDAsIDApKSArDQogIGdndGhlbWVzOjp0aGVtZV90dWZ0ZSgpICsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gYygwLjUsIDAuOSksDQogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiksDQogICAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCkpDQoNCnA0IDwtIHRyYW5zX3BlcmlvZHMgJT4lIA0KICBtdXRhdGUoY2h1cm4gPSBmYWN0b3IoY2h1cm4sIGxhYmVscyA9IGMoIkNodXJuID0gMCIsICJDaHVybiA9IDEiKSkpICU+JQ0KICBnZ3Bsb3QoKSArDQogIGFlcyh4ID0gcGN0X2FjdGl2ZTcsIGZpbGwgPSBjaHVybikgKw0KICBnZW9tX2RlbnNpdHkoYWxwaGEgPSAwLjQsIGNvbG9yID0gIndoaXRlIikgKw0KICBsYWJzKHggPSAiIiwgeSA9ICJEZW5zaXR5IiwgdGl0bGUgPSAiJSBvZiBhY3RpdmUgZGF5cyBpbiB0aGUgbGFzdCA3IGRheXMgb2Ygc3Vic2NyLiIsIGZpbGwgPSAiIikgKw0KICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gc2VxKDAsIDEwMCwgMTApKSArDQogIHNjYWxlX3lfY29udGludW91cyhleHBhbmQgPSBjKDAsIDApKSArDQogIGdndGhlbWVzOjp0aGVtZV90dWZ0ZSgpICsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gYygwLjUsIDAuOSksDQogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiksDQogICAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCkpDQoNCmdnYXJyYW5nZShwMSxwMixwMyxwNCwgbnJvdyA9IDIsIG5jb2wgPSAyKQ0KYGBgDQoNCi0tIFN1YnNjcmlwdGlvbnMgZW5kZWQgaW4gY2h1cm4gYXJlIGEgYml0IGxlc3MgYWN0aXZlIC0gYXMgb3ZlcmFsbCwgYXMgaW4gdGhlIGxhc3QgZGF5cyBvZiBzdWJzY3JpcHRpb24uDQoNCjxicj4NCg0KVGhlIHByb2JsZW0gd2l0aCBhbGwgZGVzY3JpYmVkIHVzZXIgbG9ncyBjaGFyYWN0ZXJpc3RpY3MgaXMgdGhhdCB0aGV5IGFyZSB0aW1lIGRlcGVuZGVudCwgaS5lLiB0aGV5IGNoYW5nZSB3aXRoIHRpbWUgb2Ygc3Vic2NyaXB0aW9uLiBBcyBhIGNvbnNlcXVlbmNlLCB3ZSBjYW5ub3QgdXNlIHRoZW0gaW4gYmFzaWMgc3RhdGlzdGljIG9yIE1MIHByZWRpY3Rpb24gbW9kZWxzIHdpdGhvdXQgc3BlY2lhbCBvcHRpb25zIGFuZCBwcmVsaW1pbmFyeSBkYXRhIHJlc3RydWN0dXJpbmcuIFdlIGFsc28gY2Fubm90IHVzZSBzb21lIGF2ZXJhZ2UgaW5kaWNhdG9ycyBvdmVyIHRoZSB3aG9sZSBzdWJzY3JpcHRpb24gcGVyaW9kLCBiZWNhdXNlIGZvciB2YWxpZGF0aW9uIGFuZCB0ZXN0IHNldHMsIGFzIHdlbGwgYXMgZm9yIGFueSBuZXcgZGF0YSwgd2UgZG8gbm90IGtub3cgdGhlIChmdXR1cmUpIGxlbmd0aCBvZiBzdWJzY3JpcHRpb24gYW5kIChmdXR1cmUpIHVzZXIncyBiZWhhdmlvdXIsIHdoaWxlIHdlIHdvdWxkIGxpa2UgbWFraW5nIHByZWRpY3Rpb25zIGF0IGFueSBwb2ludCBvZiBzdWJzY3JpcHRpb24gKGVpdGhlciBzdGFydCBvciBpbiBwcm9jZXNzKSB3aXRoIGFsbCBpbmZvcm1hdGlvbiBhdmFpbGFibGUgcHJpb3IgdG8gdGhhdCBtb21lbnQuIA0KDQpJbiBhZGRpdGlvbiwgd2UgY2Fubm90IHBsb3QgS2FwbGFuLU1laWVyIGN1cnZlcyBmb3IgdGltZS1kZXBlbmRlbnQgcHJlZGljdG9ycyAtIGluc3RlYWQgd2Ugc2hvdWxkIHVzZSBTaW1vbiBhbmQgTWFrdWNoIHBsb3RzLiBBbHRob3VnaCwgYXMgZm9yIEthcGxhbi1NZWllciBjdXJ2ZXMgdGhleSBhcmUgYXBwbGljYWJsZSBvbmx5IGZvciBjYXRlZ29yaWNhbCB2YXJpYWJsZXMuDQoNCjxicj4NCg0KIyAqKlByZWRpY3Rpbmcgc3Vydml2YWwgZGlzdHJpYnV0aW9ucyBmb3Igc3Vic2NyaXB0aW9ucyoqDQoNCjxicj4NCg0KV2UgaGF2ZSBzcGxpdHRlZCBvdXQgZGF0YSBmb3Igc3Vic2NyaXB0aW9uIHBlcmlvZHMgaW50byB0cmFpbiBhbmQgdGVzdCBzZXRzIHdpdGggNzAlIG9mIGFsbCBwZXJpb2RzIGluIHRyYWluLg0KDQo8YnI+DQoNCiMjICoqRmVhdHVyZXMqKg0KDQo8YnI+DQoNCkhlcmUgd2Ugc3RhcnQgZnJvbSBiYXNlbGluZSAqKm1vZGVscyB3aXRob3V0IHRpbWUtZGVwZW5kZW50IGNvdmFyaWF0ZXMqKiBhbmQgd2lsbCB1c2Ugb25seSBpbnRlcnByZXRhYmxlIG1vZGVscyBvciwgYXQgbGVhc3QsIHRob3NlIHdoaWNoIGFsbG93IGVzdGltYXRpb24gb2YgdmFyaWFibGUgaW1wb3J0YW5jZSwgc28gYSBjb21wYW55IHdpbGwgYmUgYWJsZSB0byB1bmRlcnN0YW5kIHdoYXQgY291bGQgYmUgYSBjYXVzZSBmb3IgdXNlcidzIGF0dHJpdGlvbi4NCg0KRnJvbSBhbGwgYXZhaWxhYmxlICoqZmVhdHVyZXMqKiBmb3IgZWFjaCBzdWJzY3JpcHRpb24gcGVyaW9kIHdlIGNob3NlIHRoZSBmb2xsb3dpbmc6DQoNCi0gYGNodXJuZWRfYmVmb3JlX251bWAgLSBudW1iZXIgb2YgcHJldmlvdXMgY2h1cm5zIChzZXQgdG8gMCBmb3IgdGhlIGZpcnN0IHN1YnNjcmlwdGlvbiBwZXJpb2Qgb2YgdGhlIGNvcnJlc3BvbmRpbmcgdXNlciksDQoNCi0gYGNpdHlHYCAtIGFsbCB1bmlxdWUgbnVtZXJpYyBjb2Rlcywgd2l0aCAxOSBhbmQgMjAgY29kZXMgZ3JvdXBlZCBpbnRvIG9uZSBjYXRlZ29yeSwNCg0KLSBgYmRHYCAtIGFnZSBzcGxpdHRlZCBpbnRvIDIgY2F0ZWdvcmllczogbGVzcyB0aGFuIDEwIGFuZCAxMCsgKHllYXJzKSwNCg0KLSBgZ2VuZGVyYCAtIGZlbWFsZSwgbWFsZSwgdW5rbm93biwNCg0KLSBgcmVnaXN0ZXJlZF92aWFgIC0gYWxsIHVuaXF1ZSBudW1lcmljIGNvZGVzIHdpdGhvdXQgYW55IGdyb3VwaW5nLA0KDQotIGBtb250aF9wZXJpb2RgIC0gbW9udGggYXQgd2hpY2ggc3Vic2NyaXB0aW9uIHdhcyBzdGFydGVkLA0KDQotIGB0aW1lX2Zyb21fcmVnYCAtIHRpbWUgZnJvbSByZWdpc3RyYXRpb24gYXQgdGhlIHNlcnZpY2UgdGlsbCB0aGUgc3RhcnQgb2Ygc3Vic2NyaXB0aW9uIChkYXlzKSwNCg0KLSBgZmlyc3RfcGF5bWVudF9tZXRob2RfaWRgIC0gcGF5bWVudCBtZXRob2QgdXNlZCBpbiB0aGUgZmlyc3QgdHJhbnNhY3Rpb24gKGF0IHRoZSBzdGFydCBvZiBzdWJzY3JpcHRpb24gcGVyaW9kKSwNCg0KLSBgZmlyc3RfcGxhbl9saXN0X3ByaWNlYCAtIHBsYW4gbGlzdCBwcmljZSBzdGF0ZWQgaW4gdGhlIGZpcnN0IHRyYW5zYWN0aW9uIChhdCB0aGUgc3RhcnQgb2Ygc3Vic2NyaXB0aW9uIHBlcmlvZCksDQoNCi0gYHRpbWVfZnJvbV9wcmV2X2V4cGAgLSB0aW1lIGZyb20gdGhlIGV4cGlyYXRpb24gZGF0ZSBvZiB0aGUgcHJldmlvdXMgc3Vic2NyaXB0aW9uIChkYXlzLCBzZXQgdG8gMCBmb3IgdGhlIGZpcnN0IHN1YnNjcmlwdGlvbiBvZiB0aGUgY29ycmVzcG9uZGluZyB1c2VyKSwNCg0KLSBgcHJldl90aW1lYCAtIHN1bSBvZiBhbGwgcHJldmlvdXMgc3Vic2NyaXB0aW9uIGR1cmF0aW9ucyAoZGF5cywgc2V0IHRvIDAgZm9yIHRoZSBmaXJzdCBzdWJzY3JpcHRpb24gb2YgdGhlIGNvcnJlc3BvbmRpbmcgdXNlciksIA0KDQotIGBwcmV2X3BjdF9hY3RpdmVgIC0gc2hhcmUgb2YgYWN0aXZlIGRheXMgdG8gdGhlIHRvdGFsIGxlbmd0aCBvZiBhbGwgcHJldmlvdXMgc3Vic2NyaXB0aW9ucyAoaW4gJSwgc2V0IHRvIDAgZm9yIHRoZSBmaXJzdCBzdWJzY3JpcHRpb24gb2YgdGhlIGNvcnJlc3BvbmRpbmcgdXNlciksDQoNCi0gYHByZXZfbWVhbl9udW1fdG90YCAtIGF2ZXJhZ2UgbnVtYmVyIG9mIHNvbmdzIGxpc3RlbmVkIHBlciBkYXkgZm9yIGFsbCBwcmV2aW91cyBzdWJzY3JpcHRpb25zIChzZXQgdG8gMCBmb3IgdGhlIGZpcnN0IHN1YnNjcmlwdGlvbiBvZiB0aGUgY29ycmVzcG9uZGluZyB1c2VyKSwNCg0KLSBgcHJldl9tZWFuX3RvdGFsX3NlY3NfaW1wYCAtIGF2ZXJhZ2UgbnVtYmVyIG9mIHNlY29uZHMgbGlzdGVuZWQgcGVyIGRheSBmb3IgYWxsIHByZXZpb3VzIHN1YnNjcmlwdGlvbnMgKHNldCB0byAwIGZvciB0aGUgZmlyc3Qgc3Vic2NyaXB0aW9uIG9mIHRoZSBjb3JyZXNwb25kaW5nIHVzZXI7IHdlIHVzZWQgdmFsdWVzIG9mIGRhaWx5IHNlY29uZHMgd2l0aCBpbXB1dGVkIGxhcmdlIHZhbHVlcyBhcyBpdCB3YXMgZGVzY3JpYmVkIGFib3ZlKSwNCg0KLSBgcHJldl9tZWFuX251bV91bnFgIC0gYXZlcmFnZSBudW1iZXIgb2YgdW5pcXVlIHNvbmdzIGxpc3RlbmVkIHBlciBkYXkgZm9yIGFsbCBwcmV2aW91cyBzdWJzY3JpcHRpb25zIChzZXQgdG8gMCBmb3IgdGhlIGZpcnN0IHN1YnNjcmlwdGlvbiBvZiB0aGUgY29ycmVzcG9uZGluZyB1c2VyKSwgDQoNCi0gYHByZXZfbWVhbl9hdl9wY3RfcGxheWVkX2FjdGAgLSBhdmVyYWdlICUgb2Ygb25lIHNvbmcncyBsZW5ndGggcGxheWVkIHBlciBhY3RpdmUgZGF5IGZvciBhbGwgcHJldmlvdXMgc3Vic2NyaXB0aW9ucyAoc2V0IHRvIDAgZm9yIHRoZSBmaXJzdCBzdWJzY3JpcHRpb24gb2YgdGhlIGNvcnJlc3BvbmRpbmcgdXNlciksDQoNCi0gYHByZXZfbWVhbl9hY3R1YWxfYW1vdW50X3BhaWRgIC0gbWVhbiAoZGFpbHkpIGFjdHVhbCBhbW91bnQgcGFpZCBmb3IgYWxsIHByZXZpb3VzIHN1YnNjcmlwdGlvbnMgKHNldCB0byAwIGZvciB0aGUgZmlyc3Qgc3Vic2NyaXB0aW9uIG9mIHRoZSBjb3JyZXNwb25kaW5nIHVzZXIpLCANCg0KLSBgcHJldl9udW1fY2FuY2VsYCAtIHRvdGFsIG51bWJlciBvZiBjYW5jZWxsYXRpb25zIGZvciBhbGwgcHJldmlvdXMgc3Vic2NyaXB0aW9ucyAoc2V0IHRvIDAgZm9yIHRoZSBmaXJzdCBzdWJzY3JpcHRpb24gb2YgdGhlIGNvcnJlc3BvbmRpbmcgdXNlcjsgcmVtZW1iZXIgdGhhdCBjYW5jZWxsYXRpb25zIGluY2x1ZGUgY2hhbmdlcyBpbiBzdWJzY3JpcHRpb24gcGxhbnMgYXMgd2VsbCksIA0KDQotIGBwcmV2X21vZGVfcGF5bWVudF9pZGAgLSBtb2RhbCB2YWx1ZSBmb3IgcGF5bWVudCBtZXRob2QgZm9yIGFsbCBwcmV2aW91cyBzdWJzY3JpcHRpb25zIChmaXJzdCBzdWJzY3JpcHRpb24gb2YgdGhlIGNvcnJlc3BvbmRpbmcgdXNlciB3YXMgcGxhY2VkIGluIHRoZSBzZXBhcmF0ZSBjYXRlZ29yeSBvZiB0aGlzIGZlYXR1cmU7IGFsbCBjYXRlZ29yaWVzIHdpdGggdGhlIG51bWJlciBvZiBzdWJzY3JpcHRpb25zIGxlc3MgdGhhbiAzMCB3ZXJlIGNvbWJpbmVkIGludG8gb25lIGdyb3VwIHdpdGggbGFiZWwgIjk5OSIpLg0KDQpXZSBhbHNvIHRyaWVkIG1vZGVscyB3aXRoIGxhZyB2YWx1ZXMgaW5zdGVhZCBvZiB2YWx1ZXMgZm9yIGFsbCBwcmV2aW91cyBzdWJzY3J1cHRpb25zIC0gb24gYXZlcmFnZSwgdGhleSBwZXJmb21lZCB3b3JzZSwgc28gd2Ugc3RpY2tlZCB0byBkYXRhIG9uIGFsbCBwcmV2aW91cyBzdWJzY3JpcHRpb25zLg0KDQo8YnI+DQoNCkZvciAqKm1vZGVscyB3aXRoIHRpbWUtZGVwZW5kZW50IGNvdmFyaWF0ZXMqKiB3ZSBwcmVsaW1pbmFyeSByZXN0cnVjdHVyZWQgb3VyIGRhdGEgc2V0LiBFYWNoIHN1YnNjcmlwdGlvbiBwZXJpb2Qgd2FzIHNwbGl0dGVkIGludG8gc2V2ZXJhbCBpbnRlcnZhbHMgYnkgdGhlIGRhdGVzIG9mIHVzZXJzIGxvZ3Mgb3IgdHJhbnNhY3Rpb25zIGluc2lkZSB0aGVtIChpZiB0aGV5IHdlcmUgdGhlcmU7IG90aGVyd2lzZSBhIHBlcmlvZCByZW1haW5lZCB1bmNoYW5nZWQpLiBGb3IgZWFjaCBpbnRlcnZhbCB3ZSB3ZXJlIGFibGUgdG8gdXNlIGFsbCBpbmZvcm1hdGlvbiBhYm91dCB1c2VycywgdGhlaXIgdHJhbnNhY3Rpb25zIGFuZCB1c2VyIGxvZyBzdGF0aXN0aWNzLCBidXQgZm9yIGNvbXBhcmFiaWxpdHkgd2l0aCBtb2RlbHMgd2l0aCB0aW1lLWluZGVwZW5kZW50IGNvdmFyaWF0ZXMgd2UgY2hvc2UgdGhlIGZvbGxvd2luZyBmZWF0dXJlcyAoaWYgaXQgaXMgbm90IGRlbm90ZWQgdGhhdCB0aGUgZmVhdHVyZSB3YXMgdGltZS1pbmRlcGVuZGVudCwgaXQgd2FzIHRpbWUtZGVwZW5kZW50KToNCg0KLSBgY2h1cm5lZF9iZWZvcmVfbnVtYCAodGltZS1pbmRlcGVuZGVudCkgLSBudW1iZXIgb2YgcHJldmlvdXMgY2h1cm5zIChzZXQgdG8gMCBmb3IgdGhlIGZpcnN0IHN1YnNjcmlwdGlvbiBwZXJpb2Qgb2YgdGhlIGNvcnJlc3BvbmRpbmcgdXNlciksDQoNCi0gYGNpdHlHYCAodGltZS1pbmRlcGVuZGVudCkgLSBudW1lcmljIGNvZGUgZm9yIGNpdHksIHdpdGggMTkgYW5kIDIwIGNvZGVzIGdyb3VwZWQgaW50byBvbmUgY2F0ZWdvcnksDQoNCi0gYGJkR2AgKHRpbWUtaW5kZXBlbmRlbnQpIC0gYWdlIHNwbGl0dGVkIGludG8gMiBjYXRlZ29yaWVzOiBsZXNzIHRoYW4gMTAgYW5kIDEwKyAoeWVhcnMpLA0KDQotIGBnZW5kZXJgICh0aW1lLWluZGVwZW5kZW50KSAtIGZlbWFsZSwgbWFsZSwgdW5rbm93biwNCg0KLSBgcmVnaXN0ZXJlZF92aWFgICh0aW1lLWluZGVwZW5kZW50KSAtIGFsbCB1bmlxdWUgbnVtZXJpYyBjb2RlcyB3aXRob3V0IGFueSBncm91cGluZywNCg0KLSBgbW9udGhfcGVyaW9kYCAodGltZS1pbmRlcGVuZGVudCkgLSBtb250aCBhdCB3aGljaCBzdWJzY3JpcHRpb24gd2FzIHN0YXJ0ZWQsDQoNCi0gYHRpbWVfZnJvbV9yZWdgICh0aW1lLWluZGVwZW5kZW50KSAtIHRpbWUgZnJvbSByZWdpc3RyYXRpb24gYXQgdGhlIHNlcnZpY2UgdGlsbCB0aGUgc3RhcnQgb2Ygc3Vic2NyaXB0aW9uIChkYXlzKSwNCg0KLSBgdGltZV9mcm9tX3ByZXZfZXhwYCAodGltZS1pbmRlcGVuZGVudCkgLSB0aW1lIGZyb20gdGhlIGV4cGlyYXRpb24gZGF0ZSBvZiB0aGUgcHJldmlvdXMgc3Vic2NyaXB0aW9uIChkYXlzLCBzZXQgdG8gMCBmb3IgdGhlIGZpcnN0IHN1YnNjcmlwdGlvbiBvZiB0aGUgY29ycmVzcG9uZGluZyB1c2VyKSwNCg0KLSBgcHJldl90aW1lYCAodGltZS1pbmRlcGVuZGVudCkgLSBzdW0gb2YgYWxsIHByZXZpb3VzIHN1YnNjcmlwdGlvbiBkdXJhdGlvbnMgKGRheXMsIHNldCB0byAwIGZvciB0aGUgZmlyc3Qgc3Vic2NyaXB0aW9uIG9mIHRoZSBjb3JyZXNwb25kaW5nIHVzZXIpLCANCg0KLSBgcGN0X2FjdGl2ZWAgLSBzaGFyZSBvZiBhY3RpdmUgKHdpdGggbG9ncykgZGF5cyB0byB0aGUgdG90YWwgbGVuZ3RoIG9mIHRoZSBpbnRlcnZhbCAoaW4gJSksDQoNCi0gYG1lYW5fbnVtX3RvdGAgLSBhdmVyYWdlIG51bWJlciBvZiBzb25ncyBsaXN0ZW5lZCBwZXIgZGF5IGR1cmluZyB0aGUgaW50ZXJ2YWwsDQoNCi0gYG1lYW5fdG90YWxfc2Vjc19pbXBgIC0gYXZlcmFnZSBudW1iZXIgb2Ygc2Vjb25kcyBsaXN0ZW5lZCBwZXIgZGF5IGR1cmluZyB0aGUgaW50ZXJ2YWwsDQoNCi0gYG1lYW5fbnVtX3VucWAgLSBhdmVyYWdlIG51bWJlciBvZiB1bmlxdWUgc29uZ3MgbGlzdGVuZWQgcGVyIGRheSBkdXJpbmcgdGhlIGludGVydmFsLCANCg0KLSBgbWVhbl9hdl9wY3RfcGxheWVkX2FjdGAgLSBhdmVyYWdlICUgb2Ygb25lIHNvbmcncyBsZW5ndGggcGxheWVkIHBlciBhY3RpdmUgZGF5IGR1cmluZyB0aGUgaW50ZXJ2YWwsDQoNCi0gYGN1bV9tZWFuX2FjdHVhbF9hbW91bnRfcGFpZGAgLSBjdW11bGF0aXZlIG1lYW4gKGRhaWx5KSBhY3R1YWwgYW1vdW50IHBhaWQgZnJvbSB0aGUgc3RhcnQgb2Ygc3Vic2NyaXB0aW9uIHRvIHRoZSBlbmQgb2YgdGhpcyBpbnRlcnZhbCwgDQoNCi0gYGN1bV9udW1fY2FuY2VsYCAtIGN1bXVsYXRpdmUgbnVtYmVyIG9mIGNhbmNlbGxhdGlvbnMgZnJvbSB0aGUgc3RhcnQgb2Ygc3Vic2NyaXB0aW9uIHRvIHRoZSBlbmQgb2YgdGhpcyBpbnRlcnZhbCwgDQoNCi0gYGxhc3RfcGF5bWVudF9tZXRob2RfaWRgIC0gbGFzdCB2YWx1ZSBmb3IgcGF5bWVudCBtZXRob2QgZnJvbSB0aGUgc3RhcnQgb2Ygc3Vic2NyaXB0aW9uIHRvIHRoZSBlbmQgb2YgdGhpcyBpbnRlcnZhbCwNCg0KLSBgbGFzdF9wbGFuX2xpc3RfcHJpY2VgIC0gbGFzdCB2YWx1ZSBmb3IgcGxhbiBsaXN0IHByaWNlIGZyb20gdGhlIHN0YXJ0IG9mIHN1YnNjcmlwdGlvbiB0byB0aGUgZW5kIG9mIHRoaXMgaW50ZXJ2YWwuDQoNCjxicj4NCg0KIyMgKipNb2RlbHMqKg0KDQo8YnI+DQoNCkkgaGF2ZSBjaG9zZW4gQ294IFBIIGFuZCBSU0YgYXMgbW9kZWxzIGZvciBlc3RpbWF0aW9uLCBiZWNhdXNlOg0KDQotIHRoZXkgYWxsb3cgdG8gcHJlZGljdCBzdXJ2aXZhbCBmdW5jdGlvbnMsDQoNCi0gdGhlcmUgYXJlIHBhY2thZ2VzIGluIFIgd2hpY2ggYWxsb3cgZXN0aW1hdGlvbiBhbmQgcHJlZGljdGlvbiBvZiB0aGVzZSBtb2RlbHMgd2l0aCB0aW1lLWRlcGVuZGVudCBjb3ZhcmlhdGVzLA0KDQotIGluIHByZWxpbWluYXJ5IGFuYWx5c2lzIHRoZXkgb3V0cGVyZm9ybWVkIGFsbCBvdGhlciBtb2RlbHMgKHBhcmFtZXRyaWMgUEgsIEFGVCwgeGdib29zdCkgZm9yIHRpbWUtaW5kZXBkZW5lbnQgY292YXJpYXRlcywNCg0KLSB0aGV5IGFyZSBxdWl0ZSBkaWZmZXJlbnQgaW4gdGhlaXIgYXNzdW1wdGlvbnMgLSBmaXJzdCBvZiBhbGwsIFJTRiBkb2VzIG5vdCBhc3N1bWUgcHJvcG9ydGlvbmFsaXR5IGluIGhhemFyZHMsIHdoaWxlLCBmb3IgZXhhbXBsZSwgeGdib29zdCBkb2VzIC0gdGh1cywgd2Ugc2hvdWxkIG9idGFpbiBwcmVkaWN0aW9ucyBmcm9tIHR3byBkaWZmZXJlbnQgYXNzdW1wdGlvbnMgZmllbGRzLg0KDQpNYXhpbXVtIGRlcHRoIGFuZCBtaW5pbXVtIGxlYWYgc2l6ZSBmb3IgUlNGIHdlcmUgdHVuZWQgdXNpbmcgZ3JpZCBzZWFyY2ggd2l0aCBob2xkb3V0LCBpLmUuIHRyYWluIHNhbXBsZSB3YXMgc3BsaXR0ZWQgaW50byB0cmFpbiBhbmQgdmFsaWRhdGlvbiBzZXRzIGluIHByb3BvcnRpb24gODoyLCBhbmQgcGVyZm9ybWFuY2Ugd2l0aCBkaWZmZXJlbnQgc2V0cyBvZiBwYXJhbWV0ZXJzIHdlcmUgZXN0aW1hdGVkIHVzaW5nIEhhcnJlbGwncyBDIGluZGV4IG9uIHRoZSB2YWxpZGFpb24gc2V0LiA1MDAgdHJlZXMgd2VyZSBlc3RpbWF0ZWQgKDEwMCB0cmVlcyBnYXZlIGFsbW9zdCB0aGUgc2FtZSByZXN1bHQpLg0KDQpGb3Igb3ZlcmFsbCBwZXJmb3JtYW5jZSBlc3RpbWF0aW9uIGFuZCBtb2RlbCBjb21wYXJpc29uIChmb3IgbW9kZWxzIHdpdGggdGltZS1pbmRlcGRlbmRlbnQgY292YXJpYXRlcyBvbmx5ISkgSSB1c2VkIHRoZSBmb2xsb3dpbmcgbWV0cmljczoNCg0KLSBIYXJyZWxsJ3MgQyBpbmRleCwNCg0KLSBVbm8ncyBDIGluZGV4IChpbiBjb21wYXJpc29uIHdpdGggSGFycmVsbCdzIG9uZSBpdCBpbmNvcnBvcmF0ZXMgd2VpZ2h0cyBmcm9tIGVzdGltYXRpb24gb2YgY2Vuc29yaW5nIGRpc3RyaWJ1dGlvbiksDQoNCi0gaW50ZWdyYXRlZCBCcmllciBzY29yZSAoSUJTKSBmb3IgdGhlIHBlcmlvZCBmcm9tIDEgdG8gNzIwIGRheXMgKH4gMiB5ZWFycyksDQoNCi0gdGltZS1kZXBlbmRlbnQgQnJpZXIgc2NvcmVzIGZvciBzZXZlcmFsIHRpbWUgcG9pbnRzOiA3LCAxNCwgMzAsIDYwLCA5MCwgMTgwLCAzNjAgYW5kIDcyMCBkYXlzIHRvIGVzdGltYXRlIGhvdyBwcmVkaWN0aW9uIHF1YWxpdHkgY2hhbmdlcyB3aXRoIHRpbWUuDQoNCkZvciBtb2RlbHMgd2l0aCB0aW1lLWRlcGVuZGVudCBjb3ZhcmlhdGVzIEkgdXNlZCBIYXJyZWzigJlzIEMtaW5kZXggZXN0aW1hdGVkIGJvdGggb24gdHJhaW4gYW5kIHRlc3Qgc2V0cyBhbmQgSUJTIGZvciB0aGUgcGVyaW9kIGZyb20gMSB0byA3MjAgZGF5cyBlc3RpbWF0ZWQgb24gdGVzdCBvbmx5Lg0KDQo8YnI+DQoNCiMjICoqSW5zdHJ1bWVudHMqKg0KDQo8YnI+DQoNCkkndmUgdXNlZCBhIG1peCBvZiBQeXRob24gYW5kIFIgZm9yIGFsbCB0aGUgcHJvamVjdC4gDQoNCioqRnJvbSBSIEkgdXNlZCB0aGUgZm9sbG93aW5nIHBhY2thZ2VzKio6DQoNCi0gYHN1cnZpdmFsYCBbQHRoZXJuZWF1X3BhY2thZ2VfMjAyMV0gLSBmb3IgS2FwbGFuLU1laWVyIGVzdGltYXRvcnMgKGFib3ZlKSwgQ294IHJlZ3Jlc3Npb24gd2l0aCB0aW1lLWRlcGVuZGVudCBjb3ZhcmlhdGVzLCBwcmVkaWN0aW5nIHN1cnZpdmFsIHByb2JhYmlsaXRpZXMgYW5kIGVzdGltYXRpbmcgSGFycmVsbOKAmUMgYWZ0ZXIgdGhhdCwNCg0KLSBgc3Vydm1pbmVyYCBbQGthc3NhbWJhcmFfc3Vydm1pbmVyXzIwMjFdIC0gZm9yIHBsb3R0aW5nIEthcGxhbi1NZWllciBjdXJ2ZXMgKGFib3ZlKSwNCg0KLSBgYnNoYXphcmRgIFtAcmVib3JhX2JzaGF6YXJkXzIwMTRdIC0gZm9yIG5vbi1wYXJhbWV0cmljIGVzdGltYXRpb24gYW5kIHBsb3R0aW5nIGhhemFyZCBmdW5jdGlvbnMgKGFib3ZlKSwNCg0KLSBgcm1zYCBbQGhhcnJlbGxfcm1zXzIwMjFdIC0gZm9yIGVzdGltYXRpbmcgdmFyaWFibGUgaW1wb3J0YW5jZSBpbiBDb3ggcmVncmVzc2lvbnMgYnkgcHJvcG9ydGlvbiBvZiBleHBsYWluYWJsZSBsb2ctbGlrZWxpaG9vZCwNCg0KLSBgTFRSQ2ZvcmVzdGAgW0B5YW9fbHRyY2ZvcmVzdHNfMjAyMV0gLSBmb3IgZXN0aW1hdGluZyBJQlMgaW4gbW9kZWxzIHdpdGggdGltZS1kZXBlbmRlbnQgY292YXJpYXRlcyBhbmQgZm9yIFJTRiB3aXRoIHRpbWUtZGVwZW5kZW50IGNvdmFyaWF0ZXMuDQoNCjxicj4NCg0KKipGcm9tIFB5dGhvbiBJIHVzZWQgdGhlIGZvbGxvd2luZyBsaWJyYXJpZXMqKjoNCg0KLSBgc2Npa2l0LWxlYXJuYFtAcGVkcmVnb3NhX3NjaWtpdC1sZWFybl8yMDExXSAtIGZvciB0cmFpbi90ZXN0IHNwbGl0LCBncmlkIHNlYXJjaCBDViAodHVuaW5nIGh5cGVycGFyYW1ldGVycyBmb3IgUlNGIHdpdGggdGltZS1pbmRlcGVuZGVudCBjb3ZhcmlhdGVzKSwNCg0KLSBgc2Npa2l0LXN1cnZpdmFsYCBbQHBvbHN0ZXJsX3NjaWtpdC1zdXJ2aXZhbF8yMDIwXSAtIGZvciBwcmVwcm9jZXNzaW5nIChvbmUtaG90IGVuY29kaW5nKSwgZXN0aW1hdGluZyBDb3ggUEggYW5kIFJTRiBtb2RlbHMgd2l0aCB0aW1lLWluZGVwZW5kZW50IGNvdmFyaWF0ZXMsIHByZWRpY3Rpbmcgc3Vydml2YWwgYWZ0ZXIgdGhlbSBhbmQgZXN0aW1hdGVzIG9mIGFsbCBldmFsdWF0aW9uIG1ldHJpY3MuDQoNCjxicj4NCg0KIyMgKipSZXN1bHRzKioNCg0KPGJyPg0KDQojIyMgKipNb2RlbHMgd2l0aCB0aW1lLWluZGVwZW5kZW50IGNvdmFyaWF0ZXMqKg0KDQo8YnI+DQoNCmBgYHtyIHByZXBhcmVfZGF0YSwgZXZhbD1GQUxTRSwgZWNobz1UUlVFfQ0KWCA8LSB0cmFuc19wZXJpb2RzICU+JQ0KICB0cmFuc211dGUoY2h1cm5lZF9iZWZvcmVfbnVtLCANCiAgICAgICAgICAgIGNpdHlHID0gZmN0X2x1bXBfbWluKGNpdHksIDMwLCBvdGhlcl9sZXZlbCA9ICIxOS0yMCIpLCANCiAgICAgICAgICAgIGJkRywgZ2VuZGVyLCByZWdpc3RlcmVkX3ZpYSwgbW9udGhfcGVyaW9kLA0KICAgICAgICAgICAgdGltZV9mcm9tX3JlZywgdGltZV9mcm9tX3ByZXZfZXhwLCANCiAgICAgICAgICAgIGZpcnN0X3BheW1lbnRfbWV0aG9kX2lkLCBmaXJzdF9wbGFuX2xpc3RfcHJpY2UsDQogICAgICAgICAgICBwcmV2X3RpbWUsIHByZXZfcGN0X2FjdGl2ZSA9IDEwMCpwcmV2X3BjdF9hY3RpdmUsIA0KICAgICAgICAgICAgcHJldl9tZWFuX251bV90b3QsIHByZXZfbWVhbl90b3RhbF9zZWNzX2ltcCwNCiAgICAgICAgICAgIHByZXZfbWVhbl9udW1fdW5xLCBwcmV2X21lYW5fYXZfcGN0X3BsYXllZF9hY3QsDQogICAgICAgICAgICBwcmV2X21lYW5fYWN0dWFsX2Ftb3VudF9wYWlkLCBwcmV2X251bV9jYW5jZWwsDQogICAgICAgICAgICBwcmV2X21vZGVfcGF5bWVudF9pZCkNCg0KWF9iYXNlIDwtIFggJT4lIHNlbGVjdCgtY29udGFpbnMoInByZXZfIikpDQoNCllfY2xtbnMgPC0gdHJhbnNfcGVyaW9kcyAlPiUNCiAgc2VsZWN0KGNodXJuLCB0aW1lKQ0KDQpzYW1wbGVfWF90ZXN0X2lkIDwtIHJlYWQuY3N2KCJzYW1wbGVfWF90ZXN0X2lkLmNzdiIpDQoNCmNhdF9jb2x1bW5zIDwtIG1hcF9kZmMoWCwgfiAhaXMubnVtZXJpYygueCkpICU+JSB0KCkNCm51bV9jb2x1bW5zIDwtIG1hcF9kZmMoWCwgfmlzLm51bWVyaWMoLngpKSAlPiUgdCgpDQpjYXRfY29sdW1ucyA8LSBuYW1lcyhYKVtjYXRfY29sdW1uc1ssMV1dDQpudW1fY29sdW1ucyA8LSBuYW1lcyhYKVtudW1fY29sdW1uc1ssMV1dDQoNCmNhdF9iYXNlX2NvbHVtbnMgPC0gbWFwX2RmYyhYX2Jhc2UsIH4gIWlzLm51bWVyaWMoLngpKSAlPiUgdCgpDQpudW1fYmFzZV9jb2x1bW5zIDwtIG1hcF9kZmMoWF9iYXNlLCB+aXMubnVtZXJpYygueCkpICU+JSB0KCkNCmNhdF9iYXNlX2NvbHVtbnMgPC0gbmFtZXMoWF9iYXNlKVtjYXRfYmFzZV9jb2x1bW5zWywxXV0NCm51bV9iYXNlX2NvbHVtbnMgPC0gbmFtZXMoWF9iYXNlKVtudW1fYmFzZV9jb2x1bW5zWywxXV0NCmBgYA0KDQpgYGB7cHl0aG9uIHNldHVwLCBldmFsPUZBTFNFLCBlY2hvPVRSVUV9DQppbXBvcnQgbnVtcHkgYXMgbnANCmltcG9ydCBwYW5kYXMgYXMgcGQNCmltcG9ydCBtYXRwbG90bGliLnB5cGxvdCBhcyBwbHQNCmZyb20gc2tsZWFybi5pbnNwZWN0aW9uIGltcG9ydCBwZXJtdXRhdGlvbl9pbXBvcnRhbmNlDQpmcm9tIHNrbGVhcm4uYmFzZSBpbXBvcnQgVHJhbnNmb3JtZXJNaXhpbg0KZnJvbSBza2xlYXJuLm1vZGVsX3NlbGVjdGlvbiBpbXBvcnQgdHJhaW5fdGVzdF9zcGxpdCwgR3JpZFNlYXJjaENWLCBLRm9sZA0KZnJvbSBza3N1cnYucHJlcHJvY2Vzc2luZyBpbXBvcnQgT25lSG90RW5jb2Rlcg0KZnJvbSBza2xlYXJuLmNvbXBvc2UgaW1wb3J0IENvbHVtblRyYW5zZm9ybWVyDQpmcm9tIHNrbGVhcm4ucGlwZWxpbmUgaW1wb3J0IFBpcGVsaW5lDQpmcm9tIHNrc3Vydi5ub25wYXJhbWV0cmljIGltcG9ydCBrYXBsYW5fbWVpZXJfZXN0aW1hdG9yDQpmcm9tIHNrc3Vydi5saW5lYXJfbW9kZWwgaW1wb3J0IENveFBIU3Vydml2YWxBbmFseXNpcw0KZnJvbSBza3N1cnYuZW5zZW1ibGUgaW1wb3J0IFJhbmRvbVN1cnZpdmFsRm9yZXN0DQpmcm9tIHNrc3Vydi5tZXRyaWNzIGltcG9ydCBjb25jb3JkYW5jZV9pbmRleF9jZW5zb3JlZCwgY29uY29yZGFuY2VfaW5kZXhfaXBjdywgYnJpZXJfc2NvcmUsIGludGVncmF0ZWRfYnJpZXJfc2NvcmUsIGFzX2NvbmNvcmRhbmNlX2luZGV4X2lwY3dfc2NvcmVyDQoNCiMgRnVuY3Rpb24gdG8gZ2V0IHByZWRpY3Rpb25zIGZyb20gZXN0aW1hdG9yDQoNCmRlZiBnZXRfcHJlZGljdGlvbnMoWHRyYWluLCBYdGVzdCwgeXRyYWluLCB5dGVzdCwgZXN0aW1hdG9yKToNCiAgcHJlZF90cmFpbiA9IGVzdGltYXRvci5wcmVkaWN0KFh0cmFpbikNCiAgcHJlZF90ZXN0ID0gZXN0aW1hdG9yLnByZWRpY3QoWHRlc3QpDQogIHByZWRfc3Vydl90cmFpbiA9IGVzdGltYXRvci5wcmVkaWN0X3N1cnZpdmFsX2Z1bmN0aW9uKFh0cmFpbikNCiAgcHJlZF9zdXJ2X3Rlc3QgPSBlc3RpbWF0b3IucHJlZGljdF9zdXJ2aXZhbF9mdW5jdGlvbihYdGVzdCkNCiAgDQogIHJldHVybiBwcmVkX3RyYWluLCBwcmVkX3Rlc3QsIHByZWRfc3Vydl90cmFpbiwgcHJlZF9zdXJ2X3Rlc3QNCg0KIyBGdW5jdGlvbiB0byBnZXQgbWV0cmljcyBmb3IgbW9kZWwNCg0KZGVmIGdldF9tZXRyaWNzX2RmKFh0cmFpbiwgWHRlc3QsIHl0cmFpbiwgeXRlc3QsIGVzdGltYXRvcik6DQogIHByZWRfdHJhaW4gPSBlc3RpbWF0b3IucHJlZGljdChYdHJhaW4pDQogIHByZWRfdGVzdCA9IGVzdGltYXRvci5wcmVkaWN0KFh0ZXN0KQ0KICBwcmVkX3N1cnZfdHJhaW4gPSBlc3RpbWF0b3IucHJlZGljdF9zdXJ2aXZhbF9mdW5jdGlvbihYdHJhaW4pDQogIHByZWRfc3Vydl90ZXN0ID0gZXN0aW1hdG9yLnByZWRpY3Rfc3Vydml2YWxfZnVuY3Rpb24oWHRlc3QpDQoNCiAgaGNfdHJhaW4gPSBjb25jb3JkYW5jZV9pbmRleF9jZW5zb3JlZCh5dHJhaW5bIkNodXJuIl0sIHl0cmFpblsiVGltZSJdLCBwcmVkX3RyYWluKVswXQ0KICBoY190ZXN0ID0gY29uY29yZGFuY2VfaW5kZXhfY2Vuc29yZWQoeXRlc3RbIkNodXJuIl0sIHl0ZXN0WyJUaW1lIl0sIHByZWRfdGVzdClbMF0NCiAgdWNfdHJhaW4gPSBjb25jb3JkYW5jZV9pbmRleF9pcGN3KHl0cmFpbiwgeXRyYWluLCBwcmVkX3RyYWluKVswXQ0KICB1Y190ZXN0ID0gY29uY29yZGFuY2VfaW5kZXhfaXBjdyh5dGVzdCwgeXRlc3QsIHByZWRfdGVzdClbMF0NCiAgDQogIHRpbWVzID0gbnAuYXJyYXkoWzcsIDE0LCAzMCwgNjAsIDkwLCAxODAsIDM2MCwgNzIwXSkNCiAgcHJlZHNfdHJhaW4gPSBucC5hc2FycmF5KFtbZm4odCkgZm9yIHQgaW4gdGltZXNdIGZvciBmbiBpbiBwcmVkX3N1cnZfdHJhaW5dKQ0KICBwcmVkc190ZXN0ID0gbnAuYXNhcnJheShbW2ZuKHQpIGZvciB0IGluIHRpbWVzXSBmb3IgZm4gaW4gcHJlZF9zdXJ2X3Rlc3RdKQ0KDQogIGJzX3RyYWluID0gYnJpZXJfc2NvcmUoeXRyYWluLCB5dHJhaW4sIHByZWRzX3RyYWluLCB0aW1lcykNCiAgYnNfdGVzdCA9IGJyaWVyX3Njb3JlKHl0ZXN0LCB5dGVzdCwgcHJlZHNfdGVzdCwgdGltZXMpDQogIA0KICB0aW1lczIgPSBucC5hcmFuZ2UoMSwgNzIwKQ0KICAjcHJlZHNfdHJhaW4yID0gbnAuYXNhcnJheShbW2ZuKHQpIGZvciB0IGluIHRpbWVzMl0gZm9yIGZuIGluIHByZWRfc3Vydl90cmFpbl0pDQogIHByZWRzX3Rlc3QyID0gbnAuYXNhcnJheShbW2ZuKHQpIGZvciB0IGluIHRpbWVzMl0gZm9yIGZuIGluIHByZWRfc3Vydl90ZXN0XSkNCiAgDQogICNpYnNfdHJhaW4gPSBpbnRlZ3JhdGVkX2JyaWVyX3Njb3JlKHl0cmFpbiwgeXRyYWluLCBwcmVkc190cmFpbjIsIHRpbWVzMikNCiAgaWJzX3Rlc3QgPSBpbnRlZ3JhdGVkX2JyaWVyX3Njb3JlKHl0ZXN0LCB5dGVzdCwgcHJlZHNfdGVzdDIsIHRpbWVzMikNCiAgDQogIHJlc3VsdHMgPSBwZC5EYXRhRnJhbWUoDQogICAgeyJNZXRyaWMiOiBbJ0hhcnJlbGBzIEMnLCAnVW5vYHMgQycsICdJQlMgMS03MjBkJywgJ0JTIDdkJywgJ0JTIDE0ZCcsICdCUyAzMGQnLCAnQlMgNjBkJywNCiAgICAgICdCUyA5MGQnLCAnQlMgMTgwZCcsICdCUyAzNjBkJywgJ0JTIDcyMGQnXSwNCiAgICAgICJUcmFpbiI6IFtoY190cmFpbiwgdWNfdHJhaW4sICdOVUxMJ10gKyBbaSBmb3IgaSBpbiBic190cmFpblsxXV0sDQogICAgICAiVGVzdCI6IFtoY190ZXN0LCB1Y190ZXN0LCBpYnNfdGVzdF0gKyBbaSBmb3IgaSBpbiBic190ZXN0WzFdXX0pDQogIA0KICByZXR1cm4gcmVzdWx0cywgYnNfdHJhaW4sIGJzX3Rlc3QNCg0KIyBGdW5jdGlvbiB0byBwbG90IHRpbWUtZGVwZW5kZW50IEJyaWVyIHNjb3JlDQoNCmRlZiBwbG90X3RkX2JyaWVyKGJzX3R1cGxlKToNCiAgcGx0LmZpZ3VyZSgpDQogIHBsdC5wbG90KFtpIGZvciBpIGluIGJzX2Jhc2VfdGVzdFswXV0sIFtpIGZvciBpIGluIGJzX2Jhc2VfdGVzdFsxXV0sIG1hcmtlcj0ibyIpDQogIHBsdC54bGFiZWwoIkRheXMgZnJvbSBzdGFydCBvZiBzdWJzY3JpcHRpb24iKQ0KICBwbHQueWxhYmVsKCJUaW1lLWRlcGVuZGVudCBCcmllciBzY29yZSIpDQogIHBsdC54dGlja3MoWzcsMzAsNjAsOTAsMTgwLDM2MCw3MjBdLCBbNywzMCw2MCw5MCwxODAsMzYwLDcyMF0pDQogIHBsdC5ncmlkKCkNCiAgcGx0LnNob3coKQ0KYGBgDQoNCmBgYHtweXRob24gcHJlcHJvYywgZXZhbD1GQUxTRSwgZWNobz1UUlVFfQ0KWF9iYXNlID0gci5YX2Jhc2UNClhfYmFzZS5zaGFwZQ0KDQpYID0gci5YDQpYLnNoYXBlDQoNCmNhdF9jb2x1bW5zID0gci5jYXRfY29sdW1ucw0KbnVtX2NvbHVtbnMgPSByLm51bV9jb2x1bW5zDQpjYXRfYmFzZV9jb2x1bW5zID0gci5jYXRfYmFzZV9jb2x1bW5zDQpudW1fYmFzZV9jb2x1bW5zID0gci5udW1fYmFzZV9jb2x1bW5zDQoNCmF1eCA9IFt0dXBsZShbYm9vbChpWzBdKSwgaVsxXV0pIGZvciBpIGluIHIuWV9jbG1ucy50b19udW1weSgpXQ0KeSA9IG5wLmFycmF5KGF1eCwgZHR5cGU9WygnQ2h1cm4nLCAnPycpLCAoJ1RpbWUnLCAnPGY4JyldKQ0KeQ0KeS5zaGFwZQ0KDQpyYW5kb21fc3RhdGUgPSA0Mg0KDQpYX3RyYWluLCBYX3Rlc3QsIHlfdHJhaW4sIHlfdGVzdCA9IHRyYWluX3Rlc3Rfc3BsaXQoWCwgeSwgdGVzdF9zaXplPTAuMywgcmFuZG9tX3N0YXRlPXJhbmRvbV9zdGF0ZSkNClhfYmFzZV90cmFpbiwgWF9iYXNlX3Rlc3QsIF8sIF8gPSB0cmFpbl90ZXN0X3NwbGl0KFhfYmFzZSwgeSwgdGVzdF9zaXplPTAuMywgcmFuZG9tX3N0YXRlPXJhbmRvbV9zdGF0ZSkNCg0Kc2FtcGxlX1hfdGVzdF9pZCA9IHIuc2FtcGxlX1hfdGVzdF9pZA0Kc2FtcGxlX1hfdGVzdCA9IFhfdGVzdC5sb2Nbc2FtcGxlX1hfdGVzdF9pZFsnSUQnXS52YWx1ZXNdDQpzYW1wbGVfWF9iYXNlX3Rlc3QgPSBYX2Jhc2VfdGVzdC5sb2Nbc2FtcGxlX1hfdGVzdF9pZFsnSUQnXS52YWx1ZXNdDQoNCnNhbXBsZV95X2NodXJuID0gW2ludChpWzBdKSBmb3IgaSBpbiB5W3NhbXBsZV9YX3Rlc3RfaWRbJ0lEJ10udmFsdWVzXV0NCnNhbXBsZV95X3RpbWUgPSBbaW50KGlbMV0pIGZvciBpIGluIHlbc2FtcGxlX1hfdGVzdF9pZFsnSUQnXS52YWx1ZXNdXQ0KDQpzYW1wbGVfc29ydCA9IG5wLmFyZ3NvcnQoc2FtcGxlX3lfdGltZSkNCnNhbXBsZV9zb3J0ID0gc2FtcGxlX3NvcnRbOjotMV0NCg0Kc2FtcGxlX1hfdGVzdCA9IHNhbXBsZV9YX3Rlc3QuaWxvY1tzYW1wbGVfc29ydF0NCnNhbXBsZV9YX2Jhc2VfdGVzdCA9IHNhbXBsZV9YX2Jhc2VfdGVzdC5pbG9jW3NhbXBsZV9zb3J0XQ0KDQpzYW1wbGVfeV9jaHVybiA9IFtzYW1wbGVfeV9jaHVybltpXSBmb3IgaSBpbiBzYW1wbGVfc29ydF0NCnNhbXBsZV95X3RpbWUgPSBbc2FtcGxlX3lfdGltZVtpXSBmb3IgaSBpbiBzYW1wbGVfc29ydF0NCg0KZmVhdHVyZV9uYW1lcyA9IFhfdHJhaW4uY29sdW1ucy50b2xpc3QoKQ0KZmVhdHVyZV9uYW1lc19iYXNlID0gWF9iYXNlX3RyYWluLmNvbHVtbnMudG9saXN0KCkNCmBgYA0KDQpgYGB7cHl0aG9uIGNveCwgZXZhbD1GQUxTRSwgZWNobz1UUlVFfQ0KIyBCYXNlbGluZSBDb3ggd2l0aG91dCBkYXRhIG9uIHByZXZpb3VzIHN1YnNjcmlwdGlvbg0KDQpjb3hfcGhfYmFzZSA9IFBpcGVsaW5lKA0KICBbDQogICAgKCdlbmNvZGUnLCBPbmVIb3RFbmNvZGVyKCkpLA0KICAgICgibW9kZWwiLCBDb3hQSFN1cnZpdmFsQW5hbHlzaXMoKSkNCiAgXQ0KKQ0KDQplbmNvZGVyX25hbWVzID0gT25lSG90RW5jb2RlcigpDQoNCmNveF9waF9iYXNlLmZpdChYX2Jhc2VfdHJhaW4sIHlfdHJhaW4pDQoNCmNveF9waF9iYXNlX2NvZWYgPSBwZC5TZXJpZXMoY294X3BoX2Jhc2UubmFtZWRfc3RlcHNbJ21vZGVsJ10uY29lZl8sIGluZGV4PWVuY29kZXJfbmFtZXMuZml0X3RyYW5zZm9ybShYX2Jhc2VfdHJhaW4pLmNvbHVtbnMpDQpjb3hfcGhfYmFzZV9jb2VmID0gY294X3BoX2Jhc2VfY29lZi5yZXNldF9pbmRleCgpDQpjb3hfcGhfYmFzZV9jb2VmLmNvbHVtbnMgPSBbJ1ZhcmlhYmxlJywgJ0NvZWYnXQ0KY294X3BoX2Jhc2VfY29lZi5zb3J0X3ZhbHVlcyhieT0nQ29lZicsIGtleT1hYnMsIGFzY2VuZGluZz1GYWxzZSwgaW5wbGFjZT1UcnVlKQ0KY294X3BoX2Jhc2VfY29lZlsnSFInXSA9IGNveF9waF9iYXNlX2NvZWZbJ0NvZWYnXS5hcHBseShsYW1iZGEgeDogbnAuZXhwKHgpKQ0KY294X3BoX2Jhc2VfY29lZg0KDQpjb3hfYmFzZV9wcmVkX3RyYWluLCBjb3hfYmFzZV9wcmVkX3Rlc3QsIGNveF9iYXNlX3N1cnZfdHJhaW4sIGNveF9iYXNlX3N1cnZfdGVzdCA9IGdldF9wcmVkaWN0aW9ucyhYX2Jhc2VfdHJhaW4sIFhfYmFzZV90ZXN0LCB5X3RyYWluLCB5X3Rlc3QsIGNveF9waF9iYXNlKQ0KDQpjb3hfYmFzZV9yZXN1bHRzLCBic19iYXNlX3RyYWluLCBic19iYXNlX3Rlc3QgPSBnZXRfbWV0cmljc19kZihYX2Jhc2VfdHJhaW4sIFhfYmFzZV90ZXN0LCB5X3RyYWluLCB5X3Rlc3QsIGNveF9waF9iYXNlKQ0KDQpjb3hfYmFzZV9yZXN1bHRzDQoNCnBsb3RfdGRfYnJpZXIoYnNfYmFzZV90ZXN0KQ0KDQpwcmVkX3N1cnYgPSBjb3hfcGhfYmFzZS5wcmVkaWN0X3N1cnZpdmFsX2Z1bmN0aW9uKHNhbXBsZV9YX2Jhc2VfdGVzdCkNCg0KdGltZV9wb2ludHMgPSBucC5hcmFuZ2UoMSwgNzIwKQ0KcGx0LmZpZ3VyZSgpDQpmb3IgaSwgc3Vydl9mdW5jIGluIGVudW1lcmF0ZShwcmVkX3N1cnYpOg0KICAgIHBsdC5zdGVwKHRpbWVfcG9pbnRzLCBzdXJ2X2Z1bmModGltZV9wb2ludHMpLCB3aGVyZT0icG9zdCIsDQogICAgICAgICAgICAgbGFiZWw9IkNodXJuPXtjaHVybn0sIFRpbWU9e3RpbWV9ZCIuZm9ybWF0KGNodXJuPXNhbXBsZV95X2NodXJuW2ldLCB0aW1lPXNhbXBsZV95X3RpbWVbaV0pKQ0KcGx0LnlsYWJlbCgiRXN0LiBwcm9iYWJpbGl0eSBvZiBzdXJ2aXZhbCAkXGhhdHtTfSh0KSQiKQ0KcGx0LnhsYWJlbCgiVGltZSAkdCQiKQ0KcGx0LnRpdGxlKCdDb3ggUEggYmFzZWxpbmUnKQ0KcGx0LmxlZ2VuZChiYm94X3RvX2FuY2hvcj0oMC43LCAwLjcpKQ0KcGx0LnNob3coKQ0KDQoNCiMgQ294IHdpdGggZGF0YSBvbiBwcmV2aW91cyBzdWJzY3JpcHRpb24NCg0KY294X3BoID0gUGlwZWxpbmUoDQogIFsNCiAgICAoJ2VuY29kZScsIE9uZUhvdEVuY29kZXIoKSksDQogICAgKCJtb2RlbCIsIENveFBIU3Vydml2YWxBbmFseXNpcygpKQ0KICBdDQopDQoNCmNveF9waC5maXQoWF90cmFpbiwgeV90cmFpbikNCg0KY294X3BoX2NvZWYgPSBwZC5TZXJpZXMoY294X3BoLm5hbWVkX3N0ZXBzWydtb2RlbCddLmNvZWZfLCBpbmRleD1lbmNvZGVyX25hbWVzLmZpdF90cmFuc2Zvcm0oWF90cmFpbikuY29sdW1ucykNCmNveF9waF9jb2VmID0gY294X3BoX2NvZWYucmVzZXRfaW5kZXgoKQ0KY294X3BoX2NvZWYuY29sdW1ucyA9IFsnVmFyaWFibGUnLCAnQ29lZiddDQpjb3hfcGhfY29lZi5zb3J0X3ZhbHVlcyhieT0nQ29lZicsIGtleT1hYnMsIGFzY2VuZGluZz1GYWxzZSwgaW5wbGFjZT1UcnVlKQ0KY294X3BoX2NvZWZbJ0hSJ10gPSBjb3hfcGhfY29lZlsnQ29lZiddLmFwcGx5KGxhbWJkYSB4OiBucC5leHAoeCkpDQpjb3hfcGhfY29lZg0KDQpjb3hfcHJlZF90cmFpbiwgY294X3ByZWRfdGVzdCwgY294X3N1cnZfdHJhaW4sIGNveF9zdXJ2X3Rlc3QgPSBnZXRfcHJlZGljdGlvbnMoWF90cmFpbiwgWF90ZXN0LCB5X3RyYWluLCB5X3Rlc3QsIGNveF9waCkNCg0KY294X3Jlc3VsdHMsIGJzX3RyYWluLCBic190ZXN0ID0gZ2V0X21ldHJpY3NfZGYoWF90cmFpbiwgWF90ZXN0LCB5X3RyYWluLCB5X3Rlc3QsIGNveF9waCkNCg0KY294X3Jlc3VsdHMNCg0KcGxvdF90ZF9icmllcihic190ZXN0KQ0KDQoNCnByZWRfc3VydiA9IGNveF9waC5wcmVkaWN0X3N1cnZpdmFsX2Z1bmN0aW9uKHNhbXBsZV9YX3Rlc3QpDQoNCnRpbWVfcG9pbnRzID0gbnAuYXJhbmdlKDEsIDcyMCkNCnBsdC5maWd1cmUoKQ0KZm9yIGksIHN1cnZfZnVuYyBpbiBlbnVtZXJhdGUocHJlZF9zdXJ2KToNCiAgICBwbHQuc3RlcCh0aW1lX3BvaW50cywgc3Vydl9mdW5jKHRpbWVfcG9pbnRzKSwgd2hlcmU9InBvc3QiLA0KICAgICAgICAgICAgIGxhYmVsPSJDaHVybj17Y2h1cm59LCBUaW1lPXt0aW1lfWQiLmZvcm1hdChjaHVybj1zYW1wbGVfeV9jaHVybltpXSwgdGltZT1zYW1wbGVfeV90aW1lW2ldKSkNCnBsdC55bGFiZWwoIkVzdC4gcHJvYmFiaWxpdHkgb2Ygc3Vydml2YWwgJFxoYXR7U30odCkkIikNCnBsdC54bGFiZWwoIlRpbWUgJHQkIikNCnBsdC50aXRsZSgnQ294IFBIIHByZXYnKQ0KcGx0LmxlZ2VuZChiYm94X3RvX2FuY2hvcj0oMC43LCAwLjcpKQ0KcGx0LnNob3coKQ0KYGBgDQoNCmBgYHtweXRob24gcnNmLCBldmFsPUZBTFNFLCBlY2hvPVRSVUV9DQojIFRyYWluIGFuZCB2YWxpZGF0aW9uIHNldHMNCg0KWF9iYXNlX3R0LCBYX2Jhc2VfdmFsLCB5X3R0LCB5X3ZhbCA9IHRyYWluX3Rlc3Rfc3BsaXQoWF9iYXNlX3RyYWluLCB5X3RyYWluLCB0ZXN0X3NpemU9MC4yLCByYW5kb21fc3RhdGU9cmFuZG9tX3N0YXRlKQ0KWF90dCwgWF92YWwsIF8sIF8gPSB0cmFpbl90ZXN0X3NwbGl0KFhfdHJhaW4sIHlfdHJhaW4sIHRlc3Rfc2l6ZT0wLjIsIHJhbmRvbV9zdGF0ZT1yYW5kb21fc3RhdGUpDQoNCiMgUlNGIHR1bmluZw0KDQpyc2ZfYmFzZV9zY29yZXMgPSBbXQ0KDQpmb3IgbWQgaW4gbnAuYXJhbmdlKDUsIDExLCBkdHlwZT1pbnQpOg0KICByc2YgPSBSYW5kb21TdXJ2aXZhbEZvcmVzdChuX2VzdGltYXRvcnM9NTAwLCBtYXhfZGVwdGg9bWQsIG1heF9mZWF0dXJlcz0ic3FydCIsIG1pbl9zYW1wbGVzX2xlYWY9MTAsIG1pbl9zYW1wbGVzX3NwbGl0PTEwLCBuX2pvYnM9LTEsIHJhbmRvbV9zdGF0ZT1yYW5kb21fc3RhdGUpDQogIHJzZl9maXQgPSByc2YuZml0KFhfYmFzZV90dCwgeV90dCkNCiAgcnNmX2Jhc2Vfc2NvcmVzICs9IFtyc2Yuc2NvcmUoWF9iYXNlX3ZhbCwgeV92YWwpXQ0KICBwcmludCBtZCwgcnNmX2Jhc2Vfc2NvcmVzWy0xXQ0KDQpmb3IgbXNsIGluIG5wLmFycmF5KFs1LDEwLDE1LDIwXSwgZHR5cGU9aW50KToNCiAgcnNmID0gUmFuZG9tU3Vydml2YWxGb3Jlc3Qobl9lc3RpbWF0b3JzPTUwMCwgbWF4X2RlcHRoPTE0LCBtYXhfZmVhdHVyZXM9InNxcnQiLCBtaW5fc2FtcGxlc19sZWFmPW1zbCwgbWluX3NhbXBsZXNfc3BsaXQ9MTAsIG5fam9icz0tMSwgcmFuZG9tX3N0YXRlPXJhbmRvbV9zdGF0ZSkNCiAgcnNmX2ZpdCA9IHJzZi5maXQoWF9iYXNlX3R0LCB5X3R0KQ0KICByc2ZfYmFzZV9zY29yZXMgKz0gW3JzZi5zY29yZShYX2Jhc2VfdmFsLCB5X3ZhbCldDQogIHByaW50KG1zbCwgcnNmX2Jhc2Vfc2NvcmVzWy0xXSkNCiAgDQoNCiMgUlNGIHdpdGggYmFzZWxpbmUgZGF0YQ0KDQpyc2ZfYmFzZSA9IFBpcGVsaW5lKA0KICBbDQogICAgKCdlbmNvZGUnLCBPbmVIb3RFbmNvZGVyKCkpLA0KICAgICgibW9kZWwiLCBSYW5kb21TdXJ2aXZhbEZvcmVzdChuX2VzdGltYXRvcnM9NTAwLCBtYXhfZGVwdGg9MTQsIG1heF9mZWF0dXJlcz0ic3FydCIsIG1pbl9zYW1wbGVzX2xlYWY9NSwgbWluX3NhbXBsZXNfc3BsaXQ9MTAsIG5fam9icz0tMSwgcmFuZG9tX3N0YXRlPXJhbmRvbV9zdGF0ZSkpDQogIF0NCikNCg0KcnNmX2Jhc2UuZml0KFhfYmFzZV90cmFpbiwgeV90cmFpbikNCg0KcnNmX2Jhc2VfcHJlZF90cmFpbiwgcnNmX2Jhc2VfcHJlZF90ZXN0LCByc2ZfYmFzZV9zdXJ2X3RyYWluLCByc2ZfYmFzZV9zdXJ2X3Rlc3QgPSBnZXRfcHJlZGljdGlvbnMoWF9iYXNlX3RyYWluLCBYX2Jhc2VfdGVzdCwgeV90cmFpbiwgeV90ZXN0LCByc2ZfYmFzZSwgcmY9VHJ1ZSkNCg0KcnNmX2Jhc2VfcmVzdWx0cywgcnNmX2Jhc2VfYnNfdHJhaW4sIHJzZl9iYXNlX2JzX3Rlc3QgPSBnZXRfbWV0cmljc19kZihYX2Jhc2VfdHJhaW4sIFhfYmFzZV90ZXN0LCB5X3RyYWluLCB5X3Rlc3QsIHJzZl9iYXNlKQ0KDQpyc2ZfYmFzZV9yZXN1bHRzDQoNCnByZWRfc3VydiA9IHJzZl9iYXNlLnByZWRpY3Rfc3Vydml2YWxfZnVuY3Rpb24oc2FtcGxlX1hfYmFzZV90ZXN0KQ0KDQp0aW1lX3BvaW50cyA9IG5wLmFyYW5nZSgxLCA3MjApDQpwbHQuZmlndXJlKCkNCmZvciBpLCBzdXJ2X2Z1bmMgaW4gZW51bWVyYXRlKHByZWRfc3Vydik6DQogICAgcGx0LnN0ZXAodGltZV9wb2ludHMsIHN1cnZfZnVuYyh0aW1lX3BvaW50cyksIHdoZXJlPSJwb3N0IiwNCiAgICAgICAgICAgICBsYWJlbD0iQ2h1cm49e2NodXJufSwgVGltZT17dGltZX1kIi5mb3JtYXQoY2h1cm49c2FtcGxlX3lfY2h1cm5baV0sIHRpbWU9c2FtcGxlX3lfdGltZVtpXSkpDQpwbHQueWxhYmVsKCJFc3QuIHByb2JhYmlsaXR5IG9mIHN1cnZpdmFsICRcaGF0e1N9KHQpJCIpDQpwbHQueGxhYmVsKCJUaW1lICR0JCIpDQpwbHQubGVnZW5kKGJib3hfdG9fYW5jaG9yPSgwLjcsIDAuNykpDQpwbHQudGl0bGUoJ1JTRiBiYXNlbGluZScpDQpwbHQuc2hvdygpDQoNCnJzZl9pbXBfYmFzZSA9IHBlcm11dGF0aW9uX2ltcG9ydGFuY2UocnNmX2Jhc2UsIFhfYmFzZV90ZXN0LCB5X3Rlc3QsIG5fcmVwZWF0cz0xMCwgcmFuZG9tX3N0YXRlPXJhbmRvbV9zdGF0ZSkNCnNvcnRlZF9pZHggPSByc2ZfaW1wX2Jhc2UuaW1wb3J0YW5jZXNfbWVhbi5hcmdzb3J0KCkNCg0KZmlnLCBheCA9IHBsdC5zdWJwbG90cygpDQpheC5ib3hwbG90KA0KICAgIHJzZl9pbXBfYmFzZS5pbXBvcnRhbmNlc1tzb3J0ZWRfaWR4XS5ULCB2ZXJ0PUZhbHNlLCBsYWJlbHM9WF9iYXNlX3RyYWluLmNvbHVtbnNbc29ydGVkX2lkeF0NCikNCmF4LnNldF90aXRsZSgiUlNGIGJhc2VsaW5lLCB2YXJpYWJsZSBpbXBvcnRhbmNlIChwZXJtdXRhdGlvbikiKQ0KZmlnLnRpZ2h0X2xheW91dCgpDQpwbHQuc2hvdygpDQoNCg0KIyBSU0Ygd2l0aCBkYXRhIG9uIHByZXZpb3VzIHN1YnNjcmlwdGlvbnMNCg0KcnNmID0gUGlwZWxpbmUoDQogIFsNCiAgICAoJ2VuY29kZScsIE9uZUhvdEVuY29kZXIoKSksDQogICAgKCJtb2RlbCIsIFJhbmRvbVN1cnZpdmFsRm9yZXN0KG5fZXN0aW1hdG9ycz01MDAsIG1heF9kZXB0aD0xNCwgbWF4X2ZlYXR1cmVzPSJzcXJ0IiwgbWluX3NhbXBsZXNfbGVhZj01LCBtaW5fc2FtcGxlc19zcGxpdD0xMCwgbl9qb2JzPS0xLCByYW5kb21fc3RhdGU9cmFuZG9tX3N0YXRlKSkNCiAgXQ0KKQ0KDQpyc2YuZml0KFhfdHJhaW4sIHlfdHJhaW4pDQoNCnJzZl9wcmVkX3RyYWluLCByc2ZfcHJlZF90ZXN0LCByc2Zfc3Vydl90cmFpbiwgcnNmX3N1cnZfdGVzdCA9IGdldF9wcmVkaWN0aW9ucyhYX3RyYWluLCBYX3Rlc3QsIHlfdHJhaW4sIHlfdGVzdCwgcnNmLCByZj1UcnVlKQ0KDQpyc2ZfcmVzdWx0cywgcnNmX2JzX3RyYWluLCByc2ZfYnNfdGVzdCA9IGdldF9tZXRyaWNzX2RmKFhfdHJhaW4sIFhfdGVzdCwgeV90cmFpbiwgeV90ZXN0LCByc2YpDQoNCnJzZl9yZXN1bHRzDQoNCnByZWRfc3VydiA9IHJzZi5wcmVkaWN0X3N1cnZpdmFsX2Z1bmN0aW9uKHNhbXBsZV9YX3Rlc3QpDQoNCnRpbWVfcG9pbnRzID0gbnAuYXJhbmdlKDEsIDcyMCkNCnBsdC5maWd1cmUoKQ0KZm9yIGksIHN1cnZfZnVuYyBpbiBlbnVtZXJhdGUocHJlZF9zdXJ2KToNCiAgICBwbHQuc3RlcCh0aW1lX3BvaW50cywgc3Vydl9mdW5jKHRpbWVfcG9pbnRzKSwgd2hlcmU9InBvc3QiLA0KICAgICAgICAgICAgIGxhYmVsPSJDaHVybj17Y2h1cm59LCBUaW1lPXt0aW1lfWQiLmZvcm1hdChjaHVybj1pbnQoeV90ZXN0W3NhbXBsZV9tYXNrLmluZGV4W2ldXVswXSksIHRpbWU9aW50KHlfdGVzdFtzYW1wbGVfbWFzay5pbmRleFtpXV1bMV0pKSkNCnBsdC55bGFiZWwoIkVzdC4gcHJvYmFiaWxpdHkgb2Ygc3Vydml2YWwgJFxoYXR7U30odCkkIikNCnBsdC54bGFiZWwoIlRpbWUgJHQkIikNCnBsdC5sZWdlbmQoYmJveF90b19hbmNob3I9KDAuNywgMC43KSkNCnBsdC50aXRsZSgnUlNGIHByZXYnKQ0KcGx0LnNob3coKQ0KDQpyc2ZfaW1wID0gcGVybXV0YXRpb25faW1wb3J0YW5jZShyc2YsIFhfdGVzdCwgeV90ZXN0LCBuX3JlcGVhdHM9MTAsIHJhbmRvbV9zdGF0ZT1yYW5kb21fc3RhdGUpDQpzb3J0ZWRfaWR4ID0gcnNmX2ltcC5pbXBvcnRhbmNlc19tZWFuLmFyZ3NvcnQoKQ0KDQpmaWcsIGF4ID0gcGx0LnN1YnBsb3RzKCkNCmF4LmJveHBsb3QoDQogICAgcnNmX2ltcC5pbXBvcnRhbmNlc1tzb3J0ZWRfaWR4XS5ULCB2ZXJ0PUZhbHNlLCBsYWJlbHM9WF90cmFpbi5jb2x1bW5zW3NvcnRlZF9pZHhdDQopDQpheC5zZXRfdGl0bGUoIlJTRiBwcmV2LCB2YXJpYWJsZSBpbXBvcnRhbmNlIChwZXJtdXRhdGlvbikiKQ0KZmlnLnRpZ2h0X2xheW91dCgpDQpwbHQuc2hvdygpDQpgYGANCg0KV2UgZXN0aW1hdGVkIENveCBQSCBhbmQgUlNGIG1vZGVscyBpbiB0d28gdmFyaWFudHMgYm90aDogDQoNCi0gd2l0aCBiYXNlbGluZSB2YXJpYWJsZXMgb25seSwgaS5lLiB3aXRoIHN1Y2ggZmVhdHVyZXMgd2hpY2ggYXJlIGtub3duIGF0IHRoZSBzdGFydCBvZiBzdWJzY3JpcHRpb24gKG1lbWJlcidzIGNoYXJhY3RlcmlzdGljcyBhbmQgZGF0YSBvbiBoaXMgZmlyc3QgdHJhbnNhY3Rpb24sIGZyb20gd2hpY2ggc3Vic2NyaXB0aW9uIHN0YXJ0ZWQpLCB3aXRob3V0IGFueSBpbmZvcm1hdGlvbiBhYm91dCBwcmV2aW91cyBoaXN0b3J5IG9mIHRoaXMgdXNlciwgZXhjZXB0IHRoZSBudW1iZXIgb2YgcHJldmlvdXMgY2h1cm5zIChvbiB0aGUgZGlhZ3JhbXMgYW5kIGluIHRoZSB0YWJsZXMgYmVsb3cgdGhlc2UgbW9kZWxzIGdvIHdpdGggdGhlIGluZGV4ICdiYXNlbGluZScpLA0KDQotIHdpdGggYmFzZWxpbmUgY2hhcmFjdGVyaXN0aWNzIGFuZCBmZWF0dXJlcyBjb25jZXJuaW5nIGFsbCBwcmV2aW91cyBzdWJzY3JpcHRpb25zIChvbiB0aGUgZGlhZ3JhbXMgYW5kIGluIHRoZSB0YWJsZXMgYmVsb3cgdGhlc2UgbW9kZWxzIGdvIHdpdGggdGhlIGluZGV4ICdwcmV2JykuDQoNCk92ZXJhbGwsIFJTRiBpcyBhIGJpdCBiZXR0ZXIgaW4gcHJlZGljdGlvbiBwZXJmb3JtYW5jZSBtZWFzdXJlZCBieSBDLWluZGljZXMgKEhhcnJlbGwncyBDIGRvZXMgbm90IHRha2UgY2Vuc29yaW5nIGRpc3RyaWJ1dGlvbiBpbnRvIGFjY291bnQsIHdoaWxlIFVubydzIEMgZG9lcykgdGhhbiBDb3ggUEguIEFuZCBib3RoIG9mIHRoZW0gcGVyZm9ybXMgYSBiaXQgYmV0dGVyIHdpdGggZmVhdHVyZXMgb24gcHJldmlvdXMgc3Vic2NyaXB0aW9ucywgYWx0aG91Z2ggZm9yIFJTRiB0aGlzIGdhaW4gaXMgYWxtb3N0IHVubm90aWNlYWJsZS4gSW4gYWRkaXRpb24sIGJvdGggbW9kZWxzIGdpdmUgcXVpcmUgY29tcGFyYWJsZSByZXN1bHRzIG9uIHRyYWluIGFuZCB0ZXN0IHNldHM6DQoNCmBgYHtyIGNpbmRleCwgZmlnLndpZHRoPTgsIGZpZy5oZWlnaHQ9M30NCm1ldHJpY3MgPC0gbGlzdChDb3hQSF9iYXNlID0gcmVhZC5jc3YoIkNveF9iYXNlLmNzdiIsIHNlcD0iOyIpLA0KICAgICAgICAgICAgICAgIENveFBIID0gcmVhZC5jc3YoIkNveC5jc3YiLCBzZXA9IjsiKSwNCiAgICAgICAgICAgICAgICBSU0ZfYmFzZSA9IHJlYWQuY3N2KCJyc2ZfYmFzZS5jc3YiLCBzZXA9IjsiKSwNCiAgICAgICAgICAgICAgICBSU0YgPSByZWFkLmNzdigicnNmLmNzdiIsIHNlcD0iOyIpKQ0KDQpjaW5kZXggPC0gbGFwcGx5KG1ldHJpY3MsIGZ1bmN0aW9uKHgpIHhbMToyLF0pDQpjaW5kZXggPC0gbGFwcGx5KGNpbmRleCwgZnVuY3Rpb24oeCkge3hbLDJdIDwtIGFzLm51bWVyaWMoeFssMl0pIA0KICB4fSkNCmNpbmRleCA8LSBkby5jYWxsKGRwbHlyOjpiaW5kX3Jvd3MsIGNpbmRleCkNCg0KY2luZGV4JG1vZGVsIDwtIHJlcChjKCJDb3ggUEggYmFzZWxpbmUiLCAiQ294IFBIIHByZXYiLCAiUlNGIGJhc2VsaW5lIiwgIlJTRiBwcmV2IiksIGVhY2g9MikNCmNpbmRleCA8LSByYmluZChjaW5kZXhbLGMoMToyLDQpXSAlPiUgcmVuYW1lKHZhbHVlPVRyYWluKSAlPiUgbXV0YXRlKHNldD0iVHJhaW4iKSwgDQogICAgICAgICAgICAgICAgY2luZGV4WyxjKDEsMzo0KV0gJT4lIHJlbmFtZSh2YWx1ZT1UZXN0KSAlPiUgbXV0YXRlKHNldD0iVGVzdCIpKQ0KY2luZGV4JE1ldHJpYyA8LSBnc3ViKCJIYXJyZWwiLCAiSGFycmVsbCIsIGNpbmRleCRNZXRyaWMpDQoNCmdncGxvdCgpICsNCiAgZ2VvbV9iYXIoYWVzKHg9TWV0cmljLCB5PXZhbHVlLCBmaWxsPW1vZGVsKSwgY2luZGV4LA0KICAgICAgICAgICBzdGF0ID0gImlkZW50aXR5IiwgDQogICAgICAgICAgIHBvc2l0aW9uID0gcG9zaXRpb25fZG9kZ2UoMC44KSwgY29sb3IgPSAid2hpdGUiKSArDQogIGdlb21fdGV4dChhZXMoeD1NZXRyaWMsIHk9eSwgbGFiZWw9bGJsLCBncm91cD1tb2RlbCksIA0KICAgICAgICAgICAgY2luZGV4ICU+JSBtdXRhdGUoeSA9IHZhbHVlICsgMC4wMywgbGJsID0gcm91bmQodmFsdWUsMikpLA0KICAgICAgICAgICAgcG9zaXRpb24gPSBwb3NpdGlvbl9kb2RnZSgwLjgpLCBzdGF0ID0gImlkZW50aXR5IiwNCiAgICAgICAgICAgIHNpemUgPSAzLCBmb250ZmFjZSA9ICJib2xkIikgKw0KICBmYWNldF93cmFwKH4gc2V0KSArDQogIHNjYWxlX3lfY29udGludW91cyhleHBhbmQgPSBjKDAsMCksIGxpbWl0cyA9IGMoMCwxKSkgKw0KICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBwYWxldHRlZXJfZCgiZ2d0aGVtZXM6OlRhYmxlYXVfMjAiKVtjKDIsMSw0LDMpXSkgKw0KICBsYWJzKHg9IiIsIHk9IiIsIGZpbGw9IiIsIHRpdGxlPSJDLWluZGV4IiwgY29sb3I9IiIpICsNCiAgZ2d0aGVtZXM6OnRoZW1lX3R1ZnRlKCkgKw0KICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIsIGhqdXN0ID0gMC41KSwNCiAgICAgICAgYXhpcy50aWNrcy54ID0gZWxlbWVudF9ibGFuaygpLA0KICAgICAgICBzdHJpcC50ZXh0ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgZmFjZSA9ICJib2xkIiksDQogICAgICAgIGF4aXMubGluZSA9IGVsZW1lbnRfbGluZShzaXplID0gLjIsIGNvbG9yID0gImdyZXkyMCIpLA0KICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIsIGZhY2UgPSAiYm9sZCIpLA0KICAgICAgICBzdHJpcC5iYWNrZ3JvdW5kID0gZWxlbWVudF9yZWN0KGZpbGwgPSBOQSkpDQpgYGANCg0KYGBge3IgY2luZGV4X3Bsb3QsIGZpZy53aWR0aD04LCBmaWcuaGVpZ2h0PTMsIGV2YWw9VFJVRX0NCm1ldHJpY3MgPC0gbGlzdChDb3hQSF9iYXNlID0gcmVhZC5jc3YoIkNveF9iYXNlLmNzdiIsIHNlcD0iOyIpLA0KICAgICAgICAgICAgICAgIENveFBIID0gcmVhZC5jc3YoIkNveC5jc3YiLCBzZXA9IjsiKSwNCiAgICAgICAgICAgICAgICBSU0ZfYmFzZSA9IHJlYWQuY3N2KCJyc2ZfYmFzZS5jc3YiLCBzZXA9IjsiKSwNCiAgICAgICAgICAgICAgICBSU0YgPSByZWFkLmNzdigicnNmLmNzdiIsIHNlcD0iOyIpKQ0KDQpjaW5kZXggPC0gbGFwcGx5KG1ldHJpY3MsIGZ1bmN0aW9uKHgpIHhbMToyLF0pDQpjaW5kZXggPC0gbGFwcGx5KGNpbmRleCwgZnVuY3Rpb24oeCkge3hbLDJdIDwtIGFzLm51bWVyaWMoeFssMl0pIA0KICB4fSkNCmNpbmRleCA8LSBkby5jYWxsKGRwbHlyOjpiaW5kX3Jvd3MsIGNpbmRleCkNCg0KY2luZGV4JG1vZGVsIDwtIHJlcChjKCJDb3ggUEggYmFzZWxpbmUiLCAiQ294IFBIIHByZXYiLCAiUlNGIGJhc2VsaW5lIiwgIlJTRiBwcmV2IiksIGVhY2g9MikNCmNpbmRleCA8LSByYmluZChjaW5kZXhbLGMoMToyLDQpXSAlPiUgcmVuYW1lKHZhbHVlPVRyYWluKSAlPiUgbXV0YXRlKHNldD0iVHJhaW4iKSwgDQogICAgICAgICAgICAgICAgY2luZGV4WyxjKDEsMzo0KV0gJT4lIHJlbmFtZSh2YWx1ZT1UZXN0KSAlPiUgbXV0YXRlKHNldD0iVGVzdCIpKQ0KDQpjaW5kZXgkTWV0cmljIDwtIGdzdWIoIkhhcnJlbCIsICJIYXJyZWxsIiwgY2luZGV4JE1ldHJpYykNCg0KZ2dwbG90KCkgKw0KICBnZW9tX2JhcihhZXMoeD1NZXRyaWMsIHk9dmFsdWUsIGZpbGw9bW9kZWwpLCBjaW5kZXgsDQogICAgICAgICAgIHN0YXQgPSAiaWRlbnRpdHkiLCANCiAgICAgICAgICAgcG9zaXRpb24gPSBwb3NpdGlvbl9kb2RnZSgwLjgpLCBjb2xvciA9ICJ3aGl0ZSIpICsNCiAgZ2VvbV90ZXh0KGFlcyh4PU1ldHJpYywgeT15LCBsYWJlbD1sYmwsIGdyb3VwPW1vZGVsKSwgDQogICAgICAgICAgICBjaW5kZXggJT4lIG11dGF0ZSh5ID0gdmFsdWUgKyAwLjAzLCBsYmwgPSByb3VuZCh2YWx1ZSwyKSksDQogICAgICAgICAgICBwb3NpdGlvbiA9IHBvc2l0aW9uX2RvZGdlKDAuOCksIHN0YXQgPSAiaWRlbnRpdHkiLA0KICAgICAgICAgICAgc2l6ZSA9IDMsIGZvbnRmYWNlID0gImJvbGQiKSArDQogIGZhY2V0X3dyYXAofiBzZXQpICsNCiAgc2NhbGVfeV9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwwKSwgbGltaXRzID0gYygwLDEpKSArDQogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IHBhbGV0dGVlcl9kKCJnZ3RoZW1lczo6VGFibGVhdV8yMCIpW2MoMiwxLDQsMyldKSArDQogIGxhYnMoeD0iIiwgeT0iIiwgZmlsbD0iIiwgdGl0bGU9IkMtaW5kZXgiLCBjb2xvcj0iIikgKw0KICBnZ3RoZW1lczo6dGhlbWVfdHVmdGUoKSArDQogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiwgaGp1c3QgPSAwLjUpLA0KICAgICAgICBheGlzLnRpY2tzLnggPSBlbGVtZW50X2JsYW5rKCksDQogICAgICAgIHN0cmlwLnRleHQgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSwNCiAgICAgICAgYXhpcy5saW5lID0gZWxlbWVudF9saW5lKHNpemUgPSAuMiwgY29sb3IgPSAiZ3JleTIwIiksDQogICAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgZmFjZSA9ICJib2xkIiksDQogICAgICAgIHN0cmlwLmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoZmlsbCA9IE5BKSkNCmBgYA0KDQo8YnI+DQoNCkZvciB0aGUgaW50ZWdyYXRlZCBCcmllciBzY29yZSAoSUJTKSwgY2FsY3VsYXRlZCBmb3IgdGhlIHBlcmlvZCBmcm9tIDEgdG8gNzIwIGRheXMgdGhlIGRpc3Bvc2l0aW9uIGlzIHRoZSBzYW1lOg0KDQpgYGB7ciBpYnMsIGZpZy53aWR0aD00LjUsIGZpZy5oZWlnaHQ9M30NCmlicyA8LSB1bmxpc3QobGFwcGx5KG1ldHJpY3MsIGZ1bmN0aW9uKHgpIHhbMywzXSkpDQppYnMgPC0gdGliYmxlKG1vZGVsID0gYygiQ294IFBIIGJhc2VsaW5lIiwgIkNveCBQSCBwcmV2IiwgIlJTRiBiYXNlbGluZSIsICJSU0YgcHJldiIpLA0KICAgICAgICAgICAgICB2YWx1ZSA9IGlicykNCg0KZ2dwbG90KGlicykgKw0KICBnZW9tX2JhcihhZXMoeD1tb2RlbCwgeT12YWx1ZSwgZmlsbD1tb2RlbCksIHN0YXQgPSAiaWRlbnRpdHkiLCB3aWR0aCA9IDAuNikgKw0KICBnZW9tX3RleHQoYWVzKHg9bW9kZWwsIHk9dmFsdWUtMC4wMSwgbGFiZWw9cm91bmQodmFsdWUsMykpLA0KICAgICAgICAgICAgc2l6ZSA9IDQsIGNvbG9yID0gImdyZXkxMCIsIGZvbnRmYWNlID0gImJvbGQiKSArDQogIHNjYWxlX3lfY29udGludW91cyhleHBhbmQgPSBjKDAsMCksIGxpbWl0cyA9IGMoMCwwLjE2KSwNCiAgICAgICAgICAgICAgICAgICAgIGJyZWFrcyA9IHNlcSgwLDAuMTUsMC4wNSkpICsNCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gcGFsZXR0ZWVyX2QoImdndGhlbWVzOjpUYWJsZWF1XzIwIilbYygyLDEsNCwzKV0pICsNCiAgbGFicyh4PSIiLCB5PSIiLCB0aXRsZT0iSUJTLCB0ZXN0IHNhbXBsZSIpICsNCiAgY29vcmRfZmxpcCgpICsNCiAgZ2d0aGVtZXM6OnRoZW1lX3R1ZnRlKCkgKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIsIA0KICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIsIGhqdXN0ID0gMC41KSwNCiAgICAgICAgc3RyaXAudGV4dCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIsIGZhY2UgPSAiYm9sZCIpLA0KICAgICAgICBheGlzLmxpbmUgPSBlbGVtZW50X2xpbmUoc2l6ZSA9IC4yLCBjb2xvciA9ICJncmV5MjAiKSwNCiAgICAgICAgYXhpcy50ZXh0LnkgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBoanVzdCA9IDAsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbG9yID0gImdyZXkxMCIsIGZhY2UgPSAiYm9sZCIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1hcmdpbiA9IG1hcmdpbihsID0gMTAsIHIgPSAtMTAwKSksDQogICAgICAgIGF4aXMudGlja3MueSA9IGVsZW1lbnRfYmxhbmsoKSkNCmBgYA0KDQpgYGB7ciBpYnNfcGxvdCwgZmlnLndpZHRoPTQuNSwgZmlnLmhlaWdodD0zLCBldmFsPVRSVUV9DQppYnMgPC0gdW5saXN0KGxhcHBseShtZXRyaWNzLCBmdW5jdGlvbih4KSB4WzMsM10pKQ0KaWJzIDwtIHRpYmJsZShtb2RlbCA9IGMoIkNveCBQSCBiYXNlbGluZSIsICJDb3ggUEggcHJldiIsICJSU0YgYmFzZWxpbmUiLCAiUlNGIHByZXYiKSwNCiAgICAgICAgICAgICAgdmFsdWUgPSBpYnMpDQoNCmdncGxvdChpYnMpICsNCiAgZ2VvbV9iYXIoYWVzKHg9bW9kZWwsIHk9dmFsdWUsIGZpbGw9bW9kZWwpLCBzdGF0ID0gImlkZW50aXR5Iiwgd2lkdGggPSAwLjYpICsNCiAgZ2VvbV90ZXh0KGFlcyh4PW1vZGVsLCB5PXZhbHVlLTAuMDEsIGxhYmVsPXJvdW5kKHZhbHVlLDMpKSwNCiAgICAgICAgICAgIHNpemUgPSA0LCBjb2xvciA9ICJncmV5MTAiLCBmb250ZmFjZSA9ICJib2xkIikgKw0KICBzY2FsZV95X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLDApLCBsaW1pdHMgPSBjKDAsMC4xNiksDQogICAgICAgICAgICAgICAgICAgICBicmVha3MgPSBzZXEoMCwwLjE1LDAuMDUpKSArDQogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IHBhbGV0dGVlcl9kKCJnZ3RoZW1lczo6VGFibGVhdV8yMCIpW2MoMiwxLDQsMyldKSArDQogIGxhYnMoeD0iIiwgeT0iIiwgdGl0bGU9IklCUywgdGVzdCBzYW1wbGUiKSArDQogIGNvb3JkX2ZsaXAoKSArDQogIGdndGhlbWVzOjp0aGVtZV90dWZ0ZSgpICsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiLCANCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiLCBoanVzdCA9IDAuNSksDQogICAgICAgIHN0cmlwLnRleHQgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSwNCiAgICAgICAgYXhpcy5saW5lID0gZWxlbWVudF9saW5lKHNpemUgPSAuMiwgY29sb3IgPSAiZ3JleTIwIiksDQogICAgICAgIGF4aXMudGV4dC55ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgaGp1c3QgPSAwLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xvciA9ICJncmV5MTAiLCBmYWNlID0gImJvbGQiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtYXJnaW4gPSBtYXJnaW4obCA9IDEwLCByID0gLTEwMCkpLA0KICAgICAgICBheGlzLnRpY2tzLnkgPSBlbGVtZW50X2JsYW5rKCkpDQpgYGANCg0KPGJyPg0KDQpCdXQgaWYgd2UgdHVybiBvdXIgYXR0ZW50aW9uIHRvIHRoZSB0aW1lLWRlcGVuZGVudCBCcmllciBzY29yZSAod2UgY2FsY3VsYXRlZCBpdCBmb3IgNywgMTQsIDMwLCA2MCwgOTAsIDE4MCwgMzYwIGFuZCA3MjAgZGF5cyBhZnRlciB0aGUgc3RhcnQgb2Ygc3Vic2NyaXB0aW9uKSwgdGhlbiBhZHZhbnRhZ2VzIG9mIFJTRiBtb2RlbHMgb3ZlciBDb3ggUEggYXJlIG1vcmUgcHJvbWluZW50IGZvciBpbnRlcm1lZGlhdGUgdGltZSBwb2ludHM6DQoNCmBgYHtyIGJzX3RkLCBmaWcud2lkdGg9NywgZmlnLmhlaWdodD00fQ0KYnMgPC0gbGlzdChDb3hQSF9iYXNlID0gcmVhZC5jc3YoImNveF9iYXNlX2JzLmNzdiIsIHNlcD0iOyIpLA0KICAgICAgICAgICBDb3hQSCA9IHJlYWQuY3N2KCJjb3hfYnMuY3N2Iiwgc2VwPSI7IiksDQogICAgICAgICAgIFJTRl9iYXNlID0gcmVhZC5jc3YoInJzZl9iYXNlX2JzLmNzdiIsIHNlcD0iOyIpLA0KICAgICAgICAgICBSU0YgPSByZWFkLmNzdigicnNmX2JzLmNzdiIsIHNlcD0iOyIpKQ0KYnMgPC0gbGFwcGx5KGJzLCANCiAgICAgICAgICAgICBmdW5jdGlvbih4KSB7DQogICAgICAgICAgICAgICB4WywyXSA8LSBhcy5udW1lcmljKGdzdWIoIiwiLCIuIiwgeFssMl0pKQ0KICAgICAgICAgICAgICAgeFssM10gPC0gYXMubnVtZXJpYyhnc3ViKCIsIiwiLiIsIHhbLDNdKSkNCiAgICAgICAgICAgICAgIHh9KQ0KYnMgPC0gZG8uY2FsbChkcGx5cjo6YmluZF9yb3dzLCBicykNCg0KYnMkbW9kZWwgPC0gcmVwKGMoIkNveCBQSCBiYXNlbGluZSIsICJDb3ggUEggcHJldiIsICJSU0YgYmFzZWxpbmUiLCAiUlNGIHByZXYiKSwgZWFjaD04KQ0KYnMgPC0gcmJpbmQoYnNbLGMoMToyLDQpXSAlPiUgcmVuYW1lKHZhbHVlPVRyYWluKSAlPiUgbXV0YXRlKHNldD0iVHJhaW4iKSwgDQogICAgICAgICAgICAgICAgYnNbLGMoMSwzOjQpXSAlPiUgcmVuYW1lKHZhbHVlPVRlc3QpICU+JSBtdXRhdGUoc2V0PSJUZXN0IikpDQoNCmdncGxvdCgpICsNCiAgZ2VvbV9saW5lKGFlcyh4PVRpbWUsIHk9dmFsdWUsIGNvbG9yPW1vZGVsKSwgYnMsIHNpemU9MS4yKSArDQogIGdlb21fcG9pbnQoYWVzKHg9VGltZSwgeT12YWx1ZSwgY29sb3I9bW9kZWwpLCBicywgc2l6ZT0zKSArDQogIGZhY2V0X3JlcF93cmFwKH4gc2V0LCByZXBlYXQudGljay5sYWJlbHMgPSBUUlVFKSArDQogIHNjYWxlX3lfY29udGludW91cyhleHBhbmQgPSBjKDAsMCksIGxpbWl0cyA9IGMoMCwwLjIpKSArDQogIHNjYWxlX3hfY29udGludW91cyhicmVha3MgPSBzZXEoMCw3MjAsMTgwKSkgKw0KICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gcGFsZXR0ZWVyX2QoImdndGhlbWVzOjpUYWJsZWF1XzIwIilbYygyLDEsNCwzKV0pICsNCiAgbGFicyh4PSJUaW1lIChkYXlzKSIsIHk9IlRpbWUtZGVwZW5kZW50IEJyaWVyIHNjb3JlIiwgY29sb3I9IiIsIHRpdGxlPSJCcmllciBzY29yZSIpICsNCiAgZ2d0aGVtZXM6OnRoZW1lX3R1ZnRlKCkgKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIiwNCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiLCBoanVzdCA9IDAuNSksDQogICAgICAgIHN0cmlwLnRleHQgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSwNCiAgICAgICAgYXhpcy5saW5lID0gZWxlbWVudF9saW5lKHNpemUgPSAuMiwgY29sb3IgPSAiZ3JleTIwIiksDQogICAgICAgIHN0cmlwLmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoZmlsbCA9IE5BKSkNCmBgYA0KDQpgYGB7ciBic190ZF9wbG90LCBmaWcud2lkdGg9NywgZmlnLmhlaWdodD00LCBldmFsPVRSVUV9DQpicyA8LSBsaXN0KENveFBIX2Jhc2UgPSByZWFkLmNzdigiY294X2Jhc2VfYnMuY3N2Iiwgc2VwPSI7IiksDQogICAgICAgICAgIENveFBIID0gcmVhZC5jc3YoImNveF9icy5jc3YiLCBzZXA9IjsiKSwNCiAgICAgICAgICAgUlNGX2Jhc2UgPSByZWFkLmNzdigicnNmX2Jhc2VfYnMuY3N2Iiwgc2VwPSI7IiksDQogICAgICAgICAgIFJTRiA9IHJlYWQuY3N2KCJyc2ZfYnMuY3N2Iiwgc2VwPSI7IikpDQpicyA8LSBsYXBwbHkoYnMsIA0KICAgICAgICAgICAgIGZ1bmN0aW9uKHgpIHsNCiAgICAgICAgICAgICAgIHhbLDJdIDwtIGFzLm51bWVyaWMoZ3N1YigiLCIsIi4iLCB4WywyXSkpDQogICAgICAgICAgICAgICB4WywzXSA8LSBhcy5udW1lcmljKGdzdWIoIiwiLCIuIiwgeFssM10pKQ0KICAgICAgICAgICAgICAgeH0pDQpicyA8LSBkby5jYWxsKGRwbHlyOjpiaW5kX3Jvd3MsIGJzKQ0KDQpicyRtb2RlbCA8LSByZXAoYygiQ294IFBIIGJhc2VsaW5lIiwgIkNveCBQSCBwcmV2IiwgIlJTRiBiYXNlbGluZSIsICJSU0YgcHJldiIpLCBlYWNoPTgpDQpicyA8LSByYmluZChic1ssYygxOjIsNCldICU+JSByZW5hbWUodmFsdWU9VHJhaW4pICU+JSBtdXRhdGUoc2V0PSJUcmFpbiIpLCANCiAgICAgICAgICAgICAgICBic1ssYygxLDM6NCldICU+JSByZW5hbWUodmFsdWU9VGVzdCkgJT4lIG11dGF0ZShzZXQ9IlRlc3QiKSkNCg0KZ2dwbG90KGJzKSArDQogIGdlb21fbGluZShhZXMoeD1UaW1lLCB5PXZhbHVlLCBjb2xvcj1tb2RlbCksIHNpemU9MS4yKSArDQogIGZhY2V0X3JlcF93cmFwKH4gc2V0LCByZXBlYXQudGljay5sYWJlbHMgPSBUUlVFKSArDQogIHNjYWxlX3lfY29udGludW91cyhleHBhbmQgPSBjKDAsMCksIGxpbWl0cyA9IGMoMCwwLjIpKSArDQogIHNjYWxlX3hfY29udGludW91cyhicmVha3MgPSBzZXEoMCw3MjAsMTgwKSkgKw0KICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gcGFsZXR0ZWVyX2QoImdndGhlbWVzOjpUYWJsZWF1XzIwIilbYygyLDEsNCwzKV0pICsNCiAgbGFicyh4PSJUaW1lIChkYXlzKSIsIHk9IlRpbWUtZGVwZW5kZW50IEJyaWVyIHNjb3JlIiwgY29sb3I9IiIsIHRpdGxlPSJCcmllciBzY29yZSIpICsNCiAgZ2d0aGVtZXM6OnRoZW1lX3R1ZnRlKCkgKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIiwNCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiLCBoanVzdCA9IDAuNSksDQogICAgICAgIHN0cmlwLnRleHQgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSwNCiAgICAgICAgYXhpcy5saW5lID0gZWxlbWVudF9saW5lKHNpemUgPSAuMiwgY29sb3IgPSAiZ3JleTIwIiksDQogICAgICAgIHN0cmlwLmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoZmlsbCA9IE5BKSkNCmBgYA0KDQo8YnI+DQoNCldlIHNhbXBsZWQgMyBzdWJzY3JpcHRpb25zIG91dCBvZiB0aGUgdGVzdCBzYW1wbGUgZnJvbSBib3RoIHRoZSBncm91cCBvZiBzdWJzY3JpcHRpb25zIGFjdHVhbGx5IGVuZGVkIGJ5IGNodXJuIGFuZCBjZW5zb3JlZCwgYW5kIHByZWRpY3Qgc3Vydml2YWwgZnVuY3Rpb25zIGZvciB0aGVtIHVzaW5nIGFsbCB0aGUgNCBtb2RlbHMgYWJvdmU6DQoNCmBgYHtyIHN1cnZfY3VydmVzX3ByZWQsIGZpZy53aWR0aD0xMCwgZmlnLmhlaWdodD02fQ0KcDEgPC0gZ2dkcmF3KCkgKyBkcmF3X2ltYWdlKCJjb3hfYmFzZV9zdXJ2LnBuZyIsIHNjYWxlID0gMSkNCnAyIDwtIGdnZHJhdygpICsgZHJhd19pbWFnZSgiY294X3N1cnYucG5nIiwgc2NhbGUgPSAxKQ0KcDMgPC0gZ2dkcmF3KCkgKyBkcmF3X2ltYWdlKCJyc2Zfc3Vydi5wbmciLCBzY2FsZSA9IDEpDQpwNCA8LSBnZ2RyYXcoKSArIGRyYXdfaW1hZ2UoInJzZl9zdXJ2LnBuZyIsIHNjYWxlID0gMSkNCmdnYXJyYW5nZShwMSxwMixwMyxwNCxucm93ID0gMiwgbmNvbCA9IDIpDQpgYGANCg0KQWdhaW4sIHRoZXJlIGFyZSBtb2RlIGRpZmZlcmVuY2VzIGluIHByZWRpY3Rpb25zIG9idGFpbmVkIGZyb20gdHdvIENveCBtb2RlbHMsIHRoYW4gYmV0d2VlbiB0d28gUlNGIG1vZGVscy4gQWJvdmUgYWxsLCBpdCBjb3VsZCBiZSBiZWNhdXNlIG9mIHZpb2xhdGlvbiBvZiB0aGUgUEggYXNzdW1wdGlvbi4NCg0KPGJyPg0KDQpGb3IgQ294IHJlZ3Jlc3Npb24gdmFyaWFibGUgaW1wb3J0YW5jZSBjYW4gYmUgZXN0aW1hdGVkIHVzaW5nIHRoZSBwcm9wb3J0aW9uIG9mIGxvZy1saWtlbGlob29kIHRoYXQgaXMgZXhwbGFpbmVkIGJ5IGVhY2ggdmFyaWFibGUgKGZvciBjYXRlZ29yaWNhbCB2YXJpYWJsZXMgaXQgY29tcHJpc2VzIGFsbCB0aGVpciBjYXRlZ29yaWVzIGludG8gc28gY2FsbGVkIGdyb3VwZWQgdmFyaWFibGUpLiBUaGUgcGxvdCBiZWxvdyBkaXNwbGF5cyB0aGUgV2FsZCAkXGNoaV4yJCBzdGF0aXN0aWMgbWludXMgaXRzIGRlZ3JlZXMgb2YgZnJlZWRvbSBmb3IgYXNzZXNzaW5nIHRoZSBwYXJ0aWFsIGVmZmVjdCBvZiBlYWNoIHZhcmlhYmxlLiBBY2NvcmRpbmcgdG8gQGhhcnJlbGxfcm1zXzIwMjEsIGV2ZW4gdGhvdWdoIHRoaXMgaXMgbm90IGEgc2NhbGVkIFswLDFdIHN0YXRpc3RpYywgaXQgaXMgcHJvYmFibHkgdGhlIGJlc3QgbWV0aG9kIGluIGdlbmVyYWwgYmVjYXVzZSBpdCBwZW5hbGl6ZXMgYSB2YXJpYWJsZSByZXF1aXJpbmcgYSBsYXJnZSBudW1iZXIgb2YgcGFyYW1ldGVycyB0byBhY2hpZXZlIHRoZSAkXGNoaV4yJC4NCg0KYGBge3IgY294X3ZhcmltcCwgZmlnLndpZHRoPTEwLCBmaWcuaGVpZ2h0PTR9DQpkZl9mZWF0IDwtIHRyYW5zX3BlcmlvZHMgJT4lDQogIHRyYW5zbXV0ZShzdWJzY3JfaWQsIGNodXJuLCB0aW1lLA0KICAgICAgICAgICAgY2h1cm5lZF9iZWZvcmVfbnVtLCANCiAgICAgICAgICAgIGNpdHlHID0gZmN0X2x1bXBfbWluKGNpdHksIDMwLCBvdGhlcl9sZXZlbCA9ICIxOS0yMCIpLCANCiAgICAgICAgICAgIGJkRywgZ2VuZGVyLCByZWdpc3RlcmVkX3ZpYSwgbW9udGhfcGVyaW9kLA0KICAgICAgICAgICAgdGltZV9mcm9tX3JlZywgdGltZV9mcm9tX3ByZXZfZXhwLCANCiAgICAgICAgICAgIGZpcnN0X3BheW1lbnRfbWV0aG9kX2lkLCBmaXJzdF9wbGFuX2xpc3RfcHJpY2UsDQogICAgICAgICAgICBwcmV2X3RpbWUsIHByZXZfcGN0X2FjdGl2ZSA9IDEwMCpwcmV2X3BjdF9hY3RpdmUsIA0KICAgICAgICAgICAgcHJldl9tZWFuX251bV90b3QsIHByZXZfbWVhbl90b3RhbF9zZWNzX2ltcCwNCiAgICAgICAgICAgIHByZXZfbWVhbl9udW1fdW5xLCBwcmV2X21lYW5fYXZfcGN0X3BsYXllZF9hY3QsDQogICAgICAgICAgICBwcmV2X21lYW5fYWN0dWFsX2Ftb3VudF9wYWlkLCBwcmV2X251bV9jYW5jZWwsDQogICAgICAgICAgICBwcmV2X21vZGVfcGF5bWVudF9pZCkNCg0KZGZfZmVhdF9iYXNlIDwtIGRmX2ZlYXQgJT4lIHNlbGVjdCgtY29udGFpbnMoInByZXZfIikpDQoNCmZlYXRfYmFzZSA8LSBuYW1lcyhkZl9mZWF0X2Jhc2UpWy1jKDE6MyldDQpmZWF0X3ByZXYgPC0gbmFtZXMoZGZfZmVhdClbLWMoMTozKV0NCg0KdHJhaW5fc2V0IDwtIHJlYWQuY3N2KCJYX3RyYWluX2luZGV4LmNzdiIpDQpzYW1wbGVfWF90ZXN0X2lkIDwtIHJlYWQuY3N2KCJzYW1wbGVfWF90ZXN0X2lkLmNzdiIpDQoNCmRmX2Jhc2VfdHIgPC0gZGZfZmVhdF9iYXNlICU+JQ0KICBmaWx0ZXIoc3Vic2NyX2lkICVpbiUgdHJhbnNfcGVyaW9kcyRzdWJzY3JfaWRbdHJhaW5fc2V0JGluZCArIDFdKQ0KDQpkZl9iYXNlX3RzdCA8LSBkZl9mZWF0X2Jhc2UgJT4lDQogIGZpbHRlcighIHN1YnNjcl9pZCAlaW4lIHRyYW5zX3BlcmlvZHMkc3Vic2NyX2lkW3RyYWluX3NldCRpbmQgKyAxXSkNCg0KZGZfdHIgPC0gZGZfZmVhdCAlPiUNCiAgZmlsdGVyKHN1YnNjcl9pZCAlaW4lIHRyYW5zX3BlcmlvZHMkc3Vic2NyX2lkW3RyYWluX3NldCRpbmQgKyAxXSkNCg0KZGZfdHN0IDwtIGRmX2ZlYXQgJT4lDQogIGZpbHRlcighIHN1YnNjcl9pZCAlaW4lIHRyYW5zX3BlcmlvZHMkc3Vic2NyX2lkW3RyYWluX3NldCRpbmQgKyAxXSkNCg0Kc2FtcGxlX1hfc3Vic2NyX2lkIDwtIHRyYW5zX3BlcmlvZHMkc3Vic2NyX2lkW3NhbXBsZV9YX3Rlc3RfaWQkSUQgKyAxXQ0KDQpzYW1wX2Jhc2VfdGVzdCA8LSBkZl9mZWF0X2Jhc2UgJT4lDQogIGZpbHRlcihzdWJzY3JfaWQgJWluJSB0cmFuc19wZXJpb2RzJHN1YnNjcl9pZFtzYW1wbGVfWF90ZXN0X2lkJElEICsgMV0pDQpzYW1wX3Rlc3QgPC0gZGZfZmVhdCAlPiUNCiAgZmlsdGVyKHN1YnNjcl9pZCAlaW4lIHRyYW5zX3BlcmlvZHMkc3Vic2NyX2lkW3NhbXBsZV9YX3Rlc3RfaWQkSUQgKyAxXSkNCg0KZm1sX2Jhc2UgPSBhcy5mb3JtdWxhKHBhc3RlMCgiU3Vydih0aW1lID0gdGltZSwgZXZlbnQgPSBjaHVybikgfiAiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwYXN0ZShmZWF0X2Jhc2UsIGNvbGxhcHNlID0gIiArICIpKSkNCmZtbF9wcmV2ID0gYXMuZm9ybXVsYShwYXN0ZTAoIlN1cnYodGltZSA9IHRpbWUsIGV2ZW50ID0gY2h1cm4pIH4gIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGFzdGUoZmVhdF9wcmV2LCBjb2xsYXBzZSA9ICIgKyAiKSkpDQoNCmNveF9iYXNlX3JtcyA8LSBjcGgoZm1sX2Jhc2UsIGRmX2Jhc2VfdHIsIHg9VFJVRSwgeT1UUlVFKQ0KY294X2Jhc2UgPC0gY294cGgoZm1sX2Jhc2UsIGRmX2Jhc2VfdHIpDQpjb3hfYmFzZV9hbm92YV9ybXMgPC0gYW5vdmEoY294X2Jhc2Vfcm1zKQ0KcG5nKCJjb3hfYmFzZV9pbXAucG5nIiwgd2lkdGggPSA1MDAsIGhlaWdodCA9IDQwMCkNCnBsb3QoY294X2Jhc2VfYW5vdmFfcm1zLCBzb3J0ID0gImFzY2VuZGluZyIpDQpkZXYub2ZmKCkNCg0KY294X3JtcyA8LSBjcGgoZm1sX3ByZXYsIGRmX3RyLCB4PVRSVUUsIHk9VFJVRSkNCmNveCA8LSBjb3hwaChmbWxfcHJldiwgZGZfdHIpDQpjb3hfYW5vdmFfcm1zIDwtIGFub3ZhKGNveF9ybXMpDQpwbmcoImNveF9pbXAucG5nIiwgd2lkdGggPSA1MDAsIGhlaWdodCA9IDQwMCkNCnBsb3QoY294X2Fub3ZhX3Jtcywgc29ydCA9ICJhc2NlbmRpbmciKQ0KZGV2Lm9mZigpDQoNCnAxIDwtIGdnZHJhdygpICsgZHJhd19pbWFnZSgiY294X2Jhc2VfaW1wLnBuZyIsIHNjYWxlID0gMSkNCnAyIDwtIGdnZHJhdygpICsgZHJhd19pbWFnZSgiY294X2ltcC5wbmciLCBzY2FsZSA9IDEpDQpnZ2FycmFuZ2UocDEscDIsbnJvdyA9IDEsIG5jb2wgPSAyLCANCiAgICAgICAgICBsYWJlbHMgPSBjKCJDb3ggUEggYmFzZWxpbmUsIHZhcmlhYmxlIGltcG9ydGFuY2UiLA0KICAgICAgICAgICAgICAgICAgICAgIkNveCBQSCBwcmV2LCB2YXJpYWJsZSBpbXBvcnRhbmNlIiksIGhqdXN0ID0gLTAuMjUpDQpgYGANCg0KYGBge3IgY294X3ZhcmltcF9wbG90LCBmaWcud2lkdGg9MTAsIGZpZy5oZWlnaHQ9NCwgZXZhbD1UUlVFfQ0KZGZfZmVhdCA8LSB0cmFuc19wZXJpb2RzICU+JQ0KICB0cmFuc211dGUoc3Vic2NyX2lkLCBjaHVybiwgdGltZSwNCiAgICAgICAgICAgIGNodXJuZWRfYmVmb3JlX251bSwgDQogICAgICAgICAgICBjaXR5RyA9IGZjdF9sdW1wX21pbihjaXR5LCAzMCwgb3RoZXJfbGV2ZWwgPSAiMTktMjAiKSwgDQogICAgICAgICAgICBiZEcsIGdlbmRlciwgcmVnaXN0ZXJlZF92aWEsIG1vbnRoX3BlcmlvZCwNCiAgICAgICAgICAgIHRpbWVfZnJvbV9yZWcsIHRpbWVfZnJvbV9wcmV2X2V4cCwgDQogICAgICAgICAgICBmaXJzdF9wYXltZW50X21ldGhvZF9pZCwgZmlyc3RfcGxhbl9saXN0X3ByaWNlLA0KICAgICAgICAgICAgcHJldl90aW1lLCBwcmV2X3BjdF9hY3RpdmUgPSAxMDAqcHJldl9wY3RfYWN0aXZlLCANCiAgICAgICAgICAgIHByZXZfbWVhbl9udW1fdG90LCBwcmV2X21lYW5fdG90YWxfc2Vjc19pbXAsDQogICAgICAgICAgICBwcmV2X21lYW5fbnVtX3VucSwgcHJldl9tZWFuX2F2X3BjdF9wbGF5ZWRfYWN0LA0KICAgICAgICAgICAgcHJldl9tZWFuX2FjdHVhbF9hbW91bnRfcGFpZCwgcHJldl9udW1fY2FuY2VsLA0KICAgICAgICAgICAgcHJldl9tb2RlX3BheW1lbnRfaWQpDQoNCmRmX2ZlYXRfYmFzZSA8LSBkZl9mZWF0ICU+JSBzZWxlY3QoLWNvbnRhaW5zKCJwcmV2XyIpKQ0KDQpmZWF0X2Jhc2UgPC0gbmFtZXMoZGZfZmVhdF9iYXNlKVstYygxOjMpXQ0KZmVhdF9wcmV2IDwtIG5hbWVzKGRmX2ZlYXQpWy1jKDE6MyldDQoNCnRyYWluX3NldCA8LSByZWFkLmNzdigiWF90cmFpbl9pbmRleC5jc3YiKQ0Kc2FtcGxlX1hfdGVzdF9pZCA8LSByZWFkLmNzdigic2FtcGxlX1hfdGVzdF9pZC5jc3YiKQ0KDQpkZl9iYXNlX3RyIDwtIGRmX2ZlYXRfYmFzZSAlPiUNCiAgZmlsdGVyKHN1YnNjcl9pZCAlaW4lIHRyYW5zX3BlcmlvZHMkc3Vic2NyX2lkW3RyYWluX3NldCRpbmQgKyAxXSkNCg0KZGZfYmFzZV90c3QgPC0gZGZfZmVhdF9iYXNlICU+JQ0KICBmaWx0ZXIoISBzdWJzY3JfaWQgJWluJSB0cmFuc19wZXJpb2RzJHN1YnNjcl9pZFt0cmFpbl9zZXQkaW5kICsgMV0pDQoNCmRmX3RyIDwtIGRmX2ZlYXQgJT4lDQogIGZpbHRlcihzdWJzY3JfaWQgJWluJSB0cmFuc19wZXJpb2RzJHN1YnNjcl9pZFt0cmFpbl9zZXQkaW5kICsgMV0pDQoNCmRmX3RzdCA8LSBkZl9mZWF0ICU+JQ0KICBmaWx0ZXIoISBzdWJzY3JfaWQgJWluJSB0cmFuc19wZXJpb2RzJHN1YnNjcl9pZFt0cmFpbl9zZXQkaW5kICsgMV0pDQoNCnNhbXBsZV9YX3N1YnNjcl9pZCA8LSB0cmFuc19wZXJpb2RzJHN1YnNjcl9pZFtzYW1wbGVfWF90ZXN0X2lkJElEICsgMV0NCg0Kc2FtcF9iYXNlX3Rlc3QgPC0gZGZfZmVhdF9iYXNlICU+JQ0KICBmaWx0ZXIoc3Vic2NyX2lkICVpbiUgdHJhbnNfcGVyaW9kcyRzdWJzY3JfaWRbc2FtcGxlX1hfdGVzdF9pZCRJRCArIDFdKQ0Kc2FtcF90ZXN0IDwtIGRmX2ZlYXQgJT4lDQogIGZpbHRlcihzdWJzY3JfaWQgJWluJSB0cmFuc19wZXJpb2RzJHN1YnNjcl9pZFtzYW1wbGVfWF90ZXN0X2lkJElEICsgMV0pDQoNCmZtbF9iYXNlID0gYXMuZm9ybXVsYShwYXN0ZTAoIlN1cnYodGltZSA9IHRpbWUsIGV2ZW50ID0gY2h1cm4pIH4gIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGFzdGUoZmVhdF9iYXNlLCBjb2xsYXBzZSA9ICIgKyAiKSkpDQpmbWxfcHJldiA9IGFzLmZvcm11bGEocGFzdGUwKCJTdXJ2KHRpbWUgPSB0aW1lLCBldmVudCA9IGNodXJuKSB+ICIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBhc3RlKGZlYXRfcHJldiwgY29sbGFwc2UgPSAiICsgIikpKQ0KDQpjb3hfYmFzZV9ybXMgPC0gY3BoKGZtbF9iYXNlLCBkZl9iYXNlX3RyLCB4PVRSVUUsIHk9VFJVRSkNCmNveF9iYXNlIDwtIGNveHBoKGZtbF9iYXNlLCBkZl9iYXNlX3RyKQ0KY294X2Jhc2VfYW5vdmFfcm1zIDwtIGFub3ZhKGNveF9iYXNlX3JtcykNCnBuZygiY294X2Jhc2VfaW1wLnBuZyIsIHdpZHRoID0gNTAwLCBoZWlnaHQgPSA0MDApDQpwbG90KGNveF9iYXNlX2Fub3ZhX3Jtcywgc29ydCA9ICJhc2NlbmRpbmciKQ0KZGV2Lm9mZigpDQoNCmNveF9ybXMgPC0gY3BoKGZtbF9wcmV2LCBkZl90ciwgeD1UUlVFLCB5PVRSVUUpDQpjb3ggPC0gY294cGgoZm1sX3ByZXYsIGRmX3RyKQ0KY294X2Fub3ZhX3JtcyA8LSBhbm92YShjb3hfcm1zKQ0KcG5nKCJjb3hfaW1wLnBuZyIsIHdpZHRoID0gNTAwLCBoZWlnaHQgPSA0MDApDQpwbG90KGNveF9hbm92YV9ybXMsIHNvcnQgPSAiYXNjZW5kaW5nIikNCmRldi5vZmYoKQ0KDQpwMSA8LSBnZ2RyYXcoKSArIGRyYXdfaW1hZ2UoImNveF9iYXNlX2ltcC5wbmciLCBzY2FsZSA9IDEpDQpwMiA8LSBnZ2RyYXcoKSArIGRyYXdfaW1hZ2UoImNveF9pbXAucG5nIiwgc2NhbGUgPSAxKQ0KZ2dhcnJhbmdlKHAxLHAyLG5yb3cgPSAxLCBuY29sID0gMiwgDQogICAgICAgICAgbGFiZWxzID0gYygiQ294IFBIIGJhc2VsaW5lLCB2YXJpYWJsZSBpbXBvcnRhbmNlIiwNCiAgICAgICAgICAgICAgICAgICAgICJDb3ggUEggcHJldiwgdmFyaWFibGUgaW1wb3J0YW5jZSIpLCBoanVzdCA9IC0wLjI1KQ0KYGBgDQoNCjxicj4NCg0KRm9yIFJTRiBtb2RlbHMgd2UgY2FsY3VsYXRlZCB3ZWlnaHRzIGZvciB2YXJpYWJsZSBpbXBvcnRhbmNlIHVzaW5nIDEwIHBlcm11dGF0aW9ucyAtIHRoZXNlIHdlaWdodHMgc2hvdyB0aGUgdmFsdWUgYnkgd2hpY2ggdGhlIGNvbmNvcmRhbmNlIGluZGV4IG9uIHRoZSB0ZXN0IGRhdGEgd2lsbCBkcm9wLCBvbiBhdmVyYWdlLCBpZiB0aGUgcmVsYXRpb25zaGlwIG9mIHRoZSBhY2NvcmRpbmcgZmVhdHVyZSB0byBzdXJ2aXZhbCB0aW1lIGlzIHJlbW92ZWQgKGJ5IHJhbmRvbSBzaHVmZmxpbmcpLg0KDQpgYGB7ciB2YXJpbXBfcnNmLCBmaWcud2lkdGg9MTAsIGZpZy5oZWlnaHQ9MywgZXZhbD1UUlVFfQ0KcDEgPC0gZ2dkcmF3KCkgKyBkcmF3X2ltYWdlKCJyc2ZfYmFzZV9pbXAucG5nIiwgc2NhbGUgPSAxKQ0KcDIgPC0gZ2dkcmF3KCkgKyBkcmF3X2ltYWdlKCJyc2ZfaW1wLnBuZyIsIHNjYWxlID0gMSkNCmdnYXJyYW5nZShwMSxwMixucm93ID0gMSwgbmNvbCA9IDIpDQpgYGANCg0KPGJyPg0KDQpZb3UgY2FuIHNlZSB0aGF0IGFsbCBtb2RlbHMgY29uc2lkZXIgdGhlIGZpcnN0IHBheW1lbnQgbWV0aG9kIHVzZWQgYXMgdGhlIG1vc3QgaW1wb3J0YW50IGZlYXR1cmUsIHRoZSBmaXJzdCBwbGFuIGxpc3QgcHJpY2UgaXMgdGhlIHNlY29uZCBieSBpbXBvcnRhbmNlIGluIFJTRiBtb2RlbHMsIGFuZCB0aGUgdGhpcmQgb25lIGluIENveCBQSCBtb2RlbHMuIE1vbnRoIG9mIHRoZSBzdWJzY3JpcHRpb24gb3JpZ2luIGlzIG1vcmUgaW1wb3J0YW50IGluIGJhc2VsaW5lIHNwZWNpZmljYXRpb25zIGNvbXBhcmVkIHRvIGV4dGVuZGVkIHdpdGggY2hhcmFjdGVyaXN0aWNzIG9mIHRoZSBwcmV2aW91cyBzdWJzY3JpcHRpb25zLCBhbmQgaW4gQ294IHJlZ3Jlc3Npb25zIGNvbXBhcmVkIHRvIFJTRi4gVGhlIG1vc3QgY29uc2lkZXJhYmxlIGRpZmZlcmVuY2UgaW4gcmFuZ2luZyBmZWF0dXJlcyBiZXR3ZWVuIENveCBQSCBhbmQgUlNGIG1vZGVscyBpcyBvYnNlcnZlZCBmb3IgdGhlIG1vc3QgZnJlcXVlbnQgcGF5bWVudCBtZXRob2QgdXNlZCBpbiBwcmV2aW91cyBzdWJzY3JpcHRpb25zOiBpdCBob2xkcyB0aGUgc2Vjb25kIHBsYWNlIGluIENveCByZWdyZXNzaW9uIGFuZCBvbmUgb2YgdGhlIGxhc3RzIGluIFJTRi4gQW1vbmcgb3RoZXIgaGlzdG9yaWNhbCBmZWF0dXJlcyBhdmVyYWdlIHBlcmNlbnQgb2Ygb25lIHNvbmcnIGxlbmd0aCBwbGF5ZWQgbWF0dGVycyB0aGUgbW9zdC4NCg0KPGJyPg0KDQpUbyByZXZlYWwgaG93IHN1cnZpdmFsIHByb2JhYmlsaXR5IGNoYW5nZXMgd2l0aCBkaWZmZXJlbnQgdmFsdWVzIG9mIHRoZSBtb3N0IGltcG9ydGFudCAoYW5kIG90aGVyKSB2YXJpYWJsZXMgd2UgY2FuIHByZWRpY3QgYW5kIHBsb3Qgc3Vydml2YWwgY3VydmVzIGZvciB0aGVtIGluIHBhcnRpY3VsYXIsIHRha2luZyBhbGwgb3RoZXIgZmVhdHVyZXMgZXF1YWwuIEluIGFkZGl0aW9uLCBmb3IgQ294IHJlZ3Jlc3Npb24gd2UgY2FuIGludGVycHJldCBleHBvbmVudGlhdGVkIGNvZWZmaWNpZW50cyBhcyBoYXphcmQgcmF0aW9zOiBpZiBoYXphcmQgcmF0aW8gPiAxICh0aGUgY29ycmVzcG9uZGluZyBjb2VmZmljaWVudCBpcyBuZWdhdGl2ZSksIHRoZW4gdGhlIHRoaXMgZmVhdHVyZSBwb3NpdGl2ZWx5IGNvcnJlbGF0ZWQgd2l0aCBjaHVybiBoYXphcmQsIG90aGVyd2lzZSB0aGUgY29ycmVsYXRpb24gaXMgbmVnYXRpdmUuIFdlIHdvbid0IHBsb3Qgc3VjaCBzdXJ2aXZhbCBjdXJ2ZXMgaGVyZSAtIGp1c3QgZGlzcGxheSBwbG90cyBmb3IgaGF6YXJkIHJhdGlvcy4gQWxsIGhhemFyZCByYXRpb3MgZm9yIGNhdGVnb3JpY2FsIHZhcmlhYmxlcyBzaG91bGQgYmUgaW50ZXJwcmV0ZWQgaW4gY29tcGFyaXNvbiB3aXRoIGJhc2VsaW5lIGNhdGVnb3J5IG9mIHRoZSBjb3JyZXNwb25kaW5nIHZhcmlhYmxlLg0KDQpgYGB7ciBjb3hfaHIsIGZpZy53aWR0aD0xMCwgZmlnLmhlaWdodD0xMH0NCmZlYXR1cmVfZ3JvdXAgPC0gZnVuY3Rpb24oeCwgZmVhdCkgew0KICBmb3IgKGkgaW4gMTpsZW5ndGgoeCkpIHsNCiAgICBmb3IgKGogaW4gZmVhdCkgew0KICAgICAgaWYgKGdyZXBsKGosIHhbaV0pKSB7DQogICAgICAgIHhbaV0gPC0gag0KICAgICAgfQ0KICAgIH0NCiAgfQ0KICB4DQp9DQoNCmNveF9iYXNlX2NvZWYgPC0gc3VtbWFyeShjb3hfYmFzZSkNCmNveF9iYXNlX2NvZWYgPC0gdGliYmxlKFZhcmlhYmxlID0gcm93Lm5hbWVzKGNveF9iYXNlX2NvZWYkY29lZmZpY2llbnRzKSwNCiAgICAgICAgICAgICAgICAgICAgICAgIGdyb3VwID0gZmVhdHVyZV9ncm91cChWYXJpYWJsZSwgZmVhdF9iYXNlKSwNCiAgICAgICAgICAgICAgICAgICAgICAgIEhSID0gY294X2Jhc2VfY29lZiRjb2VmZmljaWVudHNbLDJdLA0KICAgICAgICAgICAgICAgICAgICAgICAgc2UgPSBjb3hfYmFzZV9jb2VmJGNvZWZmaWNpZW50c1ssM10pICU+JQ0KICBhcnJhbmdlKEhSLCBncm91cCkNCg0KcDEgPC0gZ2dwbG90KGNveF9iYXNlX2NvZWYsIGFlcyh4PVZhcmlhYmxlLCB5PUhSLCBjb2xvcj1ncm91cCkpICsgDQogIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDEpICsNCiAgZ2VvbV9wb2ludHJhbmdlKGFlcyh5bWluPUhSKmV4cCgtMS45NipzZSksIHltYXg9SFIqZXhwKDEuOTYqc2UpKSkgKw0KICBzY2FsZV94X2Rpc2NyZXRlKGxpbWl0cyA9IGNveF9iYXNlX2NvZWYkVmFyaWFibGUpICsNCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IHJldihwYWxldHRlZXJfZCgiZ2d0aGVtZXM6OlRhYmxlYXVfMTAiKVsxOjldKSkgKw0KICBjb29yZF9mbGlwKCkgKw0KICBsYWJzKHkgPSAiSGF6YXJkIFJhdGlvIFs5NSUgQ0ldIiwgeCA9ICIiLCB0aXRsZSA9ICJDb3ggUEggYmFzZWxpbmUsIGhhemFyZCByYXRpb3MiKSArDQogIGdndGhlbWVzOjp0aGVtZV90dWZ0ZSgpICsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiLA0KICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIsIGhqdXN0ID0gMC41KSwNCiAgICAgICAgYXhpcy5saW5lID0gZWxlbWVudF9saW5lKHNpemUgPSAuMiwgY29sb3IgPSAiZ3JleTIwIiksDQogICAgICAgIGF4aXMudGlja3MueSA9IGVsZW1lbnRfYmxhbmsoKSwNCiAgICAgICAgcGFuZWwuZ3JpZC5tYWpvci54ID0gZWxlbWVudF9saW5lKHNpemU9LjIsIGNvbG9yPSJncmV5OTAiKSwNCiAgICAgICAgcGFuZWwuZ3JpZC5taW5vci54ID0gZWxlbWVudF9saW5lKHNpemU9LjIsIGNvbG9yPSJncmV5OTAiKSwNCiAgICAgICAgcGFuZWwuZ3JpZC5tYWpvci55ID0gZWxlbWVudF9saW5lKHNpemU9LjIsIGNvbG9yPSJncmV5OTAiLCBsaW5ldHlwZT0iZG90dGVkIikpDQoNCmNveF9jb2VmIDwtIHN1bW1hcnkoY294KQ0KY294X2NvZWYgPC0gdGliYmxlKFZhcmlhYmxlID0gcm93Lm5hbWVzKGNveF9jb2VmJGNvZWZmaWNpZW50cyksDQogICAgICAgICAgICAgICAgICAgICAgICBncm91cCA9IGZlYXR1cmVfZ3JvdXAoVmFyaWFibGUsIGZlYXRfcHJldiksDQogICAgICAgICAgICAgICAgICAgICAgICBIUiA9IGNveF9jb2VmJGNvZWZmaWNpZW50c1ssMl0sDQogICAgICAgICAgICAgICAgICAgICAgICBzZSA9IGNveF9jb2VmJGNvZWZmaWNpZW50c1ssM10pICU+JQ0KICBhcnJhbmdlKEhSLCBncm91cCkNCg0KcDIgPC0gZ2dwbG90KGNveF9jb2VmLCBhZXMoeD1WYXJpYWJsZSwgeT1IUiwgY29sb3I9Z3JvdXApKSArIA0KICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAxKSArDQogIGdlb21fcG9pbnRyYW5nZShhZXMoeW1pbj1IUipleHAoLTEuOTYqc2UpLCB5bWF4PUhSKmV4cCgxLjk2KnNlKSkpICsNCiAgc2NhbGVfeF9kaXNjcmV0ZShsaW1pdHMgPSBjb3hfY29lZiRWYXJpYWJsZSkgKw0KICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gcGFsZXR0ZWVyX2QoImdndGhlbWVzOjpDbGFzc2ljXzIwIilbMToxOV0pICsNCiAgY29vcmRfZmxpcCgpICsNCiAgbGFicyh5ID0gIkhhemFyZCBSYXRpbyBbOTUlIENJXSIsIHggPSAiIiwgdGl0bGUgPSAiQ294IFBIIHByZXYsIGhhemFyZCByYXRpb3MiKSArDQogIGdndGhlbWVzOjp0aGVtZV90dWZ0ZSgpICsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiLA0KICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIsIGhqdXN0ID0gMC41KSwNCiAgICAgICAgYXhpcy5saW5lID0gZWxlbWVudF9saW5lKHNpemUgPSAuMiwgY29sb3IgPSAiZ3JleTIwIiksDQogICAgICAgIGF4aXMudGlja3MueSA9IGVsZW1lbnRfYmxhbmsoKSwNCiAgICAgICAgcGFuZWwuZ3JpZC5tYWpvci54ID0gZWxlbWVudF9saW5lKHNpemU9LjIsIGNvbG9yPSJncmV5OTAiKSwNCiAgICAgICAgcGFuZWwuZ3JpZC5taW5vci54ID0gZWxlbWVudF9saW5lKHNpemU9LjIsIGNvbG9yPSJncmV5OTAiKSwNCiAgICAgICAgcGFuZWwuZ3JpZC5tYWpvci55ID0gZWxlbWVudF9saW5lKHNpemU9LjIsIGNvbG9yPSJncmV5OTAiLCBsaW5ldHlwZT0iZG90dGVkIikpDQoNCmdnYXJyYW5nZShwMSxwMixucm93ID0gMSxuY29sID0gMikNCmBgYA0KDQpgYGB7ciBjb3hfaHJfcGxvdCwgZmlnLndpZHRoPTEwLCBmaWcuaGVpZ2h0PTEwLCBldmFsPVRSVUV9DQpmZWF0dXJlX2dyb3VwIDwtIGZ1bmN0aW9uKHgsIGZlYXQpIHsNCiAgZm9yIChpIGluIDE6bGVuZ3RoKHgpKSB7DQogICAgZm9yIChqIGluIGZlYXQpIHsNCiAgICAgIGlmIChncmVwbChqLCB4W2ldKSkgew0KICAgICAgICB4W2ldIDwtIGoNCiAgICAgIH0NCiAgICB9DQogIH0NCiAgeA0KfQ0KDQpjb3hfYmFzZV9jb2VmIDwtIHN1bW1hcnkoY294X2Jhc2UpDQpjb3hfYmFzZV9jb2VmIDwtIHRpYmJsZShWYXJpYWJsZSA9IHJvdy5uYW1lcyhjb3hfYmFzZV9jb2VmJGNvZWZmaWNpZW50cyksDQogICAgICAgICAgICAgICAgICAgICAgICBncm91cCA9IGZlYXR1cmVfZ3JvdXAoVmFyaWFibGUsIGZlYXRfYmFzZSksDQogICAgICAgICAgICAgICAgICAgICAgICBIUiA9IGNveF9iYXNlX2NvZWYkY29lZmZpY2llbnRzWywyXSwNCiAgICAgICAgICAgICAgICAgICAgICAgIHNlID0gY294X2Jhc2VfY29lZiRjb2VmZmljaWVudHNbLDNdKSAlPiUNCiAgYXJyYW5nZShIUiwgZ3JvdXApDQoNCnAxIDwtIGdncGxvdChjb3hfYmFzZV9jb2VmLCBhZXMoeD1WYXJpYWJsZSwgeT1IUiwgY29sb3I9Z3JvdXApKSArIA0KICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAxKSArDQogIGdlb21fcG9pbnRyYW5nZShhZXMoeW1pbj1IUipleHAoLTEuOTYqc2UpLCB5bWF4PUhSKmV4cCgxLjk2KnNlKSkpICsNCiAgc2NhbGVfeF9kaXNjcmV0ZShsaW1pdHMgPSBjb3hfYmFzZV9jb2VmJFZhcmlhYmxlKSArDQogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSByZXYocGFsZXR0ZWVyX2QoImdndGhlbWVzOjpUYWJsZWF1XzEwIilbMTo5XSkpICsNCiAgY29vcmRfZmxpcCgpICsNCiAgbGFicyh5ID0gIkhhemFyZCBSYXRpbyBbOTUlIENJXSIsIHggPSAiIiwgdGl0bGUgPSAiQ294IFBIIGJhc2VsaW5lLCBoYXphcmQgcmF0aW9zIikgKw0KICBnZ3RoZW1lczo6dGhlbWVfdHVmdGUoKSArDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIiwNCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiLCBoanVzdCA9IDAuNSksDQogICAgICAgIGF4aXMubGluZSA9IGVsZW1lbnRfbGluZShzaXplID0gLjIsIGNvbG9yID0gImdyZXkyMCIpLA0KICAgICAgICBheGlzLnRpY2tzLnkgPSBlbGVtZW50X2JsYW5rKCksDQogICAgICAgIHBhbmVsLmdyaWQubWFqb3IueCA9IGVsZW1lbnRfbGluZShzaXplPS4yLCBjb2xvcj0iZ3JleTkwIiksDQogICAgICAgIHBhbmVsLmdyaWQubWlub3IueCA9IGVsZW1lbnRfbGluZShzaXplPS4yLCBjb2xvcj0iZ3JleTkwIiksDQogICAgICAgIHBhbmVsLmdyaWQubWFqb3IueSA9IGVsZW1lbnRfbGluZShzaXplPS4yLCBjb2xvcj0iZ3JleTkwIiwgbGluZXR5cGU9ImRvdHRlZCIpKQ0KDQpjb3hfY29lZiA8LSBzdW1tYXJ5KGNveCkNCmNveF9jb2VmIDwtIHRpYmJsZShWYXJpYWJsZSA9IHJvdy5uYW1lcyhjb3hfY29lZiRjb2VmZmljaWVudHMpLA0KICAgICAgICAgICAgICAgICAgICAgICAgZ3JvdXAgPSBmZWF0dXJlX2dyb3VwKFZhcmlhYmxlLCBmZWF0X3ByZXYpLA0KICAgICAgICAgICAgICAgICAgICAgICAgSFIgPSBjb3hfY29lZiRjb2VmZmljaWVudHNbLDJdLA0KICAgICAgICAgICAgICAgICAgICAgICAgc2UgPSBjb3hfY29lZiRjb2VmZmljaWVudHNbLDNdKSAlPiUNCiAgYXJyYW5nZShIUiwgZ3JvdXApDQoNCnAyIDwtIGdncGxvdChjb3hfY29lZiwgYWVzKHg9VmFyaWFibGUsIHk9SFIsIGNvbG9yPWdyb3VwKSkgKyANCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMSkgKw0KICBnZW9tX3BvaW50cmFuZ2UoYWVzKHltaW49SFIqZXhwKC0xLjk2KnNlKSwgeW1heD1IUipleHAoMS45NipzZSkpKSArDQogIHNjYWxlX3hfZGlzY3JldGUobGltaXRzID0gY294X2NvZWYkVmFyaWFibGUpICsNCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IHBhbGV0dGVlcl9kKCJnZ3RoZW1lczo6Q2xhc3NpY18yMCIpWzE6MTldKSArDQogIGNvb3JkX2ZsaXAoKSArDQogIGxhYnMoeSA9ICJIYXphcmQgUmF0aW8gWzk1JSBDSV0iLCB4ID0gIiIsIHRpdGxlID0gIkNveCBQSCBwcmV2LCBoYXphcmQgcmF0aW9zIikgKw0KICBnZ3RoZW1lczo6dGhlbWVfdHVmdGUoKSArDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIiwNCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiLCBoanVzdCA9IDAuNSksDQogICAgICAgIGF4aXMubGluZSA9IGVsZW1lbnRfbGluZShzaXplID0gLjIsIGNvbG9yID0gImdyZXkyMCIpLA0KICAgICAgICBheGlzLnRpY2tzLnkgPSBlbGVtZW50X2JsYW5rKCksDQogICAgICAgIHBhbmVsLmdyaWQubWFqb3IueCA9IGVsZW1lbnRfbGluZShzaXplPS4yLCBjb2xvcj0iZ3JleTkwIiksDQogICAgICAgIHBhbmVsLmdyaWQubWlub3IueCA9IGVsZW1lbnRfbGluZShzaXplPS4yLCBjb2xvcj0iZ3JleTkwIiksDQogICAgICAgIHBhbmVsLmdyaWQubWFqb3IueSA9IGVsZW1lbnRfbGluZShzaXplPS4yLCBjb2xvcj0iZ3JleTkwIiwgbGluZXR5cGU9ImRvdHRlZCIpKQ0KDQpnZ2FycmFuZ2UocDEscDIsbnJvdyA9IDEsbmNvbCA9IDIpDQpgYGANCg0KPGJyPg0KDQojIyMgKipNb2RlbHMgd2l0aCB0aW1lLWRlcGVuZGVudCBjb3ZhcmlhdGVzKioNCg0KPGJyPg0KDQpgYGB7ciB0ZH0NCnRyYW5zX2RmX2RhdGUgPC0gdHJhbnNfZGYgJT4lDQogIGdyb3VwX2J5KHN1YnNjcl9pZCwgdHJhbnNhY3Rpb25fZGF0ZSkgJT4lDQogIHN1bW1hcmlzZShkYXRlID0gdW5pcXVlKHRyYW5zYWN0aW9uX2RhdGUpLA0KICAgICAgICAgICAgYWN0dWFsX2Ftb3VudF9wYWlkID0gc3VtKGFjdHVhbF9hbW91bnRfcGFpZCksDQogICAgICAgICAgICBpc19jYW5jZWwgPSBzdW0oaXNfY2FuY2VsKSwgDQogICAgICAgICAgICBwYXltZW50X21ldGhvZF9pZCA9IHBheW1lbnRfbWV0aG9kX2lkW24oKV0sDQogICAgICAgICAgICBwbGFuX2xpc3RfcHJpY2UgPSBtYXgocGxhbl9saXN0X3ByaWNlKSkgJT4lDQogIHVuZ3JvdXAoKQ0KDQp1bG9nc190cmFuc19wZXJpb2RzIDwtIHVsb2dzX3BlcmlvZHMgJT4lDQogIHNlbGVjdChkYXRlLCBzdWJzY3JfaWQsIG51bV90b3QsIG51bV91bnEsIHRvdGFsX3NlY3NfaW1wLCBhdl9wY3RfcGxheWVkKSAlPiUNCiAgbXV0YXRlKGZpbGUgPSAidWxvZ3MiKSAlPiUNCiAgZnVsbF9qb2luKHRyYW5zX3BlcmlvZHMgJT4lDQogICAgICAgICAgICAgIHNlbGVjdChzdWJzY3JfaWQsIG1zbm8sIG1zbm9fc3Vic2NyX2lkLCBjaHVybiwgdGltZSwgc3RhcnRfZGF0ZSwgZW5kX2RhdGUsDQogICAgICAgICAgICAgICAgICAgICBjaHVybmVkX2JlZm9yZV9udW0sIGNpdHksIGJkRywgZ2VuZGVyLCByZWdpc3RlcmVkX3ZpYSwgbW9udGhfcGVyaW9kLA0KICAgICAgICAgICAgICAgICAgICAgZmlyc3RfcGF5bWVudF9tZXRob2RfaWQsIGZpcnN0X3BsYW5fbGlzdF9wcmljZSwNCiAgICAgICAgICAgICAgICAgICAgIHRpbWVfZnJvbV9yZWcsIHRpbWVfZnJvbV9wcmV2X2V4cCwgcHJldl90aW1lLA0KICAgICAgICAgICAgICAgICAgICAgcHJldl9tZWFuX251bV90b3QsIHByZXZfbWVhbl90b3RhbF9zZWNzX2ltcCwNCiAgICAgICAgICAgICAgICAgICAgIHByZXZfbWVhbl9udW1fdW5xLCBwcmV2X21lYW5fYXZfcGN0X3BsYXllZF9hY3QsDQogICAgICAgICAgICAgICAgICAgICBwcmV2X21lYW5fYWN0dWFsX2Ftb3VudF9wYWlkLCBwcmV2X251bV9jYW5jZWwsDQogICAgICAgICAgICAgICAgICAgICBwcmV2X21vZGVfcGF5bWVudF9pZCksDQogICAgICAgICAgICBieSA9ICJzdWJzY3JfaWQiKSAlPiUNCiAgZnVsbF9qb2luKHRyYW5zX2RmX2RhdGUgJT4lIHRyYW5zbXV0ZShzdWJzY3JfaWQsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGUgPSBhcy5EYXRlKGlmZWxzZShzdWJzY3JfaWQgJWluJSB1bG9nc19wZXJpb2RzJHN1YnNjcl9pZCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdHJhbnNhY3Rpb25fZGF0ZSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE5BKSwgIjE5NzAtMDEtMDEiKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0cmFuc2FjdGlvbl9kYXRlLCBhY3R1YWxfYW1vdW50X3BhaWQsIHBsYW5fbGlzdF9wcmljZSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpc19jYW5jZWwsIHBheW1lbnRfbWV0aG9kX2lkKSwNCiAgICAgICAgICAgIGJ5ID0gYygic3Vic2NyX2lkIiwgImRhdGUiKSkNCg0KdWxvZ3NfdHJhbnNfcGVyaW9kcyA8LSB1bG9nc190cmFuc19wZXJpb2RzICU+JQ0KICBtdXRhdGUoZGF0ZSA9IGlmZWxzZShpcy5uYShkYXRlKSwgdHJhbnNhY3Rpb25fZGF0ZSwgZGF0ZSkpICU+JQ0KICBtdXRhdGVfYXQodmFycyhudW1fdG90LCBudW1fdW5xLCB0b3RhbF9zZWNzX2ltcCwgYXZfcGN0X3BsYXllZCwNCiAgICAgICAgICAgICAgICAgYWN0dWFsX2Ftb3VudF9wYWlkLCBwbGFuX2xpc3RfcHJpY2UsIGlzX2NhbmNlbCksIH4gcmVwbGFjZV9uYSguLCAwKSkgJT4lDQogIGdyb3VwX2J5KHN1YnNjcl9pZCkgJT4lDQogIG11dGF0ZV9hdCh2YXJzKG1zbm8sIG1zbm9fc3Vic2NyX2lkLCBjaHVybiwgdGltZSwgc3RhcnRfZGF0ZSwgZW5kX2RhdGUsDQogICAgICAgICAgICAgICAgIGNodXJuZWRfYmVmb3JlX251bSwgY2l0eSwgYmRHLCBnZW5kZXIsIHJlZ2lzdGVyZWRfdmlhLCBtb250aF9wZXJpb2QsDQogICAgICAgICAgICAgICAgIGZpcnN0X3BheW1lbnRfbWV0aG9kX2lkLCBmaXJzdF9wbGFuX2xpc3RfcHJpY2UsDQogICAgICAgICAgICAgICAgIHRpbWVfZnJvbV9yZWcsIHRpbWVfZnJvbV9wcmV2X2V4cCwgcHJldl90aW1lLA0KICAgICAgICAgICAgICAgICBwcmV2X21lYW5fbnVtX3RvdCwgcHJldl9tZWFuX3RvdGFsX3NlY3NfaW1wLA0KICAgICAgICAgICAgICAgICBwcmV2X21lYW5fbnVtX3VucSwgcHJldl9tZWFuX2F2X3BjdF9wbGF5ZWRfYWN0LA0KICAgICAgICAgICAgICAgICBwcmV2X21lYW5fYWN0dWFsX2Ftb3VudF9wYWlkLCBwcmV2X251bV9jYW5jZWwsDQogICAgICAgICAgICAgICAgIHByZXZfbW9kZV9wYXltZW50X2lkKSwNCiAgICAgICAgICAgIH4gdW5pcXVlKC5bIWlzLm5hKC4pXSkpICU+JQ0KICBtdXRhdGUoY2h1cm4gPSBpZmVsc2Uocm93X251bWJlcigpICE9IG4oKSwgMCwgY2h1cm4pKSU+JQ0KICB1bmdyb3VwKCkNCg0KdWxvZ3NfdHJhbnNfcGVyaW9kcyA8LSB1bG9nc190cmFuc19wZXJpb2RzICU+JQ0KICBtdXRhdGUoZGF0ZSA9IGFzLkRhdGUoZGF0ZSwgIjE5NzAtMDEtMDEiKSkgJT4lDQogIGFycmFuZ2Uoc3Vic2NyX2lkLCBkYXRlKQ0KDQp1bG9nc190cmFuc19wZXJpb2RzIDwtIHN1cHByZXNzV2FybmluZ3MoDQogIHVsb2dzX3RyYW5zX3BlcmlvZHMgJT4lDQogICAgZ3JvdXBfYnkoc3Vic2NyX2lkKSAlPiUNCiAgICBtdXRhdGUodGltZV9zdGFydCA9IHJlcGxhY2VfbmEoYXMubnVtZXJpYyhkYXRlIC0gZGF0ZVsxXSwgJ2RheXMnKSwgMCksDQogICAgICAgICAgIHRpbWVfc3RvcCA9IGlmZWxzZShpcy5uYShsZWFkKHRpbWVfc3RhcnQpKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmZWxzZShpcy5uYShkYXRlKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aW1lLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFzLm51bWVyaWMoZW5kX2RhdGUgLSBkYXRlWzFdLCAnZGF5cycpICsgMSksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsZWFkKHRpbWVfc3RhcnQpKSwNCiAgICAgICAgICAgdGltZV9zdG9wID0gaWZlbHNlKHRpbWVfc3RvcCA9PSAwLCAxLCB0aW1lX3N0b3ApLA0KICAgICAgICAgICB0aW1lX2ludCA9IHRpbWVfc3RvcCAtIHRpbWVfc3RhcnQsDQogICAgICAgICAgIGN1bV9wY3RfYWN0aXZlID0gY3Vtc3VtKCFpcy5uYShmaWxlKSkvdGltZV9zdG9wKjEwMCwNCiAgICAgICAgICAgY3VtX21lYW5fbnVtX3RvdCA9IGN1bXN1bShudW1fdG90KS90aW1lX3N0b3AsDQogICAgICAgICAgIGN1bV9tZWFuX251bV91bnEgPSBjdW1zdW0obnVtX3VucSkvdGltZV9zdG9wLA0KICAgICAgICAgICBjdW1fbWVhbl90b3RhbF9zZWNzX2ltcCA9IGN1bXN1bSh0b3RhbF9zZWNzX2ltcCkvdGltZV9zdG9wLA0KICAgICAgICAgICBtZWFuX2F2X3BjdF9wbGF5ZWRfYWN0ID0gcmVwbGFjZV9uYShjdW1zdW0oYXZfcGN0X3BsYXllZCkvY3Vtc3VtKCFpcy5uYShmaWxlKSksIDApLA0KICAgICAgICAgICBjdW1fbWVhbl9hY3R1YWxfYW1vdW50X3BhaWQgPSBjdW1zdW0oYWN0dWFsX2Ftb3VudF9wYWlkKS90aW1lX3N0b3AsDQogICAgICAgICAgIGN1bV9udW1fY2FuY2VsID0gY3Vtc3VtKGlzX2NhbmNlbCksDQogICAgICAgICAgIGxhc3RfcGF5bWVudF9tZXRob2RfaWQgPSB6b286Om5hLmxvY2YocGF5bWVudF9tZXRob2RfaWQpLA0KICAgICAgICAgICBsYXN0X3BsYW5fbGlzdF9wcmljZSA9IHpvbzo6bmEubG9jZihwbGFuX2xpc3RfcHJpY2UpKSAlPiUNCiAgICB1bmdyb3VwKCkpDQoNCnNhdmVSRFModHJhbnNfcGVyaW9kcywgInRyYW5zX3BlcmlvZHMuUkRTIikNCnNhdmVSRFModWxvZ3NfdHJhbnNfcGVyaW9kcywgInVsb2dzX3RyYW5zX3BlcmlvZHMuUkRTIikNCg0KdHJhaW5fc2V0IDwtIHJlYWQuY3N2KCJYX3RyYWluX2luZGV4LmNzdiIpDQpzYW1wbGVfWF90ZXN0X2lkIDwtIHJlYWQuY3N2KCJzYW1wbGVfWF90ZXN0X2lkLmNzdiIpDQoNCmRmX2ZlYXR1cmVzIDwtIHVsb2dzX3RyYW5zX3BlcmlvZHMgJT4lDQogIHRyYW5zbXV0ZShzdWJzY3JfaWQsIHRpbWVfc3RhcnQsIHRpbWVfc3RvcCwgY2h1cm4sDQogICAgICAgICAgICBjaHVybmVkX2JlZm9yZV9udW0sIA0KICAgICAgICAgICAgY2l0eUcgPSBmY3RfbHVtcF9taW4oY2l0eSwgMzAsIG90aGVyX2xldmVsID0gIjE5LTIwIiksIGJkRywgZ2VuZGVyLA0KICAgICAgICAgICAgcmVnaXN0ZXJlZF92aWEsIG1vbnRoX3BlcmlvZCwNCiAgICAgICAgICAgIHRpbWVfZnJvbV9yZWcsIHRpbWVfZnJvbV9wcmV2X2V4cCwgcHJldl90aW1lLCANCiAgICAgICAgICAgIGN1bV9wY3RfYWN0aXZlLCBjdW1fbWVhbl9udW1fdG90LCBjdW1fbWVhbl90b3RhbF9zZWNzX2ltcCwNCiAgICAgICAgICAgIGN1bV9tZWFuX251bV91bnEsIG1lYW5fYXZfcGN0X3BsYXllZF9hY3QsIGN1bV9tZWFuX2FjdHVhbF9hbW91bnRfcGFpZCwNCiAgICAgICAgICAgIGN1bV9udW1fY2FuY2VsLCBsYXN0X3BheW1lbnRfbWV0aG9kX2lkLCBsYXN0X3BsYW5fbGlzdF9wcmljZSkNCg0KZGZfdHJhaW4gPC0gZGZfZmVhdHVyZXMgJT4lDQogIGZpbHRlcihzdWJzY3JfaWQgJWluJSB0cmFuc19wZXJpb2RzJHN1YnNjcl9pZFt0cmFpbl9zZXQkaW5kICsgMV0pDQoNCmRmX3Rlc3QgPC0gZGZfZmVhdHVyZXMgJT4lDQogIGZpbHRlcighIHN1YnNjcl9pZCAlaW4lIHRyYW5zX3BlcmlvZHMkc3Vic2NyX2lkW3RyYWluX3NldCRpbmQgKyAxXSkNCg0Kc2FtcGxlX1hfc3Vic2NyX2lkIDwtIHRyYW5zX3BlcmlvZHMkc3Vic2NyX2lkW3NhbXBsZV9YX3Rlc3RfaWQkSUQgKyAxXQ0KDQpzYW1wbGVfdGVzdCA8LSBkZl9mZWF0dXJlcyAlPiUNCiAgZmlsdGVyKHN1YnNjcl9pZCAlaW4lIHRyYW5zX3BlcmlvZHMkc3Vic2NyX2lkW3NhbXBsZV9YX3Rlc3RfaWQkSUQgKyAxXSkNCg0KZmVhdHVyZXNfYmFzZWxpbmUgPC0gYygiY2h1cm5lZF9iZWZvcmVfbnVtIiwgDQogICAgICAgICAgICAgICAgICAgICAgICJjaXR5RyIsICJiZEciLCAiZ2VuZGVyIiwNCiAgICAgICAgICAgICAgICAgICAgICAgInJlZ2lzdGVyZWRfdmlhIiwgIm1vbnRoX3BlcmlvZCIsDQogICAgICAgICAgICAgICAgICAgICAgICJ0aW1lX2Zyb21fcmVnIiwgInRpbWVfZnJvbV9wcmV2X2V4cCIsICJwcmV2X3RpbWUiKQ0KZmVhdHVyZXNfdGltZWRlcCA8LSBjKCJjdW1fcGN0X2FjdGl2ZSIsICJjdW1fbWVhbl9udW1fdG90IiwgImN1bV9tZWFuX3RvdGFsX3NlY3NfaW1wIiwNCiAgICAgICAgICAgICAgICAgICAgICAiY3VtX21lYW5fbnVtX3VucSIsICJtZWFuX2F2X3BjdF9wbGF5ZWRfYWN0IiwgImN1bV9tZWFuX2FjdHVhbF9hbW91bnRfcGFpZCIsDQogICAgICAgICAgICAgICAgICAgICAgImN1bV9udW1fY2FuY2VsIiwgImxhc3RfcGF5bWVudF9tZXRob2RfaWQiLCAibGFzdF9wbGFuX2xpc3RfcHJpY2UiKSAgDQoNCiMgUHJlZGljdCBzdXJ2aXZhbCBhZnRlciBDb3ggUEgNCg0Kc3VydmVzdF9pbmQgPC0gZnVuY3Rpb24oZXN0aW1hdG9yLCBpZF9wZXJpb2QpIHsNCiAgaW50ZXJ2YWxzIDwtIGRmX3Rlc3QgJT4lDQogICAgZmlsdGVyKHN1YnNjcl9pZCA9PSBpZF9wZXJpb2QpICU+JQ0KICAgIHNlbGVjdCh0aW1lX3N0YXJ0LCB0aW1lX3N0b3AsIGNodXJuKQ0KICANCiAgY292cyA8LSBkYXRhLmZyYW1lKGRmX3Rlc3QgJT4lIGZpbHRlcihzdWJzY3JfaWQgPT0gaWRfcGVyaW9kKSAlPiUgc2VsZWN0KGFsbF9vZihmZWF0dXJlc19iYXNlbGluZSkpICU+JSB1bmlxdWUoKSkNCiAgY292c1ssIGZlYXR1cmVzX3RpbWVkZXBdIDwtIDANCiAgY292cyRsYXN0X3BheW1lbnRfbWV0aG9kX2lkIDwtIHRyYW5zX3BlcmlvZHMkZmlyc3RfcGF5bWVudF9tZXRob2RfaWRbdHJhbnNfcGVyaW9kcyRzdWJzY3JfaWQgPT0gaWRfcGVyaW9kXQ0KICBjb3ZzJGxhc3RfcGxhbl9saXN0X3ByaWNlIDwtIHRyYW5zX3BlcmlvZHMkZmlyc3RfcGxhbl9saXN0X3ByaWNlW3RyYW5zX3BlcmlvZHMkc3Vic2NyX2lkID09IGlkX3BlcmlvZF0NCiAgDQogIG5ld2RhdGEgPC0gZGF0YS5mcmFtZShjb3ZzWzEsIF0sIGludGVydmFscywgcm93Lm5hbWVzID0gTlVMTCkNCiAgb3V0IDwtIHN1cnZmaXQoZXN0aW1hdG9yLCBuZXdkYXRhID0gbmV3ZGF0YSkNCiAgDQogIHN1cnZlc3RzIDwtIGxpc3QoYmFzZWxpbmUgPSB0aWJibGUodGltZSA9IG91dCR0aW1lLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdXJ2ID0gb3V0JHN1cnZbLDFdKSkNCiAgDQogIGNvdnMgPC0gZGZfdGVzdCAlPiUNCiAgICBmaWx0ZXIoc3Vic2NyX2lkID09IGlkX3BlcmlvZCkNCiAgDQogIG5ld2RhdGEgPC0gZGF0YS5mcmFtZShjb3ZzICU+JSBzZWxlY3QoLXN1YnNjcl9pZCksIHJvdy5uYW1lcyA9IE5VTEwpDQogIG91dCA8LSBzdXJ2Zml0KGVzdGltYXRvciwgbmV3ZGF0YSA9IG5ld2RhdGEpDQogIA0KICBzdXJ2ZXN0c1tbImNvbXBsZXRlIl1dIDwtIG91dCRzdXJ2DQogIA0KICBzdXJ2ZXN0cw0KfQ0KDQojIFBsb3QgdXBkYXRlZCBhbmQgbm90IHVwZGF0ZWQgc3Vydml2YWwgY3VydmVzIGF0ICNudW0gcG9pbnRzDQoNCnN1cnZlc3RfcGxvdHMgPC0gZnVuY3Rpb24oaWR0LCBpZHN1cnYsIG51bT0xMCkgew0KICB0aW1lcyA8LSBkZl90ZXN0ICU+JQ0KICAgIGZpbHRlcihzdWJzY3JfaWQgPT0gaWR0KSAlPiUNCiAgICBzZWxlY3QodGltZV9zdG9wKSAlPiUNCiAgICBwdWxsKCkNCiAgDQogIGlmIChsZW5ndGgodGltZXMpID49IG51bSkgew0KICAgIHRpbWVzX3NlbGVjdCA8LSBzZXFfYWxvbmcodGltZXMpDQogICAgdGltZXNfc2VsZWN0IDwtIHVuaXF1ZShjKHNlcSgxLCBtYXgodGltZXNfc2VsZWN0KSwgZmxvb3IobWF4KHRpbWVzX3NlbGVjdCkvKG51bS0xKSkpLCBtYXgodGltZXNfc2VsZWN0KSkpDQogICAgdGltZXNfc2VsZWN0IDwtIHRpbWVzW3RpbWVzX3NlbGVjdF0NCiAgfSBlbHNlIHsNCiAgICB0aW1lc19zZWxlY3QgPC0gdGltZXMNCiAgfQ0KICANCiAgdGltZXNfc2VsZWN0IDwtIHRpbWVzX3NlbGVjdFtvcmRlcih0aW1lc19zZWxlY3QpXQ0KICANCiAgdGltZXNfbGFiIDwtIGMoIkJhc2VsaW5lIiwgdGltZXNfc2VsZWN0KQ0KICANCiAgY29sb3JzIDwtIHBhbGV0dGVlcl9jKCJnckRldmljZXM6Olppc3NvdSAxIiwgMSArIGxlbmd0aCh0aW1lc19zZWxlY3QpKQ0KICANCiAgZGZwIDwtIHRpYmJsZSh0aW1lID0gYXMubnVtZXJpYyhpZHN1cnYkYmFzZWxpbmUkdGltZSksDQogICAgICAgICAgICAgICAgc3VydiA9IGFzLm51bWVyaWMoaWRzdXJ2JGJhc2VsaW5lJHN1cnYpLCANCiAgICAgICAgICAgICAgICBsYmwgPSBzcHJpbnRmKCJJZiBub3QgdXBkYXRlZCBhZnRlciAlcyVzIiwgdGltZXNfbGFiWzFdLCAiIikpDQogIHAgPC0gZ2dwbG90KGRmcCkgKw0KICAgIGdlb21fbGluZShhZXMoeCA9IHRpbWUsIHkgPSBzdXJ2LCBsaW5ldHlwZSA9IGxibCksDQogICAgICAgICAgICAgIHNpemUgPSAxLCBjb2xvciA9IGNvbG9yc1sxXSkgKw0KICAgIHNjYWxlX3lfY29udGludW91cyhicmVha3MgPSBzZXEoMCwxLDAuMiksDQogICAgICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IHNjYWxlczo6cGVyY2VudF9mb3JtYXQoYWNjdXJhY3kgPSAxKSwNCiAgICAgICAgICAgICAgICAgICAgICAgZXhwYW5kID0gYygwLjAyLDApLA0KICAgICAgICAgICAgICAgICAgICAgICBsaW1pdHMgPSBjKDAsIDEpKSArDQogICAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IHNlcSgwLCA4MTAsIDkwKSwgZXhwYW5kID0gYygwLjAyLDApKSArDQogICAgbGFicyh4ID0gIkRheXMiLCB5ID0gIkV4cC4gc3Vydml2YWwgcHJvYmFiaWxpdHkiKSArDQogICAgdGhlbWVfY2xhc3NpYygpIA0KICBwbG90cyA8LSBsaXN0KHApDQogIA0KICBzdXJ2X3VwZCA8LSBkZnAkc3VydlsxXQ0KICANCiAgZm9yIChpIGluIHNlcV9hbG9uZyh0aW1lc19zZWxlY3QpKSB7DQogICAgZGZwIDwtIHRpYmJsZSh0aW1lID0gYXMubnVtZXJpYyhpZHN1cnYkYmFzZWxpbmUkdGltZVtpZHN1cnYkYmFzZWxpbmUkdGltZSA+PSB0aW1lc19zZWxlY3RbaV1dKSwNCiAgICAgICAgICAgICAgICAgIHN1cnYgPSBhcy5udW1lcmljKGlkc3VydiRjb21wbGV0ZVtpZHN1cnYkYmFzZWxpbmUkdGltZSA+PSB0aW1lc19zZWxlY3RbaV0sIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHdoaWNoKHRpbWVzID09IHRpbWVzX3NlbGVjdFtpXSldKSwNCiAgICAgICAgICAgICAgICAgIGxibCA9IHNwcmludGYoIklmIG5vdCB1cGRhdGVkIGFmdGVyICVzJXMiLCB0aW1lc19sYWJbaSsxXSwgImQuIikpDQogICAgc3Vydl91cGQgPC0gYyhzdXJ2X3VwZCwgZGZwJHN1cnZbMV0pDQogICAgDQogICAgcCA8LSBnZ3Bsb3QoZGZwKSArDQogICAgICBnZW9tX2xpbmUoYWVzKHggPSB0aW1lLCB5ID0gc3VydiwgbGluZXR5cGUgPSBsYmwpLA0KICAgICAgICAgICAgICAgIHNpemUgPSAxLCBjb2xvciA9IGNvbG9yc1tpICsgMV0pICsNCiAgICAgIHNjYWxlX3lfY29udGludW91cyhicmVha3MgPSBzZXEoMCwxLDAuMiksIA0KICAgICAgICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IHNjYWxlczo6cGVyY2VudF9mb3JtYXQoYWNjdXJhY3kgPSAxKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICBleHBhbmQgPSBjKDAuMDIsMCksIGxpbWl0cyA9IGMoMCwgMSkpICsNCiAgICAgIHNjYWxlX3hfY29udGludW91cyhicmVha3MgPSBzZXEoMCwgODEwLCA5MCksIGV4cGFuZCA9IGMoMC4wMiwwKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICBsaW1pdHMgPSBjKDAsIDgyMSkpICsNCiAgICAgIGxhYnMoeCA9ICJEYXlzIiwgeSA9ICJFeHAuIHN1cnZpdmFsIHByb2JhYmlsaXR5IiwgbGluZXR5cGUgPSAiIikgKw0KICAgICAgdGhlbWVfY2xhc3NpYygpDQogICAgDQogICAgcGxvdHNbW2krMV1dIDwtIHANCiAgfQ0KICANCiAgZGZwIDwtIHRpYmJsZSh0aW1lID0gYygwLCB0aW1lc19zZWxlY3QpLA0KICAgICAgICAgICAgICAgIHN1cnYgPSBzdXJ2X3VwZCkNCiAgDQogIGZvciAoaSBpbiAxOm5yb3coZGZwKSkgew0KICAgIHBsb3RzW1tpXV0gPC0gcGxvdHNbW2ldXSArDQogICAgICBnZW9tX2xpbmUoYWVzKHggPSB0aW1lLCB5ID0gc3VydiwgbGluZXR5cGUgPSAiVXBkYXRlZCIpLCBkZnAsDQogICAgICAgICAgICAgICAgc2l6ZSA9IDAuNSwgY29sb3IgPSAiZ3JleTIwIiwgc2hvdy5sZWdlbmQgPSBUUlVFKSArDQogICAgICBnZW9tX3BvaW50KGFlcyh4ID0gdGltZSwgeSA9IHN1cnYpLCANCiAgICAgICAgICAgICAgICAgZGZwW2ksXSwgc2hhcGUgPSAyMSwNCiAgICAgICAgICAgICAgICAgc2l6ZSA9IDMsIGNvbG9yID0gImJsYWNrIiwgZmlsbCA9IGNvbG9yc1tpXSwNCiAgICAgICAgICAgICAgICAgc2hvdy5sZWdlbmQgPSBGQUxTRSkgKw0KICAgICAgc2NhbGVfbGluZXR5cGVfbWFudWFsKHZhbHVlcyA9IGMoInNvbGlkIiwgImRvdHRlZCIpKSArDQogICAgICBsYWJzKGxpbmV0eXBlID0gIiIpICsNCiAgICAgIGd1aWRlcyhsaW5ldHlwZSA9IGd1aWRlX2xlZ2VuZChvdmVycmlkZS5hZXMgPSBsaXN0KGNvbG9yID0gYyhjb2xvcnNbaV0sICJncmV5NTAiKSkpKSArDQogICAgICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSBjKDAuNywwLjkpLA0KICAgICAgICAgICAgbGVnZW5kLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCkpDQogICAgDQogICAgcGxvdHNbW2ldXSRsYXllcnMgPC0gcGxvdHNbW2ldXSRsYXllcnNbYygyLDEsMyldDQogICAgDQogIH0NCiAgDQogIHBsb3RzDQp9DQpgYGANCg0KSGVyZSBpbiB0aGUgdHJhaW4gc2FtcGxlIHdlIHVzZWQgYWxsIHN1YmludGVydmFscyBmb3IgdGhvc2Ugc3Vic2NyaXB0aW9uIHBlcmlvZHMgd2hpY2ggd2VyZSBhIHBhcnQgb2YgdGhlIHRyYWluaW5nIHNhbXBsZSB3aXRoIHRpbWUtaW5kZXBlbmRlbnQgY292YXJpYXRlcy4gVGhlIHNhbWUgaG9sZHMgZm9yIHRoZSB0ZXN0IHNldC4gT3ZlcmFsbCwgaW5zdGVhZCBvZiBgciBucm93KHRyYWluX3NldClgIHN1YnNjcmlwdGlvbiBwZXJpb2RzIGluIHRpbWUtaW52YXJpYW50IHRyYWluaW5nIGRhdGEgYW5kIGByIG5yb3codHJhbnNfcGVyaW9kcykgLSBucm93KHRyYWluX3NldClgIGluIHRpbWUtaW52YXJpYW50IHRlc3QgZGF0YSwgd2Ugb2J0YWluZWQsIGFjY29yZGluZ2x5LCBgciBucm93KHVsb2dzX3RyYW5zX3BlcmlvZHMgJT4lIGZpbHRlcihzdWJzY3JfaWQgJWluJSB0cmFuc19wZXJpb2RzJHN1YnNjcl9pZFt0cmFpbl9zZXQkaW5kKzFdKSlgIGFuZCBgciBucm93KHVsb2dzX3RyYW5zX3BlcmlvZHMgJT4lIGZpbHRlcighIHN1YnNjcl9pZCAlaW4lIHRyYW5zX3BlcmlvZHMkc3Vic2NyX2lkW3RyYWluX3NldCRpbmQrMV0pKWAgcHNldWRvLW9ic2VydmF0aW9ucyBhZnRlciBjb3VudGluZyBwcm9jZXNzLg0KDQpgYGB7ciBjb3hfdGR9DQojIENveA0KDQpjb3hfdGQgPC0gY294cGgoU3Vydih0aW1lID0gdGltZV9zdGFydCwgdGltZTIgPSB0aW1lX3N0b3AsIGV2ZW50ID0gY2h1cm4pIH4gLiwgDQogICAgICAgICAgICAgICAgZGZfdHJhaW4gJT4lIHNlbGVjdCgtc3Vic2NyX2lkKSwgeCA9IFRSVUUpDQoNCmNveF90ZF9zdW0gPC0gc3VtbWFyeShjb3hfdGQpDQoNCiMgUGxvdCBnaWZzIGZvciB1cGRhdGVkIGFuZCBub3QgdXBkYXRlZCBzdXJ2aXZhbCBjdXJ2ZXMgZm9yIHNhbXBsZWQgdGVzdCBwZXJpb2RzDQojICsgb25lIGFkZGl0aW9uYWwgZm9yIGlsbHVzdHJhdGlvbg0KDQpmb3IgKGlkdCBpbiBjKHNhbXBsZV9YX3N1YnNjcl9pZCwgMTY3MzkpKSB7DQogIA0KICB0ZXN0IDwtIHN1cnZlc3RfaW5kKGNveF90ZCwgaWR0KQ0KICB0ZXN0X3Bsb3RzIDwtIHN1cnZlc3RfcGxvdHMoaWR0LCB0ZXN0KQ0KICANCiAgZGlyX291dCA8LSBmaWxlLnBhdGgodGVtcGRpcigpLCBwYXN0ZTAoInN1cnZfcGxvdHMvIiwgaWR0KSkNCiAgZGlyLmNyZWF0ZShkaXJfb3V0LCByZWN1cnNpdmUgPSBUUlVFKQ0KICANCiAgZm9yIChpIGluIDE6bGVuZ3RoKHRlc3RfcGxvdHMpKSB7DQogICAgDQogICAgZnAgPC0gZmlsZS5wYXRoKGRpcl9vdXQsIHBhc3RlMCgiaWQiLCBpZHQsICJfIiwgMTAwK2ksICIucG5nIikpDQogICAgDQogICAgZ2dzYXZlKHBsb3QgPSB0ZXN0X3Bsb3RzW1tpXV0sIA0KICAgICAgICAgICBmaWxlbmFtZSA9IGZwLCB3aWR0aCA9IDUsIGhlaWdodCA9IDMsIGRwaSA9IDMwMCwNCiAgICAgICAgICAgZGV2aWNlID0gInBuZyIpDQogIH0NCiAgDQogIGltZ3MgPC0gbGlzdC5maWxlcyhkaXJfb3V0LCBmdWxsLm5hbWVzID0gVFJVRSkNCiAgaW1nX2xpc3QgPC0gbGFwcGx5KGltZ3MsIGltYWdlX3JlYWQpDQogIGltZ19saXN0IDwtIGxhcHBseShpbWdfbGlzdCwgaW1hZ2Vfc2NhbGUsICJ4NjAwIikNCiAgDQogIGltZ19qb2luZWQgPC0gaW1hZ2Vfam9pbihpbWdfbGlzdCkNCiAgDQogIGltZ19hbmltYXRlZCA8LSBpbWFnZV9hbmltYXRlKGltZ19qb2luZWQsIGZwcyA9IDEpDQogIA0KICBpbWFnZV93cml0ZShpbWFnZSA9IGltZ19hbmltYXRlZCwNCiAgICAgICAgICAgICAgcGF0aCA9IHBhc3RlMCgidGVzdF9wbG90c18iLCBpZHQsICIuZ2lmIikpDQp9ICANCmBgYA0KDQo8YnI+DQoNCmBgYHtyIGNveF90ZF9ldmFsfQ0KY294X3RkX2NvbmNfdHJhaW4gPC0gY294X3RkX3N1bSRjb25jb3JkYW5jZVsxXQ0KY294X3RkX2NvbmNfdGVzdCA8LSBjb25jb3JkYW5jZShjb3hfdGQsIG5ld2RhdGEgPSBkYXRhLmZyYW1lKGRmX3Rlc3QpKQ0KY294X3RkX2NvbmNfdGVzdCA8LSBjb3hfdGRfY29uY190ZXN0JGNvbmNvcmRhbmNlDQoNCg0KY294X3RkX3ByZWQgPC0gc3VydmZpdChjb3hfdGQsIG5ld2RhdGEgPSBkZl90ZXN0LCB0eXBlID0gImFhbGVuIiwNCiAgICAgICAgICAgICAgICAgICAgICAgaWQgPSBkZl90ZXN0JHN1YnNjcl9pZCkNCg0KY294X3RkX3ByZWRfZGYgPC0gZGF0YS5mcmFtZSh0aW1lID0gY294X3RkX3ByZWQkdGltZSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcHJvYiA9IGNveF90ZF9wcmVkJHN1cnYpDQpjb3hfdGRfcHJlZF9kZiRpZCA8LSBjb3hfdGRfcHJlZF9kZiR0aW1lIC0gcmVwbGFjZV9uYShsYWcoY294X3RkX3ByZWRfZGYkdGltZSksMykNCmNveF90ZF9wcmVkX2RmJGlkIDwtIGN1bXN1bShjb3hfdGRfcHJlZF9kZiRpZCA8PSAwKQ0KDQpjb3hfdGRfcHJlZF9saXN0IDwtIHNwbGl0KGNveF90ZF9wcmVkX2RmLCBjb3hfdGRfcHJlZF9kZiRpZCkNCmNveF90ZF9wcmVkX2xpc3QgPC0gc2V0TmFtZXMoY294X3RkX3ByZWRfbGlzdCwgTlVMTCkNCg0KY294X3RkX3ByZWRfbGlzdF9pYnM3MjAgPC0gbGFwcGx5KGNveF90ZF9wcmVkX2xpc3QsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZnVuY3Rpb24oeCkgew0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeCA8LSB4W3gkdGltZSA8PSA3MjAsIF0NCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHggPC0geFssLWMoMSwzKV0NCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGMoMSwgeCkNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfSkNCg0KY294X3RkX2Zvcl9pYnM3MjAgPC0gbGlzdChzdXJ2aXZhbC5wcm9icyA9IGNveF90ZF9wcmVkX2xpc3RfaWJzNzIwLA0KICAgICAgICAgICAgICAgICAgICAgICAgICBzdXJ2aXZhbC50aW1lcyA9IDA6NzIwLA0KICAgICAgICAgICAgICAgICAgICAgICAgICBzdXJ2aXZhbC50YXUgPSBwbWluKHRhdXMsIDcyMCkpDQoNCmRhdGEgPC0gZGF0YS5mcmFtZShkZl90ZXN0KQ0KU3Vydm9iaiA9IFN1cnYoZGF0YSR0aW1lX3N0YXJ0LCBkYXRhJHRpbWVfc3RvcCwgZGF0YSRjaHVybikNCmNveF9pYnNfdGRfcHJlZCA8LSBzYnJpZXJfbHRyYyhvYmogPSBTdXJ2b2JqLCBpZCA9IGRhdGEkc3Vic2NyX2lkLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHByZWQgPSBjb3hfdGRfZm9yX2liczcyMCwgdHlwZSA9ICJJQlMiKQ0KYGBgDQoNCmBgYHtyIHJzZl90ZH0gDQpmbWwgPSBhcy5mb3JtdWxhKHBhc3RlMCgiU3Vydih0aW1lID0gdGltZV9zdGFydCwgdGltZTIgPSB0aW1lX3N0b3AsIGV2ZW50ID0gY2h1cm4sIHR5cGUgPSAnY291bnRpbmcnKSB+ICIsDQogICAgICAgICAgICAgICAgICAgICAgICBwYXN0ZShmZWF0dXJlc19iYXNlbGluZSwgY29sbGFwc2UgPSAiICsgIiksDQogICAgICAgICAgICAgICAgICAgICAgICAiICsgIiwNCiAgICAgICAgICAgICAgICAgICAgICAgIHBhc3RlKGZlYXR1cmVzX3RpbWVkZXAsIGNvbGxhcHNlID0gIiArICIpKSkNCg0KcnJmX2ZpdCA8LSBsdHJjcnJmKGZvcm11bGEgPSBmbWwsIG50cmVlID0gMTAsIG10cnkgPSA2LA0KICAgICAgICAgICAgICAgICAgIGRhdGEgPSBkYXRhLmZyYW1lKGRmX3RyYWluKSwgDQogICAgICAgICAgICAgICAgICAgaWQgPSBzdWJzY3JfaWQpDQojIHRlc3QNCnJyZl9wcmVkIDwtIHByZWRpY3RQcm9iKG9iamVjdCA9IHJyZl9maXQsIA0KICAgICAgICAgICAgICAgICAgICAgICAgbmV3ZGF0YSA9IGRhdGEuZnJhbWUoZGZfdGVzdCksDQogICAgICAgICAgICAgICAgICAgICAgICBuZXdkYXRhLmlkID0gc3Vic2NyX2lkLA0KICAgICAgICAgICAgICAgICAgICAgICAgdGltZS5ldmFsID0gMDo3MjApDQpkYXRhIDwtIGRhdGEuZnJhbWUoZGZfdGVzdCkNClN1cnZvYmogPSBTdXJ2KGRhdGEkdGltZV9zdGFydCwgZGF0YSR0aW1lX3N0b3AsIGRhdGEkY2h1cm4pDQpycmZfcHJlZF9pYnMgPC0gc2JyaWVyX2x0cmMob2JqID0gU3Vydm9iaiwgaWQgPSBkYXRhJHN1YnNjcl9pZCwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgcHJlZCA9IHJyZl9wcmVkLCB0eXBlID0gIklCUyIpDQpycmZfcHJlZF9icyA8LSBzYnJpZXJfbHRyYyhvYmogPSBTdXJ2b2JqLCBpZCA9IGRhdGEkc3Vic2NyX2lkLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHByZWQgPSBycmZfcHJlZCwgdHlwZSA9ICJCUyIpDQoNCnJyZl9wcmVkODIxIDwtIHByZWRpY3RQcm9iKG9iamVjdCA9IHJyZl9maXQsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgbmV3ZGF0YSA9IGRhdGEuZnJhbWUoZGZfdGVzdCksDQogICAgICAgICAgICAgICAgICAgICAgICAgICBuZXdkYXRhLmlkID0gc3Vic2NyX2lkLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgdGltZS5ldmFsID0gMDo4MjEpDQpycmZfcHJlZF9wcm9iIDwtIHRpYmJsZShzdWJzY3JfaWQgPSByZXAoWF90ZXN0X3N1YnNjcl9pZCwgZWFjaCA9IDgyMiksDQogICAgICAgICAgICAgICAgICAgICAgICB0aW1lX3N0b3AgPSByZXAoMDo4MjEsIHRpbWVzID0gbGVuZ3RoKFhfdGVzdF9zdWJzY3JfaWQpKSwNCiAgICAgICAgICAgICAgICAgICAgICAgIHByZWRfcHJvYiA9IGFzLm51bWVyaWMocnJmX3ByZWQ4MjEkc3Vydml2YWwucHJvYnMpKQ0KcnJmX3ByZWRfcHJvYiA8LSBycmZfcHJlZF9wcm9iICU+JQ0KICByaWdodF9qb2luKGRmX3Rlc3QgJT4lIHNlbGVjdChzdWJzY3JfaWQsIHRpbWVfc3RvcCksIGJ5ID0gYygic3Vic2NyX2lkIiwgInRpbWVfc3RvcCIpKQ0KZGF0YSA8LSBkYXRhLmZyYW1lKGRmX3Rlc3QpDQpycmZfY29uYyA8LSBjb25jb3JkYW5jZWZpdChTdXJ2KGRhdGEkdGltZV9zdGFydCwgZGF0YSR0aW1lX3N0b3AsIGRhdGEkY2h1cm4pLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHggPSBycmZfcHJlZF9wcm9iICU+JSBwdWxsKHByZWRfcHJvYikpDQoNCiMgdHJhaW4NCnJyZl9wcmVkODIxX3RyIDwtIHByZWRpY3RQcm9iKG9iamVjdCA9IHJyZl9maXQsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbmV3ZGF0YSA9IGRhdGEuZnJhbWUoZGZfdHJhaW4pLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbmV3ZGF0YS5pZCA9IHN1YnNjcl9pZCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRpbWUuZXZhbCA9IDA6ODIxKQ0KcnJmX3ByZWRfdHJfcHJvYiA8LSB0aWJibGUoc3Vic2NyX2lkID0gcmVwKHVuaXF1ZShkZl90cmFpbiRzdWJzY3JfaWQpLCBlYWNoID0gODIyKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHRpbWVfc3RvcCA9IHJlcCgwOjgyMSwgdGltZXMgPSBsZW5ndGgodW5pcXVlKGRmX3RyYWluJHN1YnNjcl9pZCkpKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHByZWRfcHJvYiA9IGFzLm51bWVyaWMocnJmX3ByZWQ4MjFfdHIkc3Vydml2YWwucHJvYnMpKQ0KcnJmX3ByZWRfdHJfcHJvYiA8LSBycmZfcHJlZF90cl9wcm9iICU+JQ0KICByaWdodF9qb2luKGRmX3RyYWluICU+JSBzZWxlY3Qoc3Vic2NyX2lkLCB0aW1lX3N0b3ApLCBieSA9IGMoInN1YnNjcl9pZCIsICJ0aW1lX3N0b3AiKSkNCmRhdGEgPC0gZGF0YS5mcmFtZShkZl90cmFpbikNCnJyZl9jb25jX3RyIDwtIGNvbmNvcmRhbmNlZml0KFN1cnYoZGF0YSR0aW1lX3N0YXJ0LCBkYXRhJHRpbWVfc3RvcCwgZGF0YSRjaHVybiksIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeCA9IHJyZl9wcmVkX3RyX3Byb2IgJT4lIHB1bGwocHJlZF9wcm9iKSkNCg0KcnJmX3ByZWQ3MjBfdHIgPC0gcnJmX3ByZWQ4MjFfdHJbMjo0XQ0KcnJmX3ByZWQ3MjBfdHIkc3Vydml2YWwucHJvYnMgPC0gcnJmX3ByZWQ3MjBfdHIkc3Vydml2YWwucHJvYnNbMTo3MjEsXQ0KcnJmX3ByZWQ3MjBfdHIkc3Vydml2YWwudGltZXMgPC0gcnJmX3ByZWQ3MjBfdHIkc3Vydml2YWwudGltZXNbMTo3MjFdDQoNCmRhdGEgPC0gZGF0YS5mcmFtZShkZl90cmFpbikNClN1cnZvYmogPSBTdXJ2KGRhdGEkdGltZV9zdGFydCwgZGF0YSR0aW1lX3N0b3AsIGRhdGEkY2h1cm4pDQpycmZfcHJlZF9ic190ciA8LSBzYnJpZXJfbHRyYyhvYmogPSBTdXJ2b2JqLCBpZCA9IGRhdGEkc3Vic2NyX2lkLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHByZWQgPSBycmZfcHJlZDcyMF90ciwgdHlwZSA9ICJCUyIpDQpgYGANCg0KQ294IFBIIHdpdGggdGltZS1kZXBlbmRlbnQgY292YXJpYXRlcyBlbmFibGVkIGluY3JlYXNpbmcgSGFycmVsbOKAmXMgQy1pbmRleCB0byAwLjg3OCBvbiB0aGUgdHJhaW4gc2V0IGFuZCAwLjg3NiBvbiB0aGUgdGVzdCBzZXQgKGluIGNvbXBhcmlzb24gd2l0aCAwLjgxMSBhbmQgMC44MDksIGFjY29yZGluZ2x5LCBmb3IgQ294IFBIIHdpdGggYmFzZWxpbmUgdmFyaWFibGVzIGFuZCBkYXRhIG9uIHByZXZpb3VzIHN1YnNjcmlwdGlvbnMpLCBhbmQgZGVjcmVhc2luZyBJQlMgZnJvbSAwLjE0NSB0byAwLjEwOSBvbiB0aGUgdGVzdCBzZXQuDQoNCmBgYHtyIGNpbmRleF9hbGwsIGZpZy53aWR0aD02LCBmaWcuaGVpZ2h0PTR9DQpjaW5kZXhfYWxsIDwtIHJiaW5kKGNpbmRleCAlPiUNCiAgZmlsdGVyKE1ldHJpYyA9PSAiSGFycmVsbGBzIEMiKSwNCiAgdGliYmxlKE1ldHJpYyA9IHJlcCgiSGFycmVsbGBzIEMiLCA0KSwNCiAgICAgICAgIHZhbHVlID0gYygwLjg3OCwwLjkxNywwLjg3NiwwLjg4NyksDQogICAgICAgICBtb2RlbCA9IGMoIkNveCBQSCB0aW1lLWRlcCIsICJSU0YgdGltZS1kZXAiLCAiQ294IFBIIHRpbWUtZGVwIiwgIlJTRiB0aW1lLWRlcCIpLA0KICAgICAgICAgc2V0ID0gYygiVHJhaW4iLCAiVHJhaW4iLCAiVGVzdCIsICJUZXN0IikpKSAlPiUNCiAgbXV0YXRlKGdyb3VwID0gZmFjdG9yKGdyZXBsKCJSU0YiLCBtb2RlbCksIGxhYmVscyA9IGMoIkNveCBQSCIsICJSU0YiKSksDQogICAgICAgICBzcGVjID0gZmFjdG9yKGlmZWxzZShncmVwbCgibGluZSIsIG1vZGVsKSwgMSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmZWxzZShncmVwbCgicHJldiIsIG1vZGVsKSwgMiwgMykpLA0KICAgICAgICAgICAgICAgICAgICAgICAxOjMsIGMoIkJhc2VsaW5lIiwgIlByZXYiLCAiVGltZURlcCIpKSkNCg0KZ2dwbG90KCkgKw0KICBnZW9tX2JhcihhZXMoeD1zcGVjLCB5PXZhbHVlLCBmaWxsPW1vZGVsKSwgY2luZGV4X2FsbCwNCiAgICAgICAgICAgc3RhdCA9ICJpZGVudGl0eSIsIA0KICAgICAgICAgICBwb3NpdGlvbiA9IHBvc2l0aW9uX2RvZGdlKDAuOCksIGNvbG9yID0gIndoaXRlIikgKw0KICBnZW9tX3RleHQoYWVzKHg9c3BlYywgeT15LCBsYWJlbD1sYmwsIGdyb3VwPW1vZGVsKSwgDQogICAgICAgICAgICBjaW5kZXhfYWxsICU+JSBtdXRhdGUoeSA9IHZhbHVlICsgMC4wNiwgbGJsID0gcm91bmQodmFsdWUsMikpLA0KICAgICAgICAgICAgcG9zaXRpb24gPSBwb3NpdGlvbl9kb2RnZSgwLjgpLCBzdGF0ID0gImlkZW50aXR5IiwNCiAgICAgICAgICAgIHNpemUgPSA0LCBmb250ZmFjZSA9ICJib2xkIikgKw0KICBmYWNldF9yZXBfZ3JpZChzZXQgfiBncm91cCwgc3dpdGNoID0gInkiLCByZXBlYXQudGljay5sYWJlbHMgPSBUUlVFKSArDQogIHNjYWxlX3lfY29udGludW91cyhleHBhbmQgPSBjKDAsMCwwLDAuMDIpLCBsaW1pdHMgPSBjKDAsMSkpICsNCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gcGFsZXR0ZWVyX2QoImdndGhlbWVzOjpIdWVfQ2lyY2xlIilbYygxNzoxOSwgMTA6MTIpXSkgKw0KICBsYWJzKHg9IiIsIHk9IiIsIGZpbGw9IiIsIHRpdGxlPSJIYXJyZWxsJ3MgQy1pbmRleCIsIGNvbG9yPSIiKSArDQogIGdndGhlbWVzOjp0aGVtZV90dWZ0ZSgpICsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiLA0KICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIsIGhqdXN0ID0gMC41KSwNCiAgICAgICAgYXhpcy50aWNrcy54ID0gZWxlbWVudF9ibGFuaygpLA0KICAgICAgICBzdHJpcC50ZXh0ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgZmFjZSA9ICJib2xkIiksDQogICAgICAgIGF4aXMubGluZSA9IGVsZW1lbnRfbGluZShzaXplID0gLjIsIGNvbG9yID0gImdyZXkyMCIpLA0KICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIsIGZhY2UgPSAiYm9sZCIsIGNvbG9yID0gImJsYWNrIiksDQogICAgICAgIHN0cmlwLmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoZmlsbCA9IE5BKSwNCiAgICAgICAgc3RyaXAucGxhY2VtZW50ID0gIm91dHNpZGUiLA0KICAgICAgICBwYW5lbC5zcGFjaW5nLnk9dW5pdCgxLCAibGluZXMiKSkNCmBgYA0KDQpgYGB7ciBjaW5kZXhfYWxsX3Bsb3QsIGZpZy53aWR0aD02LCBmaWcuaGVpZ2h0PTQsIGV2YWw9VFJVRX0NCmNpbmRleF9hbGwgPC0gcmJpbmQoY2luZGV4ICU+JQ0KICBmaWx0ZXIoTWV0cmljID09ICJIYXJyZWxsYHMgQyIpLA0KICB0aWJibGUoTWV0cmljID0gcmVwKCJIYXJyZWxsYHMgQyIsIDQpLA0KICAgICAgICAgdmFsdWUgPSBjKDAuODc4LDAuOTE3LDAuODc2LDAuODg3KSwNCiAgICAgICAgIG1vZGVsID0gYygiQ294IFBIIHRpbWUtZGVwIiwgIlJTRiB0aW1lLWRlcCIsICJDb3ggUEggdGltZS1kZXAiLCAiUlNGIHRpbWUtZGVwIiksDQogICAgICAgICBzZXQgPSBjKCJUcmFpbiIsICJUcmFpbiIsICJUZXN0IiwgIlRlc3QiKSkpICU+JQ0KICBtdXRhdGUoZ3JvdXAgPSBmYWN0b3IoZ3JlcGwoIlJTRiIsIG1vZGVsKSwgbGFiZWxzID0gYygiQ294IFBIIiwgIlJTRiIpKSwNCiAgICAgICAgIHNwZWMgPSBmYWN0b3IoaWZlbHNlKGdyZXBsKCJsaW5lIiwgbW9kZWwpLCAxLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWZlbHNlKGdyZXBsKCJwcmV2IiwgbW9kZWwpLCAyLCAzKSksDQogICAgICAgICAgICAgICAgICAgICAgIDE6MywgYygiQmFzZWxpbmUiLCAiUHJldiIsICJUaW1lRGVwIikpKQ0KDQpnZ3Bsb3QoKSArDQogIGdlb21fYmFyKGFlcyh4PXNwZWMsIHk9dmFsdWUsIGZpbGw9bW9kZWwpLCBjaW5kZXhfYWxsLA0KICAgICAgICAgICBzdGF0ID0gImlkZW50aXR5IiwgDQogICAgICAgICAgIHBvc2l0aW9uID0gcG9zaXRpb25fZG9kZ2UoMC44KSwgY29sb3IgPSAid2hpdGUiKSArDQogIGdlb21fdGV4dChhZXMoeD1zcGVjLCB5PXksIGxhYmVsPWxibCwgZ3JvdXA9bW9kZWwpLCANCiAgICAgICAgICAgIGNpbmRleF9hbGwgJT4lIG11dGF0ZSh5ID0gdmFsdWUgKyAwLjA2LCBsYmwgPSByb3VuZCh2YWx1ZSwyKSksDQogICAgICAgICAgICBwb3NpdGlvbiA9IHBvc2l0aW9uX2RvZGdlKDAuOCksIHN0YXQgPSAiaWRlbnRpdHkiLA0KICAgICAgICAgICAgc2l6ZSA9IDQsIGZvbnRmYWNlID0gImJvbGQiKSArDQogIGZhY2V0X3JlcF9ncmlkKHNldCB+IGdyb3VwLCBzd2l0Y2ggPSAieSIsIHJlcGVhdC50aWNrLmxhYmVscyA9IFRSVUUpICsNCiAgc2NhbGVfeV9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwwLDAsMC4wMiksIGxpbWl0cyA9IGMoMCwxKSkgKw0KICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBwYWxldHRlZXJfZCgiZ2d0aGVtZXM6Okh1ZV9DaXJjbGUiKVtjKDE3OjE5LCAxMDoxMildKSArDQogIGxhYnMoeD0iIiwgeT0iIiwgZmlsbD0iIiwgdGl0bGU9IkhhcnJlbGwncyBDLWluZGV4IiwgY29sb3I9IiIpICsNCiAgZ2d0aGVtZXM6OnRoZW1lX3R1ZnRlKCkgKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIsDQogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiwgaGp1c3QgPSAwLjUpLA0KICAgICAgICBheGlzLnRpY2tzLnggPSBlbGVtZW50X2JsYW5rKCksDQogICAgICAgIHN0cmlwLnRleHQgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSwNCiAgICAgICAgYXhpcy5saW5lID0gZWxlbWVudF9saW5lKHNpemUgPSAuMiwgY29sb3IgPSAiZ3JleTIwIiksDQogICAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgZmFjZSA9ICJib2xkIiwgY29sb3IgPSAiYmxhY2siKSwNCiAgICAgICAgc3RyaXAuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gTkEpLA0KICAgICAgICBzdHJpcC5wbGFjZW1lbnQgPSAib3V0c2lkZSIsDQogICAgICAgIHBhbmVsLnNwYWNpbmcueT11bml0KDEsICJsaW5lcyIpKQ0KYGBgDQoNCmBgYHtyIGlic19hbGwsIGZpZy53aWR0aD02LCBmaWcuaGVpZ2h0PTMsIGluY2x1ZGU9RkFMU0UsIGV2YWw9RkFMU0V9DQppYnNfYWxsIDwtIHJiaW5kKGlicywNCiAgdGliYmxlKG1vZGVsID0gYygiQ294IFBIIHRpbWUtZGVwIiwgIlJTRiB0aW1lLWRlcCIpLA0KICAgICAgICAgdmFsdWUgPSBjKDAuMTA5LCAwLjA5NCkpKSAlPiUNCiAgbXV0YXRlKGdyb3VwID0gZmFjdG9yKGdyZXBsKCJSU0YiLCBtb2RlbCksIGxhYmVscyA9IGMoIkNveCBQSCIsICJSU0YiKSksDQogICAgICAgICBzcGVjID0gZmFjdG9yKGlmZWxzZShncmVwbCgibGluZSIsIG1vZGVsKSwgMSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmZWxzZShncmVwbCgicHJldiIsIG1vZGVsKSwgMiwgMykpLA0KICAgICAgICAgICAgICAgICAgICAgICAxOjMsIGMoIkJhc2VsaW5lIiwgIlByZXYiLCAiVGltZURlcCIpKSwNCiAgICAgICAgIHNldCA9ICJUZXN0IikNCg0KZ2dwbG90KCkgKw0KICBnZW9tX2JhcihhZXMoeD1zcGVjLCB5PXZhbHVlLCBmaWxsPW1vZGVsKSwgaWJzX2FsbCwNCiAgICAgICAgICAgc3RhdCA9ICJpZGVudGl0eSIsIA0KICAgICAgICAgICBwb3NpdGlvbiA9IHBvc2l0aW9uX2RvZGdlKDAuOCksIGNvbG9yID0gIndoaXRlIikgKw0KICBnZW9tX3RleHQoYWVzKHg9c3BlYywgeT15LCBsYWJlbD1sYmwsIGdyb3VwPW1vZGVsKSwgDQogICAgICAgICAgICBpYnNfYWxsICU+JSBtdXRhdGUoeSA9IHZhbHVlICsgMC4wMSwgbGJsID0gcm91bmQodmFsdWUsMykpLA0KICAgICAgICAgICAgcG9zaXRpb24gPSBwb3NpdGlvbl9kb2RnZSgwLjgpLCBzdGF0ID0gImlkZW50aXR5IiwNCiAgICAgICAgICAgIHNpemUgPSA0LCBmb250ZmFjZSA9ICJib2xkIikgKw0KICBmYWNldF9yZXBfZ3JpZChzZXQgfiBncm91cCwgcmVwZWF0LnRpY2subGFiZWxzID0gVFJVRSwgc3dpdGNoID0gInkiKSArDQogIHNjYWxlX3lfY29udGludW91cyhleHBhbmQgPSBjKDAsMCwwLDAuMDEpKSArDQogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IHBhbGV0dGVlcl9kKCJnZ3RoZW1lczo6SHVlX0NpcmNsZSIpW2MoMTc6MTksIDEwOjEyKV0pICsNCiAgbGFicyh4PSIiLCB5PSIiLCBmaWxsPSIiLCB0aXRsZT0iSW50ZWdyYXRlZCBCcmllciBTY29yZSwgZGF5cyAxLTcyMCIsIGNvbG9yPSIiKSArDQogIGdndGhlbWVzOjp0aGVtZV90dWZ0ZSgpICsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiLA0KICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIsIGhqdXN0ID0gMC41KSwNCiAgICAgICAgYXhpcy50aWNrcy54ID0gZWxlbWVudF9ibGFuaygpLA0KICAgICAgICBzdHJpcC50ZXh0ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgZmFjZSA9ICJib2xkIiksDQogICAgICAgIGF4aXMubGluZSA9IGVsZW1lbnRfbGluZShzaXplID0gLjIsIGNvbG9yID0gImdyZXkyMCIpLA0KICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIsIGZhY2UgPSAiYm9sZCIsIGNvbG9yID0gImJsYWNrIiksDQogICAgICAgIHN0cmlwLmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoZmlsbCA9IE5BKSwNCiAgICAgICAgc3RyaXAucGxhY2VtZW50ID0gIm91dHNpZGUiLA0KICAgICAgICBwYW5lbC5zcGFjaW5nLnk9dW5pdCgxLCAibGluZXMiKSkNCmBgYA0KDQpgYGB7ciBpYnNfYWxsX3Bsb3QsIGZpZy53aWR0aD02LCBmaWcuaGVpZ2h0PTMsIGV2YWw9VFJVRX0NCmlic19hbGwgPC0gcmJpbmQoaWJzLA0KICB0aWJibGUobW9kZWwgPSBjKCJDb3ggUEggdGltZS1kZXAiLCAiUlNGIHRpbWUtZGVwIiksDQogICAgICAgICB2YWx1ZSA9IGMoMC4xMDksIDAuMDk0KSkpICU+JQ0KICBtdXRhdGUoZ3JvdXAgPSBmYWN0b3IoZ3JlcGwoIlJTRiIsIG1vZGVsKSwgbGFiZWxzID0gYygiQ294IFBIIiwgIlJTRiIpKSwNCiAgICAgICAgIHNwZWMgPSBmYWN0b3IoaWZlbHNlKGdyZXBsKCJsaW5lIiwgbW9kZWwpLCAxLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWZlbHNlKGdyZXBsKCJwcmV2IiwgbW9kZWwpLCAyLCAzKSksDQogICAgICAgICAgICAgICAgICAgICAgIDE6MywgYygiQmFzZWxpbmUiLCAiUHJldiIsICJUaW1lRGVwIikpLA0KICAgICAgICAgc2V0ID0gIlRlc3QiKQ0KDQpnZ3Bsb3QoKSArDQogIGdlb21fYmFyKGFlcyh4PXNwZWMsIHk9dmFsdWUsIGZpbGw9bW9kZWwpLCBpYnNfYWxsLA0KICAgICAgICAgICBzdGF0ID0gImlkZW50aXR5IiwgDQogICAgICAgICAgIHBvc2l0aW9uID0gcG9zaXRpb25fZG9kZ2UoMC44KSwgY29sb3IgPSAid2hpdGUiKSArDQogIGdlb21fdGV4dChhZXMoeD1zcGVjLCB5PXksIGxhYmVsPWxibCwgZ3JvdXA9bW9kZWwpLCANCiAgICAgICAgICAgIGlic19hbGwgJT4lIG11dGF0ZSh5ID0gdmFsdWUgKyAwLjAxLCBsYmwgPSByb3VuZCh2YWx1ZSwzKSksDQogICAgICAgICAgICBwb3NpdGlvbiA9IHBvc2l0aW9uX2RvZGdlKDAuOCksIHN0YXQgPSAiaWRlbnRpdHkiLA0KICAgICAgICAgICAgc2l6ZSA9IDQsIGZvbnRmYWNlID0gImJvbGQiKSArDQogIGZhY2V0X3JlcF9ncmlkKHNldCB+IGdyb3VwLCByZXBlYXQudGljay5sYWJlbHMgPSBUUlVFLCBzd2l0Y2ggPSAieSIpICsNCiAgc2NhbGVfeV9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwwLDAsMC4wMSkpICsNCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gcGFsZXR0ZWVyX2QoImdndGhlbWVzOjpIdWVfQ2lyY2xlIilbYygxNzoxOSwgMTA6MTIpXSkgKw0KICBsYWJzKHg9IiIsIHk9IiIsIGZpbGw9IiIsIHRpdGxlPSJJbnRlZ3JhdGVkIEJyaWVyIFNjb3JlLCBkYXlzIDEtNzIwIiwgY29sb3I9IiIpICsNCiAgZ2d0aGVtZXM6OnRoZW1lX3R1ZnRlKCkgKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIsDQogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiwgaGp1c3QgPSAwLjUpLA0KICAgICAgICBheGlzLnRpY2tzLnggPSBlbGVtZW50X2JsYW5rKCksDQogICAgICAgIHN0cmlwLnRleHQgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSwNCiAgICAgICAgYXhpcy5saW5lID0gZWxlbWVudF9saW5lKHNpemUgPSAuMiwgY29sb3IgPSAiZ3JleTIwIiksDQogICAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgZmFjZSA9ICJib2xkIiwgY29sb3IgPSAiYmxhY2siKSwNCiAgICAgICAgc3RyaXAuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gTkEpLA0KICAgICAgICBzdHJpcC5wbGFjZW1lbnQgPSAib3V0c2lkZSIsDQogICAgICAgIHBhbmVsLnNwYWNpbmcueT11bml0KDEsICJsaW5lcyIpKQ0KYGBgDQoNCmBgYHtyIGJzX2FsbCwgZmlnLndpZHRoPTcsIGZpZy5oZWlnaHQ9NH0NCmJzIDwtIGxpc3QoQ294UEhfYmFzZSA9IHJlYWQuY3N2KCJjb3hfYmFzZV9icy5jc3YiLCBzZXA9IjsiKSwNCiAgICAgICAgICAgQ294UEggPSByZWFkLmNzdigiY294X2JzLmNzdiIsIHNlcD0iOyIpLA0KICAgICAgICAgICBSU0ZfYmFzZSA9IHJlYWQuY3N2KCJyc2ZfYmFzZV9icy5jc3YiLCBzZXA9IjsiKSwNCiAgICAgICAgICAgUlNGID0gcmVhZC5jc3YoInJzZl9icy5jc3YiLCBzZXA9IjsiKSkNCmJzIDwtIGxhcHBseShicywgDQogICAgICAgICAgICAgZnVuY3Rpb24oeCkgew0KICAgICAgICAgICAgICAgeFssMl0gPC0gYXMubnVtZXJpYyhnc3ViKCIsIiwiLiIsIHhbLDJdKSkNCiAgICAgICAgICAgICAgIHhbLDNdIDwtIGFzLm51bWVyaWMoZ3N1YigiLCIsIi4iLCB4WywzXSkpDQogICAgICAgICAgICAgICB4fSkNCmJzIDwtIGRvLmNhbGwoZHBseXI6OmJpbmRfcm93cywgYnMpDQoNCmJzJG1vZGVsIDwtIHJlcChjKCJDb3ggUEggYmFzZWxpbmUiLCAiQ294IFBIIHByZXYiLCAiUlNGIGJhc2VsaW5lIiwgIlJTRiBwcmV2IiksIGVhY2g9OCkNCmJzIDwtIHJiaW5kKGJzWyxjKDE6Miw0KV0gJT4lIHJlbmFtZSh2YWx1ZT1UcmFpbikgJT4lIG11dGF0ZShzZXQ9IlRyYWluIiksIA0KICAgICAgICAgICAgICAgIGJzWyxjKDEsMzo0KV0gJT4lIHJlbmFtZSh2YWx1ZT1UZXN0KSAlPiUgbXV0YXRlKHNldD0iVGVzdCIpKQ0KDQpic19yc2ZfdGQgPC0gcmVhZC5jc3YoInJyZl90ZF9icy5jc3YiKQ0KYnNfcnNmX3RkIDwtIHJiaW5kKGJzX3JzZl90ZCAlPiUNCiAgICAgICAgICAgICAgICAgICAgIGZpbHRlcihUaW1lICVpbiUgYyg3LDE0LDMwLDYwLDkwLDE4MCwzNjAsNzIwKSkgJT4lDQogICAgICAgICAgICAgICAgICAgICByZW5hbWUodmFsdWUgPSBCU2NvcmUpICU+JQ0KICAgICAgICAgICAgICAgICAgICAgbXV0YXRlKG1vZGVsID0gIlJTRiB0aW1lLWRlcCIsIHNldCA9ICJUZXN0IikgJT4lDQogICAgICAgICAgICAgICAgICAgICBzZWxlY3QoLXJyZl9wcmVkX2JzX3RyLi4uMi4pLA0KICAgICAgICAgICAgICAgICAgIGJzX3JzZl90ZCAlPiUNCiAgICAgICAgICAgICAgICAgICAgIGZpbHRlcihUaW1lICVpbiUgYyg3LDE0LDMwLDYwLDkwLDE4MCwzNjAsNzIwKSkgJT4lDQogICAgICAgICAgICAgICAgICAgICByZW5hbWUodmFsdWUgPSBycmZfcHJlZF9ic190ci4uLjIuKSAlPiUNCiAgICAgICAgICAgICAgICAgICAgIG11dGF0ZShtb2RlbCA9ICJSU0YgdGltZS1kZXAiLCBzZXQgPSAiVHJhaW4iKSAlPiUNCiAgICAgICAgICAgICAgICAgICAgIHNlbGVjdCgtQlNjb3JlKSkNCiAgICAgICAgICAgICAgICAgICANCmJzX2FsbCA8LSByYmluZChicywgYnNfcnNmX3RkKSAgJT4lDQogIG11dGF0ZShncm91cCA9IGZhY3RvcihncmVwbCgiUlNGIiwgbW9kZWwpLCBsYWJlbHMgPSBjKCJDb3ggUEgiLCAiUlNGIikpLA0KICAgICAgICAgc3BlYyA9IGZhY3RvcihpZmVsc2UoZ3JlcGwoImxpbmUiLCBtb2RlbCksIDEsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZmVsc2UoZ3JlcGwoInByZXYiLCBtb2RlbCksIDIsIDMpKSwNCiAgICAgICAgICAgICAgICAgICAgICAgMTozLCBjKCJCYXNlbGluZSIsICJQcmV2IiwgIlRpbWVEZXAiKSkpDQoNCmdncGxvdCgpICsNCiAgZ2VvbV9saW5lKGFlcyh4PVRpbWUsIHk9dmFsdWUsIGNvbG9yPW1vZGVsKSwgYnNfYWxsLCBzaXplID0gMSkgKw0KICBnZW9tX3BvaW50KGFlcyh4PVRpbWUsIHk9dmFsdWUsIGNvbG9yPW1vZGVsKSwgYnNfYWxsLCBzaXplID0gMiwgc2hvdy5sZWdlbmQgPSBGQUxTRSkgKw0KICBmYWNldF9yZXBfZ3JpZCh+IHNldCwgc3dpdGNoID0gInkiLCByZXBlYXQudGljay5sYWJlbHMgPSBUUlVFKSArDQogIHNjYWxlX3lfY29udGludW91cyhleHBhbmQgPSBjKDAsMCksIGxpbWl0cyA9IGMoMCwgMC4xOCkpICsNCiAgc2NhbGVfeF9jb250aW51b3VzKGV4cGFuZCA9IGMoMC4wMiwwKSwgYnJlYWtzID0gc2VxKDAsNzIwLDkwKSkgKw0KICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gcGFsZXR0ZWVyX2QoImdndGhlbWVzOjpIdWVfQ2lyY2xlIilbYygxNzoxOCwgMTA6MTIpXSkgKw0KICBsYWJzKHg9IkRheXMiLCB5PSIiLCBmaWxsPSIiLCB0aXRsZT0iVGltZS1kZXBlbmRlbnQgQnJpZXItc2NvcmUiLCBjb2xvcj0iIikgKw0KICBndWlkZXMoY29sb3IgPSBndWlkZV9sZWdlbmQob3ZlcnJpZGUuYWVzID0gbGlzdChzaXplID0gNSkpKSArDQogIGdndGhlbWVzOjp0aGVtZV90dWZ0ZSgpICsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIsDQogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiwgaGp1c3QgPSAwLjUpLA0KICAgICAgICBzdHJpcC50ZXh0ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgZmFjZSA9ICJib2xkIiksDQogICAgICAgIGF4aXMubGluZSA9IGVsZW1lbnRfbGluZShzaXplID0gLjIsIGNvbG9yID0gImdyZXkyMCIpLA0KICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApLA0KICAgICAgICBzdHJpcC5iYWNrZ3JvdW5kID0gZWxlbWVudF9yZWN0KGZpbGwgPSBOQSksDQogICAgICAgIHN0cmlwLnBsYWNlbWVudCA9ICJvdXRzaWRlIiwNCiAgICAgICAgcGFuZWwuc3BhY2luZy55PXVuaXQoMSwgImxpbmVzIikpDQpgYGANCg0KYGBge3IgYnNfYWxsX3Bsb3QsIGZpZy53aWR0aD03LCBmaWcuaGVpZ2h0PTR9DQpic19yc2ZfdGQgPC0gcmVhZC5jc3YoInJyZl90ZF9icy5jc3YiKQ0KYnNfcnNmX3RkIDwtIHJiaW5kKGJzX3JzZl90ZCAlPiUNCiAgICAgICAgICAgICAgICAgICAgIGZpbHRlcihUaW1lICVpbiUgYyg3LDE0LDMwLDYwLDkwLDE4MCwzNjAsNzIwKSkgJT4lDQogICAgICAgICAgICAgICAgICAgICByZW5hbWUodmFsdWUgPSBCU2NvcmUpICU+JQ0KICAgICAgICAgICAgICAgICAgICAgbXV0YXRlKG1vZGVsID0gIlJTRiB0aW1lLWRlcCIsIHNldCA9ICJUZXN0IikgJT4lDQogICAgICAgICAgICAgICAgICAgICBzZWxlY3QoLXJyZl9wcmVkX2JzX3RyLi4uMi4pLA0KICAgICAgICAgICAgICAgICAgIGJzX3JzZl90ZCAlPiUNCiAgICAgICAgICAgICAgICAgICAgIGZpbHRlcihUaW1lICVpbiUgYyg3LDE0LDMwLDYwLDkwLDE4MCwzNjAsNzIwKSkgJT4lDQogICAgICAgICAgICAgICAgICAgICByZW5hbWUodmFsdWUgPSBycmZfcHJlZF9ic190ci4uLjIuKSAlPiUNCiAgICAgICAgICAgICAgICAgICAgIG11dGF0ZShtb2RlbCA9ICJSU0YgdGltZS1kZXAiLCBzZXQgPSAiVHJhaW4iKSAlPiUNCiAgICAgICAgICAgICAgICAgICAgIHNlbGVjdCgtQlNjb3JlKSkNCiAgICAgICAgICAgICAgICAgICANCmJzX2FsbCA8LSByYmluZChicywgYnNfcnNmX3RkKSAgJT4lDQogIG11dGF0ZShncm91cCA9IGZhY3RvcihncmVwbCgiUlNGIiwgbW9kZWwpLCBsYWJlbHMgPSBjKCJDb3ggUEgiLCAiUlNGIikpLA0KICAgICAgICAgc3BlYyA9IGZhY3RvcihpZmVsc2UoZ3JlcGwoImxpbmUiLCBtb2RlbCksIDEsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZmVsc2UoZ3JlcGwoInByZXYiLCBtb2RlbCksIDIsIDMpKSwNCiAgICAgICAgICAgICAgICAgICAgICAgMTozLCBjKCJCYXNlbGluZSIsICJQcmV2IiwgIlRpbWVEZXAiKSkpDQoNCmdncGxvdCgpICsNCiAgZ2VvbV9saW5lKGFlcyh4PVRpbWUsIHk9dmFsdWUsIGNvbG9yPW1vZGVsKSwgYnNfYWxsLCBzaXplID0gMSkgKw0KICBnZW9tX3BvaW50KGFlcyh4PVRpbWUsIHk9dmFsdWUsIGNvbG9yPW1vZGVsKSwgYnNfYWxsLCBzaXplID0gMiwgc2hvdy5sZWdlbmQgPSBGQUxTRSkgKw0KICBmYWNldF9yZXBfZ3JpZCh+IHNldCwgc3dpdGNoID0gInkiLCByZXBlYXQudGljay5sYWJlbHMgPSBUUlVFKSArDQogIHNjYWxlX3lfY29udGludW91cyhleHBhbmQgPSBjKDAsMCksIGxpbWl0cyA9IGMoMCwgMC4xOCkpICsNCiAgc2NhbGVfeF9jb250aW51b3VzKGV4cGFuZCA9IGMoMC4wMiwwKSwgYnJlYWtzID0gc2VxKDAsNzIwLDkwKSkgKw0KICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gcGFsZXR0ZWVyX2QoImdndGhlbWVzOjpIdWVfQ2lyY2xlIilbYygxNzoxOCwgMTA6MTIpXSkgKw0KICBsYWJzKHg9IkRheXMiLCB5PSIiLCBmaWxsPSIiLCB0aXRsZT0iVGltZS1kZXBlbmRlbnQgQnJpZXItc2NvcmUiLCBjb2xvcj0iIikgKw0KICBndWlkZXMoY29sb3IgPSBndWlkZV9sZWdlbmQob3ZlcnJpZGUuYWVzID0gbGlzdChzaXplID0gNSkpKSArDQogIGdndGhlbWVzOjp0aGVtZV90dWZ0ZSgpICsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIsDQogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiwgaGp1c3QgPSAwLjUpLA0KICAgICAgICBzdHJpcC50ZXh0ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgZmFjZSA9ICJib2xkIiksDQogICAgICAgIGF4aXMubGluZSA9IGVsZW1lbnRfbGluZShzaXplID0gLjIsIGNvbG9yID0gImdyZXkyMCIpLA0KICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApLA0KICAgICAgICBzdHJpcC5iYWNrZ3JvdW5kID0gZWxlbWVudF9yZWN0KGZpbGwgPSBOQSksDQogICAgICAgIHN0cmlwLnBsYWNlbWVudCA9ICJvdXRzaWRlIiwNCiAgICAgICAgcGFuZWwuc3BhY2luZy55PXVuaXQoMSwgImxpbmVzIikpDQpgYGANCg0KPGJyPg0KDQpUaGUgb3RoZXIgYWR2YW50YWdlIG9mIHRoZSBtb2RlbHMgd2l0aCB0aW1lLWRlcGVuZGVudCBjb3ZhcmlhdGVzIGlzIHRoYXQgdGhleSBhbGxvdyB0byB1cGRhdGUgc3Vydml2YWwgcHJlZGljdGlvbnMgYWZ0ZXIgdGhlIHN0YXJ0IG9mIHN1YnNjcmlwdGlvbiBwZXJpb2QgdXNpbmcgYWxsIG5ldyBpbmZvcm1hdGlvbiBvbiB1c2VyJ3MgYmVoYXZpb3VyLg0KDQpGb3IgZXhhbXBsZSwgdXNpbmcgcmVzdWx0cyBmb3IgQ294IFBIIHJlZ3Jlc3Npb24gd2l0aCB0aW1lLWRlcGVuZGVudCBjb3ZhcmlhdGVzIHdlIGNhbiBlc3RpbWF0ZSAidXBkYXRlZCAic3Vydml2YWwgY3VydmUgLSBpdCByZWZsZWN0cyBzdXJ2aXZhbCBkaXN0cmlidXRpb24gcHJlZGljdGVkIHVzaW5nIGFsbCBkYXRhIG9idGFpbmVkIG9uIHRoaXMgc3Vic2NyaXB0aW9uIHVwIHRvIHRoZSBlbmQgb2YgdGhlIGNvcnJlc3BvbmRpbmcgc3Vic2NyaXB0aW9uIHBlcmlvZC4gSW4gYWRkaXRpb24gd2UgcHJlZGljdGVkIHN1cnZpdmFsIGN1cnZlIGF0IHRoZSB6ZXJvIHBvaW50IChiYXNlbGluZSksIGFzc3VtaW5nIHRoYXQgYSB1c2VyIGhhZCBqdXN0IHN1YnNjcmliZWQgdG8gdGhlIHNlcnZpY2UgYW5kIGhhZCBub3QgeWV0IGxpc3RlbmVkIHRvIG11c2ljLCBzbyB3ZSBoYXZlIG9ubHkgbWVtYmVyJ3MgY2hhcmFjdGVyaXN0aWNzIGFuZCBkYXRhIG9uIGhpcy9oZXIgZmlyc3QgdHJhbnNhY3Rpb24sIG51bWJlciBvZiBwcmV2aW91cyBjaHVybnMgYW5kIHRpbWUgZnJvbSBwcmV2aW91cyBleHBpcmF0aW9uIGRhdGUuIEFmdGVyIHRoYXQgd2UgdG9vayB0aGUgZmlyc3QgdGltZSBpbnRlcnZhbCBpbiBzdWJzY3JpcHRpb24gYW5kIHNldmVyYWwgaW50ZXJtZWRpYXRlIHBvaW50cywgYW5kIHByZWRpY3Qgc3Vydml2YWwgd2l0aCBhbGwgZGF0YSBvbiB1c2VyJ3MgYmVoYXZpb3Igb2J0YWluZWQgdXAgdG8gdGhhdCBwb2ludC4gRm9yIGJhc2VsaW5lIGFuZCBvdGhlciB0aW1lIHBvaW50ICJmdXR1cmUiIHBhcnRzIG9mIHByZWRpY3RlZCBzdXJ2aXZhbCBjdXJ2ZXMgYXJlIHNob3duLiBGb3IgdGhlIGxhc3QgYXZhaWxhYmxlIGZvciB0aGF0IHN1YnNjcmlwdGlvbiB0aW1lIHBvaW50IGEgImZ1dHVyZSIgcGFydCBpcyBhIHByb2xvbmdhdGlvbiBvZiB0aGUgJ3VwZGF0ZWQnIHN1cnZpdmFsIGN1cnZlLg0KDQo8ZGl2IHN0eWxlPSJ3aWR0aDo2MDBweDsgaGVpZ2h0OjQwMHB4Ij4NCiFbRXhhbXBsZSBvZiB1cGRhdGluZyBzdXJ2aXZhbCBjdXJ2ZSBmb3IgYSByYW5kb20gdXNlcl0oRDovTXlEb2N1bWVudHMvS2FnZ2xlL0trYm94L3Rlc3RfcGxvdHNfMTY3MzkuZ2lmKQ0KPC9kaXY+DQoNCjxicj4NCg0KWW91IGNhbiBzZWUgdGhhdCB3aXRoIG5ldyBkYXRhIGJlaW5nIG9idGFpbmVkIHByZWRpY3RlZCBzdXJ2aXZhbCBjdXJ2ZSBpcyBhcHByb2FjaGluZyB0byB0aGUgInVwZGF0ZWQiIG9uZSwgd2hpbGUgYXQgdGhlIGJlZ2lubmluZyBpdCBjb3VsZCBiZSB0b28gcGVzc2ltaXN0aWMgb3IgdG9vIG9wdGltaXN0aWMuDQoNCkFsc28gd2UgY2FuIHBsb3Qgc3Vydml2YWwgY3VydmVzIG5vdCBmcm9tIHplcm8gdGltZSBwb2ludCwgYnV0IGZyb20gYW55IHRpbWUgY29uZGl0aW9uYWwgb24gc3Vydml2aW5nIHRvIHRoaXMgcG9pbnQsIHNvIHdlIGNhbiBwcmVkaWN0IHN1cnZpdmFsIGZvciBzb21lIG5leHQgcGVyaW9kIG91dCBvZiAxMDAlIG9mIHRob3NlIGN1c3RvbWVycyB3aG8gcmVtYWluZWQgYWN0aXZlLiBGb3IgZXhhbXBsZSwgZm9yIHRoZSBmaXJzdCBvZiBhYm92ZSB1c2VycyBpdCBjb3VsZCBiZSBsaWtlIHRoZSBmb2xsb3dpbmc6DQoNCmBgYHtyIGNvbmRfc3Vydn0NCm5ld2RhdGEgPC0gZGZfdGVzdCAlPiUNCiAgZmlsdGVyKHN1YnNjcl9pZCA9PSAxNjczOSkNCm5ld2RhdGEgPC0gZGF0YS5mcmFtZShuZXdkYXRhICU+JSBzZWxlY3QoLXN1YnNjcl9pZCksIHJvdy5uYW1lcyA9IE5VTEwpDQpvdXQgPC0gc3VydmZpdChjb3hfdGQsIG5ld2RhdGEgPSBuZXdkYXRhLCBzZS5maXQgPSBGLCBzdGFydC50aW1lID0gMCkNCm91dDMwIDwtIHN1cnZmaXQoY294X3RkLCBuZXdkYXRhID0gbmV3ZGF0YSwgc2UuZml0ID0gRiwgc3RhcnQudGltZSA9IDMwKQ0Kb3V0NjAgPC0gc3VydmZpdChjb3hfdGQsIG5ld2RhdGEgPSBuZXdkYXRhLCBzZS5maXQgPSBGLCBzdGFydC50aW1lID0gNjApDQpvdXQ5MCA8LSBzdXJ2Zml0KGNveF90ZCwgbmV3ZGF0YSA9IG5ld2RhdGEsIHNlLmZpdCA9IEYsIHN0YXJ0LnRpbWUgPSA5MCkNCm91dDE4MCA8LSBzdXJ2Zml0KGNveF90ZCwgbmV3ZGF0YSA9IG5ld2RhdGEsIHNlLmZpdCA9IEYsIHN0YXJ0LnRpbWUgPSAxODApDQpvdXQyNzAgPC0gc3VydmZpdChjb3hfdGQsIG5ld2RhdGEgPSBuZXdkYXRhLCBzZS5maXQgPSBGLCBzdGFydC50aW1lID0gMjcwKQ0Kb3V0MzYwIDwtIHN1cnZmaXQoY294X3RkLCBuZXdkYXRhID0gbmV3ZGF0YSwgc2UuZml0ID0gRiwgc3RhcnQudGltZSA9IDM2MCkNCm91dDQ1MCA8LSBzdXJ2Zml0KGNveF90ZCwgbmV3ZGF0YSA9IG5ld2RhdGEsIHNlLmZpdCA9IEYsIHN0YXJ0LnRpbWUgPSA0NTApDQpvdXQ1NDAgPC0gc3VydmZpdChjb3hfdGQsIG5ld2RhdGEgPSBuZXdkYXRhLCBzZS5maXQgPSBGLCBzdGFydC50aW1lID0gNTQwKQ0KDQpnZ3Bsb3QoKSArDQogIGdlb21fbGluZShhZXMoeCA9IGMoMCwgb3V0JHRpbWUpLCB5ID0gYygxLCBvdXQkc3VydlssNzE0XSksIGNvbG9yID0gIjAiKSwgc2l6ZSA9IDEpICsNCiAgZ2VvbV9saW5lKGFlcyh4ID0gYygzMCwgb3V0MzAkdGltZSksIHkgPSBjKDEsIG91dDMwJHN1cnZbLDcxNF0pLCBjb2xvciA9ICIzMGQiKSwgc2l6ZSA9IDEpICsNCiAgZ2VvbV9saW5lKGFlcyh4ID0gYyg2MCwgb3V0NjAkdGltZSksIHkgPSBjKDEsIG91dDYwJHN1cnZbLDcxNF0pLCBjb2xvciA9ICI2MGQiKSwgc2l6ZSA9IDEpICsNCiAgZ2VvbV9saW5lKGFlcyh4ID0gYyg5MCwgb3V0OTAkdGltZSksIHkgPSBjKDEsIG91dDkwJHN1cnZbLDcxNF0pLCBjb2xvciA9ICI5MGQiKSwgc2l6ZSA9IDEpICsNCiAgZ2VvbV9saW5lKGFlcyh4ID0gYygxODAsIG91dDE4MCR0aW1lKSwgeSA9IGMoMSwgb3V0MTgwJHN1cnZbLDcxNF0pLCBjb2xvciA9ICIxODBkIiksIHNpemUgPSAxKSArDQogIGdlb21fbGluZShhZXMoeCA9IGMoMjcwLCBvdXQyNzAkdGltZSksIHkgPSBjKDEsIG91dDI3MCRzdXJ2Wyw3MTRdKSwgY29sb3IgPSAiMjcwZCIpLCBzaXplID0gMSkgKw0KICBnZW9tX2xpbmUoYWVzKHggPSBjKDM2MCwgb3V0MzYwJHRpbWUpLCB5ID0gYygxLCBvdXQzNjAkc3VydlssNzE0XSksIGNvbG9yID0gIjM2MGQiKSwgc2l6ZSA9IDEpICsNCiAgZ2VvbV9saW5lKGFlcyh4ID0gYyg0NTAsIG91dDQ1MCR0aW1lKSwgeSA9IGMoMSwgb3V0NDUwJHN1cnZbLDcxNF0pLCBjb2xvciA9ICI0NTBkIiksIHNpemUgPSAxKSArDQogIGdlb21fbGluZShhZXMoeCA9IGMoNTQwLCBvdXQ1NDAkdGltZSksIHkgPSBjKDEsIG91dDU0MCRzdXJ2Wyw3MTRdKSwgY29sb3IgPSAiNTQwZCIpLCBzaXplID0gMSkgKw0KICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gc2V0TmFtZXMocGFsZXR0ZWVyX2QoImdnc2NpOjpkZWVwX29yYW5nZV9tYXRlcmlhbCIpWzEwOjJdLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYygiMCIsIHBhc3RlMChjKDMwLDYwLDkwLDE4MCwyNzAsMzYwLDQ1MCw1NDApLCAiZCIpKSkpICsNCiAgc2NhbGVfeV9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwwKSwgbGltaXRzID0gYygwLDEpLA0KICAgICAgICAgICAgICAgICAgICAgYnJlYWtzID0gc2VxKDAsMSwwLjIpLA0KICAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gc2NhbGVzOjpwZXJjZW50X2Zvcm1hdChhY2N1cmFjeSA9IDEpKSArDQogIHNjYWxlX3hfY29udGludW91cyhicmVha3MgPSBzZXEoMCw4MTAsOTApLCBleHBhbmQgPSBjKDAsMCkpICsNCiAgbGFicyh4ID0gIkRheXMiLCB5ID0gIkNvbmRpdGlvbmFsIHN1cnZpdmFsIHByb2JhYmlsaXR5IiwNCiAgICAgICB0aXRsZSA9ICJQcmVkaWN0ZWQgY29uZGl0aW9uYWwgc3Vydml2YWwgcHJvYmFiaWxpdHkiLCBjb2xvciA9ICIiKSArDQogIHRoZW1lX2NsYXNzaWMoKSArDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJyaWdodCIsDQogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiwgc2l6ZSA9IDEyKSwNCiAgICAgICAgcGFuZWwuZ3JpZC5tYWpvciA9IGVsZW1lbnRfbGluZShzaXplID0gLjIsIGNvbG91ciA9ICJncmV5OTAiKSwNCiAgICAgICAgcGFuZWwuZ3JpZC5taW5vciA9IGVsZW1lbnRfbGluZShzaXplID0gLjIsIGNvbG91ciA9ICJncmV5OTAiKSkNCmBgYA0KDQohW10oRDovTXlEb2N1bWVudHMvS2FnZ2xlL0trYm94L3Bsb3RzL3Bsb3RfMDQ4LnBuZykNCg0KPGJyPg0KDQpXZSBhbHNvIGNhbiBlc3RpbWF0ZSB2YXJpYWJsZSBpbXBvcnRhbmNlIHVzaW5nIHByb3BvcnRpb24gb2YgZXhwbGFpbmFibGUgbG9nLWxpa2VsaWhvb2QgaW4gdGhlIENveCByZWdyZXNzaW9uLg0KDQpgYGB7ciBjb3hfdGRfYW5vdmF9DQpmbWwgPSBhcy5mb3JtdWxhKHBhc3RlMCgiU3Vydih0aW1lID0gdGltZV9zdGFydCwgdGltZTIgPSB0aW1lX3N0b3AsIGV2ZW50ID0gY2h1cm4pIH4gIiwNCiAgICAgICAgICAgICAgICAgICAgICAgIHBhc3RlKGZlYXR1cmVzX2Jhc2VsaW5lLCBjb2xsYXBzZSA9ICIgKyAiKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICIgKyAiLA0KICAgICAgICAgICAgICAgICAgICAgICAgcGFzdGUoZmVhdHVyZXNfdGltZWRlcCwgY29sbGFwc2UgPSAiICsgIikpKQ0KDQpjb3hfdGRfcm1zIDwtIGNwaChmbWwsIGRmX3RyYWluLCB4PVRSVUUsIHk9VFJVRSkNCmNveF90ZF9hbm92YV9ybXMgPC0gYW5vdmEoY294X3RkX3JtcykNCnBsb3QoY294X3RkX2Fub3ZhX3Jtcywgc29ydCA9ICJhc2NlbmRpbmciKQ0KYGBgDQoNCiFbVmFyaWFibGUgaW1wb3J0YW5jZSwgQ294IFBIIHdpdGggdGltZS1kZXBlbmRlbnQgdmFyaWFibGVzXShEOi9NeURvY3VtZW50cy9LYWdnbGUvS2tib3gvcGxvdHMvcGxvdF8wNDcucG5nKQ0KDQpUb3AgY29udHJpYnV0b3JzIHRvIG1vZGVsIHBlcmZvcm1hbmNlIGhlcmUgYXJlIHRpbWUtZGVwZW5kZW50IHZhcmlhYmxlcyBmb3IgdGhlIGN1bXVsYXRpdmUgbnVtYmVyIG9mIGNhbmNlbGxhdGlvbnMgYW5kIGxhc3QgcGF5bWVudCBtZXRob2QgdXNlZCBieSBhIGNsaWVudC4NCg0KPGJyPg0KDQpBcyBmYXIgYXMgY29lZmZpY2llbnRzIGFuZCBoYXphcmQgcmF0aW9zIGFyZSBjb25jZXJuZWQsIHRoZXkgaGF2ZSBub3Qgc3VjaCBjbGVhciBpbnRlcnByZXRhdGlvbiBpbiB0aGUgQ294IHJlZ3Jlc3Npb24gd2l0aCB0aW1lLWRlcGVuZGVudCBjb3ZhcmlhdGVzLCBzbyB3ZSB3aWxsIG5vdCBpbnRlcnByZXQgdGhlbSBoZXJlLg0KDQo8YnI+DQoNCiMgKipDb25jbHVzaW9uKioNCg0KPGJyPg0KDQpUaGUgdGFzayBvZiB0aGlzIHN0dWR5IHByb2plY3Qgd2FzIHRvIHByZWRpY3QgY2h1cm4gcHJvYmFiaWxpdHkgdXNpbmcgc3Vydml2YWwgYW5hbHlzaXMuIFdlIGZvdW5kIG91dCBzZXZlcmFsIGFkdmFudGFnZXMgb2YgdXNpbmcgdGhlc2UgbWV0aG9kcyBmb3IgdGhpcyBraW5kIG9mIGJ1c2luZXNzIHRhc2tzLiBGaXJzdGx5LCB0aGV5IGFsbG93IHRvIHByZWRpY3Qgbm90IG9ubHkgdGhlIHByb2JhYmlsaXR5IG9mIGNodXJuIG92ZXIgc29tZSBwcmVkZWZpbmVkIHBlcmlvZCBvZiB0aW1lLCBidXQgaXRzIHByb2JhYmlsaXR5IGZvciBkaWZmZXJlbnQgdGltZSBwb2ludHMgYWZ0ZXIgdGhlIHN0YXJ0IG9mIHRoZSBjbGllbnQtY29tcGFueSByZWxhdGlvbnNoaXAuIFNlY29uZGx5LCB0aGV5IGdpdmUgaW5mb3JtYXRpb24gYWJvdXQgdGhlIGZvcm0gb2YgY2h1cm4gaGF6YXJkIGZ1bmN0aW9uLCB3aGljaCBpcyB1c2VmdWwgaW4gdGhlIGFuYWx5c2lzIG9mIGF0dHJpdGlvbiBkeW5hbWljcyAoZS5nLiBjcml0aWNhbCBwb2ludHMgd2hlbiBhIGNvbXBhbnkgbG9zZXMgaXRzIGNsaWVudHMpLiBBdCBsYXN0LCB0aGV5IGhhdmUgYSBiYWdnYWdlIG9mIGhlbHBmdWwgdmlzdWFsIGluc3RydW1lbnRzIHRvIGlsbHVzdHJhdGUgYWxsIHRoZXNlIHJlc3VsdHMuDQoNCldlIHN0dWRpZWQgZXhpc3RpbmcgbGl0ZXJhdHVyZSBvbiBib3RoIHN0YXRpc3RpY2FsIG1vZGVscyBhbmQgbWFjaGluZSBsZWFybmluZyB0ZWNobmlxdWVzIHVzZWQgZm9yIHByZWRpY3Rpbmcgc3Vydml2YWwgZGlzdHJpYnV0aW9ucy4gSW4gY29tcGFyaXNvbiB3aXRoIG90aGVyIHRhc2tzIGluIG1hY2hpbmUgbGVhcm5pbmcsIHRoaXMgb25lIGhhcyBzZXZlcmFsIHBlY3VsaWFyaXRpZXMgY29uY2VybmluZyB0aGUgZGVmaW5pdGlvbiBvZiB0aGUgdGFyZ2V0IHZhcmlhYmxlIChjYXVzZWQgYnkgdGhlIHBoZW5vbWVub24gb2YgY2Vuc29yaW5nKSwgaXRzIHByZWRpY3Rpb24gYW5kIHBlcmZvcm1hbmNlIGV2YWx1YXRpb24uIEFzIGEgY29uc2VxdWVuY2UsIOKAnHN0YW5kYXJk4oCdIE1MIG1vZGVscyByZXF1aXJlIGFkYXB0YXRpb24gZm9yIHRoaXMgdGFzaywgYW5kLCBhcyBtYW55IHJlc3VsdHMgc2hvdywgc29tZXRpbWVzIHRoZXkgZmFsbCBiZWhpbmQgc3RhdGlzdGljYWwgbW9kZWxzIGluIHBlcmZvcm1hbmNlLiBBZGRpdGlvbmFsIG9ic3RhY2xlcyBhcmlzZSB3aXRoIHRoZSB1c2Ugb2YgdGltZS1kZXBlbmRlbnQgcHJlZGljdG9ycyBhbmQgdGltZS1kZXBlbmRlbnQgZWZmZWN0czogYWxtb3N0IG5vbmUgb2YgTUwgbW9kZWxzIGNhbiB3b3JrIHdpdGggdGhlbSwgb3IsIGF0IGxlYXN0LCB0aGVyZSBhcmUgbm8gZW5vdWdoIGluc3RydW1lbnRzIGF2YWlsYWJsZSBmb3IgaXQuDQoNCldlIHVzZWQgYSBzYW1wbGUgb2Ygb2JzZXJ2YXRpb25zIGZyb20gdGhlIGRhdGFzZXQgb24gS0tCb3ggbXVzaWMgc3Vic2NyaXB0aW9uIHNlcnZpY2UgZ2l2ZW4gYXQgS2FnZ2xlLiBXZSB1c2VkIHRoZSBzYW1lIGRlZmluaXRpb24gb2YgY2h1cm4gYXMgd2FzIGdpdmVuIGluIGNvbXBldGl0aW9uLCBidXQgbW9kaWZpZWQgdGhlIHRhc2sgc3VjaCB0aGF0IHdlIHdlcmUgYWJsZSB0byB1c2Ugc3Vydml2YWwgYW5hbHlzaXMgdG8gc29sdmUgaXQuDQoNCkFmdGVyIHByZXByb2Nlc3Npbmcgd2Ugb2J0YWluZWQgZGF0YSBvbiBzdWJzY3JpcHRpb24gcGVyaW9kcywgc29tZSBvZiB3aGljaCBlbmRlZCB3aXRoIHVzZXLigJlzIGF0dHJpdGlvbiwgb3IgY2Vuc29yZWQgb3RoZXJ3aXNlLiBXZSBkaXZpZGVkIG91ciBzYW1wbGUgb24gdHJhaW4gYW5kIHRlc3Qgc2V0cy4gQXMgYSBiYXNlbGluZSwgd2UgZXN0aW1hdGVkIENveCBwcm9wb3J0aW9uYWwgaGF6YXJkcyByZWdyZXNzaW9uLiBUaGVuLCB1c2luZyB0ZXN0IHNhbXBsZSwgd2UgY29tcGFyZWQgdGhlIHJlc3VsdHMgb2YgdGhpcyBiYXNlbGluZSBtb2RlbCB3aXRoIHJhbmRvbSBzdXJ2aXZhbCBmb3Jlc3Qgd2hpY2ggaGFzIG5vdCBhbiBhc3N1bXB0aW9uIG9mIGhhemFyZHMgcHJvcG9ydGlvbmFsaXR5IOKAkyBhbmQgaXQganVzdCBhIGxpdHRsZSBvdXRwZXJmb3JtZWQgQ294IFBILiBCb3RoIG1vZGVscyBnYWluZWQgYWZ0ZXIgYWRkaXRpb24gb2YgZGF0YSBvbiB1c2Vyc+KAmSBiZWhhdmlvciBpbiB0aGVpciBwcmV2aW91cyBzdWJzY3JpcHRpb24gcGVyaW9kcywgYWx0aG91Z2ggd2Ugd291bGQgbm90IGFzc2VydCB0aGF0IHRoaXMgZ2FpbiB3YXMgc2lnbmlmaWNhbnQuDQoNClVzaW5nIHRoZSByZXN1bHRzIGZyb20gdGhlc2UgbW9kZWxzIHdlIHByZWRpY3Qgc3Vydml2YWwgY3VydmVzIGZvciB1c2VycyBmcm9tIHRoZSB0ZXN0IHNhbXBsZSBhbmQgYXJyYW5nZSB2YXJpYWJsZXMgYnkgdGhlaXIgaW1wb3J0YW5jZS4NCldlIGVzdGltYXRlZCBDb3ggUEggYW5kIFJTRiBtb2RlbHMgd2l0aCB0aW1lLWRlcGVuZGVudCBjb3ZhcmlhdGVzIG9uIHVzZXJz4oCZIGxvZ3MgYWZ0ZXIgdGhlIHN0YXJ0IG9mIGVhY2ggc3Vic2NyaXB0aW9uIHBlcmlvZC4gV2Ugc2hvd2VkIHRoYXQgbmV3IGluZm9ybWF0aW9uIGFib3V0IGNsaWVudHMgZ2l2ZXMgYSBjb25zaWRlcmFibGUgZ2FpbiBpbiBwcmVkaWN0aW9uIHBlcmZvcm1hbmNlIGFuZCBhbGxvd3MgdG8gdXBkYXRlIHRoZWlyIHN1cnZpdmFsIGN1cnZlcyBhbmQgcHJlZGljdGlvbnMgaW4gdGhlIHByb2Nlc3Mgb2YgY3VzdG9tZXItY29tcGFueSByZWxhdGlvbnNoaXAuDQogDQoNCjx2cj4NCg0KIyAqKlJlZmVyZW5jZXMqKiB7LX0NCg==