INTRODUCTION

In this R project, we will embark on a two-phase data analysis journey. The initial phase centers on the application of simple linear regression models, followed by the second phase, which involves the utilization of k-nearest neighbors modeling. The objective is to investigate datasets pertaining to tech industry salaries and Spotify music information, with the ultimate aim of deriving meaningful insights and generating predictive outcomes. To initiate the process, we will load the essential libraries and ready the data for analysis.

Load necessary packages:

library(ggplot2)
library(forecast)
library(dplyr)
library(caret)
library(FNN)

PART I - SIMPLE LINEAR REGRESSION

In the first part of our analysis, we delve into the world of Simple Linear Regression (SLR). Our journey begins by preparing the data, exploring its nuances, and understanding the relationships between variables. We aim to uncover insights into the tech industry’s compensation dynamics and build a predictive model that can estimate total yearly compensation based on years of experience. Through careful examination and statistical analysis, we shed light on the correlation between these factors and unveil the model’s performance in predicting compensation. Join us as we navigate the intricacies of SLR to gain valuable insights that benefit HR professionals, hiring managers, and tech industry employees.

1. Data Preparation

The initial step involved loading the necessary packages and importing the dataset, “tech_salary_dataset.csv,” into an R data frame named “salary.df.” This ensured that our data was ready for further analysis.

# Load dataset
salary.df <- read.csv("tech_salary_dataset.csv")

head(salary.df)  # Return first few rows 

Data Exploration

Our data exploration phase included an examination of the dataset’s structure, identifying both numeric and categorical variables. This section provided an overview of the dataset’s key characteristics.

# View structure of dataset
str(salary.df)
'data.frame':   62642 obs. of  24 variables:
 $ timestamp              : chr  "6/7/2017 11:33:27" "6/10/2017 17:11:29" "6/11/2017 14:53:57" "6/17/2017 0:23:14" ...
 $ company                : chr  "Oracle" "eBay" "Amazon" "Apple" ...
 $ level                  : chr  "L3" "SE 2" "L7" "M1" ...
 $ title                  : chr  "Product Manager" "Software Engineer" "Product Manager" "Software Engineering Manager" ...
 $ totalyearlycompensation: int  127000 100000 310000 372000 157000 208000 300000 156000 120000 201000 ...
 $ location               : chr  "Redwood City, CA" "San Francisco, CA" "Seattle, WA" "Sunnyvale, CA" ...
 $ yearsofexperience      : num  1.5 5 8 7 5 8.5 15 4 3 12 ...
 $ yearsatcompany         : num  1.5 3 0 5 3 8.5 11 4 1 6 ...
 $ tag                    : chr  NA NA NA NA ...
 $ basesalary             : int  107000 0 155000 157000 0 0 180000 135000 0 157000 ...
 $ stockgrantvalue        : num  20000 0 0 180000 0 0 65000 8000 0 26000 ...
 $ bonus                  : num  10000 0 0 35000 0 0 55000 13000 0 28000 ...
 $ gender                 : chr  NA NA NA NA ...
 $ otherdetails           : chr  NA NA NA NA ...
 $ cityid                 : int  7392 7419 11527 7472 7322 11527 11521 11527 11521 11527 ...
 $ dmaid                  : int  807 807 819 807 807 819 819 819 819 819 ...
 $ rowNumber              : int  1 2 3 7 9 11 12 13 15 16 ...
 $ Masters_Degree         : int  0 0 0 0 0 0 0 0 0 0 ...
 $ Bachelors_Degree       : int  0 0 0 0 0 0 0 0 0 0 ...
 $ Doctorate_Degree       : int  0 0 0 0 0 0 0 0 0 0 ...
 $ Highschool             : int  0 0 0 0 0 0 0 0 0 0 ...
 $ Some_College           : int  0 0 0 0 0 0 0 0 0 0 ...
 $ Race                   : chr  NA NA NA NA ...
 $ Education              : chr  NA NA NA NA ...

The numerical variables in the dataset include: total yearly compensation, total years of experience, years at the company, base salary, stock grant value and bonus.

On the other hand, the categorical variables in the dataset include: timestamp, company, level, title, location, tag, gender, other details, cityid, dmaid, row number, masters degree, bachelors degree, doctorate degree, highschool, some college, race, and education.

Data Partitioning

Before conducting deeper analysis, the data was partitioned into training and validation sets. This crucial step ensures the model’s relevance and accuracy.

# Set seed for replicability of sample/output
set.seed(80)  

# Sample 60% of dataset & assign it to index for training set
train.index <- sample(row.names(salary.df), 0.6*dim(salary.df)[1])

# Assign remaining rows to index for validation set
valid.index <- setdiff(row.names(salary.df), train.index)  

# Subset all of the rows for all the columns associated with the training data/validation data indexes
train.df <- salary.df[train.index, ]
valid.df <- salary.df[valid.index, ]

It is important to partition the data before doing any sort of in-depth analysis of the variables in order to ensure the model is relevant to the data (i.e., the training data, which is used to fit the model); and performs accurately in classifying/predicting new records (i.e., using validation data to ensure the model was not “overfit” to the training data).

EDA - Data Visualization + Summary Stats

Exploratory data analysis involved data visualization and summary statistics. We created scatterplots, calculated correlations, and interpreted the relationships between variables.

# Create a scatterplot of yearsofexperience vs. totalyearlycompensation
ggplot(data= train.df, aes(x= yearsofexperience, y= totalyearlycompensation)) + geom_point() + labs(title = "Yearly Compensation vs. Years of Experience", x= "Years of Experience", y= "Total Yearly Compensation") + geom_smooth(method = "lm")


# the geom_smooth() function allows us to add a best-fit line

This plot suggests that the two variables have a moderate positive correlation. This is evident based on the close proximity of the points to the best-fit line (i.e., given a few potential outliers) and suggests that total yearly compensation will increase as a tech employee’s number of years of experience in the industry increases. Ultimately, this relationship makes intuitive sense to me because typically the longer you stay in a certain industry (e.g., in a certain position or at a certain company), the more knowledgeable/qualified you will be. As a consequence, more likely you are to receive a higher compensation in the following year/period – due to experience. Likewise, it is not usually common for an employee’s salary to decrease over time, especially when they are holding the same position/gaining more experience over time.

# Calculate the correlation between yearsofexperience & totalyearlycompensation
cor(x= train.df$yearsofexperience, y= train.df$totalyearlycompensation)
[1] 0.4263163
# Compute significance of correlation
cor.test(x= train.df$yearsofexperience, y= train.df$totalyearlycompensation)

    Pearson's product-moment correlation

data:  train.df$yearsofexperience and train.df$totalyearlycompensation
t = 91.366, df = 37583, p-value < 2.2e-16
alternative hypothesis: true correlation is not equal to 0
95 percent confidence interval:
 0.4180081 0.4345532
sample estimates:
      cor 
0.4263163 

The correlation observed between “yearsofexperience” and “totalyearlycompensation” in the training dataset is approximately 0.4263. This value suggests a moderate positive correlation, indicating that as years of experience increase, total yearly compensation tends to rise as well.

When subjected to statistical analysis using Pearson’s correlation test, the results confirm the significance of this relationship. The remarkably low p-value (less than 2.2e-16) unequivocally demonstrates that the observed correlation is highly meaningful. It provides compelling evidence that the connection between years of experience and total yearly compensation is not merely a random occurrence.

Furthermore, the 95 percent confidence interval for the correlation, ranging from 0.4180 to 0.4346, narrows down the likely range within which the true correlation resides. This not only reinforces the concept of a substantial and consistent link between these two variables but also enhances our confidence in the findings.

While the correlation may not be exceptionally strong, it is statistically significant and highlights a meaningful connection within the dataset. In essence, as individuals accumulate more years of experience, their total yearly compensation tends to exhibit a discernible upward trend, as substantiated by the data and statistical analysis.

Linear Regression Model

In this next step a simple linear regression model is constructed to predict total yearly compensation based on years of experience. The model’s summary and a hypothetical input prediction were included to illustrate its functionality.

# Create SLR model
model <- lm(totalyearlycompensation ~ yearsofexperience, data = train.df)

# Generate model summary
summary(model)

Call:
lm(formula = totalyearlycompensation ~ yearsofexperience, data = train.df)

Residuals:
    Min      1Q  Median      3Q     Max 
-744515  -71070  -14103   45638 4663543 

Coefficients:
                  Estimate Std. Error t value Pr(>|t|)    
(Intercept)       143168.5     1036.0  138.20   <2e-16 ***
yearsofexperience  10193.4      111.6   91.37   <2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 126600 on 37583 degrees of freedom
Multiple R-squared:  0.1817,    Adjusted R-squared:  0.1817 
F-statistic:  8348 on 1 and 37583 DF,  p-value: < 2.2e-16

Model Evaluation

In the following section, we evaluated the model’s performance using residuals analysis, highlighting instances where the model’s predictions deviated significantly from actual values.

# Generate summary of model residuals
summary(model$residuals)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
-744515  -71070  -14103       0   45638 4663543 

In this model, the minimum residual value is -744,515 & the maximum residual value is 4,663,543.

# Create df with fitted values (predictions) & residuals
residual.df <- data.frame("Residuals" = model$residuals, "Predicted Total Comp" = model$fitted.values)

# Bind the 'residual.df' to the df with our training data
train.df <- cbind(train.df, residual.df)

# Returns first few rows of data to ensure successful binding of the 2 dataframes
head(train.df)  
# Identify the observation whose rating generated the highest residual value
subset(train.df, Residuals == max(train.df$Residuals))

Based on the observation whose rating generated the highest residual value, the person’s actual total compensation was 4,980,000 (dollars); while the model predicted it would be 316,456.80 (dollars). The residual is calculated by taking the difference between the actual total compensation for each employee and what the regression model predicted it to be (i.e., 4980000 - 316456.80 = 4663543.30).

# Identify the observation whose rating generated the lowest residual value
subset(train.df, Residuals == min(train.df$Residuals))

Based on the observation whose rating generated the lowest residual value, the person’s actual total compensation was 102,000 (dollars); while the model predicted it would be 846,515.10 (dollars). The residual is calculated by taking the difference between the actual total compensation for each employee and what the regression model predicted it to be (i.e., 102000 - 846515.10 = -744,515.10).

It looks like there are some cases where this model is quite a bit “off the mark”. Although intuitively it makes sense that years of experience could be a factor in the total compensation an employee in the high tech industry receives, there a few possible reasons why years of experience might not closely predict total compensation. Specifically, it is possible that “years of experience” is correlated with one or more of the other input variables in the model, which exposes the model to the dangers of multi-collinearity. There is also the case where variable reduction is the focus; in which there are just “better” input variables that act as predictors for total compensation – with years of experience being one of the potential variables under the scope for elimination/being “dropped.” For example, based on the description of each of the variables in the original dataset, we know that total yearly compensation is the sum of several other numerical values - including base salary, bonus, & stock grant value.

Summary of Linear Regression Equation

summary(model)  # Return model summary to obtain coefficients (m, slope & b, y-intercept)

Call:
lm(formula = totalyearlycompensation ~ yearsofexperience, data = train.df)

Residuals:
    Min      1Q  Median      3Q     Max 
-744515  -71070  -14103   45638 4663543 

Coefficients:
                  Estimate Std. Error t value Pr(>|t|)    
(Intercept)       143168.5     1036.0  138.20   <2e-16 ***
yearsofexperience  10193.4      111.6   91.37   <2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 126600 on 37583 degrees of freedom
Multiple R-squared:  0.1817,    Adjusted R-squared:  0.1817 
F-statistic:  8348 on 1 and 37583 DF,  p-value: < 2.2e-16

Therefore, the regression equation generated by the model is: Total yearly compensation (y) = 10193.4(x) + 143168.5

Make up a hypothetical input value and use it to make a new prediction.

new_input <- 10

total_yearly_compensation <- function(x){
  return(10193.4*x + 143168.5)
}
total_yearly_compensation(10)
[1] 245102.5

In sum, ‘10’ was used as the hypothetical input value for “yearsofexperience” in the regression equation to predict as an outcome, the total yearly compensation of an employee with 10 years of experience in the high tech industry. As such, for an employee who has 10 years of experience in the high tech industry, the predicted total yearly compensation is 245,102.50 (dollars).

Training set:

accuracy(train.df$Predicted.Total.Comp, train.df$totalyearlycompensation)
                   ME     RMSE      MAE       MPE     MAPE
Test set 1.974391e-12 126604.5 82928.08 -39.05618 60.69246

Validation Set:

salary.pred <- predict(model, valid.df)
accuracy(salary.pred, valid.df$totalyearlycompensation)
               ME     RMSE      MAE       MPE     MAPE
Test set -757.419 122780.7 82832.89 -40.28764 62.02053

The purpose of making this comparison is to ensure the model performs well in predicting new records (i.e., evaluating its predictive accuracy). This means that the model is not overfit to the predictor values in the training set (i.e., when applied to new, unknown records).

In particular, the root-mean-squared error (RMSE) tells us about the “error magnitude” in units that correspond to the output (e.g., total yearly compensation); while the mean absolute error (MAE) tells us about the magnitude of the average absolute error. These prediction accuracy measures corresponding to the training data tell us about model fit when comparing the predicted/actual values of total yearly compensation; while the prediction accuracy measures that correspond to the validation set communicate the model’s ability to predict new records that are unfamiliar to the model/training data. In general, RMSE/MAE should be lower for the training data than the validation data, as the training data was used to fit the regression model (i.e., with low error magnitude indicating better fit).

However, even with low RMSE and MAE on training data, bias may persist - systematic errors that consistently push predictions in one direction. To detect bias, the RMSE and MAE are evaluated on the validation set. If they significantly differ from training data, it may indicate bias. In essence, this comparison ensures the model’s generalization and detects potential bias that might not be apparent solely from training data.

Model Comparison

Finally, we compared the model’s root-mean-squared error (RMSE) to the standard deviation of total compensation in the training set, providing insights into its performance.

sd(train.df$totalyearlycompensation)
[1] 139962.2

The model’s RMSE is less than the standard deviation of total compensation in the training set. Comparing our model’s RMSE to the standard deviation of total compensation reveals key insights into its performance.

In essence, when the RMSE exceeds or equals the standard deviation, it signals that our model’s predictions are no better than a “null” model that merely guesses the mean. This suggests the model isn’t effectively learning from the input features and lacks meaningful predictive power.

Conversely, a lower RMSE compared to the standard deviation indicates that the model is outperforming this basic baseline. It demonstrates the model’s ability to capture valuable patterns and make more accurate predictions.

In summary, this comparison not only gauges predictive accuracy but also highlights whether the model adds value beyond a simplistic approach.

Conclusions (SLR)

In this section, we distill the key insights gained from our analysis and discuss their relevance to various stakeholders. These insights shed light on the relationship between years of experience and total yearly compensation in the tech industry and offer practical implications for decision-makers.

Key Insights
  • Moderate Positive Correlation: Our analysis revealed a moderate positive correlation between years of experience and total yearly compensation, indicating that, on average, employees earn higher compensation as they gain more experience.

  • Statistical Significance: Statistical tests confirmed the significance of this correlation, providing robust evidence that the relationship is not random.

  • Linear Regression Model: The linear regression model provided a quantitative representation of the relationship, facilitating salary predictions based on years of experience.

  • Model Performance: Our model demonstrated good performance in predicting compensation, as indicated by low RMSE and MAE values on both training and validation datasets.

  • Bias Detection: While generally performing well, the model exhibited bias in some cases, highlighting areas for potential improvement.

  • Model Value: Comparing the RMSE to the standard deviation demonstrated that the model adds value by making more accurate predictions than a mean-based model.

Stakeholder Implications
  • HR Departments: HR professionals can utilize the linear regression model to estimate compensation for potential hires, aiding in competitive job offers.

  • Hiring Managers: Understanding the relationship between experience and compensation equips hiring managers to make informed decisions during the hiring process.

  • Employees: Current employees can gain insights into their potential career trajectories and financial goals based on how their compensation may evolve.

  • Data Analysts: The provided code and process serve as a template for data analysts and data scientists to conduct similar analyses in their organizations, exploring relationships between variables and building predictive models.

In conclusion, this analysis provides valuable insights into tech industry compensation dynamics and offers a practical model for predicting salaries based on experience, benefiting a range of stakeholders in decision-making roles.

PART II - K-NEAREST NEIGHBORS

In the second part of our analysis, we venture into the realm of K-Nearest Neighbors (k-NN) modeling. Our exploration begins by selecting a song from the Spotify Top 200 Weekly Charts of 2022 and dissecting its attributes. We leverage k-NN to predict whether this song will be liked by a user, George, and uncover its nearest musical companions. Along the way, we optimize the k-value for our model, reduce dimensionality for efficiency, and normalize the data for equitable comparisons. This section not only showcases the power of k-NN in music recommendation but also serves as a blueprint for data analysts and stakeholders aiming to enhance user experiences and decision-making in the dynamic world of music data analysis. Join us as we harmonize data science and music to predict likability and uncover the secrets of k-NN.

Load Data

The code begins by loading the ‘spotify_top_charts_22.csv’ dataset, which contains information about songs that appeared on the Top 200 Weekly (Global) charts of Spotify in 2022. Each row represents a specific song.

top.charts.df <- read.csv("spotify_top_charts_22.csv")

top.charts.df 

Song Selection

First, we will select a song for analysis.

I chose the song “Cold Heart” by Elton John, Dua Lipa, & PNAU (row 6).

top.charts.df[6, ]  # returns the row for the song "Cold Heart"

Song Information

Various attributes of the selected song are queried, such as danceability, energy, loudness, speechiness, acousticness, instrumentalness, liveness, tempo, and duration_ms.

danceability:

top.charts.df[6, "danceability"]
[1] 0.795

energy:

top.charts.df[6, "energy"]
[1] 0.8

loudness:

top.charts.df[6, "loudness"]
[1] -6.32

speechiness:

top.charts.df[6, "speechiness"]
[1] 0.0309

acousticness:

top.charts.df[6, "acousticness"]
[1] 0.0354

instrumentalness:

options(scipen = 999)
top.charts.df[6, "instrumentalness"]
[1] 0.0000725

liveness:

top.charts.df[6, "liveness"]
[1] 0.0915

tempo:

top.charts.df[6, "tempo"]
[1] 116.032

duration_ms:

top.charts.df[6, "duration_ms"]
[1] 202735

Data Exploration

In this step, we explore the datasets & examine the target variable.

# Subset selected song
song.df <- top.charts.df[6, ]

# Examine structure of dataset
str(song.df)  
'data.frame':   1 obs. of  17 variables:
 $ uri             : chr "spotify:track:7rglLriMNBPAyuJOMGwi39"
 $ artist_names    : chr "Elton John, Dua Lipa, PNAU"
 $ track_name      : chr "Cold Heart - PNAU Remix"
 $ peak_rank       : int 4
 $ weeks_on_chart  : int 32
 $ danceability    : num 0.795
 $ energy          : num 0.8
 $ key             : int 1
 $ loudness        : num -6.32
 $ mode            : int 1
 $ speechiness     : num 0.0309
 $ acousticness    : num 0.0354
 $ instrumentalness: num 0.0000725
 $ liveness        : num 0.0915
 $ tempo           : num 116
 $ time_signature  : int 4
 $ duration_ms     : int 202735

Read Additional Dataset

A second dataset, ‘spotify_data.csv,’ is read into the environment, and its structure is examined. It contains ratings of various songs, with the ‘target’ variable indicating whether George liked the song (1 for liked, 0 for disliked).

# Read dataset containing George's ratings of various songs into environment
spotify.df <- read.csv("spotify_data.csv")

# Examine structure of dataset
str(spotify.df)
'data.frame':   2017 obs. of  17 variables:
 $ X               : int  0 1 2 3 4 5 6 7 8 9 ...
 $ acousticness    : num  0.0102 0.199 0.0344 0.604 0.18 0.00479 0.0145 0.0202 0.0481 0.00208 ...
 $ danceability    : num  0.833 0.743 0.838 0.494 0.678 0.804 0.739 0.266 0.603 0.836 ...
 $ duration_ms     : int  204600 326933 185707 199413 392893 251333 241400 349667 202853 226840 ...
 $ energy          : num  0.434 0.359 0.412 0.338 0.561 0.56 0.472 0.348 0.944 0.603 ...
 $ instrumentalness: num  0.0219 0.00611 0.000234 0.51 0.512 0 0.00000727 0.664 0 0 ...
 $ key             : int  2 1 2 5 5 8 1 10 11 7 ...
 $ liveness        : num  0.165 0.137 0.159 0.0922 0.439 0.164 0.207 0.16 0.342 0.571 ...
 $ loudness        : num  -8.79 -10.4 -7.15 -15.24 -11.65 ...
 $ mode            : int  1 1 1 1 0 1 1 0 0 1 ...
 $ speechiness     : num  0.431 0.0794 0.289 0.0261 0.0694 0.185 0.156 0.0371 0.347 0.237 ...
 $ tempo           : num  150.1 160.1 75 86.5 174 ...
 $ time_signature  : num  4 4 4 4 4 4 4 4 4 4 ...
 $ valence         : num  0.286 0.588 0.173 0.23 0.904 0.264 0.308 0.393 0.398 0.386 ...
 $ target          : int  1 1 1 1 1 1 1 1 1 1 ...
 $ song_title      : chr  "Mask Off" "Redbone" "Xanny Family" "Master Of None" ...
 $ artist          : chr  "Future" "Childish Gambino" "Future" "Beach House" ...

The variable ‘target’ is a categorical variable as it tells us whether or not George liked the song (i.e., 1 = yes he liked the song, 0 = he did not like the song). Since the categories are communicated using binary dummy variables, the data structure is integer or numeric form.

If target is not currently a factor, convert it into a factor. It will be our response variable in this model. Target tells us whether George, the person who uploaded this dataset, liked the song. “1” means that George liked it, and “0” means that he did not.

spotify.df$target <- as.factor(spotify.df$target)
# Return table with unique values & the total count of observations corresponding to each of those unique values
table(spotify.df$target)  

   0    1 
 997 1020 

The unique values are “0” (i.e., George did not like the song) & “1” (i.e., George did like the song). There are 997 songs he disliked and 1020 that he liked.

Handling Missing Values

Next, we check for missing values in the dataset and find that there are none.

cat("There are", sum(is.na(spotify.df)), "missing values in the dataset.")
There are 0 missing values in the dataset.

Dimension Reduction

Unnecessary columns (e.g., ‘key’, ‘mode’, ‘time_signature’) are removed from the dataset, aiming to simplify the k-nearest neighbors (k-NN) model and improve its efficiency.

# Remove unnecessary columns (e.g., 'key', 'mode', 'time_signature')
spotify.df <- spotify.df[ , -c(7, 10, 13)]

head(spotify.df)  # Print first few rows to ensure columns were removed

Data Partitioning

The dataset is split into training and validation sets to train and evaluate the k-NN model.

# Set seed for replicability
set.seed(80)

# Partition dataset into training & validation sets
train.index <- sample(c(1:nrow(spotify.df)), nrow(spotify.df)*0.6)
train.df <- spotify.df[train.index, ]
valid.df <- spotify.df[-train.index, ]

Feature Selection

The code calculates the mean values of various attributes for songs George liked and disliked. It identifies variables showing a percentage difference of 10% or more between the two groups and removes those variables from the training data.

train.df.filtered <- train.df %>% group_by(target) %>% summarize(mean(danceability), mean(energy), mean(loudness), mean(speechiness), mean(acousticness), mean(instrumentalness), mean(liveness), mean(tempo), mean(duration_ms))

Identify which variables show a percentage difference of 10% or more? If any variables show less than 10% difference in mean value between the two groups (songs that George likes, and songs that George doesn’t like), then remove those variables entirely.

 # Use 'for' loop to create percent_change object
for (i in 2:ncol(train.df.filtered)) {
  percent_change <- ((train.df.filtered[1, c(2:10)] - train.df.filtered[2, c(2:10)])/train.df.filtered[1, c(2:10)])*100
} 

percent_change  # print percentage difference output
# Return columns (variables) of the 'percent_change' object, where the percentage difference is 10% or more
which(abs(percent_change[ , ]) >= 10) 
[1] 4 5 6

The variables “speechiness” (column 4), “acousticness” (column 5), & “instrumentalness” (column 6) show a percentage difference of 10% or more.

# List variable names in training data so we can remove these 3 variables
names(train.df) 
 [1] "X"                "acousticness"     "danceability"    
 [4] "duration_ms"      "energy"           "instrumentalness"
 [7] "liveness"         "loudness"         "speechiness"     
[10] "tempo"            "valence"          "target"          
[13] "song_title"       "artist"          
train.df <- train.df[ , -c(2, 6, 9)]

It is reasonable to consider removing variables with highly similar values in a k-nearest neighbors (k-NN) model because doing so helps create a more parsimonious model. By reducing the number of predictor variables, we aim to improve the model’s ability to generalize and make accurate predictions when faced with new data.

Moreover, when we have an excess of predictor variables, the model requires a larger number of training records to establish meaningful patterns. Consequently, the time it takes to identify the k-nearest neighbors for new records also increases.

Therefore, striving for a simpler, more concise model can significantly boost its confidence and efficiency when confronted with real-world data.

Data Normalization

The data is normalized using the preProcess function. This step is crucial for k-NN algorithms, as it ensures that all variables contribute equally to distance calculations.

norm.values <- preProcess(train.df[ , 2:7], method = c("center", "scale"))  

# only used values in columns number 2-8 (i.e., for numeric variables only)


# Initializes normalized df's to original df
train.norm.df <- train.df
valid.norm.df <- valid.df
spotify.norm.df <- spotify.df

train.norm.df[ , 2:7] <- predict(norm.values, train.df[ , 2:7])
valid.norm.df[ , c(3:5, 7:8, 10)] <- predict(norm.values, valid.df[ , c(3:5, 7:8, 10)])
spotify.norm.df[ , c(3:5, 7:8, 10)] <- predict(norm.values, spotify.df[ , c(3:5, 7:8, 10)])


song.norm.df <- predict(norm.values, song.df[, c(6:7, 9, 14:15, 17)])

Model Building

Using a k-value of 7, the code generates a predicted classification for the song “Cold Heart.” It also identifies the song’s 7 nearest neighbors.

nn <- knn(train = train.norm.df[ , 2:7], test = song.norm.df, cl = train.norm.df[ , 9], k = 7)

nn
[1] 1
attr(,"nn.index")
     [,1] [,2] [,3] [,4] [,5] [,6] [,7]
[1,]  727  170  380  729   41   50 1171
attr(,"nn.dist")
          [,1]      [,2]      [,3]      [,4]     [,5]      [,6]      [,7]
[1,] 0.6417425 0.6606155 0.6767229 0.7279269 0.730869 0.7505163 0.7629197
Levels: 1

The model predicted George would like the song “Cold Heart.” In other words, the model classified the song “Cold Heart” as belonging to the “1” (e.g., songs George will like) class.

The titles, artists, & outcome classes of the k-nearest neighbors are as follows

knn_output <- train.norm.df[c(727, 170,  380,  729,   41,   50, 1171), 9:11]
knn_output

Model Evaluation

The code evaluates the model’s performance for different k-values and determines the optimal k-value, which is found to be 6.

# Initialize df with columns for 'k' & 'accuracy'
accuracy.df <- data.frame(k = seq(1, 20, 1), accuracy = rep(0, 20))

for (i in 1:20) {
  knn.pred <- knn(train.norm.df[ , 2:7], valid.norm.df[ , c(3:5, 7:8, 10)], cl = train.norm.df[ , 9], k = i)
  accuracy.df[i, 2] <- confusionMatrix(knn.pred, valid.norm.df[ , 12])$overall[1]
}

accuracy.df 

The optimal ‘k’ value is 6 because it has the maximum accuracy rate (and minimum error rate) before the accuracy rate starts to decrease again at k=7 & k=8. Though we see that the accuracy rate starts to increase again at k=9, we must consider the bias-variance tradeoff: as k increases, the model starts to approximate the training set and would predict the majority class in the data set for all case scenarios.

Visualization

A scatterplot is created to visualize the relationship between k-values and accuracy.

library(ggplot2)

ggplot(data = accuracy.df, aes(x= k, y= accuracy)) + geom_point() + labs(title = "Accuracy vs. K", x= "K", y= "Accuracy")

Final Model & Prediction

The k-NN model is rerun with the optimal k-value, and predictions are made for the selected song. The model predicts that George will like the song “Cold Heart,” with the nearest neighbors identified.

nn.new <- knn(train = spotify.norm.df[ , c(3:5, 7:8, 10)], test = song.norm.df, cl = spotify.norm.df[ , 12], k = 6)

nn.new
[1] 1
attr(,"nn.index")
     [,1] [,2] [,3] [,4] [,5] [,6]
[1,]  454  163  520  372  474 1398
attr(,"nn.dist")
          [,1]      [,2]      [,3]      [,4]      [,5]      [,6]
[1,] 0.5804036 0.6417425 0.6606155 0.6767229 0.7012645 0.7279269
Levels: 1

The model predicted George would like the song “Cold Heart.” In other words, the model classified the song “Cold Heart” as belonging to the “1” (e.g., songs George will like) class.

knn_new_output <- spotify.norm.df[c(454,  163,  520,  372,  474, 1398), 12:14]
knn_new_output

The final result is the same as when I first ran the k-nn function, but some of the “k-nearest neighbors” changed. The songs “Forever”, “Can I Kick It?”, “Mamacita”, & “ê·\200ê°\200 Return Home” were four of seven k-nearest neighbors in the first example, while “Do It Roger” and “Bizness” are new k-nearest neighbors (e.g., the first iteration of knn included 2 songs that differ from the latter 2, and 1 more additional song).

Conclusions (k-nn)

In the following section, we delve into the key insights gleaned from the analysis and explore how these insights can be leveraged by various stakeholders. From predicting song likability to optimizing model parameters, these findings offer valuable guidance for enhancing decision-making and user experiences in the dynamic realm of music and data analysis. Let’s uncover the implications of this analysis for different stakeholders in the music industry and beyond.

Key Insights
  • The k-NN model predicts that George will like the song “Cold Heart” by Elton John, Dua Lipa, and PNAU.

  • The model identified the nearest neighbors of the song, which includes songs such as “Do It Roger,” “Forever,” and “Mamacita.”

  • The optimal k-value for the model is determined to be 6, based on evaluation results.

  • Dimension reduction was performed by removing variables with low impact on the model, which can lead to a more efficient and interpretable model.

  • Data normalization was applied to ensure that all variables contribute equally to the k-NN distance calculations.

Stakeholder Use
  • Music Recommendation Platforms: Music recommendation platforms like Spotify can use this analysis to enhance their recommendation algorithms. By predicting whether a user will like a song based on their listening history and song attributes, they can improve user engagement.

  • Record Labels: Record labels can benefit from understanding which attributes of a song contribute to its likability. This information can inform their music production and promotion strategies.

  • Artists: Artists can gain insights into what aspects of their music are more likely to resonate with their audience. This knowledge can help them create music that aligns with their fans’ preferences.

  • Data Analysts: Data analysts and data scientists can use this analysis as a template for building predictive models on similar datasets. The code and process serve as a practical example of data preprocessing, feature selection, and model evaluation.

  • Music Researchers: Researchers in the field of music analysis and psychology can use the findings to study the relationship between song attributes and listener preferences, contributing to the understanding of music psychology.

In summary, the analysis provides valuable insights into predicting song likability and can be utilized by various stakeholders in the music industry and beyond to enhance decision-making and user experiences.

LS0tDQp0aXRsZTogIkRhdGEtRHJpdmVuIFByZWRpY3RpdmUgQW5hbHl0aWNzOiBTYWxhcmllcyBhbmQgU29uZyBQcmVmZXJlbmNlcyINCmF1dGhvcjogVGFyYSBDb29sDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQojICoqSU5UUk9EVUNUSU9OKioNCg0KKkluIHRoaXMgUiBwcm9qZWN0LCB3ZSB3aWxsIGVtYmFyayBvbiBhIHR3by1waGFzZSBkYXRhIGFuYWx5c2lzIGpvdXJuZXkuIFRoZSBpbml0aWFsIHBoYXNlIGNlbnRlcnMgb24gdGhlIGFwcGxpY2F0aW9uIG9mIHNpbXBsZSBsaW5lYXIgcmVncmVzc2lvbiBtb2RlbHMsIGZvbGxvd2VkIGJ5IHRoZSBzZWNvbmQgcGhhc2UsIHdoaWNoIGludm9sdmVzIHRoZSB1dGlsaXphdGlvbiBvZiBrLW5lYXJlc3QgbmVpZ2hib3JzIG1vZGVsaW5nLiBUaGUgb2JqZWN0aXZlIGlzIHRvIGludmVzdGlnYXRlIGRhdGFzZXRzIHBlcnRhaW5pbmcgdG8gdGVjaCBpbmR1c3RyeSBzYWxhcmllcyBhbmQgU3BvdGlmeSBtdXNpYyBpbmZvcm1hdGlvbiwgd2l0aCB0aGUgdWx0aW1hdGUgYWltIG9mIGRlcml2aW5nIG1lYW5pbmdmdWwgaW5zaWdodHMgYW5kIGdlbmVyYXRpbmcgcHJlZGljdGl2ZSBvdXRjb21lcy4gVG8gaW5pdGlhdGUgdGhlIHByb2Nlc3MsIHdlIHdpbGwgbG9hZCB0aGUgZXNzZW50aWFsIGxpYnJhcmllcyBhbmQgcmVhZHkgdGhlIGRhdGEgZm9yIGFuYWx5c2lzLioNCg0KDQoNCkxvYWQgbmVjZXNzYXJ5IHBhY2thZ2VzOg0KYGBge3IgbWVzc2FnZT0gRkFMU0V9DQpsaWJyYXJ5KGdncGxvdDIpDQpsaWJyYXJ5KGZvcmVjYXN0KQ0KbGlicmFyeShkcGx5cikNCmxpYnJhcnkoY2FyZXQpDQpsaWJyYXJ5KEZOTikNCmBgYA0KDQoNCg0KDQoNCiMgKipQQVJUIEkgLSBTSU1QTEUgTElORUFSIFJFR1JFU1NJT04qKg0KDQoqSW4gdGhlIGZpcnN0IHBhcnQgb2Ygb3VyIGFuYWx5c2lzLCB3ZSBkZWx2ZSBpbnRvIHRoZSB3b3JsZCBvZiBTaW1wbGUgTGluZWFyIFJlZ3Jlc3Npb24gKFNMUikuIE91ciBqb3VybmV5IGJlZ2lucyBieSBwcmVwYXJpbmcgdGhlIGRhdGEsIGV4cGxvcmluZyBpdHMgbnVhbmNlcywgYW5kIHVuZGVyc3RhbmRpbmcgdGhlIHJlbGF0aW9uc2hpcHMgYmV0d2VlbiB2YXJpYWJsZXMuIFdlIGFpbSB0byB1bmNvdmVyIGluc2lnaHRzIGludG8gdGhlIHRlY2ggaW5kdXN0cnkncyBjb21wZW5zYXRpb24gZHluYW1pY3MgYW5kIGJ1aWxkIGEgcHJlZGljdGl2ZSBtb2RlbCB0aGF0IGNhbiBlc3RpbWF0ZSB0b3RhbCB5ZWFybHkgY29tcGVuc2F0aW9uIGJhc2VkIG9uIHllYXJzIG9mIGV4cGVyaWVuY2UuIFRocm91Z2ggY2FyZWZ1bCBleGFtaW5hdGlvbiBhbmQgc3RhdGlzdGljYWwgYW5hbHlzaXMsIHdlIHNoZWQgbGlnaHQgb24gdGhlIGNvcnJlbGF0aW9uIGJldHdlZW4gdGhlc2UgZmFjdG9ycyBhbmQgdW52ZWlsIHRoZSBtb2RlbCdzIHBlcmZvcm1hbmNlIGluIHByZWRpY3RpbmcgY29tcGVuc2F0aW9uLiBKb2luIHVzIGFzIHdlIG5hdmlnYXRlIHRoZSBpbnRyaWNhY2llcyBvZiBTTFIgdG8gZ2FpbiB2YWx1YWJsZSBpbnNpZ2h0cyB0aGF0IGJlbmVmaXQgSFIgcHJvZmVzc2lvbmFscywgaGlyaW5nIG1hbmFnZXJzLCBhbmQgdGVjaCBpbmR1c3RyeSBlbXBsb3llZXMuKg0KDQoNCg0KIyMjIyAqKjEuIERhdGEgUHJlcGFyYXRpb24qKg0KDQoqVGhlIGluaXRpYWwgc3RlcCBpbnZvbHZlZCBsb2FkaW5nIHRoZSBuZWNlc3NhcnkgcGFja2FnZXMgYW5kIGltcG9ydGluZyB0aGUgZGF0YXNldCwgInRlY2hfc2FsYXJ5X2RhdGFzZXQuY3N2LCIgaW50byBhbiBSIGRhdGEgZnJhbWUgbmFtZWQgInNhbGFyeS5kZi4iIFRoaXMgZW5zdXJlZCB0aGF0IG91ciBkYXRhIHdhcyByZWFkeSBmb3IgZnVydGhlciBhbmFseXNpcy4qDQoNCmBgYHtyfQ0KIyBMb2FkIGRhdGFzZXQNCnNhbGFyeS5kZiA8LSByZWFkLmNzdigidGVjaF9zYWxhcnlfZGF0YXNldC5jc3YiKQ0KDQpoZWFkKHNhbGFyeS5kZikgICMgUmV0dXJuIGZpcnN0IGZldyByb3dzIA0KYGBgDQoNCg0KDQojIyMjICoqRGF0YSBFeHBsb3JhdGlvbioqDQoNCipPdXIgZGF0YSBleHBsb3JhdGlvbiBwaGFzZSBpbmNsdWRlZCBhbiBleGFtaW5hdGlvbiBvZiB0aGUgZGF0YXNldCdzIHN0cnVjdHVyZSwgaWRlbnRpZnlpbmcgYm90aCBudW1lcmljIGFuZCBjYXRlZ29yaWNhbCB2YXJpYWJsZXMuIFRoaXMgc2VjdGlvbiBwcm92aWRlZCBhbiBvdmVydmlldyBvZiB0aGUgZGF0YXNldCdzIGtleSBjaGFyYWN0ZXJpc3RpY3MuKg0KDQpgYGB7cn0NCiMgVmlldyBzdHJ1Y3R1cmUgb2YgZGF0YXNldA0Kc3RyKHNhbGFyeS5kZikNCmBgYA0KVGhlIG51bWVyaWNhbCB2YXJpYWJsZXMgaW4gdGhlIGRhdGFzZXQgaW5jbHVkZTogdG90YWwgeWVhcmx5IGNvbXBlbnNhdGlvbiwgdG90YWwgeWVhcnMgb2YgZXhwZXJpZW5jZSwgeWVhcnMgYXQgdGhlIGNvbXBhbnksIGJhc2Ugc2FsYXJ5LCBzdG9jayBncmFudCB2YWx1ZSBhbmQgYm9udXMuIA0KDQpPbiB0aGUgb3RoZXIgaGFuZCwgdGhlIGNhdGVnb3JpY2FsIHZhcmlhYmxlcyBpbiB0aGUgZGF0YXNldCBpbmNsdWRlOiB0aW1lc3RhbXAsIGNvbXBhbnksIGxldmVsLCB0aXRsZSwgbG9jYXRpb24sIHRhZywgZ2VuZGVyLCBvdGhlciBkZXRhaWxzLCBjaXR5aWQsIGRtYWlkLCByb3cgbnVtYmVyLCBtYXN0ZXJzIGRlZ3JlZSwgYmFjaGVsb3JzIGRlZ3JlZSwgZG9jdG9yYXRlIGRlZ3JlZSwgaGlnaHNjaG9vbCwgc29tZSBjb2xsZWdlLCByYWNlLCBhbmQgZWR1Y2F0aW9uLg0KDQoNCg0KIyMjIyAqKkRhdGEgUGFydGl0aW9uaW5nKioNCg0KKkJlZm9yZSBjb25kdWN0aW5nIGRlZXBlciBhbmFseXNpcywgdGhlIGRhdGEgd2FzIHBhcnRpdGlvbmVkIGludG8gdHJhaW5pbmcgYW5kIHZhbGlkYXRpb24gc2V0cy4gVGhpcyBjcnVjaWFsIHN0ZXAgZW5zdXJlcyB0aGUgbW9kZWwncyByZWxldmFuY2UgYW5kIGFjY3VyYWN5LioNCg0KYGBge3J9DQojIFNldCBzZWVkIGZvciByZXBsaWNhYmlsaXR5IG9mIHNhbXBsZS9vdXRwdXQNCnNldC5zZWVkKDgwKSAgDQoNCiMgU2FtcGxlIDYwJSBvZiBkYXRhc2V0ICYgYXNzaWduIGl0IHRvIGluZGV4IGZvciB0cmFpbmluZyBzZXQNCnRyYWluLmluZGV4IDwtIHNhbXBsZShyb3cubmFtZXMoc2FsYXJ5LmRmKSwgMC42KmRpbShzYWxhcnkuZGYpWzFdKQ0KDQojIEFzc2lnbiByZW1haW5pbmcgcm93cyB0byBpbmRleCBmb3IgdmFsaWRhdGlvbiBzZXQNCnZhbGlkLmluZGV4IDwtIHNldGRpZmYocm93Lm5hbWVzKHNhbGFyeS5kZiksIHRyYWluLmluZGV4KSAgDQoNCiMgU3Vic2V0IGFsbCBvZiB0aGUgcm93cyBmb3IgYWxsIHRoZSBjb2x1bW5zIGFzc29jaWF0ZWQgd2l0aCB0aGUgdHJhaW5pbmcgZGF0YS92YWxpZGF0aW9uIGRhdGEgaW5kZXhlcw0KdHJhaW4uZGYgPC0gc2FsYXJ5LmRmW3RyYWluLmluZGV4LCBdDQp2YWxpZC5kZiA8LSBzYWxhcnkuZGZbdmFsaWQuaW5kZXgsIF0NCmBgYA0KDQoNCkl0IGlzIGltcG9ydGFudCB0byBwYXJ0aXRpb24gdGhlIGRhdGEgYmVmb3JlIGRvaW5nIGFueSBzb3J0IG9mIGluLWRlcHRoIGFuYWx5c2lzIG9mIHRoZSB2YXJpYWJsZXMgaW4gb3JkZXIgdG8gZW5zdXJlIHRoZSBtb2RlbCBpcyByZWxldmFudCB0byB0aGUgZGF0YSAoaS5lLiwgdGhlIHRyYWluaW5nIGRhdGEsIHdoaWNoIGlzIHVzZWQgdG8gZml0IHRoZSBtb2RlbCk7IGFuZCBwZXJmb3JtcyBhY2N1cmF0ZWx5IGluIGNsYXNzaWZ5aW5nL3ByZWRpY3RpbmcgbmV3IHJlY29yZHMgKGkuZS4sIHVzaW5nIHZhbGlkYXRpb24gZGF0YSB0byBlbnN1cmUgdGhlIG1vZGVsIHdhcyBub3QgIm92ZXJmaXQiIHRvIHRoZSB0cmFpbmluZyBkYXRhKS4gDQoNCg0KDQojIyMjICoqRURBIC0gRGF0YSBWaXN1YWxpemF0aW9uICsgU3VtbWFyeSBTdGF0cyoqDQoNCipFeHBsb3JhdG9yeSBkYXRhIGFuYWx5c2lzIGludm9sdmVkIGRhdGEgdmlzdWFsaXphdGlvbiBhbmQgc3VtbWFyeSBzdGF0aXN0aWNzLiBXZSBjcmVhdGVkIHNjYXR0ZXJwbG90cywgY2FsY3VsYXRlZCBjb3JyZWxhdGlvbnMsIGFuZCBpbnRlcnByZXRlZCB0aGUgcmVsYXRpb25zaGlwcyBiZXR3ZWVuIHZhcmlhYmxlcy4qDQoNCmBgYHtyIG1lc3NhZ2U9IEZBTFNFfQ0KIyBDcmVhdGUgYSBzY2F0dGVycGxvdCBvZiB5ZWFyc29mZXhwZXJpZW5jZSB2cy4gdG90YWx5ZWFybHljb21wZW5zYXRpb24NCmdncGxvdChkYXRhPSB0cmFpbi5kZiwgYWVzKHg9IHllYXJzb2ZleHBlcmllbmNlLCB5PSB0b3RhbHllYXJseWNvbXBlbnNhdGlvbikpICsgZ2VvbV9wb2ludCgpICsgbGFicyh0aXRsZSA9ICJZZWFybHkgQ29tcGVuc2F0aW9uIHZzLiBZZWFycyBvZiBFeHBlcmllbmNlIiwgeD0gIlllYXJzIG9mIEV4cGVyaWVuY2UiLCB5PSAiVG90YWwgWWVhcmx5IENvbXBlbnNhdGlvbiIpICsgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxtIikNCg0KIyB0aGUgZ2VvbV9zbW9vdGgoKSBmdW5jdGlvbiBhbGxvd3MgdXMgdG8gYWRkIGEgYmVzdC1maXQgbGluZQ0KYGBgDQoNCg0KVGhpcyBwbG90IHN1Z2dlc3RzIHRoYXQgdGhlIHR3byB2YXJpYWJsZXMgaGF2ZSBhIG1vZGVyYXRlIHBvc2l0aXZlIGNvcnJlbGF0aW9uLiBUaGlzIGlzIGV2aWRlbnQgYmFzZWQgb24gdGhlIGNsb3NlIHByb3hpbWl0eSBvZiB0aGUgcG9pbnRzIHRvIHRoZSBiZXN0LWZpdCBsaW5lIChpLmUuLCBnaXZlbiBhIGZldyBwb3RlbnRpYWwgb3V0bGllcnMpIGFuZCBzdWdnZXN0cyB0aGF0IHRvdGFsIHllYXJseSBjb21wZW5zYXRpb24gd2lsbCBpbmNyZWFzZSBhcyBhIHRlY2ggZW1wbG95ZWUncyBudW1iZXIgb2YgeWVhcnMgb2YgZXhwZXJpZW5jZSBpbiB0aGUgaW5kdXN0cnkgaW5jcmVhc2VzLiBVbHRpbWF0ZWx5LCB0aGlzIHJlbGF0aW9uc2hpcCBtYWtlcyBpbnR1aXRpdmUgc2Vuc2UgdG8gbWUgYmVjYXVzZSB0eXBpY2FsbHkgdGhlIGxvbmdlciB5b3Ugc3RheSBpbiBhIGNlcnRhaW4gaW5kdXN0cnkgKGUuZy4sIGluIGEgY2VydGFpbiBwb3NpdGlvbiBvciBhdCBhIGNlcnRhaW4gY29tcGFueSksIHRoZSBtb3JlIGtub3dsZWRnZWFibGUvcXVhbGlmaWVkIHlvdSB3aWxsIGJlLiBBcyBhIGNvbnNlcXVlbmNlLCBtb3JlIGxpa2VseSB5b3UgYXJlIHRvIHJlY2VpdmUgYSBoaWdoZXIgY29tcGVuc2F0aW9uIGluIHRoZSBmb2xsb3dpbmcgeWVhci9wZXJpb2QgLS0gZHVlIHRvIGV4cGVyaWVuY2UuIExpa2V3aXNlLCBpdCBpcyBub3QgdXN1YWxseSBjb21tb24gZm9yIGFuIGVtcGxveWVlJ3Mgc2FsYXJ5IHRvIGRlY3JlYXNlIG92ZXIgdGltZSwgZXNwZWNpYWxseSB3aGVuIHRoZXkgYXJlIGhvbGRpbmcgdGhlIHNhbWUgcG9zaXRpb24vZ2FpbmluZyBtb3JlIGV4cGVyaWVuY2Ugb3ZlciB0aW1lLg0KDQoNCg0KYGBge3J9DQojIENhbGN1bGF0ZSB0aGUgY29ycmVsYXRpb24gYmV0d2VlbiB5ZWFyc29mZXhwZXJpZW5jZSAmIHRvdGFseWVhcmx5Y29tcGVuc2F0aW9uDQpjb3IoeD0gdHJhaW4uZGYkeWVhcnNvZmV4cGVyaWVuY2UsIHk9IHRyYWluLmRmJHRvdGFseWVhcmx5Y29tcGVuc2F0aW9uKQ0KYGBgDQoNCg0KYGBge3J9DQojIENvbXB1dGUgc2lnbmlmaWNhbmNlIG9mIGNvcnJlbGF0aW9uDQpjb3IudGVzdCh4PSB0cmFpbi5kZiR5ZWFyc29mZXhwZXJpZW5jZSwgeT0gdHJhaW4uZGYkdG90YWx5ZWFybHljb21wZW5zYXRpb24pDQpgYGANCg0KDQpUaGUgY29ycmVsYXRpb24gb2JzZXJ2ZWQgYmV0d2VlbiAieWVhcnNvZmV4cGVyaWVuY2UiIGFuZCAidG90YWx5ZWFybHljb21wZW5zYXRpb24iIGluIHRoZSB0cmFpbmluZyBkYXRhc2V0IGlzIGFwcHJveGltYXRlbHkgMC40MjYzLiBUaGlzIHZhbHVlIHN1Z2dlc3RzIGEgbW9kZXJhdGUgcG9zaXRpdmUgY29ycmVsYXRpb24sIGluZGljYXRpbmcgdGhhdCBhcyB5ZWFycyBvZiBleHBlcmllbmNlIGluY3JlYXNlLCB0b3RhbCB5ZWFybHkgY29tcGVuc2F0aW9uIHRlbmRzIHRvIHJpc2UgYXMgd2VsbC4NCg0KV2hlbiBzdWJqZWN0ZWQgdG8gc3RhdGlzdGljYWwgYW5hbHlzaXMgdXNpbmcgUGVhcnNvbidzIGNvcnJlbGF0aW9uIHRlc3QsIHRoZSByZXN1bHRzIGNvbmZpcm0gdGhlIHNpZ25pZmljYW5jZSBvZiB0aGlzIHJlbGF0aW9uc2hpcC4gVGhlIHJlbWFya2FibHkgbG93IHAtdmFsdWUgKGxlc3MgdGhhbiAyLjJlLTE2KSB1bmVxdWl2b2NhbGx5IGRlbW9uc3RyYXRlcyB0aGF0IHRoZSBvYnNlcnZlZCBjb3JyZWxhdGlvbiBpcyBoaWdobHkgbWVhbmluZ2Z1bC4gSXQgcHJvdmlkZXMgY29tcGVsbGluZyBldmlkZW5jZSB0aGF0IHRoZSBjb25uZWN0aW9uIGJldHdlZW4geWVhcnMgb2YgZXhwZXJpZW5jZSBhbmQgdG90YWwgeWVhcmx5IGNvbXBlbnNhdGlvbiBpcyBub3QgbWVyZWx5IGEgcmFuZG9tIG9jY3VycmVuY2UuDQoNCkZ1cnRoZXJtb3JlLCB0aGUgOTUgcGVyY2VudCBjb25maWRlbmNlIGludGVydmFsIGZvciB0aGUgY29ycmVsYXRpb24sIHJhbmdpbmcgZnJvbSAwLjQxODAgdG8gMC40MzQ2LCBuYXJyb3dzIGRvd24gdGhlIGxpa2VseSByYW5nZSB3aXRoaW4gd2hpY2ggdGhlIHRydWUgY29ycmVsYXRpb24gcmVzaWRlcy4gVGhpcyBub3Qgb25seSByZWluZm9yY2VzIHRoZSBjb25jZXB0IG9mIGEgc3Vic3RhbnRpYWwgYW5kIGNvbnNpc3RlbnQgbGluayBiZXR3ZWVuIHRoZXNlIHR3byB2YXJpYWJsZXMgYnV0IGFsc28gZW5oYW5jZXMgb3VyIGNvbmZpZGVuY2UgaW4gdGhlIGZpbmRpbmdzLg0KDQpXaGlsZSB0aGUgY29ycmVsYXRpb24gbWF5IG5vdCBiZSBleGNlcHRpb25hbGx5IHN0cm9uZywgaXQgaXMgc3RhdGlzdGljYWxseSBzaWduaWZpY2FudCBhbmQgaGlnaGxpZ2h0cyBhIG1lYW5pbmdmdWwgY29ubmVjdGlvbiB3aXRoaW4gdGhlIGRhdGFzZXQuIEluIGVzc2VuY2UsIGFzIGluZGl2aWR1YWxzIGFjY3VtdWxhdGUgbW9yZSB5ZWFycyBvZiBleHBlcmllbmNlLCB0aGVpciB0b3RhbCB5ZWFybHkgY29tcGVuc2F0aW9uIHRlbmRzIHRvIGV4aGliaXQgYSBkaXNjZXJuaWJsZSB1cHdhcmQgdHJlbmQsIGFzIHN1YnN0YW50aWF0ZWQgYnkgdGhlIGRhdGEgYW5kIHN0YXRpc3RpY2FsIGFuYWx5c2lzLg0KDQoNCg0KIyMjIyAqKkxpbmVhciBSZWdyZXNzaW9uIE1vZGVsKioNCg0KKkluIHRoaXMgbmV4dCBzdGVwIGEgc2ltcGxlIGxpbmVhciByZWdyZXNzaW9uIG1vZGVsIGlzIGNvbnN0cnVjdGVkIHRvIHByZWRpY3QgdG90YWwgeWVhcmx5IGNvbXBlbnNhdGlvbiBiYXNlZCBvbiB5ZWFycyBvZiBleHBlcmllbmNlLiBUaGUgbW9kZWwncyBzdW1tYXJ5IGFuZCBhIGh5cG90aGV0aWNhbCBpbnB1dCBwcmVkaWN0aW9uIHdlcmUgaW5jbHVkZWQgdG8gaWxsdXN0cmF0ZSBpdHMgZnVuY3Rpb25hbGl0eS4qDQoNCg0KYGBge3J9DQojIENyZWF0ZSBTTFIgbW9kZWwNCm1vZGVsIDwtIGxtKHRvdGFseWVhcmx5Y29tcGVuc2F0aW9uIH4geWVhcnNvZmV4cGVyaWVuY2UsIGRhdGEgPSB0cmFpbi5kZikNCg0KIyBHZW5lcmF0ZSBtb2RlbCBzdW1tYXJ5DQpzdW1tYXJ5KG1vZGVsKQ0KYGBgDQoNCg0KDQojIyMjICoqTW9kZWwgRXZhbHVhdGlvbioqDQoNCipJbiB0aGUgZm9sbG93aW5nIHNlY3Rpb24sIHdlIGV2YWx1YXRlZCB0aGUgbW9kZWwncyBwZXJmb3JtYW5jZSB1c2luZyByZXNpZHVhbHMgYW5hbHlzaXMsIGhpZ2hsaWdodGluZyBpbnN0YW5jZXMgd2hlcmUgdGhlIG1vZGVsJ3MgcHJlZGljdGlvbnMgZGV2aWF0ZWQgc2lnbmlmaWNhbnRseSBmcm9tIGFjdHVhbCB2YWx1ZXMuKg0KDQpgYGB7cn0NCiMgR2VuZXJhdGUgc3VtbWFyeSBvZiBtb2RlbCByZXNpZHVhbHMNCnN1bW1hcnkobW9kZWwkcmVzaWR1YWxzKQ0KYGBgDQpJbiB0aGlzIG1vZGVsLCB0aGUgbWluaW11bSByZXNpZHVhbCB2YWx1ZSBpcyAtNzQ0LDUxNSAmIHRoZSBtYXhpbXVtIHJlc2lkdWFsIHZhbHVlIGlzIDQsNjYzLDU0My4gDQoNCg0KDQpgYGB7cn0NCiMgQ3JlYXRlIGRmIHdpdGggZml0dGVkIHZhbHVlcyAocHJlZGljdGlvbnMpICYgcmVzaWR1YWxzDQpyZXNpZHVhbC5kZiA8LSBkYXRhLmZyYW1lKCJSZXNpZHVhbHMiID0gbW9kZWwkcmVzaWR1YWxzLCAiUHJlZGljdGVkIFRvdGFsIENvbXAiID0gbW9kZWwkZml0dGVkLnZhbHVlcykNCg0KIyBCaW5kIHRoZSAncmVzaWR1YWwuZGYnIHRvIHRoZSBkZiB3aXRoIG91ciB0cmFpbmluZyBkYXRhDQp0cmFpbi5kZiA8LSBjYmluZCh0cmFpbi5kZiwgcmVzaWR1YWwuZGYpDQoNCiMgUmV0dXJucyBmaXJzdCBmZXcgcm93cyBvZiBkYXRhIHRvIGVuc3VyZSBzdWNjZXNzZnVsIGJpbmRpbmcgb2YgdGhlIDIgZGF0YWZyYW1lcw0KaGVhZCh0cmFpbi5kZikgIA0KYGBgDQoNCmBgYHtyfQ0KIyBJZGVudGlmeSB0aGUgb2JzZXJ2YXRpb24gd2hvc2UgcmF0aW5nIGdlbmVyYXRlZCB0aGUgaGlnaGVzdCByZXNpZHVhbCB2YWx1ZQ0Kc3Vic2V0KHRyYWluLmRmLCBSZXNpZHVhbHMgPT0gbWF4KHRyYWluLmRmJFJlc2lkdWFscykpDQpgYGANCkJhc2VkIG9uIHRoZSBvYnNlcnZhdGlvbiB3aG9zZSByYXRpbmcgZ2VuZXJhdGVkIHRoZSBoaWdoZXN0IHJlc2lkdWFsIHZhbHVlLCB0aGUgcGVyc29uJ3MgYWN0dWFsIHRvdGFsIGNvbXBlbnNhdGlvbiB3YXMgNCw5ODAsMDAwIChkb2xsYXJzKTsgd2hpbGUgdGhlIG1vZGVsIHByZWRpY3RlZCBpdCB3b3VsZCBiZSAzMTYsNDU2LjgwIChkb2xsYXJzKS4gVGhlIHJlc2lkdWFsIGlzIGNhbGN1bGF0ZWQgYnkgdGFraW5nIHRoZSBkaWZmZXJlbmNlIGJldHdlZW4gdGhlIGFjdHVhbCB0b3RhbCBjb21wZW5zYXRpb24gZm9yIGVhY2ggZW1wbG95ZWUgYW5kIHdoYXQgdGhlIHJlZ3Jlc3Npb24gbW9kZWwgcHJlZGljdGVkIGl0IHRvIGJlIChpLmUuLCA0OTgwMDAwIC0gMzE2NDU2LjgwID0gNDY2MzU0My4zMCkuIA0KDQoNCg0KYGBge3J9DQojIElkZW50aWZ5IHRoZSBvYnNlcnZhdGlvbiB3aG9zZSByYXRpbmcgZ2VuZXJhdGVkIHRoZSBsb3dlc3QgcmVzaWR1YWwgdmFsdWUNCnN1YnNldCh0cmFpbi5kZiwgUmVzaWR1YWxzID09IG1pbih0cmFpbi5kZiRSZXNpZHVhbHMpKQ0KYGBgDQpCYXNlZCBvbiB0aGUgb2JzZXJ2YXRpb24gd2hvc2UgcmF0aW5nIGdlbmVyYXRlZCB0aGUgbG93ZXN0IHJlc2lkdWFsIHZhbHVlLCB0aGUgcGVyc29uJ3MgYWN0dWFsIHRvdGFsIGNvbXBlbnNhdGlvbiB3YXMgMTAyLDAwMCAoZG9sbGFycyk7IHdoaWxlIHRoZSBtb2RlbCBwcmVkaWN0ZWQgaXQgd291bGQgYmUgODQ2LDUxNS4xMCAoZG9sbGFycykuIFRoZSByZXNpZHVhbCBpcyBjYWxjdWxhdGVkIGJ5IHRha2luZyB0aGUgZGlmZmVyZW5jZSBiZXR3ZWVuIHRoZSBhY3R1YWwgdG90YWwgY29tcGVuc2F0aW9uIGZvciBlYWNoIGVtcGxveWVlIGFuZCB3aGF0IHRoZSByZWdyZXNzaW9uIG1vZGVsIHByZWRpY3RlZCBpdCB0byBiZSAoaS5lLiwgMTAyMDAwIC0gODQ2NTE1LjEwID0gLTc0NCw1MTUuMTApLiANCg0KDQpJdCBsb29rcyBsaWtlIHRoZXJlIGFyZSBzb21lIGNhc2VzIHdoZXJlIHRoaXMgbW9kZWwgaXMgcXVpdGUgYSBiaXQgIm9mZiB0aGUgbWFyayIuIEFsdGhvdWdoIGludHVpdGl2ZWx5IGl0IG1ha2VzIHNlbnNlIHRoYXQgeWVhcnMgb2YgZXhwZXJpZW5jZSBjb3VsZCBiZSBhIGZhY3RvciBpbiB0aGUgdG90YWwgY29tcGVuc2F0aW9uIGFuIGVtcGxveWVlIGluIHRoZSBoaWdoIHRlY2ggaW5kdXN0cnkgcmVjZWl2ZXMsIHRoZXJlIGEgZmV3IHBvc3NpYmxlIHJlYXNvbnMgd2h5IHllYXJzIG9mIGV4cGVyaWVuY2UgbWlnaHQgbm90IGNsb3NlbHkgcHJlZGljdCB0b3RhbCBjb21wZW5zYXRpb24uIFNwZWNpZmljYWxseSwgaXQgaXMgcG9zc2libGUgdGhhdCAieWVhcnMgb2YgZXhwZXJpZW5jZSIgaXMgY29ycmVsYXRlZCB3aXRoIG9uZSBvciBtb3JlIG9mIHRoZSBvdGhlciBpbnB1dCB2YXJpYWJsZXMgaW4gdGhlIG1vZGVsLCB3aGljaCBleHBvc2VzIHRoZSBtb2RlbCB0byB0aGUgZGFuZ2VycyBvZiBtdWx0aS1jb2xsaW5lYXJpdHkuIFRoZXJlIGlzIGFsc28gdGhlIGNhc2Ugd2hlcmUgdmFyaWFibGUgcmVkdWN0aW9uIGlzIHRoZSBmb2N1czsgaW4gd2hpY2ggdGhlcmUgYXJlIGp1c3QgImJldHRlciIgaW5wdXQgdmFyaWFibGVzIHRoYXQgYWN0IGFzIHByZWRpY3RvcnMgZm9yIHRvdGFsIGNvbXBlbnNhdGlvbiAtLSB3aXRoIHllYXJzIG9mIGV4cGVyaWVuY2UgYmVpbmcgb25lIG9mIHRoZSBwb3RlbnRpYWwgdmFyaWFibGVzIHVuZGVyIHRoZSBzY29wZSBmb3IgZWxpbWluYXRpb24vYmVpbmcgImRyb3BwZWQuIiBGb3IgZXhhbXBsZSwgYmFzZWQgb24gdGhlIGRlc2NyaXB0aW9uIG9mIGVhY2ggb2YgdGhlIHZhcmlhYmxlcyBpbiB0aGUgb3JpZ2luYWwgZGF0YXNldCwgd2Uga25vdyB0aGF0IHRvdGFsIHllYXJseSBjb21wZW5zYXRpb24gaXMgdGhlIHN1bSBvZiBzZXZlcmFsIG90aGVyIG51bWVyaWNhbCB2YWx1ZXMgLSBpbmNsdWRpbmcgYmFzZSBzYWxhcnksIGJvbnVzLCAmIHN0b2NrIGdyYW50IHZhbHVlLg0KDQoNCg0KIyMjIyAqKlN1bW1hcnkgb2YgTGluZWFyIFJlZ3Jlc3Npb24gRXF1YXRpb24qKg0KDQpgYGB7cn0NCnN1bW1hcnkobW9kZWwpICAjIFJldHVybiBtb2RlbCBzdW1tYXJ5IHRvIG9idGFpbiBjb2VmZmljaWVudHMgKG0sIHNsb3BlICYgYiwgeS1pbnRlcmNlcHQpDQpgYGANClRoZXJlZm9yZSwgdGhlIHJlZ3Jlc3Npb24gZXF1YXRpb24gZ2VuZXJhdGVkIGJ5IHRoZSBtb2RlbCBpczogVG90YWwgeWVhcmx5IGNvbXBlbnNhdGlvbiAoeSkgPSAxMDE5My40KHgpICsgMTQzMTY4LjUNCg0KDQpNYWtlIHVwIGEgaHlwb3RoZXRpY2FsIGlucHV0IHZhbHVlIGFuZCB1c2UgaXQgdG8gbWFrZSBhIG5ldyBwcmVkaWN0aW9uLg0KYGBge3J9DQpuZXdfaW5wdXQgPC0gMTANCg0KdG90YWxfeWVhcmx5X2NvbXBlbnNhdGlvbiA8LSBmdW5jdGlvbih4KXsNCiAgcmV0dXJuKDEwMTkzLjQqeCArIDE0MzE2OC41KQ0KfQ0KdG90YWxfeWVhcmx5X2NvbXBlbnNhdGlvbigxMCkNCmBgYA0KSW4gc3VtLCAnMTAnIHdhcyB1c2VkIGFzIHRoZSBoeXBvdGhldGljYWwgaW5wdXQgdmFsdWUgZm9yICJ5ZWFyc29mZXhwZXJpZW5jZSIgaW4gdGhlIHJlZ3Jlc3Npb24gZXF1YXRpb24gdG8gcHJlZGljdCBhcyBhbiBvdXRjb21lLCB0aGUgdG90YWwgeWVhcmx5IGNvbXBlbnNhdGlvbiBvZiBhbiBlbXBsb3llZSB3aXRoIDEwIHllYXJzIG9mIGV4cGVyaWVuY2UgaW4gdGhlIGhpZ2ggdGVjaCBpbmR1c3RyeS4gQXMgc3VjaCwgZm9yIGFuIGVtcGxveWVlIHdobyBoYXMgMTAgeWVhcnMgb2YgZXhwZXJpZW5jZSBpbiB0aGUgaGlnaCB0ZWNoIGluZHVzdHJ5LCB0aGUgcHJlZGljdGVkIHRvdGFsIHllYXJseSBjb21wZW5zYXRpb24gaXMgMjQ1LDEwMi41MCAoZG9sbGFycykuDQoNCg0KVHJhaW5pbmcgc2V0Og0KYGBge3J9DQphY2N1cmFjeSh0cmFpbi5kZiRQcmVkaWN0ZWQuVG90YWwuQ29tcCwgdHJhaW4uZGYkdG90YWx5ZWFybHljb21wZW5zYXRpb24pDQpgYGANCg0KVmFsaWRhdGlvbiBTZXQ6DQpgYGB7cn0NCnNhbGFyeS5wcmVkIDwtIHByZWRpY3QobW9kZWwsIHZhbGlkLmRmKQ0KYWNjdXJhY3koc2FsYXJ5LnByZWQsIHZhbGlkLmRmJHRvdGFseWVhcmx5Y29tcGVuc2F0aW9uKQ0KYGBgDQpUaGUgcHVycG9zZSBvZiBtYWtpbmcgdGhpcyBjb21wYXJpc29uIGlzIHRvIGVuc3VyZSB0aGUgbW9kZWwgcGVyZm9ybXMgd2VsbCBpbiBwcmVkaWN0aW5nIG5ldyByZWNvcmRzIChpLmUuLCBldmFsdWF0aW5nIGl0cyBwcmVkaWN0aXZlIGFjY3VyYWN5KS4gVGhpcyBtZWFucyB0aGF0IHRoZSBtb2RlbCBpcyBub3Qgb3ZlcmZpdCB0byB0aGUgcHJlZGljdG9yIHZhbHVlcyBpbiB0aGUgdHJhaW5pbmcgc2V0IChpLmUuLCB3aGVuIGFwcGxpZWQgdG8gbmV3LCB1bmtub3duIHJlY29yZHMpLiANCg0KSW4gcGFydGljdWxhciwgdGhlIHJvb3QtbWVhbi1zcXVhcmVkIGVycm9yIChSTVNFKSB0ZWxscyB1cyBhYm91dCB0aGUgImVycm9yIG1hZ25pdHVkZSIgaW4gdW5pdHMgdGhhdCBjb3JyZXNwb25kIHRvIHRoZSBvdXRwdXQgKGUuZy4sIHRvdGFsIHllYXJseSBjb21wZW5zYXRpb24pOyB3aGlsZSB0aGUgbWVhbiBhYnNvbHV0ZSBlcnJvciAoTUFFKSB0ZWxscyB1cyBhYm91dCB0aGUgbWFnbml0dWRlIG9mIHRoZSBhdmVyYWdlIGFic29sdXRlIGVycm9yLiBUaGVzZSBwcmVkaWN0aW9uIGFjY3VyYWN5IG1lYXN1cmVzIGNvcnJlc3BvbmRpbmcgdG8gdGhlIHRyYWluaW5nIGRhdGEgdGVsbCB1cyBhYm91dCBtb2RlbCBmaXQgd2hlbiBjb21wYXJpbmcgdGhlIHByZWRpY3RlZC9hY3R1YWwgdmFsdWVzIG9mIHRvdGFsIHllYXJseSBjb21wZW5zYXRpb247IHdoaWxlIHRoZSBwcmVkaWN0aW9uIGFjY3VyYWN5IG1lYXN1cmVzIHRoYXQgY29ycmVzcG9uZCB0byB0aGUgdmFsaWRhdGlvbiBzZXQgY29tbXVuaWNhdGUgdGhlIG1vZGVsJ3MgYWJpbGl0eSB0byBwcmVkaWN0IG5ldyByZWNvcmRzIHRoYXQgYXJlIHVuZmFtaWxpYXIgdG8gdGhlIG1vZGVsL3RyYWluaW5nIGRhdGEuIEluIGdlbmVyYWwsIFJNU0UvTUFFIHNob3VsZCBiZSBsb3dlciBmb3IgdGhlIHRyYWluaW5nIGRhdGEgdGhhbiB0aGUgdmFsaWRhdGlvbiBkYXRhLCBhcyB0aGUgdHJhaW5pbmcgZGF0YSB3YXMgdXNlZCB0byBmaXQgdGhlIHJlZ3Jlc3Npb24gbW9kZWwgKGkuZS4sIHdpdGggbG93IGVycm9yIG1hZ25pdHVkZSBpbmRpY2F0aW5nIGJldHRlciBmaXQpLg0KDQpIb3dldmVyLCBldmVuIHdpdGggbG93IFJNU0UgYW5kIE1BRSBvbiB0cmFpbmluZyBkYXRhLCBiaWFzIG1heSBwZXJzaXN0IC0gc3lzdGVtYXRpYyBlcnJvcnMgdGhhdCBjb25zaXN0ZW50bHkgcHVzaCBwcmVkaWN0aW9ucyBpbiBvbmUgZGlyZWN0aW9uLiBUbyBkZXRlY3QgYmlhcywgdGhlIFJNU0UgYW5kIE1BRSBhcmUgZXZhbHVhdGVkIG9uIHRoZSB2YWxpZGF0aW9uIHNldC4gSWYgdGhleSBzaWduaWZpY2FudGx5IGRpZmZlciBmcm9tIHRyYWluaW5nIGRhdGEsIGl0IG1heSBpbmRpY2F0ZSBiaWFzLiBJbiBlc3NlbmNlLCB0aGlzIGNvbXBhcmlzb24gZW5zdXJlcyB0aGUgbW9kZWwncyBnZW5lcmFsaXphdGlvbiBhbmQgZGV0ZWN0cyBwb3RlbnRpYWwgYmlhcyB0aGF0IG1pZ2h0IG5vdCBiZSBhcHBhcmVudCBzb2xlbHkgZnJvbSB0cmFpbmluZyBkYXRhLg0KDQoNCg0KIyMjIyAqKk1vZGVsIENvbXBhcmlzb24qKg0KDQoqRmluYWxseSwgd2UgY29tcGFyZWQgdGhlIG1vZGVsJ3Mgcm9vdC1tZWFuLXNxdWFyZWQgZXJyb3IgKFJNU0UpIHRvIHRoZSBzdGFuZGFyZCBkZXZpYXRpb24gb2YgdG90YWwgY29tcGVuc2F0aW9uIGluIHRoZSB0cmFpbmluZyBzZXQsIHByb3ZpZGluZyBpbnNpZ2h0cyBpbnRvIGl0cyBwZXJmb3JtYW5jZS4qDQoNCmBgYHtyfQ0Kc2QodHJhaW4uZGYkdG90YWx5ZWFybHljb21wZW5zYXRpb24pDQpgYGANCg0KVGhlIG1vZGVsJ3MgUk1TRSBpcyBsZXNzIHRoYW4gdGhlIHN0YW5kYXJkIGRldmlhdGlvbiBvZiB0b3RhbCBjb21wZW5zYXRpb24gaW4gdGhlIHRyYWluaW5nIHNldC4gQ29tcGFyaW5nIG91ciBtb2RlbCdzIFJNU0UgdG8gdGhlIHN0YW5kYXJkIGRldmlhdGlvbiBvZiB0b3RhbCBjb21wZW5zYXRpb24gcmV2ZWFscyBrZXkgaW5zaWdodHMgaW50byBpdHMgcGVyZm9ybWFuY2UuIA0KDQpJbiBlc3NlbmNlLCB3aGVuIHRoZSBSTVNFIGV4Y2VlZHMgb3IgZXF1YWxzIHRoZSBzdGFuZGFyZCBkZXZpYXRpb24sIGl0IHNpZ25hbHMgdGhhdCBvdXIgbW9kZWwncyBwcmVkaWN0aW9ucyBhcmUgbm8gYmV0dGVyIHRoYW4gYSAibnVsbCIgbW9kZWwgdGhhdCBtZXJlbHkgZ3Vlc3NlcyB0aGUgbWVhbi4gVGhpcyBzdWdnZXN0cyB0aGUgbW9kZWwgaXNuJ3QgZWZmZWN0aXZlbHkgbGVhcm5pbmcgZnJvbSB0aGUgaW5wdXQgZmVhdHVyZXMgYW5kIGxhY2tzIG1lYW5pbmdmdWwgcHJlZGljdGl2ZSBwb3dlci4NCg0KQ29udmVyc2VseSwgYSBsb3dlciBSTVNFIGNvbXBhcmVkIHRvIHRoZSBzdGFuZGFyZCBkZXZpYXRpb24gaW5kaWNhdGVzIHRoYXQgdGhlIG1vZGVsIGlzIG91dHBlcmZvcm1pbmcgdGhpcyBiYXNpYyBiYXNlbGluZS4gSXQgZGVtb25zdHJhdGVzIHRoZSBtb2RlbCdzIGFiaWxpdHkgdG8gY2FwdHVyZSB2YWx1YWJsZSBwYXR0ZXJucyBhbmQgbWFrZSBtb3JlIGFjY3VyYXRlIHByZWRpY3Rpb25zLg0KDQpJbiBzdW1tYXJ5LCB0aGlzIGNvbXBhcmlzb24gbm90IG9ubHkgZ2F1Z2VzIHByZWRpY3RpdmUgYWNjdXJhY3kgYnV0IGFsc28gaGlnaGxpZ2h0cyB3aGV0aGVyIHRoZSBtb2RlbCBhZGRzIHZhbHVlIGJleW9uZCBhIHNpbXBsaXN0aWMgYXBwcm9hY2guDQoNCg0KDQojIyMjICoqQ29uY2x1c2lvbnMgKFNMUikqKg0KDQpJbiB0aGlzIHNlY3Rpb24sIHdlIGRpc3RpbGwgdGhlIGtleSBpbnNpZ2h0cyBnYWluZWQgZnJvbSBvdXIgYW5hbHlzaXMgYW5kIGRpc2N1c3MgdGhlaXIgcmVsZXZhbmNlIHRvIHZhcmlvdXMgc3Rha2Vob2xkZXJzLiBUaGVzZSBpbnNpZ2h0cyBzaGVkIGxpZ2h0IG9uIHRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiB5ZWFycyBvZiBleHBlcmllbmNlIGFuZCB0b3RhbCB5ZWFybHkgY29tcGVuc2F0aW9uIGluIHRoZSB0ZWNoIGluZHVzdHJ5IGFuZCBvZmZlciBwcmFjdGljYWwgaW1wbGljYXRpb25zIGZvciBkZWNpc2lvbi1tYWtlcnMuDQoNCiMjIyMjICoqS2V5IEluc2lnaHRzKioNCg0KKiAqTW9kZXJhdGUgUG9zaXRpdmUgQ29ycmVsYXRpb24qOiBPdXIgYW5hbHlzaXMgcmV2ZWFsZWQgYSBtb2RlcmF0ZSBwb3NpdGl2ZSBjb3JyZWxhdGlvbiBiZXR3ZWVuIHllYXJzIG9mIGV4cGVyaWVuY2UgYW5kIHRvdGFsIHllYXJseSBjb21wZW5zYXRpb24sIGluZGljYXRpbmcgdGhhdCwgb24gYXZlcmFnZSwgZW1wbG95ZWVzIGVhcm4gaGlnaGVyIGNvbXBlbnNhdGlvbiBhcyB0aGV5IGdhaW4gbW9yZSBleHBlcmllbmNlLg0KDQoqICpTdGF0aXN0aWNhbCBTaWduaWZpY2FuY2UqOiBTdGF0aXN0aWNhbCB0ZXN0cyBjb25maXJtZWQgdGhlIHNpZ25pZmljYW5jZSBvZiB0aGlzIGNvcnJlbGF0aW9uLCBwcm92aWRpbmcgcm9idXN0IGV2aWRlbmNlIHRoYXQgdGhlIHJlbGF0aW9uc2hpcCBpcyBub3QgcmFuZG9tLg0KDQoqICpMaW5lYXIgUmVncmVzc2lvbiBNb2RlbCo6IFRoZSBsaW5lYXIgcmVncmVzc2lvbiBtb2RlbCBwcm92aWRlZCBhIHF1YW50aXRhdGl2ZSByZXByZXNlbnRhdGlvbiBvZiB0aGUgcmVsYXRpb25zaGlwLCBmYWNpbGl0YXRpbmcgc2FsYXJ5IHByZWRpY3Rpb25zIGJhc2VkIG9uIHllYXJzIG9mIGV4cGVyaWVuY2UuDQoNCiogKk1vZGVsIFBlcmZvcm1hbmNlKjogT3VyIG1vZGVsIGRlbW9uc3RyYXRlZCBnb29kIHBlcmZvcm1hbmNlIGluIHByZWRpY3RpbmcgY29tcGVuc2F0aW9uLCBhcyBpbmRpY2F0ZWQgYnkgbG93IFJNU0UgYW5kIE1BRSB2YWx1ZXMgb24gYm90aCB0cmFpbmluZyBhbmQgdmFsaWRhdGlvbiBkYXRhc2V0cy4NCg0KKiAqQmlhcyBEZXRlY3Rpb24qOiBXaGlsZSBnZW5lcmFsbHkgcGVyZm9ybWluZyB3ZWxsLCB0aGUgbW9kZWwgZXhoaWJpdGVkIGJpYXMgaW4gc29tZSBjYXNlcywgaGlnaGxpZ2h0aW5nIGFyZWFzIGZvciBwb3RlbnRpYWwgaW1wcm92ZW1lbnQuDQoNCiogKk1vZGVsIFZhbHVlKjogQ29tcGFyaW5nIHRoZSBSTVNFIHRvIHRoZSBzdGFuZGFyZCBkZXZpYXRpb24gZGVtb25zdHJhdGVkIHRoYXQgdGhlIG1vZGVsIGFkZHMgdmFsdWUgYnkgbWFraW5nIG1vcmUgYWNjdXJhdGUgcHJlZGljdGlvbnMgdGhhbiBhIG1lYW4tYmFzZWQgbW9kZWwuDQoNCiMjIyMjICoqU3Rha2Vob2xkZXIgSW1wbGljYXRpb25zKioNCg0KKiAqSFIgRGVwYXJ0bWVudHMqOiBIUiBwcm9mZXNzaW9uYWxzIGNhbiB1dGlsaXplIHRoZSBsaW5lYXIgcmVncmVzc2lvbiBtb2RlbCB0byBlc3RpbWF0ZSBjb21wZW5zYXRpb24gZm9yIHBvdGVudGlhbCBoaXJlcywgYWlkaW5nIGluIGNvbXBldGl0aXZlIGpvYiBvZmZlcnMuDQoNCiogKkhpcmluZyBNYW5hZ2Vycyo6IFVuZGVyc3RhbmRpbmcgdGhlIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIGV4cGVyaWVuY2UgYW5kIGNvbXBlbnNhdGlvbiBlcXVpcHMgaGlyaW5nIG1hbmFnZXJzIHRvIG1ha2UgaW5mb3JtZWQgZGVjaXNpb25zIGR1cmluZyB0aGUgaGlyaW5nIHByb2Nlc3MuDQoNCiogKkVtcGxveWVlcyo6IEN1cnJlbnQgZW1wbG95ZWVzIGNhbiBnYWluIGluc2lnaHRzIGludG8gdGhlaXIgcG90ZW50aWFsIGNhcmVlciB0cmFqZWN0b3JpZXMgYW5kIGZpbmFuY2lhbCBnb2FscyBiYXNlZCBvbiBob3cgdGhlaXIgY29tcGVuc2F0aW9uIG1heSBldm9sdmUuDQoNCiogKkRhdGEgQW5hbHlzdHMqOiBUaGUgcHJvdmlkZWQgY29kZSBhbmQgcHJvY2VzcyBzZXJ2ZSBhcyBhIHRlbXBsYXRlIGZvciBkYXRhIGFuYWx5c3RzIGFuZCBkYXRhIHNjaWVudGlzdHMgdG8gY29uZHVjdCBzaW1pbGFyIGFuYWx5c2VzIGluIHRoZWlyIG9yZ2FuaXphdGlvbnMsIGV4cGxvcmluZyByZWxhdGlvbnNoaXBzIGJldHdlZW4gdmFyaWFibGVzIGFuZCBidWlsZGluZyBwcmVkaWN0aXZlIG1vZGVscy4NCg0KSW4gY29uY2x1c2lvbiwgdGhpcyBhbmFseXNpcyBwcm92aWRlcyB2YWx1YWJsZSBpbnNpZ2h0cyBpbnRvIHRlY2ggaW5kdXN0cnkgY29tcGVuc2F0aW9uIGR5bmFtaWNzIGFuZCBvZmZlcnMgYSBwcmFjdGljYWwgbW9kZWwgZm9yIHByZWRpY3Rpbmcgc2FsYXJpZXMgYmFzZWQgb24gZXhwZXJpZW5jZSwgYmVuZWZpdGluZyBhIHJhbmdlIG9mIHN0YWtlaG9sZGVycyBpbiBkZWNpc2lvbi1tYWtpbmcgcm9sZXMuDQoNCg0KDQoNCg0KIyAqKlBBUlQgSUkgLSBLLU5FQVJFU1QgTkVJR0hCT1JTKioNCg0KKkluIHRoZSBzZWNvbmQgcGFydCBvZiBvdXIgYW5hbHlzaXMsIHdlIHZlbnR1cmUgaW50byB0aGUgcmVhbG0gb2YgSy1OZWFyZXN0IE5laWdoYm9ycyAoay1OTikgbW9kZWxpbmcuIE91ciBleHBsb3JhdGlvbiBiZWdpbnMgYnkgc2VsZWN0aW5nIGEgc29uZyBmcm9tIHRoZSBTcG90aWZ5IFRvcCAyMDAgV2Vla2x5IENoYXJ0cyBvZiAyMDIyIGFuZCBkaXNzZWN0aW5nIGl0cyBhdHRyaWJ1dGVzLiBXZSBsZXZlcmFnZSBrLU5OIHRvIHByZWRpY3Qgd2hldGhlciB0aGlzIHNvbmcgd2lsbCBiZSBsaWtlZCBieSBhIHVzZXIsIEdlb3JnZSwgYW5kIHVuY292ZXIgaXRzIG5lYXJlc3QgbXVzaWNhbCBjb21wYW5pb25zLiBBbG9uZyB0aGUgd2F5LCB3ZSBvcHRpbWl6ZSB0aGUgay12YWx1ZSBmb3Igb3VyIG1vZGVsLCByZWR1Y2UgZGltZW5zaW9uYWxpdHkgZm9yIGVmZmljaWVuY3ksIGFuZCBub3JtYWxpemUgdGhlIGRhdGEgZm9yIGVxdWl0YWJsZSBjb21wYXJpc29ucy4gVGhpcyBzZWN0aW9uIG5vdCBvbmx5IHNob3djYXNlcyB0aGUgcG93ZXIgb2Ygay1OTiBpbiBtdXNpYyByZWNvbW1lbmRhdGlvbiBidXQgYWxzbyBzZXJ2ZXMgYXMgYSBibHVlcHJpbnQgZm9yIGRhdGEgYW5hbHlzdHMgYW5kIHN0YWtlaG9sZGVycyBhaW1pbmcgdG8gZW5oYW5jZSB1c2VyIGV4cGVyaWVuY2VzIGFuZCBkZWNpc2lvbi1tYWtpbmcgaW4gdGhlIGR5bmFtaWMgd29ybGQgb2YgbXVzaWMgZGF0YSBhbmFseXNpcy4gSm9pbiB1cyBhcyB3ZSBoYXJtb25pemUgZGF0YSBzY2llbmNlIGFuZCBtdXNpYyB0byBwcmVkaWN0IGxpa2FiaWxpdHkgYW5kIHVuY292ZXIgdGhlIHNlY3JldHMgb2Ygay1OTi4qDQoNCg0KDQojIyMjICoqTG9hZCBEYXRhKioNCg0KKlRoZSBjb2RlIGJlZ2lucyBieSBsb2FkaW5nIHRoZSAnc3BvdGlmeV90b3BfY2hhcnRzXzIyLmNzdicgZGF0YXNldCwgd2hpY2ggY29udGFpbnMgaW5mb3JtYXRpb24gYWJvdXQgc29uZ3MgdGhhdCBhcHBlYXJlZCBvbiB0aGUgVG9wIDIwMCBXZWVrbHkgKEdsb2JhbCkgY2hhcnRzIG9mIFNwb3RpZnkgaW4gMjAyMi4gRWFjaCByb3cgcmVwcmVzZW50cyBhIHNwZWNpZmljIHNvbmcuKg0KDQpgYGB7cn0NCnRvcC5jaGFydHMuZGYgPC0gcmVhZC5jc3YoInNwb3RpZnlfdG9wX2NoYXJ0c18yMi5jc3YiKQ0KDQp0b3AuY2hhcnRzLmRmIA0KYGBgDQoNCg0KDQojIyMjICoqU29uZyBTZWxlY3Rpb24qKg0KDQoqRmlyc3QsIHdlIHdpbGwgc2VsZWN0IGEgc29uZyBmb3IgYW5hbHlzaXMuKg0KDQpJIGNob3NlIHRoZSBzb25nICJDb2xkIEhlYXJ0IiBieSBFbHRvbiBKb2huLCBEdWEgTGlwYSwgJiBQTkFVIChyb3cgNikuDQpgYGB7cn0NCnRvcC5jaGFydHMuZGZbNiwgXSAgIyByZXR1cm5zIHRoZSByb3cgZm9yIHRoZSBzb25nICJDb2xkIEhlYXJ0Ig0KYGBgDQoNCg0KDQojIyMjICoqU29uZyBJbmZvcm1hdGlvbioqDQoNCipWYXJpb3VzIGF0dHJpYnV0ZXMgb2YgdGhlIHNlbGVjdGVkIHNvbmcgYXJlIHF1ZXJpZWQsIHN1Y2ggYXMgZGFuY2VhYmlsaXR5LCBlbmVyZ3ksIGxvdWRuZXNzLCBzcGVlY2hpbmVzcywgYWNvdXN0aWNuZXNzLCBpbnN0cnVtZW50YWxuZXNzLCBsaXZlbmVzcywgdGVtcG8sIGFuZCBkdXJhdGlvbl9tcy4qDQoNCiAgZGFuY2VhYmlsaXR5Og0KYGBge3J9DQp0b3AuY2hhcnRzLmRmWzYsICJkYW5jZWFiaWxpdHkiXQ0KYGBgDQogIA0KICBlbmVyZ3k6DQpgYGB7cn0NCnRvcC5jaGFydHMuZGZbNiwgImVuZXJneSJdDQpgYGANCiAgDQoNCiAgbG91ZG5lc3M6DQpgYGB7cn0NCnRvcC5jaGFydHMuZGZbNiwgImxvdWRuZXNzIl0NCmBgYA0KICANCg0KICBzcGVlY2hpbmVzczoNCmBgYHtyfQ0KdG9wLmNoYXJ0cy5kZls2LCAic3BlZWNoaW5lc3MiXQ0KYGBgDQogIA0KDQogIGFjb3VzdGljbmVzczoNCmBgYHtyfQ0KdG9wLmNoYXJ0cy5kZls2LCAiYWNvdXN0aWNuZXNzIl0NCmBgYA0KICANCg0KICBpbnN0cnVtZW50YWxuZXNzOg0KYGBge3J9DQpvcHRpb25zKHNjaXBlbiA9IDk5OSkNCnRvcC5jaGFydHMuZGZbNiwgImluc3RydW1lbnRhbG5lc3MiXQ0KYGBgDQogIA0KDQogIGxpdmVuZXNzOg0KYGBge3J9DQp0b3AuY2hhcnRzLmRmWzYsICJsaXZlbmVzcyJdDQpgYGANCiAgDQoNCiAgdGVtcG86DQpgYGB7cn0NCnRvcC5jaGFydHMuZGZbNiwgInRlbXBvIl0NCmBgYA0KICANCg0KICBkdXJhdGlvbl9tczoNCmBgYHtyfQ0KdG9wLmNoYXJ0cy5kZls2LCAiZHVyYXRpb25fbXMiXQ0KYGBgDQogIA0KDQoNCiMjIyMgKipEYXRhIEV4cGxvcmF0aW9uKioNCg0KKkluIHRoaXMgc3RlcCwgd2UgZXhwbG9yZSB0aGUgZGF0YXNldHMgJiBleGFtaW5lIHRoZSB0YXJnZXQgdmFyaWFibGUuKg0KDQpgYGB7cn0NCiMgU3Vic2V0IHNlbGVjdGVkIHNvbmcNCnNvbmcuZGYgPC0gdG9wLmNoYXJ0cy5kZls2LCBdDQoNCiMgRXhhbWluZSBzdHJ1Y3R1cmUgb2YgZGF0YXNldA0Kc3RyKHNvbmcuZGYpICANCmBgYA0KDQoNCiMjIyMgKipSZWFkIEFkZGl0aW9uYWwgRGF0YXNldCoqDQoNCipBIHNlY29uZCBkYXRhc2V0LCAnc3BvdGlmeV9kYXRhLmNzdiwnIGlzIHJlYWQgaW50byB0aGUgZW52aXJvbm1lbnQsIGFuZCBpdHMgc3RydWN0dXJlIGlzIGV4YW1pbmVkLiBJdCBjb250YWlucyByYXRpbmdzIG9mIHZhcmlvdXMgc29uZ3MsIHdpdGggdGhlICd0YXJnZXQnIHZhcmlhYmxlIGluZGljYXRpbmcgd2hldGhlciBHZW9yZ2UgbGlrZWQgdGhlIHNvbmcgKDEgZm9yIGxpa2VkLCAwIGZvciBkaXNsaWtlZCkuKg0KDQpgYGB7cn0NCiMgUmVhZCBkYXRhc2V0IGNvbnRhaW5pbmcgR2VvcmdlJ3MgcmF0aW5ncyBvZiB2YXJpb3VzIHNvbmdzIGludG8gZW52aXJvbm1lbnQNCnNwb3RpZnkuZGYgPC0gcmVhZC5jc3YoInNwb3RpZnlfZGF0YS5jc3YiKQ0KDQojIEV4YW1pbmUgc3RydWN0dXJlIG9mIGRhdGFzZXQNCnN0cihzcG90aWZ5LmRmKQ0KYGBgDQoNCg0KDQpUaGUgdmFyaWFibGUgJ3RhcmdldCcgaXMgYSBjYXRlZ29yaWNhbCB2YXJpYWJsZSBhcyBpdCB0ZWxscyB1cyB3aGV0aGVyIG9yIG5vdCBHZW9yZ2UgbGlrZWQgdGhlIHNvbmcgKGkuZS4sIDEgPSB5ZXMgaGUgbGlrZWQgdGhlIHNvbmcsIDAgPSBoZSBkaWQgbm90IGxpa2UgdGhlIHNvbmcpLiBTaW5jZSB0aGUgY2F0ZWdvcmllcyBhcmUgY29tbXVuaWNhdGVkIHVzaW5nIGJpbmFyeSBkdW1teSB2YXJpYWJsZXMsIHRoZSBkYXRhIHN0cnVjdHVyZSBpcyBpbnRlZ2VyIG9yIG51bWVyaWMgZm9ybS4gDQoNCg0KSWYgdGFyZ2V0IGlzIG5vdCBjdXJyZW50bHkgYSBmYWN0b3IsIGNvbnZlcnQgaXQgaW50byBhIGZhY3Rvci4gSXQgd2lsbCBiZSBvdXIgcmVzcG9uc2UgdmFyaWFibGUgaW4gdGhpcyBtb2RlbC4gVGFyZ2V0IHRlbGxzIHVzIHdoZXRoZXIgR2VvcmdlLCB0aGUgcGVyc29uIHdobyB1cGxvYWRlZCB0aGlzIGRhdGFzZXQsIGxpa2VkIHRoZSBzb25nLiDigJwx4oCdIG1lYW5zIHRoYXQgR2VvcmdlIGxpa2VkIGl0LCBhbmQg4oCcMOKAnSBtZWFucyB0aGF0IGhlIGRpZCBub3QuDQpgYGB7cn0NCnNwb3RpZnkuZGYkdGFyZ2V0IDwtIGFzLmZhY3RvcihzcG90aWZ5LmRmJHRhcmdldCkNCmBgYA0KDQoNCg0KYGBge3J9DQojIFJldHVybiB0YWJsZSB3aXRoIHVuaXF1ZSB2YWx1ZXMgJiB0aGUgdG90YWwgY291bnQgb2Ygb2JzZXJ2YXRpb25zIGNvcnJlc3BvbmRpbmcgdG8gZWFjaCBvZiB0aG9zZSB1bmlxdWUgdmFsdWVzDQp0YWJsZShzcG90aWZ5LmRmJHRhcmdldCkgIA0KYGBgDQpUaGUgdW5pcXVlIHZhbHVlcyBhcmUgIjAiIChpLmUuLCBHZW9yZ2UgZGlkIG5vdCBsaWtlIHRoZSBzb25nKSAmICIxIiAoaS5lLiwgR2VvcmdlIGRpZCBsaWtlIHRoZSBzb25nKS4gVGhlcmUgYXJlIDk5NyBzb25ncyBoZSBkaXNsaWtlZCBhbmQgMTAyMCB0aGF0IGhlIGxpa2VkLg0KDQoNCg0KIyMjIyAqKkhhbmRsaW5nIE1pc3NpbmcgVmFsdWVzKioNCg0KKk5leHQsIHdlIGNoZWNrIGZvciBtaXNzaW5nIHZhbHVlcyBpbiB0aGUgZGF0YXNldCBhbmQgZmluZCB0aGF0IHRoZXJlIGFyZSBub25lLioNCg0KYGBge3J9DQpjYXQoIlRoZXJlIGFyZSIsIHN1bShpcy5uYShzcG90aWZ5LmRmKSksICJtaXNzaW5nIHZhbHVlcyBpbiB0aGUgZGF0YXNldC4iKQ0KYGBgDQoNCg0KIyMjIyAqKkRpbWVuc2lvbiBSZWR1Y3Rpb24qKg0KDQoqVW5uZWNlc3NhcnkgY29sdW1ucyAoZS5nLiwgJ2tleScsICdtb2RlJywgJ3RpbWVfc2lnbmF0dXJlJykgYXJlIHJlbW92ZWQgZnJvbSB0aGUgZGF0YXNldCwgYWltaW5nIHRvIHNpbXBsaWZ5IHRoZSBrLW5lYXJlc3QgbmVpZ2hib3JzIChrLU5OKSBtb2RlbCBhbmQgaW1wcm92ZSBpdHMgZWZmaWNpZW5jeS4qDQoNCmBgYHtyfQ0KIyBSZW1vdmUgdW5uZWNlc3NhcnkgY29sdW1ucyAoZS5nLiwgJ2tleScsICdtb2RlJywgJ3RpbWVfc2lnbmF0dXJlJykNCnNwb3RpZnkuZGYgPC0gc3BvdGlmeS5kZlsgLCAtYyg3LCAxMCwgMTMpXQ0KDQpoZWFkKHNwb3RpZnkuZGYpICAjIFByaW50IGZpcnN0IGZldyByb3dzIHRvIGVuc3VyZSBjb2x1bW5zIHdlcmUgcmVtb3ZlZA0KYGBgDQoNCg0KDQojIyMjICoqRGF0YSBQYXJ0aXRpb25pbmcqKg0KDQoqVGhlIGRhdGFzZXQgaXMgc3BsaXQgaW50byB0cmFpbmluZyBhbmQgdmFsaWRhdGlvbiBzZXRzIHRvIHRyYWluIGFuZCBldmFsdWF0ZSB0aGUgay1OTiBtb2RlbC4qDQoNCmBgYHtyfQ0KIyBTZXQgc2VlZCBmb3IgcmVwbGljYWJpbGl0eQ0Kc2V0LnNlZWQoODApDQoNCiMgUGFydGl0aW9uIGRhdGFzZXQgaW50byB0cmFpbmluZyAmIHZhbGlkYXRpb24gc2V0cw0KdHJhaW4uaW5kZXggPC0gc2FtcGxlKGMoMTpucm93KHNwb3RpZnkuZGYpKSwgbnJvdyhzcG90aWZ5LmRmKSowLjYpDQp0cmFpbi5kZiA8LSBzcG90aWZ5LmRmW3RyYWluLmluZGV4LCBdDQp2YWxpZC5kZiA8LSBzcG90aWZ5LmRmWy10cmFpbi5pbmRleCwgXQ0KYGBgDQoNCg0KDQojIyMjICoqRmVhdHVyZSBTZWxlY3Rpb24qKg0KDQoqVGhlIGNvZGUgY2FsY3VsYXRlcyB0aGUgbWVhbiB2YWx1ZXMgb2YgdmFyaW91cyBhdHRyaWJ1dGVzIGZvciBzb25ncyBHZW9yZ2UgbGlrZWQgYW5kIGRpc2xpa2VkLiBJdCBpZGVudGlmaWVzIHZhcmlhYmxlcyBzaG93aW5nIGEgcGVyY2VudGFnZSBkaWZmZXJlbmNlIG9mIDEwJSBvciBtb3JlIGJldHdlZW4gdGhlIHR3byBncm91cHMgYW5kIHJlbW92ZXMgdGhvc2UgdmFyaWFibGVzIGZyb20gdGhlIHRyYWluaW5nIGRhdGEuKiANCg0KYGBge3J9DQp0cmFpbi5kZi5maWx0ZXJlZCA8LSB0cmFpbi5kZiAlPiUgZ3JvdXBfYnkodGFyZ2V0KSAlPiUgc3VtbWFyaXplKG1lYW4oZGFuY2VhYmlsaXR5KSwgbWVhbihlbmVyZ3kpLCBtZWFuKGxvdWRuZXNzKSwgbWVhbihzcGVlY2hpbmVzcyksIG1lYW4oYWNvdXN0aWNuZXNzKSwgbWVhbihpbnN0cnVtZW50YWxuZXNzKSwgbWVhbihsaXZlbmVzcyksIG1lYW4odGVtcG8pLCBtZWFuKGR1cmF0aW9uX21zKSkNCmBgYA0KDQoNCg0KKklkZW50aWZ5IHdoaWNoIHZhcmlhYmxlcyBzaG93IGEgcGVyY2VudGFnZSBkaWZmZXJlbmNlIG9mIDEwJSBvciBtb3JlPyBJZiBhbnkgdmFyaWFibGVzIHNob3cgbGVzcyB0aGFuIDEwJSBkaWZmZXJlbmNlIGluIG1lYW4gdmFsdWUgYmV0d2VlbiB0aGUgdHdvIGdyb3VwcyAoc29uZ3MgdGhhdCBHZW9yZ2UgbGlrZXMsIGFuZCBzb25ncyB0aGF0IEdlb3JnZSBkb2VzbuKAmXQgbGlrZSksIHRoZW4gcmVtb3ZlIHRob3NlIHZhcmlhYmxlcyBlbnRpcmVseS4qDQoNCmBgYHtyfQ0KICMgVXNlICdmb3InIGxvb3AgdG8gY3JlYXRlIHBlcmNlbnRfY2hhbmdlIG9iamVjdA0KZm9yIChpIGluIDI6bmNvbCh0cmFpbi5kZi5maWx0ZXJlZCkpIHsNCiAgcGVyY2VudF9jaGFuZ2UgPC0gKCh0cmFpbi5kZi5maWx0ZXJlZFsxLCBjKDI6MTApXSAtIHRyYWluLmRmLmZpbHRlcmVkWzIsIGMoMjoxMCldKS90cmFpbi5kZi5maWx0ZXJlZFsxLCBjKDI6MTApXSkqMTAwDQp9IA0KDQpwZXJjZW50X2NoYW5nZSAgIyBwcmludCBwZXJjZW50YWdlIGRpZmZlcmVuY2Ugb3V0cHV0DQpgYGANCg0KYGBge3J9DQojIFJldHVybiBjb2x1bW5zICh2YXJpYWJsZXMpIG9mIHRoZSAncGVyY2VudF9jaGFuZ2UnIG9iamVjdCwgd2hlcmUgdGhlIHBlcmNlbnRhZ2UgZGlmZmVyZW5jZSBpcyAxMCUgb3IgbW9yZQ0Kd2hpY2goYWJzKHBlcmNlbnRfY2hhbmdlWyAsIF0pID49IDEwKSANCmBgYA0KVGhlIHZhcmlhYmxlcyAic3BlZWNoaW5lc3MiIChjb2x1bW4gNCksICJhY291c3RpY25lc3MiIChjb2x1bW4gNSksICYgImluc3RydW1lbnRhbG5lc3MiIChjb2x1bW4gNikgc2hvdyBhIHBlcmNlbnRhZ2UgZGlmZmVyZW5jZSBvZiAxMCUgb3IgbW9yZS4NCg0KDQpgYGB7cn0NCiMgTGlzdCB2YXJpYWJsZSBuYW1lcyBpbiB0cmFpbmluZyBkYXRhIHNvIHdlIGNhbiByZW1vdmUgdGhlc2UgMyB2YXJpYWJsZXMNCm5hbWVzKHRyYWluLmRmKSANCmBgYA0KDQpgYGB7cn0NCnRyYWluLmRmIDwtIHRyYWluLmRmWyAsIC1jKDIsIDYsIDkpXQ0KYGBgDQoNCg0KDQpJdCBpcyByZWFzb25hYmxlIHRvIGNvbnNpZGVyIHJlbW92aW5nIHZhcmlhYmxlcyB3aXRoIGhpZ2hseSBzaW1pbGFyIHZhbHVlcyBpbiBhIGstbmVhcmVzdCBuZWlnaGJvcnMgKGstTk4pIG1vZGVsIGJlY2F1c2UgZG9pbmcgc28gaGVscHMgY3JlYXRlIGEgbW9yZSBwYXJzaW1vbmlvdXMgbW9kZWwuIEJ5IHJlZHVjaW5nIHRoZSBudW1iZXIgb2YgcHJlZGljdG9yIHZhcmlhYmxlcywgd2UgYWltIHRvIGltcHJvdmUgdGhlIG1vZGVsJ3MgYWJpbGl0eSB0byBnZW5lcmFsaXplIGFuZCBtYWtlIGFjY3VyYXRlIHByZWRpY3Rpb25zIHdoZW4gZmFjZWQgd2l0aCBuZXcgZGF0YS4gDQoNCk1vcmVvdmVyLCB3aGVuIHdlIGhhdmUgYW4gZXhjZXNzIG9mIHByZWRpY3RvciB2YXJpYWJsZXMsIHRoZSBtb2RlbCByZXF1aXJlcyBhIGxhcmdlciBudW1iZXIgb2YgdHJhaW5pbmcgcmVjb3JkcyB0byBlc3RhYmxpc2ggbWVhbmluZ2Z1bCBwYXR0ZXJucy4gQ29uc2VxdWVudGx5LCB0aGUgdGltZSBpdCB0YWtlcyB0byBpZGVudGlmeSB0aGUgay1uZWFyZXN0IG5laWdoYm9ycyBmb3IgbmV3IHJlY29yZHMgYWxzbyBpbmNyZWFzZXMuIA0KDQpUaGVyZWZvcmUsIHN0cml2aW5nIGZvciBhIHNpbXBsZXIsIG1vcmUgY29uY2lzZSBtb2RlbCBjYW4gc2lnbmlmaWNhbnRseSBib29zdCBpdHMgY29uZmlkZW5jZSBhbmQgZWZmaWNpZW5jeSB3aGVuIGNvbmZyb250ZWQgd2l0aCByZWFsLXdvcmxkIGRhdGEuDQoNCg0KDQojIyMjICoqRGF0YSBOb3JtYWxpemF0aW9uKioNCg0KKlRoZSBkYXRhIGlzIG5vcm1hbGl6ZWQgdXNpbmcgdGhlIHByZVByb2Nlc3MgZnVuY3Rpb24uIFRoaXMgc3RlcCBpcyBjcnVjaWFsIGZvciBrLU5OIGFsZ29yaXRobXMsIGFzIGl0IGVuc3VyZXMgdGhhdCBhbGwgdmFyaWFibGVzIGNvbnRyaWJ1dGUgZXF1YWxseSB0byBkaXN0YW5jZSBjYWxjdWxhdGlvbnMuKg0KDQpgYGB7cn0NCm5vcm0udmFsdWVzIDwtIHByZVByb2Nlc3ModHJhaW4uZGZbICwgMjo3XSwgbWV0aG9kID0gYygiY2VudGVyIiwgInNjYWxlIikpICANCg0KIyBvbmx5IHVzZWQgdmFsdWVzIGluIGNvbHVtbnMgbnVtYmVyIDItOCAoaS5lLiwgZm9yIG51bWVyaWMgdmFyaWFibGVzIG9ubHkpDQoNCg0KIyBJbml0aWFsaXplcyBub3JtYWxpemVkIGRmJ3MgdG8gb3JpZ2luYWwgZGYNCnRyYWluLm5vcm0uZGYgPC0gdHJhaW4uZGYNCnZhbGlkLm5vcm0uZGYgPC0gdmFsaWQuZGYNCnNwb3RpZnkubm9ybS5kZiA8LSBzcG90aWZ5LmRmDQoNCnRyYWluLm5vcm0uZGZbICwgMjo3XSA8LSBwcmVkaWN0KG5vcm0udmFsdWVzLCB0cmFpbi5kZlsgLCAyOjddKQ0KdmFsaWQubm9ybS5kZlsgLCBjKDM6NSwgNzo4LCAxMCldIDwtIHByZWRpY3Qobm9ybS52YWx1ZXMsIHZhbGlkLmRmWyAsIGMoMzo1LCA3OjgsIDEwKV0pDQpzcG90aWZ5Lm5vcm0uZGZbICwgYygzOjUsIDc6OCwgMTApXSA8LSBwcmVkaWN0KG5vcm0udmFsdWVzLCBzcG90aWZ5LmRmWyAsIGMoMzo1LCA3OjgsIDEwKV0pDQoNCg0Kc29uZy5ub3JtLmRmIDwtIHByZWRpY3Qobm9ybS52YWx1ZXMsIHNvbmcuZGZbLCBjKDY6NywgOSwgMTQ6MTUsIDE3KV0pDQpgYGANCg0KDQoNCiMjIyMgKipNb2RlbCBCdWlsZGluZyoqDQoNCipVc2luZyBhIGstdmFsdWUgb2YgNywgdGhlIGNvZGUgZ2VuZXJhdGVzIGEgcHJlZGljdGVkIGNsYXNzaWZpY2F0aW9uIGZvciB0aGUgc29uZyAiQ29sZCBIZWFydC4iIEl0IGFsc28gaWRlbnRpZmllcyB0aGUgc29uZydzIDcgbmVhcmVzdCBuZWlnaGJvcnMuKg0KDQpgYGB7cn0NCm5uIDwtIGtubih0cmFpbiA9IHRyYWluLm5vcm0uZGZbICwgMjo3XSwgdGVzdCA9IHNvbmcubm9ybS5kZiwgY2wgPSB0cmFpbi5ub3JtLmRmWyAsIDldLCBrID0gNykNCg0Kbm4NCmBgYA0KVGhlIG1vZGVsIHByZWRpY3RlZCBHZW9yZ2Ugd291bGQgbGlrZSB0aGUgc29uZyAiQ29sZCBIZWFydC4iIEluIG90aGVyIHdvcmRzLCB0aGUgbW9kZWwgY2xhc3NpZmllZCB0aGUgc29uZyAiQ29sZCBIZWFydCIgYXMgYmVsb25naW5nIHRvIHRoZSAiMSIgKGUuZy4sIHNvbmdzIEdlb3JnZSB3aWxsIGxpa2UpIGNsYXNzLiANCg0KVGhlIHRpdGxlcywgYXJ0aXN0cywgJiBvdXRjb21lIGNsYXNzZXMgb2YgdGhlIGstbmVhcmVzdCBuZWlnaGJvcnMgYXJlIGFzIGZvbGxvd3MNCmBgYHtyfQ0Ka25uX291dHB1dCA8LSB0cmFpbi5ub3JtLmRmW2MoNzI3LCAxNzAsICAzODAsICA3MjksICAgNDEsICAgNTAsIDExNzEpLCA5OjExXQ0Ka25uX291dHB1dA0KYGBgDQoNCg0KDQojIyMjICoqTW9kZWwgRXZhbHVhdGlvbioqDQoNCipUaGUgY29kZSBldmFsdWF0ZXMgdGhlIG1vZGVsJ3MgcGVyZm9ybWFuY2UgZm9yIGRpZmZlcmVudCBrLXZhbHVlcyBhbmQgZGV0ZXJtaW5lcyB0aGUgb3B0aW1hbCBrLXZhbHVlLCB3aGljaCBpcyBmb3VuZCB0byBiZSA2LioNCg0KYGBge3J9DQojIEluaXRpYWxpemUgZGYgd2l0aCBjb2x1bW5zIGZvciAnaycgJiAnYWNjdXJhY3knDQphY2N1cmFjeS5kZiA8LSBkYXRhLmZyYW1lKGsgPSBzZXEoMSwgMjAsIDEpLCBhY2N1cmFjeSA9IHJlcCgwLCAyMCkpDQoNCmZvciAoaSBpbiAxOjIwKSB7DQogIGtubi5wcmVkIDwtIGtubih0cmFpbi5ub3JtLmRmWyAsIDI6N10sIHZhbGlkLm5vcm0uZGZbICwgYygzOjUsIDc6OCwgMTApXSwgY2wgPSB0cmFpbi5ub3JtLmRmWyAsIDldLCBrID0gaSkNCiAgYWNjdXJhY3kuZGZbaSwgMl0gPC0gY29uZnVzaW9uTWF0cml4KGtubi5wcmVkLCB2YWxpZC5ub3JtLmRmWyAsIDEyXSkkb3ZlcmFsbFsxXQ0KfQ0KDQphY2N1cmFjeS5kZiANCmBgYA0KVGhlIG9wdGltYWwgJ2snIHZhbHVlIGlzIDYgYmVjYXVzZSBpdCBoYXMgdGhlIG1heGltdW0gYWNjdXJhY3kgcmF0ZSAoYW5kIG1pbmltdW0gZXJyb3IgcmF0ZSkgYmVmb3JlIHRoZSBhY2N1cmFjeSByYXRlIHN0YXJ0cyB0byBkZWNyZWFzZSBhZ2FpbiBhdCBrPTcgJiBrPTguIFRob3VnaCB3ZSBzZWUgdGhhdCB0aGUgYWNjdXJhY3kgcmF0ZSBzdGFydHMgdG8gaW5jcmVhc2UgYWdhaW4gYXQgaz05LCB3ZSBtdXN0IGNvbnNpZGVyIHRoZSBiaWFzLXZhcmlhbmNlIHRyYWRlb2ZmOiBhcyBrIGluY3JlYXNlcywgdGhlIG1vZGVsIHN0YXJ0cyB0byBhcHByb3hpbWF0ZSB0aGUgdHJhaW5pbmcgc2V0IGFuZCB3b3VsZCBwcmVkaWN0IHRoZSBtYWpvcml0eSBjbGFzcyBpbiB0aGUgZGF0YSBzZXQgZm9yIGFsbCBjYXNlIHNjZW5hcmlvcy4NCg0KDQoNCiMjIyMgKipWaXN1YWxpemF0aW9uKioNCg0KKkEgc2NhdHRlcnBsb3QgaXMgY3JlYXRlZCB0byB2aXN1YWxpemUgdGhlIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIGstdmFsdWVzIGFuZCBhY2N1cmFjeS4qDQoNCmBgYHtyfQ0KbGlicmFyeShnZ3Bsb3QyKQ0KDQpnZ3Bsb3QoZGF0YSA9IGFjY3VyYWN5LmRmLCBhZXMoeD0gaywgeT0gYWNjdXJhY3kpKSArIGdlb21fcG9pbnQoKSArIGxhYnModGl0bGUgPSAiQWNjdXJhY3kgdnMuIEsiLCB4PSAiSyIsIHk9ICJBY2N1cmFjeSIpDQpgYGANCg0KDQoNCiMjIyMgKipGaW5hbCBNb2RlbCAmIFByZWRpY3Rpb24qKg0KDQoqVGhlIGstTk4gbW9kZWwgaXMgcmVydW4gd2l0aCB0aGUgb3B0aW1hbCBrLXZhbHVlLCBhbmQgcHJlZGljdGlvbnMgYXJlIG1hZGUgZm9yIHRoZSBzZWxlY3RlZCBzb25nLiBUaGUgbW9kZWwgcHJlZGljdHMgdGhhdCBHZW9yZ2Ugd2lsbCBsaWtlIHRoZSBzb25nICJDb2xkIEhlYXJ0LCIgd2l0aCB0aGUgbmVhcmVzdCBuZWlnaGJvcnMgaWRlbnRpZmllZC4qDQoNCmBgYHtyfQ0Kbm4ubmV3IDwtIGtubih0cmFpbiA9IHNwb3RpZnkubm9ybS5kZlsgLCBjKDM6NSwgNzo4LCAxMCldLCB0ZXN0ID0gc29uZy5ub3JtLmRmLCBjbCA9IHNwb3RpZnkubm9ybS5kZlsgLCAxMl0sIGsgPSA2KQ0KDQpubi5uZXcNCmBgYA0KDQpUaGUgbW9kZWwgcHJlZGljdGVkIEdlb3JnZSB3b3VsZCBsaWtlIHRoZSBzb25nICJDb2xkIEhlYXJ0LiIgSW4gb3RoZXIgd29yZHMsIHRoZSBtb2RlbCBjbGFzc2lmaWVkIHRoZSBzb25nICJDb2xkIEhlYXJ0IiBhcyBiZWxvbmdpbmcgdG8gdGhlICIxIiAoZS5nLiwgc29uZ3MgR2VvcmdlIHdpbGwgbGlrZSkgY2xhc3MuIA0KDQoNCmBgYHtyfQ0Ka25uX25ld19vdXRwdXQgPC0gc3BvdGlmeS5ub3JtLmRmW2MoNDU0LCAgMTYzLCAgNTIwLCAgMzcyLCAgNDc0LCAxMzk4KSwgMTI6MTRdDQprbm5fbmV3X291dHB1dA0KYGBgDQoNClRoZSBmaW5hbCByZXN1bHQgaXMgdGhlIHNhbWUgYXMgd2hlbiBJIGZpcnN0IHJhbiB0aGUgay1ubiBmdW5jdGlvbiwgYnV0IHNvbWUgb2YgdGhlICJrLW5lYXJlc3QgbmVpZ2hib3JzIiBjaGFuZ2VkLiBUaGUgc29uZ3MgIkZvcmV2ZXIiLCAiQ2FuIEkgIEtpY2sgSXQ/IiwgIk1hbWFjaXRhIiwgJiAiw6rCt1wyMDDDqsKwXDIwMCBSZXR1cm4gSG9tZSIgd2VyZSBmb3VyIG9mIHNldmVuIGstbmVhcmVzdCBuZWlnaGJvcnMgaW4gdGhlIGZpcnN0IGV4YW1wbGUsIHdoaWxlICJEbyBJdCBSb2dlciIgYW5kICJCaXpuZXNzIiBhcmUgbmV3IGstbmVhcmVzdCBuZWlnaGJvcnMgKGUuZy4sIHRoZSBmaXJzdCBpdGVyYXRpb24gb2Yga25uIGluY2x1ZGVkIDIgc29uZ3MgdGhhdCBkaWZmZXIgZnJvbSB0aGUgbGF0dGVyIDIsIGFuZCAxIG1vcmUgYWRkaXRpb25hbCBzb25nKS4gDQoNCg0KDQojIyMjICoqQ29uY2x1c2lvbnMgKGstbm4pKioNCg0KSW4gdGhlIGZvbGxvd2luZyBzZWN0aW9uLCB3ZSBkZWx2ZSBpbnRvIHRoZSBrZXkgaW5zaWdodHMgZ2xlYW5lZCBmcm9tIHRoZSBhbmFseXNpcyBhbmQgZXhwbG9yZSBob3cgdGhlc2UgaW5zaWdodHMgY2FuIGJlIGxldmVyYWdlZCBieSB2YXJpb3VzIHN0YWtlaG9sZGVycy4gRnJvbSBwcmVkaWN0aW5nIHNvbmcgbGlrYWJpbGl0eSB0byBvcHRpbWl6aW5nIG1vZGVsIHBhcmFtZXRlcnMsIHRoZXNlIGZpbmRpbmdzIG9mZmVyIHZhbHVhYmxlIGd1aWRhbmNlIGZvciBlbmhhbmNpbmcgZGVjaXNpb24tbWFraW5nIGFuZCB1c2VyIGV4cGVyaWVuY2VzIGluIHRoZSBkeW5hbWljIHJlYWxtIG9mIG11c2ljIGFuZCBkYXRhIGFuYWx5c2lzLiBMZXQncyB1bmNvdmVyIHRoZSBpbXBsaWNhdGlvbnMgb2YgdGhpcyBhbmFseXNpcyBmb3IgZGlmZmVyZW50IHN0YWtlaG9sZGVycyBpbiB0aGUgbXVzaWMgaW5kdXN0cnkgYW5kIGJleW9uZC4NCg0KIyMjIyMgKipLZXkgSW5zaWdodHMqKg0KDQoqIFRoZSBrLU5OIG1vZGVsIHByZWRpY3RzIHRoYXQgR2VvcmdlIHdpbGwgbGlrZSB0aGUgc29uZyAiQ29sZCBIZWFydCIgYnkgRWx0b24gSm9obiwgRHVhIExpcGEsIGFuZCBQTkFVLg0KDQoqIFRoZSBtb2RlbCBpZGVudGlmaWVkIHRoZSBuZWFyZXN0IG5laWdoYm9ycyBvZiB0aGUgc29uZywgd2hpY2ggaW5jbHVkZXMgc29uZ3Mgc3VjaCBhcyAiRG8gSXQgUm9nZXIsIiAiRm9yZXZlciwiIGFuZCAiTWFtYWNpdGEuIg0KDQoqIFRoZSBvcHRpbWFsIGstdmFsdWUgZm9yIHRoZSBtb2RlbCBpcyBkZXRlcm1pbmVkIHRvIGJlIDYsIGJhc2VkIG9uIGV2YWx1YXRpb24gcmVzdWx0cy4NCg0KKiBEaW1lbnNpb24gcmVkdWN0aW9uIHdhcyBwZXJmb3JtZWQgYnkgcmVtb3ZpbmcgdmFyaWFibGVzIHdpdGggbG93IGltcGFjdCBvbiB0aGUgbW9kZWwsIHdoaWNoIGNhbiBsZWFkIHRvIGEgbW9yZSBlZmZpY2llbnQgYW5kIGludGVycHJldGFibGUgbW9kZWwuDQoNCiogRGF0YSBub3JtYWxpemF0aW9uIHdhcyBhcHBsaWVkIHRvIGVuc3VyZSB0aGF0IGFsbCB2YXJpYWJsZXMgY29udHJpYnV0ZSBlcXVhbGx5IHRvIHRoZSBrLU5OIGRpc3RhbmNlIGNhbGN1bGF0aW9ucy4NCg0KIyMjIyMgKipTdGFrZWhvbGRlciBVc2UqKg0KDQoqICpNdXNpYyBSZWNvbW1lbmRhdGlvbiBQbGF0Zm9ybXMqOiBNdXNpYyByZWNvbW1lbmRhdGlvbiBwbGF0Zm9ybXMgbGlrZSBTcG90aWZ5IGNhbiB1c2UgdGhpcyBhbmFseXNpcyB0byBlbmhhbmNlIHRoZWlyIHJlY29tbWVuZGF0aW9uIGFsZ29yaXRobXMuIEJ5IHByZWRpY3Rpbmcgd2hldGhlciBhIHVzZXIgd2lsbCBsaWtlIGEgc29uZyBiYXNlZCBvbiB0aGVpciBsaXN0ZW5pbmcgaGlzdG9yeSBhbmQgc29uZyBhdHRyaWJ1dGVzLCB0aGV5IGNhbiBpbXByb3ZlIHVzZXIgZW5nYWdlbWVudC4NCg0KKiAqUmVjb3JkIExhYmVscyo6IFJlY29yZCBsYWJlbHMgY2FuIGJlbmVmaXQgZnJvbSB1bmRlcnN0YW5kaW5nIHdoaWNoIGF0dHJpYnV0ZXMgb2YgYSBzb25nIGNvbnRyaWJ1dGUgdG8gaXRzIGxpa2FiaWxpdHkuIFRoaXMgaW5mb3JtYXRpb24gY2FuIGluZm9ybSB0aGVpciBtdXNpYyBwcm9kdWN0aW9uIGFuZCBwcm9tb3Rpb24gc3RyYXRlZ2llcy4NCg0KKiAqQXJ0aXN0cyo6IEFydGlzdHMgY2FuIGdhaW4gaW5zaWdodHMgaW50byB3aGF0IGFzcGVjdHMgb2YgdGhlaXIgbXVzaWMgYXJlIG1vcmUgbGlrZWx5IHRvIHJlc29uYXRlIHdpdGggdGhlaXIgYXVkaWVuY2UuIFRoaXMga25vd2xlZGdlIGNhbiBoZWxwIHRoZW0gY3JlYXRlIG11c2ljIHRoYXQgYWxpZ25zIHdpdGggdGhlaXIgZmFucycgcHJlZmVyZW5jZXMuDQoNCiogKkRhdGEgQW5hbHlzdHMqOiBEYXRhIGFuYWx5c3RzIGFuZCBkYXRhIHNjaWVudGlzdHMgY2FuIHVzZSB0aGlzIGFuYWx5c2lzIGFzIGEgdGVtcGxhdGUgZm9yIGJ1aWxkaW5nIHByZWRpY3RpdmUgbW9kZWxzIG9uIHNpbWlsYXIgZGF0YXNldHMuIFRoZSBjb2RlIGFuZCBwcm9jZXNzIHNlcnZlIGFzIGEgcHJhY3RpY2FsIGV4YW1wbGUgb2YgZGF0YSBwcmVwcm9jZXNzaW5nLCBmZWF0dXJlIHNlbGVjdGlvbiwgYW5kIG1vZGVsIGV2YWx1YXRpb24uDQoNCiogKk11c2ljIFJlc2VhcmNoZXJzKjogUmVzZWFyY2hlcnMgaW4gdGhlIGZpZWxkIG9mIG11c2ljIGFuYWx5c2lzIGFuZCBwc3ljaG9sb2d5IGNhbiB1c2UgdGhlIGZpbmRpbmdzIHRvIHN0dWR5IHRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiBzb25nIGF0dHJpYnV0ZXMgYW5kIGxpc3RlbmVyIHByZWZlcmVuY2VzLCBjb250cmlidXRpbmcgdG8gdGhlIHVuZGVyc3RhbmRpbmcgb2YgbXVzaWMgcHN5Y2hvbG9neS4NCg0KSW4gc3VtbWFyeSwgdGhlIGFuYWx5c2lzIHByb3ZpZGVzIHZhbHVhYmxlIGluc2lnaHRzIGludG8gcHJlZGljdGluZyBzb25nIGxpa2FiaWxpdHkgYW5kIGNhbiBiZSB1dGlsaXplZCBieSB2YXJpb3VzIHN0YWtlaG9sZGVycyBpbiB0aGUgbXVzaWMgaW5kdXN0cnkgYW5kIGJleW9uZCB0byBlbmhhbmNlIGRlY2lzaW9uLW1ha2luZyBhbmQgdXNlciBleHBlcmllbmNlcy4NCg0KDQo=