1. PREPARE

Each machine learning “case study” is designed to illustrate how machine learning methods and techniques can be applied to address a research question of interest, create useful data products, and conduct reproducible research. Each case study is structured around a basic analytics workflow modeled after the Data-Intensive Research Workflow from Learning Analytics Goes to School (Krumm et al., 2018):

Figure 2.2 Steps of Data-Intensive Research Workflow

In the overview presentation for this lab, we considered five steps in our supervised machine learning process. Those steps are mirrored here in this case study, with the addition of some other components of this workflow. For example, to help prepare for analysis, we’ll first take a step back and think about how we want to use machine learning, and predicting is a key word. Many scholars have focused on predicting students who are at-risk: of dropping a course or not succeeding in it. In the ML Lab 1 case study will cover the following workflow topics as we attempt to develop our own model for predicting student drop-out:

  1. Prepare: Prior to analysis, we’ll look at the context from which our data came, formulate a basic research question, and get introduced the {tidymodels} packages for machine learning.

  2. Wrangle: Wrangling data entails the work of cleaning, transforming, and merging data. In Part 2 we focus on importing CSV files and modifying some of our variables.

  3. Explore: We take a quick look at our variables of interest and do some basic “feature engineering” by creating some new variables we think will be predictive of students at risk.

  4. Model: We dive deeper into the five steps in our supervised machine learning process, focusing on the mechanics of making predictions.

  5. Communicate: To wrap up our case study, we’ll create our first “data product” and share our analyses and findings by creating our first web page using R Markdown.

1a. Conceptual Focus

Conceptually, we focus on prediction and how it differs from the goals of description or explanation. We have two readings in Lab 1 that accompany this. The first reading introduced below focuses on this distinction between prediction and description or explanation. It is one of the most widely-read papers in machine learning and articulates how machine learning differs from other kinds of statistical models. Breiman describes the difference in terms of data modeling (models for description and explanation) and algorithmic modeling (what we call prediction or machine learning models)*

Research Question

Technically, we’ll focus on the core parts of doing a machine learning analysis in R. We’ll use the {tidymodels} set of R packages (add-ons) to do so. However, to help anchor our analysis and provide us with some direction, we’ll focus on the following research question as we explore this new:

How well can we predict students who are at risk of dropping a course?

Reading: Statistical modeling: The two cultures

Breiman, L. (2001). Statistical modeling: The two cultures (with comments and a rejoinder by the author). Statistical Science, 16(3), 199-231. https://projecteuclid.org/journals/statistical-science/volume-16/issue-3/Statistical-Modeling--The-Two-Cultures-with-comments-and-a/10.1214/ss/1009213726.pdf

👉 Your Turn

You’ll be asked to reflect more deeply on this article later on (in the badge activity); but for now, open up the article and take quick scan of the article and note below an observation or question you have about the article.

  • Observation:

An observation that I have identified as concerning in this publication is the author’s position on published results. They conclude that it is often the case that professionals put less emphasis on developing a model that fits the problem they are attempting to solve than they do on a model that is in appearance, expertly crafted (Breiman, 203). In essence, complexity is favored over usability.

Reading: Predicting students’ final grades

Estrellado, R. A., Freer, E. A., Mostipak, J., Rosenberg, J. M., & Velásquez, I. C. (2020). Data science in education using R. Routledge (c14), Predicting students’ final grades using machine learning methods with online course data. http://www.datascienceineducation.com/

Please review this chapter, focusing on the overall goals of the analysis and how the analysis was presented (focusing on predictions, rather than the ways we may typically interpret a statistical model–like measures of statistical significance).

1b. Load Packages

As highlighted in Chapter 6 of Data Science in Education Using R (DSIEUR), one of the first steps of every workflow should be to set up your “Project” within RStudio. Recall that:

A Project is the home for all of the files, images, reports, and code that are used in any given project

Since we are working from an R project cloned from GitHub, a Project has already been set up for you as indicated by the .Rproj file in your main directory in the Files pane. Instead, we will focus on getting our project set up with the requisite packages we’ll need for analysis.

Packages, sometimes called libraries, are shareable collections of R code that can contain functions, data, and/or documentation and extend the functionality of R. You can always check to see which packages have already been installed and loaded into RStudio or RStudio Cloud by looking at the Files, Plots, & Packages Pane in the lower right-hand corner.

Two packages we’ll use extensively throughout these labs are the {tidyverse} and {tidymodels} packages.

tidyverse 📦

One package that we’ll be using extensively throughout LASER is the {tidyverse} package. Recall from earlier tutorials that the {tidyverse} package is actually a collection of R packages designed for reading, wrangling, and exploring data and which all share an underlying design philosophy, grammar, and data structures. These shared features are sometimes “tidy data principles.”

Click the green arrow in the right corner of the “code chunk” that follows to load the {tidyverse} library.

library(tidyverse)
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr     1.1.4     ✔ readr     2.1.5
## ✔ forcats   1.0.0     ✔ stringr   1.5.1
## ✔ ggplot2   3.5.0     ✔ tibble    3.2.1
## ✔ lubridate 1.9.3     ✔ tidyr     1.3.1
## ✔ purrr     1.0.2     
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag()    masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors

tidymodels

The tidymodels package is a “meta-package” for modeling and statistical analysis that shares 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 lightweight but highly useful {janitor} package to help with some data cleaning tasks. Use the code chunk below to load these two packages:

As a tip, remember to use the library() function to load these packages. After you’ve done that, click the green arrow to run the code chunk. If you see a bunch of messages (not anything labeled as an error), you are good to go! These messages mean the packages loaded correctly.

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. Importing and Inspecting Data. In this section, we will “read” in our CSV data file and take a quick look at what our file contains.

  2. Mutate Variables. We use the mutate() function to create a dichotomous variable for whether or not the student withdrew from the course.

1a. Import and Inspect Data

For learning labs 1-3, we’ll be using a widely-used data set in the learning analytics field: the Open University Learning Analytics Dataset (OULAD). The OULAD was created by learning analytics researchers at the United Kingdom-based Open University. It includes data from post-secondary learners participation in one of several Massive Open Online Courses (called modules in the OULAD).

Kuzilek, J., Hlosta, M., & Zdrahal, Z. (2017). Open university learning analytics dataset. Scientific Data, 4(1), 1-8. https://www.nature.com/articles/sdata2017171

Abstract

Learning Analytics focuses on the collection and analysis of learners’ data to improve their learning experience by providing informed guidance and to optimise learning materials. To support the research in this area we have developed a dataset, containing data from courses presented at the Open University (OU). What makes the dataset unique is the fact that it contains demographic data together with aggregated clickstream data of students’ interactions in the Virtual Learning Environment (VLE). This enables the analysis of student behavior, represented by their actions. The dataset contains the information about 22 courses, 32,593 students, their assessment results, and logs of their interactions with the VLE represented by daily summaries of student clicks (10,655,280 entries). The dataset is freely available at https://analyse.kmi.open.ac.uk/open_dataset under a CC-BY 4.0 license.

👉 Your Turn

You don’t need to read the entire article yet, but please open this article, scan the sections, and write down two things you notice or wonder about the dataset.

  1. One of the first thoughts I had had, being in higher education and looking to research with student data is the natural concern of FERPA violations with respect to personally identifiable information. OULAD appears to have a robust system of not only informing students of the use of data, but effectively scrubbing the data for any information which could be harmful to the student if released.

  2. Of the three different data types used in the OULAD Research (Demographic, Performance, and Learning Behavior), I am most interested to see what is included in learning behavior in order to translate that information for my own use.

Read CSV Data File

The data can be downloaded at the above link; however, for our purposes, they are already downloaded to the data sub-folder.

We’ll use the read_csv() function to load the files – two in total, with data on students and assessments. Note: we have done some minimal processing of these files to make getting us started easier. If you’re interested in what we’ve done, check out the oulad.R file in the lab-1 folder.

For now, please read in the oulad-students.csv file. Use the read_csv() function to do this, paying attention to where those files are located relative to this case study file.

students <- read_csv("oulad-students.csv")
## Rows: 32593 Columns: 15
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr (9): code_module, code_presentation, gender, region, highest_education, ...
## dbl (6): id_student, num_of_prev_attempts, studied_credits, module_presentat...
## 
## ℹ 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.

You can see a description of the data here. The students file includes three files joined together: studentInfo, courses, and studentRegistration. Take a look at the data description to get a sense for what variables are in which data frame.

👉 Your Turn

Inspect Data

In the chunk below, examine the data set using a function or means of your choice (such as just printing the data set by typing its name or using the glimpse() function). Do this in the code chunk below! Note its dimensions — especially how many rows it has!

glimpse("oulad-students.csv")
##  chr "oulad-students.csv"

Write down a few observations after inspecting the data:

  • 15 variables

  • 32593 records

  • variables include mostly demographic data

2b. “Mutate” Variables

We’re going to do a few more steps related to data wrangling here, noting we could also do these at later stages of our process (namely, in the feature engineering stage).

First, since we are interested in developing a model that can predict whether a student is at risk of dropping a course, and so we can intervene before that happens, we need an outcome variable that let’s us know if they have passed.

To create this variable, let’s use the mutate() function to create a dichotomous variable for whether or not the student withdrew from the course. Here’s a way we can do this, using if_else() and as.factor(). This will be our outcome variable, or the predicted variable.

students <- students %>%
    mutate(pass = ifelse(final_result == "Pass", 1, 0)) %>% # creates a new variable named "pass" and a dummy code of 1 if value of final_result equals "pass" and 0 if not
    mutate(pass = as.factor(pass)) # makes the variable a factor, helping later steps

Note: The mutate() function.) is a critical function to learn and is used to create new columns that are functions of existing variables. It can also modify (if the name is the same as an existing column) and delete columns (by setting their value to NULL).

Next, let’s do something similar for whether a student identifies as having a disability. In this case, there are only two values for the disability variable, so we can simply convert it directly to a factor. Look at the code you used above, modifying it for the disability variable. This will be an independent variable, or a predictor variable.

students <- students %>% 
    mutate(disability = as.factor(disability))

👉 Your Turn

In the chunk below, use the view() function to manually check and see if our new variable has indeed been added to our data frame.

view("disability")

Write down a few observations after inspecting the data:

For me this opens up a 2X2 table with disability as an ‘x’ variable value. I honestly am not sure if I am doing this correctly.

Note: The mutate() function.) is a critical function to learn and is used to create new columns that are functions of existing variables. It can also modify (if the name is the same as an existing column) and delete columns (by setting their value to NULL).

Next, let’s do something similar for whether a student identifies as having a disability. In this case, there are only two values for the disability variable, so we can simply convert it directly to a factor. Look at the code you used above, modifying it for the disability variable. This will be an independent variable, or a predictor variable.

students <- students %>% 
    mutate(disability = as.factor(disability))

3. EXPLORE

As noted by Krumm et al. (2018), exploratory data analysis often involves some combination of data visualization and feature engineering. In Part 3, we will create a quick visualization to help us spot any potential issues with our data and engineer new predictive variables or “features” that we will use in our predictive models. Specifically, in Part 3 we will:

  1. Examine Outcomes by taking a quick count() of the number of students and the number of specific offerings of each course module.

  2. Engineer Predictors by creating one more predictor variable based on a measure of socioeconomic resources–the index of multiple depravity variable.

3a. Examine Variables

A useful function for exploring data is count(); it does what it sounds like! It counts how many times values for a variable appear.

Referring to the data description, in the chunk below, count the number of students. Also, count the number of courses (modules) and specific offerings (as modules can be offered multiple times per year). Learn more about count() here.

students %>% 
    count(id_student) # this many students
## # A tibble: 28,785 × 2
##    id_student     n
##         <dbl> <int>
##  1       3733     1
##  2       6516     1
##  3       8462     2
##  4      11391     1
##  5      23629     1
##  6      23632     1
##  7      23698     1
##  8      23798     1
##  9      24186     1
## 10      24213     2
## # ℹ 28,775 more rows
students %>% 
    count(code_module, code_presentation) # this many offerings
## # A tibble: 22 × 3
##    code_module code_presentation     n
##    <chr>       <chr>             <int>
##  1 AAA         2013J               383
##  2 AAA         2014J               365
##  3 BBB         2013B              1767
##  4 BBB         2013J              2237
##  5 BBB         2014B              1613
##  6 BBB         2014J              2292
##  7 CCC         2014B              1936
##  8 CCC         2014J              2498
##  9 DDD         2013B              1303
## 10 DDD         2013J              1938
## # ℹ 12 more rows

3b. Feature Engineering

As defined by Krumm, Means, and Bienkowski (2018) in Learning Analytics Goes to School:

Feature engineering is the process of creating new variables within a dataset, which goes above and beyond the work of recoding and rescaling variables.

The authors note that feature engineering draws on substantive knowledge from theory or practice, experience with a particular data system, and general experience in data-intensive research. Moreover, these features can be used not only in machine learning models, but also in visualizations and tables comprising descriptive statistics.

Though not often discussed, feature engineering is an important element of data-intensive research that can generate new insights and improve predictive models. You can read more about feature engineering here.

Student Socioeconomic Index

For our first lab, we’ll engage in a very basic feature engineering step, though we’ll do this much more in the next learning lab.

To do feature engineering, let’s create one more predictor variable based on a measure of socioeconomic resources–the index of multiple depravity variable. The process we take here is to turn this variable that is a character string into a number by creating a factor and then coercing it to an integer.

👉 Your Turn

Please replace the ____ values in the code below with the correct variable.

students <- students %>% 
    mutate(imd_band = factor(imd_band, levels = c("0-10%",
                                                  "10-20%",
                                                  "20-30%",
                                                  "30-40%",
                                                  "40-50%",
                                                  "50-60%",
                                                  "60-70%",
                                                  "70-80%",
                                                  "80-90%",
                                                  "90-100%"))) %>% # this creates a factor with ordered levels
    mutate(imd_band = as.integer(imd_band)) # this changes the levels into integers based on the order of the factor levels

students
## # A tibble: 32,593 × 16
##    code_module code_presentation id_student gender region      highest_education
##    <chr>       <chr>                  <dbl> <chr>  <chr>       <chr>            
##  1 AAA         2013J                  11391 M      East Angli… HE Qualification 
##  2 AAA         2013J                  28400 F      Scotland    HE Qualification 
##  3 AAA         2013J                  30268 F      North West… A Level or Equiv…
##  4 AAA         2013J                  31604 F      South East… A Level or Equiv…
##  5 AAA         2013J                  32885 F      West Midla… Lower Than A Lev…
##  6 AAA         2013J                  38053 M      Wales       A Level or Equiv…
##  7 AAA         2013J                  45462 M      Scotland    HE Qualification 
##  8 AAA         2013J                  45642 F      North West… A Level or Equiv…
##  9 AAA         2013J                  52130 F      East Angli… A Level or Equiv…
## 10 AAA         2013J                  53025 M      North Regi… Post Graduate Qu…
## # ℹ 32,583 more rows
## # ℹ 10 more variables: imd_band <int>, age_band <chr>,
## #   num_of_prev_attempts <dbl>, studied_credits <dbl>, disability <fct>,
## #   final_result <chr>, module_presentation_length <dbl>,
## #   date_registration <dbl>, date_unregistration <dbl>, pass <fct>

We’re now ready to proceed to the five machine learning steps!

4. MODEL

Recall from our first reading that there are two general types of modeling approaches: unsupervised and supervised machine learning. In Part 4, we focus on supervised learning models, which are used to quantify relationships between features (e.g., motivation and performance) and a known outcome (e.g., student drop out). These models can be used for classification of binary or categorical outcomes, as we’ll illustrate in this section, or regression as we’ll demonstrate in Learning Lab 2.

Specifically, in Part 4 we will learn how to:

  1. Split Data into a training and test set that will be used to develop a predictive model;

  2. Create a “Recipe” for our predictive model and learn how to deal with nominal data that we would like to use as predictors;

  3. Specify the model and workflow by selecting the functional form of the model that we want and using a model workflow to pair our model and recipe together;

  4. Fit Models to our training set using logistic regression;

  5. Interpret Accuracy of our model to see how well our model can “predict” our outcome of interest.

Step 1. Split data

The authors of Data Science in Education Using R (Estrellado et al.,2020) remind us that:

At its core, machine learning is the process of “showing” your statistical model only some of the data at once and training the model to predict accurately on that training dataset (this is the “learning” part of machine learning). Then, the model as developed on the training data is shown new data - data you had all along, but hid from your computer initially - and you see how well the model that you developed on the training data performs on this new testing data. Eventually, you might use the model on entirely new data.

Training and Testing Sets

It is therefore 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 20% for the test set and use the remainder for the training set, which are the defaults for the {rsample} package.

Split Data Sets

To split our data, we will be using our first {tidymodels} function - initial_split().

The function initial_split() function from the {rsample} package takes the original data and saves the information on how to make the partitions. 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:

Note: Since random sampling uses random numbers, it is important to set the random number seed using the set.seed() function. This ensures that the random numbers can be reproduced at a later time (if needed). We pick the first date on which we may carry out this learning lab as the seed, but any number will work!

👉 Your Turn

Go ahead and type data_train and data_test into the console (in steps) to check that this data set indeed has 80% of the number of observations as in the larger data. Do that in the chunk below:

Step 2: Create a “Recipe”

In this section, we introduce another tidymodels package named {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.

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

  • A formula. Any variable on the left-hand side of the tilde (~) is considered the model outcome (code, in our present 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 a recipe where we predict pass (the outcome variable) on the basis of the disability and imd_band (predictor) variables.

Step 3: Specify the model and workflow

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:

Run the following code to finish specifying our model:

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

To set the engine, let’s rewrite the code above and “pipe” in the set_engine("glm") function and set_mode("classification")) to set the “mode” to classification. Note that this could also be changed to regression for a continuous/numeric outcome.

Run the following code to finish specifying our model:

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.

Add to workflow

Now we can use the recipe 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).

Step 4: Fit model

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:

Finally, we’ll fit our model.

👉 Your Turn

Importantly, here, we can look at the model. Type fitted_model in a code chunk to take a look.

Note that while we don’t typically interpret the coefficients for a machine learning model, it’s important to recognize that many models do produce coefficients we could interpret. Instead, we focus on how the model does with respect to predicting the dependent variable.

Observations

Write down a brief observation of the output above, focusing on the coefficients:

  • disability has a negative relationship with pass and imd_band has a positive relationship with pass.

The last_fit function

Finally, we’ll use the last_fit function, which is the key here: note that it uses the train_test_split data—not just the training data.

Here, then, we fit the data using the training data set and evaluate its accuracy using the testing data set (which is not used to train the model).

Type final_fit below; this is the final, fitted model—one that can be interpreted further in the next step!

You may see a message/warning above or when you examine final_fit; you can safely ignore that.

Step 5: Interpret accuracy

Run the code below to examine the predictions for the test split of data. Note that the row ID is in the output below, but this doesn’t correspond one-one to the ID variables used in the presentation/Shiny.

This is our first set of real output! Note two things:

  1. .pres_class: This is the predicted code
  2. pass: This is the known code

When these are the same, the model predicted the code correctly; when they aren’t the same, the model was incorrect.

Importantly, we can summarize across all of these codes. One way to do this is straightforward; how many of the codes were the same, as in the following chunk of code:

You may notice some of the rows may be missing values. This is because there were some missing values in the imd_band variable, and for this machine learning algorithm (the generalized linear model), missing values result in row-wise deletion.

That’s helpful, but there’s one more step we can take – counting up the values of correct:

👉 Your Turn

Let’s interpret the above. If the value of correct is TRUE when the predicted and known code are the same, what does the percent column tell us? Add one or more notes to the dashes below:

  • I cannot seem to get any functions to work in relation to this or many of the previous concepts. I would guess that ‘percent’ is in reference to the amount of true values.

A short-cut to the above is below; we’ll use this short-cut from here forward, having seen how accuracy is calculated.

How accurate was our predictive model? Consider how well our model would have done by chance alone – what would the accuracy be in that case (with the model predicting pass one-half of the time)?

Curiously, randomly picking a 0 (did not pass) or a 1 (passed) will always lead to around a 50% accuracy, regardless of how many observations are actually associated with a 0 or a 1.

Observation

Let’s step back a bit. How well could we do if we include more data? And how useful could such a model be in the real world? We’ll dive into these questions more over the forthcoming learning labs.

That’s it for now; the core parts of machine learning are used in the above steps you took; what we’ll do after this leaning lab only adds nuance and complexity to what we’ve already done.

5. COMMUNICATE

The final step in the workflow/process is sharing the results of your analysis with wider audience. Krumm et al. (2018) have outlined the following 3-step process for communicating with education stakeholders findings from an analysis:

  1. Select. Communicating what one has learned involves selecting among those analyses that are most important and most useful to an intended audience, as well as selecting a form for displaying that information, such as a graph or table in static or interactive form, i.e. a “data product.”

  2. Polish. After creating initial versions of data products, research teams often spend time refining or polishing them, by adding or editing titles, labels, and notations and by working with colors and shapes to highlight key points.

  3. Narrate. Writing a narrative to accompany the data products involves, at a minimum, pairing a data product with its related research question, describing how best to interpret the data product, and explaining the ways in which the data product helps answer the research question.

For your learning ML Learning Lab 1 Badge, you will have an opportunity to create a simple “data product” designed to illustrate insights some insights gained from your model and ideally highlight an “action step” that can be taken to act upon your findings.

👉 Your Turn

For now, we will wrap up this case study by converting our work into a webpage that can be used to communicate your learning and demonstrate some of your new R skills. To do so, you will need to “knit” your document by clicking the button in the menu bar at the the top of this file. This will do two things; it will:

  1. check through all your code for any errors; and,

  2. create a file in your directory that you can use to share you work through Posit Cloud (see screenshot example below to publish), RPubs , GitHub Pages, Quarto Pub, or any other methods.

Note: Before knitting, make sure you change the author: name in the YAML header at the top of this document so you can take credit for your hard work!

Congratulations - you’ve completed the first machine learning case study!

LS0tDQp0aXRsZTogIkxhYiAxIENhc2UgU3R1ZHkiDQphdXRob3I6ICJNaWNoYWVsIFNjaHJhbSINCmRhdGU6ICJgciBmb3JtYXQoU3lzLkRhdGUoKSwnJUIgJWUsICVZJylgIg0Kb3V0cHV0Og0KICBodG1sX2RvY3VtZW50Og0KICAgIHRvYzogeWVzDQogICAgdG9jX2RlcHRoOiA0DQogICAgdG9jX2Zsb2F0OiB5ZXMNCiAgICBjb2RlX2ZvbGRpbmc6IHNob3cNCiAgICBjb2RlX2Rvd25sb2FkOiB5ZXMNCmVkaXRvcl9vcHRpb25zOg0KICBtYXJrZG93bjoNCiAgICB3cmFwOiA3Mg0KcmVzb3VyY2VfZmlsZXM6DQotIGltZy90aWR5bW9kZWxzLnBuZw0KLS0tDQoNCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFLCBldmFsID0gVFJVRSkNCmBgYA0KDQojIyAxLiBQUkVQQVJFDQoNCkVhY2ggbWFjaGluZSBsZWFybmluZyAiY2FzZSBzdHVkeSIgaXMgZGVzaWduZWQgdG8gaWxsdXN0cmF0ZSBob3cgbWFjaGluZQ0KbGVhcm5pbmcgbWV0aG9kcyBhbmQgdGVjaG5pcXVlcyBjYW4gYmUgYXBwbGllZCB0byBhZGRyZXNzIGEgcmVzZWFyY2gNCnF1ZXN0aW9uIG9mIGludGVyZXN0LCBjcmVhdGUgdXNlZnVsIGRhdGEgcHJvZHVjdHMsIGFuZCBjb25kdWN0DQpyZXByb2R1Y2libGUgcmVzZWFyY2guIEVhY2ggY2FzZSBzdHVkeSBpcyBzdHJ1Y3R1cmVkIGFyb3VuZCBhIGJhc2ljDQphbmFseXRpY3Mgd29ya2Zsb3cgbW9kZWxlZCBhZnRlciB0aGUgRGF0YS1JbnRlbnNpdmUgUmVzZWFyY2ggV29ya2Zsb3cNCmZyb20gW0xlYXJuaW5nIEFuYWx5dGljcyBHb2VzIHRvIFNjaG9vbF0oIzApIChLcnVtbSBldCBhbC4sIDIwMTgpOg0KDQohW10oaHR0cHM6Ly9zYmtlbGxvZ2cuZ2l0aHViLmlvL2VjaS01ODkvdW5pdC0xL2ltZy93b3JrZmxvdy5wbmcpe2FsdD0iIg0Kd2lkdGg9IjgwJSJ9DQoNCkZpZ3VyZSAyLjIgU3RlcHMgb2YgRGF0YS1JbnRlbnNpdmUgUmVzZWFyY2ggV29ya2Zsb3cNCg0KSW4gdGhlIG92ZXJ2aWV3IHByZXNlbnRhdGlvbiBmb3IgdGhpcyBsYWIsIHdlIGNvbnNpZGVyZWQgZml2ZSBzdGVwcyBpbg0Kb3VyIHN1cGVydmlzZWQgbWFjaGluZSBsZWFybmluZyBwcm9jZXNzLiBUaG9zZSBzdGVwcyBhcmUgbWlycm9yZWQgaGVyZQ0KaW4gdGhpcyBjYXNlIHN0dWR5LCB3aXRoIHRoZSBhZGRpdGlvbiBvZiBzb21lIG90aGVyIGNvbXBvbmVudHMgb2YgdGhpcw0Kd29ya2Zsb3cuIEZvciBleGFtcGxlLCB0byBoZWxwIHByZXBhcmUgZm9yIGFuYWx5c2lzLCB3ZSdsbCBmaXJzdCB0YWtlIGENCnN0ZXAgYmFjayBhbmQgdGhpbmsgYWJvdXQgaG93IHdlIHdhbnQgdG8gdXNlIG1hY2hpbmUgbGVhcm5pbmcsIGFuZA0KKnByZWRpY3RpbmcqIGlzIGEga2V5IHdvcmQuIE1hbnkgc2Nob2xhcnMgaGF2ZSBmb2N1c2VkIG9uIHByZWRpY3RpbmcNCnN0dWRlbnRzIHdobyBhcmUgKmF0LXJpc2sqOiBvZiBkcm9wcGluZyBhIGNvdXJzZSBvciBub3Qgc3VjY2VlZGluZyBpbg0KaXQuIEluIHRoZSBNTCBMYWIgMSBjYXNlIHN0dWR5IHdpbGwgY292ZXIgdGhlIGZvbGxvd2luZyB3b3JrZmxvdyB0b3BpY3MNCmFzIHdlIGF0dGVtcHQgdG8gZGV2ZWxvcCBvdXIgb3duIG1vZGVsIGZvciBwcmVkaWN0aW5nIHN0dWRlbnQgZHJvcC1vdXQ6DQoNCjEuICAqKlByZXBhcmUqKjogUHJpb3IgdG8gYW5hbHlzaXMsIHdlJ2xsIGxvb2sgYXQgdGhlIGNvbnRleHQgZnJvbSB3aGljaA0KICAgIG91ciBkYXRhIGNhbWUsIGZvcm11bGF0ZSBhIGJhc2ljIHJlc2VhcmNoIHF1ZXN0aW9uLCBhbmQgZ2V0DQogICAgaW50cm9kdWNlZCB0aGUge3RpZHltb2RlbHN9IHBhY2thZ2VzIGZvciBtYWNoaW5lIGxlYXJuaW5nLg0KDQoyLiAgKipXcmFuZ2xlKio6IFdyYW5nbGluZyBkYXRhIGVudGFpbHMgdGhlIHdvcmsgb2YgY2xlYW5pbmcsDQogICAgdHJhbnNmb3JtaW5nLCBhbmQgbWVyZ2luZyBkYXRhLiBJbiBQYXJ0IDIgd2UgZm9jdXMgb24gaW1wb3J0aW5nIENTVg0KICAgIGZpbGVzIGFuZCBtb2RpZnlpbmcgc29tZSBvZiBvdXIgdmFyaWFibGVzLg0KDQozLiAgKipFeHBsb3JlKio6IFdlIHRha2UgYSBxdWljayBsb29rIGF0IG91ciB2YXJpYWJsZXMgb2YgaW50ZXJlc3QgYW5kDQogICAgZG8gc29tZSBiYXNpYyAiZmVhdHVyZSBlbmdpbmVlcmluZyIgYnkgY3JlYXRpbmcgc29tZSBuZXcgdmFyaWFibGVzDQogICAgd2UgdGhpbmsgd2lsbCBiZSBwcmVkaWN0aXZlIG9mIHN0dWRlbnRzIGF0IHJpc2suDQoNCjQuICAqKk1vZGVsOioqIFdlIGRpdmUgZGVlcGVyIGludG8gdGhlIGZpdmUgc3RlcHMgaW4gb3VyIHN1cGVydmlzZWQNCiAgICBtYWNoaW5lIGxlYXJuaW5nIHByb2Nlc3MsIGZvY3VzaW5nIG9uIHRoZSBtZWNoYW5pY3Mgb2YgKiptYWtpbmcNCiAgICBwcmVkaWN0aW9ucyoqLg0KDQo1LiAgKipDb21tdW5pY2F0ZToqKiBUbyB3cmFwIHVwIG91ciBjYXNlIHN0dWR5LCB3ZSdsbCBjcmVhdGUgb3VyIGZpcnN0DQogICAgImRhdGEgcHJvZHVjdCIgYW5kIHNoYXJlIG91ciBhbmFseXNlcyBhbmQgZmluZGluZ3MgYnkgY3JlYXRpbmcgb3VyDQogICAgZmlyc3Qgd2ViIHBhZ2UgdXNpbmcgUiBNYXJrZG93bi4NCg0KIyMjIDFhLiBDb25jZXB0dWFsIEZvY3VzDQoNCkNvbmNlcHR1YWxseSwgd2UgZm9jdXMgb24gcHJlZGljdGlvbiBhbmQgaG93IGl0IGRpZmZlcnMgZnJvbSB0aGUgZ29hbHMNCm9mIGRlc2NyaXB0aW9uIG9yIGV4cGxhbmF0aW9uLiBXZSBoYXZlIHR3byByZWFkaW5ncyBpbiBMYWIgMSB0aGF0DQphY2NvbXBhbnkgdGhpcy4gVGhlIGZpcnN0IHJlYWRpbmcgaW50cm9kdWNlZCBiZWxvdyBmb2N1c2VzIG9uIHRoaXMNCmRpc3RpbmN0aW9uIGJldHdlZW4gcHJlZGljdGlvbiBhbmQgZGVzY3JpcHRpb24gb3IgZXhwbGFuYXRpb24uIEl0IGlzIG9uZQ0Kb2YgdGhlIG1vc3Qgd2lkZWx5LXJlYWQgcGFwZXJzIGluIG1hY2hpbmUgbGVhcm5pbmcgYW5kIGFydGljdWxhdGVzIGhvdw0KbWFjaGluZSBsZWFybmluZyBkaWZmZXJzIGZyb20gb3RoZXIga2luZHMgb2Ygc3RhdGlzdGljYWwgbW9kZWxzLiBCcmVpbWFuDQpkZXNjcmliZXMgdGhlIGRpZmZlcmVuY2UgaW4gdGVybXMgb2YgKmRhdGEgbW9kZWxpbmcqIChtb2RlbHMgZm9yDQpkZXNjcmlwdGlvbiBhbmQgZXhwbGFuYXRpb24pIGFuZCAqYWxnb3JpdGhtaWMgbW9kZWxpbmcqICh3aGF0IHdlIGNhbGwNCnByZWRpY3Rpb24gb3IgbWFjaGluZSBsZWFybmluZyBtb2RlbHMpXCoNCg0KIyMjIyBSZXNlYXJjaCBRdWVzdGlvbg0KDQpUZWNobmljYWxseSwgd2UnbGwgZm9jdXMgb24gdGhlIGNvcmUgcGFydHMgb2YgZG9pbmcgYSBtYWNoaW5lIGxlYXJuaW5nDQphbmFseXNpcyBpbiBSLiBXZSdsbCB1c2UgdGhlIHtbdGlkeW1vZGVsc10oaHR0cHM6Ly93d3cudGlkeW1vZGVscy5vcmcvKX0NCnNldCBvZiBSIHBhY2thZ2VzIChhZGQtb25zKSB0byBkbyBzby4gSG93ZXZlciwgdG8gaGVscCBhbmNob3Igb3VyDQphbmFseXNpcyBhbmQgcHJvdmlkZSB1cyB3aXRoIHNvbWUgZGlyZWN0aW9uLCB3ZSdsbCBmb2N1cyBvbiB0aGUNCmZvbGxvd2luZyByZXNlYXJjaCBxdWVzdGlvbiBhcyB3ZSBleHBsb3JlIHRoaXMgbmV3Og0KDQo+IEhvdyB3ZWxsIGNhbiB3ZSBwcmVkaWN0IHN0dWRlbnRzIHdobyBhcmUgYXQgcmlzayBvZiBkcm9wcGluZyBhIGNvdXJzZT8NCg0KIyMjIyBSZWFkaW5nOiBTdGF0aXN0aWNhbCBtb2RlbGluZzogVGhlIHR3byBjdWx0dXJlcw0KDQo+IEJyZWltYW4sIEwuICgyMDAxKS4gU3RhdGlzdGljYWwgbW9kZWxpbmc6IFRoZSB0d28gY3VsdHVyZXMgKHdpdGgNCj4gY29tbWVudHMgYW5kIGEgcmVqb2luZGVyIGJ5IHRoZSBhdXRob3IpLiAqU3RhdGlzdGljYWwgU2NpZW5jZSwgMTYqKDMpLA0KPiAxOTktMjMxLg0KPiA8aHR0cHM6Ly9wcm9qZWN0ZXVjbGlkLm9yZy9qb3VybmFscy9zdGF0aXN0aWNhbC1zY2llbmNlL3ZvbHVtZS0xNi9pc3N1ZS0zL1N0YXRpc3RpY2FsLU1vZGVsaW5nLS1UaGUtVHdvLUN1bHR1cmVzLXdpdGgtY29tbWVudHMtYW5kLWEvMTAuMTIxNC9zcy8xMDA5MjEzNzI2LnBkZj4NCg0KKirwn5GJIFlvdXIgVHVybioqICoq4qS1KioNCg0KWW91J2xsIGJlIGFza2VkIHRvIHJlZmxlY3QgbW9yZSBkZWVwbHkgb24gdGhpcyBhcnRpY2xlIGxhdGVyIG9uIChpbiB0aGUNCmJhZGdlIGFjdGl2aXR5KTsgYnV0IGZvciBub3csIG9wZW4gdXAgdGhlIGFydGljbGUgYW5kIHRha2UgcXVpY2sgc2NhbiBvZg0KdGhlIGFydGljbGUgYW5kIG5vdGUgYmVsb3cgYW4gb2JzZXJ2YXRpb24gb3IgcXVlc3Rpb24geW91IGhhdmUgYWJvdXQgdGhlDQphcnRpY2xlLg0KDQotICAgKipPYnNlcnZhdGlvbjoqKg0KDQpBbiBvYnNlcnZhdGlvbiB0aGF0IEkgaGF2ZSBpZGVudGlmaWVkIGFzIGNvbmNlcm5pbmcgaW4gdGhpcyBwdWJsaWNhdGlvbiBpcyB0aGUgYXV0aG9yJ3MgcG9zaXRpb24gb24gcHVibGlzaGVkIHJlc3VsdHMuIFRoZXkgY29uY2x1ZGUgdGhhdCBpdCBpcyBvZnRlbiB0aGUgY2FzZSB0aGF0IHByb2Zlc3Npb25hbHMgcHV0IGxlc3MgZW1waGFzaXMgb24gZGV2ZWxvcGluZyBhIG1vZGVsIHRoYXQgZml0cyB0aGUgcHJvYmxlbSB0aGV5IGFyZSBhdHRlbXB0aW5nIHRvIHNvbHZlIHRoYW4gdGhleSBkbyBvbiBhIG1vZGVsIHRoYXQgaXMgaW4gYXBwZWFyYW5jZSwgZXhwZXJ0bHkgY3JhZnRlZCAoQnJlaW1hbiwgMjAzKS4gSW4gZXNzZW5jZSwgY29tcGxleGl0eSBpcyBmYXZvcmVkIG92ZXIgdXNhYmlsaXR5Lg0KDQojIyMjIFJlYWRpbmc6IFByZWRpY3Rpbmcgc3R1ZGVudHMnIGZpbmFsIGdyYWRlcw0KDQo+IEVzdHJlbGxhZG8sIFIuIEEuLCBGcmVlciwgRS4gQS4sIE1vc3RpcGFrLCBKLiwgUm9zZW5iZXJnLCBKLiBNLiwgJg0KPiBWZWzDoXNxdWV6LCBJLiBDLiAoMjAyMCkuICpEYXRhIHNjaWVuY2UgaW4gZWR1Y2F0aW9uIHVzaW5nIFIqLg0KPiBSb3V0bGVkZ2UgKGMxNCksIFByZWRpY3Rpbmcgc3R1ZGVudHMnIGZpbmFsIGdyYWRlcyB1c2luZyBtYWNoaW5lDQo+IGxlYXJuaW5nIG1ldGhvZHMgd2l0aCBvbmxpbmUgY291cnNlIGRhdGEuDQo+IDxodHRwOi8vd3d3LmRhdGFzY2llbmNlaW5lZHVjYXRpb24uY29tLz4NCg0KUGxlYXNlIHJldmlldyB0aGlzIGNoYXB0ZXIsIGZvY3VzaW5nIG9uIHRoZSBvdmVyYWxsIGdvYWxzIG9mIHRoZQ0KYW5hbHlzaXMgYW5kIGhvdyB0aGUgYW5hbHlzaXMgd2FzIHByZXNlbnRlZCAoZm9jdXNpbmcgb24gcHJlZGljdGlvbnMsDQpyYXRoZXIgdGhhbiB0aGUgd2F5cyB3ZSBtYXkgdHlwaWNhbGx5IGludGVycHJldCBhIHN0YXRpc3RpY2FsDQptb2RlbC0tbGlrZSBtZWFzdXJlcyBvZiBzdGF0aXN0aWNhbCBzaWduaWZpY2FuY2UpLg0KDQojIyMgMWIuIExvYWQgUGFja2FnZXMNCg0KQXMgaGlnaGxpZ2h0ZWQgaW7CoFtDaGFwdGVyIDYgb2YgRGF0YSBTY2llbmNlIGluIEVkdWNhdGlvbiBVc2luZw0KUl0oaHR0cHM6Ly9kYXRhc2NpZW5jZWluZWR1Y2F0aW9uLmNvbS9jMDYuaHRtbCnCoChEU0lFVVIpLCBvbmUgb2YgdGhlDQpmaXJzdCBzdGVwcyBvZiBldmVyeSB3b3JrZmxvdyBzaG91bGQgYmUgdG8gc2V0IHVwIHlvdXIgIlByb2plY3QiIHdpdGhpbg0KUlN0dWRpby4gUmVjYWxsIHRoYXQ6DQoNCj4gQSAqKlByb2plY3QqKiBpcyB0aGUgaG9tZSBmb3IgYWxsIG9mIHRoZSBmaWxlcywgaW1hZ2VzLCByZXBvcnRzLCBhbmQNCj4gY29kZSB0aGF0IGFyZSB1c2VkIGluIGFueSBnaXZlbiBwcm9qZWN0DQoNClNpbmNlIHdlIGFyZSB3b3JraW5nIGZyb20gYW4gUiBwcm9qZWN0IGNsb25lZCBmcm9tDQpbR2l0SHViXShodHRwczovL2dpdGh1Yi5jb20vRHJJdGF1bWEtTlUpLCBhIFByb2plY3QgaGFzIGFscmVhZHkgYmVlbiBzZXQNCnVwIGZvciB5b3UgYXMgaW5kaWNhdGVkIGJ5IHRoZSBgLlJwcm9qYCBmaWxlIGluIHlvdXIgbWFpbiBkaXJlY3RvcnkgaW4NCnRoZSBGaWxlcyBwYW5lLiBJbnN0ZWFkLCB3ZSB3aWxsIGZvY3VzIG9uIGdldHRpbmcgb3VyIHByb2plY3Qgc2V0IHVwDQp3aXRoIHRoZSByZXF1aXNpdGUgcGFja2FnZXMgd2UnbGwgbmVlZCBmb3IgYW5hbHlzaXMuDQoNCioqUGFja2FnZXMqKiwgc29tZXRpbWVzIGNhbGxlZCBsaWJyYXJpZXMsIGFyZSBzaGFyZWFibGUgY29sbGVjdGlvbnMgb2YgUg0KY29kZSB0aGF0IGNhbiBjb250YWluIGZ1bmN0aW9ucywgZGF0YSwgYW5kL29yIGRvY3VtZW50YXRpb24gYW5kIGV4dGVuZA0KdGhlIGZ1bmN0aW9uYWxpdHkgb2YgUi4gWW91IGNhbiBhbHdheXMgY2hlY2sgdG8gc2VlIHdoaWNoIHBhY2thZ2VzIGhhdmUNCmFscmVhZHkgYmVlbiBpbnN0YWxsZWQgYW5kIGxvYWRlZCBpbnRvIFJTdHVkaW8gb3IgUlN0dWRpbyBDbG91ZCBieQ0KbG9va2luZyBhdCB0aGUgRmlsZXMsIFBsb3RzLCAmIFBhY2thZ2VzIFBhbmUgaW4gdGhlIGxvd2VyIHJpZ2h0LWhhbmQNCmNvcm5lci4NCg0KVHdvIHBhY2thZ2VzIHdlJ2xsIHVzZSBleHRlbnNpdmVseSB0aHJvdWdob3V0IHRoZXNlIGxhYnMgYXJlIHRoZQ0Ke3RpZHl2ZXJzZX0gYW5kIHt0aWR5bW9kZWxzfSBwYWNrYWdlcy4NCg0KIyMjIyB0aWR5dmVyc2Ug8J+Tpg0KDQohW10oaW1nL3RpZHl2ZXJzZS5wbmcpe3dpZHRoPSIyMCUifQ0KDQpPbmUgcGFja2FnZSB0aGF0IHdlJ2xsIGJlIHVzaW5nIGV4dGVuc2l2ZWx5IHRocm91Z2hvdXQgTEFTRVIgaXMgdGhlDQp7dGlkeXZlcnNlfSBwYWNrYWdlLiBSZWNhbGwgZnJvbSBlYXJsaWVyIHR1dG9yaWFscyB0aGF0IHRoZSB7dGlkeXZlcnNlfQ0KcGFja2FnZSBpcyBhY3R1YWxseSBhIFtjb2xsZWN0aW9uIG9mIFINCnBhY2thZ2VzXShodHRwczovL3d3dy50aWR5dmVyc2Uub3JnL3BhY2thZ2VzKSBkZXNpZ25lZCBmb3IgcmVhZGluZywNCndyYW5nbGluZywgYW5kIGV4cGxvcmluZyBkYXRhIGFuZCB3aGljaCBhbGwgc2hhcmUgYW4gdW5kZXJseWluZyBkZXNpZ24NCnBoaWxvc29waHksIGdyYW1tYXIsIGFuZCBkYXRhIHN0cnVjdHVyZXMuIFRoZXNlIHNoYXJlZCBmZWF0dXJlcyBhcmUNCnNvbWV0aW1lcyAidGlkeSBkYXRhIHByaW5jaXBsZXMuIg0KDQpDbGljayB0aGUgZ3JlZW4gYXJyb3cgaW4gdGhlIHJpZ2h0IGNvcm5lciBvZiB0aGUgImNvZGUgY2h1bmsiIHRoYXQNCmZvbGxvd3MgdG8gbG9hZCB0aGUge3RpZHl2ZXJzZX0gbGlicmFyeS4NCg0KYGBge3IgbG9hZC10aWR5dmVyc2V9DQoNCmxpYnJhcnkodGlkeXZlcnNlKQ0KDQpgYGANCg0KIyMjIyB0aWR5bW9kZWxzDQoNClshW10oaW1nL3RpZHltb2RlbHMucG5nKXt3aWR0aD0iMjAlIn1dKGh0dHBzOi8vd3d3LnRpZHltb2RlbHMub3JnLykNCg0KVGhlIFt0aWR5bW9kZWxzXShodHRwczovL3d3dy50aWR5bW9kZWxzLm9yZy8pIHBhY2thZ2UgaXMgYQ0KIm1ldGEtcGFja2FnZSIgZm9yIG1vZGVsaW5nIGFuZCBzdGF0aXN0aWNhbCBhbmFseXNpcyB0aGF0IHNoYXJlcyB0aGUNCnVuZGVybHlpbmcgZGVzaWduIHBoaWxvc29waHksIGdyYW1tYXIsIGFuZCBkYXRhIHN0cnVjdHVyZXMgb2YgdGhlDQpbdGlkeXZlcnNlXShodHRwczovL3d3dy50aWR5dmVyc2Uub3JnLykuIEl0IGluY2x1ZGVzIGEgY29yZSBzZXQgb2YNCnBhY2thZ2VzIHRoYXQgYXJlIGxvYWRlZCBvbiBzdGFydHVwIGFuZCBjb250YWlucyB0b29scyBmb3I6DQoNCi0gICBkYXRhIHNwbGl0dGluZyBhbmQgcHJlLXByb2Nlc3Npbmc7DQoNCi0gICBtb2RlbCBzZWxlY3Rpb24sIHR1bmluZywgYW5kIGV2YWx1YXRpb247DQoNCi0gICBmZWF0dXJlIHNlbGVjdGlvbiBhbmQgdmFyaWFibGUgaW1wb3J0YW5jZSBlc3RpbWF0aW9uOw0KDQotICAgYXMgd2VsbCBhcyBvdGhlciBmdW5jdGlvbmFsaXR5Lg0KDQojIyMjICoq8J+RiSBZb3VyIFR1cm4qKiAqKuKktSoqDQoNCkluIGFkZGl0aW9uIHRvIHRoZSB7dGlkeW1vZGVsc30gcGFja2FnZSwgd2UnbGwgYWxzbyBiZSB1c2luZyB0aGUNCmxpZ2h0d2VpZ2h0IGJ1dCBoaWdobHkgdXNlZnVsIHtqYW5pdG9yfSBwYWNrYWdlIHRvIGhlbHAgd2l0aCBzb21lIGRhdGENCmNsZWFuaW5nIHRhc2tzLiBVc2UgdGhlIGNvZGUgY2h1bmsgYmVsb3cgdG8gbG9hZCB0aGVzZSB0d28gcGFja2FnZXM6DQoNCmBgYHtyIGxvYWQtdGlkeW1vZGVsc30NCg0KDQoNCmBgYA0KDQpBcyBhIHRpcCwgcmVtZW1iZXIgdG8gdXNlIHRoZSBgbGlicmFyeSgpYCBmdW5jdGlvbiB0byBsb2FkIHRoZXNlDQpwYWNrYWdlcy4gQWZ0ZXIgeW91J3ZlIGRvbmUgdGhhdCwgY2xpY2sgdGhlIGdyZWVuIGFycm93IHRvIHJ1biB0aGUgY29kZQ0KY2h1bmsuIElmIHlvdSBzZWUgYSBidW5jaCBvZiBtZXNzYWdlcyAobm90IGFueXRoaW5nIGxhYmVsZWQgYXMgYW4NCmVycm9yKSwgeW91IGFyZSBnb29kIHRvIGdvISBUaGVzZSBtZXNzYWdlcyBtZWFuIHRoZSBwYWNrYWdlcyBsb2FkZWQNCmNvcnJlY3RseS4NCg0KIyMgMi4gV1JBTkdMRQ0KDQpJbiBnZW5lcmFsLCBkYXRhIHdyYW5nbGluZyBpbnZvbHZlcyBzb21lIGNvbWJpbmF0aW9uIG9mIGNsZWFuaW5nLA0KcmVzaGFwaW5nLCB0cmFuc2Zvcm1pbmcsIGFuZCBtZXJnaW5nIGRhdGEgKFdpY2toYW0gJiBHcm9sZW11bmQsIDIwMTcpLg0KVGhlIGltcG9ydGFuY2Ugb2YgZGF0YSB3cmFuZ2xpbmcgaXMgZGlmZmljdWx0IHRvIG92ZXJzdGF0ZSwgYXMgaXQNCmludm9sdmVzIHRoZSBpbml0aWFsIHN0ZXBzIG9mIGdvaW5nIGZyb20gcmF3IGRhdGEgdG8gYSBkYXRhc2V0IHRoYXQgY2FuDQpiZSBleHBsb3JlZCBhbmQgbW9kZWxlZCAoS3J1bW0gZXQgYWwsIDIwMTgpLiBJbiBQYXJ0IDIsIHdlIGZvY3VzIG9uIHRoZQ0KdGhlIGZvbGxvd2luZyB3cmFuZ2xpbmcgcHJvY2Vzc2VzIHRvOg0KDQoxLiAgKipJbXBvcnRpbmcgYW5kIEluc3BlY3RpbmcgRGF0YSoqLiBJbiB0aGlzIHNlY3Rpb24sIHdlIHdpbGwgInJlYWQiDQogICAgaW4gb3VyIENTViBkYXRhIGZpbGUgYW5kIHRha2UgYSBxdWljayBsb29rIGF0IHdoYXQgb3VyIGZpbGUNCiAgICBjb250YWlucy4NCg0KMi4gICoqTXV0YXRlIFZhcmlhYmxlcyoqLiBXZSB1c2UgdGhlIGBtdXRhdGUoKWAgZnVuY3Rpb24gdG8gY3JlYXRlIGENCiAgICBkaWNob3RvbW91cyB2YXJpYWJsZSBmb3Igd2hldGhlciBvciBub3QgdGhlIHN0dWRlbnQgd2l0aGRyZXcgZnJvbQ0KICAgIHRoZSBjb3Vyc2UuDQoNCiMjIyAxYS4gSW1wb3J0IGFuZCBJbnNwZWN0IERhdGENCg0KRm9yIGxlYXJuaW5nIGxhYnMgMS0zLCB3ZSdsbCBiZSB1c2luZyBhIHdpZGVseS11c2VkIGRhdGEgc2V0IGluIHRoZQ0KbGVhcm5pbmcgYW5hbHl0aWNzIGZpZWxkOiB0aGUgW09wZW4gVW5pdmVyc2l0eSBMZWFybmluZyBBbmFseXRpY3MNCkRhdGFzZXQgKE9VTEFEKV0oaHR0cHM6Ly9hbmFseXNlLmttaS5vcGVuLmFjLnVrL29wZW5fZGF0YXNldCkuIFRoZSBPVUxBRA0Kd2FzIGNyZWF0ZWQgYnkgbGVhcm5pbmcgYW5hbHl0aWNzIHJlc2VhcmNoZXJzIGF0IHRoZSBVbml0ZWQNCktpbmdkb20tYmFzZWQgT3BlbiBVbml2ZXJzaXR5LiBJdCBpbmNsdWRlcyBkYXRhIGZyb20gcG9zdC1zZWNvbmRhcnkNCmxlYXJuZXJzIHBhcnRpY2lwYXRpb24gaW4gb25lIG9mIHNldmVyYWwgTWFzc2l2ZSBPcGVuIE9ubGluZSBDb3Vyc2VzDQooY2FsbGVkICptb2R1bGVzKiBpbiB0aGUgT1VMQUQpLg0KDQo+IEt1emlsZWssIEouLCBIbG9zdGEsIE0uLCAmIFpkcmFoYWwsIFouICgyMDE3KS4gT3BlbiB1bml2ZXJzaXR5DQo+IGxlYXJuaW5nIGFuYWx5dGljcyBkYXRhc2V0LiAqU2NpZW50aWZpYyBEYXRhLCA0KCoxKSwgMS04Lg0KPiA8aHR0cHM6Ly93d3cubmF0dXJlLmNvbS9hcnRpY2xlcy9zZGF0YTIwMTcxNzE+DQoNCioqQWJzdHJhY3QqKg0KDQpMZWFybmluZyBBbmFseXRpY3MgZm9jdXNlcyBvbiB0aGUgY29sbGVjdGlvbiBhbmQgYW5hbHlzaXMgb2YgbGVhcm5lcnMnDQpkYXRhIHRvIGltcHJvdmUgdGhlaXIgbGVhcm5pbmcgZXhwZXJpZW5jZSBieSBwcm92aWRpbmcgaW5mb3JtZWQgZ3VpZGFuY2UNCmFuZCB0byBvcHRpbWlzZSBsZWFybmluZyBtYXRlcmlhbHMuIFRvIHN1cHBvcnQgdGhlIHJlc2VhcmNoIGluIHRoaXMgYXJlYQ0Kd2UgaGF2ZSBkZXZlbG9wZWQgYSBkYXRhc2V0LCBjb250YWluaW5nIGRhdGEgZnJvbSBjb3Vyc2VzIHByZXNlbnRlZCBhdA0KdGhlIE9wZW4gVW5pdmVyc2l0eSAoT1UpLiBXaGF0IG1ha2VzIHRoZSBkYXRhc2V0IHVuaXF1ZSBpcyB0aGUgZmFjdCB0aGF0DQppdCBjb250YWlucyBkZW1vZ3JhcGhpYyBkYXRhIHRvZ2V0aGVyIHdpdGggYWdncmVnYXRlZCBjbGlja3N0cmVhbSBkYXRhDQpvZiBzdHVkZW50cycgaW50ZXJhY3Rpb25zIGluIHRoZSBWaXJ0dWFsIExlYXJuaW5nIEVudmlyb25tZW50IChWTEUpLg0KVGhpcyBlbmFibGVzIHRoZSBhbmFseXNpcyBvZiBzdHVkZW50IGJlaGF2aW9yLCByZXByZXNlbnRlZCBieSB0aGVpcg0KYWN0aW9ucy4gVGhlIGRhdGFzZXQgY29udGFpbnMgdGhlIGluZm9ybWF0aW9uIGFib3V0IDIyIGNvdXJzZXMsIDMyLDU5Mw0Kc3R1ZGVudHMsIHRoZWlyIGFzc2Vzc21lbnQgcmVzdWx0cywgYW5kIGxvZ3Mgb2YgdGhlaXIgaW50ZXJhY3Rpb25zIHdpdGgNCnRoZSBWTEUgcmVwcmVzZW50ZWQgYnkgZGFpbHkgc3VtbWFyaWVzIG9mIHN0dWRlbnQgY2xpY2tzICgxMCw2NTUsMjgwDQplbnRyaWVzKS4gVGhlIGRhdGFzZXQgaXMgZnJlZWx5IGF2YWlsYWJsZSBhdA0KPGh0dHBzOi8vYW5hbHlzZS5rbWkub3Blbi5hYy51ay9vcGVuX2RhdGFzZXQ+IHVuZGVyIGEgQ0MtQlkgNC4wIGxpY2Vuc2UuDQoNCiMjIyMgKirwn5GJIFlvdXIgVHVybioqICoq4qS1KioNCg0KWW91IGRvbid0IG5lZWQgdG8gcmVhZCB0aGUgZW50aXJlIGFydGljbGUgeWV0LCBidXQgcGxlYXNlIG9wZW4gdGhpcw0KYXJ0aWNsZSwgc2NhbiB0aGUgc2VjdGlvbnMsIGFuZCB3cml0ZSBkb3duIHR3byB0aGluZ3MgeW91IG5vdGljZSBvcg0Kd29uZGVyIGFib3V0IHRoZSBkYXRhc2V0Lg0KDQoxLiAgT25lIG9mIHRoZSBmaXJzdCB0aG91Z2h0cyBJIGhhZCBoYWQsIGJlaW5nIGluIGhpZ2hlciBlZHVjYXRpb24gYW5kIGxvb2tpbmcgdG8gcmVzZWFyY2ggd2l0aCBzdHVkZW50IGRhdGEgaXMgdGhlIG5hdHVyYWwgY29uY2VybiBvZiBGRVJQQSB2aW9sYXRpb25zIHdpdGggcmVzcGVjdCB0byBwZXJzb25hbGx5IGlkZW50aWZpYWJsZSBpbmZvcm1hdGlvbi4gT1VMQUQgYXBwZWFycyB0byBoYXZlIGEgcm9idXN0IHN5c3RlbSBvZiBub3Qgb25seSBpbmZvcm1pbmcgc3R1ZGVudHMgb2YgdGhlIHVzZSBvZiBkYXRhLCBidXQgZWZmZWN0aXZlbHkgc2NydWJiaW5nIHRoZSBkYXRhIGZvciBhbnkgaW5mb3JtYXRpb24gd2hpY2ggY291bGQgYmUgaGFybWZ1bCB0byB0aGUgc3R1ZGVudCBpZiByZWxlYXNlZC4NCg0KMi4gIE9mIHRoZSB0aHJlZSBkaWZmZXJlbnQgZGF0YSB0eXBlcyB1c2VkIGluIHRoZSBPVUxBRCBSZXNlYXJjaCAoRGVtb2dyYXBoaWMsIFBlcmZvcm1hbmNlLCBhbmQgTGVhcm5pbmcgQmVoYXZpb3IpLCBJIGFtIG1vc3QgaW50ZXJlc3RlZCB0byBzZWUgd2hhdCBpcyBpbmNsdWRlZCBpbiBsZWFybmluZyBiZWhhdmlvciBpbiBvcmRlciB0byB0cmFuc2xhdGUgdGhhdCBpbmZvcm1hdGlvbiBmb3IgbXkgb3duIHVzZS4NCg0KIyMjIyBSZWFkIENTViBEYXRhIEZpbGUNCg0KVGhlIGRhdGEgY2FuIGJlIGRvd25sb2FkZWQgYXQgdGhlIGFib3ZlIGxpbms7IGhvd2V2ZXIsIGZvciBvdXIgcHVycG9zZXMsDQp0aGV5IGFyZSBhbHJlYWR5IGRvd25sb2FkZWQgdG8gdGhlIGBkYXRhYCBzdWItZm9sZGVyLg0KDQpXZSdsbCB1c2UgdGhlIGByZWFkX2NzdigpYCBmdW5jdGlvbiB0byBsb2FkIHRoZSBmaWxlcyAtLSB0d28gaW4gdG90YWwsDQp3aXRoIGRhdGEgb24gc3R1ZGVudHMgYW5kIGFzc2Vzc21lbnRzLiBOb3RlOiB3ZSBoYXZlIGRvbmUgc29tZSBtaW5pbWFsDQpwcm9jZXNzaW5nIG9mIHRoZXNlIGZpbGVzIHRvIG1ha2UgZ2V0dGluZyB1cyBzdGFydGVkIGVhc2llci4gSWYgeW91J3JlDQppbnRlcmVzdGVkIGluIHdoYXQgd2UndmUgZG9uZSwgY2hlY2sgb3V0IHRoZSBgb3VsYWQuUmAgZmlsZSBpbiB0aGUNCmBsYWItMWAgZm9sZGVyLg0KDQpGb3Igbm93LCBwbGVhc2UgcmVhZCBpbiB0aGUgYG91bGFkLXN0dWRlbnRzLmNzdmAgZmlsZS4gVXNlIHRoZQ0KYHJlYWRfY3N2KClgIGZ1bmN0aW9uIHRvIGRvIHRoaXMsIHBheWluZyBhdHRlbnRpb24gdG8gd2hlcmUgdGhvc2UgZmlsZXMNCmFyZSBsb2NhdGVkIHJlbGF0aXZlIHRvIHRoaXMgY2FzZSBzdHVkeSBmaWxlLg0KDQpgYGB7cn0NCnN0dWRlbnRzIDwtIHJlYWRfY3N2KCJvdWxhZC1zdHVkZW50cy5jc3YiKQ0KYGBgDQoNCllvdSBjYW4gc2VlIGEgZGVzY3JpcHRpb24gb2YgdGhlIGRhdGENCltoZXJlXShodHRwczovL2FuYWx5c2Uua21pLm9wZW4uYWMudWsvb3Blbl9kYXRhc2V0I2Rlc2NyaXB0aW9uKS4gVGhlDQpzdHVkZW50cyBmaWxlIGluY2x1ZGVzIHRocmVlIGZpbGVzIGpvaW5lZCB0b2dldGhlcjogc3R1ZGVudEluZm8sDQpjb3Vyc2VzLCBhbmQgc3R1ZGVudFJlZ2lzdHJhdGlvbi4gVGFrZSBhIGxvb2sgYXQgdGhlIGRhdGEgZGVzY3JpcHRpb24gdG8NCmdldCBhIHNlbnNlIGZvciB3aGF0IHZhcmlhYmxlcyBhcmUgaW4gd2hpY2ggZGF0YSBmcmFtZS4NCg0KIyMjIyAqKvCfkYkgWW91ciBUdXJuKiogKiripLUqKg0KDQojIyMjIEluc3BlY3QgRGF0YQ0KDQpJbiB0aGUgY2h1bmsgYmVsb3csIGV4YW1pbmUgdGhlIGRhdGEgc2V0IHVzaW5nIGEgZnVuY3Rpb24gb3IgbWVhbnMgb2YNCnlvdXIgY2hvaWNlIChzdWNoIGFzIGp1c3QgKnByaW50aW5nKiB0aGUgZGF0YSBzZXQgYnkgdHlwaW5nIGl0cyBuYW1lIG9yDQp1c2luZyB0aGUgYGdsaW1wc2UoKWAgZnVuY3Rpb24pLiBEbyB0aGlzIGluIHRoZSBjb2RlIGNodW5rIGJlbG93ISBOb3RlDQppdHMgZGltZW5zaW9ucyAtLS0gZXNwZWNpYWxseSBob3cgbWFueSByb3dzIGl0IGhhcyENCg0KYGBge3J9DQpnbGltcHNlKCJvdWxhZC1zdHVkZW50cy5jc3YiKQ0KYGBgDQoNCldyaXRlIGRvd24gYSBmZXcgb2JzZXJ2YXRpb25zIGFmdGVyIGluc3BlY3RpbmcgdGhlIGRhdGE6DQoNCi0gIDE1IHZhcmlhYmxlcyAgDQoNCi0gICAzMjU5MyByZWNvcmRzDQoNCi0gICB2YXJpYWJsZXMgaW5jbHVkZSBtb3N0bHkgZGVtb2dyYXBoaWMgZGF0YQ0KDQojIyMgMmIuICJNdXRhdGUiIFZhcmlhYmxlcw0KDQpXZSdyZSBnb2luZyB0byBkbyBhIGZldyBtb3JlIHN0ZXBzIHJlbGF0ZWQgdG8gZGF0YSB3cmFuZ2xpbmcgaGVyZSwNCm5vdGluZyB3ZSBjb3VsZCBhbHNvIGRvIHRoZXNlIGF0IGxhdGVyIHN0YWdlcyBvZiBvdXIgcHJvY2VzcyAobmFtZWx5LCBpbg0KdGhlIGZlYXR1cmUgZW5naW5lZXJpbmcgc3RhZ2UpLg0KDQpGaXJzdCwgc2luY2Ugd2UgYXJlIGludGVyZXN0ZWQgaW4gZGV2ZWxvcGluZyBhIG1vZGVsIHRoYXQgY2FuIHByZWRpY3QNCndoZXRoZXIgYSBzdHVkZW50IGlzIGF0IHJpc2sgb2YgZHJvcHBpbmcgYSBjb3Vyc2UsIGFuZCBzbyB3ZSBjYW4NCmludGVydmVuZSBiZWZvcmUgdGhhdCBoYXBwZW5zLCB3ZSBuZWVkIGFuIG91dGNvbWUgdmFyaWFibGUgdGhhdCBsZXQncyB1cw0Ka25vdyBpZiB0aGV5IGhhdmUgcGFzc2VkLg0KDQpUbyBjcmVhdGUgdGhpcyB2YXJpYWJsZSwgbGV0J3MgdXNlIHRoZSBgbXV0YXRlKClgIGZ1bmN0aW9uIHRvIGNyZWF0ZSBhDQpkaWNob3RvbW91cyB2YXJpYWJsZSBmb3Igd2hldGhlciBvciBub3QgdGhlIHN0dWRlbnQgd2l0aGRyZXcgZnJvbSB0aGUNCmNvdXJzZS4gSGVyZSdzIGEgd2F5IHdlIGNhbiBkbyB0aGlzLCB1c2luZyBgaWZfZWxzZSgpYCBhbmQNCmBhcy5mYWN0b3IoKWAuIFRoaXMgd2lsbCBiZSBvdXIgKm91dGNvbWUqIHZhcmlhYmxlLCBvciB0aGUgcHJlZGljdGVkDQp2YXJpYWJsZS4NCg0KYGBge3J9DQpzdHVkZW50cyA8LSBzdHVkZW50cyAlPiUNCiAgICBtdXRhdGUocGFzcyA9IGlmZWxzZShmaW5hbF9yZXN1bHQgPT0gIlBhc3MiLCAxLCAwKSkgJT4lICMgY3JlYXRlcyBhIG5ldyB2YXJpYWJsZSBuYW1lZCAicGFzcyIgYW5kIGEgZHVtbXkgY29kZSBvZiAxIGlmIHZhbHVlIG9mIGZpbmFsX3Jlc3VsdCBlcXVhbHMgInBhc3MiIGFuZCAwIGlmIG5vdA0KICAgIG11dGF0ZShwYXNzID0gYXMuZmFjdG9yKHBhc3MpKSAjIG1ha2VzIHRoZSB2YXJpYWJsZSBhIGZhY3RvciwgaGVscGluZyBsYXRlciBzdGVwcw0KDQpgYGANCg0KKipOb3RlKio6IFRoZSBbYG11dGF0ZSgpYA0KZnVuY3Rpb25dKGh0dHBzOi8vZHBseXIudGlkeXZlcnNlLm9yZy9yZWZlcmVuY2UvbXV0YXRlLmh0bWwjOn46dGV4dD1tdXRhdGUoKSUyMGNyZWF0ZXMlMjBuZXclMjBjb2x1bW5zLHNldHRpbmclMjB0aGVpciUyMHZhbHVlJTIwdG8lMjBOVUxMJTIwKS4pDQppcyBhIGNyaXRpY2FsIGZ1bmN0aW9uIHRvIGxlYXJuIGFuZCBpcyB1c2VkIHRvIGNyZWF0ZSBuZXcgY29sdW1ucyB0aGF0DQphcmUgZnVuY3Rpb25zIG9mIGV4aXN0aW5nIHZhcmlhYmxlcy4gSXQgY2FuIGFsc28gbW9kaWZ5IChpZiB0aGUgbmFtZSBpcw0KdGhlIHNhbWUgYXMgYW4gZXhpc3RpbmcgY29sdW1uKSBhbmQgZGVsZXRlIGNvbHVtbnMgKGJ5IHNldHRpbmcgdGhlaXINCnZhbHVlIHRvIGBOVUxMYCkuDQoNCk5leHQsIGxldCdzIGRvIHNvbWV0aGluZyBzaW1pbGFyIGZvciB3aGV0aGVyIGEgc3R1ZGVudCBpZGVudGlmaWVzIGFzDQpoYXZpbmcgYSBkaXNhYmlsaXR5LiBJbiB0aGlzIGNhc2UsIHRoZXJlIGFyZSBvbmx5IHR3byB2YWx1ZXMgZm9yIHRoZQ0KZGlzYWJpbGl0eSB2YXJpYWJsZSwgc28gd2UgY2FuIHNpbXBseSBjb252ZXJ0IGl0IGRpcmVjdGx5IHRvIGEgZmFjdG9yLg0KTG9vayBhdCB0aGUgY29kZSB5b3UgdXNlZCBhYm92ZSwgKm1vZGlmeWluZyogaXQgZm9yIHRoZSBkaXNhYmlsaXR5DQp2YXJpYWJsZS4gVGhpcyB3aWxsIGJlIGFuICppbmRlcGVuZGVudCB2YXJpYWJsZSosIG9yIGEgcHJlZGljdG9yDQp2YXJpYWJsZS4NCg0KYGBge3J9DQpzdHVkZW50cyA8LSBzdHVkZW50cyAlPiUgDQogICAgbXV0YXRlKGRpc2FiaWxpdHkgPSBhcy5mYWN0b3IoZGlzYWJpbGl0eSkpDQpgYGANCg0KIyMjIyAqKvCfkYkgWW91ciBUdXJuKiogKiripLUqKg0KDQpJbiB0aGUgY2h1bmsgYmVsb3csIHVzZSB0aGUgYHZpZXcoKWAgZnVuY3Rpb24gdG8gbWFudWFsbHkgY2hlY2sgYW5kIHNlZQ0KaWYgb3VyIG5ldyB2YXJpYWJsZSBoYXMgaW5kZWVkIGJlZW4gYWRkZWQgdG8gb3VyIGRhdGEgZnJhbWUuDQoNCmBgYHtyLCBldmFsPUZBTFNFfQ0KdmlldygiZGlzYWJpbGl0eSIpDQpgYGANCg0KV3JpdGUgZG93biBhIGZldyBvYnNlcnZhdGlvbnMgYWZ0ZXIgaW5zcGVjdGluZyB0aGUgZGF0YToNCg0KRm9yIG1lIHRoaXMgb3BlbnMgdXAgYSAyWDIgdGFibGUgd2l0aCBkaXNhYmlsaXR5IGFzIGFuICd4JyB2YXJpYWJsZSB2YWx1ZS4gSSBob25lc3RseSBhbSBub3Qgc3VyZSBpZiBJIGFtIGRvaW5nIHRoaXMgY29ycmVjdGx5Lg0KDQoqKk5vdGUqKjogVGhlIFtgbXV0YXRlKClgDQpmdW5jdGlvbl0oaHR0cHM6Ly9kcGx5ci50aWR5dmVyc2Uub3JnL3JlZmVyZW5jZS9tdXRhdGUuaHRtbCM6fjp0ZXh0PW11dGF0ZSgpJTIwY3JlYXRlcyUyMG5ldyUyMGNvbHVtbnMsc2V0dGluZyUyMHRoZWlyJTIwdmFsdWUlMjB0byUyME5VTEwlMjApLikNCmlzIGEgY3JpdGljYWwgZnVuY3Rpb24gdG8gbGVhcm4gYW5kIGlzIHVzZWQgdG8gY3JlYXRlIG5ldyBjb2x1bW5zIHRoYXQNCmFyZSBmdW5jdGlvbnMgb2YgZXhpc3RpbmcgdmFyaWFibGVzLiBJdCBjYW4gYWxzbyBtb2RpZnkgKGlmIHRoZSBuYW1lIGlzDQp0aGUgc2FtZSBhcyBhbiBleGlzdGluZyBjb2x1bW4pIGFuZCBkZWxldGUgY29sdW1ucyAoYnkgc2V0dGluZyB0aGVpcg0KdmFsdWUgdG8gYE5VTExgKS4NCg0KTmV4dCwgbGV0J3MgZG8gc29tZXRoaW5nIHNpbWlsYXIgZm9yIHdoZXRoZXIgYSBzdHVkZW50IGlkZW50aWZpZXMgYXMNCmhhdmluZyBhIGRpc2FiaWxpdHkuIEluIHRoaXMgY2FzZSwgdGhlcmUgYXJlIG9ubHkgdHdvIHZhbHVlcyBmb3IgdGhlDQpkaXNhYmlsaXR5IHZhcmlhYmxlLCBzbyB3ZSBjYW4gc2ltcGx5IGNvbnZlcnQgaXQgZGlyZWN0bHkgdG8gYSBmYWN0b3IuDQpMb29rIGF0IHRoZSBjb2RlIHlvdSB1c2VkIGFib3ZlLCAqbW9kaWZ5aW5nKiBpdCBmb3IgdGhlIGRpc2FiaWxpdHkNCnZhcmlhYmxlLiBUaGlzIHdpbGwgYmUgYW4gKmluZGVwZW5kZW50IHZhcmlhYmxlKiwgb3IgYSBwcmVkaWN0b3INCnZhcmlhYmxlLg0KDQpgYGB7cn0NCnN0dWRlbnRzIDwtIHN0dWRlbnRzICU+JSANCiAgICBtdXRhdGUoZGlzYWJpbGl0eSA9IGFzLmZhY3RvcihkaXNhYmlsaXR5KSkNCmBgYA0KDQojIyAzLiBFWFBMT1JFDQoNCkFzIG5vdGVkIGJ5IEtydW1tIGV0IGFsLsKgKDIwMTgpLCBleHBsb3JhdG9yeSBkYXRhIGFuYWx5c2lzIG9mdGVuDQppbnZvbHZlcyBzb21lIGNvbWJpbmF0aW9uIG9mIGRhdGEgdmlzdWFsaXphdGlvbiBhbmQgKmZlYXR1cmUNCmVuZ2luZWVyaW5nKi4gSW4gUGFydCAzLCB3ZSB3aWxsIGNyZWF0ZSBhIHF1aWNrIHZpc3VhbGl6YXRpb24gdG8gaGVscCB1cw0Kc3BvdCBhbnkgcG90ZW50aWFsIGlzc3VlcyB3aXRoIG91ciBkYXRhIGFuZCBlbmdpbmVlciBuZXcgcHJlZGljdGl2ZQ0KdmFyaWFibGVzIG9yICJmZWF0dXJlcyIgdGhhdCB3ZSB3aWxsIHVzZSBpbiBvdXIgcHJlZGljdGl2ZSBtb2RlbHMuDQpTcGVjaWZpY2FsbHksIGluIFBhcnQgMyB3ZSB3aWxsOg0KDQoxLiAgKipFeGFtaW5lIE91dGNvbWVzKiogYnkgdGFraW5nIGEgcXVpY2sgYGNvdW50KClgIG9mIHRoZSBudW1iZXIgb2YNCiAgICBzdHVkZW50cyBhbmQgdGhlIG51bWJlciBvZiBzcGVjaWZpYyBvZmZlcmluZ3Mgb2YgZWFjaCBjb3Vyc2UgbW9kdWxlLg0KDQoyLiAgKipFbmdpbmVlciBQcmVkaWN0b3JzKiogYnkgY3JlYXRpbmcgb25lIG1vcmUgcHJlZGljdG9yIHZhcmlhYmxlDQogICAgYmFzZWQgb24gYSBtZWFzdXJlIG9mIHNvY2lvZWNvbm9taWMgcmVzb3VyY2VzLS10aGUgaW5kZXggb2YgbXVsdGlwbGUNCiAgICBkZXByYXZpdHkgdmFyaWFibGUuDQoNCiMjIyAzYS4gRXhhbWluZSBWYXJpYWJsZXMNCg0KQSB1c2VmdWwgZnVuY3Rpb24gZm9yIGV4cGxvcmluZyBkYXRhIGlzIGBjb3VudCgpYDsgaXQgZG9lcyB3aGF0IGl0DQpzb3VuZHMgbGlrZSEgSXQgY291bnRzIGhvdyBtYW55IHRpbWVzIHZhbHVlcyBmb3IgYSB2YXJpYWJsZSBhcHBlYXIuDQoNClJlZmVycmluZyB0byB0aGUgW2RhdGENCmRlc2NyaXB0aW9uXShodHRwczovL2FuYWx5c2Uua21pLm9wZW4uYWMudWsvb3Blbl9kYXRhc2V0I2Rlc2NyaXB0aW9uKSwNCmluIHRoZSBjaHVuayBiZWxvdywgY291bnQgdGhlIG51bWJlciBvZiBzdHVkZW50cy4gQWxzbywgY291bnQgdGhlIG51bWJlcg0Kb2YgY291cnNlcyAobW9kdWxlcykgYW5kIHNwZWNpZmljIG9mZmVyaW5ncyAoYXMgbW9kdWxlcyBjYW4gYmUgb2ZmZXJlZA0KbXVsdGlwbGUgdGltZXMgcGVyIHllYXIpLiBMZWFybiBtb3JlIGFib3V0IGBjb3VudCgpYA0KW2hlcmVdKGh0dHBzOi8vZHBseXIudGlkeXZlcnNlLm9yZy9yZWZlcmVuY2UvY291bnQuaHRtbCkuDQoNCmBgYHtyfQ0Kc3R1ZGVudHMgJT4lIA0KICAgIGNvdW50KGlkX3N0dWRlbnQpICMgdGhpcyBtYW55IHN0dWRlbnRzDQoNCnN0dWRlbnRzICU+JSANCiAgICBjb3VudChjb2RlX21vZHVsZSwgY29kZV9wcmVzZW50YXRpb24pICMgdGhpcyBtYW55IG9mZmVyaW5ncw0KYGBgDQoNCiMjIyAzYi4gRmVhdHVyZSBFbmdpbmVlcmluZw0KDQpBcyBkZWZpbmVkIGJ5IEtydW1tLCBNZWFucywgYW5kIEJpZW5rb3dza2kgKDIwMTgpIGluICpMZWFybmluZyBBbmFseXRpY3MNCkdvZXMgdG8gU2Nob29sKjoNCg0KPiAqKkZlYXR1cmUgZW5naW5lZXJpbmcqKiBpcyB0aGUgcHJvY2VzcyBvZiBjcmVhdGluZyBuZXcgdmFyaWFibGVzDQo+IHdpdGhpbiBhIGRhdGFzZXQsIHdoaWNoIGdvZXMgYWJvdmUgYW5kIGJleW9uZCB0aGUgd29yayBvZiByZWNvZGluZyBhbmQNCj4gcmVzY2FsaW5nIHZhcmlhYmxlcy4NCg0KVGhlIGF1dGhvcnMgbm90ZSB0aGF0IGZlYXR1cmUgZW5naW5lZXJpbmcgZHJhd3Mgb24gc3Vic3RhbnRpdmUga25vd2xlZGdlDQpmcm9tIHRoZW9yeSBvciBwcmFjdGljZSwgZXhwZXJpZW5jZSB3aXRoIGEgcGFydGljdWxhciBkYXRhIHN5c3RlbSwgYW5kDQpnZW5lcmFsIGV4cGVyaWVuY2UgaW4gZGF0YS1pbnRlbnNpdmUgcmVzZWFyY2guIE1vcmVvdmVyLCB0aGVzZSBmZWF0dXJlcw0KY2FuIGJlIHVzZWQgbm90IG9ubHkgaW4gbWFjaGluZSBsZWFybmluZyBtb2RlbHMsIGJ1dCBhbHNvIGluDQp2aXN1YWxpemF0aW9ucyBhbmQgdGFibGVzIGNvbXByaXNpbmcgZGVzY3JpcHRpdmUgc3RhdGlzdGljcy4NCg0KVGhvdWdoIG5vdCBvZnRlbiBkaXNjdXNzZWQsIGZlYXR1cmUgZW5naW5lZXJpbmcgaXMgYW4gaW1wb3J0YW50IGVsZW1lbnQNCm9mIGRhdGEtaW50ZW5zaXZlIHJlc2VhcmNoIHRoYXQgY2FuIGdlbmVyYXRlIG5ldyBpbnNpZ2h0cyBhbmQgaW1wcm92ZQ0KcHJlZGljdGl2ZSBtb2RlbHMuIFlvdSBjYW4gcmVhZCBtb3JlIGFib3V0IGZlYXR1cmUgZW5naW5lZXJpbmcNCltoZXJlXShodHRwczovL3d3dy50bXdyLm9yZy9yZWNpcGVzLmh0bWwpLg0KDQojIyMjIFN0dWRlbnQgU29jaW9lY29ub21pYyBJbmRleA0KDQpGb3Igb3VyIGZpcnN0IGxhYiwgd2UnbGwgZW5nYWdlIGluIGEgdmVyeSBiYXNpYyBmZWF0dXJlIGVuZ2luZWVyaW5nDQpzdGVwLCB0aG91Z2ggd2UnbGwgZG8gdGhpcyAqbXVjaCogbW9yZSBpbiB0aGUgbmV4dCBsZWFybmluZyBsYWIuDQoNClRvIGRvIGZlYXR1cmUgZW5naW5lZXJpbmcsIGxldCdzIGNyZWF0ZSBvbmUgbW9yZSBwcmVkaWN0b3IgdmFyaWFibGUNCmJhc2VkIG9uIGEgbWVhc3VyZSBvZiBzb2Npb2Vjb25vbWljIHJlc291cmNlcy0tdGhlIGluZGV4IG9mIG11bHRpcGxlDQpkZXByYXZpdHkgdmFyaWFibGUuIFRoZSBwcm9jZXNzIHdlIHRha2UgaGVyZSBpcyB0byB0dXJuIHRoaXMgdmFyaWFibGUNCnRoYXQgaXMgYSBjaGFyYWN0ZXIgc3RyaW5nIGludG8gYSBudW1iZXIgYnkgY3JlYXRpbmcgYSBmYWN0b3IgYW5kIHRoZW4NCipjb2VyY2luZyogaXQgdG8gYW4gaW50ZWdlci4NCg0KIyMjIyAqKvCfkYkgWW91ciBUdXJuKiogKiripLUqKg0KDQpQbGVhc2UgcmVwbGFjZSB0aGUgXF9cX1xfXF8gdmFsdWVzIGluIHRoZSBjb2RlIGJlbG93IHdpdGggdGhlIGNvcnJlY3QNCnZhcmlhYmxlLg0KDQpgYGB7cn0NCnN0dWRlbnRzIDwtIHN0dWRlbnRzICU+JSANCiAgICBtdXRhdGUoaW1kX2JhbmQgPSBmYWN0b3IoaW1kX2JhbmQsIGxldmVscyA9IGMoIjAtMTAlIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIjEwLTIwJSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICIyMC0zMCUiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiMzAtNDAlIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIjQwLTUwJSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICI1MC02MCUiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiNjAtNzAlIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIjcwLTgwJSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICI4MC05MCUiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiOTAtMTAwJSIpKSkgJT4lICMgdGhpcyBjcmVhdGVzIGEgZmFjdG9yIHdpdGggb3JkZXJlZCBsZXZlbHMNCiAgICBtdXRhdGUoaW1kX2JhbmQgPSBhcy5pbnRlZ2VyKGltZF9iYW5kKSkgIyB0aGlzIGNoYW5nZXMgdGhlIGxldmVscyBpbnRvIGludGVnZXJzIGJhc2VkIG9uIHRoZSBvcmRlciBvZiB0aGUgZmFjdG9yIGxldmVscw0KDQpzdHVkZW50cw0KYGBgDQoNCldlJ3JlIG5vdyByZWFkeSB0byBwcm9jZWVkIHRvIHRoZSBmaXZlIG1hY2hpbmUgbGVhcm5pbmcgc3RlcHMhDQoNCiMjIDQuIE1PREVMDQoNClJlY2FsbCBmcm9tIG91ciBmaXJzdCByZWFkaW5nIHRoYXQgdGhlcmUgYXJlIHR3byBnZW5lcmFsIHR5cGVzIG9mDQptb2RlbGluZyBhcHByb2FjaGVzOiB1bnN1cGVydmlzZWQgYW5kIHN1cGVydmlzZWQgbWFjaGluZSBsZWFybmluZy4gSW4NClBhcnQgNCwgd2UgZm9jdXMgb24gc3VwZXJ2aXNlZCBsZWFybmluZyBtb2RlbHMsIHdoaWNoIGFyZSB1c2VkIHRvDQpxdWFudGlmeSByZWxhdGlvbnNoaXBzIGJldHdlZW4gZmVhdHVyZXMgKGUuZy4sIG1vdGl2YXRpb24gYW5kDQpwZXJmb3JtYW5jZSkgYW5kIGEga25vd24gb3V0Y29tZSAoZS5nLiwgc3R1ZGVudCBkcm9wIG91dCkuIFRoZXNlIG1vZGVscw0KY2FuIGJlIHVzZWQgZm9yIGNsYXNzaWZpY2F0aW9uIG9mIGJpbmFyeSBvciBjYXRlZ29yaWNhbCBvdXRjb21lcywgYXMNCndlJ2xsIGlsbHVzdHJhdGUgaW4gdGhpcyBzZWN0aW9uLCBvciByZWdyZXNzaW9uIGFzIHdlJ2xsIGRlbW9uc3RyYXRlIGluDQpMZWFybmluZyBMYWIgMi4NCg0KU3BlY2lmaWNhbGx5LCBpbiBQYXJ0IDQgd2Ugd2lsbCBsZWFybiBob3cgdG86DQoNCjEuICAqKlNwbGl0IERhdGEqKiBpbnRvIGEgdHJhaW5pbmcgYW5kIHRlc3Qgc2V0IHRoYXQgd2lsbCBiZSB1c2VkIHRvDQogICAgZGV2ZWxvcCBhIHByZWRpY3RpdmUgbW9kZWw7DQoNCjIuICAqKkNyZWF0ZSBhICJSZWNpcGUiKiogZm9yIG91ciBwcmVkaWN0aXZlIG1vZGVsIGFuZCBsZWFybiBob3cgdG8gZGVhbA0KICAgIHdpdGggbm9taW5hbCBkYXRhIHRoYXQgd2Ugd291bGQgbGlrZSB0byB1c2UgYXMgcHJlZGljdG9yczsNCg0KMy4gICoqU3BlY2lmeSB0aGUgbW9kZWwgYW5kIHdvcmtmbG93KiogYnkgc2VsZWN0aW5nIHRoZSAqZnVuY3Rpb25hbA0KICAgIGZvcm0qIG9mIHRoZSBtb2RlbCB0aGF0IHdlIHdhbnQgYW5kIHVzaW5nIGEgKm1vZGVsIHdvcmtmbG93KiB0byBwYWlyDQogICAgb3VyIG1vZGVsIGFuZCByZWNpcGUgdG9nZXRoZXI7DQoNCjQuICAqKkZpdCBNb2RlbHMqKiB0byBvdXIgdHJhaW5pbmcgc2V0IHVzaW5nIGxvZ2lzdGljIHJlZ3Jlc3Npb247DQoNCjUuICAqKkludGVycHJldCBBY2N1cmFjeSoqIG9mIG91ciBtb2RlbCB0byBzZWUgaG93IHdlbGwgb3VyIG1vZGVsIGNhbg0KICAgICJwcmVkaWN0IiBvdXIgb3V0Y29tZSBvZiBpbnRlcmVzdC4NCg0KIyMjIFN0ZXAgMS4gU3BsaXQgZGF0YQ0KDQpUaGUgYXV0aG9ycyBvZiBEYXRhIFNjaWVuY2UgaW4gRWR1Y2F0aW9uIFVzaW5nIFIgKEVzdHJlbGxhZG8gZXQNCmFsLiwyMDIwKSByZW1pbmQgdXMgdGhhdDoNCg0KPiBBdCBpdHMgY29yZSwgbWFjaGluZSBsZWFybmluZyBpcyB0aGUgcHJvY2VzcyBvZiAic2hvd2luZyIgeW91cg0KPiBzdGF0aXN0aWNhbCBtb2RlbCBvbmx5IHNvbWUgb2YgdGhlIGRhdGEgYXQgb25jZSBhbmQgdHJhaW5pbmcgdGhlIG1vZGVsDQo+IHRvIHByZWRpY3QgYWNjdXJhdGVseSBvbiB0aGF0IHRyYWluaW5nIGRhdGFzZXQgKHRoaXMgaXMgdGhlICJsZWFybmluZyINCj4gcGFydCBvZiBtYWNoaW5lIGxlYXJuaW5nKS4gVGhlbiwgdGhlIG1vZGVsIGFzIGRldmVsb3BlZCBvbiB0aGUNCj4gdHJhaW5pbmcgZGF0YSBpcyBzaG93biBuZXcgZGF0YSAtIGRhdGEgeW91IGhhZCBhbGwgYWxvbmcsIGJ1dCBoaWQgZnJvbQ0KPiB5b3VyIGNvbXB1dGVyIGluaXRpYWxseSAtIGFuZCB5b3Ugc2VlIGhvdyB3ZWxsIHRoZSBtb2RlbCB0aGF0IHlvdQ0KPiBkZXZlbG9wZWQgb24gdGhlIHRyYWluaW5nIGRhdGEgcGVyZm9ybXMgb24gdGhpcyBuZXcgdGVzdGluZyBkYXRhLg0KPiBFdmVudHVhbGx5LCB5b3UgbWlnaHQgdXNlIHRoZSBtb2RlbCBvbiBlbnRpcmVseSBuZXcgZGF0YS4NCg0KIyMjIyBUcmFpbmluZyBhbmQgVGVzdGluZyBTZXRzDQoNCkl0IGlzIHRoZXJlZm9yZSBjb21tb24gd2hlbiBiZWdpbm5pbmcgYSBtb2RlbGluZyBwcm9qZWN0IHRvIFtzZXBhcmF0ZQ0KdGhlIGRhdGEgc2V0XShodHRwczovL2Jvb2tkb3duLm9yZy9tYXgvRkVTL2RhdGEtc3BsaXR0aW5nLmh0bWwpIGludG8gdHdvDQpwYXJ0aXRpb25zOg0KDQotICAgVGhlICp0cmFpbmluZyBzZXQqIGlzIHVzZWQgdG8gZXN0aW1hdGUsIGRldmVsb3AgYW5kIGNvbXBhcmUgbW9kZWxzOw0KICAgIGZlYXR1cmUgZW5naW5lZXJpbmcgdGVjaG5pcXVlczsgdHVuZSBtb2RlbHMsIGV0Yy4NCg0KLSAgIFRoZSAqdGVzdCBzZXQqIGlzIGhlbGQgaW4gcmVzZXJ2ZSB1bnRpbCB0aGUgZW5kIG9mIHRoZSBwcm9qZWN0LCBhdA0KICAgIHdoaWNoIHBvaW50IHRoZXJlIHNob3VsZCBvbmx5IGJlIG9uZSBvciB0d28gbW9kZWxzIHVuZGVyIHNlcmlvdXMNCiAgICBjb25zaWRlcmF0aW9uLiBJdCBpcyB1c2VkIGFzIGFuIHVuYmlhc2VkIHNvdXJjZSBmb3IgbWVhc3VyaW5nIGZpbmFsDQogICAgbW9kZWwgcGVyZm9ybWFuY2UuDQoNClRoZXJlIGFyZSBkaWZmZXJlbnQgd2F5cyB0byBjcmVhdGUgdGhlc2UgcGFydGl0aW9ucyBvZiB0aGUgZGF0YSBhbmQNCnRoZXJlIGlzIG5vIHVuaWZvcm0gZ3VpZGVsaW5lIGZvciBkZXRlcm1pbmluZyBob3cgbXVjaCBkYXRhIHNob3VsZCBiZQ0Kc2V0IGFzaWRlIGZvciB0ZXN0aW5nLiBUaGUgcHJvcG9ydGlvbiBvZiBkYXRhIGNhbiBiZSBkcml2ZW4gYnkgbWFueQ0KZmFjdG9ycywgaW5jbHVkaW5nIHRoZSBzaXplIG9mIHRoZSBvcmlnaW5hbCBwb29sIG9mIHNhbXBsZXMgYW5kIHRoZQ0KdG90YWwgbnVtYmVyIG9mIHByZWRpY3RvcnMuwqANCg0KQWZ0ZXIgeW91IGRlY2lkZSBob3cgbXVjaCB0byBzZXQgYXNpZGUsIHRoZSBtb3N0IGNvbW1vbiBhcHByb2FjaCBmb3INCmFjdHVhbGx5IHBhcnRpdGlvbmluZyB5b3VyIGRhdGEgaXMgdG8gdXNlIGEgcmFuZG9tIHNhbXBsZS4gRm9yIG91cg0KcHVycG9zZXMsIHdlJ2xsIHVzZSByYW5kb20gc2FtcGxpbmcgdG8gc2VsZWN0IDIwJSBmb3IgdGhlIHRlc3Qgc2V0IGFuZA0KdXNlIHRoZSByZW1haW5kZXIgZm9yIHRoZSB0cmFpbmluZyBzZXQsIHdoaWNoIGFyZSB0aGUgZGVmYXVsdHMgZm9yIHRoZQ0Ke1tyc2FtcGxlXShodHRwczovL3RpZHltb2RlbHMuZ2l0aHViLmlvL3JzYW1wbGUvKX0gcGFja2FnZS4NCg0KIyMjIyBTcGxpdCBEYXRhIFNldHMNCg0KVG8gc3BsaXQgb3VyIGRhdGEsIHdlIHdpbGwgYmUgdXNpbmcgb3VyIGZpcnN0IHt0aWR5bW9kZWxzfSBmdW5jdGlvbiAtDQpgaW5pdGlhbF9zcGxpdCgpYC4NCg0KVGhlIGZ1bmN0aW9uIGBpbml0aWFsX3NwbGl0KClgIGZ1bmN0aW9uIGZyb20gdGhlIHtyc2FtcGxlfSBwYWNrYWdlIHRha2VzDQp0aGUgb3JpZ2luYWwgZGF0YSBhbmQgc2F2ZXMgdGhlIGluZm9ybWF0aW9uIG9uIGhvdyB0byBtYWtlIHRoZQ0KcGFydGl0aW9ucy4gVGhlIHtyc2FtcGxlfSBwYWNrYWdlIGhhcyB0d28gYXB0bHkgbmFtZWQgZnVuY3Rpb25zIGZvcg0KY3JlYXRlZCBhIHRyYWluaW5nIGFuZCB0ZXN0aW5nIGRhdGEgc2V0IGNhbGxlZCBgdHJhaW5pbmcoKWAgYW5kDQpgdGVzdGluZygpYCwgcmVzcGVjdGl2ZWx5Lg0KDQpSdW4gdGhlIGZvbGxvd2luZyBjb2RlIHRvIHNwbGl0IHRoZSBkYXRhOg0KDQpgYGB7cn0NCg0KYGBgDQoNCioqTm90ZSoqOiBTaW5jZSByYW5kb20gc2FtcGxpbmcgdXNlcyByYW5kb20gbnVtYmVycywgaXQgaXMgaW1wb3J0YW50IHRvDQpzZXQgdGhlIHJhbmRvbSBudW1iZXIgc2VlZCB1c2luZyB0aGUgYHNldC5zZWVkKClgIGZ1bmN0aW9uLiBUaGlzIGVuc3VyZXMNCnRoYXQgdGhlIHJhbmRvbSBudW1iZXJzIGNhbiBiZSByZXByb2R1Y2VkIGF0IGEgbGF0ZXIgdGltZSAoaWYgbmVlZGVkKS4NCldlIHBpY2sgdGhlIGZpcnN0IGRhdGUgb24gd2hpY2ggd2UgbWF5IGNhcnJ5IG91dCB0aGlzIGxlYXJuaW5nIGxhYiBhcw0KdGhlIHNlZWQsIGJ1dCBhbnkgbnVtYmVyIHdpbGwgd29yayENCg0KIyMjIyAqKvCfkYkgWW91ciBUdXJuKiogKiripLUqKg0KDQpHbyBhaGVhZCBhbmQgdHlwZSBgZGF0YV90cmFpbmAgYW5kIGBkYXRhX3Rlc3RgIGludG8gdGhlIGNvbnNvbGUgKGluDQpzdGVwcykgdG8gY2hlY2sgdGhhdCB0aGlzIGRhdGEgc2V0IGluZGVlZCBoYXMgODAlIG9mIHRoZSBudW1iZXIgb2YNCm9ic2VydmF0aW9ucyBhcyBpbiB0aGUgbGFyZ2VyIGRhdGEuIERvIHRoYXQgaW4gdGhlIGNodW5rIGJlbG93Og0KDQpgYGB7cn0NCg0KDQpgYGANCg0KIyMjIFN0ZXAgMjogQ3JlYXRlIGEgIlJlY2lwZSINCg0KSW4gdGhpcyBzZWN0aW9uLCB3ZSBpbnRyb2R1Y2UgYW5vdGhlciB0aWR5bW9kZWxzIHBhY2thZ2UgbmFtZWQNCntbcmVjaXBlc10oaHR0cHM6Ly9yZWNpcGVzLnRpZHltb2RlbHMub3JnLyl9LCB3aGljaCBpcyBkZXNpZ25lZCB0byBoZWxwDQp5b3UgcHJlcGFyZSB5b3VyIGRhdGEgKmJlZm9yZSogdHJhaW5pbmcgeW91ciBtb2RlbC4gUmVjaXBlcyBhcmUgYnVpbHQgYXMNCmEgc2VyaWVzIG9mIHByZXByb2Nlc3Npbmcgc3RlcHMsIHN1Y2ggYXM6DQoNCi0gICBjb252ZXJ0aW5nIHF1YWxpdGF0aXZlIHByZWRpY3RvcnMgdG8gaW5kaWNhdG9yIHZhcmlhYmxlcyAoYWxzbyBrbm93bg0KICAgIGFzIGR1bW15IHZhcmlhYmxlcyksDQoNCi0gICB0cmFuc2Zvcm1pbmcgZGF0YSB0byBiZSBvbiBhIGRpZmZlcmVudCBzY2FsZSAoZS5nLiwgdGFraW5nIHRoZQ0KICAgIGxvZ2FyaXRobSBvZiBhIHZhcmlhYmxlKSwNCg0KLSAgIHRyYW5zZm9ybWluZyB3aG9sZSBncm91cHMgb2YgcHJlZGljdG9ycyB0b2dldGhlciwNCg0KLSAgIGV4dHJhY3Rpbmcga2V5IGZlYXR1cmVzIGZyb20gcmF3IHZhcmlhYmxlcyAoZS5nLiwgZ2V0dGluZyB0aGUgZGF5IG9mDQogICAgdGhlIHdlZWsgb3V0IG9mIGEgZGF0ZSB2YXJpYWJsZSksIGFuZCBzbyBvbi4NCg0KSWYgeW91IGFyZSBmYW1pbGlhciB3aXRoIFIncyBmb3JtdWxhIGludGVyZmFjZSwgYSBsb3Qgb2YgdGhpcyBtaWdodA0Kc291bmQgZmFtaWxpYXIgYW5kIGxpa2Ugd2hhdCBhIGZvcm11bGEgYWxyZWFkeSBkb2VzLiBSZWNpcGVzIGNhbiBiZSB1c2VkDQp0byBkbyBtYW55IG9mIHRoZSBzYW1lIHRoaW5ncywgYnV0IHRoZXkgaGF2ZSBhIG11Y2ggd2lkZXIgcmFuZ2Ugb2YNCnBvc3NpYmlsaXRpZXMuDQoNCiMjIyMgKipBZGQgYSBmb3JtdWxhKioNCg0KVG8gZ2V0IHN0YXJ0ZWQsIGxldCdzIGNyZWF0ZSBhIHJlY2lwZSBmb3IgYSBzaW1wbGUgbG9naXN0aWMgcmVncmVzc2lvbg0KbW9kZWwuIEJlZm9yZSB0cmFpbmluZyB0aGUgbW9kZWwsIHdlIGNhbiB1c2UgYSByZWNpcGUuDQoNClRoZQ0KW2ByZWNpcGUoKWBmdW5jdGlvbl0oaHR0cHM6Ly9yZWNpcGVzLnRpZHltb2RlbHMub3JnL3JlZmVyZW5jZS9yZWNpcGUuaHRtbCkNCmFzIHdlIHVzZWQgaXQgaGVyZSBoYXMgdHdvIGFyZ3VtZW50czoNCg0KLSAgIEEgKipmb3JtdWxhKiouIEFueSB2YXJpYWJsZSBvbiB0aGUgbGVmdC1oYW5kIHNpZGUgb2YgdGhlIHRpbGRlIChgfmApDQogICAgaXMgY29uc2lkZXJlZCB0aGUgbW9kZWwgb3V0Y29tZSAoYGNvZGVgLCBpbiBvdXIgcHJlc2VudCBjYXNlKS4gT24NCiAgICB0aGUgcmlnaHQtaGFuZCBzaWRlIG9mIHRoZSB0aWxkZSBhcmUgdGhlIHByZWRpY3RvcnMuIFZhcmlhYmxlcyBtYXkNCiAgICBiZSBsaXN0ZWQgYnkgbmFtZSwgb3IgeW91IGNhbiB1c2UgdGhlIGRvdCAoYC5gKSB0byBpbmRpY2F0ZSBhbGwNCiAgICBvdGhlciB2YXJpYWJsZXMgYXMgcHJlZGljdG9ycy4NCg0KLSAgIFRoZSAqKmRhdGEqKi4gQSByZWNpcGUgaXMgYXNzb2NpYXRlZCB3aXRoIHRoZSBkYXRhIHNldCB1c2VkIHRvDQogICAgY3JlYXRlIHRoZSBtb2RlbC4gVGhpcyB3aWxsIHR5cGljYWxseSBiZSB0aGUgKnRyYWluaW5nKiBzZXQsIHNvDQogICAgYGRhdGEgPSB0cmFpbl9kYXRhYCBoZXJlLiBOYW1pbmcgYSBkYXRhIHNldCBkb2Vzbid0IGFjdHVhbGx5IGNoYW5nZQ0KICAgIHRoZSBkYXRhIGl0c2VsZjsgaXQgaXMgb25seSB1c2VkIHRvIGNhdGFsb2cgdGhlIG5hbWVzIG9mIHRoZQ0KICAgIHZhcmlhYmxlcyBhbmQgdGhlaXIgdHlwZXMsIGxpa2UgZmFjdG9ycywgaW50ZWdlcnMsIGRhdGVzLCBldGMuDQoNCkxldCdzIGNyZWF0ZSBhIHJlY2lwZSB3aGVyZSB3ZSBwcmVkaWN0IGBwYXNzYCAodGhlIG91dGNvbWUgdmFyaWFibGUpIG9uDQp0aGUgYmFzaXMgb2YgdGhlIGBkaXNhYmlsaXR5YCBhbmQgYGltZF9iYW5kYCAocHJlZGljdG9yKSB2YXJpYWJsZXMuDQoNCg0KDQojIyMgU3RlcCAzOiBTcGVjaWZ5IHRoZSBtb2RlbCBhbmQgd29ya2Zsb3cNCg0KV2l0aCB0aWR5bW9kZWxzLCB3ZSBzdGFydCBidWlsZGluZyBhIG1vZGVsIGJ5IHNwZWNpZnlpbmcgdGhlICpmdW5jdGlvbmFsDQpmb3JtKiBvZiB0aGUgbW9kZWwgdGhhdCB3ZSB3YW50IHVzaW5nIHRoZSBbe3BhcnNuaXB9DQpwYWNrYWdlXShodHRwczovL3RpZHltb2RlbHMuZ2l0aHViLmlvL3BhcnNuaXAvKS4gU2luY2Ugb3VyIG91dGNvbWUgaXMNCmJpbmFyeSwgdGhlIG1vZGVsIHR5cGUgd2Ugd2lsbCB1c2UgaXPCoCJbbG9naXN0aWMNCnJlZ3Jlc3Npb25dKGh0dHBzOi8vcGFyc25pcC50aWR5bW9kZWxzLm9yZy9yZWZlcmVuY2UvbG9naXN0aWNfcmVnLmh0bWwpLiINCldlIGNhbiBkZWNsYXJlIHRoaXMgd2l0aCBgbG9naXN0aWNfcmVnKClgIGFuZCBhc3NpZ24gdG8gYW4gb2JqZWN0IHdlDQp3aWxsIGxhdGVyIHVzZSBpbiBvdXIgd29ya2Zsb3c6DQoNClJ1biB0aGUgZm9sbG93aW5nIGNvZGUgdG8gZmluaXNoIHNwZWNpZnlpbmcgb3VyIG1vZGVsOg0KDQoNClRoYXQgaXMgcHJldHR5IHVuZGVyd2hlbG1pbmcgc2luY2UsIG9uIGl0cyBvd24sIGl0IGRvZXNuJ3QgcmVhbGx5IGRvDQptdWNoLiBIb3dldmVyLCBub3cgdGhhdCB0aGUgdHlwZSBvZiBtb2RlbCBoYXMgYmVlbiBzcGVjaWZpZWQsIGEgbWV0aG9kDQpmb3LCoCpmaXR0aW5nKsKgb3IgdHJhaW5pbmcgdGhlIG1vZGVsIGNhbiBiZSBzdGF0ZWQgdXNpbmcgdGhlICoqZW5naW5lKiouDQoNCiMjIyMgKipTdGFydCB5b3VyIGVuZ2luZSoqDQoNClRvIHNldCB0aGUgKmVuZ2luZSwqIGxldCdzIHJld3JpdGUgdGhlIGNvZGUgYWJvdmUgYW5kICJwaXBlIiBpbiB0aGUNCmBzZXRfZW5naW5lKCJnbG0iKWAgZnVuY3Rpb24gYW5kIGBzZXRfbW9kZSgiY2xhc3NpZmljYXRpb24iKSlgIHRvIHNldA0KdGhlICIqbW9kZSoiIHRvIGNsYXNzaWZpY2F0aW9uLiBOb3RlIHRoYXQgdGhpcyBjb3VsZCBhbHNvIGJlIGNoYW5nZWQgdG8NCnJlZ3Jlc3Npb24gZm9yIGEgY29udGludW91cy9udW1lcmljIG91dGNvbWUuDQoNClJ1biB0aGUgZm9sbG93aW5nIGNvZGUgdG8gZmluaXNoIHNwZWNpZnlpbmcgb3VyIG1vZGVsOg0KDQoNClRoZSBlbmdpbmUgdmFsdWUgaXMgb2Z0ZW4gYSBtYXNoLXVwIG9mIGRpZmZlcmVudCBwYWNrYWdlcyB0aGF0IGNhbiBiZQ0KdXNlZCB0byBmaXQgb3IgdHJhaW4gdGhlIG1vZGVsIGFzIHdlbGwgYXMgdGhlIGVzdGltYXRpb24gbWV0aG9kLiBGb3INCmV4YW1wbGUsIHdlIHdpbGwgdXNlICJnbG0iIGEgZ2VuZXJhbGl6ZWQgbGluZWFyIG1vZGVsIGZvciBiaW5hcnkNCm91dGNvbWVzIGFuZCBkZWZhdWx0IGZvciBsb2dpc3RpYyByZWdyZXNzaW9uIGluIHRoZSB7cGFyc25pcH0gcGFja2FnZS4NCg0KIyMjIyAqKkFkZCB0byB3b3JrZmxvdyoqDQoNCk5vdyB3ZSBjYW4gdXNlIHRoZSByZWNpcGUgY3JlYXRlZCBlYXJsaWVyIGFjcm9zcyBzZXZlcmFsIHN0ZXBzIGFzIHdlDQp0cmFpbiBhbmQgdGVzdCBvdXIgbW9kZWwuIFRvIHNpbXBsaWZ5IHRoaXMgcHJvY2Vzcywgd2UgY2FuIHVzZSBhICptb2RlbA0Kd29ya2Zsb3cqLCB3aGljaCBwYWlycyBhIG1vZGVsIGFuZCByZWNpcGUgdG9nZXRoZXIuDQoNClRoaXMgaXMgYSBzdHJhaWdodGZvcndhcmQgYXBwcm9hY2ggYmVjYXVzZSBkaWZmZXJlbnQgcmVjaXBlcyBhcmUgb2Z0ZW4NCm5lZWRlZCBmb3IgZGlmZmVyZW50IG1vZGVscywgc28gd2hlbiBhIG1vZGVsIGFuZCByZWNpcGUgYXJlIGJ1bmRsZWQsIGl0DQpiZWNvbWVzIGVhc2llciB0byB0cmFpbiBhbmQgdGVzdMKgKndvcmtmbG93cyouDQoNCldlJ2xsIHVzZSB0aGV7W3dvcmtmbG93c10oaHR0cHM6Ly93b3JrZmxvd3MudGlkeW1vZGVscy5vcmcvKX0gcGFja2FnZQ0KZnJvbSB0aWR5bW9kZWxzIHRvIGJ1bmRsZSBvdXIgcGFyc25pcCBtb2RlbCAoYGxyX21vZGApIHdpdGggb3VyIGZpcnN0DQpyZWNpcGUgKGBscl9yZWNpcGVfMWApLg0KDQoNCiMjIyBTdGVwIDQ6IEZpdCBtb2RlbA0KDQpOb3cgdGhhdCB3ZSBoYXZlIGEgc2luZ2xlIHdvcmtmbG93IHRoYXQgY2FuIGJlIHVzZWQgdG8gcHJlcGFyZSB0aGUNCnJlY2lwZSBhbmQgdHJhaW4gdGhlIG1vZGVsIGZyb20gdGhlIHJlc3VsdGluZyBwcmVkaWN0b3JzLCB3ZSBjYW4gdXNlIHRoZQ0KYGZpdCgpYCBmdW5jdGlvbiB0byBmaXQgb3VyIG1vZGVsIHRvIG91ciBgdHJhaW5fZGF0YWAuIEFuZCBhZ2Fpbiwgd2Ugc2V0DQphIHJhbmRvbSBudW1iZXIgc2VlZCB0byBlbnN1cmUgdGhhdCBpZiB3ZSBydW4gdGhpcyBzYW1lIGNvZGUgYWdhaW4sIHdlDQp3aWxsIGdldCB0aGUgc2FtZSByZXN1bHRzIGluIHRlcm1zIG9mIHRoZSBkYXRhIHBhcnRpdGlvbjoNCg0KRmluYWxseSwgd2UnbGwgZml0IG91ciBtb2RlbC4NCg0KDQojIyMjICoq8J+RiSBZb3VyIFR1cm4qKiAqKuKktSoqDQoNCkltcG9ydGFudGx5LCBoZXJlLCB3ZSBjYW4gbG9vayBhdCB0aGUgbW9kZWwuIFR5cGUgYGZpdHRlZF9tb2RlbGAgaW4gYQ0KY29kZSBjaHVuayB0byB0YWtlIGEgbG9vay4NCg0KDQpOb3RlIHRoYXQgd2hpbGUgd2UgZG9uJ3QgdHlwaWNhbGx5IGludGVycHJldCB0aGUgY29lZmZpY2llbnRzIGZvciBhDQptYWNoaW5lIGxlYXJuaW5nIG1vZGVsLCBpdCdzIGltcG9ydGFudCB0byByZWNvZ25pemUgdGhhdCBtYW55IG1vZGVscw0KKmRvKiBwcm9kdWNlIGNvZWZmaWNpZW50cyB3ZSAqY291bGQqIGludGVycHJldC4gSW5zdGVhZCwgd2UgZm9jdXMgb24gaG93DQp0aGUgbW9kZWwgZG9lcyB3aXRoIHJlc3BlY3QgdG8gcHJlZGljdGluZyB0aGUgZGVwZW5kZW50IHZhcmlhYmxlLg0KDQoqKk9ic2VydmF0aW9ucyoqDQoNCldyaXRlIGRvd24gYSBicmllZiBvYnNlcnZhdGlvbiBvZiB0aGUgb3V0cHV0IGFib3ZlLCBmb2N1c2luZyBvbiB0aGUNCmNvZWZmaWNpZW50czoNCg0KLSAgIGRpc2FiaWxpdHkgaGFzIGEgbmVnYXRpdmUgcmVsYXRpb25zaGlwIHdpdGggcGFzcyBhbmQgaW1kX2JhbmQgaGFzIGENCiAgICBwb3NpdGl2ZSByZWxhdGlvbnNoaXAgd2l0aCBwYXNzLg0KDQojIyMjIFRoZSBsYXN0X2ZpdCBmdW5jdGlvbg0KDQpGaW5hbGx5LCB3ZSdsbCB1c2UgdGhlIGBsYXN0X2ZpdGAgZnVuY3Rpb24sIHdoaWNoIGlzIHRoZSBrZXkgaGVyZTogbm90ZQ0KdGhhdCBpdCB1c2VzIHRoZSBgdHJhaW5fdGVzdF9zcGxpdGAgZGF0YS0tLW5vdCBqdXN0IHRoZSB0cmFpbmluZyBkYXRhLg0KDQpIZXJlLCB0aGVuLCB3ZSBmaXQgdGhlIGRhdGEgKnVzaW5nIHRoZSB0cmFpbmluZyBkYXRhIHNldCogYW5kIGV2YWx1YXRlDQppdHMgYWNjdXJhY3kgdXNpbmcgdGhlICp0ZXN0aW5nIGRhdGEgc2V0KiAod2hpY2ggaXMgbm90IHVzZWQgdG8gdHJhaW4NCnRoZSBtb2RlbCkuDQoNCmBgYHtyfQ0KDQpgYGANCg0KVHlwZSBgZmluYWxfZml0YCBiZWxvdzsgdGhpcyBpcyB0aGUgZmluYWwsIGZpdHRlZCBtb2RlbC0tLW9uZSB0aGF0IGNhbg0KYmUgaW50ZXJwcmV0ZWQgZnVydGhlciBpbiB0aGUgbmV4dCBzdGVwIQ0KDQpgYGB7cn0NCg0KYGBgDQoNCllvdSBtYXkgc2VlIGEgbWVzc2FnZS93YXJuaW5nIGFib3ZlIG9yIHdoZW4geW91IGV4YW1pbmUgYGZpbmFsX2ZpdGA7IHlvdQ0KY2FuIHNhZmVseSBpZ25vcmUgdGhhdC4NCg0KIyMjIFN0ZXAgNTogSW50ZXJwcmV0IGFjY3VyYWN5DQoNClJ1biB0aGUgY29kZSBiZWxvdyB0byBleGFtaW5lIHRoZSBwcmVkaWN0aW9ucyBmb3IgdGhlICp0ZXN0KiBzcGxpdCBvZg0KZGF0YS4gTm90ZSB0aGF0IHRoZSByb3cgSUQgaXMgaW4gdGhlIG91dHB1dCBiZWxvdywgYnV0IHRoaXMgZG9lc24ndA0KY29ycmVzcG9uZCBvbmUtb25lIHRvIHRoZSBJRCB2YXJpYWJsZXMgdXNlZCBpbiB0aGUgcHJlc2VudGF0aW9uL1NoaW55Lg0KDQoNCg0KVGhpcyBpcyBvdXIgZmlyc3Qgc2V0IG9mIHJlYWwgb3V0cHV0ISBOb3RlIHR3byB0aGluZ3M6DQoNCjEuICBgLnByZXNfY2xhc3NgOiBUaGlzIGlzIHRoZSAqcHJlZGljdGVkKiBjb2RlXA0KMi4gIGBwYXNzYDogVGhpcyBpcyB0aGUga25vd24gKmNvZGUqDQoNCldoZW4gdGhlc2UgYXJlICoqdGhlIHNhbWUqKiwgdGhlIG1vZGVsIHByZWRpY3RlZCB0aGUgY29kZSAqY29ycmVjdGx5KjsNCndoZW4gdGhleSBhcmVuJ3QgdGhlIHNhbWUsIHRoZSBtb2RlbCB3YXMgaW5jb3JyZWN0Lg0KDQpJbXBvcnRhbnRseSwgd2UgY2FuICpzdW1tYXJpemUqIGFjcm9zcyBhbGwgb2YgdGhlc2UgY29kZXMuIE9uZSB3YXkgdG8gZG8NCnRoaXMgaXMgc3RyYWlnaHRmb3J3YXJkOyBob3cgbWFueSBvZiB0aGUgY29kZXMgd2VyZSB0aGUgc2FtZSwgYXMgaW4gdGhlDQpmb2xsb3dpbmcgY2h1bmsgb2YgY29kZToNCg0KDQoNCllvdSBtYXkgbm90aWNlIHNvbWUgb2YgdGhlIHJvd3MgbWF5IGJlIG1pc3NpbmcgdmFsdWVzLiBUaGlzIGlzIGJlY2F1c2UNCnRoZXJlIHdlcmUgc29tZSBtaXNzaW5nIHZhbHVlcyBpbiB0aGUgYGltZF9iYW5kYCB2YXJpYWJsZSwgYW5kIGZvciB0aGlzDQptYWNoaW5lIGxlYXJuaW5nIGFsZ29yaXRobSAodGhlIGdlbmVyYWxpemVkIGxpbmVhciBtb2RlbCksIG1pc3NpbmcNCnZhbHVlcyByZXN1bHQgaW4gcm93LXdpc2UgZGVsZXRpb24uDQoNClRoYXQncyBoZWxwZnVsLCBidXQgdGhlcmUncyBvbmUgbW9yZSBzdGVwIHdlIGNhbiB0YWtlIC0tIGNvdW50aW5nIHVwIHRoZQ0KdmFsdWVzIG9mIGBjb3JyZWN0YDoNCg0KDQoNCiMjIyMgKirwn5GJIFlvdXIgVHVybioqICoq4qS1KioNCg0KTGV0J3MgaW50ZXJwcmV0IHRoZSBhYm92ZS4gSWYgdGhlIHZhbHVlIG9mIGBjb3JyZWN0YCBpcyBgVFJVRWAgd2hlbiB0aGUNCnByZWRpY3RlZCBhbmQga25vd24gY29kZSBhcmUgdGhlIHNhbWUsIHdoYXQgZG9lcyB0aGUgYHBlcmNlbnRgIGNvbHVtbg0KdGVsbCB1cz8gQWRkIG9uZSBvciBtb3JlIG5vdGVzIHRvIHRoZSBkYXNoZXMgYmVsb3c6DQoNCi0gICBJIGNhbm5vdCBzZWVtIHRvIGdldCBhbnkgZnVuY3Rpb25zIHRvIHdvcmsgaW4gcmVsYXRpb24gdG8gdGhpcyBvciBtYW55IG9mIHRoZSBwcmV2aW91cyBjb25jZXB0cy4gSSB3b3VsZCBndWVzcyB0aGF0ICdwZXJjZW50JyBpcyBpbiByZWZlcmVuY2UgdG8gdGhlIGFtb3VudCBvZiB0cnVlIHZhbHVlcy4NCg0KQSBzaG9ydC1jdXQgdG8gdGhlIGFib3ZlIGlzIGJlbG93OyAqKndlJ2xsIHVzZSB0aGlzIHNob3J0LWN1dCBmcm9tIGhlcmUNCmZvcndhcmQqKiwgaGF2aW5nIHNlZW4gaG93IGFjY3VyYWN5IGlzIGNhbGN1bGF0ZWQuDQoNCmBgYHtyfQ0KDQpgYGANCg0KSG93IGFjY3VyYXRlIHdhcyBvdXIgcHJlZGljdGl2ZSBtb2RlbD8gQ29uc2lkZXIgaG93IHdlbGwgb3VyIG1vZGVsIHdvdWxkDQpoYXZlIGRvbmUgYnkgY2hhbmNlIGFsb25lIC0tIHdoYXQgd291bGQgdGhlIGFjY3VyYWN5IGJlIGluIHRoYXQgY2FzZQ0KKHdpdGggdGhlIG1vZGVsIHByZWRpY3RpbmcgcGFzcyBvbmUtaGFsZiBvZiB0aGUgdGltZSk/DQoNCkN1cmlvdXNseSwgcmFuZG9tbHkgcGlja2luZyBhIDAgKGRpZCBub3QgcGFzcykgb3IgYSAxIChwYXNzZWQpIHdpbGwNCiphbHdheXMqIGxlYWQgdG8gYXJvdW5kIGEgNTAlIGFjY3VyYWN5LCByZWdhcmRsZXNzIG9mIGhvdyBtYW55DQpvYnNlcnZhdGlvbnMgYXJlIGFjdHVhbGx5IGFzc29jaWF0ZWQgd2l0aCBhIDAgb3IgYSAxLg0KDQoqKk9ic2VydmF0aW9uKioNCg0KTGV0J3Mgc3RlcCBiYWNrIGEgYml0LiBIb3cgd2VsbCAqY291bGQqIHdlIGRvIGlmIHdlIGluY2x1ZGUgbW9yZSBkYXRhPw0KQW5kIGhvdyAqdXNlZnVsKiBjb3VsZCBzdWNoIGEgbW9kZWwgYmUgaW4gdGhlIHJlYWwgd29ybGQ/IFdlJ2xsIGRpdmUNCmludG8gdGhlc2UgcXVlc3Rpb25zIG1vcmUgb3ZlciB0aGUgZm9ydGhjb21pbmcgbGVhcm5pbmcgbGFicy4NCg0KVGhhdCdzIGl0IGZvciBub3c7IHRoZSBjb3JlIHBhcnRzIG9mIG1hY2hpbmUgbGVhcm5pbmcgYXJlIHVzZWQgaW4gdGhlDQphYm92ZSBzdGVwcyB5b3UgdG9vazsgd2hhdCB3ZSdsbCBkbyBhZnRlciB0aGlzIGxlYW5pbmcgbGFiIG9ubHkgYWRkcw0KbnVhbmNlIGFuZCBjb21wbGV4aXR5IHRvIHdoYXQgd2UndmUgYWxyZWFkeSBkb25lLg0KDQojIyA1LiBDT01NVU5JQ0FURQ0KDQpUaGUgZmluYWwgc3RlcCBpbiB0aGUgd29ya2Zsb3cvcHJvY2VzcyBpcyBzaGFyaW5nIHRoZSByZXN1bHRzIG9mIHlvdXINCmFuYWx5c2lzIHdpdGggd2lkZXIgYXVkaWVuY2UuIEtydW1tIGV0IGFsLiAoMjAxOCkgaGF2ZSBvdXRsaW5lZCB0aGUNCmZvbGxvd2luZyAzLXN0ZXAgcHJvY2VzcyBmb3IgY29tbXVuaWNhdGluZyB3aXRoIGVkdWNhdGlvbiBzdGFrZWhvbGRlcnMNCmZpbmRpbmdzIGZyb20gYW4gYW5hbHlzaXM6DQoNCjEuICAqKlNlbGVjdC4qKiBDb21tdW5pY2F0aW5nIHdoYXQgb25lIGhhcyBsZWFybmVkIGludm9sdmVzIHNlbGVjdGluZw0KICAgIGFtb25nIHRob3NlIGFuYWx5c2VzIHRoYXQgYXJlIG1vc3QgaW1wb3J0YW50IGFuZCBtb3N0IHVzZWZ1bCB0byBhbg0KICAgIGludGVuZGVkIGF1ZGllbmNlLCBhcyB3ZWxsIGFzIHNlbGVjdGluZyBhIGZvcm0gZm9yIGRpc3BsYXlpbmcgdGhhdA0KICAgIGluZm9ybWF0aW9uLCBzdWNoIGFzIGEgZ3JhcGggb3IgdGFibGUgaW4gc3RhdGljIG9yIGludGVyYWN0aXZlIGZvcm0sDQogICAgaS5lLsKgYSAiZGF0YSBwcm9kdWN0LiINCg0KMi4gICoqUG9saXNoKiouIEFmdGVyIGNyZWF0aW5nIGluaXRpYWwgdmVyc2lvbnMgb2YgZGF0YSBwcm9kdWN0cywNCiAgICByZXNlYXJjaCB0ZWFtcyBvZnRlbiBzcGVuZCB0aW1lIHJlZmluaW5nIG9yIHBvbGlzaGluZyB0aGVtLCBieQ0KICAgIGFkZGluZyBvciBlZGl0aW5nIHRpdGxlcywgbGFiZWxzLCBhbmQgbm90YXRpb25zIGFuZCBieSB3b3JraW5nIHdpdGgNCiAgICBjb2xvcnMgYW5kIHNoYXBlcyB0byBoaWdobGlnaHQga2V5IHBvaW50cy4NCg0KMy4gICoqTmFycmF0ZS4qKiBXcml0aW5nIGEgbmFycmF0aXZlIHRvIGFjY29tcGFueSB0aGUgZGF0YSBwcm9kdWN0cw0KICAgIGludm9sdmVzLCBhdCBhIG1pbmltdW0sIHBhaXJpbmcgYSBkYXRhIHByb2R1Y3Qgd2l0aCBpdHMgcmVsYXRlZA0KICAgIHJlc2VhcmNoIHF1ZXN0aW9uLCBkZXNjcmliaW5nIGhvdyBiZXN0IHRvIGludGVycHJldCB0aGUgZGF0YQ0KICAgIHByb2R1Y3QsIGFuZCBleHBsYWluaW5nIHRoZSB3YXlzIGluIHdoaWNoIHRoZSBkYXRhIHByb2R1Y3QgaGVscHMNCiAgICBhbnN3ZXIgdGhlIHJlc2VhcmNoIHF1ZXN0aW9uLg0KDQpGb3IgeW91ciBsZWFybmluZyBNTCBMZWFybmluZyBMYWIgMSBCYWRnZSwgeW91IHdpbGwgaGF2ZSBhbiBvcHBvcnR1bml0eQ0KdG8gY3JlYXRlIGEgc2ltcGxlICJkYXRhIHByb2R1Y3QiIGRlc2lnbmVkIHRvIGlsbHVzdHJhdGUgaW5zaWdodHMgc29tZQ0KaW5zaWdodHMgZ2FpbmVkIGZyb20geW91ciBtb2RlbCBhbmQgaWRlYWxseSBoaWdobGlnaHQgYW4gImFjdGlvbiBzdGVwIg0KdGhhdCBjYW4gYmUgdGFrZW4gdG8gYWN0IHVwb24geW91ciBmaW5kaW5ncy4NCg0KIyMjIyAqKvCfkYkgWW91ciBUdXJuKiogKiripLUqKg0KDQpGb3Igbm93LCB3ZSB3aWxsIHdyYXAgdXAgdGhpcyBjYXNlIHN0dWR5IGJ5IGNvbnZlcnRpbmcgb3VyIHdvcmsgaW50byBhDQp3ZWJwYWdlIHRoYXQgY2FuIGJlIHVzZWQgdG8gY29tbXVuaWNhdGUgeW91ciBsZWFybmluZyBhbmQgZGVtb25zdHJhdGUNCnNvbWUgb2YgeW91ciBuZXcgUiBza2lsbHMuIFRvIGRvIHNvLCB5b3Ugd2lsbCBuZWVkIHRvICJrbml0IiB5b3VyDQpkb2N1bWVudCBieSBjbGlja2luZyB0aGUgYnV0dG9uIGluIHRoZSBtZW51IGJhciBhdCB0aGUgdGhlIHRvcCBvZiB0aGlzDQpmaWxlLiBUaGlzIHdpbGwgZG8gdHdvIHRoaW5nczsgaXQgd2lsbDoNCg0KMS4gIGNoZWNrIHRocm91Z2ggYWxsIHlvdXIgY29kZSBmb3IgYW55IGVycm9yczsgYW5kLA0KDQoyLiAgY3JlYXRlIGEgZmlsZSBpbiB5b3VyIGRpcmVjdG9yeSB0aGF0IHlvdSBjYW4gdXNlIHRvIHNoYXJlIHlvdSB3b3JrDQogICAgdGhyb3VnaCBbUG9zaXQNCiAgICBDbG91ZF0oaHR0cHM6Ly9wb3NpdC5jbG91ZC9sZWFybi9ndWlkZSNwdWJsaXNoLWZyb20tY2xvdWQpIChzZWUNCiAgICBzY3JlZW5zaG90IGV4YW1wbGUgYmVsb3cgdG8gcHVibGlzaCksIFtSUHVic10oIzApICwgW0dpdEh1Yg0KICAgIFBhZ2VzXSgjMCksIFtRdWFydG8gUHViXSgjMCksIG9yIGFueSBbb3RoZXIgbWV0aG9kc10oIzApLg0KDQoqKk5vdGU6KiogQmVmb3JlIGtuaXR0aW5nLCBtYWtlIHN1cmUgeW91IGNoYW5nZSB0aGUgYGF1dGhvcjpgIG5hbWUgaW4NCnRoZSBZQU1MIGhlYWRlciBhdCB0aGUgdG9wIG9mIHRoaXMgZG9jdW1lbnQgc28geW91IGNhbiB0YWtlIGNyZWRpdCBmb3INCnlvdXIgaGFyZCB3b3JrIQ0KDQohW10oaW1nL2tuaXQtcHVibGlzaC5wbmcpe3dpZHRoPSI4MCUifQ0KDQpDb25ncmF0dWxhdGlvbnMgLSB5b3UndmUgY29tcGxldGVkIHRoZSBmaXJzdCBtYWNoaW5lIGxlYXJuaW5nIGNhc2UNCnN0dWR5IQ0K