Overview

We begin by shifting our focus from statistical inference to predictive modeling. Our primary concern changes from “Is variable \(X\) associated with variable \(Y\)?” to “How accurately can we estimate future values of \(Y\) given an influx of new, unseen data \(X\)?”

By the end of this hour, you will be able to:

  1. Split data into training and testing partitions using modern tidyverse-adjacent frameworks (rsample).

  2. Construct, interpret, and evaluate a multiple linear regression model using lm().

  3. Evaluate a model’s predictive performance using quantitative metrics (RMSE, MAE, \(R^2\)).

  4. Critically assess core statistical assumptions to protect our model from overfitting and operational failure.

What is R?

Before we build complex predictive frameworks, let’s demystify the environment we are working in. If you strip away the advanced machine learning algorithms, R operates on a surprisingly simple three-tier hierarchy.

At its core, R is just a high-powered calculator

When you type an expression into the console, R evaluates it and immediately returns an output. It follows standard mathematical orders of operation.

# R evaluates basic arithmetic operations instantly
2 + 2
[1] 4
# More complex scaling calculation
(300 * 0.045) + (50 * 0.18) - 5
[1] 17.5

R is organized around Functions

To scale beyond basic arithmetic, we use Functions. Think of a function as a reusable machine: you give it an input (arguments), it executes an underlying mathematical formula, and it returns an output.

Instead of manually calculating a mean by adding elements and dividing, we invoke a named function:

# Defining a quick vector of ad spends
sample_spend <- c(100, 150, 200, 300)

# Passing the vector into a built-in statistical function
mean(sample_spend)
[1] 187.5

Functions are organized into Packages

R comes with a “base” suite of tools, but the true power of the language lies in its open-source ecosystem.

Developers wrap collections of specialized functions, datasets, and documentation into cohesive bundles called Packages.

Think of R as the smartphone operating system, and packages as the apps you download to give it new capabilities.

For example, standard R can fit a linear model, but we load the tidyverse package to get advanced data manipulation (mutate(), filter()) and elegant data visualization (ggplot2).

We’ll leverage these packages to build a predictive data pipeline.

The Scenario & Data Architecture

Imagine you are the Lead Data Scientist at a consumer goods firm. The CMO has provided an advertising budget dataset (marketing_data) outlining expenditures across three media channels: YouTube, Facebook, and Print Media (expressed in thousands of dollars), along with the resulting Sales (in thousands of units).

Our goal is to build a model that predicts sales based on a prospective advertising budget allocation.

Generating the Sandbox Data

To ensure total reproducibility, we will simulate a realistic corporate dataset modeling these non-linear corporate dynamics.

# Set seed for reproducibility
set.seed(42)

n <- 200
marketing_data <- tibble(
  youtube   = runif(n, 10, 300),
  facebook  = runif(n, 5, 50),
  newspaper = runif(n, 1, 40)
) %>% 
  mutate(
    # Base sales = 5,000 units. Synergistic/linear impacts added with random Gaussian noise
    sales = 5 + (0.045 * youtube) + (0.18 * facebook) + (0.005 * newspaper) + rnorm(n, 0, 2)
  )

# Preview the architecture
glimpse(marketing_data)
Rows: 200
Columns: 4
$ youtube   <dbl> 275.29375, 281.75187, 92.98047, 250.82981, 196.10620, 160.53783, 22…
$ facebook  <dbl> 44.830296, 28.269997, 43.336894, 24.925832, 12.104605, 24.904609, 4…
$ newspaper <dbl> 1.885300, 21.016342, 25.598320, 17.332093, 35.291372, 5.211496, 39.…
$ sales     <dbl> 25.457857, 24.393000, 17.190735, 22.330796, 15.887119, 16.617315, 2…

Exploratory Data Analysis (EDA)

Before fitting any predictive model, we must visualize our distributions and relationships. We want to identify raw trends and potential collinearity early.

# Pivot longer to generate clean, faceted ggplot visualizations
marketing_data %>%
  pivot_longer(cols = c(youtube, facebook, newspaper), 
               names_to = "channel", 
               values_to = "budget") %>%
  ggplot(aes(x = budget, y = sales)) +
  geom_point(alpha = 0.6, color = "#636363") +
  geom_smooth(method = "lm", se = TRUE, color = "#de2d26", linetype = "dashed") +
  facet_wrap(~ channel, scales = "free_x") +
  theme_minimal(base_size = 14) +
  labs(
    title = "Sales Volume vs. Advertising Spend by Channel",
    subtitle = "Initial linear trend extraction for predictive viability",
    x = "Ad Budget (Thousands of $)",
    y = "Sales (Thousands of Units)"
  )

Say Something Smart: I see strong, clean linear trends for youtube and facebook. newspaper appears highly scattered around the trendline, signaling that it might act as statistical noise in our predictive model.

Fitting the Model

Let’s start with what you know. We will use 100% of our data to build the model, and then use that exact same data to assess how “accurate” we are.

\[ \text{Sales} = \beta_0 + \beta_1(\text{YouTube}) + \beta_2(\text{Facebook}) + \beta_3(\text{Newspaper}) + \epsilon \]

# Model fitting using the entire dataset
full_model <- lm(sales ~ youtube + facebook + newspaper, data = marketing_data)

# Tidy the model for later
model_coefficients <- broom::tidy(full_model)

tidy(full_model)

Whoa that’s ugly, we can clean it up

# Tidy, format, and add custom significance stars
full_model %>%
  tidy() %>%
  mutate(
    # Round statistical estimates for readability
    estimate   = round(estimate, 3),
    std.error  = round(std.error, 3),
    statistic  = round(statistic, 2),
    
    # Create custom significance stars based on p-value thresholds
    stars = case_when(
      p.value < 0.001 ~ "***",
      p.value < 0.01  ~ "**",
      p.value < 0.05  ~ "*",
      p.value < 0.1   ~ ".",
      TRUE            ~ ""
    ),
    
    # Format p-value column cleanly (handling incredibly small values)
    p.value = if_else(p.value < 0.001, "< 0.001", as.character(round(p.value, 4))),
    
    # Combine p-value and stars into a single clean column
    `p-value` = paste(p.value, stars)
  ) %>%
  select(Term = term, Estimate = estimate, `Std. Error` = std.error, `t-statistic` = statistic, `p-value`)

gt_summary has some nice functions for displaying regression results

tbl_regression(full_model)
Characteristic Beta 95% CI p-value
youtube 0.04 0.04, 0.05 <0.001
facebook 0.20 0.18, 0.22 <0.001
newspaper 0.01 -0.01, 0.04 0.2
Abbreviation: CI = Confidence Interval

Deconstructing the Output

  • YouTube (\(\beta_1\) = 0.042): Holding all other ad spend constant, every $1,000 increase in the YouTube budget yields an estimated increase of 42 units sold.

  • Facebook (\(\beta_2\) = 0.202: Every $1,000 invested drives an estimated 202 units sold, assuming other variables remain static.

Notice that Facebook exhibits a massive marginal return compared to YouTube. In a business context, if the CMO has an unallocated budget, capital should pivot aggressively toward Facebook to maximize rapid acquisition scaling.

Evaluating Training Performance Metrics

Now, let’s calculate our Root Mean Squared Error (RMSE) and Mean Absolute Error (MAE) using this full dataset.

# 1. Generate predictions using modelr
full_predictions <- marketing_data %>%
  add_predictions(full_model, var = "pred_sales")

# 2. Create a unified yardstick metric set
marketing_metrics <- yardstick::metric_set(yardstick::rmse, yardstick::mae, yardstick::rsq)

# 3. Calculate all metrics simultaneously into a clean data frame
training_metrics_table <- full_predictions %>% 
  marketing_metrics(truth = sales, estimate = pred_sales)

# 4. Extract the raw numeric values for our callout text below
# (Using pull() safely extracts the number from the .estimate column)
train_rmse_num <- training_metrics_table %>% filter(.metric == "rmse") %>% pull(.estimate)
train_mae_num  <- training_metrics_table %>% filter(.metric == "mae")  %>% pull(.estimate)
train_r2_num   <- training_metrics_table %>% filter(.metric == "rsq")  %>% pull(.estimate)

# 5. Format the table columns nicely for the lecture layout
training_metrics_formatted <- training_metrics_table %>% 
  mutate(
    Metric = c("RMSE (Standard Deviation of Error)", 
               "MAE (Average Absolute Error)", 
               "R² (Coefficient of Determination)"),
    `Training Performance Value` = round(.estimate, 3)
  ) %>% 
  select(Metric, `Training Performance Value`)

# 6. Render the table beautifully using kable
datatable(training_metrics_formatted)

The Illusion of Success

Our model boasts an \(R^2\) of 0.855, meaning it explains 85.5% of the variance in our data. On average, our predictions only miss by 1.45 thousand units.

If you show this to the CMO, they may be thrilled. But mathematically, we have cheated. We gave the model the answers to the test before it took it. We have no objective baseline to know if this model generalizes to next quarter’s unseen marketing campaigns.

In a traditional econometrics class, you use 100% of the data to estimate our model parameters. In predictive modeling, this is a cardinal sin. It leads to overfitting—capturing the random noise of the historical dataset instead of the true underlying signal.

We must evaluate our model’s performance on unseen data. We will partition our data into an 80% Training Set and a 20% Testing Set.

The Remedy: Train vs. Test Partitioning

To protect our firm from operational failure, we must evaluate our model on unseen data. We use the rsample package to partition our original dataset into an 80% Training Set (to build the model) and a 20% Testing Set (held back completely as our proxy for the future).

# Using rsample for rigorous partitioning
set.seed(123) 
data_split <- initial_split(marketing_data, prop = 0.80)

train_data <- training(data_split)
test_data  <- testing(data_split)

# Re-fit the model ONLY using the training slice
split_model <- lm(sales ~ youtube + facebook + newspaper, data = train_data)

The Ultimate Comparison: Train vs. Test Error

Let’s look at how the model performs on the data it was allowed to see (Train) versus how it performs on the data we hid from it (Test).

# 1. Predictions and Metrics on the Training Data (Pulling just the raw number)
train_pred <- train_data %>% add_predictions(split_model, var = "pred_sales")

metrics_train_rmse <- train_pred %>% 
  yardstick::rmse(truth = sales, estimate = pred_sales) %>% 
  pull(.estimate)

metrics_train_mae  <- train_pred %>% 
  yardstick::mae(truth = sales, estimate = pred_sales) %>% 
  pull(.estimate)

metrics_train_rsq  <- train_pred %>% 
  yardstick::rsq(truth = sales, estimate = pred_sales) %>% 
  pull(.estimate)

# 2. Predictions and Metrics on the Test Data (Pulling just the raw number)
test_pred <- test_data %>% add_predictions(split_model, var = "pred_sales")

metrics_test_rmse  <- test_pred %>% 
  yardstick::rmse(truth = sales, estimate = pred_sales) %>% 
  pull(.estimate)

metrics_test_mae   <- test_pred %>% 
  yardstick::mae(truth = sales, estimate = pred_sales) %>% 
  pull(.estimate)

metrics_test_rsq   <- test_pred %>% 
  yardstick::rsq(truth = sales, estimate = pred_sales) %>% 
  pull(.estimate)

# 3. Build and format the comparison table
comparison_table <- tibble(
  Metric = c("RMSE (Standard Deviation of Error)", "MAE (Average Absolute Error)", "Rsq (Coefficient of Determination)"),
  `Training Data (Seen)` = c(metrics_train_rmse, metrics_train_mae, metrics_train_rsq),
  `Testing Data (Unseen)` = c(metrics_test_rmse, metrics_test_mae, metrics_test_rsq)
) %>% 
  mutate(across(where(is.numeric), ~ round(.x, 3)))

# 4. Render the table beautifully for the lecture slides/HTML document
datatable(comparison_table, caption = "Out-of-Sample Performance Comparison")

While this result runs counter to the standard textbook rule that “training error is always lower than test error,” seeing a slightly lower test error is a very common phenomenon in practice. It does not mean our model is broken or that our code is wrong.

Random Sampling Luck (The “Easy Test” Effect)

When we split a dataset into an 80/20 partition, rsample::initial_split() assigns observations completely at random.

Because our testing set only contains 20% of the data (40 observations out of 200), it is highly susceptible to sampling variance. By pure random chance, the 40 observations that landed in our test set happen to cluster slightly closer to the true regression line, containing fewer extreme outliers or anomalous “noise” points than the training set.

Think of it like a professor writing an exam: the training set was a comprehensive, rugged study guide with a few incredibly hard trick questions (outliers). The test set happened to randomly select 40 of the more straightforward questions. The model didn’t suddenly get smarter; it just took a slightly “easier” test.

What This Tells You About our Model

Seeing our test metrics match or beat our training metrics tells you three highly positive things about our predictive architecture:

  • Zero Overfitting: If our model had overfit, it would have memorized the training data’s noise, causing the training error to be tiny (e.g., 0.5) and the test error to explode (e.g., 4.5). The fact that they are tightly bounded (1.907 vs 1.661) proves our model captured the true underlying signal without over-indexing on sample quirks.

  • High Structural Stability: our model is incredibly stable. The parameters (\(\beta\) weights) estimated on our training data generalize beautifully to independent data.

  • The Variance is Small: The difference between an RMSE of 1.91 and 1.66 on a sample size of this scale is statistically negligible. It is well within the expected bounds of standard sampling error.


How to Turn This Into a Brilliant Lecture Takeaway

This is the ultimate setup for our take-home exercise or live demonstration. You can show our students how to prove that this is just a quirk of random sampling variance by introducing Cross-Validation or Re-sampling.

Tell our students: “Look at how the metrics shift if we change the random seed that slices the data.”

If you rerun the split with a few different seeds, watch how the relationship bounces around naturally:

# Seed 123 (current split)  : Test Error < Training Error (Lucky Test Split)
# Seed 456                  : Test Error > Training Error (Standard Textbook Split)
# Seed 789                  : Test Error ≈ Training Error (Perfect Equilibrium Split)

The test error isn’t a fixed mathematical law but a random variable with its own distribution, you elevate their understanding from basic formula-following to true predictive data scientists. our model is robust, our code is working perfectly, and here’s a real-world data talking point for today’s class

Analyzing the Results in a Business Context

Usually when lm() optimizes parameters, it minimizes errors specifically for the training sample’s unique quirks and random noise. When confronted with the Testing Data, that specific noise is gone. Only the true underlying signal remains. If our testing error is close to our training error (as it is here), you have built a robust model that can be safely used to project future sales for the corporate marketing strategy. Generally, Testing Error is higher than the Training Error. This is the fundamental reality of predictive modeling.

Diagnostic Audits: Visualizing the Errors (10 Minutes)

Let’s visualize our testing errors using ggplot2 to ensure our linear assumptions hold up out-of-sample.

ggplot(test_pred, aes(x = pred_sales, y = sales)) +
  geom_point(color = "#636363", size = 2, alpha = 0.8) +
  geom_abline(intercept = 0, slope = 1, color = "#de2d26", linetype = "dashed", size = 1) +
  theme_minimal(base_size = 14) +
  labs(
    title = "Actual Sales vs. Out-of-Sample Predictions",
    subtitle = "The dashed red line represents perfect prediction",
    x = "Predicted Sales (Unseen Data)",
    y = "Actual Sales (Unseen Data)"
  )

Summary for the C-Suite

By establishing foundational programming elements, making a structural evaluation mistake, and correcting it through out-of-sample validation, we proved our model’s practical utility. We can confidently tell executive leadership that when deploying this model out into the wild, our average forecasting error will be approximately 1.31 thousand units.

Mini-Exercise

Add a highly complex, unnecessary polynomial term to the model (e.g., I(youtube^3)).

Re-calculate the Full Training Error and the Test Error. Watch how the Training Error drops even lower, while the Test Error spikes wildly. This will showcase extreme overfitting in action!

LS0tDQp0aXRsZTogIkxpbmVhciBSZWdyZXNzaW9uIGluIGEgUHJlZGljdGlvbiBDb250ZXh0Ig0Kc3VidGl0bGU6ICdTVEEgNjU0MzogUHJlZGljdGl2ZSBNb2RlbGluZycNCm91dHB1dDoNCiAgaHRtbF9ub3RlYm9vazoNCiAgICB0b2M6IHRydWUNCiAgICB0b2NfZmxvYXQ6IHRydWUNCiAgICB0b2NfZGVwdGg6IDMNCiAgICB0aGVtZTogY2VydWxlYW4NCiAgICBoaWdobGlnaHQ6IHRhbmdvDQotLS0NCg0KYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9DQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFLCBmaWcud2lkdGggPSA4LCBmaWcuaGVpZ2h0ID0gNSkNCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShicm9vbSkNCmxpYnJhcnkobW9kZWxyKQ0KbGlicmFyeShyc2FtcGxlKQ0KbGlicmFyeShrbml0cikNCmxpYnJhcnkobW9kZWxzdW1tYXJ5KQ0KbGlicmFyeShndHN1bW1hcnkpDQpsaWJyYXJ5KERUKQ0KYGBgDQoNCiMgT3ZlcnZpZXcgDQpXZSBiZWdpbiBieSBzaGlmdGluZyBvdXIgZm9jdXMgZnJvbSAgc3RhdGlzdGljYWwgaW5mZXJlbmNlIHRvIHByZWRpY3RpdmUgbW9kZWxpbmcuIE91ciBwcmltYXJ5IGNvbmNlcm4gY2hhbmdlcyBmcm9tICJJcyB2YXJpYWJsZSAkWCQgYXNzb2NpYXRlZCB3aXRoIHZhcmlhYmxlICRZJD8iIHRvICJIb3cgYWNjdXJhdGVseSBjYW4gd2UgZXN0aW1hdGUgZnV0dXJlIHZhbHVlcyBvZiAkWSQgZ2l2ZW4gYW4gaW5mbHV4IG9mIG5ldywgdW5zZWVuIGRhdGEgJFgkPyINCg0KQnkgdGhlIGVuZCBvZiB0aGlzIGhvdXIsIHlvdSB3aWxsIGJlIGFibGUgdG86ICANCg0KMS4gU3BsaXQgZGF0YSBpbnRvIHRyYWluaW5nIGFuZCB0ZXN0aW5nIHBhcnRpdGlvbnMgdXNpbmcgbW9kZXJuIGB0aWR5dmVyc2VgLWFkamFjZW50IGZyYW1ld29ya3MgKGByc2FtcGxlYCkuICAgDQoNCjIuIENvbnN0cnVjdCwgaW50ZXJwcmV0LCBhbmQgZXZhbHVhdGUgYSBtdWx0aXBsZSBsaW5lYXIgcmVncmVzc2lvbiBtb2RlbCB1c2luZyBgbG0oKWAuICANCg0KMy4gRXZhbHVhdGUgYSBtb2RlbCdzIHByZWRpY3RpdmUgcGVyZm9ybWFuY2UgdXNpbmcgcXVhbnRpdGF0aXZlIG1ldHJpY3MgKFJNU0UsIE1BRSwgJFJeMiQpLiAgDQoNCjQuIENyaXRpY2FsbHkgYXNzZXNzIGNvcmUgc3RhdGlzdGljYWwgYXNzdW1wdGlvbnMgdG8gcHJvdGVjdCBvdXIgbW9kZWwgZnJvbSBvdmVyZml0dGluZyBhbmQgb3BlcmF0aW9uYWwgZmFpbHVyZS4gIA0KDQojIFdoYXQgaXMgUj8NCkJlZm9yZSB3ZSBidWlsZCBjb21wbGV4IHByZWRpY3RpdmUgZnJhbWV3b3JrcywgbGV04oCZcyBkZW15c3RpZnkgdGhlIGVudmlyb25tZW50IHdlIGFyZSB3b3JraW5nIGluLiBJZiB5b3Ugc3RyaXAgYXdheSB0aGUgYWR2YW5jZWQgbWFjaGluZSBsZWFybmluZyBhbGdvcml0aG1zLCBSIG9wZXJhdGVzIG9uIGEgc3VycHJpc2luZ2x5IHNpbXBsZSB0aHJlZS10aWVyIGhpZXJhcmNoeS4NCg0KIyMgQXQgaXRzIGNvcmUsIFIgaXMganVzdCBhIGhpZ2gtcG93ZXJlZCBjYWxjdWxhdG9yDQpXaGVuIHlvdSB0eXBlIGFuIGV4cHJlc3Npb24gaW50byB0aGUgY29uc29sZSwgUiBldmFsdWF0ZXMgaXQgYW5kIGltbWVkaWF0ZWx5IHJldHVybnMgYW4gb3V0cHV0LiBJdCBmb2xsb3dzIHN0YW5kYXJkIG1hdGhlbWF0aWNhbCBvcmRlcnMgb2Ygb3BlcmF0aW9uLg0KDQpgYGB7cn0NCiMgUiBldmFsdWF0ZXMgYmFzaWMgYXJpdGhtZXRpYyBvcGVyYXRpb25zIGluc3RhbnRseQ0KMiArIDINCg0KIyBNb3JlIGNvbXBsZXggc2NhbGluZyBjYWxjdWxhdGlvbg0KKDMwMCAqIDAuMDQ1KSArICg1MCAqIDAuMTgpIC0gNQ0KYGBgDQojIyBSIGlzIG9yZ2FuaXplZCBhcm91bmQgRnVuY3Rpb25zDQpUbyBzY2FsZSBiZXlvbmQgYmFzaWMgYXJpdGhtZXRpYywgd2UgdXNlIEZ1bmN0aW9ucy4gVGhpbmsgb2YgYSBmdW5jdGlvbiBhcyBhIHJldXNhYmxlIG1hY2hpbmU6IHlvdSBnaXZlIGl0IGFuIGlucHV0IChhcmd1bWVudHMpLCBpdCBleGVjdXRlcyBhbiB1bmRlcmx5aW5nIG1hdGhlbWF0aWNhbCBmb3JtdWxhLCBhbmQgaXQgcmV0dXJucyBhbiBvdXRwdXQuDQoNCkluc3RlYWQgb2YgbWFudWFsbHkgY2FsY3VsYXRpbmcgYSBtZWFuIGJ5IGFkZGluZyBlbGVtZW50cyBhbmQgZGl2aWRpbmcsIHdlIGludm9rZSBhIG5hbWVkIGZ1bmN0aW9uOg0KDQpgYGB7cn0NCiMgRGVmaW5pbmcgYSBxdWljayB2ZWN0b3Igb2YgYWQgc3BlbmRzDQpzYW1wbGVfc3BlbmQgPC0gYygxMDAsIDE1MCwgMjAwLCAzMDApDQoNCiMgUGFzc2luZyB0aGUgdmVjdG9yIGludG8gYSBidWlsdC1pbiBzdGF0aXN0aWNhbCBmdW5jdGlvbg0KbWVhbihzYW1wbGVfc3BlbmQpDQpgYGANCiMjIEZ1bmN0aW9ucyBhcmUgb3JnYW5pemVkIGludG8gUGFja2FnZXMNClIgY29tZXMgd2l0aCBhICJiYXNlIiBzdWl0ZSBvZiB0b29scywgYnV0IHRoZSB0cnVlIHBvd2VyIG9mIHRoZSBsYW5ndWFnZSBsaWVzIGluIGl0cyBvcGVuLXNvdXJjZSBlY29zeXN0ZW0uDQoNCkRldmVsb3BlcnMgd3JhcCBjb2xsZWN0aW9ucyBvZiBzcGVjaWFsaXplZCBmdW5jdGlvbnMsIGRhdGFzZXRzLCBhbmQgZG9jdW1lbnRhdGlvbiBpbnRvIGNvaGVzaXZlIGJ1bmRsZXMgY2FsbGVkIFBhY2thZ2VzLg0KDQpUaGluayBvZiBSIGFzIHRoZSBzbWFydHBob25lIG9wZXJhdGluZyBzeXN0ZW0sIGFuZCBwYWNrYWdlcyBhcyB0aGUgYXBwcyB5b3UgZG93bmxvYWQgdG8gZ2l2ZSBpdCBuZXcgY2FwYWJpbGl0aWVzLg0KDQpGb3IgZXhhbXBsZSwgc3RhbmRhcmQgUiBjYW4gZml0IGEgbGluZWFyIG1vZGVsLCBidXQgd2UgbG9hZCB0aGUgYHRpZHl2ZXJzZWAgcGFja2FnZSB0byBnZXQgYWR2YW5jZWQgZGF0YSBtYW5pcHVsYXRpb24gKGBtdXRhdGUoKWAsIGBmaWx0ZXIoKWApIGFuZCBlbGVnYW50IGRhdGEgdmlzdWFsaXphdGlvbiAoYGdncGxvdDJgKS4NCg0KV2UnbGwgbGV2ZXJhZ2UgdGhlc2UgcGFja2FnZXMgdG8gYnVpbGQgYSBwcmVkaWN0aXZlIGRhdGEgcGlwZWxpbmUuDQoNCiMgVGhlIFNjZW5hcmlvICYgRGF0YSBBcmNoaXRlY3R1cmUgDQoNCkltYWdpbmUgeW91IGFyZSB0aGUgTGVhZCBEYXRhIFNjaWVudGlzdCBhdCBhIGNvbnN1bWVyIGdvb2RzIGZpcm0uIFRoZSBDTU8gaGFzIHByb3ZpZGVkIGFuIGFkdmVydGlzaW5nIGJ1ZGdldCBkYXRhc2V0IChgbWFya2V0aW5nX2RhdGFgKSBvdXRsaW5pbmcgZXhwZW5kaXR1cmVzIGFjcm9zcyB0aHJlZSBtZWRpYSBjaGFubmVsczogX1lvdVR1YmVfLCBfRmFjZWJvb2tfLCBhbmQgX1ByaW50IE1lZGlhXyAoZXhwcmVzc2VkIGluIHRob3VzYW5kcyBvZiBkb2xsYXJzKSwgYWxvbmcgd2l0aCB0aGUgcmVzdWx0aW5nIF9TYWxlc18gKGluIHRob3VzYW5kcyBvZiB1bml0cykuDQoNCk91ciBnb2FsIGlzIHRvIGJ1aWxkIGEgbW9kZWwgdGhhdCBwcmVkaWN0cyBzYWxlcyBiYXNlZCBvbiBhIHByb3NwZWN0aXZlIGFkdmVydGlzaW5nIGJ1ZGdldCBhbGxvY2F0aW9uLg0KDQojIyBHZW5lcmF0aW5nIHRoZSBTYW5kYm94IERhdGENClRvIGVuc3VyZSB0b3RhbCByZXByb2R1Y2liaWxpdHksIHdlIHdpbGwgc2ltdWxhdGUgYSByZWFsaXN0aWMgY29ycG9yYXRlIGRhdGFzZXQgbW9kZWxpbmcgdGhlc2Ugbm9uLWxpbmVhciBjb3Jwb3JhdGUgZHluYW1pY3MuDQoNCmBgYHtyfQ0KIyBTZXQgc2VlZCBmb3IgcmVwcm9kdWNpYmlsaXR5DQpzZXQuc2VlZCg0MikNCg0KbiA8LSAyMDANCm1hcmtldGluZ19kYXRhIDwtIHRpYmJsZSgNCiAgeW91dHViZSAgID0gcnVuaWYobiwgMTAsIDMwMCksDQogIGZhY2Vib29rICA9IHJ1bmlmKG4sIDUsIDUwKSwNCiAgbmV3c3BhcGVyID0gcnVuaWYobiwgMSwgNDApDQopICU+JSANCiAgbXV0YXRlKA0KICAgICMgQmFzZSBzYWxlcyA9IDUsMDAwIHVuaXRzLiBTeW5lcmdpc3RpYy9saW5lYXIgaW1wYWN0cyBhZGRlZCB3aXRoIHJhbmRvbSBHYXVzc2lhbiBub2lzZQ0KICAgIHNhbGVzID0gNSArICgwLjA0NSAqIHlvdXR1YmUpICsgKDAuMTggKiBmYWNlYm9vaykgKyAoMC4wMDUgKiBuZXdzcGFwZXIpICsgcm5vcm0obiwgMCwgMikNCiAgKQ0KDQojIFByZXZpZXcgdGhlIGFyY2hpdGVjdHVyZQ0KZ2xpbXBzZShtYXJrZXRpbmdfZGF0YSkNCmBgYA0KIyMgRXhwbG9yYXRvcnkgRGF0YSBBbmFseXNpcyAoRURBKSAgDQoNCkJlZm9yZSBmaXR0aW5nIGFueSBwcmVkaWN0aXZlIG1vZGVsLCB3ZSBtdXN0IHZpc3VhbGl6ZSBvdXIgZGlzdHJpYnV0aW9ucyBhbmQgcmVsYXRpb25zaGlwcy4gV2Ugd2FudCB0byBpZGVudGlmeSByYXcgdHJlbmRzIGFuZCBwb3RlbnRpYWwgY29sbGluZWFyaXR5IGVhcmx5Lg0KDQpgYGB7cn0NCiMgUGl2b3QgbG9uZ2VyIHRvIGdlbmVyYXRlIGNsZWFuLCBmYWNldGVkIGdncGxvdCB2aXN1YWxpemF0aW9ucw0KbWFya2V0aW5nX2RhdGEgJT4lDQogIHBpdm90X2xvbmdlcihjb2xzID0gYyh5b3V0dWJlLCBmYWNlYm9vaywgbmV3c3BhcGVyKSwgDQogICAgICAgICAgICAgICBuYW1lc190byA9ICJjaGFubmVsIiwgDQogICAgICAgICAgICAgICB2YWx1ZXNfdG8gPSAiYnVkZ2V0IikgJT4lDQogIGdncGxvdChhZXMoeCA9IGJ1ZGdldCwgeSA9IHNhbGVzKSkgKw0KICBnZW9tX3BvaW50KGFscGhhID0gMC42LCBjb2xvciA9ICIjNjM2MzYzIikgKw0KICBnZW9tX3Ntb290aChtZXRob2QgPSAibG0iLCBzZSA9IFRSVUUsIGNvbG9yID0gIiNkZTJkMjYiLCBsaW5ldHlwZSA9ICJkYXNoZWQiKSArDQogIGZhY2V0X3dyYXAofiBjaGFubmVsLCBzY2FsZXMgPSAiZnJlZV94IikgKw0KICB0aGVtZV9taW5pbWFsKGJhc2Vfc2l6ZSA9IDE0KSArDQogIGxhYnMoDQogICAgdGl0bGUgPSAiU2FsZXMgVm9sdW1lIHZzLiBBZHZlcnRpc2luZyBTcGVuZCBieSBDaGFubmVsIiwNCiAgICBzdWJ0aXRsZSA9ICJJbml0aWFsIGxpbmVhciB0cmVuZCBleHRyYWN0aW9uIGZvciBwcmVkaWN0aXZlIHZpYWJpbGl0eSIsDQogICAgeCA9ICJBZCBCdWRnZXQgKFRob3VzYW5kcyBvZiAkKSIsDQogICAgeSA9ICJTYWxlcyAoVGhvdXNhbmRzIG9mIFVuaXRzKSINCiAgKQ0KYGBgDQoNCl9fU2F5IFNvbWV0aGluZyBTbWFydF9fOiBJIHNlZSBzdHJvbmcsIGNsZWFuIGxpbmVhciB0cmVuZHMgZm9yIHlvdXR1YmUgYW5kIGZhY2Vib29rLiBuZXdzcGFwZXIgYXBwZWFycyBoaWdobHkgc2NhdHRlcmVkIGFyb3VuZCB0aGUgdHJlbmRsaW5lLCBzaWduYWxpbmcgdGhhdCBpdCBtaWdodCBhY3QgYXMgc3RhdGlzdGljYWwgbm9pc2UgaW4gb3VyIHByZWRpY3RpdmUgbW9kZWwuIA0KDQojIEZpdHRpbmcgdGhlIE1vZGVsDQoNCkxldCdzIHN0YXJ0IHdpdGggd2hhdCB5b3Uga25vdy4gV2Ugd2lsbCB1c2UgMTAwJSBvZiBvdXIgZGF0YSB0byBidWlsZCB0aGUgbW9kZWwsIGFuZCB0aGVuIHVzZSB0aGF0IGV4YWN0IHNhbWUgZGF0YSB0byBhc3Nlc3MgaG93ICJhY2N1cmF0ZSIgd2UgYXJlLg0KDQokJCBcdGV4dHtTYWxlc30gPSBcYmV0YV8wICsgXGJldGFfMShcdGV4dHtZb3VUdWJlfSkgKyBcYmV0YV8yKFx0ZXh0e0ZhY2Vib29rfSkgKyBcYmV0YV8zKFx0ZXh0e05ld3NwYXBlcn0pICsgXGVwc2lsb24gJCQNCmBgYHtyfQ0KIyBNb2RlbCBmaXR0aW5nIHVzaW5nIHRoZSBlbnRpcmUgZGF0YXNldA0KZnVsbF9tb2RlbCA8LSBsbShzYWxlcyB+IHlvdXR1YmUgKyBmYWNlYm9vayArIG5ld3NwYXBlciwgZGF0YSA9IG1hcmtldGluZ19kYXRhKQ0KDQojIFRpZHkgdGhlIG1vZGVsIGZvciBsYXRlcg0KbW9kZWxfY29lZmZpY2llbnRzIDwtIGJyb29tOjp0aWR5KGZ1bGxfbW9kZWwpDQoNCnRpZHkoZnVsbF9tb2RlbCkNCmBgYA0KV2hvYSB0aGF0J3MgdWdseSwgd2UgY2FuIGNsZWFuIGl0IHVwDQoNCmBgYHtyfQ0KIyBUaWR5LCBmb3JtYXQsIGFuZCBhZGQgY3VzdG9tIHNpZ25pZmljYW5jZSBzdGFycw0KZnVsbF9tb2RlbCAlPiUNCiAgdGlkeSgpICU+JQ0KICBtdXRhdGUoDQogICAgIyBSb3VuZCBzdGF0aXN0aWNhbCBlc3RpbWF0ZXMgZm9yIHJlYWRhYmlsaXR5DQogICAgZXN0aW1hdGUgICA9IHJvdW5kKGVzdGltYXRlLCAzKSwNCiAgICBzdGQuZXJyb3IgID0gcm91bmQoc3RkLmVycm9yLCAzKSwNCiAgICBzdGF0aXN0aWMgID0gcm91bmQoc3RhdGlzdGljLCAyKSwNCiAgICANCiAgICAjIENyZWF0ZSBjdXN0b20gc2lnbmlmaWNhbmNlIHN0YXJzIGJhc2VkIG9uIHAtdmFsdWUgdGhyZXNob2xkcw0KICAgIHN0YXJzID0gY2FzZV93aGVuKA0KICAgICAgcC52YWx1ZSA8IDAuMDAxIH4gIioqKiIsDQogICAgICBwLnZhbHVlIDwgMC4wMSAgfiAiKioiLA0KICAgICAgcC52YWx1ZSA8IDAuMDUgIH4gIioiLA0KICAgICAgcC52YWx1ZSA8IDAuMSAgIH4gIi4iLA0KICAgICAgVFJVRSAgICAgICAgICAgIH4gIiINCiAgICApLA0KICAgIA0KICAgICMgRm9ybWF0IHAtdmFsdWUgY29sdW1uIGNsZWFubHkgKGhhbmRsaW5nIGluY3JlZGlibHkgc21hbGwgdmFsdWVzKQ0KICAgIHAudmFsdWUgPSBpZl9lbHNlKHAudmFsdWUgPCAwLjAwMSwgIjwgMC4wMDEiLCBhcy5jaGFyYWN0ZXIocm91bmQocC52YWx1ZSwgNCkpKSwNCiAgICANCiAgICAjIENvbWJpbmUgcC12YWx1ZSBhbmQgc3RhcnMgaW50byBhIHNpbmdsZSBjbGVhbiBjb2x1bW4NCiAgICBgcC12YWx1ZWAgPSBwYXN0ZShwLnZhbHVlLCBzdGFycykNCiAgKSAlPiUNCiAgc2VsZWN0KFRlcm0gPSB0ZXJtLCBFc3RpbWF0ZSA9IGVzdGltYXRlLCBgU3RkLiBFcnJvcmAgPSBzdGQuZXJyb3IsIGB0LXN0YXRpc3RpY2AgPSBzdGF0aXN0aWMsIGBwLXZhbHVlYCkNCmBgYA0KDQpgZ3Rfc3VtbWFyeWAgaGFzIHNvbWUgbmljZSBmdW5jdGlvbnMgZm9yIGRpc3BsYXlpbmcgcmVncmVzc2lvbiByZXN1bHRzDQoNCmBgYHtyfQ0KdGJsX3JlZ3Jlc3Npb24oZnVsbF9tb2RlbCkNCmBgYA0KDQojIyBEZWNvbnN0cnVjdGluZyB0aGUgT3V0cHV0DQoNCiAtICoqWW91VHViZSAoJFxiZXRhXzEkID0gMC4wNDIpOioqIEhvbGRpbmcgYWxsIG90aGVyIGFkIHNwZW5kIGNvbnN0YW50LCBldmVyeSAkMSwwMDAgaW5jcmVhc2UgaW4gdGhlIFlvdVR1YmUgYnVkZ2V0IHlpZWxkcyBhbiBlc3RpbWF0ZWQgaW5jcmVhc2Ugb2YgNDIgdW5pdHMgc29sZC4NCg0KIC0gKipGYWNlYm9vayAoJFxiZXRhXzIkID0gMC4yMDI6KiogRXZlcnkgJDEsMDAwIGludmVzdGVkIGRyaXZlcyBhbiBlc3RpbWF0ZWQgMjAyIHVuaXRzIHNvbGQsIGFzc3VtaW5nIG90aGVyIHZhcmlhYmxlcyByZW1haW4gc3RhdGljLg0KDQpOb3RpY2UgdGhhdCBGYWNlYm9vayBleGhpYml0cyBhIG1hc3NpdmUgbWFyZ2luYWwgcmV0dXJuIGNvbXBhcmVkIHRvIFlvdVR1YmUuIEluIGEgYnVzaW5lc3MgY29udGV4dCwgaWYgdGhlIENNTyBoYXMgYW4gdW5hbGxvY2F0ZWQgYnVkZ2V0LCBjYXBpdGFsIHNob3VsZCBwaXZvdCBhZ2dyZXNzaXZlbHkgdG93YXJkIEZhY2Vib29rIHRvIG1heGltaXplIHJhcGlkIGFjcXVpc2l0aW9uIHNjYWxpbmcuDQogDQojIyBFdmFsdWF0aW5nIFRyYWluaW5nIFBlcmZvcm1hbmNlIE1ldHJpY3MNCg0KTm93LCBsZXQncyBjYWxjdWxhdGUgb3VyIFJvb3QgTWVhbiBTcXVhcmVkIEVycm9yIChSTVNFKSBhbmQgTWVhbiBBYnNvbHV0ZSBFcnJvciAoTUFFKSB1c2luZyB0aGlzIGZ1bGwgZGF0YXNldC4NCg0KYGBge3IgZnVsbF9tb2RlbF9tZXRyaWNzfQ0KIyAxLiBHZW5lcmF0ZSBwcmVkaWN0aW9ucyB1c2luZyBtb2RlbHINCmZ1bGxfcHJlZGljdGlvbnMgPC0gbWFya2V0aW5nX2RhdGEgJT4lDQogIGFkZF9wcmVkaWN0aW9ucyhmdWxsX21vZGVsLCB2YXIgPSAicHJlZF9zYWxlcyIpDQoNCiMgMi4gQ3JlYXRlIGEgdW5pZmllZCB5YXJkc3RpY2sgbWV0cmljIHNldA0KbWFya2V0aW5nX21ldHJpY3MgPC0geWFyZHN0aWNrOjptZXRyaWNfc2V0KHlhcmRzdGljazo6cm1zZSwgeWFyZHN0aWNrOjptYWUsIHlhcmRzdGljazo6cnNxKQ0KDQojIDMuIENhbGN1bGF0ZSBhbGwgbWV0cmljcyBzaW11bHRhbmVvdXNseSBpbnRvIGEgY2xlYW4gZGF0YSBmcmFtZQ0KdHJhaW5pbmdfbWV0cmljc190YWJsZSA8LSBmdWxsX3ByZWRpY3Rpb25zICU+JSANCiAgbWFya2V0aW5nX21ldHJpY3ModHJ1dGggPSBzYWxlcywgZXN0aW1hdGUgPSBwcmVkX3NhbGVzKQ0KDQojIDQuIEV4dHJhY3QgdGhlIHJhdyBudW1lcmljIHZhbHVlcyBmb3Igb3VyIGNhbGxvdXQgdGV4dCBiZWxvdw0KIyAoVXNpbmcgcHVsbCgpIHNhZmVseSBleHRyYWN0cyB0aGUgbnVtYmVyIGZyb20gdGhlIC5lc3RpbWF0ZSBjb2x1bW4pDQp0cmFpbl9ybXNlX251bSA8LSB0cmFpbmluZ19tZXRyaWNzX3RhYmxlICU+JSBmaWx0ZXIoLm1ldHJpYyA9PSAicm1zZSIpICU+JSBwdWxsKC5lc3RpbWF0ZSkNCnRyYWluX21hZV9udW0gIDwtIHRyYWluaW5nX21ldHJpY3NfdGFibGUgJT4lIGZpbHRlcigubWV0cmljID09ICJtYWUiKSAgJT4lIHB1bGwoLmVzdGltYXRlKQ0KdHJhaW5fcjJfbnVtICAgPC0gdHJhaW5pbmdfbWV0cmljc190YWJsZSAlPiUgZmlsdGVyKC5tZXRyaWMgPT0gInJzcSIpICAlPiUgcHVsbCguZXN0aW1hdGUpDQoNCiMgNS4gRm9ybWF0IHRoZSB0YWJsZSBjb2x1bW5zIG5pY2VseSBmb3IgdGhlIGxlY3R1cmUgbGF5b3V0DQp0cmFpbmluZ19tZXRyaWNzX2Zvcm1hdHRlZCA8LSB0cmFpbmluZ19tZXRyaWNzX3RhYmxlICU+JSANCiAgbXV0YXRlKA0KICAgIE1ldHJpYyA9IGMoIlJNU0UgKFN0YW5kYXJkIERldmlhdGlvbiBvZiBFcnJvcikiLCANCiAgICAgICAgICAgICAgICJNQUUgKEF2ZXJhZ2UgQWJzb2x1dGUgRXJyb3IpIiwgDQogICAgICAgICAgICAgICAiUsKyIChDb2VmZmljaWVudCBvZiBEZXRlcm1pbmF0aW9uKSIpLA0KICAgIGBUcmFpbmluZyBQZXJmb3JtYW5jZSBWYWx1ZWAgPSByb3VuZCguZXN0aW1hdGUsIDMpDQogICkgJT4lIA0KICBzZWxlY3QoTWV0cmljLCBgVHJhaW5pbmcgUGVyZm9ybWFuY2UgVmFsdWVgKQ0KDQojIDYuIFJlbmRlciB0aGUgdGFibGUgYmVhdXRpZnVsbHkgdXNpbmcga2FibGUNCmRhdGF0YWJsZSh0cmFpbmluZ19tZXRyaWNzX2Zvcm1hdHRlZCkNCmBgYA0KDQoNCg0KIyMjIFRoZSBJbGx1c2lvbiBvZiBTdWNjZXNzDQoNCk91ciBtb2RlbCBib2FzdHMgYW4gJFJeMiQgb2YgMC44NTUsIG1lYW5pbmcgaXQgZXhwbGFpbnMgODUuNSUgb2YgdGhlIHZhcmlhbmNlIGluIG91ciBkYXRhLiBPbiBhdmVyYWdlLCBvdXIgcHJlZGljdGlvbnMgb25seSBtaXNzIGJ5IDEuNDUgdGhvdXNhbmQgdW5pdHMuDQoNCklmIHlvdSBzaG93IHRoaXMgdG8gdGhlIENNTywgdGhleSBtYXkgYmUgdGhyaWxsZWQuIEJ1dCBtYXRoZW1hdGljYWxseSwgd2UgaGF2ZSBjaGVhdGVkLiBXZSBnYXZlIHRoZSBtb2RlbCB0aGUgYW5zd2VycyB0byB0aGUgdGVzdCBiZWZvcmUgaXQgdG9vayBpdC4gV2UgaGF2ZSBubyBvYmplY3RpdmUgYmFzZWxpbmUgdG8ga25vdyBpZiB0aGlzIG1vZGVsIGdlbmVyYWxpemVzIHRvIG5leHQgcXVhcnRlcidzIHVuc2VlbiBtYXJrZXRpbmcgY2FtcGFpZ25zLg0KDQpJbiBhIHRyYWRpdGlvbmFsIGVjb25vbWV0cmljcyBjbGFzcywgeW91IHVzZSAxMDAlIG9mIHRoZSBkYXRhIHRvIGVzdGltYXRlIG91ciBtb2RlbCBwYXJhbWV0ZXJzLiBJbiBwcmVkaWN0aXZlIG1vZGVsaW5nLCB0aGlzIGlzIGEgY2FyZGluYWwgc2luLiBJdCBsZWFkcyB0byBvdmVyZml0dGluZ+KAlGNhcHR1cmluZyB0aGUgcmFuZG9tIG5vaXNlIG9mIHRoZSBoaXN0b3JpY2FsIGRhdGFzZXQgaW5zdGVhZCBvZiB0aGUgdHJ1ZSB1bmRlcmx5aW5nIHNpZ25hbC4NCg0KV2UgbXVzdCBldmFsdWF0ZSBvdXIgbW9kZWwncyBwZXJmb3JtYW5jZSBvbiB1bnNlZW4gZGF0YS4gV2Ugd2lsbCBwYXJ0aXRpb24gb3VyIGRhdGEgaW50byBhbiA4MCUgVHJhaW5pbmcgU2V0IGFuZCBhIDIwJSBUZXN0aW5nIFNldC4NCg0KIyBUaGUgUmVtZWR5OiBUcmFpbiB2cy4gVGVzdCBQYXJ0aXRpb25pbmcgDQpUbyBwcm90ZWN0IG91ciBmaXJtIGZyb20gb3BlcmF0aW9uYWwgZmFpbHVyZSwgd2UgbXVzdCBldmFsdWF0ZSBvdXIgbW9kZWwgb24gdW5zZWVuIGRhdGEuIFdlIHVzZSB0aGUgYHJzYW1wbGVgIHBhY2thZ2UgdG8gcGFydGl0aW9uIG91ciBvcmlnaW5hbCBkYXRhc2V0IGludG8gYW4gODAlIFRyYWluaW5nIFNldCAodG8gYnVpbGQgdGhlIG1vZGVsKSBhbmQgYSAyMCUgVGVzdGluZyBTZXQgKGhlbGQgYmFjayBjb21wbGV0ZWx5IGFzIG91ciBwcm94eSBmb3IgdGhlIGZ1dHVyZSkuDQoNCmBgYHtyfQ0KIyBVc2luZyByc2FtcGxlIGZvciByaWdvcm91cyBwYXJ0aXRpb25pbmcNCnNldC5zZWVkKDEyMykgDQpkYXRhX3NwbGl0IDwtIGluaXRpYWxfc3BsaXQobWFya2V0aW5nX2RhdGEsIHByb3AgPSAwLjgwKQ0KDQp0cmFpbl9kYXRhIDwtIHRyYWluaW5nKGRhdGFfc3BsaXQpDQp0ZXN0X2RhdGEgIDwtIHRlc3RpbmcoZGF0YV9zcGxpdCkNCg0KIyBSZS1maXQgdGhlIG1vZGVsIE9OTFkgdXNpbmcgdGhlIHRyYWluaW5nIHNsaWNlDQpzcGxpdF9tb2RlbCA8LSBsbShzYWxlcyB+IHlvdXR1YmUgKyBmYWNlYm9vayArIG5ld3NwYXBlciwgZGF0YSA9IHRyYWluX2RhdGEpDQpgYGANCg0KIyMgVGhlIFVsdGltYXRlIENvbXBhcmlzb246IFRyYWluIHZzLiBUZXN0IEVycm9yDQoNCkxldCdzIGxvb2sgYXQgaG93IHRoZSBtb2RlbCBwZXJmb3JtcyBvbiB0aGUgZGF0YSBpdCB3YXMgYWxsb3dlZCB0byBzZWUgKFRyYWluKSB2ZXJzdXMgaG93IGl0IHBlcmZvcm1zIG9uIHRoZSBkYXRhIHdlIGhpZCBmcm9tIGl0IChUZXN0KS4NCg0KYGBge3IgY2FsY3VsYXRlX3NwbGl0X21ldHJpY3MsIHJlc3VsdHM9J2FzaXMnfQ0KIyAxLiBQcmVkaWN0aW9ucyBhbmQgTWV0cmljcyBvbiB0aGUgVHJhaW5pbmcgRGF0YSAoUHVsbGluZyBqdXN0IHRoZSByYXcgbnVtYmVyKQ0KdHJhaW5fcHJlZCA8LSB0cmFpbl9kYXRhICU+JSBhZGRfcHJlZGljdGlvbnMoc3BsaXRfbW9kZWwsIHZhciA9ICJwcmVkX3NhbGVzIikNCg0KbWV0cmljc190cmFpbl9ybXNlIDwtIHRyYWluX3ByZWQgJT4lIA0KICB5YXJkc3RpY2s6OnJtc2UodHJ1dGggPSBzYWxlcywgZXN0aW1hdGUgPSBwcmVkX3NhbGVzKSAlPiUgDQogIHB1bGwoLmVzdGltYXRlKQ0KDQptZXRyaWNzX3RyYWluX21hZSAgPC0gdHJhaW5fcHJlZCAlPiUgDQogIHlhcmRzdGljazo6bWFlKHRydXRoID0gc2FsZXMsIGVzdGltYXRlID0gcHJlZF9zYWxlcykgJT4lIA0KICBwdWxsKC5lc3RpbWF0ZSkNCg0KbWV0cmljc190cmFpbl9yc3EgIDwtIHRyYWluX3ByZWQgJT4lIA0KICB5YXJkc3RpY2s6OnJzcSh0cnV0aCA9IHNhbGVzLCBlc3RpbWF0ZSA9IHByZWRfc2FsZXMpICU+JSANCiAgcHVsbCguZXN0aW1hdGUpDQoNCiMgMi4gUHJlZGljdGlvbnMgYW5kIE1ldHJpY3Mgb24gdGhlIFRlc3QgRGF0YSAoUHVsbGluZyBqdXN0IHRoZSByYXcgbnVtYmVyKQ0KdGVzdF9wcmVkIDwtIHRlc3RfZGF0YSAlPiUgYWRkX3ByZWRpY3Rpb25zKHNwbGl0X21vZGVsLCB2YXIgPSAicHJlZF9zYWxlcyIpDQoNCm1ldHJpY3NfdGVzdF9ybXNlICA8LSB0ZXN0X3ByZWQgJT4lIA0KICB5YXJkc3RpY2s6OnJtc2UodHJ1dGggPSBzYWxlcywgZXN0aW1hdGUgPSBwcmVkX3NhbGVzKSAlPiUgDQogIHB1bGwoLmVzdGltYXRlKQ0KDQptZXRyaWNzX3Rlc3RfbWFlICAgPC0gdGVzdF9wcmVkICU+JSANCiAgeWFyZHN0aWNrOjptYWUodHJ1dGggPSBzYWxlcywgZXN0aW1hdGUgPSBwcmVkX3NhbGVzKSAlPiUgDQogIHB1bGwoLmVzdGltYXRlKQ0KDQptZXRyaWNzX3Rlc3RfcnNxICAgPC0gdGVzdF9wcmVkICU+JSANCiAgeWFyZHN0aWNrOjpyc3EodHJ1dGggPSBzYWxlcywgZXN0aW1hdGUgPSBwcmVkX3NhbGVzKSAlPiUgDQogIHB1bGwoLmVzdGltYXRlKQ0KDQojIDMuIEJ1aWxkIGFuZCBmb3JtYXQgdGhlIGNvbXBhcmlzb24gdGFibGUNCmNvbXBhcmlzb25fdGFibGUgPC0gdGliYmxlKA0KICBNZXRyaWMgPSBjKCJSTVNFIChTdGFuZGFyZCBEZXZpYXRpb24gb2YgRXJyb3IpIiwgIk1BRSAoQXZlcmFnZSBBYnNvbHV0ZSBFcnJvcikiLCAiUnNxIChDb2VmZmljaWVudCBvZiBEZXRlcm1pbmF0aW9uKSIpLA0KICBgVHJhaW5pbmcgRGF0YSAoU2VlbilgID0gYyhtZXRyaWNzX3RyYWluX3Jtc2UsIG1ldHJpY3NfdHJhaW5fbWFlLCBtZXRyaWNzX3RyYWluX3JzcSksDQogIGBUZXN0aW5nIERhdGEgKFVuc2VlbilgID0gYyhtZXRyaWNzX3Rlc3Rfcm1zZSwgbWV0cmljc190ZXN0X21hZSwgbWV0cmljc190ZXN0X3JzcSkNCikgJT4lIA0KICBtdXRhdGUoYWNyb3NzKHdoZXJlKGlzLm51bWVyaWMpLCB+IHJvdW5kKC54LCAzKSkpDQoNCiMgNC4gUmVuZGVyIHRoZSB0YWJsZSBiZWF1dGlmdWxseSBmb3IgdGhlIGxlY3R1cmUgc2xpZGVzL0hUTUwgZG9jdW1lbnQNCmRhdGF0YWJsZShjb21wYXJpc29uX3RhYmxlLCBjYXB0aW9uID0gIk91dC1vZi1TYW1wbGUgUGVyZm9ybWFuY2UgQ29tcGFyaXNvbiIpDQpgYGANCg0KDQpXaGlsZSB0aGlzIHJlc3VsdCBydW5zIGNvdW50ZXIgdG8gdGhlIHN0YW5kYXJkIHRleHRib29rIHJ1bGUgdGhhdCAqInRyYWluaW5nIGVycm9yIGlzIGFsd2F5cyBsb3dlciB0aGFuIHRlc3QgZXJyb3IsIiogc2VlaW5nIGEgc2xpZ2h0bHkgbG93ZXIgdGVzdCBlcnJvciBpcyBhIHZlcnkgY29tbW9uIHBoZW5vbWVub24gaW4gcHJhY3RpY2UuIEl0IGRvZXMgbm90IG1lYW4gb3VyIG1vZGVsIGlzIGJyb2tlbiBvciB0aGF0IG91ciBjb2RlIGlzIHdyb25nLg0KDQojIyMgUmFuZG9tIFNhbXBsaW5nIEx1Y2sgKFRoZSAiRWFzeSBUZXN0IiBFZmZlY3QpDQoNCldoZW4gd2Ugc3BsaXQgYSBkYXRhc2V0IGludG8gYW4gODAvMjAgcGFydGl0aW9uLCBgcnNhbXBsZTo6aW5pdGlhbF9zcGxpdCgpYCBhc3NpZ25zIG9ic2VydmF0aW9ucyBjb21wbGV0ZWx5IGF0IHJhbmRvbS4NCg0KQmVjYXVzZSBvdXIgdGVzdGluZyBzZXQgb25seSBjb250YWlucyAyMCUgb2YgdGhlIGRhdGEgKDQwIG9ic2VydmF0aW9ucyBvdXQgb2YgMjAwKSwgaXQgaXMgaGlnaGx5IHN1c2NlcHRpYmxlIHRvICoqc2FtcGxpbmcgdmFyaWFuY2UqKi4gQnkgcHVyZSByYW5kb20gY2hhbmNlLCB0aGUgNDAgb2JzZXJ2YXRpb25zIHRoYXQgbGFuZGVkIGluIG91ciB0ZXN0IHNldCBoYXBwZW4gdG8gY2x1c3RlciBzbGlnaHRseSBjbG9zZXIgdG8gdGhlIHRydWUgcmVncmVzc2lvbiBsaW5lLCBjb250YWluaW5nIGZld2VyIGV4dHJlbWUgb3V0bGllcnMgb3IgYW5vbWFsb3VzICJub2lzZSIgcG9pbnRzIHRoYW4gdGhlIHRyYWluaW5nIHNldC4NCg0KVGhpbmsgb2YgaXQgbGlrZSBhIHByb2Zlc3NvciB3cml0aW5nIGFuIGV4YW06IHRoZSB0cmFpbmluZyBzZXQgd2FzIGEgY29tcHJlaGVuc2l2ZSwgcnVnZ2VkIHN0dWR5IGd1aWRlIHdpdGggYSBmZXcgaW5jcmVkaWJseSBoYXJkIHRyaWNrIHF1ZXN0aW9ucyAob3V0bGllcnMpLiBUaGUgdGVzdCBzZXQgaGFwcGVuZWQgdG8gcmFuZG9tbHkgc2VsZWN0IDQwIG9mIHRoZSBtb3JlIHN0cmFpZ2h0Zm9yd2FyZCBxdWVzdGlvbnMuIFRoZSBtb2RlbCBkaWRuJ3Qgc3VkZGVubHkgZ2V0IHNtYXJ0ZXI7IGl0IGp1c3QgdG9vayBhIHNsaWdodGx5ICJlYXNpZXIiIHRlc3QuDQoNCiMjIyBXaGF0IFRoaXMgVGVsbHMgWW91IEFib3V0IG91ciBNb2RlbA0KDQpTZWVpbmcgb3VyIHRlc3QgbWV0cmljcyBtYXRjaCBvciBiZWF0IG91ciB0cmFpbmluZyBtZXRyaWNzIHRlbGxzIHlvdSB0aHJlZSBoaWdobHkgcG9zaXRpdmUgdGhpbmdzIGFib3V0IG91ciBwcmVkaWN0aXZlIGFyY2hpdGVjdHVyZToNCg0KKiAqKlplcm8gT3ZlcmZpdHRpbmc6KiogSWYgb3VyIG1vZGVsIGhhZCBvdmVyZml0LCBpdCB3b3VsZCBoYXZlIG1lbW9yaXplZCB0aGUgdHJhaW5pbmcgZGF0YSdzIG5vaXNlLCBjYXVzaW5nIHRoZSB0cmFpbmluZyBlcnJvciB0byBiZSB0aW55IChlLmcuLCAwLjUpIGFuZCB0aGUgdGVzdCBlcnJvciB0byBleHBsb2RlIChlLmcuLCA0LjUpLiBUaGUgZmFjdCB0aGF0IHRoZXkgYXJlIHRpZ2h0bHkgYm91bmRlZCAoMS45MDcgdnMgMS42NjEpIHByb3ZlcyBvdXIgbW9kZWwgY2FwdHVyZWQgdGhlICp0cnVlIHVuZGVybHlpbmcgc2lnbmFsKiB3aXRob3V0IG92ZXItaW5kZXhpbmcgb24gc2FtcGxlIHF1aXJrcy4NCg0KKiAqKkhpZ2ggU3RydWN0dXJhbCBTdGFiaWxpdHk6Kiogb3VyIG1vZGVsIGlzIGluY3JlZGlibHkgc3RhYmxlLiBUaGUgcGFyYW1ldGVycyAoJFxiZXRhJCB3ZWlnaHRzKSBlc3RpbWF0ZWQgb24gb3VyIHRyYWluaW5nIGRhdGEgZ2VuZXJhbGl6ZSBiZWF1dGlmdWxseSB0byBpbmRlcGVuZGVudCBkYXRhLg0KDQoqICoqVGhlIFZhcmlhbmNlIGlzIFNtYWxsOioqIFRoZSBkaWZmZXJlbmNlIGJldHdlZW4gYW4gUk1TRSBvZiAxLjkxIGFuZCAxLjY2IG9uIGEgc2FtcGxlIHNpemUgb2YgdGhpcyBzY2FsZSBpcyBzdGF0aXN0aWNhbGx5IG5lZ2xpZ2libGUuIEl0IGlzIHdlbGwgd2l0aGluIHRoZSBleHBlY3RlZCBib3VuZHMgb2Ygc3RhbmRhcmQgc2FtcGxpbmcgZXJyb3IuDQoNCi0tLQ0KDQojIyMgSG93IHRvIFR1cm4gVGhpcyBJbnRvIGEgQnJpbGxpYW50IExlY3R1cmUgVGFrZWF3YXkNCg0KVGhpcyBpcyB0aGUgdWx0aW1hdGUgc2V0dXAgZm9yIG91ciB0YWtlLWhvbWUgZXhlcmNpc2Ugb3IgbGl2ZSBkZW1vbnN0cmF0aW9uLiBZb3UgY2FuIHNob3cgb3VyIHN0dWRlbnRzIGhvdyB0byBwcm92ZSB0aGF0IHRoaXMgaXMganVzdCBhIHF1aXJrIG9mIHJhbmRvbSBzYW1wbGluZyB2YXJpYW5jZSBieSBpbnRyb2R1Y2luZyAqKkNyb3NzLVZhbGlkYXRpb24qKiBvciAqKlJlLXNhbXBsaW5nKiouDQoNClRlbGwgb3VyIHN0dWRlbnRzOiAqIkxvb2sgYXQgaG93IHRoZSBtZXRyaWNzIHNoaWZ0IGlmIHdlIGNoYW5nZSB0aGUgcmFuZG9tIHNlZWQgdGhhdCBzbGljZXMgdGhlIGRhdGEuIioNCg0KSWYgeW91IHJlcnVuIHRoZSBzcGxpdCB3aXRoIGEgZmV3IGRpZmZlcmVudCBzZWVkcywgd2F0Y2ggaG93IHRoZSByZWxhdGlvbnNoaXAgYm91bmNlcyBhcm91bmQgbmF0dXJhbGx5Og0KDQpgYGByDQojIFNlZWQgMTIzIChjdXJyZW50IHNwbGl0KSAgOiBUZXN0IEVycm9yIDwgVHJhaW5pbmcgRXJyb3IgKEx1Y2t5IFRlc3QgU3BsaXQpDQojIFNlZWQgNDU2ICAgICAgICAgICAgICAgICAgOiBUZXN0IEVycm9yID4gVHJhaW5pbmcgRXJyb3IgKFN0YW5kYXJkIFRleHRib29rIFNwbGl0KQ0KIyBTZWVkIDc4OSAgICAgICAgICAgICAgICAgIDogVGVzdCBFcnJvciDiiYggVHJhaW5pbmcgRXJyb3IgKFBlcmZlY3QgRXF1aWxpYnJpdW0gU3BsaXQpDQpgYGANCg0KVGhlIHRlc3QgZXJyb3IgaXNuJ3QgYSBmaXhlZCBtYXRoZW1hdGljYWwgbGF3IGJ1dCBhIHJhbmRvbSB2YXJpYWJsZSB3aXRoIGl0cyBvd24gZGlzdHJpYnV0aW9uLCB5b3UgZWxldmF0ZSB0aGVpciB1bmRlcnN0YW5kaW5nIGZyb20gYmFzaWMgZm9ybXVsYS1mb2xsb3dpbmcgdG8gdHJ1ZSBwcmVkaWN0aXZlIGRhdGEgc2NpZW50aXN0cy4gb3VyIG1vZGVsIGlzIHJvYnVzdCwgb3VyIGNvZGUgaXMgd29ya2luZyBwZXJmZWN0bHksIGFuZCBoZXJlJ3MgYSByZWFsLXdvcmxkIGRhdGEgdGFsa2luZyBwb2ludCBmb3IgdG9kYXkncyBjbGFzcw0KDQojIEFuYWx5emluZyB0aGUgUmVzdWx0cyBpbiBhIEJ1c2luZXNzIENvbnRleHQNCg0KVXN1YWxseSB3aGVuIGBsbSgpYCBvcHRpbWl6ZXMgcGFyYW1ldGVycywgaXQgbWluaW1pemVzIGVycm9ycyBzcGVjaWZpY2FsbHkgZm9yIHRoZSB0cmFpbmluZyBzYW1wbGUncyB1bmlxdWUgcXVpcmtzIGFuZCByYW5kb20gbm9pc2UuIFdoZW4gY29uZnJvbnRlZCB3aXRoIHRoZSBUZXN0aW5nIERhdGEsIHRoYXQgc3BlY2lmaWMgbm9pc2UgaXMgZ29uZS4gT25seSB0aGUgdHJ1ZSB1bmRlcmx5aW5nIHNpZ25hbCByZW1haW5zLiBJZiBvdXIgdGVzdGluZyBlcnJvciBpcyBjbG9zZSB0byBvdXIgdHJhaW5pbmcgZXJyb3IgKGFzIGl0IGlzIGhlcmUpLCB5b3UgaGF2ZSBidWlsdCBhIHJvYnVzdCBtb2RlbCB0aGF0IGNhbiBiZSBzYWZlbHkgdXNlZCB0byBwcm9qZWN0IGZ1dHVyZSBzYWxlcyBmb3IgdGhlIGNvcnBvcmF0ZSBtYXJrZXRpbmcgc3RyYXRlZ3kuIEdlbmVyYWxseSwgVGVzdGluZyBFcnJvciBpcyBoaWdoZXIgdGhhbiB0aGUgVHJhaW5pbmcgRXJyb3IuIFRoaXMgaXMgdGhlIGZ1bmRhbWVudGFsIHJlYWxpdHkgb2YgcHJlZGljdGl2ZSBtb2RlbGluZy4NCg0KIyBEaWFnbm9zdGljIEF1ZGl0czogVmlzdWFsaXppbmcgdGhlIEVycm9ycyAoMTAgTWludXRlcykNCg0KTGV0J3MgdmlzdWFsaXplIG91ciB0ZXN0aW5nIGVycm9ycyB1c2luZyBnZ3Bsb3QyIHRvIGVuc3VyZSBvdXIgbGluZWFyIGFzc3VtcHRpb25zIGhvbGQgdXAgb3V0LW9mLXNhbXBsZS4NCg0KYGBge3J9DQpnZ3Bsb3QodGVzdF9wcmVkLCBhZXMoeCA9IHByZWRfc2FsZXMsIHkgPSBzYWxlcykpICsNCiAgZ2VvbV9wb2ludChjb2xvciA9ICIjNjM2MzYzIiwgc2l6ZSA9IDIsIGFscGhhID0gMC44KSArDQogIGdlb21fYWJsaW5lKGludGVyY2VwdCA9IDAsIHNsb3BlID0gMSwgY29sb3IgPSAiI2RlMmQyNiIsIGxpbmV0eXBlID0gImRhc2hlZCIsIHNpemUgPSAxKSArDQogIHRoZW1lX21pbmltYWwoYmFzZV9zaXplID0gMTQpICsNCiAgbGFicygNCiAgICB0aXRsZSA9ICJBY3R1YWwgU2FsZXMgdnMuIE91dC1vZi1TYW1wbGUgUHJlZGljdGlvbnMiLA0KICAgIHN1YnRpdGxlID0gIlRoZSBkYXNoZWQgcmVkIGxpbmUgcmVwcmVzZW50cyBwZXJmZWN0IHByZWRpY3Rpb24iLA0KICAgIHggPSAiUHJlZGljdGVkIFNhbGVzIChVbnNlZW4gRGF0YSkiLA0KICAgIHkgPSAiQWN0dWFsIFNhbGVzIChVbnNlZW4gRGF0YSkiDQogICkNCmBgYA0KDQojIFN1bW1hcnkgZm9yIHRoZSBDLVN1aXRlDQoNCkJ5IGVzdGFibGlzaGluZyBmb3VuZGF0aW9uYWwgcHJvZ3JhbW1pbmcgZWxlbWVudHMsIG1ha2luZyBhIHN0cnVjdHVyYWwgZXZhbHVhdGlvbiBtaXN0YWtlLCBhbmQgY29ycmVjdGluZyBpdCB0aHJvdWdoIG91dC1vZi1zYW1wbGUgdmFsaWRhdGlvbiwgd2UgcHJvdmVkIG91ciBtb2RlbCdzIHByYWN0aWNhbCB1dGlsaXR5LiBXZSBjYW4gY29uZmlkZW50bHkgdGVsbCBleGVjdXRpdmUgbGVhZGVyc2hpcCB0aGF0IHdoZW4gZGVwbG95aW5nIHRoaXMgbW9kZWwgb3V0IGludG8gdGhlIHdpbGQsIG91ciBhdmVyYWdlIGZvcmVjYXN0aW5nIGVycm9yIHdpbGwgYmUgYXBwcm94aW1hdGVseSAxLjMxIHRob3VzYW5kIHVuaXRzLg0KDQojIyBNaW5pLUV4ZXJjaXNlDQoNCkFkZCBhIGhpZ2hseSBjb21wbGV4LCB1bm5lY2Vzc2FyeSBwb2x5bm9taWFsIHRlcm0gdG8gdGhlIG1vZGVsIChlLmcuLCBgSSh5b3V0dWJlXjMpYCkuDQoNClJlLWNhbGN1bGF0ZSB0aGUgRnVsbCBUcmFpbmluZyBFcnJvciBhbmQgdGhlIFRlc3QgRXJyb3IuIFdhdGNoIGhvdyB0aGUgVHJhaW5pbmcgRXJyb3IgZHJvcHMgZXZlbiBsb3dlciwgd2hpbGUgdGhlIFRlc3QgRXJyb3Igc3Bpa2VzIHdpbGRseS4gVGhpcyB3aWxsIHNob3djYXNlIGV4dHJlbWUgb3ZlcmZpdHRpbmcgaW4gYWN0aW9uIQ==