Keywords: Transportation Planning, Rideshare Forecast, Spatial and
Serial Autocorrelation, Nested Tibble, OLS Regression, time lag,
gganimate, purrr
Introduction
Bikeshare programs have become an integral part of urban
transportation system, providing a sustainable and convenient
alternative to traditional commuting methods. Citi Bike is one of the
earliest bicycle sharing system created by Lyft in 2008 at NYC. But in
recent years, this program also extends to Jersey City across the Hudson
River in order to cater to the dynamic mobility needs of residents who
had to commute to Manhattan for work. As the popularity of Citi Bike
continues to soar, there arises a pressing need for effective management
strategies to ensure the availability of bikes at each station in Jersey
City.
Bikesharing (and ridesharing) inherently involves the challenge of
maintaining a delicate equilibrium in bike distribution, ie.
“re-balancing”. This issue arises from the spatial and temporal
variations in user demand. If certain stations experience a surge in
demand during morning rush hours while others witness a decline, this
could result in station clusters with depleted or surplus bike stocks,
thereby compromising user experience. Companies like Uber & Lyft
generate and analyze tremendous amounts of data to incentivize bike
(ride) share use, solve routing problems, calculate pricing, and
obviously forecast demands, but they are not very transparent about the
algorithm they use. Therefore, the goal of this report is keep a simple,
but open source version of bikeshare demand prediction, by harnessing of
power of machine learning, particularly the Ordinary Least Square (OLS)
regression to rapidly predict the demand for bikes across various
stations in Jersey City. We will perform analysis on the historical data
on bike usage at different stations and different time as well as under
different weather conditions. The forecast allows the Citi Bike program
to proactively address imbalances, strategically re-positioning bikes in
anticipation of peak usage time or special events.
The subsequent sections on this report is organized as follows.
Firstly, we load in our datasets for a quick examinations and perform
some necessary feature engineering. Secondly, we conduct exploratory
analysis on the temporal and spatial aspects of bikeshare data. We will
also look at some weather data in this process. Thirdly, we create a
space-time panel where each row is a unique instance of time-space
combination of rides and visualize the correlation between space, time,
weather, and number of rides. And finally, we make created four
different models, test their sensibility to errors, and conduct cross
validation.
The GitHub repository for this report is here.
Set Up
The data we use here is obtained from NYC OpenData
that contains the latest record of Citi Bike usage on a monthly base.
Note that while Jersey City is located in New Jersey, Citi Bike is
mainly operated in NYC and therefore its dataset is also operated by
NYC. We selected all bikeshare data in Jersey City from Aug 1 2023 to
Aug 31 2023 as our temporal frame. The dataset mainly includes locations
and names of stations of which the user started the ride as well as end
the ride. In addition, we also downloaded the census tract geometries of
the entire Hudson County via tidycensus, which includes Jersey City.
nycbike <- read.csv(url("https://raw.githubusercontent.com/emilyzhou112/MUSA5080-NJ-BikeShare/main/JC-202308-citibike-tripdata.csv"))
njCensus <- get_acs(geography = "tract",
variables = c("B01003_001", "B19013_001",
"B02001_002", "B08013_001",
"B08012_001", "B08301_001",
"B08301_010", "B01002_001"),
year = 2021,
state = "NJ",
geometry = TRUE,
county=c("Hudson"),
output = "wide") %>%
rename(Total_Pop = B01003_001E,
Med_Inc = B19013_001E,
Med_Age = B01002_001E,
White_Pop = B02001_002E,
Travel_Time = B08013_001E,
Num_Commuters = B08012_001E,
Means_of_Transport = B08301_001E,
Total_Public_Trans = B08301_010E) %>%
select(Total_Pop, Med_Inc, White_Pop, Travel_Time,
Means_of_Transport, Total_Public_Trans,
Med_Age,
GEOID, geometry) %>%
mutate(Percent_White = White_Pop / Total_Pop,
Mean_Commute_Time = Travel_Time / Total_Public_Trans,
Percent_Taking_Public_Trans = Total_Public_Trans / Means_of_Transport)
jcTracts <- njCensus %>%
as.data.frame() %>%
distinct(GEOID, .keep_all = TRUE) %>%
select(GEOID, geometry) %>%
st_sf()
Since our raw bikeshare data is a non-spatial layer, despite
containing spatial attributes, our next step is to make it spatial by
joining it to the census geometry. We are only interested in the start
location of each ride because we want to predict the capacity of each
station when the user need a bike to start the ride. This requires us to
filter out rows with missing latitude and longitude. We also found that
some stations have erroneous longitude and latitude coding that lead to
these points being located in the Hudson River. These points also need
to be removed.
bike_census <- st_join(nycbike %>%
filter(is.na(start_lat) == FALSE &
is.na(start_lng) == FALSE &
is.na(end_lat) == FALSE &
is.na(end_lng) == FALSE) %>%
st_as_sf(., coords = c("start_lng", "start_lat"), crs = 4326),
jcTracts %>% st_transform(crs=4326),
join=st_intersects, left = TRUE) %>%
rename(Origin.Tract = GEOID) %>%
mutate(start_lng = unlist(map(geometry, 1)),
start_lat = unlist(map(geometry, 2)))%>%
filter(!(ride_id %in% c("BEC9CC4D1007C753", "86087F431785EDE7", "A1AAB92631439883", "71938DA085242DCC", "4203C77668C47C75", "C92410CA2AEF3947", "6E02CBF36C90F244", "0259885253FD238E", "B39136B37AFB5FE9", "1EC9376D1559C51C", "CDDF16D3A37BE370"))) %>%
as.data.frame()
The following visualization shows all the steps we did above.
ggplot()+
geom_sf(data = jcTracts %>%
st_transform(crs=4326), fill="white")+
geom_point(data = bike_census, aes(x=start_lng, y = start_lat),
color = "#6E0E0A", alpha = 1, size = 0.3) +
ylim(min(bike_census$start_lat), max(bike_census$start_lat))+
xlim(min(bike_census$start_lng), max(bike_census$start_lng)) +
labs(title = "Location of Rides Starting Points in Jersey City") +
theme(axis.text.x=element_blank(),
axis.text.y=element_blank(),
axis.ticks =element_blank(),
axis.title.x = element_blank(),
axis.title.y = element_blank(),
plot.subtitle = element_text(size = 9,face = "italic"),
plot.title = element_text(size = 12, face = "bold"),
panel.background = element_blank(),
panel.border = element_rect(colour = "grey", fill=NA, linewidth=0.8)
)
We will perform some basic feature engineering using R’s powerful
lubridate
package to extract some temporal information for
each ride. This includes calculating the 60min and 15min interval for
the starting time of each ride, and categorizing these intervals into by
the time of day into “Overnight”, “AM Rush”, “Mid-Day”, and “PM Rush”
and by day of week into weekday and weekend.
bike_census <- bike_census %>%
mutate(interval60 = floor_date(ymd_hms(started_at), unit = "hour"),
interval15 = floor_date(ymd_hms(started_at), unit = "15 mins"),
week = week(interval60),
dotw = wday(interval60, label=TRUE)) %>%
mutate(time_of_day = case_when(hour(interval60) < 7 | hour(interval60) > 18 ~ "Overnight",
hour(interval60) >= 7 & hour(interval60) < 10 ~ "AM Rush",
hour(interval60) >= 10 & hour(interval60) < 15 ~ "Mid-Day",
hour(interval60) >= 15 & hour(interval60) <= 18 ~ "PM Rush")) %>%
mutate(hour = hour(started_at),
weekend = ifelse(dotw %in% c("Sun", "Sat"), "Weekend", "Weekday"))
Interested in seeing the commuting patterns to get a sense of rider’s
daily behavior, we created line strings that connect between the start
and end locations of each ride. For the simplicity of visualization, we
only visualize 200 rides. We may see that most of the rides occur within
Jersey City, but there are also people who commute using Citi Bike from
Jersey City to Manhattan.
lines <- st_sfc(
lapply(1:nrow(bike_census), function(i) {
st_linestring(matrix(c(bike_census[i, "start_lng"], bike_census[i, "start_lat"],
bike_census[i, "end_lng"], bike_census[i, "end_lat"]), ncol = 2, byrow = TRUE))
}))
lines_sf <- st_sf(geometry = lines)
lines_sf <- st_set_crs(lines_sf, 4326)
Route <- lines_sf %>% head(200)
mapview(Route, zcol = NULL, color = "#6E0E0A", linewidth = 0.001, alpha = 0.3) # only show 200
Exploratory Analysis
In this section, we delve into some further exploratory analysis of
our data set.
Temporal Analysis
Starting with temporal analysis, we first visualized the bikeshare
trips per hour in Jersey City in August 2023. It is clear that there’s
some form of circularity in that there are both peak hours and non-peak
hours during a single day. Also, we may see that five consecutive higher
peaks are usually followed by two less higher peaks. These correspond to
high bikeshare need during rush hours and low bikeshare needs overnight,
in early mornings, and over the weekend
ggplot(bike_census %>%
group_by(interval60) %>%
tally())+
geom_line(aes(x = interval60, y = n), color= "#6E0E0A")+
labs(title="Bike Share Trips Per Hour in Jersey City, Aug 2023",
x="Date",
y="Number of trips") +
theme(plot.subtitle = element_text(size = 9,face = "italic"),
plot.title = element_text(size = 12, face = "bold"),
axis.text.x=element_text(size=8),
axis.text.y=element_text(size=8),
axis.title=element_text(size=10),
panel.background = element_blank(),
panel.border = element_rect(colour = "grey", fill=NA, linewidth =0.8))
Then, we visualized the distribution of trips by stations at each
hour in August. We may see that the distribution of rides is highly
right skewed, with most stations having zero or a few number of trips
each hour and a couple of stations having much more rides, up to 40 or
even 50 rides at a particular hour on a particular day in August
2023.
ggplot(bike_census %>%
group_by(interval60, start_station_name) %>%
tally())+
geom_histogram(aes(n), binwidth = 5, fill="#6E0E0A")+
labs(title="Bike Share Trips Per Hour by Station in Jersey City, Aug 2023",
x="Trip Counts",
y="Number of Stations") +
theme(plot.subtitle = element_text(size = 9,face = "italic"),
plot.title = element_text(size = 12, face = "bold"),
axis.text.x=element_text(size=8),
axis.text.y=element_text(size=8),
axis.title=element_text(size=10),
panel.background = element_blank(),
panel.border = element_rect(colour = "grey", fill=NA, linewidth =0.8))
We continue to visualize the distribution of mean number of trips at
each station during different times of the day. We recognize that during
PM rush, the average number of trips has been the highest throughout,
with a couple of stations’ average trip exceed 10, implying a relatively
high demand for a single station. This is followed by ride demand in mid
day, when some stations can have more than 5 rides. It is also
surprising to notice that the demand is pretty high overnight, with a
few stations having more than 5 rides.
bike_census %>%
group_by(interval60, start_station_name, time_of_day) %>%
tally()%>%
group_by(start_station_name, time_of_day)%>%
summarize(mean_trips = mean(n))%>%
ggplot()+
geom_histogram(aes(mean_trips, fill=time_of_day), binwidth = 1)+
scale_fill_manual(values = palette4, name="Time of Day") +
labs(title="Mean Number of Hourly Trips Per Station",
x="Number of trips",
y="Frequency")+
facet_wrap(~time_of_day) +
theme(plot.subtitle = element_text(size = 9,face = "italic"),
plot.title = element_text(size = 12, face = "bold"),
axis.text.x=element_text(size=8),
axis.text.y=element_text(size=8),
axis.title=element_text(size=10),
panel.background = element_blank(),
panel.border = element_rect(colour = "grey", fill=NA, size=0.8))
After that, we visualized the total number of trips in August
separated by day of the week. The two main patterns here are that
firstly, trip numbers vary by time of the day. Lowest trip counts occur
in early morning while highest trip count occur in late afternoon.
Secondly, Tuesday, Wednesday, and Thursday tend to observe higher number
of trips.
ggplot(bike_census %>% mutate(hour = hour(started_at)))+
geom_freqpoly(aes(hour, color = dotw), binwidth = 3, lwd = 0.8)+
scale_color_manual(values = c("#124E78", "#819FA1", "#F0F0C9", "#F2BB05", "#E58507", "#D74E09", "#6E0E0A"), name = "Day") +
labs(title="Bike Share Trips in Jersey City by Day of the Week, Aug 2023",
x="Hour",
y="Trip Counts") +
theme(plot.subtitle = element_text(size = 9,face = "italic"),
plot.title = element_text(size = 12, face = "bold"),
axis.text.x=element_text(size=8),
axis.text.y=element_text(size=8),
axis.title=element_text(size=10),
panel.background = element_blank(),
panel.border = element_rect(colour = "grey", fill=NA, size=0.8))
Same type of visualize but this time separated between weekend and
weekdays. It is very obvious that we see more trips during weekdays and
less trips during weekends. Also, during weekends, number of trips start
to increase much later than during weekdays, suggesting the missing of
morning rush hour will significantly impact people’s use of bikeshare
services.
ggplot(bike_census)+
geom_freqpoly(aes(hour, color = weekend), binwidth = 3, lwd = 1.2)+
scale_color_manual(values=palette2) +
labs(color = "Time of Week") +
labs(title="Bike Share Trips in Jersey City - Weekend vs Weekday, Aug 2023",
x="Hour",
y="Trip Counts") +
theme(plot.subtitle = element_text(size = 9,face = "italic"),
plot.title = element_text(size = 12, face = "bold"),
axis.text.x=element_text(size=8),
axis.text.y=element_text(size=8),
axis.title=element_text(size=10),
panel.background = element_blank(),
panel.border = element_rect(colour = "grey", fill=NA, size=0.8))
Building onto that, we group this dataset by station and time of day
to visualize the total number of trips at each station during a specific
period of time on weekday or weekend. To simplify our visualization, we
selected the top 50 occurrences in our dataset. Note that here, each bar
represent an instance of station-time of day-weekday/weekend
combination. We are able to see that PM rush hour indeed sees that
greatest bikeshare demand for a couple of stations in particular.
Weekend rides only appear three times in the top 50 list. Zooming into
the detail reveals to us that Grove St PATH station
observes a total of 1117 rides during PM rush on a weekday, 801 rides
overnight on a weekday in August 2023. Hoboken Terminal - River
St & Hudson Pl station observes a total of 864 rides during
PM rush on weekday. South Waterfront Walkway - Sinatra Dr &
1 St station observes 319 rides overnight on a weekend and 731
rides during PM rush on a weekday in August 2023.
to_plot <- bike_census%>% # only show 100 stations
group_by(start_station_id, start_lat, start_lng, weekend, time_of_day, start_station_name) %>%
tally() %>%
arrange(-n) %>%
head(50)
to_plot$ID <- seq_along(to_plot$start_station_id)
to_plot %>%
arrange(-n) %>%
head(50) %>%
ggplot(aes(x = reorder(ID, -n), n, fill = time_of_day, color = weekend)) +
scale_fill_manual(values = palette4, name="Time of Day") +
guides(color="none") +
geom_bar(stat = "identity", position="stack") +
scale_color_manual(values = c("transparent", "black")) +
labs(title="Top 50 Occurences of Rides By Time")+
ylab("Number of Rides") +
xlab("") +
theme(axis.text.x = element_text(angle = 45, hjust = 1)) +
theme(plot.subtitle = element_text(size = 9,face = "italic"),
plot.title = element_text(size = 12, face = "bold"),
axis.text.x = element_blank(),
axis.ticks.x = element_blank(),
axis.text.y=element_text(size=8),
axis.title=element_text(size=10),
panel.background = element_blank(),
panel.border = element_rect(colour = "grey", fill=NA, size=0.8))
Spatial Analysis
In addition to examining our data through temporal lens, we made some
maps to show the actual location of stations with the greatest bikeshare
demand by time of day and day of the week. This proportional map
illustrates that stations receive the highest ride demand during PM rush
and overnight in weekdays and the lowest demand during AM rush. Stations
with the highest bikeshare demand are generally located along the Hudson
River. This could be attributed to two reasons: there are simply more
stations in this part of Jersey City or Citi Bike has strategically
built bike stations along the Hudson River to make daily commute easier
for those who live in Jersey City but need to work/study in
Manhattan.
ggplot()+
geom_sf(data = jcTracts %>%
st_transform(crs=4326), fill = "white")+
geom_point(data = to_plot,
aes(x=start_lng, y = start_lat, size=n), color = alpha("#6E0E0A", 0.7)) +
ylim(min(bike_census$start_lat), max(bike_census$start_lat))+
xlim(min(bike_census$start_lng), max(bike_census$start_lng)) +
scale_size(range = c(0.5, 8)) +
labs(size = "Number of Rides") +
facet_grid(weekend ~ time_of_day) +
labs(title="Locations of Stations with Greatest Bikeshare Demand by Time")+
theme(axis.text.x=element_blank(),
axis.text.y=element_blank(),
axis.ticks =element_blank(),
axis.title.x = element_blank(),
axis.title.y = element_blank(),
plot.subtitle = element_text(size = 9,face = "italic"),
plot.title = element_text(size = 12, face = "bold"),
panel.background = element_blank(),
panel.border = element_rect(colour = "grey", fill=NA, linewidth=0.8)
)
We grouped all the starting stations together to check for spatial
autocorrelation between stations. We believe that stations with greater
demand are more likely to be located to other stations with greater
demand. In this case, we can use the demand of nearby stations at a
particular time to estimate the demand of our target station. We
selected 80 stations with the highest number of rides in August 2023 and
computed the Moran’s I with 999 random Monte Carlo simulation. The
results show that our observed Moran’s I is highly significant, implying
that there’s spatial autocorrelation between bikeshares at nearby
stations.
moran_data <- bike_census %>%
group_by(start_station_id, start_lat, start_lng, start_station_name) %>%
tally() %>%
arrange(-n) %>%
head(80) %>%
st_as_sf(., coords = c("start_lng", "start_lat"), crs = 4326)
coords <- st_coordinates(moran_data)
neighborList <- knn2nb(knearneigh(coords, 5))
spatialWeights <- nb2listw(neighborList, style="W")
moranTest <- moran.mc(moran_data$n,
spatialWeights, nsim = 999)
ggplot(as.data.frame(moranTest$res[c(1:999)]), aes(moranTest$res[c(1:999)])) +
geom_histogram(binwidth = 0.01, fill = "#124E78") +
geom_vline(aes(xintercept = moranTest$statistic), colour = "#6E0E0A" ,lwd=1) +
scale_x_continuous(limits = c(-0.5, 0.5)) +
labs(title = "Observed and Permuted Moran's I for Stations with Most Rides",
subtitle = "Observed Moran's I in Red",
x = "Moran's I",
y = "Count") +
theme_light() +
theme(plot.subtitle = element_text(size = 9,face = "italic"),
plot.title = element_text(size = 12, face = "bold"),
axis.text.x=element_text(size=8),
axis.text.y=element_text(size=8),
axis.title=element_text(size=10))
This map below shows how stations with higher bikeshare demand are
clustered. In particular, there are two clusters of stations, and within
each cluster, there are a few stations with particularly large number of
rides. These stations are generally surrounded by stations with slightly
less rides.
ggplot() +
geom_sf(data = jcTracts, fill = "white") +
geom_sf(data = moran_data,
aes(size = n, color = n)) +
ylim(min(bike_census$start_lat), max(bike_census$start_lat))+
xlim(min(bike_census$start_lng), max(bike_census$start_lng)) +
scale_size(
range = c(1, 6),
guide = guide_legend(
direction = "horizontal",
nrow = 1,
label.position = "bottom")) +
scale_color_gradientn(colours = hcl.colors(5, "RdBu",rev = TRUE, alpha = 0.9),
guide = guide_legend(direction = "horizontal", nrow = 1)) +
labs(title = "Stations with Most Rides",
subtitle = "Dot size proportional to rides") +
theme(legend.position = "bottom") +
theme(axis.text.x=element_blank(),
axis.text.y=element_blank(),
axis.ticks =element_blank(),
axis.title.x = element_blank(),
axis.title.y = element_blank(),
plot.subtitle = element_text(size = 9,face = "italic"),
plot.title = element_text(size = 12, face = "bold"),
panel.background = element_blank(),
panel.border = element_rect(colour = "grey", fill=NA, linewidth=0.8)
)
This animated map below combines the spatial and temporal
characteristics of bike rides at 15min interval into a choropleth
map.
week33 <-
filter(bike_census , week == 33 & dotw == "Mon")
week33.panel <-
expand.grid(
interval15 = unique(week33$interval15),
Origin.Tract = unique(bike_census$Origin.Tract))
bike.animation.data <-
mutate(week33, Trip_Counter = 1) %>%
right_join(week33.panel) %>%
group_by(interval15, Origin.Tract) %>%
summarize(Trip_Count = sum(Trip_Counter, na.rm=T)) %>%
ungroup() %>%
left_join(jcTracts, by=c("Origin.Tract" = "GEOID")) %>%
st_sf() %>%
mutate(Trips = case_when(Trip_Count == 0 ~ "0 trips",
Trip_Count > 0 & Trip_Count <= 3 ~ "1-3 trips",
Trip_Count > 3 & Trip_Count <= 6 ~ "4-6 trips",
Trip_Count > 6 & Trip_Count <= 10 ~ "7-10 trips",
Trip_Count > 10 ~ "11+ trips")) %>%
mutate(Trips = fct_relevel(Trips, "0 trips","1-3 trips","4-6 trips",
"7-10 trips","10+ trips"))
bikeshare_animation <-
ggplot() +
geom_sf(data = bike.animation.data, aes(fill = Trips)) +
scale_fill_manual(values = palette5) +
labs(title = "Bikeshare For One Day in Aug 2023",
subtitle = "15 minute intervals: {current_frame}") +
transition_manual(interval15) +
theme(axis.text.x=element_blank(),
axis.text.y=element_blank(),
axis.ticks =element_blank(),
axis.title.x = element_blank(),
axis.title.y = element_blank(),
plot.subtitle = element_text(size = 9,face = "italic"),
plot.title = element_text(size = 12, face = "bold"),
panel.background = element_blank(),
panel.border = element_rect(colour = "grey", fill=NA, linewidth=0.8)
)
anim_save("animation.gif", animation = bikeshare_animation, end_pause = 4, height = 1000, width = 700, fps = 6, renderer = gifski_renderer())
Weather Analysis
On top of space and time, weather can be another significant factor
of in predicting bikeshare demand. Generally, we believe bikeshare
demand will decrease if it’s on a rainy, windy, or a very hot day in our
case. With that in mind, we downloaded the weather data using the
riem_measures
function for Newark airport between Aug 1,
2023 and Sept 1, 2023. Note that the Newark Airport station sufficiently
provides temporal weather for all of Jersey City. The weather.Panel is
generated to summarize temperature, precipitation, and wind speed for
every hour.
weather.Panel <-
riem_measures(station = "EWR", date_start = "2023-08-01", date_end = "2023-09-01") %>%
dplyr::select(valid, tmpf, p01i, sknt)%>%
replace(is.na(.), 0) %>%
mutate(interval60 = ymd_h(substr(valid,1,13))) %>%
mutate(week = week(interval60),
dotw = wday(interval60, label=TRUE)) %>%
group_by(interval60) %>%
summarize(Temperature = max(tmpf),
Precipitation = sum(p01i),
Wind_Speed = max(sknt)) %>%
mutate(Temperature = ifelse(Temperature == 0, 42, Temperature))
For the temperature, precipitation, and wind data we collected during
this month. We may see that there are a couple of rainy days in August.
Wind speed is pretty consistent throughout with a couple of really windy
days. Temperature fluctuates on a daily basis with higher temperature
during the day and cooler temperature at night.
grid.arrange(
ggplot(weather.Panel, aes(interval60,Precipitation)) + geom_line(color="#6E0E0A") +
labs(title="Percipitation", x="Hour", y="Perecipitation") +
theme(plot.subtitle = element_text(size = 9,face = "italic"),
plot.title = element_text(size = 12, face = "bold"),
axis.text.x=element_text(size=8),
axis.text.y=element_text(size=8),
axis.title=element_text(size=10),
panel.background = element_blank()),
ggplot(weather.Panel, aes(interval60,Wind_Speed)) + geom_line(color="#D74E09") +
labs(title="Wind Speed", x="Hour", y="Wind Speed") +
theme(plot.subtitle = element_text(size = 9,face = "italic"),
plot.title = element_text(size = 12, face = "bold"),
axis.text.x=element_text(size=8),
axis.text.y=element_text(size=8),
axis.title=element_text(size=10),
panel.background = element_blank()),
ggplot(weather.Panel, aes(interval60,Temperature)) + geom_line(color="#124E78") +
labs(title="Temperature", x="Hour", y="Temperature") +theme(plot.subtitle = element_text(size = 9,face = "italic"),
plot.title = element_text(size = 12, face = "bold"),
axis.text.x=element_text(size=8),
axis.text.y=element_text(size=8),
axis.title=element_text(size=10),
panel.background = element_blank()),
top="Weather Data ")
Space-Time Panel
In the following steps, we created a study panel where each instance
in the panel is a unique combination of space and time. In other words,
each row will represent the ride at a particular station during a
particular hour.
study.panel <-
expand.grid(interval60=unique(bike_census$interval60),
start_station_id = unique(bike_census$start_station_id)) %>%
left_join(., bike_census %>%
select(start_station_id, start_station_name, Origin.Tract, start_lng, start_lat)%>%
distinct() %>%
group_by(start_station_id) %>%
slice(1))
We need to add some more information to this panel. This includes
counting the number of rides at this station at this particular hour,
adding weather information, bringing in census data, and calculating
time and day of week using the lubridate
package.
bike.panel <-
bike_census %>%
mutate(Trip_Counter = 1) %>%
right_join(study.panel) %>%
group_by(interval60, start_station_id, start_station_name, Origin.Tract, start_lng, start_lat) %>%
summarize(Trip_Count = sum(Trip_Counter, na.rm=T)) %>%
left_join(weather.Panel) %>%
ungroup() %>%
filter(is.na(start_station_id) == FALSE) %>%
mutate(week = week(interval60),
dotw = wday(interval60, label = TRUE)) %>%
filter(is.na(Origin.Tract) == FALSE) %>%
left_join(., njCensus %>%
as.data.frame() %>%
select(-geometry), by = c("Origin.Tract" = "GEOID"))
Weather Correlation
Now, we could perform more exploratory analysis on our panel data to
prepare for our regression model. First, we would like to see how
temperature will correlate with the number of bike rides in August. The
figure below shows a significant, positive, strong, and linear
relationship between temperature and mean trip count for all weeks in
August. This means that as the temperature increase, bikeshare demand
will increase as well.
bike.panel %>%
group_by(interval60) %>%
summarize(Trip_Count = mean(Trip_Count),
Temperature = first(Temperature)) %>%
mutate(week = week(interval60)) %>%
ggplot(aes(Temperature, Trip_Count)) +
geom_point(color = "#6E0E0A") + geom_smooth(method = "lm", se= FALSE, color="#124E78") +
facet_wrap(~week, ncol=8) +
labs(title="Trip Count As a Fuction of Temperature by Week",
x="Temperature", y="Mean Trip Count") +
theme(plot.subtitle = element_text(size = 9,face = "italic"),
plot.title = element_text(size = 12, face = "bold"),
axis.text.x = element_blank(),
axis.ticks.x = element_blank(),
axis.text.y=element_text(size=8),
axis.title=element_text(size=10),
panel.background = element_blank(),
panel.border = element_rect(colour = "grey", fill=NA, size=0.8))
Next, we would like to see how wind speed will correlate with the
number of bike rides in August. We see that a linear and positive
relationship between these two variables, but the strength of such
relationship varies between different weeks. This generally means that
we see more bikeshare demand during windy days.
bike.panel %>%
group_by(interval60) %>%
summarize(Trip_Count = mean(Trip_Count),
Wind = first(Wind_Speed)) %>%
mutate(week = week(interval60)) %>%
ggplot(aes(Wind, Trip_Count)) +
geom_point(color = "#6E0E0A") + geom_smooth(method = "lm", se= FALSE, color="#124E78") +
facet_wrap(~week, ncol=8) +
labs(title="Trip Count As a Fuction of Wind by Week",
x="Wind Speed", y="Mean Trip Count") +
theme(plot.subtitle = element_text(size = 9,face = "italic"),
plot.title = element_text(size = 12, face = "bold"),
axis.text.x = element_blank(),
axis.ticks.x = element_blank(),
axis.text.y=element_text(size=8),
axis.title=element_text(size=10),
panel.background = element_blank(),
panel.border = element_rect(colour = "grey", fill=NA, size=0.8)) +
theme(plot.subtitle = element_text(size = 9,face = "italic"),
plot.title = element_text(size = 12, face = "bold"),
axis.text.x = element_blank(),
axis.ticks.x = element_blank(),
axis.text.y=element_text(size=8),
axis.title=element_text(size=10),
panel.background = element_blank(),
panel.border = element_rect(colour = "grey", fill=NA, size=0.8))
Serial Correlation
Should the number of trips exhibit serial (temporal) correlation,
then the time lag features will lead to better predictions. The logic
behind is that the number of trips at a particular hour will very much
correlate with the number of trips immediately before or after that
hour. If we know the number of trips at a particular hour, then it would
be easier for us to estimate the number of rides around that time. To
achieve that, we calculated 6 different forms of lag hours.
bike.panel <-
bike.panel %>%
arrange(start_station_id, interval60) %>%
mutate(lagHour = dplyr::lag(Trip_Count,1),
lag2Hours = dplyr::lag(Trip_Count,2),
lag3Hours = dplyr::lag(Trip_Count,3),
lag4Hours = dplyr::lag(Trip_Count,4),
lag12Hours = dplyr::lag(Trip_Count,12),
lag1day = dplyr::lag(Trip_Count,24)) %>%
mutate(day = yday(interval60))
The relationship between these lag hours and number of trips are
visualized. We see a strong, linear, and negative relationship between
lag12hours and number of trips 12 hours before. This means for example,
the number of rides we see at 5pm on a particular day is negatively
correlated with the number of rides at 5am that day. In contrast, we see
a strong, linear, and positive relationship between laghour and lag1day.
This means that the number of rides at a particular hour is highly
correlated with the number of rides in the immediate next hour and at
the same time the following day. Other than that, the strength of the
correlation decreases for lag2hour and lag3hour, meaning that the number
of rides at 4pm on a particular day, is more correlated with the number
of rides at 5pm, rather than at 6 or 7 pm.
as.data.frame(bike.panel) %>%
group_by(interval60) %>%
summarise_at(vars(starts_with("lag"), "Trip_Count"), mean, na.rm = TRUE) %>%
gather(Variable, Value, -interval60, -Trip_Count) %>%
ggplot(aes(x = Value , y = Trip_Count)) +
geom_point(size = 1, color = "#6E0E0A") + geom_smooth(method = "lm", se= FALSE, color="#124E78") +
facet_wrap(~Variable, ncol = 6)+
labs(x = "Variable", y = "Correlation", title = "Correlation Between Serial Lag Trips and Trip Count") +
theme(axis.text.x = element_text(angle = 45, hjust = 1)) +
theme(plot.subtitle = element_text(size = 9,face = "italic"),
plot.title = element_text(size = 12, face = "bold"),
axis.text.x = element_blank(),
axis.ticks.x = element_blank(),
axis.text.y=element_text(size=8),
axis.title=element_text(size=10),
panel.background = element_blank(),
panel.border = element_rect(colour = "grey", fill=NA, size=0.8))
Spatial Correlation
We’ve seen before that spatial autocorrelation exists here in that
the number of trips at a particular station during a particular time is
correlated with the number of trips at nearby stations. This means that
if we know the number of trips at a station, we may be able to estimate
the number of trips at the station next to it. Do achieve that, we
created a new dataframe called lag_result
and wrote a
nested loop to loop through each hour in August 2023. For each hour, we
then calculate the nearest 5 neighbor of each station and the average of
rides for that 5 stations. The results were joined back to our panel
dataset.
lag_result <- data.frame()
for (d in 213:243) {
for (h in 0:23) {
spacelag <- bike.panel %>%
mutate(hour = hour(interval60)) %>%
filter(day == d & hour == h) %>%
st_as_sf(., coords = c("start_lng", "start_lat"), crs = 4326)
coords.test_lag <- st_coordinates(spacelag)
neighborList.test_lag <- knn2nb(knearneigh(coords.test_lag, 5))
spatialWeights.test_lag <- nb2listw(neighborList.test_lag, style="W")
spacelag <- spacelag %>% mutate(lagTrip = lag.listw(spatialWeights.test_lag, Trip_Count))
lag_result <- rbind(lag_result, spacelag)
}
}
bike.panel <- lag_result %>%
mutate(start_lng = unlist(map(geometry, 1)),
start_lat = unlist(map(geometry, 2))) %>%
st_drop_geometry()
The figure below shows that for all weeks in August, there’s a
strong, linear, and positive relationship between the number of trips
and the number of trips in nearby stations. We will therefore include
this variable in our following regression analysis.
bike.panel %>%
group_by(interval60) %>%
summarize(Trip_Count = mean(Trip_Count),
Lag_Count = mean(lagTrip)) %>%
mutate(week = week(interval60)) %>%
ggplot(aes(Lag_Count, Trip_Count)) +
geom_point(size = 1, color = "#6E0E0A") + geom_smooth(method = "lm", se= FALSE, color="#124E78") +
facet_wrap(~week, ncol=8) +
labs(title="Correlation Between Spatial Lag Trips and Trip Count",
x="Lag Trip Count", y="Mean Trip Count") +
theme(plot.subtitle = element_text(size = 9,face = "italic"),
plot.title = element_text(size = 12, face = "bold"),
axis.text.x = element_blank(),
axis.ticks.x = element_blank(),
axis.text.y=element_text(size=8),
axis.title=element_text(size=10),
panel.background = element_blank(),
panel.border = element_rect(colour = "grey", fill=NA, size=0.8))
OLS Regression
We split our 5 week data into training set, which includes 3 weeks
and testing set, which include 2 weeks. After that, four different
linear regression are estimaed on training data, each with different
fixed effects.
reg1
focuses on just time, including hour fixed
effects, day of the week, Wind Speed and Temperature.
reg2
focuses on just space, including station and
weather conditions.
reg3
focuses on the combined effect of space and
time
reg4
focuses adds the time and space lag features
bike.Train <- filter(bike.panel, week <= 33)
bike.Test <- filter(bike.panel, week > 33)
# Time
reg1 <-
lm(Trip_Count ~ hour(interval60) + dotw + Temperature + Wind_Speed, data=bike.Train)
# Space
reg2 <-
lm(Trip_Count ~ start_station_name + Temperature + Wind_Speed, data=bike.Train)
# Time and Space
reg3 <-
lm(Trip_Count ~ start_station_name + hour(interval60) + dotw + Temperature + Wind_Speed,
data=bike.Train)
# Time and Space and TimeLag and SpaceLag
reg4 <-
lm(Trip_Count ~ start_station_name + hour(interval60) + dotw + Temperature + Wind_Speed +
lagHour + lag2Hours + lag12Hours + lag1day + lagTrip,
data=bike.Train)
Making Predictions
The purrr family of functions will be used to loop through a set of
‘nested’ data frames to efficiently compare across different model
specifications. Before testing our data, we nested our data by week and
created a model function. The first set of code separate our testing
data into two tibbles. The small function is created that takes a
tibble, dat and a regression model, fit as its inputs, and outputs
predictions as pred. This function is used simply for prediction.
bike.Test.weekNest <-
bike.Test %>%
nest(-week)
model_pred <- function(dat, fit){
pred <- predict(fit, newdata = dat)}
The nested format allows one to loop through each model for each week
and mutate summary statistics. In the code block below, week_predictions
are calculated for each week in the testing set. The map function
applies the model_pred
function to each nested tibble. Take
the first line of the below mutate, for example. A new column, Time_FE,
includes predictions for reg1 - the time fixed effects model. The
predictions are created by mapping the function, model_pred, to each row
of data, parameterizing fit as the reg1 model.
week_predictions <-
bike.Test.weekNest %>%
mutate(ATime_FE = map(.x = data, fit = reg1, .f = model_pred),
BSpace_FE = map(.x = data, fit = reg2, .f = model_pred),
CTime_Space_FE = map(.x = data, fit = reg3, .f = model_pred),
DTime_Space_FE_Lags = map(.x = data, fit = reg4, .f = model_pred)) %>%
gather(Regression, Prediction, -data, -week) %>%
mutate(Observed = map(data, pull, Trip_Count),
Absolute_Error = map2(Observed, Prediction, ~ abs(.x - .y)),
MAE = map_dbl(Absolute_Error, mean, na.rm = TRUE),
sd_AE = map_dbl(Absolute_Error, sd, na.rm = TRUE))
Error Analysis
Comparing the mean absolute errors for our four models, we find the
error to be the higest for model 1, which only takes time effect into
account. Model 2 and 3 account for space and space + time respectively,
which lead to an increase in accuracy level. Adding space lag and time
lag, however, will greatly improve the accuracy of our mode. We also ran
the variance of inflation function in the background and checked to make
sure that there’s no colinerarity in model 4.
week_predictions %>%
dplyr::select(week, Regression, MAE) %>%
gather(Variable, MAE, -Regression, -week) %>%
ggplot(aes(week, MAE)) +
geom_bar(aes(fill = Regression), position = "dodge", stat="identity") +
scale_fill_manual(values = palette4) +
labs(title = "Mean Absolute Errors by Model Specification and Week") +
theme(plot.subtitle = element_text(size = 9,face = "italic"),
plot.title = element_text(size = 12, face = "bold"),
axis.text.x = element_blank(),
axis.ticks.x = element_blank(),
axis.text.y=element_text(size=8),
axis.title=element_text(size=10),
panel.background = element_blank(),
panel.border = element_rect(colour = "grey", fill=NA, size=0.8))
The figure below further demonstrates the accuracy of each model by
comparing predicted to observed number of trips. Model 4 clearly stands
out among the 4 models to have higher predicting power of bikeshare
demand.
week_predictions %>%
mutate(interval60 = map(data, pull, interval60),
start_station_id = map(data, pull, start_station_id)) %>%
dplyr::select(interval60, start_station_id, Observed, Prediction, Regression) %>%
unnest() %>%
gather(Variable, Value, -Regression, -interval60, -start_station_id) %>%
group_by(Regression, Variable, interval60) %>%
summarize(Value = sum(Value)) %>%
ggplot(aes(interval60, Value, colour=Variable)) +
geom_line(size = 1.1) +
scale_color_manual(values=palette2)+
facet_wrap(~Regression, ncol=1) +
labs(title = "Predicted/Observed Bike Share Time Series", subtitle = "Jersey City: a test set of 2 weeks", x = "Hour", y= "Station Trips") +
theme(plot.subtitle = element_text(size = 9,face = "italic"),
plot.title = element_text(size = 12, face = "bold"),
axis.text.x = element_blank(),
axis.ticks.x = element_blank(),
axis.text.y=element_text(size=8),
axis.title=element_text(size=10),
panel.background = element_blank(),
panel.border = element_rect(colour = "grey", fill=NA, size=0.8))
We also need to check if our predictions generalize across space and
time. To do that, we mapped our mean absolute error of model 4 across
space. We can see that this model does not do a better job in predicting
number of rides at stations along the Hudson River, which are typically
stations with higher bikeshare demands during both weekend and weekdays.
Other than that, this model does a pretty good job in predicting
bikeshare demands at stations with lower number of trips in August.
week_predictions %>%
mutate(interval60 = map(data, pull, interval60),
start_station_id = map(data, pull, start_station_id),
start_latitude = map(data, pull, start_lat),
start_longitude = map(data, pull, start_lng)) %>%
select(interval60, start_station_id, start_longitude, start_latitude, Observed, Prediction, Regression) %>%
unnest() %>%
filter(Regression == "DTime_Space_FE_Lags") %>%
group_by(start_station_id, start_longitude, start_latitude) %>%
summarize(MAE = mean(abs(Observed-Prediction), na.rm = TRUE))%>%
ggplot(.)+
geom_sf(data = jcTracts, fill = "white")+
geom_point(aes(x = start_longitude, y = start_latitude, color = MAE),
fill = "transparent")+
scale_color_continuous(low = "#124E78", high = "#6E0E0A", name= "MAE")+
ylim(min(bike_census$start_lat), max(bike_census$start_lat))+
xlim(min(bike_census$start_lng), max(bike_census$start_lng)) +
labs(title="Mean Abs Error, Test Set, Model 4") +
theme(axis.text.x=element_blank(),
axis.text.y=element_blank(),
axis.ticks =element_blank(),
axis.title.x = element_blank(),
axis.title.y = element_blank(),
plot.subtitle = element_text(size = 9,face = "italic"),
plot.title = element_text(size = 12, face = "bold"),
panel.background = element_blank(),
panel.border = element_rect(colour = "grey", fill=NA, linewidth=0.8)
)
Comparing across space and time, the figure below shows that model 4 has
been pretty consistent in predicting number of trips across time.
Stations that are predicted to have higher demand in the weekend will
continue to be predicted to have higher demand during the weekdays.
Errors carry throughout time. This implies that stations that
historically have higher demand have higher variability in the number of
rides and are more difficult to predict with the current predictors we
have. In fact, our current predictors only explains 50.12% of the
variability. What’s problematic about this is that if we continue to
have larger errors when predicting stations with higher bikeshare
demand, we will largely fail to meet the demand of bike users at these
stations.
week_predictions %>%
mutate(interval60 = map(data, pull, interval60),
start_station_id = map(data, pull, start_station_id),
start_latitude = map(data, pull, start_lat),
start_longitude = map(data, pull, start_lng),
dotw = map(data, pull, dotw) ) %>%
select(interval60, start_station_id, start_longitude,
start_latitude, Observed, Prediction, Regression,
dotw) %>%
unnest() %>%
filter(Regression == "DTime_Space_FE_Lags")%>%
mutate(weekend = ifelse(dotw %in% c("Sun", "Sat"), "Weekend", "Weekday"),
time_of_day = case_when(hour(interval60) < 7 | hour(interval60) > 18 ~ "Overnight",
hour(interval60) >= 7 & hour(interval60) < 10 ~ "AM Rush",
hour(interval60) >= 10 & hour(interval60) < 15 ~ "Mid-Day",
hour(interval60) >= 15 & hour(interval60) <= 18 ~ "PM Rush")) %>%
group_by(start_station_id, weekend, time_of_day, start_longitude, start_latitude) %>%
summarize(MAE = mean(abs(Observed-Prediction), na.rm = TRUE))%>%
ggplot(.)+
geom_sf(data = jcTracts, color = "grey", fill = "transparent")+
geom_point(aes(x = start_longitude, y = start_latitude, color = MAE),
fill = "transparent", size = 0.5)+
scale_color_continuous(low = "#124E78", high = "#6E0E0A", name= "MAE")+
ylim(min(bike_census$start_lat), max(bike_census$start_lat))+
xlim(min(bike_census$start_lng), max(bike_census$start_lng)) +
facet_grid(weekend~time_of_day)+
labs(title="Mean Absolute Errors by Time, Test Set, Model 4") +
theme(axis.text.x=element_blank(),
axis.text.y=element_blank(),
axis.ticks =element_blank(),
axis.title.x = element_blank(),
axis.title.y = element_blank(),
plot.subtitle = element_text(size = 9,face = "italic"),
plot.title = element_text(size = 12, face = "bold"),
panel.background = element_blank(),
panel.border = element_rect(colour = "grey", fill=NA, linewidth=0.8)
)
Cross-Validation
Finally, we performed 100 cross-validation tests for all five weeks
of data using model 4. The MAE value is 0.44, indicating that despite
being our best solution so far, the model’s prediction error can still
be relatively large. It will also be good practice to cross validate
across socio-economic indicators to see if bike stations’ demand within
certain neighborhoods are disproportionately under/over predicted, but
our model current does not take into account any socioeconomic variables
as predictor variable and the scenario in Jersey City is that more
stations are built
fitControl <- trainControl(method = "cv", number = 100)
reg.cv <- train(Trip_Count ~ start_station_name + hour(interval60) + dotw + Temperature + Wind_Speed + lagHour + lag2Hours + lag12Hours + lag1day + lagTrip, data=bike.panel, method = "lm", trControl = fitControl, na.action = na.pass)
reg.cv$resample %>%
summarise(MAE = mean(reg.cv$resample[,3]),
sd(reg.cv$resample[,3])
) %>%
kbl(col.name=c('Mean Absolute Error','Standard Deviation of MAE')) %>%
kable_styling(bootstrap_options = c("striped", "hover", "condensed"))
Mean Absolute Error
|
Standard Deviation of MAE
|
0.4484213
|
0.0311512
|
LS0tCnRpdGxlOiAiUHJlZGljdGluZyBTdW1tZXIgQmlrZXNoYXJlIERlbWFuZCBpbiBKZXJzZXkgQ2l0eSIKYXV0aG9yOiAiRW1pbHkgWmhvdSIKZGF0ZTogImByIFN5cy5EYXRlKClgIgpvdXRwdXQ6CiAgaHRtbF9kb2N1bWVudDoKICAgIHRoZW1lOiBzaW1wbGV4CiAgICB0b2M6IHllcwogICAgdG9jX2Zsb2F0OiB5ZXMKICAgIGNvZGVfZm9sZGluZzogaGlkZQogICAgY29kZV9kb3dubG9hZDogeWVzCmVkaXRvcl9vcHRpb25zOgogIG1hcmtkb3duOgogICAgd3JhcDogc2VudGVuY2UKLS0tCgpWZXJzaW9uIDEuMCBcfCBGaXJzdCBDcmVhdGVkIE5vdiA0LCAyMDIzIFx8IFVwZGF0ZWQgTm92IDE1LCAyMDIzCgpLZXl3b3JkczogVHJhbnNwb3J0YXRpb24gUGxhbm5pbmcsIFJpZGVzaGFyZSBGb3JlY2FzdCwgU3BhdGlhbCBhbmQgU2VyaWFsIEF1dG9jb3JyZWxhdGlvbiwgTmVzdGVkIFRpYmJsZSwgT0xTIFJlZ3Jlc3Npb24sIHRpbWUgbGFnLCBnZ2FuaW1hdGUsIHB1cnJyCgoKYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9CmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSkKYGBgCgoKIyBJbnRyb2R1Y3Rpb24KCkJpa2VzaGFyZSBwcm9ncmFtcyBoYXZlIGJlY29tZSBhbiBpbnRlZ3JhbCBwYXJ0IG9mIHVyYmFuIHRyYW5zcG9ydGF0aW9uIHN5c3RlbSwgcHJvdmlkaW5nIGEgc3VzdGFpbmFibGUgYW5kIGNvbnZlbmllbnQgYWx0ZXJuYXRpdmUgdG8gdHJhZGl0aW9uYWwgY29tbXV0aW5nIG1ldGhvZHMuIENpdGkgQmlrZSBpcyBvbmUgb2YgdGhlIGVhcmxpZXN0IGJpY3ljbGUgc2hhcmluZyBzeXN0ZW0gY3JlYXRlZCBieSBMeWZ0IGluIDIwMDggYXQgTllDLiBCdXQgaW4gcmVjZW50IHllYXJzLCB0aGlzIHByb2dyYW0gYWxzbyBleHRlbmRzIHRvIEplcnNleSBDaXR5IGFjcm9zcyB0aGUgSHVkc29uIFJpdmVyIGluIG9yZGVyIHRvIGNhdGVyIHRvIHRoZSBkeW5hbWljIG1vYmlsaXR5IG5lZWRzIG9mIHJlc2lkZW50cyB3aG8gaGFkIHRvIGNvbW11dGUgdG8gTWFuaGF0dGFuIGZvciB3b3JrLiBBcyB0aGUgcG9wdWxhcml0eSBvZiBDaXRpIEJpa2UgY29udGludWVzIHRvIHNvYXIsIHRoZXJlIGFyaXNlcyBhIHByZXNzaW5nIG5lZWQgZm9yIGVmZmVjdGl2ZSBtYW5hZ2VtZW50IHN0cmF0ZWdpZXMgdG8gZW5zdXJlIHRoZSBhdmFpbGFiaWxpdHkgb2YgYmlrZXMgYXQgZWFjaCBzdGF0aW9uIGluIEplcnNleSBDaXR5LiAKCkJpa2VzaGFyaW5nIChhbmQgcmlkZXNoYXJpbmcpIGluaGVyZW50bHkgaW52b2x2ZXMgdGhlIGNoYWxsZW5nZSBvZiBtYWludGFpbmluZyBhIGRlbGljYXRlIGVxdWlsaWJyaXVtIGluIGJpa2UgZGlzdHJpYnV0aW9uLCBpZS4gInJlLWJhbGFuY2luZyIuIFRoaXMgaXNzdWUgYXJpc2VzIGZyb20gdGhlIHNwYXRpYWwgYW5kIHRlbXBvcmFsIHZhcmlhdGlvbnMgaW4gdXNlciBkZW1hbmQuIElmIGNlcnRhaW4gc3RhdGlvbnMgZXhwZXJpZW5jZSBhIHN1cmdlIGluIGRlbWFuZCBkdXJpbmcgbW9ybmluZyBydXNoIGhvdXJzIHdoaWxlIG90aGVycyB3aXRuZXNzIGEgZGVjbGluZSwgdGhpcyBjb3VsZCByZXN1bHQgaW4gc3RhdGlvbiBjbHVzdGVycyB3aXRoIGRlcGxldGVkIG9yIHN1cnBsdXMgYmlrZSBzdG9ja3MsIHRoZXJlYnkgY29tcHJvbWlzaW5nIHVzZXIgZXhwZXJpZW5jZS4gQ29tcGFuaWVzIGxpa2UgVWJlciAmIEx5ZnQgZ2VuZXJhdGUgYW5kIGFuYWx5emUgdHJlbWVuZG91cyBhbW91bnRzIG9mIGRhdGEgdG8gaW5jZW50aXZpemUgYmlrZSAocmlkZSkgc2hhcmUgdXNlLCBzb2x2ZSByb3V0aW5nIHByb2JsZW1zLCBjYWxjdWxhdGUgcHJpY2luZywgYW5kIG9idmlvdXNseSBmb3JlY2FzdCBkZW1hbmRzLCBidXQgdGhleSBhcmUgbm90IHZlcnkgdHJhbnNwYXJlbnQgYWJvdXQgdGhlIGFsZ29yaXRobSB0aGV5IHVzZS4gVGhlcmVmb3JlLCB0aGUgZ29hbCBvZiB0aGlzIHJlcG9ydCBpcyBrZWVwIGEgc2ltcGxlLCBidXQgb3BlbiBzb3VyY2UgdmVyc2lvbiBvZiBiaWtlc2hhcmUgZGVtYW5kIHByZWRpY3Rpb24sIGJ5IGhhcm5lc3Npbmcgb2YgcG93ZXIgb2YgbWFjaGluZSBsZWFybmluZywgcGFydGljdWxhcmx5IHRoZSBPcmRpbmFyeSBMZWFzdCBTcXVhcmUgKE9MUykgcmVncmVzc2lvbiB0byByYXBpZGx5IHByZWRpY3QgdGhlIGRlbWFuZCBmb3IgYmlrZXMgYWNyb3NzIHZhcmlvdXMgc3RhdGlvbnMgaW4gSmVyc2V5IENpdHkuIFdlIHdpbGwgcGVyZm9ybSBhbmFseXNpcyBvbiB0aGUgaGlzdG9yaWNhbCBkYXRhIG9uIGJpa2UgdXNhZ2UgYXQgZGlmZmVyZW50IHN0YXRpb25zIGFuZCBkaWZmZXJlbnQgdGltZSBhcyB3ZWxsIGFzIHVuZGVyIGRpZmZlcmVudCB3ZWF0aGVyIGNvbmRpdGlvbnMuIFRoZSBmb3JlY2FzdCBhbGxvd3MgdGhlIENpdGkgQmlrZSBwcm9ncmFtIHRvIHByb2FjdGl2ZWx5IGFkZHJlc3MgaW1iYWxhbmNlcywgc3RyYXRlZ2ljYWxseSByZS1wb3NpdGlvbmluZyBiaWtlcyBpbiBhbnRpY2lwYXRpb24gb2YgcGVhayB1c2FnZSB0aW1lIG9yIHNwZWNpYWwgZXZlbnRzLiAKClRoZSBzdWJzZXF1ZW50IHNlY3Rpb25zIG9uIHRoaXMgcmVwb3J0IGlzIG9yZ2FuaXplZCBhcyBmb2xsb3dzLiBGaXJzdGx5LCB3ZSBsb2FkIGluIG91ciBkYXRhc2V0cyBmb3IgYSBxdWljayBleGFtaW5hdGlvbnMgYW5kIHBlcmZvcm0gc29tZSBuZWNlc3NhcnkgZmVhdHVyZSBlbmdpbmVlcmluZy4gU2Vjb25kbHksIHdlIGNvbmR1Y3QgZXhwbG9yYXRvcnkgYW5hbHlzaXMgb24gdGhlIHRlbXBvcmFsIGFuZCBzcGF0aWFsIGFzcGVjdHMgb2YgYmlrZXNoYXJlIGRhdGEuIFdlIHdpbGwgYWxzbyBsb29rIGF0IHNvbWUgd2VhdGhlciBkYXRhIGluIHRoaXMgcHJvY2Vzcy4gVGhpcmRseSwgd2UgY3JlYXRlIGEgc3BhY2UtdGltZSBwYW5lbCB3aGVyZSBlYWNoIHJvdyBpcyBhIHVuaXF1ZSBpbnN0YW5jZSBvZiB0aW1lLXNwYWNlIGNvbWJpbmF0aW9uIG9mIHJpZGVzIGFuZCB2aXN1YWxpemUgdGhlIGNvcnJlbGF0aW9uIGJldHdlZW4gc3BhY2UsIHRpbWUsIHdlYXRoZXIsIGFuZCBudW1iZXIgb2YgcmlkZXMuIEFuZCBmaW5hbGx5LCB3ZSBtYWtlIGNyZWF0ZWQgZm91ciBkaWZmZXJlbnQgbW9kZWxzLCB0ZXN0IHRoZWlyIHNlbnNpYmlsaXR5IHRvIGVycm9ycywgYW5kIGNvbmR1Y3QgY3Jvc3MgdmFsaWRhdGlvbi4gCgpUaGUgR2l0SHViIHJlcG9zaXRvcnkgZm9yIHRoaXMgcmVwb3J0IGlzIFtoZXJlXShodHRwczovL2dpdGh1Yi5jb20vZW1pbHl6aG91MTEyL01VU0E1MDgwLU5KLUJpa2VTaGFyZSkuCgojIFNldCBVcAoKYGBge3IgbGlicmFyeSBhbmQgZGF0YSwgaW5jbHVkZT1GQUxTRX0KbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkoc2YpCmxpYnJhcnkobHVicmlkYXRlKQpsaWJyYXJ5KHRpZ3JpcykKbGlicmFyeShnZ2FuaW1hdGUpCmxpYnJhcnkocmllbSkKbGlicmFyeShncmlkRXh0cmEpCmxpYnJhcnkoa25pdHIpCmxpYnJhcnkoa2FibGVFeHRyYSkKbGlicmFyeSh0aWR5Y2Vuc3VzKQpsaWJyYXJ5KG1hcHZpZXcpCmxpYnJhcnkoc2ZkZXApCmxpYnJhcnkoc3BkZXApCmxpYnJhcnkoY2FyZXQpCmxpYnJhcnkgKHJlYWRyKQoKb3B0aW9ucyh0aWdyaXNfY2xhc3MgPSAic2YiKQpzb3VyY2UoImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS91cmJhblNwYXRpYWwvUHVibGljLVBvbGljeS1BbmFseXRpY3MtTGFuZGluZy9tYXN0ZXIvZnVuY3Rpb25zLnIiKQoKcGFsZXR0ZTUgPC0gYygiIzEyNEU3OCIsIiNGMEYwQzkiLCIjRjJCQjA1IiwiI0Q3NEUwOSIsIiM2RTBFMEEiKQpwYWxldHRlNCA8LSBjKCIjMTI0RTc4IiwiI0YwRjBDOSIsIiNGMkJCMDUiLCIjNkUwRTBBIikKcGFsZXR0ZTIgPC0gYygiIzEyNEU3OCIsIiM2RTBFMEEiKQoKdGlkeWNlbnN1czo6Y2Vuc3VzX2FwaV9rZXkoImU3OWYzNzA2YjZkNjEyNDk5NjhjNmNlODg3OTRmNmY1NTZlNWJmM2QiLCBvdmVyd3JpdGUgPSBUUlVFKQpgYGAKClRoZSBkYXRhIHdlIHVzZSBoZXJlIGlzIG9idGFpbmVkIGZyb20gW05ZQyBPcGVuRGF0YV0oaHR0cHM6Ly9zMy5hbWF6b25hd3MuY29tL3RyaXBkYXRhL2luZGV4Lmh0bWwpIHRoYXQgY29udGFpbnMgdGhlIGxhdGVzdCByZWNvcmQgb2YgQ2l0aSBCaWtlIHVzYWdlIG9uIGEgbW9udGhseSBiYXNlLiBOb3RlIHRoYXQgd2hpbGUgSmVyc2V5IENpdHkgaXMgbG9jYXRlZCBpbiBOZXcgSmVyc2V5LCBDaXRpIEJpa2UgaXMgbWFpbmx5IG9wZXJhdGVkIGluIE5ZQyBhbmQgdGhlcmVmb3JlIGl0cyBkYXRhc2V0IGlzIGFsc28gb3BlcmF0ZWQgYnkgTllDLiBXZSBzZWxlY3RlZCBhbGwgYmlrZXNoYXJlIGRhdGEgaW4gSmVyc2V5IENpdHkgZnJvbSBBdWcgMSAyMDIzIHRvIEF1ZyAzMSAyMDIzIGFzIG91ciB0ZW1wb3JhbCBmcmFtZS4gVGhlIGRhdGFzZXQgbWFpbmx5IGluY2x1ZGVzIGxvY2F0aW9ucyBhbmQgbmFtZXMgb2Ygc3RhdGlvbnMgb2Ygd2hpY2ggdGhlIHVzZXIgc3RhcnRlZCB0aGUgcmlkZSBhcyB3ZWxsIGFzIGVuZCB0aGUgcmlkZS4gSW4gYWRkaXRpb24sIHdlIGFsc28gZG93bmxvYWRlZCB0aGUgY2Vuc3VzIHRyYWN0IGdlb21ldHJpZXMgb2YgdGhlIGVudGlyZSBIdWRzb24gQ291bnR5IHZpYSB0aWR5Y2Vuc3VzLCB3aGljaCBpbmNsdWRlcyBKZXJzZXkgQ2l0eS4gCgpgYGB7ciBvYnRhaW4gY2Vuc3VzIGdlb20gYW5kIGJpa2UgZGF0YSwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRSwgcmVzdWx0cz0naGlkZScsIGNhY2hlPVRSVUV9CgpueWNiaWtlIDwtIHJlYWQuY3N2KHVybCgiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL2VtaWx5emhvdTExMi9NVVNBNTA4MC1OSi1CaWtlU2hhcmUvbWFpbi9KQy0yMDIzMDgtY2l0aWJpa2UtdHJpcGRhdGEuY3N2IikpCgpuakNlbnN1cyA8LSBnZXRfYWNzKGdlb2dyYXBoeSA9ICJ0cmFjdCIsIAogICAgICAgICAgdmFyaWFibGVzID0gYygiQjAxMDAzXzAwMSIsICJCMTkwMTNfMDAxIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICJCMDIwMDFfMDAyIiwgIkIwODAxM18wMDEiLAogICAgICAgICAgICAgICAgICAgICAgICAiQjA4MDEyXzAwMSIsICJCMDgzMDFfMDAxIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICJCMDgzMDFfMDEwIiwgIkIwMTAwMl8wMDEiKSwgCiAgICAgICAgICB5ZWFyID0gMjAyMSwgCiAgICAgICAgICBzdGF0ZSA9ICJOSiIsIAogICAgICAgICAgZ2VvbWV0cnkgPSBUUlVFLCAKICAgICAgICAgIGNvdW50eT1jKCJIdWRzb24iKSwKICAgICAgICAgIG91dHB1dCA9ICJ3aWRlIikgJT4lCiAgcmVuYW1lKFRvdGFsX1BvcCA9ICBCMDEwMDNfMDAxRSwKICAgICAgICAgTWVkX0luYyA9IEIxOTAxM18wMDFFLAogICAgICAgICBNZWRfQWdlID0gQjAxMDAyXzAwMUUsCiAgICAgICAgIFdoaXRlX1BvcCA9IEIwMjAwMV8wMDJFLAogICAgICAgICBUcmF2ZWxfVGltZSA9IEIwODAxM18wMDFFLAogICAgICAgICBOdW1fQ29tbXV0ZXJzID0gQjA4MDEyXzAwMUUsCiAgICAgICAgIE1lYW5zX29mX1RyYW5zcG9ydCA9IEIwODMwMV8wMDFFLAogICAgICAgICBUb3RhbF9QdWJsaWNfVHJhbnMgPSBCMDgzMDFfMDEwRSkgJT4lCiAgc2VsZWN0KFRvdGFsX1BvcCwgTWVkX0luYywgV2hpdGVfUG9wLCBUcmF2ZWxfVGltZSwKICAgICAgICAgTWVhbnNfb2ZfVHJhbnNwb3J0LCBUb3RhbF9QdWJsaWNfVHJhbnMsCiAgICAgICAgIE1lZF9BZ2UsCiAgICAgICAgIEdFT0lELCBnZW9tZXRyeSkgJT4lCiAgbXV0YXRlKFBlcmNlbnRfV2hpdGUgPSBXaGl0ZV9Qb3AgLyBUb3RhbF9Qb3AsCiAgICAgICAgIE1lYW5fQ29tbXV0ZV9UaW1lID0gVHJhdmVsX1RpbWUgLyBUb3RhbF9QdWJsaWNfVHJhbnMsCiAgICAgICAgIFBlcmNlbnRfVGFraW5nX1B1YmxpY19UcmFucyA9IFRvdGFsX1B1YmxpY19UcmFucyAvIE1lYW5zX29mX1RyYW5zcG9ydCkKCmpjVHJhY3RzIDwtIG5qQ2Vuc3VzICU+JQogIGFzLmRhdGEuZnJhbWUoKSAlPiUKICBkaXN0aW5jdChHRU9JRCwgLmtlZXBfYWxsID0gVFJVRSkgJT4lCiAgc2VsZWN0KEdFT0lELCBnZW9tZXRyeSkgJT4lIAogIHN0X3NmKCkKYGBgCgpTaW5jZSBvdXIgcmF3IGJpa2VzaGFyZSBkYXRhIGlzIGEgbm9uLXNwYXRpYWwgbGF5ZXIsIGRlc3BpdGUgY29udGFpbmluZyBzcGF0aWFsIGF0dHJpYnV0ZXMsIG91ciBuZXh0IHN0ZXAgaXMgdG8gbWFrZSBpdCBzcGF0aWFsIGJ5IGpvaW5pbmcgaXQgdG8gdGhlIGNlbnN1cyBnZW9tZXRyeS4gV2UgYXJlIG9ubHkgaW50ZXJlc3RlZCBpbiB0aGUgc3RhcnQgbG9jYXRpb24gb2YgZWFjaCByaWRlIGJlY2F1c2Ugd2Ugd2FudCB0byBwcmVkaWN0IHRoZSBjYXBhY2l0eSBvZiBlYWNoIHN0YXRpb24gd2hlbiB0aGUgdXNlciBuZWVkIGEgYmlrZSB0byBzdGFydCB0aGUgcmlkZS4gVGhpcyByZXF1aXJlcyB1cyB0byBmaWx0ZXIgb3V0IHJvd3Mgd2l0aCBtaXNzaW5nIGxhdGl0dWRlIGFuZCBsb25naXR1ZGUuIFdlIGFsc28gZm91bmQgdGhhdCBzb21lIHN0YXRpb25zIGhhdmUgZXJyb25lb3VzIGxvbmdpdHVkZSBhbmQgbGF0aXR1ZGUgY29kaW5nIHRoYXQgbGVhZCB0byB0aGVzZSBwb2ludHMgYmVpbmcgbG9jYXRlZCBpbiB0aGUgSHVkc29uIFJpdmVyLiBUaGVzZSBwb2ludHMgYWxzbyBuZWVkIHRvIGJlIHJlbW92ZWQuIAoKYGBge3IgYWRkIGNlbnN1cyB0cmFjdH0KCmJpa2VfY2Vuc3VzIDwtIHN0X2pvaW4obnljYmlrZSAlPiUgCiAgICAgICAgICAgICAgICAgICAgICAgICBmaWx0ZXIoaXMubmEoc3RhcnRfbGF0KSA9PSBGQUxTRSAmCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaXMubmEoc3RhcnRfbG5nKSA9PSBGQUxTRSAmCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaXMubmEoZW5kX2xhdCkgPT0gRkFMU0UgJgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlzLm5hKGVuZF9sbmcpID09IEZBTFNFKSAlPiUKICAgICAgICAgICAgICAgICAgICAgICAgIHN0X2FzX3NmKC4sIGNvb3JkcyA9IGMoInN0YXJ0X2xuZyIsICJzdGFydF9sYXQiKSwgY3JzID0gNDMyNiksCiAgICAgICAgICAgICAgICAgICAgICAgamNUcmFjdHMgJT4lICBzdF90cmFuc2Zvcm0oY3JzPTQzMjYpLAogICAgICAgICAgICAgICAgICAgICAgIGpvaW49c3RfaW50ZXJzZWN0cywgbGVmdCA9IFRSVUUpICU+JQogIHJlbmFtZShPcmlnaW4uVHJhY3QgPSBHRU9JRCkgJT4lCiAgbXV0YXRlKHN0YXJ0X2xuZyA9IHVubGlzdChtYXAoZ2VvbWV0cnksIDEpKSwKICAgICAgICAgc3RhcnRfbGF0ID0gdW5saXN0KG1hcChnZW9tZXRyeSwgMikpKSU+JQogIGZpbHRlcighKHJpZGVfaWQgJWluJSBjKCJCRUM5Q0M0RDEwMDdDNzUzIiwgIjg2MDg3RjQzMTc4NUVERTciLCAiQTFBQUI5MjYzMTQzOTg4MyIsICI3MTkzOERBMDg1MjQyRENDIiwgIjQyMDNDNzc2NjhDNDdDNzUiLCAiQzkyNDEwQ0EyQUVGMzk0NyIsICI2RTAyQ0JGMzZDOTBGMjQ0IiwgIjAyNTk4ODUyNTNGRDIzOEUiLCAiQjM5MTM2QjM3QUZCNUZFOSIsICIxRUM5Mzc2RDE1NTlDNTFDIiwgIkNEREYxNkQzQTM3QkUzNzAiKSkpICU+JSAKICBhcy5kYXRhLmZyYW1lKCkKICAKYGBgCgpUaGUgZm9sbG93aW5nIHZpc3VhbGl6YXRpb24gc2hvd3MgYWxsIHRoZSBzdGVwcyB3ZSBkaWQgYWJvdmUuIAoKYGBge3IgZGlzdHJpYnV0aW9uIG9mIHN0YXRpb25zfQoKZ2dwbG90KCkrCiAgZ2VvbV9zZihkYXRhID0gamNUcmFjdHMgJT4lCiAgICAgICAgICBzdF90cmFuc2Zvcm0oY3JzPTQzMjYpLCBmaWxsPSJ3aGl0ZSIpKwogIGdlb21fcG9pbnQoZGF0YSA9IGJpa2VfY2Vuc3VzLCBhZXMoeD1zdGFydF9sbmcsIHkgPSBzdGFydF9sYXQpLCAKICAgICAgICAgICAgY29sb3IgPSAiIzZFMEUwQSIsIGFscGhhID0gMSwgc2l6ZSA9IDAuMykgKyAKICB5bGltKG1pbihiaWtlX2NlbnN1cyRzdGFydF9sYXQpLCBtYXgoYmlrZV9jZW5zdXMkc3RhcnRfbGF0KSkrCiAgeGxpbShtaW4oYmlrZV9jZW5zdXMkc3RhcnRfbG5nKSwgbWF4KGJpa2VfY2Vuc3VzJHN0YXJ0X2xuZykpICsKICBsYWJzKHRpdGxlID0gIkxvY2F0aW9uIG9mIFJpZGVzIFN0YXJ0aW5nIFBvaW50cyBpbiBKZXJzZXkgQ2l0eSIpICsKICB0aGVtZShheGlzLnRleHQueD1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGlja3MgPWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aXRsZS55ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDksZmFjZSA9ICJpdGFsaWMiKSwKICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgZmFjZSA9ICJib2xkIiksCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X3JlY3QoY29sb3VyID0gImdyZXkiLCBmaWxsPU5BLCBsaW5ld2lkdGg9MC44KQogICAgICAgICkKCmBgYAoKV2Ugd2lsbCBwZXJmb3JtIHNvbWUgYmFzaWMgZmVhdHVyZSBlbmdpbmVlcmluZyB1c2luZyBSJ3MgcG93ZXJmdWwgYGx1YnJpZGF0ZWAgcGFja2FnZSB0byBleHRyYWN0IHNvbWUgdGVtcG9yYWwgaW5mb3JtYXRpb24gZm9yIGVhY2ggcmlkZS4gVGhpcyBpbmNsdWRlcyBjYWxjdWxhdGluZyB0aGUgNjBtaW4gYW5kIDE1bWluIGludGVydmFsIGZvciB0aGUgc3RhcnRpbmcgdGltZSBvZiBlYWNoIHJpZGUsIGFuZCBjYXRlZ29yaXppbmcgdGhlc2UgaW50ZXJ2YWxzIGludG8gYnkgdGhlIHRpbWUgb2YgZGF5IGludG8gIk92ZXJuaWdodCIsICJBTSBSdXNoIiwgIk1pZC1EYXkiLCBhbmQgIlBNIFJ1c2giIGFuZCBieSBkYXkgb2Ygd2VlayBpbnRvIHdlZWtkYXkgYW5kIHdlZWtlbmQuIAoKYGBge3IgdGVtcG9yYWwgZmVhdHVyZSBlbmdpbmVlcmluZ30KCmJpa2VfY2Vuc3VzIDwtIGJpa2VfY2Vuc3VzICU+JQogIG11dGF0ZShpbnRlcnZhbDYwID0gZmxvb3JfZGF0ZSh5bWRfaG1zKHN0YXJ0ZWRfYXQpLCB1bml0ID0gImhvdXIiKSwKICAgICAgICAgaW50ZXJ2YWwxNSA9IGZsb29yX2RhdGUoeW1kX2htcyhzdGFydGVkX2F0KSwgdW5pdCA9ICIxNSBtaW5zIiksCiAgICAgICAgIHdlZWsgPSB3ZWVrKGludGVydmFsNjApLAogICAgICAgICBkb3R3ID0gd2RheShpbnRlcnZhbDYwLCBsYWJlbD1UUlVFKSkgJT4lIAogIG11dGF0ZSh0aW1lX29mX2RheSA9IGNhc2Vfd2hlbihob3VyKGludGVydmFsNjApIDwgNyB8IGhvdXIoaW50ZXJ2YWw2MCkgPiAxOCB+ICJPdmVybmlnaHQiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBob3VyKGludGVydmFsNjApID49IDcgJiBob3VyKGludGVydmFsNjApIDwgMTAgfiAiQU0gUnVzaCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhvdXIoaW50ZXJ2YWw2MCkgPj0gMTAgJiBob3VyKGludGVydmFsNjApIDwgMTUgfiAiTWlkLURheSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhvdXIoaW50ZXJ2YWw2MCkgPj0gMTUgJiBob3VyKGludGVydmFsNjApIDw9IDE4IH4gIlBNIFJ1c2giKSkgJT4lIAogIG11dGF0ZShob3VyID0gaG91cihzdGFydGVkX2F0KSwKICAgICAgICAgICAgICAgIHdlZWtlbmQgPSBpZmVsc2UoZG90dyAlaW4lIGMoIlN1biIsICJTYXQiKSwgIldlZWtlbmQiLCAiV2Vla2RheSIpKQpgYGAKCkludGVyZXN0ZWQgaW4gc2VlaW5nIHRoZSBjb21tdXRpbmcgcGF0dGVybnMgdG8gZ2V0IGEgc2Vuc2Ugb2YgcmlkZXIncyBkYWlseSBiZWhhdmlvciwgd2UgY3JlYXRlZCBsaW5lIHN0cmluZ3MgdGhhdCBjb25uZWN0IGJldHdlZW4gdGhlIHN0YXJ0IGFuZCBlbmQgbG9jYXRpb25zIG9mIGVhY2ggcmlkZS4gRm9yIHRoZSBzaW1wbGljaXR5IG9mIHZpc3VhbGl6YXRpb24sIHdlIG9ubHkgdmlzdWFsaXplIDIwMCByaWRlcy4gV2UgbWF5IHNlZSB0aGF0IG1vc3Qgb2YgdGhlIHJpZGVzIG9jY3VyIHdpdGhpbiBKZXJzZXkgQ2l0eSwgYnV0IHRoZXJlIGFyZSBhbHNvIHBlb3BsZSB3aG8gY29tbXV0ZSB1c2luZyBDaXRpIEJpa2UgZnJvbSBKZXJzZXkgQ2l0eSB0byBNYW5oYXR0YW4uICAKCmBgYHtyIG9yaWdpbi1kZXNpbnRhdGlvbiBtYXRyaXgsIHdhcm5pbmc9RkFMU0V9CgpsaW5lcyA8LSBzdF9zZmMoCiAgbGFwcGx5KDE6bnJvdyhiaWtlX2NlbnN1cyksIGZ1bmN0aW9uKGkpIHsKICAgIHN0X2xpbmVzdHJpbmcobWF0cml4KGMoYmlrZV9jZW5zdXNbaSwgInN0YXJ0X2xuZyJdLCBiaWtlX2NlbnN1c1tpLCAic3RhcnRfbGF0Il0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBiaWtlX2NlbnN1c1tpLCAiZW5kX2xuZyJdLCBiaWtlX2NlbnN1c1tpLCAiZW5kX2xhdCJdKSwgbmNvbCA9IDIsIGJ5cm93ID0gVFJVRSkpCiAgfSkpCgpsaW5lc19zZiA8LSBzdF9zZihnZW9tZXRyeSA9IGxpbmVzKQpsaW5lc19zZiA8LSBzdF9zZXRfY3JzKGxpbmVzX3NmLCA0MzI2KQoKUm91dGUgPC0gbGluZXNfc2YgJT4lIGhlYWQoMjAwKQptYXB2aWV3KFJvdXRlLCB6Y29sID0gTlVMTCwgY29sb3IgPSAiIzZFMEUwQSIsIGxpbmV3aWR0aCA9IDAuMDAxLCBhbHBoYSA9IDAuMykgIyBvbmx5IHNob3cgMjAwCmBgYAoKCiMgRXhwbG9yYXRvcnkgQW5hbHlzaXMKCkluIHRoaXMgc2VjdGlvbiwgd2UgZGVsdmUgaW50byBzb21lIGZ1cnRoZXIgZXhwbG9yYXRvcnkgYW5hbHlzaXMgb2Ygb3VyIGRhdGEgc2V0LiAKCiMjIFRlbXBvcmFsIEFuYWx5c2lzCgpTdGFydGluZyB3aXRoIHRlbXBvcmFsIGFuYWx5c2lzLCB3ZSBmaXJzdCB2aXN1YWxpemVkIHRoZSBiaWtlc2hhcmUgdHJpcHMgcGVyIGhvdXIgaW4gSmVyc2V5IENpdHkgaW4gQXVndXN0IDIwMjMuIEl0IGlzIGNsZWFyIHRoYXQgdGhlcmUncyBzb21lIGZvcm0gb2YgY2lyY3VsYXJpdHkgaW4gdGhhdCB0aGVyZSBhcmUgYm90aCBwZWFrIGhvdXJzIGFuZCBub24tcGVhayBob3VycyBkdXJpbmcgYSBzaW5nbGUgZGF5LiBBbHNvLCB3ZSBtYXkgc2VlIHRoYXQgZml2ZSBjb25zZWN1dGl2ZSBoaWdoZXIgcGVha3MgYXJlIHVzdWFsbHkgZm9sbG93ZWQgYnkgdHdvIGxlc3MgaGlnaGVyIHBlYWtzLiBUaGVzZSBjb3JyZXNwb25kIHRvIGhpZ2ggYmlrZXNoYXJlIG5lZWQgZHVyaW5nIHJ1c2ggaG91cnMgYW5kIGxvdyBiaWtlc2hhcmUgbmVlZHMgb3Zlcm5pZ2h0LCBpbiBlYXJseSBtb3JuaW5ncywgYW5kIG92ZXIgdGhlIHdlZWtlbmQKCmBgYHtyIGJpa2VzaGFyZSB2aXoxfQpnZ3Bsb3QoYmlrZV9jZW5zdXMgJT4lCiAgICAgICAgIGdyb3VwX2J5KGludGVydmFsNjApICU+JQogICAgICAgICB0YWxseSgpKSsKICBnZW9tX2xpbmUoYWVzKHggPSBpbnRlcnZhbDYwLCB5ID0gbiksIGNvbG9yPSAiIzZFMEUwQSIpKwogIGxhYnModGl0bGU9IkJpa2UgU2hhcmUgVHJpcHMgUGVyIEhvdXIgaW4gSmVyc2V5IENpdHksIEF1ZyAyMDIzIiwKICAgICAgIHg9IkRhdGUiLCAKICAgICAgIHk9Ik51bWJlciBvZiB0cmlwcyIpICsgCnRoZW1lKHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDksZmFjZSA9ICJpdGFsaWMiKSwKICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgZmFjZSA9ICJib2xkIiksIAogICAgICAgIGF4aXMudGV4dC54PWVsZW1lbnRfdGV4dChzaXplPTgpLAogICAgICAgIGF4aXMudGV4dC55PWVsZW1lbnRfdGV4dChzaXplPTgpLCAKICAgICAgICBheGlzLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTEwKSwgCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X3JlY3QoY29sb3VyID0gImdyZXkiLCBmaWxsPU5BLCBsaW5ld2lkdGggPTAuOCkpCiAgCmBgYAoKVGhlbiwgd2UgdmlzdWFsaXplZCB0aGUgZGlzdHJpYnV0aW9uIG9mIHRyaXBzIGJ5IHN0YXRpb25zIGF0IGVhY2ggaG91ciBpbiBBdWd1c3QuIFdlIG1heSBzZWUgdGhhdCB0aGUgZGlzdHJpYnV0aW9uIG9mIHJpZGVzIGlzIGhpZ2hseSByaWdodCBza2V3ZWQsIHdpdGggbW9zdCBzdGF0aW9ucyBoYXZpbmcgemVybyBvciBhIGZldyBudW1iZXIgb2YgdHJpcHMgZWFjaCBob3VyIGFuZCBhIGNvdXBsZSBvZiBzdGF0aW9ucyBoYXZpbmcgbXVjaCBtb3JlIHJpZGVzLCB1cCB0byA0MCBvciBldmVuIDUwIHJpZGVzIGF0IGEgcGFydGljdWxhciBob3VyIG9uIGEgcGFydGljdWxhciBkYXkgaW4gQXVndXN0IDIwMjMuIAoKYGBge3IgYmlrZXNoYXJlIHZpejMsIHdhcm5pbmc9RkFMU0V9CgpnZ3Bsb3QoYmlrZV9jZW5zdXMgJT4lCiAgICAgICAgIGdyb3VwX2J5KGludGVydmFsNjAsIHN0YXJ0X3N0YXRpb25fbmFtZSkgJT4lCiAgICAgICAgIHRhbGx5KCkpKwogIGdlb21faGlzdG9ncmFtKGFlcyhuKSwgYmlud2lkdGggPSA1LCBmaWxsPSIjNkUwRTBBIikrCiAgbGFicyh0aXRsZT0iQmlrZSBTaGFyZSBUcmlwcyBQZXIgSG91ciBieSBTdGF0aW9uIGluIEplcnNleSBDaXR5LCBBdWcgMjAyMyIsCiAgICAgICB4PSJUcmlwIENvdW50cyIsIAogICAgICAgeT0iTnVtYmVyIG9mIFN0YXRpb25zIikgKyAKICAgIHRoZW1lKHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDksZmFjZSA9ICJpdGFsaWMiKSwKICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgZmFjZSA9ICJib2xkIiksIAogICAgICAgIGF4aXMudGV4dC54PWVsZW1lbnRfdGV4dChzaXplPTgpLAogICAgICAgIGF4aXMudGV4dC55PWVsZW1lbnRfdGV4dChzaXplPTgpLCAKICAgICAgICBheGlzLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTEwKSwgCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X3JlY3QoY29sb3VyID0gImdyZXkiLCBmaWxsPU5BLCBsaW5ld2lkdGggPTAuOCkpCgpgYGAKCldlIGNvbnRpbnVlIHRvIHZpc3VhbGl6ZSB0aGUgZGlzdHJpYnV0aW9uIG9mIG1lYW4gbnVtYmVyIG9mIHRyaXBzIGF0IGVhY2ggc3RhdGlvbiBkdXJpbmcgZGlmZmVyZW50IHRpbWVzIG9mIHRoZSBkYXkuIFdlIHJlY29nbml6ZSB0aGF0IGR1cmluZyBQTSBydXNoLCB0aGUgYXZlcmFnZSBudW1iZXIgb2YgdHJpcHMgaGFzIGJlZW4gdGhlIGhpZ2hlc3QgdGhyb3VnaG91dCwgd2l0aCBhIGNvdXBsZSBvZiBzdGF0aW9ucycgYXZlcmFnZSB0cmlwIGV4Y2VlZCAxMCwgaW1wbHlpbmcgYSByZWxhdGl2ZWx5IGhpZ2ggZGVtYW5kIGZvciBhIHNpbmdsZSBzdGF0aW9uLiBUaGlzIGlzIGZvbGxvd2VkIGJ5IHJpZGUgZGVtYW5kIGluIG1pZCBkYXksIHdoZW4gc29tZSBzdGF0aW9ucyBjYW4gaGF2ZSBtb3JlIHRoYW4gNSByaWRlcy4gSXQgaXMgYWxzbyBzdXJwcmlzaW5nIHRvIG5vdGljZSB0aGF0IHRoZSBkZW1hbmQgaXMgcHJldHR5IGhpZ2ggb3Zlcm5pZ2h0LCB3aXRoIGEgZmV3IHN0YXRpb25zIGhhdmluZyBtb3JlIHRoYW4gNSByaWRlcy4gCgpgYGB7ciBiaWtlc2hhcmUgdml6Miwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0KCmJpa2VfY2Vuc3VzICU+JQogICAgICAgIGdyb3VwX2J5KGludGVydmFsNjAsIHN0YXJ0X3N0YXRpb25fbmFtZSwgdGltZV9vZl9kYXkpICU+JQogICAgICAgICB0YWxseSgpJT4lCiAgZ3JvdXBfYnkoc3RhcnRfc3RhdGlvbl9uYW1lLCB0aW1lX29mX2RheSklPiUKICBzdW1tYXJpemUobWVhbl90cmlwcyA9IG1lYW4obikpJT4lCiAgZ2dwbG90KCkrCiAgZ2VvbV9oaXN0b2dyYW0oYWVzKG1lYW5fdHJpcHMsIGZpbGw9dGltZV9vZl9kYXkpLCBiaW53aWR0aCA9IDEpKwogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IHBhbGV0dGU0LCBuYW1lPSJUaW1lIG9mIERheSIpICsgCiAgbGFicyh0aXRsZT0iTWVhbiBOdW1iZXIgb2YgSG91cmx5IFRyaXBzIFBlciBTdGF0aW9uIiwKICAgICAgIHg9Ik51bWJlciBvZiB0cmlwcyIsIAogICAgICAgeT0iRnJlcXVlbmN5IikrCiAgZmFjZXRfd3JhcCh+dGltZV9vZl9kYXkpICsKdGhlbWUocGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gOSxmYWNlID0gIml0YWxpYyIpLAogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSwgCiAgICAgICAgYXhpcy50ZXh0Lng9ZWxlbWVudF90ZXh0KHNpemU9OCksCiAgICAgICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF90ZXh0KHNpemU9OCksIAogICAgICAgIGF4aXMudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9MTApLCAKICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfcmVjdChjb2xvdXIgPSAiZ3JleSIsIGZpbGw9TkEsIHNpemU9MC44KSkKCmBgYAoKQWZ0ZXIgdGhhdCwgd2UgdmlzdWFsaXplZCB0aGUgdG90YWwgbnVtYmVyIG9mIHRyaXBzIGluIEF1Z3VzdCBzZXBhcmF0ZWQgYnkgZGF5IG9mIHRoZSB3ZWVrLiBUaGUgdHdvIG1haW4gcGF0dGVybnMgaGVyZSBhcmUgdGhhdCBmaXJzdGx5LCB0cmlwIG51bWJlcnMgdmFyeSBieSB0aW1lIG9mIHRoZSBkYXkuIExvd2VzdCB0cmlwIGNvdW50cyBvY2N1ciBpbiBlYXJseSBtb3JuaW5nIHdoaWxlIGhpZ2hlc3QgdHJpcCBjb3VudCBvY2N1ciBpbiBsYXRlIGFmdGVybm9vbi4gU2Vjb25kbHksIFR1ZXNkYXksIFdlZG5lc2RheSwgYW5kIFRodXJzZGF5IHRlbmQgdG8gb2JzZXJ2ZSBoaWdoZXIgbnVtYmVyIG9mIHRyaXBzLiAKCmBgYHtyIGJpa2VzaGFyZSB2aXo0fQoKZ2dwbG90KGJpa2VfY2Vuc3VzICU+JSBtdXRhdGUoaG91ciA9IGhvdXIoc3RhcnRlZF9hdCkpKSsKICAgICBnZW9tX2ZyZXFwb2x5KGFlcyhob3VyLCBjb2xvciA9IGRvdHcpLCBiaW53aWR0aCA9IDMsIGx3ZCA9IDAuOCkrCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGMoIiMxMjRFNzgiLCAiIzgxOUZBMSIsICIjRjBGMEM5IiwgIiNGMkJCMDUiLCAiI0U1ODUwNyIsICIjRDc0RTA5IiwgIiM2RTBFMEEiKSwgbmFtZSA9ICJEYXkiKSArIAogIGxhYnModGl0bGU9IkJpa2UgU2hhcmUgVHJpcHMgaW4gSmVyc2V5IENpdHkgYnkgRGF5IG9mIHRoZSBXZWVrLCBBdWcgMjAyMyIsCiAgICAgICB4PSJIb3VyIiwgCiAgICAgICB5PSJUcmlwIENvdW50cyIpICsgCiAgdGhlbWUocGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gOSxmYWNlID0gIml0YWxpYyIpLAogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSwgCiAgICAgICAgYXhpcy50ZXh0Lng9ZWxlbWVudF90ZXh0KHNpemU9OCksCiAgICAgICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF90ZXh0KHNpemU9OCksIAogICAgICAgIGF4aXMudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9MTApLCAKICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfcmVjdChjb2xvdXIgPSAiZ3JleSIsIGZpbGw9TkEsIHNpemU9MC44KSkKCmBgYAoKU2FtZSB0eXBlIG9mIHZpc3VhbGl6ZSBidXQgdGhpcyB0aW1lIHNlcGFyYXRlZCBiZXR3ZWVuIHdlZWtlbmQgYW5kIHdlZWtkYXlzLiBJdCBpcyB2ZXJ5IG9idmlvdXMgdGhhdCB3ZSBzZWUgbW9yZSB0cmlwcyBkdXJpbmcgd2Vla2RheXMgYW5kIGxlc3MgdHJpcHMgZHVyaW5nIHdlZWtlbmRzLiBBbHNvLCBkdXJpbmcgd2Vla2VuZHMsIG51bWJlciBvZiB0cmlwcyBzdGFydCB0byBpbmNyZWFzZSBtdWNoIGxhdGVyIHRoYW4gZHVyaW5nIHdlZWtkYXlzLCBzdWdnZXN0aW5nIHRoZSBtaXNzaW5nIG9mIG1vcm5pbmcgcnVzaCBob3VyIHdpbGwgc2lnbmlmaWNhbnRseSBpbXBhY3QgcGVvcGxlJ3MgdXNlIG9mIGJpa2VzaGFyZSBzZXJ2aWNlcy4gCgpgYGB7ciBiaWtlc2hhcmUgdml6IDV9CgpnZ3Bsb3QoYmlrZV9jZW5zdXMpKwogICAgIGdlb21fZnJlcXBvbHkoYWVzKGhvdXIsIGNvbG9yID0gd2Vla2VuZCksIGJpbndpZHRoID0gMywgbHdkID0gMS4yKSsKICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzPXBhbGV0dGUyKSArIAogIGxhYnMoY29sb3IgPSAiVGltZSBvZiBXZWVrIikgKwogIGxhYnModGl0bGU9IkJpa2UgU2hhcmUgVHJpcHMgaW4gSmVyc2V5IENpdHkgLSBXZWVrZW5kIHZzIFdlZWtkYXksIEF1ZyAyMDIzIiwKICAgICAgIHg9IkhvdXIiLCAKICAgICAgIHk9IlRyaXAgQ291bnRzIikgKyAKICB0aGVtZShwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSA5LGZhY2UgPSAiaXRhbGljIiksCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIsIGZhY2UgPSAiYm9sZCIpLCAKICAgICAgICBheGlzLnRleHQueD1lbGVtZW50X3RleHQoc2l6ZT04KSwKICAgICAgICBheGlzLnRleHQueT1lbGVtZW50X3RleHQoc2l6ZT04KSwgCiAgICAgICAgYXhpcy50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT0xMCksIAogICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9yZWN0KGNvbG91ciA9ICJncmV5IiwgZmlsbD1OQSwgc2l6ZT0wLjgpKQoKYGBgCgpCdWlsZGluZyBvbnRvIHRoYXQsIHdlIGdyb3VwIHRoaXMgZGF0YXNldCBieSBzdGF0aW9uIGFuZCB0aW1lIG9mIGRheSB0byB2aXN1YWxpemUgdGhlIHRvdGFsIG51bWJlciBvZiB0cmlwcyBhdCBlYWNoIHN0YXRpb24gZHVyaW5nIGEgc3BlY2lmaWMgcGVyaW9kIG9mIHRpbWUgb24gd2Vla2RheSBvciB3ZWVrZW5kLiBUbyBzaW1wbGlmeSBvdXIgdmlzdWFsaXphdGlvbiwgd2Ugc2VsZWN0ZWQgdGhlIHRvcCA1MCBvY2N1cnJlbmNlcyBpbiBvdXIgZGF0YXNldC4gTm90ZSB0aGF0IGhlcmUsIGVhY2ggYmFyIHJlcHJlc2VudCBhbiBpbnN0YW5jZSBvZiBzdGF0aW9uLXRpbWUgb2YgZGF5LXdlZWtkYXkvd2Vla2VuZCBjb21iaW5hdGlvbi4gV2UgYXJlIGFibGUgdG8gc2VlIHRoYXQgUE0gcnVzaCBob3VyIGluZGVlZCBzZWVzIHRoYXQgZ3JlYXRlc3QgYmlrZXNoYXJlIGRlbWFuZCBmb3IgYSBjb3VwbGUgb2Ygc3RhdGlvbnMgaW4gcGFydGljdWxhci4gV2Vla2VuZCByaWRlcyBvbmx5IGFwcGVhciB0aHJlZSB0aW1lcyBpbiB0aGUgdG9wIDUwIGxpc3QuIFpvb21pbmcgaW50byB0aGUgZGV0YWlsIHJldmVhbHMgdG8gdXMgdGhhdCAqKkdyb3ZlIFN0IFBBVEgqKiBzdGF0aW9uIG9ic2VydmVzIGEgdG90YWwgb2YgMTExNyByaWRlcyBkdXJpbmcgUE0gcnVzaCBvbiBhIHdlZWtkYXksIDgwMSByaWRlcyBvdmVybmlnaHQgb24gYSB3ZWVrZGF5IGluIEF1Z3VzdCAyMDIzLiAqKkhvYm9rZW4gVGVybWluYWwgLSBSaXZlciBTdCAmIEh1ZHNvbiBQbCoqIHN0YXRpb24gb2JzZXJ2ZXMgYSB0b3RhbCBvZiA4NjQgcmlkZXMgZHVyaW5nIFBNIHJ1c2ggb24gd2Vla2RheS4gKipTb3V0aCBXYXRlcmZyb250IFdhbGt3YXkgLSBTaW5hdHJhIERyICYgMSBTdCoqIHN0YXRpb24gb2JzZXJ2ZXMgMzE5IHJpZGVzIG92ZXJuaWdodCBvbiBhIHdlZWtlbmQgYW5kIDczMSByaWRlcyBkdXJpbmcgUE0gcnVzaCBvbiBhIHdlZWtkYXkgaW4gQXVndXN0IDIwMjMuIAoKYGBge3IgYmlrZXNoYXJlIHZpeiA3LCBmaWcud2lkdGg9MTB9Cgp0b19wbG90IDwtIGJpa2VfY2Vuc3VzJT4lICMgb25seSBzaG93IDEwMCBzdGF0aW9ucwogIGdyb3VwX2J5KHN0YXJ0X3N0YXRpb25faWQsIHN0YXJ0X2xhdCwgc3RhcnRfbG5nLCB3ZWVrZW5kLCB0aW1lX29mX2RheSwgc3RhcnRfc3RhdGlvbl9uYW1lKSAlPiUKICB0YWxseSgpICU+JSAKICBhcnJhbmdlKC1uKSAlPiUgCiAgaGVhZCg1MCkgCnRvX3Bsb3QkSUQgPC0gc2VxX2Fsb25nKHRvX3Bsb3Qkc3RhcnRfc3RhdGlvbl9pZCkKCnRvX3Bsb3QgJT4lIAogIGFycmFuZ2UoLW4pICU+JQogIGhlYWQoNTApICU+JSAKICBnZ3Bsb3QoYWVzKHggPSByZW9yZGVyKElELCAtbiksIG4sIGZpbGwgPSB0aW1lX29mX2RheSwgY29sb3IgPSB3ZWVrZW5kKSkgKwogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IHBhbGV0dGU0LCBuYW1lPSJUaW1lIG9mIERheSIpICsgCiAgZ3VpZGVzKGNvbG9yPSJub25lIikgKyAKICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IiwgcG9zaXRpb249InN0YWNrIikgKwogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBjKCJ0cmFuc3BhcmVudCIsICJibGFjayIpKSArCiAgbGFicyh0aXRsZT0iVG9wIDUwIE9jY3VyZW5jZXMgb2YgUmlkZXMgQnkgVGltZSIpKwogIHlsYWIoIk51bWJlciBvZiBSaWRlcyIpICsKICB4bGFiKCIiKSArIAogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkpICsgCiAgdGhlbWUocGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gOSxmYWNlID0gIml0YWxpYyIpLAogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSwgCiAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCksCiAgICBheGlzLnRpY2tzLnggPSBlbGVtZW50X2JsYW5rKCksIAogICAgICAgIGF4aXMudGV4dC55PWVsZW1lbnRfdGV4dChzaXplPTgpLCAKICAgICAgICBheGlzLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTEwKSwgCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X3JlY3QoY29sb3VyID0gImdyZXkiLCBmaWxsPU5BLCBzaXplPTAuOCkpCmBgYAoKCiMjIFNwYXRpYWwgQW5hbHlzaXMKCkluIGFkZGl0aW9uIHRvIGV4YW1pbmluZyBvdXIgZGF0YSB0aHJvdWdoIHRlbXBvcmFsIGxlbnMsIHdlIG1hZGUgc29tZSBtYXBzIHRvIHNob3cgdGhlIGFjdHVhbCBsb2NhdGlvbiBvZiBzdGF0aW9ucyB3aXRoIHRoZSBncmVhdGVzdCBiaWtlc2hhcmUgZGVtYW5kIGJ5IHRpbWUgb2YgZGF5IGFuZCBkYXkgb2YgdGhlIHdlZWsuIFRoaXMgcHJvcG9ydGlvbmFsIG1hcCBpbGx1c3RyYXRlcyB0aGF0IHN0YXRpb25zIHJlY2VpdmUgdGhlIGhpZ2hlc3QgcmlkZSBkZW1hbmQgZHVyaW5nIFBNIHJ1c2ggYW5kIG92ZXJuaWdodCBpbiB3ZWVrZGF5cyBhbmQgdGhlIGxvd2VzdCBkZW1hbmQgZHVyaW5nIEFNIHJ1c2guIFN0YXRpb25zIHdpdGggdGhlIGhpZ2hlc3QgYmlrZXNoYXJlIGRlbWFuZCBhcmUgZ2VuZXJhbGx5IGxvY2F0ZWQgYWxvbmcgdGhlIEh1ZHNvbiBSaXZlci4gVGhpcyBjb3VsZCBiZSBhdHRyaWJ1dGVkIHRvIHR3byByZWFzb25zOiB0aGVyZSBhcmUgc2ltcGx5IG1vcmUgc3RhdGlvbnMgaW4gdGhpcyBwYXJ0IG9mIEplcnNleSBDaXR5IG9yIENpdGkgQmlrZSBoYXMgc3RyYXRlZ2ljYWxseSBidWlsdCBiaWtlIHN0YXRpb25zIGFsb25nIHRoZSBIdWRzb24gUml2ZXIgdG8gbWFrZSBkYWlseSBjb21tdXRlIGVhc2llciBmb3IgdGhvc2Ugd2hvIGxpdmUgaW4gSmVyc2V5IENpdHkgYnV0IG5lZWQgdG8gd29yay9zdHVkeSBpbiBNYW5oYXR0YW4uIAoKYGBge3IgYmlrZXNoYXJlIHZpeiA2fQoKZ2dwbG90KCkrCiAgZ2VvbV9zZihkYXRhID0gamNUcmFjdHMgJT4lCiAgICAgICAgICBzdF90cmFuc2Zvcm0oY3JzPTQzMjYpLCBmaWxsID0gIndoaXRlIikrCiAgZ2VvbV9wb2ludChkYXRhID0gdG9fcGxvdCwKICAgICAgICAgICAgIGFlcyh4PXN0YXJ0X2xuZywgeSA9IHN0YXJ0X2xhdCwgc2l6ZT1uKSwgY29sb3IgPSBhbHBoYSgiIzZFMEUwQSIsIDAuNykpICsgCiAgeWxpbShtaW4oYmlrZV9jZW5zdXMkc3RhcnRfbGF0KSwgbWF4KGJpa2VfY2Vuc3VzJHN0YXJ0X2xhdCkpKwogIHhsaW0obWluKGJpa2VfY2Vuc3VzJHN0YXJ0X2xuZyksIG1heChiaWtlX2NlbnN1cyRzdGFydF9sbmcpKSArCiAgc2NhbGVfc2l6ZShyYW5nZSA9IGMoMC41LCA4KSkgKyAKICBsYWJzKHNpemUgPSAiTnVtYmVyIG9mIFJpZGVzIikgKwogIGZhY2V0X2dyaWQod2Vla2VuZCB+IHRpbWVfb2ZfZGF5KSArIAogICAgbGFicyh0aXRsZT0iTG9jYXRpb25zIG9mIFN0YXRpb25zIHdpdGggR3JlYXRlc3QgQmlrZXNoYXJlIERlbWFuZCBieSBUaW1lIikrIAogIHRoZW1lKGF4aXMudGV4dC54PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRleHQueT1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aWNrcyA9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpdGxlLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gOSxmYWNlID0gIml0YWxpYyIpLAogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSwKICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfcmVjdChjb2xvdXIgPSAiZ3JleSIsIGZpbGw9TkEsIGxpbmV3aWR0aD0wLjgpCiAgICAgICAgKQpgYGAKCldlIGdyb3VwZWQgYWxsIHRoZSBzdGFydGluZyBzdGF0aW9ucyB0b2dldGhlciB0byBjaGVjayBmb3Igc3BhdGlhbCBhdXRvY29ycmVsYXRpb24gYmV0d2VlbiBzdGF0aW9ucy4gV2UgYmVsaWV2ZSB0aGF0IHN0YXRpb25zIHdpdGggZ3JlYXRlciBkZW1hbmQgYXJlIG1vcmUgbGlrZWx5IHRvIGJlIGxvY2F0ZWQgdG8gb3RoZXIgc3RhdGlvbnMgd2l0aCBncmVhdGVyIGRlbWFuZC4gSW4gdGhpcyBjYXNlLCB3ZSBjYW4gdXNlIHRoZSBkZW1hbmQgb2YgbmVhcmJ5IHN0YXRpb25zIGF0IGEgcGFydGljdWxhciB0aW1lIHRvIGVzdGltYXRlIHRoZSBkZW1hbmQgb2Ygb3VyIHRhcmdldCBzdGF0aW9uLiBXZSBzZWxlY3RlZCA4MCBzdGF0aW9ucyB3aXRoIHRoZSBoaWdoZXN0IG51bWJlciBvZiByaWRlcyBpbiBBdWd1c3QgMjAyMyBhbmQgY29tcHV0ZWQgdGhlIE1vcmFuJ3MgSSB3aXRoIDk5OSByYW5kb20gTW9udGUgQ2FybG8gc2ltdWxhdGlvbi4gVGhlIHJlc3VsdHMgc2hvdyB0aGF0IG91ciBvYnNlcnZlZCBNb3JhbidzIEkgaXMgaGlnaGx5IHNpZ25pZmljYW50LCBpbXBseWluZyB0aGF0IHRoZXJlJ3Mgc3BhdGlhbCBhdXRvY29ycmVsYXRpb24gYmV0d2VlbiBiaWtlc2hhcmVzIGF0IG5lYXJieSBzdGF0aW9ucy4gIAoKYGBge3Igc3BhdGlhbCBhdXRvIGNvciwgd2FybmluZz1GQUxTRX0KCm1vcmFuX2RhdGEgPC0gYmlrZV9jZW5zdXMgJT4lCiAgZ3JvdXBfYnkoc3RhcnRfc3RhdGlvbl9pZCwgc3RhcnRfbGF0LCBzdGFydF9sbmcsIHN0YXJ0X3N0YXRpb25fbmFtZSkgJT4lCiAgdGFsbHkoKSAlPiUgCiAgYXJyYW5nZSgtbikgJT4lIAogIGhlYWQoODApICU+JSAKICBzdF9hc19zZiguLCBjb29yZHMgPSBjKCJzdGFydF9sbmciLCAic3RhcnRfbGF0IiksIGNycyA9IDQzMjYpCgoKY29vcmRzIDwtICBzdF9jb29yZGluYXRlcyhtb3Jhbl9kYXRhKSAKbmVpZ2hib3JMaXN0IDwtIGtubjJuYihrbmVhcm5laWdoKGNvb3JkcywgNSkpCnNwYXRpYWxXZWlnaHRzIDwtIG5iMmxpc3R3KG5laWdoYm9yTGlzdCwgc3R5bGU9IlciKQoKbW9yYW5UZXN0IDwtIG1vcmFuLm1jKG1vcmFuX2RhdGEkbiwgCiAgICAgICAgICAgICAgICAgICAgICBzcGF0aWFsV2VpZ2h0cywgbnNpbSA9IDk5OSkKCmdncGxvdChhcy5kYXRhLmZyYW1lKG1vcmFuVGVzdCRyZXNbYygxOjk5OSldKSwgYWVzKG1vcmFuVGVzdCRyZXNbYygxOjk5OSldKSkgKwogIGdlb21faGlzdG9ncmFtKGJpbndpZHRoID0gMC4wMSwgZmlsbCA9ICIjMTI0RTc4IikgKwogIGdlb21fdmxpbmUoYWVzKHhpbnRlcmNlcHQgPSBtb3JhblRlc3Qkc3RhdGlzdGljKSwgY29sb3VyID0gIiM2RTBFMEEiICxsd2Q9MSkgKwogIHNjYWxlX3hfY29udGludW91cyhsaW1pdHMgPSBjKC0wLjUsIDAuNSkpICsKICBsYWJzKHRpdGxlID0gIk9ic2VydmVkIGFuZCBQZXJtdXRlZCBNb3JhbidzIEkgZm9yIFN0YXRpb25zIHdpdGggTW9zdCBSaWRlcyIsCiAgICAgICBzdWJ0aXRsZSA9ICJPYnNlcnZlZCBNb3JhbidzIEkgaW4gUmVkIiwKICAgICAgIHggPSAiTW9yYW4ncyBJIiwKICAgICAgIHkgPSAiQ291bnQiKSArCiAgdGhlbWVfbGlnaHQoKSArICAgCnRoZW1lKHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDksZmFjZSA9ICJpdGFsaWMiKSwKICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgZmFjZSA9ICJib2xkIiksIAogICAgICAgIGF4aXMudGV4dC54PWVsZW1lbnRfdGV4dChzaXplPTgpLAogICAgICAgIGF4aXMudGV4dC55PWVsZW1lbnRfdGV4dChzaXplPTgpLCAKICAgICAgICBheGlzLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTEwKSkKYGBgCgpUaGlzIG1hcCBiZWxvdyBzaG93cyBob3cgc3RhdGlvbnMgd2l0aCBoaWdoZXIgYmlrZXNoYXJlIGRlbWFuZCBhcmUgY2x1c3RlcmVkLiBJbiBwYXJ0aWN1bGFyLCB0aGVyZSBhcmUgdHdvIGNsdXN0ZXJzIG9mIHN0YXRpb25zLCBhbmQgd2l0aGluIGVhY2ggY2x1c3RlciwgdGhlcmUgYXJlIGEgZmV3IHN0YXRpb25zIHdpdGggcGFydGljdWxhcmx5IGxhcmdlIG51bWJlciBvZiByaWRlcy4gVGhlc2Ugc3RhdGlvbnMgYXJlIGdlbmVyYWxseSBzdXJyb3VuZGVkIGJ5IHN0YXRpb25zIHdpdGggc2xpZ2h0bHkgbGVzcyByaWRlcy4gCgpgYGB7ciBzcGF0aWFsIGNvciB2aXosIGZpZy5oZWlnaHQ9OCwgZmlnLndpZHRoPTExfQoKICBnZ3Bsb3QoKSArCiAgZ2VvbV9zZihkYXRhID0gamNUcmFjdHMsIGZpbGwgPSAid2hpdGUiKSArCiAgZ2VvbV9zZihkYXRhID0gbW9yYW5fZGF0YSwKICAgICAgICAgIGFlcyhzaXplID0gbiwgY29sb3IgPSBuKSkgKyAKICB5bGltKG1pbihiaWtlX2NlbnN1cyRzdGFydF9sYXQpLCBtYXgoYmlrZV9jZW5zdXMkc3RhcnRfbGF0KSkrCiAgeGxpbShtaW4oYmlrZV9jZW5zdXMkc3RhcnRfbG5nKSwgbWF4KGJpa2VfY2Vuc3VzJHN0YXJ0X2xuZykpICsgCiAgc2NhbGVfc2l6ZSgKICAgIHJhbmdlID0gYygxLCA2KSwKICAgIGd1aWRlID0gZ3VpZGVfbGVnZW5kKAogICAgICBkaXJlY3Rpb24gPSAiaG9yaXpvbnRhbCIsCiAgICAgIG5yb3cgPSAxLAogICAgICBsYWJlbC5wb3NpdGlvbiA9ICJib3R0b20iKSkgKwogIHNjYWxlX2NvbG9yX2dyYWRpZW50bihjb2xvdXJzID0gaGNsLmNvbG9ycyg1LCAiUmRCdSIscmV2ID0gVFJVRSwgYWxwaGEgPSAwLjkpLCAKICAgICAgICAgICAgICAgICAgICAgICAgZ3VpZGUgPSBndWlkZV9sZWdlbmQoZGlyZWN0aW9uID0gImhvcml6b250YWwiLCBucm93ID0gMSkpICsKICBsYWJzKHRpdGxlID0gIlN0YXRpb25zIHdpdGggTW9zdCBSaWRlcyIsCiAgICAgICBzdWJ0aXRsZSA9ICAiRG90IHNpemUgcHJvcG9ydGlvbmFsIHRvIHJpZGVzIikgKyAKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIikgKyAKICB0aGVtZShheGlzLnRleHQueD1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGlja3MgPWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aXRsZS55ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDksZmFjZSA9ICJpdGFsaWMiKSwKICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgZmFjZSA9ICJib2xkIiksCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X3JlY3QoY29sb3VyID0gImdyZXkiLCBmaWxsPU5BLCBsaW5ld2lkdGg9MC44KQogICAgICAgICkKCmBgYAoKVGhpcyBhbmltYXRlZCBtYXAgYmVsb3cgY29tYmluZXMgdGhlIHNwYXRpYWwgYW5kIHRlbXBvcmFsIGNoYXJhY3RlcmlzdGljcyBvZiBiaWtlIHJpZGVzIGF0IDE1bWluIGludGVydmFsIGludG8gYSBjaG9yb3BsZXRoIG1hcC4gCgpgYGB7ciBhbmltYXRlIG1hcCB0ZW1wb3JhbCB2aXosIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGNhY2hlPVRSVUUsIGZpZy5zaG93PSdhbmltYXRlJ30KCndlZWszMyA8LQogIGZpbHRlcihiaWtlX2NlbnN1cyAsIHdlZWsgPT0gMzMgJiBkb3R3ID09ICJNb24iKQoKd2VlazMzLnBhbmVsIDwtCiAgZXhwYW5kLmdyaWQoCiAgICBpbnRlcnZhbDE1ID0gdW5pcXVlKHdlZWszMyRpbnRlcnZhbDE1KSwKICAgIE9yaWdpbi5UcmFjdCA9IHVuaXF1ZShiaWtlX2NlbnN1cyRPcmlnaW4uVHJhY3QpKQoKYmlrZS5hbmltYXRpb24uZGF0YSA8LQogIG11dGF0ZSh3ZWVrMzMsIFRyaXBfQ291bnRlciA9IDEpICU+JQogICAgcmlnaHRfam9pbih3ZWVrMzMucGFuZWwpICU+JSAKICAgIGdyb3VwX2J5KGludGVydmFsMTUsIE9yaWdpbi5UcmFjdCkgJT4lCiAgICBzdW1tYXJpemUoVHJpcF9Db3VudCA9IHN1bShUcmlwX0NvdW50ZXIsIG5hLnJtPVQpKSAlPiUgCiAgICB1bmdyb3VwKCkgJT4lIAogICAgbGVmdF9qb2luKGpjVHJhY3RzLCBieT1jKCJPcmlnaW4uVHJhY3QiID0gIkdFT0lEIikpICU+JQogICAgc3Rfc2YoKSAlPiUKICAgIG11dGF0ZShUcmlwcyA9IGNhc2Vfd2hlbihUcmlwX0NvdW50ID09IDAgfiAiMCB0cmlwcyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgVHJpcF9Db3VudCA+IDAgJiBUcmlwX0NvdW50IDw9IDMgfiAiMS0zIHRyaXBzIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBUcmlwX0NvdW50ID4gMyAmIFRyaXBfQ291bnQgPD0gNiB+ICI0LTYgdHJpcHMiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIFRyaXBfQ291bnQgPiA2ICYgVHJpcF9Db3VudCA8PSAxMCB+ICI3LTEwIHRyaXBzIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBUcmlwX0NvdW50ID4gMTAgfiAiMTErIHRyaXBzIikpICU+JQogICAgbXV0YXRlKFRyaXBzICA9IGZjdF9yZWxldmVsKFRyaXBzLCAiMCB0cmlwcyIsIjEtMyB0cmlwcyIsIjQtNiB0cmlwcyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICI3LTEwIHRyaXBzIiwiMTArIHRyaXBzIikpCgpiaWtlc2hhcmVfYW5pbWF0aW9uIDwtCiAgZ2dwbG90KCkgKwogICAgZ2VvbV9zZihkYXRhID0gYmlrZS5hbmltYXRpb24uZGF0YSwgYWVzKGZpbGwgPSBUcmlwcykpICsKICAgIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IHBhbGV0dGU1KSArCiAgICBsYWJzKHRpdGxlID0gIkJpa2VzaGFyZSBGb3IgT25lIERheSBpbiBBdWcgMjAyMyIsCiAgICAgICAgIHN1YnRpdGxlID0gIjE1IG1pbnV0ZSBpbnRlcnZhbHM6IHtjdXJyZW50X2ZyYW1lfSIpICsKICAgIHRyYW5zaXRpb25fbWFudWFsKGludGVydmFsMTUpICArIAogIHRoZW1lKGF4aXMudGV4dC54PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRleHQueT1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aWNrcyA9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpdGxlLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gOSxmYWNlID0gIml0YWxpYyIpLAogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSwKICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfcmVjdChjb2xvdXIgPSAiZ3JleSIsIGZpbGw9TkEsIGxpbmV3aWR0aD0wLjgpCiAgICAgICAgKQogIAoKCmFuaW1fc2F2ZSgiYW5pbWF0aW9uLmdpZiIsIGFuaW1hdGlvbiA9IGJpa2VzaGFyZV9hbmltYXRpb24sIGVuZF9wYXVzZSA9IDQsIGhlaWdodCA9IDEwMDAsIHdpZHRoID0gNzAwLCBmcHMgPSA2LCByZW5kZXJlciA9IGdpZnNraV9yZW5kZXJlcigpKQoKCgpgYGAKCiFbQW5pbWF0ZWQgTWFwIGZvciBCaWtlc2hhcmUgRGVtYW5kIGluIFdlZWsgMzNdKGFuaW1hdGlvbi5naWYpCgoKIyMgV2VhdGhlciBBbmFseXNpcwoKT24gdG9wIG9mIHNwYWNlIGFuZCB0aW1lLCB3ZWF0aGVyIGNhbiBiZSBhbm90aGVyIHNpZ25pZmljYW50IGZhY3RvciBvZiBpbiBwcmVkaWN0aW5nIGJpa2VzaGFyZSBkZW1hbmQuIEdlbmVyYWxseSwgd2UgYmVsaWV2ZSBiaWtlc2hhcmUgZGVtYW5kIHdpbGwgZGVjcmVhc2UgaWYgaXQncyBvbiBhIHJhaW55LCB3aW5keSwgb3IgYSB2ZXJ5IGhvdCBkYXkgaW4gb3VyIGNhc2UuICBXaXRoIHRoYXQgaW4gbWluZCwgd2UgZG93bmxvYWRlZCB0aGUgd2VhdGhlciBkYXRhIHVzaW5nIHRoZSBgcmllbV9tZWFzdXJlc2AgZnVuY3Rpb24gZm9yIE5ld2FyayBhaXJwb3J0IGJldHdlZW4gQXVnIDEsIDIwMjMgYW5kIFNlcHQgMSwgMjAyMy4gTm90ZSB0aGF0IHRoZSBOZXdhcmsgQWlycG9ydCBzdGF0aW9uIHN1ZmZpY2llbnRseSBwcm92aWRlcyB0ZW1wb3JhbCB3ZWF0aGVyIGZvciBhbGwgb2YgSmVyc2V5IENpdHkuIFRoZSB3ZWF0aGVyLlBhbmVsIGlzIGdlbmVyYXRlZCB0byBzdW1tYXJpemUgdGVtcGVyYXR1cmUsIHByZWNpcGl0YXRpb24sIGFuZCB3aW5kIHNwZWVkIGZvciBldmVyeSBob3VyLiAKCmBgYHtyIGRvd25sb2FkIGFuZCB3cmFuZ2xlIHdlYXRoZXIgZGF0YSwgd2FybmluZz1GQUxTRX0KCndlYXRoZXIuUGFuZWwgPC0gCiAgcmllbV9tZWFzdXJlcyhzdGF0aW9uID0gIkVXUiIsIGRhdGVfc3RhcnQgPSAiMjAyMy0wOC0wMSIsIGRhdGVfZW5kID0gIjIwMjMtMDktMDEiKSAlPiUKICBkcGx5cjo6c2VsZWN0KHZhbGlkLCB0bXBmLCBwMDFpLCBza250KSU+JQogIHJlcGxhY2UoaXMubmEoLiksIDApICU+JQogICAgbXV0YXRlKGludGVydmFsNjAgPSB5bWRfaChzdWJzdHIodmFsaWQsMSwxMykpKSAlPiUKICAgIG11dGF0ZSh3ZWVrID0gd2VlayhpbnRlcnZhbDYwKSwKICAgICAgICAgICBkb3R3ID0gd2RheShpbnRlcnZhbDYwLCBsYWJlbD1UUlVFKSkgJT4lCiAgICBncm91cF9ieShpbnRlcnZhbDYwKSAlPiUKICAgIHN1bW1hcml6ZShUZW1wZXJhdHVyZSA9IG1heCh0bXBmKSwKICAgICAgICAgICAgICBQcmVjaXBpdGF0aW9uID0gc3VtKHAwMWkpLAogICAgICAgICAgICAgIFdpbmRfU3BlZWQgPSBtYXgoc2tudCkpICU+JQogICAgbXV0YXRlKFRlbXBlcmF0dXJlID0gaWZlbHNlKFRlbXBlcmF0dXJlID09IDAsIDQyLCBUZW1wZXJhdHVyZSkpCgpgYGAKCkZvciB0aGUgdGVtcGVyYXR1cmUsIHByZWNpcGl0YXRpb24sIGFuZCB3aW5kIGRhdGEgd2UgY29sbGVjdGVkIGR1cmluZyB0aGlzIG1vbnRoLiBXZSBtYXkgc2VlIHRoYXQgdGhlcmUgYXJlIGEgY291cGxlIG9mIHJhaW55IGRheXMgaW4gQXVndXN0LiBXaW5kIHNwZWVkIGlzIHByZXR0eSBjb25zaXN0ZW50IHRocm91Z2hvdXQgd2l0aCBhIGNvdXBsZSBvZiByZWFsbHkgd2luZHkgZGF5cy4gVGVtcGVyYXR1cmUgZmx1Y3R1YXRlcyBvbiBhIGRhaWx5IGJhc2lzIHdpdGggaGlnaGVyIHRlbXBlcmF0dXJlIGR1cmluZyB0aGUgZGF5IGFuZCBjb29sZXIgdGVtcGVyYXR1cmUgYXQgbmlnaHQuIAoKYGBge3IgdmlzdWFsaXplIHdlYXRoZXIgZGF0YSwgZmlnLmhlaWdodD0xMSwgZmlnLndpZHRoPTgsIHdhcm5pbmc9RkFMU0V9CgpncmlkLmFycmFuZ2UoCiAgZ2dwbG90KHdlYXRoZXIuUGFuZWwsIGFlcyhpbnRlcnZhbDYwLFByZWNpcGl0YXRpb24pKSArIGdlb21fbGluZShjb2xvcj0iIzZFMEUwQSIpICsgCiAgICBsYWJzKHRpdGxlPSJQZXJjaXBpdGF0aW9uIiwgeD0iSG91ciIsIHk9IlBlcmVjaXBpdGF0aW9uIikgKyAKICAgIHRoZW1lKHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDksZmFjZSA9ICJpdGFsaWMiKSwKICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgZmFjZSA9ICJib2xkIiksIAogICAgICAgIGF4aXMudGV4dC54PWVsZW1lbnRfdGV4dChzaXplPTgpLAogICAgICAgIGF4aXMudGV4dC55PWVsZW1lbnRfdGV4dChzaXplPTgpLCAKICAgICAgICBheGlzLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTEwKSwgCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSksIAogIGdncGxvdCh3ZWF0aGVyLlBhbmVsLCBhZXMoaW50ZXJ2YWw2MCxXaW5kX1NwZWVkKSkgKyBnZW9tX2xpbmUoY29sb3I9IiNENzRFMDkiKSArIAogICAgbGFicyh0aXRsZT0iV2luZCBTcGVlZCIsIHg9IkhvdXIiLCB5PSJXaW5kIFNwZWVkIikgKwogICAgdGhlbWUocGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gOSxmYWNlID0gIml0YWxpYyIpLAogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSwgCiAgICAgICAgYXhpcy50ZXh0Lng9ZWxlbWVudF90ZXh0KHNpemU9OCksCiAgICAgICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF90ZXh0KHNpemU9OCksIAogICAgICAgIGF4aXMudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9MTApLCAKICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpKSwgIAogIGdncGxvdCh3ZWF0aGVyLlBhbmVsLCBhZXMoaW50ZXJ2YWw2MCxUZW1wZXJhdHVyZSkpICsgZ2VvbV9saW5lKGNvbG9yPSIjMTI0RTc4IikgKyAKICAgIGxhYnModGl0bGU9IlRlbXBlcmF0dXJlIiwgeD0iSG91ciIsIHk9IlRlbXBlcmF0dXJlIikgK3RoZW1lKHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDksZmFjZSA9ICJpdGFsaWMiKSwKICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgZmFjZSA9ICJib2xkIiksIAogICAgICAgIGF4aXMudGV4dC54PWVsZW1lbnRfdGV4dChzaXplPTgpLAogICAgICAgIGF4aXMudGV4dC55PWVsZW1lbnRfdGV4dChzaXplPTgpLCAKICAgICAgICBheGlzLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTEwKSwgCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSksIAogIHRvcD0iV2VhdGhlciBEYXRhICIpCgpgYGAKCgojIFNwYWNlLVRpbWUgUGFuZWwKCkluIHRoZSBmb2xsb3dpbmcgc3RlcHMsIHdlIGNyZWF0ZWQgYSBzdHVkeSBwYW5lbCB3aGVyZSBlYWNoIGluc3RhbmNlIGluIHRoZSBwYW5lbCBpcyBhIHVuaXF1ZSBjb21iaW5hdGlvbiBvZiBzcGFjZSBhbmQgdGltZS4gSW4gb3RoZXIgd29yZHMsIGVhY2ggcm93IHdpbGwgcmVwcmVzZW50IHRoZSByaWRlIGF0IGEgcGFydGljdWxhciBzdGF0aW9uIGR1cmluZyBhIHBhcnRpY3VsYXIgaG91ci4gCgpgYGB7ciBjcmVhdGUgc3R1ZHkgcGFuZWwsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9CgpzdHVkeS5wYW5lbCA8LSAKICBleHBhbmQuZ3JpZChpbnRlcnZhbDYwPXVuaXF1ZShiaWtlX2NlbnN1cyRpbnRlcnZhbDYwKSwgCiAgICAgICAgICAgICAgc3RhcnRfc3RhdGlvbl9pZCA9IHVuaXF1ZShiaWtlX2NlbnN1cyRzdGFydF9zdGF0aW9uX2lkKSkgJT4lCiAgbGVmdF9qb2luKC4sIGJpa2VfY2Vuc3VzICU+JQogICAgICAgICAgICAgIHNlbGVjdChzdGFydF9zdGF0aW9uX2lkLCBzdGFydF9zdGF0aW9uX25hbWUsIE9yaWdpbi5UcmFjdCwgc3RhcnRfbG5nLCBzdGFydF9sYXQpJT4lCiAgICAgICAgICAgICAgZGlzdGluY3QoKSAlPiUKICAgICAgICAgICAgICBncm91cF9ieShzdGFydF9zdGF0aW9uX2lkKSAlPiUKICAgICAgICAgICAgICBzbGljZSgxKSkKYGBgCgpXZSBuZWVkIHRvIGFkZCBzb21lIG1vcmUgaW5mb3JtYXRpb24gdG8gdGhpcyBwYW5lbC4gVGhpcyBpbmNsdWRlcyBjb3VudGluZyB0aGUgbnVtYmVyIG9mIHJpZGVzIGF0IHRoaXMgc3RhdGlvbiBhdCB0aGlzIHBhcnRpY3VsYXIgaG91ciwgYWRkaW5nIHdlYXRoZXIgaW5mb3JtYXRpb24sIGJyaW5naW5nIGluIGNlbnN1cyBkYXRhLCBhbmQgY2FsY3VsYXRpbmcgdGltZSBhbmQgZGF5IG9mIHdlZWsgdXNpbmcgdGhlIGBsdWJyaWRhdGVgIHBhY2thZ2UuICAKCmBgYHtyIGFkZCBpbmZvIHRvIHN0dWR5IHBhbmVsLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQoKYmlrZS5wYW5lbCA8LSAKICBiaWtlX2NlbnN1cyAlPiUKICBtdXRhdGUoVHJpcF9Db3VudGVyID0gMSkgJT4lCiAgcmlnaHRfam9pbihzdHVkeS5wYW5lbCkgJT4lIAogIGdyb3VwX2J5KGludGVydmFsNjAsIHN0YXJ0X3N0YXRpb25faWQsIHN0YXJ0X3N0YXRpb25fbmFtZSwgT3JpZ2luLlRyYWN0LCBzdGFydF9sbmcsIHN0YXJ0X2xhdCkgJT4lCiAgc3VtbWFyaXplKFRyaXBfQ291bnQgPSBzdW0oVHJpcF9Db3VudGVyLCBuYS5ybT1UKSkgJT4lCiAgbGVmdF9qb2luKHdlYXRoZXIuUGFuZWwpICU+JQogIHVuZ3JvdXAoKSAlPiUKICBmaWx0ZXIoaXMubmEoc3RhcnRfc3RhdGlvbl9pZCkgPT0gRkFMU0UpICU+JQogIG11dGF0ZSh3ZWVrID0gd2VlayhpbnRlcnZhbDYwKSwKICAgICAgICAgZG90dyA9IHdkYXkoaW50ZXJ2YWw2MCwgbGFiZWwgPSBUUlVFKSkgJT4lCiAgZmlsdGVyKGlzLm5hKE9yaWdpbi5UcmFjdCkgPT0gRkFMU0UpICU+JSAKICBsZWZ0X2pvaW4oLiwgbmpDZW5zdXMgJT4lCiAgICAgICAgICAgICAgYXMuZGF0YS5mcmFtZSgpICU+JQogICAgICAgICAgICAgIHNlbGVjdCgtZ2VvbWV0cnkpLCBieSA9IGMoIk9yaWdpbi5UcmFjdCIgPSAiR0VPSUQiKSkKCmBgYAoKIyMgV2VhdGhlciBDb3JyZWxhdGlvbgoKTm93LCB3ZSBjb3VsZCBwZXJmb3JtIG1vcmUgZXhwbG9yYXRvcnkgYW5hbHlzaXMgb24gb3VyIHBhbmVsIGRhdGEgdG8gcHJlcGFyZSBmb3Igb3VyIHJlZ3Jlc3Npb24gbW9kZWwuIEZpcnN0LCB3ZSB3b3VsZCBsaWtlIHRvIHNlZSBob3cgdGVtcGVyYXR1cmUgd2lsbCBjb3JyZWxhdGUgd2l0aCB0aGUgbnVtYmVyIG9mIGJpa2UgcmlkZXMgaW4gQXVndXN0LiBUaGUgZmlndXJlIGJlbG93IHNob3dzIGEgc2lnbmlmaWNhbnQsIHBvc2l0aXZlLCBzdHJvbmcsIGFuZCBsaW5lYXIgcmVsYXRpb25zaGlwIGJldHdlZW4gdGVtcGVyYXR1cmUgYW5kIG1lYW4gdHJpcCBjb3VudCBmb3IgYWxsIHdlZWtzIGluIEF1Z3VzdC4gVGhpcyBtZWFucyB0aGF0IGFzIHRoZSB0ZW1wZXJhdHVyZSBpbmNyZWFzZSwgYmlrZXNoYXJlIGRlbWFuZCB3aWxsIGluY3JlYXNlIGFzIHdlbGwuIAoKYGBge3IgdmlzdWFsaXplIHRlbXAsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9CgpiaWtlLnBhbmVsICU+JQogIGdyb3VwX2J5KGludGVydmFsNjApICU+JSAKICBzdW1tYXJpemUoVHJpcF9Db3VudCA9IG1lYW4oVHJpcF9Db3VudCksCiAgICAgICAgICAgIFRlbXBlcmF0dXJlID0gZmlyc3QoVGVtcGVyYXR1cmUpKSAlPiUKICBtdXRhdGUod2VlayA9IHdlZWsoaW50ZXJ2YWw2MCkpICU+JQogIGdncGxvdChhZXMoVGVtcGVyYXR1cmUsIFRyaXBfQ291bnQpKSArIAogICAgZ2VvbV9wb2ludChjb2xvciA9ICIjNkUwRTBBIikgKyBnZW9tX3Ntb290aChtZXRob2QgPSAibG0iLCBzZT0gRkFMU0UsIGNvbG9yPSIjMTI0RTc4IikgKwogICAgZmFjZXRfd3JhcCh+d2VlaywgbmNvbD04KSArIAogICAgbGFicyh0aXRsZT0iVHJpcCBDb3VudCBBcyBhIEZ1Y3Rpb24gb2YgVGVtcGVyYXR1cmUgYnkgV2VlayIsCiAgICAgICAgIHg9IlRlbXBlcmF0dXJlIiwgeT0iTWVhbiBUcmlwIENvdW50IikgKyAKICB0aGVtZShwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSA5LGZhY2UgPSAiaXRhbGljIiksCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIsIGZhY2UgPSAiYm9sZCIpLCAKICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGF4aXMudGlja3MueCA9IGVsZW1lbnRfYmxhbmsoKSwgCiAgICAgICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF90ZXh0KHNpemU9OCksIAogICAgICAgIGF4aXMudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9MTApLCAKICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfcmVjdChjb2xvdXIgPSAiZ3JleSIsIGZpbGw9TkEsIHNpemU9MC44KSkKICAKCmBgYAoKTmV4dCwgd2Ugd291bGQgbGlrZSB0byBzZWUgaG93IHdpbmQgc3BlZWQgd2lsbCBjb3JyZWxhdGUgd2l0aCB0aGUgbnVtYmVyIG9mIGJpa2UgcmlkZXMgaW4gQXVndXN0LiBXZSBzZWUgdGhhdCBhIGxpbmVhciBhbmQgcG9zaXRpdmUgcmVsYXRpb25zaGlwIGJldHdlZW4gdGhlc2UgdHdvIHZhcmlhYmxlcywgYnV0IHRoZSBzdHJlbmd0aCBvZiBzdWNoIHJlbGF0aW9uc2hpcCB2YXJpZXMgYmV0d2VlbiBkaWZmZXJlbnQgd2Vla3MuIFRoaXMgZ2VuZXJhbGx5IG1lYW5zIHRoYXQgd2Ugc2VlIG1vcmUgYmlrZXNoYXJlIGRlbWFuZCBkdXJpbmcgd2luZHkgZGF5cy4gCgpgYGB7ciB2aXN1YWxpemUgd2luZCwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0KYmlrZS5wYW5lbCAlPiUKICBncm91cF9ieShpbnRlcnZhbDYwKSAlPiUgCiAgc3VtbWFyaXplKFRyaXBfQ291bnQgPSBtZWFuKFRyaXBfQ291bnQpLAogICAgICAgICAgICBXaW5kID0gZmlyc3QoV2luZF9TcGVlZCkpICU+JQogIG11dGF0ZSh3ZWVrID0gd2VlayhpbnRlcnZhbDYwKSkgJT4lCiAgZ2dwbG90KGFlcyhXaW5kLCBUcmlwX0NvdW50KSkgKyAKICAgIGdlb21fcG9pbnQoY29sb3IgPSAiIzZFMEUwQSIpICsgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxtIiwgc2U9IEZBTFNFLCBjb2xvcj0iIzEyNEU3OCIpICsKICAgIGZhY2V0X3dyYXAofndlZWssIG5jb2w9OCkgKyAKICAgIGxhYnModGl0bGU9IlRyaXAgQ291bnQgQXMgYSBGdWN0aW9uIG9mIFdpbmQgYnkgV2VlayIsCiAgICAgICAgIHg9IldpbmQgU3BlZWQiLCB5PSJNZWFuIFRyaXAgQ291bnQiKSArIAogIHRoZW1lKHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDksZmFjZSA9ICJpdGFsaWMiKSwKICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgZmFjZSA9ICJib2xkIiksIAogICAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpLAogICAgYXhpcy50aWNrcy54ID0gZWxlbWVudF9ibGFuaygpLCAKICAgICAgICBheGlzLnRleHQueT1lbGVtZW50X3RleHQoc2l6ZT04KSwgCiAgICAgICAgYXhpcy50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT0xMCksIAogICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9yZWN0KGNvbG91ciA9ICJncmV5IiwgZmlsbD1OQSwgc2l6ZT0wLjgpKSArIAogICAgdGhlbWUocGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gOSxmYWNlID0gIml0YWxpYyIpLAogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSwgCiAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCksCiAgICBheGlzLnRpY2tzLnggPSBlbGVtZW50X2JsYW5rKCksIAogICAgICAgIGF4aXMudGV4dC55PWVsZW1lbnRfdGV4dChzaXplPTgpLCAKICAgICAgICBheGlzLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTEwKSwgCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X3JlY3QoY29sb3VyID0gImdyZXkiLCBmaWxsPU5BLCBzaXplPTAuOCkpCmBgYAoKCiMjIFNlcmlhbCBDb3JyZWxhdGlvbgoKU2hvdWxkIHRoZSBudW1iZXIgb2YgdHJpcHMgZXhoaWJpdCBzZXJpYWwgKHRlbXBvcmFsKSBjb3JyZWxhdGlvbiwgdGhlbiB0aGUgdGltZSBsYWcgZmVhdHVyZXMgd2lsbCBsZWFkIHRvIGJldHRlciBwcmVkaWN0aW9ucy4gVGhlIGxvZ2ljIGJlaGluZCBpcyB0aGF0IHRoZSBudW1iZXIgb2YgdHJpcHMgYXQgYSBwYXJ0aWN1bGFyIGhvdXIgd2lsbCB2ZXJ5IG11Y2ggY29ycmVsYXRlIHdpdGggdGhlIG51bWJlciBvZiB0cmlwcyBpbW1lZGlhdGVseSBiZWZvcmUgb3IgYWZ0ZXIgdGhhdCBob3VyLiBJZiB3ZSBrbm93IHRoZSBudW1iZXIgb2YgdHJpcHMgYXQgYSBwYXJ0aWN1bGFyIGhvdXIsIHRoZW4gaXQgd291bGQgYmUgZWFzaWVyIGZvciB1cyB0byBlc3RpbWF0ZSB0aGUgbnVtYmVyIG9mIHJpZGVzIGFyb3VuZCB0aGF0IHRpbWUuIFRvIGFjaGlldmUgdGhhdCwgd2UgY2FsY3VsYXRlZCA2IGRpZmZlcmVudCBmb3JtcyBvZiBsYWcgaG91cnMuIAoKYGBge3IgY2FsY3VsYXRlIHNlcmlhbCBsYWd9CgpiaWtlLnBhbmVsIDwtIAogIGJpa2UucGFuZWwgJT4lIAogIGFycmFuZ2Uoc3RhcnRfc3RhdGlvbl9pZCwgaW50ZXJ2YWw2MCkgJT4lIAogIG11dGF0ZShsYWdIb3VyID0gZHBseXI6OmxhZyhUcmlwX0NvdW50LDEpLAogICAgICAgICBsYWcySG91cnMgPSBkcGx5cjo6bGFnKFRyaXBfQ291bnQsMiksCiAgICAgICAgIGxhZzNIb3VycyA9IGRwbHlyOjpsYWcoVHJpcF9Db3VudCwzKSwKICAgICAgICAgbGFnNEhvdXJzID0gZHBseXI6OmxhZyhUcmlwX0NvdW50LDQpLAogICAgICAgICBsYWcxMkhvdXJzID0gZHBseXI6OmxhZyhUcmlwX0NvdW50LDEyKSwKICAgICAgICAgbGFnMWRheSA9IGRwbHlyOjpsYWcoVHJpcF9Db3VudCwyNCkpICU+JQogICBtdXRhdGUoZGF5ID0geWRheShpbnRlcnZhbDYwKSkKCmBgYAoKVGhlIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIHRoZXNlIGxhZyBob3VycyBhbmQgbnVtYmVyIG9mIHRyaXBzIGFyZSB2aXN1YWxpemVkLiBXZSBzZWUgYSBzdHJvbmcsIGxpbmVhciwgYW5kIG5lZ2F0aXZlIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIGxhZzEyaG91cnMgYW5kIG51bWJlciBvZiB0cmlwcyAxMiBob3VycyBiZWZvcmUuIFRoaXMgbWVhbnMgZm9yIGV4YW1wbGUsIHRoZSBudW1iZXIgb2YgcmlkZXMgd2Ugc2VlIGF0IDVwbSBvbiBhIHBhcnRpY3VsYXIgZGF5IGlzIG5lZ2F0aXZlbHkgY29ycmVsYXRlZCB3aXRoIHRoZSBudW1iZXIgb2YgcmlkZXMgYXQgNWFtIHRoYXQgZGF5LiBJbiBjb250cmFzdCwgd2Ugc2VlIGEgc3Ryb25nLCBsaW5lYXIsIGFuZCBwb3NpdGl2ZSByZWxhdGlvbnNoaXAgYmV0d2VlbiBsYWdob3VyIGFuZCBsYWcxZGF5LiBUaGlzIG1lYW5zIHRoYXQgdGhlIG51bWJlciBvZiByaWRlcyBhdCBhIHBhcnRpY3VsYXIgaG91ciBpcyBoaWdobHkgY29ycmVsYXRlZCB3aXRoIHRoZSBudW1iZXIgb2YgcmlkZXMgaW4gdGhlIGltbWVkaWF0ZSBuZXh0IGhvdXIgYW5kIGF0IHRoZSBzYW1lIHRpbWUgdGhlIGZvbGxvd2luZyBkYXkuIE90aGVyIHRoYW4gdGhhdCwgdGhlIHN0cmVuZ3RoIG9mIHRoZSBjb3JyZWxhdGlvbiBkZWNyZWFzZXMgZm9yIGxhZzJob3VyIGFuZCBsYWczaG91ciwgbWVhbmluZyB0aGF0IHRoZSBudW1iZXIgb2YgcmlkZXMgYXQgNHBtIG9uIGEgcGFydGljdWxhciBkYXksIGlzIG1vcmUgY29ycmVsYXRlZCB3aXRoIHRoZSBudW1iZXIgb2YgcmlkZXMgYXQgNXBtLCByYXRoZXIgdGhhbiBhdCA2IG9yIDcgcG0uIAoKYGBge3IgdmlzdWFsaXplIHNlcmlhbCBjb3JyZWxhdGlvbnMsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9Cgphcy5kYXRhLmZyYW1lKGJpa2UucGFuZWwpICU+JQogICAgZ3JvdXBfYnkoaW50ZXJ2YWw2MCkgJT4lIAogICAgc3VtbWFyaXNlX2F0KHZhcnMoc3RhcnRzX3dpdGgoImxhZyIpLCAiVHJpcF9Db3VudCIpLCBtZWFuLCBuYS5ybSA9IFRSVUUpICU+JQogICAgZ2F0aGVyKFZhcmlhYmxlLCBWYWx1ZSwgLWludGVydmFsNjAsIC1UcmlwX0NvdW50KSAlPiUKICBnZ3Bsb3QoYWVzKHggPSBWYWx1ZSAsIHkgPSBUcmlwX0NvdW50KSkgKwogICAgZ2VvbV9wb2ludChzaXplID0gMSwgY29sb3IgPSAiIzZFMEUwQSIpICsgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxtIiwgc2U9IEZBTFNFLCBjb2xvcj0iIzEyNEU3OCIpICsKICBmYWNldF93cmFwKH5WYXJpYWJsZSwgbmNvbCA9IDYpKwogIGxhYnMoeCA9ICJWYXJpYWJsZSIsIHkgPSAiQ29ycmVsYXRpb24iLCB0aXRsZSA9ICJDb3JyZWxhdGlvbiBCZXR3ZWVuIFNlcmlhbCBMYWcgVHJpcHMgYW5kIFRyaXAgQ291bnQiKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSkgKyAKICB0aGVtZShwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSA5LGZhY2UgPSAiaXRhbGljIiksCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIsIGZhY2UgPSAiYm9sZCIpLCAKICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGF4aXMudGlja3MueCA9IGVsZW1lbnRfYmxhbmsoKSwgCiAgICAgICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF90ZXh0KHNpemU9OCksIAogICAgICAgIGF4aXMudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9MTApLCAKICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfcmVjdChjb2xvdXIgPSAiZ3JleSIsIGZpbGw9TkEsIHNpemU9MC44KSkKCmBgYAoKIyMgU3BhdGlhbCBDb3JyZWxhdGlvbgoKV2UndmUgc2VlbiBiZWZvcmUgdGhhdCBzcGF0aWFsIGF1dG9jb3JyZWxhdGlvbiBleGlzdHMgaGVyZSBpbiB0aGF0IHRoZSBudW1iZXIgb2YgdHJpcHMgYXQgYSBwYXJ0aWN1bGFyIHN0YXRpb24gZHVyaW5nIGEgcGFydGljdWxhciB0aW1lIGlzIGNvcnJlbGF0ZWQgd2l0aCB0aGUgbnVtYmVyIG9mIHRyaXBzIGF0IG5lYXJieSBzdGF0aW9ucy4gVGhpcyBtZWFucyB0aGF0IGlmIHdlIGtub3cgdGhlIG51bWJlciBvZiB0cmlwcyBhdCBhIHN0YXRpb24sIHdlIG1heSBiZSBhYmxlIHRvIGVzdGltYXRlIHRoZSBudW1iZXIgb2YgdHJpcHMgYXQgdGhlIHN0YXRpb24gbmV4dCB0byBpdC4gRG8gYWNoaWV2ZSB0aGF0LCB3ZSBjcmVhdGVkIGEgbmV3IGRhdGFmcmFtZSBjYWxsZWQgYGxhZ19yZXN1bHRgIGFuZCB3cm90ZSBhIG5lc3RlZCBsb29wIHRvIGxvb3AgdGhyb3VnaCBlYWNoIGhvdXIgaW4gQXVndXN0IDIwMjMuIEZvciBlYWNoIGhvdXIsIHdlIHRoZW4gY2FsY3VsYXRlIHRoZSBuZWFyZXN0IDUgbmVpZ2hib3Igb2YgZWFjaCBzdGF0aW9uIGFuZCB0aGUgYXZlcmFnZSBvZiByaWRlcyBmb3IgdGhhdCA1IHN0YXRpb25zLiBUaGUgcmVzdWx0cyB3ZXJlIGpvaW5lZCBiYWNrIHRvIG91ciBwYW5lbCBkYXRhc2V0LiAKCmBgYHtyIGNhbGN1bGF0ZSBzcGF0aWFsIGxhZywgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KCmxhZ19yZXN1bHQgPC0gZGF0YS5mcmFtZSgpCgpmb3IgKGQgaW4gMjEzOjI0MykgewogIGZvciAoaCBpbiAwOjIzKSB7CgpzcGFjZWxhZyA8LSBiaWtlLnBhbmVsICU+JSAKICBtdXRhdGUoaG91ciA9IGhvdXIoaW50ZXJ2YWw2MCkpICU+JSAKICBmaWx0ZXIoZGF5ID09IGQgJiBob3VyID09IGgpICU+JSAKICBzdF9hc19zZiguLCBjb29yZHMgPSBjKCJzdGFydF9sbmciLCAic3RhcnRfbGF0IiksIGNycyA9IDQzMjYpIAoKCmNvb3Jkcy50ZXN0X2xhZyA8LSAgc3RfY29vcmRpbmF0ZXMoc3BhY2VsYWcpIApuZWlnaGJvckxpc3QudGVzdF9sYWcgPC0ga25uMm5iKGtuZWFybmVpZ2goY29vcmRzLnRlc3RfbGFnLCA1KSkKc3BhdGlhbFdlaWdodHMudGVzdF9sYWcgPC0gbmIybGlzdHcobmVpZ2hib3JMaXN0LnRlc3RfbGFnLCBzdHlsZT0iVyIpCgpzcGFjZWxhZyA8LSBzcGFjZWxhZyAlPiUgbXV0YXRlKGxhZ1RyaXAgPSBsYWcubGlzdHcoc3BhdGlhbFdlaWdodHMudGVzdF9sYWcsIFRyaXBfQ291bnQpKQoKbGFnX3Jlc3VsdCA8LSByYmluZChsYWdfcmVzdWx0LCBzcGFjZWxhZykKICB9CiAgCn0KCmJpa2UucGFuZWwgPC0gbGFnX3Jlc3VsdCAlPiUgCiAgbXV0YXRlKHN0YXJ0X2xuZyA9IHVubGlzdChtYXAoZ2VvbWV0cnksIDEpKSwKICAgICAgICAgc3RhcnRfbGF0ID0gdW5saXN0KG1hcChnZW9tZXRyeSwgMikpKSAlPiUgCiBzdF9kcm9wX2dlb21ldHJ5KCkgCiAgCiAgCmBgYAoKVGhlIGZpZ3VyZSBiZWxvdyBzaG93cyB0aGF0IGZvciBhbGwgd2Vla3MgaW4gQXVndXN0LCB0aGVyZSdzIGEgc3Ryb25nLCBsaW5lYXIsIGFuZCBwb3NpdGl2ZSByZWxhdGlvbnNoaXAgYmV0d2VlbiB0aGUgbnVtYmVyIG9mIHRyaXBzIGFuZCB0aGUgbnVtYmVyIG9mIHRyaXBzIGluIG5lYXJieSBzdGF0aW9ucy4gV2Ugd2lsbCB0aGVyZWZvcmUgaW5jbHVkZSB0aGlzIHZhcmlhYmxlIGluIG91ciBmb2xsb3dpbmcgcmVncmVzc2lvbiBhbmFseXNpcy4gCgpgYGB7ciB2aXN1YWxpemUgc3BhdGlhbCBjb3JyZWxhdGlvbiwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KCmJpa2UucGFuZWwgJT4lCiAgZ3JvdXBfYnkoaW50ZXJ2YWw2MCkgJT4lIAogIHN1bW1hcml6ZShUcmlwX0NvdW50ID0gbWVhbihUcmlwX0NvdW50KSwKICAgICAgICAgICAgTGFnX0NvdW50ID0gbWVhbihsYWdUcmlwKSkgJT4lCiAgbXV0YXRlKHdlZWsgPSB3ZWVrKGludGVydmFsNjApKSAlPiUKICBnZ3Bsb3QoYWVzKExhZ19Db3VudCwgVHJpcF9Db3VudCkpICsgCiAgICBnZW9tX3BvaW50KHNpemUgPSAxLCBjb2xvciA9ICIjNkUwRTBBIikgKyBnZW9tX3Ntb290aChtZXRob2QgPSAibG0iLCBzZT0gRkFMU0UsIGNvbG9yPSIjMTI0RTc4IikgKwogICAgZmFjZXRfd3JhcCh+d2VlaywgbmNvbD04KSArIAogICAgbGFicyh0aXRsZT0iQ29ycmVsYXRpb24gQmV0d2VlbiBTcGF0aWFsIExhZyBUcmlwcyBhbmQgVHJpcCBDb3VudCIsCiAgICAgICAgIHg9IkxhZyBUcmlwIENvdW50IiwgeT0iTWVhbiBUcmlwIENvdW50IikgKyAKICB0aGVtZShwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSA5LGZhY2UgPSAiaXRhbGljIiksCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIsIGZhY2UgPSAiYm9sZCIpLCAKICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGF4aXMudGlja3MueCA9IGVsZW1lbnRfYmxhbmsoKSwgCiAgICAgICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF90ZXh0KHNpemU9OCksIAogICAgICAgIGF4aXMudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9MTApLCAKICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfcmVjdChjb2xvdXIgPSAiZ3JleSIsIGZpbGw9TkEsIHNpemU9MC44KSkKCmBgYAoKCiMgT0xTIFJlZ3Jlc3Npb24KCldlIHNwbGl0IG91ciA1IHdlZWsgZGF0YSBpbnRvIHRyYWluaW5nIHNldCwgd2hpY2ggaW5jbHVkZXMgMyB3ZWVrcyBhbmQgdGVzdGluZyBzZXQsIHdoaWNoIGluY2x1ZGUgMiB3ZWVrcy4gQWZ0ZXIgdGhhdCwgZm91ciBkaWZmZXJlbnQgbGluZWFyIHJlZ3Jlc3Npb24gYXJlIGVzdGltYWVkIG9uIHRyYWluaW5nIGRhdGEsIGVhY2ggd2l0aCBkaWZmZXJlbnQgZml4ZWQgZWZmZWN0cy4gCgotIGByZWcxYCBmb2N1c2VzIG9uIGp1c3QgdGltZSwgaW5jbHVkaW5nIGhvdXIgZml4ZWQgZWZmZWN0cywgZGF5IG9mIHRoZSB3ZWVrLCBXaW5kIFNwZWVkIGFuZCBUZW1wZXJhdHVyZS4KLSBgcmVnMmAgZm9jdXNlcyBvbiBqdXN0IHNwYWNlLCBpbmNsdWRpbmcgc3RhdGlvbiBhbmQgd2VhdGhlciBjb25kaXRpb25zLiAKLSBgcmVnM2AgZm9jdXNlcyBvbiB0aGUgY29tYmluZWQgZWZmZWN0IG9mIHNwYWNlIGFuZCB0aW1lCi0gYHJlZzRgIGZvY3VzZXMgYWRkcyB0aGUgdGltZSBhbmQgc3BhY2UgbGFnIGZlYXR1cmVzCgoKYGBge3IgZml0dGluZyByZWdyZXNzaW9ufQoKYmlrZS5UcmFpbiA8LSBmaWx0ZXIoYmlrZS5wYW5lbCwgd2VlayA8PSAzMykKYmlrZS5UZXN0IDwtIGZpbHRlcihiaWtlLnBhbmVsLCB3ZWVrID4gMzMpCgojIFRpbWUKcmVnMSA8LSAKICBsbShUcmlwX0NvdW50IH4gIGhvdXIoaW50ZXJ2YWw2MCkgKyBkb3R3ICsgVGVtcGVyYXR1cmUgKyBXaW5kX1NwZWVkLCBkYXRhPWJpa2UuVHJhaW4pCgojIFNwYWNlCnJlZzIgPC0gCiAgbG0oVHJpcF9Db3VudCB+ICBzdGFydF9zdGF0aW9uX25hbWUgKyBUZW1wZXJhdHVyZSArIFdpbmRfU3BlZWQsICBkYXRhPWJpa2UuVHJhaW4pCgojIFRpbWUgYW5kIFNwYWNlCnJlZzMgPC0gCiAgbG0oVHJpcF9Db3VudCB+ICBzdGFydF9zdGF0aW9uX25hbWUgKyBob3VyKGludGVydmFsNjApICsgZG90dyArIFRlbXBlcmF0dXJlICsgV2luZF9TcGVlZCwgCiAgICAgZGF0YT1iaWtlLlRyYWluKQoKIyBUaW1lIGFuZCBTcGFjZSBhbmQgVGltZUxhZyBhbmQgU3BhY2VMYWcKcmVnNCA8LSAKICBsbShUcmlwX0NvdW50IH4gIHN0YXJ0X3N0YXRpb25fbmFtZSArICBob3VyKGludGVydmFsNjApICsgZG90dyArIFRlbXBlcmF0dXJlICsgV2luZF9TcGVlZCArCiAgICAgICAgICAgICAgICAgICBsYWdIb3VyICsgbGFnMkhvdXJzICsgbGFnMTJIb3VycyArIGxhZzFkYXkgKyBsYWdUcmlwLCAKICAgICBkYXRhPWJpa2UuVHJhaW4pCmBgYAoKIyMgTWFraW5nIFByZWRpY3Rpb25zCgpUaGUgcHVycnIgZmFtaWx5IG9mIGZ1bmN0aW9ucyB3aWxsIGJlIHVzZWQgdG8gbG9vcCB0aHJvdWdoIGEgc2V0IG9mICduZXN0ZWQnIGRhdGEgZnJhbWVzIHRvIGVmZmljaWVudGx5IGNvbXBhcmUgYWNyb3NzIGRpZmZlcmVudCBtb2RlbCBzcGVjaWZpY2F0aW9ucy4gQmVmb3JlIHRlc3Rpbmcgb3VyIGRhdGEsIHdlIG5lc3RlZCBvdXIgZGF0YSBieSB3ZWVrIGFuZCBjcmVhdGVkIGEgbW9kZWwgZnVuY3Rpb24uIFRoZSBmaXJzdCBzZXQgb2YgY29kZSBzZXBhcmF0ZSBvdXIgdGVzdGluZyBkYXRhIGludG8gdHdvIHRpYmJsZXMuIFRoZSBzbWFsbCBmdW5jdGlvbiBpcyBjcmVhdGVkIHRoYXQgdGFrZXMgYSB0aWJibGUsIGRhdCBhbmQgYSByZWdyZXNzaW9uIG1vZGVsLCBmaXQgYXMgaXRzIGlucHV0cywgYW5kIG91dHB1dHMgcHJlZGljdGlvbnMgYXMgcHJlZC4gVGhpcyBmdW5jdGlvbiBpcyB1c2VkIHNpbXBseSBmb3IgcHJlZGljdGlvbi4gCgpgYGB7ciBuZXN0IGRhdGEgYW5kIHByZWRpY3QgZnVuY3Rpb24sIHdhcm5pbmc9RkFMU0V9CgpiaWtlLlRlc3Qud2Vla05lc3QgPC0gCiAgYmlrZS5UZXN0ICU+JQogIG5lc3QoLXdlZWspIAoKbW9kZWxfcHJlZCA8LSBmdW5jdGlvbihkYXQsIGZpdCl7CiAgIHByZWQgPC0gcHJlZGljdChmaXQsIG5ld2RhdGEgPSBkYXQpfQoKYGBgCgoKVGhlIG5lc3RlZCBmb3JtYXQgYWxsb3dzIG9uZSB0byBsb29wIHRocm91Z2ggZWFjaCBtb2RlbCBmb3IgZWFjaCB3ZWVrIGFuZCBtdXRhdGUgc3VtbWFyeSBzdGF0aXN0aWNzLiBJbiB0aGUgY29kZSBibG9jayBiZWxvdywgd2Vla19wcmVkaWN0aW9ucyBhcmUgY2FsY3VsYXRlZCBmb3IgZWFjaCB3ZWVrIGluIHRoZSB0ZXN0aW5nIHNldC4gVGhlIG1hcCBmdW5jdGlvbiBhcHBsaWVzIHRoZSBgbW9kZWxfcHJlZGAgZnVuY3Rpb24gdG8gZWFjaCBuZXN0ZWQgdGliYmxlLiBUYWtlIHRoZSBmaXJzdCBsaW5lIG9mIHRoZSBiZWxvdyBtdXRhdGUsIGZvciBleGFtcGxlLiBBIG5ldyBjb2x1bW4sIFRpbWVfRkUsIGluY2x1ZGVzIHByZWRpY3Rpb25zIGZvciByZWcxIC0gdGhlIHRpbWUgZml4ZWQgZWZmZWN0cyBtb2RlbC4gVGhlIHByZWRpY3Rpb25zIGFyZSBjcmVhdGVkIGJ5IG1hcHBpbmcgdGhlIGZ1bmN0aW9uLCBtb2RlbF9wcmVkLCB0byBlYWNoIHJvdyBvZiBkYXRhLCBwYXJhbWV0ZXJpemluZyBmaXQgYXMgdGhlIHJlZzEgbW9kZWwuCgpgYGB7ciBtYWtpbmcgcHJlZGljdGlvbnMsIHdhcm5pbmc9RkFMU0V9Cgp3ZWVrX3ByZWRpY3Rpb25zIDwtIAogIGJpa2UuVGVzdC53ZWVrTmVzdCAlPiUgCiAgICBtdXRhdGUoQVRpbWVfRkUgPSBtYXAoLnggPSBkYXRhLCBmaXQgPSByZWcxLCAuZiA9IG1vZGVsX3ByZWQpLAogICAgICAgICAgIEJTcGFjZV9GRSA9IG1hcCgueCA9IGRhdGEsIGZpdCA9IHJlZzIsIC5mID0gbW9kZWxfcHJlZCksCiAgICAgICAgICAgQ1RpbWVfU3BhY2VfRkUgPSBtYXAoLnggPSBkYXRhLCBmaXQgPSByZWczLCAuZiA9IG1vZGVsX3ByZWQpLAogICAgICAgICAgIERUaW1lX1NwYWNlX0ZFX0xhZ3MgPSBtYXAoLnggPSBkYXRhLCBmaXQgPSByZWc0LCAuZiA9IG1vZGVsX3ByZWQpKSAlPiUgCiAgICBnYXRoZXIoUmVncmVzc2lvbiwgUHJlZGljdGlvbiwgLWRhdGEsIC13ZWVrKSAlPiUKICAgIG11dGF0ZShPYnNlcnZlZCA9IG1hcChkYXRhLCBwdWxsLCBUcmlwX0NvdW50KSwKICAgICAgICAgICBBYnNvbHV0ZV9FcnJvciA9IG1hcDIoT2JzZXJ2ZWQsIFByZWRpY3Rpb24sICB+IGFicygueCAtIC55KSksCiAgICAgICAgICAgTUFFID0gbWFwX2RibChBYnNvbHV0ZV9FcnJvciwgbWVhbiwgbmEucm0gPSBUUlVFKSwKICAgICAgICAgICBzZF9BRSA9IG1hcF9kYmwoQWJzb2x1dGVfRXJyb3IsIHNkLCBuYS5ybSA9IFRSVUUpKQoKYGBgCgojIyBFcnJvciBBbmFseXNpcwoKQ29tcGFyaW5nIHRoZSBtZWFuIGFic29sdXRlIGVycm9ycyBmb3Igb3VyIGZvdXIgbW9kZWxzLCB3ZSBmaW5kIHRoZSBlcnJvciB0byBiZSB0aGUgaGlnZXN0IGZvciBtb2RlbCAxLCB3aGljaCBvbmx5IHRha2VzIHRpbWUgZWZmZWN0IGludG8gYWNjb3VudC4gTW9kZWwgMiBhbmQgMyBhY2NvdW50IGZvciBzcGFjZSBhbmQgc3BhY2UgKyB0aW1lIHJlc3BlY3RpdmVseSwgd2hpY2ggbGVhZCB0byBhbiBpbmNyZWFzZSBpbiBhY2N1cmFjeSBsZXZlbC4gQWRkaW5nIHNwYWNlIGxhZyBhbmQgdGltZSBsYWcsIGhvd2V2ZXIsIHdpbGwgZ3JlYXRseSBpbXByb3ZlIHRoZSBhY2N1cmFjeSBvZiBvdXIgbW9kZS4gV2UgYWxzbyByYW4gdGhlIHZhcmlhbmNlIG9mIGluZmxhdGlvbiBmdW5jdGlvbiBpbiB0aGUgYmFja2dyb3VuZCBhbmQgY2hlY2tlZCB0byBtYWtlIHN1cmUgdGhhdCB0aGVyZSdzIG5vIGNvbGluZXJhcml0eSBpbiBtb2RlbCA0LiAKCmBgYHtyIHBsb3QgZXJyb3JzfQoKd2Vla19wcmVkaWN0aW9ucyAlPiUKICBkcGx5cjo6c2VsZWN0KHdlZWssIFJlZ3Jlc3Npb24sIE1BRSkgJT4lCiAgZ2F0aGVyKFZhcmlhYmxlLCBNQUUsIC1SZWdyZXNzaW9uLCAtd2VlaykgJT4lCiAgZ2dwbG90KGFlcyh3ZWVrLCBNQUUpKSArIAogICAgZ2VvbV9iYXIoYWVzKGZpbGwgPSBSZWdyZXNzaW9uKSwgcG9zaXRpb24gPSAiZG9kZ2UiLCBzdGF0PSJpZGVudGl0eSIpICsKICAgIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IHBhbGV0dGU0KSArCiAgICBsYWJzKHRpdGxlID0gIk1lYW4gQWJzb2x1dGUgRXJyb3JzIGJ5IE1vZGVsIFNwZWNpZmljYXRpb24gYW5kIFdlZWsiKSArCiAgdGhlbWUocGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gOSxmYWNlID0gIml0YWxpYyIpLAogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSwgCiAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCksCiAgICBheGlzLnRpY2tzLnggPSBlbGVtZW50X2JsYW5rKCksIAogICAgICAgIGF4aXMudGV4dC55PWVsZW1lbnRfdGV4dChzaXplPTgpLCAKICAgICAgICBheGlzLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTEwKSwgCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X3JlY3QoY29sb3VyID0gImdyZXkiLCBmaWxsPU5BLCBzaXplPTAuOCkpCgpgYGAKVGhlIGZpZ3VyZSBiZWxvdyBmdXJ0aGVyIGRlbW9uc3RyYXRlcyB0aGUgYWNjdXJhY3kgb2YgZWFjaCBtb2RlbCBieSBjb21wYXJpbmcgcHJlZGljdGVkIHRvIG9ic2VydmVkIG51bWJlciBvZiB0cmlwcy4gTW9kZWwgNCBjbGVhcmx5IHN0YW5kcyBvdXQgYW1vbmcgdGhlIDQgbW9kZWxzIHRvIGhhdmUgaGlnaGVyIHByZWRpY3RpbmcgcG93ZXIgb2YgYmlrZXNoYXJlIGRlbWFuZC4gCgpgYGB7ciBjb21wYXJpbmcgcHJlZGljdGVkIGFuZCBvYnNlcnZlZCwgZmlnLmhlaWdodD04LCBmaWcud2lkdGg9MTEsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGNhY2hlPVRSVUV9Cgp3ZWVrX3ByZWRpY3Rpb25zICU+JSAKICAgIG11dGF0ZShpbnRlcnZhbDYwID0gbWFwKGRhdGEsIHB1bGwsIGludGVydmFsNjApLAogICAgICAgICAgIHN0YXJ0X3N0YXRpb25faWQgPSBtYXAoZGF0YSwgcHVsbCwgc3RhcnRfc3RhdGlvbl9pZCkpICU+JQogICAgZHBseXI6OnNlbGVjdChpbnRlcnZhbDYwLCBzdGFydF9zdGF0aW9uX2lkLCBPYnNlcnZlZCwgUHJlZGljdGlvbiwgUmVncmVzc2lvbikgJT4lCiAgICB1bm5lc3QoKSAlPiUKICAgIGdhdGhlcihWYXJpYWJsZSwgVmFsdWUsIC1SZWdyZXNzaW9uLCAtaW50ZXJ2YWw2MCwgLXN0YXJ0X3N0YXRpb25faWQpICU+JQogICAgZ3JvdXBfYnkoUmVncmVzc2lvbiwgVmFyaWFibGUsIGludGVydmFsNjApICU+JQogICAgc3VtbWFyaXplKFZhbHVlID0gc3VtKFZhbHVlKSkgJT4lCiAgICBnZ3Bsb3QoYWVzKGludGVydmFsNjAsIFZhbHVlLCBjb2xvdXI9VmFyaWFibGUpKSArIAogICAgICBnZW9tX2xpbmUoc2l6ZSA9IDEuMSkgKyAKICAgICAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcz1wYWxldHRlMikrCiAgICAgIGZhY2V0X3dyYXAoflJlZ3Jlc3Npb24sIG5jb2w9MSkgKwogICAgICBsYWJzKHRpdGxlID0gIlByZWRpY3RlZC9PYnNlcnZlZCBCaWtlIFNoYXJlIFRpbWUgU2VyaWVzIiwgc3VidGl0bGUgPSAiSmVyc2V5IENpdHk6IGEgdGVzdCBzZXQgb2YgMiB3ZWVrcyIsICB4ID0gIkhvdXIiLCB5PSAiU3RhdGlvbiBUcmlwcyIpICsgCiAgIHRoZW1lKHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDksZmFjZSA9ICJpdGFsaWMiKSwKICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgZmFjZSA9ICJib2xkIiksIAogICAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpLAogICAgYXhpcy50aWNrcy54ID0gZWxlbWVudF9ibGFuaygpLCAKICAgICAgICBheGlzLnRleHQueT1lbGVtZW50X3RleHQoc2l6ZT04KSwgCiAgICAgICAgYXhpcy50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT0xMCksIAogICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9yZWN0KGNvbG91ciA9ICJncmV5IiwgZmlsbD1OQSwgc2l6ZT0wLjgpKQoKYGBgCgpXZSBhbHNvIG5lZWQgdG8gY2hlY2sgaWYgb3VyIHByZWRpY3Rpb25zIGdlbmVyYWxpemUgYWNyb3NzIHNwYWNlIGFuZCB0aW1lLiBUbyBkbyB0aGF0LCB3ZSBtYXBwZWQgb3VyIG1lYW4gYWJzb2x1dGUgZXJyb3Igb2YgbW9kZWwgNCBhY3Jvc3Mgc3BhY2UuIFdlIGNhbiBzZWUgdGhhdCB0aGlzIG1vZGVsIGRvZXMgbm90IGRvIGEgYmV0dGVyIGpvYiBpbiBwcmVkaWN0aW5nIG51bWJlciBvZiByaWRlcyBhdCBzdGF0aW9ucyBhbG9uZyB0aGUgSHVkc29uIFJpdmVyLCB3aGljaCBhcmUgdHlwaWNhbGx5IHN0YXRpb25zIHdpdGggaGlnaGVyIGJpa2VzaGFyZSBkZW1hbmRzIGR1cmluZyBib3RoIHdlZWtlbmQgYW5kIHdlZWtkYXlzLiBPdGhlciB0aGFuIHRoYXQsIHRoaXMgbW9kZWwgZG9lcyBhIHByZXR0eSBnb29kIGpvYiBpbiBwcmVkaWN0aW5nIGJpa2VzaGFyZSBkZW1hbmRzIGF0IHN0YXRpb25zIHdpdGggbG93ZXIgbnVtYmVyIG9mIHRyaXBzIGluIEF1Z3VzdC4gCgpgYGB7ciBlcnJvciBieSBzdGF0aW9uLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFLCBjYWNoZT1UUlVFfQoKd2Vla19wcmVkaWN0aW9ucyAlPiUgCiAgICBtdXRhdGUoaW50ZXJ2YWw2MCA9IG1hcChkYXRhLCBwdWxsLCBpbnRlcnZhbDYwKSwKICAgICAgICAgICBzdGFydF9zdGF0aW9uX2lkID0gbWFwKGRhdGEsIHB1bGwsIHN0YXJ0X3N0YXRpb25faWQpLCAKICAgICAgICAgICBzdGFydF9sYXRpdHVkZSA9IG1hcChkYXRhLCBwdWxsLCBzdGFydF9sYXQpLCAKICAgICAgICAgICBzdGFydF9sb25naXR1ZGUgPSBtYXAoZGF0YSwgcHVsbCwgc3RhcnRfbG5nKSkgJT4lCiAgICBzZWxlY3QoaW50ZXJ2YWw2MCwgc3RhcnRfc3RhdGlvbl9pZCwgc3RhcnRfbG9uZ2l0dWRlLCBzdGFydF9sYXRpdHVkZSwgT2JzZXJ2ZWQsIFByZWRpY3Rpb24sIFJlZ3Jlc3Npb24pICU+JQogICAgdW5uZXN0KCkgJT4lCiAgZmlsdGVyKFJlZ3Jlc3Npb24gPT0gIkRUaW1lX1NwYWNlX0ZFX0xhZ3MiKSAlPiUKICBncm91cF9ieShzdGFydF9zdGF0aW9uX2lkLCBzdGFydF9sb25naXR1ZGUsIHN0YXJ0X2xhdGl0dWRlKSAlPiUKICBzdW1tYXJpemUoTUFFID0gbWVhbihhYnMoT2JzZXJ2ZWQtUHJlZGljdGlvbiksIG5hLnJtID0gVFJVRSkpJT4lCmdncGxvdCguKSsKICBnZW9tX3NmKGRhdGEgPSBqY1RyYWN0cywgIGZpbGwgPSAid2hpdGUiKSsKICBnZW9tX3BvaW50KGFlcyh4ID0gc3RhcnRfbG9uZ2l0dWRlLCB5ID0gc3RhcnRfbGF0aXR1ZGUsIGNvbG9yID0gTUFFKSwgCiAgICAgICAgICAgICBmaWxsID0gInRyYW5zcGFyZW50IikrCiAgc2NhbGVfY29sb3JfY29udGludW91cyhsb3cgPSAiIzEyNEU3OCIsIGhpZ2ggPSAiIzZFMEUwQSIsIG5hbWU9ICJNQUUiKSsKIHlsaW0obWluKGJpa2VfY2Vuc3VzJHN0YXJ0X2xhdCksIG1heChiaWtlX2NlbnN1cyRzdGFydF9sYXQpKSsKICB4bGltKG1pbihiaWtlX2NlbnN1cyRzdGFydF9sbmcpLCBtYXgoYmlrZV9jZW5zdXMkc3RhcnRfbG5nKSkgKwogIGxhYnModGl0bGU9Ik1lYW4gQWJzIEVycm9yLCBUZXN0IFNldCwgTW9kZWwgNCIpICsgCiAgdGhlbWUoYXhpcy50ZXh0Lng9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGV4dC55PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpY2tzID1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGl0bGUueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSA5LGZhY2UgPSAiaXRhbGljIiksCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIsIGZhY2UgPSAiYm9sZCIpLAogICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9yZWN0KGNvbG91ciA9ICJncmV5IiwgZmlsbD1OQSwgbGluZXdpZHRoPTAuOCkKICAgICAgICApCgpgYGAKQ29tcGFyaW5nIGFjcm9zcyBzcGFjZSBhbmQgdGltZSwgdGhlIGZpZ3VyZSBiZWxvdyBzaG93cyB0aGF0IG1vZGVsIDQgaGFzIGJlZW4gcHJldHR5IGNvbnNpc3RlbnQgaW4gcHJlZGljdGluZyBudW1iZXIgb2YgdHJpcHMgYWNyb3NzIHRpbWUuIFN0YXRpb25zIHRoYXQgYXJlIHByZWRpY3RlZCB0byBoYXZlIGhpZ2hlciBkZW1hbmQgaW4gdGhlIHdlZWtlbmQgd2lsbCBjb250aW51ZSB0byBiZSBwcmVkaWN0ZWQgdG8gaGF2ZSBoaWdoZXIgZGVtYW5kIGR1cmluZyB0aGUgd2Vla2RheXMuIEVycm9ycyBjYXJyeSB0aHJvdWdob3V0IHRpbWUuIFRoaXMgaW1wbGllcyB0aGF0IHN0YXRpb25zIHRoYXQgaGlzdG9yaWNhbGx5IGhhdmUgaGlnaGVyIGRlbWFuZCBoYXZlIGhpZ2hlciB2YXJpYWJpbGl0eSBpbiB0aGUgbnVtYmVyIG9mIHJpZGVzIGFuZCBhcmUgbW9yZSBkaWZmaWN1bHQgdG8gcHJlZGljdCB3aXRoIHRoZSBjdXJyZW50IHByZWRpY3RvcnMgd2UgaGF2ZS4gSW4gZmFjdCwgb3VyIGN1cnJlbnQgcHJlZGljdG9ycyBvbmx5IGV4cGxhaW5zIDUwLjEyJSBvZiB0aGUgdmFyaWFiaWxpdHkuIFdoYXQncyBwcm9ibGVtYXRpYyBhYm91dCB0aGlzIGlzIHRoYXQgaWYgd2UgY29udGludWUgdG8gaGF2ZSBsYXJnZXIgZXJyb3JzIHdoZW4gcHJlZGljdGluZyBzdGF0aW9ucyB3aXRoIGhpZ2hlciBiaWtlc2hhcmUgZGVtYW5kLCB3ZSB3aWxsIGxhcmdlbHkgZmFpbCB0byBtZWV0IHRoZSBkZW1hbmQgb2YgYmlrZSB1c2VycyBhdCB0aGVzZSBzdGF0aW9ucy4gCgpgYGB7ciBjb21wYXJpbmcgZXJyb3JzIGFjcm9zcyBzcGFjZSBhbmQgdGltZSwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRSwgY2FjaGU9VFJVRX0KCndlZWtfcHJlZGljdGlvbnMgJT4lIAogICAgbXV0YXRlKGludGVydmFsNjAgPSBtYXAoZGF0YSwgcHVsbCwgaW50ZXJ2YWw2MCksCiAgICAgICAgICAgc3RhcnRfc3RhdGlvbl9pZCA9IG1hcChkYXRhLCBwdWxsLCBzdGFydF9zdGF0aW9uX2lkKSwgCiAgICAgICAgICAgc3RhcnRfbGF0aXR1ZGUgPSBtYXAoZGF0YSwgcHVsbCwgc3RhcnRfbGF0KSwgCiAgICAgICAgICAgc3RhcnRfbG9uZ2l0dWRlID0gbWFwKGRhdGEsIHB1bGwsIHN0YXJ0X2xuZyksCiAgICAgICAgICAgZG90dyA9IG1hcChkYXRhLCBwdWxsLCBkb3R3KSApICU+JQogICAgc2VsZWN0KGludGVydmFsNjAsIHN0YXJ0X3N0YXRpb25faWQsIHN0YXJ0X2xvbmdpdHVkZSwgCiAgICAgICAgICAgc3RhcnRfbGF0aXR1ZGUsIE9ic2VydmVkLCBQcmVkaWN0aW9uLCBSZWdyZXNzaW9uLAogICAgICAgICAgIGRvdHcpICU+JQogICAgdW5uZXN0KCkgJT4lCiAgZmlsdGVyKFJlZ3Jlc3Npb24gPT0gIkRUaW1lX1NwYWNlX0ZFX0xhZ3MiKSU+JQogIG11dGF0ZSh3ZWVrZW5kID0gaWZlbHNlKGRvdHcgJWluJSBjKCJTdW4iLCAiU2F0IiksICJXZWVrZW5kIiwgIldlZWtkYXkiKSwKICAgICAgICAgdGltZV9vZl9kYXkgPSBjYXNlX3doZW4oaG91cihpbnRlcnZhbDYwKSA8IDcgfCBob3VyKGludGVydmFsNjApID4gMTggfiAiT3Zlcm5pZ2h0IiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaG91cihpbnRlcnZhbDYwKSA+PSA3ICYgaG91cihpbnRlcnZhbDYwKSA8IDEwIH4gIkFNIFJ1c2giLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBob3VyKGludGVydmFsNjApID49IDEwICYgaG91cihpbnRlcnZhbDYwKSA8IDE1IH4gIk1pZC1EYXkiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBob3VyKGludGVydmFsNjApID49IDE1ICYgaG91cihpbnRlcnZhbDYwKSA8PSAxOCB+ICJQTSBSdXNoIikpICU+JQogIGdyb3VwX2J5KHN0YXJ0X3N0YXRpb25faWQsIHdlZWtlbmQsIHRpbWVfb2ZfZGF5LCBzdGFydF9sb25naXR1ZGUsIHN0YXJ0X2xhdGl0dWRlKSAlPiUKICBzdW1tYXJpemUoTUFFID0gbWVhbihhYnMoT2JzZXJ2ZWQtUHJlZGljdGlvbiksIG5hLnJtID0gVFJVRSkpJT4lCiAgZ2dwbG90KC4pKwogIGdlb21fc2YoZGF0YSA9IGpjVHJhY3RzLCBjb2xvciA9ICJncmV5IiwgZmlsbCA9ICJ0cmFuc3BhcmVudCIpKwogIGdlb21fcG9pbnQoYWVzKHggPSBzdGFydF9sb25naXR1ZGUsIHkgPSBzdGFydF9sYXRpdHVkZSwgY29sb3IgPSBNQUUpLCAKICAgICAgICAgICAgIGZpbGwgPSAidHJhbnNwYXJlbnQiLCBzaXplID0gMC41KSsKICBzY2FsZV9jb2xvcl9jb250aW51b3VzKGxvdyA9ICIjMTI0RTc4IiwgaGlnaCA9ICIjNkUwRTBBIiwgbmFtZT0gIk1BRSIpKwogeWxpbShtaW4oYmlrZV9jZW5zdXMkc3RhcnRfbGF0KSwgbWF4KGJpa2VfY2Vuc3VzJHN0YXJ0X2xhdCkpKwogIHhsaW0obWluKGJpa2VfY2Vuc3VzJHN0YXJ0X2xuZyksIG1heChiaWtlX2NlbnN1cyRzdGFydF9sbmcpKSArCiAgZmFjZXRfZ3JpZCh3ZWVrZW5kfnRpbWVfb2ZfZGF5KSsKICBsYWJzKHRpdGxlPSJNZWFuIEFic29sdXRlIEVycm9ycyBieSBUaW1lLCBUZXN0IFNldCwgTW9kZWwgNCIpICsgCiAgdGhlbWUoYXhpcy50ZXh0Lng9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGV4dC55PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpY2tzID1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGl0bGUueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSA5LGZhY2UgPSAiaXRhbGljIiksCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIsIGZhY2UgPSAiYm9sZCIpLAogICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9yZWN0KGNvbG91ciA9ICJncmV5IiwgZmlsbD1OQSwgbGluZXdpZHRoPTAuOCkKICAgICAgICApCmBgYAoKCiMjIENyb3NzLVZhbGlkYXRpb24KCkZpbmFsbHksIHdlIHBlcmZvcm1lZCAxMDAgY3Jvc3MtdmFsaWRhdGlvbiB0ZXN0cyBmb3IgYWxsIGZpdmUgd2Vla3Mgb2YgZGF0YSB1c2luZyBtb2RlbCA0LiBUaGUgTUFFIHZhbHVlIGlzIDAuNDQsIGluZGljYXRpbmcgdGhhdCBkZXNwaXRlIGJlaW5nIG91ciBiZXN0IHNvbHV0aW9uIHNvIGZhciwgdGhlIG1vZGVsJ3MgcHJlZGljdGlvbiBlcnJvciBjYW4gc3RpbGwgYmUgcmVsYXRpdmVseSBsYXJnZS4gSXQgd2lsbCBhbHNvIGJlIGdvb2QgcHJhY3RpY2UgdG8gY3Jvc3MgdmFsaWRhdGUgYWNyb3NzIHNvY2lvLWVjb25vbWljIGluZGljYXRvcnMgdG8gc2VlIGlmIGJpa2Ugc3RhdGlvbnMnIGRlbWFuZCB3aXRoaW4gY2VydGFpbiBuZWlnaGJvcmhvb2RzIGFyZSBkaXNwcm9wb3J0aW9uYXRlbHkgdW5kZXIvb3ZlciBwcmVkaWN0ZWQsIGJ1dCBvdXIgbW9kZWwgY3VycmVudCBkb2VzIG5vdCB0YWtlIGludG8gYWNjb3VudCBhbnkgc29jaW9lY29ub21pYyB2YXJpYWJsZXMgYXMgcHJlZGljdG9yIHZhcmlhYmxlIGFuZCB0aGUgc2NlbmFyaW8gaW4gSmVyc2V5IENpdHkgaXMgdGhhdCBtb3JlIHN0YXRpb25zIGFyZSBidWlsdCAKCmBgYHtyIGNyb3NzIHZhbGlkYXRpb259CgpmaXRDb250cm9sIDwtIHRyYWluQ29udHJvbChtZXRob2QgPSAiY3YiLCBudW1iZXIgPSAxMDApCgpyZWcuY3YgPC0gdHJhaW4oVHJpcF9Db3VudCB+IHN0YXJ0X3N0YXRpb25fbmFtZSArICBob3VyKGludGVydmFsNjApICsgZG90dyArIFRlbXBlcmF0dXJlICsgV2luZF9TcGVlZCArIGxhZ0hvdXIgKyBsYWcySG91cnMgKyBsYWcxMkhvdXJzICsgbGFnMWRheSArIGxhZ1RyaXAsIGRhdGE9YmlrZS5wYW5lbCwgbWV0aG9kID0gImxtIiwgdHJDb250cm9sID0gZml0Q29udHJvbCwgbmEuYWN0aW9uID0gbmEucGFzcykKCnJlZy5jdiRyZXNhbXBsZSAlPiUgCiAgc3VtbWFyaXNlKE1BRSA9IG1lYW4ocmVnLmN2JHJlc2FtcGxlWywzXSksCiAgICAgICAgICAgIHNkKHJlZy5jdiRyZXNhbXBsZVssM10pCikgJT4lCiAga2JsKGNvbC5uYW1lPWMoJ01lYW4gQWJzb2x1dGUgRXJyb3InLCdTdGFuZGFyZCBEZXZpYXRpb24gb2YgTUFFJykpICU+JQogIGthYmxlX3N0eWxpbmcoYm9vdHN0cmFwX29wdGlvbnMgPSBjKCJzdHJpcGVkIiwgImhvdmVyIiwgImNvbmRlbnNlZCIpKQpgYGAKCgojIENvbmNsdXNpb24KCkluIGNvbmNsdXNpb24sIHRoaXMgcmVwb3J0IGFkZHJlc3NlcyB0aGUgY3JpdGljYWwgbmVlZCBmb3IgZWZmZWN0aXZlIGJpa2VzaGFyZSBtYW5hZ2VtZW50IHN0cmF0ZWdpZXMgaW4gSmVyc2V5IENpdHkgYnkgZW1wbG95aW5nIG1hY2hpbmUgbGVhcm5pbmcgdGVjaG5pcXVlcy4gT3VyIGFuYWx5c2lzIHNob3dzIHRoYXQgc3BhdGlhbCwgdGVtcG9yYWwsIGFuZCB3ZWF0aGVyLXJlbGF0ZWQgZmFjdG9ycyBhbGwgY29sbGVjdGl2ZWx5IGNvbnRyaWJ1dGUgdG8gdGhlIHZhcmlhdGlvbnMgaW4gYmlrZXNoYXJlIGRlbWFuZHMuIFdlIGZvdW5kIHRoYXQgaXQgaXMgbW9yZSBhY2N1cmF0ZSB0byBwcmVkaWN0IGJpa2VzaGFyZSBkZW1hbmQgYXQgYSBwYXJ0aWN1bGFyIGhvdXIgYmFzZWQgb24gdGhlIG51bWJlciBvZiB0cmlwcyBhdCB0aGUgaG91ciBpbW1lZGlhdGVseSBiZWZvcmUgYW5kIGFmdGVyLCBkdXJpbmcgdGhlIHNhbWUgaG91ciBhIGRheSBiZWZvcmUsIGFzIHdlbGwgYXMgdGhlIG51bWJlciBvZiB0cmlwcyBhdCBuZWFyYnkgc3RhdGlvbnMuIFdlIGFsc28gZm91bmQgdGhhdCBiaWtlc2hhcmUgZGVtYW5kIGFyZSB0aGUgaGlnaGVzdCBkdXJpbmcgd2luZHkgYW5kIGhvdCBkYXlzIGluIEplcnNleSBDaXR5IGluIEF1Z3VzdC4gIFRoZSBQTSBydXNoIGFuZCBvdmVybmlnaHQgZHVyaW5nIHdlZWtkYXlzIHNlZSB0aGUgaGlnaGVzdCBkZW1hbmQgd2hpbGUgdGhlIFBNIHJ1c2ggZHVyaW5nIHdlZWtlbmQgc2VlcyB0aGUgaGlnaGVzdCBkZW1hbmQuIE9uIHRvcCBvZiB0aGF0LCByZXNpZGVudHMgb2YgSmVyc2V5IENpdHkgdXNlcyBDaXRpIEJpa2UgaGVhdmlseSBmb3Igd29yayBjb21tdXRlIHRvIE5ldyBZb3JrIENpdHksIHdoaWNoIGhhcyBtYWRlIHN0YXRpb25zIGFsb25nIHRoZSBIdWRzb24gUml2ZXIsIHBhcnRpY3VsYXIgdGhvc2UgY2xvc2VyIHRvIGZlcnJ5IHN0YXRpb25zLCB0aGUgbW9zdCBwb3B1bGFyLiAKCldlIGRpZCBzb21lIGZ1cnRoZXIgaW52ZXN0aWdhdGlvbnMgaW50byBzdGF0aW9ucyB3aXRoIGdyZWF0ZXN0IGJpa2VzaGFyZSBkZW1hbmQuICoqR3JvdmUgU3QgUEFUSCoqIHN0YXRpb24sIHdoaWNoIG9ic2VydmVzIGEgdG90YWwgb2YgMTExNyByaWRlcyBkdXJpbmcgUE0gcnVzaCBvbiB3ZWVrZGF5IGFuZCA4MDEgcmlkZXMgb3Zlcm5pZ2h0IGluIEF1Z3VzdCwgaXMgbG9jYXRlZCBpbiB0aGUgYnVzeSBkb3dudG93biBKZXJzZXkgQ2l0eS4qKkhvYm9rZW4gVGVybWluYWwgLSBSaXZlciBTdCAmIEh1ZHNvbiBQbCoqLCB3aGljaCBvYnNlcnZlcyBhIHRvdGFsIG9mIDg2NCByaWRlcyBkdXJpbmcgUE0gcnVzaCBvbiB3ZWVrZGF5LCBpcyBsb2NhdGVkIHJpZ2h0IG5leHQgdG8gSG9ib2tlbi9OSiBUcmFuc2l0IFRlcm1pbmFsIGFuZCBhbG9uZyB0aGUgSHVkc29uIFJpdmVyLiAqKlNvdXRoIFdhdGVyZnJvbnQgV2Fsa3dheSAtIFNpbmF0cmEgRHIgJiAxIFN0Kiogc3RhdGlvbiwgd2hpY2ggb2JzZXJ2ZXMgMzE5IHJpZGVzIG92ZXJuaWdodCBvbiBhIHdlZWtlbmQgYW5kIDczMSByaWRlcyBkdXJpbmcgUE0gcnVzaCBvbiBhIHdlZWtkYXkgaXMgd2l0aGluIHdhbGtpbmcgZGlzdGFuY2UgdG8gdGhlIE5KIFRyYW5zaXQgVGVybWluYWwgYW5kIGF0IHRoZSBlbnRyYW5jZSB0byBQaWVyIEEgUGFyaywgYSB3YXRlcmZyb250IHJlY3JlYXRpb24gYXJlYSBpbiBKZXJzZXkgQ2l0eS4gVGhlcmVmb3JlLCB3ZSBtYXkgY29uY2x1ZGUgdGhhdCBzdGF0aW9ucyByZWNlaXZlIGhpZ2hlciBkZW1hbmQgYmVjYXVzZSAxKSB0aGVyZSdzIG1vcmUgd29ya2Vycy92aXNpdG9ycyBpbiBkb3dudG93biBhcmVhIHdobyBuZWVkIHRvIGJpa2UgdG8gZ2V0IGFyb3VuZC4gMikgcGVvcGxlIG5lZWQgdG8gY29tbXV0ZSB0byBOZXcgWW9yayBmb3Igd29yayBhbmQgbmVlZCB0byBiaWtlIHRvIGZlcnJ5L3RyYW5zaXQgc3RhdGlvbnMsIDMpIHdhdGVyZnJvbnQgcmVjcmVhdGlvbiBhcmVhcyBpcyBncmVhdCBmb3IgYmlraW5nIGFjdGl2aXRpZXMgb3ZlciB0aGUgd2Vla2VuZC4gTG9jYWwgZ292ZXJubWVudCBzaG91bGQgaW5kZWVkIHBheSBtb3JlIGF0dGVudGlvbiB0byB0aGUgInN1cHBseSIgYW5kICJkZW1hbmQgb2YgYmlrZXMgYXQgdGhvc2Uga2luZCBvZiBzdGF0aW9ucyBhbmQgcmViYWxhbmNlIGJpa2VzIGlmIG5lZWRlZCBiZS4gCgpPdXIgbW9kZWwgaXMgYWJsZSB0byBjYXB0dXJlIG1vcmUgdGhhbiBoYWxmIG9mIHRoZSBiaWtlc2hhcmUgZGVtYW5kIGluIEplcnNleSBDaXR5LCB0aG91Z2ggc3RpbGwsIGl0IHJlcXVpcmVzIGltcHJvdmVtZW50IHRvIGNhcHR1cmUgbW9yZSB2YXJpYWJpbGl0eS4gT3RoZXJ3aXNlLCBpdCB3aWxsIGZhaWwgdG8gbWVldCB0aGUgbmVlZHMgb2YgSmVyc2V5IENpdHkgcmVzaWRlbnRzIGFuZCAgaW5jcmVhc2UgdGhlIGRpZmZpY3VsdGllcyBvZiByZWJhbGFuY2luZyBiaWtlcy4gTmV2ZXJ0aGVsZXNzLCB0aGUgdHJhbnNwYXJlbmN5IGFuZCBzaW1wbGljaXR5IG9mIHRoaXMgb3Blbi1zb3VyY2UgYXBwcm9hY2ggcHJvdmlkZSBhIHZhbHVhYmxlIHJlc291cmNlIGZvciBiaWtlc2hhcmUgcHJvZ3JhbXMgdG8gZW5oYW5jZSB1c2VyIGV4cGVyaWVuY2UgYW5kIG1haW50YWluIGEgaGFybW9uaW91cyBiaWtlIGRpc3RyaWJ1dGlvbiBhY3Jvc3Mgc3RhdGlvbnMuIEFzIHVyYmFuIG1vYmlsaXR5IGNvbnRpbnVlcyB0byBldm9sdmUsIHRoZSBhcHBsaWNhdGlvbiBvZiBzdWNoIHByZWRpY3RpdmUgbW9kZWxzIGJlY29tZXMgaW5jcmVhc2luZ2x5IGVzc2VudGlhbCBmb3Igc3VzdGFpbmFibGUgYW5kIGVmZmljaWVudCB0cmFuc3BvcnRhdGlvbiBzb2x1dGlvbnMuCgo=