2 Setup
library(tidyverse)
library(sf)
library(lubridate)
library(tigris)
library(tidycensus)
library(viridis)
library(riem)
library(gridExtra)
library(knitr)
library(kableExtra)
library(RSocrata)
library(caret)
library(gganimate)
library(gifski)
plotTheme <- theme(
plot.title =element_text(size=12),
plot.subtitle = element_text(size=8),
plot.caption = element_text(size = 6),
axis.text.x = element_text(size = 10, angle = 45, hjust = 1),
axis.text.y = element_text(size = 10),
axis.title.y = element_text(size = 10),
# Set the entire chart region to blank
panel.background=element_blank(),
plot.background=element_blank(),
#panel.border=element_rect(colour="#F0F0F0"),
# Format the grid
panel.grid.major=element_line(colour="#D0D0D0",size=.2),
axis.ticks=element_blank())
mapTheme <- theme(plot.title =element_text(size=12),
plot.subtitle = element_text(size=8),
plot.caption = element_text(size = 6),
axis.line=element_blank(),
axis.text.x=element_blank(),
axis.text.y=element_blank(),
axis.ticks=element_blank(),
axis.title.x=element_blank(),
axis.title.y=element_blank(),
panel.background=element_blank(),
panel.border=element_blank(),
panel.grid.major=element_line(colour = 'transparent'),
panel.grid.minor=element_blank(),
legend.direction = "vertical",
legend.position = "right",
plot.margin = margin(1, 1, 1, 1, 'cm'),
legend.key.height = unit(1, "cm"), legend.key.width = unit(0.2, "cm"))
palette5 <- c("#eff3ff","#bdd7e7","#6baed6","#3182bd","#08519c")
palette4 <- c("#D2FBD4","#92BCAB","#527D82","#123F5A")
palette2 <- c("#6baed6","#08519c")
palette1 <- c("#b6cde3","#58a2e8","#1f66ab","#073b6b")
Load census API key to grab stuff from tidycensus
2.1. Import Data
Let’s read in the month of May, Usually, May has a fairly pleasant
temperature range in Philly, so we may see some leisure trips as well as
commutes. https://kiosks.bicycletransit.workers.dev/phl
dat <- read.csv("https://raw.githubusercontent.com/RumRon/HW_UP_IP/main/R/data4HW5/4.25-5.29.csv")
dat <- dat %>%
rename(., from_longitude = start_lon) %>%
rename(., from_latitude = start_lat)%>%
rename(., to_latitude = end_lat ) %>%
rename(., to_longitude = end_lon) %>%
rename(., from_station_id= start_station) %>%
rename(., to_station_id = end_station)
station_name <- read.csv("https://raw.githubusercontent.com/RumRon/HW_UP_IP/main/R/data4HW5/indego_stations.csv")
station_name<- station_name %>%
select("Station_ID","Station_Name")
dat <- left_join(dat, station_name, by = c("from_station_id" = "Station_ID")) %>%
rename(., from_station_name= Station_Name)
dat <- left_join(dat, station_name, by = c("to_station_id" = "Station_ID")) %>%
rename(., to_station_name= Station_Name)
Let’s use some date parsing to bin the data by 15 and 60 minute
intervals by rounding and take a look at our data to see the format and
names of all of our columns using the glimpse command.
dat$start_time <- strptime(dat$start_time, format = "%m/%d/%Y %H:%M")
dat$end_time <- strptime(dat$end_time, format = "%m/%d/%Y %H:%M")
dat2 <- dat %>%
mutate(interval60 = floor_date(ymd_hms(start_time), unit = "hour"),
interval15 = floor_date(ymd_hms(start_time), unit = "15 mins"),
week = week(interval60),
dotw = wday(interval60, label=TRUE))
glimpse(dat2)
## Rows: 96,034
## Columns: 21
## $ trip_id <int> 475034210, 474925307, 474976334, 474925306, 474925…
## $ duration <int> 1, 17, 357, 12, 10, 4, 10, 11, 1, 4, 3, 4, 10, 173…
## $ start_time <dttm> 2022-04-25 00:05:00, 2022-04-25 00:05:00, 2022-04…
## $ end_time <dttm> 2022-04-25 00:06:00, 2022-04-25 00:22:00, 2022-04…
## $ from_station_id <int> 3190, 3190, 3190, 3212, 3026, 3033, 3006, 3167, 32…
## $ from_latitude <dbl> 39.94892, 39.94892, 39.94892, 39.96383, 39.94182, …
## $ from_longitude <dbl> -75.16991, -75.16991, -75.16991, -75.18177, -75.14…
## $ to_station_id <int> 3000, 3021, 3205, 3111, 3100, 3046, 3066, 3061, 32…
## $ to_latitude <dbl> NA, 39.95390, 39.95405, 39.97779, 39.92777, 39.950…
## $ to_longitude <dbl> NA, -75.16902, -75.16783, -75.21323, -75.15103, -7…
## $ bike_id <int> 11828, 14639, 3436, 19327, 14500, 5198, 19879, 348…
## $ plan_duration <int> 30, 30, 1, 30, 30, 365, 30, 365, 365, 365, 30, 365…
## $ trip_route_category <chr> "One Way", "One Way", "One Way", "One Way", "One W…
## $ passholder_type <chr> "Indego30", "Indego30", "Day Pass", "Indego30", "I…
## $ bike_type <chr> "standard", "standard", "standard", "electric", "s…
## $ from_station_name <chr> "17th & Locust", "17th & Locust", "17th & Locust",…
## $ to_station_name <chr> "Virtual Station", "18th & JFK", "17th & JFK", "Pa…
## $ interval60 <dttm> 2022-04-25 00:00:00, 2022-04-25 00:00:00, 2022-04…
## $ interval15 <dttm> 2022-04-25 00:00:00, 2022-04-25 00:00:00, 2022-04…
## $ week <dbl> 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17…
## $ dotw <ord> Mon, Mon, Mon, Mon, Mon, Mon, Mon, Mon, Mon, Mon, …
2.2. Import Census Info
Using the tidycensus package, we can download census
geography and variables. These are used to test generalizeability later.
We extract the tracts for mapping and joining purposes - creating an
sf object that consists only of GEOIDs and geometries.
We add the spatial information to our rideshare data as origin and
destination data, first joining the origin station, then the destination
station to our census data.
phillyCensus <-
get_acs(geography = "tract",
variables = c("B01003_001", "B19013_001",
"B02001_002", "B08013_001",
"B08012_001", "B08301_001",
"B08301_010", "B01002_001"),
year = 2020,
state = "PA",
geometry = TRUE,
county=c("Philadelphia"),
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)
phillyTracts <-
phillyCensus %>%
as.data.frame() %>%
distinct(GEOID, .keep_all = TRUE) %>%
select(GEOID, geometry) %>%
st_sf
dat_census <- st_join(dat2 %>%
filter(is.na(from_longitude) == FALSE &
is.na(from_latitude) == FALSE &
is.na(to_latitude) == FALSE &
is.na(to_longitude) == FALSE) %>%
st_as_sf(., coords = c("from_longitude", "from_latitude"), crs = 4326),
phillyTracts %>%
st_transform(crs=4326),
join=st_intersects,
left = TRUE) %>%
rename(Origin.Tract = GEOID) %>%
mutate(from_longitude = unlist(map(geometry, 1)),
from_latitude = unlist(map(geometry, 2)))%>%
as.data.frame() %>%
select(-geometry)%>%
st_as_sf(., coords = c("to_longitude", "to_latitude"), crs = 4326) %>%
st_join(., phillyTracts %>%
st_transform(crs=4326),
join=st_intersects,
left = TRUE) %>%
rename(Destination.Tract = GEOID) %>%
mutate(to_longitude = unlist(map(geometry, 1)),
to_latitude = unlist(map(geometry, 2)))%>%
as.data.frame() %>%
select(-geometry)
2.3. Import Weather Data
Import weather data from Philadelphia airport (code PHL) using
riem_measures. We can mutate the data to get
temperature, wind speed, precipitation on an hourly basis and plot the
temperature and precipitation trends over our study period.
These data can also be categorized as a part of an exploration of the
relationship between your independent and dependent variables,
e.g. “does wind appear to affect ridership during rush hour?”
weather.Panel <-
riem_measures(station = "PHL", date_start = "2022-04-25", date_end = "2022-05-30") %>%
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))
glimpse(weather.Panel)
## Rows: 840
## Columns: 4
## $ interval60 <dttm> 2022-04-25 00:00:00, 2022-04-25 01:00:00, 2022-04-25 02…
## $ Temperature <dbl> 55.0, 54.0, 52.0, 51.1, 50.0, 48.0, 48.0, 48.0, 46.0, 46…
## $ Precipitation <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,…
## $ Wind_Speed <dbl> 12, 11, 10, 8, 8, 7, 7, 7, 7, 7, 8, 9, 9, 10, 10, 10, 11…
grid.arrange(
ggplot(weather.Panel, aes(interval60,Precipitation)) + geom_line() +
labs(title="Percipitation", x="Hour", y="Perecipitation") + plotTheme,
ggplot(weather.Panel, aes(interval60,Wind_Speed)) + geom_line() +
labs(title="Wind Speed", x="Hour", y="Wind Speed") + plotTheme,
ggplot(weather.Panel, aes(interval60,Temperature)) + geom_line() +
labs(title="Temperature", x="Hour", y="Temperature") + plotTheme,
top="Weather Data - Philadelphia PHL - May, 2022")

2.4. Import Amenity Data
Import school data as amenity.
library(FNN)
school <-
st_read("https://raw.githubusercontent.com/RumRon/HW_UP_IP/main/R/Schools.geojson") %>%
st_transform(crs=4326)
root.dir = "https://raw.githubusercontent.com/urbanSpatial/Public-Policy-Analytics-Landing/master/DATA/"
source("https://raw.githubusercontent.com/urbanSpatial/Public-Policy-Analytics-Landing/master/functions.r")
3. Describe and Explore the Data
Examining the time and frequency components of our data.
3.1. Overall Time Pattern
First, we look at the overall time pattern - there is clearly a daily
periodicity and there are lull periods on weekends. Notice that the
weekend near the 28th of May (Memorial Day) doesn’t have the same dip in
activity.
ggplot(dat_census %>%
group_by(interval60) %>%
tally())+
geom_line(aes(x = interval60, y = n))+
labs(title="Bike share per hr. Philadelphia, May, 2022",
x="Date",
y="Number of trips")+
plotTheme()

3.2. Examine the Distribution of Trip Volume by Station for
Different Times of the Day
We clearly have a few high volume periods but mostly low volume. Our
data must consist of a lot of low demand station/hours and a few high
demand station hours.
There’s a possibility we may have to treat these as count data here,
which means running Poisson regression. Then again, we might have enough
of the higher counts in our high volume times and stations, that we
should really be building a linear model to accomodate our actual volume
and not worry about the low trip times/stations.
We can also track the daily trends in ridership by day of the week
and weekend versus weekday, to see what temporal patterns we’d like to
control for.
dat_census %>%
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"))%>%
group_by(interval60, from_station_name, time_of_day) %>%
tally()%>%
group_by(from_station_name, time_of_day)%>%
summarize(mean_trips = mean(n))%>%
ggplot()+
geom_histogram(aes(mean_trips), binwidth = 1)+
labs(title="Mean Number of Hourly Trips Per Station. Phialdelphia, May, 2022",
x="Number of trips",
y="Frequency")+
facet_wrap(~time_of_day)+
plotTheme()

ggplot(dat_census %>%
group_by(interval60, from_station_name) %>%
tally())+
geom_histogram(aes(n), binwidth = 5)+
labs(title="Bike share trips per hr by station. Philadelphia, May, 2022",
x="Trip Counts",
y="Number of Stations")+
plotTheme()

ggplot(dat_census %>% mutate(hour = hour(start_time)))+
geom_freqpoly(aes(hour, color = dotw), binwidth = 1)+
labs(title="Bike share trips in Philadelphia, by day of the week, May, 2022",
x="Hour",
y="Trip Counts")+
plotTheme()

ggplot(dat_census %>%
mutate(hour = hour(start_time),
weekend = ifelse(dotw %in% c("Sun", "Sat"), "Weekend", "Weekday")))+
geom_freqpoly(aes(hour, color = weekend), binwidth = 1)+
labs(title="Bike share in Philadelphia - weekend vs weekday, May, 2022",
x="Hour",
y="Trip Counts")+
plotTheme()

ggplot()+
geom_sf(data = phillyTracts %>%
st_transform(crs=4326))+
geom_point(data = dat_census %>%
mutate(hour = hour(start_time),
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(from_station_id, from_latitude, from_longitude, weekend, time_of_day) %>%
tally(),
aes(x=from_longitude, y = from_latitude, color = n),
fill = "transparent", alpha = 0.4, size = 1)+
scale_colour_viridis(direction = -1,
discrete = FALSE, option = "D")+
ylim(min(dat_census$from_latitude), max(dat_census$from_latitude))+
xlim(min(dat_census$from_longitude), max(dat_census$from_longitude))+
facet_grid(weekend ~ time_of_day)+
labs(title="Bike share trips per hr by station. Philadlephia, May, 2022")+
mapTheme()

3.3 Create Space-Time Panel
First we have to make sure each unique station and hour/day
combo exists in our data set. This is done in order to create a
“panel” (e.g. a time-series) data set where each time period in the
study is represented by a row - whether an observation took place then
or not. So if a station didn’t have any trips originating from it at a
given hour, we still need a zero in that spot in the panel.
We start by determining the maximum number of combinations.
Then we compare that to the actual number of combinations. We create
an empty data frame study.panel, is created that has each
unique space/time observations. This is done using the expand.grid
function and unique. Along the way, we keep tabs on the number of rows
our data have - nrow shows that the count is still
correct.
We then join the station name, tract and lat/lon (some have multiple
lat lon info, so we just take the first one of each using
group_by and slice).
length(unique(dat_census$interval60)) * length(unique(dat_census$from_station_id))
## [1] 148452
study.panel <-
expand.grid(interval60=unique(dat_census$interval60),
from_station_id = unique(dat_census$from_station_id)) %>%
left_join(., dat_census %>%
select(from_station_id, from_station_name, Origin.Tract, from_longitude, from_latitude )%>%
distinct() %>%
group_by(from_station_id) %>%
slice(1))
nrow(study.panel)
## [1] 148452
We create the full panel by summarizing counts by station for each
time interval, keep census info and lat/lon information along for
joining later to other data. We remove data for station IDs that are
FALSE.
ride.panel <-
dat_census %>%
mutate(Trip_Counter = 1) %>%
right_join(study.panel) %>%
group_by(interval60, from_station_id, from_station_name, Origin.Tract, from_longitude, from_latitude) %>%
summarize(Trip_Count = sum(Trip_Counter, na.rm=T)) %>%
left_join(weather.Panel) %>%
ungroup() %>%
filter(is.na(from_station_id) == FALSE) %>%
mutate(week = week(interval60),
dotw = wday(interval60, label = TRUE)) %>%
filter(is.na(Origin.Tract) == FALSE)
ride.panel <-
left_join(ride.panel, phillyCensus %>%
as.data.frame() %>%
select(-geometry), by = c("Origin.Tract" = "GEOID"))
from <- ride.panel %>%
dplyr::select(from_longitude, from_latitude)
to <- st_coordinates(school)
ride.panel <-
ride.panel %>%
mutate(
school_dist = nn_function(from, to, 3)
)
3.4. Create time lags
Creating time lag variables will add additional nuance about the
demand during a given time period - hours before and during that
day.
We can also try to control for the effects of holidays that disrupt
the expected demand during a given weekend or weekday. We have a holiday
on May 28 - Memorial Day. For that three day weekend we could use some
dummy variables indicating temporal proximity to the holiday.
The demand right now should be relatively similar to the demand
tomorrow at this time, and to the demand an hour from now, but twelve
hours from now, we likely expect the opposite in terms of demand.
ride.panel <-
ride.panel %>%
arrange(from_station_name, 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),
holiday = ifelse(yday(interval60) == 148,1,0)) %>%
mutate(day = yday(interval60)) %>%
mutate(holidayLag = case_when(dplyr::lag(holiday, 1) == 1 ~ "PlusOneDay",
dplyr::lag(holiday, 2) == 1 ~ "PlustTwoDays",
dplyr::lag(holiday, 3) == 1 ~ "PlustThreeDays",
dplyr::lead(holiday, 1) == 1 ~ "MinusOneDay",
dplyr::lead(holiday, 2) == 1 ~ "MinusTwoDays",
dplyr::lead(holiday, 3) == 1 ~ "MinusThreeDays"),
#holidayLag = replace_na(holidayLag, 0))
holidayLag = ifelse(is.na(holidayLag) == TRUE, 0, holidayLag))
as.data.frame(ride.panel) %>%
group_by(interval60) %>%
summarise_at(vars(starts_with("lag"), "Trip_Count"), mean, na.rm = TRUE) %>%
gather(Variable, Value, -interval60, -Trip_Count) %>%
mutate(Variable = factor(Variable, levels=c("lagHour","lag2Hours","lag3Hours","lag4Hours",
"lag12Hours","lag1day","school_dist")))%>%
group_by(Variable) %>%
summarize(correlation = round(cor(Value, Trip_Count),2))
## # A tibble: 6 × 2
## Variable correlation
## <fct> <dbl>
## 1 lagHour 0.89
## 2 lag2Hours 0.71
## 3 lag3Hours 0.52
## 4 lag4Hours 0.33
## 5 lag12Hours -0.52
## 6 lag1day 0.79
4. Run Models
We split our data into a training and a test set. We create five
linear models using the lm funtion. Sometimes, for data
such as these, Poisson distributions, designed for modeling counts,
might be appropriate. I’ll spare you the effort - linear models work
better with this particular data set.
We create the models using our training data ride.Train.
The first models include only temporal controls, but the later ones
contain all of our lag information.
ride.Train <- ride.panel %>% filter(., week >= 20)
ride.Test <- ride.panel %>% filter(., week < 20)
reg1 <-
lm(Trip_Count ~ hour(interval60) + dotw + Temperature, data=ride.Train)
reg2 <-
lm(Trip_Count ~ from_station_name + dotw + Temperature, data=ride.Train)
reg3 <-
lm(Trip_Count ~ from_station_name + hour(interval60) + dotw + Temperature + Precipitation,
data=ride.Train)
reg4 <-
lm(Trip_Count ~ from_station_name + hour(interval60) + dotw + Temperature + Precipitation +
lagHour + lag2Hours +lag3Hours + lag12Hours + lag1day,
data=ride.Train)
reg5 <-
lm(Trip_Count ~ from_station_name + hour(interval60) + dotw +
Temperature + Precipitation + lagHour + lag2Hours +
lag3Hours +lag12Hours + lag1day + holidayLag + holiday + school_dist,
data=ride.Train)
4.1. Predict for test data
When models have finished running, create a nested data frame of test
data by week. Nested data is common in most other programming languages.
For instance, the javascript object notation file format (aka JSON) is
highly nested.
Nesting means that instead of merely having a “flat” file consisting
of rows and columns, we have a matrix of other objects - imagine each
cell in a matrix containing another matrix within it, or a list, or a
list of lists.
The purrr package is designed to map
functions through nested data structures. This concept is important -
think of map as visiting each dataframe in a nested data
set and applies a function to it.
We create a function called model_pred which we can then
map onto each data frame in our nested structure.
This function is called in the code below in a few ways, one way is
like so:
map(.x = data, fit = name_of_your_regression, .f = model_pred).
Here’s the important bit - the argument fit takes the name
of a regression you have created that you want to use to make
predictions, and the .f argument takes a function, in this
case model_pred, which we create in order to simply execute
the predict function.
ride.Test.weekNest <-
ride.Test %>%
nest(-week)
model_pred <- function(dat, fit){
pred <- predict(fit, newdata = dat)}
When we run our predictions and summarize our results, we are going
to have some NA data - recall we have some lag information that will
necessarily trip up the model at the margins of the time frame.
week_predictions <-
ride.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_timeLags = map(.x = data, fit = reg4, .f = model_pred),
ETime_Space_FE_timeLags_holidayLags = map(.x = data, fit = reg5, .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))
## Warning in predict.lm(fit, newdata = dat): prediction from a rank-deficient fit
## may be misleading
## Warning in predict.lm(fit, newdata = dat): prediction from a rank-deficient fit
## may be misleading
## Warning in predict.lm(fit, newdata = dat): prediction from a rank-deficient fit
## may be misleading
week_predictions
## # A tibble: 15 × 8
## week data Regression Prediction Observed Absolute_Error MAE sd_AE
## <dbl> <list> <chr> <list> <list> <list> <dbl> <dbl>
## 1 17 <tibble> ATime_FE <dbl> <dbl> <dbl [21,360]> 0.780 0.771
## 2 18 <tibble> ATime_FE <dbl> <dbl> <dbl [29,904]> 0.801 0.870
## 3 19 <tibble> ATime_FE <dbl> <dbl> <dbl [29,904]> 0.779 0.829
## 4 17 <tibble> BSpace_FE <dbl> <dbl> <dbl [21,360]> 0.673 0.819
## 5 18 <tibble> BSpace_FE <dbl> <dbl> <dbl [29,904]> 0.707 0.883
## 6 19 <tibble> BSpace_FE <dbl> <dbl> <dbl [29,904]> 0.683 0.826
## 7 17 <tibble> CTime_Space_FE <dbl> <dbl> <dbl [21,360]> 0.715 0.738
## 8 18 <tibble> CTime_Space_FE <dbl> <dbl> <dbl [29,904]> 0.737 0.825
## 9 19 <tibble> CTime_Space_FE <dbl> <dbl> <dbl [29,904]> 0.721 0.789
## 10 17 <tibble> DTime_Space_FE… <dbl> <dbl> <dbl [21,360]> 0.657 0.695
## 11 18 <tibble> DTime_Space_FE… <dbl> <dbl> <dbl [29,904]> 0.670 0.765
## 12 19 <tibble> DTime_Space_FE… <dbl> <dbl> <dbl [29,904]> 0.648 0.732
## 13 17 <tibble> ETime_Space_FE… <dbl> <dbl> <dbl [21,360]> 0.661 0.692
## 14 18 <tibble> ETime_Space_FE… <dbl> <dbl> <dbl [29,904]> 0.672 0.766
## 15 19 <tibble> ETime_Space_FE… <dbl> <dbl> <dbl [29,904]> 0.648 0.731
5. Accuracy
5.1 MAE
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 = palette5) +
labs(title = "Mean Absolute Errors by model specification and week") +
plotTheme()
We could see that regression D and E has relatively low MAE by model
specification and week.
5.2 reg5 seems to have the best goodness of fit
generally.
week_predictions %>%
mutate(interval60 = map(data, pull, interval60),
from_station_id = map(data, pull, from_station_id)) %>%
dplyr::select(interval60, from_station_id, Observed, Prediction, Regression) %>%
unnest() %>%
gather(Variable, Value, -Regression, -interval60, -from_station_id) %>%
group_by(Regression, Variable, interval60) %>%
summarize(Value = sum(Value)) %>%
ggplot(aes(interval60, Value, colour=Variable)) +
geom_line(size = 1.1) +
facet_wrap(~Regression, ncol=1) +
labs(title = "Predicted/Observed bike share time series", subtitle = "Philadelphia; A test set of 2 weeks", x = "Hour", y= "Station Trips") +
plotTheme()

week_predictions %>%
mutate(interval60 = map(data, pull, interval60),
from_station_id = map(data, pull, from_station_id),
from_latitude = map(data, pull, from_latitude),
from_longitude = map(data, pull, from_longitude)) %>%
select(interval60, from_station_id, from_longitude, from_latitude, Observed, Prediction, Regression) %>%
unnest() %>%
filter(Regression == "ETime_Space_FE_timeLags_holidayLags") %>%
group_by(from_station_id, from_longitude, from_latitude) %>%
summarize(MAE = mean(abs(Observed-Prediction), na.rm = TRUE))%>%
ggplot(.)+
geom_sf(data = phillyCensus, color = "grey", fill = "transparent")+
geom_point(aes(x = from_longitude, y = from_latitude, color = MAE),
fill = "transparent", alpha = 0.4)+
scale_colour_viridis(direction = -1,
discrete = FALSE, option = "D")+
ylim(min(dat_census$from_latitude), max(dat_census$from_latitude))+
xlim(min(dat_census$from_longitude), max(dat_census$from_longitude))+
labs(title="Mean Abs Error, Test Set, Model 5")+
mapTheme()
There are some MAE around downtown area and spread to University City
and nearby areas.
5.3. Space-Time Error Evaluation
If we plot observed vs. predicted for different times of day during
the week and weekend, some patterns begin to emerge.
week_predictions %>%
mutate(interval60 = map(data, pull, interval60),
from_station_id = map(data, pull, from_station_id),
from_latitude = map(data, pull, from_latitude),
from_longitude = map(data, pull, from_longitude),
dotw = map(data, pull, dotw)) %>%
select(interval60, from_station_id, from_longitude,
from_latitude, Observed, Prediction, Regression,
dotw) %>%
unnest() %>%
filter(Regression == "ETime_Space_FE_timeLags_holidayLags")%>%
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"))%>%
ggplot()+
geom_point(aes(x= Observed, y = Prediction))+
geom_smooth(aes(x= Observed, y= Prediction), method = "lm", se = FALSE, color = "red")+
geom_abline(slope = 1, intercept = 0)+
facet_grid(time_of_day~weekend)+
labs(title="Observed vs Predicted",
x="Observed trips",
y="Predicted trips")+
plotTheme()

Weekend’s morning is the hardest to predict.
5.4. MAE map by weekend/weekday and time of day.
week_predictions %>%
mutate(interval60 = map(data, pull, interval60),
from_station_id = map(data, pull, from_station_id),
from_latitude = map(data, pull, from_latitude),
from_longitude = map(data, pull, from_longitude),
dotw = map(data, pull, dotw) ) %>%
select(interval60, from_station_id, from_longitude,
from_latitude, Observed, Prediction, Regression,
dotw) %>%
unnest() %>%
filter(Regression == "ETime_Space_FE_timeLags_holidayLags")%>%
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(from_station_id, weekend, time_of_day, from_longitude, from_latitude) %>%
summarize(MAE = mean(abs(Observed-Prediction), na.rm = TRUE))%>%
ggplot(.)+
geom_sf(data = phillyCensus, color = "grey", fill = "transparent")+
geom_point(aes(x = from_longitude, y = from_latitude, color = MAE),
fill = "transparent", size = 0.5, alpha = 0.4)+
scale_colour_viridis(direction = -1,
discrete = FALSE, option = "D")+
ylim(min(dat_census$from_latitude), max(dat_census$from_latitude))+
xlim(min(dat_census$from_longitude), max(dat_census$from_longitude))+
facet_grid(weekend~time_of_day)+
labs(title="Mean Absolute Errors, Test Set")+
mapTheme()

Seems like errors are concentrated in downtown areas. The pattern has
been visualized both as the scatter plot and the spatial map. The
ridership is high during the evening rush as compared to the morning
rush, hence why the errors are higher in the weekday predictions.
5.5 Errors as a function of socio-economic variables
week_predictions %>%
mutate(interval60 = map(data, pull, interval60),
from_station_id = map(data, pull, from_station_id),
from_latitude = map(data, pull, from_latitude),
from_longitude = map(data, pull, from_longitude),
dotw = map(data, pull, dotw),
Percent_Taking_Public_Trans = map(data, pull, Percent_Taking_Public_Trans),
Med_Inc = map(data, pull, Med_Inc),
Med_Age = map(data, pull, Med_Age),
Mean_Commute_Time = map(data,pull,Mean_Commute_Time),
Percent_White = map(data, pull, Percent_White)) %>%
select(interval60, from_station_id, from_longitude,
from_latitude, Observed, Prediction, Regression,
dotw, Med_Inc, Med_Age, Mean_Commute_Time,Percent_Taking_Public_Trans) %>%
unnest() %>%
filter(Regression == "ETime_Space_FE_timeLags_holidayLags")%>%
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")) %>%
filter(time_of_day == "AM Rush") %>%
group_by(from_station_id, Med_Inc, Percent_Taking_Public_Trans, Mean_Commute_Time) %>%
summarize(MAE = mean(abs(Observed-Prediction), na.rm = TRUE))%>%
gather(-from_station_id, -MAE, key = "variable", value = "value")%>%
ggplot(.)+
#geom_sf(data = phillyCensus, color = "grey", fill = "transparent")+
geom_point(aes(x = value, y = MAE), alpha = 0.4)+
geom_smooth(aes(x = value, y = MAE), method = "lm", se= FALSE)+
facet_wrap(~variable, scales = "free")+
labs(title="Errors as a function of socio-economic variables",
y="Mean Absolute Error (Trips)")+
plotTheme()

Let’s focus on the morning commute, where station locations probably
relate to likely users. How is the model performing on weekday mornings
relative to demand for public transportation (e.g. possible user base).
We can tell that there are a select few stations that are proving
sightly resistant to our model - they have long communication time, high
mean income and low transit usage, demographically.
5.6 Animated Map by space/time dependencies
week21 <-
filter(dat_census , week == 21)
week21.panel <-
expand.grid(
interval15 = unique(week21$interval15),
Pickup.Census.Tract = unique(dat_census$from_station_id))
ride.animation.data <-
mutate(week21, Trip_Counter = 1) %>%
select(interval15, from_station_id, from_longitude, from_latitude, Trip_Counter) %>%
group_by(interval15, from_station_id, from_longitude, from_latitude) %>%
summarize(Trip_Count = sum(Trip_Counter, na.rm=T)) %>%
ungroup() %>%
mutate(Trips = case_when(Trip_Count == 0 ~ "0 trips",
Trip_Count > 0 & Trip_Count <= 2 ~ "0-2 trips",
Trip_Count > 2 & Trip_Count <= 5 ~ "2-5 trips",
Trip_Count > 5 & Trip_Count <= 10 ~ "5-10 trips",
Trip_Count > 10 & Trip_Count <= 15 ~ "10-15 trips",
Trip_Count > 15 ~ "15+ trips")) %>%
mutate(Trips = fct_relevel(Trips, "0 trips","0-2 trips","2-5 trips",
"5-10 trips","10-15 trips","15+ trips"))
library(FNN)
mapTheme <- function(base_size = 12) {
theme(
text = element_text( color = "black"),
plot.title = element_text(size = 18,colour = "black"),
plot.subtitle=element_text(face="italic"),
plot.caption=element_text(hjust=0),
axis.ticks = element_blank(),
panel.background = element_blank(),axis.title = element_blank(),
axis.text = element_blank(),
axis.title.x = element_blank(),
axis.title.y = element_blank(),
panel.grid.minor = element_blank(),
panel.border = element_rect(colour = "black", fill=NA, size=2)
)
}
rideshare_animation <-
ggplot()+
geom_sf(data = phillyTracts %>%
st_transform(crs=4326), colour = '#efefef')+
geom_point(data = ride.animation.data,
aes(x = from_longitude, y = from_latitude, fill = Trips, color = Trips), size = 1, alpha = 1.5) +
scale_colour_manual(values = palette1) +
labs(title = "Indego pickups for one week in May 2022",
subtitle = "15 minute intervals: {current_frame}") +
transition_manual(interval15) +
mapTheme()
animate(rideshare_animation, duration=20, renderer = gifski_renderer())

LS0tCnRpdGxlOiAiU3BhY2UtVGltZSBQcmVkaWN0aW9uIG9mIEJpa2UgU2hhcmUgRGVtYW5kIC0gSFc1IChNVVNBIDUwOCwgRmFsbCwgMjAyMikiCmF1dGhvcjogIlN0dWRlbnQ6IFJ1aSBKaWFuZzsgSW5zdHJ1Y3RvcjogTWljaGFlbCBGaWNobWFuIgpkYXRlOiAiTm92ZW1iZXIgMTYsIDIwMjIiCm91dHB1dDogCiAgaHRtbF9kb2N1bWVudDoKICAgIHRvYzogdHJ1ZQogICAgdG9jX2Zsb2F0OiB0cnVlCiAgICBjb2RlX2ZvbGRpbmc6ICJoaWRlIgogICAgY29kZV9kb3dubG9hZDogdHJ1ZQotLS0KCiMjIDEuMS4gSW50cm9kdWN0aW9uCgpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFKQpgYGAKCk9uZSBvZiB0aGUgbW9zdCBkaWZmaWN1bHQgb3BlcmF0aW9uYWwgcHJvYmxlbXMgZm9yIHVyYmFuIGJpa2Ugc2hhcmUgc3lzdGVtcyBpcyB0aGUgbmVlZCB0byDigJhyZS1iYWxhbmNl4oCZIGJpY3ljbGVzIGFjcm9zcyB0aGUgbmV0d29yay4gQmlrZSBzaGFyZSBpcyBub3QgdXNlZnVsIGlmIGEgZG9jayBoYXMgbm8gYmlrZXMgdG8gcGlja3VwLCBub3IgaWYgdGhlcmUgYXJlIG5vIG9wZW4gZG9ja2luZyBzcGFjZXMgdG8gZGVwb3NpdCBhIGJpa2UuIFJlLWJhbGFuY2luZyBpcyB0aGUgcHJhY3RpY2Ugb2YgYW50aWNpcGF0aW5nIChvciBwcmVkaWN0aW5nKSBiaWtlIHNoYXJlIGRlbWFuZCBmb3IgYWxsIGRvY2tzIGF0IGFsbCB0aW1lcyBhbmQgbWFudWFsbHkgcmVkaXN0cmlidXRpbmcgYmlrZXMgdG8gZW5zdXJlIGEgYmlrZSBvciBhIGRvY2tpbmcgcGxhY2UgaXMgYXZhaWxhYmxlIHdoZW4gbmVlZGVkLjxiciAvPgpUaGUgZGVtYW5kIGZvciBwYXJraW5nIHNwYWNlcywgdWJlciB0cmlwcywgYmlrZSBzaGFyZSwgcm9hZCBhY2Nlc3MgYW5kIGEgd2hvbGUgaG9zdCBvZiB1cmJhbiB0cmFuc3BvcnRhdGlvbiBwaGVub21lbmEgYXJlIHRpbWUgYW5kIHNwYWNlIGRlcGVuZGVudCwgYW5kIG1vZGVsaW5nIHRoZW0gZnJlcXVlbnRseSBpbnZvbHZlcyBzaW1wbHkgY29udHJvbGxpbmcgZm9yIHRoZSBkYXksIGhvdXIsIGxvY2F0aW9uLCB3ZWF0aGVyIGFuZCBvdGhlciB0ZW1wb3JhbCBwaGVub21lbmEuIFF1aXRlIHNpbXBseSwgdGhlIGRlbWFuZCBmb3IgYmlrZSBzaGFyZSB0cmlwcyB0b2RheSBhdCBteSBsb2NhdGlvbiBhdCA1UE0gaXMgcHJvYmFibHkgaGlnaGx5IGNvcnJlbGF0ZWQgd2l0aCB0aGUgZGVtYW5kIGxhc3Qgd2VlayBhdCB0aGUgc2FtZSB0aW1lLiA8YnIgLz4KVGhpcyBwcm9qZWN0IHdpbGwgcHJlZGljdCBvbmx5IHRoZSBkZW1hbmQsIGJ1dCBpdCB3aWxsIGdpdmUgdXMgYSB3aW5kb3cgaW50byBob3cgd2UgY2FuIHVzZSB0aW1lLXNwYWNlIHByZWRpY3RpdmUgbW9kZWxpbmcgdG8gYWRkcmVzcyBhbiBvcGVyYXRpb25zIGlzc3VlLiBJZiB3ZSBrbmV3IHRoZSBiaWtlIHN0YXRpb24gY2FwYWNpdGllcywgd2UgY291bGQgc2VlIHdoZW4gZGVtYW5kIGZvciBiaWtlcyBtaWdodCBkcml2ZSBzdGF0aW9ucyB0byBydW4gb3V0IG9mIGJpa2VzLCBhbmQgdGhlbiBtb3ZlIGV4Y2VzcyBiaWtlcyBmcm9tIGVsc2V3aGVyZS4gRm9yIGV4YW1wbGUsIHdlIGNvdWxkIHByb3ZpZGUgcmV3YXJkIGNyZWRpdHMgZm9yIHRob3NlIHdobyBlbmQgdGhlaXIgdHJpcCBhdCB0aGUgaGlnaC1kZW1hbmQgc3RhdGlvbnMgZGVwZW5kcyBvbiB0aGUgcHJlZGljdGlvbi4gVG9kYXnigJlzIHRyZW5kIGlzIHNpbWlsYXIgdG8gdGhlIHRyZW5kIHRvbW9ycm93IGFuZCB0aGlzIHdlZWvigJlzIHRyZW5kIHdpbGwgYmUgc2ltaWxhciB0byBuZXh0IHdlZWsgdHJlbmRzLiBIZW5jZSwgd2Ugd2lsbCBiZSBhYmxlIHRvIHByZWRpY3QgaG93IG1hbnkgYmlrZXMgYW5kIGZyb20gd2hpY2ggc3RhdGlvbiB0byBoaWdoIGFjY3VyYWN5LiBUaG9zZSBjcmVkaXRzIGNvdWxkIGJlIHVzZWQgYnkgcmlkZXJzIHdobyB0cmllcyB0byBwYXkgdGhlaXIgbmV4dCBwZXJpb2QgcGxhbiAobW9udGhseSBvciB5ZWFybHkgcGFzcykgb3IgZ3Vlc3QgcGFzcy4gRWxlY3RyaWMgYmlrZXMgcmVxdWlyZSBleHRyYSAyMMKiL21pbnV0ZSB0byB1bmxvY2suIENyZWRpdHMgY291bGQgYWxzbyB3b3JrIGFzIGEgZGlzY291bnQgZm9yIHRoZSBleHRyYSBlbGVjdHJpYyBiaWtlcyBmZWUuIDxiciAvPgpQaGlsYWRlbHBoaWHigJlzIGJpa2VzaGFyZSBwcm9ncmFtIOKAkyBvZmZlcnMgNjAwIHNlbGYtc2VydmljZSBiaWtlcyBhbW9uZyA2MCBzdGF0aW9ucyBhbGwgZGF5LCBldmVyeSBkYXkuIFBoaWxhZGVscGhpYeKAmXMgQ2l0eSBnb3Zlcm5tZW50IG93bnMgdGhlIGJpY3ljbGVzIGFuZCBzdGF0aW9ucywgd2l0aCB0aGUgTWF5b3LigJlzIE9mZmljZSBvZiBUcmFuc3BvcnRhdGlvbiBhbmQgVXRpbGl0aWVzIChNT1RVKSBwbGFubmluZyBhbmQgbWFuYWdpbmcgdGhlIHByb2plY3QuIFRoZSBDaXR5IG9mIFBoaWxhZGVscGhpYSBhbmQgaXRzIHBhcnRuZXJzIGF0IEJpY3ljbGUgVHJhbnNpdCBTeXN0ZW1zIGFyZSBzaGFyaW5nIGFub255bWl6ZWQgSW5kZWdvIHRyaXAgZGF0YSwgZG93bmxvYWRhYmxlIGZyb20gT3BlbkRhdGFQaGlsbHkgYW5kIG9uIEluZGVnb+KAmXMgd2ViaXN0ZS4gVGhlIGRhdGEgaW5jbHVkZXMgdGhlIG1lbWJlcnNoaXAgdHlwZSwgYmlrZSBudW1iZXJzLCBjaGVja291dCBraW9zayBuYW1lLCBJRCwgYW5kIGxvY2F0aW9uIG9mIHN0YXRpb25zLCB0cmlwIGR1cmF0aW9uLCBjaGVja291dCBhbmQgcmV0dXJuIHRpbWVzLCBhbmQgYSBjYXRlZ29yeSBkaXN0aW5ndWlzaGluZyBiZXR3ZWVuIG9uZSB3YXkgYW5kIHJvdW5kIHRyaXBzLiAKaHR0cHM6Ly93d3cucmlkZWluZGVnby5jb20vYWJvdXQvZGF0YS8KCiMjIDIgU2V0dXAKCmBgYHtyIHNldHVwXzEzLCBjYWNoZT1UUlVFLCBtZXNzYWdlPUZBTFNFfQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShzZikKbGlicmFyeShsdWJyaWRhdGUpCmxpYnJhcnkodGlncmlzKQpsaWJyYXJ5KHRpZHljZW5zdXMpCmxpYnJhcnkodmlyaWRpcykKbGlicmFyeShyaWVtKQpsaWJyYXJ5KGdyaWRFeHRyYSkKbGlicmFyeShrbml0cikKbGlicmFyeShrYWJsZUV4dHJhKQpsaWJyYXJ5KFJTb2NyYXRhKQpsaWJyYXJ5KGNhcmV0KQpsaWJyYXJ5KGdnYW5pbWF0ZSkKbGlicmFyeShnaWZza2kpCgpwbG90VGhlbWUgPC0gdGhlbWUoCiAgcGxvdC50aXRsZSA9ZWxlbWVudF90ZXh0KHNpemU9MTIpLAogIHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZT04KSwKICBwbG90LmNhcHRpb24gPSBlbGVtZW50X3RleHQoc2l6ZSA9IDYpLAogIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCwgYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSwKICBheGlzLnRleHQueSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApLAogIGF4aXMudGl0bGUueSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApLAogICMgU2V0IHRoZSBlbnRpcmUgY2hhcnQgcmVnaW9uIHRvIGJsYW5rCiAgcGFuZWwuYmFja2dyb3VuZD1lbGVtZW50X2JsYW5rKCksCiAgcGxvdC5iYWNrZ3JvdW5kPWVsZW1lbnRfYmxhbmsoKSwKICAjcGFuZWwuYm9yZGVyPWVsZW1lbnRfcmVjdChjb2xvdXI9IiNGMEYwRjAiKSwKICAjIEZvcm1hdCB0aGUgZ3JpZAogIHBhbmVsLmdyaWQubWFqb3I9ZWxlbWVudF9saW5lKGNvbG91cj0iI0QwRDBEMCIsc2l6ZT0uMiksCiAgYXhpcy50aWNrcz1lbGVtZW50X2JsYW5rKCkpCgptYXBUaGVtZSA8LSB0aGVtZShwbG90LnRpdGxlID1lbGVtZW50X3RleHQoc2l6ZT0xMiksCiAgICAgICAgICAgICAgICAgIHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZT04KSwKICAgICAgICAgICAgICAgICAgcGxvdC5jYXB0aW9uID0gZWxlbWVudF90ZXh0KHNpemUgPSA2KSwKICAgICAgICAgICAgICAgICAgYXhpcy5saW5lPWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICAgICAgICAgYXhpcy50ZXh0Lng9ZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgICAgICAgICBheGlzLnRleHQueT1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgICAgIGF4aXMudGlja3M9ZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgICAgICAgICBheGlzLnRpdGxlLng9ZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgICAgICAgICBheGlzLnRpdGxlLnk9ZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kPWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICAgICAgICAgcGFuZWwuYm9yZGVyPWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICAgICAgICAgcGFuZWwuZ3JpZC5tYWpvcj1lbGVtZW50X2xpbmUoY29sb3VyID0gJ3RyYW5zcGFyZW50JyksCiAgICAgICAgICAgICAgICAgIHBhbmVsLmdyaWQubWlub3I9ZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgICAgICAgICBsZWdlbmQuZGlyZWN0aW9uID0gInZlcnRpY2FsIiwgCiAgICAgICAgICAgICAgICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJyaWdodCIsCiAgICAgICAgICAgICAgICAgIHBsb3QubWFyZ2luID0gbWFyZ2luKDEsIDEsIDEsIDEsICdjbScpLAogICAgICAgICAgICAgICAgICBsZWdlbmQua2V5LmhlaWdodCA9IHVuaXQoMSwgImNtIiksIGxlZ2VuZC5rZXkud2lkdGggPSB1bml0KDAuMiwgImNtIikpCgpwYWxldHRlNSA8LSBjKCIjZWZmM2ZmIiwiI2JkZDdlNyIsIiM2YmFlZDYiLCIjMzE4MmJkIiwiIzA4NTE5YyIpCnBhbGV0dGU0IDwtIGMoIiNEMkZCRDQiLCIjOTJCQ0FCIiwiIzUyN0Q4MiIsIiMxMjNGNUEiKQpwYWxldHRlMiA8LSBjKCIjNmJhZWQ2IiwiIzA4NTE5YyIpCnBhbGV0dGUxIDwtIGMoIiNiNmNkZTMiLCIjNThhMmU4IiwiIzFmNjZhYiIsIiMwNzNiNmIiKQpgYGAKCkxvYWQgY2Vuc3VzIEFQSSBrZXkgdG8gZ3JhYiBzdHVmZiBmcm9tIGB0aWR5Y2Vuc3VzYAoKYGBge3IgaW5zdGFsbF9jZW5zdXNfQVBJX2tleSwgd2FybmluZyA9IEZBTFNFLCBpbmNsdWRlPUZBTFNFLCBldmFsID0gVFJVRX0KIyBJbnN0YWxsIENlbnN1cyBBUEkgS2V5CnRpZHljZW5zdXM6OmNlbnN1c19hcGlfa2V5KCIyZjc0ODY2OGFkNTQwNzI5NmNjMWZmZGZmMWE0YWIzYjJhYTk4YTg0Iiwgb3ZlcndyaXRlID0gVFJVRSkKYGBgCgojIyMgMi4xLiBJbXBvcnQgRGF0YQoKTGV0J3MgcmVhZCBpbiB0aGUgbW9udGggb2YgTWF5LCBVc3VhbGx5LCBNYXkgaGFzIGEgZmFpcmx5IHBsZWFzYW50IHRlbXBlcmF0dXJlIHJhbmdlIGluIFBoaWxseSwgc28gd2UgbWF5IHNlZSBzb21lIGxlaXN1cmUgdHJpcHMgYXMgd2VsbCBhcyBjb21tdXRlcy4KaHR0cHM6Ly9raW9za3MuYmljeWNsZXRyYW5zaXQud29ya2Vycy5kZXYvcGhsCgoKYGBge3IgcmVhZF9kYXR9CmRhdCA8LSByZWFkLmNzdigiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL1J1bVJvbi9IV19VUF9JUC9tYWluL1IvZGF0YTRIVzUvNC4yNS01LjI5LmNzdiIpCgpkYXQgPC0gZGF0ICU+JQogIHJlbmFtZSguLCBmcm9tX2xvbmdpdHVkZSA9IHN0YXJ0X2xvbikgJT4lCiAgcmVuYW1lKC4sIGZyb21fbGF0aXR1ZGUgPSBzdGFydF9sYXQpJT4lCiAgcmVuYW1lKC4sIHRvX2xhdGl0dWRlID0gZW5kX2xhdCApICU+JQogIHJlbmFtZSguLCB0b19sb25naXR1ZGUgPSBlbmRfbG9uKSAlPiUKICByZW5hbWUoLiwgZnJvbV9zdGF0aW9uX2lkPSBzdGFydF9zdGF0aW9uKSAlPiUKICByZW5hbWUoLiwgdG9fc3RhdGlvbl9pZCA9IGVuZF9zdGF0aW9uKQpzdGF0aW9uX25hbWUgPC0gcmVhZC5jc3YoImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9SdW1Sb24vSFdfVVBfSVAvbWFpbi9SL2RhdGE0SFc1L2luZGVnb19zdGF0aW9ucy5jc3YiKQpzdGF0aW9uX25hbWU8LSBzdGF0aW9uX25hbWUgJT4lCiAgc2VsZWN0KCJTdGF0aW9uX0lEIiwiU3RhdGlvbl9OYW1lIikKZGF0IDwtIGxlZnRfam9pbihkYXQsIHN0YXRpb25fbmFtZSwgYnkgPSBjKCJmcm9tX3N0YXRpb25faWQiID0gIlN0YXRpb25fSUQiKSkgJT4lCiAgcmVuYW1lKC4sIGZyb21fc3RhdGlvbl9uYW1lPSBTdGF0aW9uX05hbWUpIApkYXQgPC0gbGVmdF9qb2luKGRhdCwgc3RhdGlvbl9uYW1lLCBieSA9IGMoInRvX3N0YXRpb25faWQiID0gIlN0YXRpb25fSUQiKSkgJT4lCiAgcmVuYW1lKC4sIHRvX3N0YXRpb25fbmFtZT0gU3RhdGlvbl9OYW1lKSAKYGBgCgpMZXQncyB1c2Ugc29tZSBkYXRlIHBhcnNpbmcgdG8gYmluIHRoZSBkYXRhIGJ5IDE1IGFuZCA2MCBtaW51dGUgaW50ZXJ2YWxzIGJ5IHJvdW5kaW5nIGFuZCB0YWtlIGEgbG9vayBhdCBvdXIgZGF0YSB0byBzZWUgdGhlIGZvcm1hdCBhbmQgbmFtZXMgb2YgYWxsIG9mIG91ciBjb2x1bW5zIHVzaW5nIHRoZSBgZ2xpbXBzZWAgY29tbWFuZC4KCmBgYHtyIHRpbWVfYmlucywgZWNobz1UUlVFfQpkYXQkc3RhcnRfdGltZSA8LSBzdHJwdGltZShkYXQkc3RhcnRfdGltZSwgZm9ybWF0ID0gIiVtLyVkLyVZICVIOiVNIikKZGF0JGVuZF90aW1lIDwtIHN0cnB0aW1lKGRhdCRlbmRfdGltZSwgZm9ybWF0ID0gIiVtLyVkLyVZICVIOiVNIikKZGF0MiA8LSBkYXQgJT4lCiAgbXV0YXRlKGludGVydmFsNjAgPSBmbG9vcl9kYXRlKHltZF9obXMoc3RhcnRfdGltZSksIHVuaXQgPSAiaG91ciIpLAogICAgICAgICBpbnRlcnZhbDE1ID0gZmxvb3JfZGF0ZSh5bWRfaG1zKHN0YXJ0X3RpbWUpLCB1bml0ID0gIjE1IG1pbnMiKSwKICAgICAgICAgd2VlayA9IHdlZWsoaW50ZXJ2YWw2MCksCiAgICAgICAgIGRvdHcgPSB3ZGF5KGludGVydmFsNjAsIGxhYmVsPVRSVUUpKQoKZ2xpbXBzZShkYXQyKQpgYGAKCiMjIyAyLjIuIEltcG9ydCBDZW5zdXMgSW5mbwoKVXNpbmcgdGhlIGB0aWR5Y2Vuc3VzYCBwYWNrYWdlLCB3ZSBjYW4gZG93bmxvYWQgY2Vuc3VzIGdlb2dyYXBoeSBhbmQgdmFyaWFibGVzLiBUaGVzZSBhcmUgdXNlZCB0byB0ZXN0IGdlbmVyYWxpemVhYmlsaXR5IGxhdGVyLiBXZSBleHRyYWN0IHRoZSB0cmFjdHMgZm9yIG1hcHBpbmcgYW5kIGpvaW5pbmcgcHVycG9zZXMgLSBjcmVhdGluZyBhbiBgc2ZgIG9iamVjdCB0aGF0IGNvbnNpc3RzIG9ubHkgb2YgR0VPSURzIGFuZCBnZW9tZXRyaWVzLgoKV2UgYWRkIHRoZSBzcGF0aWFsIGluZm9ybWF0aW9uIHRvIG91ciByaWRlc2hhcmUgZGF0YSBhcyBvcmlnaW4gYW5kIGRlc3RpbmF0aW9uIGRhdGEsIGZpcnN0IGpvaW5pbmcgdGhlIG9yaWdpbiBzdGF0aW9uLCB0aGVuIHRoZSBkZXN0aW5hdGlvbiBzdGF0aW9uIHRvIG91ciBjZW5zdXMgZGF0YS4KCmBgYHtyIGdldF9jZW5zdXMsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGNhY2hlPUZBTFNFLCByZXN1bHRzID0gJ2hpZGUnfQpwaGlsbHlDZW5zdXMgPC0gCiAgZ2V0X2FjcyhnZW9ncmFwaHkgPSAidHJhY3QiLCAKICAgICAgICAgIHZhcmlhYmxlcyA9IGMoIkIwMTAwM18wMDEiLCAiQjE5MDEzXzAwMSIsIAogICAgICAgICAgICAgICAgICAgICAgICAiQjAyMDAxXzAwMiIsICJCMDgwMTNfMDAxIiwKICAgICAgICAgICAgICAgICAgICAgICAgIkIwODAxMl8wMDEiLCAiQjA4MzAxXzAwMSIsIAogICAgICAgICAgICAgICAgICAgICAgICAiQjA4MzAxXzAxMCIsICJCMDEwMDJfMDAxIiksIAogICAgICAgICAgeWVhciA9IDIwMjAsIAogICAgICAgICAgc3RhdGUgPSAiUEEiLCAKICAgICAgICAgIGdlb21ldHJ5ID0gVFJVRSwgCiAgICAgICAgICBjb3VudHk9YygiUGhpbGFkZWxwaGlhIiksCiAgICAgICAgICBvdXRwdXQgPSAid2lkZSIpICU+JQogIHJlbmFtZShUb3RhbF9Qb3AgPSAgQjAxMDAzXzAwMUUsCiAgICAgICAgIE1lZF9JbmMgPSBCMTkwMTNfMDAxRSwKICAgICAgICAgTWVkX0FnZSA9IEIwMTAwMl8wMDFFLAogICAgICAgICBXaGl0ZV9Qb3AgPSBCMDIwMDFfMDAyRSwKICAgICAgICAgVHJhdmVsX1RpbWUgPSBCMDgwMTNfMDAxRSwKICAgICAgICAgTnVtX0NvbW11dGVycyA9IEIwODAxMl8wMDFFLAogICAgICAgICBNZWFuc19vZl9UcmFuc3BvcnQgPSBCMDgzMDFfMDAxRSwKICAgICAgICAgVG90YWxfUHVibGljX1RyYW5zID0gQjA4MzAxXzAxMEUpICU+JQogIHNlbGVjdChUb3RhbF9Qb3AsIE1lZF9JbmMsIFdoaXRlX1BvcCwgVHJhdmVsX1RpbWUsCiAgICAgICAgIE1lYW5zX29mX1RyYW5zcG9ydCwgVG90YWxfUHVibGljX1RyYW5zLAogICAgICAgICBNZWRfQWdlLAogICAgICAgICBHRU9JRCwgZ2VvbWV0cnkpICU+JQogIG11dGF0ZShQZXJjZW50X1doaXRlID0gV2hpdGVfUG9wIC8gVG90YWxfUG9wLAogICAgICAgICBNZWFuX0NvbW11dGVfVGltZSA9IFRyYXZlbF9UaW1lIC8gVG90YWxfUHVibGljX1RyYW5zLAogICAgICAgICBQZXJjZW50X1Rha2luZ19QdWJsaWNfVHJhbnMgPSBUb3RhbF9QdWJsaWNfVHJhbnMgLyBNZWFuc19vZl9UcmFuc3BvcnQpCmBgYAoKYGBge3IgZXh0cmFjdF9nZW9tZXRyaWVzIH0KcGhpbGx5VHJhY3RzIDwtIAogIHBoaWxseUNlbnN1cyAlPiUKICBhcy5kYXRhLmZyYW1lKCkgJT4lCiAgZGlzdGluY3QoR0VPSUQsIC5rZWVwX2FsbCA9IFRSVUUpICU+JQogIHNlbGVjdChHRU9JRCwgZ2VvbWV0cnkpICU+JSAKICBzdF9zZgoKYGBgCgoKYGBge3IgYWRkX2NlbnN1c190cmFjdHMgLCBtZXNzYWdlID0gRkFMU0UsIHdhcm5pbmcgPSBGQUxTRX0KZGF0X2NlbnN1cyA8LSBzdF9qb2luKGRhdDIgJT4lIAogICAgICAgICAgZmlsdGVyKGlzLm5hKGZyb21fbG9uZ2l0dWRlKSA9PSBGQUxTRSAmCiAgICAgICAgICAgICAgICAgICBpcy5uYShmcm9tX2xhdGl0dWRlKSA9PSBGQUxTRSAmCiAgICAgICAgICAgICAgICAgICBpcy5uYSh0b19sYXRpdHVkZSkgPT0gRkFMU0UgJgogICAgICAgICAgICAgICAgICAgaXMubmEodG9fbG9uZ2l0dWRlKSA9PSBGQUxTRSkgJT4lCiAgICAgICAgICBzdF9hc19zZiguLCBjb29yZHMgPSBjKCJmcm9tX2xvbmdpdHVkZSIsICJmcm9tX2xhdGl0dWRlIiksIGNycyA9IDQzMjYpLAogICAgICAgIHBoaWxseVRyYWN0cyAlPiUKICAgICAgICAgIHN0X3RyYW5zZm9ybShjcnM9NDMyNiksCiAgICAgICAgam9pbj1zdF9pbnRlcnNlY3RzLAogICAgICAgICAgICAgIGxlZnQgPSBUUlVFKSAlPiUKICByZW5hbWUoT3JpZ2luLlRyYWN0ID0gR0VPSUQpICU+JQogIG11dGF0ZShmcm9tX2xvbmdpdHVkZSA9IHVubGlzdChtYXAoZ2VvbWV0cnksIDEpKSwKICAgICAgICAgZnJvbV9sYXRpdHVkZSA9IHVubGlzdChtYXAoZ2VvbWV0cnksIDIpKSklPiUKICBhcy5kYXRhLmZyYW1lKCkgJT4lCiAgc2VsZWN0KC1nZW9tZXRyeSklPiUKICBzdF9hc19zZiguLCBjb29yZHMgPSBjKCJ0b19sb25naXR1ZGUiLCAidG9fbGF0aXR1ZGUiKSwgY3JzID0gNDMyNikgJT4lCiAgc3Rfam9pbiguLCBwaGlsbHlUcmFjdHMgJT4lCiAgICAgICAgICAgIHN0X3RyYW5zZm9ybShjcnM9NDMyNiksCiAgICAgICAgICBqb2luPXN0X2ludGVyc2VjdHMsCiAgICAgICAgICBsZWZ0ID0gVFJVRSkgJT4lCiAgcmVuYW1lKERlc3RpbmF0aW9uLlRyYWN0ID0gR0VPSUQpICAlPiUKICBtdXRhdGUodG9fbG9uZ2l0dWRlID0gdW5saXN0KG1hcChnZW9tZXRyeSwgMSkpLAogICAgICAgICB0b19sYXRpdHVkZSA9IHVubGlzdChtYXAoZ2VvbWV0cnksIDIpKSklPiUKICBhcy5kYXRhLmZyYW1lKCkgJT4lCiAgc2VsZWN0KC1nZW9tZXRyeSkKYGBgCgojIyMgMi4zLiBJbXBvcnQgV2VhdGhlciBEYXRhCgpJbXBvcnQgd2VhdGhlciBkYXRhIGZyb20gUGhpbGFkZWxwaGlhIGFpcnBvcnQgKGNvZGUgUEhMKSB1c2luZyBgcmllbV9tZWFzdXJlc2AuIFdlIGNhbiBgbXV0YXRlYCB0aGUgZGF0YSB0byBnZXQgdGVtcGVyYXR1cmUsIHdpbmQgc3BlZWQsIHByZWNpcGl0YXRpb24gb24gYW4gaG91cmx5IGJhc2lzIGFuZCBwbG90IHRoZSB0ZW1wZXJhdHVyZSBhbmQgcHJlY2lwaXRhdGlvbiB0cmVuZHMgb3ZlciBvdXIgc3R1ZHkgcGVyaW9kLgoKVGhlc2UgZGF0YSBjYW4gYWxzbyBiZSBjYXRlZ29yaXplZCBhcyBhIHBhcnQgb2YgYW4gZXhwbG9yYXRpb24gb2YgdGhlIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIHlvdXIgaW5kZXBlbmRlbnQgYW5kIGRlcGVuZGVudCB2YXJpYWJsZXMsIGUuZy4gImRvZXMgd2luZCBhcHBlYXIgdG8gYWZmZWN0IHJpZGVyc2hpcCBkdXJpbmcgcnVzaCBob3VyPyIKCmBgYHtyIGltcG9ydF93ZWF0aGVyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQp3ZWF0aGVyLlBhbmVsIDwtIAogIHJpZW1fbWVhc3VyZXMoc3RhdGlvbiA9ICJQSEwiLCBkYXRlX3N0YXJ0ID0gIjIwMjItMDQtMjUiLCBkYXRlX2VuZCA9ICIyMDIyLTA1LTMwIikgJT4lCiAgZHBseXI6OnNlbGVjdCh2YWxpZCwgdG1wZiwgcDAxaSwgc2tudCklPiUKICByZXBsYWNlKGlzLm5hKC4pLCAwKSAlPiUKICAgIG11dGF0ZShpbnRlcnZhbDYwID0geW1kX2goc3Vic3RyKHZhbGlkLDEsMTMpKSkgJT4lCiAgICBtdXRhdGUod2VlayA9IHdlZWsoaW50ZXJ2YWw2MCksCiAgICAgICAgICAgZG90dyA9IHdkYXkoaW50ZXJ2YWw2MCwgbGFiZWw9VFJVRSkpICU+JQogICAgZ3JvdXBfYnkoaW50ZXJ2YWw2MCkgJT4lCiAgICBzdW1tYXJpemUoVGVtcGVyYXR1cmUgPSBtYXgodG1wZiksCiAgICAgICAgICAgICAgUHJlY2lwaXRhdGlvbiA9IHN1bShwMDFpKSwKICAgICAgICAgICAgICBXaW5kX1NwZWVkID0gbWF4KHNrbnQpKSAlPiUKICAgIG11dGF0ZShUZW1wZXJhdHVyZSA9IGlmZWxzZShUZW1wZXJhdHVyZSA9PSAwLCA0MiwgVGVtcGVyYXR1cmUpKQoKZ2xpbXBzZSh3ZWF0aGVyLlBhbmVsKQpgYGAKCmBgYHtyIHBsb3Rfd2VhdGhlciwgY2F0Y2hlID0gVFJVRX0KZ3JpZC5hcnJhbmdlKAogIGdncGxvdCh3ZWF0aGVyLlBhbmVsLCBhZXMoaW50ZXJ2YWw2MCxQcmVjaXBpdGF0aW9uKSkgKyBnZW9tX2xpbmUoKSArIAogIGxhYnModGl0bGU9IlBlcmNpcGl0YXRpb24iLCB4PSJIb3VyIiwgeT0iUGVyZWNpcGl0YXRpb24iKSArIHBsb3RUaGVtZSwKICBnZ3Bsb3Qod2VhdGhlci5QYW5lbCwgYWVzKGludGVydmFsNjAsV2luZF9TcGVlZCkpICsgZ2VvbV9saW5lKCkgKyAKICAgIGxhYnModGl0bGU9IldpbmQgU3BlZWQiLCB4PSJIb3VyIiwgeT0iV2luZCBTcGVlZCIpICsgcGxvdFRoZW1lLAogIGdncGxvdCh3ZWF0aGVyLlBhbmVsLCBhZXMoaW50ZXJ2YWw2MCxUZW1wZXJhdHVyZSkpICsgZ2VvbV9saW5lKCkgKyAKICAgIGxhYnModGl0bGU9IlRlbXBlcmF0dXJlIiwgeD0iSG91ciIsIHk9IlRlbXBlcmF0dXJlIikgKyBwbG90VGhlbWUsCiAgdG9wPSJXZWF0aGVyIERhdGEgLSBQaGlsYWRlbHBoaWEgUEhMIC0gTWF5LCAyMDIyIikKYGBgCgojIyMgMi40LiBJbXBvcnQgQW1lbml0eSBEYXRhCgpJbXBvcnQgc2Nob29sIGRhdGEgYXMgYW1lbml0eS4KCmBgYHtyIGltcG9ydF9hbWVuaXR5LCByZXN1bHRzPSdoaWRlJ30KbGlicmFyeShGTk4pCnNjaG9vbCA8LQogIHN0X3JlYWQoImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9SdW1Sb24vSFdfVVBfSVAvbWFpbi9SL1NjaG9vbHMuZ2VvanNvbiIpICU+JQogIHN0X3RyYW5zZm9ybShjcnM9NDMyNikKcm9vdC5kaXIgPSAiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3VyYmFuU3BhdGlhbC9QdWJsaWMtUG9saWN5LUFuYWx5dGljcy1MYW5kaW5nL21hc3Rlci9EQVRBLyIKc291cmNlKCJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vdXJiYW5TcGF0aWFsL1B1YmxpYy1Qb2xpY3ktQW5hbHl0aWNzLUxhbmRpbmcvbWFzdGVyL2Z1bmN0aW9ucy5yIikKCgoKYGBgCgoKIyMgMy4gRGVzY3JpYmUgYW5kIEV4cGxvcmUgdGhlIERhdGEKCkV4YW1pbmluZyB0aGUgdGltZSBhbmQgZnJlcXVlbmN5IGNvbXBvbmVudHMgb2Ygb3VyIGRhdGEuCgojIyMgMy4xLiBPdmVyYWxsIFRpbWUgUGF0dGVybgoKRmlyc3QsIHdlIGxvb2sgYXQgdGhlIG92ZXJhbGwgdGltZSBwYXR0ZXJuIC0gdGhlcmUgaXMgY2xlYXJseSBhIGRhaWx5IHBlcmlvZGljaXR5IGFuZCB0aGVyZSBhcmUgbHVsbCBwZXJpb2RzIG9uIHdlZWtlbmRzLiBOb3RpY2UgdGhhdCB0aGUgd2Vla2VuZCBuZWFyIHRoZSAyOHRoIG9mIE1heSAoTWVtb3JpYWwgRGF5KSBkb2Vzbid0IGhhdmUgdGhlIHNhbWUgZGlwIGluIGFjdGl2aXR5LgoKYGBge3IgdHJpcF90aW1lc2VyaWVzIH0KZ2dwbG90KGRhdF9jZW5zdXMgJT4lCiAgICAgICAgIGdyb3VwX2J5KGludGVydmFsNjApICU+JQogICAgICAgICB0YWxseSgpKSsKICBnZW9tX2xpbmUoYWVzKHggPSBpbnRlcnZhbDYwLCB5ID0gbikpKwogIGxhYnModGl0bGU9IkJpa2Ugc2hhcmUgcGVyIGhyLiBQaGlsYWRlbHBoaWEsIE1heSwgMjAyMiIsCiAgICAgICB4PSJEYXRlIiwgCiAgICAgICB5PSJOdW1iZXIgb2YgdHJpcHMiKSsKICBwbG90VGhlbWUoKQpgYGAKCiMjIyAzLjIuIEV4YW1pbmUgdGhlIERpc3RyaWJ1dGlvbiBvZiBUcmlwIFZvbHVtZSBieSBTdGF0aW9uIGZvciBEaWZmZXJlbnQgVGltZXMgb2YgdGhlIERheQoKV2UgY2xlYXJseSBoYXZlIGEgZmV3IGhpZ2ggdm9sdW1lIHBlcmlvZHMgYnV0IG1vc3RseSBsb3cgdm9sdW1lLiBPdXIgZGF0YSBtdXN0IGNvbnNpc3Qgb2YgYSBsb3Qgb2YgbG93IGRlbWFuZCBzdGF0aW9uL2hvdXJzIGFuZCBhIGZldyBoaWdoIGRlbWFuZCBzdGF0aW9uIGhvdXJzLiAKClRoZXJlJ3MgYSBwb3NzaWJpbGl0eSB3ZSBtYXkgaGF2ZSB0byB0cmVhdCB0aGVzZSBhcyBjb3VudCBkYXRhIGhlcmUsIHdoaWNoIG1lYW5zIHJ1bm5pbmcgUG9pc3NvbiByZWdyZXNzaW9uLiBUaGVuIGFnYWluLCB3ZSBtaWdodCBoYXZlIGVub3VnaCBvZiB0aGUgaGlnaGVyIGNvdW50cyBpbiBvdXIgaGlnaCB2b2x1bWUgdGltZXMgYW5kIHN0YXRpb25zLCB0aGF0IHdlIHNob3VsZCByZWFsbHkgYmUgYnVpbGRpbmcgYSBsaW5lYXIgbW9kZWwgdG8gYWNjb21vZGF0ZSBvdXIgYWN0dWFsIHZvbHVtZSBhbmQgbm90IHdvcnJ5IGFib3V0IHRoZSBsb3cgdHJpcCB0aW1lcy9zdGF0aW9ucy4KCldlIGNhbiBhbHNvIHRyYWNrIHRoZSBkYWlseSB0cmVuZHMgaW4gcmlkZXJzaGlwIGJ5IGRheSBvZiB0aGUgd2VlayBhbmQgd2Vla2VuZCB2ZXJzdXMgd2Vla2RheSwgdG8gc2VlIHdoYXQgdGVtcG9yYWwgcGF0dGVybnMgd2UnZCBsaWtlIHRvIGNvbnRyb2wgZm9yLgoKYGBge3IgbWVhbl90cmlwc19oaXN0LCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRSB9CmRhdF9jZW5zdXMgJT4lCiAgICAgICAgbXV0YXRlKHRpbWVfb2ZfZGF5ID0gY2FzZV93aGVuKGhvdXIoaW50ZXJ2YWw2MCkgPCA3IHwgaG91cihpbnRlcnZhbDYwKSA+IDE4IH4gIk92ZXJuaWdodCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhvdXIoaW50ZXJ2YWw2MCkgPj0gNyAmIGhvdXIoaW50ZXJ2YWw2MCkgPCAxMCB+ICJBTSBSdXNoIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaG91cihpbnRlcnZhbDYwKSA+PSAxMCAmIGhvdXIoaW50ZXJ2YWw2MCkgPCAxNSB+ICJNaWQtRGF5IiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaG91cihpbnRlcnZhbDYwKSA+PSAxNSAmIGhvdXIoaW50ZXJ2YWw2MCkgPD0gMTggfiAiUE0gUnVzaCIpKSU+JQogICAgICAgICBncm91cF9ieShpbnRlcnZhbDYwLCBmcm9tX3N0YXRpb25fbmFtZSwgdGltZV9vZl9kYXkpICU+JQogICAgICAgICB0YWxseSgpJT4lCiAgZ3JvdXBfYnkoZnJvbV9zdGF0aW9uX25hbWUsIHRpbWVfb2ZfZGF5KSU+JQogIHN1bW1hcml6ZShtZWFuX3RyaXBzID0gbWVhbihuKSklPiUKICBnZ3Bsb3QoKSsKICBnZW9tX2hpc3RvZ3JhbShhZXMobWVhbl90cmlwcyksIGJpbndpZHRoID0gMSkrCiAgbGFicyh0aXRsZT0iTWVhbiBOdW1iZXIgb2YgSG91cmx5IFRyaXBzIFBlciBTdGF0aW9uLiBQaGlhbGRlbHBoaWEsIE1heSwgMjAyMiIsCiAgICAgICB4PSJOdW1iZXIgb2YgdHJpcHMiLCAKICAgICAgIHk9IkZyZXF1ZW5jeSIpKwogIGZhY2V0X3dyYXAofnRpbWVfb2ZfZGF5KSsKICBwbG90VGhlbWUoKQpgYGAKCmBgYHtyIHRyaXBzX3N0YXRpb25fZG90dyB9CmdncGxvdChkYXRfY2Vuc3VzICU+JQogICAgICAgICBncm91cF9ieShpbnRlcnZhbDYwLCBmcm9tX3N0YXRpb25fbmFtZSkgJT4lCiAgICAgICAgIHRhbGx5KCkpKwogIGdlb21faGlzdG9ncmFtKGFlcyhuKSwgYmlud2lkdGggPSA1KSsKICBsYWJzKHRpdGxlPSJCaWtlIHNoYXJlIHRyaXBzIHBlciBociBieSBzdGF0aW9uLiBQaGlsYWRlbHBoaWEsIE1heSwgMjAyMiIsCiAgICAgICB4PSJUcmlwIENvdW50cyIsIAogICAgICAgeT0iTnVtYmVyIG9mIFN0YXRpb25zIikrCiAgcGxvdFRoZW1lKCkKYGBgCgpgYGB7ciB0cmlwc19ob3VyX2RvdHcgfQpnZ3Bsb3QoZGF0X2NlbnN1cyAlPiUgbXV0YXRlKGhvdXIgPSBob3VyKHN0YXJ0X3RpbWUpKSkrCiAgICAgZ2VvbV9mcmVxcG9seShhZXMoaG91ciwgY29sb3IgPSBkb3R3KSwgYmlud2lkdGggPSAxKSsKICBsYWJzKHRpdGxlPSJCaWtlIHNoYXJlIHRyaXBzIGluIFBoaWxhZGVscGhpYSwgYnkgZGF5IG9mIHRoZSB3ZWVrLCBNYXksIDIwMjIiLAogICAgICAgeD0iSG91ciIsIAogICAgICAgeT0iVHJpcCBDb3VudHMiKSsKICAgICBwbG90VGhlbWUoKQoKCmdncGxvdChkYXRfY2Vuc3VzICU+JSAKICAgICAgICAgbXV0YXRlKGhvdXIgPSBob3VyKHN0YXJ0X3RpbWUpLAogICAgICAgICAgICAgICAgd2Vla2VuZCA9IGlmZWxzZShkb3R3ICVpbiUgYygiU3VuIiwgIlNhdCIpLCAiV2Vla2VuZCIsICJXZWVrZGF5IikpKSsKICAgICBnZW9tX2ZyZXFwb2x5KGFlcyhob3VyLCBjb2xvciA9IHdlZWtlbmQpLCBiaW53aWR0aCA9IDEpKwogIGxhYnModGl0bGU9IkJpa2Ugc2hhcmUgaW4gUGhpbGFkZWxwaGlhIC0gd2Vla2VuZCB2cyB3ZWVrZGF5LCBNYXksIDIwMjIiLAogICAgICAgeD0iSG91ciIsIAogICAgICAgeT0iVHJpcCBDb3VudHMiKSsKICAgICBwbG90VGhlbWUoKQpgYGAKCgpgYGB7ciBvcmlnaW5fbWFwIH0KZ2dwbG90KCkrCiAgZ2VvbV9zZihkYXRhID0gcGhpbGx5VHJhY3RzICU+JQogICAgICAgICAgc3RfdHJhbnNmb3JtKGNycz00MzI2KSkrCiAgZ2VvbV9wb2ludChkYXRhID0gZGF0X2NlbnN1cyAlPiUgCiAgICAgICAgICAgIG11dGF0ZShob3VyID0gaG91cihzdGFydF90aW1lKSwKICAgICAgICAgICAgICAgIHdlZWtlbmQgPSBpZmVsc2UoZG90dyAlaW4lIGMoIlN1biIsICJTYXQiKSwgIldlZWtlbmQiLCAiV2Vla2RheSIpLAogICAgICAgICAgICAgICAgdGltZV9vZl9kYXkgPSBjYXNlX3doZW4oaG91cihpbnRlcnZhbDYwKSA8IDcgfCBob3VyKGludGVydmFsNjApID4gMTggfiAiT3Zlcm5pZ2h0IiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaG91cihpbnRlcnZhbDYwKSA+PSA3ICYgaG91cihpbnRlcnZhbDYwKSA8IDEwIH4gIkFNIFJ1c2giLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBob3VyKGludGVydmFsNjApID49IDEwICYgaG91cihpbnRlcnZhbDYwKSA8IDE1IH4gIk1pZC1EYXkiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBob3VyKGludGVydmFsNjApID49IDE1ICYgaG91cihpbnRlcnZhbDYwKSA8PSAxOCB+ICJQTSBSdXNoIikpJT4lCiAgICAgICAgICAgICAgZ3JvdXBfYnkoZnJvbV9zdGF0aW9uX2lkLCBmcm9tX2xhdGl0dWRlLCBmcm9tX2xvbmdpdHVkZSwgd2Vla2VuZCwgdGltZV9vZl9kYXkpICU+JQogICAgICAgICAgICAgIHRhbGx5KCksCiAgICAgICAgICAgIGFlcyh4PWZyb21fbG9uZ2l0dWRlLCB5ID0gZnJvbV9sYXRpdHVkZSwgY29sb3IgPSBuKSwgCiAgICAgICAgICAgIGZpbGwgPSAidHJhbnNwYXJlbnQiLCBhbHBoYSA9IDAuNCwgc2l6ZSA9IDEpKwogIHNjYWxlX2NvbG91cl92aXJpZGlzKGRpcmVjdGlvbiA9IC0xLAogIGRpc2NyZXRlID0gRkFMU0UsIG9wdGlvbiA9ICJEIikrCiAgeWxpbShtaW4oZGF0X2NlbnN1cyRmcm9tX2xhdGl0dWRlKSwgbWF4KGRhdF9jZW5zdXMkZnJvbV9sYXRpdHVkZSkpKwogIHhsaW0obWluKGRhdF9jZW5zdXMkZnJvbV9sb25naXR1ZGUpLCBtYXgoZGF0X2NlbnN1cyRmcm9tX2xvbmdpdHVkZSkpKwogIGZhY2V0X2dyaWQod2Vla2VuZCB+IHRpbWVfb2ZfZGF5KSsKICBsYWJzKHRpdGxlPSJCaWtlIHNoYXJlIHRyaXBzIHBlciBociBieSBzdGF0aW9uLiBQaGlsYWRsZXBoaWEsIE1heSwgMjAyMiIpKwogIG1hcFRoZW1lKCkKYGBgCgoKIyMjIDMuMyBDcmVhdGUgU3BhY2UtVGltZSBQYW5lbAoKRmlyc3QgKip3ZSBoYXZlIHRvIG1ha2Ugc3VyZSBlYWNoIHVuaXF1ZSBzdGF0aW9uIGFuZCBob3VyL2RheSBjb21ibyBleGlzdHMgaW4gb3VyIGRhdGEgc2V0LioqIFRoaXMgaXMgZG9uZSBpbiBvcmRlciB0byBjcmVhdGUgYSAicGFuZWwiIChlLmcuIGEgdGltZS1zZXJpZXMpIGRhdGEgc2V0IHdoZXJlIGVhY2ggdGltZSBwZXJpb2QgaW4gdGhlIHN0dWR5IGlzIHJlcHJlc2VudGVkIGJ5IGEgcm93IC0gd2hldGhlciBhbiBvYnNlcnZhdGlvbiB0b29rIHBsYWNlIHRoZW4gb3Igbm90LiBTbyBpZiBhIHN0YXRpb24gZGlkbid0IGhhdmUgYW55IHRyaXBzIG9yaWdpbmF0aW5nIGZyb20gaXQgYXQgYSBnaXZlbiBob3VyLCB3ZSBzdGlsbCBuZWVkIGEgemVybyBpbiB0aGF0IHNwb3QgaW4gdGhlIHBhbmVsLgoKV2Ugc3RhcnQgYnkgZGV0ZXJtaW5pbmcgdGhlIG1heGltdW0gbnVtYmVyIG9mIGNvbWJpbmF0aW9ucy4KClRoZW4gd2UgY29tcGFyZSB0aGF0IHRvIHRoZSBhY3R1YWwgbnVtYmVyIG9mIGNvbWJpbmF0aW9ucy4gV2UgY3JlYXRlIGFuIGVtcHR5IGRhdGEgZnJhbWUgYHN0dWR5LnBhbmVsYCwgaXMgY3JlYXRlZCB0aGF0IGhhcyBlYWNoIHVuaXF1ZSBzcGFjZS90aW1lIG9ic2VydmF0aW9ucy4gVGhpcyBpcyBkb25lIHVzaW5nIHRoZSBleHBhbmQuZ3JpZCBmdW5jdGlvbiBhbmQgdW5pcXVlLiBBbG9uZyB0aGUgd2F5LCB3ZSBrZWVwIHRhYnMgb24gdGhlIG51bWJlciBvZiByb3dzIG91ciBkYXRhIGhhdmUgLSBgbnJvd2Agc2hvd3MgdGhhdCB0aGUgY291bnQgaXMgc3RpbGwgY29ycmVjdC4KCldlIHRoZW4gam9pbiB0aGUgc3RhdGlvbiBuYW1lLCB0cmFjdCBhbmQgbGF0L2xvbiAoc29tZSBoYXZlIG11bHRpcGxlIGxhdCBsb24gaW5mbywgc28gd2UganVzdCB0YWtlIHRoZSBmaXJzdCBvbmUgb2YgZWFjaCB1c2luZyBgZ3JvdXBfYnlgIGFuZCBgc2xpY2VgKS4KCgpgYGB7ciBwYW5lbF9sZW5ndGhfY2hlY2sgLCBtZXNzYWdlID0gRkFMU0UsIHdhcm5pbmcgPSBGQUxTRX0KbGVuZ3RoKHVuaXF1ZShkYXRfY2Vuc3VzJGludGVydmFsNjApKSAqIGxlbmd0aCh1bmlxdWUoZGF0X2NlbnN1cyRmcm9tX3N0YXRpb25faWQpKQoKCnN0dWR5LnBhbmVsIDwtIAogIGV4cGFuZC5ncmlkKGludGVydmFsNjA9dW5pcXVlKGRhdF9jZW5zdXMkaW50ZXJ2YWw2MCksIAogICAgICAgICAgICAgIGZyb21fc3RhdGlvbl9pZCA9IHVuaXF1ZShkYXRfY2Vuc3VzJGZyb21fc3RhdGlvbl9pZCkpICU+JQogIGxlZnRfam9pbiguLCBkYXRfY2Vuc3VzICU+JQogICAgICAgICAgICAgIHNlbGVjdChmcm9tX3N0YXRpb25faWQsIGZyb21fc3RhdGlvbl9uYW1lLCBPcmlnaW4uVHJhY3QsIGZyb21fbG9uZ2l0dWRlLCBmcm9tX2xhdGl0dWRlICklPiUKICAgICAgICAgICAgICBkaXN0aW5jdCgpICU+JQogICAgICAgICAgICAgIGdyb3VwX2J5KGZyb21fc3RhdGlvbl9pZCkgJT4lCiAgICAgICAgICAgICAgc2xpY2UoMSkpCgpucm93KHN0dWR5LnBhbmVsKSAgICAgIApgYGAKCgpXZSBjcmVhdGUgdGhlIGZ1bGwgcGFuZWwgYnkgc3VtbWFyaXppbmcgY291bnRzIGJ5IHN0YXRpb24gZm9yIGVhY2ggdGltZSBpbnRlcnZhbCwga2VlcCBjZW5zdXMgaW5mbyBhbmQgbGF0L2xvbiBpbmZvcm1hdGlvbiBhbG9uZyBmb3Igam9pbmluZyBsYXRlciB0byBvdGhlciBkYXRhLiBXZSByZW1vdmUgZGF0YSBmb3Igc3RhdGlvbiBJRHMgdGhhdCBhcmUgYEZBTFNFYC4KCmBgYHtyIGNyZWF0ZV9wYW5lbCAsIG1lc3NhZ2UgPSBGQUxTRX0KcmlkZS5wYW5lbCA8LSAKICBkYXRfY2Vuc3VzICU+JQogIG11dGF0ZShUcmlwX0NvdW50ZXIgPSAxKSAlPiUKICByaWdodF9qb2luKHN0dWR5LnBhbmVsKSAlPiUgCiAgZ3JvdXBfYnkoaW50ZXJ2YWw2MCwgZnJvbV9zdGF0aW9uX2lkLCBmcm9tX3N0YXRpb25fbmFtZSwgT3JpZ2luLlRyYWN0LCBmcm9tX2xvbmdpdHVkZSwgZnJvbV9sYXRpdHVkZSkgJT4lCiAgc3VtbWFyaXplKFRyaXBfQ291bnQgPSBzdW0oVHJpcF9Db3VudGVyLCBuYS5ybT1UKSkgJT4lCiAgbGVmdF9qb2luKHdlYXRoZXIuUGFuZWwpICU+JQogIHVuZ3JvdXAoKSAlPiUKICBmaWx0ZXIoaXMubmEoZnJvbV9zdGF0aW9uX2lkKSA9PSBGQUxTRSkgJT4lCiAgbXV0YXRlKHdlZWsgPSB3ZWVrKGludGVydmFsNjApLAogICAgICAgICBkb3R3ID0gd2RheShpbnRlcnZhbDYwLCBsYWJlbCA9IFRSVUUpKSAlPiUKICBmaWx0ZXIoaXMubmEoT3JpZ2luLlRyYWN0KSA9PSBGQUxTRSkKYGBgCgpgYGB7ciBjZW5zdXNfYW5kX3BhbmVsICwgbWVzc2FnZSA9IEZBTFNFfQpyaWRlLnBhbmVsIDwtIAogIGxlZnRfam9pbihyaWRlLnBhbmVsLCBwaGlsbHlDZW5zdXMgJT4lCiAgICAgICAgICAgICAgYXMuZGF0YS5mcmFtZSgpICU+JQogICAgICAgICAgICAgIHNlbGVjdCgtZ2VvbWV0cnkpLCBieSA9IGMoIk9yaWdpbi5UcmFjdCIgPSAiR0VPSUQiKSkKYGBgCgpgYGB7ciBhbWVuaXR5LCBtZXNzYWdlID0gRkFMU0V9CmZyb20gPC0gcmlkZS5wYW5lbCAlPiUKICBkcGx5cjo6c2VsZWN0KGZyb21fbG9uZ2l0dWRlLCBmcm9tX2xhdGl0dWRlKQp0byA8LSBzdF9jb29yZGluYXRlcyhzY2hvb2wpCgpyaWRlLnBhbmVsIDwtCiAgcmlkZS5wYW5lbCAlPiUKICBtdXRhdGUoCiAgICBzY2hvb2xfZGlzdCA9IG5uX2Z1bmN0aW9uKGZyb20sIHRvLCAzKQogICkKYGBgCiMjIyAzLjQuIENyZWF0ZSB0aW1lIGxhZ3MKCkNyZWF0aW5nIHRpbWUgbGFnIHZhcmlhYmxlcyB3aWxsIGFkZCBhZGRpdGlvbmFsIG51YW5jZSBhYm91dCB0aGUgZGVtYW5kIGR1cmluZyBhIGdpdmVuIHRpbWUgcGVyaW9kIC0gaG91cnMgYmVmb3JlIGFuZCBkdXJpbmcgdGhhdCBkYXkuIAoKV2UgY2FuIGFsc28gdHJ5IHRvIGNvbnRyb2wgZm9yIHRoZSBlZmZlY3RzIG9mIGhvbGlkYXlzIHRoYXQgZGlzcnVwdCB0aGUgZXhwZWN0ZWQgZGVtYW5kIGR1cmluZyBhIGdpdmVuIHdlZWtlbmQgb3Igd2Vla2RheS4gV2UgaGF2ZSBhIGhvbGlkYXkgb24gTWF5IDI4IC0gTWVtb3JpYWwgRGF5LiBGb3IgdGhhdCB0aHJlZSBkYXkgd2Vla2VuZCB3ZSBjb3VsZCB1c2Ugc29tZSBkdW1teSB2YXJpYWJsZXMgaW5kaWNhdGluZyB0ZW1wb3JhbCBwcm94aW1pdHkgdG8gdGhlIGhvbGlkYXkuCgpUaGUgZGVtYW5kIHJpZ2h0IG5vdyBzaG91bGQgYmUgcmVsYXRpdmVseSBzaW1pbGFyIHRvIHRoZSBkZW1hbmQgdG9tb3Jyb3cgYXQgdGhpcyB0aW1lLCBhbmQgdG8gdGhlIGRlbWFuZCBhbiBob3VyIGZyb20gbm93LCBidXQgdHdlbHZlIGhvdXJzIGZyb20gbm93LCB3ZSBsaWtlbHkgZXhwZWN0IHRoZSBvcHBvc2l0ZSBpbiB0ZXJtcyBvZiBkZW1hbmQuCgoKYGBge3IgdGltZV9sYWdzICwgbWVzc2FnZSA9IEZBTFNFfQpyaWRlLnBhbmVsIDwtIAogIHJpZGUucGFuZWwgJT4lIAogIGFycmFuZ2UoZnJvbV9zdGF0aW9uX25hbWUsIGludGVydmFsNjApICU+JSAKICBtdXRhdGUobGFnSG91ciA9IGRwbHlyOjpsYWcoVHJpcF9Db3VudCwxKSwKICAgICAgICAgbGFnMkhvdXJzID0gZHBseXI6OmxhZyhUcmlwX0NvdW50LDIpLAogICAgICAgICBsYWczSG91cnMgPSBkcGx5cjo6bGFnKFRyaXBfQ291bnQsMyksCiAgICAgICAgIGxhZzRIb3VycyA9IGRwbHlyOjpsYWcoVHJpcF9Db3VudCw0KSwKICAgICAgICAgbGFnMTJIb3VycyA9IGRwbHlyOjpsYWcoVHJpcF9Db3VudCwxMiksCiAgICAgICAgIGxhZzFkYXkgPSBkcGx5cjo6bGFnKFRyaXBfQ291bnQsMjQpLAogICAgICAgICBob2xpZGF5ID0gaWZlbHNlKHlkYXkoaW50ZXJ2YWw2MCkgPT0gMTQ4LDEsMCkpICU+JQogICBtdXRhdGUoZGF5ID0geWRheShpbnRlcnZhbDYwKSkgJT4lCiAgIG11dGF0ZShob2xpZGF5TGFnID0gY2FzZV93aGVuKGRwbHlyOjpsYWcoaG9saWRheSwgMSkgPT0gMSB+ICJQbHVzT25lRGF5IiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZHBseXI6OmxhZyhob2xpZGF5LCAyKSA9PSAxIH4gIlBsdXN0VHdvRGF5cyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRwbHlyOjpsYWcoaG9saWRheSwgMykgPT0gMSB+ICJQbHVzdFRocmVlRGF5cyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRwbHlyOjpsZWFkKGhvbGlkYXksIDEpID09IDEgfiAiTWludXNPbmVEYXkiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkcGx5cjo6bGVhZChob2xpZGF5LCAyKSA9PSAxIH4gIk1pbnVzVHdvRGF5cyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRwbHlyOjpsZWFkKGhvbGlkYXksIDMpID09IDEgfiAiTWludXNUaHJlZURheXMiKSwKICAgICAgICAgI2hvbGlkYXlMYWcgPSByZXBsYWNlX25hKGhvbGlkYXlMYWcsIDApKQogICAgICAgICBob2xpZGF5TGFnID0gaWZlbHNlKGlzLm5hKGhvbGlkYXlMYWcpID09IFRSVUUsIDAsIGhvbGlkYXlMYWcpKQoKYGBgCgpgYGB7ciBldmFsdWF0ZV9sYWdzICwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0V9CmFzLmRhdGEuZnJhbWUocmlkZS5wYW5lbCkgJT4lCiAgICBncm91cF9ieShpbnRlcnZhbDYwKSAlPiUgCiAgICBzdW1tYXJpc2VfYXQodmFycyhzdGFydHNfd2l0aCgibGFnIiksICJUcmlwX0NvdW50IiksIG1lYW4sIG5hLnJtID0gVFJVRSkgJT4lCiAgICBnYXRoZXIoVmFyaWFibGUsIFZhbHVlLCAtaW50ZXJ2YWw2MCwgLVRyaXBfQ291bnQpICU+JQogICAgbXV0YXRlKFZhcmlhYmxlID0gZmFjdG9yKFZhcmlhYmxlLCBsZXZlbHM9YygibGFnSG91ciIsImxhZzJIb3VycyIsImxhZzNIb3VycyIsImxhZzRIb3VycyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJsYWcxMkhvdXJzIiwibGFnMWRheSIsInNjaG9vbF9kaXN0IikpKSU+JQogICAgZ3JvdXBfYnkoVmFyaWFibGUpICU+JSAgCiAgICBzdW1tYXJpemUoY29ycmVsYXRpb24gPSByb3VuZChjb3IoVmFsdWUsIFRyaXBfQ291bnQpLDIpKQpgYGAKCgojIyA0LiBSdW4gTW9kZWxzCgpXZSBzcGxpdCBvdXIgZGF0YSBpbnRvIGEgdHJhaW5pbmcgYW5kIGEgdGVzdCBzZXQuIFdlIGNyZWF0ZSBmaXZlIGxpbmVhciBtb2RlbHMgdXNpbmcgdGhlIGBsbWAgZnVudGlvbi4gU29tZXRpbWVzLCBmb3IgZGF0YSBzdWNoIGFzIHRoZXNlLCBQb2lzc29uIGRpc3RyaWJ1dGlvbnMsIGRlc2lnbmVkIGZvciBtb2RlbGluZyBjb3VudHMsIG1pZ2h0IGJlIGFwcHJvcHJpYXRlLiBJJ2xsIHNwYXJlIHlvdSB0aGUgZWZmb3J0IC0gbGluZWFyIG1vZGVscyB3b3JrIGJldHRlciB3aXRoIHRoaXMgcGFydGljdWxhciBkYXRhIHNldC4gCgpXZSBjcmVhdGUgdGhlIG1vZGVscyB1c2luZyBvdXIgdHJhaW5pbmcgZGF0YSBgcmlkZS5UcmFpbmAuIFRoZSBmaXJzdCBtb2RlbHMgaW5jbHVkZSBvbmx5IHRlbXBvcmFsIGNvbnRyb2xzLCBidXQgdGhlIGxhdGVyIG9uZXMgY29udGFpbiBhbGwgb2Ygb3VyIGxhZyBpbmZvcm1hdGlvbi4KCgpgYGB7ciB0cmFpbl90ZXN0IH0KcmlkZS5UcmFpbiA8LSByaWRlLnBhbmVsICU+JSBmaWx0ZXIoLiwgd2VlayA+PSAyMCkKcmlkZS5UZXN0IDwtIHJpZGUucGFuZWwgJT4lIGZpbHRlciguLCB3ZWVrIDwgMjApCmBgYAoKCmBgYHtyIGZpdmVfbW9kZWxzIH0KcmVnMSA8LSAKICBsbShUcmlwX0NvdW50IH4gIGhvdXIoaW50ZXJ2YWw2MCkgKyBkb3R3ICsgVGVtcGVyYXR1cmUsICBkYXRhPXJpZGUuVHJhaW4pCgpyZWcyIDwtIAogIGxtKFRyaXBfQ291bnQgfiAgZnJvbV9zdGF0aW9uX25hbWUgKyBkb3R3ICsgVGVtcGVyYXR1cmUsICBkYXRhPXJpZGUuVHJhaW4pCgpyZWczIDwtIAogIGxtKFRyaXBfQ291bnQgfiAgZnJvbV9zdGF0aW9uX25hbWUgKyBob3VyKGludGVydmFsNjApICsgZG90dyArIFRlbXBlcmF0dXJlICsgUHJlY2lwaXRhdGlvbiwgCiAgICAgZGF0YT1yaWRlLlRyYWluKQoKcmVnNCA8LSAKICBsbShUcmlwX0NvdW50IH4gIGZyb21fc3RhdGlvbl9uYW1lICsgIGhvdXIoaW50ZXJ2YWw2MCkgKyBkb3R3ICsgVGVtcGVyYXR1cmUgKyBQcmVjaXBpdGF0aW9uICsKICAgICAgICAgICAgICAgICAgIGxhZ0hvdXIgKyBsYWcySG91cnMgK2xhZzNIb3VycyArIGxhZzEySG91cnMgKyBsYWcxZGF5LCAKICAgICBkYXRhPXJpZGUuVHJhaW4pCgpyZWc1IDwtIAogIGxtKFRyaXBfQ291bnQgfiAgZnJvbV9zdGF0aW9uX25hbWUgKyBob3VyKGludGVydmFsNjApICsgZG90dyArIAogICAgICAgICAgICAgICAgICBUZW1wZXJhdHVyZSArIFByZWNpcGl0YXRpb24gKyBsYWdIb3VyICsgbGFnMkhvdXJzICsKICAgICAgICAgICAgICAgICAgbGFnM0hvdXJzICtsYWcxMkhvdXJzICsgbGFnMWRheSArIGhvbGlkYXlMYWcgKyBob2xpZGF5ICsgc2Nob29sX2Rpc3QsIAogICAgIGRhdGE9cmlkZS5UcmFpbikKYGBgCgojIyMgNC4xLiBQcmVkaWN0IGZvciB0ZXN0IGRhdGEKCldoZW4gbW9kZWxzIGhhdmUgZmluaXNoZWQgcnVubmluZywgY3JlYXRlIGEgbmVzdGVkIGRhdGEgZnJhbWUgb2YgdGVzdCBkYXRhIGJ5IHdlZWsuIE5lc3RlZCBkYXRhIGlzIGNvbW1vbiBpbiBtb3N0IG90aGVyIHByb2dyYW1taW5nIGxhbmd1YWdlcy4gRm9yIGluc3RhbmNlLCB0aGUgamF2YXNjcmlwdCBvYmplY3Qgbm90YXRpb24gZmlsZSBmb3JtYXQgKGFrYSBKU09OKSBpcyBoaWdobHkgbmVzdGVkLgoKTmVzdGluZyBtZWFucyB0aGF0IGluc3RlYWQgb2YgbWVyZWx5IGhhdmluZyBhICJmbGF0IiBmaWxlIGNvbnNpc3Rpbmcgb2Ygcm93cyBhbmQgY29sdW1ucywgd2UgaGF2ZSBhIG1hdHJpeCBvZiBvdGhlciBvYmplY3RzIC0gaW1hZ2luZSBlYWNoIGNlbGwgaW4gYSBtYXRyaXggY29udGFpbmluZyBhbm90aGVyIG1hdHJpeCB3aXRoaW4gaXQsIG9yIGEgbGlzdCwgb3IgYSBsaXN0IG9mIGxpc3RzLiAKClRoZSBgcHVycnJgIHBhY2thZ2UgaXMgZGVzaWduZWQgdG8gYG1hcGAgZnVuY3Rpb25zIHRocm91Z2ggbmVzdGVkIGRhdGEgc3RydWN0dXJlcy4gVGhpcyBjb25jZXB0IGlzIGltcG9ydGFudCAtIHRoaW5rIG9mIGBtYXBgIGFzIHZpc2l0aW5nIGVhY2ggZGF0YWZyYW1lIGluIGEgbmVzdGVkIGRhdGEgc2V0IGFuZCBhcHBsaWVzIGEgZnVuY3Rpb24gdG8gaXQuCgpXZSBjcmVhdGUgYSBmdW5jdGlvbiBjYWxsZWQgYG1vZGVsX3ByZWRgIHdoaWNoIHdlIGNhbiB0aGVuIGBtYXBgIG9udG8gZWFjaCBkYXRhIGZyYW1lIGluIG91ciBuZXN0ZWQgc3RydWN0dXJlLgoKVGhpcyBmdW5jdGlvbiBpcyBjYWxsZWQgaW4gdGhlIGNvZGUgYmVsb3cgaW4gYSBmZXcgd2F5cywgb25lIHdheSBpcyBsaWtlIHNvOiBgbWFwKC54ID0gZGF0YSwgZml0ID0gbmFtZV9vZl95b3VyX3JlZ3Jlc3Npb24sIC5mID0gbW9kZWxfcHJlZClgLiBIZXJlJ3MgdGhlIGltcG9ydGFudCBiaXQgLSB0aGUgYXJndW1lbnQgYGZpdGAgdGFrZXMgdGhlIG5hbWUgb2YgYSByZWdyZXNzaW9uIHlvdSBoYXZlIGNyZWF0ZWQgdGhhdCB5b3Ugd2FudCB0byB1c2UgdG8gbWFrZSBwcmVkaWN0aW9ucywgYW5kIHRoZSBgLmZgIGFyZ3VtZW50IHRha2VzIGEgZnVuY3Rpb24sIGluIHRoaXMgY2FzZSBgbW9kZWxfcHJlZGAsIHdoaWNoIHdlIGNyZWF0ZSBpbiBvcmRlciB0byBzaW1wbHkgZXhlY3V0ZSB0aGUgYHByZWRpY3RgIGZ1bmN0aW9uLgoKYGBge3IgbmVzdF9kYXRhICwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0V9CnJpZGUuVGVzdC53ZWVrTmVzdCA8LSAKICByaWRlLlRlc3QgJT4lCiAgbmVzdCgtd2VlaykgCmBgYAoKCmBgYHtyIHByZWRpY3RfZnVuY3Rpb24gfQptb2RlbF9wcmVkIDwtIGZ1bmN0aW9uKGRhdCwgZml0KXsKICAgcHJlZCA8LSBwcmVkaWN0KGZpdCwgbmV3ZGF0YSA9IGRhdCl9CmBgYAoKV2hlbiB3ZSBydW4gb3VyIHByZWRpY3Rpb25zIGFuZCBzdW1tYXJpemUgb3VyIHJlc3VsdHMsIHdlIGFyZSBnb2luZyB0byBoYXZlIHNvbWUgTkEgZGF0YSAtIHJlY2FsbCB3ZSBoYXZlIHNvbWUgbGFnIGluZm9ybWF0aW9uIHRoYXQgd2lsbCBuZWNlc3NhcmlseSB0cmlwIHVwIHRoZSBtb2RlbCBhdCB0aGUgbWFyZ2lucyBvZiB0aGUgdGltZSBmcmFtZS4gCgpgYGB7ciBkb19wcmVkaWNpdG9ucywgbWVzc2FnZT1GQUxTRX0Kd2Vla19wcmVkaWN0aW9ucyA8LSAKICByaWRlLlRlc3Qud2Vla05lc3QgJT4lIAogICAgbXV0YXRlKEFUaW1lX0ZFID0gbWFwKC54ID0gZGF0YSwgZml0ID0gcmVnMSwgLmYgPSBtb2RlbF9wcmVkKSwKICAgICAgICAgICBCU3BhY2VfRkUgPSBtYXAoLnggPSBkYXRhLCBmaXQgPSByZWcyLCAuZiA9IG1vZGVsX3ByZWQpLAogICAgICAgICAgIENUaW1lX1NwYWNlX0ZFID0gbWFwKC54ID0gZGF0YSwgZml0ID0gcmVnMywgLmYgPSBtb2RlbF9wcmVkKSwKICAgICAgICAgICBEVGltZV9TcGFjZV9GRV90aW1lTGFncyA9IG1hcCgueCA9IGRhdGEsIGZpdCA9IHJlZzQsIC5mID0gbW9kZWxfcHJlZCksCiAgICAgICAgICAgRVRpbWVfU3BhY2VfRkVfdGltZUxhZ3NfaG9saWRheUxhZ3MgPSBtYXAoLnggPSBkYXRhLCBmaXQgPSByZWc1LCAuZiA9IG1vZGVsX3ByZWQpKSAlPiUgCiAgICBnYXRoZXIoUmVncmVzc2lvbiwgUHJlZGljdGlvbiwgLWRhdGEsIC13ZWVrKSAlPiUKICAgIG11dGF0ZShPYnNlcnZlZCA9IG1hcChkYXRhLCBwdWxsLCBUcmlwX0NvdW50KSwKICAgICAgICAgICBBYnNvbHV0ZV9FcnJvciA9IG1hcDIoT2JzZXJ2ZWQsIFByZWRpY3Rpb24sICB+IGFicygueCAtIC55KSksCiAgICAgICAgICAgTUFFID0gbWFwX2RibChBYnNvbHV0ZV9FcnJvciwgbWVhbiwgbmEucm0gPSBUUlVFKSwKICAgICAgICAgICBzZF9BRSA9IG1hcF9kYmwoQWJzb2x1dGVfRXJyb3IsIHNkLCBuYS5ybSA9IFRSVUUpKQoKd2Vla19wcmVkaWN0aW9ucwpgYGAKCiMjIDUuIEFjY3VyYWN5CgojIyMgNS4xIE1BRQoKYGBge3IgcGxvdF9lcnJvcnNfYnlfbW9kZWwgfQp3ZWVrX3ByZWRpY3Rpb25zICU+JQogIGRwbHlyOjpzZWxlY3Qod2VlaywgUmVncmVzc2lvbiwgTUFFKSAlPiUKICBnYXRoZXIoVmFyaWFibGUsIE1BRSwgLVJlZ3Jlc3Npb24sIC13ZWVrKSAlPiUKICBnZ3Bsb3QoYWVzKHdlZWssIE1BRSkpICsgCiAgICBnZW9tX2JhcihhZXMoZmlsbCA9IFJlZ3Jlc3Npb24pLCBwb3NpdGlvbiA9ICJkb2RnZSIsIHN0YXQ9ImlkZW50aXR5IikgKwogICAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gcGFsZXR0ZTUpICsKICAgIGxhYnModGl0bGUgPSAiTWVhbiBBYnNvbHV0ZSBFcnJvcnMgYnkgbW9kZWwgc3BlY2lmaWNhdGlvbiBhbmQgd2VlayIpICsKICBwbG90VGhlbWUoKQpgYGAKV2UgY291bGQgc2VlIHRoYXQgcmVncmVzc2lvbiBEIGFuZCBFIGhhcyByZWxhdGl2ZWx5IGxvdyBNQUUgYnkgbW9kZWwgc3BlY2lmaWNhdGlvbiBhbmQgd2Vlay4KCiMjIyA1LjIgYHJlZzVgIHNlZW1zIHRvIGhhdmUgdGhlIGJlc3QgZ29vZG5lc3Mgb2YgZml0IGdlbmVyYWxseS4KCmBgYHtyIGVycm9yX3ZzX2FjdHVhbF90aW1lc2VyaWVzICwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0V9CndlZWtfcHJlZGljdGlvbnMgJT4lIAogICAgbXV0YXRlKGludGVydmFsNjAgPSBtYXAoZGF0YSwgcHVsbCwgaW50ZXJ2YWw2MCksCiAgICAgICAgICAgZnJvbV9zdGF0aW9uX2lkID0gbWFwKGRhdGEsIHB1bGwsIGZyb21fc3RhdGlvbl9pZCkpICU+JQogICAgZHBseXI6OnNlbGVjdChpbnRlcnZhbDYwLCBmcm9tX3N0YXRpb25faWQsIE9ic2VydmVkLCBQcmVkaWN0aW9uLCBSZWdyZXNzaW9uKSAlPiUKICAgIHVubmVzdCgpICU+JQogICAgZ2F0aGVyKFZhcmlhYmxlLCBWYWx1ZSwgLVJlZ3Jlc3Npb24sIC1pbnRlcnZhbDYwLCAtZnJvbV9zdGF0aW9uX2lkKSAlPiUKICAgIGdyb3VwX2J5KFJlZ3Jlc3Npb24sIFZhcmlhYmxlLCBpbnRlcnZhbDYwKSAlPiUKICAgIHN1bW1hcml6ZShWYWx1ZSA9IHN1bShWYWx1ZSkpICU+JQogICAgZ2dwbG90KGFlcyhpbnRlcnZhbDYwLCBWYWx1ZSwgY29sb3VyPVZhcmlhYmxlKSkgKyAKICAgICAgZ2VvbV9saW5lKHNpemUgPSAxLjEpICsgCiAgICAgIGZhY2V0X3dyYXAoflJlZ3Jlc3Npb24sIG5jb2w9MSkgKwogICAgICBsYWJzKHRpdGxlID0gIlByZWRpY3RlZC9PYnNlcnZlZCBiaWtlIHNoYXJlIHRpbWUgc2VyaWVzIiwgc3VidGl0bGUgPSAiUGhpbGFkZWxwaGlhOyBBIHRlc3Qgc2V0IG9mIDIgd2Vla3MiLCAgeCA9ICJIb3VyIiwgeT0gIlN0YXRpb24gVHJpcHMiKSArCiAgICAgIHBsb3RUaGVtZSgpCmBgYAoKCmBgYHtyIGVycm9yc19ieV9zdGF0aW9uLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRSB9CndlZWtfcHJlZGljdGlvbnMgJT4lIAogICAgbXV0YXRlKGludGVydmFsNjAgPSBtYXAoZGF0YSwgcHVsbCwgaW50ZXJ2YWw2MCksCiAgICAgICAgICAgZnJvbV9zdGF0aW9uX2lkID0gbWFwKGRhdGEsIHB1bGwsIGZyb21fc3RhdGlvbl9pZCksIAogICAgICAgICAgIGZyb21fbGF0aXR1ZGUgPSBtYXAoZGF0YSwgcHVsbCwgZnJvbV9sYXRpdHVkZSksIAogICAgICAgICAgIGZyb21fbG9uZ2l0dWRlID0gbWFwKGRhdGEsIHB1bGwsIGZyb21fbG9uZ2l0dWRlKSkgJT4lCiAgICBzZWxlY3QoaW50ZXJ2YWw2MCwgZnJvbV9zdGF0aW9uX2lkLCBmcm9tX2xvbmdpdHVkZSwgZnJvbV9sYXRpdHVkZSwgT2JzZXJ2ZWQsIFByZWRpY3Rpb24sIFJlZ3Jlc3Npb24pICU+JQogICAgdW5uZXN0KCkgJT4lCiAgZmlsdGVyKFJlZ3Jlc3Npb24gPT0gIkVUaW1lX1NwYWNlX0ZFX3RpbWVMYWdzX2hvbGlkYXlMYWdzIikgJT4lCiAgZ3JvdXBfYnkoZnJvbV9zdGF0aW9uX2lkLCBmcm9tX2xvbmdpdHVkZSwgZnJvbV9sYXRpdHVkZSkgJT4lCiAgc3VtbWFyaXplKE1BRSA9IG1lYW4oYWJzKE9ic2VydmVkLVByZWRpY3Rpb24pLCBuYS5ybSA9IFRSVUUpKSU+JQpnZ3Bsb3QoLikrCiAgZ2VvbV9zZihkYXRhID0gcGhpbGx5Q2Vuc3VzLCBjb2xvciA9ICJncmV5IiwgZmlsbCA9ICJ0cmFuc3BhcmVudCIpKwogIGdlb21fcG9pbnQoYWVzKHggPSBmcm9tX2xvbmdpdHVkZSwgeSA9IGZyb21fbGF0aXR1ZGUsIGNvbG9yID0gTUFFKSwgCiAgICAgICAgICAgICBmaWxsID0gInRyYW5zcGFyZW50IiwgYWxwaGEgPSAwLjQpKwogIHNjYWxlX2NvbG91cl92aXJpZGlzKGRpcmVjdGlvbiA9IC0xLAogIGRpc2NyZXRlID0gRkFMU0UsIG9wdGlvbiA9ICJEIikrCiAgeWxpbShtaW4oZGF0X2NlbnN1cyRmcm9tX2xhdGl0dWRlKSwgbWF4KGRhdF9jZW5zdXMkZnJvbV9sYXRpdHVkZSkpKwogIHhsaW0obWluKGRhdF9jZW5zdXMkZnJvbV9sb25naXR1ZGUpLCBtYXgoZGF0X2NlbnN1cyRmcm9tX2xvbmdpdHVkZSkpKwogIGxhYnModGl0bGU9Ik1lYW4gQWJzIEVycm9yLCBUZXN0IFNldCwgTW9kZWwgNSIpKwogIG1hcFRoZW1lKCkKYGBgClRoZXJlIGFyZSBzb21lIE1BRSBhcm91bmQgZG93bnRvd24gYXJlYSBhbmQgc3ByZWFkIHRvIFVuaXZlcnNpdHkgQ2l0eSBhbmQgbmVhcmJ5IGFyZWFzLgoKIyMjIDUuMy4gU3BhY2UtVGltZSBFcnJvciBFdmFsdWF0aW9uCgpJZiB3ZSBwbG90IG9ic2VydmVkIHZzLiBwcmVkaWN0ZWQgZm9yIGRpZmZlcmVudCB0aW1lcyBvZiBkYXkgZHVyaW5nIHRoZSB3ZWVrIGFuZCB3ZWVrZW5kLCBzb21lIHBhdHRlcm5zIGJlZ2luIHRvIGVtZXJnZS4gCgpgYGB7ciBvYnNfcHJlZF9hbGwsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2UgPSBGQUxTRSwgY2FjaGU9VFJVRX0Kd2Vla19wcmVkaWN0aW9ucyAlPiUgCiAgICBtdXRhdGUoaW50ZXJ2YWw2MCA9IG1hcChkYXRhLCBwdWxsLCBpbnRlcnZhbDYwKSwKICAgICAgICAgICBmcm9tX3N0YXRpb25faWQgPSBtYXAoZGF0YSwgcHVsbCwgZnJvbV9zdGF0aW9uX2lkKSwgCiAgICAgICAgICAgZnJvbV9sYXRpdHVkZSA9IG1hcChkYXRhLCBwdWxsLCBmcm9tX2xhdGl0dWRlKSwgCiAgICAgICAgICAgZnJvbV9sb25naXR1ZGUgPSBtYXAoZGF0YSwgcHVsbCwgZnJvbV9sb25naXR1ZGUpLAogICAgICAgICAgIGRvdHcgPSBtYXAoZGF0YSwgcHVsbCwgZG90dykpICU+JQogICAgc2VsZWN0KGludGVydmFsNjAsIGZyb21fc3RhdGlvbl9pZCwgZnJvbV9sb25naXR1ZGUsIAogICAgICAgICAgIGZyb21fbGF0aXR1ZGUsIE9ic2VydmVkLCBQcmVkaWN0aW9uLCBSZWdyZXNzaW9uLAogICAgICAgICAgIGRvdHcpICU+JQogICAgdW5uZXN0KCkgJT4lCiAgZmlsdGVyKFJlZ3Jlc3Npb24gPT0gIkVUaW1lX1NwYWNlX0ZFX3RpbWVMYWdzX2hvbGlkYXlMYWdzIiklPiUKICBtdXRhdGUod2Vla2VuZCA9IGlmZWxzZShkb3R3ICVpbiUgYygiU3VuIiwgIlNhdCIpLCAiV2Vla2VuZCIsICJXZWVrZGF5IiksCiAgICAgICAgIHRpbWVfb2ZfZGF5ID0gY2FzZV93aGVuKGhvdXIoaW50ZXJ2YWw2MCkgPCA3IHwgaG91cihpbnRlcnZhbDYwKSA+IDE4IH4gIk92ZXJuaWdodCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhvdXIoaW50ZXJ2YWw2MCkgPj0gNyAmIGhvdXIoaW50ZXJ2YWw2MCkgPCAxMCB+ICJBTSBSdXNoIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaG91cihpbnRlcnZhbDYwKSA+PSAxMCAmIGhvdXIoaW50ZXJ2YWw2MCkgPCAxNSB+ICJNaWQtRGF5IiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaG91cihpbnRlcnZhbDYwKSA+PSAxNSAmIGhvdXIoaW50ZXJ2YWw2MCkgPD0gMTggfiAiUE0gUnVzaCIpKSU+JQogIGdncGxvdCgpKwogIGdlb21fcG9pbnQoYWVzKHg9IE9ic2VydmVkLCB5ID0gUHJlZGljdGlvbikpKwogICAgZ2VvbV9zbW9vdGgoYWVzKHg9IE9ic2VydmVkLCB5PSBQcmVkaWN0aW9uKSwgbWV0aG9kID0gImxtIiwgc2UgPSBGQUxTRSwgY29sb3IgPSAicmVkIikrCiAgICBnZW9tX2FibGluZShzbG9wZSA9IDEsIGludGVyY2VwdCA9IDApKwogIGZhY2V0X2dyaWQodGltZV9vZl9kYXl+d2Vla2VuZCkrCiAgbGFicyh0aXRsZT0iT2JzZXJ2ZWQgdnMgUHJlZGljdGVkIiwKICAgICAgIHg9Ik9ic2VydmVkIHRyaXBzIiwgCiAgICAgICB5PSJQcmVkaWN0ZWQgdHJpcHMiKSsKICBwbG90VGhlbWUoKQpgYGAKCldlZWtlbmQncyBtb3JuaW5nIGlzIHRoZSBoYXJkZXN0IHRvIHByZWRpY3QuIAoKIyMjIDUuNC4gTUFFIG1hcCBieSB3ZWVrZW5kL3dlZWtkYXkgYW5kIHRpbWUgb2YgZGF5LgoKYGBge3Igc3RhdGlvbl9zdW1tYXJ5LCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlID0gRkFMU0UgfQp3ZWVrX3ByZWRpY3Rpb25zICU+JSAKICAgIG11dGF0ZShpbnRlcnZhbDYwID0gbWFwKGRhdGEsIHB1bGwsIGludGVydmFsNjApLAogICAgICAgICAgIGZyb21fc3RhdGlvbl9pZCA9IG1hcChkYXRhLCBwdWxsLCBmcm9tX3N0YXRpb25faWQpLCAKICAgICAgICAgICBmcm9tX2xhdGl0dWRlID0gbWFwKGRhdGEsIHB1bGwsIGZyb21fbGF0aXR1ZGUpLCAKICAgICAgICAgICBmcm9tX2xvbmdpdHVkZSA9IG1hcChkYXRhLCBwdWxsLCBmcm9tX2xvbmdpdHVkZSksCiAgICAgICAgICAgZG90dyA9IG1hcChkYXRhLCBwdWxsLCBkb3R3KSApICU+JQogICAgc2VsZWN0KGludGVydmFsNjAsIGZyb21fc3RhdGlvbl9pZCwgZnJvbV9sb25naXR1ZGUsIAogICAgICAgICAgIGZyb21fbGF0aXR1ZGUsIE9ic2VydmVkLCBQcmVkaWN0aW9uLCBSZWdyZXNzaW9uLAogICAgICAgICAgIGRvdHcpICU+JQogICAgdW5uZXN0KCkgJT4lCiAgZmlsdGVyKFJlZ3Jlc3Npb24gPT0gIkVUaW1lX1NwYWNlX0ZFX3RpbWVMYWdzX2hvbGlkYXlMYWdzIiklPiUKICBtdXRhdGUod2Vla2VuZCA9IGlmZWxzZShkb3R3ICVpbiUgYygiU3VuIiwgIlNhdCIpLCAiV2Vla2VuZCIsICJXZWVrZGF5IiksCiAgICAgICAgIHRpbWVfb2ZfZGF5ID0gY2FzZV93aGVuKGhvdXIoaW50ZXJ2YWw2MCkgPCA3IHwgaG91cihpbnRlcnZhbDYwKSA+IDE4IH4gIk92ZXJuaWdodCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhvdXIoaW50ZXJ2YWw2MCkgPj0gNyAmIGhvdXIoaW50ZXJ2YWw2MCkgPCAxMCB+ICJBTSBSdXNoIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaG91cihpbnRlcnZhbDYwKSA+PSAxMCAmIGhvdXIoaW50ZXJ2YWw2MCkgPCAxNSB+ICJNaWQtRGF5IiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaG91cihpbnRlcnZhbDYwKSA+PSAxNSAmIGhvdXIoaW50ZXJ2YWw2MCkgPD0gMTggfiAiUE0gUnVzaCIpKSAlPiUKICBncm91cF9ieShmcm9tX3N0YXRpb25faWQsIHdlZWtlbmQsIHRpbWVfb2ZfZGF5LCBmcm9tX2xvbmdpdHVkZSwgZnJvbV9sYXRpdHVkZSkgJT4lCiAgc3VtbWFyaXplKE1BRSA9IG1lYW4oYWJzKE9ic2VydmVkLVByZWRpY3Rpb24pLCBuYS5ybSA9IFRSVUUpKSU+JQogIGdncGxvdCguKSsKICBnZW9tX3NmKGRhdGEgPSBwaGlsbHlDZW5zdXMsIGNvbG9yID0gImdyZXkiLCBmaWxsID0gInRyYW5zcGFyZW50IikrCiAgZ2VvbV9wb2ludChhZXMoeCA9IGZyb21fbG9uZ2l0dWRlLCB5ID0gZnJvbV9sYXRpdHVkZSwgY29sb3IgPSBNQUUpLCAKICAgICAgICAgICAgIGZpbGwgPSAidHJhbnNwYXJlbnQiLCBzaXplID0gMC41LCBhbHBoYSA9IDAuNCkrCiAgc2NhbGVfY29sb3VyX3ZpcmlkaXMoZGlyZWN0aW9uID0gLTEsCiAgZGlzY3JldGUgPSBGQUxTRSwgb3B0aW9uID0gIkQiKSsKICB5bGltKG1pbihkYXRfY2Vuc3VzJGZyb21fbGF0aXR1ZGUpLCBtYXgoZGF0X2NlbnN1cyRmcm9tX2xhdGl0dWRlKSkrCiAgeGxpbShtaW4oZGF0X2NlbnN1cyRmcm9tX2xvbmdpdHVkZSksIG1heChkYXRfY2Vuc3VzJGZyb21fbG9uZ2l0dWRlKSkrCiAgZmFjZXRfZ3JpZCh3ZWVrZW5kfnRpbWVfb2ZfZGF5KSsKICBsYWJzKHRpdGxlPSJNZWFuIEFic29sdXRlIEVycm9ycywgVGVzdCBTZXQiKSsKICBtYXBUaGVtZSgpCiAgCmBgYAoKU2VlbXMgbGlrZSBlcnJvcnMgYXJlIGNvbmNlbnRyYXRlZCBpbiBkb3dudG93biBhcmVhcy4gVGhlIHBhdHRlcm4gaGFzIGJlZW4gdmlzdWFsaXplZCBib3RoIGFzIHRoZSBzY2F0dGVyIHBsb3QgYW5kIHRoZSBzcGF0aWFsIG1hcC4gVGhlIHJpZGVyc2hpcCBpcyBoaWdoIGR1cmluZyB0aGUgZXZlbmluZyBydXNoIGFzIGNvbXBhcmVkIHRvIHRoZSBtb3JuaW5nIHJ1c2gsIGhlbmNlIHdoeSB0aGUgZXJyb3JzIGFyZSBoaWdoZXIgaW4gdGhlIHdlZWtkYXkgcHJlZGljdGlvbnMuIAoKIyMjIDUuNSBFcnJvcnMgYXMgYSBmdW5jdGlvbiBvZiBzb2Npby1lY29ub21pYyB2YXJpYWJsZXMKCmBgYHtyIHN0YXRpb25fc3VtbWFyeTIsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2UgPSBGQUxTRSB9CndlZWtfcHJlZGljdGlvbnMgJT4lIAogICAgbXV0YXRlKGludGVydmFsNjAgPSBtYXAoZGF0YSwgcHVsbCwgaW50ZXJ2YWw2MCksCiAgICAgICAgICAgZnJvbV9zdGF0aW9uX2lkID0gbWFwKGRhdGEsIHB1bGwsIGZyb21fc3RhdGlvbl9pZCksIAogICAgICAgICAgIGZyb21fbGF0aXR1ZGUgPSBtYXAoZGF0YSwgcHVsbCwgZnJvbV9sYXRpdHVkZSksIAogICAgICAgICAgIGZyb21fbG9uZ2l0dWRlID0gbWFwKGRhdGEsIHB1bGwsIGZyb21fbG9uZ2l0dWRlKSwKICAgICAgICAgICBkb3R3ID0gbWFwKGRhdGEsIHB1bGwsIGRvdHcpLAogICAgICAgICAgIFBlcmNlbnRfVGFraW5nX1B1YmxpY19UcmFucyA9IG1hcChkYXRhLCBwdWxsLCBQZXJjZW50X1Rha2luZ19QdWJsaWNfVHJhbnMpLAogICAgICAgICAgIE1lZF9JbmMgPSBtYXAoZGF0YSwgcHVsbCwgTWVkX0luYyksCiAgICAgICAgICAgTWVkX0FnZSA9IG1hcChkYXRhLCBwdWxsLCBNZWRfQWdlKSwKICAgICAgICAgICBNZWFuX0NvbW11dGVfVGltZSA9IG1hcChkYXRhLHB1bGwsTWVhbl9Db21tdXRlX1RpbWUpLAogICAgICAgICAgIFBlcmNlbnRfV2hpdGUgPSBtYXAoZGF0YSwgcHVsbCwgUGVyY2VudF9XaGl0ZSkpICU+JQogICAgc2VsZWN0KGludGVydmFsNjAsIGZyb21fc3RhdGlvbl9pZCwgZnJvbV9sb25naXR1ZGUsIAogICAgICAgICAgIGZyb21fbGF0aXR1ZGUsIE9ic2VydmVkLCBQcmVkaWN0aW9uLCBSZWdyZXNzaW9uLAogICAgICAgICAgIGRvdHcsIE1lZF9JbmMsIE1lZF9BZ2UsIE1lYW5fQ29tbXV0ZV9UaW1lLFBlcmNlbnRfVGFraW5nX1B1YmxpY19UcmFucykgJT4lCiAgICB1bm5lc3QoKSAlPiUKICBmaWx0ZXIoUmVncmVzc2lvbiA9PSAiRVRpbWVfU3BhY2VfRkVfdGltZUxhZ3NfaG9saWRheUxhZ3MiKSU+JQogIG11dGF0ZSh3ZWVrZW5kID0gaWZlbHNlKGRvdHcgJWluJSBjKCJTdW4iLCAiU2F0IiksICJXZWVrZW5kIiwgIldlZWtkYXkiKSwKICAgICAgICAgdGltZV9vZl9kYXkgPSBjYXNlX3doZW4oaG91cihpbnRlcnZhbDYwKSA8IDcgfCBob3VyKGludGVydmFsNjApID4gMTggfiAiT3Zlcm5pZ2h0IiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaG91cihpbnRlcnZhbDYwKSA+PSA3ICYgaG91cihpbnRlcnZhbDYwKSA8IDEwIH4gIkFNIFJ1c2giLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBob3VyKGludGVydmFsNjApID49IDEwICYgaG91cihpbnRlcnZhbDYwKSA8IDE1IH4gIk1pZC1EYXkiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBob3VyKGludGVydmFsNjApID49IDE1ICYgaG91cihpbnRlcnZhbDYwKSA8PSAxOCB+ICJQTSBSdXNoIikpICU+JQogIGZpbHRlcih0aW1lX29mX2RheSA9PSAiQU0gUnVzaCIpICU+JQogIGdyb3VwX2J5KGZyb21fc3RhdGlvbl9pZCwgTWVkX0luYywgUGVyY2VudF9UYWtpbmdfUHVibGljX1RyYW5zLCBNZWFuX0NvbW11dGVfVGltZSkgJT4lCiAgc3VtbWFyaXplKE1BRSA9IG1lYW4oYWJzKE9ic2VydmVkLVByZWRpY3Rpb24pLCBuYS5ybSA9IFRSVUUpKSU+JQogIGdhdGhlcigtZnJvbV9zdGF0aW9uX2lkLCAtTUFFLCBrZXkgPSAidmFyaWFibGUiLCB2YWx1ZSA9ICJ2YWx1ZSIpJT4lCiAgZ2dwbG90KC4pKwogICNnZW9tX3NmKGRhdGEgPSBwaGlsbHlDZW5zdXMsIGNvbG9yID0gImdyZXkiLCBmaWxsID0gInRyYW5zcGFyZW50IikrCiAgZ2VvbV9wb2ludChhZXMoeCA9IHZhbHVlLCB5ID0gTUFFKSwgYWxwaGEgPSAwLjQpKwogIGdlb21fc21vb3RoKGFlcyh4ID0gdmFsdWUsIHkgPSBNQUUpLCBtZXRob2QgPSAibG0iLCBzZT0gRkFMU0UpKwogIGZhY2V0X3dyYXAofnZhcmlhYmxlLCBzY2FsZXMgPSAiZnJlZSIpKwogIGxhYnModGl0bGU9IkVycm9ycyBhcyBhIGZ1bmN0aW9uIG9mIHNvY2lvLWVjb25vbWljIHZhcmlhYmxlcyIsCiAgICAgICB5PSJNZWFuIEFic29sdXRlIEVycm9yIChUcmlwcykiKSsKICBwbG90VGhlbWUoKQogIApgYGAKCkxldCdzIGZvY3VzIG9uIHRoZSBtb3JuaW5nIGNvbW11dGUsIHdoZXJlIHN0YXRpb24gbG9jYXRpb25zIHByb2JhYmx5IHJlbGF0ZSB0byBsaWtlbHkgdXNlcnMuIEhvdyBpcyB0aGUgbW9kZWwgcGVyZm9ybWluZyBvbiB3ZWVrZGF5IG1vcm5pbmdzIHJlbGF0aXZlIHRvIGRlbWFuZCBmb3IgcHVibGljIHRyYW5zcG9ydGF0aW9uIChlLmcuIHBvc3NpYmxlIHVzZXIgYmFzZSkuIFdlIGNhbiB0ZWxsIHRoYXQgdGhlcmUgYXJlIGEgc2VsZWN0IGZldyBzdGF0aW9ucyB0aGF0IGFyZSBwcm92aW5nIHNpZ2h0bHkgcmVzaXN0YW50IHRvIG91ciBtb2RlbCAtIHRoZXkgaGF2ZSBsb25nIGNvbW11bmljYXRpb24gdGltZSwgaGlnaCBtZWFuIGluY29tZSBhbmQgbG93IHRyYW5zaXQgdXNhZ2UsIGRlbW9ncmFwaGljYWxseS4KCgoKIyMjIDUuNiAgQW5pbWF0ZWQgTWFwIGJ5IHNwYWNlL3RpbWUgZGVwZW5kZW5jaWVzCiAKYGBge3IgYW5pbWF0aW9uLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlID0gRkFMU0UgfQp3ZWVrMjEgPC0KICBmaWx0ZXIoZGF0X2NlbnN1cyAsIHdlZWsgPT0gMjEpCgp3ZWVrMjEucGFuZWwgPC0KICBleHBhbmQuZ3JpZCgKICAgIGludGVydmFsMTUgPSB1bmlxdWUod2VlazIxJGludGVydmFsMTUpLAogICAgUGlja3VwLkNlbnN1cy5UcmFjdCA9IHVuaXF1ZShkYXRfY2Vuc3VzJGZyb21fc3RhdGlvbl9pZCkpCgpyaWRlLmFuaW1hdGlvbi5kYXRhIDwtCiAgbXV0YXRlKHdlZWsyMSwgVHJpcF9Db3VudGVyID0gMSkgJT4lCiAgc2VsZWN0KGludGVydmFsMTUsIGZyb21fc3RhdGlvbl9pZCwgZnJvbV9sb25naXR1ZGUsIGZyb21fbGF0aXR1ZGUsIFRyaXBfQ291bnRlcikgJT4lCiAgZ3JvdXBfYnkoaW50ZXJ2YWwxNSwgZnJvbV9zdGF0aW9uX2lkLCBmcm9tX2xvbmdpdHVkZSwgZnJvbV9sYXRpdHVkZSkgJT4lCiAgc3VtbWFyaXplKFRyaXBfQ291bnQgPSBzdW0oVHJpcF9Db3VudGVyLCBuYS5ybT1UKSkgJT4lIAogIHVuZ3JvdXAoKSAlPiUgCiAgbXV0YXRlKFRyaXBzID0gY2FzZV93aGVuKFRyaXBfQ291bnQgPT0gMCB+ICIwIHRyaXBzIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgVHJpcF9Db3VudCA+IDAgJiBUcmlwX0NvdW50IDw9IDIgfiAiMC0yIHRyaXBzIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgVHJpcF9Db3VudCA+IDIgJiBUcmlwX0NvdW50IDw9IDUgfiAiMi01IHRyaXBzIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgVHJpcF9Db3VudCA+IDUgJiBUcmlwX0NvdW50IDw9IDEwIH4gIjUtMTAgdHJpcHMiLAogICAgICAgICAgICAgICAgICAgICAgICAgICBUcmlwX0NvdW50ID4gMTAgJiBUcmlwX0NvdW50IDw9IDE1IH4gIjEwLTE1IHRyaXBzIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgVHJpcF9Db3VudCA+IDE1IH4gIjE1KyB0cmlwcyIpKSAlPiUKICBtdXRhdGUoVHJpcHMgID0gZmN0X3JlbGV2ZWwoVHJpcHMsICIwIHRyaXBzIiwiMC0yIHRyaXBzIiwiMi01IHRyaXBzIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIjUtMTAgdHJpcHMiLCIxMC0xNSB0cmlwcyIsIjE1KyB0cmlwcyIpKQpsaWJyYXJ5KEZOTikKCm1hcFRoZW1lIDwtIGZ1bmN0aW9uKGJhc2Vfc2l6ZSA9IDEyKSB7CiAgdGhlbWUoCiAgICB0ZXh0ID0gZWxlbWVudF90ZXh0KCBjb2xvciA9ICJibGFjayIpLAogICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTgsY29sb3VyID0gImJsYWNrIiksCiAgICBwbG90LnN1YnRpdGxlPWVsZW1lbnRfdGV4dChmYWNlPSJpdGFsaWMiKSwKICAgIHBsb3QuY2FwdGlvbj1lbGVtZW50X3RleHQoaGp1c3Q9MCksCiAgICBheGlzLnRpY2tzID0gZWxlbWVudF9ibGFuaygpLAogICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSxheGlzLnRpdGxlID0gZWxlbWVudF9ibGFuaygpLAogICAgYXhpcy50ZXh0ID0gZWxlbWVudF9ibGFuaygpLAogICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF9ibGFuaygpLAogICAgYXhpcy50aXRsZS55ID0gZWxlbWVudF9ibGFuaygpLAogICAgcGFuZWwuZ3JpZC5taW5vciA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfcmVjdChjb2xvdXIgPSAiYmxhY2siLCBmaWxsPU5BLCBzaXplPTIpCiAgKQp9CgpyaWRlc2hhcmVfYW5pbWF0aW9uIDwtCiAgZ2dwbG90KCkrCiAgZ2VvbV9zZihkYXRhID0gcGhpbGx5VHJhY3RzICU+JQogICAgICAgICAgICBzdF90cmFuc2Zvcm0oY3JzPTQzMjYpLCBjb2xvdXIgPSAnI2VmZWZlZicpKwogIGdlb21fcG9pbnQoZGF0YSA9IHJpZGUuYW5pbWF0aW9uLmRhdGEsIAogICAgICAgICAgICAgYWVzKHggPSBmcm9tX2xvbmdpdHVkZSwgeSA9IGZyb21fbGF0aXR1ZGUsIGZpbGwgPSBUcmlwcywgY29sb3IgPSBUcmlwcyksIHNpemUgPSAxLCBhbHBoYSA9IDEuNSkgKwogIHNjYWxlX2NvbG91cl9tYW51YWwodmFsdWVzID0gcGFsZXR0ZTEpICsKICBsYWJzKHRpdGxlID0gIkluZGVnbyBwaWNrdXBzIGZvciBvbmUgd2VlayBpbiBNYXkgMjAyMiIsCiAgICAgICBzdWJ0aXRsZSA9ICIxNSBtaW51dGUgaW50ZXJ2YWxzOiB7Y3VycmVudF9mcmFtZX0iKSArCiAgdHJhbnNpdGlvbl9tYW51YWwoaW50ZXJ2YWwxNSkgKwogIG1hcFRoZW1lKCkKCmFuaW1hdGUocmlkZXNoYXJlX2FuaW1hdGlvbiwgZHVyYXRpb249MjAsIHJlbmRlcmVyID0gZ2lmc2tpX3JlbmRlcmVyKCkpCmBgYAogCiMjIDYuIGstZm9sZCBjcm9zcyB2YWxpZGF0aW9uCiAKYGBge3IgY3YgfQppbmRlZ29zYW1wbGUgPC0gc2FtcGxlX24ocmlkZS5wYW5lbCwgMTAwMDAwKSU+JQogIG5hLm9taXQoKQoKZml0Q29udHJvbCA8LSB0cmFpbkNvbnRyb2wobWV0aG9kID0gImN2IiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgIG51bWJlciA9IDEwMCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgc2F2ZVByZWRpY3Rpb25zID0gVFJVRSkKCnNldC5zZWVkKDQyKQojIGZvciBrLWZvbGRzIENWCgpyZWcuY3YgPC0gIAogIHRyYWluKFRyaXBfQ291bnQgfiBmcm9tX3N0YXRpb25fbmFtZSArIGhvdXIoaW50ZXJ2YWw2MCkgKyBkb3R3ICsgVGVtcGVyYXR1cmUgKyBQcmVjaXBpdGF0aW9uICsKICAgICAgICAgICAgICAgICAgIGxhZ0hvdXIgKyBsYWcySG91cnMgK2xhZzNIb3VycyArbGFnMTJIb3VycyArIGxhZzFkYXkgKyBob2xpZGF5TGFnICsgaG9saWRheSwgCiAgICAgICAgZGF0YSA9IGluZGVnb3NhbXBsZSwgIAogICAgICAgIG1ldGhvZCA9ICJsbSIsICAKICAgICAgICB0ckNvbnRyb2wgPSBmaXRDb250cm9sLCAgCiAgICAgICAgbmEuYWN0aW9uID0gbmEucGFzcykKCnJlZy5jdgoKYGBgCldoZW4gd2Ugd29yayB3aXRoIGxhcmdlciBkYXRhIHNldHMsIHdlIGNhbm5vdCBleGFtaW5lIGVhY2ggdmFsdWUgdG8gc2VlIGlmIHRoZXJlIGlzIGFuIG91dGxpZXIgb3Igc29tZSBvdXRsaWVycywgb3IgaWYgYWxsIGVycm9ycyBhcmUgc3lzdGVtYXRpY2FsbHkgaGlnaGVyLiBMb29raW5nIGF0IHRoZSByYXRpbyBvZiBNQUUgdG8gUk1TRSBjYW4gaGVscCB1cyB1bmRlcnN0YW5kIGlmIHRoZXJlIGFyZSBsYXJnZXIgYnV0IGxlc3MgY29tbW9uIGVycm9ycy4gClRoZSBjbG9zZXIgdGhlIG1vZGVsIHByZWRpY3Rpb25zIGFyZSB0byB0aGUgb2JzZXJ2YXRpb25zLCB0aGUgc21hbGxlciB0aGUgTVNFIHdpbGwgYmUuCgojIyA3LiBDb25jbHVzaW9uCgpUaGlzIG1vZGVsIGNvdWxkIGhlbHAgaW5kaWNhdGUgd2hlcmUgYW5kIHdoZW4gdGhlIGhpZ2ggZGVtYW5kIHdvdWxkIG9jY3VyLiBBIGJpa2UgcmUtYmFsYW5jaW5nIHBsYW4gY291bGQgYmUgbWFkZSBiYXNlZCBvbiB0aGlzIG1vZGVsJ3MgcmVzdWx0LCB3aGljaCBoYXMgc21hbGwgZXJyb3JzIGJ1dCBpcyBvdmVyYWxsIGJlbmVmaWNpYWwuIEluIHJlZ2FyZHMgdG8gdGhlIHJlLWJhbGFuY2luZyBwbGFuLCB0aGUgYWxnb3JpdGhtIHByZWRpY3RzIGhvdXJseSBhbmQgZGFpbHkgcGF0dGVybnMgc28gd2VsbCB0aGF0IGl0IGlzIHBvc3NpYmxlIHRvIHByZWRpY3Qgd2hlbiBhbmQgaG93IGFuIGluZGl2aWR1YWwgd2lsbCBhcnJpdmUgYXQgYSBwYXJ0aWN1bGFyIGxvY2F0aW9uLiBBcyBhIHJlc3VsdCwgd2Ugd2lsbCBiZSBhYmxlIHRvIGluY2VudGl2aXplIHVzZXJzIGJhc2VkIG9uIHRoZWlyIGJlaGF2aW9yIGFuZCB0aHVzIG1hbmFnZSB0aGUgc3VwcGx5IGFuZCBkZW1hbmQgZm9yIGJpa2VzLiBUaGUgcmV3YXJkIHByb2dyYW0gZm9yIHJlLWJhbGFuY2luZyBjYW4gZ2l2ZSBzb21lIGNyZWRpdHMgdG8gcmlkZXJzIHdobyBwYXJrIHRoZWlyIHNoYXJlZCBiaWtlcyBpbiBwcmVkaWN0ZWQgaGlnaC1kZW1hbmQgYXJlYXMgZHVyaW5nIHRoZSBtb3JuaW5nIGFuZCBldmVuaW5nIHBlYWsgY29tbXV0ZSBwZXJpb2RzIG9uIHdlZWtkYXlzLiBFYWNoIHdlZWsgY2FuIGJlIGFkanVzdGVkIGJhc2VkIG9uIHRoZSBwcmV2aW91cyB3ZWVrLCB3aGlsZSB0aGUgc2FtZSB0aW1lIHBlcmlvZCBmcm9tIHRoZSBwcmV2aW91cyB5ZWFyIG9yIHR3byB5ZWFycyBjYW4gYWxzbyBiZSB1c2VkLCBhcyBzZWFzb25zLCB3ZWF0aGVyLCBhbmQgaG9saWRheXMgY2FuIGFsc28gYmUgcmVmZXJlbmNlZC4gSW4gdGhlIGZ1dHVyZSwgaWYgdGhlcmUgYXJlIG90aGVyIGZhY3RvcnMgdGhhdCByYWlzZSBhdHRlbnRpb24sIHdlIGNvdWxkIGFsc28gYWRkIHRoZW0gdG8gdGhlIGFsZ29yaXRobSBhbmQgdGVzdCB0aGVtIHRvIHNlZSB0aGUgYWNjdXJhY3kgYW5kIGdlbmVyYWxpemF0aW9uLiAK