Introduction

Bikeshare programs have become an essential part of urban transportation, offering a sustainable and flexible commuting option. Citi Bike, launched by Lyft in 2008 in New York City, has expanded to Jersey City to meet the needs of commuters traveling to Manhattan. As Citi Bike’s popularity grows, managing bike availability effectively becomes crucial, especially given the fluctuations in demand throughout the day and across different locations.

Maintaining a balanced distribution of bikes, known as “re-balancing,” is a complex challenge influenced by these spatial and temporal demand patterns. Companies like Uber and Lyft use proprietary algorithms to address these issues, but the details of their models are not publicly available. This report aims to develop a transparent, open-source approach to forecasting bike demand in Jersey City using Ordinary Least Squares (OLS) regression. By analyzing historical data, including weather and bike usage patterns, our goal is to create a simple yet effective model to anticipate demand spikes and guide efficient bike reallocation.

We will begin by loading and processing our datasets, followed by an exploratory analysis of usage trends and weather impacts. Next, we’ll create a space-time panel to examine correlations and finally develop and evaluate four predictive models, testing their performance and reliability.

Set Up

The data used in this analysis comes from NYC OpenData, which provides monthly records of Citi Bike usage. Even though Jersey City is in New Jersey, Citi Bike’s operations are largely centered in New York City, and thus the data is managed through NYC’s system. For this study, we focused on bikeshare trips in Jersey City during August 2024, capturing information about the starting and ending stations for each ride. Additionally, we acquired the census tract geometries for all of Hudson County, which encompasses Jersey City, using the tidycensus package.

nycbike <- read.csv("/Users/bin/Documents/MUSA/MUSA 5080/JC-202308-citibike-tripdata.csv")

njCensus <- get_acs(geography = "tract", 
          variables = c("B01003_001", "B19013_001", 
                        "B02001_002", "B08013_001",
                        "B08012_001", "B08301_001", 
                        "B08301_010", "B01002_001"), 
          year = 2021, 
          state = "NJ", 
          geometry = TRUE, 
          county=c("Hudson"),
          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)

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

The bikeshare dataset we’re working with contains spatial details, but it isn’t inherently formatted as spatial data. To conduct a spatial analysis, we first need to integrate it with the census geometry. Our focus is on the starting locations of rides, as our aim is to predict bike availability at stations when users initiate a trip. To prepare the data, we filter out records lacking latitude or longitude information and address errors where certain coordinates incorrectly place stations in the Hudson River. These inaccurate entries are removed to ensure data quality.

bike_census <- st_join(nycbike %>% 
                         filter(is.na(start_lat) == FALSE &
                                is.na(start_lng) == FALSE &
                                is.na(end_lat) == FALSE &
                                is.na(end_lng) == FALSE) %>%
                         st_as_sf(., coords = c("start_lng", "start_lat"), crs = 4326),
                       jcTracts %>%  st_transform(crs=4326),
                       join=st_intersects, left = TRUE) %>%
  rename(Origin.Tract = GEOID) %>%
  mutate(start_lng = unlist(map(geometry, 1)),
         start_lat = unlist(map(geometry, 2)))%>%
  filter(!(ride_id %in% c("BEC9CC4D1007C753", "86087F431785EDE7", "A1AAB92631439883", "71938DA085242DCC", "4203C77668C47C75", "C92410CA2AEF3947", "6E02CBF36C90F244", "0259885253FD238E", "B39136B37AFB5FE9", "1EC9376D1559C51C", "CDDF16D3A37BE370"))) %>% 
  as.data.frame()

The following map shows the Locartion of ride starting points in Jersey City

ggplot()+
  geom_sf(data = jcTracts %>%
          st_transform(crs=4326), fill="white")+
  geom_point(data = bike_census, aes(x=start_lng, y = start_lat), 
            color = "blue", alpha = 1, size = 0.3) + 
  ylim(min(bike_census$start_lat), max(bike_census$start_lat))+
  xlim(min(bike_census$start_lng), max(bike_census$start_lng)) +
  labs(title = "Location of Rides Starting Points in Jersey City") +
  theme(axis.text.x=element_blank(),
        axis.text.y=element_blank(),
        axis.ticks =element_blank(),
        axis.title.x = element_blank(),
        axis.title.y = element_blank(),
        plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 12, face = "bold"),
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill=NA, linewidth=0.8)
        )

We carry out essential feature engineering to derive temporal insights from the ride data. Specifically, we’ll create time intervals at both 60-minute and 15-minute increments based on each ride’s start time. Additionally, we’ll categorize these intervals into broader time periods, such as “Overnight,” “AM Rush,” “Mid-Day,” and “PM Rush,” and classify each day as either a weekday or weekend.

bike_census <- bike_census %>%
  mutate(
    interval60 = floor_date(ymd_hms(started_at), unit = "hour"),
    interval15 = floor_date(ymd_hms(started_at), unit = "15 mins"),
    week = week(interval60),
    dotw = wday(interval60),  # Removed label = TRUE
    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"
    ),
    hour = hour(started_at),
    weekend = ifelse(dotw %in% c(1, 7), "Weekend", "Weekday")  # Use numeric values for days
  )

To better understand commuting patterns and rider behavior throughout the day, we generated line strings to represent the routes between the start and end points of rides. To simplify the visualization, we limited it to 200 sample rides. The visualization reveals that the majority of trips are confined to Jersey City, though some riders use Citi Bike to travel from Jersey City to Manhattan.

lines <- st_sfc(
  lapply(1:nrow(bike_census), function(i) {
    st_linestring(matrix(c(bike_census[i, "start_lng"], bike_census[i, "start_lat"],
                            bike_census[i, "end_lng"], bike_census[i, "end_lat"]), ncol = 2, byrow = TRUE))
  }))

lines_sf <- st_sf(geometry = lines)
lines_sf <- st_set_crs(lines_sf, 4326)

Route <- lines_sf %>% head(200)
mapview(Route, zcol = NULL, color = "#6E0E0A", linewidth = 0.001, alpha = 0.3) # only show 200

Exploratory Analysis

In this section, we delve into some further exploratory analysis of our data set.

Temporal Analysis

We began our analysis by examining the temporal patterns of bikeshare usage in Jersey City throughout August 2023, focusing on the distribution of trips by the hour. Our visualization reveals distinct fluctuations, with demand rising and falling in a predictable cycle each day. During weekdays, we observe pronounced peaks that align with typical rush hours—mornings and late afternoons—when people are commuting to and from work. These busy periods are followed by noticeable lulls during the night and early morning hours, as well as during weekends, when overall usage tends to decrease. The pattern is characterized by a series of five consecutive high-demand peaks that correspond to weekdays, consistently followed by two lower peaks over the weekend, highlighting the influence of workweek routines on bikeshare activity. This cyclical behavior underscores the strong link between bikeshare demand and daily commuting schedules.

ggplot(bike_census %>%
         group_by(interval60) %>%
         tally())+
  geom_line(aes(x = interval60, y = n), color= "blue")+
  labs(title="Bike Share Trips Per Hour in Jersey City, Aug 2023",
       x="Date", 
       y="Number of trips") + 
theme(plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 12, face = "bold"), 
        axis.text.x=element_text(size=8),
        axis.text.y=element_text(size=8), 
        axis.title=element_text(size=10), 
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill=NA, linewidth =0.8))

Next, we analyzed how trips were distributed across different stations on an hourly basis throughout August. The resulting visualization revealed a highly skewed distribution: the vast majority of stations recorded either no rides or just a few trips per hour. However, a select few stations stood out, experiencing significantly higher demand. These high-traffic stations sometimes saw as many as 40 to 50 rides within a single hour on certain days. This uneven distribution highlights the varying levels of bikeshare usage across Jersey City, with certain locations acting as major hubs of activity while many others experience minimal ridership.

ggplot(bike_census %>%
         group_by(interval60, start_station_name) %>%
         tally())+
  geom_histogram(aes(n), binwidth = 5, fill="lightblue")+
  labs(title="Bike Share Trips Per Hour by Station in Jersey City, Aug 2023",
       x="Trip Counts", 
       y="Number of Stations") + 
    theme(plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 12, face = "bold"), 
        axis.text.x=element_text(size=8),
        axis.text.y=element_text(size=8), 
        axis.title=element_text(size=10), 
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill=NA, linewidth =0.8))

We further explored the average number of trips per station at different times of the day. The analysis showed that PM rush hours consistently had the highest average trip counts, with several stations averaging more than 10 trips, indicating substantial demand at these locations. Midday came next in terms of activity levels, where some stations recorded averages of over 5 trips. Surprisingly, there was also considerable demand overnight, as a few stations averaged more than 5 rides even during late-night hours. This unexpected overnight activity suggests that bikeshare usage extends beyond traditional commuting patterns, potentially serving other purposes such as late-night leisure or early-morning travel.

bike_census %>%
        group_by(interval60, start_station_name, time_of_day) %>%
         tally()%>%
  group_by(start_station_name, time_of_day)%>%
  summarize(mean_trips = mean(n))%>%
  ggplot()+
  geom_histogram(aes(mean_trips, fill=time_of_day), binwidth = 1)+
  scale_fill_manual(values = palette4, name="Time of Day") + 
  labs(title="Mean Number of Hourly Trips Per Station",
       x="Number of trips", 
       y="Frequency")+
  facet_wrap(~time_of_day) +
theme(plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 12, face = "bold"), 
        axis.text.x=element_text(size=8),
        axis.text.y=element_text(size=8), 
        axis.title=element_text(size=10), 
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill=NA, size=0.8))

We then examined the total number of trips throughout August, breaking them down by day of the week. Two key patterns emerged from this visualization. First, the number of trips fluctuates significantly over the course of a day, with the lowest activity observed in the early morning hours and the highest in the late afternoon, corresponding to evening commute times. Second, the data shows that trip volumes are generally higher on Tuesdays, Wednesdays, and Thursdays, suggesting that midweek days experience the greatest demand for bikeshare services, likely driven by regular work-related travel.

ggplot(bike_census %>% mutate(hour = hour(started_at)))+
     geom_freqpoly(aes(hour, color = dotw), binwidth = 3, lwd = 0.8)+
  scale_color_manual(values = c("#124E78", "#819FA1", "#F0F0C9", "#F2BB05", "#E58507", "#D74E09", "#6E0E0A"), name = "Day") + 
  labs(title="Bike Share Trips in Jersey City by Day of the Week, Aug 2023",
       x="Hour", 
       y="Trip Counts") + 
  theme(plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 12, face = "bold"), 
        axis.text.x=element_text(size=8),
        axis.text.y=element_text(size=8), 
        axis.title=element_text(size=10), 
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill=NA, size=0.8))
## Warning: The following aesthetics were dropped during statistical transformation:
## colour.
## ℹ This can happen when ggplot fails to infer the correct grouping structure in
##   the data.
## ℹ Did you forget to specify a `group` aesthetic or to convert a numerical
##   variable into a factor?

We created a similar visualization but this time compared trip volumes between weekdays and weekends. The differences are striking: bikeshare usage is noticeably higher on weekdays compared to weekends. Additionally, on weekends, trips start to pick up much later in the day, reflecting the absence of the typical weekday morning rush. This delay suggests that without the pressure of early work commutes, people are more likely to use bikeshare services later in the day for leisure or non-work-related activities, highlighting the influence of daily routines on bikeshare demand.

ggplot(bike_census)+
     geom_freqpoly(aes(hour, color = weekend), binwidth = 3, lwd = 1.2)+
  scale_color_manual(values=palette2) + 
  labs(color = "Time of Week") +
  labs(title="Bike Share Trips in Jersey City - Weekend vs Weekday, Aug 2023",
       x="Hour", 
       y="Trip Counts") + 
  theme(plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 12, face = "bold"), 
        axis.text.x=element_text(size=8),
        axis.text.y=element_text(size=8), 
        axis.title=element_text(size=10), 
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill=NA, size=0.8))

# Assuming `bike_census` has a column 'Origin.Tract' that maps each bike trip to a census tract
# and `jcTracts` is already loaded and transformed as necessary

# Aggregate bike trip data by census tract
bike_trips_by_tract <- bike_census %>%
  group_by(Origin.Tract) %>%
  summarise(Total_Trips = n(), .groups = 'drop') %>%
  ungroup()

# Join this aggregated data to the jcTracts GeoDataFrame
jcTracts <- st_transform(jcTracts, crs = 4326)  # Make sure CRS matches, if not already
jcTracts_data <- jcTracts %>%
  left_join(bike_trips_by_tract, by = c("GEOID" = "Origin.Tract"))

# Calculate the bounding box for centering
bbox <- st_bbox(jcTracts_data)

# Plot the choropleth map with adjusted limits
ggplot(data = jcTracts_data) +
  geom_sf(aes(fill = Total_Trips), color = "white", size = 0.1) +
  scale_fill_viridis_c(option = "D", name = "Total Bike Trips", na.value = "white") +
  coord_sf(xlim = c(bbox["xmin"], bbox["xmax"]), ylim = c(bbox["ymin"], bbox["ymax"]), expand = FALSE) +  # Set limits
  labs(title = "Bike Share Trips per Census Tract in Jersey City") +
  theme_minimal() +
  theme(legend.position = "bottom",
        axis.text.x = element_blank(),
        axis.text.y = element_blank(),
        axis.ticks = element_blank(),
        axis.title.x = element_blank(),
        axis.title.y = element_blank(),
        plot.title = element_text(size = 14, face = "bold"),
        plot.subtitle = element_text(size = 12))

Expanding on our analysis, we grouped the data by station and time of day to explore trip volumes during different periods on weekdays versus weekends. To keep the visualization clear, we focused on the top 50 station-time occurrences. Each bar in the chart represents a specific combination of station, time of day, and whether it’s a weekday or weekend. The results clearly show that PM rush hours generate the highest bikeshare demand, particularly at a few key stations. Notably, weekend rides are scarce in the top 50 list, appearing only three times. A closer look reveals that Grove St PATH station had a total of 1,117 rides during PM rush on a weekday and 801 rides overnight on a weekday in August. Hoboken Terminal - River St & Hudson Pl recorded 864 rides during weekday PM rush. South Waterfront Walkway - Sinatra Dr & 1 St had 319 rides overnight on a weekend and 731 rides during PM rush on a weekday. These details underscore how certain stations experience significant spikes in demand, especially during weekday commute hours.

to_plot <- bike_census%>% # only show 100 stations
  group_by(start_station_id, start_lat, start_lng, weekend, time_of_day, start_station_name) %>%
  tally() %>% 
  arrange(-n) %>% 
  head(50) 
to_plot$ID <- seq_along(to_plot$start_station_id)

to_plot %>% 
  arrange(-n) %>%
  head(50) %>% 
  ggplot(aes(x = reorder(ID, -n), n, fill = time_of_day, color = weekend)) +
  scale_fill_manual(values = palette4, name="Time of Day") + 
  guides(color="none") + 
  geom_bar(stat = "identity", position="stack") +
  scale_color_manual(values = c("transparent", "black")) +
  labs(title="Top 50 Occurences of Rides By Time")+
  ylab("Number of Rides") +
  xlab("") + 
  theme(axis.text.x = element_text(angle = 45, hjust = 1)) + 
  theme(plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 12, face = "bold"), 
        axis.text.x = element_blank(),
    axis.ticks.x = element_blank(), 
        axis.text.y=element_text(size=8), 
        axis.title=element_text(size=10), 
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill=NA, size=0.8))

Spatial Analysis

Beyond our temporal analysis, we created maps to visualize the locations of stations with the highest bikeshare demand, broken down by time of day and day of the week. These proportional maps reveal that the greatest demand occurs during PM rush hours and overnight on weekdays, while AM rush hours see the lowest activity. The stations with the highest usage are predominantly clustered along the Hudson River. This pattern could be due to two factors: a higher concentration of stations in this area or strategic placement by Citi Bike to accommodate commuters traveling from Jersey City to Manhattan for work or school. The proximity to major transit connections likely drives this increased demand.

ggplot()+
  geom_sf(data = jcTracts %>%
          st_transform(crs=4326), fill = "white")+
  geom_point(data = to_plot,
             aes(x=start_lng, y = start_lat, size=n), color = alpha("#6E0E0A", 0.7)) + 
  ylim(min(bike_census$start_lat), max(bike_census$start_lat))+
  xlim(min(bike_census$start_lng), max(bike_census$start_lng)) +
  scale_size(range = c(0.5, 8)) + 
  labs(size = "Number of Rides") +
  facet_grid(weekend ~ time_of_day) + 
    labs(title="Locations of Stations with Greatest Bikeshare Demand by Time")+ 
  theme(axis.text.x=element_blank(),
        axis.text.y=element_blank(),
        axis.ticks =element_blank(),
        axis.title.x = element_blank(),
        axis.title.y = element_blank(),
        plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 12, face = "bold"),
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill=NA, linewidth=0.8)
        )

We analyzed all the starting stations collectively to assess spatial autocorrelation, aiming to determine if stations with high demand tend to be located near other high-demand stations. If such a pattern exists, it would allow us to predict demand at a specific station based on the activity levels at neighboring stations. To investigate this, we selected the top 80 stations with the highest number of rides in August 2023 and calculated Moran’s I using 999 random Monte Carlo simulations. The results indicated a highly significant Moran’s I, confirming that there is indeed spatial autocorrelation among nearby bikeshare stations, meaning demand is not randomly distributed but clustered in specific areas.

moran_data <- bike_census %>%
  group_by(start_station_id, start_lat, start_lng, start_station_name) %>%
  tally() %>% 
  arrange(-n) %>% 
  head(80) %>% 
  st_as_sf(., coords = c("start_lng", "start_lat"), crs = 4326)


coords <-  st_coordinates(moran_data) 
neighborList <- knn2nb(knearneigh(coords, 5))
spatialWeights <- nb2listw(neighborList, style="W")

moranTest <- moran.mc(moran_data$n, 
                      spatialWeights, nsim = 999)

ggplot(as.data.frame(moranTest$res[c(1:999)]), aes(moranTest$res[c(1:999)])) +
  geom_histogram(binwidth = 0.01, fill = "#124E78") +
  geom_vline(aes(xintercept = moranTest$statistic), colour = "#6E0E0A" ,lwd=1) +
  scale_x_continuous(limits = c(-0.5, 0.5)) +
  labs(title = "Observed and Permuted Moran's I for Stations with Most Rides",
       subtitle = "Observed Moran's I in Red",
       x = "Moran's I",
       y = "Count") +
  theme_light() +   
theme(plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 12, face = "bold"), 
        axis.text.x=element_text(size=8),
        axis.text.y=element_text(size=8), 
        axis.title=element_text(size=10))

The map below illustrates the clustering of stations with higher bikeshare demand. Specifically, two main clusters emerge, and within each of these clusters, a few stations experience exceptionally high numbers of rides. These high-demand stations are typically surrounded by others with slightly lower, but still significant, levels of activity, highlighting a clear pattern of concentrated bikeshare usage.

  ggplot() +
  geom_sf(data = jcTracts, fill = "white") +
  geom_sf(data = moran_data,
          aes(size = n, color = n)) + 
  ylim(min(bike_census$start_lat), max(bike_census$start_lat))+
  xlim(min(bike_census$start_lng), max(bike_census$start_lng)) + 
  scale_size(
    range = c(1, 6),
    guide = guide_legend(
      direction = "horizontal",
      nrow = 1,
      label.position = "bottom")) +
  scale_color_gradientn(colours = hcl.colors(5, "RdBu",rev = TRUE, alpha = 0.9), 
                        guide = guide_legend(direction = "horizontal", nrow = 1)) +
  labs(title = "Stations with Most Rides",
       subtitle =  "Dot size proportional to rides") + 
  theme(legend.position = "bottom") + 
  theme(axis.text.x=element_blank(),
        axis.text.y=element_blank(),
        axis.ticks =element_blank(),
        axis.title.x = element_blank(),
        axis.title.y = element_blank(),
        plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 12, face = "bold"),
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill=NA, linewidth=0.8)
        )

This map below combines the spatial and temporal characteristics of bike rides at 15min interval into a choropleth map.

bike_aggregated <- bike_census %>%
  group_by(interval15, Origin.Tract) %>%
  summarize(Trip_Count = n(), .groups = 'drop')

# Joining with spatial data
choropleth_data <- bike_aggregated %>%
  left_join(jcTracts, by = c("Origin.Tract" = "GEOID")) %>%
  st_sf()

# Creating the choropleth map with the same scale
choropleth_bike_map <- ggplot() +
  geom_sf(data = jcTracts, fill = "white", color = "grey", alpha = 0.5) + # Base map
  geom_sf(data = choropleth_data, aes(fill = Trip_Count), color = NA) + # Choropleth layer
  scale_fill_gradientn(
    colours = hcl.colors(5, "YlOrRd", rev = TRUE),
    name = "Number of Trips",
    na.value = "transparent"
  ) +
  # Set the same scale using xlim and ylim
  xlim(min(bike_census$start_lng), max(bike_census$start_lng)) +
  ylim(min(bike_census$start_lat), max(bike_census$start_lat)) +
  labs(
    title = "Bikeshare Trip Density by 15-Minute Intervals",
    subtitle = "Number of bike rides aggregated by origin tract and time interval"
  ) +
  theme(
    legend.position = "bottom",
    axis.text.x = element_blank(),
    axis.text.y = element_blank(),
    axis.ticks = element_blank(),
    axis.title.x = element_blank(),
    axis.title.y = element_blank(),
    plot.subtitle = element_text(size = 9, face = "italic"),
    plot.title = element_text(size = 12, face = "bold"),
    panel.background = element_blank(),
    panel.border = element_rect(colour = "grey", fill = NA, linewidth = 0.8)
  )

# Display the updated map
print(choropleth_bike_map)

Weather Analysis

In addition to space and time, weather plays a crucial role in influencing bikeshare demand. We expect that demand typically drops on rainy, windy, or extremely hot days. To incorporate this factor into our analysis, we collected hourly weather data from Newark Airport using the riem_measures function, covering the period from August 1 to September 1, 2023. Since Newark Airport is close enough to Jersey City, it serves as a reliable source for local weather conditions. We then created a weather panel summarizing key variables such as temperature, precipitation, and wind speed for each hour.

weather.Panel <- 
  riem_measures(station = "EWR", date_start = "2023-08-01", date_end = "2023-09-01") %>%
  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)  # Remove label = TRUE
  ) %>%
  group_by(interval60) %>%
  summarize(
    Temperature = max(tmpf),
    Precipitation = sum(p01i),
    Wind_Speed = max(sknt)
  ) %>%
  mutate(Temperature = ifelse(Temperature == 0, 42, Temperature))

Examining the temperature, precipitation, and wind data collected for August, we observe a few notable weather patterns. There were several rainy days scattered throughout the month, while wind speeds remained generally stable, with only a few instances of particularly strong winds. Temperature showed daily fluctuations, with higher readings during the daytime and cooler temperatures at night, reflecting a typical summer cycle.

grid.arrange(
  ggplot(weather.Panel, aes(interval60,Precipitation)) + geom_line(color="#124E78") + 
    labs(title="Percipitation", x="Hour", y="Perecipitation") + 
    theme(plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 12, face = "bold"), 
        axis.text.x=element_text(size=8),
        axis.text.y=element_text(size=8), 
        axis.title=element_text(size=10), 
        panel.background = element_blank()), 
  ggplot(weather.Panel, aes(interval60,Wind_Speed)) + geom_line(color="#124E78") + 
    labs(title="Wind Speed", x="Hour", y="Wind Speed") +
    theme(plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 12, face = "bold"), 
        axis.text.x=element_text(size=8),
        axis.text.y=element_text(size=8), 
        axis.title=element_text(size=10), 
        panel.background = element_blank()),  
  ggplot(weather.Panel, aes(interval60,Temperature)) + geom_line(color="#124E78") + 
    labs(title="Temperature", x="Hour", y="Temperature") +theme(plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 12, face = "bold"), 
        axis.text.x=element_text(size=8),
        axis.text.y=element_text(size=8), 
        axis.title=element_text(size=10), 
        panel.background = element_blank()), 
  top="Weather Data ")

Space-Time Panel

We proceeded by constructing a study panel that captures unique combinations of space and time. This means each row in the panel corresponds to a ride occurring at a specific station during a specific hour, allowing us to analyze bikeshare activity across both temporal and spatial dimensions.

# Check if 'interval60' exists and is not entirely NULL
if("interval60" %in% names(bike_census) && !all(is.na(bike_census$interval60))) {
  print("Column 'interval60' exists and has data.")
  print(head(bike_census$interval60))
} else {
  print("Column 'interval60' does not exist or is all NA.")
}
## [1] "Column 'interval60' exists and has data."
## [1] "2023-08-07 19:00:00 UTC" "2023-08-01 13:00:00 UTC"
## [3] "2023-08-15 17:00:00 UTC" "2023-08-01 12:00:00 UTC"
## [5] "2023-08-08 12:00:00 UTC" "2023-08-02 07:00:00 UTC"
# If 'interval60' needs to be created or recalculated:
if(!"interval60" %in% names(bike_census) || all(is.na(bike_census$interval60))) {
  print("Recalculating 'interval60' from existing datetime column, assuming 'started_at' exists.")
  
  # Assuming 'started_at' is the column from which 'interval60' should be derived:
  if("started_at" %in% names(bike_census)) {
    bike_census$interval60 <- as.POSIXct(bike_census$started_at, format = "%Y-%m-%d %H:%M:%S") # Customize format as necessary
    bike_census$interval60 <- floor_date(bike_census$interval60, "hour") # Using lubridate to round down to the nearest hour
    print("New 'interval60' calculated and added to 'bike_census'.")
  } else {
    print("'started_at' column also missing; please check data source.")
  }
}

# Check and recreate the data frame for joining
if("interval60" %in% names(bike_census) && "start_station_id" %in% names(bike_census)) {
  combined_df <- expand.grid(interval60 = unique(bike_census$interval60),
                            start_station_id = unique(bike_census$start_station_id))
  print("Combined DataFrame created successfully. Displaying structure:")
  print(str(combined_df))
} else {
  print("One or both key columns are still missing or invalid.")
}
## [1] "Combined DataFrame created successfully. Displaying structure:"
## 'data.frame':    78120 obs. of  2 variables:
##  $ interval60      : POSIXct, format: "2023-08-07 19:00:00" "2023-08-01 13:00:00" ...
##  $ start_station_id: Factor w/ 105 levels "HB302","JC059",..: 1 1 1 1 1 1 1 1 1 1 ...
##  - attr(*, "out.attrs")=List of 2
##   ..$ dim     : Named int [1:2] 744 105
##   .. ..- attr(*, "names")= chr [1:2] "interval60" "start_station_id"
##   ..$ dimnames:List of 2
##   .. ..$ interval60      : chr [1:744] "interval60=2023-08-07 19:00:00" "interval60=2023-08-01 13:00:00" "interval60=2023-08-15 17:00:00" "interval60=2023-08-01 12:00:00" ...
##   .. ..$ start_station_id: chr [1:105] "start_station_id=HB302" "start_station_id=JC059" "start_station_id=JC105" "start_station_id=JC063" ...
## NULL
study.panel <- 
  expand.grid(interval60=unique(bike_census$interval60), 
              start_station_id = unique(bike_census$start_station_id)) %>%
  left_join(., bike_census %>%
              select(start_station_id, start_station_name, Origin.Tract, start_lng, start_lat)%>%
              distinct() %>%
              group_by(start_station_id) %>%
              slice(1))

We then enhanced this panel by adding more detailed information. This involved counting the number of rides at each station for each hour, incorporating weather data, merging in census information, and using the lubridate package to derive time-related variables such as the day of the week and hour of the day.

bike.panel <- 
  bike_census %>%
  mutate(Trip_Counter = 1) %>%
  right_join(study.panel) %>% 
  group_by(interval60, start_station_id, start_station_name, Origin.Tract, start_lng, start_lat) %>%
  summarize(Trip_Count = sum(Trip_Counter, na.rm = TRUE), .groups = 'drop') %>%
  left_join(weather.Panel) %>%
  ungroup() %>%
  filter(!is.na(start_station_id)) %>%
  mutate(
    week = week(interval60),
    dotw = wday(interval60)  # Removed label = TRUE
  ) %>%
  filter(!is.na(Origin.Tract)) %>% 
  left_join(
    njCensus %>%
      as.data.frame() %>%
      select(-geometry),
    by = c("Origin.Tract" = "GEOID")
  )

Weather Correlation

With our panel data ready, we conducted further exploratory analysis to prepare for our regression modeling. Our first focus was on understanding the relationship between temperature and the number of bike rides in August. The analysis revealed a strong, positive, and linear correlation, indicating that bikeshare demand tends to increase as temperatures rise throughout the month.

bike.panel %>%
  group_by(interval60) %>% 
  summarize(Trip_Count = mean(Trip_Count),
            Temperature = first(Temperature)) %>%
  mutate(week = week(interval60)) %>%
  ggplot(aes(Temperature, Trip_Count)) + 
    geom_point(size = .5, color = "black") + geom_smooth(method = "lm", se= FALSE, color="darkred") +
    facet_wrap(~week, ncol=8) + 
    labs(title="Trip Count As a Fuction of Temperature by Week",
         x="Temperature", y="Mean Trip Count") + 
  theme(plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 12, face = "bold"), 
        axis.text.x = element_blank(),
    axis.ticks.x = element_blank(), 
        axis.text.y=element_text(size=8), 
        axis.title=element_text(size=10), 
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill=NA, size=0.8))

Next, we examined the correlation between wind speed and the number of bike rides in August. We observed a generally positive linear relationship, though the strength of this correlation varied across different weeks. This suggests that bikeshare demand tends to increase on windy days, but the impact of wind speed on ride activity is not uniform throughout the month.

bike.panel %>%
  group_by(interval60) %>% 
  summarize(Trip_Count = mean(Trip_Count),
            Wind = first(Wind_Speed)) %>%
  mutate(week = week(interval60)) %>%
  ggplot(aes(Wind, Trip_Count)) + 
    geom_point(size = .5, color = "black") + geom_smooth(method = "lm", se= FALSE, color="darkred") +
    facet_wrap(~week, ncol=8) + 
    labs(title="Trip Count As a Fuction of Wind by Week",
         x="Wind Speed", y="Mean Trip Count") + 
  theme(plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 12, face = "bold"), 
        axis.text.x = element_blank(),
    axis.ticks.x = element_blank(), 
        axis.text.y=element_text(size=8), 
        axis.title=element_text(size=10), 
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill=NA, size=0.8)) + 
    theme(plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 12, face = "bold"), 
        axis.text.x = element_blank(),
    axis.ticks.x = element_blank(), 
        axis.text.y=element_text(size=8), 
        axis.title=element_text(size=10), 
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill=NA, size=0.8))

Serial Correlation

If the number of bike trips shows serial (temporal) correlation, incorporating time lag features into our model could significantly enhance its predictive power. The underlying concept is that bikeshare demand at any given hour is likely to be influenced by demand patterns immediately preceding or following that hour. For example, if we know there was a high number of trips during one hour, we can reasonably predict that nearby hours will also have elevated demand. To capture these temporal dependencies, we computed six different lagged variables, each representing the number of trips one, two, three, four, twelve hours earlier, and one day earlier. These lag features allow us to account for short-term fluctuations as well as daily patterns, providing a more comprehensive framework for modeling bikeshare activity. By leveraging these time-based correlations, our model can better anticipate shifts in demand, improving accuracy and reliability.

bike.panel <- 
  bike.panel %>% 
  arrange(start_station_id, 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)) %>%
   mutate(day = yday(interval60))

We visualized the relationships between the number of trips and the various lagged hours to better understand these temporal correlations. The analysis revealed some interesting patterns. There is a strong, linear, and negative relationship between the number of trips and the lagged variable representing 12 hours prior. For instance, the number of rides recorded at 5 PM on a given day tends to be negatively correlated with the number of rides at 5 AM on the same day, likely reflecting contrasting peak and off-peak usage times.

Conversely, we observed a strong, positive, and linear correlation between the number of trips at a given hour and both the immediate next hour (laghour) and the same hour on the following day (lag1day). This indicates that bikeshare demand is highly predictable from one hour to the next and tends to follow consistent daily patterns. However, the strength of the correlation decreases for the lag variables representing two and three hours prior. For example, the number of rides at 4 PM is more strongly correlated with the number of rides at 5 PM than with rides at 6 or 7 PM. This gradual weakening of correlation over time highlights how bikeshare demand is most heavily influenced by recent past activity, making these time lag features valuable for modeling purposes.

as.data.frame(bike.panel) %>%
    group_by(interval60) %>% 
    summarise_at(vars(starts_with("lag"), "Trip_Count"), mean, na.rm = TRUE) %>%
    gather(Variable, Value, -interval60, -Trip_Count) %>%
  ggplot(aes(x = Value , y = Trip_Count)) +
    geom_point(size = .5, color = "black") + geom_smooth(method = "lm", se= FALSE, color="darkred") +
  facet_wrap(~Variable, ncol = 6)+
  labs(x = "Variable", y = "Correlation", title = "Correlation Between Serial Lag Trips and Trip Count") +
  theme(axis.text.x = element_text(angle = 45, hjust = 1)) + 
  theme(plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 12, face = "bold"), 
        axis.text.x = element_blank(),
    axis.ticks.x = element_blank(), 
        axis.text.y=element_text(size=8), 
        axis.title=element_text(size=10), 
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill=NA, size=0.8))

Spatial Correlation

We previously established that spatial autocorrelation is present in our data, indicating that the number of trips at one station during a specific time is related to the number of trips at nearby stations. In other words, knowing the demand at one station can help us estimate demand at neighboring stations. To leverage this spatial relationship, we created a new dataframe called lag_result.

We then implemented a nested loop to systematically calculate spatial lags for each hour in August 2023. For each hour, we identified the five nearest neighboring stations for every station and computed the average number of trips across these neighbors. This provided a measure of spatial influence, capturing how demand at one station is affected by activity at surrounding stations. Finally, we integrated these spatial lag values back into our panel dataset, enhancing our model’s ability to predict bikeshare demand by incorporating both spatial and temporal dependencies.

library(sf)
library(spdep)
library(dplyr)
library(parallel)

# Step 1: Filter data once and convert to sf object
bike.panel_sf <- bike.panel %>%
  st_as_sf(coords = c("start_lng", "start_lat"), crs = 4326)

# Step 2: Define a function to calculate spatial lag for a given day and hour
calculate_spatial_lag <- function(d, h, bike_data) {
  # Load required libraries within the function
  library(dplyr)
  library(sf)
  library(spdep)
  library(lubridate)
  
  # Filter data for the specified day and hour
  spacelag <- bike_data %>%
    mutate(hour = hour(interval60), day = yday(interval60)) %>%
    filter(day == d & hour == h)
  
  if (nrow(spacelag) == 0) return(NULL)  # Skip if no data
  
  # Compute spatial neighbors and weights
  coords <- st_coordinates(spacelag)
  neighborList <- knn2nb(knearneigh(coords, k = 5))
  spatialWeights <- nb2listw(neighborList, style = "W")
  
  # Calculate spatial lag
  spacelag <- spacelag %>%
    mutate(lagTrip = lag.listw(spatialWeights, Trip_Count))
  
  return(spacelag)
}

# Step 3: Use parallel processing to speed up the loop
cl <- makeCluster(detectCores() - 1)  # Create a cluster using available cores
clusterExport(cl, c("bike.panel_sf", "calculate_spatial_lag"))  # Export data and function
clusterEvalQ(cl, {  # Load required libraries in each worker
  library(dplyr)
  library(sf)
  library(spdep)
  library(lubridate)
})
## [[1]]
##  [1] "lubridate" "spdep"     "spData"    "sf"        "dplyr"     "stats"    
##  [7] "graphics"  "grDevices" "utils"     "datasets"  "methods"   "base"     
## 
## [[2]]
##  [1] "lubridate" "spdep"     "spData"    "sf"        "dplyr"     "stats"    
##  [7] "graphics"  "grDevices" "utils"     "datasets"  "methods"   "base"     
## 
## [[3]]
##  [1] "lubridate" "spdep"     "spData"    "sf"        "dplyr"     "stats"    
##  [7] "graphics"  "grDevices" "utils"     "datasets"  "methods"   "base"
# Run the loop in parallel
lag_result_list <- parLapply(cl, 213:243, function(d) {
  lapply(0:23, function(h) calculate_spatial_lag(d, h, bike.panel_sf))
})

stopCluster(cl)  # Stop the cluster

# Combine the results into a single data frame
lag_result <- do.call(rbind, unlist(lag_result_list, recursive = FALSE)) %>%
  mutate(start_lng = unlist(map(geometry, 1)),
         start_lat = unlist(map(geometry, 2))) %>%
  st_drop_geometry()

# Final result
bike.panel <- lag_result

The figure below illustrates a consistent, strong, and positive linear relationship between the number of trips at a station and the number of trips at nearby stations throughout all weeks in August. This confirms the presence of significant spatial dependence, suggesting that stations in close proximity tend to experience similar levels of demand. Given this finding, we will incorporate this spatial lag variable into our regression analysis to improve the model’s predictive accuracy by accounting for the influence of nearby station activity.

bike.panel %>%
  group_by(interval60) %>% 
  summarize(Trip_Count = mean(Trip_Count),
            Lag_Count = mean(lagTrip)) %>%
  mutate(week = week(interval60)) %>%
  ggplot(aes(Lag_Count, Trip_Count)) + 
    geom_point(size = 1, color = "#6E0E0A") + geom_smooth(method = "lm", se= FALSE, color="#124E78") +
    facet_wrap(~week, ncol=8) + 
    labs(title="Correlation Between Spatial Lag Trips and Trip Count",
         x="Lag Trip Count", y="Mean Trip Count") + 
  theme(plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 12, face = "bold"), 
        axis.text.x = element_blank(),
    axis.ticks.x = element_blank(), 
        axis.text.y=element_text(size=8), 
        axis.title=element_text(size=10), 
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill=NA, size=0.8))

OLS Regression

We divided our five weeks of data into a training set consisting of three weeks and a testing set comprising the remaining two weeks. Using the training data, we estimated four distinct linear regression models, each incorporating different fixed effects:

  • reg1 captures only temporal factors, including hour-of-day fixed effects, day of the week, wind speed, and temperature.
  • reg2 emphasizes spatial components, using station-specific effects and weather conditions.
  • reg3 combines both spatial and temporal elements to account for interactions between space and time.
  • reg4 extends the third model by adding both time and spatial lag features, capturing temporal and spatial dependencies to enhance predictive accuracy.
bike.Train <- filter(bike.panel, week <= 33)
bike.Test <- filter(bike.panel, week > 33)

# Time
reg1 <- 
  lm(Trip_Count ~  hour(interval60) + dotw + Temperature + Wind_Speed, data=bike.Train)

# Space
reg2 <- 
  lm(Trip_Count ~  start_station_name + Temperature + Wind_Speed,  data=bike.Train)

# Time and Space
reg3 <- 
  lm(Trip_Count ~  start_station_name + hour(interval60) + dotw + Temperature + Wind_Speed, 
     data=bike.Train)

# Time and Space and TimeLag and SpaceLag
reg4 <- 
  lm(Trip_Count ~  start_station_name +  hour(interval60) + dotw + Temperature + Wind_Speed +
                   lagHour + lag2Hours + lag12Hours + lag1day + lagTrip, 
     data=bike.Train)

Making Predictions

We utilized the purrr package to efficiently iterate over a set of nested data frames, allowing us to compare different model specifications. To prepare for testing, we organized our data by week, nesting it into separate tibbles. We then created a simple prediction function that takes a tibble (dat) and a regression model (fit) as inputs and generates predictions, returning them as pred. This approach streamlines the process of applying our models across multiple subsets of data, making it easy to evaluate and compare their performance.

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

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

The nested data format enables us to iterate through each model for every week and compute summary statistics efficiently. In the code block that follows, we generate week_predictions for each week within the testing set. Using the map function from purrr, we apply the model_pred function to each nested tibble. For instance, in the first line of the mutate operation, a new column called Time_FE is created, which contains predictions from reg1, the model that includes time fixed effects. These predictions are produced by mapping model_pred to each data subset, with fit set to the reg1 model. This method allows us to easily apply multiple models and gather prediction results for comparison.

week_predictions <- 
  bike.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_Lags = map(.x = data, fit = reg4, .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))

Error Analysis

When comparing the mean absolute errors (MAE) across our four models, we observe that Model 1, which only incorporates time effects, has the highest error rate. Models 2 and 3, which account for spatial effects and the combination of space and time, show improved accuracy. However, the greatest enhancement in predictive performance comes from Model 4, which includes both space and time lag features, significantly reducing the error. To ensure the reliability of Model 4, we also performed a variance inflation factor (VIF) analysis to confirm that multicollinearity was not an issue among the predictors.

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 = palette4) +
    labs(title = "Mean Absolute Errors by Model Specification and Week") +
  theme(plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 12, face = "bold"), 
        axis.text.x = element_blank(),
    axis.ticks.x = element_blank(), 
        axis.text.y=element_text(size=8), 
        axis.title=element_text(size=10), 
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill=NA, size=0.8))

The figure below provides a visual comparison of the accuracy of each model by plotting the predicted versus observed number of trips. Among the four models, Model 4 clearly demonstrates superior predictive performance, closely aligning with the actual bikeshare demand. This highlights the effectiveness of incorporating both spatial and temporal lag features, making Model 4 the most robust and reliable in forecasting trip counts.

week_predictions %>% 
    mutate(interval60 = map(data, pull, interval60),
           start_station_id = map(data, pull, start_station_id)) %>%
    dplyr::select(interval60, start_station_id, Observed, Prediction, Regression) %>%
    unnest() %>%
    gather(Variable, Value, -Regression, -interval60, -start_station_id) %>%
    group_by(Regression, Variable, interval60) %>%
    summarize(Value = sum(Value)) %>%
    ggplot(aes(interval60, Value, colour=Variable)) + 
      geom_line(size = 1.1) + 
      scale_color_manual(values=palette2)+
      facet_wrap(~Regression, ncol=1) +
      labs(title = "Predicted/Observed Bike Share Time Series", subtitle = "Jersey City: a test set of 2 weeks",  x = "Hour", y= "Station Trips") + 
   theme(plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 12, face = "bold"), 
        axis.text.x = element_blank(),
    axis.ticks.x = element_blank(), 
        axis.text.y=element_text(size=8), 
        axis.title=element_text(size=10), 
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill=NA, size=0.8))

To evaluate whether our predictions generalize well across different locations and times, we mapped the mean absolute error (MAE) of Model 4 across various stations. The results show that the model struggles to accurately predict the number of rides at stations located along the Hudson River, which tend to experience high bikeshare demand on both weekdays and weekends. However, for stations with lower ridership throughout August, the model performs significantly better, demonstrating reasonable predictive accuracy in those areas.

week_predictions %>% 
    mutate(interval60 = map(data, pull, interval60),
           start_station_id = map(data, pull, start_station_id), 
           start_latitude = map(data, pull, start_lat), 
           start_longitude = map(data, pull, start_lng)) %>%
    select(interval60, start_station_id, start_longitude, start_latitude, Observed, Prediction, Regression) %>%
    unnest() %>%
  filter(Regression == "DTime_Space_FE_Lags") %>%
  group_by(start_station_id, start_longitude, start_latitude) %>%
  summarize(MAE = mean(abs(Observed-Prediction), na.rm = TRUE))%>%
ggplot(.)+
  geom_sf(data = jcTracts,  fill = "white")+
  geom_point(aes(x = start_longitude, y = start_latitude, color = MAE), 
             fill = "transparent")+
  scale_color_continuous(low = "#124E78", high = "#6E0E0A", name= "MAE")+
 ylim(min(bike_census$start_lat), max(bike_census$start_lat))+
  xlim(min(bike_census$start_lng), max(bike_census$start_lng)) +
  labs(title="Mean Abs Error, Test Set, Model 4") + 
  theme(axis.text.x=element_blank(),
        axis.text.y=element_blank(),
        axis.ticks =element_blank(),
        axis.title.x = element_blank(),
        axis.title.y = element_blank(),
        plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 12, face = "bold"),
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill=NA, linewidth=0.8)
        )

When examining Model 4’s performance across both space and time, the figure demonstrates that the model maintains consistent predictions throughout different periods. Stations identified as having high demand on weekends are similarly predicted to have high demand during weekdays. However, the errors persist over time, indicating that stations with historically high demand also exhibit greater variability in ride counts, making them more challenging to predict with our current set of features. In fact, our model only explains 50.12% of the variability, which is a limitation. The concern is that if these larger errors remain unaddressed for high-demand stations, we risk failing to adequately meet bikeshare users’ needs at these crucial locations.

week_predictions %>% 
    mutate(interval60 = map(data, pull, interval60),
           start_station_id = map(data, pull, start_station_id), 
           start_latitude = map(data, pull, start_lat), 
           start_longitude = map(data, pull, start_lng),
           dotw = map(data, pull, dotw) ) %>%
    select(interval60, start_station_id, start_longitude, 
           start_latitude, Observed, Prediction, Regression,
           dotw) %>%
    unnest() %>%
  filter(Regression == "DTime_Space_FE_Lags")%>%
  mutate(weekend = ifelse(dotw %in% c("Sun", "Sat"), "Weekend", "Weekday"),
         time_of_day = case_when(hour(interval60) < 7 | hour(interval60) > 18 ~ "Overnight",
                                 hour(interval60) >= 7 & hour(interval60) < 10 ~ "AM Rush",
                                 hour(interval60) >= 10 & hour(interval60) < 15 ~ "Mid-Day",
                                 hour(interval60) >= 15 & hour(interval60) <= 18 ~ "PM Rush")) %>%
  group_by(start_station_id, weekend, time_of_day, start_longitude, start_latitude) %>%
  summarize(MAE = mean(abs(Observed-Prediction), na.rm = TRUE))%>%
  ggplot(.)+
  geom_sf(data = jcTracts, color = "grey", fill = "transparent")+
  geom_point(aes(x = start_longitude, y = start_latitude, color = MAE), 
             fill = "transparent", size = 0.5)+
  scale_color_continuous(low = "#124E78", high = "#6E0E0A", name= "MAE")+
 ylim(min(bike_census$start_lat), max(bike_census$start_lat))+
  xlim(min(bike_census$start_lng), max(bike_census$start_lng)) +
  facet_grid(weekend~time_of_day)+
  labs(title="Mean Absolute Errors by Time, Test Set, Model 4") + 
  theme(axis.text.x=element_blank(),
        axis.text.y=element_blank(),
        axis.ticks =element_blank(),
        axis.title.x = element_blank(),
        axis.title.y = element_blank(),
        plot.subtitle = element_text(size = 9,face = "italic"),
        plot.title = element_text(size = 12, face = "bold"),
        panel.background = element_blank(),
        panel.border = element_rect(colour = "grey", fill=NA, linewidth=0.8)
        )

Cross-Validation

Lastly, we conducted 100 cross-validation tests using Model 4 on data spanning all five weeks. The mean absolute error (MAE) was 0.44, indicating that while this model is our most effective solution so far, there is still a notable level of prediction error. To improve the model further, it would be valuable to cross-validate against socio-economic indicators. This approach could reveal whether bike demand at stations in specific neighborhoods is being disproportionately under- or over-predicted. However, our current model does not incorporate socioeconomic variables as predictors, and the dynamics in Jersey City—such as the continued development of new stations—may also impact demand patterns.

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

reg.cv <- train(Trip_Count ~ start_station_name +  hour(interval60) + dotw + Temperature + Wind_Speed + lagHour + lag2Hours + lag12Hours + lag1day + lagTrip, data=bike.panel, method = "lm", trControl = fitControl, na.action = na.pass)

reg.cv$resample %>% 
  summarise(MAE = mean(reg.cv$resample[,3]),
            sd(reg.cv$resample[,3])
) %>%
  kbl(col.name=c('Mean Absolute Error','Standard Deviation of MAE')) %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"))
Mean Absolute Error Standard Deviation of MAE
0.4418984 0.029647

Conclusion

In summary, this report highlights the urgent need for efficient bikeshare management strategies in Jersey City and presents a machine learning approach to address this challenge. Our analysis demonstrates that bikeshare demand is influenced by a combination of spatial, temporal, and weather-related factors. Specifically, the model shows that forecasting demand is more accurate when considering the number of trips in the preceding and following hours, the same hour on the previous day, and nearby station activity. We also discovered that demand peaks during windy and hot days in August, with the highest usage occurring during weekday PM rush hours and, to a lesser extent, overnight. Weekends see a significant spike in demand during the PM rush as well. Notably, Citi Bike is heavily used for commuting to New York City, making stations along the Hudson River, especially near ferry terminals, the most active.

We further explored specific high-demand stations in detail. Grove St PATH station recorded an impressive 1,117 rides during the weekday PM rush and 801 rides overnight in August, reflecting its strategic location in downtown Jersey City. Hoboken Terminal - River St & Hudson Pl had 864 rides during weekday PM rush hours, likely due to its proximity to major transit connections. South Waterfront Walkway - Sinatra Dr & 1 St station saw 319 rides overnight on a weekend and 731 during the weekday PM rush, benefiting from its location near the NJ Transit Terminal and the popular Pier A Park. These findings suggest that stations experience higher demand because they serve key areas such as employment hubs, transit nodes for commuting, and recreational spaces along the waterfront. To address this, local authorities should consider rebalancing bike supplies more strategically at these critical locations.

Although our model successfully captures over half of the demand variations, it still requires further refinement to increase its predictive accuracy. Without these improvements, the model risks failing to meet rider needs and complicating bike redistribution efforts. Nevertheless, the straightforward and open-source nature of our approach makes it a valuable tool for bikeshare operators looking to improve service quality and optimize bike availability. As urban transportation continues to evolve, the use of predictive models like ours will be vital for creating sustainable, well-balanced mobility systems.

LS0tCnRpdGxlOiAiUHJlZGljdGluZyBCaWtlc2hhcmUgRGVtYW5kIGluIEplcnNleSBDaXR5IgphdXRob3I6ICJZdXRvbmcgSmlhbmciCmRhdGU6ICJOb3YsMTUsMjAyNCIKb3V0cHV0OgogIGh0bWxfZG9jdW1lbnQ6CiAgICB0aGVtZTogY29zbW8gCiAgICB0b2M6IHllcwogICAgdG9jX2Zsb2F0OiB5ZXMKICAgIGNvZGVfZm9sZGluZzogaGlkZQogICAgY29kZV9kb3dubG9hZDogeWVzCmVkaXRvcl9vcHRpb25zOgogIG1hcmtkb3duOgogICAgd3JhcDogc2VudGVuY2UKLS0tCgoKYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9CmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSkKYGBgCgoKIyBJbnRyb2R1Y3Rpb24KCkJpa2VzaGFyZSBwcm9ncmFtcyBoYXZlIGJlY29tZSBhbiBlc3NlbnRpYWwgcGFydCBvZiB1cmJhbiB0cmFuc3BvcnRhdGlvbiwgb2ZmZXJpbmcgYSBzdXN0YWluYWJsZSBhbmQgZmxleGlibGUgY29tbXV0aW5nIG9wdGlvbi4gQ2l0aSBCaWtlLCBsYXVuY2hlZCBieSBMeWZ0IGluIDIwMDggaW4gTmV3IFlvcmsgQ2l0eSwgaGFzIGV4cGFuZGVkIHRvIEplcnNleSBDaXR5IHRvIG1lZXQgdGhlIG5lZWRzIG9mIGNvbW11dGVycyB0cmF2ZWxpbmcgdG8gTWFuaGF0dGFuLiBBcyBDaXRpIEJpa2UncyBwb3B1bGFyaXR5IGdyb3dzLCBtYW5hZ2luZyBiaWtlIGF2YWlsYWJpbGl0eSBlZmZlY3RpdmVseSBiZWNvbWVzIGNydWNpYWwsIGVzcGVjaWFsbHkgZ2l2ZW4gdGhlIGZsdWN0dWF0aW9ucyBpbiBkZW1hbmQgdGhyb3VnaG91dCB0aGUgZGF5IGFuZCBhY3Jvc3MgZGlmZmVyZW50IGxvY2F0aW9ucy4KCk1haW50YWluaW5nIGEgYmFsYW5jZWQgZGlzdHJpYnV0aW9uIG9mIGJpa2VzLCBrbm93biBhcyAicmUtYmFsYW5jaW5nLCIgaXMgYSBjb21wbGV4IGNoYWxsZW5nZSBpbmZsdWVuY2VkIGJ5IHRoZXNlIHNwYXRpYWwgYW5kIHRlbXBvcmFsIGRlbWFuZCBwYXR0ZXJucy4gQ29tcGFuaWVzIGxpa2UgVWJlciBhbmQgTHlmdCB1c2UgcHJvcHJpZXRhcnkgYWxnb3JpdGhtcyB0byBhZGRyZXNzIHRoZXNlIGlzc3VlcywgYnV0IHRoZSBkZXRhaWxzIG9mIHRoZWlyIG1vZGVscyBhcmUgbm90IHB1YmxpY2x5IGF2YWlsYWJsZS4gVGhpcyByZXBvcnQgYWltcyB0byBkZXZlbG9wIGEgdHJhbnNwYXJlbnQsIG9wZW4tc291cmNlIGFwcHJvYWNoIHRvIGZvcmVjYXN0aW5nIGJpa2UgZGVtYW5kIGluIEplcnNleSBDaXR5IHVzaW5nIE9yZGluYXJ5IExlYXN0IFNxdWFyZXMgKE9MUykgcmVncmVzc2lvbi4gQnkgYW5hbHl6aW5nIGhpc3RvcmljYWwgZGF0YSwgaW5jbHVkaW5nIHdlYXRoZXIgYW5kIGJpa2UgdXNhZ2UgcGF0dGVybnMsIG91ciBnb2FsIGlzIHRvIGNyZWF0ZSBhIHNpbXBsZSB5ZXQgZWZmZWN0aXZlIG1vZGVsIHRvIGFudGljaXBhdGUgZGVtYW5kIHNwaWtlcyBhbmQgZ3VpZGUgZWZmaWNpZW50IGJpa2UgcmVhbGxvY2F0aW9uLgoKV2Ugd2lsbCBiZWdpbiBieSBsb2FkaW5nIGFuZCBwcm9jZXNzaW5nIG91ciBkYXRhc2V0cywgZm9sbG93ZWQgYnkgYW4gZXhwbG9yYXRvcnkgYW5hbHlzaXMgb2YgdXNhZ2UgdHJlbmRzIGFuZCB3ZWF0aGVyIGltcGFjdHMuIE5leHQsIHdl4oCZbGwgY3JlYXRlIGEgc3BhY2UtdGltZSBwYW5lbCB0byBleGFtaW5lIGNvcnJlbGF0aW9ucyBhbmQgZmluYWxseSBkZXZlbG9wIGFuZCBldmFsdWF0ZSBmb3VyIHByZWRpY3RpdmUgbW9kZWxzLCB0ZXN0aW5nIHRoZWlyIHBlcmZvcm1hbmNlIGFuZCByZWxpYWJpbGl0eS4KCgojIFNldCBVcAoKYGBge3IgbGlicmFyeSBhbmQgZGF0YSwgaW5jbHVkZT1GQUxTRX0KbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkoc2YpCmxpYnJhcnkobHVicmlkYXRlKQpsaWJyYXJ5KHRpZ3JpcykKbGlicmFyeShnZ2FuaW1hdGUpCmxpYnJhcnkocmllbSkKbGlicmFyeShncmlkRXh0cmEpCmxpYnJhcnkoa25pdHIpCmxpYnJhcnkoa2FibGVFeHRyYSkKbGlicmFyeSh0aWR5Y2Vuc3VzKQpsaWJyYXJ5KG1hcHZpZXcpCmxpYnJhcnkoc2ZkZXApCmxpYnJhcnkoc3BkZXApCmxpYnJhcnkoY2FyZXQpCmxpYnJhcnkgKHJlYWRyKQoKb3B0aW9ucyh0aWdyaXNfY2xhc3MgPSAic2YiKQpzb3VyY2UoImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS91cmJhblNwYXRpYWwvUHVibGljLVBvbGljeS1BbmFseXRpY3MtTGFuZGluZy9tYXN0ZXIvZnVuY3Rpb25zLnIiKQoKcGFsZXR0ZTUgPC0gYygiIzFGNzhCNCIsICIjMzNBMDJDIiwgIiNFMzFBMUMiLCAiI0ZGN0YwMCIsICIjNkEzRDlBIikgICMgQmx1ZSwgR3JlZW4sIFJlZCwgT3JhbmdlLCBQdXJwbGUKcGFsZXR0ZTQgPC0gYygiIzFGNzhCNCIsICIjMzNBMDJDIiwgIiNFMzFBMUMiLCAiIzZBM0Q5QSIpICAgICAgICAgICAgIyBCbHVlLCBHcmVlbiwgUmVkLCBQdXJwbGUKcGFsZXR0ZTIgPC0gYygiIzFGNzhCNCIsICIjRTMxQTFDIikgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBCbHVlLCBSZWQKCgp0aWR5Y2Vuc3VzOjpjZW5zdXNfYXBpX2tleSgiZTc5ZjM3MDZiNmQ2MTI0OTk2OGM2Y2U4ODc5NGY2ZjU1NmU1YmYzZCIsIG92ZXJ3cml0ZSA9IFRSVUUpCmBgYAoKVGhlIGRhdGEgdXNlZCBpbiB0aGlzIGFuYWx5c2lzIGNvbWVzIGZyb20gTllDIE9wZW5EYXRhLCB3aGljaCBwcm92aWRlcyBtb250aGx5IHJlY29yZHMgb2YgQ2l0aSBCaWtlIHVzYWdlLiBFdmVuIHRob3VnaCBKZXJzZXkgQ2l0eSBpcyBpbiBOZXcgSmVyc2V5LCBDaXRpIEJpa2UncyBvcGVyYXRpb25zIGFyZSBsYXJnZWx5IGNlbnRlcmVkIGluIE5ldyBZb3JrIENpdHksIGFuZCB0aHVzIHRoZSBkYXRhIGlzIG1hbmFnZWQgdGhyb3VnaCBOWUMncyBzeXN0ZW0uIEZvciB0aGlzIHN0dWR5LCB3ZSBmb2N1c2VkIG9uIGJpa2VzaGFyZSB0cmlwcyBpbiBKZXJzZXkgQ2l0eSBkdXJpbmcgQXVndXN0IDIwMjQsIGNhcHR1cmluZyBpbmZvcm1hdGlvbiBhYm91dCB0aGUgc3RhcnRpbmcgYW5kIGVuZGluZyBzdGF0aW9ucyBmb3IgZWFjaCByaWRlLiBBZGRpdGlvbmFsbHksIHdlIGFjcXVpcmVkIHRoZSBjZW5zdXMgdHJhY3QgZ2VvbWV0cmllcyBmb3IgYWxsIG9mIEh1ZHNvbiBDb3VudHksIHdoaWNoIGVuY29tcGFzc2VzIEplcnNleSBDaXR5LCB1c2luZyB0aGUgdGlkeWNlbnN1cyBwYWNrYWdlLgoKYGBge3Igb2J0YWluIGNlbnN1cyBnZW9tIGFuZCBiaWtlIGRhdGEsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0UsIHJlc3VsdHM9J2hpZGUnLCBjYWNoZT1UUlVFfQoKbnljYmlrZSA8LSByZWFkLmNzdigiL1VzZXJzL2Jpbi9Eb2N1bWVudHMvTVVTQS9NVVNBIDUwODAvSkMtMjAyMzA4LWNpdGliaWtlLXRyaXBkYXRhLmNzdiIpCgpuakNlbnN1cyA8LSBnZXRfYWNzKGdlb2dyYXBoeSA9ICJ0cmFjdCIsIAogICAgICAgICAgdmFyaWFibGVzID0gYygiQjAxMDAzXzAwMSIsICJCMTkwMTNfMDAxIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICJCMDIwMDFfMDAyIiwgIkIwODAxM18wMDEiLAogICAgICAgICAgICAgICAgICAgICAgICAiQjA4MDEyXzAwMSIsICJCMDgzMDFfMDAxIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICJCMDgzMDFfMDEwIiwgIkIwMTAwMl8wMDEiKSwgCiAgICAgICAgICB5ZWFyID0gMjAyMSwgCiAgICAgICAgICBzdGF0ZSA9ICJOSiIsIAogICAgICAgICAgZ2VvbWV0cnkgPSBUUlVFLCAKICAgICAgICAgIGNvdW50eT1jKCJIdWRzb24iKSwKICAgICAgICAgIG91dHB1dCA9ICJ3aWRlIikgJT4lCiAgcmVuYW1lKFRvdGFsX1BvcCA9ICBCMDEwMDNfMDAxRSwKICAgICAgICAgTWVkX0luYyA9IEIxOTAxM18wMDFFLAogICAgICAgICBNZWRfQWdlID0gQjAxMDAyXzAwMUUsCiAgICAgICAgIFdoaXRlX1BvcCA9IEIwMjAwMV8wMDJFLAogICAgICAgICBUcmF2ZWxfVGltZSA9IEIwODAxM18wMDFFLAogICAgICAgICBOdW1fQ29tbXV0ZXJzID0gQjA4MDEyXzAwMUUsCiAgICAgICAgIE1lYW5zX29mX1RyYW5zcG9ydCA9IEIwODMwMV8wMDFFLAogICAgICAgICBUb3RhbF9QdWJsaWNfVHJhbnMgPSBCMDgzMDFfMDEwRSkgJT4lCiAgc2VsZWN0KFRvdGFsX1BvcCwgTWVkX0luYywgV2hpdGVfUG9wLCBUcmF2ZWxfVGltZSwKICAgICAgICAgTWVhbnNfb2ZfVHJhbnNwb3J0LCBUb3RhbF9QdWJsaWNfVHJhbnMsCiAgICAgICAgIE1lZF9BZ2UsCiAgICAgICAgIEdFT0lELCBnZW9tZXRyeSkgJT4lCiAgbXV0YXRlKFBlcmNlbnRfV2hpdGUgPSBXaGl0ZV9Qb3AgLyBUb3RhbF9Qb3AsCiAgICAgICAgIE1lYW5fQ29tbXV0ZV9UaW1lID0gVHJhdmVsX1RpbWUgLyBUb3RhbF9QdWJsaWNfVHJhbnMsCiAgICAgICAgIFBlcmNlbnRfVGFraW5nX1B1YmxpY19UcmFucyA9IFRvdGFsX1B1YmxpY19UcmFucyAvIE1lYW5zX29mX1RyYW5zcG9ydCkKCmpjVHJhY3RzIDwtIG5qQ2Vuc3VzICU+JQogIGFzLmRhdGEuZnJhbWUoKSAlPiUKICBkaXN0aW5jdChHRU9JRCwgLmtlZXBfYWxsID0gVFJVRSkgJT4lCiAgc2VsZWN0KEdFT0lELCBnZW9tZXRyeSkgJT4lIAogIHN0X3NmKCkKYGBgCgpUaGUgYmlrZXNoYXJlIGRhdGFzZXQgd2XigJlyZSB3b3JraW5nIHdpdGggY29udGFpbnMgc3BhdGlhbCBkZXRhaWxzLCBidXQgaXQgaXNuJ3QgaW5oZXJlbnRseSBmb3JtYXR0ZWQgYXMgc3BhdGlhbCBkYXRhLiBUbyBjb25kdWN0IGEgc3BhdGlhbCBhbmFseXNpcywgd2UgZmlyc3QgbmVlZCB0byBpbnRlZ3JhdGUgaXQgd2l0aCB0aGUgY2Vuc3VzIGdlb21ldHJ5LiBPdXIgZm9jdXMgaXMgb24gdGhlIHN0YXJ0aW5nIGxvY2F0aW9ucyBvZiByaWRlcywgYXMgb3VyIGFpbSBpcyB0byBwcmVkaWN0IGJpa2UgYXZhaWxhYmlsaXR5IGF0IHN0YXRpb25zIHdoZW4gdXNlcnMgaW5pdGlhdGUgYSB0cmlwLiBUbyBwcmVwYXJlIHRoZSBkYXRhLCB3ZSBmaWx0ZXIgb3V0IHJlY29yZHMgbGFja2luZyBsYXRpdHVkZSBvciBsb25naXR1ZGUgaW5mb3JtYXRpb24gYW5kIGFkZHJlc3MgZXJyb3JzIHdoZXJlIGNlcnRhaW4gY29vcmRpbmF0ZXMgaW5jb3JyZWN0bHkgcGxhY2Ugc3RhdGlvbnMgaW4gdGhlIEh1ZHNvbiBSaXZlci4gVGhlc2UgaW5hY2N1cmF0ZSBlbnRyaWVzIGFyZSByZW1vdmVkIHRvIGVuc3VyZSBkYXRhIHF1YWxpdHkuCgoKCmBgYHtyIGFkZCBjZW5zdXMgdHJhY3R9CgpiaWtlX2NlbnN1cyA8LSBzdF9qb2luKG55Y2Jpa2UgJT4lIAogICAgICAgICAgICAgICAgICAgICAgICAgZmlsdGVyKGlzLm5hKHN0YXJ0X2xhdCkgPT0gRkFMU0UgJgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlzLm5hKHN0YXJ0X2xuZykgPT0gRkFMU0UgJgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlzLm5hKGVuZF9sYXQpID09IEZBTFNFICYKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpcy5uYShlbmRfbG5nKSA9PSBGQUxTRSkgJT4lCiAgICAgICAgICAgICAgICAgICAgICAgICBzdF9hc19zZiguLCBjb29yZHMgPSBjKCJzdGFydF9sbmciLCAic3RhcnRfbGF0IiksIGNycyA9IDQzMjYpLAogICAgICAgICAgICAgICAgICAgICAgIGpjVHJhY3RzICU+JSAgc3RfdHJhbnNmb3JtKGNycz00MzI2KSwKICAgICAgICAgICAgICAgICAgICAgICBqb2luPXN0X2ludGVyc2VjdHMsIGxlZnQgPSBUUlVFKSAlPiUKICByZW5hbWUoT3JpZ2luLlRyYWN0ID0gR0VPSUQpICU+JQogIG11dGF0ZShzdGFydF9sbmcgPSB1bmxpc3QobWFwKGdlb21ldHJ5LCAxKSksCiAgICAgICAgIHN0YXJ0X2xhdCA9IHVubGlzdChtYXAoZ2VvbWV0cnksIDIpKSklPiUKICBmaWx0ZXIoIShyaWRlX2lkICVpbiUgYygiQkVDOUNDNEQxMDA3Qzc1MyIsICI4NjA4N0Y0MzE3ODVFREU3IiwgIkExQUFCOTI2MzE0Mzk4ODMiLCAiNzE5MzhEQTA4NTI0MkRDQyIsICI0MjAzQzc3NjY4QzQ3Qzc1IiwgIkM5MjQxMENBMkFFRjM5NDciLCAiNkUwMkNCRjM2QzkwRjI0NCIsICIwMjU5ODg1MjUzRkQyMzhFIiwgIkIzOTEzNkIzN0FGQjVGRTkiLCAiMUVDOTM3NkQxNTU5QzUxQyIsICJDRERGMTZEM0EzN0JFMzcwIikpKSAlPiUgCiAgYXMuZGF0YS5mcmFtZSgpCiAgCmBgYAoKVGhlIGZvbGxvd2luZyBtYXAgc2hvd3MgdGhlIExvY2FydGlvbiBvZiByaWRlIHN0YXJ0aW5nIHBvaW50cyBpbiBKZXJzZXkgQ2l0eQoKYGBge3IgZGlzdHJpYnV0aW9uIG9mIHN0YXRpb25zfQoKZ2dwbG90KCkrCiAgZ2VvbV9zZihkYXRhID0gamNUcmFjdHMgJT4lCiAgICAgICAgICBzdF90cmFuc2Zvcm0oY3JzPTQzMjYpLCBmaWxsPSJ3aGl0ZSIpKwogIGdlb21fcG9pbnQoZGF0YSA9IGJpa2VfY2Vuc3VzLCBhZXMoeD1zdGFydF9sbmcsIHkgPSBzdGFydF9sYXQpLCAKICAgICAgICAgICAgY29sb3IgPSAiYmx1ZSIsIGFscGhhID0gMSwgc2l6ZSA9IDAuMykgKyAKICB5bGltKG1pbihiaWtlX2NlbnN1cyRzdGFydF9sYXQpLCBtYXgoYmlrZV9jZW5zdXMkc3RhcnRfbGF0KSkrCiAgeGxpbShtaW4oYmlrZV9jZW5zdXMkc3RhcnRfbG5nKSwgbWF4KGJpa2VfY2Vuc3VzJHN0YXJ0X2xuZykpICsKICBsYWJzKHRpdGxlID0gIkxvY2F0aW9uIG9mIFJpZGVzIFN0YXJ0aW5nIFBvaW50cyBpbiBKZXJzZXkgQ2l0eSIpICsKICB0aGVtZShheGlzLnRleHQueD1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGlja3MgPWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aXRsZS55ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDksZmFjZSA9ICJpdGFsaWMiKSwKICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgZmFjZSA9ICJib2xkIiksCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X3JlY3QoY29sb3VyID0gImdyZXkiLCBmaWxsPU5BLCBsaW5ld2lkdGg9MC44KQogICAgICAgICkKCmBgYAoKV2UgY2Fycnkgb3V0IGVzc2VudGlhbCBmZWF0dXJlIGVuZ2luZWVyaW5nIHRvIGRlcml2ZSB0ZW1wb3JhbCBpbnNpZ2h0cyBmcm9tIHRoZSByaWRlIGRhdGEuIFNwZWNpZmljYWxseSwgd2UnbGwgY3JlYXRlIHRpbWUgaW50ZXJ2YWxzIGF0IGJvdGggNjAtbWludXRlIGFuZCAxNS1taW51dGUgaW5jcmVtZW50cyBiYXNlZCBvbiBlYWNoIHJpZGUncyBzdGFydCB0aW1lLiBBZGRpdGlvbmFsbHksIHdlJ2xsIGNhdGVnb3JpemUgdGhlc2UgaW50ZXJ2YWxzIGludG8gYnJvYWRlciB0aW1lIHBlcmlvZHMsIHN1Y2ggYXMgIk92ZXJuaWdodCwiICJBTSBSdXNoLCIgIk1pZC1EYXksIiBhbmQgIlBNIFJ1c2gsIiBhbmQgY2xhc3NpZnkgZWFjaCBkYXkgYXMgZWl0aGVyIGEgd2Vla2RheSBvciB3ZWVrZW5kLgoKYGBge3IgdGVtcG9yYWwgZmVhdHVyZSBlbmdpbmVlcmluZ30KYmlrZV9jZW5zdXMgPC0gYmlrZV9jZW5zdXMgJT4lCiAgbXV0YXRlKAogICAgaW50ZXJ2YWw2MCA9IGZsb29yX2RhdGUoeW1kX2htcyhzdGFydGVkX2F0KSwgdW5pdCA9ICJob3VyIiksCiAgICBpbnRlcnZhbDE1ID0gZmxvb3JfZGF0ZSh5bWRfaG1zKHN0YXJ0ZWRfYXQpLCB1bml0ID0gIjE1IG1pbnMiKSwKICAgIHdlZWsgPSB3ZWVrKGludGVydmFsNjApLAogICAgZG90dyA9IHdkYXkoaW50ZXJ2YWw2MCksICAjIFJlbW92ZWQgbGFiZWwgPSBUUlVFCiAgICB0aW1lX29mX2RheSA9IGNhc2Vfd2hlbigKICAgICAgaG91cihpbnRlcnZhbDYwKSA8IDcgfCBob3VyKGludGVydmFsNjApID4gMTggfiAiT3Zlcm5pZ2h0IiwKICAgICAgaG91cihpbnRlcnZhbDYwKSA+PSA3ICYgaG91cihpbnRlcnZhbDYwKSA8IDEwIH4gIkFNIFJ1c2giLAogICAgICBob3VyKGludGVydmFsNjApID49IDEwICYgaG91cihpbnRlcnZhbDYwKSA8IDE1IH4gIk1pZC1EYXkiLAogICAgICBob3VyKGludGVydmFsNjApID49IDE1ICYgaG91cihpbnRlcnZhbDYwKSA8PSAxOCB+ICJQTSBSdXNoIgogICAgKSwKICAgIGhvdXIgPSBob3VyKHN0YXJ0ZWRfYXQpLAogICAgd2Vla2VuZCA9IGlmZWxzZShkb3R3ICVpbiUgYygxLCA3KSwgIldlZWtlbmQiLCAiV2Vla2RheSIpICAjIFVzZSBudW1lcmljIHZhbHVlcyBmb3IgZGF5cwogICkKCmBgYAoKVG8gYmV0dGVyIHVuZGVyc3RhbmQgY29tbXV0aW5nIHBhdHRlcm5zIGFuZCByaWRlciBiZWhhdmlvciB0aHJvdWdob3V0IHRoZSBkYXksIHdlIGdlbmVyYXRlZCBsaW5lIHN0cmluZ3MgdG8gcmVwcmVzZW50IHRoZSByb3V0ZXMgYmV0d2VlbiB0aGUgc3RhcnQgYW5kIGVuZCBwb2ludHMgb2YgcmlkZXMuIFRvIHNpbXBsaWZ5IHRoZSB2aXN1YWxpemF0aW9uLCB3ZSBsaW1pdGVkIGl0IHRvIDIwMCBzYW1wbGUgcmlkZXMuIFRoZSB2aXN1YWxpemF0aW9uIHJldmVhbHMgdGhhdCB0aGUgbWFqb3JpdHkgb2YgdHJpcHMgYXJlIGNvbmZpbmVkIHRvIEplcnNleSBDaXR5LCB0aG91Z2ggc29tZSByaWRlcnMgdXNlIENpdGkgQmlrZSB0byB0cmF2ZWwgZnJvbSBKZXJzZXkgQ2l0eSB0byBNYW5oYXR0YW4uCgpgYGB7ciBvcmlnaW4tZGVzaW50YXRpb24gbWF0cml4LCB3YXJuaW5nPUZBTFNFfQoKbGluZXMgPC0gc3Rfc2ZjKAogIGxhcHBseSgxOm5yb3coYmlrZV9jZW5zdXMpLCBmdW5jdGlvbihpKSB7CiAgICBzdF9saW5lc3RyaW5nKG1hdHJpeChjKGJpa2VfY2Vuc3VzW2ksICJzdGFydF9sbmciXSwgYmlrZV9jZW5zdXNbaSwgInN0YXJ0X2xhdCJdLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgYmlrZV9jZW5zdXNbaSwgImVuZF9sbmciXSwgYmlrZV9jZW5zdXNbaSwgImVuZF9sYXQiXSksIG5jb2wgPSAyLCBieXJvdyA9IFRSVUUpKQogIH0pKQoKbGluZXNfc2YgPC0gc3Rfc2YoZ2VvbWV0cnkgPSBsaW5lcykKbGluZXNfc2YgPC0gc3Rfc2V0X2NycyhsaW5lc19zZiwgNDMyNikKClJvdXRlIDwtIGxpbmVzX3NmICU+JSBoZWFkKDIwMCkKbWFwdmlldyhSb3V0ZSwgemNvbCA9IE5VTEwsIGNvbG9yID0gIiM2RTBFMEEiLCBsaW5ld2lkdGggPSAwLjAwMSwgYWxwaGEgPSAwLjMpICMgb25seSBzaG93IDIwMApgYGAKCgojIEV4cGxvcmF0b3J5IEFuYWx5c2lzCgpJbiB0aGlzIHNlY3Rpb24sIHdlIGRlbHZlIGludG8gc29tZSBmdXJ0aGVyIGV4cGxvcmF0b3J5IGFuYWx5c2lzIG9mIG91ciBkYXRhIHNldC4gCgojIyBUZW1wb3JhbCBBbmFseXNpcwoKV2UgYmVnYW4gb3VyIGFuYWx5c2lzIGJ5IGV4YW1pbmluZyB0aGUgdGVtcG9yYWwgcGF0dGVybnMgb2YgYmlrZXNoYXJlIHVzYWdlIGluIEplcnNleSBDaXR5IHRocm91Z2hvdXQgQXVndXN0IDIwMjMsIGZvY3VzaW5nIG9uIHRoZSBkaXN0cmlidXRpb24gb2YgdHJpcHMgYnkgdGhlIGhvdXIuIE91ciB2aXN1YWxpemF0aW9uIHJldmVhbHMgZGlzdGluY3QgZmx1Y3R1YXRpb25zLCB3aXRoIGRlbWFuZCByaXNpbmcgYW5kIGZhbGxpbmcgaW4gYSBwcmVkaWN0YWJsZSBjeWNsZSBlYWNoIGRheS4gRHVyaW5nIHdlZWtkYXlzLCB3ZSBvYnNlcnZlIHByb25vdW5jZWQgcGVha3MgdGhhdCBhbGlnbiB3aXRoIHR5cGljYWwgcnVzaCBob3Vyc+KAlG1vcm5pbmdzIGFuZCBsYXRlIGFmdGVybm9vbnPigJR3aGVuIHBlb3BsZSBhcmUgY29tbXV0aW5nIHRvIGFuZCBmcm9tIHdvcmsuIFRoZXNlIGJ1c3kgcGVyaW9kcyBhcmUgZm9sbG93ZWQgYnkgbm90aWNlYWJsZSBsdWxscyBkdXJpbmcgdGhlIG5pZ2h0IGFuZCBlYXJseSBtb3JuaW5nIGhvdXJzLCBhcyB3ZWxsIGFzIGR1cmluZyB3ZWVrZW5kcywgd2hlbiBvdmVyYWxsIHVzYWdlIHRlbmRzIHRvIGRlY3JlYXNlLiBUaGUgcGF0dGVybiBpcyBjaGFyYWN0ZXJpemVkIGJ5IGEgc2VyaWVzIG9mIGZpdmUgY29uc2VjdXRpdmUgaGlnaC1kZW1hbmQgcGVha3MgdGhhdCBjb3JyZXNwb25kIHRvIHdlZWtkYXlzLCBjb25zaXN0ZW50bHkgZm9sbG93ZWQgYnkgdHdvIGxvd2VyIHBlYWtzIG92ZXIgdGhlIHdlZWtlbmQsIGhpZ2hsaWdodGluZyB0aGUgaW5mbHVlbmNlIG9mIHdvcmt3ZWVrIHJvdXRpbmVzIG9uIGJpa2VzaGFyZSBhY3Rpdml0eS4gVGhpcyBjeWNsaWNhbCBiZWhhdmlvciB1bmRlcnNjb3JlcyB0aGUgc3Ryb25nIGxpbmsgYmV0d2VlbiBiaWtlc2hhcmUgZGVtYW5kIGFuZCBkYWlseSBjb21tdXRpbmcgc2NoZWR1bGVzLgoKYGBge3IgYmlrZXNoYXJlIHZpejF9CmdncGxvdChiaWtlX2NlbnN1cyAlPiUKICAgICAgICAgZ3JvdXBfYnkoaW50ZXJ2YWw2MCkgJT4lCiAgICAgICAgIHRhbGx5KCkpKwogIGdlb21fbGluZShhZXMoeCA9IGludGVydmFsNjAsIHkgPSBuKSwgY29sb3I9ICJibHVlIikrCiAgbGFicyh0aXRsZT0iQmlrZSBTaGFyZSBUcmlwcyBQZXIgSG91ciBpbiBKZXJzZXkgQ2l0eSwgQXVnIDIwMjMiLAogICAgICAgeD0iRGF0ZSIsIAogICAgICAgeT0iTnVtYmVyIG9mIHRyaXBzIikgKyAKdGhlbWUocGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gOSxmYWNlID0gIml0YWxpYyIpLAogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSwgCiAgICAgICAgYXhpcy50ZXh0Lng9ZWxlbWVudF90ZXh0KHNpemU9OCksCiAgICAgICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF90ZXh0KHNpemU9OCksIAogICAgICAgIGF4aXMudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9MTApLCAKICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfcmVjdChjb2xvdXIgPSAiZ3JleSIsIGZpbGw9TkEsIGxpbmV3aWR0aCA9MC44KSkKICAKYGBgCgpOZXh0LCB3ZSBhbmFseXplZCBob3cgdHJpcHMgd2VyZSBkaXN0cmlidXRlZCBhY3Jvc3MgZGlmZmVyZW50IHN0YXRpb25zIG9uIGFuIGhvdXJseSBiYXNpcyB0aHJvdWdob3V0IEF1Z3VzdC4gVGhlIHJlc3VsdGluZyB2aXN1YWxpemF0aW9uIHJldmVhbGVkIGEgaGlnaGx5IHNrZXdlZCBkaXN0cmlidXRpb246IHRoZSB2YXN0IG1ham9yaXR5IG9mIHN0YXRpb25zIHJlY29yZGVkIGVpdGhlciBubyByaWRlcyBvciBqdXN0IGEgZmV3IHRyaXBzIHBlciBob3VyLiBIb3dldmVyLCBhIHNlbGVjdCBmZXcgc3RhdGlvbnMgc3Rvb2Qgb3V0LCBleHBlcmllbmNpbmcgc2lnbmlmaWNhbnRseSBoaWdoZXIgZGVtYW5kLiBUaGVzZSBoaWdoLXRyYWZmaWMgc3RhdGlvbnMgc29tZXRpbWVzIHNhdyBhcyBtYW55IGFzIDQwIHRvIDUwIHJpZGVzIHdpdGhpbiBhIHNpbmdsZSBob3VyIG9uIGNlcnRhaW4gZGF5cy4gVGhpcyB1bmV2ZW4gZGlzdHJpYnV0aW9uIGhpZ2hsaWdodHMgdGhlIHZhcnlpbmcgbGV2ZWxzIG9mIGJpa2VzaGFyZSB1c2FnZSBhY3Jvc3MgSmVyc2V5IENpdHksIHdpdGggY2VydGFpbiBsb2NhdGlvbnMgYWN0aW5nIGFzIG1ham9yIGh1YnMgb2YgYWN0aXZpdHkgd2hpbGUgbWFueSBvdGhlcnMgZXhwZXJpZW5jZSBtaW5pbWFsIHJpZGVyc2hpcC4KCmBgYHtyIGJpa2VzaGFyZSB2aXozLCB3YXJuaW5nPUZBTFNFfQoKZ2dwbG90KGJpa2VfY2Vuc3VzICU+JQogICAgICAgICBncm91cF9ieShpbnRlcnZhbDYwLCBzdGFydF9zdGF0aW9uX25hbWUpICU+JQogICAgICAgICB0YWxseSgpKSsKICBnZW9tX2hpc3RvZ3JhbShhZXMobiksIGJpbndpZHRoID0gNSwgZmlsbD0ibGlnaHRibHVlIikrCiAgbGFicyh0aXRsZT0iQmlrZSBTaGFyZSBUcmlwcyBQZXIgSG91ciBieSBTdGF0aW9uIGluIEplcnNleSBDaXR5LCBBdWcgMjAyMyIsCiAgICAgICB4PSJUcmlwIENvdW50cyIsIAogICAgICAgeT0iTnVtYmVyIG9mIFN0YXRpb25zIikgKyAKICAgIHRoZW1lKHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDksZmFjZSA9ICJpdGFsaWMiKSwKICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgZmFjZSA9ICJib2xkIiksIAogICAgICAgIGF4aXMudGV4dC54PWVsZW1lbnRfdGV4dChzaXplPTgpLAogICAgICAgIGF4aXMudGV4dC55PWVsZW1lbnRfdGV4dChzaXplPTgpLCAKICAgICAgICBheGlzLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTEwKSwgCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X3JlY3QoY29sb3VyID0gImdyZXkiLCBmaWxsPU5BLCBsaW5ld2lkdGggPTAuOCkpCgpgYGAKCldlIGZ1cnRoZXIgZXhwbG9yZWQgdGhlIGF2ZXJhZ2UgbnVtYmVyIG9mIHRyaXBzIHBlciBzdGF0aW9uIGF0IGRpZmZlcmVudCB0aW1lcyBvZiB0aGUgZGF5LiBUaGUgYW5hbHlzaXMgc2hvd2VkIHRoYXQgUE0gcnVzaCBob3VycyBjb25zaXN0ZW50bHkgaGFkIHRoZSBoaWdoZXN0IGF2ZXJhZ2UgdHJpcCBjb3VudHMsIHdpdGggc2V2ZXJhbCBzdGF0aW9ucyBhdmVyYWdpbmcgbW9yZSB0aGFuIDEwIHRyaXBzLCBpbmRpY2F0aW5nIHN1YnN0YW50aWFsIGRlbWFuZCBhdCB0aGVzZSBsb2NhdGlvbnMuIE1pZGRheSBjYW1lIG5leHQgaW4gdGVybXMgb2YgYWN0aXZpdHkgbGV2ZWxzLCB3aGVyZSBzb21lIHN0YXRpb25zIHJlY29yZGVkIGF2ZXJhZ2VzIG9mIG92ZXIgNSB0cmlwcy4gU3VycHJpc2luZ2x5LCB0aGVyZSB3YXMgYWxzbyBjb25zaWRlcmFibGUgZGVtYW5kIG92ZXJuaWdodCwgYXMgYSBmZXcgc3RhdGlvbnMgYXZlcmFnZWQgbW9yZSB0aGFuIDUgcmlkZXMgZXZlbiBkdXJpbmcgbGF0ZS1uaWdodCBob3Vycy4gVGhpcyB1bmV4cGVjdGVkIG92ZXJuaWdodCBhY3Rpdml0eSBzdWdnZXN0cyB0aGF0IGJpa2VzaGFyZSB1c2FnZSBleHRlbmRzIGJleW9uZCB0cmFkaXRpb25hbCBjb21tdXRpbmcgcGF0dGVybnMsIHBvdGVudGlhbGx5IHNlcnZpbmcgb3RoZXIgcHVycG9zZXMgc3VjaCBhcyBsYXRlLW5pZ2h0IGxlaXN1cmUgb3IgZWFybHktbW9ybmluZyB0cmF2ZWwuCgoKCmBgYHtyIGJpa2VzaGFyZSB2aXoyLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQoKYmlrZV9jZW5zdXMgJT4lCiAgICAgICAgZ3JvdXBfYnkoaW50ZXJ2YWw2MCwgc3RhcnRfc3RhdGlvbl9uYW1lLCB0aW1lX29mX2RheSkgJT4lCiAgICAgICAgIHRhbGx5KCklPiUKICBncm91cF9ieShzdGFydF9zdGF0aW9uX25hbWUsIHRpbWVfb2ZfZGF5KSU+JQogIHN1bW1hcml6ZShtZWFuX3RyaXBzID0gbWVhbihuKSklPiUKICBnZ3Bsb3QoKSsKICBnZW9tX2hpc3RvZ3JhbShhZXMobWVhbl90cmlwcywgZmlsbD10aW1lX29mX2RheSksIGJpbndpZHRoID0gMSkrCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gcGFsZXR0ZTQsIG5hbWU9IlRpbWUgb2YgRGF5IikgKyAKICBsYWJzKHRpdGxlPSJNZWFuIE51bWJlciBvZiBIb3VybHkgVHJpcHMgUGVyIFN0YXRpb24iLAogICAgICAgeD0iTnVtYmVyIG9mIHRyaXBzIiwgCiAgICAgICB5PSJGcmVxdWVuY3kiKSsKICBmYWNldF93cmFwKH50aW1lX29mX2RheSkgKwp0aGVtZShwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSA5LGZhY2UgPSAiaXRhbGljIiksCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIsIGZhY2UgPSAiYm9sZCIpLCAKICAgICAgICBheGlzLnRleHQueD1lbGVtZW50X3RleHQoc2l6ZT04KSwKICAgICAgICBheGlzLnRleHQueT1lbGVtZW50X3RleHQoc2l6ZT04KSwgCiAgICAgICAgYXhpcy50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT0xMCksIAogICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9yZWN0KGNvbG91ciA9ICJncmV5IiwgZmlsbD1OQSwgc2l6ZT0wLjgpKQoKYGBgCgpXZSB0aGVuIGV4YW1pbmVkIHRoZSB0b3RhbCBudW1iZXIgb2YgdHJpcHMgdGhyb3VnaG91dCBBdWd1c3QsIGJyZWFraW5nIHRoZW0gZG93biBieSBkYXkgb2YgdGhlIHdlZWsuIFR3byBrZXkgcGF0dGVybnMgZW1lcmdlZCBmcm9tIHRoaXMgdmlzdWFsaXphdGlvbi4gRmlyc3QsIHRoZSBudW1iZXIgb2YgdHJpcHMgZmx1Y3R1YXRlcyBzaWduaWZpY2FudGx5IG92ZXIgdGhlIGNvdXJzZSBvZiBhIGRheSwgd2l0aCB0aGUgbG93ZXN0IGFjdGl2aXR5IG9ic2VydmVkIGluIHRoZSBlYXJseSBtb3JuaW5nIGhvdXJzIGFuZCB0aGUgaGlnaGVzdCBpbiB0aGUgbGF0ZSBhZnRlcm5vb24sIGNvcnJlc3BvbmRpbmcgdG8gZXZlbmluZyBjb21tdXRlIHRpbWVzLiBTZWNvbmQsIHRoZSBkYXRhIHNob3dzIHRoYXQgdHJpcCB2b2x1bWVzIGFyZSBnZW5lcmFsbHkgaGlnaGVyIG9uIFR1ZXNkYXlzLCBXZWRuZXNkYXlzLCBhbmQgVGh1cnNkYXlzLCBzdWdnZXN0aW5nIHRoYXQgbWlkd2VlayBkYXlzIGV4cGVyaWVuY2UgdGhlIGdyZWF0ZXN0IGRlbWFuZCBmb3IgYmlrZXNoYXJlIHNlcnZpY2VzLCBsaWtlbHkgZHJpdmVuIGJ5IHJlZ3VsYXIgd29yay1yZWxhdGVkIHRyYXZlbC4KCmBgYHtyIGJpa2VzaGFyZSB2aXo0fQoKZ2dwbG90KGJpa2VfY2Vuc3VzICU+JSBtdXRhdGUoaG91ciA9IGhvdXIoc3RhcnRlZF9hdCkpKSsKICAgICBnZW9tX2ZyZXFwb2x5KGFlcyhob3VyLCBjb2xvciA9IGRvdHcpLCBiaW53aWR0aCA9IDMsIGx3ZCA9IDAuOCkrCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGMoIiMxMjRFNzgiLCAiIzgxOUZBMSIsICIjRjBGMEM5IiwgIiNGMkJCMDUiLCAiI0U1ODUwNyIsICIjRDc0RTA5IiwgIiM2RTBFMEEiKSwgbmFtZSA9ICJEYXkiKSArIAogIGxhYnModGl0bGU9IkJpa2UgU2hhcmUgVHJpcHMgaW4gSmVyc2V5IENpdHkgYnkgRGF5IG9mIHRoZSBXZWVrLCBBdWcgMjAyMyIsCiAgICAgICB4PSJIb3VyIiwgCiAgICAgICB5PSJUcmlwIENvdW50cyIpICsgCiAgdGhlbWUocGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gOSxmYWNlID0gIml0YWxpYyIpLAogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSwgCiAgICAgICAgYXhpcy50ZXh0Lng9ZWxlbWVudF90ZXh0KHNpemU9OCksCiAgICAgICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF90ZXh0KHNpemU9OCksIAogICAgICAgIGF4aXMudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9MTApLCAKICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfcmVjdChjb2xvdXIgPSAiZ3JleSIsIGZpbGw9TkEsIHNpemU9MC44KSkKCmBgYAoKV2UgY3JlYXRlZCBhIHNpbWlsYXIgdmlzdWFsaXphdGlvbiBidXQgdGhpcyB0aW1lIGNvbXBhcmVkIHRyaXAgdm9sdW1lcyBiZXR3ZWVuIHdlZWtkYXlzIGFuZCB3ZWVrZW5kcy4gVGhlIGRpZmZlcmVuY2VzIGFyZSBzdHJpa2luZzogYmlrZXNoYXJlIHVzYWdlIGlzIG5vdGljZWFibHkgaGlnaGVyIG9uIHdlZWtkYXlzIGNvbXBhcmVkIHRvIHdlZWtlbmRzLiBBZGRpdGlvbmFsbHksIG9uIHdlZWtlbmRzLCB0cmlwcyBzdGFydCB0byBwaWNrIHVwIG11Y2ggbGF0ZXIgaW4gdGhlIGRheSwgcmVmbGVjdGluZyB0aGUgYWJzZW5jZSBvZiB0aGUgdHlwaWNhbCB3ZWVrZGF5IG1vcm5pbmcgcnVzaC4gVGhpcyBkZWxheSBzdWdnZXN0cyB0aGF0IHdpdGhvdXQgdGhlIHByZXNzdXJlIG9mIGVhcmx5IHdvcmsgY29tbXV0ZXMsIHBlb3BsZSBhcmUgbW9yZSBsaWtlbHkgdG8gdXNlIGJpa2VzaGFyZSBzZXJ2aWNlcyBsYXRlciBpbiB0aGUgZGF5IGZvciBsZWlzdXJlIG9yIG5vbi13b3JrLXJlbGF0ZWQgYWN0aXZpdGllcywgaGlnaGxpZ2h0aW5nIHRoZSBpbmZsdWVuY2Ugb2YgZGFpbHkgcm91dGluZXMgb24gYmlrZXNoYXJlIGRlbWFuZC4KCmBgYHtyIGJpa2VzaGFyZSB2aXogNX0KCmdncGxvdChiaWtlX2NlbnN1cykrCiAgICAgZ2VvbV9mcmVxcG9seShhZXMoaG91ciwgY29sb3IgPSB3ZWVrZW5kKSwgYmlud2lkdGggPSAzLCBsd2QgPSAxLjIpKwogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXM9cGFsZXR0ZTIpICsgCiAgbGFicyhjb2xvciA9ICJUaW1lIG9mIFdlZWsiKSArCiAgbGFicyh0aXRsZT0iQmlrZSBTaGFyZSBUcmlwcyBpbiBKZXJzZXkgQ2l0eSAtIFdlZWtlbmQgdnMgV2Vla2RheSwgQXVnIDIwMjMiLAogICAgICAgeD0iSG91ciIsIAogICAgICAgeT0iVHJpcCBDb3VudHMiKSArIAogIHRoZW1lKHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDksZmFjZSA9ICJpdGFsaWMiKSwKICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgZmFjZSA9ICJib2xkIiksIAogICAgICAgIGF4aXMudGV4dC54PWVsZW1lbnRfdGV4dChzaXplPTgpLAogICAgICAgIGF4aXMudGV4dC55PWVsZW1lbnRfdGV4dChzaXplPTgpLCAKICAgICAgICBheGlzLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTEwKSwgCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X3JlY3QoY29sb3VyID0gImdyZXkiLCBmaWxsPU5BLCBzaXplPTAuOCkpCgpgYGAKCmBgYHtyIG9yaWdpbl9tYXAgfQojIEFzc3VtaW5nIGBiaWtlX2NlbnN1c2AgaGFzIGEgY29sdW1uICdPcmlnaW4uVHJhY3QnIHRoYXQgbWFwcyBlYWNoIGJpa2UgdHJpcCB0byBhIGNlbnN1cyB0cmFjdAojIGFuZCBgamNUcmFjdHNgIGlzIGFscmVhZHkgbG9hZGVkIGFuZCB0cmFuc2Zvcm1lZCBhcyBuZWNlc3NhcnkKCiMgQWdncmVnYXRlIGJpa2UgdHJpcCBkYXRhIGJ5IGNlbnN1cyB0cmFjdApiaWtlX3RyaXBzX2J5X3RyYWN0IDwtIGJpa2VfY2Vuc3VzICU+JQogIGdyb3VwX2J5KE9yaWdpbi5UcmFjdCkgJT4lCiAgc3VtbWFyaXNlKFRvdGFsX1RyaXBzID0gbigpLCAuZ3JvdXBzID0gJ2Ryb3AnKSAlPiUKICB1bmdyb3VwKCkKCiMgSm9pbiB0aGlzIGFnZ3JlZ2F0ZWQgZGF0YSB0byB0aGUgamNUcmFjdHMgR2VvRGF0YUZyYW1lCmpjVHJhY3RzIDwtIHN0X3RyYW5zZm9ybShqY1RyYWN0cywgY3JzID0gNDMyNikgICMgTWFrZSBzdXJlIENSUyBtYXRjaGVzLCBpZiBub3QgYWxyZWFkeQpqY1RyYWN0c19kYXRhIDwtIGpjVHJhY3RzICU+JQogIGxlZnRfam9pbihiaWtlX3RyaXBzX2J5X3RyYWN0LCBieSA9IGMoIkdFT0lEIiA9ICJPcmlnaW4uVHJhY3QiKSkKCiMgQ2FsY3VsYXRlIHRoZSBib3VuZGluZyBib3ggZm9yIGNlbnRlcmluZwpiYm94IDwtIHN0X2Jib3goamNUcmFjdHNfZGF0YSkKCiMgUGxvdCB0aGUgY2hvcm9wbGV0aCBtYXAgd2l0aCBhZGp1c3RlZCBsaW1pdHMKZ2dwbG90KGRhdGEgPSBqY1RyYWN0c19kYXRhKSArCiAgZ2VvbV9zZihhZXMoZmlsbCA9IFRvdGFsX1RyaXBzKSwgY29sb3IgPSAid2hpdGUiLCBzaXplID0gMC4xKSArCiAgc2NhbGVfZmlsbF92aXJpZGlzX2Mob3B0aW9uID0gIkQiLCBuYW1lID0gIlRvdGFsIEJpa2UgVHJpcHMiLCBuYS52YWx1ZSA9ICJ3aGl0ZSIpICsKICBjb29yZF9zZih4bGltID0gYyhiYm94WyJ4bWluIl0sIGJib3hbInhtYXgiXSksIHlsaW0gPSBjKGJib3hbInltaW4iXSwgYmJveFsieW1heCJdKSwgZXhwYW5kID0gRkFMU0UpICsgICMgU2V0IGxpbWl0cwogIGxhYnModGl0bGUgPSAiQmlrZSBTaGFyZSBUcmlwcyBwZXIgQ2Vuc3VzIFRyYWN0IGluIEplcnNleSBDaXR5IikgKwogIHRoZW1lX21pbmltYWwoKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIsCiAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50ZXh0LnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aWNrcyA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aXRsZS55ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDE0LCBmYWNlID0gImJvbGQiKSwKICAgICAgICBwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMikpCgpgYGAKCkV4cGFuZGluZyBvbiBvdXIgYW5hbHlzaXMsIHdlIGdyb3VwZWQgdGhlIGRhdGEgYnkgc3RhdGlvbiBhbmQgdGltZSBvZiBkYXkgdG8gZXhwbG9yZSB0cmlwIHZvbHVtZXMgZHVyaW5nIGRpZmZlcmVudCBwZXJpb2RzIG9uIHdlZWtkYXlzIHZlcnN1cyB3ZWVrZW5kcy4gVG8ga2VlcCB0aGUgdmlzdWFsaXphdGlvbiBjbGVhciwgd2UgZm9jdXNlZCBvbiB0aGUgdG9wIDUwIHN0YXRpb24tdGltZSBvY2N1cnJlbmNlcy4gRWFjaCBiYXIgaW4gdGhlIGNoYXJ0IHJlcHJlc2VudHMgYSBzcGVjaWZpYyBjb21iaW5hdGlvbiBvZiBzdGF0aW9uLCB0aW1lIG9mIGRheSwgYW5kIHdoZXRoZXIgaXQncyBhIHdlZWtkYXkgb3Igd2Vla2VuZC4gVGhlIHJlc3VsdHMgY2xlYXJseSBzaG93IHRoYXQgUE0gcnVzaCBob3VycyBnZW5lcmF0ZSB0aGUgaGlnaGVzdCBiaWtlc2hhcmUgZGVtYW5kLCBwYXJ0aWN1bGFybHkgYXQgYSBmZXcga2V5IHN0YXRpb25zLiBOb3RhYmx5LCB3ZWVrZW5kIHJpZGVzIGFyZSBzY2FyY2UgaW4gdGhlIHRvcCA1MCBsaXN0LCBhcHBlYXJpbmcgb25seSB0aHJlZSB0aW1lcy4gQSBjbG9zZXIgbG9vayByZXZlYWxzIHRoYXQgR3JvdmUgU3QgUEFUSCBzdGF0aW9uIGhhZCBhIHRvdGFsIG9mIDEsMTE3IHJpZGVzIGR1cmluZyBQTSBydXNoIG9uIGEgd2Vla2RheSBhbmQgODAxIHJpZGVzIG92ZXJuaWdodCBvbiBhIHdlZWtkYXkgaW4gQXVndXN0LiBIb2Jva2VuIFRlcm1pbmFsIC0gUml2ZXIgU3QgJiBIdWRzb24gUGwgcmVjb3JkZWQgODY0IHJpZGVzIGR1cmluZyB3ZWVrZGF5IFBNIHJ1c2guIFNvdXRoIFdhdGVyZnJvbnQgV2Fsa3dheSAtIFNpbmF0cmEgRHIgJiAxIFN0IGhhZCAzMTkgcmlkZXMgb3Zlcm5pZ2h0IG9uIGEgd2Vla2VuZCBhbmQgNzMxIHJpZGVzIGR1cmluZyBQTSBydXNoIG9uIGEgd2Vla2RheS4gVGhlc2UgZGV0YWlscyB1bmRlcnNjb3JlIGhvdyBjZXJ0YWluIHN0YXRpb25zIGV4cGVyaWVuY2Ugc2lnbmlmaWNhbnQgc3Bpa2VzIGluIGRlbWFuZCwgZXNwZWNpYWxseSBkdXJpbmcgd2Vla2RheSBjb21tdXRlIGhvdXJzLgoKYGBge3IgYmlrZXNoYXJlIHZpeiA3LCBmaWcud2lkdGg9MTB9Cgp0b19wbG90IDwtIGJpa2VfY2Vuc3VzJT4lICMgb25seSBzaG93IDEwMCBzdGF0aW9ucwogIGdyb3VwX2J5KHN0YXJ0X3N0YXRpb25faWQsIHN0YXJ0X2xhdCwgc3RhcnRfbG5nLCB3ZWVrZW5kLCB0aW1lX29mX2RheSwgc3RhcnRfc3RhdGlvbl9uYW1lKSAlPiUKICB0YWxseSgpICU+JSAKICBhcnJhbmdlKC1uKSAlPiUgCiAgaGVhZCg1MCkgCnRvX3Bsb3QkSUQgPC0gc2VxX2Fsb25nKHRvX3Bsb3Qkc3RhcnRfc3RhdGlvbl9pZCkKCnRvX3Bsb3QgJT4lIAogIGFycmFuZ2UoLW4pICU+JQogIGhlYWQoNTApICU+JSAKICBnZ3Bsb3QoYWVzKHggPSByZW9yZGVyKElELCAtbiksIG4sIGZpbGwgPSB0aW1lX29mX2RheSwgY29sb3IgPSB3ZWVrZW5kKSkgKwogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IHBhbGV0dGU0LCBuYW1lPSJUaW1lIG9mIERheSIpICsgCiAgZ3VpZGVzKGNvbG9yPSJub25lIikgKyAKICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IiwgcG9zaXRpb249InN0YWNrIikgKwogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBjKCJ0cmFuc3BhcmVudCIsICJibGFjayIpKSArCiAgbGFicyh0aXRsZT0iVG9wIDUwIE9jY3VyZW5jZXMgb2YgUmlkZXMgQnkgVGltZSIpKwogIHlsYWIoIk51bWJlciBvZiBSaWRlcyIpICsKICB4bGFiKCIiKSArIAogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkpICsgCiAgdGhlbWUocGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gOSxmYWNlID0gIml0YWxpYyIpLAogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSwgCiAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCksCiAgICBheGlzLnRpY2tzLnggPSBlbGVtZW50X2JsYW5rKCksIAogICAgICAgIGF4aXMudGV4dC55PWVsZW1lbnRfdGV4dChzaXplPTgpLCAKICAgICAgICBheGlzLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTEwKSwgCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X3JlY3QoY29sb3VyID0gImdyZXkiLCBmaWxsPU5BLCBzaXplPTAuOCkpCmBgYAoKCiMjIFNwYXRpYWwgQW5hbHlzaXMKCkJleW9uZCBvdXIgdGVtcG9yYWwgYW5hbHlzaXMsIHdlIGNyZWF0ZWQgbWFwcyB0byB2aXN1YWxpemUgdGhlIGxvY2F0aW9ucyBvZiBzdGF0aW9ucyB3aXRoIHRoZSBoaWdoZXN0IGJpa2VzaGFyZSBkZW1hbmQsIGJyb2tlbiBkb3duIGJ5IHRpbWUgb2YgZGF5IGFuZCBkYXkgb2YgdGhlIHdlZWsuIFRoZXNlIHByb3BvcnRpb25hbCBtYXBzIHJldmVhbCB0aGF0IHRoZSBncmVhdGVzdCBkZW1hbmQgb2NjdXJzIGR1cmluZyBQTSBydXNoIGhvdXJzIGFuZCBvdmVybmlnaHQgb24gd2Vla2RheXMsIHdoaWxlIEFNIHJ1c2ggaG91cnMgc2VlIHRoZSBsb3dlc3QgYWN0aXZpdHkuIFRoZSBzdGF0aW9ucyB3aXRoIHRoZSBoaWdoZXN0IHVzYWdlIGFyZSBwcmVkb21pbmFudGx5IGNsdXN0ZXJlZCBhbG9uZyB0aGUgSHVkc29uIFJpdmVyLiBUaGlzIHBhdHRlcm4gY291bGQgYmUgZHVlIHRvIHR3byBmYWN0b3JzOiBhIGhpZ2hlciBjb25jZW50cmF0aW9uIG9mIHN0YXRpb25zIGluIHRoaXMgYXJlYSBvciBzdHJhdGVnaWMgcGxhY2VtZW50IGJ5IENpdGkgQmlrZSB0byBhY2NvbW1vZGF0ZSBjb21tdXRlcnMgdHJhdmVsaW5nIGZyb20gSmVyc2V5IENpdHkgdG8gTWFuaGF0dGFuIGZvciB3b3JrIG9yIHNjaG9vbC4gVGhlIHByb3hpbWl0eSB0byBtYWpvciB0cmFuc2l0IGNvbm5lY3Rpb25zIGxpa2VseSBkcml2ZXMgdGhpcyBpbmNyZWFzZWQgZGVtYW5kLgoKYGBge3IgYmlrZXNoYXJlIHZpeiA2fQoKZ2dwbG90KCkrCiAgZ2VvbV9zZihkYXRhID0gamNUcmFjdHMgJT4lCiAgICAgICAgICBzdF90cmFuc2Zvcm0oY3JzPTQzMjYpLCBmaWxsID0gIndoaXRlIikrCiAgZ2VvbV9wb2ludChkYXRhID0gdG9fcGxvdCwKICAgICAgICAgICAgIGFlcyh4PXN0YXJ0X2xuZywgeSA9IHN0YXJ0X2xhdCwgc2l6ZT1uKSwgY29sb3IgPSBhbHBoYSgiIzZFMEUwQSIsIDAuNykpICsgCiAgeWxpbShtaW4oYmlrZV9jZW5zdXMkc3RhcnRfbGF0KSwgbWF4KGJpa2VfY2Vuc3VzJHN0YXJ0X2xhdCkpKwogIHhsaW0obWluKGJpa2VfY2Vuc3VzJHN0YXJ0X2xuZyksIG1heChiaWtlX2NlbnN1cyRzdGFydF9sbmcpKSArCiAgc2NhbGVfc2l6ZShyYW5nZSA9IGMoMC41LCA4KSkgKyAKICBsYWJzKHNpemUgPSAiTnVtYmVyIG9mIFJpZGVzIikgKwogIGZhY2V0X2dyaWQod2Vla2VuZCB+IHRpbWVfb2ZfZGF5KSArIAogICAgbGFicyh0aXRsZT0iTG9jYXRpb25zIG9mIFN0YXRpb25zIHdpdGggR3JlYXRlc3QgQmlrZXNoYXJlIERlbWFuZCBieSBUaW1lIikrIAogIHRoZW1lKGF4aXMudGV4dC54PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRleHQueT1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aWNrcyA9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpdGxlLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gOSxmYWNlID0gIml0YWxpYyIpLAogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSwKICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfcmVjdChjb2xvdXIgPSAiZ3JleSIsIGZpbGw9TkEsIGxpbmV3aWR0aD0wLjgpCiAgICAgICAgKQpgYGAKCldlIGFuYWx5emVkIGFsbCB0aGUgc3RhcnRpbmcgc3RhdGlvbnMgY29sbGVjdGl2ZWx5IHRvIGFzc2VzcyBzcGF0aWFsIGF1dG9jb3JyZWxhdGlvbiwgYWltaW5nIHRvIGRldGVybWluZSBpZiBzdGF0aW9ucyB3aXRoIGhpZ2ggZGVtYW5kIHRlbmQgdG8gYmUgbG9jYXRlZCBuZWFyIG90aGVyIGhpZ2gtZGVtYW5kIHN0YXRpb25zLiBJZiBzdWNoIGEgcGF0dGVybiBleGlzdHMsIGl0IHdvdWxkIGFsbG93IHVzIHRvIHByZWRpY3QgZGVtYW5kIGF0IGEgc3BlY2lmaWMgc3RhdGlvbiBiYXNlZCBvbiB0aGUgYWN0aXZpdHkgbGV2ZWxzIGF0IG5laWdoYm9yaW5nIHN0YXRpb25zLiBUbyBpbnZlc3RpZ2F0ZSB0aGlzLCB3ZSBzZWxlY3RlZCB0aGUgdG9wIDgwIHN0YXRpb25zIHdpdGggdGhlIGhpZ2hlc3QgbnVtYmVyIG9mIHJpZGVzIGluIEF1Z3VzdCAyMDIzIGFuZCBjYWxjdWxhdGVkIE1vcmFuJ3MgSSB1c2luZyA5OTkgcmFuZG9tIE1vbnRlIENhcmxvIHNpbXVsYXRpb25zLiBUaGUgcmVzdWx0cyBpbmRpY2F0ZWQgYSBoaWdobHkgc2lnbmlmaWNhbnQgTW9yYW4ncyBJLCBjb25maXJtaW5nIHRoYXQgdGhlcmUgaXMgaW5kZWVkIHNwYXRpYWwgYXV0b2NvcnJlbGF0aW9uIGFtb25nIG5lYXJieSBiaWtlc2hhcmUgc3RhdGlvbnMsIG1lYW5pbmcgZGVtYW5kIGlzIG5vdCByYW5kb21seSBkaXN0cmlidXRlZCBidXQgY2x1c3RlcmVkIGluIHNwZWNpZmljIGFyZWFzLgoKCgoKYGBge3Igc3BhdGlhbCBhdXRvIGNvciwgd2FybmluZz1GQUxTRX0KCm1vcmFuX2RhdGEgPC0gYmlrZV9jZW5zdXMgJT4lCiAgZ3JvdXBfYnkoc3RhcnRfc3RhdGlvbl9pZCwgc3RhcnRfbGF0LCBzdGFydF9sbmcsIHN0YXJ0X3N0YXRpb25fbmFtZSkgJT4lCiAgdGFsbHkoKSAlPiUgCiAgYXJyYW5nZSgtbikgJT4lIAogIGhlYWQoODApICU+JSAKICBzdF9hc19zZiguLCBjb29yZHMgPSBjKCJzdGFydF9sbmciLCAic3RhcnRfbGF0IiksIGNycyA9IDQzMjYpCgoKY29vcmRzIDwtICBzdF9jb29yZGluYXRlcyhtb3Jhbl9kYXRhKSAKbmVpZ2hib3JMaXN0IDwtIGtubjJuYihrbmVhcm5laWdoKGNvb3JkcywgNSkpCnNwYXRpYWxXZWlnaHRzIDwtIG5iMmxpc3R3KG5laWdoYm9yTGlzdCwgc3R5bGU9IlciKQoKbW9yYW5UZXN0IDwtIG1vcmFuLm1jKG1vcmFuX2RhdGEkbiwgCiAgICAgICAgICAgICAgICAgICAgICBzcGF0aWFsV2VpZ2h0cywgbnNpbSA9IDk5OSkKCmdncGxvdChhcy5kYXRhLmZyYW1lKG1vcmFuVGVzdCRyZXNbYygxOjk5OSldKSwgYWVzKG1vcmFuVGVzdCRyZXNbYygxOjk5OSldKSkgKwogIGdlb21faGlzdG9ncmFtKGJpbndpZHRoID0gMC4wMSwgZmlsbCA9ICIjMTI0RTc4IikgKwogIGdlb21fdmxpbmUoYWVzKHhpbnRlcmNlcHQgPSBtb3JhblRlc3Qkc3RhdGlzdGljKSwgY29sb3VyID0gIiM2RTBFMEEiICxsd2Q9MSkgKwogIHNjYWxlX3hfY29udGludW91cyhsaW1pdHMgPSBjKC0wLjUsIDAuNSkpICsKICBsYWJzKHRpdGxlID0gIk9ic2VydmVkIGFuZCBQZXJtdXRlZCBNb3JhbidzIEkgZm9yIFN0YXRpb25zIHdpdGggTW9zdCBSaWRlcyIsCiAgICAgICBzdWJ0aXRsZSA9ICJPYnNlcnZlZCBNb3JhbidzIEkgaW4gUmVkIiwKICAgICAgIHggPSAiTW9yYW4ncyBJIiwKICAgICAgIHkgPSAiQ291bnQiKSArCiAgdGhlbWVfbGlnaHQoKSArICAgCnRoZW1lKHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDksZmFjZSA9ICJpdGFsaWMiKSwKICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgZmFjZSA9ICJib2xkIiksIAogICAgICAgIGF4aXMudGV4dC54PWVsZW1lbnRfdGV4dChzaXplPTgpLAogICAgICAgIGF4aXMudGV4dC55PWVsZW1lbnRfdGV4dChzaXplPTgpLCAKICAgICAgICBheGlzLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTEwKSkKYGBgCgpUaGUgbWFwIGJlbG93IGlsbHVzdHJhdGVzIHRoZSBjbHVzdGVyaW5nIG9mIHN0YXRpb25zIHdpdGggaGlnaGVyIGJpa2VzaGFyZSBkZW1hbmQuIFNwZWNpZmljYWxseSwgdHdvIG1haW4gY2x1c3RlcnMgZW1lcmdlLCBhbmQgd2l0aGluIGVhY2ggb2YgdGhlc2UgY2x1c3RlcnMsIGEgZmV3IHN0YXRpb25zIGV4cGVyaWVuY2UgZXhjZXB0aW9uYWxseSBoaWdoIG51bWJlcnMgb2YgcmlkZXMuIFRoZXNlIGhpZ2gtZGVtYW5kIHN0YXRpb25zIGFyZSB0eXBpY2FsbHkgc3Vycm91bmRlZCBieSBvdGhlcnMgd2l0aCBzbGlnaHRseSBsb3dlciwgYnV0IHN0aWxsIHNpZ25pZmljYW50LCBsZXZlbHMgb2YgYWN0aXZpdHksIGhpZ2hsaWdodGluZyBhIGNsZWFyIHBhdHRlcm4gb2YgY29uY2VudHJhdGVkIGJpa2VzaGFyZSB1c2FnZS4KCmBgYHtyIHNwYXRpYWwgY29yIHZpeiwgZmlnLmhlaWdodD04LCBmaWcud2lkdGg9MTF9CgogIGdncGxvdCgpICsKICBnZW9tX3NmKGRhdGEgPSBqY1RyYWN0cywgZmlsbCA9ICJ3aGl0ZSIpICsKICBnZW9tX3NmKGRhdGEgPSBtb3Jhbl9kYXRhLAogICAgICAgICAgYWVzKHNpemUgPSBuLCBjb2xvciA9IG4pKSArIAogIHlsaW0obWluKGJpa2VfY2Vuc3VzJHN0YXJ0X2xhdCksIG1heChiaWtlX2NlbnN1cyRzdGFydF9sYXQpKSsKICB4bGltKG1pbihiaWtlX2NlbnN1cyRzdGFydF9sbmcpLCBtYXgoYmlrZV9jZW5zdXMkc3RhcnRfbG5nKSkgKyAKICBzY2FsZV9zaXplKAogICAgcmFuZ2UgPSBjKDEsIDYpLAogICAgZ3VpZGUgPSBndWlkZV9sZWdlbmQoCiAgICAgIGRpcmVjdGlvbiA9ICJob3Jpem9udGFsIiwKICAgICAgbnJvdyA9IDEsCiAgICAgIGxhYmVsLnBvc2l0aW9uID0gImJvdHRvbSIpKSArCiAgc2NhbGVfY29sb3JfZ3JhZGllbnRuKGNvbG91cnMgPSBoY2wuY29sb3JzKDUsICJSZEJ1IixyZXYgPSBUUlVFLCBhbHBoYSA9IDAuOSksIAogICAgICAgICAgICAgICAgICAgICAgICBndWlkZSA9IGd1aWRlX2xlZ2VuZChkaXJlY3Rpb24gPSAiaG9yaXpvbnRhbCIsIG5yb3cgPSAxKSkgKwogIGxhYnModGl0bGUgPSAiU3RhdGlvbnMgd2l0aCBNb3N0IFJpZGVzIiwKICAgICAgIHN1YnRpdGxlID0gICJEb3Qgc2l6ZSBwcm9wb3J0aW9uYWwgdG8gcmlkZXMiKSArIAogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iKSArIAogIHRoZW1lKGF4aXMudGV4dC54PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRleHQueT1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aWNrcyA9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpdGxlLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gOSxmYWNlID0gIml0YWxpYyIpLAogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSwKICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfcmVjdChjb2xvdXIgPSAiZ3JleSIsIGZpbGw9TkEsIGxpbmV3aWR0aD0wLjgpCiAgICAgICAgKQoKYGBgCgpUaGlzIG1hcCBiZWxvdyBjb21iaW5lcyB0aGUgc3BhdGlhbCBhbmQgdGVtcG9yYWwgY2hhcmFjdGVyaXN0aWNzIG9mIGJpa2UgcmlkZXMgYXQgMTVtaW4gaW50ZXJ2YWwgaW50byBhIGNob3JvcGxldGggbWFwLiAKCmBgYHtyIGFuaW1hdGUgbWFwIHRlbXBvcmFsIHZpeiwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgY2FjaGU9VFJVRSwgIGZpZy5oZWlnaHQ9OCwgZmlnLndpZHRoPTExfQpiaWtlX2FnZ3JlZ2F0ZWQgPC0gYmlrZV9jZW5zdXMgJT4lCiAgZ3JvdXBfYnkoaW50ZXJ2YWwxNSwgT3JpZ2luLlRyYWN0KSAlPiUKICBzdW1tYXJpemUoVHJpcF9Db3VudCA9IG4oKSwgLmdyb3VwcyA9ICdkcm9wJykKCiMgSm9pbmluZyB3aXRoIHNwYXRpYWwgZGF0YQpjaG9yb3BsZXRoX2RhdGEgPC0gYmlrZV9hZ2dyZWdhdGVkICU+JQogIGxlZnRfam9pbihqY1RyYWN0cywgYnkgPSBjKCJPcmlnaW4uVHJhY3QiID0gIkdFT0lEIikpICU+JQogIHN0X3NmKCkKCiMgQ3JlYXRpbmcgdGhlIGNob3JvcGxldGggbWFwIHdpdGggdGhlIHNhbWUgc2NhbGUKY2hvcm9wbGV0aF9iaWtlX21hcCA8LSBnZ3Bsb3QoKSArCiAgZ2VvbV9zZihkYXRhID0gamNUcmFjdHMsIGZpbGwgPSAid2hpdGUiLCBjb2xvciA9ICJncmV5IiwgYWxwaGEgPSAwLjUpICsgIyBCYXNlIG1hcAogIGdlb21fc2YoZGF0YSA9IGNob3JvcGxldGhfZGF0YSwgYWVzKGZpbGwgPSBUcmlwX0NvdW50KSwgY29sb3IgPSBOQSkgKyAjIENob3JvcGxldGggbGF5ZXIKICBzY2FsZV9maWxsX2dyYWRpZW50bigKICAgIGNvbG91cnMgPSBoY2wuY29sb3JzKDUsICJZbE9yUmQiLCByZXYgPSBUUlVFKSwKICAgIG5hbWUgPSAiTnVtYmVyIG9mIFRyaXBzIiwKICAgIG5hLnZhbHVlID0gInRyYW5zcGFyZW50IgogICkgKwogICMgU2V0IHRoZSBzYW1lIHNjYWxlIHVzaW5nIHhsaW0gYW5kIHlsaW0KICB4bGltKG1pbihiaWtlX2NlbnN1cyRzdGFydF9sbmcpLCBtYXgoYmlrZV9jZW5zdXMkc3RhcnRfbG5nKSkgKwogIHlsaW0obWluKGJpa2VfY2Vuc3VzJHN0YXJ0X2xhdCksIG1heChiaWtlX2NlbnN1cyRzdGFydF9sYXQpKSArCiAgbGFicygKICAgIHRpdGxlID0gIkJpa2VzaGFyZSBUcmlwIERlbnNpdHkgYnkgMTUtTWludXRlIEludGVydmFscyIsCiAgICBzdWJ0aXRsZSA9ICJOdW1iZXIgb2YgYmlrZSByaWRlcyBhZ2dyZWdhdGVkIGJ5IG9yaWdpbiB0cmFjdCBhbmQgdGltZSBpbnRlcnZhbCIKICApICsKICB0aGVtZSgKICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iLAogICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCksCiAgICBheGlzLnRleHQueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGF4aXMudGlja3MgPSBlbGVtZW50X2JsYW5rKCksCiAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X2JsYW5rKCksCiAgICBheGlzLnRpdGxlLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICBwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSA5LCBmYWNlID0gIml0YWxpYyIpLAogICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIsIGZhY2UgPSAiYm9sZCIpLAogICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfcmVjdChjb2xvdXIgPSAiZ3JleSIsIGZpbGwgPSBOQSwgbGluZXdpZHRoID0gMC44KQogICkKCiMgRGlzcGxheSB0aGUgdXBkYXRlZCBtYXAKcHJpbnQoY2hvcm9wbGV0aF9iaWtlX21hcCkKCmBgYAoKCgojIyBXZWF0aGVyIEFuYWx5c2lzCgpJbiBhZGRpdGlvbiB0byBzcGFjZSBhbmQgdGltZSwgd2VhdGhlciBwbGF5cyBhIGNydWNpYWwgcm9sZSBpbiBpbmZsdWVuY2luZyBiaWtlc2hhcmUgZGVtYW5kLiBXZSBleHBlY3QgdGhhdCBkZW1hbmQgdHlwaWNhbGx5IGRyb3BzIG9uIHJhaW55LCB3aW5keSwgb3IgZXh0cmVtZWx5IGhvdCBkYXlzLiBUbyBpbmNvcnBvcmF0ZSB0aGlzIGZhY3RvciBpbnRvIG91ciBhbmFseXNpcywgd2UgY29sbGVjdGVkIGhvdXJseSB3ZWF0aGVyIGRhdGEgZnJvbSBOZXdhcmsgQWlycG9ydCB1c2luZyB0aGUgcmllbV9tZWFzdXJlcyBmdW5jdGlvbiwgY292ZXJpbmcgdGhlIHBlcmlvZCBmcm9tIEF1Z3VzdCAxIHRvIFNlcHRlbWJlciAxLCAyMDIzLiBTaW5jZSBOZXdhcmsgQWlycG9ydCBpcyBjbG9zZSBlbm91Z2ggdG8gSmVyc2V5IENpdHksIGl0IHNlcnZlcyBhcyBhIHJlbGlhYmxlIHNvdXJjZSBmb3IgbG9jYWwgd2VhdGhlciBjb25kaXRpb25zLiBXZSB0aGVuIGNyZWF0ZWQgYSB3ZWF0aGVyIHBhbmVsIHN1bW1hcml6aW5nIGtleSB2YXJpYWJsZXMgc3VjaCBhcyB0ZW1wZXJhdHVyZSwgcHJlY2lwaXRhdGlvbiwgYW5kIHdpbmQgc3BlZWQgZm9yIGVhY2ggaG91ci4KCmBgYHtyIGRvd25sb2FkIGFuZCB3cmFuZ2xlIHdlYXRoZXIgZGF0YSwgd2FybmluZz1GQUxTRX0Kd2VhdGhlci5QYW5lbCA8LSAKICByaWVtX21lYXN1cmVzKHN0YXRpb24gPSAiRVdSIiwgZGF0ZV9zdGFydCA9ICIyMDIzLTA4LTAxIiwgZGF0ZV9lbmQgPSAiMjAyMy0wOS0wMSIpICU+JQogIGRwbHlyOjpzZWxlY3QodmFsaWQsIHRtcGYsIHAwMWksIHNrbnQpICU+JQogIHJlcGxhY2UoaXMubmEoLiksIDApICU+JQogIG11dGF0ZShpbnRlcnZhbDYwID0geW1kX2goc3Vic3RyKHZhbGlkLCAxLCAxMykpKSAlPiUKICBtdXRhdGUoCiAgICB3ZWVrID0gd2VlayhpbnRlcnZhbDYwKSwKICAgIGRvdHcgPSB3ZGF5KGludGVydmFsNjApICAjIFJlbW92ZSBsYWJlbCA9IFRSVUUKICApICU+JQogIGdyb3VwX2J5KGludGVydmFsNjApICU+JQogIHN1bW1hcml6ZSgKICAgIFRlbXBlcmF0dXJlID0gbWF4KHRtcGYpLAogICAgUHJlY2lwaXRhdGlvbiA9IHN1bShwMDFpKSwKICAgIFdpbmRfU3BlZWQgPSBtYXgoc2tudCkKICApICU+JQogIG11dGF0ZShUZW1wZXJhdHVyZSA9IGlmZWxzZShUZW1wZXJhdHVyZSA9PSAwLCA0MiwgVGVtcGVyYXR1cmUpKQoKYGBgCgpFeGFtaW5pbmcgdGhlIHRlbXBlcmF0dXJlLCBwcmVjaXBpdGF0aW9uLCBhbmQgd2luZCBkYXRhIGNvbGxlY3RlZCBmb3IgQXVndXN0LCB3ZSBvYnNlcnZlIGEgZmV3IG5vdGFibGUgd2VhdGhlciBwYXR0ZXJucy4gVGhlcmUgd2VyZSBzZXZlcmFsIHJhaW55IGRheXMgc2NhdHRlcmVkIHRocm91Z2hvdXQgdGhlIG1vbnRoLCB3aGlsZSB3aW5kIHNwZWVkcyByZW1haW5lZCBnZW5lcmFsbHkgc3RhYmxlLCB3aXRoIG9ubHkgYSBmZXcgaW5zdGFuY2VzIG9mIHBhcnRpY3VsYXJseSBzdHJvbmcgd2luZHMuIFRlbXBlcmF0dXJlIHNob3dlZCBkYWlseSBmbHVjdHVhdGlvbnMsIHdpdGggaGlnaGVyIHJlYWRpbmdzIGR1cmluZyB0aGUgZGF5dGltZSBhbmQgY29vbGVyIHRlbXBlcmF0dXJlcyBhdCBuaWdodCwgcmVmbGVjdGluZyBhIHR5cGljYWwgc3VtbWVyIGN5Y2xlLgoKYGBge3IgdmlzdWFsaXplIHdlYXRoZXIgZGF0YSwgZmlnLmhlaWdodD0xMSwgZmlnLndpZHRoPTgsIHdhcm5pbmc9RkFMU0V9CgpncmlkLmFycmFuZ2UoCiAgZ2dwbG90KHdlYXRoZXIuUGFuZWwsIGFlcyhpbnRlcnZhbDYwLFByZWNpcGl0YXRpb24pKSArIGdlb21fbGluZShjb2xvcj0iIzEyNEU3OCIpICsgCiAgICBsYWJzKHRpdGxlPSJQZXJjaXBpdGF0aW9uIiwgeD0iSG91ciIsIHk9IlBlcmVjaXBpdGF0aW9uIikgKyAKICAgIHRoZW1lKHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDksZmFjZSA9ICJpdGFsaWMiKSwKICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgZmFjZSA9ICJib2xkIiksIAogICAgICAgIGF4aXMudGV4dC54PWVsZW1lbnRfdGV4dChzaXplPTgpLAogICAgICAgIGF4aXMudGV4dC55PWVsZW1lbnRfdGV4dChzaXplPTgpLCAKICAgICAgICBheGlzLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTEwKSwgCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSksIAogIGdncGxvdCh3ZWF0aGVyLlBhbmVsLCBhZXMoaW50ZXJ2YWw2MCxXaW5kX1NwZWVkKSkgKyBnZW9tX2xpbmUoY29sb3I9IiMxMjRFNzgiKSArIAogICAgbGFicyh0aXRsZT0iV2luZCBTcGVlZCIsIHg9IkhvdXIiLCB5PSJXaW5kIFNwZWVkIikgKwogICAgdGhlbWUocGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gOSxmYWNlID0gIml0YWxpYyIpLAogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSwgCiAgICAgICAgYXhpcy50ZXh0Lng9ZWxlbWVudF90ZXh0KHNpemU9OCksCiAgICAgICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF90ZXh0KHNpemU9OCksIAogICAgICAgIGF4aXMudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9MTApLCAKICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpKSwgIAogIGdncGxvdCh3ZWF0aGVyLlBhbmVsLCBhZXMoaW50ZXJ2YWw2MCxUZW1wZXJhdHVyZSkpICsgZ2VvbV9saW5lKGNvbG9yPSIjMTI0RTc4IikgKyAKICAgIGxhYnModGl0bGU9IlRlbXBlcmF0dXJlIiwgeD0iSG91ciIsIHk9IlRlbXBlcmF0dXJlIikgK3RoZW1lKHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDksZmFjZSA9ICJpdGFsaWMiKSwKICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgZmFjZSA9ICJib2xkIiksIAogICAgICAgIGF4aXMudGV4dC54PWVsZW1lbnRfdGV4dChzaXplPTgpLAogICAgICAgIGF4aXMudGV4dC55PWVsZW1lbnRfdGV4dChzaXplPTgpLCAKICAgICAgICBheGlzLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTEwKSwgCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSksIAogIHRvcD0iV2VhdGhlciBEYXRhICIpCgpgYGAKCgojIFNwYWNlLVRpbWUgUGFuZWwKCldlIHByb2NlZWRlZCBieSBjb25zdHJ1Y3RpbmcgYSBzdHVkeSBwYW5lbCB0aGF0IGNhcHR1cmVzIHVuaXF1ZSBjb21iaW5hdGlvbnMgb2Ygc3BhY2UgYW5kIHRpbWUuIFRoaXMgbWVhbnMgZWFjaCByb3cgaW4gdGhlIHBhbmVsIGNvcnJlc3BvbmRzIHRvIGEgcmlkZSBvY2N1cnJpbmcgYXQgYSBzcGVjaWZpYyBzdGF0aW9uIGR1cmluZyBhIHNwZWNpZmljIGhvdXIsIGFsbG93aW5nIHVzIHRvIGFuYWx5emUgYmlrZXNoYXJlIGFjdGl2aXR5IGFjcm9zcyBib3RoIHRlbXBvcmFsIGFuZCBzcGF0aWFsIGRpbWVuc2lvbnMuCgpgYGB7ciBjcmVhdGUgc3R1ZHkgcGFuZWwsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9CiMgQ2hlY2sgaWYgJ2ludGVydmFsNjAnIGV4aXN0cyBhbmQgaXMgbm90IGVudGlyZWx5IE5VTEwKaWYoImludGVydmFsNjAiICVpbiUgbmFtZXMoYmlrZV9jZW5zdXMpICYmICFhbGwoaXMubmEoYmlrZV9jZW5zdXMkaW50ZXJ2YWw2MCkpKSB7CiAgcHJpbnQoIkNvbHVtbiAnaW50ZXJ2YWw2MCcgZXhpc3RzIGFuZCBoYXMgZGF0YS4iKQogIHByaW50KGhlYWQoYmlrZV9jZW5zdXMkaW50ZXJ2YWw2MCkpCn0gZWxzZSB7CiAgcHJpbnQoIkNvbHVtbiAnaW50ZXJ2YWw2MCcgZG9lcyBub3QgZXhpc3Qgb3IgaXMgYWxsIE5BLiIpCn0KCiMgSWYgJ2ludGVydmFsNjAnIG5lZWRzIHRvIGJlIGNyZWF0ZWQgb3IgcmVjYWxjdWxhdGVkOgppZighImludGVydmFsNjAiICVpbiUgbmFtZXMoYmlrZV9jZW5zdXMpIHx8IGFsbChpcy5uYShiaWtlX2NlbnN1cyRpbnRlcnZhbDYwKSkpIHsKICBwcmludCgiUmVjYWxjdWxhdGluZyAnaW50ZXJ2YWw2MCcgZnJvbSBleGlzdGluZyBkYXRldGltZSBjb2x1bW4sIGFzc3VtaW5nICdzdGFydGVkX2F0JyBleGlzdHMuIikKICAKICAjIEFzc3VtaW5nICdzdGFydGVkX2F0JyBpcyB0aGUgY29sdW1uIGZyb20gd2hpY2ggJ2ludGVydmFsNjAnIHNob3VsZCBiZSBkZXJpdmVkOgogIGlmKCJzdGFydGVkX2F0IiAlaW4lIG5hbWVzKGJpa2VfY2Vuc3VzKSkgewogICAgYmlrZV9jZW5zdXMkaW50ZXJ2YWw2MCA8LSBhcy5QT1NJWGN0KGJpa2VfY2Vuc3VzJHN0YXJ0ZWRfYXQsIGZvcm1hdCA9ICIlWS0lbS0lZCAlSDolTTolUyIpICMgQ3VzdG9taXplIGZvcm1hdCBhcyBuZWNlc3NhcnkKICAgIGJpa2VfY2Vuc3VzJGludGVydmFsNjAgPC0gZmxvb3JfZGF0ZShiaWtlX2NlbnN1cyRpbnRlcnZhbDYwLCAiaG91ciIpICMgVXNpbmcgbHVicmlkYXRlIHRvIHJvdW5kIGRvd24gdG8gdGhlIG5lYXJlc3QgaG91cgogICAgcHJpbnQoIk5ldyAnaW50ZXJ2YWw2MCcgY2FsY3VsYXRlZCBhbmQgYWRkZWQgdG8gJ2Jpa2VfY2Vuc3VzJy4iKQogIH0gZWxzZSB7CiAgICBwcmludCgiJ3N0YXJ0ZWRfYXQnIGNvbHVtbiBhbHNvIG1pc3Npbmc7IHBsZWFzZSBjaGVjayBkYXRhIHNvdXJjZS4iKQogIH0KfQoKIyBDaGVjayBhbmQgcmVjcmVhdGUgdGhlIGRhdGEgZnJhbWUgZm9yIGpvaW5pbmcKaWYoImludGVydmFsNjAiICVpbiUgbmFtZXMoYmlrZV9jZW5zdXMpICYmICJzdGFydF9zdGF0aW9uX2lkIiAlaW4lIG5hbWVzKGJpa2VfY2Vuc3VzKSkgewogIGNvbWJpbmVkX2RmIDwtIGV4cGFuZC5ncmlkKGludGVydmFsNjAgPSB1bmlxdWUoYmlrZV9jZW5zdXMkaW50ZXJ2YWw2MCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdGFydF9zdGF0aW9uX2lkID0gdW5pcXVlKGJpa2VfY2Vuc3VzJHN0YXJ0X3N0YXRpb25faWQpKQogIHByaW50KCJDb21iaW5lZCBEYXRhRnJhbWUgY3JlYXRlZCBzdWNjZXNzZnVsbHkuIERpc3BsYXlpbmcgc3RydWN0dXJlOiIpCiAgcHJpbnQoc3RyKGNvbWJpbmVkX2RmKSkKfSBlbHNlIHsKICBwcmludCgiT25lIG9yIGJvdGgga2V5IGNvbHVtbnMgYXJlIHN0aWxsIG1pc3Npbmcgb3IgaW52YWxpZC4iKQp9CnN0dWR5LnBhbmVsIDwtIAogIGV4cGFuZC5ncmlkKGludGVydmFsNjA9dW5pcXVlKGJpa2VfY2Vuc3VzJGludGVydmFsNjApLCAKICAgICAgICAgICAgICBzdGFydF9zdGF0aW9uX2lkID0gdW5pcXVlKGJpa2VfY2Vuc3VzJHN0YXJ0X3N0YXRpb25faWQpKSAlPiUKICBsZWZ0X2pvaW4oLiwgYmlrZV9jZW5zdXMgJT4lCiAgICAgICAgICAgICAgc2VsZWN0KHN0YXJ0X3N0YXRpb25faWQsIHN0YXJ0X3N0YXRpb25fbmFtZSwgT3JpZ2luLlRyYWN0LCBzdGFydF9sbmcsIHN0YXJ0X2xhdCklPiUKICAgICAgICAgICAgICBkaXN0aW5jdCgpICU+JQogICAgICAgICAgICAgIGdyb3VwX2J5KHN0YXJ0X3N0YXRpb25faWQpICU+JQogICAgICAgICAgICAgIHNsaWNlKDEpKQpgYGAKCgpXZSB0aGVuIGVuaGFuY2VkIHRoaXMgcGFuZWwgYnkgYWRkaW5nIG1vcmUgZGV0YWlsZWQgaW5mb3JtYXRpb24uIFRoaXMgaW52b2x2ZWQgY291bnRpbmcgdGhlIG51bWJlciBvZiByaWRlcyBhdCBlYWNoIHN0YXRpb24gZm9yIGVhY2ggaG91ciwgaW5jb3Jwb3JhdGluZyB3ZWF0aGVyIGRhdGEsIG1lcmdpbmcgaW4gY2Vuc3VzIGluZm9ybWF0aW9uLCBhbmQgdXNpbmcgdGhlIGx1YnJpZGF0ZSBwYWNrYWdlIHRvIGRlcml2ZSB0aW1lLXJlbGF0ZWQgdmFyaWFibGVzIHN1Y2ggYXMgdGhlIGRheSBvZiB0aGUgd2VlayBhbmQgaG91ciBvZiB0aGUgZGF5LgoKYGBge3IgYWRkIGluZm8gdG8gc3R1ZHkgcGFuZWwsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9CgpiaWtlLnBhbmVsIDwtIAogIGJpa2VfY2Vuc3VzICU+JQogIG11dGF0ZShUcmlwX0NvdW50ZXIgPSAxKSAlPiUKICByaWdodF9qb2luKHN0dWR5LnBhbmVsKSAlPiUgCiAgZ3JvdXBfYnkoaW50ZXJ2YWw2MCwgc3RhcnRfc3RhdGlvbl9pZCwgc3RhcnRfc3RhdGlvbl9uYW1lLCBPcmlnaW4uVHJhY3QsIHN0YXJ0X2xuZywgc3RhcnRfbGF0KSAlPiUKICBzdW1tYXJpemUoVHJpcF9Db3VudCA9IHN1bShUcmlwX0NvdW50ZXIsIG5hLnJtID0gVFJVRSksIC5ncm91cHMgPSAnZHJvcCcpICU+JQogIGxlZnRfam9pbih3ZWF0aGVyLlBhbmVsKSAlPiUKICB1bmdyb3VwKCkgJT4lCiAgZmlsdGVyKCFpcy5uYShzdGFydF9zdGF0aW9uX2lkKSkgJT4lCiAgbXV0YXRlKAogICAgd2VlayA9IHdlZWsoaW50ZXJ2YWw2MCksCiAgICBkb3R3ID0gd2RheShpbnRlcnZhbDYwKSAgIyBSZW1vdmVkIGxhYmVsID0gVFJVRQogICkgJT4lCiAgZmlsdGVyKCFpcy5uYShPcmlnaW4uVHJhY3QpKSAlPiUgCiAgbGVmdF9qb2luKAogICAgbmpDZW5zdXMgJT4lCiAgICAgIGFzLmRhdGEuZnJhbWUoKSAlPiUKICAgICAgc2VsZWN0KC1nZW9tZXRyeSksCiAgICBieSA9IGMoIk9yaWdpbi5UcmFjdCIgPSAiR0VPSUQiKQogICkKCmBgYAoKIyMgV2VhdGhlciBDb3JyZWxhdGlvbgoKV2l0aCBvdXIgcGFuZWwgZGF0YSByZWFkeSwgd2UgY29uZHVjdGVkIGZ1cnRoZXIgZXhwbG9yYXRvcnkgYW5hbHlzaXMgdG8gcHJlcGFyZSBmb3Igb3VyIHJlZ3Jlc3Npb24gbW9kZWxpbmcuIE91ciBmaXJzdCBmb2N1cyB3YXMgb24gdW5kZXJzdGFuZGluZyB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gdGVtcGVyYXR1cmUgYW5kIHRoZSBudW1iZXIgb2YgYmlrZSByaWRlcyBpbiBBdWd1c3QuIFRoZSBhbmFseXNpcyByZXZlYWxlZCBhIHN0cm9uZywgcG9zaXRpdmUsIGFuZCBsaW5lYXIgY29ycmVsYXRpb24sIGluZGljYXRpbmcgdGhhdCBiaWtlc2hhcmUgZGVtYW5kIHRlbmRzIHRvIGluY3JlYXNlIGFzIHRlbXBlcmF0dXJlcyByaXNlIHRocm91Z2hvdXQgdGhlIG1vbnRoLgoKCmBgYHtyIHZpc3VhbGl6ZSB0ZW1wLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQoKYmlrZS5wYW5lbCAlPiUKICBncm91cF9ieShpbnRlcnZhbDYwKSAlPiUgCiAgc3VtbWFyaXplKFRyaXBfQ291bnQgPSBtZWFuKFRyaXBfQ291bnQpLAogICAgICAgICAgICBUZW1wZXJhdHVyZSA9IGZpcnN0KFRlbXBlcmF0dXJlKSkgJT4lCiAgbXV0YXRlKHdlZWsgPSB3ZWVrKGludGVydmFsNjApKSAlPiUKICBnZ3Bsb3QoYWVzKFRlbXBlcmF0dXJlLCBUcmlwX0NvdW50KSkgKyAKICAgIGdlb21fcG9pbnQoc2l6ZSA9IC41LCBjb2xvciA9ICJibGFjayIpICsgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxtIiwgc2U9IEZBTFNFLCBjb2xvcj0iZGFya3JlZCIpICsKICAgIGZhY2V0X3dyYXAofndlZWssIG5jb2w9OCkgKyAKICAgIGxhYnModGl0bGU9IlRyaXAgQ291bnQgQXMgYSBGdWN0aW9uIG9mIFRlbXBlcmF0dXJlIGJ5IFdlZWsiLAogICAgICAgICB4PSJUZW1wZXJhdHVyZSIsIHk9Ik1lYW4gVHJpcCBDb3VudCIpICsgCiAgdGhlbWUocGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gOSxmYWNlID0gIml0YWxpYyIpLAogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSwgCiAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCksCiAgICBheGlzLnRpY2tzLnggPSBlbGVtZW50X2JsYW5rKCksIAogICAgICAgIGF4aXMudGV4dC55PWVsZW1lbnRfdGV4dChzaXplPTgpLCAKICAgICAgICBheGlzLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTEwKSwgCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X3JlY3QoY29sb3VyID0gImdyZXkiLCBmaWxsPU5BLCBzaXplPTAuOCkpCiAgCgpgYGAKCk5leHQsIHdlIGV4YW1pbmVkIHRoZSBjb3JyZWxhdGlvbiBiZXR3ZWVuIHdpbmQgc3BlZWQgYW5kIHRoZSBudW1iZXIgb2YgYmlrZSByaWRlcyBpbiBBdWd1c3QuIFdlIG9ic2VydmVkIGEgZ2VuZXJhbGx5IHBvc2l0aXZlIGxpbmVhciByZWxhdGlvbnNoaXAsIHRob3VnaCB0aGUgc3RyZW5ndGggb2YgdGhpcyBjb3JyZWxhdGlvbiB2YXJpZWQgYWNyb3NzIGRpZmZlcmVudCB3ZWVrcy4gVGhpcyBzdWdnZXN0cyB0aGF0IGJpa2VzaGFyZSBkZW1hbmQgdGVuZHMgdG8gaW5jcmVhc2Ugb24gd2luZHkgZGF5cywgYnV0IHRoZSBpbXBhY3Qgb2Ygd2luZCBzcGVlZCBvbiByaWRlIGFjdGl2aXR5IGlzIG5vdCB1bmlmb3JtIHRocm91Z2hvdXQgdGhlIG1vbnRoLgoKYGBge3IgdmlzdWFsaXplIHdpbmQsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9CmJpa2UucGFuZWwgJT4lCiAgZ3JvdXBfYnkoaW50ZXJ2YWw2MCkgJT4lIAogIHN1bW1hcml6ZShUcmlwX0NvdW50ID0gbWVhbihUcmlwX0NvdW50KSwKICAgICAgICAgICAgV2luZCA9IGZpcnN0KFdpbmRfU3BlZWQpKSAlPiUKICBtdXRhdGUod2VlayA9IHdlZWsoaW50ZXJ2YWw2MCkpICU+JQogIGdncGxvdChhZXMoV2luZCwgVHJpcF9Db3VudCkpICsgCiAgICBnZW9tX3BvaW50KHNpemUgPSAuNSwgY29sb3IgPSAiYmxhY2siKSArIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsbSIsIHNlPSBGQUxTRSwgY29sb3I9ImRhcmtyZWQiKSArCiAgICBmYWNldF93cmFwKH53ZWVrLCBuY29sPTgpICsgCiAgICBsYWJzKHRpdGxlPSJUcmlwIENvdW50IEFzIGEgRnVjdGlvbiBvZiBXaW5kIGJ5IFdlZWsiLAogICAgICAgICB4PSJXaW5kIFNwZWVkIiwgeT0iTWVhbiBUcmlwIENvdW50IikgKyAKICB0aGVtZShwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSA5LGZhY2UgPSAiaXRhbGljIiksCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIsIGZhY2UgPSAiYm9sZCIpLCAKICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGF4aXMudGlja3MueCA9IGVsZW1lbnRfYmxhbmsoKSwgCiAgICAgICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF90ZXh0KHNpemU9OCksIAogICAgICAgIGF4aXMudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9MTApLCAKICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfcmVjdChjb2xvdXIgPSAiZ3JleSIsIGZpbGw9TkEsIHNpemU9MC44KSkgKyAKICAgIHRoZW1lKHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDksZmFjZSA9ICJpdGFsaWMiKSwKICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgZmFjZSA9ICJib2xkIiksIAogICAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpLAogICAgYXhpcy50aWNrcy54ID0gZWxlbWVudF9ibGFuaygpLCAKICAgICAgICBheGlzLnRleHQueT1lbGVtZW50X3RleHQoc2l6ZT04KSwgCiAgICAgICAgYXhpcy50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT0xMCksIAogICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9yZWN0KGNvbG91ciA9ICJncmV5IiwgZmlsbD1OQSwgc2l6ZT0wLjgpKQpgYGAKCgojIyBTZXJpYWwgQ29ycmVsYXRpb24KCklmIHRoZSBudW1iZXIgb2YgYmlrZSB0cmlwcyBzaG93cyBzZXJpYWwgKHRlbXBvcmFsKSBjb3JyZWxhdGlvbiwgaW5jb3Jwb3JhdGluZyB0aW1lIGxhZyBmZWF0dXJlcyBpbnRvIG91ciBtb2RlbCBjb3VsZCBzaWduaWZpY2FudGx5IGVuaGFuY2UgaXRzIHByZWRpY3RpdmUgcG93ZXIuIFRoZSB1bmRlcmx5aW5nIGNvbmNlcHQgaXMgdGhhdCBiaWtlc2hhcmUgZGVtYW5kIGF0IGFueSBnaXZlbiBob3VyIGlzIGxpa2VseSB0byBiZSBpbmZsdWVuY2VkIGJ5IGRlbWFuZCBwYXR0ZXJucyBpbW1lZGlhdGVseSBwcmVjZWRpbmcgb3IgZm9sbG93aW5nIHRoYXQgaG91ci4gRm9yIGV4YW1wbGUsIGlmIHdlIGtub3cgdGhlcmUgd2FzIGEgaGlnaCBudW1iZXIgb2YgdHJpcHMgZHVyaW5nIG9uZSBob3VyLCB3ZSBjYW4gcmVhc29uYWJseSBwcmVkaWN0IHRoYXQgbmVhcmJ5IGhvdXJzIHdpbGwgYWxzbyBoYXZlIGVsZXZhdGVkIGRlbWFuZC4gVG8gY2FwdHVyZSB0aGVzZSB0ZW1wb3JhbCBkZXBlbmRlbmNpZXMsIHdlIGNvbXB1dGVkIHNpeCBkaWZmZXJlbnQgbGFnZ2VkIHZhcmlhYmxlcywgZWFjaCByZXByZXNlbnRpbmcgdGhlIG51bWJlciBvZiB0cmlwcyBvbmUsIHR3bywgdGhyZWUsIGZvdXIsIHR3ZWx2ZSBob3VycyBlYXJsaWVyLCBhbmQgb25lIGRheSBlYXJsaWVyLiBUaGVzZSBsYWcgZmVhdHVyZXMgYWxsb3cgdXMgdG8gYWNjb3VudCBmb3Igc2hvcnQtdGVybSBmbHVjdHVhdGlvbnMgYXMgd2VsbCBhcyBkYWlseSBwYXR0ZXJucywgcHJvdmlkaW5nIGEgbW9yZSBjb21wcmVoZW5zaXZlIGZyYW1ld29yayBmb3IgbW9kZWxpbmcgYmlrZXNoYXJlIGFjdGl2aXR5LiBCeSBsZXZlcmFnaW5nIHRoZXNlIHRpbWUtYmFzZWQgY29ycmVsYXRpb25zLCBvdXIgbW9kZWwgY2FuIGJldHRlciBhbnRpY2lwYXRlIHNoaWZ0cyBpbiBkZW1hbmQsIGltcHJvdmluZyBhY2N1cmFjeSBhbmQgcmVsaWFiaWxpdHkuCgoKYGBge3IgY2FsY3VsYXRlIHNlcmlhbCBsYWd9CgpiaWtlLnBhbmVsIDwtIAogIGJpa2UucGFuZWwgJT4lIAogIGFycmFuZ2Uoc3RhcnRfc3RhdGlvbl9pZCwgaW50ZXJ2YWw2MCkgJT4lIAogIG11dGF0ZShsYWdIb3VyID0gZHBseXI6OmxhZyhUcmlwX0NvdW50LDEpLAogICAgICAgICBsYWcySG91cnMgPSBkcGx5cjo6bGFnKFRyaXBfQ291bnQsMiksCiAgICAgICAgIGxhZzNIb3VycyA9IGRwbHlyOjpsYWcoVHJpcF9Db3VudCwzKSwKICAgICAgICAgbGFnNEhvdXJzID0gZHBseXI6OmxhZyhUcmlwX0NvdW50LDQpLAogICAgICAgICBsYWcxMkhvdXJzID0gZHBseXI6OmxhZyhUcmlwX0NvdW50LDEyKSwKICAgICAgICAgbGFnMWRheSA9IGRwbHlyOjpsYWcoVHJpcF9Db3VudCwyNCkpICU+JQogICBtdXRhdGUoZGF5ID0geWRheShpbnRlcnZhbDYwKSkKCmBgYAoKV2UgdmlzdWFsaXplZCB0aGUgcmVsYXRpb25zaGlwcyBiZXR3ZWVuIHRoZSBudW1iZXIgb2YgdHJpcHMgYW5kIHRoZSB2YXJpb3VzIGxhZ2dlZCBob3VycyB0byBiZXR0ZXIgdW5kZXJzdGFuZCB0aGVzZSB0ZW1wb3JhbCBjb3JyZWxhdGlvbnMuIFRoZSBhbmFseXNpcyByZXZlYWxlZCBzb21lIGludGVyZXN0aW5nIHBhdHRlcm5zLiBUaGVyZSBpcyBhIHN0cm9uZywgbGluZWFyLCBhbmQgbmVnYXRpdmUgcmVsYXRpb25zaGlwIGJldHdlZW4gdGhlIG51bWJlciBvZiB0cmlwcyBhbmQgdGhlIGxhZ2dlZCB2YXJpYWJsZSByZXByZXNlbnRpbmcgMTIgaG91cnMgcHJpb3IuIEZvciBpbnN0YW5jZSwgdGhlIG51bWJlciBvZiByaWRlcyByZWNvcmRlZCBhdCA1IFBNIG9uIGEgZ2l2ZW4gZGF5IHRlbmRzIHRvIGJlIG5lZ2F0aXZlbHkgY29ycmVsYXRlZCB3aXRoIHRoZSBudW1iZXIgb2YgcmlkZXMgYXQgNSBBTSBvbiB0aGUgc2FtZSBkYXksIGxpa2VseSByZWZsZWN0aW5nIGNvbnRyYXN0aW5nIHBlYWsgYW5kIG9mZi1wZWFrIHVzYWdlIHRpbWVzLgoKQ29udmVyc2VseSwgd2Ugb2JzZXJ2ZWQgYSBzdHJvbmcsIHBvc2l0aXZlLCBhbmQgbGluZWFyIGNvcnJlbGF0aW9uIGJldHdlZW4gdGhlIG51bWJlciBvZiB0cmlwcyBhdCBhIGdpdmVuIGhvdXIgYW5kIGJvdGggdGhlIGltbWVkaWF0ZSBuZXh0IGhvdXIgKGxhZ2hvdXIpIGFuZCB0aGUgc2FtZSBob3VyIG9uIHRoZSBmb2xsb3dpbmcgZGF5IChsYWcxZGF5KS4gVGhpcyBpbmRpY2F0ZXMgdGhhdCBiaWtlc2hhcmUgZGVtYW5kIGlzIGhpZ2hseSBwcmVkaWN0YWJsZSBmcm9tIG9uZSBob3VyIHRvIHRoZSBuZXh0IGFuZCB0ZW5kcyB0byBmb2xsb3cgY29uc2lzdGVudCBkYWlseSBwYXR0ZXJucy4gSG93ZXZlciwgdGhlIHN0cmVuZ3RoIG9mIHRoZSBjb3JyZWxhdGlvbiBkZWNyZWFzZXMgZm9yIHRoZSBsYWcgdmFyaWFibGVzIHJlcHJlc2VudGluZyB0d28gYW5kIHRocmVlIGhvdXJzIHByaW9yLiBGb3IgZXhhbXBsZSwgdGhlIG51bWJlciBvZiByaWRlcyBhdCA0IFBNIGlzIG1vcmUgc3Ryb25nbHkgY29ycmVsYXRlZCB3aXRoIHRoZSBudW1iZXIgb2YgcmlkZXMgYXQgNSBQTSB0aGFuIHdpdGggcmlkZXMgYXQgNiBvciA3IFBNLiBUaGlzIGdyYWR1YWwgd2Vha2VuaW5nIG9mIGNvcnJlbGF0aW9uIG92ZXIgdGltZSBoaWdobGlnaHRzIGhvdyBiaWtlc2hhcmUgZGVtYW5kIGlzIG1vc3QgaGVhdmlseSBpbmZsdWVuY2VkIGJ5IHJlY2VudCBwYXN0IGFjdGl2aXR5LCBtYWtpbmcgdGhlc2UgdGltZSBsYWcgZmVhdHVyZXMgdmFsdWFibGUgZm9yIG1vZGVsaW5nIHB1cnBvc2VzLgoKYGBge3IgdmlzdWFsaXplIHNlcmlhbCBjb3JyZWxhdGlvbnMsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9Cgphcy5kYXRhLmZyYW1lKGJpa2UucGFuZWwpICU+JQogICAgZ3JvdXBfYnkoaW50ZXJ2YWw2MCkgJT4lIAogICAgc3VtbWFyaXNlX2F0KHZhcnMoc3RhcnRzX3dpdGgoImxhZyIpLCAiVHJpcF9Db3VudCIpLCBtZWFuLCBuYS5ybSA9IFRSVUUpICU+JQogICAgZ2F0aGVyKFZhcmlhYmxlLCBWYWx1ZSwgLWludGVydmFsNjAsIC1UcmlwX0NvdW50KSAlPiUKICBnZ3Bsb3QoYWVzKHggPSBWYWx1ZSAsIHkgPSBUcmlwX0NvdW50KSkgKwogICAgZ2VvbV9wb2ludChzaXplID0gLjUsIGNvbG9yID0gImJsYWNrIikgKyBnZW9tX3Ntb290aChtZXRob2QgPSAibG0iLCBzZT0gRkFMU0UsIGNvbG9yPSJkYXJrcmVkIikgKwogIGZhY2V0X3dyYXAoflZhcmlhYmxlLCBuY29sID0gNikrCiAgbGFicyh4ID0gIlZhcmlhYmxlIiwgeSA9ICJDb3JyZWxhdGlvbiIsIHRpdGxlID0gIkNvcnJlbGF0aW9uIEJldHdlZW4gU2VyaWFsIExhZyBUcmlwcyBhbmQgVHJpcCBDb3VudCIpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpKSArIAogIHRoZW1lKHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDksZmFjZSA9ICJpdGFsaWMiKSwKICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgZmFjZSA9ICJib2xkIiksIAogICAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpLAogICAgYXhpcy50aWNrcy54ID0gZWxlbWVudF9ibGFuaygpLCAKICAgICAgICBheGlzLnRleHQueT1lbGVtZW50X3RleHQoc2l6ZT04KSwgCiAgICAgICAgYXhpcy50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT0xMCksIAogICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9yZWN0KGNvbG91ciA9ICJncmV5IiwgZmlsbD1OQSwgc2l6ZT0wLjgpKQoKYGBgCgojIyBTcGF0aWFsIENvcnJlbGF0aW9uCgpXZSBwcmV2aW91c2x5IGVzdGFibGlzaGVkIHRoYXQgc3BhdGlhbCBhdXRvY29ycmVsYXRpb24gaXMgcHJlc2VudCBpbiBvdXIgZGF0YSwgaW5kaWNhdGluZyB0aGF0IHRoZSBudW1iZXIgb2YgdHJpcHMgYXQgb25lIHN0YXRpb24gZHVyaW5nIGEgc3BlY2lmaWMgdGltZSBpcyByZWxhdGVkIHRvIHRoZSBudW1iZXIgb2YgdHJpcHMgYXQgbmVhcmJ5IHN0YXRpb25zLiBJbiBvdGhlciB3b3Jkcywga25vd2luZyB0aGUgZGVtYW5kIGF0IG9uZSBzdGF0aW9uIGNhbiBoZWxwIHVzIGVzdGltYXRlIGRlbWFuZCBhdCBuZWlnaGJvcmluZyBzdGF0aW9ucy4gVG8gbGV2ZXJhZ2UgdGhpcyBzcGF0aWFsIHJlbGF0aW9uc2hpcCwgd2UgY3JlYXRlZCBhIG5ldyBkYXRhZnJhbWUgY2FsbGVkIGxhZ19yZXN1bHQuCgpXZSB0aGVuIGltcGxlbWVudGVkIGEgbmVzdGVkIGxvb3AgdG8gc3lzdGVtYXRpY2FsbHkgY2FsY3VsYXRlIHNwYXRpYWwgbGFncyBmb3IgZWFjaCBob3VyIGluIEF1Z3VzdCAyMDIzLiBGb3IgZWFjaCBob3VyLCB3ZSBpZGVudGlmaWVkIHRoZSBmaXZlIG5lYXJlc3QgbmVpZ2hib3Jpbmcgc3RhdGlvbnMgZm9yIGV2ZXJ5IHN0YXRpb24gYW5kIGNvbXB1dGVkIHRoZSBhdmVyYWdlIG51bWJlciBvZiB0cmlwcyBhY3Jvc3MgdGhlc2UgbmVpZ2hib3JzLiBUaGlzIHByb3ZpZGVkIGEgbWVhc3VyZSBvZiBzcGF0aWFsIGluZmx1ZW5jZSwgY2FwdHVyaW5nIGhvdyBkZW1hbmQgYXQgb25lIHN0YXRpb24gaXMgYWZmZWN0ZWQgYnkgYWN0aXZpdHkgYXQgc3Vycm91bmRpbmcgc3RhdGlvbnMuIEZpbmFsbHksIHdlIGludGVncmF0ZWQgdGhlc2Ugc3BhdGlhbCBsYWcgdmFsdWVzIGJhY2sgaW50byBvdXIgcGFuZWwgZGF0YXNldCwgZW5oYW5jaW5nIG91ciBtb2RlbCdzIGFiaWxpdHkgdG8gcHJlZGljdCBiaWtlc2hhcmUgZGVtYW5kIGJ5IGluY29ycG9yYXRpbmcgYm90aCBzcGF0aWFsIGFuZCB0ZW1wb3JhbCBkZXBlbmRlbmNpZXMuCgoKCgpgYGB7ciBjYWxjdWxhdGUgc3BhdGlhbCBsYWcsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmxpYnJhcnkoc2YpCmxpYnJhcnkoc3BkZXApCmxpYnJhcnkoZHBseXIpCmxpYnJhcnkocGFyYWxsZWwpCgojIFN0ZXAgMTogRmlsdGVyIGRhdGEgb25jZSBhbmQgY29udmVydCB0byBzZiBvYmplY3QKYmlrZS5wYW5lbF9zZiA8LSBiaWtlLnBhbmVsICU+JQogIHN0X2FzX3NmKGNvb3JkcyA9IGMoInN0YXJ0X2xuZyIsICJzdGFydF9sYXQiKSwgY3JzID0gNDMyNikKCiMgU3RlcCAyOiBEZWZpbmUgYSBmdW5jdGlvbiB0byBjYWxjdWxhdGUgc3BhdGlhbCBsYWcgZm9yIGEgZ2l2ZW4gZGF5IGFuZCBob3VyCmNhbGN1bGF0ZV9zcGF0aWFsX2xhZyA8LSBmdW5jdGlvbihkLCBoLCBiaWtlX2RhdGEpIHsKICAjIExvYWQgcmVxdWlyZWQgbGlicmFyaWVzIHdpdGhpbiB0aGUgZnVuY3Rpb24KICBsaWJyYXJ5KGRwbHlyKQogIGxpYnJhcnkoc2YpCiAgbGlicmFyeShzcGRlcCkKICBsaWJyYXJ5KGx1YnJpZGF0ZSkKICAKICAjIEZpbHRlciBkYXRhIGZvciB0aGUgc3BlY2lmaWVkIGRheSBhbmQgaG91cgogIHNwYWNlbGFnIDwtIGJpa2VfZGF0YSAlPiUKICAgIG11dGF0ZShob3VyID0gaG91cihpbnRlcnZhbDYwKSwgZGF5ID0geWRheShpbnRlcnZhbDYwKSkgJT4lCiAgICBmaWx0ZXIoZGF5ID09IGQgJiBob3VyID09IGgpCiAgCiAgaWYgKG5yb3coc3BhY2VsYWcpID09IDApIHJldHVybihOVUxMKSAgIyBTa2lwIGlmIG5vIGRhdGEKICAKICAjIENvbXB1dGUgc3BhdGlhbCBuZWlnaGJvcnMgYW5kIHdlaWdodHMKICBjb29yZHMgPC0gc3RfY29vcmRpbmF0ZXMoc3BhY2VsYWcpCiAgbmVpZ2hib3JMaXN0IDwtIGtubjJuYihrbmVhcm5laWdoKGNvb3JkcywgayA9IDUpKQogIHNwYXRpYWxXZWlnaHRzIDwtIG5iMmxpc3R3KG5laWdoYm9yTGlzdCwgc3R5bGUgPSAiVyIpCiAgCiAgIyBDYWxjdWxhdGUgc3BhdGlhbCBsYWcKICBzcGFjZWxhZyA8LSBzcGFjZWxhZyAlPiUKICAgIG11dGF0ZShsYWdUcmlwID0gbGFnLmxpc3R3KHNwYXRpYWxXZWlnaHRzLCBUcmlwX0NvdW50KSkKICAKICByZXR1cm4oc3BhY2VsYWcpCn0KCiMgU3RlcCAzOiBVc2UgcGFyYWxsZWwgcHJvY2Vzc2luZyB0byBzcGVlZCB1cCB0aGUgbG9vcApjbCA8LSBtYWtlQ2x1c3RlcihkZXRlY3RDb3JlcygpIC0gMSkgICMgQ3JlYXRlIGEgY2x1c3RlciB1c2luZyBhdmFpbGFibGUgY29yZXMKY2x1c3RlckV4cG9ydChjbCwgYygiYmlrZS5wYW5lbF9zZiIsICJjYWxjdWxhdGVfc3BhdGlhbF9sYWciKSkgICMgRXhwb3J0IGRhdGEgYW5kIGZ1bmN0aW9uCmNsdXN0ZXJFdmFsUShjbCwgeyAgIyBMb2FkIHJlcXVpcmVkIGxpYnJhcmllcyBpbiBlYWNoIHdvcmtlcgogIGxpYnJhcnkoZHBseXIpCiAgbGlicmFyeShzZikKICBsaWJyYXJ5KHNwZGVwKQogIGxpYnJhcnkobHVicmlkYXRlKQp9KQoKIyBSdW4gdGhlIGxvb3AgaW4gcGFyYWxsZWwKbGFnX3Jlc3VsdF9saXN0IDwtIHBhckxhcHBseShjbCwgMjEzOjI0MywgZnVuY3Rpb24oZCkgewogIGxhcHBseSgwOjIzLCBmdW5jdGlvbihoKSBjYWxjdWxhdGVfc3BhdGlhbF9sYWcoZCwgaCwgYmlrZS5wYW5lbF9zZikpCn0pCgpzdG9wQ2x1c3RlcihjbCkgICMgU3RvcCB0aGUgY2x1c3RlcgoKIyBDb21iaW5lIHRoZSByZXN1bHRzIGludG8gYSBzaW5nbGUgZGF0YSBmcmFtZQpsYWdfcmVzdWx0IDwtIGRvLmNhbGwocmJpbmQsIHVubGlzdChsYWdfcmVzdWx0X2xpc3QsIHJlY3Vyc2l2ZSA9IEZBTFNFKSkgJT4lCiAgbXV0YXRlKHN0YXJ0X2xuZyA9IHVubGlzdChtYXAoZ2VvbWV0cnksIDEpKSwKICAgICAgICAgc3RhcnRfbGF0ID0gdW5saXN0KG1hcChnZW9tZXRyeSwgMikpKSAlPiUKICBzdF9kcm9wX2dlb21ldHJ5KCkKCiMgRmluYWwgcmVzdWx0CmJpa2UucGFuZWwgPC0gbGFnX3Jlc3VsdAoKICAKYGBgCgpUaGUgZmlndXJlIGJlbG93IGlsbHVzdHJhdGVzIGEgY29uc2lzdGVudCwgc3Ryb25nLCBhbmQgcG9zaXRpdmUgbGluZWFyIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIHRoZSBudW1iZXIgb2YgdHJpcHMgYXQgYSBzdGF0aW9uIGFuZCB0aGUgbnVtYmVyIG9mIHRyaXBzIGF0IG5lYXJieSBzdGF0aW9ucyB0aHJvdWdob3V0IGFsbCB3ZWVrcyBpbiBBdWd1c3QuIFRoaXMgY29uZmlybXMgdGhlIHByZXNlbmNlIG9mIHNpZ25pZmljYW50IHNwYXRpYWwgZGVwZW5kZW5jZSwgc3VnZ2VzdGluZyB0aGF0IHN0YXRpb25zIGluIGNsb3NlIHByb3hpbWl0eSB0ZW5kIHRvIGV4cGVyaWVuY2Ugc2ltaWxhciBsZXZlbHMgb2YgZGVtYW5kLiBHaXZlbiB0aGlzIGZpbmRpbmcsIHdlIHdpbGwgaW5jb3Jwb3JhdGUgdGhpcyBzcGF0aWFsIGxhZyB2YXJpYWJsZSBpbnRvIG91ciByZWdyZXNzaW9uIGFuYWx5c2lzIHRvIGltcHJvdmUgdGhlIG1vZGVsJ3MgcHJlZGljdGl2ZSBhY2N1cmFjeSBieSBhY2NvdW50aW5nIGZvciB0aGUgaW5mbHVlbmNlIG9mIG5lYXJieSBzdGF0aW9uIGFjdGl2aXR5LgoKCgpgYGB7ciB2aXN1YWxpemUgc3BhdGlhbCBjb3JyZWxhdGlvbiwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KCmJpa2UucGFuZWwgJT4lCiAgZ3JvdXBfYnkoaW50ZXJ2YWw2MCkgJT4lIAogIHN1bW1hcml6ZShUcmlwX0NvdW50ID0gbWVhbihUcmlwX0NvdW50KSwKICAgICAgICAgICAgTGFnX0NvdW50ID0gbWVhbihsYWdUcmlwKSkgJT4lCiAgbXV0YXRlKHdlZWsgPSB3ZWVrKGludGVydmFsNjApKSAlPiUKICBnZ3Bsb3QoYWVzKExhZ19Db3VudCwgVHJpcF9Db3VudCkpICsgCiAgICBnZW9tX3BvaW50KHNpemUgPSAxLCBjb2xvciA9ICIjNkUwRTBBIikgKyBnZW9tX3Ntb290aChtZXRob2QgPSAibG0iLCBzZT0gRkFMU0UsIGNvbG9yPSIjMTI0RTc4IikgKwogICAgZmFjZXRfd3JhcCh+d2VlaywgbmNvbD04KSArIAogICAgbGFicyh0aXRsZT0iQ29ycmVsYXRpb24gQmV0d2VlbiBTcGF0aWFsIExhZyBUcmlwcyBhbmQgVHJpcCBDb3VudCIsCiAgICAgICAgIHg9IkxhZyBUcmlwIENvdW50IiwgeT0iTWVhbiBUcmlwIENvdW50IikgKyAKICB0aGVtZShwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSA5LGZhY2UgPSAiaXRhbGljIiksCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIsIGZhY2UgPSAiYm9sZCIpLCAKICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGF4aXMudGlja3MueCA9IGVsZW1lbnRfYmxhbmsoKSwgCiAgICAgICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF90ZXh0KHNpemU9OCksIAogICAgICAgIGF4aXMudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9MTApLCAKICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfcmVjdChjb2xvdXIgPSAiZ3JleSIsIGZpbGw9TkEsIHNpemU9MC44KSkKCmBgYAoKCiMgT0xTIFJlZ3Jlc3Npb24KCldlIGRpdmlkZWQgb3VyIGZpdmUgd2Vla3Mgb2YgZGF0YSBpbnRvIGEgdHJhaW5pbmcgc2V0IGNvbnNpc3Rpbmcgb2YgdGhyZWUgd2Vla3MgYW5kIGEgdGVzdGluZyBzZXQgY29tcHJpc2luZyB0aGUgcmVtYWluaW5nIHR3byB3ZWVrcy4gVXNpbmcgdGhlIHRyYWluaW5nIGRhdGEsIHdlIGVzdGltYXRlZCBmb3VyIGRpc3RpbmN0IGxpbmVhciByZWdyZXNzaW9uIG1vZGVscywgZWFjaCBpbmNvcnBvcmF0aW5nIGRpZmZlcmVudCBmaXhlZCBlZmZlY3RzOgoKLSByZWcxIGNhcHR1cmVzIG9ubHkgdGVtcG9yYWwgZmFjdG9ycywgaW5jbHVkaW5nIGhvdXItb2YtZGF5IGZpeGVkIGVmZmVjdHMsIGRheSBvZiB0aGUgd2Vlaywgd2luZCBzcGVlZCwgYW5kIHRlbXBlcmF0dXJlLgotIHJlZzIgZW1waGFzaXplcyBzcGF0aWFsIGNvbXBvbmVudHMsIHVzaW5nIHN0YXRpb24tc3BlY2lmaWMgZWZmZWN0cyBhbmQgd2VhdGhlciBjb25kaXRpb25zLgotIHJlZzMgY29tYmluZXMgYm90aCBzcGF0aWFsIGFuZCB0ZW1wb3JhbCBlbGVtZW50cyB0byBhY2NvdW50IGZvciBpbnRlcmFjdGlvbnMgYmV0d2VlbiBzcGFjZSBhbmQgdGltZS4KLSByZWc0IGV4dGVuZHMgdGhlIHRoaXJkIG1vZGVsIGJ5IGFkZGluZyBib3RoIHRpbWUgYW5kIHNwYXRpYWwgbGFnIGZlYXR1cmVzLCBjYXB0dXJpbmcgdGVtcG9yYWwgYW5kIHNwYXRpYWwgZGVwZW5kZW5jaWVzIHRvIGVuaGFuY2UgcHJlZGljdGl2ZSBhY2N1cmFjeS4KCgoKCmBgYHtyIGZpdHRpbmcgcmVncmVzc2lvbn0KCmJpa2UuVHJhaW4gPC0gZmlsdGVyKGJpa2UucGFuZWwsIHdlZWsgPD0gMzMpCmJpa2UuVGVzdCA8LSBmaWx0ZXIoYmlrZS5wYW5lbCwgd2VlayA+IDMzKQoKIyBUaW1lCnJlZzEgPC0gCiAgbG0oVHJpcF9Db3VudCB+ICBob3VyKGludGVydmFsNjApICsgZG90dyArIFRlbXBlcmF0dXJlICsgV2luZF9TcGVlZCwgZGF0YT1iaWtlLlRyYWluKQoKIyBTcGFjZQpyZWcyIDwtIAogIGxtKFRyaXBfQ291bnQgfiAgc3RhcnRfc3RhdGlvbl9uYW1lICsgVGVtcGVyYXR1cmUgKyBXaW5kX1NwZWVkLCAgZGF0YT1iaWtlLlRyYWluKQoKIyBUaW1lIGFuZCBTcGFjZQpyZWczIDwtIAogIGxtKFRyaXBfQ291bnQgfiAgc3RhcnRfc3RhdGlvbl9uYW1lICsgaG91cihpbnRlcnZhbDYwKSArIGRvdHcgKyBUZW1wZXJhdHVyZSArIFdpbmRfU3BlZWQsIAogICAgIGRhdGE9YmlrZS5UcmFpbikKCiMgVGltZSBhbmQgU3BhY2UgYW5kIFRpbWVMYWcgYW5kIFNwYWNlTGFnCnJlZzQgPC0gCiAgbG0oVHJpcF9Db3VudCB+ICBzdGFydF9zdGF0aW9uX25hbWUgKyAgaG91cihpbnRlcnZhbDYwKSArIGRvdHcgKyBUZW1wZXJhdHVyZSArIFdpbmRfU3BlZWQgKwogICAgICAgICAgICAgICAgICAgbGFnSG91ciArIGxhZzJIb3VycyArIGxhZzEySG91cnMgKyBsYWcxZGF5ICsgbGFnVHJpcCwgCiAgICAgZGF0YT1iaWtlLlRyYWluKQpgYGAKCiMjIE1ha2luZyBQcmVkaWN0aW9ucwoKV2UgdXRpbGl6ZWQgdGhlIHB1cnJyIHBhY2thZ2UgdG8gZWZmaWNpZW50bHkgaXRlcmF0ZSBvdmVyIGEgc2V0IG9mIG5lc3RlZCBkYXRhIGZyYW1lcywgYWxsb3dpbmcgdXMgdG8gY29tcGFyZSBkaWZmZXJlbnQgbW9kZWwgc3BlY2lmaWNhdGlvbnMuIFRvIHByZXBhcmUgZm9yIHRlc3RpbmcsIHdlIG9yZ2FuaXplZCBvdXIgZGF0YSBieSB3ZWVrLCBuZXN0aW5nIGl0IGludG8gc2VwYXJhdGUgdGliYmxlcy4gV2UgdGhlbiBjcmVhdGVkIGEgc2ltcGxlIHByZWRpY3Rpb24gZnVuY3Rpb24gdGhhdCB0YWtlcyBhIHRpYmJsZSAoZGF0KSBhbmQgYSByZWdyZXNzaW9uIG1vZGVsIChmaXQpIGFzIGlucHV0cyBhbmQgZ2VuZXJhdGVzIHByZWRpY3Rpb25zLCByZXR1cm5pbmcgdGhlbSBhcyBwcmVkLiBUaGlzIGFwcHJvYWNoIHN0cmVhbWxpbmVzIHRoZSBwcm9jZXNzIG9mIGFwcGx5aW5nIG91ciBtb2RlbHMgYWNyb3NzIG11bHRpcGxlIHN1YnNldHMgb2YgZGF0YSwgbWFraW5nIGl0IGVhc3kgdG8gZXZhbHVhdGUgYW5kIGNvbXBhcmUgdGhlaXIgcGVyZm9ybWFuY2UuCgpgYGB7ciBuZXN0IGRhdGEgYW5kIHByZWRpY3QgZnVuY3Rpb24sIHdhcm5pbmc9RkFMU0V9CgpiaWtlLlRlc3Qud2Vla05lc3QgPC0gCiAgYmlrZS5UZXN0ICU+JQogIG5lc3QoLXdlZWspIAoKbW9kZWxfcHJlZCA8LSBmdW5jdGlvbihkYXQsIGZpdCl7CiAgIHByZWQgPC0gcHJlZGljdChmaXQsIG5ld2RhdGEgPSBkYXQpfQoKYGBgCgoKVGhlIG5lc3RlZCBkYXRhIGZvcm1hdCBlbmFibGVzIHVzIHRvIGl0ZXJhdGUgdGhyb3VnaCBlYWNoIG1vZGVsIGZvciBldmVyeSB3ZWVrIGFuZCBjb21wdXRlIHN1bW1hcnkgc3RhdGlzdGljcyBlZmZpY2llbnRseS4gSW4gdGhlIGNvZGUgYmxvY2sgdGhhdCBmb2xsb3dzLCB3ZSBnZW5lcmF0ZSB3ZWVrX3ByZWRpY3Rpb25zIGZvciBlYWNoIHdlZWsgd2l0aGluIHRoZSB0ZXN0aW5nIHNldC4gVXNpbmcgdGhlIG1hcCBmdW5jdGlvbiBmcm9tIHB1cnJyLCB3ZSBhcHBseSB0aGUgbW9kZWxfcHJlZCBmdW5jdGlvbiB0byBlYWNoIG5lc3RlZCB0aWJibGUuIEZvciBpbnN0YW5jZSwgaW4gdGhlIGZpcnN0IGxpbmUgb2YgdGhlIG11dGF0ZSBvcGVyYXRpb24sIGEgbmV3IGNvbHVtbiBjYWxsZWQgVGltZV9GRSBpcyBjcmVhdGVkLCB3aGljaCBjb250YWlucyBwcmVkaWN0aW9ucyBmcm9tIHJlZzEsIHRoZSBtb2RlbCB0aGF0IGluY2x1ZGVzIHRpbWUgZml4ZWQgZWZmZWN0cy4gVGhlc2UgcHJlZGljdGlvbnMgYXJlIHByb2R1Y2VkIGJ5IG1hcHBpbmcgbW9kZWxfcHJlZCB0byBlYWNoIGRhdGEgc3Vic2V0LCB3aXRoIGZpdCBzZXQgdG8gdGhlIHJlZzEgbW9kZWwuIFRoaXMgbWV0aG9kIGFsbG93cyB1cyB0byBlYXNpbHkgYXBwbHkgbXVsdGlwbGUgbW9kZWxzIGFuZCBnYXRoZXIgcHJlZGljdGlvbiByZXN1bHRzIGZvciBjb21wYXJpc29uLgoKYGBge3IgbWFraW5nIHByZWRpY3Rpb25zLCB3YXJuaW5nPUZBTFNFfQoKd2Vla19wcmVkaWN0aW9ucyA8LSAKICBiaWtlLlRlc3Qud2Vla05lc3QgJT4lIAogICAgbXV0YXRlKEFUaW1lX0ZFID0gbWFwKC54ID0gZGF0YSwgZml0ID0gcmVnMSwgLmYgPSBtb2RlbF9wcmVkKSwKICAgICAgICAgICBCU3BhY2VfRkUgPSBtYXAoLnggPSBkYXRhLCBmaXQgPSByZWcyLCAuZiA9IG1vZGVsX3ByZWQpLAogICAgICAgICAgIENUaW1lX1NwYWNlX0ZFID0gbWFwKC54ID0gZGF0YSwgZml0ID0gcmVnMywgLmYgPSBtb2RlbF9wcmVkKSwKICAgICAgICAgICBEVGltZV9TcGFjZV9GRV9MYWdzID0gbWFwKC54ID0gZGF0YSwgZml0ID0gcmVnNCwgLmYgPSBtb2RlbF9wcmVkKSkgJT4lIAogICAgZ2F0aGVyKFJlZ3Jlc3Npb24sIFByZWRpY3Rpb24sIC1kYXRhLCAtd2VlaykgJT4lCiAgICBtdXRhdGUoT2JzZXJ2ZWQgPSBtYXAoZGF0YSwgcHVsbCwgVHJpcF9Db3VudCksCiAgICAgICAgICAgQWJzb2x1dGVfRXJyb3IgPSBtYXAyKE9ic2VydmVkLCBQcmVkaWN0aW9uLCAgfiBhYnMoLnggLSAueSkpLAogICAgICAgICAgIE1BRSA9IG1hcF9kYmwoQWJzb2x1dGVfRXJyb3IsIG1lYW4sIG5hLnJtID0gVFJVRSksCiAgICAgICAgICAgc2RfQUUgPSBtYXBfZGJsKEFic29sdXRlX0Vycm9yLCBzZCwgbmEucm0gPSBUUlVFKSkKCmBgYAoKIyMgRXJyb3IgQW5hbHlzaXMKCldoZW4gY29tcGFyaW5nIHRoZSBtZWFuIGFic29sdXRlIGVycm9ycyAoTUFFKSBhY3Jvc3Mgb3VyIGZvdXIgbW9kZWxzLCB3ZSBvYnNlcnZlIHRoYXQgTW9kZWwgMSwgd2hpY2ggb25seSBpbmNvcnBvcmF0ZXMgdGltZSBlZmZlY3RzLCBoYXMgdGhlIGhpZ2hlc3QgZXJyb3IgcmF0ZS4gTW9kZWxzIDIgYW5kIDMsIHdoaWNoIGFjY291bnQgZm9yIHNwYXRpYWwgZWZmZWN0cyBhbmQgdGhlIGNvbWJpbmF0aW9uIG9mIHNwYWNlIGFuZCB0aW1lLCBzaG93IGltcHJvdmVkIGFjY3VyYWN5LiBIb3dldmVyLCB0aGUgZ3JlYXRlc3QgZW5oYW5jZW1lbnQgaW4gcHJlZGljdGl2ZSBwZXJmb3JtYW5jZSBjb21lcyBmcm9tIE1vZGVsIDQsIHdoaWNoIGluY2x1ZGVzIGJvdGggc3BhY2UgYW5kIHRpbWUgbGFnIGZlYXR1cmVzLCBzaWduaWZpY2FudGx5IHJlZHVjaW5nIHRoZSBlcnJvci4gVG8gZW5zdXJlIHRoZSByZWxpYWJpbGl0eSBvZiBNb2RlbCA0LCB3ZSBhbHNvIHBlcmZvcm1lZCBhIHZhcmlhbmNlIGluZmxhdGlvbiBmYWN0b3IgKFZJRikgYW5hbHlzaXMgdG8gY29uZmlybSB0aGF0IG11bHRpY29sbGluZWFyaXR5IHdhcyBub3QgYW4gaXNzdWUgYW1vbmcgdGhlIHByZWRpY3RvcnMuCgpgYGB7ciBwbG90IGVycm9yc30KCndlZWtfcHJlZGljdGlvbnMgJT4lCiAgZHBseXI6OnNlbGVjdCh3ZWVrLCBSZWdyZXNzaW9uLCBNQUUpICU+JQogIGdhdGhlcihWYXJpYWJsZSwgTUFFLCAtUmVncmVzc2lvbiwgLXdlZWspICU+JQogIGdncGxvdChhZXMod2VlaywgTUFFKSkgKyAKICAgIGdlb21fYmFyKGFlcyhmaWxsID0gUmVncmVzc2lvbiksIHBvc2l0aW9uID0gImRvZGdlIiwgc3RhdD0iaWRlbnRpdHkiKSArCiAgICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBwYWxldHRlNCkgKwogICAgbGFicyh0aXRsZSA9ICJNZWFuIEFic29sdXRlIEVycm9ycyBieSBNb2RlbCBTcGVjaWZpY2F0aW9uIGFuZCBXZWVrIikgKwogIHRoZW1lKHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDksZmFjZSA9ICJpdGFsaWMiKSwKICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgZmFjZSA9ICJib2xkIiksIAogICAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpLAogICAgYXhpcy50aWNrcy54ID0gZWxlbWVudF9ibGFuaygpLCAKICAgICAgICBheGlzLnRleHQueT1lbGVtZW50X3RleHQoc2l6ZT04KSwgCiAgICAgICAgYXhpcy50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT0xMCksIAogICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9yZWN0KGNvbG91ciA9ICJncmV5IiwgZmlsbD1OQSwgc2l6ZT0wLjgpKQoKYGBgCgpUaGUgZmlndXJlIGJlbG93IHByb3ZpZGVzIGEgdmlzdWFsIGNvbXBhcmlzb24gb2YgdGhlIGFjY3VyYWN5IG9mIGVhY2ggbW9kZWwgYnkgcGxvdHRpbmcgdGhlIHByZWRpY3RlZCB2ZXJzdXMgb2JzZXJ2ZWQgbnVtYmVyIG9mIHRyaXBzLiBBbW9uZyB0aGUgZm91ciBtb2RlbHMsIE1vZGVsIDQgY2xlYXJseSBkZW1vbnN0cmF0ZXMgc3VwZXJpb3IgcHJlZGljdGl2ZSBwZXJmb3JtYW5jZSwgY2xvc2VseSBhbGlnbmluZyB3aXRoIHRoZSBhY3R1YWwgYmlrZXNoYXJlIGRlbWFuZC4gVGhpcyBoaWdobGlnaHRzIHRoZSBlZmZlY3RpdmVuZXNzIG9mIGluY29ycG9yYXRpbmcgYm90aCBzcGF0aWFsIGFuZCB0ZW1wb3JhbCBsYWcgZmVhdHVyZXMsIG1ha2luZyBNb2RlbCA0IHRoZSBtb3N0IHJvYnVzdCBhbmQgcmVsaWFibGUgaW4gZm9yZWNhc3RpbmcgdHJpcCBjb3VudHMuCgoKCgpgYGB7ciBjb21wYXJpbmcgcHJlZGljdGVkIGFuZCBvYnNlcnZlZCwgZmlnLmhlaWdodD04LCBmaWcud2lkdGg9MTEsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGNhY2hlPVRSVUV9Cgp3ZWVrX3ByZWRpY3Rpb25zICU+JSAKICAgIG11dGF0ZShpbnRlcnZhbDYwID0gbWFwKGRhdGEsIHB1bGwsIGludGVydmFsNjApLAogICAgICAgICAgIHN0YXJ0X3N0YXRpb25faWQgPSBtYXAoZGF0YSwgcHVsbCwgc3RhcnRfc3RhdGlvbl9pZCkpICU+JQogICAgZHBseXI6OnNlbGVjdChpbnRlcnZhbDYwLCBzdGFydF9zdGF0aW9uX2lkLCBPYnNlcnZlZCwgUHJlZGljdGlvbiwgUmVncmVzc2lvbikgJT4lCiAgICB1bm5lc3QoKSAlPiUKICAgIGdhdGhlcihWYXJpYWJsZSwgVmFsdWUsIC1SZWdyZXNzaW9uLCAtaW50ZXJ2YWw2MCwgLXN0YXJ0X3N0YXRpb25faWQpICU+JQogICAgZ3JvdXBfYnkoUmVncmVzc2lvbiwgVmFyaWFibGUsIGludGVydmFsNjApICU+JQogICAgc3VtbWFyaXplKFZhbHVlID0gc3VtKFZhbHVlKSkgJT4lCiAgICBnZ3Bsb3QoYWVzKGludGVydmFsNjAsIFZhbHVlLCBjb2xvdXI9VmFyaWFibGUpKSArIAogICAgICBnZW9tX2xpbmUoc2l6ZSA9IDEuMSkgKyAKICAgICAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcz1wYWxldHRlMikrCiAgICAgIGZhY2V0X3dyYXAoflJlZ3Jlc3Npb24sIG5jb2w9MSkgKwogICAgICBsYWJzKHRpdGxlID0gIlByZWRpY3RlZC9PYnNlcnZlZCBCaWtlIFNoYXJlIFRpbWUgU2VyaWVzIiwgc3VidGl0bGUgPSAiSmVyc2V5IENpdHk6IGEgdGVzdCBzZXQgb2YgMiB3ZWVrcyIsICB4ID0gIkhvdXIiLCB5PSAiU3RhdGlvbiBUcmlwcyIpICsgCiAgIHRoZW1lKHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDksZmFjZSA9ICJpdGFsaWMiKSwKICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgZmFjZSA9ICJib2xkIiksIAogICAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpLAogICAgYXhpcy50aWNrcy54ID0gZWxlbWVudF9ibGFuaygpLCAKICAgICAgICBheGlzLnRleHQueT1lbGVtZW50X3RleHQoc2l6ZT04KSwgCiAgICAgICAgYXhpcy50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT0xMCksIAogICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9yZWN0KGNvbG91ciA9ICJncmV5IiwgZmlsbD1OQSwgc2l6ZT0wLjgpKQoKYGBgCgpUbyBldmFsdWF0ZSB3aGV0aGVyIG91ciBwcmVkaWN0aW9ucyBnZW5lcmFsaXplIHdlbGwgYWNyb3NzIGRpZmZlcmVudCBsb2NhdGlvbnMgYW5kIHRpbWVzLCB3ZSBtYXBwZWQgdGhlIG1lYW4gYWJzb2x1dGUgZXJyb3IgKE1BRSkgb2YgTW9kZWwgNCBhY3Jvc3MgdmFyaW91cyBzdGF0aW9ucy4gVGhlIHJlc3VsdHMgc2hvdyB0aGF0IHRoZSBtb2RlbCBzdHJ1Z2dsZXMgdG8gYWNjdXJhdGVseSBwcmVkaWN0IHRoZSBudW1iZXIgb2YgcmlkZXMgYXQgc3RhdGlvbnMgbG9jYXRlZCBhbG9uZyB0aGUgSHVkc29uIFJpdmVyLCB3aGljaCB0ZW5kIHRvIGV4cGVyaWVuY2UgaGlnaCBiaWtlc2hhcmUgZGVtYW5kIG9uIGJvdGggd2Vla2RheXMgYW5kIHdlZWtlbmRzLiBIb3dldmVyLCBmb3Igc3RhdGlvbnMgd2l0aCBsb3dlciByaWRlcnNoaXAgdGhyb3VnaG91dCBBdWd1c3QsIHRoZSBtb2RlbCBwZXJmb3JtcyBzaWduaWZpY2FudGx5IGJldHRlciwgZGVtb25zdHJhdGluZyByZWFzb25hYmxlIHByZWRpY3RpdmUgYWNjdXJhY3kgaW4gdGhvc2UgYXJlYXMuCgoKCgoKYGBge3IgZXJyb3IgYnkgc3RhdGlvbiwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRSwgY2FjaGU9VFJVRX0KCndlZWtfcHJlZGljdGlvbnMgJT4lIAogICAgbXV0YXRlKGludGVydmFsNjAgPSBtYXAoZGF0YSwgcHVsbCwgaW50ZXJ2YWw2MCksCiAgICAgICAgICAgc3RhcnRfc3RhdGlvbl9pZCA9IG1hcChkYXRhLCBwdWxsLCBzdGFydF9zdGF0aW9uX2lkKSwgCiAgICAgICAgICAgc3RhcnRfbGF0aXR1ZGUgPSBtYXAoZGF0YSwgcHVsbCwgc3RhcnRfbGF0KSwgCiAgICAgICAgICAgc3RhcnRfbG9uZ2l0dWRlID0gbWFwKGRhdGEsIHB1bGwsIHN0YXJ0X2xuZykpICU+JQogICAgc2VsZWN0KGludGVydmFsNjAsIHN0YXJ0X3N0YXRpb25faWQsIHN0YXJ0X2xvbmdpdHVkZSwgc3RhcnRfbGF0aXR1ZGUsIE9ic2VydmVkLCBQcmVkaWN0aW9uLCBSZWdyZXNzaW9uKSAlPiUKICAgIHVubmVzdCgpICU+JQogIGZpbHRlcihSZWdyZXNzaW9uID09ICJEVGltZV9TcGFjZV9GRV9MYWdzIikgJT4lCiAgZ3JvdXBfYnkoc3RhcnRfc3RhdGlvbl9pZCwgc3RhcnRfbG9uZ2l0dWRlLCBzdGFydF9sYXRpdHVkZSkgJT4lCiAgc3VtbWFyaXplKE1BRSA9IG1lYW4oYWJzKE9ic2VydmVkLVByZWRpY3Rpb24pLCBuYS5ybSA9IFRSVUUpKSU+JQpnZ3Bsb3QoLikrCiAgZ2VvbV9zZihkYXRhID0gamNUcmFjdHMsICBmaWxsID0gIndoaXRlIikrCiAgZ2VvbV9wb2ludChhZXMoeCA9IHN0YXJ0X2xvbmdpdHVkZSwgeSA9IHN0YXJ0X2xhdGl0dWRlLCBjb2xvciA9IE1BRSksIAogICAgICAgICAgICAgZmlsbCA9ICJ0cmFuc3BhcmVudCIpKwogIHNjYWxlX2NvbG9yX2NvbnRpbnVvdXMobG93ID0gIiMxMjRFNzgiLCBoaWdoID0gIiM2RTBFMEEiLCBuYW1lPSAiTUFFIikrCiB5bGltKG1pbihiaWtlX2NlbnN1cyRzdGFydF9sYXQpLCBtYXgoYmlrZV9jZW5zdXMkc3RhcnRfbGF0KSkrCiAgeGxpbShtaW4oYmlrZV9jZW5zdXMkc3RhcnRfbG5nKSwgbWF4KGJpa2VfY2Vuc3VzJHN0YXJ0X2xuZykpICsKICBsYWJzKHRpdGxlPSJNZWFuIEFicyBFcnJvciwgVGVzdCBTZXQsIE1vZGVsIDQiKSArIAogIHRoZW1lKGF4aXMudGV4dC54PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRleHQueT1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aWNrcyA9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpdGxlLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gOSxmYWNlID0gIml0YWxpYyIpLAogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSwKICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfcmVjdChjb2xvdXIgPSAiZ3JleSIsIGZpbGw9TkEsIGxpbmV3aWR0aD0wLjgpCiAgICAgICAgKQoKYGBgCgpXaGVuIGV4YW1pbmluZyBNb2RlbCA0J3MgcGVyZm9ybWFuY2UgYWNyb3NzIGJvdGggc3BhY2UgYW5kIHRpbWUsIHRoZSBmaWd1cmUgZGVtb25zdHJhdGVzIHRoYXQgdGhlIG1vZGVsIG1haW50YWlucyBjb25zaXN0ZW50IHByZWRpY3Rpb25zIHRocm91Z2hvdXQgZGlmZmVyZW50IHBlcmlvZHMuIFN0YXRpb25zIGlkZW50aWZpZWQgYXMgaGF2aW5nIGhpZ2ggZGVtYW5kIG9uIHdlZWtlbmRzIGFyZSBzaW1pbGFybHkgcHJlZGljdGVkIHRvIGhhdmUgaGlnaCBkZW1hbmQgZHVyaW5nIHdlZWtkYXlzLiBIb3dldmVyLCB0aGUgZXJyb3JzIHBlcnNpc3Qgb3ZlciB0aW1lLCBpbmRpY2F0aW5nIHRoYXQgc3RhdGlvbnMgd2l0aCBoaXN0b3JpY2FsbHkgaGlnaCBkZW1hbmQgYWxzbyBleGhpYml0IGdyZWF0ZXIgdmFyaWFiaWxpdHkgaW4gcmlkZSBjb3VudHMsIG1ha2luZyB0aGVtIG1vcmUgY2hhbGxlbmdpbmcgdG8gcHJlZGljdCB3aXRoIG91ciBjdXJyZW50IHNldCBvZiBmZWF0dXJlcy4gSW4gZmFjdCwgb3VyIG1vZGVsIG9ubHkgZXhwbGFpbnMgNTAuMTIlIG9mIHRoZSB2YXJpYWJpbGl0eSwgd2hpY2ggaXMgYSBsaW1pdGF0aW9uLiBUaGUgY29uY2VybiBpcyB0aGF0IGlmIHRoZXNlIGxhcmdlciBlcnJvcnMgcmVtYWluIHVuYWRkcmVzc2VkIGZvciBoaWdoLWRlbWFuZCBzdGF0aW9ucywgd2UgcmlzayBmYWlsaW5nIHRvIGFkZXF1YXRlbHkgbWVldCBiaWtlc2hhcmUgdXNlcnMnIG5lZWRzIGF0IHRoZXNlIGNydWNpYWwgbG9jYXRpb25zLgoKCgoKCmBgYHtyIGNvbXBhcmluZyBlcnJvcnMgYWNyb3NzIHNwYWNlIGFuZCB0aW1lLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFLCBjYWNoZT1UUlVFfQoKd2Vla19wcmVkaWN0aW9ucyAlPiUgCiAgICBtdXRhdGUoaW50ZXJ2YWw2MCA9IG1hcChkYXRhLCBwdWxsLCBpbnRlcnZhbDYwKSwKICAgICAgICAgICBzdGFydF9zdGF0aW9uX2lkID0gbWFwKGRhdGEsIHB1bGwsIHN0YXJ0X3N0YXRpb25faWQpLCAKICAgICAgICAgICBzdGFydF9sYXRpdHVkZSA9IG1hcChkYXRhLCBwdWxsLCBzdGFydF9sYXQpLCAKICAgICAgICAgICBzdGFydF9sb25naXR1ZGUgPSBtYXAoZGF0YSwgcHVsbCwgc3RhcnRfbG5nKSwKICAgICAgICAgICBkb3R3ID0gbWFwKGRhdGEsIHB1bGwsIGRvdHcpICkgJT4lCiAgICBzZWxlY3QoaW50ZXJ2YWw2MCwgc3RhcnRfc3RhdGlvbl9pZCwgc3RhcnRfbG9uZ2l0dWRlLCAKICAgICAgICAgICBzdGFydF9sYXRpdHVkZSwgT2JzZXJ2ZWQsIFByZWRpY3Rpb24sIFJlZ3Jlc3Npb24sCiAgICAgICAgICAgZG90dykgJT4lCiAgICB1bm5lc3QoKSAlPiUKICBmaWx0ZXIoUmVncmVzc2lvbiA9PSAiRFRpbWVfU3BhY2VfRkVfTGFncyIpJT4lCiAgbXV0YXRlKHdlZWtlbmQgPSBpZmVsc2UoZG90dyAlaW4lIGMoIlN1biIsICJTYXQiKSwgIldlZWtlbmQiLCAiV2Vla2RheSIpLAogICAgICAgICB0aW1lX29mX2RheSA9IGNhc2Vfd2hlbihob3VyKGludGVydmFsNjApIDwgNyB8IGhvdXIoaW50ZXJ2YWw2MCkgPiAxOCB+ICJPdmVybmlnaHQiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBob3VyKGludGVydmFsNjApID49IDcgJiBob3VyKGludGVydmFsNjApIDwgMTAgfiAiQU0gUnVzaCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhvdXIoaW50ZXJ2YWw2MCkgPj0gMTAgJiBob3VyKGludGVydmFsNjApIDwgMTUgfiAiTWlkLURheSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhvdXIoaW50ZXJ2YWw2MCkgPj0gMTUgJiBob3VyKGludGVydmFsNjApIDw9IDE4IH4gIlBNIFJ1c2giKSkgJT4lCiAgZ3JvdXBfYnkoc3RhcnRfc3RhdGlvbl9pZCwgd2Vla2VuZCwgdGltZV9vZl9kYXksIHN0YXJ0X2xvbmdpdHVkZSwgc3RhcnRfbGF0aXR1ZGUpICU+JQogIHN1bW1hcml6ZShNQUUgPSBtZWFuKGFicyhPYnNlcnZlZC1QcmVkaWN0aW9uKSwgbmEucm0gPSBUUlVFKSklPiUKICBnZ3Bsb3QoLikrCiAgZ2VvbV9zZihkYXRhID0gamNUcmFjdHMsIGNvbG9yID0gImdyZXkiLCBmaWxsID0gInRyYW5zcGFyZW50IikrCiAgZ2VvbV9wb2ludChhZXMoeCA9IHN0YXJ0X2xvbmdpdHVkZSwgeSA9IHN0YXJ0X2xhdGl0dWRlLCBjb2xvciA9IE1BRSksIAogICAgICAgICAgICAgZmlsbCA9ICJ0cmFuc3BhcmVudCIsIHNpemUgPSAwLjUpKwogIHNjYWxlX2NvbG9yX2NvbnRpbnVvdXMobG93ID0gIiMxMjRFNzgiLCBoaWdoID0gIiM2RTBFMEEiLCBuYW1lPSAiTUFFIikrCiB5bGltKG1pbihiaWtlX2NlbnN1cyRzdGFydF9sYXQpLCBtYXgoYmlrZV9jZW5zdXMkc3RhcnRfbGF0KSkrCiAgeGxpbShtaW4oYmlrZV9jZW5zdXMkc3RhcnRfbG5nKSwgbWF4KGJpa2VfY2Vuc3VzJHN0YXJ0X2xuZykpICsKICBmYWNldF9ncmlkKHdlZWtlbmR+dGltZV9vZl9kYXkpKwogIGxhYnModGl0bGU9Ik1lYW4gQWJzb2x1dGUgRXJyb3JzIGJ5IFRpbWUsIFRlc3QgU2V0LCBNb2RlbCA0IikgKyAKICB0aGVtZShheGlzLnRleHQueD1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGlja3MgPWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aXRsZS55ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDksZmFjZSA9ICJpdGFsaWMiKSwKICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgZmFjZSA9ICJib2xkIiksCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X3JlY3QoY29sb3VyID0gImdyZXkiLCBmaWxsPU5BLCBsaW5ld2lkdGg9MC44KQogICAgICAgICkKYGBgCgoKIyMgQ3Jvc3MtVmFsaWRhdGlvbgoKTGFzdGx5LCB3ZSBjb25kdWN0ZWQgMTAwIGNyb3NzLXZhbGlkYXRpb24gdGVzdHMgdXNpbmcgTW9kZWwgNCBvbiBkYXRhIHNwYW5uaW5nIGFsbCBmaXZlIHdlZWtzLiBUaGUgbWVhbiBhYnNvbHV0ZSBlcnJvciAoTUFFKSB3YXMgMC40NCwgaW5kaWNhdGluZyB0aGF0IHdoaWxlIHRoaXMgbW9kZWwgaXMgb3VyIG1vc3QgZWZmZWN0aXZlIHNvbHV0aW9uIHNvIGZhciwgdGhlcmUgaXMgc3RpbGwgYSBub3RhYmxlIGxldmVsIG9mIHByZWRpY3Rpb24gZXJyb3IuIFRvIGltcHJvdmUgdGhlIG1vZGVsIGZ1cnRoZXIsIGl0IHdvdWxkIGJlIHZhbHVhYmxlIHRvIGNyb3NzLXZhbGlkYXRlIGFnYWluc3Qgc29jaW8tZWNvbm9taWMgaW5kaWNhdG9ycy4gVGhpcyBhcHByb2FjaCBjb3VsZCByZXZlYWwgd2hldGhlciBiaWtlIGRlbWFuZCBhdCBzdGF0aW9ucyBpbiBzcGVjaWZpYyBuZWlnaGJvcmhvb2RzIGlzIGJlaW5nIGRpc3Byb3BvcnRpb25hdGVseSB1bmRlci0gb3Igb3Zlci1wcmVkaWN0ZWQuIEhvd2V2ZXIsIG91ciBjdXJyZW50IG1vZGVsIGRvZXMgbm90IGluY29ycG9yYXRlIHNvY2lvZWNvbm9taWMgdmFyaWFibGVzIGFzIHByZWRpY3RvcnMsIGFuZCB0aGUgZHluYW1pY3MgaW4gSmVyc2V5IENpdHnigJRzdWNoIGFzIHRoZSBjb250aW51ZWQgZGV2ZWxvcG1lbnQgb2YgbmV3IHN0YXRpb25z4oCUbWF5IGFsc28gaW1wYWN0IGRlbWFuZCBwYXR0ZXJucy4KCmBgYHtyIGNyb3NzIHZhbGlkYXRpb259CgpmaXRDb250cm9sIDwtIHRyYWluQ29udHJvbChtZXRob2QgPSAiY3YiLCBudW1iZXIgPSAxMDApCgpyZWcuY3YgPC0gdHJhaW4oVHJpcF9Db3VudCB+IHN0YXJ0X3N0YXRpb25fbmFtZSArICBob3VyKGludGVydmFsNjApICsgZG90dyArIFRlbXBlcmF0dXJlICsgV2luZF9TcGVlZCArIGxhZ0hvdXIgKyBsYWcySG91cnMgKyBsYWcxMkhvdXJzICsgbGFnMWRheSArIGxhZ1RyaXAsIGRhdGE9YmlrZS5wYW5lbCwgbWV0aG9kID0gImxtIiwgdHJDb250cm9sID0gZml0Q29udHJvbCwgbmEuYWN0aW9uID0gbmEucGFzcykKCnJlZy5jdiRyZXNhbXBsZSAlPiUgCiAgc3VtbWFyaXNlKE1BRSA9IG1lYW4ocmVnLmN2JHJlc2FtcGxlWywzXSksCiAgICAgICAgICAgIHNkKHJlZy5jdiRyZXNhbXBsZVssM10pCikgJT4lCiAga2JsKGNvbC5uYW1lPWMoJ01lYW4gQWJzb2x1dGUgRXJyb3InLCdTdGFuZGFyZCBEZXZpYXRpb24gb2YgTUFFJykpICU+JQogIGthYmxlX3N0eWxpbmcoYm9vdHN0cmFwX29wdGlvbnMgPSBjKCJzdHJpcGVkIiwgImhvdmVyIiwgImNvbmRlbnNlZCIpKQpgYGAKCgojIENvbmNsdXNpb24KCkluIHN1bW1hcnksIHRoaXMgcmVwb3J0IGhpZ2hsaWdodHMgdGhlIHVyZ2VudCBuZWVkIGZvciBlZmZpY2llbnQgYmlrZXNoYXJlIG1hbmFnZW1lbnQgc3RyYXRlZ2llcyBpbiBKZXJzZXkgQ2l0eSBhbmQgcHJlc2VudHMgYSBtYWNoaW5lIGxlYXJuaW5nIGFwcHJvYWNoIHRvIGFkZHJlc3MgdGhpcyBjaGFsbGVuZ2UuIE91ciBhbmFseXNpcyBkZW1vbnN0cmF0ZXMgdGhhdCBiaWtlc2hhcmUgZGVtYW5kIGlzIGluZmx1ZW5jZWQgYnkgYSBjb21iaW5hdGlvbiBvZiBzcGF0aWFsLCB0ZW1wb3JhbCwgYW5kIHdlYXRoZXItcmVsYXRlZCBmYWN0b3JzLiBTcGVjaWZpY2FsbHksIHRoZSBtb2RlbCBzaG93cyB0aGF0IGZvcmVjYXN0aW5nIGRlbWFuZCBpcyBtb3JlIGFjY3VyYXRlIHdoZW4gY29uc2lkZXJpbmcgdGhlIG51bWJlciBvZiB0cmlwcyBpbiB0aGUgcHJlY2VkaW5nIGFuZCBmb2xsb3dpbmcgaG91cnMsIHRoZSBzYW1lIGhvdXIgb24gdGhlIHByZXZpb3VzIGRheSwgYW5kIG5lYXJieSBzdGF0aW9uIGFjdGl2aXR5LiBXZSBhbHNvIGRpc2NvdmVyZWQgdGhhdCBkZW1hbmQgcGVha3MgZHVyaW5nIHdpbmR5IGFuZCBob3QgZGF5cyBpbiBBdWd1c3QsIHdpdGggdGhlIGhpZ2hlc3QgdXNhZ2Ugb2NjdXJyaW5nIGR1cmluZyB3ZWVrZGF5IFBNIHJ1c2ggaG91cnMgYW5kLCB0byBhIGxlc3NlciBleHRlbnQsIG92ZXJuaWdodC4gV2Vla2VuZHMgc2VlIGEgc2lnbmlmaWNhbnQgc3Bpa2UgaW4gZGVtYW5kIGR1cmluZyB0aGUgUE0gcnVzaCBhcyB3ZWxsLiBOb3RhYmx5LCBDaXRpIEJpa2UgaXMgaGVhdmlseSB1c2VkIGZvciBjb21tdXRpbmcgdG8gTmV3IFlvcmsgQ2l0eSwgbWFraW5nIHN0YXRpb25zIGFsb25nIHRoZSBIdWRzb24gUml2ZXIsIGVzcGVjaWFsbHkgbmVhciBmZXJyeSB0ZXJtaW5hbHMsIHRoZSBtb3N0IGFjdGl2ZS4KCldlIGZ1cnRoZXIgZXhwbG9yZWQgc3BlY2lmaWMgaGlnaC1kZW1hbmQgc3RhdGlvbnMgaW4gZGV0YWlsLiBHcm92ZSBTdCBQQVRIIHN0YXRpb24gcmVjb3JkZWQgYW4gaW1wcmVzc2l2ZSAxLDExNyByaWRlcyBkdXJpbmcgdGhlIHdlZWtkYXkgUE0gcnVzaCBhbmQgODAxIHJpZGVzIG92ZXJuaWdodCBpbiBBdWd1c3QsIHJlZmxlY3RpbmcgaXRzIHN0cmF0ZWdpYyBsb2NhdGlvbiBpbiBkb3dudG93biBKZXJzZXkgQ2l0eS4gSG9ib2tlbiBUZXJtaW5hbCAtIFJpdmVyIFN0ICYgSHVkc29uIFBsIGhhZCA4NjQgcmlkZXMgZHVyaW5nIHdlZWtkYXkgUE0gcnVzaCBob3VycywgbGlrZWx5IGR1ZSB0byBpdHMgcHJveGltaXR5IHRvIG1ham9yIHRyYW5zaXQgY29ubmVjdGlvbnMuIFNvdXRoIFdhdGVyZnJvbnQgV2Fsa3dheSAtIFNpbmF0cmEgRHIgJiAxIFN0IHN0YXRpb24gc2F3IDMxOSByaWRlcyBvdmVybmlnaHQgb24gYSB3ZWVrZW5kIGFuZCA3MzEgZHVyaW5nIHRoZSB3ZWVrZGF5IFBNIHJ1c2gsIGJlbmVmaXRpbmcgZnJvbSBpdHMgbG9jYXRpb24gbmVhciB0aGUgTkogVHJhbnNpdCBUZXJtaW5hbCBhbmQgdGhlIHBvcHVsYXIgUGllciBBIFBhcmsuIFRoZXNlIGZpbmRpbmdzIHN1Z2dlc3QgdGhhdCBzdGF0aW9ucyBleHBlcmllbmNlIGhpZ2hlciBkZW1hbmQgYmVjYXVzZSB0aGV5IHNlcnZlIGtleSBhcmVhcyBzdWNoIGFzIGVtcGxveW1lbnQgaHVicywgdHJhbnNpdCBub2RlcyBmb3IgY29tbXV0aW5nLCBhbmQgcmVjcmVhdGlvbmFsIHNwYWNlcyBhbG9uZyB0aGUgd2F0ZXJmcm9udC4gVG8gYWRkcmVzcyB0aGlzLCBsb2NhbCBhdXRob3JpdGllcyBzaG91bGQgY29uc2lkZXIgcmViYWxhbmNpbmcgYmlrZSBzdXBwbGllcyBtb3JlIHN0cmF0ZWdpY2FsbHkgYXQgdGhlc2UgY3JpdGljYWwgbG9jYXRpb25zLgoKQWx0aG91Z2ggb3VyIG1vZGVsIHN1Y2Nlc3NmdWxseSBjYXB0dXJlcyBvdmVyIGhhbGYgb2YgdGhlIGRlbWFuZCB2YXJpYXRpb25zLCBpdCBzdGlsbCByZXF1aXJlcyBmdXJ0aGVyIHJlZmluZW1lbnQgdG8gaW5jcmVhc2UgaXRzIHByZWRpY3RpdmUgYWNjdXJhY3kuIFdpdGhvdXQgdGhlc2UgaW1wcm92ZW1lbnRzLCB0aGUgbW9kZWwgcmlza3MgZmFpbGluZyB0byBtZWV0IHJpZGVyIG5lZWRzIGFuZCBjb21wbGljYXRpbmcgYmlrZSByZWRpc3RyaWJ1dGlvbiBlZmZvcnRzLiBOZXZlcnRoZWxlc3MsIHRoZSBzdHJhaWdodGZvcndhcmQgYW5kIG9wZW4tc291cmNlIG5hdHVyZSBvZiBvdXIgYXBwcm9hY2ggbWFrZXMgaXQgYSB2YWx1YWJsZSB0b29sIGZvciBiaWtlc2hhcmUgb3BlcmF0b3JzIGxvb2tpbmcgdG8gaW1wcm92ZSBzZXJ2aWNlIHF1YWxpdHkgYW5kIG9wdGltaXplIGJpa2UgYXZhaWxhYmlsaXR5LiBBcyB1cmJhbiB0cmFuc3BvcnRhdGlvbiBjb250aW51ZXMgdG8gZXZvbHZlLCB0aGUgdXNlIG9mIHByZWRpY3RpdmUgbW9kZWxzIGxpa2Ugb3VycyB3aWxsIGJlIHZpdGFsIGZvciBjcmVhdGluZyBzdXN0YWluYWJsZSwgd2VsbC1iYWxhbmNlZCBtb2JpbGl0eSBzeXN0ZW1zLgo=