Install a useful package called “pacman”. This is a manager to manage package. Like your HR perosonnel, an employee to manage other employees.

once you have pacman installed and loaded. You can install/load all other packages using a function “p_load” from “pacman”.

Feel free to use other ways of installing and loading packages.

install.packages("pacman") 
WARNING: Rtools is required to build R packages but is not currently installed. Please download and install the appropriate version of Rtools before proceeding:

https://cran.rstudio.com/bin/windows/Rtools/
Installing package into 㤼㸱C:/Users/ranai/Documents/R/win-library/3.6㤼㸲
(as 㤼㸱lib㤼㸲 is unspecified)
trying URL 'https://cran.rstudio.com/bin/windows/contrib/3.6/pacman_0.5.1.zip'
Content type 'application/zip' length 389783 bytes (380 KB)
downloaded 380 KB
package ‘pacman’ successfully unpacked and MD5 sums checked

The downloaded binary packages are in
    C:\Users\ranai\AppData\Local\Temp\RtmpI93LAv\downloaded_packages
library(pacman)
#I spoke to Professor Ron in 5/4 9-11am CT office hours about the WARNING message below and showed him that Rtools is already installed on my computer (by following the link below). I don't know how to otherwise make this warning message go away.#

Install/ load required packages.

p_load(dplyr, ggplot2, tidyr, stringr, lubridate)

Read the data file as “df”

df <- read.csv("Live_Session_Mod_3.csv")# make sure to put the csv file in your working directory

Quickly inspect the structure of the various variables

str(df)
'data.frame':   145615 obs. of  21 variables:
 $ Time             : Factor w/ 74492 levels "2014-01-02T11:03:00Z",..: 22553 35327 28037 15513 59404 21678 50788 46834 6313 72359 ...
 $ OperationType    : Factor w/ 2 levels "RETURN","SALE": 2 2 2 2 2 2 2 2 2 2 ...
 $ BarCode          : Factor w/ 1 level "*": 1 1 1 1 1 1 1 1 1 1 ...
 $ CashierName      : Factor w/ 53 levels "Aimee Port","Alberta Lynch",..: 22 11 30 12 7 29 11 42 44 29 ...
 $ LineItem         : Factor w/ 28 levels "Aubergine and Chickpea Vindaloo",..: 20 2 28 24 7 24 2 21 23 28 ...
 $ Department       : Factor w/ 8 levels "Beverage","Catering",..: 3 3 8 3 6 3 3 8 8 8 ...
 $ Category         : Factor w/ 20 levels "Aubergine and Chickpea Vindaloo",..: 14 3 20 19 2 19 3 15 18 20 ...
 $ CardholderName   : Factor w/ 48790 levels "AABID FIENE",..: 38867 41457 8593 22456 23959 28233 2516 10529 36382 44814 ...
 $ RegisterName     : Factor w/ 12 levels "FT136","FT137",..: 8 8 8 8 10 8 10 10 6 10 ...
 $ StoreNumber      : Factor w/ 29 levels "AZ23501251","AZ23501258",..: 1 2 19 5 11 5 2 14 15 19 ...
 $ TransactionNumber: Factor w/ 78559 levels "00002QD152261",..: 44782 9936 50563 40892 68609 37351 4676 44674 76508 61003 ...
 $ CustomerCode     : Factor w/ 6022 levels "C00114815PM",..: 5449 5449 5449 5449 5449 5449 5449 5449 5449 5449 ...
 $ Cost             : num  0.11 0.11 0.11 0.11 0.11 0.11 0.11 0.11 0.11 0.11 ...
 $ Price            : num  7.84 14.35 4.5 14.68 12.02 ...
 $ Quantity         : int  1 1 1 1 1 1 1 1 1 1 ...
 $ Modifiers        : num  0.01 0.01 2.36 0.01 1.08 0.01 0.01 2.36 0.01 0.01 ...
 $ Subtotal         : num  7.85 14.36 6.86 14.69 13.1 ...
 $ Discounts        : num  -0.03 -0.03 -0.03 -0.03 -0.03 -0.03 -0.03 -0.03 -0.03 -0.03 ...
 $ NetTotal         : num  7.88 14.39 6.89 14.72 13.13 ...
 $ Tax              : num  0.62 1.13 0.54 1.16 1.04 1.16 2.27 0.41 0.22 0.35 ...
 $ TotalDue         : num  8.5 15.52 7.43 15.88 14.17 ...

Please reveiew the nature of the variables and make sure they all look good. For example, if the variables related to date, such as “Time” in our dataset, is saved as a factor, we won’t be able to use it meaningfully. Thus, we must convert into a date object. Professor Ron covered this idea at length in Live Session 2.

Once, we convert “Time” as a date object, we can extract the variables such as Year, Month, Day, WeekDay, Hour for analyzing the trend over time.

First check a few values in the df$Time

head(df$Time, 3)
[1] 2015-02-19T15:30:00Z 2015-08-04T11:50:00Z 2015-04-29T19:02:00Z
74492 Levels: 2014-01-02T11:03:00Z 2014-01-02T11:29:00Z 2014-01-02T11:33:00Z ... 2017-04-03T17:08:00Z

You will notice a problem in the way the Time variable is stored currently. 1) 144660 levels - presence of levels means it’s stored as factors.

Let’s check the class of df$Time

class(df$Time)
[1] "factor"

This won’t help. We need to extract variables such as “day”, “month”, “year” etc. from this for analysis. And, we can’t do that on a factor variable.

Let’s set this as a time variable using the “strptime” function from base R package. Lubridate also has many useful functions.

df$Time <- lubridate::ymd_hms(df$Time)

Let’s check a few values, now

head(df$Time, 3)
[1] "2015-02-19 15:30:00 UTC" "2015-08-04 11:50:00 UTC" "2015-04-29 19:02:00 UTC"

Now, we see them in a proper date format with everything nicely lined up

We are ready to extract the variables.

Let’s extract the variables such as year, month, weekday, day and hour

We will use the functions from the lubridate package for this purpose.

df <- df %>% mutate(Year = year(Time), Month = month(Time), Day = day(Time), 
                    Hour = hour(Time), WeekDay = wday(Time), Date = date(Time),
                    Week = week(Time), Quarter = quarter(Time))

# I hope you remember the "mutate" function from our Mod3 on Coursera. This is to create new columns. Please play with this code by changing names "Year" and "Month" to "Year_abcd" and "Month_abcd" so that you understand which part of the code is a function and which part is user defined names. "year", "month", "day", "hour"... are all functions. 
# Try playing with them using "df %>% mutate(Year = year_abcd(Time)" and R will wreak havoc on your the notebook. If you do "df %>% mutate(Year_abcd = year(Time)", R will be kind to run the code nicely. 

We added 8 new variables in the chunk above. Now the number of columns has increased to 29 from 21. Check in the “Environment” on the top right corner of your screen.

colSums(is.na(df))
             Time     OperationType           BarCode       CashierName          LineItem        Department 
                0                 0                 0                 0                 0                 0 
         Category    CardholderName      RegisterName       StoreNumber TransactionNumber      CustomerCode 
                0                 0                 0                 0                 0                 0 
             Cost             Price          Quantity         Modifiers          Subtotal         Discounts 
                0                 0                 0                 0                 0                 0 
         NetTotal               Tax          TotalDue              Year             Month               Day 
                0                 0                 0                 0                 0                 0 
             Hour           WeekDay              Date              Week           Quarter 
                0                 0                 0                 0                 0 

There seems to be no missing values. So we are good for now.

Let’s quickly check the first and the last date in the data

min(df$Date, na.rm = TRUE)
[1] "2014-01-02"
max(df$Date, na.rm = TRUE)
[1] "2017-04-03"

We see that the first mentioned date in the data is 2nd-Jan-2014 and 3rd-April-2017.

Generate Feature - Profitability

We create a variable called “Profitability” as (NetTotal - (Quantity X Cost))*100/ (Quantity X Cost).

Please note that the figures in the data seem to be odd. But we will just ignore that part right now.

df <- df %>% mutate(Profitability = (NetTotal - (Quantity*Cost))/ (Quantity*Cost))

There is possibility that if the quantity or cost is zero in one of the rows, the profitability may turn out to be undefined value. We can check if do suffer from that problem here.

sum(!is.finite(df$Profitability))
[1] 0

No bad values for Profitability. There may be a negative values based on the return products. But we won’t bother right now.

***************Business Decision, Analysis, and the Data*********************

Customer Analysis

To perform customer level analysis, we need to bring our data to the customer level. That means each row must correspond to a unique customer.

To perform customer level analysis, marketing folks use something called RFM analysis. Where, R stands for Recency, F stands for Frequency and M Stands for Monetary.

Check this Wiki entry for basic idea - https://en.wikipedia.org/wiki/RFM_(market_research). It’s an intutive idea.

Feature Generation for RFM Analysis

Christina wants to perform RFM analysis to find out customer segments. Thus, she must calculate the following variables for each customer: Recency - How recently has the customer made a purchase? Frequency - How frequently has the customer made purchases in the past? Monetary Value - How much average purchase ($) has the customer made in the past?

Note - there are various approaches to define Monetary - such as mean_purchase vs total_purchase. We will stick to mean_pruchase.

We may also want to keep other variables for Profitability analysis.

From LineItem within a Transaction to Customer Level Data

You need to prepare a customer level data to help Christina. Currently, the unit of observation is a unique LineItem within a Transaction. You will have to aggregate the data at the Customer Level for Customer Level Analysis.

This is a question for you for Mod 3 Assignment.

To help you, I am showing you the Transaction level aggregation. You may use the same code (with appropriate tweaking) for creating customer level data.

Creating Transaction Level Data

Remember, for aggregation, we use group_by function and the summarise function from “dplyr” package.

I will call the transaction level data as df_Transactions.

Note - This code may take a few minutes. Have faith in GOD.

df_Transactions <- df %>% group_by(TransactionNumber) %>% summarise(
                      UniqueLineItemsCount = n_distinct(LineItem), # For the count of UNIQUE LineItems in each transaction
                      TotalItemsCount = sum(Quantity), # For the count of LineItems in each transaction
                      TransactionAmount = sum(NetTotal),# Total Purchase ($) in each transaction
                      Profitability = sum(Profitability), # Total profitability of each transaction 
                      Date = Date[1], # Since the date of each transaction is the same, we will get just the first date
                      WeekDay = WeekDay[1],# same logic as above
                      Hour = Hour[1],                                                                                                            OperationType = OperationType[1],# whether Sale or Return
                      Avg_Discounts = mean(Discounts),# Average discount in the transaction
                      Items = paste(unique(LineItem), collapse = ', ')) # List of unique items in each transaction

Note the number of rows and the number of columns of the new dataframe “df_Transactions”. Do they make sense?

The number of rows is 78559. This means in the data, there are 78559 unique transactions. You only created 10 variables. The number of columns is 11. How come?

To check if there are any Missing Values in your dataframe “df_Transactions”

anyNA(df_Transactions)
[1] FALSE

If you want you can save this as an RDS object for later use.

saveRDS(df_Transactions, "df_Transactions.RDS")

or as a csv file

write.csv(df_Transactions, "df_Transactions.csv", row.names = FALSE)

RDS is an R specific format. This is optimized to use very little memory.

Compare the size of the df_Transactions.RDS and df_Transactions.csv files. The size is almost 1:5.

You may read an RDS datafile using a function called ’readRDS(filename.RDS)"

Check the nature of the variables

str(df_Transactions)
tibble [78,559 x 11] (S3: tbl_df/tbl/data.frame)
 $ TransactionNumber   : Factor w/ 78559 levels "00002QD152261",..: 1 2 3 4 5 6 7 8 9 10 ...
 $ UniqueLineItemsCount: int [1:78559] 2 2 2 1 1 2 1 1 1 1 ...
 $ TotalItemsCount     : int [1:78559] 2 2 2 1 1 2 1 1 1 1 ...
 $ TransactionAmount   : num [1:78559] 21.7 24.1 30.9 15.1 17.1 ...
 $ Profitability       : num [1:78559] 195 217 279 136 154 ...
 $ Date                : Date[1:78559], format: "2014-05-13" "2015-07-14" "2014-05-14" ...
 $ WeekDay             : num [1:78559] 3 3 4 5 3 3 1 3 5 1 ...
 $ Hour                : int [1:78559] 12 15 12 20 12 15 17 11 18 17 ...
 $ OperationType       : Factor w/ 2 levels "RETURN","SALE": 2 2 2 2 2 2 2 2 2 2 ...
 $ Avg_Discounts       : num [1:78559] -0.03 -0.03 -0.03 -0.03 -0.03 -0.03 -0.03 -0.03 -0.03 -0.03 ...
 $ Items               : chr [1:78559] "Coconut and Beef Vindaloo, Chicken and Onion Kabob" "Beef and Apple Burgers, Salmon and Wheat Bran Salad" "Chicken and Onion Kabob, Beef and Broccoli Stir Fry" "Soya and Basil Salad" ...

Understand the data better by looking at the distributions of a few variables.

summary(df_Transactions$TotalItemsCount)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  1.000   1.000   1.000   1.854   2.000  22.000 

The number of items in a transcation range between 1 and 22. The average number of items in a transaction is 1.85. Median is 1. This means half of the transactions have one items only. 50% orders have only one item.

Get the first six rows in the data

head(df_Transactions)

A few questions for you to consider: - What is the average/ median Transaction Amount? - How many Transactions sell more than 1 item? - What are the number of transactions across all days in a week? See the code below

df_Transactions %>% group_by(WeekDay) %>% summarise(total_transaction = n() )

Your Task - Create data for Customer Level Analysis

Remember to create the Recency variable, you will have to calculate how many days have passed since the customer made the last purchase. FOr that, we will need a reference date. You may set the reference date as the last date in the data.

Hint - you may get difference between two dates using “difftime()” function as Prof Ron showed in Mod2. You may also just substract two days to get the duration in days.

Please get the following variables in your customer level data and save it as “df_Customers”

Customer Level Data with the following variables:

  1. TotalItemsCount - Number of total items bought
  2. AvgItemCount – Avg number of items bought
  3. Recency – difference between the last date in the data and the last date on which the customer made a transaction
  4. Frequency – Total number of transactions by a customer
  5. Monetary – Average amount of purchase
  6. Profitability – Total profitability
  7. Discounts – Total discount
  8. UniqueItems – Number of unique items bought (indicating broad taste vs narrow taste)
  9. Items – the list of unique items bought by a given customer

Customer Level Data - You can fill your code here.

str(df)
'data.frame':   145615 obs. of  30 variables:
 $ Time             : POSIXct, format: "2015-02-19 15:30:00" "2015-08-04 11:50:00" "2015-04-29 19:02:00" ...
 $ OperationType    : Factor w/ 2 levels "RETURN","SALE": 2 2 2 2 2 2 2 2 2 2 ...
 $ BarCode          : Factor w/ 1 level "*": 1 1 1 1 1 1 1 1 1 1 ...
 $ CashierName      : Factor w/ 53 levels "Aimee Port","Alberta Lynch",..: 22 11 30 12 7 29 11 42 44 29 ...
 $ LineItem         : Factor w/ 28 levels "Aubergine and Chickpea Vindaloo",..: 20 2 28 24 7 24 2 21 23 28 ...
 $ Department       : Factor w/ 8 levels "Beverage","Catering",..: 3 3 8 3 6 3 3 8 8 8 ...
 $ Category         : Factor w/ 20 levels "Aubergine and Chickpea Vindaloo",..: 14 3 20 19 2 19 3 15 18 20 ...
 $ CardholderName   : Factor w/ 48790 levels "AABID FIENE",..: 38867 41457 8593 22456 23959 28233 2516 10529 36382 44814 ...
 $ RegisterName     : Factor w/ 12 levels "FT136","FT137",..: 8 8 8 8 10 8 10 10 6 10 ...
 $ StoreNumber      : Factor w/ 29 levels "AZ23501251","AZ23501258",..: 1 2 19 5 11 5 2 14 15 19 ...
 $ TransactionNumber: Factor w/ 78559 levels "00002QD152261",..: 44782 9936 50563 40892 68609 37351 4676 44674 76508 61003 ...
 $ CustomerCode     : Factor w/ 6022 levels "C00114815PM",..: 5449 5449 5449 5449 5449 5449 5449 5449 5449 5449 ...
 $ Cost             : num  0.11 0.11 0.11 0.11 0.11 0.11 0.11 0.11 0.11 0.11 ...
 $ Price            : num  7.84 14.35 4.5 14.68 12.02 ...
 $ Quantity         : int  1 1 1 1 1 1 1 1 1 1 ...
 $ Modifiers        : num  0.01 0.01 2.36 0.01 1.08 0.01 0.01 2.36 0.01 0.01 ...
 $ Subtotal         : num  7.85 14.36 6.86 14.69 13.1 ...
 $ Discounts        : num  -0.03 -0.03 -0.03 -0.03 -0.03 -0.03 -0.03 -0.03 -0.03 -0.03 ...
 $ NetTotal         : num  7.88 14.39 6.89 14.72 13.13 ...
 $ Tax              : num  0.62 1.13 0.54 1.16 1.04 1.16 2.27 0.41 0.22 0.35 ...
 $ TotalDue         : num  8.5 15.52 7.43 15.88 14.17 ...
 $ Year             : num  2015 2015 2015 2014 2016 ...
 $ Month            : num  2 8 4 11 7 2 3 1 4 2 ...
 $ Day              : int  19 4 29 4 19 7 15 19 23 27 ...
 $ Hour             : int  15 11 19 17 17 12 13 13 16 16 ...
 $ WeekDay          : num  5 3 4 3 3 7 3 3 4 2 ...
 $ Date             : Date, format: "2015-02-19" "2015-08-04" "2015-04-29" ...
 $ Week             : num  8 31 17 44 29 6 11 3 17 9 ...
 $ Quarter          : int  1 3 2 4 3 1 1 1 2 1 ...
 $ Profitability    : num  70.6 129.8 61.6 132.8 118.4 ...
df_Customers <- df %>% group_by(CardholderName) %>% summarise(TotalItemsCount = sum(Quantity), #Create df_Customers dataframe grouped by Cardholder Name, with new columns as follows... Number of total items bought
        AvgItemCount = mean(Quantity), #Avg number of items bought
        Recency = difftime(min(Time), max(Time)), #Difference between the last date in the data and the last date on which the customer made a transaction
        Frequency = sum(n_distinct(as.numeric(TransactionNumber))), #Total number of transactions by a customer 
        Monetary = mean(NetTotal), #Average amount of purchase
        Profitability = sum(Profitability), #Total profitability 
        Discounts = sum(Discounts), #Total discount
        UniqueItems = n_distinct(LineItem), #Number of unique items bought (indicating broad taste vs narrow taste)
        Items = paste(unique(LineItem), collapse = ', ')
        ) #The list of unique items bought by a given customer 

Show first six rows of df_Customers

head(df_Customers)

Product Strategy/ Management - Department and Category

Christina also mentioned that she can perform some analysis to help Sameera take decisions for product stratgey also. Essentially figuring out which products sell well, have higher profitability, can be co-promoted, cross-sold, etc.

This can be done at various levels such as Department, Catgeory or even LineItem.

I am creating Depatment Level and LineItem Level Data and your task is to create the Category Level Data

Department Level

df_Department <- df %>% group_by(Department) %>% summarise( 
                        ItemsCount = n_distinct(LineItem), # n_distinct gets the count of 
                        #distinct/ unique values
                        TransactionsCount = n_distinct(TransactionNumber),
                        TotalSales = sum(NetTotal), 
                        CustomerCount = n_distinct(CustomerCode),
                        Profitability = mean(Profitability),
                        Discounts = mean(Discounts),
                        Items = paste(unique(LineItem), collapse = ', '))

Check the number of rows and columns in “df_Department” using “dim” function. This gives the dimensions of the dataframe.

dim(df_Department)
[1] 8 8

8 rows means there are 8 unique departments and now eahc row corresponds to one department. Again, we created 7 columns, the dataframe has got 8?

A few questions for you to consider: - Which Department pulls the highest number of customers? - Which Department has highest Sales? - Which Department sells most on discount? - Which Department has highest number of transactions?

What if we were interested in assesing each LInItem for Porfitability or Popularity or Ability to pull diverse customers

LineItem Management

df_LineItem  <-  df %>% group_by(LineItem) %>% summarise( 
                        TransactionsCount = n_distinct(TransactionNumber),
                        TotalSales = sum(NetTotal), 
                        CustomerCount = n_distinct(CustomerCode),
                        Profitability = mean(Profitability),
                        Discounts = mean(Discounts))

A few questions for you to consider: - Which LineItem pulls the highest number of customers? - Which LineItem has highest Sales? - Which Lineitem sells most on discount? This is tricky, how will you get it? - Which Lineitem has highest number of transactions?

Your Task - Create the following variables at the Catgeory Level Data

  1. UniqueItemsCount - Number of unique items in the category
  2. TransactionsCount - Number of transactions in the category
  3. TotalSales - Total Sales in the category
  4. CustomerCount - Total number of customers in this category
  5. Profitability - Average profitability
  6. Discounts - Average discount
  7. Items - the list of unique items in the category

Write your code below for creating data at Category level

df_Category  <-  df %>% group_by(Category) %>% summarise( #Creating new df_Category dataframe grouped by Category with the following columns: 
        UniqueItemsCount = n_distinct(LineItem), #Number of unique items in the category
        TransactionsCount = n_distinct(TransactionNumber), #Number of transactions in the category
        TotalSales = sum(NetTotal), #Total Sales in the category 
        CustomerCount = n_distinct(CardholderName), #Total number of customers in this category 
        Profitability = mean(Profitability), #Average profitability 
        Discounts = mean(Discounts), #Average discount
        Items = paste(unique(LineItem), collapse = ', ') #The list of unique items in the category 
)

Show first six rows of df_Category

head(df_Category)

TimeSeries Data

I will create Daily TimeSeries. Your task is to create data agrregated at monthly level.

Daily

df_day <- df %>% group_by(Day,Month,Year) %>% summarise(TransactionsCount = n_distinct(TransactionNumber),
                   TotalSales = sum(NetTotal), 
                   CustomerCount = n_distinct(CustomerCode),
                   Profitability = mean(Profitability),
                   Discounts = mean(Discounts))%>%
                   mutate(Date = make_date(Year, Month, Day))# this is to get a column "Date" so that we can use it on X-axis to create a Line Chart

# I will show a line chart below. 
                   

There are 951 rows and 9 columns in df_day data.

A few questions you may consider: How have sales increased/ decreased over time? How has number of orders/ transcations increased/ decreased over time? How has profitability increased/ decreased over time?

Write your code below to create monthly data with the following variables:

  1. TransactionsCount - Number of transactions in each month
  2. TotalSales – Total Sales in each month
  3. CustomerCount – Total Customers in each month
  4. LineItemCount – Total LineItems sold each month
  5. UniqueLineItemCount - Number of Unique Items Sold each month
  6. CategoryCount – Total Category Count each month

Monthly

df_month <- df %>% group_by(Month) %>% summarise( #Creating df_month dataframe grouped by Month with the following columns: 
        TransactionsCount = n_distinct(TransactionNumber), #Number of transactions in each month 
        TotalSales = sum(NetTotal), #Total Sales in each month 
        CustomerCount = n_distinct(CustomerCode), #Total Customers in each month 
        LineItemCount = sum(as.numeric(LineItem)),  #Total LineItems sold each month 
        UniqueLineItemCount = n_distinct(LineItem), #Number of Unique Items Sold each month 
        CategoryCount = n_distinct(Category), #Total Category Count each month
)

Get first six rows of df_month

head(df_month)
LS0tDQp0aXRsZTogIm9jb25ubzI1X01vZF8zLlJNRCINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KYXV0aG9yIjogIlJhbmFpdCBPQ29ubm9yIiANCi0tLQ0KSW5zdGFsbCBhIHVzZWZ1bCBwYWNrYWdlIGNhbGxlZCAicGFjbWFuIi4gVGhpcyBpcyBhIG1hbmFnZXIgdG8gbWFuYWdlIHBhY2thZ2UuIExpa2UgeW91ciBIUiBwZXJvc29ubmVsLCBhbiBlbXBsb3llZSB0byBtYW5hZ2Ugb3RoZXIgZW1wbG95ZWVzLiANCg0Kb25jZSB5b3UgaGF2ZSBwYWNtYW4gaW5zdGFsbGVkIGFuZCBsb2FkZWQuIFlvdSBjYW4gaW5zdGFsbC9sb2FkIGFsbCBvdGhlciBwYWNrYWdlcyB1c2luZyBhIGZ1bmN0aW9uICJwX2xvYWQiIGZyb20gInBhY21hbiIuIA0KDQpGZWVsIGZyZWUgdG8gdXNlIG90aGVyIHdheXMgb2YgaW5zdGFsbGluZyBhbmQgbG9hZGluZyBwYWNrYWdlcy4gDQoNCmBgYHtyfQ0KaW5zdGFsbC5wYWNrYWdlcygicGFjbWFuIikgDQpsaWJyYXJ5KHBhY21hbikNCiNJIHNwb2tlIHRvIFByb2Zlc3NvciBSb24gaW4gNS80IDktMTFhbSBDVCBvZmZpY2UgaG91cnMgYWJvdXQgdGhlIFdBUk5JTkcgbWVzc2FnZSBiZWxvdyBhbmQgc2hvd2VkIGhpbSB0aGF0IFJ0b29scyBpcyBhbHJlYWR5IGluc3RhbGxlZCBvbiBteSBjb21wdXRlciAoYnkgZm9sbG93aW5nIHRoZSBsaW5rIGJlbG93KS4gSSBkb24ndCBrbm93IGhvdyB0byBvdGhlcndpc2UgbWFrZSB0aGlzIHdhcm5pbmcgbWVzc2FnZSBnbyBhd2F5LiMNCmBgYA0KDQpJbnN0YWxsLyBsb2FkIHJlcXVpcmVkIHBhY2thZ2VzLg0KYGBge3J9DQpwX2xvYWQoZHBseXIsIGdncGxvdDIsIHRpZHlyLCBzdHJpbmdyLCBsdWJyaWRhdGUpDQpgYGANCg0KUmVhZCB0aGUgZGF0YSBmaWxlIGFzICJkZiINCmBgYHtyfQ0KZGYgPC0gcmVhZC5jc3YoIkxpdmVfU2Vzc2lvbl9Nb2RfMy5jc3YiKSMgbWFrZSBzdXJlIHRvIHB1dCB0aGUgY3N2IGZpbGUgaW4geW91ciB3b3JraW5nIGRpcmVjdG9yeQ0KYGBgDQoNCg0KUXVpY2tseSBpbnNwZWN0IHRoZSBzdHJ1Y3R1cmUgb2YgdGhlIHZhcmlvdXMgdmFyaWFibGVzDQpgYGB7cn0NCnN0cihkZikNCmBgYA0KDQpQbGVhc2UgcmV2ZWlldyB0aGUgbmF0dXJlIG9mIHRoZSB2YXJpYWJsZXMgYW5kIG1ha2Ugc3VyZSB0aGV5IGFsbCBsb29rIGdvb2QuIA0KRm9yIGV4YW1wbGUsIGlmIHRoZSB2YXJpYWJsZXMgcmVsYXRlZCB0byBkYXRlLCBzdWNoIGFzICJUaW1lIiBpbiBvdXIgZGF0YXNldCwgaXMgc2F2ZWQgYXMgYSBmYWN0b3IsIHdlIHdvbid0IGJlIGFibGUgdG8gdXNlIGl0IG1lYW5pbmdmdWxseS4gVGh1cywgd2UgbXVzdCBjb252ZXJ0IGludG8gYSBkYXRlIG9iamVjdC4gUHJvZmVzc29yIFJvbiBjb3ZlcmVkIHRoaXMgaWRlYSBhdCBsZW5ndGggaW4gTGl2ZSBTZXNzaW9uIDIuIA0KDQpPbmNlLCB3ZSBjb252ZXJ0ICJUaW1lIiBhcyBhIGRhdGUgb2JqZWN0LCB3ZSBjYW4gZXh0cmFjdCB0aGUgdmFyaWFibGVzIHN1Y2ggYXMgWWVhciwgTW9udGgsIERheSwgV2Vla0RheSwgSG91ciBmb3IgYW5hbHl6aW5nIHRoZSB0cmVuZCBvdmVyIHRpbWUuIA0KDQpGaXJzdCBjaGVjayBhIGZldyB2YWx1ZXMgaW4gdGhlIGRmJFRpbWUNCmBgYHtyfQ0KaGVhZChkZiRUaW1lLCAzKQ0KYGBgDQpZb3Ugd2lsbCBub3RpY2UgYSBwcm9ibGVtIGluIHRoZSB3YXkgdGhlIFRpbWUgdmFyaWFibGUgaXMgc3RvcmVkIGN1cnJlbnRseS4gDQoxKSAxNDQ2NjAgbGV2ZWxzIC0gcHJlc2VuY2Ugb2YgbGV2ZWxzIG1lYW5zIGl0J3Mgc3RvcmVkIGFzIGZhY3RvcnMuDQoNCkxldCdzIGNoZWNrIHRoZSBjbGFzcyBvZiBkZiRUaW1lDQpgYGB7cn0NCmNsYXNzKGRmJFRpbWUpDQpgYGANClRoaXMgd29uJ3QgaGVscC4gV2UgbmVlZCB0byBleHRyYWN0IHZhcmlhYmxlcyBzdWNoIGFzICJkYXkiLCAibW9udGgiLCAieWVhciIgZXRjLiBmcm9tIHRoaXMgZm9yIGFuYWx5c2lzLiBBbmQsIHdlIGNhbid0IGRvIHRoYXQgb24gYSBmYWN0b3IgdmFyaWFibGUuIA0KDQoNCkxldCdzIHNldCB0aGlzIGFzIGEgdGltZSB2YXJpYWJsZSB1c2luZyB0aGUgInN0cnB0aW1lIiBmdW5jdGlvbiBmcm9tIGJhc2UgUiBwYWNrYWdlLiBMdWJyaWRhdGUgYWxzbyBoYXMgbWFueSB1c2VmdWwgZnVuY3Rpb25zLg0KDQpgYGB7cn0NCmRmJFRpbWUgPC0gbHVicmlkYXRlOjp5bWRfaG1zKGRmJFRpbWUpDQpgYGANCg0KDQpMZXQncyBjaGVjayBhIGZldyB2YWx1ZXMsIG5vdw0KYGBge3J9DQpoZWFkKGRmJFRpbWUsIDMpDQpgYGANCk5vdywgd2Ugc2VlIHRoZW0gaW4gYSBwcm9wZXIgZGF0ZSBmb3JtYXQgd2l0aCBldmVyeXRoaW5nIG5pY2VseSBsaW5lZCB1cA0KDQoNCldlIGFyZSByZWFkeSB0byBleHRyYWN0IHRoZSB2YXJpYWJsZXMuDQoNCkxldCdzIGV4dHJhY3QgdGhlIHZhcmlhYmxlcyBzdWNoIGFzIHllYXIsIG1vbnRoLCB3ZWVrZGF5LCBkYXkgYW5kIGhvdXINCg0KV2Ugd2lsbCB1c2UgdGhlIGZ1bmN0aW9ucyBmcm9tIHRoZSBsdWJyaWRhdGUgcGFja2FnZSBmb3IgdGhpcyBwdXJwb3NlLiANCmBgYHtyfQ0KZGYgPC0gZGYgJT4lIG11dGF0ZShZZWFyID0geWVhcihUaW1lKSwgTW9udGggPSBtb250aChUaW1lKSwgRGF5ID0gZGF5KFRpbWUpLCANCiAgICAgICAgICAgICAgICAgICAgSG91ciA9IGhvdXIoVGltZSksIFdlZWtEYXkgPSB3ZGF5KFRpbWUpLCBEYXRlID0gZGF0ZShUaW1lKSwNCiAgICAgICAgICAgICAgICAgICAgV2VlayA9IHdlZWsoVGltZSksIFF1YXJ0ZXIgPSBxdWFydGVyKFRpbWUpKQ0KDQojIEkgaG9wZSB5b3UgcmVtZW1iZXIgdGhlICJtdXRhdGUiIGZ1bmN0aW9uIGZyb20gb3VyIE1vZDMgb24gQ291cnNlcmEuIFRoaXMgaXMgdG8gY3JlYXRlIG5ldyBjb2x1bW5zLiBQbGVhc2UgcGxheSB3aXRoIHRoaXMgY29kZSBieSBjaGFuZ2luZyBuYW1lcyAiWWVhciIgYW5kICJNb250aCIgdG8gIlllYXJfYWJjZCIgYW5kICJNb250aF9hYmNkIiBzbyB0aGF0IHlvdSB1bmRlcnN0YW5kIHdoaWNoIHBhcnQgb2YgdGhlIGNvZGUgaXMgYSBmdW5jdGlvbiBhbmQgd2hpY2ggcGFydCBpcyB1c2VyIGRlZmluZWQgbmFtZXMuICJ5ZWFyIiwgIm1vbnRoIiwgImRheSIsICJob3VyIi4uLiBhcmUgYWxsIGZ1bmN0aW9ucy4gDQojIFRyeSBwbGF5aW5nIHdpdGggdGhlbSB1c2luZyAiZGYgJT4lIG11dGF0ZShZZWFyID0geWVhcl9hYmNkKFRpbWUpIiBhbmQgUiB3aWxsIHdyZWFrIGhhdm9jIG9uIHlvdXIgdGhlIG5vdGVib29rLiBJZiB5b3UgZG8gImRmICU+JSBtdXRhdGUoWWVhcl9hYmNkID0geWVhcihUaW1lKSIsIFIgd2lsbCBiZSBraW5kIHRvIHJ1biB0aGUgY29kZSBuaWNlbHkuIA0KYGBgDQoNCldlIGFkZGVkIDggbmV3IHZhcmlhYmxlcyBpbiB0aGUgY2h1bmsgYWJvdmUuIE5vdyB0aGUgbnVtYmVyIG9mIGNvbHVtbnMgaGFzIGluY3JlYXNlZCB0byAyOSBmcm9tIDIxLiBDaGVjayBpbiB0aGUgIkVudmlyb25tZW50IiBvbiB0aGUgdG9wIHJpZ2h0IGNvcm5lciBvZiB5b3VyIHNjcmVlbi4gDQoNCg0KYGBge3J9DQpjb2xTdW1zKGlzLm5hKGRmKSkNCmBgYA0KVGhlcmUgc2VlbXMgdG8gYmUgbm8gbWlzc2luZyB2YWx1ZXMuIFNvIHdlIGFyZSBnb29kIGZvciBub3cuIA0KDQoNCg0KTGV0J3MgcXVpY2tseSBjaGVjayB0aGUgZmlyc3QgYW5kIHRoZSBsYXN0IGRhdGUgaW4gdGhlIGRhdGEgDQpgYGB7cn0NCm1pbihkZiREYXRlLCBuYS5ybSA9IFRSVUUpDQptYXgoZGYkRGF0ZSwgbmEucm0gPSBUUlVFKQ0KYGBgDQpXZSBzZWUgdGhhdCB0aGUgZmlyc3QgbWVudGlvbmVkIGRhdGUgaW4gdGhlIGRhdGEgaXMgMm5kLUphbi0yMDE0IGFuZCAzcmQtQXByaWwtMjAxNy4gDQoNCkdlbmVyYXRlIEZlYXR1cmUgLSBQcm9maXRhYmlsaXR5IA0KDQpXZSBjcmVhdGUgYSB2YXJpYWJsZSBjYWxsZWQgIlByb2ZpdGFiaWxpdHkiIGFzIChOZXRUb3RhbCAtIChRdWFudGl0eSBYIENvc3QpKSoxMDAvIChRdWFudGl0eSBYIENvc3QpLiANCg0KUGxlYXNlIG5vdGUgdGhhdCB0aGUgZmlndXJlcyBpbiB0aGUgZGF0YSBzZWVtIHRvIGJlIG9kZC4gQnV0IHdlIHdpbGwganVzdCBpZ25vcmUgdGhhdCBwYXJ0IHJpZ2h0IG5vdy4gDQpgYGB7cn0NCmRmIDwtIGRmICU+JSBtdXRhdGUoUHJvZml0YWJpbGl0eSA9IChOZXRUb3RhbCAtIChRdWFudGl0eSpDb3N0KSkvIChRdWFudGl0eSpDb3N0KSkNCg0KYGBgDQoNClRoZXJlIGlzIHBvc3NpYmlsaXR5IHRoYXQgaWYgdGhlIHF1YW50aXR5IG9yIGNvc3QgaXMgemVybyBpbiBvbmUgb2YgdGhlIHJvd3MsIHRoZSBwcm9maXRhYmlsaXR5IG1heSB0dXJuIG91dCB0byBiZSB1bmRlZmluZWQgdmFsdWUuIFdlIGNhbiBjaGVjayBpZiBkbyBzdWZmZXIgZnJvbSB0aGF0IHByb2JsZW0gaGVyZS4gDQpgYGB7cn0NCnN1bSghaXMuZmluaXRlKGRmJFByb2ZpdGFiaWxpdHkpKQ0KYGBgDQpObyBiYWQgdmFsdWVzIGZvciBQcm9maXRhYmlsaXR5LiBUaGVyZSBtYXkgYmUgYSBuZWdhdGl2ZSB2YWx1ZXMgYmFzZWQgb24gdGhlIHJldHVybiBwcm9kdWN0cy4gQnV0IHdlIHdvbid0IGJvdGhlciByaWdodCBub3cuIA0KDQoqKioqKioqKioqKioqKipCdXNpbmVzcyBEZWNpc2lvbiwgQW5hbHlzaXMsIGFuZCB0aGUgRGF0YSoqKioqKioqKioqKioqKioqKioqKg0KDQojIEN1c3RvbWVyIEFuYWx5c2lzDQpUbyBwZXJmb3JtIGN1c3RvbWVyIGxldmVsIGFuYWx5c2lzLCB3ZSBuZWVkIHRvIGJyaW5nIG91ciBkYXRhIHRvIHRoZSBjdXN0b21lciBsZXZlbC4gVGhhdCBtZWFucyBlYWNoIHJvdyBtdXN0IGNvcnJlc3BvbmQgdG8gYSB1bmlxdWUgY3VzdG9tZXIuDQoNClRvIHBlcmZvcm0gY3VzdG9tZXIgbGV2ZWwgYW5hbHlzaXMsIG1hcmtldGluZyBmb2xrcyB1c2Ugc29tZXRoaW5nIGNhbGxlZCBSRk0gYW5hbHlzaXMuIFdoZXJlLCBSIHN0YW5kcyBmb3IgUmVjZW5jeSwgRiBzdGFuZHMgZm9yIEZyZXF1ZW5jeSBhbmQgTSBTdGFuZHMgZm9yIE1vbmV0YXJ5Lg0KDQpDaGVjayB0aGlzIFdpa2kgZW50cnkgZm9yIGJhc2ljIGlkZWEgLSBodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9SRk1fKG1hcmtldF9yZXNlYXJjaCkuIEl0J3MgYW4gaW50dXRpdmUgaWRlYS4gDQoNCg0KIyMgRmVhdHVyZSBHZW5lcmF0aW9uIGZvciBSRk0gQW5hbHlzaXMgDQpDaHJpc3RpbmEgd2FudHMgdG8gcGVyZm9ybSBSRk0gYW5hbHlzaXMgdG8gZmluZCBvdXQgY3VzdG9tZXIgc2VnbWVudHMuIFRodXMsIHNoZSBtdXN0IGNhbGN1bGF0ZSB0aGUgZm9sbG93aW5nIHZhcmlhYmxlcyBmb3IgZWFjaCBjdXN0b21lcjoNClJlY2VuY3kgLSBIb3cgcmVjZW50bHkgaGFzIHRoZSBjdXN0b21lciBtYWRlIGEgcHVyY2hhc2U/DQpGcmVxdWVuY3kgLSBIb3cgZnJlcXVlbnRseSBoYXMgdGhlIGN1c3RvbWVyIG1hZGUgcHVyY2hhc2VzIGluIHRoZSBwYXN0Pw0KTW9uZXRhcnkgVmFsdWUgLSBIb3cgbXVjaCBhdmVyYWdlIHB1cmNoYXNlICgkKSBoYXMgdGhlIGN1c3RvbWVyIG1hZGUgaW4gdGhlIHBhc3Q/DQoNCk5vdGUgLSB0aGVyZSBhcmUgdmFyaW91cyBhcHByb2FjaGVzIHRvIGRlZmluZSBNb25ldGFyeSAtIHN1Y2ggYXMgbWVhbl9wdXJjaGFzZSB2cyB0b3RhbF9wdXJjaGFzZS4gV2Ugd2lsbCBzdGljayB0byBtZWFuX3BydWNoYXNlLiANCg0KV2UgbWF5IGFsc28gd2FudCB0byBrZWVwIG90aGVyIHZhcmlhYmxlcyBmb3IgUHJvZml0YWJpbGl0eSBhbmFseXNpcy4gDQoNCg0KIyMgRnJvbSBMaW5lSXRlbSB3aXRoaW4gYSBUcmFuc2FjdGlvbiB0byBDdXN0b21lciBMZXZlbCBEYXRhDQpZb3UgbmVlZCB0byBwcmVwYXJlIGEgY3VzdG9tZXIgbGV2ZWwgZGF0YSB0byBoZWxwIENocmlzdGluYS4gQ3VycmVudGx5LCB0aGUgdW5pdCBvZiBvYnNlcnZhdGlvbiBpcyBhIHVuaXF1ZSBMaW5lSXRlbSB3aXRoaW4gYSBUcmFuc2FjdGlvbi4gWW91IHdpbGwgaGF2ZSB0byBhZ2dyZWdhdGUgdGhlIGRhdGEgYXQgdGhlIEN1c3RvbWVyIExldmVsIGZvciBDdXN0b21lciBMZXZlbCBBbmFseXNpcy4gDQoNClRoaXMgaXMgYSBxdWVzdGlvbiBmb3IgeW91IGZvciBNb2QgMyBBc3NpZ25tZW50LiANCg0KVG8gaGVscCB5b3UsIEkgYW0gc2hvd2luZyB5b3UgdGhlIFRyYW5zYWN0aW9uIGxldmVsIGFnZ3JlZ2F0aW9uLiBZb3UgbWF5IHVzZSB0aGUgc2FtZSBjb2RlICh3aXRoIGFwcHJvcHJpYXRlIHR3ZWFraW5nKSBmb3IgY3JlYXRpbmcgY3VzdG9tZXIgbGV2ZWwgZGF0YS4gIA0KDQoNCiMjIENyZWF0aW5nIFRyYW5zYWN0aW9uIExldmVsIERhdGENCg0KUmVtZW1iZXIsIGZvciBhZ2dyZWdhdGlvbiwgd2UgdXNlIGdyb3VwX2J5IGZ1bmN0aW9uIGFuZCB0aGUgc3VtbWFyaXNlIGZ1bmN0aW9uIGZyb20gImRwbHlyIiBwYWNrYWdlLg0KDQoNCkkgd2lsbCBjYWxsIHRoZSB0cmFuc2FjdGlvbiBsZXZlbCBkYXRhIGFzIGRmX1RyYW5zYWN0aW9ucy4gDQoNCk5vdGUgLSBUaGlzIGNvZGUgbWF5IHRha2UgYSBmZXcgbWludXRlcy4gSGF2ZSBmYWl0aCBpbiBHT0QuIA0KYGBge3J9DQpkZl9UcmFuc2FjdGlvbnMgPC0gZGYgJT4lIGdyb3VwX2J5KFRyYW5zYWN0aW9uTnVtYmVyKSAlPiUgc3VtbWFyaXNlKA0KICAgICAgICAgICAgICAgICAgICAgIFVuaXF1ZUxpbmVJdGVtc0NvdW50ID0gbl9kaXN0aW5jdChMaW5lSXRlbSksICMgRm9yIHRoZSBjb3VudCBvZiBVTklRVUUgTGluZUl0ZW1zIGluIGVhY2ggdHJhbnNhY3Rpb24NCiAgICAgICAgICAgICAgICAgICAgICBUb3RhbEl0ZW1zQ291bnQgPSBzdW0oUXVhbnRpdHkpLCAjIEZvciB0aGUgY291bnQgb2YgTGluZUl0ZW1zIGluIGVhY2ggdHJhbnNhY3Rpb24NCiAgICAgICAgICAgICAgICAgICAgICBUcmFuc2FjdGlvbkFtb3VudCA9IHN1bShOZXRUb3RhbCksIyBUb3RhbCBQdXJjaGFzZSAoJCkgaW4gZWFjaCB0cmFuc2FjdGlvbg0KICAgICAgICAgICAgICAgICAgICAgIFByb2ZpdGFiaWxpdHkgPSBzdW0oUHJvZml0YWJpbGl0eSksICMgVG90YWwgcHJvZml0YWJpbGl0eSBvZiBlYWNoIHRyYW5zYWN0aW9uIA0KICAgICAgICAgICAgICAgICAgICAgIERhdGUgPSBEYXRlWzFdLCAjIFNpbmNlIHRoZSBkYXRlIG9mIGVhY2ggdHJhbnNhY3Rpb24gaXMgdGhlIHNhbWUsIHdlIHdpbGwgZ2V0IGp1c3QgdGhlIGZpcnN0IGRhdGUNCiAgICAgICAgICAgICAgICAgICAgICBXZWVrRGF5ID0gV2Vla0RheVsxXSwjIHNhbWUgbG9naWMgYXMgYWJvdmUNCiAgICAgICAgICAgICAgICAgICAgICBIb3VyID0gSG91clsxXSwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBPcGVyYXRpb25UeXBlID0gT3BlcmF0aW9uVHlwZVsxXSwjIHdoZXRoZXIgU2FsZSBvciBSZXR1cm4NCiAgICAgICAgICAgICAgICAgICAgICBBdmdfRGlzY291bnRzID0gbWVhbihEaXNjb3VudHMpLCMgQXZlcmFnZSBkaXNjb3VudCBpbiB0aGUgdHJhbnNhY3Rpb24NCiAgICAgICAgICAgICAgICAgICAgICBJdGVtcyA9IHBhc3RlKHVuaXF1ZShMaW5lSXRlbSksIGNvbGxhcHNlID0gJywgJykpICMgTGlzdCBvZiB1bmlxdWUgaXRlbXMgaW4gZWFjaCB0cmFuc2FjdGlvbg0KYGBgDQoNCk5vdGUgdGhlIG51bWJlciBvZiByb3dzIGFuZCB0aGUgbnVtYmVyIG9mIGNvbHVtbnMgb2YgdGhlIG5ldyBkYXRhZnJhbWUgImRmX1RyYW5zYWN0aW9ucyIuIERvIHRoZXkgbWFrZSBzZW5zZT8gDQoNClRoZSBudW1iZXIgb2Ygcm93cyBpcyA3ODU1OS4gVGhpcyBtZWFucyBpbiB0aGUgZGF0YSwgdGhlcmUgYXJlIDc4NTU5IHVuaXF1ZSB0cmFuc2FjdGlvbnMuIA0KWW91IG9ubHkgY3JlYXRlZCAxMCB2YXJpYWJsZXMuIFRoZSBudW1iZXIgb2YgY29sdW1ucyBpcyAxMS4gSG93IGNvbWU/DQoNCg0KVG8gY2hlY2sgaWYgdGhlcmUgYXJlIGFueSBNaXNzaW5nIFZhbHVlcyBpbiB5b3VyIGRhdGFmcmFtZSAiZGZfVHJhbnNhY3Rpb25zIg0KYGBge3J9DQphbnlOQShkZl9UcmFuc2FjdGlvbnMpDQpgYGANCg0KSWYgeW91IHdhbnQgeW91IGNhbiBzYXZlIHRoaXMgYXMgYW4gUkRTIG9iamVjdCBmb3IgbGF0ZXIgdXNlLiANCmBgYHtyfQ0Kc2F2ZVJEUyhkZl9UcmFuc2FjdGlvbnMsICJkZl9UcmFuc2FjdGlvbnMuUkRTIikNCmBgYA0KDQpvciBhcyBhIGNzdiBmaWxlDQpgYGB7cn0NCndyaXRlLmNzdihkZl9UcmFuc2FjdGlvbnMsICJkZl9UcmFuc2FjdGlvbnMuY3N2Iiwgcm93Lm5hbWVzID0gRkFMU0UpDQpgYGANCg0KUkRTIGlzIGFuIFIgc3BlY2lmaWMgZm9ybWF0LiBUaGlzIGlzIG9wdGltaXplZCB0byB1c2UgdmVyeSBsaXR0bGUgbWVtb3J5LiANCg0KQ29tcGFyZSB0aGUgc2l6ZSBvZiB0aGUgZGZfVHJhbnNhY3Rpb25zLlJEUyBhbmQgZGZfVHJhbnNhY3Rpb25zLmNzdiBmaWxlcy4gVGhlIHNpemUgaXMgYWxtb3N0IDE6NS4gDQoNCllvdSBtYXkgcmVhZCBhbiBSRFMgZGF0YWZpbGUgdXNpbmcgYSBmdW5jdGlvbiBjYWxsZWQgJ3JlYWRSRFMoZmlsZW5hbWUuUkRTKSINCg0KDQoNCg0KQ2hlY2sgdGhlIG5hdHVyZSBvZiB0aGUgdmFyaWFibGVzDQpgYGB7cn0NCnN0cihkZl9UcmFuc2FjdGlvbnMpDQpgYGANCg0KVW5kZXJzdGFuZCB0aGUgZGF0YSBiZXR0ZXIgYnkgbG9va2luZyBhdCB0aGUgZGlzdHJpYnV0aW9ucyBvZiBhIGZldyB2YXJpYWJsZXMuDQpgYGB7cn0NCnN1bW1hcnkoZGZfVHJhbnNhY3Rpb25zJFRvdGFsSXRlbXNDb3VudCkNCmBgYA0KVGhlIG51bWJlciBvZiBpdGVtcyBpbiBhIHRyYW5zY2F0aW9uIHJhbmdlIGJldHdlZW4gMSBhbmQgMjIuIFRoZSBhdmVyYWdlIG51bWJlciBvZiBpdGVtcyBpbiBhIHRyYW5zYWN0aW9uIGlzIDEuODUuIE1lZGlhbiBpcyAxLiBUaGlzIG1lYW5zIGhhbGYgb2YgdGhlIHRyYW5zYWN0aW9ucyBoYXZlIG9uZSBpdGVtcyBvbmx5LiA1MCUgb3JkZXJzIGhhdmUgb25seSBvbmUgaXRlbS4gDQoNCg0KDQoNCkdldCB0aGUgZmlyc3Qgc2l4IHJvd3MgaW4gdGhlIGRhdGENCmBgYHtyfQ0KaGVhZChkZl9UcmFuc2FjdGlvbnMpDQpgYGANCg0KQSBmZXcgcXVlc3Rpb25zIGZvciB5b3UgdG8gY29uc2lkZXI6DQogLSBXaGF0IGlzIHRoZSBhdmVyYWdlLyBtZWRpYW4gVHJhbnNhY3Rpb24gQW1vdW50Pw0KIC0gSG93IG1hbnkgVHJhbnNhY3Rpb25zIHNlbGwgbW9yZSB0aGFuIDEgaXRlbT8NCiAtIFdoYXQgYXJlIHRoZSBudW1iZXIgb2YgdHJhbnNhY3Rpb25zIGFjcm9zcyBhbGwgZGF5cyBpbiBhIHdlZWs/IFNlZSB0aGUgY29kZSBiZWxvdw0KYGBge3J9DQpkZl9UcmFuc2FjdGlvbnMgJT4lIGdyb3VwX2J5KFdlZWtEYXkpICU+JSBzdW1tYXJpc2UodG90YWxfdHJhbnNhY3Rpb24gPSBuKCkgKQ0KYGBgDQogDQogDQogDQogDQojIFlvdXIgVGFzayAtIENyZWF0ZSBkYXRhIGZvciBDdXN0b21lciBMZXZlbCBBbmFseXNpcyANCg0KUmVtZW1iZXIgdG8gY3JlYXRlIHRoZSBSZWNlbmN5IHZhcmlhYmxlLCB5b3Ugd2lsbCBoYXZlIHRvIGNhbGN1bGF0ZSBob3cgbWFueSBkYXlzIGhhdmUgcGFzc2VkIHNpbmNlIHRoZSBjdXN0b21lciBtYWRlIHRoZSBsYXN0IHB1cmNoYXNlLiBGT3IgdGhhdCwgd2Ugd2lsbCBuZWVkIGEgcmVmZXJlbmNlIGRhdGUuIFlvdSBtYXkgc2V0IHRoZSByZWZlcmVuY2UgZGF0ZSBhcyB0aGUgbGFzdCBkYXRlIGluIHRoZSBkYXRhLiANCg0KSGludCAtIHlvdSBtYXkgZ2V0IGRpZmZlcmVuY2UgYmV0d2VlbiB0d28gZGF0ZXMgdXNpbmcgImRpZmZ0aW1lKCkiIGZ1bmN0aW9uIGFzIFByb2YgUm9uIHNob3dlZCBpbiBNb2QyLiBZb3UgbWF5IGFsc28ganVzdCBzdWJzdHJhY3QgdHdvIGRheXMgdG8gZ2V0IHRoZSBkdXJhdGlvbiBpbiBkYXlzLiANCg0KUGxlYXNlIGdldCB0aGUgZm9sbG93aW5nIHZhcmlhYmxlcyBpbiB5b3VyIGN1c3RvbWVyIGxldmVsIGRhdGEgYW5kIHNhdmUgaXQgYXMgImRmX0N1c3RvbWVycyINCg0KQ3VzdG9tZXIgTGV2ZWwgRGF0YSB3aXRoIHRoZSBmb2xsb3dpbmcgdmFyaWFibGVzOg0KDQoxLglUb3RhbEl0ZW1zQ291bnQgLSBOdW1iZXIgb2YgdG90YWwgaXRlbXMgYm91Z2h0DQoyLglBdmdJdGVtQ291bnQg4oCTIEF2ZyBudW1iZXIgb2YgaXRlbXMgYm91Z2h0DQozLglSZWNlbmN5IOKAkyBkaWZmZXJlbmNlIGJldHdlZW4gdGhlIGxhc3QgZGF0ZSBpbiB0aGUgZGF0YSBhbmQgdGhlIGxhc3QgZGF0ZSBvbiB3aGljaCB0aGUgICAgY3VzdG9tZXIgbWFkZSBhIHRyYW5zYWN0aW9uIA0KNC4JRnJlcXVlbmN5IOKAkyBUb3RhbCBudW1iZXIgb2YgdHJhbnNhY3Rpb25zIGJ5IGEgY3VzdG9tZXINCjUuCU1vbmV0YXJ5IOKAkyBBdmVyYWdlIGFtb3VudCBvZiBwdXJjaGFzZQ0KNi4JUHJvZml0YWJpbGl0eSDigJMgVG90YWwgcHJvZml0YWJpbGl0eQ0KNy4JRGlzY291bnRzIOKAkyBUb3RhbCBkaXNjb3VudA0KOC4JVW5pcXVlSXRlbXMg4oCTIE51bWJlciBvZiB1bmlxdWUgaXRlbXMgYm91Z2h0IChpbmRpY2F0aW5nIGJyb2FkIHRhc3RlIHZzIG5hcnJvdyB0YXN0ZSkNCjkuCUl0ZW1zIOKAkyB0aGUgbGlzdCBvZiB1bmlxdWUgaXRlbXMgYm91Z2h0IGJ5IGEgZ2l2ZW4gY3VzdG9tZXINCg0KDQojIEN1c3RvbWVyIExldmVsIERhdGEgLSBZb3UgY2FuIGZpbGwgeW91ciBjb2RlIGhlcmUuIA0KYGBge3J9DQpzdHIoZGYpDQpkZl9DdXN0b21lcnMgPC0gZGYgJT4lIGdyb3VwX2J5KENhcmRob2xkZXJOYW1lKSAlPiUgc3VtbWFyaXNlKFRvdGFsSXRlbXNDb3VudCA9IHN1bShRdWFudGl0eSksICNDcmVhdGUgZGZfQ3VzdG9tZXJzIGRhdGFmcmFtZSBncm91cGVkIGJ5IENhcmRob2xkZXIgTmFtZSwgd2l0aCBuZXcgY29sdW1ucyBhcyBmb2xsb3dzLi4uIE51bWJlciBvZiB0b3RhbCBpdGVtcyBib3VnaHQNCiAgICAgICAgQXZnSXRlbUNvdW50ID0gbWVhbihRdWFudGl0eSksICNBdmcgbnVtYmVyIG9mIGl0ZW1zIGJvdWdodA0KICAgICAgICBSZWNlbmN5ID0gZGlmZnRpbWUobWluKFRpbWUpLCBtYXgoVGltZSkpLCAjRGlmZmVyZW5jZSBiZXR3ZWVuIHRoZSBsYXN0IGRhdGUgaW4gdGhlIGRhdGEgYW5kIHRoZSBsYXN0IGRhdGUgb24gd2hpY2ggdGhlIGN1c3RvbWVyIG1hZGUgYSB0cmFuc2FjdGlvbg0KICAgICAgICBGcmVxdWVuY3kgPSBzdW0obl9kaXN0aW5jdChhcy5udW1lcmljKFRyYW5zYWN0aW9uTnVtYmVyKSkpLCAjVG90YWwgbnVtYmVyIG9mIHRyYW5zYWN0aW9ucyBieSBhIGN1c3RvbWVyIA0KICAgICAgICBNb25ldGFyeSA9IG1lYW4oTmV0VG90YWwpLCAjQXZlcmFnZSBhbW91bnQgb2YgcHVyY2hhc2UNCiAgICAgICAgUHJvZml0YWJpbGl0eSA9IHN1bShQcm9maXRhYmlsaXR5KSwgI1RvdGFsIHByb2ZpdGFiaWxpdHkgDQogICAgICAgIERpc2NvdW50cyA9IHN1bShEaXNjb3VudHMpLCAjVG90YWwgZGlzY291bnQNCiAgICAgICAgVW5pcXVlSXRlbXMgPSBuX2Rpc3RpbmN0KExpbmVJdGVtKSwgI051bWJlciBvZiB1bmlxdWUgaXRlbXMgYm91Z2h0IChpbmRpY2F0aW5nIGJyb2FkIHRhc3RlIHZzIG5hcnJvdyB0YXN0ZSkNCiAgICAgICAgSXRlbXMgPSBwYXN0ZSh1bmlxdWUoTGluZUl0ZW0pLCBjb2xsYXBzZSA9ICcsICcpDQogICAgICAgICkgI1RoZSBsaXN0IG9mIHVuaXF1ZSBpdGVtcyBib3VnaHQgYnkgYSBnaXZlbiBjdXN0b21lciANCg0KYGBgDQoNClNob3cgZmlyc3Qgc2l4IHJvd3Mgb2YgZGZfQ3VzdG9tZXJzIA0KDQpgYGB7cn0NCmhlYWQoZGZfQ3VzdG9tZXJzKQ0KYGBgDQoNCg0KIyBQcm9kdWN0IFN0cmF0ZWd5LyBNYW5hZ2VtZW50IC0gRGVwYXJ0bWVudCBhbmQgQ2F0ZWdvcnkgDQoNCkNocmlzdGluYSBhbHNvIG1lbnRpb25lZCB0aGF0IHNoZSBjYW4gcGVyZm9ybSBzb21lIGFuYWx5c2lzIHRvIGhlbHAgU2FtZWVyYSB0YWtlIGRlY2lzaW9ucyBmb3IgcHJvZHVjdCBzdHJhdGdleSBhbHNvLiBFc3NlbnRpYWxseSBmaWd1cmluZyBvdXQgd2hpY2ggcHJvZHVjdHMgc2VsbCB3ZWxsLCBoYXZlIGhpZ2hlciBwcm9maXRhYmlsaXR5LCBjYW4gYmUgY28tcHJvbW90ZWQsIGNyb3NzLXNvbGQsIGV0Yy4gDQoNClRoaXMgY2FuIGJlIGRvbmUgYXQgdmFyaW91cyBsZXZlbHMgc3VjaCBhcyBEZXBhcnRtZW50LCBDYXRnZW9yeSBvciBldmVuIExpbmVJdGVtLiANCg0KSSBhbSBjcmVhdGluZyBEZXBhdG1lbnQgTGV2ZWwgYW5kIExpbmVJdGVtIExldmVsIERhdGEgYW5kIHlvdXIgdGFzayBpcyB0byBjcmVhdGUgdGhlIENhdGVnb3J5IExldmVsIERhdGENCg0KIyMgRGVwYXJ0bWVudCBMZXZlbCANCmBgYHtyfQ0KZGZfRGVwYXJ0bWVudCA8LSBkZiAlPiUgZ3JvdXBfYnkoRGVwYXJ0bWVudCkgJT4lIHN1bW1hcmlzZSggDQogICAgICAgICAgICAgICAgICAgICAgICBJdGVtc0NvdW50ID0gbl9kaXN0aW5jdChMaW5lSXRlbSksICMgbl9kaXN0aW5jdCBnZXRzIHRoZSBjb3VudCBvZiANCiAgICAgICAgICAgICAgICAgICAgICAgICNkaXN0aW5jdC8gdW5pcXVlIHZhbHVlcw0KICAgICAgICAgICAgICAgICAgICAgICAgVHJhbnNhY3Rpb25zQ291bnQgPSBuX2Rpc3RpbmN0KFRyYW5zYWN0aW9uTnVtYmVyKSwNCiAgICAgICAgICAgICAgICAgICAgICAgIFRvdGFsU2FsZXMgPSBzdW0oTmV0VG90YWwpLCANCiAgICAgICAgICAgICAgICAgICAgICAgIEN1c3RvbWVyQ291bnQgPSBuX2Rpc3RpbmN0KEN1c3RvbWVyQ29kZSksDQogICAgICAgICAgICAgICAgICAgICAgICBQcm9maXRhYmlsaXR5ID0gbWVhbihQcm9maXRhYmlsaXR5KSwNCiAgICAgICAgICAgICAgICAgICAgICAgIERpc2NvdW50cyA9IG1lYW4oRGlzY291bnRzKSwNCiAgICAgICAgICAgICAgICAgICAgICAgIEl0ZW1zID0gcGFzdGUodW5pcXVlKExpbmVJdGVtKSwgY29sbGFwc2UgPSAnLCAnKSkNCmBgYA0KDQpDaGVjayB0aGUgbnVtYmVyIG9mIHJvd3MgYW5kIGNvbHVtbnMgaW4gImRmX0RlcGFydG1lbnQiIHVzaW5nICJkaW0iIGZ1bmN0aW9uLiBUaGlzIGdpdmVzIHRoZSBkaW1lbnNpb25zIG9mIHRoZSBkYXRhZnJhbWUuIA0KDQpgYGB7cn0NCmRpbShkZl9EZXBhcnRtZW50KQ0KYGBgDQo4IHJvd3MgbWVhbnMgdGhlcmUgYXJlIDggdW5pcXVlIGRlcGFydG1lbnRzIGFuZCBub3cgZWFoYyByb3cgY29ycmVzcG9uZHMgdG8gb25lIGRlcGFydG1lbnQuIA0KQWdhaW4sIHdlIGNyZWF0ZWQgNyBjb2x1bW5zLCB0aGUgZGF0YWZyYW1lIGhhcyBnb3QgOD8NCg0KQSBmZXcgcXVlc3Rpb25zIGZvciB5b3UgdG8gY29uc2lkZXI6DQogLSBXaGljaCBEZXBhcnRtZW50IHB1bGxzIHRoZSBoaWdoZXN0IG51bWJlciBvZiBjdXN0b21lcnM/DQogLSBXaGljaCBEZXBhcnRtZW50IGhhcyBoaWdoZXN0IFNhbGVzPw0KIC0gV2hpY2ggRGVwYXJ0bWVudCBzZWxscyBtb3N0IG9uIGRpc2NvdW50PyANCiAtIFdoaWNoIERlcGFydG1lbnQgaGFzIGhpZ2hlc3QgbnVtYmVyIG9mIHRyYW5zYWN0aW9ucz8NCg0KV2hhdCBpZiB3ZSB3ZXJlIGludGVyZXN0ZWQgaW4gYXNzZXNpbmcgZWFjaCBMSW5JdGVtIGZvciBQb3JmaXRhYmlsaXR5IG9yIFBvcHVsYXJpdHkgb3IgQWJpbGl0eSB0byBwdWxsIGRpdmVyc2UgY3VzdG9tZXJzDQoNCiMjIExpbmVJdGVtIE1hbmFnZW1lbnQgDQpgYGB7cn0NCmRmX0xpbmVJdGVtICA8LSAgZGYgJT4lIGdyb3VwX2J5KExpbmVJdGVtKSAlPiUgc3VtbWFyaXNlKCANCiAgICAgICAgICAgICAgICAgICAgICAgIFRyYW5zYWN0aW9uc0NvdW50ID0gbl9kaXN0aW5jdChUcmFuc2FjdGlvbk51bWJlciksDQogICAgICAgICAgICAgICAgICAgICAgICBUb3RhbFNhbGVzID0gc3VtKE5ldFRvdGFsKSwgDQogICAgICAgICAgICAgICAgICAgICAgICBDdXN0b21lckNvdW50ID0gbl9kaXN0aW5jdChDdXN0b21lckNvZGUpLA0KICAgICAgICAgICAgICAgICAgICAgICAgUHJvZml0YWJpbGl0eSA9IG1lYW4oUHJvZml0YWJpbGl0eSksDQogICAgICAgICAgICAgICAgICAgICAgICBEaXNjb3VudHMgPSBtZWFuKERpc2NvdW50cykpDQpgYGANCg0KQSBmZXcgcXVlc3Rpb25zIGZvciB5b3UgdG8gY29uc2lkZXI6DQogLSBXaGljaCBMaW5lSXRlbSBwdWxscyB0aGUgaGlnaGVzdCBudW1iZXIgb2YgY3VzdG9tZXJzPw0KIC0gV2hpY2ggTGluZUl0ZW0gaGFzIGhpZ2hlc3QgU2FsZXM/DQogLSBXaGljaCBMaW5laXRlbSBzZWxscyBtb3N0IG9uIGRpc2NvdW50PyBUaGlzIGlzIHRyaWNreSwgaG93IHdpbGwgeW91IGdldCBpdD8NCiAtIFdoaWNoIExpbmVpdGVtIGhhcyBoaWdoZXN0IG51bWJlciBvZiB0cmFuc2FjdGlvbnM/DQoNCg0KIyMgWW91ciBUYXNrIC0gQ3JlYXRlIHRoZSBmb2xsb3dpbmcgdmFyaWFibGVzIGF0IHRoZSBDYXRnZW9yeSBMZXZlbCBEYXRhDQoNCjEuCVVuaXF1ZUl0ZW1zQ291bnQgLSBOdW1iZXIgb2YgdW5pcXVlIGl0ZW1zIGluIHRoZSBjYXRlZ29yeQ0KMi4JVHJhbnNhY3Rpb25zQ291bnQgLSBOdW1iZXIgb2YgdHJhbnNhY3Rpb25zIGluIHRoZSBjYXRlZ29yeQ0KMy4JVG90YWxTYWxlcyAtIFRvdGFsIFNhbGVzIGluIHRoZSBjYXRlZ29yeQ0KNC4JQ3VzdG9tZXJDb3VudCAtIFRvdGFsIG51bWJlciBvZiBjdXN0b21lcnMgaW4gdGhpcyBjYXRlZ29yeQ0KNS4JUHJvZml0YWJpbGl0eSAtIEF2ZXJhZ2UgcHJvZml0YWJpbGl0eQ0KNi4JRGlzY291bnRzIC0gQXZlcmFnZSBkaXNjb3VudA0KNy4JSXRlbXMgLSB0aGUgbGlzdCBvZiB1bmlxdWUgaXRlbXMgaW4gdGhlIGNhdGVnb3J5DQoNCg0KV3JpdGUgeW91ciBjb2RlIGJlbG93IGZvciBjcmVhdGluZyBkYXRhIGF0IENhdGVnb3J5IGxldmVsDQpgYGB7cn0NCmRmX0NhdGVnb3J5ICA8LSAgZGYgJT4lIGdyb3VwX2J5KENhdGVnb3J5KSAlPiUgc3VtbWFyaXNlKCAjQ3JlYXRpbmcgbmV3IGRmX0NhdGVnb3J5IGRhdGFmcmFtZSBncm91cGVkIGJ5IENhdGVnb3J5IHdpdGggdGhlIGZvbGxvd2luZyBjb2x1bW5zOiANCiAgICAgICAgVW5pcXVlSXRlbXNDb3VudCA9IG5fZGlzdGluY3QoTGluZUl0ZW0pLCAjTnVtYmVyIG9mIHVuaXF1ZSBpdGVtcyBpbiB0aGUgY2F0ZWdvcnkNCiAgICAgICAgVHJhbnNhY3Rpb25zQ291bnQgPSBuX2Rpc3RpbmN0KFRyYW5zYWN0aW9uTnVtYmVyKSwgI051bWJlciBvZiB0cmFuc2FjdGlvbnMgaW4gdGhlIGNhdGVnb3J5DQogICAgICAgIFRvdGFsU2FsZXMgPSBzdW0oTmV0VG90YWwpLCAjVG90YWwgU2FsZXMgaW4gdGhlIGNhdGVnb3J5IA0KICAgICAgICBDdXN0b21lckNvdW50ID0gbl9kaXN0aW5jdChDYXJkaG9sZGVyTmFtZSksICNUb3RhbCBudW1iZXIgb2YgY3VzdG9tZXJzIGluIHRoaXMgY2F0ZWdvcnkgDQogICAgICAgIFByb2ZpdGFiaWxpdHkgPSBtZWFuKFByb2ZpdGFiaWxpdHkpLCAjQXZlcmFnZSBwcm9maXRhYmlsaXR5IA0KICAgICAgICBEaXNjb3VudHMgPSBtZWFuKERpc2NvdW50cyksICNBdmVyYWdlIGRpc2NvdW50DQogICAgICAgIEl0ZW1zID0gcGFzdGUodW5pcXVlKExpbmVJdGVtKSwgY29sbGFwc2UgPSAnLCAnKSAjVGhlIGxpc3Qgb2YgdW5pcXVlIGl0ZW1zIGluIHRoZSBjYXRlZ29yeSANCikNCmBgYA0KDQoNCg0KU2hvdyBmaXJzdCBzaXggcm93cyBvZiBkZl9DYXRlZ29yeQ0KYGBge3J9DQpoZWFkKGRmX0NhdGVnb3J5KQ0KYGBgDQoNCg0KDQojIFRpbWVTZXJpZXMgRGF0YSANCg0KSSB3aWxsIGNyZWF0ZSBEYWlseSBUaW1lU2VyaWVzLiBZb3VyIHRhc2sgaXMgdG8gY3JlYXRlIGRhdGEgYWdycmVnYXRlZCBhdCBtb250aGx5IGxldmVsLg0KDQojIERhaWx5DQpgYGB7cn0NCmRmX2RheSA8LSBkZiAlPiUgZ3JvdXBfYnkoRGF5LE1vbnRoLFllYXIpICU+JSBzdW1tYXJpc2UoVHJhbnNhY3Rpb25zQ291bnQgPSBuX2Rpc3RpbmN0KFRyYW5zYWN0aW9uTnVtYmVyKSwNCiAgICAgICAgICAgICAgICAgICBUb3RhbFNhbGVzID0gc3VtKE5ldFRvdGFsKSwgDQogICAgICAgICAgICAgICAgICAgQ3VzdG9tZXJDb3VudCA9IG5fZGlzdGluY3QoQ3VzdG9tZXJDb2RlKSwNCiAgICAgICAgICAgICAgICAgICBQcm9maXRhYmlsaXR5ID0gbWVhbihQcm9maXRhYmlsaXR5KSwNCiAgICAgICAgICAgICAgICAgICBEaXNjb3VudHMgPSBtZWFuKERpc2NvdW50cykpJT4lDQogICAgICAgICAgICAgICAgICAgbXV0YXRlKERhdGUgPSBtYWtlX2RhdGUoWWVhciwgTW9udGgsIERheSkpIyB0aGlzIGlzIHRvIGdldCBhIGNvbHVtbiAiRGF0ZSIgc28gdGhhdCB3ZSBjYW4gdXNlIGl0IG9uIFgtYXhpcyB0byBjcmVhdGUgYSBMaW5lIENoYXJ0DQoNCiMgSSB3aWxsIHNob3cgYSBsaW5lIGNoYXJ0IGJlbG93LiANCiAgICAgICAgICAgICAgICAgICANCmBgYA0KDQpUaGVyZSBhcmUgOTUxIHJvd3MgYW5kIDkgY29sdW1ucyBpbiBkZl9kYXkgZGF0YS4NCg0KQSBmZXcgcXVlc3Rpb25zIHlvdSBtYXkgY29uc2lkZXI6DQpIb3cgaGF2ZSBzYWxlcyBpbmNyZWFzZWQvIGRlY3JlYXNlZCBvdmVyIHRpbWU/DQpIb3cgaGFzIG51bWJlciBvZiBvcmRlcnMvIHRyYW5zY2F0aW9ucyBpbmNyZWFzZWQvIGRlY3JlYXNlZCBvdmVyIHRpbWU/DQpIb3cgaGFzIHByb2ZpdGFiaWxpdHkgaW5jcmVhc2VkLyBkZWNyZWFzZWQgb3ZlciB0aW1lPw0KDQoNCg0KV3JpdGUgeW91ciBjb2RlIGJlbG93IHRvIGNyZWF0ZSBtb250aGx5IGRhdGEgd2l0aCB0aGUgZm9sbG93aW5nIHZhcmlhYmxlczoNCg0KMS4JVHJhbnNhY3Rpb25zQ291bnQgLSBOdW1iZXIgb2YgdHJhbnNhY3Rpb25zIGluIGVhY2ggbW9udGgNCjIuCVRvdGFsU2FsZXMg4oCTIFRvdGFsIFNhbGVzIGluIGVhY2ggbW9udGgNCjMuCUN1c3RvbWVyQ291bnQg4oCTIFRvdGFsIEN1c3RvbWVycyBpbiBlYWNoIG1vbnRoDQo0LglMaW5lSXRlbUNvdW50IOKAkyBUb3RhbCBMaW5lSXRlbXMgc29sZCBlYWNoIG1vbnRoDQo1LglVbmlxdWVMaW5lSXRlbUNvdW50IC0gTnVtYmVyIG9mIFVuaXF1ZSBJdGVtcyBTb2xkIGVhY2ggbW9udGgNCjYuCUNhdGVnb3J5Q291bnQg4oCTIFRvdGFsIENhdGVnb3J5IENvdW50IGVhY2ggbW9udGgNCg0KIyBNb250aGx5DQpgYGB7cn0NCmRmX21vbnRoIDwtIGRmICU+JSBncm91cF9ieShNb250aCkgJT4lIHN1bW1hcmlzZSggI0NyZWF0aW5nIGRmX21vbnRoIGRhdGFmcmFtZSBncm91cGVkIGJ5IE1vbnRoIHdpdGggdGhlIGZvbGxvd2luZyBjb2x1bW5zOiANCiAgICAgICAgVHJhbnNhY3Rpb25zQ291bnQgPSBuX2Rpc3RpbmN0KFRyYW5zYWN0aW9uTnVtYmVyKSwgI051bWJlciBvZiB0cmFuc2FjdGlvbnMgaW4gZWFjaCBtb250aCANCiAgICAgICAgVG90YWxTYWxlcyA9IHN1bShOZXRUb3RhbCksICNUb3RhbCBTYWxlcyBpbiBlYWNoIG1vbnRoIA0KICAgICAgICBDdXN0b21lckNvdW50ID0gbl9kaXN0aW5jdChDdXN0b21lckNvZGUpLCAjVG90YWwgQ3VzdG9tZXJzIGluIGVhY2ggbW9udGggDQogICAgICAgIExpbmVJdGVtQ291bnQgPSBzdW0oYXMubnVtZXJpYyhMaW5lSXRlbSkpLCAgI1RvdGFsIExpbmVJdGVtcyBzb2xkIGVhY2ggbW9udGggDQogICAgICAgIFVuaXF1ZUxpbmVJdGVtQ291bnQgPSBuX2Rpc3RpbmN0KExpbmVJdGVtKSwgI051bWJlciBvZiBVbmlxdWUgSXRlbXMgU29sZCBlYWNoIG1vbnRoIA0KICAgICAgICBDYXRlZ29yeUNvdW50ID0gbl9kaXN0aW5jdChDYXRlZ29yeSksICNUb3RhbCBDYXRlZ29yeSBDb3VudCBlYWNoIG1vbnRoDQopDQoNCmBgYA0KDQoNCkdldCBmaXJzdCBzaXggcm93cyBvZiBkZl9tb250aA0KYGBge3J9DQpoZWFkKGRmX21vbnRoKQ0KYGBgDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0K