• 1. Introduction
  • 2. Data Wrangling
  • 3. Exploratory Analysis
  • 4. Modeling
  • 5. Accuracy and Generalizability
    • 5.1. Examine Error Metrics for Accuracy
    • 5.2 K-fold Cross Validation
    • 5.3. Space-Time Error Evaluation
  • 6. Discussion

1. Introduction

Bike share programs have become an integral part of urban transportation systems, offering a convenient, sustainable, and cost-effective alternative to private vehicles and public transit. Philadelphia’s Indego bike share network, for instance, serves as a critical mobility option for residents and visitors alike, enabling them to navigate the city while reducing congestion and emissions. However, a common operational challenge for bike share programs is the need to maintain an optimal balance of bikes across stations to meet variable demand patterns. Due to daily commuting habits and various socio-economic factors, some parts of a city may experience an abundance of bikes, while others struggle with shortages. For example, residential neighborhoods may see a surge in bike numbers in the morning as commuters head to work, only to be left with empty stations by evening. On the other hand, business districts may have too many bikes during the day, requiring redistribution for the evening commute. Without efficient re-balancing, the bike share system’s effectiveness is compromised, leading to reduced customer satisfaction and a drop in usage. Without effective re-balancing strategies, stations may experience shortages or surpluses, leading to user frustration and decreased system efficiency.

To address these challenges, we propose a proactive re-balancing plan for Indego, the bike share system in Philadelphia, that combines predictive analytics with operational logistics. Launched in 2015, Indego has 1,400 bikes distributed across 130 stations, mostly concentrated in Center City, West Philadelphia, and South Philadelphia1. Our approach involves managing a fleet of re-balancing trucks to redistribute bikes across the network based on demand forecasts. Additionally, we plan to implement user-focused incentives, such as offering ride discounts for returning bikes to underutilized stations or peak-demand locations. This dual approach enhances operational efficiency while fostering rider participation in maintaining system equilibrium.

Our model take one month (May) as the study period, and can provide estimated bike share traffic for each station, every hour of a day throughout the month, to enable timely and actionable interventions. While it remains to be seen how the model performs in other months of a year. The choice of time lag features such as the previous hour’s trip counts, weather conditions, amenity features such as the distance to the nearest subway station, and historical demand patterns at each station, aligns with this operational horizon. By leveraging these insights, our re-balancing plan ensures a dynamic, data-driven response to changing demand, maximizing both user satisfaction and system performance.

2. Data Wrangling

For our analysis, we used four datasets to build the variables: 1)Indego station data in May, 2022; 2)weather data collected at Philadelphia International Airport in May, 2022, including percipitation, temperature and wind speed; 3)5-year ACS Census Data from 2018 to 2022; 4)Septa Data, the subway system in Philadelphia

2.1. Setup

library(tidyverse)
library(sf)
library(lubridate)
library(tigris)
library(tidycensus)
library(viridis)
library(riem)
library(gridExtra)
library(knitr)
library(kableExtra)
library(RSocrata)
library(gganimate)
library(caret)
library(RColorBrewer)
library(gifski)

Sys.setlocale("LC_TIME", "English")

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(),
  # 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"))

palette7 <- c("#eff3ff","#bdd7e7","#6baed6","#3182bd","#08519c","darkblue","#123F5A")
palette4 <- c("#D2FBD4","#92BCAB","#527D82","#123F5A")
palette2 <- c("#6baed6","#08519c")

2.2. Import Census Data

Import basic census data in Philadelphia in 2022, including total population, median household income, median age, racial demographics, travel time, and transportation data related to commuting and public transportation usage. Additionally, new calculated columns are created, such as the percentage of the population that is White, the mean commute time, and the percentage of commuters who use public transportation.

PHLCensus <- 
  get_acs(geography = "tract", 
          variables = c("B01003_001", "B19013_001", 
                        "B02001_002", "B08013_001",
                        "B08012_001", "B08301_001", 
                        "B08301_010", "B01002_001"), 
          year = 2022, 
          state = "PA", 
          geometry = TRUE, 
          county=c("Philadelphia"),
          output = "wide") %>%
  rename(Total_Pop =  B01003_001E,
         Med_Inc = B19013_001E,
         Med_Age = B01002_001E,
         White_Pop = B02001_002E,
         Travel_Time = B08013_001E,
         Num_Commuters = B08012_001E,
         Means_of_Transport = B08301_001E,
         Total_Public_Trans = B08301_010E) %>%
  select(Total_Pop, Med_Inc, White_Pop, Travel_Time,
         Means_of_Transport, Total_Public_Trans,
         Med_Age,
         GEOID, geometry) %>%
  mutate(Percent_White = White_Pop / Total_Pop,
         Mean_Commute_Time = Travel_Time / Total_Public_Trans,
         Percent_Taking_Public_Trans = Total_Public_Trans / Means_of_Transport)
PHLTracts <- 
  PHLCensus %>%
  as.data.frame() %>%
  distinct(GEOID, .keep_all = TRUE) %>%
  select(GEOID, geometry) %>% 
  st_sf

2.3. Import Indego Data

Import Philadelphia’s bike-sharing data (Indego), which provides information about bike trips made by users throughout the city. The dataset includes trip start and end times, start and end station locations, user types, and trip durations.

dat<-read.csv("indego-trips-2022-q2.csv")

dat$start_time <- mdy_hm(dat$start_time)
dat$end_time <- mdy_hm(dat$end_time)
# head(dat$start_time)

# only keep records of May
dat_may <- dat %>%
  filter(month(start_time) == 5)
dat2 <- dat_may %>%
  mutate(interval60 = floor_date(ymd_hms(start_time), unit = "hour"),
         interval15 = floor_date(ymd_hms(start_time), unit = "15 mins"),
         week = week(interval60),
         dotw = wday(interval60, label=TRUE))
dat_census <- st_join(
  dat2 %>% 
          filter(is.na(start_lon) == FALSE &
                   is.na(start_lat) == FALSE &
                   is.na(end_lat) == FALSE &
                   is.na(end_lon) == FALSE) %>%
          st_as_sf(., coords = c("start_lon", "start_lat"), crs = 4326),
  PHLTracts %>%
          st_transform(crs=4326),
          join = st_intersects, left = TRUE) %>%
  rename(Origin.Tract = GEOID) %>%
  mutate(start_lon = unlist(map(geometry, 1)),
         start_lat = unlist(map(geometry, 2)))%>%
  as.data.frame() %>%
  select(-geometry)%>%
  st_as_sf(., coords = c("end_lon", "end_lat"), crs = 4326) %>%
  st_join(., PHLTracts %>%
            st_transform(crs=4326),
          join=st_intersects,
          left = TRUE) %>%
  rename(Destination.Tract = GEOID)  %>%
  mutate(end_lon = unlist(map(geometry, 1)),
         end_lat = unlist(map(geometry, 2)))%>%
  as.data.frame() %>%
  select(-geometry)%>%
  na.omit()
dat_sf <- st_as_sf(dat%>%na.omit(), coords = c("start_lon", "start_lat"), crs = 4326)
ggplot() +
  geom_sf(data = PHLTracts) +
  geom_sf(data = dat_sf %>%
            group_by(start_station) %>%
            summarize(count = n()),
          color = "#08519c",
          alpha = 0.4) +
  labs(title = "Distribution of Indego Stations in Philadelphia",
       subtitle = "May 2022")+
  mapTheme

2.4. Import Weather Data

Import weather data in Philadelphia, including perception, temperature and wind speed.

weather.Panel <- 
  riem_measures(station = "PHL", date_start = "2022-05-01", date_end = "2022-05-31") %>%
  dplyr::select(valid, tmpf, p01i, sknt)%>%
  replace(is.na(.), 0) %>%
    mutate(interval60 = ymd_h(substr(valid,1,13))) %>%
    mutate(week = week(interval60),
           dotw = wday(interval60, label=TRUE)) %>%
    group_by(interval60) %>%
    summarize(Temperature = max(tmpf),
              Precipitation = sum(p01i),
              Wind_Speed = max(sknt)) %>%
    mutate(Temperature = ifelse(Temperature == 0, 42, Temperature))
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 - PHL PHL - May, 2022")

2.5 Import Septa Data

Import subway station points in Philadelphia.

el <- st_read("https://opendata.arcgis.com/datasets/8c6e2575c8ad46eb887e6bb35825e1a6_0.geojson")
Broad_St <- st_read("https://opendata.arcgis.com/datasets/2e9037fd5bef406488ffe5bb67d21312_0.geojson")
septaStops <- 
  rbind(
     el %>% 
      mutate(Line = "El") %>%
      dplyr::select(Station, Line),
     Broad_St %>%
      mutate(Line ="Broad_St") %>%
      dplyr::select(Station, Line)) %>%
  st_transform(crs=4326)  

ggplot()+
  geom_sf(data = PHLTracts)+
  geom_sf(data = septaStops,fill = "#08519c", color = "#08519c", alpha = 0.7)+
  labs(title = "Subway Stations in Philadelphia")+
  theme_minimal()

3. Exploratory Analysis

3.1. Bike share trips per hour

Exploring bike share trips per hour, we observed a cyclical pattern in trip numbers throughout the day, with certain hours showing clear peak demand. Additionally, trips on weekends were significantly lower compared to weekdays. However, during the weekend around May 28th (the Memorial Day), there was no noticeable dip in trips, reflecting the impact of holidays on people’s travel behavior.

ggplot(dat_census %>%
         group_by(interval60) %>%
         tally())+
  geom_line(aes(x = interval60, y = n))+
  labs(title="Bike share trips per hr. PHL, May, 2022",
       x="Date", 
       y="Number of trips")+
  plotTheme

Breaking these trends down, we categorized the 24 hours into four time periods: overnight, AM Rush, Mid-Day, and PM Rush, and explored the distribution of the number of trips per hour within these periods. We found that trip counts in all four time slots were mostly concentrated around 1 and 2, with Mid-Day and PM Rush periods tending to have more counts of 2.

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, time_of_day) %>%
         tally()%>%
  group_by(start_station, 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. PHL, May, 2022",
       x="Number of trips", 
       y="Frequency")+
  facet_wrap(~time_of_day)+
  plotTheme

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

Exploring bike share trips by day, we can see that the trip counts in Friday and weekends are clearly lower.

ggplot(dat_census %>% mutate(hour = hour(start_time)))+
     geom_freqpoly(aes(hour, color = dotw), binwidth = 1)+
  labs(title="Bike share trips in PHL, by day of the week, May, 2022",
       x="Hour", 
       y="Trip Counts")+
  scale_color_manual(values = brewer.pal(n = 7, name = "Set2") ) +
     plotTheme

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

These patterns are also evident spatially, with the highest ridership per station concentrated in the downtown and University City areas during the afternoon rush hour.

ggplot()+
  geom_sf(data = PHLTracts %>%
          st_transform(crs=4326))+
  geom_point(data = dat_census %>%
               na.omit()%>%
            mutate(hour = hour(start_time),
                weekend = ifelse(dotw %in% c("Sun", "Sat"), "Weekend", "Weekday"),
                time_of_day = case_when(hour(interval60) < 7 | hour(interval60) > 18 ~ "Overnight",
                                 hour(interval60) >= 7 & hour(interval60) < 10 ~ "AM Rush",
                                 hour(interval60) >= 10 & hour(interval60) < 15 ~ "Mid-Day",
                                 hour(interval60) >= 15 & hour(interval60) <= 18 ~ "PM Rush"))%>%
              group_by(start_station, start_lat, start_lon, weekend, time_of_day) %>%
              tally(),
            aes(x=start_lon, y = start_lat, color = n), 
            fill = "transparent", alpha = 0.5, size = 1)+
  scale_colour_viridis(direction = -1,
  discrete = FALSE, option = "D")+
  ylim(min(dat_census$start_lat), max(dat_census$start_lat))+
  xlim(min(dat_census$start_lon), max(dat_census$start_lon))+
  facet_grid(weekend ~ time_of_day)+
  labs(title="Bike share trips per hr by station. PHL, May, 2022")+
  mapTheme

3.2 Create Space-Time Panel

Here we create a study panel that has each unique time and start station observations.

study.panel <- 
  expand.grid(interval60=unique(dat_census$interval60), 
              start_station = unique(dat_census$start_station)) %>%
  left_join(., dat_census %>%
              select(start_station, Origin.Tract, start_lon, start_lat )%>%
              distinct() %>%
              group_by(start_station) %>%
              slice(1))
ride.panel <- 
  dat_census %>%
  mutate(Trip_Counter = 1) %>%
  right_join(study.panel) %>% 
  group_by(interval60, start_station, Origin.Tract, start_lon, start_lat) %>%
  summarize(Trip_Count = sum(Trip_Counter, na.rm=T)) %>%
  left_join(weather.Panel) %>%
  ungroup() %>%
  filter(is.na(start_station) == FALSE) %>%
  mutate(week = week(interval60),
         dotw = wday(interval60, label = TRUE)) %>%
  filter(is.na(Origin.Tract) == FALSE)

# turn start station id frome numeric to categorical variable
ride.panel$start_station <- factor(ride.panel$start_station)
ride.panel <- 
  left_join(ride.panel, PHLCensus %>%
              as.data.frame() %>%
              select(-geometry), by = c("Origin.Tract" = "GEOID"))

3.3. Create time lags

We created time lags to capture the temporal dependency and better fit the historical impact. Here we created 1, 2, 3, 4 ,12 hours lag, and 1, 2, 3-day holiday lag and lead.

ride.panel <- 
  ride.panel %>% 
  arrange(start_station, interval60) %>% 
  mutate(lagHour = dplyr::lag(Trip_Count,1),
         lag2Hours = dplyr::lag(Trip_Count,2),
         lag3Hours = dplyr::lag(Trip_Count,3),
         lag4Hours = dplyr::lag(Trip_Count,4),
         lag12Hours = dplyr::lag(Trip_Count,12),
         lag1day = dplyr::lag(Trip_Count,24),
         holiday = ifelse(yday(interval60) == 148,1,0)) %>%
   mutate(day = yday(interval60)) %>%
   mutate(holidayLag = case_when(dplyr::lag(holiday, 1) == 1 ~ "PlusOneDay",
                                 dplyr::lag(holiday, 2) == 1 ~ "PlustTwoDays",
                                 dplyr::lag(holiday, 3) == 1 ~ "PlustThreeDays",
                                 dplyr::lead(holiday, 1) == 1 ~ "MinusOneDay",
                                 dplyr::lead(holiday, 2) == 1 ~ "MinusTwoDays",
                                 dplyr::lead(holiday, 3) == 1 ~ "MinusThreeDays"),
         holidayLag = ifelse(is.na(holidayLag) == TRUE, 0, holidayLag))
as.data.frame(ride.panel) %>%
    group_by(interval60) %>% 
    summarise_at(vars(starts_with("lag"), "Trip_Count"), mean, na.rm = TRUE) %>%
    gather(Variable, Value, -interval60, -Trip_Count) %>%
    mutate(Variable = factor(Variable, levels=c("lagHour","lag2Hours","lag3Hours","lag4Hours",
                                                "lag12Hours","lag1day")))%>%
    group_by(Variable) %>%  
    summarize(correlation = round(cor(Value, Trip_Count),2))

3.4 Create Amenity Features

We add an amenity feature, distance to the nearest subway station as one of the variables. Because a large portion of people using shared bikes are heading to the subways.

# Convert ride.panel to an sf object using start_lon and start_lat
ride.panel_sf <- st_as_sf(ride.panel, coords = c("start_lon", "start_lat"), crs = 4326)

# Reproject both dataframes to EPSG:2272
ride.panel_proj <- st_transform(ride.panel_sf, crs = 2272)
septaStops_proj <- st_transform(septaStops, crs = 2272)

# Calculate the nearest distance from each ride.panel point to the closest septaStop
nearest_distances <- st_nearest_feature(ride.panel_proj, septaStops_proj)  # Index of nearest points
nearest_points <- septaStops_proj[nearest_distances, ]  # Get the nearest points
distances <- st_distance(ride.panel_proj, nearest_points, by_element = TRUE)  # Calculate distances

# Add the distances back to ride.panel
ride.panel$nearest_septa_dist <- as.numeric(distances)  # Convert to numeric

# Optional: Add nearest septaStop ID or other attributes
ride.panel$nearest_septaStop_id <- nearest_points$Station  
ride.panel <- na.omit(ride.panel)

Finally, we created an animated plot and note clear spatial and temporal autocorrelation in our data: the animated map indicates that data cluster at certain times of the day in central Philadelphia.

data_5_18 <- ride.panel %>%
  filter(format(interval60, "%Y-%m-%d") == "2022-05-18")  
data_5_18$interval60 <- data_5_18$interval60 + hours(4)


p <- ggplot() +
  geom_sf(data = PHLTracts)+
  geom_point(data = data_5_18, aes(x = start_lon, y = start_lat, color = Trip_Count), alpha = 0.8, size = 2) + 
  scale_color_gradient(low = "lightblue", high = "darkblue") + 
  labs(title = "Bike Share Trips on 2022-05-18", 
       subtitle = "Hour: {format(frame_time, '%Y-%m-%d %H:00:00')}", 
       x = "Longitude", 
       y = "Latitude") +
  theme_minimal() +
  transition_time(interval60) + 
  ease_aes('linear') 

# Export the animation as a GIF
# animated_gif <- animate(p, width = 800, height = 600, fps = 10, renderer = gifski_renderer())
# anim_save("bike_share_trips.gif", animated_gif)
knitr::include_graphics("bike_share_trips.gif")

4. Modeling

4.1. Run Models

We use Week 20, 21, and 22 as train dataset, for it includes the holiday lag in the last 3 weeks. And take the 18th and 19th week as test set.

ride.Train <- filter(ride.panel, week >= 20)
ride.Test <- filter(ride.panel, week < 20)

For our analysis, we used OLS regressions. Although OLS may not be the best method due to the spatial autocorrelation discussed earlier, alternative approaches like spatial lag or geographically weighted regression could enhance performance and generalizability. Nonetheless, for the objectives of this task, OLS proves adequate.

In our analysis, seven regression models are built:

reg1: only time fixed effect

reg2: only space fixed effect

reg3: time - space fixed effect

reg4: reg3 with hour lag

reg5: reg4 with holiday lag

reg6: reg5 with amenity feature

reg7: reg6 with some social indicators (population,median household income and percent of people taking public transprtation)

reg1 <- 
  lm(Trip_Count ~  hour(interval60) + dotw + Temperature,  data=ride.Train)

reg2 <- 
  lm(Trip_Count ~  start_station + dotw + Temperature,  data=ride.Train)

reg3 <- 
  lm(Trip_Count ~  start_station + hour(interval60) + dotw + Temperature + Precipitation, 
     data=ride.Train)

reg4 <- 
  lm(Trip_Count ~  start_station +  hour(interval60) + dotw + Temperature + Precipitation +
                   lagHour + lag2Hours +lag3Hours + lag12Hours + lag1day, 
     data=ride.Train)

reg5 <- 
  lm(Trip_Count ~  start_station + hour(interval60) + dotw + Temperature + Precipitation +
                   lagHour + lag2Hours +lag3Hours +lag12Hours + lag1day + holidayLag + holiday, 
     data=ride.Train)

reg6 <- 
  lm(Trip_Count ~  start_station + hour(interval60) + dotw + Temperature + Precipitation +
                   lagHour + lag2Hours +lag3Hours +lag12Hours + lag1day + holidayLag + holiday + nearest_septa_dist, 
     data=ride.Train)

reg7 <- 
  lm(Trip_Count ~  start_station + hour(interval60) + dotw + Temperature + Precipitation +
                   lagHour + lag2Hours +lag3Hours +lag12Hours + lag1day + holidayLag + holiday + nearest_septa_dist + Total_Pop + Med_Inc + Percent_Taking_Public_Trans, 
     data=ride.Train)

4.2. Predict for test data

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),
           FTime_Space_FE_timeLags_holidayLags_amenity = map(.x = data, fit = reg6, .f = model_pred),
           GTime_Space_FE_timeLags_holidayLags_amenity_society = map(.x = data, fit = reg7, .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))

week_predictions

5. Accuracy and Generalizability

5.1. Examine Error Metrics for Accuracy

Among the seven models we ran, we found that those incorporating both temporal and spatial features, along with various time-lagged variables, performed the best. Interestingly, spatial features appeared to be more influential than temporal ones; including spatial lags in the model led to significant improvements. The addition of holiday lags, amenity features, and social economic variables brought almost no improvement. These features might already be implicitly captured by the pure spatial and temporal characteristics in the dataset. For example, the variable representing income, with its strong spatial autocorrelation, is likely already embedded in the location data; and the distance to the nearest subway station seems to be included in the spatial fixed effect.

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 = palette7) +
    labs(title = "Mean Absolute Errors by model specification and week") +
  plotTheme

week_predictions %>% 
    mutate(interval60 = map(data, pull, interval60),
           start_station = map(data, pull, start_station)) %>%
    dplyr::select(interval60, start_station, Observed, Prediction, Regression) %>%
    unnest() %>%
    gather(Variable, Value, -Regression, -interval60, -start_station) %>%
    group_by(Regression, Variable, interval60) %>%
    summarize(Value = sum(Value)) %>%
    ggplot(aes(interval60, Value, colour=Variable)) + 
      geom_line(size = 1.1) + 
      facet_wrap(~Regression, ncol=1) +
      labs(title = "Predicted/Observed bike share time series", subtitle = "PHL; A test set of 2 weeks",  x = "Hour", y= "Station Trips") +
      plotTheme

5.2 K-fold Cross Validation

Moving forward, we stick with reg6, which seems to have the best goodness of fit generally. We used k-fold cross validation to test our model’s accuracy and generalizability. The RMSE is 1.04 and MAE is 0.67, which indicate a moderate level of performance.

folds <- 5

control <- trainControl(method="cv", number=folds)


set.seed(123)

model_cv <- train(Trip_Count ~ start_station + hour(interval60) + dotw + Temperature + Precipitation +
                   lagHour + lag2Hours +lag3Hours +lag12Hours + lag1day + holidayLag + holiday + nearest_septa_dist, 
                  data=ride.panel,
                  method="lm",
                  trControl=control)

cv_df <- data.frame(
  model = "FTime_Space_FE_timeLags_holidayLags_amenity",
  folds = folds,
  rmse = model_cv$results$RMSE,
  mae = model_cv$results$MAE
)
cv_df %>%
    kbl(caption = "Cross-Validation Results") %>%
    kable_paper(bootstrap_options = "striped", full_width = F)
Cross-Validation Results
model folds rmse mae
FTime_Space_FE_timeLags_holidayLags_amenity 5 1.043422 0.6721477

Exploring the mean absolute errors (MAE) by station, we find that there seems to be a spatial pattern to our model.The high MAE values are concemtrated in the downtown area.

week_predictions %>% 
    mutate(interval60 = map(data, pull, interval60),
           start_station = map(data, pull, start_station), 
           start_lat = map(data, pull, start_lat), 
           start_lon = map(data, pull, start_lon)) %>%
    select(interval60, start_station, start_lon, start_lat, Observed, Prediction, Regression) %>%
    unnest() %>%
  filter(Regression == "FTime_Space_FE_timeLags_holidayLags_amenity") %>%
  group_by(start_station, start_lon, start_lat) %>%
  summarize(MAE = mean(abs(Observed-Prediction), na.rm = TRUE))%>%
ggplot(.)+
  geom_sf(data = PHLCensus, color = "grey", fill = "transparent")+
  geom_point(aes(x = start_lon, y = start_lat, color = MAE), 
             fill = "transparent", alpha = 0.5)+
  scale_colour_viridis(direction = -1,
  discrete = FALSE, option = "D")+
  ylim(min(dat_census$start_lat), max(dat_census$start_lat))+
  xlim(min(dat_census$start_lon), max(dat_census$start_lon))+
  labs(title="Mean Abs Error, Test Set, Model 6")+
  mapTheme

5.3. Space-Time Error Evaluation

Except for spatial error distribution, we should also consider temporal erros. We plot observed and predicted for different times of day during the week and weekend, and some patterns begin to emerge.

  1. Underprediction: In most of the plots, the predicted values are consistently lower than the observed ones, particularly as the number of trips increases. This is shown by data points lying above the 45-degree line (representing perfect prediction). This suggests that the model has more difficulty with higher-demand stations.

  2. Time Effects: The underprediction seems more pronounced during peak times like the AM and PM rush hours compared to midday or overnight. This indicates the model may not fully capture the patterns of travel during these peak periods.

  3. Outliers: Some data points, especially in the higher trip ranges, are far from the main cluster. These outliers highlight cases where the model significantly underperforms in predicting the correct number of trips.

week_predictions %>% 
    mutate(interval60 = map(data, pull, interval60),
           start_station = map(data, pull, start_station), 
           start_lat = map(data, pull, start_lat), 
           start_lon = map(data, pull, start_lon),
           dotw = map(data, pull, dotw)) %>%
    select(interval60, start_station, start_lon, 
           start_lat, Observed, Prediction, Regression,
           dotw) %>%
    unnest() %>%
  filter(Regression == "FTime_Space_FE_timeLags_holidayLags_amenity")%>%
  mutate(weekend = ifelse(dotw %in% c("Sun", "Sat"), "Weekend", "Weekday"),
         time_of_day = case_when(hour(interval60) < 7 | hour(interval60) > 18 ~ "Overnight",
                                 hour(interval60) >= 7 & hour(interval60) < 10 ~ "AM Rush",
                                 hour(interval60) >= 10 & hour(interval60) < 15 ~ "Mid-Day",
                                 hour(interval60) >= 15 & hour(interval60) <= 18 ~ "PM Rush"))%>%
  ggplot()+
  geom_point(aes(x= Observed, y = Prediction))+
    geom_smooth(aes(x= Observed, y= Prediction), method = "lm", se = FALSE, color = "red")+
    geom_abline(slope = 1, intercept = 0)+
  facet_grid(time_of_day~weekend)+
  labs(title="Observed vs Predicted",
       x="Observed trips", 
       y="Predicted trips")+
  plotTheme

We found that the MAE is highest during the PM rush, followed by the AM rush and midday, with the smallest MAE occurring during the overnight period. This reflects the model’s challenges in accurately predicting demand during peak hours. These periods, along with the areas in downtown where the model’s errors are most pronounced, correspond to more developed regions with higher demand. This suggests that the model’s performance, particularly in high-demand, spatially concentrated areas, still requires significant improvement.

week_predictions %>% 
    mutate(interval60 = map(data, pull, interval60),
           start_station = map(data, pull, start_station), 
           start_lat = map(data, pull, start_lat), 
           start_lon = map(data, pull, start_lon),
           dotw = map(data, pull, dotw) ) %>%
    select(interval60, start_station, start_lon, 
           start_lat, Observed, Prediction, Regression,
           dotw) %>%
    unnest() %>%
  filter(Regression == "FTime_Space_FE_timeLags_holidayLags_amenity")%>%
  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, weekend, time_of_day, start_lon, start_lat) %>%
  summarize(MAE = mean(abs(Observed-Prediction), na.rm = TRUE))%>%
  ggplot(.)+
  geom_sf(data = PHLCensus, color = "grey", fill = "transparent")+
  geom_point(aes(x = start_lon, y = start_lat, color = MAE), 
             fill = "transparent", alpha = 0.5)+
  scale_colour_viridis(direction = -1,
  discrete = FALSE, option = "D")+
  ylim(min(dat_census$start_lat), max(dat_census$start_lat))+
  xlim(min(dat_census$start_lon), max(dat_census$start_lon))+
  facet_grid(weekend~time_of_day)+
  labs(title="Mean Absolute Errors, Test Set")+
  mapTheme

Lastly, we find that our model does not generalize perfectly. Our model performs less accurately in wealthier, whiter neighborhoods, and more accurately in areas with higher public transportation reliance.

week_predictions %>% 
    mutate(interval60 = map(data, pull, interval60),
           start_station = map(data, pull, start_station), 
           start_lat = map(data, pull, start_lat), 
           start_lon = map(data, pull, start_lon),
           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, start_lon, 
           start_lat, Observed, Prediction, Regression,
           dotw, Percent_Taking_Public_Trans, Med_Inc, Percent_White) %>%
    unnest() %>%
  filter(Regression == "FTime_Space_FE_timeLags_holidayLags_amenity")%>%
  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, Percent_Taking_Public_Trans, Med_Inc, Percent_White) %>%
  summarize(MAE = mean(abs(Observed-Prediction), na.rm = TRUE))%>%
  gather(-start_station, -MAE, key = "variable", value = "value")%>%
  ggplot(.)+
  #geom_sf(data = PHLCensus, color = "grey", fill = "transparent")+
  geom_point(aes(x = value, y = MAE), alpha = 0.4)+
  geom_smooth(aes(x = value, y = MAE), method = "lm", se= FALSE)+
  facet_wrap(~variable, scales = "free")+
  labs(title="Errors as a function of socio-economic variables",
       y="Mean Absolute Error (Trips)")+
  plotTheme

6. Discussion

In summary, we can see that while we are able to track the time components of demand, we miss the peaks and underpredict during periods of high demand. Our model can effectively predict the demand for bike redistribution on the Indego bikeshare system to some extent, with an MAE of approximately 0.6, indicating that the model’s predicted bike demand at any given station is fairly close to the actual demand (within about 1 bike). However, since the base bike demand per station per hour is relatively small (typically under 3 bikes), the accuracy of predictions still needs improvement. Moreover, Our model demonstrates limited generalizability, as the MAE varies across different socio-economic regions. And it remains to be seen how the model performs in other months of a year.

The main issue we face is the underestimation of demand at high-demand stations, and these are precisely the areas that need attention the most. From an operations perspective, underestimating high demand may lead to inadequate resource allocation, especially during peak periods (such as morning and evening rush hours), directly affecting user experience. For example, during these peak periods, stations may run out of bikes, preventing users from renting or returning bikes, which reduces the satisfaction of meeting the demand. As demand increases, a lack of sufficient bikes can lead to longer wait times for users, or even user loss, lowering the overall usage of the bikeshare system. Furthermore, the operator may fail to effectively redistribute bikes to high-demand areas, thus reducing the efficiency and service quality of the entire system.

To reduce errors, we could incorporate more spatial-temporal features. For example, we can use binary variables to account for peak periods and add social variables such as nearby amenities and residential density. Additionally, using more detailed historical data for training could allow for more complex models. For example, the model could differentiate between holidays that generate varying levels of demand (e.g., Memorial Day versus Christmas) or learn from a broader range of historical data. We can also analyze more specific error plots to identify areas or times where the predictions deviate significantly, helping us optimize resource distribution for specific locations or times. In terms of spatial analysis, methods such as geographically weighted regression (GWR) can be applied to explore the demand patterns in different areas, identifying regions where the demand characteristics differ significantly from others, allowing for more precise predictions.

References:

LS0tDQp0aXRsZTogIkJpa2VzaGFyZSBSZWJhbGFuY2luZyBQcmVkaWN0aW9uIGluIFBoaWxhZGVscGhpYSINCmF1dGhvcjogIkppbmdtaWFvIEZlaSINCmRhdGU6ICIyMDI0LTExLTIyIg0Kb3V0cHV0Og0KICBodG1sX2RvY3VtZW50Og0KICAgIHRvYzogdHJ1ZQ0KICAgIHRvY19mbG9hdDogdHJ1ZQ0KICAgIGNvZGVfZm9sZGluZzogaGlkZQ0KICAgIGNvZGVfZG93bmxvYWQ6IHRydWUNCi0tLQ0KDQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0NCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSkNCmBgYA0KDQojIDEuIEludHJvZHVjdGlvbg0KDQpCaWtlIHNoYXJlIHByb2dyYW1zIGhhdmUgYmVjb21lIGFuIGludGVncmFsIHBhcnQgb2YgdXJiYW4gdHJhbnNwb3J0YXRpb24gc3lzdGVtcywgb2ZmZXJpbmcgYSBjb252ZW5pZW50LCBzdXN0YWluYWJsZSwgYW5kIGNvc3QtZWZmZWN0aXZlIGFsdGVybmF0aXZlIHRvIHByaXZhdGUgdmVoaWNsZXMgYW5kIHB1YmxpYyB0cmFuc2l0LiBQaGlsYWRlbHBoaWHigJlzIEluZGVnbyBiaWtlIHNoYXJlIG5ldHdvcmssIGZvciBpbnN0YW5jZSwgc2VydmVzIGFzIGEgY3JpdGljYWwgbW9iaWxpdHkgb3B0aW9uIGZvciByZXNpZGVudHMgYW5kIHZpc2l0b3JzIGFsaWtlLCBlbmFibGluZyB0aGVtIHRvIG5hdmlnYXRlIHRoZSBjaXR5IHdoaWxlIHJlZHVjaW5nIGNvbmdlc3Rpb24gYW5kIGVtaXNzaW9ucy4gSG93ZXZlciwgYSBjb21tb24gb3BlcmF0aW9uYWwgY2hhbGxlbmdlIGZvciBiaWtlIHNoYXJlIHByb2dyYW1zIGlzIHRoZSBuZWVkIHRvIG1haW50YWluIGFuIG9wdGltYWwgYmFsYW5jZSBvZiBiaWtlcyBhY3Jvc3Mgc3RhdGlvbnMgdG8gbWVldCB2YXJpYWJsZSBkZW1hbmQgcGF0dGVybnMuIER1ZSB0byBkYWlseSBjb21tdXRpbmcgaGFiaXRzIGFuZCB2YXJpb3VzIHNvY2lvLWVjb25vbWljIGZhY3RvcnMsIHNvbWUgcGFydHMgb2YgYSBjaXR5IG1heSBleHBlcmllbmNlIGFuIGFidW5kYW5jZSBvZiBiaWtlcywgd2hpbGUgb3RoZXJzIHN0cnVnZ2xlIHdpdGggc2hvcnRhZ2VzLiBGb3IgZXhhbXBsZSwgcmVzaWRlbnRpYWwgbmVpZ2hib3Job29kcyBtYXkgc2VlIGEgc3VyZ2UgaW4gYmlrZSBudW1iZXJzIGluIHRoZSBtb3JuaW5nIGFzIGNvbW11dGVycyBoZWFkIHRvIHdvcmssIG9ubHkgdG8gYmUgbGVmdCB3aXRoIGVtcHR5IHN0YXRpb25zIGJ5IGV2ZW5pbmcuIE9uIHRoZSBvdGhlciBoYW5kLCBidXNpbmVzcyBkaXN0cmljdHMgbWF5IGhhdmUgdG9vIG1hbnkgYmlrZXMgZHVyaW5nIHRoZSBkYXksIHJlcXVpcmluZyByZWRpc3RyaWJ1dGlvbiBmb3IgdGhlIGV2ZW5pbmcgY29tbXV0ZS4gV2l0aG91dCBlZmZpY2llbnQgcmUtYmFsYW5jaW5nLCB0aGUgYmlrZSBzaGFyZSBzeXN0ZW0ncyBlZmZlY3RpdmVuZXNzIGlzIGNvbXByb21pc2VkLCBsZWFkaW5nIHRvIHJlZHVjZWQgY3VzdG9tZXIgc2F0aXNmYWN0aW9uIGFuZCBhIGRyb3AgaW4gdXNhZ2UuIFdpdGhvdXQgZWZmZWN0aXZlIHJlLWJhbGFuY2luZyBzdHJhdGVnaWVzLCBzdGF0aW9ucyBtYXkgZXhwZXJpZW5jZSBzaG9ydGFnZXMgb3Igc3VycGx1c2VzLCBsZWFkaW5nIHRvIHVzZXIgZnJ1c3RyYXRpb24gYW5kIGRlY3JlYXNlZCBzeXN0ZW0gZWZmaWNpZW5jeS4gDQoNClRvIGFkZHJlc3MgdGhlc2UgY2hhbGxlbmdlcywgd2UgcHJvcG9zZSBhIHByb2FjdGl2ZSByZS1iYWxhbmNpbmcgcGxhbiBmb3IgSW5kZWdvLCB0aGUgYmlrZSBzaGFyZSBzeXN0ZW0gaW4gUGhpbGFkZWxwaGlhLCB0aGF0IGNvbWJpbmVzIHByZWRpY3RpdmUgYW5hbHl0aWNzIHdpdGggb3BlcmF0aW9uYWwgbG9naXN0aWNzLiBMYXVuY2hlZCBpbiAyMDE1LCBJbmRlZ28gaGFzIDEsNDAwIGJpa2VzIGRpc3RyaWJ1dGVkIGFjcm9zcyAxMzAgc3RhdGlvbnMsIG1vc3RseSBjb25jZW50cmF0ZWQgaW4gQ2VudGVyIENpdHksIFdlc3QgUGhpbGFkZWxwaGlhLCBhbmQgU291dGggUGhpbGFkZWxwaGlhW14xXS4gT3VyIGFwcHJvYWNoIGludm9sdmVzIG1hbmFnaW5nIGEgZmxlZXQgb2YgcmUtYmFsYW5jaW5nIHRydWNrcyB0byByZWRpc3RyaWJ1dGUgYmlrZXMgYWNyb3NzIHRoZSBuZXR3b3JrIGJhc2VkIG9uIGRlbWFuZCBmb3JlY2FzdHMuIEFkZGl0aW9uYWxseSwgd2UgcGxhbiB0byBpbXBsZW1lbnQgdXNlci1mb2N1c2VkIGluY2VudGl2ZXMsIHN1Y2ggYXMgb2ZmZXJpbmcgcmlkZSBkaXNjb3VudHMgZm9yIHJldHVybmluZyBiaWtlcyB0byB1bmRlcnV0aWxpemVkIHN0YXRpb25zIG9yIHBlYWstZGVtYW5kIGxvY2F0aW9ucy4gVGhpcyBkdWFsIGFwcHJvYWNoIGVuaGFuY2VzIG9wZXJhdGlvbmFsIGVmZmljaWVuY3kgd2hpbGUgZm9zdGVyaW5nIHJpZGVyIHBhcnRpY2lwYXRpb24gaW4gbWFpbnRhaW5pbmcgc3lzdGVtIGVxdWlsaWJyaXVtLg0KDQpPdXIgbW9kZWwgdGFrZSBvbmUgbW9udGggKE1heSkgYXMgdGhlIHN0dWR5IHBlcmlvZCwgYW5kIGNhbiBwcm92aWRlIGVzdGltYXRlZCBiaWtlIHNoYXJlIHRyYWZmaWMgZm9yIGVhY2ggc3RhdGlvbiwgZXZlcnkgaG91ciBvZiBhIGRheSB0aHJvdWdob3V0IHRoZSBtb250aCwgdG8gZW5hYmxlIHRpbWVseSBhbmQgYWN0aW9uYWJsZSBpbnRlcnZlbnRpb25zLiBXaGlsZSBpdCByZW1haW5zIHRvIGJlIHNlZW4gaG93IHRoZSBtb2RlbCBwZXJmb3JtcyBpbiBvdGhlciBtb250aHMgb2YgYSB5ZWFyLiBUaGUgY2hvaWNlIG9mIHRpbWUgbGFnIGZlYXR1cmVzIHN1Y2ggYXMgdGhlIHByZXZpb3VzIGhvdXLigJlzIHRyaXAgY291bnRzLCB3ZWF0aGVyIGNvbmRpdGlvbnMsIGFtZW5pdHkgZmVhdHVyZXMgc3VjaCBhcyB0aGUgZGlzdGFuY2UgdG8gdGhlIG5lYXJlc3Qgc3Vid2F5IHN0YXRpb24sIGFuZCBoaXN0b3JpY2FsIGRlbWFuZCBwYXR0ZXJucyBhdCBlYWNoIHN0YXRpb24sIGFsaWducyB3aXRoIHRoaXMgb3BlcmF0aW9uYWwgaG9yaXpvbi4gQnkgbGV2ZXJhZ2luZyB0aGVzZSBpbnNpZ2h0cywgb3VyIHJlLWJhbGFuY2luZyBwbGFuIGVuc3VyZXMgYSBkeW5hbWljLCBkYXRhLWRyaXZlbiByZXNwb25zZSB0byBjaGFuZ2luZyBkZW1hbmQsIG1heGltaXppbmcgYm90aCB1c2VyIHNhdGlzZmFjdGlvbiBhbmQgc3lzdGVtIHBlcmZvcm1hbmNlLg0KDQojIDIuIERhdGEgV3JhbmdsaW5nDQoNCkZvciBvdXIgYW5hbHlzaXMsIHdlIHVzZWQgZm91ciBkYXRhc2V0cyB0byBidWlsZCB0aGUgdmFyaWFibGVzOiAxKUluZGVnbyBzdGF0aW9uIGRhdGEgaW4gTWF5LCAyMDIyOyAyKXdlYXRoZXIgZGF0YSBjb2xsZWN0ZWQgYXQgUGhpbGFkZWxwaGlhIEludGVybmF0aW9uYWwgQWlycG9ydCBpbiBNYXksIDIwMjIsIGluY2x1ZGluZyBwZXJjaXBpdGF0aW9uLCB0ZW1wZXJhdHVyZSBhbmQgd2luZCBzcGVlZDsgMyk1LXllYXIgQUNTIENlbnN1cyBEYXRhIGZyb20gMjAxOCB0byAyMDIyOyA0KVNlcHRhIERhdGEsIHRoZSBzdWJ3YXkgc3lzdGVtIGluIFBoaWxhZGVscGhpYQ0KDQojIyAyLjEuIFNldHVwDQoNCmBgYHtyIHNldHVwXzEzLCAgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgcmVzdWx0cz0naGlkZSd9DQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkoc2YpDQpsaWJyYXJ5KGx1YnJpZGF0ZSkNCmxpYnJhcnkodGlncmlzKQ0KbGlicmFyeSh0aWR5Y2Vuc3VzKQ0KbGlicmFyeSh2aXJpZGlzKQ0KbGlicmFyeShyaWVtKQ0KbGlicmFyeShncmlkRXh0cmEpDQpsaWJyYXJ5KGtuaXRyKQ0KbGlicmFyeShrYWJsZUV4dHJhKQ0KbGlicmFyeShSU29jcmF0YSkNCmxpYnJhcnkoZ2dhbmltYXRlKQ0KbGlicmFyeShjYXJldCkNCmxpYnJhcnkoUkNvbG9yQnJld2VyKQ0KbGlicmFyeShnaWZza2kpDQoNClN5cy5zZXRsb2NhbGUoIkxDX1RJTUUiLCAiRW5nbGlzaCIpDQoNCnBsb3RUaGVtZSA8LSB0aGVtZSgNCiAgcGxvdC50aXRsZSA9ZWxlbWVudF90ZXh0KHNpemU9MTIpLA0KICBwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemU9OCksDQogIHBsb3QuY2FwdGlvbiA9IGVsZW1lbnRfdGV4dChzaXplID0gNiksDQogIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCwgYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSwNCiAgYXhpcy50ZXh0LnkgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSwNCiAgYXhpcy50aXRsZS55ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCksDQogICMgU2V0IHRoZSBlbnRpcmUgY2hhcnQgcmVnaW9uIHRvIGJsYW5rDQogIHBhbmVsLmJhY2tncm91bmQ9ZWxlbWVudF9ibGFuaygpLA0KICBwbG90LmJhY2tncm91bmQ9ZWxlbWVudF9ibGFuaygpLA0KICAjIEZvcm1hdCB0aGUgZ3JpZA0KICBwYW5lbC5ncmlkLm1ham9yPWVsZW1lbnRfbGluZShjb2xvdXI9IiNEMEQwRDAiLHNpemU9LjIpLA0KICBheGlzLnRpY2tzPWVsZW1lbnRfYmxhbmsoKSkNCg0KbWFwVGhlbWUgPC0gdGhlbWUocGxvdC50aXRsZSA9ZWxlbWVudF90ZXh0KHNpemU9MTIpLA0KICAgICAgICAgICAgICAgICAgcGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplPTgpLA0KICAgICAgICAgICAgICAgICAgcGxvdC5jYXB0aW9uID0gZWxlbWVudF90ZXh0KHNpemUgPSA2KSwNCiAgICAgICAgICAgICAgICAgIGF4aXMubGluZT1lbGVtZW50X2JsYW5rKCksDQogICAgICAgICAgICAgICAgICBheGlzLnRleHQueD1lbGVtZW50X2JsYW5rKCksDQogICAgICAgICAgICAgICAgICBheGlzLnRleHQueT1lbGVtZW50X2JsYW5rKCksDQogICAgICAgICAgICAgICAgICBheGlzLnRpY2tzPWVsZW1lbnRfYmxhbmsoKSwNCiAgICAgICAgICAgICAgICAgIGF4aXMudGl0bGUueD1lbGVtZW50X2JsYW5rKCksDQogICAgICAgICAgICAgICAgICBheGlzLnRpdGxlLnk9ZWxlbWVudF9ibGFuaygpLA0KICAgICAgICAgICAgICAgICAgcGFuZWwuYmFja2dyb3VuZD1lbGVtZW50X2JsYW5rKCksDQogICAgICAgICAgICAgICAgICBwYW5lbC5ib3JkZXI9ZWxlbWVudF9ibGFuaygpLA0KICAgICAgICAgICAgICAgICAgcGFuZWwuZ3JpZC5tYWpvcj1lbGVtZW50X2xpbmUoY29sb3VyID0gJ3RyYW5zcGFyZW50JyksDQogICAgICAgICAgICAgICAgICBwYW5lbC5ncmlkLm1pbm9yPWVsZW1lbnRfYmxhbmsoKSwNCiAgICAgICAgICAgICAgICAgIGxlZ2VuZC5kaXJlY3Rpb24gPSAidmVydGljYWwiLCANCiAgICAgICAgICAgICAgICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJyaWdodCIsDQogICAgICAgICAgICAgICAgICBwbG90Lm1hcmdpbiA9IG1hcmdpbigxLCAxLCAxLCAxLCAnY20nKSwNCiAgICAgICAgICAgICAgICAgIGxlZ2VuZC5rZXkuaGVpZ2h0ID0gdW5pdCgxLCAiY20iKSwgbGVnZW5kLmtleS53aWR0aCA9IHVuaXQoMC4yLCAiY20iKSkNCg0KcGFsZXR0ZTcgPC0gYygiI2VmZjNmZiIsIiNiZGQ3ZTciLCIjNmJhZWQ2IiwiIzMxODJiZCIsIiMwODUxOWMiLCJkYXJrYmx1ZSIsIiMxMjNGNUEiKQ0KcGFsZXR0ZTQgPC0gYygiI0QyRkJENCIsIiM5MkJDQUIiLCIjNTI3RDgyIiwiIzEyM0Y1QSIpDQpwYWxldHRlMiA8LSBjKCIjNmJhZWQ2IiwiIzA4NTE5YyIpDQpgYGANCg0KYGBge3IgaW5zdGFsbF9jZW5zdXNfQVBJX2tleSwgd2FybmluZyA9IEZBTFNFLCBpbmNsdWRlPUZBTFNFLCBldmFsID0gVFJVRX0NCiMgSW5zdGFsbCBDZW5zdXMgQVBJIEtleQ0KdGlkeWNlbnN1czo6Y2Vuc3VzX2FwaV9rZXkoImU2ZWQzN2UxNDViOTAxYzhjNDE4YTE5M2Q1YmMxNzQ5NDgwNDU0ODgiLCBvdmVyd3JpdGUgPSBUUlVFKQ0KYGBgDQoNCg0KIyMgMi4yLiBJbXBvcnQgQ2Vuc3VzIERhdGENCg0KSW1wb3J0IGJhc2ljIGNlbnN1cyBkYXRhIGluIFBoaWxhZGVscGhpYSBpbiAyMDIyLCBpbmNsdWRpbmcgdG90YWwgcG9wdWxhdGlvbiwgbWVkaWFuIGhvdXNlaG9sZCBpbmNvbWUsIG1lZGlhbiBhZ2UsIHJhY2lhbCBkZW1vZ3JhcGhpY3MsIHRyYXZlbCB0aW1lLCBhbmQgdHJhbnNwb3J0YXRpb24gZGF0YSByZWxhdGVkIHRvIGNvbW11dGluZyBhbmQgcHVibGljIHRyYW5zcG9ydGF0aW9uIHVzYWdlLiBBZGRpdGlvbmFsbHksIG5ldyBjYWxjdWxhdGVkIGNvbHVtbnMgYXJlIGNyZWF0ZWQsIHN1Y2ggYXMgdGhlIHBlcmNlbnRhZ2Ugb2YgdGhlIHBvcHVsYXRpb24gdGhhdCBpcyBXaGl0ZSwgdGhlIG1lYW4gY29tbXV0ZSB0aW1lLCBhbmQgdGhlIHBlcmNlbnRhZ2Ugb2YgY29tbXV0ZXJzIHdobyB1c2UgcHVibGljIHRyYW5zcG9ydGF0aW9uLg0KDQpgYGB7ciBnZXRfY2Vuc3VzLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBjYWNoZT1UUlVFLCByZXN1bHRzID0gJ2hpZGUnfQ0KUEhMQ2Vuc3VzIDwtIA0KICBnZXRfYWNzKGdlb2dyYXBoeSA9ICJ0cmFjdCIsIA0KICAgICAgICAgIHZhcmlhYmxlcyA9IGMoIkIwMTAwM18wMDEiLCAiQjE5MDEzXzAwMSIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgIkIwMjAwMV8wMDIiLCAiQjA4MDEzXzAwMSIsDQogICAgICAgICAgICAgICAgICAgICAgICAiQjA4MDEyXzAwMSIsICJCMDgzMDFfMDAxIiwgDQogICAgICAgICAgICAgICAgICAgICAgICAiQjA4MzAxXzAxMCIsICJCMDEwMDJfMDAxIiksIA0KICAgICAgICAgIHllYXIgPSAyMDIyLCANCiAgICAgICAgICBzdGF0ZSA9ICJQQSIsIA0KICAgICAgICAgIGdlb21ldHJ5ID0gVFJVRSwgDQogICAgICAgICAgY291bnR5PWMoIlBoaWxhZGVscGhpYSIpLA0KICAgICAgICAgIG91dHB1dCA9ICJ3aWRlIikgJT4lDQogIHJlbmFtZShUb3RhbF9Qb3AgPSAgQjAxMDAzXzAwMUUsDQogICAgICAgICBNZWRfSW5jID0gQjE5MDEzXzAwMUUsDQogICAgICAgICBNZWRfQWdlID0gQjAxMDAyXzAwMUUsDQogICAgICAgICBXaGl0ZV9Qb3AgPSBCMDIwMDFfMDAyRSwNCiAgICAgICAgIFRyYXZlbF9UaW1lID0gQjA4MDEzXzAwMUUsDQogICAgICAgICBOdW1fQ29tbXV0ZXJzID0gQjA4MDEyXzAwMUUsDQogICAgICAgICBNZWFuc19vZl9UcmFuc3BvcnQgPSBCMDgzMDFfMDAxRSwNCiAgICAgICAgIFRvdGFsX1B1YmxpY19UcmFucyA9IEIwODMwMV8wMTBFKSAlPiUNCiAgc2VsZWN0KFRvdGFsX1BvcCwgTWVkX0luYywgV2hpdGVfUG9wLCBUcmF2ZWxfVGltZSwNCiAgICAgICAgIE1lYW5zX29mX1RyYW5zcG9ydCwgVG90YWxfUHVibGljX1RyYW5zLA0KICAgICAgICAgTWVkX0FnZSwNCiAgICAgICAgIEdFT0lELCBnZW9tZXRyeSkgJT4lDQogIG11dGF0ZShQZXJjZW50X1doaXRlID0gV2hpdGVfUG9wIC8gVG90YWxfUG9wLA0KICAgICAgICAgTWVhbl9Db21tdXRlX1RpbWUgPSBUcmF2ZWxfVGltZSAvIFRvdGFsX1B1YmxpY19UcmFucywNCiAgICAgICAgIFBlcmNlbnRfVGFraW5nX1B1YmxpY19UcmFucyA9IFRvdGFsX1B1YmxpY19UcmFucyAvIE1lYW5zX29mX1RyYW5zcG9ydCkNCmBgYA0KDQpgYGB7ciBleHRyYWN0X2dlb21ldHJpZXMgfQ0KUEhMVHJhY3RzIDwtIA0KICBQSExDZW5zdXMgJT4lDQogIGFzLmRhdGEuZnJhbWUoKSAlPiUNCiAgZGlzdGluY3QoR0VPSUQsIC5rZWVwX2FsbCA9IFRSVUUpICU+JQ0KICBzZWxlY3QoR0VPSUQsIGdlb21ldHJ5KSAlPiUgDQogIHN0X3NmDQoNCmBgYA0KDQoNCiMjIDIuMy4gSW1wb3J0IEluZGVnbyBEYXRhDQoNCkltcG9ydCBQaGlsYWRlbHBoaWEncyBiaWtlLXNoYXJpbmcgZGF0YSAoSW5kZWdvKSwgd2hpY2ggcHJvdmlkZXMgaW5mb3JtYXRpb24gYWJvdXQgYmlrZSB0cmlwcyBtYWRlIGJ5IHVzZXJzIHRocm91Z2hvdXQgdGhlIGNpdHkuIFRoZSBkYXRhc2V0IGluY2x1ZGVzIHRyaXAgc3RhcnQgYW5kIGVuZCB0aW1lcywgc3RhcnQgYW5kIGVuZCBzdGF0aW9uIGxvY2F0aW9ucywgdXNlciB0eXBlcywgYW5kIHRyaXAgZHVyYXRpb25zLiANCg0KYGBge3IgcmVhZF9kYXQsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIHJlc3VsdHM9J2hpZGUnIH0NCmRhdDwtcmVhZC5jc3YoImluZGVnby10cmlwcy0yMDIyLXEyLmNzdiIpDQoNCmRhdCRzdGFydF90aW1lIDwtIG1keV9obShkYXQkc3RhcnRfdGltZSkNCmRhdCRlbmRfdGltZSA8LSBtZHlfaG0oZGF0JGVuZF90aW1lKQ0KIyBoZWFkKGRhdCRzdGFydF90aW1lKQ0KDQojIG9ubHkga2VlcCByZWNvcmRzIG9mIE1heQ0KZGF0X21heSA8LSBkYXQgJT4lDQogIGZpbHRlcihtb250aChzdGFydF90aW1lKSA9PSA1KQ0KDQpgYGANCmBgYHtyIHRpbWVfYmlucywgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgcmVzdWx0cz0naGlkZScgfQ0KZGF0MiA8LSBkYXRfbWF5ICU+JQ0KICBtdXRhdGUoaW50ZXJ2YWw2MCA9IGZsb29yX2RhdGUoeW1kX2htcyhzdGFydF90aW1lKSwgdW5pdCA9ICJob3VyIiksDQogICAgICAgICBpbnRlcnZhbDE1ID0gZmxvb3JfZGF0ZSh5bWRfaG1zKHN0YXJ0X3RpbWUpLCB1bml0ID0gIjE1IG1pbnMiKSwNCiAgICAgICAgIHdlZWsgPSB3ZWVrKGludGVydmFsNjApLA0KICAgICAgICAgZG90dyA9IHdkYXkoaW50ZXJ2YWw2MCwgbGFiZWw9VFJVRSkpDQpgYGANCg0KDQpgYGB7ciBhZGRfY2Vuc3VzX3RyYWN0cyAsIG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFfQ0KZGF0X2NlbnN1cyA8LSBzdF9qb2luKA0KICBkYXQyICU+JSANCiAgICAgICAgICBmaWx0ZXIoaXMubmEoc3RhcnRfbG9uKSA9PSBGQUxTRSAmDQogICAgICAgICAgICAgICAgICAgaXMubmEoc3RhcnRfbGF0KSA9PSBGQUxTRSAmDQogICAgICAgICAgICAgICAgICAgaXMubmEoZW5kX2xhdCkgPT0gRkFMU0UgJg0KICAgICAgICAgICAgICAgICAgIGlzLm5hKGVuZF9sb24pID09IEZBTFNFKSAlPiUNCiAgICAgICAgICBzdF9hc19zZiguLCBjb29yZHMgPSBjKCJzdGFydF9sb24iLCAic3RhcnRfbGF0IiksIGNycyA9IDQzMjYpLA0KICBQSExUcmFjdHMgJT4lDQogICAgICAgICAgc3RfdHJhbnNmb3JtKGNycz00MzI2KSwNCiAgICAgICAgICBqb2luID0gc3RfaW50ZXJzZWN0cywgbGVmdCA9IFRSVUUpICU+JQ0KICByZW5hbWUoT3JpZ2luLlRyYWN0ID0gR0VPSUQpICU+JQ0KICBtdXRhdGUoc3RhcnRfbG9uID0gdW5saXN0KG1hcChnZW9tZXRyeSwgMSkpLA0KICAgICAgICAgc3RhcnRfbGF0ID0gdW5saXN0KG1hcChnZW9tZXRyeSwgMikpKSU+JQ0KICBhcy5kYXRhLmZyYW1lKCkgJT4lDQogIHNlbGVjdCgtZ2VvbWV0cnkpJT4lDQogIHN0X2FzX3NmKC4sIGNvb3JkcyA9IGMoImVuZF9sb24iLCAiZW5kX2xhdCIpLCBjcnMgPSA0MzI2KSAlPiUNCiAgc3Rfam9pbiguLCBQSExUcmFjdHMgJT4lDQogICAgICAgICAgICBzdF90cmFuc2Zvcm0oY3JzPTQzMjYpLA0KICAgICAgICAgIGpvaW49c3RfaW50ZXJzZWN0cywNCiAgICAgICAgICBsZWZ0ID0gVFJVRSkgJT4lDQogIHJlbmFtZShEZXN0aW5hdGlvbi5UcmFjdCA9IEdFT0lEKSAgJT4lDQogIG11dGF0ZShlbmRfbG9uID0gdW5saXN0KG1hcChnZW9tZXRyeSwgMSkpLA0KICAgICAgICAgZW5kX2xhdCA9IHVubGlzdChtYXAoZ2VvbWV0cnksIDIpKSklPiUNCiAgYXMuZGF0YS5mcmFtZSgpICU+JQ0KICBzZWxlY3QoLWdlb21ldHJ5KSU+JQ0KICBuYS5vbWl0KCkNCmBgYA0KDQoNCmBgYHtyfQ0KZGF0X3NmIDwtIHN0X2FzX3NmKGRhdCU+JW5hLm9taXQoKSwgY29vcmRzID0gYygic3RhcnRfbG9uIiwgInN0YXJ0X2xhdCIpLCBjcnMgPSA0MzI2KQ0KZ2dwbG90KCkgKw0KICBnZW9tX3NmKGRhdGEgPSBQSExUcmFjdHMpICsNCiAgZ2VvbV9zZihkYXRhID0gZGF0X3NmICU+JQ0KICAgICAgICAgICAgZ3JvdXBfYnkoc3RhcnRfc3RhdGlvbikgJT4lDQogICAgICAgICAgICBzdW1tYXJpemUoY291bnQgPSBuKCkpLA0KICAgICAgICAgIGNvbG9yID0gIiMwODUxOWMiLA0KICAgICAgICAgIGFscGhhID0gMC40KSArDQogIGxhYnModGl0bGUgPSAiRGlzdHJpYnV0aW9uIG9mIEluZGVnbyBTdGF0aW9ucyBpbiBQaGlsYWRlbHBoaWEiLA0KICAgICAgIHN1YnRpdGxlID0gIk1heSAyMDIyIikrDQogIG1hcFRoZW1lDQpgYGANCg0KIyMgMi40LiBJbXBvcnQgV2VhdGhlciBEYXRhDQoNCkltcG9ydCB3ZWF0aGVyIGRhdGEgaW4gUGhpbGFkZWxwaGlhLCBpbmNsdWRpbmcgcGVyY2VwdGlvbiwgdGVtcGVyYXR1cmUgYW5kIHdpbmQgc3BlZWQuDQoNCmBgYHtyIGltcG9ydF93ZWF0aGVyLG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIHJlc3VsdHM9J2hpZGUnfQ0Kd2VhdGhlci5QYW5lbCA8LSANCiAgcmllbV9tZWFzdXJlcyhzdGF0aW9uID0gIlBITCIsIGRhdGVfc3RhcnQgPSAiMjAyMi0wNS0wMSIsIGRhdGVfZW5kID0gIjIwMjItMDUtMzEiKSAlPiUNCiAgZHBseXI6OnNlbGVjdCh2YWxpZCwgdG1wZiwgcDAxaSwgc2tudCklPiUNCiAgcmVwbGFjZShpcy5uYSguKSwgMCkgJT4lDQogICAgbXV0YXRlKGludGVydmFsNjAgPSB5bWRfaChzdWJzdHIodmFsaWQsMSwxMykpKSAlPiUNCiAgICBtdXRhdGUod2VlayA9IHdlZWsoaW50ZXJ2YWw2MCksDQogICAgICAgICAgIGRvdHcgPSB3ZGF5KGludGVydmFsNjAsIGxhYmVsPVRSVUUpKSAlPiUNCiAgICBncm91cF9ieShpbnRlcnZhbDYwKSAlPiUNCiAgICBzdW1tYXJpemUoVGVtcGVyYXR1cmUgPSBtYXgodG1wZiksDQogICAgICAgICAgICAgIFByZWNpcGl0YXRpb24gPSBzdW0ocDAxaSksDQogICAgICAgICAgICAgIFdpbmRfU3BlZWQgPSBtYXgoc2tudCkpICU+JQ0KICAgIG11dGF0ZShUZW1wZXJhdHVyZSA9IGlmZWxzZShUZW1wZXJhdHVyZSA9PSAwLCA0MiwgVGVtcGVyYXR1cmUpKQ0KYGBgDQoNCmBgYHtyIHBsb3Rfd2VhdGhlciwgY2F0Y2hlID0gVFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgcmVzdWx0cz0naGlkZSd9DQpncmlkLmFycmFuZ2UoDQogIGdncGxvdCh3ZWF0aGVyLlBhbmVsLCBhZXMoaW50ZXJ2YWw2MCxQcmVjaXBpdGF0aW9uKSkgKyBnZW9tX2xpbmUoKSArIA0KICBsYWJzKHRpdGxlPSJQZXJjaXBpdGF0aW9uIiwgeD0iSG91ciIsIHk9IlBlcmVjaXBpdGF0aW9uIikgKyBwbG90VGhlbWUsDQogIGdncGxvdCh3ZWF0aGVyLlBhbmVsLCBhZXMoaW50ZXJ2YWw2MCxXaW5kX1NwZWVkKSkgKyBnZW9tX2xpbmUoKSArIA0KICAgIGxhYnModGl0bGU9IldpbmQgU3BlZWQiLCB4PSJIb3VyIiwgeT0iV2luZCBTcGVlZCIpICsgcGxvdFRoZW1lLA0KICBnZ3Bsb3Qod2VhdGhlci5QYW5lbCwgYWVzKGludGVydmFsNjAsVGVtcGVyYXR1cmUpKSArIGdlb21fbGluZSgpICsgDQogICAgbGFicyh0aXRsZT0iVGVtcGVyYXR1cmUiLCB4PSJIb3VyIiwgeT0iVGVtcGVyYXR1cmUiKSArIHBsb3RUaGVtZSwNCiAgdG9wPSJXZWF0aGVyIERhdGEgLSBQSEwgUEhMIC0gTWF5LCAyMDIyIikNCmBgYA0KDQojIyAyLjUgSW1wb3J0IFNlcHRhIERhdGENCg0KSW1wb3J0IHN1YndheSBzdGF0aW9uIHBvaW50cyBpbiBQaGlsYWRlbHBoaWEuDQoNCmBgYHtyIHNlcHRhZGF0YSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgcmVzdWx0cz0naGlkZSd9DQplbCA8LSBzdF9yZWFkKCJodHRwczovL29wZW5kYXRhLmFyY2dpcy5jb20vZGF0YXNldHMvOGM2ZTI1NzVjOGFkNDZlYjg4N2U2YmIzNTgyNWUxYTZfMC5nZW9qc29uIikNCkJyb2FkX1N0IDwtIHN0X3JlYWQoImh0dHBzOi8vb3BlbmRhdGEuYXJjZ2lzLmNvbS9kYXRhc2V0cy8yZTkwMzdmZDViZWY0MDY0ODhmZmU1YmI2N2QyMTMxMl8wLmdlb2pzb24iKQ0Kc2VwdGFTdG9wcyA8LSANCiAgcmJpbmQoDQogICAgIGVsICU+JSANCiAgICAgIG11dGF0ZShMaW5lID0gIkVsIikgJT4lDQogICAgICBkcGx5cjo6c2VsZWN0KFN0YXRpb24sIExpbmUpLA0KICAgICBCcm9hZF9TdCAlPiUNCiAgICAgIG11dGF0ZShMaW5lID0iQnJvYWRfU3QiKSAlPiUNCiAgICAgIGRwbHlyOjpzZWxlY3QoU3RhdGlvbiwgTGluZSkpICU+JQ0KICBzdF90cmFuc2Zvcm0oY3JzPTQzMjYpICANCg0KZ2dwbG90KCkrDQogIGdlb21fc2YoZGF0YSA9IFBITFRyYWN0cykrDQogIGdlb21fc2YoZGF0YSA9IHNlcHRhU3RvcHMsZmlsbCA9ICIjMDg1MTljIiwgY29sb3IgPSAiIzA4NTE5YyIsIGFscGhhID0gMC43KSsNCiAgbGFicyh0aXRsZSA9ICJTdWJ3YXkgU3RhdGlvbnMgaW4gUGhpbGFkZWxwaGlhIikrDQogIHRoZW1lX21pbmltYWwoKQ0KYGBgDQoNCg0KIyAzLiBFeHBsb3JhdG9yeSBBbmFseXNpcw0KDQojIyAzLjEuIEJpa2Ugc2hhcmUgdHJpcHMgcGVyIGhvdXINCkV4cGxvcmluZyBiaWtlIHNoYXJlIHRyaXBzIHBlciBob3VyLCB3ZSBvYnNlcnZlZCBhIGN5Y2xpY2FsIHBhdHRlcm4gaW4gdHJpcCBudW1iZXJzIHRocm91Z2hvdXQgdGhlIGRheSwgd2l0aCBjZXJ0YWluIGhvdXJzIHNob3dpbmcgY2xlYXIgcGVhayBkZW1hbmQuIEFkZGl0aW9uYWxseSwgdHJpcHMgb24gd2Vla2VuZHMgd2VyZSBzaWduaWZpY2FudGx5IGxvd2VyIGNvbXBhcmVkIHRvIHdlZWtkYXlzLiBIb3dldmVyLCBkdXJpbmcgdGhlIHdlZWtlbmQgYXJvdW5kIE1heSAyOHRoICh0aGUgTWVtb3JpYWwgRGF5KSwgdGhlcmUgd2FzIG5vIG5vdGljZWFibGUgZGlwIGluIHRyaXBzLCByZWZsZWN0aW5nIHRoZSBpbXBhY3Qgb2YgaG9saWRheXMgb24gcGVvcGxlJ3MgdHJhdmVsIGJlaGF2aW9yLg0KDQoNCmBgYHtyIHRyaXBfdGltZXNlcmllcyB9DQpnZ3Bsb3QoZGF0X2NlbnN1cyAlPiUNCiAgICAgICAgIGdyb3VwX2J5KGludGVydmFsNjApICU+JQ0KICAgICAgICAgdGFsbHkoKSkrDQogIGdlb21fbGluZShhZXMoeCA9IGludGVydmFsNjAsIHkgPSBuKSkrDQogIGxhYnModGl0bGU9IkJpa2Ugc2hhcmUgdHJpcHMgcGVyIGhyLiBQSEwsIE1heSwgMjAyMiIsDQogICAgICAgeD0iRGF0ZSIsIA0KICAgICAgIHk9Ik51bWJlciBvZiB0cmlwcyIpKw0KICBwbG90VGhlbWUNCmBgYA0KDQpCcmVha2luZyB0aGVzZSB0cmVuZHMgZG93biwgd2UgY2F0ZWdvcml6ZWQgdGhlIDI0IGhvdXJzIGludG8gZm91ciB0aW1lIHBlcmlvZHM6IG92ZXJuaWdodCwgQU0gUnVzaCwgTWlkLURheSwgYW5kIFBNIFJ1c2gsIGFuZCBleHBsb3JlZCB0aGUgZGlzdHJpYnV0aW9uIG9mIHRoZSBudW1iZXIgb2YgdHJpcHMgcGVyIGhvdXIgd2l0aGluIHRoZXNlIHBlcmlvZHMuIFdlIGZvdW5kIHRoYXQgdHJpcCBjb3VudHMgaW4gYWxsIGZvdXIgdGltZSBzbG90cyB3ZXJlIG1vc3RseSBjb25jZW50cmF0ZWQgYXJvdW5kIDEgYW5kIDIsIHdpdGggTWlkLURheSBhbmQgUE0gUnVzaCBwZXJpb2RzIHRlbmRpbmcgdG8gaGF2ZSBtb3JlIGNvdW50cyBvZiAyLg0KDQpgYGB7ciBtZWFuX3RyaXBzX2hpc3QsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFIH0NCmRhdF9jZW5zdXMgJT4lDQogICAgICAgIG11dGF0ZSh0aW1lX29mX2RheSA9IGNhc2Vfd2hlbihob3VyKGludGVydmFsNjApIDwgNyB8IGhvdXIoaW50ZXJ2YWw2MCkgPiAxOCB+ICJPdmVybmlnaHQiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaG91cihpbnRlcnZhbDYwKSA+PSA3ICYgaG91cihpbnRlcnZhbDYwKSA8IDEwIH4gIkFNIFJ1c2giLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaG91cihpbnRlcnZhbDYwKSA+PSAxMCAmIGhvdXIoaW50ZXJ2YWw2MCkgPCAxNSB+ICJNaWQtRGF5IiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhvdXIoaW50ZXJ2YWw2MCkgPj0gMTUgJiBob3VyKGludGVydmFsNjApIDw9IDE4IH4gIlBNIFJ1c2giKSklPiUNCiAgICAgICAgIGdyb3VwX2J5KGludGVydmFsNjAsIHN0YXJ0X3N0YXRpb24sIHRpbWVfb2ZfZGF5KSAlPiUNCiAgICAgICAgIHRhbGx5KCklPiUNCiAgZ3JvdXBfYnkoc3RhcnRfc3RhdGlvbiwgdGltZV9vZl9kYXkpJT4lDQogIHN1bW1hcml6ZShtZWFuX3RyaXBzID0gbWVhbihuKSklPiUNCiAgZ2dwbG90KCkrDQogIGdlb21faGlzdG9ncmFtKGFlcyhtZWFuX3RyaXBzKSwgYmlud2lkdGggPSAxKSsNCiAgbGFicyh0aXRsZT0iTWVhbiBOdW1iZXIgb2YgSG91cmx5IFRyaXBzIFBlciBTdGF0aW9uLiBQSEwsIE1heSwgMjAyMiIsDQogICAgICAgeD0iTnVtYmVyIG9mIHRyaXBzIiwgDQogICAgICAgeT0iRnJlcXVlbmN5IikrDQogIGZhY2V0X3dyYXAofnRpbWVfb2ZfZGF5KSsNCiAgcGxvdFRoZW1lDQpgYGANCg0KYGBge3IgdHJpcHNfc3RhdGlvbl9kb3R3IH0NCmdncGxvdChkYXRfY2Vuc3VzICU+JQ0KICAgICAgICAgZ3JvdXBfYnkoaW50ZXJ2YWw2MCwgc3RhcnRfc3RhdGlvbikgJT4lDQogICAgICAgICB0YWxseSgpKSsNCiAgZ2VvbV9oaXN0b2dyYW0oYWVzKG4pLCBiaW53aWR0aCA9IDEpKw0KICBsYWJzKHRpdGxlPSJCaWtlIHNoYXJlIHRyaXBzIHBlciBociBieSBzdGF0aW9uLiBQSEwsIE1heSwgMjAyMiIsDQogICAgICAgeD0iVHJpcCBDb3VudHMiLCANCiAgICAgICB5PSJOdW1iZXIgb2YgU3RhdGlvbnMiKSsNCiAgcGxvdFRoZW1lDQpgYGANCg0KRXhwbG9yaW5nIGJpa2Ugc2hhcmUgdHJpcHMgYnkgZGF5LCB3ZSBjYW4gc2VlIHRoYXQgdGhlIHRyaXAgY291bnRzIGluIEZyaWRheSBhbmQgd2Vla2VuZHMgYXJlIGNsZWFybHkgbG93ZXIuDQpgYGB7ciB0cmlwc19ob3VyX2RvdHcgfQ0KZ2dwbG90KGRhdF9jZW5zdXMgJT4lIG11dGF0ZShob3VyID0gaG91cihzdGFydF90aW1lKSkpKw0KICAgICBnZW9tX2ZyZXFwb2x5KGFlcyhob3VyLCBjb2xvciA9IGRvdHcpLCBiaW53aWR0aCA9IDEpKw0KICBsYWJzKHRpdGxlPSJCaWtlIHNoYXJlIHRyaXBzIGluIFBITCwgYnkgZGF5IG9mIHRoZSB3ZWVrLCBNYXksIDIwMjIiLA0KICAgICAgIHg9IkhvdXIiLCANCiAgICAgICB5PSJUcmlwIENvdW50cyIpKw0KICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gYnJld2VyLnBhbChuID0gNywgbmFtZSA9ICJTZXQyIikgKSArDQogICAgIHBsb3RUaGVtZQ0KDQoNCmdncGxvdChkYXRfY2Vuc3VzICU+JSANCiAgICAgICAgIG11dGF0ZShob3VyID0gaG91cihzdGFydF90aW1lKSwNCiAgICAgICAgICAgICAgICB3ZWVrZW5kID0gaWZlbHNlKGRvdHcgJWluJSBjKCJTdW4iLCAiU2F0IiksICJXZWVrZW5kIiwgIldlZWtkYXkiKSkpKw0KICAgICBnZW9tX2ZyZXFwb2x5KGFlcyhob3VyLCBjb2xvciA9IHdlZWtlbmQpLCBiaW53aWR0aCA9IDEpKw0KICBsYWJzKHRpdGxlPSJCaWtlIHNoYXJlIHRyaXBzIGluIFBITCAtIHdlZWtlbmQgdnMgd2Vla2RheSwgTWF5LCAyMDIyIiwNCiAgICAgICB4PSJIb3VyIiwgDQogICAgICAgeT0iVHJpcCBDb3VudHMiKSsNCiAgICAgcGxvdFRoZW1lDQpgYGANCg0KVGhlc2UgcGF0dGVybnMgYXJlIGFsc28gZXZpZGVudCBzcGF0aWFsbHksIHdpdGggdGhlIGhpZ2hlc3QgcmlkZXJzaGlwIHBlciBzdGF0aW9uIGNvbmNlbnRyYXRlZCBpbiB0aGUgZG93bnRvd24gYW5kIFVuaXZlcnNpdHkgQ2l0eSBhcmVhcyBkdXJpbmcgdGhlIGFmdGVybm9vbiBydXNoIGhvdXIuDQoNCmBgYHtyIG9yaWdpbl9tYXAgfQ0KZ2dwbG90KCkrDQogIGdlb21fc2YoZGF0YSA9IFBITFRyYWN0cyAlPiUNCiAgICAgICAgICBzdF90cmFuc2Zvcm0oY3JzPTQzMjYpKSsNCiAgZ2VvbV9wb2ludChkYXRhID0gZGF0X2NlbnN1cyAlPiUNCiAgICAgICAgICAgICAgIG5hLm9taXQoKSU+JQ0KICAgICAgICAgICAgbXV0YXRlKGhvdXIgPSBob3VyKHN0YXJ0X3RpbWUpLA0KICAgICAgICAgICAgICAgIHdlZWtlbmQgPSBpZmVsc2UoZG90dyAlaW4lIGMoIlN1biIsICJTYXQiKSwgIldlZWtlbmQiLCAiV2Vla2RheSIpLA0KICAgICAgICAgICAgICAgIHRpbWVfb2ZfZGF5ID0gY2FzZV93aGVuKGhvdXIoaW50ZXJ2YWw2MCkgPCA3IHwgaG91cihpbnRlcnZhbDYwKSA+IDE4IH4gIk92ZXJuaWdodCIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBob3VyKGludGVydmFsNjApID49IDcgJiBob3VyKGludGVydmFsNjApIDwgMTAgfiAiQU0gUnVzaCIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBob3VyKGludGVydmFsNjApID49IDEwICYgaG91cihpbnRlcnZhbDYwKSA8IDE1IH4gIk1pZC1EYXkiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaG91cihpbnRlcnZhbDYwKSA+PSAxNSAmIGhvdXIoaW50ZXJ2YWw2MCkgPD0gMTggfiAiUE0gUnVzaCIpKSU+JQ0KICAgICAgICAgICAgICBncm91cF9ieShzdGFydF9zdGF0aW9uLCBzdGFydF9sYXQsIHN0YXJ0X2xvbiwgd2Vla2VuZCwgdGltZV9vZl9kYXkpICU+JQ0KICAgICAgICAgICAgICB0YWxseSgpLA0KICAgICAgICAgICAgYWVzKHg9c3RhcnRfbG9uLCB5ID0gc3RhcnRfbGF0LCBjb2xvciA9IG4pLCANCiAgICAgICAgICAgIGZpbGwgPSAidHJhbnNwYXJlbnQiLCBhbHBoYSA9IDAuNSwgc2l6ZSA9IDEpKw0KICBzY2FsZV9jb2xvdXJfdmlyaWRpcyhkaXJlY3Rpb24gPSAtMSwNCiAgZGlzY3JldGUgPSBGQUxTRSwgb3B0aW9uID0gIkQiKSsNCiAgeWxpbShtaW4oZGF0X2NlbnN1cyRzdGFydF9sYXQpLCBtYXgoZGF0X2NlbnN1cyRzdGFydF9sYXQpKSsNCiAgeGxpbShtaW4oZGF0X2NlbnN1cyRzdGFydF9sb24pLCBtYXgoZGF0X2NlbnN1cyRzdGFydF9sb24pKSsNCiAgZmFjZXRfZ3JpZCh3ZWVrZW5kIH4gdGltZV9vZl9kYXkpKw0KICBsYWJzKHRpdGxlPSJCaWtlIHNoYXJlIHRyaXBzIHBlciBociBieSBzdGF0aW9uLiBQSEwsIE1heSwgMjAyMiIpKw0KICBtYXBUaGVtZQ0KYGBgDQoNCg0KIyMgMy4yIENyZWF0ZSBTcGFjZS1UaW1lIFBhbmVsDQoNCkhlcmUgd2UgY3JlYXRlIGEgc3R1ZHkgcGFuZWwgdGhhdCBoYXMgZWFjaCB1bmlxdWUgdGltZSBhbmQgc3RhcnQgc3RhdGlvbiBvYnNlcnZhdGlvbnMuDQoNCmBgYHtyIHBhbmVsX2xlbmd0aF9jaGVjayAsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIHJlc3VsdHM9J2hpZGUnfQ0KDQpzdHVkeS5wYW5lbCA8LSANCiAgZXhwYW5kLmdyaWQoaW50ZXJ2YWw2MD11bmlxdWUoZGF0X2NlbnN1cyRpbnRlcnZhbDYwKSwgDQogICAgICAgICAgICAgIHN0YXJ0X3N0YXRpb24gPSB1bmlxdWUoZGF0X2NlbnN1cyRzdGFydF9zdGF0aW9uKSkgJT4lDQogIGxlZnRfam9pbiguLCBkYXRfY2Vuc3VzICU+JQ0KICAgICAgICAgICAgICBzZWxlY3Qoc3RhcnRfc3RhdGlvbiwgT3JpZ2luLlRyYWN0LCBzdGFydF9sb24sIHN0YXJ0X2xhdCApJT4lDQogICAgICAgICAgICAgIGRpc3RpbmN0KCkgJT4lDQogICAgICAgICAgICAgIGdyb3VwX2J5KHN0YXJ0X3N0YXRpb24pICU+JQ0KICAgICAgICAgICAgICBzbGljZSgxKSkNCg0KYGBgDQoNCmBgYHtyIGNyZWF0ZV9wYW5lbCAsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIHJlc3VsdHM9J2hpZGUnfQ0KcmlkZS5wYW5lbCA8LSANCiAgZGF0X2NlbnN1cyAlPiUNCiAgbXV0YXRlKFRyaXBfQ291bnRlciA9IDEpICU+JQ0KICByaWdodF9qb2luKHN0dWR5LnBhbmVsKSAlPiUgDQogIGdyb3VwX2J5KGludGVydmFsNjAsIHN0YXJ0X3N0YXRpb24sIE9yaWdpbi5UcmFjdCwgc3RhcnRfbG9uLCBzdGFydF9sYXQpICU+JQ0KICBzdW1tYXJpemUoVHJpcF9Db3VudCA9IHN1bShUcmlwX0NvdW50ZXIsIG5hLnJtPVQpKSAlPiUNCiAgbGVmdF9qb2luKHdlYXRoZXIuUGFuZWwpICU+JQ0KICB1bmdyb3VwKCkgJT4lDQogIGZpbHRlcihpcy5uYShzdGFydF9zdGF0aW9uKSA9PSBGQUxTRSkgJT4lDQogIG11dGF0ZSh3ZWVrID0gd2VlayhpbnRlcnZhbDYwKSwNCiAgICAgICAgIGRvdHcgPSB3ZGF5KGludGVydmFsNjAsIGxhYmVsID0gVFJVRSkpICU+JQ0KICBmaWx0ZXIoaXMubmEoT3JpZ2luLlRyYWN0KSA9PSBGQUxTRSkNCg0KIyB0dXJuIHN0YXJ0IHN0YXRpb24gaWQgZnJvbWUgbnVtZXJpYyB0byBjYXRlZ29yaWNhbCB2YXJpYWJsZQ0KcmlkZS5wYW5lbCRzdGFydF9zdGF0aW9uIDwtIGZhY3RvcihyaWRlLnBhbmVsJHN0YXJ0X3N0YXRpb24pDQpgYGANCg0KYGBge3IgY2Vuc3VzX2FuZF9wYW5lbCAsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIHJlc3VsdHM9J2hpZGUnfQ0KcmlkZS5wYW5lbCA8LSANCiAgbGVmdF9qb2luKHJpZGUucGFuZWwsIFBITENlbnN1cyAlPiUNCiAgICAgICAgICAgICAgYXMuZGF0YS5mcmFtZSgpICU+JQ0KICAgICAgICAgICAgICBzZWxlY3QoLWdlb21ldHJ5KSwgYnkgPSBjKCJPcmlnaW4uVHJhY3QiID0gIkdFT0lEIikpDQpgYGANCg0KIyMgMy4zLiBDcmVhdGUgdGltZSBsYWdzDQoNCldlIGNyZWF0ZWQgdGltZSBsYWdzIHRvIGNhcHR1cmUgdGhlIHRlbXBvcmFsIGRlcGVuZGVuY3kgYW5kIGJldHRlciBmaXQgdGhlIGhpc3RvcmljYWwgaW1wYWN0LiBIZXJlIHdlIGNyZWF0ZWQgMSwgMiwgMywgNCAsMTIgaG91cnMgbGFnLCBhbmQgMSwgMiwgMy1kYXkgaG9saWRheSBsYWcgYW5kIGxlYWQuDQpgYGB7ciB0aW1lX2xhZ3MgLG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIHJlc3VsdHM9J2hpZGUnfQ0KcmlkZS5wYW5lbCA8LSANCiAgcmlkZS5wYW5lbCAlPiUgDQogIGFycmFuZ2Uoc3RhcnRfc3RhdGlvbiwgaW50ZXJ2YWw2MCkgJT4lIA0KICBtdXRhdGUobGFnSG91ciA9IGRwbHlyOjpsYWcoVHJpcF9Db3VudCwxKSwNCiAgICAgICAgIGxhZzJIb3VycyA9IGRwbHlyOjpsYWcoVHJpcF9Db3VudCwyKSwNCiAgICAgICAgIGxhZzNIb3VycyA9IGRwbHlyOjpsYWcoVHJpcF9Db3VudCwzKSwNCiAgICAgICAgIGxhZzRIb3VycyA9IGRwbHlyOjpsYWcoVHJpcF9Db3VudCw0KSwNCiAgICAgICAgIGxhZzEySG91cnMgPSBkcGx5cjo6bGFnKFRyaXBfQ291bnQsMTIpLA0KICAgICAgICAgbGFnMWRheSA9IGRwbHlyOjpsYWcoVHJpcF9Db3VudCwyNCksDQogICAgICAgICBob2xpZGF5ID0gaWZlbHNlKHlkYXkoaW50ZXJ2YWw2MCkgPT0gMTQ4LDEsMCkpICU+JQ0KICAgbXV0YXRlKGRheSA9IHlkYXkoaW50ZXJ2YWw2MCkpICU+JQ0KICAgbXV0YXRlKGhvbGlkYXlMYWcgPSBjYXNlX3doZW4oZHBseXI6OmxhZyhob2xpZGF5LCAxKSA9PSAxIH4gIlBsdXNPbmVEYXkiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZHBseXI6OmxhZyhob2xpZGF5LCAyKSA9PSAxIH4gIlBsdXN0VHdvRGF5cyIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkcGx5cjo6bGFnKGhvbGlkYXksIDMpID09IDEgfiAiUGx1c3RUaHJlZURheXMiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZHBseXI6OmxlYWQoaG9saWRheSwgMSkgPT0gMSB+ICJNaW51c09uZURheSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkcGx5cjo6bGVhZChob2xpZGF5LCAyKSA9PSAxIH4gIk1pbnVzVHdvRGF5cyIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkcGx5cjo6bGVhZChob2xpZGF5LCAzKSA9PSAxIH4gIk1pbnVzVGhyZWVEYXlzIiksDQogICAgICAgICBob2xpZGF5TGFnID0gaWZlbHNlKGlzLm5hKGhvbGlkYXlMYWcpID09IFRSVUUsIDAsIGhvbGlkYXlMYWcpKQ0KDQpgYGANCg0KYGBge3IgZXZhbHVhdGVfbGFncyAsbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgcmVzdWx0cz0naGlkZSd9DQphcy5kYXRhLmZyYW1lKHJpZGUucGFuZWwpICU+JQ0KICAgIGdyb3VwX2J5KGludGVydmFsNjApICU+JSANCiAgICBzdW1tYXJpc2VfYXQodmFycyhzdGFydHNfd2l0aCgibGFnIiksICJUcmlwX0NvdW50IiksIG1lYW4sIG5hLnJtID0gVFJVRSkgJT4lDQogICAgZ2F0aGVyKFZhcmlhYmxlLCBWYWx1ZSwgLWludGVydmFsNjAsIC1UcmlwX0NvdW50KSAlPiUNCiAgICBtdXRhdGUoVmFyaWFibGUgPSBmYWN0b3IoVmFyaWFibGUsIGxldmVscz1jKCJsYWdIb3VyIiwibGFnMkhvdXJzIiwibGFnM0hvdXJzIiwibGFnNEhvdXJzIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJsYWcxMkhvdXJzIiwibGFnMWRheSIpKSklPiUNCiAgICBncm91cF9ieShWYXJpYWJsZSkgJT4lICANCiAgICBzdW1tYXJpemUoY29ycmVsYXRpb24gPSByb3VuZChjb3IoVmFsdWUsIFRyaXBfQ291bnQpLDIpKQ0KYGBgDQoNCiMjIDMuNCBDcmVhdGUgQW1lbml0eSBGZWF0dXJlcw0KDQpXZSBhZGQgYW4gYW1lbml0eSBmZWF0dXJlLCBkaXN0YW5jZSB0byB0aGUgbmVhcmVzdCBzdWJ3YXkgc3RhdGlvbiBhcyBvbmUgb2YgdGhlIHZhcmlhYmxlcy4gQmVjYXVzZSBhIGxhcmdlIHBvcnRpb24gb2YgcGVvcGxlIHVzaW5nIHNoYXJlZCBiaWtlcyBhcmUgaGVhZGluZyB0byB0aGUgc3Vid2F5cy4NCg0KYGBge3J9DQojIENvbnZlcnQgcmlkZS5wYW5lbCB0byBhbiBzZiBvYmplY3QgdXNpbmcgc3RhcnRfbG9uIGFuZCBzdGFydF9sYXQNCnJpZGUucGFuZWxfc2YgPC0gc3RfYXNfc2YocmlkZS5wYW5lbCwgY29vcmRzID0gYygic3RhcnRfbG9uIiwgInN0YXJ0X2xhdCIpLCBjcnMgPSA0MzI2KQ0KDQojIFJlcHJvamVjdCBib3RoIGRhdGFmcmFtZXMgdG8gRVBTRzoyMjcyDQpyaWRlLnBhbmVsX3Byb2ogPC0gc3RfdHJhbnNmb3JtKHJpZGUucGFuZWxfc2YsIGNycyA9IDIyNzIpDQpzZXB0YVN0b3BzX3Byb2ogPC0gc3RfdHJhbnNmb3JtKHNlcHRhU3RvcHMsIGNycyA9IDIyNzIpDQoNCiMgQ2FsY3VsYXRlIHRoZSBuZWFyZXN0IGRpc3RhbmNlIGZyb20gZWFjaCByaWRlLnBhbmVsIHBvaW50IHRvIHRoZSBjbG9zZXN0IHNlcHRhU3RvcA0KbmVhcmVzdF9kaXN0YW5jZXMgPC0gc3RfbmVhcmVzdF9mZWF0dXJlKHJpZGUucGFuZWxfcHJvaiwgc2VwdGFTdG9wc19wcm9qKSAgIyBJbmRleCBvZiBuZWFyZXN0IHBvaW50cw0KbmVhcmVzdF9wb2ludHMgPC0gc2VwdGFTdG9wc19wcm9qW25lYXJlc3RfZGlzdGFuY2VzLCBdICAjIEdldCB0aGUgbmVhcmVzdCBwb2ludHMNCmRpc3RhbmNlcyA8LSBzdF9kaXN0YW5jZShyaWRlLnBhbmVsX3Byb2osIG5lYXJlc3RfcG9pbnRzLCBieV9lbGVtZW50ID0gVFJVRSkgICMgQ2FsY3VsYXRlIGRpc3RhbmNlcw0KDQojIEFkZCB0aGUgZGlzdGFuY2VzIGJhY2sgdG8gcmlkZS5wYW5lbA0KcmlkZS5wYW5lbCRuZWFyZXN0X3NlcHRhX2Rpc3QgPC0gYXMubnVtZXJpYyhkaXN0YW5jZXMpICAjIENvbnZlcnQgdG8gbnVtZXJpYw0KDQojIE9wdGlvbmFsOiBBZGQgbmVhcmVzdCBzZXB0YVN0b3AgSUQgb3Igb3RoZXIgYXR0cmlidXRlcw0KcmlkZS5wYW5lbCRuZWFyZXN0X3NlcHRhU3RvcF9pZCA8LSBuZWFyZXN0X3BvaW50cyRTdGF0aW9uICANCmBgYA0KDQpgYGB7cn0NCnJpZGUucGFuZWwgPC0gbmEub21pdChyaWRlLnBhbmVsKQ0KYGBgDQoNCkZpbmFsbHksIHdlIGNyZWF0ZWQgYW4gYW5pbWF0ZWQgcGxvdCBhbmQgbm90ZSBjbGVhciBzcGF0aWFsIGFuZCB0ZW1wb3JhbCBhdXRvY29ycmVsYXRpb24gaW4gb3VyIGRhdGE6IHRoZSBhbmltYXRlZCBtYXAgaW5kaWNhdGVzIHRoYXQgZGF0YSBjbHVzdGVyIGF0IGNlcnRhaW4gdGltZXMgb2YgdGhlIGRheSBpbiBjZW50cmFsIFBoaWxhZGVscGhpYS4NCg0KYGBge3IgYW5pbWF0ZV9wbG90LCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQ0KDQpkYXRhXzVfMTggPC0gcmlkZS5wYW5lbCAlPiUNCiAgZmlsdGVyKGZvcm1hdChpbnRlcnZhbDYwLCAiJVktJW0tJWQiKSA9PSAiMjAyMi0wNS0xOCIpICANCmRhdGFfNV8xOCRpbnRlcnZhbDYwIDwtIGRhdGFfNV8xOCRpbnRlcnZhbDYwICsgaG91cnMoNCkNCg0KDQpwIDwtIGdncGxvdCgpICsNCiAgZ2VvbV9zZihkYXRhID0gUEhMVHJhY3RzKSsNCiAgZ2VvbV9wb2ludChkYXRhID0gZGF0YV81XzE4LCBhZXMoeCA9IHN0YXJ0X2xvbiwgeSA9IHN0YXJ0X2xhdCwgY29sb3IgPSBUcmlwX0NvdW50KSwgYWxwaGEgPSAwLjgsIHNpemUgPSAyKSArIA0KICBzY2FsZV9jb2xvcl9ncmFkaWVudChsb3cgPSAibGlnaHRibHVlIiwgaGlnaCA9ICJkYXJrYmx1ZSIpICsgDQogIGxhYnModGl0bGUgPSAiQmlrZSBTaGFyZSBUcmlwcyBvbiAyMDIyLTA1LTE4IiwgDQogICAgICAgc3VidGl0bGUgPSAiSG91cjoge2Zvcm1hdChmcmFtZV90aW1lLCAnJVktJW0tJWQgJUg6MDA6MDAnKX0iLCANCiAgICAgICB4ID0gIkxvbmdpdHVkZSIsIA0KICAgICAgIHkgPSAiTGF0aXR1ZGUiKSArDQogIHRoZW1lX21pbmltYWwoKSArDQogIHRyYW5zaXRpb25fdGltZShpbnRlcnZhbDYwKSArIA0KICBlYXNlX2FlcygnbGluZWFyJykgDQoNCiMgRXhwb3J0IHRoZSBhbmltYXRpb24gYXMgYSBHSUYNCiMgYW5pbWF0ZWRfZ2lmIDwtIGFuaW1hdGUocCwgd2lkdGggPSA4MDAsIGhlaWdodCA9IDYwMCwgZnBzID0gMTAsIHJlbmRlcmVyID0gZ2lmc2tpX3JlbmRlcmVyKCkpDQojIGFuaW1fc2F2ZSgiYmlrZV9zaGFyZV90cmlwcy5naWYiLCBhbmltYXRlZF9naWYpDQoNCmBgYA0KDQpgYGB7ciBEaXNwbGF5IHRoZSBHSUZ9DQprbml0cjo6aW5jbHVkZV9ncmFwaGljcygiYmlrZV9zaGFyZV90cmlwcy5naWYiKQ0KYGBgDQoNCiMgNC4gTW9kZWxpbmcNCg0KIyMgNC4xLiBSdW4gTW9kZWxzDQoNCldlIHVzZSBXZWVrIDIwLCAyMSwgYW5kIDIyIGFzIHRyYWluIGRhdGFzZXQsIGZvciBpdCBpbmNsdWRlcyB0aGUgaG9saWRheSBsYWcgaW4gdGhlIGxhc3QgMyB3ZWVrcy4gQW5kIHRha2UgdGhlIDE4dGggYW5kIDE5dGggd2VlayBhcyB0ZXN0IHNldC4NCmBgYHtyIHRyYWluX3Rlc3QgfQ0KcmlkZS5UcmFpbiA8LSBmaWx0ZXIocmlkZS5wYW5lbCwgd2VlayA+PSAyMCkNCnJpZGUuVGVzdCA8LSBmaWx0ZXIocmlkZS5wYW5lbCwgd2VlayA8IDIwKQ0KYGBgDQoNCkZvciBvdXIgYW5hbHlzaXMsIHdlIHVzZWQgT0xTIHJlZ3Jlc3Npb25zLiBBbHRob3VnaCBPTFMgbWF5IG5vdCBiZSB0aGUgYmVzdCBtZXRob2QgZHVlIHRvIHRoZSBzcGF0aWFsIGF1dG9jb3JyZWxhdGlvbiBkaXNjdXNzZWQgZWFybGllciwgYWx0ZXJuYXRpdmUgYXBwcm9hY2hlcyBsaWtlIHNwYXRpYWwgbGFnIG9yIGdlb2dyYXBoaWNhbGx5IHdlaWdodGVkIHJlZ3Jlc3Npb24gY291bGQgZW5oYW5jZSBwZXJmb3JtYW5jZSBhbmQgZ2VuZXJhbGl6YWJpbGl0eS4gTm9uZXRoZWxlc3MsIGZvciB0aGUgb2JqZWN0aXZlcyBvZiB0aGlzIHRhc2ssIE9MUyBwcm92ZXMgYWRlcXVhdGUuDQoNCkluIG91ciBhbmFseXNpcywgc2V2ZW4gcmVncmVzc2lvbiBtb2RlbHMgYXJlIGJ1aWx0OiANCg0KcmVnMTogb25seSB0aW1lIGZpeGVkIGVmZmVjdA0KDQpyZWcyOiBvbmx5IHNwYWNlIGZpeGVkIGVmZmVjdA0KDQpyZWczOiB0aW1lIC0gc3BhY2UgZml4ZWQgZWZmZWN0DQoNCnJlZzQ6IHJlZzMgd2l0aCBob3VyIGxhZw0KDQpyZWc1OiByZWc0IHdpdGggaG9saWRheSBsYWcNCg0KcmVnNjogcmVnNSB3aXRoIGFtZW5pdHkgZmVhdHVyZQ0KDQpyZWc3OiByZWc2IHdpdGggc29tZSBzb2NpYWwgaW5kaWNhdG9ycyAocG9wdWxhdGlvbixtZWRpYW4gaG91c2Vob2xkIGluY29tZSBhbmQgcGVyY2VudCBvZiBwZW9wbGUgdGFraW5nIHB1YmxpYyB0cmFuc3BydGF0aW9uKQ0KDQoNCmBgYHtyIHNldmVuX21vZGVscyB9DQpyZWcxIDwtIA0KICBsbShUcmlwX0NvdW50IH4gIGhvdXIoaW50ZXJ2YWw2MCkgKyBkb3R3ICsgVGVtcGVyYXR1cmUsICBkYXRhPXJpZGUuVHJhaW4pDQoNCnJlZzIgPC0gDQogIGxtKFRyaXBfQ291bnQgfiAgc3RhcnRfc3RhdGlvbiArIGRvdHcgKyBUZW1wZXJhdHVyZSwgIGRhdGE9cmlkZS5UcmFpbikNCg0KcmVnMyA8LSANCiAgbG0oVHJpcF9Db3VudCB+ICBzdGFydF9zdGF0aW9uICsgaG91cihpbnRlcnZhbDYwKSArIGRvdHcgKyBUZW1wZXJhdHVyZSArIFByZWNpcGl0YXRpb24sIA0KICAgICBkYXRhPXJpZGUuVHJhaW4pDQoNCnJlZzQgPC0gDQogIGxtKFRyaXBfQ291bnQgfiAgc3RhcnRfc3RhdGlvbiArICBob3VyKGludGVydmFsNjApICsgZG90dyArIFRlbXBlcmF0dXJlICsgUHJlY2lwaXRhdGlvbiArDQogICAgICAgICAgICAgICAgICAgbGFnSG91ciArIGxhZzJIb3VycyArbGFnM0hvdXJzICsgbGFnMTJIb3VycyArIGxhZzFkYXksIA0KICAgICBkYXRhPXJpZGUuVHJhaW4pDQoNCnJlZzUgPC0gDQogIGxtKFRyaXBfQ291bnQgfiAgc3RhcnRfc3RhdGlvbiArIGhvdXIoaW50ZXJ2YWw2MCkgKyBkb3R3ICsgVGVtcGVyYXR1cmUgKyBQcmVjaXBpdGF0aW9uICsNCiAgICAgICAgICAgICAgICAgICBsYWdIb3VyICsgbGFnMkhvdXJzICtsYWczSG91cnMgK2xhZzEySG91cnMgKyBsYWcxZGF5ICsgaG9saWRheUxhZyArIGhvbGlkYXksIA0KICAgICBkYXRhPXJpZGUuVHJhaW4pDQoNCnJlZzYgPC0gDQogIGxtKFRyaXBfQ291bnQgfiAgc3RhcnRfc3RhdGlvbiArIGhvdXIoaW50ZXJ2YWw2MCkgKyBkb3R3ICsgVGVtcGVyYXR1cmUgKyBQcmVjaXBpdGF0aW9uICsNCiAgICAgICAgICAgICAgICAgICBsYWdIb3VyICsgbGFnMkhvdXJzICtsYWczSG91cnMgK2xhZzEySG91cnMgKyBsYWcxZGF5ICsgaG9saWRheUxhZyArIGhvbGlkYXkgKyBuZWFyZXN0X3NlcHRhX2Rpc3QsIA0KICAgICBkYXRhPXJpZGUuVHJhaW4pDQoNCnJlZzcgPC0gDQogIGxtKFRyaXBfQ291bnQgfiAgc3RhcnRfc3RhdGlvbiArIGhvdXIoaW50ZXJ2YWw2MCkgKyBkb3R3ICsgVGVtcGVyYXR1cmUgKyBQcmVjaXBpdGF0aW9uICsNCiAgICAgICAgICAgICAgICAgICBsYWdIb3VyICsgbGFnMkhvdXJzICtsYWczSG91cnMgK2xhZzEySG91cnMgKyBsYWcxZGF5ICsgaG9saWRheUxhZyArIGhvbGlkYXkgKyBuZWFyZXN0X3NlcHRhX2Rpc3QgKyBUb3RhbF9Qb3AgKyBNZWRfSW5jICsgUGVyY2VudF9UYWtpbmdfUHVibGljX1RyYW5zLCANCiAgICAgZGF0YT1yaWRlLlRyYWluKQ0KDQpgYGANCg0KIyMgNC4yLiBQcmVkaWN0IGZvciB0ZXN0IGRhdGENCg0KYGBge3IgbmVzdF9kYXRhICxtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCByZXN1bHRzPSdoaWRlJ30NCnJpZGUuVGVzdC53ZWVrTmVzdCA8LSANCiAgcmlkZS5UZXN0ICU+JQ0KICBuZXN0KC13ZWVrKSANCmBgYA0KDQoNCmBgYHtyIHByZWRpY3RfZnVuY3Rpb24sbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgcmVzdWx0cz0naGlkZScgfQ0KbW9kZWxfcHJlZCA8LSBmdW5jdGlvbihkYXQsIGZpdCl7cHJlZCA8LSBwcmVkaWN0KGZpdCwgbmV3ZGF0YSA9IGRhdCl9DQpgYGANCg0KDQpgYGB7ciBkb19wcmVkaWNpdG9ucyxtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCByZXN1bHRzPSdoaWRlJyAgfQ0Kd2Vla19wcmVkaWN0aW9ucyA8LSANCiAgcmlkZS5UZXN0LndlZWtOZXN0ICU+JSANCiAgICBtdXRhdGUoQVRpbWVfRkUgPSBtYXAoLnggPSBkYXRhLCBmaXQgPSByZWcxLCAuZiA9IG1vZGVsX3ByZWQpLA0KICAgICAgICAgICBCU3BhY2VfRkUgPSBtYXAoLnggPSBkYXRhLCBmaXQgPSByZWcyLCAuZiA9IG1vZGVsX3ByZWQpLA0KICAgICAgICAgICBDVGltZV9TcGFjZV9GRSA9IG1hcCgueCA9IGRhdGEsIGZpdCA9IHJlZzMsIC5mID0gbW9kZWxfcHJlZCksDQogICAgICAgICAgIERUaW1lX1NwYWNlX0ZFX3RpbWVMYWdzID0gbWFwKC54ID0gZGF0YSwgZml0ID0gcmVnNCwgLmYgPSBtb2RlbF9wcmVkKSwNCiAgICAgICAgICAgRVRpbWVfU3BhY2VfRkVfdGltZUxhZ3NfaG9saWRheUxhZ3MgPSBtYXAoLnggPSBkYXRhLCBmaXQgPSByZWc1LCAuZiA9IG1vZGVsX3ByZWQpLA0KICAgICAgICAgICBGVGltZV9TcGFjZV9GRV90aW1lTGFnc19ob2xpZGF5TGFnc19hbWVuaXR5ID0gbWFwKC54ID0gZGF0YSwgZml0ID0gcmVnNiwgLmYgPSBtb2RlbF9wcmVkKSwNCiAgICAgICAgICAgR1RpbWVfU3BhY2VfRkVfdGltZUxhZ3NfaG9saWRheUxhZ3NfYW1lbml0eV9zb2NpZXR5ID0gbWFwKC54ID0gZGF0YSwgZml0ID0gcmVnNywgLmYgPSBtb2RlbF9wcmVkKQ0KICAgICAgICAgICApICU+JSANCiAgICBnYXRoZXIoUmVncmVzc2lvbiwgUHJlZGljdGlvbiwgLWRhdGEsIC13ZWVrKSAlPiUNCiAgICBtdXRhdGUoT2JzZXJ2ZWQgPSBtYXAoZGF0YSwgcHVsbCwgVHJpcF9Db3VudCksDQogICAgICAgICAgIEFic29sdXRlX0Vycm9yID0gbWFwMihPYnNlcnZlZCwgUHJlZGljdGlvbiwgIH4gYWJzKC54IC0gLnkpKSwNCiAgICAgICAgICAgTUFFID0gbWFwX2RibChBYnNvbHV0ZV9FcnJvciwgbWVhbiwgbmEucm0gPSBUUlVFKSwNCiAgICAgICAgICAgc2RfQUUgPSBtYXBfZGJsKEFic29sdXRlX0Vycm9yLCBzZCwgbmEucm0gPSBUUlVFKSkNCg0Kd2Vla19wcmVkaWN0aW9ucw0KYGBgDQoNCiMgNS4gQWNjdXJhY3kgYW5kIEdlbmVyYWxpemFiaWxpdHkNCg0KIyMgNS4xLiBFeGFtaW5lIEVycm9yIE1ldHJpY3MgZm9yIEFjY3VyYWN5DQoNCkFtb25nIHRoZSBzZXZlbiBtb2RlbHMgd2UgcmFuLCB3ZSBmb3VuZCB0aGF0IHRob3NlIGluY29ycG9yYXRpbmcgYm90aCB0ZW1wb3JhbCBhbmQgc3BhdGlhbCBmZWF0dXJlcywgYWxvbmcgd2l0aCB2YXJpb3VzIHRpbWUtbGFnZ2VkIHZhcmlhYmxlcywgcGVyZm9ybWVkIHRoZSBiZXN0LiBJbnRlcmVzdGluZ2x5LCBzcGF0aWFsIGZlYXR1cmVzIGFwcGVhcmVkIHRvIGJlIG1vcmUgaW5mbHVlbnRpYWwgdGhhbiB0ZW1wb3JhbCBvbmVzOyBpbmNsdWRpbmcgc3BhdGlhbCBsYWdzIGluIHRoZSBtb2RlbCBsZWQgdG8gc2lnbmlmaWNhbnQgaW1wcm92ZW1lbnRzLiBUaGUgYWRkaXRpb24gb2YgaG9saWRheSBsYWdzLCBhbWVuaXR5IGZlYXR1cmVzLCBhbmQgc29jaWFsIGVjb25vbWljIHZhcmlhYmxlcyBicm91Z2h0IGFsbW9zdCBubyBpbXByb3ZlbWVudC4gVGhlc2UgZmVhdHVyZXMgbWlnaHQgYWxyZWFkeSBiZSBpbXBsaWNpdGx5IGNhcHR1cmVkIGJ5IHRoZSBwdXJlIHNwYXRpYWwgYW5kIHRlbXBvcmFsIGNoYXJhY3RlcmlzdGljcyBpbiB0aGUgZGF0YXNldC4gRm9yIGV4YW1wbGUsIHRoZSB2YXJpYWJsZSByZXByZXNlbnRpbmcgaW5jb21lLCB3aXRoIGl0cyBzdHJvbmcgc3BhdGlhbCBhdXRvY29ycmVsYXRpb24sIGlzIGxpa2VseSBhbHJlYWR5IGVtYmVkZGVkIGluIHRoZSBsb2NhdGlvbiBkYXRhOyBhbmQgdGhlIGRpc3RhbmNlIHRvIHRoZSBuZWFyZXN0IHN1YndheSBzdGF0aW9uIHNlZW1zIHRvIGJlIGluY2x1ZGVkIGluIHRoZSBzcGF0aWFsIGZpeGVkIGVmZmVjdC4NCg0KYGBge3IgcGxvdF9lcnJvcnNfYnlfbW9kZWwgfQ0Kd2Vla19wcmVkaWN0aW9ucyAlPiUNCiAgZHBseXI6OnNlbGVjdCh3ZWVrLCBSZWdyZXNzaW9uLCBNQUUpICU+JQ0KICBnYXRoZXIoVmFyaWFibGUsIE1BRSwgLVJlZ3Jlc3Npb24sIC13ZWVrKSAlPiUNCiAgZ2dwbG90KGFlcyh3ZWVrLCBNQUUpKSArIA0KICAgIGdlb21fYmFyKGFlcyhmaWxsID0gUmVncmVzc2lvbiksIHBvc2l0aW9uID0gImRvZGdlIiwgc3RhdD0iaWRlbnRpdHkiKSArDQogICAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gcGFsZXR0ZTcpICsNCiAgICBsYWJzKHRpdGxlID0gIk1lYW4gQWJzb2x1dGUgRXJyb3JzIGJ5IG1vZGVsIHNwZWNpZmljYXRpb24gYW5kIHdlZWsiKSArDQogIHBsb3RUaGVtZQ0KYGBgDQoNCmBgYHtyIGVycm9yX3ZzX2FjdHVhbF90aW1lc2VyaWVzICwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0V9DQp3ZWVrX3ByZWRpY3Rpb25zICU+JSANCiAgICBtdXRhdGUoaW50ZXJ2YWw2MCA9IG1hcChkYXRhLCBwdWxsLCBpbnRlcnZhbDYwKSwNCiAgICAgICAgICAgc3RhcnRfc3RhdGlvbiA9IG1hcChkYXRhLCBwdWxsLCBzdGFydF9zdGF0aW9uKSkgJT4lDQogICAgZHBseXI6OnNlbGVjdChpbnRlcnZhbDYwLCBzdGFydF9zdGF0aW9uLCBPYnNlcnZlZCwgUHJlZGljdGlvbiwgUmVncmVzc2lvbikgJT4lDQogICAgdW5uZXN0KCkgJT4lDQogICAgZ2F0aGVyKFZhcmlhYmxlLCBWYWx1ZSwgLVJlZ3Jlc3Npb24sIC1pbnRlcnZhbDYwLCAtc3RhcnRfc3RhdGlvbikgJT4lDQogICAgZ3JvdXBfYnkoUmVncmVzc2lvbiwgVmFyaWFibGUsIGludGVydmFsNjApICU+JQ0KICAgIHN1bW1hcml6ZShWYWx1ZSA9IHN1bShWYWx1ZSkpICU+JQ0KICAgIGdncGxvdChhZXMoaW50ZXJ2YWw2MCwgVmFsdWUsIGNvbG91cj1WYXJpYWJsZSkpICsgDQogICAgICBnZW9tX2xpbmUoc2l6ZSA9IDEuMSkgKyANCiAgICAgIGZhY2V0X3dyYXAoflJlZ3Jlc3Npb24sIG5jb2w9MSkgKw0KICAgICAgbGFicyh0aXRsZSA9ICJQcmVkaWN0ZWQvT2JzZXJ2ZWQgYmlrZSBzaGFyZSB0aW1lIHNlcmllcyIsIHN1YnRpdGxlID0gIlBITDsgQSB0ZXN0IHNldCBvZiAyIHdlZWtzIiwgIHggPSAiSG91ciIsIHk9ICJTdGF0aW9uIFRyaXBzIikgKw0KICAgICAgcGxvdFRoZW1lDQpgYGANCg0KIyMgNS4yIEstZm9sZCBDcm9zcyBWYWxpZGF0aW9uDQoNCk1vdmluZyBmb3J3YXJkLCB3ZSBzdGljayB3aXRoIGByZWc2YCwgd2hpY2ggc2VlbXMgdG8gaGF2ZSB0aGUgYmVzdCBnb29kbmVzcyBvZiBmaXQgZ2VuZXJhbGx5LiBXZSB1c2VkIGstZm9sZCBjcm9zcyB2YWxpZGF0aW9uIHRvIHRlc3Qgb3VyIG1vZGVsJ3MgYWNjdXJhY3kgYW5kIGdlbmVyYWxpemFiaWxpdHkuIFRoZSBSTVNFIGlzIDEuMDQgYW5kIE1BRSBpcyAwLjY3LCB3aGljaCBpbmRpY2F0ZSBhIG1vZGVyYXRlIGxldmVsIG9mIHBlcmZvcm1hbmNlLg0KDQpgYGB7cn0NCmZvbGRzIDwtIDUNCg0KY29udHJvbCA8LSB0cmFpbkNvbnRyb2wobWV0aG9kPSJjdiIsIG51bWJlcj1mb2xkcykNCg0KDQpzZXQuc2VlZCgxMjMpDQoNCm1vZGVsX2N2IDwtIHRyYWluKFRyaXBfQ291bnQgfiBzdGFydF9zdGF0aW9uICsgaG91cihpbnRlcnZhbDYwKSArIGRvdHcgKyBUZW1wZXJhdHVyZSArIFByZWNpcGl0YXRpb24gKw0KICAgICAgICAgICAgICAgICAgIGxhZ0hvdXIgKyBsYWcySG91cnMgK2xhZzNIb3VycyArbGFnMTJIb3VycyArIGxhZzFkYXkgKyBob2xpZGF5TGFnICsgaG9saWRheSArIG5lYXJlc3Rfc2VwdGFfZGlzdCwgDQogICAgICAgICAgICAgICAgICBkYXRhPXJpZGUucGFuZWwsDQogICAgICAgICAgICAgICAgICBtZXRob2Q9ImxtIiwNCiAgICAgICAgICAgICAgICAgIHRyQ29udHJvbD1jb250cm9sKQ0KDQpjdl9kZiA8LSBkYXRhLmZyYW1lKA0KICBtb2RlbCA9ICJGVGltZV9TcGFjZV9GRV90aW1lTGFnc19ob2xpZGF5TGFnc19hbWVuaXR5IiwNCiAgZm9sZHMgPSBmb2xkcywNCiAgcm1zZSA9IG1vZGVsX2N2JHJlc3VsdHMkUk1TRSwNCiAgbWFlID0gbW9kZWxfY3YkcmVzdWx0cyRNQUUNCikNCmBgYA0KDQpgYGB7cn0NCmN2X2RmICU+JQ0KICAgIGtibChjYXB0aW9uID0gIkNyb3NzLVZhbGlkYXRpb24gUmVzdWx0cyIpICU+JQ0KICAgIGthYmxlX3BhcGVyKGJvb3RzdHJhcF9vcHRpb25zID0gInN0cmlwZWQiLCBmdWxsX3dpZHRoID0gRikNCmBgYA0KDQoNCg0KRXhwbG9yaW5nIHRoZSBtZWFuIGFic29sdXRlIGVycm9ycyAoTUFFKSBieSBzdGF0aW9uLCB3ZSBmaW5kIHRoYXQgdGhlcmUgc2VlbXMgdG8gYmUgYSBzcGF0aWFsIHBhdHRlcm4gdG8gb3VyIG1vZGVsLlRoZSBoaWdoIE1BRSB2YWx1ZXMgYXJlIGNvbmNlbXRyYXRlZCBpbiB0aGUgZG93bnRvd24gYXJlYS4NCg0KYGBge3IgZXJyb3JzX2J5X3N0YXRpb24sIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFIH0NCndlZWtfcHJlZGljdGlvbnMgJT4lIA0KICAgIG11dGF0ZShpbnRlcnZhbDYwID0gbWFwKGRhdGEsIHB1bGwsIGludGVydmFsNjApLA0KICAgICAgICAgICBzdGFydF9zdGF0aW9uID0gbWFwKGRhdGEsIHB1bGwsIHN0YXJ0X3N0YXRpb24pLCANCiAgICAgICAgICAgc3RhcnRfbGF0ID0gbWFwKGRhdGEsIHB1bGwsIHN0YXJ0X2xhdCksIA0KICAgICAgICAgICBzdGFydF9sb24gPSBtYXAoZGF0YSwgcHVsbCwgc3RhcnRfbG9uKSkgJT4lDQogICAgc2VsZWN0KGludGVydmFsNjAsIHN0YXJ0X3N0YXRpb24sIHN0YXJ0X2xvbiwgc3RhcnRfbGF0LCBPYnNlcnZlZCwgUHJlZGljdGlvbiwgUmVncmVzc2lvbikgJT4lDQogICAgdW5uZXN0KCkgJT4lDQogIGZpbHRlcihSZWdyZXNzaW9uID09ICJGVGltZV9TcGFjZV9GRV90aW1lTGFnc19ob2xpZGF5TGFnc19hbWVuaXR5IikgJT4lDQogIGdyb3VwX2J5KHN0YXJ0X3N0YXRpb24sIHN0YXJ0X2xvbiwgc3RhcnRfbGF0KSAlPiUNCiAgc3VtbWFyaXplKE1BRSA9IG1lYW4oYWJzKE9ic2VydmVkLVByZWRpY3Rpb24pLCBuYS5ybSA9IFRSVUUpKSU+JQ0KZ2dwbG90KC4pKw0KICBnZW9tX3NmKGRhdGEgPSBQSExDZW5zdXMsIGNvbG9yID0gImdyZXkiLCBmaWxsID0gInRyYW5zcGFyZW50IikrDQogIGdlb21fcG9pbnQoYWVzKHggPSBzdGFydF9sb24sIHkgPSBzdGFydF9sYXQsIGNvbG9yID0gTUFFKSwgDQogICAgICAgICAgICAgZmlsbCA9ICJ0cmFuc3BhcmVudCIsIGFscGhhID0gMC41KSsNCiAgc2NhbGVfY29sb3VyX3ZpcmlkaXMoZGlyZWN0aW9uID0gLTEsDQogIGRpc2NyZXRlID0gRkFMU0UsIG9wdGlvbiA9ICJEIikrDQogIHlsaW0obWluKGRhdF9jZW5zdXMkc3RhcnRfbGF0KSwgbWF4KGRhdF9jZW5zdXMkc3RhcnRfbGF0KSkrDQogIHhsaW0obWluKGRhdF9jZW5zdXMkc3RhcnRfbG9uKSwgbWF4KGRhdF9jZW5zdXMkc3RhcnRfbG9uKSkrDQogIGxhYnModGl0bGU9Ik1lYW4gQWJzIEVycm9yLCBUZXN0IFNldCwgTW9kZWwgNiIpKw0KICBtYXBUaGVtZQ0KYGBgDQoNCg0KIyMgNS4zLiBTcGFjZS1UaW1lIEVycm9yIEV2YWx1YXRpb24NCg0KRXhjZXB0IGZvciBzcGF0aWFsIGVycm9yIGRpc3RyaWJ1dGlvbiwgd2Ugc2hvdWxkIGFsc28gY29uc2lkZXIgdGVtcG9yYWwgZXJyb3MuIFdlIHBsb3Qgb2JzZXJ2ZWQgYW5kIHByZWRpY3RlZCBmb3IgZGlmZmVyZW50IHRpbWVzIG9mIGRheSBkdXJpbmcgdGhlIHdlZWsgYW5kIHdlZWtlbmQsIGFuZCBzb21lIHBhdHRlcm5zIGJlZ2luIHRvIGVtZXJnZS4NCg0KMSkgVW5kZXJwcmVkaWN0aW9uOiBJbiBtb3N0IG9mIHRoZSBwbG90cywgdGhlIHByZWRpY3RlZCB2YWx1ZXMgYXJlIGNvbnNpc3RlbnRseSBsb3dlciB0aGFuIHRoZSBvYnNlcnZlZCBvbmVzLCBwYXJ0aWN1bGFybHkgYXMgdGhlIG51bWJlciBvZiB0cmlwcyBpbmNyZWFzZXMuIFRoaXMgaXMgc2hvd24gYnkgZGF0YSBwb2ludHMgbHlpbmcgYWJvdmUgdGhlIDQ1LWRlZ3JlZSBsaW5lIChyZXByZXNlbnRpbmcgcGVyZmVjdCBwcmVkaWN0aW9uKS4gVGhpcyBzdWdnZXN0cyB0aGF0IHRoZSBtb2RlbCBoYXMgbW9yZSBkaWZmaWN1bHR5IHdpdGggaGlnaGVyLWRlbWFuZCBzdGF0aW9ucy4NCg0KMikgVGltZSBFZmZlY3RzOiBUaGUgdW5kZXJwcmVkaWN0aW9uIHNlZW1zIG1vcmUgcHJvbm91bmNlZCBkdXJpbmcgcGVhayB0aW1lcyBsaWtlIHRoZSBBTSBhbmQgUE0gcnVzaCBob3VycyBjb21wYXJlZCB0byBtaWRkYXkgb3Igb3Zlcm5pZ2h0LiBUaGlzIGluZGljYXRlcyB0aGUgbW9kZWwgbWF5IG5vdCBmdWxseSBjYXB0dXJlIHRoZSBwYXR0ZXJucyBvZiB0cmF2ZWwgZHVyaW5nIHRoZXNlIHBlYWsgcGVyaW9kcy4NCg0KMykgT3V0bGllcnM6IFNvbWUgZGF0YSBwb2ludHMsIGVzcGVjaWFsbHkgaW4gdGhlIGhpZ2hlciB0cmlwIHJhbmdlcywgYXJlIGZhciBmcm9tIHRoZSBtYWluIGNsdXN0ZXIuIFRoZXNlIG91dGxpZXJzIGhpZ2hsaWdodCBjYXNlcyB3aGVyZSB0aGUgbW9kZWwgc2lnbmlmaWNhbnRseSB1bmRlcnBlcmZvcm1zIGluIHByZWRpY3RpbmcgdGhlIGNvcnJlY3QgbnVtYmVyIG9mIHRyaXBzLg0KDQpgYGB7ciBvYnNfcHJlZF9hbGwsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2UgPSBGQUxTRSwgY2FjaGU9VFJVRX0NCndlZWtfcHJlZGljdGlvbnMgJT4lIA0KICAgIG11dGF0ZShpbnRlcnZhbDYwID0gbWFwKGRhdGEsIHB1bGwsIGludGVydmFsNjApLA0KICAgICAgICAgICBzdGFydF9zdGF0aW9uID0gbWFwKGRhdGEsIHB1bGwsIHN0YXJ0X3N0YXRpb24pLCANCiAgICAgICAgICAgc3RhcnRfbGF0ID0gbWFwKGRhdGEsIHB1bGwsIHN0YXJ0X2xhdCksIA0KICAgICAgICAgICBzdGFydF9sb24gPSBtYXAoZGF0YSwgcHVsbCwgc3RhcnRfbG9uKSwNCiAgICAgICAgICAgZG90dyA9IG1hcChkYXRhLCBwdWxsLCBkb3R3KSkgJT4lDQogICAgc2VsZWN0KGludGVydmFsNjAsIHN0YXJ0X3N0YXRpb24sIHN0YXJ0X2xvbiwgDQogICAgICAgICAgIHN0YXJ0X2xhdCwgT2JzZXJ2ZWQsIFByZWRpY3Rpb24sIFJlZ3Jlc3Npb24sDQogICAgICAgICAgIGRvdHcpICU+JQ0KICAgIHVubmVzdCgpICU+JQ0KICBmaWx0ZXIoUmVncmVzc2lvbiA9PSAiRlRpbWVfU3BhY2VfRkVfdGltZUxhZ3NfaG9saWRheUxhZ3NfYW1lbml0eSIpJT4lDQogIG11dGF0ZSh3ZWVrZW5kID0gaWZlbHNlKGRvdHcgJWluJSBjKCJTdW4iLCAiU2F0IiksICJXZWVrZW5kIiwgIldlZWtkYXkiKSwNCiAgICAgICAgIHRpbWVfb2ZfZGF5ID0gY2FzZV93aGVuKGhvdXIoaW50ZXJ2YWw2MCkgPCA3IHwgaG91cihpbnRlcnZhbDYwKSA+IDE4IH4gIk92ZXJuaWdodCIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBob3VyKGludGVydmFsNjApID49IDcgJiBob3VyKGludGVydmFsNjApIDwgMTAgfiAiQU0gUnVzaCIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBob3VyKGludGVydmFsNjApID49IDEwICYgaG91cihpbnRlcnZhbDYwKSA8IDE1IH4gIk1pZC1EYXkiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaG91cihpbnRlcnZhbDYwKSA+PSAxNSAmIGhvdXIoaW50ZXJ2YWw2MCkgPD0gMTggfiAiUE0gUnVzaCIpKSU+JQ0KICBnZ3Bsb3QoKSsNCiAgZ2VvbV9wb2ludChhZXMoeD0gT2JzZXJ2ZWQsIHkgPSBQcmVkaWN0aW9uKSkrDQogICAgZ2VvbV9zbW9vdGgoYWVzKHg9IE9ic2VydmVkLCB5PSBQcmVkaWN0aW9uKSwgbWV0aG9kID0gImxtIiwgc2UgPSBGQUxTRSwgY29sb3IgPSAicmVkIikrDQogICAgZ2VvbV9hYmxpbmUoc2xvcGUgPSAxLCBpbnRlcmNlcHQgPSAwKSsNCiAgZmFjZXRfZ3JpZCh0aW1lX29mX2RheX53ZWVrZW5kKSsNCiAgbGFicyh0aXRsZT0iT2JzZXJ2ZWQgdnMgUHJlZGljdGVkIiwNCiAgICAgICB4PSJPYnNlcnZlZCB0cmlwcyIsIA0KICAgICAgIHk9IlByZWRpY3RlZCB0cmlwcyIpKw0KICBwbG90VGhlbWUNCmBgYA0KDQpXZSBmb3VuZCB0aGF0IHRoZSBNQUUgaXMgaGlnaGVzdCBkdXJpbmcgdGhlIFBNIHJ1c2gsIGZvbGxvd2VkIGJ5IHRoZSBBTSBydXNoIGFuZCBtaWRkYXksIHdpdGggdGhlIHNtYWxsZXN0IE1BRSBvY2N1cnJpbmcgZHVyaW5nIHRoZSBvdmVybmlnaHQgcGVyaW9kLiBUaGlzIHJlZmxlY3RzIHRoZSBtb2RlbCdzIGNoYWxsZW5nZXMgaW4gYWNjdXJhdGVseSBwcmVkaWN0aW5nIGRlbWFuZCBkdXJpbmcgcGVhayBob3Vycy4gVGhlc2UgcGVyaW9kcywgYWxvbmcgd2l0aCB0aGUgYXJlYXMgaW4gZG93bnRvd24gd2hlcmUgdGhlIG1vZGVsJ3MgZXJyb3JzIGFyZSBtb3N0IHByb25vdW5jZWQsIGNvcnJlc3BvbmQgdG8gbW9yZSBkZXZlbG9wZWQgcmVnaW9ucyB3aXRoIGhpZ2hlciBkZW1hbmQuIFRoaXMgc3VnZ2VzdHMgdGhhdCB0aGUgbW9kZWwncyBwZXJmb3JtYW5jZSwgcGFydGljdWxhcmx5IGluIGhpZ2gtZGVtYW5kLCBzcGF0aWFsbHkgY29uY2VudHJhdGVkIGFyZWFzLCBzdGlsbCByZXF1aXJlcyBzaWduaWZpY2FudCBpbXByb3ZlbWVudC4NCg0KYGBge3Igc3RhdGlvbl9zdW1tYXJ5LCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlID0gRkFMU0UgfQ0Kd2Vla19wcmVkaWN0aW9ucyAlPiUgDQogICAgbXV0YXRlKGludGVydmFsNjAgPSBtYXAoZGF0YSwgcHVsbCwgaW50ZXJ2YWw2MCksDQogICAgICAgICAgIHN0YXJ0X3N0YXRpb24gPSBtYXAoZGF0YSwgcHVsbCwgc3RhcnRfc3RhdGlvbiksIA0KICAgICAgICAgICBzdGFydF9sYXQgPSBtYXAoZGF0YSwgcHVsbCwgc3RhcnRfbGF0KSwgDQogICAgICAgICAgIHN0YXJ0X2xvbiA9IG1hcChkYXRhLCBwdWxsLCBzdGFydF9sb24pLA0KICAgICAgICAgICBkb3R3ID0gbWFwKGRhdGEsIHB1bGwsIGRvdHcpICkgJT4lDQogICAgc2VsZWN0KGludGVydmFsNjAsIHN0YXJ0X3N0YXRpb24sIHN0YXJ0X2xvbiwgDQogICAgICAgICAgIHN0YXJ0X2xhdCwgT2JzZXJ2ZWQsIFByZWRpY3Rpb24sIFJlZ3Jlc3Npb24sDQogICAgICAgICAgIGRvdHcpICU+JQ0KICAgIHVubmVzdCgpICU+JQ0KICBmaWx0ZXIoUmVncmVzc2lvbiA9PSAiRlRpbWVfU3BhY2VfRkVfdGltZUxhZ3NfaG9saWRheUxhZ3NfYW1lbml0eSIpJT4lDQogIG11dGF0ZSh3ZWVrZW5kID0gaWZlbHNlKGRvdHcgJWluJSBjKCJTdW4iLCAiU2F0IiksICJXZWVrZW5kIiwgIldlZWtkYXkiKSwNCiAgICAgICAgIHRpbWVfb2ZfZGF5ID0gY2FzZV93aGVuKGhvdXIoaW50ZXJ2YWw2MCkgPCA3IHwgaG91cihpbnRlcnZhbDYwKSA+IDE4IH4gIk92ZXJuaWdodCIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBob3VyKGludGVydmFsNjApID49IDcgJiBob3VyKGludGVydmFsNjApIDwgMTAgfiAiQU0gUnVzaCIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBob3VyKGludGVydmFsNjApID49IDEwICYgaG91cihpbnRlcnZhbDYwKSA8IDE1IH4gIk1pZC1EYXkiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaG91cihpbnRlcnZhbDYwKSA+PSAxNSAmIGhvdXIoaW50ZXJ2YWw2MCkgPD0gMTggfiAiUE0gUnVzaCIpKSAlPiUNCiAgZ3JvdXBfYnkoc3RhcnRfc3RhdGlvbiwgd2Vla2VuZCwgdGltZV9vZl9kYXksIHN0YXJ0X2xvbiwgc3RhcnRfbGF0KSAlPiUNCiAgc3VtbWFyaXplKE1BRSA9IG1lYW4oYWJzKE9ic2VydmVkLVByZWRpY3Rpb24pLCBuYS5ybSA9IFRSVUUpKSU+JQ0KICBnZ3Bsb3QoLikrDQogIGdlb21fc2YoZGF0YSA9IFBITENlbnN1cywgY29sb3IgPSAiZ3JleSIsIGZpbGwgPSAidHJhbnNwYXJlbnQiKSsNCiAgZ2VvbV9wb2ludChhZXMoeCA9IHN0YXJ0X2xvbiwgeSA9IHN0YXJ0X2xhdCwgY29sb3IgPSBNQUUpLCANCiAgICAgICAgICAgICBmaWxsID0gInRyYW5zcGFyZW50IiwgYWxwaGEgPSAwLjUpKw0KICBzY2FsZV9jb2xvdXJfdmlyaWRpcyhkaXJlY3Rpb24gPSAtMSwNCiAgZGlzY3JldGUgPSBGQUxTRSwgb3B0aW9uID0gIkQiKSsNCiAgeWxpbShtaW4oZGF0X2NlbnN1cyRzdGFydF9sYXQpLCBtYXgoZGF0X2NlbnN1cyRzdGFydF9sYXQpKSsNCiAgeGxpbShtaW4oZGF0X2NlbnN1cyRzdGFydF9sb24pLCBtYXgoZGF0X2NlbnN1cyRzdGFydF9sb24pKSsNCiAgZmFjZXRfZ3JpZCh3ZWVrZW5kfnRpbWVfb2ZfZGF5KSsNCiAgbGFicyh0aXRsZT0iTWVhbiBBYnNvbHV0ZSBFcnJvcnMsIFRlc3QgU2V0IikrDQogIG1hcFRoZW1lDQogIA0KYGBgDQoNCkxhc3RseSwgd2UgZmluZCB0aGF0IG91ciBtb2RlbCBkb2VzIG5vdCBnZW5lcmFsaXplIHBlcmZlY3RseS4gT3VyIG1vZGVsIHBlcmZvcm1zIGxlc3MgYWNjdXJhdGVseSBpbiB3ZWFsdGhpZXIsIHdoaXRlciBuZWlnaGJvcmhvb2RzLCBhbmQgbW9yZSBhY2N1cmF0ZWx5IGluIGFyZWFzIHdpdGggaGlnaGVyIHB1YmxpYyB0cmFuc3BvcnRhdGlvbiByZWxpYW5jZS4gDQoNCmBgYHtyIHN0YXRpb25fc3VtbWFyeTIsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2UgPSBGQUxTRSB9DQp3ZWVrX3ByZWRpY3Rpb25zICU+JSANCiAgICBtdXRhdGUoaW50ZXJ2YWw2MCA9IG1hcChkYXRhLCBwdWxsLCBpbnRlcnZhbDYwKSwNCiAgICAgICAgICAgc3RhcnRfc3RhdGlvbiA9IG1hcChkYXRhLCBwdWxsLCBzdGFydF9zdGF0aW9uKSwgDQogICAgICAgICAgIHN0YXJ0X2xhdCA9IG1hcChkYXRhLCBwdWxsLCBzdGFydF9sYXQpLCANCiAgICAgICAgICAgc3RhcnRfbG9uID0gbWFwKGRhdGEsIHB1bGwsIHN0YXJ0X2xvbiksDQogICAgICAgICAgIGRvdHcgPSBtYXAoZGF0YSwgcHVsbCwgZG90dyksDQogICAgICAgICAgIFBlcmNlbnRfVGFraW5nX1B1YmxpY19UcmFucyA9IG1hcChkYXRhLCBwdWxsLCBQZXJjZW50X1Rha2luZ19QdWJsaWNfVHJhbnMpLA0KICAgICAgICAgICBNZWRfSW5jID0gbWFwKGRhdGEsIHB1bGwsIE1lZF9JbmMpLA0KICAgICAgICAgICBQZXJjZW50X1doaXRlID0gbWFwKGRhdGEsIHB1bGwsIFBlcmNlbnRfV2hpdGUpKSAlPiUNCiAgICBzZWxlY3QoaW50ZXJ2YWw2MCwgc3RhcnRfc3RhdGlvbiwgc3RhcnRfbG9uLCANCiAgICAgICAgICAgc3RhcnRfbGF0LCBPYnNlcnZlZCwgUHJlZGljdGlvbiwgUmVncmVzc2lvbiwNCiAgICAgICAgICAgZG90dywgUGVyY2VudF9UYWtpbmdfUHVibGljX1RyYW5zLCBNZWRfSW5jLCBQZXJjZW50X1doaXRlKSAlPiUNCiAgICB1bm5lc3QoKSAlPiUNCiAgZmlsdGVyKFJlZ3Jlc3Npb24gPT0gIkZUaW1lX1NwYWNlX0ZFX3RpbWVMYWdzX2hvbGlkYXlMYWdzX2FtZW5pdHkiKSU+JQ0KICBtdXRhdGUod2Vla2VuZCA9IGlmZWxzZShkb3R3ICVpbiUgYygiU3VuIiwgIlNhdCIpLCAiV2Vla2VuZCIsICJXZWVrZGF5IiksDQogICAgICAgICB0aW1lX29mX2RheSA9IGNhc2Vfd2hlbihob3VyKGludGVydmFsNjApIDwgNyB8IGhvdXIoaW50ZXJ2YWw2MCkgPiAxOCB+ICJPdmVybmlnaHQiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaG91cihpbnRlcnZhbDYwKSA+PSA3ICYgaG91cihpbnRlcnZhbDYwKSA8IDEwIH4gIkFNIFJ1c2giLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaG91cihpbnRlcnZhbDYwKSA+PSAxMCAmIGhvdXIoaW50ZXJ2YWw2MCkgPCAxNSB+ICJNaWQtRGF5IiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhvdXIoaW50ZXJ2YWw2MCkgPj0gMTUgJiBob3VyKGludGVydmFsNjApIDw9IDE4IH4gIlBNIFJ1c2giKSkgJT4lDQogIGZpbHRlcih0aW1lX29mX2RheSA9PSAiQU0gUnVzaCIpICU+JQ0KICBncm91cF9ieShzdGFydF9zdGF0aW9uLCBQZXJjZW50X1Rha2luZ19QdWJsaWNfVHJhbnMsIE1lZF9JbmMsIFBlcmNlbnRfV2hpdGUpICU+JQ0KICBzdW1tYXJpemUoTUFFID0gbWVhbihhYnMoT2JzZXJ2ZWQtUHJlZGljdGlvbiksIG5hLnJtID0gVFJVRSkpJT4lDQogIGdhdGhlcigtc3RhcnRfc3RhdGlvbiwgLU1BRSwga2V5ID0gInZhcmlhYmxlIiwgdmFsdWUgPSAidmFsdWUiKSU+JQ0KICBnZ3Bsb3QoLikrDQogICNnZW9tX3NmKGRhdGEgPSBQSExDZW5zdXMsIGNvbG9yID0gImdyZXkiLCBmaWxsID0gInRyYW5zcGFyZW50IikrDQogIGdlb21fcG9pbnQoYWVzKHggPSB2YWx1ZSwgeSA9IE1BRSksIGFscGhhID0gMC40KSsNCiAgZ2VvbV9zbW9vdGgoYWVzKHggPSB2YWx1ZSwgeSA9IE1BRSksIG1ldGhvZCA9ICJsbSIsIHNlPSBGQUxTRSkrDQogIGZhY2V0X3dyYXAofnZhcmlhYmxlLCBzY2FsZXMgPSAiZnJlZSIpKw0KICBsYWJzKHRpdGxlPSJFcnJvcnMgYXMgYSBmdW5jdGlvbiBvZiBzb2Npby1lY29ub21pYyB2YXJpYWJsZXMiLA0KICAgICAgIHk9Ik1lYW4gQWJzb2x1dGUgRXJyb3IgKFRyaXBzKSIpKw0KICBwbG90VGhlbWUNCiAgDQpgYGANCg0KIyA2LiBEaXNjdXNzaW9uDQoNCkluIHN1bW1hcnksIHdlIGNhbiBzZWUgdGhhdCB3aGlsZSB3ZSBhcmUgYWJsZSB0byB0cmFjayB0aGUgdGltZSBjb21wb25lbnRzIG9mIGRlbWFuZCwgd2UgbWlzcyB0aGUgcGVha3MgYW5kIHVuZGVycHJlZGljdCBkdXJpbmcgcGVyaW9kcyBvZiBoaWdoIGRlbWFuZC4gT3VyIG1vZGVsIGNhbiBlZmZlY3RpdmVseSBwcmVkaWN0IHRoZSBkZW1hbmQgZm9yIGJpa2UgcmVkaXN0cmlidXRpb24gb24gdGhlIEluZGVnbyBiaWtlc2hhcmUgc3lzdGVtIHRvIHNvbWUgZXh0ZW50LCB3aXRoIGFuIE1BRSBvZiBhcHByb3hpbWF0ZWx5IDAuNiwgaW5kaWNhdGluZyB0aGF0IHRoZSBtb2RlbCdzIHByZWRpY3RlZCBiaWtlIGRlbWFuZCBhdCBhbnkgZ2l2ZW4gc3RhdGlvbiBpcyBmYWlybHkgY2xvc2UgdG8gdGhlIGFjdHVhbCBkZW1hbmQgKHdpdGhpbiBhYm91dCAxIGJpa2UpLiBIb3dldmVyLCBzaW5jZSB0aGUgYmFzZSBiaWtlIGRlbWFuZCBwZXIgc3RhdGlvbiBwZXIgaG91ciBpcyByZWxhdGl2ZWx5IHNtYWxsICh0eXBpY2FsbHkgdW5kZXIgMyBiaWtlcyksIHRoZSBhY2N1cmFjeSBvZiBwcmVkaWN0aW9ucyBzdGlsbCBuZWVkcyBpbXByb3ZlbWVudC4gTW9yZW92ZXIsIE91ciBtb2RlbCBkZW1vbnN0cmF0ZXMgbGltaXRlZCBnZW5lcmFsaXphYmlsaXR5LCBhcyB0aGUgTUFFIHZhcmllcyBhY3Jvc3MgZGlmZmVyZW50IHNvY2lvLWVjb25vbWljIHJlZ2lvbnMuIEFuZCBpdCByZW1haW5zIHRvIGJlIHNlZW4gaG93IHRoZSBtb2RlbCBwZXJmb3JtcyBpbiBvdGhlciBtb250aHMgb2YgYSB5ZWFyLg0KDQpUaGUgbWFpbiBpc3N1ZSB3ZSBmYWNlIGlzIHRoZSB1bmRlcmVzdGltYXRpb24gb2YgZGVtYW5kIGF0IGhpZ2gtZGVtYW5kIHN0YXRpb25zLCBhbmQgdGhlc2UgYXJlIHByZWNpc2VseSB0aGUgYXJlYXMgdGhhdCBuZWVkIGF0dGVudGlvbiB0aGUgbW9zdC4gRnJvbSBhbiBvcGVyYXRpb25zIHBlcnNwZWN0aXZlLCB1bmRlcmVzdGltYXRpbmcgaGlnaCBkZW1hbmQgbWF5IGxlYWQgdG8gaW5hZGVxdWF0ZSByZXNvdXJjZSBhbGxvY2F0aW9uLCBlc3BlY2lhbGx5IGR1cmluZyBwZWFrIHBlcmlvZHMgKHN1Y2ggYXMgbW9ybmluZyBhbmQgZXZlbmluZyBydXNoIGhvdXJzKSwgZGlyZWN0bHkgYWZmZWN0aW5nIHVzZXIgZXhwZXJpZW5jZS4gRm9yIGV4YW1wbGUsIGR1cmluZyB0aGVzZSBwZWFrIHBlcmlvZHMsIHN0YXRpb25zIG1heSBydW4gb3V0IG9mIGJpa2VzLCBwcmV2ZW50aW5nIHVzZXJzIGZyb20gcmVudGluZyBvciByZXR1cm5pbmcgYmlrZXMsIHdoaWNoIHJlZHVjZXMgdGhlIHNhdGlzZmFjdGlvbiBvZiBtZWV0aW5nIHRoZSBkZW1hbmQuIEFzIGRlbWFuZCBpbmNyZWFzZXMsIGEgbGFjayBvZiBzdWZmaWNpZW50IGJpa2VzIGNhbiBsZWFkIHRvIGxvbmdlciB3YWl0IHRpbWVzIGZvciB1c2Vycywgb3IgZXZlbiB1c2VyIGxvc3MsIGxvd2VyaW5nIHRoZSBvdmVyYWxsIHVzYWdlIG9mIHRoZSBiaWtlc2hhcmUgc3lzdGVtLiBGdXJ0aGVybW9yZSwgdGhlIG9wZXJhdG9yIG1heSBmYWlsIHRvIGVmZmVjdGl2ZWx5IHJlZGlzdHJpYnV0ZSBiaWtlcyB0byBoaWdoLWRlbWFuZCBhcmVhcywgdGh1cyByZWR1Y2luZyB0aGUgZWZmaWNpZW5jeSBhbmQgc2VydmljZSBxdWFsaXR5IG9mIHRoZSBlbnRpcmUgc3lzdGVtLg0KDQpUbyByZWR1Y2UgZXJyb3JzLCB3ZSBjb3VsZCBpbmNvcnBvcmF0ZSBtb3JlIHNwYXRpYWwtdGVtcG9yYWwgZmVhdHVyZXMuIEZvciBleGFtcGxlLCB3ZSBjYW4gdXNlIGJpbmFyeSB2YXJpYWJsZXMgdG8gYWNjb3VudCBmb3IgcGVhayBwZXJpb2RzIGFuZCBhZGQgc29jaWFsIHZhcmlhYmxlcyBzdWNoIGFzIG5lYXJieSBhbWVuaXRpZXMgYW5kIHJlc2lkZW50aWFsIGRlbnNpdHkuIEFkZGl0aW9uYWxseSwgdXNpbmcgbW9yZSBkZXRhaWxlZCBoaXN0b3JpY2FsIGRhdGEgZm9yIHRyYWluaW5nIGNvdWxkIGFsbG93IGZvciBtb3JlIGNvbXBsZXggbW9kZWxzLiBGb3IgZXhhbXBsZSwgdGhlIG1vZGVsIGNvdWxkIGRpZmZlcmVudGlhdGUgYmV0d2VlbiBob2xpZGF5cyB0aGF0IGdlbmVyYXRlIHZhcnlpbmcgbGV2ZWxzIG9mIGRlbWFuZCAoZS5nLiwgTWVtb3JpYWwgRGF5IHZlcnN1cyBDaHJpc3RtYXMpIG9yIGxlYXJuIGZyb20gYSBicm9hZGVyIHJhbmdlIG9mIGhpc3RvcmljYWwgZGF0YS4gV2UgY2FuIGFsc28gYW5hbHl6ZSBtb3JlIHNwZWNpZmljIGVycm9yIHBsb3RzIHRvIGlkZW50aWZ5IGFyZWFzIG9yIHRpbWVzIHdoZXJlIHRoZSBwcmVkaWN0aW9ucyBkZXZpYXRlIHNpZ25pZmljYW50bHksIGhlbHBpbmcgdXMgb3B0aW1pemUgcmVzb3VyY2UgZGlzdHJpYnV0aW9uIGZvciBzcGVjaWZpYyBsb2NhdGlvbnMgb3IgdGltZXMuIEluIHRlcm1zIG9mIHNwYXRpYWwgYW5hbHlzaXMsIG1ldGhvZHMgc3VjaCBhcyBnZW9ncmFwaGljYWxseSB3ZWlnaHRlZCByZWdyZXNzaW9uIChHV1IpIGNhbiBiZSBhcHBsaWVkIHRvIGV4cGxvcmUgdGhlIGRlbWFuZCBwYXR0ZXJucyBpbiBkaWZmZXJlbnQgYXJlYXMsIGlkZW50aWZ5aW5nIHJlZ2lvbnMgd2hlcmUgdGhlIGRlbWFuZCBjaGFyYWN0ZXJpc3RpY3MgZGlmZmVyIHNpZ25pZmljYW50bHkgZnJvbSBvdGhlcnMsIGFsbG93aW5nIGZvciBtb3JlIHByZWNpc2UgcHJlZGljdGlvbnMuDQoNCg0KUmVmZXJlbmNlczoNCg0KW14xXTogaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvSW5kZWdvDQo=