Learning Objectives
At the conclusion of this lesson, you will be able to:
- define the need for forecasting
- distinguish forecasting from predictive modeling
- create moving average, exponential smoothing, and trend models
- account for trend and seasonality
- evaluate models using MAE, MSE, and MAPE
- tune model hyperparameters
- construct ensemble models
Introduction
Forecasting is a specific way to build predictive models that rely on trends in time series data. They are often used in finance, economics, and various branches of science where predicting future values based on historical time series data is essential and feasible. Forecasting involves making predictions about future events based on historical data. The primary goal is to identify patterns and trends within the data that can be extrapolated to predict future outcomes. Accurate forecasting can guide decision-making processes in various fields, from stock market analysis to weather prediction and inventory management.
In this lesson, we will delve into the fundamental principles of forecasting, explore various forecasting methods, and demonstrate practical implementations using R. Many of the techniques explored in this lesson also apply to constructed predictive models for classification and regression using supervised machine learning.
Forecasting and predictive modeling are related concepts in data science and machine learning, but they have some key differences. Forecasting primarily focuses on estimating future values of a specific variable or set of variables based on historical data and trends. It is often used for time series data and is particularly common in fields like economics, finance, and weather prediction. Predictive modeling, on the other hand, is a broader concept that involves using statistical algorithms and machine learning techniques to identify patterns in data and make predictions about future outcomes or to classify new instances. It can be applied to a wide range of problems, not just time series data. Nevertheless, in both modeling efforts data is used to “train a model” on historical data to make a prediction or derive an estimate.
Forecasting typically deals with time series data, where observations are collected sequentially over time. The temporal aspect is crucial, and the order of data points matters. Predictive modeling can work with various types of data, including structured (tabular), unstructured (text, images), and time series data. For predictive modeling, the order of data points may or may not be relevant, depending on the specific problem, but it certainly matters for forecasting.
Forecasting often employs simple methods such as weighted averages, statistical methods like ARIMA (AutoRegressive Integrated Moving Average), exponential smoothing, and more advanced techniques like Prophet or LSTM (Long Short-Term Memory) neural networks for complex time series. On the other hand, predictive modeling encompasses a broader range of algorithms, including regression, classification, clustering, and deep learning techniques. It may use methods like nearest neighbors, Bayesian networks, decision trees, random forests, support vector machines, neural networks, and various ensemble methods.
Forecasting typically focuses on predicting future values at specific time points or over a defined period. The time horizon can be short-term, medium-term, or long-term, depending on the application. Predictive modeling doesn’t necessarily have a fixed time horizon, instead it estimates a numeric value or assigns some observation to a class.
Forecasting often emphasizes quantifying uncertainty in predictions, providing prediction intervals along with point estimates. Predictive modeling may or may not explicitly quantify uncertainty, depending on the algorithm used. Some models provide probability estimates or confidence scores, while others may only give point predictions.
Forecasting models often prioritize interpretability, as understanding the factors driving future trends is essential in many applications. Predictive modeling can range from highly interpretable (e.g., multiple regression, decision trees) to more black-box approaches (e.g., deep neural networks), depending on the complexity of the problem and the chosen algorithm.
In practice, there’s often an overlap between forecasting and predictive modeling, especially when dealing with time series data. Many modern approaches combine elements of both to create more powerful and flexible models.
Time Series Data
Central to forecasting is time series data, which is a sequence of data points collected or recorded at specific time intervals. Time series data can exhibit various components such as trend, seasonality, and noise. Understanding these components is critical for building effective forecasting models.
- Trend: The long-term movement in the data.
- Seasonality: Regular, repeating patterns or cycles in the data.
- Noise: Random variability in the data that cannot be explained by the model.
Seasonality refers to the presence of regular, predictable patterns or cycles that repeat over a specific period within a time series dataset. These patterns are often influenced by external factors such as weather, holidays, or economic cycles, and they can significantly affect the behavior of the data. Seasonality adjustment is an essential concept in time series forecasting because it helps identify and account for these periodic fluctuations, leading to more accurate predictions.
Trend in forecasting refers to the long-term movement or direction in time series data over a period. It represents the underlying pattern of growth or decline that persists over time, ignoring short-term fluctuations and noise. Identifying and modeling trends is paramount for accurate time series forecasting, as it helps in understanding the general direction in which the data is moving.
The components are often discovered through visualization. For example, the line chart below shows a time series of average daily water usage over the course of several months.
# load data from CSV
csv <- "https://s3.us-east-2.amazonaws.com/artificium.us/datasets/waterusage.csv"
water.usage.ts <- read.csv(csv,
stringsAsFactors = F)
library(ggplot2)
# Convert 'period' to a factor to maintain the order in the plot
water.usage.ts$period <- as.factor(water.usage.ts$period)
# Line plot for daily usage over periods
ggplot(water.usage.ts, aes(x = period, y = daily, group = 1)) +
geom_line(color = "green") +
geom_point(color = "orange") +
labs(title = "Average Daily Water Usage Over Time", x = "Period", y = "Daily Usage (ltr)") +
theme_minimal()
The time series shows some peaks and valleys which might indicate seasonality, but there is no overall upward or downward trend. These characteristics will determine which forecasting model will be most appropriate.
Forecasting Models
A forecasting model uses a series/sequence of data values measuring some event over time (referred to as time series data) and using the sequence to “estimate” the next few values in the sequence. Forecasting solely focuses on the trend of the data values over time, while other models include additional data. For example, if a real estate agent has average monthly home sales prices for the past fifteen years, they can use a forecasting model to “predict” the average sales price for the next few months. But that “prediction” would not take into account any other factors, so it would not be useful to predict the likely selling price for a specific home having a specific lot size, living area, pool, age of home, etc. A predictive model constructed using a machine learning algorithm would take those other “features” into account; those types of models are often referred to as causal models.
Several mathematical methods exist for forecasting, ranging from simple extrapolation techniques to complex machine learning models. In this lesson, we will focus on the following common approaches:
- Simple Moving Averages (SMA)
- Weighted Moving Averages (WMA)
- Simple Exponential Smoothing (SES)
- Holt’s Linear Trend Model (HLTM)
- Linear Regression Trend Model (LRTM)
- AutoRegressive Integrated Moving Average (ARIMA)
In addition, we will explain how to tune and evaluate a model, account for trend and seasonality in the data, combine multiple forecasting methods into an ensemble, and provide a forecast interval in addition to a point estimate.
Sample Data
The examples in the ensuing sections use the time series data in the CSV file wateruage.csv. The first few rows are shown below:
# load data from CSV
csv <- "https://s3.us-east-2.amazonaws.com/artificium.us/datasets/waterusage.csv"
water.usage.ts <- read.csv(csv,
stringsAsFactors = F)
# display first few rows
knitr::kable(head(water.usage.ts, 4),
format = "simple",
align = "c")
1 |
Aug |
2023 |
13 |
32 |
406 |
2 |
Sep |
2023 |
10 |
30 |
333 |
3 |
Oct |
2023 |
10 |
33 |
303 |
4 |
Nov |
2023 |
20 |
30 |
665 |
The data file contains average daily water use (in liters) over the course of several months.
Simple Moving Average
This is the unweighted mean of the previous \(n\) data points. A Simple Moving Average (SMA) is a basic approach to time series analysis which smoothes out short-term fluctuations. It is calculated by averaging a fixed number of past observations.
The SMA for a given time period is calculated by taking the arithmetic mean of the last \(n\) observations. It can be represented mathematically as:
\(\text{SMA}_t = \frac{1}{n} \sum_{i=0}^{n-1} y_{t-i}\)
where:
- \(SMA_t\) is the Simple Moving Average at time \(t\)
- \(n\) is the number of periods over which the average is calculated
- \(y_{t-i}\) are the values of the time series at time \(t, t-1, t-2, \ldots, t-(n-1)\)
For example, suppose we have a time series of daily water usage for the past 7 days: [110, 90, 100, 120, 140, 160, 180]. To calculate the 3-day SMA at the end of this series to get a forecast for time period 8 (the next time period):
\(t(8) = \frac{140 + 160 + 180}{3} = \frac{480}{3} = 160\)
Naturally, this means that the forecast can never be larger than the largest value in the forecast calculation, so any upward (or downward) trend that might exist would be smoothed out. Consequently, SMA is not an appropriate method for calculating a forecast when there is a trend as it simply smoothes out fluctuations.
By calculating the arithmetic mean of a fixed number of past observations, SMA provides a clear and easily interpretable measure of central tendency in the data. The mathematical simplicity and ease of implementation make SMA a popular choice for initial data analysis and trend identification.
Calculating SMA in R
Let’s calculate the SMA for the daily
column in the waterusage.csv
dataset using the SMA
function from the TTR
package to compute the moving average for the next time period.
Here’s the R code to perform these steps:
library(TTR)
csv <- "https://s3.us-east-2.amazonaws.com/artificium.us/datasets/waterusage.csv"
# load data from CSV
water.usage.ts <- read.csv(csv,
stringsAsFactors = F)
# Convert 'daily' to a time series object
daily.ts <- ts(water.usage.ts$daily,
start = c(2023, 8),
frequency = 12)
# Calculate the 3-period Simple Moving Average
sma_3 <- SMA(daily.ts, n = 3)
The function SMA
calculates the 3-day moving average for all time periods (except the first two as it would be impossible to do so). It requires a “time series object” which is produced by the function ts
. In the above code we start the time series at month 8 in 2023 because that is the oldest month in the data set water.usage.ts. We use frequency = 12
because our data set from the CSV has 12 time periods.
The last value in the vector of forecasts is the forecast for the next time period. So the forecast for August 2024 would be in sma_3[12]
and is 494.3.
Weighted Moving Average
A Weighted Moving Average (WMA) is a type of moving average where each data point is assigned a different weight, with more recent observations typically given higher weights. This method gives more importance to recent data points, making it more responsive to changes in the data compared to the Simple Moving Average (SMA).
The WMA for a given time period \(n\) is calculated by assigning a weight to each data point and then taking the weighted average. The weights typically sum to 1 and are written as fractions, although they do not have. The benefit of writing them as fractions that sum to 1 is that the sum of the weights is always 1 making the calculation simpler. If the weights do not sum to 1, then it is important to divide by the sum of the weights.
\(WMA_t = \frac{\sum_{i=0}^{n-1} w_i y_{t-i}}{\sum_{i=0}^{n-1} w_i}\)
where:
- \(WMA_t\) is the Weighted Moving Average at time \(t\)
- \(y_{t-i}\) are the values of the time series at times \(t, t-1, t-2, \ldots, t-(n-1)\)
- \(w_i\) are the weights assigned to each observation
Suppose we have the following data points: [100, 120, 140, 160, 180], and we want to calculate the 3-period WMA with weights 0.1, 0.3, and 0.6 for the most recent three observations.
The calculation for \(WMA_5\) (the WMA at the fifth data point) would be:
\(WMA_5 = \frac{0.1 \times 140 + 0.3 \times 160 + 0.6 \times 180}{0.1 + 0.3 + 0.6}\)
\(WMA_5 = \frac{14 + 48 + 108}{1}\)
\(WMA_5 = 170.0\)
Calculating WMA in R
Let’s implement the Weighted Moving Average using the waterusage.csv
dataset. The forecast package in R does not have a dedicated function specifically for calculating a Weighted Moving Average (WMA) forecast, although the package provides various functions for other types of moving averages, exponential smoothing, and ARIMA models which we will explore later in this lesson.
To calculate a Weighted Moving Average, we can either use the WMA
function from the TTR package or write a custom function.
Custom Function for Calculating WMA
The calculate.wma
function takes three arguments: the data, the number of periods \(n\), and the weights. It initializes a vector wma
with NA
values to store the WMA results. A loop iterates over the data, calculating the WMA for each position where there are enough data points. The WMA is calculated by taking the weighted sum of the last \(n\) observations and dividing by the sum of the weights.
The weights for the 3-period WMA are defined as c(0.1, 0.3, 0.6)
, with the most recent observation given the highest weight. The results are stored in a new column WMA
in the water_usage
data frame.
# Load the data
csv <- "https://s3.us-east-2.amazonaws.com/artificium.us/datasets/waterusage.csv"
water.usage <- read.csv(csv,
stringsAsFactors = F,
header = T)
# Function to calculate the Weighted Moving Average
calculate.wma <- function(data, n, weights) {
# Initialize WMA column with NA values
wma <- rep(NA, length(data))
for (i in n:length(data)) {
wma[i] <- sum(weights * data[(i-n+1):i]) / sum(weights)
}
return(wma)
}
# Define the weights for the WMA (3-period example)
w <- c(0.1, 0.3, 0.6)
# Calculate the WMA
WMA <- calculate.wma(water.usage$daily,
n = 3,
weights = w)
forecast.val <- tail(WMA,1)
The forecast is in the last element of the returned vector of forecasts (it actually calculates the forecast for all prior values) and is 496.9.
Using WMA
Function from TTR
The WMA
function from the TTR package calculates the Weighted Moving Average based on the defined weights. The calculated WMA is added as a new column wma
in the data frame. The forecast for the next period is made using the last value of the WMA. This is a simple approach where the next forecast value is assumed to be the same as the most recent WMA value.
library(TTR)
csv <- "https://s3.us-east-2.amazonaws.com/artificium.us/datasets/waterusage.csv"
# Load the data
water.usage <- read.csv(csv,
stringsAsFactors = F)
# Define the weights for the WMA (example with 3-period weights)
weights <- c(0.1, 0.3, 0.6)
# Calculate the Weighted Moving Average
wma <- TTR::WMA(water.usage$daily, n = length(weights), wts = weights)
# Forecast using the last value of WMA
forecast.val <- tail(wma, 1)
The forecast is in the last element of the returned vector of forecasts (it actually calculates the forecast for all prior values) and is 496.9 – the same as with our custom function (as we would expect.)
The TTR package has a number of other forecasting methods, such as Exponential Weighted Mean, so check it out.
The Weighted Moving Average (WMA) is an effective method for smoothing time series data, giving more importance to recent observations. Naturally, given that it is an average, WMA does not account for either trend or seasonality, although a seasonality adjustment can be added as explained later in this lesson.
Lecture: Moving Average
In the lecture below, Dr. Martin Schedlbauer of Khoury Boston demonstrates the calculation of weighted moving average directly in R without the use of a package.
Simple Exponential Smoothing
Simple Exponential Smoothing (SES) is a time series forecasting method for univariate data that can be used to forecast data with no clear trend or seasonal pattern. SES uses weighted averages of past observations, with more recent observations given higher weights. The method applies exponentially decreasing weights to past data, making it more responsive to recent changes. This method is particularly useful for data with a clear trend and seasonal patterns.
The forecast at time \(t+1\), the next time period, is given by:
\(F_{t+1} = \alpha Y_t + (1 - \alpha) F_t\)
where:
- \(F_{t+1}\) is the forecast for the next period
- \(Y_t\) is the actual value at time \(t\)
- \(F_t\) is the forecast for time \(t\)
- \(\alpha\) (alpha) is the smoothing parameter, \(0 \leq \alpha \leq 1\)
The initial forecast \(F_1\) can be set to the first observation \(Y_1\) or the average of the first few observations.
From the formula above, we can see that it is recursive and requires starting at the first time period.
The SES formula can be applied recursively as:
\(F_{t+1} = \alpha Y_t + (1 - \alpha) (\alpha Y_{t-1} + (1 - \alpha) F_{t-1})\)
This recursive nature means that we apply exponentially decreasing weights to past observations.
Let’s consider a small dataset for illustration. Suppose we have the following data points: [10, 12, 14, 13, 15]. Assume we choose \(\alpha = 0.3\) (a common value). The initial forecast \(F_1\) is set to \(Y_1\), which is 10.
Initial Forecast:
\(F_1 = Y_1 = 10\)
First Forecast:
\(F_2 = \alpha Y_1 + (1 - \alpha) F_1\)
\(F_2 = 0.3 \times 10 + 0.7 \times 10\)
\(F_2 = 10\)
Second Forecast:
\(F_3 = \alpha Y_2 + (1 - \alpha) F_2\)
\(F_3 = 0.3 \times 12 + 0.7 \times 10\)
\(F_3 = 10.6\)
Third Forecast:
\(F_4 = \alpha Y_3 + (1 - \alpha) F_3\)
\(F_4 = 0.3 \times 14 + 0.7 \times 10.6\)
\(F_4 = 11.62\)
Fourth Forecast:
\(F_5 = \alpha Y_4 + (1 - \alpha) F_4\)
\(F_5 = 0.3 \times 13 + 0.7 \times 11.62\)
\(F_5 = 12.134\)
The “ideal” value for \(\alpha\) is found by iteratively trying various values and seeing which model, when applied to its prior observations (called “backfitting”), results in the best fit. The section on Model Evaluation shows this approach in more detail.
Calculating SES in R
Let’s calculate SES for the waterusage.csv
data to forecast daily water usage. We will use the HoltWinters
function from the forecast package. For this function to calculate Simple Exponential Smoothing requires setting the beta and gamma argument to FALSE. Notice that once again we need to convert the data to a “time series” object of type ts.
library(forecast)
csv <- "https://s3.us-east-2.amazonaws.com/artificium.us/datasets/waterusage.csv"
water_usage <- read.csv(csv,
stringsAsFactors = F)
# convert 'daily' to a time series object
daily.ts <- ts(water_usage$daily,
start = c(2023, 8),
frequency = 12)
# create SES model using HoltWinters
# with beta and gamma set to FALSE
ses.model <- HoltWinters(daily.ts,
beta = FALSE,
gamma = FALSE)
# Forecast the next period (next month)
forecast.val <- forecast(ses.model, h = 1)
The SES forecasting model estimates the average daily water use for the next month to be 406 with a 95% prediction range from 172 to 640.
The details of the model can be inspecting by using the function summary(ses.model)
or by accessing its components:
forecast.val$mean[[1]]
is the forecast value
forecast.val$lower[[2]]
is the lower bound of the 95% prediction interval
forecast.val$upper[[2]]
is the upper bound of the 95% prediction interval
The forecast()
function also return the 80% prediction interval.
Lecture: Exponential Smoothing
In the lecture below, Dr. Martin Schedlbauer of Khoury Boston demonstrates the calculation of weighted moving average directly in R without the use of a package.
To summarize, Simple Exponential Smoothing (SES) is an effective method for forecasting time series data that lacks a clear trend or seasonal pattern. By applying exponentially decreasing weights to past observations, SES provides a smooth forecast that emphasizes recent changes in the data. The HoltWinters
function makes calculating an SES forecast quite simple.
Holt’s Linear Trend Model
Holt’s Linear Trend Model (HLTM), also known as Holt’s Exponential Smoothing, is an extension of Simple Exponential Smoothing that allows forecasting of data with a simple linear trend. It incorporates two equations: one for the level and one for the trend. This method is useful when the data exhibits a trend but no seasonality.
Level Equation:
\(L_t = \alpha Y_t + (1 - \alpha) (L_{t-1} + T_{t-1})\)
Trend Equation:
\(T_t = \beta (L_t - L_{t-1}) + (1 - \beta) T_{t-1}\)
The forecast equation for the next h time periods is:
\(F_{t+h} = L_t + h T_t\)
where:
- \(L_t\) is the estimated level at time \(t\)
- \(T_t\) is the estimated trend at time \(t\)
- \(Y_t\) is the actual value at time \(t\)
- \(\alpha\) is the smoothing parameter for the level, \(0 \leq \alpha \leq 1\)
- \(\beta\) is the smoothing parameter for the trend, \(0 \leq \beta \leq 1\)
- \(h\) is the number of periods ahead to be forecasted.
Let’s look at an example. Suppose we have the following data points for a series: [10, 12, 14, 13, 15] and we choose \(\alpha = 0.5\) and \(\beta = 0.5\). Let’s calculate the level and trend for these points.
Initial Level and Trend:
\(L_1 = Y_1 = 10\)
\(T_1 = Y_2 - Y_1 = 2\)
First Update:
\(L_2 = \alpha Y_2 + (1 - \alpha) (L_1 + T_1)\)
\(L_2 = 0.5 \times 12 + 0.5 \times (10 + 2)\)
\(L_2 = 12\)
\(T_2 = \beta (L_2 - L_1) + (1 - \beta) T_1\)
\(T_2 = 0.5 \times (12 - 10) + 0.5 \times 2\)
\(T_2 = 2\)
Second Update:
\(L_3 = \alpha Y_3 + (1 - \alpha) (L_2 + T_2)\)
\(L_3 = 0.5 \times 14 + 0.5 \times (12 + 2)\)
$L_3 = 14 $
\(T_3 = \beta (L_3 - L_2) + (1 - \beta) T_2\)
\(T_3 = 0.5 \times (14 - 12) + 0.5 \times 2\) $T_3 = 2$
The level and trend are updated at each step, and the forecast can be made using these updated values. Once again, the model is recursive and requires starting at the first observation. The first forecast can be set to the first observed value or we could start at observation three and make its first forecast the average of the first two observed values.
Calculating HLTM in R
Let’s implement Holt’s Linear Trend Model using the waterusage.csv
data to forecast daily water usage. We will choose \(\alpha = 0.3\) and \(\beta = 0.5\).
library(forecast)
# convert 'daily' to a time series object
daily.ts <- ts(water_usage$daily, start = c(2023, 8), frequency = 12)
# apply Holt's Linear Trend Model using HoltWinters
# with gamma set to FALSE
holt.model <- HoltWinters(daily.ts,
alpha = 0.3,
beta = 0.5,
gamma = FALSE)
# Forecast the next period (next month)
forecast.val <- forecast(holt.model, h = 1)
The Holt’s Linear Trend Model estimates the average daily water use for the next month to be 510 with a 95% prediction range from 183 to 838.
The details of the model can be inspecting by using the function summary(holt.model)
or by accessing its components:
forecast.val$mean[[1]]
is the forecast value
forecast.val$lower[[2]]
is the lower bound of the 95% prediction interval
forecast.val$upper[[2]]
is the upper bound of the 95% prediction interval
The forecast()
function also returns the residuals and the 80% prediction interval.
Holt’s Linear Trend Model extends simple exponential smoothing to capture linear trends in time series data. By incorporating both level and trend components, it provides a more accurate forecast for data with trends but no seasonality.
Linear Regression Trend Model
Trend in forecasting refers to the long-term movement or direction in a time series data over a period. It represents the underlying pattern of growth or decline that persists over time, ignoring short-term fluctuations and noise. Identifying and modeling trends is crucial for accurate time series forecasting, as it helps in understanding the general direction in which the data is moving.
Trends capture the general direction of the data over a long period. This movement can be upward, downward, or flat (no trend). There may be noise in the data and values may move up or down, but over the long-term, if they remain stable, then there is no trend. Trends are persistent patterns that last for a significant period, distinguishing them from short-term variations.
We generally recognize three types of trends, which can be represented mathematically in various forms depending on the nature of the trend:
Linear Trend: A constant rate of increase or decrease over time.
\(T_t = a + bt\)
where:
- \(T_t\) is the trend component at time \(t\)
- \(a\) is the intercept
- \(b\) is the slope (i.e., rate of change)
Exponential Trend: A rate of increase or decrease that accelerates over time.
\(T_t = ae^{bt}\)
where \(e\) is the base of the natural logarithm.
Polynomial Trend: More complex trends that can change direction over time, modeled as higher-order polynomials.
\(T_t = a + bt + ct^2 + dt^3 + \ldots\)
where \(a, b, c, d, \ldots\) are coefficients of the polynomial.
Trends can be identified through visual inspection of time series plots or by using statistical methods like moving averages and regression analysis. Plotting the data helps in visualizing the long-term direction, while regression techniques can quantify the trend mathematically. For example, if we have monthly sales data for a retail store over several years, and we observe that sales are generally increasing, then we can use a linear regression trend model to express the trend mathematically and provide useful forecasts.
Calculating LRTM in R
Let’s use the waterusage.csv
dataset to identify and model the trend in daily water usage using the lm()
function.
# Load necessary libraries
library(forecast)
csv <- "https://s3.us-east-2.amazonaws.com/artificium.us/datasets/waterusage.csv"
# Load the data
water.usage <- read.csv(csv,
stringsAsFactors = F)
# fit a linear trend model using linear regression
trend.model <- lm(water.usage$daily ~ water.usage$period)
# Forecast future values using the trend model
forecast.val <- predict(trend.model,
newdata = data.frame(period = 13))
The Linear Regression Trend Model forecasting model estimates the average daily water use for the next month to be 468.
The details of the model can be inspecting by using the function summary(ses.model)
or by accessing its components:
trend.model$coefficients[[1]]
is the coefficient of the regression equation
trend.model$coefficients[[2]]
is the slope of the regression equation
The equation for the linear regression trend model is:
\(F(t) = 379.939 + 7.304t\)
Plugging in \(t=12\) results in 4566.6, the same value as we got from the predict()
function.
Lecture: Trend Line
In the lecture below, Dr. Martin Schedlbauer of Khoury Boston demonstrates the calculation of weighted moving average directly in R without the use of a package.
ARIMA Models
ARIMA (AutoRegressive Integrated Moving Average) is a widely used statistical method for time series forecasting. It is particularly effective for data that shows evidence of non-stationarity, where the statistical properties of the series (mean, variance, etc.) change over time. ARIMA models are powerful tools for forecasting time series data that can be made stationary through differencing.
ARIMA combines three components:
- AutoRegression (AR): A model that uses the dependency between an observation and a number of lagged observations (i.e., previous time points).
- Integrated (I): The use of differencing of observations (subtracting an observation from an observation at the previous time step) to make the time series stationary.
- Moving Average (MA): A model that uses the dependency between an observation and a residual error from a moving average model applied to lagged observations.
The ARIMA model is typically denoted as ARIMA(p, d, q), where:
- p: The number of lag observations in the model (lag order).
- d: The number of times that the raw observations are differenced (degree of differencing).
- q: The size of the moving average window (order of moving average).
Calculating ARIMA in R
In R, the forecast package provides functions for fitting ARIMA models. Let’s use the ARIMA model to forecast the average daily water usage for the next time period using the provided CSV data. First, we need to convert the daily
column into a “time series object”. Next, we use the auto.arima
function from the forecast package to fit an ARIMA model to the time series data. This function automatically selects the best ARIMA model based on the data. Finally, we can use the fitted ARIMA model to forecast the daily water usage for the next time period.
library(forecast)
# Convert 'daily' to a time series object
daily.ts <- ts(water.usage.ts$daily,
start = c(2023, 8),
frequency = 12)
# Fit an ARIMA model
arima.model <- auto.arima(daily.ts)
# Forecast the next period (next month)
forecast.val <- forecast(arima.model, h = 1)
The ARIMA forecasting model estimates the average daily water use for the next month to be 427 with a 95% prediction range from 203 to 651.
The details of the model can be inspecting by using the function summary(arima.model)
or by accessing its components:
forecast.val$mean[[1]]
is the forecast value
forecast.val$lower[[2]]
is the lower bound of the 95% prediction interval
forecast.val$upper[[2]]
is the upper bound of the 95% prediction interval
The forecast()
function also contains the residuals and the 80% prediction interval.
Overall, ARIMA is a commonly used method for time series forecasting because of its excellent forecasts and its capability to handle various types of temporal data. By fitting an ARIMA model to historical data, a decision maker can use informed predictions about future values.
Mathematical Foundation of ARIMA
The ARIMA (AutoRegressive Integrated Moving Average) model combines aspects of autoregressive models, differencing, and moving averages to handle non-stationary data. Here, we will delve into the mathematical formulation and components of ARIMA: AutoRegressive (AR) part, Integrated (I) part, and Moving Average (MA) part.
The autoregressive part of ARIMA models the relationship between an observation and a number of lagged observations (previous values). The order of the autoregressive model is denoted by \(p\).
The AR model of order \(p\), AR(p), can be written as:
\[ y_t = \phi_1 y_{t-1} + \phi_2 y_{t-2} + \cdots + \phi_p y_{t-p} + \epsilon_t \]
where:
- \(y_t\) is the value of the time series at time \(t\).
- \(\phi_1, \phi_2, \ldots, \phi_p\) are the parameters of the model.
- \(\epsilon_t\) is white noise (error term) at time \(t\).
The integrated part of ARIMA involves differencing the data to make it stationary. The order of differencing is denoted by \(d\). Differencing means subtracting the previous observation from the current observation.
If \(d = 1\), the differenced series \(y'_t\) is:
\[ y'_t = y_t - y_{t-1} \]
For higher orders of differencing, the differencing process is repeated.
The moving average part models the relationship between an observation and a residual error from a moving average model applied to lagged observations. The order of the moving average model is denoted by \(q\).
The MA model of order \(q\), MA(q), can be written as:
\[ y_t = \epsilon_t + \theta_1 \epsilon_{t-1} + \theta_2 \epsilon_{t-2} + \cdots + \theta_q \epsilon_{t-q} \]
where:
- \(\epsilon_t, \epsilon_{t-1}, \ldots, \epsilon_{t-q}\) are the white noise error terms.
- \(\theta_1, \theta_2, \ldots, \theta_q\) are the parameters of the model.
The ARIMA model combines these three components. An ARIMA(p, d, q) model is represented as:
\[ y'_t = \phi_1 y'_{t-1} + \phi_2 y'_{t-2} + \cdots + \phi_p y'_{t-p} + \epsilon_t + \theta_1 \epsilon_{t-1} + \theta_2 \epsilon_{t-2} + \cdots + \theta_q \epsilon_{t-q} \]
where \(y'_t\) is the differenced series if \(d\) is the differencing order.
Model Evaluation
Once a model is built, it is essential to evaluate its performance. Evaluating the performance of forecasting models is done through a process known as backfitting or backtesting where we use the model to predict each prior time period for which we have an actual observation and comparing the forecast value against the actual value. The difference between the two is the “error” or “deviation”.
Common evaluation metrics for forecasting models include:
- Mean Absolute Error (MAE): The average of the absolute errors.
- Mean Squared Error (MSE): The average of the squared errors.
- Root Mean Squared Error (RMSE): The square root of MSE.
- Mean Absolute Percentage Error (MAPE): The average of the absolute percentage errors.
These metrics are also be used to compare model performance. If model A has a substantially lower MSE than model B, it means that, on average, its predictions are closer to the actual value and are likely going to be more accurate and that model A is preferable for making forecasts.
Let’s look at one of those metrics in more detail: MAE. The other metrics are calculated very similarly, so looking at just MAE also shows us how to calculate the others.
MAE calculates the average of the absolute errors. It is also known as Mean Absolute Deviation (MAD). Let’s compare two forecasting models for the water usage time series we have looked at before: Linear Regression Trend Model and Weighted Moving Average. To calculate the MAE, we need to use each model to calculate a “forecast” for prior time periods where we have the actual observed values and then calculate the difference between the forecasts and actual values and finally calculate the average (mean) of the absolute errors.
So, MAE is defined as
\(\text{MAE} = \frac{1}{n}\sum_{t=1}^{n}(|Y_t - F_t|)\)
The code below demonstrates how to do the calculation of MAE explicitly. We will reuse the calculate.wma()
function developed in section Weighted Moving Average and the linear regression model created in the section Linear Regression Trend Model. Go back and refresh your memory for both of those models before proceeding. We assume that the data set has been loaded into the data frame water.usage
as before.
Note that some modeling functions such as lm
and auto.arima
will automatically calculate residuals which are the difference between actual and predicted values – although not the absolute value, but that is simple to calculate. Naturally, this makes calculating the metrics much simpler.
Calculating MAE for WMA Model
The code below calculates a “forecast” for each prior time period except the first three as it would be impossible to do so as they do not have three prior time periods. The function calculate.wma()
places the “forecast” value for time period \(t\) into next row. So, the forecast for time period 13 is in row 12 and the forecast for time period 4 is in row 3. We need to be mindful of that when we calculate the errors.
Before running the code, do the calculations by hand or in a spreadsheet program and convince yourself that the result produced by the code is correct.
# define the weights for the WMA (3-period example)
w <- c(0.1, 0.3, 0.6)
# calculate "forecast" for each prior time period
water.usage$WMA <- calculate.wma(water.usage$daily,
n = 3,
weights = w)
periods <- nrow(water.usage)
# calculate the absolute errors
for (t in 3:periods) {
water.usage$abs.err[t] <- abs(water.usage$daily[t] - water.usage$WMA[t-1])
}
# calculate mean of absolute errors (MAE)
MAE.WMA <- mean(water.usage$abs.err, na.rm = T)
The MAE for the WMA with the above weights is 128.8. By itself it is not that meaningful, although a large MAE is comparison to the values in the time series would indicate that the model’s forecasts are wildly off. More useful is comparing the MAE of thw WMA with another model. So, let’s do that by calculating the MAE for the Linear Regression Trend Model.
MAE for Linear Regression Trend Model:
Rather than calculating the errors using the slope and intercept from the lm()
function, we will rely on the residuals produced by lm
– the residuals are the actual errors. They can be found in trend.model$residuals
. Print them out to convince yourself that they are correct. For example, for time period 12, we can see:
F.12 <- trend.model$coefficients[[1]] + trend.model$coefficients[[2]]*12
Y.12 <- water.usage$daily[12]
residual.12.1 <- Y.12 - F.12
residual.12.2 <- trend.model$residuals[12]
cat("residuals are", residual.12.1, "vs", residual.12.2)
## residuals are 16.41026 vs 16.41026
Before running the code, do the calculations by hand or in a spreadsheet program and convince yourself that the result produced by the code is correct.
MAE.LRTM <- mean(abs(trend.model$residuals), na.rm = T)
The MAE for the LRTM is 83.2. This is smaller than the MAE for the WMA which means that the backfitted forecasts were closer to the observed values. We can surmise that future forecasts of the LRTM will likely be more accurate compared to the WMA model (with those weights; perhaps a WMA with different weights might be better).
Lecture: Model Evaluation
In the lecture below, Dr. Martin Schedlbauer of Khoury Boston demonstrates the calculation of weighted moving average directly in R without the use of a package. Note that the lecture uses the alternative term MAD instead of MAE, but they are defined the same; both terms are used in practice.
Model Tuning
Model tuning is the process of optimizing a model’s hyperparameters to improve its performance. Hyperparameters are parameters that are set before the learning process begins and are not learned from the data. In contrast, model parameters are learned from the data during training. For example, the weights for the Weighted Moving Average model are hyperparameters and so is the \(\alpha\) for Simple Exponential Smoothing.
Tuning involves selecting the best set of hyperparameters that minimize a predefined loss function or maximize the model’s accuracy. This process often involves techniques such as cross-validation, grid search, or random search.
Example: Tuning Alpha for SES
Let’s take a look at an example of tuning a model. In Simple Exponential Smoothing (SES), the hyperparameter \(\alpha\) (alpha) controls the smoothing factor. The value of \(\alpha\) ranges between 0 and 1 and wile we often use a value of 0.3 this may not be the one that has the best fit and produces the smallest MAE or MSE. Furthermore, a value closer to 1 puts more weight on recent observations, making the model more responsive to recent changes, while a value closer to 0 gives more weight to past observations, making the model less responsive to recent changes.
The goal of model tuning is to find the \(\alpha\) value that minimizes the forecasting error. First, we need to choose a loss function to evaluate the model’s performance. Common choices are Mean Absolute Error (MAE), Mean Squared Error (MSE), or Root Mean Squared Error (RMSE). For this example, we will use MSE. We need to then try various values of \(\alpha\) and perform a grid search over a range of \(\alpha\) values to find the one that minimizes MSE.
Here’s how you can perform grid search to tune the alpha parameter for Simple Exponential Smoothing in R using the waterusage.csv
dataset assuming the same data frame we loaded before. The HoltWinters
function fits an SES model using the an alpha value, and the forecast
function generates forecasts for the next 12 periods.
# Convert 'daily' to a time series object
daily.ts <- ts(water.usage$daily,
start = c(2023, 8),
frequency = 12)
# Define a range of alpha values to search over
alpha.values <- seq(0.01, 1, by = 0.01)
# Define a function to calculate the MSE for a given alpha
calculate.mse <- function(alpha, data) {
ses.model <- HoltWinters(data, alpha = alpha, beta = FALSE, gamma = FALSE)
fitted.values <- fitted(ses.model)[, 1]
# Extract the fitted values
mse <- mean((data - fitted.values)^2, na.rm = TRUE)
return(mse)
}
# Perform grid search over the alpha values
mse.values <- sapply(alpha.values, calculate.mse, data = daily.ts)
# Find the alpha value that minimizes the MSE
best.alpha <- alpha.values[which.min(mse.values)]
cat("Best alpha:", best.alpha, "\n")
## Best alpha: 0.01
Model tuning is a essential step in improving the performance of forecasting models by optimizing its hyperparameters. In this example, we performed grid search to tune the alpha parameter for Simple Exponential Smoothing (SES). By finding the alpha value that minimizes the Mean Squared Error (MSE), we improved the model’s accuracy, leading to more reliable forecasts. The process of tuning hyperparameters can be applied to all forecasting models and datasets to achieve optimal fit.
Ensemble Methods
An ensemble model combines predictions from multiple models to improve overall forecasting accuracy and robustness. The idea is that by aggregating the strengths of various models, the ensemble can outperform any single model. Ensemble methods can be used in regression, classification, and time series forecasting tasks.
Let’s create an ensemble model by combining the predictions from three different models:
- ARIMA (AutoRegressive Integrated Moving Average)
- SES (Simple Exponential Smoothing)
- Linear Trend Model
We will average the predictions from the three models to create the ensemble forecast, although if we find that one model has generally lower MAD or MSE we might choose a weighted average with higher weights for the better performing model.
library(TTR)
library(forecast)
csv <- "https://s3.us-east-2.amazonaws.com/artificium.us/datasets/waterusage.csv"
# Load the data
water.usage <- read.csv(csv,
stringsAsFactors = F)
# Convert 'daily' to a time series object
daily.ts <- ts(water.usage$daily, start = c(2023, 8),
frequency = 12)
# Fit ARIMA model
arima.model <- auto.arima(daily.ts)
arima.forecast <- forecast(arima.model, h = 12)$mean
# Fit SES model
ses.model <- HoltWinters(daily.ts,
beta = FALSE,
gamma = FALSE)
ses.forecast <- forecast(ses.model, h = 12)$mean
# Fit Linear Trend model
t <- time(daily.ts)
linear.model <- lm(water.usage$daily ~ t)
future.time <- seq(from = (max(t) + 1/12),
to = (max(t) + 12/12),
by = 1/12)
linear.forecast <- predict(linear.model,
newdata = data.frame(time = future.time))
# Combine predictions (ensemble)
ensemble.forecast <- (arima.forecast + ses.forecast + linear.forecast) / 3
The forecast for the ensemble model is 433.7. Ensemble models combine predictions from multiple models to improve forecasting accuracy and robustness. By averaging the forecasts from ARIMA, SES, and Linear Trend models, we created an ensemble model that leverages the strengths of each individual model. This approach can lead to more accurate and reliable forecasts.
Seasonality Adjustment
Seasonality involves fluctuations that occur at regular intervals and are thus periodic. For example, retail sales might increase every December due to the holiday shopping season, or electricity consumption might peak every summer due to increased use of air conditioning. Furthermore, the length of the seasonal cycle is constant and known, i.e., it has a fixed frequency. Common seasonal frequencies include monthly (e.g., monthly sales data), quarterly (e.g., quarterly financial results or budgets), and annually (e.g., annual holiday sales periods). Because seasonality follows a regular pattern, it is predictable and can be identified and modeled to improve forecasting accuracy, i.e. forecasts can be adjusted to account for seasonal ups and downs.
Seasonality can be identified through visual inspection of time series plots or by using statistical methods such as autocorrelation plots. Visual inspection involves plotting the data and looking for regular patterns. Autocorrelation plots (ACF plots) show the correlation of the time series with its own past values and can highlight the presence of seasonality.
In time series forecasting models, seasonality can be accommodated additively or multiplicatively. An additive model adds a “seasonality adjustment” to each forecast that adjusts the forecast value up or down depending in which season the forecast value falls. A multiplicative model multiplies the forecast value by a “seasonality index” to make an adjustment of the forecast for a season.
Seasonality (or cyclicality) adjustment involves adjusting the forecast to account for period peaks and valleys, i.e. accounting for some seasons being above normal and some being below normal. This process helps in better understanding the underlying trend and making more accurate predictions.
There are two common ways to account for seasonality and to adjust forecasts for seasonality:
Multiplicative Adjustment: The forecast is multiplied by an adjustment factor (the “seasonality index”) depending for which season the forecast is. The adjustment factor is calculated from the data by finding the average difference between the mean and each season.
Additive Adjustment: This requires building a Linear Trend Line Model using both time and the season as factors in the regression. The forecast is adjusted by adding (or subtracting) a seasonality factor depending for which season the forecast is.
Let’s explore both of these techniques by considering the example below for a time series containing in-bound calls to a call center for four years by quarter. For example, in quarter 3 of year 2, the call center received 255 calls.
Q1 |
218 |
225 |
234 |
250 |
Q2 |
247 |
254 |
265 |
283 |
Q3 |
243 |
255 |
264 |
289 |
Q4 |
292 |
299 |
327 |
356 |
Identify Seasonality
The first step is not plot time series and use the visualization to determine whether their is cyclicality and what the season are.
library(ggplot2)
ggplot(callcenter.df,
aes(x = Period, y = Calls,
color = factor(Quarter), group = 1)) +
geom_line() +
geom_point() +
labs(title = "Quarterly Call Volume",
x = "Time Period",
y = "Number of Calls",
color = "Quarter") +
theme_minimal()
From the graph we can see that every Q4 (i.e., time period 4, 8, 12, and 16) is a peak where the number of calls spike, and Q1 of each year (i.e., time period 1, 5, 9, and 13). In addition, there is an overall upward trend, so a Linear Regression Trend Model would be an appropriate model to use; an alternative might be a Holt’s Linear Trend Model.
Multiplicative Adjustment
The process for a multiplicative seasonality adjusted forecast model requires a seasonality index (the multiplicative factor) and the process for calculating this factor is summarized below:
- For each one year period (the cycle), calculate the average across quarters (the seasons) within the cycle.
- For each time period, divide the actual seasonal demand by the average seasonal demand.
- Compute the average seasonality index for each season.
- Remove seasonality variance by calculating a forecast for the entire next year (cycle) and then divide that by the number of quarters (seasons) to get an average.
- Multiply the average forecast by the seasonality index for each season to get a forecast for the next year (cycle) per quarter (season).
Let’s start by calculating the seasonality index. The graphic below shows how to calculate the seasonality index for each season.
avg.calls <- aggregate(
x = callcenter.df[,c("Year","Calls")],
FUN = mean,
by = list(callcenter.df$Year)
)
avg.calls <- avg.calls[,2:3]
colnames(avg.calls) <- c("Year","AvgNumCalls")
for (t in 1:nrow(callcenter.df)) {
Y <- callcenter.df$Year[t]
SI <- avg.calls$AvgNumCalls[Y]
callcenter.df$SI[t] <- callcenter.df$Calls[t] / SI
}
avg.SI <- aggregate(
x = callcenter.df[,c("QuarterNum","SI")],
FUN = mean,
by = list(callcenter.df$Quarter)
)
Next, we’ll build a Linear Regression Trend Model; of course, we’ll need to make the data a time series rather than a 2D table. So, the time is Year 1/Quarter 1 followed by Year 1/Quarter 2, and so forth.
The data frame created looks like this (only the first and last three rows are shown):
dots.df <- data.frame(
Period = "...",
Calls = "...",
Quarter = "...",
Year = "..."
)
knitr::kable(
rbind(head(callcenter.df[,c(1,2,4,5)],3),
rbind(dots.df,
tail(callcenter.df[,c(1,2,4,5)],3))),
format = "simple",
align = "cccc",
row.names = FALSE
)
1 |
218 |
Q1 |
1 |
2 |
247 |
Q2 |
1 |
3 |
243 |
Q3 |
1 |
… |
… |
… |
… |
14 |
283 |
Q2 |
4 |
15 |
289 |
Q3 |
4 |
16 |
356 |
Q4 |
4 |
Now we can train the linear regression model.
trend.model.calls <- lm(callcenter.df$Calls ~ callcenter.df$Period)
# forecast quarterly calls for next four quarters
n <- nrow(callcenter.df) + 1 # time periods
fc <- c(4) # forecasts
b <- trend.model.calls$coefficients[[1]] # intercept
m <- trend.model.calls$coefficients[[2]] # slope
for (t in n:(n+3)) {
fc[t-n+1] <- m*t + b
}
Next, we need to average the forecasts and multiply by its seasonality index. This will be a forecast for time period 17 (the next time period) and that is a Q1, so the seasonality index for Q1 is 0.8627152:
mean.fc <- mean(fc)
adjusted.forecast <- mean.fc * avg.SI$SI[1]
The seasonality adjusted forecast for Quarter 1 of Year 5 is therefore 275 calls.
Additive Adjustment
An alternative approach to multiplicative seasonality adjustments is an additive model. This approach requires the use of a Linear Regression Trend Line Model, while a multiplicative model can be used with any forecasting method. In an additive model, we will add the season as a factor. Since it is a categorical factor it must be one-hot encoded.
One-hot encoding, also sometimes called “dummy encoding” as it introduced “dummy” or “placeholder” variables, transforms a categorical variable into a series of binary (0 or 1) columns. Each unique category value (level) becomes a new column, and a 1 or 0 is placed in the column depending on whether the categorical variable matches the column heading. If the categorical variable has n levels (i.e., n unique values), then we only need n-1 binary columns.
For the call center example, each quarter is a season, so “season” has four levels (Quarter 1 through Quarter 4), so we need to add three more columns to the data frame labeled “Q1”, “Q2”, and “Q3”. The quarterly seasons are then encoding as follows:
Q1 |
1 |
0 |
0 |
Q2 |
0 |
1 |
0 |
Q3 |
0 |
0 |
1 |
Q4 |
0 |
0 |
0 |
Note that Q4 is encoded as not Q1, not Q2,and not Q3.
Next, we will add the new dummy columns for the one-hot endoing to the callcenter.df
data frame. The packages dummies and caret provide functions for doing such as encoding, but we will not use those; instead, we will do the encoding ourselves using ifelse()
.
callcenter.df$Q1 <- ifelse(callcenter.df$Quarter == "Q1", 1, 0)
callcenter.df$Q2 <- ifelse(callcenter.df$Quarter == "Q2", 1, 0)
callcenter.df$Q3 <- ifelse(callcenter.df$Quarter == "Q3", 1, 0)
To build an additive model, we will train a linear regression trend model with the “dummy columns” for the season levels as additional independent variables.
trend.model.adjusted <- lm(
callcenter.df$Calls ~ callcenter.df$Period +
callcenter.df$Q1 +
callcenter.df$Q2 +
callcenter.df$Q3
)
summary(trend.model.adjusted)
##
## Call:
## lm(formula = callcenter.df$Calls ~ callcenter.df$Period + callcenter.df$Q1 +
## callcenter.df$Q2 + callcenter.df$Q3)
##
## Residuals:
## Min 1Q Median 3Q Max
## -12.1125 -4.4125 -0.6125 2.8313 15.3375
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 281.5625 5.7530 48.942 3.18e-14 ***
## callcenter.df$Period 3.6938 0.4288 8.614 3.21e-06 ***
## callcenter.df$Q1 -75.6688 5.5745 -13.574 3.25e-08 ***
## callcenter.df$Q2 -48.8625 5.4914 -8.898 2.34e-06 ***
## callcenter.df$Q3 -52.0562 5.4409 -9.568 1.15e-06 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 7.671 on 11 degrees of freedom
## Multiple R-squared: 0.9687, Adjusted R-squared: 0.9574
## F-statistic: 85.21 on 4 and 11 DF, p-value: 3.342e-08
Now, we can calculate a forecast for the next time period (time period 17, which is Quarter 1 of Year 5). Since that is a “Q1”, we will pass 1 for the variable Q1
in the model and 0 for the variables Q2
and Q3
. In other words, we will use 1 for the correct season or all 0 values if it is a “Q4”.
Notice how the coefficients are all negative as all quarters except Q4 are below the mean. Also, note that the coefficient for Q1 is the largest as it is the most below the mean.
b <- trend.model.adjusted$coefficients[[1]] # intercept
t <- trend.model.adjusted$coefficients[[2]] # time period coefficient
Q1 <- trend.model.adjusted$coefficients[[3]] # Q1 coefficient
Q2 <- trend.model.adjusted$coefficients[[4]] # Q1 coefficient
Q3 <- trend.model.adjusted$coefficients[[5]] # Q1 coefficient
F.17 <- (t * 17) + b + Q1*1 + Q2*0 + Q3*0
From this we get a seasonality adjusted forecast for Quarter 1 of Year 5 of 269 calls.
Forecast Interval
A forecast interval (or prediction interval) provides a range within which we expect future observations to fall with a certain probability. It quantifies the uncertainty around the point forecast, offering a more comprehensive view of potential future values. The width of the interval reflects the model’s uncertainty: wider intervals indicate greater uncertainty and consequently more risk in using the point forecast.
The Point Forecast is the single best estimate of the future value and what we have calculated thus far However, it is generally much better to report a range between a lower and upper value at a probability that the true value will fall within the forecast interval (e.g., 95%).
A 95% forecast interval implies that we are 95% confident that the true future value will lie within this interval. This is typically calculated as:
\(\text{Forecast Interval} = \text{Point Forecast} \pm Z_{\alpha/2} \times \text{Standard Error}\)
where:
- \(Z_{\alpha/2}\) is the critical value from the standard normal distribution for a 95% confidence level, approximately 1.96. Other values can be obtained from the standard distribution; the table below lists common values for common probabilities.
- Standard Error (SE) is the standard deviation of the forecast errors, reflecting the variability of the forecast.
# Calculate z-scores for different confidence levels
z_scores <- data.frame(
Confidence_Level = c("99%", "95%", "90%", "80%", "70%"),
Z_Score = c(
qnorm(0.995), # 99% confidence level
qnorm(0.975), # 95% confidence level
qnorm(0.95), # 90% confidence level
qnorm(0.90), # 80% confidence level
qnorm(0.85) # 70% confidence level
)
)
# Display the data frame using kable
knitr::kable(z_scores,
col.names = c("Confidence Level", "Z-Score"),
caption = "Z-Scores for Different Confidence Levels",
format = "simple",
digits = 3,
align = "cc",
label = NA)
Z-Scores for Different Confidence Levels
99% |
2.576 |
95% |
1.960 |
90% |
1.645 |
80% |
1.282 |
70% |
1.036 |
Forecasts produced by a Linear Regression Trend Model have a different way of calculating the forecast interval as it is a statistical method.The other methods are non-statistical and therefore do produce a value of the standard error and one has to be estimated.
LTRM Forecast Interval
For an LRTM the forecast interval is calculated as shown below. The example uses the water.usage
time series. The function summary(trend.model)
produces the residual standard error as one of its components: model.results$sigma
.
# Load necessary libraries
library(forecast)
csv <- "https://s3.us-east-2.amazonaws.com/artificium.us/datasets/waterusage.csv"
# Load the data
water.usage <- read.csv(csv,
stringsAsFactors = F)
# fit a linear trend model using linear regression
trend.model <- lm(water.usage$daily ~ water.usage$period)
print(trend.model)
##
## Call:
## lm(formula = water.usage$daily ~ water.usage$period)
##
## Coefficients:
## (Intercept) water.usage$period
## 379.939 7.304
# Forecast future values using the trend model
b <- trend.model$coefficients[[1]]
m <- trend.model$coefficients[[2]]
# forecast for next time period
forecast.val <- m*13 + b
model.results <- summary(trend.model)
SE <- model.results$sigma
z.score.95 <- qnorm(0.975) # two-tailed, so 0.05/2
lower95 <- forecast.val - (z.score.95 * SE)
upper95 <- forecast.val + (z.score.95 * SE)
So, for the LRTM we have a point forecast of 474.9 with a 95% forecast (prediction) interval ranging from 246.4 to 703.4. The range is quite wide indicating substantial uncertainty in the forecast and using the point forecast is thus risky. If someone were to budget for next month’s water use and they are “risk averse”, they’d be best served by using the upper range as it is more pessimistic in this situation.
Other Forecast Intervals
For all other methods, because they are not statistical, we need to use a different method since we do not have an standard error from the regression. We do, however, have residuals (errors) and can use those to derive an estimate of the standard error.
\(\sigma_E = \sqrt{\frac{\sum_{t=1}^{n}(E_t-\bar{E})^2}{n-1}}\)
Let’s demonstrate by calculating the 95% forecast interval for a WMA model.
# define the weights for the WMA (3-period example)
w <- c(0.1, 0.3, 0.6)
# calculate "forecast" for each prior time period
water.usage$WMA <- calculate.wma(water.usage$daily,
n = 3,
weights = w)
forecast.val <- water.usage$WMA[12]
periods <- nrow(water.usage)
# calculate the absolute errors
for (t in 3:periods) {
water.usage$E[t] <- water.usage$daily[t-1] - water.usage$WMA[t]
}
# get the errors for all but first two forecasts as those are NA
WMA.E <- water.usage$E[3:periods]
mean.E <- mean(WMA.E)
sigma <- sqrt(sum((WMA.E - mean.E)^2)/(length(WMA.E)))
z.score.95 <- qnorm(0.975) # two-tailed, so 0.05/2
lower95 <- forecast.val - (z.score.95 * sigma)
upper95 <- forecast.val + (z.score.95 * sigma)
So, for the WMA we have a point forecast of 496.9 with a 95% forecast (prediction) interval ranging from 266.1 to 727.7.
Tracking Signal
Forecasting models must be continually evaluated to assure that they still provide accurate forecast estimates.
A tracking signal (TS) is a measure used in forecasting to detect bias in the forecast. It helps in identifying whether the forecast model consistently overestimates or underestimates the actual values. It is defined as the ratio of the Cumulative Forecast Error (CFE) and the Mean Absolute Error (MAE). It should be calculated after each forecast and then evaluated to see if the forecast model requires re-training or further tuning.
\(\text{TS} = \frac{\text{CFE}}{\text{MAE}}\)
where
\(\text{CFE} = \sum_{t=1}^{n}(Y_t - F_t)\)
and
\(\text{MAE} = \sum_{t=1}^{n}(|Y_t - F_t|)\)
Interpretation of the tracking signal generally consider whether it is:
- Positive: which means that the forecasts are generally under predicting, i.e., \(Y > F\)
- Negative: which means that the forecasts are generally over predicting, i.e., \(Y < F\)
- Near Zero: which means that the forecast model is unbiased
A re-training and/or re-tuning of the forecast model is generally advisable when \(\text{TS} > ±4 \times MAE\) as that means that the errors are starting to far exceed the mean.
Example TS Calculation
Let’s use a simple example to demonstrate the calculation process for the tracking signal.
1 |
220 |
212 |
8 |
8 |
8 |
2 |
245 |
249 |
-4 |
4 |
4 |
3 |
270 |
260 |
10 |
14 |
10 |
4 |
261 |
252 |
9 |
23 |
9 |
5 |
283 |
276 |
7 |
30 |
7 |
6 |
301 |
292 |
9 |
39 |
9 |
The errors are the difference between the actual and the forecast values. The CFE is 39 and the MAE is 7.8, so the tracking signal is
\(\text{TS} = \frac{\text{CFE}}{\text{MAE}}=\frac{\text{39}}{\text{7.83}}=4.98\)
The tracking signal is positive so this means that the model that produced these forecasts is biased towards under predicting.
Calculating TS in R
As an example, let’s calculate TS for the previously built WMA Model for the water.usage
data set from Section Calculating WMA in R. The MAE was previously calculated in Section Calculating MAE for WMA Model.
We need two metrics:
- MAE, and
- CFE.
Since we have MAE in the variable MAE.WMA
from before, we only need to calculate CFE which is the sum of the forecast errors.
# calculate the errors
periods <- nrow(water.usage)
for (t in 3:periods) {
water.usage$abs.err[t] <- water.usage$daily[t-1] - water.usage$WMA[t]
}
# calculate sum of errors (CFE)
CFE <- sum(water.usage$abs.err, na.rm = T)
The CFE for this model’s first forecast is -77.9 and its MAE is 128.8, which makes the tracking signal -0.6. Since the tracking signal is negative, it means that the model, on average, over predicts. However, the tracking signal is well below 4 and thus within the expected range.
Summary: Forecasting
Forecasting is a multifaceted discipline that blends statistical methods with domain expertise. By understanding the underlying components of time series data and selecting appropriate forecasting models, one can make accurate predictions that are invaluable in many fields. R provides a rich ecosystem of packages and functions to implement and evaluate various forecasting methods, making it an common platform for this purpose.
In this lesson, we explored several key methods for forecasting, including Weighted Moving Average (WMA), Exponential Smoothing, ARIMA, and Regression Trend Line. We showed how to incorporate trend and seasonality into forecasts and how to use various metrics to evaluate and compare models. In addition, we explained how to tune models and combine them into ensemble models. Finally, we demonstrated the use of a tracking signal to detect forecast bias. Throughout the lesson, we emphasized the importance of these techniques and metrics in improving the accuracy and reliability of forecasts.
See Also
LS0tCnRpdGxlOiAiQmFzaWMgVGltZSBTZXJpZXMgRm9yZWNhc3RpbmciCnBhcmFtczoKICBjYXRlZ29yeTogMwogIHN0YWNrczogIjAsMCIKICBudW1iZXI6IDMwMwogIHRpbWU6IDQ1CiAgbGV2ZWw6IGJlZ2lubmVyCiAgdGFnczogUixzdGF0aXN0aWNzLHRpbWUgc2VyaWVzLGZvcmVjYXN0aW5nLE1BRCxNU0UsZnVuY3Rpb25zCiAgZGVzY3JpcHRpb246ICJJbnRyb2R1Y2VzIHRpbWUtc2VyaWVzIGZvcmVjYXN0aW5nIHVzaW5nIHdlaWdodGVkCiAgICAgICAgICAgICAgICBtb3ZpbmcgYXZlcmFnZXMsIGxpbmVhciByZWdyZXNzaW9uLCBhbmQgZXhwb25lbnRpYWwKICAgICAgICAgICAgICAgIHNtb290aGluZy4gU2hvd3MgaG93IHRvIGJ1aWxkIGEgZm9yZWNhc3RpbmcKICAgICAgICAgICAgICAgIG1vZGVsLCB0dW5lIG1vZGVscywgZXZhbHVhdGUgdGhlIG1vZGVsLCBjb25zdHJ1Y3QgZW5zZW1ibGVzLAogICAgICAgICAgICAgICAgcHJvdmlkZSBmb3JlY2FzdCBpbnRlcnZhbHMsIGFjY291bnQgZm9yIHRyZW5kIGFuZAogICAgICAgICAgICAgICAgc2Vhc29uYWxpdHkuIgpkYXRlOiAiPHNtYWxsPmByIFN5cy5EYXRlKClgPC9zbWFsbD4iCmF1dGhvcjogIjxzbWFsbD5NYXJ0aW4gU2NoZWRsYmF1ZXI8L3NtYWxsPiIKZW1haWw6ICJtLnNjaGVkbGJhdWVyQG5ldS5lZHUiCmFmZmlsaXRhdGlvbjogIk5vcnRoZWFzdGVybiBVbml2ZXJzaXR5IgpvdXRwdXQ6IAogIGJvb2tkb3duOjpodG1sX2RvY3VtZW50MjoKICAgIHRvYzogdHJ1ZQogICAgdG9jX2Zsb2F0OiB0cnVlCiAgICBjb2xsYXBzZWQ6IGZhbHNlCiAgICBudW1iZXJfc2VjdGlvbnM6IGZhbHNlCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlCiAgICB0aGVtZTogcGFwZXIKICAgIGhpZ2hsaWdodDogdGFuZ28KICAgIGNvZGVfZm9sZGluZzogc2hvdwotLS0KCi0tLQp0aXRsZTogIjxzbWFsbD5gciBwYXJhbXMkY2F0ZWdvcnlgLmByIHBhcmFtcyRudW1iZXJgPC9zbWFsbD48YnIvPjxzcGFuIHN0eWxlPSdjb2xvcjogIzJFNDA1MzsgZm9udC1zaXplOiAwLjllbSc+YHIgcm1hcmtkb3duOjptZXRhZGF0YSR0aXRsZWA8L3NwYW4+IgotLS0KCmBgYHtyIGNvZGU9eGZ1bjo6cmVhZF91dGY4KHBhc3RlMChoZXJlOjpoZXJlKCksJy9SL19pbnNlcnQyREIuUicpKSwgaW5jbHVkZSA9IEZBTFNFfQpgYGAKCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQoKIyMgTGVhcm5pbmcgT2JqZWN0aXZlcwoKQXQgdGhlIGNvbmNsdXNpb24gb2YgdGhpcyBsZXNzb24sIHlvdSB3aWxsIGJlIGFibGUgdG86CgotICAgZGVmaW5lIHRoZSBuZWVkIGZvciBmb3JlY2FzdGluZwotICAgZGlzdGluZ3Vpc2ggZm9yZWNhc3RpbmcgZnJvbSBwcmVkaWN0aXZlIG1vZGVsaW5nCi0gICBjcmVhdGUgbW92aW5nIGF2ZXJhZ2UsIGV4cG9uZW50aWFsIHNtb290aGluZywgYW5kIHRyZW5kIG1vZGVscwotICAgYWNjb3VudCBmb3IgdHJlbmQgYW5kIHNlYXNvbmFsaXR5Ci0gICBldmFsdWF0ZSBtb2RlbHMgdXNpbmcgTUFFLCBNU0UsIGFuZCBNQVBFCi0gICB0dW5lIG1vZGVsIGh5cGVycGFyYW1ldGVycwotICAgY29uc3RydWN0IGVuc2VtYmxlIG1vZGVscwoKLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCgojIyBJbnRyb2R1Y3Rpb24KCkZvcmVjYXN0aW5nIGlzIGEgc3BlY2lmaWMgd2F5IHRvIGJ1aWxkIHByZWRpY3RpdmUgbW9kZWxzIHRoYXQgcmVseSBvbiB0cmVuZHMgaW4gdGltZSBzZXJpZXMgZGF0YS4gVGhleSBhcmUgb2Z0ZW4gdXNlZCBpbiBmaW5hbmNlLCBlY29ub21pY3MsIGFuZCB2YXJpb3VzIGJyYW5jaGVzIG9mIHNjaWVuY2Ugd2hlcmUgcHJlZGljdGluZyBmdXR1cmUgdmFsdWVzIGJhc2VkIG9uIGhpc3RvcmljYWwgdGltZSBzZXJpZXMgZGF0YSBpcyBlc3NlbnRpYWwgYW5kIGZlYXNpYmxlLiBGb3JlY2FzdGluZyBpbnZvbHZlcyBtYWtpbmcgcHJlZGljdGlvbnMgYWJvdXQgZnV0dXJlIGV2ZW50cyBiYXNlZCBvbiBoaXN0b3JpY2FsIGRhdGEuIFRoZSBwcmltYXJ5IGdvYWwgaXMgdG8gaWRlbnRpZnkgcGF0dGVybnMgYW5kIHRyZW5kcyB3aXRoaW4gdGhlIGRhdGEgdGhhdCBjYW4gYmUgZXh0cmFwb2xhdGVkIHRvIHByZWRpY3QgZnV0dXJlIG91dGNvbWVzLiBBY2N1cmF0ZSBmb3JlY2FzdGluZyBjYW4gZ3VpZGUgZGVjaXNpb24tbWFraW5nIHByb2Nlc3NlcyBpbiB2YXJpb3VzIGZpZWxkcywgZnJvbSBzdG9jayBtYXJrZXQgYW5hbHlzaXMgdG8gd2VhdGhlciBwcmVkaWN0aW9uIGFuZCBpbnZlbnRvcnkgbWFuYWdlbWVudC4KCkluIHRoaXMgbGVzc29uLCB3ZSB3aWxsIGRlbHZlIGludG8gdGhlIGZ1bmRhbWVudGFsIHByaW5jaXBsZXMgb2YgZm9yZWNhc3RpbmcsIGV4cGxvcmUgdmFyaW91cyBmb3JlY2FzdGluZyBtZXRob2RzLCBhbmQgZGVtb25zdHJhdGUgcHJhY3RpY2FsIGltcGxlbWVudGF0aW9ucyB1c2luZyBSLiBNYW55IG9mIHRoZSB0ZWNobmlxdWVzIGV4cGxvcmVkIGluIHRoaXMgbGVzc29uIGFsc28gYXBwbHkgdG8gY29uc3RydWN0ZWQgcHJlZGljdGl2ZSBtb2RlbHMgZm9yIGNsYXNzaWZpY2F0aW9uIGFuZCByZWdyZXNzaW9uIHVzaW5nIHN1cGVydmlzZWQgbWFjaGluZSBsZWFybmluZy4KCkZvcmVjYXN0aW5nIGFuZCBwcmVkaWN0aXZlIG1vZGVsaW5nIGFyZSByZWxhdGVkIGNvbmNlcHRzIGluIGRhdGEgc2NpZW5jZSBhbmQgbWFjaGluZSBsZWFybmluZywgYnV0IHRoZXkgaGF2ZSBzb21lIGtleSBkaWZmZXJlbmNlcy4gRm9yZWNhc3RpbmcgcHJpbWFyaWx5IGZvY3VzZXMgb24gZXN0aW1hdGluZyBmdXR1cmUgdmFsdWVzIG9mIGEgc3BlY2lmaWMgdmFyaWFibGUgb3Igc2V0IG9mIHZhcmlhYmxlcyBiYXNlZCBvbiBoaXN0b3JpY2FsIGRhdGEgYW5kIHRyZW5kcy4gSXQgaXMgb2Z0ZW4gdXNlZCBmb3IgdGltZSBzZXJpZXMgZGF0YSBhbmQgaXMgcGFydGljdWxhcmx5IGNvbW1vbiBpbiBmaWVsZHMgbGlrZSBlY29ub21pY3MsIGZpbmFuY2UsIGFuZCB3ZWF0aGVyIHByZWRpY3Rpb24uIFByZWRpY3RpdmUgbW9kZWxpbmcsIG9uIHRoZSBvdGhlciBoYW5kLCBpcyBhIGJyb2FkZXIgY29uY2VwdCB0aGF0IGludm9sdmVzIHVzaW5nIHN0YXRpc3RpY2FsIGFsZ29yaXRobXMgYW5kIG1hY2hpbmUgbGVhcm5pbmcgdGVjaG5pcXVlcyB0byBpZGVudGlmeSBwYXR0ZXJucyBpbiBkYXRhIGFuZCBtYWtlIHByZWRpY3Rpb25zIGFib3V0IGZ1dHVyZSBvdXRjb21lcyBvciB0byBjbGFzc2lmeSBuZXcgaW5zdGFuY2VzLiBJdCBjYW4gYmUgYXBwbGllZCB0byBhIHdpZGUgcmFuZ2Ugb2YgcHJvYmxlbXMsIG5vdCBqdXN0IHRpbWUgc2VyaWVzIGRhdGEuIE5ldmVydGhlbGVzcywgaW4gYm90aCBtb2RlbGluZyBlZmZvcnRzIGRhdGEgaXMgdXNlZCB0byAidHJhaW4gYSBtb2RlbCIgb24gaGlzdG9yaWNhbCBkYXRhIHRvIG1ha2UgYSBwcmVkaWN0aW9uIG9yIGRlcml2ZSBhbiBlc3RpbWF0ZS4KCkZvcmVjYXN0aW5nIHR5cGljYWxseSBkZWFscyB3aXRoIHRpbWUgc2VyaWVzIGRhdGEsIHdoZXJlIG9ic2VydmF0aW9ucyBhcmUgY29sbGVjdGVkIHNlcXVlbnRpYWxseSBvdmVyIHRpbWUuIFRoZSB0ZW1wb3JhbCBhc3BlY3QgaXMgY3J1Y2lhbCwgYW5kIHRoZSBvcmRlciBvZiBkYXRhIHBvaW50cyBtYXR0ZXJzLiBQcmVkaWN0aXZlIG1vZGVsaW5nIGNhbiB3b3JrIHdpdGggdmFyaW91cyB0eXBlcyBvZiBkYXRhLCBpbmNsdWRpbmcgc3RydWN0dXJlZCAodGFidWxhciksIHVuc3RydWN0dXJlZCAodGV4dCwgaW1hZ2VzKSwgYW5kIHRpbWUgc2VyaWVzIGRhdGEuIEZvciBwcmVkaWN0aXZlIG1vZGVsaW5nLCB0aGUgb3JkZXIgb2YgZGF0YSBwb2ludHMgbWF5IG9yIG1heSBub3QgYmUgcmVsZXZhbnQsIGRlcGVuZGluZyBvbiB0aGUgc3BlY2lmaWMgcHJvYmxlbSwgYnV0IGl0IGNlcnRhaW5seSBtYXR0ZXJzIGZvciBmb3JlY2FzdGluZy4KCkZvcmVjYXN0aW5nIG9mdGVuIGVtcGxveXMgc2ltcGxlIG1ldGhvZHMgc3VjaCBhcyB3ZWlnaHRlZCBhdmVyYWdlcywgc3RhdGlzdGljYWwgbWV0aG9kcyBsaWtlIEFSSU1BIChBdXRvUmVncmVzc2l2ZSBJbnRlZ3JhdGVkIE1vdmluZyBBdmVyYWdlKSwgZXhwb25lbnRpYWwgc21vb3RoaW5nLCBhbmQgbW9yZSBhZHZhbmNlZCB0ZWNobmlxdWVzIGxpa2UgUHJvcGhldCBvciBMU1RNIChMb25nIFNob3J0LVRlcm0gTWVtb3J5KSBuZXVyYWwgbmV0d29ya3MgZm9yIGNvbXBsZXggdGltZSBzZXJpZXMuIE9uIHRoZSBvdGhlciBoYW5kLCBwcmVkaWN0aXZlIG1vZGVsaW5nIGVuY29tcGFzc2VzIGEgYnJvYWRlciByYW5nZSBvZiBhbGdvcml0aG1zLCBpbmNsdWRpbmcgcmVncmVzc2lvbiwgY2xhc3NpZmljYXRpb24sIGNsdXN0ZXJpbmcsIGFuZCBkZWVwIGxlYXJuaW5nIHRlY2huaXF1ZXMuIEl0IG1heSB1c2UgbWV0aG9kcyBsaWtlIG5lYXJlc3QgbmVpZ2hib3JzLCBCYXllc2lhbiBuZXR3b3JrcywgZGVjaXNpb24gdHJlZXMsIHJhbmRvbSBmb3Jlc3RzLCBzdXBwb3J0IHZlY3RvciBtYWNoaW5lcywgbmV1cmFsIG5ldHdvcmtzLCBhbmQgdmFyaW91cyBlbnNlbWJsZSBtZXRob2RzLgoKRm9yZWNhc3RpbmcgdHlwaWNhbGx5IGZvY3VzZXMgb24gcHJlZGljdGluZyBmdXR1cmUgdmFsdWVzIGF0IHNwZWNpZmljIHRpbWUgcG9pbnRzIG9yIG92ZXIgYSBkZWZpbmVkIHBlcmlvZC4gVGhlIHRpbWUgaG9yaXpvbiBjYW4gYmUgc2hvcnQtdGVybSwgbWVkaXVtLXRlcm0sIG9yIGxvbmctdGVybSwgZGVwZW5kaW5nIG9uIHRoZSBhcHBsaWNhdGlvbi4gUHJlZGljdGl2ZSBtb2RlbGluZyBkb2Vzbid0IG5lY2Vzc2FyaWx5IGhhdmUgYSBmaXhlZCB0aW1lIGhvcml6b24sIGluc3RlYWQgaXQgZXN0aW1hdGVzIGEgbnVtZXJpYyB2YWx1ZSBvciBhc3NpZ25zIHNvbWUgb2JzZXJ2YXRpb24gdG8gYSBjbGFzcy4KCkZvcmVjYXN0aW5nIG9mdGVuIGVtcGhhc2l6ZXMgcXVhbnRpZnlpbmcgdW5jZXJ0YWludHkgaW4gcHJlZGljdGlvbnMsIHByb3ZpZGluZyBwcmVkaWN0aW9uIGludGVydmFscyBhbG9uZyB3aXRoIHBvaW50IGVzdGltYXRlcy4gUHJlZGljdGl2ZSBtb2RlbGluZyBtYXkgb3IgbWF5IG5vdCBleHBsaWNpdGx5IHF1YW50aWZ5IHVuY2VydGFpbnR5LCBkZXBlbmRpbmcgb24gdGhlIGFsZ29yaXRobSB1c2VkLiBTb21lIG1vZGVscyBwcm92aWRlIHByb2JhYmlsaXR5IGVzdGltYXRlcyBvciBjb25maWRlbmNlIHNjb3Jlcywgd2hpbGUgb3RoZXJzIG1heSBvbmx5IGdpdmUgcG9pbnQgcHJlZGljdGlvbnMuCgpGb3JlY2FzdGluZyBtb2RlbHMgb2Z0ZW4gcHJpb3JpdGl6ZSBpbnRlcnByZXRhYmlsaXR5LCBhcyB1bmRlcnN0YW5kaW5nIHRoZSBmYWN0b3JzIGRyaXZpbmcgZnV0dXJlIHRyZW5kcyBpcyBlc3NlbnRpYWwgaW4gbWFueSBhcHBsaWNhdGlvbnMuIFByZWRpY3RpdmUgbW9kZWxpbmcgY2FuIHJhbmdlIGZyb20gaGlnaGx5IGludGVycHJldGFibGUgKCplLmcuKiwgbXVsdGlwbGUgcmVncmVzc2lvbiwgZGVjaXNpb24gdHJlZXMpIHRvIG1vcmUgYmxhY2stYm94IGFwcHJvYWNoZXMgKCplLmcuKiwgZGVlcCBuZXVyYWwgbmV0d29ya3MpLCBkZXBlbmRpbmcgb24gdGhlIGNvbXBsZXhpdHkgb2YgdGhlIHByb2JsZW0gYW5kIHRoZSBjaG9zZW4gYWxnb3JpdGhtLgoKSW4gcHJhY3RpY2UsIHRoZXJlJ3Mgb2Z0ZW4gYW4gb3ZlcmxhcCBiZXR3ZWVuIGZvcmVjYXN0aW5nIGFuZCBwcmVkaWN0aXZlIG1vZGVsaW5nLCBlc3BlY2lhbGx5IHdoZW4gZGVhbGluZyB3aXRoIHRpbWUgc2VyaWVzIGRhdGEuIE1hbnkgbW9kZXJuIGFwcHJvYWNoZXMgY29tYmluZSBlbGVtZW50cyBvZiBib3RoIHRvIGNyZWF0ZSBtb3JlIHBvd2VyZnVsIGFuZCBmbGV4aWJsZSBtb2RlbHMuCgojIyBUaW1lIFNlcmllcyBEYXRhCgpDZW50cmFsIHRvIGZvcmVjYXN0aW5nIGlzIHRpbWUgc2VyaWVzIGRhdGEsIHdoaWNoIGlzIGEgc2VxdWVuY2Ugb2YgZGF0YSBwb2ludHMgY29sbGVjdGVkIG9yIHJlY29yZGVkIGF0IHNwZWNpZmljIHRpbWUgaW50ZXJ2YWxzLiBUaW1lIHNlcmllcyBkYXRhIGNhbiBleGhpYml0IHZhcmlvdXMgY29tcG9uZW50cyBzdWNoIGFzIHRyZW5kLCBzZWFzb25hbGl0eSwgYW5kIG5vaXNlLiBVbmRlcnN0YW5kaW5nIHRoZXNlIGNvbXBvbmVudHMgaXMgY3JpdGljYWwgZm9yIGJ1aWxkaW5nIGVmZmVjdGl2ZSBmb3JlY2FzdGluZyBtb2RlbHMuCgoxLiAgKipUcmVuZCoqOiBUaGUgbG9uZy10ZXJtIG1vdmVtZW50IGluIHRoZSBkYXRhLgoyLiAgKipTZWFzb25hbGl0eSoqOiBSZWd1bGFyLCByZXBlYXRpbmcgcGF0dGVybnMgb3IgY3ljbGVzIGluIHRoZSBkYXRhLgozLiAgKipOb2lzZSoqOiBSYW5kb20gdmFyaWFiaWxpdHkgaW4gdGhlIGRhdGEgdGhhdCBjYW5ub3QgYmUgZXhwbGFpbmVkIGJ5IHRoZSBtb2RlbC4KCioqU2Vhc29uYWxpdHkqKiByZWZlcnMgdG8gdGhlIHByZXNlbmNlIG9mIHJlZ3VsYXIsIHByZWRpY3RhYmxlIHBhdHRlcm5zIG9yIGN5Y2xlcyB0aGF0IHJlcGVhdCBvdmVyIGEgc3BlY2lmaWMgcGVyaW9kIHdpdGhpbiBhIHRpbWUgc2VyaWVzIGRhdGFzZXQuIFRoZXNlIHBhdHRlcm5zIGFyZSBvZnRlbiBpbmZsdWVuY2VkIGJ5IGV4dGVybmFsIGZhY3RvcnMgc3VjaCBhcyB3ZWF0aGVyLCBob2xpZGF5cywgb3IgZWNvbm9taWMgY3ljbGVzLCBhbmQgdGhleSBjYW4gc2lnbmlmaWNhbnRseSBhZmZlY3QgdGhlIGJlaGF2aW9yIG9mIHRoZSBkYXRhLiBTZWFzb25hbGl0eSBhZGp1c3RtZW50IGlzIGFuIGVzc2VudGlhbCBjb25jZXB0IGluIHRpbWUgc2VyaWVzIGZvcmVjYXN0aW5nIGJlY2F1c2UgaXQgaGVscHMgaWRlbnRpZnkgYW5kIGFjY291bnQgZm9yIHRoZXNlIHBlcmlvZGljIGZsdWN0dWF0aW9ucywgbGVhZGluZyB0byBtb3JlIGFjY3VyYXRlIHByZWRpY3Rpb25zLgoKKipUcmVuZCoqIGluIGZvcmVjYXN0aW5nIHJlZmVycyB0byB0aGUgbG9uZy10ZXJtIG1vdmVtZW50IG9yIGRpcmVjdGlvbiBpbiB0aW1lIHNlcmllcyBkYXRhIG92ZXIgYSBwZXJpb2QuIEl0IHJlcHJlc2VudHMgdGhlIHVuZGVybHlpbmcgcGF0dGVybiBvZiBncm93dGggb3IgZGVjbGluZSB0aGF0IHBlcnNpc3RzIG92ZXIgdGltZSwgaWdub3Jpbmcgc2hvcnQtdGVybSBmbHVjdHVhdGlvbnMgYW5kIG5vaXNlLiBJZGVudGlmeWluZyBhbmQgbW9kZWxpbmcgdHJlbmRzIGlzIHBhcmFtb3VudCBmb3IgYWNjdXJhdGUgdGltZSBzZXJpZXMgZm9yZWNhc3RpbmcsIGFzIGl0IGhlbHBzIGluIHVuZGVyc3RhbmRpbmcgdGhlIGdlbmVyYWwgZGlyZWN0aW9uIGluIHdoaWNoIHRoZSBkYXRhIGlzIG1vdmluZy4KClRoZSBjb21wb25lbnRzIGFyZSBvZnRlbiBkaXNjb3ZlcmVkIHRocm91Z2ggdmlzdWFsaXphdGlvbi4gRm9yIGV4YW1wbGUsIHRoZSBsaW5lIGNoYXJ0IGJlbG93IHNob3dzIGEgdGltZSBzZXJpZXMgb2YgYXZlcmFnZSBkYWlseSB3YXRlciB1c2FnZSBvdmVyIHRoZSBjb3Vyc2Ugb2Ygc2V2ZXJhbCBtb250aHMuCgpgYGB7ciBwbG90VGltZVNlcmllcywgZXZhbCA9IFQsIGNsYXNzLnNvdXJjZSA9ICdmb2xkLWhpZGUnfQojIGxvYWQgZGF0YSBmcm9tIENTVgpjc3YgPC0gImh0dHBzOi8vczMudXMtZWFzdC0yLmFtYXpvbmF3cy5jb20vYXJ0aWZpY2l1bS51cy9kYXRhc2V0cy93YXRlcnVzYWdlLmNzdiIKd2F0ZXIudXNhZ2UudHMgPC0gcmVhZC5jc3YoY3N2LAogICAgICAgICAgICAgICAgICAgICAgICAgICBzdHJpbmdzQXNGYWN0b3JzID0gRikKCmxpYnJhcnkoZ2dwbG90MikKCiMgQ29udmVydCAncGVyaW9kJyB0byBhIGZhY3RvciB0byBtYWludGFpbiB0aGUgb3JkZXIgaW4gdGhlIHBsb3QKd2F0ZXIudXNhZ2UudHMkcGVyaW9kIDwtIGFzLmZhY3Rvcih3YXRlci51c2FnZS50cyRwZXJpb2QpCgojIExpbmUgcGxvdCBmb3IgZGFpbHkgdXNhZ2Ugb3ZlciBwZXJpb2RzCmdncGxvdCh3YXRlci51c2FnZS50cywgYWVzKHggPSBwZXJpb2QsIHkgPSBkYWlseSwgZ3JvdXAgPSAxKSkgKwogIGdlb21fbGluZShjb2xvciA9ICJncmVlbiIpICsKICBnZW9tX3BvaW50KGNvbG9yID0gIm9yYW5nZSIpICsKICBsYWJzKHRpdGxlID0gIkF2ZXJhZ2UgRGFpbHkgV2F0ZXIgVXNhZ2UgT3ZlciBUaW1lIiwgeCA9ICJQZXJpb2QiLCB5ID0gIkRhaWx5IFVzYWdlIChsdHIpIikgKwogIHRoZW1lX21pbmltYWwoKQoKYGBgCgpUaGUgdGltZSBzZXJpZXMgc2hvd3Mgc29tZSBwZWFrcyBhbmQgdmFsbGV5cyB3aGljaCBtaWdodCBpbmRpY2F0ZSBzZWFzb25hbGl0eSwgYnV0IHRoZXJlIGlzIG5vIG92ZXJhbGwgdXB3YXJkIG9yIGRvd253YXJkIHRyZW5kLiBUaGVzZSBjaGFyYWN0ZXJpc3RpY3Mgd2lsbCBkZXRlcm1pbmUgd2hpY2ggZm9yZWNhc3RpbmcgbW9kZWwgd2lsbCBiZSBtb3N0IGFwcHJvcHJpYXRlLgoKIyMgRm9yZWNhc3RpbmcgTW9kZWxzCgpBIGZvcmVjYXN0aW5nIG1vZGVsIHVzZXMgYSBzZXJpZXMvc2VxdWVuY2Ugb2YgZGF0YSB2YWx1ZXMgbWVhc3VyaW5nIHNvbWUgZXZlbnQgb3ZlciB0aW1lIChyZWZlcnJlZCB0byBhcyAqdGltZSBzZXJpZXMqIGRhdGEpIGFuZCB1c2luZyB0aGUgc2VxdWVuY2UgdG8gImVzdGltYXRlIiB0aGUgbmV4dCBmZXcgdmFsdWVzIGluIHRoZSBzZXF1ZW5jZS4gRm9yZWNhc3Rpbmcgc29sZWx5IGZvY3VzZXMgb24gdGhlIHRyZW5kIG9mIHRoZSBkYXRhIHZhbHVlcyBvdmVyIHRpbWUsIHdoaWxlIG90aGVyIG1vZGVscyBpbmNsdWRlIGFkZGl0aW9uYWwgZGF0YS4gRm9yIGV4YW1wbGUsIGlmIGEgcmVhbCBlc3RhdGUgYWdlbnQgaGFzIGF2ZXJhZ2UgbW9udGhseSBob21lIHNhbGVzIHByaWNlcyBmb3IgdGhlIHBhc3QgZmlmdGVlbiB5ZWFycywgdGhleSBjYW4gdXNlIGEgZm9yZWNhc3RpbmcgbW9kZWwgdG8gInByZWRpY3QiIHRoZSBhdmVyYWdlIHNhbGVzIHByaWNlIGZvciB0aGUgbmV4dCBmZXcgbW9udGhzLiBCdXQgdGhhdCAicHJlZGljdGlvbiIgd291bGQgbm90IHRha2UgaW50byBhY2NvdW50IGFueSBvdGhlciBmYWN0b3JzLCBzbyBpdCB3b3VsZCBub3QgYmUgdXNlZnVsIHRvIHByZWRpY3QgdGhlIGxpa2VseSBzZWxsaW5nIHByaWNlIGZvciBhIHNwZWNpZmljIGhvbWUgaGF2aW5nIGEgc3BlY2lmaWMgbG90IHNpemUsIGxpdmluZyBhcmVhLCBwb29sLCBhZ2Ugb2YgaG9tZSwgKmV0Yy4qIEEgcHJlZGljdGl2ZSBtb2RlbCBjb25zdHJ1Y3RlZCB1c2luZyBhIG1hY2hpbmUgbGVhcm5pbmcgYWxnb3JpdGhtIHdvdWxkIHRha2UgdGhvc2Ugb3RoZXIgImZlYXR1cmVzIiBpbnRvIGFjY291bnQ7IHRob3NlIHR5cGVzIG9mIG1vZGVscyBhcmUgb2Z0ZW4gcmVmZXJyZWQgdG8gYXMgKmNhdXNhbCBtb2RlbHMqLgoKU2V2ZXJhbCBtYXRoZW1hdGljYWwgbWV0aG9kcyBleGlzdCBmb3IgZm9yZWNhc3RpbmcsIHJhbmdpbmcgZnJvbSBzaW1wbGUgZXh0cmFwb2xhdGlvbiB0ZWNobmlxdWVzIHRvIGNvbXBsZXggbWFjaGluZSBsZWFybmluZyBtb2RlbHMuIEluIHRoaXMgbGVzc29uLCB3ZSB3aWxsIGZvY3VzIG9uIHRoZSBmb2xsb3dpbmcgY29tbW9uIGFwcHJvYWNoZXM6CgoxLiAgU2ltcGxlIE1vdmluZyBBdmVyYWdlcyAoU01BKQoyLiAgV2VpZ2h0ZWQgTW92aW5nIEF2ZXJhZ2VzIChXTUEpCjMuICBTaW1wbGUgRXhwb25lbnRpYWwgU21vb3RoaW5nIChTRVMpCjQuICBIb2x0J3MgTGluZWFyIFRyZW5kIE1vZGVsIChITFRNKQo1LiAgTGluZWFyIFJlZ3Jlc3Npb24gVHJlbmQgTW9kZWwgKExSVE0pCjYuICBBdXRvUmVncmVzc2l2ZSBJbnRlZ3JhdGVkIE1vdmluZyBBdmVyYWdlIChBUklNQSkKCkluIGFkZGl0aW9uLCB3ZSB3aWxsIGV4cGxhaW4gaG93IHRvIHR1bmUgYW5kIGV2YWx1YXRlIGEgbW9kZWwsIGFjY291bnQgZm9yIHRyZW5kIGFuZCBzZWFzb25hbGl0eSBpbiB0aGUgZGF0YSwgY29tYmluZSBtdWx0aXBsZSBmb3JlY2FzdGluZyBtZXRob2RzIGludG8gYW4gZW5zZW1ibGUsIGFuZCBwcm92aWRlIGEgZm9yZWNhc3QgaW50ZXJ2YWwgaW4gYWRkaXRpb24gdG8gYSBwb2ludCBlc3RpbWF0ZS4KCiMjIFNhbXBsZSBEYXRhCgpUaGUgZXhhbXBsZXMgaW4gdGhlIGVuc3Vpbmcgc2VjdGlvbnMgdXNlIHRoZSB0aW1lIHNlcmllcyBkYXRhIGluIHRoZSBDU1YgZmlsZSBbd2F0ZXJ1YWdlLmNzdl0oJTIyaHR0cHM6Ly9zMy51cy1lYXN0LTIuYW1hem9uYXdzLmNvbS9hcnRpZmljaXVtLnVzL2RhdGFzZXRzL3dhdGVydXNhZ2UuY3N2JTIyKS4gVGhlIGZpcnN0IGZldyByb3dzIGFyZSBzaG93biBiZWxvdzoKCmBgYHtyIGRpc3BsYXlDU1YsIGV2YWwgPSBULCBjbGFzcy5zb3VyY2UgPSAnZm9sZC1oaWRlJ30KIyBsb2FkIGRhdGEgZnJvbSBDU1YKY3N2IDwtICJodHRwczovL3MzLnVzLWVhc3QtMi5hbWF6b25hd3MuY29tL2FydGlmaWNpdW0udXMvZGF0YXNldHMvd2F0ZXJ1c2FnZS5jc3YiCndhdGVyLnVzYWdlLnRzIDwtIHJlYWQuY3N2KGNzdiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RyaW5nc0FzRmFjdG9ycyA9IEYpCgojIGRpc3BsYXkgZmlyc3QgZmV3IHJvd3MKa25pdHI6OmthYmxlKGhlYWQod2F0ZXIudXNhZ2UudHMsIDQpLAogICAgICAgICAgICAgZm9ybWF0ID0gInNpbXBsZSIsCiAgICAgICAgICAgICBhbGlnbiA9ICJjIikKYGBgCgpUaGUgZGF0YSBmaWxlIGNvbnRhaW5zIGF2ZXJhZ2UgZGFpbHkgd2F0ZXIgdXNlIChpbiAqbGl0ZXJzKikgb3ZlciB0aGUgY291cnNlIG9mIHNldmVyYWwgbW9udGhzLgoKIyMgU2ltcGxlIE1vdmluZyBBdmVyYWdlCgpUaGlzIGlzIHRoZSB1bndlaWdodGVkIG1lYW4gb2YgdGhlIHByZXZpb3VzICRuJCBkYXRhIHBvaW50cy4gQSBTaW1wbGUgTW92aW5nIEF2ZXJhZ2UgKFNNQSkgaXMgYSBiYXNpYyBhcHByb2FjaCB0byB0aW1lIHNlcmllcyBhbmFseXNpcyB3aGljaCBzbW9vdGhlcyBvdXQgc2hvcnQtdGVybSBmbHVjdHVhdGlvbnMuIEl0IGlzIGNhbGN1bGF0ZWQgYnkgYXZlcmFnaW5nIGEgZml4ZWQgbnVtYmVyIG9mIHBhc3Qgb2JzZXJ2YXRpb25zLgoKVGhlIFNNQSBmb3IgYSBnaXZlbiB0aW1lIHBlcmlvZCBpcyBjYWxjdWxhdGVkIGJ5IHRha2luZyB0aGUgYXJpdGhtZXRpYyBtZWFuIG9mIHRoZSBsYXN0ICRuJCBvYnNlcnZhdGlvbnMuIEl0IGNhbiBiZSByZXByZXNlbnRlZCBtYXRoZW1hdGljYWxseSBhczoKCiRcdGV4dHtTTUF9X3QgPSBcZnJhY3sxfXtufSBcc3VtX3tpPTB9XntuLTF9IHlfe3QtaX0kCgp3aGVyZToKCi0gICAkU01BX3QkIGlzIHRoZSBTaW1wbGUgTW92aW5nIEF2ZXJhZ2UgYXQgdGltZSAkdCQKLSAgICRuJCBpcyB0aGUgbnVtYmVyIG9mIHBlcmlvZHMgb3ZlciB3aGljaCB0aGUgYXZlcmFnZSBpcyBjYWxjdWxhdGVkCi0gICAkeV97dC1pfSQgYXJlIHRoZSB2YWx1ZXMgb2YgdGhlIHRpbWUgc2VyaWVzIGF0IHRpbWUgJHQsIHQtMSwgdC0yLCBcbGRvdHMsIHQtKG4tMSkkCgpGb3IgZXhhbXBsZSwgc3VwcG9zZSB3ZSBoYXZlIGEgdGltZSBzZXJpZXMgb2YgZGFpbHkgd2F0ZXIgdXNhZ2UgZm9yIHRoZSBwYXN0IDcgZGF5czogWzExMCwgOTAsIDEwMCwgMTIwLCAxNDAsIDE2MCwgMTgwXS4gVG8gY2FsY3VsYXRlIHRoZSAzLWRheSBTTUEgYXQgdGhlIGVuZCBvZiB0aGlzIHNlcmllcyB0byBnZXQgYSBmb3JlY2FzdCBmb3IgdGltZSBwZXJpb2QgOCAodGhlIG5leHQgdGltZSBwZXJpb2QpOgoKJHQoOCkgPSBcZnJhY3sxNDAgKyAxNjAgKyAxODB9ezN9ID0gXGZyYWN7NDgwfXszfSA9IDE2MCQKCk5hdHVyYWxseSwgdGhpcyBtZWFucyB0aGF0IHRoZSBmb3JlY2FzdCBjYW4gbmV2ZXIgYmUgbGFyZ2VyIHRoYW4gdGhlIGxhcmdlc3QgdmFsdWUgaW4gdGhlIGZvcmVjYXN0IGNhbGN1bGF0aW9uLCBzbyBhbnkgdXB3YXJkIChvciBkb3dud2FyZCkgdHJlbmQgdGhhdCBtaWdodCBleGlzdCB3b3VsZCBiZSBzbW9vdGhlZCBvdXQuIENvbnNlcXVlbnRseSwgU01BIGlzIG5vdCBhbiBhcHByb3ByaWF0ZSBtZXRob2QgZm9yIGNhbGN1bGF0aW5nIGEgZm9yZWNhc3Qgd2hlbiB0aGVyZSBpcyBhIHRyZW5kIGFzIGl0IHNpbXBseSBzbW9vdGhlcyBvdXQgZmx1Y3R1YXRpb25zLgoKQnkgY2FsY3VsYXRpbmcgdGhlIGFyaXRobWV0aWMgbWVhbiBvZiBhIGZpeGVkIG51bWJlciBvZiBwYXN0IG9ic2VydmF0aW9ucywgU01BIHByb3ZpZGVzIGEgY2xlYXIgYW5kIGVhc2lseSBpbnRlcnByZXRhYmxlIG1lYXN1cmUgb2YgY2VudHJhbCB0ZW5kZW5jeSBpbiB0aGUgZGF0YS4gVGhlIG1hdGhlbWF0aWNhbCBzaW1wbGljaXR5IGFuZCBlYXNlIG9mIGltcGxlbWVudGF0aW9uIG1ha2UgU01BIGEgcG9wdWxhciBjaG9pY2UgZm9yIGluaXRpYWwgZGF0YSBhbmFseXNpcyBhbmQgdHJlbmQgaWRlbnRpZmljYXRpb24uCgojIyMgQ2FsY3VsYXRpbmcgU01BIGluIFIKCkxldCdzIGNhbGN1bGF0ZSB0aGUgU01BIGZvciB0aGUgYGRhaWx5YCBjb2x1bW4gaW4gdGhlIGB3YXRlcnVzYWdlLmNzdmAgZGF0YXNldCB1c2luZyB0aGUgYFNNQWAgZnVuY3Rpb24gZnJvbSB0aGUgYFRUUmAgcGFja2FnZSB0byBjb21wdXRlIHRoZSBtb3ZpbmcgYXZlcmFnZSBmb3IgdGhlIG5leHQgdGltZSBwZXJpb2QuCgpIZXJlJ3MgdGhlIFIgY29kZSB0byBwZXJmb3JtIHRoZXNlIHN0ZXBzOgoKYGBge3IgU01BfQpsaWJyYXJ5KFRUUikKCmNzdiA8LSAiaHR0cHM6Ly9zMy51cy1lYXN0LTIuYW1hem9uYXdzLmNvbS9hcnRpZmljaXVtLnVzL2RhdGFzZXRzL3dhdGVydXNhZ2UuY3N2IgoKIyBsb2FkIGRhdGEgZnJvbSBDU1YKd2F0ZXIudXNhZ2UudHMgPC0gcmVhZC5jc3YoY3N2LAogICAgICAgICAgICAgICAgICAgICAgICAgICBzdHJpbmdzQXNGYWN0b3JzID0gRikKCiMgQ29udmVydCAnZGFpbHknIHRvIGEgdGltZSBzZXJpZXMgb2JqZWN0CmRhaWx5LnRzIDwtIHRzKHdhdGVyLnVzYWdlLnRzJGRhaWx5LCAKICAgICAgICAgICAgICAgc3RhcnQgPSBjKDIwMjMsIDgpLCAKICAgICAgICAgICAgICAgZnJlcXVlbmN5ID0gMTIpCgojIENhbGN1bGF0ZSB0aGUgMy1wZXJpb2QgU2ltcGxlIE1vdmluZyBBdmVyYWdlCnNtYV8zIDwtIFNNQShkYWlseS50cywgbiA9IDMpCmBgYAoKVGhlIGZ1bmN0aW9uIGBTTUFgIGNhbGN1bGF0ZXMgdGhlIDMtZGF5IG1vdmluZyBhdmVyYWdlIGZvciBhbGwgdGltZSBwZXJpb2RzIChleGNlcHQgdGhlIGZpcnN0IHR3byBhcyBpdCB3b3VsZCBiZSBpbXBvc3NpYmxlIHRvIGRvIHNvKS4gSXQgcmVxdWlyZXMgYSAidGltZSBzZXJpZXMgb2JqZWN0IiB3aGljaCBpcyBwcm9kdWNlZCBieSB0aGUgZnVuY3Rpb24gYHRzYC4gSW4gdGhlIGFib3ZlIGNvZGUgd2Ugc3RhcnQgdGhlIHRpbWUgc2VyaWVzIGF0IG1vbnRoIDggaW4gMjAyMyBiZWNhdXNlIHRoYXQgaXMgdGhlIG9sZGVzdCBtb250aCBpbiB0aGUgZGF0YSBzZXQgKndhdGVyLnVzYWdlLnRzKi4gV2UgdXNlIGBmcmVxdWVuY3kgPSAxMmAgYmVjYXVzZSBvdXIgZGF0YSBzZXQgZnJvbSB0aGUgQ1NWIGhhcyAxMiB0aW1lIHBlcmlvZHMuCgpUaGUgbGFzdCB2YWx1ZSBpbiB0aGUgdmVjdG9yIG9mIGZvcmVjYXN0cyBpcyB0aGUgZm9yZWNhc3QgZm9yIHRoZSBuZXh0IHRpbWUgcGVyaW9kLiBTbyB0aGUgZm9yZWNhc3QgZm9yIEF1Z3VzdCAyMDI0IHdvdWxkIGJlIGluIGBzbWFfM1sxMl1gIGFuZCBpcyBgciByb3VuZChzbWFfM1sxMl0sMSlgLgoKIyMgV2VpZ2h0ZWQgTW92aW5nIEF2ZXJhZ2UKCkEgV2VpZ2h0ZWQgTW92aW5nIEF2ZXJhZ2UgKFdNQSkgaXMgYSB0eXBlIG9mIG1vdmluZyBhdmVyYWdlIHdoZXJlIGVhY2ggZGF0YSBwb2ludCBpcyBhc3NpZ25lZCBhIGRpZmZlcmVudCB3ZWlnaHQsIHdpdGggbW9yZSByZWNlbnQgb2JzZXJ2YXRpb25zIHR5cGljYWxseSBnaXZlbiBoaWdoZXIgd2VpZ2h0cy4gVGhpcyBtZXRob2QgZ2l2ZXMgbW9yZSBpbXBvcnRhbmNlIHRvIHJlY2VudCBkYXRhIHBvaW50cywgbWFraW5nIGl0IG1vcmUgcmVzcG9uc2l2ZSB0byBjaGFuZ2VzIGluIHRoZSBkYXRhIGNvbXBhcmVkIHRvIHRoZSBTaW1wbGUgTW92aW5nIEF2ZXJhZ2UgKFNNQSkuCgpUaGUgV01BIGZvciBhIGdpdmVuIHRpbWUgcGVyaW9kICRuJCBpcyBjYWxjdWxhdGVkIGJ5IGFzc2lnbmluZyBhIHdlaWdodCB0byBlYWNoIGRhdGEgcG9pbnQgYW5kIHRoZW4gdGFraW5nIHRoZSB3ZWlnaHRlZCBhdmVyYWdlLiBUaGUgd2VpZ2h0cyB0eXBpY2FsbHkgc3VtIHRvIDEgYW5kIGFyZSB3cml0dGVuIGFzIGZyYWN0aW9ucywgYWx0aG91Z2ggdGhleSBkbyBub3QgaGF2ZS4gVGhlIGJlbmVmaXQgb2Ygd3JpdGluZyB0aGVtIGFzIGZyYWN0aW9ucyB0aGF0IHN1bSB0byAxIGlzIHRoYXQgdGhlIHN1bSBvZiB0aGUgd2VpZ2h0cyBpcyBhbHdheXMgMSBtYWtpbmcgdGhlIGNhbGN1bGF0aW9uIHNpbXBsZXIuIElmIHRoZSB3ZWlnaHRzIGRvIG5vdCBzdW0gdG8gMSwgdGhlbiBpdCBpcyBpbXBvcnRhbnQgdG8gZGl2aWRlIGJ5IHRoZSBzdW0gb2YgdGhlIHdlaWdodHMuCgokV01BX3QgPSBcZnJhY3tcc3VtX3tpPTB9XntuLTF9IHdfaSB5X3t0LWl9fXtcc3VtX3tpPTB9XntuLTF9IHdfaX0kCgp3aGVyZToKCi0gICAkV01BX3QkIGlzIHRoZSBXZWlnaHRlZCBNb3ZpbmcgQXZlcmFnZSBhdCB0aW1lICR0JAotICAgJHlfe3QtaX0kIGFyZSB0aGUgdmFsdWVzIG9mIHRoZSB0aW1lIHNlcmllcyBhdCB0aW1lcyAkdCwgdC0xLCB0LTIsIFxsZG90cywgdC0obi0xKSQKLSAgICR3X2kkIGFyZSB0aGUgd2VpZ2h0cyBhc3NpZ25lZCB0byBlYWNoIG9ic2VydmF0aW9uCgpTdXBwb3NlIHdlIGhhdmUgdGhlIGZvbGxvd2luZyBkYXRhIHBvaW50czogWzEwMCwgMTIwLCAxNDAsIDE2MCwgMTgwXSwgYW5kIHdlIHdhbnQgdG8gY2FsY3VsYXRlIHRoZSAzLXBlcmlvZCBXTUEgd2l0aCB3ZWlnaHRzIDAuMSwgMC4zLCBhbmQgMC42IGZvciB0aGUgbW9zdCByZWNlbnQgdGhyZWUgb2JzZXJ2YXRpb25zLgoKVGhlIGNhbGN1bGF0aW9uIGZvciAkV01BXzUkICh0aGUgV01BIGF0IHRoZSBmaWZ0aCBkYXRhIHBvaW50KSB3b3VsZCBiZToKCiRXTUFfNSA9IFxmcmFjezAuMSBcdGltZXMgMTQwICsgMC4zIFx0aW1lcyAxNjAgKyAwLjYgXHRpbWVzIDE4MH17MC4xICsgMC4zICsgMC42fSQKCiRXTUFfNSA9IFxmcmFjezE0ICsgNDggKyAxMDh9ezF9JAoKJFdNQV81ID0gMTcwLjAkCgojIyMgQ2FsY3VsYXRpbmcgV01BIGluIFIKCkxldOKAmXMgaW1wbGVtZW50IHRoZSBXZWlnaHRlZCBNb3ZpbmcgQXZlcmFnZSB1c2luZyB0aGUgYHdhdGVydXNhZ2UuY3N2YCBkYXRhc2V0LiBUaGUgKipmb3JlY2FzdCoqIHBhY2thZ2UgaW4gUiBkb2VzIG5vdCBoYXZlIGEgZGVkaWNhdGVkIGZ1bmN0aW9uIHNwZWNpZmljYWxseSBmb3IgY2FsY3VsYXRpbmcgYSBXZWlnaHRlZCBNb3ZpbmcgQXZlcmFnZSAoV01BKSBmb3JlY2FzdCwgYWx0aG91Z2ggdGhlIHBhY2thZ2UgcHJvdmlkZXMgdmFyaW91cyBmdW5jdGlvbnMgZm9yIG90aGVyIHR5cGVzIG9mIG1vdmluZyBhdmVyYWdlcywgZXhwb25lbnRpYWwgc21vb3RoaW5nLCBhbmQgQVJJTUEgbW9kZWxzIHdoaWNoIHdlIHdpbGwgZXhwbG9yZSBsYXRlciBpbiB0aGlzIGxlc3Nvbi4KClRvIGNhbGN1bGF0ZSBhIFdlaWdodGVkIE1vdmluZyBBdmVyYWdlLCB3ZSBjYW4gZWl0aGVyIHVzZSB0aGUgYFdNQWAgZnVuY3Rpb24gZnJvbSB0aGUgKipUVFIqKiBwYWNrYWdlIG9yIHdyaXRlIGEgY3VzdG9tIGZ1bmN0aW9uLgoKKipDdXN0b20gRnVuY3Rpb24gZm9yIENhbGN1bGF0aW5nIFdNQSoqCgpUaGUgYGNhbGN1bGF0ZS53bWFgIGZ1bmN0aW9uIHRha2VzIHRocmVlIGFyZ3VtZW50czogdGhlIGRhdGEsIHRoZSBudW1iZXIgb2YgcGVyaW9kcyAkbiQsIGFuZCB0aGUgd2VpZ2h0cy4gSXQgaW5pdGlhbGl6ZXMgYSB2ZWN0b3IgYHdtYWAgd2l0aCBgTkFgIHZhbHVlcyB0byBzdG9yZSB0aGUgV01BIHJlc3VsdHMuIEEgbG9vcCBpdGVyYXRlcyBvdmVyIHRoZSBkYXRhLCBjYWxjdWxhdGluZyB0aGUgV01BIGZvciBlYWNoIHBvc2l0aW9uIHdoZXJlIHRoZXJlIGFyZSBlbm91Z2ggZGF0YSBwb2ludHMuIFRoZSBXTUEgaXMgY2FsY3VsYXRlZCBieSB0YWtpbmcgdGhlIHdlaWdodGVkIHN1bSBvZiB0aGUgbGFzdCAkbiQgb2JzZXJ2YXRpb25zIGFuZCBkaXZpZGluZyBieSB0aGUgc3VtIG9mIHRoZSB3ZWlnaHRzLgoKVGhlIHdlaWdodHMgZm9yIHRoZSAzLXBlcmlvZCBXTUEgYXJlIGRlZmluZWQgYXMgYGMoMC4xLCAwLjMsIDAuNilgLCB3aXRoIHRoZSBtb3N0IHJlY2VudCBvYnNlcnZhdGlvbiBnaXZlbiB0aGUgaGlnaGVzdCB3ZWlnaHQuIFRoZSByZXN1bHRzIGFyZSBzdG9yZWQgaW4gYSBuZXcgY29sdW1uIGBXTUFgIGluIHRoZSBgd2F0ZXJfdXNhZ2VgIGRhdGEgZnJhbWUuCgpgYGB7ciBjdXN0b21XTUF9CiMgTG9hZCB0aGUgZGF0YQpjc3YgPC0gImh0dHBzOi8vczMudXMtZWFzdC0yLmFtYXpvbmF3cy5jb20vYXJ0aWZpY2l1bS51cy9kYXRhc2V0cy93YXRlcnVzYWdlLmNzdiIKd2F0ZXIudXNhZ2UgPC0gcmVhZC5jc3YoY3N2LAogICAgICAgICAgICAgICAgICAgICAgICBzdHJpbmdzQXNGYWN0b3JzID0gRiwKICAgICAgICAgICAgICAgICAgICAgICAgaGVhZGVyID0gVCkKCiMgRnVuY3Rpb24gdG8gY2FsY3VsYXRlIHRoZSBXZWlnaHRlZCBNb3ZpbmcgQXZlcmFnZQpjYWxjdWxhdGUud21hIDwtIGZ1bmN0aW9uKGRhdGEsIG4sIHdlaWdodHMpIHsKICAjIEluaXRpYWxpemUgV01BIGNvbHVtbiB3aXRoIE5BIHZhbHVlcwogIHdtYSA8LSByZXAoTkEsIGxlbmd0aChkYXRhKSkgCiAgZm9yIChpIGluIG46bGVuZ3RoKGRhdGEpKSB7CiAgICB3bWFbaV0gPC0gc3VtKHdlaWdodHMgKiBkYXRhWyhpLW4rMSk6aV0pIC8gc3VtKHdlaWdodHMpCiAgfQogIHJldHVybih3bWEpCn0KCiMgRGVmaW5lIHRoZSB3ZWlnaHRzIGZvciB0aGUgV01BICgzLXBlcmlvZCBleGFtcGxlKQp3IDwtIGMoMC4xLCAwLjMsIDAuNikKCiMgQ2FsY3VsYXRlIHRoZSBXTUEKV01BIDwtIGNhbGN1bGF0ZS53bWEod2F0ZXIudXNhZ2UkZGFpbHksIAogICAgICAgICAgICAgICAgICAgICBuID0gMywgCiAgICAgICAgICAgICAgICAgICAgIHdlaWdodHMgPSB3KQoKZm9yZWNhc3QudmFsIDwtIHRhaWwoV01BLDEpCmBgYAoKVGhlIGZvcmVjYXN0IGlzIGluIHRoZSBsYXN0IGVsZW1lbnQgb2YgdGhlIHJldHVybmVkIHZlY3RvciBvZiBmb3JlY2FzdHMgKGl0IGFjdHVhbGx5IGNhbGN1bGF0ZXMgdGhlIGZvcmVjYXN0IGZvciBhbGwgcHJpb3IgdmFsdWVzKSBhbmQgaXMgYHIgZm9yZWNhc3QudmFsYC4KCioqVXNpbmcgYFdNQWAgRnVuY3Rpb24gZnJvbSoqICoqVFRSKioKClRoZSBgV01BYCBmdW5jdGlvbiBmcm9tIHRoZSAqKlRUUioqIHBhY2thZ2UgY2FsY3VsYXRlcyB0aGUgV2VpZ2h0ZWQgTW92aW5nIEF2ZXJhZ2UgYmFzZWQgb24gdGhlIGRlZmluZWQgd2VpZ2h0cy4gVGhlIGNhbGN1bGF0ZWQgV01BIGlzIGFkZGVkIGFzIGEgbmV3IGNvbHVtbiBgd21hYCBpbiB0aGUgZGF0YSBmcmFtZS4gVGhlIGZvcmVjYXN0IGZvciB0aGUgbmV4dCBwZXJpb2QgaXMgbWFkZSB1c2luZyB0aGUgbGFzdCB2YWx1ZSBvZiB0aGUgV01BLiBUaGlzIGlzIGEgc2ltcGxlIGFwcHJvYWNoIHdoZXJlIHRoZSBuZXh0IGZvcmVjYXN0IHZhbHVlIGlzIGFzc3VtZWQgdG8gYmUgdGhlIHNhbWUgYXMgdGhlIG1vc3QgcmVjZW50IFdNQSB2YWx1ZS4KCmBgYHtyIFdNQV9UVFJ9CmxpYnJhcnkoVFRSKQoKY3N2IDwtICJodHRwczovL3MzLnVzLWVhc3QtMi5hbWF6b25hd3MuY29tL2FydGlmaWNpdW0udXMvZGF0YXNldHMvd2F0ZXJ1c2FnZS5jc3YiCgojIExvYWQgdGhlIGRhdGEKd2F0ZXIudXNhZ2UgPC0gcmVhZC5jc3YoY3N2LAogICAgICAgICAgICAgICAgICAgICAgICBzdHJpbmdzQXNGYWN0b3JzID0gRikKCiMgRGVmaW5lIHRoZSB3ZWlnaHRzIGZvciB0aGUgV01BIChleGFtcGxlIHdpdGggMy1wZXJpb2Qgd2VpZ2h0cykKd2VpZ2h0cyA8LSBjKDAuMSwgMC4zLCAwLjYpCgojIENhbGN1bGF0ZSB0aGUgV2VpZ2h0ZWQgTW92aW5nIEF2ZXJhZ2UKd21hIDwtIFRUUjo6V01BKHdhdGVyLnVzYWdlJGRhaWx5LCBuID0gbGVuZ3RoKHdlaWdodHMpLCB3dHMgPSB3ZWlnaHRzKQoKIyBGb3JlY2FzdCB1c2luZyB0aGUgbGFzdCB2YWx1ZSBvZiBXTUEKZm9yZWNhc3QudmFsIDwtIHRhaWwod21hLCAxKQpgYGAKClRoZSBmb3JlY2FzdCBpcyBpbiB0aGUgbGFzdCBlbGVtZW50IG9mIHRoZSByZXR1cm5lZCB2ZWN0b3Igb2YgZm9yZWNhc3RzIChpdCBhY3R1YWxseSBjYWxjdWxhdGVzIHRoZSBmb3JlY2FzdCBmb3IgYWxsIHByaW9yIHZhbHVlcykgYW5kIGlzIGByIGZvcmVjYXN0LnZhbGAgLS0gdGhlIHNhbWUgYXMgd2l0aCBvdXIgY3VzdG9tIGZ1bmN0aW9uIChhcyB3ZSB3b3VsZCBleHBlY3QuKQoKVGhlICoqVFRSKiogcGFja2FnZSBoYXMgYSBudW1iZXIgb2Ygb3RoZXIgZm9yZWNhc3RpbmcgbWV0aG9kcywgc3VjaCBhcyBFeHBvbmVudGlhbCBXZWlnaHRlZCBNZWFuLCBzbyBjaGVjayBpdCBvdXQuCgpUaGUgV2VpZ2h0ZWQgTW92aW5nIEF2ZXJhZ2UgKFdNQSkgaXMgYW4gZWZmZWN0aXZlIG1ldGhvZCBmb3Igc21vb3RoaW5nIHRpbWUgc2VyaWVzIGRhdGEsIGdpdmluZyBtb3JlIGltcG9ydGFuY2UgdG8gcmVjZW50IG9ic2VydmF0aW9ucy4gTmF0dXJhbGx5LCBnaXZlbiB0aGF0IGl0IGlzIGFuIGF2ZXJhZ2UsIFdNQSBkb2VzIG5vdCBhY2NvdW50IGZvciBlaXRoZXIgdHJlbmQgb3Igc2Vhc29uYWxpdHksIGFsdGhvdWdoIGEgc2Vhc29uYWxpdHkgYWRqdXN0bWVudCBjYW4gYmUgYWRkZWQgYXMgZXhwbGFpbmVkIGxhdGVyIGluIHRoaXMgbGVzc29uLgoKIyMjIExlY3R1cmU6IE1vdmluZyBBdmVyYWdlCgpJbiB0aGUgbGVjdHVyZSBiZWxvdywgRHIuIE1hcnRpbiBTY2hlZGxiYXVlciBvZiBLaG91cnkgQm9zdG9uIGRlbW9uc3RyYXRlcyB0aGUgY2FsY3VsYXRpb24gb2Ygd2VpZ2h0ZWQgbW92aW5nIGF2ZXJhZ2UgZGlyZWN0bHkgaW4gUiB3aXRob3V0IHRoZSB1c2Ugb2YgYSBwYWNrYWdlLgoKPGlmcmFtZSBzcmM9Imh0dHBzOi8vcGxheWVyLnZpbWVvLmNvbS92aWRlby85NzMzNDA1MTU/YmFkZ2U9MCZhbXA7YXV0b3BhdXNlPTAmYW1wO3BsYXllcl9pZD0wJmFtcDthcHBfaWQ9NTg0NzkiIHdpZHRoPSI0ODAiIGhlaWdodD0iMjcwIiBmcmFtZWJvcmRlcj0iMSIgYWxsb3c9ImF1dG9wbGF5OyBmdWxsc2NyZWVuOyBwaWN0dXJlLWluLXBpY3R1cmU7IGNsaXBib2FyZC13cml0ZSIgZGF0YS1leHRlcm5hbD0iMSI+Cgo8L2lmcmFtZT4KCiMjIFNpbXBsZSBFeHBvbmVudGlhbCBTbW9vdGhpbmcKClNpbXBsZSBFeHBvbmVudGlhbCBTbW9vdGhpbmcgKFNFUykgaXMgYSB0aW1lIHNlcmllcyBmb3JlY2FzdGluZyBtZXRob2QgZm9yIHVuaXZhcmlhdGUgZGF0YSB0aGF0IGNhbiBiZSB1c2VkIHRvIGZvcmVjYXN0IGRhdGEgd2l0aCBubyBjbGVhciB0cmVuZCBvciBzZWFzb25hbCBwYXR0ZXJuLiBTRVMgdXNlcyB3ZWlnaHRlZCBhdmVyYWdlcyBvZiBwYXN0IG9ic2VydmF0aW9ucywgd2l0aCBtb3JlIHJlY2VudCBvYnNlcnZhdGlvbnMgZ2l2ZW4gaGlnaGVyIHdlaWdodHMuIFRoZSBtZXRob2QgYXBwbGllcyBleHBvbmVudGlhbGx5IGRlY3JlYXNpbmcgd2VpZ2h0cyB0byBwYXN0IGRhdGEsIG1ha2luZyBpdCBtb3JlIHJlc3BvbnNpdmUgdG8gcmVjZW50IGNoYW5nZXMuIFRoaXMgbWV0aG9kIGlzIHBhcnRpY3VsYXJseSB1c2VmdWwgZm9yIGRhdGEgd2l0aCBhIGNsZWFyIHRyZW5kIGFuZCBzZWFzb25hbCBwYXR0ZXJucy4KClRoZSBmb3JlY2FzdCBhdCB0aW1lICR0KzEkLCB0aGUgbmV4dCB0aW1lIHBlcmlvZCwgaXMgZ2l2ZW4gYnk6CgokRl97dCsxfSA9IFxhbHBoYSBZX3QgKyAoMSAtIFxhbHBoYSkgRl90JAoKd2hlcmU6CgotICAgJEZfe3QrMX0kIGlzIHRoZSBmb3JlY2FzdCBmb3IgdGhlIG5leHQgcGVyaW9kCi0gICAkWV90JCBpcyB0aGUgYWN0dWFsIHZhbHVlIGF0IHRpbWUgJHQkCi0gICAkRl90JCBpcyB0aGUgZm9yZWNhc3QgZm9yIHRpbWUgJHQkCi0gICAkXGFscGhhJCAoYWxwaGEpIGlzIHRoZSBzbW9vdGhpbmcgcGFyYW1ldGVyLCAkMCBcbGVxIFxhbHBoYSBcbGVxIDEkCgpUaGUgaW5pdGlhbCBmb3JlY2FzdCAkRl8xJCBjYW4gYmUgc2V0IHRvIHRoZSBmaXJzdCBvYnNlcnZhdGlvbiAkWV8xJCBvciB0aGUgYXZlcmFnZSBvZiB0aGUgZmlyc3QgZmV3IG9ic2VydmF0aW9ucy4KCkZyb20gdGhlIGZvcm11bGEgYWJvdmUsIHdlIGNhbiBzZWUgdGhhdCBpdCBpcyByZWN1cnNpdmUgYW5kIHJlcXVpcmVzIHN0YXJ0aW5nIGF0IHRoZSBmaXJzdCB0aW1lIHBlcmlvZC4KClRoZSBTRVMgZm9ybXVsYSBjYW4gYmUgYXBwbGllZCByZWN1cnNpdmVseSBhczoKCiRGX3t0KzF9ID0gXGFscGhhIFlfdCArICgxIC0gXGFscGhhKSAoXGFscGhhIFlfe3QtMX0gKyAoMSAtIFxhbHBoYSkgRl97dC0xfSkkCgpUaGlzIHJlY3Vyc2l2ZSBuYXR1cmUgbWVhbnMgdGhhdCB3ZSBhcHBseSBleHBvbmVudGlhbGx5IGRlY3JlYXNpbmcgd2VpZ2h0cyB0byBwYXN0IG9ic2VydmF0aW9ucy4KCkxldCdzIGNvbnNpZGVyIGEgc21hbGwgZGF0YXNldCBmb3IgaWxsdXN0cmF0aW9uLiBTdXBwb3NlIHdlIGhhdmUgdGhlIGZvbGxvd2luZyBkYXRhIHBvaW50czogWzEwLCAxMiwgMTQsIDEzLCAxNV0uIEFzc3VtZSB3ZSBjaG9vc2UgJFxhbHBoYSA9IDAuMyQgKGEgY29tbW9uIHZhbHVlKS4gVGhlIGluaXRpYWwgZm9yZWNhc3QgJEZfMSQgaXMgc2V0IHRvICRZXzEkLCB3aGljaCBpcyAxMC4KCioqSW5pdGlhbCBGb3JlY2FzdCoqOgoKJEZfMSA9IFlfMSA9IDEwJAoKKipGaXJzdCBGb3JlY2FzdCoqOgoKJEZfMiA9IFxhbHBoYSBZXzEgKyAoMSAtIFxhbHBoYSkgRl8xJAoKJEZfMiA9IDAuMyBcdGltZXMgMTAgKyAwLjcgXHRpbWVzIDEwJAoKJEZfMiA9IDEwJAoKKipTZWNvbmQgRm9yZWNhc3QqKjoKCiRGXzMgPSBcYWxwaGEgWV8yICsgKDEgLSBcYWxwaGEpIEZfMiQKCiRGXzMgPSAwLjMgXHRpbWVzIDEyICsgMC43IFx0aW1lcyAxMCQKCiRGXzMgPSAxMC42JAoKKipUaGlyZCBGb3JlY2FzdCoqOgoKJEZfNCA9IFxhbHBoYSBZXzMgKyAoMSAtIFxhbHBoYSkgRl8zJAoKJEZfNCA9IDAuMyBcdGltZXMgMTQgKyAwLjcgXHRpbWVzIDEwLjYkCgokRl80ID0gMTEuNjIkCgoqKkZvdXJ0aCBGb3JlY2FzdCoqOgoKJEZfNSA9IFxhbHBoYSBZXzQgKyAoMSAtIFxhbHBoYSkgRl80JAoKJEZfNSA9IDAuMyBcdGltZXMgMTMgKyAwLjcgXHRpbWVzIDExLjYyJAoKJEZfNSA9IDEyLjEzNCQKClRoZSAiaWRlYWwiIHZhbHVlIGZvciAkXGFscGhhJCBpcyBmb3VuZCBieSBpdGVyYXRpdmVseSB0cnlpbmcgdmFyaW91cyB2YWx1ZXMgYW5kIHNlZWluZyB3aGljaCBtb2RlbCwgd2hlbiBhcHBsaWVkIHRvIGl0cyBwcmlvciBvYnNlcnZhdGlvbnMgKGNhbGxlZCAiYmFja2ZpdHRpbmciKSwgcmVzdWx0cyBpbiB0aGUgYmVzdCBmaXQuIFRoZSBzZWN0aW9uIG9uIFtNb2RlbCBFdmFsdWF0aW9uXSBzaG93cyB0aGlzIGFwcHJvYWNoIGluIG1vcmUgZGV0YWlsLgoKIyMjIENhbGN1bGF0aW5nIFNFUyBpbiBSCgpMZXTigJlzIGNhbGN1bGF0ZSBTRVMgZm9yIHRoZSBgd2F0ZXJ1c2FnZS5jc3ZgIGRhdGEgdG8gZm9yZWNhc3QgZGFpbHkgd2F0ZXIgdXNhZ2UuIFdlIHdpbGwgdXNlIHRoZSBgSG9sdFdpbnRlcnNgIGZ1bmN0aW9uIGZyb20gdGhlICoqZm9yZWNhc3QqKiBwYWNrYWdlLiBGb3IgdGhpcyBmdW5jdGlvbiB0byBjYWxjdWxhdGUgU2ltcGxlIEV4cG9uZW50aWFsIFNtb290aGluZyByZXF1aXJlcyBzZXR0aW5nIHRoZSAqYmV0YSogYW5kICpnYW1tYSogYXJndW1lbnQgdG8gKkZBTFNFKi4gTm90aWNlIHRoYXQgb25jZSBhZ2FpbiB3ZSBuZWVkIHRvIGNvbnZlcnQgdGhlIGRhdGEgdG8gYSAidGltZSBzZXJpZXMiIG9iamVjdCBvZiB0eXBlICp0cyouCgpgYGB7ciB3YXJuaW5nPUYsIG1lc3NhZ2U9Rn0KbGlicmFyeShmb3JlY2FzdCkKCmNzdiA8LSAiaHR0cHM6Ly9zMy51cy1lYXN0LTIuYW1hem9uYXdzLmNvbS9hcnRpZmljaXVtLnVzL2RhdGFzZXRzL3dhdGVydXNhZ2UuY3N2IgoKd2F0ZXJfdXNhZ2UgPC0gcmVhZC5jc3YoY3N2LAogICAgICAgICAgICAgICAgICAgICAgICBzdHJpbmdzQXNGYWN0b3JzID0gRikKCiMgY29udmVydCAnZGFpbHknIHRvIGEgdGltZSBzZXJpZXMgb2JqZWN0CmRhaWx5LnRzIDwtIHRzKHdhdGVyX3VzYWdlJGRhaWx5LCAKICAgICAgICAgICAgICAgc3RhcnQgPSBjKDIwMjMsIDgpLCAKICAgICAgICAgICAgICAgZnJlcXVlbmN5ID0gMTIpCgojIGNyZWF0ZSBTRVMgbW9kZWwgdXNpbmcgSG9sdFdpbnRlcnMgCiMgd2l0aCBiZXRhIGFuZCBnYW1tYSBzZXQgdG8gRkFMU0UKc2VzLm1vZGVsIDwtIEhvbHRXaW50ZXJzKGRhaWx5LnRzLCAKICAgICAgICAgICAgICAgICAgICAgICAgIGJldGEgPSBGQUxTRSwgCiAgICAgICAgICAgICAgICAgICAgICAgICBnYW1tYSA9IEZBTFNFKQoKIyBGb3JlY2FzdCB0aGUgbmV4dCBwZXJpb2QgKG5leHQgbW9udGgpCmZvcmVjYXN0LnZhbCA8LSBmb3JlY2FzdChzZXMubW9kZWwsIGggPSAxKQpgYGAKClRoZSBTRVMgZm9yZWNhc3RpbmcgbW9kZWwgZXN0aW1hdGVzIHRoZSBhdmVyYWdlIGRhaWx5IHdhdGVyIHVzZSBmb3IgdGhlIG5leHQgbW9udGggdG8gYmUgYHIgcm91bmQoZm9yZWNhc3QudmFsJG1lYW5bWzFdXSwwKWAgd2l0aCBhIDk1JSBwcmVkaWN0aW9uIHJhbmdlIGZyb20gYHIgcm91bmQoZm9yZWNhc3QudmFsJGxvd2VyW1syXV0sMClgIHRvIGByIHJvdW5kKGZvcmVjYXN0LnZhbCR1cHBlcltbMl1dLDApYC4KClRoZSBkZXRhaWxzIG9mIHRoZSBtb2RlbCBjYW4gYmUgaW5zcGVjdGluZyBieSB1c2luZyB0aGUgZnVuY3Rpb24gYHN1bW1hcnkoc2VzLm1vZGVsKWAgb3IgYnkgYWNjZXNzaW5nIGl0cyBjb21wb25lbnRzOgoKLSAgIGBmb3JlY2FzdC52YWwkbWVhbltbMV1dYCBpcyB0aGUgZm9yZWNhc3QgdmFsdWUKLSAgIGBmb3JlY2FzdC52YWwkbG93ZXJbWzJdXWAgaXMgdGhlIGxvd2VyIGJvdW5kIG9mIHRoZSA5NSUgcHJlZGljdGlvbiBpbnRlcnZhbAotICAgYGZvcmVjYXN0LnZhbCR1cHBlcltbMl1dYCBpcyB0aGUgdXBwZXIgYm91bmQgb2YgdGhlIDk1JSBwcmVkaWN0aW9uIGludGVydmFsCgpUaGUgYGZvcmVjYXN0KClgIGZ1bmN0aW9uIGFsc28gcmV0dXJuIHRoZSA4MCUgcHJlZGljdGlvbiBpbnRlcnZhbC4KCiMjIyBMZWN0dXJlOiBFeHBvbmVudGlhbCBTbW9vdGhpbmcKCkluIHRoZSBsZWN0dXJlIGJlbG93LCBEci4gTWFydGluIFNjaGVkbGJhdWVyIG9mIEtob3VyeSBCb3N0b24gZGVtb25zdHJhdGVzIHRoZSBjYWxjdWxhdGlvbiBvZiB3ZWlnaHRlZCBtb3ZpbmcgYXZlcmFnZSBkaXJlY3RseSBpbiBSIHdpdGhvdXQgdGhlIHVzZSBvZiBhIHBhY2thZ2UuCgo8aWZyYW1lIHNyYz0iaHR0cHM6Ly9wbGF5ZXIudmltZW8uY29tL3ZpZGVvLzk3MzQxNTk0NT9iYWRnZT0wJmFtcDthdXRvcGF1c2U9MCZhbXA7cGxheWVyX2lkPTAmYW1wO2FwcF9pZD01ODQ3OSIgd2lkdGg9IjQ4MCIgaGVpZ2h0PSIzNjAiIGZyYW1lYm9yZGVyPSIxIiBhbGxvdz0iYXV0b3BsYXk7IGZ1bGxzY3JlZW47IHBpY3R1cmUtaW4tcGljdHVyZTsgY2xpcGJvYXJkLXdyaXRlIiBkYXRhLWV4dGVybmFsPSIxIj4KCjwvaWZyYW1lPgoKVG8gc3VtbWFyaXplLCBTaW1wbGUgRXhwb25lbnRpYWwgU21vb3RoaW5nIChTRVMpIGlzIGFuIGVmZmVjdGl2ZSBtZXRob2QgZm9yIGZvcmVjYXN0aW5nIHRpbWUgc2VyaWVzIGRhdGEgdGhhdCBsYWNrcyBhIGNsZWFyIHRyZW5kIG9yIHNlYXNvbmFsIHBhdHRlcm4uIEJ5IGFwcGx5aW5nIGV4cG9uZW50aWFsbHkgZGVjcmVhc2luZyB3ZWlnaHRzIHRvIHBhc3Qgb2JzZXJ2YXRpb25zLCBTRVMgcHJvdmlkZXMgYSBzbW9vdGggZm9yZWNhc3QgdGhhdCBlbXBoYXNpemVzIHJlY2VudCBjaGFuZ2VzIGluIHRoZSBkYXRhLiBUaGUgYEhvbHRXaW50ZXJzYCBmdW5jdGlvbiBtYWtlcyBjYWxjdWxhdGluZyBhbiBTRVMgZm9yZWNhc3QgcXVpdGUgc2ltcGxlLgoKIyMgSG9sdOKAmXMgTGluZWFyIFRyZW5kIE1vZGVsCgpIb2x0J3MgTGluZWFyIFRyZW5kIE1vZGVsIChITFRNKSwgYWxzbyBrbm93biBhcyBIb2x04oCZcyBFeHBvbmVudGlhbCBTbW9vdGhpbmcsIGlzIGFuIGV4dGVuc2lvbiBvZiBTaW1wbGUgRXhwb25lbnRpYWwgU21vb3RoaW5nIHRoYXQgYWxsb3dzIGZvcmVjYXN0aW5nIG9mIGRhdGEgd2l0aCBhIHNpbXBsZSBsaW5lYXIgdHJlbmQuIEl0IGluY29ycG9yYXRlcyB0d28gZXF1YXRpb25zOiBvbmUgZm9yIHRoZSBsZXZlbCBhbmQgb25lIGZvciB0aGUgdHJlbmQuIFRoaXMgbWV0aG9kIGlzIHVzZWZ1bCB3aGVuIHRoZSBkYXRhIGV4aGliaXRzIGEgdHJlbmQgYnV0IG5vIHNlYXNvbmFsaXR5LgoKKipMZXZlbCBFcXVhdGlvbioqOgoKJExfdCA9IFxhbHBoYSBZX3QgKyAoMSAtIFxhbHBoYSkgKExfe3QtMX0gKyBUX3t0LTF9KSQKCioqVHJlbmQgRXF1YXRpb24qKjoKCiRUX3QgPSBcYmV0YSAoTF90IC0gTF97dC0xfSkgKyAoMSAtIFxiZXRhKSBUX3t0LTF9JAoKVGhlIGZvcmVjYXN0IGVxdWF0aW9uIGZvciB0aGUgbmV4dCAqaCogdGltZSBwZXJpb2RzIGlzOgoKJEZfe3QraH0gPSBMX3QgKyBoIFRfdCQKCndoZXJlOgoKLSAgICRMX3QkIGlzIHRoZSBlc3RpbWF0ZWQgbGV2ZWwgYXQgdGltZSAkdCQKLSAgICRUX3QkIGlzIHRoZSBlc3RpbWF0ZWQgdHJlbmQgYXQgdGltZSAkdCQKLSAgICRZX3QkIGlzIHRoZSBhY3R1YWwgdmFsdWUgYXQgdGltZSAkdCQKLSAgICRcYWxwaGEkIGlzIHRoZSBzbW9vdGhpbmcgcGFyYW1ldGVyIGZvciB0aGUgbGV2ZWwsICQwIFxsZXEgXGFscGhhIFxsZXEgMSQKLSAgICRcYmV0YSQgaXMgdGhlIHNtb290aGluZyBwYXJhbWV0ZXIgZm9yIHRoZSB0cmVuZCwgJDAgXGxlcSBcYmV0YSBcbGVxIDEkCi0gICAkaCQgaXMgdGhlIG51bWJlciBvZiBwZXJpb2RzIGFoZWFkIHRvIGJlIGZvcmVjYXN0ZWQuCgpMZXQncyBsb29rIGF0IGFuIGV4YW1wbGUuIFN1cHBvc2Ugd2UgaGF2ZSB0aGUgZm9sbG93aW5nIGRhdGEgcG9pbnRzIGZvciBhIHNlcmllczogWzEwLCAxMiwgMTQsIDEzLCAxNV0gYW5kIHdlIGNob29zZSAkXGFscGhhID0gMC41JCBhbmQgJFxiZXRhID0gMC41JC4gTGV0J3MgY2FsY3VsYXRlIHRoZSBsZXZlbCBhbmQgdHJlbmQgZm9yIHRoZXNlIHBvaW50cy4KCioqSW5pdGlhbCBMZXZlbCBhbmQgVHJlbmQqKjoKCiRMXzEgPSBZXzEgPSAxMCQKCiRUXzEgPSBZXzIgLSBZXzEgPSAyJAoKKipGaXJzdCBVcGRhdGUqKjoKCiRMXzIgPSBcYWxwaGEgWV8yICsgKDEgLSBcYWxwaGEpIChMXzEgKyBUXzEpJAoKJExfMiA9IDAuNSBcdGltZXMgMTIgKyAwLjUgXHRpbWVzICgxMCArIDIpJAoKJExfMiA9IDEyJAoKJFRfMiA9IFxiZXRhIChMXzIgLSBMXzEpICsgKDEgLSBcYmV0YSkgVF8xJAoKJFRfMiA9IDAuNSBcdGltZXMgKDEyIC0gMTApICsgMC41IFx0aW1lcyAyJAoKJFRfMiA9IDIkCgoqKlNlY29uZCBVcGRhdGUqKjoKCiRMXzMgPSBcYWxwaGEgWV8zICsgKDEgLSBcYWxwaGEpIChMXzIgKyBUXzIpJAoKJExfMyA9IDAuNSBcdGltZXMgMTQgKyAwLjUgXHRpbWVzICgxMiArIDIpJAoKXCRMXzMgPSAxNCBcJAoKJFRfMyA9IFxiZXRhIChMXzMgLSBMXzIpICsgKDEgLSBcYmV0YSkgVF8yJAoKJFRfMyA9IDAuNSBcdGltZXMgKDE0IC0gMTIpICsgMC41IFx0aW1lcyAyJCBcJFRfMyA9IDJcJAoKVGhlIGxldmVsIGFuZCB0cmVuZCBhcmUgdXBkYXRlZCBhdCBlYWNoIHN0ZXAsIGFuZCB0aGUgZm9yZWNhc3QgY2FuIGJlIG1hZGUgdXNpbmcgdGhlc2UgdXBkYXRlZCB2YWx1ZXMuIE9uY2UgYWdhaW4sIHRoZSBtb2RlbCBpcyByZWN1cnNpdmUgYW5kIHJlcXVpcmVzIHN0YXJ0aW5nIGF0IHRoZSBmaXJzdCBvYnNlcnZhdGlvbi4gVGhlIGZpcnN0IGZvcmVjYXN0IGNhbiBiZSBzZXQgdG8gdGhlIGZpcnN0IG9ic2VydmVkIHZhbHVlIG9yIHdlIGNvdWxkIHN0YXJ0IGF0IG9ic2VydmF0aW9uIHRocmVlIGFuZCBtYWtlIGl0cyBmaXJzdCBmb3JlY2FzdCB0aGUgYXZlcmFnZSBvZiB0aGUgZmlyc3QgdHdvIG9ic2VydmVkIHZhbHVlcy4KCiMjIyBDYWxjdWxhdGluZyBITFRNIGluIFIKCkxldOKAmXMgaW1wbGVtZW50IEhvbHTigJlzIExpbmVhciBUcmVuZCBNb2RlbCB1c2luZyB0aGUgYHdhdGVydXNhZ2UuY3N2YCBkYXRhIHRvIGZvcmVjYXN0IGRhaWx5IHdhdGVyIHVzYWdlLiBXZSB3aWxsIGNob29zZSAkXGFscGhhID0gMC4zJCBhbmQgJFxiZXRhID0gMC41JC4KCmBgYHtyIHdhcm5pbmc9RiwgbWVzc2FnZT1GfQpsaWJyYXJ5KGZvcmVjYXN0KQoKIyBjb252ZXJ0ICdkYWlseScgdG8gYSB0aW1lIHNlcmllcyBvYmplY3QKZGFpbHkudHMgPC0gdHMod2F0ZXJfdXNhZ2UkZGFpbHksIHN0YXJ0ID0gYygyMDIzLCA4KSwgZnJlcXVlbmN5ID0gMTIpCgojIGFwcGx5IEhvbHQncyBMaW5lYXIgVHJlbmQgTW9kZWwgdXNpbmcgSG9sdFdpbnRlcnMgCiMgd2l0aCBnYW1tYSBzZXQgdG8gRkFMU0UKaG9sdC5tb2RlbCA8LSBIb2x0V2ludGVycyhkYWlseS50cywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgYWxwaGEgPSAwLjMsIAogICAgICAgICAgICAgICAgICAgICAgICAgIGJldGEgPSAwLjUsIAogICAgICAgICAgICAgICAgICAgICAgICAgIGdhbW1hID0gRkFMU0UpCgojIEZvcmVjYXN0IHRoZSBuZXh0IHBlcmlvZCAobmV4dCBtb250aCkKZm9yZWNhc3QudmFsIDwtIGZvcmVjYXN0KGhvbHQubW9kZWwsIGggPSAxKQpgYGAKClRoZSBIb2x0J3MgTGluZWFyIFRyZW5kIE1vZGVsIGVzdGltYXRlcyB0aGUgYXZlcmFnZSBkYWlseSB3YXRlciB1c2UgZm9yIHRoZSBuZXh0IG1vbnRoIHRvIGJlIGByIHJvdW5kKGZvcmVjYXN0LnZhbCRtZWFuW1sxXV0sMClgIHdpdGggYSA5NSUgcHJlZGljdGlvbiByYW5nZSBmcm9tIGByIHJvdW5kKGZvcmVjYXN0LnZhbCRsb3dlcltbMl1dLDApYCB0byBgciByb3VuZChmb3JlY2FzdC52YWwkdXBwZXJbWzJdXSwwKWAuCgpUaGUgZGV0YWlscyBvZiB0aGUgbW9kZWwgY2FuIGJlIGluc3BlY3RpbmcgYnkgdXNpbmcgdGhlIGZ1bmN0aW9uIGBzdW1tYXJ5KGhvbHQubW9kZWwpYCBvciBieSBhY2Nlc3NpbmcgaXRzIGNvbXBvbmVudHM6CgotICAgYGZvcmVjYXN0LnZhbCRtZWFuW1sxXV1gIGlzIHRoZSBmb3JlY2FzdCB2YWx1ZQotICAgYGZvcmVjYXN0LnZhbCRsb3dlcltbMl1dYCBpcyB0aGUgbG93ZXIgYm91bmQgb2YgdGhlIDk1JSBwcmVkaWN0aW9uIGludGVydmFsCi0gICBgZm9yZWNhc3QudmFsJHVwcGVyW1syXV1gIGlzIHRoZSB1cHBlciBib3VuZCBvZiB0aGUgOTUlIHByZWRpY3Rpb24gaW50ZXJ2YWwKClRoZSBgZm9yZWNhc3QoKWAgZnVuY3Rpb24gYWxzbyByZXR1cm5zIHRoZSByZXNpZHVhbHMgYW5kIHRoZSA4MCUgcHJlZGljdGlvbiBpbnRlcnZhbC4KCkhvbHTigJlzIExpbmVhciBUcmVuZCBNb2RlbCBleHRlbmRzIHNpbXBsZSBleHBvbmVudGlhbCBzbW9vdGhpbmcgdG8gY2FwdHVyZSBsaW5lYXIgdHJlbmRzIGluIHRpbWUgc2VyaWVzIGRhdGEuIEJ5IGluY29ycG9yYXRpbmcgYm90aCBsZXZlbCBhbmQgdHJlbmQgY29tcG9uZW50cywgaXQgcHJvdmlkZXMgYSBtb3JlIGFjY3VyYXRlIGZvcmVjYXN0IGZvciBkYXRhIHdpdGggdHJlbmRzIGJ1dCBubyBzZWFzb25hbGl0eS4KCiMjIExpbmVhciBSZWdyZXNzaW9uIFRyZW5kIE1vZGVsCgoqKlRyZW5kKiogaW4gZm9yZWNhc3RpbmcgcmVmZXJzIHRvIHRoZSBsb25nLXRlcm0gbW92ZW1lbnQgb3IgZGlyZWN0aW9uIGluIGEgdGltZSBzZXJpZXMgZGF0YSBvdmVyIGEgcGVyaW9kLiBJdCByZXByZXNlbnRzIHRoZSB1bmRlcmx5aW5nIHBhdHRlcm4gb2YgZ3Jvd3RoIG9yIGRlY2xpbmUgdGhhdCBwZXJzaXN0cyBvdmVyIHRpbWUsIGlnbm9yaW5nIHNob3J0LXRlcm0gZmx1Y3R1YXRpb25zIGFuZCBub2lzZS4gSWRlbnRpZnlpbmcgYW5kIG1vZGVsaW5nIHRyZW5kcyBpcyBjcnVjaWFsIGZvciBhY2N1cmF0ZSB0aW1lIHNlcmllcyBmb3JlY2FzdGluZywgYXMgaXQgaGVscHMgaW4gdW5kZXJzdGFuZGluZyB0aGUgZ2VuZXJhbCBkaXJlY3Rpb24gaW4gd2hpY2ggdGhlIGRhdGEgaXMgbW92aW5nLgoKVHJlbmRzIGNhcHR1cmUgdGhlIGdlbmVyYWwgZGlyZWN0aW9uIG9mIHRoZSBkYXRhIG92ZXIgYSBsb25nIHBlcmlvZC4gVGhpcyBtb3ZlbWVudCBjYW4gYmUgdXB3YXJkLCBkb3dud2FyZCwgb3IgZmxhdCAobm8gdHJlbmQpLiBUaGVyZSBtYXkgYmUgbm9pc2UgaW4gdGhlIGRhdGEgYW5kIHZhbHVlcyBtYXkgbW92ZSB1cCBvciBkb3duLCBidXQgb3ZlciB0aGUgbG9uZy10ZXJtLCBpZiB0aGV5IHJlbWFpbiBzdGFibGUsIHRoZW4gdGhlcmUgaXMgbm8gdHJlbmQuIFRyZW5kcyBhcmUgcGVyc2lzdGVudCBwYXR0ZXJucyB0aGF0IGxhc3QgZm9yIGEgc2lnbmlmaWNhbnQgcGVyaW9kLCBkaXN0aW5ndWlzaGluZyB0aGVtIGZyb20gc2hvcnQtdGVybSB2YXJpYXRpb25zLgoKV2UgZ2VuZXJhbGx5IHJlY29nbml6ZSB0aHJlZSB0eXBlcyBvZiB0cmVuZHMsIHdoaWNoIGNhbiBiZSByZXByZXNlbnRlZCBtYXRoZW1hdGljYWxseSBpbiB2YXJpb3VzIGZvcm1zIGRlcGVuZGluZyBvbiB0aGUgbmF0dXJlIG9mIHRoZSB0cmVuZDoKCioqTGluZWFyIFRyZW5kKio6IEEgY29uc3RhbnQgcmF0ZSBvZiBpbmNyZWFzZSBvciBkZWNyZWFzZSBvdmVyIHRpbWUuCgokVF90ID0gYSArIGJ0JAoKd2hlcmU6CgotICAgJFRfdCQgaXMgdGhlIHRyZW5kIGNvbXBvbmVudCBhdCB0aW1lICR0JAotICAgJGEkIGlzIHRoZSBpbnRlcmNlcHQKLSAgICRiJCBpcyB0aGUgc2xvcGUgKCppLmUuKiwgcmF0ZSBvZiBjaGFuZ2UpCgoqKkV4cG9uZW50aWFsIFRyZW5kKio6IEEgcmF0ZSBvZiBpbmNyZWFzZSBvciBkZWNyZWFzZSB0aGF0IGFjY2VsZXJhdGVzIG92ZXIgdGltZS4KCiRUX3QgPSBhZV57YnR9JAoKd2hlcmUgJGUkIGlzIHRoZSBiYXNlIG9mIHRoZSBuYXR1cmFsIGxvZ2FyaXRobS4KCioqUG9seW5vbWlhbCBUcmVuZCoqOiBNb3JlIGNvbXBsZXggdHJlbmRzIHRoYXQgY2FuIGNoYW5nZSBkaXJlY3Rpb24gb3ZlciB0aW1lLCBtb2RlbGVkIGFzIGhpZ2hlci1vcmRlciBwb2x5bm9taWFscy4KCiRUX3QgPSBhICsgYnQgKyBjdF4yICsgZHReMyArIFxsZG90cyQKCndoZXJlICRhLCBiLCBjLCBkLCBcbGRvdHMkIGFyZSBjb2VmZmljaWVudHMgb2YgdGhlIHBvbHlub21pYWwuCgpUcmVuZHMgY2FuIGJlIGlkZW50aWZpZWQgdGhyb3VnaCB2aXN1YWwgaW5zcGVjdGlvbiBvZiB0aW1lIHNlcmllcyBwbG90cyBvciBieSB1c2luZyBzdGF0aXN0aWNhbCBtZXRob2RzIGxpa2UgbW92aW5nIGF2ZXJhZ2VzIGFuZCByZWdyZXNzaW9uIGFuYWx5c2lzLiBQbG90dGluZyB0aGUgZGF0YSBoZWxwcyBpbiB2aXN1YWxpemluZyB0aGUgbG9uZy10ZXJtIGRpcmVjdGlvbiwgd2hpbGUgcmVncmVzc2lvbiB0ZWNobmlxdWVzIGNhbiBxdWFudGlmeSB0aGUgdHJlbmQgbWF0aGVtYXRpY2FsbHkuIEZvciBleGFtcGxlLCBpZiB3ZSBoYXZlIG1vbnRobHkgc2FsZXMgZGF0YSBmb3IgYSByZXRhaWwgc3RvcmUgb3ZlciBzZXZlcmFsIHllYXJzLCBhbmQgd2Ugb2JzZXJ2ZSB0aGF0IHNhbGVzIGFyZSBnZW5lcmFsbHkgaW5jcmVhc2luZywgdGhlbiB3ZSBjYW4gdXNlIGEgbGluZWFyIHJlZ3Jlc3Npb24gdHJlbmQgbW9kZWwgdG8gZXhwcmVzcyB0aGUgdHJlbmQgbWF0aGVtYXRpY2FsbHkgYW5kIHByb3ZpZGUgdXNlZnVsIGZvcmVjYXN0cy4KCiMjIyBDYWxjdWxhdGluZyBMUlRNIGluIFIKCkxldCdzIHVzZSB0aGUgYHdhdGVydXNhZ2UuY3N2YCBkYXRhc2V0IHRvIGlkZW50aWZ5IGFuZCBtb2RlbCB0aGUgdHJlbmQgaW4gZGFpbHkgd2F0ZXIgdXNhZ2UgdXNpbmcgdGhlIGBsbSgpYCBmdW5jdGlvbi4KCmBgYHtyIHdhcm5pbmc9RiwgbWVzc2FnZT1GfQojIExvYWQgbmVjZXNzYXJ5IGxpYnJhcmllcwpsaWJyYXJ5KGZvcmVjYXN0KQoKY3N2IDwtICJodHRwczovL3MzLnVzLWVhc3QtMi5hbWF6b25hd3MuY29tL2FydGlmaWNpdW0udXMvZGF0YXNldHMvd2F0ZXJ1c2FnZS5jc3YiCgojIExvYWQgdGhlIGRhdGEKd2F0ZXIudXNhZ2UgPC0gcmVhZC5jc3YoY3N2LAogICAgICAgICAgICAgICAgICAgICAgICBzdHJpbmdzQXNGYWN0b3JzID0gRikKCiMgZml0IGEgbGluZWFyIHRyZW5kIG1vZGVsIHVzaW5nIGxpbmVhciByZWdyZXNzaW9uCnRyZW5kLm1vZGVsIDwtIGxtKHdhdGVyLnVzYWdlJGRhaWx5IH4gd2F0ZXIudXNhZ2UkcGVyaW9kKQoKIyBGb3JlY2FzdCBmdXR1cmUgdmFsdWVzIHVzaW5nIHRoZSB0cmVuZCBtb2RlbApmb3JlY2FzdC52YWwgPC0gcHJlZGljdCh0cmVuZC5tb2RlbCwgCiAgICAgICAgICAgICAgICAgICAgICAgIG5ld2RhdGEgPSBkYXRhLmZyYW1lKHBlcmlvZCA9IDEzKSkKYGBgCgpUaGUgTGluZWFyIFJlZ3Jlc3Npb24gVHJlbmQgTW9kZWwgZm9yZWNhc3RpbmcgbW9kZWwgZXN0aW1hdGVzIHRoZSBhdmVyYWdlIGRhaWx5IHdhdGVyIHVzZSBmb3IgdGhlIG5leHQgbW9udGggdG8gYmUgYHIgcm91bmQoZm9yZWNhc3QudmFsWzEyXSwwKWAuCgpUaGUgZGV0YWlscyBvZiB0aGUgbW9kZWwgY2FuIGJlIGluc3BlY3RpbmcgYnkgdXNpbmcgdGhlIGZ1bmN0aW9uIGBzdW1tYXJ5KHNlcy5tb2RlbClgIG9yIGJ5IGFjY2Vzc2luZyBpdHMgY29tcG9uZW50czoKCi0gICBgdHJlbmQubW9kZWwkY29lZmZpY2llbnRzW1sxXV1gIGlzIHRoZSBjb2VmZmljaWVudCBvZiB0aGUgcmVncmVzc2lvbiBlcXVhdGlvbgotICAgYHRyZW5kLm1vZGVsJGNvZWZmaWNpZW50c1tbMl1dYCBpcyB0aGUgc2xvcGUgb2YgdGhlIHJlZ3Jlc3Npb24gZXF1YXRpb24KClRoZSBlcXVhdGlvbiBmb3IgdGhlIGxpbmVhciByZWdyZXNzaW9uIHRyZW5kIG1vZGVsIGlzOgoKJEYodCkgPSAzNzkuOTM5ICsgNy4zMDR0JAoKUGx1Z2dpbmcgaW4gJHQ9MTIkIHJlc3VsdHMgaW4gYHIgcm91bmQodHJlbmQubW9kZWwkY29lZmZpY2llbnRzW1sxXV0qKDEyKSArIHRyZW5kLm1vZGVsJGNvZWZmaWNpZW50c1tbMl1dLDEpYCwgdGhlIHNhbWUgdmFsdWUgYXMgd2UgZ290IGZyb20gdGhlIGBwcmVkaWN0KClgIGZ1bmN0aW9uLgoKIyMjIExlY3R1cmU6IFRyZW5kIExpbmUKCkluIHRoZSBsZWN0dXJlIGJlbG93LCBEci4gTWFydGluIFNjaGVkbGJhdWVyIG9mIEtob3VyeSBCb3N0b24gZGVtb25zdHJhdGVzIHRoZSBjYWxjdWxhdGlvbiBvZiB3ZWlnaHRlZCBtb3ZpbmcgYXZlcmFnZSBkaXJlY3RseSBpbiBSIHdpdGhvdXQgdGhlIHVzZSBvZiBhIHBhY2thZ2UuCgo8aWZyYW1lIHNyYz0iaHR0cHM6Ly9wbGF5ZXIudmltZW8uY29tL3ZpZGVvLzk3MzQyNzYxNT9iYWRnZT0wJmFtcDthdXRvcGF1c2U9MCZhbXA7cGxheWVyX2lkPTAmYW1wO2FwcF9pZD01ODQ3OSIgd2lkdGg9IjQ4MCIgaGVpZ2h0PSIzNjAiIGZyYW1lYm9yZGVyPSIxIiBhbGxvdz0iYXV0b3BsYXk7IGZ1bGxzY3JlZW47IHBpY3R1cmUtaW4tcGljdHVyZTsgY2xpcGJvYXJkLXdyaXRlIiB0aXRsZT0iQ2FsY3VsYXRpbmcgTGluZWFyIFJlZ3Jlc3Npb24gVHJlbmQgTW9kZWwgaW4gUiIgZGF0YS1leHRlcm5hbD0iMSI+Cgo8L2lmcmFtZT4KCiMjIEFSSU1BIE1vZGVscwoKQVJJTUEgKEF1dG9SZWdyZXNzaXZlIEludGVncmF0ZWQgTW92aW5nIEF2ZXJhZ2UpIGlzIGEgd2lkZWx5IHVzZWQgc3RhdGlzdGljYWwgbWV0aG9kIGZvciB0aW1lIHNlcmllcyBmb3JlY2FzdGluZy4gSXQgaXMgcGFydGljdWxhcmx5IGVmZmVjdGl2ZSBmb3IgZGF0YSB0aGF0IHNob3dzIGV2aWRlbmNlIG9mIG5vbi1zdGF0aW9uYXJpdHksIHdoZXJlIHRoZSBzdGF0aXN0aWNhbCBwcm9wZXJ0aWVzIG9mIHRoZSBzZXJpZXMgKG1lYW4sIHZhcmlhbmNlLCBldGMuKSBjaGFuZ2Ugb3ZlciB0aW1lLiBBUklNQSBtb2RlbHMgYXJlIHBvd2VyZnVsIHRvb2xzIGZvciBmb3JlY2FzdGluZyB0aW1lIHNlcmllcyBkYXRhIHRoYXQgY2FuIGJlIG1hZGUgc3RhdGlvbmFyeSB0aHJvdWdoIGRpZmZlcmVuY2luZy4KCkFSSU1BIGNvbWJpbmVzIHRocmVlIGNvbXBvbmVudHM6CgoxLiAgKipBdXRvUmVncmVzc2lvbiAoQVIpKio6IEEgbW9kZWwgdGhhdCB1c2VzIHRoZSBkZXBlbmRlbmN5IGJldHdlZW4gYW4gb2JzZXJ2YXRpb24gYW5kIGEgbnVtYmVyIG9mIGxhZ2dlZCBvYnNlcnZhdGlvbnMgKCppLmUuKiwgcHJldmlvdXMgdGltZSBwb2ludHMpLgoyLiAgKipJbnRlZ3JhdGVkIChJKSoqOiBUaGUgdXNlIG9mIGRpZmZlcmVuY2luZyBvZiBvYnNlcnZhdGlvbnMgKHN1YnRyYWN0aW5nIGFuIG9ic2VydmF0aW9uIGZyb20gYW4gb2JzZXJ2YXRpb24gYXQgdGhlIHByZXZpb3VzIHRpbWUgc3RlcCkgdG8gbWFrZSB0aGUgdGltZSBzZXJpZXMgc3RhdGlvbmFyeS4KMy4gICoqTW92aW5nIEF2ZXJhZ2UgKE1BKSoqOiBBIG1vZGVsIHRoYXQgdXNlcyB0aGUgZGVwZW5kZW5jeSBiZXR3ZWVuIGFuIG9ic2VydmF0aW9uIGFuZCBhIHJlc2lkdWFsIGVycm9yIGZyb20gYSBtb3ZpbmcgYXZlcmFnZSBtb2RlbCBhcHBsaWVkIHRvIGxhZ2dlZCBvYnNlcnZhdGlvbnMuCgpUaGUgQVJJTUEgbW9kZWwgaXMgdHlwaWNhbGx5IGRlbm90ZWQgYXMgQVJJTUEocCwgZCwgcSksIHdoZXJlOgoKLSAgICoqcCoqOiBUaGUgbnVtYmVyIG9mIGxhZyBvYnNlcnZhdGlvbnMgaW4gdGhlIG1vZGVsIChsYWcgb3JkZXIpLgotICAgKipkKio6IFRoZSBudW1iZXIgb2YgdGltZXMgdGhhdCB0aGUgcmF3IG9ic2VydmF0aW9ucyBhcmUgZGlmZmVyZW5jZWQgKGRlZ3JlZSBvZiBkaWZmZXJlbmNpbmcpLgotICAgKipxKio6IFRoZSBzaXplIG9mIHRoZSBtb3ZpbmcgYXZlcmFnZSB3aW5kb3cgKG9yZGVyIG9mIG1vdmluZyBhdmVyYWdlKS4KCiMjIyBDYWxjdWxhdGluZyBBUklNQSBpbiBSCgpJbiBSLCB0aGUgKipmb3JlY2FzdCoqIHBhY2thZ2UgcHJvdmlkZXMgZnVuY3Rpb25zIGZvciBmaXR0aW5nIEFSSU1BIG1vZGVscy4gTGV0J3MgdXNlIHRoZSBBUklNQSBtb2RlbCB0byBmb3JlY2FzdCB0aGUgYXZlcmFnZSBkYWlseSB3YXRlciB1c2FnZSBmb3IgdGhlIG5leHQgdGltZSBwZXJpb2QgdXNpbmcgdGhlIHByb3ZpZGVkIENTViBkYXRhLiBGaXJzdCwgd2UgbmVlZCB0byBjb252ZXJ0IHRoZSBgZGFpbHlgIGNvbHVtbiBpbnRvIGEgInRpbWUgc2VyaWVzIG9iamVjdCIuIE5leHQsIHdlIHVzZSB0aGUgYGF1dG8uYXJpbWFgIGZ1bmN0aW9uIGZyb20gdGhlICoqZm9yZWNhc3QqKiBwYWNrYWdlIHRvIGZpdCBhbiBBUklNQSBtb2RlbCB0byB0aGUgdGltZSBzZXJpZXMgZGF0YS4gVGhpcyBmdW5jdGlvbiBhdXRvbWF0aWNhbGx5IHNlbGVjdHMgdGhlIGJlc3QgQVJJTUEgbW9kZWwgYmFzZWQgb24gdGhlIGRhdGEuIEZpbmFsbHksIHdlIGNhbiB1c2UgdGhlIGZpdHRlZCBBUklNQSBtb2RlbCB0byBmb3JlY2FzdCB0aGUgZGFpbHkgd2F0ZXIgdXNhZ2UgZm9yIHRoZSBuZXh0IHRpbWUgcGVyaW9kLgoKYGBge3IgbG9hZFBhY2thZ2VfZm9yZWNhc3QsIGVjaG8gPSBGLCB3YXJuaW5nPUYsIG1lc3NhZ2U9Rn0KcGFja2FnZSA8LSAiZm9yZWNhc3QiCgppZiAoIXJlcXVpcmUocGFja2FnZSwgY2hhcmFjdGVyLm9ubHkgPSBUUlVFKSkgewogIGluc3RhbGwucGFja2FnZXMocGFja2FnZSwgZGVwZW5kZW5jaWVzID0gVFJVRSwgcXVpZXRseSA9IFRSVUUpCn0KCmxpYnJhcnkocGFja2FnZSwgY2hhcmFjdGVyLm9ubHkgPSBUUlVFKQpgYGAKCmBgYHtyIEFSSU1BIEV4YW1wbGUsIGVjaG8gPSBULCBldmFsID0gVH0KbGlicmFyeShmb3JlY2FzdCkKCiMgQ29udmVydCAnZGFpbHknIHRvIGEgdGltZSBzZXJpZXMgb2JqZWN0CmRhaWx5LnRzIDwtIHRzKHdhdGVyLnVzYWdlLnRzJGRhaWx5LCAKICAgICAgICAgICAgICAgc3RhcnQgPSBjKDIwMjMsIDgpLCAKICAgICAgICAgICAgICAgZnJlcXVlbmN5ID0gMTIpCgojIEZpdCBhbiBBUklNQSBtb2RlbAphcmltYS5tb2RlbCA8LSBhdXRvLmFyaW1hKGRhaWx5LnRzKQoKIyBGb3JlY2FzdCB0aGUgbmV4dCBwZXJpb2QgKG5leHQgbW9udGgpCmZvcmVjYXN0LnZhbCA8LSBmb3JlY2FzdChhcmltYS5tb2RlbCwgaCA9IDEpCmBgYAoKVGhlIEFSSU1BIGZvcmVjYXN0aW5nIG1vZGVsIGVzdGltYXRlcyB0aGUgYXZlcmFnZSBkYWlseSB3YXRlciB1c2UgZm9yIHRoZSBuZXh0IG1vbnRoIHRvIGJlIGByIHJvdW5kKGZvcmVjYXN0LnZhbCRtZWFuW1sxXV0sMClgIHdpdGggYSA5NSUgcHJlZGljdGlvbiByYW5nZSBmcm9tIGByIHJvdW5kKGZvcmVjYXN0LnZhbCRsb3dlcltbMl1dLDApYCB0byBgciByb3VuZChmb3JlY2FzdC52YWwkdXBwZXJbWzJdXSwwKWAuCgpUaGUgZGV0YWlscyBvZiB0aGUgbW9kZWwgY2FuIGJlIGluc3BlY3RpbmcgYnkgdXNpbmcgdGhlIGZ1bmN0aW9uIGBzdW1tYXJ5KGFyaW1hLm1vZGVsKWAgb3IgYnkgYWNjZXNzaW5nIGl0cyBjb21wb25lbnRzOgoKLSAgIGBmb3JlY2FzdC52YWwkbWVhbltbMV1dYCBpcyB0aGUgZm9yZWNhc3QgdmFsdWUKLSAgIGBmb3JlY2FzdC52YWwkbG93ZXJbWzJdXWAgaXMgdGhlIGxvd2VyIGJvdW5kIG9mIHRoZSA5NSUgcHJlZGljdGlvbiBpbnRlcnZhbAotICAgYGZvcmVjYXN0LnZhbCR1cHBlcltbMl1dYCBpcyB0aGUgdXBwZXIgYm91bmQgb2YgdGhlIDk1JSBwcmVkaWN0aW9uIGludGVydmFsCgpUaGUgYGZvcmVjYXN0KClgIGZ1bmN0aW9uIGFsc28gY29udGFpbnMgdGhlIHJlc2lkdWFscyBhbmQgdGhlIDgwJSBwcmVkaWN0aW9uIGludGVydmFsLgoKT3ZlcmFsbCwgQVJJTUEgaXMgYSBjb21tb25seSB1c2VkIG1ldGhvZCBmb3IgdGltZSBzZXJpZXMgZm9yZWNhc3RpbmcgYmVjYXVzZSBvZiBpdHMgZXhjZWxsZW50IGZvcmVjYXN0cyBhbmQgaXRzIGNhcGFiaWxpdHkgdG8gaGFuZGxlIHZhcmlvdXMgdHlwZXMgb2YgdGVtcG9yYWwgZGF0YS4gQnkgZml0dGluZyBhbiBBUklNQSBtb2RlbCB0byBoaXN0b3JpY2FsIGRhdGEsIGEgZGVjaXNpb24gbWFrZXIgY2FuIHVzZSBpbmZvcm1lZCBwcmVkaWN0aW9ucyBhYm91dCBmdXR1cmUgdmFsdWVzLgoKIyMjIE1hdGhlbWF0aWNhbCBGb3VuZGF0aW9uIG9mIEFSSU1BCgpUaGUgQVJJTUEgKEF1dG9SZWdyZXNzaXZlIEludGVncmF0ZWQgTW92aW5nIEF2ZXJhZ2UpIG1vZGVsIGNvbWJpbmVzIGFzcGVjdHMgb2YgYXV0b3JlZ3Jlc3NpdmUgbW9kZWxzLCBkaWZmZXJlbmNpbmcsIGFuZCBtb3ZpbmcgYXZlcmFnZXMgdG8gaGFuZGxlIG5vbi1zdGF0aW9uYXJ5IGRhdGEuIEhlcmUsIHdlIHdpbGwgZGVsdmUgaW50byB0aGUgbWF0aGVtYXRpY2FsIGZvcm11bGF0aW9uIGFuZCBjb21wb25lbnRzIG9mIEFSSU1BOiBBdXRvUmVncmVzc2l2ZSAoKkFSKikgcGFydCwgSW50ZWdyYXRlZCAoKkkqKSBwYXJ0LCBhbmQgTW92aW5nIEF2ZXJhZ2UgKCpNQSopIHBhcnQuCgpUaGUgYXV0b3JlZ3Jlc3NpdmUgcGFydCBvZiBBUklNQSBtb2RlbHMgdGhlIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIGFuIG9ic2VydmF0aW9uIGFuZCBhIG51bWJlciBvZiBsYWdnZWQgb2JzZXJ2YXRpb25zIChwcmV2aW91cyB2YWx1ZXMpLiBUaGUgb3JkZXIgb2YgdGhlIGF1dG9yZWdyZXNzaXZlIG1vZGVsIGlzIGRlbm90ZWQgYnkgJHAkLgoKVGhlIEFSIG1vZGVsIG9mIG9yZGVyICRwJCwgQVIocCksIGNhbiBiZSB3cml0dGVuIGFzOgoKJCQgeV90ID0gXHBoaV8xIHlfe3QtMX0gKyBccGhpXzIgeV97dC0yfSArIFxjZG90cyArIFxwaGlfcCB5X3t0LXB9ICsgXGVwc2lsb25fdCAkJAoKd2hlcmU6CgotICAgJHlfdCQgaXMgdGhlIHZhbHVlIG9mIHRoZSB0aW1lIHNlcmllcyBhdCB0aW1lICR0JC4KLSAgICRccGhpXzEsIFxwaGlfMiwgXGxkb3RzLCBccGhpX3AkIGFyZSB0aGUgcGFyYW1ldGVycyBvZiB0aGUgbW9kZWwuCi0gICAkXGVwc2lsb25fdCQgaXMgd2hpdGUgbm9pc2UgKGVycm9yIHRlcm0pIGF0IHRpbWUgJHQkLgoKVGhlIGludGVncmF0ZWQgcGFydCBvZiBBUklNQSBpbnZvbHZlcyBkaWZmZXJlbmNpbmcgdGhlIGRhdGEgdG8gbWFrZSBpdCBzdGF0aW9uYXJ5LiBUaGUgb3JkZXIgb2YgZGlmZmVyZW5jaW5nIGlzIGRlbm90ZWQgYnkgJGQkLiBEaWZmZXJlbmNpbmcgbWVhbnMgc3VidHJhY3RpbmcgdGhlIHByZXZpb3VzIG9ic2VydmF0aW9uIGZyb20gdGhlIGN1cnJlbnQgb2JzZXJ2YXRpb24uCgpJZiAkZCA9IDEkLCB0aGUgZGlmZmVyZW5jZWQgc2VyaWVzICR5J190JCBpczoKCiQkIHknX3QgPSB5X3QgLSB5X3t0LTF9ICQkCgpGb3IgaGlnaGVyIG9yZGVycyBvZiBkaWZmZXJlbmNpbmcsIHRoZSBkaWZmZXJlbmNpbmcgcHJvY2VzcyBpcyByZXBlYXRlZC4KClRoZSBtb3ZpbmcgYXZlcmFnZSBwYXJ0IG1vZGVscyB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gYW4gb2JzZXJ2YXRpb24gYW5kIGEgcmVzaWR1YWwgZXJyb3IgZnJvbSBhIG1vdmluZyBhdmVyYWdlIG1vZGVsIGFwcGxpZWQgdG8gbGFnZ2VkIG9ic2VydmF0aW9ucy4gVGhlIG9yZGVyIG9mIHRoZSBtb3ZpbmcgYXZlcmFnZSBtb2RlbCBpcyBkZW5vdGVkIGJ5ICRxJC4KClRoZSBNQSBtb2RlbCBvZiBvcmRlciAkcSQsIE1BKHEpLCBjYW4gYmUgd3JpdHRlbiBhczoKCiQkIHlfdCA9IFxlcHNpbG9uX3QgKyBcdGhldGFfMSBcZXBzaWxvbl97dC0xfSArIFx0aGV0YV8yIFxlcHNpbG9uX3t0LTJ9ICsgXGNkb3RzICsgXHRoZXRhX3EgXGVwc2lsb25fe3QtcX0gJCQKCndoZXJlOgoKLSAgICRcZXBzaWxvbl90LCBcZXBzaWxvbl97dC0xfSwgXGxkb3RzLCBcZXBzaWxvbl97dC1xfSQgYXJlIHRoZSB3aGl0ZSBub2lzZSBlcnJvciB0ZXJtcy4KLSAgICRcdGhldGFfMSwgXHRoZXRhXzIsIFxsZG90cywgXHRoZXRhX3EkIGFyZSB0aGUgcGFyYW1ldGVycyBvZiB0aGUgbW9kZWwuCgpUaGUgQVJJTUEgbW9kZWwgY29tYmluZXMgdGhlc2UgdGhyZWUgY29tcG9uZW50cy4gQW4gKkFSSU1BKHAsIGQsIHEpKiBtb2RlbCBpcyByZXByZXNlbnRlZCBhczoKCiQkIHknX3QgPSBccGhpXzEgeSdfe3QtMX0gKyBccGhpXzIgeSdfe3QtMn0gKyBcY2RvdHMgKyBccGhpX3AgeSdfe3QtcH0gKyBcZXBzaWxvbl90ICsgXHRoZXRhXzEgXGVwc2lsb25fe3QtMX0gKyBcdGhldGFfMiBcZXBzaWxvbl97dC0yfSArIFxjZG90cyArIFx0aGV0YV9xIFxlcHNpbG9uX3t0LXF9ICQkCgp3aGVyZSAkeSdfdCQgaXMgdGhlIGRpZmZlcmVuY2VkIHNlcmllcyBpZiAkZCQgaXMgdGhlIGRpZmZlcmVuY2luZyBvcmRlci4KCiMjIE1vZGVsIEV2YWx1YXRpb24KCk9uY2UgYSBtb2RlbCBpcyBidWlsdCwgaXQgaXMgZXNzZW50aWFsIHRvIGV2YWx1YXRlIGl0cyBwZXJmb3JtYW5jZS4gRXZhbHVhdGluZyB0aGUgcGVyZm9ybWFuY2Ugb2YgZm9yZWNhc3RpbmcgbW9kZWxzIGlzIGRvbmUgdGhyb3VnaCBhIHByb2Nlc3Mga25vd24gYXMgKmJhY2tmaXR0aW5nKiBvciAqYmFja3Rlc3RpbmcqIHdoZXJlIHdlIHVzZSB0aGUgbW9kZWwgdG8gcHJlZGljdCBlYWNoIHByaW9yIHRpbWUgcGVyaW9kIGZvciB3aGljaCB3ZSBoYXZlIGFuIGFjdHVhbCBvYnNlcnZhdGlvbiBhbmQgY29tcGFyaW5nIHRoZSBmb3JlY2FzdCB2YWx1ZSBhZ2FpbnN0IHRoZSBhY3R1YWwgdmFsdWUuIFRoZSBkaWZmZXJlbmNlIGJldHdlZW4gdGhlIHR3byBpcyB0aGUgImVycm9yIiBvciAiZGV2aWF0aW9uIi4KCkNvbW1vbiBldmFsdWF0aW9uIG1ldHJpY3MgZm9yIGZvcmVjYXN0aW5nIG1vZGVscyBpbmNsdWRlOgoKLSAgICoqTWVhbiBBYnNvbHV0ZSBFcnJvciAoTUFFKSoqOiBUaGUgYXZlcmFnZSBvZiB0aGUgYWJzb2x1dGUgZXJyb3JzLgotICAgKipNZWFuIFNxdWFyZWQgRXJyb3IgKE1TRSkqKjogVGhlIGF2ZXJhZ2Ugb2YgdGhlIHNxdWFyZWQgZXJyb3JzLgotICAgKipSb290IE1lYW4gU3F1YXJlZCBFcnJvciAoUk1TRSkqKjogVGhlIHNxdWFyZSByb290IG9mIE1TRS4KLSAgICoqTWVhbiBBYnNvbHV0ZSBQZXJjZW50YWdlIEVycm9yIChNQVBFKSoqOiBUaGUgYXZlcmFnZSBvZiB0aGUgYWJzb2x1dGUgcGVyY2VudGFnZSBlcnJvcnMuCgpUaGVzZSBtZXRyaWNzIGFyZSBhbHNvIGJlIHVzZWQgdG8gY29tcGFyZSBtb2RlbCBwZXJmb3JtYW5jZS4gSWYgbW9kZWwgQSBoYXMgYSBzdWJzdGFudGlhbGx5IGxvd2VyIE1TRSB0aGFuIG1vZGVsIEIsIGl0IG1lYW5zIHRoYXQsIG9uIGF2ZXJhZ2UsIGl0cyBwcmVkaWN0aW9ucyBhcmUgY2xvc2VyIHRvIHRoZSBhY3R1YWwgdmFsdWUgYW5kIGFyZSBsaWtlbHkgZ29pbmcgdG8gYmUgbW9yZSBhY2N1cmF0ZSBhbmQgdGhhdCBtb2RlbCBBIGlzIHByZWZlcmFibGUgZm9yIG1ha2luZyBmb3JlY2FzdHMuCgpMZXQncyBsb29rIGF0IG9uZSBvZiB0aG9zZSBtZXRyaWNzIGluIG1vcmUgZGV0YWlsOiAqTUFFKi4gVGhlIG90aGVyIG1ldHJpY3MgYXJlIGNhbGN1bGF0ZWQgdmVyeSBzaW1pbGFybHksIHNvIGxvb2tpbmcgYXQganVzdCAqTUFFKiBhbHNvIHNob3dzIHVzIGhvdyB0byBjYWxjdWxhdGUgdGhlIG90aGVycy4KCk1BRSBjYWxjdWxhdGVzIHRoZSBhdmVyYWdlIG9mIHRoZSBhYnNvbHV0ZSBlcnJvcnMuIEl0IGlzIGFsc28ga25vd24gYXMgKk1lYW4gQWJzb2x1dGUgRGV2aWF0aW9uIChNQUQpKi4gTGV0J3MgY29tcGFyZSB0d28gZm9yZWNhc3RpbmcgbW9kZWxzIGZvciB0aGUgd2F0ZXIgdXNhZ2UgdGltZSBzZXJpZXMgd2UgaGF2ZSBsb29rZWQgYXQgYmVmb3JlOiBMaW5lYXIgUmVncmVzc2lvbiBUcmVuZCBNb2RlbCBhbmQgV2VpZ2h0ZWQgTW92aW5nIEF2ZXJhZ2UuIFRvIGNhbGN1bGF0ZSB0aGUgKk1BRSosIHdlIG5lZWQgdG8gdXNlIGVhY2ggbW9kZWwgdG8gY2FsY3VsYXRlIGEgImZvcmVjYXN0IiBmb3IgcHJpb3IgdGltZSBwZXJpb2RzIHdoZXJlIHdlIGhhdmUgdGhlIGFjdHVhbCBvYnNlcnZlZCB2YWx1ZXMgYW5kIHRoZW4gY2FsY3VsYXRlIHRoZSBkaWZmZXJlbmNlIGJldHdlZW4gdGhlIGZvcmVjYXN0cyBhbmQgYWN0dWFsIHZhbHVlcyBhbmQgZmluYWxseSBjYWxjdWxhdGUgdGhlIGF2ZXJhZ2UgKG1lYW4pIG9mIHRoZSBhYnNvbHV0ZSBlcnJvcnMuCgpTbywgKk1BRSogaXMgZGVmaW5lZCBhcwoKJFx0ZXh0e01BRX0gPSBcZnJhY3sxfXtufVxzdW1fe3Q9MX1ee259KHxZX3QgLSBGX3R8KSQKClRoZSBjb2RlIGJlbG93IGRlbW9uc3RyYXRlcyBob3cgdG8gZG8gdGhlIGNhbGN1bGF0aW9uIG9mIE1BRSBleHBsaWNpdGx5LiBXZSB3aWxsIHJldXNlIHRoZSBgY2FsY3VsYXRlLndtYSgpYCBmdW5jdGlvbiBkZXZlbG9wZWQgaW4gc2VjdGlvbiBbV2VpZ2h0ZWQgTW92aW5nIEF2ZXJhZ2VdIGFuZCB0aGUgbGluZWFyIHJlZ3Jlc3Npb24gbW9kZWwgY3JlYXRlZCBpbiB0aGUgc2VjdGlvbiBbTGluZWFyIFJlZ3Jlc3Npb24gVHJlbmQgTW9kZWxdLiBHbyBiYWNrIGFuZCByZWZyZXNoIHlvdXIgbWVtb3J5IGZvciBib3RoIG9mIHRob3NlIG1vZGVscyBiZWZvcmUgcHJvY2VlZGluZy4gV2UgYXNzdW1lIHRoYXQgdGhlIGRhdGEgc2V0IGhhcyBiZWVuIGxvYWRlZCBpbnRvIHRoZSBkYXRhIGZyYW1lIGB3YXRlci51c2FnZWAgYXMgYmVmb3JlLgoKTm90ZSB0aGF0IHNvbWUgbW9kZWxpbmcgZnVuY3Rpb25zIHN1Y2ggYXMgYGxtYCBhbmQgYGF1dG8uYXJpbWFgIHdpbGwgYXV0b21hdGljYWxseSBjYWxjdWxhdGUgcmVzaWR1YWxzIHdoaWNoIGFyZSB0aGUgZGlmZmVyZW5jZSBiZXR3ZWVuIGFjdHVhbCBhbmQgcHJlZGljdGVkIHZhbHVlcyAtLSBhbHRob3VnaCBub3QgdGhlIGFic29sdXRlIHZhbHVlLCBidXQgdGhhdCBpcyBzaW1wbGUgdG8gY2FsY3VsYXRlLiBOYXR1cmFsbHksIHRoaXMgbWFrZXMgY2FsY3VsYXRpbmcgdGhlIG1ldHJpY3MgbXVjaCBzaW1wbGVyLgoKIyMjIENhbGN1bGF0aW5nIE1BRSBmb3IgV01BIE1vZGVsCgpUaGUgY29kZSBiZWxvdyBjYWxjdWxhdGVzIGEgImZvcmVjYXN0IiBmb3IgZWFjaCBwcmlvciB0aW1lIHBlcmlvZCBleGNlcHQgdGhlIGZpcnN0IHRocmVlIGFzIGl0IHdvdWxkIGJlIGltcG9zc2libGUgdG8gZG8gc28gYXMgdGhleSBkbyBub3QgaGF2ZSB0aHJlZSBwcmlvciB0aW1lIHBlcmlvZHMuIFRoZSBmdW5jdGlvbiBgY2FsY3VsYXRlLndtYSgpYCBwbGFjZXMgdGhlICJmb3JlY2FzdCIgdmFsdWUgZm9yIHRpbWUgcGVyaW9kICR0JCBpbnRvIG5leHQgcm93LiBTbywgdGhlIGZvcmVjYXN0IGZvciB0aW1lIHBlcmlvZCAxMyBpcyBpbiByb3cgMTIgYW5kIHRoZSBmb3JlY2FzdCBmb3IgdGltZSBwZXJpb2QgNCBpcyBpbiByb3cgMy4gV2UgbmVlZCB0byBiZSBtaW5kZnVsIG9mIHRoYXQgd2hlbiB3ZSBjYWxjdWxhdGUgdGhlIGVycm9ycy4KCj4gQmVmb3JlIHJ1bm5pbmcgdGhlIGNvZGUsIGRvIHRoZSBjYWxjdWxhdGlvbnMgYnkgaGFuZCBvciBpbiBhIHNwcmVhZHNoZWV0IHByb2dyYW0gYW5kIGNvbnZpbmNlIHlvdXJzZWxmIHRoYXQgdGhlIHJlc3VsdCBwcm9kdWNlZCBieSB0aGUgY29kZSBpcyBjb3JyZWN0LgoKYGBge3J9CiMgZGVmaW5lIHRoZSB3ZWlnaHRzIGZvciB0aGUgV01BICgzLXBlcmlvZCBleGFtcGxlKQp3IDwtIGMoMC4xLCAwLjMsIDAuNikKCiMgY2FsY3VsYXRlICJmb3JlY2FzdCIgZm9yIGVhY2ggcHJpb3IgdGltZSBwZXJpb2QKd2F0ZXIudXNhZ2UkV01BIDwtIGNhbGN1bGF0ZS53bWEod2F0ZXIudXNhZ2UkZGFpbHksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuID0gMywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHdlaWdodHMgPSB3KQoKcGVyaW9kcyA8LSBucm93KHdhdGVyLnVzYWdlKQoKIyBjYWxjdWxhdGUgdGhlIGFic29sdXRlIGVycm9ycwpmb3IgKHQgaW4gMzpwZXJpb2RzKSB7CiAgd2F0ZXIudXNhZ2UkYWJzLmVyclt0XSA8LSBhYnMod2F0ZXIudXNhZ2UkZGFpbHlbdF0gLSB3YXRlci51c2FnZSRXTUFbdC0xXSkKfQoKIyBjYWxjdWxhdGUgbWVhbiBvZiBhYnNvbHV0ZSBlcnJvcnMgKE1BRSkKTUFFLldNQSA8LSBtZWFuKHdhdGVyLnVzYWdlJGFicy5lcnIsIG5hLnJtID0gVCkKYGBgCgpUaGUgTUFFIGZvciB0aGUgV01BIHdpdGggdGhlIGFib3ZlIHdlaWdodHMgaXMgYHIgcm91bmQoTUFFLldNQSwyKWAuIEJ5IGl0c2VsZiBpdCBpcyBub3QgdGhhdCBtZWFuaW5nZnVsLCBhbHRob3VnaCBhIGxhcmdlIE1BRSBpcyBjb21wYXJpc29uIHRvIHRoZSB2YWx1ZXMgaW4gdGhlIHRpbWUgc2VyaWVzIHdvdWxkIGluZGljYXRlIHRoYXQgdGhlIG1vZGVsJ3MgZm9yZWNhc3RzIGFyZSB3aWxkbHkgb2ZmLiBNb3JlIHVzZWZ1bCBpcyBjb21wYXJpbmcgdGhlIE1BRSBvZiB0aHcgV01BIHdpdGggYW5vdGhlciBtb2RlbC4gU28sIGxldCdzIGRvIHRoYXQgYnkgY2FsY3VsYXRpbmcgdGhlIE1BRSBmb3IgdGhlIExpbmVhciBSZWdyZXNzaW9uIFRyZW5kIE1vZGVsLgoKKipNQUUgZm9yIExpbmVhciBSZWdyZXNzaW9uIFRyZW5kIE1vZGVsOioqCgpSYXRoZXIgdGhhbiBjYWxjdWxhdGluZyB0aGUgZXJyb3JzIHVzaW5nIHRoZSBzbG9wZSBhbmQgaW50ZXJjZXB0IGZyb20gdGhlIGBsbSgpYCBmdW5jdGlvbiwgd2Ugd2lsbCByZWx5IG9uIHRoZSByZXNpZHVhbHMgcHJvZHVjZWQgYnkgYGxtYCAtLSB0aGUgcmVzaWR1YWxzIGFyZSB0aGUgYWN0dWFsIGVycm9ycy4gVGhleSBjYW4gYmUgZm91bmQgaW4gYHRyZW5kLm1vZGVsJHJlc2lkdWFsc2AuIFByaW50IHRoZW0gb3V0IHRvIGNvbnZpbmNlIHlvdXJzZWxmIHRoYXQgdGhleSBhcmUgY29ycmVjdC4gRm9yIGV4YW1wbGUsIGZvciB0aW1lIHBlcmlvZCAxMiwgd2UgY2FuIHNlZToKCmBgYHtyfQpGLjEyIDwtIHRyZW5kLm1vZGVsJGNvZWZmaWNpZW50c1tbMV1dICsgdHJlbmQubW9kZWwkY29lZmZpY2llbnRzW1syXV0qMTIKWS4xMiA8LSB3YXRlci51c2FnZSRkYWlseVsxMl0KCnJlc2lkdWFsLjEyLjEgPC0gWS4xMiAtIEYuMTIKcmVzaWR1YWwuMTIuMiA8LSB0cmVuZC5tb2RlbCRyZXNpZHVhbHNbMTJdCgpjYXQoInJlc2lkdWFscyBhcmUiLCByZXNpZHVhbC4xMi4xLCAidnMiLCByZXNpZHVhbC4xMi4yKQpgYGAKCj4gQmVmb3JlIHJ1bm5pbmcgdGhlIGNvZGUsIGRvIHRoZSBjYWxjdWxhdGlvbnMgYnkgaGFuZCBvciBpbiBhIHNwcmVhZHNoZWV0IHByb2dyYW0gYW5kIGNvbnZpbmNlIHlvdXJzZWxmIHRoYXQgdGhlIHJlc3VsdCBwcm9kdWNlZCBieSB0aGUgY29kZSBpcyBjb3JyZWN0LgoKYGBge3J9Ck1BRS5MUlRNIDwtIG1lYW4oYWJzKHRyZW5kLm1vZGVsJHJlc2lkdWFscyksIG5hLnJtID0gVCkKYGBgCgpUaGUgTUFFIGZvciB0aGUgTFJUTSBpcyBgciByb3VuZChNQUUuTFJUTSwyKWAuIFRoaXMgaXMgc21hbGxlciB0aGFuIHRoZSBNQUUgZm9yIHRoZSBXTUEgd2hpY2ggbWVhbnMgdGhhdCB0aGUgYmFja2ZpdHRlZCBmb3JlY2FzdHMgd2VyZSBjbG9zZXIgdG8gdGhlIG9ic2VydmVkIHZhbHVlcy4gV2UgY2FuIHN1cm1pc2UgdGhhdCBmdXR1cmUgZm9yZWNhc3RzIG9mIHRoZSBMUlRNIHdpbGwgbGlrZWx5IGJlIG1vcmUgYWNjdXJhdGUgY29tcGFyZWQgdG8gdGhlIFdNQSBtb2RlbCAod2l0aCB0aG9zZSB3ZWlnaHRzOyBwZXJoYXBzIGEgV01BIHdpdGggZGlmZmVyZW50IHdlaWdodHMgbWlnaHQgYmUgYmV0dGVyKS4KCiMjIyBMZWN0dXJlOiBNb2RlbCBFdmFsdWF0aW9uCgpJbiB0aGUgbGVjdHVyZSBiZWxvdywgRHIuIE1hcnRpbiBTY2hlZGxiYXVlciBvZiBLaG91cnkgQm9zdG9uIGRlbW9uc3RyYXRlcyB0aGUgY2FsY3VsYXRpb24gb2Ygd2VpZ2h0ZWQgbW92aW5nIGF2ZXJhZ2UgZGlyZWN0bHkgaW4gUiB3aXRob3V0IHRoZSB1c2Ugb2YgYSBwYWNrYWdlLiBOb3RlIHRoYXQgdGhlIGxlY3R1cmUgdXNlcyB0aGUgYWx0ZXJuYXRpdmUgdGVybSAqTUFEKiBpbnN0ZWFkIG9mICpNQUUqLCBidXQgdGhleSBhcmUgZGVmaW5lZCB0aGUgc2FtZTsgYm90aCB0ZXJtcyBhcmUgdXNlZCBpbiBwcmFjdGljZS4KCjxpZnJhbWUgc3JjPSJodHRwczovL3BsYXllci52aW1lby5jb20vdmlkZW8vOTczNDQxNDM3P2JhZGdlPTAmYW1wO2F1dG9wYXVzZT0wJmFtcDtwbGF5ZXJfaWQ9MCZhbXA7YXBwX2lkPTU4NDc5IiB3aWR0aD0iNDgwIiBoZWlnaHQ9IjM2MCIgZnJhbWVib3JkZXI9IjAiIGFsbG93PSJhdXRvcGxheTsgZnVsbHNjcmVlbjsgcGljdHVyZS1pbi1waWN0dXJlOyBjbGlwYm9hcmQtd3JpdGUiIHRpdGxlPSJFdmFsdWF0aW5nIE1vZGVscyBpbiBSIHdpdGggTUFEIiBkYXRhLWV4dGVybmFsPSIxIj4KCjwvaWZyYW1lPgoKIyMgTW9kZWwgVHVuaW5nCgoqKk1vZGVsIHR1bmluZyoqIGlzIHRoZSBwcm9jZXNzIG9mIG9wdGltaXppbmcgYSBtb2RlbCdzIGh5cGVycGFyYW1ldGVycyB0byBpbXByb3ZlIGl0cyBwZXJmb3JtYW5jZS4gSHlwZXJwYXJhbWV0ZXJzIGFyZSBwYXJhbWV0ZXJzIHRoYXQgYXJlIHNldCBiZWZvcmUgdGhlIGxlYXJuaW5nIHByb2Nlc3MgYmVnaW5zIGFuZCBhcmUgbm90IGxlYXJuZWQgZnJvbSB0aGUgZGF0YS4gSW4gY29udHJhc3QsIG1vZGVsIHBhcmFtZXRlcnMgYXJlIGxlYXJuZWQgZnJvbSB0aGUgZGF0YSBkdXJpbmcgdHJhaW5pbmcuIEZvciBleGFtcGxlLCB0aGUgd2VpZ2h0cyBmb3IgdGhlIFdlaWdodGVkIE1vdmluZyBBdmVyYWdlIG1vZGVsIGFyZSBoeXBlcnBhcmFtZXRlcnMgYW5kIHNvIGlzIHRoZSAkXGFscGhhJCBmb3IgU2ltcGxlIEV4cG9uZW50aWFsIFNtb290aGluZy4KClR1bmluZyBpbnZvbHZlcyBzZWxlY3RpbmcgdGhlIGJlc3Qgc2V0IG9mIGh5cGVycGFyYW1ldGVycyB0aGF0IG1pbmltaXplIGEgcHJlZGVmaW5lZCBsb3NzIGZ1bmN0aW9uIG9yIG1heGltaXplIHRoZSBtb2RlbCdzIGFjY3VyYWN5LiBUaGlzIHByb2Nlc3Mgb2Z0ZW4gaW52b2x2ZXMgdGVjaG5pcXVlcyBzdWNoIGFzIGNyb3NzLXZhbGlkYXRpb24sIGdyaWQgc2VhcmNoLCBvciByYW5kb20gc2VhcmNoLgoKIyMjIEV4YW1wbGU6IFR1bmluZyBBbHBoYSBmb3IgU0VTCgpMZXQncyB0YWtlIGEgbG9vayBhdCBhbiBleGFtcGxlIG9mIHR1bmluZyBhIG1vZGVsLiBJbiBTaW1wbGUgRXhwb25lbnRpYWwgU21vb3RoaW5nIChTRVMpLCB0aGUgaHlwZXJwYXJhbWV0ZXIgJFxhbHBoYSQgKGFscGhhKSBjb250cm9scyB0aGUgc21vb3RoaW5nIGZhY3Rvci4gVGhlIHZhbHVlIG9mICRcYWxwaGEkIHJhbmdlcyBiZXR3ZWVuIDAgYW5kIDEgYW5kIHdpbGUgd2Ugb2Z0ZW4gdXNlIGEgdmFsdWUgb2YgMC4zIHRoaXMgbWF5IG5vdCBiZSB0aGUgb25lIHRoYXQgaGFzIHRoZSBiZXN0IGZpdCBhbmQgcHJvZHVjZXMgdGhlIHNtYWxsZXN0IE1BRSBvciBNU0UuIEZ1cnRoZXJtb3JlLCBhIHZhbHVlIGNsb3NlciB0byAxIHB1dHMgbW9yZSB3ZWlnaHQgb24gcmVjZW50IG9ic2VydmF0aW9ucywgbWFraW5nIHRoZSBtb2RlbCBtb3JlIHJlc3BvbnNpdmUgdG8gcmVjZW50IGNoYW5nZXMsIHdoaWxlIGEgdmFsdWUgY2xvc2VyIHRvIDAgZ2l2ZXMgbW9yZSB3ZWlnaHQgdG8gcGFzdCBvYnNlcnZhdGlvbnMsIG1ha2luZyB0aGUgbW9kZWwgbGVzcyByZXNwb25zaXZlIHRvIHJlY2VudCBjaGFuZ2VzLgoKVGhlIGdvYWwgb2YgbW9kZWwgdHVuaW5nIGlzIHRvIGZpbmQgdGhlICRcYWxwaGEkIHZhbHVlIHRoYXQgbWluaW1pemVzIHRoZSBmb3JlY2FzdGluZyBlcnJvci4gRmlyc3QsIHdlIG5lZWQgdG8gY2hvb3NlIGEgbG9zcyBmdW5jdGlvbiB0byBldmFsdWF0ZSB0aGUgbW9kZWwncyBwZXJmb3JtYW5jZS4gQ29tbW9uIGNob2ljZXMgYXJlIE1lYW4gQWJzb2x1dGUgRXJyb3IgKE1BRSksIE1lYW4gU3F1YXJlZCBFcnJvciAoTVNFKSwgb3IgUm9vdCBNZWFuIFNxdWFyZWQgRXJyb3IgKFJNU0UpLiBGb3IgdGhpcyBleGFtcGxlLCB3ZSB3aWxsIHVzZSBNU0UuIFdlIG5lZWQgdG8gdGhlbiB0cnkgdmFyaW91cyB2YWx1ZXMgb2YgJFxhbHBoYSQgYW5kIHBlcmZvcm0gYSBncmlkIHNlYXJjaCBvdmVyIGEgcmFuZ2Ugb2YgJFxhbHBoYSQgdmFsdWVzIHRvIGZpbmQgdGhlIG9uZSB0aGF0IG1pbmltaXplcyBNU0UuCgpIZXJl4oCZcyBob3cgeW91IGNhbiBwZXJmb3JtIGdyaWQgc2VhcmNoIHRvIHR1bmUgdGhlIGFscGhhIHBhcmFtZXRlciBmb3IgU2ltcGxlIEV4cG9uZW50aWFsIFNtb290aGluZyBpbiBSIHVzaW5nIHRoZSBgd2F0ZXJ1c2FnZS5jc3ZgIGRhdGFzZXQgYXNzdW1pbmcgdGhlIHNhbWUgZGF0YSBmcmFtZSB3ZSBsb2FkZWQgYmVmb3JlLiBUaGUgYEhvbHRXaW50ZXJzYCBmdW5jdGlvbiBmaXRzIGFuIFNFUyBtb2RlbCB1c2luZyB0aGUgYW4gYWxwaGEgdmFsdWUsIGFuZCB0aGUgYGZvcmVjYXN0YCBmdW5jdGlvbiBnZW5lcmF0ZXMgZm9yZWNhc3RzIGZvciB0aGUgbmV4dCAxMiBwZXJpb2RzLgoKYGBge3J9CiMgQ29udmVydCAnZGFpbHknIHRvIGEgdGltZSBzZXJpZXMgb2JqZWN0CmRhaWx5LnRzIDwtIHRzKHdhdGVyLnVzYWdlJGRhaWx5LCAKICAgICAgICAgICAgICAgc3RhcnQgPSBjKDIwMjMsIDgpLCAKICAgICAgICAgICAgICAgZnJlcXVlbmN5ID0gMTIpCgojIERlZmluZSBhIHJhbmdlIG9mIGFscGhhIHZhbHVlcyB0byBzZWFyY2ggb3ZlcgphbHBoYS52YWx1ZXMgPC0gc2VxKDAuMDEsIDEsIGJ5ID0gMC4wMSkKCiMgRGVmaW5lIGEgZnVuY3Rpb24gdG8gY2FsY3VsYXRlIHRoZSBNU0UgZm9yIGEgZ2l2ZW4gYWxwaGEKY2FsY3VsYXRlLm1zZSA8LSBmdW5jdGlvbihhbHBoYSwgZGF0YSkgewogIHNlcy5tb2RlbCA8LSBIb2x0V2ludGVycyhkYXRhLCBhbHBoYSA9IGFscGhhLCBiZXRhID0gRkFMU0UsIGdhbW1hID0gRkFMU0UpCiAgZml0dGVkLnZhbHVlcyA8LSBmaXR0ZWQoc2VzLm1vZGVsKVssIDFdICAKICAjIEV4dHJhY3QgdGhlIGZpdHRlZCB2YWx1ZXMKICBtc2UgPC0gbWVhbigoZGF0YSAtIGZpdHRlZC52YWx1ZXMpXjIsIG5hLnJtID0gVFJVRSkKICByZXR1cm4obXNlKQp9CgojIFBlcmZvcm0gZ3JpZCBzZWFyY2ggb3ZlciB0aGUgYWxwaGEgdmFsdWVzCm1zZS52YWx1ZXMgPC0gc2FwcGx5KGFscGhhLnZhbHVlcywgY2FsY3VsYXRlLm1zZSwgZGF0YSA9IGRhaWx5LnRzKQoKIyBGaW5kIHRoZSBhbHBoYSB2YWx1ZSB0aGF0IG1pbmltaXplcyB0aGUgTVNFCmJlc3QuYWxwaGEgPC0gYWxwaGEudmFsdWVzW3doaWNoLm1pbihtc2UudmFsdWVzKV0KY2F0KCJCZXN0IGFscGhhOiIsIGJlc3QuYWxwaGEsICJcbiIpCmBgYAoKTW9kZWwgdHVuaW5nIGlzIGEgZXNzZW50aWFsIHN0ZXAgaW4gaW1wcm92aW5nIHRoZSBwZXJmb3JtYW5jZSBvZiBmb3JlY2FzdGluZyBtb2RlbHMgYnkgb3B0aW1pemluZyBpdHMgaHlwZXJwYXJhbWV0ZXJzLiBJbiB0aGlzIGV4YW1wbGUsIHdlIHBlcmZvcm1lZCBncmlkIHNlYXJjaCB0byB0dW5lIHRoZSBhbHBoYSBwYXJhbWV0ZXIgZm9yIFNpbXBsZSBFeHBvbmVudGlhbCBTbW9vdGhpbmcgKFNFUykuIEJ5IGZpbmRpbmcgdGhlIGFscGhhIHZhbHVlIHRoYXQgbWluaW1pemVzIHRoZSBNZWFuIFNxdWFyZWQgRXJyb3IgKE1TRSksIHdlIGltcHJvdmVkIHRoZSBtb2RlbCdzIGFjY3VyYWN5LCBsZWFkaW5nIHRvIG1vcmUgcmVsaWFibGUgZm9yZWNhc3RzLiBUaGUgcHJvY2VzcyBvZiB0dW5pbmcgaHlwZXJwYXJhbWV0ZXJzIGNhbiBiZSBhcHBsaWVkIHRvIGFsbCBmb3JlY2FzdGluZyBtb2RlbHMgYW5kIGRhdGFzZXRzIHRvIGFjaGlldmUgb3B0aW1hbCBmaXQuCgojIyBFbnNlbWJsZSBNZXRob2RzCgpBbiBlbnNlbWJsZSBtb2RlbCBjb21iaW5lcyBwcmVkaWN0aW9ucyBmcm9tIG11bHRpcGxlIG1vZGVscyB0byBpbXByb3ZlIG92ZXJhbGwgZm9yZWNhc3RpbmcgYWNjdXJhY3kgYW5kIHJvYnVzdG5lc3MuIFRoZSBpZGVhIGlzIHRoYXQgYnkgYWdncmVnYXRpbmcgdGhlIHN0cmVuZ3RocyBvZiB2YXJpb3VzIG1vZGVscywgdGhlIGVuc2VtYmxlIGNhbiBvdXRwZXJmb3JtIGFueSBzaW5nbGUgbW9kZWwuIEVuc2VtYmxlIG1ldGhvZHMgY2FuIGJlIHVzZWQgaW4gcmVncmVzc2lvbiwgY2xhc3NpZmljYXRpb24sIGFuZCB0aW1lIHNlcmllcyBmb3JlY2FzdGluZyB0YXNrcy4KCkxldCdzIGNyZWF0ZSBhbiBlbnNlbWJsZSBtb2RlbCBieSBjb21iaW5pbmcgdGhlIHByZWRpY3Rpb25zIGZyb20gdGhyZWUgZGlmZmVyZW50IG1vZGVsczoKCjEuICAqKkFSSU1BIChBdXRvUmVncmVzc2l2ZSBJbnRlZ3JhdGVkIE1vdmluZyBBdmVyYWdlKSoqCjIuICAqKlNFUyAoU2ltcGxlIEV4cG9uZW50aWFsIFNtb290aGluZykqKgozLiAgKipMaW5lYXIgVHJlbmQgTW9kZWwqKgoKV2Ugd2lsbCBhdmVyYWdlIHRoZSBwcmVkaWN0aW9ucyBmcm9tIHRoZSB0aHJlZSBtb2RlbHMgdG8gY3JlYXRlIHRoZSBlbnNlbWJsZSBmb3JlY2FzdCwgYWx0aG91Z2ggaWYgd2UgZmluZCB0aGF0IG9uZSBtb2RlbCBoYXMgZ2VuZXJhbGx5IGxvd2VyICpNQUQqIG9yICpNU0UqIHdlIG1pZ2h0IGNob29zZSBhIHdlaWdodGVkIGF2ZXJhZ2Ugd2l0aCBoaWdoZXIgd2VpZ2h0cyBmb3IgdGhlIGJldHRlciBwZXJmb3JtaW5nIG1vZGVsLgoKYGBge3J9CmxpYnJhcnkoVFRSKQpsaWJyYXJ5KGZvcmVjYXN0KQoKY3N2IDwtICJodHRwczovL3MzLnVzLWVhc3QtMi5hbWF6b25hd3MuY29tL2FydGlmaWNpdW0udXMvZGF0YXNldHMvd2F0ZXJ1c2FnZS5jc3YiCgojIExvYWQgdGhlIGRhdGEKd2F0ZXIudXNhZ2UgPC0gcmVhZC5jc3YoY3N2LAogICAgICAgICAgICAgICAgICAgICAgICBzdHJpbmdzQXNGYWN0b3JzID0gRikKCiMgQ29udmVydCAnZGFpbHknIHRvIGEgdGltZSBzZXJpZXMgb2JqZWN0CmRhaWx5LnRzIDwtIHRzKHdhdGVyLnVzYWdlJGRhaWx5LCBzdGFydCA9IGMoMjAyMywgOCksIAogICAgICAgICAgICAgICBmcmVxdWVuY3kgPSAxMikKCiMgRml0IEFSSU1BIG1vZGVsCmFyaW1hLm1vZGVsIDwtIGF1dG8uYXJpbWEoZGFpbHkudHMpCmFyaW1hLmZvcmVjYXN0IDwtIGZvcmVjYXN0KGFyaW1hLm1vZGVsLCBoID0gMTIpJG1lYW4KCiMgRml0IFNFUyBtb2RlbApzZXMubW9kZWwgPC0gSG9sdFdpbnRlcnMoZGFpbHkudHMsIAogICAgICAgICAgICAgICAgICAgICAgICAgYmV0YSA9IEZBTFNFLCAKICAgICAgICAgICAgICAgICAgICAgICAgIGdhbW1hID0gRkFMU0UpCnNlcy5mb3JlY2FzdCA8LSBmb3JlY2FzdChzZXMubW9kZWwsIGggPSAxMikkbWVhbgoKIyBGaXQgTGluZWFyIFRyZW5kIG1vZGVsCnQgPC0gdGltZShkYWlseS50cykKbGluZWFyLm1vZGVsIDwtIGxtKHdhdGVyLnVzYWdlJGRhaWx5IH4gdCkKZnV0dXJlLnRpbWUgPC0gc2VxKGZyb20gPSAobWF4KHQpICsgMS8xMiksIAogICAgICAgICAgICAgICAgICAgdG8gPSAobWF4KHQpICsgMTIvMTIpLCAKICAgICAgICAgICAgICAgICAgIGJ5ID0gMS8xMikKbGluZWFyLmZvcmVjYXN0IDwtIHByZWRpY3QobGluZWFyLm1vZGVsLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgbmV3ZGF0YSA9IGRhdGEuZnJhbWUodGltZSA9IGZ1dHVyZS50aW1lKSkKCiMgQ29tYmluZSBwcmVkaWN0aW9ucyAoZW5zZW1ibGUpCmVuc2VtYmxlLmZvcmVjYXN0IDwtIChhcmltYS5mb3JlY2FzdCArIHNlcy5mb3JlY2FzdCArIGxpbmVhci5mb3JlY2FzdCkgLyAzCmBgYAoKVGhlIGZvcmVjYXN0IGZvciB0aGUgZW5zZW1ibGUgbW9kZWwgaXMgYHIgcm91bmQoZW5zZW1ibGUuZm9yZWNhc3RbMTJdLDEpYC4gRW5zZW1ibGUgbW9kZWxzIGNvbWJpbmUgcHJlZGljdGlvbnMgZnJvbSBtdWx0aXBsZSBtb2RlbHMgdG8gaW1wcm92ZSBmb3JlY2FzdGluZyBhY2N1cmFjeSBhbmQgcm9idXN0bmVzcy4gQnkgYXZlcmFnaW5nIHRoZSBmb3JlY2FzdHMgZnJvbSBBUklNQSwgU0VTLCBhbmQgTGluZWFyIFRyZW5kIG1vZGVscywgd2UgY3JlYXRlZCBhbiBlbnNlbWJsZSBtb2RlbCB0aGF0IGxldmVyYWdlcyB0aGUgc3RyZW5ndGhzIG9mIGVhY2ggaW5kaXZpZHVhbCBtb2RlbC4gVGhpcyBhcHByb2FjaCBjYW4gbGVhZCB0byBtb3JlIGFjY3VyYXRlIGFuZCByZWxpYWJsZSBmb3JlY2FzdHMuCgojIyBTZWFzb25hbGl0eSBBZGp1c3RtZW50CgpTZWFzb25hbGl0eSBpbnZvbHZlcyBmbHVjdHVhdGlvbnMgdGhhdCBvY2N1ciBhdCByZWd1bGFyIGludGVydmFscyBhbmQgYXJlIHRodXMgcGVyaW9kaWMuIEZvciBleGFtcGxlLCByZXRhaWwgc2FsZXMgbWlnaHQgaW5jcmVhc2UgZXZlcnkgRGVjZW1iZXIgZHVlIHRvIHRoZSBob2xpZGF5IHNob3BwaW5nIHNlYXNvbiwgb3IgZWxlY3RyaWNpdHkgY29uc3VtcHRpb24gbWlnaHQgcGVhayBldmVyeSBzdW1tZXIgZHVlIHRvIGluY3JlYXNlZCB1c2Ugb2YgYWlyIGNvbmRpdGlvbmluZy4gRnVydGhlcm1vcmUsIHRoZSBsZW5ndGggb2YgdGhlIHNlYXNvbmFsIGN5Y2xlIGlzIGNvbnN0YW50IGFuZCBrbm93biwgKmkuZS4qLCBpdCBoYXMgYSBmaXhlZCBmcmVxdWVuY3kuIENvbW1vbiBzZWFzb25hbCBmcmVxdWVuY2llcyBpbmNsdWRlIG1vbnRobHkgKCplLmcuKiwgbW9udGhseSBzYWxlcyBkYXRhKSwgcXVhcnRlcmx5ICgqZS5nLiosIHF1YXJ0ZXJseSBmaW5hbmNpYWwgcmVzdWx0cyBvciBidWRnZXRzKSwgYW5kIGFubnVhbGx5ICgqZS5nLiosIGFubnVhbCBob2xpZGF5IHNhbGVzIHBlcmlvZHMpLiBCZWNhdXNlIHNlYXNvbmFsaXR5IGZvbGxvd3MgYSByZWd1bGFyIHBhdHRlcm4sIGl0IGlzIHByZWRpY3RhYmxlIGFuZCBjYW4gYmUgaWRlbnRpZmllZCBhbmQgbW9kZWxlZCB0byBpbXByb3ZlIGZvcmVjYXN0aW5nIGFjY3VyYWN5LCAqaS5lLiogZm9yZWNhc3RzIGNhbiBiZSBhZGp1c3RlZCB0byBhY2NvdW50IGZvciBzZWFzb25hbCB1cHMgYW5kIGRvd25zLgoKU2Vhc29uYWxpdHkgY2FuIGJlIGlkZW50aWZpZWQgdGhyb3VnaCB2aXN1YWwgaW5zcGVjdGlvbiBvZiB0aW1lIHNlcmllcyBwbG90cyBvciBieSB1c2luZyBzdGF0aXN0aWNhbCBtZXRob2RzIHN1Y2ggYXMgYXV0b2NvcnJlbGF0aW9uIHBsb3RzLiBWaXN1YWwgaW5zcGVjdGlvbiBpbnZvbHZlcyBwbG90dGluZyB0aGUgZGF0YSBhbmQgbG9va2luZyBmb3IgcmVndWxhciBwYXR0ZXJucy4gQXV0b2NvcnJlbGF0aW9uIHBsb3RzIChBQ0YgcGxvdHMpIHNob3cgdGhlIGNvcnJlbGF0aW9uIG9mIHRoZSB0aW1lIHNlcmllcyB3aXRoIGl0cyBvd24gcGFzdCB2YWx1ZXMgYW5kIGNhbiBoaWdobGlnaHQgdGhlIHByZXNlbmNlIG9mIHNlYXNvbmFsaXR5LgoKSW4gdGltZSBzZXJpZXMgZm9yZWNhc3RpbmcgbW9kZWxzLCBzZWFzb25hbGl0eSBjYW4gYmUgYWNjb21tb2RhdGVkICphZGRpdGl2ZWx5KiBvciAqbXVsdGlwbGljYXRpdmVseSouIEFuIGFkZGl0aXZlIG1vZGVsIGFkZHMgYSAic2Vhc29uYWxpdHkgYWRqdXN0bWVudCIgdG8gZWFjaCBmb3JlY2FzdCB0aGF0IGFkanVzdHMgdGhlIGZvcmVjYXN0IHZhbHVlIHVwIG9yIGRvd24gZGVwZW5kaW5nIGluIHdoaWNoIHNlYXNvbiB0aGUgZm9yZWNhc3QgdmFsdWUgZmFsbHMuIEEgbXVsdGlwbGljYXRpdmUgbW9kZWwgbXVsdGlwbGllcyB0aGUgZm9yZWNhc3QgdmFsdWUgYnkgYSAic2Vhc29uYWxpdHkgaW5kZXgiIHRvIG1ha2UgYW4gYWRqdXN0bWVudCBvZiB0aGUgZm9yZWNhc3QgZm9yIGEgc2Vhc29uLgoKU2Vhc29uYWxpdHkgKG9yIGN5Y2xpY2FsaXR5KSBhZGp1c3RtZW50IGludm9sdmVzIGFkanVzdGluZyB0aGUgZm9yZWNhc3QgdG8gYWNjb3VudCBmb3IgcGVyaW9kIHBlYWtzIGFuZCB2YWxsZXlzLCAqaS5lLiogYWNjb3VudGluZyBmb3Igc29tZSBzZWFzb25zIGJlaW5nIGFib3ZlIG5vcm1hbCBhbmQgc29tZSBiZWluZyBiZWxvdyBub3JtYWwuIFRoaXMgcHJvY2VzcyBoZWxwcyBpbiBiZXR0ZXIgdW5kZXJzdGFuZGluZyB0aGUgdW5kZXJseWluZyB0cmVuZCBhbmQgbWFraW5nIG1vcmUgYWNjdXJhdGUgcHJlZGljdGlvbnMuCgpUaGVyZSBhcmUgdHdvIGNvbW1vbiB3YXlzIHRvIGFjY291bnQgZm9yIHNlYXNvbmFsaXR5IGFuZCB0byBhZGp1c3QgZm9yZWNhc3RzIGZvciBzZWFzb25hbGl0eToKCjEuICAqKk11bHRpcGxpY2F0aXZlIEFkanVzdG1lbnQqKjogVGhlIGZvcmVjYXN0IGlzIG11bHRpcGxpZWQgYnkgYW4gYWRqdXN0bWVudCBmYWN0b3IgKHRoZSAic2Vhc29uYWxpdHkgaW5kZXgiKSBkZXBlbmRpbmcgZm9yIHdoaWNoIHNlYXNvbiB0aGUgZm9yZWNhc3QgaXMuIFRoZSBhZGp1c3RtZW50IGZhY3RvciBpcyBjYWxjdWxhdGVkIGZyb20gdGhlIGRhdGEgYnkgZmluZGluZyB0aGUgYXZlcmFnZSBkaWZmZXJlbmNlIGJldHdlZW4gdGhlIG1lYW4gYW5kIGVhY2ggc2Vhc29uLgoKMi4gICoqQWRkaXRpdmUgQWRqdXN0bWVudCoqOiBUaGlzIHJlcXVpcmVzIGJ1aWxkaW5nIGEgTGluZWFyIFRyZW5kIExpbmUgTW9kZWwgdXNpbmcgYm90aCB0aW1lIGFuZCB0aGUgc2Vhc29uIGFzIGZhY3RvcnMgaW4gdGhlIHJlZ3Jlc3Npb24uIFRoZSBmb3JlY2FzdCBpcyBhZGp1c3RlZCBieSBhZGRpbmcgKG9yIHN1YnRyYWN0aW5nKSBhIHNlYXNvbmFsaXR5IGZhY3RvciBkZXBlbmRpbmcgZm9yIHdoaWNoIHNlYXNvbiB0aGUgZm9yZWNhc3QgaXMuCgpgYGB7ciBlY2hvPUZ9ClBlcmlvZCA8LSAxOjE2CkNhbGxzIDwtIGMoMjE4LDI0NywyNDMsMjkyLDIyNSwyNTQsMjU1LDI5OSwyMzQsMjY1LDI2NCwzMjcsMjUwLDI4MywyODksMzU2KQpRdWFydGVyTnVtIDwtIHJlcCgxOjQsIHRpbWVzID0gNCkKUXVhcnRlciA8LSBOQQpZZWFyIDwtIGMoMSwxLDEsMSwyLDIsMiwyLDMsMywzLDMsNCw0LDQsNCkKCmNhbGxjZW50ZXIuZGYgPC0gZGF0YS5mcmFtZSgKICBQZXJpb2QsCiAgQ2FsbHMsCiAgUXVhcnRlck51bSwKICBRdWFydGVyLAogIFllYXIKKQoKY2FsbGNlbnRlci5kZiRRdWFydGVyIDwtIHBhc3RlMCgiUSIsY2FsbGNlbnRlci5kZiRRdWFydGVyTnVtKQpjb2xuYW1lcyhjYWxsY2VudGVyLmRmKSA8LSBjKCJQZXJpb2QiLCJDYWxscyIsIlF1YXJ0ZXJOdW0iLCJRdWFydGVyIiwiWWVhciIpCmBgYAoKTGV0J3MgZXhwbG9yZSBib3RoIG9mIHRoZXNlIHRlY2huaXF1ZXMgYnkgY29uc2lkZXJpbmcgdGhlIGV4YW1wbGUgYmVsb3cgZm9yIGEgdGltZSBzZXJpZXMgY29udGFpbmluZyBpbi1ib3VuZCBjYWxscyB0byBhIGNhbGwgY2VudGVyIGZvciBmb3VyIHllYXJzIGJ5IHF1YXJ0ZXIuIEZvciBleGFtcGxlLCBpbiBxdWFydGVyIDMgb2YgeWVhciAyLCB0aGUgY2FsbCBjZW50ZXIgcmVjZWl2ZWQgYHIgY2FsbGNlbnRlci5kZiRDYWxsc1t3aGljaChjYWxsY2VudGVyLmRmJFF1YXJ0ZXIgPT0gIlEzIiAmIGNhbGxjZW50ZXIuZGYkWWVhciA9PSAiMiIpXWAgY2FsbHMuCgpgYGB7ciBlY2hvPUZ9CmxpYnJhcnkocmVzaGFwZTIpCgpyZXNoYXBlZC5kZiA8LSBkY2FzdChjYWxsY2VudGVyLmRmLCBRdWFydGVyIH4gWWVhciwgdmFsdWUudmFyID0gIkNhbGxzIikKCmtuaXRyOjprYWJsZSAoCiAgcmVzaGFwZWQuZGYsCiAgZm9ybWF0ID0gInNpbXBsZSIsCiAgYWxpZ24gPSAiY2NjYyIsCiAgY29sLm5hbWVzID0gYygiUXVhcnRlciIsIlkxIiwiWTIiLCJZMyIsIlk0IikKKQpgYGAKCiMjIyBJZGVudGlmeSBTZWFzb25hbGl0eQoKVGhlIGZpcnN0IHN0ZXAgaXMgbm90IHBsb3QgdGltZSBzZXJpZXMgYW5kIHVzZSB0aGUgdmlzdWFsaXphdGlvbiB0byBkZXRlcm1pbmUgd2hldGhlciB0aGVpciBpcyBjeWNsaWNhbGl0eSBhbmQgd2hhdCB0aGUgc2Vhc29uIGFyZS4KCmBgYHtyIGNsYXNzLnNvdXJjZSA9ICdmb2xkLWhpZGUnfQpsaWJyYXJ5KGdncGxvdDIpCgpnZ3Bsb3QoY2FsbGNlbnRlci5kZiwgCiAgICAgICBhZXMoeCA9IFBlcmlvZCwgeSA9IENhbGxzLCAKICAgICAgICAgICBjb2xvciA9IGZhY3RvcihRdWFydGVyKSwgZ3JvdXAgPSAxKSkgKwogIGdlb21fbGluZSgpICsKICBnZW9tX3BvaW50KCkgKwogIGxhYnModGl0bGUgPSAiUXVhcnRlcmx5IENhbGwgVm9sdW1lIiwKICAgICAgIHggPSAiVGltZSBQZXJpb2QiLAogICAgICAgeSA9ICJOdW1iZXIgb2YgQ2FsbHMiLAogICAgICAgY29sb3IgPSAiUXVhcnRlciIpICsKICB0aGVtZV9taW5pbWFsKCkKYGBgCgpGcm9tIHRoZSBncmFwaCB3ZSBjYW4gc2VlIHRoYXQgZXZlcnkgUTQgKCppLmUuKiwgdGltZSBwZXJpb2QgNCwgOCwgMTIsIGFuZCAxNikgaXMgYSBwZWFrIHdoZXJlIHRoZSBudW1iZXIgb2YgY2FsbHMgc3Bpa2UsIGFuZCBRMSBvZiBlYWNoIHllYXIgKCppLmUuKiwgdGltZSBwZXJpb2QgMSwgNSwgOSwgYW5kIDEzKS4gSW4gYWRkaXRpb24sIHRoZXJlIGlzIGFuIG92ZXJhbGwgdXB3YXJkIHRyZW5kLCBzbyBhIExpbmVhciBSZWdyZXNzaW9uIFRyZW5kIE1vZGVsIHdvdWxkIGJlIGFuIGFwcHJvcHJpYXRlIG1vZGVsIHRvIHVzZTsgYW4gYWx0ZXJuYXRpdmUgbWlnaHQgYmUgYSBIb2x0J3MgTGluZWFyIFRyZW5kIE1vZGVsLgoKIyMjIE11bHRpcGxpY2F0aXZlIEFkanVzdG1lbnQKClRoZSBwcm9jZXNzIGZvciBhIG11bHRpcGxpY2F0aXZlIHNlYXNvbmFsaXR5IGFkanVzdGVkIGZvcmVjYXN0IG1vZGVsIHJlcXVpcmVzIGEgc2Vhc29uYWxpdHkgaW5kZXggKHRoZSBtdWx0aXBsaWNhdGl2ZSBmYWN0b3IpIGFuZCB0aGUgcHJvY2VzcyBmb3IgY2FsY3VsYXRpbmcgdGhpcyBmYWN0b3IgaXMgc3VtbWFyaXplZCBiZWxvdzoKCjEuICBGb3IgZWFjaCBvbmUgeWVhciBwZXJpb2QgKHRoZSBjeWNsZSksIGNhbGN1bGF0ZSB0aGUgYXZlcmFnZSBhY3Jvc3MgcXVhcnRlcnMgKHRoZSBzZWFzb25zKSB3aXRoaW4gdGhlIGN5Y2xlLgoyLiAgRm9yIGVhY2ggdGltZSBwZXJpb2QsIGRpdmlkZSB0aGUgYWN0dWFsIHNlYXNvbmFsIGRlbWFuZCBieSB0aGUgYXZlcmFnZSBzZWFzb25hbCBkZW1hbmQuCjMuICBDb21wdXRlIHRoZSBhdmVyYWdlIHNlYXNvbmFsaXR5IGluZGV4IGZvciBlYWNoIHNlYXNvbi4KNC4gIFJlbW92ZSBzZWFzb25hbGl0eSB2YXJpYW5jZSBieSBjYWxjdWxhdGluZyBhIGZvcmVjYXN0IGZvciB0aGUgZW50aXJlIG5leHQgeWVhciAoY3ljbGUpIGFuZCB0aGVuIGRpdmlkZSB0aGF0IGJ5IHRoZSBudW1iZXIgb2YgcXVhcnRlcnMgKHNlYXNvbnMpIHRvIGdldCBhbiBhdmVyYWdlLgo1LiAgTXVsdGlwbHkgdGhlIGF2ZXJhZ2UgZm9yZWNhc3QgYnkgdGhlIHNlYXNvbmFsaXR5IGluZGV4IGZvciBlYWNoIHNlYXNvbiB0byBnZXQgYSBmb3JlY2FzdCBmb3IgdGhlIG5leHQgeWVhciAoY3ljbGUpIHBlciBxdWFydGVyIChzZWFzb24pLgoKTGV0J3Mgc3RhcnQgYnkgY2FsY3VsYXRpbmcgdGhlIHNlYXNvbmFsaXR5IGluZGV4LiBUaGUgZ3JhcGhpYyBiZWxvdyBzaG93cyBob3cgdG8gY2FsY3VsYXRlIHRoZSBzZWFzb25hbGl0eSBpbmRleCBmb3IgZWFjaCBzZWFzb24uCgpgYGB7ciBjbGFzcy5zb3VyY2UgPSAnZm9sZC1oaWRlJ30KYXZnLmNhbGxzIDwtIGFnZ3JlZ2F0ZSgKICB4ID0gY2FsbGNlbnRlci5kZlssYygiWWVhciIsIkNhbGxzIildLCAKICBGVU4gPSBtZWFuLCAKICBieSA9IGxpc3QoY2FsbGNlbnRlci5kZiRZZWFyKQopCgphdmcuY2FsbHMgPC0gYXZnLmNhbGxzWywyOjNdCgpjb2xuYW1lcyhhdmcuY2FsbHMpIDwtIGMoIlllYXIiLCJBdmdOdW1DYWxscyIpCgpmb3IgKHQgaW4gMTpucm93KGNhbGxjZW50ZXIuZGYpKSB7CiAgWSA8LSBjYWxsY2VudGVyLmRmJFllYXJbdF0KICBTSSA8LSBhdmcuY2FsbHMkQXZnTnVtQ2FsbHNbWV0KICBjYWxsY2VudGVyLmRmJFNJW3RdIDwtIGNhbGxjZW50ZXIuZGYkQ2FsbHNbdF0gLyBTSQp9CgphdmcuU0kgPC0gYWdncmVnYXRlKAogIHggPSBjYWxsY2VudGVyLmRmWyxjKCJRdWFydGVyTnVtIiwiU0kiKV0sIAogIEZVTiA9IG1lYW4sIAogIGJ5ID0gbGlzdChjYWxsY2VudGVyLmRmJFF1YXJ0ZXIpCikKYGBgCgohW10oaW1hZ2VzL3NlYXNvbmFsaXR5LWluZGV4LWNhbGN1bGF0aW9uLmpwZWcpe3dpZHRoPSI3NSUifQoKTmV4dCwgd2UnbGwgYnVpbGQgYSBMaW5lYXIgUmVncmVzc2lvbiBUcmVuZCBNb2RlbDsgb2YgY291cnNlLCB3ZSdsbCBuZWVkIHRvIG1ha2UgdGhlIGRhdGEgYSB0aW1lIHNlcmllcyByYXRoZXIgdGhhbiBhIDJEIHRhYmxlLiBTbywgdGhlIHRpbWUgaXMgWWVhciAxL1F1YXJ0ZXIgMSBmb2xsb3dlZCBieSBZZWFyIDEvUXVhcnRlciAyLCBhbmQgc28gZm9ydGguCgpUaGUgZGF0YSBmcmFtZSBjcmVhdGVkIGxvb2tzIGxpa2UgdGhpcyAob25seSB0aGUgZmlyc3QgYW5kIGxhc3QgdGhyZWUgcm93cyBhcmUgc2hvd24pOgoKYGBge3IgY2xhc3Muc291cmNlID0gJ2ZvbGQtaGlkZSd9CmRvdHMuZGYgPC0gZGF0YS5mcmFtZSgKICBQZXJpb2QgPSAiLi4uIiwKICBDYWxscyA9ICIuLi4iLAogIFF1YXJ0ZXIgPSAiLi4uIiwKICBZZWFyID0gIi4uLiIKKQoKa25pdHI6OmthYmxlKAogIHJiaW5kKGhlYWQoY2FsbGNlbnRlci5kZlssYygxLDIsNCw1KV0sMyksCiAgICAgICAgcmJpbmQoZG90cy5kZiwKICAgICAgICAgICAgICB0YWlsKGNhbGxjZW50ZXIuZGZbLGMoMSwyLDQsNSldLDMpKSksCiAgZm9ybWF0ID0gInNpbXBsZSIsCiAgYWxpZ24gPSAiY2NjYyIsCiAgcm93Lm5hbWVzID0gRkFMU0UKKQpgYGAKCk5vdyB3ZSBjYW4gdHJhaW4gdGhlIGxpbmVhciByZWdyZXNzaW9uIG1vZGVsLgoKYGBge3J9CnRyZW5kLm1vZGVsLmNhbGxzIDwtIGxtKGNhbGxjZW50ZXIuZGYkQ2FsbHMgfiBjYWxsY2VudGVyLmRmJFBlcmlvZCkKCiMgZm9yZWNhc3QgcXVhcnRlcmx5IGNhbGxzIGZvciBuZXh0IGZvdXIgcXVhcnRlcnMKbiA8LSBucm93KGNhbGxjZW50ZXIuZGYpICsgMSAgICAgICAgICAgICAgIyB0aW1lIHBlcmlvZHMKZmMgPC0gYyg0KSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBmb3JlY2FzdHMKYiA8LSB0cmVuZC5tb2RlbC5jYWxscyRjb2VmZmljaWVudHNbWzFdXSAgIyBpbnRlcmNlcHQKbSA8LSB0cmVuZC5tb2RlbC5jYWxscyRjb2VmZmljaWVudHNbWzJdXSAgIyBzbG9wZQoKZm9yICh0IGluIG46KG4rMykpIHsKICBmY1t0LW4rMV0gPC0gbSp0ICsgYgp9CmBgYAoKTmV4dCwgd2UgbmVlZCB0byBhdmVyYWdlIHRoZSBmb3JlY2FzdHMgYW5kIG11bHRpcGx5IGJ5IGl0cyBzZWFzb25hbGl0eSBpbmRleC4gVGhpcyB3aWxsIGJlIGEgZm9yZWNhc3QgZm9yIHRpbWUgcGVyaW9kIDE3ICh0aGUgbmV4dCB0aW1lIHBlcmlvZCkgYW5kIHRoYXQgaXMgYSBRMSwgc28gdGhlIHNlYXNvbmFsaXR5IGluZGV4IGZvciBRMSBpcyBgciBhdmcuU0kkU0lbMV1gOgoKYGBge3J9Cm1lYW4uZmMgPC0gbWVhbihmYykKYWRqdXN0ZWQuZm9yZWNhc3QgPC0gbWVhbi5mYyAqIGF2Zy5TSSRTSVsxXQpgYGAKClRoZSBzZWFzb25hbGl0eSBhZGp1c3RlZCBmb3JlY2FzdCBmb3IgUXVhcnRlciAxIG9mIFllYXIgNSBpcyB0aGVyZWZvcmUgYHIgcm91bmQoYWRqdXN0ZWQuZm9yZWNhc3QsMClgIGNhbGxzLgoKIyMjIEFkZGl0aXZlIEFkanVzdG1lbnQKCkFuIGFsdGVybmF0aXZlIGFwcHJvYWNoIHRvIG11bHRpcGxpY2F0aXZlIHNlYXNvbmFsaXR5IGFkanVzdG1lbnRzIGlzIGFuIGFkZGl0aXZlIG1vZGVsLiBUaGlzIGFwcHJvYWNoIHJlcXVpcmVzIHRoZSB1c2Ugb2YgYSBMaW5lYXIgUmVncmVzc2lvbiBUcmVuZCBMaW5lIE1vZGVsLCB3aGlsZSBhIG11bHRpcGxpY2F0aXZlIG1vZGVsIGNhbiBiZSB1c2VkIHdpdGggYW55IGZvcmVjYXN0aW5nIG1ldGhvZC4gSW4gYW4gYWRkaXRpdmUgbW9kZWwsIHdlIHdpbGwgYWRkIHRoZSBzZWFzb24gYXMgYSBmYWN0b3IuIFNpbmNlIGl0IGlzIGEgY2F0ZWdvcmljYWwgZmFjdG9yIGl0IG11c3QgYmUgb25lLWhvdCBlbmNvZGVkLgoKT25lLWhvdCBlbmNvZGluZywgYWxzbyBzb21ldGltZXMgY2FsbGVkICJkdW1teSBlbmNvZGluZyIgYXMgaXQgaW50cm9kdWNlZCAiZHVtbXkiIG9yICJwbGFjZWhvbGRlciIgdmFyaWFibGVzLCB0cmFuc2Zvcm1zIGEgY2F0ZWdvcmljYWwgdmFyaWFibGUgaW50byBhIHNlcmllcyBvZiBiaW5hcnkgKDAgb3IgMSkgY29sdW1ucy4gRWFjaCB1bmlxdWUgY2F0ZWdvcnkgdmFsdWUgKGxldmVsKSBiZWNvbWVzIGEgbmV3IGNvbHVtbiwgYW5kIGEgMSBvciAwIGlzIHBsYWNlZCBpbiB0aGUgY29sdW1uIGRlcGVuZGluZyBvbiB3aGV0aGVyIHRoZSBjYXRlZ29yaWNhbCB2YXJpYWJsZSBtYXRjaGVzIHRoZSBjb2x1bW4gaGVhZGluZy4gSWYgdGhlIGNhdGVnb3JpY2FsIHZhcmlhYmxlIGhhcyAqbiogbGV2ZWxzICgqaS5lLiosICpuKiB1bmlxdWUgdmFsdWVzKSwgdGhlbiB3ZSBvbmx5IG5lZWQgKm4tMSogYmluYXJ5IGNvbHVtbnMuCgpGb3IgdGhlIGNhbGwgY2VudGVyIGV4YW1wbGUsIGVhY2ggcXVhcnRlciBpcyBhIHNlYXNvbiwgc28gInNlYXNvbiIgaGFzIGZvdXIgbGV2ZWxzIChRdWFydGVyIDEgdGhyb3VnaCBRdWFydGVyIDQpLCBzbyB3ZSBuZWVkIHRvIGFkZCB0aHJlZSBtb3JlIGNvbHVtbnMgdG8gdGhlIGRhdGEgZnJhbWUgbGFiZWxlZCAiUTEiLCAiUTIiLCBhbmQgIlEzIi4gVGhlIHF1YXJ0ZXJseSBzZWFzb25zIGFyZSB0aGVuIGVuY29kaW5nIGFzIGZvbGxvd3M6CgpgYGB7ciBlY2hvPUZ9CmR1bW1pZXMgPC0gZGF0YS5mcmFtZSAoCiAgUXVhcnRlciA8LSBjKCJRMSIsIlEyIiwiUTMiLCJRNCIpLAogIFExIDwtIGMoMSwwLDAsMCksCiAgUTIgPC0gYygwLDEsMCwwKSwKICBRMyA8LSBjKDAsMCwxLDApCikKCmtuaXRyOjprYWJsZSgKICB4ID0gZHVtbWllcywKICBhbGlnbiA9ICJjY2NjIiwKICBmb3JtYXQgPSAic2ltcGxlIiwKICBjb2wubmFtZXMgPSBjKCJRdWFydGVyIiwiUTEiLCJRMiIsIlEzIikKKQpgYGAKCk5vdGUgdGhhdCBRNCBpcyBlbmNvZGVkIGFzIG5vdCBRMSwgbm90IFEyLGFuZCBub3QgUTMuCgpOZXh0LCB3ZSB3aWxsIGFkZCB0aGUgbmV3IGR1bW15IGNvbHVtbnMgZm9yIHRoZSBvbmUtaG90IGVuZG9pbmcgdG8gdGhlIGBjYWxsY2VudGVyLmRmYCBkYXRhIGZyYW1lLiBUaGUgcGFja2FnZXMgKipkdW1taWVzKiogYW5kICoqY2FyZXQqKiBwcm92aWRlIGZ1bmN0aW9ucyBmb3IgZG9pbmcgc3VjaCBhcyBlbmNvZGluZywgYnV0IHdlIHdpbGwgbm90IHVzZSB0aG9zZTsgaW5zdGVhZCwgd2Ugd2lsbCBkbyB0aGUgZW5jb2Rpbmcgb3Vyc2VsdmVzIHVzaW5nIGBpZmVsc2UoKWAuCgpgYGB7cn0KY2FsbGNlbnRlci5kZiRRMSA8LSBpZmVsc2UoY2FsbGNlbnRlci5kZiRRdWFydGVyID09ICJRMSIsIDEsIDApCmNhbGxjZW50ZXIuZGYkUTIgPC0gaWZlbHNlKGNhbGxjZW50ZXIuZGYkUXVhcnRlciA9PSAiUTIiLCAxLCAwKQpjYWxsY2VudGVyLmRmJFEzIDwtIGlmZWxzZShjYWxsY2VudGVyLmRmJFF1YXJ0ZXIgPT0gIlEzIiwgMSwgMCkKYGBgCgpUbyBidWlsZCBhbiBhZGRpdGl2ZSBtb2RlbCwgd2Ugd2lsbCB0cmFpbiBhIGxpbmVhciByZWdyZXNzaW9uIHRyZW5kIG1vZGVsIHdpdGggdGhlICJkdW1teSBjb2x1bW5zIiBmb3IgdGhlIHNlYXNvbiBsZXZlbHMgYXMgYWRkaXRpb25hbCBpbmRlcGVuZGVudCB2YXJpYWJsZXMuCgpgYGB7cn0KdHJlbmQubW9kZWwuYWRqdXN0ZWQgPC0gbG0oCiAgY2FsbGNlbnRlci5kZiRDYWxscyB+IGNhbGxjZW50ZXIuZGYkUGVyaW9kICsgCiAgICBjYWxsY2VudGVyLmRmJFExICsKICAgIGNhbGxjZW50ZXIuZGYkUTIgKwogICAgY2FsbGNlbnRlci5kZiRRMwopCgpzdW1tYXJ5KHRyZW5kLm1vZGVsLmFkanVzdGVkKQpgYGAKCk5vdywgd2UgY2FuIGNhbGN1bGF0ZSBhIGZvcmVjYXN0IGZvciB0aGUgbmV4dCB0aW1lIHBlcmlvZCAodGltZSBwZXJpb2QgMTcsIHdoaWNoIGlzIFF1YXJ0ZXIgMSBvZiBZZWFyIDUpLiBTaW5jZSB0aGF0IGlzIGEgIlExIiwgd2Ugd2lsbCBwYXNzICoxKiBmb3IgdGhlIHZhcmlhYmxlIGBRMWAgaW4gdGhlIG1vZGVsIGFuZCAqMCogZm9yIHRoZSB2YXJpYWJsZXMgYFEyYCBhbmQgYFEzYC4gSW4gb3RoZXIgd29yZHMsIHdlIHdpbGwgdXNlICoxKiBmb3IgdGhlIGNvcnJlY3Qgc2Vhc29uIG9yIGFsbCAqMCogdmFsdWVzIGlmIGl0IGlzIGEgIlE0Ii4KCk5vdGljZSBob3cgdGhlIGNvZWZmaWNpZW50cyBhcmUgYWxsIG5lZ2F0aXZlIGFzIGFsbCBxdWFydGVycyBleGNlcHQgUTQgYXJlIGJlbG93IHRoZSBtZWFuLiBBbHNvLCBub3RlIHRoYXQgdGhlIGNvZWZmaWNpZW50IGZvciBRMSBpcyB0aGUgbGFyZ2VzdCBhcyBpdCBpcyB0aGUgbW9zdCBiZWxvdyB0aGUgbWVhbi4KCmBgYHtyfQpiIDwtIHRyZW5kLm1vZGVsLmFkanVzdGVkJGNvZWZmaWNpZW50c1tbMV1dICAgIyBpbnRlcmNlcHQKdCA8LSB0cmVuZC5tb2RlbC5hZGp1c3RlZCRjb2VmZmljaWVudHNbWzJdXSAgICMgdGltZSBwZXJpb2QgY29lZmZpY2llbnQKUTEgPC0gdHJlbmQubW9kZWwuYWRqdXN0ZWQkY29lZmZpY2llbnRzW1szXV0gICMgUTEgY29lZmZpY2llbnQKUTIgPC0gdHJlbmQubW9kZWwuYWRqdXN0ZWQkY29lZmZpY2llbnRzW1s0XV0gICMgUTEgY29lZmZpY2llbnQKUTMgPC0gdHJlbmQubW9kZWwuYWRqdXN0ZWQkY29lZmZpY2llbnRzW1s1XV0gICMgUTEgY29lZmZpY2llbnQKCkYuMTcgPC0gKHQgKiAxNykgKyBiICsgUTEqMSArIFEyKjAgKyBRMyowCmBgYAoKRnJvbSB0aGlzIHdlIGdldCBhIHNlYXNvbmFsaXR5IGFkanVzdGVkIGZvcmVjYXN0IGZvciBRdWFydGVyIDEgb2YgWWVhciA1IG9mIGByIHJvdW5kKEYuMTcsMClgIGNhbGxzLgoKIyMgRm9yZWNhc3QgSW50ZXJ2YWwKCkEgKipmb3JlY2FzdCBpbnRlcnZhbCoqIChvciBwcmVkaWN0aW9uIGludGVydmFsKSBwcm92aWRlcyBhIHJhbmdlIHdpdGhpbiB3aGljaCB3ZSBleHBlY3QgZnV0dXJlIG9ic2VydmF0aW9ucyB0byBmYWxsIHdpdGggYSBjZXJ0YWluIHByb2JhYmlsaXR5LiBJdCBxdWFudGlmaWVzIHRoZSB1bmNlcnRhaW50eSBhcm91bmQgdGhlIHBvaW50IGZvcmVjYXN0LCBvZmZlcmluZyBhIG1vcmUgY29tcHJlaGVuc2l2ZSB2aWV3IG9mIHBvdGVudGlhbCBmdXR1cmUgdmFsdWVzLiBUaGUgd2lkdGggb2YgdGhlIGludGVydmFsIHJlZmxlY3RzIHRoZSBtb2RlbCdzIHVuY2VydGFpbnR5OiB3aWRlciBpbnRlcnZhbHMgaW5kaWNhdGUgZ3JlYXRlciB1bmNlcnRhaW50eSBhbmQgY29uc2VxdWVudGx5IG1vcmUgcmlzayBpbiB1c2luZyB0aGUgcG9pbnQgZm9yZWNhc3QuCgpUaGUgKipQb2ludCBGb3JlY2FzdCoqIGlzIHRoZSBzaW5nbGUgYmVzdCBlc3RpbWF0ZSBvZiB0aGUgZnV0dXJlIHZhbHVlIGFuZCB3aGF0IHdlIGhhdmUgY2FsY3VsYXRlZCB0aHVzIGZhciBIb3dldmVyLCBpdCBpcyBnZW5lcmFsbHkgbXVjaCBiZXR0ZXIgdG8gcmVwb3J0IGEgcmFuZ2UgYmV0d2VlbiBhIGxvd2VyIGFuZCB1cHBlciB2YWx1ZSBhdCBhIHByb2JhYmlsaXR5IHRoYXQgdGhlIHRydWUgdmFsdWUgd2lsbCBmYWxsIHdpdGhpbiB0aGUgZm9yZWNhc3QgaW50ZXJ2YWwgKCplLmcuKiwgOTUlKS4KCkEgOTUlIGZvcmVjYXN0IGludGVydmFsIGltcGxpZXMgdGhhdCB3ZSBhcmUgOTUlIGNvbmZpZGVudCB0aGF0IHRoZSB0cnVlIGZ1dHVyZSB2YWx1ZSB3aWxsIGxpZSB3aXRoaW4gdGhpcyBpbnRlcnZhbC4gVGhpcyBpcyB0eXBpY2FsbHkgY2FsY3VsYXRlZCBhczoKCiRcdGV4dHtGb3JlY2FzdCBJbnRlcnZhbH0gPSBcdGV4dHtQb2ludCBGb3JlY2FzdH0gXHBtIFpfe1xhbHBoYS8yfSBcdGltZXMgXHRleHR7U3RhbmRhcmQgRXJyb3J9JAoKd2hlcmU6CgotICAgJFpfe1xhbHBoYS8yfSQgaXMgdGhlIGNyaXRpY2FsIHZhbHVlIGZyb20gdGhlIHN0YW5kYXJkIG5vcm1hbCBkaXN0cmlidXRpb24gZm9yIGEgOTUlIGNvbmZpZGVuY2UgbGV2ZWwsIGFwcHJveGltYXRlbHkgMS45Ni4gT3RoZXIgdmFsdWVzIGNhbiBiZSBvYnRhaW5lZCBmcm9tIHRoZSBzdGFuZGFyZCBkaXN0cmlidXRpb247IHRoZSB0YWJsZSBiZWxvdyBsaXN0cyBjb21tb24gdmFsdWVzIGZvciBjb21tb24gcHJvYmFiaWxpdGllcy4KLSAgICoqU3RhbmRhcmQgRXJyb3IqKiAoU0UpIGlzIHRoZSBzdGFuZGFyZCBkZXZpYXRpb24gb2YgdGhlIGZvcmVjYXN0IGVycm9ycywgcmVmbGVjdGluZyB0aGUgdmFyaWFiaWxpdHkgb2YgdGhlIGZvcmVjYXN0LgoKYGBge3IgZXZhbD1ULCBjbGFzcy5zb3VyY2UgPSAnZm9sZC1oaWRlJ30KIyBDYWxjdWxhdGUgei1zY29yZXMgZm9yIGRpZmZlcmVudCBjb25maWRlbmNlIGxldmVscwp6X3Njb3JlcyA8LSBkYXRhLmZyYW1lKAogIENvbmZpZGVuY2VfTGV2ZWwgPSBjKCI5OSUiLCAiOTUlIiwgIjkwJSIsICI4MCUiLCAiNzAlIiksCiAgWl9TY29yZSA9IGMoCiAgICBxbm9ybSgwLjk5NSksICAjIDk5JSBjb25maWRlbmNlIGxldmVsCiAgICBxbm9ybSgwLjk3NSksICAjIDk1JSBjb25maWRlbmNlIGxldmVsCiAgICBxbm9ybSgwLjk1KSwgICAjIDkwJSBjb25maWRlbmNlIGxldmVsCiAgICBxbm9ybSgwLjkwKSwgICAjIDgwJSBjb25maWRlbmNlIGxldmVsCiAgICBxbm9ybSgwLjg1KSAgICAjIDcwJSBjb25maWRlbmNlIGxldmVsCiAgKQopCgojIERpc3BsYXkgdGhlIGRhdGEgZnJhbWUgdXNpbmcga2FibGUKa25pdHI6OmthYmxlKHpfc2NvcmVzLCAKICAgICAgICAgICAgIGNvbC5uYW1lcyA9IGMoIkNvbmZpZGVuY2UgTGV2ZWwiLCAiWi1TY29yZSIpLCAKICAgICAgICAgICAgIGNhcHRpb24gPSAiWi1TY29yZXMgZm9yIERpZmZlcmVudCBDb25maWRlbmNlIExldmVscyIsCiAgICAgICAgICAgICBmb3JtYXQgPSAic2ltcGxlIiwKICAgICAgICAgICAgIGRpZ2l0cyA9IDMsCiAgICAgICAgICAgICBhbGlnbiA9ICJjYyIsCiAgICAgICAgICAgICBsYWJlbCA9IE5BKQpgYGAKCkZvcmVjYXN0cyBwcm9kdWNlZCBieSBhIExpbmVhciBSZWdyZXNzaW9uIFRyZW5kIE1vZGVsIGhhdmUgYSBkaWZmZXJlbnQgd2F5IG9mIGNhbGN1bGF0aW5nIHRoZSBmb3JlY2FzdCBpbnRlcnZhbCBhcyBpdCBpcyBhIHN0YXRpc3RpY2FsIG1ldGhvZC5UaGUgb3RoZXIgbWV0aG9kcyBhcmUgbm9uLXN0YXRpc3RpY2FsIGFuZCB0aGVyZWZvcmUgZG8gcHJvZHVjZSBhIHZhbHVlIG9mIHRoZSBzdGFuZGFyZCBlcnJvciBhbmQgb25lIGhhcyB0byBiZSBlc3RpbWF0ZWQuCgoqKkxUUk0gRm9yZWNhc3QgSW50ZXJ2YWwqKgoKRm9yIGFuIExSVE0gdGhlIGZvcmVjYXN0IGludGVydmFsIGlzIGNhbGN1bGF0ZWQgYXMgc2hvd24gYmVsb3cuIFRoZSBleGFtcGxlIHVzZXMgdGhlIGB3YXRlci51c2FnZWAgdGltZSBzZXJpZXMuIFRoZSBmdW5jdGlvbiBgc3VtbWFyeSh0cmVuZC5tb2RlbClgIHByb2R1Y2VzIHRoZSByZXNpZHVhbCBzdGFuZGFyZCBlcnJvciBhcyBvbmUgb2YgaXRzIGNvbXBvbmVudHM6IGBtb2RlbC5yZXN1bHRzJHNpZ21hYC4KCmBgYHtyfQojIExvYWQgbmVjZXNzYXJ5IGxpYnJhcmllcwpsaWJyYXJ5KGZvcmVjYXN0KQoKY3N2IDwtICJodHRwczovL3MzLnVzLWVhc3QtMi5hbWF6b25hd3MuY29tL2FydGlmaWNpdW0udXMvZGF0YXNldHMvd2F0ZXJ1c2FnZS5jc3YiCgojIExvYWQgdGhlIGRhdGEKd2F0ZXIudXNhZ2UgPC0gcmVhZC5jc3YoY3N2LAogICAgICAgICAgICAgICAgICAgICAgICBzdHJpbmdzQXNGYWN0b3JzID0gRikKCiMgZml0IGEgbGluZWFyIHRyZW5kIG1vZGVsIHVzaW5nIGxpbmVhciByZWdyZXNzaW9uCnRyZW5kLm1vZGVsIDwtIGxtKHdhdGVyLnVzYWdlJGRhaWx5IH4gd2F0ZXIudXNhZ2UkcGVyaW9kKQoKcHJpbnQodHJlbmQubW9kZWwpCgojIEZvcmVjYXN0IGZ1dHVyZSB2YWx1ZXMgdXNpbmcgdGhlIHRyZW5kIG1vZGVsCmIgPC0gdHJlbmQubW9kZWwkY29lZmZpY2llbnRzW1sxXV0KbSA8LSB0cmVuZC5tb2RlbCRjb2VmZmljaWVudHNbWzJdXQoKIyBmb3JlY2FzdCBmb3IgbmV4dCB0aW1lIHBlcmlvZApmb3JlY2FzdC52YWwgPC0gbSoxMyArIGIKCm1vZGVsLnJlc3VsdHMgPC0gc3VtbWFyeSh0cmVuZC5tb2RlbCkKU0UgPC0gbW9kZWwucmVzdWx0cyRzaWdtYQoKei5zY29yZS45NSA8LSBxbm9ybSgwLjk3NSkgIyB0d28tdGFpbGVkLCBzbyAwLjA1LzIKCmxvd2VyOTUgPC0gZm9yZWNhc3QudmFsIC0gKHouc2NvcmUuOTUgKiBTRSkKdXBwZXI5NSA8LSBmb3JlY2FzdC52YWwgKyAoei5zY29yZS45NSAqIFNFKQpgYGAKClNvLCBmb3IgdGhlIExSVE0gd2UgaGF2ZSBhIHBvaW50IGZvcmVjYXN0IG9mIGByIHJvdW5kKGZvcmVjYXN0LnZhbCwxKWAgd2l0aCBhIDk1JSBmb3JlY2FzdCAocHJlZGljdGlvbikgaW50ZXJ2YWwgcmFuZ2luZyBmcm9tIGByIHJvdW5kKGxvd2VyOTUsMSlgIHRvIGByIHJvdW5kKHVwcGVyOTUsMSlgLiBUaGUgcmFuZ2UgaXMgcXVpdGUgd2lkZSBpbmRpY2F0aW5nIHN1YnN0YW50aWFsIHVuY2VydGFpbnR5IGluIHRoZSBmb3JlY2FzdCBhbmQgdXNpbmcgdGhlIHBvaW50IGZvcmVjYXN0IGlzIHRodXMgcmlza3kuIElmIHNvbWVvbmUgd2VyZSB0byBidWRnZXQgZm9yIG5leHQgbW9udGgncyB3YXRlciB1c2UgYW5kIHRoZXkgYXJlICJyaXNrIGF2ZXJzZSIsIHRoZXknZCBiZSBiZXN0IHNlcnZlZCBieSB1c2luZyB0aGUgdXBwZXIgcmFuZ2UgYXMgaXQgaXMgbW9yZSBwZXNzaW1pc3RpYyBpbiB0aGlzIHNpdHVhdGlvbi4KCioqT3RoZXIgRm9yZWNhc3QgSW50ZXJ2YWxzKioKCkZvciBhbGwgb3RoZXIgbWV0aG9kcywgYmVjYXVzZSB0aGV5IGFyZSBub3Qgc3RhdGlzdGljYWwsIHdlIG5lZWQgdG8gdXNlIGEgZGlmZmVyZW50IG1ldGhvZCBzaW5jZSB3ZSBkbyBub3QgaGF2ZSBhbiBzdGFuZGFyZCBlcnJvciBmcm9tIHRoZSByZWdyZXNzaW9uLiBXZSBkbywgaG93ZXZlciwgaGF2ZSByZXNpZHVhbHMgKGVycm9ycykgYW5kIGNhbiB1c2UgdGhvc2UgdG8gZGVyaXZlIGFuIGVzdGltYXRlIG9mIHRoZSBzdGFuZGFyZCBlcnJvci4KCiRcc2lnbWFfRSA9IFxzcXJ0e1xmcmFje1xzdW1fe3Q9MX1ee259KEVfdC1cYmFye0V9KV4yfXtuLTF9fSQKCkxldCdzIGRlbW9uc3RyYXRlIGJ5IGNhbGN1bGF0aW5nIHRoZSA5NSUgZm9yZWNhc3QgaW50ZXJ2YWwgZm9yIGEgV01BIG1vZGVsLgoKYGBge3J9CiMgZGVmaW5lIHRoZSB3ZWlnaHRzIGZvciB0aGUgV01BICgzLXBlcmlvZCBleGFtcGxlKQp3IDwtIGMoMC4xLCAwLjMsIDAuNikKCiMgY2FsY3VsYXRlICJmb3JlY2FzdCIgZm9yIGVhY2ggcHJpb3IgdGltZSBwZXJpb2QKd2F0ZXIudXNhZ2UkV01BIDwtIGNhbGN1bGF0ZS53bWEod2F0ZXIudXNhZ2UkZGFpbHksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuID0gMywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHdlaWdodHMgPSB3KQoKZm9yZWNhc3QudmFsIDwtIHdhdGVyLnVzYWdlJFdNQVsxMl0KCnBlcmlvZHMgPC0gbnJvdyh3YXRlci51c2FnZSkKCiMgY2FsY3VsYXRlIHRoZSBhYnNvbHV0ZSBlcnJvcnMKZm9yICh0IGluIDM6cGVyaW9kcykgewogIHdhdGVyLnVzYWdlJEVbdF0gPC0gd2F0ZXIudXNhZ2UkZGFpbHlbdC0xXSAtIHdhdGVyLnVzYWdlJFdNQVt0XQp9CgojIGdldCB0aGUgZXJyb3JzIGZvciBhbGwgYnV0IGZpcnN0IHR3byBmb3JlY2FzdHMgYXMgdGhvc2UgYXJlIE5BCldNQS5FIDwtIHdhdGVyLnVzYWdlJEVbMzpwZXJpb2RzXQoKbWVhbi5FIDwtIG1lYW4oV01BLkUpCgpzaWdtYSA8LSBzcXJ0KHN1bSgoV01BLkUgLSBtZWFuLkUpXjIpLyhsZW5ndGgoV01BLkUpKSkKCnouc2NvcmUuOTUgPC0gcW5vcm0oMC45NzUpICMgdHdvLXRhaWxlZCwgc28gMC4wNS8yCgpsb3dlcjk1IDwtIGZvcmVjYXN0LnZhbCAtICh6LnNjb3JlLjk1ICogc2lnbWEpCnVwcGVyOTUgPC0gZm9yZWNhc3QudmFsICsgKHouc2NvcmUuOTUgKiBzaWdtYSkKYGBgCgpTbywgZm9yIHRoZSBXTUEgd2UgaGF2ZSBhIHBvaW50IGZvcmVjYXN0IG9mIGByIHJvdW5kKGZvcmVjYXN0LnZhbCwxKWAgd2l0aCBhIDk1JSBmb3JlY2FzdCAocHJlZGljdGlvbikgaW50ZXJ2YWwgcmFuZ2luZyBmcm9tIGByIHJvdW5kKGxvd2VyOTUsMSlgIHRvIGByIHJvdW5kKHVwcGVyOTUsMSlgLgoKIyMgVHJhY2tpbmcgU2lnbmFsCgpGb3JlY2FzdGluZyBtb2RlbHMgbXVzdCBiZSBjb250aW51YWxseSBldmFsdWF0ZWQgdG8gYXNzdXJlIHRoYXQgdGhleSBzdGlsbCBwcm92aWRlIGFjY3VyYXRlIGZvcmVjYXN0IGVzdGltYXRlcy4KCkEgKnRyYWNraW5nIHNpZ25hbCAoVFMpKiBpcyBhIG1lYXN1cmUgdXNlZCBpbiBmb3JlY2FzdGluZyB0byBkZXRlY3QgYmlhcyBpbiB0aGUgZm9yZWNhc3QuIEl0IGhlbHBzIGluIGlkZW50aWZ5aW5nIHdoZXRoZXIgdGhlIGZvcmVjYXN0IG1vZGVsIGNvbnNpc3RlbnRseSBvdmVyZXN0aW1hdGVzIG9yIHVuZGVyZXN0aW1hdGVzIHRoZSBhY3R1YWwgdmFsdWVzLiBJdCBpcyBkZWZpbmVkIGFzIHRoZSByYXRpbyBvZiB0aGUgQ3VtdWxhdGl2ZSBGb3JlY2FzdCBFcnJvciAoKkNGRSopIGFuZCB0aGUgTWVhbiBBYnNvbHV0ZSBFcnJvciAoKk1BRSopLiBJdCBzaG91bGQgYmUgY2FsY3VsYXRlZCBhZnRlciBlYWNoIGZvcmVjYXN0IGFuZCB0aGVuIGV2YWx1YXRlZCB0byBzZWUgaWYgdGhlIGZvcmVjYXN0IG1vZGVsIHJlcXVpcmVzIHJlLXRyYWluaW5nIG9yIGZ1cnRoZXIgdHVuaW5nLgoKJFx0ZXh0e1RTfSA9IFxmcmFje1x0ZXh0e0NGRX19e1x0ZXh0e01BRX19JAoKd2hlcmUKCiRcdGV4dHtDRkV9ID0gXHN1bV97dD0xfV57bn0oWV90IC0gRl90KSQKCmFuZAoKJFx0ZXh0e01BRX0gPSBcc3VtX3t0PTF9XntufSh8WV90IC0gRl90fCkkCgpJbnRlcnByZXRhdGlvbiBvZiB0aGUgdHJhY2tpbmcgc2lnbmFsIGdlbmVyYWxseSBjb25zaWRlciB3aGV0aGVyIGl0IGlzOgoKLSAgICoqUG9zaXRpdmUqKjogd2hpY2ggbWVhbnMgdGhhdCB0aGUgZm9yZWNhc3RzIGFyZSBnZW5lcmFsbHkgdW5kZXIgcHJlZGljdGluZywgKmkuZS4qLCAkWSA+IEYkCi0gICAqKk5lZ2F0aXZlKio6IHdoaWNoIG1lYW5zIHRoYXQgdGhlIGZvcmVjYXN0cyBhcmUgZ2VuZXJhbGx5IG92ZXIgcHJlZGljdGluZywgKmkuZS4qLCAkWSA8IEYkCi0gICAqKk5lYXIgWmVybyoqOiB3aGljaCBtZWFucyB0aGF0IHRoZSBmb3JlY2FzdCBtb2RlbCBpcyB1bmJpYXNlZAoKQSByZS10cmFpbmluZyBhbmQvb3IgcmUtdHVuaW5nIG9mIHRoZSBmb3JlY2FzdCBtb2RlbCBpcyBnZW5lcmFsbHkgYWR2aXNhYmxlIHdoZW4gJFx0ZXh0e1RTfSA+IMKxNCBcdGltZXMgTUFFJCBhcyB0aGF0IG1lYW5zIHRoYXQgdGhlIGVycm9ycyBhcmUgc3RhcnRpbmcgdG8gZmFyIGV4Y2VlZCB0aGUgbWVhbi4KCiMjIyBFeGFtcGxlIFRTIENhbGN1bGF0aW9uCgpgYGB7ciBlY2hvPUZ9CmFjdHVhbCA8LSBjKDIyMCwgMjQ1LCAyNzAsIDI2MSwgMjgzLCAzMDEpCmZvcmVjYXN0IDwtIGMoMjEyLCAyNDksIDI2MCwgMjUyLCAyNzYsIDI5MikKZXJyb3IgPC0gYWN0dWFsIC0gZm9yZWNhc3QKCnBlcmlvZHMgPC0gbGVuZ3RoKGFjdHVhbCkKCiMgQ3JlYXRlIHRoZSBkYXRhIGZyYW1lCmRmLlRTIDwtIGRhdGEuZnJhbWUgKAogIHBlcmlvZCA9IDE6cGVyaW9kcywKICBhY3R1YWwsCiAgZm9yZWNhc3QsCiAgZXJyb3IKKQoKZGYuVFMkQ0ZFIDwtIGN1bXN1bShkZi5UUyRlcnJvcikKCmRmLlRTJEFCUyA8LSBhYnMoZGYuVFMkYWN0dWFsIC0gZGYuVFMkZm9yZWNhc3QpCgpNQUUgPC0gbWVhbihkZi5UUyRBQlMpCkNGRSA8LSB0YWlsKGRmLlRTJENGRSwxKQpgYGAKCkxldCdzIHVzZSBhIHNpbXBsZSBleGFtcGxlIHRvIGRlbW9uc3RyYXRlIHRoZSBjYWxjdWxhdGlvbiBwcm9jZXNzIGZvciB0aGUgdHJhY2tpbmcgc2lnbmFsLgoKYGBge3IgZWNobz1GfQprbml0cjo6a2FibGUoeCA9IGRmLlRTLAogICAgICAgICAgICAgYWxpZ24gPSAiYyIsCiAgICAgICAgICAgICBmb3JtYXQgPSAic2ltcGxlIiwKICAgICAgICAgICAgIGNvbC5uYW1lcyA9IGMoInQiLCAiWSh0KSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICJGKHQpIiwgIkUodCkiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAiQ0ZFIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgInxFKHQpfCIpKQpgYGAKClRoZSBlcnJvcnMgYXJlIHRoZSBkaWZmZXJlbmNlIGJldHdlZW4gdGhlIGFjdHVhbCBhbmQgdGhlIGZvcmVjYXN0IHZhbHVlcy4gVGhlIENGRSBpcyBgciByb3VuZChDRkUsMClgIGFuZCB0aGUgTUFFIGlzIGByIHJvdW5kKE1BRSwxKWAsIHNvIHRoZSB0cmFja2luZyBzaWduYWwgaXMKCiRcdGV4dHtUU30gPSBcZnJhY3tcdGV4dHtDRkV9fXtcdGV4dHtNQUV9fT1cZnJhY3tcdGV4dHszOX19e1x0ZXh0ezcuODN9fT00Ljk4JAoKVGhlIHRyYWNraW5nIHNpZ25hbCBpcyBwb3NpdGl2ZSBzbyB0aGlzIG1lYW5zIHRoYXQgdGhlIG1vZGVsIHRoYXQgcHJvZHVjZWQgdGhlc2UgZm9yZWNhc3RzIGlzIGJpYXNlZCB0b3dhcmRzIHVuZGVyIHByZWRpY3RpbmcuCgojIyMgQ2FsY3VsYXRpbmcgVFMgaW4gUgoKQXMgYW4gZXhhbXBsZSwgbGV0J3MgY2FsY3VsYXRlIFRTIGZvciB0aGUgcHJldmlvdXNseSBidWlsdCBXTUEgTW9kZWwgZm9yIHRoZSBgd2F0ZXIudXNhZ2VgIGRhdGEgc2V0IGZyb20gU2VjdGlvbiBbQ2FsY3VsYXRpbmcgV01BIGluIFJdLiBUaGUgKk1BRSogd2FzIHByZXZpb3VzbHkgY2FsY3VsYXRlZCBpbiBTZWN0aW9uIFtDYWxjdWxhdGluZyBNQUUgZm9yIFdNQSBNb2RlbF0uCgpXZSBuZWVkIHR3byBtZXRyaWNzOgoKMS4gICpNQUUqLCBhbmQKMi4gICpDRkUqLgoKU2luY2Ugd2UgaGF2ZSAqTUFFKiBpbiB0aGUgdmFyaWFibGUgYE1BRS5XTUFgIGZyb20gYmVmb3JlLCB3ZSBvbmx5IG5lZWQgdG8gY2FsY3VsYXRlICpDRkUqIHdoaWNoIGlzIHRoZSBzdW0gb2YgdGhlIGZvcmVjYXN0IGVycm9ycy4KCmBgYHtyfQojIGNhbGN1bGF0ZSB0aGUgZXJyb3JzCnBlcmlvZHMgPC0gbnJvdyh3YXRlci51c2FnZSkKZm9yICh0IGluIDM6cGVyaW9kcykgewogIHdhdGVyLnVzYWdlJGFicy5lcnJbdF0gPC0gd2F0ZXIudXNhZ2UkZGFpbHlbdC0xXSAtIHdhdGVyLnVzYWdlJFdNQVt0XQp9CgojIGNhbGN1bGF0ZSBzdW0gb2YgZXJyb3JzIChDRkUpCkNGRSA8LSBzdW0od2F0ZXIudXNhZ2UkYWJzLmVyciwgbmEucm0gPSBUKQpgYGAKClRoZSBDRkUgZm9yIHRoaXMgbW9kZWwncyBmaXJzdCBmb3JlY2FzdCBpcyBgciByb3VuZChDRkUsMSlgIGFuZCBpdHMgTUFFIGlzIGByIHJvdW5kKE1BRS5XTUEsMSlgLCB3aGljaCBtYWtlcyB0aGUgdHJhY2tpbmcgc2lnbmFsIGByIHJvdW5kKENGRS9NQUUuV01BLDEpYC4gU2luY2UgdGhlIHRyYWNraW5nIHNpZ25hbCBpcyBuZWdhdGl2ZSwgaXQgbWVhbnMgdGhhdCB0aGUgbW9kZWwsIG9uIGF2ZXJhZ2UsIG92ZXIgcHJlZGljdHMuIEhvd2V2ZXIsIHRoZSB0cmFja2luZyBzaWduYWwgaXMgd2VsbCBiZWxvdyAqNCogYW5kIHRodXMgd2l0aGluIHRoZSBleHBlY3RlZCByYW5nZS4KCiMjIFN1bW1hcnk6IEZvcmVjYXN0aW5nIHsjY29uY2x9CgpGb3JlY2FzdGluZyBpcyBhIG11bHRpZmFjZXRlZCBkaXNjaXBsaW5lIHRoYXQgYmxlbmRzIHN0YXRpc3RpY2FsIG1ldGhvZHMgd2l0aCBkb21haW4gZXhwZXJ0aXNlLiBCeSB1bmRlcnN0YW5kaW5nIHRoZSB1bmRlcmx5aW5nIGNvbXBvbmVudHMgb2YgdGltZSBzZXJpZXMgZGF0YSBhbmQgc2VsZWN0aW5nIGFwcHJvcHJpYXRlIGZvcmVjYXN0aW5nIG1vZGVscywgb25lIGNhbiBtYWtlIGFjY3VyYXRlIHByZWRpY3Rpb25zIHRoYXQgYXJlIGludmFsdWFibGUgaW4gbWFueSBmaWVsZHMuIFIgcHJvdmlkZXMgYSByaWNoIGVjb3N5c3RlbSBvZiBwYWNrYWdlcyBhbmQgZnVuY3Rpb25zIHRvIGltcGxlbWVudCBhbmQgZXZhbHVhdGUgdmFyaW91cyBmb3JlY2FzdGluZyBtZXRob2RzLCBtYWtpbmcgaXQgYW4gY29tbW9uIHBsYXRmb3JtIGZvciB0aGlzIHB1cnBvc2UuCgpJbiB0aGlzIGxlc3Nvbiwgd2UgZXhwbG9yZWQgc2V2ZXJhbCBrZXkgbWV0aG9kcyBmb3IgZm9yZWNhc3RpbmcsIGluY2x1ZGluZyBXZWlnaHRlZCBNb3ZpbmcgQXZlcmFnZSAoV01BKSwgRXhwb25lbnRpYWwgU21vb3RoaW5nLCBBUklNQSwgYW5kIFJlZ3Jlc3Npb24gVHJlbmQgTGluZS4gV2Ugc2hvd2VkIGhvdyB0byBpbmNvcnBvcmF0ZSB0cmVuZCBhbmQgc2Vhc29uYWxpdHkgaW50byBmb3JlY2FzdHMgYW5kIGhvdyB0byB1c2UgdmFyaW91cyBtZXRyaWNzIHRvIGV2YWx1YXRlIGFuZCBjb21wYXJlIG1vZGVscy4gSW4gYWRkaXRpb24sIHdlIGV4cGxhaW5lZCBob3cgdG8gdHVuZSBtb2RlbHMgYW5kIGNvbWJpbmUgdGhlbSBpbnRvIGVuc2VtYmxlIG1vZGVscy4gRmluYWxseSwgd2UgZGVtb25zdHJhdGVkIHRoZSB1c2Ugb2YgYSB0cmFja2luZyBzaWduYWwgdG8gZGV0ZWN0IGZvcmVjYXN0IGJpYXMuIFRocm91Z2hvdXQgdGhlIGxlc3Nvbiwgd2UgZW1waGFzaXplZCB0aGUgaW1wb3J0YW5jZSBvZiB0aGVzZSB0ZWNobmlxdWVzIGFuZCBtZXRyaWNzIGluIGltcHJvdmluZyB0aGUgYWNjdXJhY3kgYW5kIHJlbGlhYmlsaXR5IG9mIGZvcmVjYXN0cy4KCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQoKIyMgRmlsZXMgJiBSZXNvdXJjZXMKCmBgYHtyIHppcEZpbGVzLCBlY2hvPUZBTFNFfQp6aXBOYW1lID0gc3ByaW50ZigiTGVzc29uRmlsZXMtJXMtJXMuemlwIiwgCiAgICAgICAgICAgICAgICAgcGFyYW1zJGNhdGVnb3J5LAogICAgICAgICAgICAgICAgIHBhcmFtcyRudW1iZXIpCgp0ZXh0QUxpbmsgPSBwYXN0ZTAoIkFsbCBGaWxlcyBmb3IgTGVzc29uICIsIAogICAgICAgICAgICAgICBwYXJhbXMkY2F0ZWdvcnksIi4iLHBhcmFtcyRudW1iZXIpCgojIGRvd25sb2FkRmlsZXNMaW5rKCkgaXMgaW5jbHVkZWQgZnJvbSBfaW5zZXJ0MkRCLlIKa25pdHI6OnJhd19odG1sKGRvd25sb2FkRmlsZXNMaW5rKCIuIiwgemlwTmFtZSwgdGV4dEFMaW5rKSkKYGBgCgotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KCiMjIFNlZSBBbHNvCgojIyBTbGlkZSBEZWNrCgpTbGlkZSBEZWNrIChbSFRNTF0oc2xpZGUtZGVjay9zZC0zLTMwMy5odG1sKSBcfCBbUXVhcnRvIFByZXNlbnRhdGlvbl0oc2xpZGUtZGVjay9zZC0zLTMwMy5xbWQpKQoKIyMgUmVmZXJlbmNlcwoKW0Z1bmRhbWVudGFscyBvZiBUaW1lIFNlcmllcyBEYXRhIGFuZCBBbmFseXNpc10oaHR0cHM6Ly93d3cuYXB0ZWNoLmNvbS9ibG9nL2ludHJvZHVjdGlvbi10by10aGUtZnVuZGFtZW50YWxzLW9mLXRpbWUtc2VyaWVzLWRhdGEtYW5kLWFuYWx5c2lzLykKCiMjIEVycmF0YQoKW0xldCB1cyBrbm93XShodHRwczovL2Zvcm0uam90Zm9ybS5jb20vMjEyMTg3MDcyNzg0MTU3KXt0YXJnZXQ9Il9ibGFuayJ9Lgo=