1. Introduction to Bike Share and Rebalancing Plan

1.1 Bikshare Syatems- Capital Bikeshare DC

Capital Bikeshare in Washington, DC makes it easy for people to grab a bike, ride where they need to go, and drop it off at another station. It’s a simple, affordable way to get around without needing a car. Bike-share systems like this help cut down traffic, reduce pollution, and make it easier for everyone to move around the city.

1.2 Operational Challenges Faced by Bike-Share Systems:

One of the biggest challenges for bike-share systems is making sure there are always enough bikes to pick up and enough empty docks to return bikes. If a station runs out of bikes or gets completely full, it becomes frustrating for users. To keep things running smoothly, bike-share operators have to constantly move bikes around the city based on where they think people will need them next.

1.3 Rebalancing Problem

Rebalancing ensure bike availability across all docks so that demand can be met without any lags or hiccups. It involved manually redistributing bikes across stations to ensure bike and dock availability. For the same, it required anticipating demand for all docks at all times, which is a problem hence advanced models to accurately predict demand using historical data are used here. The following model also forecasts space/time demand for bike share pickup in Washington DC’s Capital Bikeshare, using May 2019 data.

1.4 Proposed Rebalancing Strategy:

I plan to use a small fleet of trucks to move bikes between stations based on where we expect demand to be in the next 2 to 3 hours. The predictions use information like the time of day, day of the week, weather (like temperature and rain), and whether it’s a holiday. We also look at how busy each station has been recently by using past trip counts. This way, we can send trucks to the right places before stations run out of bikes or get too full. On weekends or during special events, we could also offer discounts or rewards to riders to help move bikes when predictions are less certain.

1.5 Load Libraries

library(tidyverse)
library(sf)
library(lubridate)
library(tigris)
library(tidycensus)
library(viridis)
library(riem)
library(gridExtra)
library(knitr)
library(kableExtra)
library(RSocrata)
library(dplyr)

plotTheme <- theme(
  plot.title =element_text(size=12),
  plot.subtitle = element_text(size=8),
  plot.caption = element_text(size = 6),
  axis.text.x = element_text(size = 10, angle = 45, hjust = 1),
  axis.text.y = element_text(size = 10),
  axis.title.y = element_text(size = 10),
  # Set the entire chart region to blank
  panel.background=element_blank(),
  plot.background=element_blank(),
  #panel.border=element_rect(colour="#F0F0F0"),
  # Format the grid
  panel.grid.major=element_line(colour="#D0D0D0",size=.2),
  axis.ticks=element_blank())

mapTheme <- theme(plot.title =element_text(size=12),
                  plot.subtitle = element_text(size=8),
                  plot.caption = element_text(size = 6),
                  axis.line=element_blank(),
                  axis.text.x=element_blank(),
                  axis.text.y=element_blank(),
                  axis.ticks=element_blank(),
                  axis.title.x=element_blank(),
                  axis.title.y=element_blank(),
                  panel.background=element_blank(),
                  panel.border=element_blank(),
                  panel.grid.major=element_line(colour = 'transparent'),
                  panel.grid.minor=element_blank(),
                  legend.direction = "vertical", 
                  legend.position = "right",
                  plot.margin = margin(1, 1, 1, 1, 'cm'),
                  legend.key.height = unit(1, "cm"), legend.key.width = unit(0.2, "cm"))

palette5 <- c("#fee5d9", "#fcae91", "#fb6a4a", "#de2d26", "#a50f15")
palette4 <- c("#fee0d2", "#fc9272", "#de2d26", "#a50f15")
palette2 <- c("#fb6a4a", "#a50f15")

1.6 Install Census API Key

# Install Census API Key
tidycensus::census_api_key("02524912081801068f04f286a086d19e9fc641ca", overwrite = TRUE)

1.7 Load and Clean Bike and Station Data

dat_bike <- st_read("Capital_Bikeshare_Locations.geojson")
dat_bi_wa <- st_read("201905-capitalbikeshare-tripdata.csv")

glimpse(dat_bike) #spatial station data
glimpse(dat_bi_wa) #trips data

start_st <- dat_bike %>% 
  select(NAME, geometry)

end_st<- dat_bike %>% 
  select(NAME, geometry)

##making data spatial by adding point data of start and end station to the trips data

dat <- dat_bi_wa %>% 
  left_join(start_st, by = c("Start.station" = "NAME")) %>% 
  rename(start_geo = geometry) %>% 
  left_join(end_st, by = c("End.station" = "NAME")) %>% 
  rename(end_geo = geometry)

dat %>% ##Checking for missing values
  summarise_all(~ sum(is.na(.))) %>%
  gather(key = "variable", value = "num_missing") %>%
  filter(num_missing > 0) %>%
  arrange(desc(num_missing)) %>%
  kable(caption = "Number of Missing Values per Column") %>%
  kable_styling(full_width = FALSE)


# Outlier detection: Trip duration
dat %>%
  mutate(Duration = as.numeric(Duration)) %>%
  ggplot(aes(x = Duration)) +
  geom_histogram(binwidth = 60, fill = "#a50f15", color = "#fcae91") +
  labs(title = "Distribution of Trip Durations (seconds)", x = "Trip Duration", y = "Count") +
  plotTheme

# Remove entries with missing station names or extremely long trips (likely errors)
dat <- dat %>%
  filter(!is.na(Start.station) & !is.na(End.station)) %>%
  filter(Duration > 60 & Duration < 7200)  # Keeping trips between 1 minute and 2 hours

2. Data Preprocessing and Feature Engineering

2.1 Feature Engineering

2.1.1 Time Bins

Creating 15 and 60 minute intervals by rounding.

dat2 <- dat %>%
  mutate(
    interval60 = floor_date(ymd_hms(Start.date), unit = "hour"),
    interval15 = floor_date(ymd_hms(Start.date), unit = "15 mins"),
    week = week(interval60),
    dotw = wday(interval60, label = TRUE)
  )

glimpse(dat2)
## Rows: 337,704
## Columns: 15
## $ Duration             <chr> "653", "297", "129", "1119", "533", "895", "489",…
## $ Start.date           <chr> "2019-05-01 00:00:02", "2019-05-01 00:00:13", "20…
## $ End.date             <chr> "2019-05-01 00:10:55", "2019-05-01 00:05:11", "20…
## $ Start.station.number <chr> "31127", "31641", "31255", "31655", "31203", "316…
## $ Start.station        <chr> "22nd & H St NW", "2nd St & Massachusetts Ave NE"…
## $ End.station.number   <chr> "31202", "31603", "31237", "31244", "31223", "315…
## $ End.station          <chr> "14th & R St NW", "1st & M St NE", "25th St & Pen…
## $ Bike.number          <chr> "W00233", "W00555", "W23383", "W20337", "W23707",…
## $ Member.type          <chr> "Member", "Member", "Member", "Member", "Member",…
## $ start_geo            <POINT [°]> POINT (-77.04886 38.89893), POINT (-77.0031…
## $ end_geo              <POINT [°]> POINT (-77.03201 38.91305), POINT (-77.0054…
## $ interval60           <dttm> 2019-05-01, 2019-05-01, 2019-05-01, 2019-05-01, …
## $ interval15           <dttm> 2019-05-01 00:00:00, 2019-05-01 00:00:00, 2019-0…
## $ week                 <dbl> 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 18, 1…
## $ dotw                 <ord> Wed, Wed, Wed, Wed, Wed, Wed, Wed, Wed, Wed, Wed,…

2.1.2 Census Data

Getting variables influencing demand from census data-

dcCensus <- 
  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 = "DC",   # ← changed to DC
          geometry = TRUE, 
          county = NULL,  # ← DC is a state + city, no county needed
          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 Geometries-

dcTracts <- 
  dcCensus %>%
  as.data.frame() %>%
  distinct(GEOID, .keep_all = TRUE) %>%
  select(GEOID, geometry) %>% 
  st_sf()

Adding the spatial information to our rideshare data as origin and destination data-

dat2 <- dat2 %>%
  mutate(
    from_longitude = st_coordinates(start_geo)[, 1],
    from_latitude = st_coordinates(start_geo)[, 2],
    to_longitude = st_coordinates(end_geo)[, 1],
    to_latitude = st_coordinates(end_geo)[, 2]
  )


dat_census <- dat2 %>%
  filter(!is.na(from_longitude) &
         !is.na(from_latitude) &
         !is.na(to_longitude) &
         !is.na(to_latitude)) %>%
  st_as_sf(coords = c("from_longitude", "from_latitude"), crs = 4326) %>%
  st_join(
    dcTracts %>% st_transform(crs = 4326),
    join = st_intersects,
    left = TRUE
  ) %>%
  rename(Origin.Tract = GEOID) %>%
  mutate(
    from_longitude = unlist(map(geometry, 1)),
    from_latitude = unlist(map(geometry, 2))
  ) %>%
  as.data.frame() %>%
  select(-geometry) %>%
  st_as_sf(coords = c("to_longitude", "to_latitude"), crs = 4326) %>%
  st_join(
    dcTracts %>% st_transform(crs = 4326),
    join = st_intersects,
    left = TRUE
  ) %>%
  rename(Destination.Tract = GEOID) %>%
  mutate(
    to_longitude = unlist(map(geometry, 1)),
    to_latitude = unlist(map(geometry, 2))
  ) %>%
  as.data.frame() %>%
  select(-geometry)

2.1.3 Weather Data

weather.Panel <- 
  riem_measures(station = "DCA", date_start = "2019-05-01", date_end = "2019-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)
  )


glimpse(weather.Panel)
## Rows: 720
## Columns: 4
## $ interval60    <dttm> 2019-05-01 00:00:00, 2019-05-01 01:00:00, 2019-05-01 02…
## $ Temperature   <dbl> 75, 71, 69, 70, 67, 64, 63, 62, 61, 61, 60, 60, 60, 60, …
## $ Precipitation <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,…
## $ Wind_Speed    <dbl> 7, 7, 7, 7, 10, 12, 13, 8, 10, 8, 10, 10, 10, 8, 8, 6, 5…

Plotting Weather Data for Washington DC-

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 - Washington DC - May, 2019")

2.2 Exploratory data analysis

2.2.1 Bike share trips per hr. Washington DC for all of May, 2019

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

The above graph shows the demand pattern in DC during the 4 weeks of May, with high ridership during the week and general low rideship during weekends. The graph also shows a big fluctuation in demand during the week with lower demand on Mondays and higher demand during the week.

2.2.2 Analysing Mean Number of Hourly Trips Per Station

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. Washington, May, 2019",
       x="Number of trips", 
       y="Frequency")+
  facet_wrap(~time_of_day)+
  plotTheme

The above graph clearly shows that DC has higher AM and Overnight rush and lower Mid-day and PM rush in the city.

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

This graph show that most stations have very low trip counts per hour (near 0 to 2 trips).A smaller number of stations have medium activity (5–10 trips) and very few stations have high trip activity (15+, 20+, almost none past 30 trips).From this we can conclude that bike share usage is very uneven.A few popular stations (near downtown, offices, metro stops) get tons of trips. Most stations (especially residential or suburban ones) are quiet most of the day.

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

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

Following up from the above graph, it is clear that office hour rush in the city is more, with demand peaking around 8-9 am and 5-6 pm on most weekdays and 12-3 pm on most weekends.

2.2.3 Mapping Station Usage and frequency

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

Confirming our previous analysis, we see that the city center sees most demand during the weekdays with less use in the outskirts in the city. Weekend see a general low demand across all stations.

2.3 Model training

2.3.1 Space Time Panel -

We created a complete panel dataset where every station and every hour is included, even if no trips happened. This makes sure our model can learn from both busy and quiet times. We also added weather and census features to help improve our predictions.

length(unique(dat_census$interval60)) * length(unique(dat_census$Start.station.number))
## [1] 346704
study.panel <- 
  expand.grid(interval60=unique(dat_census$interval60), 
              Start.station.number = unique(dat_census$Start.station.number)) %>%
  left_join(., dat_census %>%
              select(Start.station.number, Start.station, Origin.Tract, from_longitude, from_latitude )%>%
              distinct() %>%
              group_by(Start.station.number) %>%
              slice(1))

nrow(study.panel)      
## [1] 346704
ride.panel <- 
  dat_census %>%
  mutate(Trip_Counter = 1) %>%
  right_join(study.panel) %>% 
  group_by(interval60, Start.station.number, Start.station, Origin.Tract, from_longitude, from_latitude) %>%
  summarize(Trip_Count = sum(Trip_Counter, na.rm=T)) %>%
  left_join(weather.Panel) %>%
  ungroup() %>%
  filter(is.na(Start.station.number) == FALSE) %>%
  mutate(week = week(interval60),
         dotw = wday(interval60, label = TRUE)) %>%
  filter(is.na(Origin.Tract) == FALSE)
ride.panel <- 
  left_join(ride.panel, dcCensus %>%
              as.data.frame() %>%
              select(-geometry), by = c("Origin.Tract" = "GEOID"))

2.3.2 Time Lags

We created lag features to capture how past trip counts affect future trips. We also added holiday information and checked how well the lagged values are related to current trip counts. This helps the model better predict busy and slow periods.

ride.panel <- 
  ride.panel %>% 
  arrange(Start.station.number, 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))
## # A tibble: 6 × 2
##   Variable   correlation
##   <fct>            <dbl>
## 1 lagHour           0.82
## 2 lag2Hours         0.54
## 3 lag3Hours         0.31
## 4 lag4Hours         0.14
## 5 lag12Hours       -0.33
## 6 lag1day           0.81

3. Model Training and Validation

3.1 Data splitting

The data is split to ensure a time based split.

ride.panel <- ride.panel %>%
  mutate(week = week(interval60))

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

Creating differnet Regression models (reg1 to reg5)

reg1 <-
  lm(Trip_Count ~  factor(hour(interval60)) + factor(dotw) + Temperature,  data=ride.Train)
 
reg2 <-
  lm(Trip_Count ~  Start.station.number +  factor(dotw)+ Temperature,  data=ride.Train)
 
reg3 <-
  lm(Trip_Count ~  Start.station.number + factor(hour(interval60)) + factor(dotw) + Temperature + Precipitation,
     data=ride.Train)
 
reg4 <-
  lm(Trip_Count ~  Start.station.number +  factor(hour(interval60)) +  factor(dotw) + Temperature + Precipitation +
                   lagHour + lag2Hours +lag3Hours + lag12Hours + lag1day,
     data=ride.Train)
 
reg5 <-
  lm(Trip_Count ~  Start.station.number + factor(hour(interval60)) +  factor(dotw) + Temperature + Precipitation +
                   lagHour + lag2Hours +lag3Hours +lag12Hours + lag1day + holidayLag + holiday,
     data=ride.Train)

Five different regression models were created using different sets of features like time of day, weather, past trip counts (lags), and holidays. Each model builds on the previous one by adding more detailed information.

3.2 Predict using nested structure

ride.Test.weekNest <- 
  ride.Test %>%
  nest(-week) 

The test dataset was nested by week so that predictions could be made separately for each week. This helps in organizing and comparing model performance across different time periods.

model_pred <- function(dat, fit){
   pred <- predict(fit, newdata = dat)}

Each model was used to predict trip counts on the test data. The actual and predicted values were compared to calculate errors like Mean Absolute Error (MAE) and standard deviation of errors for each model and week to understand which is the best model.

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))

week_predictions

4. Model Evaluation and Application to Rebalancing

4.1 Examine Error Metrics for Accuracy

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

Model 5 (ETime_Space_FE_timeLags_holidayLags) had the lowest Mean Absolute Error (MAE) across all test weeks. As we added more features like lags and holidays, the models got better at predicting demand. This shows that using past trip patterns and holiday effects helps improve forecast accuracy.

4.2 Predicted vs actual time series

week_predictions %>% 
    mutate(interval60 = map(data, pull, interval60),
           Start.station.number = map(data, pull, Start.station.number)) %>%
    dplyr::select(interval60, Start.station.number, Observed, Prediction, Regression) %>%
    unnest() %>%
    gather(Variable, Value, -Regression, -interval60, -Start.station.number) %>%
    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 = "Chicago; A test set of 2 weeks",  x = "Hour", y= "Station Trips") +
      plotTheme

The predicted trip counts closely follow the actual trip patterns across different models. Model 5 shows the best alignment with actual demand, especially during peak hours. However, there are still small gaps during sudden spikes, like on weekends or holidays.

4.3 MAE spatial error maps

week_predictions %>% 
    mutate(interval60 = map(data, pull, interval60),
           Start.station.number = map(data, pull, Start.station.number), 
           from_latitude = map(data, pull, from_latitude), 
           from_longitude = map(data, pull, from_longitude)) %>%
    select(interval60, Start.station.number, from_longitude, from_latitude, Observed, Prediction, Regression) %>%
    unnest() %>%
  filter(Regression == "ETime_Space_FE_timeLags_holidayLags") %>%
  group_by(Start.station.number, from_longitude, from_latitude) %>%
  summarize(MAE = mean(abs(Observed-Prediction), na.rm = TRUE))%>%
ggplot(.)+
  geom_sf(data = dcCensus, color = "grey", fill = "transparent")+
  geom_point(aes(x = from_longitude, y = from_latitude, color = MAE), 
             fill = "transparent", alpha = 0.4)+
  scale_colour_viridis(direction = -1,
  discrete = FALSE, option = "D")+
  ylim(min(dat_census$from_latitude), max(dat_census$from_latitude))+
  xlim(min(dat_census$from_longitude), max(dat_census$from_longitude))+
  labs(title="Mean Abs Error, Test Set, Model 5")+
  mapTheme

Errors are higher at some stations compared to others. Downtown and busy areas have lower prediction errors, while outlying areas show slightly higher errors. This means the model is better at predicting trips where there is regular and stable bike usage.

4.4 Observed vs Predicted

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

The model predicts weekday trips better than weekend trips, especially during AM and PM rush hours. On weekends, the predictions are slightly less accurate, with more scatter and bigger errors during mid-day and overnight periods. Overall, the model tends to slightly underpredict when the number of trips is very high.

4.5 Mean Absolute Errors Mapping

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

Prediction errors (MAE) are generally low across the city, staying mostly between 1 and 4 trips. Errors are slightly higher during mid-day and overnight, especially on weekends. The model performs better during AM and PM rush hours on weekdays, where trip patterns are more regular and predictable.

week_predictions %>% 
    mutate(interval60 = map(data, pull, interval60),
           Start.station.number = map(data, pull, Start.station.number), 
           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.number, 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.number, Percent_Taking_Public_Trans, Med_Inc, Percent_White) %>%
  summarize(MAE = mean(abs(Observed-Prediction), na.rm = TRUE))%>%
  gather(-Start.station.number, -MAE, key = "variable", value = "value")%>%
  ggplot(.)+
  #geom_sf(data = dcCensus, 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

Prediction errors are slightly higher in areas with higher median incomes and a higher percentage of White residents. There is not much change in error based on the percentage of people taking public transit. Overall, the model does a good job across different socio-economic groups, but small differences still exist.

4.6 Rebalancing Application

The model’s predictions can directly support a smarter bike rebalancing strategy across Washington, DC. Because the model forecasts demand 2 to 3 hours ahead, trucks can be sent to stations that are likely to run low on bikes or docking spaces before the problem actually happens. During weekday AM and PM rush hours, where the model predicts most accurately, truck routes can be scheduled confidently to refill or clear key commuter stations. On weekends and mid-day periods, where predictions are less stable, additional rider incentives (such as discounts or extra ride time) could help naturally move bikes without relying only on trucks.

The spatial error maps show that downtown stations are easier to predict, meaning rebalancing trucks should prioritize residential or edge areas that have slightly higher errors.

Socio-economic patterns also suggest that wealthier neighborhoods and areas with a higher percentage of White residents may have slightly higher uncertainty, so flexible truck routing or mobile response teams could be helpful there.

Overall, the model allows for an efficient rebalancing plan, minimizing empty docks and stranded bikes across both busy and quiet parts of the city.

5. Conclusion

This modeling approach successfully forecasts short-term bike share demand and supports smarter rebalancing decisions. The model captures daily patterns and rush hour peaks well, helping operators plan ahead and avoid service gaps. While it performed strongly during predictable periods, unexpected events like holidays and weekend travel patterns still presented challenges. Errors were slightly higher at less busy stations and in wealthier areas, suggesting the need for more localized adjustments. Going forward, improvements could include adding real-time station availability data, accounting for special city events, and testing more flexible machine learning models to better handle unpredictable demand shifts.

LS0tDQp0aXRsZTogIkFzc2lnbm1lbnQgNSAtIEJpa2UgU2hhcmUgRGVtYW5kIEZvcmVjYXN0aW5nIg0KYXV0aG9yOiAiQW5hdXNoa2EgR295YWwiDQpkYXRlOiAiQXByaWwgMTEsIDIwMjUiDQpvdXRwdXQ6IA0KICBodG1sX2RvY3VtZW50Og0KICAgIHRvYzogdHJ1ZQ0KICAgIHRvY19mbG9hdDogdHJ1ZQ0KICAgIGNvZGVfZG93bmxvYWQ6IHRydWUNCiAgICB0aGVtZTogImNvc21vIg0KICAgIGhpZ2hsaWdodDogInRhbmdvIg0KICAgIGRmX3ByaW50OiAicGFnZWQiDQogICAgZm9udDogIlJvYm90byINCi0tLQ0KDQojIDEuIEludHJvZHVjdGlvbiB0byBCaWtlIFNoYXJlIGFuZCBSZWJhbGFuY2luZyBQbGFuDQoNCg0KIyMgMS4xIEJpa3NoYXJlIFN5YXRlbXMtIENhcGl0YWwgQmlrZXNoYXJlIERDDQoNCkNhcGl0YWwgQmlrZXNoYXJlIGluIFdhc2hpbmd0b24sIERDIG1ha2VzIGl0IGVhc3kgZm9yIHBlb3BsZSB0byBncmFiIGEgYmlrZSwgcmlkZSB3aGVyZSB0aGV5IG5lZWQgdG8gZ28sIGFuZCBkcm9wIGl0IG9mZiBhdCBhbm90aGVyIHN0YXRpb24uIEl04oCZcyBhIHNpbXBsZSwgYWZmb3JkYWJsZSB3YXkgdG8gZ2V0IGFyb3VuZCB3aXRob3V0IG5lZWRpbmcgYSBjYXIuIEJpa2Utc2hhcmUgc3lzdGVtcyBsaWtlIHRoaXMgaGVscCBjdXQgZG93biB0cmFmZmljLCByZWR1Y2UgcG9sbHV0aW9uLCBhbmQgbWFrZSBpdCBlYXNpZXIgZm9yIGV2ZXJ5b25lIHRvIG1vdmUgYXJvdW5kIHRoZSBjaXR5Lg0KDQojIyAxLjIgT3BlcmF0aW9uYWwgQ2hhbGxlbmdlcyBGYWNlZCBieSBCaWtlLVNoYXJlIFN5c3RlbXM6DQoNCk9uZSBvZiB0aGUgYmlnZ2VzdCBjaGFsbGVuZ2VzIGZvciBiaWtlLXNoYXJlIHN5c3RlbXMgaXMgbWFraW5nIHN1cmUgdGhlcmUgYXJlIGFsd2F5cyBlbm91Z2ggYmlrZXMgdG8gcGljayB1cCBhbmQgZW5vdWdoIGVtcHR5IGRvY2tzIHRvIHJldHVybiBiaWtlcy4gSWYgYSBzdGF0aW9uIHJ1bnMgb3V0IG9mIGJpa2VzIG9yIGdldHMgY29tcGxldGVseSBmdWxsLCBpdCBiZWNvbWVzIGZydXN0cmF0aW5nIGZvciB1c2Vycy4gVG8ga2VlcCB0aGluZ3MgcnVubmluZyBzbW9vdGhseSwgYmlrZS1zaGFyZSBvcGVyYXRvcnMgaGF2ZSB0byBjb25zdGFudGx5IG1vdmUgYmlrZXMgYXJvdW5kIHRoZSBjaXR5IGJhc2VkIG9uIHdoZXJlIHRoZXkgdGhpbmsgcGVvcGxlIHdpbGwgbmVlZCB0aGVtIG5leHQuDQoNCiMjIDEuMyBSZWJhbGFuY2luZyBQcm9ibGVtDQoNClJlYmFsYW5jaW5nIGVuc3VyZSBiaWtlIGF2YWlsYWJpbGl0eSBhY3Jvc3MgYWxsIGRvY2tzIHNvIHRoYXQgZGVtYW5kIGNhbiBiZSBtZXQgd2l0aG91dCBhbnkgbGFncyBvciBoaWNjdXBzLiBJdCBpbnZvbHZlZCBtYW51YWxseSByZWRpc3RyaWJ1dGluZyBiaWtlcyBhY3Jvc3Mgc3RhdGlvbnMgdG8gZW5zdXJlIGJpa2UgYW5kIGRvY2sgYXZhaWxhYmlsaXR5LiBGb3IgdGhlIHNhbWUsIGl0IHJlcXVpcmVkIGFudGljaXBhdGluZyBkZW1hbmQgZm9yIGFsbCBkb2NrcyBhdCBhbGwgdGltZXMsIHdoaWNoIGlzIGEgcHJvYmxlbSBoZW5jZSBhZHZhbmNlZCBtb2RlbHMgdG8gYWNjdXJhdGVseSBwcmVkaWN0IGRlbWFuZCB1c2luZyBoaXN0b3JpY2FsIGRhdGEgYXJlIHVzZWQgaGVyZS4gVGhlIGZvbGxvd2luZyBtb2RlbCBhbHNvIGZvcmVjYXN0cyBzcGFjZS90aW1lIGRlbWFuZCBmb3IgYmlrZSBzaGFyZSBwaWNrdXAgaW4gV2FzaGluZ3RvbiBEQydzIENhcGl0YWwgQmlrZXNoYXJlLCB1c2luZyBNYXkgMjAxOSBkYXRhLiANCg0KIyMgMS40IFByb3Bvc2VkIFJlYmFsYW5jaW5nIFN0cmF0ZWd5Og0KSSBwbGFuIHRvIHVzZSBhIHNtYWxsIGZsZWV0IG9mIHRydWNrcyB0byBtb3ZlIGJpa2VzIGJldHdlZW4gc3RhdGlvbnMgYmFzZWQgb24gd2hlcmUgd2UgZXhwZWN0IGRlbWFuZCB0byBiZSBpbiB0aGUgbmV4dCAyIHRvIDMgaG91cnMuIFRoZSBwcmVkaWN0aW9ucyB1c2UgaW5mb3JtYXRpb24gbGlrZSB0aGUgdGltZSBvZiBkYXksIGRheSBvZiB0aGUgd2Vlaywgd2VhdGhlciAobGlrZSB0ZW1wZXJhdHVyZSBhbmQgcmFpbiksIGFuZCB3aGV0aGVyIGl0J3MgYSBob2xpZGF5LiBXZSBhbHNvIGxvb2sgYXQgaG93IGJ1c3kgZWFjaCBzdGF0aW9uIGhhcyBiZWVuIHJlY2VudGx5IGJ5IHVzaW5nIHBhc3QgdHJpcCBjb3VudHMuIFRoaXMgd2F5LCB3ZSBjYW4gc2VuZCB0cnVja3MgdG8gdGhlIHJpZ2h0IHBsYWNlcyBiZWZvcmUgc3RhdGlvbnMgcnVuIG91dCBvZiBiaWtlcyBvciBnZXQgdG9vIGZ1bGwuIE9uIHdlZWtlbmRzIG9yIGR1cmluZyBzcGVjaWFsIGV2ZW50cywgd2UgY291bGQgYWxzbyBvZmZlciBkaXNjb3VudHMgb3IgcmV3YXJkcyB0byByaWRlcnMgdG8gaGVscCBtb3ZlIGJpa2VzIHdoZW4gcHJlZGljdGlvbnMgYXJlIGxlc3MgY2VydGFpbi4NCg0KYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9DQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUpDQpgYGANCg0KIyMgMS41IExvYWQgTGlicmFyaWVzIA0KYGBge3Igc2V0dXBfMTMsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGNhY2hlPVRSVUUsIHJlc3VsdHMgPSAnaGlkZSd9DQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkoc2YpDQpsaWJyYXJ5KGx1YnJpZGF0ZSkNCmxpYnJhcnkodGlncmlzKQ0KbGlicmFyeSh0aWR5Y2Vuc3VzKQ0KbGlicmFyeSh2aXJpZGlzKQ0KbGlicmFyeShyaWVtKQ0KbGlicmFyeShncmlkRXh0cmEpDQpsaWJyYXJ5KGtuaXRyKQ0KbGlicmFyeShrYWJsZUV4dHJhKQ0KbGlicmFyeShSU29jcmF0YSkNCmxpYnJhcnkoZHBseXIpDQoNCnBsb3RUaGVtZSA8LSB0aGVtZSgNCiAgcGxvdC50aXRsZSA9ZWxlbWVudF90ZXh0KHNpemU9MTIpLA0KICBwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemU9OCksDQogIHBsb3QuY2FwdGlvbiA9IGVsZW1lbnRfdGV4dChzaXplID0gNiksDQogIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCwgYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSwNCiAgYXhpcy50ZXh0LnkgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSwNCiAgYXhpcy50aXRsZS55ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCksDQogICMgU2V0IHRoZSBlbnRpcmUgY2hhcnQgcmVnaW9uIHRvIGJsYW5rDQogIHBhbmVsLmJhY2tncm91bmQ9ZWxlbWVudF9ibGFuaygpLA0KICBwbG90LmJhY2tncm91bmQ9ZWxlbWVudF9ibGFuaygpLA0KICAjcGFuZWwuYm9yZGVyPWVsZW1lbnRfcmVjdChjb2xvdXI9IiNGMEYwRjAiKSwNCiAgIyBGb3JtYXQgdGhlIGdyaWQNCiAgcGFuZWwuZ3JpZC5tYWpvcj1lbGVtZW50X2xpbmUoY29sb3VyPSIjRDBEMEQwIixzaXplPS4yKSwNCiAgYXhpcy50aWNrcz1lbGVtZW50X2JsYW5rKCkpDQoNCm1hcFRoZW1lIDwtIHRoZW1lKHBsb3QudGl0bGUgPWVsZW1lbnRfdGV4dChzaXplPTEyKSwNCiAgICAgICAgICAgICAgICAgIHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZT04KSwNCiAgICAgICAgICAgICAgICAgIHBsb3QuY2FwdGlvbiA9IGVsZW1lbnRfdGV4dChzaXplID0gNiksDQogICAgICAgICAgICAgICAgICBheGlzLmxpbmU9ZWxlbWVudF9ibGFuaygpLA0KICAgICAgICAgICAgICAgICAgYXhpcy50ZXh0Lng9ZWxlbWVudF9ibGFuaygpLA0KICAgICAgICAgICAgICAgICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF9ibGFuaygpLA0KICAgICAgICAgICAgICAgICAgYXhpcy50aWNrcz1lbGVtZW50X2JsYW5rKCksDQogICAgICAgICAgICAgICAgICBheGlzLnRpdGxlLng9ZWxlbWVudF9ibGFuaygpLA0KICAgICAgICAgICAgICAgICAgYXhpcy50aXRsZS55PWVsZW1lbnRfYmxhbmsoKSwNCiAgICAgICAgICAgICAgICAgIHBhbmVsLmJhY2tncm91bmQ9ZWxlbWVudF9ibGFuaygpLA0KICAgICAgICAgICAgICAgICAgcGFuZWwuYm9yZGVyPWVsZW1lbnRfYmxhbmsoKSwNCiAgICAgICAgICAgICAgICAgIHBhbmVsLmdyaWQubWFqb3I9ZWxlbWVudF9saW5lKGNvbG91ciA9ICd0cmFuc3BhcmVudCcpLA0KICAgICAgICAgICAgICAgICAgcGFuZWwuZ3JpZC5taW5vcj1lbGVtZW50X2JsYW5rKCksDQogICAgICAgICAgICAgICAgICBsZWdlbmQuZGlyZWN0aW9uID0gInZlcnRpY2FsIiwgDQogICAgICAgICAgICAgICAgICBsZWdlbmQucG9zaXRpb24gPSAicmlnaHQiLA0KICAgICAgICAgICAgICAgICAgcGxvdC5tYXJnaW4gPSBtYXJnaW4oMSwgMSwgMSwgMSwgJ2NtJyksDQogICAgICAgICAgICAgICAgICBsZWdlbmQua2V5LmhlaWdodCA9IHVuaXQoMSwgImNtIiksIGxlZ2VuZC5rZXkud2lkdGggPSB1bml0KDAuMiwgImNtIikpDQoNCnBhbGV0dGU1IDwtIGMoIiNmZWU1ZDkiLCAiI2ZjYWU5MSIsICIjZmI2YTRhIiwgIiNkZTJkMjYiLCAiI2E1MGYxNSIpDQpwYWxldHRlNCA8LSBjKCIjZmVlMGQyIiwgIiNmYzkyNzIiLCAiI2RlMmQyNiIsICIjYTUwZjE1IikNCnBhbGV0dGUyIDwtIGMoIiNmYjZhNGEiLCAiI2E1MGYxNSIpDQoNCmBgYA0KDQoNCiMjIDEuNiBJbnN0YWxsIENlbnN1cyBBUEkgS2V5DQpgYGB7ciBpbnN0YWxsX2NlbnN1c19BUElfa2V5LCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBjYWNoZT1UUlVFLCByZXN1bHRzID0gJ2hpZGUnLCBldmFsID0gVFJVRX0NCiMgSW5zdGFsbCBDZW5zdXMgQVBJIEtleQ0KdGlkeWNlbnN1czo6Y2Vuc3VzX2FwaV9rZXkoIjAyNTI0OTEyMDgxODAxMDY4ZjA0ZjI4NmEwODZkMTllOWZjNjQxY2EiLCBvdmVyd3JpdGUgPSBUUlVFKQ0KYGBgDQoNCiMjIDEuNyBMb2FkIGFuZCBDbGVhbiBCaWtlIGFuZCBTdGF0aW9uIERhdGENCmBgYHtyIHJlYWRfZGF0LCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBjYWNoZT1UUlVFLCByZXN1bHRzID0gJ2hpZGUnIH0NCg0KZGF0X2Jpa2UgPC0gc3RfcmVhZCgiQ2FwaXRhbF9CaWtlc2hhcmVfTG9jYXRpb25zLmdlb2pzb24iKQ0KZGF0X2JpX3dhIDwtIHN0X3JlYWQoIjIwMTkwNS1jYXBpdGFsYmlrZXNoYXJlLXRyaXBkYXRhLmNzdiIpDQoNCmdsaW1wc2UoZGF0X2Jpa2UpICNzcGF0aWFsIHN0YXRpb24gZGF0YQ0KZ2xpbXBzZShkYXRfYmlfd2EpICN0cmlwcyBkYXRhDQoNCnN0YXJ0X3N0IDwtIGRhdF9iaWtlICU+JSANCiAgc2VsZWN0KE5BTUUsIGdlb21ldHJ5KQ0KDQplbmRfc3Q8LSBkYXRfYmlrZSAlPiUgDQogIHNlbGVjdChOQU1FLCBnZW9tZXRyeSkNCg0KIyNtYWtpbmcgZGF0YSBzcGF0aWFsIGJ5IGFkZGluZyBwb2ludCBkYXRhIG9mIHN0YXJ0IGFuZCBlbmQgc3RhdGlvbiB0byB0aGUgdHJpcHMgZGF0YQ0KDQpkYXQgPC0gZGF0X2JpX3dhICU+JSANCiAgbGVmdF9qb2luKHN0YXJ0X3N0LCBieSA9IGMoIlN0YXJ0LnN0YXRpb24iID0gIk5BTUUiKSkgJT4lIA0KICByZW5hbWUoc3RhcnRfZ2VvID0gZ2VvbWV0cnkpICU+JSANCiAgbGVmdF9qb2luKGVuZF9zdCwgYnkgPSBjKCJFbmQuc3RhdGlvbiIgPSAiTkFNRSIpKSAlPiUgDQogIHJlbmFtZShlbmRfZ2VvID0gZ2VvbWV0cnkpDQoNCmRhdCAlPiUgIyNDaGVja2luZyBmb3IgbWlzc2luZyB2YWx1ZXMNCiAgc3VtbWFyaXNlX2FsbCh+IHN1bShpcy5uYSguKSkpICU+JQ0KICBnYXRoZXIoa2V5ID0gInZhcmlhYmxlIiwgdmFsdWUgPSAibnVtX21pc3NpbmciKSAlPiUNCiAgZmlsdGVyKG51bV9taXNzaW5nID4gMCkgJT4lDQogIGFycmFuZ2UoZGVzYyhudW1fbWlzc2luZykpICU+JQ0KICBrYWJsZShjYXB0aW9uID0gIk51bWJlciBvZiBNaXNzaW5nIFZhbHVlcyBwZXIgQ29sdW1uIikgJT4lDQogIGthYmxlX3N0eWxpbmcoZnVsbF93aWR0aCA9IEZBTFNFKQ0KDQoNCiMgT3V0bGllciBkZXRlY3Rpb246IFRyaXAgZHVyYXRpb24NCmRhdCAlPiUNCiAgbXV0YXRlKER1cmF0aW9uID0gYXMubnVtZXJpYyhEdXJhdGlvbikpICU+JQ0KICBnZ3Bsb3QoYWVzKHggPSBEdXJhdGlvbikpICsNCiAgZ2VvbV9oaXN0b2dyYW0oYmlud2lkdGggPSA2MCwgZmlsbCA9ICIjYTUwZjE1IiwgY29sb3IgPSAiI2ZjYWU5MSIpICsNCiAgbGFicyh0aXRsZSA9ICJEaXN0cmlidXRpb24gb2YgVHJpcCBEdXJhdGlvbnMgKHNlY29uZHMpIiwgeCA9ICJUcmlwIER1cmF0aW9uIiwgeSA9ICJDb3VudCIpICsNCiAgcGxvdFRoZW1lDQoNCiMgUmVtb3ZlIGVudHJpZXMgd2l0aCBtaXNzaW5nIHN0YXRpb24gbmFtZXMgb3IgZXh0cmVtZWx5IGxvbmcgdHJpcHMgKGxpa2VseSBlcnJvcnMpDQpkYXQgPC0gZGF0ICU+JQ0KICBmaWx0ZXIoIWlzLm5hKFN0YXJ0LnN0YXRpb24pICYgIWlzLm5hKEVuZC5zdGF0aW9uKSkgJT4lDQogIGZpbHRlcihEdXJhdGlvbiA+IDYwICYgRHVyYXRpb24gPCA3MjAwKSAgIyBLZWVwaW5nIHRyaXBzIGJldHdlZW4gMSBtaW51dGUgYW5kIDIgaG91cnMNCg0KYGBgDQoNCiMgMi4gIERhdGEgUHJlcHJvY2Vzc2luZyBhbmQgRmVhdHVyZSBFbmdpbmVlcmluZw0KDQojIyAyLjEgRmVhdHVyZSBFbmdpbmVlcmluZw0KDQojIyMgMi4xLjEgVGltZSBCaW5zDQpDcmVhdGluZyAxNSBhbmQgNjAgbWludXRlIGludGVydmFscyBieSByb3VuZGluZy4gDQoNCmBgYHtyIHRpbWVfYmlucywgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgY2FjaGU9VFJVRX0NCmRhdDIgPC0gZGF0ICU+JQ0KICBtdXRhdGUoDQogICAgaW50ZXJ2YWw2MCA9IGZsb29yX2RhdGUoeW1kX2htcyhTdGFydC5kYXRlKSwgdW5pdCA9ICJob3VyIiksDQogICAgaW50ZXJ2YWwxNSA9IGZsb29yX2RhdGUoeW1kX2htcyhTdGFydC5kYXRlKSwgdW5pdCA9ICIxNSBtaW5zIiksDQogICAgd2VlayA9IHdlZWsoaW50ZXJ2YWw2MCksDQogICAgZG90dyA9IHdkYXkoaW50ZXJ2YWw2MCwgbGFiZWwgPSBUUlVFKQ0KICApDQoNCmdsaW1wc2UoZGF0MikNCmBgYA0KIyMjIDIuMS4yIENlbnN1cyBEYXRhIA0KDQpHZXR0aW5nIHZhcmlhYmxlcyBpbmZsdWVuY2luZyBkZW1hbmQgZnJvbSBjZW5zdXMgZGF0YS0gDQoNCmBgYHtyIGdldF9jZW5zdXMsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGNhY2hlPVRSVUUsIHJlc3VsdHMgPSAnaGlkZSd9DQpkY0NlbnN1cyA8LSANCiAgZ2V0X2FjcyhnZW9ncmFwaHkgPSAidHJhY3QiLCANCiAgICAgICAgICB2YXJpYWJsZXMgPSBjKCJCMDEwMDNfMDAxIiwgIkIxOTAxM18wMDEiLCANCiAgICAgICAgICAgICAgICAgICAgICAgICJCMDIwMDFfMDAyIiwgIkIwODAxM18wMDEiLA0KICAgICAgICAgICAgICAgICAgICAgICAgIkIwODAxMl8wMDEiLCAiQjA4MzAxXzAwMSIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgIkIwODMwMV8wMTAiLCAiQjAxMDAyXzAwMSIpLCANCiAgICAgICAgICB5ZWFyID0gMjAxNywgDQogICAgICAgICAgc3RhdGUgPSAiREMiLCAgICMg4oaQIGNoYW5nZWQgdG8gREMNCiAgICAgICAgICBnZW9tZXRyeSA9IFRSVUUsIA0KICAgICAgICAgIGNvdW50eSA9IE5VTEwsICAjIOKGkCBEQyBpcyBhIHN0YXRlICsgY2l0eSwgbm8gY291bnR5IG5lZWRlZA0KICAgICAgICAgIG91dHB1dCA9ICJ3aWRlIikgJT4lDQogIHJlbmFtZShUb3RhbF9Qb3AgPSAgQjAxMDAzXzAwMUUsDQogICAgICAgICBNZWRfSW5jID0gQjE5MDEzXzAwMUUsDQogICAgICAgICBNZWRfQWdlID0gQjAxMDAyXzAwMUUsDQogICAgICAgICBXaGl0ZV9Qb3AgPSBCMDIwMDFfMDAyRSwNCiAgICAgICAgIFRyYXZlbF9UaW1lID0gQjA4MDEzXzAwMUUsDQogICAgICAgICBOdW1fQ29tbXV0ZXJzID0gQjA4MDEyXzAwMUUsDQogICAgICAgICBNZWFuc19vZl9UcmFuc3BvcnQgPSBCMDgzMDFfMDAxRSwNCiAgICAgICAgIFRvdGFsX1B1YmxpY19UcmFucyA9IEIwODMwMV8wMTBFKSAlPiUNCiAgc2VsZWN0KFRvdGFsX1BvcCwgTWVkX0luYywgV2hpdGVfUG9wLCBUcmF2ZWxfVGltZSwNCiAgICAgICAgIE1lYW5zX29mX1RyYW5zcG9ydCwgVG90YWxfUHVibGljX1RyYW5zLA0KICAgICAgICAgTWVkX0FnZSwNCiAgICAgICAgIEdFT0lELCBnZW9tZXRyeSkgJT4lDQogIG11dGF0ZShQZXJjZW50X1doaXRlID0gV2hpdGVfUG9wIC8gVG90YWxfUG9wLA0KICAgICAgICAgTWVhbl9Db21tdXRlX1RpbWUgPSBUcmF2ZWxfVGltZSAvIFRvdGFsX1B1YmxpY19UcmFucywNCiAgICAgICAgIFBlcmNlbnRfVGFraW5nX1B1YmxpY19UcmFucyA9IFRvdGFsX1B1YmxpY19UcmFucyAvIE1lYW5zX29mX1RyYW5zcG9ydCkNCg0KYGBgDQoNCkV4dHJhY3QgR2VvbWV0cmllcy0gDQoNCmBgYHtyIGV4dHJhY3RfZ2VvbWV0cmllcywgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgY2FjaGU9VFJVRX0NCmRjVHJhY3RzIDwtIA0KICBkY0NlbnN1cyAlPiUNCiAgYXMuZGF0YS5mcmFtZSgpICU+JQ0KICBkaXN0aW5jdChHRU9JRCwgLmtlZXBfYWxsID0gVFJVRSkgJT4lDQogIHNlbGVjdChHRU9JRCwgZ2VvbWV0cnkpICU+JSANCiAgc3Rfc2YoKQ0KDQpgYGANCg0KQWRkaW5nIHRoZSBzcGF0aWFsIGluZm9ybWF0aW9uIHRvIG91ciByaWRlc2hhcmUgZGF0YSBhcyBvcmlnaW4gYW5kIGRlc3RpbmF0aW9uIGRhdGEtIA0KYGBge3IgYWRkX2NlbnN1c190cmFjdHMgLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBjYWNoZT1UUlVFfQ0KDQpkYXQyIDwtIGRhdDIgJT4lDQogIG11dGF0ZSgNCiAgICBmcm9tX2xvbmdpdHVkZSA9IHN0X2Nvb3JkaW5hdGVzKHN0YXJ0X2dlbylbLCAxXSwNCiAgICBmcm9tX2xhdGl0dWRlID0gc3RfY29vcmRpbmF0ZXMoc3RhcnRfZ2VvKVssIDJdLA0KICAgIHRvX2xvbmdpdHVkZSA9IHN0X2Nvb3JkaW5hdGVzKGVuZF9nZW8pWywgMV0sDQogICAgdG9fbGF0aXR1ZGUgPSBzdF9jb29yZGluYXRlcyhlbmRfZ2VvKVssIDJdDQogICkNCg0KDQpkYXRfY2Vuc3VzIDwtIGRhdDIgJT4lDQogIGZpbHRlcighaXMubmEoZnJvbV9sb25naXR1ZGUpICYNCiAgICAgICAgICFpcy5uYShmcm9tX2xhdGl0dWRlKSAmDQogICAgICAgICAhaXMubmEodG9fbG9uZ2l0dWRlKSAmDQogICAgICAgICAhaXMubmEodG9fbGF0aXR1ZGUpKSAlPiUNCiAgc3RfYXNfc2YoY29vcmRzID0gYygiZnJvbV9sb25naXR1ZGUiLCAiZnJvbV9sYXRpdHVkZSIpLCBjcnMgPSA0MzI2KSAlPiUNCiAgc3Rfam9pbigNCiAgICBkY1RyYWN0cyAlPiUgc3RfdHJhbnNmb3JtKGNycyA9IDQzMjYpLA0KICAgIGpvaW4gPSBzdF9pbnRlcnNlY3RzLA0KICAgIGxlZnQgPSBUUlVFDQogICkgJT4lDQogIHJlbmFtZShPcmlnaW4uVHJhY3QgPSBHRU9JRCkgJT4lDQogIG11dGF0ZSgNCiAgICBmcm9tX2xvbmdpdHVkZSA9IHVubGlzdChtYXAoZ2VvbWV0cnksIDEpKSwNCiAgICBmcm9tX2xhdGl0dWRlID0gdW5saXN0KG1hcChnZW9tZXRyeSwgMikpDQogICkgJT4lDQogIGFzLmRhdGEuZnJhbWUoKSAlPiUNCiAgc2VsZWN0KC1nZW9tZXRyeSkgJT4lDQogIHN0X2FzX3NmKGNvb3JkcyA9IGMoInRvX2xvbmdpdHVkZSIsICJ0b19sYXRpdHVkZSIpLCBjcnMgPSA0MzI2KSAlPiUNCiAgc3Rfam9pbigNCiAgICBkY1RyYWN0cyAlPiUgc3RfdHJhbnNmb3JtKGNycyA9IDQzMjYpLA0KICAgIGpvaW4gPSBzdF9pbnRlcnNlY3RzLA0KICAgIGxlZnQgPSBUUlVFDQogICkgJT4lDQogIHJlbmFtZShEZXN0aW5hdGlvbi5UcmFjdCA9IEdFT0lEKSAlPiUNCiAgbXV0YXRlKA0KICAgIHRvX2xvbmdpdHVkZSA9IHVubGlzdChtYXAoZ2VvbWV0cnksIDEpKSwNCiAgICB0b19sYXRpdHVkZSA9IHVubGlzdChtYXAoZ2VvbWV0cnksIDIpKQ0KICApICU+JQ0KICBhcy5kYXRhLmZyYW1lKCkgJT4lDQogIHNlbGVjdCgtZ2VvbWV0cnkpDQoNCmBgYA0KDQojIyMgMi4xLjMgV2VhdGhlciBEYXRhIA0KDQpgYGB7ciBpbXBvcnRfd2VhdGhlciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgY2FjaGU9VFJVRSB9DQp3ZWF0aGVyLlBhbmVsIDwtIA0KICByaWVtX21lYXN1cmVzKHN0YXRpb24gPSAiRENBIiwgZGF0ZV9zdGFydCA9ICIyMDE5LTA1LTAxIiwgZGF0ZV9lbmQgPSAiMjAxOS0wNS0zMSIpICU+JSANCiAgZHBseXI6OnNlbGVjdCh2YWxpZCwgdG1wZiwgcDAxaSwgc2tudCkgJT4lDQogIHJlcGxhY2UoaXMubmEoLiksIDApICU+JQ0KICBtdXRhdGUoaW50ZXJ2YWw2MCA9IHltZF9oKHN1YnN0cih2YWxpZCwgMSwgMTMpKSkgJT4lDQogIG11dGF0ZSgNCiAgICB3ZWVrID0gd2VlayhpbnRlcnZhbDYwKSwNCiAgICBkb3R3ID0gd2RheShpbnRlcnZhbDYwLCBsYWJlbCA9IFRSVUUpDQogICkgJT4lDQogIGdyb3VwX2J5KGludGVydmFsNjApICU+JQ0KICBzdW1tYXJpemUoDQogICAgVGVtcGVyYXR1cmUgPSBtYXgodG1wZiksDQogICAgUHJlY2lwaXRhdGlvbiA9IHN1bShwMDFpKSwNCiAgICBXaW5kX1NwZWVkID0gbWF4KHNrbnQpDQogICkgJT4lDQogIG11dGF0ZSgNCiAgICBUZW1wZXJhdHVyZSA9IGlmZWxzZShUZW1wZXJhdHVyZSA9PSAwLCA0MiwgVGVtcGVyYXR1cmUpDQogICkNCg0KDQpnbGltcHNlKHdlYXRoZXIuUGFuZWwpDQoNCmBgYA0KDQpQbG90dGluZyBXZWF0aGVyIERhdGEgZm9yIFdhc2hpbmd0b24gREMtIA0KDQpgYGB7ciBwbG90X3dlYXRoZXIsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGNhY2hlPVRSVUV9DQpncmlkLmFycmFuZ2UoDQogIGdncGxvdCh3ZWF0aGVyLlBhbmVsLCBhZXMoaW50ZXJ2YWw2MCxQcmVjaXBpdGF0aW9uKSkgKyBnZW9tX2xpbmUoKSArIA0KICBsYWJzKHRpdGxlPSJQZXJjaXBpdGF0aW9uIiwgeD0iSG91ciIsIHk9IlBlcmVjaXBpdGF0aW9uIikgKyBwbG90VGhlbWUsDQogIGdncGxvdCh3ZWF0aGVyLlBhbmVsLCBhZXMoaW50ZXJ2YWw2MCxXaW5kX1NwZWVkKSkgKyBnZW9tX2xpbmUoKSArIA0KICAgIGxhYnModGl0bGU9IldpbmQgU3BlZWQiLCB4PSJIb3VyIiwgeT0iV2luZCBTcGVlZCIpICsgcGxvdFRoZW1lLA0KICBnZ3Bsb3Qod2VhdGhlci5QYW5lbCwgYWVzKGludGVydmFsNjAsVGVtcGVyYXR1cmUpKSArIGdlb21fbGluZSgpICsgDQogICAgbGFicyh0aXRsZT0iVGVtcGVyYXR1cmUiLCB4PSJIb3VyIiwgeT0iVGVtcGVyYXR1cmUiKSArIHBsb3RUaGVtZSwNCiAgdG9wPSJXZWF0aGVyIERhdGEgLSBXYXNoaW5ndG9uIERDIC0gTWF5LCAyMDE5IikNCmBgYA0KDQojIyAyLjIgIEV4cGxvcmF0b3J5IGRhdGEgYW5hbHlzaXMNCg0KIyMjIDIuMi4xIEJpa2Ugc2hhcmUgdHJpcHMgcGVyIGhyLiBXYXNoaW5ndG9uIERDIGZvciBhbGwgb2YgTWF5LCAyMDE5IA0KDQpgYGB7ciB0cmlwX3RpbWVzZXJpZXMsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGNhY2hlPVRSVUUgfQ0KZ2dwbG90KGRhdF9jZW5zdXMgJT4lDQogICAgICAgICBncm91cF9ieShpbnRlcnZhbDYwKSAlPiUNCiAgICAgICAgIHRhbGx5KCkpKw0KICBnZW9tX2xpbmUoYWVzKHggPSBpbnRlcnZhbDYwLCB5ID0gbikpKw0KICBsYWJzKHRpdGxlPSJCaWtlIHNoYXJlIHRyaXBzIHBlciBoci4gV2FzaGluZ3RvbiBEQywgTWF5LCAyMDE5IiwNCiAgICAgICB4PSJEYXRlIiwgDQogICAgICAgeT0iTnVtYmVyIG9mIHRyaXBzIikrDQogIHBsb3RUaGVtZQ0KYGBgDQoNClRoZSBhYm92ZSBncmFwaCBzaG93cyB0aGUgZGVtYW5kIHBhdHRlcm4gaW4gREMgZHVyaW5nIHRoZSA0IHdlZWtzIG9mIE1heSwgd2l0aCBoaWdoIHJpZGVyc2hpcCBkdXJpbmcgdGhlIHdlZWsgYW5kIGdlbmVyYWwgbG93IHJpZGVzaGlwIGR1cmluZyB3ZWVrZW5kcy4gVGhlIGdyYXBoIGFsc28gc2hvd3MgYSBiaWcgZmx1Y3R1YXRpb24gaW4gZGVtYW5kIGR1cmluZyB0aGUgd2VlayB3aXRoIGxvd2VyIGRlbWFuZCBvbiBNb25kYXlzIGFuZCBoaWdoZXIgZGVtYW5kIGR1cmluZyB0aGUgd2Vlay4gDQoNCiMjIyAyLjIuMiBBbmFseXNpbmcgTWVhbiBOdW1iZXIgb2YgSG91cmx5IFRyaXBzIFBlciBTdGF0aW9uDQoNCmBgYHtyIG1lYW5fdHJpcHNfaGlzdCwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgY2FjaGU9VFJVRSB9DQpkYXRfY2Vuc3VzICU+JQ0KICAgICAgICBtdXRhdGUodGltZV9vZl9kYXkgPSBjYXNlX3doZW4oaG91cihpbnRlcnZhbDYwKSA8IDcgfCBob3VyKGludGVydmFsNjApID4gMTggfiAiT3Zlcm5pZ2h0IiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhvdXIoaW50ZXJ2YWw2MCkgPj0gNyAmIGhvdXIoaW50ZXJ2YWw2MCkgPCAxMCB+ICJBTSBSdXNoIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhvdXIoaW50ZXJ2YWw2MCkgPj0gMTAgJiBob3VyKGludGVydmFsNjApIDwgMTUgfiAiTWlkLURheSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBob3VyKGludGVydmFsNjApID49IDE1ICYgaG91cihpbnRlcnZhbDYwKSA8PSAxOCB+ICJQTSBSdXNoIikpJT4lDQogICAgICAgICBncm91cF9ieShpbnRlcnZhbDYwLCBTdGFydC5zdGF0aW9uLCB0aW1lX29mX2RheSkgJT4lDQogICAgICAgICB0YWxseSgpJT4lDQogIGdyb3VwX2J5KFN0YXJ0LnN0YXRpb24sIHRpbWVfb2ZfZGF5KSU+JQ0KICBzdW1tYXJpemUobWVhbl90cmlwcyA9IG1lYW4obikpJT4lDQogIGdncGxvdCgpKw0KICBnZW9tX2hpc3RvZ3JhbShhZXMobWVhbl90cmlwcyksIGJpbndpZHRoID0gMSkrDQogIGxhYnModGl0bGU9Ik1lYW4gTnVtYmVyIG9mIEhvdXJseSBUcmlwcyBQZXIgU3RhdGlvbi4gV2FzaGluZ3RvbiwgTWF5LCAyMDE5IiwNCiAgICAgICB4PSJOdW1iZXIgb2YgdHJpcHMiLCANCiAgICAgICB5PSJGcmVxdWVuY3kiKSsNCiAgZmFjZXRfd3JhcCh+dGltZV9vZl9kYXkpKw0KICBwbG90VGhlbWUNCmBgYA0KDQpUaGUgYWJvdmUgZ3JhcGggY2xlYXJseSBzaG93cyB0aGF0IERDIGhhcyBoaWdoZXIgQU0gYW5kIE92ZXJuaWdodCBydXNoIGFuZCBsb3dlciBNaWQtZGF5IGFuZCBQTSBydXNoIGluIHRoZSBjaXR5LiANCg0KYGBge3IgdHJpcHNfc3RhdGlvbl9kb3R3LCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBjYWNoZT1UUlVFIH0NCmdncGxvdChkYXRfY2Vuc3VzICU+JQ0KICAgICAgICAgZ3JvdXBfYnkoaW50ZXJ2YWw2MCwgU3RhcnQuc3RhdGlvbikgJT4lDQogICAgICAgICB0YWxseSgpKSsNCiAgZ2VvbV9oaXN0b2dyYW0oYWVzKG4pLCBiaW53aWR0aCA9IDUpKw0KICBsYWJzKHRpdGxlPSJCaWtlIHNoYXJlIHRyaXBzIHBlciBociBieSBzdGF0aW9uLiBXYXNoaW5ndG9uLCBNYXksIDIwMTkiLA0KICAgICAgIHg9IlRyaXAgQ291bnRzIiwgDQogICAgICAgeT0iTnVtYmVyIG9mIFN0YXRpb25zIikrDQogIHBsb3RUaGVtZQ0KYGBgDQpUaGlzIGdyYXBoIHNob3cgdGhhdCBtb3N0IHN0YXRpb25zIGhhdmUgdmVyeSBsb3cgdHJpcCBjb3VudHMgcGVyIGhvdXIgKG5lYXIgMCB0byAyIHRyaXBzKS5BIHNtYWxsZXIgbnVtYmVyIG9mIHN0YXRpb25zIGhhdmUgbWVkaXVtIGFjdGl2aXR5ICg14oCTMTAgdHJpcHMpIGFuZCB2ZXJ5IGZldyBzdGF0aW9ucyBoYXZlIGhpZ2ggdHJpcCBhY3Rpdml0eSAoMTUrLCAyMCssIGFsbW9zdCBub25lIHBhc3QgMzAgdHJpcHMpLkZyb20gdGhpcyB3ZSBjYW4gY29uY2x1ZGUgdGhhdCBiaWtlIHNoYXJlIHVzYWdlIGlzIHZlcnkgdW5ldmVuLkEgZmV3IHBvcHVsYXIgc3RhdGlvbnMgKG5lYXIgZG93bnRvd24sIG9mZmljZXMsIG1ldHJvIHN0b3BzKSBnZXQgdG9ucyBvZiB0cmlwcy4gTW9zdCBzdGF0aW9ucyAoZXNwZWNpYWxseSByZXNpZGVudGlhbCBvciBzdWJ1cmJhbiBvbmVzKSBhcmUgcXVpZXQgbW9zdCBvZiB0aGUgZGF5Lg0KDQoNCmBgYHtyIHRyaXBzX2hvdXJfZG90dywgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgY2FjaGU9VFJVRSB9DQpnZ3Bsb3QoZGF0X2NlbnN1cyAlPiUgbXV0YXRlKGhvdXIgPSBob3VyKFN0YXJ0LmRhdGUpKSkrDQogICAgIGdlb21fZnJlcXBvbHkoYWVzKGhvdXIsIGNvbG9yID0gZG90dyksIGJpbndpZHRoID0gMSkrDQogIGxhYnModGl0bGU9IkJpa2Ugc2hhcmUgdHJpcHMgaW4gV2FzaGluZ3RvbiwgYnkgZGF5IG9mIHRoZSB3ZWVrLCBNYXksIDIwMTkiLA0KICAgICAgIHg9IkhvdXIiLCANCiAgICAgICB5PSJUcmlwIENvdW50cyIpKw0KICAgICBwbG90VGhlbWUNCg0KDQpnZ3Bsb3QoZGF0X2NlbnN1cyAlPiUgDQogICAgICAgICBtdXRhdGUoaG91ciA9IGhvdXIoU3RhcnQuZGF0ZSksDQogICAgICAgICAgICAgICAgd2Vla2VuZCA9IGlmZWxzZShkb3R3ICVpbiUgYygiU3VuIiwgIlNhdCIpLCAiV2Vla2VuZCIsICJXZWVrZGF5IikpKSsNCiAgICAgZ2VvbV9mcmVxcG9seShhZXMoaG91ciwgY29sb3IgPSB3ZWVrZW5kKSwgYmlud2lkdGggPSAxKSsNCiAgbGFicyh0aXRsZT0iQmlrZSBzaGFyZSB0cmlwcyBpbiBXYXNoaW5ndG9uIC0gd2Vla2VuZCB2cyB3ZWVrZGF5LCBNYXksIDIwMTgiLA0KICAgICAgIHg9IkhvdXIiLCANCiAgICAgICB5PSJUcmlwIENvdW50cyIpKw0KICAgICBwbG90VGhlbWUNCmBgYA0KDQpGb2xsb3dpbmcgdXAgZnJvbSB0aGUgYWJvdmUgZ3JhcGgsIGl0IGlzIGNsZWFyIHRoYXQgb2ZmaWNlIGhvdXIgcnVzaCBpbiB0aGUgY2l0eSBpcyBtb3JlLCB3aXRoIGRlbWFuZCBwZWFraW5nIGFyb3VuZCA4LTkgYW0gYW5kIDUtNiBwbSBvbiBtb3N0IHdlZWtkYXlzIGFuZCAxMi0zIHBtIG9uIG1vc3Qgd2Vla2VuZHMuDQoNCiMjIyAyLjIuMyBNYXBwaW5nIFN0YXRpb24gVXNhZ2UgYW5kIGZyZXF1ZW5jeQ0KDQpgYGB7ciBvcmlnaW5fbWFwLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBjYWNoZT1UUlVFIH0NCmdncGxvdCgpKw0KICBnZW9tX3NmKGRhdGEgPSBkY1RyYWN0cyAlPiUNCiAgICAgICAgICBzdF90cmFuc2Zvcm0oY3JzPTQzMjYpKSsNCiAgZ2VvbV9wb2ludChkYXRhID0gZGF0X2NlbnN1cyAlPiUgDQogICAgICAgICAgICBtdXRhdGUoaG91ciA9IGhvdXIoU3RhcnQuZGF0ZSksDQogICAgICAgICAgICAgICAgd2Vla2VuZCA9IGlmZWxzZShkb3R3ICVpbiUgYygiU3VuIiwgIlNhdCIpLCAiV2Vla2VuZCIsICJXZWVrZGF5IiksDQogICAgICAgICAgICAgICAgdGltZV9vZl9kYXkgPSBjYXNlX3doZW4oaG91cihpbnRlcnZhbDYwKSA8IDcgfCBob3VyKGludGVydmFsNjApID4gMTggfiAiT3Zlcm5pZ2h0IiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhvdXIoaW50ZXJ2YWw2MCkgPj0gNyAmIGhvdXIoaW50ZXJ2YWw2MCkgPCAxMCB+ICJBTSBSdXNoIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhvdXIoaW50ZXJ2YWw2MCkgPj0gMTAgJiBob3VyKGludGVydmFsNjApIDwgMTUgfiAiTWlkLURheSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBob3VyKGludGVydmFsNjApID49IDE1ICYgaG91cihpbnRlcnZhbDYwKSA8PSAxOCB+ICJQTSBSdXNoIikpJT4lDQogICAgICAgICAgICAgIGdyb3VwX2J5KFN0YXJ0LnN0YXRpb24ubnVtYmVyLCBmcm9tX2xhdGl0dWRlLCBmcm9tX2xvbmdpdHVkZSwgd2Vla2VuZCwgdGltZV9vZl9kYXkpICU+JQ0KICAgICAgICAgICAgICB0YWxseSgpLA0KICAgICAgICAgICAgYWVzKHg9ZnJvbV9sb25naXR1ZGUsIHkgPSBmcm9tX2xhdGl0dWRlLCBjb2xvciA9IG4pLCANCiAgICAgICAgICAgIGZpbGwgPSAidHJhbnNwYXJlbnQiLCBhbHBoYSA9IDAuNCwgc2l6ZSA9IDAuMykrDQogIHNjYWxlX2NvbG91cl92aXJpZGlzKGRpcmVjdGlvbiA9IC0xLA0KICBkaXNjcmV0ZSA9IEZBTFNFLCBvcHRpb24gPSAiRCIpKw0KICB5bGltKG1pbihkYXRfY2Vuc3VzJGZyb21fbGF0aXR1ZGUpLCBtYXgoZGF0X2NlbnN1cyRmcm9tX2xhdGl0dWRlKSkrDQogIHhsaW0obWluKGRhdF9jZW5zdXMkZnJvbV9sb25naXR1ZGUpLCBtYXgoZGF0X2NlbnN1cyRmcm9tX2xvbmdpdHVkZSkpKw0KICBmYWNldF9ncmlkKHdlZWtlbmQgfiB0aW1lX29mX2RheSkrDQogIGxhYnModGl0bGU9IkJpa2Ugc2hhcmUgdHJpcHMgcGVyIGhyIGJ5IHN0YXRpb24uIFdhc2hpbmd0b24sIE1heSwgMjAxOSIpKw0KICBtYXBUaGVtZQ0KYGBgDQoNCkNvbmZpcm1pbmcgb3VyIHByZXZpb3VzIGFuYWx5c2lzLCB3ZSBzZWUgdGhhdCB0aGUgY2l0eSBjZW50ZXIgc2VlcyBtb3N0IGRlbWFuZCBkdXJpbmcgdGhlIHdlZWtkYXlzIHdpdGggbGVzcyB1c2UgaW4gdGhlIG91dHNraXJ0cyBpbiB0aGUgY2l0eS4gV2Vla2VuZCBzZWUgYSBnZW5lcmFsIGxvdyBkZW1hbmQgYWNyb3NzIGFsbCBzdGF0aW9ucy4NCg0KIyMgMi4zIE1vZGVsIHRyYWluaW5nDQoNCiMjIyAyLjMuMSBTcGFjZSBUaW1lIFBhbmVsIC0gDQoNCldlIGNyZWF0ZWQgYSBjb21wbGV0ZSBwYW5lbCBkYXRhc2V0IHdoZXJlIGV2ZXJ5IHN0YXRpb24gYW5kIGV2ZXJ5IGhvdXIgaXMgaW5jbHVkZWQsIGV2ZW4gaWYgbm8gdHJpcHMgaGFwcGVuZWQuIFRoaXMgbWFrZXMgc3VyZSBvdXIgbW9kZWwgY2FuIGxlYXJuIGZyb20gYm90aCBidXN5IGFuZCBxdWlldCB0aW1lcy4gV2UgYWxzbyBhZGRlZCB3ZWF0aGVyIGFuZCBjZW5zdXMgZmVhdHVyZXMgdG8gaGVscCBpbXByb3ZlIG91ciBwcmVkaWN0aW9ucy4NCg0KYGBge3IgcGFuZWxfbGVuZ3RoX2NoZWNrICwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgY2FjaGU9VFJVRX0NCmxlbmd0aCh1bmlxdWUoZGF0X2NlbnN1cyRpbnRlcnZhbDYwKSkgKiBsZW5ndGgodW5pcXVlKGRhdF9jZW5zdXMkU3RhcnQuc3RhdGlvbi5udW1iZXIpKQ0KDQoNCnN0dWR5LnBhbmVsIDwtIA0KICBleHBhbmQuZ3JpZChpbnRlcnZhbDYwPXVuaXF1ZShkYXRfY2Vuc3VzJGludGVydmFsNjApLCANCiAgICAgICAgICAgICAgU3RhcnQuc3RhdGlvbi5udW1iZXIgPSB1bmlxdWUoZGF0X2NlbnN1cyRTdGFydC5zdGF0aW9uLm51bWJlcikpICU+JQ0KICBsZWZ0X2pvaW4oLiwgZGF0X2NlbnN1cyAlPiUNCiAgICAgICAgICAgICAgc2VsZWN0KFN0YXJ0LnN0YXRpb24ubnVtYmVyLCBTdGFydC5zdGF0aW9uLCBPcmlnaW4uVHJhY3QsIGZyb21fbG9uZ2l0dWRlLCBmcm9tX2xhdGl0dWRlICklPiUNCiAgICAgICAgICAgICAgZGlzdGluY3QoKSAlPiUNCiAgICAgICAgICAgICAgZ3JvdXBfYnkoU3RhcnQuc3RhdGlvbi5udW1iZXIpICU+JQ0KICAgICAgICAgICAgICBzbGljZSgxKSkNCg0KbnJvdyhzdHVkeS5wYW5lbCkgICAgICANCmBgYA0KDQpgYGB7ciBjcmVhdGVfcGFuZWwgLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBjYWNoZT1UUlVFfQ0KcmlkZS5wYW5lbCA8LSANCiAgZGF0X2NlbnN1cyAlPiUNCiAgbXV0YXRlKFRyaXBfQ291bnRlciA9IDEpICU+JQ0KICByaWdodF9qb2luKHN0dWR5LnBhbmVsKSAlPiUgDQogIGdyb3VwX2J5KGludGVydmFsNjAsIFN0YXJ0LnN0YXRpb24ubnVtYmVyLCBTdGFydC5zdGF0aW9uLCBPcmlnaW4uVHJhY3QsIGZyb21fbG9uZ2l0dWRlLCBmcm9tX2xhdGl0dWRlKSAlPiUNCiAgc3VtbWFyaXplKFRyaXBfQ291bnQgPSBzdW0oVHJpcF9Db3VudGVyLCBuYS5ybT1UKSkgJT4lDQogIGxlZnRfam9pbih3ZWF0aGVyLlBhbmVsKSAlPiUNCiAgdW5ncm91cCgpICU+JQ0KICBmaWx0ZXIoaXMubmEoU3RhcnQuc3RhdGlvbi5udW1iZXIpID09IEZBTFNFKSAlPiUNCiAgbXV0YXRlKHdlZWsgPSB3ZWVrKGludGVydmFsNjApLA0KICAgICAgICAgZG90dyA9IHdkYXkoaW50ZXJ2YWw2MCwgbGFiZWwgPSBUUlVFKSkgJT4lDQogIGZpbHRlcihpcy5uYShPcmlnaW4uVHJhY3QpID09IEZBTFNFKQ0KYGBgDQoNCmBgYHtyIGNlbnN1c19hbmRfcGFuZWwgLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBjYWNoZT1UUlVFfQ0KcmlkZS5wYW5lbCA8LSANCiAgbGVmdF9qb2luKHJpZGUucGFuZWwsIGRjQ2Vuc3VzICU+JQ0KICAgICAgICAgICAgICBhcy5kYXRhLmZyYW1lKCkgJT4lDQogICAgICAgICAgICAgIHNlbGVjdCgtZ2VvbWV0cnkpLCBieSA9IGMoIk9yaWdpbi5UcmFjdCIgPSAiR0VPSUQiKSkNCmBgYA0KDQojIyMgMi4zLjIgVGltZSBMYWdzDQoNCldlIGNyZWF0ZWQgbGFnIGZlYXR1cmVzIHRvIGNhcHR1cmUgaG93IHBhc3QgdHJpcCBjb3VudHMgYWZmZWN0IGZ1dHVyZSB0cmlwcy4gV2UgYWxzbyBhZGRlZCBob2xpZGF5IGluZm9ybWF0aW9uIGFuZCBjaGVja2VkIGhvdyB3ZWxsIHRoZSBsYWdnZWQgdmFsdWVzIGFyZSByZWxhdGVkIHRvIGN1cnJlbnQgdHJpcCBjb3VudHMuIFRoaXMgaGVscHMgdGhlIG1vZGVsIGJldHRlciBwcmVkaWN0IGJ1c3kgYW5kIHNsb3cgcGVyaW9kcy4NCg0KYGBge3IgdGltZV9sYWdzICwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgY2FjaGU9VFJVRX0NCnJpZGUucGFuZWwgPC0gDQogIHJpZGUucGFuZWwgJT4lIA0KICBhcnJhbmdlKFN0YXJ0LnN0YXRpb24ubnVtYmVyLCBpbnRlcnZhbDYwKSAlPiUgDQogIG11dGF0ZShsYWdIb3VyID0gZHBseXI6OmxhZyhUcmlwX0NvdW50LDEpLA0KICAgICAgICAgbGFnMkhvdXJzID0gZHBseXI6OmxhZyhUcmlwX0NvdW50LDIpLA0KICAgICAgICAgbGFnM0hvdXJzID0gZHBseXI6OmxhZyhUcmlwX0NvdW50LDMpLA0KICAgICAgICAgbGFnNEhvdXJzID0gZHBseXI6OmxhZyhUcmlwX0NvdW50LDQpLA0KICAgICAgICAgbGFnMTJIb3VycyA9IGRwbHlyOjpsYWcoVHJpcF9Db3VudCwxMiksDQogICAgICAgICBsYWcxZGF5ID0gZHBseXI6OmxhZyhUcmlwX0NvdW50LDI0KSwNCiAgICAgICAgIGhvbGlkYXkgPSBpZmVsc2UoeWRheShpbnRlcnZhbDYwKSA9PSAxNDgsMSwwKSkgJT4lDQogICBtdXRhdGUoZGF5ID0geWRheShpbnRlcnZhbDYwKSkgJT4lDQogICBtdXRhdGUoaG9saWRheUxhZyA9IGNhc2Vfd2hlbihkcGx5cjo6bGFnKGhvbGlkYXksIDEpID09IDEgfiAiUGx1c09uZURheSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkcGx5cjo6bGFnKGhvbGlkYXksIDIpID09IDEgfiAiUGx1c3RUd29EYXlzIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRwbHlyOjpsYWcoaG9saWRheSwgMykgPT0gMSB+ICJQbHVzdFRocmVlRGF5cyIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkcGx5cjo6bGVhZChob2xpZGF5LCAxKSA9PSAxIH4gIk1pbnVzT25lRGF5IiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRwbHlyOjpsZWFkKGhvbGlkYXksIDIpID09IDEgfiAiTWludXNUd29EYXlzIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRwbHlyOjpsZWFkKGhvbGlkYXksIDMpID09IDEgfiAiTWludXNUaHJlZURheXMiKSwNCiAgICAgICAgIGhvbGlkYXlMYWcgPSBpZmVsc2UoaXMubmEoaG9saWRheUxhZykgPT0gVFJVRSwgMCwgaG9saWRheUxhZykpDQoNCmBgYA0KDQpgYGB7ciBldmFsdWF0ZV9sYWdzICwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgY2FjaGU9VFJVRX0NCmFzLmRhdGEuZnJhbWUocmlkZS5wYW5lbCkgJT4lDQogICAgZ3JvdXBfYnkoaW50ZXJ2YWw2MCkgJT4lIA0KICAgIHN1bW1hcmlzZV9hdCh2YXJzKHN0YXJ0c193aXRoKCJsYWciKSwgIlRyaXBfQ291bnQiKSwgbWVhbiwgbmEucm0gPSBUUlVFKSAlPiUNCiAgICBnYXRoZXIoVmFyaWFibGUsIFZhbHVlLCAtaW50ZXJ2YWw2MCwgLVRyaXBfQ291bnQpICU+JQ0KICAgIG11dGF0ZShWYXJpYWJsZSA9IGZhY3RvcihWYXJpYWJsZSwgbGV2ZWxzPWMoImxhZ0hvdXIiLCJsYWcySG91cnMiLCJsYWczSG91cnMiLCJsYWc0SG91cnMiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImxhZzEySG91cnMiLCJsYWcxZGF5IikpKSU+JQ0KICAgIGdyb3VwX2J5KFZhcmlhYmxlKSAlPiUgIA0KICAgIHN1bW1hcml6ZShjb3JyZWxhdGlvbiA9IHJvdW5kKGNvcihWYWx1ZSwgVHJpcF9Db3VudCksMikpDQpgYGANCiMgMy4gTW9kZWwgVHJhaW5pbmcgYW5kIFZhbGlkYXRpb24NCg0KIyMgMy4xIERhdGEgc3BsaXR0aW5nDQoNClRoZSBkYXRhIGlzIHNwbGl0IHRvIGVuc3VyZSBhIHRpbWUgYmFzZWQgc3BsaXQuIA0KDQpgYGB7ciB0cmFpbl90ZXN0IH0NCnJpZGUucGFuZWwgPC0gcmlkZS5wYW5lbCAlPiUNCiAgbXV0YXRlKHdlZWsgPSB3ZWVrKGludGVydmFsNjApKQ0KDQpyaWRlLlRyYWluIDwtIGZpbHRlcihyaWRlLnBhbmVsLCB3ZWVrID49IDIwKQ0KcmlkZS5UZXN0IDwtIGZpbHRlcihyaWRlLnBhbmVsLCB3ZWVrIDwgMjApDQpgYGANCg0KIyMjIyBDcmVhdGluZyBkaWZmZXJuZXQgUmVncmVzc2lvbiBtb2RlbHMgKHJlZzEgdG8gcmVnNSkNCg0KYGBge3IgZml2ZV9tb2RlbHMgfQ0KcmVnMSA8LQ0KICBsbShUcmlwX0NvdW50IH4gIGZhY3Rvcihob3VyKGludGVydmFsNjApKSArIGZhY3Rvcihkb3R3KSArIFRlbXBlcmF0dXJlLCAgZGF0YT1yaWRlLlRyYWluKQ0KIA0KcmVnMiA8LQ0KICBsbShUcmlwX0NvdW50IH4gIFN0YXJ0LnN0YXRpb24ubnVtYmVyICsgIGZhY3Rvcihkb3R3KSsgVGVtcGVyYXR1cmUsICBkYXRhPXJpZGUuVHJhaW4pDQogDQpyZWczIDwtDQogIGxtKFRyaXBfQ291bnQgfiAgU3RhcnQuc3RhdGlvbi5udW1iZXIgKyBmYWN0b3IoaG91cihpbnRlcnZhbDYwKSkgKyBmYWN0b3IoZG90dykgKyBUZW1wZXJhdHVyZSArIFByZWNpcGl0YXRpb24sDQogICAgIGRhdGE9cmlkZS5UcmFpbikNCiANCnJlZzQgPC0NCiAgbG0oVHJpcF9Db3VudCB+ICBTdGFydC5zdGF0aW9uLm51bWJlciArICBmYWN0b3IoaG91cihpbnRlcnZhbDYwKSkgKyAgZmFjdG9yKGRvdHcpICsgVGVtcGVyYXR1cmUgKyBQcmVjaXBpdGF0aW9uICsNCiAgICAgICAgICAgICAgICAgICBsYWdIb3VyICsgbGFnMkhvdXJzICtsYWczSG91cnMgKyBsYWcxMkhvdXJzICsgbGFnMWRheSwNCiAgICAgZGF0YT1yaWRlLlRyYWluKQ0KIA0KcmVnNSA8LQ0KICBsbShUcmlwX0NvdW50IH4gIFN0YXJ0LnN0YXRpb24ubnVtYmVyICsgZmFjdG9yKGhvdXIoaW50ZXJ2YWw2MCkpICsgIGZhY3Rvcihkb3R3KSArIFRlbXBlcmF0dXJlICsgUHJlY2lwaXRhdGlvbiArDQogICAgICAgICAgICAgICAgICAgbGFnSG91ciArIGxhZzJIb3VycyArbGFnM0hvdXJzICtsYWcxMkhvdXJzICsgbGFnMWRheSArIGhvbGlkYXlMYWcgKyBob2xpZGF5LA0KICAgICBkYXRhPXJpZGUuVHJhaW4pDQogDQpgYGANCg0KDQpGaXZlIGRpZmZlcmVudCByZWdyZXNzaW9uIG1vZGVscyB3ZXJlIGNyZWF0ZWQgdXNpbmcgZGlmZmVyZW50IHNldHMgb2YgZmVhdHVyZXMgbGlrZSB0aW1lIG9mIGRheSwgd2VhdGhlciwgcGFzdCB0cmlwIGNvdW50cyAobGFncyksIGFuZCBob2xpZGF5cy4gRWFjaCBtb2RlbCBidWlsZHMgb24gdGhlIHByZXZpb3VzIG9uZSBieSBhZGRpbmcgbW9yZSBkZXRhaWxlZCBpbmZvcm1hdGlvbi4NCg0KIyMgMy4yIFByZWRpY3QgdXNpbmcgbmVzdGVkIHN0cnVjdHVyZSANCg0KYGBge3IgbmVzdF9kYXRhICwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0V9DQpyaWRlLlRlc3Qud2Vla05lc3QgPC0gDQogIHJpZGUuVGVzdCAlPiUNCiAgbmVzdCgtd2VlaykgDQpgYGANCg0KDQpUaGUgdGVzdCBkYXRhc2V0IHdhcyBuZXN0ZWQgYnkgd2VlayBzbyB0aGF0IHByZWRpY3Rpb25zIGNvdWxkIGJlIG1hZGUgc2VwYXJhdGVseSBmb3IgZWFjaCB3ZWVrLiBUaGlzIGhlbHBzIGluIG9yZ2FuaXppbmcgYW5kIGNvbXBhcmluZyBtb2RlbCBwZXJmb3JtYW5jZSBhY3Jvc3MgZGlmZmVyZW50IHRpbWUgcGVyaW9kcy4NCg0KYGBge3IgcHJlZGljdF9mdW5jdGlvbiB9DQptb2RlbF9wcmVkIDwtIGZ1bmN0aW9uKGRhdCwgZml0KXsNCiAgIHByZWQgPC0gcHJlZGljdChmaXQsIG5ld2RhdGEgPSBkYXQpfQ0KYGBgDQoNCg0KRWFjaCBtb2RlbCB3YXMgdXNlZCB0byBwcmVkaWN0IHRyaXAgY291bnRzIG9uIHRoZSB0ZXN0IGRhdGEuIFRoZSBhY3R1YWwgYW5kIHByZWRpY3RlZCB2YWx1ZXMgd2VyZSBjb21wYXJlZCB0byBjYWxjdWxhdGUgZXJyb3JzIGxpa2UgTWVhbiBBYnNvbHV0ZSBFcnJvciAoTUFFKSBhbmQgc3RhbmRhcmQgZGV2aWF0aW9uIG9mIGVycm9ycyBmb3IgZWFjaCBtb2RlbCBhbmQgd2VlayB0byB1bmRlcnN0YW5kIHdoaWNoIGlzIHRoZSBiZXN0IG1vZGVsLg0KDQpgYGB7ciBkb19wcmVkaWNpdG9ucyB9DQp3ZWVrX3ByZWRpY3Rpb25zIDwtIA0KICByaWRlLlRlc3Qud2Vla05lc3QgJT4lIA0KICAgIG11dGF0ZShBVGltZV9GRSA9IG1hcCgueCA9IGRhdGEsIGZpdCA9IHJlZzEsIC5mID0gbW9kZWxfcHJlZCksDQogICAgICAgICAgIEJTcGFjZV9GRSA9IG1hcCgueCA9IGRhdGEsIGZpdCA9IHJlZzIsIC5mID0gbW9kZWxfcHJlZCksDQogICAgICAgICAgIENUaW1lX1NwYWNlX0ZFID0gbWFwKC54ID0gZGF0YSwgZml0ID0gcmVnMywgLmYgPSBtb2RlbF9wcmVkKSwNCiAgICAgICAgICAgRFRpbWVfU3BhY2VfRkVfdGltZUxhZ3MgPSBtYXAoLnggPSBkYXRhLCBmaXQgPSByZWc0LCAuZiA9IG1vZGVsX3ByZWQpLA0KICAgICAgICAgICBFVGltZV9TcGFjZV9GRV90aW1lTGFnc19ob2xpZGF5TGFncyA9IG1hcCgueCA9IGRhdGEsIGZpdCA9IHJlZzUsIC5mID0gbW9kZWxfcHJlZCkpICU+JSANCiAgICBnYXRoZXIoUmVncmVzc2lvbiwgUHJlZGljdGlvbiwgLWRhdGEsIC13ZWVrKSAlPiUNCiAgICBtdXRhdGUoT2JzZXJ2ZWQgPSBtYXAoZGF0YSwgcHVsbCwgVHJpcF9Db3VudCksDQogICAgICAgICAgIEFic29sdXRlX0Vycm9yID0gbWFwMihPYnNlcnZlZCwgUHJlZGljdGlvbiwgIH4gYWJzKC54IC0gLnkpKSwNCiAgICAgICAgICAgTUFFID0gbWFwX2RibChBYnNvbHV0ZV9FcnJvciwgbWVhbiwgbmEucm0gPSBUUlVFKSwNCiAgICAgICAgICAgc2RfQUUgPSBtYXBfZGJsKEFic29sdXRlX0Vycm9yLCBzZCwgbmEucm0gPSBUUlVFKSkNCg0Kd2Vla19wcmVkaWN0aW9ucw0KYGBgDQojIDQuIE1vZGVsIEV2YWx1YXRpb24gYW5kIEFwcGxpY2F0aW9uIHRvIFJlYmFsYW5jaW5nDQoNCiMjIDQuMSBFeGFtaW5lIEVycm9yIE1ldHJpY3MgZm9yIEFjY3VyYWN5DQoNCmBgYHtyIHBsb3RfZXJyb3JzX2J5X21vZGVsIH0NCndlZWtfcHJlZGljdGlvbnMgJT4lDQogIGRwbHlyOjpzZWxlY3Qod2VlaywgUmVncmVzc2lvbiwgTUFFKSAlPiUNCiAgZ2F0aGVyKFZhcmlhYmxlLCBNQUUsIC1SZWdyZXNzaW9uLCAtd2VlaykgJT4lDQogIGdncGxvdChhZXMod2VlaywgTUFFKSkgKyANCiAgICBnZW9tX2JhcihhZXMoZmlsbCA9IFJlZ3Jlc3Npb24pLCBwb3NpdGlvbiA9ICJkb2RnZSIsIHN0YXQ9ImlkZW50aXR5IikgKw0KICAgIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IHBhbGV0dGU1KSArDQogICAgbGFicyh0aXRsZSA9ICJNZWFuIEFic29sdXRlIEVycm9ycyBieSBtb2RlbCBzcGVjaWZpY2F0aW9uIGFuZCB3ZWVrIikgKw0KICBwbG90VGhlbWUNCmBgYA0KDQpNb2RlbCA1IChFVGltZV9TcGFjZV9GRV90aW1lTGFnc19ob2xpZGF5TGFncykgaGFkIHRoZSBsb3dlc3QgTWVhbiBBYnNvbHV0ZSBFcnJvciAoTUFFKSBhY3Jvc3MgYWxsIHRlc3Qgd2Vla3MuDQpBcyB3ZSBhZGRlZCBtb3JlIGZlYXR1cmVzIGxpa2UgbGFncyBhbmQgaG9saWRheXMsIHRoZSBtb2RlbHMgZ290IGJldHRlciBhdCBwcmVkaWN0aW5nIGRlbWFuZC4NClRoaXMgc2hvd3MgdGhhdCB1c2luZyBwYXN0IHRyaXAgcGF0dGVybnMgYW5kIGhvbGlkYXkgZWZmZWN0cyBoZWxwcyBpbXByb3ZlIGZvcmVjYXN0IGFjY3VyYWN5Lg0KDQojIyA0LjIgUHJlZGljdGVkIHZzIGFjdHVhbCB0aW1lIHNlcmllcw0KDQpgYGB7ciBlcnJvcl92c19hY3R1YWxfdGltZXNlcmllcyAsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFfQ0Kd2Vla19wcmVkaWN0aW9ucyAlPiUgDQogICAgbXV0YXRlKGludGVydmFsNjAgPSBtYXAoZGF0YSwgcHVsbCwgaW50ZXJ2YWw2MCksDQogICAgICAgICAgIFN0YXJ0LnN0YXRpb24ubnVtYmVyID0gbWFwKGRhdGEsIHB1bGwsIFN0YXJ0LnN0YXRpb24ubnVtYmVyKSkgJT4lDQogICAgZHBseXI6OnNlbGVjdChpbnRlcnZhbDYwLCBTdGFydC5zdGF0aW9uLm51bWJlciwgT2JzZXJ2ZWQsIFByZWRpY3Rpb24sIFJlZ3Jlc3Npb24pICU+JQ0KICAgIHVubmVzdCgpICU+JQ0KICAgIGdhdGhlcihWYXJpYWJsZSwgVmFsdWUsIC1SZWdyZXNzaW9uLCAtaW50ZXJ2YWw2MCwgLVN0YXJ0LnN0YXRpb24ubnVtYmVyKSAlPiUNCiAgICBncm91cF9ieShSZWdyZXNzaW9uLCBWYXJpYWJsZSwgaW50ZXJ2YWw2MCkgJT4lDQogICAgc3VtbWFyaXplKFZhbHVlID0gc3VtKFZhbHVlKSkgJT4lDQogICAgZ2dwbG90KGFlcyhpbnRlcnZhbDYwLCBWYWx1ZSwgY29sb3VyPVZhcmlhYmxlKSkgKyANCiAgICAgIGdlb21fbGluZShzaXplID0gMS4xKSArIA0KICAgICAgZmFjZXRfd3JhcCh+UmVncmVzc2lvbiwgbmNvbD0xKSArDQogICAgICBsYWJzKHRpdGxlID0gIlByZWRpY3RlZC9PYnNlcnZlZCBiaWtlIHNoYXJlIHRpbWUgc2VyaWVzIiwgc3VidGl0bGUgPSAiQ2hpY2FnbzsgQSB0ZXN0IHNldCBvZiAyIHdlZWtzIiwgIHggPSAiSG91ciIsIHk9ICJTdGF0aW9uIFRyaXBzIikgKw0KICAgICAgcGxvdFRoZW1lDQpgYGANCg0KVGhlIHByZWRpY3RlZCB0cmlwIGNvdW50cyBjbG9zZWx5IGZvbGxvdyB0aGUgYWN0dWFsIHRyaXAgcGF0dGVybnMgYWNyb3NzIGRpZmZlcmVudCBtb2RlbHMuDQpNb2RlbCA1IHNob3dzIHRoZSBiZXN0IGFsaWdubWVudCB3aXRoIGFjdHVhbCBkZW1hbmQsIGVzcGVjaWFsbHkgZHVyaW5nIHBlYWsgaG91cnMuDQpIb3dldmVyLCB0aGVyZSBhcmUgc3RpbGwgc21hbGwgZ2FwcyBkdXJpbmcgc3VkZGVuIHNwaWtlcywgbGlrZSBvbiB3ZWVrZW5kcyBvciBob2xpZGF5cy4NCg0KIyMgNC4zIE1BRSBzcGF0aWFsIGVycm9yIG1hcHMNCg0KYGBge3IgZXJyb3JzX2J5X3N0YXRpb24sIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFIH0NCndlZWtfcHJlZGljdGlvbnMgJT4lIA0KICAgIG11dGF0ZShpbnRlcnZhbDYwID0gbWFwKGRhdGEsIHB1bGwsIGludGVydmFsNjApLA0KICAgICAgICAgICBTdGFydC5zdGF0aW9uLm51bWJlciA9IG1hcChkYXRhLCBwdWxsLCBTdGFydC5zdGF0aW9uLm51bWJlciksIA0KICAgICAgICAgICBmcm9tX2xhdGl0dWRlID0gbWFwKGRhdGEsIHB1bGwsIGZyb21fbGF0aXR1ZGUpLCANCiAgICAgICAgICAgZnJvbV9sb25naXR1ZGUgPSBtYXAoZGF0YSwgcHVsbCwgZnJvbV9sb25naXR1ZGUpKSAlPiUNCiAgICBzZWxlY3QoaW50ZXJ2YWw2MCwgU3RhcnQuc3RhdGlvbi5udW1iZXIsIGZyb21fbG9uZ2l0dWRlLCBmcm9tX2xhdGl0dWRlLCBPYnNlcnZlZCwgUHJlZGljdGlvbiwgUmVncmVzc2lvbikgJT4lDQogICAgdW5uZXN0KCkgJT4lDQogIGZpbHRlcihSZWdyZXNzaW9uID09ICJFVGltZV9TcGFjZV9GRV90aW1lTGFnc19ob2xpZGF5TGFncyIpICU+JQ0KICBncm91cF9ieShTdGFydC5zdGF0aW9uLm51bWJlciwgZnJvbV9sb25naXR1ZGUsIGZyb21fbGF0aXR1ZGUpICU+JQ0KICBzdW1tYXJpemUoTUFFID0gbWVhbihhYnMoT2JzZXJ2ZWQtUHJlZGljdGlvbiksIG5hLnJtID0gVFJVRSkpJT4lDQpnZ3Bsb3QoLikrDQogIGdlb21fc2YoZGF0YSA9IGRjQ2Vuc3VzLCBjb2xvciA9ICJncmV5IiwgZmlsbCA9ICJ0cmFuc3BhcmVudCIpKw0KICBnZW9tX3BvaW50KGFlcyh4ID0gZnJvbV9sb25naXR1ZGUsIHkgPSBmcm9tX2xhdGl0dWRlLCBjb2xvciA9IE1BRSksIA0KICAgICAgICAgICAgIGZpbGwgPSAidHJhbnNwYXJlbnQiLCBhbHBoYSA9IDAuNCkrDQogIHNjYWxlX2NvbG91cl92aXJpZGlzKGRpcmVjdGlvbiA9IC0xLA0KICBkaXNjcmV0ZSA9IEZBTFNFLCBvcHRpb24gPSAiRCIpKw0KICB5bGltKG1pbihkYXRfY2Vuc3VzJGZyb21fbGF0aXR1ZGUpLCBtYXgoZGF0X2NlbnN1cyRmcm9tX2xhdGl0dWRlKSkrDQogIHhsaW0obWluKGRhdF9jZW5zdXMkZnJvbV9sb25naXR1ZGUpLCBtYXgoZGF0X2NlbnN1cyRmcm9tX2xvbmdpdHVkZSkpKw0KICBsYWJzKHRpdGxlPSJNZWFuIEFicyBFcnJvciwgVGVzdCBTZXQsIE1vZGVsIDUiKSsNCiAgbWFwVGhlbWUNCmBgYA0KDQpFcnJvcnMgYXJlIGhpZ2hlciBhdCBzb21lIHN0YXRpb25zIGNvbXBhcmVkIHRvIG90aGVycy4NCkRvd250b3duIGFuZCBidXN5IGFyZWFzIGhhdmUgbG93ZXIgcHJlZGljdGlvbiBlcnJvcnMsIHdoaWxlIG91dGx5aW5nIGFyZWFzIHNob3cgc2xpZ2h0bHkgaGlnaGVyIGVycm9ycy4NClRoaXMgbWVhbnMgdGhlIG1vZGVsIGlzIGJldHRlciBhdCBwcmVkaWN0aW5nIHRyaXBzIHdoZXJlIHRoZXJlIGlzIHJlZ3VsYXIgYW5kIHN0YWJsZSBiaWtlIHVzYWdlLg0KDQojIyA0LjQgT2JzZXJ2ZWQgdnMgUHJlZGljdGVkIA0KDQpgYGB7ciBvYnNfcHJlZF9hbGwsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2UgPSBGQUxTRSwgY2FjaGU9VFJVRX0NCndlZWtfcHJlZGljdGlvbnMgJT4lIA0KICAgIG11dGF0ZShpbnRlcnZhbDYwID0gbWFwKGRhdGEsIHB1bGwsIGludGVydmFsNjApLA0KICAgICAgICAgICBTdGFydC5zdGF0aW9uLm51bWJlciA9IG1hcChkYXRhLCBwdWxsLCBTdGFydC5zdGF0aW9uLm51bWJlciksIA0KICAgICAgICAgICBmcm9tX2xhdGl0dWRlID0gbWFwKGRhdGEsIHB1bGwsIGZyb21fbGF0aXR1ZGUpLCANCiAgICAgICAgICAgZnJvbV9sb25naXR1ZGUgPSBtYXAoZGF0YSwgcHVsbCwgZnJvbV9sb25naXR1ZGUpLA0KICAgICAgICAgICBkb3R3ID0gbWFwKGRhdGEsIHB1bGwsIGRvdHcpKSAlPiUNCiAgICBzZWxlY3QoaW50ZXJ2YWw2MCwgU3RhcnQuc3RhdGlvbi5udW1iZXIsIGZyb21fbG9uZ2l0dWRlLCANCiAgICAgICAgICAgZnJvbV9sYXRpdHVkZSwgT2JzZXJ2ZWQsIFByZWRpY3Rpb24sIFJlZ3Jlc3Npb24sDQogICAgICAgICAgIGRvdHcpICU+JQ0KICAgIHVubmVzdCgpICU+JQ0KICBmaWx0ZXIoUmVncmVzc2lvbiA9PSAiRVRpbWVfU3BhY2VfRkVfdGltZUxhZ3NfaG9saWRheUxhZ3MiKSU+JQ0KICBtdXRhdGUod2Vla2VuZCA9IGlmZWxzZShkb3R3ICVpbiUgYygiU3VuIiwgIlNhdCIpLCAiV2Vla2VuZCIsICJXZWVrZGF5IiksDQogICAgICAgICB0aW1lX29mX2RheSA9IGNhc2Vfd2hlbihob3VyKGludGVydmFsNjApIDwgNyB8IGhvdXIoaW50ZXJ2YWw2MCkgPiAxOCB+ICJPdmVybmlnaHQiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaG91cihpbnRlcnZhbDYwKSA+PSA3ICYgaG91cihpbnRlcnZhbDYwKSA8IDEwIH4gIkFNIFJ1c2giLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaG91cihpbnRlcnZhbDYwKSA+PSAxMCAmIGhvdXIoaW50ZXJ2YWw2MCkgPCAxNSB+ICJNaWQtRGF5IiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhvdXIoaW50ZXJ2YWw2MCkgPj0gMTUgJiBob3VyKGludGVydmFsNjApIDw9IDE4IH4gIlBNIFJ1c2giKSklPiUNCiAgZ2dwbG90KCkrDQogIGdlb21fcG9pbnQoYWVzKHg9IE9ic2VydmVkLCB5ID0gUHJlZGljdGlvbikpKw0KICAgIGdlb21fc21vb3RoKGFlcyh4PSBPYnNlcnZlZCwgeT0gUHJlZGljdGlvbiksIG1ldGhvZCA9ICJsbSIsIHNlID0gRkFMU0UsIGNvbG9yID0gInJlZCIpKw0KICAgIGdlb21fYWJsaW5lKHNsb3BlID0gMSwgaW50ZXJjZXB0ID0gMCkrDQogIGZhY2V0X2dyaWQodGltZV9vZl9kYXl+d2Vla2VuZCkrDQogIGxhYnModGl0bGU9Ik9ic2VydmVkIHZzIFByZWRpY3RlZCIsDQogICAgICAgeD0iT2JzZXJ2ZWQgdHJpcHMiLCANCiAgICAgICB5PSJQcmVkaWN0ZWQgdHJpcHMiKSsNCiAgcGxvdFRoZW1lDQpgYGANCg0KVGhlIG1vZGVsIHByZWRpY3RzIHdlZWtkYXkgdHJpcHMgYmV0dGVyIHRoYW4gd2Vla2VuZCB0cmlwcywgZXNwZWNpYWxseSBkdXJpbmcgQU0gYW5kIFBNIHJ1c2ggaG91cnMuDQpPbiB3ZWVrZW5kcywgdGhlIHByZWRpY3Rpb25zIGFyZSBzbGlnaHRseSBsZXNzIGFjY3VyYXRlLCB3aXRoIG1vcmUgc2NhdHRlciBhbmQgYmlnZ2VyIGVycm9ycyBkdXJpbmcgbWlkLWRheSBhbmQgb3Zlcm5pZ2h0IHBlcmlvZHMuDQpPdmVyYWxsLCB0aGUgbW9kZWwgdGVuZHMgdG8gc2xpZ2h0bHkgdW5kZXJwcmVkaWN0IHdoZW4gdGhlIG51bWJlciBvZiB0cmlwcyBpcyB2ZXJ5IGhpZ2guDQoNCiMjIDQuNSBNZWFuIEFic29sdXRlIEVycm9ycyBNYXBwaW5nDQoNCmBgYHtyIHN0YXRpb25fc3VtbWFyeSwgd2FybmluZz1GQUxTRSwgbWVzc2FnZSA9IEZBTFNFIH0NCndlZWtfcHJlZGljdGlvbnMgJT4lIA0KICAgIG11dGF0ZShpbnRlcnZhbDYwID0gbWFwKGRhdGEsIHB1bGwsIGludGVydmFsNjApLA0KICAgICAgICAgICBTdGFydC5zdGF0aW9uLm51bWJlciA9IG1hcChkYXRhLCBwdWxsLCBTdGFydC5zdGF0aW9uLm51bWJlciksIA0KICAgICAgICAgICBmcm9tX2xhdGl0dWRlID0gbWFwKGRhdGEsIHB1bGwsIGZyb21fbGF0aXR1ZGUpLCANCiAgICAgICAgICAgZnJvbV9sb25naXR1ZGUgPSBtYXAoZGF0YSwgcHVsbCwgZnJvbV9sb25naXR1ZGUpLA0KICAgICAgICAgICBkb3R3ID0gbWFwKGRhdGEsIHB1bGwsIGRvdHcpICkgJT4lDQogICAgc2VsZWN0KGludGVydmFsNjAsIFN0YXJ0LnN0YXRpb24ubnVtYmVyLCBmcm9tX2xvbmdpdHVkZSwgDQogICAgICAgICAgIGZyb21fbGF0aXR1ZGUsIE9ic2VydmVkLCBQcmVkaWN0aW9uLCBSZWdyZXNzaW9uLA0KICAgICAgICAgICBkb3R3KSAlPiUNCiAgICB1bm5lc3QoKSAlPiUNCiAgZmlsdGVyKFJlZ3Jlc3Npb24gPT0gIkVUaW1lX1NwYWNlX0ZFX3RpbWVMYWdzX2hvbGlkYXlMYWdzIiklPiUNCiAgbXV0YXRlKHdlZWtlbmQgPSBpZmVsc2UoZG90dyAlaW4lIGMoIlN1biIsICJTYXQiKSwgIldlZWtlbmQiLCAiV2Vla2RheSIpLA0KICAgICAgICAgdGltZV9vZl9kYXkgPSBjYXNlX3doZW4oaG91cihpbnRlcnZhbDYwKSA8IDcgfCBob3VyKGludGVydmFsNjApID4gMTggfiAiT3Zlcm5pZ2h0IiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhvdXIoaW50ZXJ2YWw2MCkgPj0gNyAmIGhvdXIoaW50ZXJ2YWw2MCkgPCAxMCB+ICJBTSBSdXNoIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhvdXIoaW50ZXJ2YWw2MCkgPj0gMTAgJiBob3VyKGludGVydmFsNjApIDwgMTUgfiAiTWlkLURheSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBob3VyKGludGVydmFsNjApID49IDE1ICYgaG91cihpbnRlcnZhbDYwKSA8PSAxOCB+ICJQTSBSdXNoIikpICU+JQ0KICBncm91cF9ieShTdGFydC5zdGF0aW9uLm51bWJlciwgd2Vla2VuZCwgdGltZV9vZl9kYXksIGZyb21fbG9uZ2l0dWRlLCBmcm9tX2xhdGl0dWRlKSAlPiUNCiAgc3VtbWFyaXplKE1BRSA9IG1lYW4oYWJzKE9ic2VydmVkLVByZWRpY3Rpb24pLCBuYS5ybSA9IFRSVUUpKSU+JQ0KICBnZ3Bsb3QoLikrDQogIGdlb21fc2YoZGF0YSA9IGRjQ2Vuc3VzLCBjb2xvciA9ICJncmV5IiwgZmlsbCA9ICJ0cmFuc3BhcmVudCIpKw0KICBnZW9tX3BvaW50KGFlcyh4ID0gZnJvbV9sb25naXR1ZGUsIHkgPSBmcm9tX2xhdGl0dWRlLCBjb2xvciA9IE1BRSksIA0KICAgICAgICAgICAgIGZpbGwgPSAidHJhbnNwYXJlbnQiLCBzaXplID0gMC41LCBhbHBoYSA9IDAuNCkrDQogIHNjYWxlX2NvbG91cl92aXJpZGlzKGRpcmVjdGlvbiA9IC0xLA0KICBkaXNjcmV0ZSA9IEZBTFNFLCBvcHRpb24gPSAiRCIpKw0KICB5bGltKG1pbihkYXRfY2Vuc3VzJGZyb21fbGF0aXR1ZGUpLCBtYXgoZGF0X2NlbnN1cyRmcm9tX2xhdGl0dWRlKSkrDQogIHhsaW0obWluKGRhdF9jZW5zdXMkZnJvbV9sb25naXR1ZGUpLCBtYXgoZGF0X2NlbnN1cyRmcm9tX2xvbmdpdHVkZSkpKw0KICBmYWNldF9ncmlkKHdlZWtlbmR+dGltZV9vZl9kYXkpKw0KICBsYWJzKHRpdGxlPSJNZWFuIEFic29sdXRlIEVycm9ycywgVGVzdCBTZXQiKSsNCiAgbWFwVGhlbWUNCiAgDQpgYGANCg0KUHJlZGljdGlvbiBlcnJvcnMgKE1BRSkgYXJlIGdlbmVyYWxseSBsb3cgYWNyb3NzIHRoZSBjaXR5LCBzdGF5aW5nIG1vc3RseSBiZXR3ZWVuIDEgYW5kIDQgdHJpcHMuDQpFcnJvcnMgYXJlIHNsaWdodGx5IGhpZ2hlciBkdXJpbmcgbWlkLWRheSBhbmQgb3Zlcm5pZ2h0LCBlc3BlY2lhbGx5IG9uIHdlZWtlbmRzLg0KVGhlIG1vZGVsIHBlcmZvcm1zIGJldHRlciBkdXJpbmcgQU0gYW5kIFBNIHJ1c2ggaG91cnMgb24gd2Vla2RheXMsIHdoZXJlIHRyaXAgcGF0dGVybnMgYXJlIG1vcmUgcmVndWxhciBhbmQgcHJlZGljdGFibGUuIA0KDQpgYGB7ciBzdGF0aW9uX3N1bW1hcnkyLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlID0gRkFMU0UgfQ0Kd2Vla19wcmVkaWN0aW9ucyAlPiUgDQogICAgbXV0YXRlKGludGVydmFsNjAgPSBtYXAoZGF0YSwgcHVsbCwgaW50ZXJ2YWw2MCksDQogICAgICAgICAgIFN0YXJ0LnN0YXRpb24ubnVtYmVyID0gbWFwKGRhdGEsIHB1bGwsIFN0YXJ0LnN0YXRpb24ubnVtYmVyKSwgDQogICAgICAgICAgIGZyb21fbGF0aXR1ZGUgPSBtYXAoZGF0YSwgcHVsbCwgZnJvbV9sYXRpdHVkZSksIA0KICAgICAgICAgICBmcm9tX2xvbmdpdHVkZSA9IG1hcChkYXRhLCBwdWxsLCBmcm9tX2xvbmdpdHVkZSksDQogICAgICAgICAgIGRvdHcgPSBtYXAoZGF0YSwgcHVsbCwgZG90dyksDQogICAgICAgICAgIFBlcmNlbnRfVGFraW5nX1B1YmxpY19UcmFucyA9IG1hcChkYXRhLCBwdWxsLCBQZXJjZW50X1Rha2luZ19QdWJsaWNfVHJhbnMpLA0KICAgICAgICAgICBNZWRfSW5jID0gbWFwKGRhdGEsIHB1bGwsIE1lZF9JbmMpLA0KICAgICAgICAgICBQZXJjZW50X1doaXRlID0gbWFwKGRhdGEsIHB1bGwsIFBlcmNlbnRfV2hpdGUpKSAlPiUNCiAgICBzZWxlY3QoaW50ZXJ2YWw2MCwgU3RhcnQuc3RhdGlvbi5udW1iZXIsIGZyb21fbG9uZ2l0dWRlLCANCiAgICAgICAgICAgZnJvbV9sYXRpdHVkZSwgT2JzZXJ2ZWQsIFByZWRpY3Rpb24sIFJlZ3Jlc3Npb24sDQogICAgICAgICAgIGRvdHcsIFBlcmNlbnRfVGFraW5nX1B1YmxpY19UcmFucywgTWVkX0luYywgUGVyY2VudF9XaGl0ZSkgJT4lDQogICAgdW5uZXN0KCkgJT4lDQogIGZpbHRlcihSZWdyZXNzaW9uID09ICJFVGltZV9TcGFjZV9GRV90aW1lTGFnc19ob2xpZGF5TGFncyIpJT4lDQogIG11dGF0ZSh3ZWVrZW5kID0gaWZlbHNlKGRvdHcgJWluJSBjKCJTdW4iLCAiU2F0IiksICJXZWVrZW5kIiwgIldlZWtkYXkiKSwNCiAgICAgICAgIHRpbWVfb2ZfZGF5ID0gY2FzZV93aGVuKGhvdXIoaW50ZXJ2YWw2MCkgPCA3IHwgaG91cihpbnRlcnZhbDYwKSA+IDE4IH4gIk92ZXJuaWdodCIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBob3VyKGludGVydmFsNjApID49IDcgJiBob3VyKGludGVydmFsNjApIDwgMTAgfiAiQU0gUnVzaCIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBob3VyKGludGVydmFsNjApID49IDEwICYgaG91cihpbnRlcnZhbDYwKSA8IDE1IH4gIk1pZC1EYXkiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaG91cihpbnRlcnZhbDYwKSA+PSAxNSAmIGhvdXIoaW50ZXJ2YWw2MCkgPD0gMTggfiAiUE0gUnVzaCIpKSAlPiUNCiAgZmlsdGVyKHRpbWVfb2ZfZGF5ID09ICJBTSBSdXNoIikgJT4lDQogIGdyb3VwX2J5KFN0YXJ0LnN0YXRpb24ubnVtYmVyLCBQZXJjZW50X1Rha2luZ19QdWJsaWNfVHJhbnMsIE1lZF9JbmMsIFBlcmNlbnRfV2hpdGUpICU+JQ0KICBzdW1tYXJpemUoTUFFID0gbWVhbihhYnMoT2JzZXJ2ZWQtUHJlZGljdGlvbiksIG5hLnJtID0gVFJVRSkpJT4lDQogIGdhdGhlcigtU3RhcnQuc3RhdGlvbi5udW1iZXIsIC1NQUUsIGtleSA9ICJ2YXJpYWJsZSIsIHZhbHVlID0gInZhbHVlIiklPiUNCiAgZ2dwbG90KC4pKw0KICAjZ2VvbV9zZihkYXRhID0gZGNDZW5zdXMsIGNvbG9yID0gImdyZXkiLCBmaWxsID0gInRyYW5zcGFyZW50IikrDQogIGdlb21fcG9pbnQoYWVzKHggPSB2YWx1ZSwgeSA9IE1BRSksIGFscGhhID0gMC40KSsNCiAgZ2VvbV9zbW9vdGgoYWVzKHggPSB2YWx1ZSwgeSA9IE1BRSksIG1ldGhvZCA9ICJsbSIsIHNlPSBGQUxTRSkrDQogIGZhY2V0X3dyYXAofnZhcmlhYmxlLCBzY2FsZXMgPSAiZnJlZSIpKw0KICBsYWJzKHRpdGxlPSJFcnJvcnMgYXMgYSBmdW5jdGlvbiBvZiBzb2Npby1lY29ub21pYyB2YXJpYWJsZXMiLA0KICAgICAgIHk9Ik1lYW4gQWJzb2x1dGUgRXJyb3IgKFRyaXBzKSIpKw0KICBwbG90VGhlbWUNCiAgDQpgYGANCg0KUHJlZGljdGlvbiBlcnJvcnMgYXJlIHNsaWdodGx5IGhpZ2hlciBpbiBhcmVhcyB3aXRoIGhpZ2hlciBtZWRpYW4gaW5jb21lcyBhbmQgYSBoaWdoZXIgcGVyY2VudGFnZSBvZiBXaGl0ZSByZXNpZGVudHMuDQpUaGVyZSBpcyBub3QgbXVjaCBjaGFuZ2UgaW4gZXJyb3IgYmFzZWQgb24gdGhlIHBlcmNlbnRhZ2Ugb2YgcGVvcGxlIHRha2luZyBwdWJsaWMgdHJhbnNpdC4NCk92ZXJhbGwsIHRoZSBtb2RlbCBkb2VzIGEgZ29vZCBqb2IgYWNyb3NzIGRpZmZlcmVudCBzb2Npby1lY29ub21pYyBncm91cHMsIGJ1dCBzbWFsbCBkaWZmZXJlbmNlcyBzdGlsbCBleGlzdC4NCg0KIyMgNC42IFJlYmFsYW5jaW5nIEFwcGxpY2F0aW9uDQoNClRoZSBtb2RlbOKAmXMgcHJlZGljdGlvbnMgY2FuIGRpcmVjdGx5IHN1cHBvcnQgYSBzbWFydGVyIGJpa2UgcmViYWxhbmNpbmcgc3RyYXRlZ3kgYWNyb3NzIFdhc2hpbmd0b24sIERDLiBCZWNhdXNlIHRoZSBtb2RlbCBmb3JlY2FzdHMgZGVtYW5kIDIgdG8gMyBob3VycyBhaGVhZCwgdHJ1Y2tzIGNhbiBiZSBzZW50IHRvIHN0YXRpb25zIHRoYXQgYXJlIGxpa2VseSB0byBydW4gbG93IG9uIGJpa2VzIG9yIGRvY2tpbmcgc3BhY2VzIGJlZm9yZSB0aGUgcHJvYmxlbSBhY3R1YWxseSBoYXBwZW5zLiBEdXJpbmcgd2Vla2RheSBBTSBhbmQgUE0gcnVzaCBob3Vycywgd2hlcmUgdGhlIG1vZGVsIHByZWRpY3RzIG1vc3QgYWNjdXJhdGVseSwgdHJ1Y2sgcm91dGVzIGNhbiBiZSBzY2hlZHVsZWQgY29uZmlkZW50bHkgdG8gcmVmaWxsIG9yIGNsZWFyIGtleSBjb21tdXRlciBzdGF0aW9ucy4gT24gd2Vla2VuZHMgYW5kIG1pZC1kYXkgcGVyaW9kcywgd2hlcmUgcHJlZGljdGlvbnMgYXJlIGxlc3Mgc3RhYmxlLCBhZGRpdGlvbmFsIHJpZGVyIGluY2VudGl2ZXMgKHN1Y2ggYXMgZGlzY291bnRzIG9yIGV4dHJhIHJpZGUgdGltZSkgY291bGQgaGVscCBuYXR1cmFsbHkgbW92ZSBiaWtlcyB3aXRob3V0IHJlbHlpbmcgb25seSBvbiB0cnVja3MuDQoNCg0KVGhlIHNwYXRpYWwgZXJyb3IgbWFwcyBzaG93IHRoYXQgZG93bnRvd24gc3RhdGlvbnMgYXJlIGVhc2llciB0byBwcmVkaWN0LCBtZWFuaW5nIHJlYmFsYW5jaW5nIHRydWNrcyBzaG91bGQgcHJpb3JpdGl6ZSByZXNpZGVudGlhbCBvciBlZGdlIGFyZWFzIHRoYXQgaGF2ZSBzbGlnaHRseSBoaWdoZXIgZXJyb3JzLg0KDQoNClNvY2lvLWVjb25vbWljIHBhdHRlcm5zIGFsc28gc3VnZ2VzdCB0aGF0IHdlYWx0aGllciBuZWlnaGJvcmhvb2RzIGFuZCBhcmVhcyB3aXRoIGEgaGlnaGVyIHBlcmNlbnRhZ2Ugb2YgV2hpdGUgcmVzaWRlbnRzIG1heSBoYXZlIHNsaWdodGx5IGhpZ2hlciB1bmNlcnRhaW50eSwgc28gZmxleGlibGUgdHJ1Y2sgcm91dGluZyBvciBtb2JpbGUgcmVzcG9uc2UgdGVhbXMgY291bGQgYmUgaGVscGZ1bCB0aGVyZS4NCg0KDQpPdmVyYWxsLCB0aGUgbW9kZWwgYWxsb3dzIGZvciBhbiBlZmZpY2llbnQgcmViYWxhbmNpbmcgcGxhbiwgbWluaW1pemluZyBlbXB0eSBkb2NrcyBhbmQgc3RyYW5kZWQgYmlrZXMgYWNyb3NzIGJvdGggYnVzeSBhbmQgcXVpZXQgcGFydHMgb2YgdGhlIGNpdHkuDQoNCg0KIyA1LiBDb25jbHVzaW9uIA0KDQpUaGlzIG1vZGVsaW5nIGFwcHJvYWNoIHN1Y2Nlc3NmdWxseSBmb3JlY2FzdHMgc2hvcnQtdGVybSBiaWtlIHNoYXJlIGRlbWFuZCBhbmQgc3VwcG9ydHMgc21hcnRlciByZWJhbGFuY2luZyBkZWNpc2lvbnMuIFRoZSBtb2RlbCBjYXB0dXJlcyBkYWlseSBwYXR0ZXJucyBhbmQgcnVzaCBob3VyIHBlYWtzIHdlbGwsIGhlbHBpbmcgb3BlcmF0b3JzIHBsYW4gYWhlYWQgYW5kIGF2b2lkIHNlcnZpY2UgZ2Fwcy4gV2hpbGUgaXQgcGVyZm9ybWVkIHN0cm9uZ2x5IGR1cmluZyBwcmVkaWN0YWJsZSBwZXJpb2RzLCB1bmV4cGVjdGVkIGV2ZW50cyBsaWtlIGhvbGlkYXlzIGFuZCB3ZWVrZW5kIHRyYXZlbCBwYXR0ZXJucyBzdGlsbCBwcmVzZW50ZWQgY2hhbGxlbmdlcy4gRXJyb3JzIHdlcmUgc2xpZ2h0bHkgaGlnaGVyIGF0IGxlc3MgYnVzeSBzdGF0aW9ucyBhbmQgaW4gd2VhbHRoaWVyIGFyZWFzLCBzdWdnZXN0aW5nIHRoZSBuZWVkIGZvciBtb3JlIGxvY2FsaXplZCBhZGp1c3RtZW50cy4gR29pbmcgZm9yd2FyZCwgaW1wcm92ZW1lbnRzIGNvdWxkIGluY2x1ZGUgYWRkaW5nIHJlYWwtdGltZSBzdGF0aW9uIGF2YWlsYWJpbGl0eSBkYXRhLCBhY2NvdW50aW5nIGZvciBzcGVjaWFsIGNpdHkgZXZlbnRzLCBhbmQgdGVzdGluZyBtb3JlIGZsZXhpYmxlIG1hY2hpbmUgbGVhcm5pbmcgbW9kZWxzIHRvIGJldHRlciBoYW5kbGUgdW5wcmVkaWN0YWJsZSBkZW1hbmQgc2hpZnRzLg0KDQo=