1. Introduction
Bike share systems have emerged as a pivotal component of urban
mobility, offering a sustainable and convenient transportation
alternative to city dwellers and visitors. These systems function
through a network of strategically placed docking stations where
bicycles can be picked up and returned. However, the effectiveness of
bike sharing hinges significantly on the availability of bicycles and
open docks at these stations. Due to varying daily patterns, some
stations may end up with too many bikes while others may have none,
leading to inconvenience for users seeking to either pick up or drop off
bicycles.
To ensure that bikes are available where and when they are needed,
bike share systems must engage in active re-balancing. This process
involves redistributing bicycles across the network to maintain an
equilibrium between bike and dock availability. One effective strategy
for re-balancing involves utilizing a fleet of specially equipped
vehicles that can transport multiple bikes at once. This method allows
for rapid and efficient movement of bicycles from stations with a
surplus to those with a deficit. Fleet-based re-balancing relies on
predictive analytics to forecast the demand for bicycles at different
stations throughout the day. Predictive methods incorporate time lag
features, which allow the model to use data from past intervals to
forecast future demands. By incorporating these time lags, the
predictive system can accurately anticipate demand patterns, enabling
the fleet to be deployed proactively. This strategic deployment ensures
that each station maintains an optimal number of bikes and open docks,
thereby maximizing the system’s overall utility and user
satisfaction.
library(tidyverse)
library(sf)
library(lubridate)
library(tigris)
library(tidycensus)
library(viridis)
library(riem)
library(gridExtra)
library(knitr)
library(kableExtra)
library(RSocrata)
library(dplyr)
library(knitr)
library(purrr)
# Load the readr package at the beginning of your script
library(readr)
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"))
# Install Census API Key
tidycensus::census_api_key("e79f3706b6d61249968c6ce88794f6f556e5bf3d", overwrite = TRUE)
2. Data Wrangling
Citi Bike, powered by Lyft, is a prominent bike-sharing service in
New York City, designed to offer an eco-friendly, efficient, and
convenient mode of transportation for both locals and tourists. To
enhance user understanding and encourage community engagement, Citi Bike
makes available comprehensive trip data, which can be downloaded for
further exploration. This dataset includes key details such as ride IDs,
bike types, trip start and end times, station names, and station IDs,
along with the geographical coordinates of each station. Additionally,
the data distinguishes between member and casual riders, providing
insights into different user patterns and behaviors. The dissemination
of this data empowers stakeholders to perform detailed analyses, helping
to address operational challenges such as the distribution and
availability of bikes to ensure that the bike-sharing system meets its
users’ needs effectively.
In this section, we will import data from the Citi Bike sharing
program, along with New York City demographic statistics and weather
records from JFK Airport. By integrating demographic and weather
information, we aim to enhance the accuracy of our predictions for
future trends in the NYC Citi Bike sharing usage.
Data Source: https://citibikenyc.com/system-data
data <- read_csv("C:/Users/25077/Desktop/MUSA 508_PPA/Assignment 5/data/202401-citibike-tripdata.csv/202401-citibike-tripdata_1.csv")
set.seed(123) # Setting a seed for reproducibility
data_nyc <- data %>%
sample_n(size = 400000)
dat_interval <- data_nyc %>%
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))
NYCCensus <-
get_acs(geography = "tract",
variables = c("B01003_001", "B19013_001",
"B02001_002", "B08013_001",
"B08012_001", "B08301_001",
"B08301_010", "B01002_001"),
year = 2017,
state = "NY",
geometry = TRUE,
county=c("New York", "Kings", "Queens", "Richmond", "Bronx"),
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)
# Extract geography factors
NYCTracts <-
NYCCensus %>%
as.data.frame() %>%
distinct(GEOID, .keep_all = TRUE) %>%
select(GEOID, geometry) %>%
st_sf
# join census tract information to "dat2" data frame
dat_census <- st_join(dat_interval %>%
filter(is.na(start_lng) == FALSE &
is.na(start_lat) == FALSE &
is.na(end_lng) == FALSE &
is.na(end_lat) == FALSE) %>%
st_as_sf(., coords = c("start_lng", "start_lat"), crs = 4326),
NYCTracts %>%
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("end_lng", "end_lat"), crs = 4326) %>%
st_join(., NYCTracts %>%
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)
weather_Panel <-
riem_measures(station = "JFK", date_start = "2024-01-01", date_end = "2024-01-31") %>%
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))
Present the hourly variations in weather conditions, including
precipitation levels, wind speed, and temperature fluctuations, for New
York City.
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 - NYC JFK - Jan, 2024")

3. Explore the Data
1) Bike Share Trends by Dates.
ggplot(dat_census %>%
group_by(interval60) %>%
tally())+
geom_line(aes(x = interval60, y = n))+
labs(title="Bike share trips per hr. NYC, Jan, 2024",
x="Date",
y="Number of trips")+
plotTheme

2) Bike Share Trends by Time Periods (Rush hours,
AM/PM).
dat_census <- dat_census %>%
na.omit()
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, 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), binwidth = 1)+
labs(title="Mean Number of Hourly Trips Per Station. NYC, Jan, 2024",
x="Number of trips",
y="Frequency")+
facet_wrap(~time_of_day)+
plotTheme

The chart indicates that both Mid-Day and PM Rush periods experience
the highest frequency of bike trips, with PM Rush exhibiting not only
the greatest overall trip frequency but also the most frequent
occurrence of second trips per station. This suggests that during the PM
Rush, many stations consistently see at least two trips starting per
hour on average, which may reflect a robust pattern of usage as people
conclude their workday or engage in evening activities.
Overnight periods display a surprisingly elevated frequency of trips,
which, while not as high as the daytime peaks, is significant when
compared to the AM Rush period, which has the lowest frequency of trips.
This could be due to a variety of factors, such as night-shift workers,
late-night entertainment activities, or even early-morning fitness
routines.
3) Bike Share Trends by station.
ggplot(dat_census %>%
group_by(interval60, start_station_name) %>%
tally())+
geom_histogram(aes(n), binwidth = 5)+
labs(title="Bike share trips per hr by station. NYC, Jan. 2024",
x="Number of Stations",
y="Trip Counts")+
plotTheme

4) Bike Share Trends by day of the week.
# by week days
ggplot(dat_census %>% mutate(hour = hour(started_at)))+
geom_freqpoly(aes(hour, color = dotw), binwidth = 1)+
labs(title="Bike share trips in NYC, by day of the week, Jan. 2024",
x="Hour",
y="Trip Counts")+
plotTheme

# by workdays or weekends
ggplot(dat_census %>%
mutate(hour = hour(started_at),
weekend = ifelse(dotw %in% c("Sun", "Sat"), "Weekend", "Weekday")))+
geom_freqpoly(aes(hour, color = weekend), binwidth = 1)+
labs(title="Bike share trips in NYC - weekend vs weekday, Jan. 2024",
x="Hour",
y="Trip Counts")+
plotTheme

The lines representing weekdays generally follow a similar pattern,
with pronounced peaks, suggesting consistent commuting behavior on these
days. In contrast, Saturday and Sunday show different patterns, with a
more gradual increase in trips throughout the day and not as sharp peaks
during traditional rush hours, which is typical for weekends when people
are not bound to standard work or school schedules.
ggplot() +
geom_sf(data = NYCTracts %>%
st_transform(crs = 4326), fill = "grey70", color = "grey80") +
geom_point(data = dat_census %>%
mutate(hour = hour(started_at),
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, from_latitude, from_longitude, weekend, time_of_day) %>%
tally(),
aes(x = from_longitude, y = from_latitude, color = log1p(n)),
fill = "transparent", size = 0.08) +
scale_colour_gradient(low = "cornsilk", high = "indianred2") +
ylim(min(dat_census$from_latitude), max(dat_census$from_latitude)) +
xlim(min(dat_census$from_longitude), max(dat_census$from_longitude))+
labs(title = "Bike share trips per hr by station. NYC, Jan.2024") +
mapTheme +
facet_grid(weekend ~ time_of_day) +
theme(legend.position = "right")

During weekdays, a high concentration of bike trips occurs within key
business districts in Manhattan, indicating a prevalent commuter pattern
associated with work-related travel via bike sharing. However, this
pattern dissipates over the weekend; trip distribution becomes more
dispersed across various locations and the frequency diminishes,
reflecting a shift to more leisurely or diverse travel purposes that are
not centered around commuting.
4. Model Development
1) Create Space-Time Panel
# Calculate the number of rows in the resulting panel dataset
# This line calculates the number of rows in the resulting panel dataset by multiplying the number of unique values in the 'interval60' column with the number of unique values in the 'start_station_id' column in the 'dat_census' dataset.
length(unique(dat_census$interval60)) * length(unique(dat_census$start_station_id))
study.panel <- expand.grid(interval60 = unique(dat_census$interval60),
start_station_id = unique(dat_census$start_station_id)) %>%
left_join(dat_census %>%
select(start_station_id, start_station_name, Origin.Tract, from_longitude, from_latitude) %>%
distinct() %>%
group_by(start_station_id) %>%
slice(1),
by = "start_station_id")
2. Create Panel
# Create a panel dataset for bike ride data
ride.panel <-
dat_census %>% # Start with the dat_census dataset
mutate(Trip_Counter = 1) %>% # Add a Trip_Counter variable, indicating one trip for each row
right_join(study.panel) %>% # Right join with the study.panel dataset to ensure all spatial-time combinations are included
group_by(interval60, start_station_id, start_station_name, Origin.Tract, from_longitude, from_latitude) %>% # Group by spatial-time and station variables
summarize(Trip_Count = sum(Trip_Counter, na.rm = TRUE)) %>% # Summarize the Trip_Counter variable to calculate total trip count for each group
left_join(weather_Panel) %>% # Left join with the weather.Panel dataset to include weather information
ungroup() %>% # Remove grouping
filter(!is.na(start_station_id)) %>% # Filter out rows with missing start_station_id values
mutate(week = week(interval60), # Create a week variable indicating the week number of the year
dotw = wday(interval60, label = TRUE)) %>% # Create a dotw variable indicating the day of the week (label format)
filter(!is.na(Origin.Tract)) # Filter out rows with missing Origin.Tract values
ride.panel <-
left_join(ride.panel, NYCCensus %>%
as.data.frame() %>%
select(-geometry), by = c("Origin.Tract" = "GEOID"))
3) Create time lags
# Arrange the ride.panel dataset based on 'start_station_id' and 'interval60'
# This step ensures that the dataset is ordered properly for subsequent calculations
ride.panel <-
ride.panel %>%
arrange(start_station_id, interval60) %>%
# Compute lag variables for the 'Trip_Count' variable at different time intervals
mutate(
lagHour = dplyr::lag(Trip_Count, 1), # Lag of 1 hour
lag2Hours = dplyr::lag(Trip_Count, 2), # Lag of 2 hours
lag3Hours = dplyr::lag(Trip_Count, 3), # Lag of 3 hours
lag4Hours = dplyr::lag(Trip_Count, 4), # Lag of 4 hours
lag12Hours = dplyr::lag(Trip_Count, 12), # Lag of 12 hours
lag1day = dplyr::lag(Trip_Count, 24) # Lag of 1 day (24 hours)
) %>%
# Identify holidays and create a binary variable indicating whether it's a holiday
mutate(
holiday = ifelse(yday(interval60) == 148, 1, 0) # Check if it's a holiday (148th day of the year)
) %>%
# Create a variable representing the day of the year
mutate(
day = yday(interval60) # Extract the day of the year from 'interval60'
) %>%
# Create additional variables related to holiday lag
mutate(
holidayLag = case_when(
dplyr::lag(holiday, 1) == 1 ~ "PlusOneDay", # If the previous day was a holiday
dplyr::lag(holiday, 2) == 1 ~ "PlusTwoDays", # If two days ago was a holiday
dplyr::lag(holiday, 3) == 1 ~ "PlusThreeDays", # If three days ago was a holiday
dplyr::lead(holiday, 1) == 1 ~ "MinusOneDay", # If the next day will be a holiday
dplyr::lead(holiday, 2) == 1 ~ "MinusTwoDays", # If two days ahead will be a holiday
dplyr::lead(holiday, 3) == 1 ~ "MinusThreeDays" # If three days ahead will be a holiday
),
holidayLag = ifelse(is.na(holidayLag) == TRUE, 0, holidayLag) # Convert NA to 0 for non-holiday cases
)
# Assuming that ride.panel is your data frame and it has been processed as per the R code you provided
# Now, we will summarize the correlations and then create a kable
ride.panel %>%
as.data.frame() %>%
group_by(interval60) %>%
summarize_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"))) %>%
group_by(Variable) %>%
summarize(correlation = round(cor(Value, Trip_Count), 2)) %>%
# Now we will use kable to create a table
kable(format = "html", caption = "Correlation between lags and Trip Count") %>%
kable_styling(bootstrap_options = c("striped", "hover"), full_width = F) %>%
column_spec(1, bold = T) # Make the first column bold
Correlation between lags and Trip Count
|
Variable
|
correlation
|
|
lagHour
|
0.88
|
|
lag2Hours
|
0.67
|
|
lag3Hours
|
0.48
|
|
lag4Hours
|
0.31
|
|
lag12Hours
|
-0.45
|
|
lag1day
|
0.72
|
lagHour (0.83): Indicates a very strong positive correlation with the
number of trips from the previous hour. This suggests that bike usage in
the previous hour is a strong predictor of bike usage in the current
hour. lag2Hours (0.63): Also a positive correlation, though not as
strong as the 1-hour lag, showing that bike usage two hours prior still
has a significant impact on current bike usage. lag3Hours (0.44) and
lag4Hours (0.28): These values represent positive correlations but are
weaker, indicating that as the time lag increases, the predictive power
of previous bike usage diminishes. lag12Hours (-0.39): This negative
correlation suggests an inverse relationship, indicating that the number
of trips is generally opposite in trend 12 hours prior. This could
reflect a difference in bike usage patterns between morning and evening.
lag1day (0.66): A relatively strong positive correlation indicates that
the number of trips is similar from one day to the next, possibly
highlighting consistent daily patterns of bike usage.
4. Run Models
# Filter the ride.panel data to create subsets for training and testing
ride.panel.train.subset <- ride.panel %>% filter(week >= 3)
ride.panel.test.subset <- ride.panel %>% filter(week < 3)
# Randomly select 40,000 rows from the training subset
ride.Train <- ride.panel.train.subset %>% sample_n(40000)
# Now, select up to 40,000 rows for the test set. If there are less than 40,000, select all.
n_test <- min(40000, nrow(ride.panel.test.subset))
ride.Test <- ride.panel.test.subset %>% sample_n(n_test)
reg1 <-
lm(Trip_Count ~ hour(interval60) + dotw + Temperature, data=ride.Train)
reg2 <-
lm(Trip_Count ~ start_station_name + dotw + Temperature, data=ride.Train)
reg3 <-
lm(Trip_Count ~ start_station_name + hour(interval60) + dotw + Temperature + Precipitation,
data=ride.Train)
reg4 <-
lm(Trip_Count ~ start_station_name + hour(interval60) + dotw + Temperature + Precipitation +
lagHour + lag2Hours +lag3Hours + lag12Hours + lag1day,
data=ride.Train)
reg5 <-
lm(Trip_Count ~ start_station_name + hour(interval60) + dotw + Temperature + Precipitation +
lagHour + lag2Hours +lag3Hours +lag12Hours + lag1day + holidayLag + holiday,
data=ride.Train)
4. Model Evaluation
1) Prediction
ride.Test.weekNest <-
ride.Test %>%
nest(-week)
model_pred <- function(dat, fit){
pred <- predict(fit, newdata = dat)}
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))
kable_output <- week_predictions %>%
kable(format = "html", caption = "Weekly Predictions and Error Metrics") %>%
kable_styling(bootstrap_options = c("striped", "hover"), full_width = FALSE) %>%
column_spec(1, bold = TRUE)
2) Examine Error Metrics for Accuracy
palette5 <- c("cornsilk", "#f8e0c8", "#f1c1b1", "#ea929a", "indianred3")
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

In the provided chart, it appears that Model A, which incorporates
time of day and temperature variables, results in the highest Mean
Absolute Errors (MAE), suggesting that it may not be capturing enough of
the factors that influence trip counts. Models D and E, which are more
complex and include time lags and holiday effects in addition to spatial
and weather variables, show relatively lower MAEs. This indicates that
the integration of additional time-related factors generally contributes
to a decrease in prediction error, underscoring the importance of
considering both immediate and historical temporal patterns in trip
count prediction.
Interestingly, Model C stands out with an abnormally higher error
than might be expected given its combination of time, spatial, and
weather variables. This could imply a need to re-evaluate the specific
variables included or the way they interact within the model.
Notably, Models D and E perform similarly across the weeks,
suggesting that the inclusion of holiday lags does not significantly
enhance model performance beyond the time lags already accounted for in
Model D. This could be a reflection of the non-linear nature of holiday
impacts or an indication of overfitting, where the model complexity
begins to capture noise rather than the true underlying
relationships.
From these observations, it becomes clear that while adding more
variables can be beneficial, there is a nuanced balance between model
complexity and predictive performance. Future model improvements might
involve refining the selection of lag variables, re-assessing the
inclusion of holiday effects, or exploring non-linear modeling
techniques that might better capture the complexities of trip count
variations.
3) Observed and Prediction
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 = c("Observed" = "#f1c1b1", "Prediction" = "indianred3")) + # Specify your colors here
facet_wrap(~Regression, ncol = 1) +
labs(title = "Predicted/Observed bike share time series",
subtitle = "Chicago; A test set of 2 weeks",
x = "Hour",
y = "Station Trips") +
plotTheme

The time series plots for different regression models over a two-week
period illustrate varying degrees of predictive accuracy in estimating
bike share usage. Model A, focusing solely on temporal and temperature
variables, demonstrates moderate predictive capability but shows notable
errors, particularly at peak usage times. Model B, which incorporates
spatial factors, captures some patterns more accurately than Model A,
yet still exhibits deviations due to the absence of temporal details.
Model C, which combines both temporal and spatial variables, along with
weather conditions, closely follows the observed data trends, although
it occasionally misses peak values. Model D adds temporal lags and
presents a better fit, closely mirroring the observed trips, signifying
the relevance of historical data in forecasting. Lastly, Model E, which
extends Model D with holiday lags, offers no significant improvement,
indicating that additional complexity through holiday lags may not
contribute meaningfully to the model’s performance in this context.
Overall, while the inclusion of time lags enhances model accuracy, the
benefit of integrating holiday-related variables appears limited.
4) Errors by Station
week_predictions %>%
mutate(interval60 = map(data, pull, interval60),
start_station_id = map(data, pull, start_station_id),
from_latitude = map(data, pull, from_latitude),
from_longitude = map(data, pull, from_longitude)) %>%
select(interval60, start_station_id, from_longitude, from_latitude, Observed, Prediction, Regression) %>%
unnest() %>%
filter(Regression == "ETime_Space_FE_timeLags_holidayLags") %>%
group_by(start_station_id, from_longitude, from_latitude) %>%
summarize(MAE = mean(abs(Observed-Prediction), na.rm = TRUE))%>%
ggplot(.)+
geom_sf(data = NYCCensus, color = "grey80", fill = "grey70")+
geom_point(aes(x = from_longitude, y = from_latitude, color = MAE),
fill = "transparent", size=0.3)+
scale_colour_gradient(low = "cornsilk", high = "indianred2")+
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

Based on the observations, it is evident that the East Village area
exhibits the highest prediction errors, indicating that the model’s
forecasting is less accurate in this locale. Furthermore, the areas
encompassing Greenwich Village and the vicinities bordering Central Park
also demonstrate relatively higher MAE values. These discrepancies
suggest that the model may not fully account for the unique factors or
behaviors influencing bike share usage patterns in these specific urban
regions, which are possibly characterized by their own distinct
demographic, cultural, or infrastructural attributes.
5) Space-Time Error Evaluation
week_predictions %>%
mutate(interval60 = map(data, pull, interval60),
start_station_id = map(data, pull, start_station_id),
from_latitude = map(data, pull, from_latitude),
from_longitude = map(data, pull, from_longitude),
dotw = map(data, pull, dotw)) %>%
select(interval60, start_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 = "indianred2")+
geom_abline(slope = 1, intercept = 0)+
facet_grid(time_of_day~weekend)+
labs(title="Observed vs Predicted",
x="Observed trips",
y="Predicted trips")+
plotTheme

During weekdays, there seems to be a pattern of overprediction during
the AM rush and underprediction during the PM rush. Midday and overnight
predictions appear to be more evenly distributed around the diagonal,
suggesting better predictive accuracy during these times. On weekends,
the scatter plots show a wide dispersion of points, indicating a greater
degree of variance in the predictive accuracy. This could be due to more
variable trip patterns during weekends that are not as well-captured by
the model.
5) Space-Time Error Evaluation by Station
week_predictions %>%
mutate(interval60 = map(data, pull, interval60),
start_station_id = map(data, pull, start_station_id),
from_latitude = map(data, pull, from_latitude),
from_longitude = map(data, pull, from_longitude),
dotw = map(data, pull, dotw) ) %>%
select(interval60, start_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(start_station_id, weekend, time_of_day, from_longitude, from_latitude) %>%
summarize(MAE = mean(abs(Observed-Prediction), na.rm = TRUE))%>%
ggplot(.) +
geom_sf(data = NYCCensus, color = "grey70", fill = "grey80") +
geom_point(aes(x = from_longitude, y = from_latitude, color = MAE),
size = 0.5) +
scale_colour_gradient(low = "cornsilk", high = "indianred2") +
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

5) Socio-Economic Factors
week_predictions %>%
mutate(interval60 = map(data, pull, interval60),
start_station_id = map(data, pull, start_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),
Percent_White = map(data, pull, Percent_White)) %>%
select(interval60, start_station_id, from_longitude,
from_latitude, Observed, Prediction, Regression,
dotw, Percent_Taking_Public_Trans, Med_Inc, Percent_White) %>%
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(start_station_id, Percent_Taking_Public_Trans, Med_Inc, Percent_White) %>%
summarize(MAE = mean(abs(Observed-Prediction), na.rm = TRUE))%>%
gather(-start_station_id, -MAE, key = "variable", value = "value")%>%
ggplot(.)+
#geom_sf(data = NYCCensus, 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, color = "indianred")+
facet_wrap(~variable, scales = "free")+
labs(title="Errors as a function of socio-economic variables",
y="Mean Absolute Error (Trips)")+
plotTheme

Median Income (Med_Inc) vs. MAE: The scatter plot does not
show a clear correlation between median income and MAE. However, there’s
a slight concentration of data points at the lower end of the median
income scale with lower MAE values, which might indicate that the model
predicts trips more accurately in areas with lower median incomes.
Percentage Taking Public Transportation vs. MAE: This plot
appears to show a slight upward trend, where areas with a higher
percentage of people using public transport tend to have higher MAE
values in trip prediction. This could suggest that the model struggles
to predict trips accurately in areas with higher public transportation
usage.
Percentage White vs. MAE: The scatter plot for the
percentage of the white population against MAE seems to show no strong
correlation. There’s a wide dispersion of MAE values across all
percentages, indicating that the racial composition, represented by the
percentage of white residents, does not have a clear impact on the
predictive error of the model.
5. Interpretation
The algorithm’s efficacy, particularly the models utilizing various
time lags, underscores its potential for optimizing bike fleet
transportation for re-balancing efforts. The inclusion of time lags
allows the algorithm to account for patterns and trends over time, which
is crucial for anticipating future demand and planning logistics.
For instance, the D and E models, which include time lag variables,
have shown a relatively lower Mean Absolute Error (MAE) in predictions.
This suggests they are more adept at capturing the temporal dynamics of
bike usage. For re-balancing operations, this means the algorithm can
predict not just the immediate demand for bikes but also how demand
evolves throughout the day or week. With this information, a
bike-sharing service can proactively move bikes from low-demand to
high-demand areas in anticipation of usage spikes, rather than reacting
to shortages or surpluses as they happen.
The model’s capability to reflect the influence of time on usage
patterns enables a more nuanced approach to fleet management. For
example, during weekday AM rush hours, where overprediction is common,
the model’s insights could prevent the unnecessary allocation of too
many bikes to certain areas, thereby optimizing the use of
transportation resources. Conversely, during PM rush hours, where
underprediction occurs, the model can signal the need to allocate
additional bikes ahead of time to meet the surge in demand.
By employing a model with time lags, the transportation fleet used
for re-balancing can operate more efficiently. It allows for the
scheduling of fleet movements during off-peak hours, which can reduce
operational costs and minimize the impact on traffic. It also ensures
that the re-balancing efforts are less disruptive to the service and
more aligned with the predicted user demand, thus enhancing the overall
user experience.
In conclusion, the time lag models demonstrate considerable potential
for improving bike fleet re-balancing operations. The algorithm can
provide predictive insights that allow for a more strategic deployment
of the re-balancing fleet, potentially leading to cost savings, better
resource allocation, and an overall increase in the effectiveness of the
bike-sharing system.
LS0tDQp0aXRsZTogIlNwYWNlLVRpbWUgUHJlZGljdGlvbiBvZiBCaWtlIFNoYXJlIERlbWFuZCBpbiBOWUMiDQphdXRob3I6ICJaaXlpIEd1byINCmRhdGU6ICJBcHIgMjgsIDIwMjQiDQpvdXRwdXQ6IA0KICBodG1sX2RvY3VtZW50Og0KICAgIHRvYzogdHJ1ZQ0KICAgIHRvY19mbG9hdDogdHJ1ZQ0KICAgIGNvZGVfZm9sZGluZzogImhpZGUiDQogICAgY29kZV9kb3dubG9hZDogdHJ1ZQ0KLS0tDQoNCiMjIDEuIEludHJvZHVjdGlvbg0KDQpCaWtlIHNoYXJlIHN5c3RlbXMgaGF2ZSBlbWVyZ2VkIGFzIGEgcGl2b3RhbCBjb21wb25lbnQgb2YgdXJiYW4gbW9iaWxpdHksIG9mZmVyaW5nIGEgc3VzdGFpbmFibGUgYW5kIGNvbnZlbmllbnQgdHJhbnNwb3J0YXRpb24gYWx0ZXJuYXRpdmUgdG8gY2l0eSBkd2VsbGVycyBhbmQgdmlzaXRvcnMuIFRoZXNlIHN5c3RlbXMgZnVuY3Rpb24gdGhyb3VnaCBhIG5ldHdvcmsgb2Ygc3RyYXRlZ2ljYWxseSBwbGFjZWQgZG9ja2luZyBzdGF0aW9ucyB3aGVyZSBiaWN5Y2xlcyBjYW4gYmUgcGlja2VkIHVwIGFuZCByZXR1cm5lZC4gSG93ZXZlciwgdGhlIGVmZmVjdGl2ZW5lc3Mgb2YgYmlrZSBzaGFyaW5nIGhpbmdlcyBzaWduaWZpY2FudGx5IG9uIHRoZSBhdmFpbGFiaWxpdHkgb2YgYmljeWNsZXMgYW5kIG9wZW4gZG9ja3MgYXQgdGhlc2Ugc3RhdGlvbnMuIER1ZSB0byB2YXJ5aW5nIGRhaWx5IHBhdHRlcm5zLCBzb21lIHN0YXRpb25zIG1heSBlbmQgdXAgd2l0aCB0b28gbWFueSBiaWtlcyB3aGlsZSBvdGhlcnMgbWF5IGhhdmUgbm9uZSwgbGVhZGluZyB0byBpbmNvbnZlbmllbmNlIGZvciB1c2VycyBzZWVraW5nIHRvIGVpdGhlciBwaWNrIHVwIG9yIGRyb3Agb2ZmIGJpY3ljbGVzLg0KDQpUbyBlbnN1cmUgdGhhdCBiaWtlcyBhcmUgYXZhaWxhYmxlIHdoZXJlIGFuZCB3aGVuIHRoZXkgYXJlIG5lZWRlZCwgYmlrZSBzaGFyZSBzeXN0ZW1zIG11c3QgZW5nYWdlIGluIGFjdGl2ZSByZS1iYWxhbmNpbmcuIFRoaXMgcHJvY2VzcyBpbnZvbHZlcyByZWRpc3RyaWJ1dGluZyBiaWN5Y2xlcyBhY3Jvc3MgdGhlIG5ldHdvcmsgdG8gbWFpbnRhaW4gYW4gZXF1aWxpYnJpdW0gYmV0d2VlbiBiaWtlIGFuZCBkb2NrIGF2YWlsYWJpbGl0eS4gT25lIGVmZmVjdGl2ZSBzdHJhdGVneSBmb3IgcmUtYmFsYW5jaW5nIGludm9sdmVzIHV0aWxpemluZyBhIGZsZWV0IG9mIHNwZWNpYWxseSBlcXVpcHBlZCB2ZWhpY2xlcyB0aGF0IGNhbiB0cmFuc3BvcnQgbXVsdGlwbGUgYmlrZXMgYXQgb25jZS4gVGhpcyBtZXRob2QgYWxsb3dzIGZvciByYXBpZCBhbmQgZWZmaWNpZW50IG1vdmVtZW50IG9mIGJpY3ljbGVzIGZyb20gc3RhdGlvbnMgd2l0aCBhIHN1cnBsdXMgdG8gdGhvc2Ugd2l0aCBhIGRlZmljaXQuIEZsZWV0LWJhc2VkIHJlLWJhbGFuY2luZyByZWxpZXMgb24gcHJlZGljdGl2ZSBhbmFseXRpY3MgdG8gZm9yZWNhc3QgdGhlIGRlbWFuZCBmb3IgYmljeWNsZXMgYXQgZGlmZmVyZW50IHN0YXRpb25zIHRocm91Z2hvdXQgdGhlIGRheS4gUHJlZGljdGl2ZSBtZXRob2RzIGluY29ycG9yYXRlIHRpbWUgbGFnIGZlYXR1cmVzLCB3aGljaCBhbGxvdyB0aGUgbW9kZWwgdG8gdXNlIGRhdGEgZnJvbSBwYXN0IGludGVydmFscyB0byBmb3JlY2FzdCBmdXR1cmUgZGVtYW5kcy4gQnkgaW5jb3Jwb3JhdGluZyB0aGVzZSB0aW1lIGxhZ3MsIHRoZSBwcmVkaWN0aXZlIHN5c3RlbSBjYW4gYWNjdXJhdGVseSBhbnRpY2lwYXRlIGRlbWFuZCBwYXR0ZXJucywgZW5hYmxpbmcgdGhlIGZsZWV0IHRvIGJlIGRlcGxveWVkIHByb2FjdGl2ZWx5LiBUaGlzIHN0cmF0ZWdpYyBkZXBsb3ltZW50IGVuc3VyZXMgdGhhdCBlYWNoIHN0YXRpb24gbWFpbnRhaW5zIGFuIG9wdGltYWwgbnVtYmVyIG9mIGJpa2VzIGFuZCBvcGVuIGRvY2tzLCB0aGVyZWJ5IG1heGltaXppbmcgdGhlIHN5c3RlbSdzIG92ZXJhbGwgdXRpbGl0eSBhbmQgdXNlciBzYXRpc2ZhY3Rpb24uDQoNCmBgYHtyIHNldHVwXzEzLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRSwgcmVzdWx0cz0naGlkZSd9DQoNCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShzZikNCmxpYnJhcnkobHVicmlkYXRlKQ0KbGlicmFyeSh0aWdyaXMpDQpsaWJyYXJ5KHRpZHljZW5zdXMpDQpsaWJyYXJ5KHZpcmlkaXMpDQpsaWJyYXJ5KHJpZW0pDQpsaWJyYXJ5KGdyaWRFeHRyYSkNCmxpYnJhcnkoa25pdHIpDQpsaWJyYXJ5KGthYmxlRXh0cmEpDQpsaWJyYXJ5KFJTb2NyYXRhKQ0KbGlicmFyeShkcGx5cikNCmxpYnJhcnkoa25pdHIpDQpsaWJyYXJ5KHB1cnJyKQ0KDQojIExvYWQgdGhlIHJlYWRyIHBhY2thZ2UgYXQgdGhlIGJlZ2lubmluZyBvZiB5b3VyIHNjcmlwdA0KbGlicmFyeShyZWFkcikNCg0KcGxvdFRoZW1lIDwtIHRoZW1lKA0KICBwbG90LnRpdGxlID1lbGVtZW50X3RleHQoc2l6ZT0xMiksDQogIHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZT04KSwNCiAgcGxvdC5jYXB0aW9uID0gZWxlbWVudF90ZXh0KHNpemUgPSA2KSwNCiAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwLCBhbmdsZSA9IDQ1LCBoanVzdCA9IDEpLA0KICBheGlzLnRleHQueSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApLA0KICBheGlzLnRpdGxlLnkgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSwNCiAgIyBTZXQgdGhlIGVudGlyZSBjaGFydCByZWdpb24gdG8gYmxhbmsNCiAgcGFuZWwuYmFja2dyb3VuZD1lbGVtZW50X2JsYW5rKCksDQogIHBsb3QuYmFja2dyb3VuZD1lbGVtZW50X2JsYW5rKCksDQogICNwYW5lbC5ib3JkZXI9ZWxlbWVudF9yZWN0KGNvbG91cj0iI0YwRjBGMCIpLA0KICAjIEZvcm1hdCB0aGUgZ3JpZA0KICBwYW5lbC5ncmlkLm1ham9yPWVsZW1lbnRfbGluZShjb2xvdXI9IiNEMEQwRDAiLHNpemU9LjIpLA0KICBheGlzLnRpY2tzPWVsZW1lbnRfYmxhbmsoKSkNCg0KbWFwVGhlbWUgPC0gdGhlbWUocGxvdC50aXRsZSA9ZWxlbWVudF90ZXh0KHNpemU9MTIpLA0KICAgICAgICAgICAgICAgICAgcGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplPTgpLA0KICAgICAgICAgICAgICAgICAgcGxvdC5jYXB0aW9uID0gZWxlbWVudF90ZXh0KHNpemUgPSA2KSwNCiAgICAgICAgICAgICAgICAgIGF4aXMubGluZT1lbGVtZW50X2JsYW5rKCksDQogICAgICAgICAgICAgICAgICBheGlzLnRleHQueD1lbGVtZW50X2JsYW5rKCksDQogICAgICAgICAgICAgICAgICBheGlzLnRleHQueT1lbGVtZW50X2JsYW5rKCksDQogICAgICAgICAgICAgICAgICBheGlzLnRpY2tzPWVsZW1lbnRfYmxhbmsoKSwNCiAgICAgICAgICAgICAgICAgIGF4aXMudGl0bGUueD1lbGVtZW50X2JsYW5rKCksDQogICAgICAgICAgICAgICAgICBheGlzLnRpdGxlLnk9ZWxlbWVudF9ibGFuaygpLA0KICAgICAgICAgICAgICAgICAgcGFuZWwuYmFja2dyb3VuZD1lbGVtZW50X2JsYW5rKCksDQogICAgICAgICAgICAgICAgICBwYW5lbC5ib3JkZXI9ZWxlbWVudF9ibGFuaygpLA0KICAgICAgICAgICAgICAgICAgcGFuZWwuZ3JpZC5tYWpvcj1lbGVtZW50X2xpbmUoY29sb3VyID0gJ3RyYW5zcGFyZW50JyksDQogICAgICAgICAgICAgICAgICBwYW5lbC5ncmlkLm1pbm9yPWVsZW1lbnRfYmxhbmsoKSwNCiAgICAgICAgICAgICAgICAgIGxlZ2VuZC5kaXJlY3Rpb24gPSAidmVydGljYWwiLCANCiAgICAgICAgICAgICAgICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJyaWdodCIsDQogICAgICAgICAgICAgICAgICBwbG90Lm1hcmdpbiA9IG1hcmdpbigxLCAxLCAxLCAxLCAnY20nKSwNCiAgICAgICAgICAgICAgICAgIGxlZ2VuZC5rZXkuaGVpZ2h0ID0gdW5pdCgxLCAiY20iKSwgbGVnZW5kLmtleS53aWR0aCA9IHVuaXQoMC4yLCAiY20iKSkNCg0KIyBJbnN0YWxsIENlbnN1cyBBUEkgS2V5DQp0aWR5Y2Vuc3VzOjpjZW5zdXNfYXBpX2tleSgiZTc5ZjM3MDZiNmQ2MTI0OTk2OGM2Y2U4ODc5NGY2ZjU1NmU1YmYzZCIsIG92ZXJ3cml0ZSA9IFRSVUUpDQpgYGANCg0KDQojIyAyLiBEYXRhIFdyYW5nbGluZw0KDQpDaXRpIEJpa2UsIHBvd2VyZWQgYnkgTHlmdCwgaXMgYSBwcm9taW5lbnQgYmlrZS1zaGFyaW5nIHNlcnZpY2UgaW4gTmV3IFlvcmsgQ2l0eSwgZGVzaWduZWQgdG8gb2ZmZXIgYW4gZWNvLWZyaWVuZGx5LCBlZmZpY2llbnQsIGFuZCBjb252ZW5pZW50IG1vZGUgb2YgdHJhbnNwb3J0YXRpb24gZm9yIGJvdGggbG9jYWxzIGFuZCB0b3VyaXN0cy4gVG8gZW5oYW5jZSB1c2VyIHVuZGVyc3RhbmRpbmcgYW5kIGVuY291cmFnZSBjb21tdW5pdHkgZW5nYWdlbWVudCwgQ2l0aSBCaWtlIG1ha2VzIGF2YWlsYWJsZSBjb21wcmVoZW5zaXZlIHRyaXAgZGF0YSwgd2hpY2ggY2FuIGJlIGRvd25sb2FkZWQgZm9yIGZ1cnRoZXIgZXhwbG9yYXRpb24uIFRoaXMgZGF0YXNldCBpbmNsdWRlcyBrZXkgZGV0YWlscyBzdWNoIGFzIHJpZGUgSURzLCBiaWtlIHR5cGVzLCB0cmlwIHN0YXJ0IGFuZCBlbmQgdGltZXMsIHN0YXRpb24gbmFtZXMsIGFuZCBzdGF0aW9uIElEcywgYWxvbmcgd2l0aCB0aGUgZ2VvZ3JhcGhpY2FsIGNvb3JkaW5hdGVzIG9mIGVhY2ggc3RhdGlvbi4gQWRkaXRpb25hbGx5LCB0aGUgZGF0YSBkaXN0aW5ndWlzaGVzIGJldHdlZW4gbWVtYmVyIGFuZCBjYXN1YWwgcmlkZXJzLCBwcm92aWRpbmcgaW5zaWdodHMgaW50byBkaWZmZXJlbnQgdXNlciBwYXR0ZXJucyBhbmQgYmVoYXZpb3JzLiBUaGUgZGlzc2VtaW5hdGlvbiBvZiB0aGlzIGRhdGEgZW1wb3dlcnMgc3Rha2Vob2xkZXJzIHRvIHBlcmZvcm0gZGV0YWlsZWQgYW5hbHlzZXMsIGhlbHBpbmcgdG8gYWRkcmVzcyBvcGVyYXRpb25hbCBjaGFsbGVuZ2VzIHN1Y2ggYXMgdGhlIGRpc3RyaWJ1dGlvbiBhbmQgYXZhaWxhYmlsaXR5IG9mIGJpa2VzIHRvIGVuc3VyZSB0aGF0IHRoZSBiaWtlLXNoYXJpbmcgc3lzdGVtIG1lZXRzIGl0cyB1c2VycycgbmVlZHMgZWZmZWN0aXZlbHkuDQoNCkluIHRoaXMgc2VjdGlvbiwgd2Ugd2lsbCBpbXBvcnQgZGF0YSBmcm9tIHRoZSBDaXRpIEJpa2Ugc2hhcmluZyBwcm9ncmFtLCBhbG9uZyB3aXRoIE5ldyBZb3JrIENpdHkgZGVtb2dyYXBoaWMgc3RhdGlzdGljcyBhbmQgd2VhdGhlciByZWNvcmRzIGZyb20gSkZLIEFpcnBvcnQuIEJ5IGludGVncmF0aW5nIGRlbW9ncmFwaGljIGFuZCB3ZWF0aGVyIGluZm9ybWF0aW9uLCB3ZSBhaW0gdG8gZW5oYW5jZSB0aGUgYWNjdXJhY3kgb2Ygb3VyIHByZWRpY3Rpb25zIGZvciBmdXR1cmUgdHJlbmRzIGluIHRoZSBOWUMgQ2l0aSBCaWtlIHNoYXJpbmcgdXNhZ2UuDQoNCkRhdGEgU291cmNlOg0KaHR0cHM6Ly9jaXRpYmlrZW55Yy5jb20vc3lzdGVtLWRhdGENCg0KYGBge3IgZGF0X3ByZXAsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFLCByZXN1bHRzPSdoaWRlJyB9DQoNCmRhdGEgPC0gcmVhZF9jc3YoIkM6L1VzZXJzLzI1MDc3L0Rlc2t0b3AvTVVTQSA1MDhfUFBBL0Fzc2lnbm1lbnQgNS9kYXRhLzIwMjQwMS1jaXRpYmlrZS10cmlwZGF0YS5jc3YvMjAyNDAxLWNpdGliaWtlLXRyaXBkYXRhXzEuY3N2IikgDQoNCnNldC5zZWVkKDEyMykgIyBTZXR0aW5nIGEgc2VlZCBmb3IgcmVwcm9kdWNpYmlsaXR5DQpkYXRhX255YyA8LSBkYXRhICU+JSANCiAgc2FtcGxlX24oc2l6ZSA9IDQwMDAwMCkNCg0KDQpkYXRfaW50ZXJ2YWwgPC0gZGF0YV9ueWMgJT4lDQogIG11dGF0ZShpbnRlcnZhbDYwID0gZmxvb3JfZGF0ZSh5bWRfaG1zKHN0YXJ0ZWRfYXQpLCB1bml0ID0gImhvdXIiKSwNCiAgICAgICAgIGludGVydmFsMTUgPSBmbG9vcl9kYXRlKHltZF9obXMoc3RhcnRlZF9hdCksIHVuaXQgPSAiMTUgbWlucyIpLA0KICAgICAgICAgd2VlayA9IHdlZWsoaW50ZXJ2YWw2MCksDQogICAgICAgICBkb3R3ID0gd2RheShpbnRlcnZhbDYwLCBsYWJlbD1UUlVFKSkNCg0KDQpOWUNDZW5zdXMgPC0gDQogIGdldF9hY3MoZ2VvZ3JhcGh5ID0gInRyYWN0IiwgDQogICAgICAgICAgdmFyaWFibGVzID0gYygiQjAxMDAzXzAwMSIsICJCMTkwMTNfMDAxIiwgDQogICAgICAgICAgICAgICAgICAgICAgICAiQjAyMDAxXzAwMiIsICJCMDgwMTNfMDAxIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICJCMDgwMTJfMDAxIiwgIkIwODMwMV8wMDEiLCANCiAgICAgICAgICAgICAgICAgICAgICAgICJCMDgzMDFfMDEwIiwgIkIwMTAwMl8wMDEiKSwgDQogICAgICAgICAgeWVhciA9IDIwMTcsIA0KICAgICAgICAgIHN0YXRlID0gIk5ZIiwgDQogICAgICAgICAgZ2VvbWV0cnkgPSBUUlVFLCANCiAgICAgICAgICBjb3VudHk9YygiTmV3IFlvcmsiLCAiS2luZ3MiLCAiUXVlZW5zIiwgIlJpY2htb25kIiwgIkJyb254IiksDQogICAgICAgICAgb3V0cHV0ID0gIndpZGUiKSAlPiUNCiAgcmVuYW1lKFRvdGFsX1BvcCA9ICBCMDEwMDNfMDAxRSwNCiAgICAgICAgIE1lZF9JbmMgPSBCMTkwMTNfMDAxRSwNCiAgICAgICAgIE1lZF9BZ2UgPSBCMDEwMDJfMDAxRSwNCiAgICAgICAgIFdoaXRlX1BvcCA9IEIwMjAwMV8wMDJFLA0KICAgICAgICAgVHJhdmVsX1RpbWUgPSBCMDgwMTNfMDAxRSwNCiAgICAgICAgIE51bV9Db21tdXRlcnMgPSBCMDgwMTJfMDAxRSwNCiAgICAgICAgIE1lYW5zX29mX1RyYW5zcG9ydCA9IEIwODMwMV8wMDFFLA0KICAgICAgICAgVG90YWxfUHVibGljX1RyYW5zID0gQjA4MzAxXzAxMEUpICU+JQ0KICBzZWxlY3QoVG90YWxfUG9wLCBNZWRfSW5jLCBXaGl0ZV9Qb3AsIFRyYXZlbF9UaW1lLA0KICAgICAgICAgTWVhbnNfb2ZfVHJhbnNwb3J0LCBUb3RhbF9QdWJsaWNfVHJhbnMsDQogICAgICAgICBNZWRfQWdlLA0KICAgICAgICAgR0VPSUQsIGdlb21ldHJ5KSAlPiUNCiAgbXV0YXRlKFBlcmNlbnRfV2hpdGUgPSBXaGl0ZV9Qb3AgLyBUb3RhbF9Qb3AsDQogICAgICAgICBNZWFuX0NvbW11dGVfVGltZSA9IFRyYXZlbF9UaW1lIC8gVG90YWxfUHVibGljX1RyYW5zLA0KICAgICAgICAgUGVyY2VudF9UYWtpbmdfUHVibGljX1RyYW5zID0gVG90YWxfUHVibGljX1RyYW5zIC8gTWVhbnNfb2ZfVHJhbnNwb3J0KQ0KDQojIEV4dHJhY3QgZ2VvZ3JhcGh5IGZhY3RvcnMNCk5ZQ1RyYWN0cyA8LSANCiAgTllDQ2Vuc3VzICU+JQ0KICBhcy5kYXRhLmZyYW1lKCkgJT4lDQogIGRpc3RpbmN0KEdFT0lELCAua2VlcF9hbGwgPSBUUlVFKSAlPiUNCiAgc2VsZWN0KEdFT0lELCBnZW9tZXRyeSkgJT4lIA0KICBzdF9zZg0KDQoNCiMgam9pbiBjZW5zdXMgdHJhY3QgaW5mb3JtYXRpb24gdG8gImRhdDIiIGRhdGEgZnJhbWUNCmRhdF9jZW5zdXMgPC0gc3Rfam9pbihkYXRfaW50ZXJ2YWwgJT4lIA0KICAgICAgICAgIGZpbHRlcihpcy5uYShzdGFydF9sbmcpID09IEZBTFNFICYNCiAgICAgICAgICAgICAgICAgICBpcy5uYShzdGFydF9sYXQpID09IEZBTFNFICYNCiAgICAgICAgICAgICAgICAgICBpcy5uYShlbmRfbG5nKSA9PSBGQUxTRSAmDQogICAgICAgICAgICAgICAgICAgaXMubmEoZW5kX2xhdCkgPT0gRkFMU0UpICU+JQ0KICAgICAgICAgIHN0X2FzX3NmKC4sIGNvb3JkcyA9IGMoInN0YXJ0X2xuZyIsICJzdGFydF9sYXQiKSwgY3JzID0gNDMyNiksDQogICAgICAgIE5ZQ1RyYWN0cyAlPiUNCiAgICAgICAgICBzdF90cmFuc2Zvcm0oY3JzPTQzMjYpLA0KICAgICAgICBqb2luPXN0X2ludGVyc2VjdHMsDQogICAgICAgICAgICAgIGxlZnQgPSBUUlVFKSAlPiUNCiAgcmVuYW1lKE9yaWdpbi5UcmFjdCA9IEdFT0lEKSAlPiUNCiAgbXV0YXRlKGZyb21fbG9uZ2l0dWRlID0gdW5saXN0KG1hcChnZW9tZXRyeSwgMSkpLA0KICAgICAgICAgZnJvbV9sYXRpdHVkZSA9IHVubGlzdChtYXAoZ2VvbWV0cnksIDIpKSklPiUNCiAgYXMuZGF0YS5mcmFtZSgpICU+JQ0KICBzZWxlY3QoLWdlb21ldHJ5KSU+JQ0KICBzdF9hc19zZiguLCBjb29yZHMgPSBjKCJlbmRfbG5nIiwgImVuZF9sYXQiKSwgY3JzID0gNDMyNikgJT4lDQogIHN0X2pvaW4oLiwgTllDVHJhY3RzICU+JQ0KICAgICAgICAgICAgc3RfdHJhbnNmb3JtKGNycz00MzI2KSwNCiAgICAgICAgICBqb2luPXN0X2ludGVyc2VjdHMsDQogICAgICAgICAgbGVmdCA9IFRSVUUpICU+JQ0KICByZW5hbWUoRGVzdGluYXRpb24uVHJhY3QgPSBHRU9JRCkgICU+JQ0KICBtdXRhdGUodG9fbG9uZ2l0dWRlID0gdW5saXN0KG1hcChnZW9tZXRyeSwgMSkpLA0KICAgICAgICAgdG9fbGF0aXR1ZGUgPSB1bmxpc3QobWFwKGdlb21ldHJ5LCAyKSkpJT4lDQogIGFzLmRhdGEuZnJhbWUoKSAlPiUNCiAgc2VsZWN0KC1nZW9tZXRyeSkNCg0KDQp3ZWF0aGVyX1BhbmVsIDwtIA0KICByaWVtX21lYXN1cmVzKHN0YXRpb24gPSAiSkZLIiwgZGF0ZV9zdGFydCA9ICIyMDI0LTAxLTAxIiwgZGF0ZV9lbmQgPSAiMjAyNC0wMS0zMSIpICU+JQ0KICBzZWxlY3QodmFsaWQsIHRtcGYsIHAwMWksIHNrbnQpICU+JQ0KICByZXBsYWNlKGlzLm5hKC4pLCAwKSAlPiUNCiAgbXV0YXRlKGludGVydmFsNjAgPSB5bWRfaChzdWJzdHIodmFsaWQsIDEsIDEzKSkpICU+JQ0KICBtdXRhdGUod2VlayA9IHdlZWsoaW50ZXJ2YWw2MCksDQogICAgICAgICBkb3R3ID0gd2RheShpbnRlcnZhbDYwLCBsYWJlbD1UUlVFKSkgJT4lDQogIGdyb3VwX2J5KGludGVydmFsNjApICU+JQ0KICBzdW1tYXJpemUoVGVtcGVyYXR1cmUgPSBtYXgodG1wZiksDQogICAgICAgICAgICBQcmVjaXBpdGF0aW9uID0gc3VtKHAwMWkpLA0KICAgICAgICAgICAgV2luZF9TcGVlZCA9IG1heChza250KSkgJT4lDQogIG11dGF0ZShUZW1wZXJhdHVyZSA9IGlmZWxzZShUZW1wZXJhdHVyZSA9PSAwLCA0MiwgVGVtcGVyYXR1cmUpKQ0KDQpgYGANCg0KUHJlc2VudCB0aGUgaG91cmx5IHZhcmlhdGlvbnMgaW4gd2VhdGhlciBjb25kaXRpb25zLCBpbmNsdWRpbmcgcHJlY2lwaXRhdGlvbiBsZXZlbHMsIHdpbmQgc3BlZWQsIGFuZCB0ZW1wZXJhdHVyZSBmbHVjdHVhdGlvbnMsIGZvciBOZXcgWW9yayBDaXR5Lg0KDQpgYGB7ciBwbG90X3dlYXRoZXIsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFLCByZXN1bHRzPSdoaWRlJ30NCmdyaWQuYXJyYW5nZSgNCiAgZ2dwbG90KHdlYXRoZXJfUGFuZWwsIGFlcyhpbnRlcnZhbDYwLFByZWNpcGl0YXRpb24pKSArIGdlb21fbGluZSgpICsgDQogIGxhYnModGl0bGU9IlBlcmNpcGl0YXRpb24iLCB4PSJIb3VyIiwgeT0iUGVyZWNpcGl0YXRpb24iKSArIHBsb3RUaGVtZSwNCiAgZ2dwbG90KHdlYXRoZXJfUGFuZWwsIGFlcyhpbnRlcnZhbDYwLFdpbmRfU3BlZWQpKSArIGdlb21fbGluZSgpICsgDQogICAgbGFicyh0aXRsZT0iV2luZCBTcGVlZCIsIHg9IkhvdXIiLCB5PSJXaW5kIFNwZWVkIikgKyBwbG90VGhlbWUsDQogIGdncGxvdCh3ZWF0aGVyX1BhbmVsLCBhZXMoaW50ZXJ2YWw2MCxUZW1wZXJhdHVyZSkpICsgZ2VvbV9saW5lKCkgKyANCiAgICBsYWJzKHRpdGxlPSJUZW1wZXJhdHVyZSIsIHg9IkhvdXIiLCB5PSJUZW1wZXJhdHVyZSIpICsgcGxvdFRoZW1lLA0KICB0b3A9IldlYXRoZXIgRGF0YSAtIE5ZQyBKRksgLSBKYW4sIDIwMjQiKQ0KYGBgDQoNCiMjIDMuIEV4cGxvcmUgdGhlIERhdGENCiANCioxKSBCaWtlIFNoYXJlIFRyZW5kcyBieSBEYXRlcy4gKg0KDQpgYGB7ciB0cmlwX3RpbWVzZXJpZXMsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFLCByZXN1bHRzPSdoaWRlJyB9DQpnZ3Bsb3QoZGF0X2NlbnN1cyAlPiUNCiAgICAgICAgIGdyb3VwX2J5KGludGVydmFsNjApICU+JQ0KICAgICAgICAgdGFsbHkoKSkrDQogIGdlb21fbGluZShhZXMoeCA9IGludGVydmFsNjAsIHkgPSBuKSkrDQogIGxhYnModGl0bGU9IkJpa2Ugc2hhcmUgdHJpcHMgcGVyIGhyLiBOWUMsIEphbiwgMjAyNCIsDQogICAgICAgeD0iRGF0ZSIsIA0KICAgICAgIHk9Ik51bWJlciBvZiB0cmlwcyIpKw0KICBwbG90VGhlbWUNCmBgYA0KDQoqMikgQmlrZSBTaGFyZSBUcmVuZHMgYnkgVGltZSBQZXJpb2RzIChSdXNoIGhvdXJzLCBBTS9QTSkuKg0KDQpgYGB7ciBtZWFuX3RyaXBzX2hpc3QsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFLCByZXN1bHRzPSdoaWRlJ30NCg0KZGF0X2NlbnN1cyA8LSBkYXRfY2Vuc3VzICU+JQ0KICBuYS5vbWl0KCkgDQoNCmRhdF9jZW5zdXMgJT4lDQogICAgICAgIG11dGF0ZSh0aW1lX29mX2RheSA9IGNhc2Vfd2hlbihob3VyKGludGVydmFsNjApIDwgNyB8IGhvdXIoaW50ZXJ2YWw2MCkgPiAxOCB+ICJPdmVybmlnaHQiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaG91cihpbnRlcnZhbDYwKSA+PSA3ICYgaG91cihpbnRlcnZhbDYwKSA8IDEwIH4gIkFNIFJ1c2giLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaG91cihpbnRlcnZhbDYwKSA+PSAxMCAmIGhvdXIoaW50ZXJ2YWw2MCkgPCAxNSB+ICJNaWQtRGF5IiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhvdXIoaW50ZXJ2YWw2MCkgPj0gMTUgJiBob3VyKGludGVydmFsNjApIDw9IDE4IH4gIlBNIFJ1c2giKSklPiUNCiAgICAgICAgIGdyb3VwX2J5KGludGVydmFsNjAsIHN0YXJ0X3N0YXRpb25fbmFtZSwgdGltZV9vZl9kYXkpICU+JQ0KICAgICAgICAgdGFsbHkoKSU+JQ0KICBncm91cF9ieShzdGFydF9zdGF0aW9uX25hbWUsIHRpbWVfb2ZfZGF5KSU+JQ0KICBzdW1tYXJpemUobWVhbl90cmlwcyA9IG1lYW4obikpJT4lDQogIGdncGxvdCgpKw0KICBnZW9tX2hpc3RvZ3JhbShhZXMobWVhbl90cmlwcyksIGJpbndpZHRoID0gMSkrDQogIGxhYnModGl0bGU9Ik1lYW4gTnVtYmVyIG9mIEhvdXJseSBUcmlwcyBQZXIgU3RhdGlvbi4gTllDLCBKYW4sIDIwMjQiLA0KICAgICAgIHg9Ik51bWJlciBvZiB0cmlwcyIsIA0KICAgICAgIHk9IkZyZXF1ZW5jeSIpKw0KICBmYWNldF93cmFwKH50aW1lX29mX2RheSkrDQogIHBsb3RUaGVtZQ0KYGBgDQoNClRoZSBjaGFydCBpbmRpY2F0ZXMgdGhhdCBib3RoIE1pZC1EYXkgYW5kIFBNIFJ1c2ggcGVyaW9kcyBleHBlcmllbmNlIHRoZSBoaWdoZXN0IGZyZXF1ZW5jeSBvZiBiaWtlIHRyaXBzLCB3aXRoIFBNIFJ1c2ggZXhoaWJpdGluZyBub3Qgb25seSB0aGUgZ3JlYXRlc3Qgb3ZlcmFsbCB0cmlwIGZyZXF1ZW5jeSBidXQgYWxzbyB0aGUgbW9zdCBmcmVxdWVudCBvY2N1cnJlbmNlIG9mIHNlY29uZCB0cmlwcyBwZXIgc3RhdGlvbi4gVGhpcyBzdWdnZXN0cyB0aGF0IGR1cmluZyB0aGUgUE0gUnVzaCwgbWFueSBzdGF0aW9ucyBjb25zaXN0ZW50bHkgc2VlIGF0IGxlYXN0IHR3byB0cmlwcyBzdGFydGluZyBwZXIgaG91ciBvbiBhdmVyYWdlLCB3aGljaCBtYXkgcmVmbGVjdCBhIHJvYnVzdCBwYXR0ZXJuIG9mIHVzYWdlIGFzIHBlb3BsZSBjb25jbHVkZSB0aGVpciB3b3JrZGF5IG9yIGVuZ2FnZSBpbiBldmVuaW5nIGFjdGl2aXRpZXMuDQoNCk92ZXJuaWdodCBwZXJpb2RzIGRpc3BsYXkgYSBzdXJwcmlzaW5nbHkgZWxldmF0ZWQgZnJlcXVlbmN5IG9mIHRyaXBzLCB3aGljaCwgd2hpbGUgbm90IGFzIGhpZ2ggYXMgdGhlIGRheXRpbWUgcGVha3MsIGlzIHNpZ25pZmljYW50IHdoZW4gY29tcGFyZWQgdG8gdGhlIEFNIFJ1c2ggcGVyaW9kLCB3aGljaCBoYXMgdGhlIGxvd2VzdCBmcmVxdWVuY3kgb2YgdHJpcHMuIFRoaXMgY291bGQgYmUgZHVlIHRvIGEgdmFyaWV0eSBvZiBmYWN0b3JzLCBzdWNoIGFzIG5pZ2h0LXNoaWZ0IHdvcmtlcnMsIGxhdGUtbmlnaHQgZW50ZXJ0YWlubWVudCBhY3Rpdml0aWVzLCBvciBldmVuIGVhcmx5LW1vcm5pbmcgZml0bmVzcyByb3V0aW5lcy4NCg0KDQoqMykgQmlrZSBTaGFyZSBUcmVuZHMgYnkgc3RhdGlvbi4qDQoNCmBgYHtyIHRyaXBzX3N0YXRpb25fZG90dyAsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFLCByZXN1bHRzPSdoaWRlJ30NCmdncGxvdChkYXRfY2Vuc3VzICU+JQ0KICAgICAgICAgZ3JvdXBfYnkoaW50ZXJ2YWw2MCwgc3RhcnRfc3RhdGlvbl9uYW1lKSAlPiUNCiAgICAgICAgIHRhbGx5KCkpKw0KICBnZW9tX2hpc3RvZ3JhbShhZXMobiksIGJpbndpZHRoID0gNSkrDQogIGxhYnModGl0bGU9IkJpa2Ugc2hhcmUgdHJpcHMgcGVyIGhyIGJ5IHN0YXRpb24uIE5ZQywgSmFuLiAyMDI0IiwNCiAgICAgICB4PSJOdW1iZXIgb2YgU3RhdGlvbnMiLCANCiAgICAgICB5PSJUcmlwIENvdW50cyIpKw0KICBwbG90VGhlbWUNCmBgYA0KDQoqNCkgQmlrZSBTaGFyZSBUcmVuZHMgYnkgZGF5IG9mIHRoZSB3ZWVrLioNCg0KYGBge3IgdHJpcHNfaG91cl9kb3R3LCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRSwgcmVzdWx0cz0naGlkZScgfQ0KDQojIGJ5IHdlZWsgZGF5cw0KZ2dwbG90KGRhdF9jZW5zdXMgJT4lIG11dGF0ZShob3VyID0gaG91cihzdGFydGVkX2F0KSkpKw0KICAgICBnZW9tX2ZyZXFwb2x5KGFlcyhob3VyLCBjb2xvciA9IGRvdHcpLCBiaW53aWR0aCA9IDEpKw0KICBsYWJzKHRpdGxlPSJCaWtlIHNoYXJlIHRyaXBzIGluIE5ZQywgYnkgZGF5IG9mIHRoZSB3ZWVrLCBKYW4uIDIwMjQiLA0KICAgICAgIHg9IkhvdXIiLCANCiAgICAgICB5PSJUcmlwIENvdW50cyIpKw0KICAgICBwbG90VGhlbWUNCg0KIyBieSB3b3JrZGF5cyBvciB3ZWVrZW5kcw0KZ2dwbG90KGRhdF9jZW5zdXMgJT4lIA0KICAgICAgICAgbXV0YXRlKGhvdXIgPSBob3VyKHN0YXJ0ZWRfYXQpLA0KICAgICAgICAgICAgICAgIHdlZWtlbmQgPSBpZmVsc2UoZG90dyAlaW4lIGMoIlN1biIsICJTYXQiKSwgIldlZWtlbmQiLCAiV2Vla2RheSIpKSkrDQogICAgIGdlb21fZnJlcXBvbHkoYWVzKGhvdXIsIGNvbG9yID0gd2Vla2VuZCksIGJpbndpZHRoID0gMSkrDQogIGxhYnModGl0bGU9IkJpa2Ugc2hhcmUgdHJpcHMgaW4gTllDIC0gd2Vla2VuZCB2cyB3ZWVrZGF5LCBKYW4uIDIwMjQiLA0KICAgICAgIHg9IkhvdXIiLCANCiAgICAgICB5PSJUcmlwIENvdW50cyIpKw0KICAgICBwbG90VGhlbWUNCmBgYA0KDQpUaGUgbGluZXMgcmVwcmVzZW50aW5nIHdlZWtkYXlzIGdlbmVyYWxseSBmb2xsb3cgYSBzaW1pbGFyIHBhdHRlcm4sIHdpdGggcHJvbm91bmNlZCBwZWFrcywgc3VnZ2VzdGluZyBjb25zaXN0ZW50IGNvbW11dGluZyBiZWhhdmlvciBvbiB0aGVzZSBkYXlzLiBJbiBjb250cmFzdCwgU2F0dXJkYXkgYW5kIFN1bmRheSBzaG93IGRpZmZlcmVudCBwYXR0ZXJucywgd2l0aCBhIG1vcmUgZ3JhZHVhbCBpbmNyZWFzZSBpbiB0cmlwcyB0aHJvdWdob3V0IHRoZSBkYXkgYW5kIG5vdCBhcyBzaGFycCBwZWFrcyBkdXJpbmcgdHJhZGl0aW9uYWwgcnVzaCBob3Vycywgd2hpY2ggaXMgdHlwaWNhbCBmb3Igd2Vla2VuZHMgd2hlbiBwZW9wbGUgYXJlIG5vdCBib3VuZCB0byBzdGFuZGFyZCB3b3JrIG9yIHNjaG9vbCBzY2hlZHVsZXMuDQoNCg0KYGBge3Igb3JpZ2luX21hcCxmaWcud2lkdGg9MTAsIGZpZy5oZWlnaHQ9MTIsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFLCByZXN1bHRzPSdoaWRlJyB9DQoNCmdncGxvdCgpICsNCiAgZ2VvbV9zZihkYXRhID0gTllDVHJhY3RzICU+JQ0KICAgICAgICAgICAgc3RfdHJhbnNmb3JtKGNycyA9IDQzMjYpLCBmaWxsID0gImdyZXk3MCIsIGNvbG9yID0gImdyZXk4MCIpICsNCiAgZ2VvbV9wb2ludChkYXRhID0gZGF0X2NlbnN1cyAlPiUNCiAgICAgICAgICAgICAgICAgICBtdXRhdGUoaG91ciA9IGhvdXIoc3RhcnRlZF9hdCksDQogICAgICAgICAgICAgICAgICAgICAgICAgIHdlZWtlbmQgPSBpZmVsc2UoZG90dyAlaW4lIGMoIlN1biIsICJTYXQiKSwgIldlZWtlbmQiLCAiV2Vla2RheSIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICB0aW1lX29mX2RheSA9IGNhc2Vfd2hlbihob3VyKGludGVydmFsNjApIDwgNyB8IGhvdXIoaW50ZXJ2YWw2MCkgPiAxOCB+ICJPdmVybmlnaHQiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBob3VyKGludGVydmFsNjApID49IDcgJiBob3VyKGludGVydmFsNjApIDwgMTAgfiAiQU0gUnVzaCIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhvdXIoaW50ZXJ2YWw2MCkgPj0gMTAgJiBob3VyKGludGVydmFsNjApIDwgMTUgfiAiTWlkLURheSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhvdXIoaW50ZXJ2YWw2MCkgPj0gMTUgJiBob3VyKGludGVydmFsNjApIDw9IDE4IH4gIlBNIFJ1c2giKSkgJT4lDQogICAgICAgICAgICAgICAgICAgZ3JvdXBfYnkoc3RhcnRfc3RhdGlvbl9pZCwgZnJvbV9sYXRpdHVkZSwgZnJvbV9sb25naXR1ZGUsIHdlZWtlbmQsIHRpbWVfb2ZfZGF5KSAlPiUNCiAgICAgICAgICAgICAgICAgICB0YWxseSgpLA0KICAgICAgICAgICAgIGFlcyh4ID0gZnJvbV9sb25naXR1ZGUsIHkgPSBmcm9tX2xhdGl0dWRlLCBjb2xvciA9IGxvZzFwKG4pKSwNCiAgICAgICAgICAgICBmaWxsID0gInRyYW5zcGFyZW50Iiwgc2l6ZSA9IDAuMDgpICsNCiAgc2NhbGVfY29sb3VyX2dyYWRpZW50KGxvdyA9ICJjb3Juc2lsayIsIGhpZ2ggPSAiaW5kaWFucmVkMiIpICsNCiAgeWxpbShtaW4oZGF0X2NlbnN1cyRmcm9tX2xhdGl0dWRlKSwgbWF4KGRhdF9jZW5zdXMkZnJvbV9sYXRpdHVkZSkpICsNCiAgeGxpbShtaW4oZGF0X2NlbnN1cyRmcm9tX2xvbmdpdHVkZSksIG1heChkYXRfY2Vuc3VzJGZyb21fbG9uZ2l0dWRlKSkrDQogIGxhYnModGl0bGUgPSAiQmlrZSBzaGFyZSB0cmlwcyBwZXIgaHIgYnkgc3RhdGlvbi4gTllDLCBKYW4uMjAyNCIpICsNCiAgbWFwVGhlbWUgKw0KICBmYWNldF9ncmlkKHdlZWtlbmQgfiB0aW1lX29mX2RheSkgKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAicmlnaHQiKQ0KDQpgYGANCg0KRHVyaW5nIHdlZWtkYXlzLCBhIGhpZ2ggY29uY2VudHJhdGlvbiBvZiBiaWtlIHRyaXBzIG9jY3VycyB3aXRoaW4ga2V5IGJ1c2luZXNzIGRpc3RyaWN0cyBpbiBNYW5oYXR0YW4sIGluZGljYXRpbmcgYSBwcmV2YWxlbnQgY29tbXV0ZXIgcGF0dGVybiBhc3NvY2lhdGVkIHdpdGggd29yay1yZWxhdGVkIHRyYXZlbCB2aWEgYmlrZSBzaGFyaW5nLiBIb3dldmVyLCB0aGlzIHBhdHRlcm4gZGlzc2lwYXRlcyBvdmVyIHRoZSB3ZWVrZW5kOyB0cmlwIGRpc3RyaWJ1dGlvbiBiZWNvbWVzIG1vcmUgZGlzcGVyc2VkIGFjcm9zcyB2YXJpb3VzIGxvY2F0aW9ucyBhbmQgdGhlIGZyZXF1ZW5jeSBkaW1pbmlzaGVzLCByZWZsZWN0aW5nIGEgc2hpZnQgdG8gbW9yZSBsZWlzdXJlbHkgb3IgZGl2ZXJzZSB0cmF2ZWwgcHVycG9zZXMgdGhhdCBhcmUgbm90IGNlbnRlcmVkIGFyb3VuZCBjb21tdXRpbmcuDQoNCiMjIDQuIE1vZGVsIERldmVsb3BtZW50DQoNCioxKSBDcmVhdGUgU3BhY2UtVGltZSBQYW5lbCoNCg0KYGBge3IgcGFuZWxfbGVuZ3RoX2NoZWNrLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRSwgcmVzdWx0cz0naGlkZSd9DQoNCiMgQ2FsY3VsYXRlIHRoZSBudW1iZXIgb2Ygcm93cyBpbiB0aGUgcmVzdWx0aW5nIHBhbmVsIGRhdGFzZXQNCiMgVGhpcyBsaW5lIGNhbGN1bGF0ZXMgdGhlIG51bWJlciBvZiByb3dzIGluIHRoZSByZXN1bHRpbmcgcGFuZWwgZGF0YXNldCBieSBtdWx0aXBseWluZyB0aGUgbnVtYmVyIG9mIHVuaXF1ZSB2YWx1ZXMgaW4gdGhlICdpbnRlcnZhbDYwJyBjb2x1bW4gd2l0aCB0aGUgbnVtYmVyIG9mIHVuaXF1ZSB2YWx1ZXMgaW4gdGhlICdzdGFydF9zdGF0aW9uX2lkJyBjb2x1bW4gaW4gdGhlICdkYXRfY2Vuc3VzJyBkYXRhc2V0Lg0KDQpsZW5ndGgodW5pcXVlKGRhdF9jZW5zdXMkaW50ZXJ2YWw2MCkpICogbGVuZ3RoKHVuaXF1ZShkYXRfY2Vuc3VzJHN0YXJ0X3N0YXRpb25faWQpKQ0KDQoNCnN0dWR5LnBhbmVsIDwtIGV4cGFuZC5ncmlkKGludGVydmFsNjAgPSB1bmlxdWUoZGF0X2NlbnN1cyRpbnRlcnZhbDYwKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHN0YXJ0X3N0YXRpb25faWQgPSB1bmlxdWUoZGF0X2NlbnN1cyRzdGFydF9zdGF0aW9uX2lkKSkgJT4lDQogIGxlZnRfam9pbihkYXRfY2Vuc3VzICU+JSANCiAgICAgICAgICAgIHNlbGVjdChzdGFydF9zdGF0aW9uX2lkLCBzdGFydF9zdGF0aW9uX25hbWUsIE9yaWdpbi5UcmFjdCwgZnJvbV9sb25naXR1ZGUsIGZyb21fbGF0aXR1ZGUpICU+JSANCiAgICAgICAgICAgIGRpc3RpbmN0KCkgJT4lIA0KICAgICAgICAgICAgZ3JvdXBfYnkoc3RhcnRfc3RhdGlvbl9pZCkgJT4lIA0KICAgICAgICAgICAgc2xpY2UoMSksDQogICAgICAgICAgICBieSA9ICJzdGFydF9zdGF0aW9uX2lkIikNCg0KYGBgDQoNCioyLiBDcmVhdGUgUGFuZWwqDQoNCmBgYHtyIGNyZWF0ZV9wYW5lbCwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0UsIHJlc3VsdHM9J2hpZGUnfQ0KIyBDcmVhdGUgYSBwYW5lbCBkYXRhc2V0IGZvciBiaWtlIHJpZGUgZGF0YQ0KcmlkZS5wYW5lbCA8LSANCiAgZGF0X2NlbnN1cyAlPiUgICMgU3RhcnQgd2l0aCB0aGUgZGF0X2NlbnN1cyBkYXRhc2V0DQogIG11dGF0ZShUcmlwX0NvdW50ZXIgPSAxKSAlPiUgICMgQWRkIGEgVHJpcF9Db3VudGVyIHZhcmlhYmxlLCBpbmRpY2F0aW5nIG9uZSB0cmlwIGZvciBlYWNoIHJvdw0KICByaWdodF9qb2luKHN0dWR5LnBhbmVsKSAlPiUgICMgUmlnaHQgam9pbiB3aXRoIHRoZSBzdHVkeS5wYW5lbCBkYXRhc2V0IHRvIGVuc3VyZSBhbGwgc3BhdGlhbC10aW1lIGNvbWJpbmF0aW9ucyBhcmUgaW5jbHVkZWQNCiAgZ3JvdXBfYnkoaW50ZXJ2YWw2MCwgc3RhcnRfc3RhdGlvbl9pZCwgc3RhcnRfc3RhdGlvbl9uYW1lLCBPcmlnaW4uVHJhY3QsIGZyb21fbG9uZ2l0dWRlLCBmcm9tX2xhdGl0dWRlKSAlPiUgICMgR3JvdXAgYnkgc3BhdGlhbC10aW1lIGFuZCBzdGF0aW9uIHZhcmlhYmxlcw0KICBzdW1tYXJpemUoVHJpcF9Db3VudCA9IHN1bShUcmlwX0NvdW50ZXIsIG5hLnJtID0gVFJVRSkpICU+JSAgIyBTdW1tYXJpemUgdGhlIFRyaXBfQ291bnRlciB2YXJpYWJsZSB0byBjYWxjdWxhdGUgdG90YWwgdHJpcCBjb3VudCBmb3IgZWFjaCBncm91cA0KICBsZWZ0X2pvaW4od2VhdGhlcl9QYW5lbCkgJT4lICAjIExlZnQgam9pbiB3aXRoIHRoZSB3ZWF0aGVyLlBhbmVsIGRhdGFzZXQgdG8gaW5jbHVkZSB3ZWF0aGVyIGluZm9ybWF0aW9uDQogIHVuZ3JvdXAoKSAlPiUgICMgUmVtb3ZlIGdyb3VwaW5nDQogIGZpbHRlcighaXMubmEoc3RhcnRfc3RhdGlvbl9pZCkpICU+JSAgIyBGaWx0ZXIgb3V0IHJvd3Mgd2l0aCBtaXNzaW5nIHN0YXJ0X3N0YXRpb25faWQgdmFsdWVzDQogIG11dGF0ZSh3ZWVrID0gd2VlayhpbnRlcnZhbDYwKSwgICMgQ3JlYXRlIGEgd2VlayB2YXJpYWJsZSBpbmRpY2F0aW5nIHRoZSB3ZWVrIG51bWJlciBvZiB0aGUgeWVhcg0KICAgICAgICAgZG90dyA9IHdkYXkoaW50ZXJ2YWw2MCwgbGFiZWwgPSBUUlVFKSkgJT4lICAjIENyZWF0ZSBhIGRvdHcgdmFyaWFibGUgaW5kaWNhdGluZyB0aGUgZGF5IG9mIHRoZSB3ZWVrIChsYWJlbCBmb3JtYXQpDQogIGZpbHRlcighaXMubmEoT3JpZ2luLlRyYWN0KSkgICMgRmlsdGVyIG91dCByb3dzIHdpdGggbWlzc2luZyBPcmlnaW4uVHJhY3QgdmFsdWVzDQoNCg0KcmlkZS5wYW5lbCA8LSANCiAgbGVmdF9qb2luKHJpZGUucGFuZWwsIE5ZQ0NlbnN1cyAlPiUNCiAgICAgICAgICAgICAgYXMuZGF0YS5mcmFtZSgpICU+JQ0KICAgICAgICAgICAgICBzZWxlY3QoLWdlb21ldHJ5KSwgYnkgPSBjKCJPcmlnaW4uVHJhY3QiID0gIkdFT0lEIikpDQpgYGANCg0KKjMpIENyZWF0ZSB0aW1lIGxhZ3MqDQoNCg0KYGBge3IgdGltZV9sYWdzLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRSwgcmVzdWx0cz0naGlkZSd9DQoNCiMgQXJyYW5nZSB0aGUgcmlkZS5wYW5lbCBkYXRhc2V0IGJhc2VkIG9uICdzdGFydF9zdGF0aW9uX2lkJyBhbmQgJ2ludGVydmFsNjAnDQojIFRoaXMgc3RlcCBlbnN1cmVzIHRoYXQgdGhlIGRhdGFzZXQgaXMgb3JkZXJlZCBwcm9wZXJseSBmb3Igc3Vic2VxdWVudCBjYWxjdWxhdGlvbnMNCnJpZGUucGFuZWwgPC0gDQogIHJpZGUucGFuZWwgJT4lIA0KICBhcnJhbmdlKHN0YXJ0X3N0YXRpb25faWQsIGludGVydmFsNjApICU+JQ0KDQojIENvbXB1dGUgbGFnIHZhcmlhYmxlcyBmb3IgdGhlICdUcmlwX0NvdW50JyB2YXJpYWJsZSBhdCBkaWZmZXJlbnQgdGltZSBpbnRlcnZhbHMNCiAgbXV0YXRlKA0KICAgIGxhZ0hvdXIgPSBkcGx5cjo6bGFnKFRyaXBfQ291bnQsIDEpLCAgIyBMYWcgb2YgMSBob3VyDQogICAgbGFnMkhvdXJzID0gZHBseXI6OmxhZyhUcmlwX0NvdW50LCAyKSwgICMgTGFnIG9mIDIgaG91cnMNCiAgICBsYWczSG91cnMgPSBkcGx5cjo6bGFnKFRyaXBfQ291bnQsIDMpLCAgIyBMYWcgb2YgMyBob3Vycw0KICAgIGxhZzRIb3VycyA9IGRwbHlyOjpsYWcoVHJpcF9Db3VudCwgNCksICAjIExhZyBvZiA0IGhvdXJzDQogICAgbGFnMTJIb3VycyA9IGRwbHlyOjpsYWcoVHJpcF9Db3VudCwgMTIpLCAgIyBMYWcgb2YgMTIgaG91cnMNCiAgICBsYWcxZGF5ID0gZHBseXI6OmxhZyhUcmlwX0NvdW50LCAyNCkgICMgTGFnIG9mIDEgZGF5ICgyNCBob3VycykNCiAgKSAlPiUNCg0KIyBJZGVudGlmeSBob2xpZGF5cyBhbmQgY3JlYXRlIGEgYmluYXJ5IHZhcmlhYmxlIGluZGljYXRpbmcgd2hldGhlciBpdCdzIGEgaG9saWRheQ0KICBtdXRhdGUoDQogICAgaG9saWRheSA9IGlmZWxzZSh5ZGF5KGludGVydmFsNjApID09IDE0OCwgMSwgMCkgICMgQ2hlY2sgaWYgaXQncyBhIGhvbGlkYXkgKDE0OHRoIGRheSBvZiB0aGUgeWVhcikNCiAgKSAlPiUNCg0KIyBDcmVhdGUgYSB2YXJpYWJsZSByZXByZXNlbnRpbmcgdGhlIGRheSBvZiB0aGUgeWVhcg0KICBtdXRhdGUoDQogICAgZGF5ID0geWRheShpbnRlcnZhbDYwKSAgIyBFeHRyYWN0IHRoZSBkYXkgb2YgdGhlIHllYXIgZnJvbSAnaW50ZXJ2YWw2MCcNCiAgKSAlPiUNCg0KIyBDcmVhdGUgYWRkaXRpb25hbCB2YXJpYWJsZXMgcmVsYXRlZCB0byBob2xpZGF5IGxhZw0KICBtdXRhdGUoDQogICAgaG9saWRheUxhZyA9IGNhc2Vfd2hlbigNCiAgICAgIGRwbHlyOjpsYWcoaG9saWRheSwgMSkgPT0gMSB+ICJQbHVzT25lRGF5IiwgICMgSWYgdGhlIHByZXZpb3VzIGRheSB3YXMgYSBob2xpZGF5DQogICAgICBkcGx5cjo6bGFnKGhvbGlkYXksIDIpID09IDEgfiAiUGx1c1R3b0RheXMiLCAgIyBJZiB0d28gZGF5cyBhZ28gd2FzIGEgaG9saWRheQ0KICAgICAgZHBseXI6OmxhZyhob2xpZGF5LCAzKSA9PSAxIH4gIlBsdXNUaHJlZURheXMiLCAgIyBJZiB0aHJlZSBkYXlzIGFnbyB3YXMgYSBob2xpZGF5DQogICAgICBkcGx5cjo6bGVhZChob2xpZGF5LCAxKSA9PSAxIH4gIk1pbnVzT25lRGF5IiwgICMgSWYgdGhlIG5leHQgZGF5IHdpbGwgYmUgYSBob2xpZGF5DQogICAgICBkcGx5cjo6bGVhZChob2xpZGF5LCAyKSA9PSAxIH4gIk1pbnVzVHdvRGF5cyIsICAjIElmIHR3byBkYXlzIGFoZWFkIHdpbGwgYmUgYSBob2xpZGF5DQogICAgICBkcGx5cjo6bGVhZChob2xpZGF5LCAzKSA9PSAxIH4gIk1pbnVzVGhyZWVEYXlzIiAgIyBJZiB0aHJlZSBkYXlzIGFoZWFkIHdpbGwgYmUgYSBob2xpZGF5DQogICAgKSwNCiAgICBob2xpZGF5TGFnID0gaWZlbHNlKGlzLm5hKGhvbGlkYXlMYWcpID09IFRSVUUsIDAsIGhvbGlkYXlMYWcpICAjIENvbnZlcnQgTkEgdG8gMCBmb3Igbm9uLWhvbGlkYXkgY2FzZXMNCiAgKQ0KDQoNCmBgYA0KDQpgYGB7ciBldmFsdWF0ZV9sYWdzLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRX0NCg0KIyBBc3N1bWluZyB0aGF0IHJpZGUucGFuZWwgaXMgeW91ciBkYXRhIGZyYW1lIGFuZCBpdCBoYXMgYmVlbiBwcm9jZXNzZWQgYXMgcGVyIHRoZSBSIGNvZGUgeW91IHByb3ZpZGVkDQojIE5vdywgd2Ugd2lsbCBzdW1tYXJpemUgdGhlIGNvcnJlbGF0aW9ucyBhbmQgdGhlbiBjcmVhdGUgYSBrYWJsZQ0KDQpyaWRlLnBhbmVsICU+JQ0KICBhcy5kYXRhLmZyYW1lKCkgJT4lDQogIGdyb3VwX2J5KGludGVydmFsNjApICU+JQ0KICBzdW1tYXJpemVfYXQodmFycyhzdGFydHNfd2l0aCgibGFnIiksICJUcmlwX0NvdW50IiksIG1lYW4sIG5hLnJtID0gVFJVRSkgJT4lDQogIGdhdGhlcihWYXJpYWJsZSwgVmFsdWUsIC1pbnRlcnZhbDYwLCAtVHJpcF9Db3VudCkgJT4lDQogIG11dGF0ZShWYXJpYWJsZSA9IGZhY3RvcihWYXJpYWJsZSwgbGV2ZWxzPWMoImxhZ0hvdXIiLCAibGFnMkhvdXJzIiwgImxhZzNIb3VycyIsICJsYWc0SG91cnMiLCAibGFnMTJIb3VycyIsICJsYWcxZGF5IikpKSAlPiUNCiAgZ3JvdXBfYnkoVmFyaWFibGUpICU+JQ0KICBzdW1tYXJpemUoY29ycmVsYXRpb24gPSByb3VuZChjb3IoVmFsdWUsIFRyaXBfQ291bnQpLCAyKSkgJT4lDQogICMgTm93IHdlIHdpbGwgdXNlIGthYmxlIHRvIGNyZWF0ZSBhIHRhYmxlDQogIGthYmxlKGZvcm1hdCA9ICJodG1sIiwgY2FwdGlvbiA9ICJDb3JyZWxhdGlvbiBiZXR3ZWVuIGxhZ3MgYW5kIFRyaXAgQ291bnQiKSAlPiUNCiAga2FibGVfc3R5bGluZyhib290c3RyYXBfb3B0aW9ucyA9IGMoInN0cmlwZWQiLCAiaG92ZXIiKSwgZnVsbF93aWR0aCA9IEYpICU+JQ0KICBjb2x1bW5fc3BlYygxLCBib2xkID0gVCkgIyBNYWtlIHRoZSBmaXJzdCBjb2x1bW4gYm9sZA0KDQpgYGANCg0KbGFnSG91ciAoMC44Myk6IEluZGljYXRlcyBhIHZlcnkgc3Ryb25nIHBvc2l0aXZlIGNvcnJlbGF0aW9uIHdpdGggdGhlIG51bWJlciBvZiB0cmlwcyBmcm9tIHRoZSBwcmV2aW91cyBob3VyLiBUaGlzIHN1Z2dlc3RzIHRoYXQgYmlrZSB1c2FnZSBpbiB0aGUgcHJldmlvdXMgaG91ciBpcyBhIHN0cm9uZyBwcmVkaWN0b3Igb2YgYmlrZSB1c2FnZSBpbiB0aGUgY3VycmVudCBob3VyLg0KbGFnMkhvdXJzICgwLjYzKTogQWxzbyBhIHBvc2l0aXZlIGNvcnJlbGF0aW9uLCB0aG91Z2ggbm90IGFzIHN0cm9uZyBhcyB0aGUgMS1ob3VyIGxhZywgc2hvd2luZyB0aGF0IGJpa2UgdXNhZ2UgdHdvIGhvdXJzIHByaW9yIHN0aWxsIGhhcyBhIHNpZ25pZmljYW50IGltcGFjdCBvbiBjdXJyZW50IGJpa2UgdXNhZ2UuDQpsYWczSG91cnMgKDAuNDQpIGFuZCBsYWc0SG91cnMgKDAuMjgpOiBUaGVzZSB2YWx1ZXMgcmVwcmVzZW50IHBvc2l0aXZlIGNvcnJlbGF0aW9ucyBidXQgYXJlIHdlYWtlciwgaW5kaWNhdGluZyB0aGF0IGFzIHRoZSB0aW1lIGxhZyBpbmNyZWFzZXMsIHRoZSBwcmVkaWN0aXZlIHBvd2VyIG9mIHByZXZpb3VzIGJpa2UgdXNhZ2UgZGltaW5pc2hlcy4NCmxhZzEySG91cnMgKC0wLjM5KTogVGhpcyBuZWdhdGl2ZSBjb3JyZWxhdGlvbiBzdWdnZXN0cyBhbiBpbnZlcnNlIHJlbGF0aW9uc2hpcCwgaW5kaWNhdGluZyB0aGF0IHRoZSBudW1iZXIgb2YgdHJpcHMgaXMgZ2VuZXJhbGx5IG9wcG9zaXRlIGluIHRyZW5kIDEyIGhvdXJzIHByaW9yLiBUaGlzIGNvdWxkIHJlZmxlY3QgYSBkaWZmZXJlbmNlIGluIGJpa2UgdXNhZ2UgcGF0dGVybnMgYmV0d2VlbiBtb3JuaW5nIGFuZCBldmVuaW5nLg0KbGFnMWRheSAoMC42Nik6IEEgcmVsYXRpdmVseSBzdHJvbmcgcG9zaXRpdmUgY29ycmVsYXRpb24gaW5kaWNhdGVzIHRoYXQgdGhlIG51bWJlciBvZiB0cmlwcyBpcyBzaW1pbGFyIGZyb20gb25lIGRheSB0byB0aGUgbmV4dCwgcG9zc2libHkgaGlnaGxpZ2h0aW5nIGNvbnNpc3RlbnQgZGFpbHkgcGF0dGVybnMgb2YgYmlrZSB1c2FnZS4NCg0KDQoqNC4gUnVuIE1vZGVscyoNCg0KDQpgYGB7ciB0cmFpbl90ZXN0LCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRSwgcmVzdWx0cz0naGlkZScgfQ0KDQoNCiMgRmlsdGVyIHRoZSByaWRlLnBhbmVsIGRhdGEgdG8gY3JlYXRlIHN1YnNldHMgZm9yIHRyYWluaW5nIGFuZCB0ZXN0aW5nDQpyaWRlLnBhbmVsLnRyYWluLnN1YnNldCA8LSByaWRlLnBhbmVsICU+JSBmaWx0ZXIod2VlayA+PSAzKQ0KcmlkZS5wYW5lbC50ZXN0LnN1YnNldCA8LSByaWRlLnBhbmVsICU+JSBmaWx0ZXIod2VlayA8IDMpDQoNCiMgUmFuZG9tbHkgc2VsZWN0IDQwLDAwMCByb3dzIGZyb20gdGhlIHRyYWluaW5nIHN1YnNldA0KcmlkZS5UcmFpbiA8LSByaWRlLnBhbmVsLnRyYWluLnN1YnNldCAlPiUgc2FtcGxlX24oNDAwMDApDQoNCiMgTm93LCBzZWxlY3QgdXAgdG8gNDAsMDAwIHJvd3MgZm9yIHRoZSB0ZXN0IHNldC4gSWYgdGhlcmUgYXJlIGxlc3MgdGhhbiA0MCwwMDAsIHNlbGVjdCBhbGwuDQpuX3Rlc3QgPC0gbWluKDQwMDAwLCBucm93KHJpZGUucGFuZWwudGVzdC5zdWJzZXQpKQ0KcmlkZS5UZXN0IDwtIHJpZGUucGFuZWwudGVzdC5zdWJzZXQgJT4lIHNhbXBsZV9uKG5fdGVzdCkNCg0KDQoNCnJlZzEgPC0gDQogIGxtKFRyaXBfQ291bnQgfiAgaG91cihpbnRlcnZhbDYwKSArIGRvdHcgKyBUZW1wZXJhdHVyZSwgIGRhdGE9cmlkZS5UcmFpbikNCg0KcmVnMiA8LSANCiAgbG0oVHJpcF9Db3VudCB+ICBzdGFydF9zdGF0aW9uX25hbWUgKyBkb3R3ICsgVGVtcGVyYXR1cmUsICBkYXRhPXJpZGUuVHJhaW4pDQoNCnJlZzMgPC0gDQogIGxtKFRyaXBfQ291bnQgfiAgc3RhcnRfc3RhdGlvbl9uYW1lICsgaG91cihpbnRlcnZhbDYwKSArIGRvdHcgKyBUZW1wZXJhdHVyZSArIFByZWNpcGl0YXRpb24sIA0KICAgICBkYXRhPXJpZGUuVHJhaW4pDQoNCnJlZzQgPC0gDQogIGxtKFRyaXBfQ291bnQgfiAgc3RhcnRfc3RhdGlvbl9uYW1lICsgIGhvdXIoaW50ZXJ2YWw2MCkgKyBkb3R3ICsgVGVtcGVyYXR1cmUgKyBQcmVjaXBpdGF0aW9uICsNCiAgICAgICAgICAgICAgICAgICBsYWdIb3VyICsgbGFnMkhvdXJzICtsYWczSG91cnMgKyBsYWcxMkhvdXJzICsgbGFnMWRheSwgDQogICAgIGRhdGE9cmlkZS5UcmFpbikNCg0KcmVnNSA8LSANCiAgbG0oVHJpcF9Db3VudCB+ICBzdGFydF9zdGF0aW9uX25hbWUgKyBob3VyKGludGVydmFsNjApICsgZG90dyArIFRlbXBlcmF0dXJlICsgUHJlY2lwaXRhdGlvbiArDQogICAgICAgICAgICAgICAgICAgbGFnSG91ciArIGxhZzJIb3VycyArbGFnM0hvdXJzICtsYWcxMkhvdXJzICsgbGFnMWRheSArIGhvbGlkYXlMYWcgKyBob2xpZGF5LCANCiAgICAgZGF0YT1yaWRlLlRyYWluKQ0KYGBgDQoNCiMjIDQuIE1vZGVsIEV2YWx1YXRpb24NCg0KKjEpIFByZWRpY3Rpb24qDQoNCmBgYHtyIG5lc3RfZGF0YSwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0V9DQpyaWRlLlRlc3Qud2Vla05lc3QgPC0gDQogIHJpZGUuVGVzdCAlPiUNCiAgbmVzdCgtd2VlaykgDQoNCm1vZGVsX3ByZWQgPC0gZnVuY3Rpb24oZGF0LCBmaXQpew0KICAgcHJlZCA8LSBwcmVkaWN0KGZpdCwgbmV3ZGF0YSA9IGRhdCl9DQoNCndlZWtfcHJlZGljdGlvbnMgPC0gDQogIHJpZGUuVGVzdC53ZWVrTmVzdCAlPiUgDQogICAgbXV0YXRlKEFUaW1lX0ZFID0gbWFwKC54ID0gZGF0YSwgZml0ID0gcmVnMSwgLmYgPSBtb2RlbF9wcmVkKSwNCiAgICAgICAgICAgQlNwYWNlX0ZFID0gbWFwKC54ID0gZGF0YSwgZml0ID0gcmVnMiwgLmYgPSBtb2RlbF9wcmVkKSwNCiAgICAgICAgICAgQ1RpbWVfU3BhY2VfRkUgPSBtYXAoLnggPSBkYXRhLCBmaXQgPSByZWczLCAuZiA9IG1vZGVsX3ByZWQpLA0KICAgICAgICAgICBEVGltZV9TcGFjZV9GRV90aW1lTGFncyA9IG1hcCgueCA9IGRhdGEsIGZpdCA9IHJlZzQsIC5mID0gbW9kZWxfcHJlZCksDQogICAgICAgICAgIEVUaW1lX1NwYWNlX0ZFX3RpbWVMYWdzX2hvbGlkYXlMYWdzID0gbWFwKC54ID0gZGF0YSwgZml0ID0gcmVnNSwgLmYgPSBtb2RlbF9wcmVkKSkgJT4lIA0KICAgIGdhdGhlcihSZWdyZXNzaW9uLCBQcmVkaWN0aW9uLCAtZGF0YSwgLXdlZWspICU+JQ0KICAgIG11dGF0ZShPYnNlcnZlZCA9IG1hcChkYXRhLCBwdWxsLCBUcmlwX0NvdW50KSwNCiAgICAgICAgICAgQWJzb2x1dGVfRXJyb3IgPSBtYXAyKE9ic2VydmVkLCBQcmVkaWN0aW9uLCAgfiBhYnMoLnggLSAueSkpLA0KICAgICAgICAgICBNQUUgPSBtYXBfZGJsKEFic29sdXRlX0Vycm9yLCBtZWFuLCBuYS5ybSA9IFRSVUUpLA0KICAgICAgICAgICBzZF9BRSA9IG1hcF9kYmwoQWJzb2x1dGVfRXJyb3IsIHNkLCBuYS5ybSA9IFRSVUUpKQ0KDQprYWJsZV9vdXRwdXQgPC0gd2Vla19wcmVkaWN0aW9ucyAlPiUNCiAga2FibGUoZm9ybWF0ID0gImh0bWwiLCBjYXB0aW9uID0gIldlZWtseSBQcmVkaWN0aW9ucyBhbmQgRXJyb3IgTWV0cmljcyIpICU+JQ0KICBrYWJsZV9zdHlsaW5nKGJvb3RzdHJhcF9vcHRpb25zID0gYygic3RyaXBlZCIsICJob3ZlciIpLCBmdWxsX3dpZHRoID0gRkFMU0UpICU+JQ0KICBjb2x1bW5fc3BlYygxLCBib2xkID0gVFJVRSkNCmBgYA0KDQoqMikgRXhhbWluZSBFcnJvciBNZXRyaWNzIGZvciBBY2N1cmFjeSoNCg0KDQpgYGB7ciBwbG90X2Vycm9yc19ieV9tb2RlbCwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0UsIHJlc3VsdHM9J2hpZGUnIH0NCg0KcGFsZXR0ZTUgPC0gYygiY29ybnNpbGsiLCAiI2Y4ZTBjOCIsICIjZjFjMWIxIiwgIiNlYTkyOWEiLCAiaW5kaWFucmVkMyIpDQoNCndlZWtfcHJlZGljdGlvbnMgJT4lDQogIGRwbHlyOjpzZWxlY3Qod2VlaywgUmVncmVzc2lvbiwgTUFFKSAlPiUNCiAgZ2F0aGVyKFZhcmlhYmxlLCBNQUUsIC1SZWdyZXNzaW9uLCAtd2VlaykgJT4lDQogIGdncGxvdChhZXMod2VlaywgTUFFKSkgKyANCiAgICBnZW9tX2JhcihhZXMoZmlsbCA9IFJlZ3Jlc3Npb24pLCBwb3NpdGlvbiA9ICJkb2RnZSIsIHN0YXQ9ImlkZW50aXR5IikgKw0KICAgIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IHBhbGV0dGU1KSArDQogICAgbGFicyh0aXRsZSA9ICJNZWFuIEFic29sdXRlIEVycm9ycyBieSBtb2RlbCBzcGVjaWZpY2F0aW9uIGFuZCB3ZWVrIikgKw0KICBwbG90VGhlbWUNCg0KDQpgYGANCg0KSW4gdGhlIHByb3ZpZGVkIGNoYXJ0LCBpdCBhcHBlYXJzIHRoYXQgTW9kZWwgQSwgd2hpY2ggaW5jb3Jwb3JhdGVzIHRpbWUgb2YgZGF5IGFuZCB0ZW1wZXJhdHVyZSB2YXJpYWJsZXMsIHJlc3VsdHMgaW4gdGhlIGhpZ2hlc3QgTWVhbiBBYnNvbHV0ZSBFcnJvcnMgKE1BRSksIHN1Z2dlc3RpbmcgdGhhdCBpdCBtYXkgbm90IGJlIGNhcHR1cmluZyBlbm91Z2ggb2YgdGhlIGZhY3RvcnMgdGhhdCBpbmZsdWVuY2UgdHJpcCBjb3VudHMuIE1vZGVscyBEIGFuZCBFLCB3aGljaCBhcmUgbW9yZSBjb21wbGV4IGFuZCBpbmNsdWRlIHRpbWUgbGFncyBhbmQgaG9saWRheSBlZmZlY3RzIGluIGFkZGl0aW9uIHRvIHNwYXRpYWwgYW5kIHdlYXRoZXIgdmFyaWFibGVzLCBzaG93IHJlbGF0aXZlbHkgbG93ZXIgTUFFcy4gVGhpcyBpbmRpY2F0ZXMgdGhhdCB0aGUgaW50ZWdyYXRpb24gb2YgYWRkaXRpb25hbCB0aW1lLXJlbGF0ZWQgZmFjdG9ycyBnZW5lcmFsbHkgY29udHJpYnV0ZXMgdG8gYSBkZWNyZWFzZSBpbiBwcmVkaWN0aW9uIGVycm9yLCB1bmRlcnNjb3JpbmcgdGhlIGltcG9ydGFuY2Ugb2YgY29uc2lkZXJpbmcgYm90aCBpbW1lZGlhdGUgYW5kIGhpc3RvcmljYWwgdGVtcG9yYWwgcGF0dGVybnMgaW4gdHJpcCBjb3VudCBwcmVkaWN0aW9uLg0KDQpJbnRlcmVzdGluZ2x5LCBNb2RlbCBDIHN0YW5kcyBvdXQgd2l0aCBhbiBhYm5vcm1hbGx5IGhpZ2hlciBlcnJvciB0aGFuIG1pZ2h0IGJlIGV4cGVjdGVkIGdpdmVuIGl0cyBjb21iaW5hdGlvbiBvZiB0aW1lLCBzcGF0aWFsLCBhbmQgd2VhdGhlciB2YXJpYWJsZXMuIFRoaXMgY291bGQgaW1wbHkgYSBuZWVkIHRvIHJlLWV2YWx1YXRlIHRoZSBzcGVjaWZpYyB2YXJpYWJsZXMgaW5jbHVkZWQgb3IgdGhlIHdheSB0aGV5IGludGVyYWN0IHdpdGhpbiB0aGUgbW9kZWwuDQoNCk5vdGFibHksIE1vZGVscyBEIGFuZCBFIHBlcmZvcm0gc2ltaWxhcmx5IGFjcm9zcyB0aGUgd2Vla3MsIHN1Z2dlc3RpbmcgdGhhdCB0aGUgaW5jbHVzaW9uIG9mIGhvbGlkYXkgbGFncyBkb2VzIG5vdCBzaWduaWZpY2FudGx5IGVuaGFuY2UgbW9kZWwgcGVyZm9ybWFuY2UgYmV5b25kIHRoZSB0aW1lIGxhZ3MgYWxyZWFkeSBhY2NvdW50ZWQgZm9yIGluIE1vZGVsIEQuIFRoaXMgY291bGQgYmUgYSByZWZsZWN0aW9uIG9mIHRoZSBub24tbGluZWFyIG5hdHVyZSBvZiBob2xpZGF5IGltcGFjdHMgb3IgYW4gaW5kaWNhdGlvbiBvZiBvdmVyZml0dGluZywgd2hlcmUgdGhlIG1vZGVsIGNvbXBsZXhpdHkgYmVnaW5zIHRvIGNhcHR1cmUgbm9pc2UgcmF0aGVyIHRoYW4gdGhlIHRydWUgdW5kZXJseWluZyByZWxhdGlvbnNoaXBzLg0KDQpGcm9tIHRoZXNlIG9ic2VydmF0aW9ucywgaXQgYmVjb21lcyBjbGVhciB0aGF0IHdoaWxlIGFkZGluZyBtb3JlIHZhcmlhYmxlcyBjYW4gYmUgYmVuZWZpY2lhbCwgdGhlcmUgaXMgYSBudWFuY2VkIGJhbGFuY2UgYmV0d2VlbiBtb2RlbCBjb21wbGV4aXR5IGFuZCBwcmVkaWN0aXZlIHBlcmZvcm1hbmNlLiBGdXR1cmUgbW9kZWwgaW1wcm92ZW1lbnRzIG1pZ2h0IGludm9sdmUgcmVmaW5pbmcgdGhlIHNlbGVjdGlvbiBvZiBsYWcgdmFyaWFibGVzLCByZS1hc3Nlc3NpbmcgdGhlIGluY2x1c2lvbiBvZiBob2xpZGF5IGVmZmVjdHMsIG9yIGV4cGxvcmluZyBub24tbGluZWFyIG1vZGVsaW5nIHRlY2huaXF1ZXMgdGhhdCBtaWdodCBiZXR0ZXIgY2FwdHVyZSB0aGUgY29tcGxleGl0aWVzIG9mIHRyaXAgY291bnQgdmFyaWF0aW9ucy4NCg0KKjMpIE9ic2VydmVkIGFuZCBQcmVkaWN0aW9uKg0KDQpgYGB7ciBlcnJvcl92c19hY3R1YWxfdGltZXNlcmllcywgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0UsIHJlc3VsdHM9J2hpZGUnfQ0Kd2Vla19wcmVkaWN0aW9ucyAlPiUgDQogICAgbXV0YXRlKGludGVydmFsNjAgPSBtYXAoZGF0YSwgcHVsbCwgaW50ZXJ2YWw2MCksDQogICAgICAgICAgIHN0YXJ0X3N0YXRpb25faWQgPSBtYXAoZGF0YSwgcHVsbCwgc3RhcnRfc3RhdGlvbl9pZCkpICU+JQ0KICAgIGRwbHlyOjpzZWxlY3QoaW50ZXJ2YWw2MCwgc3RhcnRfc3RhdGlvbl9pZCwgT2JzZXJ2ZWQsIFByZWRpY3Rpb24sIFJlZ3Jlc3Npb24pICU+JQ0KICAgIHVubmVzdCgpICU+JQ0KICAgIGdhdGhlcihWYXJpYWJsZSwgVmFsdWUsIC1SZWdyZXNzaW9uLCAtaW50ZXJ2YWw2MCwgLXN0YXJ0X3N0YXRpb25faWQpICU+JQ0KICAgIGdyb3VwX2J5KFJlZ3Jlc3Npb24sIFZhcmlhYmxlLCBpbnRlcnZhbDYwKSAlPiUNCiAgICBzdW1tYXJpemUoVmFsdWUgPSBzdW0oVmFsdWUpKSAlPiUNCiAgICBnZ3Bsb3QoYWVzKGludGVydmFsNjAsIFZhbHVlLCBjb2xvdXIgPSBWYXJpYWJsZSkpICsNCiAgZ2VvbV9saW5lKHNpemUgPSAxLjEpICsNCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGMoIk9ic2VydmVkIiA9ICIjZjFjMWIxIiwgIlByZWRpY3Rpb24iID0gImluZGlhbnJlZDMiKSkgKyAjIFNwZWNpZnkgeW91ciBjb2xvcnMgaGVyZQ0KICBmYWNldF93cmFwKH5SZWdyZXNzaW9uLCBuY29sID0gMSkgKw0KICBsYWJzKHRpdGxlID0gIlByZWRpY3RlZC9PYnNlcnZlZCBiaWtlIHNoYXJlIHRpbWUgc2VyaWVzIiwgDQogICAgICAgc3VidGl0bGUgPSAiQ2hpY2FnbzsgQSB0ZXN0IHNldCBvZiAyIHdlZWtzIiwgDQogICAgICAgeCA9ICJIb3VyIiwgDQogICAgICAgeSA9ICJTdGF0aW9uIFRyaXBzIikgKw0KICBwbG90VGhlbWUNCg0KYGBgDQoNClRoZSB0aW1lIHNlcmllcyBwbG90cyBmb3IgZGlmZmVyZW50IHJlZ3Jlc3Npb24gbW9kZWxzIG92ZXIgYSB0d28td2VlayBwZXJpb2QgaWxsdXN0cmF0ZSB2YXJ5aW5nIGRlZ3JlZXMgb2YgcHJlZGljdGl2ZSBhY2N1cmFjeSBpbiBlc3RpbWF0aW5nIGJpa2Ugc2hhcmUgdXNhZ2UuIE1vZGVsIEEsIGZvY3VzaW5nIHNvbGVseSBvbiB0ZW1wb3JhbCBhbmQgdGVtcGVyYXR1cmUgdmFyaWFibGVzLCBkZW1vbnN0cmF0ZXMgbW9kZXJhdGUgcHJlZGljdGl2ZSBjYXBhYmlsaXR5IGJ1dCBzaG93cyBub3RhYmxlIGVycm9ycywgcGFydGljdWxhcmx5IGF0IHBlYWsgdXNhZ2UgdGltZXMuIE1vZGVsIEIsIHdoaWNoIGluY29ycG9yYXRlcyBzcGF0aWFsIGZhY3RvcnMsIGNhcHR1cmVzIHNvbWUgcGF0dGVybnMgbW9yZSBhY2N1cmF0ZWx5IHRoYW4gTW9kZWwgQSwgeWV0IHN0aWxsIGV4aGliaXRzIGRldmlhdGlvbnMgZHVlIHRvIHRoZSBhYnNlbmNlIG9mIHRlbXBvcmFsIGRldGFpbHMuIE1vZGVsIEMsIHdoaWNoIGNvbWJpbmVzIGJvdGggdGVtcG9yYWwgYW5kIHNwYXRpYWwgdmFyaWFibGVzLCBhbG9uZyB3aXRoIHdlYXRoZXIgY29uZGl0aW9ucywgY2xvc2VseSBmb2xsb3dzIHRoZSBvYnNlcnZlZCBkYXRhIHRyZW5kcywgYWx0aG91Z2ggaXQgb2NjYXNpb25hbGx5IG1pc3NlcyBwZWFrIHZhbHVlcy4gTW9kZWwgRCBhZGRzIHRlbXBvcmFsIGxhZ3MgYW5kIHByZXNlbnRzIGEgYmV0dGVyIGZpdCwgY2xvc2VseSBtaXJyb3JpbmcgdGhlIG9ic2VydmVkIHRyaXBzLCBzaWduaWZ5aW5nIHRoZSByZWxldmFuY2Ugb2YgaGlzdG9yaWNhbCBkYXRhIGluIGZvcmVjYXN0aW5nLiBMYXN0bHksIE1vZGVsIEUsIHdoaWNoIGV4dGVuZHMgTW9kZWwgRCB3aXRoIGhvbGlkYXkgbGFncywgb2ZmZXJzIG5vIHNpZ25pZmljYW50IGltcHJvdmVtZW50LCBpbmRpY2F0aW5nIHRoYXQgYWRkaXRpb25hbCBjb21wbGV4aXR5IHRocm91Z2ggaG9saWRheSBsYWdzIG1heSBub3QgY29udHJpYnV0ZSBtZWFuaW5nZnVsbHkgdG8gdGhlIG1vZGVsJ3MgcGVyZm9ybWFuY2UgaW4gdGhpcyBjb250ZXh0LiBPdmVyYWxsLCB3aGlsZSB0aGUgaW5jbHVzaW9uIG9mIHRpbWUgbGFncyBlbmhhbmNlcyBtb2RlbCBhY2N1cmFjeSwgdGhlIGJlbmVmaXQgb2YgaW50ZWdyYXRpbmcgaG9saWRheS1yZWxhdGVkIHZhcmlhYmxlcyBhcHBlYXJzIGxpbWl0ZWQuDQoNCio0KSBFcnJvcnMgYnkgU3RhdGlvbioNCg0KYGBge3IgZXJyb3JzX2J5X3N0YXRpb24sIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFLCByZXN1bHRzPSdoaWRlJyB9DQp3ZWVrX3ByZWRpY3Rpb25zICU+JSANCiAgICBtdXRhdGUoaW50ZXJ2YWw2MCA9IG1hcChkYXRhLCBwdWxsLCBpbnRlcnZhbDYwKSwNCiAgICAgICAgICAgc3RhcnRfc3RhdGlvbl9pZCA9IG1hcChkYXRhLCBwdWxsLCBzdGFydF9zdGF0aW9uX2lkKSwgDQogICAgICAgICAgIGZyb21fbGF0aXR1ZGUgPSBtYXAoZGF0YSwgcHVsbCwgZnJvbV9sYXRpdHVkZSksIA0KICAgICAgICAgICBmcm9tX2xvbmdpdHVkZSA9IG1hcChkYXRhLCBwdWxsLCBmcm9tX2xvbmdpdHVkZSkpICU+JQ0KICAgIHNlbGVjdChpbnRlcnZhbDYwLCBzdGFydF9zdGF0aW9uX2lkLCBmcm9tX2xvbmdpdHVkZSwgZnJvbV9sYXRpdHVkZSwgT2JzZXJ2ZWQsIFByZWRpY3Rpb24sIFJlZ3Jlc3Npb24pICU+JQ0KICAgIHVubmVzdCgpICU+JQ0KICBmaWx0ZXIoUmVncmVzc2lvbiA9PSAiRVRpbWVfU3BhY2VfRkVfdGltZUxhZ3NfaG9saWRheUxhZ3MiKSAlPiUNCiAgZ3JvdXBfYnkoc3RhcnRfc3RhdGlvbl9pZCwgZnJvbV9sb25naXR1ZGUsIGZyb21fbGF0aXR1ZGUpICU+JQ0KICBzdW1tYXJpemUoTUFFID0gbWVhbihhYnMoT2JzZXJ2ZWQtUHJlZGljdGlvbiksIG5hLnJtID0gVFJVRSkpJT4lDQpnZ3Bsb3QoLikrDQogIGdlb21fc2YoZGF0YSA9IE5ZQ0NlbnN1cywgY29sb3IgPSAiZ3JleTgwIiwgZmlsbCA9ICJncmV5NzAiKSsNCiAgZ2VvbV9wb2ludChhZXMoeCA9IGZyb21fbG9uZ2l0dWRlLCB5ID0gZnJvbV9sYXRpdHVkZSwgY29sb3IgPSBNQUUpLCANCiAgICAgICAgICAgICBmaWxsID0gInRyYW5zcGFyZW50Iiwgc2l6ZT0wLjMpKw0KICBzY2FsZV9jb2xvdXJfZ3JhZGllbnQobG93ID0gImNvcm5zaWxrIiwgaGlnaCA9ICJpbmRpYW5yZWQyIikrDQogIHlsaW0obWluKGRhdF9jZW5zdXMkZnJvbV9sYXRpdHVkZSksIG1heChkYXRfY2Vuc3VzJGZyb21fbGF0aXR1ZGUpKSsNCiAgeGxpbShtaW4oZGF0X2NlbnN1cyRmcm9tX2xvbmdpdHVkZSksIG1heChkYXRfY2Vuc3VzJGZyb21fbG9uZ2l0dWRlKSkrDQogIGxhYnModGl0bGU9Ik1lYW4gQWJzIEVycm9yLCBUZXN0IFNldCwgTW9kZWwgNSIpKw0KICBtYXBUaGVtZQ0KYGBgDQoNCkJhc2VkIG9uIHRoZSBvYnNlcnZhdGlvbnMsIGl0IGlzIGV2aWRlbnQgdGhhdCB0aGUgRWFzdCBWaWxsYWdlIGFyZWEgZXhoaWJpdHMgdGhlIGhpZ2hlc3QgcHJlZGljdGlvbiBlcnJvcnMsIGluZGljYXRpbmcgdGhhdCB0aGUgbW9kZWwncyBmb3JlY2FzdGluZyBpcyBsZXNzIGFjY3VyYXRlIGluIHRoaXMgbG9jYWxlLiBGdXJ0aGVybW9yZSwgdGhlIGFyZWFzIGVuY29tcGFzc2luZyBHcmVlbndpY2ggVmlsbGFnZSBhbmQgdGhlIHZpY2luaXRpZXMgYm9yZGVyaW5nIENlbnRyYWwgUGFyayBhbHNvIGRlbW9uc3RyYXRlIHJlbGF0aXZlbHkgaGlnaGVyIE1BRSB2YWx1ZXMuIFRoZXNlIGRpc2NyZXBhbmNpZXMgc3VnZ2VzdCB0aGF0IHRoZSBtb2RlbCBtYXkgbm90IGZ1bGx5IGFjY291bnQgZm9yIHRoZSB1bmlxdWUgZmFjdG9ycyBvciBiZWhhdmlvcnMgaW5mbHVlbmNpbmcgYmlrZSBzaGFyZSB1c2FnZSBwYXR0ZXJucyBpbiB0aGVzZSBzcGVjaWZpYyB1cmJhbiByZWdpb25zLCB3aGljaCBhcmUgcG9zc2libHkgY2hhcmFjdGVyaXplZCBieSB0aGVpciBvd24gZGlzdGluY3QgZGVtb2dyYXBoaWMsIGN1bHR1cmFsLCBvciBpbmZyYXN0cnVjdHVyYWwgYXR0cmlidXRlcy4NCg0KKjUpIFNwYWNlLVRpbWUgRXJyb3IgRXZhbHVhdGlvbioNCg0KDQpgYGB7ciBvYnNfcHJlZF9hbGwsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFLCByZXN1bHRzPSdoaWRlJ30NCndlZWtfcHJlZGljdGlvbnMgJT4lIA0KICAgIG11dGF0ZShpbnRlcnZhbDYwID0gbWFwKGRhdGEsIHB1bGwsIGludGVydmFsNjApLA0KICAgICAgICAgICBzdGFydF9zdGF0aW9uX2lkID0gbWFwKGRhdGEsIHB1bGwsIHN0YXJ0X3N0YXRpb25faWQpLCANCiAgICAgICAgICAgZnJvbV9sYXRpdHVkZSA9IG1hcChkYXRhLCBwdWxsLCBmcm9tX2xhdGl0dWRlKSwgDQogICAgICAgICAgIGZyb21fbG9uZ2l0dWRlID0gbWFwKGRhdGEsIHB1bGwsIGZyb21fbG9uZ2l0dWRlKSwNCiAgICAgICAgICAgZG90dyA9IG1hcChkYXRhLCBwdWxsLCBkb3R3KSkgJT4lDQogICAgc2VsZWN0KGludGVydmFsNjAsIHN0YXJ0X3N0YXRpb25faWQsIGZyb21fbG9uZ2l0dWRlLCANCiAgICAgICAgICAgZnJvbV9sYXRpdHVkZSwgT2JzZXJ2ZWQsIFByZWRpY3Rpb24sIFJlZ3Jlc3Npb24sDQogICAgICAgICAgIGRvdHcpICU+JQ0KICAgIHVubmVzdCgpICU+JQ0KICBmaWx0ZXIoUmVncmVzc2lvbiA9PSAiRVRpbWVfU3BhY2VfRkVfdGltZUxhZ3NfaG9saWRheUxhZ3MiKSU+JQ0KICBtdXRhdGUod2Vla2VuZCA9IGlmZWxzZShkb3R3ICVpbiUgYygiU3VuIiwgIlNhdCIpLCAiV2Vla2VuZCIsICJXZWVrZGF5IiksDQogICAgICAgICB0aW1lX29mX2RheSA9IGNhc2Vfd2hlbihob3VyKGludGVydmFsNjApIDwgNyB8IGhvdXIoaW50ZXJ2YWw2MCkgPiAxOCB+ICJPdmVybmlnaHQiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaG91cihpbnRlcnZhbDYwKSA+PSA3ICYgaG91cihpbnRlcnZhbDYwKSA8IDEwIH4gIkFNIFJ1c2giLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaG91cihpbnRlcnZhbDYwKSA+PSAxMCAmIGhvdXIoaW50ZXJ2YWw2MCkgPCAxNSB+ICJNaWQtRGF5IiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhvdXIoaW50ZXJ2YWw2MCkgPj0gMTUgJiBob3VyKGludGVydmFsNjApIDw9IDE4IH4gIlBNIFJ1c2giKSklPiUNCiAgZ2dwbG90KCkrDQogIGdlb21fcG9pbnQoYWVzKHg9IE9ic2VydmVkLCB5ID0gUHJlZGljdGlvbikpKw0KICAgIGdlb21fc21vb3RoKGFlcyh4PSBPYnNlcnZlZCwgeT0gUHJlZGljdGlvbiksIG1ldGhvZCA9ICJsbSIsIHNlID0gRkFMU0UsIGNvbG9yID0gImluZGlhbnJlZDIiKSsNCiAgICBnZW9tX2FibGluZShzbG9wZSA9IDEsIGludGVyY2VwdCA9IDApKw0KICBmYWNldF9ncmlkKHRpbWVfb2ZfZGF5fndlZWtlbmQpKw0KICBsYWJzKHRpdGxlPSJPYnNlcnZlZCB2cyBQcmVkaWN0ZWQiLA0KICAgICAgIHg9Ik9ic2VydmVkIHRyaXBzIiwgDQogICAgICAgeT0iUHJlZGljdGVkIHRyaXBzIikrDQogIHBsb3RUaGVtZQ0KYGBgDQoNCkR1cmluZyB3ZWVrZGF5cywgdGhlcmUgc2VlbXMgdG8gYmUgYSBwYXR0ZXJuIG9mIG92ZXJwcmVkaWN0aW9uIGR1cmluZyB0aGUgQU0gcnVzaCBhbmQgdW5kZXJwcmVkaWN0aW9uIGR1cmluZyB0aGUgUE0gcnVzaC4gTWlkZGF5IGFuZCBvdmVybmlnaHQgcHJlZGljdGlvbnMgYXBwZWFyIHRvIGJlIG1vcmUgZXZlbmx5IGRpc3RyaWJ1dGVkIGFyb3VuZCB0aGUgZGlhZ29uYWwsIHN1Z2dlc3RpbmcgYmV0dGVyIHByZWRpY3RpdmUgYWNjdXJhY3kgZHVyaW5nIHRoZXNlIHRpbWVzLg0KT24gd2Vla2VuZHMsIHRoZSBzY2F0dGVyIHBsb3RzIHNob3cgYSB3aWRlIGRpc3BlcnNpb24gb2YgcG9pbnRzLCBpbmRpY2F0aW5nIGEgZ3JlYXRlciBkZWdyZWUgb2YgdmFyaWFuY2UgaW4gdGhlIHByZWRpY3RpdmUgYWNjdXJhY3kuIFRoaXMgY291bGQgYmUgZHVlIHRvIG1vcmUgdmFyaWFibGUgdHJpcCBwYXR0ZXJucyBkdXJpbmcgd2Vla2VuZHMgdGhhdCBhcmUgbm90IGFzIHdlbGwtY2FwdHVyZWQgYnkgdGhlIG1vZGVsLg0KDQoqNSkgU3BhY2UtVGltZSBFcnJvciBFdmFsdWF0aW9uIGJ5IFN0YXRpb24qDQoNCmBgYHtyIHN0YXRpb25fc3VtbWFyeSxmaWcud2lkdGg9MTAsIGZpZy5oZWlnaHQ9MTIsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFLCByZXN1bHRzPSdoaWRlJyB9DQp3ZWVrX3ByZWRpY3Rpb25zICU+JSANCiAgICBtdXRhdGUoaW50ZXJ2YWw2MCA9IG1hcChkYXRhLCBwdWxsLCBpbnRlcnZhbDYwKSwNCiAgICAgICAgICAgc3RhcnRfc3RhdGlvbl9pZCA9IG1hcChkYXRhLCBwdWxsLCBzdGFydF9zdGF0aW9uX2lkKSwgDQogICAgICAgICAgIGZyb21fbGF0aXR1ZGUgPSBtYXAoZGF0YSwgcHVsbCwgZnJvbV9sYXRpdHVkZSksIA0KICAgICAgICAgICBmcm9tX2xvbmdpdHVkZSA9IG1hcChkYXRhLCBwdWxsLCBmcm9tX2xvbmdpdHVkZSksDQogICAgICAgICAgIGRvdHcgPSBtYXAoZGF0YSwgcHVsbCwgZG90dykgKSAlPiUNCiAgICBzZWxlY3QoaW50ZXJ2YWw2MCwgc3RhcnRfc3RhdGlvbl9pZCwgZnJvbV9sb25naXR1ZGUsIA0KICAgICAgICAgICBmcm9tX2xhdGl0dWRlLCBPYnNlcnZlZCwgUHJlZGljdGlvbiwgUmVncmVzc2lvbiwNCiAgICAgICAgICAgZG90dykgJT4lDQogICAgdW5uZXN0KCkgJT4lDQogIGZpbHRlcihSZWdyZXNzaW9uID09ICJFVGltZV9TcGFjZV9GRV90aW1lTGFnc19ob2xpZGF5TGFncyIpJT4lDQogIG11dGF0ZSh3ZWVrZW5kID0gaWZlbHNlKGRvdHcgJWluJSBjKCJTdW4iLCAiU2F0IiksICJXZWVrZW5kIiwgIldlZWtkYXkiKSwNCiAgICAgICAgIHRpbWVfb2ZfZGF5ID0gY2FzZV93aGVuKGhvdXIoaW50ZXJ2YWw2MCkgPCA3IHwgaG91cihpbnRlcnZhbDYwKSA+IDE4IH4gIk92ZXJuaWdodCIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBob3VyKGludGVydmFsNjApID49IDcgJiBob3VyKGludGVydmFsNjApIDwgMTAgfiAiQU0gUnVzaCIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBob3VyKGludGVydmFsNjApID49IDEwICYgaG91cihpbnRlcnZhbDYwKSA8IDE1IH4gIk1pZC1EYXkiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaG91cihpbnRlcnZhbDYwKSA+PSAxNSAmIGhvdXIoaW50ZXJ2YWw2MCkgPD0gMTggfiAiUE0gUnVzaCIpKSAlPiUNCiAgZ3JvdXBfYnkoc3RhcnRfc3RhdGlvbl9pZCwgd2Vla2VuZCwgdGltZV9vZl9kYXksIGZyb21fbG9uZ2l0dWRlLCBmcm9tX2xhdGl0dWRlKSAlPiUNCiAgc3VtbWFyaXplKE1BRSA9IG1lYW4oYWJzKE9ic2VydmVkLVByZWRpY3Rpb24pLCBuYS5ybSA9IFRSVUUpKSU+JQ0KICBnZ3Bsb3QoLikgKw0KICBnZW9tX3NmKGRhdGEgPSBOWUNDZW5zdXMsIGNvbG9yID0gImdyZXk3MCIsIGZpbGwgPSAiZ3JleTgwIikgKw0KICBnZW9tX3BvaW50KGFlcyh4ID0gZnJvbV9sb25naXR1ZGUsIHkgPSBmcm9tX2xhdGl0dWRlLCBjb2xvciA9IE1BRSksIA0KICAgICAgICAgICAgIHNpemUgPSAwLjUpICsNCiAgc2NhbGVfY29sb3VyX2dyYWRpZW50KGxvdyA9ICJjb3Juc2lsayIsIGhpZ2ggPSAiaW5kaWFucmVkMiIpICsNCiAgeWxpbShtaW4oZGF0X2NlbnN1cyRmcm9tX2xhdGl0dWRlKSwgbWF4KGRhdF9jZW5zdXMkZnJvbV9sYXRpdHVkZSkpICsNCiAgeGxpbShtaW4oZGF0X2NlbnN1cyRmcm9tX2xvbmdpdHVkZSksIG1heChkYXRfY2Vuc3VzJGZyb21fbG9uZ2l0dWRlKSkgKw0KICBmYWNldF9ncmlkKHdlZWtlbmQgfiB0aW1lX29mX2RheSkgKw0KICBsYWJzKHRpdGxlPSJNZWFuIEFic29sdXRlIEVycm9ycywgVGVzdCBTZXQiKSArDQogIG1hcFRoZW1lDQoNCiAgDQpgYGANCg0KKjUpIFNvY2lvLUVjb25vbWljIEZhY3RvcnMqDQoNCmBgYHtyIHN0YXRpb25fc3VtbWFyeTIsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFLCByZXN1bHRzPSdoaWRlJyB9DQp3ZWVrX3ByZWRpY3Rpb25zICU+JSANCiAgICBtdXRhdGUoaW50ZXJ2YWw2MCA9IG1hcChkYXRhLCBwdWxsLCBpbnRlcnZhbDYwKSwNCiAgICAgICAgICAgc3RhcnRfc3RhdGlvbl9pZCA9IG1hcChkYXRhLCBwdWxsLCBzdGFydF9zdGF0aW9uX2lkKSwgDQogICAgICAgICAgIGZyb21fbGF0aXR1ZGUgPSBtYXAoZGF0YSwgcHVsbCwgZnJvbV9sYXRpdHVkZSksIA0KICAgICAgICAgICBmcm9tX2xvbmdpdHVkZSA9IG1hcChkYXRhLCBwdWxsLCBmcm9tX2xvbmdpdHVkZSksDQogICAgICAgICAgIGRvdHcgPSBtYXAoZGF0YSwgcHVsbCwgZG90dyksDQogICAgICAgICAgIFBlcmNlbnRfVGFraW5nX1B1YmxpY19UcmFucyA9IG1hcChkYXRhLCBwdWxsLCBQZXJjZW50X1Rha2luZ19QdWJsaWNfVHJhbnMpLA0KICAgICAgICAgICBNZWRfSW5jID0gbWFwKGRhdGEsIHB1bGwsIE1lZF9JbmMpLA0KICAgICAgICAgICBQZXJjZW50X1doaXRlID0gbWFwKGRhdGEsIHB1bGwsIFBlcmNlbnRfV2hpdGUpKSAlPiUNCiAgICBzZWxlY3QoaW50ZXJ2YWw2MCwgc3RhcnRfc3RhdGlvbl9pZCwgZnJvbV9sb25naXR1ZGUsIA0KICAgICAgICAgICBmcm9tX2xhdGl0dWRlLCBPYnNlcnZlZCwgUHJlZGljdGlvbiwgUmVncmVzc2lvbiwNCiAgICAgICAgICAgZG90dywgUGVyY2VudF9UYWtpbmdfUHVibGljX1RyYW5zLCBNZWRfSW5jLCBQZXJjZW50X1doaXRlKSAlPiUNCiAgICB1bm5lc3QoKSAlPiUNCiAgZmlsdGVyKFJlZ3Jlc3Npb24gPT0gIkVUaW1lX1NwYWNlX0ZFX3RpbWVMYWdzX2hvbGlkYXlMYWdzIiklPiUNCiAgbXV0YXRlKHdlZWtlbmQgPSBpZmVsc2UoZG90dyAlaW4lIGMoIlN1biIsICJTYXQiKSwgIldlZWtlbmQiLCAiV2Vla2RheSIpLA0KICAgICAgICAgdGltZV9vZl9kYXkgPSBjYXNlX3doZW4oaG91cihpbnRlcnZhbDYwKSA8IDcgfCBob3VyKGludGVydmFsNjApID4gMTggfiAiT3Zlcm5pZ2h0IiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhvdXIoaW50ZXJ2YWw2MCkgPj0gNyAmIGhvdXIoaW50ZXJ2YWw2MCkgPCAxMCB+ICJBTSBSdXNoIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhvdXIoaW50ZXJ2YWw2MCkgPj0gMTAgJiBob3VyKGludGVydmFsNjApIDwgMTUgfiAiTWlkLURheSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBob3VyKGludGVydmFsNjApID49IDE1ICYgaG91cihpbnRlcnZhbDYwKSA8PSAxOCB+ICJQTSBSdXNoIikpICU+JQ0KICBmaWx0ZXIodGltZV9vZl9kYXkgPT0gIkFNIFJ1c2giKSAlPiUNCiAgZ3JvdXBfYnkoc3RhcnRfc3RhdGlvbl9pZCwgUGVyY2VudF9UYWtpbmdfUHVibGljX1RyYW5zLCBNZWRfSW5jLCBQZXJjZW50X1doaXRlKSAlPiUNCiAgc3VtbWFyaXplKE1BRSA9IG1lYW4oYWJzKE9ic2VydmVkLVByZWRpY3Rpb24pLCBuYS5ybSA9IFRSVUUpKSU+JQ0KICBnYXRoZXIoLXN0YXJ0X3N0YXRpb25faWQsIC1NQUUsIGtleSA9ICJ2YXJpYWJsZSIsIHZhbHVlID0gInZhbHVlIiklPiUNCiAgZ2dwbG90KC4pKw0KICAjZ2VvbV9zZihkYXRhID0gTllDQ2Vuc3VzLCBjb2xvciA9ICJncmV5IiwgZmlsbCA9ICJ0cmFuc3BhcmVudCIpKw0KICBnZW9tX3BvaW50KGFlcyh4ID0gdmFsdWUsIHkgPSBNQUUpLCBhbHBoYSA9IDAuNCkrDQogIGdlb21fc21vb3RoKGFlcyh4ID0gdmFsdWUsIHkgPSBNQUUpLCBtZXRob2QgPSAibG0iLCBzZT0gRkFMU0UsIGNvbG9yID0gImluZGlhbnJlZCIpKw0KICBmYWNldF93cmFwKH52YXJpYWJsZSwgc2NhbGVzID0gImZyZWUiKSsNCiAgbGFicyh0aXRsZT0iRXJyb3JzIGFzIGEgZnVuY3Rpb24gb2Ygc29jaW8tZWNvbm9taWMgdmFyaWFibGVzIiwNCiAgICAgICB5PSJNZWFuIEFic29sdXRlIEVycm9yIChUcmlwcykiKSsNCiAgcGxvdFRoZW1lDQogIA0KYGBgDQoNCipNZWRpYW4gSW5jb21lIChNZWRfSW5jKSB2cy4gTUFFOioNClRoZSBzY2F0dGVyIHBsb3QgZG9lcyBub3Qgc2hvdyBhIGNsZWFyIGNvcnJlbGF0aW9uIGJldHdlZW4gbWVkaWFuIGluY29tZSBhbmQgTUFFLiBIb3dldmVyLCB0aGVyZSdzIGEgc2xpZ2h0IGNvbmNlbnRyYXRpb24gb2YgZGF0YSBwb2ludHMgYXQgdGhlIGxvd2VyIGVuZCBvZiB0aGUgbWVkaWFuIGluY29tZSBzY2FsZSB3aXRoIGxvd2VyIE1BRSB2YWx1ZXMsIHdoaWNoIG1pZ2h0IGluZGljYXRlIHRoYXQgdGhlIG1vZGVsIHByZWRpY3RzIHRyaXBzIG1vcmUgYWNjdXJhdGVseSBpbiBhcmVhcyB3aXRoIGxvd2VyIG1lZGlhbiBpbmNvbWVzLg0KDQoqUGVyY2VudGFnZSBUYWtpbmcgUHVibGljIFRyYW5zcG9ydGF0aW9uIHZzLiBNQUU6Kg0KVGhpcyBwbG90IGFwcGVhcnMgdG8gc2hvdyBhIHNsaWdodCB1cHdhcmQgdHJlbmQsIHdoZXJlIGFyZWFzIHdpdGggYSBoaWdoZXIgcGVyY2VudGFnZSBvZiBwZW9wbGUgdXNpbmcgcHVibGljIHRyYW5zcG9ydCB0ZW5kIHRvIGhhdmUgaGlnaGVyIE1BRSB2YWx1ZXMgaW4gdHJpcCBwcmVkaWN0aW9uLiBUaGlzIGNvdWxkIHN1Z2dlc3QgdGhhdCB0aGUgbW9kZWwgc3RydWdnbGVzIHRvIHByZWRpY3QgdHJpcHMgYWNjdXJhdGVseSBpbiBhcmVhcyB3aXRoIGhpZ2hlciBwdWJsaWMgdHJhbnNwb3J0YXRpb24gdXNhZ2UuDQoNCipQZXJjZW50YWdlIFdoaXRlIHZzLiBNQUU6Kg0KVGhlIHNjYXR0ZXIgcGxvdCBmb3IgdGhlIHBlcmNlbnRhZ2Ugb2YgdGhlIHdoaXRlIHBvcHVsYXRpb24gYWdhaW5zdCBNQUUgc2VlbXMgdG8gc2hvdyBubyBzdHJvbmcgY29ycmVsYXRpb24uIFRoZXJlJ3MgYSB3aWRlIGRpc3BlcnNpb24gb2YgTUFFIHZhbHVlcyBhY3Jvc3MgYWxsIHBlcmNlbnRhZ2VzLCBpbmRpY2F0aW5nIHRoYXQgdGhlIHJhY2lhbCBjb21wb3NpdGlvbiwgcmVwcmVzZW50ZWQgYnkgdGhlIHBlcmNlbnRhZ2Ugb2Ygd2hpdGUgcmVzaWRlbnRzLCBkb2VzIG5vdCBoYXZlIGEgY2xlYXIgaW1wYWN0IG9uIHRoZSBwcmVkaWN0aXZlIGVycm9yIG9mIHRoZSBtb2RlbC4NCg0KDQojIyA1LiBJbnRlcnByZXRhdGlvbg0KDQpUaGUgYWxnb3JpdGhtJ3MgZWZmaWNhY3ksIHBhcnRpY3VsYXJseSB0aGUgbW9kZWxzIHV0aWxpemluZyB2YXJpb3VzIHRpbWUgbGFncywgdW5kZXJzY29yZXMgaXRzIHBvdGVudGlhbCBmb3Igb3B0aW1pemluZyBiaWtlIGZsZWV0IHRyYW5zcG9ydGF0aW9uIGZvciByZS1iYWxhbmNpbmcgZWZmb3J0cy4gVGhlIGluY2x1c2lvbiBvZiB0aW1lIGxhZ3MgYWxsb3dzIHRoZSBhbGdvcml0aG0gdG8gYWNjb3VudCBmb3IgcGF0dGVybnMgYW5kIHRyZW5kcyBvdmVyIHRpbWUsIHdoaWNoIGlzIGNydWNpYWwgZm9yIGFudGljaXBhdGluZyBmdXR1cmUgZGVtYW5kIGFuZCBwbGFubmluZyBsb2dpc3RpY3MuDQoNCkZvciBpbnN0YW5jZSwgdGhlIEQgYW5kIEUgbW9kZWxzLCB3aGljaCBpbmNsdWRlIHRpbWUgbGFnIHZhcmlhYmxlcywgaGF2ZSBzaG93biBhIHJlbGF0aXZlbHkgbG93ZXIgTWVhbiBBYnNvbHV0ZSBFcnJvciAoTUFFKSBpbiBwcmVkaWN0aW9ucy4gVGhpcyBzdWdnZXN0cyB0aGV5IGFyZSBtb3JlIGFkZXB0IGF0IGNhcHR1cmluZyB0aGUgdGVtcG9yYWwgZHluYW1pY3Mgb2YgYmlrZSB1c2FnZS4gRm9yIHJlLWJhbGFuY2luZyBvcGVyYXRpb25zLCB0aGlzIG1lYW5zIHRoZSBhbGdvcml0aG0gY2FuIHByZWRpY3Qgbm90IGp1c3QgdGhlIGltbWVkaWF0ZSBkZW1hbmQgZm9yIGJpa2VzIGJ1dCBhbHNvIGhvdyBkZW1hbmQgZXZvbHZlcyB0aHJvdWdob3V0IHRoZSBkYXkgb3Igd2Vlay4gV2l0aCB0aGlzIGluZm9ybWF0aW9uLCBhIGJpa2Utc2hhcmluZyBzZXJ2aWNlIGNhbiBwcm9hY3RpdmVseSBtb3ZlIGJpa2VzIGZyb20gbG93LWRlbWFuZCB0byBoaWdoLWRlbWFuZCBhcmVhcyBpbiBhbnRpY2lwYXRpb24gb2YgdXNhZ2Ugc3Bpa2VzLCByYXRoZXIgdGhhbiByZWFjdGluZyB0byBzaG9ydGFnZXMgb3Igc3VycGx1c2VzIGFzIHRoZXkgaGFwcGVuLg0KDQpUaGUgbW9kZWwncyBjYXBhYmlsaXR5IHRvIHJlZmxlY3QgdGhlIGluZmx1ZW5jZSBvZiB0aW1lIG9uIHVzYWdlIHBhdHRlcm5zIGVuYWJsZXMgYSBtb3JlIG51YW5jZWQgYXBwcm9hY2ggdG8gZmxlZXQgbWFuYWdlbWVudC4gRm9yIGV4YW1wbGUsIGR1cmluZyB3ZWVrZGF5IEFNIHJ1c2ggaG91cnMsIHdoZXJlIG92ZXJwcmVkaWN0aW9uIGlzIGNvbW1vbiwgdGhlIG1vZGVsJ3MgaW5zaWdodHMgY291bGQgcHJldmVudCB0aGUgdW5uZWNlc3NhcnkgYWxsb2NhdGlvbiBvZiB0b28gbWFueSBiaWtlcyB0byBjZXJ0YWluIGFyZWFzLCB0aGVyZWJ5IG9wdGltaXppbmcgdGhlIHVzZSBvZiB0cmFuc3BvcnRhdGlvbiByZXNvdXJjZXMuIENvbnZlcnNlbHksIGR1cmluZyBQTSBydXNoIGhvdXJzLCB3aGVyZSB1bmRlcnByZWRpY3Rpb24gb2NjdXJzLCB0aGUgbW9kZWwgY2FuIHNpZ25hbCB0aGUgbmVlZCB0byBhbGxvY2F0ZSBhZGRpdGlvbmFsIGJpa2VzIGFoZWFkIG9mIHRpbWUgdG8gbWVldCB0aGUgc3VyZ2UgaW4gZGVtYW5kLg0KDQpCeSBlbXBsb3lpbmcgYSBtb2RlbCB3aXRoIHRpbWUgbGFncywgdGhlIHRyYW5zcG9ydGF0aW9uIGZsZWV0IHVzZWQgZm9yIHJlLWJhbGFuY2luZyBjYW4gb3BlcmF0ZSBtb3JlIGVmZmljaWVudGx5LiBJdCBhbGxvd3MgZm9yIHRoZSBzY2hlZHVsaW5nIG9mIGZsZWV0IG1vdmVtZW50cyBkdXJpbmcgb2ZmLXBlYWsgaG91cnMsIHdoaWNoIGNhbiByZWR1Y2Ugb3BlcmF0aW9uYWwgY29zdHMgYW5kIG1pbmltaXplIHRoZSBpbXBhY3Qgb24gdHJhZmZpYy4gSXQgYWxzbyBlbnN1cmVzIHRoYXQgdGhlIHJlLWJhbGFuY2luZyBlZmZvcnRzIGFyZSBsZXNzIGRpc3J1cHRpdmUgdG8gdGhlIHNlcnZpY2UgYW5kIG1vcmUgYWxpZ25lZCB3aXRoIHRoZSBwcmVkaWN0ZWQgdXNlciBkZW1hbmQsIHRodXMgZW5oYW5jaW5nIHRoZSBvdmVyYWxsIHVzZXIgZXhwZXJpZW5jZS4NCg0KSW4gY29uY2x1c2lvbiwgdGhlIHRpbWUgbGFnIG1vZGVscyBkZW1vbnN0cmF0ZSBjb25zaWRlcmFibGUgcG90ZW50aWFsIGZvciBpbXByb3ZpbmcgYmlrZSBmbGVldCByZS1iYWxhbmNpbmcgb3BlcmF0aW9ucy4gVGhlIGFsZ29yaXRobSBjYW4gcHJvdmlkZSBwcmVkaWN0aXZlIGluc2lnaHRzIHRoYXQgYWxsb3cgZm9yIGEgbW9yZSBzdHJhdGVnaWMgZGVwbG95bWVudCBvZiB0aGUgcmUtYmFsYW5jaW5nIGZsZWV0LCBwb3RlbnRpYWxseSBsZWFkaW5nIHRvIGNvc3Qgc2F2aW5ncywgYmV0dGVyIHJlc291cmNlIGFsbG9jYXRpb24sIGFuZCBhbiBvdmVyYWxsIGluY3JlYXNlIGluIHRoZSBlZmZlY3RpdmVuZXNzIG9mIHRoZSBiaWtlLXNoYXJpbmcgc3lzdGVtLg0KDQo=