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"
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=