diff --git a/5-Clustering/1-Visualize/solution/R/lesson_14.html b/5-Clustering/1-Visualize/solution/R/lesson_14.html new file mode 100644 index 00000000..c6b791f3 --- /dev/null +++ b/5-Clustering/1-Visualize/solution/R/lesson_14.html @@ -0,0 +1,5445 @@ + + + + + + + + + + + + + +Introduction to clustering: Clean, prep and visualize your data + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+
+
+
+
+ +
+ + + + + + + +
+

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 +
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 +
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.Infographic by Dasani Madipalli

  • +
  • 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.

+
+ +
+

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.

+
+
+
+

🚀 Challenge

+

In preparation for the next lesson, make a chart about the various +clustering algorithms you might discover and use in a production +environment. What kinds of problems is the clustering trying to +address?

+
+
+

Post-lecture +quiz

+
+
+

Review & Self Study

+

Before you apply clustering algorithms, as we have learned, it’s a +good idea to understand the nature of your dataset. Read more on this +topic here

+

Deepen your understanding of clustering techniques:

+ +
+ +
+

THANK YOU TO:

+

Jen Looper for +creating the original Python version of this module ♥️

+

Dasani Madipalli +for creating the amazing illustrations that make machine learning +concepts more interpretable and easier to understand.

+

Happy Learning,

+

Eric, Gold Microsoft Learn +Student Ambassador.

+
+ +
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
+ + +
+
+ +
+ + + + + + + + + + + + + + + + +