Load Libraries and Data

The Tidyverse library is a useful tool that enables us to read various datasets into a data frame.

First, 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.1     ✔ 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 ]8;;http://conflicted.r-lib.org/conflicted package]8;; to force all conflicts to become errors

The original Airline dataset is hosted on IBM Data Asset eXchange. This sample dataset can be found here. We will be using a subset of the original dataset, which contains just LAX to JFK flights, throughout this notebook.

Now using the subset dataset link, you can load it and store as a dataframe sub_airline:

url <- "https://dax-cdn.cdn.appdomain.cloud/dax-airline/1.0.1/lax_to_jfk.tar.gz"
# Download the file
download.file(url, destfile = "lax_to_jfk.tar.gz")
probando la URL 'https://dax-cdn.cdn.appdomain.cloud/dax-airline/1.0.1/lax_to_jfk.tar.gz'
Content type 'application/x-gzip' length 58424 bytes (57 KB)
downloaded 57 KB
# Untar the file so we can get the csv only
untar("lax_to_jfk.tar.gz", tar = "internal")
Aviso: using pax extended headers
# read_csv only
sub_airline <- read_csv("lax_to_jfk/lax_to_jfk.csv", col_types = cols(
  'DivDistance' = col_number(),
  'DivArrDelay' = col_number()
))

1. Missing Values and Formatting

Now that we have the data loaded, let’s first take a look at the data using the method head().

head(sub_airline)

1.1 Identify Missing Values

In R, there are some special symbols to represent special cases in data:

  • NA: missing values are represented by the symbol NA (not available), it is a special symbol in R. Note, that "NA" (a string) is not the same as NA.

  • NaN: Impossible values (e.g., dividing by zero) are represented by the symbol NaN (not a number).

The missing values in airline dataset are already represented with R’s NA symbol. We use R’s built-in (also called base R) functions to identify these missing values. There are two methods to detect missing data:

  1. is.na(x): x can be a vector or list, this method returns a vector of TRUE or FALSE depending if the according element in x is NA or not. For example is.na(c(1, NA)) returns FALSE TRUE

  2. anyNA(x, recursive = FALSE): x can be a vector or list, this method returns TRUE if x contains any NAs and FALSE otherwise. For example is.na(c(1, NA)) returns TRUE

is.na(c(1, NA))
[1] FALSE  TRUE
is.na(paste(c(1, NA)))
[1] FALSE FALSE

Again, the output for is.na() is a vector of logical values whereTRUE stands for missing value, while FALSE stands for not missing value.

anyNA(c(1, NA))
[1] TRUE

The output for anyNA() is a vector of logical values where TRUE stands for at least one missing value in the vector, while FALSE stands for no missing values in the vector.

Counting Missing Values

We can quickly figure out the number of missing values in each column. As mentioned above, when using the function is.na(), TRUE represents a missing value while FALSE is otherwise. The method sum() counts the number of TRUE values.

Let’s check how many missing values in CarrierDelay column and also check how many missing values in each column:

# counting missing values
sub_airline %>%
  summarise(count = sum(is.na(CarrierDelay)))

We can use purrr::map() to count missing values in each of the columns.

map() essentially maps (applies) a function or formula to each given element. In the code below, it is mapping a formula, ~sum(is.na(.)), that sums the NAs to every column in sub_airline. Since it is using a formula, you will also notice two special operators dot . and tilde ~:

  • The tilde ~ separates the left side of a formula with the right side. Normally formulas are two-sided like y ~ x, in this case in map(), the formula gets converted to a function so it only needs the right side.

  • The dot . refers to each column in the dataset. If you view the documentation with ?map, you can see that you use . when the function takes in just one parameter (a column in this case).

See ?formula and ?map for more information.

map(sub_airline, ~sum(is.na(.)))
$Month
[1] 0

$DayOfWeek
[1] 0

$FlightDate
[1] 0

$Reporting_Airline
[1] 0

$Origin
[1] 0

$Dest
[1] 0

$CRSDepTime
[1] 0

$CRSArrTime
[1] 0

$DepTime
[1] 0

$ArrTime
[1] 0

$ArrDelay
[1] 0

$ArrDelayMinutes
[1] 0

$CarrierDelay
[1] 2486

$WeatherDelay
[1] 2486

$NASDelay
[1] 2486

$SecurityDelay
[1] 2486

$LateAircraftDelay
[1] 2486

$DepDelay
[1] 0

$DepDelayMinutes
[1] 0

$DivDistance
[1] 2855

$DivArrDelay
[1] 2855
dim(sub_airline)
[1] 2855   21

Based on the summary above, “CarrierDelay”, “WeatherDelay”, “NASDelay”, “SecurityDelay” and “LateAircraftDelay” columns have 2486 rows of missing data, while “DivDistance” and “DivArrDelay” columns have 2855 rows of missing data. All other columns do not have missing data.

  1. “CarrierDelay”: 2486 missing data

  2. “WeatherDelay”: 2486 missing data

  3. “NASDelay”: 2486 missing data

  4. “SecurityDelay” : 2486 missing data

  5. “LateAircraftDelay”: 2486 missing data

  6. “DivDistance”: 2855 missing data

  7. “DivArrDelay”: 2855 missing data

1.2 Handle Missing Data

How to deal with missing data?

  1. Drop data
    1. Drop the whole column
    2. Drop the whole row
  2. Replace data
    1. Replace it by mean
    2. Replace it by frequency
    3. Replace it based on other functions

Generally, you should not blindly drop NAs. However if an entire column or almost the entire column contains NAs, then it may be a good idea to leave it out. In our dataset, columns DivDistance and DivArrDelay are nearly all empty so we will drop them entirely.

Drop the whole column:

  • “DivDistance”: 2855 missing data

  • “DivArrDelay”: 2855 missing data

drop_na_cols <- sub_airline %>%
  select(-DivDistance, -DivArrDelay)
dim(drop_na_cols)
[1] 2855   19
head(drop_na_cols, n = 10)

Drop the whole row:

  • “CarrierDelay”: 2486 missing data

  • “WeatherDelay”: 2486 missing data

  • “NASDelay”: 2486 missing data

  • “SecurityDelay” : 2486 missing data

  • “LateAircraftDelay”: 2486 missing data

We see CarrierDelay, WeatherDelay, NASDelay, SecurityDelay, LateAircraftDelay have the same amount of missing values from the summary. By dropping the missing values in one column will also solve the missing value issues in the others.

# Drop the missing values
drop_na_rows <- drop_na_cols %>%
  drop_na(CarrierDelay)
dim(drop_na_rows)
[1] 369  19
head(drop_na_rows)

We have some freedom in choosing which method to replace data; however, some methods may seem more reasonable than others. In this scenario, we would like to replace missing values with 0.

Convert NA to 0

In the airline dataset, missing data for the different types of delay corresponds to no delay. So, we can replace these NAs with 0 in this case. To do this we use the function:

tidyr::replace_na(data, replace, ...)

The columns that corresponds with types of delays are CarrierDelay, WeatherDelay, NASDelay, SecurityDelay, and LateAircraftDelay. For example if CarrierDelay = NA then this means there is no delay in Carrier, so the delay in minutes can be changed to 0 or CarrierDelay = 0. Let’s transform these columns and see the result:

# Replace the missing values in five columns
replace_nan <- drop_na_rows %>%
  replace_na(list(CarrierDelay = 0, WeatherDelay = 0, NSADelay = 0, SecurityDelay = 0, LateAircraftDelay = 0))
head(replace_nan)

Question #1:

According to the example above, let’s try to replace NA in “CarrierDelay” column by the mean value.

carrier_mean <- mean(drop_na_rows$CarrierDelay)
sub_airline %>% replace_na(list(CarrierDelay = carrier_mean))
head(sub_airline)

1.3 Correct Data Format

We are almost there!

The last step in data cleaning is checking and making sure that all data is in the correct format.

Scoped dplyr Verbs

In dplyr, we can add _all and _if to the end of it’s main functions like mutate, filter, group_by, and summarize.

These are called scoped dplyr verbs. The _all variant applies an operation on all variaibles and the _if variant applies an operation to a variable if the given function is TRUE.

Now that we understand these different versions of dplyr functions, let’s list the data types for each column by using summarize_all() and gather():

sub_airline %>%
  summarise_all(class) %>%
  gather(variable, class)
date_airline <- replace_nan %>%
  separate(FlightDate, sep = "-", into = c("year", "month", "day"))

head(date_airline)

For a number of reasons, including when we import a dataset into R or process a variable, the data type may be incorrectly established. For example, here we notice that the assigned data type to for year, month, and day is “character” although the expected data type should really be numeric. You can change the type using mutate_all() and mutate_if().

In the below code, it is mutating year, month, and day values to numeric only if it is a character. Note that in mutate_if(), the first parameter is the function is.character that checks a condition while the second parameters is the function as.numeric that modifies the data if the condition is true.

date_airline %>%
  select(year, month, day) %>%
  mutate_all(type.convert) %>%
  mutate_if(., is.character, as.numeric)
Aviso: There were 3 warnings in `mutate()`.
The first warning was:
ℹ In argument: `year = (function (x, ...) ...`.
Caused by warning in `type.convert.default()`:
! 'as.is' should be specified by the caller; using TRUE
ℹ Run ]8;;ide:run:dplyr::last_dplyr_warnings()dplyr::last_dplyr_warnings()]8;; to see the 2 remaining warnings.

Wonderful!

Now, we finally obtain the cleaned dataset with no missing values and all data in its proper format.

Data Normalization

Why normalization?

Normalization is the process of transforming values of several features (variables) into a similar range. An example of why this could be important is if you have a variable for income and another variable for age. Income is likely much bigger values than age, so in a model, income would naturally influence the model more. Thus, normalization helps make comparisons between different variables more fair.

Simple scaling

Simple scaling divides each value by the maximum value in a feature. The new range is between 0 and 1.

Example

To demonstrate sampling scaling, let’s say we want to scale the column “ArrDelay”.

  • Target: Would like to Normalize those variables so their value ranges from 0 to 1.

  • Approach: Replace the original value by (original value) / (maximum value)

simple_scale <- sub_airline$ArrDelay / max(sub_airline$ArrDelay)
head(simple_scale, n=10)
 [1]  0.002932551 -0.030791789 -0.020527859 -0.004398827 -0.019061584 -0.029325513  0.001466276 -0.058651026  0.036656891  0.010263930

Question #2:

According to the example above, normalize the column “DepDelay” using the simple scaling technique.

head(simple_scale_depdly, n=10)
 [1] -0.001373626  0.005494505 -0.010989011  0.009615385 -0.005494505  0.002747253 -0.002747253 -0.002747253  0.002747253  0.020604396

Min - max

Min-max subtracts the minimum value from the original and divides by the maximum minus the minimum. The minimum becomes 0 and the maximum becomes 1.

Example

Using “ArrDelay” as an example again, you can transform this column using the min-max technique:

head(minmax_scale, n=10)
 [1] 0.09933775 0.06887417 0.07814570 0.09271523 0.07947020 0.07019868 0.09801325 0.04370861 0.12980132 0.10596026

Data Standardization (Z-score)

Standardization (Z-score) subtracts the mean (𝜇) of the feature and divides by the standard deviation (𝜎).

Example

Let’s use “ArrDelay” again. We can use mean() to find the mean of the feature and we can use sd() to find the standard deviation of the feature.

head(z_scale, n=10)
 [1] -0.04815631 -0.60922513 -0.43846505 -0.17012779 -0.41407075 -0.58483083 -0.07255060 -1.07271676  0.51291251  0.07381518

Question #3:

According to the example above, standardize the “DepDelay” column.

head(z_scale_DepDly, n=10)
 [1] -0.28063352 -0.14031185 -0.47708387 -0.05611884 -0.36482653 -0.19644052 -0.30869786 -0.30869786 -0.19644052  0.16839584

Binning

Why use binning?

Binning is a process of transforming continuous numerical variables into discrete categorical ‘bins’, for grouped analysis.

Example:

Using binning, we categorize arrival delays into four bins by quartiles.

Quartiles refer to the boundaries that divide observations into four defined intervals. They are often determined based on the values of data points and how they are compared with the rest of the dataset. In the actual airline dataset, “ArrDelay” is a numerical variable ranging from -73 to 682, it has 2855 unique values. We can categorize them into 4 bins. Can we rearrange them into four ‘bins’ to simplify analysis?

We will use the tidyverse method mutate(quantile_rank = ntile()) to segment the “ArrDelay” column into 4 bins

Example of Binning Data In Tidyverse

Lets plot the histogram of flight arrival delays, to see what the distribution of “ArrDelay” looks like.

ggplot(data = sub_airline, mapping = aes(x= ArrDelay)) + 
  geom_histogram(bins = 100, color = "white", fill = "red") +
  coord_cartesian(xlim = c(-73, 682))

First we use the dplyr function ntile to break “ArrDelay” into 4 buckets, which have equal amount of observations of flight arrival delays. We then create a list “quantile_rank” that contains 4 bins, which are respectively labeled “1”, “2”, “3”, “4”. So bin 1 would contain the first 25% of data, bin 2 the next 25% of data and so on.

binning <- sub_airline %>%
  mutate(quantile_rank = ntile(sub_airline$ArrDelay, 4))

head(binning)

The observations are put into different bins based on the flights’ delay minutes. The larger the bin label is, the longer the flight was delayed.

Now if we look at a histogram of the bins, you can see that all bins are equal.

ggplot(data = binning, mapping = aes(x = quantile_rank)) + 
  geom_histogram(bins = 4, color = "white", fill = "red")

Indicator variable

What is an indicator variable?

An indicator variable (or dummy variable) is a numerical variable used to label categories. They are called ‘dummies’ because the numbers themselves don’t have inherent meaning.

Why we use indicator variables?

Regression models need numerical variables, however categorical variables in their original forms are strings. Using indicator variables allows us to be able to use these categorical variables in regression models.

Example

In the airline dataset, the “Reporting_Airline” feature is a categorical variable that has nine values, “AA”, “AS”, “B6”, “DL”, “HP”, “PA (1)”, “TW”, “UA” or “VX”, which are in character type. For further analysis, we need to convert these variables into some form of numeric format.

We will use the tidyverse’s method spread() method to convert categorical variables to dummy variables. The parameters to use in the function are:

  • key: the column to convert into categorical values

  • value: the value you want to set the key to

  • fill: fills the missing values with this value

Also keep in mind that the method will drop the “Reporting_Airline” column by default. So if you used “ArrDelay” as the key value instead, that column would get dropped.

The below code does the following:

  1. mutate - creates a column dummy with all 1’s then

  2. spread - creates new column for every Reporting_Ariline and sets the value to 1 from dummy. But replaces the 1 with a 0 if the corresponding value is NA in Reporting_Airline then

  3. slice - looks at the specified rows

sub_airline %>%
  mutate(dummy = 1) %>% #column with single value
  spread(key = Reporting_Airline, value = dummy, fill = 0) %>%
  slice(1:10)

When a value occurs in the original feature, we set the corresponding value to one in the new feature; the rest of the features are set to zero.

So in the output above, for the first row, the reporting airline is “UA”. Therefore, the feature “UA” is set to one and the other features to zero. Similarly, for the second row, the reporting airline value is “AS”. Therefore, the feature “AS” is set to one and the other features to zero.

Alternatively, instead of assigning dummy values 0 or 1, we can assign flight delay values to each feature. So this will also create new columns, one for each Reporting_Airline. Taking the first row for example, the column “UA” is now set to 2 because that is the value from ArrDelay.

sub_airline %>%
  spread(Reporting_Airline, ArrDelay) %>%
  slice(1:10)
view(sub_airline)

Visualize Airline Category

Let’s visualize how many data points in each airline category.

sub_airline %>%
  mutate(Reporting_Airline = factor(Reporting_Airline, labels = c("AA", "AS", "DL", "UA", "B6", "PA (1)", "HP", "TW", "VX"))) %>%
  ggplot(aes(Reporting_Airline)) + 
  stat_count(width = 0.5) +
  labs(x = "Number of datapoints in each airline")

Question #4:

As above, create indicator variable to the column of “Month”.

sub_airline %>%
  mutate(Month = factor(Month, labels = c("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"))) %>%
  ggplot(aes(Month)) + 
  stat_count(width = 0.5) +
  labs(x = "Number of data points in each Month")

Question #5:

Now, create indicator variable to the column of “Month” by applying departure delay values

sub_airline %>%
  spread(Month, DepDelay) %>%
  slice(1:10)

Descriptive Statistics

summary_airline_delays <- sub_airline %>%
  group_by(Reporting_Airline) %>%
  summarise(mean = mean(ArrDelayMinutes, na.rm = TRUE), std_dev = sd(ArrDelayMinutes, na.rm = TRUE)) %>%
  arrange(mean)
summary_airline_delays

Create a simple average across Reporting_Airline and DayOfWeek

avg_delays <- sub_airline %>%
  group_by(Reporting_Airline, DayOfWeek) %>%
  summarise(mean_delays = mean(ArrDelayMinutes))
`summarise()` has grouped output by 'Reporting_Airline'. You can override using the `.groups` argument.
avg_delays %>% arrange(desc(mean_delays))

Plot target variable over multiple variables

avg_delays %>%
  ggplot(aes(x = Reporting_Airline, y = DayOfWeek, fill = mean_delays)) +
  geom_tile(color = "white", size = 0.2) +
  scale_fill_gradient(low = "skyblue", high = "blue")
Aviso: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
Please use `linewidth` instead.

Analyzing Individual Feature Patterns using Visualization

Boxplots are a great way to visualize numeric (or quantitative) data, since you can visualize the various distributions of the data. They are similar to histograms but can show more informatioin such as:

  • The median of the data, which represents the middle datapoint

  • The Upper Quartile, which is the 75th percentile

  • The Lower Quartile, which is the 25th percentile

  • The Interquartile Range, which is the data between the Upper and Lower Quartile

Boxplots also display outliers as individual dots that occur outside the upper and lower extremes. With boxplots, you can easily spot outliers and also see the distribution and skewness of the data.

We will use the ggplot library to create plots, which is already loaded when the tidyverse library is loaded.

To create boxplots, the main function to use is geom_boxplot(). In the below code we also use additional functions primarly to customize the aesthetics of the plot. You can check out the Data Visualization Course in R if you want to know more about customizing plots with ggplot.

  • geom_boxplot(): The boxplot compactly displays the distribution of a continuous variable. It visualises five summary statistics (the median, two hinges and two whiskers), and all “outlying” points individually.

  • geom_jitter(): The jitter geom is a convenient shortcut for geom_point(position = “jitter”). It adds a small amount of random variation to the location of each point, and is a useful way of handling overplotting caused by discreteness in smaller datasets.

  • ggtitle(): Modifies plot titles (main title, axis labels and legend titles).

  • guides(): Guides for each scale can be set scale-by-scale with the guide argument.

  • theme_minimal(): A minimalistic theme with no background annotations.

  • coord_cartesian(): The Cartesian coordinate system is the most familiar, and common, type of coordinate system. Setting limits on the coordinate system will zoom the plot (like you’re looking at it with a magnifying glass), and will not change the underlying data like setting limits on a scale will.

Below shows the distribution of arrival delays for each reporting airline. So here, ArrDelay is the numerica data.

ggplot(data = sub_airline, mapping = aes(x = Reporting_Airline, y = ArrDelay)) +
  geom_boxplot(fill = "bisque", color = "black", alpha = 0.3) +
  geom_jitter(aes(color = "blue"), alpha = 0.2) +
  labs(x = "Airline") +
  ggtitle("Arrival Delays by Airline") +
  guides(color = "none") +
  theme_minimal() +
  coord_cartesian(ylim = quantile(sub_airline$ArrDelay, c(0, 0.99)))

Often times we see continuous variables in our data. These data points are numbers contained in some range.

For example, in our dataset, departure delays and arrival delays are continuous numeric variables. What if we want to understand the relationship between “DepDelay” and “ArrDelay”? Could departure delay possibly predict arrival delay?

One good way to visualize two continuous variables is to use a scatter plot. Each observation in a scatter plot is represented as a point.

Using ggplot, you can input the two continuous variables to compare and add gemo_point() to show the points in a scatter plot.

alaska_flights <- sub_airline %>%
  filter(Reporting_Airline == "AS") %>%
  filter(!is.na(DepDelay) & !is.na(ArrDelay)) %>%
  filter(DepDelay < 40)

ggplot(data = alaska_flights, mapping = aes(x = DepDelay, y = ArrDelay)) +
  geom_point() +
  ggtitle("Alaska flight Departure Delays vs. Arrival Delays")

How to choose the right visualization method?

When visualizing individual variables, it is important to first understand what type of variable you are dealing with. This will help us find the right visualization method for that variable. Below is a way to show each variable and their type in a dataframe.

str(sub_airline)
spc_tbl_ [2,855 × 21] (S3: spec_tbl_df/tbl_df/tbl/data.frame)
 $ Month            : num [1:2855] 3 11 8 4 11 4 12 12 2 3 ...
 $ DayOfWeek        : num [1:2855] 5 4 5 7 3 1 1 3 4 4 ...
 $ FlightDate       : Date[1:2855], format: "2003-03-28" "2018-11-29" "2015-08-28" "2003-04-20" ...
 $ Reporting_Airline: chr [1:2855] "UA" "AS" "UA" "DL" ...
 $ Origin           : chr [1:2855] "LAX" "LAX" "LAX" "LAX" ...
 $ Dest             : chr [1:2855] "JFK" "JFK" "JFK" "JFK" ...
 $ CRSDepTime       : chr [1:2855] "2210" "1045" "0805" "2205" ...
 $ CRSArrTime       : chr [1:2855] "0615" "1912" "1634" "0619" ...
 $ DepTime          : chr [1:2855] "2209" "1049" "0757" "2212" ...
 $ ArrTime          : chr [1:2855] "0617" "1851" "1620" "0616" ...
 $ ArrDelay         : num [1:2855] 2 -21 -14 -3 -13 -20 1 -40 25 7 ...
 $ ArrDelayMinutes  : num [1:2855] 2 0 0 0 0 0 1 0 25 7 ...
 $ CarrierDelay     : num [1:2855] NA NA NA NA NA NA NA NA NA NA ...
 $ WeatherDelay     : num [1:2855] NA NA NA NA NA NA NA NA NA NA ...
 $ NASDelay         : num [1:2855] NA NA NA NA NA NA NA NA NA NA ...
 $ SecurityDelay    : num [1:2855] NA NA NA NA NA NA NA NA NA NA ...
 $ LateAircraftDelay: num [1:2855] NA NA NA NA NA NA NA NA NA NA ...
 $ DepDelay         : num [1:2855] -1 4 -8 7 -4 2 -2 -2 2 15 ...
 $ DepDelayMinutes  : num [1:2855] 0 4 0 7 0 2 0 0 2 15 ...
 $ DivDistance      : num [1:2855] NA NA NA NA NA NA NA NA NA NA ...
 $ DivArrDelay      : num [1:2855] NA NA NA NA NA NA NA NA NA NA ...
 - attr(*, "spec")=
  .. cols(
  ..   Month = col_double(),
  ..   DayOfWeek = col_double(),
  ..   FlightDate = col_date(format = ""),
  ..   Reporting_Airline = col_character(),
  ..   Origin = col_character(),
  ..   Dest = col_character(),
  ..   CRSDepTime = col_character(),
  ..   CRSArrTime = col_character(),
  ..   DepTime = col_character(),
  ..   ArrTime = col_character(),
  ..   ArrDelay = col_double(),
  ..   ArrDelayMinutes = col_double(),
  ..   CarrierDelay = col_double(),
  ..   WeatherDelay = col_double(),
  ..   NASDelay = col_double(),
  ..   SecurityDelay = col_double(),
  ..   LateAircraftDelay = col_double(),
  ..   DepDelay = col_double(),
  ..   DepDelayMinutes = col_double(),
  ..   DivDistance = col_number(),
  ..   DivArrDelay = col_number()
  .. )
 - attr(*, "problems")=<externalptr> 

Now, we turn our focus to “ArrDelayMinutes” as we want to create models to predict this variable

Question #1:

What is the data type of the column “ArrDelayMinutes”?

class(sub_airline$ArrDelayMinutes)
[1] "numeric"

Next, let’s take a look at other variables, like “DepDelayMinutes” that could potentially help predict “ArrDelayMinutes”.

Question #2:

Find the correlation between the following columns: DepDelayMinutes and ArrDelayMinutes.

Hint: if you would like to select those columns, use the dollar sign ($)

cor(sub_airline$DepDelayMinutes, sub_airline$ArrDelayMinutes)
[1] 0.9213328

Continuous numerical variables:

Continuous numerical variables are variables that may contain any value within some range. In R, continuous numerical variables can have the type “integer” or “numeric” (these are real numbers and sometimes are called “float” in other programming languages). A great way to visualize these variables is by using scatterplots with fitted lines.

With ggplot, we can visualize this by using geom_point() to plot the data points and geom_smooth() to plot a fitted linear regression line (by default the model uses formula = y ~ x).

Let’s see several examples of different linear relationships:

Positive linear relationship

Let’s find the scatterplot of “DepDelayMinutes” and “ArrDelayMinutes” of all airlines.

ANOVA Analysis of Variance

Example: Average flight delays of different reporting airlines

Finding correlation between different groups of categorical variable

Null hypothesis: The mean of the reporting airline is the same for all groups

# ANOVA between "AA" and "AS"
aa_as_subset <- sub_airline %>%
  select(ArrDelay, Reporting_Airline) %>%
  filter(Reporting_Airline == "AA" | Reporting_Airline == "AS")

ad_aov <- aov(ArrDelay ~ Reporting_Airline, data = aa_as_subset)
summary(ad_aov)
                    Df  Sum Sq Mean Sq F value Pr(>F)
Reporting_Airline    1     126   125.7    0.13  0.718
Residuals         1139 1097707   963.7               
aa_pa_subset <- sub_airline %>% 
  select(ArrDelay, Reporting_Airline) %>%
  filter(Reporting_Airline == "AA" | Reporting_Airline == "PA (1)")

ad_aov <- aov(ArrDelay ~ Reporting_Airline, data = aa_pa_subset)
summary(ad_aov)
                    Df  Sum Sq Mean Sq F value   Pr(>F)    
Reporting_Airline    1   24008   24008   17.95 2.45e-05 ***
Residuals         1127 1507339    1337                     
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Continuous numerical variables:

Continuous numerical variables are variables that may contain any value within some range. In R, continuous numerical variables can have the type “integer” or “numeric” (these are real numbers and sometimes are called “float” in other programming languages). A great way to visualize these variables is by using scatterplots with fitted lines.

With ggplot, we can visualize this by using geom_point() to plot the data points and geom_smooth() to plot a fitted linear regression line (by default the model uses formula = y ~ x).

Let’s see several examples of different linear relationships:

Positive linear relationship

Let’s find the scatterplot of “DepDelayMinutes” and “ArrDelayMinutes” of all airlines.

ggplot(sub_airline, aes(DepDelayMinutes, ArrDelayMinutes)) +
  geom_point() +
  geom_smooth(method = "lm")

From the plot, as the depature delay (“DepDelayMinutes”) increases, the arrival delay (“ArrDelayMinutes”) increases. This indicates a positive direct correlation between these two variables. “DepDelayMinutes” may be a decent predictor of “ArrDelayMinutes” since the regression line is increasing and generally matches the data points.

Next, we can examine the correlation between “DepDelayMinutes” and “ArrDelayMinutes” and see it’s approximately 0.92

sub_airline %>%
  select(DepDelayMinutes, ArrDelayMinutes) %>%
  cor(method = "pearson")
                DepDelayMinutes ArrDelayMinutes
DepDelayMinutes       1.0000000       0.9213328
ArrDelayMinutes       0.9213328       1.0000000
cor(sub_airline$DepDelayMinutes, sub_airline$ArrDelayMinutes)
[1] 0.9213328

Weak Linear Relationship

Let’s now look at if “WeatherDelay” is a good predictor variable of “ArrDelayMinutes”.

ggplot(sub_airline, aes(WeatherDelay, ArrDelayMinutes)) +
  geom_point(na.rm = TRUE) +
  geom_smooth(method = "lm", na.rm = TRUE)

Weather delay does not seem like a good predictor of arrival delay minutes since the regression line is close to horizontal. Also, for small values of “WeatherDelay”, the data points are very scattered and far from the fitted line, showing lots of variability. Therefore it is not a reliable variable.

In the cor() function, you can add use = "complete.obs" in order to only use complete observations, that is, exclude NAs. You can examine the correlation between “WeatherDelay” and “ArrDelayMinutes” and see that it’s a very weak relationship since the value is close to 0.

sub_airline %>%
  select(WeatherDelay, ArrDelayMinutes) %>%
  cor(method = "pearson", use = "complete.obs")
                WeatherDelay ArrDelayMinutes
WeatherDelay      1.00000000      0.04019104
ArrDelayMinutes   0.04019104      1.00000000
cor(sub_airline$WeatherDelay, sub_airline$ArrDelayMinutes, use = "complete.obs")
[1] 0.04019104

Question 3 a):

Find the correlation between x=“CarrierDelay”, y=“ArrDelayMinutes”.

Hint: if you would like to select those columns, use the dollar sign ($)

cor(sub_airline$CarrierDelay, sub_airline$ArrDelayMinutes, use = "complete.obs")
[1] 0.7287601

Question 3 b):

Given the correlation results between x=“CarrierDelay”, y=“ArrDelayMinutes”, do you expect a linear relationship?

Verify your results using the function of ggplot.

ggplot(data = sub_airline, mapping = aes(x = CarrierDelay, y = ArrDelayMinutes)) +
  geom_point(na.rm = TRUE) +
  geom_smooth(method = "lm", na.rm = TRUE)

ggplot(sub_airline, aes(CarrierDelay, LateAircraftDelay)) +
  geom_point(na.rm = TRUE) +
  geom_smooth(method = "lm", na.rm = TRUE)


sub_airline %>%
  select(CarrierDelay, LateAircraftDelay) %>%
  cor(method = "pearson", use = "complete.obs")
                  CarrierDelay LateAircraftDelay
CarrierDelay        1.00000000       -0.04173491
LateAircraftDelay  -0.04173491        1.00000000

Pearson correlation - Example

sub_airline %>%
  select(DepDelay, ArrDelay) %>%
  cor(method = "pearson")
          DepDelay  ArrDelay
DepDelay 1.0000000 0.8826109
ArrDelay 0.8826109 1.0000000
sub_airline %>%
  cor.test(~DepDelay + ArrDelay, data = .)

    Pearson's product-moment correlation

data:  DepDelay and ArrDelay
t = 100.28, df = 2853, p-value < 2.2e-16
alternative hypothesis: true correlation is not equal to 0
95 percent confidence interval:
 0.8742325 0.8904638
sample estimates:
      cor 
0.8826109 

There is a strong correlation that is statistically significant

library(Hmisc)
Registered S3 method overwritten by 'htmlwidgets':
  method           from         
  print.htmlwidget tools:rstudio
Registered S3 method overwritten by 'data.table':
  method           from
  print.data.table     

Adjuntando el paquete: ‘Hmisc’

The following objects are masked from ‘package:dplyr’:

    src, summarize

The following objects are masked from ‘package:base’:

    format.pval, units
numerics_airline <- sub_airline %>%
  select(ArrDelayMinutes, DepDelayMinutes, CarrierDelay, WeatherDelay, NASDelay, SecurityDelay, LateAircraftDelay)

airlines_cor <- rcorr(as.matrix(numerics_airline), type = "pearson")
airlines_cor
                  ArrDelayMinutes DepDelayMinutes CarrierDelay WeatherDelay NASDelay SecurityDelay LateAircraftDelay
ArrDelayMinutes              1.00            0.92         0.73         0.04     0.26          0.08              0.42
DepDelayMinutes              0.92            1.00         0.75         0.04     0.07          0.11              0.45
CarrierDelay                 0.73            0.75         1.00        -0.04    -0.19         -0.02             -0.04
WeatherDelay                 0.04            0.04        -0.04         1.00    -0.04         -0.01             -0.03
NASDelay                     0.26            0.07        -0.19        -0.04     1.00         -0.05             -0.16
SecurityDelay                0.08            0.11        -0.02        -0.01    -0.05          1.00             -0.03
LateAircraftDelay            0.42            0.45        -0.04        -0.03    -0.16         -0.03              1.00

n
                  ArrDelayMinutes DepDelayMinutes CarrierDelay WeatherDelay NASDelay SecurityDelay LateAircraftDelay
ArrDelayMinutes              2855            2855          369          369      369           369               369
DepDelayMinutes              2855            2855          369          369      369           369               369
CarrierDelay                  369             369          369          369      369           369               369
WeatherDelay                  369             369          369          369      369           369               369
NASDelay                      369             369          369          369      369           369               369
SecurityDelay                 369             369          369          369      369           369               369
LateAircraftDelay             369             369          369          369      369           369               369

P
                  ArrDelayMinutes DepDelayMinutes CarrierDelay WeatherDelay NASDelay SecurityDelay LateAircraftDelay
ArrDelayMinutes                   0.0000          0.0000       0.4415       0.0000   0.1398        0.0000           
DepDelayMinutes   0.0000                          0.0000       0.4096       0.1730   0.0402        0.0000           
CarrierDelay      0.0000          0.0000                       0.4501       0.0003   0.6464        0.4241           
WeatherDelay      0.4415          0.4096          0.4501                    0.4441   0.8444        0.5930           
NASDelay          0.0000          0.1730          0.0003       0.4441                0.3121        0.0024           
SecurityDelay     0.1398          0.0402          0.6464       0.8444       0.3121                 0.6093           
LateAircraftDelay 0.0000          0.0000          0.4241       0.5930       0.0024   0.6093                         

Descriptive Statistical Analysis

When we began to analyze data, it’s important to first explore your data before you spend time building complicated models. One easy way to do so is to calculate some descriptive statistics for your data.

Descriptive statistical analysis helps to describe basic features of a dataset and generates a short summary about the sample and measures of the data.

Let’s take a look at a couple of different useful methods. One way in which we can do this is by using the summarize() function in tidyverse:dplyr(). We introduced this method in previous modules. Method group_by() is often used together with summarize(), which summarizes each group into a single-row summary of that group. group_by() takes as arguments the column names that contain the categorical variables for which you want to calculate the summary statistics.

This will show:

  • the count of that variable

  • the mean

  • the standard deviation (std)

  • the minimum value

  • the median (50th percentile or quartile 2)

  • the IQR (Interquartile Range: quartile 3 minus quartile 1)

  • the maximum value

We can apply the method “summarize” and “group_by” as follows:

summary_airline_delays <- sub_airline %>%
  group_by(Reporting_Airline) %>%
  summarise(count = n(), avg = mean(ArrDelayMinutes, na.rm = TRUE), std_dev = sd(ArrDelayMinutes, na.rm = TRUE), min = min(ArrDelayMinutes, na.rm = TRUE), median = median(ArrDelayMinutes, na.rm = TRUE), iqr = IQR(ArrDelayMinutes, na.rm = TRUE), max = max(ArrDelayMinutes, na.rm = TRUE)) %>%
  arrange(desc(avg))
summary_airline_delays

To identify the data type of each column in a dataframe, we can use sapply() with typeof, which finds the type of something. So here, you apply typeof to every column in sub_airline.

sapply(sub_airline, typeof)
            Month         DayOfWeek        FlightDate Reporting_Airline            Origin              Dest        CRSDepTime        CRSArrTime 
         "double"          "double"          "double"       "character"       "character"       "character"       "character"       "character" 
          DepTime           ArrTime          ArrDelay   ArrDelayMinutes      CarrierDelay      WeatherDelay          NASDelay     SecurityDelay 
      "character"       "character"          "double"          "double"          "double"          "double"          "double"          "double" 
LateAircraftDelay          DepDelay   DepDelayMinutes       DivDistance       DivArrDelay 
         "double"          "double"          "double"          "double"          "double" 

Value Counts

One way you can summarize the categorical data is by using the function count(). As an example, you can get the count of each reporting airline in this dataset. We see that we have 1096 flights from AA - American Airlines, 45 flights from AS - Alaska Airlines, 258 from B6 - jetBlue airlines, etc.

sub_airline %>%
  count(Reporting_Airline) %>% 
  arrange(desc(n))

Basics of Grouping

We often ask questions like: Is there any relationship between the reporting airline and the flight delays? If so, which day of the week do flights have relatively longer delay times? For example, people take flights most frequently on Monday and Friday for business trips. Would that impact how long the flight is delayed? It would be nice if we could group the data by the different reporting airline, and compare the results of these different day of week against each other.

In tidyverse this can be done using the “group_by” method. The group by method is used on categorical variables, it groups the data into subsets according to the different categories of that variable. You can group by a single variable or you can group by multiple variables by passing in multiple variable names.

Let’s say we are interested in finding the average delay minutes of flights and observe how they differ between different “Reporting_Airline” and “DayOfWeeks” variables.

To do this, you can use group_by() to group by “Reporting_Airline” and “DayOfWeeks”, then use summarize() to calculate the mean delay minutes. Setting .groups = 'keep' keeps the grouping structure, if you instead were to set it to "drop" then the output would be ungrouped and essentially be a regualr tibble (dataframe).

avg_delays <- sub_airline %>%
  group_by(Reporting_Airline, DayOfWeek) %>%
  summarise(mean_delays = mean(ArrDelayMinutes), groups = "keep")
`summarise()` has grouped output by 'Reporting_Airline'. You can override using the `.groups` argument.
avg_delays

The function arrange() will reorder the rows in an ascending order. However, using arrange() with desc() sorts data in descending order.

So now, we sort the mean_delays column by descending order using arrange(desc()) and print the output table to see the top airlines and day of the week pairs that have the highest average arrival delays.

sorted <- avg_delays %>%
  arrange(desc(mean_delays))

sorted

To make it easier to understand, we can transform this table to a heatmap.

A heatmap has one variable displayed along the x-axis and the other variable displayed along the y-axis. A heat map (or heatmap) is a data visualization technique that shows magnitude of a phenomenon as color in two dimensions.

The variation in color may be by hue or intensity, giving obvious visual cues to the reader about how the phenomenon is clustered or varied over space. It is a great way to plot the target variable over multiple variables and through this get visual clues of the relationship between these variables and the target.

With ggplot, you can use geom_tile() to create heatmaps and use scale_fill_gradient() to change the coloring of the heatmap. So to interpret the heatmap below, the closer a tile is to the color red, the higher the mean arrival delay for that particualr day of the week and airline pair.

avg_delays %>%
  ggplot(aes(x = Reporting_Airline, y = DayOfWeek, fill = mean_delays)) +
  # Set the tile's borders to be white with size 0.2
  geom_tile(color = "white", size = 0.2) +
  # Define gradient color scales
  scale_fill_gradient(low = "yellow", high = "red")

BONUS Heatmap

For something more sophisticated, you can always add more blocks in your code.

# This visualizations will use lubridate package
library(lubridate)
# Let's take a simple average accross Reporting_Airline amns DayOfWeek
avg_delays <- sub_airline %>%
  group_by(Reporting_Airline, DayOfWeek) %>%
  summarise(mean_delays = mean(ArrDelayMinutes), .groups = "keep") %>%
  # create a new variable from mean_delays
  # make the first range -0.1 to 0.1 to include zero values
  mutate(bins = cut(mean_delays, breaks = c(-0.1, 0.1, 10, 20, 30, 50))) %>%
  mutate(bins = factor(as.character(bins), levels = rev(levels(bins))))

ggplot(avg_delays, aes(x = Reporting_Airline, y = lubridate::wday(DayOfWeek, label = TRUE), fill = bins)) +
  geom_tile(colour = "white", size = 0.2) +
  geom_text(aes(label = round(mean_delays, 2))) +
  guides(fill = guide_legend(titel = "Delays Time Scale")) +
  labs(x = "Reporting Airline", y = "Days of Week", title = "Average Arrival Delays") +
  scale_fill_manual(values = c("#d53e4f", "#f46d43", "#fdae61", "#fee08b", "#e6f598", "#abdda4"))

Question 4:

Use the “groupby” and “summarize” function to find the average “ArrDelayMinutes” of each flight based on “Reporting_Airline” ?

sub_airline %>%
  group_by(Reporting_Airline) %>%
  summarise(mean_delays = mean(ArrDelayMinutes)) %>% 
  arrange(mean_delays)

Correlation and Causation

The main question we are trying to answer in this module is: “What causes flight delays?”. To get a better measure of the important characteristics, we look at the correlation of these variables with the arrival delay, AKA, “ArrDelayMinutes”, in other words: how is the arrival delay minutes dependent on this variable?

First, let’s begin with some important definitions:

  • Correlation: a measure of the extent of interdependence between variables.

  • Causation: the relationship between cause and effect between two variables.

It is important to know the difference between these two and that correlation does not imply causation. Determining correlation is much simpler than determining causation as causation may require independent experimentation.

Pearson Correlation

The Pearson Correlation measures the linear dependence between two variables X and Y.

The resulting coefficient is a value between -1 and 1 inclusive, where:

  • 1: Total positive linear correlation.

  • 0: No linear correlation, the two variables most likely do not affect each other.

  • -1: Total negative linear correlation

Pearson Correlation is the default method of the function cor(), but other methods can be specified by setting method. Like before we can calculate the Pearson Correlation of the “integer” or “numeric” variables.

sub_airline %>%
  select(DepDelayMinutes, ArrDelayMinutes) %>%
  cor(method = "pearson")
                DepDelayMinutes ArrDelayMinutes
DepDelayMinutes       1.0000000       0.9213328
ArrDelayMinutes       0.9213328       1.0000000

Sometimes, we would like to know the significant of the correlation estimate.

P-value:

What is this P-value? The P-value is the probability value that the correlation between these two variables is statistically significant. Normally, we choose a significance level of 0.05, which means that we are 95% confident that the correlation between the variables is significant.

By convention, when the

  • p-value is < 0.001: we say there is strong evidence that the correlation is significant.

  • the p-value is < 0.05: there is moderate evidence that the correlation is significant.

  • the p-value is < 0.1: there is weak evidence that the correlation is significant.

  • the p-value is > 0.1: there is no evidence that the correlation is significant.

In R, to conduct a significance test and get the p-values, you can use cor.test().

sub_airline %>%
  cor.test(~DepDelayMinutes + ArrDelayMinutes, data = .)

    Pearson's product-moment correlation

data:  DepDelayMinutes and ArrDelayMinutes
t = 126.58, df = 2853, p-value < 2.2e-16
alternative hypothesis: true correlation is not equal to 0
95 percent confidence interval:
 0.9155942 0.9266962
sample estimates:
      cor 
0.9213328 

Conclusion:

See that the P-value is very small, much smaller than .001. And so we can conclude that we are certain about the strong positive correlation.

Correlations between multiple variables

To calculate correlations on multiple variables, you can select all the variable then use the cor() function but set use = "pairwise.complete.obs" so that the correlation of every pair of variables is calculated. This computes a matrix of Pearson’s r correlation coefficients for all possible pairs of columns.

correlation <- sub_airline %>%
  select(ArrDelayMinutes, DepDelayMinutes, CarrierDelay, WeatherDelay, NASDelay, SecurityDelay, LateAircraftDelay) %>%
  cor(use = "pairwise.complete.obs", method = "pearson")

correlation
                  ArrDelayMinutes DepDelayMinutes CarrierDelay WeatherDelay    NASDelay SecurityDelay LateAircraftDelay
ArrDelayMinutes        1.00000000      0.92133281   0.72876012   0.04019104  0.26170778    0.07702208        0.41691413
DepDelayMinutes        0.92133281      1.00000000   0.75399335   0.04304843  0.07109447    0.10683841        0.44701937
CarrierDelay           0.72876012      0.75399335   1.00000000  -0.03943409 -0.18634695   -0.02396245       -0.04173491
WeatherDelay           0.04019104      0.04304843  -0.03943409   1.00000000 -0.03995573   -0.01024942       -0.02791712
NASDelay               0.26170778      0.07109447  -0.18634695  -0.03995573  1.00000000   -0.05276635       -0.15735914
SecurityDelay          0.07702208      0.10683841  -0.02396245  -0.01024942 -0.05276635    1.00000000       -0.02668880
LateAircraftDelay      0.41691413      0.44701937  -0.04173491  -0.02791712 -0.15735914   -0.02668880        1.00000000

Taking all variables into account, we can now create a heatmap that visualizes the correlation between each of the variables with one another.

Let’s use the corrplot() function to plot an elegant graph of a correlation matrix, you may need to install the library corrplot first before loading it. The color scheme indicates the Pearson correlation coefficient, indicating the strength of the correlation between two variables. We can see a diagonal line with a dark blue color, indicating that all the values on this diagonal are highly correlated. This makes sense because when you look closer, the values on the diagonal are the correlation of all variables with themselves, which will always be 1.

This correlation heatmap gives us a good overview of how the different variables are related to one another and, most importantly, how these variables are related to arrival delays.

library(corrplot)
corrplot 0.92 loaded
numerics_airline <- sub_airline %>%
  select(ArrDelayMinutes, DepDelayMinutes, CarrierDelay, WeatherDelay, NASDelay, SecurityDelay, LateAircraftDelay)

airlines_cor <- cor(numerics_airline, method = "pearson", use = "pairwise.complete.obs")
col <- colorRampPalette(c("#BB4444", "#EE9988", "#FFFFFF", "#77AADD", "#4477AA"))

corrplot(airlines_cor, method = "color", col = col(200), type = "upper", order = "hclust", addCoef.col = "black", tl.col = "black", tl.srt = 45)

From the above correlation plot, you can see that of the features we used, “CarrierDelay”, “DepDelayMinutes”, and “LateAircraftDelay” have the highest correlations with “ArrDelayMinutes”. The correlation between “CarrierDelay” and “ArrDelayMinutes” is 0.73, “DepDelayMinutes” and “ArrDelayMinutes” is 0.92, and so on.

ANOVA (Analysis of Variance)

Let’s say that we want to analyze a categorical variable and see the correlation among different categories. For example, consider the airline dataset, the question we may ask is, how do different categories of the reporting airline feature (as a categorical variable) impact flight delays?

The bar graph below shows the average flight delays of different airlines. The code first groups the data by airline, then finds the average arrival delay for each airline, then it plots this as a bar chart.

In geom_bar(), you should set stat = "identity" since you are passing in the y values (“Average_Delays”). If you left out this parameter then by default stat = "count", which would instead count the frequency of each airline.

summary_airline_delays <- sub_airline %>%
  group_by(Reporting_Airline) %>%
  summarise(Average_Delays = mean(ArrDelayMinutes, na.rm = TRUE))

summary_airline_delays %>%
  ggplot(aes(x = Reporting_Airline, y = Average_Delays)) +
  geom_bar(stat = "identity") +
  ggtitle("Average Arrival Delays by Airline")

To analyze categorical variables such as the “Reporting_Airline” variable, we can use a method such as the ANOVA method.

ANOVA: Analysis of Variance

The Analysis of Variance (ANOVA) is a statistical method used to test whether there are significant differences between the means of two or more groups. ANOVA returns two parameters:

  • F-test score: ANOVA assumes the means of all groups are the same, calculates how much the actual means deviate from the assumption, and reports it as the F-test score. A larger score means there is a larger difference between the means.

  • P-value: The p-value tells you how statistically significant the calculated score value is.

If our ArrDelay variable is strongly correlated with the variable we are analyzing, expect ANOVA to return a sizeable F-test score and a small p-value.

American Airline (AA) and Alaska Airline (AS)

The ANOVA test can be performed in base R’s stats package using the aov() function. You can pass in the arrival delay data of the two airline groups that we want to compare and it calculates the ANOVA results.

In this first example, you can compare American Airline and Alaska Airline. The results confirm what we guessed at first. The flight delay between “AA” and “AS” are not significantly different, as the F score (0.13) is less than 1 and p-value is larger than 0.05.

aa_as_subset <- sub_airline %>%
  select(ArrDelay, Reporting_Airline) %>%
  filter(Reporting_Airline == "AA" | Reporting_Airline == "AS")

ad_aov <- aov(ArrDelay ~ Reporting_Airline, data = aa_as_subset)
summary(ad_aov)
                    Df  Sum Sq Mean Sq F value Pr(>F)
Reporting_Airline    1     126   125.7    0.13  0.718
Residuals         1139 1097707   963.7               

American Airline (AA) and Pan Am Airline (PA (1))

As another example, you can compare American Airline and Pan Am Airline. From the below output, the arrival delay between “AA” and “PA (1)” are significantly different, since the F-score is very large (F = 17.95) and the p-value is 0.0000245 which is smaller than 0.05. All in all, we can say that there is a strong correlation between a categorical variable and other variables, if the ANOVA test gives us a large F-test value and a small p-value.

aa_pa_subset <- sub_airline %>%
  select(ArrDelay, Reporting_Airline) %>%
  filter(Reporting_Airline == "AA" | Reporting_Airline == "PA (1)")

ad_aov <- aov(ArrDelay ~ Reporting_Airline, data = aa_pa_subset)
summary(ad_aov)
                    Df  Sum Sq Mean Sq F value   Pr(>F)    
Reporting_Airline    1   24008   24008   17.95 2.45e-05 ***
Residuals         1127 1507339    1337                     
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Conclusion: Important Variables

We now have a better idea of what our data looks like and which variables are important to take into account when predicting the arrival delay (ArrDelay). We have narrowed it down to the following variables:

Continuous numerical variables:

  • DepDelayMinutes

  • CarrierDelay

  • LateAircraftDelay

Categorical variables:

  • ReportingAirline

As we now move into building machine learning models to automate our analysis, feeding the model with variables that meaningfully affect our target variable will improve our model’s prediction performance.

_______________________________________________________________________________________________________________

Simple Linear Regression

Simple Linear Regression is a method to help us understand the relationship between two variables:

  • X: Predictor variable - DepDelayMinutes

  • Y: Target variable - ArrDelayMinutes

The result of Linear Regression is a linear function that predicts the response (dependent) variable as a function of the predictor (independent) variable.

Linear Model Function:

\[ \hat{Y} = b_{0} + b_{1}X \]

  • \(b_{0}\) refers to the intercept of the regression line, the value of Y when X is 0

  • \(b_{1}\) refers to the slope of the regression line, the value with which Y changes when X increases by 1 unit

  • \(\hat{Y}\) (y-hat) is the predicted value from the linear model

Fit the data into a linear regression model

First, let’s just look at just Alaska Airline (AA) data, so filter the data first. We also filter out the NAs in CarrierDelay because you will use that variable later.

  1. Define the dataset as aa_delays
aa_delays <- sub_airline %>%
  filter(CarrierDelay != "NA", Reporting_Airline == "AA")
head(aa_delays)

For this example, we want to look at how departure delay (DepDelayMinutes) can help us predict arrival delay (ArrDelayMinutes). Using simple linear regression, we will create a linear function with “DepDelayMinutes” as the predictor variable and the “ArrDelayMinutes” as the response variable. You can use base R’s function lm() to create a linear model.

Fit the data into a linear regression model:

linear_model <- lm(ArrDelayMinutes ~ DepDelayMinutes, data = aa_delays)

Summarize the regression model using summary(). The output displays the learned coefficients (“Estimate” in the output) of the model, \(b_0\) and \(b_1\) as well as other information about the fitted model.

summary(linear_model)

Call:
lm(formula = ArrDelayMinutes ~ DepDelayMinutes, data = aa_delays)

Residuals:
    Min      1Q  Median      3Q     Max 
-37.234 -12.716  -1.354   7.747  93.646 

Coefficients:
                Estimate Std. Error t value Pr(>|t|)    
(Intercept)      17.3544     2.5084   6.919  2.9e-10 ***
DepDelayMinutes   0.7523     0.0399  18.855  < 2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 20.03 on 113 degrees of freedom
Multiple R-squared:  0.7588,    Adjusted R-squared:  0.7567 
F-statistic: 355.5 on 1 and 113 DF,  p-value: < 2.2e-16

SLR - Estimated Linear Model

  • View the intercept (b0) 17.35

  • View the slope (b1) 0.7523

  • The relationship between arrival and departure delay is given by: ArrDelayMinutes = 17.35 + 0.7523 * DepDelayMinutes

SLM: Prediction

Create a never seen data, and then we can output a prediction of three new data points

new_depdelay <- data.frame(DepDelayMinutes = c(12, 19, 24))

Predict the regression model

pred <- predict(linear_model, newdata = new_depdelay, interval = "confidence")
pred
       fit      lwr      upr
1 26.38175 21.98838 30.77512
2 31.64769 27.52630 35.76908
3 35.40907 31.44593 39.37222

When we print the pred object, we can see that there are 3 columns: fit, lwr and upr. The “fit” column is the prediction results of the inputs. And “lwr” and “upr” are the lower bound and upper bound of the 95% confidence intervals of prediction results. The confidence interval reflects the uncertainty around the mean predictions.

For example, given that the DepDelayMinutes is 12, then the model predicts the ArrDelayMinutes to be 26.38, and we are 95% confident that the interval (21.98, 30.77) captures the true mean arrival delay for this instance.

What is the value of the intercept ($b_0$) and the Slope ($b_1$)?

What is the final estimated linear model we get?

As we saw above, we should get a final linear model with the structure:

\[ \hat{Y} = b_0 + b_1 X \]

Remember that we are predicting ArrDelayMinutes using DepDealyMinutes. So, plugging in the actual values we get:

\[ ArrDelayMinutes = 17.35 + 0.7523 * DepDelayMinutes \]

Question #1 a):

Create a linear function with “CarrierDelay” as the predictor variable and the “ArrDelayMinutes” as the response variable.

linear_model2 <- lm(ArrDelayMinutes ~ CarrierDelay, data = aa_delays)

Question #1 b):

Find the coefficients (intercept and slope) of the model.

linear_model2$coefficients
 (Intercept) CarrierDelay 
  35.1176108    0.7031761 

Question #1 c):

What is the equation of the predicted line. You can use x and yhat or ‘CarrierDelay’ or ‘ArrDelayMinutes’?

\[ ArrDelayMinutes = 35.12 + 0.703 * CarrierDelay \]

Visualization

Simply visualizing your data with a regression

ggplot(aa_delays, aes(x = DepDelayMinutes, y = ArrDelayMinutes)) +
  geom_point() + 
  stat_smooth(method = "lm", col = "red", se = FALSE)

Multiple linear Regression

What if we want to predict arrival delay minutes using more than one variable?

If we want to use more variables in our model to predict arrival delay minutes, we can use Multiple Linear Regression. Multiple Linear Regression is very similar to Simple Linear Regression, but this method is used to explain the relationship between one continuous response (dependent) variable and two or more predictor (independent) variables. Most of the real-world regression models involve multiple predictors. We will illustrate the structure by using two predictor variables, but these results can generalize to any amount of predictor variables:

  • \(Y\): Response Variable

  • \(X_1\): Predictor Variable

  • \(X2\): Predictor Variable 2

The equation is given by:

\[ \hat{Y} = b_0 + b_1X_1 + b_2X_2 \]

Where,

  • \(b_0\): intercept

  • \(b_1\): coefficient of Variable 1

  • \(b_2\): coefficient of Variable 2

From previous cells we know that other good predictors of ArrDelayMinutes could be:

  • DepDelayMinutes

  • LateAircraftDelay

Let´s develop a model using these variables as the predictor variables by fitting the data

Fitting a MLR Model

  • Define the dataset, the predictor variables, and the target variable

  • Use lm() function to fit the model, find parameter b0, b1, b2

mlr <- lm(ArrDelayMinutes ~ DepDelayMinutes + LateAircraftDelay, data = aa_delays)
mlr

Call:
lm(formula = ArrDelayMinutes ~ DepDelayMinutes + LateAircraftDelay, 
    data = aa_delays)

Coefficients:
      (Intercept)    DepDelayMinutes  LateAircraftDelay  
         17.31707            0.75556           -0.01028  

Summary: Estimated MLR model

Use summary() to output the model results

summary(mlr)

Call:
lm(formula = ArrDelayMinutes ~ DepDelayMinutes + LateAircraftDelay, 
    data = aa_delays)

Residuals:
    Min      1Q  Median      3Q     Max 
-37.188 -12.545  -1.317   7.791  93.683 

Coefficients:
                  Estimate Std. Error t value Pr(>|t|)    
(Intercept)       17.31707    2.53786   6.823 4.78e-10 ***
DepDelayMinutes    0.75556    0.04822  15.668  < 2e-16 ***
LateAircraftDelay -0.01028    0.08407  -0.122    0.903    
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 20.11 on 112 degrees of freedom
Multiple R-squared:  0.7588,    Adjusted R-squared:  0.7545 
F-statistic: 176.2 on 2 and 112 DF,  p-value: < 2.2e-16

What is the value of the intercept (b0) and the coefficients (b1, b2)?

mlr$coefficients
      (Intercept)   DepDelayMinutes LateAircraftDelay 
      17.31706644        0.75555543       -0.01027513 

What is the final estimated linear model that we get?

As we saw above, we should get a final linear function with the structure:

\[ \hat{Y} = b_0 + b_1X_1 + b_2X_2 \]

What is the linear function we get in this example?

\[ ArrDelayMinutes = 17.32 + 0.7556 * DepDelayMinutes - 0.0103 * LateAircraftDelay \]

Question #2 a):

Create and train a Multiple Linear Regression model “mlr2” where the response variable is ArrDelayMinutes, and the predictor variable is ‘DepDelayMinutes’, ‘LateAircraftDelay’ and ‘CarrierDelay’.

mlr2 = lm(ArrDelayMinutes ~ DepDelayMinutes + LateAircraftDelay + CarrierDelay, data = aa_delays)
summary(mlr2)

Call:
lm(formula = ArrDelayMinutes ~ DepDelayMinutes + LateAircraftDelay + 
    CarrierDelay, data = aa_delays)

Residuals:
    Min      1Q  Median      3Q     Max 
-33.711 -12.875  -0.265   6.105  92.735 

Coefficients:
                  Estimate Std. Error t value Pr(>|t|)    
(Intercept)        18.2650     2.5820   7.074 1.43e-10 ***
DepDelayMinutes     0.6107     0.0995   6.137 1.33e-08 ***
LateAircraftDelay   0.1536     0.1292   1.189   0.2370    
CarrierDelay        0.1799     0.1083   1.661   0.0996 .  
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 19.96 on 111 degrees of freedom
Multiple R-squared:  0.7647,    Adjusted R-squared:  0.7583 
F-statistic: 120.2 on 3 and 111 DF,  p-value: < 2.2e-16

Question #2 b):

Find the coefficients of the model?

mlr2$coefficients
      (Intercept)   DepDelayMinutes LateAircraftDelay      CarrierDelay 
       18.2649917         0.6106613         0.1536269         0.1799444 

Question #2 c):

Using the fitted model, mlr2, what are the predicted values for the following new data points?

MLR prediction

  • Create a new dataset
DepDelayMinutes <- c(10, 20, 30)
LateAircraftDelay <- c(20, 60, 30)
new_multidelay <- data.frame(DepDelayMinutes, LateAircraftDelay)
  • Calculate the predictions
pred <- predict(mlr, newdata = new_multidelay, interval = "confidence")
pred
       fit      lwr      upr
1 24.66712 19.02312 30.31112
2 31.81167 21.42849 42.19485
3 39.67548 34.12732 45.22364

3. Assessing Models Visually

Now that we’ve developed some models, how do we evaluate our models and how do we choose the best one? One way to do this is by using visualization.

Regression Plot

When it comes to simple linear regression, an excellent way to visualize the fit of our model is by using regression plots.

Regression plots are a good estimate of:

  • The relationship of two variables,

  • The strength of the correlation, and

  • The direction of the relationship (positive or negative).

There are several ways to plot a regression plot; a simple way is to use “ggplot” from the tidyverse library.

This plot will show a combination of a scattered data points (a scatter plot), as well as the fitted linear regression line going through the data. This will give us a reasonable estimate of the relationship between the two variables, the strength of the correlation, as well as the direction (positive or negative correlation).

Regression Plot

ggplot(aa_delays, aes(x = DepDelayMinutes, y = ArrDelayMinutes)) +
  geom_point() +
  stat_smooth(method = "lm", col = "red")

We can see from this plot that Arrival Delay Minutes (ArrDelayMinutes) is positively correlated to Departure Delay Minutes (DepDelayMinutes), since the regression slope is positive.

One thing to keep in mind when looking at a regression plot is to pay attention to how scattered the data points are around the regression line. This will give you a good indication of the variance of the data, and whether a linear model would be the best fit or not. If the data is too far off from the line, this linear model might not be the best model for this data.

Question #3 a):

Create a regression plot of “CarrierDelay” and “ArrDelayMinutes” using “aa_delays” dataset

ggplot(aa_delays, aes(x = CarrierDelay, y = ArrDelayMinutes)) +
  geom_point() +
  geom_smooth(method = "lm", col = "red")

Question #3 b):

Given the regression plots above is “DepDelayMinutes” or “CarrierDelay” more strongly correlated with “ArrDelayMinutes”. Use the method “cor()” to verify your answer.

cor(aa_delays$DepDelayMinutes, aa_delays$ArrDelayMinutes)
[1] 0.8710917
cor(aa_delays$CarrierDelay, aa_delays$ArrDelayMinutes)
[1] 0.624374

The variable “DepDelayMinutes” has a stronger correlation with “ArrDelayMinutes”, it is approximately 0.871 compared to “CarrierDelay” which is approximately 0.624.

Residual Plot

A good way to visualize the variance of the data is to use a residual plot. Before we start creating residual plots let’s first answer the following questions:

  • What is a residual?

    • The difference between the observed value (\(Y\)) and the predicted value (\(\hat{Y}\)) is called the residual (or error). When we look at a regression plot, the residual is the distance from the data point to the fitted regression line.
  • What is a residual plot?

    • A residual plot is a graph that shows the residuals on the vertical y-axis and the independent variable on the horizontal x-axis.
  • What do we pay attention to when looking at a residual plot?

    • Homoscedasticity: If the residual plot is homoscedastic, then the points in the plot are randomly spread out around the x-axis, which means that a linear model is appropriate for the data. Why is that? Randomly spread out residuals means that the variance is constant, and thus the linear model is a good fit for this data.

Now, let’s look again at the regression plot of ArrDelayMinutes as the response and DepDelayMinutes as the predictor. This time, let’s visualize the residuals on this plot.

  • The red line is the regression line

  • The black dots represent the observed values of ArrDelayMinutes

  • The white dots are the predicted values from the linear regression model

  • The light gray lines are the residuals, or errors. It shows how far away the observed values are from the predicted values. So a longer line means a larger error.

# Create a new column "predicted"
aa_delays$predicted <- predict(linear_model)

score_model <- lm(ArrDelayMinutes ~ DepDelayMinutes, data = aa_delays)

ggplot(aa_delays, aes(x = DepDelayMinutes, y = ArrDelayMinutes)) +
  # Plot the actual points
  geom_point() +
  # Plot regression line
  geom_smooth(method = "lm", se = FALSE, color = "red") +
  # Add the predicted values
  geom_point(aes(y = predicted), shape = 1, color = "blue") +
  # Connect the actual data points with their corresponding predicted value
  geom_segment(aes(xend = DepDelayMinutes, yend = predicted), alpha = 0.1)

Next, we can create a residual plot, which graphs the residuals (light gray lines in the previous graph) against the observed DepDelayMinutes. The code to do this is similar to a normal scatterplot, but you pass in the linear model lm(ArrDelayMinutes ~ DepDelayMinutes) and when setting the y axis, you can use .resid which will use the residuals from the model you inputted.

We can see from this residual plot that the residuals are not randomly spread around the x-axis, which leads us to believe that maybe a non-linear model is more appropriate for this data.

ggplot(lm(ArrDelayMinutes ~ DepDelayMinutes, data = aa_delays)) +
  geom_point(aes(x = DepDelayMinutes, y = .resid))

Other Diagnostic Plots

In addition to residual plots, there are other useful plots. A simple way to view diagnostic plots is to first create the linear model using lm(), then call base R’s plot() function on the model.

The below code will output four graphs:

  1. Residual plot: Identical to the graph we made with ggplot, here it again shows that the residuals are not randomly spread around the x-axis.

  2. Q-Q plot: The dotted diagonal line represents what normally distributed error (residual) values would follow. In this case, the residuals do not look normally distributed since there are many observations that fall above the line on the right side

  3. Scale-location plot: This plot helps check the homoscedasticity assumption. Here, it shows a red line that is not straight and validates the homoscedasticity assumption is not satisfied.

  4. Residuals vs leverage plot: This helps determine influential points. Any points outside the dotted lines (Cook’s distance) would make it influential. Here, none of the points cross the lines, however several points come close and could be removed or analyzed further.

Diagnostic Plot

linear_model <- lm(ArrDelayMinutes ~ DepDelayMinutes, data = aa_delays)
plot(linear_model)

Polynomial regression

Polynomial regression is a particular case of the general linear regression model or multiple linear regression models. That is, although the data is nonlinear in polynomial regression (the predicator variables have higher order terms), the model in all cases is linear. The model is always linear because it predicts the coefficients (𝑏0,𝑏1,…) which are always of order 1.

There are different orders of polynomial regression:

Quadratic - 2nd order

\[ \hat{Y} = b_0 + b_1X + b_2X^2 \]

Cubic - 3rd order

\[ Y = b_0 + b_1X + b_2X^2 + b_3X^3 \]

Higher (\(n^{th}\)) order

\[ Y = b_0 + b_1X + b_2X^2 + b_2X^3 .... + b_nX^n \]

Let’s look at the below example. Here, we create random predictor variable q and random response variable y that follows a 3rd order polynomial but then we add some random noise to it to get noise.y. We set the seed so that this result can be reproduced.

set.seed(20)
x <- seq(from=0, to=20, by=0.1)

# value to predict (y):
y <- 500 + 0.4 * (x - 10)^3

# some noise is generated and added to the ral signal (y):
noise <- rnorm(length(x), mean=10, sd=80)
noisy.y <- y + noise

In the graph below, we fit a first order linear model to this example dataset and can see that the model does not fit the data very well.

# fit linear model
ggplot(data = NULL, aes(x, noisy.y)) +
  geom_point() +
  geom_smooth(method = "lm")

Instead, we can use a polynomial model. It is similar to the first order linear model except that you include poly() within geom_smooth() to indicate what order polynomial to use. For example, using poly(x, 5) equates to having \(b_0 + b_1X^2 + b_3X^3 + b_4X^4 + b_5X^5\).

ggplot(data=NULL, aes(x, noisy.y)) +
  geom_point() +
  geom_smooth(method = "lm", formula = y ~ poly(x, 5))

We can already see from plotting that this polynomial model performs better than the linear model. This is because the generated polynomial function “hits” more of the data points.

Polynomial 2nd Order

Now let’s look at another example, this time using a 2nd order polynomial. Again, we use a toy dataset where time is the predictor and temp is the response.

time <- 6:19
temp <- c(4, 6, 7, 9, 10, 11, 11.5, 12, 12, 11.5, 11, 10, 9, 8)

ggplot(data = NULL, aes(time, temp)) +
  geom_point()

We can create a model like how we saw before using lm() and to include higher order, you can used poly().

For this dataset, we try a 2nd order polynomial model to see how it fits. The equation the model follows is:

\[ temp = b_0 + b_1 * time + b_2 + time^2 \]

polyfit2 <- lm(temp ~ poly(time, 2, raw = TRUE))
summary(polyfit2)

Call:
lm(formula = temp ~ poly(time, 2, raw = TRUE))

Residuals:
     Min       1Q   Median       3Q      Max 
-0.52005 -0.06387  0.03970  0.15543  0.21250 

Coefficients:
                             Estimate Std. Error t value Pr(>|t|)    
(Intercept)                -13.710165   0.601247  -22.80 1.30e-10 ***
poly(time, 2, raw = TRUE)1   3.760920   0.102822   36.58 7.69e-13 ***
poly(time, 2, raw = TRUE)2  -0.138393   0.004071  -33.99 1.71e-12 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 0.2197 on 11 degrees of freedom
Multiple R-squared:  0.9931,    Adjusted R-squared:  0.9918 
F-statistic: 791.3 on 2 and 11 DF,  p-value: 1.301e-12

From the summary output of the model, you can find the coefficients, so to predict temp, you could use:

$ temp = -13.7 + 3.76 * time - 0.138 * time^2 $

Like for the first order linear models, you can use ggplot to graph the model.

Plot a line

ggplot(data = NULL, aes(time, temp)) +
  geom_point() +
  geom_smooth(method = "lm", formula = y ~ poly(x, 2))

Question #4 a):

Create a 4th order polynomial model with the variables time and temp from above and display the summary of the model.

polyfit4 <- lm(temp ~ poly(time, 4, raw = TRUE))
summary(polyfit4)

Call:
lm(formula = temp ~ poly(time, 4, raw = TRUE))

Residuals:
     Min       1Q   Median       3Q      Max 
-0.33403 -0.05810 -0.01222  0.07299  0.26814 

Coefficients:
                             Estimate Std. Error t value Pr(>|t|)  
(Intercept)                 0.9580273  5.3098909   0.180   0.8608  
poly(time, 4, raw = TRUE)1 -1.6827915  1.9381916  -0.868   0.4078  
poly(time, 4, raw = TRUE)2  0.5770452  0.2523955   2.286   0.0481 *
poly(time, 4, raw = TRUE)3 -0.0397085  0.0139698  -2.842   0.0193 *
poly(time, 4, raw = TRUE)4  0.0007906  0.0002788   2.836   0.0195 *
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 0.1763 on 9 degrees of freedom
Multiple R-squared:  0.9964,    Adjusted R-squared:  0.9947 
F-statistic: 616.4 on 4 and 9 DF,  p-value: 5.787e-11
polyfit4$coefficients
               (Intercept) poly(time, 4, raw = TRUE)1 poly(time, 4, raw = TRUE)2 poly(time, 4, raw = TRUE)3 poly(time, 4, raw = TRUE)4 
              0.9580272669              -1.6827914732               0.5770451974              -0.0397084533               0.0007905697 

Question #4 b):

Using the predicted coefficients from the summary output for the 4th order model, write down the model equation.

\[ temp = 0.96 - 1.682 * time + 0.577 * time^2 - 0.04 * time^3 + 0.00079 * time^4 \]

Assessing the Model

When evaluating our models, not only do we want to visualize the results, but we also want a quantitative measure to determine how accurate the model is.

Two very important measures that are often used in Statistics to determine the accuracy of a model are:

  1. \(R^2\) / R-squared

  2. Mean Squared Error (MSE)

R-squared

R squared, also known as the coefficient of determination, is a measure to indicate how close the data is to the fitted regression line. The value of the R-squared is the percentage of variation of the response variable (y) that is explained by a linear model.

Mean Squared Error (MSE)

\[ MSE = average((\hat{Y} - Y)^2) \]

\[ RMSE = \sqrt{\bar{MSE}} \]

The Mean Squared Error measures the average of the squares of errors, that is, the difference between actual value (\(Y\)) and the estimated value (\(\hat{Y}\)). Another metric that is related to MSE is root mean squared error (RMSE) and is simply the square root of MSE.

Model 1: Simple Linear Regression

Let’s use the simple linear regression model we created previously.

Using this model, you can calculate MSE and RMSE. From below, MSE is 394 and RMSE is 19.85.

mse <- mean(linear_model$residuals^2)
mse
[1] 394.0639
rmse <- sqrt(mse)
rmse
[1] 19.85104

\(R^2\) can be obtained from the summary of the model. From the output below, we can say that approximately 75.9% of the variation of price is explained by this simple linear model.

summary(linear_model)$r.squared
[1] 0.7588008

Model 2: Multiple Linear Regression

Next, let’s use the multiple linear regression model we created in section 3.

mls <- lm(ArrDelayMinutes ~ DepDelayMinutes + LateAircraftDelay, data = aa_delays)

Let’s calculate MSE and RMSE. From below, MSE is 394 and RMSE is 19.849.

mse_mlr <- mean(mlr$residuals^2)
mse_mlr
[1] 394.0113
rmse_mlr <- sqrt(mse_mlr)
rmse_mlr
[1] 19.84972

From the r-squared value belwo, we can say that approximately 75.9 % of the variation of Arrival Delay Minutes is explained by this multiple linear regression “mlr”.

summary(mlr)$r.squared
[1] 0.7588329

Model 3: Polynomial Regression

Finally, we can use a polynomial regression model using the skills from section 5.

poly_reg <- lm(ArrDelayMinutes ~ poly(DepDelayMinutes, 3), data = aa_delays)

Similar to model 1 and 2, you can find MSE, RMSE, and R^2. Here the MSE is 328.97, RMSE is 19.85, and R^2 is 0.798.

mse_poly <- mean(poly_reg$residuals^2)
mse_poly
[1] 328.9701
rmse_poly <- sqrt(mse)
rmse_poly
[1] 19.85104
summary(poly_reg)$r.squared
[1] 0.7986434

Prediction and Decision Making

Prediction

Previously, we trained the model using the method lm() and we used the method predict() to produce a prediction.

head(predict(score_model))
       1        2        3        4        5        6 
17.35443 83.55480 38.41818 73.77520 62.49104 67.75698 

Decision Making: Determining a Good Model Fit

Now that we have visualized the different models, and generated the R-squared and MSE values for the fits, how do we determine a good model fit?

  • What is a good R-squared value?

    • When comparing models, the model with the higher R-squared value is a better fit for the data.
  • What is a good MSE?

    • When comparing models, the model with the smallest MSE value is a better fit for the data

Let’s take a look at the values for the different models.

Simple Linear Regression: Using DepDelayMinutes as a Predictor Variable of ArrDelayMinutes.

  • R-squared: 0.7588

  • MSE: 394.06

Multiple Linear Regression: Using DepDelayMinutes and LateAircraftDelay as Predictor Variables of ArrDelayMinutes.

  • R-squared: 0.75883

  • MSE: 394.011

Polynomial Fit: Using 3rd Oder Polynomial of DepDelayMinutes as a Predictor Variable of ArrDelayMinutes

  • R-squared: 0.7986

  • MSE: 328.970

Simple Linear Regression model (SLR) vs Multiple Linear Regression model (MLR)

Usually, the more variables you have, the better your model is at predicting, but this is not always true. Sometimes you may not have enough data, you may run into numerical problems, or many of the variables may not be useful and or even act as noise. As a result, you should always check the MSE and R^2.

So to be able to compare the results of the MLR vs SLR models, we look at a combination of both the R-squared and MSE to make the best conclusion about the fit of the model

  • MSE: The MSE of SLR model is 394.063 while MLR has an MSE of 394.0113. The MSE of MLR model is ever slightly smaller.

  • R-squared: In this case, we can see that the R-squared for the SLR is a little lower than the R-squared for the MLR model.

This R-squared in combination with the MSE show that MLR seems like a slightly better model fit in this case, compared to SLR. However, you could try adding more predictor variables in the MLR model to see if that made a bigger improvement since in our example only two were used.

Simple Linear Model (SLR) vs Polynomial Fit

  • MSE: We can see that Polynomial model brought down the MSE, since this MSE is smaller than the one from the SLR.

  • R-squared: The R-squared for the Polyfit is larger than the R-squared for the SLR, so the Polynomial Fit also brought up the R-squared quite a bit.

Since the Polynomial Fit resulted in a lower MSE and a higher R-squared, we can conclude that this was a better fit model than the simple linear regression for predicting ArrDelayMinutes.

Multiple Linear Regression (MLR) vs Polynomial Fit

  • MSE: The MSE for the polynomial model is smaller than the MSE for the MLR model.

  • R-squared: The R-squared for the polynomial model is also larger than the MLR model’s.

Conclusion:

Comparing these three models, the MLR model performs slightly better than the SLR model. Perhaps if we tried adding some more predictor variables, the MLR model could do even better. Of the three models, we conclude that the polynomial of order 3 model seems to be the best fit it as it has the highest R^2 and the lowest MSE.

LS0tDQp0aXRsZTogIkRhdGEgV3JhbmdsaW5nIGluIFIgd2l0aCBUaWR5dmVyc2UiDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCmF1dGhvcjogSnVhbiBGZXJuYW5kbyBNb3NxdWVyYSBBcmF1am8NCi0tLQ0KDQojIyBMb2FkIExpYnJhcmllcyBhbmQgRGF0YQ0KDQpUaGUgVGlkeXZlcnNlIGxpYnJhcnkgaXMgYSB1c2VmdWwgdG9vbCB0aGF0IGVuYWJsZXMgdXMgdG8gcmVhZCB2YXJpb3VzIGRhdGFzZXRzIGludG8gYSBkYXRhIGZyYW1lLg0KDQpGaXJzdCwgbG9hZCB0aGUgdGlkeXZlcnNlIGxpYnJhcnkuDQoNCmBgYHtyfQ0KbGlicmFyeSh0aWR5dmVyc2UpDQpgYGANCg0KVGhlIG9yaWdpbmFsIEFpcmxpbmUgZGF0YXNldCBpcyBob3N0ZWQgb24gW0lCTSBEYXRhIEFzc2V0IGVYY2hhbmdlXShodHRwczovL2RldmVsb3Blci5pYm0uY29tL2V4Y2hhbmdlcy9kYXRhL2FsbC9haXJsaW5lLykuIFRoaXMgc2FtcGxlIGRhdGFzZXQgY2FuIGJlIGZvdW5kIFtoZXJlXShodHRwczovL2RheC1jZG4uY2RuLmFwcGRvbWFpbi5jbG91ZC9kYXgtYWlybGluZS8xLjAuMS9sYXhfdG9famZrLnRhci5neikuIFdlIHdpbGwgYmUgdXNpbmcgYSBzdWJzZXQgb2YgdGhlIG9yaWdpbmFsIGRhdGFzZXQsIHdoaWNoIGNvbnRhaW5zIGp1c3QgKipMQVggdG8gSkZLKiogZmxpZ2h0cywgdGhyb3VnaG91dCB0aGlzIG5vdGVib29rLg0KDQpOb3cgdXNpbmcgdGhlIHN1YnNldCBkYXRhc2V0IGxpbmssIHlvdSBjYW4gbG9hZCBpdCBhbmQgc3RvcmUgYXMgYSBkYXRhZnJhbWUgYHN1Yl9haXJsaW5lYDoNCg0KYGBge3J9DQp1cmwgPC0gImh0dHBzOi8vZGF4LWNkbi5jZG4uYXBwZG9tYWluLmNsb3VkL2RheC1haXJsaW5lLzEuMC4xL2xheF90b19qZmsudGFyLmd6Ig0KIyBEb3dubG9hZCB0aGUgZmlsZQ0KZG93bmxvYWQuZmlsZSh1cmwsIGRlc3RmaWxlID0gImxheF90b19qZmsudGFyLmd6IikNCiMgVW50YXIgdGhlIGZpbGUgc28gd2UgY2FuIGdldCB0aGUgY3N2IG9ubHkNCnVudGFyKCJsYXhfdG9famZrLnRhci5neiIsIHRhciA9ICJpbnRlcm5hbCIpDQojIHJlYWRfY3N2IG9ubHkNCnN1Yl9haXJsaW5lIDwtIHJlYWRfY3N2KCJsYXhfdG9famZrL2xheF90b19qZmsuY3N2IiwgY29sX3R5cGVzID0gY29scygNCiAgJ0RpdkRpc3RhbmNlJyA9IGNvbF9udW1iZXIoKSwNCiAgJ0RpdkFyckRlbGF5JyA9IGNvbF9udW1iZXIoKQ0KKSkNCmBgYA0KDQojIyAxLiBNaXNzaW5nIFZhbHVlcyBhbmQgRm9ybWF0dGluZw0KDQpOb3cgdGhhdCB3ZSBoYXZlIHRoZSBkYXRhIGxvYWRlZCwgbGV0J3MgZmlyc3QgdGFrZSBhIGxvb2sgYXQgdGhlIGRhdGEgdXNpbmcgdGhlIG1ldGhvZCBgaGVhZCgpYC4NCg0KYGBge3J9DQpoZWFkKHN1Yl9haXJsaW5lKQ0KYGBgDQoNCiMjIyAxLjEgSWRlbnRpZnkgTWlzc2luZyBWYWx1ZXMNCg0KSW4gUiwgdGhlcmUgYXJlIHNvbWUgc3BlY2lhbCBzeW1ib2xzIHRvIHJlcHJlc2VudCBzcGVjaWFsIGNhc2VzIGluIGRhdGE6DQoNCi0gICBgTkFgOiBtaXNzaW5nIHZhbHVlcyBhcmUgcmVwcmVzZW50ZWQgYnkgdGhlIHN5bWJvbCBgTkFgIChub3QgYXZhaWxhYmxlKSwgaXQgaXMgYSBzcGVjaWFsIHN5bWJvbCBpbiBSLiBOb3RlLCB0aGF0IGAiTkEiYCAoYSBzdHJpbmcpIGlzIG5vdCB0aGUgc2FtZSBhcyBgTkFgLg0KDQotICAgYE5hTmA6IEltcG9zc2libGUgdmFsdWVzIChlLmcuLCBkaXZpZGluZyBieSB6ZXJvKSBhcmUgcmVwcmVzZW50ZWQgYnkgdGhlIHN5bWJvbCBgTmFOYCAobm90IGEgbnVtYmVyKS4NCg0KVGhlIG1pc3NpbmcgdmFsdWVzIGluIGFpcmxpbmUgZGF0YXNldCBhcmUgYWxyZWFkeSByZXByZXNlbnRlZCB3aXRoIFIncyBgTkFgIHN5bWJvbC4gV2UgdXNlIFIncyBidWlsdC1pbiAoYWxzbyBjYWxsZWQgKipiYXNlIFIqKikgZnVuY3Rpb25zIHRvIGlkZW50aWZ5IHRoZXNlIG1pc3NpbmcgdmFsdWVzLiBUaGVyZSBhcmUgdHdvIG1ldGhvZHMgdG8gZGV0ZWN0IG1pc3NpbmcgZGF0YToNCg0KMS4gIGBpcy5uYSh4KWA6IHggY2FuIGJlIGEgdmVjdG9yIG9yIGxpc3QsIHRoaXMgbWV0aG9kIHJldHVybnMgYSB2ZWN0b3Igb2YgVFJVRSBvciBGQUxTRSBkZXBlbmRpbmcgaWYgdGhlIGFjY29yZGluZyBlbGVtZW50IGluIHggaXMgYE5BYCBvciBub3QuIEZvciBleGFtcGxlIGBpcy5uYShjKDEsIE5BKSlgIHJldHVybnMgYEZBTFNFIFRSVUVgDQoNCjIuICBgYW55TkEoeCwgcmVjdXJzaXZlID0gRkFMU0UpYDogeCBjYW4gYmUgYSB2ZWN0b3Igb3IgbGlzdCwgdGhpcyBtZXRob2QgcmV0dXJucyBUUlVFIGlmIHggY29udGFpbnMgYW55IE5BcyBhbmQgRkFMU0Ugb3RoZXJ3aXNlLiBGb3IgZXhhbXBsZSBgaXMubmEoYygxLCBOQSkpYCByZXR1cm5zIGBUUlVFYA0KDQpgYGB7cn0NCmlzLm5hKGMoMSwgTkEpKQ0KaXMubmEocGFzdGUoYygxLCBOQSkpKQ0KYGBgDQoNCkFnYWluLCB0aGUgb3V0cHV0IGZvciBgaXMubmEoKWAgaXMgYSB2ZWN0b3Igb2YgbG9naWNhbCB2YWx1ZXMgd2hlcmVgVFJVRWAgc3RhbmRzIGZvciBtaXNzaW5nIHZhbHVlLCB3aGlsZSBgRkFMU0VgIHN0YW5kcyBmb3Igbm90IG1pc3NpbmcgdmFsdWUuDQoNCmBgYHtyfQ0KYW55TkEoYygxLCBOQSkpDQpgYGANCg0KVGhlIG91dHB1dCBmb3IgYGFueU5BKClgIGlzIGEgdmVjdG9yIG9mIGxvZ2ljYWwgdmFsdWVzIHdoZXJlIGBUUlVFYCBzdGFuZHMgZm9yIGF0IGxlYXN0IG9uZSBtaXNzaW5nIHZhbHVlIGluIHRoZSB2ZWN0b3IsIHdoaWxlIGBGQUxTRWAgc3RhbmRzIGZvciBubyBtaXNzaW5nIHZhbHVlcyBpbiB0aGUgdmVjdG9yLg0KDQojIyMjIENvdW50aW5nIE1pc3NpbmcgVmFsdWVzDQoNCldlIGNhbiBxdWlja2x5IGZpZ3VyZSBvdXQgdGhlIG51bWJlciBvZiBtaXNzaW5nIHZhbHVlcyBpbiBlYWNoIGNvbHVtbi4gQXMgbWVudGlvbmVkIGFib3ZlLCB3aGVuIHVzaW5nIHRoZSBmdW5jdGlvbiBgaXMubmEoKWAsIGBUUlVFYCByZXByZXNlbnRzIGEgbWlzc2luZyB2YWx1ZSB3aGlsZSBgRkFMU0VgIGlzIG90aGVyd2lzZS4gVGhlIG1ldGhvZCBgc3VtKClgIGNvdW50cyB0aGUgbnVtYmVyIG9mIGBUUlVFYCB2YWx1ZXMuDQoNCkxldCdzIGNoZWNrIGhvdyBtYW55IG1pc3NpbmcgdmFsdWVzIGluIGBDYXJyaWVyRGVsYXlgIGNvbHVtbiBhbmQgYWxzbyBjaGVjayBob3cgbWFueSBtaXNzaW5nIHZhbHVlcyBpbiBlYWNoIGNvbHVtbjoNCg0KYGBge3J9DQojIGNvdW50aW5nIG1pc3NpbmcgdmFsdWVzDQpzdWJfYWlybGluZSAlPiUNCiAgc3VtbWFyaXNlKGNvdW50ID0gc3VtKGlzLm5hKENhcnJpZXJEZWxheSkpKQ0KYGBgDQoNCldlIGNhbiB1c2UgYHB1cnJyOjptYXAoKWAgdG8gY291bnQgbWlzc2luZyB2YWx1ZXMgaW4gZWFjaCBvZiB0aGUgY29sdW1ucy4NCg0KYG1hcCgpYCBlc3NlbnRpYWxseSBtYXBzIChhcHBsaWVzKSBhIGZ1bmN0aW9uIG9yIGZvcm11bGEgdG8gZWFjaCBnaXZlbiBlbGVtZW50LiBJbiB0aGUgY29kZSBiZWxvdywgaXQgaXMgbWFwcGluZyBhICpmb3JtdWxhKiwgYH5zdW0oaXMubmEoLikpYCwgdGhhdCBzdW1zIHRoZSBOQXMgdG8gZXZlcnkgY29sdW1uIGluIGBzdWJfYWlybGluZWAuIFNpbmNlIGl0IGlzIHVzaW5nIGEgKmZvcm11bGEqLCB5b3Ugd2lsbCBhbHNvIG5vdGljZSB0d28gc3BlY2lhbCBvcGVyYXRvcnMgZG90IGAuYCBhbmQgdGlsZGUgYH5gOg0KDQotICAgVGhlIHRpbGRlIGB+YCBzZXBhcmF0ZXMgdGhlIGxlZnQgc2lkZSBvZiBhIGZvcm11bGEgd2l0aCB0aGUgcmlnaHQgc2lkZS4gTm9ybWFsbHkgZm9ybXVsYXMgYXJlIHR3by1zaWRlZCBsaWtlIGB5IH4geGAsIGluIHRoaXMgY2FzZSBpbiBgbWFwKClgLCB0aGUgZm9ybXVsYSBnZXRzIGNvbnZlcnRlZCB0byBhIGZ1bmN0aW9uIHNvIGl0IG9ubHkgbmVlZHMgdGhlIHJpZ2h0IHNpZGUuDQoNCi0gICBUaGUgZG90IGAuYCByZWZlcnMgdG8gZWFjaCBjb2x1bW4gaW4gdGhlIGRhdGFzZXQuIElmIHlvdSB2aWV3IHRoZSBkb2N1bWVudGF0aW9uIHdpdGggYD9tYXBgLCB5b3UgY2FuIHNlZSB0aGF0IHlvdSB1c2UgYC5gIHdoZW4gdGhlIGZ1bmN0aW9uIHRha2VzIGluIGp1c3Qgb25lIHBhcmFtZXRlciAoYSBjb2x1bW4gaW4gdGhpcyBjYXNlKS4NCg0KU2VlIGA/Zm9ybXVsYWAgYW5kIGA/bWFwYCBmb3IgbW9yZSBpbmZvcm1hdGlvbi4NCg0KYGBge3J9DQptYXAoc3ViX2FpcmxpbmUsIH5zdW0oaXMubmEoLikpKQ0KYGBgDQoNCmBgYHtyfQ0KZGltKHN1Yl9haXJsaW5lKQ0KYGBgDQoNCkJhc2VkIG9uIHRoZSBzdW1tYXJ5IGFib3ZlLCAiQ2FycmllckRlbGF5IiwgIldlYXRoZXJEZWxheSIsICJOQVNEZWxheSIsICJTZWN1cml0eURlbGF5IiBhbmQgIkxhdGVBaXJjcmFmdERlbGF5IiBjb2x1bW5zIGhhdmUgMjQ4NiByb3dzIG9mIG1pc3NpbmcgZGF0YSwgd2hpbGUgIkRpdkRpc3RhbmNlIiBhbmQgIkRpdkFyckRlbGF5IiBjb2x1bW5zIGhhdmUgMjg1NSByb3dzIG9mIG1pc3NpbmcgZGF0YS4gQWxsIG90aGVyIGNvbHVtbnMgZG8gbm90IGhhdmUgbWlzc2luZyBkYXRhLg0KDQoxLiAgIkNhcnJpZXJEZWxheSI6IDI0ODYgbWlzc2luZyBkYXRhDQoNCjIuICAiV2VhdGhlckRlbGF5IjogMjQ4NiBtaXNzaW5nIGRhdGENCg0KMy4gICJOQVNEZWxheSI6IDI0ODYgbWlzc2luZyBkYXRhDQoNCjQuICAiU2VjdXJpdHlEZWxheSIgOiAyNDg2IG1pc3NpbmcgZGF0YQ0KDQo1LiAgIkxhdGVBaXJjcmFmdERlbGF5IjogMjQ4NiBtaXNzaW5nIGRhdGENCg0KNi4gICJEaXZEaXN0YW5jZSI6IDI4NTUgbWlzc2luZyBkYXRhDQoNCjcuICAiRGl2QXJyRGVsYXkiOiAyODU1IG1pc3NpbmcgZGF0YQ0KDQojIyMgMS4yIEhhbmRsZSBNaXNzaW5nIERhdGENCg0KKipIb3cgdG8gZGVhbCB3aXRoIG1pc3NpbmcgZGF0YT8qKg0KDQoxLiAgRHJvcCBkYXRhXA0KICAgIGEuICBEcm9wIHRoZSB3aG9sZSBjb2x1bW5cDQogICAgYi4gIERyb3AgdGhlIHdob2xlIHJvdw0KMi4gIFJlcGxhY2UgZGF0YVwNCiAgICBhLiAgUmVwbGFjZSBpdCBieSBtZWFuXA0KICAgIGIuICBSZXBsYWNlIGl0IGJ5IGZyZXF1ZW5jeVwNCiAgICBjLiAgUmVwbGFjZSBpdCBiYXNlZCBvbiBvdGhlciBmdW5jdGlvbnMNCg0KR2VuZXJhbGx5LCB5b3Ugc2hvdWxkIG5vdCBibGluZGx5IGRyb3AgTkFzLiBIb3dldmVyIGlmIGFuIGVudGlyZSBjb2x1bW4gb3IgYWxtb3N0IHRoZSBlbnRpcmUgY29sdW1uIGNvbnRhaW5zIE5BcywgdGhlbiBpdCBtYXkgYmUgYSBnb29kIGlkZWEgdG8gbGVhdmUgaXQgb3V0LiBJbiBvdXIgZGF0YXNldCwgY29sdW1ucyBgRGl2RGlzdGFuY2VgIGFuZCBgRGl2QXJyRGVsYXlgIGFyZSBuZWFybHkgYWxsIGVtcHR5IHNvIHdlIHdpbGwgZHJvcCB0aGVtIGVudGlyZWx5Lg0KDQoqKkRyb3AgdGhlIHdob2xlIGNvbHVtbjoqKg0KDQotICAgIkRpdkRpc3RhbmNlIjogMjg1NSBtaXNzaW5nIGRhdGENCg0KLSAgICJEaXZBcnJEZWxheSI6IDI4NTUgbWlzc2luZyBkYXRhDQoNCmBgYHtyfQ0KZHJvcF9uYV9jb2xzIDwtIHN1Yl9haXJsaW5lICU+JQ0KICBzZWxlY3QoLURpdkRpc3RhbmNlLCAtRGl2QXJyRGVsYXkpDQpkaW0oZHJvcF9uYV9jb2xzKQ0KaGVhZChkcm9wX25hX2NvbHMsIG4gPSAxMCkNCmBgYA0KDQoqKkRyb3AgdGhlIHdob2xlIHJvdzoqKg0KDQotICAgIkNhcnJpZXJEZWxheSI6IDI0ODYgbWlzc2luZyBkYXRhDQoNCi0gICAiV2VhdGhlckRlbGF5IjogMjQ4NiBtaXNzaW5nIGRhdGENCg0KLSAgICJOQVNEZWxheSI6IDI0ODYgbWlzc2luZyBkYXRhDQoNCi0gICAiU2VjdXJpdHlEZWxheSIgOiAyNDg2IG1pc3NpbmcgZGF0YQ0KDQotICAgIkxhdGVBaXJjcmFmdERlbGF5IjogMjQ4NiBtaXNzaW5nIGRhdGENCg0KV2Ugc2VlIGBDYXJyaWVyRGVsYXlgLCBgV2VhdGhlckRlbGF5YCwgYE5BU0RlbGF5YCwgYFNlY3VyaXR5RGVsYXlgLCBgTGF0ZUFpcmNyYWZ0RGVsYXlgIGhhdmUgdGhlIHNhbWUgYW1vdW50IG9mIG1pc3NpbmcgdmFsdWVzIGZyb20gdGhlIHN1bW1hcnkuIEJ5IGRyb3BwaW5nIHRoZSBtaXNzaW5nIHZhbHVlcyBpbiBvbmUgY29sdW1uIHdpbGwgYWxzbyBzb2x2ZSB0aGUgbWlzc2luZyB2YWx1ZSBpc3N1ZXMgaW4gdGhlIG90aGVycy4NCg0KYGBge3J9DQojIERyb3AgdGhlIG1pc3NpbmcgdmFsdWVzDQpkcm9wX25hX3Jvd3MgPC0gZHJvcF9uYV9jb2xzICU+JQ0KICBkcm9wX25hKENhcnJpZXJEZWxheSkNCmRpbShkcm9wX25hX3Jvd3MpDQpoZWFkKGRyb3BfbmFfcm93cykNCmBgYA0KDQpXZSBoYXZlIHNvbWUgZnJlZWRvbSBpbiBjaG9vc2luZyB3aGljaCBtZXRob2QgdG8gcmVwbGFjZSBkYXRhOyBob3dldmVyLCBzb21lIG1ldGhvZHMgbWF5IHNlZW0gbW9yZSByZWFzb25hYmxlIHRoYW4gb3RoZXJzLiBJbiB0aGlzIHNjZW5hcmlvLCB3ZSB3b3VsZCBsaWtlIHRvIHJlcGxhY2UgbWlzc2luZyB2YWx1ZXMgd2l0aCAwLg0KDQoqKkNvbnZlcnQgTkEgdG8gMCoqDQoNCkluIHRoZSBhaXJsaW5lIGRhdGFzZXQsIG1pc3NpbmcgZGF0YSBmb3IgdGhlIGRpZmZlcmVudCB0eXBlcyBvZiBkZWxheSBjb3JyZXNwb25kcyB0byBubyBkZWxheS4gU28sIHdlIGNhbiByZXBsYWNlIHRoZXNlIE5BcyB3aXRoIDAgaW4gdGhpcyBjYXNlLiBUbyBkbyB0aGlzIHdlIHVzZSB0aGUgZnVuY3Rpb246DQoNCmBgYCAgICAgICAgIA0KdGlkeXI6OnJlcGxhY2VfbmEoZGF0YSwgcmVwbGFjZSwgLi4uKQ0KYGBgDQoNClRoZSBjb2x1bW5zIHRoYXQgY29ycmVzcG9uZHMgd2l0aCB0eXBlcyBvZiBkZWxheXMgYXJlIENhcnJpZXJEZWxheSwgV2VhdGhlckRlbGF5LCBOQVNEZWxheSwgU2VjdXJpdHlEZWxheSwgYW5kIExhdGVBaXJjcmFmdERlbGF5LiBGb3IgZXhhbXBsZSBpZiBgQ2FycmllckRlbGF5ID0gTkFgIHRoZW4gdGhpcyBtZWFucyB0aGVyZSBpcyBubyBkZWxheSBpbiBDYXJyaWVyLCBzbyB0aGUgZGVsYXkgaW4gbWludXRlcyBjYW4gYmUgY2hhbmdlZCB0byAwIG9yIGBDYXJyaWVyRGVsYXkgPSAwYC4gTGV0J3MgdHJhbnNmb3JtIHRoZXNlIGNvbHVtbnMgYW5kIHNlZSB0aGUgcmVzdWx0Og0KDQpgYGB7cn0NCiMgUmVwbGFjZSB0aGUgbWlzc2luZyB2YWx1ZXMgaW4gZml2ZSBjb2x1bW5zDQpyZXBsYWNlX25hbiA8LSBkcm9wX25hX3Jvd3MgJT4lDQogIHJlcGxhY2VfbmEobGlzdChDYXJyaWVyRGVsYXkgPSAwLCBXZWF0aGVyRGVsYXkgPSAwLCBOU0FEZWxheSA9IDAsIFNlY3VyaXR5RGVsYXkgPSAwLCBMYXRlQWlyY3JhZnREZWxheSA9IDApKQ0KaGVhZChyZXBsYWNlX25hbikNCmBgYA0KDQojIFF1ZXN0aW9uICMxOg0KDQoqKkFjY29yZGluZyB0byB0aGUgZXhhbXBsZSBhYm92ZSwgbGV0J3MgdHJ5IHRvIHJlcGxhY2UgTkEgaW4gIkNhcnJpZXJEZWxheSIgY29sdW1uIGJ5IHRoZSBtZWFuIHZhbHVlLioqDQoNCmBgYHtyfQ0KY2Fycmllcl9tZWFuIDwtIG1lYW4oZHJvcF9uYV9yb3dzJENhcnJpZXJEZWxheSkNCnN1Yl9haXJsaW5lICU+JSByZXBsYWNlX25hKGxpc3QoQ2FycmllckRlbGF5ID0gY2Fycmllcl9tZWFuKSkNCmhlYWQoc3ViX2FpcmxpbmUpDQpgYGANCg0KIyMjIDEuMyBDb3JyZWN0IERhdGEgRm9ybWF0DQoNCioqV2UgYXJlIGFsbW9zdCB0aGVyZSEqKg0KDQpUaGUgbGFzdCBzdGVwIGluIGRhdGEgY2xlYW5pbmcgaXMgY2hlY2tpbmcgYW5kIG1ha2luZyBzdXJlIHRoYXQgYWxsIGRhdGEgaXMgaW4gdGhlIGNvcnJlY3QgZm9ybWF0Lg0KDQojIyMjIFNjb3BlZCBkcGx5ciBWZXJicw0KDQpJbiBkcGx5ciwgd2UgY2FuIGFkZCBgX2FsbGAgYW5kIGBfaWZgIHRvIHRoZSBlbmQgb2YgaXQncyBtYWluIGZ1bmN0aW9ucyBsaWtlIGBtdXRhdGVgLCBgZmlsdGVyYCwgYGdyb3VwX2J5YCwgYW5kIGBzdW1tYXJpemVgLg0KDQpUaGVzZSBhcmUgY2FsbGVkICoqc2NvcGVkKiogZHBseXIgdmVyYnMuIFRoZSBgX2FsbGAgdmFyaWFudCBhcHBsaWVzIGFuIG9wZXJhdGlvbiBvbiAqYWxsKiB2YXJpYWlibGVzIGFuZCB0aGUgYF9pZmAgdmFyaWFudCBhcHBsaWVzIGFuIG9wZXJhdGlvbiB0byBhIHZhcmlhYmxlIGlmIHRoZSBnaXZlbiBmdW5jdGlvbiBpcyBgVFJVRWAuDQoNCk5vdyB0aGF0IHdlIHVuZGVyc3RhbmQgdGhlc2UgZGlmZmVyZW50IHZlcnNpb25zIG9mIGRwbHlyIGZ1bmN0aW9ucywgbGV0J3MgbGlzdCB0aGUgZGF0YSB0eXBlcyBmb3IgZWFjaCBjb2x1bW4gYnkgdXNpbmcgYHN1bW1hcml6ZV9hbGwoKWAgYW5kIGBnYXRoZXIoKWA6DQoNCmBgYHtyfQ0Kc3ViX2FpcmxpbmUgJT4lDQogIHN1bW1hcmlzZV9hbGwoY2xhc3MpICU+JQ0KICBnYXRoZXIodmFyaWFibGUsIGNsYXNzKQ0KYGBgDQoNCmBgYHtyfQ0KZGF0ZV9haXJsaW5lIDwtIHJlcGxhY2VfbmFuICU+JQ0KICBzZXBhcmF0ZShGbGlnaHREYXRlLCBzZXAgPSAiLSIsIGludG8gPSBjKCJ5ZWFyIiwgIm1vbnRoIiwgImRheSIpKQ0KDQpoZWFkKGRhdGVfYWlybGluZSkNCmBgYA0KDQpGb3IgYSBudW1iZXIgb2YgcmVhc29ucywgaW5jbHVkaW5nIHdoZW4gd2UgaW1wb3J0IGEgZGF0YXNldCBpbnRvIFIgb3IgcHJvY2VzcyBhIHZhcmlhYmxlLCB0aGUgZGF0YSB0eXBlIG1heSBiZSBpbmNvcnJlY3RseSBlc3RhYmxpc2hlZC4gRm9yIGV4YW1wbGUsIGhlcmUgd2Ugbm90aWNlIHRoYXQgdGhlIGFzc2lnbmVkIGRhdGEgdHlwZSB0byBmb3IgYHllYXJgLCBgbW9udGhgLCBhbmQgYGRheWAgaXMgImNoYXJhY3RlciIgYWx0aG91Z2ggdGhlIGV4cGVjdGVkIGRhdGEgdHlwZSBzaG91bGQgcmVhbGx5IGJlIG51bWVyaWMuIFlvdSBjYW4gY2hhbmdlIHRoZSB0eXBlIHVzaW5nIGBtdXRhdGVfYWxsKClgIGFuZCBgbXV0YXRlX2lmKClgLg0KDQpJbiB0aGUgYmVsb3cgY29kZSwgaXQgaXMgbXV0YXRpbmcgYHllYXJgLCBgbW9udGhgLCBhbmQgYGRheWAgdmFsdWVzIHRvIG51bWVyaWMgb25seSBpZiBpdCBpcyBhIGNoYXJhY3Rlci4gTm90ZSB0aGF0IGluIGBtdXRhdGVfaWYoKWAsIHRoZSBmaXJzdCBwYXJhbWV0ZXIgaXMgdGhlIGZ1bmN0aW9uIGBpcy5jaGFyYWN0ZXJgIHRoYXQgY2hlY2tzIGEgKmNvbmRpdGlvbiogd2hpbGUgdGhlIHNlY29uZCBwYXJhbWV0ZXJzIGlzIHRoZSBmdW5jdGlvbiBgYXMubnVtZXJpY2AgdGhhdCAqbW9kaWZpZXMqIHRoZSBkYXRhICppZiogdGhlIGNvbmRpdGlvbiBpcyB0cnVlLg0KDQpgYGB7cn0NCmRhdGVfYWlybGluZSAlPiUNCiAgc2VsZWN0KHllYXIsIG1vbnRoLCBkYXkpICU+JQ0KICBtdXRhdGVfYWxsKHR5cGUuY29udmVydCkgJT4lDQogIG11dGF0ZV9pZiguLCBpcy5jaGFyYWN0ZXIsIGFzLm51bWVyaWMpDQpgYGANCg0KKipXb25kZXJmdWwhKioNCg0KTm93LCB3ZSBmaW5hbGx5IG9idGFpbiB0aGUgY2xlYW5lZCBkYXRhc2V0IHdpdGggbm8gbWlzc2luZyB2YWx1ZXMgYW5kIGFsbCBkYXRhIGluIGl0cyBwcm9wZXIgZm9ybWF0Lg0KDQojIyBEYXRhIE5vcm1hbGl6YXRpb24NCg0KKipXaHkgbm9ybWFsaXphdGlvbj8qKg0KDQpOb3JtYWxpemF0aW9uIGlzIHRoZSBwcm9jZXNzIG9mIHRyYW5zZm9ybWluZyB2YWx1ZXMgb2Ygc2V2ZXJhbCBmZWF0dXJlcyAodmFyaWFibGVzKSBpbnRvIGEgc2ltaWxhciByYW5nZS4gQW4gZXhhbXBsZSBvZiB3aHkgdGhpcyBjb3VsZCBiZSBpbXBvcnRhbnQgaXMgaWYgeW91IGhhdmUgYSB2YXJpYWJsZSBmb3IgaW5jb21lIGFuZCBhbm90aGVyIHZhcmlhYmxlIGZvciBhZ2UuIEluY29tZSBpcyBsaWtlbHkgbXVjaCBiaWdnZXIgdmFsdWVzIHRoYW4gYWdlLCBzbyBpbiBhIG1vZGVsLCBpbmNvbWUgd291bGQgbmF0dXJhbGx5IGluZmx1ZW5jZSB0aGUgbW9kZWwgbW9yZS4gVGh1cywgbm9ybWFsaXphdGlvbiBoZWxwcyBtYWtlIGNvbXBhcmlzb25zIGJldHdlZW4gZGlmZmVyZW50IHZhcmlhYmxlcyBtb3JlICpmYWlyKi4NCg0KIyMjIFNpbXBsZSBzY2FsaW5nDQoNCioqU2ltcGxlIHNjYWxpbmcqKiBkaXZpZGVzIGVhY2ggdmFsdWUgYnkgdGhlIG1heGltdW0gdmFsdWUgaW4gYSBmZWF0dXJlLiBUaGUgbmV3IHJhbmdlIGlzIGJldHdlZW4gMCBhbmQgMS4NCg0KKipFeGFtcGxlKioNCg0KVG8gZGVtb25zdHJhdGUgc2FtcGxpbmcgc2NhbGluZywgbGV0J3Mgc2F5IHdlIHdhbnQgdG8gc2NhbGUgdGhlIGNvbHVtbiAiQXJyRGVsYXkiLg0KDQotICAgKipUYXJnZXQ6KiogV291bGQgbGlrZSB0byBOb3JtYWxpemUgdGhvc2UgdmFyaWFibGVzIHNvIHRoZWlyIHZhbHVlIHJhbmdlcyBmcm9tIDAgdG8gMS4NCg0KLSAgICoqQXBwcm9hY2g6KiogUmVwbGFjZSB0aGUgb3JpZ2luYWwgdmFsdWUgYnkgKihvcmlnaW5hbCB2YWx1ZSkgLyAobWF4aW11bSB2YWx1ZSkqDQoNCmBgYHtyfQ0Kc2ltcGxlX3NjYWxlIDwtIHN1Yl9haXJsaW5lJEFyckRlbGF5IC8gbWF4KHN1Yl9haXJsaW5lJEFyckRlbGF5KQ0KaGVhZChzaW1wbGVfc2NhbGUsIG49MTApDQpgYGANCg0KIyBRdWVzdGlvbiAjMjoNCg0KKipBY2NvcmRpbmcgdG8gdGhlIGV4YW1wbGUgYWJvdmUsIG5vcm1hbGl6ZSB0aGUgY29sdW1uICJEZXBEZWxheSIgdXNpbmcgdGhlIHNpbXBsZSBzY2FsaW5nIHRlY2huaXF1ZS4qKg0KDQpgYGB7cn0NCnNpbXBsZV9zY2FsZV9kZXBkbHkgPC0gc3ViX2FpcmxpbmUkRGVwRGVsYXkgLyBtYXgoc3ViX2FpcmxpbmUkRGVwRGVsYXkpDQpoZWFkKHNpbXBsZV9zY2FsZV9kZXBkbHksIG49MTApDQpgYGANCg0KIyMjIE1pbiAtIG1heA0KDQoqKk1pbi1tYXgqKiBzdWJ0cmFjdHMgdGhlIG1pbmltdW0gdmFsdWUgZnJvbSB0aGUgb3JpZ2luYWwgYW5kIGRpdmlkZXMgYnkgdGhlIG1heGltdW0gbWludXMgdGhlIG1pbmltdW0uIFRoZSBtaW5pbXVtIGJlY29tZXMgMCBhbmQgdGhlIG1heGltdW0gYmVjb21lcyAxLg0KDQoqKkV4YW1wbGUqKg0KDQpVc2luZyAiQXJyRGVsYXkiIGFzIGFuIGV4YW1wbGUgYWdhaW4sIHlvdSBjYW4gdHJhbnNmb3JtIHRoaXMgY29sdW1uIHVzaW5nIHRoZSBtaW4tbWF4IHRlY2huaXF1ZToNCg0KYGBge3J9DQptaW5tYXhfc2NhbGUgPC0gKHN1Yl9haXJsaW5lJEFyckRlbGF5IC0gbWluKHN1Yl9haXJsaW5lJEFyckRlbGF5KSkgLyAobWF4KHN1Yl9haXJsaW5lJEFyckRlbGF5KSAtIG1pbihzdWJfYWlybGluZSRBcnJEZWxheSkpDQpoZWFkKG1pbm1heF9zY2FsZSwgbj0xMCkNCmBgYA0KDQojIyMgRGF0YSBTdGFuZGFyZGl6YXRpb24gKFotc2NvcmUpDQoNCioqU3RhbmRhcmRpemF0aW9uIChaLXNjb3JlKSoqIHN1YnRyYWN0cyB0aGUgbWVhbiAo8J2chykgb2YgdGhlIGZlYXR1cmUgYW5kIGRpdmlkZXMgYnkgdGhlIHN0YW5kYXJkIGRldmlhdGlvbiAo8J2cjikuDQoNCioqRXhhbXBsZSoqDQoNCkxldCdzIHVzZSAiQXJyRGVsYXkiIGFnYWluLiBXZSBjYW4gdXNlIGBtZWFuKClgIHRvIGZpbmQgdGhlIG1lYW4gb2YgdGhlIGZlYXR1cmUgYW5kIHdlIGNhbiB1c2UgYHNkKClgIHRvIGZpbmQgdGhlIHN0YW5kYXJkIGRldmlhdGlvbiBvZiB0aGUgZmVhdHVyZS4NCg0KYGBge3J9DQp6X3NjYWxlIDwtIChzdWJfYWlybGluZSRBcnJEZWxheSAtIG1lYW4oc3ViX2FpcmxpbmUkQXJyRGVsYXkpKSAvIHNkKHN1Yl9haXJsaW5lJEFyckRlbGF5KQ0KaGVhZCh6X3NjYWxlLCBuPTEwKQ0KYGBgDQoNCiMgUXVlc3Rpb24gIzM6DQoNCioqQWNjb3JkaW5nIHRvIHRoZSBleGFtcGxlIGFib3ZlLCBzdGFuZGFyZGl6ZSB0aGUgIkRlcERlbGF5IiBjb2x1bW4uKioNCg0KYGBge3J9DQp6X3NjYWxlX0RlcERseSA8LSAoc3ViX2FpcmxpbmUkRGVwRGVsYXkgLSBtZWFuKHN1Yl9haXJsaW5lJERlcERlbGF5KSkgLyBzZChzdWJfYWlybGluZSREZXBEZWxheSkNCmhlYWQoel9zY2FsZV9EZXBEbHksIG49MTApDQpgYGANCg0KIyMgQmlubmluZw0KDQojIyMgV2h5IHVzZSBiaW5uaW5nPw0KDQpCaW5uaW5nIGlzIGEgcHJvY2VzcyBvZiB0cmFuc2Zvcm1pbmcgY29udGludW91cyBudW1lcmljYWwgdmFyaWFibGVzIGludG8gZGlzY3JldGUgY2F0ZWdvcmljYWwgJ2JpbnMnLCBmb3IgZ3JvdXBlZCBhbmFseXNpcy4NCg0KKipFeGFtcGxlOioqDQoNClVzaW5nIGJpbm5pbmcsIHdlIGNhdGVnb3JpemUgYXJyaXZhbCBkZWxheXMgaW50byBmb3VyIGJpbnMgYnkgcXVhcnRpbGVzLg0KDQpRdWFydGlsZXMgcmVmZXIgdG8gdGhlIGJvdW5kYXJpZXMgdGhhdCBkaXZpZGUgb2JzZXJ2YXRpb25zIGludG8gZm91ciBkZWZpbmVkIGludGVydmFscy4gVGhleSBhcmUgb2Z0ZW4gZGV0ZXJtaW5lZCBiYXNlZCBvbiB0aGUgdmFsdWVzIG9mIGRhdGEgcG9pbnRzIGFuZCBob3cgdGhleSBhcmUgY29tcGFyZWQgd2l0aCB0aGUgcmVzdCBvZiB0aGUgZGF0YXNldC4gSW4gdGhlIGFjdHVhbCBhaXJsaW5lIGRhdGFzZXQsICJBcnJEZWxheSIgaXMgYSBudW1lcmljYWwgdmFyaWFibGUgcmFuZ2luZyBmcm9tIC03MyB0byA2ODIsIGl0IGhhcyAyODU1IHVuaXF1ZSB2YWx1ZXMuIFdlIGNhbiBjYXRlZ29yaXplIHRoZW0gaW50byA0IGJpbnMuIENhbiB3ZSByZWFycmFuZ2UgdGhlbSBpbnRvIGZvdXIgJ2JpbnMnIHRvIHNpbXBsaWZ5IGFuYWx5c2lzPw0KDQpXZSB3aWxsIHVzZSB0aGUgdGlkeXZlcnNlIG1ldGhvZCBgbXV0YXRlKHF1YW50aWxlX3JhbmsgPSBudGlsZSgpKWAgdG8gc2VnbWVudCB0aGUgIkFyckRlbGF5IiBjb2x1bW4gaW50byA0IGJpbnMNCg0KIyMjIEV4YW1wbGUgb2YgQmlubmluZyBEYXRhIEluIFRpZHl2ZXJzZQ0KDQpMZXRzIHBsb3QgdGhlIGhpc3RvZ3JhbSBvZiBmbGlnaHQgYXJyaXZhbCBkZWxheXMsIHRvIHNlZSB3aGF0IHRoZSBkaXN0cmlidXRpb24gb2YgIkFyckRlbGF5IiBsb29rcyBsaWtlLg0KDQpgYGB7cn0NCmdncGxvdChkYXRhID0gc3ViX2FpcmxpbmUsIG1hcHBpbmcgPSBhZXMoeD0gQXJyRGVsYXkpKSArIA0KICBnZW9tX2hpc3RvZ3JhbShiaW5zID0gMTAwLCBjb2xvciA9ICJ3aGl0ZSIsIGZpbGwgPSAicmVkIikgKw0KICBjb29yZF9jYXJ0ZXNpYW4oeGxpbSA9IGMoLTczLCA2ODIpKQ0KYGBgDQoNCkZpcnN0IHdlIHVzZSB0aGUgZHBseXIgZnVuY3Rpb24gYG50aWxlYCB0byBicmVhayAiQXJyRGVsYXkiIGludG8gNCBidWNrZXRzLCB3aGljaCBoYXZlIGVxdWFsIGFtb3VudCBvZiBvYnNlcnZhdGlvbnMgb2YgZmxpZ2h0IGFycml2YWwgZGVsYXlzLiBXZSB0aGVuIGNyZWF0ZSBhIGxpc3QgInF1YW50aWxlX3JhbmsiIHRoYXQgY29udGFpbnMgNCBiaW5zLCB3aGljaCBhcmUgcmVzcGVjdGl2ZWx5IGxhYmVsZWQgIjEiLCAiMiIsICIzIiwgIjQiLiBTbyBiaW4gMSB3b3VsZCBjb250YWluIHRoZSBmaXJzdCAyNSUgb2YgZGF0YSwgYmluIDIgdGhlIG5leHQgMjUlIG9mIGRhdGEgYW5kIHNvIG9uLg0KDQpgYGB7cn0NCmJpbm5pbmcgPC0gc3ViX2FpcmxpbmUgJT4lDQogIG11dGF0ZShxdWFudGlsZV9yYW5rID0gbnRpbGUoc3ViX2FpcmxpbmUkQXJyRGVsYXksIDQpKQ0KDQpoZWFkKGJpbm5pbmcpDQpgYGANCg0KVGhlIG9ic2VydmF0aW9ucyBhcmUgcHV0IGludG8gZGlmZmVyZW50IGJpbnMgYmFzZWQgb24gdGhlIGZsaWdodHMnIGRlbGF5IG1pbnV0ZXMuIFRoZSBsYXJnZXIgdGhlIGJpbiBsYWJlbCBpcywgdGhlIGxvbmdlciB0aGUgZmxpZ2h0IHdhcyBkZWxheWVkLg0KDQpOb3cgaWYgd2UgbG9vayBhdCBhIGhpc3RvZ3JhbSBvZiB0aGUgYmlucywgeW91IGNhbiBzZWUgdGhhdCBhbGwgYmlucyBhcmUgZXF1YWwuDQoNCmBgYHtyfQ0KZ2dwbG90KGRhdGEgPSBiaW5uaW5nLCBtYXBwaW5nID0gYWVzKHggPSBxdWFudGlsZV9yYW5rKSkgKyANCiAgZ2VvbV9oaXN0b2dyYW0oYmlucyA9IDQsIGNvbG9yID0gIndoaXRlIiwgZmlsbCA9ICJyZWQiKQ0KYGBgDQoNCiMjIEluZGljYXRvciB2YXJpYWJsZQ0KDQojIyMjIFdoYXQgaXMgYW4gaW5kaWNhdG9yIHZhcmlhYmxlPw0KDQpBbiBpbmRpY2F0b3IgdmFyaWFibGUgKG9yIGR1bW15IHZhcmlhYmxlKSBpcyBhICoqbnVtZXJpY2FsIHZhcmlhYmxlKiogdXNlZCB0byAqKmxhYmVsIGNhdGVnb3JpZXMqKi4gVGhleSBhcmUgY2FsbGVkICdkdW1taWVzJyBiZWNhdXNlIHRoZSBudW1iZXJzIHRoZW1zZWx2ZXMgZG9uJ3QgaGF2ZSBpbmhlcmVudCBtZWFuaW5nLg0KDQoqKldoeSB3ZSB1c2UgaW5kaWNhdG9yIHZhcmlhYmxlcz8qKg0KDQpSZWdyZXNzaW9uIG1vZGVscyBuZWVkIG51bWVyaWNhbCB2YXJpYWJsZXMsIGhvd2V2ZXIgY2F0ZWdvcmljYWwgdmFyaWFibGVzIGluIHRoZWlyIG9yaWdpbmFsIGZvcm1zIGFyZSBzdHJpbmdzLiBVc2luZyBpbmRpY2F0b3IgdmFyaWFibGVzIGFsbG93cyB1cyB0byBiZSBhYmxlIHRvIHVzZSB0aGVzZSBjYXRlZ29yaWNhbCB2YXJpYWJsZXMgaW4gcmVncmVzc2lvbiBtb2RlbHMuDQoNCioqRXhhbXBsZSoqDQoNCkluIHRoZSBhaXJsaW5lIGRhdGFzZXQsIHRoZSAiUmVwb3J0aW5nX0FpcmxpbmUiIGZlYXR1cmUgaXMgYSBjYXRlZ29yaWNhbCB2YXJpYWJsZSB0aGF0IGhhcyBuaW5lIHZhbHVlcywgIkFBIiwgIkFTIiwgIkI2IiwgIkRMIiwgIkhQIiwgIlBBICgxKSIsICJUVyIsICJVQSIgb3IgIlZYIiwgd2hpY2ggYXJlIGluIGNoYXJhY3RlciB0eXBlLiBGb3IgZnVydGhlciBhbmFseXNpcywgd2UgbmVlZCB0byBjb252ZXJ0IHRoZXNlIHZhcmlhYmxlcyBpbnRvIHNvbWUgZm9ybSBvZiBudW1lcmljIGZvcm1hdC4NCg0KV2Ugd2lsbCB1c2UgdGhlIHRpZHl2ZXJzZSdzIG1ldGhvZCBgc3ByZWFkKClgIG1ldGhvZCB0byBjb252ZXJ0IGNhdGVnb3JpY2FsIHZhcmlhYmxlcyB0byBkdW1teSB2YXJpYWJsZXMuIFRoZSBwYXJhbWV0ZXJzIHRvIHVzZSBpbiB0aGUgZnVuY3Rpb24gYXJlOg0KDQotICAgYGtleWA6IHRoZSBjb2x1bW4gdG8gY29udmVydCBpbnRvIGNhdGVnb3JpY2FsIHZhbHVlcw0KDQotICAgYHZhbHVlYDogdGhlIHZhbHVlIHlvdSB3YW50IHRvIHNldCB0aGUga2V5IHRvDQoNCi0gICBgZmlsbGA6IGZpbGxzIHRoZSBtaXNzaW5nIHZhbHVlcyB3aXRoIHRoaXMgdmFsdWUNCg0KQWxzbyBrZWVwIGluIG1pbmQgdGhhdCB0aGUgbWV0aG9kIHdpbGwgZHJvcCB0aGUgIlJlcG9ydGluZ19BaXJsaW5lIiBjb2x1bW4gYnkgZGVmYXVsdC4gU28gaWYgeW91IHVzZWQgIkFyckRlbGF5IiBhcyB0aGUga2V5IHZhbHVlIGluc3RlYWQsIHRoYXQgY29sdW1uIHdvdWxkIGdldCBkcm9wcGVkLg0KDQpUaGUgYmVsb3cgY29kZSBkb2VzIHRoZSBmb2xsb3dpbmc6DQoNCjEuICBgbXV0YXRlYCAtIGNyZWF0ZXMgYSBjb2x1bW4gYGR1bW15YCB3aXRoIGFsbCAxJ3MgdGhlbg0KDQoyLiAgYHNwcmVhZGAgLSBjcmVhdGVzIG5ldyBjb2x1bW4gZm9yIGV2ZXJ5IGBSZXBvcnRpbmdfQXJpbGluZWAgYW5kIHNldHMgdGhlIHZhbHVlIHRvIDEgZnJvbSBgZHVtbXlgLiBCdXQgcmVwbGFjZXMgdGhlIDEgd2l0aCBhIDAgaWYgdGhlIGNvcnJlc3BvbmRpbmcgdmFsdWUgaXMgYE5BYCBpbiBgUmVwb3J0aW5nX0FpcmxpbmVgIHRoZW4NCg0KMy4gIGBzbGljZWAgLSBsb29rcyBhdCB0aGUgc3BlY2lmaWVkIHJvd3MNCg0KYGBge3J9DQpzdWJfYWlybGluZSAlPiUNCiAgbXV0YXRlKGR1bW15ID0gMSkgJT4lICNjb2x1bW4gd2l0aCBzaW5nbGUgdmFsdWUNCiAgc3ByZWFkKGtleSA9IFJlcG9ydGluZ19BaXJsaW5lLCB2YWx1ZSA9IGR1bW15LCBmaWxsID0gMCkgJT4lDQogIHNsaWNlKDE6MTApDQpgYGANCg0KV2hlbiBhIHZhbHVlIG9jY3VycyBpbiB0aGUgb3JpZ2luYWwgZmVhdHVyZSwgd2Ugc2V0IHRoZSBjb3JyZXNwb25kaW5nIHZhbHVlIHRvIG9uZSBpbiB0aGUgbmV3IGZlYXR1cmU7IHRoZSByZXN0IG9mIHRoZSBmZWF0dXJlcyBhcmUgc2V0IHRvIHplcm8uDQoNClNvIGluIHRoZSBvdXRwdXQgYWJvdmUsIGZvciB0aGUgZmlyc3Qgcm93LCB0aGUgcmVwb3J0aW5nIGFpcmxpbmUgaXMgIlVBIi4gVGhlcmVmb3JlLCB0aGUgZmVhdHVyZSAiVUEiIGlzIHNldCB0byBvbmUgYW5kIHRoZSBvdGhlciBmZWF0dXJlcyB0byB6ZXJvLiBTaW1pbGFybHksIGZvciB0aGUgc2Vjb25kIHJvdywgdGhlIHJlcG9ydGluZyBhaXJsaW5lIHZhbHVlIGlzICJBUyIuIFRoZXJlZm9yZSwgdGhlIGZlYXR1cmUgIkFTIiBpcyBzZXQgdG8gb25lIGFuZCB0aGUgb3RoZXIgZmVhdHVyZXMgdG8gemVyby4NCg0KQWx0ZXJuYXRpdmVseSwgaW5zdGVhZCBvZiBhc3NpZ25pbmcgZHVtbXkgdmFsdWVzIDAgb3IgMSwgd2UgY2FuIGFzc2lnbiBmbGlnaHQgZGVsYXkgdmFsdWVzIHRvIGVhY2ggZmVhdHVyZS4gU28gdGhpcyB3aWxsIGFsc28gY3JlYXRlIG5ldyBjb2x1bW5zLCBvbmUgZm9yIGVhY2ggYFJlcG9ydGluZ19BaXJsaW5lYC4gVGFraW5nIHRoZSBmaXJzdCByb3cgZm9yIGV4YW1wbGUsIHRoZSBjb2x1bW4gIlVBIiBpcyBub3cgc2V0IHRvIDIgYmVjYXVzZSB0aGF0IGlzIHRoZSB2YWx1ZSBmcm9tIEFyckRlbGF5Lg0KDQpgYGB7cn0NCnN1Yl9haXJsaW5lICU+JQ0KICBzcHJlYWQoUmVwb3J0aW5nX0FpcmxpbmUsIEFyckRlbGF5KSAlPiUNCiAgc2xpY2UoMToxMCkNCmBgYA0KDQpgYGB7cn0NCnZpZXcoc3ViX2FpcmxpbmUpDQpgYGANCg0KKipWaXN1YWxpemUgQWlybGluZSBDYXRlZ29yeSoqDQoNCkxldCdzIHZpc3VhbGl6ZSBob3cgbWFueSBkYXRhIHBvaW50cyBpbiBlYWNoIGFpcmxpbmUgY2F0ZWdvcnkuDQoNCmBgYHtyfQ0Kc3ViX2FpcmxpbmUgJT4lDQogIG11dGF0ZShSZXBvcnRpbmdfQWlybGluZSA9IGZhY3RvcihSZXBvcnRpbmdfQWlybGluZSwgbGFiZWxzID0gYygiQUEiLCAiQVMiLCAiREwiLCAiVUEiLCAiQjYiLCAiUEEgKDEpIiwgIkhQIiwgIlRXIiwgIlZYIikpKSAlPiUNCiAgZ2dwbG90KGFlcyhSZXBvcnRpbmdfQWlybGluZSkpICsgDQogIHN0YXRfY291bnQod2lkdGggPSAwLjUpICsNCiAgbGFicyh4ID0gIk51bWJlciBvZiBkYXRhcG9pbnRzIGluIGVhY2ggYWlybGluZSIpDQpgYGANCg0KIyBRdWVzdGlvbiAjNDoNCg0KKipBcyBhYm92ZSwgY3JlYXRlIGluZGljYXRvciB2YXJpYWJsZSB0byB0aGUgY29sdW1uIG9mICJNb250aCIuKioNCg0KYGBge3J9DQpzdWJfYWlybGluZSAlPiUNCiAgbXV0YXRlKE1vbnRoID0gZmFjdG9yKE1vbnRoLCBsYWJlbHMgPSBjKCJKYW4iLCAiRmViIiwgIk1hciIsICJBcHIiLCAiTWF5IiwgIkp1biIsICJKdWwiLCAiQXVnIiwgIlNlcCIsICJPY3QiLCAiTm92IiwgIkRlYyIpKSkgJT4lDQogIGdncGxvdChhZXMoTW9udGgpKSArIA0KICBzdGF0X2NvdW50KHdpZHRoID0gMC41KSArDQogIGxhYnMoeCA9ICJOdW1iZXIgb2YgZGF0YSBwb2ludHMgaW4gZWFjaCBNb250aCIpDQpgYGANCg0KIyBRdWVzdGlvbiAjNToNCg0KKipOb3csIGNyZWF0ZSBpbmRpY2F0b3IgdmFyaWFibGUgdG8gdGhlIGNvbHVtbiBvZiAiTW9udGgiIGJ5IGFwcGx5aW5nIGRlcGFydHVyZSBkZWxheSB2YWx1ZXMqKg0KDQpgYGB7cn0NCnN1Yl9haXJsaW5lICU+JQ0KICBzcHJlYWQoTW9udGgsIERlcERlbGF5KSAlPiUNCiAgc2xpY2UoMToxMCkNCmBgYA0KDQojIyBEZXNjcmlwdGl2ZSBTdGF0aXN0aWNzDQoNCmBgYHtyfQ0Kc3VtbWFyeV9haXJsaW5lX2RlbGF5cyA8LSBzdWJfYWlybGluZSAlPiUNCiAgZ3JvdXBfYnkoUmVwb3J0aW5nX0FpcmxpbmUpICU+JQ0KICBzdW1tYXJpc2UobWVhbiA9IG1lYW4oQXJyRGVsYXlNaW51dGVzLCBuYS5ybSA9IFRSVUUpLCBzdGRfZGV2ID0gc2QoQXJyRGVsYXlNaW51dGVzLCBuYS5ybSA9IFRSVUUpKSAlPiUNCiAgYXJyYW5nZShtZWFuKQ0Kc3VtbWFyeV9haXJsaW5lX2RlbGF5cw0KYGBgDQoNCkNyZWF0ZSBhIHNpbXBsZSBhdmVyYWdlIGFjcm9zcyBSZXBvcnRpbmdfQWlybGluZSBhbmQgRGF5T2ZXZWVrDQoNCmBgYHtyfQ0KYXZnX2RlbGF5cyA8LSBzdWJfYWlybGluZSAlPiUNCiAgZ3JvdXBfYnkoUmVwb3J0aW5nX0FpcmxpbmUsIERheU9mV2VlaykgJT4lDQogIHN1bW1hcmlzZShtZWFuX2RlbGF5cyA9IG1lYW4oQXJyRGVsYXlNaW51dGVzKSkNCg0KYXZnX2RlbGF5cyAlPiUgYXJyYW5nZShkZXNjKG1lYW5fZGVsYXlzKSkNCmBgYA0KDQpQbG90IHRhcmdldCB2YXJpYWJsZSBvdmVyIG11bHRpcGxlIHZhcmlhYmxlcw0KDQpgYGB7cn0NCmF2Z19kZWxheXMgJT4lDQogIGdncGxvdChhZXMoeCA9IFJlcG9ydGluZ19BaXJsaW5lLCB5ID0gRGF5T2ZXZWVrLCBmaWxsID0gbWVhbl9kZWxheXMpKSArDQogIGdlb21fdGlsZShjb2xvciA9ICJ3aGl0ZSIsIHNpemUgPSAwLjIpICsNCiAgc2NhbGVfZmlsbF9ncmFkaWVudChsb3cgPSAic2t5Ymx1ZSIsIGhpZ2ggPSAiYmx1ZSIpDQpgYGANCg0KIyMgQW5hbHl6aW5nIEluZGl2aWR1YWwgRmVhdHVyZSBQYXR0ZXJucyB1c2luZyBWaXN1YWxpemF0aW9uDQoNCkJveHBsb3RzIGFyZSBhIGdyZWF0IHdheSB0byB2aXN1YWxpemUgbnVtZXJpYyAob3IgcXVhbnRpdGF0aXZlKSBkYXRhLCBzaW5jZSB5b3UgY2FuIHZpc3VhbGl6ZSB0aGUgdmFyaW91cyBkaXN0cmlidXRpb25zIG9mIHRoZSBkYXRhLiBUaGV5IGFyZSBzaW1pbGFyIHRvIGhpc3RvZ3JhbXMgYnV0IGNhbiBzaG93IG1vcmUgaW5mb3JtYXRpb2luIHN1Y2ggYXM6DQoNCi0gICBUaGUgbWVkaWFuIG9mIHRoZSBkYXRhLCB3aGljaCByZXByZXNlbnRzIHRoZSBtaWRkbGUgZGF0YXBvaW50DQoNCi0gICBUaGUgVXBwZXIgUXVhcnRpbGUsIHdoaWNoIGlzIHRoZSA3NXRoIHBlcmNlbnRpbGUNCg0KLSAgIFRoZSBMb3dlciBRdWFydGlsZSwgd2hpY2ggaXMgdGhlIDI1dGggcGVyY2VudGlsZQ0KDQotICAgVGhlIEludGVycXVhcnRpbGUgUmFuZ2UsIHdoaWNoIGlzIHRoZSBkYXRhIGJldHdlZW4gdGhlIFVwcGVyIGFuZCBMb3dlciBRdWFydGlsZQ0KDQpCb3hwbG90cyBhbHNvIGRpc3BsYXkgb3V0bGllcnMgYXMgaW5kaXZpZHVhbCBkb3RzIHRoYXQgb2NjdXIgb3V0c2lkZSB0aGUgdXBwZXIgYW5kIGxvd2VyIGV4dHJlbWVzLiBXaXRoIGJveHBsb3RzLCB5b3UgY2FuIGVhc2lseSBzcG90IG91dGxpZXJzIGFuZCBhbHNvIHNlZSB0aGUgZGlzdHJpYnV0aW9uIGFuZCBza2V3bmVzcyBvZiB0aGUgZGF0YS4NCg0KV2Ugd2lsbCB1c2UgdGhlIGBnZ3Bsb3RgIGxpYnJhcnkgdG8gY3JlYXRlIHBsb3RzLCB3aGljaCBpcyBhbHJlYWR5IGxvYWRlZCB3aGVuIHRoZSBgdGlkeXZlcnNlYCBsaWJyYXJ5IGlzIGxvYWRlZC4NCg0KVG8gY3JlYXRlIGJveHBsb3RzLCB0aGUgbWFpbiBmdW5jdGlvbiB0byB1c2UgaXMgYGdlb21fYm94cGxvdCgpYC4gSW4gdGhlIGJlbG93IGNvZGUgd2UgYWxzbyB1c2UgYWRkaXRpb25hbCBmdW5jdGlvbnMgcHJpbWFybHkgdG8gY3VzdG9taXplIHRoZSBhZXN0aGV0aWNzIG9mIHRoZSBwbG90LiBZb3UgY2FuIGNoZWNrIG91dCB0aGUgRGF0YSBWaXN1YWxpemF0aW9uIENvdXJzZSBpbiBSIGlmIHlvdSB3YW50IHRvIGtub3cgbW9yZSBhYm91dCBjdXN0b21pemluZyBwbG90cyB3aXRoIGBnZ3Bsb3RgLg0KDQotICAgYGdlb21fYm94cGxvdCgpYDogVGhlIGJveHBsb3QgY29tcGFjdGx5IGRpc3BsYXlzIHRoZSBkaXN0cmlidXRpb24gb2YgYSBjb250aW51b3VzIHZhcmlhYmxlLiBJdCB2aXN1YWxpc2VzIGZpdmUgc3VtbWFyeSBzdGF0aXN0aWNzICh0aGUgbWVkaWFuLCB0d28gaGluZ2VzIGFuZCB0d28gd2hpc2tlcnMpLCBhbmQgYWxsICJvdXRseWluZyIgcG9pbnRzIGluZGl2aWR1YWxseS4NCg0KLSAgIGBnZW9tX2ppdHRlcigpYDogVGhlIGppdHRlciBnZW9tIGlzIGEgY29udmVuaWVudCBzaG9ydGN1dCBmb3IgZ2VvbV9wb2ludChwb3NpdGlvbiA9ICJqaXR0ZXIiKS4gSXQgYWRkcyBhIHNtYWxsIGFtb3VudCBvZiByYW5kb20gdmFyaWF0aW9uIHRvIHRoZSBsb2NhdGlvbiBvZiBlYWNoIHBvaW50LCBhbmQgaXMgYSB1c2VmdWwgd2F5IG9mIGhhbmRsaW5nIG92ZXJwbG90dGluZyBjYXVzZWQgYnkgZGlzY3JldGVuZXNzIGluIHNtYWxsZXIgZGF0YXNldHMuDQoNCi0gICBgZ2d0aXRsZSgpYDogTW9kaWZpZXMgcGxvdCB0aXRsZXMgKG1haW4gdGl0bGUsIGF4aXMgbGFiZWxzIGFuZCBsZWdlbmQgdGl0bGVzKS4NCg0KLSAgIGBndWlkZXMoKWA6IEd1aWRlcyBmb3IgZWFjaCBzY2FsZSBjYW4gYmUgc2V0IHNjYWxlLWJ5LXNjYWxlIHdpdGggdGhlIGd1aWRlIGFyZ3VtZW50Lg0KDQotICAgYHRoZW1lX21pbmltYWwoKWA6IEEgbWluaW1hbGlzdGljIHRoZW1lIHdpdGggbm8gYmFja2dyb3VuZCBhbm5vdGF0aW9ucy4NCg0KLSAgIGBjb29yZF9jYXJ0ZXNpYW4oKWA6IFRoZSBDYXJ0ZXNpYW4gY29vcmRpbmF0ZSBzeXN0ZW0gaXMgdGhlIG1vc3QgZmFtaWxpYXIsIGFuZCBjb21tb24sIHR5cGUgb2YgY29vcmRpbmF0ZSBzeXN0ZW0uIFNldHRpbmcgbGltaXRzIG9uIHRoZSBjb29yZGluYXRlIHN5c3RlbSB3aWxsIHpvb20gdGhlIHBsb3QgKGxpa2UgeW91J3JlIGxvb2tpbmcgYXQgaXQgd2l0aCBhIG1hZ25pZnlpbmcgZ2xhc3MpLCBhbmQgd2lsbCBub3QgY2hhbmdlIHRoZSB1bmRlcmx5aW5nIGRhdGEgbGlrZSBzZXR0aW5nIGxpbWl0cyBvbiBhIHNjYWxlIHdpbGwuDQoNCkJlbG93IHNob3dzIHRoZSBkaXN0cmlidXRpb24gb2YgYXJyaXZhbCBkZWxheXMgZm9yIGVhY2ggcmVwb3J0aW5nIGFpcmxpbmUuIFNvIGhlcmUsIEFyckRlbGF5IGlzIHRoZSBudW1lcmljYSBkYXRhLg0KDQpgYGB7cn0NCmdncGxvdChkYXRhID0gc3ViX2FpcmxpbmUsIG1hcHBpbmcgPSBhZXMoeCA9IFJlcG9ydGluZ19BaXJsaW5lLCB5ID0gQXJyRGVsYXkpKSArDQogIGdlb21fYm94cGxvdChmaWxsID0gImJpc3F1ZSIsIGNvbG9yID0gImJsYWNrIiwgYWxwaGEgPSAwLjMpICsNCiAgZ2VvbV9qaXR0ZXIoYWVzKGNvbG9yID0gImJsdWUiKSwgYWxwaGEgPSAwLjIpICsNCiAgbGFicyh4ID0gIkFpcmxpbmUiKSArDQogIGdndGl0bGUoIkFycml2YWwgRGVsYXlzIGJ5IEFpcmxpbmUiKSArDQogIGd1aWRlcyhjb2xvciA9ICJub25lIikgKw0KICB0aGVtZV9taW5pbWFsKCkgKw0KICBjb29yZF9jYXJ0ZXNpYW4oeWxpbSA9IHF1YW50aWxlKHN1Yl9haXJsaW5lJEFyckRlbGF5LCBjKDAsIDAuOTkpKSkNCmBgYA0KDQpPZnRlbiB0aW1lcyB3ZSBzZWUgY29udGludW91cyB2YXJpYWJsZXMgaW4gb3VyIGRhdGEuIFRoZXNlIGRhdGEgcG9pbnRzIGFyZSBudW1iZXJzIGNvbnRhaW5lZCBpbiBzb21lIHJhbmdlLg0KDQpGb3IgZXhhbXBsZSwgaW4gb3VyIGRhdGFzZXQsIGRlcGFydHVyZSBkZWxheXMgYW5kIGFycml2YWwgZGVsYXlzIGFyZSBjb250aW51b3VzIG51bWVyaWMgdmFyaWFibGVzLiBXaGF0IGlmIHdlIHdhbnQgdG8gdW5kZXJzdGFuZCB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gIkRlcERlbGF5IiBhbmQgIkFyckRlbGF5Ij8gQ291bGQgZGVwYXJ0dXJlIGRlbGF5IHBvc3NpYmx5IHByZWRpY3QgYXJyaXZhbCBkZWxheT8NCg0KT25lIGdvb2Qgd2F5IHRvIHZpc3VhbGl6ZSB0d28gY29udGludW91cyB2YXJpYWJsZXMgaXMgdG8gdXNlIGEgc2NhdHRlciBwbG90LiBFYWNoIG9ic2VydmF0aW9uIGluIGEgc2NhdHRlciBwbG90IGlzIHJlcHJlc2VudGVkIGFzIGEgcG9pbnQuDQoNClVzaW5nIGBnZ3Bsb3RgLCB5b3UgY2FuIGlucHV0IHRoZSB0d28gY29udGludW91cyB2YXJpYWJsZXMgdG8gY29tcGFyZSBhbmQgYWRkIGBnZW1vX3BvaW50KClgIHRvIHNob3cgdGhlIHBvaW50cyBpbiBhIHNjYXR0ZXIgcGxvdC4NCg0KYGBge3J9DQphbGFza2FfZmxpZ2h0cyA8LSBzdWJfYWlybGluZSAlPiUNCiAgZmlsdGVyKFJlcG9ydGluZ19BaXJsaW5lID09ICJBUyIpICU+JQ0KICBmaWx0ZXIoIWlzLm5hKERlcERlbGF5KSAmICFpcy5uYShBcnJEZWxheSkpICU+JQ0KICBmaWx0ZXIoRGVwRGVsYXkgPCA0MCkNCg0KZ2dwbG90KGRhdGEgPSBhbGFza2FfZmxpZ2h0cywgbWFwcGluZyA9IGFlcyh4ID0gRGVwRGVsYXksIHkgPSBBcnJEZWxheSkpICsNCiAgZ2VvbV9wb2ludCgpICsNCiAgZ2d0aXRsZSgiQWxhc2thIGZsaWdodCBEZXBhcnR1cmUgRGVsYXlzIHZzLiBBcnJpdmFsIERlbGF5cyIpDQpgYGANCg0KIyMjIyBIb3cgdG8gY2hvb3NlIHRoZSByaWdodCB2aXN1YWxpemF0aW9uIG1ldGhvZD8NCg0KV2hlbiB2aXN1YWxpemluZyBpbmRpdmlkdWFsIHZhcmlhYmxlcywgaXQgaXMgaW1wb3J0YW50IHRvIGZpcnN0IHVuZGVyc3RhbmQgd2hhdCB0eXBlIG9mIHZhcmlhYmxlIHlvdSBhcmUgZGVhbGluZyB3aXRoLiBUaGlzIHdpbGwgaGVscCB1cyBmaW5kIHRoZSByaWdodCB2aXN1YWxpemF0aW9uIG1ldGhvZCBmb3IgdGhhdCB2YXJpYWJsZS4gQmVsb3cgaXMgYSB3YXkgdG8gc2hvdyBlYWNoIHZhcmlhYmxlIGFuZCB0aGVpciB0eXBlIGluIGEgZGF0YWZyYW1lLg0KDQpgYGB7cn0NCnN0cihzdWJfYWlybGluZSkNCmBgYA0KDQpOb3csIHdlIHR1cm4gb3VyIGZvY3VzIHRvICJBcnJEZWxheU1pbnV0ZXMiIGFzIHdlIHdhbnQgdG8gY3JlYXRlIG1vZGVscyB0byBwcmVkaWN0IHRoaXMgdmFyaWFibGUNCg0KIyBRdWVzdGlvbiAjMToNCg0KKipXaGF0IGlzIHRoZSBkYXRhIHR5cGUgb2YgdGhlIGNvbHVtbiAiQXJyRGVsYXlNaW51dGVzIj8qKg0KDQpgYGB7cn0NCmNsYXNzKHN1Yl9haXJsaW5lJEFyckRlbGF5TWludXRlcykNCmBgYA0KDQpOZXh0LCBsZXQncyB0YWtlIGEgbG9vayBhdCBvdGhlciB2YXJpYWJsZXMsIGxpa2UgIkRlcERlbGF5TWludXRlcyIgdGhhdCBjb3VsZCBwb3RlbnRpYWxseSBoZWxwIHByZWRpY3QgIkFyckRlbGF5TWludXRlcyIuDQoNCiMgUXVlc3Rpb24gIzI6DQoNCkZpbmQgdGhlIGNvcnJlbGF0aW9uIGJldHdlZW4gdGhlIGZvbGxvd2luZyBjb2x1bW5zOiBEZXBEZWxheU1pbnV0ZXMgYW5kIEFyckRlbGF5TWludXRlcy4NCg0KSGludDogaWYgeW91IHdvdWxkIGxpa2UgdG8gc2VsZWN0IHRob3NlIGNvbHVtbnMsIHVzZSB0aGUgZG9sbGFyIHNpZ24gKFwkKQ0KDQpgYGB7cn0NCmNvcihzdWJfYWlybGluZSREZXBEZWxheU1pbnV0ZXMsIHN1Yl9haXJsaW5lJEFyckRlbGF5TWludXRlcykNCmBgYA0KDQojIyBDb250aW51b3VzIG51bWVyaWNhbCB2YXJpYWJsZXM6DQoNCkNvbnRpbnVvdXMgbnVtZXJpY2FsIHZhcmlhYmxlcyBhcmUgdmFyaWFibGVzIHRoYXQgbWF5IGNvbnRhaW4gYW55IHZhbHVlIHdpdGhpbiBzb21lIHJhbmdlLiBJbiBSLCBjb250aW51b3VzIG51bWVyaWNhbCB2YXJpYWJsZXMgY2FuIGhhdmUgdGhlIHR5cGUgImludGVnZXIiIG9yICJudW1lcmljIiAodGhlc2UgYXJlIHJlYWwgbnVtYmVycyBhbmQgc29tZXRpbWVzIGFyZSBjYWxsZWQgImZsb2F0IiBpbiBvdGhlciBwcm9ncmFtbWluZyBsYW5ndWFnZXMpLiBBIGdyZWF0IHdheSB0byB2aXN1YWxpemUgdGhlc2UgdmFyaWFibGVzIGlzIGJ5IHVzaW5nIHNjYXR0ZXJwbG90cyB3aXRoIGZpdHRlZCBsaW5lcy4NCg0KV2l0aCBgZ2dwbG90YCwgd2UgY2FuIHZpc3VhbGl6ZSB0aGlzIGJ5IHVzaW5nIGBnZW9tX3BvaW50KClgIHRvIHBsb3QgdGhlIGRhdGEgcG9pbnRzIGFuZCBgZ2VvbV9zbW9vdGgoKWAgdG8gcGxvdCBhIGZpdHRlZCBsaW5lYXIgcmVncmVzc2lvbiBsaW5lIChieSBkZWZhdWx0IHRoZSBtb2RlbCB1c2VzIGBmb3JtdWxhID0geSB+IHhgKS4NCg0KTGV0J3Mgc2VlIHNldmVyYWwgZXhhbXBsZXMgb2YgZGlmZmVyZW50IGxpbmVhciByZWxhdGlvbnNoaXBzOg0KDQojIyMgUG9zaXRpdmUgbGluZWFyIHJlbGF0aW9uc2hpcA0KDQpMZXQncyBmaW5kIHRoZSBzY2F0dGVycGxvdCBvZiAiRGVwRGVsYXlNaW51dGVzIiBhbmQgIkFyckRlbGF5TWludXRlcyIgb2YgYWxsIGFpcmxpbmVzLg0KDQojIyBBTk9WQSBBbmFseXNpcyBvZiBWYXJpYW5jZQ0KDQpFeGFtcGxlOiBBdmVyYWdlIGZsaWdodCBkZWxheXMgb2YgZGlmZmVyZW50IHJlcG9ydGluZyBhaXJsaW5lcw0KDQpGaW5kaW5nIGNvcnJlbGF0aW9uIGJldHdlZW4gZGlmZmVyZW50IGdyb3VwcyBvZiBjYXRlZ29yaWNhbCB2YXJpYWJsZQ0KDQoqKk51bGwgaHlwb3RoZXNpczoqKiBUaGUgbWVhbiBvZiB0aGUgcmVwb3J0aW5nIGFpcmxpbmUgaXMgdGhlIHNhbWUgZm9yIGFsbCBncm91cHMNCg0KYGBge3J9DQojIEFOT1ZBIGJldHdlZW4gIkFBIiBhbmQgIkFTIg0KYWFfYXNfc3Vic2V0IDwtIHN1Yl9haXJsaW5lICU+JQ0KICBzZWxlY3QoQXJyRGVsYXksIFJlcG9ydGluZ19BaXJsaW5lKSAlPiUNCiAgZmlsdGVyKFJlcG9ydGluZ19BaXJsaW5lID09ICJBQSIgfCBSZXBvcnRpbmdfQWlybGluZSA9PSAiQVMiKQ0KDQphZF9hb3YgPC0gYW92KEFyckRlbGF5IH4gUmVwb3J0aW5nX0FpcmxpbmUsIGRhdGEgPSBhYV9hc19zdWJzZXQpDQpzdW1tYXJ5KGFkX2FvdikNCmBgYA0KDQpgYGB7cn0NCmFhX3BhX3N1YnNldCA8LSBzdWJfYWlybGluZSAlPiUgDQogIHNlbGVjdChBcnJEZWxheSwgUmVwb3J0aW5nX0FpcmxpbmUpICU+JQ0KICBmaWx0ZXIoUmVwb3J0aW5nX0FpcmxpbmUgPT0gIkFBIiB8IFJlcG9ydGluZ19BaXJsaW5lID09ICJQQSAoMSkiKQ0KDQphZF9hb3YgPC0gYW92KEFyckRlbGF5IH4gUmVwb3J0aW5nX0FpcmxpbmUsIGRhdGEgPSBhYV9wYV9zdWJzZXQpDQpzdW1tYXJ5KGFkX2FvdikNCmBgYA0KDQojIyBDb250aW51b3VzIG51bWVyaWNhbCB2YXJpYWJsZXM6DQoNCkNvbnRpbnVvdXMgbnVtZXJpY2FsIHZhcmlhYmxlcyBhcmUgdmFyaWFibGVzIHRoYXQgbWF5IGNvbnRhaW4gYW55IHZhbHVlIHdpdGhpbiBzb21lIHJhbmdlLiBJbiBSLCBjb250aW51b3VzIG51bWVyaWNhbCB2YXJpYWJsZXMgY2FuIGhhdmUgdGhlIHR5cGUgImludGVnZXIiIG9yICJudW1lcmljIiAodGhlc2UgYXJlIHJlYWwgbnVtYmVycyBhbmQgc29tZXRpbWVzIGFyZSBjYWxsZWQgImZsb2F0IiBpbiBvdGhlciBwcm9ncmFtbWluZyBsYW5ndWFnZXMpLiBBIGdyZWF0IHdheSB0byB2aXN1YWxpemUgdGhlc2UgdmFyaWFibGVzIGlzIGJ5IHVzaW5nIHNjYXR0ZXJwbG90cyB3aXRoIGZpdHRlZCBsaW5lcy4NCg0KV2l0aCBgZ2dwbG90YCwgd2UgY2FuIHZpc3VhbGl6ZSB0aGlzIGJ5IHVzaW5nIGBnZW9tX3BvaW50KClgIHRvIHBsb3QgdGhlIGRhdGEgcG9pbnRzIGFuZCBgZ2VvbV9zbW9vdGgoKWAgdG8gcGxvdCBhIGZpdHRlZCBsaW5lYXIgcmVncmVzc2lvbiBsaW5lIChieSBkZWZhdWx0IHRoZSBtb2RlbCB1c2VzIGBmb3JtdWxhID0geSB+IHhgKS4NCg0KTGV0J3Mgc2VlIHNldmVyYWwgZXhhbXBsZXMgb2YgZGlmZmVyZW50IGxpbmVhciByZWxhdGlvbnNoaXBzOg0KDQojIyMgUG9zaXRpdmUgbGluZWFyIHJlbGF0aW9uc2hpcA0KDQpMZXQncyBmaW5kIHRoZSBzY2F0dGVycGxvdCBvZiAiRGVwRGVsYXlNaW51dGVzIiBhbmQgIkFyckRlbGF5TWludXRlcyIgb2YgYWxsIGFpcmxpbmVzLg0KDQpgYGB7cn0NCmdncGxvdChzdWJfYWlybGluZSwgYWVzKERlcERlbGF5TWludXRlcywgQXJyRGVsYXlNaW51dGVzKSkgKw0KICBnZW9tX3BvaW50KCkgKw0KICBnZW9tX3Ntb290aChtZXRob2QgPSAibG0iKQ0KYGBgDQoNCkZyb20gdGhlIHBsb3QsIGFzIHRoZSBkZXBhdHVyZSBkZWxheSAoIkRlcERlbGF5TWludXRlcyIpIGluY3JlYXNlcywgdGhlIGFycml2YWwgZGVsYXkgKCJBcnJEZWxheU1pbnV0ZXMiKSBpbmNyZWFzZXMuIFRoaXMgaW5kaWNhdGVzIGEgcG9zaXRpdmUgZGlyZWN0IGNvcnJlbGF0aW9uIGJldHdlZW4gdGhlc2UgdHdvIHZhcmlhYmxlcy4gIkRlcERlbGF5TWludXRlcyIgbWF5IGJlIGEgZGVjZW50IHByZWRpY3RvciBvZiAiQXJyRGVsYXlNaW51dGVzIiBzaW5jZSB0aGUgcmVncmVzc2lvbiBsaW5lIGlzIGluY3JlYXNpbmcgYW5kIGdlbmVyYWxseSBtYXRjaGVzIHRoZSBkYXRhIHBvaW50cy4NCg0KTmV4dCwgd2UgY2FuIGV4YW1pbmUgdGhlIGNvcnJlbGF0aW9uIGJldHdlZW4gIkRlcERlbGF5TWludXRlcyIgYW5kICJBcnJEZWxheU1pbnV0ZXMiIGFuZCBzZWUgaXQncyBhcHByb3hpbWF0ZWx5IDAuOTINCg0KYGBge3J9DQpzdWJfYWlybGluZSAlPiUNCiAgc2VsZWN0KERlcERlbGF5TWludXRlcywgQXJyRGVsYXlNaW51dGVzKSAlPiUNCiAgY29yKG1ldGhvZCA9ICJwZWFyc29uIikNCg0KY29yKHN1Yl9haXJsaW5lJERlcERlbGF5TWludXRlcywgc3ViX2FpcmxpbmUkQXJyRGVsYXlNaW51dGVzKQ0KYGBgDQoNCiMjIyBXZWFrIExpbmVhciBSZWxhdGlvbnNoaXANCg0KTGV0J3Mgbm93IGxvb2sgYXQgaWYgIldlYXRoZXJEZWxheSIgaXMgYSBnb29kIHByZWRpY3RvciB2YXJpYWJsZSBvZiAiQXJyRGVsYXlNaW51dGVzIi4NCg0KYGBge3J9DQpnZ3Bsb3Qoc3ViX2FpcmxpbmUsIGFlcyhXZWF0aGVyRGVsYXksIEFyckRlbGF5TWludXRlcykpICsNCiAgZ2VvbV9wb2ludChuYS5ybSA9IFRSVUUpICsNCiAgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxtIiwgbmEucm0gPSBUUlVFKQ0KYGBgDQoNCldlYXRoZXIgZGVsYXkgZG9lcyBub3Qgc2VlbSBsaWtlIGEgZ29vZCBwcmVkaWN0b3Igb2YgYXJyaXZhbCBkZWxheSBtaW51dGVzIHNpbmNlIHRoZSByZWdyZXNzaW9uIGxpbmUgaXMgY2xvc2UgdG8gaG9yaXpvbnRhbC4gQWxzbywgZm9yIHNtYWxsIHZhbHVlcyBvZiAiV2VhdGhlckRlbGF5IiwgdGhlIGRhdGEgcG9pbnRzIGFyZSB2ZXJ5IHNjYXR0ZXJlZCBhbmQgZmFyIGZyb20gdGhlIGZpdHRlZCBsaW5lLCBzaG93aW5nIGxvdHMgb2YgdmFyaWFiaWxpdHkuIFRoZXJlZm9yZSBpdCBpcyBub3QgYSByZWxpYWJsZSB2YXJpYWJsZS4NCg0KSW4gdGhlIGBjb3IoKWAgZnVuY3Rpb24sIHlvdSBjYW4gYWRkIGB1c2UgPSAiY29tcGxldGUub2JzImAgaW4gb3JkZXIgdG8gb25seSB1c2UgY29tcGxldGUgb2JzZXJ2YXRpb25zLCB0aGF0IGlzLCBleGNsdWRlIE5Bcy4gWW91IGNhbiBleGFtaW5lIHRoZSBjb3JyZWxhdGlvbiBiZXR3ZWVuICJXZWF0aGVyRGVsYXkiIGFuZCAiQXJyRGVsYXlNaW51dGVzIiBhbmQgc2VlIHRoYXQgaXQncyBhIHZlcnkgd2VhayByZWxhdGlvbnNoaXAgc2luY2UgdGhlIHZhbHVlIGlzIGNsb3NlIHRvIDAuDQoNCmBgYHtyfQ0Kc3ViX2FpcmxpbmUgJT4lDQogIHNlbGVjdChXZWF0aGVyRGVsYXksIEFyckRlbGF5TWludXRlcykgJT4lDQogIGNvcihtZXRob2QgPSAicGVhcnNvbiIsIHVzZSA9ICJjb21wbGV0ZS5vYnMiKQ0KDQpjb3Ioc3ViX2FpcmxpbmUkV2VhdGhlckRlbGF5LCBzdWJfYWlybGluZSRBcnJEZWxheU1pbnV0ZXMsIHVzZSA9ICJjb21wbGV0ZS5vYnMiKQ0KYGBgDQoNCiMgUXVlc3Rpb24gMyBhKToNCg0KRmluZCB0aGUgY29ycmVsYXRpb24gYmV0d2VlbiB4PSJDYXJyaWVyRGVsYXkiLCB5PSJBcnJEZWxheU1pbnV0ZXMiLg0KDQpIaW50OiBpZiB5b3Ugd291bGQgbGlrZSB0byBzZWxlY3QgdGhvc2UgY29sdW1ucywgdXNlIHRoZSBkb2xsYXIgc2lnbiAoXCQpDQoNCmBgYHtyfQ0KY29yKHN1Yl9haXJsaW5lJENhcnJpZXJEZWxheSwgc3ViX2FpcmxpbmUkQXJyRGVsYXlNaW51dGVzLCB1c2UgPSAiY29tcGxldGUub2JzIikNCmBgYA0KDQojIFF1ZXN0aW9uIDMgYik6DQoNCkdpdmVuIHRoZSBjb3JyZWxhdGlvbiByZXN1bHRzIGJldHdlZW4geD0iQ2FycmllckRlbGF5IiwgeT0iQXJyRGVsYXlNaW51dGVzIiwgZG8geW91IGV4cGVjdCBhIGxpbmVhciByZWxhdGlvbnNoaXA/DQoNClZlcmlmeSB5b3VyIHJlc3VsdHMgdXNpbmcgdGhlIGZ1bmN0aW9uIG9mIGdncGxvdC4NCg0KYGBge3J9DQpnZ3Bsb3QoZGF0YSA9IHN1Yl9haXJsaW5lLCBtYXBwaW5nID0gYWVzKHggPSBDYXJyaWVyRGVsYXksIHkgPSBBcnJEZWxheU1pbnV0ZXMpKSArDQogIGdlb21fcG9pbnQobmEucm0gPSBUUlVFKSArDQogIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsbSIsIG5hLnJtID0gVFJVRSkNCmBgYA0KDQpgYGB7cn0NCmdncGxvdChzdWJfYWlybGluZSwgYWVzKENhcnJpZXJEZWxheSwgTGF0ZUFpcmNyYWZ0RGVsYXkpKSArDQogIGdlb21fcG9pbnQobmEucm0gPSBUUlVFKSArDQogIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsbSIsIG5hLnJtID0gVFJVRSkNCg0Kc3ViX2FpcmxpbmUgJT4lDQogIHNlbGVjdChDYXJyaWVyRGVsYXksIExhdGVBaXJjcmFmdERlbGF5KSAlPiUNCiAgY29yKG1ldGhvZCA9ICJwZWFyc29uIiwgdXNlID0gImNvbXBsZXRlLm9icyIpDQpgYGANCg0KIyMgUGVhcnNvbiBjb3JyZWxhdGlvbiAtIEV4YW1wbGUNCg0KYGBge3J9DQpzdWJfYWlybGluZSAlPiUNCiAgc2VsZWN0KERlcERlbGF5LCBBcnJEZWxheSkgJT4lDQogIGNvcihtZXRob2QgPSAicGVhcnNvbiIpDQpgYGANCg0KYGBge3J9DQpzdWJfYWlybGluZSAlPiUNCiAgY29yLnRlc3QofkRlcERlbGF5ICsgQXJyRGVsYXksIGRhdGEgPSAuKQ0KYGBgDQoNClRoZXJlIGlzIGEgc3Ryb25nIGNvcnJlbGF0aW9uIHRoYXQgaXMgc3RhdGlzdGljYWxseSBzaWduaWZpY2FudA0KDQpgYGB7cn0NCmxpYnJhcnkoSG1pc2MpDQpudW1lcmljc19haXJsaW5lIDwtIHN1Yl9haXJsaW5lICU+JQ0KICBzZWxlY3QoQXJyRGVsYXlNaW51dGVzLCBEZXBEZWxheU1pbnV0ZXMsIENhcnJpZXJEZWxheSwgV2VhdGhlckRlbGF5LCBOQVNEZWxheSwgU2VjdXJpdHlEZWxheSwgTGF0ZUFpcmNyYWZ0RGVsYXkpDQoNCmFpcmxpbmVzX2NvciA8LSByY29ycihhcy5tYXRyaXgobnVtZXJpY3NfYWlybGluZSksIHR5cGUgPSAicGVhcnNvbiIpDQphaXJsaW5lc19jb3INCmBgYA0KDQojIyBEZXNjcmlwdGl2ZSBTdGF0aXN0aWNhbCBBbmFseXNpcw0KDQpXaGVuIHdlIGJlZ2FuIHRvIGFuYWx5emUgZGF0YSwgaXQncyBpbXBvcnRhbnQgdG8gZmlyc3QgZXhwbG9yZSB5b3VyIGRhdGEgYmVmb3JlIHlvdSBzcGVuZCB0aW1lIGJ1aWxkaW5nIGNvbXBsaWNhdGVkIG1vZGVscy4gT25lIGVhc3kgd2F5IHRvIGRvIHNvIGlzIHRvIGNhbGN1bGF0ZSBzb21lIGRlc2NyaXB0aXZlIHN0YXRpc3RpY3MgZm9yIHlvdXIgZGF0YS4NCg0KRGVzY3JpcHRpdmUgc3RhdGlzdGljYWwgYW5hbHlzaXMgaGVscHMgdG8gZGVzY3JpYmUgYmFzaWMgZmVhdHVyZXMgb2YgYSBkYXRhc2V0IGFuZCBnZW5lcmF0ZXMgYSBzaG9ydCBzdW1tYXJ5IGFib3V0IHRoZSBzYW1wbGUgYW5kIG1lYXN1cmVzIG9mIHRoZSBkYXRhLg0KDQpMZXQncyB0YWtlIGEgbG9vayBhdCBhIGNvdXBsZSBvZiBkaWZmZXJlbnQgdXNlZnVsIG1ldGhvZHMuIE9uZSB3YXkgaW4gd2hpY2ggd2UgY2FuIGRvIHRoaXMgaXMgYnkgdXNpbmcgdGhlIGBzdW1tYXJpemUoKWAgZnVuY3Rpb24gaW4gYHRpZHl2ZXJzZTpkcGx5cigpYC4gV2UgaW50cm9kdWNlZCB0aGlzIG1ldGhvZCBpbiBwcmV2aW91cyBtb2R1bGVzLiBNZXRob2QgYGdyb3VwX2J5KClgIGlzIG9mdGVuIHVzZWQgdG9nZXRoZXIgd2l0aCBgc3VtbWFyaXplKClgLCB3aGljaCBzdW1tYXJpemVzIGVhY2ggZ3JvdXAgaW50byBhIHNpbmdsZS1yb3cgc3VtbWFyeSBvZiB0aGF0IGdyb3VwLiBgZ3JvdXBfYnkoKWAgdGFrZXMgYXMgYXJndW1lbnRzIHRoZSBjb2x1bW4gbmFtZXMgdGhhdCBjb250YWluIHRoZSBjYXRlZ29yaWNhbCB2YXJpYWJsZXMgZm9yIHdoaWNoIHlvdSB3YW50IHRvIGNhbGN1bGF0ZSB0aGUgc3VtbWFyeSBzdGF0aXN0aWNzLg0KDQpUaGlzIHdpbGwgc2hvdzoNCg0KLSAgIHRoZSBjb3VudCBvZiB0aGF0IHZhcmlhYmxlDQoNCi0gICB0aGUgbWVhbg0KDQotICAgdGhlIHN0YW5kYXJkIGRldmlhdGlvbiAoc3RkKQ0KDQotICAgdGhlIG1pbmltdW0gdmFsdWUNCg0KLSAgIHRoZSBtZWRpYW4gKDUwdGggcGVyY2VudGlsZSBvciBxdWFydGlsZSAyKQ0KDQotICAgdGhlIElRUiAoSW50ZXJxdWFydGlsZSBSYW5nZTogcXVhcnRpbGUgMyBtaW51cyBxdWFydGlsZSAxKQ0KDQotICAgdGhlIG1heGltdW0gdmFsdWUNCg0KV2UgY2FuIGFwcGx5IHRoZSBtZXRob2QgInN1bW1hcml6ZSIgYW5kICJncm91cF9ieSIgYXMgZm9sbG93czoNCg0KYGBge3J9DQpzdW1tYXJ5X2FpcmxpbmVfZGVsYXlzIDwtIHN1Yl9haXJsaW5lICU+JQ0KICBncm91cF9ieShSZXBvcnRpbmdfQWlybGluZSkgJT4lDQogIHN1bW1hcmlzZShjb3VudCA9IG4oKSwgYXZnID0gbWVhbihBcnJEZWxheU1pbnV0ZXMsIG5hLnJtID0gVFJVRSksIHN0ZF9kZXYgPSBzZChBcnJEZWxheU1pbnV0ZXMsIG5hLnJtID0gVFJVRSksIG1pbiA9IG1pbihBcnJEZWxheU1pbnV0ZXMsIG5hLnJtID0gVFJVRSksIG1lZGlhbiA9IG1lZGlhbihBcnJEZWxheU1pbnV0ZXMsIG5hLnJtID0gVFJVRSksIGlxciA9IElRUihBcnJEZWxheU1pbnV0ZXMsIG5hLnJtID0gVFJVRSksIG1heCA9IG1heChBcnJEZWxheU1pbnV0ZXMsIG5hLnJtID0gVFJVRSkpICU+JQ0KICBhcnJhbmdlKGRlc2MoYXZnKSkNCnN1bW1hcnlfYWlybGluZV9kZWxheXMNCmBgYA0KDQpUbyBpZGVudGlmeSB0aGUgZGF0YSB0eXBlIG9mIGVhY2ggY29sdW1uIGluIGEgZGF0YWZyYW1lLCB3ZSBjYW4gdXNlIGBzYXBwbHkoKWAgd2l0aCBgdHlwZW9mYCwgd2hpY2ggZmluZHMgdGhlIHR5cGUgb2Ygc29tZXRoaW5nLiBTbyBoZXJlLCB5b3UgKmFwcGx5KiBgdHlwZW9mYCB0byBldmVyeSBjb2x1bW4gaW4gc3ViX2FpcmxpbmUuDQoNCmBgYHtyfQ0Kc2FwcGx5KHN1Yl9haXJsaW5lLCB0eXBlb2YpDQpgYGANCg0KIyMjIFZhbHVlIENvdW50cw0KDQpPbmUgd2F5IHlvdSBjYW4gc3VtbWFyaXplIHRoZSBjYXRlZ29yaWNhbCBkYXRhIGlzIGJ5IHVzaW5nIHRoZSBmdW5jdGlvbiBgY291bnQoKWAuIEFzIGFuIGV4YW1wbGUsIHlvdSBjYW4gZ2V0IHRoZSBjb3VudCBvZiBlYWNoIHJlcG9ydGluZyBhaXJsaW5lIGluIHRoaXMgZGF0YXNldC4gV2Ugc2VlIHRoYXQgd2UgaGF2ZSAxMDk2IGZsaWdodHMgZnJvbSBBQSAtIEFtZXJpY2FuIEFpcmxpbmVzLCA0NSBmbGlnaHRzIGZyb20gQVMgLSBBbGFza2EgQWlybGluZXMsIDI1OCBmcm9tIEI2IC0gamV0Qmx1ZSBhaXJsaW5lcywgZXRjLg0KDQpgYGB7cn0NCnN1Yl9haXJsaW5lICU+JQ0KICBjb3VudChSZXBvcnRpbmdfQWlybGluZSkgJT4lIA0KICBhcnJhbmdlKGRlc2MobikpDQpgYGANCg0KIyMgQmFzaWNzIG9mIEdyb3VwaW5nDQoNCldlIG9mdGVuIGFzayBxdWVzdGlvbnMgbGlrZTogKipJcyB0aGVyZSBhbnkgcmVsYXRpb25zaGlwIGJldHdlZW4gdGhlIHJlcG9ydGluZyBhaXJsaW5lIGFuZCB0aGUgZmxpZ2h0IGRlbGF5cz8qKiBJZiBzbywgd2hpY2ggZGF5IG9mIHRoZSB3ZWVrIGRvIGZsaWdodHMgaGF2ZSByZWxhdGl2ZWx5IGxvbmdlciBkZWxheSB0aW1lcz8gRm9yIGV4YW1wbGUsIHBlb3BsZSB0YWtlIGZsaWdodHMgbW9zdCBmcmVxdWVudGx5IG9uIE1vbmRheSBhbmQgRnJpZGF5IGZvciBidXNpbmVzcyB0cmlwcy4gV291bGQgdGhhdCBpbXBhY3QgaG93IGxvbmcgdGhlIGZsaWdodCBpcyBkZWxheWVkPyBJdCB3b3VsZCBiZSBuaWNlIGlmIHdlIGNvdWxkIGdyb3VwIHRoZSBkYXRhIGJ5IHRoZSBkaWZmZXJlbnQgcmVwb3J0aW5nIGFpcmxpbmUsIGFuZCBjb21wYXJlIHRoZSByZXN1bHRzIG9mIHRoZXNlIGRpZmZlcmVudCBkYXkgb2Ygd2VlayBhZ2FpbnN0IGVhY2ggb3RoZXIuDQoNCkluIHRpZHl2ZXJzZSB0aGlzIGNhbiBiZSBkb25lIHVzaW5nIHRoZSAiZ3JvdXBfYnkiIG1ldGhvZC4gVGhlIGdyb3VwIGJ5IG1ldGhvZCBpcyB1c2VkIG9uIGNhdGVnb3JpY2FsIHZhcmlhYmxlcywgaXQgZ3JvdXBzIHRoZSBkYXRhIGludG8gc3Vic2V0cyBhY2NvcmRpbmcgdG8gdGhlIGRpZmZlcmVudCBjYXRlZ29yaWVzIG9mIHRoYXQgdmFyaWFibGUuIFlvdSBjYW4gZ3JvdXAgYnkgYSBzaW5nbGUgdmFyaWFibGUgb3IgeW91IGNhbiBncm91cCBieSBtdWx0aXBsZSB2YXJpYWJsZXMgYnkgcGFzc2luZyBpbiBtdWx0aXBsZSB2YXJpYWJsZSBuYW1lcy4NCg0KTGV0J3Mgc2F5IHdlIGFyZSBpbnRlcmVzdGVkIGluIGZpbmRpbmcgdGhlIGF2ZXJhZ2UgZGVsYXkgbWludXRlcyBvZiBmbGlnaHRzIGFuZCBvYnNlcnZlIGhvdyB0aGV5IGRpZmZlciBiZXR3ZWVuIGRpZmZlcmVudCAiUmVwb3J0aW5nX0FpcmxpbmUiIGFuZCAiRGF5T2ZXZWVrcyIgdmFyaWFibGVzLg0KDQpUbyBkbyB0aGlzLCB5b3UgY2FuIHVzZSBgZ3JvdXBfYnkoKWAgdG8gZ3JvdXAgYnkgIlJlcG9ydGluZ19BaXJsaW5lIiBhbmQgIkRheU9mV2Vla3MiLCB0aGVuIHVzZSBgc3VtbWFyaXplKClgIHRvIGNhbGN1bGF0ZSB0aGUgbWVhbiBkZWxheSBtaW51dGVzLiBTZXR0aW5nIGAuZ3JvdXBzID0gJ2tlZXAnYCBrZWVwcyB0aGUgZ3JvdXBpbmcgc3RydWN0dXJlLCBpZiB5b3UgaW5zdGVhZCB3ZXJlIHRvIHNldCBpdCB0byBgImRyb3AiYCB0aGVuIHRoZSBvdXRwdXQgd291bGQgYmUgdW5ncm91cGVkIGFuZCBlc3NlbnRpYWxseSBiZSBhIHJlZ3VhbHIgdGliYmxlIChkYXRhZnJhbWUpLg0KDQpgYGB7cn0NCmF2Z19kZWxheXMgPC0gc3ViX2FpcmxpbmUgJT4lDQogIGdyb3VwX2J5KFJlcG9ydGluZ19BaXJsaW5lLCBEYXlPZldlZWspICU+JQ0KICBzdW1tYXJpc2UobWVhbl9kZWxheXMgPSBtZWFuKEFyckRlbGF5TWludXRlcyksIGdyb3VwcyA9ICJrZWVwIikNCg0KYXZnX2RlbGF5cw0KYGBgDQoNClRoZSBmdW5jdGlvbiBgYXJyYW5nZSgpYCB3aWxsIHJlb3JkZXIgdGhlIHJvd3MgaW4gYW4gKmFzY2VuZGluZyogb3JkZXIuIEhvd2V2ZXIsIHVzaW5nIGBhcnJhbmdlKClgIHdpdGggYGRlc2MoKWAgc29ydHMgZGF0YSBpbiAqZGVzY2VuZGluZyogb3JkZXIuDQoNClNvIG5vdywgd2Ugc29ydCB0aGUgbWVhbl9kZWxheXMgY29sdW1uIGJ5IGRlc2NlbmRpbmcgb3JkZXIgdXNpbmcgYGFycmFuZ2UoZGVzYygpKWAgYW5kIHByaW50IHRoZSBvdXRwdXQgdGFibGUgdG8gc2VlIHRoZSB0b3AgYWlybGluZXMgYW5kIGRheSBvZiB0aGUgd2VlayBwYWlycyB0aGF0IGhhdmUgdGhlIGhpZ2hlc3QgYXZlcmFnZSBhcnJpdmFsIGRlbGF5cy4NCg0KYGBge3J9DQpzb3J0ZWQgPC0gYXZnX2RlbGF5cyAlPiUNCiAgYXJyYW5nZShkZXNjKG1lYW5fZGVsYXlzKSkNCg0Kc29ydGVkDQpgYGANCg0KVG8gbWFrZSBpdCBlYXNpZXIgdG8gdW5kZXJzdGFuZCwgd2UgY2FuIHRyYW5zZm9ybSB0aGlzIHRhYmxlIHRvIGEgKipoZWF0bWFwKiouDQoNCkEgaGVhdG1hcCBoYXMgb25lIHZhcmlhYmxlIGRpc3BsYXllZCBhbG9uZyB0aGUgeC1heGlzIGFuZCB0aGUgb3RoZXIgdmFyaWFibGUgZGlzcGxheWVkIGFsb25nIHRoZSB5LWF4aXMuIEEgaGVhdCBtYXAgKG9yIGhlYXRtYXApIGlzIGEgZGF0YSB2aXN1YWxpemF0aW9uIHRlY2huaXF1ZSB0aGF0IHNob3dzIG1hZ25pdHVkZSBvZiBhIHBoZW5vbWVub24gYXMgY29sb3IgaW4gdHdvIGRpbWVuc2lvbnMuDQoNClRoZSB2YXJpYXRpb24gaW4gY29sb3IgbWF5IGJlIGJ5IGh1ZSBvciBpbnRlbnNpdHksIGdpdmluZyBvYnZpb3VzIHZpc3VhbCBjdWVzIHRvIHRoZSByZWFkZXIgYWJvdXQgaG93IHRoZSBwaGVub21lbm9uIGlzIGNsdXN0ZXJlZCBvciB2YXJpZWQgb3ZlciBzcGFjZS4gSXQgaXMgYSBncmVhdCB3YXkgdG8gcGxvdCB0aGUgdGFyZ2V0IHZhcmlhYmxlIG92ZXIgbXVsdGlwbGUgdmFyaWFibGVzIGFuZCB0aHJvdWdoIHRoaXMgZ2V0IHZpc3VhbCBjbHVlcyBvZiB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gdGhlc2UgdmFyaWFibGVzIGFuZCB0aGUgdGFyZ2V0Lg0KDQpXaXRoIGBnZ3Bsb3RgLCB5b3UgY2FuIHVzZSBgZ2VvbV90aWxlKClgIHRvIGNyZWF0ZSBoZWF0bWFwcyBhbmQgdXNlIGBzY2FsZV9maWxsX2dyYWRpZW50KClgIHRvIGNoYW5nZSB0aGUgY29sb3Jpbmcgb2YgdGhlIGhlYXRtYXAuIFNvIHRvIGludGVycHJldCB0aGUgaGVhdG1hcCBiZWxvdywgdGhlIGNsb3NlciBhIHRpbGUgaXMgdG8gdGhlIGNvbG9yIHJlZCwgdGhlIGhpZ2hlciB0aGUgbWVhbiBhcnJpdmFsIGRlbGF5IGZvciB0aGF0IHBhcnRpY3VhbHIgZGF5IG9mIHRoZSB3ZWVrIGFuZCBhaXJsaW5lIHBhaXIuDQoNCmBgYHtyfQ0KYXZnX2RlbGF5cyAlPiUNCiAgZ2dwbG90KGFlcyh4ID0gUmVwb3J0aW5nX0FpcmxpbmUsIHkgPSBEYXlPZldlZWssIGZpbGwgPSBtZWFuX2RlbGF5cykpICsNCiAgIyBTZXQgdGhlIHRpbGUncyBib3JkZXJzIHRvIGJlIHdoaXRlIHdpdGggc2l6ZSAwLjINCiAgZ2VvbV90aWxlKGNvbG9yID0gIndoaXRlIiwgc2l6ZSA9IDAuMikgKw0KICAjIERlZmluZSBncmFkaWVudCBjb2xvciBzY2FsZXMNCiAgc2NhbGVfZmlsbF9ncmFkaWVudChsb3cgPSAieWVsbG93IiwgaGlnaCA9ICJyZWQiKQ0KYGBgDQoNCioqQk9OVVMgSGVhdG1hcCoqDQoNCkZvciBzb21ldGhpbmcgbW9yZSBzb3BoaXN0aWNhdGVkLCB5b3UgY2FuIGFsd2F5cyBhZGQgbW9yZSBibG9ja3MgaW4geW91ciBjb2RlLg0KDQpgYGB7cn0NCiMgVGhpcyB2aXN1YWxpemF0aW9ucyB3aWxsIHVzZSBsdWJyaWRhdGUgcGFja2FnZQ0KbGlicmFyeShsdWJyaWRhdGUpDQojIExldCdzIHRha2UgYSBzaW1wbGUgYXZlcmFnZSBhY2Nyb3NzIFJlcG9ydGluZ19BaXJsaW5lIGFtbnMgRGF5T2ZXZWVrDQphdmdfZGVsYXlzIDwtIHN1Yl9haXJsaW5lICU+JQ0KICBncm91cF9ieShSZXBvcnRpbmdfQWlybGluZSwgRGF5T2ZXZWVrKSAlPiUNCiAgc3VtbWFyaXNlKG1lYW5fZGVsYXlzID0gbWVhbihBcnJEZWxheU1pbnV0ZXMpLCAuZ3JvdXBzID0gImtlZXAiKSAlPiUNCiAgIyBjcmVhdGUgYSBuZXcgdmFyaWFibGUgZnJvbSBtZWFuX2RlbGF5cw0KICAjIG1ha2UgdGhlIGZpcnN0IHJhbmdlIC0wLjEgdG8gMC4xIHRvIGluY2x1ZGUgemVybyB2YWx1ZXMNCiAgbXV0YXRlKGJpbnMgPSBjdXQobWVhbl9kZWxheXMsIGJyZWFrcyA9IGMoLTAuMSwgMC4xLCAxMCwgMjAsIDMwLCA1MCkpKSAlPiUNCiAgbXV0YXRlKGJpbnMgPSBmYWN0b3IoYXMuY2hhcmFjdGVyKGJpbnMpLCBsZXZlbHMgPSByZXYobGV2ZWxzKGJpbnMpKSkpDQoNCmdncGxvdChhdmdfZGVsYXlzLCBhZXMoeCA9IFJlcG9ydGluZ19BaXJsaW5lLCB5ID0gbHVicmlkYXRlOjp3ZGF5KERheU9mV2VlaywgbGFiZWwgPSBUUlVFKSwgZmlsbCA9IGJpbnMpKSArDQogIGdlb21fdGlsZShjb2xvdXIgPSAid2hpdGUiLCBzaXplID0gMC4yKSArDQogIGdlb21fdGV4dChhZXMobGFiZWwgPSByb3VuZChtZWFuX2RlbGF5cywgMikpKSArDQogIGd1aWRlcyhmaWxsID0gZ3VpZGVfbGVnZW5kKHRpdGVsID0gIkRlbGF5cyBUaW1lIFNjYWxlIikpICsNCiAgbGFicyh4ID0gIlJlcG9ydGluZyBBaXJsaW5lIiwgeSA9ICJEYXlzIG9mIFdlZWsiLCB0aXRsZSA9ICJBdmVyYWdlIEFycml2YWwgRGVsYXlzIikgKw0KICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjKCIjZDUzZTRmIiwgIiNmNDZkNDMiLCAiI2ZkYWU2MSIsICIjZmVlMDhiIiwgIiNlNmY1OTgiLCAiI2FiZGRhNCIpKQ0KYGBgDQoNCiMgUXVlc3Rpb24gNDoNCg0KVXNlIHRoZSAiZ3JvdXBieSIgYW5kICJzdW1tYXJpemUiIGZ1bmN0aW9uIHRvIGZpbmQgdGhlIGF2ZXJhZ2UgIkFyckRlbGF5TWludXRlcyIgb2YgZWFjaCBmbGlnaHQgYmFzZWQgb24gIlJlcG9ydGluZ19BaXJsaW5lIiA/DQoNCmBgYHtyfQ0Kc3ViX2FpcmxpbmUgJT4lDQogIGdyb3VwX2J5KFJlcG9ydGluZ19BaXJsaW5lKSAlPiUNCiAgc3VtbWFyaXNlKG1lYW5fZGVsYXlzID0gbWVhbihBcnJEZWxheU1pbnV0ZXMpKSAlPiUgDQogIGFycmFuZ2UobWVhbl9kZWxheXMpDQpgYGANCg0KIyMgQ29ycmVsYXRpb24gYW5kIENhdXNhdGlvbg0KDQpUaGUgbWFpbiBxdWVzdGlvbiB3ZSBhcmUgdHJ5aW5nIHRvIGFuc3dlciBpbiB0aGlzIG1vZHVsZSBpczogIldoYXQgY2F1c2VzIGZsaWdodCBkZWxheXM/Ii4gVG8gZ2V0IGEgYmV0dGVyIG1lYXN1cmUgb2YgdGhlIGltcG9ydGFudCBjaGFyYWN0ZXJpc3RpY3MsIHdlIGxvb2sgYXQgdGhlIGNvcnJlbGF0aW9uIG9mIHRoZXNlIHZhcmlhYmxlcyB3aXRoIHRoZSBhcnJpdmFsIGRlbGF5LCBBS0EsICJBcnJEZWxheU1pbnV0ZXMiLCBpbiBvdGhlciB3b3JkczogaG93IGlzIHRoZSBhcnJpdmFsIGRlbGF5IG1pbnV0ZXMgZGVwZW5kZW50IG9uIHRoaXMgdmFyaWFibGU/DQoNCkZpcnN0LCBsZXQncyBiZWdpbiB3aXRoIHNvbWUgaW1wb3J0YW50IGRlZmluaXRpb25zOg0KDQotICAgQ29ycmVsYXRpb246IGEgbWVhc3VyZSBvZiB0aGUgZXh0ZW50IG9mIGludGVyZGVwZW5kZW5jZSBiZXR3ZWVuIHZhcmlhYmxlcy4NCg0KLSAgIENhdXNhdGlvbjogdGhlIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIGNhdXNlIGFuZCBlZmZlY3QgYmV0d2VlbiB0d28gdmFyaWFibGVzLg0KDQpJdCBpcyBpbXBvcnRhbnQgdG8ga25vdyB0aGUgZGlmZmVyZW5jZSBiZXR3ZWVuIHRoZXNlIHR3byBhbmQgdGhhdCBjb3JyZWxhdGlvbiBkb2VzIG5vdCBpbXBseSBjYXVzYXRpb24uIERldGVybWluaW5nIGNvcnJlbGF0aW9uIGlzIG11Y2ggc2ltcGxlciB0aGFuIGRldGVybWluaW5nIGNhdXNhdGlvbiBhcyBjYXVzYXRpb24gbWF5IHJlcXVpcmUgaW5kZXBlbmRlbnQgZXhwZXJpbWVudGF0aW9uLg0KDQojIyMgUGVhcnNvbiBDb3JyZWxhdGlvbg0KDQpUaGUgUGVhcnNvbiBDb3JyZWxhdGlvbiBtZWFzdXJlcyB0aGUgbGluZWFyIGRlcGVuZGVuY2UgYmV0d2VlbiB0d28gdmFyaWFibGVzIFggYW5kIFkuDQoNClRoZSByZXN1bHRpbmcgY29lZmZpY2llbnQgaXMgYSB2YWx1ZSBiZXR3ZWVuIC0xIGFuZCAxIGluY2x1c2l2ZSwgd2hlcmU6DQoNCi0gICAxOiBUb3RhbCBwb3NpdGl2ZSBsaW5lYXIgY29ycmVsYXRpb24uDQoNCi0gICAwOiBObyBsaW5lYXIgY29ycmVsYXRpb24sIHRoZSB0d28gdmFyaWFibGVzIG1vc3QgbGlrZWx5IGRvIG5vdCBhZmZlY3QgZWFjaCBvdGhlci4NCg0KLSAgIC0xOiBUb3RhbCBuZWdhdGl2ZSBsaW5lYXIgY29ycmVsYXRpb24NCg0KUGVhcnNvbiBDb3JyZWxhdGlvbiBpcyB0aGUgZGVmYXVsdCBtZXRob2Qgb2YgdGhlIGZ1bmN0aW9uIGBjb3IoKWAsIGJ1dCBvdGhlciBtZXRob2RzIGNhbiBiZSBzcGVjaWZpZWQgYnkgc2V0dGluZyBgbWV0aG9kYC4gTGlrZSBiZWZvcmUgd2UgY2FuIGNhbGN1bGF0ZSB0aGUgUGVhcnNvbiBDb3JyZWxhdGlvbiBvZiB0aGUgImludGVnZXIiIG9yICJudW1lcmljIiB2YXJpYWJsZXMuDQoNCmBgYHtyfQ0Kc3ViX2FpcmxpbmUgJT4lDQogIHNlbGVjdChEZXBEZWxheU1pbnV0ZXMsIEFyckRlbGF5TWludXRlcykgJT4lDQogIGNvcihtZXRob2QgPSAicGVhcnNvbiIpDQpgYGANCg0KU29tZXRpbWVzLCB3ZSB3b3VsZCBsaWtlIHRvIGtub3cgdGhlIHNpZ25pZmljYW50IG9mIHRoZSBjb3JyZWxhdGlvbiBlc3RpbWF0ZS4NCg0KKipQLXZhbHVlKio6DQoNCldoYXQgaXMgdGhpcyBQLXZhbHVlPyBUaGUgUC12YWx1ZSBpcyB0aGUgcHJvYmFiaWxpdHkgdmFsdWUgdGhhdCB0aGUgY29ycmVsYXRpb24gYmV0d2VlbiB0aGVzZSB0d28gdmFyaWFibGVzIGlzIHN0YXRpc3RpY2FsbHkgc2lnbmlmaWNhbnQuIE5vcm1hbGx5LCB3ZSBjaG9vc2UgYSBzaWduaWZpY2FuY2UgbGV2ZWwgb2YgMC4wNSwgd2hpY2ggbWVhbnMgdGhhdCB3ZSBhcmUgOTUlIGNvbmZpZGVudCB0aGF0IHRoZSBjb3JyZWxhdGlvbiBiZXR3ZWVuIHRoZSB2YXJpYWJsZXMgaXMgc2lnbmlmaWNhbnQuDQoNCkJ5IGNvbnZlbnRpb24sIHdoZW4gdGhlDQoNCi0gICBwLXZhbHVlIGlzIFw8IDAuMDAxOiB3ZSBzYXkgdGhlcmUgaXMgc3Ryb25nIGV2aWRlbmNlIHRoYXQgdGhlIGNvcnJlbGF0aW9uIGlzIHNpZ25pZmljYW50Lg0KDQotICAgdGhlIHAtdmFsdWUgaXMgXDwgMC4wNTogdGhlcmUgaXMgbW9kZXJhdGUgZXZpZGVuY2UgdGhhdCB0aGUgY29ycmVsYXRpb24gaXMgc2lnbmlmaWNhbnQuDQoNCi0gICB0aGUgcC12YWx1ZSBpcyBcPCAwLjE6IHRoZXJlIGlzIHdlYWsgZXZpZGVuY2UgdGhhdCB0aGUgY29ycmVsYXRpb24gaXMgc2lnbmlmaWNhbnQuDQoNCi0gICB0aGUgcC12YWx1ZSBpcyBcPiAwLjE6IHRoZXJlIGlzIG5vIGV2aWRlbmNlIHRoYXQgdGhlIGNvcnJlbGF0aW9uIGlzIHNpZ25pZmljYW50Lg0KDQpJbiBSLCB0byBjb25kdWN0IGEgc2lnbmlmaWNhbmNlIHRlc3QgYW5kIGdldCB0aGUgcC12YWx1ZXMsIHlvdSBjYW4gdXNlIGBjb3IudGVzdCgpYC4NCg0KYGBge3J9DQpzdWJfYWlybGluZSAlPiUNCiAgY29yLnRlc3QofkRlcERlbGF5TWludXRlcyArIEFyckRlbGF5TWludXRlcywgZGF0YSA9IC4pDQpgYGANCg0KKipDb25jbHVzaW9uKio6DQoNClNlZSB0aGF0IHRoZSBQLXZhbHVlIGlzIHZlcnkgc21hbGwsIG11Y2ggc21hbGxlciB0aGFuIC4wMDEuIEFuZCBzbyB3ZSBjYW4gY29uY2x1ZGUgdGhhdCB3ZSBhcmUgY2VydGFpbiBhYm91dCB0aGUgc3Ryb25nIHBvc2l0aXZlIGNvcnJlbGF0aW9uLg0KDQojIyMgQ29ycmVsYXRpb25zIGJldHdlZW4gbXVsdGlwbGUgdmFyaWFibGVzDQoNClRvIGNhbGN1bGF0ZSBjb3JyZWxhdGlvbnMgb24gbXVsdGlwbGUgdmFyaWFibGVzLCB5b3UgY2FuIGBzZWxlY3RgIGFsbCB0aGUgdmFyaWFibGUgdGhlbiB1c2UgdGhlIGBjb3IoKWAgZnVuY3Rpb24gYnV0IHNldCBgdXNlID0gInBhaXJ3aXNlLmNvbXBsZXRlLm9icyJgIHNvIHRoYXQgdGhlIGNvcnJlbGF0aW9uIG9mIGV2ZXJ5IHBhaXIgb2YgdmFyaWFibGVzIGlzIGNhbGN1bGF0ZWQuIFRoaXMgY29tcHV0ZXMgYSBtYXRyaXggb2YgUGVhcnNvbidzIHIgY29ycmVsYXRpb24gY29lZmZpY2llbnRzIGZvciBhbGwgcG9zc2libGUgcGFpcnMgb2YgY29sdW1ucy4NCg0KYGBge3J9DQpjb3JyZWxhdGlvbiA8LSBzdWJfYWlybGluZSAlPiUNCiAgc2VsZWN0KEFyckRlbGF5TWludXRlcywgRGVwRGVsYXlNaW51dGVzLCBDYXJyaWVyRGVsYXksIFdlYXRoZXJEZWxheSwgTkFTRGVsYXksIFNlY3VyaXR5RGVsYXksIExhdGVBaXJjcmFmdERlbGF5KSAlPiUNCiAgY29yKHVzZSA9ICJwYWlyd2lzZS5jb21wbGV0ZS5vYnMiLCBtZXRob2QgPSAicGVhcnNvbiIpDQoNCmNvcnJlbGF0aW9uDQpgYGANCg0KVGFraW5nIGFsbCB2YXJpYWJsZXMgaW50byBhY2NvdW50LCB3ZSBjYW4gbm93IGNyZWF0ZSBhIGhlYXRtYXAgdGhhdCB2aXN1YWxpemVzIHRoZSBjb3JyZWxhdGlvbiBiZXR3ZWVuIGVhY2ggb2YgdGhlIHZhcmlhYmxlcyB3aXRoIG9uZSBhbm90aGVyLg0KDQpMZXQncyB1c2UgdGhlIGBjb3JycGxvdCgpYCBmdW5jdGlvbiB0byBwbG90IGFuIGVsZWdhbnQgZ3JhcGggb2YgYSBjb3JyZWxhdGlvbiBtYXRyaXgsIHlvdSBtYXkgbmVlZCB0byBpbnN0YWxsIHRoZSBsaWJyYXJ5IGBjb3JycGxvdGAgZmlyc3QgYmVmb3JlIGxvYWRpbmcgaXQuIFRoZSBjb2xvciBzY2hlbWUgaW5kaWNhdGVzIHRoZSBQZWFyc29uIGNvcnJlbGF0aW9uIGNvZWZmaWNpZW50LCBpbmRpY2F0aW5nIHRoZSBzdHJlbmd0aCBvZiB0aGUgY29ycmVsYXRpb24gYmV0d2VlbiB0d28gdmFyaWFibGVzLiBXZSBjYW4gc2VlIGEgZGlhZ29uYWwgbGluZSB3aXRoIGEgZGFyayBibHVlIGNvbG9yLCBpbmRpY2F0aW5nIHRoYXQgYWxsIHRoZSB2YWx1ZXMgb24gdGhpcyBkaWFnb25hbCBhcmUgaGlnaGx5IGNvcnJlbGF0ZWQuIFRoaXMgbWFrZXMgc2Vuc2UgYmVjYXVzZSB3aGVuIHlvdSBsb29rIGNsb3NlciwgdGhlIHZhbHVlcyBvbiB0aGUgZGlhZ29uYWwgYXJlIHRoZSBjb3JyZWxhdGlvbiBvZiBhbGwgdmFyaWFibGVzIHdpdGggdGhlbXNlbHZlcywgd2hpY2ggd2lsbCBhbHdheXMgYmUgMS4NCg0KVGhpcyBjb3JyZWxhdGlvbiBoZWF0bWFwIGdpdmVzIHVzIGEgZ29vZCBvdmVydmlldyBvZiBob3cgdGhlIGRpZmZlcmVudCB2YXJpYWJsZXMgYXJlIHJlbGF0ZWQgdG8gb25lIGFub3RoZXIgYW5kLCBtb3N0IGltcG9ydGFudGx5LCBob3cgdGhlc2UgdmFyaWFibGVzIGFyZSByZWxhdGVkIHRvIGFycml2YWwgZGVsYXlzLg0KDQpgYGB7cn0NCmxpYnJhcnkoY29ycnBsb3QpDQoNCm51bWVyaWNzX2FpcmxpbmUgPC0gc3ViX2FpcmxpbmUgJT4lDQogIHNlbGVjdChBcnJEZWxheU1pbnV0ZXMsIERlcERlbGF5TWludXRlcywgQ2FycmllckRlbGF5LCBXZWF0aGVyRGVsYXksIE5BU0RlbGF5LCBTZWN1cml0eURlbGF5LCBMYXRlQWlyY3JhZnREZWxheSkNCg0KYWlybGluZXNfY29yIDwtIGNvcihudW1lcmljc19haXJsaW5lLCBtZXRob2QgPSAicGVhcnNvbiIsIHVzZSA9ICJwYWlyd2lzZS5jb21wbGV0ZS5vYnMiKQ0KY29sIDwtIGNvbG9yUmFtcFBhbGV0dGUoYygiI0JCNDQ0NCIsICIjRUU5OTg4IiwgIiNGRkZGRkYiLCAiIzc3QUFERCIsICIjNDQ3N0FBIikpDQoNCmNvcnJwbG90KGFpcmxpbmVzX2NvciwgbWV0aG9kID0gImNvbG9yIiwgY29sID0gY29sKDIwMCksIHR5cGUgPSAidXBwZXIiLCBvcmRlciA9ICJoY2x1c3QiLCBhZGRDb2VmLmNvbCA9ICJibGFjayIsIHRsLmNvbCA9ICJibGFjayIsIHRsLnNydCA9IDQ1KQ0KYGBgDQoNCioqRnJvbSB0aGUgYWJvdmUgY29ycmVsYXRpb24gcGxvdCwgeW91IGNhbiBzZWUgdGhhdCBvZiB0aGUgZmVhdHVyZXMgd2UgdXNlZCwgIkNhcnJpZXJEZWxheSIsICJEZXBEZWxheU1pbnV0ZXMiLCBhbmQgIkxhdGVBaXJjcmFmdERlbGF5IiBoYXZlIHRoZSBoaWdoZXN0IGNvcnJlbGF0aW9ucyB3aXRoICJBcnJEZWxheU1pbnV0ZXMiLiBUaGUgY29ycmVsYXRpb24gYmV0d2VlbiAiQ2FycmllckRlbGF5IiBhbmQgIkFyckRlbGF5TWludXRlcyIgaXMgMC43MywgIkRlcERlbGF5TWludXRlcyIgYW5kICJBcnJEZWxheU1pbnV0ZXMiIGlzIDAuOTIsIGFuZCBzbyBvbi4qKg0KDQojIyBBTk9WQSAoQW5hbHlzaXMgb2YgVmFyaWFuY2UpDQoNCkxldCdzIHNheSB0aGF0IHdlIHdhbnQgdG8gYW5hbHl6ZSBhIGNhdGVnb3JpY2FsIHZhcmlhYmxlIGFuZCBzZWUgdGhlIGNvcnJlbGF0aW9uIGFtb25nIGRpZmZlcmVudCBjYXRlZ29yaWVzLiBGb3IgZXhhbXBsZSwgY29uc2lkZXIgdGhlIGFpcmxpbmUgZGF0YXNldCwgdGhlIHF1ZXN0aW9uIHdlIG1heSBhc2sgaXMsIGhvdyBkbyBkaWZmZXJlbnQgY2F0ZWdvcmllcyBvZiB0aGUgcmVwb3J0aW5nIGFpcmxpbmUgZmVhdHVyZSAoYXMgYSBjYXRlZ29yaWNhbCB2YXJpYWJsZSkgaW1wYWN0IGZsaWdodCBkZWxheXM/DQoNClRoZSBiYXIgZ3JhcGggYmVsb3cgc2hvd3MgdGhlIGF2ZXJhZ2UgZmxpZ2h0IGRlbGF5cyBvZiBkaWZmZXJlbnQgYWlybGluZXMuIFRoZSBjb2RlIGZpcnN0IGdyb3VwcyB0aGUgZGF0YSBieSBhaXJsaW5lLCB0aGVuIGZpbmRzIHRoZSBhdmVyYWdlIGFycml2YWwgZGVsYXkgZm9yIGVhY2ggYWlybGluZSwgdGhlbiBpdCBwbG90cyB0aGlzIGFzIGEgYmFyIGNoYXJ0Lg0KDQpJbiBgZ2VvbV9iYXIoKWAsIHlvdSBzaG91bGQgc2V0IGBzdGF0ID0gImlkZW50aXR5ImAgc2luY2UgeW91IGFyZSBwYXNzaW5nIGluIHRoZSB5IHZhbHVlcyAoIkF2ZXJhZ2VfRGVsYXlzIikuIElmIHlvdSBsZWZ0IG91dCB0aGlzIHBhcmFtZXRlciB0aGVuIGJ5IGRlZmF1bHQgYHN0YXQgPSAiY291bnQiYCwgd2hpY2ggd291bGQgaW5zdGVhZCBjb3VudCB0aGUgZnJlcXVlbmN5IG9mIGVhY2ggYWlybGluZS4NCg0KYGBge3J9DQpzdW1tYXJ5X2FpcmxpbmVfZGVsYXlzIDwtIHN1Yl9haXJsaW5lICU+JQ0KICBncm91cF9ieShSZXBvcnRpbmdfQWlybGluZSkgJT4lDQogIHN1bW1hcmlzZShBdmVyYWdlX0RlbGF5cyA9IG1lYW4oQXJyRGVsYXlNaW51dGVzLCBuYS5ybSA9IFRSVUUpKQ0KDQpzdW1tYXJ5X2FpcmxpbmVfZGVsYXlzICU+JQ0KICBnZ3Bsb3QoYWVzKHggPSBSZXBvcnRpbmdfQWlybGluZSwgeSA9IEF2ZXJhZ2VfRGVsYXlzKSkgKw0KICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IikgKw0KICBnZ3RpdGxlKCJBdmVyYWdlIEFycml2YWwgRGVsYXlzIGJ5IEFpcmxpbmUiKQ0KYGBgDQoNClRvIGFuYWx5emUgY2F0ZWdvcmljYWwgdmFyaWFibGVzIHN1Y2ggYXMgdGhlICJSZXBvcnRpbmdfQWlybGluZSIgdmFyaWFibGUsIHdlIGNhbiB1c2UgYSBtZXRob2Qgc3VjaCBhcyB0aGUgQU5PVkEgbWV0aG9kLg0KDQojIyMgQU5PVkE6IEFuYWx5c2lzIG9mIFZhcmlhbmNlDQoNClRoZSBBbmFseXNpcyBvZiBWYXJpYW5jZSAoQU5PVkEpIGlzIGEgc3RhdGlzdGljYWwgbWV0aG9kIHVzZWQgdG8gdGVzdCB3aGV0aGVyIHRoZXJlIGFyZSBzaWduaWZpY2FudCBkaWZmZXJlbmNlcyBiZXR3ZWVuIHRoZSBtZWFucyBvZiB0d28gb3IgbW9yZSBncm91cHMuIEFOT1ZBIHJldHVybnMgdHdvIHBhcmFtZXRlcnM6DQoNCi0gICAqKkYtdGVzdCBzY29yZSoqOiBBTk9WQSBhc3N1bWVzIHRoZSBtZWFucyBvZiBhbGwgZ3JvdXBzIGFyZSB0aGUgc2FtZSwgY2FsY3VsYXRlcyBob3cgbXVjaCB0aGUgYWN0dWFsIG1lYW5zIGRldmlhdGUgZnJvbSB0aGUgYXNzdW1wdGlvbiwgYW5kIHJlcG9ydHMgaXQgYXMgdGhlIEYtdGVzdCBzY29yZS4gQSBsYXJnZXIgc2NvcmUgbWVhbnMgdGhlcmUgaXMgYSBsYXJnZXIgZGlmZmVyZW5jZSBiZXR3ZWVuIHRoZSBtZWFucy4NCg0KLSAgICoqUC12YWx1ZSoqOiBUaGUgcC12YWx1ZSB0ZWxscyB5b3UgaG93IHN0YXRpc3RpY2FsbHkgc2lnbmlmaWNhbnQgdGhlIGNhbGN1bGF0ZWQgc2NvcmUgdmFsdWUgaXMuDQoNCklmIG91ciBgQXJyRGVsYXlgIHZhcmlhYmxlIGlzIHN0cm9uZ2x5IGNvcnJlbGF0ZWQgd2l0aCB0aGUgdmFyaWFibGUgd2UgYXJlIGFuYWx5emluZywgZXhwZWN0IEFOT1ZBIHRvIHJldHVybiBhIHNpemVhYmxlIEYtdGVzdCBzY29yZSBhbmQgYSBzbWFsbCBwLXZhbHVlLg0KDQoqKkFtZXJpY2FuIEFpcmxpbmUgKEFBKSBhbmQgQWxhc2thIEFpcmxpbmUgKEFTKSoqDQoNClRoZSBBTk9WQSB0ZXN0IGNhbiBiZSBwZXJmb3JtZWQgaW4gYmFzZSBSJ3MgYHN0YXRzYCBwYWNrYWdlIHVzaW5nIHRoZSBgYW92KClgIGZ1bmN0aW9uLiBZb3UgY2FuIHBhc3MgaW4gdGhlIGFycml2YWwgZGVsYXkgZGF0YSBvZiB0aGUgdHdvIGFpcmxpbmUgZ3JvdXBzIHRoYXQgd2Ugd2FudCB0byBjb21wYXJlIGFuZCBpdCBjYWxjdWxhdGVzIHRoZSBBTk9WQSByZXN1bHRzLg0KDQpJbiB0aGlzIGZpcnN0IGV4YW1wbGUsIHlvdSBjYW4gY29tcGFyZSBBbWVyaWNhbiBBaXJsaW5lIGFuZCBBbGFza2EgQWlybGluZS4gVGhlIHJlc3VsdHMgY29uZmlybSB3aGF0IHdlIGd1ZXNzZWQgYXQgZmlyc3QuIFRoZSBmbGlnaHQgZGVsYXkgYmV0d2VlbiAiQUEiIGFuZCAiQVMiIGFyZSBub3Qgc2lnbmlmaWNhbnRseSBkaWZmZXJlbnQsIGFzIHRoZSBGIHNjb3JlICgwLjEzKSBpcyBsZXNzIHRoYW4gMSBhbmQgcC12YWx1ZSBpcyBsYXJnZXIgdGhhbiAwLjA1Lg0KDQpgYGB7cn0NCmFhX2FzX3N1YnNldCA8LSBzdWJfYWlybGluZSAlPiUNCiAgc2VsZWN0KEFyckRlbGF5LCBSZXBvcnRpbmdfQWlybGluZSkgJT4lDQogIGZpbHRlcihSZXBvcnRpbmdfQWlybGluZSA9PSAiQUEiIHwgUmVwb3J0aW5nX0FpcmxpbmUgPT0gIkFTIikNCg0KYWRfYW92IDwtIGFvdihBcnJEZWxheSB+IFJlcG9ydGluZ19BaXJsaW5lLCBkYXRhID0gYWFfYXNfc3Vic2V0KQ0Kc3VtbWFyeShhZF9hb3YpDQpgYGANCg0KKipBbWVyaWNhbiBBaXJsaW5lIChBQSkgYW5kIFBhbiBBbSBBaXJsaW5lIChQQSAoMSkpKioNCg0KQXMgYW5vdGhlciBleGFtcGxlLCB5b3UgY2FuIGNvbXBhcmUgQW1lcmljYW4gQWlybGluZSBhbmQgUGFuIEFtIEFpcmxpbmUuIEZyb20gdGhlIGJlbG93IG91dHB1dCwgdGhlIGFycml2YWwgZGVsYXkgYmV0d2VlbiAiQUEiIGFuZCAiUEEgKDEpIiBhcmUgc2lnbmlmaWNhbnRseSBkaWZmZXJlbnQsIHNpbmNlIHRoZSBGLXNjb3JlIGlzIHZlcnkgbGFyZ2UgKEYgPSAxNy45NSkgYW5kIHRoZSBwLXZhbHVlIGlzIDAuMDAwMDI0NSB3aGljaCBpcyBzbWFsbGVyIHRoYW4gMC4wNS4gQWxsIGluIGFsbCwgd2UgY2FuIHNheSB0aGF0IHRoZXJlIGlzIGEgc3Ryb25nIGNvcnJlbGF0aW9uIGJldHdlZW4gYSBjYXRlZ29yaWNhbCB2YXJpYWJsZSBhbmQgb3RoZXIgdmFyaWFibGVzLCBpZiB0aGUgQU5PVkEgdGVzdCBnaXZlcyB1cyBhIGxhcmdlIEYtdGVzdCB2YWx1ZSBhbmQgYSBzbWFsbCBwLXZhbHVlLg0KDQpgYGB7cn0NCmFhX3BhX3N1YnNldCA8LSBzdWJfYWlybGluZSAlPiUNCiAgc2VsZWN0KEFyckRlbGF5LCBSZXBvcnRpbmdfQWlybGluZSkgJT4lDQogIGZpbHRlcihSZXBvcnRpbmdfQWlybGluZSA9PSAiQUEiIHwgUmVwb3J0aW5nX0FpcmxpbmUgPT0gIlBBICgxKSIpDQoNCmFkX2FvdiA8LSBhb3YoQXJyRGVsYXkgfiBSZXBvcnRpbmdfQWlybGluZSwgZGF0YSA9IGFhX3BhX3N1YnNldCkNCnN1bW1hcnkoYWRfYW92KQ0KYGBgDQoNCiMjIyBDb25jbHVzaW9uOiBJbXBvcnRhbnQgVmFyaWFibGVzDQoNCldlIG5vdyBoYXZlIGEgYmV0dGVyIGlkZWEgb2Ygd2hhdCBvdXIgZGF0YSBsb29rcyBsaWtlIGFuZCB3aGljaCB2YXJpYWJsZXMgYXJlIGltcG9ydGFudCB0byB0YWtlIGludG8gYWNjb3VudCB3aGVuIHByZWRpY3RpbmcgdGhlIGFycml2YWwgZGVsYXkgKEFyckRlbGF5KS4gV2UgaGF2ZSBuYXJyb3dlZCBpdCBkb3duIHRvIHRoZSBmb2xsb3dpbmcgdmFyaWFibGVzOg0KDQpDb250aW51b3VzIG51bWVyaWNhbCB2YXJpYWJsZXM6DQoNCi0gICBEZXBEZWxheU1pbnV0ZXMNCg0KLSAgIENhcnJpZXJEZWxheQ0KDQotICAgTGF0ZUFpcmNyYWZ0RGVsYXkNCg0KQ2F0ZWdvcmljYWwgdmFyaWFibGVzOg0KDQotICAgUmVwb3J0aW5nQWlybGluZQ0KDQpBcyB3ZSBub3cgbW92ZSBpbnRvIGJ1aWxkaW5nIG1hY2hpbmUgbGVhcm5pbmcgbW9kZWxzIHRvIGF1dG9tYXRlIG91ciBhbmFseXNpcywgZmVlZGluZyB0aGUgbW9kZWwgd2l0aCB2YXJpYWJsZXMgdGhhdCBtZWFuaW5nZnVsbHkgYWZmZWN0IG91ciB0YXJnZXQgdmFyaWFibGUgd2lsbCBpbXByb3ZlIG91ciBtb2RlbCdzIHByZWRpY3Rpb24gcGVyZm9ybWFuY2UuDQoNClxfXF9cX1xfXF9cX1xfXF9cX1xfXF9cX1xfXF9cX1xfXF9cX1xfXF9cX1xfXF9cX1xfXF9cX1xfXF9cX1xfXF9cX1xfXF9cX1xfXF9cX1xfXF9cX1xfXF9cX1xfXF9cX1xfXF9cX1xfXF9cX1xfXF9cX1xfXF9cX1xfXF9cX1xfXF9cX1xfXF9cX1xfXF9cX1xfXF9cX1xfXF9cX1xfXF9cX1xfXF9cX1xfXF9cX1xfXF9cX1xfXF9cX1xfXF9cX1xfXF9cX1xfXF9cX1xfXF9cX1xfXF9cX1xfXF9cXw0KDQojIyBTaW1wbGUgTGluZWFyIFJlZ3Jlc3Npb24NCg0KU2ltcGxlIExpbmVhciBSZWdyZXNzaW9uIGlzIGEgbWV0aG9kIHRvIGhlbHAgdXMgdW5kZXJzdGFuZCB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gdHdvIHZhcmlhYmxlczoNCg0KLSAgIFg6IFByZWRpY3RvciB2YXJpYWJsZSAtIERlcERlbGF5TWludXRlcw0KDQotICAgWTogVGFyZ2V0IHZhcmlhYmxlIC0gQXJyRGVsYXlNaW51dGVzDQoNClRoZSByZXN1bHQgb2YgTGluZWFyIFJlZ3Jlc3Npb24gaXMgYSAqKmxpbmVhciBmdW5jdGlvbioqIHRoYXQgcHJlZGljdHMgdGhlIHJlc3BvbnNlIChkZXBlbmRlbnQpIHZhcmlhYmxlIGFzIGEgZnVuY3Rpb24gb2YgdGhlIHByZWRpY3RvciAoaW5kZXBlbmRlbnQpIHZhcmlhYmxlLg0KDQojIyMgTGluZWFyIE1vZGVsIEZ1bmN0aW9uOg0KDQokJA0KXGhhdHtZfSA9IGJfezB9ICsgYl97MX1YDQokJA0KDQotICAgJGJfezB9JCByZWZlcnMgdG8gdGhlIGludGVyY2VwdCBvZiB0aGUgcmVncmVzc2lvbiBsaW5lLCB0aGUgdmFsdWUgb2YgWSB3aGVuIFggaXMgMA0KDQotICAgJGJfezF9JCByZWZlcnMgdG8gdGhlIHNsb3BlIG9mIHRoZSByZWdyZXNzaW9uIGxpbmUsIHRoZSB2YWx1ZSB3aXRoIHdoaWNoIFkgY2hhbmdlcyB3aGVuIFggaW5jcmVhc2VzIGJ5IDEgdW5pdA0KDQotICAgJFxoYXR7WX0kICh5LWhhdCkgaXMgdGhlIHByZWRpY3RlZCB2YWx1ZSBmcm9tIHRoZSBsaW5lYXIgbW9kZWwNCg0KRml0IHRoZSBkYXRhIGludG8gYSBsaW5lYXIgcmVncmVzc2lvbiBtb2RlbA0KDQpGaXJzdCwgbGV0J3MganVzdCBsb29rIGF0IGp1c3QgQWxhc2thIEFpcmxpbmUgKEFBKSBkYXRhLCBzbyBmaWx0ZXIgdGhlIGRhdGEgZmlyc3QuIFdlIGFsc28gZmlsdGVyIG91dCB0aGUgTkFzIGluIENhcnJpZXJEZWxheSBiZWNhdXNlIHlvdSB3aWxsIHVzZSB0aGF0IHZhcmlhYmxlIGxhdGVyLg0KDQoxLiAgRGVmaW5lIHRoZSBkYXRhc2V0IGFzIGFhX2RlbGF5cw0KDQpgYGB7cn0NCmFhX2RlbGF5cyA8LSBzdWJfYWlybGluZSAlPiUNCiAgZmlsdGVyKENhcnJpZXJEZWxheSAhPSAiTkEiLCBSZXBvcnRpbmdfQWlybGluZSA9PSAiQUEiKQ0KaGVhZChhYV9kZWxheXMpDQpgYGANCg0KRm9yIHRoaXMgZXhhbXBsZSwgKip3ZSB3YW50IHRvIGxvb2sgYXQgaG93IGRlcGFydHVyZSBkZWxheSAoRGVwRGVsYXlNaW51dGVzKSBjYW4gaGVscCB1cyBwcmVkaWN0IGFycml2YWwgZGVsYXkgKEFyckRlbGF5TWludXRlcykqKi4gVXNpbmcgc2ltcGxlIGxpbmVhciByZWdyZXNzaW9uLCB3ZSB3aWxsIGNyZWF0ZSBhIGxpbmVhciBmdW5jdGlvbiB3aXRoICJEZXBEZWxheU1pbnV0ZXMiIGFzIHRoZSBwcmVkaWN0b3IgdmFyaWFibGUgYW5kIHRoZSAiQXJyRGVsYXlNaW51dGVzIiBhcyB0aGUgcmVzcG9uc2UgdmFyaWFibGUuIFlvdSBjYW4gdXNlIGJhc2UgUidzIGZ1bmN0aW9uIGBsbSgpYCB0byBjcmVhdGUgYSBsaW5lYXIgbW9kZWwuDQoNCkZpdCB0aGUgZGF0YSBpbnRvIGEgbGluZWFyIHJlZ3Jlc3Npb24gbW9kZWw6DQoNCmBgYHtyfQ0KbGluZWFyX21vZGVsIDwtIGxtKEFyckRlbGF5TWludXRlcyB+IERlcERlbGF5TWludXRlcywgZGF0YSA9IGFhX2RlbGF5cykNCmBgYA0KDQpTdW1tYXJpemUgdGhlIHJlZ3Jlc3Npb24gbW9kZWwgdXNpbmcgYHN1bW1hcnkoKWAuIFRoZSBvdXRwdXQgZGlzcGxheXMgdGhlIGxlYXJuZWQgY29lZmZpY2llbnRzICgiRXN0aW1hdGUiIGluIHRoZSBvdXRwdXQpIG9mIHRoZSBtb2RlbCwgJGJfMCQgYW5kICRiXzEkIGFzIHdlbGwgYXMgb3RoZXIgaW5mb3JtYXRpb24gYWJvdXQgdGhlIGZpdHRlZCBtb2RlbC4NCg0KYGBge3J9DQpzdW1tYXJ5KGxpbmVhcl9tb2RlbCkNCmBgYA0KDQpTTFIgLSBFc3RpbWF0ZWQgTGluZWFyIE1vZGVsDQoNCi0gICBWaWV3IHRoZSBpbnRlcmNlcHQgKGIwKSAxNy4zNQ0KDQotICAgVmlldyB0aGUgc2xvcGUgKGIxKSAwLjc1MjMNCg0KLSAgIFRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiBhcnJpdmFsIGFuZCBkZXBhcnR1cmUgZGVsYXkgaXMgZ2l2ZW4gYnk6ICoqKkFyckRlbGF5TWludXRlcyA9IDE3LjM1ICsgMC43NTIzIFwqIERlcERlbGF5TWludXRlcyoqKg0KDQojIyMgU0xNOiBQcmVkaWN0aW9uDQoNCkNyZWF0ZSBhIG5ldmVyIHNlZW4gZGF0YSwgYW5kIHRoZW4gd2UgY2FuIG91dHB1dCBhIHByZWRpY3Rpb24gb2YgdGhyZWUgbmV3IGRhdGEgcG9pbnRzDQoNCmBgYHtyfQ0KbmV3X2RlcGRlbGF5IDwtIGRhdGEuZnJhbWUoRGVwRGVsYXlNaW51dGVzID0gYygxMiwgMTksIDI0KSkNCmBgYA0KDQpQcmVkaWN0IHRoZSByZWdyZXNzaW9uIG1vZGVsDQoNCmBgYHtyfQ0KcHJlZCA8LSBwcmVkaWN0KGxpbmVhcl9tb2RlbCwgbmV3ZGF0YSA9IG5ld19kZXBkZWxheSwgaW50ZXJ2YWwgPSAiY29uZmlkZW5jZSIpDQpwcmVkDQpgYGANCg0KV2hlbiB3ZSBwcmludCB0aGUgYHByZWRgIG9iamVjdCwgd2UgY2FuIHNlZSB0aGF0IHRoZXJlIGFyZSAzIGNvbHVtbnM6IGZpdCwgbHdyIGFuZCB1cHIuIFRoZSAiZml0IiBjb2x1bW4gaXMgdGhlIHByZWRpY3Rpb24gcmVzdWx0cyBvZiB0aGUgaW5wdXRzLiBBbmQgImx3ciIgYW5kICJ1cHIiIGFyZSB0aGUgbG93ZXIgYm91bmQgYW5kIHVwcGVyIGJvdW5kIG9mIHRoZSA5NSUgY29uZmlkZW5jZSBpbnRlcnZhbHMgb2YgcHJlZGljdGlvbiByZXN1bHRzLiBUaGUgY29uZmlkZW5jZSBpbnRlcnZhbCByZWZsZWN0cyB0aGUgdW5jZXJ0YWludHkgYXJvdW5kIHRoZSBtZWFuIHByZWRpY3Rpb25zLg0KDQpGb3IgZXhhbXBsZSwgZ2l2ZW4gdGhhdCB0aGUgRGVwRGVsYXlNaW51dGVzIGlzIDEyLCB0aGVuIHRoZSBtb2RlbCBwcmVkaWN0cyB0aGUgQXJyRGVsYXlNaW51dGVzIHRvIGJlIDI2LjM4LCBhbmQgd2UgYXJlIDk1JSBjb25maWRlbnQgdGhhdCB0aGUgaW50ZXJ2YWwgKDIxLjk4LCAzMC43NykgY2FwdHVyZXMgdGhlIHRydWUgbWVhbiBhcnJpdmFsIGRlbGF5IGZvciB0aGlzIGluc3RhbmNlLg0KDQpXaGF0IGlzIHRoZSB2YWx1ZSBvZiB0aGUgaW50ZXJjZXB0IChcJGJfMFwkKSBhbmQgdGhlIFNsb3BlIChcJGJfMVwkKT8NCg0KIyMjIFdoYXQgaXMgdGhlIGZpbmFsIGVzdGltYXRlZCBsaW5lYXIgbW9kZWwgd2UgZ2V0Pw0KDQpBcyB3ZSBzYXcgYWJvdmUsIHdlIHNob3VsZCBnZXQgYSBmaW5hbCBsaW5lYXIgbW9kZWwgd2l0aCB0aGUgc3RydWN0dXJlOg0KDQokJA0KXGhhdHtZfSA9IGJfMCArIGJfMSBYDQokJA0KDQpSZW1lbWJlciB0aGF0IHdlIGFyZSBwcmVkaWN0aW5nIEFyckRlbGF5TWludXRlcyB1c2luZyBEZXBEZWFseU1pbnV0ZXMuIFNvLCBwbHVnZ2luZyBpbiB0aGUgYWN0dWFsIHZhbHVlcyB3ZSBnZXQ6DQoNCiQkDQpBcnJEZWxheU1pbnV0ZXMgPSAxNy4zNSArIDAuNzUyMyAqIERlcERlbGF5TWludXRlcw0KJCQNCg0KIyBRdWVzdGlvbiAjMSBhKToNCg0KKipDcmVhdGUgYSBsaW5lYXIgZnVuY3Rpb24gd2l0aCAiQ2FycmllckRlbGF5IiBhcyB0aGUgcHJlZGljdG9yIHZhcmlhYmxlIGFuZCB0aGUgIkFyckRlbGF5TWludXRlcyIgYXMgdGhlIHJlc3BvbnNlIHZhcmlhYmxlLioqDQoNCmBgYHtyfQ0KbGluZWFyX21vZGVsMiA8LSBsbShBcnJEZWxheU1pbnV0ZXMgfiBDYXJyaWVyRGVsYXksIGRhdGEgPSBhYV9kZWxheXMpDQpgYGANCg0KIyBRdWVzdGlvbiAjMSBiKToNCg0KKipGaW5kIHRoZSBjb2VmZmljaWVudHMgKGludGVyY2VwdCBhbmQgc2xvcGUpIG9mIHRoZSBtb2RlbC4qKg0KDQpgYGB7cn0NCmxpbmVhcl9tb2RlbDIkY29lZmZpY2llbnRzDQpgYGANCg0KIyBRdWVzdGlvbiAjMSBjKToNCg0KKipXaGF0IGlzIHRoZSBlcXVhdGlvbiBvZiB0aGUgcHJlZGljdGVkIGxpbmUuIFlvdSBjYW4gdXNlIHggYW5kIHloYXQgb3IgJ0NhcnJpZXJEZWxheScgb3IgJ0FyckRlbGF5TWludXRlcyc/KioNCg0KJCQNCkFyckRlbGF5TWludXRlcyA9IDM1LjEyICsgMC43MDMgKiBDYXJyaWVyRGVsYXkNCiQkDQoNCiMjIyBWaXN1YWxpemF0aW9uDQoNClNpbXBseSB2aXN1YWxpemluZyB5b3VyIGRhdGEgd2l0aCBhIHJlZ3Jlc3Npb24NCg0KYGBge3J9DQpnZ3Bsb3QoYWFfZGVsYXlzLCBhZXMoeCA9IERlcERlbGF5TWludXRlcywgeSA9IEFyckRlbGF5TWludXRlcykpICsNCiAgZ2VvbV9wb2ludCgpICsgDQogIHN0YXRfc21vb3RoKG1ldGhvZCA9ICJsbSIsIGNvbCA9ICJyZWQiLCBzZSA9IEZBTFNFKQ0KYGBgDQoNCiMjIE11bHRpcGxlIGxpbmVhciBSZWdyZXNzaW9uDQoNCldoYXQgaWYgd2Ugd2FudCB0byBwcmVkaWN0IGFycml2YWwgZGVsYXkgbWludXRlcyB1c2luZyAqbW9yZSB0aGFuIG9uZSogdmFyaWFibGU/DQoNCklmIHdlIHdhbnQgdG8gdXNlIG1vcmUgdmFyaWFibGVzIGluIG91ciBtb2RlbCB0byBwcmVkaWN0IGFycml2YWwgZGVsYXkgbWludXRlcywgd2UgY2FuIHVzZSAqKk11bHRpcGxlIExpbmVhciBSZWdyZXNzaW9uKiouIE11bHRpcGxlIExpbmVhciBSZWdyZXNzaW9uIGlzIHZlcnkgc2ltaWxhciB0byBTaW1wbGUgTGluZWFyIFJlZ3Jlc3Npb24sIGJ1dCB0aGlzIG1ldGhvZCBpcyB1c2VkIHRvIGV4cGxhaW4gdGhlIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIG9uZSBjb250aW51b3VzIHJlc3BvbnNlIChkZXBlbmRlbnQpIHZhcmlhYmxlIGFuZCAqKnR3byBvciBtb3JlKiogcHJlZGljdG9yIChpbmRlcGVuZGVudCkgdmFyaWFibGVzLiAqKk1vc3Qgb2YgdGhlIHJlYWwtd29ybGQgcmVncmVzc2lvbiBtb2RlbHMgaW52b2x2ZSBtdWx0aXBsZSBwcmVkaWN0b3JzKiouIFdlIHdpbGwgaWxsdXN0cmF0ZSB0aGUgc3RydWN0dXJlIGJ5IHVzaW5nIHR3byBwcmVkaWN0b3IgdmFyaWFibGVzLCBidXQgdGhlc2UgcmVzdWx0cyBjYW4gZ2VuZXJhbGl6ZSB0byBhbnkgYW1vdW50IG9mIHByZWRpY3RvciB2YXJpYWJsZXM6DQoNCi0gICAkWSQ6IFJlc3BvbnNlIFZhcmlhYmxlDQoNCi0gICAkWF8xJDogUHJlZGljdG9yIFZhcmlhYmxlDQoNCi0gICAkWDIkOiBQcmVkaWN0b3IgVmFyaWFibGUgMg0KDQpUaGUgZXF1YXRpb24gaXMgZ2l2ZW4gYnk6DQoNCiQkDQpcaGF0e1l9ID0gYl8wICsgYl8xWF8xICsgYl8yWF8yDQokJA0KDQpXaGVyZSwNCg0KLSAgICRiXzAkOiBpbnRlcmNlcHQNCg0KLSAgICRiXzEkOiBjb2VmZmljaWVudCBvZiBWYXJpYWJsZSAxDQoNCi0gICAkYl8yJDogY29lZmZpY2llbnQgb2YgVmFyaWFibGUgMg0KDQpGcm9tIHByZXZpb3VzIGNlbGxzIHdlIGtub3cgdGhhdCBvdGhlciBnb29kIHByZWRpY3RvcnMgb2YgQXJyRGVsYXlNaW51dGVzIGNvdWxkIGJlOg0KDQotICAgRGVwRGVsYXlNaW51dGVzDQoNCi0gICBMYXRlQWlyY3JhZnREZWxheQ0KDQpMZXTCtHMgZGV2ZWxvcCBhIG1vZGVsIHVzaW5nIHRoZXNlIHZhcmlhYmxlcyBhcyB0aGUgcHJlZGljdG9yIHZhcmlhYmxlcyBieSBmaXR0aW5nIHRoZSBkYXRhDQoNCiMjIEZpdHRpbmcgYSBNTFIgTW9kZWwNCg0KLSAgIERlZmluZSB0aGUgZGF0YXNldCwgdGhlIHByZWRpY3RvciB2YXJpYWJsZXMsIGFuZCB0aGUgdGFyZ2V0IHZhcmlhYmxlDQoNCi0gICBVc2UgbG0oKSBmdW5jdGlvbiB0byBmaXQgdGhlIG1vZGVsLCBmaW5kIHBhcmFtZXRlciBiMCwgYjEsIGIyDQoNCmBgYHtyfQ0KbWxyIDwtIGxtKEFyckRlbGF5TWludXRlcyB+IERlcERlbGF5TWludXRlcyArIExhdGVBaXJjcmFmdERlbGF5LCBkYXRhID0gYWFfZGVsYXlzKQ0KbWxyDQpgYGANCg0KIyMjIFN1bW1hcnk6IEVzdGltYXRlZCBNTFIgbW9kZWwNCg0KVXNlIHN1bW1hcnkoKSB0byBvdXRwdXQgdGhlIG1vZGVsIHJlc3VsdHMNCg0KYGBge3J9DQpzdW1tYXJ5KG1scikNCmBgYA0KDQpXaGF0IGlzIHRoZSB2YWx1ZSBvZiB0aGUgaW50ZXJjZXB0IChiMCkgYW5kIHRoZSBjb2VmZmljaWVudHMgKGIxLCBiMik/DQoNCmBgYHtyfQ0KbWxyJGNvZWZmaWNpZW50cw0KYGBgDQoNCldoYXQgaXMgdGhlIGZpbmFsIGVzdGltYXRlZCBsaW5lYXIgbW9kZWwgdGhhdCB3ZSBnZXQ/DQoNCkFzIHdlIHNhdyBhYm92ZSwgd2Ugc2hvdWxkIGdldCBhIGZpbmFsIGxpbmVhciBmdW5jdGlvbiB3aXRoIHRoZSBzdHJ1Y3R1cmU6DQoNCiQkDQpcaGF0e1l9ID0gYl8wICsgYl8xWF8xICsgYl8yWF8yDQokJA0KDQpXaGF0IGlzIHRoZSBsaW5lYXIgZnVuY3Rpb24gd2UgZ2V0IGluIHRoaXMgZXhhbXBsZT8NCg0KJCQNCkFyckRlbGF5TWludXRlcyA9IDE3LjMyICsgMC43NTU2ICogRGVwRGVsYXlNaW51dGVzIC0gMC4wMTAzICogTGF0ZUFpcmNyYWZ0RGVsYXkNCiQkDQoNCiMjIFF1ZXN0aW9uICMyIGEpOg0KDQoqKkNyZWF0ZSBhbmQgdHJhaW4gYSBNdWx0aXBsZSBMaW5lYXIgUmVncmVzc2lvbiBtb2RlbCAibWxyMiIgd2hlcmUgdGhlIHJlc3BvbnNlIHZhcmlhYmxlIGlzIEFyckRlbGF5TWludXRlcywgYW5kIHRoZSBwcmVkaWN0b3IgdmFyaWFibGUgaXMgJ0RlcERlbGF5TWludXRlcycsICdMYXRlQWlyY3JhZnREZWxheScgYW5kICdDYXJyaWVyRGVsYXknLioqDQoNCmBgYHtyfQ0KbWxyMiA9IGxtKEFyckRlbGF5TWludXRlcyB+IERlcERlbGF5TWludXRlcyArIExhdGVBaXJjcmFmdERlbGF5ICsgQ2FycmllckRlbGF5LCBkYXRhID0gYWFfZGVsYXlzKQ0Kc3VtbWFyeShtbHIyKQ0KYGBgDQoNCiMjIFF1ZXN0aW9uICMyIGIpOg0KDQoqKkZpbmQgdGhlIGNvZWZmaWNpZW50cyBvZiB0aGUgbW9kZWw/KioNCg0KYGBge3J9DQptbHIyJGNvZWZmaWNpZW50cw0KYGBgDQoNCiMjIFF1ZXN0aW9uICMyIGMpOg0KDQoqKlVzaW5nIHRoZSBmaXR0ZWQgbW9kZWwsIG1scjIsIHdoYXQgYXJlIHRoZSBwcmVkaWN0ZWQgdmFsdWVzIGZvciB0aGUgZm9sbG93aW5nIG5ldyBkYXRhIHBvaW50cz8qKg0KDQojIyMgTUxSIHByZWRpY3Rpb24NCg0KLSAgIENyZWF0ZSBhIG5ldyBkYXRhc2V0DQoNCmBgYHtyfQ0KRGVwRGVsYXlNaW51dGVzIDwtIGMoMTAsIDIwLCAzMCkNCkxhdGVBaXJjcmFmdERlbGF5IDwtIGMoMjAsIDYwLCAzMCkNCm5ld19tdWx0aWRlbGF5IDwtIGRhdGEuZnJhbWUoRGVwRGVsYXlNaW51dGVzLCBMYXRlQWlyY3JhZnREZWxheSkNCmBgYA0KDQotICAgQ2FsY3VsYXRlIHRoZSBwcmVkaWN0aW9ucw0KDQpgYGB7cn0NCnByZWQgPC0gcHJlZGljdChtbHIsIG5ld2RhdGEgPSBuZXdfbXVsdGlkZWxheSwgaW50ZXJ2YWwgPSAiY29uZmlkZW5jZSIpDQpwcmVkDQpgYGANCg0KIyMgMy4gQXNzZXNzaW5nIE1vZGVscyBWaXN1YWxseQ0KDQpOb3cgdGhhdCB3ZSd2ZSBkZXZlbG9wZWQgc29tZSBtb2RlbHMsIGhvdyBkbyB3ZSBldmFsdWF0ZSBvdXIgbW9kZWxzIGFuZCBob3cgZG8gd2UgY2hvb3NlIHRoZSBiZXN0IG9uZT8gT25lIHdheSB0byBkbyB0aGlzIGlzIGJ5IHVzaW5nIHZpc3VhbGl6YXRpb24uDQoNCiMjIyBSZWdyZXNzaW9uIFBsb3QNCg0KV2hlbiBpdCBjb21lcyB0byBzaW1wbGUgbGluZWFyIHJlZ3Jlc3Npb24sIGFuIGV4Y2VsbGVudCB3YXkgdG8gdmlzdWFsaXplIHRoZSBmaXQgb2Ygb3VyIG1vZGVsIGlzIGJ5IHVzaW5nICoqcmVncmVzc2lvbiBwbG90cyoqLg0KDQpSZWdyZXNzaW9uIHBsb3RzIGFyZSBhIGdvb2QgZXN0aW1hdGUgb2Y6DQoNCi0gICBUaGUgcmVsYXRpb25zaGlwIG9mIHR3byB2YXJpYWJsZXMsDQoNCi0gICBUaGUgc3RyZW5ndGggb2YgdGhlIGNvcnJlbGF0aW9uLCBhbmQNCg0KLSAgIFRoZSBkaXJlY3Rpb24gb2YgdGhlIHJlbGF0aW9uc2hpcCAocG9zaXRpdmUgb3IgbmVnYXRpdmUpLg0KDQpUaGVyZSBhcmUgc2V2ZXJhbCB3YXlzIHRvIHBsb3QgYSByZWdyZXNzaW9uIHBsb3Q7IGEgc2ltcGxlIHdheSBpcyB0byB1c2UgImdncGxvdCIgZnJvbSB0aGUgdGlkeXZlcnNlIGxpYnJhcnkuDQoNClRoaXMgcGxvdCB3aWxsIHNob3cgYSBjb21iaW5hdGlvbiBvZiBhIHNjYXR0ZXJlZCBkYXRhIHBvaW50cyAoYSAqKnNjYXR0ZXIgcGxvdCoqKSwgYXMgd2VsbCBhcyB0aGUgZml0dGVkICoqbGluZWFyIHJlZ3Jlc3Npb24qKiBsaW5lIGdvaW5nIHRocm91Z2ggdGhlIGRhdGEuIFRoaXMgd2lsbCBnaXZlIHVzIGEgcmVhc29uYWJsZSBlc3RpbWF0ZSBvZiB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gdGhlIHR3byB2YXJpYWJsZXMsIHRoZSBzdHJlbmd0aCBvZiB0aGUgY29ycmVsYXRpb24sIGFzIHdlbGwgYXMgdGhlIGRpcmVjdGlvbiAocG9zaXRpdmUgb3IgbmVnYXRpdmUgY29ycmVsYXRpb24pLg0KDQojIyMgUmVncmVzc2lvbiBQbG90DQoNCmBgYHtyfQ0KZ2dwbG90KGFhX2RlbGF5cywgYWVzKHggPSBEZXBEZWxheU1pbnV0ZXMsIHkgPSBBcnJEZWxheU1pbnV0ZXMpKSArDQogIGdlb21fcG9pbnQoKSArDQogIHN0YXRfc21vb3RoKG1ldGhvZCA9ICJsbSIsIGNvbCA9ICJyZWQiKQ0KYGBgDQoNCldlIGNhbiBzZWUgZnJvbSB0aGlzIHBsb3QgdGhhdCBBcnJpdmFsIERlbGF5IE1pbnV0ZXMgKEFyckRlbGF5TWludXRlcykgaXMgcG9zaXRpdmVseSBjb3JyZWxhdGVkIHRvIERlcGFydHVyZSBEZWxheSBNaW51dGVzIChEZXBEZWxheU1pbnV0ZXMpLCBzaW5jZSB0aGUgcmVncmVzc2lvbiBzbG9wZSBpcyBwb3NpdGl2ZS4NCg0KT25lIHRoaW5nIHRvIGtlZXAgaW4gbWluZCB3aGVuIGxvb2tpbmcgYXQgYSByZWdyZXNzaW9uIHBsb3QgaXMgdG8gcGF5IGF0dGVudGlvbiB0byBob3cgc2NhdHRlcmVkIHRoZSBkYXRhIHBvaW50cyBhcmUgYXJvdW5kIHRoZSByZWdyZXNzaW9uIGxpbmUuIFRoaXMgd2lsbCBnaXZlIHlvdSBhIGdvb2QgaW5kaWNhdGlvbiBvZiB0aGUgdmFyaWFuY2Ugb2YgdGhlIGRhdGEsIGFuZCB3aGV0aGVyIGEgbGluZWFyIG1vZGVsIHdvdWxkIGJlIHRoZSBiZXN0IGZpdCBvciBub3QuIElmIHRoZSBkYXRhIGlzIHRvbyBmYXIgb2ZmIGZyb20gdGhlIGxpbmUsIHRoaXMgbGluZWFyIG1vZGVsIG1pZ2h0IG5vdCBiZSB0aGUgYmVzdCBtb2RlbCBmb3IgdGhpcyBkYXRhLg0KDQojIyBRdWVzdGlvbiAjMyBhKToNCg0KKipDcmVhdGUgYSByZWdyZXNzaW9uIHBsb3Qgb2YgIkNhcnJpZXJEZWxheSIgYW5kICJBcnJEZWxheU1pbnV0ZXMiIHVzaW5nICJhYV9kZWxheXMiIGRhdGFzZXQqKg0KDQpgYGB7cn0NCmdncGxvdChhYV9kZWxheXMsIGFlcyh4ID0gQ2FycmllckRlbGF5LCB5ID0gQXJyRGVsYXlNaW51dGVzKSkgKw0KICBnZW9tX3BvaW50KCkgKw0KICBnZW9tX3Ntb290aChtZXRob2QgPSAibG0iLCBjb2wgPSAicmVkIikNCmBgYA0KDQojIyBRdWVzdGlvbiAjMyBiKToNCg0KKipHaXZlbiB0aGUgcmVncmVzc2lvbiBwbG90cyBhYm92ZSBpcyAiRGVwRGVsYXlNaW51dGVzIiBvciAiQ2FycmllckRlbGF5IiBtb3JlIHN0cm9uZ2x5IGNvcnJlbGF0ZWQgd2l0aCAiQXJyRGVsYXlNaW51dGVzIi4gVXNlIHRoZSBtZXRob2QgImNvcigpIiB0byB2ZXJpZnkgeW91ciBhbnN3ZXIuKioNCg0KYGBge3J9DQpjb3IoYWFfZGVsYXlzJERlcERlbGF5TWludXRlcywgYWFfZGVsYXlzJEFyckRlbGF5TWludXRlcykNCmNvcihhYV9kZWxheXMkQ2FycmllckRlbGF5LCBhYV9kZWxheXMkQXJyRGVsYXlNaW51dGVzKQ0KYGBgDQoNCioqVGhlIHZhcmlhYmxlICJEZXBEZWxheU1pbnV0ZXMiIGhhcyBhIHN0cm9uZ2VyIGNvcnJlbGF0aW9uIHdpdGggIkFyckRlbGF5TWludXRlcyIsIGl0IGlzIGFwcHJveGltYXRlbHkgMC44NzEgY29tcGFyZWQgdG8gIkNhcnJpZXJEZWxheSIgd2hpY2ggaXMgYXBwcm94aW1hdGVseSAwLjYyNC4qKg0KDQojIyMgUmVzaWR1YWwgUGxvdA0KDQpBIGdvb2Qgd2F5IHRvIHZpc3VhbGl6ZSB0aGUgdmFyaWFuY2Ugb2YgdGhlIGRhdGEgaXMgdG8gdXNlIGEgcmVzaWR1YWwgcGxvdC4gQmVmb3JlIHdlIHN0YXJ0IGNyZWF0aW5nIHJlc2lkdWFsIHBsb3RzIGxldCdzIGZpcnN0IGFuc3dlciB0aGUgZm9sbG93aW5nIHF1ZXN0aW9uczoNCg0KLSAgIFdoYXQgaXMgYSAqKnJlc2lkdWFsKio/DQoNCiAgICAtICAgVGhlIGRpZmZlcmVuY2UgYmV0d2VlbiB0aGUgb2JzZXJ2ZWQgdmFsdWUgKCRZJCkgYW5kIHRoZSBwcmVkaWN0ZWQgdmFsdWUgKCRcaGF0e1l9JCkgaXMgY2FsbGVkIHRoZSByZXNpZHVhbCAob3IgZXJyb3IpLiBXaGVuIHdlIGxvb2sgYXQgYSByZWdyZXNzaW9uIHBsb3QsIHRoZSByZXNpZHVhbCBpcyB0aGUgZGlzdGFuY2UgZnJvbSB0aGUgZGF0YSBwb2ludCB0byB0aGUgZml0dGVkIHJlZ3Jlc3Npb24gbGluZS4NCg0KLSAgIFdoYXQgaXMgYSAqKnJlc2lkdWFsIHBsb3QqKj8NCg0KICAgIC0gICBBIHJlc2lkdWFsIHBsb3QgaXMgYSBncmFwaCB0aGF0IHNob3dzIHRoZSByZXNpZHVhbHMgb24gdGhlIHZlcnRpY2FsIHktYXhpcyBhbmQgdGhlIGluZGVwZW5kZW50IHZhcmlhYmxlIG9uIHRoZSBob3Jpem9udGFsIHgtYXhpcy4NCg0KLSAgIFdoYXQgZG8gd2UgcGF5IGF0dGVudGlvbiB0byB3aGVuIGxvb2tpbmcgYXQgYSByZXNpZHVhbCBwbG90Pw0KDQogICAgLSAgICoqSG9tb3NjZWRhc3RpY2l0eSoqOiBJZiB0aGUgcmVzaWR1YWwgcGxvdCBpcyBob21vc2NlZGFzdGljLCB0aGVuIHRoZSBwb2ludHMgaW4gdGhlIHBsb3QgYXJlICoqcmFuZG9tbHkgc3ByZWFkIG91dCBhcm91bmQgdGhlIHgtYXhpcyoqLCB3aGljaCBtZWFucyB0aGF0IGEgKipsaW5lYXIgbW9kZWwgaXMgYXBwcm9wcmlhdGUqKiBmb3IgdGhlIGRhdGEuIFdoeSBpcyB0aGF0PyBSYW5kb21seSBzcHJlYWQgb3V0IHJlc2lkdWFscyBtZWFucyB0aGF0IHRoZSB2YXJpYW5jZSBpcyBjb25zdGFudCwgYW5kIHRodXMgdGhlIGxpbmVhciBtb2RlbCBpcyBhIGdvb2QgZml0IGZvciB0aGlzIGRhdGEuDQoNCk5vdywgbGV0J3MgbG9vayBhZ2FpbiBhdCB0aGUgcmVncmVzc2lvbiBwbG90IG9mIEFyckRlbGF5TWludXRlcyBhcyB0aGUgcmVzcG9uc2UgYW5kIERlcERlbGF5TWludXRlcyBhcyB0aGUgcHJlZGljdG9yLiBUaGlzIHRpbWUsIGxldCdzIHZpc3VhbGl6ZSB0aGUgcmVzaWR1YWxzIG9uIHRoaXMgcGxvdC4NCg0KLSAgIFRoZSByZWQgbGluZSBpcyB0aGUgcmVncmVzc2lvbiBsaW5lDQoNCi0gICBUaGUgYmxhY2sgZG90cyByZXByZXNlbnQgdGhlIG9ic2VydmVkIHZhbHVlcyBvZiBBcnJEZWxheU1pbnV0ZXMNCg0KLSAgIFRoZSB3aGl0ZSBkb3RzIGFyZSB0aGUgcHJlZGljdGVkIHZhbHVlcyBmcm9tIHRoZSBsaW5lYXIgcmVncmVzc2lvbiBtb2RlbA0KDQotICAgVGhlIGxpZ2h0IGdyYXkgbGluZXMgYXJlIHRoZSByZXNpZHVhbHMsIG9yIGVycm9ycy4gSXQgc2hvd3MgaG93IGZhciBhd2F5IHRoZSBvYnNlcnZlZCB2YWx1ZXMgYXJlIGZyb20gdGhlIHByZWRpY3RlZCB2YWx1ZXMuIFNvIGEgbG9uZ2VyIGxpbmUgbWVhbnMgYSBsYXJnZXIgZXJyb3IuDQoNCmBgYHtyfQ0KIyBDcmVhdGUgYSBuZXcgY29sdW1uICJwcmVkaWN0ZWQiDQphYV9kZWxheXMkcHJlZGljdGVkIDwtIHByZWRpY3QobGluZWFyX21vZGVsKQ0KDQpzY29yZV9tb2RlbCA8LSBsbShBcnJEZWxheU1pbnV0ZXMgfiBEZXBEZWxheU1pbnV0ZXMsIGRhdGEgPSBhYV9kZWxheXMpDQoNCmdncGxvdChhYV9kZWxheXMsIGFlcyh4ID0gRGVwRGVsYXlNaW51dGVzLCB5ID0gQXJyRGVsYXlNaW51dGVzKSkgKw0KICAjIFBsb3QgdGhlIGFjdHVhbCBwb2ludHMNCiAgZ2VvbV9wb2ludCgpICsNCiAgIyBQbG90IHJlZ3Jlc3Npb24gbGluZQ0KICBnZW9tX3Ntb290aChtZXRob2QgPSAibG0iLCBzZSA9IEZBTFNFLCBjb2xvciA9ICJyZWQiKSArDQogICMgQWRkIHRoZSBwcmVkaWN0ZWQgdmFsdWVzDQogIGdlb21fcG9pbnQoYWVzKHkgPSBwcmVkaWN0ZWQpLCBzaGFwZSA9IDEsIGNvbG9yID0gImJsdWUiKSArDQogICMgQ29ubmVjdCB0aGUgYWN0dWFsIGRhdGEgcG9pbnRzIHdpdGggdGhlaXIgY29ycmVzcG9uZGluZyBwcmVkaWN0ZWQgdmFsdWUNCiAgZ2VvbV9zZWdtZW50KGFlcyh4ZW5kID0gRGVwRGVsYXlNaW51dGVzLCB5ZW5kID0gcHJlZGljdGVkKSwgYWxwaGEgPSAwLjEpDQpgYGANCg0KTmV4dCwgd2UgY2FuIGNyZWF0ZSBhIHJlc2lkdWFsIHBsb3QsIHdoaWNoIGdyYXBocyB0aGUgcmVzaWR1YWxzIChsaWdodCBncmF5IGxpbmVzIGluIHRoZSBwcmV2aW91cyBncmFwaCkgYWdhaW5zdCB0aGUgb2JzZXJ2ZWQgRGVwRGVsYXlNaW51dGVzLiBUaGUgY29kZSB0byBkbyB0aGlzIGlzIHNpbWlsYXIgdG8gYSBub3JtYWwgc2NhdHRlcnBsb3QsIGJ1dCB5b3UgcGFzcyBpbiB0aGUgbGluZWFyIG1vZGVsIGBsbShBcnJEZWxheU1pbnV0ZXMgfiBEZXBEZWxheU1pbnV0ZXMpYCBhbmQgd2hlbiBzZXR0aW5nIHRoZSB5IGF4aXMsIHlvdSBjYW4gdXNlIGAucmVzaWRgIHdoaWNoIHdpbGwgdXNlIHRoZSByZXNpZHVhbHMgZnJvbSB0aGUgbW9kZWwgeW91IGlucHV0dGVkLg0KDQpXZSBjYW4gc2VlIGZyb20gdGhpcyByZXNpZHVhbCBwbG90IHRoYXQgdGhlIHJlc2lkdWFscyBhcmUgbm90IHJhbmRvbWx5IHNwcmVhZCBhcm91bmQgdGhlIHgtYXhpcywgd2hpY2ggbGVhZHMgdXMgdG8gYmVsaWV2ZSB0aGF0IG1heWJlIGEgbm9uLWxpbmVhciBtb2RlbCBpcyBtb3JlIGFwcHJvcHJpYXRlIGZvciB0aGlzIGRhdGEuDQoNCmBgYHtyfQ0KZ2dwbG90KGxtKEFyckRlbGF5TWludXRlcyB+IERlcERlbGF5TWludXRlcywgZGF0YSA9IGFhX2RlbGF5cykpICsNCiAgZ2VvbV9wb2ludChhZXMoeCA9IERlcERlbGF5TWludXRlcywgeSA9IC5yZXNpZCkpDQpgYGANCg0KIyMjIE90aGVyIERpYWdub3N0aWMgUGxvdHMNCg0KSW4gYWRkaXRpb24gdG8gcmVzaWR1YWwgcGxvdHMsIHRoZXJlIGFyZSBvdGhlciB1c2VmdWwgcGxvdHMuIEEgc2ltcGxlIHdheSB0byB2aWV3IGRpYWdub3N0aWMgcGxvdHMgaXMgdG8gZmlyc3QgY3JlYXRlIHRoZSBsaW5lYXIgbW9kZWwgdXNpbmcgYGxtKClgLCB0aGVuIGNhbGwgYmFzZSBSJ3MgYHBsb3QoKWAgZnVuY3Rpb24gb24gdGhlIG1vZGVsLg0KDQpUaGUgYmVsb3cgY29kZSB3aWxsIG91dHB1dCBmb3VyIGdyYXBoczoNCg0KMS4gICoqUmVzaWR1YWwgcGxvdCoqOiBJZGVudGljYWwgdG8gdGhlIGdyYXBoIHdlIG1hZGUgd2l0aCBnZ3Bsb3QsIGhlcmUgaXQgYWdhaW4gc2hvd3MgdGhhdCB0aGUgcmVzaWR1YWxzIGFyZSBub3QgcmFuZG9tbHkgc3ByZWFkIGFyb3VuZCB0aGUgeC1heGlzLg0KDQoyLiAgKipRLVEgcGxvdCoqOiBUaGUgZG90dGVkIGRpYWdvbmFsIGxpbmUgcmVwcmVzZW50cyB3aGF0IG5vcm1hbGx5IGRpc3RyaWJ1dGVkIGVycm9yIChyZXNpZHVhbCkgdmFsdWVzIHdvdWxkIGZvbGxvdy4gSW4gdGhpcyBjYXNlLCB0aGUgcmVzaWR1YWxzIGRvIG5vdCBsb29rIG5vcm1hbGx5IGRpc3RyaWJ1dGVkIHNpbmNlIHRoZXJlIGFyZSBtYW55IG9ic2VydmF0aW9ucyB0aGF0IGZhbGwgYWJvdmUgdGhlIGxpbmUgb24gdGhlIHJpZ2h0IHNpZGUNCg0KMy4gICoqU2NhbGUtbG9jYXRpb24gcGxvdCoqOiBUaGlzIHBsb3QgaGVscHMgY2hlY2sgdGhlIGhvbW9zY2VkYXN0aWNpdHkgYXNzdW1wdGlvbi4gSGVyZSwgaXQgc2hvd3MgYSByZWQgbGluZSB0aGF0IGlzIG5vdCBzdHJhaWdodCBhbmQgdmFsaWRhdGVzIHRoZSBob21vc2NlZGFzdGljaXR5IGFzc3VtcHRpb24gaXMgbm90IHNhdGlzZmllZC4NCg0KNC4gICoqUmVzaWR1YWxzIHZzIGxldmVyYWdlIHBsb3QqKjogVGhpcyBoZWxwcyBkZXRlcm1pbmUgKippbmZsdWVudGlhbCBwb2ludHMqKi4gQW55IHBvaW50cyBvdXRzaWRlIHRoZSBkb3R0ZWQgbGluZXMgKENvb2sncyBkaXN0YW5jZSkgd291bGQgbWFrZSBpdCBpbmZsdWVudGlhbC4gSGVyZSwgbm9uZSBvZiB0aGUgcG9pbnRzIGNyb3NzIHRoZSBsaW5lcywgaG93ZXZlciBzZXZlcmFsIHBvaW50cyBjb21lIGNsb3NlIGFuZCBjb3VsZCBiZSByZW1vdmVkIG9yIGFuYWx5emVkIGZ1cnRoZXIuDQoNCiMjIyBEaWFnbm9zdGljIFBsb3QNCg0KYGBge3J9DQpsaW5lYXJfbW9kZWwgPC0gbG0oQXJyRGVsYXlNaW51dGVzIH4gRGVwRGVsYXlNaW51dGVzLCBkYXRhID0gYWFfZGVsYXlzKQ0KcGxvdChsaW5lYXJfbW9kZWwpDQpgYGANCg0KIyMjIFBvbHlub21pYWwgcmVncmVzc2lvbg0KDQoqKlBvbHlub21pYWwgcmVncmVzc2lvbioqIGlzIGEgcGFydGljdWxhciBjYXNlIG9mIHRoZSBnZW5lcmFsIGxpbmVhciByZWdyZXNzaW9uIG1vZGVsIG9yIG11bHRpcGxlIGxpbmVhciByZWdyZXNzaW9uIG1vZGVscy4gVGhhdCBpcywgYWx0aG91Z2ggdGhlIGRhdGEgaXMgbm9ubGluZWFyIGluIHBvbHlub21pYWwgcmVncmVzc2lvbiAodGhlIHByZWRpY2F0b3IgdmFyaWFibGVzIGhhdmUgaGlnaGVyIG9yZGVyIHRlcm1zKSwgdGhlIG1vZGVsIGluIGFsbCBjYXNlcyBpcyAqKmxpbmVhcioqLiBUaGUgbW9kZWwgaXMgYWx3YXlzICoqbGluZWFyKiogYmVjYXVzZSBpdCBwcmVkaWN0cyB0aGUgY29lZmZpY2llbnRzICjwnZGPMCzwnZGPMSwuLi4pIHdoaWNoIGFyZSBhbHdheXMgb2Ygb3JkZXIgMS4NCg0KVGhlcmUgYXJlIGRpZmZlcmVudCBvcmRlcnMgb2YgcG9seW5vbWlhbCByZWdyZXNzaW9uOg0KDQpRdWFkcmF0aWMgLSAybmQgb3JkZXINCg0KJCQNClxoYXR7WX0gPSBiXzAgKyBiXzFYICsgYl8yWF4yDQokJA0KDQpDdWJpYyAtIDNyZCBvcmRlcg0KDQokJA0KWSA9IGJfMCArIGJfMVggKyBiXzJYXjIgKyBiXzNYXjMNCiQkDQoNCkhpZ2hlciAoJG5ee3RofSQpIG9yZGVyDQoNCiQkDQpZID0gYl8wICsgYl8xWCArIGJfMlheMiArIGJfMlheMyAuLi4uICsgYl9uWF5uDQokJA0KDQpMZXQncyBsb29rIGF0IHRoZSBiZWxvdyBleGFtcGxlLiBIZXJlLCB3ZSBjcmVhdGUgcmFuZG9tIHByZWRpY3RvciB2YXJpYWJsZSBgcWAgYW5kIHJhbmRvbSByZXNwb25zZSB2YXJpYWJsZSBgeWAgdGhhdCBmb2xsb3dzIGEgM3JkIG9yZGVyIHBvbHlub21pYWwgYnV0IHRoZW4gd2UgYWRkIHNvbWUgcmFuZG9tIG5vaXNlIHRvIGl0IHRvIGdldCBgbm9pc2UueWAuIFdlIHNldCB0aGUgc2VlZCBzbyB0aGF0IHRoaXMgcmVzdWx0IGNhbiBiZSByZXByb2R1Y2VkLg0KDQpgYGB7cn0NCnNldC5zZWVkKDIwKQ0KeCA8LSBzZXEoZnJvbT0wLCB0bz0yMCwgYnk9MC4xKQ0KDQojIHZhbHVlIHRvIHByZWRpY3QgKHkpOg0KeSA8LSA1MDAgKyAwLjQgKiAoeCAtIDEwKV4zDQoNCiMgc29tZSBub2lzZSBpcyBnZW5lcmF0ZWQgYW5kIGFkZGVkIHRvIHRoZSByYWwgc2lnbmFsICh5KToNCm5vaXNlIDwtIHJub3JtKGxlbmd0aCh4KSwgbWVhbj0xMCwgc2Q9ODApDQpub2lzeS55IDwtIHkgKyBub2lzZQ0KYGBgDQoNCkluIHRoZSBncmFwaCBiZWxvdywgd2UgZml0IGEgZmlyc3Qgb3JkZXIgbGluZWFyIG1vZGVsIHRvIHRoaXMgZXhhbXBsZSBkYXRhc2V0IGFuZCBjYW4gc2VlIHRoYXQgdGhlIG1vZGVsIGRvZXMgbm90IGZpdCB0aGUgZGF0YSB2ZXJ5IHdlbGwuDQoNCmBgYHtyfQ0KIyBmaXQgbGluZWFyIG1vZGVsDQpnZ3Bsb3QoZGF0YSA9IE5VTEwsIGFlcyh4LCBub2lzeS55KSkgKw0KICBnZW9tX3BvaW50KCkgKw0KICBnZW9tX3Ntb290aChtZXRob2QgPSAibG0iKQ0KYGBgDQoNCkluc3RlYWQsIHdlIGNhbiB1c2UgYSBwb2x5bm9taWFsIG1vZGVsLiBJdCBpcyBzaW1pbGFyIHRvIHRoZSBmaXJzdCBvcmRlciBsaW5lYXIgbW9kZWwgZXhjZXB0IHRoYXQgeW91IGluY2x1ZGUgYHBvbHkoKWAgd2l0aGluIGBnZW9tX3Ntb290aCgpYCB0byBpbmRpY2F0ZSB3aGF0IG9yZGVyIHBvbHlub21pYWwgdG8gdXNlLiBGb3IgZXhhbXBsZSwgdXNpbmcgYHBvbHkoeCwgNSlgIGVxdWF0ZXMgdG8gaGF2aW5nICRiXzAgKyBiXzFYXjIgKyBiXzNYXjMgKyBiXzRYXjQgKyBiXzVYXjUkLg0KDQpgYGB7cn0NCmdncGxvdChkYXRhPU5VTEwsIGFlcyh4LCBub2lzeS55KSkgKw0KICBnZW9tX3BvaW50KCkgKw0KICBnZW9tX3Ntb290aChtZXRob2QgPSAibG0iLCBmb3JtdWxhID0geSB+IHBvbHkoeCwgNSkpDQpgYGANCg0KV2UgY2FuIGFscmVhZHkgc2VlIGZyb20gcGxvdHRpbmcgdGhhdCB0aGlzIHBvbHlub21pYWwgbW9kZWwgcGVyZm9ybXMgYmV0dGVyIHRoYW4gdGhlIGxpbmVhciBtb2RlbC4gVGhpcyBpcyBiZWNhdXNlIHRoZSBnZW5lcmF0ZWQgcG9seW5vbWlhbCBmdW5jdGlvbiAiaGl0cyIgbW9yZSBvZiB0aGUgZGF0YSBwb2ludHMuDQoNCiMjIyBQb2x5bm9taWFsIDJuZCBPcmRlcg0KDQpOb3cgbGV0J3MgbG9vayBhdCBhbm90aGVyIGV4YW1wbGUsIHRoaXMgdGltZSB1c2luZyBhIDJuZCBvcmRlciBwb2x5bm9taWFsLiBBZ2Fpbiwgd2UgdXNlIGEgdG95IGRhdGFzZXQgd2hlcmUgYHRpbWVgIGlzIHRoZSBwcmVkaWN0b3IgYW5kIGB0ZW1wYCBpcyB0aGUgcmVzcG9uc2UuDQoNCmBgYHtyfQ0KdGltZSA8LSA2OjE5DQp0ZW1wIDwtIGMoNCwgNiwgNywgOSwgMTAsIDExLCAxMS41LCAxMiwgMTIsIDExLjUsIDExLCAxMCwgOSwgOCkNCg0KZ2dwbG90KGRhdGEgPSBOVUxMLCBhZXModGltZSwgdGVtcCkpICsNCiAgZ2VvbV9wb2ludCgpDQpgYGANCg0KV2UgY2FuIGNyZWF0ZSBhIG1vZGVsIGxpa2UgaG93IHdlIHNhdyBiZWZvcmUgdXNpbmcgYGxtKClgIGFuZCB0byBpbmNsdWRlIGhpZ2hlciBvcmRlciwgeW91IGNhbiB1c2VkIGBwb2x5KClgLg0KDQpGb3IgdGhpcyBkYXRhc2V0LCB3ZSB0cnkgYSAybmQgb3JkZXIgcG9seW5vbWlhbCBtb2RlbCB0byBzZWUgaG93IGl0IGZpdHMuIFRoZSBlcXVhdGlvbiB0aGUgbW9kZWwgZm9sbG93cyBpczoNCg0KJCQNCnRlbXAgPSBiXzAgKyBiXzEgKiB0aW1lICsgYl8yICsgdGltZV4yDQokJA0KDQpgYGB7cn0NCnBvbHlmaXQyIDwtIGxtKHRlbXAgfiBwb2x5KHRpbWUsIDIsIHJhdyA9IFRSVUUpKQ0Kc3VtbWFyeShwb2x5Zml0MikNCmBgYA0KDQpGcm9tIHRoZSBzdW1tYXJ5IG91dHB1dCBvZiB0aGUgbW9kZWwsIHlvdSBjYW4gZmluZCB0aGUgY29lZmZpY2llbnRzLCBzbyB0byBwcmVkaWN0IHRlbXAsIHlvdSBjb3VsZCB1c2U6DQoNClwkIHRlbXAgPSAtMTMuNyArIDMuNzYgXCogdGltZSAtIDAuMTM4IFwqIHRpbWVcXjIgXCQNCg0KTGlrZSBmb3IgdGhlIGZpcnN0IG9yZGVyIGxpbmVhciBtb2RlbHMsIHlvdSBjYW4gdXNlIGBnZ3Bsb3RgIHRvIGdyYXBoIHRoZSBtb2RlbC4NCg0KIyMjIFBsb3QgYSBsaW5lDQoNCmBgYHtyfQ0KZ2dwbG90KGRhdGEgPSBOVUxMLCBhZXModGltZSwgdGVtcCkpICsNCiAgZ2VvbV9wb2ludCgpICsNCiAgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxtIiwgZm9ybXVsYSA9IHkgfiBwb2x5KHgsIDIpKQ0KYGBgDQoNCiMjIFF1ZXN0aW9uICM0IGEpOg0KDQoqKkNyZWF0ZSBhIDR0aCBvcmRlciBwb2x5bm9taWFsIG1vZGVsIHdpdGggdGhlIHZhcmlhYmxlcyB0aW1lIGFuZCB0ZW1wIGZyb20gYWJvdmUgYW5kIGRpc3BsYXkgdGhlIHN1bW1hcnkgb2YgdGhlIG1vZGVsLioqDQoNCmBgYHtyfQ0KcG9seWZpdDQgPC0gbG0odGVtcCB+IHBvbHkodGltZSwgNCwgcmF3ID0gVFJVRSkpDQpzdW1tYXJ5KHBvbHlmaXQ0KQ0KYGBgDQoNCmBgYHtyfQ0KcG9seWZpdDQkY29lZmZpY2llbnRzDQpgYGANCg0KIyMgUXVlc3Rpb24gIzQgYik6DQoNCioqVXNpbmcgdGhlIHByZWRpY3RlZCBjb2VmZmljaWVudHMgZnJvbSB0aGUgc3VtbWFyeSBvdXRwdXQgZm9yIHRoZSA0dGggb3JkZXIgbW9kZWwsIHdyaXRlIGRvd24gdGhlIG1vZGVsIGVxdWF0aW9uLioqDQoNCiQkDQp0ZW1wID0gMC45NiAtIDEuNjgyICogdGltZSArIDAuNTc3ICogdGltZV4yIC0gMC4wNCAqIHRpbWVeMyArIDAuMDAwNzkgKiB0aW1lXjQNCiQkDQoNCiMjIEFzc2Vzc2luZyB0aGUgTW9kZWwNCg0KV2hlbiBldmFsdWF0aW5nIG91ciBtb2RlbHMsIG5vdCBvbmx5IGRvIHdlIHdhbnQgdG8gdmlzdWFsaXplIHRoZSByZXN1bHRzLCBidXQgd2UgYWxzbyB3YW50IGEgcXVhbnRpdGF0aXZlIG1lYXN1cmUgdG8gZGV0ZXJtaW5lIGhvdyBhY2N1cmF0ZSB0aGUgbW9kZWwgaXMuDQoNClR3byB2ZXJ5IGltcG9ydGFudCBtZWFzdXJlcyB0aGF0IGFyZSBvZnRlbiB1c2VkIGluIFN0YXRpc3RpY3MgdG8gZGV0ZXJtaW5lIHRoZSBhY2N1cmFjeSBvZiBhIG1vZGVsIGFyZToNCg0KMS4gICRSXjIkIC8gUi1zcXVhcmVkDQoNCjIuICBNZWFuIFNxdWFyZWQgRXJyb3IgKE1TRSkNCg0KKipSLXNxdWFyZWQqKg0KDQpSIHNxdWFyZWQsIGFsc28ga25vd24gYXMgdGhlIGNvZWZmaWNpZW50IG9mIGRldGVybWluYXRpb24sIGlzIGEgbWVhc3VyZSB0byBpbmRpY2F0ZSBob3cgY2xvc2UgdGhlIGRhdGEgaXMgdG8gdGhlIGZpdHRlZCByZWdyZXNzaW9uIGxpbmUuIFRoZSB2YWx1ZSBvZiB0aGUgUi1zcXVhcmVkIGlzIHRoZSBwZXJjZW50YWdlIG9mIHZhcmlhdGlvbiBvZiB0aGUgcmVzcG9uc2UgdmFyaWFibGUgKHkpIHRoYXQgaXMgZXhwbGFpbmVkIGJ5IGEgbGluZWFyIG1vZGVsLg0KDQoqKk1lYW4gU3F1YXJlZCBFcnJvciAoTVNFKSoqDQoNCiQkDQpNU0UgPSBhdmVyYWdlKChcaGF0e1l9IC0gWSleMikNCiQkDQoNCiQkDQpSTVNFID0gXHNxcnR7XGJhcntNU0V9fQ0KJCQNCg0KVGhlIE1lYW4gU3F1YXJlZCBFcnJvciBtZWFzdXJlcyB0aGUgYXZlcmFnZSBvZiB0aGUgc3F1YXJlcyBvZiBlcnJvcnMsIHRoYXQgaXMsIHRoZSBkaWZmZXJlbmNlIGJldHdlZW4gYWN0dWFsIHZhbHVlICgkWSQpIGFuZCB0aGUgZXN0aW1hdGVkIHZhbHVlICgkXGhhdHtZfSQpLiBBbm90aGVyIG1ldHJpYyB0aGF0IGlzIHJlbGF0ZWQgdG8gTVNFIGlzICoqcm9vdCBtZWFuIHNxdWFyZWQgZXJyb3IgKFJNU0UpKiogYW5kIGlzIHNpbXBseSB0aGUgc3F1YXJlIHJvb3Qgb2YgTVNFLg0KDQojIyMgTW9kZWwgMTogU2ltcGxlIExpbmVhciBSZWdyZXNzaW9uDQoNCkxldCdzIHVzZSB0aGUgc2ltcGxlIGxpbmVhciByZWdyZXNzaW9uIG1vZGVsIHdlIGNyZWF0ZWQgcHJldmlvdXNseS4NCg0KVXNpbmcgdGhpcyBtb2RlbCwgeW91IGNhbiBjYWxjdWxhdGUgTVNFIGFuZCBSTVNFLiBGcm9tIGJlbG93LCBNU0UgaXMgMzk0IGFuZCBSTVNFIGlzIDE5Ljg1Lg0KDQpgYGB7cn0NCm1zZSA8LSBtZWFuKGxpbmVhcl9tb2RlbCRyZXNpZHVhbHNeMikNCm1zZQ0KYGBgDQoNCmBgYHtyfQ0Kcm1zZSA8LSBzcXJ0KG1zZSkNCnJtc2UNCmBgYA0KDQokUl4yJCBjYW4gYmUgb2J0YWluZWQgZnJvbSB0aGUgc3VtbWFyeSBvZiB0aGUgbW9kZWwuIEZyb20gdGhlIG91dHB1dCBiZWxvdywgd2UgY2FuIHNheSB0aGF0IGFwcHJveGltYXRlbHkgNzUuOSUgb2YgdGhlIHZhcmlhdGlvbiBvZiBwcmljZSBpcyBleHBsYWluZWQgYnkgdGhpcyBzaW1wbGUgbGluZWFyIG1vZGVsLg0KDQpgYGB7cn0NCnN1bW1hcnkobGluZWFyX21vZGVsKSRyLnNxdWFyZWQNCmBgYA0KDQojIyMgTW9kZWwgMjogTXVsdGlwbGUgTGluZWFyIFJlZ3Jlc3Npb24NCg0KTmV4dCwgbGV0J3MgdXNlIHRoZSBtdWx0aXBsZSBsaW5lYXIgcmVncmVzc2lvbiBtb2RlbCB3ZSBjcmVhdGVkIGluIHNlY3Rpb24gMy4NCg0KYGBge3J9DQptbHMgPC0gbG0oQXJyRGVsYXlNaW51dGVzIH4gRGVwRGVsYXlNaW51dGVzICsgTGF0ZUFpcmNyYWZ0RGVsYXksIGRhdGEgPSBhYV9kZWxheXMpDQpgYGANCg0KTGV0J3MgY2FsY3VsYXRlIE1TRSBhbmQgUk1TRS4gRnJvbSBiZWxvdywgTVNFIGlzIDM5NCBhbmQgUk1TRSBpcyAxOS44NDkuDQoNCmBgYHtyfQ0KbXNlX21sciA8LSBtZWFuKG1sciRyZXNpZHVhbHNeMikNCm1zZV9tbHINCmBgYA0KDQpgYGB7cn0NCnJtc2VfbWxyIDwtIHNxcnQobXNlX21scikNCnJtc2VfbWxyDQpgYGANCg0KRnJvbSB0aGUgci1zcXVhcmVkIHZhbHVlIGJlbHdvLCB3ZSBjYW4gc2F5IHRoYXQgYXBwcm94aW1hdGVseSA3NS45ICUgb2YgdGhlIHZhcmlhdGlvbiBvZiBBcnJpdmFsIERlbGF5IE1pbnV0ZXMgaXMgZXhwbGFpbmVkIGJ5IHRoaXMgbXVsdGlwbGUgbGluZWFyIHJlZ3Jlc3Npb24gIm1sciIuDQoNCmBgYHtyfQ0Kc3VtbWFyeShtbHIpJHIuc3F1YXJlZA0KYGBgDQoNCiMjIyBNb2RlbCAzOiBQb2x5bm9taWFsIFJlZ3Jlc3Npb24NCg0KRmluYWxseSwgd2UgY2FuIHVzZSBhIHBvbHlub21pYWwgcmVncmVzc2lvbiBtb2RlbCB1c2luZyB0aGUgc2tpbGxzIGZyb20gc2VjdGlvbiA1Lg0KDQpgYGB7cn0NCnBvbHlfcmVnIDwtIGxtKEFyckRlbGF5TWludXRlcyB+IHBvbHkoRGVwRGVsYXlNaW51dGVzLCAzKSwgZGF0YSA9IGFhX2RlbGF5cykNCmBgYA0KDQpTaW1pbGFyIHRvIG1vZGVsIDEgYW5kIDIsIHlvdSBjYW4gZmluZCBNU0UsIFJNU0UsIGFuZCBSXF4yLiBIZXJlIHRoZSBNU0UgaXMgMzI4Ljk3LCBSTVNFIGlzIDE5Ljg1LCBhbmQgUlxeMiBpcyAwLjc5OC4NCg0KYGBge3J9DQptc2VfcG9seSA8LSBtZWFuKHBvbHlfcmVnJHJlc2lkdWFsc14yKQ0KbXNlX3BvbHkNCmBgYA0KDQpgYGB7cn0NCnJtc2VfcG9seSA8LSBzcXJ0KG1zZSkNCnJtc2VfcG9seQ0KYGBgDQoNCmBgYHtyfQ0Kc3VtbWFyeShwb2x5X3JlZykkci5zcXVhcmVkDQpgYGANCg0KIyMgUHJlZGljdGlvbiBhbmQgRGVjaXNpb24gTWFraW5nDQoNCiMjIyBQcmVkaWN0aW9uDQoNClByZXZpb3VzbHksIHdlIHRyYWluZWQgdGhlIG1vZGVsIHVzaW5nIHRoZSBtZXRob2QgYGxtKClgIGFuZCB3ZSB1c2VkIHRoZSBtZXRob2QgYHByZWRpY3QoKWAgdG8gcHJvZHVjZSBhIHByZWRpY3Rpb24uDQoNCmBgYHtyfQ0KaGVhZChwcmVkaWN0KHNjb3JlX21vZGVsKSkNCmBgYA0KDQojIyMgRGVjaXNpb24gTWFraW5nOiBEZXRlcm1pbmluZyBhIEdvb2QgTW9kZWwgRml0DQoNCk5vdyB0aGF0IHdlIGhhdmUgdmlzdWFsaXplZCB0aGUgZGlmZmVyZW50IG1vZGVscywgYW5kIGdlbmVyYXRlZCB0aGUgUi1zcXVhcmVkIGFuZCBNU0UgdmFsdWVzIGZvciB0aGUgZml0cywgaG93IGRvIHdlIGRldGVybWluZSBhIGdvb2QgbW9kZWwgZml0Pw0KDQotICAgV2hhdCBpcyBhIGdvb2QgKipSLXNxdWFyZWQqKiB2YWx1ZT8NCg0KICAgIC0gICBXaGVuIGNvbXBhcmluZyBtb2RlbHMsIHRoZSBtb2RlbCB3aXRoIHRoZSAqaGlnaGVyIFItc3F1YXJlZCogdmFsdWUgaXMgYSBiZXR0ZXIgZml0IGZvciB0aGUgZGF0YS4NCg0KLSAgIFdoYXQgaXMgYSBnb29kICoqTVNFKio/DQoNCiAgICAtICAgV2hlbiBjb21wYXJpbmcgbW9kZWxzLCB0aGUgbW9kZWwgd2l0aCB0aGUgKnNtYWxsZXN0IE1TRSogdmFsdWUgaXMgYSBiZXR0ZXIgZml0IGZvciB0aGUgZGF0YQ0KDQojIyMjIExldCdzIHRha2UgYSBsb29rIGF0IHRoZSB2YWx1ZXMgZm9yIHRoZSBkaWZmZXJlbnQgbW9kZWxzLg0KDQpTaW1wbGUgTGluZWFyIFJlZ3Jlc3Npb246IFVzaW5nIERlcERlbGF5TWludXRlcyBhcyBhIFByZWRpY3RvciBWYXJpYWJsZSBvZiBBcnJEZWxheU1pbnV0ZXMuDQoNCi0gICBSLXNxdWFyZWQ6IDAuNzU4OA0KDQotICAgTVNFOiAzOTQuMDYNCg0KTXVsdGlwbGUgTGluZWFyIFJlZ3Jlc3Npb246IFVzaW5nIERlcERlbGF5TWludXRlcyBhbmQgTGF0ZUFpcmNyYWZ0RGVsYXkgYXMgUHJlZGljdG9yIFZhcmlhYmxlcyBvZiBBcnJEZWxheU1pbnV0ZXMuDQoNCi0gICBSLXNxdWFyZWQ6IDAuNzU4ODMNCg0KLSAgIE1TRTogMzk0LjAxMQ0KDQpQb2x5bm9taWFsIEZpdDogVXNpbmcgM3JkIE9kZXIgUG9seW5vbWlhbCBvZiBEZXBEZWxheU1pbnV0ZXMgYXMgYSBQcmVkaWN0b3IgVmFyaWFibGUgb2YgQXJyRGVsYXlNaW51dGVzDQoNCi0gICBSLXNxdWFyZWQ6IDAuNzk4Ng0KDQotICAgTVNFOiAzMjguOTcwDQoNCiMjIyBTaW1wbGUgTGluZWFyIFJlZ3Jlc3Npb24gbW9kZWwgKFNMUikgdnMgTXVsdGlwbGUgTGluZWFyIFJlZ3Jlc3Npb24gbW9kZWwgKE1MUikNCg0KVXN1YWxseSwgdGhlIG1vcmUgdmFyaWFibGVzIHlvdSBoYXZlLCB0aGUgYmV0dGVyIHlvdXIgbW9kZWwgaXMgYXQgcHJlZGljdGluZywgYnV0IHRoaXMgaXMgbm90IGFsd2F5cyB0cnVlLiBTb21ldGltZXMgeW91IG1heSBub3QgaGF2ZSBlbm91Z2ggZGF0YSwgeW91IG1heSBydW4gaW50byBudW1lcmljYWwgcHJvYmxlbXMsIG9yIG1hbnkgb2YgdGhlIHZhcmlhYmxlcyBtYXkgbm90IGJlIHVzZWZ1bCBhbmQgb3IgZXZlbiBhY3QgYXMgbm9pc2UuIEFzIGEgcmVzdWx0LCB5b3Ugc2hvdWxkIGFsd2F5cyBjaGVjayB0aGUgTVNFIGFuZCBSXF4yLg0KDQpTbyB0byBiZSBhYmxlIHRvIGNvbXBhcmUgdGhlIHJlc3VsdHMgb2YgdGhlIE1MUiB2cyBTTFIgbW9kZWxzLCB3ZSBsb29rIGF0IGEgY29tYmluYXRpb24gb2YgYm90aCB0aGUgUi1zcXVhcmVkIGFuZCBNU0UgdG8gbWFrZSB0aGUgYmVzdCBjb25jbHVzaW9uIGFib3V0IHRoZSBmaXQgb2YgdGhlIG1vZGVsDQoNCi0gICBNU0U6IFRoZSBNU0Ugb2YgU0xSIG1vZGVsIGlzIDM5NC4wNjMgd2hpbGUgTUxSIGhhcyBhbiBNU0Ugb2YgMzk0LjAxMTMuIFRoZSBNU0Ugb2YgTUxSIG1vZGVsIGlzIGV2ZXIgc2xpZ2h0bHkgc21hbGxlci4NCg0KLSAgIFItc3F1YXJlZDogSW4gdGhpcyBjYXNlLCB3ZSBjYW4gc2VlIHRoYXQgdGhlIFItc3F1YXJlZCBmb3IgdGhlIFNMUiBpcyBhIGxpdHRsZSBsb3dlciB0aGFuIHRoZSBSLXNxdWFyZWQgZm9yIHRoZSBNTFIgbW9kZWwuDQoNClRoaXMgUi1zcXVhcmVkIGluIGNvbWJpbmF0aW9uIHdpdGggdGhlIE1TRSBzaG93IHRoYXQgTUxSIHNlZW1zIGxpa2UgYSBzbGlnaHRseSBiZXR0ZXIgbW9kZWwgZml0IGluIHRoaXMgY2FzZSwgY29tcGFyZWQgdG8gU0xSLiBIb3dldmVyLCB5b3UgY291bGQgdHJ5IGFkZGluZyBtb3JlIHByZWRpY3RvciB2YXJpYWJsZXMgaW4gdGhlIE1MUiBtb2RlbCB0byBzZWUgaWYgdGhhdCBtYWRlIGEgYmlnZ2VyIGltcHJvdmVtZW50IHNpbmNlIGluIG91ciBleGFtcGxlIG9ubHkgdHdvIHdlcmUgdXNlZC4NCg0KIyMjIFNpbXBsZSBMaW5lYXIgTW9kZWwgKFNMUikgdnMgUG9seW5vbWlhbCBGaXQNCg0KLSAgICoqTVNFKio6IFdlIGNhbiBzZWUgdGhhdCBQb2x5bm9taWFsIG1vZGVsIGJyb3VnaHQgZG93biB0aGUgTVNFLCBzaW5jZSB0aGlzIE1TRSBpcyBzbWFsbGVyIHRoYW4gdGhlIG9uZSBmcm9tIHRoZSBTTFIuDQoNCi0gICAqKlItc3F1YXJlZCoqOiBUaGUgUi1zcXVhcmVkIGZvciB0aGUgUG9seWZpdCBpcyBsYXJnZXIgdGhhbiB0aGUgUi1zcXVhcmVkIGZvciB0aGUgU0xSLCBzbyB0aGUgUG9seW5vbWlhbCBGaXQgYWxzbyBicm91Z2h0IHVwIHRoZSBSLXNxdWFyZWQgcXVpdGUgYSBiaXQuDQoNClNpbmNlIHRoZSBQb2x5bm9taWFsIEZpdCByZXN1bHRlZCBpbiBhIGxvd2VyIE1TRSBhbmQgYSBoaWdoZXIgUi1zcXVhcmVkLCB3ZSBjYW4gY29uY2x1ZGUgdGhhdCB0aGlzIHdhcyBhIGJldHRlciBmaXQgbW9kZWwgdGhhbiB0aGUgc2ltcGxlIGxpbmVhciByZWdyZXNzaW9uIGZvciBwcmVkaWN0aW5nIEFyckRlbGF5TWludXRlcy4NCg0KIyMjIE11bHRpcGxlIExpbmVhciBSZWdyZXNzaW9uIChNTFIpIHZzIFBvbHlub21pYWwgRml0DQoNCi0gICAqKk1TRSoqOiBUaGUgTVNFIGZvciB0aGUgcG9seW5vbWlhbCBtb2RlbCBpcyBzbWFsbGVyIHRoYW4gdGhlIE1TRSBmb3IgdGhlIE1MUiBtb2RlbC4NCg0KLSAgICoqUi1zcXVhcmVkKio6IFRoZSBSLXNxdWFyZWQgZm9yIHRoZSBwb2x5bm9taWFsIG1vZGVsIGlzIGFsc28gbGFyZ2VyIHRoYW4gdGhlIE1MUiBtb2RlbCdzLg0KDQojIyBDb25jbHVzaW9uOg0KDQpDb21wYXJpbmcgdGhlc2UgdGhyZWUgbW9kZWxzLCB0aGUgTUxSIG1vZGVsIHBlcmZvcm1zIHNsaWdodGx5IGJldHRlciB0aGFuIHRoZSBTTFIgbW9kZWwuIFBlcmhhcHMgaWYgd2UgdHJpZWQgYWRkaW5nIHNvbWUgbW9yZSBwcmVkaWN0b3IgdmFyaWFibGVzLCB0aGUgTUxSIG1vZGVsIGNvdWxkIGRvIGV2ZW4gYmV0dGVyLiBPZiB0aGUgdGhyZWUgbW9kZWxzLCB3ZSBjb25jbHVkZSB0aGF0IHRoZSAqcG9seW5vbWlhbCBvZiBvcmRlciAzIG1vZGVsKiBzZWVtcyB0byBiZSB0aGUgYmVzdCBmaXQgaXQgYXMgaXQgaGFzIHRoZSBoaWdoZXN0IFJcXjIgYW5kIHRoZSBsb3dlc3QgTVNFLg0K