INTRODUCTION

This capstone project is the final project in my Google Data Analytics Professional Certificate Course. In this case study, I will be analyzing a public dataset for a fictional company called Cyclistic, provided by the course. Here, I will be using R programming language for this analysis because of its potential benefits to reproducibility, transparency, easy statistical analysis tools and data visualizations.

The following sets of data analysis process will be followed:

Ask,

Prepare,

Process,

Analyze,

Share,

Act.

The case study road map as listed below will be followed on each step

Codes, when needed.

Key tasks.

Deliverable.

I will use Cyclistic’s trip data ranging from July 2023 to June 2024 to analyze and identify trends.The data has been made available by Motivate International Inc. under this license. Datasets are available here link. Key tasks

Download data and store it appropriately. Data has been downloaded and copies have been stored securely on my computer. Identify how it’s organized. The data is in CSV (comma-separated values) format, and there are a total of 13 columns. Sort and filter the data. For this analysis, I will be using data for the year 2019 and 2020. Determine the credibility of the data. For the purposes of this case study, the datasets are appropriate and will enable me to answer the business questions. The data has been made available by Motivate International Inc. This is public data that I can use to explore how different customer types are using Cyclistic bikes. But data-privacy issues will prohibit me from using rider’s personally identifiable information and this will prevent me from determining if riders have purchased multiple single passes. All ride ids are unique. Deliverable

A description of all data sources used The main source of all the data used was provided by the Cyclistic Company.

library(tidyverse)
library(DT)
library(lubridate)
options(scipen = 999)
library(modeest)

Importing Data from 2023

Error: '\U' used without hex digits in character string (<input>:1:31)

Import Data from 2024

Combining Rows

#bind rows 2023
q3_2023 <- rbind(July_2023,August_2023,September_2023)


q4_2023 <- rbind(October_2023,Novemeber_2023,December_2023)

#bindrows 2024
q1_2024 <- rbind(January_2024,February_2024,March_2024)


q2_2024 <- rbind(April_2024,May_2024,June_2024)


all_trips <-  rbind(q3_2023,q4_2023,q1_2024,q2_2024)

PROCESS

Cleaning up data and adding data to prepare for analysis

Key tasks

Check the data for errors.

Choose your tools.

Transform the data so you can work with it effectively.

Document the cleaning process.

Deliverables

Documentation of any cleaning or manipulation of data. I inspected the new table that has been created using the following code chunks

colnames(all_trips)  #List of column names
 [1] "ride_id"            "rideable_type"     
 [3] "started_at"         "ended_at"          
 [5] "start_station_name" "start_station_id"  
 [7] "end_station_name"   "end_station_id"    
 [9] "start_lat"          "start_lng"         
[11] "end_lat"            "end_lng"           
[13] "member_casual"     
nrow(all_trips)  #How many rows are in data frame
[1] 5734381
dim(all_trips)  #Dimensions of the data frame
[1] 5734381      13
head(all_trips)  #See the first 6 rows of data frame
tail(all_trips)  #see the last 6 rows of data frame
str(all_trips)  #See list of columns and data types (numeric, character, etc)
'data.frame':   5734381 obs. of  13 variables:
 $ ride_id           : chr  "9340B064F0AEE130" "D1460EE3CE0D8AF8" "DF41BE31B895A25E" "9624A293749EF703" ...
 $ rideable_type     : chr  "electric_bike" "classic_bike" "classic_bike" "electric_bike" ...
 $ started_at        : chr  "2023-07-23 20:06:14" "2023-07-23 17:05:07" "2023-07-23 10:14:53" "2023-07-21 08:27:44" ...
 $ ended_at          : chr  "2023-07-23 20:22:44" "2023-07-23 17:18:37" "2023-07-23 10:24:29" "2023-07-21 08:32:40" ...
 $ start_station_name: chr  "Kedzie Ave & 110th St" "Western Ave & Walton St" "Western Ave & Walton St" "Racine Ave & Randolph St" ...
 $ start_station_id  : chr  "20204" "KA1504000103" "KA1504000103" "13155" ...
 $ end_station_name  : chr  "Public Rack - Racine Ave & 109th Pl" "Milwaukee Ave & Grand Ave" "Damen Ave & Pierce Ave" "Clinton St & Madison St" ...
 $ end_station_id    : chr  "877" "13033" "TA1305000041" "TA1305000032" ...
 $ start_lat         : num  41.7 41.9 41.9 41.9 42 ...
 $ start_lng         : num  -87.7 -87.7 -87.7 -87.7 -87.7 ...
 $ end_lat           : num  41.7 41.9 41.9 41.9 42 ...
 $ end_lng           : num  -87.7 -87.6 -87.7 -87.6 -87.6 ...
 $ member_casual     : chr  "member" "member" "member" "member" ...
summary(all_trips)  #Statistical summary of data. Mainly for numeric
   ride_id          rideable_type       started_at       
 Length:5734381     Length:5734381     Length:5734381    
 Class :character   Class :character   Class :character  
 Mode  :character   Mode  :character   Mode  :character  
                                                         
                                                         
                                                         
                                                         
   ended_at         start_station_name start_station_id  
 Length:5734381     Length:5734381     Length:5734381    
 Class :character   Class :character   Class :character  
 Mode  :character   Mode  :character   Mode  :character  
                                                         
                                                         
                                                         
                                                         
 end_station_name   end_station_id       start_lat    
 Length:5734381     Length:5734381     Min.   :41.63  
 Class :character   Class :character   1st Qu.:41.88  
 Mode  :character   Mode  :character   Median :41.90  
                                       Mean   :41.90  
                                       3rd Qu.:41.93  
                                       Max.   :42.07  
                                                      
   start_lng         end_lat         end_lng      
 Min.   :-87.94   Min.   : 0.00   Min.   :-88.12  
 1st Qu.:-87.66   1st Qu.:41.88   1st Qu.:-87.66  
 Median :-87.64   Median :41.90   Median :-87.64  
 Mean   :-87.65   Mean   :41.90   Mean   :-87.65  
 3rd Qu.:-87.63   3rd Qu.:41.93   3rd Qu.:-87.63  
 Max.   :-87.46   Max.   :42.19   Max.   :  0.00  
                  NA's   :7919    NA's   :7919    
 member_casual     
 Length:5734381    
 Class :character  
 Mode  :character  
                   
                   
                   
                   

Adding 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. Therefore, the code chunks used are as follows

all_trips$date <- as.Date(all_trips$started_at) #The default format is yyyy-mm-dd
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")
all_trips$day_of_week <- format(as.Date(all_trips$date), "%A")

Adding a “ride_length” calculation to all_trips (in seconds) convert to minutes

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

str(all_trips)  #to inspect the structure of the columns
'data.frame':   5734381 obs. of  19 variables:
 $ ride_id           : chr  "9340B064F0AEE130" "D1460EE3CE0D8AF8" "DF41BE31B895A25E" "9624A293749EF703" ...
 $ rideable_type     : chr  "electric_bike" "classic_bike" "classic_bike" "electric_bike" ...
 $ started_at        : chr  "2023-07-23 20:06:14" "2023-07-23 17:05:07" "2023-07-23 10:14:53" "2023-07-21 08:27:44" ...
 $ ended_at          : chr  "2023-07-23 20:22:44" "2023-07-23 17:18:37" "2023-07-23 10:24:29" "2023-07-21 08:32:40" ...
 $ start_station_name: chr  "Kedzie Ave & 110th St" "Western Ave & Walton St" "Western Ave & Walton St" "Racine Ave & Randolph St" ...
 $ start_station_id  : chr  "20204" "KA1504000103" "KA1504000103" "13155" ...
 $ end_station_name  : chr  "Public Rack - Racine Ave & 109th Pl" "Milwaukee Ave & Grand Ave" "Damen Ave & Pierce Ave" "Clinton St & Madison St" ...
 $ end_station_id    : chr  "877" "13033" "TA1305000041" "TA1305000032" ...
 $ start_lat         : num  41.7 41.9 41.9 41.9 42 ...
 $ start_lng         : num  -87.7 -87.7 -87.7 -87.7 -87.7 ...
 $ end_lat           : num  41.7 41.9 41.9 41.9 42 ...
 $ end_lng           : num  -87.7 -87.6 -87.7 -87.6 -87.6 ...
 $ member_casual     : chr  "member" "member" "member" "member" ...
 $ date              : Date, format: "2023-07-23" ...
 $ month             : chr  "07" "07" "07" "07" ...
 $ day               : chr  "23" "23" "23" "21" ...
 $ year              : chr  "2023" "2023" "2023" "2023" ...
 $ day_of_week       : chr  "Sunday" "Sunday" "Sunday" "Friday" ...
 $ ride_length       : 'difftime' num  990 810 576 296 ...
  ..- attr(*, "units")= chr "secs"

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

all_trips$ride_length <- as.numeric(as.character(all_trips$ride_length))

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

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 or Zero, so I created a new version of the dataframe (v2) since data is being removed

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

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

sum(duplicated(all_trips_v2))

ANALYZE

All the data has been stored appropriately and has been prepared for analysis, so they are ready for exploration. Key tasks

Aggregate your data so it’s useful and accessible. Organize and format your data. Perform calculations. Identify trends and relationships. Deliverables

A summary of your analysis Conduct descriptive analysis - Descriptive analysis on ride_length (all figures in seconds)

mean(all_trips_v2$ride_length,na_rm=T) 
[1] 1099.923
median(all_trips_v2$ride_length,na_rm=T) 
[1] 585
mfv(all_trips_v2$ride_length,na_rm=T) 
[1] 292
max(all_trips_v2$ride_length,na_rm=T) 
[1] 5909344
min(all_trips_v2$ride_length,na_rm=T) 
[1] 0
range = max(all_trips_v2$ride_length,na_rm=T) - min(all_trips_v2$ride_length,na_rm=T) 
range
[1] 5909344

Let’s visualize members and casuals by the total ride taken (ride count)

all_trips_v2 %>% 
  group_by(member_casual) %>% 
  summarise(ride_count = n()) %>% 
  ggplot(aes(member_casual,ride_count,fill=member_casual))+geom_col(position = 'dodge')+
  labs(title = 'Total rides taken (ride_count) of Members and Casual riders')+
  theme_classic()+geom_col(width = .5,position = position_dodge(width=.5))+
  geom_text(aes(label=ride_count),size=4)+
  theme(plot.title = element_text(size=12, hjust = .4 ))

NA
all_trips_v2

From the above graph, we can observe that there are far more riders as registered members compared to casual rides based on the ride count.




Let’s see the average time ride by each day for members vs casual users

wdaytoavgridel <- all_trips_v2 %>% 
  select(ride_length, member_casual,day_of_week) %>% 
   mutate(ride_length=ride_length/60) %>% # ride length converted from seconds to minutes 

  group_by(member_casual,day_of_week) %>% 
  summarise(avgride_length=round(mean(ride_length)))
`summarise()` has grouped output by 'member_casual'. You can override using the `.groups` argument.
datatable(wdaytoavgridel)

average ride time by each day for member vs casual users (in minutes)

memcas_durationandridenumber <-  all_trips_v2 %>%  #week trends
    mutate(ride_length=ride_length/60) %>% # ride length converted from seconds to minutes
  mutate(weekday = wday(started_at, label = TRUE)) %>%
  group_by(member_casual, weekday) %>%  
  summarise(number_of_rides = n(), average_duration = round(mean(ride_length) )) %>%
  arrange(member_casual, weekday)

datatable(memcas_durationandridenumber)

total rides and average ride_time duration(in minutes) by each day for members vs casual riders


Let’s visualize the above table by days of the week and number of rides taken by member and casual riders.

all_trips_v2 %>% 
    mutate(ride_length=ride_length/60) %>% # ride length converted from seconds to minutes
  mutate(weekday = wday(started_at, label = TRUE)) %>%
  group_by(member_casual, weekday) %>%  
  summarise(number_of_rides = n(), average_duration = round(mean(ride_length))) %>%
  arrange(member_casual, weekday) %>% 
  ggplot(aes(weekday,number_of_rides,fill=member_casual))+theme_classic()+geom_col(position = 'dodge')+geom_text(aes(label=number_of_rides),size=2.8)+ggtitle('Total rides of Members and Casual riders Vs. Day of the week')+
  theme(plot.title = element_text(size=11,hjust=.3))
`summarise()` has grouped output by 'member_casual'. You can override using the `.groups` argument.

NA


Let’s visualize the average duration of Members and Casual riders Vs. Day of the week

all_trips_v2 %>%  
  mutate(ride_length=ride_length/60) %>% 
  mutate(weekday = wday(started_at, label = TRUE)) %>% 
  group_by(member_casual, weekday) %>% 
  summarise(number_of_rides = n()
            ,average_duration = round(mean(ride_length))) %>% 
  arrange(member_casual, weekday)  %>% 
  ggplot(aes(x = weekday, y = average_duration, fill = member_casual)) +
  geom_col(position = "dodge") +
  labs(title ="Average duration (minutes) of Members and Casual riders") +theme_classic()+
  geom_col(width=0.5, position = position_dodge(width=0.5)) +geom_text(aes(label=average_duration),size=2.8)+theme(plot.title = element_text(size = 10,hjust=.05))
`summarise()` has grouped output by 'member_casual'. You can override using the `.groups` argument.

From the graph above, it is observed that the casual riders ride the longest throughout the weekend and shortest duration between Tuesday and Thursday, while registered riders ride at a faster more consistent pace during the week with the longest rides on the weekend.

Let’s create a visualization for Total rides by members and casual riders by month


all_trips_v2 %>%  
   
  group_by(member_casual, month) %>% 
  summarise(number_of_rides = n(),.groups="drop") %>% 
  arrange(member_casual, month)  %>% 
  ggplot(aes(x = month, y = number_of_rides, fill = member_casual)) +
  labs(title ="Total rides by Members and Casual riders by Month") +
  theme(axis.text.x = element_text(angle = 45)) +theme_classic()+
  geom_col(width=0.5, position = position_dodge(width=0.5)) +geom_text(aes(label=number_of_rides),size=2.4)+
  theme(plot.title = element_text(size=11,hjust=.6))

NA

From the above graph, it is observed that members have the highest number of rides throughout the year with August being the month with the highest number of rides for members whilst july has the most casual riders


Let’s compare Members and Casual riders depending on ride distance.


all_trips_v2 %>% 
  group_by(member_casual) %>% drop_na() %>%
  summarise(average_ride_length_kilometers = mean(ride_length)) %>%
mutate(average_ride_length_Miles = round(( average_ride_length_kilometers * 2)/3 )) %>% 
  select(member_casual,average_ride_length_Miles) %>% 
  ggplot(aes(x= member_casual,y= average_ride_length_Miles,fill=member_casual)) + 
  geom_col()+geom_text(aes(label = average_ride_length_Miles))+
  labs(title = "Mean distance traveled by Members and Casual riders")+
  theme(plot.title = element_text(size=11,hjust=.2))+theme_classic()

NA
NA
NA

From the graph above, we can observe that the distance traveled by the casual riders is far more than the distance traveled by the member with a very large difference in miles. Roughly 855 miles is the average distance traveled by casual riders and 494 miles is the average distance traveled by registered members
note: converted kilometers to miles




Conclusion
  1. Members have more bikes compared to casual riders.
  2. We have more members riding in all months compared to casual riders.
  3. Casual riders travel for a longer time period.
  4. Members ride more throughout the entire weekday while the casual riders also have a high ride record during the weekends(Saturday and Sunday) compared to the other days of the week.
  5. Casual riders go farther in terms of distance.

Deliverable

  1. Have a slash sale or promo for casual riders so they can acquire more bikes and indulge them in the benefits of being a member.
  2. Host fun biking competitions with prizes at intervals for casual riders on the weekends. Given that there are a lot of members on weekends this could attract them to get a membership.
  3. Encourage casual riders to ride more throughout the year using additional advertising, handing out flyers or by giving them various coupons so as to convince them into becoming a member.
LS0tDQp0aXRsZTogIkJpa2UgU2hhcmUiDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCmF1dGhvcjogSGFycmlzb24NCg0KLS0tDQoNCklOVFJPRFVDVElPTg0KDQpUaGlzIGNhcHN0b25lIHByb2plY3QgaXMgdGhlIGZpbmFsIHByb2plY3QgaW4gbXkgR29vZ2xlIERhdGEgQW5hbHl0aWNzIFByb2Zlc3Npb25hbCBDZXJ0aWZpY2F0ZSBDb3Vyc2UuIEluIHRoaXMgY2FzZSBzdHVkeSwgSSB3aWxsIGJlIGFuYWx5emluZyBhIHB1YmxpYyBkYXRhc2V0IGZvciBhIGZpY3Rpb25hbCBjb21wYW55IGNhbGxlZCBDeWNsaXN0aWMsIHByb3ZpZGVkIGJ5IHRoZSBjb3Vyc2UuIEhlcmUsIEkgd2lsbCBiZSB1c2luZyBSIHByb2dyYW1taW5nIGxhbmd1YWdlIGZvciB0aGlzIGFuYWx5c2lzIGJlY2F1c2Ugb2YgaXRzIHBvdGVudGlhbCBiZW5lZml0cyB0byByZXByb2R1Y2liaWxpdHksIHRyYW5zcGFyZW5jeSwgZWFzeSBzdGF0aXN0aWNhbCBhbmFseXNpcyB0b29scyBhbmQgZGF0YSB2aXN1YWxpemF0aW9ucy4NCg0KVGhlIGZvbGxvd2luZyBzZXRzIG9mIGRhdGEgYW5hbHlzaXMgcHJvY2VzcyB3aWxsIGJlIGZvbGxvd2VkOg0KDQpBc2ssDQoNClByZXBhcmUsDQoNClByb2Nlc3MsDQoNCkFuYWx5emUsDQoNClNoYXJlLA0KDQpBY3QuDQoNClRoZSBjYXNlIHN0dWR5IHJvYWQgbWFwIGFzIGxpc3RlZCBiZWxvdyB3aWxsIGJlIGZvbGxvd2VkIG9uIGVhY2ggc3RlcA0KDQpDb2Rlcywgd2hlbiBuZWVkZWQuDQoNCktleSB0YXNrcy4NCg0KRGVsaXZlcmFibGUuDQoNCg0KDQpJIHdpbGwgdXNlIEN5Y2xpc3RpY+KAmXMgdHJpcCBkYXRhIHJhbmdpbmcgZnJvbSBKdWx5IDIwMjMgdG8gSnVuZSAyMDI0IHRvIGFuYWx5emUgYW5kIGlkZW50aWZ5IHRyZW5kcy5UaGUgZGF0YSBoYXMgYmVlbiBtYWRlIGF2YWlsYWJsZSBieSBNb3RpdmF0ZSBJbnRlcm5hdGlvbmFsIEluYy4gdW5kZXIgdGhpcyBsaWNlbnNlLiBEYXRhc2V0cyBhcmUgYXZhaWxhYmxlIGhlcmUgbGluay4NCktleSB0YXNrcw0KDQpEb3dubG9hZCBkYXRhIGFuZCBzdG9yZSBpdCBhcHByb3ByaWF0ZWx5Lg0KRGF0YSBoYXMgYmVlbiBkb3dubG9hZGVkIGFuZCBjb3BpZXMgaGF2ZSBiZWVuIHN0b3JlZCBzZWN1cmVseSBvbiBteSBjb21wdXRlci4NCklkZW50aWZ5IGhvdyBpdOKAmXMgb3JnYW5pemVkLg0KVGhlIGRhdGEgaXMgaW4gQ1NWIChjb21tYS1zZXBhcmF0ZWQgdmFsdWVzKSBmb3JtYXQsIGFuZCB0aGVyZSBhcmUgYSB0b3RhbCBvZiAxMyBjb2x1bW5zLg0KU29ydCBhbmQgZmlsdGVyIHRoZSBkYXRhLg0KRm9yIHRoaXMgYW5hbHlzaXMsIEkgd2lsbCBiZSB1c2luZyBkYXRhIGZvciB0aGUgeWVhciAyMDE5IGFuZCAyMDIwLg0KRGV0ZXJtaW5lIHRoZSBjcmVkaWJpbGl0eSBvZiB0aGUgZGF0YS4NCkZvciB0aGUgcHVycG9zZXMgb2YgdGhpcyBjYXNlIHN0dWR5LCB0aGUgZGF0YXNldHMgYXJlIGFwcHJvcHJpYXRlIGFuZCB3aWxsIGVuYWJsZSBtZSB0byBhbnN3ZXIgdGhlIGJ1c2luZXNzIHF1ZXN0aW9ucy4gVGhlIGRhdGEgaGFzIGJlZW4gbWFkZSBhdmFpbGFibGUgYnkgTW90aXZhdGUgSW50ZXJuYXRpb25hbCBJbmMuIFRoaXMgaXMgcHVibGljIGRhdGEgdGhhdCBJIGNhbiB1c2UgdG8gZXhwbG9yZSBob3cgZGlmZmVyZW50IGN1c3RvbWVyIHR5cGVzIGFyZSB1c2luZyBDeWNsaXN0aWMgYmlrZXMuIEJ1dCBkYXRhLXByaXZhY3kgaXNzdWVzIHdpbGwgcHJvaGliaXQgbWUgZnJvbSB1c2luZyByaWRlcuKAmXMgcGVyc29uYWxseSBpZGVudGlmaWFibGUgaW5mb3JtYXRpb24gYW5kIHRoaXMgd2lsbCBwcmV2ZW50IG1lIGZyb20gZGV0ZXJtaW5pbmcgaWYgcmlkZXJzIGhhdmUgcHVyY2hhc2VkIG11bHRpcGxlIHNpbmdsZSBwYXNzZXMuIEFsbCByaWRlIGlkcyBhcmUgdW5pcXVlLg0KRGVsaXZlcmFibGUNCg0KQSBkZXNjcmlwdGlvbiBvZiBhbGwgZGF0YSBzb3VyY2VzIHVzZWQNClRoZSBtYWluIHNvdXJjZSBvZiBhbGwgdGhlIGRhdGEgdXNlZCB3YXMgcHJvdmlkZWQgYnkgdGhlIEN5Y2xpc3RpYyBDb21wYW55Lg0KDQpgYGB7cn0NCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShEVCkNCmxpYnJhcnkobHVicmlkYXRlKQ0Kb3B0aW9ucyhzY2lwZW4gPSA5OTkpDQpsaWJyYXJ5KG1vZGVlc3QpDQpgYGANCg0KSW1wb3J0aW5nIERhdGEgZnJvbSAyMDIzDQoNCmBgYHtyLGVjaG89RkFMU0V9DQojcmVhZF9jc3YoIkM6XFVzZXJzXHRoZXB5XERvd25sb2Fkc1xjeWNsaXN0IGRhdGFcMjAyMzA3LWRpdnZ5LXRyaXBkYXRhLmNzdiIpDQoNCkp1bHlfMjAyMyA8LSByZWFkLmNzdigiQzovVXNlcnMvdGhlcHkvRG93bmxvYWRzL2Jpa2UyMDIzLzIwMjMwNy1kaXZ2eS10cmlwZGF0YS5jc3YiKQ0KQXVndXN0XzIwMjMgPC0gcmVhZC5jc3YoIkM6L1VzZXJzL3RoZXB5L0Rvd25sb2Fkcy9iaWtlMjAyMy8yMDIzMDgtZGl2dnktdHJpcGRhdGEuY3N2IikNClNlcHRlbWJlcl8yMDIzIDwtIHJlYWQuY3N2ICgiQzovVXNlcnMvdGhlcHkvRG93bmxvYWRzL2Jpa2UyMDIzLzIwMjMwOS1kaXZ2eS10cmlwZGF0YS5jc3YiKQ0KT2N0b2Jlcl8yMDIzIDwtIHJlYWQuY3N2KCJDOi9Vc2Vycy90aGVweS9Eb3dubG9hZHMvYmlrZTIwMjMvMjAyMzEwLWRpdnZ5LXRyaXBkYXRhLmNzdiIpDQpOb3ZlbWViZXJfMjAyMyA8LSByZWFkLmNzdigiQzovVXNlcnMvdGhlcHkvRG93bmxvYWRzL2Jpa2UyMDIzLzIwMjMxMS1kaXZ2eS10cmlwZGF0YS5jc3YiKQ0KRGVjZW1iZXJfMjAyMyA8LSByZWFkLmNzdigiQzovVXNlcnMvdGhlcHkvRG93bmxvYWRzL2Jpa2UyMDIzLzIwMjMxMi1kaXZ2eS10cmlwZGF0YS5jc3YiKQ0KDQoNCg0KDQoNCmBgYA0KDQpJbXBvcnQgRGF0YSBmcm9tIDIwMjQNCmBgYHtyLGVjaG89RkFMU0V9DQpKYW51YXJ5XzIwMjQgPC0gcmVhZC5jc3YoIkM6L1VzZXJzL3RoZXB5L0Rvd25sb2Fkcy9iaWtlMjAyNC8yMDI0MDEtZGl2dnktdHJpcGRhdGEuY3N2IikNCkZlYnJ1YXJ5XzIwMjQgPC0gcmVhZC5jc3YoIkM6L1VzZXJzL3RoZXB5L0Rvd25sb2Fkcy9iaWtlMjAyNC8yMDI0MDItZGl2dnktdHJpcGRhdGEuY3N2IikNCk1hcmNoXzIwMjQgPC0gcmVhZC5jc3YgKCJDOi9Vc2Vycy90aGVweS9Eb3dubG9hZHMvYmlrZTIwMjQvMjAyNDAzLWRpdnZ5LXRyaXBkYXRhLmNzdiIpDQpBcHJpbF8yMDI0IDwtIHJlYWQuY3N2KCJDOi9Vc2Vycy90aGVweS9Eb3dubG9hZHMvYmlrZTIwMjQvMjAyNDA0LWRpdnZ5LXRyaXBkYXRhLmNzdiIpDQpNYXlfMjAyNCA8LSByZWFkLmNzdigiQzovVXNlcnMvdGhlcHkvRG93bmxvYWRzL2Jpa2UyMDI0LzIwMjQwNS1kaXZ2eS10cmlwZGF0YS5jc3YiKQ0KSnVuZV8yMDI0IDwtIHJlYWQuY3N2KCJDOi9Vc2Vycy90aGVweS9Eb3dubG9hZHMvYmlrZTIwMjQvMjAyNDA2LWRpdnZ5LXRyaXBkYXRhLmNzdiIpDQpgYGANCg0KQ29tYmluaW5nIFJvd3MNCg0KYGBge3J9DQojYmluZCByb3dzIDIwMjMNCnEzXzIwMjMgPC0gcmJpbmQoSnVseV8yMDIzLEF1Z3VzdF8yMDIzLFNlcHRlbWJlcl8yMDIzKQ0KDQoNCnE0XzIwMjMgPC0gcmJpbmQoT2N0b2Jlcl8yMDIzLE5vdmVtZWJlcl8yMDIzLERlY2VtYmVyXzIwMjMpDQoNCiNiaW5kcm93cyAyMDI0DQpxMV8yMDI0IDwtIHJiaW5kKEphbnVhcnlfMjAyNCxGZWJydWFyeV8yMDI0LE1hcmNoXzIwMjQpDQoNCg0KcTJfMjAyNCA8LSByYmluZChBcHJpbF8yMDI0LE1heV8yMDI0LEp1bmVfMjAyNCkNCg0KDQphbGxfdHJpcHMgPC0gIHJiaW5kKHEzXzIwMjMscTRfMjAyMyxxMV8yMDI0LHEyXzIwMjQpDQpgYGANCg0KDQpgYGB7cn0NCmBgYA0KDQpQUk9DRVNTDQoNCkNsZWFuaW5nIHVwIGRhdGEgYW5kIGFkZGluZyBkYXRhIHRvIHByZXBhcmUgZm9yIGFuYWx5c2lzDQoNCktleSB0YXNrcw0KDQpDaGVjayB0aGUgZGF0YSBmb3IgZXJyb3JzLg0KDQpDaG9vc2UgeW91ciB0b29scy4NCg0KVHJhbnNmb3JtIHRoZSBkYXRhIHNvIHlvdSBjYW4gd29yayB3aXRoIGl0IGVmZmVjdGl2ZWx5Lg0KDQpEb2N1bWVudCB0aGUgY2xlYW5pbmcgcHJvY2Vzcy4NCg0KRGVsaXZlcmFibGVzDQoNCkRvY3VtZW50YXRpb24gb2YgYW55IGNsZWFuaW5nIG9yIG1hbmlwdWxhdGlvbiBvZiBkYXRhLg0KSSBpbnNwZWN0ZWQgdGhlIG5ldyB0YWJsZSB0aGF0IGhhcyBiZWVuIGNyZWF0ZWQgdXNpbmcgdGhlIGZvbGxvd2luZyBjb2RlIGNodW5rcw0KYGBge3J9DQpgYGANCg0KDQpgYGB7cn0NCmNvbG5hbWVzKGFsbF90cmlwcykgICNMaXN0IG9mIGNvbHVtbiBuYW1lcw0KbnJvdyhhbGxfdHJpcHMpICAjSG93IG1hbnkgcm93cyBhcmUgaW4gZGF0YSBmcmFtZQ0KZGltKGFsbF90cmlwcykgICNEaW1lbnNpb25zIG9mIHRoZSBkYXRhIGZyYW1lDQpoZWFkKGFsbF90cmlwcykgICNTZWUgdGhlIGZpcnN0IDYgcm93cyBvZiBkYXRhIGZyYW1lDQp0YWlsKGFsbF90cmlwcykgICNzZWUgdGhlIGxhc3QgNiByb3dzIG9mIGRhdGEgZnJhbWUNCnN0cihhbGxfdHJpcHMpICAjU2VlIGxpc3Qgb2YgY29sdW1ucyBhbmQgZGF0YSB0eXBlcyAobnVtZXJpYywgY2hhcmFjdGVyLCBldGMpDQpzdW1tYXJ5KGFsbF90cmlwcykgICNTdGF0aXN0aWNhbCBzdW1tYXJ5IG9mIGRhdGEuIE1haW5seSBmb3IgbnVtZXJpYw0KYGBgDQoNCkFkZGluZyBjb2x1bW5zIHRoYXQgbGlzdCB0aGUgZGF0ZSwgbW9udGgsIGRheSwgYW5kIHllYXIgb2YgZWFjaCByaWRlLg0KDQpUaGlzIHdpbGwgYWxsb3cgdXMgdG8gYWdncmVnYXRlIHJpZGUgZGF0YSBmb3IgZWFjaCBtb250aCwgZGF5LCBvciB5ZWFyLiBUaGVyZWZvcmUsIHRoZSBjb2RlIGNodW5rcyB1c2VkIGFyZSBhcyBmb2xsb3dzDQpgYGB7cn0NCmFsbF90cmlwcyRkYXRlIDwtIGFzLkRhdGUoYWxsX3RyaXBzJHN0YXJ0ZWRfYXQpICNUaGUgZGVmYXVsdCBmb3JtYXQgaXMgeXl5eS1tbS1kZA0KYWxsX3RyaXBzJG1vbnRoIDwtIGZvcm1hdChhcy5EYXRlKGFsbF90cmlwcyRkYXRlKSwgIiVtIikNCmFsbF90cmlwcyRkYXkgPC0gZm9ybWF0KGFzLkRhdGUoYWxsX3RyaXBzJGRhdGUpLCAiJWQiKQ0KYWxsX3RyaXBzJHllYXIgPC0gZm9ybWF0KGFzLkRhdGUoYWxsX3RyaXBzJGRhdGUpLCAiJVkiKQ0KYWxsX3RyaXBzJGRheV9vZl93ZWVrIDwtIGZvcm1hdChhcy5EYXRlKGFsbF90cmlwcyRkYXRlKSwgIiVBIikNCmBgYA0KDQpBZGRpbmcgYSDigJxyaWRlX2xlbmd0aOKAnSBjYWxjdWxhdGlvbiB0byBhbGxfdHJpcHMgKGluIHNlY29uZHMpDQpjb252ZXJ0IHRvIG1pbnV0ZXMNCmBgYHtyfQ0KYWxsX3RyaXBzJHJpZGVfbGVuZ3RoIDwtIGRpZmZ0aW1lKGFsbF90cmlwcyRlbmRlZF9hdCxhbGxfdHJpcHMkc3RhcnRlZF9hdCkNCg0Kc3RyKGFsbF90cmlwcykgICN0byBpbnNwZWN0IHRoZSBzdHJ1Y3R1cmUgb2YgdGhlIGNvbHVtbnMNCmBgYA0KDQoNCmBgYHtyfQ0KYGBgDQoNCg0KDQpDb252ZXJ0IOKAnHJpZGVfbGVuZ3Ro4oCdIGZyb20gZmFjdG9yIHRvIG51bWVyaWMgc28gd2UgY2FuIHJ1biBjYWxjdWxhdGlvbnMgb24gdGhlIGRhdGENCmBgYHtyfQ0KYWxsX3RyaXBzJHJpZGVfbGVuZ3RoIDwtIGFzLm51bWVyaWMoYXMuY2hhcmFjdGVyKGFsbF90cmlwcyRyaWRlX2xlbmd0aCkpDQoNCmlzLm51bWVyaWMoYWxsX3RyaXBzJHJpZGVfbGVuZ3RoKQ0KDQpgYGANCg0KDQpgYGB7cn0NCmBgYA0KDQoNCg0KUmVtb3ZlIOKAnGJhZOKAnSBkYXRhDQoNClRoZSBkYXRhZnJhbWUgaW5jbHVkZXMgYSBmZXcgaHVuZHJlZCBlbnRyaWVzIHdoZW4gYmlrZXMgd2VyZSB0YWtlbiBvdXQgb2YgZG9ja3MgYW5kIGNoZWNrZWQgZm9yIHF1YWxpdHkgYnkgRGl2dnkgb3IgcmlkZV9sZW5ndGggd2FzIG5lZ2F0aXZlIG9yIFplcm8sIHNvIEkgY3JlYXRlZCBhIG5ldyB2ZXJzaW9uIG9mIHRoZSBkYXRhZnJhbWUgKHYyKSBzaW5jZSBkYXRhIGlzIGJlaW5nIHJlbW92ZWQNCg0KYGBge3J9DQphbGxfdHJpcHNbIShhbGxfdHJpcHMkc3RhcnRfc3RhdGlvbl9uYW1lID09ICJIUSBRUiIgfCBhbGxfdHJpcHMkcmlkZV9sZW5ndGg8MCksXQ0KDQphbGxfdHJpcHNfdjIgPC0gYWxsX3RyaXBzWyEoYWxsX3RyaXBzJHN0YXJ0X3N0YXRpb25fbmFtZSA9PSAiSFEgUVIiIHwgYWxsX3RyaXBzJHJpZGVfbGVuZ3RoPDApLF0NCg0Kc3VtKGR1cGxpY2F0ZWQoYWxsX3RyaXBzX3YyKSkNCmBgYA0KDQoNCmBgYHtyfQ0KYGBgDQoNCg0KYGBge3J9DQpgYGANCg0KPGI+QU5BTFlaRTwvYj4NCg0KQWxsIHRoZSBkYXRhIGhhcyBiZWVuIHN0b3JlZCBhcHByb3ByaWF0ZWx5IGFuZCBoYXMgYmVlbiBwcmVwYXJlZCBmb3IgYW5hbHlzaXMsIHNvIHRoZXkgYXJlIHJlYWR5IGZvciBleHBsb3JhdGlvbi4NCktleSB0YXNrcw0KDQpBZ2dyZWdhdGUgeW91ciBkYXRhIHNvIGl04oCZcyB1c2VmdWwgYW5kIGFjY2Vzc2libGUuDQpPcmdhbml6ZSBhbmQgZm9ybWF0IHlvdXIgZGF0YS4NClBlcmZvcm0gY2FsY3VsYXRpb25zLg0KSWRlbnRpZnkgdHJlbmRzIGFuZCByZWxhdGlvbnNoaXBzLg0KRGVsaXZlcmFibGVzDQoNCkEgc3VtbWFyeSBvZiB5b3VyIGFuYWx5c2lzDQpDb25kdWN0IGRlc2NyaXB0aXZlIGFuYWx5c2lzIC0gRGVzY3JpcHRpdmUgYW5hbHlzaXMgb24gcmlkZV9sZW5ndGggKGFsbCBmaWd1cmVzIGluIHNlY29uZHMpDQoNCmBgYHtyfQ0KbWVhbihhbGxfdHJpcHNfdjIkcmlkZV9sZW5ndGgsbmFfcm09VCkgDQptZWRpYW4oYWxsX3RyaXBzX3YyJHJpZGVfbGVuZ3RoLG5hX3JtPVQpIA0KbWZ2KGFsbF90cmlwc192MiRyaWRlX2xlbmd0aCxuYV9ybT1UKSANCm1heChhbGxfdHJpcHNfdjIkcmlkZV9sZW5ndGgsbmFfcm09VCkgDQptaW4oYWxsX3RyaXBzX3YyJHJpZGVfbGVuZ3RoLG5hX3JtPVQpIA0KcmFuZ2UgPSBtYXgoYWxsX3RyaXBzX3YyJHJpZGVfbGVuZ3RoLG5hX3JtPVQpIC0gbWluKGFsbF90cmlwc192MiRyaWRlX2xlbmd0aCxuYV9ybT1UKSANCnJhbmdlDQpgYGANCg0KDQoNCkxldOKAmXMgdmlzdWFsaXplIG1lbWJlcnMgYW5kIGNhc3VhbHMgYnkgdGhlIHRvdGFsIHJpZGUgdGFrZW4gKHJpZGUgY291bnQpDQo8YnI+DQoNCmBgYHtyfQ0KYWxsX3RyaXBzX3YyICU+JSANCiAgZ3JvdXBfYnkobWVtYmVyX2Nhc3VhbCkgJT4lIA0KICBzdW1tYXJpc2UocmlkZV9jb3VudCA9IG4oKSkgJT4lIA0KICBnZ3Bsb3QoYWVzKG1lbWJlcl9jYXN1YWwscmlkZV9jb3VudCxmaWxsPW1lbWJlcl9jYXN1YWwpKStnZW9tX2NvbChwb3NpdGlvbiA9ICdkb2RnZScpKw0KICBsYWJzKHRpdGxlID0gJ1RvdGFsIHJpZGVzIHRha2VuIChyaWRlX2NvdW50KSBvZiBNZW1iZXJzIGFuZCBDYXN1YWwgcmlkZXJzJykrDQogIHRoZW1lX2NsYXNzaWMoKStnZW9tX2NvbCh3aWR0aCA9IC41LHBvc2l0aW9uID0gcG9zaXRpb25fZG9kZ2Uod2lkdGg9LjUpKSsNCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbD1yaWRlX2NvdW50KSxzaXplPTQpKw0KICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemU9MTIsIGhqdXN0ID0gLjQgKSkNCiAgDQpgYGANCg0KYGBge3J9DQphbGxfdHJpcHNfdjINCmBgYA0KDQpGcm9tIHRoZSBhYm92ZSBncmFwaCwgd2UgY2FuIG9ic2VydmUgdGhhdCB0aGVyZSBhcmUgZmFyIG1vcmUgcmlkZXJzIGFzIHJlZ2lzdGVyZWQgbWVtYmVycyBjb21wYXJlZCB0byBjYXN1YWwgcmlkZXMgYmFzZWQgb24gdGhlIHJpZGUgY291bnQuDQoNCjxicj4NCjxicj4NCmBgYHtyfQ0KYGBgDQo8YnI+DQpMZXTigJlzIHNlZSB0aGUgYXZlcmFnZSB0aW1lIHJpZGUgYnkgZWFjaCBkYXkgZm9yIG1lbWJlcnMgdnMgY2FzdWFsIHVzZXJzDQoNCg0KDQpgYGB7cn0NCndkYXl0b2F2Z3JpZGVsIDwtIGFsbF90cmlwc192MiAlPiUgDQogIHNlbGVjdChyaWRlX2xlbmd0aCwgbWVtYmVyX2Nhc3VhbCxkYXlfb2Zfd2VlaykgJT4lIA0KICAgbXV0YXRlKHJpZGVfbGVuZ3RoPXJpZGVfbGVuZ3RoLzYwKSAlPiUgIyByaWRlIGxlbmd0aCBjb252ZXJ0ZWQgZnJvbSBzZWNvbmRzIHRvIG1pbnV0ZXMgDQoNCiAgZ3JvdXBfYnkobWVtYmVyX2Nhc3VhbCxkYXlfb2Zfd2VlaykgJT4lIA0KICBzdW1tYXJpc2UoYXZncmlkZV9sZW5ndGg9cm91bmQobWVhbihyaWRlX2xlbmd0aCkpKQ0KZGF0YXRhYmxlKHdkYXl0b2F2Z3JpZGVsKQ0KYGBgDQphdmVyYWdlIHJpZGUgdGltZSBieSBlYWNoIGRheSBmb3IgbWVtYmVyIHZzIGNhc3VhbCB1c2VycyAoaW4gbWludXRlcykNCjxicj4NCg0KDQoNCg0KYGBge3J9DQptZW1jYXNfZHVyYXRpb25hbmRyaWRlbnVtYmVyIDwtICBhbGxfdHJpcHNfdjIgJT4lICAjd2VlayB0cmVuZHMNCiAgICBtdXRhdGUocmlkZV9sZW5ndGg9cmlkZV9sZW5ndGgvNjApICU+JSAjIHJpZGUgbGVuZ3RoIGNvbnZlcnRlZCBmcm9tIHNlY29uZHMgdG8gbWludXRlcw0KICBtdXRhdGUod2Vla2RheSA9IHdkYXkoc3RhcnRlZF9hdCwgbGFiZWwgPSBUUlVFKSkgJT4lDQogIGdyb3VwX2J5KG1lbWJlcl9jYXN1YWwsIHdlZWtkYXkpICU+JSAgDQogIHN1bW1hcmlzZShudW1iZXJfb2ZfcmlkZXMgPSBuKCksIGF2ZXJhZ2VfZHVyYXRpb24gPSByb3VuZChtZWFuKHJpZGVfbGVuZ3RoKSApKSAlPiUNCiAgYXJyYW5nZShtZW1iZXJfY2FzdWFsLCB3ZWVrZGF5KQ0KDQpkYXRhdGFibGUobWVtY2FzX2R1cmF0aW9uYW5kcmlkZW51bWJlcikNCg0KYGBgDQoNCg0KdG90YWwgcmlkZXMgYW5kIGF2ZXJhZ2UgcmlkZV90aW1lIGR1cmF0aW9uKGluIG1pbnV0ZXMpIGJ5IGVhY2ggZGF5IGZvciBtZW1iZXJzIHZzIGNhc3VhbCByaWRlcnMNCmBgYHtyfQ0KYGBgDQo8YnI+DQo8Yj5MZXTigJlzIHZpc3VhbGl6ZSB0aGUgYWJvdmUgdGFibGUgYnkgZGF5cyBvZiB0aGUgd2VlayBhbmQgbnVtYmVyIG9mIHJpZGVzIHRha2VuIGJ5IG1lbWJlciBhbmQgY2FzdWFsIHJpZGVycy48L2I+DQoNCg0KYGBge3J9DQphbGxfdHJpcHNfdjIgJT4lIA0KICAgIG11dGF0ZShyaWRlX2xlbmd0aD1yaWRlX2xlbmd0aC82MCkgJT4lICMgcmlkZSBsZW5ndGggY29udmVydGVkIGZyb20gc2Vjb25kcyB0byBtaW51dGVzDQogIG11dGF0ZSh3ZWVrZGF5ID0gd2RheShzdGFydGVkX2F0LCBsYWJlbCA9IFRSVUUpKSAlPiUNCiAgZ3JvdXBfYnkobWVtYmVyX2Nhc3VhbCwgd2Vla2RheSkgJT4lICANCiAgc3VtbWFyaXNlKG51bWJlcl9vZl9yaWRlcyA9IG4oKSwgYXZlcmFnZV9kdXJhdGlvbiA9IHJvdW5kKG1lYW4ocmlkZV9sZW5ndGgpKSkgJT4lDQogIGFycmFuZ2UobWVtYmVyX2Nhc3VhbCwgd2Vla2RheSkgJT4lIA0KICBnZ3Bsb3QoYWVzKHdlZWtkYXksbnVtYmVyX29mX3JpZGVzLGZpbGw9bWVtYmVyX2Nhc3VhbCkpK3RoZW1lX2NsYXNzaWMoKStnZW9tX2NvbChwb3NpdGlvbiA9ICdkb2RnZScpK2dlb21fdGV4dChhZXMobGFiZWw9bnVtYmVyX29mX3JpZGVzKSxzaXplPTIuOCkrZ2d0aXRsZSgnVG90YWwgcmlkZXMgb2YgTWVtYmVycyBhbmQgQ2FzdWFsIHJpZGVycyBWcy4gRGF5IG9mIHRoZSB3ZWVrJykrDQogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZT0xMSxoanVzdD0uMykpDQogIA0KYGBgDQoNCjx1bD4NCjxsaT4NCkZyb20gdGhlIGFib3ZlIGdyYXBoLCBpdCBpcyBvYnNlcnZlZCB0aGF0IHRoZSBtZW1iZXJzIGFyZSBxdWl0ZSBjb25zaXN0ZW50IHdpdGggaGlnaGVyIG51bWJlciBvZiByaWRlcyB0aHJvdWdob3V0IHRoZSB3b3JrIHdlZWsgY29tcGFyZWQgdG8gdGhlIGNhc3VhbCByaWRlcnMsIGhvd2V2ZXIgdGhlIGRpZmZlcmVuY2VzIGluIG51bWJlciBvZiByaWRlcyBiZXR3ZWVuIHRoZSBtZW1iZXJzIGFuZCBjYXN1YWwgcmlkZXJzIGR1cmluZyB0aGUgd2Vla2VuZHMoU2F0dXJkYXlzIGFuZCBTdW5kYXlzKSBpcyBub3QgbmVhcmx5IGFzIG11Y2ggYXMgdGhlIGRpZmZlcmVuY2VzIGR1cmluZyB0aGUgb3RoZXIgZGF5cyBvZiB0aGUgd2Vlay4NCjwvbGk+DQo8L3VsPg0KYGBge3J9DQpgYGANCjxicj4NCg0KYGBge3J9DQpgYGANCg0KPGI+TGV04oCZcyB2aXN1YWxpemUgdGhlIGF2ZXJhZ2UgZHVyYXRpb24gb2YgTWVtYmVycyBhbmQgQ2FzdWFsIHJpZGVycyBWcy4gRGF5IG9mIHRoZSB3ZWVrPC9iPg0KYGBge3J9DQpgYGANCg0KDQpgYGB7cn0NCmFsbF90cmlwc192MiAlPiUgIA0KICBtdXRhdGUocmlkZV9sZW5ndGg9cmlkZV9sZW5ndGgvNjApICU+JSANCiAgbXV0YXRlKHdlZWtkYXkgPSB3ZGF5KHN0YXJ0ZWRfYXQsIGxhYmVsID0gVFJVRSkpICU+JSANCiAgZ3JvdXBfYnkobWVtYmVyX2Nhc3VhbCwgd2Vla2RheSkgJT4lIA0KICBzdW1tYXJpc2UobnVtYmVyX29mX3JpZGVzID0gbigpDQogICAgICAgICAgICAsYXZlcmFnZV9kdXJhdGlvbiA9IHJvdW5kKG1lYW4ocmlkZV9sZW5ndGgpKSkgJT4lIA0KICBhcnJhbmdlKG1lbWJlcl9jYXN1YWwsIHdlZWtkYXkpICAlPiUgDQogIGdncGxvdChhZXMoeCA9IHdlZWtkYXksIHkgPSBhdmVyYWdlX2R1cmF0aW9uLCBmaWxsID0gbWVtYmVyX2Nhc3VhbCkpICsNCiAgZ2VvbV9jb2wocG9zaXRpb24gPSAiZG9kZ2UiKSArDQogIGxhYnModGl0bGUgPSJBdmVyYWdlIGR1cmF0aW9uIChtaW51dGVzKSBvZiBNZW1iZXJzIGFuZCBDYXN1YWwgcmlkZXJzIikgK3RoZW1lX2NsYXNzaWMoKSsNCiAgZ2VvbV9jb2wod2lkdGg9MC41LCBwb3NpdGlvbiA9IHBvc2l0aW9uX2RvZGdlKHdpZHRoPTAuNSkpICtnZW9tX3RleHQoYWVzKGxhYmVsPWF2ZXJhZ2VfZHVyYXRpb24pLHNpemU9Mi44KSt0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCxoanVzdD0uMDUpKQ0KDQpgYGANCkZyb20gdGhlIGdyYXBoIGFib3ZlLCBpdCBpcyBvYnNlcnZlZCB0aGF0IHRoZSBjYXN1YWwgcmlkZXJzIHJpZGUgdGhlIGxvbmdlc3QgdGhyb3VnaG91dCB0aGUgd2Vla2VuZCBhbmQgc2hvcnRlc3QgZHVyYXRpb24gYmV0d2VlbiBUdWVzZGF5IGFuZCBUaHVyc2RheSwgd2hpbGUgcmVnaXN0ZXJlZCByaWRlcnMgcmlkZSBhdCBhIGZhc3RlciBtb3JlIGNvbnNpc3RlbnQgcGFjZSBkdXJpbmcgdGhlIHdlZWsgd2l0aCB0aGUgbG9uZ2VzdCByaWRlcyBvbiB0aGUgd2Vla2VuZC4NCjxicj4NCmBgYHtyfQ0KYGBgDQoNCjxiPiBMZXTigJlzIGNyZWF0ZSBhIHZpc3VhbGl6YXRpb24gZm9yIFRvdGFsIHJpZGVzIGJ5IG1lbWJlcnMgYW5kIGNhc3VhbCByaWRlcnMgYnkgbW9udGgNCg0KPC9iPg0KPGJyPg0KDQoNCmBgYHtyfQ0KYWxsX3RyaXBzX3YyICU+JSAgDQogICANCiAgZ3JvdXBfYnkobWVtYmVyX2Nhc3VhbCwgbW9udGgpICU+JSANCiAgc3VtbWFyaXNlKG51bWJlcl9vZl9yaWRlcyA9IG4oKSwuZ3JvdXBzPSJkcm9wIikgJT4lIA0KICBhcnJhbmdlKG1lbWJlcl9jYXN1YWwsIG1vbnRoKSAgJT4lIA0KICBnZ3Bsb3QoYWVzKHggPSBtb250aCwgeSA9IG51bWJlcl9vZl9yaWRlcywgZmlsbCA9IG1lbWJlcl9jYXN1YWwpKSArDQogIGxhYnModGl0bGUgPSJUb3RhbCByaWRlcyBieSBNZW1iZXJzIGFuZCBDYXN1YWwgcmlkZXJzIGJ5IE1vbnRoIikgKw0KICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1KSkgK3RoZW1lX2NsYXNzaWMoKSsNCiAgZ2VvbV9jb2wod2lkdGg9MC41LCBwb3NpdGlvbiA9IHBvc2l0aW9uX2RvZGdlKHdpZHRoPTAuNSkpICtnZW9tX3RleHQoYWVzKGxhYmVsPW51bWJlcl9vZl9yaWRlcyksc2l6ZT0yLjQpKw0KICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemU9MTEsaGp1c3Q9LjYpKQ0KICANCmBgYA0KRnJvbSB0aGUgYWJvdmUgZ3JhcGgsIGl0IGlzIG9ic2VydmVkIHRoYXQgbWVtYmVycyBoYXZlIHRoZSBoaWdoZXN0IG51bWJlciBvZiByaWRlcyB0aHJvdWdob3V0IHRoZSB5ZWFyIHdpdGggQXVndXN0IGJlaW5nIHRoZSBtb250aCB3aXRoIHRoZSBoaWdoZXN0IG51bWJlciBvZiByaWRlcyBmb3IgbWVtYmVycyB3aGlsc3QganVseSBoYXMgdGhlIG1vc3QgY2FzdWFsIHJpZGVycw0KDQo8YnI+DQo8Yj5MZXTigJlzIGNvbXBhcmUgTWVtYmVycyBhbmQgQ2FzdWFsIHJpZGVycyBkZXBlbmRpbmcgb24gcmlkZSBkaXN0YW5jZS48L2I+DQpgYGB7cn0NCg0KYWxsX3RyaXBzX3YyICU+JSANCiAgZ3JvdXBfYnkobWVtYmVyX2Nhc3VhbCkgJT4lIGRyb3BfbmEoKSAlPiUNCiAgc3VtbWFyaXNlKGF2ZXJhZ2VfcmlkZV9sZW5ndGhfa2lsb21ldGVycyA9IG1lYW4ocmlkZV9sZW5ndGgpKSAlPiUNCm11dGF0ZShhdmVyYWdlX3JpZGVfbGVuZ3RoX01pbGVzID0gcm91bmQoKCBhdmVyYWdlX3JpZGVfbGVuZ3RoX2tpbG9tZXRlcnMgKiAyKS8zICkpICU+JSANCiAgc2VsZWN0KG1lbWJlcl9jYXN1YWwsYXZlcmFnZV9yaWRlX2xlbmd0aF9NaWxlcykgJT4lIA0KICBnZ3Bsb3QoYWVzKHg9IG1lbWJlcl9jYXN1YWwseT0gYXZlcmFnZV9yaWRlX2xlbmd0aF9NaWxlcyxmaWxsPW1lbWJlcl9jYXN1YWwpKSArIA0KICBnZW9tX2NvbCgpK2dlb21fdGV4dChhZXMobGFiZWwgPSBhdmVyYWdlX3JpZGVfbGVuZ3RoX01pbGVzKSkrDQogIGxhYnModGl0bGUgPSAiTWVhbiBkaXN0YW5jZSB0cmF2ZWxlZCBieSBNZW1iZXJzIGFuZCBDYXN1YWwgcmlkZXJzIikrDQogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZT0xMSxoanVzdD0uMikpK3RoZW1lX2NsYXNzaWMoKQ0KICANCg0KDQpgYGANCkZyb20gdGhlIGdyYXBoIGFib3ZlLCB3ZSBjYW4gb2JzZXJ2ZSB0aGF0IHRoZSBkaXN0YW5jZSB0cmF2ZWxlZCBieSB0aGUgY2FzdWFsIHJpZGVycyBpcyBmYXIgbW9yZSB0aGFuIHRoZSBkaXN0YW5jZSB0cmF2ZWxlZCBieSB0aGUgbWVtYmVyIHdpdGggYSB2ZXJ5IGxhcmdlIGRpZmZlcmVuY2UgaW4gbWlsZXMuDQpSb3VnaGx5IDg1NSBtaWxlcyBpcyB0aGUgYXZlcmFnZSBkaXN0YW5jZSB0cmF2ZWxlZCBieSBjYXN1YWwgcmlkZXJzIGFuZCA0OTQgbWlsZXMgaXMgdGhlIGF2ZXJhZ2UgZGlzdGFuY2UgdHJhdmVsZWQgYnkgcmVnaXN0ZXJlZCBtZW1iZXJzDQo8YnI+DQpub3RlOiBjb252ZXJ0ZWQga2lsb21ldGVycyB0byBtaWxlcw0KDQo8YnI+DQoNCjxicj4NCg0KDQo8YnI+DQoNCg0KPGI+Q29uY2x1c2lvbjwvYj4NCjxvbD4NCjxsaT5NZW1iZXJzIGhhdmUgbW9yZSBiaWtlcyBjb21wYXJlZCB0byBjYXN1YWwgcmlkZXJzLjwvbGk+DQoNCjxsaT5XZSBoYXZlIG1vcmUgbWVtYmVycyByaWRpbmcgaW4gYWxsIG1vbnRocyBjb21wYXJlZCB0byBjYXN1YWwgcmlkZXJzLjwvbGk+DQoNCjxsaT5DYXN1YWwgcmlkZXJzIHRyYXZlbCBmb3IgYSBsb25nZXIgdGltZSBwZXJpb2QuPC9saT4NCg0KPGxpPk1lbWJlcnMgcmlkZSBtb3JlIHRocm91Z2hvdXQgdGhlIGVudGlyZSB3ZWVrZGF5IHdoaWxlIHRoZSBjYXN1YWwgcmlkZXJzIGFsc28gaGF2ZSBhIGhpZ2ggcmlkZSByZWNvcmQgZHVyaW5nIHRoZSB3ZWVrZW5kcyhTYXR1cmRheSBhbmQgU3VuZGF5KSBjb21wYXJlZCB0byB0aGUgb3RoZXIgZGF5cyBvZiB0aGUgd2Vlay48L2xpPg0KDQo8bGk+Q2FzdWFsIHJpZGVycyBnbyBmYXJ0aGVyIGluIHRlcm1zIG9mIGRpc3RhbmNlLjwvbGk+DQo8L29sPg0KDQpgYGB7cn0NCg0KYGBgDQoNCmBgYHtyfQ0KYGBgDQoNCjxiPkRlbGl2ZXJhYmxlPC9iPg0KDQo8dWw+PGxpPllvdXIgdG9wIDMgcmVjb21tZW5kYXRpb25zIGJhc2VkIG9uIHlvdXIgYW5hbHlzaXM8L2xpPjwvdWw+DQo8b2w+IDxsaT4NCkhhdmUgYSBzbGFzaCBzYWxlIG9yIHByb21vIGZvciBjYXN1YWwgcmlkZXJzIHNvIHRoZXkgY2FuIGFjcXVpcmUgbW9yZSBiaWtlcyBhbmQgaW5kdWxnZSB0aGVtIGluIHRoZSBiZW5lZml0cyBvZiBiZWluZyBhIG1lbWJlci48L2xpPg0KDQo8bGk+SG9zdCBmdW4gYmlraW5nIGNvbXBldGl0aW9ucyB3aXRoIHByaXplcyBhdCBpbnRlcnZhbHMgZm9yIGNhc3VhbCByaWRlcnMgb24gdGhlIHdlZWtlbmRzLiBHaXZlbiB0aGF0IHRoZXJlIGFyZSBhIGxvdCBvZiBtZW1iZXJzIG9uIHdlZWtlbmRzIHRoaXMgY291bGQgYXR0cmFjdCB0aGVtIHRvIGdldCBhIG1lbWJlcnNoaXAuPC9saT4NCg0KPGxpPkVuY291cmFnZSBjYXN1YWwgcmlkZXJzIHRvIHJpZGUgbW9yZSB0aHJvdWdob3V0IHRoZSB5ZWFyIHVzaW5nIGFkZGl0aW9uYWwgYWR2ZXJ0aXNpbmcsIGhhbmRpbmcgb3V0IGZseWVycyBvciBieSBnaXZpbmcgdGhlbSB2YXJpb3VzIGNvdXBvbnMgc28gYXMgdG8gY29udmluY2UgdGhlbSBpbnRvIGJlY29taW5nIGEgbWVtYmVyLjwvbGk+IDwvb2w+DQpgYGB7cn0NCmBgYA0KDQoNCmBgYHtyfQ0KYGBgDQoNCg0KDQpgYGB7cn0NCmBgYA0KDQo=