Required packages

library(magrittr)
library(tidyr)
library(dplyr)
library(lubridate)
library(Hmisc)
library(outliers)
library(forecast)

Executive Summary

In order to give recommendations whether Rossmann should expand stores or not in next years, the first thing I need to do is searching data to understand what they have done during the period of its operation and its competitive level compared to its rivals from 2013 to 2015. I find out 2 datasets related to the problem statement from kaggle website and make the data ready for the further statistical analysis. More detail, after importing and merging 2 datasets to gain knowlege about this brand, I filter important variables and deeply observe to make my analysis easier. Then, I check data structure to understand its attributes in the combined dataset and understand the meaning of each value for the variables. Next, I check whether my data is messy or not; fortunately; my data already tidy up because each variable has its own column, each observation has its own row and each type of observational unit has its own table. Furthermore, to know the level of customer preference which supports for problem statement, I create a new variable, especifically average purchase per customer, to know the level of spending on Rossmann products of every customer in each area and each period of time. In addition, I clean data for obvious errors, identify and handle multivariate outliers by replacing them with 5th and 95th quantile, and deal with missing values by replacing them with “None” and “median value” depending on natures of variables. Lastly, my dataset has right-skewed distribution, so I transform it to change its distribution into normal distribution which will make easier in statistical analysis. To make my description about data preprocessing simplier, steps below will clearly demonstrate what I describe the process above.

Data

The purpose of this report is to determine whether Rossmann should keep openning stores in next years. Therefore, deeply understanding its operation is the most important thing I need to focus. In order to have a better overview about Rossmann, the combined dataset below is merged by two relative detasets and divided into 18 variables representing historical sales for 1,115 Rossmann stores in last 3 years. During operation, some stores were temporarily closed for refurbishment, so I remove dates without Rossmann’s revenue for more accurate analysis.

# Set working directory
setwd("~/Desktop/Week3")
The working directory was changed to /Users/diepdo/Desktop/Week3 inside a notebook chunk. The working directory will be reset when the chunk is finished running. Use the knitr root.dir option in the setup chunk to change the working directory for notebook chunks.
# Import two datasets
train <- read.csv("train.csv", stringsAsFactors = FALSE)
head(train)
store <- read.csv("store.csv", stringsAsFactors = FALSE)
head(store)
# Combine two datasets 
combination <- left_join(store,train,key="Store")
Joining, by = "Store"
# Filter data
combination <- combination %>% filter(Sales > 0)
head(combination)

Understand

# Summarise data
summary(combination)
     Store         StoreType          Assortment        CompetitionDistance CompetitionOpenSinceMonth
 Min.   :   1.0   Length:844338      Length:844338      Min.   :   20       Min.   : 1.00            
 1st Qu.: 280.0   Class :character   Class :character   1st Qu.:  710       1st Qu.: 4.00            
 Median : 558.0   Mode  :character   Mode  :character   Median : 2320       Median : 8.00            
 Mean   : 558.4                                         Mean   : 5458       Mean   : 7.22            
 3rd Qu.: 837.0                                         3rd Qu.: 6890       3rd Qu.:10.00            
 Max.   :1115.0                                         Max.   :75860       Max.   :12.00            
                                                        NA's   :2186        NA's   :268600           
 CompetitionOpenSinceYear     Promo2       Promo2SinceWeek  Promo2SinceYear  PromoInterval     
 Min.   :1900             Min.   :0.0000   Min.   : 1.0     Min.   :2009     Length:844338     
 1st Qu.:2006             1st Qu.:0.0000   1st Qu.:13.0     1st Qu.:2011     Class :character  
 Median :2010             Median :0.0000   Median :22.0     Median :2012     Mode  :character  
 Mean   :2009             Mean   :0.4987   Mean   :23.3     Mean   :2012                       
 3rd Qu.:2013             3rd Qu.:1.0000   3rd Qu.:37.0     3rd Qu.:2013                       
 Max.   :2015             Max.   :1.0000   Max.   :50.0     Max.   :2015                       
 NA's   :268600                            NA's   :423292   NA's   :423292                     
   DayOfWeek        Date               Sales         Customers           Open       Promo       
 Min.   :1.00   Length:844338      Min.   :   46   Min.   :   8.0   Min.   :1   Min.   :0.0000  
 1st Qu.:2.00   Class :character   1st Qu.: 4859   1st Qu.: 519.0   1st Qu.:1   1st Qu.:0.0000  
 Median :3.00   Mode  :character   Median : 6369   Median : 676.0   Median :1   Median :0.0000  
 Mean   :3.52                      Mean   : 6956   Mean   : 762.8   Mean   :1   Mean   :0.4464  
 3rd Qu.:5.00                      3rd Qu.: 8360   3rd Qu.: 893.0   3rd Qu.:1   3rd Qu.:1.0000  
 Max.   :7.00                      Max.   :41551   Max.   :7388.0   Max.   :1   Max.   :1.0000  
                                                                                                
 StateHoliday       SchoolHoliday   
 Length:844338      Min.   :0.0000  
 Class :character   1st Qu.:0.0000  
 Mode  :character   Median :0.0000  
                    Mean   :0.1936  
                    3rd Qu.:0.0000  
                    Max.   :1.0000  
                                    
# Check data structure
str(combination)
'data.frame':   844338 obs. of  18 variables:
 $ Store                    : int  1 1 1 1 1 1 1 1 1 1 ...
 $ StoreType                : chr  "c" "c" "c" "c" ...
 $ Assortment               : chr  "a" "a" "a" "a" ...
 $ CompetitionDistance      : int  1270 1270 1270 1270 1270 1270 1270 1270 1270 1270 ...
 $ CompetitionOpenSinceMonth: int  9 9 9 9 9 9 9 9 9 9 ...
 $ CompetitionOpenSinceYear : int  2008 2008 2008 2008 2008 2008 2008 2008 2008 2008 ...
 $ Promo2                   : int  0 0 0 0 0 0 0 0 0 0 ...
 $ Promo2SinceWeek          : int  NA NA NA NA NA NA NA NA NA NA ...
 $ Promo2SinceYear          : int  NA NA NA NA NA NA NA NA NA NA ...
 $ PromoInterval            : chr  "" "" "" "" ...
 $ DayOfWeek                : int  5 4 3 2 1 6 5 4 3 2 ...
 $ Date                     : chr  "2015-07-31" "2015-07-30" "2015-07-29" "2015-07-28" ...
 $ Sales                    : int  5263 5020 4782 5011 6102 4364 3706 3769 3464 3558 ...
 $ Customers                : int  555 546 523 560 612 500 459 503 463 469 ...
 $ Open                     : int  1 1 1 1 1 1 1 1 1 1 ...
 $ Promo                    : int  1 1 1 1 1 0 0 0 0 0 ...
 $ StateHoliday             : chr  "0" "0" "0" "0" ...
 $ SchoolHoliday            : int  1 1 1 1 1 0 0 0 0 0 ...

In the combined dataset, some variables are not presented as its natures (e.g: Assortment is not character variable)

# Adjust data structure
combination$Assortment <- factor(combination$Assortment,levels=c("a","b","c"), 
                                 labels = c("basic","extra","extended"))
combination$Promo2 <- factor(combination$Promo2,levels=c(0,1), 
                                 labels = c("not continuing promotion","continuing promotion"))
combination$DayOfWeek <- factor(combination$DayOfWeek,levels=c(1,2,3,4,5,6,7), 
                                 labels = c("Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"))
combination$Open <- factor(combination$Open,levels=c(0,1), 
                             labels = c("closed","open"))
combination$Promo <- factor(combination$Promo,levels=c(0,1),
                            labels=c("no promotion","promotion"))
combination$StateHoliday <- factor(combination$StateHoliday,levels=c("a","b","c",0),
                                   labels=c("public-holiday","Easter-holiday","Christmas","None"))
combination$SchoolHoliday <- factor(combination$SchoolHoliday, levels=c(0,1),
                                    labels=c("closed","open"))
combination$Date <- as.Date(combination$Date)
str(combination)
'data.frame':   844338 obs. of  18 variables:
 $ Store                    : int  1 1 1 1 1 1 1 1 1 1 ...
 $ StoreType                : chr  "c" "c" "c" "c" ...
 $ Assortment               : Factor w/ 3 levels "basic","extra",..: 1 1 1 1 1 1 1 1 1 1 ...
 $ CompetitionDistance      : int  1270 1270 1270 1270 1270 1270 1270 1270 1270 1270 ...
 $ CompetitionOpenSinceMonth: int  9 9 9 9 9 9 9 9 9 9 ...
 $ CompetitionOpenSinceYear : int  2008 2008 2008 2008 2008 2008 2008 2008 2008 2008 ...
 $ Promo2                   : Factor w/ 2 levels "not continuing promotion",..: 1 1 1 1 1 1 1 1 1 1 ...
 $ Promo2SinceWeek          : int  NA NA NA NA NA NA NA NA NA NA ...
 $ Promo2SinceYear          : int  NA NA NA NA NA NA NA NA NA NA ...
 $ PromoInterval            : chr  "" "" "" "" ...
 $ DayOfWeek                : Factor w/ 7 levels "Sunday","Monday",..: 5 4 3 2 1 6 5 4 3 2 ...
 $ Date                     : Date, format: "2015-07-31" "2015-07-30" "2015-07-29" ...
 $ Sales                    : int  5263 5020 4782 5011 6102 4364 3706 3769 3464 3558 ...
 $ Customers                : int  555 546 523 560 612 500 459 503 463 469 ...
 $ Open                     : Factor w/ 2 levels "closed","open": 2 2 2 2 2 2 2 2 2 2 ...
 $ Promo                    : Factor w/ 2 levels "no promotion",..: 2 2 2 2 2 1 1 1 1 1 ...
 $ StateHoliday             : Factor w/ 4 levels "public-holiday",..: 4 4 4 4 4 4 4 4 4 4 ...
 $ SchoolHoliday            : Factor w/ 2 levels "closed","open": 2 2 2 2 2 1 1 1 1 1 ...

For example, with assortment variable, I convert from character into factor and label it as basic, extra and extended stores respectively in relation to its level a,b,c.

Tidy & Manipulate Data I

This combined dataset is long format as it is arranged in such a way that a single subject’s information is stored in multiple rows. Each variable has its own column. Each observation has its own row. Each value has its own cell. Therefore, this combined dataset represents as a tidy data.

Tidy & Manipulate Data II

In order to positively forecast Rossmann’s sales in next two years with the purpose of extending stores, I create new column named Average Purchase Per Customer from 2 existing variables (Sales and Customers) by mutate () function to know the average amount of money each customer tend to spend on Rossmann products in each day in each store.

# Create new variable 
combination <- mutate(combination, AveragePurchasePerCustomer=Sales/Customers) 
head(combination$AveragePurchasePerCustomer)
[1] 9.482883 9.194139 9.143403 8.948214 9.970588 8.728000

Scan I

In the combined dataset, some variables, including competition distance, competition open since month, competition open since year, promo2 since week, promo2 since year, have some missing values.

# Check missing values
sapply(combination, function(x) sum(is.na(x)))
                     Store                  StoreType                 Assortment        CompetitionDistance 
                         0                          0                          0                       2186 
 CompetitionOpenSinceMonth   CompetitionOpenSinceYear                     Promo2            Promo2SinceWeek 
                    268600                     268600                          0                     423292 
           Promo2SinceYear              PromoInterval                  DayOfWeek                       Date 
                    423292                          0                          0                          0 
                     Sales                  Customers                       Open                      Promo 
                         0                          0                          0                          0 
              StateHoliday              SchoolHoliday AveragePurchasePerCustomer 
                         0                          0                          0 

Depending on natures of variables, I handle these missing values with different ways. Specifically,

# Deal with missing values
combination$Promo2SinceWeek <- ifelse(is.na(combination$Promo2SinceWeek), 
                             'None', combination$Promo2SinceWeek)
combination$Promo2SinceYear <- ifelse(is.na(combination$Promo2SinceYear), 
                             'None', combination$Promo2SinceYear)
combination$CompetitionDistance[is.na(combination$CompetitionDistance)] <- median(combination$CompetitionDistance, na.rm=TRUE)
combination$CompetitionOpenSinceMonth[is.na(combination$CompetitionOpenSinceMonth)] <- median(combination$CompetitionOpenSinceMonth, na.rm=TRUE)
combination$CompetitionOpenSinceYear[is.na(combination$CompetitionOpenSinceYear)] <- median(combination$CompetitionOpenSinceYear, na.rm=TRUE)
sapply(combination, function(x) sum(is.na(x)))
                     Store                  StoreType                 Assortment        CompetitionDistance 
                         0                          0                          0                          0 
 CompetitionOpenSinceMonth   CompetitionOpenSinceYear                     Promo2            Promo2SinceWeek 
                         0                          0                          0                          0 
           Promo2SinceYear              PromoInterval                  DayOfWeek                       Date 
                         0                          0                          0                          0 
                     Sales                  Customers                       Open                      Promo 
                         0                          0                          0                          0 
              StateHoliday              SchoolHoliday AveragePurchasePerCustomer 
                         0                          0                          0 

Scan II

# Detect outliers Sales & AveragePurchasePerCustomer
par(mfrow=c(2,1))
boxplot(combination$Sales, main="The number of Sales between stores")
boxplot(combination$AveragePurchasePerCustomer, main="The Average Spending of Each Customer between Stores")

# See descriptive statistics of Sales
summary(combination$Sales)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
     46    4859    6369    6956    8360   41551 
IQR(combination$Sales, na.rm=TRUE)
[1] 3501
thelowerfence_Sales <- quantile(combination$Sales,0.25, na.rm=TRUE)-1.5*IQR(combination$Sales, na.rm=TRUE)
head(thelowerfence_Sales)
   25% 
-392.5 
thelowerfence_Sales <- 0
head(thelowerfence_Sales)
[1] 0
theupperfence_Sales <- quantile(combination$Sales,0.75, na.rm=TRUE)+1.5*IQR(combination$Sales, na.rm=TRUE)
head(theupperfence_Sales)
    75% 
13611.5 
sd(combination$Sales, na.rm=TRUE)
[1] 3103.816
# See descriptive statistics of AveragePurchasePerCustomer
summary(combination$AveragePurchasePerCustomer)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  2.749   7.896   9.250   9.494  10.900  64.958 
IQR(combination$AveragePurchasePerCustomer,na.rm=TRUE)
[1] 3.004158
thelowerfence_AveragePurchasePerCustomer <- quantile(combination$AveragePurchasePerCustomer,na.rm=TRUE,0.25)-1.5*IQR(combination$AveragePurchasePerCustomer,na.rm=TRUE)
head(thelowerfence_AveragePurchasePerCustomer)
     25% 
3.389334 
theupperfence_AveragePurchasePerCustomer <- quantile(combination$AveragePurchasePerCustomer,na.rm=TRUE,0.75)+1.5*IQR(combination$AveragePurchasePerCustomer,na.rm=TRUE)
head(theupperfence_AveragePurchasePerCustomer)
     75% 
15.40597 
sd(combination$AveragePurchasePerCustomer, na.rm=TRUE)
[1] 2.197448
# Handle outliers
cap <- function(x){quantiles <- quantile( x, c(.05, 0.25, 0.75, .95),na.rm=TRUE)
x[ x < quantiles[2] - 1.5*IQR(x,na.rm=TRUE) ] <- quantiles[1]
x[ x > quantiles[3] + 1.5*IQR(x,na.rm=TRUE) ] <- quantiles[4]
x
}
Sales_capped <- combination$Sales %>% cap()
combination_sub <- combination %>% dplyr::select(Sales, AveragePurchasePerCustomer)
# Check summary statistics
summary(combination_sub)
     Sales       AveragePurchasePerCustomer
 Min.   :   46   Min.   : 2.749            
 1st Qu.: 4859   1st Qu.: 7.896            
 Median : 6369   Median : 9.250            
 Mean   : 6956   Mean   : 9.494            
 3rd Qu.: 8360   3rd Qu.:10.900            
 Max.   :41551   Max.   :64.958            
# Check summary statistics again
combination_capped <- sapply(combination_sub, FUN=cap)
summary(combination_capped)
     Sales       AveragePurchasePerCustomer
 Min.   :   46   Min.   : 3.389            
 1st Qu.: 4859   1st Qu.: 7.896            
 Median : 6369   Median : 9.250            
 Mean   : 6807   Mean   : 9.472            
 3rd Qu.: 8360   3rd Qu.:10.900            
 Max.   :13611   Max.   :15.406            
#Check outliers
boxplot(combination_capped)

The boxplot above shows that variables of Sales and Average Purchase Per Customer visualise multivariate outliers. In order to handle these outliers, I need to find the lower and upper fence and replace the outliers with neighbours inside this fence. In this situation, because the value of Sales cannot be negative, I change from -392.5 into 0 as the lower fence and choose values of 5th and 95th percentile as neighbours. If any value of variables is greater than the upper fence, I replace it with the value of 95th percentile. If any value of variables is less than the lower fence, I replace it with the value of 5th percentile. You can see that multivariate outliers will disappear in the second boxplot and the maximum for Sales and Average Purchase Per Customer are now 13611 and 15.406 instead of 41551 and 64.958.

Transform

# Check & Change data distribution Sales
par(mfrow=c(1,2))
hist(combination$Sales)
boxcox_x <- BoxCox(combination$Sales, lambda = "auto")
hist(boxcox_x)

# Check & Change data distribution AveragePurchasePerCustomer
par(mfrow=c(1,2))
hist(combination$AveragePurchasePerCustomer)
boxcox_y <- BoxCox(combination$AveragePurchasePerCustomer, lambda = "auto")
hist(boxcox_y)

As you can see, the value of Sales and Average Purchase Per Customer has right-skewed distribution. In order to make it easier in doing hypothesis testing as well as analysing, I transform data by using BoxCox() to improve assumptions of normality and homogeneity of variance of these variables.

Reference

https://www.kaggle.com/pratyushakar/rossmann-store-sales#store.csv

LS0tCnRpdGxlOiAiUm9zc21hbm4ncyBTYWxlcyBpbiAyMDEzLTIwMTUiCmF1dGhvcjogIlRoaSBIb2FuZyBZZW4gTmd1eWVuIC0gczM1OTQ0NDUiCnN1YnRpdGxlOiBBc3NpZ25tZW50IDMKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6IGRlZmF1bHQKLS0tCgojIyBSZXF1aXJlZCBwYWNrYWdlcyAKCmBgYHtyfQpsaWJyYXJ5KG1hZ3JpdHRyKQpsaWJyYXJ5KHRpZHlyKQpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KGx1YnJpZGF0ZSkKbGlicmFyeShIbWlzYykKbGlicmFyeShvdXRsaWVycykKbGlicmFyeShmb3JlY2FzdCkKYGBgCgojIyBFeGVjdXRpdmUgU3VtbWFyeSAKCkluIG9yZGVyIHRvIGdpdmUgcmVjb21tZW5kYXRpb25zIHdoZXRoZXIgUm9zc21hbm4gc2hvdWxkIGV4cGFuZCBzdG9yZXMgb3Igbm90IGluIG5leHQgeWVhcnMsIHRoZSBmaXJzdCB0aGluZyBJIG5lZWQgdG8gZG8gaXMgc2VhcmNoaW5nIGRhdGEgdG8gdW5kZXJzdGFuZCB3aGF0IHRoZXkgaGF2ZSBkb25lIGR1cmluZyB0aGUgcGVyaW9kIG9mIGl0cyBvcGVyYXRpb24gYW5kIGl0cyBjb21wZXRpdGl2ZSBsZXZlbCBjb21wYXJlZCB0byBpdHMgcml2YWxzIGZyb20gMjAxMyB0byAyMDE1LiBJIGZpbmQgb3V0IDIgZGF0YXNldHMgcmVsYXRlZCB0byB0aGUgcHJvYmxlbSBzdGF0ZW1lbnQgZnJvbSBrYWdnbGUgd2Vic2l0ZSBhbmQgbWFrZSB0aGUgZGF0YSByZWFkeSBmb3IgdGhlIGZ1cnRoZXIgc3RhdGlzdGljYWwgYW5hbHlzaXMuIE1vcmUgZGV0YWlsLCBhZnRlciBpbXBvcnRpbmcgYW5kIG1lcmdpbmcgMiBkYXRhc2V0cyB0byBnYWluIGtub3dsZWdlIGFib3V0IHRoaXMgYnJhbmQsIEkgZmlsdGVyIGltcG9ydGFudCB2YXJpYWJsZXMgYW5kIGRlZXBseSBvYnNlcnZlIHRvIG1ha2UgbXkgYW5hbHlzaXMgZWFzaWVyLiBUaGVuLCBJIGNoZWNrIGRhdGEgc3RydWN0dXJlIHRvIHVuZGVyc3RhbmQgaXRzIGF0dHJpYnV0ZXMgaW4gdGhlIGNvbWJpbmVkIGRhdGFzZXQgYW5kIHVuZGVyc3RhbmQgdGhlIG1lYW5pbmcgb2YgZWFjaCB2YWx1ZSBmb3IgdGhlIHZhcmlhYmxlcy4gTmV4dCwgSSBjaGVjayB3aGV0aGVyIG15IGRhdGEgaXMgbWVzc3kgb3Igbm90OyBmb3J0dW5hdGVseTsgbXkgZGF0YSBhbHJlYWR5IHRpZHkgdXAgYmVjYXVzZSBlYWNoIHZhcmlhYmxlIGhhcyBpdHMgb3duIGNvbHVtbiwgZWFjaCBvYnNlcnZhdGlvbiBoYXMgaXRzIG93biByb3cgYW5kIGVhY2ggdHlwZSBvZiBvYnNlcnZhdGlvbmFsIHVuaXQgaGFzIGl0cyBvd24gdGFibGUuIEZ1cnRoZXJtb3JlLCB0byBrbm93IHRoZSBsZXZlbCBvZiBjdXN0b21lciBwcmVmZXJlbmNlIHdoaWNoIHN1cHBvcnRzIGZvciBwcm9ibGVtIHN0YXRlbWVudCwgSSBjcmVhdGUgYSBuZXcgdmFyaWFibGUsIGVzcGVjaWZpY2FsbHkgYXZlcmFnZSBwdXJjaGFzZSBwZXIgY3VzdG9tZXIsIHRvIGtub3cgdGhlIGxldmVsIG9mIHNwZW5kaW5nIG9uIFJvc3NtYW5uIHByb2R1Y3RzIG9mIGV2ZXJ5IGN1c3RvbWVyIGluIGVhY2ggYXJlYSBhbmQgZWFjaCBwZXJpb2Qgb2YgdGltZS4gSW4gYWRkaXRpb24sIEkgY2xlYW4gZGF0YSBmb3Igb2J2aW91cyBlcnJvcnMsIGlkZW50aWZ5IGFuZCBoYW5kbGUgbXVsdGl2YXJpYXRlIG91dGxpZXJzIGJ5IHJlcGxhY2luZyB0aGVtIHdpdGggNXRoIGFuZCA5NXRoIHF1YW50aWxlLCBhbmQgZGVhbCB3aXRoIG1pc3NpbmcgdmFsdWVzIGJ5IHJlcGxhY2luZyB0aGVtIHdpdGggIk5vbmUiIGFuZCAibWVkaWFuIHZhbHVlIiBkZXBlbmRpbmcgb24gbmF0dXJlcyBvZiB2YXJpYWJsZXMuIExhc3RseSwgbXkgZGF0YXNldCBoYXMgcmlnaHQtc2tld2VkIGRpc3RyaWJ1dGlvbiwgc28gSSB0cmFuc2Zvcm0gaXQgdG8gY2hhbmdlIGl0cyBkaXN0cmlidXRpb24gaW50byBub3JtYWwgZGlzdHJpYnV0aW9uIHdoaWNoIHdpbGwgbWFrZSBlYXNpZXIgaW4gc3RhdGlzdGljYWwgYW5hbHlzaXMuIFRvIG1ha2UgbXkgZGVzY3JpcHRpb24gYWJvdXQgZGF0YSBwcmVwcm9jZXNzaW5nIHNpbXBsaWVyLCBzdGVwcyBiZWxvdyB3aWxsIGNsZWFybHkgZGVtb25zdHJhdGUgd2hhdCBJIGRlc2NyaWJlIHRoZSBwcm9jZXNzIGFib3ZlLiAgCgojIyBEYXRhIAoKVGhlIHB1cnBvc2Ugb2YgdGhpcyByZXBvcnQgaXMgdG8gZGV0ZXJtaW5lIHdoZXRoZXIgUm9zc21hbm4gc2hvdWxkIGtlZXAgb3Blbm5pbmcgc3RvcmVzIGluIG5leHQgeWVhcnMuIFRoZXJlZm9yZSwgZGVlcGx5IHVuZGVyc3RhbmRpbmcgaXRzIG9wZXJhdGlvbiBpcyB0aGUgbW9zdCBpbXBvcnRhbnQgdGhpbmcgSSBuZWVkIHRvIGZvY3VzLiBJbiBvcmRlciB0byBoYXZlIGEgYmV0dGVyIG92ZXJ2aWV3IGFib3V0IFJvc3NtYW5uLCB0aGUgY29tYmluZWQgZGF0YXNldCBiZWxvdyBpcyBtZXJnZWQgYnkgdHdvIHJlbGF0aXZlIGRldGFzZXRzIGFuZCBkaXZpZGVkIGludG8gMTggdmFyaWFibGVzIHJlcHJlc2VudGluZyBoaXN0b3JpY2FsIHNhbGVzIGZvciAxLDExNSBSb3NzbWFubiBzdG9yZXMgaW4gbGFzdCAzIHllYXJzLiBEdXJpbmcgb3BlcmF0aW9uLCBzb21lIHN0b3JlcyB3ZXJlIHRlbXBvcmFyaWx5IGNsb3NlZCBmb3IgcmVmdXJiaXNobWVudCwgc28gSSByZW1vdmUgZGF0ZXMgd2l0aG91dCBSb3NzbWFubidzIHJldmVudWUgZm9yIG1vcmUgYWNjdXJhdGUgYW5hbHlzaXMuIAoKYGBge3J9CiMgU2V0IHdvcmtpbmcgZGlyZWN0b3J5CnNldHdkKCJ+L0Rlc2t0b3AvV2VlazMiKQoKIyBJbXBvcnQgdHdvIGRhdGFzZXRzCnRyYWluIDwtIHJlYWQuY3N2KCJ0cmFpbi5jc3YiLCBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UpCmhlYWQodHJhaW4pCnN0b3JlIDwtIHJlYWQuY3N2KCJzdG9yZS5jc3YiLCBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UpCmhlYWQoc3RvcmUpCgojIENvbWJpbmUgdHdvIGRhdGFzZXRzIApjb21iaW5hdGlvbiA8LSBsZWZ0X2pvaW4oc3RvcmUsdHJhaW4sa2V5PSJTdG9yZSIpCgojIEZpbHRlciBkYXRhCmNvbWJpbmF0aW9uIDwtIGNvbWJpbmF0aW9uICU+JSBmaWx0ZXIoU2FsZXMgPiAwKQpoZWFkKGNvbWJpbmF0aW9uKQpgYGAKCiMjIFVuZGVyc3RhbmQgCgpgYGB7cn0KIyBTdW1tYXJpc2UgZGF0YQpzdW1tYXJ5KGNvbWJpbmF0aW9uKQoKIyBDaGVjayBkYXRhIHN0cnVjdHVyZQpzdHIoY29tYmluYXRpb24pCmBgYApJbiB0aGUgY29tYmluZWQgZGF0YXNldCwgc29tZSB2YXJpYWJsZXMgYXJlIG5vdCBwcmVzZW50ZWQgYXMgaXRzIG5hdHVyZXMgKGUuZzogQXNzb3J0bWVudCBpcyBub3QgY2hhcmFjdGVyIHZhcmlhYmxlKQoKCmBgYHtyfQojIEFkanVzdCBkYXRhIHN0cnVjdHVyZQpjb21iaW5hdGlvbiRBc3NvcnRtZW50IDwtIGZhY3Rvcihjb21iaW5hdGlvbiRBc3NvcnRtZW50LGxldmVscz1jKCJhIiwiYiIsImMiKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IGMoImJhc2ljIiwiZXh0cmEiLCJleHRlbmRlZCIpKQpjb21iaW5hdGlvbiRQcm9tbzIgPC0gZmFjdG9yKGNvbWJpbmF0aW9uJFByb21vMixsZXZlbHM9YygwLDEpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gYygibm90IGNvbnRpbnVpbmcgcHJvbW90aW9uIiwiY29udGludWluZyBwcm9tb3Rpb24iKSkKY29tYmluYXRpb24kRGF5T2ZXZWVrIDwtIGZhY3Rvcihjb21iaW5hdGlvbiREYXlPZldlZWssbGV2ZWxzPWMoMSwyLDMsNCw1LDYsNyksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBjKCJTdW5kYXkiLCJNb25kYXkiLCJUdWVzZGF5IiwiV2VkbmVzZGF5IiwiVGh1cnNkYXkiLCJGcmlkYXkiLCJTYXR1cmRheSIpKQpjb21iaW5hdGlvbiRPcGVuIDwtIGZhY3Rvcihjb21iaW5hdGlvbiRPcGVuLGxldmVscz1jKDAsMSksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IGMoImNsb3NlZCIsIm9wZW4iKSkKY29tYmluYXRpb24kUHJvbW8gPC0gZmFjdG9yKGNvbWJpbmF0aW9uJFByb21vLGxldmVscz1jKDAsMSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBsYWJlbHM9Yygibm8gcHJvbW90aW9uIiwicHJvbW90aW9uIikpCmNvbWJpbmF0aW9uJFN0YXRlSG9saWRheSA8LSBmYWN0b3IoY29tYmluYXRpb24kU3RhdGVIb2xpZGF5LGxldmVscz1jKCJhIiwiYiIsImMiLDApLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxhYmVscz1jKCJwdWJsaWMtaG9saWRheSIsIkVhc3Rlci1ob2xpZGF5IiwiQ2hyaXN0bWFzIiwiTm9uZSIpKQpjb21iaW5hdGlvbiRTY2hvb2xIb2xpZGF5IDwtIGZhY3Rvcihjb21iaW5hdGlvbiRTY2hvb2xIb2xpZGF5LCBsZXZlbHM9YygwLDEpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsYWJlbHM9YygiY2xvc2VkIiwib3BlbiIpKQpjb21iaW5hdGlvbiREYXRlIDwtIGFzLkRhdGUoY29tYmluYXRpb24kRGF0ZSkKc3RyKGNvbWJpbmF0aW9uKQpgYGAKRm9yIGV4YW1wbGUsIHdpdGggYXNzb3J0bWVudCB2YXJpYWJsZSwgSSBjb252ZXJ0IGZyb20gY2hhcmFjdGVyIGludG8gZmFjdG9yIGFuZCBsYWJlbCBpdCBhcyBiYXNpYywgZXh0cmEgYW5kIGV4dGVuZGVkIHN0b3JlcyByZXNwZWN0aXZlbHkgaW4gcmVsYXRpb24gdG8gaXRzIGxldmVsIGEsYixjLiAKCiMjCVRpZHkgJiBNYW5pcHVsYXRlIERhdGEgSSAKClRoaXMgY29tYmluZWQgZGF0YXNldCBpcyBsb25nIGZvcm1hdCBhcyBpdCBpcyBhcnJhbmdlZCBpbiBzdWNoIGEgd2F5IHRoYXQgYSBzaW5nbGUgc3ViamVjdCdzIGluZm9ybWF0aW9uIGlzIHN0b3JlZCBpbiBtdWx0aXBsZSByb3dzLiBFYWNoIHZhcmlhYmxlIGhhcyBpdHMgb3duIGNvbHVtbi4gRWFjaCBvYnNlcnZhdGlvbiBoYXMgaXRzIG93biByb3cuIEVhY2ggdmFsdWUgaGFzIGl0cyBvd24gY2VsbC4gVGhlcmVmb3JlLCB0aGlzIGNvbWJpbmVkIGRhdGFzZXQgcmVwcmVzZW50cyBhcyBhIHRpZHkgZGF0YS4gCgojIwlUaWR5ICYgTWFuaXB1bGF0ZSBEYXRhIElJIAoKSW4gb3JkZXIgdG8gcG9zaXRpdmVseSBmb3JlY2FzdCBSb3NzbWFubidzIHNhbGVzIGluIG5leHQgdHdvIHllYXJzIHdpdGggdGhlIHB1cnBvc2Ugb2YgZXh0ZW5kaW5nIHN0b3JlcywgSSBjcmVhdGUgbmV3IGNvbHVtbiBuYW1lZCBBdmVyYWdlIFB1cmNoYXNlIFBlciBDdXN0b21lciBmcm9tIDIgZXhpc3RpbmcgdmFyaWFibGVzIChTYWxlcyBhbmQgQ3VzdG9tZXJzKSBieSBtdXRhdGUgKCkgZnVuY3Rpb24gdG8ga25vdyB0aGUgYXZlcmFnZSBhbW91bnQgb2YgbW9uZXkgZWFjaCBjdXN0b21lciB0ZW5kIHRvIHNwZW5kIG9uIFJvc3NtYW5uIHByb2R1Y3RzIGluIGVhY2ggZGF5IGluIGVhY2ggc3RvcmUuCgpgYGB7cn0KIyBDcmVhdGUgbmV3IHZhcmlhYmxlIApjb21iaW5hdGlvbiA8LSBtdXRhdGUoY29tYmluYXRpb24sIEF2ZXJhZ2VQdXJjaGFzZVBlckN1c3RvbWVyPVNhbGVzL0N1c3RvbWVycykgCmhlYWQoY29tYmluYXRpb24kQXZlcmFnZVB1cmNoYXNlUGVyQ3VzdG9tZXIpCmBgYAoKIyMJU2NhbiBJIAoKSW4gdGhlIGNvbWJpbmVkIGRhdGFzZXQsIHNvbWUgdmFyaWFibGVzLCBpbmNsdWRpbmcgY29tcGV0aXRpb24gZGlzdGFuY2UsIGNvbXBldGl0aW9uIG9wZW4gc2luY2UgbW9udGgsIGNvbXBldGl0aW9uIG9wZW4gc2luY2UgeWVhciwgcHJvbW8yIHNpbmNlIHdlZWssIHByb21vMiBzaW5jZSB5ZWFyLCBoYXZlIHNvbWUgbWlzc2luZyB2YWx1ZXMuIAoKYGBge3J9CiMgQ2hlY2sgbWlzc2luZyB2YWx1ZXMKc2FwcGx5KGNvbWJpbmF0aW9uLCBmdW5jdGlvbih4KSBzdW0oaXMubmEoeCkpKQpgYGAKCgpEZXBlbmRpbmcgb24gbmF0dXJlcyBvZiB2YXJpYWJsZXMsIEkgaGFuZGxlIHRoZXNlIG1pc3NpbmcgdmFsdWVzIHdpdGggZGlmZmVyZW50IHdheXMuIFNwZWNpZmljYWxseSwKCiogU29tZSBzdG9yZXMgd2lsbCBoYXZlIG5vIDJuZCBwcm9tb3Rpb24sIHNvIHR3byB2YXJpYWJsZXMtIHByb21vMiBzaW5jZSB3ZWVrIGFuZCBwcm9tbzIgc2luY2UgeWVhciB3aWxsIGhhdmUgTkFzLiBUaGVuLCBJIHJlcGxhY2UgIk5BcyIgd2l0aCAiTm9uZSIgdG8gaW5kaWNhdGUgdGhhdCBubyAybmQgcHJvbW90aW9uIHJlc3VsdHMgaW4gbm8gcHJvbW8yIHNpbmNlIHdlZWsgYW5kIG5vIHByb21vMiBzaW5jZSB5ZWFyLiAKCiogV2l0aCBtaXNzaW5nIHZhbHVlcyBvZiB2YXJpYWJsZXMgb2YgY29tcGV0aXRpb24gZGlzdGFuY2UsIGNvbXBldGl0aW9uIG9wZW4gc2luY2UgbW9udGggYW5kIGNvbXBldGl0aW9uIG9wZW4gc2luY2UgeWVhciwgSSByZXBsYWNlICJOQXMiIiBpbnRvIGl0cyAibWVkaWFuIHZhbHVlIiIgYmVjYXVzZSBtZWRpYW4gdmFsdWUgd2lsbCB3b3JrIHdlbGwgd2l0aCBpdHMgY29sdW1uOyBtb250aCBhbmQgeWVhciBjb3VsZCBub3QgYmUgZG91YmxlIHZhcmlhYmxlIChtb250aCBhbmQgeWVhciBjb3VsZCBiZSBkb3VibGUgdmFyaWFibGUgd2hlbiB1c2luZyBtZWFuKQoKYGBge3J9CiMgRGVhbCB3aXRoIG1pc3NpbmcgdmFsdWVzCmNvbWJpbmF0aW9uJFByb21vMlNpbmNlV2VlayA8LSBpZmVsc2UoaXMubmEoY29tYmluYXRpb24kUHJvbW8yU2luY2VXZWVrKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ05vbmUnLCBjb21iaW5hdGlvbiRQcm9tbzJTaW5jZVdlZWspCgpjb21iaW5hdGlvbiRQcm9tbzJTaW5jZVllYXIgPC0gaWZlbHNlKGlzLm5hKGNvbWJpbmF0aW9uJFByb21vMlNpbmNlWWVhciksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICdOb25lJywgY29tYmluYXRpb24kUHJvbW8yU2luY2VZZWFyKQoKY29tYmluYXRpb24kQ29tcGV0aXRpb25EaXN0YW5jZVtpcy5uYShjb21iaW5hdGlvbiRDb21wZXRpdGlvbkRpc3RhbmNlKV0gPC0gbWVkaWFuKGNvbWJpbmF0aW9uJENvbXBldGl0aW9uRGlzdGFuY2UsIG5hLnJtPVRSVUUpCmNvbWJpbmF0aW9uJENvbXBldGl0aW9uT3BlblNpbmNlTW9udGhbaXMubmEoY29tYmluYXRpb24kQ29tcGV0aXRpb25PcGVuU2luY2VNb250aCldIDwtIG1lZGlhbihjb21iaW5hdGlvbiRDb21wZXRpdGlvbk9wZW5TaW5jZU1vbnRoLCBuYS5ybT1UUlVFKQpjb21iaW5hdGlvbiRDb21wZXRpdGlvbk9wZW5TaW5jZVllYXJbaXMubmEoY29tYmluYXRpb24kQ29tcGV0aXRpb25PcGVuU2luY2VZZWFyKV0gPC0gbWVkaWFuKGNvbWJpbmF0aW9uJENvbXBldGl0aW9uT3BlblNpbmNlWWVhciwgbmEucm09VFJVRSkKc2FwcGx5KGNvbWJpbmF0aW9uLCBmdW5jdGlvbih4KSBzdW0oaXMubmEoeCkpKQpgYGAKCiMjCVNjYW4gSUkKCmBgYHtyfQojIERldGVjdCBvdXRsaWVycyBTYWxlcyAmIEF2ZXJhZ2VQdXJjaGFzZVBlckN1c3RvbWVyCnBhcihtZnJvdz1jKDIsMSkpCmJveHBsb3QoY29tYmluYXRpb24kU2FsZXMsIG1haW49IlRoZSBudW1iZXIgb2YgU2FsZXMgYmV0d2VlbiBzdG9yZXMiKQpib3hwbG90KGNvbWJpbmF0aW9uJEF2ZXJhZ2VQdXJjaGFzZVBlckN1c3RvbWVyLCBtYWluPSJUaGUgQXZlcmFnZSBTcGVuZGluZyBvZiBFYWNoIEN1c3RvbWVyIGJldHdlZW4gU3RvcmVzIikKCiMgU2VlIGRlc2NyaXB0aXZlIHN0YXRpc3RpY3Mgb2YgU2FsZXMKc3VtbWFyeShjb21iaW5hdGlvbiRTYWxlcykKSVFSKGNvbWJpbmF0aW9uJFNhbGVzLCBuYS5ybT1UUlVFKQp0aGVsb3dlcmZlbmNlX1NhbGVzIDwtIHF1YW50aWxlKGNvbWJpbmF0aW9uJFNhbGVzLDAuMjUsIG5hLnJtPVRSVUUpLTEuNSpJUVIoY29tYmluYXRpb24kU2FsZXMsIG5hLnJtPVRSVUUpCmhlYWQodGhlbG93ZXJmZW5jZV9TYWxlcykKdGhlbG93ZXJmZW5jZV9TYWxlcyA8LSAwCmhlYWQodGhlbG93ZXJmZW5jZV9TYWxlcykKdGhldXBwZXJmZW5jZV9TYWxlcyA8LSBxdWFudGlsZShjb21iaW5hdGlvbiRTYWxlcywwLjc1LCBuYS5ybT1UUlVFKSsxLjUqSVFSKGNvbWJpbmF0aW9uJFNhbGVzLCBuYS5ybT1UUlVFKQpoZWFkKHRoZXVwcGVyZmVuY2VfU2FsZXMpCnNkKGNvbWJpbmF0aW9uJFNhbGVzLCBuYS5ybT1UUlVFKQoKIyBTZWUgZGVzY3JpcHRpdmUgc3RhdGlzdGljcyBvZiBBdmVyYWdlUHVyY2hhc2VQZXJDdXN0b21lcgpzdW1tYXJ5KGNvbWJpbmF0aW9uJEF2ZXJhZ2VQdXJjaGFzZVBlckN1c3RvbWVyKQpJUVIoY29tYmluYXRpb24kQXZlcmFnZVB1cmNoYXNlUGVyQ3VzdG9tZXIsbmEucm09VFJVRSkKdGhlbG93ZXJmZW5jZV9BdmVyYWdlUHVyY2hhc2VQZXJDdXN0b21lciA8LSBxdWFudGlsZShjb21iaW5hdGlvbiRBdmVyYWdlUHVyY2hhc2VQZXJDdXN0b21lcixuYS5ybT1UUlVFLDAuMjUpLTEuNSpJUVIoY29tYmluYXRpb24kQXZlcmFnZVB1cmNoYXNlUGVyQ3VzdG9tZXIsbmEucm09VFJVRSkKaGVhZCh0aGVsb3dlcmZlbmNlX0F2ZXJhZ2VQdXJjaGFzZVBlckN1c3RvbWVyKQp0aGV1cHBlcmZlbmNlX0F2ZXJhZ2VQdXJjaGFzZVBlckN1c3RvbWVyIDwtIHF1YW50aWxlKGNvbWJpbmF0aW9uJEF2ZXJhZ2VQdXJjaGFzZVBlckN1c3RvbWVyLG5hLnJtPVRSVUUsMC43NSkrMS41KklRUihjb21iaW5hdGlvbiRBdmVyYWdlUHVyY2hhc2VQZXJDdXN0b21lcixuYS5ybT1UUlVFKQpoZWFkKHRoZXVwcGVyZmVuY2VfQXZlcmFnZVB1cmNoYXNlUGVyQ3VzdG9tZXIpCnNkKGNvbWJpbmF0aW9uJEF2ZXJhZ2VQdXJjaGFzZVBlckN1c3RvbWVyLCBuYS5ybT1UUlVFKQoKIyBIYW5kbGUgb3V0bGllcnMKY2FwIDwtIGZ1bmN0aW9uKHgpe3F1YW50aWxlcyA8LSBxdWFudGlsZSggeCwgYyguMDUsIDAuMjUsIDAuNzUsIC45NSksbmEucm09VFJVRSkKeFsgeCA8IHF1YW50aWxlc1syXSAtIDEuNSpJUVIoeCxuYS5ybT1UUlVFKSBdIDwtIHF1YW50aWxlc1sxXQp4WyB4ID4gcXVhbnRpbGVzWzNdICsgMS41KklRUih4LG5hLnJtPVRSVUUpIF0gPC0gcXVhbnRpbGVzWzRdCngKfQpTYWxlc19jYXBwZWQgPC0gY29tYmluYXRpb24kU2FsZXMgJT4lIGNhcCgpCmNvbWJpbmF0aW9uX3N1YiA8LSBjb21iaW5hdGlvbiAlPiUgZHBseXI6OnNlbGVjdChTYWxlcywgQXZlcmFnZVB1cmNoYXNlUGVyQ3VzdG9tZXIpCgojIENoZWNrIHN1bW1hcnkgc3RhdGlzdGljcwpzdW1tYXJ5KGNvbWJpbmF0aW9uX3N1YikKCiMgQ2hlY2sgc3VtbWFyeSBzdGF0aXN0aWNzIGFnYWluCmNvbWJpbmF0aW9uX2NhcHBlZCA8LSBzYXBwbHkoY29tYmluYXRpb25fc3ViLCBGVU49Y2FwKQpzdW1tYXJ5KGNvbWJpbmF0aW9uX2NhcHBlZCkKCiNDaGVjayBvdXRsaWVycwpib3hwbG90KGNvbWJpbmF0aW9uX2NhcHBlZCkKCmBgYAoKVGhlIGJveHBsb3QgYWJvdmUgc2hvd3MgdGhhdCB2YXJpYWJsZXMgb2YgU2FsZXMgYW5kIEF2ZXJhZ2UgUHVyY2hhc2UgUGVyIEN1c3RvbWVyIHZpc3VhbGlzZSBtdWx0aXZhcmlhdGUgb3V0bGllcnMuCkluIG9yZGVyIHRvIGhhbmRsZSB0aGVzZSBvdXRsaWVycywgSSBuZWVkIHRvIGZpbmQgdGhlIGxvd2VyIGFuZCB1cHBlciBmZW5jZSBhbmQgcmVwbGFjZSB0aGUgb3V0bGllcnMgd2l0aCBuZWlnaGJvdXJzIGluc2lkZSB0aGlzIGZlbmNlLiBJbiB0aGlzIHNpdHVhdGlvbiwgYmVjYXVzZSB0aGUgdmFsdWUgb2YgU2FsZXMgY2Fubm90IGJlIG5lZ2F0aXZlLCBJIGNoYW5nZSBmcm9tIC0zOTIuNSBpbnRvIDAgYXMgdGhlIGxvd2VyIGZlbmNlIGFuZCBjaG9vc2UgdmFsdWVzIG9mIDV0aCBhbmQgOTV0aCBwZXJjZW50aWxlIGFzIG5laWdoYm91cnMuIElmIGFueSB2YWx1ZSBvZiB2YXJpYWJsZXMgaXMgZ3JlYXRlciB0aGFuIHRoZSB1cHBlciBmZW5jZSwgSSByZXBsYWNlIGl0IHdpdGggdGhlIHZhbHVlIG9mIDk1dGggcGVyY2VudGlsZS4gSWYgYW55IHZhbHVlIG9mIHZhcmlhYmxlcyBpcyBsZXNzIHRoYW4gdGhlIGxvd2VyIGZlbmNlLCBJIHJlcGxhY2UgaXQgd2l0aCB0aGUgdmFsdWUgb2YgNXRoIHBlcmNlbnRpbGUuIFlvdSBjYW4gc2VlIHRoYXQgbXVsdGl2YXJpYXRlIG91dGxpZXJzIHdpbGwgZGlzYXBwZWFyIGluIHRoZSBzZWNvbmQgYm94cGxvdCBhbmQgdGhlIG1heGltdW0gZm9yIFNhbGVzIGFuZCBBdmVyYWdlIFB1cmNoYXNlIFBlciBDdXN0b21lciBhcmUgbm93IDEzNjExIGFuZCAxNS40MDYgaW5zdGVhZCBvZiA0MTU1MSBhbmQgNjQuOTU4LgoKIyMJVHJhbnNmb3JtIAoKYGBge3J9CiMgQ2hlY2sgJiBDaGFuZ2UgZGF0YSBkaXN0cmlidXRpb24gU2FsZXMKcGFyKG1mcm93PWMoMSwyKSkKaGlzdChjb21iaW5hdGlvbiRTYWxlcykKYm94Y294X3ggPC0gQm94Q294KGNvbWJpbmF0aW9uJFNhbGVzLCBsYW1iZGEgPSAiYXV0byIpCmhpc3QoYm94Y294X3gpCmBgYApgYGB7cn0KIyBDaGVjayAmIENoYW5nZSBkYXRhIGRpc3RyaWJ1dGlvbiBBdmVyYWdlUHVyY2hhc2VQZXJDdXN0b21lcgpwYXIobWZyb3c9YygxLDIpKQpoaXN0KGNvbWJpbmF0aW9uJEF2ZXJhZ2VQdXJjaGFzZVBlckN1c3RvbWVyKQpib3hjb3hfeSA8LSBCb3hDb3goY29tYmluYXRpb24kQXZlcmFnZVB1cmNoYXNlUGVyQ3VzdG9tZXIsIGxhbWJkYSA9ICJhdXRvIikKaGlzdChib3hjb3hfeSkKYGBgCgpBcyB5b3UgY2FuIHNlZSwgdGhlIHZhbHVlIG9mIFNhbGVzIGFuZCBBdmVyYWdlIFB1cmNoYXNlIFBlciBDdXN0b21lciBoYXMgcmlnaHQtc2tld2VkIGRpc3RyaWJ1dGlvbi4gSW4gb3JkZXIgdG8gbWFrZSBpdCBlYXNpZXIgaW4gZG9pbmcgaHlwb3RoZXNpcyB0ZXN0aW5nIGFzIHdlbGwgYXMgYW5hbHlzaW5nLCBJIHRyYW5zZm9ybSBkYXRhIGJ5IHVzaW5nIEJveENveCgpIHRvIGltcHJvdmUgYXNzdW1wdGlvbnMgb2Ygbm9ybWFsaXR5IGFuZCBob21vZ2VuZWl0eSBvZiB2YXJpYW5jZSBvZiB0aGVzZSB2YXJpYWJsZXMuIAoKIyNSZWZlcmVuY2UKCmh0dHBzOi8vd3d3LmthZ2dsZS5jb20vcHJhdHl1c2hha2FyL3Jvc3NtYW5uLXN0b3JlLXNhbGVzI3N0b3JlLmNzdgo=