Introduction I am a MSBA student , who is interested in data science and machine learning. To examine and implement my learning curve for knowledge that I gained in my Data mining course , I am using McDonald’s dataset that provides a nutrition information of every menu item on the US McDonald’s menu, including breakfast, beef burgers, chicken and fish sandwiches, fries, salads, soda, coffee and tea, milkshakes, and desserts. This report might not look very impressive but since every one of us starts from somewhere to reach the final perfect output. This is my starting point and I would like to hear your advice and suggestion.

Problem: The availability of fast foods, fatty food, and other unhealthy snacks in the living environment of children is assumed to contribute to be a very biggest reason of obesity among children. In particular, it is very clear that food retailers are targeting kid’s using different strategies to attract them. I am using McDonald’s menu and serving data to investigate (1) the clustering of food facts in the overall menu (2) the influence of particular food item in mac’s big menu on children. McDonald menu consist of breakfasts and fast food like burgers and sandwiches and I would like to compare it with respect to the content of food and quality. As a data enthusiast, I would also want to know all the good and bad aspect of McDonald’s most famous menu items - like the Mc Nuggets, Big Mac, Filet-O-Fish, and McChicken

Inspiration: Which item in the McDonald’s menu contain highest amount of total fat, saturated fat, cholesterol, sodium etc. that are alarming to child’s health? What are the healthy items in the menu that a child can eat? What is the least number of items could you order from the menu to meet one day’s nutritional requirements?

#Load libraries
library(cluster)
library(ggplot2)
library(dendextend)
library(dplyr)
library(datasets)
library(NbClust)
library(heatmaply)
library(kohonen)
library(party)
menudata <- read.csv("menu.csv",sep = ",",na.strings = "undefined",colClasses = c("character", "character","character", rep("numeric", 21)))
# data class
class(menudata)
[1] "data.frame"
head(menudata)
#summary of the dataset
summary(menudata)
   Category             Item           Serving.Size          Calories      Calories.from.Fat
 Length:260         Length:260         Length:260         Min.   :   0.0   Min.   :   0.0   
 Class :character   Class :character   Class :character   1st Qu.: 210.0   1st Qu.:  20.0   
 Mode  :character   Mode  :character   Mode  :character   Median : 340.0   Median : 100.0   
                                                          Mean   : 368.3   Mean   : 127.1   
                                                          3rd Qu.: 500.0   3rd Qu.: 200.0   
                                                          Max.   :1880.0   Max.   :1060.0   
   Total.Fat       Total.Fat....Daily.Value. Saturated.Fat    Saturated.Fat....Daily.Value.
 Min.   :  0.000   Min.   :  0.00            Min.   : 0.000   Min.   :  0.00               
 1st Qu.:  2.375   1st Qu.:  3.75            1st Qu.: 1.000   1st Qu.:  4.75               
 Median : 11.000   Median : 17.00            Median : 5.000   Median : 24.00               
 Mean   : 14.165   Mean   : 21.82            Mean   : 6.008   Mean   : 29.97               
 3rd Qu.: 22.250   3rd Qu.: 35.00            3rd Qu.:10.000   3rd Qu.: 48.00               
 Max.   :118.000   Max.   :182.00            Max.   :20.000   Max.   :102.00               
   Trans.Fat       Cholesterol     Cholesterol....Daily.Value.     Sodium      
 Min.   :0.0000   Min.   :  0.00   Min.   :  0.00              Min.   :   0.0  
 1st Qu.:0.0000   1st Qu.:  5.00   1st Qu.:  2.00              1st Qu.: 107.5  
 Median :0.0000   Median : 35.00   Median : 11.00              Median : 190.0  
 Mean   :0.2038   Mean   : 54.94   Mean   : 18.39              Mean   : 495.8  
 3rd Qu.:0.0000   3rd Qu.: 65.00   3rd Qu.: 21.25              3rd Qu.: 865.0  
 Max.   :2.5000   Max.   :575.00   Max.   :192.00              Max.   :3600.0  
 Sodium....Daily.Value. Carbohydrates    Carbohydrates....Daily.Value. Dietary.Fiber  
 Min.   :  0.00         Min.   :  0.00   Min.   : 0.00                 Min.   :0.000  
 1st Qu.:  4.75         1st Qu.: 30.00   1st Qu.:10.00                 1st Qu.:0.000  
 Median :  8.00         Median : 44.00   Median :15.00                 Median :1.000  
 Mean   : 20.68         Mean   : 47.35   Mean   :15.78                 Mean   :1.631  
 3rd Qu.: 36.25         3rd Qu.: 60.00   3rd Qu.:20.00                 3rd Qu.:3.000  
 Max.   :150.00         Max.   :141.00   Max.   :47.00                 Max.   :7.000  
 Dietary.Fiber....Daily.Value.     Sugars          Protein      Vitamin.A....Daily.Value.
 Min.   : 0.000                Min.   :  0.00   Min.   : 0.00   Min.   :  0.00           
 1st Qu.: 0.000                1st Qu.:  5.75   1st Qu.: 4.00   1st Qu.:  2.00           
 Median : 5.000                Median : 17.50   Median :12.00   Median :  8.00           
 Mean   : 6.531                Mean   : 29.42   Mean   :13.34   Mean   : 13.43           
 3rd Qu.:10.000                3rd Qu.: 48.00   3rd Qu.:19.00   3rd Qu.: 15.00           
 Max.   :28.000                Max.   :128.00   Max.   :87.00   Max.   :170.00           
 Vitamin.C....Daily.Value. Calcium....Daily.Value. Iron....Daily.Value.
 Min.   :  0.000           Min.   : 0.00           Min.   : 0.000      
 1st Qu.:  0.000           1st Qu.: 6.00           1st Qu.: 0.000      
 Median :  0.000           Median :20.00           Median : 4.000      
 Mean   :  8.535           Mean   :20.97           Mean   : 7.735      
 3rd Qu.:  4.000           3rd Qu.:30.00           3rd Qu.:15.000      
 Max.   :240.000           Max.   :70.00           Max.   :40.000      
str(menudata)
'data.frame':   260 obs. of  24 variables:
 $ Category                     : chr  "Breakfast" "Breakfast" "Breakfast" "Breakfast" ...
 $ Item                         : chr  "Egg McMuffin" "Egg White Delight" "Sausage McMuffin" "Sausage McMuffin with Egg" ...
 $ Serving.Size                 : chr  "4.8 oz (136 g)" "4.8 oz (135 g)" "3.9 oz (111 g)" "5.7 oz (161 g)" ...
 $ Calories                     : num  300 250 370 450 400 430 460 520 410 470 ...
 $ Calories.from.Fat            : num  120 70 200 250 210 210 230 270 180 220 ...
 $ Total.Fat                    : num  13 8 23 28 23 23 26 30 20 25 ...
 $ Total.Fat....Daily.Value.    : num  20 12 35 43 35 36 40 47 32 38 ...
 $ Saturated.Fat                : num  5 3 8 10 8 9 13 14 11 12 ...
 $ Saturated.Fat....Daily.Value.: num  25 15 42 52 42 46 65 68 56 59 ...
 $ Trans.Fat                    : num  0 0 0 0 0 1 0 0 0 0 ...
 $ Cholesterol                  : num  260 25 45 285 50 300 250 250 35 35 ...
 $ Cholesterol....Daily.Value.  : num  87 8 15 95 16 100 83 83 11 11 ...
 $ Sodium                       : num  750 770 780 860 880 960 1300 1410 1300 1420 ...
 $ Sodium....Daily.Value.       : num  31 32 33 36 37 40 54 59 54 59 ...
 $ Carbohydrates                : num  31 30 29 30 30 31 38 43 36 42 ...
 $ Carbohydrates....Daily.Value.: num  10 10 10 10 10 10 13 14 12 14 ...
 $ Dietary.Fiber                : num  4 4 4 4 4 4 2 3 2 3 ...
 $ Dietary.Fiber....Daily.Value.: num  17 17 17 17 17 18 7 12 7 12 ...
 $ Sugars                       : num  3 3 2 2 2 3 3 4 3 4 ...
 $ Protein                      : num  17 18 14 21 21 26 19 19 20 20 ...
 $ Vitamin.A....Daily.Value.    : num  10 6 8 15 6 15 10 15 2 6 ...
 $ Vitamin.C....Daily.Value.    : num  0 0 0 0 0 2 8 8 8 8 ...
 $ Calcium....Daily.Value.      : num  25 25 25 30 25 30 15 20 15 15 ...
 $ Iron....Daily.Value.         : num  15 8 10 15 10 20 15 20 10 15 ...

Interesting facts about data: Action: With the help of aggregate function in R , I performed a search for categories with high and low value of contents across the whole menu.

#study the nutitional fact about each catagory
Food_content = aggregate(cbind(Total.Fat....Daily.Value., Saturated.Fat....Daily.Value., Cholesterol....Daily.Value., Sodium....Daily.Value., Carbohydrates....Daily.Value., Dietary.Fiber....Daily.Value.,Vitamin.A....Daily.Value., Vitamin.C....Daily.Value.,Calcium....Daily.Value., Iron....Daily.Value.) ~ Category,
                               data = menudata, FUN = mean
)

Food_content
(Food_content$Total.Fat....Daily.Value.)

Preliminary Analysis:

By performing the aggregation of the content values across the menu category wise, I get to know that:

A.) In overall menu of MacDonald, breakfast is the category that has highest percentage of fat, cholesterol, sodium and saturated fat. In addition to that their breakfast items are very low in percentage of goodness contents that are vitamins A, Vitamin C, dietary fibers etc. B.) On the second-place Beef and pork items in the menu can satisfy 38% of the daily value of fat and 42 % of daily value of sodium. In other words , this means a single item of “Beef & Pork” can provide daily recommended fat consumption by this amount. C.) Salads in the menu is the category that can satisfy 146 % of your daily recommended Vitamin A consumption. D.) Smoothies & shakes provide 35 % of your daily overall calcium recommended consumption .

Data Preparation and cleaning: For running clustering algorithms. To start the analysis, we first need to clean and scale the data. Step 1: Delete the first column i: e Category because this is a character variable and just a meal information which is not required for my study.

#Let's clean the data to perform clustering
# Delete first column
menudata$Category <-NULL
View(menudata)

Step 2: With the remaining 23 columns, we still have 2 more character variables Items and serving size. Setting these two variables into numeric to check if there are any NA’s in the dataset.

# First convert column 2 to numeric
menudata[, 2] <-lapply(menudata[,2], as.numeric)
str(menudata)

Step 3: Selected Menu Data Subset that has less than 25 NA’s. Step 4: Removed all duplicate items from the dataset so that it does not affect the clustering algorithms. Step 5: After finding the values that had NA, I set all NA’s to zero .

# Select Product Data Subset that has less than 25 NA's
menuDataSubset <- menudata[rowSums(is.na(menudata))<25,]
summary(menuDataSubset)
head(menuDataSubset)

# Remove duplicates
CleanMenuData <- menuDataSubset %>% distinct(Item , .keep_all = TRUE)

head(CleanMenuData)
hist(CleanMenuData$Calories)
qqnorm(CleanMenuData$Calories)
qqline(CleanMenuData$Calories)
hist(CleanMenuData$Cholesterol)

# Set NA's to zero
CleanMenuData[is.na(CleanMenuData)] <- 0
head(CleanMenuData)

Step 6: Set column name " item " as row names so that we can delete that column . Step 7 : Deleted the column “Item” after retaining it as Row names. Step 8 : Scaling the data: finally scaled the data by setting all NA’s if any to 0.

# Set item as row names
rownames(CleanMenuData) <- CleanMenuData[,1]

# Delete column "Item".
CleanMenuData$Item <- NULL
summary(CleanMenuData)
View(CleanMenuData)

# Scale the data
ScaledMenuData <- scale(CleanMenuData, center = FALSE)
ScaledMenuData[is.na(ScaledMenuData)] <- 0

After cleaning the data, I found that the data is skewed on the right-hand side having highest frequency of items with low calorie amount.

hist(ScaledMenuData)

Clustering Methods and outputs K-Means : Number of clusters: NBclust The are many traditional approaches like k-means, hierarchical clustering and model-based clustering but here firstly I need to specify the number of clusters in advance. The objective is to find number of clusters. There are ways to select the optimal number of clusters using gap and prediction strength statistics and with NbClust package in R. I tried to find out best number of cluster that can be formed using “NBclust” and method = " Kmeans" with “Kl” index to see what items in the menu list comes together and form several distinct clusters

# Clustering with different algorithms
#Best number of cluster for the data
bestK <- NbClust(ScaledMenuData, min.nc=2, max.nc=5,index = "kl", method="kmeans")
bestK$Best.nc
Number_clusters     Value_Index 
         4.0000         13.6295 
#bestK$Best.partition
head(CleanMenuData)

Performing K means with K= 15 and K= 5 on data with respect to calories from fat and overall calories. Elbow plot: Plotting a elbow plot Within groups sum of squares with K=5 . This represents the variance within the clusters. It decreases as k increases, but one can notice a bend (or “elbow”) right at k=4. This bend indicates that additional clusters beyond the fourth have little value.

#Elbow in the resulting plot suggests a suitable number of clusters for the kmeans.
wgss <- (nrow(ScaledMenuData)-1)*sum(apply(ScaledMenuData,2,var))
for (i in 2:5) wgss[i] <- sum(kmeans(ScaledMenuData,
                                      centers=i)$withinss)
#Plot elbow model showing 3 numbers of  clusters
plot(1:5, wgss, type="b", xlab="Number of Clusters",
     ylab="Within groups sum of squares")

Clustering Algorithms: K-means Since my findings are related to obesity in children, my focus is mainly on variable Calories from Fat, Cholesterol and Sodium in McDonald’s menu items K-means clustering with 5 clusters of sizes 126, 49, 74, 1, 10 K-means clustering with 4 clusters of sizes 99, 13, 1, 147

#Kmeans clustering by setting 5 clusters
set.seed(20)
menuKCluster1 <- kmeans(CleanMenuData[, 3:4], 5, nstart = 20)
menuKCluster1
menuKCluster1$size
menuKCluster1$centers
menuKCluster1$cluster <- as.factor(menuKCluster1$cluster)
ggplot(CleanMenuData, aes(Calories, Calories.from.Fat, color = menuKCluster1$cluster), environment=environment()) + geom_point()

Best value for cluster is 4 that is validated with elbow graph as well as above scatterplots. The one dot on the right hand top corner in both the cluster plot is an item " 40 piece mac Nuggets in the menu that has highest amount of cholesterol , fat and sodium.

Findings from the plot:

-Highest number of Calories from fat is in Chicken Mc Nuggets (40 piece) -Items with calories more than 500 calories are scattered and very less in numbers. -The calories and calorie from fat is positively correlated. -Clusters are very distinct as the calories and calories from fat increases another group has been formed.

# Kmeans with best number of cluster i;e 4 clusters
set.seed(20)
menuKCluster2 <- kmeans(CleanMenuData[ , 3:4], 4, nstart = 20)
menuKCluster2
menuKCluster2$size
menuKCluster2$centers
menuKCluster2$cluster <- as.factor(menuKCluster2$cluster)
ggplot(CleanMenuData, aes(Calories, Calories.from.Fat, color = menuKCluster2$cluster), environment=environment()) + geom_point()

Furthermore, mining the data with respect to other alarming contents like Cholesterol and sodium also , I will explore k clusters , from 1 to 9, on this clustering. First cluster the data 9 times, each time different k and see how the distribution of clusters takes place each time.

library(broom)
#Explore k, from 1 to 9, on this clustering. First cluster the data 9 times, each time different k
kclusts <- data.frame(k=1:9) %>% group_by(k) %>% do(kclust=kmeans(ScaledMenuData, .$k))

# Tidy the clusterings using augment and glance 
assignments <- kclusts %>% group_by(k) %>% do(augment(.$kclust[[1]], ScaledMenuData))
clusterings <- kclusts %>% group_by(k) %>% do(glance(.$kclust[[1]]))

Tidy the clustering using augment and glance and then plotting the original points, with each point colored according to the original cluster. Augment adds the point classifications to the original dataset and the glance function extracts a single-row summary: Let’s start by generating some random 2d data with three clusters. I want to see calorie on the X axis and Calorie from Fat, Cholesterol and Sodium respectively on the Y axis.

# plot the original points, with each point colored according to the original cluster
p1 <- ggplot(assignments, aes(Calories, Calories.from.Fat, color = menuKCluster$cluster)) + geom_point(aes(color=.cluster)) + facet_wrap(~ k)
p1

p2 <- ggplot(assignments, aes(Calories, Cholesterol, color = menuKCluster$cluster)) + geom_point(aes(color=.cluster)) + facet_wrap(~ k)
p2

p3 <- ggplot(assignments, aes(Calories, Sodium, color = menuKCluster$cluster)) + geom_point(aes(color=.cluster)) + facet_wrap(~ k)
p3

ggplot(clusterings, aes(k, tot.withinss)) + geom_line()

The MacDonald’s data from glance fits a different but equally important purpose: It lets you view trends of some summary statistics across values of k. of particular interest is the total within sum of squares, saved in the tot.withinss column. Findings from the plots:

Sodium and calories from fat are positively correlated and clustered in a dense cluster whereas sodium is cholesterol is constant thought out the calorie scale.

Data always clustered around four very distinct group with rising amount of cholesterol , fat and Sodium as the calorie rises.

Clustering with Self Organising Maps

# Clustering with Self Organising Maps
menusom <- som(data=ScaledMenuData, grid = somgrid(3, 4, "hexagonal"))
plot(menusom, type="count", labels = rownames(ScaledMenuData),cex=.5)

plot(menusom, type="code", labels = rownames(ScaledMenuData),cex=.3)

We can see the neurons of the network marked as yellow colors. The representation reveals that these are a separate cluster in the upper right corner of this representation. The clusters are separated by a different color. This result was achieved by unsupervised learning, without any forced input.

#Agglomerative clustering using hclust with ward.D2
set.seed(20)
Myhclust <- function(ScaledMenuData) hclust(ScaledMenuData, method="ward.D2")
  1. Analysis and recommendation The data is highly skewed on the right-hand side having highest frequency of items with low calorie amount. As evinced by the plots one item (data point) that is on the top most right corner is the 40-piece Chicken McNuggets that is the greatest contributor to fat, cholesterol and Sodium intake. The biggest devil is McDonald’s big breakfast menu which has the that can be viewed in all the plots where the clusters are formed based on low to high value of cholesterol, sodium and fat. The plots for Sodium with respect to calories seem be clear that a MacDonald food items contributing the high amount of sodium in its big mac menu range. As an evidence above a single item that is the 40-piece Chicken McNuggets are the greatest contributor to Sodium intake. The plot shows that there are quite a handful of MacDonald’s food items which contain a dangerous number of calories from saturated Fat, and one single item can contain an amount close to the one’s recommended daily value of sodium, cholesterol and fat.

Recommendations:

1.) Breakfast items are required to be the healthiest in the whole menu. Although, McDonald tries to update and introduce healthy item like for example grilled food items and vegetables, there is still lot of imbalance in the content. McDonald should reduce cholesterol and sodium in breakfast items. 2.) Child menu item like 40-piece chicken mc nuggets is found to be the unhealthiest food with respect to unsaturated fat and cholesterol and this could be one of the reason for overweight children who feed MacDonald’s on a regular basis. 3.) Although people concern about how McDonalds influence badly on their health, it is also a chance for McDonalds. This company can develop new products, specifically fresh burger or healthy dessert. 4.) McDonald should introduce healthy breakfast separately for kids that includes lots of fresh juices, non-deep fried meat and vegetables with high protein and lots of goodness factor.

LS0tDQp0aXRsZTogIkNsdXN0ZXJpbmcgTWNEb25hbGQncyBNZW51IGZvb2QgZmFjdCINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KLS0tDQoNCkludHJvZHVjdGlvbiANCkkgYW0gYSBNU0JBIHN0dWRlbnQgLCB3aG8gaXMgaW50ZXJlc3RlZCBpbiBkYXRhIHNjaWVuY2UgYW5kIG1hY2hpbmUgbGVhcm5pbmcuIFRvIGV4YW1pbmUgYW5kIGltcGxlbWVudCBteSBsZWFybmluZyBjdXJ2ZSBmb3Iga25vd2xlZGdlIHRoYXQgSSBnYWluZWQgaW4gbXkgRGF0YSBtaW5pbmcgY291cnNlICwgSSBhbSB1c2luZyBNY0RvbmFsZCdzIGRhdGFzZXQgdGhhdCBwcm92aWRlcyBhIG51dHJpdGlvbiBpbmZvcm1hdGlvbiBvZiBldmVyeSBtZW51IGl0ZW0gb24gdGhlIFVTIE1jRG9uYWxkJ3MgbWVudSwgaW5jbHVkaW5nIGJyZWFrZmFzdCwgYmVlZiBidXJnZXJzLCBjaGlja2VuIGFuZCBmaXNoIHNhbmR3aWNoZXMsIGZyaWVzLCBzYWxhZHMsIHNvZGEsIGNvZmZlZSBhbmQgdGVhLCBtaWxrc2hha2VzLCBhbmQgZGVzc2VydHMuIFRoaXMgcmVwb3J0IG1pZ2h0IG5vdCBsb29rIHZlcnkgaW1wcmVzc2l2ZSBidXQgc2luY2UgZXZlcnkgb25lIG9mIHVzIHN0YXJ0cyBmcm9tIHNvbWV3aGVyZSB0byByZWFjaCB0aGUgZmluYWwgcGVyZmVjdCBvdXRwdXQuIFRoaXMgaXMgbXkgc3RhcnRpbmcgcG9pbnQgYW5kIEkgd291bGQgbGlrZSB0byBoZWFyIHlvdXIgYWR2aWNlIGFuZCBzdWdnZXN0aW9uLiANCg0KUHJvYmxlbToNClRoZSBhdmFpbGFiaWxpdHkgb2YgZmFzdCBmb29kcywgZmF0dHkgZm9vZCwgYW5kIG90aGVyIHVuaGVhbHRoeSBzbmFja3MgaW4gdGhlIGxpdmluZyBlbnZpcm9ubWVudCBvZiBjaGlsZHJlbiBpcyBhc3N1bWVkIHRvIGNvbnRyaWJ1dGUgdG8gYmUgYSB2ZXJ5IGJpZ2dlc3QgcmVhc29uIG9mIG9iZXNpdHkgYW1vbmcgY2hpbGRyZW4uIEluIHBhcnRpY3VsYXIsIGl0IGlzIHZlcnkgY2xlYXIgdGhhdCBmb29kIHJldGFpbGVycyBhcmUgdGFyZ2V0aW5nIGtpZCdzIHVzaW5nIGRpZmZlcmVudCBzdHJhdGVnaWVzIHRvIGF0dHJhY3QgdGhlbS4gSSBhbSB1c2luZyBNY0RvbmFsZCdzIG1lbnUgYW5kIHNlcnZpbmcgZGF0YSB0byBpbnZlc3RpZ2F0ZSAoMSkgdGhlIGNsdXN0ZXJpbmcgb2YgZm9vZCBmYWN0cyBpbiB0aGUgb3ZlcmFsbCBtZW51ICgyKSB0aGUgaW5mbHVlbmNlIG9mIHBhcnRpY3VsYXIgZm9vZCBpdGVtIGluIG1hYydzIGJpZyBtZW51IG9uIGNoaWxkcmVuLiBNY0RvbmFsZCBtZW51IGNvbnNpc3Qgb2YgYnJlYWtmYXN0cyBhbmQgZmFzdCBmb29kIGxpa2UgYnVyZ2VycyBhbmQgc2FuZHdpY2hlcyBhbmQgSSB3b3VsZCBsaWtlIHRvIGNvbXBhcmUgaXQgd2l0aCByZXNwZWN0IHRvIHRoZSBjb250ZW50IG9mIGZvb2QgYW5kIHF1YWxpdHkuIEFzIGEgZGF0YSBlbnRodXNpYXN0LCBJIHdvdWxkIGFsc28gd2FudCB0byBrbm93IGFsbCB0aGUgZ29vZCBhbmQgYmFkIGFzcGVjdCBvZiBNY0RvbmFsZCdzIG1vc3QgZmFtb3VzIG1lbnUgaXRlbXMgLSBsaWtlIHRoZSBNYyBOdWdnZXRzLCBCaWcgTWFjLCBGaWxldC1PLUZpc2gsIGFuZCBNY0NoaWNrZW4NCg0KSW5zcGlyYXRpb246IA0KV2hpY2ggaXRlbSBpbiB0aGUgTWNEb25hbGQncyBtZW51IGNvbnRhaW4gaGlnaGVzdCBhbW91bnQgb2YgdG90YWwgZmF0LCBzYXR1cmF0ZWQgZmF0LCBjaG9sZXN0ZXJvbCwgc29kaXVtIGV0Yy4gdGhhdCBhcmUgYWxhcm1pbmcgdG8gY2hpbGQncyBoZWFsdGg/IFdoYXQgYXJlIHRoZSBoZWFsdGh5IGl0ZW1zIGluIHRoZSBtZW51IHRoYXQgYSBjaGlsZCBjYW4gZWF0PyBXaGF0IGlzIHRoZSBsZWFzdCBudW1iZXIgb2YgaXRlbXMgY291bGQgeW91IG9yZGVyIGZyb20gdGhlIG1lbnUgdG8gbWVldCBvbmUgZGF5J3MgbnV0cml0aW9uYWwgcmVxdWlyZW1lbnRzPw0KDQoNCmBgYHtyfQ0KI0xvYWQgbGlicmFyaWVzDQpsaWJyYXJ5KGNsdXN0ZXIpDQpsaWJyYXJ5KGdncGxvdDIpDQpsaWJyYXJ5KGRlbmRleHRlbmQpDQpsaWJyYXJ5KGRwbHlyKQ0KbGlicmFyeShkYXRhc2V0cykNCmxpYnJhcnkoTmJDbHVzdCkNCmxpYnJhcnkoaGVhdG1hcGx5KQ0KbGlicmFyeShrb2hvbmVuKQ0KbGlicmFyeShwYXJ0eSkNCg0KbWVudWRhdGEgPC0gcmVhZC5jc3YoIm1lbnUuY3N2IixzZXAgPSAiLCIsbmEuc3RyaW5ncyA9ICJ1bmRlZmluZWQiLGNvbENsYXNzZXMgPSBjKCJjaGFyYWN0ZXIiLCAiY2hhcmFjdGVyIiwiY2hhcmFjdGVyIiwgcmVwKCJudW1lcmljIiwgMjEpKSkNCg0KIyBkYXRhIGNsYXNzDQpjbGFzcyhtZW51ZGF0YSkNCg0KYGBgDQoNCmBgYHtyfQ0KaGVhZChtZW51ZGF0YSkNCg0KI3N1bW1hcnkgb2YgdGhlIGRhdGFzZXQNCnN1bW1hcnkobWVudWRhdGEpDQpzdHIobWVudWRhdGEpDQoNCmBgYA0KSW50ZXJlc3RpbmcgZmFjdHMgYWJvdXQgZGF0YToNCkFjdGlvbjogDQpXaXRoIHRoZSBoZWxwIG9mIGFnZ3JlZ2F0ZSBmdW5jdGlvbiBpbiBSICwgSSBwZXJmb3JtZWQgYSBzZWFyY2ggZm9yIGNhdGVnb3JpZXMgd2l0aCBoaWdoIGFuZCBsb3cgdmFsdWUgb2YgY29udGVudHMgYWNyb3NzIHRoZSB3aG9sZSBtZW51LiANCg0KYGBge3J9DQojc3R1ZHkgdGhlIG51dGl0aW9uYWwgZmFjdCBhYm91dCBlYWNoIGNhdGFnb3J5DQpGb29kX2NvbnRlbnQgPSBhZ2dyZWdhdGUoY2JpbmQoVG90YWwuRmF0Li4uLkRhaWx5LlZhbHVlLiwgU2F0dXJhdGVkLkZhdC4uLi5EYWlseS5WYWx1ZS4sIENob2xlc3Rlcm9sLi4uLkRhaWx5LlZhbHVlLiwgU29kaXVtLi4uLkRhaWx5LlZhbHVlLiwgQ2FyYm9oeWRyYXRlcy4uLi5EYWlseS5WYWx1ZS4sIERpZXRhcnkuRmliZXIuLi4uRGFpbHkuVmFsdWUuLFZpdGFtaW4uQS4uLi5EYWlseS5WYWx1ZS4sIFZpdGFtaW4uQy4uLi5EYWlseS5WYWx1ZS4sQ2FsY2l1bS4uLi5EYWlseS5WYWx1ZS4sIElyb24uLi4uRGFpbHkuVmFsdWUuKSB+IENhdGVnb3J5LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGEgPSBtZW51ZGF0YSwgRlVOID0gbWVhbg0KKQ0KDQpGb29kX2NvbnRlbnQNCihGb29kX2NvbnRlbnQkVG90YWwuRmF0Li4uLkRhaWx5LlZhbHVlLikNCg0KYGBgDQoNClByZWxpbWluYXJ5IEFuYWx5c2lzOiANCg0KQnkgcGVyZm9ybWluZyB0aGUgYWdncmVnYXRpb24gb2YgdGhlIGNvbnRlbnQgdmFsdWVzIGFjcm9zcyB0aGUgbWVudSBjYXRlZ29yeSB3aXNlLCBJIGdldCB0byBrbm93IHRoYXQ6DQoNCkEuKQlJbiBvdmVyYWxsIG1lbnUgb2YgTWFjRG9uYWxkLCBicmVha2Zhc3QgaXMgdGhlIGNhdGVnb3J5IHRoYXQgaGFzIGhpZ2hlc3QgcGVyY2VudGFnZSBvZiBmYXQsIGNob2xlc3Rlcm9sLCBzb2RpdW0gYW5kIHNhdHVyYXRlZCBmYXQuIEluIGFkZGl0aW9uIHRvIHRoYXQgdGhlaXIgYnJlYWtmYXN0IGl0ZW1zIGFyZSB2ZXJ5IGxvdyBpbiBwZXJjZW50YWdlIG9mIGdvb2RuZXNzIGNvbnRlbnRzIHRoYXQgYXJlIHZpdGFtaW5zIEEsIFZpdGFtaW4gQywgZGlldGFyeSBmaWJlcnMgZXRjLg0KQi4pCU9uIHRoZSBzZWNvbmQtcGxhY2UgQmVlZiBhbmQgcG9yayBpdGVtcyBpbiB0aGUgbWVudSBjYW4gc2F0aXNmeSAzOCUgb2YgdGhlIGRhaWx5IHZhbHVlIG9mIGZhdCBhbmQgNDIgJSBvZiBkYWlseSB2YWx1ZSBvZiBzb2RpdW0uIEluIG90aGVyIHdvcmRzICwgdGhpcyBtZWFucyBhIHNpbmdsZSBpdGVtIG9mICJCZWVmICYgUG9yayIgY2FuICBwcm92aWRlIGRhaWx5IHJlY29tbWVuZGVkIGZhdCBjb25zdW1wdGlvbiBieSB0aGlzIGFtb3VudC4NCkMuKQlTYWxhZHMgaW4gdGhlIG1lbnUgaXMgdGhlIGNhdGVnb3J5IHRoYXQgY2FuIHNhdGlzZnkgMTQ2ICUgb2YgeW91ciBkYWlseSByZWNvbW1lbmRlZCBWaXRhbWluIEEgY29uc3VtcHRpb24uDQpELikJU21vb3RoaWVzICYgc2hha2VzIHByb3ZpZGUgIDM1ICUgb2YgeW91ciBkYWlseSBvdmVyYWxsIGNhbGNpdW0gcmVjb21tZW5kZWQgY29uc3VtcHRpb24gLg0KDQoNCkRhdGEgUHJlcGFyYXRpb24gYW5kIGNsZWFuaW5nOiBGb3IgcnVubmluZyBjbHVzdGVyaW5nIGFsZ29yaXRobXMuDQpUbyBzdGFydCB0aGUgYW5hbHlzaXMsIHdlIGZpcnN0IG5lZWQgdG8gY2xlYW4gYW5kIHNjYWxlIHRoZSBkYXRhLg0KU3RlcCAxOiBEZWxldGUgdGhlIGZpcnN0IGNvbHVtbiBpOiBlIENhdGVnb3J5IGJlY2F1c2UgdGhpcyBpcyBhIGNoYXJhY3RlciB2YXJpYWJsZSBhbmQganVzdCBhIG1lYWwgaW5mb3JtYXRpb24gd2hpY2ggaXMgbm90IHJlcXVpcmVkIGZvciBteSBzdHVkeS4NCg0KYGBge3J9DQojTGV0J3MgY2xlYW4gdGhlIGRhdGEgdG8gcGVyZm9ybSBjbHVzdGVyaW5nDQojIERlbGV0ZSBmaXJzdCBjb2x1bW4NCm1lbnVkYXRhJENhdGVnb3J5IDwtTlVMTA0KVmlldyhtZW51ZGF0YSkNCmBgYA0KDQpTdGVwIDI6IFdpdGggdGhlIHJlbWFpbmluZyAyMyBjb2x1bW5zLCB3ZSBzdGlsbCBoYXZlIDIgbW9yZSBjaGFyYWN0ZXIgdmFyaWFibGVzIEl0ZW1zIGFuZCBzZXJ2aW5nIHNpemUuIFNldHRpbmcgdGhlc2UgdHdvIHZhcmlhYmxlcyBpbnRvIG51bWVyaWMgdG8gY2hlY2sgaWYgdGhlcmUgYXJlIGFueSBOQSdzIGluIHRoZSBkYXRhc2V0Lg0KDQoNCmBgYHtyfQ0KIyBGaXJzdCBjb252ZXJ0IGNvbHVtbiAyIHRvIG51bWVyaWMNCm1lbnVkYXRhWywgMl0gPC1sYXBwbHkobWVudWRhdGFbLDJdLCBhcy5udW1lcmljKQ0Kc3RyKG1lbnVkYXRhKQ0KDQpgYGANClN0ZXAgMzogIFNlbGVjdGVkIE1lbnUgRGF0YSBTdWJzZXQgdGhhdCBoYXMgbGVzcyB0aGFuIDI1IE5BJ3MuDQpTdGVwIDQ6IFJlbW92ZWQgYWxsIGR1cGxpY2F0ZSBpdGVtcyBmcm9tIHRoZSBkYXRhc2V0IHNvIHRoYXQgaXQgZG9lcyBub3QgYWZmZWN0IHRoZSBjbHVzdGVyaW5nIGFsZ29yaXRobXMuDQpTdGVwIDU6IEFmdGVyIGZpbmRpbmcgdGhlIHZhbHVlcyB0aGF0IGhhZCBOQSwgSSBzZXQgYWxsIE5BJ3MgdG8gemVybyAuDQoNCmBgYHtyfQ0KIyBTZWxlY3QgUHJvZHVjdCBEYXRhIFN1YnNldCB0aGF0IGhhcyBsZXNzIHRoYW4gMjUgTkEncw0KbWVudURhdGFTdWJzZXQgPC0gbWVudWRhdGFbcm93U3Vtcyhpcy5uYShtZW51ZGF0YSkpPDI1LF0NCnN1bW1hcnkobWVudURhdGFTdWJzZXQpDQpoZWFkKG1lbnVEYXRhU3Vic2V0KQ0KDQojIFJlbW92ZSBkdXBsaWNhdGVzDQpDbGVhbk1lbnVEYXRhIDwtIG1lbnVEYXRhU3Vic2V0ICU+JSBkaXN0aW5jdChJdGVtICwgLmtlZXBfYWxsID0gVFJVRSkNCg0KaGVhZChDbGVhbk1lbnVEYXRhKQ0KaGlzdChDbGVhbk1lbnVEYXRhJENhbG9yaWVzKQ0KcXFub3JtKENsZWFuTWVudURhdGEkQ2Fsb3JpZXMpDQpxcWxpbmUoQ2xlYW5NZW51RGF0YSRDYWxvcmllcykNCmhpc3QoQ2xlYW5NZW51RGF0YSRDaG9sZXN0ZXJvbCkNCg0KIyBTZXQgTkEncyB0byB6ZXJvDQpDbGVhbk1lbnVEYXRhW2lzLm5hKENsZWFuTWVudURhdGEpXSA8LSAwDQpoZWFkKENsZWFuTWVudURhdGEpDQpgYGANCg0KU3RlcCA2OiBTZXQgY29sdW1uIG5hbWUgIiBpdGVtICIgIGFzIHJvdyBuYW1lcyBzbyB0aGF0IHdlIGNhbiBkZWxldGUgdGhhdCBjb2x1bW4gLg0KU3RlcCA3IDogRGVsZXRlZCB0aGUgY29sdW1uICJJdGVtIiBhZnRlciByZXRhaW5pbmcgaXQgYXMgUm93IG5hbWVzLg0KU3RlcCA4IDogU2NhbGluZyB0aGUgZGF0YTogZmluYWxseSBzY2FsZWQgdGhlIGRhdGEgYnkgc2V0dGluZyBhbGwgTkEncyBpZiBhbnkgdG8gMC4NCg0KYGBge3J9DQojIFNldCBpdGVtIGFzIHJvdyBuYW1lcw0Kcm93bmFtZXMoQ2xlYW5NZW51RGF0YSkgPC0gQ2xlYW5NZW51RGF0YVssMV0NCg0KIyBEZWxldGUgY29sdW1uICJJdGVtIi4NCkNsZWFuTWVudURhdGEkSXRlbSA8LSBOVUxMDQpzdW1tYXJ5KENsZWFuTWVudURhdGEpDQpWaWV3KENsZWFuTWVudURhdGEpDQoNCiMgU2NhbGUgdGhlIGRhdGENClNjYWxlZE1lbnVEYXRhIDwtIHNjYWxlKENsZWFuTWVudURhdGEsIGNlbnRlciA9IEZBTFNFKQ0KU2NhbGVkTWVudURhdGFbaXMubmEoU2NhbGVkTWVudURhdGEpXSA8LSAwDQpgYGANCg0KQWZ0ZXIgY2xlYW5pbmcgdGhlIGRhdGEsIEkgZm91bmQgdGhhdCB0aGUgZGF0YSBpcyBza2V3ZWQgb24gdGhlIHJpZ2h0LWhhbmQgc2lkZSBoYXZpbmcgaGlnaGVzdCBmcmVxdWVuY3kgb2YgaXRlbXMgd2l0aCBsb3cgY2Fsb3JpZSBhbW91bnQuDQoNCmBgYHtyfQ0KaGlzdChTY2FsZWRNZW51RGF0YSkNCmBgYA0KDQpDbHVzdGVyaW5nIE1ldGhvZHMgYW5kIG91dHB1dHMgDQpLLU1lYW5zIDoNCk51bWJlciBvZiBjbHVzdGVyczogTkJjbHVzdA0KVGhlIGFyZSBtYW55IHRyYWRpdGlvbmFsIGFwcHJvYWNoZXMgbGlrZSBrLW1lYW5zLCBoaWVyYXJjaGljYWwgY2x1c3RlcmluZyBhbmQgbW9kZWwtYmFzZWQgY2x1c3RlcmluZyBidXQgaGVyZSBmaXJzdGx5IEkgbmVlZCB0byBzcGVjaWZ5IHRoZSBudW1iZXIgb2YgY2x1c3RlcnMgaW4gYWR2YW5jZS4gVGhlIG9iamVjdGl2ZSBpcyB0byBmaW5kIG51bWJlciBvZiBjbHVzdGVycy4gVGhlcmUgYXJlIHdheXMgdG8gc2VsZWN0IHRoZSBvcHRpbWFsIG51bWJlciBvZiBjbHVzdGVycyB1c2luZyBnYXAgYW5kIHByZWRpY3Rpb24gc3RyZW5ndGggc3RhdGlzdGljcyBhbmQgd2l0aCBOYkNsdXN0IHBhY2thZ2UgaW4gUi4NCkkgdHJpZWQgdG8gZmluZCBvdXQgYmVzdCBudW1iZXIgb2YgY2x1c3RlciB0aGF0IGNhbiBiZSBmb3JtZWQgdXNpbmcgIk5CY2x1c3QgIiBhbmQgbWV0aG9kID0gICIgS21lYW5zIiB3aXRoICAiS2wiIGluZGV4IHRvIHNlZSB3aGF0IGl0ZW1zIGluIHRoZSBtZW51IGxpc3QgY29tZXMgdG9nZXRoZXIgYW5kIGZvcm0gc2V2ZXJhbCBkaXN0aW5jdCBjbHVzdGVycw0KDQpgYGB7cn0NCiMgQ2x1c3RlcmluZyB3aXRoIGRpZmZlcmVudCBhbGdvcml0aG1zDQoNCiNCZXN0IG51bWJlciBvZiBjbHVzdGVyIGZvciB0aGUgZGF0YQ0KYmVzdEsgPC0gTmJDbHVzdChTY2FsZWRNZW51RGF0YSwgbWluLm5jPTIsIG1heC5uYz01LGluZGV4ID0gImtsIiwgbWV0aG9kPSJrbWVhbnMiKQ0KYmVzdEskQmVzdC5uYw0KI2Jlc3RLJEJlc3QucGFydGl0aW9uDQpoZWFkKENsZWFuTWVudURhdGEpDQpgYGANCg0KUGVyZm9ybWluZyBLIG1lYW5zIHdpdGggSz0gMTUgYW5kIEs9IDUgb24gZGF0YSB3aXRoIHJlc3BlY3QgdG8gY2Fsb3JpZXMgZnJvbSBmYXQgYW5kIG92ZXJhbGwgY2Fsb3JpZXMuDQpFbGJvdyBwbG90OiBQbG90dGluZyBhIGVsYm93IHBsb3QgV2l0aGluIGdyb3VwcyBzdW0gb2Ygc3F1YXJlcyB3aXRoIEs9NSAuIFRoaXMgcmVwcmVzZW50cyB0aGUgdmFyaWFuY2Ugd2l0aGluIHRoZSBjbHVzdGVycy4gSXQgZGVjcmVhc2VzIGFzIGsgaW5jcmVhc2VzLCBidXQgb25lIGNhbiBub3RpY2UgYSBiZW5kIChvciAiZWxib3ciKSByaWdodCBhdCBrPTQuIFRoaXMgYmVuZCBpbmRpY2F0ZXMgdGhhdCBhZGRpdGlvbmFsIGNsdXN0ZXJzIGJleW9uZCB0aGUgZm91cnRoIGhhdmUgbGl0dGxlIHZhbHVlLg0KDQpgYGB7cn0NCiNFbGJvdyBpbiB0aGUgcmVzdWx0aW5nIHBsb3Qgc3VnZ2VzdHMgYSBzdWl0YWJsZSBudW1iZXIgb2YgY2x1c3RlcnMgZm9yIHRoZSBrbWVhbnMuDQoNCndnc3MgPC0gKG5yb3coU2NhbGVkTWVudURhdGEpLTEpKnN1bShhcHBseShTY2FsZWRNZW51RGF0YSwyLHZhcikpDQpmb3IgKGkgaW4gMjo1KSB3Z3NzW2ldIDwtIHN1bShrbWVhbnMoU2NhbGVkTWVudURhdGEsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNlbnRlcnM9aSkkd2l0aGluc3MpDQojUGxvdCBlbGJvdyBtb2RlbCBzaG93aW5nIDMgbnVtYmVycyBvZiAgY2x1c3RlcnMNCnBsb3QoMTo1LCB3Z3NzLCB0eXBlPSJiIiwgeGxhYj0iTnVtYmVyIG9mIENsdXN0ZXJzIiwNCiAgICAgeWxhYj0iV2l0aGluIGdyb3VwcyBzdW0gb2Ygc3F1YXJlcyIpDQpgYGANCg0KDQpDbHVzdGVyaW5nIEFsZ29yaXRobXM6IEstbWVhbnMNClNpbmNlIG15IGZpbmRpbmdzIGFyZSByZWxhdGVkIHRvIG9iZXNpdHkgaW4gY2hpbGRyZW4sIG15IGZvY3VzIGlzIG1haW5seSBvbiB2YXJpYWJsZSBDYWxvcmllcyBmcm9tIEZhdCwgQ2hvbGVzdGVyb2wgYW5kIFNvZGl1bSBpbiBNY0RvbmFsZCdzIG1lbnUgaXRlbXMNCkstbWVhbnMgY2x1c3RlcmluZyB3aXRoIDUgY2x1c3RlcnMgb2Ygc2l6ZXMgMTI2LCA0OSwgNzQsIDEsIDEwDQpLLW1lYW5zIGNsdXN0ZXJpbmcgd2l0aCA0IGNsdXN0ZXJzIG9mIHNpemVzIDk5LCAxMywgMSwgMTQ3DQoNCmBgYHtyfQ0KI0ttZWFucyBjbHVzdGVyaW5nIGJ5IHNldHRpbmcgNSBjbHVzdGVycw0Kc2V0LnNlZWQoMjApDQptZW51S0NsdXN0ZXIxIDwtIGttZWFucyhDbGVhbk1lbnVEYXRhWywgMzo0XSwgNSwgbnN0YXJ0ID0gMjApDQptZW51S0NsdXN0ZXIxDQptZW51S0NsdXN0ZXIxJHNpemUNCm1lbnVLQ2x1c3RlcjEkY2VudGVycw0KbWVudUtDbHVzdGVyMSRjbHVzdGVyIDwtIGFzLmZhY3RvcihtZW51S0NsdXN0ZXIxJGNsdXN0ZXIpDQpnZ3Bsb3QoQ2xlYW5NZW51RGF0YSwgYWVzKENhbG9yaWVzLCBDYWxvcmllcy5mcm9tLkZhdCwgY29sb3IgPSBtZW51S0NsdXN0ZXIxJGNsdXN0ZXIpLCBlbnZpcm9ubWVudD1lbnZpcm9ubWVudCgpKSArIGdlb21fcG9pbnQoKQ0KDQpgYGANCg0KICBCZXN0IHZhbHVlIGZvciBjbHVzdGVyIGlzIDQgdGhhdCBpcyB2YWxpZGF0ZWQgd2l0aCBlbGJvdyBncmFwaCBhcyB3ZWxsIGFzIGFib3ZlIHNjYXR0ZXJwbG90cy4gVGhlIG9uZSBkb3Qgb24gdGhlIHJpZ2h0IGhhbmQgdG9wIGNvcm5lciBpbiBib3RoIHRoZSBjbHVzdGVyIHBsb3QgaXMgYW4gaXRlbSAiIDQwIHBpZWNlIG1hYyBOdWdnZXRzIGluIHRoZSBtZW51IHRoYXQgaGFzIGhpZ2hlc3QgYW1vdW50IG9mIGNob2xlc3Rlcm9sICwgZmF0IGFuZCBzb2RpdW0uDQoNCkZpbmRpbmdzIGZyb20gdGhlIHBsb3Q6IA0KDQotSGlnaGVzdCBudW1iZXIgb2YgQ2Fsb3JpZXMgZnJvbSBmYXQgaXMgaW4gQ2hpY2tlbiBNYyBOdWdnZXRzICg0MCBwaWVjZSkNCi1JdGVtcyB3aXRoIGNhbG9yaWVzIG1vcmUgdGhhbiA1MDAgY2Fsb3JpZXMgYXJlIHNjYXR0ZXJlZCBhbmQgdmVyeSBsZXNzIGluIG51bWJlcnMuIA0KLVRoZSBjYWxvcmllcyBhbmQgY2Fsb3JpZSBmcm9tIGZhdCBpcyBwb3NpdGl2ZWx5IGNvcnJlbGF0ZWQuDQotQ2x1c3RlcnMgYXJlIHZlcnkgZGlzdGluY3QgYXMgdGhlIGNhbG9yaWVzIGFuZCBjYWxvcmllcyBmcm9tIGZhdCBpbmNyZWFzZXMgYW5vdGhlciBncm91cCBoYXMgYmVlbiBmb3JtZWQuDQoNCmBgYHtyfQ0KIyBLbWVhbnMgd2l0aCBiZXN0IG51bWJlciBvZiBjbHVzdGVyIGk7ZSA0IGNsdXN0ZXJzDQpzZXQuc2VlZCgyMCkNCm1lbnVLQ2x1c3RlcjIgPC0ga21lYW5zKENsZWFuTWVudURhdGFbICwgMzo0XSwgNCwgbnN0YXJ0ID0gMjApDQptZW51S0NsdXN0ZXIyDQptZW51S0NsdXN0ZXIyJHNpemUNCm1lbnVLQ2x1c3RlcjIkY2VudGVycw0KbWVudUtDbHVzdGVyMiRjbHVzdGVyIDwtIGFzLmZhY3RvcihtZW51S0NsdXN0ZXIyJGNsdXN0ZXIpDQpnZ3Bsb3QoQ2xlYW5NZW51RGF0YSwgYWVzKENhbG9yaWVzLCBDYWxvcmllcy5mcm9tLkZhdCwgY29sb3IgPSBtZW51S0NsdXN0ZXIyJGNsdXN0ZXIpLCBlbnZpcm9ubWVudD1lbnZpcm9ubWVudCgpKSArIGdlb21fcG9pbnQoKQ0KDQpgYGANCkZ1cnRoZXJtb3JlLCBtaW5pbmcgdGhlIGRhdGEgd2l0aCByZXNwZWN0IHRvIG90aGVyIGFsYXJtaW5nIGNvbnRlbnRzIGxpa2UgQ2hvbGVzdGVyb2wgYW5kIHNvZGl1bSBhbHNvICwgSSB3aWxsIGV4cGxvcmUgayBjbHVzdGVycyAsIGZyb20gMSB0byA5LCBvbiB0aGlzIGNsdXN0ZXJpbmcuIEZpcnN0IGNsdXN0ZXIgdGhlIGRhdGEgOSB0aW1lcywgZWFjaCB0aW1lIGRpZmZlcmVudCBrIGFuZCBzZWUgaG93IHRoZSBkaXN0cmlidXRpb24gb2YgY2x1c3RlcnMgdGFrZXMgcGxhY2UgZWFjaCB0aW1lLg0KDQpgYGB7cn0NCmxpYnJhcnkoYnJvb20pDQojRXhwbG9yZSBrLCBmcm9tIDEgdG8gOSwgb24gdGhpcyBjbHVzdGVyaW5nLiBGaXJzdCBjbHVzdGVyIHRoZSBkYXRhIDkgdGltZXMsIGVhY2ggdGltZSBkaWZmZXJlbnQgaw0Ka2NsdXN0cyA8LSBkYXRhLmZyYW1lKGs9MTo5KSAlPiUgZ3JvdXBfYnkoaykgJT4lIGRvKGtjbHVzdD1rbWVhbnMoU2NhbGVkTWVudURhdGEsIC4kaykpDQoNCiMgVGlkeSB0aGUgY2x1c3RlcmluZ3MgdXNpbmcgYXVnbWVudCBhbmQgZ2xhbmNlIA0KYXNzaWdubWVudHMgPC0ga2NsdXN0cyAlPiUgZ3JvdXBfYnkoaykgJT4lIGRvKGF1Z21lbnQoLiRrY2x1c3RbWzFdXSwgU2NhbGVkTWVudURhdGEpKQ0KY2x1c3RlcmluZ3MgPC0ga2NsdXN0cyAlPiUgZ3JvdXBfYnkoaykgJT4lIGRvKGdsYW5jZSguJGtjbHVzdFtbMV1dKSkNCmBgYA0KDQoNClRpZHkgdGhlIGNsdXN0ZXJpbmcgdXNpbmcgYXVnbWVudCBhbmQgZ2xhbmNlIGFuZCB0aGVuIHBsb3R0aW5nIHRoZSBvcmlnaW5hbCBwb2ludHMsIHdpdGggZWFjaCBwb2ludCBjb2xvcmVkIGFjY29yZGluZyB0byB0aGUgb3JpZ2luYWwgY2x1c3Rlci4gQXVnbWVudCBhZGRzIHRoZSBwb2ludCBjbGFzc2lmaWNhdGlvbnMgdG8gdGhlIG9yaWdpbmFsIGRhdGFzZXQgYW5kIHRoZSBnbGFuY2UgZnVuY3Rpb24gZXh0cmFjdHMgYSBzaW5nbGUtcm93IHN1bW1hcnk6IExldCdzIHN0YXJ0IGJ5IGdlbmVyYXRpbmcgc29tZSByYW5kb20gMmQgZGF0YSB3aXRoIHRocmVlIGNsdXN0ZXJzLiBJIHdhbnQgdG8gc2VlIGNhbG9yaWUgb24gdGhlIFggYXhpcyBhbmQgQ2Fsb3JpZSBmcm9tIEZhdCwgQ2hvbGVzdGVyb2wgYW5kIFNvZGl1bSByZXNwZWN0aXZlbHkgb24gdGhlIFkgYXhpcy4NCg0KDQpgYGB7cn0NCiMgcGxvdCB0aGUgb3JpZ2luYWwgcG9pbnRzLCB3aXRoIGVhY2ggcG9pbnQgY29sb3JlZCBhY2NvcmRpbmcgdG8gdGhlIG9yaWdpbmFsIGNsdXN0ZXINCnAxIDwtIGdncGxvdChhc3NpZ25tZW50cywgYWVzKENhbG9yaWVzLCBDYWxvcmllcy5mcm9tLkZhdCwgY29sb3IgPSBtZW51S0NsdXN0ZXIkY2x1c3RlcikpICsgZ2VvbV9wb2ludChhZXMoY29sb3I9LmNsdXN0ZXIpKSArIGZhY2V0X3dyYXAofiBrKQ0KcDENCg0KYGBgDQoNCmBgYHtyfQ0KcDIgPC0gZ2dwbG90KGFzc2lnbm1lbnRzLCBhZXMoQ2Fsb3JpZXMsIENob2xlc3Rlcm9sLCBjb2xvciA9IG1lbnVLQ2x1c3RlciRjbHVzdGVyKSkgKyBnZW9tX3BvaW50KGFlcyhjb2xvcj0uY2x1c3RlcikpICsgZmFjZXRfd3JhcCh+IGspDQpwMg0KYGBgDQoNCg0KYGBge3J9DQpwMyA8LSBnZ3Bsb3QoYXNzaWdubWVudHMsIGFlcyhDYWxvcmllcywgU29kaXVtLCBjb2xvciA9IG1lbnVLQ2x1c3RlciRjbHVzdGVyKSkgKyBnZW9tX3BvaW50KGFlcyhjb2xvcj0uY2x1c3RlcikpICsgZmFjZXRfd3JhcCh+IGspDQpwMw0KYGBgDQoNCg0KYGBge3J9DQpnZ3Bsb3QoY2x1c3RlcmluZ3MsIGFlcyhrLCB0b3Qud2l0aGluc3MpKSArIGdlb21fbGluZSgpDQpgYGANCg0KVGhlIE1hY0RvbmFsZCdzIGRhdGEgZnJvbSBnbGFuY2UgZml0cyBhIGRpZmZlcmVudCBidXQgZXF1YWxseSBpbXBvcnRhbnQgcHVycG9zZTogSXQgbGV0cyB5b3UgdmlldyB0cmVuZHMgb2Ygc29tZSBzdW1tYXJ5IHN0YXRpc3RpY3MgYWNyb3NzIHZhbHVlcyBvZiBrLiBvZiBwYXJ0aWN1bGFyIGludGVyZXN0IGlzIHRoZSB0b3RhbCB3aXRoaW4gc3VtIG9mIHNxdWFyZXMsIHNhdmVkIGluIHRoZSB0b3Qud2l0aGluc3MgY29sdW1uLg0KRmluZGluZ3MgZnJvbSB0aGUgcGxvdHM6IA0KDQpTb2RpdW0gYW5kIGNhbG9yaWVzIGZyb20gZmF0IGFyZSBwb3NpdGl2ZWx5IGNvcnJlbGF0ZWQgYW5kIGNsdXN0ZXJlZCBpbiBhIGRlbnNlIGNsdXN0ZXIgd2hlcmVhcyBzb2RpdW0gaXMgY2hvbGVzdGVyb2wgaXMgY29uc3RhbnQgdGhvdWdodCBvdXQgdGhlIGNhbG9yaWUgc2NhbGUuDQoNCkRhdGEgYWx3YXlzIGNsdXN0ZXJlZCBhcm91bmQgZm91ciB2ZXJ5IGRpc3RpbmN0IGdyb3VwIHdpdGggcmlzaW5nIGFtb3VudCBvZiBjaG9sZXN0ZXJvbCAsIGZhdCBhbmQgU29kaXVtIGFzIHRoZSBjYWxvcmllIHJpc2VzLg0KDQoNCkNsdXN0ZXJpbmcgd2l0aCBTZWxmIE9yZ2FuaXNpbmcgTWFwcw0KYGBge3J9DQojIENsdXN0ZXJpbmcgd2l0aCBTZWxmIE9yZ2FuaXNpbmcgTWFwcw0KbWVudXNvbSA8LSBzb20oZGF0YT1TY2FsZWRNZW51RGF0YSwgZ3JpZCA9IHNvbWdyaWQoMywgNCwgImhleGFnb25hbCIpKQ0KcGxvdChtZW51c29tLCB0eXBlPSJjb3VudCIsIGxhYmVscyA9IHJvd25hbWVzKFNjYWxlZE1lbnVEYXRhKSxjZXg9LjUpDQpwbG90KG1lbnVzb20sIHR5cGU9ImNvZGUiLCBsYWJlbHMgPSByb3duYW1lcyhTY2FsZWRNZW51RGF0YSksY2V4PS4zKQ0KYGBgDQoNCldlIGNhbiBzZWUgdGhlIG5ldXJvbnMgb2YgdGhlIG5ldHdvcmsgbWFya2VkIGFzIHllbGxvdyBjb2xvcnMuIFRoZSByZXByZXNlbnRhdGlvbiByZXZlYWxzIHRoYXQgdGhlc2UgYXJlIGEgc2VwYXJhdGUgY2x1c3RlciBpbiB0aGUgdXBwZXIgcmlnaHQgY29ybmVyIG9mIHRoaXMgcmVwcmVzZW50YXRpb24uIFRoZSBjbHVzdGVycyBhcmUgc2VwYXJhdGVkIGJ5IGEgZGlmZmVyZW50IGNvbG9yLiBUaGlzIHJlc3VsdCB3YXMgYWNoaWV2ZWQgYnkgdW5zdXBlcnZpc2VkIGxlYXJuaW5nLCB3aXRob3V0IGFueSBmb3JjZWQgaW5wdXQuIA0KDQpgYGB7cn0NCiNBZ2dsb21lcmF0aXZlIGNsdXN0ZXJpbmcgdXNpbmcgaGNsdXN0IHdpdGggd2FyZC5EMg0KDQpzZXQuc2VlZCgyMCkNCk15aGNsdXN0IDwtIGZ1bmN0aW9uKFNjYWxlZE1lbnVEYXRhKSBoY2x1c3QoU2NhbGVkTWVudURhdGEsIG1ldGhvZD0id2FyZC5EMiIpDQpgYGANCg0KDQo1LiBBbmFseXNpcyBhbmQgcmVjb21tZW5kYXRpb24NClRoZSBkYXRhIGlzIGhpZ2hseSBza2V3ZWQgb24gdGhlIHJpZ2h0LWhhbmQgc2lkZSBoYXZpbmcgaGlnaGVzdCBmcmVxdWVuY3kgb2YgaXRlbXMgd2l0aCBsb3cgY2Fsb3JpZSBhbW91bnQuIA0KQXMgZXZpbmNlZCBieSB0aGUgcGxvdHMgb25lIGl0ZW0gKGRhdGEgcG9pbnQpIHRoYXQgaXMgb24gdGhlIHRvcCBtb3N0IHJpZ2h0IGNvcm5lciBpcyB0aGUgNDAtcGllY2UgQ2hpY2tlbiBNY051Z2dldHMgdGhhdCBpcyB0aGUgZ3JlYXRlc3QgY29udHJpYnV0b3IgdG8gZmF0LCBjaG9sZXN0ZXJvbCBhbmQgU29kaXVtIGludGFrZS4NClRoZSBiaWdnZXN0IGRldmlsIGlzIE1jRG9uYWxkJ3MgYmlnIGJyZWFrZmFzdCBtZW51IHdoaWNoIGhhcyB0aGUgdGhhdCBjYW4gYmUgdmlld2VkIGluIGFsbCB0aGUgcGxvdHMgd2hlcmUgdGhlIGNsdXN0ZXJzIGFyZSBmb3JtZWQgYmFzZWQgb24gbG93IHRvIGhpZ2ggdmFsdWUgb2YgY2hvbGVzdGVyb2wsIHNvZGl1bSBhbmQgZmF0Lg0KVGhlIHBsb3RzIGZvciBTb2RpdW0gd2l0aCByZXNwZWN0IHRvIGNhbG9yaWVzIHNlZW0gYmUgY2xlYXIgdGhhdCBhIE1hY0RvbmFsZCBmb29kIGl0ZW1zIGNvbnRyaWJ1dGluZyB0aGUgaGlnaCBhbW91bnQgb2Ygc29kaXVtIGluIGl0cyBiaWcgbWFjIG1lbnUgcmFuZ2UuIEFzIGFuIGV2aWRlbmNlIGFib3ZlIGEgc2luZ2xlIGl0ZW0gdGhhdCBpcyB0aGUgNDAtcGllY2UgQ2hpY2tlbiBNY051Z2dldHMgYXJlIHRoZSBncmVhdGVzdCBjb250cmlidXRvciB0byBTb2RpdW0gaW50YWtlLg0KVGhlIHBsb3Qgc2hvd3MgdGhhdCB0aGVyZSBhcmUgcXVpdGUgYSBoYW5kZnVsIG9mIE1hY0RvbmFsZCdzIGZvb2QgaXRlbXMgd2hpY2ggY29udGFpbiBhIGRhbmdlcm91cyBudW1iZXIgb2YgY2Fsb3JpZXMgZnJvbSBzYXR1cmF0ZWQgRmF0LCBhbmQgb25lIHNpbmdsZSBpdGVtIGNhbiBjb250YWluIGFuIGFtb3VudCBjbG9zZSB0byB0aGUgb25lJ3MgcmVjb21tZW5kZWQgZGFpbHkgdmFsdWUgb2Ygc29kaXVtLCBjaG9sZXN0ZXJvbCBhbmQgZmF0Lg0KDQpSZWNvbW1lbmRhdGlvbnM6IA0KDQoxLikJQnJlYWtmYXN0IGl0ZW1zIGFyZSByZXF1aXJlZCB0byBiZSB0aGUgaGVhbHRoaWVzdCBpbiB0aGUgd2hvbGUgbWVudS4gQWx0aG91Z2gsIE1jRG9uYWxkIHRyaWVzIHRvIHVwZGF0ZSBhbmQgaW50cm9kdWNlIGhlYWx0aHkgaXRlbSBsaWtlIGZvciBleGFtcGxlIGdyaWxsZWQgZm9vZCBpdGVtcyBhbmQgdmVnZXRhYmxlcywgdGhlcmUgaXMgc3RpbGwgbG90IG9mIGltYmFsYW5jZSBpbiB0aGUgY29udGVudC4gTWNEb25hbGQgc2hvdWxkIHJlZHVjZSBjaG9sZXN0ZXJvbCBhbmQgc29kaXVtIGluIGJyZWFrZmFzdCBpdGVtcy4NCjIuKQlDaGlsZCBtZW51IGl0ZW0gbGlrZSA0MC1waWVjZSBjaGlja2VuIG1jIG51Z2dldHMgaXMgZm91bmQgdG8gYmUgdGhlIHVuaGVhbHRoaWVzdCBmb29kIHdpdGggcmVzcGVjdCB0byB1bnNhdHVyYXRlZCBmYXQgYW5kIGNob2xlc3Rlcm9sIGFuZCB0aGlzIGNvdWxkIGJlIG9uZSBvZiB0aGUgcmVhc29uIGZvciBvdmVyd2VpZ2h0IGNoaWxkcmVuIHdobyBmZWVkIE1hY0RvbmFsZCdzIG9uIGEgcmVndWxhciBiYXNpcy4NCjMuKQlBbHRob3VnaCBwZW9wbGUgY29uY2VybiBhYm91dCBob3cgTWNEb25hbGRzIGluZmx1ZW5jZSBiYWRseSBvbiB0aGVpciBoZWFsdGgsIGl0IGlzIGFsc28gYSBjaGFuY2UgZm9yIE1jRG9uYWxkcy4gVGhpcyBjb21wYW55IGNhbiBkZXZlbG9wIG5ldyBwcm9kdWN0cywgc3BlY2lmaWNhbGx5IGZyZXNoIGJ1cmdlciBvciBoZWFsdGh5IGRlc3NlcnQuDQo0LikJTWNEb25hbGQgc2hvdWxkIGludHJvZHVjZSBoZWFsdGh5IGJyZWFrZmFzdCBzZXBhcmF0ZWx5IGZvciBraWRzIHRoYXQgaW5jbHVkZXMgbG90cyBvZiBmcmVzaCBqdWljZXMsIG5vbi1kZWVwIGZyaWVkIG1lYXQgYW5kIHZlZ2V0YWJsZXMgd2l0aCBoaWdoIHByb3RlaW4gYW5kIGxvdHMgb2YgZ29vZG5lc3MgZmFjdG9yLg0KDQoNCg==