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
-
From the above graph, it is observed that the members are quite
consistent with higher number of rides throughout the work week compared
to the casual riders, however the differences in number of rides between
the members and casual riders during the weekends(Saturdays and Sundays)
is not nearly as much as the differences during the other days of the
week.
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
-
Members have more bikes compared to casual riders.
-
We have more members riding in all months compared to casual riders.
-
Casual riders travel for a longer time period.
-
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.
-
Casual riders go farther in terms of distance.
Deliverable
-
Your top 3 recommendations based on your analysis
-
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.
-
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.
-
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=