1. PREPARE

In Unit 2, we pick up where we left off and learn to apply some basic machine learning techniques to help us understand how a predictive model might be developed and tested as part of an early warning system to identify students at risk of failing. Specifically, we will make a very crude first attempt at developing a testing a logistic regression and a random first model that can (we hope!) accurately predict whether a student is likely to pass or fail and online course. Unlike Unit 1, where we were interested in identifying factors from data collected throughout the course that might help explain why students earned the grade they did, we are instead interested identifying students who may be at risk before it is too late to intervene. Therefore we will only use information that is available at the start of the course.

a. Load Libraries

tidymodels 📦

The tidymodels package is a “meta-package” for modeling and statistical analysis that share the underlying design philosophy, grammar, and data structures of the tidyverse. It includes a core set of packages that are loaded on startup and contains tools for:

  • data splitting and pre-processing;
  • model selection, tuning, and evaluation;
  • feature selection and variable importance estimation;
  • as well as other functionality.

Your Turn

In addition to the {tidymodels} package, we’ll also be using the {tidyverse}, {skimr} and {here} packages we learned about in Unit 1. Use the code chunk below to load these three packages:

library(tidymodels)
## Registered S3 method overwritten by 'tune':
##   method                   from   
##   required_pkgs.model_spec parsnip
## ── Attaching packages ────────────────────────────────────── tidymodels 0.1.4 ──
## ✓ broom        0.7.9      ✓ recipes      0.1.17
## ✓ dials        0.0.10     ✓ rsample      0.1.0 
## ✓ dplyr        1.0.7      ✓ tibble       3.1.5 
## ✓ ggplot2      3.3.5      ✓ tidyr        1.1.4 
## ✓ infer        1.0.0      ✓ tune         0.1.6 
## ✓ modeldata    0.1.1      ✓ workflows    0.2.3 
## ✓ parsnip      0.1.7      ✓ workflowsets 0.1.0 
## ✓ purrr        0.3.4      ✓ yardstick    0.0.8
## ── Conflicts ───────────────────────────────────────── tidymodels_conflicts() ──
## x purrr::discard() masks scales::discard()
## x dplyr::filter()  masks stats::filter()
## x dplyr::lag()     masks stats::lag()
## x recipes::step()  masks stats::step()
## • Learn how to get started at https://www.tidymodels.org/start/
library(tidyverse)
## ── Attaching packages ─────────────────────────────────────── tidyverse 1.3.1 ──
## ✓ readr   2.0.2     ✓ forcats 0.5.1
## ✓ stringr 1.4.0
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## x readr::col_factor() masks scales::col_factor()
## x purrr::discard()    masks scales::discard()
## x dplyr::filter()     masks stats::filter()
## x stringr::fixed()    masks recipes::fixed()
## x dplyr::lag()        masks stats::lag()
## x readr::spec()       masks yardstick::spec()
library(skimr)
library(here)
## here() starts at /cloud/project

b. Import & Inspect Data

For this case study, we will again be working again with data from the online courses introduced in Unit 1. This data is located in the data folder and named data-to-explore.csv. Fortunately, our data has already been largely wrangled. This will save us quite a bit of time, which we’ll need to help wrap our heads around some supervised machine learning basics.

Your Turn

Use the code chunk below to read this data into your R environment and assign the name data_to_model. Note, you may choose to use the {here} package or you may specify the file path directly:

data_to_model <- read_csv(here("data", "data-to-explore.csv"))
## Rows: 943 Columns: 34
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr   (8): student_id, subject, semester, section, gender, enrollment_reason...
## dbl  (23): total_points_possible, total_points_earned, proportion_earned, ti...
## dttm  (3): date_x, date_y, date
## 
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.

As noted in Chapter 14 of Data Science in Education Using R, it’s good practice to inspect your data and make sure it looks the way you expect it to.

Use the code chunk below to take a look our data, then answer the questions that follow:

glimpse(data_to_model)
## Rows: 943
## Columns: 34
## $ student_id            <chr> "43146", "44638", "47448", "47979", "48797", "51…
## $ subject               <chr> "FrScA", "OcnA", "FrScA", "OcnA", "PhysA", "FrSc…
## $ semester              <chr> "S216", "S116", "S216", "S216", "S116", "S216", …
## $ section               <chr> "02", "01", "01", "01", "01", "03", "01", "01", …
## $ total_points_possible <dbl> 1217, 1676, 1232, 1833, 2225, 1222, 1775, 2225, …
## $ total_points_earned   <dbl> 1150.00, 1384.23, 1116.00, 1492.73, 1994.75, 70.…
## $ proportion_earned     <dbl> 0.94494659, 0.82591289, 0.90584416, 0.81436443, …
## $ gender                <chr> "F", "M", "F", "M", "M", "M", "F", "F", "F", "F"…
## $ enrollment_reason     <chr> "Course Unavailable at Local School", "Course Un…
## $ enrollment_status     <chr> "Approved/Enrolled", "Approved/Enrolled", "Appro…
## $ time_spent            <dbl> 1555.1667, 1382.7001, 860.4335, 1598.6166, 1481.…
## $ time_spent_hours      <dbl> 25.91944500, 23.04500167, 14.34055833, 26.643610…
## $ course_id             <chr> "FrScA-S216-02", "OcnA-S116-01", "FrScA-S216-01"…
## $ int                   <dbl> 4.2, 4.0, 4.2, 4.0, 3.8, 3.8, 3.6, 4.2, 3.8, NA,…
## $ val                   <dbl> 3.666667, 3.000000, 3.000000, 3.666667, 3.666667…
## $ percomp               <dbl> 4.0, 3.0, 3.0, 2.5, 3.5, 3.5, 3.0, 3.0, 3.0, NA,…
## $ tv                    <dbl> 3.857143, 3.571429, 3.714286, 3.857143, 3.714286…
## $ q1                    <dbl> 4, 4, 5, 4, 4, 4, 4, 4, 5, NA, 5, 4, 3, 4, 4, 5,…
## $ q2                    <dbl> 4, 2, 3, 3, 4, 4, 4, 4, 2, NA, 4, 5, 1, 3, 2, 5,…
## $ q3                    <dbl> 4, 2, 3, 2, 3, 3, 4, 3, 3, NA, 4, 4, 3, 3, 2, 4,…
## $ q4                    <dbl> 5, 4, 4, 4, 4, 4, 2, 4, 4, NA, 5, 4, 4, 4, 5, 5,…
## $ q5                    <dbl> 4, 4, 4, 4, 4, 3, 4, 4, 4, NA, 5, 5, 4, 4, 5, 5,…
## $ q6                    <dbl> 4, 4, 3, 4, 4, 3, 4, 4, 2, NA, 3, 5, 3, 3, 3, 5,…
## $ q7                    <dbl> 4, 4, 3, 3, 4, 4, 2, 3, 3, NA, 4, 4, 4, 4, 4, 4,…
## $ q8                    <dbl> 4, 4, 4, 4, 4, 4, 4, 5, 4, NA, 5, 5, 3, 4, 5, 5,…
## $ q9                    <dbl> 3, 3, 3, 4, 3, 4, 4, 3, 2, NA, 3, 4, 2, 3, 1, 5,…
## $ q10                   <dbl> 4, 4, 4, 4, 3, 4, 4, 4, 2, NA, 4, 5, 3, 4, 3, 5,…
## $ date_x                <dttm> 2016-02-02 18:44:00, 2015-09-09 13:41:00, 2016-…
## $ post_int              <dbl> NA, NA, NA, NA, NA, NA, NA, 3.50, 3.75, NA, NA, …
## $ post_uv               <dbl> NA, NA, NA, NA, NA, NA, NA, 3.666667, 2.000000, …
## $ post_tv               <dbl> NA, NA, NA, NA, NA, NA, NA, 3.571429, 3.000000, …
## $ post_percomp          <dbl> NA, NA, NA, NA, NA, NA, NA, 3.5, 3.0, NA, NA, 4.…
## $ date_y                <dttm> NA, NA, NA, NA, NA, NA, NA, 2016-01-02 00:41:00…
## $ date                  <dttm> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
  1. How many observations and variables are in our dataset?

    • 34
  2. Since our goal is develop a model that predicts whether students are likely to be at risk of failing, what variable might we use as our outcome variable for our model?

    • proportion_earned
  3. Based on your prior work with this data, and the descriptions of the data in Chapter 7 of DSIEUR, what are some variables collected prior to the course that you think might make good predictors of at risk students? Why?

    • subject - some classes are potentially more difficult

    • gender - gender makeup of class could influence study habits

    • enrollment_reason - personal motivation may influence study effort

    • int - interest level may be a predictor of intrinsic motivation

2. WRANGLE

In general, data wrangling involves some combination of cleaning, reshaping, transforming, and merging data (Wickham & Grolemund, 2017). The importance of data wrangling is difficult to overstate, as it involves the initial steps of going from raw data to a dataset that can be explored and modeled (Krumm et al, 2018). In Part 2, we focus on the the following wrangling processes to:

  1. Isolate Predictors. In this section, we will narrow down our data set to all potential predictors of interest.

  2. Preprocess Data. We create a binary variable outcome we are interested in predicting, and remove quite a bit of cruft.

  3. Split Data. We split our data into a training and test set that will be used to develop a predictive model.

  4. Create a Recipe. Finally, we will test our a couple different “recipes” for our predictive model and learn how to deal with nominal data that we would like to use as predictors.

a. Isolate Predictors

As you’ve probably noticed, there is a lot of great information in this dataset - but we won’t need all of it. Estrellado et al. note in DSIEUR that for statistical reasons and as a good general practice, it’s better to select a few variables you are interested in and go from there:

At a certain point, adding more variables will appear to make your analysis more accurate, but will in fact obscure the truth from you.

Unlike Unit 1, we are not interested interested so much explaining why students earned the grade they did after the fact, or which variables might be significant predictors of their final grade. Instead we are interested in a more immediate and practical application of modeling, that is developing a model using machine learning techniques that can accurately predict whether a student is likely to pass or fail and online course using only information available prior to course.

Our goal, therefore, is to identify students who may be at risk of failing using information that is available to teachers before it is too late to intervene.

Your Turn

Complete the code chunk below to “select” (hint, hint) proportioned_earned for our outcome variable, as well as any variables from our data_to_model data frame that teachers would have access to at the start of the course (e.g. survey data, course info, student id and demographics, etc.). Save as a new data frame named course_data and inspect your data.

course_data <- data_to_model %>%
  select(student_id, proportion_earned, subject, semester, 
         section, enrollment_reason, gender, int, val, percomp, 
         q1:q10)

b. Preprocess Data

Prior to developing a predictive model from our data, we have a little bit of data processing to do, after which we will divide, or “split,” our data set into two separate data frames, one for training our predictive model and one for testing our model to see how well it performs.

Create Outcome Variable

First, however, we need an outcome variable that let’s us know if they have either passed or failed.

Let’s combine the hopefully familiar mutate() function with the if_else() function also from the {dplyr} package to create a new variable called at_risk which indicates “no” for students whose proportioned_earned is greater than or equal too 66.7% (NC State’s cutoff for a D-) and “yes” if it is not.

course_data <- course_data %>% 
  mutate(at_risk = if_else(proportion_earned >= .667, "no", "yes")) 

Drop Data

As you may have noticed from your inspection of the data earlier, some students do not have the course grade data at all. Let’s use the drop_na() function also from {dplyr} to remove observations with no grade data and the select function again to remove the proportion_earned variable entirely so we’re not tempted to cheat and use this for what would be a VERY good predictor of being “at risk”:

course_data <- course_data %>% 
  drop_na(proportion_earned) %>%
  select(-proportion_earned)

Let’s also use the na.omit() function introduced previously to delete cases listwise (that is, an entire row is deleted if any single value is missing), since most models does not accept cases with missing data:

course_data <- course_data %>%
  na.omit()

Convert Characters to Factors

While inspecting the data, you may have noticed that many of our predictors of interest are character variables. For creating models, it is better to have qualitative variable like gender encoded as factors instead of character strings. Factors store data as categorical variables, each with its own levels. Because categorical variables are used in statistical models differently than continuous variables, storing data as factors ensures that the modeling functions will treat them correctly.

To do so, we introduce a variation of the mutate() function that will look for any variable that is a character and recode as a factor:

course_data <- course_data %>%
  mutate_if(is.character, as.factor)

course_data
## # A tibble: 533 × 20
##    student_id subject semester section enrollment_reason      gender   int   val
##    <fct>      <fct>   <fct>    <fct>   <fct>                  <fct>  <dbl> <dbl>
##  1 43146      FrScA   S216     02      Course Unavailable at… F        4.2  3.67
##  2 44638      OcnA    S116     01      Course Unavailable at… M        4    3   
##  3 47448      FrScA   S216     01      Other                  F        4.2  3   
##  4 47979      OcnA    S216     01      Course Unavailable at… M        4    3.67
##  5 48797      PhysA   S116     01      Learning Preference o… M        3.8  3.67
##  6 51943      FrScA   S216     03      Learning Preference o… M        3.8  3.67
##  7 52326      AnPhA   S216     01      Other                  F        3.6  4   
##  8 52446      PhysA   S116     01      Learning Preference o… F        4.2  3.67
##  9 53447      FrScA   S116     01      Course Unavailable at… F        3.8  2   
## 10 53475      FrScA   S216     01      Course Unavailable at… F        4.8  3.33
## # … with 523 more rows, and 12 more variables: percomp <dbl>, q1 <dbl>,
## #   q2 <dbl>, q3 <dbl>, q4 <dbl>, q5 <dbl>, q6 <dbl>, q7 <dbl>, q8 <dbl>,
## #   q9 <dbl>, q10 <dbl>, at_risk <fct>

Your Turn

To reduce all the redundancy caused by demonstrating each step separately above, complete the following code below by using the pipe operator to repeat all of these steps starting from isolating predictors to dropping empty values from proportion_earned and removing all incomplete cases.

course_data <- data_to_model %>%
  select(student_id, proportion_earned, subject, semester, 
         section, enrollment_reason, gender, int, val, percomp, 
         q1:q10) %>%
  mutate(at_risk = if_else(proportion_earned >= .667, "no", "yes")) %>% 
  drop_na(proportion_earned) %>%
  select(-proportion_earned) %>% 
  na.omit() %>%
  mutate_if(is.character, as.factor)

Now inspect your data again and make sure it looks like expected:

course_data
## # A tibble: 533 × 20
##    student_id subject semester section enrollment_reason      gender   int   val
##    <fct>      <fct>   <fct>    <fct>   <fct>                  <fct>  <dbl> <dbl>
##  1 43146      FrScA   S216     02      Course Unavailable at… F        4.2  3.67
##  2 44638      OcnA    S116     01      Course Unavailable at… M        4    3   
##  3 47448      FrScA   S216     01      Other                  F        4.2  3   
##  4 47979      OcnA    S216     01      Course Unavailable at… M        4    3.67
##  5 48797      PhysA   S116     01      Learning Preference o… M        3.8  3.67
##  6 51943      FrScA   S216     03      Learning Preference o… M        3.8  3.67
##  7 52326      AnPhA   S216     01      Other                  F        3.6  4   
##  8 52446      PhysA   S116     01      Learning Preference o… F        4.2  3.67
##  9 53447      FrScA   S116     01      Course Unavailable at… F        3.8  2   
## 10 53475      FrScA   S216     01      Course Unavailable at… F        4.8  3.33
## # … with 523 more rows, and 12 more variables: percomp <dbl>, q1 <dbl>,
## #   q2 <dbl>, q3 <dbl>, q4 <dbl>, q5 <dbl>, q6 <dbl>, q7 <dbl>, q8 <dbl>,
## #   q9 <dbl>, q10 <dbl>, at_risk <fct>

Hint: You should see a total of 532 observations and 20 variables including 1 outcome variable named at_risk, 1 student identifier named student_id, and 18 potential predictors.

Count proportions

One last step before splitting our data is to take a quick look at the proportion of students in our final data set that were identified as at-risk.

To do so, first let’s take a count of the number of students labeled “yes” or “no” of being at_risk, and then create a new variable called proportion that takes the number n of each and divides by the total number:

course_data %>%
  count(at_risk) %>%
  mutate(proportion = n/sum(n))
## # A tibble: 2 × 3
##   at_risk     n proportion
##   <fct>   <int>      <dbl>
## 1 no        439      0.824
## 2 yes        94      0.176

As you can see, roughly 17% of students in our data set were identified as “yes” for being at risk.

c. Split Data

It is common when beginning a modeling project to separate the data set into two partitions:

  • The training set is used to estimate develop and compare models, feature engineering techniques, tune models, etc.

  • The test set is held in reserve until the end of the project, at which point there should only be one or two models under serious consideration. It is used as an unbiased source for measuring final model performance.

There are different ways to create these partitions of the data and there is no uniform guideline for determining how much data should be set aside for testing. The proportion of data can be driven by many factors, including the size of the original pool of samples and the total number of predictors. 

After you decide how much to set aside, the most common approach for actually partitioning your data is to use a random sample. For our purposes, we’ll use random sampling to select 25% for the test set and use the remainder for the training set, which are the defaults for the {rsample} package.

Additionally, since random sampling uses random numbers, it is important to set the random number seed. This ensures that the random numbers can be reproduced at a later time (if needed).

The function initial_split() function from the {rsample} package takes the original data and saves the information on how to make the partitions.

Run the following code to set the random number seed and make our initial data split:

set.seed(586)

course_split <- initial_split(course_data, 
                              strata = at_risk)

Note that we used the strata = argument, which conducts a stratified split. This ensures that, despite the imbalance we noticed in our at_risk variable, our training and test data sets will keep roughly the same proportions of at-risk students as in the original data. 

Your Turn

Type course_split into the code chunk below, run, and answer the question that follows?

course_split
## <Analysis/Assess/Total>
## <399/134/533>
  1. How many observations should we expect to see in our training and test sets respectively?

    • 399 for training, 134 for test

Create a training and test set

The {rsample} package has two aptly named functions for created a training and testing data set called training() and testing() respectively.

Run the following code to split the data into

train_data <- training(course_split)

test_data  <- testing(course_split)

Your Turn

First take a look at the training and testing sets we just created:

train_data
## # A tibble: 399 × 20
##    student_id subject semester section enrollment_reason      gender   int   val
##    <fct>      <fct>   <fct>    <fct>   <fct>                  <fct>  <dbl> <dbl>
##  1 43146      FrScA   S216     02      Course Unavailable at… F        4.2  3.67
##  2 44638      OcnA    S116     01      Course Unavailable at… M        4    3   
##  3 47979      OcnA    S216     01      Course Unavailable at… M        4    3.67
##  4 52326      AnPhA   S216     01      Other                  F        3.6  4   
##  5 52446      PhysA   S116     01      Learning Preference o… F        4.2  3.67
##  6 54066      OcnA    S116     01      Other                  F        4.6  4.67
##  7 54282      OcnA    S116     02      Course Unavailable at… F        3.4  2   
##  8 54342      OcnA    S116     02      Learning Preference o… F        4    3   
##  9 55078      FrScA   S216     01      Learning Preference o… M        4.4  3.67
## 10 55140      PhysA   S116     01      Course Unavailable at… M        5    4   
## # … with 389 more rows, and 12 more variables: percomp <dbl>, q1 <dbl>,
## #   q2 <dbl>, q3 <dbl>, q4 <dbl>, q5 <dbl>, q6 <dbl>, q7 <dbl>, q8 <dbl>,
## #   q9 <dbl>, q10 <dbl>, at_risk <fct>
test_data
## # A tibble: 134 × 20
##    student_id subject semester section enrollment_reason      gender   int   val
##    <fct>      <fct>   <fct>    <fct>   <fct>                  <fct>  <dbl> <dbl>
##  1 47448      FrScA   S216     01      Other                  F        4.2  3   
##  2 48797      PhysA   S116     01      Learning Preference o… M        3.8  3.67
##  3 53447      FrScA   S116     01      Course Unavailable at… F        3.8  2   
##  4 53475      FrScA   S216     01      Course Unavailable at… F        4.8  3.33
##  5 54434      PhysA   S116     01      Learning Preference o… F        5    5   
##  6 54567      OcnA    S216     02      Course Unavailable at… M        4    3.67
##  7 57489      PhysA   S116     01      Course Unavailable at… M        4.6  3.67
##  8 60186      AnPhA   S116     01      Course Unavailable at… M        4.2  4.33
##  9 61357      FrScA   S116     02      Course Unavailable at… F        5    4.67
## 10 61941      FrScA   S116     01      Course Unavailable at… F        4.6  4.33
## # … with 124 more rows, and 12 more variables: percomp <dbl>, q1 <dbl>,
## #   q2 <dbl>, q3 <dbl>, q4 <dbl>, q5 <dbl>, q6 <dbl>, q7 <dbl>, q8 <dbl>,
## #   q9 <dbl>, q10 <dbl>, at_risk <fct>

Next, recycle the code from above to check to see that the proportion of at-risk students in our training and test data are close to those in our overall course_data:

train_data %>%
  count(at_risk) %>%
  mutate(proportion = n/sum(n))
## # A tibble: 2 × 3
##   at_risk     n proportion
##   <fct>   <int>      <dbl>
## 1 no        329      0.825
## 2 yes        70      0.175
test_data %>%
  count(at_risk) %>%
  mutate(proportion = n/sum(n))
## # A tibble: 2 × 3
##   at_risk     n proportion
##   <fct>   <int>      <dbl>
## 1 no        110      0.821
## 2 yes        24      0.179

Now answer the following questions:

  1. Do the number of observations in each set match your expectations? Why?

    • 399 for train and 134 for test; yes, they match the splits we set up earlier
  2. Do the proportion of at-risk students in each set match your expectations? Why?

    • yes, the proportions match because we stratified the results to match the at_risk results in the course_data (done during original data set split)

3. CREATE A RECIPE

In this section, we introduce another tidymodels package, recipes, which is designed to help you prepare your data before training your model. Recipes are built as a series of preprocessing steps, such as:

  • converting qualitative predictors to indicator variables (also known as dummy variables),

  • transforming data to be on a different scale (e.g., taking the logarithm of a variable),

  • transforming whole groups of predictors together,

  • extracting key features from raw variables (e.g., getting the day of the week out of a date variable),

and so on. If you are familiar with R’s formula interface, a lot of this might sound familiar and like what a formula already does. Recipes can be used to do many of the same things, but they have a much wider range of possibilities.

Add a formula

To get started, let’s create a recipe for a simple logistic regression model. Before training the model, we can use a recipe to create a few new predictors and conduct some preprocessing required by the model.

The recipe() function as we used it here has two arguments:

  • formula. Any variable on the left-hand side of the tilde (~) is considered the model outcome (at_risk in our case). On the right-hand side of the tilde are the predictors. Variables may be listed by name, or you can use the dot (.) to indicate all other variables as predictors.

  • The data. A recipe is associated with the data set used to create the model. This will typically be the training set, so data = train_data here. Naming a data set doesn’t actually change the data itself; it is only used to catalog the names of the variables and their types, like factors, integers, dates, etc.

Let’s create our very first recipe using at_risk as our outcome variable; int and gender and enrollment_reason as predictors; and train_data as our data to train:

lr_recipe_1 <- recipe(at_risk ~ int + gender + enrollment_reason,
                      data = train_data)

Now let’s take a quick peek at our recipe and create a quick summary of our recipe using the summary() function:

lr_recipe_1
## Recipe
## 
## Inputs:
## 
##       role #variables
##    outcome          1
##  predictor          3
summary(lr_recipe_1)
## # A tibble: 4 × 4
##   variable          type    role      source  
##   <chr>             <chr>   <chr>     <chr>   
## 1 int               numeric predictor original
## 2 gender            nominal predictor original
## 3 enrollment_reason nominal predictor original
## 4 at_risk           nominal outcome   original

You can see that our recipe has three predictors and 1 outcome, just as expected.

Create Dummy Variables

Because we’ll be using a simple logistic regression model, variables like gender and enrollment_reason will need to be coded as dummy variables. Dummy coding means transforming a variable with multiple categories into new variables, where each binary variable indicates the presence and absence of each category. For example, gender will be recoded to gender_f, where 1 indicates female and 0 indicates male.

Unlike the standard model formula methods in R, a recipe does not automatically create these dummy variables for you; you’ll need to tell your recipe to add this step. This is for two reasons. First, many models do not require numeric predictors, so dummy variables may not always be preferred. Second, recipes can also be used for purposes outside of modeling, where non-dummy versions of the variables may work better. For example, you may want to make a table or a plot with a variable as a single factor.

For these reasons, you need to explicitly tell recipes to create dummy variables using step_dummy(). Let’s add this to our recipe and include all_nominal_predictors() to tell our recipe to change all of our factor variables to dummy variables:

lr_recipe_1 <- 
  recipe(at_risk ~ int + gender + enrollment_reason,
                      data = train_data) %>%
  step_dummy(all_nominal_predictors())

lr_recipe_1
## Recipe
## 
## Inputs:
## 
##       role #variables
##    outcome          1
##  predictor          3
## 
## Operations:
## 
## Dummy variables from all_nominal_predictors()
summary(lr_recipe_1)
## # A tibble: 4 × 4
##   variable          type    role      source  
##   <chr>             <chr>   <chr>     <chr>   
## 1 int               numeric predictor original
## 2 gender            nominal predictor original
## 3 enrollment_reason nominal predictor original
## 4 at_risk           nominal outcome   original

Created a kitchen sink recipe

Before training our model, let’s create a second recipe just for contrast that includes all of our predictors by adding the . after the tilde as noted above.

In addition to our step for creating dummy variables, we’ll also have to indicate that our student_id variable is not used for prediction purposes, but rather as a unique identifier for our students.

To do so, we will have to add roles to this recipe using the update_role() function that lets recipes know that student_id is a variable with a custom role that we called "ID" (a role can have any character value). Whereas our formula includes all variables in the training set other than at_risk as predictors, this tells the recipe to keep these two variables but not use them as either outcomes or predictors.

Run the following code to add student_id as a role to our model:

lr_recipe_2 <- 
  recipe(at_risk ~ ., 
         data = train_data) %>%
  step_dummy(all_nominal_predictors()) %>%
  update_role(student_id, new_role = "ID") 

lr_recipe_2
## Recipe
## 
## Inputs:
## 
##       role #variables
##         ID          1
##    outcome          1
##  predictor         18
## 
## Operations:
## 
## Dummy variables from all_nominal_predictors()
summary(lr_recipe_2)
## # A tibble: 20 × 4
##    variable          type    role      source  
##    <chr>             <chr>   <chr>     <chr>   
##  1 student_id        nominal ID        original
##  2 subject           nominal predictor original
##  3 semester          nominal predictor original
##  4 section           nominal predictor original
##  5 enrollment_reason nominal predictor original
##  6 gender            nominal predictor original
##  7 int               numeric predictor original
##  8 val               numeric predictor original
##  9 percomp           numeric predictor original
## 10 q1                numeric predictor original
## 11 q2                numeric predictor original
## 12 q3                numeric predictor original
## 13 q4                numeric predictor original
## 14 q5                numeric predictor original
## 15 q6                numeric predictor original
## 16 q7                numeric predictor original
## 17 q8                numeric predictor original
## 18 q9                numeric predictor original
## 19 q10               numeric predictor original
## 20 at_risk           nominal outcome   original

4. MODEL

a. Fit a Logistic Regression Model

With tidymodels, we start building a model by specifying the functional form of the model that we want using the [parsnip] package. Since our outcome is binary, the model type we will use is “logistic regression.” We can declare this with logistic_reg() and assign to an object we will later use in our workflow:

lr_mod <- logistic_reg()

That is pretty underwhelming since, on its own, it doesn’t really do much. However, now that the type of model has been specified, a method for fitting or training the model can be stated using the engine.

Start your engine

The engine value is often a mash-up of different packages that can be used to fit or train the model as well as the estimation method. For example, we will use “glm” a generalized linear model for binary outcomes and default for logistic regression in the {parsnip} package.

Run the following code to finish specifying our model:

lr_mod <- 
  logistic_reg() %>% 
  set_engine("glm")

Add to workflow

We will want to use our recipes created earlier across several steps as we train and test our model. To simplify this process, we can use a model workflow, which pairs a model and recipe together.

This is a straightforward approach because different recipes are often needed for different models, so when a model and recipe are bundled, it becomes easier to train and test workflows.

We’ll use the {workflows} package from tidymodels to bundle our parsnip model (lr_mod) with our first recipe (lr_recipe_1).

lr_workflow <- 
  workflow() %>% 
  add_model(lr_mod) %>% 
  add_recipe(lr_recipe_1)

Fit model to training set

Now that we have a single workflow that can be used to prepare the recipe and train the model from the resulting predictors, we can use the fit() function to fit our model to our train_data. And again, we set a random number seed to ensure that if we run this same code again, we will get the same results in terms of the data partition:

set.seed(586)

lr_fit <- 
  lr_workflow %>% 
  fit(data = train_data)

This lr_fit object has the finalized recipe and fitted model objects inside. To extract the model fit from the workflow, we will use the helper functions extract_fit_parsnip(). Here we pull the fitted model object then use the broom::tidy() function to get a tidy tibble of model coefficients:

lr_fit %>% 
  extract_fit_parsnip() %>% 
  tidy()
## # A tibble: 7 × 5
##   term                                      estimate std.error statistic p.value
##   <chr>                                        <dbl>     <dbl>     <dbl>   <dbl>
## 1 (Intercept)                                -0.0105     0.920   -0.0114  0.991 
## 2 int                                        -0.355      0.209   -1.70    0.0892
## 3 gender_M                                    0.174      0.283    0.616   0.538 
## 4 enrollment_reason_Credit.Recovery           1.08       1.04     1.03    0.301 
## 5 enrollment_reason_Learning.Preference.of…  -0.497      0.418   -1.19    0.234 
## 6 enrollment_reason_Other                    -0.109      0.377   -0.288   0.773 
## 7 enrollment_reason_Scheduling.Conflict      -0.255      0.451   -0.566   0.571

Among the predictors included in our model, int or interest in subject area score, seems to be the sole significant predictor of being identified as at-risk according to our definition. This doesn’t bode too well for our model but let’s proceed with testing anyways.

Test the model

Now that we’ve fit our model to our training data, we’re FINALLY ready to test our model on the data we set aside in the beginning. Just to recap the steps that led to this moment, however, recall that we:

  1. Built the model (lr_mod),

  2. Created a preprocessing recipe (lr_recipe_1),

  3. Bundled the model and recipe (lr_workflow), and

  4. Trained our workflow using a single call to fit().

The next step is to use the trained workflow (lr_fit) to predict outcomes for our unseen test data, which we will do with the function predict(). The predict() method applies the recipe to the new data, then passes them to the fitted model.

predict(lr_fit, test_data)
## # A tibble: 134 × 1
##    .pred_class
##    <fct>      
##  1 no         
##  2 no         
##  3 no         
##  4 no         
##  5 no         
##  6 no         
##  7 no         
##  8 no         
##  9 no         
## 10 no         
## # … with 124 more rows

Because our outcome variable at_risk here is a factor, the output from predict() returns the predicted class: no versus yes. Not a super useful output to be honest though.

Fortunately there is an augment() function we can use with our lr_fir model and test_data to save them together:

Let’s use this function, save as lr_predictions:

lr_predictions <- augment(lr_fit, test_data)

Your Turn

Take a quick look at lr_predictions in the code chunk below and answer the question that follows?

lr_predictions
## # A tibble: 134 × 23
##    student_id subject semester section enrollment_reason      gender   int   val
##    <fct>      <fct>   <fct>    <fct>   <fct>                  <fct>  <dbl> <dbl>
##  1 47448      FrScA   S216     01      Other                  F        4.2  3   
##  2 48797      PhysA   S116     01      Learning Preference o… M        3.8  3.67
##  3 53447      FrScA   S116     01      Course Unavailable at… F        3.8  2   
##  4 53475      FrScA   S216     01      Course Unavailable at… F        4.8  3.33
##  5 54434      PhysA   S116     01      Learning Preference o… F        5    5   
##  6 54567      OcnA    S216     02      Course Unavailable at… M        4    3.67
##  7 57489      PhysA   S116     01      Course Unavailable at… M        4.6  3.67
##  8 60186      AnPhA   S116     01      Course Unavailable at… M        4.2  4.33
##  9 61357      FrScA   S116     02      Course Unavailable at… F        5    4.67
## 10 61941      FrScA   S116     01      Course Unavailable at… F        4.6  4.33
## # … with 124 more rows, and 15 more variables: percomp <dbl>, q1 <dbl>,
## #   q2 <dbl>, q3 <dbl>, q4 <dbl>, q5 <dbl>, q6 <dbl>, q7 <dbl>, q8 <dbl>,
## #   q9 <dbl>, q10 <dbl>, at_risk <fct>, .pred_class <fct>, .pred_no <dbl>,
## #   .pred_yes <dbl>

Was our model successful at predicting any students who were at-risk? How do you know?

  • No. Variable .pred_class had 0 “yes” results. Every instance was predicted as “no.”

Hint: Scroll to the end of the data frame and take a look at our original at_risk outcome and the .pred_class variable which shows the predicted outcomes.

Check model accuracy

As you probably noticed, just looking at the lr_predictions object is not the easiest way to check for model accuracy. Fortunately, the {yardstick} package has an accuracy() function for looking at the overall classification accuracy, which uses the hard class predictions to measure performance.

Hard class predictions tell us whether our model predicted yes or no for each student in the .pred_class column, as well as the estimating a probability. A simple 50% probability cutoff is used to categorize a student as at risk. For example, student 47448 had a .pred_no probability of 0.8854332 and .pred_yes probability of 0.11456677 and so was classified as no for not being for at_risk.

Run the following code to select() these variables followed by the accuracy() function to see how frequently our prediction matched our observed data:

lr_predictions %>%
  select(at_risk, .pred_class) %>%
  accuracy(truth = at_risk, .pred_class)
## # A tibble: 1 × 3
##   .metric  .estimator .estimate
##   <chr>    <chr>          <dbl>
## 1 accuracy binary         0.821

Overall it looks like our model was correct 82% of the time, which we’ll see in a second is not terribly impressive.

Another way to check model accuracy is with the conf_mat() function from {yardstick} for creating a confusion matrix. Recall from our course text Learning Analytics Goes to School that a confusion matrix is simply a 2 × 2 table that lists the number of true-negatives, false-negatives, true-positives, and false-positives.

Run the follow code to create a confusion matrix for our logistic regression predictions:

lr_predictions %>%
conf_mat(at_risk, .pred_class)
##           Truth
## Prediction  no yes
##        no  110  24
##        yes   0   0

As you can see our model accurately predicted all students as “no” for at risk 110 times, but inaccurately predicted 24 students a “no” for at risk when they were actually “yes” in our test_data. Overall our model was 82% accurate, but it achieved this by simply labeling everyone as “no” for at risk. And since rough 82% of our students were actually “no,” this is clearly not a great prediction model.

Your Turn

Recycle our code from above to create a workflow for lr_recipe_2 and test our “kitchen sink” model on our test data. Hint: You can accomplish this by simply copying and pasting and changing a single character from above.

# create workflow
lr_workflow <- 
  workflow() %>% 
  add_model(lr_mod) %>% 
  add_recipe(lr_recipe_2)

# set seeed
set.seed(586)


# fit model to workflow
lr_fit <- 
  lr_workflow %>% 
  fit(data = test_data)

# extract model estimates
lr_fit %>% 
  extract_fit_parsnip() %>% 
  tidy()
## # A tibble: 28 × 5
##    term        estimate std.error statistic p.value
##    <chr>          <dbl>     <dbl>     <dbl>   <dbl>
##  1 (Intercept)  -2.72       2.74    -0.991    0.322
##  2 int          -0.749      2.53    -0.295    0.768
##  3 val           0.544      1.62     0.335    0.738
##  4 percomp       0.752      0.850    0.884    0.377
##  5 q1           -0.702      1.06    -0.664    0.507
##  6 q2            0.624      0.787    0.793    0.428
##  7 q3            0.0931     0.584    0.159    0.873
##  8 q4           -0.863      0.875   -0.987    0.324
##  9 q5            0.920      1.04     0.887    0.375
## 10 q6            0.0174     0.765    0.0227   0.982
## # … with 18 more rows
# get predictions 
predict(lr_fit, test_data)
## # A tibble: 134 × 1
##    .pred_class
##    <fct>      
##  1 no         
##  2 no         
##  3 no         
##  4 no         
##  5 no         
##  6 yes        
##  7 no         
##  8 no         
##  9 no         
## 10 no         
## # … with 124 more rows
lr_predictions <- augment(lr_fit, test_data)

# check overall accuracy 
lr_predictions %>%
  select(at_risk, .pred_class) %>%
  accuracy(truth = at_risk, .pred_class)
## # A tibble: 1 × 3
##   .metric  .estimator .estimate
##   <chr>    <chr>          <dbl>
## 1 accuracy binary         0.851
# create a confusion matrix
lr_predictions %>%
conf_mat(at_risk, .pred_class)
##           Truth
## Prediction  no yes
##        no  106  16
##        yes   4   8
  1. Does this model perform any better? How do you know?

    • .estimate increased to 85.1%

    • multiple “yes” values had a .pred_yes result > 50%

b. Fit a Random Forest Model

Random forest models are ensembles of decision trees. Each of those terms is linked because there is a dense amount of information required to understand what each of those terms really means. For now, however, them main thing to know is that:

One of the benefits of a random forest model is that it is very low maintenance; it requires very little preprocessing of the data and the default parameters tend to give reasonable results.

For that reason, we’ll just create a very simple recipe for our course_data data includes all our predictors and singles out our student_id role:

rf_recipe <- 
  recipe(at_risk ~ ., 
         data = train_data) %>%
  update_role(student_id, new_role = "ID")

rf_recipe
## Recipe
## 
## Inputs:
## 
##       role #variables
##         ID          1
##    outcome          1
##  predictor         18
summary(rf_recipe)
## # A tibble: 20 × 4
##    variable          type    role      source  
##    <chr>             <chr>   <chr>     <chr>   
##  1 student_id        nominal ID        original
##  2 subject           nominal predictor original
##  3 semester          nominal predictor original
##  4 section           nominal predictor original
##  5 enrollment_reason nominal predictor original
##  6 gender            nominal predictor original
##  7 int               numeric predictor original
##  8 val               numeric predictor original
##  9 percomp           numeric predictor original
## 10 q1                numeric predictor original
## 11 q2                numeric predictor original
## 12 q3                numeric predictor original
## 13 q4                numeric predictor original
## 14 q5                numeric predictor original
## 15 q6                numeric predictor original
## 16 q7                numeric predictor original
## 17 q8                numeric predictor original
## 18 q9                numeric predictor original
## 19 q10               numeric predictor original
## 20 at_risk           nominal outcome   original

To fit a random forest model on the training set, we’ll use the {parsnip} package again along with the ranger engine. We’ll also include the set_mode() function to specify our model as “classification” rather than “regression.”

Recall from our Learning Analytics Goes to School that supervised machined learning, or predictive modeling, involves two broad approaches: classification and regression. Classification algorithms model categorical outcomes (e.g., yes or no outcomes like with our at risk data). Regression algorithms characterize continuous outcomes (e.g., test scores).

Run the following code to create our random forest model for our training data:

rf_mod <- 
  rand_forest(trees = 1000) %>% 
  set_engine("ranger", importance = "impurity") %>% 
  set_mode("classification")

Now let’s combine our model and recipe to create our new random forest workflow:

rf_workflow <- 
  workflow() %>% 
  add_model(rf_mod) %>% 
  add_recipe(rf_recipe)

And fit our model and recipe to our training data:

set.seed(586)

rf_fit <- 
  rf_workflow %>% 
  fit(data = train_data)

Gather our random forest predictions:

rf_predictions <- augment(rf_fit, test_data)

rf_predictions
## # A tibble: 134 × 23
##    student_id subject semester section enrollment_reason      gender   int   val
##    <fct>      <fct>   <fct>    <fct>   <fct>                  <fct>  <dbl> <dbl>
##  1 47448      FrScA   S216     01      Other                  F        4.2  3   
##  2 48797      PhysA   S116     01      Learning Preference o… M        3.8  3.67
##  3 53447      FrScA   S116     01      Course Unavailable at… F        3.8  2   
##  4 53475      FrScA   S216     01      Course Unavailable at… F        4.8  3.33
##  5 54434      PhysA   S116     01      Learning Preference o… F        5    5   
##  6 54567      OcnA    S216     02      Course Unavailable at… M        4    3.67
##  7 57489      PhysA   S116     01      Course Unavailable at… M        4.6  3.67
##  8 60186      AnPhA   S116     01      Course Unavailable at… M        4.2  4.33
##  9 61357      FrScA   S116     02      Course Unavailable at… F        5    4.67
## 10 61941      FrScA   S116     01      Course Unavailable at… F        4.6  4.33
## # … with 124 more rows, and 15 more variables: percomp <dbl>, q1 <dbl>,
## #   q2 <dbl>, q3 <dbl>, q4 <dbl>, q5 <dbl>, q6 <dbl>, q7 <dbl>, q8 <dbl>,
## #   q9 <dbl>, q10 <dbl>, at_risk <fct>, .pred_class <fct>, .pred_no <dbl>,
## #   .pred_yes <dbl>

And check our model’s overall accuracy:

rf_predictions %>%
  select(at_risk, .pred_class) %>%
  accuracy(truth = at_risk, .pred_class)
## # A tibble: 1 × 3
##   .metric  .estimator .estimate
##   <chr>    <chr>          <dbl>
## 1 accuracy binary         0.821

And create a confusion matrix to see where it did and did not make accurate predictions:

rf_predictions %>%
conf_mat(at_risk, .pred_class)
##           Truth
## Prediction  no yes
##        no  110  24
##        yes   0   0

Overall, our random forest model is slightly better at predicting students who were defined as “at risk,” and definitely better at predicting students who were truly “at risk” according to our definition.

Unfortunately, if this had been a real-world situation, only 7 of the 24 students would have received additional support. Clearly we’d need to build a better model. In this case, more data would definitely help, but it’s likely we’re missing some important information about students that might be helpful for including in our model.

Your Turn

Try creating your own recipe and a logistic regression or random forest model and see how it performs against the three that I created.

Use the code chunk below to record your final model:

#create rf recipe using only 4 predictor variables
rf_recipe2 <- 
  recipe(at_risk ~ student_id + subject + gender + enrollment_reason + int, 
         data = train_data) %>%
  update_role(student_id, new_role = "ID")

# create model for training data
rf_mod <- 
  rand_forest(trees = 1000) %>% 
  set_engine("ranger", importance = "impurity") %>% 
  set_mode("classification")

# create workflow
rf_workflow <- 
  workflow() %>% 
  add_model(rf_mod) %>% 
  add_recipe(rf_recipe)

# fit model and recipe to training data
set.seed(586)

rf_fit <- 
  rf_workflow %>% 
  fit(data = train_data)

# gather predictions
rf_predictions <- augment(rf_fit, test_data)

rf_predictions
## # A tibble: 134 × 23
##    student_id subject semester section enrollment_reason      gender   int   val
##    <fct>      <fct>   <fct>    <fct>   <fct>                  <fct>  <dbl> <dbl>
##  1 47448      FrScA   S216     01      Other                  F        4.2  3   
##  2 48797      PhysA   S116     01      Learning Preference o… M        3.8  3.67
##  3 53447      FrScA   S116     01      Course Unavailable at… F        3.8  2   
##  4 53475      FrScA   S216     01      Course Unavailable at… F        4.8  3.33
##  5 54434      PhysA   S116     01      Learning Preference o… F        5    5   
##  6 54567      OcnA    S216     02      Course Unavailable at… M        4    3.67
##  7 57489      PhysA   S116     01      Course Unavailable at… M        4.6  3.67
##  8 60186      AnPhA   S116     01      Course Unavailable at… M        4.2  4.33
##  9 61357      FrScA   S116     02      Course Unavailable at… F        5    4.67
## 10 61941      FrScA   S116     01      Course Unavailable at… F        4.6  4.33
## # … with 124 more rows, and 15 more variables: percomp <dbl>, q1 <dbl>,
## #   q2 <dbl>, q3 <dbl>, q4 <dbl>, q5 <dbl>, q6 <dbl>, q7 <dbl>, q8 <dbl>,
## #   q9 <dbl>, q10 <dbl>, at_risk <fct>, .pred_class <fct>, .pred_no <dbl>,
## #   .pred_yes <dbl>
# check accuracy 
rf_predictions %>%
  select(at_risk, .pred_class) %>%
  accuracy(truth = at_risk, .pred_class)
## # A tibble: 1 × 3
##   .metric  .estimator .estimate
##   <chr>    <chr>          <dbl>
## 1 accuracy binary         0.821
# create confusion matrix
rf_predictions %>%
conf_mat(at_risk, .pred_class)
##           Truth
## Prediction  no yes
##        no  110  24
##        yes   0   0

Now answer the following questions?

  1. How did you model do compared to others?

    • My model, with an additional variable (subject), produced the same accuracy as the linear regression model for the training data, 82%. This shows that the course subject had little to no bearing on a student’s chances for pass/fail.
  2. What information might be useful to know about students prior to the course start that might be useful for improving our model?

    • current GPA - could indicate overall aptitude

5. COMMUNICATE

In this case study, we focused applying some basic machine learning techniques to help us understand how a predictive model used in early warning systems might actually be developed and tested. Specifically, we made a very crude first attempt at developing a model using machine learning techniques that were not terribly great at accurately predict whether a student is likely to pass or fail and online course.

Below, add a few notes in response to the following prompts:

  1. One thing I took away from this learning lab that I found especially useful: learning the standard workflow to create, train/test a model.

  2. One thing I want to learn more about: how do we adjust the split ratios for our data sets? If we want an 80/20 or a 90/10 split, how would we adjust our code to return those sized data sets?

To “turn in” your work, you can click the “Knit” icon at the top of the file, or click the dropdown arrow next to it and select “Knit top HTML.” This will create a report in your Files pane that serves as a record of your completed assignment and its output you can open or share.

Congratulations!

LS0tCnRpdGxlOiAnVW5pdCAyIENhc2UgU3R1ZHk6IElkZW50aWZ5aW5nIEF0LVJpc2sgU3R1ZGVudHMnCmF1dGhvcjogIkRyLiBTaGF1biBLZWxsb2dnIgpkYXRlOiAiT2N0IDEsIDIwMjEgKHVwZGF0ZWQ6IGByIFN5cy5EYXRlKClgKSIKb3V0cHV0OgogIGh0bWxfZG9jdW1lbnQ6CiAgICB0b2M6IHllcwogICAgdG9jX2RlcHRoOiAnNScKICAgIHRvY19mbG9hdDogeWVzCiAgICBjb2RlX2ZvbGRpbmc6IHNob3cKICAgIGNvZGVfZG93bmxvYWQ6IFRSVUUKbmFtZTogJycKZWRpdG9yX29wdGlvbnM6CiAgbWFya2Rvd246CiAgICB3cmFwOiA3MgpiaWJsaW9ncmFwaHk6IGxpdC9yZWZlcmVuY2VzLmJpYgotLS0KCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUpCmBgYAoKIyMgMS4gUFJFUEFSRQoKSW4gVW5pdCAyLCB3ZSBwaWNrIHVwIHdoZXJlIHdlIGxlZnQgb2ZmIGFuZCBsZWFybiB0byBhcHBseSBzb21lIGJhc2ljCm1hY2hpbmUgbGVhcm5pbmcgdGVjaG5pcXVlcyB0byBoZWxwIHVzIHVuZGVyc3RhbmQgaG93IGEgcHJlZGljdGl2ZSBtb2RlbAptaWdodCBiZSBkZXZlbG9wZWQgYW5kIHRlc3RlZCBhcyBwYXJ0IG9mIGFuIGVhcmx5IHdhcm5pbmcgc3lzdGVtIHRvCmlkZW50aWZ5IHN0dWRlbnRzIGF0IHJpc2sgb2YgZmFpbGluZy4gU3BlY2lmaWNhbGx5LCB3ZSB3aWxsIG1ha2UgYSB2ZXJ5CmNydWRlIGZpcnN0IGF0dGVtcHQgYXQgZGV2ZWxvcGluZyBhIHRlc3RpbmcgYSBsb2dpc3RpYyByZWdyZXNzaW9uIGFuZCBhCnJhbmRvbSBmaXJzdCBtb2RlbCB0aGF0IGNhbiAod2UgaG9wZSEpIGFjY3VyYXRlbHkgcHJlZGljdCB3aGV0aGVyIGEKc3R1ZGVudCBpcyBsaWtlbHkgdG8gcGFzcyBvciBmYWlsIGFuZCBvbmxpbmUgY291cnNlLiBVbmxpa2UgVW5pdCAxLAp3aGVyZSB3ZSB3ZXJlIGludGVyZXN0ZWQgaW4gaWRlbnRpZnlpbmcgZmFjdG9ycyBmcm9tIGRhdGEgY29sbGVjdGVkCnRocm91Z2hvdXQgdGhlIGNvdXJzZSB0aGF0IG1pZ2h0IGhlbHAgZXhwbGFpbiB3aHkgc3R1ZGVudHMgZWFybmVkIHRoZQpncmFkZSB0aGV5IGRpZCwgd2UgYXJlIGluc3RlYWQgaW50ZXJlc3RlZCBpZGVudGlmeWluZyBzdHVkZW50cyB3aG8gbWF5CmJlIGF0IHJpc2sgYmVmb3JlIGl0IGlzIHRvbyBsYXRlIHRvIGludGVydmVuZS4gVGhlcmVmb3JlIHdlIHdpbGwgb25seQp1c2UgaW5mb3JtYXRpb24gdGhhdCBpcyBhdmFpbGFibGUgYXQgdGhlIHN0YXJ0IG9mIHRoZSBjb3Vyc2UuCgojIyMgYS4gTG9hZCBMaWJyYXJpZXMKCiMjIyMgdGlkeW1vZGVscyDwn5OmCgpbIVtdKGltZy90aWR5bW9kZWxzLnN2Zyl7d2lkdGg9IjIwJSJ9XShodHRwczovL3d3dy50aWR5bW9kZWxzLm9yZykKClRoZSBbdGlkeW1vZGVsc10oaHR0cHM6Ly93d3cudGlkeW1vZGVscy5vcmcpIHBhY2thZ2UgaXMgYSAibWV0YS1wYWNrYWdlIgpmb3IgbW9kZWxpbmcgYW5kIHN0YXRpc3RpY2FsIGFuYWx5c2lzIHRoYXQgc2hhcmUgdGhlIHVuZGVybHlpbmcgZGVzaWduCnBoaWxvc29waHksIGdyYW1tYXIsIGFuZCBkYXRhIHN0cnVjdHVyZXMgb2YgdGhlClt0aWR5dmVyc2VdKGh0dHBzOi8vd3d3LnRpZHl2ZXJzZS5vcmcvKS4gSXQgaW5jbHVkZXMgYSBjb3JlIHNldCBvZgpwYWNrYWdlcyB0aGF0IGFyZSBsb2FkZWQgb24gc3RhcnR1cCBhbmQgY29udGFpbnMgdG9vbHMgZm9yOgoKLSAgIGRhdGEgc3BsaXR0aW5nIGFuZCBwcmUtcHJvY2Vzc2luZzsKLSAgIG1vZGVsIHNlbGVjdGlvbiwgdHVuaW5nLCBhbmQgZXZhbHVhdGlvbjsKLSAgIGZlYXR1cmUgc2VsZWN0aW9uIGFuZCB2YXJpYWJsZSBpbXBvcnRhbmNlIGVzdGltYXRpb247Ci0gICBhcyB3ZWxsIGFzIG90aGVyIGZ1bmN0aW9uYWxpdHkuCgojIyMjIFsqKllvdXIgVHVybioqXXtzdHlsZT0iY29sb3I6IGdyZWVuOyJ9ICoq4qS1KioKCkluIGFkZGl0aW9uIHRvIHRoZSB7dGlkeW1vZGVsc30gcGFja2FnZSwgd2UnbGwgYWxzbyBiZSB1c2luZyB0aGUKe3RpZHl2ZXJzZX0sIHtza2ltcn0gYW5kIHtoZXJlfSBwYWNrYWdlcyB3ZSBsZWFybmVkIGFib3V0IGluIFVuaXQgMS4gVXNlCnRoZSBjb2RlIGNodW5rIGJlbG93IHRvIGxvYWQgdGhlc2UgdGhyZWUgcGFja2FnZXM6CgpgYGB7cn0KbGlicmFyeSh0aWR5bW9kZWxzKQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShza2ltcikKbGlicmFyeShoZXJlKQpgYGAKCiMjIyBiLiBJbXBvcnQgJiBJbnNwZWN0IERhdGEKCkZvciB0aGlzIGNhc2Ugc3R1ZHksIHdlIHdpbGwgYWdhaW4gYmUgd29ya2luZyBhZ2FpbiB3aXRoIGRhdGEgZnJvbSB0aGUKb25saW5lIGNvdXJzZXMgaW50cm9kdWNlZCBpbiBVbml0IDEuIFRoaXMgZGF0YSBpcyBsb2NhdGVkIGluIHRoZSBkYXRhCmZvbGRlciBhbmQgbmFtZWQgYGRhdGEtdG8tZXhwbG9yZS5jc3ZgLiBGb3J0dW5hdGVseSwgb3VyIGRhdGEgaGFzCmFscmVhZHkgYmVlbiBsYXJnZWx5IHdyYW5nbGVkLiBUaGlzIHdpbGwgc2F2ZSB1cyBxdWl0ZSBhIGJpdCBvZiB0aW1lLAp3aGljaCB3ZSdsbCBuZWVkIHRvIGhlbHAgd3JhcCBvdXIgaGVhZHMgYXJvdW5kIHNvbWUgc3VwZXJ2aXNlZCBtYWNoaW5lCmxlYXJuaW5nIGJhc2ljcy4KCiMjIyMgWyoqWW91ciBUdXJuKipde3N0eWxlPSJjb2xvcjogZ3JlZW47In0gKiripLUqKgoKVXNlIHRoZSBjb2RlIGNodW5rIGJlbG93IHRvIHJlYWQgdGhpcyBkYXRhIGludG8geW91ciBSIGVudmlyb25tZW50IGFuZAphc3NpZ24gdGhlIG5hbWUgYGRhdGFfdG9fbW9kZWxgLiBOb3RlLCB5b3UgbWF5IGNob29zZSB0byB1c2UgdGhlIHtoZXJlfQpwYWNrYWdlIG9yIHlvdSBtYXkgc3BlY2lmeSB0aGUgZmlsZSBwYXRoIGRpcmVjdGx5OgoKYGBge3J9CmRhdGFfdG9fbW9kZWwgPC0gcmVhZF9jc3YoaGVyZSgiZGF0YSIsICJkYXRhLXRvLWV4cGxvcmUuY3N2IikpCmBgYAoKQXMgbm90ZWQgaW4gQ2hhcHRlciAxNCBvZiBEYXRhIFNjaWVuY2UgaW4gRWR1Y2F0aW9uIFVzaW5nIFIsIGl0J3MgZ29vZApwcmFjdGljZSB0byBpbnNwZWN0IHlvdXIgZGF0YSBhbmQgbWFrZSBzdXJlIGl0IGxvb2tzIHRoZSB3YXkgeW91IGV4cGVjdAppdCB0by4KClVzZSB0aGUgY29kZSBjaHVuayBiZWxvdyB0byB0YWtlIGEgbG9vayBvdXIgZGF0YSwgdGhlbiBhbnN3ZXIgdGhlCnF1ZXN0aW9ucyB0aGF0IGZvbGxvdzoKCmBgYHtyfQpnbGltcHNlKGRhdGFfdG9fbW9kZWwpCmBgYAoKMS4gIEhvdyBtYW55IG9ic2VydmF0aW9ucyBhbmQgdmFyaWFibGVzIGFyZSBpbiBvdXIgZGF0YXNldD8KCiAgICAtICAgMzQKCjIuICBTaW5jZSBvdXIgZ29hbCBpcyBkZXZlbG9wIGEgbW9kZWwgdGhhdCBwcmVkaWN0cyB3aGV0aGVyIHN0dWRlbnRzIGFyZQogICAgbGlrZWx5IHRvIGJlIGF0IHJpc2sgb2YgZmFpbGluZywgd2hhdCB2YXJpYWJsZSBtaWdodCB3ZSB1c2UgYXMgb3VyCiAgICBvdXRjb21lIHZhcmlhYmxlIGZvciBvdXIgbW9kZWw/CgogICAgLSAgIHByb3BvcnRpb25fZWFybmVkCgozLiAgQmFzZWQgb24geW91ciBwcmlvciB3b3JrIHdpdGggdGhpcyBkYXRhLCBhbmQgdGhlIGRlc2NyaXB0aW9ucyBvZiB0aGUKICAgIGRhdGEgaW4gW0NoYXB0ZXIgNyBvZgogICAgRFNJRVVSXShiZWZvcmUlMjBpdCUyMGlzJTIwdG9vJTIwbGF0ZSUyMHRvJTIwaW50ZXJ2ZW5lLiksIHdoYXQgYXJlCiAgICBzb21lIHZhcmlhYmxlcyBjb2xsZWN0ZWQgcHJpb3IgdG8gdGhlIGNvdXJzZSB0aGF0IHlvdSB0aGluayBtaWdodAogICAgbWFrZSBnb29kIHByZWRpY3RvcnMgb2YgYXQgcmlzayBzdHVkZW50cz8gV2h5PwoKICAgIC0gICBzdWJqZWN0IC0gc29tZSBjbGFzc2VzIGFyZSBwb3RlbnRpYWxseSBtb3JlIGRpZmZpY3VsdAoKICAgIC0gICBnZW5kZXIgLSBnZW5kZXIgbWFrZXVwIG9mIGNsYXNzIGNvdWxkIGluZmx1ZW5jZSBzdHVkeSBoYWJpdHMKCiAgICAtICAgZW5yb2xsbWVudF9yZWFzb24gLSBwZXJzb25hbCBtb3RpdmF0aW9uIG1heSBpbmZsdWVuY2Ugc3R1ZHkKICAgICAgICBlZmZvcnQKCiAgICAtICAgaW50IC0gaW50ZXJlc3QgbGV2ZWwgbWF5IGJlIGEgcHJlZGljdG9yIG9mIGludHJpbnNpYyBtb3RpdmF0aW9uCgojIyAyLiBXUkFOR0xFCgpJbiBnZW5lcmFsLCBkYXRhIHdyYW5nbGluZyBpbnZvbHZlcyBzb21lIGNvbWJpbmF0aW9uIG9mIGNsZWFuaW5nLApyZXNoYXBpbmcsIHRyYW5zZm9ybWluZywgYW5kIG1lcmdpbmcgZGF0YSAoV2lja2hhbSAmIEdyb2xlbXVuZCwgMjAxNykuClRoZSBpbXBvcnRhbmNlIG9mIGRhdGEgd3JhbmdsaW5nIGlzIGRpZmZpY3VsdCB0byBvdmVyc3RhdGUsIGFzIGl0Cmludm9sdmVzIHRoZSBpbml0aWFsIHN0ZXBzIG9mIGdvaW5nIGZyb20gcmF3IGRhdGEgdG8gYSBkYXRhc2V0IHRoYXQgY2FuCmJlIGV4cGxvcmVkIGFuZCBtb2RlbGVkIChLcnVtbSBldCBhbCwgMjAxOCkuIEluIFBhcnQgMiwgd2UgZm9jdXMgb24gdGhlCnRoZSBmb2xsb3dpbmcgd3JhbmdsaW5nIHByb2Nlc3NlcyB0bzoKCmEuICAqKklzb2xhdGUgUHJlZGljdG9ycyoqLiBJbiB0aGlzIHNlY3Rpb24sIHdlIHdpbGwgbmFycm93IGRvd24gb3VyCiAgICBkYXRhIHNldCB0byBhbGwgcG90ZW50aWFsIHByZWRpY3RvcnMgb2YgaW50ZXJlc3QuCgpiLiAgKipQcmVwcm9jZXNzIERhdGEqKi4gV2UgY3JlYXRlIGEgYmluYXJ5IHZhcmlhYmxlIG91dGNvbWUgd2UgYXJlCiAgICBpbnRlcmVzdGVkIGluIHByZWRpY3RpbmcsIGFuZCByZW1vdmUgcXVpdGUgYSBiaXQgb2YgY3J1ZnQuCgpjLiAgKipTcGxpdCBEYXRhKiouIFdlIHNwbGl0IG91ciBkYXRhIGludG8gYSB0cmFpbmluZyBhbmQgdGVzdCBzZXQgdGhhdAogICAgd2lsbCBiZSB1c2VkIHRvIGRldmVsb3AgYSBwcmVkaWN0aXZlIG1vZGVsLgoKZC4gICoqQ3JlYXRlIGEgUmVjaXBlKiouIEZpbmFsbHksIHdlIHdpbGwgdGVzdCBvdXIgYSBjb3VwbGUgZGlmZmVyZW50CiAgICAicmVjaXBlcyIgZm9yIG91ciBwcmVkaWN0aXZlIG1vZGVsIGFuZCBsZWFybiBob3cgdG8gZGVhbCB3aXRoCiAgICBub21pbmFsIGRhdGEgdGhhdCB3ZSB3b3VsZCBsaWtlIHRvIHVzZSBhcyBwcmVkaWN0b3JzLgoKIyMjIGEuIElzb2xhdGUgUHJlZGljdG9ycwoKQXMgeW91J3ZlIHByb2JhYmx5IG5vdGljZWQsIHRoZXJlIGlzIGEgbG90IG9mIGdyZWF0IGluZm9ybWF0aW9uIGluIHRoaXMKZGF0YXNldCAtIGJ1dCB3ZSB3b24ndCBuZWVkIGFsbCBvZiBpdC4gRXN0cmVsbGFkbyBldCBhbC4gbm90ZSBpbiBEU0lFVVIKdGhhdCBmb3Igc3RhdGlzdGljYWwgcmVhc29ucyBhbmQgYXMgYSBnb29kIGdlbmVyYWwgcHJhY3RpY2UsIGl0J3MgYmV0dGVyCnRvIHNlbGVjdCBhIGZldyB2YXJpYWJsZXMgeW91IGFyZSBpbnRlcmVzdGVkIGluIGFuZCBnbyBmcm9tIHRoZXJlOgoKPiBBdCBhIGNlcnRhaW4gcG9pbnQsIGFkZGluZyBtb3JlIHZhcmlhYmxlcyB3aWxsICphcHBlYXIqIHRvIG1ha2UgeW91cgo+IGFuYWx5c2lzIG1vcmUgYWNjdXJhdGUsIGJ1dCB3aWxsIGluIGZhY3Qgb2JzY3VyZSB0aGUgdHJ1dGggZnJvbSB5b3UuCgpVbmxpa2UgVW5pdCAxLCB3ZSBhcmUgbm90IGludGVyZXN0ZWQgaW50ZXJlc3RlZCBzbyBtdWNoIGV4cGxhaW5pbmcgd2h5CnN0dWRlbnRzIGVhcm5lZCB0aGUgZ3JhZGUgdGhleSBkaWQgYWZ0ZXIgdGhlIGZhY3QsIG9yIHdoaWNoIHZhcmlhYmxlcwptaWdodCBiZSBzaWduaWZpY2FudCBwcmVkaWN0b3JzIG9mIHRoZWlyIGZpbmFsIGdyYWRlLiBJbnN0ZWFkIHdlIGFyZQppbnRlcmVzdGVkIGluIGEgbW9yZSBpbW1lZGlhdGUgYW5kIHByYWN0aWNhbCBhcHBsaWNhdGlvbiBvZiBtb2RlbGluZywKdGhhdCBpcyBkZXZlbG9waW5nIGEgbW9kZWwgdXNpbmcgbWFjaGluZSBsZWFybmluZyB0ZWNobmlxdWVzIHRoYXQgY2FuCmFjY3VyYXRlbHkgcHJlZGljdCB3aGV0aGVyIGEgc3R1ZGVudCBpcyBsaWtlbHkgdG8gcGFzcyBvciBmYWlsIGFuZApvbmxpbmUgY291cnNlIHVzaW5nIG9ubHkgaW5mb3JtYXRpb24gYXZhaWxhYmxlIHByaW9yIHRvIGNvdXJzZS4KCk91ciBnb2FsLCB0aGVyZWZvcmUsIGlzIHRvIGlkZW50aWZ5IHN0dWRlbnRzIHdobyBtYXkgYmUgYXQgcmlzayBvZgpmYWlsaW5nIHVzaW5nIGluZm9ybWF0aW9uIHRoYXQgaXMgYXZhaWxhYmxlIHRvIHRlYWNoZXJzIGJlZm9yZSBpdCBpcyB0b28KbGF0ZSB0byBpbnRlcnZlbmUuCgojIyMjIFsqKllvdXIgVHVybioqXXtzdHlsZT0iY29sb3I6IGdyZWVuOyJ9ICoq4qS1KioKCkNvbXBsZXRlIHRoZSBjb2RlIGNodW5rIGJlbG93IHRvICJzZWxlY3QiIChoaW50LCBoaW50KQpgcHJvcG9ydGlvbmVkX2Vhcm5lZGAgZm9yIG91ciBvdXRjb21lIHZhcmlhYmxlLCBhcyB3ZWxsIGFzIGFueSB2YXJpYWJsZXMKZnJvbSBvdXIgYGRhdGFfdG9fbW9kZWxgIGRhdGEgZnJhbWUgdGhhdCB0ZWFjaGVycyB3b3VsZCBoYXZlIGFjY2VzcyB0bwphdCB0aGUgc3RhcnQgb2YgdGhlIGNvdXJzZSAoZS5nLiBzdXJ2ZXkgZGF0YSwgY291cnNlIGluZm8sIHN0dWRlbnQgaWQKYW5kIGRlbW9ncmFwaGljcywgZXRjLikuIFNhdmUgYXMgYSBuZXcgZGF0YSBmcmFtZSBuYW1lZCBgY291cnNlX2RhdGFgCmFuZCBpbnNwZWN0IHlvdXIgZGF0YS4KCmBgYHtyfQpjb3Vyc2VfZGF0YSA8LSBkYXRhX3RvX21vZGVsICU+JQogIHNlbGVjdChzdHVkZW50X2lkLCBwcm9wb3J0aW9uX2Vhcm5lZCwgc3ViamVjdCwgc2VtZXN0ZXIsIAogICAgICAgICBzZWN0aW9uLCBlbnJvbGxtZW50X3JlYXNvbiwgZ2VuZGVyLCBpbnQsIHZhbCwgcGVyY29tcCwgCiAgICAgICAgIHExOnExMCkKCmBgYAoKIyMjICoqYi4gUHJlcHJvY2VzcyBEYXRhKioKClByaW9yIHRvIGRldmVsb3BpbmcgYSBwcmVkaWN0aXZlIG1vZGVsIGZyb20gb3VyIGRhdGEsIHdlIGhhdmUgYSBsaXR0bGUKYml0IG9mIGRhdGEgcHJvY2Vzc2luZyB0byBkbywgYWZ0ZXIgd2hpY2ggd2Ugd2lsbCBkaXZpZGUsIG9yICJzcGxpdCwiCm91ciBkYXRhIHNldCBpbnRvIHR3byBzZXBhcmF0ZSBkYXRhIGZyYW1lcywgb25lIGZvciB0cmFpbmluZyBvdXIKcHJlZGljdGl2ZSBtb2RlbCBhbmQgb25lIGZvciB0ZXN0aW5nIG91ciBtb2RlbCB0byBzZWUgaG93IHdlbGwgaXQKcGVyZm9ybXMuCgojIyMjIENyZWF0ZSBPdXRjb21lIFZhcmlhYmxlCgpGaXJzdCwgaG93ZXZlciwgd2UgbmVlZCBhbiBvdXRjb21lIHZhcmlhYmxlIHRoYXQgbGV0J3MgdXMga25vdyBpZiB0aGV5CmhhdmUgZWl0aGVyIHBhc3NlZCBvciBmYWlsZWQuCgpMZXQncyBjb21iaW5lIHRoZSBob3BlZnVsbHkgZmFtaWxpYXIgYG11dGF0ZSgpYCBmdW5jdGlvbiB3aXRoIHRoZQpgaWZfZWxzZSgpYCBmdW5jdGlvbiBhbHNvIGZyb20gdGhlIHtkcGx5cn0gcGFja2FnZSB0byBjcmVhdGUgYSBuZXcKdmFyaWFibGUgY2FsbGVkIGBhdF9yaXNrYCB3aGljaCBpbmRpY2F0ZXMgIm5vIiBmb3Igc3R1ZGVudHMgd2hvc2UKYHByb3BvcnRpb25lZF9lYXJuZWRgIGlzIGdyZWF0ZXIgdGhhbiBvciBlcXVhbCB0b28gNjYuNyUgKE5DIFN0YXRlJ3MKY3V0b2ZmIGZvciBhIEQtKSBhbmQgInllcyIgaWYgaXQgaXMgbm90LgoKYGBge3J9CmNvdXJzZV9kYXRhIDwtIGNvdXJzZV9kYXRhICU+JSAKICBtdXRhdGUoYXRfcmlzayA9IGlmX2Vsc2UocHJvcG9ydGlvbl9lYXJuZWQgPj0gLjY2NywgIm5vIiwgInllcyIpKSAKYGBgCgojIyMjIERyb3AgRGF0YQoKQXMgeW91IG1heSBoYXZlIG5vdGljZWQgZnJvbSB5b3VyIGluc3BlY3Rpb24gb2YgdGhlIGRhdGEgZWFybGllciwgc29tZQpzdHVkZW50cyBkbyBub3QgaGF2ZSB0aGUgY291cnNlIGdyYWRlIGRhdGEgYXQgYWxsLiBMZXQncyB1c2UgdGhlCmBkcm9wX25hKClgIGZ1bmN0aW9uIGFsc28gZnJvbSB7ZHBseXJ9IHRvIHJlbW92ZSBvYnNlcnZhdGlvbnMgd2l0aCBubwpncmFkZSBkYXRhIGFuZCB0aGUgc2VsZWN0IGZ1bmN0aW9uIGFnYWluIHRvIHJlbW92ZSB0aGUKYHByb3BvcnRpb25fZWFybmVkYCB2YXJpYWJsZSBlbnRpcmVseSBzbyB3ZSdyZSBub3QgdGVtcHRlZCB0byBjaGVhdCBhbmQKdXNlIHRoaXMgZm9yIHdoYXQgd291bGQgYmUgYSBWRVJZIGdvb2QgcHJlZGljdG9yIG9mIGJlaW5nICJhdCByaXNrIjoKCmBgYHtyfQpjb3Vyc2VfZGF0YSA8LSBjb3Vyc2VfZGF0YSAlPiUgCiAgZHJvcF9uYShwcm9wb3J0aW9uX2Vhcm5lZCkgJT4lCiAgc2VsZWN0KC1wcm9wb3J0aW9uX2Vhcm5lZCkKYGBgCgpMZXQncyBhbHNvIHVzZSB0aGUgYG5hLm9taXQoKWAgZnVuY3Rpb24gaW50cm9kdWNlZCBwcmV2aW91c2x5IHRvIGRlbGV0ZQpjYXNlcyBsaXN0d2lzZSAodGhhdCBpcywgYW4gZW50aXJlIHJvdyBpcyBkZWxldGVkIGlmIGFueSBzaW5nbGUgdmFsdWUgaXMKbWlzc2luZyksIHNpbmNlIG1vc3QgbW9kZWxzIGRvZXMgbm90IGFjY2VwdCBjYXNlcyB3aXRoIG1pc3NpbmcgZGF0YToKCmBgYHtyfQpjb3Vyc2VfZGF0YSA8LSBjb3Vyc2VfZGF0YSAlPiUKICBuYS5vbWl0KCkKYGBgCgojIyMjIENvbnZlcnQgQ2hhcmFjdGVycyB0byBGYWN0b3JzCgpXaGlsZSBpbnNwZWN0aW5nIHRoZSBkYXRhLCB5b3UgbWF5IGhhdmUgbm90aWNlZCB0aGF0IG1hbnkgb2Ygb3VyCnByZWRpY3RvcnMgb2YgaW50ZXJlc3QgYXJlIGNoYXJhY3RlciB2YXJpYWJsZXMuIEZvciBjcmVhdGluZyBtb2RlbHMsIGl0CmlzIGJldHRlciB0byBoYXZlIHF1YWxpdGF0aXZlIHZhcmlhYmxlIGxpa2UgYGdlbmRlcmAgZW5jb2RlZCBhcyBmYWN0b3JzCmluc3RlYWQgb2YgY2hhcmFjdGVyIHN0cmluZ3MuIEZhY3RvcnMgc3RvcmUgZGF0YSBhcyBjYXRlZ29yaWNhbAp2YXJpYWJsZXMsIGVhY2ggd2l0aCBpdHMgb3duIGxldmVscy4gQmVjYXVzZSBjYXRlZ29yaWNhbCB2YXJpYWJsZXMgYXJlCnVzZWQgaW4gc3RhdGlzdGljYWwgbW9kZWxzIGRpZmZlcmVudGx5IHRoYW4gY29udGludW91cyB2YXJpYWJsZXMsCnN0b3JpbmcgZGF0YSBhcyBmYWN0b3JzIGVuc3VyZXMgdGhhdCB0aGUgbW9kZWxpbmcgZnVuY3Rpb25zIHdpbGwgdHJlYXQKdGhlbSBjb3JyZWN0bHkuCgpUbyBkbyBzbywgd2UgaW50cm9kdWNlIGEgdmFyaWF0aW9uIG9mIHRoZSBgbXV0YXRlKClgIGZ1bmN0aW9uIHRoYXQgd2lsbApsb29rIGZvciBhbnkgdmFyaWFibGUgdGhhdCBpcyBhIGNoYXJhY3RlciBhbmQgcmVjb2RlIGFzIGEgZmFjdG9yOgoKYGBge3J9CmNvdXJzZV9kYXRhIDwtIGNvdXJzZV9kYXRhICU+JQogIG11dGF0ZV9pZihpcy5jaGFyYWN0ZXIsIGFzLmZhY3RvcikKCmNvdXJzZV9kYXRhCmBgYAoKIyMjIyBbKipZb3VyIFR1cm4qKl17c3R5bGU9ImNvbG9yOiBncmVlbjsifSAqKuKktSoqCgpUbyByZWR1Y2UgYWxsIHRoZSByZWR1bmRhbmN5IGNhdXNlZCBieSBkZW1vbnN0cmF0aW5nIGVhY2ggc3RlcApzZXBhcmF0ZWx5IGFib3ZlLCBjb21wbGV0ZSB0aGUgZm9sbG93aW5nIGNvZGUgYmVsb3cgYnkgdXNpbmcgdGhlIHBpcGUKb3BlcmF0b3IgdG8gcmVwZWF0IGFsbCBvZiB0aGVzZSBzdGVwcyBzdGFydGluZyBmcm9tIGlzb2xhdGluZyBwcmVkaWN0b3JzCnRvIGRyb3BwaW5nIGVtcHR5IHZhbHVlcyBmcm9tIGBwcm9wb3J0aW9uX2Vhcm5lZGAgYW5kIHJlbW92aW5nIGFsbAppbmNvbXBsZXRlIGNhc2VzLgoKYGBge3J9CmNvdXJzZV9kYXRhIDwtIGRhdGFfdG9fbW9kZWwgJT4lCiAgc2VsZWN0KHN0dWRlbnRfaWQsIHByb3BvcnRpb25fZWFybmVkLCBzdWJqZWN0LCBzZW1lc3RlciwgCiAgICAgICAgIHNlY3Rpb24sIGVucm9sbG1lbnRfcmVhc29uLCBnZW5kZXIsIGludCwgdmFsLCBwZXJjb21wLCAKICAgICAgICAgcTE6cTEwKSAlPiUKICBtdXRhdGUoYXRfcmlzayA9IGlmX2Vsc2UocHJvcG9ydGlvbl9lYXJuZWQgPj0gLjY2NywgIm5vIiwgInllcyIpKSAlPiUgCiAgZHJvcF9uYShwcm9wb3J0aW9uX2Vhcm5lZCkgJT4lCiAgc2VsZWN0KC1wcm9wb3J0aW9uX2Vhcm5lZCkgJT4lIAogIG5hLm9taXQoKSAlPiUKICBtdXRhdGVfaWYoaXMuY2hhcmFjdGVyLCBhcy5mYWN0b3IpCmBgYAoKTm93IGluc3BlY3QgeW91ciBkYXRhIGFnYWluIGFuZCBtYWtlIHN1cmUgaXQgbG9va3MgbGlrZSBleHBlY3RlZDoKCmBgYHtyfQpjb3Vyc2VfZGF0YQpgYGAKCioqSGludDoqKiBZb3Ugc2hvdWxkIHNlZSBhIHRvdGFsIG9mIDUzMiBvYnNlcnZhdGlvbnMgYW5kIDIwIHZhcmlhYmxlcwppbmNsdWRpbmcgMSBvdXRjb21lIHZhcmlhYmxlIG5hbWVkIGBhdF9yaXNrYCwgMSBzdHVkZW50IGlkZW50aWZpZXIgbmFtZWQKYHN0dWRlbnRfaWRgLCBhbmQgMTggcG90ZW50aWFsIHByZWRpY3RvcnMuCgojIyMjIENvdW50IHByb3BvcnRpb25zCgpPbmUgbGFzdCBzdGVwIGJlZm9yZSBzcGxpdHRpbmcgb3VyIGRhdGEgaXMgdG8gdGFrZSBhIHF1aWNrIGxvb2sgYXQgdGhlCnByb3BvcnRpb24gb2Ygc3R1ZGVudHMgaW4gb3VyIGZpbmFsIGRhdGEgc2V0IHRoYXQgd2VyZSBpZGVudGlmaWVkIGFzCmF0LXJpc2suCgpUbyBkbyBzbywgZmlyc3QgbGV0J3MgdGFrZSBhIGNvdW50IG9mIHRoZSBudW1iZXIgb2Ygc3R1ZGVudHMgbGFiZWxlZAoieWVzIiBvciAibm8iIG9mIGJlaW5nIGBhdF9yaXNrYCwgYW5kIHRoZW4gY3JlYXRlIGEgbmV3IHZhcmlhYmxlIGNhbGxlZApwcm9wb3J0aW9uIHRoYXQgdGFrZXMgdGhlIG51bWJlciBgbmAgb2YgZWFjaCBhbmQgZGl2aWRlcyBieSB0aGUgdG90YWwKbnVtYmVyOgoKYGBge3J9CmNvdXJzZV9kYXRhICU+JQogIGNvdW50KGF0X3Jpc2spICU+JQogIG11dGF0ZShwcm9wb3J0aW9uID0gbi9zdW0obikpCmBgYAoKQXMgeW91IGNhbiBzZWUsIHJvdWdobHkgMTclIG9mIHN0dWRlbnRzIGluIG91ciBkYXRhIHNldCB3ZXJlIGlkZW50aWZpZWQKYXMgInllcyIgZm9yIGJlaW5nIGF0IHJpc2suCgojIyMgYy4gU3BsaXQgRGF0YQoKSXQgaXMgY29tbW9uIHdoZW4gYmVnaW5uaW5nIGEgbW9kZWxpbmcgcHJvamVjdCB0byBbc2VwYXJhdGUgdGhlIGRhdGEKc2V0XShodHRwczovL2Jvb2tkb3duLm9yZy9tYXgvRkVTL2RhdGEtc3BsaXR0aW5nLmh0bWwpIGludG8gdHdvCnBhcnRpdGlvbnM6CgotICAgVGhlICp0cmFpbmluZyBzZXQqIGlzIHVzZWQgdG8gZXN0aW1hdGUgZGV2ZWxvcCBhbmQgY29tcGFyZSBtb2RlbHMsCiAgICBmZWF0dXJlIGVuZ2luZWVyaW5nIHRlY2huaXF1ZXMsIHR1bmUgbW9kZWxzLCBldGMuCgotICAgVGhlICp0ZXN0IHNldCogaXMgaGVsZCBpbiByZXNlcnZlIHVudGlsIHRoZSBlbmQgb2YgdGhlIHByb2plY3QsIGF0CiAgICB3aGljaCBwb2ludCB0aGVyZSBzaG91bGQgb25seSBiZSBvbmUgb3IgdHdvIG1vZGVscyB1bmRlciBzZXJpb3VzCiAgICBjb25zaWRlcmF0aW9uLiBJdCBpcyB1c2VkIGFzIGFuIHVuYmlhc2VkIHNvdXJjZSBmb3IgbWVhc3VyaW5nIGZpbmFsCiAgICBtb2RlbCBwZXJmb3JtYW5jZS4KClRoZXJlIGFyZSBkaWZmZXJlbnQgd2F5cyB0byBjcmVhdGUgdGhlc2UgcGFydGl0aW9ucyBvZiB0aGUgZGF0YSBhbmQKdGhlcmUgaXMgbm8gdW5pZm9ybSBndWlkZWxpbmUgZm9yIGRldGVybWluaW5nIGhvdyBtdWNoIGRhdGEgc2hvdWxkIGJlCnNldCBhc2lkZSBmb3IgdGVzdGluZy4gVGhlIHByb3BvcnRpb24gb2YgZGF0YSBjYW4gYmUgZHJpdmVuIGJ5IG1hbnkKZmFjdG9ycywgaW5jbHVkaW5nIHRoZSBzaXplIG9mIHRoZSBvcmlnaW5hbCBwb29sIG9mIHNhbXBsZXMgYW5kIHRoZQp0b3RhbCBudW1iZXIgb2YgcHJlZGljdG9ycy7CoAoKQWZ0ZXIgeW91IGRlY2lkZSBob3cgbXVjaCB0byBzZXQgYXNpZGUsIHRoZSBtb3N0IGNvbW1vbiBhcHByb2FjaCBmb3IKYWN0dWFsbHkgcGFydGl0aW9uaW5nIHlvdXIgZGF0YSBpcyB0byB1c2UgYSByYW5kb20gc2FtcGxlLiBGb3Igb3VyCnB1cnBvc2VzLCB3ZSdsbCB1c2UgcmFuZG9tIHNhbXBsaW5nIHRvIHNlbGVjdCAyNSUgZm9yIHRoZSB0ZXN0IHNldCBhbmQKdXNlIHRoZSByZW1haW5kZXIgZm9yIHRoZSB0cmFpbmluZyBzZXQsIHdoaWNoIGFyZSB0aGUgZGVmYXVsdHMgZm9yIHRoZQp7W3JzYW1wbGVdKGh0dHBzOi8vdGlkeW1vZGVscy5naXRodWIuaW8vcnNhbXBsZS8pfSBwYWNrYWdlLgoKQWRkaXRpb25hbGx5LCBzaW5jZSByYW5kb20gc2FtcGxpbmcgdXNlcyByYW5kb20gbnVtYmVycywgaXQgaXMgaW1wb3J0YW50CnRvIHNldCB0aGUgcmFuZG9tIG51bWJlciBzZWVkLiBUaGlzIGVuc3VyZXMgdGhhdCB0aGUgcmFuZG9tIG51bWJlcnMgY2FuCmJlIHJlcHJvZHVjZWQgYXQgYSBsYXRlciB0aW1lIChpZiBuZWVkZWQpLgoKVGhlIGZ1bmN0aW9uIGBpbml0aWFsX3NwbGl0KClgIGZ1bmN0aW9uIGZyb20gdGhlIHtyc2FtcGxlfSBwYWNrYWdlIHRha2VzCnRoZSBvcmlnaW5hbCBkYXRhIGFuZCBzYXZlcyB0aGUgaW5mb3JtYXRpb24gb24gaG93IHRvIG1ha2UgdGhlCnBhcnRpdGlvbnMuCgpSdW4gdGhlIGZvbGxvd2luZyBjb2RlIHRvIHNldCB0aGUgcmFuZG9tIG51bWJlciBzZWVkIGFuZCBtYWtlIG91cgppbml0aWFsIGRhdGEgc3BsaXQ6CgpgYGB7cn0Kc2V0LnNlZWQoNTg2KQoKY291cnNlX3NwbGl0IDwtIGluaXRpYWxfc3BsaXQoY291cnNlX2RhdGEsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdHJhdGEgPSBhdF9yaXNrKQpgYGAKCk5vdGUgdGhhdCB3ZSB1c2VkIHRoZSBgc3RyYXRhID1gIGFyZ3VtZW50LCB3aGljaCBjb25kdWN0cyBhIHN0cmF0aWZpZWQKc3BsaXQuIFRoaXMgZW5zdXJlcyB0aGF0LCBkZXNwaXRlIHRoZSBpbWJhbGFuY2Ugd2Ugbm90aWNlZCBpbiBvdXIKYGF0X3Jpc2tgIHZhcmlhYmxlLCBvdXIgdHJhaW5pbmcgYW5kIHRlc3QgZGF0YSBzZXRzIHdpbGwga2VlcCByb3VnaGx5CnRoZSBzYW1lIHByb3BvcnRpb25zIG9mIGF0LXJpc2sgc3R1ZGVudHMgYXMgaW4gdGhlIG9yaWdpbmFsIGRhdGEuwqAKCiMjIyMgWyoqWW91ciBUdXJuKipde3N0eWxlPSJjb2xvcjogZ3JlZW47In0gKiripLUqKgoKVHlwZSBgY291cnNlX3NwbGl0YCBpbnRvIHRoZSBjb2RlIGNodW5rIGJlbG93LCBydW4sIGFuZCBhbnN3ZXIgdGhlCnF1ZXN0aW9uIHRoYXQgZm9sbG93cz8KCmBgYHtyfQpjb3Vyc2Vfc3BsaXQKYGBgCgoxLiAgSG93IG1hbnkgb2JzZXJ2YXRpb25zIHNob3VsZCB3ZSBleHBlY3QgdG8gc2VlIGluIG91ciB0cmFpbmluZyBhbmQKICAgIHRlc3Qgc2V0cyByZXNwZWN0aXZlbHk/CgogICAgLSAgIDM5OSBmb3IgdHJhaW5pbmcsIDEzNCBmb3IgdGVzdAoKIyMjIyBDcmVhdGUgYSB0cmFpbmluZyBhbmQgdGVzdCBzZXQKClRoZSB7cnNhbXBsZX0gcGFja2FnZSBoYXMgdHdvIGFwdGx5IG5hbWVkIGZ1bmN0aW9ucyBmb3IgY3JlYXRlZCBhCnRyYWluaW5nIGFuZCB0ZXN0aW5nIGRhdGEgc2V0IGNhbGxlZCBgdHJhaW5pbmcoKWAgYW5kIGB0ZXN0aW5nKClgCnJlc3BlY3RpdmVseS4KClJ1biB0aGUgZm9sbG93aW5nIGNvZGUgdG8gc3BsaXQgdGhlIGRhdGEgaW50bwoKYGBge3J9CnRyYWluX2RhdGEgPC0gdHJhaW5pbmcoY291cnNlX3NwbGl0KQoKdGVzdF9kYXRhICA8LSB0ZXN0aW5nKGNvdXJzZV9zcGxpdCkKYGBgCgojIyMjIFsqKllvdXIgVHVybioqXXtzdHlsZT0iY29sb3I6IGdyZWVuOyJ9ICoq4qS1KioKCkZpcnN0IHRha2UgYSBsb29rIGF0IHRoZSB0cmFpbmluZyBhbmQgdGVzdGluZyBzZXRzIHdlIGp1c3QgY3JlYXRlZDoKCmBgYHtyfQp0cmFpbl9kYXRhCnRlc3RfZGF0YQpgYGAKCk5leHQsIHJlY3ljbGUgdGhlIGNvZGUgZnJvbSBhYm92ZSB0byBjaGVjayB0byBzZWUgdGhhdCB0aGUgcHJvcG9ydGlvbiBvZgphdC1yaXNrIHN0dWRlbnRzIGluIG91ciB0cmFpbmluZyBhbmQgdGVzdCBkYXRhIGFyZSBjbG9zZSB0byB0aG9zZSBpbiBvdXIKb3ZlcmFsbCBgY291cnNlX2RhdGFgOgoKYGBge3J9CnRyYWluX2RhdGEgJT4lCiAgY291bnQoYXRfcmlzaykgJT4lCiAgbXV0YXRlKHByb3BvcnRpb24gPSBuL3N1bShuKSkKdGVzdF9kYXRhICU+JQogIGNvdW50KGF0X3Jpc2spICU+JQogIG11dGF0ZShwcm9wb3J0aW9uID0gbi9zdW0obikpCmBgYAoKTm93IGFuc3dlciB0aGUgZm9sbG93aW5nIHF1ZXN0aW9uczoKCjEuICBEbyB0aGUgbnVtYmVyIG9mIG9ic2VydmF0aW9ucyBpbiBlYWNoIHNldCBtYXRjaCB5b3VyIGV4cGVjdGF0aW9ucz8KICAgIFdoeT8KCiAgICAtICAgMzk5IGZvciB0cmFpbiBhbmQgMTM0IGZvciB0ZXN0OyB5ZXMsIHRoZXkgbWF0Y2ggdGhlIHNwbGl0cyB3ZQogICAgICAgIHNldCB1cCBlYXJsaWVyCgoyLiAgRG8gdGhlIHByb3BvcnRpb24gb2YgYXQtcmlzayBzdHVkZW50cyBpbiBlYWNoIHNldCBtYXRjaCB5b3VyCiAgICBleHBlY3RhdGlvbnM/IFdoeT8KCiAgICAtICAgeWVzLCB0aGUgcHJvcG9ydGlvbnMgbWF0Y2ggYmVjYXVzZSB3ZSBzdHJhdGlmaWVkIHRoZSByZXN1bHRzIHRvCiAgICAgICAgbWF0Y2ggdGhlIGF0X3Jpc2sgcmVzdWx0cyBpbiB0aGUgY291cnNlX2RhdGEgKGRvbmUgZHVyaW5nCiAgICAgICAgb3JpZ2luYWwgZGF0YSBzZXQgc3BsaXQpCgojIyAzLiAqKkNSRUFURSBBIFJFQ0lQRSoqCgpJbiB0aGlzIHNlY3Rpb24sIHdlIGludHJvZHVjZSBhbm90aGVyIHRpZHltb2RlbHMgcGFja2FnZSwKW3JlY2lwZXNdKGh0dHBzOi8vcmVjaXBlcy50aWR5bW9kZWxzLm9yZy8pLCB3aGljaCBpcyBkZXNpZ25lZCB0byBoZWxwCnlvdSBwcmVwYXJlIHlvdXIgZGF0YSAqYmVmb3JlKiB0cmFpbmluZyB5b3VyIG1vZGVsLiBSZWNpcGVzIGFyZSBidWlsdCBhcwphIHNlcmllcyBvZiBwcmVwcm9jZXNzaW5nIHN0ZXBzLCBzdWNoIGFzOgoKLSAgIGNvbnZlcnRpbmcgcXVhbGl0YXRpdmUgcHJlZGljdG9ycyB0byBpbmRpY2F0b3IgdmFyaWFibGVzIChhbHNvIGtub3duCiAgICBhcyBkdW1teSB2YXJpYWJsZXMpLAoKLSAgIHRyYW5zZm9ybWluZyBkYXRhIHRvIGJlIG9uIGEgZGlmZmVyZW50IHNjYWxlIChlLmcuLCB0YWtpbmcgdGhlCiAgICBsb2dhcml0aG0gb2YgYSB2YXJpYWJsZSksCgotICAgdHJhbnNmb3JtaW5nIHdob2xlIGdyb3VwcyBvZiBwcmVkaWN0b3JzIHRvZ2V0aGVyLAoKLSAgIGV4dHJhY3Rpbmcga2V5IGZlYXR1cmVzIGZyb20gcmF3IHZhcmlhYmxlcyAoZS5nLiwgZ2V0dGluZyB0aGUgZGF5IG9mCiAgICB0aGUgd2VlayBvdXQgb2YgYSBkYXRlIHZhcmlhYmxlKSwKCmFuZCBzbyBvbi4gSWYgeW91IGFyZSBmYW1pbGlhciB3aXRoIFIncyBmb3JtdWxhIGludGVyZmFjZSwgYSBsb3Qgb2YgdGhpcwptaWdodCBzb3VuZCBmYW1pbGlhciBhbmQgbGlrZSB3aGF0IGEgZm9ybXVsYSBhbHJlYWR5IGRvZXMuIFJlY2lwZXMgY2FuCmJlIHVzZWQgdG8gZG8gbWFueSBvZiB0aGUgc2FtZSB0aGluZ3MsIGJ1dCB0aGV5IGhhdmUgYSBtdWNoIHdpZGVyIHJhbmdlCm9mIHBvc3NpYmlsaXRpZXMuCgojIyMjIEFkZCBhIGZvcm11bGEKClRvIGdldCBzdGFydGVkLCBsZXQncyBjcmVhdGUgYSByZWNpcGUgZm9yIGEgc2ltcGxlIGxvZ2lzdGljIHJlZ3Jlc3Npb24KbW9kZWwuIEJlZm9yZSB0cmFpbmluZyB0aGUgbW9kZWwsIHdlIGNhbiB1c2UgYSByZWNpcGUgdG8gY3JlYXRlIGEgZmV3Cm5ldyBwcmVkaWN0b3JzIGFuZCBjb25kdWN0IHNvbWUgcHJlcHJvY2Vzc2luZyByZXF1aXJlZCBieSB0aGUgbW9kZWwuCgpUaGXCoFtgcmVjaXBlKClgwqBmdW5jdGlvbl0oaHR0cHM6Ly9yZWNpcGVzLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9yZWNpcGUuaHRtbCnCoGFzCndlIHVzZWQgaXQgaGVyZSBoYXMgdHdvIGFyZ3VtZW50czoKCi0gICBBwqAqKmZvcm11bGEqKi4gQW55IHZhcmlhYmxlIG9uIHRoZSBsZWZ0LWhhbmQgc2lkZSBvZiB0aGUgdGlsZGUgKGB+YCkKICAgIGlzIGNvbnNpZGVyZWQgdGhlIG1vZGVsIG91dGNvbWUgKGBhdF9yaXNrYCBpbiBvdXIgY2FzZSkuIE9uIHRoZQogICAgcmlnaHQtaGFuZCBzaWRlIG9mIHRoZSB0aWxkZSBhcmUgdGhlIHByZWRpY3RvcnMuIFZhcmlhYmxlcyBtYXkgYmUKICAgIGxpc3RlZCBieSBuYW1lLCBvciB5b3UgY2FuIHVzZSB0aGUgZG90IChgLmApIHRvIGluZGljYXRlIGFsbCBvdGhlcgogICAgdmFyaWFibGVzIGFzIHByZWRpY3RvcnMuCgotICAgVGhlICoqZGF0YSoqLiBBIHJlY2lwZSBpcyBhc3NvY2lhdGVkIHdpdGggdGhlIGRhdGEgc2V0IHVzZWQgdG8KICAgIGNyZWF0ZSB0aGUgbW9kZWwuIFRoaXMgd2lsbCB0eXBpY2FsbHkgYmUgdGhlwqAqdHJhaW5pbmcqwqBzZXQsCiAgICBzb8KgYGRhdGEgPSB0cmFpbl9kYXRhYMKgaGVyZS4gTmFtaW5nIGEgZGF0YSBzZXQgZG9lc24ndCBhY3R1YWxseQogICAgY2hhbmdlIHRoZSBkYXRhIGl0c2VsZjsgaXQgaXMgb25seSB1c2VkIHRvIGNhdGFsb2cgdGhlIG5hbWVzIG9mIHRoZQogICAgdmFyaWFibGVzIGFuZCB0aGVpciB0eXBlcywgbGlrZSBmYWN0b3JzLCBpbnRlZ2VycywgZGF0ZXMsIGV0Yy4KCkxldCdzIGNyZWF0ZSBvdXIgdmVyeSBmaXJzdCByZWNpcGUgdXNpbmcgYGF0X3Jpc2tgIGFzIG91ciBvdXRjb21lCnZhcmlhYmxlOyBgaW50YCBhbmQgYGdlbmRlcmAgYW5kIGBlbnJvbGxtZW50X3JlYXNvbmAgYXMgcHJlZGljdG9yczsgYW5kCmB0cmFpbl9kYXRhYCBhcyBvdXIgZGF0YSB0byB0cmFpbjoKCmBgYHtyfQpscl9yZWNpcGVfMSA8LSByZWNpcGUoYXRfcmlzayB+IGludCArIGdlbmRlciArIGVucm9sbG1lbnRfcmVhc29uLAogICAgICAgICAgICAgICAgICAgICAgZGF0YSA9IHRyYWluX2RhdGEpCmBgYAoKTm93IGxldCdzIHRha2UgYSBxdWljayBwZWVrIGF0IG91ciByZWNpcGUgYW5kIGNyZWF0ZSBhIHF1aWNrIHN1bW1hcnkgb2YKb3VyIHJlY2lwZSB1c2luZyB0aGUgYHN1bW1hcnkoKWAgZnVuY3Rpb246CgpgYGB7cn0KbHJfcmVjaXBlXzEKCnN1bW1hcnkobHJfcmVjaXBlXzEpCmBgYAoKWW91IGNhbiBzZWUgdGhhdCBvdXIgcmVjaXBlIGhhcyB0aHJlZSBwcmVkaWN0b3JzIGFuZCAxIG91dGNvbWUsIGp1c3QgYXMKZXhwZWN0ZWQuCgojIyMjIENyZWF0ZSBEdW1teSBWYXJpYWJsZXMKCkJlY2F1c2Ugd2UnbGwgYmUgdXNpbmcgYSBzaW1wbGUgbG9naXN0aWMgcmVncmVzc2lvbiBtb2RlbCwgdmFyaWFibGVzCmxpa2UgYGdlbmRlcmAgYW5kIGBlbnJvbGxtZW50X3JlYXNvbmAgd2lsbCBuZWVkIHRvIGJlIGNvZGVkIGFzIFtkdW1teQp2YXJpYWJsZXNdKGh0dHBzOi8vYm9va2Rvd24ub3JnL21heC9GRVMvY3JlYXRpbmctZHVtbXktdmFyaWFibGVzLWZvci11bm9yZGVyZWQtY2F0ZWdvcmllcy5odG1sKS4KRHVtbXkgY29kaW5nIG1lYW5zIHRyYW5zZm9ybWluZyBhIHZhcmlhYmxlIHdpdGggbXVsdGlwbGUgY2F0ZWdvcmllcyBpbnRvCm5ldyB2YXJpYWJsZXMsIHdoZXJlIGVhY2ggYmluYXJ5IHZhcmlhYmxlIGluZGljYXRlcyB0aGUgcHJlc2VuY2UgYW5kCmFic2VuY2Ugb2YgZWFjaCBjYXRlZ29yeS4gRm9yIGV4YW1wbGUsIGdlbmRlciB3aWxsIGJlIHJlY29kZWQgdG8KZ2VuZGVyX2YsIHdoZXJlIDEgaW5kaWNhdGVzIGZlbWFsZSBhbmQgMCBpbmRpY2F0ZXMgbWFsZS4KClVubGlrZSB0aGUgc3RhbmRhcmQgbW9kZWwgZm9ybXVsYSBtZXRob2RzIGluIFIsIGEgcmVjaXBlICoqZG9lcyBub3QqKgphdXRvbWF0aWNhbGx5IGNyZWF0ZSB0aGVzZSBkdW1teSB2YXJpYWJsZXMgZm9yIHlvdTsgeW91J2xsIG5lZWQgdG8gdGVsbAp5b3VyIHJlY2lwZSB0byBhZGQgdGhpcyBzdGVwLiBUaGlzIGlzIGZvciB0d28gcmVhc29ucy4gRmlyc3QsIG1hbnkKbW9kZWxzIGRvIG5vdCByZXF1aXJlwqBbbnVtZXJpYwpwcmVkaWN0b3JzXShodHRwczovL2Jvb2tkb3duLm9yZy9tYXgvRkVTL2NhdGVnb3JpY2FsLXRyZWVzLmh0bWwpLCBzbwpkdW1teSB2YXJpYWJsZXMgbWF5IG5vdCBhbHdheXMgYmUgcHJlZmVycmVkLiBTZWNvbmQsIHJlY2lwZXMgY2FuIGFsc28gYmUKdXNlZCBmb3IgcHVycG9zZXMgb3V0c2lkZSBvZiBtb2RlbGluZywgd2hlcmUgbm9uLWR1bW15IHZlcnNpb25zIG9mIHRoZQp2YXJpYWJsZXMgbWF5IHdvcmsgYmV0dGVyLiBGb3IgZXhhbXBsZSwgeW91IG1heSB3YW50IHRvIG1ha2UgYSB0YWJsZSBvcgphIHBsb3Qgd2l0aCBhIHZhcmlhYmxlIGFzIGEgc2luZ2xlIGZhY3Rvci4KCkZvciB0aGVzZSByZWFzb25zLCB5b3UgbmVlZCB0byBleHBsaWNpdGx5IHRlbGwgcmVjaXBlcyB0byBjcmVhdGUgZHVtbXkKdmFyaWFibGVzIHVzaW5nwqBgc3RlcF9kdW1teSgpYC4gTGV0J3MgYWRkIHRoaXMgdG8gb3VyIHJlY2lwZSBhbmQgaW5jbHVkZQpgYWxsX25vbWluYWxfcHJlZGljdG9ycygpYCB0byB0ZWxsIG91ciByZWNpcGUgdG8gY2hhbmdlIGFsbCBvZiBvdXIKZmFjdG9yIHZhcmlhYmxlcyB0byBkdW1teSB2YXJpYWJsZXM6CgpgYGB7cn0KbHJfcmVjaXBlXzEgPC0gCiAgcmVjaXBlKGF0X3Jpc2sgfiBpbnQgKyBnZW5kZXIgKyBlbnJvbGxtZW50X3JlYXNvbiwKICAgICAgICAgICAgICAgICAgICAgIGRhdGEgPSB0cmFpbl9kYXRhKSAlPiUKICBzdGVwX2R1bW15KGFsbF9ub21pbmFsX3ByZWRpY3RvcnMoKSkKCmxyX3JlY2lwZV8xCgpzdW1tYXJ5KGxyX3JlY2lwZV8xKQpgYGAKCiMjIyMgQ3JlYXRlZCBhIGtpdGNoZW4gc2luayByZWNpcGUKCkJlZm9yZSB0cmFpbmluZyBvdXIgbW9kZWwsIGxldCdzIGNyZWF0ZSBhIHNlY29uZCByZWNpcGUganVzdCBmb3IKY29udHJhc3QgdGhhdCBpbmNsdWRlcyBhbGwgb2Ygb3VyIHByZWRpY3RvcnMgYnkgYWRkaW5nIHRoZSBgLmAgYWZ0ZXIgdGhlCnRpbGRlIGFzIG5vdGVkIGFib3ZlLgoKSW4gYWRkaXRpb24gdG8gb3VyIHN0ZXAgZm9yIGNyZWF0aW5nIGR1bW15IHZhcmlhYmxlcywgd2UnbGwgYWxzbyBoYXZlIHRvCmluZGljYXRlIHRoYXQgb3VyIGBzdHVkZW50X2lkYCB2YXJpYWJsZSBpcyBub3QgdXNlZCBmb3IgcHJlZGljdGlvbgpwdXJwb3NlcywgYnV0IHJhdGhlciBhcyBhIHVuaXF1ZSBpZGVudGlmaWVyIGZvciBvdXIgc3R1ZGVudHMuCgpUbyBkbyBzbywgd2Ugd2lsbCBoYXZlIHRvIGFkZApbcm9sZXNdKGh0dHBzOi8vcmVjaXBlcy50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2Uvcm9sZXMuaHRtbCkgdG8gdGhpcwpyZWNpcGUgdXNpbmcKdGhlwqBbYHVwZGF0ZV9yb2xlKClgwqBmdW5jdGlvbl0oaHR0cHM6Ly9yZWNpcGVzLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9yb2xlcy5odG1sKQp0aGF0IGxldHMgcmVjaXBlcyBrbm93IHRoYXTCoGBzdHVkZW50X2lkYCBpcyBhIHZhcmlhYmxlIHdpdGggYSBjdXN0b20Kcm9sZSB0aGF0IHdlIGNhbGxlZMKgYCJJRCJgwqAoYSByb2xlIGNhbiBoYXZlIGFueSBjaGFyYWN0ZXIgdmFsdWUpLgpXaGVyZWFzIG91ciBmb3JtdWxhIGluY2x1ZGVzIGFsbCB2YXJpYWJsZXMgaW4gdGhlIHRyYWluaW5nIHNldCBvdGhlcgp0aGFuwqBgYXRfcmlza2DCoGFzIHByZWRpY3RvcnMsIHRoaXMgdGVsbHMgdGhlIHJlY2lwZSB0byBrZWVwIHRoZXNlIHR3bwp2YXJpYWJsZXMgYnV0IG5vdCB1c2UgdGhlbSBhcyBlaXRoZXIgb3V0Y29tZXMgb3IgcHJlZGljdG9ycy4KClJ1biB0aGUgZm9sbG93aW5nIGNvZGUgdG8gYWRkIGBzdHVkZW50X2lkYCBhcyBhIHJvbGUgdG8gb3VyIG1vZGVsOgoKYGBge3J9CmxyX3JlY2lwZV8yIDwtIAogIHJlY2lwZShhdF9yaXNrIH4gLiwgCiAgICAgICAgIGRhdGEgPSB0cmFpbl9kYXRhKSAlPiUKICBzdGVwX2R1bW15KGFsbF9ub21pbmFsX3ByZWRpY3RvcnMoKSkgJT4lCiAgdXBkYXRlX3JvbGUoc3R1ZGVudF9pZCwgbmV3X3JvbGUgPSAiSUQiKSAKCmxyX3JlY2lwZV8yCgpzdW1tYXJ5KGxyX3JlY2lwZV8yKQpgYGAKCiMjIDQuIE1PREVMCgojIyMgYS4gRml0IGEgTG9naXN0aWMgUmVncmVzc2lvbiBNb2RlbAoKV2l0aCB0aWR5bW9kZWxzLCB3ZSBzdGFydCBidWlsZGluZyBhIG1vZGVsIGJ5IHNwZWNpZnlpbmcgdGhlICpmdW5jdGlvbmFsCmZvcm0qIG9mIHRoZSBtb2RlbCB0aGF0IHdlIHdhbnQgdXNpbmcgdGhlIFtbcGFyc25pcF0KcGFja2FnZV0oaHR0cHM6Ly90aWR5bW9kZWxzLmdpdGh1Yi5pby9wYXJzbmlwLykuIFNpbmNlIG91ciBvdXRjb21lIGlzCmJpbmFyeSwgdGhlIG1vZGVsIHR5cGUgd2Ugd2lsbCB1c2UgaXPCoCJbbG9naXN0aWMKcmVncmVzc2lvbl0oaHR0cHM6Ly9wYXJzbmlwLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9sb2dpc3RpY19yZWcuaHRtbCkiLgpXZSBjYW4gZGVjbGFyZSB0aGlzIHdpdGggYGxvZ2lzdGljX3JlZygpYCBhbmQgYXNzaWduIHRvIGFuIG9iamVjdCB3ZQp3aWxsIGxhdGVyIHVzZSBpbiBvdXIgd29ya2Zsb3c6CgpgYGB7cn0KbHJfbW9kIDwtIGxvZ2lzdGljX3JlZygpCmBgYAoKVGhhdCBpcyBwcmV0dHkgdW5kZXJ3aGVsbWluZyBzaW5jZSwgb24gaXRzIG93biwgaXQgZG9lc24ndCByZWFsbHkgZG8KbXVjaC4gSG93ZXZlciwgbm93IHRoYXQgdGhlIHR5cGUgb2YgbW9kZWwgaGFzIGJlZW4gc3BlY2lmaWVkLCBhIG1ldGhvZApmb3LCoCpmaXR0aW5nKsKgb3IgdHJhaW5pbmcgdGhlIG1vZGVsIGNhbiBiZSBzdGF0ZWQgdXNpbmcgdGhlwqAqKmVuZ2luZSoqLgoKIyMjIyBTdGFydCB5b3VyIGVuZ2luZQoKVGhlIGVuZ2luZSB2YWx1ZSBpcyBvZnRlbiBhIG1hc2gtdXAgb2YgZGlmZmVyZW50IHBhY2thZ2VzIHRoYXQgY2FuIGJlCnVzZWQgdG8gZml0IG9yIHRyYWluIHRoZSBtb2RlbCBhcyB3ZWxsIGFzIHRoZSBlc3RpbWF0aW9uIG1ldGhvZC4gRm9yCmV4YW1wbGUsIHdlIHdpbGwgdXNlICJnbG0iIGEgZ2VuZXJhbGl6ZWQgbGluZWFyIG1vZGVsIGZvciBiaW5hcnkKb3V0Y29tZXMgYW5kIGRlZmF1bHQgZm9yIGxvZ2lzdGljIHJlZ3Jlc3Npb24gaW4gdGhlIHtwYXJzbmlwfSBwYWNrYWdlLgoKUnVuIHRoZSBmb2xsb3dpbmcgY29kZSB0byBmaW5pc2ggc3BlY2lmeWluZyBvdXIgbW9kZWw6CgpgYGB7cn0KbHJfbW9kIDwtIAogIGxvZ2lzdGljX3JlZygpICU+JSAKICBzZXRfZW5naW5lKCJnbG0iKQpgYGAKCiMjIyMgQWRkIHRvIHdvcmtmbG93CgpXZSB3aWxsIHdhbnQgdG8gdXNlIG91ciByZWNpcGVzIGNyZWF0ZWQgZWFybGllciBhY3Jvc3Mgc2V2ZXJhbCBzdGVwcyBhcwp3ZSB0cmFpbiBhbmQgdGVzdCBvdXIgbW9kZWwuIFRvIHNpbXBsaWZ5IHRoaXMgcHJvY2Vzcywgd2UgY2FuIHVzZQphwqAqbW9kZWwgd29ya2Zsb3cqLCB3aGljaCBwYWlycyBhIG1vZGVsIGFuZCByZWNpcGUgdG9nZXRoZXIuCgpUaGlzIGlzIGEgc3RyYWlnaHRmb3J3YXJkIGFwcHJvYWNoIGJlY2F1c2UgZGlmZmVyZW50IHJlY2lwZXMgYXJlIG9mdGVuCm5lZWRlZCBmb3IgZGlmZmVyZW50IG1vZGVscywgc28gd2hlbiBhIG1vZGVsIGFuZCByZWNpcGUgYXJlIGJ1bmRsZWQsIGl0CmJlY29tZXMgZWFzaWVyIHRvIHRyYWluIGFuZCB0ZXN0wqAqd29ya2Zsb3dzKi4KCldlJ2xsIHVzZSB0aGXCoHtbd29ya2Zsb3dzXShodHRwczovL3dvcmtmbG93cy50aWR5bW9kZWxzLm9yZy8pfSBwYWNrYWdlCmZyb20gdGlkeW1vZGVscyB0byBidW5kbGUgb3VyIHBhcnNuaXAgbW9kZWwgKGBscl9tb2RgKSB3aXRoIG91ciBmaXJzdApyZWNpcGUgKGBscl9yZWNpcGVfMWApLgoKYGBge3J9CmxyX3dvcmtmbG93IDwtIAogIHdvcmtmbG93KCkgJT4lIAogIGFkZF9tb2RlbChscl9tb2QpICU+JSAKICBhZGRfcmVjaXBlKGxyX3JlY2lwZV8xKQpgYGAKCiMjIyMgRml0IG1vZGVsIHRvIHRyYWluaW5nIHNldAoKTm93IHRoYXQgd2UgaGF2ZSBhIHNpbmdsZSB3b3JrZmxvdyB0aGF0IGNhbiBiZSB1c2VkIHRvIHByZXBhcmUgdGhlCnJlY2lwZSBhbmQgdHJhaW4gdGhlIG1vZGVsIGZyb20gdGhlIHJlc3VsdGluZyBwcmVkaWN0b3JzLCB3ZSBjYW4gdXNlIHRoZQpgZml0KClgIGZ1bmN0aW9uIHRvIGZpdCBvdXIgbW9kZWwgdG8gb3VyIGB0cmFpbl9kYXRhYC4gQW5kIGFnYWluLCB3ZSBzZXQKYSByYW5kb20gbnVtYmVyIHNlZWQgdG8gZW5zdXJlIHRoYXQgaWYgd2UgcnVuIHRoaXMgc2FtZSBjb2RlIGFnYWluLCB3ZQp3aWxsIGdldCB0aGUgc2FtZSByZXN1bHRzIGluIHRlcm1zIG9mIHRoZSBkYXRhIHBhcnRpdGlvbjoKCmBgYHtyfQpzZXQuc2VlZCg1ODYpCgpscl9maXQgPC0gCiAgbHJfd29ya2Zsb3cgJT4lIAogIGZpdChkYXRhID0gdHJhaW5fZGF0YSkKYGBgCgpUaGlzIGBscl9maXRgIG9iamVjdCBoYXMgdGhlIGZpbmFsaXplZCByZWNpcGUgYW5kIGZpdHRlZCBtb2RlbCBvYmplY3RzCmluc2lkZS4gVG8gZXh0cmFjdCB0aGUgbW9kZWwgZml0IGZyb20gdGhlIHdvcmtmbG93LCB3ZSB3aWxsIHVzZSB0aGUKaGVscGVyIGZ1bmN0aW9uc8KgYGV4dHJhY3RfZml0X3BhcnNuaXAoKWAuIEhlcmUgd2UgcHVsbCB0aGUgZml0dGVkIG1vZGVsCm9iamVjdCB0aGVuIHVzZSB0aGXCoGBicm9vbTo6dGlkeSgpYMKgZnVuY3Rpb24gdG8gZ2V0IGEgdGlkeSB0aWJibGUgb2YKbW9kZWwgY29lZmZpY2llbnRzOgoKYGBge3J9CmxyX2ZpdCAlPiUgCiAgZXh0cmFjdF9maXRfcGFyc25pcCgpICU+JSAKICB0aWR5KCkKYGBgCgpBbW9uZyB0aGUgcHJlZGljdG9ycyBpbmNsdWRlZCBpbiBvdXIgbW9kZWwsIGBpbnRgIG9yIGludGVyZXN0IGluIHN1YmplY3QKYXJlYSBzY29yZSwgc2VlbXMgdG8gYmUgdGhlIHNvbGUgc2lnbmlmaWNhbnQgcHJlZGljdG9yIG9mIGJlaW5nCmlkZW50aWZpZWQgYXMgYXQtcmlzayBhY2NvcmRpbmcgdG8gb3VyIGRlZmluaXRpb24uIFRoaXMgZG9lc24ndCBib2RlIHRvbwp3ZWxsIGZvciBvdXIgbW9kZWwgYnV0IGxldCdzIHByb2NlZWQgd2l0aCB0ZXN0aW5nIGFueXdheXMuCgojIyMjIFRlc3QgdGhlIG1vZGVsCgpOb3cgdGhhdCB3ZSd2ZSBmaXQgb3VyIG1vZGVsIHRvIG91ciB0cmFpbmluZyBkYXRhLCB3ZSdyZSBGSU5BTExZIHJlYWR5CnRvIHRlc3Qgb3VyIG1vZGVsIG9uIHRoZSBkYXRhIHdlIHNldCBhc2lkZSBpbiB0aGUgYmVnaW5uaW5nLiBKdXN0IHRvCnJlY2FwIHRoZSBzdGVwcyB0aGF0IGxlZCB0byB0aGlzIG1vbWVudCwgaG93ZXZlciwgcmVjYWxsIHRoYXQgd2U6CgoxLiAgQnVpbHQgdGhlIG1vZGVsIChgbHJfbW9kYCksCgoyLiAgQ3JlYXRlZCBhIHByZXByb2Nlc3NpbmcgcmVjaXBlIChgbHJfcmVjaXBlXzFgKSwKCjMuICBCdW5kbGVkIHRoZSBtb2RlbCBhbmQgcmVjaXBlIChgbHJfd29ya2Zsb3dgKSwgYW5kCgo0LiAgVHJhaW5lZCBvdXIgd29ya2Zsb3cgdXNpbmcgYSBzaW5nbGUgY2FsbCB0b8KgYGZpdCgpYC4KClRoZSBuZXh0IHN0ZXAgaXMgdG8gdXNlIHRoZSB0cmFpbmVkIHdvcmtmbG93IChgbHJfZml0YCkgdG8gcHJlZGljdApvdXRjb21lcyBmb3Igb3VyIHVuc2VlbiB0ZXN0IGRhdGEsIHdoaWNoIHdlIHdpbGwgZG8gd2l0aCB0aGUKZnVuY3Rpb27CoGBwcmVkaWN0KClgLiBUaGXCoGBwcmVkaWN0KClgwqBtZXRob2QgYXBwbGllcyB0aGUgcmVjaXBlIHRvIHRoZQpuZXcgZGF0YSwgdGhlbiBwYXNzZXMgdGhlbSB0byB0aGUgZml0dGVkIG1vZGVsLgoKYGBge3J9CgpwcmVkaWN0KGxyX2ZpdCwgdGVzdF9kYXRhKQpgYGAKCkJlY2F1c2Ugb3VyIG91dGNvbWUgdmFyaWFibGUgYGF0X3Jpc2tgIGhlcmUgaXMgYSBmYWN0b3IsIHRoZSBvdXRwdXQKZnJvbcKgYHByZWRpY3QoKWDCoHJldHVybnMgdGhlIHByZWRpY3RlZCBjbGFzczrCoGBub2DCoHZlcnN1c8KgYHllc2AuIE5vdCBhCnN1cGVyIHVzZWZ1bCBvdXRwdXQgdG8gYmUgaG9uZXN0IHRob3VnaC4KCkZvcnR1bmF0ZWx5IHRoZXJlIGlzIGFuIGBhdWdtZW50KClgIGZ1bmN0aW9uIHdlIGNhbiB1c2Ugd2l0aCBvdXIKYGxyX2ZpcmAgbW9kZWwgYW5kIGB0ZXN0X2RhdGFgIHRvIHNhdmUgdGhlbSB0b2dldGhlcjoKCkxldCdzIHVzZSB0aGlzIGZ1bmN0aW9uLCBzYXZlIGFzIGBscl9wcmVkaWN0aW9uc2A6CgpgYGB7cn0KbHJfcHJlZGljdGlvbnMgPC0gYXVnbWVudChscl9maXQsIHRlc3RfZGF0YSkKYGBgCgojIyMjIFsqKllvdXIgVHVybioqXXtzdHlsZT0iY29sb3I6IGdyZWVuOyJ9ICoq4qS1KioKClRha2UgYSBxdWljayBsb29rIGF0IGBscl9wcmVkaWN0aW9uc2AgaW4gdGhlIGNvZGUgY2h1bmsgYmVsb3cgYW5kIGFuc3dlcgp0aGUgcXVlc3Rpb24gdGhhdCBmb2xsb3dzPwoKYGBge3J9CmxyX3ByZWRpY3Rpb25zCmBgYAoKV2FzIG91ciBtb2RlbCBzdWNjZXNzZnVsIGF0IHByZWRpY3RpbmcgYW55IHN0dWRlbnRzIHdobyB3ZXJlIGF0LXJpc2s/CkhvdyBkbyB5b3Uga25vdz8KCi0gICBOby4gVmFyaWFibGUgLnByZWRfY2xhc3MgaGFkIDAgInllcyIgcmVzdWx0cy4gRXZlcnkgaW5zdGFuY2Ugd2FzCiAgICBwcmVkaWN0ZWQgYXMgIm5vLiIKCioqSGludDoqKiBTY3JvbGwgdG8gdGhlIGVuZCBvZiB0aGUgZGF0YSBmcmFtZSBhbmQgdGFrZSBhIGxvb2sgYXQgb3VyCm9yaWdpbmFsIGBhdF9yaXNrYCBvdXRjb21lIGFuZCB0aGUgYC5wcmVkX2NsYXNzYCB2YXJpYWJsZSB3aGljaCBzaG93cwp0aGUgcHJlZGljdGVkIG91dGNvbWVzLgoKIyMjIyBDaGVjayBtb2RlbCBhY2N1cmFjeQoKQXMgeW91IHByb2JhYmx5IG5vdGljZWQsIGp1c3QgbG9va2luZyBhdCB0aGUgYGxyX3ByZWRpY3Rpb25zYCBvYmplY3QgaXMKbm90IHRoZSBlYXNpZXN0IHdheSB0byBjaGVjayBmb3IgbW9kZWwgYWNjdXJhY3kuIEZvcnR1bmF0ZWx5LCB0aGUKe1t5YXJkc3RpY2t9XShodHRwczovL3RpZHltb2RlbHMuZ2l0aHViLmlvL3lhcmRzdGljay8pIHBhY2thZ2UgaGFzIGFuCmBhY2N1cmFjeSgpYCBmdW5jdGlvbiBmb3IgbG9va2luZyBhdCB0aGUgb3ZlcmFsbCBjbGFzc2lmaWNhdGlvbgphY2N1cmFjeSwgd2hpY2ggdXNlcyB0aGUgaGFyZCBjbGFzcyBwcmVkaWN0aW9ucyB0byBtZWFzdXJlIHBlcmZvcm1hbmNlLgoKSGFyZCBjbGFzcyBwcmVkaWN0aW9ucyB0ZWxsIHVzIHdoZXRoZXIgb3VyIG1vZGVsCnByZWRpY3RlZMKgYHllc2DCoG9ywqBgbm9gwqBmb3IgZWFjaCBzdHVkZW50IGluIHRoZSBgLnByZWRfY2xhc3NgIGNvbHVtbiwgYXMKd2VsbCBhcyB0aGUgZXN0aW1hdGluZyBhIHByb2JhYmlsaXR5LsKgQSBzaW1wbGUgNTAlIHByb2JhYmlsaXR5IGN1dG9mZiBpcwp1c2VkIHRvIGNhdGVnb3JpemUgYSBzdHVkZW50IGFzIGF0IHJpc2suIEZvciBleGFtcGxlLCBzdHVkZW50IGA0NzQ0OGAKaGFkIGEgYC5wcmVkX25vYCBwcm9iYWJpbGl0eSBvZiAwLjg4NTQzMzIgYW5kIGAucHJlZF95ZXNgIHByb2JhYmlsaXR5IG9mCjAuMTE0NTY2NzcgYW5kIHNvIHdhcyBjbGFzc2lmaWVkIGFzIGBub2AgZm9yIG5vdCBiZWluZyBmb3IgYGF0X3Jpc2tgLgoKUnVuIHRoZSBmb2xsb3dpbmcgY29kZSB0byBgc2VsZWN0KClgIHRoZXNlIHZhcmlhYmxlcyBmb2xsb3dlZCBieSB0aGUKYGFjY3VyYWN5KClgIGZ1bmN0aW9uIHRvIHNlZSBob3cgZnJlcXVlbnRseSBvdXIgcHJlZGljdGlvbiBtYXRjaGVkIG91cgpvYnNlcnZlZCBkYXRhOgoKYGBge3J9CmxyX3ByZWRpY3Rpb25zICU+JQogIHNlbGVjdChhdF9yaXNrLCAucHJlZF9jbGFzcykgJT4lCiAgYWNjdXJhY3kodHJ1dGggPSBhdF9yaXNrLCAucHJlZF9jbGFzcykKYGBgCgpPdmVyYWxsIGl0IGxvb2tzIGxpa2Ugb3VyIG1vZGVsIHdhcyBjb3JyZWN0IDgyJSBvZiB0aGUgdGltZSwgd2hpY2ggd2UnbGwKc2VlIGluIGEgc2Vjb25kIGlzIG5vdCB0ZXJyaWJseSBpbXByZXNzaXZlLgoKQW5vdGhlciB3YXkgdG8gY2hlY2sgbW9kZWwgYWNjdXJhY3kgaXMgd2l0aCB0aGUgYGNvbmZfbWF0KClgIGZ1bmN0aW9uCmZyb20ge3lhcmRzdGlja30gZm9yIGNyZWF0aW5nIGEgY29uZnVzaW9uIG1hdHJpeC4gUmVjYWxsIGZyb20gb3VyIGNvdXJzZQp0ZXh0IExlYXJuaW5nIEFuYWx5dGljcyBHb2VzIHRvIFNjaG9vbCB0aGF0IGEgY29uZnVzaW9uIG1hdHJpeCBpcyBzaW1wbHkKYSAyIMOXIDIgdGFibGUgdGhhdCBsaXN0cyB0aGUgbnVtYmVyIG9mIHRydWUtbmVnYXRpdmVzLCBmYWxzZS1uZWdhdGl2ZXMsCnRydWUtcG9zaXRpdmVzLCBhbmQgZmFsc2UtcG9zaXRpdmVzLgoKUnVuIHRoZSBmb2xsb3cgY29kZSB0byBjcmVhdGUgYSBjb25mdXNpb24gbWF0cml4IGZvciBvdXIgbG9naXN0aWMKcmVncmVzc2lvbiBwcmVkaWN0aW9uczoKCmBgYHtyfQpscl9wcmVkaWN0aW9ucyAlPiUKY29uZl9tYXQoYXRfcmlzaywgLnByZWRfY2xhc3MpCmBgYAoKQXMgeW91IGNhbiBzZWUgb3VyIG1vZGVsIGFjY3VyYXRlbHkgcHJlZGljdGVkIGFsbCBzdHVkZW50cyBhcyAibm8iIGZvcgphdCByaXNrIDExMCB0aW1lcywgYnV0IGluYWNjdXJhdGVseSBwcmVkaWN0ZWQgMjQgc3R1ZGVudHMgYSAibm8iIGZvciBhdApyaXNrIHdoZW4gdGhleSB3ZXJlIGFjdHVhbGx5ICJ5ZXMiIGluIG91ciBgdGVzdF9kYXRhYC4gT3ZlcmFsbCBvdXIgbW9kZWwKd2FzIDgyJSBhY2N1cmF0ZSwgYnV0IGl0IGFjaGlldmVkIHRoaXMgYnkgc2ltcGx5IGxhYmVsaW5nIGV2ZXJ5b25lIGFzCiJubyIgZm9yIGF0IHJpc2suIEFuZCBzaW5jZSByb3VnaCA4MiUgb2Ygb3VyIHN0dWRlbnRzIHdlcmUgYWN0dWFsbHkKIm5vLCIgdGhpcyBpcyBjbGVhcmx5IG5vdCBhIGdyZWF0IHByZWRpY3Rpb24gbW9kZWwuCgojIyMjIFsqKllvdXIgVHVybioqXXtzdHlsZT0iY29sb3I6IGdyZWVuOyJ9ICoq4qS1KioKClJlY3ljbGUgb3VyIGNvZGUgZnJvbSBhYm92ZSB0byBjcmVhdGUgYSB3b3JrZmxvdyBmb3IgYGxyX3JlY2lwZV8yYCBhbmQKdGVzdCBvdXIgImtpdGNoZW4gc2luayIgbW9kZWwgb24gb3VyIHRlc3QgZGF0YS4gKipIaW50OioqIFlvdSBjYW4KYWNjb21wbGlzaCB0aGlzIGJ5IHNpbXBseSBjb3B5aW5nIGFuZCBwYXN0aW5nIGFuZCBjaGFuZ2luZyBhIHNpbmdsZQpjaGFyYWN0ZXIgZnJvbSBhYm92ZS4KCmBgYHtyLCB3YXJuaW5nPUZBTFNFfQoKIyBjcmVhdGUgd29ya2Zsb3cKbHJfd29ya2Zsb3cgPC0gCiAgd29ya2Zsb3coKSAlPiUgCiAgYWRkX21vZGVsKGxyX21vZCkgJT4lIAogIGFkZF9yZWNpcGUobHJfcmVjaXBlXzIpCgojIHNldCBzZWVlZApzZXQuc2VlZCg1ODYpCgoKIyBmaXQgbW9kZWwgdG8gd29ya2Zsb3cKbHJfZml0IDwtIAogIGxyX3dvcmtmbG93ICU+JSAKICBmaXQoZGF0YSA9IHRlc3RfZGF0YSkKCiMgZXh0cmFjdCBtb2RlbCBlc3RpbWF0ZXMKbHJfZml0ICU+JSAKICBleHRyYWN0X2ZpdF9wYXJzbmlwKCkgJT4lIAogIHRpZHkoKQoKIyBnZXQgcHJlZGljdGlvbnMgCnByZWRpY3QobHJfZml0LCB0ZXN0X2RhdGEpCmxyX3ByZWRpY3Rpb25zIDwtIGF1Z21lbnQobHJfZml0LCB0ZXN0X2RhdGEpCgojIGNoZWNrIG92ZXJhbGwgYWNjdXJhY3kgCmxyX3ByZWRpY3Rpb25zICU+JQogIHNlbGVjdChhdF9yaXNrLCAucHJlZF9jbGFzcykgJT4lCiAgYWNjdXJhY3kodHJ1dGggPSBhdF9yaXNrLCAucHJlZF9jbGFzcykKCiMgY3JlYXRlIGEgY29uZnVzaW9uIG1hdHJpeApscl9wcmVkaWN0aW9ucyAlPiUKY29uZl9tYXQoYXRfcmlzaywgLnByZWRfY2xhc3MpCmBgYAoKMS4gIERvZXMgdGhpcyBtb2RlbCBwZXJmb3JtIGFueSBiZXR0ZXI/IEhvdyBkbyB5b3Uga25vdz8KCiAgICAtICAgLmVzdGltYXRlIGluY3JlYXNlZCB0byA4NS4xJQoKICAgIC0gICBtdWx0aXBsZSAieWVzIiB2YWx1ZXMgaGFkIGEgLnByZWRfeWVzIHJlc3VsdCBcPiA1MCUKCiMjIyBiLiBGaXQgYSBSYW5kb20gRm9yZXN0IE1vZGVsCgpbUmFuZG9tIGZvcmVzdCBtb2RlbHNdKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL1JhbmRvbV9mb3Jlc3QpCmFyZcKgW2Vuc2VtYmxlc10oaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvRW5zZW1ibGVfbGVhcm5pbmcpIG9mCltkZWNpc2lvbiB0cmVlc10oaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvRGVjaXNpb25fdHJlZSkuIEVhY2ggb2YKdGhvc2UgdGVybXMgaXMgbGlua2VkIGJlY2F1c2UgdGhlcmUgaXMgYSBkZW5zZSBhbW91bnQgb2YgaW5mb3JtYXRpb24KcmVxdWlyZWQgdG8gdW5kZXJzdGFuZCB3aGF0IGVhY2ggb2YgdGhvc2UgdGVybXMgcmVhbGx5IG1lYW5zLiBGb3Igbm93LApob3dldmVyLCB0aGVtIG1haW4gdGhpbmcgdG8ga25vdyBpcyB0aGF0OgoKPiBPbmUgb2YgdGhlIGJlbmVmaXRzIG9mIGEgcmFuZG9tIGZvcmVzdCBtb2RlbCBpcyB0aGF0IGl0IGlzIHZlcnkgbG93Cj4gbWFpbnRlbmFuY2U7IGl0IHJlcXVpcmVzIHZlcnkgbGl0dGxlIHByZXByb2Nlc3Npbmcgb2YgdGhlIGRhdGEgYW5kIHRoZQo+IGRlZmF1bHQgcGFyYW1ldGVycyB0ZW5kIHRvIGdpdmUgcmVhc29uYWJsZSByZXN1bHRzLgoKRm9yIHRoYXQgcmVhc29uLCB3ZSdsbCBqdXN0IGNyZWF0ZSBhIHZlcnkgc2ltcGxlIHJlY2lwZSBmb3IKb3VywqBgY291cnNlX2RhdGFgwqBkYXRhIGluY2x1ZGVzIGFsbCBvdXIgcHJlZGljdG9ycyBhbmQgc2luZ2xlcyBvdXQgb3VyCmBzdHVkZW50X2lkYCByb2xlOgoKYGBge3J9CnJmX3JlY2lwZSA8LSAKICByZWNpcGUoYXRfcmlzayB+IC4sIAogICAgICAgICBkYXRhID0gdHJhaW5fZGF0YSkgJT4lCiAgdXBkYXRlX3JvbGUoc3R1ZGVudF9pZCwgbmV3X3JvbGUgPSAiSUQiKQoKcmZfcmVjaXBlCgpzdW1tYXJ5KHJmX3JlY2lwZSkKYGBgCgpUbyBmaXQgYSByYW5kb20gZm9yZXN0IG1vZGVsIG9uIHRoZSB0cmFpbmluZyBzZXQsIHdlJ2xsIHVzZQp0aGXCoHtbcGFyc25pcF0oaHR0cHM6Ly90aWR5bW9kZWxzLmdpdGh1Yi5pby9wYXJzbmlwLyl9IHBhY2thZ2UgYWdhaW4KYWxvbmcgd2l0aCB0aGUKW3Jhbmdlcl0oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL3Jhbmdlci9pbmRleC5odG1sKQplbmdpbmUuIFdlJ2xsIGFsc28gaW5jbHVkZSB0aGUgYHNldF9tb2RlKClgIGZ1bmN0aW9uIHRvIHNwZWNpZnkgb3VyCm1vZGVsIGFzICJjbGFzc2lmaWNhdGlvbiIgcmF0aGVyIHRoYW4gInJlZ3Jlc3Npb24uIgoKUmVjYWxsIGZyb20gb3VyIExlYXJuaW5nIEFuYWx5dGljcyBHb2VzIHRvIFNjaG9vbCB0aGF0IHN1cGVydmlzZWQKbWFjaGluZWQgbGVhcm5pbmcsIG9yIHByZWRpY3RpdmUgbW9kZWxpbmcsIGludm9sdmVzIHR3byBicm9hZAphcHByb2FjaGVzOiBjbGFzc2lmaWNhdGlvbiBhbmQgcmVncmVzc2lvbi4gQ2xhc3NpZmljYXRpb24gYWxnb3JpdGhtcwptb2RlbCBjYXRlZ29yaWNhbCBvdXRjb21lcyAoZS5nLiwgeWVzIG9yIG5vIG91dGNvbWVzIGxpa2Ugd2l0aCBvdXIgYXQKcmlzayBkYXRhKS4gUmVncmVzc2lvbiBhbGdvcml0aG1zIGNoYXJhY3Rlcml6ZSBjb250aW51b3VzIG91dGNvbWVzCihlLmcuLCB0ZXN0IHNjb3JlcykuCgpSdW4gdGhlIGZvbGxvd2luZyBjb2RlIHRvIGNyZWF0ZSBvdXIgcmFuZG9tIGZvcmVzdCBtb2RlbCBmb3Igb3VyCnRyYWluaW5nIGRhdGE6CgpgYGB7cn0KcmZfbW9kIDwtIAogIHJhbmRfZm9yZXN0KHRyZWVzID0gMTAwMCkgJT4lIAogIHNldF9lbmdpbmUoInJhbmdlciIsIGltcG9ydGFuY2UgPSAiaW1wdXJpdHkiKSAlPiUgCiAgc2V0X21vZGUoImNsYXNzaWZpY2F0aW9uIikKYGBgCgpOb3cgbGV0J3MgY29tYmluZSBvdXIgbW9kZWwgYW5kIHJlY2lwZSB0byBjcmVhdGUgb3VyIG5ldyByYW5kb20gZm9yZXN0CndvcmtmbG93OgoKYGBge3J9CnJmX3dvcmtmbG93IDwtIAogIHdvcmtmbG93KCkgJT4lIAogIGFkZF9tb2RlbChyZl9tb2QpICU+JSAKICBhZGRfcmVjaXBlKHJmX3JlY2lwZSkKYGBgCgpBbmQgZml0IG91ciBtb2RlbCBhbmQgcmVjaXBlIHRvIG91ciB0cmFpbmluZyBkYXRhOgoKYGBge3J9CnNldC5zZWVkKDU4NikKCnJmX2ZpdCA8LSAKICByZl93b3JrZmxvdyAlPiUgCiAgZml0KGRhdGEgPSB0cmFpbl9kYXRhKQpgYGAKCkdhdGhlciBvdXIgcmFuZG9tIGZvcmVzdCBwcmVkaWN0aW9uczoKCmBgYHtyfQoKcmZfcHJlZGljdGlvbnMgPC0gYXVnbWVudChyZl9maXQsIHRlc3RfZGF0YSkKCnJmX3ByZWRpY3Rpb25zCmBgYAoKQW5kIGNoZWNrIG91ciBtb2RlbCdzIG92ZXJhbGwgYWNjdXJhY3k6CgpgYGB7cn0KcmZfcHJlZGljdGlvbnMgJT4lCiAgc2VsZWN0KGF0X3Jpc2ssIC5wcmVkX2NsYXNzKSAlPiUKICBhY2N1cmFjeSh0cnV0aCA9IGF0X3Jpc2ssIC5wcmVkX2NsYXNzKQpgYGAKCkFuZCBjcmVhdGUgYSBjb25mdXNpb24gbWF0cml4IHRvIHNlZSB3aGVyZSBpdCBkaWQgYW5kIGRpZCBub3QgbWFrZQphY2N1cmF0ZSBwcmVkaWN0aW9uczoKCmBgYHtyfQpyZl9wcmVkaWN0aW9ucyAlPiUKY29uZl9tYXQoYXRfcmlzaywgLnByZWRfY2xhc3MpCmBgYAoKT3ZlcmFsbCwgb3VyIHJhbmRvbSBmb3Jlc3QgbW9kZWwgaXMgc2xpZ2h0bHkgYmV0dGVyIGF0IHByZWRpY3RpbmcKc3R1ZGVudHMgd2hvIHdlcmUgZGVmaW5lZCBhcyAiYXQgcmlzayIsIGFuZCBkZWZpbml0ZWx5IGJldHRlciBhdApwcmVkaWN0aW5nIHN0dWRlbnRzIHdobyB3ZXJlIHRydWx5ICJhdCByaXNrIiBhY2NvcmRpbmcgdG8gb3VyCmRlZmluaXRpb24uCgpVbmZvcnR1bmF0ZWx5LCBpZiB0aGlzIGhhZCBiZWVuIGEgcmVhbC13b3JsZCBzaXR1YXRpb24sIG9ubHkgNyBvZiB0aGUgMjQKc3R1ZGVudHMgd291bGQgaGF2ZSByZWNlaXZlZCBhZGRpdGlvbmFsIHN1cHBvcnQuIENsZWFybHkgd2UnZCBuZWVkIHRvCmJ1aWxkIGEgYmV0dGVyIG1vZGVsLiBJbiB0aGlzIGNhc2UsIG1vcmUgZGF0YSB3b3VsZCBkZWZpbml0ZWx5IGhlbHAsIGJ1dAppdCdzIGxpa2VseSB3ZSdyZSBtaXNzaW5nIHNvbWUgaW1wb3J0YW50IGluZm9ybWF0aW9uIGFib3V0IHN0dWRlbnRzIHRoYXQKbWlnaHQgYmUgaGVscGZ1bCBmb3IgaW5jbHVkaW5nIGluIG91ciBtb2RlbC4KCiMjIyMgWyoqWW91ciBUdXJuKipde3N0eWxlPSJjb2xvcjogZ3JlZW47In0gKiripLUqKgoKVHJ5IGNyZWF0aW5nIHlvdXIgb3duIHJlY2lwZSBhbmQgYSBsb2dpc3RpYyByZWdyZXNzaW9uIG9yIHJhbmRvbSBmb3Jlc3QKbW9kZWwgYW5kIHNlZSBob3cgaXQgcGVyZm9ybXMgYWdhaW5zdCB0aGUgdGhyZWUgdGhhdCBJIGNyZWF0ZWQuCgpVc2UgdGhlIGNvZGUgY2h1bmsgYmVsb3cgdG8gcmVjb3JkIHlvdXIgZmluYWwgbW9kZWw6CgpgYGB7cn0KI2NyZWF0ZSByZiByZWNpcGUgdXNpbmcgb25seSA0IHByZWRpY3RvciB2YXJpYWJsZXMKcmZfcmVjaXBlMiA8LSAKICByZWNpcGUoYXRfcmlzayB+IHN0dWRlbnRfaWQgKyBzdWJqZWN0ICsgZ2VuZGVyICsgZW5yb2xsbWVudF9yZWFzb24gKyBpbnQsIAogICAgICAgICBkYXRhID0gdHJhaW5fZGF0YSkgJT4lCiAgdXBkYXRlX3JvbGUoc3R1ZGVudF9pZCwgbmV3X3JvbGUgPSAiSUQiKQoKIyBjcmVhdGUgbW9kZWwgZm9yIHRyYWluaW5nIGRhdGEKcmZfbW9kIDwtIAogIHJhbmRfZm9yZXN0KHRyZWVzID0gMTAwMCkgJT4lIAogIHNldF9lbmdpbmUoInJhbmdlciIsIGltcG9ydGFuY2UgPSAiaW1wdXJpdHkiKSAlPiUgCiAgc2V0X21vZGUoImNsYXNzaWZpY2F0aW9uIikKCiMgY3JlYXRlIHdvcmtmbG93CnJmX3dvcmtmbG93IDwtIAogIHdvcmtmbG93KCkgJT4lIAogIGFkZF9tb2RlbChyZl9tb2QpICU+JSAKICBhZGRfcmVjaXBlKHJmX3JlY2lwZSkKCiMgZml0IG1vZGVsIGFuZCByZWNpcGUgdG8gdHJhaW5pbmcgZGF0YQpzZXQuc2VlZCg1ODYpCgpyZl9maXQgPC0gCiAgcmZfd29ya2Zsb3cgJT4lIAogIGZpdChkYXRhID0gdHJhaW5fZGF0YSkKCiMgZ2F0aGVyIHByZWRpY3Rpb25zCnJmX3ByZWRpY3Rpb25zIDwtIGF1Z21lbnQocmZfZml0LCB0ZXN0X2RhdGEpCgpyZl9wcmVkaWN0aW9ucwoKIyBjaGVjayBhY2N1cmFjeSAKcmZfcHJlZGljdGlvbnMgJT4lCiAgc2VsZWN0KGF0X3Jpc2ssIC5wcmVkX2NsYXNzKSAlPiUKICBhY2N1cmFjeSh0cnV0aCA9IGF0X3Jpc2ssIC5wcmVkX2NsYXNzKQoKIyBjcmVhdGUgY29uZnVzaW9uIG1hdHJpeApyZl9wcmVkaWN0aW9ucyAlPiUKY29uZl9tYXQoYXRfcmlzaywgLnByZWRfY2xhc3MpCmBgYAoKTm93IGFuc3dlciB0aGUgZm9sbG93aW5nIHF1ZXN0aW9ucz8KCjEuICBIb3cgZGlkIHlvdSBtb2RlbCBkbyBjb21wYXJlZCB0byBvdGhlcnM/CgogICAgLSAgIE15IG1vZGVsLCB3aXRoIGFuIGFkZGl0aW9uYWwgdmFyaWFibGUgKHN1YmplY3QpLCBwcm9kdWNlZCB0aGUKICAgICAgICBzYW1lIGFjY3VyYWN5IGFzIHRoZSBsaW5lYXIgcmVncmVzc2lvbiBtb2RlbCBmb3IgdGhlIHRyYWluaW5nCiAgICAgICAgZGF0YSwgODIlLiBUaGlzIHNob3dzIHRoYXQgdGhlIGNvdXJzZSBzdWJqZWN0IGhhZCBsaXR0bGUgdG8gbm8KICAgICAgICBiZWFyaW5nIG9uIGEgc3R1ZGVudCdzIGNoYW5jZXMgZm9yIHBhc3MvZmFpbC4KCjIuICBXaGF0IGluZm9ybWF0aW9uIG1pZ2h0IGJlIHVzZWZ1bCB0byBrbm93IGFib3V0IHN0dWRlbnRzIHByaW9yIHRvIHRoZQogICAgY291cnNlIHN0YXJ0IHRoYXQgbWlnaHQgYmUgdXNlZnVsIGZvciBpbXByb3Zpbmcgb3VyIG1vZGVsPwoKICAgIC0gICBjdXJyZW50IEdQQSAtIGNvdWxkIGluZGljYXRlIG92ZXJhbGwgYXB0aXR1ZGUKCiMjIDUuIENPTU1VTklDQVRFCgpJbiB0aGlzIGNhc2Ugc3R1ZHksIHdlIGZvY3VzZWQgYXBwbHlpbmcgc29tZSBiYXNpYyBtYWNoaW5lIGxlYXJuaW5nCnRlY2huaXF1ZXMgdG8gaGVscCB1cyB1bmRlcnN0YW5kIGhvdyBhIHByZWRpY3RpdmUgbW9kZWwgdXNlZCBpbiBlYXJseQp3YXJuaW5nIHN5c3RlbXMgbWlnaHQgYWN0dWFsbHkgYmUgZGV2ZWxvcGVkIGFuZCB0ZXN0ZWQuIFNwZWNpZmljYWxseSwgd2UKbWFkZSBhIHZlcnkgY3J1ZGUgZmlyc3QgYXR0ZW1wdCBhdCBkZXZlbG9waW5nIGEgbW9kZWwgdXNpbmcgbWFjaGluZQpsZWFybmluZyB0ZWNobmlxdWVzIHRoYXQgd2VyZSBub3QgdGVycmlibHkgZ3JlYXQgYXQgYWNjdXJhdGVseSBwcmVkaWN0CndoZXRoZXIgYSBzdHVkZW50IGlzIGxpa2VseSB0byBwYXNzIG9yIGZhaWwgYW5kIG9ubGluZSBjb3Vyc2UuCgpCZWxvdywgYWRkIGEgZmV3IG5vdGVzIGluIHJlc3BvbnNlIHRvIHRoZSBmb2xsb3dpbmcgcHJvbXB0czoKCjEuICBPbmUgdGhpbmcgSSB0b29rIGF3YXkgZnJvbSB0aGlzIGxlYXJuaW5nIGxhYiB0aGF0IEkgZm91bmQgZXNwZWNpYWxseQogICAgdXNlZnVsOiBsZWFybmluZyB0aGUgc3RhbmRhcmQgd29ya2Zsb3cgdG8gY3JlYXRlLCB0cmFpbi90ZXN0IGEKICAgIG1vZGVsLgoKMi4gIE9uZSB0aGluZyBJIHdhbnQgdG8gbGVhcm4gbW9yZSBhYm91dDogaG93IGRvIHdlIGFkanVzdCB0aGUgc3BsaXQKICAgIHJhdGlvcyBmb3Igb3VyIGRhdGEgc2V0cz8gSWYgd2Ugd2FudCBhbiA4MC8yMCBvciBhIDkwLzEwIHNwbGl0LCBob3cKICAgIHdvdWxkIHdlIGFkanVzdCBvdXIgY29kZSB0byByZXR1cm4gdGhvc2Ugc2l6ZWQgZGF0YSBzZXRzPwoKVG8gInR1cm4gaW4iIHlvdXIgd29yaywgeW91IGNhbiBjbGljayB0aGUgIktuaXQiIGljb24gYXQgdGhlIHRvcCBvZiB0aGUKZmlsZSwgb3IgY2xpY2sgdGhlIGRyb3Bkb3duIGFycm93IG5leHQgdG8gaXQgYW5kIHNlbGVjdCAiS25pdCB0b3AgSFRNTCIuClRoaXMgd2lsbCBjcmVhdGUgYSByZXBvcnQgaW4geW91ciBGaWxlcyBwYW5lIHRoYXQgc2VydmVzIGFzIGEgcmVjb3JkIG9mCnlvdXIgY29tcGxldGVkIGFzc2lnbm1lbnQgYW5kIGl0cyBvdXRwdXQgeW91IGNhbiBvcGVuIG9yIHNoYXJlLgoKIyMjIENvbmdyYXR1bGF0aW9ucyEK