+
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