Version 1.0 | First Created Nov 4, 2023 | Updated Nov 15, 2023

Keywords: Transportation Planning, Rideshare Forecast, Spatial and Serial Autocorrelation, Nested Tibble, OLS Regression, time lag, gganimate, purrr

Introduction

Bikeshare programs have become an integral part of urban transportation system, providing a sustainable and convenient alternative to traditional commuting methods. Citi Bike is one of the earliest bicycle sharing system created by Lyft in 2008 at NYC. But in recent years, this program also extends to Jersey City across the Hudson River in order to cater to the dynamic mobility needs of residents who had to commute to Manhattan for work. As the popularity of Citi Bike continues to soar, there arises a pressing need for effective management strategies to ensure the availability of bikes at each station in Jersey City.

Bikesharing (and ridesharing) inherently involves the challenge of maintaining a delicate equilibrium in bike distribution, ie. “re-balancing”. This issue arises from the spatial and temporal variations in user demand. If certain stations experience a surge in demand during morning rush hours while others witness a decline, this could result in station clusters with depleted or surplus bike stocks, thereby compromising user experience. Companies like Uber & Lyft generate and analyze tremendous amounts of data to incentivize bike (ride) share use, solve routing problems, calculate pricing, and obviously forecast demands, but they are not very transparent about the algorithm they use. Therefore, the goal of this report is keep a simple, but open source version of bikeshare demand prediction, by harnessing of power of machine learning, particularly the Ordinary Least Square (OLS) regression to rapidly predict the demand for bikes across various stations in Jersey City. We will perform analysis on the historical data on bike usage at different stations and different time as well as under different weather conditions. The forecast allows the Citi Bike program to proactively address imbalances, strategically re-positioning bikes in anticipation of peak usage time or special events.

The subsequent sections on this report is organized as follows. Firstly, we load in our datasets for a quick examinations and perform some necessary feature engineering. Secondly, we conduct exploratory analysis on the temporal and spatial aspects of bikeshare data. We will also look at some weather data in this process. Thirdly, we create a space-time panel where each row is a unique instance of time-space combination of rides and visualize the correlation between space, time, weather, and number of rides. And finally, we make created four different models, test their sensibility to errors, and conduct cross validation.

The GitHub repository for this report is here.

Set Up

The data we use here is obtained from NYC OpenData that contains the latest record of Citi Bike usage on a monthly base. Note that while Jersey City is located in New Jersey, Citi Bike is mainly operated in NYC and therefore its dataset is also operated by NYC. We selected all bikeshare data in Jersey City from Aug 1 2023 to Aug 31 2023 as our temporal frame. The dataset mainly includes locations and names of stations of which the user started the ride as well as end the ride. In addition, we also downloaded the census tract geometries of the entire Hudson County via tidycensus, which includes Jersey City.

nycbike <- read.csv(url("https://raw.githubusercontent.com/emilyzhou112/MUSA5080-NJ-BikeShare/main/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()

Since our raw bikeshare data is a non-spatial layer, despite containing spatial attributes, our next step is to make it spatial by joining it to the census geometry. We are only interested in the start location of each ride because we want to predict the capacity of each station when the user need a bike to start the ride. This requires us to filter out rows with missing latitude and longitude. We also found that some stations have erroneous longitude and latitude coding that lead to these points being located in the Hudson River. These points also need to be removed.

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 visualization shows all the steps we did above.

ggplot()+
  geom_sf(data = jcTracts %>%
          st_transform(crs=4326), fill="white")+
  geom_point(data = bike_census, aes(x=start_lng, y = start_lat), 
            color = "#6E0E0A", 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 will perform some basic feature engineering using R’s powerful lubridate package to extract some temporal information for each ride. This includes calculating the 60min and 15min interval for the starting time of each ride, and categorizing these intervals into by the time of day into “Overnight”, “AM Rush”, “Mid-Day”, and “PM Rush” and by day of week into weekday and 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, label=TRUE)) %>% 
  mutate(time_of_day = case_when(hour(interval60) < 7 | hour(interval60) > 18 ~ "Overnight",
                                 hour(interval60) >= 7 & hour(interval60) < 10 ~ "AM Rush",
                                 hour(interval60) >= 10 & hour(interval60) < 15 ~ "Mid-Day",
                                 hour(interval60) >= 15 & hour(interval60) <= 18 ~ "PM Rush")) %>% 
  mutate(hour = hour(started_at),
                weekend = ifelse(dotw %in% c("Sun", "Sat"), "Weekend", "Weekday"))

Interested in seeing the commuting patterns to get a sense of rider’s daily behavior, we created line strings that connect between the start and end locations of each ride. For the simplicity of visualization, we only visualize 200 rides. We may see that most of the rides occur within Jersey City, but there are also people who commute using Citi Bike 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

Starting with temporal analysis, we first visualized the bikeshare trips per hour in Jersey City in August 2023. It is clear that there’s some form of circularity in that there are both peak hours and non-peak hours during a single day. Also, we may see that five consecutive higher peaks are usually followed by two less higher peaks. These correspond to high bikeshare need during rush hours and low bikeshare needs overnight, in early mornings, and over the weekend

ggplot(bike_census %>%
         group_by(interval60) %>%
         tally())+
  geom_line(aes(x = interval60, y = n), color= "#6E0E0A")+
  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))

Then, we visualized the distribution of trips by stations at each hour in August. We may see that the distribution of rides is highly right skewed, with most stations having zero or a few number of trips each hour and a couple of stations having much more rides, up to 40 or even 50 rides at a particular hour on a particular day in August 2023.

ggplot(bike_census %>%
         group_by(interval60, start_station_name) %>%
         tally())+
  geom_histogram(aes(n), binwidth = 5, fill="#6E0E0A")+
  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 continue to visualize the distribution of mean number of trips at each station during different times of the day. We recognize that during PM rush, the average number of trips has been the highest throughout, with a couple of stations’ average trip exceed 10, implying a relatively high demand for a single station. This is followed by ride demand in mid day, when some stations can have more than 5 rides. It is also surprising to notice that the demand is pretty high overnight, with a few stations having more than 5 rides.

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

After that, we visualized the total number of trips in August separated by day of the week. The two main patterns here are that firstly, trip numbers vary by time of the day. Lowest trip counts occur in early morning while highest trip count occur in late afternoon. Secondly, Tuesday, Wednesday, and Thursday tend to observe higher number of trips.

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

Same type of visualize but this time separated between weekend and weekdays. It is very obvious that we see more trips during weekdays and less trips during weekends. Also, during weekends, number of trips start to increase much later than during weekdays, suggesting the missing of morning rush hour will significantly impact people’s use of bikeshare services.

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

Building onto that, we group this dataset by station and time of day to visualize the total number of trips at each station during a specific period of time on weekday or weekend. To simplify our visualization, we selected the top 50 occurrences in our dataset. Note that here, each bar represent an instance of station-time of day-weekday/weekend combination. We are able to see that PM rush hour indeed sees that greatest bikeshare demand for a couple of stations in particular. Weekend rides only appear three times in the top 50 list. Zooming into the detail reveals to us that Grove St PATH station observes a total of 1117 rides during PM rush on a weekday, 801 rides overnight on a weekday in August 2023. Hoboken Terminal - River St & Hudson Pl station observes a total of 864 rides during PM rush on weekday. South Waterfront Walkway - Sinatra Dr & 1 St station observes 319 rides overnight on a weekend and 731 rides during PM rush on a weekday in August 2023.

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

In addition to examining our data through temporal lens, we made some maps to show the actual location of stations with the greatest bikeshare demand by time of day and day of the week. This proportional map illustrates that stations receive the highest ride demand during PM rush and overnight in weekdays and the lowest demand during AM rush. Stations with the highest bikeshare demand are generally located along the Hudson River. This could be attributed to two reasons: there are simply more stations in this part of Jersey City or Citi Bike has strategically built bike stations along the Hudson River to make daily commute easier for those who live in Jersey City but need to work/study in Manhattan.

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 grouped all the starting stations together to check for spatial autocorrelation between stations. We believe that stations with greater demand are more likely to be located to other stations with greater demand. In this case, we can use the demand of nearby stations at a particular time to estimate the demand of our target station. We selected 80 stations with the highest number of rides in August 2023 and computed the Moran’s I with 999 random Monte Carlo simulation. The results show that our observed Moran’s I is highly significant, implying that there’s spatial autocorrelation between bikeshares at nearby stations.

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

This map below shows how stations with higher bikeshare demand are clustered. In particular, there are two clusters of stations, and within each cluster, there are a few stations with particularly large number of rides. These stations are generally surrounded by stations with slightly less rides.

  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 animated map below combines the spatial and temporal characteristics of bike rides at 15min interval into a choropleth map.

week33 <-
  filter(bike_census , week == 33 & dotw == "Mon")

week33.panel <-
  expand.grid(
    interval15 = unique(week33$interval15),
    Origin.Tract = unique(bike_census$Origin.Tract))

bike.animation.data <-
  mutate(week33, Trip_Counter = 1) %>%
    right_join(week33.panel) %>% 
    group_by(interval15, Origin.Tract) %>%
    summarize(Trip_Count = sum(Trip_Counter, na.rm=T)) %>% 
    ungroup() %>% 
    left_join(jcTracts, by=c("Origin.Tract" = "GEOID")) %>%
    st_sf() %>%
    mutate(Trips = case_when(Trip_Count == 0 ~ "0 trips",
                             Trip_Count > 0 & Trip_Count <= 3 ~ "1-3 trips",
                             Trip_Count > 3 & Trip_Count <= 6 ~ "4-6 trips",
                             Trip_Count > 6 & Trip_Count <= 10 ~ "7-10 trips",
                             Trip_Count > 10 ~ "11+ trips")) %>%
    mutate(Trips  = fct_relevel(Trips, "0 trips","1-3 trips","4-6 trips",
                                       "7-10 trips","10+ trips"))

bikeshare_animation <-
  ggplot() +
    geom_sf(data = bike.animation.data, aes(fill = Trips)) +
    scale_fill_manual(values = palette5) +
    labs(title = "Bikeshare For One Day in Aug 2023",
         subtitle = "15 minute intervals: {current_frame}") +
    transition_manual(interval15)  + 
  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)
        )
  


anim_save("animation.gif", animation = bikeshare_animation, end_pause = 4, height = 1000, width = 700, fps = 6, renderer = gifski_renderer())

Animated Map for Bikeshare Demand in Week 33

Weather Analysis

On top of space and time, weather can be another significant factor of in predicting bikeshare demand. Generally, we believe bikeshare demand will decrease if it’s on a rainy, windy, or a very hot day in our case. With that in mind, we downloaded the weather data using the riem_measures function for Newark airport between Aug 1, 2023 and Sept 1, 2023. Note that the Newark Airport station sufficiently provides temporal weather for all of Jersey City. The weather.Panel is generated to summarize temperature, precipitation, and wind speed for every 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, label=TRUE)) %>%
    group_by(interval60) %>%
    summarize(Temperature = max(tmpf),
              Precipitation = sum(p01i),
              Wind_Speed = max(sknt)) %>%
    mutate(Temperature = ifelse(Temperature == 0, 42, Temperature))

For the temperature, precipitation, and wind data we collected during this month. We may see that there are a couple of rainy days in August. Wind speed is pretty consistent throughout with a couple of really windy days. Temperature fluctuates on a daily basis with higher temperature during the day and cooler temperature at night.

grid.arrange(
  ggplot(weather.Panel, aes(interval60,Precipitation)) + geom_line(color="#6E0E0A") + 
    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="#D74E09") + 
    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

In the following steps, we created a study panel where each instance in the panel is a unique combination of space and time. In other words, each row will represent the ride at a particular station during a particular hour.

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 need to add some more information to this panel. This includes counting the number of rides at this station at this particular hour, adding weather information, bringing in census data, and calculating time and day of week using the lubridate package.

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=T)) %>%
  left_join(weather.Panel) %>%
  ungroup() %>%
  filter(is.na(start_station_id) == FALSE) %>%
  mutate(week = week(interval60),
         dotw = wday(interval60, label = TRUE)) %>%
  filter(is.na(Origin.Tract) == FALSE) %>% 
  left_join(., njCensus %>%
              as.data.frame() %>%
              select(-geometry), by = c("Origin.Tract" = "GEOID"))

Weather Correlation

Now, we could perform more exploratory analysis on our panel data to prepare for our regression model. First, we would like to see how temperature will correlate with the number of bike rides in August. The figure below shows a significant, positive, strong, and linear relationship between temperature and mean trip count for all weeks in August. This means that as the temperature increase, bikeshare demand will increase as well.

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(color = "#6E0E0A") + geom_smooth(method = "lm", se= FALSE, color="#124E78") +
    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 would like to see how wind speed will correlate with the number of bike rides in August. We see that a linear and positive relationship between these two variables, but the strength of such relationship varies between different weeks. This generally means that we see more bikeshare demand during windy days.

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(color = "#6E0E0A") + geom_smooth(method = "lm", se= FALSE, color="#124E78") +
    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

Should the number of trips exhibit serial (temporal) correlation, then the time lag features will lead to better predictions. The logic behind is that the number of trips at a particular hour will very much correlate with the number of trips immediately before or after that hour. If we know the number of trips at a particular hour, then it would be easier for us to estimate the number of rides around that time. To achieve that, we calculated 6 different forms of lag hours.

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

The relationship between these lag hours and number of trips are visualized. We see a strong, linear, and negative relationship between lag12hours and number of trips 12 hours before. This means for example, the number of rides we see at 5pm on a particular day is negatively correlated with the number of rides at 5am that day. In contrast, we see a strong, linear, and positive relationship between laghour and lag1day. This means that the number of rides at a particular hour is highly correlated with the number of rides in the immediate next hour and at the same time the following day. Other than that, the strength of the correlation decreases for lag2hour and lag3hour, meaning that the number of rides at 4pm on a particular day, is more correlated with the number of rides at 5pm, rather than at 6 or 7 pm.

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 = 1, color = "#6E0E0A") + geom_smooth(method = "lm", se= FALSE, color="#124E78") +
  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’ve seen before that spatial autocorrelation exists here in that the number of trips at a particular station during a particular time is correlated with the number of trips at nearby stations. This means that if we know the number of trips at a station, we may be able to estimate the number of trips at the station next to it. Do achieve that, we created a new dataframe called lag_result and wrote a nested loop to loop through each hour in August 2023. For each hour, we then calculate the nearest 5 neighbor of each station and the average of rides for that 5 stations. The results were joined back to our panel dataset.

lag_result <- data.frame()

for (d in 213:243) {
  for (h in 0:23) {

spacelag <- bike.panel %>% 
  mutate(hour = hour(interval60)) %>% 
  filter(day == d & hour == h) %>% 
  st_as_sf(., coords = c("start_lng", "start_lat"), crs = 4326) 


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

spacelag <- spacelag %>% mutate(lagTrip = lag.listw(spatialWeights.test_lag, Trip_Count))

lag_result <- rbind(lag_result, spacelag)
  }
  
}

bike.panel <- lag_result %>% 
  mutate(start_lng = unlist(map(geometry, 1)),
         start_lat = unlist(map(geometry, 2))) %>% 
 st_drop_geometry() 

The figure below shows that for all weeks in August, there’s a strong, linear, and positive relationship between the number of trips and the number of trips in nearby stations. We will therefore include this variable in our following regression analysis.

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 split our 5 week data into training set, which includes 3 weeks and testing set, which include 2 weeks. After that, four different linear regression are estimaed on training data, each with different fixed effects.

  • reg1 focuses on just time, including hour fixed effects, day of the week, Wind Speed and Temperature.
  • reg2 focuses on just space, including station and weather conditions.
  • reg3 focuses on the combined effect of space and time
  • reg4 focuses adds the time and space lag features
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

The purrr family of functions will be used to loop through a set of ‘nested’ data frames to efficiently compare across different model specifications. Before testing our data, we nested our data by week and created a model function. The first set of code separate our testing data into two tibbles. The small function is created that takes a tibble, dat and a regression model, fit as its inputs, and outputs predictions as pred. This function is used simply for prediction.

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

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

The nested format allows one to loop through each model for each week and mutate summary statistics. In the code block below, week_predictions are calculated for each week in the testing set. The map function applies the model_pred function to each nested tibble. Take the first line of the below mutate, for example. A new column, Time_FE, includes predictions for reg1 - the time fixed effects model. The predictions are created by mapping the function, model_pred, to each row of data, parameterizing fit as the reg1 model.

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

Comparing the mean absolute errors for our four models, we find the error to be the higest for model 1, which only takes time effect into account. Model 2 and 3 account for space and space + time respectively, which lead to an increase in accuracy level. Adding space lag and time lag, however, will greatly improve the accuracy of our mode. We also ran the variance of inflation function in the background and checked to make sure that there’s no colinerarity in model 4.

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 further demonstrates the accuracy of each model by comparing predicted to observed number of trips. Model 4 clearly stands out among the 4 models to have higher predicting power of bikeshare demand.

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

We also need to check if our predictions generalize across space and time. To do that, we mapped our mean absolute error of model 4 across space. We can see that this model does not do a better job in predicting number of rides at stations along the Hudson River, which are typically stations with higher bikeshare demands during both weekend and weekdays. Other than that, this model does a pretty good job in predicting bikeshare demands at stations with lower number of trips in August.

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

Comparing across space and time, the figure below shows that model 4 has been pretty consistent in predicting number of trips across time. Stations that are predicted to have higher demand in the weekend will continue to be predicted to have higher demand during the weekdays. Errors carry throughout time. This implies that stations that historically have higher demand have higher variability in the number of rides and are more difficult to predict with the current predictors we have. In fact, our current predictors only explains 50.12% of the variability. What’s problematic about this is that if we continue to have larger errors when predicting stations with higher bikeshare demand, we will largely fail to meet the demand of bike users at these stations.

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

Finally, we performed 100 cross-validation tests for all five weeks of data using model 4. The MAE value is 0.44, indicating that despite being our best solution so far, the model’s prediction error can still be relatively large. It will also be good practice to cross validate across socio-economic indicators to see if bike stations’ demand within certain neighborhoods are disproportionately under/over predicted, but our model current does not take into account any socioeconomic variables as predictor variable and the scenario in Jersey City is that more stations are built

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.4484213 0.0311512

Conclusion

In conclusion, this report addresses the critical need for effective bikeshare management strategies in Jersey City by employing machine learning techniques. Our analysis shows that spatial, temporal, and weather-related factors all collectively contribute to the variations in bikeshare demands. We found that it is more accurate to predict bikeshare demand at a particular hour based on the number of trips at the hour immediately before and after, during the same hour a day before, as well as the number of trips at nearby stations. We also found that bikeshare demand are the highest during windy and hot days in Jersey City in August. The PM rush and overnight during weekdays see the highest demand while the PM rush during weekend sees the highest demand. On top of that, residents of Jersey City uses Citi Bike heavily for work commute to New York City, which has made stations along the Hudson River, particular those closer to ferry stations, the most popular.

We did some further investigations into stations with greatest bikeshare demand. Grove St PATH station, which observes a total of 1117 rides during PM rush on weekday and 801 rides overnight in August, is located in the busy downtown Jersey City.Hoboken Terminal - River St & Hudson Pl, which observes a total of 864 rides during PM rush on weekday, is located right next to Hoboken/NJ Transit Terminal and along the Hudson River. South Waterfront Walkway - Sinatra Dr & 1 St station, which observes 319 rides overnight on a weekend and 731 rides during PM rush on a weekday is within walking distance to the NJ Transit Terminal and at the entrance to Pier A Park, a waterfront recreation area in Jersey City. Therefore, we may conclude that stations receive higher demand because 1) there’s more workers/visitors in downtown area who need to bike to get around. 2) people need to commute to New York for work and need to bike to ferry/transit stations, 3) waterfront recreation areas is great for biking activities over the weekend. Local government should indeed pay more attention to the “supply” and “demand of bikes at those kind of stations and rebalance bikes if needed be.

Our model is able to capture more than half of the bikeshare demand in Jersey City, though still, it requires improvement to capture more variability. Otherwise, it will fail to meet the needs of Jersey City residents and increase the difficulties of rebalancing bikes. Nevertheless, the transparency and simplicity of this open-source approach provide a valuable resource for bikeshare programs to enhance user experience and maintain a harmonious bike distribution across stations. As urban mobility continues to evolve, the application of such predictive models becomes increasingly essential for sustainable and efficient transportation solutions.

LS0tCnRpdGxlOiAiUHJlZGljdGluZyBTdW1tZXIgQmlrZXNoYXJlIERlbWFuZCBpbiBKZXJzZXkgQ2l0eSIKYXV0aG9yOiAiRW1pbHkgWmhvdSIKZGF0ZTogImByIFN5cy5EYXRlKClgIgpvdXRwdXQ6CiAgaHRtbF9kb2N1bWVudDoKICAgIHRoZW1lOiBzaW1wbGV4CiAgICB0b2M6IHllcwogICAgdG9jX2Zsb2F0OiB5ZXMKICAgIGNvZGVfZm9sZGluZzogaGlkZQogICAgY29kZV9kb3dubG9hZDogeWVzCmVkaXRvcl9vcHRpb25zOgogIG1hcmtkb3duOgogICAgd3JhcDogc2VudGVuY2UKLS0tCgpWZXJzaW9uIDEuMCBcfCBGaXJzdCBDcmVhdGVkIE5vdiA0LCAyMDIzIFx8IFVwZGF0ZWQgTm92IDE1LCAyMDIzCgpLZXl3b3JkczogVHJhbnNwb3J0YXRpb24gUGxhbm5pbmcsIFJpZGVzaGFyZSBGb3JlY2FzdCwgU3BhdGlhbCBhbmQgU2VyaWFsIEF1dG9jb3JyZWxhdGlvbiwgTmVzdGVkIFRpYmJsZSwgT0xTIFJlZ3Jlc3Npb24sIHRpbWUgbGFnLCBnZ2FuaW1hdGUsIHB1cnJyCgoKYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9CmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSkKYGBgCgoKIyBJbnRyb2R1Y3Rpb24KCkJpa2VzaGFyZSBwcm9ncmFtcyBoYXZlIGJlY29tZSBhbiBpbnRlZ3JhbCBwYXJ0IG9mIHVyYmFuIHRyYW5zcG9ydGF0aW9uIHN5c3RlbSwgcHJvdmlkaW5nIGEgc3VzdGFpbmFibGUgYW5kIGNvbnZlbmllbnQgYWx0ZXJuYXRpdmUgdG8gdHJhZGl0aW9uYWwgY29tbXV0aW5nIG1ldGhvZHMuIENpdGkgQmlrZSBpcyBvbmUgb2YgdGhlIGVhcmxpZXN0IGJpY3ljbGUgc2hhcmluZyBzeXN0ZW0gY3JlYXRlZCBieSBMeWZ0IGluIDIwMDggYXQgTllDLiBCdXQgaW4gcmVjZW50IHllYXJzLCB0aGlzIHByb2dyYW0gYWxzbyBleHRlbmRzIHRvIEplcnNleSBDaXR5IGFjcm9zcyB0aGUgSHVkc29uIFJpdmVyIGluIG9yZGVyIHRvIGNhdGVyIHRvIHRoZSBkeW5hbWljIG1vYmlsaXR5IG5lZWRzIG9mIHJlc2lkZW50cyB3aG8gaGFkIHRvIGNvbW11dGUgdG8gTWFuaGF0dGFuIGZvciB3b3JrLiBBcyB0aGUgcG9wdWxhcml0eSBvZiBDaXRpIEJpa2UgY29udGludWVzIHRvIHNvYXIsIHRoZXJlIGFyaXNlcyBhIHByZXNzaW5nIG5lZWQgZm9yIGVmZmVjdGl2ZSBtYW5hZ2VtZW50IHN0cmF0ZWdpZXMgdG8gZW5zdXJlIHRoZSBhdmFpbGFiaWxpdHkgb2YgYmlrZXMgYXQgZWFjaCBzdGF0aW9uIGluIEplcnNleSBDaXR5LiAKCkJpa2VzaGFyaW5nIChhbmQgcmlkZXNoYXJpbmcpIGluaGVyZW50bHkgaW52b2x2ZXMgdGhlIGNoYWxsZW5nZSBvZiBtYWludGFpbmluZyBhIGRlbGljYXRlIGVxdWlsaWJyaXVtIGluIGJpa2UgZGlzdHJpYnV0aW9uLCBpZS4gInJlLWJhbGFuY2luZyIuIFRoaXMgaXNzdWUgYXJpc2VzIGZyb20gdGhlIHNwYXRpYWwgYW5kIHRlbXBvcmFsIHZhcmlhdGlvbnMgaW4gdXNlciBkZW1hbmQuIElmIGNlcnRhaW4gc3RhdGlvbnMgZXhwZXJpZW5jZSBhIHN1cmdlIGluIGRlbWFuZCBkdXJpbmcgbW9ybmluZyBydXNoIGhvdXJzIHdoaWxlIG90aGVycyB3aXRuZXNzIGEgZGVjbGluZSwgdGhpcyBjb3VsZCByZXN1bHQgaW4gc3RhdGlvbiBjbHVzdGVycyB3aXRoIGRlcGxldGVkIG9yIHN1cnBsdXMgYmlrZSBzdG9ja3MsIHRoZXJlYnkgY29tcHJvbWlzaW5nIHVzZXIgZXhwZXJpZW5jZS4gQ29tcGFuaWVzIGxpa2UgVWJlciAmIEx5ZnQgZ2VuZXJhdGUgYW5kIGFuYWx5emUgdHJlbWVuZG91cyBhbW91bnRzIG9mIGRhdGEgdG8gaW5jZW50aXZpemUgYmlrZSAocmlkZSkgc2hhcmUgdXNlLCBzb2x2ZSByb3V0aW5nIHByb2JsZW1zLCBjYWxjdWxhdGUgcHJpY2luZywgYW5kIG9idmlvdXNseSBmb3JlY2FzdCBkZW1hbmRzLCBidXQgdGhleSBhcmUgbm90IHZlcnkgdHJhbnNwYXJlbnQgYWJvdXQgdGhlIGFsZ29yaXRobSB0aGV5IHVzZS4gVGhlcmVmb3JlLCB0aGUgZ29hbCBvZiB0aGlzIHJlcG9ydCBpcyBrZWVwIGEgc2ltcGxlLCBidXQgb3BlbiBzb3VyY2UgdmVyc2lvbiBvZiBiaWtlc2hhcmUgZGVtYW5kIHByZWRpY3Rpb24sIGJ5IGhhcm5lc3Npbmcgb2YgcG93ZXIgb2YgbWFjaGluZSBsZWFybmluZywgcGFydGljdWxhcmx5IHRoZSBPcmRpbmFyeSBMZWFzdCBTcXVhcmUgKE9MUykgcmVncmVzc2lvbiB0byByYXBpZGx5IHByZWRpY3QgdGhlIGRlbWFuZCBmb3IgYmlrZXMgYWNyb3NzIHZhcmlvdXMgc3RhdGlvbnMgaW4gSmVyc2V5IENpdHkuIFdlIHdpbGwgcGVyZm9ybSBhbmFseXNpcyBvbiB0aGUgaGlzdG9yaWNhbCBkYXRhIG9uIGJpa2UgdXNhZ2UgYXQgZGlmZmVyZW50IHN0YXRpb25zIGFuZCBkaWZmZXJlbnQgdGltZSBhcyB3ZWxsIGFzIHVuZGVyIGRpZmZlcmVudCB3ZWF0aGVyIGNvbmRpdGlvbnMuIFRoZSBmb3JlY2FzdCBhbGxvd3MgdGhlIENpdGkgQmlrZSBwcm9ncmFtIHRvIHByb2FjdGl2ZWx5IGFkZHJlc3MgaW1iYWxhbmNlcywgc3RyYXRlZ2ljYWxseSByZS1wb3NpdGlvbmluZyBiaWtlcyBpbiBhbnRpY2lwYXRpb24gb2YgcGVhayB1c2FnZSB0aW1lIG9yIHNwZWNpYWwgZXZlbnRzLiAKClRoZSBzdWJzZXF1ZW50IHNlY3Rpb25zIG9uIHRoaXMgcmVwb3J0IGlzIG9yZ2FuaXplZCBhcyBmb2xsb3dzLiBGaXJzdGx5LCB3ZSBsb2FkIGluIG91ciBkYXRhc2V0cyBmb3IgYSBxdWljayBleGFtaW5hdGlvbnMgYW5kIHBlcmZvcm0gc29tZSBuZWNlc3NhcnkgZmVhdHVyZSBlbmdpbmVlcmluZy4gU2Vjb25kbHksIHdlIGNvbmR1Y3QgZXhwbG9yYXRvcnkgYW5hbHlzaXMgb24gdGhlIHRlbXBvcmFsIGFuZCBzcGF0aWFsIGFzcGVjdHMgb2YgYmlrZXNoYXJlIGRhdGEuIFdlIHdpbGwgYWxzbyBsb29rIGF0IHNvbWUgd2VhdGhlciBkYXRhIGluIHRoaXMgcHJvY2Vzcy4gVGhpcmRseSwgd2UgY3JlYXRlIGEgc3BhY2UtdGltZSBwYW5lbCB3aGVyZSBlYWNoIHJvdyBpcyBhIHVuaXF1ZSBpbnN0YW5jZSBvZiB0aW1lLXNwYWNlIGNvbWJpbmF0aW9uIG9mIHJpZGVzIGFuZCB2aXN1YWxpemUgdGhlIGNvcnJlbGF0aW9uIGJldHdlZW4gc3BhY2UsIHRpbWUsIHdlYXRoZXIsIGFuZCBudW1iZXIgb2YgcmlkZXMuIEFuZCBmaW5hbGx5LCB3ZSBtYWtlIGNyZWF0ZWQgZm91ciBkaWZmZXJlbnQgbW9kZWxzLCB0ZXN0IHRoZWlyIHNlbnNpYmlsaXR5IHRvIGVycm9ycywgYW5kIGNvbmR1Y3QgY3Jvc3MgdmFsaWRhdGlvbi4gCgpUaGUgR2l0SHViIHJlcG9zaXRvcnkgZm9yIHRoaXMgcmVwb3J0IGlzIFtoZXJlXShodHRwczovL2dpdGh1Yi5jb20vZW1pbHl6aG91MTEyL01VU0E1MDgwLU5KLUJpa2VTaGFyZSkuCgojIFNldCBVcAoKYGBge3IgbGlicmFyeSBhbmQgZGF0YSwgaW5jbHVkZT1GQUxTRX0KbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkoc2YpCmxpYnJhcnkobHVicmlkYXRlKQpsaWJyYXJ5KHRpZ3JpcykKbGlicmFyeShnZ2FuaW1hdGUpCmxpYnJhcnkocmllbSkKbGlicmFyeShncmlkRXh0cmEpCmxpYnJhcnkoa25pdHIpCmxpYnJhcnkoa2FibGVFeHRyYSkKbGlicmFyeSh0aWR5Y2Vuc3VzKQpsaWJyYXJ5KG1hcHZpZXcpCmxpYnJhcnkoc2ZkZXApCmxpYnJhcnkoc3BkZXApCmxpYnJhcnkoY2FyZXQpCmxpYnJhcnkgKHJlYWRyKQoKb3B0aW9ucyh0aWdyaXNfY2xhc3MgPSAic2YiKQpzb3VyY2UoImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS91cmJhblNwYXRpYWwvUHVibGljLVBvbGljeS1BbmFseXRpY3MtTGFuZGluZy9tYXN0ZXIvZnVuY3Rpb25zLnIiKQoKcGFsZXR0ZTUgPC0gYygiIzEyNEU3OCIsIiNGMEYwQzkiLCIjRjJCQjA1IiwiI0Q3NEUwOSIsIiM2RTBFMEEiKQpwYWxldHRlNCA8LSBjKCIjMTI0RTc4IiwiI0YwRjBDOSIsIiNGMkJCMDUiLCIjNkUwRTBBIikKcGFsZXR0ZTIgPC0gYygiIzEyNEU3OCIsIiM2RTBFMEEiKQoKdGlkeWNlbnN1czo6Y2Vuc3VzX2FwaV9rZXkoImU3OWYzNzA2YjZkNjEyNDk5NjhjNmNlODg3OTRmNmY1NTZlNWJmM2QiLCBvdmVyd3JpdGUgPSBUUlVFKQpgYGAKClRoZSBkYXRhIHdlIHVzZSBoZXJlIGlzIG9idGFpbmVkIGZyb20gW05ZQyBPcGVuRGF0YV0oaHR0cHM6Ly9zMy5hbWF6b25hd3MuY29tL3RyaXBkYXRhL2luZGV4Lmh0bWwpIHRoYXQgY29udGFpbnMgdGhlIGxhdGVzdCByZWNvcmQgb2YgQ2l0aSBCaWtlIHVzYWdlIG9uIGEgbW9udGhseSBiYXNlLiBOb3RlIHRoYXQgd2hpbGUgSmVyc2V5IENpdHkgaXMgbG9jYXRlZCBpbiBOZXcgSmVyc2V5LCBDaXRpIEJpa2UgaXMgbWFpbmx5IG9wZXJhdGVkIGluIE5ZQyBhbmQgdGhlcmVmb3JlIGl0cyBkYXRhc2V0IGlzIGFsc28gb3BlcmF0ZWQgYnkgTllDLiBXZSBzZWxlY3RlZCBhbGwgYmlrZXNoYXJlIGRhdGEgaW4gSmVyc2V5IENpdHkgZnJvbSBBdWcgMSAyMDIzIHRvIEF1ZyAzMSAyMDIzIGFzIG91ciB0ZW1wb3JhbCBmcmFtZS4gVGhlIGRhdGFzZXQgbWFpbmx5IGluY2x1ZGVzIGxvY2F0aW9ucyBhbmQgbmFtZXMgb2Ygc3RhdGlvbnMgb2Ygd2hpY2ggdGhlIHVzZXIgc3RhcnRlZCB0aGUgcmlkZSBhcyB3ZWxsIGFzIGVuZCB0aGUgcmlkZS4gSW4gYWRkaXRpb24sIHdlIGFsc28gZG93bmxvYWRlZCB0aGUgY2Vuc3VzIHRyYWN0IGdlb21ldHJpZXMgb2YgdGhlIGVudGlyZSBIdWRzb24gQ291bnR5IHZpYSB0aWR5Y2Vuc3VzLCB3aGljaCBpbmNsdWRlcyBKZXJzZXkgQ2l0eS4gCgpgYGB7ciBvYnRhaW4gY2Vuc3VzIGdlb20gYW5kIGJpa2UgZGF0YSwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRSwgcmVzdWx0cz0naGlkZScsIGNhY2hlPVRSVUV9CgpueWNiaWtlIDwtIHJlYWQuY3N2KHVybCgiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL2VtaWx5emhvdTExMi9NVVNBNTA4MC1OSi1CaWtlU2hhcmUvbWFpbi9KQy0yMDIzMDgtY2l0aWJpa2UtdHJpcGRhdGEuY3N2IikpCgpuakNlbnN1cyA8LSBnZXRfYWNzKGdlb2dyYXBoeSA9ICJ0cmFjdCIsIAogICAgICAgICAgdmFyaWFibGVzID0gYygiQjAxMDAzXzAwMSIsICJCMTkwMTNfMDAxIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICJCMDIwMDFfMDAyIiwgIkIwODAxM18wMDEiLAogICAgICAgICAgICAgICAgICAgICAgICAiQjA4MDEyXzAwMSIsICJCMDgzMDFfMDAxIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICJCMDgzMDFfMDEwIiwgIkIwMTAwMl8wMDEiKSwgCiAgICAgICAgICB5ZWFyID0gMjAyMSwgCiAgICAgICAgICBzdGF0ZSA9ICJOSiIsIAogICAgICAgICAgZ2VvbWV0cnkgPSBUUlVFLCAKICAgICAgICAgIGNvdW50eT1jKCJIdWRzb24iKSwKICAgICAgICAgIG91dHB1dCA9ICJ3aWRlIikgJT4lCiAgcmVuYW1lKFRvdGFsX1BvcCA9ICBCMDEwMDNfMDAxRSwKICAgICAgICAgTWVkX0luYyA9IEIxOTAxM18wMDFFLAogICAgICAgICBNZWRfQWdlID0gQjAxMDAyXzAwMUUsCiAgICAgICAgIFdoaXRlX1BvcCA9IEIwMjAwMV8wMDJFLAogICAgICAgICBUcmF2ZWxfVGltZSA9IEIwODAxM18wMDFFLAogICAgICAgICBOdW1fQ29tbXV0ZXJzID0gQjA4MDEyXzAwMUUsCiAgICAgICAgIE1lYW5zX29mX1RyYW5zcG9ydCA9IEIwODMwMV8wMDFFLAogICAgICAgICBUb3RhbF9QdWJsaWNfVHJhbnMgPSBCMDgzMDFfMDEwRSkgJT4lCiAgc2VsZWN0KFRvdGFsX1BvcCwgTWVkX0luYywgV2hpdGVfUG9wLCBUcmF2ZWxfVGltZSwKICAgICAgICAgTWVhbnNfb2ZfVHJhbnNwb3J0LCBUb3RhbF9QdWJsaWNfVHJhbnMsCiAgICAgICAgIE1lZF9BZ2UsCiAgICAgICAgIEdFT0lELCBnZW9tZXRyeSkgJT4lCiAgbXV0YXRlKFBlcmNlbnRfV2hpdGUgPSBXaGl0ZV9Qb3AgLyBUb3RhbF9Qb3AsCiAgICAgICAgIE1lYW5fQ29tbXV0ZV9UaW1lID0gVHJhdmVsX1RpbWUgLyBUb3RhbF9QdWJsaWNfVHJhbnMsCiAgICAgICAgIFBlcmNlbnRfVGFraW5nX1B1YmxpY19UcmFucyA9IFRvdGFsX1B1YmxpY19UcmFucyAvIE1lYW5zX29mX1RyYW5zcG9ydCkKCmpjVHJhY3RzIDwtIG5qQ2Vuc3VzICU+JQogIGFzLmRhdGEuZnJhbWUoKSAlPiUKICBkaXN0aW5jdChHRU9JRCwgLmtlZXBfYWxsID0gVFJVRSkgJT4lCiAgc2VsZWN0KEdFT0lELCBnZW9tZXRyeSkgJT4lIAogIHN0X3NmKCkKYGBgCgpTaW5jZSBvdXIgcmF3IGJpa2VzaGFyZSBkYXRhIGlzIGEgbm9uLXNwYXRpYWwgbGF5ZXIsIGRlc3BpdGUgY29udGFpbmluZyBzcGF0aWFsIGF0dHJpYnV0ZXMsIG91ciBuZXh0IHN0ZXAgaXMgdG8gbWFrZSBpdCBzcGF0aWFsIGJ5IGpvaW5pbmcgaXQgdG8gdGhlIGNlbnN1cyBnZW9tZXRyeS4gV2UgYXJlIG9ubHkgaW50ZXJlc3RlZCBpbiB0aGUgc3RhcnQgbG9jYXRpb24gb2YgZWFjaCByaWRlIGJlY2F1c2Ugd2Ugd2FudCB0byBwcmVkaWN0IHRoZSBjYXBhY2l0eSBvZiBlYWNoIHN0YXRpb24gd2hlbiB0aGUgdXNlciBuZWVkIGEgYmlrZSB0byBzdGFydCB0aGUgcmlkZS4gVGhpcyByZXF1aXJlcyB1cyB0byBmaWx0ZXIgb3V0IHJvd3Mgd2l0aCBtaXNzaW5nIGxhdGl0dWRlIGFuZCBsb25naXR1ZGUuIFdlIGFsc28gZm91bmQgdGhhdCBzb21lIHN0YXRpb25zIGhhdmUgZXJyb25lb3VzIGxvbmdpdHVkZSBhbmQgbGF0aXR1ZGUgY29kaW5nIHRoYXQgbGVhZCB0byB0aGVzZSBwb2ludHMgYmVpbmcgbG9jYXRlZCBpbiB0aGUgSHVkc29uIFJpdmVyLiBUaGVzZSBwb2ludHMgYWxzbyBuZWVkIHRvIGJlIHJlbW92ZWQuIAoKYGBge3IgYWRkIGNlbnN1cyB0cmFjdH0KCmJpa2VfY2Vuc3VzIDwtIHN0X2pvaW4obnljYmlrZSAlPiUgCiAgICAgICAgICAgICAgICAgICAgICAgICBmaWx0ZXIoaXMubmEoc3RhcnRfbGF0KSA9PSBGQUxTRSAmCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaXMubmEoc3RhcnRfbG5nKSA9PSBGQUxTRSAmCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaXMubmEoZW5kX2xhdCkgPT0gRkFMU0UgJgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlzLm5hKGVuZF9sbmcpID09IEZBTFNFKSAlPiUKICAgICAgICAgICAgICAgICAgICAgICAgIHN0X2FzX3NmKC4sIGNvb3JkcyA9IGMoInN0YXJ0X2xuZyIsICJzdGFydF9sYXQiKSwgY3JzID0gNDMyNiksCiAgICAgICAgICAgICAgICAgICAgICAgamNUcmFjdHMgJT4lICBzdF90cmFuc2Zvcm0oY3JzPTQzMjYpLAogICAgICAgICAgICAgICAgICAgICAgIGpvaW49c3RfaW50ZXJzZWN0cywgbGVmdCA9IFRSVUUpICU+JQogIHJlbmFtZShPcmlnaW4uVHJhY3QgPSBHRU9JRCkgJT4lCiAgbXV0YXRlKHN0YXJ0X2xuZyA9IHVubGlzdChtYXAoZ2VvbWV0cnksIDEpKSwKICAgICAgICAgc3RhcnRfbGF0ID0gdW5saXN0KG1hcChnZW9tZXRyeSwgMikpKSU+JQogIGZpbHRlcighKHJpZGVfaWQgJWluJSBjKCJCRUM5Q0M0RDEwMDdDNzUzIiwgIjg2MDg3RjQzMTc4NUVERTciLCAiQTFBQUI5MjYzMTQzOTg4MyIsICI3MTkzOERBMDg1MjQyRENDIiwgIjQyMDNDNzc2NjhDNDdDNzUiLCAiQzkyNDEwQ0EyQUVGMzk0NyIsICI2RTAyQ0JGMzZDOTBGMjQ0IiwgIjAyNTk4ODUyNTNGRDIzOEUiLCAiQjM5MTM2QjM3QUZCNUZFOSIsICIxRUM5Mzc2RDE1NTlDNTFDIiwgIkNEREYxNkQzQTM3QkUzNzAiKSkpICU+JSAKICBhcy5kYXRhLmZyYW1lKCkKICAKYGBgCgpUaGUgZm9sbG93aW5nIHZpc3VhbGl6YXRpb24gc2hvd3MgYWxsIHRoZSBzdGVwcyB3ZSBkaWQgYWJvdmUuIAoKYGBge3IgZGlzdHJpYnV0aW9uIG9mIHN0YXRpb25zfQoKZ2dwbG90KCkrCiAgZ2VvbV9zZihkYXRhID0gamNUcmFjdHMgJT4lCiAgICAgICAgICBzdF90cmFuc2Zvcm0oY3JzPTQzMjYpLCBmaWxsPSJ3aGl0ZSIpKwogIGdlb21fcG9pbnQoZGF0YSA9IGJpa2VfY2Vuc3VzLCBhZXMoeD1zdGFydF9sbmcsIHkgPSBzdGFydF9sYXQpLCAKICAgICAgICAgICAgY29sb3IgPSAiIzZFMEUwQSIsIGFscGhhID0gMSwgc2l6ZSA9IDAuMykgKyAKICB5bGltKG1pbihiaWtlX2NlbnN1cyRzdGFydF9sYXQpLCBtYXgoYmlrZV9jZW5zdXMkc3RhcnRfbGF0KSkrCiAgeGxpbShtaW4oYmlrZV9jZW5zdXMkc3RhcnRfbG5nKSwgbWF4KGJpa2VfY2Vuc3VzJHN0YXJ0X2xuZykpICsKICBsYWJzKHRpdGxlID0gIkxvY2F0aW9uIG9mIFJpZGVzIFN0YXJ0aW5nIFBvaW50cyBpbiBKZXJzZXkgQ2l0eSIpICsKICB0aGVtZShheGlzLnRleHQueD1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGlja3MgPWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aXRsZS55ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDksZmFjZSA9ICJpdGFsaWMiKSwKICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgZmFjZSA9ICJib2xkIiksCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X3JlY3QoY29sb3VyID0gImdyZXkiLCBmaWxsPU5BLCBsaW5ld2lkdGg9MC44KQogICAgICAgICkKCmBgYAoKV2Ugd2lsbCBwZXJmb3JtIHNvbWUgYmFzaWMgZmVhdHVyZSBlbmdpbmVlcmluZyB1c2luZyBSJ3MgcG93ZXJmdWwgYGx1YnJpZGF0ZWAgcGFja2FnZSB0byBleHRyYWN0IHNvbWUgdGVtcG9yYWwgaW5mb3JtYXRpb24gZm9yIGVhY2ggcmlkZS4gVGhpcyBpbmNsdWRlcyBjYWxjdWxhdGluZyB0aGUgNjBtaW4gYW5kIDE1bWluIGludGVydmFsIGZvciB0aGUgc3RhcnRpbmcgdGltZSBvZiBlYWNoIHJpZGUsIGFuZCBjYXRlZ29yaXppbmcgdGhlc2UgaW50ZXJ2YWxzIGludG8gYnkgdGhlIHRpbWUgb2YgZGF5IGludG8gIk92ZXJuaWdodCIsICJBTSBSdXNoIiwgIk1pZC1EYXkiLCBhbmQgIlBNIFJ1c2giIGFuZCBieSBkYXkgb2Ygd2VlayBpbnRvIHdlZWtkYXkgYW5kIHdlZWtlbmQuIAoKYGBge3IgdGVtcG9yYWwgZmVhdHVyZSBlbmdpbmVlcmluZ30KCmJpa2VfY2Vuc3VzIDwtIGJpa2VfY2Vuc3VzICU+JQogIG11dGF0ZShpbnRlcnZhbDYwID0gZmxvb3JfZGF0ZSh5bWRfaG1zKHN0YXJ0ZWRfYXQpLCB1bml0ID0gImhvdXIiKSwKICAgICAgICAgaW50ZXJ2YWwxNSA9IGZsb29yX2RhdGUoeW1kX2htcyhzdGFydGVkX2F0KSwgdW5pdCA9ICIxNSBtaW5zIiksCiAgICAgICAgIHdlZWsgPSB3ZWVrKGludGVydmFsNjApLAogICAgICAgICBkb3R3ID0gd2RheShpbnRlcnZhbDYwLCBsYWJlbD1UUlVFKSkgJT4lIAogIG11dGF0ZSh0aW1lX29mX2RheSA9IGNhc2Vfd2hlbihob3VyKGludGVydmFsNjApIDwgNyB8IGhvdXIoaW50ZXJ2YWw2MCkgPiAxOCB+ICJPdmVybmlnaHQiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBob3VyKGludGVydmFsNjApID49IDcgJiBob3VyKGludGVydmFsNjApIDwgMTAgfiAiQU0gUnVzaCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhvdXIoaW50ZXJ2YWw2MCkgPj0gMTAgJiBob3VyKGludGVydmFsNjApIDwgMTUgfiAiTWlkLURheSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhvdXIoaW50ZXJ2YWw2MCkgPj0gMTUgJiBob3VyKGludGVydmFsNjApIDw9IDE4IH4gIlBNIFJ1c2giKSkgJT4lIAogIG11dGF0ZShob3VyID0gaG91cihzdGFydGVkX2F0KSwKICAgICAgICAgICAgICAgIHdlZWtlbmQgPSBpZmVsc2UoZG90dyAlaW4lIGMoIlN1biIsICJTYXQiKSwgIldlZWtlbmQiLCAiV2Vla2RheSIpKQpgYGAKCkludGVyZXN0ZWQgaW4gc2VlaW5nIHRoZSBjb21tdXRpbmcgcGF0dGVybnMgdG8gZ2V0IGEgc2Vuc2Ugb2YgcmlkZXIncyBkYWlseSBiZWhhdmlvciwgd2UgY3JlYXRlZCBsaW5lIHN0cmluZ3MgdGhhdCBjb25uZWN0IGJldHdlZW4gdGhlIHN0YXJ0IGFuZCBlbmQgbG9jYXRpb25zIG9mIGVhY2ggcmlkZS4gRm9yIHRoZSBzaW1wbGljaXR5IG9mIHZpc3VhbGl6YXRpb24sIHdlIG9ubHkgdmlzdWFsaXplIDIwMCByaWRlcy4gV2UgbWF5IHNlZSB0aGF0IG1vc3Qgb2YgdGhlIHJpZGVzIG9jY3VyIHdpdGhpbiBKZXJzZXkgQ2l0eSwgYnV0IHRoZXJlIGFyZSBhbHNvIHBlb3BsZSB3aG8gY29tbXV0ZSB1c2luZyBDaXRpIEJpa2UgZnJvbSBKZXJzZXkgQ2l0eSB0byBNYW5oYXR0YW4uICAKCmBgYHtyIG9yaWdpbi1kZXNpbnRhdGlvbiBtYXRyaXgsIHdhcm5pbmc9RkFMU0V9CgpsaW5lcyA8LSBzdF9zZmMoCiAgbGFwcGx5KDE6bnJvdyhiaWtlX2NlbnN1cyksIGZ1bmN0aW9uKGkpIHsKICAgIHN0X2xpbmVzdHJpbmcobWF0cml4KGMoYmlrZV9jZW5zdXNbaSwgInN0YXJ0X2xuZyJdLCBiaWtlX2NlbnN1c1tpLCAic3RhcnRfbGF0Il0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBiaWtlX2NlbnN1c1tpLCAiZW5kX2xuZyJdLCBiaWtlX2NlbnN1c1tpLCAiZW5kX2xhdCJdKSwgbmNvbCA9IDIsIGJ5cm93ID0gVFJVRSkpCiAgfSkpCgpsaW5lc19zZiA8LSBzdF9zZihnZW9tZXRyeSA9IGxpbmVzKQpsaW5lc19zZiA8LSBzdF9zZXRfY3JzKGxpbmVzX3NmLCA0MzI2KQoKUm91dGUgPC0gbGluZXNfc2YgJT4lIGhlYWQoMjAwKQptYXB2aWV3KFJvdXRlLCB6Y29sID0gTlVMTCwgY29sb3IgPSAiIzZFMEUwQSIsIGxpbmV3aWR0aCA9IDAuMDAxLCBhbHBoYSA9IDAuMykgIyBvbmx5IHNob3cgMjAwCmBgYAoKCiMgRXhwbG9yYXRvcnkgQW5hbHlzaXMKCkluIHRoaXMgc2VjdGlvbiwgd2UgZGVsdmUgaW50byBzb21lIGZ1cnRoZXIgZXhwbG9yYXRvcnkgYW5hbHlzaXMgb2Ygb3VyIGRhdGEgc2V0LiAKCiMjIFRlbXBvcmFsIEFuYWx5c2lzCgpTdGFydGluZyB3aXRoIHRlbXBvcmFsIGFuYWx5c2lzLCB3ZSBmaXJzdCB2aXN1YWxpemVkIHRoZSBiaWtlc2hhcmUgdHJpcHMgcGVyIGhvdXIgaW4gSmVyc2V5IENpdHkgaW4gQXVndXN0IDIwMjMuIEl0IGlzIGNsZWFyIHRoYXQgdGhlcmUncyBzb21lIGZvcm0gb2YgY2lyY3VsYXJpdHkgaW4gdGhhdCB0aGVyZSBhcmUgYm90aCBwZWFrIGhvdXJzIGFuZCBub24tcGVhayBob3VycyBkdXJpbmcgYSBzaW5nbGUgZGF5LiBBbHNvLCB3ZSBtYXkgc2VlIHRoYXQgZml2ZSBjb25zZWN1dGl2ZSBoaWdoZXIgcGVha3MgYXJlIHVzdWFsbHkgZm9sbG93ZWQgYnkgdHdvIGxlc3MgaGlnaGVyIHBlYWtzLiBUaGVzZSBjb3JyZXNwb25kIHRvIGhpZ2ggYmlrZXNoYXJlIG5lZWQgZHVyaW5nIHJ1c2ggaG91cnMgYW5kIGxvdyBiaWtlc2hhcmUgbmVlZHMgb3Zlcm5pZ2h0LCBpbiBlYXJseSBtb3JuaW5ncywgYW5kIG92ZXIgdGhlIHdlZWtlbmQKCmBgYHtyIGJpa2VzaGFyZSB2aXoxfQpnZ3Bsb3QoYmlrZV9jZW5zdXMgJT4lCiAgICAgICAgIGdyb3VwX2J5KGludGVydmFsNjApICU+JQogICAgICAgICB0YWxseSgpKSsKICBnZW9tX2xpbmUoYWVzKHggPSBpbnRlcnZhbDYwLCB5ID0gbiksIGNvbG9yPSAiIzZFMEUwQSIpKwogIGxhYnModGl0bGU9IkJpa2UgU2hhcmUgVHJpcHMgUGVyIEhvdXIgaW4gSmVyc2V5IENpdHksIEF1ZyAyMDIzIiwKICAgICAgIHg9IkRhdGUiLCAKICAgICAgIHk9Ik51bWJlciBvZiB0cmlwcyIpICsgCnRoZW1lKHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDksZmFjZSA9ICJpdGFsaWMiKSwKICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgZmFjZSA9ICJib2xkIiksIAogICAgICAgIGF4aXMudGV4dC54PWVsZW1lbnRfdGV4dChzaXplPTgpLAogICAgICAgIGF4aXMudGV4dC55PWVsZW1lbnRfdGV4dChzaXplPTgpLCAKICAgICAgICBheGlzLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTEwKSwgCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X3JlY3QoY29sb3VyID0gImdyZXkiLCBmaWxsPU5BLCBsaW5ld2lkdGggPTAuOCkpCiAgCmBgYAoKVGhlbiwgd2UgdmlzdWFsaXplZCB0aGUgZGlzdHJpYnV0aW9uIG9mIHRyaXBzIGJ5IHN0YXRpb25zIGF0IGVhY2ggaG91ciBpbiBBdWd1c3QuIFdlIG1heSBzZWUgdGhhdCB0aGUgZGlzdHJpYnV0aW9uIG9mIHJpZGVzIGlzIGhpZ2hseSByaWdodCBza2V3ZWQsIHdpdGggbW9zdCBzdGF0aW9ucyBoYXZpbmcgemVybyBvciBhIGZldyBudW1iZXIgb2YgdHJpcHMgZWFjaCBob3VyIGFuZCBhIGNvdXBsZSBvZiBzdGF0aW9ucyBoYXZpbmcgbXVjaCBtb3JlIHJpZGVzLCB1cCB0byA0MCBvciBldmVuIDUwIHJpZGVzIGF0IGEgcGFydGljdWxhciBob3VyIG9uIGEgcGFydGljdWxhciBkYXkgaW4gQXVndXN0IDIwMjMuIAoKYGBge3IgYmlrZXNoYXJlIHZpejMsIHdhcm5pbmc9RkFMU0V9CgpnZ3Bsb3QoYmlrZV9jZW5zdXMgJT4lCiAgICAgICAgIGdyb3VwX2J5KGludGVydmFsNjAsIHN0YXJ0X3N0YXRpb25fbmFtZSkgJT4lCiAgICAgICAgIHRhbGx5KCkpKwogIGdlb21faGlzdG9ncmFtKGFlcyhuKSwgYmlud2lkdGggPSA1LCBmaWxsPSIjNkUwRTBBIikrCiAgbGFicyh0aXRsZT0iQmlrZSBTaGFyZSBUcmlwcyBQZXIgSG91ciBieSBTdGF0aW9uIGluIEplcnNleSBDaXR5LCBBdWcgMjAyMyIsCiAgICAgICB4PSJUcmlwIENvdW50cyIsIAogICAgICAgeT0iTnVtYmVyIG9mIFN0YXRpb25zIikgKyAKICAgIHRoZW1lKHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDksZmFjZSA9ICJpdGFsaWMiKSwKICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgZmFjZSA9ICJib2xkIiksIAogICAgICAgIGF4aXMudGV4dC54PWVsZW1lbnRfdGV4dChzaXplPTgpLAogICAgICAgIGF4aXMudGV4dC55PWVsZW1lbnRfdGV4dChzaXplPTgpLCAKICAgICAgICBheGlzLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTEwKSwgCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X3JlY3QoY29sb3VyID0gImdyZXkiLCBmaWxsPU5BLCBsaW5ld2lkdGggPTAuOCkpCgpgYGAKCldlIGNvbnRpbnVlIHRvIHZpc3VhbGl6ZSB0aGUgZGlzdHJpYnV0aW9uIG9mIG1lYW4gbnVtYmVyIG9mIHRyaXBzIGF0IGVhY2ggc3RhdGlvbiBkdXJpbmcgZGlmZmVyZW50IHRpbWVzIG9mIHRoZSBkYXkuIFdlIHJlY29nbml6ZSB0aGF0IGR1cmluZyBQTSBydXNoLCB0aGUgYXZlcmFnZSBudW1iZXIgb2YgdHJpcHMgaGFzIGJlZW4gdGhlIGhpZ2hlc3QgdGhyb3VnaG91dCwgd2l0aCBhIGNvdXBsZSBvZiBzdGF0aW9ucycgYXZlcmFnZSB0cmlwIGV4Y2VlZCAxMCwgaW1wbHlpbmcgYSByZWxhdGl2ZWx5IGhpZ2ggZGVtYW5kIGZvciBhIHNpbmdsZSBzdGF0aW9uLiBUaGlzIGlzIGZvbGxvd2VkIGJ5IHJpZGUgZGVtYW5kIGluIG1pZCBkYXksIHdoZW4gc29tZSBzdGF0aW9ucyBjYW4gaGF2ZSBtb3JlIHRoYW4gNSByaWRlcy4gSXQgaXMgYWxzbyBzdXJwcmlzaW5nIHRvIG5vdGljZSB0aGF0IHRoZSBkZW1hbmQgaXMgcHJldHR5IGhpZ2ggb3Zlcm5pZ2h0LCB3aXRoIGEgZmV3IHN0YXRpb25zIGhhdmluZyBtb3JlIHRoYW4gNSByaWRlcy4gCgpgYGB7ciBiaWtlc2hhcmUgdml6Miwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0KCmJpa2VfY2Vuc3VzICU+JQogICAgICAgIGdyb3VwX2J5KGludGVydmFsNjAsIHN0YXJ0X3N0YXRpb25fbmFtZSwgdGltZV9vZl9kYXkpICU+JQogICAgICAgICB0YWxseSgpJT4lCiAgZ3JvdXBfYnkoc3RhcnRfc3RhdGlvbl9uYW1lLCB0aW1lX29mX2RheSklPiUKICBzdW1tYXJpemUobWVhbl90cmlwcyA9IG1lYW4obikpJT4lCiAgZ2dwbG90KCkrCiAgZ2VvbV9oaXN0b2dyYW0oYWVzKG1lYW5fdHJpcHMsIGZpbGw9dGltZV9vZl9kYXkpLCBiaW53aWR0aCA9IDEpKwogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IHBhbGV0dGU0LCBuYW1lPSJUaW1lIG9mIERheSIpICsgCiAgbGFicyh0aXRsZT0iTWVhbiBOdW1iZXIgb2YgSG91cmx5IFRyaXBzIFBlciBTdGF0aW9uIiwKICAgICAgIHg9Ik51bWJlciBvZiB0cmlwcyIsIAogICAgICAgeT0iRnJlcXVlbmN5IikrCiAgZmFjZXRfd3JhcCh+dGltZV9vZl9kYXkpICsKdGhlbWUocGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gOSxmYWNlID0gIml0YWxpYyIpLAogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSwgCiAgICAgICAgYXhpcy50ZXh0Lng9ZWxlbWVudF90ZXh0KHNpemU9OCksCiAgICAgICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF90ZXh0KHNpemU9OCksIAogICAgICAgIGF4aXMudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9MTApLCAKICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfcmVjdChjb2xvdXIgPSAiZ3JleSIsIGZpbGw9TkEsIHNpemU9MC44KSkKCmBgYAoKQWZ0ZXIgdGhhdCwgd2UgdmlzdWFsaXplZCB0aGUgdG90YWwgbnVtYmVyIG9mIHRyaXBzIGluIEF1Z3VzdCBzZXBhcmF0ZWQgYnkgZGF5IG9mIHRoZSB3ZWVrLiBUaGUgdHdvIG1haW4gcGF0dGVybnMgaGVyZSBhcmUgdGhhdCBmaXJzdGx5LCB0cmlwIG51bWJlcnMgdmFyeSBieSB0aW1lIG9mIHRoZSBkYXkuIExvd2VzdCB0cmlwIGNvdW50cyBvY2N1ciBpbiBlYXJseSBtb3JuaW5nIHdoaWxlIGhpZ2hlc3QgdHJpcCBjb3VudCBvY2N1ciBpbiBsYXRlIGFmdGVybm9vbi4gU2Vjb25kbHksIFR1ZXNkYXksIFdlZG5lc2RheSwgYW5kIFRodXJzZGF5IHRlbmQgdG8gb2JzZXJ2ZSBoaWdoZXIgbnVtYmVyIG9mIHRyaXBzLiAKCmBgYHtyIGJpa2VzaGFyZSB2aXo0fQoKZ2dwbG90KGJpa2VfY2Vuc3VzICU+JSBtdXRhdGUoaG91ciA9IGhvdXIoc3RhcnRlZF9hdCkpKSsKICAgICBnZW9tX2ZyZXFwb2x5KGFlcyhob3VyLCBjb2xvciA9IGRvdHcpLCBiaW53aWR0aCA9IDMsIGx3ZCA9IDAuOCkrCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGMoIiMxMjRFNzgiLCAiIzgxOUZBMSIsICIjRjBGMEM5IiwgIiNGMkJCMDUiLCAiI0U1ODUwNyIsICIjRDc0RTA5IiwgIiM2RTBFMEEiKSwgbmFtZSA9ICJEYXkiKSArIAogIGxhYnModGl0bGU9IkJpa2UgU2hhcmUgVHJpcHMgaW4gSmVyc2V5IENpdHkgYnkgRGF5IG9mIHRoZSBXZWVrLCBBdWcgMjAyMyIsCiAgICAgICB4PSJIb3VyIiwgCiAgICAgICB5PSJUcmlwIENvdW50cyIpICsgCiAgdGhlbWUocGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gOSxmYWNlID0gIml0YWxpYyIpLAogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSwgCiAgICAgICAgYXhpcy50ZXh0Lng9ZWxlbWVudF90ZXh0KHNpemU9OCksCiAgICAgICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF90ZXh0KHNpemU9OCksIAogICAgICAgIGF4aXMudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9MTApLCAKICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfcmVjdChjb2xvdXIgPSAiZ3JleSIsIGZpbGw9TkEsIHNpemU9MC44KSkKCmBgYAoKU2FtZSB0eXBlIG9mIHZpc3VhbGl6ZSBidXQgdGhpcyB0aW1lIHNlcGFyYXRlZCBiZXR3ZWVuIHdlZWtlbmQgYW5kIHdlZWtkYXlzLiBJdCBpcyB2ZXJ5IG9idmlvdXMgdGhhdCB3ZSBzZWUgbW9yZSB0cmlwcyBkdXJpbmcgd2Vla2RheXMgYW5kIGxlc3MgdHJpcHMgZHVyaW5nIHdlZWtlbmRzLiBBbHNvLCBkdXJpbmcgd2Vla2VuZHMsIG51bWJlciBvZiB0cmlwcyBzdGFydCB0byBpbmNyZWFzZSBtdWNoIGxhdGVyIHRoYW4gZHVyaW5nIHdlZWtkYXlzLCBzdWdnZXN0aW5nIHRoZSBtaXNzaW5nIG9mIG1vcm5pbmcgcnVzaCBob3VyIHdpbGwgc2lnbmlmaWNhbnRseSBpbXBhY3QgcGVvcGxlJ3MgdXNlIG9mIGJpa2VzaGFyZSBzZXJ2aWNlcy4gCgpgYGB7ciBiaWtlc2hhcmUgdml6IDV9CgpnZ3Bsb3QoYmlrZV9jZW5zdXMpKwogICAgIGdlb21fZnJlcXBvbHkoYWVzKGhvdXIsIGNvbG9yID0gd2Vla2VuZCksIGJpbndpZHRoID0gMywgbHdkID0gMS4yKSsKICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzPXBhbGV0dGUyKSArIAogIGxhYnMoY29sb3IgPSAiVGltZSBvZiBXZWVrIikgKwogIGxhYnModGl0bGU9IkJpa2UgU2hhcmUgVHJpcHMgaW4gSmVyc2V5IENpdHkgLSBXZWVrZW5kIHZzIFdlZWtkYXksIEF1ZyAyMDIzIiwKICAgICAgIHg9IkhvdXIiLCAKICAgICAgIHk9IlRyaXAgQ291bnRzIikgKyAKICB0aGVtZShwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSA5LGZhY2UgPSAiaXRhbGljIiksCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIsIGZhY2UgPSAiYm9sZCIpLCAKICAgICAgICBheGlzLnRleHQueD1lbGVtZW50X3RleHQoc2l6ZT04KSwKICAgICAgICBheGlzLnRleHQueT1lbGVtZW50X3RleHQoc2l6ZT04KSwgCiAgICAgICAgYXhpcy50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT0xMCksIAogICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9yZWN0KGNvbG91ciA9ICJncmV5IiwgZmlsbD1OQSwgc2l6ZT0wLjgpKQoKYGBgCgpCdWlsZGluZyBvbnRvIHRoYXQsIHdlIGdyb3VwIHRoaXMgZGF0YXNldCBieSBzdGF0aW9uIGFuZCB0aW1lIG9mIGRheSB0byB2aXN1YWxpemUgdGhlIHRvdGFsIG51bWJlciBvZiB0cmlwcyBhdCBlYWNoIHN0YXRpb24gZHVyaW5nIGEgc3BlY2lmaWMgcGVyaW9kIG9mIHRpbWUgb24gd2Vla2RheSBvciB3ZWVrZW5kLiBUbyBzaW1wbGlmeSBvdXIgdmlzdWFsaXphdGlvbiwgd2Ugc2VsZWN0ZWQgdGhlIHRvcCA1MCBvY2N1cnJlbmNlcyBpbiBvdXIgZGF0YXNldC4gTm90ZSB0aGF0IGhlcmUsIGVhY2ggYmFyIHJlcHJlc2VudCBhbiBpbnN0YW5jZSBvZiBzdGF0aW9uLXRpbWUgb2YgZGF5LXdlZWtkYXkvd2Vla2VuZCBjb21iaW5hdGlvbi4gV2UgYXJlIGFibGUgdG8gc2VlIHRoYXQgUE0gcnVzaCBob3VyIGluZGVlZCBzZWVzIHRoYXQgZ3JlYXRlc3QgYmlrZXNoYXJlIGRlbWFuZCBmb3IgYSBjb3VwbGUgb2Ygc3RhdGlvbnMgaW4gcGFydGljdWxhci4gV2Vla2VuZCByaWRlcyBvbmx5IGFwcGVhciB0aHJlZSB0aW1lcyBpbiB0aGUgdG9wIDUwIGxpc3QuIFpvb21pbmcgaW50byB0aGUgZGV0YWlsIHJldmVhbHMgdG8gdXMgdGhhdCAqKkdyb3ZlIFN0IFBBVEgqKiBzdGF0aW9uIG9ic2VydmVzIGEgdG90YWwgb2YgMTExNyByaWRlcyBkdXJpbmcgUE0gcnVzaCBvbiBhIHdlZWtkYXksIDgwMSByaWRlcyBvdmVybmlnaHQgb24gYSB3ZWVrZGF5IGluIEF1Z3VzdCAyMDIzLiAqKkhvYm9rZW4gVGVybWluYWwgLSBSaXZlciBTdCAmIEh1ZHNvbiBQbCoqIHN0YXRpb24gb2JzZXJ2ZXMgYSB0b3RhbCBvZiA4NjQgcmlkZXMgZHVyaW5nIFBNIHJ1c2ggb24gd2Vla2RheS4gKipTb3V0aCBXYXRlcmZyb250IFdhbGt3YXkgLSBTaW5hdHJhIERyICYgMSBTdCoqIHN0YXRpb24gb2JzZXJ2ZXMgMzE5IHJpZGVzIG92ZXJuaWdodCBvbiBhIHdlZWtlbmQgYW5kIDczMSByaWRlcyBkdXJpbmcgUE0gcnVzaCBvbiBhIHdlZWtkYXkgaW4gQXVndXN0IDIwMjMuIAoKYGBge3IgYmlrZXNoYXJlIHZpeiA3LCBmaWcud2lkdGg9MTB9Cgp0b19wbG90IDwtIGJpa2VfY2Vuc3VzJT4lICMgb25seSBzaG93IDEwMCBzdGF0aW9ucwogIGdyb3VwX2J5KHN0YXJ0X3N0YXRpb25faWQsIHN0YXJ0X2xhdCwgc3RhcnRfbG5nLCB3ZWVrZW5kLCB0aW1lX29mX2RheSwgc3RhcnRfc3RhdGlvbl9uYW1lKSAlPiUKICB0YWxseSgpICU+JSAKICBhcnJhbmdlKC1uKSAlPiUgCiAgaGVhZCg1MCkgCnRvX3Bsb3QkSUQgPC0gc2VxX2Fsb25nKHRvX3Bsb3Qkc3RhcnRfc3RhdGlvbl9pZCkKCnRvX3Bsb3QgJT4lIAogIGFycmFuZ2UoLW4pICU+JQogIGhlYWQoNTApICU+JSAKICBnZ3Bsb3QoYWVzKHggPSByZW9yZGVyKElELCAtbiksIG4sIGZpbGwgPSB0aW1lX29mX2RheSwgY29sb3IgPSB3ZWVrZW5kKSkgKwogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IHBhbGV0dGU0LCBuYW1lPSJUaW1lIG9mIERheSIpICsgCiAgZ3VpZGVzKGNvbG9yPSJub25lIikgKyAKICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IiwgcG9zaXRpb249InN0YWNrIikgKwogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBjKCJ0cmFuc3BhcmVudCIsICJibGFjayIpKSArCiAgbGFicyh0aXRsZT0iVG9wIDUwIE9jY3VyZW5jZXMgb2YgUmlkZXMgQnkgVGltZSIpKwogIHlsYWIoIk51bWJlciBvZiBSaWRlcyIpICsKICB4bGFiKCIiKSArIAogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkpICsgCiAgdGhlbWUocGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gOSxmYWNlID0gIml0YWxpYyIpLAogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSwgCiAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCksCiAgICBheGlzLnRpY2tzLnggPSBlbGVtZW50X2JsYW5rKCksIAogICAgICAgIGF4aXMudGV4dC55PWVsZW1lbnRfdGV4dChzaXplPTgpLCAKICAgICAgICBheGlzLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTEwKSwgCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X3JlY3QoY29sb3VyID0gImdyZXkiLCBmaWxsPU5BLCBzaXplPTAuOCkpCmBgYAoKCiMjIFNwYXRpYWwgQW5hbHlzaXMKCkluIGFkZGl0aW9uIHRvIGV4YW1pbmluZyBvdXIgZGF0YSB0aHJvdWdoIHRlbXBvcmFsIGxlbnMsIHdlIG1hZGUgc29tZSBtYXBzIHRvIHNob3cgdGhlIGFjdHVhbCBsb2NhdGlvbiBvZiBzdGF0aW9ucyB3aXRoIHRoZSBncmVhdGVzdCBiaWtlc2hhcmUgZGVtYW5kIGJ5IHRpbWUgb2YgZGF5IGFuZCBkYXkgb2YgdGhlIHdlZWsuIFRoaXMgcHJvcG9ydGlvbmFsIG1hcCBpbGx1c3RyYXRlcyB0aGF0IHN0YXRpb25zIHJlY2VpdmUgdGhlIGhpZ2hlc3QgcmlkZSBkZW1hbmQgZHVyaW5nIFBNIHJ1c2ggYW5kIG92ZXJuaWdodCBpbiB3ZWVrZGF5cyBhbmQgdGhlIGxvd2VzdCBkZW1hbmQgZHVyaW5nIEFNIHJ1c2guIFN0YXRpb25zIHdpdGggdGhlIGhpZ2hlc3QgYmlrZXNoYXJlIGRlbWFuZCBhcmUgZ2VuZXJhbGx5IGxvY2F0ZWQgYWxvbmcgdGhlIEh1ZHNvbiBSaXZlci4gVGhpcyBjb3VsZCBiZSBhdHRyaWJ1dGVkIHRvIHR3byByZWFzb25zOiB0aGVyZSBhcmUgc2ltcGx5IG1vcmUgc3RhdGlvbnMgaW4gdGhpcyBwYXJ0IG9mIEplcnNleSBDaXR5IG9yIENpdGkgQmlrZSBoYXMgc3RyYXRlZ2ljYWxseSBidWlsdCBiaWtlIHN0YXRpb25zIGFsb25nIHRoZSBIdWRzb24gUml2ZXIgdG8gbWFrZSBkYWlseSBjb21tdXRlIGVhc2llciBmb3IgdGhvc2Ugd2hvIGxpdmUgaW4gSmVyc2V5IENpdHkgYnV0IG5lZWQgdG8gd29yay9zdHVkeSBpbiBNYW5oYXR0YW4uIAoKYGBge3IgYmlrZXNoYXJlIHZpeiA2fQoKZ2dwbG90KCkrCiAgZ2VvbV9zZihkYXRhID0gamNUcmFjdHMgJT4lCiAgICAgICAgICBzdF90cmFuc2Zvcm0oY3JzPTQzMjYpLCBmaWxsID0gIndoaXRlIikrCiAgZ2VvbV9wb2ludChkYXRhID0gdG9fcGxvdCwKICAgICAgICAgICAgIGFlcyh4PXN0YXJ0X2xuZywgeSA9IHN0YXJ0X2xhdCwgc2l6ZT1uKSwgY29sb3IgPSBhbHBoYSgiIzZFMEUwQSIsIDAuNykpICsgCiAgeWxpbShtaW4oYmlrZV9jZW5zdXMkc3RhcnRfbGF0KSwgbWF4KGJpa2VfY2Vuc3VzJHN0YXJ0X2xhdCkpKwogIHhsaW0obWluKGJpa2VfY2Vuc3VzJHN0YXJ0X2xuZyksIG1heChiaWtlX2NlbnN1cyRzdGFydF9sbmcpKSArCiAgc2NhbGVfc2l6ZShyYW5nZSA9IGMoMC41LCA4KSkgKyAKICBsYWJzKHNpemUgPSAiTnVtYmVyIG9mIFJpZGVzIikgKwogIGZhY2V0X2dyaWQod2Vla2VuZCB+IHRpbWVfb2ZfZGF5KSArIAogICAgbGFicyh0aXRsZT0iTG9jYXRpb25zIG9mIFN0YXRpb25zIHdpdGggR3JlYXRlc3QgQmlrZXNoYXJlIERlbWFuZCBieSBUaW1lIikrIAogIHRoZW1lKGF4aXMudGV4dC54PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRleHQueT1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aWNrcyA9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpdGxlLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gOSxmYWNlID0gIml0YWxpYyIpLAogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSwKICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfcmVjdChjb2xvdXIgPSAiZ3JleSIsIGZpbGw9TkEsIGxpbmV3aWR0aD0wLjgpCiAgICAgICAgKQpgYGAKCldlIGdyb3VwZWQgYWxsIHRoZSBzdGFydGluZyBzdGF0aW9ucyB0b2dldGhlciB0byBjaGVjayBmb3Igc3BhdGlhbCBhdXRvY29ycmVsYXRpb24gYmV0d2VlbiBzdGF0aW9ucy4gV2UgYmVsaWV2ZSB0aGF0IHN0YXRpb25zIHdpdGggZ3JlYXRlciBkZW1hbmQgYXJlIG1vcmUgbGlrZWx5IHRvIGJlIGxvY2F0ZWQgdG8gb3RoZXIgc3RhdGlvbnMgd2l0aCBncmVhdGVyIGRlbWFuZC4gSW4gdGhpcyBjYXNlLCB3ZSBjYW4gdXNlIHRoZSBkZW1hbmQgb2YgbmVhcmJ5IHN0YXRpb25zIGF0IGEgcGFydGljdWxhciB0aW1lIHRvIGVzdGltYXRlIHRoZSBkZW1hbmQgb2Ygb3VyIHRhcmdldCBzdGF0aW9uLiBXZSBzZWxlY3RlZCA4MCBzdGF0aW9ucyB3aXRoIHRoZSBoaWdoZXN0IG51bWJlciBvZiByaWRlcyBpbiBBdWd1c3QgMjAyMyBhbmQgY29tcHV0ZWQgdGhlIE1vcmFuJ3MgSSB3aXRoIDk5OSByYW5kb20gTW9udGUgQ2FybG8gc2ltdWxhdGlvbi4gVGhlIHJlc3VsdHMgc2hvdyB0aGF0IG91ciBvYnNlcnZlZCBNb3JhbidzIEkgaXMgaGlnaGx5IHNpZ25pZmljYW50LCBpbXBseWluZyB0aGF0IHRoZXJlJ3Mgc3BhdGlhbCBhdXRvY29ycmVsYXRpb24gYmV0d2VlbiBiaWtlc2hhcmVzIGF0IG5lYXJieSBzdGF0aW9ucy4gIAoKYGBge3Igc3BhdGlhbCBhdXRvIGNvciwgd2FybmluZz1GQUxTRX0KCm1vcmFuX2RhdGEgPC0gYmlrZV9jZW5zdXMgJT4lCiAgZ3JvdXBfYnkoc3RhcnRfc3RhdGlvbl9pZCwgc3RhcnRfbGF0LCBzdGFydF9sbmcsIHN0YXJ0X3N0YXRpb25fbmFtZSkgJT4lCiAgdGFsbHkoKSAlPiUgCiAgYXJyYW5nZSgtbikgJT4lIAogIGhlYWQoODApICU+JSAKICBzdF9hc19zZiguLCBjb29yZHMgPSBjKCJzdGFydF9sbmciLCAic3RhcnRfbGF0IiksIGNycyA9IDQzMjYpCgoKY29vcmRzIDwtICBzdF9jb29yZGluYXRlcyhtb3Jhbl9kYXRhKSAKbmVpZ2hib3JMaXN0IDwtIGtubjJuYihrbmVhcm5laWdoKGNvb3JkcywgNSkpCnNwYXRpYWxXZWlnaHRzIDwtIG5iMmxpc3R3KG5laWdoYm9yTGlzdCwgc3R5bGU9IlciKQoKbW9yYW5UZXN0IDwtIG1vcmFuLm1jKG1vcmFuX2RhdGEkbiwgCiAgICAgICAgICAgICAgICAgICAgICBzcGF0aWFsV2VpZ2h0cywgbnNpbSA9IDk5OSkKCmdncGxvdChhcy5kYXRhLmZyYW1lKG1vcmFuVGVzdCRyZXNbYygxOjk5OSldKSwgYWVzKG1vcmFuVGVzdCRyZXNbYygxOjk5OSldKSkgKwogIGdlb21faGlzdG9ncmFtKGJpbndpZHRoID0gMC4wMSwgZmlsbCA9ICIjMTI0RTc4IikgKwogIGdlb21fdmxpbmUoYWVzKHhpbnRlcmNlcHQgPSBtb3JhblRlc3Qkc3RhdGlzdGljKSwgY29sb3VyID0gIiM2RTBFMEEiICxsd2Q9MSkgKwogIHNjYWxlX3hfY29udGludW91cyhsaW1pdHMgPSBjKC0wLjUsIDAuNSkpICsKICBsYWJzKHRpdGxlID0gIk9ic2VydmVkIGFuZCBQZXJtdXRlZCBNb3JhbidzIEkgZm9yIFN0YXRpb25zIHdpdGggTW9zdCBSaWRlcyIsCiAgICAgICBzdWJ0aXRsZSA9ICJPYnNlcnZlZCBNb3JhbidzIEkgaW4gUmVkIiwKICAgICAgIHggPSAiTW9yYW4ncyBJIiwKICAgICAgIHkgPSAiQ291bnQiKSArCiAgdGhlbWVfbGlnaHQoKSArICAgCnRoZW1lKHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDksZmFjZSA9ICJpdGFsaWMiKSwKICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgZmFjZSA9ICJib2xkIiksIAogICAgICAgIGF4aXMudGV4dC54PWVsZW1lbnRfdGV4dChzaXplPTgpLAogICAgICAgIGF4aXMudGV4dC55PWVsZW1lbnRfdGV4dChzaXplPTgpLCAKICAgICAgICBheGlzLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTEwKSkKYGBgCgpUaGlzIG1hcCBiZWxvdyBzaG93cyBob3cgc3RhdGlvbnMgd2l0aCBoaWdoZXIgYmlrZXNoYXJlIGRlbWFuZCBhcmUgY2x1c3RlcmVkLiBJbiBwYXJ0aWN1bGFyLCB0aGVyZSBhcmUgdHdvIGNsdXN0ZXJzIG9mIHN0YXRpb25zLCBhbmQgd2l0aGluIGVhY2ggY2x1c3RlciwgdGhlcmUgYXJlIGEgZmV3IHN0YXRpb25zIHdpdGggcGFydGljdWxhcmx5IGxhcmdlIG51bWJlciBvZiByaWRlcy4gVGhlc2Ugc3RhdGlvbnMgYXJlIGdlbmVyYWxseSBzdXJyb3VuZGVkIGJ5IHN0YXRpb25zIHdpdGggc2xpZ2h0bHkgbGVzcyByaWRlcy4gCgpgYGB7ciBzcGF0aWFsIGNvciB2aXosIGZpZy5oZWlnaHQ9OCwgZmlnLndpZHRoPTExfQoKICBnZ3Bsb3QoKSArCiAgZ2VvbV9zZihkYXRhID0gamNUcmFjdHMsIGZpbGwgPSAid2hpdGUiKSArCiAgZ2VvbV9zZihkYXRhID0gbW9yYW5fZGF0YSwKICAgICAgICAgIGFlcyhzaXplID0gbiwgY29sb3IgPSBuKSkgKyAKICB5bGltKG1pbihiaWtlX2NlbnN1cyRzdGFydF9sYXQpLCBtYXgoYmlrZV9jZW5zdXMkc3RhcnRfbGF0KSkrCiAgeGxpbShtaW4oYmlrZV9jZW5zdXMkc3RhcnRfbG5nKSwgbWF4KGJpa2VfY2Vuc3VzJHN0YXJ0X2xuZykpICsgCiAgc2NhbGVfc2l6ZSgKICAgIHJhbmdlID0gYygxLCA2KSwKICAgIGd1aWRlID0gZ3VpZGVfbGVnZW5kKAogICAgICBkaXJlY3Rpb24gPSAiaG9yaXpvbnRhbCIsCiAgICAgIG5yb3cgPSAxLAogICAgICBsYWJlbC5wb3NpdGlvbiA9ICJib3R0b20iKSkgKwogIHNjYWxlX2NvbG9yX2dyYWRpZW50bihjb2xvdXJzID0gaGNsLmNvbG9ycyg1LCAiUmRCdSIscmV2ID0gVFJVRSwgYWxwaGEgPSAwLjkpLCAKICAgICAgICAgICAgICAgICAgICAgICAgZ3VpZGUgPSBndWlkZV9sZWdlbmQoZGlyZWN0aW9uID0gImhvcml6b250YWwiLCBucm93ID0gMSkpICsKICBsYWJzKHRpdGxlID0gIlN0YXRpb25zIHdpdGggTW9zdCBSaWRlcyIsCiAgICAgICBzdWJ0aXRsZSA9ICAiRG90IHNpemUgcHJvcG9ydGlvbmFsIHRvIHJpZGVzIikgKyAKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIikgKyAKICB0aGVtZShheGlzLnRleHQueD1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGlja3MgPWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aXRsZS55ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDksZmFjZSA9ICJpdGFsaWMiKSwKICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgZmFjZSA9ICJib2xkIiksCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X3JlY3QoY29sb3VyID0gImdyZXkiLCBmaWxsPU5BLCBsaW5ld2lkdGg9MC44KQogICAgICAgICkKCmBgYAoKVGhpcyBhbmltYXRlZCBtYXAgYmVsb3cgY29tYmluZXMgdGhlIHNwYXRpYWwgYW5kIHRlbXBvcmFsIGNoYXJhY3RlcmlzdGljcyBvZiBiaWtlIHJpZGVzIGF0IDE1bWluIGludGVydmFsIGludG8gYSBjaG9yb3BsZXRoIG1hcC4gCgpgYGB7ciBhbmltYXRlIG1hcCB0ZW1wb3JhbCB2aXosIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGNhY2hlPVRSVUUsIGZpZy5zaG93PSdhbmltYXRlJ30KCndlZWszMyA8LQogIGZpbHRlcihiaWtlX2NlbnN1cyAsIHdlZWsgPT0gMzMgJiBkb3R3ID09ICJNb24iKQoKd2VlazMzLnBhbmVsIDwtCiAgZXhwYW5kLmdyaWQoCiAgICBpbnRlcnZhbDE1ID0gdW5pcXVlKHdlZWszMyRpbnRlcnZhbDE1KSwKICAgIE9yaWdpbi5UcmFjdCA9IHVuaXF1ZShiaWtlX2NlbnN1cyRPcmlnaW4uVHJhY3QpKQoKYmlrZS5hbmltYXRpb24uZGF0YSA8LQogIG11dGF0ZSh3ZWVrMzMsIFRyaXBfQ291bnRlciA9IDEpICU+JQogICAgcmlnaHRfam9pbih3ZWVrMzMucGFuZWwpICU+JSAKICAgIGdyb3VwX2J5KGludGVydmFsMTUsIE9yaWdpbi5UcmFjdCkgJT4lCiAgICBzdW1tYXJpemUoVHJpcF9Db3VudCA9IHN1bShUcmlwX0NvdW50ZXIsIG5hLnJtPVQpKSAlPiUgCiAgICB1bmdyb3VwKCkgJT4lIAogICAgbGVmdF9qb2luKGpjVHJhY3RzLCBieT1jKCJPcmlnaW4uVHJhY3QiID0gIkdFT0lEIikpICU+JQogICAgc3Rfc2YoKSAlPiUKICAgIG11dGF0ZShUcmlwcyA9IGNhc2Vfd2hlbihUcmlwX0NvdW50ID09IDAgfiAiMCB0cmlwcyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgVHJpcF9Db3VudCA+IDAgJiBUcmlwX0NvdW50IDw9IDMgfiAiMS0zIHRyaXBzIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBUcmlwX0NvdW50ID4gMyAmIFRyaXBfQ291bnQgPD0gNiB+ICI0LTYgdHJpcHMiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIFRyaXBfQ291bnQgPiA2ICYgVHJpcF9Db3VudCA8PSAxMCB+ICI3LTEwIHRyaXBzIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBUcmlwX0NvdW50ID4gMTAgfiAiMTErIHRyaXBzIikpICU+JQogICAgbXV0YXRlKFRyaXBzICA9IGZjdF9yZWxldmVsKFRyaXBzLCAiMCB0cmlwcyIsIjEtMyB0cmlwcyIsIjQtNiB0cmlwcyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICI3LTEwIHRyaXBzIiwiMTArIHRyaXBzIikpCgpiaWtlc2hhcmVfYW5pbWF0aW9uIDwtCiAgZ2dwbG90KCkgKwogICAgZ2VvbV9zZihkYXRhID0gYmlrZS5hbmltYXRpb24uZGF0YSwgYWVzKGZpbGwgPSBUcmlwcykpICsKICAgIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IHBhbGV0dGU1KSArCiAgICBsYWJzKHRpdGxlID0gIkJpa2VzaGFyZSBGb3IgT25lIERheSBpbiBBdWcgMjAyMyIsCiAgICAgICAgIHN1YnRpdGxlID0gIjE1IG1pbnV0ZSBpbnRlcnZhbHM6IHtjdXJyZW50X2ZyYW1lfSIpICsKICAgIHRyYW5zaXRpb25fbWFudWFsKGludGVydmFsMTUpICArIAogIHRoZW1lKGF4aXMudGV4dC54PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRleHQueT1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aWNrcyA9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpdGxlLnkgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gOSxmYWNlID0gIml0YWxpYyIpLAogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSwKICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfcmVjdChjb2xvdXIgPSAiZ3JleSIsIGZpbGw9TkEsIGxpbmV3aWR0aD0wLjgpCiAgICAgICAgKQogIAoKCmFuaW1fc2F2ZSgiYW5pbWF0aW9uLmdpZiIsIGFuaW1hdGlvbiA9IGJpa2VzaGFyZV9hbmltYXRpb24sIGVuZF9wYXVzZSA9IDQsIGhlaWdodCA9IDEwMDAsIHdpZHRoID0gNzAwLCBmcHMgPSA2LCByZW5kZXJlciA9IGdpZnNraV9yZW5kZXJlcigpKQoKCgpgYGAKCiFbQW5pbWF0ZWQgTWFwIGZvciBCaWtlc2hhcmUgRGVtYW5kIGluIFdlZWsgMzNdKGFuaW1hdGlvbi5naWYpCgoKIyMgV2VhdGhlciBBbmFseXNpcwoKT24gdG9wIG9mIHNwYWNlIGFuZCB0aW1lLCB3ZWF0aGVyIGNhbiBiZSBhbm90aGVyIHNpZ25pZmljYW50IGZhY3RvciBvZiBpbiBwcmVkaWN0aW5nIGJpa2VzaGFyZSBkZW1hbmQuIEdlbmVyYWxseSwgd2UgYmVsaWV2ZSBiaWtlc2hhcmUgZGVtYW5kIHdpbGwgZGVjcmVhc2UgaWYgaXQncyBvbiBhIHJhaW55LCB3aW5keSwgb3IgYSB2ZXJ5IGhvdCBkYXkgaW4gb3VyIGNhc2UuICBXaXRoIHRoYXQgaW4gbWluZCwgd2UgZG93bmxvYWRlZCB0aGUgd2VhdGhlciBkYXRhIHVzaW5nIHRoZSBgcmllbV9tZWFzdXJlc2AgZnVuY3Rpb24gZm9yIE5ld2FyayBhaXJwb3J0IGJldHdlZW4gQXVnIDEsIDIwMjMgYW5kIFNlcHQgMSwgMjAyMy4gTm90ZSB0aGF0IHRoZSBOZXdhcmsgQWlycG9ydCBzdGF0aW9uIHN1ZmZpY2llbnRseSBwcm92aWRlcyB0ZW1wb3JhbCB3ZWF0aGVyIGZvciBhbGwgb2YgSmVyc2V5IENpdHkuIFRoZSB3ZWF0aGVyLlBhbmVsIGlzIGdlbmVyYXRlZCB0byBzdW1tYXJpemUgdGVtcGVyYXR1cmUsIHByZWNpcGl0YXRpb24sIGFuZCB3aW5kIHNwZWVkIGZvciBldmVyeSBob3VyLiAKCmBgYHtyIGRvd25sb2FkIGFuZCB3cmFuZ2xlIHdlYXRoZXIgZGF0YSwgd2FybmluZz1GQUxTRX0KCndlYXRoZXIuUGFuZWwgPC0gCiAgcmllbV9tZWFzdXJlcyhzdGF0aW9uID0gIkVXUiIsIGRhdGVfc3RhcnQgPSAiMjAyMy0wOC0wMSIsIGRhdGVfZW5kID0gIjIwMjMtMDktMDEiKSAlPiUKICBkcGx5cjo6c2VsZWN0KHZhbGlkLCB0bXBmLCBwMDFpLCBza250KSU+JQogIHJlcGxhY2UoaXMubmEoLiksIDApICU+JQogICAgbXV0YXRlKGludGVydmFsNjAgPSB5bWRfaChzdWJzdHIodmFsaWQsMSwxMykpKSAlPiUKICAgIG11dGF0ZSh3ZWVrID0gd2VlayhpbnRlcnZhbDYwKSwKICAgICAgICAgICBkb3R3ID0gd2RheShpbnRlcnZhbDYwLCBsYWJlbD1UUlVFKSkgJT4lCiAgICBncm91cF9ieShpbnRlcnZhbDYwKSAlPiUKICAgIHN1bW1hcml6ZShUZW1wZXJhdHVyZSA9IG1heCh0bXBmKSwKICAgICAgICAgICAgICBQcmVjaXBpdGF0aW9uID0gc3VtKHAwMWkpLAogICAgICAgICAgICAgIFdpbmRfU3BlZWQgPSBtYXgoc2tudCkpICU+JQogICAgbXV0YXRlKFRlbXBlcmF0dXJlID0gaWZlbHNlKFRlbXBlcmF0dXJlID09IDAsIDQyLCBUZW1wZXJhdHVyZSkpCgpgYGAKCkZvciB0aGUgdGVtcGVyYXR1cmUsIHByZWNpcGl0YXRpb24sIGFuZCB3aW5kIGRhdGEgd2UgY29sbGVjdGVkIGR1cmluZyB0aGlzIG1vbnRoLiBXZSBtYXkgc2VlIHRoYXQgdGhlcmUgYXJlIGEgY291cGxlIG9mIHJhaW55IGRheXMgaW4gQXVndXN0LiBXaW5kIHNwZWVkIGlzIHByZXR0eSBjb25zaXN0ZW50IHRocm91Z2hvdXQgd2l0aCBhIGNvdXBsZSBvZiByZWFsbHkgd2luZHkgZGF5cy4gVGVtcGVyYXR1cmUgZmx1Y3R1YXRlcyBvbiBhIGRhaWx5IGJhc2lzIHdpdGggaGlnaGVyIHRlbXBlcmF0dXJlIGR1cmluZyB0aGUgZGF5IGFuZCBjb29sZXIgdGVtcGVyYXR1cmUgYXQgbmlnaHQuIAoKYGBge3IgdmlzdWFsaXplIHdlYXRoZXIgZGF0YSwgZmlnLmhlaWdodD0xMSwgZmlnLndpZHRoPTgsIHdhcm5pbmc9RkFMU0V9CgpncmlkLmFycmFuZ2UoCiAgZ2dwbG90KHdlYXRoZXIuUGFuZWwsIGFlcyhpbnRlcnZhbDYwLFByZWNpcGl0YXRpb24pKSArIGdlb21fbGluZShjb2xvcj0iIzZFMEUwQSIpICsgCiAgICBsYWJzKHRpdGxlPSJQZXJjaXBpdGF0aW9uIiwgeD0iSG91ciIsIHk9IlBlcmVjaXBpdGF0aW9uIikgKyAKICAgIHRoZW1lKHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDksZmFjZSA9ICJpdGFsaWMiKSwKICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgZmFjZSA9ICJib2xkIiksIAogICAgICAgIGF4aXMudGV4dC54PWVsZW1lbnRfdGV4dChzaXplPTgpLAogICAgICAgIGF4aXMudGV4dC55PWVsZW1lbnRfdGV4dChzaXplPTgpLCAKICAgICAgICBheGlzLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTEwKSwgCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSksIAogIGdncGxvdCh3ZWF0aGVyLlBhbmVsLCBhZXMoaW50ZXJ2YWw2MCxXaW5kX1NwZWVkKSkgKyBnZW9tX2xpbmUoY29sb3I9IiNENzRFMDkiKSArIAogICAgbGFicyh0aXRsZT0iV2luZCBTcGVlZCIsIHg9IkhvdXIiLCB5PSJXaW5kIFNwZWVkIikgKwogICAgdGhlbWUocGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gOSxmYWNlID0gIml0YWxpYyIpLAogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSwgCiAgICAgICAgYXhpcy50ZXh0Lng9ZWxlbWVudF90ZXh0KHNpemU9OCksCiAgICAgICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF90ZXh0KHNpemU9OCksIAogICAgICAgIGF4aXMudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9MTApLCAKICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpKSwgIAogIGdncGxvdCh3ZWF0aGVyLlBhbmVsLCBhZXMoaW50ZXJ2YWw2MCxUZW1wZXJhdHVyZSkpICsgZ2VvbV9saW5lKGNvbG9yPSIjMTI0RTc4IikgKyAKICAgIGxhYnModGl0bGU9IlRlbXBlcmF0dXJlIiwgeD0iSG91ciIsIHk9IlRlbXBlcmF0dXJlIikgK3RoZW1lKHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDksZmFjZSA9ICJpdGFsaWMiKSwKICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgZmFjZSA9ICJib2xkIiksIAogICAgICAgIGF4aXMudGV4dC54PWVsZW1lbnRfdGV4dChzaXplPTgpLAogICAgICAgIGF4aXMudGV4dC55PWVsZW1lbnRfdGV4dChzaXplPTgpLCAKICAgICAgICBheGlzLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTEwKSwgCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSksIAogIHRvcD0iV2VhdGhlciBEYXRhICIpCgpgYGAKCgojIFNwYWNlLVRpbWUgUGFuZWwKCkluIHRoZSBmb2xsb3dpbmcgc3RlcHMsIHdlIGNyZWF0ZWQgYSBzdHVkeSBwYW5lbCB3aGVyZSBlYWNoIGluc3RhbmNlIGluIHRoZSBwYW5lbCBpcyBhIHVuaXF1ZSBjb21iaW5hdGlvbiBvZiBzcGFjZSBhbmQgdGltZS4gSW4gb3RoZXIgd29yZHMsIGVhY2ggcm93IHdpbGwgcmVwcmVzZW50IHRoZSByaWRlIGF0IGEgcGFydGljdWxhciBzdGF0aW9uIGR1cmluZyBhIHBhcnRpY3VsYXIgaG91ci4gCgpgYGB7ciBjcmVhdGUgc3R1ZHkgcGFuZWwsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9CgpzdHVkeS5wYW5lbCA8LSAKICBleHBhbmQuZ3JpZChpbnRlcnZhbDYwPXVuaXF1ZShiaWtlX2NlbnN1cyRpbnRlcnZhbDYwKSwgCiAgICAgICAgICAgICAgc3RhcnRfc3RhdGlvbl9pZCA9IHVuaXF1ZShiaWtlX2NlbnN1cyRzdGFydF9zdGF0aW9uX2lkKSkgJT4lCiAgbGVmdF9qb2luKC4sIGJpa2VfY2Vuc3VzICU+JQogICAgICAgICAgICAgIHNlbGVjdChzdGFydF9zdGF0aW9uX2lkLCBzdGFydF9zdGF0aW9uX25hbWUsIE9yaWdpbi5UcmFjdCwgc3RhcnRfbG5nLCBzdGFydF9sYXQpJT4lCiAgICAgICAgICAgICAgZGlzdGluY3QoKSAlPiUKICAgICAgICAgICAgICBncm91cF9ieShzdGFydF9zdGF0aW9uX2lkKSAlPiUKICAgICAgICAgICAgICBzbGljZSgxKSkKYGBgCgpXZSBuZWVkIHRvIGFkZCBzb21lIG1vcmUgaW5mb3JtYXRpb24gdG8gdGhpcyBwYW5lbC4gVGhpcyBpbmNsdWRlcyBjb3VudGluZyB0aGUgbnVtYmVyIG9mIHJpZGVzIGF0IHRoaXMgc3RhdGlvbiBhdCB0aGlzIHBhcnRpY3VsYXIgaG91ciwgYWRkaW5nIHdlYXRoZXIgaW5mb3JtYXRpb24sIGJyaW5naW5nIGluIGNlbnN1cyBkYXRhLCBhbmQgY2FsY3VsYXRpbmcgdGltZSBhbmQgZGF5IG9mIHdlZWsgdXNpbmcgdGhlIGBsdWJyaWRhdGVgIHBhY2thZ2UuICAKCmBgYHtyIGFkZCBpbmZvIHRvIHN0dWR5IHBhbmVsLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQoKYmlrZS5wYW5lbCA8LSAKICBiaWtlX2NlbnN1cyAlPiUKICBtdXRhdGUoVHJpcF9Db3VudGVyID0gMSkgJT4lCiAgcmlnaHRfam9pbihzdHVkeS5wYW5lbCkgJT4lIAogIGdyb3VwX2J5KGludGVydmFsNjAsIHN0YXJ0X3N0YXRpb25faWQsIHN0YXJ0X3N0YXRpb25fbmFtZSwgT3JpZ2luLlRyYWN0LCBzdGFydF9sbmcsIHN0YXJ0X2xhdCkgJT4lCiAgc3VtbWFyaXplKFRyaXBfQ291bnQgPSBzdW0oVHJpcF9Db3VudGVyLCBuYS5ybT1UKSkgJT4lCiAgbGVmdF9qb2luKHdlYXRoZXIuUGFuZWwpICU+JQogIHVuZ3JvdXAoKSAlPiUKICBmaWx0ZXIoaXMubmEoc3RhcnRfc3RhdGlvbl9pZCkgPT0gRkFMU0UpICU+JQogIG11dGF0ZSh3ZWVrID0gd2VlayhpbnRlcnZhbDYwKSwKICAgICAgICAgZG90dyA9IHdkYXkoaW50ZXJ2YWw2MCwgbGFiZWwgPSBUUlVFKSkgJT4lCiAgZmlsdGVyKGlzLm5hKE9yaWdpbi5UcmFjdCkgPT0gRkFMU0UpICU+JSAKICBsZWZ0X2pvaW4oLiwgbmpDZW5zdXMgJT4lCiAgICAgICAgICAgICAgYXMuZGF0YS5mcmFtZSgpICU+JQogICAgICAgICAgICAgIHNlbGVjdCgtZ2VvbWV0cnkpLCBieSA9IGMoIk9yaWdpbi5UcmFjdCIgPSAiR0VPSUQiKSkKCmBgYAoKIyMgV2VhdGhlciBDb3JyZWxhdGlvbgoKTm93LCB3ZSBjb3VsZCBwZXJmb3JtIG1vcmUgZXhwbG9yYXRvcnkgYW5hbHlzaXMgb24gb3VyIHBhbmVsIGRhdGEgdG8gcHJlcGFyZSBmb3Igb3VyIHJlZ3Jlc3Npb24gbW9kZWwuIEZpcnN0LCB3ZSB3b3VsZCBsaWtlIHRvIHNlZSBob3cgdGVtcGVyYXR1cmUgd2lsbCBjb3JyZWxhdGUgd2l0aCB0aGUgbnVtYmVyIG9mIGJpa2UgcmlkZXMgaW4gQXVndXN0LiBUaGUgZmlndXJlIGJlbG93IHNob3dzIGEgc2lnbmlmaWNhbnQsIHBvc2l0aXZlLCBzdHJvbmcsIGFuZCBsaW5lYXIgcmVsYXRpb25zaGlwIGJldHdlZW4gdGVtcGVyYXR1cmUgYW5kIG1lYW4gdHJpcCBjb3VudCBmb3IgYWxsIHdlZWtzIGluIEF1Z3VzdC4gVGhpcyBtZWFucyB0aGF0IGFzIHRoZSB0ZW1wZXJhdHVyZSBpbmNyZWFzZSwgYmlrZXNoYXJlIGRlbWFuZCB3aWxsIGluY3JlYXNlIGFzIHdlbGwuIAoKYGBge3IgdmlzdWFsaXplIHRlbXAsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9CgpiaWtlLnBhbmVsICU+JQogIGdyb3VwX2J5KGludGVydmFsNjApICU+JSAKICBzdW1tYXJpemUoVHJpcF9Db3VudCA9IG1lYW4oVHJpcF9Db3VudCksCiAgICAgICAgICAgIFRlbXBlcmF0dXJlID0gZmlyc3QoVGVtcGVyYXR1cmUpKSAlPiUKICBtdXRhdGUod2VlayA9IHdlZWsoaW50ZXJ2YWw2MCkpICU+JQogIGdncGxvdChhZXMoVGVtcGVyYXR1cmUsIFRyaXBfQ291bnQpKSArIAogICAgZ2VvbV9wb2ludChjb2xvciA9ICIjNkUwRTBBIikgKyBnZW9tX3Ntb290aChtZXRob2QgPSAibG0iLCBzZT0gRkFMU0UsIGNvbG9yPSIjMTI0RTc4IikgKwogICAgZmFjZXRfd3JhcCh+d2VlaywgbmNvbD04KSArIAogICAgbGFicyh0aXRsZT0iVHJpcCBDb3VudCBBcyBhIEZ1Y3Rpb24gb2YgVGVtcGVyYXR1cmUgYnkgV2VlayIsCiAgICAgICAgIHg9IlRlbXBlcmF0dXJlIiwgeT0iTWVhbiBUcmlwIENvdW50IikgKyAKICB0aGVtZShwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSA5LGZhY2UgPSAiaXRhbGljIiksCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIsIGZhY2UgPSAiYm9sZCIpLCAKICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGF4aXMudGlja3MueCA9IGVsZW1lbnRfYmxhbmsoKSwgCiAgICAgICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF90ZXh0KHNpemU9OCksIAogICAgICAgIGF4aXMudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9MTApLCAKICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfcmVjdChjb2xvdXIgPSAiZ3JleSIsIGZpbGw9TkEsIHNpemU9MC44KSkKICAKCmBgYAoKTmV4dCwgd2Ugd291bGQgbGlrZSB0byBzZWUgaG93IHdpbmQgc3BlZWQgd2lsbCBjb3JyZWxhdGUgd2l0aCB0aGUgbnVtYmVyIG9mIGJpa2UgcmlkZXMgaW4gQXVndXN0LiBXZSBzZWUgdGhhdCBhIGxpbmVhciBhbmQgcG9zaXRpdmUgcmVsYXRpb25zaGlwIGJldHdlZW4gdGhlc2UgdHdvIHZhcmlhYmxlcywgYnV0IHRoZSBzdHJlbmd0aCBvZiBzdWNoIHJlbGF0aW9uc2hpcCB2YXJpZXMgYmV0d2VlbiBkaWZmZXJlbnQgd2Vla3MuIFRoaXMgZ2VuZXJhbGx5IG1lYW5zIHRoYXQgd2Ugc2VlIG1vcmUgYmlrZXNoYXJlIGRlbWFuZCBkdXJpbmcgd2luZHkgZGF5cy4gCgpgYGB7ciB2aXN1YWxpemUgd2luZCwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0KYmlrZS5wYW5lbCAlPiUKICBncm91cF9ieShpbnRlcnZhbDYwKSAlPiUgCiAgc3VtbWFyaXplKFRyaXBfQ291bnQgPSBtZWFuKFRyaXBfQ291bnQpLAogICAgICAgICAgICBXaW5kID0gZmlyc3QoV2luZF9TcGVlZCkpICU+JQogIG11dGF0ZSh3ZWVrID0gd2VlayhpbnRlcnZhbDYwKSkgJT4lCiAgZ2dwbG90KGFlcyhXaW5kLCBUcmlwX0NvdW50KSkgKyAKICAgIGdlb21fcG9pbnQoY29sb3IgPSAiIzZFMEUwQSIpICsgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxtIiwgc2U9IEZBTFNFLCBjb2xvcj0iIzEyNEU3OCIpICsKICAgIGZhY2V0X3dyYXAofndlZWssIG5jb2w9OCkgKyAKICAgIGxhYnModGl0bGU9IlRyaXAgQ291bnQgQXMgYSBGdWN0aW9uIG9mIFdpbmQgYnkgV2VlayIsCiAgICAgICAgIHg9IldpbmQgU3BlZWQiLCB5PSJNZWFuIFRyaXAgQ291bnQiKSArIAogIHRoZW1lKHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDksZmFjZSA9ICJpdGFsaWMiKSwKICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgZmFjZSA9ICJib2xkIiksIAogICAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpLAogICAgYXhpcy50aWNrcy54ID0gZWxlbWVudF9ibGFuaygpLCAKICAgICAgICBheGlzLnRleHQueT1lbGVtZW50X3RleHQoc2l6ZT04KSwgCiAgICAgICAgYXhpcy50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT0xMCksIAogICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9yZWN0KGNvbG91ciA9ICJncmV5IiwgZmlsbD1OQSwgc2l6ZT0wLjgpKSArIAogICAgdGhlbWUocGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gOSxmYWNlID0gIml0YWxpYyIpLAogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSwgCiAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCksCiAgICBheGlzLnRpY2tzLnggPSBlbGVtZW50X2JsYW5rKCksIAogICAgICAgIGF4aXMudGV4dC55PWVsZW1lbnRfdGV4dChzaXplPTgpLCAKICAgICAgICBheGlzLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTEwKSwgCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X3JlY3QoY29sb3VyID0gImdyZXkiLCBmaWxsPU5BLCBzaXplPTAuOCkpCmBgYAoKCiMjIFNlcmlhbCBDb3JyZWxhdGlvbgoKU2hvdWxkIHRoZSBudW1iZXIgb2YgdHJpcHMgZXhoaWJpdCBzZXJpYWwgKHRlbXBvcmFsKSBjb3JyZWxhdGlvbiwgdGhlbiB0aGUgdGltZSBsYWcgZmVhdHVyZXMgd2lsbCBsZWFkIHRvIGJldHRlciBwcmVkaWN0aW9ucy4gVGhlIGxvZ2ljIGJlaGluZCBpcyB0aGF0IHRoZSBudW1iZXIgb2YgdHJpcHMgYXQgYSBwYXJ0aWN1bGFyIGhvdXIgd2lsbCB2ZXJ5IG11Y2ggY29ycmVsYXRlIHdpdGggdGhlIG51bWJlciBvZiB0cmlwcyBpbW1lZGlhdGVseSBiZWZvcmUgb3IgYWZ0ZXIgdGhhdCBob3VyLiBJZiB3ZSBrbm93IHRoZSBudW1iZXIgb2YgdHJpcHMgYXQgYSBwYXJ0aWN1bGFyIGhvdXIsIHRoZW4gaXQgd291bGQgYmUgZWFzaWVyIGZvciB1cyB0byBlc3RpbWF0ZSB0aGUgbnVtYmVyIG9mIHJpZGVzIGFyb3VuZCB0aGF0IHRpbWUuIFRvIGFjaGlldmUgdGhhdCwgd2UgY2FsY3VsYXRlZCA2IGRpZmZlcmVudCBmb3JtcyBvZiBsYWcgaG91cnMuIAoKYGBge3IgY2FsY3VsYXRlIHNlcmlhbCBsYWd9CgpiaWtlLnBhbmVsIDwtIAogIGJpa2UucGFuZWwgJT4lIAogIGFycmFuZ2Uoc3RhcnRfc3RhdGlvbl9pZCwgaW50ZXJ2YWw2MCkgJT4lIAogIG11dGF0ZShsYWdIb3VyID0gZHBseXI6OmxhZyhUcmlwX0NvdW50LDEpLAogICAgICAgICBsYWcySG91cnMgPSBkcGx5cjo6bGFnKFRyaXBfQ291bnQsMiksCiAgICAgICAgIGxhZzNIb3VycyA9IGRwbHlyOjpsYWcoVHJpcF9Db3VudCwzKSwKICAgICAgICAgbGFnNEhvdXJzID0gZHBseXI6OmxhZyhUcmlwX0NvdW50LDQpLAogICAgICAgICBsYWcxMkhvdXJzID0gZHBseXI6OmxhZyhUcmlwX0NvdW50LDEyKSwKICAgICAgICAgbGFnMWRheSA9IGRwbHlyOjpsYWcoVHJpcF9Db3VudCwyNCkpICU+JQogICBtdXRhdGUoZGF5ID0geWRheShpbnRlcnZhbDYwKSkKCmBgYAoKVGhlIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIHRoZXNlIGxhZyBob3VycyBhbmQgbnVtYmVyIG9mIHRyaXBzIGFyZSB2aXN1YWxpemVkLiBXZSBzZWUgYSBzdHJvbmcsIGxpbmVhciwgYW5kIG5lZ2F0aXZlIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIGxhZzEyaG91cnMgYW5kIG51bWJlciBvZiB0cmlwcyAxMiBob3VycyBiZWZvcmUuIFRoaXMgbWVhbnMgZm9yIGV4YW1wbGUsIHRoZSBudW1iZXIgb2YgcmlkZXMgd2Ugc2VlIGF0IDVwbSBvbiBhIHBhcnRpY3VsYXIgZGF5IGlzIG5lZ2F0aXZlbHkgY29ycmVsYXRlZCB3aXRoIHRoZSBudW1iZXIgb2YgcmlkZXMgYXQgNWFtIHRoYXQgZGF5LiBJbiBjb250cmFzdCwgd2Ugc2VlIGEgc3Ryb25nLCBsaW5lYXIsIGFuZCBwb3NpdGl2ZSByZWxhdGlvbnNoaXAgYmV0d2VlbiBsYWdob3VyIGFuZCBsYWcxZGF5LiBUaGlzIG1lYW5zIHRoYXQgdGhlIG51bWJlciBvZiByaWRlcyBhdCBhIHBhcnRpY3VsYXIgaG91ciBpcyBoaWdobHkgY29ycmVsYXRlZCB3aXRoIHRoZSBudW1iZXIgb2YgcmlkZXMgaW4gdGhlIGltbWVkaWF0ZSBuZXh0IGhvdXIgYW5kIGF0IHRoZSBzYW1lIHRpbWUgdGhlIGZvbGxvd2luZyBkYXkuIE90aGVyIHRoYW4gdGhhdCwgdGhlIHN0cmVuZ3RoIG9mIHRoZSBjb3JyZWxhdGlvbiBkZWNyZWFzZXMgZm9yIGxhZzJob3VyIGFuZCBsYWczaG91ciwgbWVhbmluZyB0aGF0IHRoZSBudW1iZXIgb2YgcmlkZXMgYXQgNHBtIG9uIGEgcGFydGljdWxhciBkYXksIGlzIG1vcmUgY29ycmVsYXRlZCB3aXRoIHRoZSBudW1iZXIgb2YgcmlkZXMgYXQgNXBtLCByYXRoZXIgdGhhbiBhdCA2IG9yIDcgcG0uIAoKYGBge3IgdmlzdWFsaXplIHNlcmlhbCBjb3JyZWxhdGlvbnMsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9Cgphcy5kYXRhLmZyYW1lKGJpa2UucGFuZWwpICU+JQogICAgZ3JvdXBfYnkoaW50ZXJ2YWw2MCkgJT4lIAogICAgc3VtbWFyaXNlX2F0KHZhcnMoc3RhcnRzX3dpdGgoImxhZyIpLCAiVHJpcF9Db3VudCIpLCBtZWFuLCBuYS5ybSA9IFRSVUUpICU+JQogICAgZ2F0aGVyKFZhcmlhYmxlLCBWYWx1ZSwgLWludGVydmFsNjAsIC1UcmlwX0NvdW50KSAlPiUKICBnZ3Bsb3QoYWVzKHggPSBWYWx1ZSAsIHkgPSBUcmlwX0NvdW50KSkgKwogICAgZ2VvbV9wb2ludChzaXplID0gMSwgY29sb3IgPSAiIzZFMEUwQSIpICsgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxtIiwgc2U9IEZBTFNFLCBjb2xvcj0iIzEyNEU3OCIpICsKICBmYWNldF93cmFwKH5WYXJpYWJsZSwgbmNvbCA9IDYpKwogIGxhYnMoeCA9ICJWYXJpYWJsZSIsIHkgPSAiQ29ycmVsYXRpb24iLCB0aXRsZSA9ICJDb3JyZWxhdGlvbiBCZXR3ZWVuIFNlcmlhbCBMYWcgVHJpcHMgYW5kIFRyaXAgQ291bnQiKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSkgKyAKICB0aGVtZShwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSA5LGZhY2UgPSAiaXRhbGljIiksCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIsIGZhY2UgPSAiYm9sZCIpLCAKICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGF4aXMudGlja3MueCA9IGVsZW1lbnRfYmxhbmsoKSwgCiAgICAgICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF90ZXh0KHNpemU9OCksIAogICAgICAgIGF4aXMudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9MTApLCAKICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfcmVjdChjb2xvdXIgPSAiZ3JleSIsIGZpbGw9TkEsIHNpemU9MC44KSkKCmBgYAoKIyMgU3BhdGlhbCBDb3JyZWxhdGlvbgoKV2UndmUgc2VlbiBiZWZvcmUgdGhhdCBzcGF0aWFsIGF1dG9jb3JyZWxhdGlvbiBleGlzdHMgaGVyZSBpbiB0aGF0IHRoZSBudW1iZXIgb2YgdHJpcHMgYXQgYSBwYXJ0aWN1bGFyIHN0YXRpb24gZHVyaW5nIGEgcGFydGljdWxhciB0aW1lIGlzIGNvcnJlbGF0ZWQgd2l0aCB0aGUgbnVtYmVyIG9mIHRyaXBzIGF0IG5lYXJieSBzdGF0aW9ucy4gVGhpcyBtZWFucyB0aGF0IGlmIHdlIGtub3cgdGhlIG51bWJlciBvZiB0cmlwcyBhdCBhIHN0YXRpb24sIHdlIG1heSBiZSBhYmxlIHRvIGVzdGltYXRlIHRoZSBudW1iZXIgb2YgdHJpcHMgYXQgdGhlIHN0YXRpb24gbmV4dCB0byBpdC4gRG8gYWNoaWV2ZSB0aGF0LCB3ZSBjcmVhdGVkIGEgbmV3IGRhdGFmcmFtZSBjYWxsZWQgYGxhZ19yZXN1bHRgIGFuZCB3cm90ZSBhIG5lc3RlZCBsb29wIHRvIGxvb3AgdGhyb3VnaCBlYWNoIGhvdXIgaW4gQXVndXN0IDIwMjMuIEZvciBlYWNoIGhvdXIsIHdlIHRoZW4gY2FsY3VsYXRlIHRoZSBuZWFyZXN0IDUgbmVpZ2hib3Igb2YgZWFjaCBzdGF0aW9uIGFuZCB0aGUgYXZlcmFnZSBvZiByaWRlcyBmb3IgdGhhdCA1IHN0YXRpb25zLiBUaGUgcmVzdWx0cyB3ZXJlIGpvaW5lZCBiYWNrIHRvIG91ciBwYW5lbCBkYXRhc2V0LiAKCmBgYHtyIGNhbGN1bGF0ZSBzcGF0aWFsIGxhZywgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KCmxhZ19yZXN1bHQgPC0gZGF0YS5mcmFtZSgpCgpmb3IgKGQgaW4gMjEzOjI0MykgewogIGZvciAoaCBpbiAwOjIzKSB7CgpzcGFjZWxhZyA8LSBiaWtlLnBhbmVsICU+JSAKICBtdXRhdGUoaG91ciA9IGhvdXIoaW50ZXJ2YWw2MCkpICU+JSAKICBmaWx0ZXIoZGF5ID09IGQgJiBob3VyID09IGgpICU+JSAKICBzdF9hc19zZiguLCBjb29yZHMgPSBjKCJzdGFydF9sbmciLCAic3RhcnRfbGF0IiksIGNycyA9IDQzMjYpIAoKCmNvb3Jkcy50ZXN0X2xhZyA8LSAgc3RfY29vcmRpbmF0ZXMoc3BhY2VsYWcpIApuZWlnaGJvckxpc3QudGVzdF9sYWcgPC0ga25uMm5iKGtuZWFybmVpZ2goY29vcmRzLnRlc3RfbGFnLCA1KSkKc3BhdGlhbFdlaWdodHMudGVzdF9sYWcgPC0gbmIybGlzdHcobmVpZ2hib3JMaXN0LnRlc3RfbGFnLCBzdHlsZT0iVyIpCgpzcGFjZWxhZyA8LSBzcGFjZWxhZyAlPiUgbXV0YXRlKGxhZ1RyaXAgPSBsYWcubGlzdHcoc3BhdGlhbFdlaWdodHMudGVzdF9sYWcsIFRyaXBfQ291bnQpKQoKbGFnX3Jlc3VsdCA8LSByYmluZChsYWdfcmVzdWx0LCBzcGFjZWxhZykKICB9CiAgCn0KCmJpa2UucGFuZWwgPC0gbGFnX3Jlc3VsdCAlPiUgCiAgbXV0YXRlKHN0YXJ0X2xuZyA9IHVubGlzdChtYXAoZ2VvbWV0cnksIDEpKSwKICAgICAgICAgc3RhcnRfbGF0ID0gdW5saXN0KG1hcChnZW9tZXRyeSwgMikpKSAlPiUgCiBzdF9kcm9wX2dlb21ldHJ5KCkgCiAgCiAgCmBgYAoKVGhlIGZpZ3VyZSBiZWxvdyBzaG93cyB0aGF0IGZvciBhbGwgd2Vla3MgaW4gQXVndXN0LCB0aGVyZSdzIGEgc3Ryb25nLCBsaW5lYXIsIGFuZCBwb3NpdGl2ZSByZWxhdGlvbnNoaXAgYmV0d2VlbiB0aGUgbnVtYmVyIG9mIHRyaXBzIGFuZCB0aGUgbnVtYmVyIG9mIHRyaXBzIGluIG5lYXJieSBzdGF0aW9ucy4gV2Ugd2lsbCB0aGVyZWZvcmUgaW5jbHVkZSB0aGlzIHZhcmlhYmxlIGluIG91ciBmb2xsb3dpbmcgcmVncmVzc2lvbiBhbmFseXNpcy4gCgpgYGB7ciB2aXN1YWxpemUgc3BhdGlhbCBjb3JyZWxhdGlvbiwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KCmJpa2UucGFuZWwgJT4lCiAgZ3JvdXBfYnkoaW50ZXJ2YWw2MCkgJT4lIAogIHN1bW1hcml6ZShUcmlwX0NvdW50ID0gbWVhbihUcmlwX0NvdW50KSwKICAgICAgICAgICAgTGFnX0NvdW50ID0gbWVhbihsYWdUcmlwKSkgJT4lCiAgbXV0YXRlKHdlZWsgPSB3ZWVrKGludGVydmFsNjApKSAlPiUKICBnZ3Bsb3QoYWVzKExhZ19Db3VudCwgVHJpcF9Db3VudCkpICsgCiAgICBnZW9tX3BvaW50KHNpemUgPSAxLCBjb2xvciA9ICIjNkUwRTBBIikgKyBnZW9tX3Ntb290aChtZXRob2QgPSAibG0iLCBzZT0gRkFMU0UsIGNvbG9yPSIjMTI0RTc4IikgKwogICAgZmFjZXRfd3JhcCh+d2VlaywgbmNvbD04KSArIAogICAgbGFicyh0aXRsZT0iQ29ycmVsYXRpb24gQmV0d2VlbiBTcGF0aWFsIExhZyBUcmlwcyBhbmQgVHJpcCBDb3VudCIsCiAgICAgICAgIHg9IkxhZyBUcmlwIENvdW50IiwgeT0iTWVhbiBUcmlwIENvdW50IikgKyAKICB0aGVtZShwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSA5LGZhY2UgPSAiaXRhbGljIiksCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIsIGZhY2UgPSAiYm9sZCIpLCAKICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgIGF4aXMudGlja3MueCA9IGVsZW1lbnRfYmxhbmsoKSwgCiAgICAgICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF90ZXh0KHNpemU9OCksIAogICAgICAgIGF4aXMudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9MTApLCAKICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfcmVjdChjb2xvdXIgPSAiZ3JleSIsIGZpbGw9TkEsIHNpemU9MC44KSkKCmBgYAoKCiMgT0xTIFJlZ3Jlc3Npb24KCldlIHNwbGl0IG91ciA1IHdlZWsgZGF0YSBpbnRvIHRyYWluaW5nIHNldCwgd2hpY2ggaW5jbHVkZXMgMyB3ZWVrcyBhbmQgdGVzdGluZyBzZXQsIHdoaWNoIGluY2x1ZGUgMiB3ZWVrcy4gQWZ0ZXIgdGhhdCwgZm91ciBkaWZmZXJlbnQgbGluZWFyIHJlZ3Jlc3Npb24gYXJlIGVzdGltYWVkIG9uIHRyYWluaW5nIGRhdGEsIGVhY2ggd2l0aCBkaWZmZXJlbnQgZml4ZWQgZWZmZWN0cy4gCgotIGByZWcxYCBmb2N1c2VzIG9uIGp1c3QgdGltZSwgaW5jbHVkaW5nIGhvdXIgZml4ZWQgZWZmZWN0cywgZGF5IG9mIHRoZSB3ZWVrLCBXaW5kIFNwZWVkIGFuZCBUZW1wZXJhdHVyZS4KLSBgcmVnMmAgZm9jdXNlcyBvbiBqdXN0IHNwYWNlLCBpbmNsdWRpbmcgc3RhdGlvbiBhbmQgd2VhdGhlciBjb25kaXRpb25zLiAKLSBgcmVnM2AgZm9jdXNlcyBvbiB0aGUgY29tYmluZWQgZWZmZWN0IG9mIHNwYWNlIGFuZCB0aW1lCi0gYHJlZzRgIGZvY3VzZXMgYWRkcyB0aGUgdGltZSBhbmQgc3BhY2UgbGFnIGZlYXR1cmVzCgoKYGBge3IgZml0dGluZyByZWdyZXNzaW9ufQoKYmlrZS5UcmFpbiA8LSBmaWx0ZXIoYmlrZS5wYW5lbCwgd2VlayA8PSAzMykKYmlrZS5UZXN0IDwtIGZpbHRlcihiaWtlLnBhbmVsLCB3ZWVrID4gMzMpCgojIFRpbWUKcmVnMSA8LSAKICBsbShUcmlwX0NvdW50IH4gIGhvdXIoaW50ZXJ2YWw2MCkgKyBkb3R3ICsgVGVtcGVyYXR1cmUgKyBXaW5kX1NwZWVkLCBkYXRhPWJpa2UuVHJhaW4pCgojIFNwYWNlCnJlZzIgPC0gCiAgbG0oVHJpcF9Db3VudCB+ICBzdGFydF9zdGF0aW9uX25hbWUgKyBUZW1wZXJhdHVyZSArIFdpbmRfU3BlZWQsICBkYXRhPWJpa2UuVHJhaW4pCgojIFRpbWUgYW5kIFNwYWNlCnJlZzMgPC0gCiAgbG0oVHJpcF9Db3VudCB+ICBzdGFydF9zdGF0aW9uX25hbWUgKyBob3VyKGludGVydmFsNjApICsgZG90dyArIFRlbXBlcmF0dXJlICsgV2luZF9TcGVlZCwgCiAgICAgZGF0YT1iaWtlLlRyYWluKQoKIyBUaW1lIGFuZCBTcGFjZSBhbmQgVGltZUxhZyBhbmQgU3BhY2VMYWcKcmVnNCA8LSAKICBsbShUcmlwX0NvdW50IH4gIHN0YXJ0X3N0YXRpb25fbmFtZSArICBob3VyKGludGVydmFsNjApICsgZG90dyArIFRlbXBlcmF0dXJlICsgV2luZF9TcGVlZCArCiAgICAgICAgICAgICAgICAgICBsYWdIb3VyICsgbGFnMkhvdXJzICsgbGFnMTJIb3VycyArIGxhZzFkYXkgKyBsYWdUcmlwLCAKICAgICBkYXRhPWJpa2UuVHJhaW4pCmBgYAoKIyMgTWFraW5nIFByZWRpY3Rpb25zCgpUaGUgcHVycnIgZmFtaWx5IG9mIGZ1bmN0aW9ucyB3aWxsIGJlIHVzZWQgdG8gbG9vcCB0aHJvdWdoIGEgc2V0IG9mICduZXN0ZWQnIGRhdGEgZnJhbWVzIHRvIGVmZmljaWVudGx5IGNvbXBhcmUgYWNyb3NzIGRpZmZlcmVudCBtb2RlbCBzcGVjaWZpY2F0aW9ucy4gQmVmb3JlIHRlc3Rpbmcgb3VyIGRhdGEsIHdlIG5lc3RlZCBvdXIgZGF0YSBieSB3ZWVrIGFuZCBjcmVhdGVkIGEgbW9kZWwgZnVuY3Rpb24uIFRoZSBmaXJzdCBzZXQgb2YgY29kZSBzZXBhcmF0ZSBvdXIgdGVzdGluZyBkYXRhIGludG8gdHdvIHRpYmJsZXMuIFRoZSBzbWFsbCBmdW5jdGlvbiBpcyBjcmVhdGVkIHRoYXQgdGFrZXMgYSB0aWJibGUsIGRhdCBhbmQgYSByZWdyZXNzaW9uIG1vZGVsLCBmaXQgYXMgaXRzIGlucHV0cywgYW5kIG91dHB1dHMgcHJlZGljdGlvbnMgYXMgcHJlZC4gVGhpcyBmdW5jdGlvbiBpcyB1c2VkIHNpbXBseSBmb3IgcHJlZGljdGlvbi4gCgpgYGB7ciBuZXN0IGRhdGEgYW5kIHByZWRpY3QgZnVuY3Rpb24sIHdhcm5pbmc9RkFMU0V9CgpiaWtlLlRlc3Qud2Vla05lc3QgPC0gCiAgYmlrZS5UZXN0ICU+JQogIG5lc3QoLXdlZWspIAoKbW9kZWxfcHJlZCA8LSBmdW5jdGlvbihkYXQsIGZpdCl7CiAgIHByZWQgPC0gcHJlZGljdChmaXQsIG5ld2RhdGEgPSBkYXQpfQoKYGBgCgoKVGhlIG5lc3RlZCBmb3JtYXQgYWxsb3dzIG9uZSB0byBsb29wIHRocm91Z2ggZWFjaCBtb2RlbCBmb3IgZWFjaCB3ZWVrIGFuZCBtdXRhdGUgc3VtbWFyeSBzdGF0aXN0aWNzLiBJbiB0aGUgY29kZSBibG9jayBiZWxvdywgd2Vla19wcmVkaWN0aW9ucyBhcmUgY2FsY3VsYXRlZCBmb3IgZWFjaCB3ZWVrIGluIHRoZSB0ZXN0aW5nIHNldC4gVGhlIG1hcCBmdW5jdGlvbiBhcHBsaWVzIHRoZSBgbW9kZWxfcHJlZGAgZnVuY3Rpb24gdG8gZWFjaCBuZXN0ZWQgdGliYmxlLiBUYWtlIHRoZSBmaXJzdCBsaW5lIG9mIHRoZSBiZWxvdyBtdXRhdGUsIGZvciBleGFtcGxlLiBBIG5ldyBjb2x1bW4sIFRpbWVfRkUsIGluY2x1ZGVzIHByZWRpY3Rpb25zIGZvciByZWcxIC0gdGhlIHRpbWUgZml4ZWQgZWZmZWN0cyBtb2RlbC4gVGhlIHByZWRpY3Rpb25zIGFyZSBjcmVhdGVkIGJ5IG1hcHBpbmcgdGhlIGZ1bmN0aW9uLCBtb2RlbF9wcmVkLCB0byBlYWNoIHJvdyBvZiBkYXRhLCBwYXJhbWV0ZXJpemluZyBmaXQgYXMgdGhlIHJlZzEgbW9kZWwuCgpgYGB7ciBtYWtpbmcgcHJlZGljdGlvbnMsIHdhcm5pbmc9RkFMU0V9Cgp3ZWVrX3ByZWRpY3Rpb25zIDwtIAogIGJpa2UuVGVzdC53ZWVrTmVzdCAlPiUgCiAgICBtdXRhdGUoQVRpbWVfRkUgPSBtYXAoLnggPSBkYXRhLCBmaXQgPSByZWcxLCAuZiA9IG1vZGVsX3ByZWQpLAogICAgICAgICAgIEJTcGFjZV9GRSA9IG1hcCgueCA9IGRhdGEsIGZpdCA9IHJlZzIsIC5mID0gbW9kZWxfcHJlZCksCiAgICAgICAgICAgQ1RpbWVfU3BhY2VfRkUgPSBtYXAoLnggPSBkYXRhLCBmaXQgPSByZWczLCAuZiA9IG1vZGVsX3ByZWQpLAogICAgICAgICAgIERUaW1lX1NwYWNlX0ZFX0xhZ3MgPSBtYXAoLnggPSBkYXRhLCBmaXQgPSByZWc0LCAuZiA9IG1vZGVsX3ByZWQpKSAlPiUgCiAgICBnYXRoZXIoUmVncmVzc2lvbiwgUHJlZGljdGlvbiwgLWRhdGEsIC13ZWVrKSAlPiUKICAgIG11dGF0ZShPYnNlcnZlZCA9IG1hcChkYXRhLCBwdWxsLCBUcmlwX0NvdW50KSwKICAgICAgICAgICBBYnNvbHV0ZV9FcnJvciA9IG1hcDIoT2JzZXJ2ZWQsIFByZWRpY3Rpb24sICB+IGFicygueCAtIC55KSksCiAgICAgICAgICAgTUFFID0gbWFwX2RibChBYnNvbHV0ZV9FcnJvciwgbWVhbiwgbmEucm0gPSBUUlVFKSwKICAgICAgICAgICBzZF9BRSA9IG1hcF9kYmwoQWJzb2x1dGVfRXJyb3IsIHNkLCBuYS5ybSA9IFRSVUUpKQoKYGBgCgojIyBFcnJvciBBbmFseXNpcwoKQ29tcGFyaW5nIHRoZSBtZWFuIGFic29sdXRlIGVycm9ycyBmb3Igb3VyIGZvdXIgbW9kZWxzLCB3ZSBmaW5kIHRoZSBlcnJvciB0byBiZSB0aGUgaGlnZXN0IGZvciBtb2RlbCAxLCB3aGljaCBvbmx5IHRha2VzIHRpbWUgZWZmZWN0IGludG8gYWNjb3VudC4gTW9kZWwgMiBhbmQgMyBhY2NvdW50IGZvciBzcGFjZSBhbmQgc3BhY2UgKyB0aW1lIHJlc3BlY3RpdmVseSwgd2hpY2ggbGVhZCB0byBhbiBpbmNyZWFzZSBpbiBhY2N1cmFjeSBsZXZlbC4gQWRkaW5nIHNwYWNlIGxhZyBhbmQgdGltZSBsYWcsIGhvd2V2ZXIsIHdpbGwgZ3JlYXRseSBpbXByb3ZlIHRoZSBhY2N1cmFjeSBvZiBvdXIgbW9kZS4gV2UgYWxzbyByYW4gdGhlIHZhcmlhbmNlIG9mIGluZmxhdGlvbiBmdW5jdGlvbiBpbiB0aGUgYmFja2dyb3VuZCBhbmQgY2hlY2tlZCB0byBtYWtlIHN1cmUgdGhhdCB0aGVyZSdzIG5vIGNvbGluZXJhcml0eSBpbiBtb2RlbCA0LiAKCmBgYHtyIHBsb3QgZXJyb3JzfQoKd2Vla19wcmVkaWN0aW9ucyAlPiUKICBkcGx5cjo6c2VsZWN0KHdlZWssIFJlZ3Jlc3Npb24sIE1BRSkgJT4lCiAgZ2F0aGVyKFZhcmlhYmxlLCBNQUUsIC1SZWdyZXNzaW9uLCAtd2VlaykgJT4lCiAgZ2dwbG90KGFlcyh3ZWVrLCBNQUUpKSArIAogICAgZ2VvbV9iYXIoYWVzKGZpbGwgPSBSZWdyZXNzaW9uKSwgcG9zaXRpb24gPSAiZG9kZ2UiLCBzdGF0PSJpZGVudGl0eSIpICsKICAgIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IHBhbGV0dGU0KSArCiAgICBsYWJzKHRpdGxlID0gIk1lYW4gQWJzb2x1dGUgRXJyb3JzIGJ5IE1vZGVsIFNwZWNpZmljYXRpb24gYW5kIFdlZWsiKSArCiAgdGhlbWUocGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gOSxmYWNlID0gIml0YWxpYyIpLAogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSwgCiAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCksCiAgICBheGlzLnRpY2tzLnggPSBlbGVtZW50X2JsYW5rKCksIAogICAgICAgIGF4aXMudGV4dC55PWVsZW1lbnRfdGV4dChzaXplPTgpLCAKICAgICAgICBheGlzLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTEwKSwgCiAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X3JlY3QoY29sb3VyID0gImdyZXkiLCBmaWxsPU5BLCBzaXplPTAuOCkpCgpgYGAKVGhlIGZpZ3VyZSBiZWxvdyBmdXJ0aGVyIGRlbW9uc3RyYXRlcyB0aGUgYWNjdXJhY3kgb2YgZWFjaCBtb2RlbCBieSBjb21wYXJpbmcgcHJlZGljdGVkIHRvIG9ic2VydmVkIG51bWJlciBvZiB0cmlwcy4gTW9kZWwgNCBjbGVhcmx5IHN0YW5kcyBvdXQgYW1vbmcgdGhlIDQgbW9kZWxzIHRvIGhhdmUgaGlnaGVyIHByZWRpY3RpbmcgcG93ZXIgb2YgYmlrZXNoYXJlIGRlbWFuZC4gCgpgYGB7ciBjb21wYXJpbmcgcHJlZGljdGVkIGFuZCBvYnNlcnZlZCwgZmlnLmhlaWdodD04LCBmaWcud2lkdGg9MTEsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGNhY2hlPVRSVUV9Cgp3ZWVrX3ByZWRpY3Rpb25zICU+JSAKICAgIG11dGF0ZShpbnRlcnZhbDYwID0gbWFwKGRhdGEsIHB1bGwsIGludGVydmFsNjApLAogICAgICAgICAgIHN0YXJ0X3N0YXRpb25faWQgPSBtYXAoZGF0YSwgcHVsbCwgc3RhcnRfc3RhdGlvbl9pZCkpICU+JQogICAgZHBseXI6OnNlbGVjdChpbnRlcnZhbDYwLCBzdGFydF9zdGF0aW9uX2lkLCBPYnNlcnZlZCwgUHJlZGljdGlvbiwgUmVncmVzc2lvbikgJT4lCiAgICB1bm5lc3QoKSAlPiUKICAgIGdhdGhlcihWYXJpYWJsZSwgVmFsdWUsIC1SZWdyZXNzaW9uLCAtaW50ZXJ2YWw2MCwgLXN0YXJ0X3N0YXRpb25faWQpICU+JQogICAgZ3JvdXBfYnkoUmVncmVzc2lvbiwgVmFyaWFibGUsIGludGVydmFsNjApICU+JQogICAgc3VtbWFyaXplKFZhbHVlID0gc3VtKFZhbHVlKSkgJT4lCiAgICBnZ3Bsb3QoYWVzKGludGVydmFsNjAsIFZhbHVlLCBjb2xvdXI9VmFyaWFibGUpKSArIAogICAgICBnZW9tX2xpbmUoc2l6ZSA9IDEuMSkgKyAKICAgICAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcz1wYWxldHRlMikrCiAgICAgIGZhY2V0X3dyYXAoflJlZ3Jlc3Npb24sIG5jb2w9MSkgKwogICAgICBsYWJzKHRpdGxlID0gIlByZWRpY3RlZC9PYnNlcnZlZCBCaWtlIFNoYXJlIFRpbWUgU2VyaWVzIiwgc3VidGl0bGUgPSAiSmVyc2V5IENpdHk6IGEgdGVzdCBzZXQgb2YgMiB3ZWVrcyIsICB4ID0gIkhvdXIiLCB5PSAiU3RhdGlvbiBUcmlwcyIpICsgCiAgIHRoZW1lKHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDksZmFjZSA9ICJpdGFsaWMiKSwKICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgZmFjZSA9ICJib2xkIiksIAogICAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpLAogICAgYXhpcy50aWNrcy54ID0gZWxlbWVudF9ibGFuaygpLCAKICAgICAgICBheGlzLnRleHQueT1lbGVtZW50X3RleHQoc2l6ZT04KSwgCiAgICAgICAgYXhpcy50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT0xMCksIAogICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9yZWN0KGNvbG91ciA9ICJncmV5IiwgZmlsbD1OQSwgc2l6ZT0wLjgpKQoKYGBgCgpXZSBhbHNvIG5lZWQgdG8gY2hlY2sgaWYgb3VyIHByZWRpY3Rpb25zIGdlbmVyYWxpemUgYWNyb3NzIHNwYWNlIGFuZCB0aW1lLiBUbyBkbyB0aGF0LCB3ZSBtYXBwZWQgb3VyIG1lYW4gYWJzb2x1dGUgZXJyb3Igb2YgbW9kZWwgNCBhY3Jvc3Mgc3BhY2UuIFdlIGNhbiBzZWUgdGhhdCB0aGlzIG1vZGVsIGRvZXMgbm90IGRvIGEgYmV0dGVyIGpvYiBpbiBwcmVkaWN0aW5nIG51bWJlciBvZiByaWRlcyBhdCBzdGF0aW9ucyBhbG9uZyB0aGUgSHVkc29uIFJpdmVyLCB3aGljaCBhcmUgdHlwaWNhbGx5IHN0YXRpb25zIHdpdGggaGlnaGVyIGJpa2VzaGFyZSBkZW1hbmRzIGR1cmluZyBib3RoIHdlZWtlbmQgYW5kIHdlZWtkYXlzLiBPdGhlciB0aGFuIHRoYXQsIHRoaXMgbW9kZWwgZG9lcyBhIHByZXR0eSBnb29kIGpvYiBpbiBwcmVkaWN0aW5nIGJpa2VzaGFyZSBkZW1hbmRzIGF0IHN0YXRpb25zIHdpdGggbG93ZXIgbnVtYmVyIG9mIHRyaXBzIGluIEF1Z3VzdC4gCgpgYGB7ciBlcnJvciBieSBzdGF0aW9uLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFLCBjYWNoZT1UUlVFfQoKd2Vla19wcmVkaWN0aW9ucyAlPiUgCiAgICBtdXRhdGUoaW50ZXJ2YWw2MCA9IG1hcChkYXRhLCBwdWxsLCBpbnRlcnZhbDYwKSwKICAgICAgICAgICBzdGFydF9zdGF0aW9uX2lkID0gbWFwKGRhdGEsIHB1bGwsIHN0YXJ0X3N0YXRpb25faWQpLCAKICAgICAgICAgICBzdGFydF9sYXRpdHVkZSA9IG1hcChkYXRhLCBwdWxsLCBzdGFydF9sYXQpLCAKICAgICAgICAgICBzdGFydF9sb25naXR1ZGUgPSBtYXAoZGF0YSwgcHVsbCwgc3RhcnRfbG5nKSkgJT4lCiAgICBzZWxlY3QoaW50ZXJ2YWw2MCwgc3RhcnRfc3RhdGlvbl9pZCwgc3RhcnRfbG9uZ2l0dWRlLCBzdGFydF9sYXRpdHVkZSwgT2JzZXJ2ZWQsIFByZWRpY3Rpb24sIFJlZ3Jlc3Npb24pICU+JQogICAgdW5uZXN0KCkgJT4lCiAgZmlsdGVyKFJlZ3Jlc3Npb24gPT0gIkRUaW1lX1NwYWNlX0ZFX0xhZ3MiKSAlPiUKICBncm91cF9ieShzdGFydF9zdGF0aW9uX2lkLCBzdGFydF9sb25naXR1ZGUsIHN0YXJ0X2xhdGl0dWRlKSAlPiUKICBzdW1tYXJpemUoTUFFID0gbWVhbihhYnMoT2JzZXJ2ZWQtUHJlZGljdGlvbiksIG5hLnJtID0gVFJVRSkpJT4lCmdncGxvdCguKSsKICBnZW9tX3NmKGRhdGEgPSBqY1RyYWN0cywgIGZpbGwgPSAid2hpdGUiKSsKICBnZW9tX3BvaW50KGFlcyh4ID0gc3RhcnRfbG9uZ2l0dWRlLCB5ID0gc3RhcnRfbGF0aXR1ZGUsIGNvbG9yID0gTUFFKSwgCiAgICAgICAgICAgICBmaWxsID0gInRyYW5zcGFyZW50IikrCiAgc2NhbGVfY29sb3JfY29udGludW91cyhsb3cgPSAiIzEyNEU3OCIsIGhpZ2ggPSAiIzZFMEUwQSIsIG5hbWU9ICJNQUUiKSsKIHlsaW0obWluKGJpa2VfY2Vuc3VzJHN0YXJ0X2xhdCksIG1heChiaWtlX2NlbnN1cyRzdGFydF9sYXQpKSsKICB4bGltKG1pbihiaWtlX2NlbnN1cyRzdGFydF9sbmcpLCBtYXgoYmlrZV9jZW5zdXMkc3RhcnRfbG5nKSkgKwogIGxhYnModGl0bGU9Ik1lYW4gQWJzIEVycm9yLCBUZXN0IFNldCwgTW9kZWwgNCIpICsgCiAgdGhlbWUoYXhpcy50ZXh0Lng9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGV4dC55PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpY2tzID1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGl0bGUueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSA5LGZhY2UgPSAiaXRhbGljIiksCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIsIGZhY2UgPSAiYm9sZCIpLAogICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9yZWN0KGNvbG91ciA9ICJncmV5IiwgZmlsbD1OQSwgbGluZXdpZHRoPTAuOCkKICAgICAgICApCgpgYGAKQ29tcGFyaW5nIGFjcm9zcyBzcGFjZSBhbmQgdGltZSwgdGhlIGZpZ3VyZSBiZWxvdyBzaG93cyB0aGF0IG1vZGVsIDQgaGFzIGJlZW4gcHJldHR5IGNvbnNpc3RlbnQgaW4gcHJlZGljdGluZyBudW1iZXIgb2YgdHJpcHMgYWNyb3NzIHRpbWUuIFN0YXRpb25zIHRoYXQgYXJlIHByZWRpY3RlZCB0byBoYXZlIGhpZ2hlciBkZW1hbmQgaW4gdGhlIHdlZWtlbmQgd2lsbCBjb250aW51ZSB0byBiZSBwcmVkaWN0ZWQgdG8gaGF2ZSBoaWdoZXIgZGVtYW5kIGR1cmluZyB0aGUgd2Vla2RheXMuIEVycm9ycyBjYXJyeSB0aHJvdWdob3V0IHRpbWUuIFRoaXMgaW1wbGllcyB0aGF0IHN0YXRpb25zIHRoYXQgaGlzdG9yaWNhbGx5IGhhdmUgaGlnaGVyIGRlbWFuZCBoYXZlIGhpZ2hlciB2YXJpYWJpbGl0eSBpbiB0aGUgbnVtYmVyIG9mIHJpZGVzIGFuZCBhcmUgbW9yZSBkaWZmaWN1bHQgdG8gcHJlZGljdCB3aXRoIHRoZSBjdXJyZW50IHByZWRpY3RvcnMgd2UgaGF2ZS4gSW4gZmFjdCwgb3VyIGN1cnJlbnQgcHJlZGljdG9ycyBvbmx5IGV4cGxhaW5zIDUwLjEyJSBvZiB0aGUgdmFyaWFiaWxpdHkuIFdoYXQncyBwcm9ibGVtYXRpYyBhYm91dCB0aGlzIGlzIHRoYXQgaWYgd2UgY29udGludWUgdG8gaGF2ZSBsYXJnZXIgZXJyb3JzIHdoZW4gcHJlZGljdGluZyBzdGF0aW9ucyB3aXRoIGhpZ2hlciBiaWtlc2hhcmUgZGVtYW5kLCB3ZSB3aWxsIGxhcmdlbHkgZmFpbCB0byBtZWV0IHRoZSBkZW1hbmQgb2YgYmlrZSB1c2VycyBhdCB0aGVzZSBzdGF0aW9ucy4gCgpgYGB7ciBjb21wYXJpbmcgZXJyb3JzIGFjcm9zcyBzcGFjZSBhbmQgdGltZSwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRSwgY2FjaGU9VFJVRX0KCndlZWtfcHJlZGljdGlvbnMgJT4lIAogICAgbXV0YXRlKGludGVydmFsNjAgPSBtYXAoZGF0YSwgcHVsbCwgaW50ZXJ2YWw2MCksCiAgICAgICAgICAgc3RhcnRfc3RhdGlvbl9pZCA9IG1hcChkYXRhLCBwdWxsLCBzdGFydF9zdGF0aW9uX2lkKSwgCiAgICAgICAgICAgc3RhcnRfbGF0aXR1ZGUgPSBtYXAoZGF0YSwgcHVsbCwgc3RhcnRfbGF0KSwgCiAgICAgICAgICAgc3RhcnRfbG9uZ2l0dWRlID0gbWFwKGRhdGEsIHB1bGwsIHN0YXJ0X2xuZyksCiAgICAgICAgICAgZG90dyA9IG1hcChkYXRhLCBwdWxsLCBkb3R3KSApICU+JQogICAgc2VsZWN0KGludGVydmFsNjAsIHN0YXJ0X3N0YXRpb25faWQsIHN0YXJ0X2xvbmdpdHVkZSwgCiAgICAgICAgICAgc3RhcnRfbGF0aXR1ZGUsIE9ic2VydmVkLCBQcmVkaWN0aW9uLCBSZWdyZXNzaW9uLAogICAgICAgICAgIGRvdHcpICU+JQogICAgdW5uZXN0KCkgJT4lCiAgZmlsdGVyKFJlZ3Jlc3Npb24gPT0gIkRUaW1lX1NwYWNlX0ZFX0xhZ3MiKSU+JQogIG11dGF0ZSh3ZWVrZW5kID0gaWZlbHNlKGRvdHcgJWluJSBjKCJTdW4iLCAiU2F0IiksICJXZWVrZW5kIiwgIldlZWtkYXkiKSwKICAgICAgICAgdGltZV9vZl9kYXkgPSBjYXNlX3doZW4oaG91cihpbnRlcnZhbDYwKSA8IDcgfCBob3VyKGludGVydmFsNjApID4gMTggfiAiT3Zlcm5pZ2h0IiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaG91cihpbnRlcnZhbDYwKSA+PSA3ICYgaG91cihpbnRlcnZhbDYwKSA8IDEwIH4gIkFNIFJ1c2giLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBob3VyKGludGVydmFsNjApID49IDEwICYgaG91cihpbnRlcnZhbDYwKSA8IDE1IH4gIk1pZC1EYXkiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBob3VyKGludGVydmFsNjApID49IDE1ICYgaG91cihpbnRlcnZhbDYwKSA8PSAxOCB+ICJQTSBSdXNoIikpICU+JQogIGdyb3VwX2J5KHN0YXJ0X3N0YXRpb25faWQsIHdlZWtlbmQsIHRpbWVfb2ZfZGF5LCBzdGFydF9sb25naXR1ZGUsIHN0YXJ0X2xhdGl0dWRlKSAlPiUKICBzdW1tYXJpemUoTUFFID0gbWVhbihhYnMoT2JzZXJ2ZWQtUHJlZGljdGlvbiksIG5hLnJtID0gVFJVRSkpJT4lCiAgZ2dwbG90KC4pKwogIGdlb21fc2YoZGF0YSA9IGpjVHJhY3RzLCBjb2xvciA9ICJncmV5IiwgZmlsbCA9ICJ0cmFuc3BhcmVudCIpKwogIGdlb21fcG9pbnQoYWVzKHggPSBzdGFydF9sb25naXR1ZGUsIHkgPSBzdGFydF9sYXRpdHVkZSwgY29sb3IgPSBNQUUpLCAKICAgICAgICAgICAgIGZpbGwgPSAidHJhbnNwYXJlbnQiLCBzaXplID0gMC41KSsKICBzY2FsZV9jb2xvcl9jb250aW51b3VzKGxvdyA9ICIjMTI0RTc4IiwgaGlnaCA9ICIjNkUwRTBBIiwgbmFtZT0gIk1BRSIpKwogeWxpbShtaW4oYmlrZV9jZW5zdXMkc3RhcnRfbGF0KSwgbWF4KGJpa2VfY2Vuc3VzJHN0YXJ0X2xhdCkpKwogIHhsaW0obWluKGJpa2VfY2Vuc3VzJHN0YXJ0X2xuZyksIG1heChiaWtlX2NlbnN1cyRzdGFydF9sbmcpKSArCiAgZmFjZXRfZ3JpZCh3ZWVrZW5kfnRpbWVfb2ZfZGF5KSsKICBsYWJzKHRpdGxlPSJNZWFuIEFic29sdXRlIEVycm9ycyBieSBUaW1lLCBUZXN0IFNldCwgTW9kZWwgNCIpICsgCiAgdGhlbWUoYXhpcy50ZXh0Lng9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGV4dC55PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBheGlzLnRpY2tzID1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGl0bGUueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICBwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSA5LGZhY2UgPSAiaXRhbGljIiksCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIsIGZhY2UgPSAiYm9sZCIpLAogICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9yZWN0KGNvbG91ciA9ICJncmV5IiwgZmlsbD1OQSwgbGluZXdpZHRoPTAuOCkKICAgICAgICApCmBgYAoKCiMjIENyb3NzLVZhbGlkYXRpb24KCkZpbmFsbHksIHdlIHBlcmZvcm1lZCAxMDAgY3Jvc3MtdmFsaWRhdGlvbiB0ZXN0cyBmb3IgYWxsIGZpdmUgd2Vla3Mgb2YgZGF0YSB1c2luZyBtb2RlbCA0LiBUaGUgTUFFIHZhbHVlIGlzIDAuNDQsIGluZGljYXRpbmcgdGhhdCBkZXNwaXRlIGJlaW5nIG91ciBiZXN0IHNvbHV0aW9uIHNvIGZhciwgdGhlIG1vZGVsJ3MgcHJlZGljdGlvbiBlcnJvciBjYW4gc3RpbGwgYmUgcmVsYXRpdmVseSBsYXJnZS4gSXQgd2lsbCBhbHNvIGJlIGdvb2QgcHJhY3RpY2UgdG8gY3Jvc3MgdmFsaWRhdGUgYWNyb3NzIHNvY2lvLWVjb25vbWljIGluZGljYXRvcnMgdG8gc2VlIGlmIGJpa2Ugc3RhdGlvbnMnIGRlbWFuZCB3aXRoaW4gY2VydGFpbiBuZWlnaGJvcmhvb2RzIGFyZSBkaXNwcm9wb3J0aW9uYXRlbHkgdW5kZXIvb3ZlciBwcmVkaWN0ZWQsIGJ1dCBvdXIgbW9kZWwgY3VycmVudCBkb2VzIG5vdCB0YWtlIGludG8gYWNjb3VudCBhbnkgc29jaW9lY29ub21pYyB2YXJpYWJsZXMgYXMgcHJlZGljdG9yIHZhcmlhYmxlIGFuZCB0aGUgc2NlbmFyaW8gaW4gSmVyc2V5IENpdHkgaXMgdGhhdCBtb3JlIHN0YXRpb25zIGFyZSBidWlsdCAKCmBgYHtyIGNyb3NzIHZhbGlkYXRpb259CgpmaXRDb250cm9sIDwtIHRyYWluQ29udHJvbChtZXRob2QgPSAiY3YiLCBudW1iZXIgPSAxMDApCgpyZWcuY3YgPC0gdHJhaW4oVHJpcF9Db3VudCB+IHN0YXJ0X3N0YXRpb25fbmFtZSArICBob3VyKGludGVydmFsNjApICsgZG90dyArIFRlbXBlcmF0dXJlICsgV2luZF9TcGVlZCArIGxhZ0hvdXIgKyBsYWcySG91cnMgKyBsYWcxMkhvdXJzICsgbGFnMWRheSArIGxhZ1RyaXAsIGRhdGE9YmlrZS5wYW5lbCwgbWV0aG9kID0gImxtIiwgdHJDb250cm9sID0gZml0Q29udHJvbCwgbmEuYWN0aW9uID0gbmEucGFzcykKCnJlZy5jdiRyZXNhbXBsZSAlPiUgCiAgc3VtbWFyaXNlKE1BRSA9IG1lYW4ocmVnLmN2JHJlc2FtcGxlWywzXSksCiAgICAgICAgICAgIHNkKHJlZy5jdiRyZXNhbXBsZVssM10pCikgJT4lCiAga2JsKGNvbC5uYW1lPWMoJ01lYW4gQWJzb2x1dGUgRXJyb3InLCdTdGFuZGFyZCBEZXZpYXRpb24gb2YgTUFFJykpICU+JQogIGthYmxlX3N0eWxpbmcoYm9vdHN0cmFwX29wdGlvbnMgPSBjKCJzdHJpcGVkIiwgImhvdmVyIiwgImNvbmRlbnNlZCIpKQpgYGAKCgojIENvbmNsdXNpb24KCkluIGNvbmNsdXNpb24sIHRoaXMgcmVwb3J0IGFkZHJlc3NlcyB0aGUgY3JpdGljYWwgbmVlZCBmb3IgZWZmZWN0aXZlIGJpa2VzaGFyZSBtYW5hZ2VtZW50IHN0cmF0ZWdpZXMgaW4gSmVyc2V5IENpdHkgYnkgZW1wbG95aW5nIG1hY2hpbmUgbGVhcm5pbmcgdGVjaG5pcXVlcy4gT3VyIGFuYWx5c2lzIHNob3dzIHRoYXQgc3BhdGlhbCwgdGVtcG9yYWwsIGFuZCB3ZWF0aGVyLXJlbGF0ZWQgZmFjdG9ycyBhbGwgY29sbGVjdGl2ZWx5IGNvbnRyaWJ1dGUgdG8gdGhlIHZhcmlhdGlvbnMgaW4gYmlrZXNoYXJlIGRlbWFuZHMuIFdlIGZvdW5kIHRoYXQgaXQgaXMgbW9yZSBhY2N1cmF0ZSB0byBwcmVkaWN0IGJpa2VzaGFyZSBkZW1hbmQgYXQgYSBwYXJ0aWN1bGFyIGhvdXIgYmFzZWQgb24gdGhlIG51bWJlciBvZiB0cmlwcyBhdCB0aGUgaG91ciBpbW1lZGlhdGVseSBiZWZvcmUgYW5kIGFmdGVyLCBkdXJpbmcgdGhlIHNhbWUgaG91ciBhIGRheSBiZWZvcmUsIGFzIHdlbGwgYXMgdGhlIG51bWJlciBvZiB0cmlwcyBhdCBuZWFyYnkgc3RhdGlvbnMuIFdlIGFsc28gZm91bmQgdGhhdCBiaWtlc2hhcmUgZGVtYW5kIGFyZSB0aGUgaGlnaGVzdCBkdXJpbmcgd2luZHkgYW5kIGhvdCBkYXlzIGluIEplcnNleSBDaXR5IGluIEF1Z3VzdC4gIFRoZSBQTSBydXNoIGFuZCBvdmVybmlnaHQgZHVyaW5nIHdlZWtkYXlzIHNlZSB0aGUgaGlnaGVzdCBkZW1hbmQgd2hpbGUgdGhlIFBNIHJ1c2ggZHVyaW5nIHdlZWtlbmQgc2VlcyB0aGUgaGlnaGVzdCBkZW1hbmQuIE9uIHRvcCBvZiB0aGF0LCByZXNpZGVudHMgb2YgSmVyc2V5IENpdHkgdXNlcyBDaXRpIEJpa2UgaGVhdmlseSBmb3Igd29yayBjb21tdXRlIHRvIE5ldyBZb3JrIENpdHksIHdoaWNoIGhhcyBtYWRlIHN0YXRpb25zIGFsb25nIHRoZSBIdWRzb24gUml2ZXIsIHBhcnRpY3VsYXIgdGhvc2UgY2xvc2VyIHRvIGZlcnJ5IHN0YXRpb25zLCB0aGUgbW9zdCBwb3B1bGFyLiAKCldlIGRpZCBzb21lIGZ1cnRoZXIgaW52ZXN0aWdhdGlvbnMgaW50byBzdGF0aW9ucyB3aXRoIGdyZWF0ZXN0IGJpa2VzaGFyZSBkZW1hbmQuICoqR3JvdmUgU3QgUEFUSCoqIHN0YXRpb24sIHdoaWNoIG9ic2VydmVzIGEgdG90YWwgb2YgMTExNyByaWRlcyBkdXJpbmcgUE0gcnVzaCBvbiB3ZWVrZGF5IGFuZCA4MDEgcmlkZXMgb3Zlcm5pZ2h0IGluIEF1Z3VzdCwgaXMgbG9jYXRlZCBpbiB0aGUgYnVzeSBkb3dudG93biBKZXJzZXkgQ2l0eS4qKkhvYm9rZW4gVGVybWluYWwgLSBSaXZlciBTdCAmIEh1ZHNvbiBQbCoqLCB3aGljaCBvYnNlcnZlcyBhIHRvdGFsIG9mIDg2NCByaWRlcyBkdXJpbmcgUE0gcnVzaCBvbiB3ZWVrZGF5LCBpcyBsb2NhdGVkIHJpZ2h0IG5leHQgdG8gSG9ib2tlbi9OSiBUcmFuc2l0IFRlcm1pbmFsIGFuZCBhbG9uZyB0aGUgSHVkc29uIFJpdmVyLiAqKlNvdXRoIFdhdGVyZnJvbnQgV2Fsa3dheSAtIFNpbmF0cmEgRHIgJiAxIFN0Kiogc3RhdGlvbiwgd2hpY2ggb2JzZXJ2ZXMgMzE5IHJpZGVzIG92ZXJuaWdodCBvbiBhIHdlZWtlbmQgYW5kIDczMSByaWRlcyBkdXJpbmcgUE0gcnVzaCBvbiBhIHdlZWtkYXkgaXMgd2l0aGluIHdhbGtpbmcgZGlzdGFuY2UgdG8gdGhlIE5KIFRyYW5zaXQgVGVybWluYWwgYW5kIGF0IHRoZSBlbnRyYW5jZSB0byBQaWVyIEEgUGFyaywgYSB3YXRlcmZyb250IHJlY3JlYXRpb24gYXJlYSBpbiBKZXJzZXkgQ2l0eS4gVGhlcmVmb3JlLCB3ZSBtYXkgY29uY2x1ZGUgdGhhdCBzdGF0aW9ucyByZWNlaXZlIGhpZ2hlciBkZW1hbmQgYmVjYXVzZSAxKSB0aGVyZSdzIG1vcmUgd29ya2Vycy92aXNpdG9ycyBpbiBkb3dudG93biBhcmVhIHdobyBuZWVkIHRvIGJpa2UgdG8gZ2V0IGFyb3VuZC4gMikgcGVvcGxlIG5lZWQgdG8gY29tbXV0ZSB0byBOZXcgWW9yayBmb3Igd29yayBhbmQgbmVlZCB0byBiaWtlIHRvIGZlcnJ5L3RyYW5zaXQgc3RhdGlvbnMsIDMpIHdhdGVyZnJvbnQgcmVjcmVhdGlvbiBhcmVhcyBpcyBncmVhdCBmb3IgYmlraW5nIGFjdGl2aXRpZXMgb3ZlciB0aGUgd2Vla2VuZC4gTG9jYWwgZ292ZXJubWVudCBzaG91bGQgaW5kZWVkIHBheSBtb3JlIGF0dGVudGlvbiB0byB0aGUgInN1cHBseSIgYW5kICJkZW1hbmQgb2YgYmlrZXMgYXQgdGhvc2Uga2luZCBvZiBzdGF0aW9ucyBhbmQgcmViYWxhbmNlIGJpa2VzIGlmIG5lZWRlZCBiZS4gCgpPdXIgbW9kZWwgaXMgYWJsZSB0byBjYXB0dXJlIG1vcmUgdGhhbiBoYWxmIG9mIHRoZSBiaWtlc2hhcmUgZGVtYW5kIGluIEplcnNleSBDaXR5LCB0aG91Z2ggc3RpbGwsIGl0IHJlcXVpcmVzIGltcHJvdmVtZW50IHRvIGNhcHR1cmUgbW9yZSB2YXJpYWJpbGl0eS4gT3RoZXJ3aXNlLCBpdCB3aWxsIGZhaWwgdG8gbWVldCB0aGUgbmVlZHMgb2YgSmVyc2V5IENpdHkgcmVzaWRlbnRzIGFuZCAgaW5jcmVhc2UgdGhlIGRpZmZpY3VsdGllcyBvZiByZWJhbGFuY2luZyBiaWtlcy4gTmV2ZXJ0aGVsZXNzLCB0aGUgdHJhbnNwYXJlbmN5IGFuZCBzaW1wbGljaXR5IG9mIHRoaXMgb3Blbi1zb3VyY2UgYXBwcm9hY2ggcHJvdmlkZSBhIHZhbHVhYmxlIHJlc291cmNlIGZvciBiaWtlc2hhcmUgcHJvZ3JhbXMgdG8gZW5oYW5jZSB1c2VyIGV4cGVyaWVuY2UgYW5kIG1haW50YWluIGEgaGFybW9uaW91cyBiaWtlIGRpc3RyaWJ1dGlvbiBhY3Jvc3Mgc3RhdGlvbnMuIEFzIHVyYmFuIG1vYmlsaXR5IGNvbnRpbnVlcyB0byBldm9sdmUsIHRoZSBhcHBsaWNhdGlvbiBvZiBzdWNoIHByZWRpY3RpdmUgbW9kZWxzIGJlY29tZXMgaW5jcmVhc2luZ2x5IGVzc2VudGlhbCBmb3Igc3VzdGFpbmFibGUgYW5kIGVmZmljaWVudCB0cmFuc3BvcnRhdGlvbiBzb2x1dGlvbnMuCgo=