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=