1.1. Introduction

One of the most difficult operational problems for urban bike share systems is the need to ‘re-balance’ bicycles across the network. Bike share is not useful if a dock has no bikes to pickup, nor if there are no open docking spaces to deposit a bike. Re-balancing is the practice of anticipating (or predicting) bike share demand for all docks at all times and manually redistributing bikes to ensure a bike or a docking place is available when needed.
The demand for parking spaces, uber trips, bike share, road access and a whole host of urban transportation phenomena are time and space dependent, and modeling them frequently involves simply controlling for the day, hour, location, weather and other temporal phenomena. Quite simply, the demand for bike share trips today at my location at 5PM is probably highly correlated with the demand last week at the same time.
This project will predict only the demand, but it will give us a window into how we can use time-space predictive modeling to address an operations issue. If we knew the bike station capacities, we could see when demand for bikes might drive stations to run out of bikes, and then move excess bikes from elsewhere. For example, we could provide reward credits for those who end their trip at the high-demand stations depends on the prediction. Today’s trend is similar to the trend tomorrow and this week’s trend will be similar to next week trends. Hence, we will be able to predict how many bikes and from which station to high accuracy. Those credits could be used by riders who tries to pay their next period plan (monthly or yearly pass) or guest pass. Electric bikes require extra 20¢/minute to unlock. Credits could also work as a discount for the extra electric bikes fee.
Philadelphia’s bikeshare program – offers 600 self-service bikes among 60 stations all day, every day. Philadelphia’s City government owns the bicycles and stations, with the Mayor’s Office of Transportation and Utilities (MOTU) planning and managing the project. The City of Philadelphia and its partners at Bicycle Transit Systems are sharing anonymized Indego trip data, downloadable from OpenDataPhilly and on Indego’s webiste. The data includes the membership type, bike numbers, checkout kiosk name, ID, and location of stations, trip duration, checkout and return times, and a category distinguishing between one way and round trips. https://www.rideindego.com/about/data/

2 Setup

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

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

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

palette5 <- c("#eff3ff","#bdd7e7","#6baed6","#3182bd","#08519c")
palette4 <- c("#D2FBD4","#92BCAB","#527D82","#123F5A")
palette2 <- c("#6baed6","#08519c")
palette1 <- c("#b6cde3","#58a2e8","#1f66ab","#073b6b")

Load census API key to grab stuff from tidycensus

2.1. Import Data

Let’s read in the month of May, Usually, May has a fairly pleasant temperature range in Philly, so we may see some leisure trips as well as commutes. https://kiosks.bicycletransit.workers.dev/phl

dat <- read.csv("https://raw.githubusercontent.com/RumRon/HW_UP_IP/main/R/data4HW5/4.25-5.29.csv")

dat <- dat %>%
  rename(., from_longitude = start_lon) %>%
  rename(., from_latitude = start_lat)%>%
  rename(., to_latitude = end_lat ) %>%
  rename(., to_longitude = end_lon) %>%
  rename(., from_station_id= start_station) %>%
  rename(., to_station_id = end_station)
station_name <- read.csv("https://raw.githubusercontent.com/RumRon/HW_UP_IP/main/R/data4HW5/indego_stations.csv")
station_name<- station_name %>%
  select("Station_ID","Station_Name")
dat <- left_join(dat, station_name, by = c("from_station_id" = "Station_ID")) %>%
  rename(., from_station_name= Station_Name) 
dat <- left_join(dat, station_name, by = c("to_station_id" = "Station_ID")) %>%
  rename(., to_station_name= Station_Name) 

Let’s use some date parsing to bin the data by 15 and 60 minute intervals by rounding and take a look at our data to see the format and names of all of our columns using the glimpse command.

dat$start_time <- strptime(dat$start_time, format = "%m/%d/%Y %H:%M")
dat$end_time <- strptime(dat$end_time, format = "%m/%d/%Y %H:%M")
dat2 <- dat %>%
  mutate(interval60 = floor_date(ymd_hms(start_time), unit = "hour"),
         interval15 = floor_date(ymd_hms(start_time), unit = "15 mins"),
         week = week(interval60),
         dotw = wday(interval60, label=TRUE))

glimpse(dat2)
## Rows: 96,034
## Columns: 21
## $ trip_id             <int> 475034210, 474925307, 474976334, 474925306, 474925…
## $ duration            <int> 1, 17, 357, 12, 10, 4, 10, 11, 1, 4, 3, 4, 10, 173…
## $ start_time          <dttm> 2022-04-25 00:05:00, 2022-04-25 00:05:00, 2022-04…
## $ end_time            <dttm> 2022-04-25 00:06:00, 2022-04-25 00:22:00, 2022-04…
## $ from_station_id     <int> 3190, 3190, 3190, 3212, 3026, 3033, 3006, 3167, 32…
## $ from_latitude       <dbl> 39.94892, 39.94892, 39.94892, 39.96383, 39.94182, …
## $ from_longitude      <dbl> -75.16991, -75.16991, -75.16991, -75.18177, -75.14…
## $ to_station_id       <int> 3000, 3021, 3205, 3111, 3100, 3046, 3066, 3061, 32…
## $ to_latitude         <dbl> NA, 39.95390, 39.95405, 39.97779, 39.92777, 39.950…
## $ to_longitude        <dbl> NA, -75.16902, -75.16783, -75.21323, -75.15103, -7…
## $ bike_id             <int> 11828, 14639, 3436, 19327, 14500, 5198, 19879, 348…
## $ plan_duration       <int> 30, 30, 1, 30, 30, 365, 30, 365, 365, 365, 30, 365…
## $ trip_route_category <chr> "One Way", "One Way", "One Way", "One Way", "One W…
## $ passholder_type     <chr> "Indego30", "Indego30", "Day Pass", "Indego30", "I…
## $ bike_type           <chr> "standard", "standard", "standard", "electric", "s…
## $ from_station_name   <chr> "17th & Locust", "17th & Locust", "17th & Locust",…
## $ to_station_name     <chr> "Virtual Station", "18th & JFK", "17th & JFK", "Pa…
## $ interval60          <dttm> 2022-04-25 00:00:00, 2022-04-25 00:00:00, 2022-04…
## $ interval15          <dttm> 2022-04-25 00:00:00, 2022-04-25 00:00:00, 2022-04…
## $ week                <dbl> 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17…
## $ dotw                <ord> Mon, Mon, Mon, Mon, Mon, Mon, Mon, Mon, Mon, Mon, …

2.2. Import Census Info

Using the tidycensus package, we can download census geography and variables. These are used to test generalizeability later. We extract the tracts for mapping and joining purposes - creating an sf object that consists only of GEOIDs and geometries.

We add the spatial information to our rideshare data as origin and destination data, first joining the origin station, then the destination station to our census data.

phillyCensus <- 
  get_acs(geography = "tract", 
          variables = c("B01003_001", "B19013_001", 
                        "B02001_002", "B08013_001",
                        "B08012_001", "B08301_001", 
                        "B08301_010", "B01002_001"), 
          year = 2020, 
          state = "PA", 
          geometry = TRUE, 
          county=c("Philadelphia"),
          output = "wide") %>%
  rename(Total_Pop =  B01003_001E,
         Med_Inc = B19013_001E,
         Med_Age = B01002_001E,
         White_Pop = B02001_002E,
         Travel_Time = B08013_001E,
         Num_Commuters = B08012_001E,
         Means_of_Transport = B08301_001E,
         Total_Public_Trans = B08301_010E) %>%
  select(Total_Pop, Med_Inc, White_Pop, Travel_Time,
         Means_of_Transport, Total_Public_Trans,
         Med_Age,
         GEOID, geometry) %>%
  mutate(Percent_White = White_Pop / Total_Pop,
         Mean_Commute_Time = Travel_Time / Total_Public_Trans,
         Percent_Taking_Public_Trans = Total_Public_Trans / Means_of_Transport)
phillyTracts <- 
  phillyCensus %>%
  as.data.frame() %>%
  distinct(GEOID, .keep_all = TRUE) %>%
  select(GEOID, geometry) %>% 
  st_sf
dat_census <- st_join(dat2 %>% 
          filter(is.na(from_longitude) == FALSE &
                   is.na(from_latitude) == FALSE &
                   is.na(to_latitude) == FALSE &
                   is.na(to_longitude) == FALSE) %>%
          st_as_sf(., coords = c("from_longitude", "from_latitude"), crs = 4326),
        phillyTracts %>%
          st_transform(crs=4326),
        join=st_intersects,
              left = TRUE) %>%
  rename(Origin.Tract = GEOID) %>%
  mutate(from_longitude = unlist(map(geometry, 1)),
         from_latitude = unlist(map(geometry, 2)))%>%
  as.data.frame() %>%
  select(-geometry)%>%
  st_as_sf(., coords = c("to_longitude", "to_latitude"), crs = 4326) %>%
  st_join(., phillyTracts %>%
            st_transform(crs=4326),
          join=st_intersects,
          left = TRUE) %>%
  rename(Destination.Tract = GEOID)  %>%
  mutate(to_longitude = unlist(map(geometry, 1)),
         to_latitude = unlist(map(geometry, 2)))%>%
  as.data.frame() %>%
  select(-geometry)

2.3. Import Weather Data

Import weather data from Philadelphia airport (code PHL) using riem_measures. We can mutate the data to get temperature, wind speed, precipitation on an hourly basis and plot the temperature and precipitation trends over our study period.

These data can also be categorized as a part of an exploration of the relationship between your independent and dependent variables, e.g. “does wind appear to affect ridership during rush hour?”

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

glimpse(weather.Panel)
## Rows: 840
## Columns: 4
## $ interval60    <dttm> 2022-04-25 00:00:00, 2022-04-25 01:00:00, 2022-04-25 02…
## $ Temperature   <dbl> 55.0, 54.0, 52.0, 51.1, 50.0, 48.0, 48.0, 48.0, 46.0, 46…
## $ Precipitation <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,…
## $ Wind_Speed    <dbl> 12, 11, 10, 8, 8, 7, 7, 7, 7, 7, 8, 9, 9, 10, 10, 10, 11…
grid.arrange(
  ggplot(weather.Panel, aes(interval60,Precipitation)) + geom_line() + 
  labs(title="Percipitation", x="Hour", y="Perecipitation") + plotTheme,
  ggplot(weather.Panel, aes(interval60,Wind_Speed)) + geom_line() + 
    labs(title="Wind Speed", x="Hour", y="Wind Speed") + plotTheme,
  ggplot(weather.Panel, aes(interval60,Temperature)) + geom_line() + 
    labs(title="Temperature", x="Hour", y="Temperature") + plotTheme,
  top="Weather Data - Philadelphia PHL - May, 2022")

2.4. Import Amenity Data

Import school data as amenity.

library(FNN)
school <-
  st_read("https://raw.githubusercontent.com/RumRon/HW_UP_IP/main/R/Schools.geojson") %>%
  st_transform(crs=4326)
root.dir = "https://raw.githubusercontent.com/urbanSpatial/Public-Policy-Analytics-Landing/master/DATA/"
source("https://raw.githubusercontent.com/urbanSpatial/Public-Policy-Analytics-Landing/master/functions.r")

3. Describe and Explore the Data

Examining the time and frequency components of our data.

3.1. Overall Time Pattern

First, we look at the overall time pattern - there is clearly a daily periodicity and there are lull periods on weekends. Notice that the weekend near the 28th of May (Memorial Day) doesn’t have the same dip in activity.

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

3.2. Examine the Distribution of Trip Volume by Station for Different Times of the Day

We clearly have a few high volume periods but mostly low volume. Our data must consist of a lot of low demand station/hours and a few high demand station hours.

There’s a possibility we may have to treat these as count data here, which means running Poisson regression. Then again, we might have enough of the higher counts in our high volume times and stations, that we should really be building a linear model to accomodate our actual volume and not worry about the low trip times/stations.

We can also track the daily trends in ridership by day of the week and weekend versus weekday, to see what temporal patterns we’d like to control for.

dat_census %>%
        mutate(time_of_day = case_when(hour(interval60) < 7 | hour(interval60) > 18 ~ "Overnight",
                                 hour(interval60) >= 7 & hour(interval60) < 10 ~ "AM Rush",
                                 hour(interval60) >= 10 & hour(interval60) < 15 ~ "Mid-Day",
                                 hour(interval60) >= 15 & hour(interval60) <= 18 ~ "PM Rush"))%>%
         group_by(interval60, from_station_name, time_of_day) %>%
         tally()%>%
  group_by(from_station_name, time_of_day)%>%
  summarize(mean_trips = mean(n))%>%
  ggplot()+
  geom_histogram(aes(mean_trips), binwidth = 1)+
  labs(title="Mean Number of Hourly Trips Per Station. Phialdelphia, May, 2022",
       x="Number of trips", 
       y="Frequency")+
  facet_wrap(~time_of_day)+
  plotTheme()

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

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

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

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

3.3 Create Space-Time Panel

First we have to make sure each unique station and hour/day combo exists in our data set. This is done in order to create a “panel” (e.g. a time-series) data set where each time period in the study is represented by a row - whether an observation took place then or not. So if a station didn’t have any trips originating from it at a given hour, we still need a zero in that spot in the panel.

We start by determining the maximum number of combinations.

Then we compare that to the actual number of combinations. We create an empty data frame study.panel, is created that has each unique space/time observations. This is done using the expand.grid function and unique. Along the way, we keep tabs on the number of rows our data have - nrow shows that the count is still correct.

We then join the station name, tract and lat/lon (some have multiple lat lon info, so we just take the first one of each using group_by and slice).

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

nrow(study.panel)      
## [1] 148452

We create the full panel by summarizing counts by station for each time interval, keep census info and lat/lon information along for joining later to other data. We remove data for station IDs that are FALSE.

ride.panel <- 
  dat_census %>%
  mutate(Trip_Counter = 1) %>%
  right_join(study.panel) %>% 
  group_by(interval60, from_station_id, from_station_name, Origin.Tract, from_longitude, from_latitude) %>%
  summarize(Trip_Count = sum(Trip_Counter, na.rm=T)) %>%
  left_join(weather.Panel) %>%
  ungroup() %>%
  filter(is.na(from_station_id) == FALSE) %>%
  mutate(week = week(interval60),
         dotw = wday(interval60, label = TRUE)) %>%
  filter(is.na(Origin.Tract) == FALSE)
ride.panel <- 
  left_join(ride.panel, phillyCensus %>%
              as.data.frame() %>%
              select(-geometry), by = c("Origin.Tract" = "GEOID"))
from <- ride.panel %>%
  dplyr::select(from_longitude, from_latitude)
to <- st_coordinates(school)

ride.panel <-
  ride.panel %>%
  mutate(
    school_dist = nn_function(from, to, 3)
  )

3.4. Create time lags

Creating time lag variables will add additional nuance about the demand during a given time period - hours before and during that day.

We can also try to control for the effects of holidays that disrupt the expected demand during a given weekend or weekday. We have a holiday on May 28 - Memorial Day. For that three day weekend we could use some dummy variables indicating temporal proximity to the holiday.

The demand right now should be relatively similar to the demand tomorrow at this time, and to the demand an hour from now, but twelve hours from now, we likely expect the opposite in terms of demand.

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

4. Run Models

We split our data into a training and a test set. We create five linear models using the lm funtion. Sometimes, for data such as these, Poisson distributions, designed for modeling counts, might be appropriate. I’ll spare you the effort - linear models work better with this particular data set.

We create the models using our training data ride.Train. The first models include only temporal controls, but the later ones contain all of our lag information.

ride.Train <- ride.panel %>% filter(., week >= 20)
ride.Test <- ride.panel %>% filter(., week < 20)
reg1 <- 
  lm(Trip_Count ~  hour(interval60) + dotw + Temperature,  data=ride.Train)

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

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

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

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

4.1. Predict for test data

When models have finished running, create a nested data frame of test data by week. Nested data is common in most other programming languages. For instance, the javascript object notation file format (aka JSON) is highly nested.

Nesting means that instead of merely having a “flat” file consisting of rows and columns, we have a matrix of other objects - imagine each cell in a matrix containing another matrix within it, or a list, or a list of lists.

The purrr package is designed to map functions through nested data structures. This concept is important - think of map as visiting each dataframe in a nested data set and applies a function to it.

We create a function called model_pred which we can then map onto each data frame in our nested structure.

This function is called in the code below in a few ways, one way is like so: map(.x = data, fit = name_of_your_regression, .f = model_pred). Here’s the important bit - the argument fit takes the name of a regression you have created that you want to use to make predictions, and the .f argument takes a function, in this case model_pred, which we create in order to simply execute the predict function.

ride.Test.weekNest <- 
  ride.Test %>%
  nest(-week) 
model_pred <- function(dat, fit){
   pred <- predict(fit, newdata = dat)}

When we run our predictions and summarize our results, we are going to have some NA data - recall we have some lag information that will necessarily trip up the model at the margins of the time frame.

week_predictions <- 
  ride.Test.weekNest %>% 
    mutate(ATime_FE = map(.x = data, fit = reg1, .f = model_pred),
           BSpace_FE = map(.x = data, fit = reg2, .f = model_pred),
           CTime_Space_FE = map(.x = data, fit = reg3, .f = model_pred),
           DTime_Space_FE_timeLags = map(.x = data, fit = reg4, .f = model_pred),
           ETime_Space_FE_timeLags_holidayLags = map(.x = data, fit = reg5, .f = model_pred)) %>% 
    gather(Regression, Prediction, -data, -week) %>%
    mutate(Observed = map(data, pull, Trip_Count),
           Absolute_Error = map2(Observed, Prediction,  ~ abs(.x - .y)),
           MAE = map_dbl(Absolute_Error, mean, na.rm = TRUE),
           sd_AE = map_dbl(Absolute_Error, sd, na.rm = TRUE))
## Warning in predict.lm(fit, newdata = dat): prediction from a rank-deficient fit
## may be misleading

## Warning in predict.lm(fit, newdata = dat): prediction from a rank-deficient fit
## may be misleading

## Warning in predict.lm(fit, newdata = dat): prediction from a rank-deficient fit
## may be misleading
week_predictions
## # A tibble: 15 × 8
##     week data     Regression      Prediction Observed Absolute_Error   MAE sd_AE
##    <dbl> <list>   <chr>           <list>     <list>   <list>         <dbl> <dbl>
##  1    17 <tibble> ATime_FE        <dbl>      <dbl>    <dbl [21,360]> 0.780 0.771
##  2    18 <tibble> ATime_FE        <dbl>      <dbl>    <dbl [29,904]> 0.801 0.870
##  3    19 <tibble> ATime_FE        <dbl>      <dbl>    <dbl [29,904]> 0.779 0.829
##  4    17 <tibble> BSpace_FE       <dbl>      <dbl>    <dbl [21,360]> 0.673 0.819
##  5    18 <tibble> BSpace_FE       <dbl>      <dbl>    <dbl [29,904]> 0.707 0.883
##  6    19 <tibble> BSpace_FE       <dbl>      <dbl>    <dbl [29,904]> 0.683 0.826
##  7    17 <tibble> CTime_Space_FE  <dbl>      <dbl>    <dbl [21,360]> 0.715 0.738
##  8    18 <tibble> CTime_Space_FE  <dbl>      <dbl>    <dbl [29,904]> 0.737 0.825
##  9    19 <tibble> CTime_Space_FE  <dbl>      <dbl>    <dbl [29,904]> 0.721 0.789
## 10    17 <tibble> DTime_Space_FE… <dbl>      <dbl>    <dbl [21,360]> 0.657 0.695
## 11    18 <tibble> DTime_Space_FE… <dbl>      <dbl>    <dbl [29,904]> 0.670 0.765
## 12    19 <tibble> DTime_Space_FE… <dbl>      <dbl>    <dbl [29,904]> 0.648 0.732
## 13    17 <tibble> ETime_Space_FE… <dbl>      <dbl>    <dbl [21,360]> 0.661 0.692
## 14    18 <tibble> ETime_Space_FE… <dbl>      <dbl>    <dbl [29,904]> 0.672 0.766
## 15    19 <tibble> ETime_Space_FE… <dbl>      <dbl>    <dbl [29,904]> 0.648 0.731

5. Accuracy

5.1 MAE

week_predictions %>%
  dplyr::select(week, Regression, MAE) %>%
  gather(Variable, MAE, -Regression, -week) %>%
  ggplot(aes(week, MAE)) + 
    geom_bar(aes(fill = Regression), position = "dodge", stat="identity") +
    scale_fill_manual(values = palette5) +
    labs(title = "Mean Absolute Errors by model specification and week") +
  plotTheme()

We could see that regression D and E has relatively low MAE by model specification and week.

5.2 reg5 seems to have the best goodness of fit generally.

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

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

There are some MAE around downtown area and spread to University City and nearby areas.

5.3. Space-Time Error Evaluation

If we plot observed vs. predicted for different times of day during the week and weekend, some patterns begin to emerge.

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

Weekend’s morning is the hardest to predict.

5.4. MAE map by weekend/weekday and time of day.

week_predictions %>% 
    mutate(interval60 = map(data, pull, interval60),
           from_station_id = map(data, pull, from_station_id), 
           from_latitude = map(data, pull, from_latitude), 
           from_longitude = map(data, pull, from_longitude),
           dotw = map(data, pull, dotw) ) %>%
    select(interval60, from_station_id, from_longitude, 
           from_latitude, Observed, Prediction, Regression,
           dotw) %>%
    unnest() %>%
  filter(Regression == "ETime_Space_FE_timeLags_holidayLags")%>%
  mutate(weekend = ifelse(dotw %in% c("Sun", "Sat"), "Weekend", "Weekday"),
         time_of_day = case_when(hour(interval60) < 7 | hour(interval60) > 18 ~ "Overnight",
                                 hour(interval60) >= 7 & hour(interval60) < 10 ~ "AM Rush",
                                 hour(interval60) >= 10 & hour(interval60) < 15 ~ "Mid-Day",
                                 hour(interval60) >= 15 & hour(interval60) <= 18 ~ "PM Rush")) %>%
  group_by(from_station_id, weekend, time_of_day, from_longitude, from_latitude) %>%
  summarize(MAE = mean(abs(Observed-Prediction), na.rm = TRUE))%>%
  ggplot(.)+
  geom_sf(data = phillyCensus, color = "grey", fill = "transparent")+
  geom_point(aes(x = from_longitude, y = from_latitude, color = MAE), 
             fill = "transparent", size = 0.5, alpha = 0.4)+
  scale_colour_viridis(direction = -1,
  discrete = FALSE, option = "D")+
  ylim(min(dat_census$from_latitude), max(dat_census$from_latitude))+
  xlim(min(dat_census$from_longitude), max(dat_census$from_longitude))+
  facet_grid(weekend~time_of_day)+
  labs(title="Mean Absolute Errors, Test Set")+
  mapTheme()

Seems like errors are concentrated in downtown areas. The pattern has been visualized both as the scatter plot and the spatial map. The ridership is high during the evening rush as compared to the morning rush, hence why the errors are higher in the weekday predictions.

5.5 Errors as a function of socio-economic variables

week_predictions %>% 
    mutate(interval60 = map(data, pull, interval60),
           from_station_id = map(data, pull, from_station_id), 
           from_latitude = map(data, pull, from_latitude), 
           from_longitude = map(data, pull, from_longitude),
           dotw = map(data, pull, dotw),
           Percent_Taking_Public_Trans = map(data, pull, Percent_Taking_Public_Trans),
           Med_Inc = map(data, pull, Med_Inc),
           Med_Age = map(data, pull, Med_Age),
           Mean_Commute_Time = map(data,pull,Mean_Commute_Time),
           Percent_White = map(data, pull, Percent_White)) %>%
    select(interval60, from_station_id, from_longitude, 
           from_latitude, Observed, Prediction, Regression,
           dotw, Med_Inc, Med_Age, Mean_Commute_Time,Percent_Taking_Public_Trans) %>%
    unnest() %>%
  filter(Regression == "ETime_Space_FE_timeLags_holidayLags")%>%
  mutate(weekend = ifelse(dotw %in% c("Sun", "Sat"), "Weekend", "Weekday"),
         time_of_day = case_when(hour(interval60) < 7 | hour(interval60) > 18 ~ "Overnight",
                                 hour(interval60) >= 7 & hour(interval60) < 10 ~ "AM Rush",
                                 hour(interval60) >= 10 & hour(interval60) < 15 ~ "Mid-Day",
                                 hour(interval60) >= 15 & hour(interval60) <= 18 ~ "PM Rush")) %>%
  filter(time_of_day == "AM Rush") %>%
  group_by(from_station_id, Med_Inc, Percent_Taking_Public_Trans, Mean_Commute_Time) %>%
  summarize(MAE = mean(abs(Observed-Prediction), na.rm = TRUE))%>%
  gather(-from_station_id, -MAE, key = "variable", value = "value")%>%
  ggplot(.)+
  #geom_sf(data = phillyCensus, color = "grey", fill = "transparent")+
  geom_point(aes(x = value, y = MAE), alpha = 0.4)+
  geom_smooth(aes(x = value, y = MAE), method = "lm", se= FALSE)+
  facet_wrap(~variable, scales = "free")+
  labs(title="Errors as a function of socio-economic variables",
       y="Mean Absolute Error (Trips)")+
  plotTheme()

Let’s focus on the morning commute, where station locations probably relate to likely users. How is the model performing on weekday mornings relative to demand for public transportation (e.g. possible user base). We can tell that there are a select few stations that are proving sightly resistant to our model - they have long communication time, high mean income and low transit usage, demographically.

5.6 Animated Map by space/time dependencies

week21 <-
  filter(dat_census , week == 21)

week21.panel <-
  expand.grid(
    interval15 = unique(week21$interval15),
    Pickup.Census.Tract = unique(dat_census$from_station_id))

ride.animation.data <-
  mutate(week21, Trip_Counter = 1) %>%
  select(interval15, from_station_id, from_longitude, from_latitude, Trip_Counter) %>%
  group_by(interval15, from_station_id, from_longitude, from_latitude) %>%
  summarize(Trip_Count = sum(Trip_Counter, na.rm=T)) %>% 
  ungroup() %>% 
  mutate(Trips = case_when(Trip_Count == 0 ~ "0 trips",
                           Trip_Count > 0 & Trip_Count <= 2 ~ "0-2 trips",
                           Trip_Count > 2 & Trip_Count <= 5 ~ "2-5 trips",
                           Trip_Count > 5 & Trip_Count <= 10 ~ "5-10 trips",
                           Trip_Count > 10 & Trip_Count <= 15 ~ "10-15 trips",
                           Trip_Count > 15 ~ "15+ trips")) %>%
  mutate(Trips  = fct_relevel(Trips, "0 trips","0-2 trips","2-5 trips",
                              "5-10 trips","10-15 trips","15+ trips"))
library(FNN)

mapTheme <- function(base_size = 12) {
  theme(
    text = element_text( color = "black"),
    plot.title = element_text(size = 18,colour = "black"),
    plot.subtitle=element_text(face="italic"),
    plot.caption=element_text(hjust=0),
    axis.ticks = element_blank(),
    panel.background = element_blank(),axis.title = element_blank(),
    axis.text = element_blank(),
    axis.title.x = element_blank(),
    axis.title.y = element_blank(),
    panel.grid.minor = element_blank(),
    panel.border = element_rect(colour = "black", fill=NA, size=2)
  )
}

rideshare_animation <-
  ggplot()+
  geom_sf(data = phillyTracts %>%
            st_transform(crs=4326), colour = '#efefef')+
  geom_point(data = ride.animation.data, 
             aes(x = from_longitude, y = from_latitude, fill = Trips, color = Trips), size = 1, alpha = 1.5) +
  scale_colour_manual(values = palette1) +
  labs(title = "Indego pickups for one week in May 2022",
       subtitle = "15 minute intervals: {current_frame}") +
  transition_manual(interval15) +
  mapTheme()

animate(rideshare_animation, duration=20, renderer = gifski_renderer())

6. k-fold cross validation

indegosample <- sample_n(ride.panel, 100000)%>%
  na.omit()

fitControl <- trainControl(method = "cv", 
                           number = 100,
                           savePredictions = TRUE)

set.seed(42)
# for k-folds CV

reg.cv <-  
  train(Trip_Count ~ from_station_name + hour(interval60) + dotw + Temperature + Precipitation +
                   lagHour + lag2Hours +lag3Hours +lag12Hours + lag1day + holidayLag + holiday, 
        data = indegosample,  
        method = "lm",  
        trControl = fitControl,  
        na.action = na.pass)

reg.cv
## Linear Regression 
## 
## 91556 samples
##    12 predictor
## 
## No pre-processing
## Resampling: Cross-Validated (100 fold) 
## Summary of sample sizes: 90641, 90640, 90640, 90640, 90641, 90640, ... 
## Resampling results:
## 
##   RMSE     Rsquared   MAE      
##   1.03191  0.2855807  0.6646996
## 
## Tuning parameter 'intercept' was held constant at a value of TRUE

When we work with larger data sets, we cannot examine each value to see if there is an outlier or some outliers, or if all errors are systematically higher. Looking at the ratio of MAE to RMSE can help us understand if there are larger but less common errors. The closer the model predictions are to the observations, the smaller the MSE will be.

7. Conclusion

This model could help indicate where and when the high demand would occur. A bike re-balancing plan could be made based on this model’s result, which has small errors but is overall beneficial. In regards to the re-balancing plan, the algorithm predicts hourly and daily patterns so well that it is possible to predict when and how an individual will arrive at a particular location. As a result, we will be able to incentivize users based on their behavior and thus manage the supply and demand for bikes. The reward program for re-balancing can give some credits to riders who park their shared bikes in predicted high-demand areas during the morning and evening peak commute periods on weekdays. Each week can be adjusted based on the previous week, while the same time period from the previous year or two years can also be used, as seasons, weather, and holidays can also be referenced. In the future, if there are other factors that raise attention, we could also add them to the algorithm and test them to see the accuracy and generalization.

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