This analysis is based on the Divvy case study “‘Sophisticated, Clear, and Polished’: Divvy and Data Visualization” written by Kevin Hartman (found here: https://artscience.blog/home/divvy-dataviz-case-study). The purpose of this script is to consolidate downloaded Divvy data into a single dataframe and then conduct simple analysis to help answer the key question: “In what ways do members and casual riders use Divvy bikes differently?”

Install and load required packages ‘tidyverse’, ‘lubridate’ y ggplot

install.packages("tidyverse")
install.packages("lubridate")
install.packages("ggplot2")
library('tidyverse')
library('lubridate')
library(ggplot2)
getwd()
[1] "C:/Users/nicol/OneDrive/Documentos/Analisis de Datos de Google/Caso estidio1/Divvy_Exercise_Full_Year_Analysis/divvy_exercise_full_year_analysis"
setwd("C:/Users/nicol/OneDrive/Documentos/Analisis de Datos de Google/Caso estidio1/Divvy_Exercise_Full_Year_Analysis/divvy_exercise_full_year_analysis")

STEP 1: COLLECT DATA

Upload Divvy datasets (csv files) here

q2_2019 <- read.csv("Data/Divvy_Trips_2019_Q2.csv")
q3_2019 <- read.csv("Data/Divvy_Trips_2019_Q3.csv")
q4_2019 <- read.csv("Data/Divvy_Trips_2019_Q4.csv")
q1_2020 <- read.csv("Data/Divvy_Trips_2020_Q1.csv")

STEP 2: WRANGLE DATA AND COMBINE INTO A SINGLE FILE

Compare column names each of the files

While the names don’t have to be in the same order, they DO need to match perfectly before we can use a command to join them into one file

colnames(q3_2019)
 [1] "trip_id"           "start_time"        "end_time"          "bikeid"           
 [5] "tripduration"      "from_station_id"   "from_station_name" "to_station_id"    
 [9] "to_station_name"   "usertype"          "gender"            "birthyear"        
colnames(q4_2019)
 [1] "trip_id"           "start_time"        "end_time"          "bikeid"           
 [5] "tripduration"      "from_station_id"   "from_station_name" "to_station_id"    
 [9] "to_station_name"   "usertype"          "gender"            "birthyear"        
colnames(q2_2019)
 [1] "X01...Rental.Details.Rental.ID"                   
 [2] "X01...Rental.Details.Local.Start.Time"            
 [3] "X01...Rental.Details.Local.End.Time"              
 [4] "X01...Rental.Details.Bike.ID"                     
 [5] "X01...Rental.Details.Duration.In.Seconds.Uncapped"
 [6] "X03...Rental.Start.Station.ID"                    
 [7] "X03...Rental.Start.Station.Name"                  
 [8] "X02...Rental.End.Station.ID"                      
 [9] "X02...Rental.End.Station.Name"                    
[10] "User.Type"                                        
[11] "Member.Gender"                                    
[12] "X05...Member.Details.Member.Birthday.Year"        
colnames(q1_2020)
 [1] "ride_id"            "rideable_type"      "started_at"         "ended_at"          
 [5] "start_station_name" "start_station_id"   "end_station_name"   "end_station_id"    
 [9] "start_lat"          "start_lng"          "end_lat"            "end_lng"           
[13] "member_casual"     

Rename columns to make them consisent with q1_2020 (as this will be the supposed going-forward table design for Divvy)

q4_2019 <- rename(q4_2019,ride_id = trip_id, rideable_type = bikeid, started_at = start_time, ended_at = end_time, start_station_name = from_station_name, start_station_id = from_station_id, end_station_name = to_station_name, end_station_id = to_station_id, menber_casual = usertype)
q3_2019 <- rename(q3_2019, ride_id = trip_id, rideable_type = bikeid, started_at = start_time, ended_at = end_time, start_station_name = from_station_name, start_station_id = from_station_id, end_station_name = to_station_name, end_station_id = to_station_id, member_casual = usertype)
q2_2019 <- rename(q2_2019, ride_id = "X01...Rental.Details.Rental.ID", rideable_type = "X01...Rental.Details.Bike.ID", started_at = "X01...Rental.Details.Local.Start.Time", ended_at = "X01...Rental.Details.Local.End.Time", start_station_name = "X03...Rental.Start.Station.Name", start_station_id = "X03...Rental.Start.Station.ID", end_station_name = "X02...Rental.End.Station.Name", end_station_id = "X02...Rental.End.Station.ID", member_casual = "User.Type")

Inspect the dataframes and look for incongruencies

str(q1_2020)
'data.frame':   426887 obs. of  13 variables:
 $ ride_id           : chr  "EACB19130B0CDA4A" "8FED874C809DC021" "789F3C21E472CA96" "C9A388DAC6ABF313" ...
 $ rideable_type     : chr  "docked_bike" "docked_bike" "docked_bike" "docked_bike" ...
 $ started_at        : chr  "2020-01-21 20:06:59" "2020-01-30 14:22:39" "2020-01-09 19:29:26" "2020-01-06 16:17:07" ...
 $ ended_at          : chr  "2020-01-21 20:14:30" "2020-01-30 14:26:22" "2020-01-09 19:32:17" "2020-01-06 16:25:56" ...
 $ start_station_name: chr  "Western Ave & Leland Ave" "Clark St & Montrose Ave" "Broadway & Belmont Ave" "Clark St & Randolph St" ...
 $ start_station_id  : int  239 234 296 51 66 212 96 96 212 38 ...
 $ end_station_name  : chr  "Clark St & Leland Ave" "Southport Ave & Irving Park Rd" "Wilton Ave & Belmont Ave" "Fairbanks Ct & Grand Ave" ...
 $ end_station_id    : int  326 318 117 24 212 96 212 212 96 100 ...
 $ start_lat         : num  42 42 41.9 41.9 41.9 ...
 $ start_lng         : num  -87.7 -87.7 -87.6 -87.6 -87.6 ...
 $ end_lat           : num  42 42 41.9 41.9 41.9 ...
 $ end_lng           : num  -87.7 -87.7 -87.7 -87.6 -87.6 ...
 $ member_casual     : chr  "member" "member" "member" "member" ...
str(q4_2019)
'data.frame':   704054 obs. of  12 variables:
 $ ride_id           : int  25223640 25223641 25223642 25223643 25223644 25223645 25223646 25223647 25223648 25223649 ...
 $ started_at        : chr  "2019-10-01 00:01:39" "2019-10-01 00:02:16" "2019-10-01 00:04:32" "2019-10-01 00:04:32" ...
 $ ended_at          : chr  "2019-10-01 00:17:20" "2019-10-01 00:06:34" "2019-10-01 00:18:43" "2019-10-01 00:43:43" ...
 $ rideable_type     : int  2215 6328 3003 3275 5294 1891 1061 1274 6011 2957 ...
 $ tripduration      : chr  "940.0" "258.0" "850.0" "2,350.0" ...
 $ start_station_id  : int  20 19 84 313 210 156 84 156 156 336 ...
 $ start_station_name: chr  "Sheffield Ave & Kingsbury St" "Throop (Loomis) St & Taylor St" "Milwaukee Ave & Grand Ave" "Lakeview Ave & Fullerton Pkwy" ...
 $ end_station_id    : int  309 241 199 290 382 226 142 463 463 336 ...
 $ end_station_name  : chr  "Leavitt St & Armitage Ave" "Morgan St & Polk St" "Wabash Ave & Grand Ave" "Kedzie Ave & Palmer Ct" ...
 $ menber_casual     : chr  "Subscriber" "Subscriber" "Subscriber" "Subscriber" ...
 $ gender            : chr  "Male" "Male" "Female" "Male" ...
 $ birthyear         : int  1987 1998 1991 1990 1987 1994 1991 1995 1993 NA ...
str(q3_2019)
'data.frame':   1640718 obs. of  12 variables:
 $ ride_id           : int  23479388 23479389 23479390 23479391 23479392 23479393 23479394 23479395 23479396 23479397 ...
 $ started_at        : chr  "2019-07-01 00:00:27" "2019-07-01 00:01:16" "2019-07-01 00:01:48" "2019-07-01 00:02:07" ...
 $ ended_at          : chr  "2019-07-01 00:20:41" "2019-07-01 00:18:44" "2019-07-01 00:27:42" "2019-07-01 00:27:10" ...
 $ rideable_type     : int  3591 5353 6180 5540 6014 4941 3770 5442 2957 6091 ...
 $ tripduration      : chr  "1,214.0" "1,048.0" "1,554.0" "1,503.0" ...
 $ start_station_id  : int  117 381 313 313 168 300 168 313 43 43 ...
 $ start_station_name: chr  "Wilton Ave & Belmont Ave" "Western Ave & Monroe St" "Lakeview Ave & Fullerton Pkwy" "Lakeview Ave & Fullerton Pkwy" ...
 $ end_station_id    : int  497 203 144 144 62 232 62 144 195 195 ...
 $ end_station_name  : chr  "Kimball Ave & Belmont Ave" "Western Ave & 21st St" "Larrabee St & Webster Ave" "Larrabee St & Webster Ave" ...
 $ member_casual     : chr  "Subscriber" "Customer" "Customer" "Customer" ...
 $ gender            : chr  "Male" "" "" "" ...
 $ birthyear         : int  1992 NA NA NA NA 1990 NA NA NA NA ...
str(q2_2019)
'data.frame':   1108163 obs. of  12 variables:
 $ ride_id                                          : int  22178529 22178530 22178531 22178532 22178533 22178534 22178535 22178536 22178537 22178538 ...
 $ started_at                                       : chr  "2019-04-01 00:02:22" "2019-04-01 00:03:02" "2019-04-01 00:11:07" "2019-04-01 00:13:01" ...
 $ ended_at                                         : chr  "2019-04-01 00:09:48" "2019-04-01 00:20:30" "2019-04-01 00:15:19" "2019-04-01 00:18:58" ...
 $ rideable_type                                    : int  6251 6226 5649 4151 3270 3123 6418 4513 3280 5534 ...
 $ X01...Rental.Details.Duration.In.Seconds.Uncapped: chr  "446.0" "1,048.0" "252.0" "357.0" ...
 $ start_station_id                                 : int  81 317 283 26 202 420 503 260 211 211 ...
 $ start_station_name                               : chr  "Daley Center Plaza" "Wood St & Taylor St" "LaSalle St & Jackson Blvd" "McClurg Ct & Illinois St" ...
 $ end_station_id                                   : int  56 59 174 133 129 426 500 499 211 211 ...
 $ end_station_name                                 : chr  "Desplaines St & Kinzie St" "Wabash Ave & Roosevelt Rd" "Canal St & Madison St" "Kingsbury St & Kinzie St" ...
 $ member_casual                                    : chr  "Subscriber" "Subscriber" "Subscriber" "Subscriber" ...
 $ Member.Gender                                    : chr  "Male" "Female" "Male" "Male" ...
 $ X05...Member.Details.Member.Birthday.Year        : int  1975 1984 1990 1993 1992 1999 1969 1991 NA NA ...

Convert ride_id and rideable_type to character so that they can stack correctly

q4_2019 <- mutate(q4_2019, ride_id = as.character(ride_id), rideable_type = as.character(rideable_type))
q3_2019 <- mutate(q3_2019, ride_id = as.character(ride_id), rideable_type = as.character(rideable_type))
q2_2019 <- mutate(q2_2019, ride_id = as.character(ride_id), rideable_type = as.character(rideable_type))

Stack individual quarter’s data frames into one big data frame

all_trips <- bind_rows(q2_2019, q3_2019, q4_2019, q1_2020)

Remove lat, long, birthyear, and gender fields as this data was dropped beginning in 2020

all_trips <- all_trips %>%
  select(-c(start_lat, start_lng, end_lat, end_lng, birthyear, gender, "X01...Rental.Details.Duration.In.Seconds.Uncapped", "X05...Member.Details.Member.Birthday.Year", "Member.Gender", "tripduration"))

STEP 3: CLEAN UP AND ADD DATA TO PREPARE FOR ANALYSIS

Inspect the new table that has been created

colnames(all_trips)
 [1] "ride_id"            "started_at"         "ended_at"           "rideable_type"     
 [5] "start_station_id"   "start_station_name" "end_station_id"     "end_station_name"  
 [9] "member_casual"      "menber_casual"      "date"               "month"             
[13] "day"                "year"               "day_of_week"        "ride_length"       
nrow(all_trips)
[1] 3879822
dim(all_trips)
[1] 3879822      16
head(all_trips)
str(all_trips)
'data.frame':   3879822 obs. of  16 variables:
 $ ride_id           : chr  "22178529" "22178530" "22178531" "22178532" ...
 $ started_at        : chr  "2019-04-01 00:02:22" "2019-04-01 00:03:02" "2019-04-01 00:11:07" "2019-04-01 00:13:01" ...
 $ ended_at          : chr  "2019-04-01 00:09:48" "2019-04-01 00:20:30" "2019-04-01 00:15:19" "2019-04-01 00:18:58" ...
 $ rideable_type     : chr  "6251" "6226" "5649" "4151" ...
 $ start_station_id  : int  81 317 283 26 202 420 503 260 211 211 ...
 $ start_station_name: chr  "Daley Center Plaza" "Wood St & Taylor St" "LaSalle St & Jackson Blvd" "McClurg Ct & Illinois St" ...
 $ end_station_id    : int  56 59 174 133 129 426 500 499 211 211 ...
 $ end_station_name  : chr  "Desplaines St & Kinzie St" "Wabash Ave & Roosevelt Rd" "Canal St & Madison St" "Kingsbury St & Kinzie St" ...
 $ member_casual     : chr  "member" "member" "member" "member" ...
 $ menber_casual     : chr  NA NA NA NA ...
 $ date              : Date, format: "2019-04-01" "2019-04-01" "2019-04-01" ...
 $ month             : chr  "04" "04" "04" "04" ...
 $ day               : chr  "01" "01" "01" "01" ...
 $ year              : chr  "2019" "2019" "2019" "2019" ...
 $ day_of_week       : chr  "Monday" "Monday" "Monday" "Monday" ...
 $ ride_length       : num  446 1048 252 357 1007 ...
summary(all_trips)

There are a few problems we will need to fix:

    1. In the “member_casual” column, there are two names for members (“member” and “Subscriber”) and two names for casual riders (“Customer” and “casual”). We will need to consolidate that from four to two labels.
    1. The data can only be aggregated at the ride-level, which is too granular. We will want to add some additional columns of data – such as day, month, year – that provide additional opportunities to aggregate the data.

_ (3) We will want to add a calculated field for length of ride since the 2020Q1 data did not have the “tripduration” column. We will add “ride_length” to the entire dataframe for consistency.

    1. There are some rides where tripduration shows up as negative, including several hundred rides where Divvy took bikes out of circulation for Quality Control reasons. We will want to delete these rides.

In the “member_casual” column, replace “Subscriber” with “member” and “Customer” with “casual”

  • Before 2020, Divvy used different labels for these two types of riders … we will want to make our dataframe consistent with their current nomenclature

  • N.B.: “Level” is a special property of a column that is retained even if a subset does not contain any values from a specific level

  • Begin by seeing how many observations fall under each usertype

summary(all_trips)
   ride_id           started_at          ended_at         rideable_type      start_station_id
 Length:3879822     Length:3879822     Length:3879822     Length:3879822     Min.   :  1.0   
 Class :character   Class :character   Class :character   Class :character   1st Qu.: 77.0   
 Mode  :character   Mode  :character   Mode  :character   Mode  :character   Median :174.0   
                                                                             Mean   :202.9   
                                                                             3rd Qu.:291.0   
                                                                             Max.   :675.0   
                                                                                             
 start_station_name end_station_id  end_station_name   member_casual      menber_casual     
 Length:3879822     Min.   :  1.0   Length:3879822     Length:3879822     Length:3879822    
 Class :character   1st Qu.: 77.0   Class :character   Class :character   Class :character  
 Mode  :character   Median :174.0   Mode  :character   Mode  :character   Mode  :character  
                    Mean   :203.8                                                           
                    3rd Qu.:291.0                                                           
                    Max.   :675.0                                                           
                    NA's   :1                                                               
      date               month               day                year          
 Min.   :2019-04-01   Length:3879822     Length:3879822     Length:3879822    
 1st Qu.:2019-06-23   Class :character   Class :character   Class :character  
 Median :2019-08-14   Mode  :character   Mode  :character   Mode  :character  
 Mean   :2019-08-25                                                           
 3rd Qu.:2019-10-12                                                           
 Max.   :2020-03-31                                                           
                                                                              
 day_of_week         ride_length     
 Length:3879822     Min.   :  -6982  
 Class :character   1st Qu.:    411  
 Mode  :character   Median :    711  
                    Mean   :   1478  
                    3rd Qu.:   1288  
                    Max.   :9383424  
                                     

Reassign to the desired values (we will go with the current 2020 labels)

table(all_trips$member_casual)

 casual  member 
 799760 2376008 

Check to make sure the proper number of observations were reassigned

all_trips <- all_trips %>%
  mutate(member_casual = recode(member_casual, "Subscriber" = "member", "Customer" = "casual"))

Add columns that list the date, month, day, and year of each ride

This will allow us to aggregate ride data for each month, day, or year … before completing these operations we could only aggregate at the ride level

all_trips$date <- as.Date(all_trips$started_at)
table(all_trips$member_casual)

 casual  member 
 799760 2376008 
all_trips$month <- format(as.Date(all_trips$date),"%m")
all_trips$day <- format(as.Date(all_trips$date), "%d")
all_trips$year <- format(as.Date(all_trips$date), "%Y")

Add a “ride_length” calculation to all_trips (in seconds)

all_trips$day_of_week <- format(as.Date(all_trips$date), "%A")

Inspect the structure of the columns

all_trips$ride_length <- difftime(all_trips$ended_at, all_trips$started_at)

Convert “ride_length” from Factor to numeric so we can run calculations on the data

str(all_trips)
'data.frame':   3879822 obs. of  16 variables:
 $ ride_id           : chr  "22178529" "22178530" "22178531" "22178532" ...
 $ started_at        : chr  "2019-04-01 00:02:22" "2019-04-01 00:03:02" "2019-04-01 00:11:07" "2019-04-01 00:13:01" ...
 $ ended_at          : chr  "2019-04-01 00:09:48" "2019-04-01 00:20:30" "2019-04-01 00:15:19" "2019-04-01 00:18:58" ...
 $ rideable_type     : chr  "6251" "6226" "5649" "4151" ...
 $ start_station_id  : int  81 317 283 26 202 420 503 260 211 211 ...
 $ start_station_name: chr  "Daley Center Plaza" "Wood St & Taylor St" "LaSalle St & Jackson Blvd" "McClurg Ct & Illinois St" ...
 $ end_station_id    : int  56 59 174 133 129 426 500 499 211 211 ...
 $ end_station_name  : chr  "Desplaines St & Kinzie St" "Wabash Ave & Roosevelt Rd" "Canal St & Madison St" "Kingsbury St & Kinzie St" ...
 $ member_casual     : chr  "member" "member" "member" "member" ...
 $ menber_casual     : chr  NA NA NA NA ...
 $ date              : Date, format: "2019-04-01" "2019-04-01" "2019-04-01" ...
 $ month             : chr  "04" "04" "04" "04" ...
 $ day               : chr  "01" "01" "01" "01" ...
 $ year              : chr  "2019" "2019" "2019" "2019" ...
 $ day_of_week       : chr  "Monday" "Monday" "Monday" "Monday" ...
 $ ride_length       : 'difftime' num  446 1048 252 357 ...
  ..- attr(*, "units")= chr "secs"
is.factor(all_trips$ride_length)
[1] FALSE
all_trips$ride_length <- as.numeric(as.character(all_trips$ride_length))

Remove “bad” data

The dataframe includes a few hundred entries when bikes were taken out of docks and checked for quality by Divvy or ride_length was negative ## We will create a new version of the dataframe (v2) since data is being removed

is.numeric(all_trips$ride_length)
[1] TRUE

STEP 4: CONDUCT DESCRIPTIVE ANALYSIS

Descriptive analysis on ride_length

mean(all_trips_v2$ride_length)
[1] 1479.159
median(all_trips_v2$ride_length)
[1] 712
max(all_trips_v2$ride_length)
[1] 9383424
min(all_trips_v2$ride_length)
[1] 1

You can condense the four lines above to one line using summary() on the specific attribute

all_trips_v2 <- all_trips[!(all_trips$start_station_name == "HQ QR" | all_trips$ride_length<0),]

Compare members and casual users

summary(all_trips_v2$ride_length)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
      1     412     712    1479    1289 9383424 
aggregate(all_trips_v2$ride_length ~ all_trips_v2$member_casual, FUN = mean)
aggregate(all_trips_v2$ride_length ~ all_trips_v2$member_casual, FUN = median)
aggregate(all_trips_v2$ride_length ~ all_trips_v2$member_casual, FUN = max)

See the average ride time by each day for members vs casual users

aggregate(all_trips_v2$ride_length ~ all_trips_v2$member_casual, FUN = min)

Notice that the days of the week are out of order. Let’s fix that.

aggregate(all_trips_v2$ride_length ~ all_trips_v2$member_casual + all_trips_v2$day_of_week, FUN = mean)

Now, let’s run the average ride time by each day for members vs casual users

all_trips_v2$day_of_week <- ordered(all_trips_v2$day_of_week, levels=c("Sunday", "Monday", "Tuesday", "Thursday", "Friday", "Saturday"))

analyze ridership data by type and weekday

all_trips_v2 %>%
  mutate(weekday = wday(started_at, label = TRUE)) %>%
  group_by(member_casual, weekday) %>%
  summarise(number_of_rides = n(), average_duration = mean(ride_length))%>%
  arrange(member_casual, weekday)
`summarise()` has grouped output by 'member_casual'. You can override using the `.groups` argument.

Let’s visualize the number of rides by rider type

aggregate(all_trips_v2$ride_length ~ all_trips_v2$member_casual + all_trips_v2$day_of_week, FUN = mean)

Let’s create a visualization for average duration

all_trips_v2 %>%
  mutate( weekday = wday(started_at, label = TRUE)) %>%
  group_by(member_casual, weekday) %>%
  summarise(number_of_rides = n(), average_duration = mean(ride_length)) %>%
  arrange(member_casual, weekday) %>%
  ggplot(aes(x = weekday, y = number_of_rides, fill = member_casual)) +
  geom_col(position = "dodge")
`summarise()` has grouped output by 'member_casual'. You can override using the `.groups` argument.

STEP 5: EXPORT SUMMARY FILE FOR FURTHER ANALYSIS

Create a csv file that we will visualize in Excel, Tableau, or my presentation software

all_trips_v2 %>%
  mutate( weekday = wday(started_at, label = TRUE)) %>%
  group_by(member_casual, weekday) %>%
  summarise(number_of_rides = n(), average_duration = mean(ride_length)) %>%
  arrange(member_casual, weekday) %>%
  ggplot(aes(x = weekday, y =average_duration, fill = member_casual)) +
  geom_col(position = "dodge")
`summarise()` has grouped output by 'member_casual'. You can override using the `.groups` argument.

counts <- aggregate(all_trips_v2$ride_length ~ all_trips_v2$member_casual + all_trips_v2$day_of_week, FUN = mean)
LS0tDQp0aXRsZTogIkRpdnZ5X0V4ZXJjaXNlX0Z1bGxfWWVhcl9BbmFseXNpcyAiDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCmVkaXRvcl9vcHRpb25zOiANCiAgY2h1bmtfb3V0cHV0X3R5cGU6IGlubGluZQ0KLS0tDQoNClRoaXMgYW5hbHlzaXMgaXMgYmFzZWQgb24gdGhlIERpdnZ5IGNhc2Ugc3R1ZHkgIidTb3BoaXN0aWNhdGVkLCBDbGVhciwgYW5kIFBvbGlzaGVk4oCZOiBEaXZ2eSBhbmQgRGF0YSBWaXN1YWxpemF0aW9uIiB3cml0dGVuIGJ5IEtldmluIEhhcnRtYW4gKGZvdW5kIGhlcmU6IGh0dHBzOi8vYXJ0c2NpZW5jZS5ibG9nL2hvbWUvZGl2dnktZGF0YXZpei1jYXNlLXN0dWR5KS4gVGhlIHB1cnBvc2Ugb2YgdGhpcyBzY3JpcHQgaXMgdG8gY29uc29saWRhdGUgZG93bmxvYWRlZCBEaXZ2eSBkYXRhIGludG8gYSBzaW5nbGUgZGF0YWZyYW1lIGFuZCB0aGVuIGNvbmR1Y3Qgc2ltcGxlIGFuYWx5c2lzIHRvIGhlbHAgYW5zd2VyIHRoZSBrZXkgcXVlc3Rpb246IOKAnEluIHdoYXQgd2F5cyBkbyBtZW1iZXJzIGFuZCBjYXN1YWwgcmlkZXJzIHVzZSBEaXZ2eSBiaWtlcyBkaWZmZXJlbnRseT/igJ0NCg0KIyMgSW5zdGFsbCBhbmQgbG9hZCByZXF1aXJlZCBwYWNrYWdlcyAndGlkeXZlcnNlJywgJ2x1YnJpZGF0ZScgeSBnZ3Bsb3QNCg0KYGBge3IgdGlkeXZlcnNlIGZvciBkYXRhIGltcG9ydCBhbmQgd3JhbmdsaW5nLCBsb2FkLXBhY2thZ2VzLCBtZXNzYWdlID0gRkFMU0UsIGVjaG8gPSBUUlVFfQ0KaW5zdGFsbC5wYWNrYWdlcygidGlkeXZlcnNlIikNCmBgYA0KDQpgYGB7ciBsdWJyaWRhdGUgZm9yIGRhdGUgZnVuY3Rpb25zLCBsb2FkLXBhY2thZ2VzLCBtZXNzYWdlID0gRkFMU0UsIGVjaG8gPSBUUlVFfQ0KaW5zdGFsbC5wYWNrYWdlcygibHVicmlkYXRlIikNCmBgYA0KDQpgYGB7ciBnZ3Bsb3QgZm9yIHZpc3VhbGl6YXRpb24sIGxvYWQtcGFja2FnZXMsIG1lc3NhZ2UgPSBGQUxTRSwgZWNobyA9IFRSVUV9DQppbnN0YWxsLnBhY2thZ2VzKCJnZ3Bsb3QyIikNCmBgYA0KDQpgYGB7ciBoZWxwcyB3cmFuZ2xlIGRhdGEsIG1lc3NhZ2UgPSBGQUxTRSwgZWNobyA9IFRSVUV9DQpsaWJyYXJ5KCd0aWR5dmVyc2UnKQ0KYGBgDQoNCmBgYHtyIGhlbHBzIHdyYW5nbGUgZGF0ZSBhdHRyaWJ1dGVzLCBtZXNzYWdlID0gRkFMU0UsIGVjaG8gPSBUUlVFfQ0KbGlicmFyeSgnbHVicmlkYXRlJykNCmBgYA0KDQpgYGB7ciBoZWxwcyB2aXN1YWxpemUgZGF0YSwgbWVzc2FnZSA9IEZBTFNFfQ0KbGlicmFyeShnZ3Bsb3QyKQ0KYGBgDQoNCg0KYGBge3IgZGlzcGxheXMgeW91ciB3b3JraW5nIGRpcmVjdG9yeX0NCmdldHdkKCkNCmBgYA0KDQpgYGB7ciBzZXRzIHlvdXIgd29ya2luZyBkaXJlY3RvcnkgdG8gc2ltcGxpZnkgY2FsbHMgdG8gZGF0YX0NCnNldHdkKCJDOi9Vc2Vycy9uaWNvbC9PbmVEcml2ZS9Eb2N1bWVudG9zL0FuYWxpc2lzIGRlIERhdG9zIGRlIEdvb2dsZS9DYXNvIGVzdGlkaW8xL0RpdnZ5X0V4ZXJjaXNlX0Z1bGxfWWVhcl9BbmFseXNpcy9kaXZ2eV9leGVyY2lzZV9mdWxsX3llYXJfYW5hbHlzaXMiKQ0KYGBgDQoNCiMjIFNURVAgMTogQ09MTEVDVCBEQVRBDQoNClVwbG9hZCBEaXZ2eSBkYXRhc2V0cyAoY3N2IGZpbGVzKSBoZXJlDQoNCmBgYHtyfQ0KcTJfMjAxOSA8LSByZWFkLmNzdigiRGF0YS9EaXZ2eV9Ucmlwc18yMDE5X1EyLmNzdiIpDQpgYGANCg0KDQpgYGB7cn0NCnEzXzIwMTkgPC0gcmVhZC5jc3YoIkRhdGEvRGl2dnlfVHJpcHNfMjAxOV9RMy5jc3YiKQ0KYGBgDQoNCg0KYGBge3J9DQpxNF8yMDE5IDwtIHJlYWQuY3N2KCJEYXRhL0RpdnZ5X1RyaXBzXzIwMTlfUTQuY3N2IikNCmBgYA0KDQoNCmBgYHtyfQ0KcTFfMjAyMCA8LSByZWFkLmNzdigiRGF0YS9EaXZ2eV9Ucmlwc18yMDIwX1ExLmNzdiIpDQpgYGANCg0KIyMgU1RFUCAyOiBXUkFOR0xFIERBVEEgQU5EIENPTUJJTkUgSU5UTyBBIFNJTkdMRSBGSUxFDQoNCiMjIyMgQ29tcGFyZSBjb2x1bW4gbmFtZXMgZWFjaCBvZiB0aGUgZmlsZXMNCg0KV2hpbGUgdGhlIG5hbWVzIGRvbid0IGhhdmUgdG8gYmUgaW4gdGhlIHNhbWUgb3JkZXIsIHRoZXkgRE8gbmVlZCB0byBtYXRjaCBwZXJmZWN0bHkgYmVmb3JlIHdlIGNhbiB1c2UgYSBjb21tYW5kIHRvIGpvaW4gdGhlbSBpbnRvIG9uZSBmaWxlDQoNCmBgYHtyfQ0KY29sbmFtZXMocTNfMjAxOSkNCmBgYA0KDQoNCmBgYHtyfQ0KY29sbmFtZXMocTRfMjAxOSkNCmBgYA0KDQoNCmBgYHtyfQ0KY29sbmFtZXMocTJfMjAxOSkNCmBgYA0KDQoNCmBgYHtyfQ0KY29sbmFtZXMocTFfMjAyMCkNCmBgYA0KDQojIyMjIFJlbmFtZSBjb2x1bW5zICB0byBtYWtlIHRoZW0gY29uc2lzZW50IHdpdGggcTFfMjAyMCAoYXMgdGhpcyB3aWxsIGJlIHRoZSBzdXBwb3NlZCBnb2luZy1mb3J3YXJkIHRhYmxlIGRlc2lnbiBmb3IgRGl2dnkpDQoNCmBgYHtyIHJlbmFtZSBjb2x1bW5zIHE0XzIwMTl9DQpxNF8yMDE5IDwtIHJlbmFtZShxNF8yMDE5LHJpZGVfaWQgPSB0cmlwX2lkLCByaWRlYWJsZV90eXBlID0gYmlrZWlkLCBzdGFydGVkX2F0ID0gc3RhcnRfdGltZSwgZW5kZWRfYXQgPSBlbmRfdGltZSwgc3RhcnRfc3RhdGlvbl9uYW1lID0gZnJvbV9zdGF0aW9uX25hbWUsIHN0YXJ0X3N0YXRpb25faWQgPSBmcm9tX3N0YXRpb25faWQsIGVuZF9zdGF0aW9uX25hbWUgPSB0b19zdGF0aW9uX25hbWUsIGVuZF9zdGF0aW9uX2lkID0gdG9fc3RhdGlvbl9pZCwgbWVuYmVyX2Nhc3VhbCA9IHVzZXJ0eXBlKQ0KYGBgDQoNCg0KYGBge3IgcmVuYW1lIGNvbHVtbnMgcTNfMjAxOX0NCnEzXzIwMTkgPC0gcmVuYW1lKHEzXzIwMTksIHJpZGVfaWQgPSB0cmlwX2lkLCByaWRlYWJsZV90eXBlID0gYmlrZWlkLCBzdGFydGVkX2F0ID0gc3RhcnRfdGltZSwgZW5kZWRfYXQgPSBlbmRfdGltZSwgc3RhcnRfc3RhdGlvbl9uYW1lID0gZnJvbV9zdGF0aW9uX25hbWUsIHN0YXJ0X3N0YXRpb25faWQgPSBmcm9tX3N0YXRpb25faWQsIGVuZF9zdGF0aW9uX25hbWUgPSB0b19zdGF0aW9uX25hbWUsIGVuZF9zdGF0aW9uX2lkID0gdG9fc3RhdGlvbl9pZCwgbWVtYmVyX2Nhc3VhbCA9IHVzZXJ0eXBlKQ0KYGBgDQoNCmBgYHtyIHJlbmFtZSBjb2x1bW5zIHEyXzIwMTl9DQpxMl8yMDE5IDwtIHJlbmFtZShxMl8yMDE5LCByaWRlX2lkID0gIlgwMS4uLlJlbnRhbC5EZXRhaWxzLlJlbnRhbC5JRCIsIHJpZGVhYmxlX3R5cGUgPSAiWDAxLi4uUmVudGFsLkRldGFpbHMuQmlrZS5JRCIsIHN0YXJ0ZWRfYXQgPSAiWDAxLi4uUmVudGFsLkRldGFpbHMuTG9jYWwuU3RhcnQuVGltZSIsIGVuZGVkX2F0ID0gIlgwMS4uLlJlbnRhbC5EZXRhaWxzLkxvY2FsLkVuZC5UaW1lIiwgc3RhcnRfc3RhdGlvbl9uYW1lID0gIlgwMy4uLlJlbnRhbC5TdGFydC5TdGF0aW9uLk5hbWUiLCBzdGFydF9zdGF0aW9uX2lkID0gIlgwMy4uLlJlbnRhbC5TdGFydC5TdGF0aW9uLklEIiwgZW5kX3N0YXRpb25fbmFtZSA9ICJYMDIuLi5SZW50YWwuRW5kLlN0YXRpb24uTmFtZSIsIGVuZF9zdGF0aW9uX2lkID0gIlgwMi4uLlJlbnRhbC5FbmQuU3RhdGlvbi5JRCIsIG1lbWJlcl9jYXN1YWwgPSAiVXNlci5UeXBlIikNCmBgYA0KIyMjIyBJbnNwZWN0IHRoZSBkYXRhZnJhbWVzIGFuZCBsb29rIGZvciBpbmNvbmdydWVuY2llcw0KDQpgYGB7cn0NCnN0cihxMV8yMDIwKQ0KYGBgDQoNCmBgYHtyfQ0Kc3RyKHE0XzIwMTkpDQpgYGANCg0KYGBge3J9DQpzdHIocTNfMjAxOSkNCmBgYA0KDQpgYGB7cn0NCnN0cihxMl8yMDE5KQ0KYGBgDQoNCiMjIyMgQ29udmVydCByaWRlX2lkIGFuZCByaWRlYWJsZV90eXBlIHRvIGNoYXJhY3RlciBzbyB0aGF0IHRoZXkgY2FuIHN0YWNrIGNvcnJlY3RseQ0KDQpgYGB7cn0NCnE0XzIwMTkgPC0gbXV0YXRlKHE0XzIwMTksIHJpZGVfaWQgPSBhcy5jaGFyYWN0ZXIocmlkZV9pZCksIHJpZGVhYmxlX3R5cGUgPSBhcy5jaGFyYWN0ZXIocmlkZWFibGVfdHlwZSkpDQpgYGANCg0KDQpgYGB7cn0NCnEzXzIwMTkgPC0gbXV0YXRlKHEzXzIwMTksIHJpZGVfaWQgPSBhcy5jaGFyYWN0ZXIocmlkZV9pZCksIHJpZGVhYmxlX3R5cGUgPSBhcy5jaGFyYWN0ZXIocmlkZWFibGVfdHlwZSkpDQpgYGANCg0KDQpgYGB7cn0NCnEyXzIwMTkgPC0gbXV0YXRlKHEyXzIwMTksIHJpZGVfaWQgPSBhcy5jaGFyYWN0ZXIocmlkZV9pZCksIHJpZGVhYmxlX3R5cGUgPSBhcy5jaGFyYWN0ZXIocmlkZWFibGVfdHlwZSkpDQpgYGANCg0KIyMjIyBTdGFjayBpbmRpdmlkdWFsIHF1YXJ0ZXIncyBkYXRhIGZyYW1lcyBpbnRvIG9uZSBiaWcgZGF0YSBmcmFtZQ0KDQpgYGB7cn0NCmFsbF90cmlwcyA8LSBiaW5kX3Jvd3MocTJfMjAxOSwgcTNfMjAxOSwgcTRfMjAxOSwgcTFfMjAyMCkNCmBgYA0KDQojIyMjIFJlbW92ZSBsYXQsIGxvbmcsIGJpcnRoeWVhciwgYW5kIGdlbmRlciBmaWVsZHMgYXMgdGhpcyBkYXRhIHdhcyBkcm9wcGVkIGJlZ2lubmluZyBpbiAyMDIwDQoNCmBgYHtyfQ0KYWxsX3RyaXBzIDwtIGFsbF90cmlwcyAlPiUNCiAgc2VsZWN0KC1jKHN0YXJ0X2xhdCwgc3RhcnRfbG5nLCBlbmRfbGF0LCBlbmRfbG5nLCBiaXJ0aHllYXIsIGdlbmRlciwgIlgwMS4uLlJlbnRhbC5EZXRhaWxzLkR1cmF0aW9uLkluLlNlY29uZHMuVW5jYXBwZWQiLCAiWDA1Li4uTWVtYmVyLkRldGFpbHMuTWVtYmVyLkJpcnRoZGF5LlllYXIiLCAiTWVtYmVyLkdlbmRlciIsICJ0cmlwZHVyYXRpb24iKSkNCmBgYA0KDQoNCiMjIFNURVAgMzogQ0xFQU4gVVAgQU5EIEFERCBEQVRBIFRPIFBSRVBBUkUgRk9SIEFOQUxZU0lTDQoNCiMjIyMgSW5zcGVjdCB0aGUgbmV3IHRhYmxlIHRoYXQgaGFzIGJlZW4gY3JlYXRlZA0KDQpgYGB7ciBMaXN0IG9mIGNvbHVtbiBuYW1lc30NCmNvbG5hbWVzKGFsbF90cmlwcykNCmBgYA0KDQpgYGB7ciBIb3cgbWFueSByb3dzIGFyZSBpbiBkYXRhIGZyYW1lP30NCm5yb3coYWxsX3RyaXBzKQ0KYGBgDQoNCmBgYHtyIERpbWVuc2lvbnMgb2YgdGhlIGRhdGEgZnJhbWU/fQ0KZGltKGFsbF90cmlwcykNCmBgYA0KDQpgYGB7ciBTZWUgdGhlIGZpcnN0IDYgcm93cyBvZiBkYXRhIGZyYW1lfQ0KaGVhZChhbGxfdHJpcHMpDQpgYGANCg0KYGBge3IgU2VlIGxpc3Qgb2YgY29sdW1ucyBhbmQgZGF0YSB0eXBlc30NCnN0cihhbGxfdHJpcHMpDQpgYGANCg0KYGBge3IsIFN0YXRpc3RpY2FsIHN1bW1hcnkgb2YgZGF0YS4gTWFpbmx5IGZvciBudW1lcmljc30NCnN1bW1hcnkoYWxsX3RyaXBzKQ0KYGBgDQoNCg0KIyMjIyBUaGVyZSBhcmUgYSBmZXcgcHJvYmxlbXMgd2Ugd2lsbCBuZWVkIHRvIGZpeDoNCg0KLSAoMSkgSW4gdGhlICJtZW1iZXJfY2FzdWFsIiBjb2x1bW4sIHRoZXJlIGFyZSB0d28gbmFtZXMgZm9yIG1lbWJlcnMgKCJtZW1iZXIiIGFuZCAiU3Vic2NyaWJlciIpIGFuZCB0d28gbmFtZXMgZm9yIGNhc3VhbCByaWRlcnMgKCJDdXN0b21lciIgYW5kICJjYXN1YWwiKS4gV2Ugd2lsbCBuZWVkIHRvIGNvbnNvbGlkYXRlIHRoYXQgZnJvbSBmb3VyIHRvIHR3byBsYWJlbHMuDQoNCi0gKDIpIFRoZSBkYXRhIGNhbiBvbmx5IGJlIGFnZ3JlZ2F0ZWQgYXQgdGhlIHJpZGUtbGV2ZWwsIHdoaWNoIGlzIHRvbyBncmFudWxhci4gV2Ugd2lsbCB3YW50IHRvIGFkZCBzb21lIGFkZGl0aW9uYWwgY29sdW1ucyBvZiBkYXRhIC0tIHN1Y2ggYXMgZGF5LCBtb250aCwgeWVhciAtLSB0aGF0IHByb3ZpZGUgYWRkaXRpb25hbCBvcHBvcnR1bml0aWVzIHRvIGFnZ3JlZ2F0ZSB0aGUgZGF0YS4NCg0KXyAoMykgV2Ugd2lsbCB3YW50IHRvIGFkZCBhIGNhbGN1bGF0ZWQgZmllbGQgZm9yIGxlbmd0aCBvZiByaWRlIHNpbmNlIHRoZSAyMDIwUTEgZGF0YSBkaWQgbm90IGhhdmUgdGhlICJ0cmlwZHVyYXRpb24iIGNvbHVtbi4gV2Ugd2lsbCBhZGQgInJpZGVfbGVuZ3RoIiB0byB0aGUgZW50aXJlIGRhdGFmcmFtZSBmb3IgY29uc2lzdGVuY3kuDQoNCi0gKDQpIFRoZXJlIGFyZSBzb21lIHJpZGVzIHdoZXJlIHRyaXBkdXJhdGlvbiBzaG93cyB1cCBhcyBuZWdhdGl2ZSwgaW5jbHVkaW5nIHNldmVyYWwgaHVuZHJlZCByaWRlcyB3aGVyZSBEaXZ2eSB0b29rIGJpa2VzIG91dCBvZiBjaXJjdWxhdGlvbiBmb3IgUXVhbGl0eSBDb250cm9sIHJlYXNvbnMuIFdlIHdpbGwgd2FudCB0byBkZWxldGUgdGhlc2UgcmlkZXMuDQoNCiMjIyMgSW4gdGhlICJtZW1iZXJfY2FzdWFsIiBjb2x1bW4sIHJlcGxhY2UgIlN1YnNjcmliZXIiIHdpdGggIm1lbWJlciIgYW5kICJDdXN0b21lciIgd2l0aCAiY2FzdWFsIg0KDQotIEJlZm9yZSAyMDIwLCBEaXZ2eSB1c2VkIGRpZmZlcmVudCBsYWJlbHMgZm9yIHRoZXNlIHR3byB0eXBlcyBvZiByaWRlcnMgLi4uIHdlIHdpbGwgd2FudCB0byBtYWtlIG91ciBkYXRhZnJhbWUgY29uc2lzdGVudCB3aXRoIHRoZWlyIGN1cnJlbnQgbm9tZW5jbGF0dXJlDQoNCi0gTi5CLjogIkxldmVsIiBpcyBhIHNwZWNpYWwgcHJvcGVydHkgb2YgYSBjb2x1bW4gdGhhdCBpcyByZXRhaW5lZCBldmVuIGlmIGEgc3Vic2V0IGRvZXMgbm90IGNvbnRhaW4gYW55IHZhbHVlcyBmcm9tIGEgc3BlY2lmaWMgbGV2ZWwNCg0KLSBCZWdpbiBieSBzZWVpbmcgaG93IG1hbnkgb2JzZXJ2YXRpb25zIGZhbGwgdW5kZXIgZWFjaCB1c2VydHlwZQ0KDQpgYGB7cn0NCnRhYmxlKGFsbF90cmlwcyRtZW1iZXJfY2FzdWFsKQ0KYGBgDQoNCiMjIyMgUmVhc3NpZ24gdG8gdGhlIGRlc2lyZWQgdmFsdWVzICh3ZSB3aWxsIGdvIHdpdGggdGhlIGN1cnJlbnQgMjAyMCBsYWJlbHMpDQoNCmBgYHtyfQ0KYWxsX3RyaXBzIDwtIGFsbF90cmlwcyAlPiUNCiAgbXV0YXRlKG1lbWJlcl9jYXN1YWwgPSByZWNvZGUobWVtYmVyX2Nhc3VhbCwgIlN1YnNjcmliZXIiID0gIm1lbWJlciIsICJDdXN0b21lciIgPSAiY2FzdWFsIikpDQpgYGANCg0KIyMjIyBDaGVjayB0byBtYWtlIHN1cmUgdGhlIHByb3BlciBudW1iZXIgb2Ygb2JzZXJ2YXRpb25zIHdlcmUgcmVhc3NpZ25lZA0KDQpgYGB7cn0NCnRhYmxlKGFsbF90cmlwcyRtZW1iZXJfY2FzdWFsKQ0KYGBgDQoNCiMjIyMgQWRkIGNvbHVtbnMgdGhhdCBsaXN0IHRoZSBkYXRlLCBtb250aCwgZGF5LCBhbmQgeWVhciBvZiBlYWNoIHJpZGUNCg0KVGhpcyB3aWxsIGFsbG93IHVzIHRvIGFnZ3JlZ2F0ZSByaWRlIGRhdGEgZm9yIGVhY2ggbW9udGgsIGRheSwgb3IgeWVhciAuLi4gYmVmb3JlIGNvbXBsZXRpbmcgdGhlc2Ugb3BlcmF0aW9ucyB3ZSBjb3VsZCBvbmx5IGFnZ3JlZ2F0ZSBhdCB0aGUgcmlkZSBsZXZlbA0KDQpgYGB7ciBUaGUgZGVmYXVsdCBmb3JtYXQgaXMgeXl5eS1tbS1kZH0NCmFsbF90cmlwcyRkYXRlIDwtIGFzLkRhdGUoYWxsX3RyaXBzJHN0YXJ0ZWRfYXQpDQpgYGANCg0KYGBge3J9DQphbGxfdHJpcHMkbW9udGggPC0gZm9ybWF0KGFzLkRhdGUoYWxsX3RyaXBzJGRhdGUpLCIlbSIpDQpgYGANCg0KYGBge3J9DQphbGxfdHJpcHMkZGF5IDwtIGZvcm1hdChhcy5EYXRlKGFsbF90cmlwcyRkYXRlKSwgIiVkIikNCmBgYA0KDQoNCmBgYHtyfQ0KYWxsX3RyaXBzJHllYXIgPC0gZm9ybWF0KGFzLkRhdGUoYWxsX3RyaXBzJGRhdGUpLCAiJVkiKQ0KYGBgDQoNCg0KYGBge3J9DQphbGxfdHJpcHMkZGF5X29mX3dlZWsgPC0gZm9ybWF0KGFzLkRhdGUoYWxsX3RyaXBzJGRhdGUpLCAiJUEiKQ0KYGBgDQoNCiMjIyMgQWRkIGEgInJpZGVfbGVuZ3RoIiBjYWxjdWxhdGlvbiB0byBhbGxfdHJpcHMgKGluIHNlY29uZHMpDQoNCmBgYHtyfQ0KYWxsX3RyaXBzJHJpZGVfbGVuZ3RoIDwtIGRpZmZ0aW1lKGFsbF90cmlwcyRlbmRlZF9hdCwgYWxsX3RyaXBzJHN0YXJ0ZWRfYXQpDQpgYGANCg0KIyMjIyBJbnNwZWN0IHRoZSBzdHJ1Y3R1cmUgb2YgdGhlIGNvbHVtbnMNCg0KYGBge3J9DQpzdHIoYWxsX3RyaXBzKQ0KYGBgDQoNCiMjIyMgQ29udmVydCAicmlkZV9sZW5ndGgiIGZyb20gRmFjdG9yIHRvIG51bWVyaWMgc28gd2UgY2FuIHJ1biBjYWxjdWxhdGlvbnMgb24gdGhlIGRhdGENCg0KYGBge3J9DQppcy5mYWN0b3IoYWxsX3RyaXBzJHJpZGVfbGVuZ3RoKQ0KYGBgDQoNCmBgYHtyfQ0KYWxsX3RyaXBzJHJpZGVfbGVuZ3RoIDwtIGFzLm51bWVyaWMoYXMuY2hhcmFjdGVyKGFsbF90cmlwcyRyaWRlX2xlbmd0aCkpDQpgYGANCg0KYGBge3J9DQppcy5udW1lcmljKGFsbF90cmlwcyRyaWRlX2xlbmd0aCkNCmBgYA0KDQoNCiMjIyMgUmVtb3ZlICJiYWQiIGRhdGENClRoZSBkYXRhZnJhbWUgaW5jbHVkZXMgYSBmZXcgaHVuZHJlZCBlbnRyaWVzIHdoZW4gYmlrZXMgd2VyZSB0YWtlbiBvdXQgb2YgZG9ja3MgYW5kIGNoZWNrZWQgZm9yIHF1YWxpdHkgYnkgRGl2dnkgb3IgcmlkZV9sZW5ndGggd2FzIG5lZ2F0aXZlDQojIyBXZSB3aWxsIGNyZWF0ZSBhIG5ldyB2ZXJzaW9uIG9mIHRoZSBkYXRhZnJhbWUgKHYyKSBzaW5jZSBkYXRhIGlzIGJlaW5nIHJlbW92ZWQNCg0KYGBge3J9DQphbGxfdHJpcHNfdjIgPC0gYWxsX3RyaXBzWyEoYWxsX3RyaXBzJHN0YXJ0X3N0YXRpb25fbmFtZSA9PSAiSFEgUVIiIHwgYWxsX3RyaXBzJHJpZGVfbGVuZ3RoPDApLF0NCmBgYA0KDQojIyBTVEVQIDQ6IENPTkRVQ1QgREVTQ1JJUFRJVkUgQU5BTFlTSVMNCg0KIyMjIyBEZXNjcmlwdGl2ZSBhbmFseXNpcyBvbiByaWRlX2xlbmd0aA0KDQpgYGB7ciBzdHJhaWdodCBhdmVyYWdlICh0b3RhbCByaWRlIGxlbmd0aCAvIHJpZGVzKX0NCm1lYW4oYWxsX3RyaXBzX3YyJHJpZGVfbGVuZ3RoKQ0KYGBgDQoNCmBgYHtyIG1pZHBvaW50IG51bWJlciBpbiB0aGUgYXNjZW5kaW5nIGFycmF5IG9mIHJpZGUgbGVuZ3Roc30NCm1lZGlhbihhbGxfdHJpcHNfdjIkcmlkZV9sZW5ndGgpDQpgYGANCg0KYGBge3IgbG9uZ2VzdCByaWRlfQ0KbWF4KGFsbF90cmlwc192MiRyaWRlX2xlbmd0aCkNCmBgYA0KDQpgYGB7ciBzaG9ydGVzdCByaWRlfQ0KbWluKGFsbF90cmlwc192MiRyaWRlX2xlbmd0aCkNCmBgYA0KDQojIyMjIFlvdSBjYW4gY29uZGVuc2UgdGhlIGZvdXIgbGluZXMgYWJvdmUgdG8gb25lIGxpbmUgdXNpbmcgc3VtbWFyeSgpIG9uIHRoZSBzcGVjaWZpYyBhdHRyaWJ1dGUNCg0KYGBge3J9DQpzdW1tYXJ5KGFsbF90cmlwc192MiRyaWRlX2xlbmd0aCkNCmBgYA0KDQojIyMjIENvbXBhcmUgbWVtYmVycyBhbmQgY2FzdWFsIHVzZXJzDQoNCmBgYHtyfQ0KYWdncmVnYXRlKGFsbF90cmlwc192MiRyaWRlX2xlbmd0aCB+IGFsbF90cmlwc192MiRtZW1iZXJfY2FzdWFsLCBGVU4gPSBtZWFuKQ0KYGBgDQoNCmBgYHtyfQ0KYWdncmVnYXRlKGFsbF90cmlwc192MiRyaWRlX2xlbmd0aCB+IGFsbF90cmlwc192MiRtZW1iZXJfY2FzdWFsLCBGVU4gPSBtZWRpYW4pDQpgYGANCg0KYGBge3J9DQphZ2dyZWdhdGUoYWxsX3RyaXBzX3YyJHJpZGVfbGVuZ3RoIH4gYWxsX3RyaXBzX3YyJG1lbWJlcl9jYXN1YWwsIEZVTiA9IG1heCkNCmBgYA0KDQpgYGB7cn0NCmFnZ3JlZ2F0ZShhbGxfdHJpcHNfdjIkcmlkZV9sZW5ndGggfiBhbGxfdHJpcHNfdjIkbWVtYmVyX2Nhc3VhbCwgRlVOID0gbWluKQ0KYGBgDQoNCg0KIyMjIyBTZWUgdGhlIGF2ZXJhZ2UgcmlkZSB0aW1lIGJ5IGVhY2ggZGF5IGZvciBtZW1iZXJzIHZzIGNhc3VhbCB1c2Vycw0KDQpgYGB7cn0NCmFnZ3JlZ2F0ZShhbGxfdHJpcHNfdjIkcmlkZV9sZW5ndGggfiBhbGxfdHJpcHNfdjIkbWVtYmVyX2Nhc3VhbCArIGFsbF90cmlwc192MiRkYXlfb2Zfd2VlaywgRlVOID0gbWVhbikNCmBgYA0KDQoNCiMjIyMgTm90aWNlIHRoYXQgdGhlIGRheXMgb2YgdGhlIHdlZWsgYXJlIG91dCBvZiBvcmRlci4gTGV0J3MgZml4IHRoYXQuDQoNCmBgYHtyfQ0KYWxsX3RyaXBzX3YyJGRheV9vZl93ZWVrIDwtIG9yZGVyZWQoYWxsX3RyaXBzX3YyJGRheV9vZl93ZWVrLCBsZXZlbHM9YygiU3VuZGF5IiwgIk1vbmRheSIsICJUdWVzZGF5IiwgIlRodXJzZGF5IiwgIkZyaWRheSIsICJTYXR1cmRheSIpKQ0KYGBgDQoNCiMjIyMgTm93LCBsZXQncyBydW4gdGhlIGF2ZXJhZ2UgcmlkZSB0aW1lIGJ5IGVhY2ggZGF5IGZvciBtZW1iZXJzIHZzIGNhc3VhbCB1c2Vycw0KDQpgYGB7cn0NCmFnZ3JlZ2F0ZShhbGxfdHJpcHNfdjIkcmlkZV9sZW5ndGggfiBhbGxfdHJpcHNfdjIkbWVtYmVyX2Nhc3VhbCArIGFsbF90cmlwc192MiRkYXlfb2Zfd2VlaywgRlVOID0gbWVhbikNCmBgYA0KDQojIyMjIGFuYWx5emUgcmlkZXJzaGlwIGRhdGEgYnkgdHlwZSBhbmQgd2Vla2RheQ0KDQpgYGB7ciBjcmVhdGVzIHdlZWtkYXkgZmllbGQgdXNpbmcgd2RheSgpfQ0KYWxsX3RyaXBzX3YyICU+JQ0KICBtdXRhdGUod2Vla2RheSA9IHdkYXkoc3RhcnRlZF9hdCwgbGFiZWwgPSBUUlVFKSkgJT4lDQogIGdyb3VwX2J5KG1lbWJlcl9jYXN1YWwsIHdlZWtkYXkpICU+JQ0KICBzdW1tYXJpc2UobnVtYmVyX29mX3JpZGVzID0gbigpLCBhdmVyYWdlX2R1cmF0aW9uID0gbWVhbihyaWRlX2xlbmd0aCkpJT4lDQogIGFycmFuZ2UobWVtYmVyX2Nhc3VhbCwgd2Vla2RheSkNCmBgYA0KDQojIyMjIExldCdzIHZpc3VhbGl6ZSB0aGUgbnVtYmVyIG9mIHJpZGVzIGJ5IHJpZGVyIHR5cGUNCg0KYGBge3J9DQphbGxfdHJpcHNfdjIgJT4lDQogIG11dGF0ZSggd2Vla2RheSA9IHdkYXkoc3RhcnRlZF9hdCwgbGFiZWwgPSBUUlVFKSkgJT4lDQogIGdyb3VwX2J5KG1lbWJlcl9jYXN1YWwsIHdlZWtkYXkpICU+JQ0KICBzdW1tYXJpc2UobnVtYmVyX29mX3JpZGVzID0gbigpLCBhdmVyYWdlX2R1cmF0aW9uID0gbWVhbihyaWRlX2xlbmd0aCkpICU+JQ0KICBhcnJhbmdlKG1lbWJlcl9jYXN1YWwsIHdlZWtkYXkpICU+JQ0KICBnZ3Bsb3QoYWVzKHggPSB3ZWVrZGF5LCB5ID0gbnVtYmVyX29mX3JpZGVzLCBmaWxsID0gbWVtYmVyX2Nhc3VhbCkpICsNCiAgZ2VvbV9jb2wocG9zaXRpb24gPSAiZG9kZ2UiKQ0KYGBgDQoNCiMjIyMgTGV0J3MgY3JlYXRlIGEgdmlzdWFsaXphdGlvbiBmb3IgYXZlcmFnZSBkdXJhdGlvbg0KDQpgYGB7cn0NCmFsbF90cmlwc192MiAlPiUNCiAgbXV0YXRlKCB3ZWVrZGF5ID0gd2RheShzdGFydGVkX2F0LCBsYWJlbCA9IFRSVUUpKSAlPiUNCiAgZ3JvdXBfYnkobWVtYmVyX2Nhc3VhbCwgd2Vla2RheSkgJT4lDQogIHN1bW1hcmlzZShudW1iZXJfb2ZfcmlkZXMgPSBuKCksIGF2ZXJhZ2VfZHVyYXRpb24gPSBtZWFuKHJpZGVfbGVuZ3RoKSkgJT4lDQogIGFycmFuZ2UobWVtYmVyX2Nhc3VhbCwgd2Vla2RheSkgJT4lDQogIGdncGxvdChhZXMoeCA9IHdlZWtkYXksIHkgPWF2ZXJhZ2VfZHVyYXRpb24sIGZpbGwgPSBtZW1iZXJfY2FzdWFsKSkgKw0KICBnZW9tX2NvbChwb3NpdGlvbiA9ICJkb2RnZSIpDQpgYGANCg0KIyMgU1RFUCA1OiBFWFBPUlQgU1VNTUFSWSBGSUxFIEZPUiBGVVJUSEVSIEFOQUxZU0lTDQoNCiMjIyMgQ3JlYXRlIGEgY3N2IGZpbGUgdGhhdCB3ZSB3aWxsIHZpc3VhbGl6ZSBpbiBFeGNlbCwgVGFibGVhdSwgb3IgbXkgcHJlc2VudGF0aW9uIHNvZnR3YXJlDQoNCmBgYHtyfQ0KY291bnRzIDwtIGFnZ3JlZ2F0ZShhbGxfdHJpcHNfdjIkcmlkZV9sZW5ndGggfiBhbGxfdHJpcHNfdjIkbWVtYmVyX2Nhc3VhbCArIGFsbF90cmlwc192MiRkYXlfb2Zfd2VlaywgRlVOID0gbWVhbikNCmBgYA0KDQpgYGB7cn0NCndyaXRlLmNzdihjb3VudHMsIGZpbGUgPSAnfi9BbmFsaXNpcyBkZSBEYXRvcyBkZSBHb29nbGUvQ2FzbyBlc3RpZGlvMS9EaXZ2eV9FeGVyY2lzZV9GdWxsX1llYXJfQW5hbHlzaXMvZGl2dnlfZXhlcmNpc2VfZnVsbF95ZWFyX2FuYWx5c2lzL2F2Z19yaWRlX2xlbmd0aC5jc3YnKQ0KYGBgDQoNCg0KDQo=