Sushant Gote 17PHD1054

This R Notebook demonstrates application ?f different Clusterring algorithms as Kmeans, K-medoids, Diana, Ages, SOM on USArrests data availed by R.

`` # Install packages

install.packages("cluster")
install.packages("dendextend")
install.packages('factoextra')
#Load the packages :

?``{r} ### Installling addtional Packages if required

install.packages(‘kohonen’) library(‘kohonen’) ``` # Kmeans Clustering Clustering is a broad set of techniques for finding subgroups of observations within a data set. When we cluster observations, we w?nt observations in the same group to be similar and observations in different groups to be dissimilar. Because there isn’t a response variable, this is an unsupervised method, which implies that it seeks to find relationships between the n observations w?thout being trained by a response variable. Clustering allows us to identify which observations are alike, and potentially categorize them therein. K-means clustering is the simplest and the most commonly used clustering method for splitting a dataset into?a set of k groups.

K-means algorithm can be summarized as follows:

Specify the number of clusters (K) to be created (by the analyst) Select randomly k objects from the data set as the initial cluster centers or means Assigns each observation to their ?losest centroid, based on the Euclidean distance between the object and the centroid For each of the k clusters update the cluster centroid by calculating the new mean values of all the data points in the cluster. The centroid of a Kth cluster is a vector ?f length p containing the means of all variables for the observations in the kth cluster; p is the number of variables. Iteratively minimize the total within sum of square (Eq. 7). That is, iterate steps 3 and 4 until the cluster assignments stop changing ?r the maximum number of iterations is reached. By default, the R software uses 10 as the default value for the maximum number of iterations.

Optimal Number of clusters

Algorithm to define the optimal clusters:

Compute clustering algorithm (e.?., k-means clustering) for different values of k. For instance, by varying k from 1 to 10 clusters

For each k, calculate the total within-cluster sum of square (wss)

Plot the curve of wss according to the number of clusters k.

The location o? a bend (knee) in the plot is generally considered as an indicator of the appropriate number of clusters.

The results suggest that 4 is the optimal number of clusters as it appears to be the bend in the knee (or elbow).

set.seed(123)

# functi?n to compute total within-cluster sum of square 
wss <- function(k) {
  kmeans(df, k, nstart = 10 )$tot.withinss
}

# Compute and plot wss for k = 1 to k = 15
k.values <- 1:15

# extract wss for 2-15 clusters
wss_values <- map_dbl(k.values, wss)

plot(k.va?ues, wss_values,
       type="b", pch = 19, frame = FALSE, 
       xlab="Number of clusters K",
       ylab="Total within-clusters sum of squares")
set.seed(123)
# function to compute total within-cluster sum of square 
wss <- function(k) {
  kmeans(df, k, nstart = 10 )$tot.withinss
}
# Compute and plot wss for k = 1 to k = 15
k.values <- 1:15
# extract wss for 2-15 clusters
wss_values <- map_dbl(k.values, wss)
plot(k.values, wss_values,
       type="b", pch = 19, frame = FALSE, 
       xlab="Number of clusters K",
       ylab="Total within-clusters sum of squares")

Apply?ng Kmeans for K=4

library('cluster')
library('dendextend')
library('factoextra')
print('Done')
[1] "Done"
 

Visualizing Kmeans Clusters

set.seed(123)
final <- kmeans(df, 4, nstart = 25)
print(final)
K-means clustering with 4 clusters of sizes 13, 16, 13, 8

Cluster means:
      Murder    Assault   UrbanPop        Rape
1 -0.9615407 -1.1066010 -0.9301069 -0.96676331
2 -0.4894375 -0.3826001  0.5758298 -0.26165379
3  0.6950701  1.0394414  0.7226370  1.27693964
4  1.4118898  0.8743346 -0.8145211  0.01927104

Clustering vector:
       Alabama         Alaska        Arizona       Arkansas 
             4              3              3              4 
    California       Colorado    Connecticut       Delaware 
             3              3              2              2 
       Florida        Georgia         Hawaii          Idaho 
             3              4              2              1 
      Illinois        Indiana           Iowa         Kansas 
             3              2              1              2 
      Kentucky      Louisiana          Maine       Maryland 
             1              4              1              3 
 Massachusetts       Michigan      Minnesota    Mississippi 
             2              3              1              4 
      Missouri        Montana       Nebraska         Nevada 
             3              1              1              3 
 New Hampshire     New Jersey     New Mexico       New York 
             1              2              3              3 
North Carolina   North Dakota           Ohio       Oklahoma 
             4              1              2              2 
        Oregon   Pennsylvania   Rhode Island South Carolina 
             2              2              2              4 
  South Dakota      Tennessee          Texas           Utah 
             1              4              3              2 
       Vermont       Virginia     Washington  West Virginia 
             1              2              2              1 
     Wisconsin        Wyoming 
             1              2 

Within cluster sum of squares by cluster:
[1] 11.952463 16.212213 19.922437  8.316061
 (between_SS / total_SS =  71.2 %)

Available components:

[1] "cluster"      "centers"      "totss"        "withinss"    
[5] "tot.withinss" "betweenss"    "size"         "iter"        
[9] "ifault"      

K Medoid Clustering

fviz_cluster(final, data = df)

Vi?ualizing K-medioids Clusters

NA

Hierachical Clustering

Hierarchical clustering can be divided into two main types: agglomerative and divisive.

Agglomerative clustering: It’s also known as AGNES (Agglomerative N?sting). It works in a bottom-up manner. That is, each object is initially considered as a single-element cluster (leaf). At each step of the algorithm, the two clusters that are the most similar are combined into a new bigger cluster (nodes). This procedur? is iterated until all points are member of just one single big cluster (root) (see figure below). The result is a tree which can be plotted as a dendrogram.

Divisive hierarchical clustering: It’s also known as DIANA (Divise Analysis) and it works in a to?-down manner. The algorithm is an inverse order of AGNES. It begins with the root, in which all objects are included in a single cluster. At each step of iteration, the most heterogeneous cluster is divided into two. The process is iterated until all objec?s are in their own cluster .

Note that agglomerative clustering is good at identifying small clusters. Divisive hierarchical clustering is good at identifying large clusters.

The merging or the division of clusters is performed according some (dis)simil?rity measure. In R softwrare, the Euclidean distance is used by default to measure the dissimilarity between each pair of observations.

Tt’s easy to compute dissimilarity measure between two pairs of observations. It’s mentioned above that two clusters th?t are most similar are fused into a new big cluster.

A natural question is : How to measure the dissimilarity between two clusters of observations?

A number of different cluster agglomeration methods (i.e, linkage methods) has been developed to answer to?this question. The most common types methods are:

Maximum or complete linkage clustering: It computes all pairwise dissimilarities between the elements in cluster 1 and the elements in cluster 2, and considers the largest value (i.e., maximum value) of t?ese dissimilarities as the distance between the two clusters. It tends to produce more compact clusters.

Minimum or single linkage clustering: It computes all pairwise dissimilarities between the elements in cluster 1 and the elements in cluster 2, and c?nsiders the smallest of these dissimilarities as a linkage criterion. It tends to produce long, “loose” clusters.

Mean or average linkage clustering: It computes all pairwise dissimilarities between the elements in cluster 1 and the elements in cluster 2,?and considers the average of these dissimilarities as the distance between the two clusters.

Complete linkage and Ward’s method are generally preferred.

Centroid linkage clustering: It computes the dissimilarity between the centroid for cluster 1 (a mean?vector of length p variables) and the centroid for cluster 2.

Ward’s minimum variance method: It minimizes the total within-cluster variance. At each step the pair of clusters with minimum between-cluster distance are merged.

Data preparation and descr?ptive statistics

R dataset USArrest which contains statistics, in arrests per 100,000 residents for assault, murder, and rape in each of the 50 US states in 1973. It includes also the percent of the population living in urban areas.

It contains 50 observa?ions on 4 variables:

[,1] Murder numeric Murder arrests (per 100,000) [,2] Assault numeric Assault arrests (per 100,000) [,3] UrbanPop numeric Percent urban population [,4] Rape numeric Rape arrests (per 100,000)

fviz_cluster(pam.res, data = df)

Before hierarchical clustering, compute some descriptive statistics?

# Load the data set
data("USArrests")
# Remove any missing value (i.e, NA values for not available)
# That might be present in the data
df <- na.omit(USArrests)
# View the firt 6 rows of the data
head(df, n = 6)

Note that the variables have a large different means and variances. This is explained by the fact that the variables are measured in different units; Murder, Rape, and Assault are measured as the number of occurrences per?100 000 people, and UrbanPop is the percentage of the state’s population that lives in an urban area.

They must be standardized (i.e., scaled) to make them comparable. Recall that, standardization consists of transforming the variables such that they have?mean zero and standard deviation one.

desc_stats <- data.frame(
  Min = apply(df, 2, min), # minimum
  Med = apply(df, 2, median), # median
  Mean = apply(df, 2, mean), # mean
  SD = apply(df, 2, sd), # Standard deviation
  Max = apply(df, 2, max) # Maximum
  )
desc_stats <- round(desc_stats, 1)
head(desc_stats)

R functions for hierarchical clustering

There are different functions available in R for computing hierarchical clustering. The commonly used function? are:

hclust() [in stats package] and agnes() [in cluster package] for agglomerative hierarchical clustering (HC) diana() [in cluster package] for divisive HC #### hclust() function hclust() is the built-in R function [in stats package] for computing hier?rchical clustering.

The simplified format is:

hclust(d, method = “complete”)

d a dissimilarity structure as produced by the dist() function. method: The agglomeration method to be used. Allowed values is one of “ward.D”, “ward.D2”, “single”, “complete”,?“average”, “mcquitty”, “median” or “centroid”.

The dist() function is used to compute the Euclidean distance between observations. Finally, observations are clustered using Ward’s method.

df <- scale(df)
head(df)
               Murder   Assault   UrbanPop         Rape
Alabama    1.24256408 0.7828393 -0.5209066 -0.003416473
Alaska     0.50786248 1.1068225 -1.2117642  2.484202941
Arizona    0.07163341 1.4788032  0.9989801  1.042878388
Arkansas   0.23234938 0.2308680 -1.0735927 -0.184916602
California 0.27826823 1.2628144  1.7589234  2.067820292
Colorado   0.02571456 0.3988593  0.8608085  1.864967207
print('Look @ this head')
[1] "Look @ this head"

agnes() and diana() functions

The R function agnes() [in cluster package] can be also used to ?ompute agglomerative hierarchical clustering. The R function diana() [ in cluster package ] is an example of divisive hierarchical clustering.

Agglomerative Nesting (Hierarchical Clustering)

agnes(x, metric = “euclidean”, stand = FALSE, method = “aver?ge”)

DIvisive ANAlysis Clustering

diana(x, metric = “euclidean”, stand = FALSE)

x: data matrix or data frame or dissimilarity matrix. In case of matrix and data frame, rows are observations and columns are variables. In case of a dissimilarity matrix? x is typically the output of daisy() or dist(). metric: the metric to be used for calculating dissimilarities between observations. Possible values are “euclidean” and “manhattan”. stand: if TRUE, then the measurements in x are standardized before calcula?ing the dissimilarities. Measurements are standardized for each variable (column), by subtracting the variable’s mean value and dividing by the variable’s mean absolute deviation method: The clustering method. Possible values includes “average”, “single”, ???complete“,”ward".

The function agnes() returns an object of class “agnes” (see ?agnes.object) which has methods for the functions: print(), summary(), plot(), pltree(), as.dendrogram(), as.hclust() and cutree(). The function diana() returns an object of?class “diana” (see ?diana.object) which has also methods for the functions: print(), summary(), plot(), pltree(), as.dendrogram(), as.hclust() and cutree(). Compared to other agglomerative clustering methods such as hclust(), agnes() has the following feat?res:

It yields the agglomerative coefficient (see agnes.object) which measures the amount of clustering structure found Apart from the usual tree it also provides the banner, a novel graphical display (see plot.agnes).

# Dissimilarity matrix
d <- dist(df, method = "euclidean")
# Hierarchical clustering using Ward's method
res.hc <- hclust(d, method = "ward.D2" )
# Plot the obtained dendrogram
plot(res.hc, cex = 0.6, hang = -1)

library("cluster")
# Compute agnes()
res.agnes <- agnes(df, method = "ward")
# Agglomerative coefficient
res.agnes$ac
[1] 0.934621
# Plot the tree using pltree()
pltree(res.agnes, cex = 0.6, hang = -1, main = "Dendrogram of Agnes") 

print('Done')
[1] "Done"

It’s also possible to draw AGNES dendrogram using the function plot.hclust() and the function plot.dendrogram() as follow:

pltree(res.agnes, cex = 0.6, hang = -1, main = "Dendrogram of Agnes") 

plot.?endrogram()

# plot.hclust()
plot(as.hclust(res.agnes), cex = 0.6, hang = -1)

# plot.dendrogram()
plot(as.dendrogram(res.agnes), cex = 0.6, 
     horiz = TRUE)

# Cut tree into 4 groups
grp <- cutree(res.hc, k = 4)
# Number of members in each cluster
table(grp)
grp
 1  2  3  4 
 7 12 19 12 
plot(res.hc, cex = 0.6)
rect.hclust(res.hc, k = 4, border = 2:5)

Diana Clustering for USArrest dataset

library(factoextra)
fviz_cluster(list(data = df, cluster = grp))

# Compute diana()
res.diana <- diana(df)
print('Diana Computed')
[1] "Diana Computed"
# Plot the tree
pltree(res.diana, cex = 0.6, hang = -1,
       main = "Dendrogram of Diana")

 
# Plot the tree
pltree(res.diana, cex = 0.6, hang = -1,
       main = "Second time Dendrogram of Diana")

As for plotting AGNES dendrogram, the functions plot.hclust() and plot.dendrogram() can be used as follow:

# Divise coefficient; amount of clustering structure found
res.diana$dc
[1] 0.8514345
## [1] 0.8514345

pl?t.dendrogram()

# plot.hclust()
plot(as.hclust(res.diana), cex = 0.6, hang = -1)

Interpretation of the dendrogram

In the dendrogram displayed above, each leaf corresponds to one observation. moving up the tree, observations that are similar to e?ch other are combined into branches, which are themselves fused at a higher height.

The height of the fusion, provided on the vertical axis, indicates the (dis)similarity between two observations. The higher the height of the fusion, the less similar the ?bservations are.

Note that, conclusions about the proximity of two observations can be drawn only based on the height where branches containing those two observations first are fused. The proximity of two observations along the horizontal axis can not be ?sed as a criteria of their similarity.

In order to identify sub-groups (i.e. clusters), Cuttingt the dendrogram at a certain height as described in the next section.

Cut the dendrogram into different groups

The height of the cut to the dendrogram co?trols the number of clusters obtained. It plays the same role as the k in k-means clustering.

The function cutree() is used and it returns a vector containing the cluster number of each observation

plot(as.dendrogram(res.diana), cex = 0.6, 
     horiz = TRUE)

It’s also possible to draw the dendrogram with a border around the 4 clusters. The argument border is used to specify the border ?olors for the rectangles:

# Cut tree into 4 groups
grp <- cutree(res.hc, k = 4)
# Number of members in each cluster
table(grp)
grp
 1  2  3  4 
 7 12 19 12 
 
# Get the names for the members of cluster 1
rownames(df)[grp == 1]
[1] "Alabama"        "Georgia"        "Louisiana"      "Mississippi"    "North Carolina"
[6] "South Carolina" "Tennessee"     
 

Look at leaves

Observe clusters

plot(res.hc, cex = 0.6)
rect.hclust(res.hc, k = 4, border = 2:5)

Using the function fviz_cluster() [in factoextra], visualizing the result in a scatter plot. Observations are represented by points in the plot, using principal components. A frame is drawn around?each cluster.

plot(res.hc, cex = 0.6)
rect.hclust(res.hc, k = 4, border = 2:5)

plot(res.hc, cex = 0.6)
rect.hclust(res.hc, k = 4, border = 2:5)

library(factoextra)
fviz_cluster(list(data = df, cluster = grp))

The function cutree() c?n be used also to cut the tree generated with agnes() and diana() as follow:

library(factoextra)
fviz_cluster(list(data = df, cluster = grp))

# Cut diana() tree into 4 groups
cutree(as.hclust(res.diana), k = 4)
       Alabama         Alaska        Arizona       Arkansas     California       Colorado 
             1              2              2              3              2              2 
   Connecticut       Delaware        Florida        Georgia         Hawaii          Idaho 
             3              3              2              1              3              4 
      Illinois        Indiana           Iowa         Kansas       Kentucky      Louisiana 
             2              3              4              3              4              1 
         Maine       Maryland  Massachusetts       Michigan      Minnesota    Mississippi 
             4              2              3              2              4              1 
      Missouri        Montana       Nebraska         Nevada  New Hampshire     New Jersey 
             2              4              4              2              4              3 
    New Mexico       New York North Carolina   North Dakota           Ohio       Oklahoma 
             2              2              1              4              3              3 
        Oregon   Pennsylvania   Rhode Island South Carolina   South Dakota      Tennessee 
             3              3              3              1              4              1 
         Texas           Utah        Vermont       Virginia     Washington  West Virginia 
             2              3              4              3              3              4 
     Wisconsin        Wyoming 
             4              3 

Self Organizing Map SOM Clustering

# Cut diana() tree into 4 groups
cutree(as.hclust(res.diana), k = 4)
       Alabama         Alaska        Arizona       Arkansas     California       Colorado 
             1              2              2              3              2              2 
   Connecticut       Delaware        Florida        Georgia         Hawaii          Idaho 
             3              3              2              1              3              4 
      Illinois        Indiana           Iowa         Kansas       Kentucky      Louisiana 
             2              3              4              3              4              1 
         Maine       Maryland  Massachusetts       Michigan      Minnesota    Mississippi 
             4              2              3              2              4              1 
      Missouri        Montana       Nebraska         Nevada  New Hampshire     New Jersey 
             2              4              4              2              4              3 
    New Mexico       New York North Carolina   North Dakota           Ohio       Oklahoma 
             2              2              1              4              3              3 
        Oregon   Pennsylvania   Rhode Island South Carolina   South Dakota      Tennessee 
             3              3              3              1              4              1 
         Texas           Utah        Vermont       Virginia     Washington  West Virginia 
             2              3              4              3              3              4 
     Wisconsin        Wyoming 
             4              3 

Training progress for SOM

plot(som_model, type="changes")

Node Counts

The Kohonen packages allows us to visualise the count of how many samples are mapped to each node on the map. This metric can be used as a measure of map quality - ideally the sample distribution is re?atively uniform. Large values in some map areas suggests that a larger map would be benificial. Empty nodes indicate that your map size is too big for the number of samples. Aim for at least 5-10 samples per node when choosing map size.

# Change the data frame with training data to a matrix
# Also center and scale all variables to give them equal importance during
# the SOM training process. 
data_train_matrix <- as.matrix(scale(df))
# Create the SOM Grid - you generally have to specify the size of the 
# training grid prior to training the SOM. Hexagonal and Circular 
# topologies are possible
som_grid <- somgrid(xdim = 20, ydim=20, topo="hexagonal")
# Finally, train the SOM, options for the number of iterations,
# the learning rates, and the neighbourhood are available
 
 
 
som_model <- som(data_train_matrix, somgrid(5, 5, "hexagonal"))
plot(som_model, type="changes")

Neighbour Distance

Often referred to as the “U-Matrix”, this visualisati?n is of the distance between each node and its neighbours. Typically viewed with a grayscale palette, areas of low neighbour distance indicate groups of nodes that are similar. Areas with large distances indicate the nodes are much more dissimilar - and in?icate natural boundaries between node clusters. The U-Matrix can be used to identify clusters within the SOM map. #### U-matrix visualisation plot(som_model, type=“dist.neighbours”, main = “SOM neighbour distances”)

# Node count plot
plot(som_model, type="count", main="Node Counts")

print('many samples are mapped to each node on the map. This metric can be used as a measure of map quality')
[1] "many samples are mapped to each node on the map. This metric can be used as a measure of map quality"

Nodes / Weight vectors

The node weight vectors, or “codes”, are made up of normalised values of the original variables used to generate th? SOM. Each node’s weight vector is representative / similar of the samples mapped to that node. By visualising the weight vectors across the map, analyzing patterns in the distribution of samples and variables. The default visualisation of the weight vecto?s is a “fan diagram”, where individual fan representations of the magnitude of each variable in the weight vector is shown for each node.

#### U-matrix visualisation
plot(som_model, type="dist.neighbours", main = "SOM neighbour distances")

print('SOM Neighbour Distance')
[1] "SOM Neighbour Distance"

Heatmaps

H?atmaps are perhaps the most important visualisation possible for Self-Organising Maps. The use of a weight space view as in (4) that tries to view all dimensions on the one diagram is unsuitable for a high-dimensional (>7 variable) SOM. A SOM heatmap allow? the visualisation of the distribution of a single variable across the map. Typically, a SOM investigative process involves the creation of multiple heatmaps, and then the comparison of these heatmaps to identify interesting areas on the map. It is importa?t to remember that the individual sample positions do not move from one visualisation to another, the map is simply coloured by different variables.

# Weight Vector View
plot(som_model, type="codes")

print('Weight vector View')
[1] "Weight vector View"
# Kohonen Heatmap creation
plot(som_model, type = "property", property = getCodes(som_model)[,4], main=colnames(getCodes(som_model))[4], palette.name=coolBlueHotRed)

plot(som_model, type = "property", property = getCodes(som_model)[,4], main=colnames(getCodes(som_model))[3], palette.name=coolBlueHotRed)

plot(som_model, type = "property", property = getCodes(som_model)[,4], main=colnames(getCodes(som_model))[2], palette.name=coolBlueHotRed)

A more intuitive and useful visualisation is of the variable prior to scaling, which involves some R trickery - using the aggregate function to regenerate the variable from the original training set and th? SOM node/sample mappings. The result is scaled to the real values of the training variable (in this case, unemployment percent).

print('Kohenen HeatMap')
[1] "Kohenen HeatMap"

Clustering

Clustering can be performed on the SOM nodes to i?olate groups of samples with similar metrics. Manual identification of clusters is completed by exploring the heatmaps for a number of variables and drawing up a “story” about the different areas on the map. An estimate of the number of clusters that would?be suitable can be ascertained using a kmeans algorithm and examing for an “elbow-point” in the plot of “within cluster sum of squares”. The Kohonen package documentation shows how a map can be clustered using hierachical clustering. The results of the cl?stering can be visualised using the SOM plot function again.

var <- 3 #define the variable to plot 
var_unscaled <- aggregate(as.numeric(my_data_matrix[,var]), by=list(som_model$unit.classif), FUN=mean, simplify=TRUE)[,2] 

Conclusion

. The cluster formed by Alabama, Alaska, Arizona, California, Dela?are, Florida, Illinois, Louisiana, Maryland, Michigan, Mississippi, Nevada, New Mexico, New York, North Carolina, South Carolina has the highest Murder, Assault and Rape arests (per 100,00) and, not least, the largest population.

. The cluster formed by A?kansas, Colorado, Georgia, Massachusetts, Missouri, New Jersey, Oklahoma, Oregon, Rhode Island, Tennessee, Texas, Virginia, Washington, Wyoming has the intermediate Murder, Assault and Rape arests (per 100,00) and, not least, the largest population.

. Th? cluster formed by Connecticut, Hawaii, Idaho, Indiana, Iowa, Kansas, Kentucky, Maine, Minnesota, Montana, Nebraska, New Hampshire, North Dakota, Ohio, Pennsylvania, South Dakota, Utah, Vermont, West Virginia, Wisconsin has the lowest Murder, Assault and R?pe arests (per 100,00) and, not least, the largest population.

Remarks for Alogorithms

Kmeans , Kedoid, Hierarchical

K-means clustering is a very simple and fast algorithm. Furthermore, it can efficiently deal with very large data sets? However, there are some weaknesses of the k-means approach.

One potential disadvantage of K-means clustering is that it requires us to pre-specify the number of clusters. Hierarchical clustering is an alternative approach which does not require that we c?mmit to a particular choice of clusters. Hierarchical clustering has an added advantage over K-means clustering in that it results in an attractive tree-based representation of the observations, called a dendrogram.

An additional disadvantage of K-means?is that it’s sensitive to outliers and different results can occur if you change the ordering of your data. The Partitioning Around Medoids (PAM) clustering approach is less sensititive to outliers and provides a robust alternative to k-means to deal with ?hese situations.

LS0tDQp0aXRsZTogJ01hY2hpbmUgTGVhbnJpbmcgQXNzaWdubWVudCAyOiBBbmFseXNpcyBvZiBDbHVzdGVyaW5nIEstTWVhbnMsIERJQU5BLCBBR05FUywNCiAgU09NJw0Kb3V0cHV0Og0KICBwZGZfZG9jdW1lbnQ6IGRlZmF1bHQNCiAgaHRtbF9ub3RlYm9vazogZGVmYXVsdA0KICB3b3JkX2RvY3VtZW50OiBkZWZhdWx0DQotLS0NCiMjIyBTdXNoYW50IEdvdGUgMTdQSEQxMDU0DQojIyMjIyBUaGlzIFIgTm90ZWJvb2sgZGVtb25zdHJhdGVzIGFwcGxpY2F0aW9uID9mIGRpZmZlcmVudCBDbHVzdGVycmluZyBhbGdvcml0aG1zIGFzIEttZWFucywgSy1tZWRvaWRzLCBEaWFuYSwgQWdlcywgU09NIG9uIFVTQXJyZXN0cyBkYXRhIGF2YWlsZWQgYnkgUi4gDQoNCiANCmBgDQojIEluc3RhbGwgcGFja2FnZXMNCg0KYGBge3J9DQppbnN0YWxsLnBhY2thZ2VzKCJjbHVzdGVyIikNCmluc3RhbGwucGFja2FnZXMoImRlbmRleHRlbmQiKQ0KaW5zdGFsbC5wYWNrYWdlcygnZmFjdG9leHRyYScpDQojTG9hZCB0aGUgcGFja2FnZXMgOg0KYGBgDQoNCj9gYHtyfQ0KIyMjIEluc3RhbGxsaW5nIGFkZHRpb25hbCBQYWNrYWdlcyBpZiByZXF1aXJlZA0KDQppbnN0YWxsLnBhY2thZ2VzKCdrb2hvbmVuJykNCmxpYnJhcnkoJ2tvaG9uZW4nKQ0KYGBgDQojIEttZWFucyBDbHVzdGVyaW5nDQpDbHVzdGVyaW5nIGlzIGEgYnJvYWQgc2V0IG9mIHRlY2huaXF1ZXMgZm9yIGZpbmRpbmcgc3ViZ3JvdXBzIG9mIG9ic2VydmF0aW9ucyB3aXRoaW4gYSBkYXRhIHNldC4gV2hlbiB3ZSBjbHVzdGVyIG9ic2VydmF0aW9ucywgd2Ugdz9udCBvYnNlcnZhdGlvbnMgaW4gdGhlIHNhbWUgZ3JvdXAgdG8gYmUgc2ltaWxhciBhbmQgb2JzZXJ2YXRpb25zIGluIGRpZmZlcmVudCBncm91cHMgdG8gYmUgZGlzc2ltaWxhci4gQmVjYXVzZSB0aGVyZSBpc24ndCBhIHJlc3BvbnNlIHZhcmlhYmxlLCB0aGlzIGlzIGFuIHVuc3VwZXJ2aXNlZCBtZXRob2QsIHdoaWNoIGltcGxpZXMgdGhhdCBpdCBzZWVrcyB0byBmaW5kIHJlbGF0aW9uc2hpcHMgYmV0d2VlbiB0aGUgDQpuDQogb2JzZXJ2YXRpb25zIHc/dGhvdXQgYmVpbmcgdHJhaW5lZCBieSBhIHJlc3BvbnNlIHZhcmlhYmxlLiBDbHVzdGVyaW5nIGFsbG93cyB1cyB0byBpZGVudGlmeSB3aGljaCBvYnNlcnZhdGlvbnMgYXJlIGFsaWtlLCBhbmQgcG90ZW50aWFsbHkgY2F0ZWdvcml6ZSB0aGVtIHRoZXJlaW4uIEstbWVhbnMgY2x1c3RlcmluZyBpcyB0aGUgc2ltcGxlc3QgYW5kIHRoZSBtb3N0IGNvbW1vbmx5IHVzZWQgY2x1c3RlcmluZyBtZXRob2QgZm9yIHNwbGl0dGluZyBhIGRhdGFzZXQgaW50bz9hIHNldCBvZiBrIGdyb3Vwcy4NCiANCiMgSy1tZWFucyBhbGdvcml0aG0gY2FuIGJlIHN1bW1hcml6ZWQgYXMgZm9sbG93czoNCg0KU3BlY2lmeSB0aGUgbnVtYmVyIG9mIGNsdXN0ZXJzIChLKSB0byBiZSBjcmVhdGVkIChieSB0aGUgYW5hbHlzdCkNClNlbGVjdCByYW5kb21seSBrIG9iamVjdHMgZnJvbSB0aGUgZGF0YSBzZXQgYXMgdGhlIGluaXRpYWwgY2x1c3RlciBjZW50ZXJzIG9yIG1lYW5zDQpBc3NpZ25zIGVhY2ggb2JzZXJ2YXRpb24gdG8gdGhlaXIgP2xvc2VzdCBjZW50cm9pZCwgYmFzZWQgb24gdGhlIEV1Y2xpZGVhbiBkaXN0YW5jZSBiZXR3ZWVuIHRoZSBvYmplY3QgYW5kIHRoZSBjZW50cm9pZA0KRm9yIGVhY2ggb2YgdGhlIGsgY2x1c3RlcnMgdXBkYXRlIHRoZSBjbHVzdGVyIGNlbnRyb2lkIGJ5IGNhbGN1bGF0aW5nIHRoZSBuZXcgbWVhbiB2YWx1ZXMgb2YgYWxsIHRoZSBkYXRhIHBvaW50cyBpbiB0aGUgY2x1c3Rlci4gVGhlIGNlbnRyb2lkIG9mIGEgS3RoIGNsdXN0ZXIgaXMgYSB2ZWN0b3IgP2YgbGVuZ3RoIHAgY29udGFpbmluZyB0aGUgbWVhbnMgb2YgYWxsIHZhcmlhYmxlcyBmb3IgdGhlIG9ic2VydmF0aW9ucyBpbiB0aGUga3RoIGNsdXN0ZXI7IHAgaXMgdGhlIG51bWJlciBvZiB2YXJpYWJsZXMuDQpJdGVyYXRpdmVseSBtaW5pbWl6ZSB0aGUgdG90YWwgd2l0aGluIHN1bSBvZiBzcXVhcmUgKEVxLiA3KS4gVGhhdCBpcywgaXRlcmF0ZSBzdGVwcyAzIGFuZCA0IHVudGlsIHRoZSBjbHVzdGVyIGFzc2lnbm1lbnRzIHN0b3AgY2hhbmdpbmcgP3IgdGhlIG1heGltdW0gbnVtYmVyIG9mIGl0ZXJhdGlvbnMgaXMgcmVhY2hlZC4gQnkgZGVmYXVsdCwgdGhlIFIgc29mdHdhcmUgdXNlcyAxMCBhcyB0aGUgZGVmYXVsdCB2YWx1ZSBmb3IgdGhlIG1heGltdW0gbnVtYmVyIG9mIGl0ZXJhdGlvbnMuDQoNCiMgT3B0aW1hbCBOdW1iZXIgb2YgY2x1c3RlcnMNCg0KIyMjIEFsZ29yaXRobSB0byBkZWZpbmUgdGhlIG9wdGltYWwgY2x1c3RlcnM6DQoNCiMjIyMgQ29tcHV0ZSBjbHVzdGVyaW5nIGFsZ29yaXRobSAoZS4/Liwgay1tZWFucyBjbHVzdGVyaW5nKSBmb3IgZGlmZmVyZW50IHZhbHVlcyBvZiBrLiBGb3IgaW5zdGFuY2UsIGJ5IHZhcnlpbmcgayBmcm9tIDEgdG8gMTAgY2x1c3RlcnMNCiMjIyMgRm9yIGVhY2ggaywgY2FsY3VsYXRlIHRoZSB0b3RhbCB3aXRoaW4tY2x1c3RlciBzdW0gb2Ygc3F1YXJlICh3c3MpDQojIyMjIFBsb3QgdGhlIGN1cnZlIG9mIHdzcyBhY2NvcmRpbmcgdG8gdGhlIG51bWJlciBvZiBjbHVzdGVycyBrLg0KIyMjIyBUaGUgbG9jYXRpb24gbz8gYSBiZW5kIChrbmVlKSBpbiB0aGUgcGxvdCBpcyBnZW5lcmFsbHkgY29uc2lkZXJlZCBhcyBhbiBpbmRpY2F0b3Igb2YgdGhlIGFwcHJvcHJpYXRlIG51bWJlciBvZiBjbHVzdGVycy4NCiMjIyMgVGhlIHJlc3VsdHMgc3VnZ2VzdCB0aGF0IDQgaXMgdGhlIG9wdGltYWwgbnVtYmVyIG9mIGNsdXN0ZXJzIGFzIGl0IGFwcGVhcnMgdG8gYmUgdGhlIGJlbmQgaW4gdGhlIGtuZWUgKG9yIGVsYm93KS4NCg0KYGBge3J9DQpzZXQuc2VlZCgxMjMpDQoNCiMgZnVuY3RpP24gdG8gY29tcHV0ZSB0b3RhbCB3aXRoaW4tY2x1c3RlciBzdW0gb2Ygc3F1YXJlIA0Kd3NzIDwtIGZ1bmN0aW9uKGspIHsNCiAga21lYW5zKGRmLCBrLCBuc3RhcnQgPSAxMCApJHRvdC53aXRoaW5zcw0KfQ0KDQojIENvbXB1dGUgYW5kIHBsb3Qgd3NzIGZvciBrID0gMSB0byBrID0gMTUNCmsudmFsdWVzIDwtIDE6MTUNCg0KIyBleHRyYWN0IHdzcyBmb3IgMi0xNSBjbHVzdGVycw0Kd3NzX3ZhbHVlcyA8LSBtYXBfZGJsKGsudmFsdWVzLCB3c3MpDQoNCnBsb3Qoay52YT91ZXMsIHdzc192YWx1ZXMsDQogICAgICAgdHlwZT0iYiIsIHBjaCA9IDE5LCBmcmFtZSA9IEZBTFNFLCANCiAgICAgICB4bGFiPSJOdW1iZXIgb2YgY2x1c3RlcnMgSyIsDQogICAgICAgeWxhYj0iVG90YWwgd2l0aGluLWNsdXN0ZXJzIHN1bSBvZiBzcXVhcmVzIikNCmBgYA0KDQogDQogDQpgYGB7cn0NCmxpYnJhcnkoJ2NsdXN0ZXInKQ0KbGlicmFyeSgnZGVuZGV4dGVuZCcpDQpsaWJyYXJ5KCdmYWN0b2V4dHJhJykNCnByaW50KCdEb25lJykNCiANCmBgYA0KDQojIyBBcHBseT9uZyBLbWVhbnMgZm9yIEs9NA0KYGBge3J9DQpzZXQuc2VlZCgxMjMpDQpmaW5hbCA8LSBrbWVhbnMoZGYsIDQsIG5zdGFydCA9IDI1KQ0KcHJpbnQoZmluYWwpDQpgYGANCiMgVmlzdWFsaXppbmcgS21lYW5zIENsdXN0ZXJzDQoNCmBgYHtyfQ0KZnZpel9jbHVzdGVyKGZpbmFsLCBkYXRhID0gZGYpDQpgYGANCiMjIEsgTWVkb2lkIENsdXN0ZXJpbmcNCg0KYGBge3J9DQpwYW0ucmVzIDwtIHBhbShkZiwgNCkNCnByaW50KHBhbS5yZXMpDQpgYGANCmBgYHtyfQ0KDQogDQoNCmBgYA0KIyBWaT91YWxpemluZyBLLW1lZGlvaWRzIENsdXN0ZXJzDQpgYGB7cn0NCmZ2aXpfY2x1c3RlcihwYW0ucmVzLCBkYXRhID0gZGYpDQpgYGANCg0KDQojIEhpZXJhY2hpY2FsIENsdXN0ZXJpbmcNCkhpZXJhcmNoaWNhbCBjbHVzdGVyaW5nIGNhbiBiZSBkaXZpZGVkIGludG8gdHdvIG1haW4gdHlwZXM6IGFnZ2xvbWVyYXRpdmUgYW5kIGRpdmlzaXZlLg0KDQpBZ2dsb21lcmF0aXZlIGNsdXN0ZXJpbmc6IEl0J3MgYWxzbyBrbm93biBhcyBBR05FUyAoQWdnbG9tZXJhdGl2ZSBOP3N0aW5nKS4gSXQgd29ya3MgaW4gYSBib3R0b20tdXAgbWFubmVyLiBUaGF0IGlzLCBlYWNoIG9iamVjdCBpcyBpbml0aWFsbHkgY29uc2lkZXJlZCBhcyBhIHNpbmdsZS1lbGVtZW50IGNsdXN0ZXIgKGxlYWYpLiBBdCBlYWNoIHN0ZXAgb2YgdGhlIGFsZ29yaXRobSwgdGhlIHR3byBjbHVzdGVycyB0aGF0IGFyZSB0aGUgbW9zdCBzaW1pbGFyIGFyZSBjb21iaW5lZCBpbnRvIGEgbmV3IGJpZ2dlciBjbHVzdGVyIChub2RlcykuIFRoaXMgcHJvY2VkdXI/IGlzIGl0ZXJhdGVkIHVudGlsIGFsbCBwb2ludHMgYXJlIG1lbWJlciBvZiBqdXN0IG9uZSBzaW5nbGUgYmlnIGNsdXN0ZXIgKHJvb3QpIChzZWUgZmlndXJlIGJlbG93KS4gVGhlIHJlc3VsdCBpcyBhIHRyZWUgd2hpY2ggY2FuIGJlIHBsb3R0ZWQgYXMgYSBkZW5kcm9ncmFtLg0KDQpEaXZpc2l2ZSBoaWVyYXJjaGljYWwgY2x1c3RlcmluZzogSXQncyBhbHNvIGtub3duIGFzIERJQU5BIChEaXZpc2UgQW5hbHlzaXMpIGFuZCBpdCB3b3JrcyBpbiBhIHRvPy1kb3duIG1hbm5lci4gVGhlIGFsZ29yaXRobSBpcyBhbiBpbnZlcnNlIG9yZGVyIG9mIEFHTkVTLiBJdCBiZWdpbnMgd2l0aCB0aGUgcm9vdCwgaW4gd2hpY2ggYWxsIG9iamVjdHMgYXJlIGluY2x1ZGVkIGluIGEgc2luZ2xlIGNsdXN0ZXIuIEF0IGVhY2ggc3RlcCBvZiBpdGVyYXRpb24sIHRoZSBtb3N0IGhldGVyb2dlbmVvdXMgY2x1c3RlciBpcyBkaXZpZGVkIGludG8gdHdvLiBUaGUgcHJvY2VzcyBpcyBpdGVyYXRlZCB1bnRpbCBhbGwgb2JqZWM/cyBhcmUgaW4gdGhlaXIgb3duIGNsdXN0ZXIgIC4NCg0KTm90ZSB0aGF0IGFnZ2xvbWVyYXRpdmUgY2x1c3RlcmluZyBpcyBnb29kIGF0IGlkZW50aWZ5aW5nIHNtYWxsIGNsdXN0ZXJzLiBEaXZpc2l2ZSBoaWVyYXJjaGljYWwgY2x1c3RlcmluZyBpcyBnb29kIGF0IGlkZW50aWZ5aW5nIGxhcmdlIGNsdXN0ZXJzLg0KDQpUaGUgbWVyZ2luZyBvciB0aGUgZGl2aXNpb24gb2YgY2x1c3RlcnMgaXMgcGVyZm9ybWVkIGFjY29yZGluZyBzb21lIChkaXMpc2ltaWw/cml0eSBtZWFzdXJlLiBJbiBSIHNvZnR3cmFyZSwgdGhlIEV1Y2xpZGVhbiBkaXN0YW5jZSBpcyB1c2VkIGJ5IGRlZmF1bHQgdG8gbWVhc3VyZSB0aGUgZGlzc2ltaWxhcml0eSBiZXR3ZWVuIGVhY2ggcGFpciBvZiBvYnNlcnZhdGlvbnMuDQoNClR0J3MgZWFzeSB0byBjb21wdXRlIGRpc3NpbWlsYXJpdHkgbWVhc3VyZSBiZXR3ZWVuIHR3byBwYWlycyBvZiBvYnNlcnZhdGlvbnMuIEl0J3MgbWVudGlvbmVkIGFib3ZlIHRoYXQgdHdvIGNsdXN0ZXJzIHRoP3QgYXJlIG1vc3Qgc2ltaWxhciBhcmUgZnVzZWQgaW50byBhIG5ldyBiaWcgY2x1c3Rlci4NCg0KQSBuYXR1cmFsIHF1ZXN0aW9uIGlzIDogSG93IHRvIG1lYXN1cmUgdGhlIGRpc3NpbWlsYXJpdHkgYmV0d2VlbiB0d28gY2x1c3RlcnMgb2Ygb2JzZXJ2YXRpb25zPw0KDQpBIG51bWJlciBvZiBkaWZmZXJlbnQgY2x1c3RlciBhZ2dsb21lcmF0aW9uIG1ldGhvZHMgKGkuZSwgbGlua2FnZSBtZXRob2RzKSBoYXMgYmVlbiBkZXZlbG9wZWQgdG8gYW5zd2VyIHRvP3RoaXMgcXVlc3Rpb24uIFRoZSBtb3N0IGNvbW1vbiB0eXBlcyBtZXRob2RzIGFyZToNCg0KDQpNYXhpbXVtIG9yIGNvbXBsZXRlIGxpbmthZ2UgY2x1c3RlcmluZzogSXQgY29tcHV0ZXMgYWxsIHBhaXJ3aXNlIGRpc3NpbWlsYXJpdGllcyBiZXR3ZWVuIHRoZSBlbGVtZW50cyBpbiBjbHVzdGVyIDEgYW5kIHRoZSBlbGVtZW50cyBpbiBjbHVzdGVyIDIsIGFuZCBjb25zaWRlcnMgdGhlIGxhcmdlc3QgdmFsdWUgKGkuZS4sIG1heGltdW0gdmFsdWUpIG9mIHQ/ZXNlIGRpc3NpbWlsYXJpdGllcyBhcyB0aGUgZGlzdGFuY2UgYmV0d2VlbiB0aGUgdHdvIGNsdXN0ZXJzLiBJdCB0ZW5kcyB0byBwcm9kdWNlIG1vcmUgY29tcGFjdCBjbHVzdGVycy4gDQoNCk1pbmltdW0gb3Igc2luZ2xlIGxpbmthZ2UgY2x1c3RlcmluZzogSXQgY29tcHV0ZXMgYWxsIHBhaXJ3aXNlIGRpc3NpbWlsYXJpdGllcyBiZXR3ZWVuIHRoZSBlbGVtZW50cyBpbiBjbHVzdGVyIDEgYW5kIHRoZSBlbGVtZW50cyBpbiBjbHVzdGVyIDIsIGFuZCBjP25zaWRlcnMgdGhlIHNtYWxsZXN0IG9mIHRoZXNlIGRpc3NpbWlsYXJpdGllcyBhcyBhIGxpbmthZ2UgY3JpdGVyaW9uLiBJdCB0ZW5kcyB0byBwcm9kdWNlIGxvbmcsICJsb29zZSIgY2x1c3RlcnMuDQoNCk1lYW4gb3IgYXZlcmFnZSBsaW5rYWdlIGNsdXN0ZXJpbmc6IEl0IGNvbXB1dGVzIGFsbCBwYWlyd2lzZSBkaXNzaW1pbGFyaXRpZXMgYmV0d2VlbiB0aGUgZWxlbWVudHMgaW4gY2x1c3RlciAxIGFuZCB0aGUgZWxlbWVudHMgaW4gY2x1c3RlciAyLD9hbmQgY29uc2lkZXJzIHRoZSBhdmVyYWdlIG9mIHRoZXNlIGRpc3NpbWlsYXJpdGllcyBhcyB0aGUgZGlzdGFuY2UgYmV0d2VlbiB0aGUgdHdvIGNsdXN0ZXJzLg0KDQpDb21wbGV0ZSBsaW5rYWdlIGFuZCBXYXJkJ3MgbWV0aG9kIGFyZSBnZW5lcmFsbHkgcHJlZmVycmVkLg0KDQpDZW50cm9pZCBsaW5rYWdlIGNsdXN0ZXJpbmc6IEl0IGNvbXB1dGVzIHRoZSBkaXNzaW1pbGFyaXR5IGJldHdlZW4gdGhlIGNlbnRyb2lkIGZvciBjbHVzdGVyIDEgKGEgbWVhbj92ZWN0b3Igb2YgbGVuZ3RoIHAgdmFyaWFibGVzKSBhbmQgdGhlIGNlbnRyb2lkIGZvciBjbHVzdGVyIDIuDQoNCldhcmQncyBtaW5pbXVtIHZhcmlhbmNlIG1ldGhvZDogSXQgbWluaW1pemVzIHRoZSB0b3RhbCB3aXRoaW4tY2x1c3RlciB2YXJpYW5jZS4gQXQgZWFjaCBzdGVwIHRoZSBwYWlyIG9mIGNsdXN0ZXJzIHdpdGggbWluaW11bSBiZXR3ZWVuLWNsdXN0ZXIgZGlzdGFuY2UgYXJlIG1lcmdlZC4NCg0KIyBEYXRhIHByZXBhcmF0aW9uIGFuZCBkZXNjcj9wdGl2ZSBzdGF0aXN0aWNzDQpSIGRhdGFzZXQgVVNBcnJlc3Qgd2hpY2ggY29udGFpbnMgc3RhdGlzdGljcywgaW4gYXJyZXN0cyBwZXIgMTAwLDAwMCByZXNpZGVudHMgZm9yIGFzc2F1bHQsIG11cmRlciwgYW5kIHJhcGUgaW4gZWFjaCBvZiB0aGUgNTAgVVMgc3RhdGVzIGluIDE5NzMuIEl0IGluY2x1ZGVzIGFsc28gdGhlIHBlcmNlbnQgb2YgdGhlIHBvcHVsYXRpb24gbGl2aW5nIGluIHVyYmFuIGFyZWFzLg0KDQpJdCBjb250YWlucyA1MCBvYnNlcnZhP2lvbnMgb24gNCB2YXJpYWJsZXM6DQoNClssMV0gTXVyZGVyIG51bWVyaWMgTXVyZGVyIGFycmVzdHMgKHBlciAxMDAsMDAwKQ0KWywyXSBBc3NhdWx0IG51bWVyaWMgQXNzYXVsdCBhcnJlc3RzIChwZXIgMTAwLDAwMCkNClssM10gVXJiYW5Qb3AgbnVtZXJpYyBQZXJjZW50IHVyYmFuIHBvcHVsYXRpb24NClssNF0gUmFwZSBudW1lcmljIFJhcGUgYXJyZXN0cyAocGVyIDEwMCwwMDApDQoNCg0KYGBge3J9DQojIExvYWQgdGhlIGRhdGEgc2V0DQpkYXRhKCJVU0FycmVzP3MiKQ0KDQojIFJlbW92ZSBhbnkgbWlzc2luZyB2YWx1ZSAoaS5lLCBOQSB2YWx1ZXMgZm9yIG5vdCBhdmFpbGFibGUpDQojIFRoYXQgbWlnaHQgYmUgcHJlc2VudCBpbiB0aGUgZGF0YQ0KZGYgPC0gbmEub21pdChVU0FycmVzdHMpDQoNCiMgVmlldyB0aGUgZmlydCA2IHJvd3Mgb2YgdGhlIGRhdGENCmhlYWQoZGYsIG4gPSA2KQ0KYGBgDQojIyMgQmVmb3JlIGhpZXJhcmNoaWNhbCBjbHVzdGVyaW5nLCAgY29tcHV0ZSBzb21lIGRlc2NyaXB0aXZlIHN0YXRpc3RpY3M/DQoNCg0KYGBge3J9DQoNCmRlc2Nfc3RhdHMgPC0gZGF0YS5mcmFtZSgNCiAgTWluID0gYXBwbHkoZGYsIDIsIG1pbiksICMgbWluaW11bQ0KICBNZWQgPSBhcHBseShkZiwgMiwgbWVkaWFuKSwgIyBtZWRpYW4NCiAgTWVhbiA9IGFwcGx5KGRmLCAyLCBtZWFuKSwgIyBtZWFuDQogIFNEID0gYXBwbHkoZGYsIDIsIHNkKSwgIyBTdGFuZGFyZCBkZXZpYXRpb24NCiAgTWF4ID0gYXBwbHkoZGYsIDIsIG1heCkgIyBNYXhpbXVtDQogICkNCmRlc2Nfc3RhdHMgPC0gcm91bmQoZD9zY19zdGF0cywgMSkNCmhlYWQoZGVzY19zdGF0cykNCg0KYGBgDQpOb3RlIHRoYXQgdGhlIHZhcmlhYmxlcyBoYXZlIGEgbGFyZ2UgZGlmZmVyZW50IG1lYW5zIGFuZCB2YXJpYW5jZXMuIFRoaXMgaXMgZXhwbGFpbmVkIGJ5IHRoZSBmYWN0IHRoYXQgdGhlIHZhcmlhYmxlcyBhcmUgbWVhc3VyZWQgaW4gZGlmZmVyZW50IHVuaXRzOyBNdXJkZXIsIFJhcGUsIGFuZCBBc3NhdWx0IGFyZSBtZWFzdXJlZCBhcyB0aGUgbnVtYmVyIG9mIG9jY3VycmVuY2VzIHBlcj8xMDAgMDAwIHBlb3BsZSwgYW5kIFVyYmFuUG9wIGlzIHRoZSBwZXJjZW50YWdlIG9mIHRoZSBzdGF0ZSdzIHBvcHVsYXRpb24gdGhhdCBsaXZlcyBpbiBhbiB1cmJhbiBhcmVhLg0KDQpUaGV5IG11c3QgYmUgc3RhbmRhcmRpemVkIChpLmUuLCBzY2FsZWQpIHRvIG1ha2UgdGhlbSBjb21wYXJhYmxlLiBSZWNhbGwgdGhhdCwgc3RhbmRhcmRpemF0aW9uIGNvbnNpc3RzIG9mIHRyYW5zZm9ybWluZyB0aGUgdmFyaWFibGVzIHN1Y2ggdGhhdCB0aGV5IGhhdmU/bWVhbiB6ZXJvIGFuZCBzdGFuZGFyZCBkZXZpYXRpb24gb25lLg0KIA0KYGBge3J9DQpkZiA8LSBzY2FsZShkZikNCmhlYWQoZGYpDQpwcmludCgnTG9vayBAIHRoaXMgaGVhZCcpDQpgYGANCg0KIyMgUiBmdW5jdGlvbnMgZm9yIGhpZXJhcmNoaWNhbCBjbHVzdGVyaW5nDQpUaGVyZSBhcmUgZGlmZmVyZW50IGZ1bmN0aW9ucyBhdmFpbGFibGUgaW4gUiBmb3IgY29tcHV0aW5nIGhpZXJhcmNoaWNhbCBjbHVzdGVyaW5nLiBUaGUgY29tbW9ubHkgdXNlZCBmdW5jdGlvbj8gYXJlOg0KDQpoY2x1c3QoKSBbaW4gc3RhdHMgcGFja2FnZV0gYW5kIGFnbmVzKCkgW2luIGNsdXN0ZXIgcGFja2FnZV0gZm9yIGFnZ2xvbWVyYXRpdmUgaGllcmFyY2hpY2FsIGNsdXN0ZXJpbmcgKEhDKQ0KZGlhbmEoKSBbaW4gY2x1c3RlciBwYWNrYWdlXSBmb3IgZGl2aXNpdmUgSEMNCiMjIyMgaGNsdXN0KCkgZnVuY3Rpb24NCmhjbHVzdCgpIGlzIHRoZSBidWlsdC1pbiBSIGZ1bmN0aW9uIFtpbiBzdGF0cyBwYWNrYWdlXSBmb3IgY29tcHV0aW5nIGhpZXI/cmNoaWNhbCBjbHVzdGVyaW5nLg0KDQpUaGUgc2ltcGxpZmllZCBmb3JtYXQgaXM6DQoNCmhjbHVzdChkLCBtZXRob2QgPSAiY29tcGxldGUiKQ0KDQpkIGEgZGlzc2ltaWxhcml0eSBzdHJ1Y3R1cmUgYXMgcHJvZHVjZWQgYnkgdGhlIGRpc3QoKSBmdW5jdGlvbi4NCm1ldGhvZDogVGhlIGFnZ2xvbWVyYXRpb24gbWV0aG9kIHRvIGJlIHVzZWQuIEFsbG93ZWQgdmFsdWVzIGlzIG9uZSBvZiAid2FyZC5EIiwgIndhcmQuRDIiLCAic2luZ2xlIiwgImNvbXBsZXRlIiw/ImF2ZXJhZ2UiLCAibWNxdWl0dHkiLCAibWVkaWFuIiBvciAiY2VudHJvaWQiLg0KDQoNClRoZSBkaXN0KCkgZnVuY3Rpb24gaXMgdXNlZCB0byBjb21wdXRlIHRoZSBFdWNsaWRlYW4gZGlzdGFuY2UgYmV0d2VlbiBvYnNlcnZhdGlvbnMuIEZpbmFsbHksIG9ic2VydmF0aW9ucyBhcmUgY2x1c3RlcmVkIHVzaW5nIFdhcmQncyBtZXRob2QuDQoNCmBgYHtyfQ0KIyBEaXNzaW1pbGFyaXR5IG1hdHJpeA0KZCA8LSBkaXN0KGRmLCBtZXRob2QgPSAiZXVjbGlkZWFuIik/DQoNCiMgSGllcmFyY2hpY2FsIGNsdXN0ZXJpbmcgdXNpbmcgV2FyZCdzIG1ldGhvZA0KcmVzLmhjIDwtIGhjbHVzdChkLCBtZXRob2QgPSAid2FyZC5EMiIgKQ0KIyBQbG90IHRoZSBvYnRhaW5lZCBkZW5kcm9ncmFtDQpwbG90KHJlcy5oYywgY2V4ID0gMC42LCBoYW5nID0gLTEpDQoNCmBgYA0KIyBhZ25lcygpIGFuZCBkaWFuYSgpIGZ1bmN0aW9ucw0KVGhlIFIgZnVuY3Rpb24gYWduZXMoKSBbaW4gY2x1c3RlciBwYWNrYWdlXSBjYW4gYmUgYWxzbyB1c2VkIHRvID9vbXB1dGUgYWdnbG9tZXJhdGl2ZSBoaWVyYXJjaGljYWwgY2x1c3RlcmluZy4gVGhlIFIgZnVuY3Rpb24gZGlhbmEoKSBbIGluIGNsdXN0ZXIgcGFja2FnZSBdIGlzIGFuIGV4YW1wbGUgb2YgZGl2aXNpdmUgaGllcmFyY2hpY2FsIGNsdXN0ZXJpbmcuDQoNCiMjIyBBZ2dsb21lcmF0aXZlIE5lc3RpbmcgKEhpZXJhcmNoaWNhbCBDbHVzdGVyaW5nKQ0KYWduZXMoeCwgbWV0cmljID0gImV1Y2xpZGVhbiIsIHN0YW5kID0gRkFMU0UsIG1ldGhvZCA9ICJhdmVyP2dlIikNCg0KIyMjIERJdmlzaXZlIEFOQWx5c2lzIENsdXN0ZXJpbmcNCmRpYW5hKHgsIG1ldHJpYyA9ICJldWNsaWRlYW4iLCBzdGFuZCA9IEZBTFNFKQ0KDQp4OiBkYXRhIG1hdHJpeCBvciBkYXRhIGZyYW1lIG9yIGRpc3NpbWlsYXJpdHkgbWF0cml4LiBJbiBjYXNlIG9mIG1hdHJpeCBhbmQgZGF0YSBmcmFtZSwgcm93cyBhcmUgb2JzZXJ2YXRpb25zIGFuZCBjb2x1bW5zIGFyZSB2YXJpYWJsZXMuIEluIGNhc2Ugb2YgYSBkaXNzaW1pbGFyaXR5IG1hdHJpeD8geCBpcyB0eXBpY2FsbHkgdGhlIG91dHB1dCBvZiBkYWlzeSgpIG9yIGRpc3QoKS4NCm1ldHJpYzogdGhlIG1ldHJpYyB0byBiZSB1c2VkIGZvciBjYWxjdWxhdGluZyBkaXNzaW1pbGFyaXRpZXMgYmV0d2VlbiBvYnNlcnZhdGlvbnMuIFBvc3NpYmxlIHZhbHVlcyBhcmUgImV1Y2xpZGVhbiIgYW5kICJtYW5oYXR0YW4iLg0Kc3RhbmQ6IGlmIFRSVUUsIHRoZW4gdGhlIG1lYXN1cmVtZW50cyBpbiB4IGFyZSBzdGFuZGFyZGl6ZWQgYmVmb3JlIGNhbGN1bGE/aW5nIHRoZSBkaXNzaW1pbGFyaXRpZXMuIE1lYXN1cmVtZW50cyBhcmUgc3RhbmRhcmRpemVkIGZvciBlYWNoIHZhcmlhYmxlIChjb2x1bW4pLCBieSBzdWJ0cmFjdGluZyB0aGUgdmFyaWFibGUncyBtZWFuIHZhbHVlIGFuZCBkaXZpZGluZyBieSB0aGUgdmFyaWFibGUncyBtZWFuIGFic29sdXRlIGRldmlhdGlvbg0KbWV0aG9kOiBUaGUgY2x1c3RlcmluZyBtZXRob2QuIFBvc3NpYmxlIHZhbHVlcyBpbmNsdWRlcyAiYXZlcmFnZSIsICJzaW5nbGUiLCA/Pz9jb21wbGV0ZSIsICJ3YXJkIi4NCg0KDQpUaGUgZnVuY3Rpb24gYWduZXMoKSByZXR1cm5zIGFuIG9iamVjdCBvZiBjbGFzcyAiYWduZXMiIChzZWUgP2FnbmVzLm9iamVjdCkgd2hpY2ggaGFzIG1ldGhvZHMgZm9yIHRoZSBmdW5jdGlvbnM6IHByaW50KCksIHN1bW1hcnkoKSwgcGxvdCgpLCBwbHRyZWUoKSwgYXMuZGVuZHJvZ3JhbSgpLCBhcy5oY2x1c3QoKSBhbmQgY3V0cmVlKCkuDQpUaGUgZnVuY3Rpb24gZGlhbmEoKSByZXR1cm5zIGFuIG9iamVjdCBvZj9jbGFzcyAiZGlhbmEiIChzZWUgP2RpYW5hLm9iamVjdCkgd2hpY2ggaGFzIGFsc28gbWV0aG9kcyBmb3IgdGhlIGZ1bmN0aW9uczogcHJpbnQoKSwgc3VtbWFyeSgpLCBwbG90KCksIHBsdHJlZSgpLCBhcy5kZW5kcm9ncmFtKCksIGFzLmhjbHVzdCgpIGFuZCBjdXRyZWUoKS4NCkNvbXBhcmVkIHRvIG90aGVyIGFnZ2xvbWVyYXRpdmUgY2x1c3RlcmluZyBtZXRob2RzIHN1Y2ggYXMgaGNsdXN0KCksIGFnbmVzKCkgaGFzIHRoZSBmb2xsb3dpbmcgZmVhdD9yZXM6DQoNCkl0IHlpZWxkcyB0aGUgYWdnbG9tZXJhdGl2ZSBjb2VmZmljaWVudCAoc2VlIGFnbmVzLm9iamVjdCkgd2hpY2ggbWVhc3VyZXMgdGhlIGFtb3VudCBvZiBjbHVzdGVyaW5nIHN0cnVjdHVyZSBmb3VuZA0KQXBhcnQgZnJvbSB0aGUgdXN1YWwgdHJlZSBpdCBhbHNvIHByb3ZpZGVzIHRoZSBiYW5uZXIsIGEgbm92ZWwgZ3JhcGhpY2FsIGRpc3BsYXkgKHNlZSBwbG90LmFnbmVzKS4NCmBgYHtyfQ0KbGlicmFyeSgiY2x1c3RlciIpDQojIENvbXB1dGUgP2duZXMoKQ0KcmVzLmFnbmVzIDwtIGFnbmVzKGRmLCBtZXRob2QgPSAid2FyZCIpDQojIEFnZ2xvbWVyYXRpdmUgY29lZmZpY2llbnQNCnJlcy5hZ25lcyRhYw0KYGBgDQpgYGB7cn0NCiMgUGxvdCB0aGUgdHJlZSB1c2luZyBwbHRyZWUoKQ0KcGx0cmVlKHJlcy5hZ25lcywgY2V4ID0gMC42LCBoYW5nID0gLTEsIG1haW4gPSAiRGVuZHJvZ3JhbSBvZiBBZ25lcyIpIA0KcHJpbnQoJ0RvbmUnKQ0KYGBgDQpgYGB7cn0NCnBsdHJlZShyZXMuYWduZXMsIGNleCA9IDAuNiwgaD9uZyA9IC0xLCBtYWluID0gIkRlbmRyb2dyYW0gb2YgQWduZXMiKSANCmBgYA0KDQojIyMjIEl0J3MgYWxzbyBwb3NzaWJsZSB0byBkcmF3IEFHTkVTIGRlbmRyb2dyYW0gdXNpbmcgdGhlIGZ1bmN0aW9uIHBsb3QuaGNsdXN0KCkgYW5kIHRoZSBmdW5jdGlvbiBwbG90LmRlbmRyb2dyYW0oKSBhcyBmb2xsb3c6DQoNCmBgYHtyfQ0KIyBwbG90LmhjbHVzdCgpDQpwbG90KGFzLmhjbHVzdChyZXMuYWduZXMpLCBjZXggPSAwLjYsIGhhbmcgPSAtMSkNCg0KYGBgDQpwbG90Lj9lbmRyb2dyYW0oKQ0KYGBge3J9DQojIHBsb3QuZGVuZHJvZ3JhbSgpDQpwbG90KGFzLmRlbmRyb2dyYW0ocmVzLmFnbmVzKSwgY2V4ID0gMC42LCANCiAgICAgaG9yaXogPSBUUlVFKQ0KYGBgDQpgYGB7cn0NCiMgQ3V0IHRyZWUgaW50byA0IGdyb3Vwcw0KZ3JwIDwtIGN1dHJlZShyZXMuaGMsIGsgPSA0KQ0KDQojIE51bWJlciBvZiBtZW1iZXJzIGluIGVhY2ggY2x1c3Rlcg0KdGFibGUoZ3JwKQ0KDQpgYGANCg0KYGBge3J9DQpwbG90KHJlcy5oYywgY2V4ID0gMC42KQ0KcmVjdC4/Y2x1c3QocmVzLmhjLCBrID0gNCwgYm9yZGVyID0gMjo1KQ0KDQpgYGANCg0KDQpgYGB7cn0NCmxpYnJhcnkoZmFjdG9leHRyYSkNCmZ2aXpfY2x1c3RlcihsaXN0KGRhdGEgPSBkZiwgY2x1c3RlciA9IGdycCkpDQpgYGANCg0KDQoNCg0KDQojIyMgRGlhbmEgQ2x1c3RlcmluZyAgZm9yIFVTQXJyZXN0IGRhdGFzZXQNCiANCiANCg0KYGBge3J9DQojIENvbXB1dGUgZGlhbmEoKQ0KcmVzLmRpYW5hIDwtIGRpYW5hKGRmKQ0KcHJpbnQoJ0RpYW5hIENvbXB1dGVkJykNCmBgYA0KDQpgYGB7cn0NCiM/UGxvdCB0aGUgdHJlZQ0KcGx0cmVlKHJlcy5kaWFuYSwgY2V4ID0gMC42LCBoYW5nID0gLTEsDQogICAgICAgbWFpbiA9ICJEZW5kcm9ncmFtIG9mIERpYW5hIikNCmBgYA0KYGBge3J9DQogDQojIFBsb3QgdGhlIHRyZWUNCnBsdHJlZShyZXMuZGlhbmEsIGNleCA9IDAuNiwgaGFuZyA9IC0xLA0KICAgICAgIG1haW4gPSAiU2Vjb25kIHRpbWUgRGVuZHJvZ3JhbSBvZiBEaWFuYSIpDQpgYGANCg0KYGBge3J9DQojIERpdmlzZSBjb2VmZmljaWVudDsgYW1vdW50IG9mIGNsdT90ZXJpbmcgc3RydWN0dXJlIGZvdW5kDQpyZXMuZGlhbmEkZGMNCiMjIFsxXSAwLjg1MTQzNDUNCg0KYGBgDQojIyMjIEFzIGZvciBwbG90dGluZyBBR05FUyBkZW5kcm9ncmFtLCB0aGUgZnVuY3Rpb25zIHBsb3QuaGNsdXN0KCkgYW5kIHBsb3QuZGVuZHJvZ3JhbSgpIGNhbiBiZSB1c2VkIGFzIGZvbGxvdzoNCg0KYGBge3J9DQojIHBsb3QuaGNsdXN0KCkNCnBsb3QoYXMuaGNsdXN0KHJlcy5kaWFuYSksIGNleCA9IDAuNiwgaGFuZyA9IC0xKQ0KDQpgYGANCg0KIyMjIyBwbD90LmRlbmRyb2dyYW0oKQ0KYGBge3J9DQpwbG90KGFzLmRlbmRyb2dyYW0ocmVzLmRpYW5hKSwgY2V4ID0gMC42LCANCiAgICAgaG9yaXogPSBUUlVFKQ0KYGBgDQogDQojIEludGVycHJldGF0aW9uIG9mIHRoZSBkZW5kcm9ncmFtDQpJbiB0aGUgZGVuZHJvZ3JhbSBkaXNwbGF5ZWQgYWJvdmUsIGVhY2ggbGVhZiBjb3JyZXNwb25kcyB0byBvbmUgb2JzZXJ2YXRpb24uIG1vdmluZyB1cCB0aGUgdHJlZSwgb2JzZXJ2YXRpb25zIHRoYXQgYXJlIHNpbWlsYXIgdG8gZT9jaCBvdGhlciBhcmUgY29tYmluZWQgaW50byBicmFuY2hlcywgd2hpY2ggYXJlIHRoZW1zZWx2ZXMgZnVzZWQgYXQgYSBoaWdoZXIgaGVpZ2h0Lg0KDQpUaGUgaGVpZ2h0IG9mIHRoZSBmdXNpb24sIHByb3ZpZGVkIG9uIHRoZSB2ZXJ0aWNhbCBheGlzLCBpbmRpY2F0ZXMgdGhlIChkaXMpc2ltaWxhcml0eSBiZXR3ZWVuIHR3byBvYnNlcnZhdGlvbnMuIFRoZSBoaWdoZXIgdGhlIGhlaWdodCBvZiB0aGUgZnVzaW9uLCB0aGUgbGVzcyBzaW1pbGFyIHRoZSA/YnNlcnZhdGlvbnMgYXJlLg0KDQpOb3RlIHRoYXQsIGNvbmNsdXNpb25zIGFib3V0IHRoZSBwcm94aW1pdHkgb2YgdHdvIG9ic2VydmF0aW9ucyBjYW4gYmUgZHJhd24gb25seSBiYXNlZCBvbiB0aGUgaGVpZ2h0IHdoZXJlIGJyYW5jaGVzIGNvbnRhaW5pbmcgdGhvc2UgdHdvIG9ic2VydmF0aW9ucyBmaXJzdCBhcmUgZnVzZWQuIFRoZSBwcm94aW1pdHkgb2YgdHdvIG9ic2VydmF0aW9ucyBhbG9uZyB0aGUgaG9yaXpvbnRhbCBheGlzIGNhbiBub3QgYmUgP3NlZCBhcyBhIGNyaXRlcmlhIG9mIHRoZWlyIHNpbWlsYXJpdHkuDQoNCkluIG9yZGVyIHRvIGlkZW50aWZ5IHN1Yi1ncm91cHMgKGkuZS4gY2x1c3RlcnMpLCBDdXR0aW5ndCB0aGUgZGVuZHJvZ3JhbSBhdCBhIGNlcnRhaW4gaGVpZ2h0IGFzIGRlc2NyaWJlZCBpbiB0aGUgbmV4dCBzZWN0aW9uLg0KDQojIyMjIEN1dCB0aGUgZGVuZHJvZ3JhbSBpbnRvIGRpZmZlcmVudCBncm91cHMNClRoZSBoZWlnaHQgb2YgdGhlIGN1dCB0byB0aGUgZGVuZHJvZ3JhbSBjbz90cm9scyB0aGUgbnVtYmVyIG9mIGNsdXN0ZXJzIG9idGFpbmVkLiBJdCBwbGF5cyB0aGUgc2FtZSByb2xlIGFzIHRoZSBrIGluIGstbWVhbnMgY2x1c3RlcmluZy4NCg0KVGhlIGZ1bmN0aW9uIGN1dHJlZSgpIGlzIHVzZWQgYW5kIGl0IHJldHVybnMgYSB2ZWN0b3IgY29udGFpbmluZyB0aGUgY2x1c3RlciBudW1iZXIgb2YgZWFjaCBvYnNlcnZhdGlvbg0KYGBge3J9DQojIEN1dCB0cmVlIGludG8gNCBncm91cHMNCmdycCA8LSBjdXRyZWUocmVzLmhjLCBrID0/NCkNCiMgTnVtYmVyIG9mIG1lbWJlcnMgaW4gZWFjaCBjbHVzdGVyDQp0YWJsZShncnApDQogDQojIEdldCB0aGUgbmFtZXMgZm9yIHRoZSBtZW1iZXJzIG9mIGNsdXN0ZXIgMQ0Kcm93bmFtZXMoZGYpW2dycCA9PSAxXQ0KIA0KYGBgDQoNCkl0J3MgYWxzbyBwb3NzaWJsZSB0byBkcmF3IHRoZSBkZW5kcm9ncmFtIHdpdGggYSBib3JkZXIgYXJvdW5kIHRoZSA0IGNsdXN0ZXJzLiBUaGUgYXJndW1lbnQgYm9yZGVyIGlzIHVzZWQgdG8gc3BlY2lmeSB0aGUgYm9yZGVyID9vbG9ycyBmb3IgdGhlIHJlY3RhbmdsZXM6DQpgYGB7cn0NCg0KcGxvdChyZXMuaGMsIGNleCA9IDAuNikNCnJlY3QuaGNsdXN0KHJlcy5oYywgayA9IDQsIGJvcmRlciA9IDI6NSkNCg0KYGBgDQojIyMgTG9vayBhdCBsZWF2ZXMNCmBgYHtyfQ0KDQoNCg0KYGBgDQoNCmBgYHtyfQ0KcGxvdChyZXMuaGMsIGNleCA9IDAuNikNCnJlY3QuaGNsdXN0KHJlcy5oYywgayA9IDQsIGJvcmRlciA9IDI6NSkNCmBgYA0KDQojIyMjIE9ic2VydmUgY2x1c3RlcnMgDQpgYGB7cn0NCnBsb3QocmVzLmhjPyBjZXggPSAwLjYpDQpyZWN0LmhjbHVzdChyZXMuaGMsIGsgPSA0LCBib3JkZXIgPSAyOjUpDQoNCmBgYA0KVXNpbmcgdGhlIGZ1bmN0aW9uIGZ2aXpfY2x1c3RlcigpIFtpbiBmYWN0b2V4dHJhXSwgICB2aXN1YWxpemluZyB0aGUgcmVzdWx0IGluIGEgc2NhdHRlciBwbG90LiBPYnNlcnZhdGlvbnMgYXJlIHJlcHJlc2VudGVkIGJ5IHBvaW50cyBpbiB0aGUgcGxvdCwgdXNpbmcgcHJpbmNpcGFsIGNvbXBvbmVudHMuIEEgZnJhbWUgaXMgZHJhd24gYXJvdW5kP2VhY2ggY2x1c3Rlci4NCmBgYHtyfQ0KcGxvdChyZXMuaGMsIGNleCA9IDAuNikNCnJlY3QuaGNsdXN0KHJlcy5oYywgayA9IDQsIGJvcmRlciA9IDI6NSkNCg0KbGlicmFyeShmYWN0b2V4dHJhKQ0KZnZpel9jbHVzdGVyKGxpc3QoZGF0YSA9IGRmLCBjbHVzdGVyID0gZ3JwKSkNCmBgYA0KYGBge3J9DQpsaWJyYXJ5KGZhY3RvZXh0cmEpDQpmdml6X2NsdXN0ZXIobGlzdChkYXRhID0gZGYsIGNsdXN0ZXIgPSBncnApKQ0KYGBgDQoNClRoZSBmdW5jdGlvbiBjdXRyZWUoKSBjP24gYmUgdXNlZCBhbHNvIHRvIGN1dCB0aGUgdHJlZSBnZW5lcmF0ZWQgd2l0aCBhZ25lcygpIGFuZCBkaWFuYSgpIGFzIGZvbGxvdzoNCg0KYGBge3J9DQojIEN1dCBhZ25lcygpIHRyZWUgaW50byA0IGdyb3Vwcw0KY3V0cmVlKHJlcy5hZ25lcywgayA9IDQpDQoNCmBgYA0KYGBge3J9DQoNCmBgYA0KDQpgYGB7cn0NCg0KIyBDdXQgZGlhbmEoKSB0cmVlIGludG8gNCBncm91cHMNCmN1dHJlZShhcy5oY2x1c3QocmVzLmRpYW5hKSwgayA9IDQpDQpgYGANCg0KYGBge3J9DQoNCiMgQ3V0P2RpYW5hKCkgdHJlZSBpbnRvIDQgZ3JvdXBzDQpjdXRyZWUoYXMuaGNsdXN0KHJlcy5kaWFuYSksIGsgPSA0KQ0KYGBgDQojIyBTZWxmIE9yZ2FuaXppbmcgTWFwIFNPTSBDbHVzdGVyaW5nDQpgYGB7cn0NCiMgQ2hhbmdlIHRoZSBkYXRhIGZyYW1lIHdpdGggdHJhaW5pbmcgZGF0YSB0byBhIG1hdHJpeA0KIyBBbHNvIGNlbnRlciBhbmQgc2NhbGUgYWxsIHZhcmlhYmxlcyB0byBnaXZlIHRoZW0gZXF1YWwgaW1wb3J0YW5jZSBkdXJpbmcNCiMgdGhlIFNPTSB0cmFpP2luZyBwcm9jZXNzLiANCmRhdGFfdHJhaW5fbWF0cml4IDwtIGFzLm1hdHJpeChzY2FsZShkZikpDQojIENyZWF0ZSB0aGUgU09NIEdyaWQgLSB5b3UgZ2VuZXJhbGx5IGhhdmUgdG8gc3BlY2lmeSB0aGUgc2l6ZSBvZiB0aGUgDQojIHRyYWluaW5nIGdyaWQgcHJpb3IgdG8gdHJhaW5pbmcgdGhlIFNPTS4gSGV4YWdvbmFsIGFuZCBDaXJjdWxhciANCiMgdG9wb2xvZ2llcyBhcmUgcG9zc2libGUNCnNvbV9ncmlkIDwtIHNvbWdyaWQoeGRpbSA9IDIwLCB5ZGltPT8wLCB0b3BvPSJoZXhhZ29uYWwiKQ0KIyBGaW5hbGx5LCB0cmFpbiB0aGUgU09NLCBvcHRpb25zIGZvciB0aGUgbnVtYmVyIG9mIGl0ZXJhdGlvbnMsDQojIHRoZSBsZWFybmluZyByYXRlcywgYW5kIHRoZSBuZWlnaGJvdXJob29kIGFyZSBhdmFpbGFibGUNCg0KIA0KDQogDQogDQpzb21fbW9kZWwgPC0gc29tKGRhdGFfdHJhaW5fbWF0cml4LCBzb21ncmlkKDUsIDUsICJoZXhhZ29uYWwiKSkNCg0KYGBgDQogDQojIyMjIFRyYWluaW5nIHByb2dyZXNzIGZvciBTT00NCmBgYD9yfQ0KcGxvdChzb21fbW9kZWwsIHR5cGU9ImNoYW5nZXMiKQ0KYGBgDQojIyMgTm9kZSBDb3VudHMNClRoZSBLb2hvbmVuIHBhY2thZ2VzIGFsbG93cyB1cyB0byB2aXN1YWxpc2UgdGhlIGNvdW50IG9mIGhvdyBtYW55IHNhbXBsZXMgYXJlIG1hcHBlZCB0byBlYWNoIG5vZGUgb24gdGhlIG1hcC4gVGhpcyBtZXRyaWMgY2FuIGJlIHVzZWQgYXMgYSBtZWFzdXJlIG9mIG1hcCBxdWFsaXR5IC0gaWRlYWxseSB0aGUgc2FtcGxlIGRpc3RyaWJ1dGlvbiBpcyByZT9hdGl2ZWx5IHVuaWZvcm0uIExhcmdlIHZhbHVlcyBpbiBzb21lIG1hcCBhcmVhcyBzdWdnZXN0cyB0aGF0IGEgbGFyZ2VyIG1hcCB3b3VsZCBiZSBiZW5pZmljaWFsLiBFbXB0eSBub2RlcyBpbmRpY2F0ZSB0aGF0IHlvdXIgbWFwIHNpemUgaXMgdG9vIGJpZyBmb3IgdGhlIG51bWJlciBvZiBzYW1wbGVzLiBBaW0gZm9yIGF0IGxlYXN0IDUtMTAgc2FtcGxlcyBwZXIgbm9kZSB3aGVuIGNob29zaW5nIG1hcCBzaXplLg0KYGBge3J9DQojIE5vZGUgY291bnQ/cGxvdA0KcGxvdChzb21fbW9kZWwsIHR5cGU9ImNvdW50IiwgbWFpbj0iTm9kZSBDb3VudHMiKQ0KYGBgDQpgYGB7cn0NCnByaW50KCdtYW55IHNhbXBsZXMgYXJlIG1hcHBlZCB0byBlYWNoIG5vZGUgb24gdGhlIG1hcC4gVGhpcyBtZXRyaWMgY2FuIGJlIHVzZWQgYXMgYSBtZWFzdXJlIG9mIG1hcCBxdWFsaXR5JykNCmBgYA0KIyMjIE5laWdoYm91ciBEaXN0YW5jZQ0KT2Z0ZW4gcmVmZXJyZWQgdG8gYXMgdGhlICJVLU1hdHJpeCIsIHRoaXMgdmlzdWFsaXNhdGk/biBpcyBvZiB0aGUgZGlzdGFuY2UgYmV0d2VlbiBlYWNoIG5vZGUgYW5kIGl0cyBuZWlnaGJvdXJzLiBUeXBpY2FsbHkgdmlld2VkIHdpdGggYSBncmF5c2NhbGUgcGFsZXR0ZSwgYXJlYXMgb2YgbG93IG5laWdoYm91ciBkaXN0YW5jZSBpbmRpY2F0ZSBncm91cHMgb2Ygbm9kZXMgdGhhdCBhcmUgc2ltaWxhci4gQXJlYXMgd2l0aCBsYXJnZSBkaXN0YW5jZXMgaW5kaWNhdGUgdGhlIG5vZGVzIGFyZSBtdWNoIG1vcmUgZGlzc2ltaWxhciAtIGFuZCBpbj9pY2F0ZSBuYXR1cmFsIGJvdW5kYXJpZXMgYmV0d2VlbiBub2RlIGNsdXN0ZXJzLiBUaGUgVS1NYXRyaXggY2FuIGJlIHVzZWQgdG8gaWRlbnRpZnkgY2x1c3RlcnMgd2l0aGluIHRoZSBTT00gbWFwLg0KIyMjIyBVLW1hdHJpeCB2aXN1YWxpc2F0aW9uDQpwbG90KHNvbV9tb2RlbCwgdHlwZT0iZGlzdC5uZWlnaGJvdXJzIiwgbWFpbiA9ICJTT00gbmVpZ2hib3VyIGRpc3RhbmNlcyIpDQpgYGB7cn0NCiMjIyMgVS1tYXRyaXggdmlzdWFsaXNhdGlvbg0KcGxvdCg/b21fbW9kZWwsIHR5cGU9ImRpc3QubmVpZ2hib3VycyIsIG1haW4gPSAiU09NIG5laWdoYm91ciBkaXN0YW5jZXMiKQ0KYGBgDQoNCmBgYHtyfQ0KcHJpbnQoJ1NPTSBOZWlnaGJvdXIgRGlzdGFuY2UnKQ0KYGBgDQojIE5vZGVzIC8gV2VpZ2h0IHZlY3RvcnMNClRoZSBub2RlIHdlaWdodCB2ZWN0b3JzLCBvciAiY29kZXMiLCBhcmUgbWFkZSB1cCBvZiBub3JtYWxpc2VkIHZhbHVlcyBvZiB0aGUgb3JpZ2luYWwgdmFyaWFibGVzIHVzZWQgdG8gZ2VuZXJhdGUgdGg/IFNPTS4gRWFjaCBub2RlJ3Mgd2VpZ2h0IHZlY3RvciBpcyByZXByZXNlbnRhdGl2ZSAvIHNpbWlsYXIgb2YgdGhlIHNhbXBsZXMgbWFwcGVkIHRvIHRoYXQgbm9kZS4gQnkgdmlzdWFsaXNpbmcgdGhlIHdlaWdodCB2ZWN0b3JzIGFjcm9zcyB0aGUgbWFwLCBhbmFseXppbmcgcGF0dGVybnMgaW4gdGhlIGRpc3RyaWJ1dGlvbiBvZiBzYW1wbGVzIGFuZCB2YXJpYWJsZXMuIFRoZSBkZWZhdWx0IHZpc3VhbGlzYXRpb24gb2YgdGhlIHdlaWdodCB2ZWN0bz9zIGlzIGEgImZhbiBkaWFncmFtIiwgd2hlcmUgaW5kaXZpZHVhbCBmYW4gcmVwcmVzZW50YXRpb25zIG9mIHRoZSBtYWduaXR1ZGUgb2YgZWFjaCB2YXJpYWJsZSBpbiB0aGUgd2VpZ2h0IHZlY3RvciBpcyBzaG93biBmb3IgZWFjaCBub2RlLg0KDQpgYGB7cn0NCiMgV2VpZ2h0IFZlY3RvciBWaWV3DQpwbG90KHNvbV9tb2RlbCwgdHlwZT0iY29kZXMiKQ0KYGBgDQoNCiMNCmBgYHtyfQ0KcHJpbnQoJ1dlaWdodCB2ZWN0b3IgVmlldycpDQpgYGANCiMjIEhlYXRtYXBzDQpIP2F0bWFwcyBhcmUgcGVyaGFwcyB0aGUgbW9zdCBpbXBvcnRhbnQgdmlzdWFsaXNhdGlvbiBwb3NzaWJsZSBmb3IgU2VsZi1PcmdhbmlzaW5nIE1hcHMuIFRoZSB1c2Ugb2YgYSB3ZWlnaHQgc3BhY2UgdmlldyBhcyBpbiAoNCkgdGhhdCB0cmllcyB0byB2aWV3IGFsbCBkaW1lbnNpb25zIG9uIHRoZSBvbmUgZGlhZ3JhbSBpcyB1bnN1aXRhYmxlIGZvciBhIGhpZ2gtZGltZW5zaW9uYWwgKD43IHZhcmlhYmxlKSBTT00uIEEgU09NIGhlYXRtYXAgYWxsb3c/IHRoZSB2aXN1YWxpc2F0aW9uIG9mIHRoZSBkaXN0cmlidXRpb24gb2YgYSBzaW5nbGUgdmFyaWFibGUgYWNyb3NzIHRoZSBtYXAuIFR5cGljYWxseSwgYSBTT00gaW52ZXN0aWdhdGl2ZSBwcm9jZXNzIGludm9sdmVzIHRoZSBjcmVhdGlvbiBvZiBtdWx0aXBsZSBoZWF0bWFwcywgYW5kIHRoZW4gdGhlIGNvbXBhcmlzb24gb2YgdGhlc2UgaGVhdG1hcHMgdG8gaWRlbnRpZnkgaW50ZXJlc3RpbmcgYXJlYXMgb24gdGhlIG1hcC4gSXQgaXMgaW1wb3J0YT90IHRvIHJlbWVtYmVyIHRoYXQgdGhlIGluZGl2aWR1YWwgc2FtcGxlIHBvc2l0aW9ucyBkbyBub3QgbW92ZSBmcm9tIG9uZSB2aXN1YWxpc2F0aW9uIHRvIGFub3RoZXIsIHRoZSBtYXAgaXMgc2ltcGx5IGNvbG91cmVkIGJ5IGRpZmZlcmVudCB2YXJpYWJsZXMuDQogDQoNCmBgYHtyfQ0KIyBLb2hvbmVuIEhlYXRtYXAgY3JlYXRpb24NCnBsb3Qoc29tX21vZGVsLCB0eXBlID0gInByb3BlcnR5IiwgcHJvcGVydHkgPSBnZXRDb2Rlcyhzb21fbW9kZWwpWyw0XSw/bWFpbj1jb2xuYW1lcyhnZXRDb2Rlcyhzb21fbW9kZWwpKVs0XSwgcGFsZXR0ZS5uYW1lPWNvb2xCbHVlSG90UmVkKQ0KDQpgYGANCmBgYHtyfQ0KDQpwbG90KHNvbV9tb2RlbCwgdHlwZSA9ICJwcm9wZXJ0eSIsIHByb3BlcnR5ID0gZ2V0Q29kZXMoc29tX21vZGVsKVssNF0sIG1haW49Y29sbmFtZXMoZ2V0Q29kZXMoc29tX21vZGVsKSlbM10sIHBhbGV0dGUubmFtZT1jb29sQmx1ZUhvdFJlZCkNCmBgYA0KYGBge3J9DQoNCnBsb3Qoc29tX21vZGVsLCB0eXBlID0gIj9yb3BlcnR5IiwgcHJvcGVydHkgPSBnZXRDb2Rlcyhzb21fbW9kZWwpWyw0XSwgbWFpbj1jb2xuYW1lcyhnZXRDb2Rlcyhzb21fbW9kZWwpKVsyXSwgcGFsZXR0ZS5uYW1lPWNvb2xCbHVlSG90UmVkKQ0KYGBgDQpgYGB7cn0NCg0KcGxvdChzb21fbW9kZWwsIHR5cGUgPSAicHJvcGVydHkiLCBwcm9wZXJ0eSA9IGdldENvZGVzKHNvbV9tb2RlbClbLDRdLCBtYWluPWNvbG5hbWVzKGdldENvZGVzKHNvbV9tb2RlbCkpWzFdLCBwYWxldHRlLm5hbWU9Y29vbEJsdT9Ib3RSZWQpDQpgYGANCg0KIw0KYGBge3J9DQpwcmludCgnS29oZW5lbiBIZWF0TWFwJykNCmBgYA0KQSBtb3JlIGludHVpdGl2ZSBhbmQgdXNlZnVsIHZpc3VhbGlzYXRpb24gaXMgb2YgdGhlIHZhcmlhYmxlIHByaW9yIHRvIHNjYWxpbmcsIHdoaWNoIGludm9sdmVzIHNvbWUgUiB0cmlja2VyeSAtIHVzaW5nIHRoZSBhZ2dyZWdhdGUgZnVuY3Rpb24gdG8gcmVnZW5lcmF0ZSB0aGUgdmFyaWFibGUgZnJvbSB0aGUgb3JpZ2luYWwgdHJhaW5pbmcgc2V0IGFuZCB0aD8gU09NIG5vZGUvc2FtcGxlIG1hcHBpbmdzLiBUaGUgcmVzdWx0IGlzIHNjYWxlZCB0byB0aGUgcmVhbCB2YWx1ZXMgb2YgdGhlIHRyYWluaW5nIHZhcmlhYmxlIChpbiB0aGlzIGNhc2UsIHVuZW1wbG95bWVudCBwZXJjZW50KS4NCmBgYHtyfQ0KDQpgYGANCiANCmBgYHtyfQ0KdmFyIDwtIDMgI2RlZmluZSB0aGUgdmFyaWFibGUgdG8gcGxvdCANCnZhcl91bnNjYWxlZCA8LSBhZ2dyZWdhdGUoYXMubnVtZXJpYyhteV9kYXRhX21hdHJpeFssdmFyXSksIGJ5PWxpcz8oc29tX21vZGVsJHVuaXQuY2xhc3NpZiksIEZVTj1tZWFuLCBzaW1wbGlmeT1UUlVFKVssMl0gDQoNCmBgYA0KYGBge3J9DQpwbG90KHNvbV9tb2RlbCwgdHlwZT0ncHJvcGVydHknLCBwcm9wZXJ0eSA9IHZhcl91bnNjYWxlZCwgbWFpbj1uYW1lcyhteV9kYXRhX21hdHJpeClbdmFyXSwgcGFsZXR0ZS5uYW1lPWNvb2xCbHVlSG90UmVkKQ0KYGBgDQoNCiMjIENsdXN0ZXJpbmcNCkNsdXN0ZXJpbmcgY2FuIGJlIHBlcmZvcm1lZCBvbiB0aGUgU09NIG5vZGVzIHRvIGk/b2xhdGUgZ3JvdXBzIG9mIHNhbXBsZXMgd2l0aCBzaW1pbGFyIG1ldHJpY3MuIE1hbnVhbCBpZGVudGlmaWNhdGlvbiBvZiBjbHVzdGVycyBpcyBjb21wbGV0ZWQgYnkgZXhwbG9yaW5nIHRoZSBoZWF0bWFwcyBmb3IgYSBudW1iZXIgb2YgdmFyaWFibGVzIGFuZCBkcmF3aW5nIHVwIGEgInN0b3J5IiBhYm91dCB0aGUgZGlmZmVyZW50IGFyZWFzIG9uIHRoZSBtYXAuIEFuIGVzdGltYXRlIG9mIHRoZSBudW1iZXIgb2YgY2x1c3RlcnMgdGhhdCB3b3VsZD9iZSBzdWl0YWJsZSBjYW4gYmUgYXNjZXJ0YWluZWQgdXNpbmcgYSBrbWVhbnMgYWxnb3JpdGhtIGFuZCBleGFtaW5nIGZvciBhbiAiZWxib3ctcG9pbnQiIGluIHRoZSBwbG90IG9mICJ3aXRoaW4gY2x1c3RlciBzdW0gb2Ygc3F1YXJlcyIuICBUaGUgS29ob25lbiBwYWNrYWdlIGRvY3VtZW50YXRpb24gc2hvd3MgaG93IGEgbWFwIGNhbiBiZSBjbHVzdGVyZWQgdXNpbmcgaGllcmFjaGljYWwgY2x1c3RlcmluZy4gVGhlIHJlc3VsdHMgb2YgdGhlIGNsP3N0ZXJpbmcgY2FuIGJlIHZpc3VhbGlzZWQgdXNpbmcgdGhlIFNPTSBwbG90IGZ1bmN0aW9uIGFnYWluLg0KYGBge3J9DQpwbG90KHNvbV9tb2RlbCwgIHByb3BlcnR5PXZhcl91bnNjYWxlZCwgbWFpbj1uYW1lcyhteV9kYXRhX21hdHJpeClbdmFyXSwgcGFsZXR0ZS5uYW1lPWNvb2xCbHVlSG90UmVkKQ0KYGBgDQoNCg0KIyBDb25jbHVzaW9uDQoNCi4gVGhlIGNsdXN0ZXIgZm9ybWVkIGJ5IEFsYWJhbWEsIEFsYXNrYSwgQXJpem9uYSwgQ2FsaWZvcm5pYSwgRGVsYT9hcmUsIEZsb3JpZGEsIElsbGlub2lzLCBMb3Vpc2lhbmEsIE1hcnlsYW5kLCBNaWNoaWdhbiwgTWlzc2lzc2lwcGksIE5ldmFkYSwgTmV3IE1leGljbywgTmV3IFlvcmssIE5vcnRoIENhcm9saW5hLCBTb3V0aCBDYXJvbGluYSBoYXMgdGhlIGhpZ2hlc3QgTXVyZGVyLCBBc3NhdWx0IGFuZCBSYXBlIGFyZXN0cyAocGVyIDEwMCwwMCkgYW5kLCBub3QgbGVhc3QsIHRoZSBsYXJnZXN0IHBvcHVsYXRpb24uDQoNCi4gVGhlIGNsdXN0ZXIgZm9ybWVkIGJ5IEE/a2Fuc2FzLCBDb2xvcmFkbywgR2VvcmdpYSwgTWFzc2FjaHVzZXR0cywgTWlzc291cmksIE5ldyBKZXJzZXksIE9rbGFob21hLCBPcmVnb24sIFJob2RlIElzbGFuZCwgVGVubmVzc2VlLCBUZXhhcywgVmlyZ2luaWEsIFdhc2hpbmd0b24sIFd5b21pbmcgaGFzIHRoZSBpbnRlcm1lZGlhdGUgTXVyZGVyLCBBc3NhdWx0IGFuZCBSYXBlIGFyZXN0cyAocGVyIDEwMCwwMCkgYW5kLCBub3QgbGVhc3QsIHRoZSBsYXJnZXN0IHBvcHVsYXRpb24uIA0KDQouIFRoPyBjbHVzdGVyIGZvcm1lZCBieSBDb25uZWN0aWN1dCwgSGF3YWlpLCBJZGFobywgSW5kaWFuYSwgSW93YSwgS2Fuc2FzLCBLZW50dWNreSwgTWFpbmUsIE1pbm5lc290YSwgTW9udGFuYSwgTmVicmFza2EsIE5ldyBIYW1wc2hpcmUsIE5vcnRoIERha290YSwgT2hpbywgUGVubnN5bHZhbmlhLCBTb3V0aCBEYWtvdGEsIFV0YWgsIFZlcm1vbnQsIFdlc3QgVmlyZ2luaWEsIFdpc2NvbnNpbiBoYXMgdGhlIGxvd2VzdCBNdXJkZXIsIEFzc2F1bHQgYW5kIFI/cGUgYXJlc3RzIChwZXIgMTAwLDAwKSBhbmQsIG5vdCBsZWFzdCwgdGhlIGxhcmdlc3QgcG9wdWxhdGlvbi4gIA0KDQpgYGB7cn0NCg0KYGBgDQojIFJlbWFya3MgZm9yIEFsb2dvcml0aG1zIA0KIyMgS21lYW5zICwgS2Vkb2lkLCBIaWVyYXJjaGljYWwNCkstbWVhbnMgY2x1c3RlcmluZyBpcyBhIHZlcnkgc2ltcGxlIGFuZCBmYXN0IGFsZ29yaXRobS4gRnVydGhlcm1vcmUsIGl0IGNhbiBlZmZpY2llbnRseSBkZWFsIHdpdGggdmVyeSBsYXJnZSBkYXRhIHNldHM/IEhvd2V2ZXIsIHRoZXJlIGFyZSBzb21lIHdlYWtuZXNzZXMgb2YgdGhlIGstbWVhbnMgYXBwcm9hY2guDQoNCk9uZSBwb3RlbnRpYWwgZGlzYWR2YW50YWdlIG9mIEstbWVhbnMgY2x1c3RlcmluZyBpcyB0aGF0IGl0IHJlcXVpcmVzIHVzIHRvIHByZS1zcGVjaWZ5IHRoZSBudW1iZXIgb2YgY2x1c3RlcnMuIEhpZXJhcmNoaWNhbCBjbHVzdGVyaW5nIGlzIGFuIGFsdGVybmF0aXZlIGFwcHJvYWNoIHdoaWNoIGRvZXMgbm90IHJlcXVpcmUgdGhhdCB3ZSBjP21taXQgdG8gYSBwYXJ0aWN1bGFyIGNob2ljZSBvZiBjbHVzdGVycy4gSGllcmFyY2hpY2FsIGNsdXN0ZXJpbmcgaGFzIGFuIGFkZGVkIGFkdmFudGFnZSBvdmVyIEstbWVhbnMgY2x1c3RlcmluZyBpbiB0aGF0IGl0IHJlc3VsdHMgaW4gYW4gYXR0cmFjdGl2ZSB0cmVlLWJhc2VkIHJlcHJlc2VudGF0aW9uIG9mIHRoZSBvYnNlcnZhdGlvbnMsIGNhbGxlZCBhIGRlbmRyb2dyYW0uICANCg0KQW4gYWRkaXRpb25hbCBkaXNhZHZhbnRhZ2Ugb2YgSy1tZWFucz9pcyB0aGF0IGl0J3Mgc2Vuc2l0aXZlIHRvIG91dGxpZXJzIGFuZCBkaWZmZXJlbnQgcmVzdWx0cyBjYW4gb2NjdXIgaWYgeW91IGNoYW5nZSB0aGUgb3JkZXJpbmcgb2YgeW91ciBkYXRhLiBUaGUgUGFydGl0aW9uaW5nIEFyb3VuZCBNZWRvaWRzIChQQU0pIGNsdXN0ZXJpbmcgYXBwcm9hY2ggaXMgbGVzcyBzZW5zaXRpdGl2ZSB0byBvdXRsaWVycyBhbmQgcHJvdmlkZXMgYSByb2J1c3QgYWx0ZXJuYXRpdmUgdG8gay1tZWFucyB0byBkZWFsIHdpdGggP2hlc2Ugc2l0dWF0aW9ucy4gIA0KDQpgYGB7cn0NCg0KYGBgDQoNCiANCg0KIyMjIyMjIFJlZmVyZW5jZXMNCg0KMS4gIGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9rb2hvbmVuL2tvaG9uZW4ucGRmIA0KMi4gICBodHRwczovL3d3dy5zdXBlcmRhdGFzY2llbmNlLmNvbS9ibG9ncy90aGUtdWx0aW1hdGUtZ3VpZGUtdG8tc2VsZi1vcmdhbml6aW5nLW1hcHMtc29tcw0KMy4gICBodHRwczovL2VuLndpa2lib29rcy5vcmcvd2lraS9EYXRhX01pbmk/Z19BbGdvcml0aG1zX0luX1IvQ2x1c3RlcmluZy9TZWxmLU9yZ2FuaXppbmdfTWFwc18oU09NKSNJbnRyb2R1Y3Rpb24gDQo0LiAgIGh0dHBzOi8vd3d3LndlYnBhZ2VzLnVpZGFoby5lZHUvfnN0ZXZlbC81MTcvRGF0YSUyME1pbmluZyUyMEFsZ29yaXRobXMlMjBJbiUyMFIucGRmIA0KNS4gICBodHRwOi8vZ2lya2UuYmlvaW5mb3JtYXRpY3MudWNyLmVkdS9HRU4yNDIvcGFnZXMvbXlkb2MvUmNsdXN0ZXJpbmcuaHRtbCANCjYuICAgaHR0cHM6Ly9jbGFyaz9hdGFsYWJzLmdpdGh1Yi5pby9zb21zL1NPTV9OQkEgDQo3LiAgIGh0dHBzOi8vd3d3LnItYmxvZ2dlcnMuY29tL3NlbGYtb3JnYW5pc2luZy1tYXBzLWZvci1jdXN0b21lci1zZWdtZW50YXRpb24tdXNpbmctci8gDQo4LiAgIGh0dHA6Ly93d3cuc3RoZGEuY29tL2VuZ2xpc2gvd2lraS8NCjkuICAgaHR0cHM6Ly91Yy1yLmdpdGh1Yi5pby9rbWVhbnNfY2x1c3RlcmluZyANCjEwLiAgaHR0cHM6Ly93d3cuc2hhbmVseW5uLmllL3NlbGYtb3JnYW5pc2luZy1tYT9zLWZvci1jdXN0b21lci1zZWdtZW50YXRpb24tdXNpbmctci8NCjExLiAgaHR0cHM6Ly93d3cuZGF0YW5vdmlhLmNvbS9lbi9jb3Vyc2VzL3BhcnRpdGlvbmFsLWNsdXN0ZXJpbmctaW4tci10aGUtZXNzZW50aWFscy8NCg0KDQoNCiANCg0KDQoNCg0KIA0K