Nigerian Music scraped from Spotify - an
analysis
Clustering is a type of Unsupervised
Learning that presumes that a dataset is unlabelled or that its
inputs are not matched with predefined outputs. It uses various
algorithms to sort through unlabeled data and provide groupings
according to patterns it discerns in the data.
Pre-lecture
quiz
Introduction
Clustering
is very useful for data exploration. Let’s see if it can help discover
trends and patterns in the way Nigerian audiences consume music.
✅ Take a minute to think about the uses of clustering. In real life,
clustering happens whenever you have a pile of laundry and need to sort
out your family members’ clothes 🧦👕👖🩲. In data science, clustering
happens when trying to analyze a user’s preferences, or determine the
characteristics of any unlabeled dataset. Clustering, in a way, helps
make sense of chaos, like a sock drawer.
In a professional setting, clustering can be used to determine things
like market segmentation, determining what age groups buy what items,
for example. Another use would be anomaly detection, perhaps to detect
fraud from a dataset of credit card transactions. Or you might use
clustering to determine tumors in a batch of medical scans.
✅ Think a minute about how you might have encountered clustering ‘in
the wild’, in a banking, e-commerce, or business setting.
🎓 Interestingly, cluster analysis originated in the fields of
Anthropology and Psychology in the 1930s. Can you imagine how it might
have been used?
Alternately, you could use it for grouping search results - by
shopping links, images, or reviews, for example. Clustering is useful
when you have a large dataset that you want to reduce and on which you
want to perform more granular analysis, so the technique can be used to
learn about data before other models are constructed.
✅ Once your data is organized in clusters, you assign it a cluster
Id, and this technique can be useful when preserving a dataset’s
privacy; you can instead refer to a data point by its cluster id, rather
than by more revealing identifiable data. Can you think of other reasons
why you’d refer to a cluster Id rather than other elements of the
cluster to identify it?
Getting started with clustering
🎓 How we create clusters has a lot to do with how we gather up the
data points into groups. Let’s unpack some vocabulary:
🎓 ‘Transductive’
vs. ‘inductive’
Transductive inference is derived from observed training cases that
map to specific test cases. Inductive inference is derived from training
cases that map to general rules which are only then applied to test
cases.
An example: Imagine you have a dataset that is only partially
labelled. Some things are ‘records’, some ‘cds’, and some are blank.
Your job is to provide labels for the blanks. If you choose an inductive
approach, you’d train a model looking for ‘records’ and ‘cds’, and apply
those labels to your unlabeled data. This approach will have trouble
classifying things that are actually ‘cassettes’. A transductive
approach, on the other hand, handles this unknown data more effectively
as it works to group similar items together and then applies a label to
a group. In this case, clusters might reflect ‘round musical things’ and
‘square musical things’.
🎓 ‘Non-flat’
vs. ‘flat’ geometry
Derived from mathematical terminology, non-flat vs. flat geometry
refers to the measure of distances between points by either ‘flat’ (Euclidean) or
‘non-flat’ (non-Euclidean) geometrical methods.
‘Flat’ in this context refers to Euclidean geometry (parts of which
are taught as ‘plane’ geometry), and non-flat refers to non-Euclidean
geometry. What does geometry have to do with machine learning? Well, as
two fields that are rooted in mathematics, there must be a common way to
measure distances between points in clusters, and that can be done in a
‘flat’ or ‘non-flat’ way, depending on the nature of the data. Euclidean
distances are measured as the length of a line segment between two
points. Non-Euclidean
distances are measured along a curve. If your data, visualized,
seems to not exist on a plane, you might need to use a specialized
algorithm to handle it.
Infographic by Dasani Madipalli
🎓 ‘Distances’
Clusters are defined by their distance matrix, e.g. the distances
between points. This distance can be measured a few ways. Euclidean
clusters are defined by the average of the point values, and contain a
‘centroid’ or center point. Distances are thus measured by the distance
to that centroid. Non-Euclidean distances refer to ‘clustroids’, the
point closest to other points. Clustroids in turn can be defined in
various ways.
🎓 ‘Constrained’
Constrained
Clustering introduces ‘semi-supervised’ learning into this
unsupervised method. The relationships between points are flagged as
‘cannot link’ or ‘must-link’ so some rules are forced on the
dataset.
An example: If an algorithm is set free on a batch of unlabelled or
semi-labelled data, the clusters it produces may be of poor quality. In
the example above, the clusters might group ‘round music things’ and
‘square music things’ and ‘triangular things’ and ‘cookies’. If given
some constraints, or rules to follow (“the item must be made of
plastic”, “the item needs to be able to produce music”) this can help
‘constrain’ the algorithm to make better choices.
🎓 ‘Density’
Data that is ‘noisy’ is considered to be ‘dense’. The distances
between points in each of its clusters may prove, on examination, to be
more or less dense, or ‘crowded’ and thus this data needs to be analyzed
with the appropriate clustering method. This
article demonstrates the difference between using K-Means clustering
vs. HDBSCAN algorithms to explore a noisy dataset with uneven cluster
density.
Deepen your understanding of clustering techniques in this Learn
module
Clustering algorithms
There are over 100 clustering algorithms, and their use depends on
the nature of the data at hand. Let’s discuss some of the major
ones:
- Hierarchical clustering. If an object is classified
by its proximity to a nearby object, rather than to one farther away,
clusters are formed based on their members’ distance to and from other
objects. Hierarchical clustering is characterized by repeatedly
combining two clusters.
Infographic by Dasani Madipalli
Centroid clustering. This popular algorithm
requires the choice of ‘k’, or the number of clusters to form, after
which the algorithm determines the center point of a cluster and gathers
data around that point. K-means
clustering is a popular version of centroid clustering which
separates a data set into pre-defined K groups. The center is determined
by the nearest mean, thus the name. The squared distance from the
cluster is minimized.
Distribution-based clustering. Based in
statistical modeling, distribution-based clustering centers on
determining the probability that a data point belongs to a cluster, and
assigning it accordingly. Gaussian mixture methods belong to this
type.
Density-based clustering. Data points are
assigned to clusters based on their density, or their grouping around
each other. Data points far from the group are considered outliers or
noise. DBSCAN, Mean-shift and OPTICS belong to this type of
clustering.
Grid-based clustering. For multi-dimensional
datasets, a grid is created and the data is divided amongst the grid’s
cells, thereby creating clusters.
The best way to learn about clustering is to try it for yourself, so
that’s what you’ll do in this exercise.
We’ll require some packages to knock-off this module. You can have
them installed as:
install.packages(c('tidyverse', 'tidymodels', 'DataExplorer', 'summarytools', 'plotly', 'paletteer', 'corrplot', 'patchwork'))
Alternatively, the script below checks whether you have the packages
required to complete this module and installs them for you in case some
are missing.
suppressWarnings(if(!require("pacman")) install.packages("pacman"))
## Loading required package: pacman
pacman::p_load('tidyverse', 'tidymodels', 'DataExplorer', 'summarytools', 'plotly', 'paletteer', 'corrplot', 'patchwork')
##
## The downloaded binary packages are in
## /var/folders/c9/r3f6t3kj3wv9jrh50g63hp1r0000gn/T//RtmplRAI5s/downloaded_packages
##
## summarytools installed
## Warning in pacman::p_load("tidyverse", "tidymodels", "DataExplorer", "summarytools", : Failed to install/load:
## summarytools
knitr::opts_chunk$set(warning = F, message = F)
Exercise - cluster your data
Clustering as a technique is greatly aided by proper visualization,
so let’s get started by visualizing our music data. This exercise will
help us decide which of the methods of clustering we should most
effectively use for the nature of this data.
Let’s hit the ground running by importing the data.
# Load the core tidyverse and make it available in your current R session
library(tidyverse)
# Import the data into a tibble
df <- read_csv(file = "https://raw.githubusercontent.com/microsoft/ML-For-Beginners/main/5-Clustering/data/nigerian-songs.csv")
# View the first 5 rows of the data set
df %>%
slice_head(n = 5)
Sometimes, we may want some little more information on our data. We
can have a look at the data
and its structure
by using the glimpse()
function:
# Glimpse into the data set
df %>%
glimpse()
## Rows: 530
## Columns: 16
## $ name <chr> "Sparky", "shuga rush", "LITT!", "Confident / Feeling…
## $ album <chr> "Mandy & The Jungle", "EVERYTHING YOU HEARD IS TRUE",…
## $ artist <chr> "Cruel Santino", "Odunsi (The Engine)", "AYLØ", "Lady…
## $ artist_top_genre <chr> "alternative r&b", "afropop", "indie r&b", "nigerian …
## $ release_date <dbl> 2019, 2020, 2018, 2019, 2018, 2020, 2018, 2018, 2019,…
## $ length <dbl> 144000, 89488, 207758, 175135, 152049, 184800, 202648…
## $ popularity <dbl> 48, 30, 40, 14, 25, 26, 29, 27, 36, 30, 33, 35, 46, 2…
## $ danceability <dbl> 0.666, 0.710, 0.836, 0.894, 0.702, 0.803, 0.818, 0.80…
## $ acousticness <dbl> 0.8510, 0.0822, 0.2720, 0.7980, 0.1160, 0.1270, 0.452…
## $ energy <dbl> 0.420, 0.683, 0.564, 0.611, 0.833, 0.525, 0.587, 0.30…
## $ instrumentalness <dbl> 5.34e-01, 1.69e-04, 5.37e-04, 1.87e-04, 9.10e-01, 6.6…
## $ liveness <dbl> 0.1100, 0.1010, 0.1100, 0.0964, 0.3480, 0.1290, 0.590…
## $ loudness <dbl> -6.699, -5.640, -7.127, -4.961, -6.044, -10.034, -9.8…
## $ speechiness <dbl> 0.0829, 0.3600, 0.0424, 0.1130, 0.0447, 0.1970, 0.199…
## $ tempo <dbl> 133.015, 129.993, 130.005, 111.087, 105.115, 100.103,…
## $ time_signature <dbl> 5, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 5, 4, 4, 4, 4, 4,…
Good job!💪
We can observe that glimpse()
will give you the total
number of rows (observations) and columns (variables), then, the first
few entries of each variable in a row after the variable name. In
addition, the data type of the variable is given immediately
after each variable’s name inside < >
.
DataExplorer::introduce()
can summarize this information
neatly:
# Describe basic information for our data
df %>%
introduce()
# A visual display of the same
df %>%
plot_intro()
Awesome! We have just learnt that our data has no missing values.
While we are at it, we can explore common central tendency statistics
(e.g mean
and median) and
measures of dispersion (e.g standard
deviation) using summarytools::descr()
# Describe common statistics
df %>% descr(stats = "common")
Let’s look at the general values of the data. Note that popularity
can be 0
, which show songs that have no ranking. We’ll
remove those shortly.
🤔 If we are working with clustering, an unsupervised method that
does not require labeled data, why are we showing this data with labels?
In the data exploration phase, they come in handy, but they are not
necessary for the clustering algorithms to work.
1. Explore popular genres
Let’s go ahead and find out the most popular genres 🎶 by making a
count of the instances it appears.
# Popular genres
top_genres <- df %>%
count(artist_top_genre, sort = TRUE) %>%
# Encode to categorical and reorder the according to count
mutate(artist_top_genre = factor(artist_top_genre) %>% fct_inorder())
# Print the top genres
top_genres
That went well! They say a picture is worth a thousand rows of a data
frame (actually nobody ever says that 😅). But you get the gist of it,
right?
One way to visualize categorical data (character or factor variables)
is using barplots. Let’s make a barplot of the top 10 genres:
# Change the default gray theme
theme_set(theme_light())
# Visualize popular genres
top_genres %>%
slice(1:10) %>%
ggplot(mapping = aes(x = artist_top_genre, y = n,
fill = artist_top_genre)) +
geom_col(alpha = 0.8) +
paletteer::scale_fill_paletteer_d("rcartocolor::Vivid") +
ggtitle("Top genres") +
theme(plot.title = element_text(hjust = 0.5),
# Rotates the X markers (so we can read them)
axis.text.x = element_text(angle = 90))
Now it’s way easier to identify that we have missing
genres 🧐!
A good visualisation will show you things that you did not expect, or
raise new questions about the data - Hadley Wickham and Garrett
Grolemund, R For Data
Science
Note, when the top genre is described as Missing
, that
means that Spotify did not classify it, so let’s get rid of it.
# Visualize popular genres
top_genres %>%
filter(artist_top_genre != "Missing") %>%
slice(1:10) %>%
ggplot(mapping = aes(x = artist_top_genre, y = n,
fill = artist_top_genre)) +
geom_col(alpha = 0.8) +
paletteer::scale_fill_paletteer_d("rcartocolor::Vivid") +
ggtitle("Top genres") +
theme(plot.title = element_text(hjust = 0.5),
# Rotates the X markers (so we can read them)
axis.text.x = element_text(angle = 90))
From the little data exploration, we learn that the top three genres
dominate this dataset. Let’s concentrate on afro dancehall
,
afropop
, and nigerian pop
, additionally filter
the dataset to remove anything with a 0 popularity value (meaning it was
not classified with a popularity in the dataset and can be considered
noise for our purposes):
nigerian_songs <- df %>%
# Concentrate on top 3 genres
filter(artist_top_genre %in% c("afro dancehall", "afropop","nigerian pop")) %>%
# Remove unclassified observations
filter(popularity != 0)
# Visualize popular genres
nigerian_songs %>%
count(artist_top_genre) %>%
ggplot(mapping = aes(x = artist_top_genre, y = n,
fill = artist_top_genre)) +
geom_col(alpha = 0.8) +
paletteer::scale_fill_paletteer_d("ggsci::category10_d3") +
ggtitle("Top genres") +
theme(plot.title = element_text(hjust = 0.5))
Let’s see whether there is any apparent linear relationship among the
numerical variables in our data set. This relationship is quantified
mathematically by the correlation
statistic.
The correlation statistic is a value between -1 and 1 that indicates
the strength of a relationship. Values above 0 indicate a
positive correlation (high values of one variable tend to
coincide with high values of the other), while values below 0 indicate a
negative correlation (high values of one variable tend to
coincide with low values of the other).
# Narrow down to numeric variables and fid correlation
corr_mat <- nigerian_songs %>%
select(where(is.numeric)) %>%
cor()
# Visualize correlation matrix
corrplot(corr_mat, order = 'AOE', col = c('white', 'black'), bg = 'gold2')
The data is not strongly correlated except between
energy
and loudness
, which makes sense, given
that loud music is usually pretty energetic. Popularity
has
a correspondence to release date
, which also makes sense,
as more recent songs are probably more popular. Length and energy seem
to have a correlation too.
It will be interesting to see what a clustering algorithm can make of
this data!
🎓 Note that correlation does not imply causation! We have proof of
correlation but no proof of causation. An amusing web site
has some visuals that emphasize this point.
2. Explore data distribution
Let’s ask some more subtle questions. Are the genres significantly
different in the perception of their danceability, based on their
popularity? Let’s examine our top three genres data distribution for
popularity and danceability along a given x and y axis using density
plots.
# Perform 2D kernel density estimation
density_estimate_2d <- nigerian_songs %>%
ggplot(mapping = aes(x = popularity, y = danceability, color = artist_top_genre)) +
geom_density_2d(bins = 5, size = 1) +
paletteer::scale_color_paletteer_d("RSkittleBrewer::wildberry") +
xlim(-20, 80) +
ylim(0, 1.2)
# Density plot based on the popularity
density_estimate_pop <- nigerian_songs %>%
ggplot(mapping = aes(x = popularity, fill = artist_top_genre, color = artist_top_genre)) +
geom_density(size = 1, alpha = 0.5) +
paletteer::scale_fill_paletteer_d("RSkittleBrewer::wildberry") +
paletteer::scale_color_paletteer_d("RSkittleBrewer::wildberry") +
theme(legend.position = "none")
# Density plot based on the danceability
density_estimate_dance <- nigerian_songs %>%
ggplot(mapping = aes(x = danceability, fill = artist_top_genre, color = artist_top_genre)) +
geom_density(size = 1, alpha = 0.5) +
paletteer::scale_fill_paletteer_d("RSkittleBrewer::wildberry") +
paletteer::scale_color_paletteer_d("RSkittleBrewer::wildberry")
# Patch everything together
library(patchwork)
density_estimate_2d / (density_estimate_pop + density_estimate_dance)
We see that there are concentric circles that line up, regardless of
genre. Could it be that Nigerian tastes converge at a certain level of
danceability for this genre?
In general, the three genres align in terms of their popularity and
danceability. Determining clusters in this loosely-aligned data will be
a challenge. Let’s see whether a scatter plot can support this.
# A scatter plot of popularity and danceability
scatter_plot <- nigerian_songs %>%
ggplot(mapping = aes(x = popularity, y = danceability, color = artist_top_genre, shape = artist_top_genre)) +
geom_point(size = 2, alpha = 0.8) +
paletteer::scale_color_paletteer_d("futurevisions::mars")
# Add a touch of interactivity
ggplotly(scatter_plot)
A scatterplot of the same axes shows a similar pattern of
convergence.
In general, for clustering, you can use scatterplots to show clusters
of data, so mastering this type of visualization is very useful. In the
next lesson, we will take this filtered data and use k-means clustering
to discover groups in this data that see to overlap in interesting
ways.
LS0tCnRpdGxlOiAnSW50cm9kdWN0aW9uIHRvIGNsdXN0ZXJpbmc6IENsZWFuLCBwcmVwIGFuZCB2aXN1YWxpemUgeW91ciBkYXRhJwpvdXRwdXQ6CiAgaHRtbF9kb2N1bWVudDoKICAgIGRmX3ByaW50OiBwYWdlZAogICAgdGhlbWU6IGZsYXRseQogICAgaGlnaGxpZ2h0OiBicmVlemVkYXJrCiAgICB0b2M6IHllcwogICAgdG9jX2Zsb2F0OiB5ZXMKICAgIGNvZGVfZG93bmxvYWQ6IHllcwotLS0KCiMjICoqTmlnZXJpYW4gTXVzaWMgc2NyYXBlZCBmcm9tIFNwb3RpZnkgLSBhbiBhbmFseXNpcyoqCgpDbHVzdGVyaW5nIGlzIGEgdHlwZSBvZiBbVW5zdXBlcnZpc2VkIExlYXJuaW5nXShodHRwczovL3dpa2lwZWRpYS5vcmcvd2lraS9VbnN1cGVydmlzZWRfbGVhcm5pbmcpIHRoYXQgcHJlc3VtZXMgdGhhdCBhIGRhdGFzZXQgaXMgdW5sYWJlbGxlZCBvciB0aGF0IGl0cyBpbnB1dHMgYXJlIG5vdCBtYXRjaGVkIHdpdGggcHJlZGVmaW5lZCBvdXRwdXRzLiBJdCB1c2VzIHZhcmlvdXMgYWxnb3JpdGhtcyB0byBzb3J0IHRocm91Z2ggdW5sYWJlbGVkIGRhdGEgYW5kIHByb3ZpZGUgZ3JvdXBpbmdzIGFjY29yZGluZyB0byBwYXR0ZXJucyBpdCBkaXNjZXJucyBpbiB0aGUgZGF0YS4KClsqKlByZS1sZWN0dXJlIHF1aXoqKl0oaHR0cHM6Ly9ncmF5LXNhbmQtMDdhMTBmNDAzLjEuYXp1cmVzdGF0aWNhcHBzLm5ldC9xdWl6LzI3LykKCiMjIyAqKkludHJvZHVjdGlvbioqCgpbQ2x1c3RlcmluZ10oaHR0cHM6Ly9saW5rLnNwcmluZ2VyLmNvbS9yZWZlcmVuY2V3b3JrZW50cnkvMTAuMTAwNyUyRjk3OC0wLTM4Ny0zMDE2NC04XzEyNCkgaXMgdmVyeSB1c2VmdWwgZm9yIGRhdGEgZXhwbG9yYXRpb24uIExldCdzIHNlZSBpZiBpdCBjYW4gaGVscCBkaXNjb3ZlciB0cmVuZHMgYW5kIHBhdHRlcm5zIGluIHRoZSB3YXkgTmlnZXJpYW4gYXVkaWVuY2VzIGNvbnN1bWUgbXVzaWMuCgo+IOKchSBUYWtlIGEgbWludXRlIHRvIHRoaW5rIGFib3V0IHRoZSB1c2VzIG9mIGNsdXN0ZXJpbmcuIEluIHJlYWwgbGlmZSwgY2x1c3RlcmluZyBoYXBwZW5zIHdoZW5ldmVyIHlvdSBoYXZlIGEgcGlsZSBvZiBsYXVuZHJ5IGFuZCBuZWVkIHRvIHNvcnQgb3V0IHlvdXIgZmFtaWx5IG1lbWJlcnMnIGNsb3RoZXMg8J+npvCfkZXwn5GW8J+psi4gSW4gZGF0YSBzY2llbmNlLCBjbHVzdGVyaW5nIGhhcHBlbnMgd2hlbiB0cnlpbmcgdG8gYW5hbHl6ZSBhIHVzZXIncyBwcmVmZXJlbmNlcywgb3IgZGV0ZXJtaW5lIHRoZSBjaGFyYWN0ZXJpc3RpY3Mgb2YgYW55IHVubGFiZWxlZCBkYXRhc2V0LiBDbHVzdGVyaW5nLCBpbiBhIHdheSwgaGVscHMgbWFrZSBzZW5zZSBvZiBjaGFvcywgbGlrZSBhIHNvY2sgZHJhd2VyLgoKSW4gYSBwcm9mZXNzaW9uYWwgc2V0dGluZywgY2x1c3RlcmluZyBjYW4gYmUgdXNlZCB0byBkZXRlcm1pbmUgdGhpbmdzIGxpa2UgbWFya2V0IHNlZ21lbnRhdGlvbiwgZGV0ZXJtaW5pbmcgd2hhdCBhZ2UgZ3JvdXBzIGJ1eSB3aGF0IGl0ZW1zLCBmb3IgZXhhbXBsZS4gQW5vdGhlciB1c2Ugd291bGQgYmUgYW5vbWFseSBkZXRlY3Rpb24sIHBlcmhhcHMgdG8gZGV0ZWN0IGZyYXVkIGZyb20gYSBkYXRhc2V0IG9mIGNyZWRpdCBjYXJkIHRyYW5zYWN0aW9ucy4gT3IgeW91IG1pZ2h0IHVzZSBjbHVzdGVyaW5nIHRvIGRldGVybWluZSB0dW1vcnMgaW4gYSBiYXRjaCBvZiBtZWRpY2FsIHNjYW5zLgoK4pyFIFRoaW5rIGEgbWludXRlIGFib3V0IGhvdyB5b3UgbWlnaHQgaGF2ZSBlbmNvdW50ZXJlZCBjbHVzdGVyaW5nICdpbiB0aGUgd2lsZCcsIGluIGEgYmFua2luZywgZS1jb21tZXJjZSwgb3IgYnVzaW5lc3Mgc2V0dGluZy4KCj4g8J+OkyBJbnRlcmVzdGluZ2x5LCBjbHVzdGVyIGFuYWx5c2lzIG9yaWdpbmF0ZWQgaW4gdGhlIGZpZWxkcyBvZiBBbnRocm9wb2xvZ3kgYW5kIFBzeWNob2xvZ3kgaW4gdGhlIDE5MzBzLiBDYW4geW91IGltYWdpbmUgaG93IGl0IG1pZ2h0IGhhdmUgYmVlbiB1c2VkPwoKQWx0ZXJuYXRlbHksIHlvdSBjb3VsZCB1c2UgaXQgZm9yIGdyb3VwaW5nIHNlYXJjaCByZXN1bHRzIC0gYnkgc2hvcHBpbmcgbGlua3MsIGltYWdlcywgb3IgcmV2aWV3cywgZm9yIGV4YW1wbGUuIENsdXN0ZXJpbmcgaXMgdXNlZnVsIHdoZW4geW91IGhhdmUgYSBsYXJnZSBkYXRhc2V0IHRoYXQgeW91IHdhbnQgdG8gcmVkdWNlIGFuZCBvbiB3aGljaCB5b3Ugd2FudCB0byBwZXJmb3JtIG1vcmUgZ3JhbnVsYXIgYW5hbHlzaXMsIHNvIHRoZSB0ZWNobmlxdWUgY2FuIGJlIHVzZWQgdG8gbGVhcm4gYWJvdXQgZGF0YSBiZWZvcmUgb3RoZXIgbW9kZWxzIGFyZSBjb25zdHJ1Y3RlZC4KCuKchSBPbmNlIHlvdXIgZGF0YSBpcyBvcmdhbml6ZWQgaW4gY2x1c3RlcnMsIHlvdSBhc3NpZ24gaXQgYSBjbHVzdGVyIElkLCBhbmQgdGhpcyB0ZWNobmlxdWUgY2FuIGJlIHVzZWZ1bCB3aGVuIHByZXNlcnZpbmcgYSBkYXRhc2V0J3MgcHJpdmFjeTsgeW91IGNhbiBpbnN0ZWFkIHJlZmVyIHRvIGEgZGF0YSBwb2ludCBieSBpdHMgY2x1c3RlciBpZCwgcmF0aGVyIHRoYW4gYnkgbW9yZSByZXZlYWxpbmcgaWRlbnRpZmlhYmxlIGRhdGEuIENhbiB5b3UgdGhpbmsgb2Ygb3RoZXIgcmVhc29ucyB3aHkgeW91J2QgcmVmZXIgdG8gYSBjbHVzdGVyIElkIHJhdGhlciB0aGFuIG90aGVyIGVsZW1lbnRzIG9mIHRoZSBjbHVzdGVyIHRvIGlkZW50aWZ5IGl0PwoKIyMjIEdldHRpbmcgc3RhcnRlZCB3aXRoIGNsdXN0ZXJpbmcKCj4g8J+OkyBIb3cgd2UgY3JlYXRlIGNsdXN0ZXJzIGhhcyBhIGxvdCB0byBkbyB3aXRoIGhvdyB3ZSBnYXRoZXIgdXAgdGhlIGRhdGEgcG9pbnRzIGludG8gZ3JvdXBzLiBMZXQncyB1bnBhY2sgc29tZSB2b2NhYnVsYXJ5Ogo+Cj4g8J+OkyBbJ1RyYW5zZHVjdGl2ZScgdnMuICdpbmR1Y3RpdmUnXShodHRwczovL3dpa2lwZWRpYS5vcmcvd2lraS9UcmFuc2R1Y3Rpb25fKG1hY2hpbmVfbGVhcm5pbmcpKQo+Cj4gVHJhbnNkdWN0aXZlIGluZmVyZW5jZSBpcyBkZXJpdmVkIGZyb20gb2JzZXJ2ZWQgdHJhaW5pbmcgY2FzZXMgdGhhdCBtYXAgdG8gc3BlY2lmaWMgdGVzdCBjYXNlcy4gSW5kdWN0aXZlIGluZmVyZW5jZSBpcyBkZXJpdmVkIGZyb20gdHJhaW5pbmcgY2FzZXMgdGhhdCBtYXAgdG8gZ2VuZXJhbCBydWxlcyB3aGljaCBhcmUgb25seSB0aGVuIGFwcGxpZWQgdG8gdGVzdCBjYXNlcy4KPgo+IEFuIGV4YW1wbGU6IEltYWdpbmUgeW91IGhhdmUgYSBkYXRhc2V0IHRoYXQgaXMgb25seSBwYXJ0aWFsbHkgbGFiZWxsZWQuIFNvbWUgdGhpbmdzIGFyZSAncmVjb3JkcycsIHNvbWUgJ2NkcycsIGFuZCBzb21lIGFyZSBibGFuay4gWW91ciBqb2IgaXMgdG8gcHJvdmlkZSBsYWJlbHMgZm9yIHRoZSBibGFua3MuIElmIHlvdSBjaG9vc2UgYW4gaW5kdWN0aXZlIGFwcHJvYWNoLCB5b3UnZCB0cmFpbiBhIG1vZGVsIGxvb2tpbmcgZm9yICdyZWNvcmRzJyBhbmQgJ2NkcycsIGFuZCBhcHBseSB0aG9zZSBsYWJlbHMgdG8geW91ciB1bmxhYmVsZWQgZGF0YS4gVGhpcyBhcHByb2FjaCB3aWxsIGhhdmUgdHJvdWJsZSBjbGFzc2lmeWluZyB0aGluZ3MgdGhhdCBhcmUgYWN0dWFsbHkgJ2Nhc3NldHRlcycuIEEgdHJhbnNkdWN0aXZlIGFwcHJvYWNoLCBvbiB0aGUgb3RoZXIgaGFuZCwgaGFuZGxlcyB0aGlzIHVua25vd24gZGF0YSBtb3JlIGVmZmVjdGl2ZWx5IGFzIGl0IHdvcmtzIHRvIGdyb3VwIHNpbWlsYXIgaXRlbXMgdG9nZXRoZXIgYW5kIHRoZW4gYXBwbGllcyBhIGxhYmVsIHRvIGEgZ3JvdXAuIEluIHRoaXMgY2FzZSwgY2x1c3RlcnMgbWlnaHQgcmVmbGVjdCAncm91bmQgbXVzaWNhbCB0aGluZ3MnIGFuZCAnc3F1YXJlIG11c2ljYWwgdGhpbmdzJy4KPgo+IPCfjpMgWydOb24tZmxhdCcgdnMuICdmbGF0JyBnZW9tZXRyeV0oaHR0cHM6Ly9kYXRhc2NpZW5jZS5zdGFja2V4Y2hhbmdlLmNvbS9xdWVzdGlvbnMvNTIyNjAvdGVybWlub2xvZ3ktZmxhdC1nZW9tZXRyeS1pbi10aGUtY29udGV4dC1vZi1jbHVzdGVyaW5nKQo+Cj4gRGVyaXZlZCBmcm9tIG1hdGhlbWF0aWNhbCB0ZXJtaW5vbG9neSwgbm9uLWZsYXQgdnMuIGZsYXQgZ2VvbWV0cnkgcmVmZXJzIHRvIHRoZSBtZWFzdXJlIG9mIGRpc3RhbmNlcyBiZXR3ZWVuIHBvaW50cyBieSBlaXRoZXIgJ2ZsYXQnIChbRXVjbGlkZWFuXShodHRwczovL3dpa2lwZWRpYS5vcmcvd2lraS9FdWNsaWRlYW5fZ2VvbWV0cnkpKSBvciAnbm9uLWZsYXQnIChub24tRXVjbGlkZWFuKSBnZW9tZXRyaWNhbCBtZXRob2RzLgo+Cj4gJ0ZsYXQnIGluIHRoaXMgY29udGV4dCByZWZlcnMgdG8gRXVjbGlkZWFuIGdlb21ldHJ5IChwYXJ0cyBvZiB3aGljaCBhcmUgdGF1Z2h0IGFzICdwbGFuZScgZ2VvbWV0cnkpLCBhbmQgbm9uLWZsYXQgcmVmZXJzIHRvIG5vbi1FdWNsaWRlYW4gZ2VvbWV0cnkuIFdoYXQgZG9lcyBnZW9tZXRyeSBoYXZlIHRvIGRvIHdpdGggbWFjaGluZSBsZWFybmluZz8gV2VsbCwgYXMgdHdvIGZpZWxkcyB0aGF0IGFyZSByb290ZWQgaW4gbWF0aGVtYXRpY3MsIHRoZXJlIG11c3QgYmUgYSBjb21tb24gd2F5IHRvIG1lYXN1cmUgZGlzdGFuY2VzIGJldHdlZW4gcG9pbnRzIGluIGNsdXN0ZXJzLCBhbmQgdGhhdCBjYW4gYmUgZG9uZSBpbiBhICdmbGF0JyBvciAnbm9uLWZsYXQnIHdheSwgZGVwZW5kaW5nIG9uIHRoZSBuYXR1cmUgb2YgdGhlIGRhdGEuIFtFdWNsaWRlYW4gZGlzdGFuY2VzXShodHRwczovL3dpa2lwZWRpYS5vcmcvd2lraS9FdWNsaWRlYW5fZGlzdGFuY2UpIGFyZSBtZWFzdXJlZCBhcyB0aGUgbGVuZ3RoIG9mIGEgbGluZSBzZWdtZW50IGJldHdlZW4gdHdvIHBvaW50cy4gW05vbi1FdWNsaWRlYW4gZGlzdGFuY2VzXShodHRwczovL3dpa2lwZWRpYS5vcmcvd2lraS9Ob24tRXVjbGlkZWFuX2dlb21ldHJ5KSBhcmUgbWVhc3VyZWQgYWxvbmcgYSBjdXJ2ZS4gSWYgeW91ciBkYXRhLCB2aXN1YWxpemVkLCBzZWVtcyB0byBub3QgZXhpc3Qgb24gYSBwbGFuZSwgeW91IG1pZ2h0IG5lZWQgdG8gdXNlIGEgc3BlY2lhbGl6ZWQgYWxnb3JpdGhtIHRvIGhhbmRsZSBpdC4KCiFbSW5mb2dyYXBoaWMgYnkgRGFzYW5pIE1hZGlwYWxsaV0oLi4vLi4vaW1hZ2VzL2ZsYXQtbm9uZmxhdC5wbmcpe3dpZHRoPSI1MDAifQoKPiDwn46TIFsnRGlzdGFuY2VzJ10oaHR0cHM6Ly93ZWIuc3RhbmZvcmQuZWR1L2NsYXNzL2NzMzQ1YS9zbGlkZXMvMTItY2x1c3RlcmluZy5wZGYpCj4KPiBDbHVzdGVycyBhcmUgZGVmaW5lZCBieSB0aGVpciBkaXN0YW5jZSBtYXRyaXgsIGUuZy4gdGhlIGRpc3RhbmNlcyBiZXR3ZWVuIHBvaW50cy4gVGhpcyBkaXN0YW5jZSBjYW4gYmUgbWVhc3VyZWQgYSBmZXcgd2F5cy4gRXVjbGlkZWFuIGNsdXN0ZXJzIGFyZSBkZWZpbmVkIGJ5IHRoZSBhdmVyYWdlIG9mIHRoZSBwb2ludCB2YWx1ZXMsIGFuZCBjb250YWluIGEgJ2NlbnRyb2lkJyBvciBjZW50ZXIgcG9pbnQuIERpc3RhbmNlcyBhcmUgdGh1cyBtZWFzdXJlZCBieSB0aGUgZGlzdGFuY2UgdG8gdGhhdCBjZW50cm9pZC4gTm9uLUV1Y2xpZGVhbiBkaXN0YW5jZXMgcmVmZXIgdG8gJ2NsdXN0cm9pZHMnLCB0aGUgcG9pbnQgY2xvc2VzdCB0byBvdGhlciBwb2ludHMuIENsdXN0cm9pZHMgaW4gdHVybiBjYW4gYmUgZGVmaW5lZCBpbiB2YXJpb3VzIHdheXMuCj4KPiDwn46TIFsnQ29uc3RyYWluZWQnXShodHRwczovL3dpa2lwZWRpYS5vcmcvd2lraS9Db25zdHJhaW5lZF9jbHVzdGVyaW5nKQo+Cj4gW0NvbnN0cmFpbmVkIENsdXN0ZXJpbmddKGh0dHBzOi8vd2ViLmNzLnVjZGF2aXMuZWR1L35kYXZpZHNvbi9QdWJsaWNhdGlvbnMvSUNETVR1dG9yaWFsLnBkZikgaW50cm9kdWNlcyAnc2VtaS1zdXBlcnZpc2VkJyBsZWFybmluZyBpbnRvIHRoaXMgdW5zdXBlcnZpc2VkIG1ldGhvZC4gVGhlIHJlbGF0aW9uc2hpcHMgYmV0d2VlbiBwb2ludHMgYXJlIGZsYWdnZWQgYXMgJ2Nhbm5vdCBsaW5rJyBvciAnbXVzdC1saW5rJyBzbyBzb21lIHJ1bGVzIGFyZSBmb3JjZWQgb24gdGhlIGRhdGFzZXQuCj4KPiBBbiBleGFtcGxlOiBJZiBhbiBhbGdvcml0aG0gaXMgc2V0IGZyZWUgb24gYSBiYXRjaCBvZiB1bmxhYmVsbGVkIG9yIHNlbWktbGFiZWxsZWQgZGF0YSwgdGhlIGNsdXN0ZXJzIGl0IHByb2R1Y2VzIG1heSBiZSBvZiBwb29yIHF1YWxpdHkuIEluIHRoZSBleGFtcGxlIGFib3ZlLCB0aGUgY2x1c3RlcnMgbWlnaHQgZ3JvdXAgJ3JvdW5kIG11c2ljIHRoaW5ncycgYW5kICdzcXVhcmUgbXVzaWMgdGhpbmdzJyBhbmQgJ3RyaWFuZ3VsYXIgdGhpbmdzJyBhbmQgJ2Nvb2tpZXMnLiBJZiBnaXZlbiBzb21lIGNvbnN0cmFpbnRzLCBvciBydWxlcyB0byBmb2xsb3cgKCJ0aGUgaXRlbSBtdXN0IGJlIG1hZGUgb2YgcGxhc3RpYyIsICJ0aGUgaXRlbSBuZWVkcyB0byBiZSBhYmxlIHRvIHByb2R1Y2UgbXVzaWMiKSB0aGlzIGNhbiBoZWxwICdjb25zdHJhaW4nIHRoZSBhbGdvcml0aG0gdG8gbWFrZSBiZXR0ZXIgY2hvaWNlcy4KPgo+IPCfjpMgJ0RlbnNpdHknCj4KPiBEYXRhIHRoYXQgaXMgJ25vaXN5JyBpcyBjb25zaWRlcmVkIHRvIGJlICdkZW5zZScuIFRoZSBkaXN0YW5jZXMgYmV0d2VlbiBwb2ludHMgaW4gZWFjaCBvZiBpdHMgY2x1c3RlcnMgbWF5IHByb3ZlLCBvbiBleGFtaW5hdGlvbiwgdG8gYmUgbW9yZSBvciBsZXNzIGRlbnNlLCBvciAnY3Jvd2RlZCcgYW5kIHRodXMgdGhpcyBkYXRhIG5lZWRzIHRvIGJlIGFuYWx5emVkIHdpdGggdGhlIGFwcHJvcHJpYXRlIGNsdXN0ZXJpbmcgbWV0aG9kLiBbVGhpcyBhcnRpY2xlXShodHRwczovL3d3dy5rZG51Z2dldHMuY29tLzIwMjAvMDIvdW5kZXJzdGFuZGluZy1kZW5zaXR5LWJhc2VkLWNsdXN0ZXJpbmcuaHRtbCkgZGVtb25zdHJhdGVzIHRoZSBkaWZmZXJlbmNlIGJldHdlZW4gdXNpbmcgSy1NZWFucyBjbHVzdGVyaW5nIHZzLiBIREJTQ0FOIGFsZ29yaXRobXMgdG8gZXhwbG9yZSBhIG5vaXN5IGRhdGFzZXQgd2l0aCB1bmV2ZW4gY2x1c3RlciBkZW5zaXR5LgoKRGVlcGVuIHlvdXIgdW5kZXJzdGFuZGluZyBvZiBjbHVzdGVyaW5nIHRlY2huaXF1ZXMgaW4gdGhpcyBbTGVhcm4gbW9kdWxlXShodHRwczovL2RvY3MubWljcm9zb2Z0LmNvbS9sZWFybi9tb2R1bGVzL3RyYWluLWV2YWx1YXRlLWNsdXN0ZXItbW9kZWxzP1dULm1jX2lkPWFjYWRlbWljLTc3OTUyLWxlZXN0b3R0KQoKIyMjICoqQ2x1c3RlcmluZyBhbGdvcml0aG1zKioKClRoZXJlIGFyZSBvdmVyIDEwMCBjbHVzdGVyaW5nIGFsZ29yaXRobXMsIGFuZCB0aGVpciB1c2UgZGVwZW5kcyBvbiB0aGUgbmF0dXJlIG9mIHRoZSBkYXRhIGF0IGhhbmQuIExldCdzIGRpc2N1c3Mgc29tZSBvZiB0aGUgbWFqb3Igb25lczoKCi0gICAqKkhpZXJhcmNoaWNhbCBjbHVzdGVyaW5nKiouIElmIGFuIG9iamVjdCBpcyBjbGFzc2lmaWVkIGJ5IGl0cyBwcm94aW1pdHkgdG8gYSBuZWFyYnkgb2JqZWN0LCByYXRoZXIgdGhhbiB0byBvbmUgZmFydGhlciBhd2F5LCBjbHVzdGVycyBhcmUgZm9ybWVkIGJhc2VkIG9uIHRoZWlyIG1lbWJlcnMnIGRpc3RhbmNlIHRvIGFuZCBmcm9tIG90aGVyIG9iamVjdHMuIEhpZXJhcmNoaWNhbCBjbHVzdGVyaW5nIGlzIGNoYXJhY3Rlcml6ZWQgYnkgcmVwZWF0ZWRseSBjb21iaW5pbmcgdHdvIGNsdXN0ZXJzLgoKIVtJbmZvZ3JhcGhpYyBieSBEYXNhbmkgTWFkaXBhbGxpXSguLi8uLi9pbWFnZXMvaGllcmFyY2hpY2FsLnBuZyl7d2lkdGg9IjUwMCJ9CgotICAgKipDZW50cm9pZCBjbHVzdGVyaW5nKiouIFRoaXMgcG9wdWxhciBhbGdvcml0aG0gcmVxdWlyZXMgdGhlIGNob2ljZSBvZiAnaycsIG9yIHRoZSBudW1iZXIgb2YgY2x1c3RlcnMgdG8gZm9ybSwgYWZ0ZXIgd2hpY2ggdGhlIGFsZ29yaXRobSBkZXRlcm1pbmVzIHRoZSBjZW50ZXIgcG9pbnQgb2YgYSBjbHVzdGVyIGFuZCBnYXRoZXJzIGRhdGEgYXJvdW5kIHRoYXQgcG9pbnQuIFtLLW1lYW5zIGNsdXN0ZXJpbmddKGh0dHBzOi8vd2lraXBlZGlhLm9yZy93aWtpL0stbWVhbnNfY2x1c3RlcmluZykgaXMgYSBwb3B1bGFyIHZlcnNpb24gb2YgY2VudHJvaWQgY2x1c3RlcmluZyB3aGljaCBzZXBhcmF0ZXMgYSBkYXRhIHNldCBpbnRvIHByZS1kZWZpbmVkIEsgZ3JvdXBzLiBUaGUgY2VudGVyIGlzIGRldGVybWluZWQgYnkgdGhlIG5lYXJlc3QgbWVhbiwgdGh1cyB0aGUgbmFtZS4gVGhlIHNxdWFyZWQgZGlzdGFuY2UgZnJvbSB0aGUgY2x1c3RlciBpcyBtaW5pbWl6ZWQuIVtJbmZvZ3JhcGhpYyBieSBEYXNhbmkgTWFkaXBhbGxpXSguLi8uLi9pbWFnZXMvY2VudHJvaWQucG5nKXt3aWR0aD0iNTAwIn0KCi0gICAqKkRpc3RyaWJ1dGlvbi1iYXNlZCBjbHVzdGVyaW5nKiouIEJhc2VkIGluIHN0YXRpc3RpY2FsIG1vZGVsaW5nLCBkaXN0cmlidXRpb24tYmFzZWQgY2x1c3RlcmluZyBjZW50ZXJzIG9uIGRldGVybWluaW5nIHRoZSBwcm9iYWJpbGl0eSB0aGF0IGEgZGF0YSBwb2ludCBiZWxvbmdzIHRvIGEgY2x1c3RlciwgYW5kIGFzc2lnbmluZyBpdCBhY2NvcmRpbmdseS4gR2F1c3NpYW4gbWl4dHVyZSBtZXRob2RzIGJlbG9uZyB0byB0aGlzIHR5cGUuCgotICAgKipEZW5zaXR5LWJhc2VkIGNsdXN0ZXJpbmcqKi4gRGF0YSBwb2ludHMgYXJlIGFzc2lnbmVkIHRvIGNsdXN0ZXJzIGJhc2VkIG9uIHRoZWlyIGRlbnNpdHksIG9yIHRoZWlyIGdyb3VwaW5nIGFyb3VuZCBlYWNoIG90aGVyLiBEYXRhIHBvaW50cyBmYXIgZnJvbSB0aGUgZ3JvdXAgYXJlIGNvbnNpZGVyZWQgb3V0bGllcnMgb3Igbm9pc2UuIERCU0NBTiwgTWVhbi1zaGlmdCBhbmQgT1BUSUNTIGJlbG9uZyB0byB0aGlzIHR5cGUgb2YgY2x1c3RlcmluZy4KCi0gICAqKkdyaWQtYmFzZWQgY2x1c3RlcmluZyoqLiBGb3IgbXVsdGktZGltZW5zaW9uYWwgZGF0YXNldHMsIGEgZ3JpZCBpcyBjcmVhdGVkIGFuZCB0aGUgZGF0YSBpcyBkaXZpZGVkIGFtb25nc3QgdGhlIGdyaWQncyBjZWxscywgdGhlcmVieSBjcmVhdGluZyBjbHVzdGVycy4KClRoZSBiZXN0IHdheSB0byBsZWFybiBhYm91dCBjbHVzdGVyaW5nIGlzIHRvIHRyeSBpdCBmb3IgeW91cnNlbGYsIHNvIHRoYXQncyB3aGF0IHlvdSdsbCBkbyBpbiB0aGlzIGV4ZXJjaXNlLgoKV2UnbGwgcmVxdWlyZSBzb21lIHBhY2thZ2VzIHRvIGtub2NrLW9mZiB0aGlzIG1vZHVsZS4gWW91IGNhbiBoYXZlIHRoZW0gaW5zdGFsbGVkIGFzOiBgaW5zdGFsbC5wYWNrYWdlcyhjKCd0aWR5dmVyc2UnLCAndGlkeW1vZGVscycsICdEYXRhRXhwbG9yZXInLCAnc3VtbWFyeXRvb2xzJywgJ3Bsb3RseScsICdwYWxldHRlZXInLCAnY29ycnBsb3QnLCAncGF0Y2h3b3JrJykpYAoKQWx0ZXJuYXRpdmVseSwgdGhlIHNjcmlwdCBiZWxvdyBjaGVja3Mgd2hldGhlciB5b3UgaGF2ZSB0aGUgcGFja2FnZXMgcmVxdWlyZWQgdG8gY29tcGxldGUgdGhpcyBtb2R1bGUgYW5kIGluc3RhbGxzIHRoZW0gZm9yIHlvdSBpbiBjYXNlIHNvbWUgYXJlIG1pc3NpbmcuCgpgYGB7cn0Kc3VwcHJlc3NXYXJuaW5ncyhpZighcmVxdWlyZSgicGFjbWFuIikpIGluc3RhbGwucGFja2FnZXMoInBhY21hbiIpKQoKcGFjbWFuOjpwX2xvYWQoJ3RpZHl2ZXJzZScsICd0aWR5bW9kZWxzJywgJ0RhdGFFeHBsb3JlcicsICdzdW1tYXJ5dG9vbHMnLCAncGxvdGx5JywgJ3BhbGV0dGVlcicsICdjb3JycGxvdCcsICdwYXRjaHdvcmsnKQpgYGAKCmBgYHtyIHNldHVwfQprbml0cjo6b3B0c19jaHVuayRzZXQod2FybmluZyA9IEYsIG1lc3NhZ2UgPSBGKQoKYGBgCgojIyBFeGVyY2lzZSAtIGNsdXN0ZXIgeW91ciBkYXRhCgpDbHVzdGVyaW5nIGFzIGEgdGVjaG5pcXVlIGlzIGdyZWF0bHkgYWlkZWQgYnkgcHJvcGVyIHZpc3VhbGl6YXRpb24sIHNvIGxldCdzIGdldCBzdGFydGVkIGJ5IHZpc3VhbGl6aW5nIG91ciBtdXNpYyBkYXRhLiBUaGlzIGV4ZXJjaXNlIHdpbGwgaGVscCB1cyBkZWNpZGUgd2hpY2ggb2YgdGhlIG1ldGhvZHMgb2YgY2x1c3RlcmluZyB3ZSBzaG91bGQgbW9zdCBlZmZlY3RpdmVseSB1c2UgZm9yIHRoZSBuYXR1cmUgb2YgdGhpcyBkYXRhLgoKTGV0J3MgaGl0IHRoZSBncm91bmQgcnVubmluZyBieSBpbXBvcnRpbmcgdGhlIGRhdGEuCgpgYGB7cn0KIyBMb2FkIHRoZSBjb3JlIHRpZHl2ZXJzZSBhbmQgbWFrZSBpdCBhdmFpbGFibGUgaW4geW91ciBjdXJyZW50IFIgc2Vzc2lvbgpsaWJyYXJ5KHRpZHl2ZXJzZSkKCiMgSW1wb3J0IHRoZSBkYXRhIGludG8gYSB0aWJibGUKZGYgPC0gcmVhZF9jc3YoZmlsZSA9ICJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vbWljcm9zb2Z0L01MLUZvci1CZWdpbm5lcnMvbWFpbi81LUNsdXN0ZXJpbmcvZGF0YS9uaWdlcmlhbi1zb25ncy5jc3YiKQoKIyBWaWV3IHRoZSBmaXJzdCA1IHJvd3Mgb2YgdGhlIGRhdGEgc2V0CmRmICU+JSAKICBzbGljZV9oZWFkKG4gPSA1KQoKYGBgCgpTb21ldGltZXMsIHdlIG1heSB3YW50IHNvbWUgbGl0dGxlIG1vcmUgaW5mb3JtYXRpb24gb24gb3VyIGRhdGEuIFdlIGNhbiBoYXZlIGEgbG9vayBhdCB0aGUgYGRhdGFgIGFuZCBgaXRzIHN0cnVjdHVyZWAgYnkgdXNpbmcgdGhlIFsqZ2xpbXBzZSgpKl0oaHR0cHM6Ly9waWxsYXIuci1saWIub3JnL3JlZmVyZW5jZS9nbGltcHNlLmh0bWwpIGZ1bmN0aW9uOgoKYGBge3J9CiMgR2xpbXBzZSBpbnRvIHRoZSBkYXRhIHNldApkZiAlPiUgCiAgZ2xpbXBzZSgpCmBgYAoKR29vZCBqb2Ih8J+SqgoKV2UgY2FuIG9ic2VydmUgdGhhdCBgZ2xpbXBzZSgpYCB3aWxsIGdpdmUgeW91IHRoZSB0b3RhbCBudW1iZXIgb2Ygcm93cyAob2JzZXJ2YXRpb25zKSBhbmQgY29sdW1ucyAodmFyaWFibGVzKSwgdGhlbiwgdGhlIGZpcnN0IGZldyBlbnRyaWVzIG9mIGVhY2ggdmFyaWFibGUgaW4gYSByb3cgYWZ0ZXIgdGhlIHZhcmlhYmxlIG5hbWUuIEluIGFkZGl0aW9uLCB0aGUgKmRhdGEgdHlwZSogb2YgdGhlIHZhcmlhYmxlIGlzIGdpdmVuIGltbWVkaWF0ZWx5IGFmdGVyIGVhY2ggdmFyaWFibGUncyBuYW1lIGluc2lkZSBgPCA+YC4KCmBEYXRhRXhwbG9yZXI6OmludHJvZHVjZSgpYCBjYW4gc3VtbWFyaXplIHRoaXMgaW5mb3JtYXRpb24gbmVhdGx5OgoKYGBge3IgRGF0YUV4cGxvcmVyfQojIERlc2NyaWJlIGJhc2ljIGluZm9ybWF0aW9uIGZvciBvdXIgZGF0YQpkZiAlPiUgCiAgaW50cm9kdWNlKCkKCiMgQSB2aXN1YWwgZGlzcGxheSBvZiB0aGUgc2FtZQpkZiAlPiUgCiAgcGxvdF9pbnRybygpCgpgYGAKCkF3ZXNvbWUhIFdlIGhhdmUganVzdCBsZWFybnQgdGhhdCBvdXIgZGF0YSBoYXMgbm8gbWlzc2luZyB2YWx1ZXMuCgpXaGlsZSB3ZSBhcmUgYXQgaXQsIHdlIGNhbiBleHBsb3JlIGNvbW1vbiBjZW50cmFsIHRlbmRlbmN5IHN0YXRpc3RpY3MgKGUuZyBbbWVhbl0oaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvQXJpdGhtZXRpY19tZWFuKSBhbmQgW21lZGlhbl0oaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvTWVkaWFuKSkgYW5kIG1lYXN1cmVzIG9mIGRpc3BlcnNpb24gKGUuZyBbc3RhbmRhcmQgZGV2aWF0aW9uXShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9TdGFuZGFyZF9kZXZpYXRpb24pKSB1c2luZyBgc3VtbWFyeXRvb2xzOjpkZXNjcigpYAoKYGBgCiMgRGVzY3JpYmUgY29tbW9uIHN0YXRpc3RpY3MKZGYgJT4lIGRlc2NyKHN0YXRzID0gImNvbW1vbiIpCgpgYGAKCkxldCdzIGxvb2sgYXQgdGhlIGdlbmVyYWwgdmFsdWVzIG9mIHRoZSBkYXRhLiBOb3RlIHRoYXQgcG9wdWxhcml0eSBjYW4gYmUgYDBgLCB3aGljaCBzaG93IHNvbmdzIHRoYXQgaGF2ZSBubyByYW5raW5nLiBXZSdsbCByZW1vdmUgdGhvc2Ugc2hvcnRseS4KCj4g8J+klCBJZiB3ZSBhcmUgd29ya2luZyB3aXRoIGNsdXN0ZXJpbmcsIGFuIHVuc3VwZXJ2aXNlZCBtZXRob2QgdGhhdCBkb2VzIG5vdCByZXF1aXJlIGxhYmVsZWQgZGF0YSwgd2h5IGFyZSB3ZSBzaG93aW5nIHRoaXMgZGF0YSB3aXRoIGxhYmVscz8gSW4gdGhlIGRhdGEgZXhwbG9yYXRpb24gcGhhc2UsIHRoZXkgY29tZSBpbiBoYW5keSwgYnV0IHRoZXkgYXJlIG5vdCBuZWNlc3NhcnkgZm9yIHRoZSBjbHVzdGVyaW5nIGFsZ29yaXRobXMgdG8gd29yay4KCiMjIyAxLiBFeHBsb3JlIHBvcHVsYXIgZ2VucmVzCgpMZXQncyBnbyBhaGVhZCBhbmQgZmluZCBvdXQgdGhlIG1vc3QgcG9wdWxhciBnZW5yZXMg8J+OtiBieSBtYWtpbmcgYSBjb3VudCBvZiB0aGUgaW5zdGFuY2VzIGl0IGFwcGVhcnMuCgpgYGB7ciBjb3VudF9nZW5yZXN9CiMgUG9wdWxhciBnZW5yZXMKdG9wX2dlbnJlcyA8LSBkZiAlPiUgCiAgY291bnQoYXJ0aXN0X3RvcF9nZW5yZSwgc29ydCA9IFRSVUUpICU+JSAKIyBFbmNvZGUgdG8gY2F0ZWdvcmljYWwgYW5kIHJlb3JkZXIgdGhlIGFjY29yZGluZyB0byBjb3VudAogIG11dGF0ZShhcnRpc3RfdG9wX2dlbnJlID0gZmFjdG9yKGFydGlzdF90b3BfZ2VucmUpICU+JSBmY3RfaW5vcmRlcigpKQoKIyBQcmludCB0aGUgdG9wIGdlbnJlcwp0b3BfZ2VucmVzCgpgYGAKClRoYXQgd2VudCB3ZWxsISBUaGV5IHNheSBhIHBpY3R1cmUgaXMgd29ydGggYSB0aG91c2FuZCByb3dzIG9mIGEgZGF0YSBmcmFtZSAoYWN0dWFsbHkgbm9ib2R5IGV2ZXIgc2F5cyB0aGF0IPCfmIUpLiBCdXQgeW91IGdldCB0aGUgZ2lzdCBvZiBpdCwgcmlnaHQ/CgpPbmUgd2F5IHRvIHZpc3VhbGl6ZSBjYXRlZ29yaWNhbCBkYXRhIChjaGFyYWN0ZXIgb3IgZmFjdG9yIHZhcmlhYmxlcykgaXMgdXNpbmcgYmFycGxvdHMuIExldCdzIG1ha2UgYSBiYXJwbG90IG9mIHRoZSB0b3AgMTAgZ2VucmVzOgoKYGBge3IgYmFyX3Bsb3RfZ2VucmV9CiMgQ2hhbmdlIHRoZSBkZWZhdWx0IGdyYXkgdGhlbWUKdGhlbWVfc2V0KHRoZW1lX2xpZ2h0KCkpCgojIFZpc3VhbGl6ZSBwb3B1bGFyIGdlbnJlcwp0b3BfZ2VucmVzICU+JQogIHNsaWNlKDE6MTApICU+JSAKICBnZ3Bsb3QobWFwcGluZyA9IGFlcyh4ID0gYXJ0aXN0X3RvcF9nZW5yZSwgeSA9IG4sCiAgICAgICAgICAgICAgICAgICAgICAgZmlsbCA9IGFydGlzdF90b3BfZ2VucmUpKSArCiAgZ2VvbV9jb2woYWxwaGEgPSAwLjgpICsKICBwYWxldHRlZXI6OnNjYWxlX2ZpbGxfcGFsZXR0ZWVyX2QoInJjYXJ0b2NvbG9yOjpWaXZpZCIpICsKICBnZ3RpdGxlKCJUb3AgZ2VucmVzIikgKwogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpLAogICAgICAgICMgUm90YXRlcyB0aGUgWCBtYXJrZXJzIChzbyB3ZSBjYW4gcmVhZCB0aGVtKQogICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCkpCmBgYAoKTm93IGl0J3Mgd2F5IGVhc2llciB0byBpZGVudGlmeSB0aGF0IHdlIGhhdmUgYG1pc3NpbmdgIGdlbnJlcyDwn6eQIQoKPiBBIGdvb2QgdmlzdWFsaXNhdGlvbiB3aWxsIHNob3cgeW91IHRoaW5ncyB0aGF0IHlvdSBkaWQgbm90IGV4cGVjdCwgb3IgcmFpc2UgbmV3IHF1ZXN0aW9ucyBhYm91dCB0aGUgZGF0YSAtIEhhZGxleSBXaWNraGFtIGFuZCBHYXJyZXR0IEdyb2xlbXVuZCwgW1IgRm9yIERhdGEgU2NpZW5jZV0oaHR0cHM6Ly9yNGRzLmhhZC5jby5uei9pbnRyb2R1Y3Rpb24uaHRtbCkKCk5vdGUsIHdoZW4gdGhlIHRvcCBnZW5yZSBpcyBkZXNjcmliZWQgYXMgYE1pc3NpbmdgLCB0aGF0IG1lYW5zIHRoYXQgU3BvdGlmeSBkaWQgbm90IGNsYXNzaWZ5IGl0LCBzbyBsZXQncyBnZXQgcmlkIG9mIGl0LgoKYGBge3IgcmVtb3ZlX21pc3Npbmd9CiMgVmlzdWFsaXplIHBvcHVsYXIgZ2VucmVzCnRvcF9nZW5yZXMgJT4lCiAgZmlsdGVyKGFydGlzdF90b3BfZ2VucmUgIT0gIk1pc3NpbmciKSAlPiUgCiAgc2xpY2UoMToxMCkgJT4lIAogIGdncGxvdChtYXBwaW5nID0gYWVzKHggPSBhcnRpc3RfdG9wX2dlbnJlLCB5ID0gbiwKICAgICAgICAgICAgICAgICAgICAgICBmaWxsID0gYXJ0aXN0X3RvcF9nZW5yZSkpICsKICBnZW9tX2NvbChhbHBoYSA9IDAuOCkgKwogIHBhbGV0dGVlcjo6c2NhbGVfZmlsbF9wYWxldHRlZXJfZCgicmNhcnRvY29sb3I6OlZpdmlkIikgKwogIGdndGl0bGUoIlRvcCBnZW5yZXMiKSArCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSksCiAgICAgICAgIyBSb3RhdGVzIHRoZSBYIG1hcmtlcnMgKHNvIHdlIGNhbiByZWFkIHRoZW0pCiAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwKSkKYGBgCgpGcm9tIHRoZSBsaXR0bGUgZGF0YSBleHBsb3JhdGlvbiwgd2UgbGVhcm4gdGhhdCB0aGUgdG9wIHRocmVlIGdlbnJlcyBkb21pbmF0ZSB0aGlzIGRhdGFzZXQuIExldCdzIGNvbmNlbnRyYXRlIG9uIGBhZnJvIGRhbmNlaGFsbGAsIGBhZnJvcG9wYCwgYW5kIGBuaWdlcmlhbiBwb3BgLCBhZGRpdGlvbmFsbHkgZmlsdGVyIHRoZSBkYXRhc2V0IHRvIHJlbW92ZSBhbnl0aGluZyB3aXRoIGEgMCBwb3B1bGFyaXR5IHZhbHVlIChtZWFuaW5nIGl0IHdhcyBub3QgY2xhc3NpZmllZCB3aXRoIGEgcG9wdWxhcml0eSBpbiB0aGUgZGF0YXNldCBhbmQgY2FuIGJlIGNvbnNpZGVyZWQgbm9pc2UgZm9yIG91ciBwdXJwb3Nlcyk6CgpgYGB7ciBuZXdfZGF0YXNldH0KbmlnZXJpYW5fc29uZ3MgPC0gZGYgJT4lIAogICMgQ29uY2VudHJhdGUgb24gdG9wIDMgZ2VucmVzCiAgZmlsdGVyKGFydGlzdF90b3BfZ2VucmUgJWluJSBjKCJhZnJvIGRhbmNlaGFsbCIsICJhZnJvcG9wIiwibmlnZXJpYW4gcG9wIikpICU+JSAKICAjIFJlbW92ZSB1bmNsYXNzaWZpZWQgb2JzZXJ2YXRpb25zCiAgZmlsdGVyKHBvcHVsYXJpdHkgIT0gMCkKCgoKIyBWaXN1YWxpemUgcG9wdWxhciBnZW5yZXMKbmlnZXJpYW5fc29uZ3MgJT4lCiAgY291bnQoYXJ0aXN0X3RvcF9nZW5yZSkgJT4lCiAgZ2dwbG90KG1hcHBpbmcgPSBhZXMoeCA9IGFydGlzdF90b3BfZ2VucmUsIHkgPSBuLAogICAgICAgICAgICAgICAgICAgICAgIGZpbGwgPSBhcnRpc3RfdG9wX2dlbnJlKSkgKwogIGdlb21fY29sKGFscGhhID0gMC44KSArCiAgcGFsZXR0ZWVyOjpzY2FsZV9maWxsX3BhbGV0dGVlcl9kKCJnZ3NjaTo6Y2F0ZWdvcnkxMF9kMyIpICsKICBnZ3RpdGxlKCJUb3AgZ2VucmVzIikgKwogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpKQpgYGAKCkxldCdzIHNlZSB3aGV0aGVyIHRoZXJlIGlzIGFueSBhcHBhcmVudCBsaW5lYXIgcmVsYXRpb25zaGlwIGFtb25nIHRoZSBudW1lcmljYWwgdmFyaWFibGVzIGluIG91ciBkYXRhIHNldC4gVGhpcyByZWxhdGlvbnNoaXAgaXMgcXVhbnRpZmllZCBtYXRoZW1hdGljYWxseSBieSB0aGUgW2NvcnJlbGF0aW9uIHN0YXRpc3RpY10oaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvQ29ycmVsYXRpb24pLgoKVGhlIGNvcnJlbGF0aW9uIHN0YXRpc3RpYyBpcyBhIHZhbHVlIGJldHdlZW4gLTEgYW5kIDEgdGhhdCBpbmRpY2F0ZXMgdGhlIHN0cmVuZ3RoIG9mIGEgcmVsYXRpb25zaGlwLiBWYWx1ZXMgYWJvdmUgMCBpbmRpY2F0ZSBhICpwb3NpdGl2ZSogY29ycmVsYXRpb24gKGhpZ2ggdmFsdWVzIG9mIG9uZSB2YXJpYWJsZSB0ZW5kIHRvIGNvaW5jaWRlIHdpdGggaGlnaCB2YWx1ZXMgb2YgdGhlIG90aGVyKSwgd2hpbGUgdmFsdWVzIGJlbG93IDAgaW5kaWNhdGUgYSAqbmVnYXRpdmUqIGNvcnJlbGF0aW9uIChoaWdoIHZhbHVlcyBvZiBvbmUgdmFyaWFibGUgdGVuZCB0byBjb2luY2lkZSB3aXRoIGxvdyB2YWx1ZXMgb2YgdGhlIG90aGVyKS4KCmBgYHtyIGNvcnJlbGF0aW9ufQojIE5hcnJvdyBkb3duIHRvIG51bWVyaWMgdmFyaWFibGVzIGFuZCBmaWQgY29ycmVsYXRpb24KY29ycl9tYXQgPC0gbmlnZXJpYW5fc29uZ3MgJT4lIAogIHNlbGVjdCh3aGVyZShpcy5udW1lcmljKSkgJT4lIAogIGNvcigpCgojIFZpc3VhbGl6ZSBjb3JyZWxhdGlvbiBtYXRyaXgKY29ycnBsb3QoY29ycl9tYXQsIG9yZGVyID0gJ0FPRScsIGNvbCA9IGMoJ3doaXRlJywgJ2JsYWNrJyksIGJnID0gJ2dvbGQyJykgIApgYGAKClRoZSBkYXRhIGlzIG5vdCBzdHJvbmdseSBjb3JyZWxhdGVkIGV4Y2VwdCBiZXR3ZWVuIGBlbmVyZ3lgIGFuZCBgbG91ZG5lc3NgLCB3aGljaCBtYWtlcyBzZW5zZSwgZ2l2ZW4gdGhhdCBsb3VkIG11c2ljIGlzIHVzdWFsbHkgcHJldHR5IGVuZXJnZXRpYy4gYFBvcHVsYXJpdHlgIGhhcyBhIGNvcnJlc3BvbmRlbmNlIHRvIGByZWxlYXNlIGRhdGVgLCB3aGljaCBhbHNvIG1ha2VzIHNlbnNlLCBhcyBtb3JlIHJlY2VudCBzb25ncyBhcmUgcHJvYmFibHkgbW9yZSBwb3B1bGFyLiBMZW5ndGggYW5kIGVuZXJneSBzZWVtIHRvIGhhdmUgYSBjb3JyZWxhdGlvbiB0b28uCgpJdCB3aWxsIGJlIGludGVyZXN0aW5nIHRvIHNlZSB3aGF0IGEgY2x1c3RlcmluZyBhbGdvcml0aG0gY2FuIG1ha2Ugb2YgdGhpcyBkYXRhIQoKPiDwn46TIE5vdGUgdGhhdCBjb3JyZWxhdGlvbiBkb2VzIG5vdCBpbXBseSBjYXVzYXRpb24hIFdlIGhhdmUgcHJvb2Ygb2YgY29ycmVsYXRpb24gYnV0IG5vIHByb29mIG9mIGNhdXNhdGlvbi4gQW4gW2FtdXNpbmcgd2ViIHNpdGVdKGh0dHBzOi8vdHlsZXJ2aWdlbi5jb20vc3B1cmlvdXMtY29ycmVsYXRpb25zKSBoYXMgc29tZSB2aXN1YWxzIHRoYXQgZW1waGFzaXplIHRoaXMgcG9pbnQuCgojIyMgMi4gRXhwbG9yZSBkYXRhIGRpc3RyaWJ1dGlvbgoKTGV0J3MgYXNrIHNvbWUgbW9yZSBzdWJ0bGUgcXVlc3Rpb25zLiBBcmUgdGhlIGdlbnJlcyBzaWduaWZpY2FudGx5IGRpZmZlcmVudCBpbiB0aGUgcGVyY2VwdGlvbiBvZiB0aGVpciBkYW5jZWFiaWxpdHksIGJhc2VkIG9uIHRoZWlyIHBvcHVsYXJpdHk/IExldCdzIGV4YW1pbmUgb3VyIHRvcCB0aHJlZSBnZW5yZXMgZGF0YSBkaXN0cmlidXRpb24gZm9yIHBvcHVsYXJpdHkgYW5kIGRhbmNlYWJpbGl0eSBhbG9uZyBhIGdpdmVuIHggYW5kIHkgYXhpcyB1c2luZyBbZGVuc2l0eSBwbG90c10oaHR0cHM6Ly93d3cua2hhbmFjYWRlbXkub3JnL21hdGgvYXAtc3RhdGlzdGljcy9kZW5zaXR5LWN1cnZlcy1ub3JtYWwtZGlzdHJpYnV0aW9uLWFwL2RlbnNpdHktY3VydmVzL3YvZGVuc2l0eS1jdXJ2ZXMpLgoKYGBge3J9CiMgUGVyZm9ybSAyRCBrZXJuZWwgZGVuc2l0eSBlc3RpbWF0aW9uCmRlbnNpdHlfZXN0aW1hdGVfMmQgPC0gbmlnZXJpYW5fc29uZ3MgJT4lIAogIGdncGxvdChtYXBwaW5nID0gYWVzKHggPSBwb3B1bGFyaXR5LCB5ID0gZGFuY2VhYmlsaXR5LCBjb2xvciA9IGFydGlzdF90b3BfZ2VucmUpKSArCiAgZ2VvbV9kZW5zaXR5XzJkKGJpbnMgPSA1LCBzaXplID0gMSkgKwogIHBhbGV0dGVlcjo6c2NhbGVfY29sb3JfcGFsZXR0ZWVyX2QoIlJTa2l0dGxlQnJld2VyOjp3aWxkYmVycnkiKSArCiAgeGxpbSgtMjAsIDgwKSArCiAgeWxpbSgwLCAxLjIpCgojIERlbnNpdHkgcGxvdCBiYXNlZCBvbiB0aGUgcG9wdWxhcml0eQpkZW5zaXR5X2VzdGltYXRlX3BvcCA8LSBuaWdlcmlhbl9zb25ncyAlPiUgCiAgZ2dwbG90KG1hcHBpbmcgPSBhZXMoeCA9IHBvcHVsYXJpdHksIGZpbGwgPSBhcnRpc3RfdG9wX2dlbnJlLCBjb2xvciA9IGFydGlzdF90b3BfZ2VucmUpKSArCiAgZ2VvbV9kZW5zaXR5KHNpemUgPSAxLCBhbHBoYSA9IDAuNSkgKwogIHBhbGV0dGVlcjo6c2NhbGVfZmlsbF9wYWxldHRlZXJfZCgiUlNraXR0bGVCcmV3ZXI6OndpbGRiZXJyeSIpICsKICBwYWxldHRlZXI6OnNjYWxlX2NvbG9yX3BhbGV0dGVlcl9kKCJSU2tpdHRsZUJyZXdlcjo6d2lsZGJlcnJ5IikgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikKCiMgRGVuc2l0eSBwbG90IGJhc2VkIG9uIHRoZSBkYW5jZWFiaWxpdHkKZGVuc2l0eV9lc3RpbWF0ZV9kYW5jZSA8LSBuaWdlcmlhbl9zb25ncyAlPiUgCiAgZ2dwbG90KG1hcHBpbmcgPSBhZXMoeCA9IGRhbmNlYWJpbGl0eSwgZmlsbCA9IGFydGlzdF90b3BfZ2VucmUsIGNvbG9yID0gYXJ0aXN0X3RvcF9nZW5yZSkpICsKICBnZW9tX2RlbnNpdHkoc2l6ZSA9IDEsIGFscGhhID0gMC41KSArCiAgcGFsZXR0ZWVyOjpzY2FsZV9maWxsX3BhbGV0dGVlcl9kKCJSU2tpdHRsZUJyZXdlcjo6d2lsZGJlcnJ5IikgKwogIHBhbGV0dGVlcjo6c2NhbGVfY29sb3JfcGFsZXR0ZWVyX2QoIlJTa2l0dGxlQnJld2VyOjp3aWxkYmVycnkiKQoKCiMgUGF0Y2ggZXZlcnl0aGluZyB0b2dldGhlcgpsaWJyYXJ5KHBhdGNod29yaykKZGVuc2l0eV9lc3RpbWF0ZV8yZCAvIChkZW5zaXR5X2VzdGltYXRlX3BvcCArIGRlbnNpdHlfZXN0aW1hdGVfZGFuY2UpCmBgYAoKV2Ugc2VlIHRoYXQgdGhlcmUgYXJlIGNvbmNlbnRyaWMgY2lyY2xlcyB0aGF0IGxpbmUgdXAsIHJlZ2FyZGxlc3Mgb2YgZ2VucmUuIENvdWxkIGl0IGJlIHRoYXQgTmlnZXJpYW4gdGFzdGVzIGNvbnZlcmdlIGF0IGEgY2VydGFpbiBsZXZlbCBvZiBkYW5jZWFiaWxpdHkgZm9yIHRoaXMgZ2VucmU/CgpJbiBnZW5lcmFsLCB0aGUgdGhyZWUgZ2VucmVzIGFsaWduIGluIHRlcm1zIG9mIHRoZWlyIHBvcHVsYXJpdHkgYW5kIGRhbmNlYWJpbGl0eS4gRGV0ZXJtaW5pbmcgY2x1c3RlcnMgaW4gdGhpcyBsb29zZWx5LWFsaWduZWQgZGF0YSB3aWxsIGJlIGEgY2hhbGxlbmdlLiBMZXQncyBzZWUgd2hldGhlciBhIHNjYXR0ZXIgcGxvdCBjYW4gc3VwcG9ydCB0aGlzLgoKYGBge3Igc2NhdHRlcl9wbG90fQojIEEgc2NhdHRlciBwbG90IG9mIHBvcHVsYXJpdHkgYW5kIGRhbmNlYWJpbGl0eQpzY2F0dGVyX3Bsb3QgPC0gbmlnZXJpYW5fc29uZ3MgJT4lIAogIGdncGxvdChtYXBwaW5nID0gYWVzKHggPSBwb3B1bGFyaXR5LCB5ID0gZGFuY2VhYmlsaXR5LCBjb2xvciA9IGFydGlzdF90b3BfZ2VucmUsIHNoYXBlID0gYXJ0aXN0X3RvcF9nZW5yZSkpICsKICBnZW9tX3BvaW50KHNpemUgPSAyLCBhbHBoYSA9IDAuOCkgKwogIHBhbGV0dGVlcjo6c2NhbGVfY29sb3JfcGFsZXR0ZWVyX2QoImZ1dHVyZXZpc2lvbnM6Om1hcnMiKQoKIyBBZGQgYSB0b3VjaCBvZiBpbnRlcmFjdGl2aXR5CmdncGxvdGx5KHNjYXR0ZXJfcGxvdCkKYGBgCgpBIHNjYXR0ZXJwbG90IG9mIHRoZSBzYW1lIGF4ZXMgc2hvd3MgYSBzaW1pbGFyIHBhdHRlcm4gb2YgY29udmVyZ2VuY2UuCgpJbiBnZW5lcmFsLCBmb3IgY2x1c3RlcmluZywgeW91IGNhbiB1c2Ugc2NhdHRlcnBsb3RzIHRvIHNob3cgY2x1c3RlcnMgb2YgZGF0YSwgc28gbWFzdGVyaW5nIHRoaXMgdHlwZSBvZiB2aXN1YWxpemF0aW9uIGlzIHZlcnkgdXNlZnVsLiBJbiB0aGUgbmV4dCBsZXNzb24sIHdlIHdpbGwgdGFrZSB0aGlzIGZpbHRlcmVkIGRhdGEgYW5kIHVzZSBrLW1lYW5zIGNsdXN0ZXJpbmcgdG8gZGlzY292ZXIgZ3JvdXBzIGluIHRoaXMgZGF0YSB0aGF0IHNlZSB0byBvdmVybGFwIGluIGludGVyZXN0aW5nIHdheXMuCgojIyAqKvCfmoAgQ2hhbGxlbmdlKioKCkluIHByZXBhcmF0aW9uIGZvciB0aGUgbmV4dCBsZXNzb24sIG1ha2UgYSBjaGFydCBhYm91dCB0aGUgdmFyaW91cyBjbHVzdGVyaW5nIGFsZ29yaXRobXMgeW91IG1pZ2h0IGRpc2NvdmVyIGFuZCB1c2UgaW4gYSBwcm9kdWN0aW9uIGVudmlyb25tZW50LiBXaGF0IGtpbmRzIG9mIHByb2JsZW1zIGlzIHRoZSBjbHVzdGVyaW5nIHRyeWluZyB0byBhZGRyZXNzPwoKIyMgWyoqUG9zdC1sZWN0dXJlIHF1aXoqKl0oaHR0cHM6Ly9ncmF5LXNhbmQtMDdhMTBmNDAzLjEuYXp1cmVzdGF0aWNhcHBzLm5ldC9xdWl6LzI4LykKCiMjICoqUmV2aWV3ICYgU2VsZiBTdHVkeSoqCgpCZWZvcmUgeW91IGFwcGx5IGNsdXN0ZXJpbmcgYWxnb3JpdGhtcywgYXMgd2UgaGF2ZSBsZWFybmVkLCBpdCdzIGEgZ29vZCBpZGVhIHRvIHVuZGVyc3RhbmQgdGhlIG5hdHVyZSBvZiB5b3VyIGRhdGFzZXQuIFJlYWQgbW9yZSBvbiB0aGlzIHRvcGljIFtoZXJlXShodHRwczovL3d3dy5rZG51Z2dldHMuY29tLzIwMTkvMTAvcmlnaHQtY2x1c3RlcmluZy1hbGdvcml0aG0uaHRtbCkKCkRlZXBlbiB5b3VyIHVuZGVyc3RhbmRpbmcgb2YgY2x1c3RlcmluZyB0ZWNobmlxdWVzOgoKLSAgIFtUcmFpbiBhbmQgRXZhbHVhdGUgQ2x1c3RlcmluZyBNb2RlbHMgdXNpbmcgVGlkeW1vZGVscyBhbmQgZnJpZW5kc10oaHR0cHM6Ly9ycHVicy5jb20vZVJfaWMvY2x1c3RlcmluZykKCi0gICBCcmFkbGV5IEJvZWhta2UgJiBCcmFuZG9uIEdyZWVud2VsbCwgWypIYW5kcy1PbiBNYWNoaW5lIExlYXJuaW5nIHdpdGggUipdKGh0dHBzOi8vYnJhZGxleWJvZWhta2UuZ2l0aHViLmlvL0hPTUwvKSouKgoKIyMgKipBc3NpZ25tZW50KioKCltSZXNlYXJjaCBvdGhlciB2aXN1YWxpemF0aW9ucyBmb3IgY2x1c3RlcmluZ10oaHR0cHM6Ly9naXRodWIuY29tL21pY3Jvc29mdC9NTC1Gb3ItQmVnaW5uZXJzL2Jsb2IvbWFpbi81LUNsdXN0ZXJpbmcvMS1WaXN1YWxpemUvYXNzaWdubWVudC5tZCkKCiMjIFRIQU5LIFlPVSBUTzoKCltKZW4gTG9vcGVyXShodHRwczovL3d3dy50d2l0dGVyLmNvbS9qZW5sb29wZXIpIGZvciBjcmVhdGluZyB0aGUgb3JpZ2luYWwgUHl0aG9uIHZlcnNpb24gb2YgdGhpcyBtb2R1bGUg4pml77iPCgpbYERhc2FuaSBNYWRpcGFsbGlgXShodHRwczovL3R3aXR0ZXIuY29tL2Rhc2FuaV9kZWNvZGVkKSBmb3IgY3JlYXRpbmcgdGhlIGFtYXppbmcgaWxsdXN0cmF0aW9ucyB0aGF0IG1ha2UgbWFjaGluZSBsZWFybmluZyBjb25jZXB0cyBtb3JlIGludGVycHJldGFibGUgYW5kIGVhc2llciB0byB1bmRlcnN0YW5kLgoKSGFwcHkgTGVhcm5pbmcsCgpbRXJpY10oaHR0cHM6Ly90d2l0dGVyLmNvbS9lcmljbnRheSksIEdvbGQgTWljcm9zb2Z0IExlYXJuIFN0dWRlbnQgQW1iYXNzYWRvci4K