Introduction

The Rebalancing Challenge in Philadelphia

Philadelphia’s Indego bike share system faces the same operational challenge as every bike share system: rebalancing bikes to meet anticipated demand.

Imagine you’re an Indego operations manager at 6:00 AM on a Monday morning. You have: - 200 stations across Philadelphia - Limited trucks and staff for moving bikes - 2-3 hours before morning rush hour demand peaks - The question: Which stations will run out of bikes by 8:30 AM?

This lab will teach you to build predictive models that forecast bike share demand across space (different stations) and time (different hours) to help solve this operational problem.

Setup

Load Libraries

# Core tidyverse
library(tidyverse)
library(lubridate)

# Spatial data
library(sf)
library(tigris)

# Census data
library(tidycensus)

# Weather data
library(riem)  # For Philadelphia weather from ASOS stations

# Visualization
library(viridis)
library(gridExtra)
library(knitr)
library(kableExtra)
library(grid)
library(gridExtra)
library(patchwork)
library(ggplot2)


# here!
library(here)
# Get rid of scientific notation. We gotta look good!
options(scipen = 999)

Define Themes

plotTheme <- ggplot2::theme(
  plot.title = ggplot2::element_text(size = 14, face = "bold"),
  plot.subtitle = ggplot2::element_text(size = 10),
  plot.caption = ggplot2::element_text(size = 8),
  axis.text.x = ggplot2::element_text(size = 10, angle = 45, hjust = 1),
  axis.text.y = ggplot2::element_text(size = 10),
  axis.title = ggplot2::element_text(size = 11, face = "bold"),
  panel.background = ggplot2::element_blank(),
  panel.grid.major = ggplot2::element_line(colour = "#D0D0D0", size = 0.2),
  panel.grid.minor = ggplot2::element_blank(),
  axis.ticks = ggplot2::element_blank(),
  legend.position = "right"
)

mapTheme <- ggplot2::theme(
  plot.title = ggplot2::element_text(size = 14, face = "bold"),
  plot.subtitle = ggplot2::element_text(size = 10),
  plot.caption = ggplot2::element_text(size = 8),
  axis.line = ggplot2::element_blank(),
  axis.text = ggplot2::element_blank(),
  axis.ticks = ggplot2::element_blank(),
  axis.title = ggplot2::element_blank(),
  panel.background = ggplot2::element_blank(),
  panel.border = ggplot2::element_blank(),
  panel.grid.major = ggplot2::element_line(colour = 'transparent'),
  panel.grid.minor = ggplot2::element_blank(),
  legend.position = "right",
  plot.margin = ggplot2::margin(1, 1, 1, 1, 'cm'),
  legend.key.height = grid::unit(1, "cm"),
  legend.key.width = grid::unit(0.2, "cm")
)

palette5 <- c("#eff3ff", "#E1DCF5", "#BBADF0", "#6849BA", "#361AA3")

Set Census API Key


Data Import & Preparation

Load Indego Trip Data (Q1 - Q4 2024)

# Read Q1 2025 data
indegoq1 <- read.csv(here("labs/lab5/data/indego-trips-2024-q1.csv"))
indegoq2 <- read.csv(here("labs/lab5/data/indego-trips-2024-q2.csv"))
indegoq3 <- read.csv(here("labs/lab5/data/indego-trips-2024-q3.csv"))
indegoq4 <- read.csv(here("labs/lab5/data/indego-trips-2024-q4.csv"))

# Look at data
glimpse(indegoq1)
## Rows: 193,770
## Columns: 15
## $ trip_id             <int> 820756378, 820756236, 820756358, 820756551, 820886…
## $ duration            <int> 7, 1, 5, 9, 600, 31, 11, 30, 37, 545, 546, 33, 27,…
## $ start_time          <chr> "1/1/2024 0:04", "1/1/2024 0:04", "1/1/2024 0:05",…
## $ end_time            <chr> "1/1/2024 0:11", "1/1/2024 0:05", "1/1/2024 0:10",…
## $ start_station       <int> 3125, 3068, 3068, 3049, 3010, 3012, 3349, 3012, 32…
## $ start_lat           <dbl> 39.94391, 39.93549, 39.93549, 39.94509, 39.94711, …
## $ start_lon           <dbl> -75.16735, -75.16711, -75.16711, -75.14250, -75.16…
## $ end_station         <int> 3010, 3068, 3086, 3063, 3236, 3209, 3322, 3209, 30…
## $ end_lat             <dbl> 39.94711, 39.93549, 39.94019, 39.94633, 39.96566, …
## $ end_lon             <dbl> -75.16618, -75.16711, -75.16691, -75.16980, -75.17…
## $ bike_id             <chr> "23172", "14667", "14667", "22420", "23095", "0355…
## $ plan_duration       <int> 30, 30, 30, 30, 30, 30, 365, 30, 30, 30, 365, 1, 3…
## $ trip_route_category <chr> "One Way", "Round Trip", "One Way", "One Way", "On…
## $ passholder_type     <chr> "Indego30", "Indego30", "Indego30", "Indego30", "I…
## $ bike_type           <chr> "electric", "standard", "standard", "electric", "e…

Examine the Data Structure

Quarter 1 (January - March 2024)

Q1 (Jan - March 2024) Indego Trip Summary
Metric Value
Total trips in Q1 (Jan-March) 2024 193,770
Date range (start) 2024-01-01 00:04:00
Date range (end) 2024-03-31 23:58:00
Unique start stations 259
Route Categories
Route Category Count Percentage
One Way 182,750 94.31%
Round Trip 11,020 5.69%
Passholder Type
Passholder Type Count Percentage
Day Pass 6,966 3.59%
Indego30 114,818 59.25%
Indego365 71,986 37.15%
Bike Types
Bike Type Count Percentage
electric 113,962 58.8%
standard 79,808 41.2%

Quarter 2 (April - June 2024)

# Summary statistics table
summary_stats <- data.frame(
  Metric = c(
    "Total trips in Q2 2024",
    "Date range (start)",
    "Date range (end)",
    "Unique start stations"
  ),
  Value = c(
    format(nrow(indegoq2), big.mark = ","),
    as.character(min(mdy_hm(indegoq2$start_time))),
    as.character(max(mdy_hm(indegoq2$start_time))),
    length(unique(indegoq2$start_station))
  )
)

kable(summary_stats, 
      caption = "Q2 2024 Indego Trip Summary",
      col.names = c("Metric", "Value"),
      align = c("l", "r")) %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"),
                full_width = FALSE)
Q2 2024 Indego Trip Summary
Metric Value
Total trips in Q2 2024 368,091
Date range (start) 2024-04-01
Date range (end) 2024-06-30 23:56:00
Unique start stations 255
# Trip route categories
route_table <- as.data.frame(table(indegoq2$trip_route_category))
colnames(route_table) <- c("Trip Route Category", "Count")
route_table$Percentage <- paste0(round(100 * route_table$Count / sum(route_table$Count), 1), "%")

kable(route_table,
      caption = "Route Categories",
      format.args = list(big.mark = ","),
      align = c("l", "r", "r")) %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"),
                full_width = FALSE)
Route Categories
Trip Route Category Count Percentage
One Way 342,299 93%
Round Trip 25,792 7%
# Passholder types (including NAs)
passholder_table <- as.data.frame(table(indegoq2$passholder_type))
colnames(passholder_table) <- c("Passholder Type", "Count")
passholder_table$Percentage <- paste0(round(100 * passholder_table$Count / sum(passholder_table$Count), 2), "%")

kable(passholder_table,
      caption = "Passholder Type",
      format.args = list(big.mark = ","),
      align = c("l", "r", "r")) %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"),
                full_width = FALSE)
Passholder Type
Passholder Type Count Percentage
Day Pass 20,926 5.69%
Indego30 231,909 63%
Indego365 115,256 31.31%
# Bike types
bike_table <- as.data.frame(table(indegoq2$bike_type))
colnames(bike_table) <- c("Bike Type", "Count")
bike_table$Percentage <- paste0(round(100 * bike_table$Count / sum(bike_table$Count), 1), "%")

kable(bike_table,
      caption = "Bike Types",
      format.args = list(big.mark = ","),
      align = c("l", "r", "r")) %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"),
                full_width = FALSE)
Bike Types
Bike Type Count Percentage
electric 216,497 58.8%
standard 151,594 41.2%

Quarter 3 (July - September 2024)

# Summary statistics table
summary_stats <- data.frame(
  Metric = c(
    "Total trips in Q3 (Jul-Sept) 2024",
    "Date range (start)",
    "Date range (end)",
    "Unique start stations"
  ),
  Value = c(
    format(nrow(indegoq3), big.mark = ","),
    as.character(min(mdy_hm(indegoq3$start_time))),
    as.character(max(mdy_hm(indegoq3$start_time))),
    length(unique(indegoq3$start_station))
  )
)

kable(summary_stats, 
      caption = "Q3 (Jul - Sept 2024) Indego Trip Summary",
      col.names = c("Metric", "Value"),
      align = c("l", "r")) %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"),
                full_width = FALSE)
Q3 (Jul - Sept 2024) Indego Trip Summary
Metric Value
Total trips in Q3 (Jul-Sept) 2024 408,408
Date range (start) 2024-07-01 00:02:00
Date range (end) 2024-09-30 23:59:00
Unique start stations 261
# Trip route categories
route_table <- as.data.frame(table(indegoq3$trip_route_category))
colnames(route_table) <- c("Route Category", "Count")
route_table$Percentage <- paste0(round(100 * route_table$Count / sum(route_table$Count), 2), "%")

kable(route_table,
      caption = "Route Categories",
      format.args = list(big.mark = ","),
      align = c("l", "r", "r")) %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"),
                full_width = FALSE)
Route Categories
Route Category Count Percentage
One Way 380,939 93.27%
Round Trip 27,469 6.73%
# Passholder types
passholder_table <- as.data.frame(table(indegoq3$passholder_type))
colnames(passholder_table) <- c("Passholder Type", "Count")
passholder_table$Percentage <- paste0(round(100 * passholder_table$Count / sum(passholder_table$Count), 2), "%")

kable(passholder_table,
      caption = "Passholder Type",
      format.args = list(big.mark = ","),
      align = c("l", "r", "r")) %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"),
                full_width = FALSE)
Passholder Type
Passholder Type Count Percentage
Day Pass 22,885 5.6%
Indego30 239,857 58.73%
Indego365 132,358 32.41%
Walk-up 13,308 3.26%
# Bike types
bike_table <- as.data.frame(table(indegoq3$bike_type))
colnames(bike_table) <- c("Bike Type", "Count")
bike_table$Percentage <- paste0(round(100 * bike_table$Count / sum(bike_table$Count), 1), "%")

kable(bike_table,
      caption = "Bike Types",
      format.args = list(big.mark = ","),
      align = c("l", "r", "r")) %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"),
                full_width = FALSE)
Bike Types
Bike Type Count Percentage
electric 236,839 58%
standard 171,569 42%

Quarter 4 (October - December 2024)

Q4 (Oct - Dec 2024) Indego Trip Summary
Metric Value
Total trips in Q4 (Oct-Dec) 2024 299,121
Date range (start) 2024-10-01
Date range (end) 2024-12-31 23:56:00
Unique start stations 256
Route Categories
Route Category Count Percentage
One Way 282,675 94.5%
Round Trip 16,446 5.5%
Passholder Type
Passholder Type Count Percentage
Day Pass 10,711 3.58%
Indego30 159,895 53.45%
Indego365 112,690 37.67%
IndegoFlex 1 0%
NULL 1,165 0.39%
Walk-up 14,659 4.9%
Bike Types
Bike Type Count Percentage
electric 216,497 58.8%
standard 151,594 41.2%

Combine all quarters

indego_all <- bind_rows(
  Q1_2025 = indegoq1,
  Q2_2024 = indegoq2,
  Q3_2024 = indegoq3,
  Q4_2024 = indegoq4,
  .id = "quarter"
)

# Check the distribution
table(indego_all$quarter)
## 
## Q1_2025 Q2_2024 Q3_2024 Q4_2024 
##  193770  368091  408408  299121

Create Time Bins

We need to aggregate trips into hourly intervals for our panel data structure.

Quarter 1

Quarter 2

Quarter 3

Quarter 4


Part 1: Why I Chose Full Year 2024

Data Selection Rationale

I selected all four quarters of 2024 rather than a single quarter for a few reasons. First, combining all quarters allows for a larger training dataset which can help minimize the risk of overfitting. Second, Philadelphia experiences very seasonal weather, which in turn affects bike share ridership and demand. In the winter months (Q1 and Q4), low ridership can be attributed to cold temperatures and inclement weather, whereas the spring and summer (Q2 and Q3) see most of the demand. By including the full year, the model learns these seasonal patterns and can generalize better.

Comparing across all quarters reveals clear temporal differences that are explored in the “Trips Over Time” and “Hourly Patterns” sections below.


Exploratory Analysis

Trips Over Time

The drastic dip in early October that shoots from 3000 trips to almost 5000 is suspicious. I tried looking up notable events that occurred in Philadelphia last fall, and I couldn’t find much information. Also the spike in ridership in Q1 March 2024 could be due to a number of things, oddly warm weather, SEPTA safety issues, and more.

Combining all quarters into one variable

This code chunk combines all quarters into one variable for easier synthesis once we start looking at the regressions and temporal patterns.

indego_all <- bind_rows(
  Q1_2024 = indegoq1,  # Now these have interval60, week, month, hour, etc.
  Q2_2024 = indegoq2,
  Q3_2024 = indegoq3,
  Q4_2024 = indegoq4,
  .id = "quarter"
)

# Verify time features are present
cat("Total trips in processed indego_all:", format(nrow(indego_all), big.mark = ","), "\n")
## Total trips in processed indego_all: 1,269,390
cat("Time features present:", all(c("interval60", "week", "month", "hour", "dotw") %in% names(indego_all)), "\n")
## Time features present: TRUE
# Check distribution by quarter
table(indego_all$quarter)
## 
## Q1_2024 Q2_2024 Q3_2024 Q4_2024 
##  193770  368091  408408  299121

Hourly Patterns

In this code chunk I took each quarter, assigned it to a variable, then assigned that variable to a plotting variable, then lined them up for display below.

During typical morning and evening rush hour commute times, there is peak Indego ridership almost consistently across all four quarters at what is assuming to be around 7/8 AM and 5/6 PM. For the weekends, there is more of a gradual increase in ridership as the day progresses, then casually falls off as the evening progresses.

Top Stations

The chunk below ranks each station based on the station that has the most starts/deployments from.

Top 10 Stations - Q1 2024
Rank Station Total Trips Avg Daily
1 3010 3,722 40.9
2 3295 3,358 36.9
3 3032 2,966 32.6
4 3020 2,747 30.2
5 3208 2,737 30.1
6 3359 2,513 27.6
7 3012 2,480 27.3
8 3296 2,384 26.2
9 3066 2,321 25.5
10 3052 2,282 25.4
Top 10 Stations - Q2 2024
Rank Station Total Trips Avg Daily
1 3010 6,115 67.2
2 3032 5,231 58.1
3 3295 4,451 48.9
4 3359 4,248 46.7
5 3022 4,070 44.7
6 3028 4,052 44.5
7 3066 4,047 44.5
8 3054 3,847 42.3
9 3020 3,792 41.7
10 3208 3,661 40.2
Top 10 Stations - Q3 2024
Rank Station Total Trips Avg Daily
1 3010 6,654 72.3
2 3032 5,436 59.1
3 3244 4,421 48.1
4 3054 4,305 46.8
5 3359 4,305 46.8
6 3296 4,252 46.2
7 3101 4,226 45.9
8 3020 4,154 45.2
9 3295 4,144 45.0
10 3066 4,114 44.7
Top 10 Stations - Q4 2024
Rank Station Total Trips Avg Daily
1 3010 5,943 64.6
2 3032 4,471 48.6
3 3359 3,923 42.6
4 3244 3,492 38.0
5 3295 3,411 37.1
6 3020 3,369 36.6
7 3208 3,343 36.3
8 3066 3,342 36.3
9 3054 3,297 35.8
10 3101 3,214 34.9

Station #3010 in Q1 2024 has the most origin trips at 40.9 average trips per day. Station #3010 continues the trend of being the highest starting point of travel in all quarters of 2024.


Get Philadelphia Spatial Context

Load Philadelphia Census Data

We’ll get census tract data to add demographic context to our stations.

##   |                                                                              |                                                                      |   0%  |                                                                              |=                                                                     |   1%  |                                                                              |==                                                                    |   2%  |                                                                              |==                                                                    |   3%  |                                                                              |===                                                                   |   4%  |                                                                              |===                                                                   |   5%  |                                                                              |====                                                                  |   5%  |                                                                              |====                                                                  |   6%  |                                                                              |=====                                                                 |   7%  |                                                                              |=====                                                                 |   8%  |                                                                              |======                                                                |   8%  |                                                                              |======                                                                |   9%  |                                                                              |=======                                                               |  10%  |                                                                              |=======                                                               |  11%  |                                                                              |========                                                              |  11%  |                                                                              |========                                                              |  12%  |                                                                              |=========                                                             |  13%  |                                                                              |==========                                                            |  14%  |                                                                              |===========                                                           |  15%  |                                                                              |===========                                                           |  16%  |                                                                              |============                                                          |  17%  |                                                                              |=============                                                         |  18%  |                                                                              |=============                                                         |  19%  |                                                                              |==============                                                        |  20%  |                                                                              |===============                                                       |  21%  |                                                                              |===============                                                       |  22%  |                                                                              |================                                                      |  23%  |                                                                              |=================                                                     |  24%  |                                                                              |=================                                                     |  25%  |                                                                              |==================                                                    |  26%  |                                                                              |===================                                                   |  27%  |                                                                              |===================                                                   |  28%  |                                                                              |====================                                                  |  29%  |                                                                              |=====================                                                 |  30%  |                                                                              |=====================                                                 |  31%  |                                                                              |======================                                                |  32%  |                                                                              |=======================                                               |  33%  |                                                                              |========================                                              |  34%  |                                                                              |========================                                              |  35%  |                                                                              |=========================                                             |  36%  |                                                                              |==========================                                            |  37%  |                                                                              |==========================                                            |  38%  |                                                                              |===========================                                           |  39%  |                                                                              |============================                                          |  40%  |                                                                              |============================                                          |  41%  |                                                                              |=============================                                         |  41%  |                                                                              |==============================                                        |  42%  |                                                                              |==============================                                        |  43%  |                                                                              |===============================                                       |  44%  |                                                                              |================================                                      |  45%  |                                                                              |================================                                      |  46%  |                                                                              |=================================                                     |  47%  |                                                                              |==================================                                    |  48%  |                                                                              |===================================                                   |  49%  |                                                                              |===================================                                   |  50%  |                                                                              |====================================                                  |  51%  |                                                                              |=====================================                                 |  52%  |                                                                              |=====================================                                 |  53%  |                                                                              |======================================                                |  54%  |                                                                              |=======================================                               |  55%  |                                                                              |=======================================                               |  56%  |                                                                              |========================================                              |  57%  |                                                                              |=========================================                             |  58%  |                                                                              |=========================================                             |  59%  |                                                                              |==========================================                            |  60%  |                                                                              |===========================================                           |  61%  |                                                                              |===========================================                           |  62%  |                                                                              |============================================                          |  63%  |                                                                              |=============================================                         |  64%  |                                                                              |==============================================                        |  65%  |                                                                              |==============================================                        |  66%  |                                                                              |===============================================                       |  67%  |                                                                              |================================================                      |  68%  |                                                                              |================================================                      |  69%  |                                                                              |=================================================                     |  70%  |                                                                              |==================================================                    |  71%  |                                                                              |==================================================                    |  72%  |                                                                              |===================================================                   |  73%  |                                                                              |====================================================                  |  74%  |                                                                              |====================================================                  |  75%  |                                                                              |=====================================================                 |  76%  |                                                                              |======================================================                |  77%  |                                                                              |======================================================                |  78%  |                                                                              |=======================================================               |  79%  |                                                                              |========================================================              |  80%  |                                                                              |=========================================================             |  81%  |                                                                              |=========================================================             |  82%  |                                                                              |==========================================================            |  83%  |                                                                              |===========================================================           |  84%  |                                                                              |===========================================================           |  85%  |                                                                              |============================================================          |  86%  |                                                                              |=============================================================         |  87%  |                                                                              |=============================================================         |  88%  |                                                                              |==============================================================        |  89%  |                                                                              |===============================================================       |  90%  |                                                                              |===============================================================       |  91%  |                                                                              |================================================================      |  92%  |                                                                              |=================================================================     |  92%  |                                                                              |=================================================================     |  93%  |                                                                              |==================================================================    |  94%  |                                                                              |===================================================================   |  95%  |                                                                              |===================================================================   |  96%  |                                                                              |====================================================================  |  97%  |                                                                              |===================================================================== |  98%  |                                                                              |======================================================================|  99%  |                                                                              |======================================================================| 100%
## Rows: 408
## Columns: 17
## $ GEOID                  <chr> "42101001500", "42101001800", "42101002802", "4…
## $ NAME                   <chr> "Census Tract 15; Philadelphia County; Pennsylv…
## $ Total_Pop              <dbl> 3251, 3300, 5720, 4029, 4415, 1815, 3374, 2729,…
## $ B01003_001M            <dbl> 677, 369, 796, 437, 853, 210, 480, 734, 763, 11…
## $ Med_Inc                <dbl> 110859, 114063, 78871, 61583, 32347, 48581, 597…
## $ B19013_001M            <dbl> 24975, 30714, 20396, 22293, 4840, 13812, 6278, …
## $ Total_Commuters        <dbl> 2073, 2255, 3032, 2326, 1980, 969, 2427, 708, 2…
## $ B08301_001M            <dbl> 387, 308, 478, 383, 456, 189, 380, 281, 456, 68…
## $ Transit_Commuters      <dbl> 429, 123, 685, 506, 534, 192, 658, 218, 438, 51…
## $ B08301_010M            <dbl> 188, 66, 219, 144, 285, 71, 278, 184, 176, 235,…
## $ White_Pop              <dbl> 2185, 2494, 3691, 3223, 182, 984, 2111, 231, 35…
## $ B02001_002M            <dbl> 268, 381, 592, 380, 88, 190, 463, 112, 238, 778…
## $ Med_Home_Value         <dbl> 568300, 605000, 350600, 296400, 76600, 289700, …
## $ B25077_001M            <dbl> 58894, 34876, 12572, 22333, 10843, 118720, 1506…
## $ geometry               <MULTIPOLYGON [°]> MULTIPOLYGON (((-75.16558 3..., MU…
## $ Percent_Taking_Transit <dbl> 20.694645, 5.454545, 22.592348, 21.754084, 26.9…
## $ Percent_White          <dbl> 67.2100892, 75.5757576, 64.5279720, 79.9950360,…

Map Philadelphia Context, Median Household Income

# Create station location data
stations_q1_clean <- indegoq1 %>%
  group_by(start_station, start_lon, start_lat) %>%
  summarize(trips = n(), .groups = "drop")

cat("Removed", nrow(stations_q1_clean) - nrow(stations_q1_clean), "stations with missing coordinates\n")
## Removed 0 stations with missing coordinates
stations_q2 <- indegoq2 %>%
  group_by(start_station, start_lon, start_lat) %>%
  summarize(trips = n(), .groups = "drop")

stations_q3 <- indegoq3 %>%
  group_by(start_station, start_lon, start_lat) %>%
  summarize(trips = n(), .groups = "drop")

stations_q4 <- indegoq4 %>%
  group_by(start_station, start_lon, start_lat) %>%
  summarize(trips = n(), .groups = "drop")

# Q1 Map 
mhhinc1 <- ggplot() +
  geom_sf(data = philly_census, aes(fill = Med_Inc), color = NA) +
  scale_fill_viridis(
    option = "viridis",
    name = "Median Income",
    labels = scales::label_dollar(scale = 1/1000, suffix = "K")
  ) +
  labs(title = "Q1 2024") +
  geom_point(
    data = stations_q1_clean,
    aes(x = start_lon, y = start_lat, size = trips),
    color = "#CC2810", alpha = 0.1
  ) +
  scale_size_continuous(range = c(1, 4), guide = "none") +
  coord_sf(xlim = c(-75.28, -74.96), ylim = c(39.87, 40.14), expand = FALSE) +  # FIX: Force proper extent
  mapTheme +
  theme(legend.position = "none")

# Q2 Map
mhhinc2 <- ggplot() +
  geom_sf(data = philly_census, aes(fill = Med_Inc), color = NA) +
  scale_fill_viridis(
    option = "viridis",
    name = "Median Income",
    labels = scales::label_dollar(scale = 1/1000, suffix = "K")
  ) +
  labs(title = "Q2 2024") +
  geom_point(
    data = stations_q2,
    aes(x = start_lon, y = start_lat, size = trips),
    color = "#CC2810", alpha = 0.1
  ) +
  scale_size_continuous(range = c(1, 4), guide = "none") +
  mapTheme +
  theme(legend.position = "none")

# Q3 Map
mhhinc3 <- ggplot() +
  geom_sf(data = philly_census, aes(fill = Med_Inc), color = NA) +
  scale_fill_viridis(
    option = "viridis",
    name = "Median Income",
    labels = scales::label_dollar(scale = 1/1000, suffix = "K")
  ) +
  labs(title = "Q3 2024") +
  geom_point(
    data = stations_q3,
    aes(x = start_lon, y = start_lat, size = trips),
    color = "#CC2810", alpha = 0.1
  ) +
  scale_size_continuous(range = c(1, 4), guide = "none") +
  mapTheme +
  theme(legend.position = "none")

# Q4 Map
mhhinc4 <- ggplot() +
  geom_sf(data = philly_census, aes(fill = Med_Inc), color = NA) +
  scale_fill_viridis(
    option = "viridis",
    name = "Median Income",
    labels = scales::label_dollar(scale = 1/1000, suffix = "K")
  ) +
  labs(title = "Q4 2024") +
  geom_point(
    data = stations_q4,
    aes(x = start_lon, y = start_lat, size = trips),
    color = "#CC2810", alpha = 0.1
  ) +
  scale_size_continuous(range = c(1, 4), guide = "none") +
  mapTheme


mhhinc1 + mhhinc2 + mhhinc3 + mhhinc4 +   # <-- ADD ALL FOUR!
  plot_layout(guides = "collect", nrow = 1) +
  plot_annotation(
    title = "Indego Station Activity by Quarter: 2024",
    subtitle = "Red points show station locations sized by trip volume",
    caption = "Source: Indego bike share, US Census ACS 2022"
  ) &
  theme(
    legend.position = "bottom",
    legend.direction = "horizontal",
    legend.key.width = unit(3, "cm"),
    legend.key.height = unit(0.3, "cm"),
    legend.box.margin = margin(t = 10)
  )

A quick visualization shows there is not much differentiation between the quarters in terms of trip volume at the city-scale.

Join Census Data to Stations

We’ll spatially join census characteristics to each bike station.

# Create sf object for stations from COMBINED data
stations_sf <- indego_all %>%
  distinct(start_station, start_lat, start_lon) %>%
  filter(!is.na(start_lat), !is.na(start_lon)) %>%
  st_as_sf(coords = c("start_lon", "start_lat"), crs = 4326)

# Spatial join to get census tract for each station
stations_census <- st_join(stations_sf, philly_census, left = TRUE) %>%
  st_drop_geometry()

# Look at the result - investigate whether all of the stations joined to census data
stations_for_map <- indego_all %>%
  distinct(start_station, start_lat, start_lon) %>%
  filter(!is.na(start_lat), !is.na(start_lon)) %>%
  left_join(
    stations_census %>% dplyr::select(start_station, Med_Inc),
    by = "start_station"
  ) %>%
  mutate(has_census = !is.na(Med_Inc))

# Add back to trip data
indego_all_census <- indego_all %>%
  left_join(
    stations_census %>% 
      dplyr::select(start_station, Med_Inc, Percent_Taking_Transit, 
             Percent_White, Total_Pop),
    by = "start_station"
  )

# Prepare data for visualization
stations_for_map <- indego_all %>%
  distinct(start_station, start_lat, start_lon) %>%
  filter(!is.na(start_lat), !is.na(start_lon)) %>%
  left_join(
    stations_census %>% dplyr::select(start_station, Med_Inc),
    by = "start_station"
  ) %>%
  mutate(has_census = !is.na(Med_Inc))

# Create the map showing problem stations
ggplot() +
  geom_sf(data = philly_census, aes(fill = Med_Inc), color = "white", size = 0.1) +
  scale_fill_viridis(
    option = "viridis",
    name = "Median\nIncome",
    labels = scales::dollar,
    na.value = "grey90"
  ) +
  # Stations with census data (small grey dots)
  geom_point(
    data = stations_for_map %>% filter(has_census),
    aes(x = start_lon, y = start_lat),
    color = "grey30", size = 1, alpha = 0.6
  ) +
  # Stations WITHOUT census data (red X marks the spot)
  geom_point(
    data = stations_for_map %>% filter(!has_census),
    aes(x = start_lon, y = start_lat),
    color = "red", size = 1, shape = 4, stroke = 1.5
  ) +
  labs(
    title = "Philadelphia Median Household Income by Census Tract",
    subtitle = "Indego stations shown (RED = no census data match)",
    caption = "Red X marks indicate stations that didn't join to census tracts"
  ) +
  mapTheme

Unsure what the deal is with this outlier, as our earlier cleaning should have done away with it.

# Dealing with missing data
# Identify which stations to keep (only residential neighborhoods)
valid_stations <- stations_census %>%
  filter(!is.na(Med_Inc)) %>%
  pull(start_station)

# Filter trip data to valid stations only
indego_all_census <- indego_all %>%
  filter(start_station %in% valid_stations) %>%
  left_join(
    stations_census %>% 
      dplyr::select(start_station, Med_Inc, Percent_Taking_Transit, 
             Percent_White, Total_Pop),
    by = "start_station"
  )

# Summary
cat("Total trips before filtering:", format(nrow(indego_all), big.mark = ","), "\n")
## Total trips before filtering: 1,269,390
cat("Trips at residential stations:", format(nrow(indego_all_census), big.mark = ","), "\n")
## Trips at residential stations: 1,192,977
cat("Trips removed:", format(nrow(indego_all) - nrow(indego_all_census), big.mark = ","), "\n")
## Trips removed: 76,413

Get Weather Data

Weather significantly affects bike share demand! We need to get weather data covering all four quarters of 2024 plus Q1 2025.

# Get weather from Philadelphia International Airport (KPHL)
# Covering full year: Q1 2024 through Q4 2024

# Query each quarter separately
weather_q1 <- riem_measures(station = "PHL", 
                            date_start = "2024-01-01", 
                            date_end = "2024-03-31") #Quarter 1 '24

weather_q2 <- riem_measures(station = "PHL", 
                            date_start = "2024-04-01", 
                            date_end = "2024-06-30") # Quarter 2 '24

weather_q3 <- riem_measures(station = "PHL", 
                            date_start = "2024-07-01", 
                            date_end = "2024-09-30") # Quarter 3 '24

weather_q4 <- riem_measures(station = "PHL", 
                            date_start = "2024-10-01", 
                            date_end = "2024-12-31") # Quarter 4 '24 

# Combine them
weather_2024 <- bind_rows(weather_q1, weather_q2, weather_q3, weather_q4)

# Process weather data
weather_processed <- weather_2024 %>%
  mutate(
    interval60 = floor_date(valid, unit = "hour"),
    Temperature = tmpf,  # Temperature in Fahrenheit
    Precipitation = ifelse(is.na(p01i), 0, p01i),  # Hourly precip in inches
    Wind_Speed = sknt  # Wind speed in knots
  ) %>%
  dplyr::select(interval60, Temperature, Precipitation, Wind_Speed) %>%
  distinct()%>%
  arrange(interval60)

# Check for missing hours and interpolate if needed
weather_complete <- weather_processed %>%
  complete(interval60 = seq(min(interval60), max(interval60), by = "hour")) %>%
  fill(Temperature, Precipitation, Wind_Speed, .direction = "down")

# Summary statistics
cat("Weather data coverage:\n")
## Weather data coverage:
cat("Start:", as.character(min(weather_complete$interval60)), "\n")
## Start: 2024-01-01
cat("End:", as.character(max(weather_complete$interval60)), "\n")
## End: 2024-12-30 23:00:00
cat("Total hours:", nrow(weather_complete), "\n\n")
## Total hours: 10693
summary(weather_complete %>% dplyr::select(Temperature, Precipitation, Wind_Speed))
##   Temperature    Precipitation        Wind_Speed    
##  Min.   :12.00   Min.   :0.000000   Min.   : 0.000  
##  1st Qu.:46.00   1st Qu.:0.000000   1st Qu.: 5.000  
##  Median :59.00   Median :0.000000   Median : 7.000  
##  Mean   :58.87   Mean   :0.006827   Mean   : 7.619  
##  3rd Qu.:73.00   3rd Qu.:0.000000   3rd Qu.:10.000  
##  Max.   :98.00   Max.   :1.250000   Max.   :44.000

Visualize Weather Patterns

# Temperature patterns by quarter
weather_complete %>%
  mutate(
    quarter = paste0("Q", quarter(interval60), " 2024")
  ) %>%  
  ggplot(aes(x = interval60, y = Temperature, color = quarter)) +
  geom_line(alpha = 0.6) +
  geom_smooth(se = FALSE, linewidth = 1.2) +
  scale_color_viridis_d(option = "plasma") +
  labs(
    title = "Philadelphia Temperature Patterns Across All Quarters",
    subtitle = "2024 Seasonal Temperature Variation",
    x = "Date",
    y = "Temperature (°F)",
    color = "Quarter"
  ) +
  plotTheme +
  theme(legend.position = "bottom")


Create Space-Time Panel

Aggregate Trips to Station-Hour Level

# Count trips by station-hour
trips_panel <- indego_all_census %>%
  group_by(interval60, start_station, start_lat, start_lon,
           Med_Inc, Percent_Taking_Transit, Percent_White, Total_Pop) %>%
  summarize(Trip_Count = n()) %>%
  ungroup()


# Summary statistics
cat("Trips by station-hour:\n")
## Trips by station-hour:
cat("How many station-hour observations?:", nrow(trips_panel), "\n")
## How many station-hour observations?: 633678
cat("How many unique stations?:", length(unique(trips_panel$start_station)) , "\n")
## How many unique stations?: 254
cat("How many unique hours?:", length(unique(trips_panel$interval60)), "\n\n")
## How many unique hours?: 8762

Create Complete Panel Structure

Not every station has trips every hour. We need a complete panel where every station-hour combination exists (even if Trip_Count = 0).

# Calculate expected panel size
n_stations <- length(unique(trips_panel$start_station))
n_hours <- length(unique(trips_panel$interval60))
expected_rows <- n_stations * n_hours

cat("Expected panel rows:", format(expected_rows, big.mark = ","), "\n")
## Expected panel rows: 2,225,548
cat("Current rows:", format(nrow(trips_panel), big.mark = ","), "\n")
## Current rows: 633,678
cat("Missing rows:", format(expected_rows - nrow(trips_panel), big.mark = ","), "\n")
## Missing rows: 1,591,870
# Create complete panel
# Create complete panel
study_panel <- expand.grid(
  interval60 = unique(trips_panel$interval60),
  start_station = unique(trips_panel$start_station)
) %>%
  left_join(trips_panel, by = c("interval60", "start_station")) %>%
  mutate(
    Trip_Count = replace_na(Trip_Count, 0),

    # extract month from interval60 (assuming POSIXct)
    month = lubridate::month(interval60, label = TRUE, abbr = TRUE),

    # force ALL 12 months so no new levels occur in testing
    month = factor(month, levels = month.abb)
  )

# Fill in station attributes (same for all hours)
station_attributes <- trips_panel %>%
  group_by(start_station) %>%
  summarize(
    start_lat = first(start_lat),
    start_lon = first(start_lon),
    Med_Inc = first(Med_Inc),
    Percent_Taking_Transit = first(Percent_Taking_Transit),
    Percent_White = first(Percent_White),
    Total_Pop = first(Total_Pop)
  )

study_panel <- study_panel %>%
  left_join(station_attributes, by = "start_station")

cat("Complete panel rows:", format(nrow(study_panel), big.mark = ","), "\n")
## Complete panel rows: 2,225,548

Add Time Features

study_panel <- study_panel %>%
  mutate(
    week = week(interval60),
    month = month(interval60, label = TRUE),
    dotw = wday(interval60, label = TRUE),
    hour = hour(interval60),
    date = as.Date(interval60),
    weekend = ifelse(dotw %in% c("Sat", "Sun"), 1, 0),
    rush_hour = ifelse(hour %in% c(7, 8, 9, 16, 17, 18), 1, 0)
  )

Join Weather Data

study_panel <- study_panel %>%
  left_join(weather_complete, by = "interval60")

# Check for missing values
summary(study_panel %>% select(Trip_Count, Temperature, Precipitation))
##    Trip_Count       Temperature   Precipitation   
##  Min.   : 0.0000   Min.   :12.0   Min.   :0.0000  
##  1st Qu.: 0.0000   1st Qu.:46.0   1st Qu.:0.0000  
##  Median : 0.0000   Median :59.0   Median :0.0000  
##  Mean   : 0.5156   Mean   :58.9   Mean   :0.0068  
##  3rd Qu.: 1.0000   3rd Qu.:73.0   3rd Qu.:0.0000  
##  Max.   :28.0000   Max.   :98.0   Max.   :1.2500  
##                    NA's   :6096   NA's   :6096

Create Temporal Lag Variables

The key innovation for space-time prediction: past demand predicts future demand.

Why Lags?

If there were 15 bike trips from Station A at 8:00 AM, there will probably be ~15 trips at 9:00 AM. We can use this temporal persistence to improve predictions.

# Sort by station and time
study_panel <- study_panel %>%
  arrange(start_station, interval60)

# Create lag variables WITHIN each station
study_panel <- study_panel %>%
  group_by(start_station) %>%
  mutate(
    lag1Hour = lag(Trip_Count, 1),
    lag2Hours = lag(Trip_Count, 2),
    lag3Hours = lag(Trip_Count, 3),
    lag12Hours = lag(Trip_Count, 12),
    lag1day = lag(Trip_Count, 24)
  ) %>%
  ungroup()

# Remove rows with NA lags (first 24 hours for each station)
study_panel_complete <- study_panel %>%
  filter(!is.na(lag1day))

cat("Rows after removing NA lags:", format(nrow(study_panel_complete), big.mark = ","), "\n")
## Rows after removing NA lags: 2,709,672

Visualize Lag Correlations

# Find stations with highest trip counts
top_stations <- study_panel_complete %>%
  group_by(start_station) %>%
  summarize(total_trips = sum(Trip_Count)) %>%
  arrange(desc(total_trips)) %>%
  head(10)

print(top_stations)
## # A tibble: 10 × 2
##    start_station total_trips
##            <int>       <int>
##  1          3010       26470
##  2          3032       21419
##  3          3295       18090
##  4          3359       17482
##  5          3066       16242
##  6          3244       15695
##  7          3028       15602
##  8          3054       15591
##  9          3022       15376
## 10          3101       15078
# Pick a high-activity station (use one from the list above)
example_station <- study_panel_complete %>%
  filter(start_station == top_stations$start_station[1]) %>%  # Highest activity station
  head(168)  # One week

# Check if you have data
cat("Trip count range:", range(example_station$Trip_Count), "\n")
## Trip count range: 0 10
cat("Mean trips:", mean(example_station$Trip_Count), "\n")
## Mean trips: 1.446429
# Now plot
ggplot(example_station, aes(x = interval60)) +
  geom_line(aes(y = Trip_Count, color = "Current"), linewidth = 1) +
  geom_line(aes(y = lag1Hour, color = "1 Hour Ago"), linewidth = 1, alpha = 0.7) +
  geom_line(aes(y = lag1day, color = "24 Hours Ago"), linewidth = 1, alpha = 0.7) +
  scale_color_manual(values = c(
    "Current" = "#08519c",
    "1 Hour Ago" = "#3182bd",
    "24 Hours Ago" = "#6baed6"
  )) +
  labs(
    title = "Temporal Lag Patterns at One Station",
    subtitle = "Past demand predicts future demand",
    x = "Date-Time",
    y = "Trip Count",
    color = "Time Period"
  ) +
  plotTheme


Temporal Train/Test Split

CRITICAL: We must train on PAST data and test on FUTURE data!

Why Temporal Validation Matters

In real operations, at 6:00 AM on March 15, we need to predict demand for March 15-31. We have data from Jan 1 - March 14, but NOT from March 15-31 (it hasn’t happened yet!).

Wrong approach: Train on weeks 10-13, test on weeks 1-9 (predicting past from future!)

Correct approach: Train on weeks 1-9, test on weeks 10-13 (predicting future from past)

# Split by week
# Q1 has weeks 1-13 (Jan-Mar)
# Train on weeks 1-9 (Jan 1 - early March)
# Test on weeks 10-13 (rest of March)

# Which stations have trips in BOTH early and late periods?
early_stations <- study_panel_complete %>%
  filter(week < 10) %>%
  filter(Trip_Count > 0) %>%
  distinct(start_station) %>%
  pull(start_station)

late_stations <- study_panel_complete %>%
  filter(week >= 10) %>%
  filter(Trip_Count > 0) %>%
  distinct(start_station) %>%
  pull(start_station)

# Keep only stations that appear in BOTH periods
common_stations <- intersect(early_stations, late_stations)


# Filter panel to only common stations
study_panel_complete <- study_panel_complete %>%
  filter(start_station %in% common_stations)

# NOW create train/test split
train <- study_panel_complete %>%
  filter(week < 10)

test <- study_panel_complete %>%
  filter(week >= 10)

cat("Training observations:", format(nrow(train), big.mark = ","), "\n")
## Training observations: 445,496
cat("Testing observations:", format(nrow(test), big.mark = ","), "\n")
## Testing observations: 2,040,148
cat("Training date range:", min(train$date), "to", max(train$date), "\n")
## Training date range: 19723 to 19785
cat("Testing date range:", min(test$date), "to", max(test$date), "\n")
## Testing date range: 19786 to 20088

Build Predictive Models

We’ll build 5 models with increasing complexity to see what improves predictions.

Model 1: Baseline (Time + Weather)

# Create day of week factor with treatment (dummy) coding
train <- train %>%
  mutate(dotw_simple = factor(dotw, levels = c("Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun")))

# Set contrasts to treatment coding (dummy variables)
contrasts(train$dotw_simple) <- contr.treatment(7)

# Now run the model
model1 <- lm(
  Trip_Count ~ as.factor(hour) + dotw_simple + Temperature + Precipitation,
  data = train
)

summary(model1)
## 
## Call:
## lm(formula = Trip_Count ~ as.factor(hour) + dotw_simple + Temperature + 
##     Precipitation, data = train)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -0.8997 -0.3927 -0.1859  0.0252 14.2090 
## 
## Coefficients:
##                     Estimate Std. Error t value             Pr(>|t|)    
## (Intercept)       -0.1658768  0.0080090 -20.711 < 0.0000000000000002 ***
## as.factor(hour)1  -0.0278062  0.0076114  -3.653             0.000259 ***
## as.factor(hour)2  -0.0277130  0.0076920  -3.603             0.000315 ***
## as.factor(hour)3  -0.0528180  0.0077975  -6.774      0.0000000000126 ***
## as.factor(hour)4  -0.0262147  0.0076254  -3.438             0.000586 ***
## as.factor(hour)5   0.0434695  0.0076812   5.659      0.0000000152186 ***
## as.factor(hour)6   0.1577783  0.0075942  20.776 < 0.0000000000000002 ***
## as.factor(hour)7   0.3041425  0.0077357  39.317 < 0.0000000000000002 ***
## as.factor(hour)8   0.4836049  0.0074759  64.689 < 0.0000000000000002 ***
## as.factor(hour)9   0.3756329  0.0076227  49.278 < 0.0000000000000002 ***
## as.factor(hour)10  0.2979748  0.0075114  39.669 < 0.0000000000000002 ***
## as.factor(hour)11  0.2975841  0.0074157  40.129 < 0.0000000000000002 ***
## as.factor(hour)12  0.3567110  0.0073915  48.260 < 0.0000000000000002 ***
## as.factor(hour)13  0.3687245  0.0075332  48.946 < 0.0000000000000002 ***
## as.factor(hour)14  0.3830709  0.0075447  50.773 < 0.0000000000000002 ***
## as.factor(hour)15  0.4299939  0.0075008  57.326 < 0.0000000000000002 ***
## as.factor(hour)16  0.4808562  0.0075001  64.113 < 0.0000000000000002 ***
## as.factor(hour)17  0.6077052  0.0074797  81.248 < 0.0000000000000002 ***
## as.factor(hour)18  0.4438296  0.0075275  58.961 < 0.0000000000000002 ***
## as.factor(hour)19  0.3029202  0.0076918  39.382 < 0.0000000000000002 ***
## as.factor(hour)20  0.1732385  0.0077211  22.437 < 0.0000000000000002 ***
## as.factor(hour)21  0.1353487  0.0075538  17.918 < 0.0000000000000002 ***
## as.factor(hour)22  0.0930418  0.0076863  12.105 < 0.0000000000000002 ***
## as.factor(hour)23  0.0491728  0.0076353   6.440      0.0000000001194 ***
## dotw_simple2      -0.0502449  0.0042331 -11.869 < 0.0000000000000002 ***
## dotw_simple3       0.0287986  0.0043908   6.559      0.0000000000543 ***
## dotw_simple4       0.0220058  0.0044176   4.981      0.0000006315001 ***
## dotw_simple5      -0.0205392  0.0043194  -4.755      0.0000019834995 ***
## dotw_simple6      -0.1181527  0.0043247 -27.320 < 0.0000000000000002 ***
## dotw_simple7      -0.0940563  0.0043776 -21.486 < 0.0000000000000002 ***
## Temperature        0.0070358  0.0001312  53.612 < 0.0000000000000002 ***
## Precipitation     -1.4704955  0.0384650 -38.229 < 0.0000000000000002 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.7182 on 445464 degrees of freedom
## Multiple R-squared:  0.07979,    Adjusted R-squared:  0.07973 
## F-statistic:  1246 on 31 and 445464 DF,  p-value: < 0.00000000000000022

The model uses Monday as the baseline. Each coefficient represents the difference in expected trips per station-hour compared to Monday - dow_simple2 = Tuesday..

Weekday Pattern (Tue-Fri):

  • All weekdays have positive coefficients (0.007 to 0.608)
  • Wednesday has the highest weekday effect (+0.029)
  • Weekdays likely benefit from concentrated commuting patterns

Weekend Pattern (Sat-Sun):

  • Both weekend days have negative coefficients (-0.118 and -0.094)
  • This means FEWER trips per station-hour than Monday

Hourly Interpretation

Hour Coefficient Interpretation 0 (baseline) 0.000 trips/hour (midnight) 1 -0.028 slightly fewer than midnight … 6 +0.158 morning activity starting 7 +0.304 morning rush building 8 +0.484 PEAK morning rush 9 +0.376 post-rush … 17 +0.608 PEAK evening rush (5 PM!) 18 +0.444 evening declining … 23 +0.049 late night minimal

Model 2: Add Temporal Lags

model2 <- lm(
  Trip_Count ~ as.factor(hour) + dotw_simple + Temperature + Precipitation +
    lag1Hour + lag3Hours + lag1day,
  data = train
)

summary(model2)
## 
## Call:
## lm(formula = Trip_Count ~ as.factor(hour) + dotw_simple + Temperature + 
##     Precipitation + lag1Hour + lag3Hours + lag1day, data = train)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -4.4519 -0.2470 -0.0946  0.0300 11.1281 
## 
## Coefficients:
##                     Estimate Std. Error t value             Pr(>|t|)    
## (Intercept)       -0.0920058  0.0070480 -13.054 < 0.0000000000000002 ***
## as.factor(hour)1  -0.0132102  0.0066959  -1.973             0.048511 *  
## as.factor(hour)2  -0.0024910  0.0067675  -0.368             0.712811    
## as.factor(hour)3  -0.0208350  0.0068627  -3.036             0.002398 ** 
## as.factor(hour)4   0.0064110  0.0067119   0.955             0.339491    
## as.factor(hour)5   0.0551125  0.0067626   8.150 0.000000000000000366 ***
## as.factor(hour)6   0.1442512  0.0066896  21.564 < 0.0000000000000002 ***
## as.factor(hour)7   0.2316881  0.0068170  33.987 < 0.0000000000000002 ***
## as.factor(hour)8   0.3405440  0.0065971  51.621 < 0.0000000000000002 ***
## as.factor(hour)9   0.1581361  0.0067347  23.481 < 0.0000000000000002 ***
## as.factor(hour)10  0.1230812  0.0066256  18.577 < 0.0000000000000002 ***
## as.factor(hour)11  0.1445921  0.0065409  22.106 < 0.0000000000000002 ***
## as.factor(hour)12  0.1941731  0.0065186  29.788 < 0.0000000000000002 ***
## as.factor(hour)13  0.2100062  0.0066413  31.621 < 0.0000000000000002 ***
## as.factor(hour)14  0.2141320  0.0066531  32.185 < 0.0000000000000002 ***
## as.factor(hour)15  0.2537284  0.0066161  38.350 < 0.0000000000000002 ***
## as.factor(hour)16  0.2849932  0.0066197  43.052 < 0.0000000000000002 ***
## as.factor(hour)17  0.3844077  0.0066086  58.168 < 0.0000000000000002 ***
## as.factor(hour)18  0.1871858  0.0066621  28.097 < 0.0000000000000002 ***
## as.factor(hour)19  0.1086558  0.0067927  15.996 < 0.0000000000000002 ***
## as.factor(hour)20  0.0290851  0.0068172   4.266 0.000019865510120456 ***
## as.factor(hour)21  0.0540361  0.0066546   8.120 0.000000000000000467 ***
## as.factor(hour)22  0.0416605  0.0067647   6.158 0.000000000735060990 ***
## as.factor(hour)23  0.0245186  0.0067170   3.650             0.000262 ***
## dotw_simple2      -0.0287872  0.0037245  -7.729 0.000000000000010851 ***
## dotw_simple3       0.0106808  0.0038627   2.765             0.005691 ** 
## dotw_simple4      -0.0035308  0.0038896  -0.908             0.364002    
## dotw_simple5      -0.0195895  0.0038010  -5.154 0.000000255476506511 ***
## dotw_simple6      -0.0699725  0.0038089 -18.371 < 0.0000000000000002 ***
## dotw_simple7      -0.0500267  0.0038534 -12.982 < 0.0000000000000002 ***
## Temperature        0.0026179  0.0001162  22.532 < 0.0000000000000002 ***
## Precipitation     -0.5949000  0.0339493 -17.523 < 0.0000000000000002 ***
## lag1Hour           0.3835597  0.0014070 272.602 < 0.0000000000000002 ***
## lag3Hours          0.1095022  0.0013938  78.564 < 0.0000000000000002 ***
## lag1day            0.1246783  0.0013173  94.646 < 0.0000000000000002 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.6318 on 445461 degrees of freedom
## Multiple R-squared:  0.288,  Adjusted R-squared:  0.2879 
## F-statistic:  5299 on 34 and 445461 DF,  p-value: < 0.00000000000000022
# Extract R² values
r2_model1 <- summary(model1)$r.squared
r2_model2 <- summary(model2)$r.squared

adj_r2_model1 <- summary(model1)$adj.r.squared
adj_r2_model2 <- summary(model2)$adj.r.squared

# Calculate improvements
r2_improvement <- r2_model2 - r2_model1
adj_r2_improvement <- adj_r2_model2 - adj_r2_model1


# Create comparison dataframe
model_comparison <- data.frame(
  Model = c("Model 1 (No Lags)", "Model 2 (With Lags)", "Improvement"),
  R_squared = c(r2_model1, r2_model2, r2_improvement),
  Adj_R_squared = c(adj_r2_model1, adj_r2_model2, adj_r2_improvement),
  Percent_Variance = c(r2_model1 * 100, r2_model2 * 100, r2_improvement * 100)
)

print(model_comparison, row.names = FALSE)
##                Model R_squared Adj_R_squared Percent_Variance
##    Model 1 (No Lags) 0.0797915    0.07972746          7.97915
##  Model 2 (With Lags) 0.2879868    0.28793250         28.79868
##          Improvement 0.2081953    0.20820504         20.81953

Adding temporal lag variables increased the model’s explanatory power from approx. 8% to approx. 29% - a 21 percentage point improvement. In model 1, only about 8% of ridership is explained by hour-of-day, day-of-week, temperature, and precipitation. While not spectacular by any means, adding lag-hour-1, lag-hour-3, and lag-hour-day (accounting for data collected one hour ago, three hours ago, and one day ago), the model now predicts about 29% of ridership tendencies.

Model 3: Add Demographics

model3 <- lm(
  Trip_Count ~ as.factor(hour) + dotw_simple + Temperature + Precipitation +
    lag1Hour + lag3Hours + lag1day +
    Med_Inc.x + Percent_Taking_Transit.y + Percent_White.y,
  data = train
)

summary(model3)
## 
## Call:
## lm(formula = Trip_Count ~ as.factor(hour) + dotw_simple + Temperature + 
##     Precipitation + lag1Hour + lag3Hours + lag1day + Med_Inc.x + 
##     Percent_Taking_Transit.y + Percent_White.y, data = train)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -3.5811 -0.4844 -0.2214  0.2805 10.9327 
## 
## Coefficients:
##                                Estimate     Std. Error t value
## (Intercept)               0.79563703104  0.03379946736  23.540
## as.factor(hour)1          0.00993510579  0.04139646645   0.240
## as.factor(hour)2          0.01190820057  0.04742786569   0.251
## as.factor(hour)3         -0.10410141993  0.06283855887  -1.657
## as.factor(hour)4         -0.04622792618  0.04778488492  -0.967
## as.factor(hour)5          0.05490017571  0.03562865250   1.541
## as.factor(hour)6          0.25269447535  0.03095583290   8.163
## as.factor(hour)7          0.33210493879  0.02946309573  11.272
## as.factor(hour)8          0.43832208054  0.02820832586  15.539
## as.factor(hour)9          0.11048040821  0.02861806592   3.861
## as.factor(hour)10         0.07696822769  0.02874259902   2.678
## as.factor(hour)11         0.10789086743  0.02863557779   3.768
## as.factor(hour)12         0.17632310406  0.02836458898   6.216
## as.factor(hour)13         0.18275449047  0.02828971500   6.460
## as.factor(hour)14         0.17001189142  0.02817802033   6.033
## as.factor(hour)15         0.21467263669  0.02799987232   7.667
## as.factor(hour)16         0.28721232519  0.02788905915  10.298
## as.factor(hour)17         0.50038106556  0.02779108127  18.005
## as.factor(hour)18         0.19715253016  0.02817631725   6.997
## as.factor(hour)19         0.09592941590  0.02885662899   3.324
## as.factor(hour)20         0.01814080506  0.02995163252   0.606
## as.factor(hour)21         0.04087870931  0.02994088872   1.365
## as.factor(hour)22         0.06734480687  0.03139099277   2.145
## as.factor(hour)23         0.03151691816  0.03285351493   0.959
## dotw_simple2             -0.03742796491  0.01165377363  -3.212
## dotw_simple3              0.00299430719  0.01133778696   0.264
## dotw_simple4             -0.03226296781  0.01138738385  -2.833
## dotw_simple5             -0.06672081195  0.01138243814  -5.862
## dotw_simple6             -0.08906181841  0.01207393451  -7.376
## dotw_simple7             -0.05334796403  0.01221079293  -4.369
## Temperature               0.00297627230  0.00039030472   7.626
## Precipitation            -2.38881308670  0.16131889972 -14.808
## lag1Hour                  0.24939883336  0.00274630369  90.813
## lag3Hours                 0.06549188253  0.00290032270  22.581
## lag1day                   0.10616171709  0.00286484284  37.057
## Med_Inc.x                 0.00000002638  0.00000010243   0.258
## Percent_Taking_Transit.y -0.00177898711  0.00037723057  -4.716
## Percent_White.y           0.00247318709  0.00019377296  12.763
##                                      Pr(>|t|)    
## (Intercept)              < 0.0000000000000002 ***
## as.factor(hour)1                     0.810332    
## as.factor(hour)2                     0.801753    
## as.factor(hour)3                     0.097594 .  
## as.factor(hour)4                     0.333338    
## as.factor(hour)5                     0.123345    
## as.factor(hour)6         0.000000000000000331 ***
## as.factor(hour)7         < 0.0000000000000002 ***
## as.factor(hour)8         < 0.0000000000000002 ***
## as.factor(hour)9                     0.000113 ***
## as.factor(hour)10                    0.007411 ** 
## as.factor(hour)11                    0.000165 ***
## as.factor(hour)12        0.000000000511222058 ***
## as.factor(hour)13        0.000000000105167362 ***
## as.factor(hour)14        0.000000001610820730 ***
## as.factor(hour)15        0.000000000000017795 ***
## as.factor(hour)16        < 0.0000000000000002 ***
## as.factor(hour)17        < 0.0000000000000002 ***
## as.factor(hour)18        0.000000000002631454 ***
## as.factor(hour)19                    0.000887 ***
## as.factor(hour)20                    0.544735    
## as.factor(hour)21                    0.172158    
## as.factor(hour)22                    0.031927 *  
## as.factor(hour)23                    0.337402    
## dotw_simple2                         0.001320 ** 
## dotw_simple3                         0.791704    
## dotw_simple4                         0.004609 ** 
## dotw_simple5             0.000000004596663688 ***
## dotw_simple6             0.000000000000164067 ***
## dotw_simple7             0.000012500376353510 ***
## Temperature              0.000000000000024547 ***
## Precipitation            < 0.0000000000000002 ***
## lag1Hour                 < 0.0000000000000002 ***
## lag3Hours                < 0.0000000000000002 ***
## lag1day                  < 0.0000000000000002 ***
## Med_Inc.x                            0.796729    
## Percent_Taking_Transit.y 0.000002409906833269 ***
## Percent_White.y          < 0.0000000000000002 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.8735 on 89219 degrees of freedom
##   (356239 observations deleted due to missingness)
## Multiple R-squared:  0.182,  Adjusted R-squared:  0.1817 
## F-statistic: 536.6 on 37 and 89219 DF,  p-value: < 0.00000000000000022

Model 4: Add Station Fixed Effects

model4 <- lm(
  Trip_Count ~ as.factor(hour) + dotw_simple + Temperature + Precipitation +
    lag1Hour + lag3Hours + lag1day +
    Med_Inc.x + Percent_Taking_Transit.y + Percent_White.y +
    as.factor(start_station),
  data = train
)

# Summary too long with all station dummies, just show key metrics
cat("Model 4 R-squared:", summary(model4)$r.squared, "\n")
## Model 4 R-squared: 0.2071224
cat("Model 4 Adj R-squared:", summary(model4)$adj.r.squared, "\n")
## Model 4 Adj R-squared: 0.2047524

Station fixed effects capture baseline differences in demand across Indego stations that aren’t explained by Census tract demographics, weather, or temporal patterns.

Model 5: Add Rush Hour Interaction

model5 <- lm(
  Trip_Count ~ as.factor(hour) + dotw_simple + Temperature + Precipitation +
    lag1Hour + lag3Hours + lag1day + rush_hour + 
    Med_Inc.x + Percent_Taking_Transit.y + Percent_White.y +
    as.factor(start_station) +
    rush_hour * weekend,  # Rush hour effects different on weekends
  data = train
)

cat("Model 5 R-squared:", summary(model5)$r.squared, "\n")
## Model 5 R-squared: 0.2112196
cat("Model 5 Adj R-squared:", summary(model5)$adj.r.squared, "\n")
## Model 5 Adj R-squared: 0.208853

Model Evaluation

Calculate Predictions and MAE

# Get predictions on test set

# Create day of week factor with treatment (dummy) coding
test <- test %>%
  mutate(dotw_simple = factor(dotw, levels = c("Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun")))

# Set contrasts to treatment coding (dummy variables)
contrasts(test$dotw_simple) <- contr.treatment(7)

test <- test %>%
  mutate(
    pred1 = predict(model1, newdata = test),
    pred2 = predict(model2, newdata = test),
    pred3 = predict(model3, newdata = test),
    pred4 = predict(model4, newdata = test),
    pred5 = predict(model5, newdata = test)
  )

# Calculate MAE for each model
mae_results <- data.frame(
  Model = c(
    "1. Time + Weather",
    "2. + Temporal Lags",
    "3. + Demographics",
    "4. + Station FE",
    "5. + Rush Hour Interaction"
  ),
  MAE = c(
    mean(abs(test$Trip_Count - test$pred1), na.rm = TRUE),
    mean(abs(test$Trip_Count - test$pred2), na.rm = TRUE),
    mean(abs(test$Trip_Count - test$pred3), na.rm = TRUE),
    mean(abs(test$Trip_Count - test$pred4), na.rm = TRUE),
    mean(abs(test$Trip_Count - test$pred5), na.rm = TRUE)
  )
)

kable(mae_results, 
      digits = 2,
      caption = "Mean Absolute Error by Model (Test Set)",
      col.names = c("Model", "MAE (trips)")) %>%
  kable_styling(bootstrap_options = c("striped", "hover"))
Mean Absolute Error by Model (Test Set)
Model MAE (trips)
  1. Time + Weather
0.71
    • Temporal Lags
0.58
    • Demographics
0.84
    • Station FE
0.83
    • Rush Hour Interaction
0.83

Visualize Model Comparison

ggplot(mae_results, aes(x = reorder(Model, -MAE), y = MAE)) +
  geom_col(fill = "#E1DCF5", alpha = 0.8) +
  geom_text(aes(label = round(MAE, 2)), vjust = -0.5) +
  labs(
    title = "Model Performance Comparison",
    subtitle = "Lower MAE = Better Predictions",
    x = "Model",
    y = "Mean Absolute Error (trips)"
  ) +
  plotTheme +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))

The temporal lag variables provided the largest improvement in predictive accuracy. Moving from Model 1 (time + weather only) to Model 2(adding lag1Hour, lag3Hours, lag1Day) reduced the MAE from 0.71 to 0.58, a solid improvement. The temporal lags are meant to capture “if X riders departed from Station #3010, for example, 1 hour ago, then X riders will depart from Station #3010 at the same time in the next hour.” Or, stations that were busy one hour ago are likely to remain busy in the next hour. Interestingly, adding demographic variables, station fixed effects, rush hour interactions, and later precipitation forecast, distance to nearest university, as well as station-type clustering feature implementation interactions actually increased the MAE, suggesting that these additions led to overfitting on the training data.


Space-Time Error Analysis

Observed vs. Predicted

Let’s use our best model (Model 2, temporal lag) for error analysis.

test <- test %>%
  mutate(
    error = Trip_Count - pred2,
    abs_error = abs(error),
    time_of_day = case_when(
      hour < 7 ~ "Overnight",
      hour >= 7 & hour < 10 ~ "AM Rush",
      hour >= 10 & hour < 15 ~ "Mid-Day",
      hour >= 15 & hour <= 18 ~ "PM Rush",
      hour > 18 ~ "Evening"
    )
  )

# Scatter plot by time and day type
ggplot(test, aes(x = Trip_Count, y = pred2)) +
  geom_point(alpha = 0.2, color = "#6849BA") +
  geom_abline(slope = 1, intercept = 0, color = "red", linewidth = 1) +
  geom_smooth(method = "lm", se = FALSE, color = "darkgreen") +
  facet_grid(weekend ~ time_of_day) +
  labs(
    title = "Observed vs. Predicted Bike Trips",
    subtitle = "Model 2 performance by time period",
    x = "Observed Trips",
    y = "Predicted Trips",
    caption = "Red line = perfect predictions; Green line = actual model fit"
  ) +
  plotTheme
## `geom_smooth()` using formula = 'y ~ x'
## Warning: Removed 5592 rows containing non-finite outside the scale range
## (`stat_smooth()`).
## Warning: Removed 5592 rows containing missing values or values outside the scale range
## (`geom_point()`).

The observed vs. predicted plots reveal systematic patterns in Model 2’s errors. The model performs best during overnight hours, where demand is consistently near zero and predictions tightly cluster along the perfect-prediction line. Performance degrades substantially during peak periods—particularly PM Rush on weekdays—where the green regression line diverges sharply from the red diagonal. The model exhibits a clear “ceiling effect,” rarely predicting more than 6-8 trips per station-hour even when actual demand reaches 20+ trips. This systematic underprediction of high-demand observations is operationally concerning: out-of-stock risk is highest precisely when demand spikes, and those are the predictions where accuracy matters most for rebalancing decisions. Weekend predictions (bottom row) show less extreme divergence, likely because weekend demand is lower and more evenly distributed throughout the day.

Spatial Error Patterns

Are prediction errors clustered in certain parts of Philadelphia?

# Calculate station errors
station_errors <- test %>%
  filter(!is.na(pred2)) %>%
  group_by(start_station, start_lat.x, start_lon.y) %>%
  summarize(
    MAE = mean(abs(Trip_Count - pred2), na.rm = TRUE),
    avg_demand = mean(Trip_Count, na.rm = TRUE),
    total_trips = sum(Trip_Count),
    .groups = "drop"
  ) %>%
  filter(!is.na(start_lat.x), !is.na(start_lon.y))

# Map 1: Prediction Errors
p1 <- ggplot() +
  geom_sf(data = philly_census, fill = "#f0f0f0", color = "white", linewidth = 0.3) +
  geom_point(
    data = station_errors,
    aes(x = start_lon.y, y = start_lat.x, size = total_trips, color = MAE),
    alpha = 0.8
  ) +
  scale_color_gradientn(
    colors = rev(palette5),  # Use your palette
    name = "Mean Absolute\nError (trips)",
    breaks = seq(0, max(station_errors$MAE, na.rm = TRUE), length.out = 4),
    labels = function(x) round(x, 2)
  ) +
  scale_size_continuous(
    name = "Total Trips",
    range = c(2, 8),
    breaks = c(100, 500, 1000, 2000),
    labels = scales::comma
  ) +
  labs(
    title = "Model Prediction Errors by Station",
    subtitle = "Larger points = more trips; Darker = higher error"
  ) +
  coord_sf(xlim = c(-75.25, -75.00), ylim = c(39.88, 40.08)) +
  mapTheme

# Map 2: Average Demand
p2 <- ggplot() +
  geom_sf(data = philly_census, fill = "#f0f0f0", color = "white", linewidth = 0.3) +
  geom_point(
    data = station_errors,
    aes(x = start_lon.y, y = start_lat.x, size = total_trips, color = avg_demand),
    alpha = 0.8
  ) +
  scale_color_gradientn(
    colors = c("#08519c", "#3182bd", "#6baed6", "#bdd7e7", "#eff3ff"),
    name = "Average\nDemand\n(trips/hour)",
    breaks = seq(0, max(station_errors$avg_demand, na.rm = TRUE), length.out = 4),
    labels = function(x) round(x, 2)
  ) +
  scale_size_continuous(
    name = "Total Trips",
    range = c(2, 8),
    breaks = c(100, 500, 1000, 2000),
    labels = scales::comma
  ) +
  labs(
    title = "Average Demand by Station",
    subtitle = "Darker blue = higher ridership concentration"
  ) +
  coord_sf(xlim = c(-75.25, -75.00), ylim = c(39.88, 40.08)) +
  mapTheme

# Combine with patchwork
p1 + p2 +
  plot_annotation(
    title = "Model 2 Performance: Spatial Distribution of Errors and Demand",
    subtitle = "Philadelphia Indego Bike Share - 2024",
    caption = "Source: Indego bike share trip data; Model 2 predictions (temporal lags)",
    theme = theme(
      plot.title = element_text(size = 14, face = "bold", hjust = 0.5),
      plot.subtitle = element_text(size = 10, hjust = 0.5),
      plot.caption = element_text(size = 8)
    )
  )

The prediction errors show spatial clustering in Center City and University City having high MAE concentrations. This pattern suggests that the model struggles with high-volume stations that experience rapid demand fluctuations. Neighborhoods like East Passyunk, Point Breeze, Queen Village, Market East, and Northern Liberties/Fishtown show areas where demand is less and also more stable. The spatial concentration of errors in the urban core is operationally significant because these are the stations most likely to experience stockouts and where rebalancing decisions are most consequential.

Temporal Error Patterns

When are we most wrong?

# MAE by time of day and day type
temporal_errors <- test %>%
  group_by(time_of_day, weekend) %>%
  summarize(
    MAE = mean(abs_error, na.rm = TRUE),
    .groups = "drop"
  ) %>%
  mutate(day_type = ifelse(weekend == 1, "Weekend", "Weekday"))

ggplot(temporal_errors, aes(x = time_of_day, y = MAE, fill = day_type)) +
  geom_col(position = "dodge") +
  scale_fill_manual(values = c("Weekday" = "#08519c", "Weekend" = "#6baed6")) +
  labs(
    title = "Prediction Errors by Time Period",
    subtitle = "When is the model struggling most?",
    x = "Time of Day",
    y = "Mean Absolute Error (trips)",
    fill = "Day Type"
  ) +
  plotTheme +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))

Errors and Demographics

Are prediction errors related to neighborhood characteristics?

# Join demographic data to station errors
station_errors_demo <- station_errors %>%
  left_join(
    station_attributes %>% dplyr::select(start_station, Med_Inc, Percent_Taking_Transit, Percent_White),
    by = "start_station"
  ) %>%
  filter(!is.na(Med_Inc))

# Create plots
p1 <- ggplot(station_errors_demo, aes(x = Med_Inc, y = MAE)) +
  geom_point(alpha = 0.5, color = "#E1DCF5") +
  geom_smooth(method = "lm", se = FALSE, color = "red") +
  scale_x_continuous(labels = scales::dollar) +
  labs(title = "Errors vs. Median Income", x = "Median Income", y = "MAE") +
  plotTheme

p2 <- ggplot(station_errors_demo, aes(x = Percent_Taking_Transit, y = MAE)) +
  geom_point(alpha = 0.5, color = "#E1DCF5") +
  geom_smooth(method = "lm", se = FALSE, color = "red") +
  labs(title = "Errors vs. Transit Usage", x = "Percent Taking Transit", y = "MAE") +
  plotTheme

p3 <- ggplot(station_errors_demo, aes(x = Percent_White, y = MAE)) +
  geom_point(alpha = 0.5, color = "#E1DCF5") +
  geom_smooth(method = "lm", se = FALSE, color = "red") +
  labs(title = "Errors vs. Race", x = "Percent White", y = "MAE") +
  plotTheme

grid.arrange(p1, p2, p3, ncol = 2)
## `geom_smooth()` using formula = 'y ~ x'
## `geom_smooth()` using formula = 'y ~ x'
## `geom_smooth()` using formula = 'y ~ x'

The demographic error analysis reveals nuanced patterns in model performance across neighborhoods. Median income shows virtually no relationship with prediction error—the regression line is nearly flat, suggesting the model neither advantages nor disadvantages stations based on neighborhood wealth. Transit usage shows the clearest pattern: stations in areas with higher transit commuter rates have substantially lower MAE (around 0.6) compared to car-dependent areas (MAE exceeding 1.0). This suggests the model performs best in dense, transit-oriented urban core neighborhoods where bike share integrates into predictable multimodal commuting patterns. Perhaps counterintuitively, the percent white relationship shows a slight positive slope—errors are marginally higher in whiter neighborhoods. This likely reflects that whiter areas in Philadelphia (Center City, Rittenhouse) also have higher ridership volumes and greater demand variability, leading to larger absolute errors. From an equity standpoint, these patterns are relatively reassuring: the model does not systematically underserve lower-income neighborhoods or communities of color. However, the strong transit-usage relationship suggests that peripheral, car-dependent neighborhoods—which may already have limited transportation options—receive less accurate predictions, potentially affecting service quality in areas that could most benefit from reliable bike share access.

Feature Implementation

Feature Selection Rationale

I engineered three new features targeting specific weaknesses in the baseline model:

1. Recent Rain (Precipitation Lag) The baseline model includes current precipitation, but people don’t immediately resume biking when rain stops. Obstacles like wet streets and wet seats influence rider choice. This “hangover effect” motivated creating a binary indicator for whether precipitation occurred in the previous 3 hours. The visualization confirms that ridership remains suppressed even after rain stops.

2. Distance to Nearest University The spatial error analysis showed elevated errors near University City, suggesting campus-related demand patterns the model wasn’t capturing. Universities generate distinctive ridership: class schedules create predictable hourly peaks, academic calendars can shift demand seasonally, and student demographics differ from typical commuters. I calculated distance from each station to the University of Pennsylvania, Drexel University, and Temple University, flagging stations within 0.5 miles as “near university.” Hub campuses like Temple University Center City campus and Saint Joesph’s Hawk Hill campus in University City are excluded.

3. Station Type Clustering Not all stations serve the same purpose. K-means clustering on hourly demand profiles revealed three distinct station types: Commuter Hubs (strong AM/PM peaks), Residential stations (more distributed demand), and Low Activity stations (consistently low ridership). This categorical feature allows the model to learn different demand dynamics for different station types rather than treating all stations identically.

Feature 1: Precipitation Forecast

study_panel_complete <- study_panel_complete %>%
  arrange(start_station, interval60) %>%
  group_by(start_station) %>%
  mutate(
    # Rolling 3-hour precipitation sum
    precip_last_3hrs = lag(Precipitation, 1) + lag(Precipitation, 2) + lag(Precipitation, 3),
    # Binary: any precipitation in last 3 hours?
    recent_rain = ifelse(precip_last_3hrs > 0, 1, 0),
    recent_rain = replace_na(recent_rain, 0)
  ) %>%
  ungroup()


# Add to train/test
train <- study_panel_complete %>%
  filter(week < 10, start_station %in% common_stations)

test <- study_panel_complete %>%
  filter(week >= 10, start_station %in% common_stations)

# Recreate dotw_simple for both
train <- train %>%
  mutate(dotw_simple = factor(dotw, levels = c("Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun")))
contrasts(train$dotw_simple) <- contr.treatment(7)

test <- test %>%
  mutate(dotw_simple = factor(dotw, levels = c("Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun")))
contrasts(test$dotw_simple) <- contr.treatment(7)

# Visualize the impact
train %>%
  filter(!is.na(recent_rain)) %>%
  group_by(hour, recent_rain) %>%
  summarize(avg_trips = mean(Trip_Count), .groups = "drop") %>%
  ggplot(aes(x = hour, y = avg_trips, color = factor(recent_rain))) +
  geom_line(linewidth = 1.2) +
  scale_color_manual(
    values = c("0" = "#3182bd", "1" = "grey60"),
    labels = c("No Recent Rain", "Rain in Last 3 Hours"),
    name = ""
  ) +
  labs(
    title = "Impact of Recent Precipitation on Demand",
    subtitle = "Ridership remains suppressed even after rain stops",
    x = "Hour of Day",
    y = "Average Trips per Station"
  ) +
  plotTheme +
  theme(legend.position = "bottom")


Feature 2: Distance to Nearest University

# Philadelphia university coordinates
universities <- data.frame(
  name = c("Penn", "Drexel", "Temple"),
  lon = c(-75.1932,  -75.1897,  -75.1492),
  lat = c(39.9522,   39.9566,   39.9812)
)%>%
  st_as_sf(coords = c("lon", "lat"), crs = 4326)

# Get unique stations as sf object
stations_sf_dist <- study_panel_complete %>%
  distinct(start_station, start_lat.x, start_lon.x) %>%
  filter(!is.na(start_lat.x), !is.na(start_lon.x)) %>%
  st_as_sf(coords = c("start_lon.x", "start_lat.x"), crs = 4326)

dist_to_penn <- st_distance(stations_sf_dist, universities[1, ]) / 1609.34  # meters to miles
dist_to_drexel <- st_distance(stations_sf_dist, universities[2, ]) / 1609.34
dist_to_temple <- st_distance(stations_sf_dist, universities[3, ]) / 1609.34

# Add distances to dataframe
station_uni_dist <- stations_sf_dist %>%
  st_drop_geometry() %>%
  bind_cols(
    dist_to_penn = as.numeric(dist_to_penn),
    dist_to_drexel = as.numeric(dist_to_drexel),
    dist_to_temple = as.numeric(dist_to_temple)
  ) %>%
  mutate(
    dist_to_nearest_uni = pmin(dist_to_penn, dist_to_drexel, dist_to_temple),
    near_university = ifelse(dist_to_nearest_uni < 0.5, 1, 0)  # Within 0.5 miles
  )

# Need to get the coordinates back for mapping
station_uni_dist <- study_panel_complete %>%
  distinct(start_station, start_lat.x, start_lon.x) %>%
  filter(!is.na(start_lat.x), !is.na(start_lon.x)) %>%
  left_join(station_uni_dist, by = "start_station")

cat("Stations with distance calculated:", nrow(station_uni_dist), "\n\n")
## Stations with distance calculated: 233
# Join back to datasets
train <- train %>%
  left_join(station_uni_dist %>% select(start_station, dist_to_nearest_uni, near_university),
            by = "start_station")
test <- test %>%
  left_join(station_uni_dist %>% select(start_station, dist_to_nearest_uni, near_university),
            by = "start_station")

# Extract university coordinates for plotting
uni_coords <- universities %>%
  st_coordinates() %>%
  as.data.frame() %>%
  bind_cols(name = c("Penn", "Drexel", "Temple"))



# Map university proximity
ggplot() +
  geom_sf(data = philly_census, fill = "#f0f0f0", color = "white", linewidth = 0.3) +
  # Universities
  geom_point(
    data = uni_coords,
    aes(x = X, y = Y),
    color = "#E1DCF5", size = 8, shape = 18  # Diamond shape
  ) +
  geom_text(
    data = uni_coords,
    aes(x = X, y = Y, label = name),
    vjust = -1.5, size = 3.5, fontface = "bold"
  ) +
  # Stations colored by university proximity
  geom_point(
    data = station_uni_dist,
    aes(x = start_lon.x, y = start_lat.x, color = dist_to_nearest_uni, size = near_university),
    alpha = 0.7
  ) +
  scale_color_gradientn(
    colors = rev(palette5),
    name = "Distance to\nNearest Uni\n(miles)",
    limits = c(0, 5)
  ) +
  scale_size_continuous(range = c(1.5, 4), guide = "none") +
  labs(
    title = "Indego Stations by University Proximity",
    subtitle = "Larger points = within 0.5 miles of university"
  ) +
  coord_sf(xlim = c(-75.25, -75.00), ylim = c(39.88, 40.08)) +
  mapTheme

# Compare demand near vs far from universities
train %>%
  group_by(hour, near_university, dotw) %>%
  summarize(avg_trips = mean(Trip_Count), .groups = "drop") %>%
  filter(dotw %in% c("Mon", "Tue", "Wed", "Thu", "Fri")) %>%
  ggplot(aes(x = hour, y = avg_trips, color = factor(near_university))) +
  geom_line(linewidth = 1.2) +
  scale_color_manual(
    values = c("0" = "grey60", "1" = "#6849BA"),
    labels = c("Away from Universities", "Near Universities (< 0.5 mi)"),
    name = ""
  ) +
  labs(
    title = "Ridership Patterns: University vs Non-University Stations",
    subtitle = "Weekdays only - University stations show distinct peaks",
    x = "Hour of Day",
    y = "Average Trips per Station"
  ) +
  plotTheme +
  theme(legend.position = "bottom") 


Feature 3: Station-type Clustering

# Create hourly demand profiles for each station
station_profiles <- train %>%
  group_by(start_station, hour) %>%
  summarize(
    avg_demand = mean(Trip_Count),
    weekend_demand = mean(Trip_Count[weekend == 1]),
    weekday_demand = mean(Trip_Count[weekend == 0]),
    .groups = "drop"
  ) %>%
  pivot_wider(
    id_cols = start_station,
    names_from = hour,
    values_from = avg_demand,
    names_prefix = "hour_"
  )

# K-means clustering (3 types)
set.seed(123)
station_clusters <- station_profiles %>%
  select(starts_with("hour_")) %>%
  scale() %>%
  kmeans(centers = 3, nstart = 25)

# Add cluster labels
station_profiles$station_type <- station_clusters$cluster

# Interpret clusters
cluster_summary <- train %>%
  left_join(
    station_profiles %>% select(start_station, station_type),
    by = "start_station"
  ) %>%
  group_by(station_type, hour) %>%
  summarize(avg_demand = mean(Trip_Count), .groups = "drop")

 #Visualize cluster patterns
ggplot(cluster_summary, aes(x = hour, y = avg_demand, color = factor(station_type))) +
  geom_line(linewidth = 1.2) +
  scale_color_manual(
    values = c("1" = palette5[5], "2" = palette5[3], "3" = palette5[1]),
    name = "Station Type",
    labels = c("Type 1", "Type 2", "Type 3")
  ) +
  labs(
    title = "Station Types Based on Temporal Demand Patterns",
    subtitle = "K-means clustering reveals distinct usage profiles",
    x = "Hour of Day",
    y = "Average Trips per Station"
  ) +
  plotTheme +
  theme(legend.position = "bottom")

# Label the clusters based on patterns
station_profiles <- station_profiles %>%
  mutate(
    station_type_label = case_when(
      station_type == 1 ~ "Commuter Hub",      # High AM/PM peaks
      station_type == 2 ~ "Residential",       # Moderate, spread out
      station_type == 3 ~ "Low Activity"       # Consistently low
    )
  )

# Map station types
station_map_data <- train %>%
  distinct(start_station, start_lat.x, start_lon.x) %>%
  left_join(station_profiles %>% select(start_station, station_type_label),
            by = "start_station")

ggplot() +
  geom_sf(data = philly_census, fill = "#f0f0f0", color = "white", linewidth = 0.3) +
  geom_point(
    data = station_map_data,
    aes(x = start_lon.x, y = start_lat.x, color = station_type_label),
    size = 3, alpha = 0.7
  ) +
  scale_color_manual(
    values = c(
      "Commuter Hub" = palette5[5],
      "Residential" = palette5[3],
      "Low Activity" = palette5[1]
    ),
    name = "Station Type"
  ) +
  labs(
    title = "Station Types Across Philadelphia",
    subtitle = "Clustered by temporal demand patterns"
  ) +
  coord_sf(xlim = c(-75.25, -75.00), ylim = c(39.88, 40.08)) +
  mapTheme


Model Improvements

Now we’ll test whether our three engineered features actually improve predictions.

Model 6: Add Engineered Features

We add our three new features to Model 5 (our best OLS model so far):

  1. recent_rain: Binary indicator if precipitation occurred in the last 3 hours
  2. near_university: Binary indicator if station is within 0.5 miles of Penn, Drexel, or Temple
  3. station_type_label: Categorical variable from k-means clustering (Commuter Hub, Residential, Low Activity)
# Add to train/test
train <- study_panel_complete %>%
  filter(week < 10, start_station %in% common_stations)

test <- study_panel_complete %>%
  filter(week >= 10, start_station %in% common_stations)

# Recreate dotw_simple for both
train <- train %>%
  mutate(dotw_simple = factor(dotw, levels = c("Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun")))
contrasts(train$dotw_simple) <- contr.treatment(7)

test <- test %>%
  mutate(dotw_simple = factor(dotw, levels = c("Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun")))
contrasts(test$dotw_simple) <- contr.treatment(7)

# ADD THIS HERE - Re-join university data and station clusters
train <- train %>%
  left_join(station_uni_dist %>% dplyr::select(start_station, dist_to_nearest_uni, near_university),
            by = "start_station") %>%
  left_join(station_profiles %>% dplyr::select(start_station, station_type_label),
            by = "start_station")

test <- test %>%
  left_join(station_uni_dist %>% dplyr::select(start_station, dist_to_nearest_uni, near_university),
            by = "start_station") %>%
  left_join(station_profiles %>% dplyr::select(start_station, station_type_label),
            by = "start_station")



model6 <- lm(
  Trip_Count ~ as.factor(hour) + dotw_simple + Temperature + Precipitation +
    lag1Hour + lag3Hours + lag1day + rush_hour + 
    Med_Inc.x + Percent_Taking_Transit.x + Percent_White.x +
    as.factor(start_station) +
    rush_hour * weekend +
    recent_rain +
    near_university +
    station_type_label,
  data = train
)



cat("Model 6 Adj R-squared:", round(summary(model6)$adj.r.squared, 4), "\n")
## Model 6 Adj R-squared: 0.2135
# Calculate MAE on test set
test$pred6 <- predict(model6, newdata = test)
mae_model6 <- mean(abs(test$Trip_Count - test$pred6), na.rm = TRUE)
cat("Model 6 MAE:", round(mae_model6, 3), "\n")
## Model 6 MAE: 0.832
# Compare to best model (Model 2)
cat("\nComparison to Model 2 (best baseline):\n")
## 
## Comparison to Model 2 (best baseline):
cat("Model 2 MAE: 0.577\n")
## Model 2 MAE: 0.577
cat("Model 6 MAE:", round(mae_model6, 3), "\n")
## Model 6 MAE: 0.832
cat("Difference:", round(mae_model6 - 0.577, 3), "(positive = worse)\n")
## Difference: 0.255 (positive = worse)

Despite these theoretically-motivated features, Model 6 (OLS with new features) achieved an MAE of 0.832—actually worse than the simple temporal lag model (0.577). This suggests overfitting: the new features may capture patterns present in the training period that don’t generalize to the test period.

Model 7: Poisson

# Poisson regression - better for count data
model7_poisson <- glm(
  Trip_Count ~ as.factor(hour) + dotw_simple + Temperature + Precipitation +
    lag1Hour + lag3Hours + lag1day + rush_hour + 
    Med_Inc.x + Percent_Taking_Transit.y + Percent_White.y +
    as.factor(start_station) +
    rush_hour * weekend +
    recent_rain +
    near_university +
    station_type_label,
  data = train,
  family = poisson(link = "log")
)

# Check for overdispersion
dispersion <- model7_poisson$deviance / model7_poisson$df.residual
cat("Poisson Dispersion Ratio:", round(dispersion, 2), "\n")
## Poisson Dispersion Ratio: 0.34
cat("(Should be ~1; if >> 1, data is overdispersed)\n")
## (Should be ~1; if >> 1, data is overdispersed)

The Poisson regression (MAE = 0.824) performed better than Model 6 but still worse than Model 2. The Poisson regression yielded a dispersion ratio of 0.34, indicating the data is underdispersed. Trip counts have less variability than a Poisson distribution would predict. This suggests bike share demand is fairly regular and predictable at the hourly level, likely because strong temporal patterns (commute times, weather effects) constrain the variance.

Comparison

test <- test %>%
  mutate(dotw_simple = factor(dotw, levels = c("Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun")))

contrasts(test$dotw_simple) <- contr.treatment(7)

test <- test %>%
  mutate(
    pred1 = predict(model1, newdata = test),
    pred2 = predict(model2, newdata = test),
    pred3 = predict(model3, newdata = test),
    pred4 = predict(model4, newdata = test),
    pred5 = predict(model5, newdata = test),
    pred6 = predict(model6, newdata = test),
    pred7_poisson = predict(model7_poisson, newdata = test, type = "response")
  )

mae_results <- data.frame(
  Model = c(
    "1. Time + Weather",
    "2. + Temporal Lags",
    "3. + Demographics",
    "4. + Station FE",
    "5. + Rush Hour Interaction",
    "6. + New Features (OLS)",
    "7. Poisson"
  ),
  MAE = c(
    mean(abs(test$Trip_Count - test$pred1), na.rm = TRUE),
    mean(abs(test$Trip_Count - test$pred2), na.rm = TRUE),
    mean(abs(test$Trip_Count - test$pred3), na.rm = TRUE),
    mean(abs(test$Trip_Count - test$pred4), na.rm = TRUE),
    mean(abs(test$Trip_Count - test$pred5), na.rm = TRUE),
    mean(abs(test$Trip_Count - test$pred6), na.rm = TRUE),
    mean(abs(test$Trip_Count - test$pred7_poisson), na.rm = TRUE)
  )
)

kable(mae_results, 
      digits = 3,
      caption = "Mean Absolute Error by Model (Test Set)",
      col.names = c("Model", "MAE (trips)")) %>%
  kable_styling(bootstrap_options = c("striped", "hover"))
Mean Absolute Error by Model (Test Set)
Model MAE (trips)
  1. Time + Weather
0.709
    • Temporal Lags
0.577
    • Demographics
0.844
    • Station FE
0.831
    • Rush Hour Interaction
0.827
    • New Features (OLS)
0.832
  1. Poisson
0.824

Despite the inclusion of new spatial features, the second model highlighting the temporal lags yield the lowest MAE (0.577), making it the best model of the analysis. The high MAE of new feature regressions could be indicative of overfitting.

Part 4: Critical Reflection

Let’s review the implications of these findings.

# Make sure pred2 exists for all diagnostics
test$pred2 <- predict(model2, newdata = test)


# What's the distribution of actual trip counts?
cat("Trip Count Summary:\n")
## Trip Count Summary:
summary(test$Trip_Count)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##  0.0000  0.0000  0.0000  0.5998  1.0000 28.0000
cat("\n\nTrip count distribution:\n")
## 
## 
## Trip count distribution:
table(cut(test$Trip_Count, breaks = c(-1, 0, 2, 5, 10, 15, Inf),
          labels = c("0", "1-2", "3-5", "6-10", "11-15", "16+")))
## 
##       0     1-2     3-5    6-10   11-15     16+ 
## 1399403  499960  121034   18646    1036      69
# What % of station-hours have 0 trips?
cat("\n\nPercent of station-hours with 0 trips:", 
    round(mean(test$Trip_Count == 0) * 100, 1), "%\n")
## 
## 
## Percent of station-hours with 0 trips: 68.6 %
# Average demand at different station types
cat("\n\nAverage trips by station type:\n")
## 
## 
## Average trips by station type:
test %>%
  group_by(station_type_label) %>%
  summarize(
    avg_trips = mean(Trip_Count),
    median_trips = median(Trip_Count),
    max_trips = max(Trip_Count)
  ) %>%
  print()
## # A tibble: 3 × 4
##   station_type_label avg_trips median_trips max_trips
##   <chr>                  <dbl>        <dbl>     <int>
## 1 Commuter Hub           0.831            0        23
## 2 Low Activity           0.292            0        28
## 3 Residential            1.49             1        24
# Relative error at high vs low demand hours
cat("\n\nMAE by demand level:\n")
## 
## 
## MAE by demand level:
test %>%
  mutate(
    demand_level = case_when(
      Trip_Count == 0 ~ "Zero trips",
      Trip_Count <= 2 ~ "Low (1-2)",
      Trip_Count <= 5 ~ "Medium (3-5)",
      TRUE ~ "High (6+)"
    ),
    pred2 = predict(model2, newdata = cur_data()),  # recalculate inline
    error = abs(Trip_Count - pred2)
  ) %>%
  group_by(demand_level) %>%
  summarize(
    n = n(),
    avg_actual = mean(Trip_Count),
    MAE = mean(error, na.rm = TRUE),
    relative_error_pct = mean(error / pmax(Trip_Count, 0.5)) * 100
  )
# Error during rush hour vs not
cat("\n\nMAE by time period:\n")
## 
## 
## MAE by time period:
test %>%
  mutate(error = abs(Trip_Count - pred2)) %>%
  group_by(rush_hour) %>%
  summarize(
    avg_trips = mean(Trip_Count),
    MAE = mean(error)
  ) %>%
  print()
## # A tibble: 2 × 3
##   rush_hour avg_trips   MAE
##       <dbl>     <dbl> <dbl>
## 1         0     0.472    NA
## 2         1     0.988    NA

Operational Implications This model achieves a best-case MAE of 0.577 trips per station-hour using only temporal lag features (Model 2). At first glance this seems operationally useful, but context is critical: 68.6% of station-hours have zero trips, and the median is also zero. The mean of just 0.6 trips per station-hour means most predictions are for very low-demand situations. When we examine MAE by demand level, the model performs well for quiet periods (MAE = 0.35 for zero-trip hours) but struggles dramatically during high-demand periods (MAE = 4.97 for stations with 6+ trips). This “ceiling effect” is operationally concerning—the model underpredicts precisely when stockout risk is highest.

For Indego’s rebalancing operations, I would recommend deploying this model as a screening tool rather than an automated decision-maker. The model reliably identifies which stations will remain quiet, freeing operations staff to focus attention on the ~31% of station-hours with actual activity. However, for high-volume stations during peak periods, predictions should be treated as lower bounds rather than point estimates. A risk-averse deployment strategy might add a buffer to predictions at Residential stations (which average 1.49 trips/hour—nearly double Commuter Hubs at 0.83) and flag any station predicted above 3 trips as “high priority” for monitoring.

Equity Considerations The error analysis revealed that prediction errors do not systematically disadvantage lower-income neighborhoods or communities of color—if anything, errors are slightly higher in wealthier, whiter areas due to their higher ridership volumes. The strongest demographic pattern was transit usage: stations in transit-rich areas (60%+ taking transit) had substantially lower MAE than car-dependent areas. This suggests the model works best in the dense urban core where bike share integrates into predictable multimodal commuting.

From an equity standpoint, this pattern raises concerns for peripheral neighborhoods. Areas with lower transit usage—which may already have limited transportation options—receive less accurate predictions, potentially affecting service quality where reliable bike share access could provide the most benefit. To mitigate these risks, I would recommend: (1) establishing minimum service levels for all stations regardless of predicted demand; (2) regularly auditing model performance across demographic groups; (3) applying conservative prediction buffers in underserved areas to avoid systematic under-allocation; and (4) supplementing algorithmic predictions with community input about where bikes are actually needed.

Model Limitations Several important patterns remain outside this model’s predictive capacity. The station type analysis revealed unexpected results: “Low Activity” stations had the highest maximum trips (28), suggesting occasional demand spikes the model cannot anticipate—likely from special events or irregular usage patterns. The model also cannot account for Phillies games, university graduations, SEPTA disruptions, or construction detours that temporarily shift demand.

Perhaps most fundamentally, the model assumes future demand mirrors historical patterns. With 68.6% of observations being zeros, the model is essentially learning “most station-hours are quiet”—which is true but not operationally useful. The real value would come from accurately predicting the 1-2% of station-hours with 6+ trips, but that’s exactly where MAE is worst (4.97 trips). With additional time and data, I would prioritize: building separate models for high-volume vs. low-volume stations; incorporating event calendars; testing classification approaches (will this station-hour exceed 5 trips? yes/no) rather than regression; and adding real-time dock availability to identify stations already approaching capacity constraints.

LS0tDQp0aXRsZTogIlNwYWNlLVRpbWUgUHJlZGljdGlvbiAtIFBoaWxhZGVscGhpYSBJbmRlZ28gQmlrZSINCmF1dGhvcjogIkFsZXggU3RhdWZmZXIiDQpkYXRlOiAiMjAyNS0xMi0wMSINCm91dHB1dDoNCiAgaHRtbF9kb2N1bWVudDoNCiAgICB0aGVtZTogeWV0aQ0KICAgIGhpZ2hsaWdodDogdGFuZ28NCiAgICB0b2M6IHRydWUNCiAgICB0b2NfZmxvYXQ6IHRydWUNCiAgICBjb2RlX2ZvbGRpbmc6IGhpZGUNCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlDQogICAgZGZfcHJpbnQ6IHBhZ2VkDQotLS0NCg0KIyBJbnRyb2R1Y3Rpb24NCg0KIyMgVGhlIFJlYmFsYW5jaW5nIENoYWxsZW5nZSBpbiBQaGlsYWRlbHBoaWENCg0KUGhpbGFkZWxwaGlhJ3MgSW5kZWdvIGJpa2Ugc2hhcmUgc3lzdGVtIGZhY2VzIHRoZSBzYW1lIG9wZXJhdGlvbmFsIGNoYWxsZW5nZSBhcyBldmVyeSBiaWtlIHNoYXJlIHN5c3RlbTogcmViYWxhbmNpbmcgYmlrZXMgdG8gbWVldCBhbnRpY2lwYXRlZCBkZW1hbmQuIA0KDQpJbWFnaW5lIHlvdSdyZSBhbiBJbmRlZ28gb3BlcmF0aW9ucyBtYW5hZ2VyIGF0IDY6MDAgQU0gb24gYSBNb25kYXkgbW9ybmluZy4gWW91IGhhdmU6DQotIDIwMCBzdGF0aW9ucyBhY3Jvc3MgUGhpbGFkZWxwaGlhDQotIExpbWl0ZWQgdHJ1Y2tzIGFuZCBzdGFmZiBmb3IgbW92aW5nIGJpa2VzDQotIDItMyBob3VycyBiZWZvcmUgbW9ybmluZyBydXNoIGhvdXIgZGVtYW5kIHBlYWtzDQotIFRoZSBxdWVzdGlvbjogV2hpY2ggc3RhdGlvbnMgd2lsbCBydW4gb3V0IG9mIGJpa2VzIGJ5IDg6MzAgQU0/DQoNClRoaXMgbGFiIHdpbGwgdGVhY2ggeW91IHRvIGJ1aWxkIHByZWRpY3RpdmUgbW9kZWxzIHRoYXQgZm9yZWNhc3QgYmlrZSBzaGFyZSBkZW1hbmQgYWNyb3NzIHNwYWNlIChkaWZmZXJlbnQgc3RhdGlvbnMpIGFuZCB0aW1lIChkaWZmZXJlbnQgaG91cnMpIHRvIGhlbHAgc29sdmUgdGhpcyBvcGVyYXRpb25hbCBwcm9ibGVtLg0KDQojIFNldHVwIA0KDQojIyBMb2FkIExpYnJhcmllcw0KDQpgYGB7ciBsb2FkX2xpYnJhcmllcywgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlPUZBTFNFLCBjYWNoZSA9IFRSVUV9DQojIENvcmUgdGlkeXZlcnNlDQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkobHVicmlkYXRlKQ0KDQojIFNwYXRpYWwgZGF0YQ0KbGlicmFyeShzZikNCmxpYnJhcnkodGlncmlzKQ0KDQojIENlbnN1cyBkYXRhDQpsaWJyYXJ5KHRpZHljZW5zdXMpDQoNCiMgV2VhdGhlciBkYXRhDQpsaWJyYXJ5KHJpZW0pICAjIEZvciBQaGlsYWRlbHBoaWEgd2VhdGhlciBmcm9tIEFTT1Mgc3RhdGlvbnMNCg0KIyBWaXN1YWxpemF0aW9uDQpsaWJyYXJ5KHZpcmlkaXMpDQpsaWJyYXJ5KGdyaWRFeHRyYSkNCmxpYnJhcnkoa25pdHIpDQpsaWJyYXJ5KGthYmxlRXh0cmEpDQpsaWJyYXJ5KGdyaWQpDQpsaWJyYXJ5KGdyaWRFeHRyYSkNCmxpYnJhcnkocGF0Y2h3b3JrKQ0KbGlicmFyeShnZ3Bsb3QyKQ0KDQoNCiMgaGVyZSENCmxpYnJhcnkoaGVyZSkNCiMgR2V0IHJpZCBvZiBzY2llbnRpZmljIG5vdGF0aW9uLiBXZSBnb3R0YSBsb29rIGdvb2QhDQpvcHRpb25zKHNjaXBlbiA9IDk5OSkNCmBgYA0KDQojIyBEZWZpbmUgVGhlbWVzDQoNCmBgYHtyIHRoZW1lcywgY2FjaGUgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFfQ0KcGxvdFRoZW1lIDwtIGdncGxvdDI6OnRoZW1lKA0KICBwbG90LnRpdGxlID0gZ2dwbG90Mjo6ZWxlbWVudF90ZXh0KHNpemUgPSAxNCwgZmFjZSA9ICJib2xkIiksDQogIHBsb3Quc3VidGl0bGUgPSBnZ3Bsb3QyOjplbGVtZW50X3RleHQoc2l6ZSA9IDEwKSwNCiAgcGxvdC5jYXB0aW9uID0gZ2dwbG90Mjo6ZWxlbWVudF90ZXh0KHNpemUgPSA4KSwNCiAgYXhpcy50ZXh0LnggPSBnZ3Bsb3QyOjplbGVtZW50X3RleHQoc2l6ZSA9IDEwLCBhbmdsZSA9IDQ1LCBoanVzdCA9IDEpLA0KICBheGlzLnRleHQueSA9IGdncGxvdDI6OmVsZW1lbnRfdGV4dChzaXplID0gMTApLA0KICBheGlzLnRpdGxlID0gZ2dwbG90Mjo6ZWxlbWVudF90ZXh0KHNpemUgPSAxMSwgZmFjZSA9ICJib2xkIiksDQogIHBhbmVsLmJhY2tncm91bmQgPSBnZ3Bsb3QyOjplbGVtZW50X2JsYW5rKCksDQogIHBhbmVsLmdyaWQubWFqb3IgPSBnZ3Bsb3QyOjplbGVtZW50X2xpbmUoY29sb3VyID0gIiNEMEQwRDAiLCBzaXplID0gMC4yKSwNCiAgcGFuZWwuZ3JpZC5taW5vciA9IGdncGxvdDI6OmVsZW1lbnRfYmxhbmsoKSwNCiAgYXhpcy50aWNrcyA9IGdncGxvdDI6OmVsZW1lbnRfYmxhbmsoKSwNCiAgbGVnZW5kLnBvc2l0aW9uID0gInJpZ2h0Ig0KKQ0KDQptYXBUaGVtZSA8LSBnZ3Bsb3QyOjp0aGVtZSgNCiAgcGxvdC50aXRsZSA9IGdncGxvdDI6OmVsZW1lbnRfdGV4dChzaXplID0gMTQsIGZhY2UgPSAiYm9sZCIpLA0KICBwbG90LnN1YnRpdGxlID0gZ2dwbG90Mjo6ZWxlbWVudF90ZXh0KHNpemUgPSAxMCksDQogIHBsb3QuY2FwdGlvbiA9IGdncGxvdDI6OmVsZW1lbnRfdGV4dChzaXplID0gOCksDQogIGF4aXMubGluZSA9IGdncGxvdDI6OmVsZW1lbnRfYmxhbmsoKSwNCiAgYXhpcy50ZXh0ID0gZ2dwbG90Mjo6ZWxlbWVudF9ibGFuaygpLA0KICBheGlzLnRpY2tzID0gZ2dwbG90Mjo6ZWxlbWVudF9ibGFuaygpLA0KICBheGlzLnRpdGxlID0gZ2dwbG90Mjo6ZWxlbWVudF9ibGFuaygpLA0KICBwYW5lbC5iYWNrZ3JvdW5kID0gZ2dwbG90Mjo6ZWxlbWVudF9ibGFuaygpLA0KICBwYW5lbC5ib3JkZXIgPSBnZ3Bsb3QyOjplbGVtZW50X2JsYW5rKCksDQogIHBhbmVsLmdyaWQubWFqb3IgPSBnZ3Bsb3QyOjplbGVtZW50X2xpbmUoY29sb3VyID0gJ3RyYW5zcGFyZW50JyksDQogIHBhbmVsLmdyaWQubWlub3IgPSBnZ3Bsb3QyOjplbGVtZW50X2JsYW5rKCksDQogIGxlZ2VuZC5wb3NpdGlvbiA9ICJyaWdodCIsDQogIHBsb3QubWFyZ2luID0gZ2dwbG90Mjo6bWFyZ2luKDEsIDEsIDEsIDEsICdjbScpLA0KICBsZWdlbmQua2V5LmhlaWdodCA9IGdyaWQ6OnVuaXQoMSwgImNtIiksDQogIGxlZ2VuZC5rZXkud2lkdGggPSBncmlkOjp1bml0KDAuMiwgImNtIikNCikNCg0KcGFsZXR0ZTUgPC0gYygiI2VmZjNmZiIsICIjRTFEQ0Y1IiwgIiNCQkFERjAiLCAiIzY4NDlCQSIsICIjMzYxQUEzIikNCmBgYA0KDQojIyBTZXQgQ2Vuc3VzIEFQSSBLZXkNCg0KYGBge3IgY2Vuc3VzX2tleSwgaW5jbHVkZT1GQUxTRX0NCg0KY2Vuc3VzX2FwaV9rZXkoImVlYjQ2YTRlNjhkMDU5OTgyYzJiMDQ4Zjg0MzA2M2UxOWJjNGMyOTQiLCBvdmVyd3JpdGUgPSBUUlVFLCBpbnN0YWxsID0gVFJVRSkNCg0KYGBgDQoNCi0tLQ0KDQojIERhdGEgSW1wb3J0ICYgUHJlcGFyYXRpb24NCg0KIyMgTG9hZCBJbmRlZ28gVHJpcCBEYXRhIChRMSAtIFE0IDIwMjQpDQpgYGB7ciBsb2FkLWluZGVnb3MtMjAyNH0NCiMgUmVhZCBRMSAyMDI1IGRhdGENCmluZGVnb3ExIDwtIHJlYWQuY3N2KGhlcmUoImxhYnMvbGFiNS9kYXRhL2luZGVnby10cmlwcy0yMDI0LXExLmNzdiIpKQ0KaW5kZWdvcTIgPC0gcmVhZC5jc3YoaGVyZSgibGFicy9sYWI1L2RhdGEvaW5kZWdvLXRyaXBzLTIwMjQtcTIuY3N2IikpDQppbmRlZ29xMyA8LSByZWFkLmNzdihoZXJlKCJsYWJzL2xhYjUvZGF0YS9pbmRlZ28tdHJpcHMtMjAyNC1xMy5jc3YiKSkNCmluZGVnb3E0IDwtIHJlYWQuY3N2KGhlcmUoImxhYnMvbGFiNS9kYXRhL2luZGVnby10cmlwcy0yMDI0LXE0LmNzdiIpKQ0KDQojIExvb2sgYXQgZGF0YQ0KZ2xpbXBzZShpbmRlZ29xMSkNCmBgYA0KDQojIyBFeGFtaW5lIHRoZSBEYXRhIFN0cnVjdHVyZQ0KDQojIyMgUXVhcnRlciAxIChKYW51YXJ5IC0gTWFyY2ggMjAyNCkNCg0KYGBge3IgZXhwbG9yZS1xMSwgZWNobyA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2U9RkFMU0UsIGNhY2hlID0gVFJVRX0NCiMgU3VtbWFyeSBzdGF0aXN0aWNzIHRhYmxlDQpzdW1tYXJ5X3N0YXRzIDwtIGRhdGEuZnJhbWUoDQogIE1ldHJpYyA9IGMoDQogICAgIlRvdGFsIHRyaXBzIGluIFExIChKYW4tTWFyY2gpIDIwMjQiLA0KICAgICJEYXRlIHJhbmdlIChzdGFydCkiLA0KICAgICJEYXRlIHJhbmdlIChlbmQpIiwNCiAgICAiVW5pcXVlIHN0YXJ0IHN0YXRpb25zIg0KICApLA0KICBWYWx1ZSA9IGMoDQogICAgZm9ybWF0KG5yb3coaW5kZWdvcTEpLCBiaWcubWFyayA9ICIsIiksDQogICAgYXMuY2hhcmFjdGVyKG1pbihtZHlfaG0oaW5kZWdvcTEkc3RhcnRfdGltZSkpKSwNCiAgICBhcy5jaGFyYWN0ZXIobWF4KG1keV9obShpbmRlZ29xMSRzdGFydF90aW1lKSkpLA0KICAgIGxlbmd0aCh1bmlxdWUoaW5kZWdvcTEkc3RhcnRfc3RhdGlvbikpDQogICkNCikNCg0Ka2FibGUoc3VtbWFyeV9zdGF0cywgDQogICAgICBjYXB0aW9uID0gIlExIChKYW4gLSBNYXJjaCAyMDI0KSBJbmRlZ28gVHJpcCBTdW1tYXJ5IiwNCiAgICAgIGNvbC5uYW1lcyA9IGMoIk1ldHJpYyIsICJWYWx1ZSIpLA0KICAgICAgYWxpZ24gPSBjKCJsIiwgInIiKSkgJT4lDQogIGthYmxlX3N0eWxpbmcoYm9vdHN0cmFwX29wdGlvbnMgPSBjKCJzdHJpcGVkIiwgImhvdmVyIiwgImNvbmRlbnNlZCIpLA0KICAgICAgICAgICAgICAgIGZ1bGxfd2lkdGggPSBGQUxTRSkNCg0KIyBUcmlwIHJvdXRlIGNhdGVnb3JpZXMNCnJvdXRlX3RhYmxlIDwtIGFzLmRhdGEuZnJhbWUodGFibGUoaW5kZWdvcTEkdHJpcF9yb3V0ZV9jYXRlZ29yeSkpDQpjb2xuYW1lcyhyb3V0ZV90YWJsZSkgPC0gYygiUm91dGUgQ2F0ZWdvcnkiLCAiQ291bnQiKQ0Kcm91dGVfdGFibGUkUGVyY2VudGFnZSA8LSBwYXN0ZTAocm91bmQoMTAwICogcm91dGVfdGFibGUkQ291bnQgLyBzdW0ocm91dGVfdGFibGUkQ291bnQpLCAyKSwgIiUiKQ0KDQprYWJsZShyb3V0ZV90YWJsZSwNCiAgICAgIGNhcHRpb24gPSAiUm91dGUgQ2F0ZWdvcmllcyIsDQogICAgICBmb3JtYXQuYXJncyA9IGxpc3QoYmlnLm1hcmsgPSAiLCIpLA0KICAgICAgYWxpZ24gPSBjKCJsIiwgInIiLCAiciIpKSAlPiUNCiAga2FibGVfc3R5bGluZyhib290c3RyYXBfb3B0aW9ucyA9IGMoInN0cmlwZWQiLCAiaG92ZXIiLCAiY29uZGVuc2VkIiksDQogICAgICAgICAgICAgICAgZnVsbF93aWR0aCA9IEZBTFNFKQ0KDQojIFBhc3Nob2xkZXIgdHlwZXMNCnBhc3Nob2xkZXJfdGFibGUgPC0gYXMuZGF0YS5mcmFtZSh0YWJsZShpbmRlZ29xMSRwYXNzaG9sZGVyX3R5cGUpKQ0KY29sbmFtZXMocGFzc2hvbGRlcl90YWJsZSkgPC0gYygiUGFzc2hvbGRlciBUeXBlIiwgIkNvdW50IikNCnBhc3Nob2xkZXJfdGFibGUkUGVyY2VudGFnZSA8LSBwYXN0ZTAocm91bmQoMTAwICogcGFzc2hvbGRlcl90YWJsZSRDb3VudCAvIHN1bShwYXNzaG9sZGVyX3RhYmxlJENvdW50KSwgMiksICIlIikNCg0Ka2FibGUocGFzc2hvbGRlcl90YWJsZSwNCiAgICAgIGNhcHRpb24gPSAiUGFzc2hvbGRlciBUeXBlIiwNCiAgICAgIGZvcm1hdC5hcmdzID0gbGlzdChiaWcubWFyayA9ICIsIiksDQogICAgICBhbGlnbiA9IGMoImwiLCAiciIsICJyIikpICU+JQ0KICBrYWJsZV9zdHlsaW5nKGJvb3RzdHJhcF9vcHRpb25zID0gYygic3RyaXBlZCIsICJob3ZlciIsICJjb25kZW5zZWQiKSwNCiAgICAgICAgICAgICAgICBmdWxsX3dpZHRoID0gRkFMU0UpDQoNCiMgQmlrZSB0eXBlcw0KYmlrZV90YWJsZSA8LSBhcy5kYXRhLmZyYW1lKHRhYmxlKGluZGVnb3ExJGJpa2VfdHlwZSkpDQpjb2xuYW1lcyhiaWtlX3RhYmxlKSA8LSBjKCJCaWtlIFR5cGUiLCAiQ291bnQiKQ0KYmlrZV90YWJsZSRQZXJjZW50YWdlIDwtIHBhc3RlMChyb3VuZCgxMDAgKiBiaWtlX3RhYmxlJENvdW50IC8gc3VtKGJpa2VfdGFibGUkQ291bnQpLCAxKSwgIiUiKQ0KDQprYWJsZShiaWtlX3RhYmxlLA0KICAgICAgY2FwdGlvbiA9ICJCaWtlIFR5cGVzIiwNCiAgICAgIGZvcm1hdC5hcmdzID0gbGlzdChiaWcubWFyayA9ICIsIiksDQogICAgICBhbGlnbiA9IGMoImwiLCAiciIsICJyIikpICU+JQ0KICBrYWJsZV9zdHlsaW5nKGJvb3RzdHJhcF9vcHRpb25zID0gYygic3RyaXBlZCIsICJob3ZlciIsICJjb25kZW5zZWQiKSwNCiAgICAgICAgICAgICAgICBmdWxsX3dpZHRoID0gRkFMU0UpDQpgYGANCg0KIyMjIFF1YXJ0ZXIgMiAoQXByaWwgLSBKdW5lIDIwMjQpDQoNCmBgYHtyIGV4cGxvcmUtcTIsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZT1GQUxTRSwgY2FjaGUgPSBUUlVFfQ0KDQojIFN1bW1hcnkgc3RhdGlzdGljcyB0YWJsZQ0Kc3VtbWFyeV9zdGF0cyA8LSBkYXRhLmZyYW1lKA0KICBNZXRyaWMgPSBjKA0KICAgICJUb3RhbCB0cmlwcyBpbiBRMiAyMDI0IiwNCiAgICAiRGF0ZSByYW5nZSAoc3RhcnQpIiwNCiAgICAiRGF0ZSByYW5nZSAoZW5kKSIsDQogICAgIlVuaXF1ZSBzdGFydCBzdGF0aW9ucyINCiAgKSwNCiAgVmFsdWUgPSBjKA0KICAgIGZvcm1hdChucm93KGluZGVnb3EyKSwgYmlnLm1hcmsgPSAiLCIpLA0KICAgIGFzLmNoYXJhY3RlcihtaW4obWR5X2htKGluZGVnb3EyJHN0YXJ0X3RpbWUpKSksDQogICAgYXMuY2hhcmFjdGVyKG1heChtZHlfaG0oaW5kZWdvcTIkc3RhcnRfdGltZSkpKSwNCiAgICBsZW5ndGgodW5pcXVlKGluZGVnb3EyJHN0YXJ0X3N0YXRpb24pKQ0KICApDQopDQoNCmthYmxlKHN1bW1hcnlfc3RhdHMsIA0KICAgICAgY2FwdGlvbiA9ICJRMiAyMDI0IEluZGVnbyBUcmlwIFN1bW1hcnkiLA0KICAgICAgY29sLm5hbWVzID0gYygiTWV0cmljIiwgIlZhbHVlIiksDQogICAgICBhbGlnbiA9IGMoImwiLCAiciIpKSAlPiUNCiAga2FibGVfc3R5bGluZyhib290c3RyYXBfb3B0aW9ucyA9IGMoInN0cmlwZWQiLCAiaG92ZXIiLCAiY29uZGVuc2VkIiksDQogICAgICAgICAgICAgICAgZnVsbF93aWR0aCA9IEZBTFNFKQ0KDQojIFRyaXAgcm91dGUgY2F0ZWdvcmllcw0Kcm91dGVfdGFibGUgPC0gYXMuZGF0YS5mcmFtZSh0YWJsZShpbmRlZ29xMiR0cmlwX3JvdXRlX2NhdGVnb3J5KSkNCmNvbG5hbWVzKHJvdXRlX3RhYmxlKSA8LSBjKCJUcmlwIFJvdXRlIENhdGVnb3J5IiwgIkNvdW50IikNCnJvdXRlX3RhYmxlJFBlcmNlbnRhZ2UgPC0gcGFzdGUwKHJvdW5kKDEwMCAqIHJvdXRlX3RhYmxlJENvdW50IC8gc3VtKHJvdXRlX3RhYmxlJENvdW50KSwgMSksICIlIikNCg0Ka2FibGUocm91dGVfdGFibGUsDQogICAgICBjYXB0aW9uID0gIlJvdXRlIENhdGVnb3JpZXMiLA0KICAgICAgZm9ybWF0LmFyZ3MgPSBsaXN0KGJpZy5tYXJrID0gIiwiKSwNCiAgICAgIGFsaWduID0gYygibCIsICJyIiwgInIiKSkgJT4lDQogIGthYmxlX3N0eWxpbmcoYm9vdHN0cmFwX29wdGlvbnMgPSBjKCJzdHJpcGVkIiwgImhvdmVyIiwgImNvbmRlbnNlZCIpLA0KICAgICAgICAgICAgICAgIGZ1bGxfd2lkdGggPSBGQUxTRSkNCg0KIyBQYXNzaG9sZGVyIHR5cGVzIChpbmNsdWRpbmcgTkFzKQ0KcGFzc2hvbGRlcl90YWJsZSA8LSBhcy5kYXRhLmZyYW1lKHRhYmxlKGluZGVnb3EyJHBhc3Nob2xkZXJfdHlwZSkpDQpjb2xuYW1lcyhwYXNzaG9sZGVyX3RhYmxlKSA8LSBjKCJQYXNzaG9sZGVyIFR5cGUiLCAiQ291bnQiKQ0KcGFzc2hvbGRlcl90YWJsZSRQZXJjZW50YWdlIDwtIHBhc3RlMChyb3VuZCgxMDAgKiBwYXNzaG9sZGVyX3RhYmxlJENvdW50IC8gc3VtKHBhc3Nob2xkZXJfdGFibGUkQ291bnQpLCAyKSwgIiUiKQ0KDQprYWJsZShwYXNzaG9sZGVyX3RhYmxlLA0KICAgICAgY2FwdGlvbiA9ICJQYXNzaG9sZGVyIFR5cGUiLA0KICAgICAgZm9ybWF0LmFyZ3MgPSBsaXN0KGJpZy5tYXJrID0gIiwiKSwNCiAgICAgIGFsaWduID0gYygibCIsICJyIiwgInIiKSkgJT4lDQogIGthYmxlX3N0eWxpbmcoYm9vdHN0cmFwX29wdGlvbnMgPSBjKCJzdHJpcGVkIiwgImhvdmVyIiwgImNvbmRlbnNlZCIpLA0KICAgICAgICAgICAgICAgIGZ1bGxfd2lkdGggPSBGQUxTRSkNCg0KIyBCaWtlIHR5cGVzDQpiaWtlX3RhYmxlIDwtIGFzLmRhdGEuZnJhbWUodGFibGUoaW5kZWdvcTIkYmlrZV90eXBlKSkNCmNvbG5hbWVzKGJpa2VfdGFibGUpIDwtIGMoIkJpa2UgVHlwZSIsICJDb3VudCIpDQpiaWtlX3RhYmxlJFBlcmNlbnRhZ2UgPC0gcGFzdGUwKHJvdW5kKDEwMCAqIGJpa2VfdGFibGUkQ291bnQgLyBzdW0oYmlrZV90YWJsZSRDb3VudCksIDEpLCAiJSIpDQoNCmthYmxlKGJpa2VfdGFibGUsDQogICAgICBjYXB0aW9uID0gIkJpa2UgVHlwZXMiLA0KICAgICAgZm9ybWF0LmFyZ3MgPSBsaXN0KGJpZy5tYXJrID0gIiwiKSwNCiAgICAgIGFsaWduID0gYygibCIsICJyIiwgInIiKSkgJT4lDQogIGthYmxlX3N0eWxpbmcoYm9vdHN0cmFwX29wdGlvbnMgPSBjKCJzdHJpcGVkIiwgImhvdmVyIiwgImNvbmRlbnNlZCIpLA0KICAgICAgICAgICAgICAgIGZ1bGxfd2lkdGggPSBGQUxTRSkNCg0KDQpgYGANCg0KIyMjIFF1YXJ0ZXIgMyAoSnVseSAtIFNlcHRlbWJlciAyMDI0KQ0KYGBge3IgZXhwbG9yZS1xMywgZWNobyA9IFRSVUUsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZT1GQUxTRSwgY2FjaGUgPSBUUlVFfQ0KIyBTdW1tYXJ5IHN0YXRpc3RpY3MgdGFibGUNCnN1bW1hcnlfc3RhdHMgPC0gZGF0YS5mcmFtZSgNCiAgTWV0cmljID0gYygNCiAgICAiVG90YWwgdHJpcHMgaW4gUTMgKEp1bC1TZXB0KSAyMDI0IiwNCiAgICAiRGF0ZSByYW5nZSAoc3RhcnQpIiwNCiAgICAiRGF0ZSByYW5nZSAoZW5kKSIsDQogICAgIlVuaXF1ZSBzdGFydCBzdGF0aW9ucyINCiAgKSwNCiAgVmFsdWUgPSBjKA0KICAgIGZvcm1hdChucm93KGluZGVnb3EzKSwgYmlnLm1hcmsgPSAiLCIpLA0KICAgIGFzLmNoYXJhY3RlcihtaW4obWR5X2htKGluZGVnb3EzJHN0YXJ0X3RpbWUpKSksDQogICAgYXMuY2hhcmFjdGVyKG1heChtZHlfaG0oaW5kZWdvcTMkc3RhcnRfdGltZSkpKSwNCiAgICBsZW5ndGgodW5pcXVlKGluZGVnb3EzJHN0YXJ0X3N0YXRpb24pKQ0KICApDQopDQoNCmthYmxlKHN1bW1hcnlfc3RhdHMsIA0KICAgICAgY2FwdGlvbiA9ICJRMyAoSnVsIC0gU2VwdCAyMDI0KSBJbmRlZ28gVHJpcCBTdW1tYXJ5IiwNCiAgICAgIGNvbC5uYW1lcyA9IGMoIk1ldHJpYyIsICJWYWx1ZSIpLA0KICAgICAgYWxpZ24gPSBjKCJsIiwgInIiKSkgJT4lDQogIGthYmxlX3N0eWxpbmcoYm9vdHN0cmFwX29wdGlvbnMgPSBjKCJzdHJpcGVkIiwgImhvdmVyIiwgImNvbmRlbnNlZCIpLA0KICAgICAgICAgICAgICAgIGZ1bGxfd2lkdGggPSBGQUxTRSkNCg0KIyBUcmlwIHJvdXRlIGNhdGVnb3JpZXMNCnJvdXRlX3RhYmxlIDwtIGFzLmRhdGEuZnJhbWUodGFibGUoaW5kZWdvcTMkdHJpcF9yb3V0ZV9jYXRlZ29yeSkpDQpjb2xuYW1lcyhyb3V0ZV90YWJsZSkgPC0gYygiUm91dGUgQ2F0ZWdvcnkiLCAiQ291bnQiKQ0Kcm91dGVfdGFibGUkUGVyY2VudGFnZSA8LSBwYXN0ZTAocm91bmQoMTAwICogcm91dGVfdGFibGUkQ291bnQgLyBzdW0ocm91dGVfdGFibGUkQ291bnQpLCAyKSwgIiUiKQ0KDQprYWJsZShyb3V0ZV90YWJsZSwNCiAgICAgIGNhcHRpb24gPSAiUm91dGUgQ2F0ZWdvcmllcyIsDQogICAgICBmb3JtYXQuYXJncyA9IGxpc3QoYmlnLm1hcmsgPSAiLCIpLA0KICAgICAgYWxpZ24gPSBjKCJsIiwgInIiLCAiciIpKSAlPiUNCiAga2FibGVfc3R5bGluZyhib290c3RyYXBfb3B0aW9ucyA9IGMoInN0cmlwZWQiLCAiaG92ZXIiLCAiY29uZGVuc2VkIiksDQogICAgICAgICAgICAgICAgZnVsbF93aWR0aCA9IEZBTFNFKQ0KDQojIFBhc3Nob2xkZXIgdHlwZXMNCnBhc3Nob2xkZXJfdGFibGUgPC0gYXMuZGF0YS5mcmFtZSh0YWJsZShpbmRlZ29xMyRwYXNzaG9sZGVyX3R5cGUpKQ0KY29sbmFtZXMocGFzc2hvbGRlcl90YWJsZSkgPC0gYygiUGFzc2hvbGRlciBUeXBlIiwgIkNvdW50IikNCnBhc3Nob2xkZXJfdGFibGUkUGVyY2VudGFnZSA8LSBwYXN0ZTAocm91bmQoMTAwICogcGFzc2hvbGRlcl90YWJsZSRDb3VudCAvIHN1bShwYXNzaG9sZGVyX3RhYmxlJENvdW50KSwgMiksICIlIikNCg0Ka2FibGUocGFzc2hvbGRlcl90YWJsZSwNCiAgICAgIGNhcHRpb24gPSAiUGFzc2hvbGRlciBUeXBlIiwNCiAgICAgIGZvcm1hdC5hcmdzID0gbGlzdChiaWcubWFyayA9ICIsIiksDQogICAgICBhbGlnbiA9IGMoImwiLCAiciIsICJyIikpICU+JQ0KICBrYWJsZV9zdHlsaW5nKGJvb3RzdHJhcF9vcHRpb25zID0gYygic3RyaXBlZCIsICJob3ZlciIsICJjb25kZW5zZWQiKSwNCiAgICAgICAgICAgICAgICBmdWxsX3dpZHRoID0gRkFMU0UpDQoNCiMgQmlrZSB0eXBlcw0KYmlrZV90YWJsZSA8LSBhcy5kYXRhLmZyYW1lKHRhYmxlKGluZGVnb3EzJGJpa2VfdHlwZSkpDQpjb2xuYW1lcyhiaWtlX3RhYmxlKSA8LSBjKCJCaWtlIFR5cGUiLCAiQ291bnQiKQ0KYmlrZV90YWJsZSRQZXJjZW50YWdlIDwtIHBhc3RlMChyb3VuZCgxMDAgKiBiaWtlX3RhYmxlJENvdW50IC8gc3VtKGJpa2VfdGFibGUkQ291bnQpLCAxKSwgIiUiKQ0KDQprYWJsZShiaWtlX3RhYmxlLA0KICAgICAgY2FwdGlvbiA9ICJCaWtlIFR5cGVzIiwNCiAgICAgIGZvcm1hdC5hcmdzID0gbGlzdChiaWcubWFyayA9ICIsIiksDQogICAgICBhbGlnbiA9IGMoImwiLCAiciIsICJyIikpICU+JQ0KICBrYWJsZV9zdHlsaW5nKGJvb3RzdHJhcF9vcHRpb25zID0gYygic3RyaXBlZCIsICJob3ZlciIsICJjb25kZW5zZWQiKSwNCiAgICAgICAgICAgICAgICBmdWxsX3dpZHRoID0gRkFMU0UpDQoNCmBgYA0KDQojIyMgUXVhcnRlciA0IChPY3RvYmVyIC0gRGVjZW1iZXIgMjAyNCkNCg0KYGBge3IgZXhwbG9yZS1xNCwgZWNobyA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2U9RkFMU0UsIGNhY2hlID0gVFJVRX0NCiMgU3VtbWFyeSBzdGF0aXN0aWNzIHRhYmxlDQpzdW1tYXJ5X3N0YXRzIDwtIGRhdGEuZnJhbWUoDQogIE1ldHJpYyA9IGMoDQogICAgIlRvdGFsIHRyaXBzIGluIFE0IChPY3QtRGVjKSAyMDI0IiwNCiAgICAiRGF0ZSByYW5nZSAoc3RhcnQpIiwNCiAgICAiRGF0ZSByYW5nZSAoZW5kKSIsDQogICAgIlVuaXF1ZSBzdGFydCBzdGF0aW9ucyINCiAgKSwNCiAgVmFsdWUgPSBjKA0KICAgIGZvcm1hdChucm93KGluZGVnb3E0KSwgYmlnLm1hcmsgPSAiLCIpLA0KICAgIGFzLmNoYXJhY3RlcihtaW4obWR5X2htKGluZGVnb3E0JHN0YXJ0X3RpbWUpKSksDQogICAgYXMuY2hhcmFjdGVyKG1heChtZHlfaG0oaW5kZWdvcTQkc3RhcnRfdGltZSkpKSwNCiAgICBsZW5ndGgodW5pcXVlKGluZGVnb3E0JHN0YXJ0X3N0YXRpb24pKQ0KICApDQopDQoNCmthYmxlKHN1bW1hcnlfc3RhdHMsIA0KICAgICAgY2FwdGlvbiA9ICJRNCAoT2N0IC0gRGVjIDIwMjQpIEluZGVnbyBUcmlwIFN1bW1hcnkiLA0KICAgICAgY29sLm5hbWVzID0gYygiTWV0cmljIiwgIlZhbHVlIiksDQogICAgICBhbGlnbiA9IGMoImwiLCAiciIpKSAlPiUNCiAga2FibGVfc3R5bGluZyhib290c3RyYXBfb3B0aW9ucyA9IGMoInN0cmlwZWQiLCAiaG92ZXIiLCAiY29uZGVuc2VkIiksDQogICAgICAgICAgICAgICAgZnVsbF93aWR0aCA9IEZBTFNFKQ0KDQojIFRyaXAgcm91dGUgY2F0ZWdvcmllcw0Kcm91dGVfdGFibGUgPC0gYXMuZGF0YS5mcmFtZSh0YWJsZShpbmRlZ29xNCR0cmlwX3JvdXRlX2NhdGVnb3J5KSkNCmNvbG5hbWVzKHJvdXRlX3RhYmxlKSA8LSBjKCJSb3V0ZSBDYXRlZ29yeSIsICJDb3VudCIpDQpyb3V0ZV90YWJsZSRQZXJjZW50YWdlIDwtIHBhc3RlMChyb3VuZCgxMDAgKiByb3V0ZV90YWJsZSRDb3VudCAvIHN1bShyb3V0ZV90YWJsZSRDb3VudCksIDIpLCAiJSIpDQoNCmthYmxlKHJvdXRlX3RhYmxlLA0KICAgICAgY2FwdGlvbiA9ICJSb3V0ZSBDYXRlZ29yaWVzIiwNCiAgICAgIGZvcm1hdC5hcmdzID0gbGlzdChiaWcubWFyayA9ICIsIiksDQogICAgICBhbGlnbiA9IGMoImwiLCAiciIsICJyIikpICU+JQ0KICBrYWJsZV9zdHlsaW5nKGJvb3RzdHJhcF9vcHRpb25zID0gYygic3RyaXBlZCIsICJob3ZlciIsICJjb25kZW5zZWQiKSwNCiAgICAgICAgICAgICAgICBmdWxsX3dpZHRoID0gRkFMU0UpDQoNCiMgUGFzc2hvbGRlciB0eXBlcw0KcGFzc2hvbGRlcl90YWJsZSA8LSBhcy5kYXRhLmZyYW1lKHRhYmxlKGluZGVnb3E0JHBhc3Nob2xkZXJfdHlwZSkpDQpjb2xuYW1lcyhwYXNzaG9sZGVyX3RhYmxlKSA8LSBjKCJQYXNzaG9sZGVyIFR5cGUiLCAiQ291bnQiKQ0KcGFzc2hvbGRlcl90YWJsZSRQZXJjZW50YWdlIDwtIHBhc3RlMChyb3VuZCgxMDAgKiBwYXNzaG9sZGVyX3RhYmxlJENvdW50IC8gc3VtKHBhc3Nob2xkZXJfdGFibGUkQ291bnQpLCAyKSwgIiUiKQ0KDQprYWJsZShwYXNzaG9sZGVyX3RhYmxlLA0KICAgICAgY2FwdGlvbiA9ICJQYXNzaG9sZGVyIFR5cGUiLA0KICAgICAgZm9ybWF0LmFyZ3MgPSBsaXN0KGJpZy5tYXJrID0gIiwiKSwNCiAgICAgIGFsaWduID0gYygibCIsICJyIiwgInIiKSkgJT4lDQogIGthYmxlX3N0eWxpbmcoYm9vdHN0cmFwX29wdGlvbnMgPSBjKCJzdHJpcGVkIiwgImhvdmVyIiwgImNvbmRlbnNlZCIpLA0KICAgICAgICAgICAgICAgIGZ1bGxfd2lkdGggPSBGQUxTRSkNCg0KIyBCaWtlIHR5cGVzDQpiaWtlX3RhYmxlIDwtIGFzLmRhdGEuZnJhbWUodGFibGUoaW5kZWdvcTIkYmlrZV90eXBlKSkNCmNvbG5hbWVzKGJpa2VfdGFibGUpIDwtIGMoIkJpa2UgVHlwZSIsICJDb3VudCIpDQpiaWtlX3RhYmxlJFBlcmNlbnRhZ2UgPC0gcGFzdGUwKHJvdW5kKDEwMCAqIGJpa2VfdGFibGUkQ291bnQgLyBzdW0oYmlrZV90YWJsZSRDb3VudCksIDEpLCAiJSIpDQoNCmthYmxlKGJpa2VfdGFibGUsDQogICAgICBjYXB0aW9uID0gIkJpa2UgVHlwZXMiLA0KICAgICAgZm9ybWF0LmFyZ3MgPSBsaXN0KGJpZy5tYXJrID0gIiwiKSwNCiAgICAgIGFsaWduID0gYygibCIsICJyIiwgInIiKSkgJT4lDQogIGthYmxlX3N0eWxpbmcoYm9vdHN0cmFwX29wdGlvbnMgPSBjKCJzdHJpcGVkIiwgImhvdmVyIiwgImNvbmRlbnNlZCIpLA0KICAgICAgICAgICAgICAgIGZ1bGxfd2lkdGggPSBGQUxTRSkNCg0KYGBgDQoNCiMjIyBDb21iaW5lIGFsbCBxdWFydGVycw0KDQpgYGB7cn0NCmluZGVnb19hbGwgPC0gYmluZF9yb3dzKA0KICBRMV8yMDI1ID0gaW5kZWdvcTEsDQogIFEyXzIwMjQgPSBpbmRlZ29xMiwNCiAgUTNfMjAyNCA9IGluZGVnb3EzLA0KICBRNF8yMDI0ID0gaW5kZWdvcTQsDQogIC5pZCA9ICJxdWFydGVyIg0KKQ0KDQojIENoZWNrIHRoZSBkaXN0cmlidXRpb24NCnRhYmxlKGluZGVnb19hbGwkcXVhcnRlcikNCmBgYA0KDQojIyBDcmVhdGUgVGltZSBCaW5zDQoNCldlIG5lZWQgdG8gYWdncmVnYXRlIHRyaXBzIGludG8gaG91cmx5IGludGVydmFscyBmb3Igb3VyIHBhbmVsIGRhdGEgc3RydWN0dXJlLg0KDQojIyMgUXVhcnRlciAxDQoNCmBgYHtyIGNyZWF0ZV90aW1lX2JpbnMtcTEsIGVjaG8gPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlPUZBTFNFLCBjYWNoZSA9IFRSVUV9DQppbmRlZ29xMSA8LSBpbmRlZ29xMSAlPiUNCiAgbXV0YXRlKA0KICAgICMgUGFyc2UgZGF0ZXRpbWUNCiAgICBzdGFydF9kYXRldGltZSA9IG1keV9obShzdGFydF90aW1lKSwNCiAgICBlbmRfZGF0ZXRpbWUgPSBtZHlfaG0oZW5kX3RpbWUpLA0KICAgIA0KICAgICMgQ3JlYXRlIGhvdXJseSBiaW5zDQogICAgaW50ZXJ2YWw2MCA9IGZsb29yX2RhdGUoc3RhcnRfZGF0ZXRpbWUsIHVuaXQgPSAiaG91ciIpLA0KICAgIA0KICAgICMgRXh0cmFjdCB0aW1lIGZlYXR1cmVzDQogICAgd2VlayA9IHdlZWsoaW50ZXJ2YWw2MCksDQogICAgbW9udGggPSBtb250aChpbnRlcnZhbDYwLCBsYWJlbCA9IFRSVUUpLA0KICAgIGRvdHcgPSB3ZGF5KGludGVydmFsNjAsIGxhYmVsID0gVFJVRSksDQogICAgaG91ciA9IGhvdXIoaW50ZXJ2YWw2MCksDQogICAgZGF0ZSA9IGFzLkRhdGUoaW50ZXJ2YWw2MCksDQogICAgDQogICAgIyBDcmVhdGUgdXNlZnVsIGluZGljYXRvcnMNCiAgICB3ZWVrZW5kID0gaWZlbHNlKGRvdHcgJWluJSBjKCJTYXQiLCAiU3VuIiksIDEsIDApLA0KICAgIHJ1c2hfaG91ciA9IGlmZWxzZShob3VyICVpbiUgYyg3LCA4LCA5LCAxNiwgMTcsIDE4KSwgMSwgMCkNCiAgKQ0KDQojIExvb2sgYXQgdGVtcG9yYWwgZmVhdHVyZXMNCmhlYWQoaW5kZWdvcTEgJT4lIHNlbGVjdChzdGFydF9kYXRldGltZSwgaW50ZXJ2YWw2MCwgd2VlaywgZG90dywgaG91ciwgd2Vla2VuZCkpDQpgYGANCg0KIyMjIFF1YXJ0ZXIgMg0KDQpgYGB7ciBjcmVhdGVfdGltZV9iaW5zLXEyLCBlY2hvID0gRkFMU0UsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZT1GQUxTRSwgY2FjaGUgPSBUUlVFfQ0KaW5kZWdvcTIgPC0gaW5kZWdvcTIgJT4lDQogIG11dGF0ZSgNCiAgICAjIFBhcnNlIGRhdGV0aW1lDQogICAgc3RhcnRfZGF0ZXRpbWUgPSBtZHlfaG0oc3RhcnRfdGltZSksDQogICAgZW5kX2RhdGV0aW1lID0gbWR5X2htKGVuZF90aW1lKSwNCiAgICANCiAgICAjIENyZWF0ZSBob3VybHkgYmlucw0KICAgIGludGVydmFsNjAgPSBmbG9vcl9kYXRlKHN0YXJ0X2RhdGV0aW1lLCB1bml0ID0gImhvdXIiKSwNCiAgICANCiAgICAjIEV4dHJhY3QgdGltZSBmZWF0dXJlcw0KICAgIHdlZWsgPSB3ZWVrKGludGVydmFsNjApLA0KICAgIG1vbnRoID0gbW9udGgoaW50ZXJ2YWw2MCwgbGFiZWwgPSBUUlVFKSwNCiAgICBkb3R3ID0gd2RheShpbnRlcnZhbDYwLCBsYWJlbCA9IFRSVUUpLA0KICAgIGhvdXIgPSBob3VyKGludGVydmFsNjApLA0KICAgIGRhdGUgPSBhcy5EYXRlKGludGVydmFsNjApLA0KICAgIA0KICAgICMgQ3JlYXRlIHVzZWZ1bCBpbmRpY2F0b3JzDQogICAgd2Vla2VuZCA9IGlmZWxzZShkb3R3ICVpbiUgYygiU2F0IiwgIlN1biIpLCAxLCAwKSwNCiAgICBydXNoX2hvdXIgPSBpZmVsc2UoaG91ciAlaW4lIGMoNywgOCwgOSwgMTYsIDE3LCAxOCksIDEsIDApDQogICkNCg0KIyBMb29rIGF0IHRlbXBvcmFsIGZlYXR1cmVzDQpoZWFkKGluZGVnb3EyICU+JSBzZWxlY3Qoc3RhcnRfZGF0ZXRpbWUsIGludGVydmFsNjAsIHdlZWssIGRvdHcsIGhvdXIsIHdlZWtlbmQpKQ0KYGBgDQoNCiMjIyBRdWFydGVyIDMNCmBgYHtyIGNyZWF0ZV90aW1lX2JpbnMtcTMsIGVjaG8gPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlPUZBTFNFLCBjYWNoZSA9IFRSVUV9DQppbmRlZ29xMyA8LSBpbmRlZ29xMyAlPiUNCiAgbXV0YXRlKA0KICAgICMgUGFyc2UgZGF0ZXRpbWUNCiAgICBzdGFydF9kYXRldGltZSA9IG1keV9obShzdGFydF90aW1lKSwNCiAgICBlbmRfZGF0ZXRpbWUgPSBtZHlfaG0oZW5kX3RpbWUpLA0KICAgIA0KICAgICMgQ3JlYXRlIGhvdXJseSBiaW5zDQogICAgaW50ZXJ2YWw2MCA9IGZsb29yX2RhdGUoc3RhcnRfZGF0ZXRpbWUsIHVuaXQgPSAiaG91ciIpLA0KICAgIA0KICAgICMgRXh0cmFjdCB0aW1lIGZlYXR1cmVzDQogICAgd2VlayA9IHdlZWsoaW50ZXJ2YWw2MCksDQogICAgbW9udGggPSBtb250aChpbnRlcnZhbDYwLCBsYWJlbCA9IFRSVUUpLA0KICAgIGRvdHcgPSB3ZGF5KGludGVydmFsNjAsIGxhYmVsID0gVFJVRSksDQogICAgaG91ciA9IGhvdXIoaW50ZXJ2YWw2MCksDQogICAgZGF0ZSA9IGFzLkRhdGUoaW50ZXJ2YWw2MCksDQogICAgDQogICAgIyBDcmVhdGUgdXNlZnVsIGluZGljYXRvcnMNCiAgICB3ZWVrZW5kID0gaWZlbHNlKGRvdHcgJWluJSBjKCJTYXQiLCAiU3VuIiksIDEsIDApLA0KICAgIHJ1c2hfaG91ciA9IGlmZWxzZShob3VyICVpbiUgYyg3LCA4LCA5LCAxNiwgMTcsIDE4KSwgMSwgMCkNCiAgKQ0KDQojIExvb2sgYXQgdGVtcG9yYWwgZmVhdHVyZXMNCmhlYWQoaW5kZWdvcTMgJT4lIHNlbGVjdChzdGFydF9kYXRldGltZSwgaW50ZXJ2YWw2MCwgd2VlaywgZG90dywgaG91ciwgd2Vla2VuZCkpDQpgYGANCg0KIyMjIFF1YXJ0ZXIgNA0KDQpgYGB7ciBjcmVhdGVfdGltZV9iaW5zLXE0LCBlY2hvID0gRkFMU0UsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZT1GQUxTRSwgY2FjaGUgPSBUUlVFfQ0KaW5kZWdvcTQgPC0gaW5kZWdvcTQgJT4lDQogIG11dGF0ZSgNCiAgICAjIFBhcnNlIGRhdGV0aW1lDQogICAgc3RhcnRfZGF0ZXRpbWUgPSBtZHlfaG0oc3RhcnRfdGltZSksDQogICAgZW5kX2RhdGV0aW1lID0gbWR5X2htKGVuZF90aW1lKSwNCiAgICANCiAgICAjIENyZWF0ZSBob3VybHkgYmlucw0KICAgIGludGVydmFsNjAgPSBmbG9vcl9kYXRlKHN0YXJ0X2RhdGV0aW1lLCB1bml0ID0gImhvdXIiKSwNCiAgICANCiAgICAjIEV4dHJhY3QgdGltZSBmZWF0dXJlcw0KICAgIHdlZWsgPSB3ZWVrKGludGVydmFsNjApLA0KICAgIG1vbnRoID0gbW9udGgoaW50ZXJ2YWw2MCwgbGFiZWwgPSBUUlVFKSwNCiAgICBkb3R3ID0gd2RheShpbnRlcnZhbDYwLCBsYWJlbCA9IFRSVUUpLA0KICAgIGhvdXIgPSBob3VyKGludGVydmFsNjApLA0KICAgIGRhdGUgPSBhcy5EYXRlKGludGVydmFsNjApLA0KICAgIA0KICAgICMgQ3JlYXRlIHVzZWZ1bCBpbmRpY2F0b3JzDQogICAgd2Vla2VuZCA9IGlmZWxzZShkb3R3ICVpbiUgYygiU2F0IiwgIlN1biIpLCAxLCAwKSwNCiAgICBydXNoX2hvdXIgPSBpZmVsc2UoaG91ciAlaW4lIGMoNywgOCwgOSwgMTYsIDE3LCAxOCksIDEsIDApDQogICkNCg0KIyBMb29rIGF0IHRlbXBvcmFsIGZlYXR1cmVzDQpoZWFkKGluZGVnb3E0ICU+JSBzZWxlY3Qoc3RhcnRfZGF0ZXRpbWUsIGludGVydmFsNjAsIHdlZWssIGRvdHcsIGhvdXIsIHdlZWtlbmQpKQ0KYGBgDQotLS0NCg0KIyBQYXJ0IDE6IFdoeSBJIENob3NlIEZ1bGwgWWVhciAyMDI0DQoNCiMjIERhdGEgU2VsZWN0aW9uIFJhdGlvbmFsZQ0KDQpJIHNlbGVjdGVkIGFsbCBmb3VyIHF1YXJ0ZXJzIG9mIDIwMjQgcmF0aGVyIHRoYW4gYSBzaW5nbGUgcXVhcnRlciBmb3IgYSBmZXcgcmVhc29ucy4gRmlyc3QsIGNvbWJpbmluZyBhbGwgcXVhcnRlcnMgYWxsb3dzIGZvciBhIGxhcmdlciB0cmFpbmluZyBkYXRhc2V0IHdoaWNoIGNhbiBoZWxwIG1pbmltaXplIHRoZSByaXNrIG9mIG92ZXJmaXR0aW5nLiBTZWNvbmQsIFBoaWxhZGVscGhpYSBleHBlcmllbmNlcyB2ZXJ5IHNlYXNvbmFsIHdlYXRoZXIsIHdoaWNoIGluIHR1cm4gYWZmZWN0cyBiaWtlIHNoYXJlIHJpZGVyc2hpcCBhbmQgZGVtYW5kLiBJbiB0aGUgd2ludGVyIG1vbnRocyAoUTEgYW5kIFE0KSwgbG93IHJpZGVyc2hpcCBjYW4gYmUgYXR0cmlidXRlZCB0byBjb2xkIHRlbXBlcmF0dXJlcyBhbmQgaW5jbGVtZW50IHdlYXRoZXIsIHdoZXJlYXMgdGhlIHNwcmluZyBhbmQgc3VtbWVyIChRMiBhbmQgUTMpIHNlZSBtb3N0IG9mIHRoZSBkZW1hbmQuIEJ5IGluY2x1ZGluZyB0aGUgZnVsbCB5ZWFyLCB0aGUgbW9kZWwgbGVhcm5zIHRoZXNlIHNlYXNvbmFsIHBhdHRlcm5zIGFuZCBjYW4gZ2VuZXJhbGl6ZSBiZXR0ZXIuIA0KDQpDb21wYXJpbmcgYWNyb3NzIGFsbCBxdWFydGVycyByZXZlYWxzIGNsZWFyIHRlbXBvcmFsIGRpZmZlcmVuY2VzIHRoYXQgYXJlIGV4cGxvcmVkIGluIHRoZSAiVHJpcHMgT3ZlciBUaW1lIiBhbmQgIkhvdXJseSBQYXR0ZXJucyIgc2VjdGlvbnMgYmVsb3cuIA0KDQotLS0NCg0KIyBFeHBsb3JhdG9yeSBBbmFseXNpcw0KDQojIyBUcmlwcyBPdmVyIFRpbWUNCmBgYHtyIHRyaXBzX292ZXJfdGltZSwgZWNobyA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2U9RkFMU0UsIGNhY2hlID0gVFJVRX0NCg0KIyBEYWlseSB0cmlwIGNvdW50cw0KZGFpbHkxIDwtIGluZGVnb3ExICU+JQ0KICBncm91cF9ieShkYXRlKSAlPiUNCiAgc3VtbWFyaXplKHRyaXBzID0gbigpKQ0KZGFpbHkyIDwtIGluZGVnb3EyICU+JQ0KICBncm91cF9ieShkYXRlKSAlPiUNCiAgc3VtbWFyaXplKHRyaXBzID0gbigpKQ0KZGFpbHkzIDwtIGluZGVnb3EzICU+JQ0KICBncm91cF9ieShkYXRlKSAlPiUNCiAgc3VtbWFyaXplKHRyaXBzID0gbigpKQ0KZGFpbHk0IDwtIGluZGVnb3E0ICU+JQ0KICBncm91cF9ieShkYXRlKSAlPiUNCiAgc3VtbWFyaXplKHRyaXBzID0gbigpKQ0KDQojIENvbnN0cnVjdCBwbG90DQpxMSA8LSBnZ3Bsb3QoZGFpbHkxLCBhZXMoeCA9IGRhdGUsIHkgPSB0cmlwcykpICsNCiAgZ2VvbV9saW5lKGNvbG9yID0gIiMzMTgyYmQiLCBsaW5ld2lkdGggPSAxKSArDQogIGdlb21fc21vb3RoKHNlID0gRkFMU0UsIGNvbG9yID0gIiNDQzI4MTAiLCBsaW5ldHlwZSA9ICJkYXNoZWQiKSArDQogIGxhYnMoDQogICAgdGl0bGUgPSAiUTEgMjAyNCIsDQogICAgc3VidGl0bGUgPSAiV2ludGVyIHBhdHRlcm5zIiwNCiAgICB4ID0gIkRhdGUiLA0KICAgIHkgPSAiRGFpbHkgVHJpcHMiDQogICkgKw0KICBwbG90VGhlbWUNCg0KcTIgPC0gZ2dwbG90KGRhaWx5MiwgYWVzKHggPSBkYXRlLCB5ID0gdHJpcHMpKSArDQogIGdlb21fbGluZShjb2xvciA9ICIjMzFhMzU0IiwgbGluZXdpZHRoID0gMSkgKw0KICBnZW9tX3Ntb290aChzZSA9IEZBTFNFLCBjb2xvciA9ICIjQ0MyODEwIiwgbGluZXR5cGUgPSAiZGFzaGVkIikgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIlEyIDIwMjQiLA0KICAgIHN1YnRpdGxlID0gIlNwcmluZyBwYXR0ZXJucyIsDQogICAgeCA9ICJEYXRlIiwNCiAgICB5ID0gIkRhaWx5IFRyaXBzIg0KICApICsNCiAgcGxvdFRoZW1lDQoNCnEzIDwtIGdncGxvdChkYWlseTMsIGFlcyh4ID0gZGF0ZSwgeSA9IHRyaXBzKSkgKw0KICBnZW9tX2xpbmUoY29sb3IgPSAiZGFya29yYW5nZSIsIGxpbmV3aWR0aCA9IDEpICsNCiAgZ2VvbV9zbW9vdGgoc2UgPSBGQUxTRSwgY29sb3IgPSAiI0NDMjgxMCIsIGxpbmV0eXBlID0gImRhc2hlZCIpICsNCiAgbGFicygNCiAgICB0aXRsZSA9ICJRMyAyMDI0IiwNCiAgICBzdWJ0aXRsZSA9ICJTdW1tZXIgcGF0dGVybnMiLA0KICAgIHggPSAiRGF0ZSIsDQogICAgeSA9ICJEYWlseSBUcmlwcyINCiAgKSArDQogIHBsb3RUaGVtZQ0KDQpxNCA8LSBnZ3Bsb3QoZGFpbHk0LCBhZXMoeCA9IGRhdGUsIHkgPSB0cmlwcykpICsNCiAgZ2VvbV9saW5lKGNvbG9yID0gIiM3NTZiYjEiLCBsaW5ld2lkdGggPSAxKSArDQogIGdlb21fc21vb3RoKHNlID0gRkFMU0UsIGNvbG9yID0gIiNDQzI4MTAiLCBsaW5ldHlwZSA9ICJkYXNoZWQiKSArDQogIGxhYnMoDQogICAgdGl0bGUgPSAiUTQgMjAyNCIsDQogICAgc3VidGl0bGUgPSAiRmFsbCBwYXR0ZXJucyIsDQogICAgeCA9ICJEYXRlIiwNCiAgICB5ID0gIkRhaWx5IFRyaXBzIg0KICApICsNCiAgcGxvdFRoZW1lDQoNCg0KIyAyeDIgZ3JpZCAoZGVmYXVsdCkNCihxMSB8IHEyKSAvIChxMyB8IHE0KSArIA0KICBwbG90X2Fubm90YXRpb24oDQogICAgdGl0bGUgPSAiSW5kZWdvIERhaWx5IFJpZGVyc2hpcCAtIDIwMjQiLA0KICAgIGNhcHRpb24gPSAiU291cmNlOiBJbmRlZ28gYmlrZSBzaGFyZSINCiAgKQ0KDQpgYGANCg0KVGhlIGRyYXN0aWMgZGlwIGluIGVhcmx5IE9jdG9iZXIgdGhhdCBzaG9vdHMgZnJvbSAzMDAwIHRyaXBzIHRvIGFsbW9zdCA1MDAwIGlzIHN1c3BpY2lvdXMuIEkgdHJpZWQgbG9va2luZyB1cCBub3RhYmxlIGV2ZW50cyB0aGF0IG9jY3VycmVkIGluIFBoaWxhZGVscGhpYSBsYXN0IGZhbGwsIGFuZCBJIGNvdWxkbid0IGZpbmQgbXVjaCBpbmZvcm1hdGlvbi4gQWxzbyB0aGUgc3Bpa2UgaW4gcmlkZXJzaGlwIGluIFExIE1hcmNoIDIwMjQgY291bGQgYmUgZHVlIHRvIGEgbnVtYmVyIG9mIHRoaW5ncywgb2RkbHkgd2FybSB3ZWF0aGVyLCBTRVBUQSBzYWZldHkgaXNzdWVzLCBhbmQgbW9yZS4gDQoNCiMjIENvbWJpbmluZyBhbGwgcXVhcnRlcnMgaW50byBvbmUgdmFyaWFibGUNClRoaXMgY29kZSBjaHVuayBjb21iaW5lcyBhbGwgcXVhcnRlcnMgaW50byBvbmUgdmFyaWFibGUgZm9yIGVhc2llciBzeW50aGVzaXMgb25jZSB3ZSBzdGFydCBsb29raW5nIGF0IHRoZSByZWdyZXNzaW9ucyBhbmQgdGVtcG9yYWwgcGF0dGVybnMuDQpgYGB7ciByZWNvbWJpbmUtcHJvY2Vzc2VkLXF1YXJ0ZXJzLCBlY2hvID0gVFJVRSwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlPUZBTFNFLCBjYWNoZSA9IFRSVUV9DQoNCmluZGVnb19hbGwgPC0gYmluZF9yb3dzKA0KICBRMV8yMDI0ID0gaW5kZWdvcTEsICAjIE5vdyB0aGVzZSBoYXZlIGludGVydmFsNjAsIHdlZWssIG1vbnRoLCBob3VyLCBldGMuDQogIFEyXzIwMjQgPSBpbmRlZ29xMiwNCiAgUTNfMjAyNCA9IGluZGVnb3EzLA0KICBRNF8yMDI0ID0gaW5kZWdvcTQsDQogIC5pZCA9ICJxdWFydGVyIg0KKQ0KDQojIFZlcmlmeSB0aW1lIGZlYXR1cmVzIGFyZSBwcmVzZW50DQpjYXQoIlRvdGFsIHRyaXBzIGluIHByb2Nlc3NlZCBpbmRlZ29fYWxsOiIsIGZvcm1hdChucm93KGluZGVnb19hbGwpLCBiaWcubWFyayA9ICIsIiksICJcbiIpDQpjYXQoIlRpbWUgZmVhdHVyZXMgcHJlc2VudDoiLCBhbGwoYygiaW50ZXJ2YWw2MCIsICJ3ZWVrIiwgIm1vbnRoIiwgImhvdXIiLCAiZG90dyIpICVpbiUgbmFtZXMoaW5kZWdvX2FsbCkpLCAiXG4iKQ0KDQojIENoZWNrIGRpc3RyaWJ1dGlvbiBieSBxdWFydGVyDQp0YWJsZShpbmRlZ29fYWxsJHF1YXJ0ZXIpDQpgYGANCg0KDQojIyBIb3VybHkgUGF0dGVybnMNCkluIHRoaXMgY29kZSBjaHVuayBJIHRvb2sgZWFjaCBxdWFydGVyLCBhc3NpZ25lZCBpdCB0byBhIHZhcmlhYmxlLCB0aGVuIGFzc2lnbmVkIHRoYXQgdmFyaWFibGUgdG8gYSBwbG90dGluZyB2YXJpYWJsZSwgdGhlbiBsaW5lZCB0aGVtIHVwIGZvciBkaXNwbGF5IGJlbG93Lg0KDQpgYGB7ciBob3VybHktcGF0dGVybnMsICBlY2hvID0gRkFMU0UsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZT1GQUxTRSwgY2FjaGUgPSBUUlVFfQ0KDQojIEF2ZXJhZ2UgdHJpcHMgYnkgaG91ciBhbmQgZGF5IHR5cGUgKHg0KQ0KaG91cmx5MSA8LSBpbmRlZ29xMSAlPiUNCiAgZ3JvdXBfYnkoaG91ciwgd2Vla2VuZCkgJT4lDQogIHN1bW1hcml6ZShhdmdfdHJpcHMgPSBuKCkgLyBuX2Rpc3RpbmN0KGRhdGUpLCAuZ3JvdXBzID0gImRyb3AiKSAlPiUNCiAgbXV0YXRlKGRheV90eXBlID0gaWZlbHNlKHdlZWtlbmQgPT0gMSwgIldlZWtlbmQiLCAiV2Vla2RheSIpKQ0KDQpob3VybHkyIDwtIGluZGVnb3EyICU+JQ0KICBncm91cF9ieShob3VyLCB3ZWVrZW5kKSAlPiUNCiAgc3VtbWFyaXplKGF2Z190cmlwcyA9IG4oKSAvIG5fZGlzdGluY3QoZGF0ZSksIC5ncm91cHMgPSAiZHJvcCIpICU+JQ0KICBtdXRhdGUoZGF5X3R5cGUgPSBpZmVsc2Uod2Vla2VuZCA9PSAxLCAiV2Vla2VuZCIsICJXZWVrZGF5IikpDQoNCmhvdXJseTMgPC0gaW5kZWdvcTMgJT4lDQogIGdyb3VwX2J5KGhvdXIsIHdlZWtlbmQpICU+JQ0KICBzdW1tYXJpemUoYXZnX3RyaXBzID0gbigpIC8gbl9kaXN0aW5jdChkYXRlKSwgLmdyb3VwcyA9ICJkcm9wIikgJT4lDQogIG11dGF0ZShkYXlfdHlwZSA9IGlmZWxzZSh3ZWVrZW5kID09IDEsICJXZWVrZW5kIiwgIldlZWtkYXkiKSkNCg0KaG91cmx5NCA8LSBpbmRlZ29xNCAlPiUNCiAgZ3JvdXBfYnkoaG91ciwgd2Vla2VuZCkgJT4lDQogIHN1bW1hcml6ZShhdmdfdHJpcHMgPSBuKCkgLyBuX2Rpc3RpbmN0KGRhdGUpLCAuZ3JvdXBzID0gImRyb3AiKSAlPiUNCiAgbXV0YXRlKGRheV90eXBlID0gaWZlbHNlKHdlZWtlbmQgPT0gMSwgIldlZWtlbmQiLCAiV2Vla2RheSIpKQ0KDQojIFNpbXBsaWZpZWQgcGxvdHMgLSBzaG9ydGVyIHRpdGxlcywgcmVtb3ZlIGluZGl2aWR1YWwgc3VidGl0bGVzDQpoMSA8LSBnZ3Bsb3QoaG91cmx5MSwgYWVzKHggPSBob3VyLCB5ID0gYXZnX3RyaXBzLCBjb2xvciA9IGRheV90eXBlKSkgKw0KICBnZW9tX2xpbmUobGluZXdpZHRoID0gMS4yKSArDQogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBjKCJXZWVrZGF5IiA9ICIjMDg1MTljIiwgIldlZWtlbmQiID0gIiM2YmFlZDYiKSkgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIlExIDIwMjQgKFdpbnRlcikiLA0KICAgIHggPSBOVUxMLCAgIyBSZW1vdmUgeC1heGlzIGxhYmVsIGZyb20gdG9wIHBsb3RzDQogICAgeSA9ICJBdmcgVHJpcHMvSG91ciIsDQogICAgY29sb3IgPSAiRGF5IFR5cGUiDQogICkgKw0KICBwbG90VGhlbWUgKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpICAjIFJlbW92ZSBsZWdlbmQgZnJvbSBpbmRpdmlkdWFsIHBsb3RzDQoNCmgyIDwtIGdncGxvdChob3VybHkyLCBhZXMoeCA9IGhvdXIsIHkgPSBhdmdfdHJpcHMsIGNvbG9yID0gZGF5X3R5cGUpKSArDQogIGdlb21fbGluZShsaW5ld2lkdGggPSAxLjIpICsNCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGMoIldlZWtkYXkiID0gIiMwODUxOWMiLCAiV2Vla2VuZCIgPSAiIzZiYWVkNiIpKSArDQogIGxhYnMoDQogICAgdGl0bGUgPSAiUTIgMjAyNCAoU3ByaW5nKSIsDQogICAgeCA9IE5VTEwsDQogICAgeSA9ICJBdmcgVHJpcHMvSG91ciIsDQogICAgY29sb3IgPSAiRGF5IFR5cGUiDQogICkgKw0KICBwbG90VGhlbWUgKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpDQoNCmgzIDwtIGdncGxvdChob3VybHkzLCBhZXMoeCA9IGhvdXIsIHkgPSBhdmdfdHJpcHMsIGNvbG9yID0gZGF5X3R5cGUpKSArDQogIGdlb21fbGluZShsaW5ld2lkdGggPSAxLjIpICsNCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGMoIldlZWtkYXkiID0gIiMwODUxOWMiLCAiV2Vla2VuZCIgPSAiIzZiYWVkNiIpKSArDQogIGxhYnMoDQogICAgdGl0bGUgPSAiUTMgMjAyNCAoU3VtbWVyKSIsDQogICAgeCA9ICJIb3VyIG9mIERheSIsDQogICAgeSA9ICJBdmcgVHJpcHMvSG91ciIsDQogICAgY29sb3IgPSAiRGF5IFR5cGUiDQogICkgKw0KICBwbG90VGhlbWUgKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpDQoNCmg0IDwtIGdncGxvdChob3VybHk0LCBhZXMoeCA9IGhvdXIsIHkgPSBhdmdfdHJpcHMsIGNvbG9yID0gZGF5X3R5cGUpKSArDQogIGdlb21fbGluZShsaW5ld2lkdGggPSAxLjIpICsNCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGMoIldlZWtkYXkiID0gIiMwODUxOWMiLCAiV2Vla2VuZCIgPSAiIzZiYWVkNiIpKSArDQogIGxhYnMoDQogICAgdGl0bGUgPSAiUTQgMjAyNCAoRmFsbCkiLA0KICAgIHggPSAiSG91ciBvZiBEYXkiLA0KICAgIHkgPSAiQXZnIFRyaXBzL0hvdXIiLA0KICAgIGNvbG9yID0gIkRheSBUeXBlIg0KICApICsNCiAgcGxvdFRoZW1lDQoNCiMgMngyIGdyaWQgd2l0aCBPTkUgc2hhcmVkIGxlZ2VuZA0KKGgxIHwgaDIpIC8gKGgzIHwgaDQpICsgDQogIHBsb3RfbGF5b3V0KGd1aWRlcyA9ICJjb2xsZWN0IikgKyAgIyBDb2xsZWN0IGxlZ2VuZHMgaW50byBvbmUNCiAgcGxvdF9hbm5vdGF0aW9uKA0KICAgIHRpdGxlID0gIkF2ZXJhZ2UgSG91cmx5IFJpZGVyc2hpcCBQYXR0ZXJucyBieSBRdWFydGVyIiwNCiAgICBzdWJ0aXRsZSA9ICJDbGVhciBjb21tdXRlIHBhdHRlcm5zIG9uIHdlZWtkYXlzIGFjcm9zcyBhbGwgc2Vhc29ucyIsDQogICAgY2FwdGlvbiA9ICJTb3VyY2U6IEluZGVnbyBiaWtlIHNoYXJlIg0KICApICYNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIpICAjIFB1dCBsZWdlbmQgYXQgYm90dG9tDQpgYGANCg0KIA0KRHVyaW5nIHR5cGljYWwgbW9ybmluZyBhbmQgZXZlbmluZyBydXNoIGhvdXIgY29tbXV0ZSB0aW1lcywgdGhlcmUgaXMgcGVhayBJbmRlZ28gcmlkZXJzaGlwIGFsbW9zdCBjb25zaXN0ZW50bHkgYWNyb3NzIGFsbCBmb3VyIHF1YXJ0ZXJzIGF0IHdoYXQgaXMgYXNzdW1pbmcgdG8gYmUgYXJvdW5kIDcvOCBBTSBhbmQgNS82IFBNLiBGb3IgdGhlIHdlZWtlbmRzLCB0aGVyZSBpcyBtb3JlIG9mIGEgZ3JhZHVhbCBpbmNyZWFzZSBpbiByaWRlcnNoaXAgYXMgdGhlIGRheSBwcm9ncmVzc2VzLCB0aGVuIGNhc3VhbGx5IGZhbGxzIG9mZiBhcyB0aGUgZXZlbmluZyBwcm9ncmVzc2VzLiANCg0KIyMgVG9wIFN0YXRpb25zDQoNClRoZSBjaHVuayBiZWxvdyByYW5rcyBlYWNoIHN0YXRpb24gYmFzZWQgb24gdGhlIHN0YXRpb24gdGhhdCBoYXMgdGhlIG1vc3Qgc3RhcnRzL2RlcGxveW1lbnRzIGZyb20uIA0KDQpgYGB7ciB0b3Bfc3RhdGlvbnMsICBlY2hvID0gRkFMU0UsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZT1GQUxTRSwgY2FjaGUgPSBUUlVFfQ0KDQojIEZ1bmN0aW9uIHRvIGNyZWF0ZSBzdGF0aW9uIHN1bW1hcnkgdGFibGUNCmNyZWF0ZV9zdGF0aW9uX3N1bW1hcnkgPC0gZnVuY3Rpb24oZGF0YSwgcXVhcnRlcl9uYW1lKSB7DQogIHN0YXRpb25fc3VtbWFyeSA8LSBkYXRhICU+JQ0KICAgIGdyb3VwX2J5KHN0YXJ0X3N0YXRpb24pICU+JQ0KICAgIHN1bW1hcml6ZSgNCiAgICAgIHRvdGFsX3RyaXBzID0gbigpLA0KICAgICAgYXZnX2RhaWx5ID0gbigpIC8gbl9kaXN0aW5jdChkYXRlKSwNCiAgICAgIC5ncm91cHMgPSAiZHJvcCINCiAgICApICU+JQ0KICAgIGFycmFuZ2UoZGVzYyh0b3RhbF90cmlwcykpICU+JQ0KICAgIGhlYWQoMTApICU+JQ0KICAgIG11dGF0ZSgNCiAgICAgIHJhbmsgPSByb3dfbnVtYmVyKCksDQogICAgICB0b3RhbF90cmlwcyA9IGZvcm1hdCh0b3RhbF90cmlwcywgYmlnLm1hcmsgPSAiLCIpLA0KICAgICAgYXZnX2RhaWx5ID0gcm91bmQoYXZnX2RhaWx5LCAxKQ0KICAgICkgJT4lDQogICAgZHBseXI6OnNlbGVjdChyYW5rLCBzdGFydF9zdGF0aW9uLCB0b3RhbF90cmlwcywgYXZnX2RhaWx5KQ0KICANCiAga2FibGUoc3RhdGlvbl9zdW1tYXJ5LA0KICAgICAgICBjYXB0aW9uID0gcGFzdGUoIlRvcCAxMCBTdGF0aW9ucyAtIiwgcXVhcnRlcl9uYW1lKSwNCiAgICAgICAgY29sLm5hbWVzID0gYygiUmFuayIsICJTdGF0aW9uIiwgIlRvdGFsIFRyaXBzIiwgIkF2ZyBEYWlseSIpLA0KICAgICAgICBhbGlnbiA9IGMoImMiLCAibCIsICJyIiwgInIiKSkgJT4lDQogICAga2FibGVfc3R5bGluZygNCiAgICAgIGJvb3RzdHJhcF9vcHRpb25zID0gYygic3RyaXBlZCIsICJjb25kZW5zZWQiKSwNCiAgICAgIGZ1bGxfd2lkdGggPSBGQUxTRSwNCiAgICAgIGZvbnRfc2l6ZSA9IDEyDQogICAgKSAlPiUNCiAgICBjb2x1bW5fc3BlYygxLCBib2xkID0gVFJVRSwgd2lkdGggPSAiMC41aW4iKSAlPiUNCiAgICBjb2x1bW5fc3BlYygyLCB3aWR0aCA9ICIzaW4iKSAlPiUNCiAgICBjb2x1bW5fc3BlYygzOjQsIHdpZHRoID0gIjAuOGluIikNCn0NCg0KIyBDcmVhdGUgdGFibGVzIGZvciBlYWNoIHF1YXJ0ZXINCmNyZWF0ZV9zdGF0aW9uX3N1bW1hcnkoaW5kZWdvcTEsICJRMSAyMDI0IikNCmNyZWF0ZV9zdGF0aW9uX3N1bW1hcnkoaW5kZWdvcTIsICJRMiAyMDI0IikNCmNyZWF0ZV9zdGF0aW9uX3N1bW1hcnkoaW5kZWdvcTMsICJRMyAyMDI0IikNCmNyZWF0ZV9zdGF0aW9uX3N1bW1hcnkoaW5kZWdvcTQsICJRNCAyMDI0IikNCmBgYA0KDQpTdGF0aW9uICMzMDEwIGluIFExIDIwMjQgaGFzIHRoZSBtb3N0IG9yaWdpbiB0cmlwcyBhdCA0MC45IGF2ZXJhZ2UgdHJpcHMgcGVyIGRheS4gU3RhdGlvbiAjMzAxMCBjb250aW51ZXMgdGhlIHRyZW5kIG9mIGJlaW5nIHRoZSBoaWdoZXN0IHN0YXJ0aW5nIHBvaW50IG9mIHRyYXZlbCBpbiBhbGwgcXVhcnRlcnMgb2YgMjAyNC4gDQoNCi0tLQ0KDQojIEdldCBQaGlsYWRlbHBoaWEgU3BhdGlhbCBDb250ZXh0DQoNCiMjIExvYWQgUGhpbGFkZWxwaGlhIENlbnN1cyBEYXRhDQoNCldlJ2xsIGdldCBjZW5zdXMgdHJhY3QgZGF0YSB0byBhZGQgZGVtb2dyYXBoaWMgY29udGV4dCB0byBvdXIgc3RhdGlvbnMuDQoNCmBgYHtyIGxvYWRfY2Vuc3VzLCBlY2hvID0gRkFMU0UsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFfQ0KIyBHZXQgUGhpbGFkZWxwaGlhIGNlbnN1cyB0cmFjdHMNCnBoaWxseV9jZW5zdXMgPC0gZ2V0X2FjcygNCiAgZ2VvZ3JhcGh5ID0gInRyYWN0IiwNCiAgdmFyaWFibGVzID0gYygNCiAgICAiQjAxMDAzXzAwMSIsICAjIFRvdGFsIHBvcHVsYXRpb24NCiAgICAiQjE5MDEzXzAwMSIsICAjIE1lZGlhbiBob3VzZWhvbGQgaW5jb21lDQogICAgIkIwODMwMV8wMDEiLCAgIyBUb3RhbCBjb21tdXRlcnMNCiAgICAiQjA4MzAxXzAxMCIsICAjIENvbW11dGUgYnkgdHJhbnNpdA0KICAgICJCMDIwMDFfMDAyIiwgICMgV2hpdGUgYWxvbmUNCiAgICAiQjI1MDc3XzAwMSIgICAjIE1lZGlhbiBob21lIHZhbHVlDQogICksDQogIHN0YXRlID0gIlBBIiwNCiAgY291bnR5ID0gIlBoaWxhZGVscGhpYSIsDQogIHllYXIgPSAyMDIyLA0KICBnZW9tZXRyeSA9IFRSVUUsDQogIG91dHB1dCA9ICJ3aWRlIg0KKSAlPiUNCiAgcmVuYW1lKA0KICAgIFRvdGFsX1BvcCA9IEIwMTAwM18wMDFFLA0KICAgIE1lZF9JbmMgPSBCMTkwMTNfMDAxRSwNCiAgICBUb3RhbF9Db21tdXRlcnMgPSBCMDgzMDFfMDAxRSwNCiAgICBUcmFuc2l0X0NvbW11dGVycyA9IEIwODMwMV8wMTBFLA0KICAgIFdoaXRlX1BvcCA9IEIwMjAwMV8wMDJFLA0KICAgIE1lZF9Ib21lX1ZhbHVlID0gQjI1MDc3XzAwMUUNCiAgKSAlPiUNCiAgbXV0YXRlKA0KICAgIFBlcmNlbnRfVGFraW5nX1RyYW5zaXQgPSAoVHJhbnNpdF9Db21tdXRlcnMgLyBUb3RhbF9Db21tdXRlcnMpICogMTAwLA0KICAgIFBlcmNlbnRfV2hpdGUgPSAoV2hpdGVfUG9wIC8gVG90YWxfUG9wKSAqIDEwMA0KICApICU+JQ0KICBzdF90cmFuc2Zvcm0oY3JzID0gNDMyNikgICMgV0dTODQgZm9yIGxhdC9sb24gbWF0Y2hpbmcNCg0KIyBDaGVjayB0aGUgZGF0YQ0KZ2xpbXBzZShwaGlsbHlfY2Vuc3VzKQ0KYGBgDQoNCiMjIE1hcCBQaGlsYWRlbHBoaWEgQ29udGV4dCwgTWVkaWFuIEhvdXNlaG9sZCBJbmNvbWUNCg0KYGBge3IgbWFwX3BoaWxseSwgZWNobyA9IFRSVUUsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZT1GQUxTRSwgY2FjaGUgPSBUUlVFIH0NCg0KDQojIENyZWF0ZSBzdGF0aW9uIGxvY2F0aW9uIGRhdGENCnN0YXRpb25zX3ExX2NsZWFuIDwtIGluZGVnb3ExICU+JQ0KICBncm91cF9ieShzdGFydF9zdGF0aW9uLCBzdGFydF9sb24sIHN0YXJ0X2xhdCkgJT4lDQogIHN1bW1hcml6ZSh0cmlwcyA9IG4oKSwgLmdyb3VwcyA9ICJkcm9wIikNCg0KY2F0KCJSZW1vdmVkIiwgbnJvdyhzdGF0aW9uc19xMV9jbGVhbikgLSBucm93KHN0YXRpb25zX3ExX2NsZWFuKSwgInN0YXRpb25zIHdpdGggbWlzc2luZyBjb29yZGluYXRlc1xuIikNCg0Kc3RhdGlvbnNfcTIgPC0gaW5kZWdvcTIgJT4lDQogIGdyb3VwX2J5KHN0YXJ0X3N0YXRpb24sIHN0YXJ0X2xvbiwgc3RhcnRfbGF0KSAlPiUNCiAgc3VtbWFyaXplKHRyaXBzID0gbigpLCAuZ3JvdXBzID0gImRyb3AiKQ0KDQpzdGF0aW9uc19xMyA8LSBpbmRlZ29xMyAlPiUNCiAgZ3JvdXBfYnkoc3RhcnRfc3RhdGlvbiwgc3RhcnRfbG9uLCBzdGFydF9sYXQpICU+JQ0KICBzdW1tYXJpemUodHJpcHMgPSBuKCksIC5ncm91cHMgPSAiZHJvcCIpDQoNCnN0YXRpb25zX3E0IDwtIGluZGVnb3E0ICU+JQ0KICBncm91cF9ieShzdGFydF9zdGF0aW9uLCBzdGFydF9sb24sIHN0YXJ0X2xhdCkgJT4lDQogIHN1bW1hcml6ZSh0cmlwcyA9IG4oKSwgLmdyb3VwcyA9ICJkcm9wIikNCg0KIyBRMSBNYXAgDQptaGhpbmMxIDwtIGdncGxvdCgpICsNCiAgZ2VvbV9zZihkYXRhID0gcGhpbGx5X2NlbnN1cywgYWVzKGZpbGwgPSBNZWRfSW5jKSwgY29sb3IgPSBOQSkgKw0KICBzY2FsZV9maWxsX3ZpcmlkaXMoDQogICAgb3B0aW9uID0gInZpcmlkaXMiLA0KICAgIG5hbWUgPSAiTWVkaWFuIEluY29tZSIsDQogICAgbGFiZWxzID0gc2NhbGVzOjpsYWJlbF9kb2xsYXIoc2NhbGUgPSAxLzEwMDAsIHN1ZmZpeCA9ICJLIikNCiAgKSArDQogIGxhYnModGl0bGUgPSAiUTEgMjAyNCIpICsNCiAgZ2VvbV9wb2ludCgNCiAgICBkYXRhID0gc3RhdGlvbnNfcTFfY2xlYW4sDQogICAgYWVzKHggPSBzdGFydF9sb24sIHkgPSBzdGFydF9sYXQsIHNpemUgPSB0cmlwcyksDQogICAgY29sb3IgPSAiI0NDMjgxMCIsIGFscGhhID0gMC4xDQogICkgKw0KICBzY2FsZV9zaXplX2NvbnRpbnVvdXMocmFuZ2UgPSBjKDEsIDQpLCBndWlkZSA9ICJub25lIikgKw0KICBjb29yZF9zZih4bGltID0gYygtNzUuMjgsIC03NC45NiksIHlsaW0gPSBjKDM5Ljg3LCA0MC4xNCksIGV4cGFuZCA9IEZBTFNFKSArICAjIEZJWDogRm9yY2UgcHJvcGVyIGV4dGVudA0KICBtYXBUaGVtZSArDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikNCg0KIyBRMiBNYXANCm1oaGluYzIgPC0gZ2dwbG90KCkgKw0KICBnZW9tX3NmKGRhdGEgPSBwaGlsbHlfY2Vuc3VzLCBhZXMoZmlsbCA9IE1lZF9JbmMpLCBjb2xvciA9IE5BKSArDQogIHNjYWxlX2ZpbGxfdmlyaWRpcygNCiAgICBvcHRpb24gPSAidmlyaWRpcyIsDQogICAgbmFtZSA9ICJNZWRpYW4gSW5jb21lIiwNCiAgICBsYWJlbHMgPSBzY2FsZXM6OmxhYmVsX2RvbGxhcihzY2FsZSA9IDEvMTAwMCwgc3VmZml4ID0gIksiKQ0KICApICsNCiAgbGFicyh0aXRsZSA9ICJRMiAyMDI0IikgKw0KICBnZW9tX3BvaW50KA0KICAgIGRhdGEgPSBzdGF0aW9uc19xMiwNCiAgICBhZXMoeCA9IHN0YXJ0X2xvbiwgeSA9IHN0YXJ0X2xhdCwgc2l6ZSA9IHRyaXBzKSwNCiAgICBjb2xvciA9ICIjQ0MyODEwIiwgYWxwaGEgPSAwLjENCiAgKSArDQogIHNjYWxlX3NpemVfY29udGludW91cyhyYW5nZSA9IGMoMSwgNCksIGd1aWRlID0gIm5vbmUiKSArDQogIG1hcFRoZW1lICsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKQ0KDQojIFEzIE1hcA0KbWhoaW5jMyA8LSBnZ3Bsb3QoKSArDQogIGdlb21fc2YoZGF0YSA9IHBoaWxseV9jZW5zdXMsIGFlcyhmaWxsID0gTWVkX0luYyksIGNvbG9yID0gTkEpICsNCiAgc2NhbGVfZmlsbF92aXJpZGlzKA0KICAgIG9wdGlvbiA9ICJ2aXJpZGlzIiwNCiAgICBuYW1lID0gIk1lZGlhbiBJbmNvbWUiLA0KICAgIGxhYmVscyA9IHNjYWxlczo6bGFiZWxfZG9sbGFyKHNjYWxlID0gMS8xMDAwLCBzdWZmaXggPSAiSyIpDQogICkgKw0KICBsYWJzKHRpdGxlID0gIlEzIDIwMjQiKSArDQogIGdlb21fcG9pbnQoDQogICAgZGF0YSA9IHN0YXRpb25zX3EzLA0KICAgIGFlcyh4ID0gc3RhcnRfbG9uLCB5ID0gc3RhcnRfbGF0LCBzaXplID0gdHJpcHMpLA0KICAgIGNvbG9yID0gIiNDQzI4MTAiLCBhbHBoYSA9IDAuMQ0KICApICsNCiAgc2NhbGVfc2l6ZV9jb250aW51b3VzKHJhbmdlID0gYygxLCA0KSwgZ3VpZGUgPSAibm9uZSIpICsNCiAgbWFwVGhlbWUgKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpDQoNCiMgUTQgTWFwDQptaGhpbmM0IDwtIGdncGxvdCgpICsNCiAgZ2VvbV9zZihkYXRhID0gcGhpbGx5X2NlbnN1cywgYWVzKGZpbGwgPSBNZWRfSW5jKSwgY29sb3IgPSBOQSkgKw0KICBzY2FsZV9maWxsX3ZpcmlkaXMoDQogICAgb3B0aW9uID0gInZpcmlkaXMiLA0KICAgIG5hbWUgPSAiTWVkaWFuIEluY29tZSIsDQogICAgbGFiZWxzID0gc2NhbGVzOjpsYWJlbF9kb2xsYXIoc2NhbGUgPSAxLzEwMDAsIHN1ZmZpeCA9ICJLIikNCiAgKSArDQogIGxhYnModGl0bGUgPSAiUTQgMjAyNCIpICsNCiAgZ2VvbV9wb2ludCgNCiAgICBkYXRhID0gc3RhdGlvbnNfcTQsDQogICAgYWVzKHggPSBzdGFydF9sb24sIHkgPSBzdGFydF9sYXQsIHNpemUgPSB0cmlwcyksDQogICAgY29sb3IgPSAiI0NDMjgxMCIsIGFscGhhID0gMC4xDQogICkgKw0KICBzY2FsZV9zaXplX2NvbnRpbnVvdXMocmFuZ2UgPSBjKDEsIDQpLCBndWlkZSA9ICJub25lIikgKw0KICBtYXBUaGVtZQ0KDQoNCm1oaGluYzEgKyBtaGhpbmMyICsgbWhoaW5jMyArIG1oaGluYzQgKyAgICMgPC0tIEFERCBBTEwgRk9VUiENCiAgcGxvdF9sYXlvdXQoZ3VpZGVzID0gImNvbGxlY3QiLCBucm93ID0gMSkgKw0KICBwbG90X2Fubm90YXRpb24oDQogICAgdGl0bGUgPSAiSW5kZWdvIFN0YXRpb24gQWN0aXZpdHkgYnkgUXVhcnRlcjogMjAyNCIsDQogICAgc3VidGl0bGUgPSAiUmVkIHBvaW50cyBzaG93IHN0YXRpb24gbG9jYXRpb25zIHNpemVkIGJ5IHRyaXAgdm9sdW1lIiwNCiAgICBjYXB0aW9uID0gIlNvdXJjZTogSW5kZWdvIGJpa2Ugc2hhcmUsIFVTIENlbnN1cyBBQ1MgMjAyMiINCiAgKSAmDQogIHRoZW1lKA0KICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iLA0KICAgIGxlZ2VuZC5kaXJlY3Rpb24gPSAiaG9yaXpvbnRhbCIsDQogICAgbGVnZW5kLmtleS53aWR0aCA9IHVuaXQoMywgImNtIiksDQogICAgbGVnZW5kLmtleS5oZWlnaHQgPSB1bml0KDAuMywgImNtIiksDQogICAgbGVnZW5kLmJveC5tYXJnaW4gPSBtYXJnaW4odCA9IDEwKQ0KICApDQoNCmBgYA0KDQpBIHF1aWNrIHZpc3VhbGl6YXRpb24gc2hvd3MgdGhlcmUgaXMgbm90IG11Y2ggZGlmZmVyZW50aWF0aW9uIGJldHdlZW4gdGhlIHF1YXJ0ZXJzIGluIHRlcm1zIG9mIHRyaXAgdm9sdW1lIGF0IHRoZSBjaXR5LXNjYWxlLg0KDQojIyBKb2luIENlbnN1cyBEYXRhIHRvIFN0YXRpb25zDQoNCldlJ2xsIHNwYXRpYWxseSBqb2luIGNlbnN1cyBjaGFyYWN0ZXJpc3RpY3MgdG8gZWFjaCBiaWtlIHN0YXRpb24uDQoNCmBgYHtyIGNlbnN1cy1kYXRhLCBlY2hvID0gVFJVRSwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlPUZBTFNFLCBjYWNoZSA9IFRSVUV9DQojIENyZWF0ZSBzZiBvYmplY3QgZm9yIHN0YXRpb25zIGZyb20gQ09NQklORUQgZGF0YQ0Kc3RhdGlvbnNfc2YgPC0gaW5kZWdvX2FsbCAlPiUNCiAgZGlzdGluY3Qoc3RhcnRfc3RhdGlvbiwgc3RhcnRfbGF0LCBzdGFydF9sb24pICU+JQ0KICBmaWx0ZXIoIWlzLm5hKHN0YXJ0X2xhdCksICFpcy5uYShzdGFydF9sb24pKSAlPiUNCiAgc3RfYXNfc2YoY29vcmRzID0gYygic3RhcnRfbG9uIiwgInN0YXJ0X2xhdCIpLCBjcnMgPSA0MzI2KQ0KDQojIFNwYXRpYWwgam9pbiB0byBnZXQgY2Vuc3VzIHRyYWN0IGZvciBlYWNoIHN0YXRpb24NCnN0YXRpb25zX2NlbnN1cyA8LSBzdF9qb2luKHN0YXRpb25zX3NmLCBwaGlsbHlfY2Vuc3VzLCBsZWZ0ID0gVFJVRSkgJT4lDQogIHN0X2Ryb3BfZ2VvbWV0cnkoKQ0KDQojIExvb2sgYXQgdGhlIHJlc3VsdCAtIGludmVzdGlnYXRlIHdoZXRoZXIgYWxsIG9mIHRoZSBzdGF0aW9ucyBqb2luZWQgdG8gY2Vuc3VzIGRhdGENCnN0YXRpb25zX2Zvcl9tYXAgPC0gaW5kZWdvX2FsbCAlPiUNCiAgZGlzdGluY3Qoc3RhcnRfc3RhdGlvbiwgc3RhcnRfbGF0LCBzdGFydF9sb24pICU+JQ0KICBmaWx0ZXIoIWlzLm5hKHN0YXJ0X2xhdCksICFpcy5uYShzdGFydF9sb24pKSAlPiUNCiAgbGVmdF9qb2luKA0KICAgIHN0YXRpb25zX2NlbnN1cyAlPiUgZHBseXI6OnNlbGVjdChzdGFydF9zdGF0aW9uLCBNZWRfSW5jKSwNCiAgICBieSA9ICJzdGFydF9zdGF0aW9uIg0KICApICU+JQ0KICBtdXRhdGUoaGFzX2NlbnN1cyA9ICFpcy5uYShNZWRfSW5jKSkNCg0KIyBBZGQgYmFjayB0byB0cmlwIGRhdGENCmluZGVnb19hbGxfY2Vuc3VzIDwtIGluZGVnb19hbGwgJT4lDQogIGxlZnRfam9pbigNCiAgICBzdGF0aW9uc19jZW5zdXMgJT4lIA0KICAgICAgZHBseXI6OnNlbGVjdChzdGFydF9zdGF0aW9uLCBNZWRfSW5jLCBQZXJjZW50X1Rha2luZ19UcmFuc2l0LCANCiAgICAgICAgICAgICBQZXJjZW50X1doaXRlLCBUb3RhbF9Qb3ApLA0KICAgIGJ5ID0gInN0YXJ0X3N0YXRpb24iDQogICkNCg0KIyBQcmVwYXJlIGRhdGEgZm9yIHZpc3VhbGl6YXRpb24NCnN0YXRpb25zX2Zvcl9tYXAgPC0gaW5kZWdvX2FsbCAlPiUNCiAgZGlzdGluY3Qoc3RhcnRfc3RhdGlvbiwgc3RhcnRfbGF0LCBzdGFydF9sb24pICU+JQ0KICBmaWx0ZXIoIWlzLm5hKHN0YXJ0X2xhdCksICFpcy5uYShzdGFydF9sb24pKSAlPiUNCiAgbGVmdF9qb2luKA0KICAgIHN0YXRpb25zX2NlbnN1cyAlPiUgZHBseXI6OnNlbGVjdChzdGFydF9zdGF0aW9uLCBNZWRfSW5jKSwNCiAgICBieSA9ICJzdGFydF9zdGF0aW9uIg0KICApICU+JQ0KICBtdXRhdGUoaGFzX2NlbnN1cyA9ICFpcy5uYShNZWRfSW5jKSkNCg0KIyBDcmVhdGUgdGhlIG1hcCBzaG93aW5nIHByb2JsZW0gc3RhdGlvbnMNCmdncGxvdCgpICsNCiAgZ2VvbV9zZihkYXRhID0gcGhpbGx5X2NlbnN1cywgYWVzKGZpbGwgPSBNZWRfSW5jKSwgY29sb3IgPSAid2hpdGUiLCBzaXplID0gMC4xKSArDQogIHNjYWxlX2ZpbGxfdmlyaWRpcygNCiAgICBvcHRpb24gPSAidmlyaWRpcyIsDQogICAgbmFtZSA9ICJNZWRpYW5cbkluY29tZSIsDQogICAgbGFiZWxzID0gc2NhbGVzOjpkb2xsYXIsDQogICAgbmEudmFsdWUgPSAiZ3JleTkwIg0KICApICsNCiAgIyBTdGF0aW9ucyB3aXRoIGNlbnN1cyBkYXRhIChzbWFsbCBncmV5IGRvdHMpDQogIGdlb21fcG9pbnQoDQogICAgZGF0YSA9IHN0YXRpb25zX2Zvcl9tYXAgJT4lIGZpbHRlcihoYXNfY2Vuc3VzKSwNCiAgICBhZXMoeCA9IHN0YXJ0X2xvbiwgeSA9IHN0YXJ0X2xhdCksDQogICAgY29sb3IgPSAiZ3JleTMwIiwgc2l6ZSA9IDEsIGFscGhhID0gMC42DQogICkgKw0KICAjIFN0YXRpb25zIFdJVEhPVVQgY2Vuc3VzIGRhdGEgKHJlZCBYIG1hcmtzIHRoZSBzcG90KQ0KICBnZW9tX3BvaW50KA0KICAgIGRhdGEgPSBzdGF0aW9uc19mb3JfbWFwICU+JSBmaWx0ZXIoIWhhc19jZW5zdXMpLA0KICAgIGFlcyh4ID0gc3RhcnRfbG9uLCB5ID0gc3RhcnRfbGF0KSwNCiAgICBjb2xvciA9ICJyZWQiLCBzaXplID0gMSwgc2hhcGUgPSA0LCBzdHJva2UgPSAxLjUNCiAgKSArDQogIGxhYnMoDQogICAgdGl0bGUgPSAiUGhpbGFkZWxwaGlhIE1lZGlhbiBIb3VzZWhvbGQgSW5jb21lIGJ5IENlbnN1cyBUcmFjdCIsDQogICAgc3VidGl0bGUgPSAiSW5kZWdvIHN0YXRpb25zIHNob3duIChSRUQgPSBubyBjZW5zdXMgZGF0YSBtYXRjaCkiLA0KICAgIGNhcHRpb24gPSAiUmVkIFggbWFya3MgaW5kaWNhdGUgc3RhdGlvbnMgdGhhdCBkaWRuJ3Qgam9pbiB0byBjZW5zdXMgdHJhY3RzIg0KICApICsNCiAgbWFwVGhlbWUNCmBgYA0KDQpVbnN1cmUgd2hhdCB0aGUgZGVhbCBpcyB3aXRoIHRoaXMgb3V0bGllciwgYXMgb3VyIGVhcmxpZXIgY2xlYW5pbmcgc2hvdWxkIGhhdmUgZG9uZSBhd2F5IHdpdGggaXQuDQoNCmBgYHtyIGRlYWwtd2l0aC1taXNzaW5nLWRhdGEsIGVjaG8gPSBUUlVFLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2U9RkFMU0UsIGNhY2hlID0gVFJVRX0NCiMgRGVhbGluZyB3aXRoIG1pc3NpbmcgZGF0YQ0KIyBJZGVudGlmeSB3aGljaCBzdGF0aW9ucyB0byBrZWVwIChvbmx5IHJlc2lkZW50aWFsIG5laWdoYm9yaG9vZHMpDQp2YWxpZF9zdGF0aW9ucyA8LSBzdGF0aW9uc19jZW5zdXMgJT4lDQogIGZpbHRlcighaXMubmEoTWVkX0luYykpICU+JQ0KICBwdWxsKHN0YXJ0X3N0YXRpb24pDQoNCiMgRmlsdGVyIHRyaXAgZGF0YSB0byB2YWxpZCBzdGF0aW9ucyBvbmx5DQppbmRlZ29fYWxsX2NlbnN1cyA8LSBpbmRlZ29fYWxsICU+JQ0KICBmaWx0ZXIoc3RhcnRfc3RhdGlvbiAlaW4lIHZhbGlkX3N0YXRpb25zKSAlPiUNCiAgbGVmdF9qb2luKA0KICAgIHN0YXRpb25zX2NlbnN1cyAlPiUgDQogICAgICBkcGx5cjo6c2VsZWN0KHN0YXJ0X3N0YXRpb24sIE1lZF9JbmMsIFBlcmNlbnRfVGFraW5nX1RyYW5zaXQsIA0KICAgICAgICAgICAgIFBlcmNlbnRfV2hpdGUsIFRvdGFsX1BvcCksDQogICAgYnkgPSAic3RhcnRfc3RhdGlvbiINCiAgKQ0KDQojIFN1bW1hcnkNCmNhdCgiVG90YWwgdHJpcHMgYmVmb3JlIGZpbHRlcmluZzoiLCBmb3JtYXQobnJvdyhpbmRlZ29fYWxsKSwgYmlnLm1hcmsgPSAiLCIpLCAiXG4iKQ0KY2F0KCJUcmlwcyBhdCByZXNpZGVudGlhbCBzdGF0aW9uczoiLCBmb3JtYXQobnJvdyhpbmRlZ29fYWxsX2NlbnN1cyksIGJpZy5tYXJrID0gIiwiKSwgIlxuIikNCmNhdCgiVHJpcHMgcmVtb3ZlZDoiLCBmb3JtYXQobnJvdyhpbmRlZ29fYWxsKSAtIG5yb3coaW5kZWdvX2FsbF9jZW5zdXMpLCBiaWcubWFyayA9ICIsIiksICJcbiIpDQpgYGANCg0KIyBHZXQgV2VhdGhlciBEYXRhDQoNCldlYXRoZXIgc2lnbmlmaWNhbnRseSBhZmZlY3RzIGJpa2Ugc2hhcmUgZGVtYW5kISBXZSBuZWVkIHRvIGdldCB3ZWF0aGVyIGRhdGEgY292ZXJpbmcgYWxsIGZvdXIgcXVhcnRlcnMgb2YgMjAyNCBwbHVzIFExIDIwMjUuDQoNCmBgYHtyIGdldF93ZWF0aGVyLGVjaG89VFJVRSwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0UsIGNhY2hlID0gVFJVRX0NCg0KIyBHZXQgd2VhdGhlciBmcm9tIFBoaWxhZGVscGhpYSBJbnRlcm5hdGlvbmFsIEFpcnBvcnQgKEtQSEwpDQojIENvdmVyaW5nIGZ1bGwgeWVhcjogUTEgMjAyNCB0aHJvdWdoIFE0IDIwMjQNCg0KIyBRdWVyeSBlYWNoIHF1YXJ0ZXIgc2VwYXJhdGVseQ0Kd2VhdGhlcl9xMSA8LSByaWVtX21lYXN1cmVzKHN0YXRpb24gPSAiUEhMIiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0ZV9zdGFydCA9ICIyMDI0LTAxLTAxIiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0ZV9lbmQgPSAiMjAyNC0wMy0zMSIpICNRdWFydGVyIDEgJzI0DQoNCndlYXRoZXJfcTIgPC0gcmllbV9tZWFzdXJlcyhzdGF0aW9uID0gIlBITCIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGVfc3RhcnQgPSAiMjAyNC0wNC0wMSIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGVfZW5kID0gIjIwMjQtMDYtMzAiKSAjIFF1YXJ0ZXIgMiAnMjQNCg0Kd2VhdGhlcl9xMyA8LSByaWVtX21lYXN1cmVzKHN0YXRpb24gPSAiUEhMIiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0ZV9zdGFydCA9ICIyMDI0LTA3LTAxIiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0ZV9lbmQgPSAiMjAyNC0wOS0zMCIpICMgUXVhcnRlciAzICcyNA0KDQp3ZWF0aGVyX3E0IDwtIHJpZW1fbWVhc3VyZXMoc3RhdGlvbiA9ICJQSEwiLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRlX3N0YXJ0ID0gIjIwMjQtMTAtMDEiLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRlX2VuZCA9ICIyMDI0LTEyLTMxIikgIyBRdWFydGVyIDQgJzI0IA0KDQojIENvbWJpbmUgdGhlbQ0Kd2VhdGhlcl8yMDI0IDwtIGJpbmRfcm93cyh3ZWF0aGVyX3ExLCB3ZWF0aGVyX3EyLCB3ZWF0aGVyX3EzLCB3ZWF0aGVyX3E0KQ0KDQojIFByb2Nlc3Mgd2VhdGhlciBkYXRhDQp3ZWF0aGVyX3Byb2Nlc3NlZCA8LSB3ZWF0aGVyXzIwMjQgJT4lDQogIG11dGF0ZSgNCiAgICBpbnRlcnZhbDYwID0gZmxvb3JfZGF0ZSh2YWxpZCwgdW5pdCA9ICJob3VyIiksDQogICAgVGVtcGVyYXR1cmUgPSB0bXBmLCAgIyBUZW1wZXJhdHVyZSBpbiBGYWhyZW5oZWl0DQogICAgUHJlY2lwaXRhdGlvbiA9IGlmZWxzZShpcy5uYShwMDFpKSwgMCwgcDAxaSksICAjIEhvdXJseSBwcmVjaXAgaW4gaW5jaGVzDQogICAgV2luZF9TcGVlZCA9IHNrbnQgICMgV2luZCBzcGVlZCBpbiBrbm90cw0KICApICU+JQ0KICBkcGx5cjo6c2VsZWN0KGludGVydmFsNjAsIFRlbXBlcmF0dXJlLCBQcmVjaXBpdGF0aW9uLCBXaW5kX1NwZWVkKSAlPiUNCiAgZGlzdGluY3QoKSU+JQ0KICBhcnJhbmdlKGludGVydmFsNjApDQoNCiMgQ2hlY2sgZm9yIG1pc3NpbmcgaG91cnMgYW5kIGludGVycG9sYXRlIGlmIG5lZWRlZA0Kd2VhdGhlcl9jb21wbGV0ZSA8LSB3ZWF0aGVyX3Byb2Nlc3NlZCAlPiUNCiAgY29tcGxldGUoaW50ZXJ2YWw2MCA9IHNlcShtaW4oaW50ZXJ2YWw2MCksIG1heChpbnRlcnZhbDYwKSwgYnkgPSAiaG91ciIpKSAlPiUNCiAgZmlsbChUZW1wZXJhdHVyZSwgUHJlY2lwaXRhdGlvbiwgV2luZF9TcGVlZCwgLmRpcmVjdGlvbiA9ICJkb3duIikNCg0KIyBTdW1tYXJ5IHN0YXRpc3RpY3MNCmNhdCgiV2VhdGhlciBkYXRhIGNvdmVyYWdlOlxuIikNCmNhdCgiU3RhcnQ6IiwgYXMuY2hhcmFjdGVyKG1pbih3ZWF0aGVyX2NvbXBsZXRlJGludGVydmFsNjApKSwgIlxuIikNCmNhdCgiRW5kOiIsIGFzLmNoYXJhY3RlcihtYXgod2VhdGhlcl9jb21wbGV0ZSRpbnRlcnZhbDYwKSksICJcbiIpDQpjYXQoIlRvdGFsIGhvdXJzOiIsIG5yb3cod2VhdGhlcl9jb21wbGV0ZSksICJcblxuIikNCg0Kc3VtbWFyeSh3ZWF0aGVyX2NvbXBsZXRlICU+JSBkcGx5cjo6c2VsZWN0KFRlbXBlcmF0dXJlLCBQcmVjaXBpdGF0aW9uLCBXaW5kX1NwZWVkKSkNCmBgYA0KDQojIyBWaXN1YWxpemUgV2VhdGhlciBQYXR0ZXJucw0KDQpgYGB7ciB2aXN1YWxpemVfd2VhdGhlciwgZWNobyA9IFRSVUUsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZT1GQUxTRSwgY2FjaGUgPSBUUlVFfQ0KIyBUZW1wZXJhdHVyZSBwYXR0ZXJucyBieSBxdWFydGVyDQp3ZWF0aGVyX2NvbXBsZXRlICU+JQ0KICBtdXRhdGUoDQogICAgcXVhcnRlciA9IHBhc3RlMCgiUSIsIHF1YXJ0ZXIoaW50ZXJ2YWw2MCksICIgMjAyNCIpDQogICkgJT4lICANCiAgZ2dwbG90KGFlcyh4ID0gaW50ZXJ2YWw2MCwgeSA9IFRlbXBlcmF0dXJlLCBjb2xvciA9IHF1YXJ0ZXIpKSArDQogIGdlb21fbGluZShhbHBoYSA9IDAuNikgKw0KICBnZW9tX3Ntb290aChzZSA9IEZBTFNFLCBsaW5ld2lkdGggPSAxLjIpICsNCiAgc2NhbGVfY29sb3JfdmlyaWRpc19kKG9wdGlvbiA9ICJwbGFzbWEiKSArDQogIGxhYnMoDQogICAgdGl0bGUgPSAiUGhpbGFkZWxwaGlhIFRlbXBlcmF0dXJlIFBhdHRlcm5zIEFjcm9zcyBBbGwgUXVhcnRlcnMiLA0KICAgIHN1YnRpdGxlID0gIjIwMjQgU2Vhc29uYWwgVGVtcGVyYXR1cmUgVmFyaWF0aW9uIiwNCiAgICB4ID0gIkRhdGUiLA0KICAgIHkgPSAiVGVtcGVyYXR1cmUgKMKwRikiLA0KICAgIGNvbG9yID0gIlF1YXJ0ZXIiDQogICkgKw0KICBwbG90VGhlbWUgKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIikNCmBgYA0KDQotLS0NCg0KIyBDcmVhdGUgU3BhY2UtVGltZSBQYW5lbA0KDQojIyBBZ2dyZWdhdGUgVHJpcHMgdG8gU3RhdGlvbi1Ib3VyIExldmVsDQoNCmBgYHtyIGFnZ3JlZ2F0ZV90cmlwcywgZWNobyA9IFRSVUUsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZT1GQUxTRSwgY2FjaGUgPSBUUlVFfQ0KIyBDb3VudCB0cmlwcyBieSBzdGF0aW9uLWhvdXINCnRyaXBzX3BhbmVsIDwtIGluZGVnb19hbGxfY2Vuc3VzICU+JQ0KICBncm91cF9ieShpbnRlcnZhbDYwLCBzdGFydF9zdGF0aW9uLCBzdGFydF9sYXQsIHN0YXJ0X2xvbiwNCiAgICAgICAgICAgTWVkX0luYywgUGVyY2VudF9UYWtpbmdfVHJhbnNpdCwgUGVyY2VudF9XaGl0ZSwgVG90YWxfUG9wKSAlPiUNCiAgc3VtbWFyaXplKFRyaXBfQ291bnQgPSBuKCkpICU+JQ0KICB1bmdyb3VwKCkNCg0KDQojIFN1bW1hcnkgc3RhdGlzdGljcw0KY2F0KCJUcmlwcyBieSBzdGF0aW9uLWhvdXI6XG4iKQ0KY2F0KCJIb3cgbWFueSBzdGF0aW9uLWhvdXIgb2JzZXJ2YXRpb25zPzoiLCBucm93KHRyaXBzX3BhbmVsKSwgIlxuIikNCmNhdCgiSG93IG1hbnkgdW5pcXVlIHN0YXRpb25zPzoiLCBsZW5ndGgodW5pcXVlKHRyaXBzX3BhbmVsJHN0YXJ0X3N0YXRpb24pKSAsICJcbiIpDQpjYXQoIkhvdyBtYW55IHVuaXF1ZSBob3Vycz86IiwgbGVuZ3RoKHVuaXF1ZSh0cmlwc19wYW5lbCRpbnRlcnZhbDYwKSksICJcblxuIikNCmBgYA0KDQojIyBDcmVhdGUgQ29tcGxldGUgUGFuZWwgU3RydWN0dXJlDQoNCk5vdCBldmVyeSBzdGF0aW9uIGhhcyB0cmlwcyBldmVyeSBob3VyLiBXZSBuZWVkIGEgKipjb21wbGV0ZSBwYW5lbCoqIHdoZXJlIGV2ZXJ5IHN0YXRpb24taG91ciBjb21iaW5hdGlvbiBleGlzdHMgKGV2ZW4gaWYgVHJpcF9Db3VudCA9IDApLg0KDQpgYGB7ciBjb21wbGV0ZV9wYW5lbCwgZWNobyA9IFRSVUUsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZT1GQUxTRSwgY2FjaGUgPSBUUlVFfQ0KIyBDYWxjdWxhdGUgZXhwZWN0ZWQgcGFuZWwgc2l6ZQ0Kbl9zdGF0aW9ucyA8LSBsZW5ndGgodW5pcXVlKHRyaXBzX3BhbmVsJHN0YXJ0X3N0YXRpb24pKQ0Kbl9ob3VycyA8LSBsZW5ndGgodW5pcXVlKHRyaXBzX3BhbmVsJGludGVydmFsNjApKQ0KZXhwZWN0ZWRfcm93cyA8LSBuX3N0YXRpb25zICogbl9ob3Vycw0KDQpjYXQoIkV4cGVjdGVkIHBhbmVsIHJvd3M6IiwgZm9ybWF0KGV4cGVjdGVkX3Jvd3MsIGJpZy5tYXJrID0gIiwiKSwgIlxuIikNCmNhdCgiQ3VycmVudCByb3dzOiIsIGZvcm1hdChucm93KHRyaXBzX3BhbmVsKSwgYmlnLm1hcmsgPSAiLCIpLCAiXG4iKQ0KY2F0KCJNaXNzaW5nIHJvd3M6IiwgZm9ybWF0KGV4cGVjdGVkX3Jvd3MgLSBucm93KHRyaXBzX3BhbmVsKSwgYmlnLm1hcmsgPSAiLCIpLCAiXG4iKQ0KDQojIENyZWF0ZSBjb21wbGV0ZSBwYW5lbA0KIyBDcmVhdGUgY29tcGxldGUgcGFuZWwNCnN0dWR5X3BhbmVsIDwtIGV4cGFuZC5ncmlkKA0KICBpbnRlcnZhbDYwID0gdW5pcXVlKHRyaXBzX3BhbmVsJGludGVydmFsNjApLA0KICBzdGFydF9zdGF0aW9uID0gdW5pcXVlKHRyaXBzX3BhbmVsJHN0YXJ0X3N0YXRpb24pDQopICU+JQ0KICBsZWZ0X2pvaW4odHJpcHNfcGFuZWwsIGJ5ID0gYygiaW50ZXJ2YWw2MCIsICJzdGFydF9zdGF0aW9uIikpICU+JQ0KICBtdXRhdGUoDQogICAgVHJpcF9Db3VudCA9IHJlcGxhY2VfbmEoVHJpcF9Db3VudCwgMCksDQoNCiAgICAjIGV4dHJhY3QgbW9udGggZnJvbSBpbnRlcnZhbDYwIChhc3N1bWluZyBQT1NJWGN0KQ0KICAgIG1vbnRoID0gbHVicmlkYXRlOjptb250aChpbnRlcnZhbDYwLCBsYWJlbCA9IFRSVUUsIGFiYnIgPSBUUlVFKSwNCg0KICAgICMgZm9yY2UgQUxMIDEyIG1vbnRocyBzbyBubyBuZXcgbGV2ZWxzIG9jY3VyIGluIHRlc3RpbmcNCiAgICBtb250aCA9IGZhY3Rvcihtb250aCwgbGV2ZWxzID0gbW9udGguYWJiKQ0KICApDQoNCiMgRmlsbCBpbiBzdGF0aW9uIGF0dHJpYnV0ZXMgKHNhbWUgZm9yIGFsbCBob3VycykNCnN0YXRpb25fYXR0cmlidXRlcyA8LSB0cmlwc19wYW5lbCAlPiUNCiAgZ3JvdXBfYnkoc3RhcnRfc3RhdGlvbikgJT4lDQogIHN1bW1hcml6ZSgNCiAgICBzdGFydF9sYXQgPSBmaXJzdChzdGFydF9sYXQpLA0KICAgIHN0YXJ0X2xvbiA9IGZpcnN0KHN0YXJ0X2xvbiksDQogICAgTWVkX0luYyA9IGZpcnN0KE1lZF9JbmMpLA0KICAgIFBlcmNlbnRfVGFraW5nX1RyYW5zaXQgPSBmaXJzdChQZXJjZW50X1Rha2luZ19UcmFuc2l0KSwNCiAgICBQZXJjZW50X1doaXRlID0gZmlyc3QoUGVyY2VudF9XaGl0ZSksDQogICAgVG90YWxfUG9wID0gZmlyc3QoVG90YWxfUG9wKQ0KICApDQoNCnN0dWR5X3BhbmVsIDwtIHN0dWR5X3BhbmVsICU+JQ0KICBsZWZ0X2pvaW4oc3RhdGlvbl9hdHRyaWJ1dGVzLCBieSA9ICJzdGFydF9zdGF0aW9uIikNCg0KY2F0KCJDb21wbGV0ZSBwYW5lbCByb3dzOiIsIGZvcm1hdChucm93KHN0dWR5X3BhbmVsKSwgYmlnLm1hcmsgPSAiLCIpLCAiXG4iKQ0KDQpgYGANCg0KIyMgQWRkIFRpbWUgRmVhdHVyZXMNCg0KYGBge3IgYWRkX3RpbWVfZmVhdHVyZXMsIGVjaG8gPSBUUlVFLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2U9RkFMU0UsIGNhY2hlID0gVFJVRX0NCnN0dWR5X3BhbmVsIDwtIHN0dWR5X3BhbmVsICU+JQ0KICBtdXRhdGUoDQogICAgd2VlayA9IHdlZWsoaW50ZXJ2YWw2MCksDQogICAgbW9udGggPSBtb250aChpbnRlcnZhbDYwLCBsYWJlbCA9IFRSVUUpLA0KICAgIGRvdHcgPSB3ZGF5KGludGVydmFsNjAsIGxhYmVsID0gVFJVRSksDQogICAgaG91ciA9IGhvdXIoaW50ZXJ2YWw2MCksDQogICAgZGF0ZSA9IGFzLkRhdGUoaW50ZXJ2YWw2MCksDQogICAgd2Vla2VuZCA9IGlmZWxzZShkb3R3ICVpbiUgYygiU2F0IiwgIlN1biIpLCAxLCAwKSwNCiAgICBydXNoX2hvdXIgPSBpZmVsc2UoaG91ciAlaW4lIGMoNywgOCwgOSwgMTYsIDE3LCAxOCksIDEsIDApDQogICkNCmBgYA0KDQoNCiMjIEpvaW4gV2VhdGhlciBEYXRhDQoNCmBgYHtyIGpvaW5fd2VhdGhlciwgZWNobyA9IFRSVUUsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZT1GQUxTRSwgY2FjaGUgPSBUUlVFfQ0Kc3R1ZHlfcGFuZWwgPC0gc3R1ZHlfcGFuZWwgJT4lDQogIGxlZnRfam9pbih3ZWF0aGVyX2NvbXBsZXRlLCBieSA9ICJpbnRlcnZhbDYwIikNCg0KIyBDaGVjayBmb3IgbWlzc2luZyB2YWx1ZXMNCnN1bW1hcnkoc3R1ZHlfcGFuZWwgJT4lIHNlbGVjdChUcmlwX0NvdW50LCBUZW1wZXJhdHVyZSwgUHJlY2lwaXRhdGlvbikpDQpgYGANCg0KLS0tDQoNCiMgQ3JlYXRlIFRlbXBvcmFsIExhZyBWYXJpYWJsZXMNCg0KVGhlIGtleSBpbm5vdmF0aW9uIGZvciBzcGFjZS10aW1lIHByZWRpY3Rpb246ICoqcGFzdCBkZW1hbmQgcHJlZGljdHMgZnV0dXJlIGRlbWFuZCoqLg0KDQojIyBXaHkgTGFncz8NCg0KSWYgdGhlcmUgd2VyZSAxNSBiaWtlIHRyaXBzIGZyb20gU3RhdGlvbiBBIGF0IDg6MDAgQU0sIHRoZXJlIHdpbGwgcHJvYmFibHkgYmUgfjE1IHRyaXBzIGF0IDk6MDAgQU0uIFdlIGNhbiB1c2UgdGhpcyB0ZW1wb3JhbCBwZXJzaXN0ZW5jZSB0byBpbXByb3ZlIHByZWRpY3Rpb25zLg0KDQpgYGB7ciBjcmVhdGVfbGFncywgZWNobyA9IFRSVUUsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZT1GQUxTRSwgY2FjaGUgPSBUUlVFfQ0KIyBTb3J0IGJ5IHN0YXRpb24gYW5kIHRpbWUNCnN0dWR5X3BhbmVsIDwtIHN0dWR5X3BhbmVsICU+JQ0KICBhcnJhbmdlKHN0YXJ0X3N0YXRpb24sIGludGVydmFsNjApDQoNCiMgQ3JlYXRlIGxhZyB2YXJpYWJsZXMgV0lUSElOIGVhY2ggc3RhdGlvbg0Kc3R1ZHlfcGFuZWwgPC0gc3R1ZHlfcGFuZWwgJT4lDQogIGdyb3VwX2J5KHN0YXJ0X3N0YXRpb24pICU+JQ0KICBtdXRhdGUoDQogICAgbGFnMUhvdXIgPSBsYWcoVHJpcF9Db3VudCwgMSksDQogICAgbGFnMkhvdXJzID0gbGFnKFRyaXBfQ291bnQsIDIpLA0KICAgIGxhZzNIb3VycyA9IGxhZyhUcmlwX0NvdW50LCAzKSwNCiAgICBsYWcxMkhvdXJzID0gbGFnKFRyaXBfQ291bnQsIDEyKSwNCiAgICBsYWcxZGF5ID0gbGFnKFRyaXBfQ291bnQsIDI0KQ0KICApICU+JQ0KICB1bmdyb3VwKCkNCg0KIyBSZW1vdmUgcm93cyB3aXRoIE5BIGxhZ3MgKGZpcnN0IDI0IGhvdXJzIGZvciBlYWNoIHN0YXRpb24pDQpzdHVkeV9wYW5lbF9jb21wbGV0ZSA8LSBzdHVkeV9wYW5lbCAlPiUNCiAgZmlsdGVyKCFpcy5uYShsYWcxZGF5KSkNCg0KY2F0KCJSb3dzIGFmdGVyIHJlbW92aW5nIE5BIGxhZ3M6IiwgZm9ybWF0KG5yb3coc3R1ZHlfcGFuZWxfY29tcGxldGUpLCBiaWcubWFyayA9ICIsIiksICJcbiIpDQpgYGANCg0KIyMgVmlzdWFsaXplIExhZyBDb3JyZWxhdGlvbnMNCg0KYGBge3IgbGFnX2NvcnJlbGF0aW9uc30NCiMgRmluZCBzdGF0aW9ucyB3aXRoIGhpZ2hlc3QgdHJpcCBjb3VudHMNCnRvcF9zdGF0aW9ucyA8LSBzdHVkeV9wYW5lbF9jb21wbGV0ZSAlPiUNCiAgZ3JvdXBfYnkoc3RhcnRfc3RhdGlvbikgJT4lDQogIHN1bW1hcml6ZSh0b3RhbF90cmlwcyA9IHN1bShUcmlwX0NvdW50KSkgJT4lDQogIGFycmFuZ2UoZGVzYyh0b3RhbF90cmlwcykpICU+JQ0KICBoZWFkKDEwKQ0KDQpwcmludCh0b3Bfc3RhdGlvbnMpDQoNCiMgUGljayBhIGhpZ2gtYWN0aXZpdHkgc3RhdGlvbiAodXNlIG9uZSBmcm9tIHRoZSBsaXN0IGFib3ZlKQ0KZXhhbXBsZV9zdGF0aW9uIDwtIHN0dWR5X3BhbmVsX2NvbXBsZXRlICU+JQ0KICBmaWx0ZXIoc3RhcnRfc3RhdGlvbiA9PSB0b3Bfc3RhdGlvbnMkc3RhcnRfc3RhdGlvblsxXSkgJT4lICAjIEhpZ2hlc3QgYWN0aXZpdHkgc3RhdGlvbg0KICBoZWFkKDE2OCkgICMgT25lIHdlZWsNCg0KIyBDaGVjayBpZiB5b3UgaGF2ZSBkYXRhDQpjYXQoIlRyaXAgY291bnQgcmFuZ2U6IiwgcmFuZ2UoZXhhbXBsZV9zdGF0aW9uJFRyaXBfQ291bnQpLCAiXG4iKQ0KY2F0KCJNZWFuIHRyaXBzOiIsIG1lYW4oZXhhbXBsZV9zdGF0aW9uJFRyaXBfQ291bnQpLCAiXG4iKQ0KDQojIE5vdyBwbG90DQpnZ3Bsb3QoZXhhbXBsZV9zdGF0aW9uLCBhZXMoeCA9IGludGVydmFsNjApKSArDQogIGdlb21fbGluZShhZXMoeSA9IFRyaXBfQ291bnQsIGNvbG9yID0gIkN1cnJlbnQiKSwgbGluZXdpZHRoID0gMSkgKw0KICBnZW9tX2xpbmUoYWVzKHkgPSBsYWcxSG91ciwgY29sb3IgPSAiMSBIb3VyIEFnbyIpLCBsaW5ld2lkdGggPSAxLCBhbHBoYSA9IDAuNykgKw0KICBnZW9tX2xpbmUoYWVzKHkgPSBsYWcxZGF5LCBjb2xvciA9ICIyNCBIb3VycyBBZ28iKSwgbGluZXdpZHRoID0gMSwgYWxwaGEgPSAwLjcpICsNCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGMoDQogICAgIkN1cnJlbnQiID0gIiMwODUxOWMiLA0KICAgICIxIEhvdXIgQWdvIiA9ICIjMzE4MmJkIiwNCiAgICAiMjQgSG91cnMgQWdvIiA9ICIjNmJhZWQ2Ig0KICApKSArDQogIGxhYnMoDQogICAgdGl0bGUgPSAiVGVtcG9yYWwgTGFnIFBhdHRlcm5zIGF0IE9uZSBTdGF0aW9uIiwNCiAgICBzdWJ0aXRsZSA9ICJQYXN0IGRlbWFuZCBwcmVkaWN0cyBmdXR1cmUgZGVtYW5kIiwNCiAgICB4ID0gIkRhdGUtVGltZSIsDQogICAgeSA9ICJUcmlwIENvdW50IiwNCiAgICBjb2xvciA9ICJUaW1lIFBlcmlvZCINCiAgKSArDQogIHBsb3RUaGVtZQ0KYGBgDQoNCi0tLQ0KDQojIFRlbXBvcmFsIFRyYWluL1Rlc3QgU3BsaXQNCg0KKipDUklUSUNBTDoqKiBXZSBtdXN0IHRyYWluIG9uIFBBU1QgZGF0YSBhbmQgdGVzdCBvbiBGVVRVUkUgZGF0YSENCg0KIyMgV2h5IFRlbXBvcmFsIFZhbGlkYXRpb24gTWF0dGVycw0KDQpJbiByZWFsIG9wZXJhdGlvbnMsIGF0IDY6MDAgQU0gb24gTWFyY2ggMTUsIHdlIG5lZWQgdG8gcHJlZGljdCBkZW1hbmQgZm9yIE1hcmNoIDE1LTMxLiBXZSBoYXZlIGRhdGEgZnJvbSBKYW4gMSAtIE1hcmNoIDE0LCBidXQgTk9UIGZyb20gTWFyY2ggMTUtMzEgKGl0IGhhc24ndCBoYXBwZW5lZCB5ZXQhKS4NCg0KKipXcm9uZyBhcHByb2FjaDoqKiBUcmFpbiBvbiB3ZWVrcyAxMC0xMywgdGVzdCBvbiB3ZWVrcyAxLTkgKHByZWRpY3RpbmcgcGFzdCBmcm9tIGZ1dHVyZSEpDQoNCioqQ29ycmVjdCBhcHByb2FjaDoqKiBUcmFpbiBvbiB3ZWVrcyAxLTksIHRlc3Qgb24gd2Vla3MgMTAtMTMgKHByZWRpY3RpbmcgZnV0dXJlIGZyb20gcGFzdCkNCg0KYGBge3IgdGVtcG9yYWxfc3BsaXQsIGVjaG8gPSBUUlVFLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2U9RkFMU0UsIGNhY2hlID0gVFJVRX0NCiMgU3BsaXQgYnkgd2Vlaw0KIyBRMSBoYXMgd2Vla3MgMS0xMyAoSmFuLU1hcikNCiMgVHJhaW4gb24gd2Vla3MgMS05IChKYW4gMSAtIGVhcmx5IE1hcmNoKQ0KIyBUZXN0IG9uIHdlZWtzIDEwLTEzIChyZXN0IG9mIE1hcmNoKQ0KDQojIFdoaWNoIHN0YXRpb25zIGhhdmUgdHJpcHMgaW4gQk9USCBlYXJseSBhbmQgbGF0ZSBwZXJpb2RzPw0KZWFybHlfc3RhdGlvbnMgPC0gc3R1ZHlfcGFuZWxfY29tcGxldGUgJT4lDQogIGZpbHRlcih3ZWVrIDwgMTApICU+JQ0KICBmaWx0ZXIoVHJpcF9Db3VudCA+IDApICU+JQ0KICBkaXN0aW5jdChzdGFydF9zdGF0aW9uKSAlPiUNCiAgcHVsbChzdGFydF9zdGF0aW9uKQ0KDQpsYXRlX3N0YXRpb25zIDwtIHN0dWR5X3BhbmVsX2NvbXBsZXRlICU+JQ0KICBmaWx0ZXIod2VlayA+PSAxMCkgJT4lDQogIGZpbHRlcihUcmlwX0NvdW50ID4gMCkgJT4lDQogIGRpc3RpbmN0KHN0YXJ0X3N0YXRpb24pICU+JQ0KICBwdWxsKHN0YXJ0X3N0YXRpb24pDQoNCiMgS2VlcCBvbmx5IHN0YXRpb25zIHRoYXQgYXBwZWFyIGluIEJPVEggcGVyaW9kcw0KY29tbW9uX3N0YXRpb25zIDwtIGludGVyc2VjdChlYXJseV9zdGF0aW9ucywgbGF0ZV9zdGF0aW9ucykNCg0KDQojIEZpbHRlciBwYW5lbCB0byBvbmx5IGNvbW1vbiBzdGF0aW9ucw0Kc3R1ZHlfcGFuZWxfY29tcGxldGUgPC0gc3R1ZHlfcGFuZWxfY29tcGxldGUgJT4lDQogIGZpbHRlcihzdGFydF9zdGF0aW9uICVpbiUgY29tbW9uX3N0YXRpb25zKQ0KDQojIE5PVyBjcmVhdGUgdHJhaW4vdGVzdCBzcGxpdA0KdHJhaW4gPC0gc3R1ZHlfcGFuZWxfY29tcGxldGUgJT4lDQogIGZpbHRlcih3ZWVrIDwgMTApDQoNCnRlc3QgPC0gc3R1ZHlfcGFuZWxfY29tcGxldGUgJT4lDQogIGZpbHRlcih3ZWVrID49IDEwKQ0KDQpjYXQoIlRyYWluaW5nIG9ic2VydmF0aW9uczoiLCBmb3JtYXQobnJvdyh0cmFpbiksIGJpZy5tYXJrID0gIiwiKSwgIlxuIikNCmNhdCgiVGVzdGluZyBvYnNlcnZhdGlvbnM6IiwgZm9ybWF0KG5yb3codGVzdCksIGJpZy5tYXJrID0gIiwiKSwgIlxuIikNCmNhdCgiVHJhaW5pbmcgZGF0ZSByYW5nZToiLCBtaW4odHJhaW4kZGF0ZSksICJ0byIsIG1heCh0cmFpbiRkYXRlKSwgIlxuIikNCmNhdCgiVGVzdGluZyBkYXRlIHJhbmdlOiIsIG1pbih0ZXN0JGRhdGUpLCAidG8iLCBtYXgodGVzdCRkYXRlKSwgIlxuIikNCg0KDQpgYGANCg0KLS0tDQoNCiMgQnVpbGQgUHJlZGljdGl2ZSBNb2RlbHMNCg0KV2UnbGwgYnVpbGQgNSBtb2RlbHMgd2l0aCBpbmNyZWFzaW5nIGNvbXBsZXhpdHkgdG8gc2VlIHdoYXQgaW1wcm92ZXMgcHJlZGljdGlvbnMuDQoNCiMjIE1vZGVsIDE6IEJhc2VsaW5lIChUaW1lICsgV2VhdGhlcikNCg0KYGBge3IgbW9kZWwxLCBlY2hvID0gVFJVRSwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlPUZBTFNFLCBjYWNoZSA9IFRSVUV9DQoNCiMgQ3JlYXRlIGRheSBvZiB3ZWVrIGZhY3RvciB3aXRoIHRyZWF0bWVudCAoZHVtbXkpIGNvZGluZw0KdHJhaW4gPC0gdHJhaW4gJT4lDQogIG11dGF0ZShkb3R3X3NpbXBsZSA9IGZhY3Rvcihkb3R3LCBsZXZlbHMgPSBjKCJNb24iLCAiVHVlIiwgIldlZCIsICJUaHUiLCAiRnJpIiwgIlNhdCIsICJTdW4iKSkpDQoNCiMgU2V0IGNvbnRyYXN0cyB0byB0cmVhdG1lbnQgY29kaW5nIChkdW1teSB2YXJpYWJsZXMpDQpjb250cmFzdHModHJhaW4kZG90d19zaW1wbGUpIDwtIGNvbnRyLnRyZWF0bWVudCg3KQ0KDQojIE5vdyBydW4gdGhlIG1vZGVsDQptb2RlbDEgPC0gbG0oDQogIFRyaXBfQ291bnQgfiBhcy5mYWN0b3IoaG91cikgKyBkb3R3X3NpbXBsZSArIFRlbXBlcmF0dXJlICsgUHJlY2lwaXRhdGlvbiwNCiAgZGF0YSA9IHRyYWluDQopDQoNCnN1bW1hcnkobW9kZWwxKQ0KYGBgDQoNClRoZSBtb2RlbCB1c2VzIE1vbmRheSBhcyB0aGUgYmFzZWxpbmUuIEVhY2ggY29lZmZpY2llbnQgcmVwcmVzZW50cyB0aGUgZGlmZmVyZW5jZSANCmluIGV4cGVjdGVkIHRyaXBzIHBlciBzdGF0aW9uLWhvdXIgY29tcGFyZWQgdG8gTW9uZGF5IC0gZG93X3NpbXBsZTIgPSBUdWVzZGF5Li4NCg0KKipXZWVrZGF5IFBhdHRlcm4gKFR1ZS1GcmkpOioqDQoNCi0gQWxsIHdlZWtkYXlzIGhhdmUgcG9zaXRpdmUgY29lZmZpY2llbnRzICgwLjAwNyB0byAwLjYwOCkNCi0gV2VkbmVzZGF5IGhhcyB0aGUgaGlnaGVzdCB3ZWVrZGF5IGVmZmVjdCAoKzAuMDI5KQ0KLSBXZWVrZGF5cyBsaWtlbHkgYmVuZWZpdCBmcm9tIGNvbmNlbnRyYXRlZCBjb21tdXRpbmcgcGF0dGVybnMNCg0KKipXZWVrZW5kIFBhdHRlcm4gKFNhdC1TdW4pOioqDQoNCi0gQm90aCB3ZWVrZW5kIGRheXMgaGF2ZSBuZWdhdGl2ZSBjb2VmZmljaWVudHMgKC0wLjExOCBhbmQgLTAuMDk0KQ0KLSBUaGlzIG1lYW5zIEZFV0VSIHRyaXBzIHBlciBzdGF0aW9uLWhvdXIgdGhhbiBNb25kYXkNCg0KKipIb3VybHkgSW50ZXJwcmV0YXRpb24qKg0KDQpIb3VyICAgQ29lZmZpY2llbnQgICBJbnRlcnByZXRhdGlvbg0KMCAgICAgIChiYXNlbGluZSkgICAgMC4wMDAgdHJpcHMvaG91ciAobWlkbmlnaHQpDQoxICAgICAgLTAuMDI4ICAgICAgIHNsaWdodGx5IGZld2VyIHRoYW4gbWlkbmlnaHQNCi4uLg0KNiAgICAgICswLjE1OCAgICAgICBtb3JuaW5nIGFjdGl2aXR5IHN0YXJ0aW5nDQo3ICAgICAgKzAuMzA0ICAgICAgIG1vcm5pbmcgcnVzaCBidWlsZGluZw0KOCAgICAgICswLjQ4NCAgICAgICBQRUFLIG1vcm5pbmcgcnVzaA0KOSAgICAgICswLjM3NiAgICAgICBwb3N0LXJ1c2gNCi4uLg0KMTcgICAgICswLjYwOCAgICAgICBQRUFLIGV2ZW5pbmcgcnVzaCAoNSBQTSEpDQoxOCAgICAgKzAuNDQ0ICAgICAgIGV2ZW5pbmcgZGVjbGluaW5nDQouLi4NCjIzICAgICArMC4wNDkgICAgICAgbGF0ZSBuaWdodCBtaW5pbWFsDQoNCg0KIyMgTW9kZWwgMjogQWRkIFRlbXBvcmFsIExhZ3MNCg0KYGBge3IgbW9kZWwyLCBlY2hvID0gVFJVRSwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlPUZBTFNFLCBjYWNoZSA9IFRSVUV9DQptb2RlbDIgPC0gbG0oDQogIFRyaXBfQ291bnQgfiBhcy5mYWN0b3IoaG91cikgKyBkb3R3X3NpbXBsZSArIFRlbXBlcmF0dXJlICsgUHJlY2lwaXRhdGlvbiArDQogICAgbGFnMUhvdXIgKyBsYWczSG91cnMgKyBsYWcxZGF5LA0KICBkYXRhID0gdHJhaW4NCikNCg0Kc3VtbWFyeShtb2RlbDIpDQpgYGANCg0KYGBge3IgY29tcGFyZS1tb2RlbHMsIGVjaG8gPSBUUlVFLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2U9RkFMU0UsIGNhY2hlID0gVFJVRX0NCiMgRXh0cmFjdCBSwrIgdmFsdWVzDQpyMl9tb2RlbDEgPC0gc3VtbWFyeShtb2RlbDEpJHIuc3F1YXJlZA0KcjJfbW9kZWwyIDwtIHN1bW1hcnkobW9kZWwyKSRyLnNxdWFyZWQNCg0KYWRqX3IyX21vZGVsMSA8LSBzdW1tYXJ5KG1vZGVsMSkkYWRqLnIuc3F1YXJlZA0KYWRqX3IyX21vZGVsMiA8LSBzdW1tYXJ5KG1vZGVsMikkYWRqLnIuc3F1YXJlZA0KDQojIENhbGN1bGF0ZSBpbXByb3ZlbWVudHMNCnIyX2ltcHJvdmVtZW50IDwtIHIyX21vZGVsMiAtIHIyX21vZGVsMQ0KYWRqX3IyX2ltcHJvdmVtZW50IDwtIGFkal9yMl9tb2RlbDIgLSBhZGpfcjJfbW9kZWwxDQoNCg0KIyBDcmVhdGUgY29tcGFyaXNvbiBkYXRhZnJhbWUNCm1vZGVsX2NvbXBhcmlzb24gPC0gZGF0YS5mcmFtZSgNCiAgTW9kZWwgPSBjKCJNb2RlbCAxIChObyBMYWdzKSIsICJNb2RlbCAyIChXaXRoIExhZ3MpIiwgIkltcHJvdmVtZW50IiksDQogIFJfc3F1YXJlZCA9IGMocjJfbW9kZWwxLCByMl9tb2RlbDIsIHIyX2ltcHJvdmVtZW50KSwNCiAgQWRqX1Jfc3F1YXJlZCA9IGMoYWRqX3IyX21vZGVsMSwgYWRqX3IyX21vZGVsMiwgYWRqX3IyX2ltcHJvdmVtZW50KSwNCiAgUGVyY2VudF9WYXJpYW5jZSA9IGMocjJfbW9kZWwxICogMTAwLCByMl9tb2RlbDIgKiAxMDAsIHIyX2ltcHJvdmVtZW50ICogMTAwKQ0KKQ0KDQpwcmludChtb2RlbF9jb21wYXJpc29uLCByb3cubmFtZXMgPSBGQUxTRSkNCmBgYA0KDQpBZGRpbmcgdGVtcG9yYWwgbGFnIHZhcmlhYmxlcyBpbmNyZWFzZWQgdGhlIG1vZGVsJ3MgZXhwbGFuYXRvcnkgcG93ZXIgZnJvbSBhcHByb3guIDglIHRvIGFwcHJveC4gMjklIC0gYSAyMSBwZXJjZW50YWdlIHBvaW50IGltcHJvdmVtZW50LiBJbiBtb2RlbCAxLCBvbmx5IGFib3V0IDglIG9mIHJpZGVyc2hpcCBpcyBleHBsYWluZWQgYnkgaG91ci1vZi1kYXksIGRheS1vZi13ZWVrLCB0ZW1wZXJhdHVyZSwgYW5kIHByZWNpcGl0YXRpb24uIFdoaWxlIG5vdCBzcGVjdGFjdWxhciBieSBhbnkgbWVhbnMsIGFkZGluZyBsYWctaG91ci0xLCBsYWctaG91ci0zLCBhbmQgbGFnLWhvdXItZGF5IChhY2NvdW50aW5nIGZvciBkYXRhIGNvbGxlY3RlZCBvbmUgaG91ciBhZ28sIHRocmVlIGhvdXJzIGFnbywgYW5kIG9uZSBkYXkgYWdvKSwgdGhlIG1vZGVsIG5vdyBwcmVkaWN0cyBhYm91dCAyOSUgb2YgcmlkZXJzaGlwIHRlbmRlbmNpZXMuDQoNCg0KIyMgTW9kZWwgMzogQWRkIERlbW9ncmFwaGljcw0KDQpgYGB7ciBtb2RlbDMsIGVjaG8gPSBUUlVFLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2U9RkFMU0UsIGNhY2hlID0gVFJVRX0NCm1vZGVsMyA8LSBsbSgNCiAgVHJpcF9Db3VudCB+IGFzLmZhY3Rvcihob3VyKSArIGRvdHdfc2ltcGxlICsgVGVtcGVyYXR1cmUgKyBQcmVjaXBpdGF0aW9uICsNCiAgICBsYWcxSG91ciArIGxhZzNIb3VycyArIGxhZzFkYXkgKw0KICAgIE1lZF9JbmMueCArIFBlcmNlbnRfVGFraW5nX1RyYW5zaXQueSArIFBlcmNlbnRfV2hpdGUueSwNCiAgZGF0YSA9IHRyYWluDQopDQoNCnN1bW1hcnkobW9kZWwzKQ0KYGBgDQoNCiMjIE1vZGVsIDQ6IEFkZCBTdGF0aW9uIEZpeGVkIEVmZmVjdHMNCg0KYGBge3IgbW9kZWw0LCBlY2hvID0gVFJVRSwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlPUZBTFNFLCBjYWNoZSA9IFRSVUV9DQptb2RlbDQgPC0gbG0oDQogIFRyaXBfQ291bnQgfiBhcy5mYWN0b3IoaG91cikgKyBkb3R3X3NpbXBsZSArIFRlbXBlcmF0dXJlICsgUHJlY2lwaXRhdGlvbiArDQogICAgbGFnMUhvdXIgKyBsYWczSG91cnMgKyBsYWcxZGF5ICsNCiAgICBNZWRfSW5jLnggKyBQZXJjZW50X1Rha2luZ19UcmFuc2l0LnkgKyBQZXJjZW50X1doaXRlLnkgKw0KICAgIGFzLmZhY3RvcihzdGFydF9zdGF0aW9uKSwNCiAgZGF0YSA9IHRyYWluDQopDQoNCiMgU3VtbWFyeSB0b28gbG9uZyB3aXRoIGFsbCBzdGF0aW9uIGR1bW1pZXMsIGp1c3Qgc2hvdyBrZXkgbWV0cmljcw0KY2F0KCJNb2RlbCA0IFItc3F1YXJlZDoiLCBzdW1tYXJ5KG1vZGVsNCkkci5zcXVhcmVkLCAiXG4iKQ0KY2F0KCJNb2RlbCA0IEFkaiBSLXNxdWFyZWQ6Iiwgc3VtbWFyeShtb2RlbDQpJGFkai5yLnNxdWFyZWQsICJcbiIpDQpgYGANCiANClN0YXRpb24gZml4ZWQgZWZmZWN0cyBjYXB0dXJlIGJhc2VsaW5lIGRpZmZlcmVuY2VzIGluIGRlbWFuZCBhY3Jvc3MgSW5kZWdvIHN0YXRpb25zIHRoYXQgYXJlbid0IGV4cGxhaW5lZCBieSBDZW5zdXMgdHJhY3QgZGVtb2dyYXBoaWNzLCB3ZWF0aGVyLCBvciB0ZW1wb3JhbCBwYXR0ZXJucy4NCg0KIyMgTW9kZWwgNTogQWRkIFJ1c2ggSG91ciBJbnRlcmFjdGlvbg0KDQpgYGB7ciBtb2RlbDUsIGVjaG8gPSBUUlVFLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2U9RkFMU0UsIGNhY2hlID0gVFJVRX0NCm1vZGVsNSA8LSBsbSgNCiAgVHJpcF9Db3VudCB+IGFzLmZhY3Rvcihob3VyKSArIGRvdHdfc2ltcGxlICsgVGVtcGVyYXR1cmUgKyBQcmVjaXBpdGF0aW9uICsNCiAgICBsYWcxSG91ciArIGxhZzNIb3VycyArIGxhZzFkYXkgKyBydXNoX2hvdXIgKyANCiAgICBNZWRfSW5jLnggKyBQZXJjZW50X1Rha2luZ19UcmFuc2l0LnkgKyBQZXJjZW50X1doaXRlLnkgKw0KICAgIGFzLmZhY3RvcihzdGFydF9zdGF0aW9uKSArDQogICAgcnVzaF9ob3VyICogd2Vla2VuZCwgICMgUnVzaCBob3VyIGVmZmVjdHMgZGlmZmVyZW50IG9uIHdlZWtlbmRzDQogIGRhdGEgPSB0cmFpbg0KKQ0KDQpjYXQoIk1vZGVsIDUgUi1zcXVhcmVkOiIsIHN1bW1hcnkobW9kZWw1KSRyLnNxdWFyZWQsICJcbiIpDQpjYXQoIk1vZGVsIDUgQWRqIFItc3F1YXJlZDoiLCBzdW1tYXJ5KG1vZGVsNSkkYWRqLnIuc3F1YXJlZCwgIlxuIikNCmBgYA0KDQotLS0NCg0KIyBNb2RlbCBFdmFsdWF0aW9uDQoNCiMjIENhbGN1bGF0ZSBQcmVkaWN0aW9ucyBhbmQgTUFFDQoNCmBgYHtyIGNhbGN1bGF0ZV9tYWUsIGVjaG8gPSBUUlVFLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2U9RkFMU0UsIGNhY2hlID0gVFJVRX0NCiMgR2V0IHByZWRpY3Rpb25zIG9uIHRlc3Qgc2V0DQoNCiMgQ3JlYXRlIGRheSBvZiB3ZWVrIGZhY3RvciB3aXRoIHRyZWF0bWVudCAoZHVtbXkpIGNvZGluZw0KdGVzdCA8LSB0ZXN0ICU+JQ0KICBtdXRhdGUoZG90d19zaW1wbGUgPSBmYWN0b3IoZG90dywgbGV2ZWxzID0gYygiTW9uIiwgIlR1ZSIsICJXZWQiLCAiVGh1IiwgIkZyaSIsICJTYXQiLCAiU3VuIikpKQ0KDQojIFNldCBjb250cmFzdHMgdG8gdHJlYXRtZW50IGNvZGluZyAoZHVtbXkgdmFyaWFibGVzKQ0KY29udHJhc3RzKHRlc3QkZG90d19zaW1wbGUpIDwtIGNvbnRyLnRyZWF0bWVudCg3KQ0KDQp0ZXN0IDwtIHRlc3QgJT4lDQogIG11dGF0ZSgNCiAgICBwcmVkMSA9IHByZWRpY3QobW9kZWwxLCBuZXdkYXRhID0gdGVzdCksDQogICAgcHJlZDIgPSBwcmVkaWN0KG1vZGVsMiwgbmV3ZGF0YSA9IHRlc3QpLA0KICAgIHByZWQzID0gcHJlZGljdChtb2RlbDMsIG5ld2RhdGEgPSB0ZXN0KSwNCiAgICBwcmVkNCA9IHByZWRpY3QobW9kZWw0LCBuZXdkYXRhID0gdGVzdCksDQogICAgcHJlZDUgPSBwcmVkaWN0KG1vZGVsNSwgbmV3ZGF0YSA9IHRlc3QpDQogICkNCg0KIyBDYWxjdWxhdGUgTUFFIGZvciBlYWNoIG1vZGVsDQptYWVfcmVzdWx0cyA8LSBkYXRhLmZyYW1lKA0KICBNb2RlbCA9IGMoDQogICAgIjEuIFRpbWUgKyBXZWF0aGVyIiwNCiAgICAiMi4gKyBUZW1wb3JhbCBMYWdzIiwNCiAgICAiMy4gKyBEZW1vZ3JhcGhpY3MiLA0KICAgICI0LiArIFN0YXRpb24gRkUiLA0KICAgICI1LiArIFJ1c2ggSG91ciBJbnRlcmFjdGlvbiINCiAgKSwNCiAgTUFFID0gYygNCiAgICBtZWFuKGFicyh0ZXN0JFRyaXBfQ291bnQgLSB0ZXN0JHByZWQxKSwgbmEucm0gPSBUUlVFKSwNCiAgICBtZWFuKGFicyh0ZXN0JFRyaXBfQ291bnQgLSB0ZXN0JHByZWQyKSwgbmEucm0gPSBUUlVFKSwNCiAgICBtZWFuKGFicyh0ZXN0JFRyaXBfQ291bnQgLSB0ZXN0JHByZWQzKSwgbmEucm0gPSBUUlVFKSwNCiAgICBtZWFuKGFicyh0ZXN0JFRyaXBfQ291bnQgLSB0ZXN0JHByZWQ0KSwgbmEucm0gPSBUUlVFKSwNCiAgICBtZWFuKGFicyh0ZXN0JFRyaXBfQ291bnQgLSB0ZXN0JHByZWQ1KSwgbmEucm0gPSBUUlVFKQ0KICApDQopDQoNCmthYmxlKG1hZV9yZXN1bHRzLCANCiAgICAgIGRpZ2l0cyA9IDIsDQogICAgICBjYXB0aW9uID0gIk1lYW4gQWJzb2x1dGUgRXJyb3IgYnkgTW9kZWwgKFRlc3QgU2V0KSIsDQogICAgICBjb2wubmFtZXMgPSBjKCJNb2RlbCIsICJNQUUgKHRyaXBzKSIpKSAlPiUNCiAga2FibGVfc3R5bGluZyhib290c3RyYXBfb3B0aW9ucyA9IGMoInN0cmlwZWQiLCAiaG92ZXIiKSkNCmBgYA0KDQoNCiMjIFZpc3VhbGl6ZSBNb2RlbCBDb21wYXJpc29uDQoNCmBgYHtyIGNvbXBhcmVfbW9kZWxzLCBlY2hvID0gVFJVRSwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlPUZBTFNFLCBjYWNoZSA9IFRSVUUgfQ0KZ2dwbG90KG1hZV9yZXN1bHRzLCBhZXMoeCA9IHJlb3JkZXIoTW9kZWwsIC1NQUUpLCB5ID0gTUFFKSkgKw0KICBnZW9tX2NvbChmaWxsID0gIiNFMURDRjUiLCBhbHBoYSA9IDAuOCkgKw0KICBnZW9tX3RleHQoYWVzKGxhYmVsID0gcm91bmQoTUFFLCAyKSksIHZqdXN0ID0gLTAuNSkgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIk1vZGVsIFBlcmZvcm1hbmNlIENvbXBhcmlzb24iLA0KICAgIHN1YnRpdGxlID0gIkxvd2VyIE1BRSA9IEJldHRlciBQcmVkaWN0aW9ucyIsDQogICAgeCA9ICJNb2RlbCIsDQogICAgeSA9ICJNZWFuIEFic29sdXRlIEVycm9yICh0cmlwcykiDQogICkgKw0KICBwbG90VGhlbWUgKw0KICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpKQ0KYGBgDQoNCg0KVGhlIHRlbXBvcmFsIGxhZyB2YXJpYWJsZXMgcHJvdmlkZWQgdGhlIGxhcmdlc3QgaW1wcm92ZW1lbnQgaW4gcHJlZGljdGl2ZSBhY2N1cmFjeS4gTW92aW5nIGZyb20gTW9kZWwgMSAodGltZSArIHdlYXRoZXIgb25seSkgdG8gTW9kZWwgMihhZGRpbmcgbGFnMUhvdXIsIGxhZzNIb3VycywgbGFnMURheSkgcmVkdWNlZCB0aGUgTUFFIGZyb20gMC43MSB0byAwLjU4LCBhIHNvbGlkIGltcHJvdmVtZW50LiBUaGUgdGVtcG9yYWwgbGFncyBhcmUgbWVhbnQgdG8gY2FwdHVyZSAiaWYgWCByaWRlcnMgZGVwYXJ0ZWQgZnJvbSBTdGF0aW9uICMzMDEwLCBmb3IgZXhhbXBsZSwgMSBob3VyIGFnbywgdGhlbiBYIHJpZGVycyB3aWxsIGRlcGFydCBmcm9tIFN0YXRpb24gIzMwMTAgYXQgdGhlIHNhbWUgdGltZSBpbiB0aGUgbmV4dCBob3VyLiIgT3IsIHN0YXRpb25zIHRoYXQgd2VyZSBidXN5IG9uZSBob3VyIGFnbyBhcmUgbGlrZWx5IHRvIHJlbWFpbiBidXN5IGluIHRoZSBuZXh0IGhvdXIuIEludGVyZXN0aW5nbHksIGFkZGluZyBkZW1vZ3JhcGhpYyB2YXJpYWJsZXMsIHN0YXRpb24gZml4ZWQgZWZmZWN0cywgcnVzaCBob3VyIGludGVyYWN0aW9ucywgYW5kIGxhdGVyIHByZWNpcGl0YXRpb24gZm9yZWNhc3QsIGRpc3RhbmNlIHRvIG5lYXJlc3QgdW5pdmVyc2l0eSwgYXMgd2VsbCBhcyBzdGF0aW9uLXR5cGUgY2x1c3RlcmluZyBmZWF0dXJlIGltcGxlbWVudGF0aW9uIGludGVyYWN0aW9ucyBhY3R1YWxseSBpbmNyZWFzZWQgdGhlIE1BRSwgc3VnZ2VzdGluZyB0aGF0IHRoZXNlIGFkZGl0aW9ucyBsZWQgdG8gb3ZlcmZpdHRpbmcgb24gdGhlIHRyYWluaW5nIGRhdGEuIA0KDQotLS0NCg0KIyBTcGFjZS1UaW1lIEVycm9yIEFuYWx5c2lzDQoNCiMjIE9ic2VydmVkIHZzLiBQcmVkaWN0ZWQNCg0KTGV0J3MgdXNlIG91ciBiZXN0IG1vZGVsIChNb2RlbCAyLCB0ZW1wb3JhbCBsYWcpIGZvciBlcnJvciBhbmFseXNpcy4NCg0KYGBge3Igb2JzX3ZzX3ByZWR9DQp0ZXN0IDwtIHRlc3QgJT4lDQogIG11dGF0ZSgNCiAgICBlcnJvciA9IFRyaXBfQ291bnQgLSBwcmVkMiwNCiAgICBhYnNfZXJyb3IgPSBhYnMoZXJyb3IpLA0KICAgIHRpbWVfb2ZfZGF5ID0gY2FzZV93aGVuKA0KICAgICAgaG91ciA8IDcgfiAiT3Zlcm5pZ2h0IiwNCiAgICAgIGhvdXIgPj0gNyAmIGhvdXIgPCAxMCB+ICJBTSBSdXNoIiwNCiAgICAgIGhvdXIgPj0gMTAgJiBob3VyIDwgMTUgfiAiTWlkLURheSIsDQogICAgICBob3VyID49IDE1ICYgaG91ciA8PSAxOCB+ICJQTSBSdXNoIiwNCiAgICAgIGhvdXIgPiAxOCB+ICJFdmVuaW5nIg0KICAgICkNCiAgKQ0KDQojIFNjYXR0ZXIgcGxvdCBieSB0aW1lIGFuZCBkYXkgdHlwZQ0KZ2dwbG90KHRlc3QsIGFlcyh4ID0gVHJpcF9Db3VudCwgeSA9IHByZWQyKSkgKw0KICBnZW9tX3BvaW50KGFscGhhID0gMC4yLCBjb2xvciA9ICIjNjg0OUJBIikgKw0KICBnZW9tX2FibGluZShzbG9wZSA9IDEsIGludGVyY2VwdCA9IDAsIGNvbG9yID0gInJlZCIsIGxpbmV3aWR0aCA9IDEpICsNCiAgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxtIiwgc2UgPSBGQUxTRSwgY29sb3IgPSAiZGFya2dyZWVuIikgKw0KICBmYWNldF9ncmlkKHdlZWtlbmQgfiB0aW1lX29mX2RheSkgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIk9ic2VydmVkIHZzLiBQcmVkaWN0ZWQgQmlrZSBUcmlwcyIsDQogICAgc3VidGl0bGUgPSAiTW9kZWwgMiBwZXJmb3JtYW5jZSBieSB0aW1lIHBlcmlvZCIsDQogICAgeCA9ICJPYnNlcnZlZCBUcmlwcyIsDQogICAgeSA9ICJQcmVkaWN0ZWQgVHJpcHMiLA0KICAgIGNhcHRpb24gPSAiUmVkIGxpbmUgPSBwZXJmZWN0IHByZWRpY3Rpb25zOyBHcmVlbiBsaW5lID0gYWN0dWFsIG1vZGVsIGZpdCINCiAgKSArDQogIHBsb3RUaGVtZQ0KYGBgDQoNClRoZSBvYnNlcnZlZCB2cy4gcHJlZGljdGVkIHBsb3RzIHJldmVhbCBzeXN0ZW1hdGljIHBhdHRlcm5zIGluIE1vZGVsIDIncyBlcnJvcnMuIFRoZSBtb2RlbCBwZXJmb3JtcyBiZXN0IGR1cmluZyBvdmVybmlnaHQgaG91cnMsIHdoZXJlIGRlbWFuZCBpcyBjb25zaXN0ZW50bHkgbmVhciB6ZXJvIGFuZCBwcmVkaWN0aW9ucyB0aWdodGx5IGNsdXN0ZXIgYWxvbmcgdGhlIHBlcmZlY3QtcHJlZGljdGlvbiBsaW5lLiBQZXJmb3JtYW5jZSBkZWdyYWRlcyBzdWJzdGFudGlhbGx5IGR1cmluZyBwZWFrIHBlcmlvZHPigJRwYXJ0aWN1bGFybHkgUE0gUnVzaCBvbiB3ZWVrZGF5c+KAlHdoZXJlIHRoZSBncmVlbiByZWdyZXNzaW9uIGxpbmUgZGl2ZXJnZXMgc2hhcnBseSBmcm9tIHRoZSByZWQgZGlhZ29uYWwuIFRoZSBtb2RlbCBleGhpYml0cyBhIGNsZWFyICJjZWlsaW5nIGVmZmVjdCwiIHJhcmVseSBwcmVkaWN0aW5nIG1vcmUgdGhhbiA2LTggdHJpcHMgcGVyIHN0YXRpb24taG91ciBldmVuIHdoZW4gYWN0dWFsIGRlbWFuZCByZWFjaGVzIDIwKyB0cmlwcy4gVGhpcyBzeXN0ZW1hdGljIHVuZGVycHJlZGljdGlvbiBvZiBoaWdoLWRlbWFuZCBvYnNlcnZhdGlvbnMgaXMgb3BlcmF0aW9uYWxseSBjb25jZXJuaW5nOiBvdXQtb2Ytc3RvY2sgcmlzayBpcyBoaWdoZXN0IHByZWNpc2VseSB3aGVuIGRlbWFuZCBzcGlrZXMsIGFuZCB0aG9zZSBhcmUgdGhlIHByZWRpY3Rpb25zIHdoZXJlIGFjY3VyYWN5IG1hdHRlcnMgbW9zdCBmb3IgcmViYWxhbmNpbmcgZGVjaXNpb25zLiBXZWVrZW5kIHByZWRpY3Rpb25zIChib3R0b20gcm93KSBzaG93IGxlc3MgZXh0cmVtZSBkaXZlcmdlbmNlLCBsaWtlbHkgYmVjYXVzZSB3ZWVrZW5kIGRlbWFuZCBpcyBsb3dlciBhbmQgbW9yZSBldmVubHkgZGlzdHJpYnV0ZWQgdGhyb3VnaG91dCB0aGUgZGF5Lg0KDQojIyBTcGF0aWFsIEVycm9yIFBhdHRlcm5zDQoNCkFyZSBwcmVkaWN0aW9uIGVycm9ycyBjbHVzdGVyZWQgaW4gY2VydGFpbiBwYXJ0cyBvZiBQaGlsYWRlbHBoaWE/DQoNCmBgYHtyIHNwYXRpYWxfZXJyb3JzLCBlY2hvID0gVFJVRSwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlPUZBTFNFLCBjYWNoZSA9IFRSVUUsIGZpZy53aWR0aD0xMiwgZmlnLmhlaWdodD02fQ0KIyBDYWxjdWxhdGUgc3RhdGlvbiBlcnJvcnMNCnN0YXRpb25fZXJyb3JzIDwtIHRlc3QgJT4lDQogIGZpbHRlcighaXMubmEocHJlZDIpKSAlPiUNCiAgZ3JvdXBfYnkoc3RhcnRfc3RhdGlvbiwgc3RhcnRfbGF0LngsIHN0YXJ0X2xvbi55KSAlPiUNCiAgc3VtbWFyaXplKA0KICAgIE1BRSA9IG1lYW4oYWJzKFRyaXBfQ291bnQgLSBwcmVkMiksIG5hLnJtID0gVFJVRSksDQogICAgYXZnX2RlbWFuZCA9IG1lYW4oVHJpcF9Db3VudCwgbmEucm0gPSBUUlVFKSwNCiAgICB0b3RhbF90cmlwcyA9IHN1bShUcmlwX0NvdW50KSwNCiAgICAuZ3JvdXBzID0gImRyb3AiDQogICkgJT4lDQogIGZpbHRlcighaXMubmEoc3RhcnRfbGF0LngpLCAhaXMubmEoc3RhcnRfbG9uLnkpKQ0KDQojIE1hcCAxOiBQcmVkaWN0aW9uIEVycm9ycw0KcDEgPC0gZ2dwbG90KCkgKw0KICBnZW9tX3NmKGRhdGEgPSBwaGlsbHlfY2Vuc3VzLCBmaWxsID0gIiNmMGYwZjAiLCBjb2xvciA9ICJ3aGl0ZSIsIGxpbmV3aWR0aCA9IDAuMykgKw0KICBnZW9tX3BvaW50KA0KICAgIGRhdGEgPSBzdGF0aW9uX2Vycm9ycywNCiAgICBhZXMoeCA9IHN0YXJ0X2xvbi55LCB5ID0gc3RhcnRfbGF0LngsIHNpemUgPSB0b3RhbF90cmlwcywgY29sb3IgPSBNQUUpLA0KICAgIGFscGhhID0gMC44DQogICkgKw0KICBzY2FsZV9jb2xvcl9ncmFkaWVudG4oDQogICAgY29sb3JzID0gcmV2KHBhbGV0dGU1KSwgICMgVXNlIHlvdXIgcGFsZXR0ZQ0KICAgIG5hbWUgPSAiTWVhbiBBYnNvbHV0ZVxuRXJyb3IgKHRyaXBzKSIsDQogICAgYnJlYWtzID0gc2VxKDAsIG1heChzdGF0aW9uX2Vycm9ycyRNQUUsIG5hLnJtID0gVFJVRSksIGxlbmd0aC5vdXQgPSA0KSwNCiAgICBsYWJlbHMgPSBmdW5jdGlvbih4KSByb3VuZCh4LCAyKQ0KICApICsNCiAgc2NhbGVfc2l6ZV9jb250aW51b3VzKA0KICAgIG5hbWUgPSAiVG90YWwgVHJpcHMiLA0KICAgIHJhbmdlID0gYygyLCA4KSwNCiAgICBicmVha3MgPSBjKDEwMCwgNTAwLCAxMDAwLCAyMDAwKSwNCiAgICBsYWJlbHMgPSBzY2FsZXM6OmNvbW1hDQogICkgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIk1vZGVsIFByZWRpY3Rpb24gRXJyb3JzIGJ5IFN0YXRpb24iLA0KICAgIHN1YnRpdGxlID0gIkxhcmdlciBwb2ludHMgPSBtb3JlIHRyaXBzOyBEYXJrZXIgPSBoaWdoZXIgZXJyb3IiDQogICkgKw0KICBjb29yZF9zZih4bGltID0gYygtNzUuMjUsIC03NS4wMCksIHlsaW0gPSBjKDM5Ljg4LCA0MC4wOCkpICsNCiAgbWFwVGhlbWUNCg0KIyBNYXAgMjogQXZlcmFnZSBEZW1hbmQNCnAyIDwtIGdncGxvdCgpICsNCiAgZ2VvbV9zZihkYXRhID0gcGhpbGx5X2NlbnN1cywgZmlsbCA9ICIjZjBmMGYwIiwgY29sb3IgPSAid2hpdGUiLCBsaW5ld2lkdGggPSAwLjMpICsNCiAgZ2VvbV9wb2ludCgNCiAgICBkYXRhID0gc3RhdGlvbl9lcnJvcnMsDQogICAgYWVzKHggPSBzdGFydF9sb24ueSwgeSA9IHN0YXJ0X2xhdC54LCBzaXplID0gdG90YWxfdHJpcHMsIGNvbG9yID0gYXZnX2RlbWFuZCksDQogICAgYWxwaGEgPSAwLjgNCiAgKSArDQogIHNjYWxlX2NvbG9yX2dyYWRpZW50bigNCiAgICBjb2xvcnMgPSBjKCIjMDg1MTljIiwgIiMzMTgyYmQiLCAiIzZiYWVkNiIsICIjYmRkN2U3IiwgIiNlZmYzZmYiKSwNCiAgICBuYW1lID0gIkF2ZXJhZ2VcbkRlbWFuZFxuKHRyaXBzL2hvdXIpIiwNCiAgICBicmVha3MgPSBzZXEoMCwgbWF4KHN0YXRpb25fZXJyb3JzJGF2Z19kZW1hbmQsIG5hLnJtID0gVFJVRSksIGxlbmd0aC5vdXQgPSA0KSwNCiAgICBsYWJlbHMgPSBmdW5jdGlvbih4KSByb3VuZCh4LCAyKQ0KICApICsNCiAgc2NhbGVfc2l6ZV9jb250aW51b3VzKA0KICAgIG5hbWUgPSAiVG90YWwgVHJpcHMiLA0KICAgIHJhbmdlID0gYygyLCA4KSwNCiAgICBicmVha3MgPSBjKDEwMCwgNTAwLCAxMDAwLCAyMDAwKSwNCiAgICBsYWJlbHMgPSBzY2FsZXM6OmNvbW1hDQogICkgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIkF2ZXJhZ2UgRGVtYW5kIGJ5IFN0YXRpb24iLA0KICAgIHN1YnRpdGxlID0gIkRhcmtlciBibHVlID0gaGlnaGVyIHJpZGVyc2hpcCBjb25jZW50cmF0aW9uIg0KICApICsNCiAgY29vcmRfc2YoeGxpbSA9IGMoLTc1LjI1LCAtNzUuMDApLCB5bGltID0gYygzOS44OCwgNDAuMDgpKSArDQogIG1hcFRoZW1lDQoNCiMgQ29tYmluZSB3aXRoIHBhdGNod29yaw0KcDEgKyBwMiArDQogIHBsb3RfYW5ub3RhdGlvbigNCiAgICB0aXRsZSA9ICJNb2RlbCAyIFBlcmZvcm1hbmNlOiBTcGF0aWFsIERpc3RyaWJ1dGlvbiBvZiBFcnJvcnMgYW5kIERlbWFuZCIsDQogICAgc3VidGl0bGUgPSAiUGhpbGFkZWxwaGlhIEluZGVnbyBCaWtlIFNoYXJlIC0gMjAyNCIsDQogICAgY2FwdGlvbiA9ICJTb3VyY2U6IEluZGVnbyBiaWtlIHNoYXJlIHRyaXAgZGF0YTsgTW9kZWwgMiBwcmVkaWN0aW9ucyAodGVtcG9yYWwgbGFncykiLA0KICAgIHRoZW1lID0gdGhlbWUoDQogICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxNCwgZmFjZSA9ICJib2xkIiwgaGp1c3QgPSAwLjUpLA0KICAgICAgcGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTAsIGhqdXN0ID0gMC41KSwNCiAgICAgIHBsb3QuY2FwdGlvbiA9IGVsZW1lbnRfdGV4dChzaXplID0gOCkNCiAgICApDQogICkNCmBgYA0KDQoNClRoZSBwcmVkaWN0aW9uIGVycm9ycyBzaG93IHNwYXRpYWwgY2x1c3RlcmluZyBpbiBDZW50ZXIgQ2l0eSBhbmQgVW5pdmVyc2l0eSBDaXR5IGhhdmluZyBoaWdoIE1BRSBjb25jZW50cmF0aW9ucy4gVGhpcyBwYXR0ZXJuIHN1Z2dlc3RzIHRoYXQgdGhlIG1vZGVsIHN0cnVnZ2xlcyB3aXRoIGhpZ2gtdm9sdW1lIHN0YXRpb25zIHRoYXQgZXhwZXJpZW5jZSByYXBpZCBkZW1hbmQgZmx1Y3R1YXRpb25zLiBOZWlnaGJvcmhvb2RzIGxpa2UgRWFzdCBQYXNzeXVuaywgUG9pbnQgQnJlZXplLCBRdWVlbiBWaWxsYWdlLCBNYXJrZXQgRWFzdCwgYW5kIE5vcnRoZXJuIExpYmVydGllcy9GaXNodG93biBzaG93IGFyZWFzIHdoZXJlIGRlbWFuZCBpcyBsZXNzIGFuZCBhbHNvIG1vcmUgc3RhYmxlLiBUaGUgc3BhdGlhbCBjb25jZW50cmF0aW9uIG9mIGVycm9ycyBpbiB0aGUgdXJiYW4gY29yZSBpcyBvcGVyYXRpb25hbGx5IHNpZ25pZmljYW50IGJlY2F1c2UgdGhlc2UgYXJlIHRoZSBzdGF0aW9ucyBtb3N0IGxpa2VseSB0byBleHBlcmllbmNlIHN0b2Nrb3V0cyBhbmQgd2hlcmUgcmViYWxhbmNpbmcgZGVjaXNpb25zIGFyZSBtb3N0IGNvbnNlcXVlbnRpYWwuICANCg0KIyMgVGVtcG9yYWwgRXJyb3IgUGF0dGVybnMNCg0KV2hlbiBhcmUgd2UgbW9zdCB3cm9uZz8NCg0KYGBge3IgdGVtcG9yYWxfZXJyb3JzfQ0KIyBNQUUgYnkgdGltZSBvZiBkYXkgYW5kIGRheSB0eXBlDQp0ZW1wb3JhbF9lcnJvcnMgPC0gdGVzdCAlPiUNCiAgZ3JvdXBfYnkodGltZV9vZl9kYXksIHdlZWtlbmQpICU+JQ0KICBzdW1tYXJpemUoDQogICAgTUFFID0gbWVhbihhYnNfZXJyb3IsIG5hLnJtID0gVFJVRSksDQogICAgLmdyb3VwcyA9ICJkcm9wIg0KICApICU+JQ0KICBtdXRhdGUoZGF5X3R5cGUgPSBpZmVsc2Uod2Vla2VuZCA9PSAxLCAiV2Vla2VuZCIsICJXZWVrZGF5IikpDQoNCmdncGxvdCh0ZW1wb3JhbF9lcnJvcnMsIGFlcyh4ID0gdGltZV9vZl9kYXksIHkgPSBNQUUsIGZpbGwgPSBkYXlfdHlwZSkpICsNCiAgZ2VvbV9jb2wocG9zaXRpb24gPSAiZG9kZ2UiKSArDQogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGMoIldlZWtkYXkiID0gIiMwODUxOWMiLCAiV2Vla2VuZCIgPSAiIzZiYWVkNiIpKSArDQogIGxhYnMoDQogICAgdGl0bGUgPSAiUHJlZGljdGlvbiBFcnJvcnMgYnkgVGltZSBQZXJpb2QiLA0KICAgIHN1YnRpdGxlID0gIldoZW4gaXMgdGhlIG1vZGVsIHN0cnVnZ2xpbmcgbW9zdD8iLA0KICAgIHggPSAiVGltZSBvZiBEYXkiLA0KICAgIHkgPSAiTWVhbiBBYnNvbHV0ZSBFcnJvciAodHJpcHMpIiwNCiAgICBmaWxsID0gIkRheSBUeXBlIg0KICApICsNCiAgcGxvdFRoZW1lICsNCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSkNCmBgYA0KDQojIyBFcnJvcnMgYW5kIERlbW9ncmFwaGljcw0KDQpBcmUgcHJlZGljdGlvbiBlcnJvcnMgcmVsYXRlZCB0byBuZWlnaGJvcmhvb2QgY2hhcmFjdGVyaXN0aWNzPw0KDQpgYGB7ciBlcnJvcnNfZGVtb2dyYXBoaWNzfQ0KIyBKb2luIGRlbW9ncmFwaGljIGRhdGEgdG8gc3RhdGlvbiBlcnJvcnMNCnN0YXRpb25fZXJyb3JzX2RlbW8gPC0gc3RhdGlvbl9lcnJvcnMgJT4lDQogIGxlZnRfam9pbigNCiAgICBzdGF0aW9uX2F0dHJpYnV0ZXMgJT4lIGRwbHlyOjpzZWxlY3Qoc3RhcnRfc3RhdGlvbiwgTWVkX0luYywgUGVyY2VudF9UYWtpbmdfVHJhbnNpdCwgUGVyY2VudF9XaGl0ZSksDQogICAgYnkgPSAic3RhcnRfc3RhdGlvbiINCiAgKSAlPiUNCiAgZmlsdGVyKCFpcy5uYShNZWRfSW5jKSkNCg0KIyBDcmVhdGUgcGxvdHMNCnAxIDwtIGdncGxvdChzdGF0aW9uX2Vycm9yc19kZW1vLCBhZXMoeCA9IE1lZF9JbmMsIHkgPSBNQUUpKSArDQogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjUsIGNvbG9yID0gIiNFMURDRjUiKSArDQogIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsbSIsIHNlID0gRkFMU0UsIGNvbG9yID0gInJlZCIpICsNCiAgc2NhbGVfeF9jb250aW51b3VzKGxhYmVscyA9IHNjYWxlczo6ZG9sbGFyKSArDQogIGxhYnModGl0bGUgPSAiRXJyb3JzIHZzLiBNZWRpYW4gSW5jb21lIiwgeCA9ICJNZWRpYW4gSW5jb21lIiwgeSA9ICJNQUUiKSArDQogIHBsb3RUaGVtZQ0KDQpwMiA8LSBnZ3Bsb3Qoc3RhdGlvbl9lcnJvcnNfZGVtbywgYWVzKHggPSBQZXJjZW50X1Rha2luZ19UcmFuc2l0LCB5ID0gTUFFKSkgKw0KICBnZW9tX3BvaW50KGFscGhhID0gMC41LCBjb2xvciA9ICIjRTFEQ0Y1IikgKw0KICBnZW9tX3Ntb290aChtZXRob2QgPSAibG0iLCBzZSA9IEZBTFNFLCBjb2xvciA9ICJyZWQiKSArDQogIGxhYnModGl0bGUgPSAiRXJyb3JzIHZzLiBUcmFuc2l0IFVzYWdlIiwgeCA9ICJQZXJjZW50IFRha2luZyBUcmFuc2l0IiwgeSA9ICJNQUUiKSArDQogIHBsb3RUaGVtZQ0KDQpwMyA8LSBnZ3Bsb3Qoc3RhdGlvbl9lcnJvcnNfZGVtbywgYWVzKHggPSBQZXJjZW50X1doaXRlLCB5ID0gTUFFKSkgKw0KICBnZW9tX3BvaW50KGFscGhhID0gMC41LCBjb2xvciA9ICIjRTFEQ0Y1IikgKw0KICBnZW9tX3Ntb290aChtZXRob2QgPSAibG0iLCBzZSA9IEZBTFNFLCBjb2xvciA9ICJyZWQiKSArDQogIGxhYnModGl0bGUgPSAiRXJyb3JzIHZzLiBSYWNlIiwgeCA9ICJQZXJjZW50IFdoaXRlIiwgeSA9ICJNQUUiKSArDQogIHBsb3RUaGVtZQ0KDQpncmlkLmFycmFuZ2UocDEsIHAyLCBwMywgbmNvbCA9IDIpDQpgYGANCg0KDQpUaGUgZGVtb2dyYXBoaWMgZXJyb3IgYW5hbHlzaXMgcmV2ZWFscyBudWFuY2VkIHBhdHRlcm5zIGluIG1vZGVsIHBlcmZvcm1hbmNlIGFjcm9zcyBuZWlnaGJvcmhvb2RzLiBNZWRpYW4gaW5jb21lIHNob3dzIHZpcnR1YWxseSBubyByZWxhdGlvbnNoaXAgd2l0aCBwcmVkaWN0aW9uIGVycm9y4oCUdGhlIHJlZ3Jlc3Npb24gbGluZSBpcyBuZWFybHkgZmxhdCwgc3VnZ2VzdGluZyB0aGUgbW9kZWwgbmVpdGhlciBhZHZhbnRhZ2VzIG5vciBkaXNhZHZhbnRhZ2VzIHN0YXRpb25zIGJhc2VkIG9uIG5laWdoYm9yaG9vZCB3ZWFsdGguIFRyYW5zaXQgdXNhZ2Ugc2hvd3MgdGhlIGNsZWFyZXN0IHBhdHRlcm46IHN0YXRpb25zIGluIGFyZWFzIHdpdGggaGlnaGVyIHRyYW5zaXQgY29tbXV0ZXIgcmF0ZXMgaGF2ZSBzdWJzdGFudGlhbGx5IGxvd2VyIE1BRSAoYXJvdW5kIDAuNikgY29tcGFyZWQgdG8gY2FyLWRlcGVuZGVudCBhcmVhcyAoTUFFIGV4Y2VlZGluZyAxLjApLiBUaGlzIHN1Z2dlc3RzIHRoZSBtb2RlbCBwZXJmb3JtcyBiZXN0IGluIGRlbnNlLCB0cmFuc2l0LW9yaWVudGVkIHVyYmFuIGNvcmUgbmVpZ2hib3Job29kcyB3aGVyZSBiaWtlIHNoYXJlIGludGVncmF0ZXMgaW50byBwcmVkaWN0YWJsZSBtdWx0aW1vZGFsIGNvbW11dGluZyBwYXR0ZXJucy4gUGVyaGFwcyBjb3VudGVyaW50dWl0aXZlbHksIHRoZSBwZXJjZW50IHdoaXRlIHJlbGF0aW9uc2hpcCBzaG93cyBhIHNsaWdodCBwb3NpdGl2ZSBzbG9wZeKAlGVycm9ycyBhcmUgbWFyZ2luYWxseSBoaWdoZXIgaW4gd2hpdGVyIG5laWdoYm9yaG9vZHMuIFRoaXMgbGlrZWx5IHJlZmxlY3RzIHRoYXQgd2hpdGVyIGFyZWFzIGluIFBoaWxhZGVscGhpYSAoQ2VudGVyIENpdHksIFJpdHRlbmhvdXNlKSBhbHNvIGhhdmUgaGlnaGVyIHJpZGVyc2hpcCB2b2x1bWVzIGFuZCBncmVhdGVyIGRlbWFuZCB2YXJpYWJpbGl0eSwgbGVhZGluZyB0byBsYXJnZXIgYWJzb2x1dGUgZXJyb3JzLiBGcm9tIGFuIGVxdWl0eSBzdGFuZHBvaW50LCB0aGVzZSBwYXR0ZXJucyBhcmUgcmVsYXRpdmVseSByZWFzc3VyaW5nOiB0aGUgbW9kZWwgZG9lcyBub3Qgc3lzdGVtYXRpY2FsbHkgdW5kZXJzZXJ2ZSBsb3dlci1pbmNvbWUgbmVpZ2hib3Job29kcyBvciBjb21tdW5pdGllcyBvZiBjb2xvci4gSG93ZXZlciwgdGhlIHN0cm9uZyB0cmFuc2l0LXVzYWdlIHJlbGF0aW9uc2hpcCBzdWdnZXN0cyB0aGF0IHBlcmlwaGVyYWwsIGNhci1kZXBlbmRlbnQgbmVpZ2hib3Job29kc+KAlHdoaWNoIG1heSBhbHJlYWR5IGhhdmUgbGltaXRlZCB0cmFuc3BvcnRhdGlvbiBvcHRpb25z4oCUcmVjZWl2ZSBsZXNzIGFjY3VyYXRlIHByZWRpY3Rpb25zLCBwb3RlbnRpYWxseSBhZmZlY3Rpbmcgc2VydmljZSBxdWFsaXR5IGluIGFyZWFzIHRoYXQgY291bGQgbW9zdCBiZW5lZml0IGZyb20gcmVsaWFibGUgYmlrZSBzaGFyZSBhY2Nlc3MuDQotLS0NCiMgRmVhdHVyZSBJbXBsZW1lbnRhdGlvbiANCg0KIyMgRmVhdHVyZSBTZWxlY3Rpb24gUmF0aW9uYWxlDQoNCkkgZW5naW5lZXJlZCB0aHJlZSBuZXcgZmVhdHVyZXMgdGFyZ2V0aW5nIHNwZWNpZmljIHdlYWtuZXNzZXMgaW4gdGhlIGJhc2VsaW5lIG1vZGVsOg0KDQoqKjEuIFJlY2VudCBSYWluIChQcmVjaXBpdGF0aW9uIExhZykqKg0KVGhlIGJhc2VsaW5lIG1vZGVsIGluY2x1ZGVzIGN1cnJlbnQgcHJlY2lwaXRhdGlvbiwgYnV0IHBlb3BsZSBkb24ndCBpbW1lZGlhdGVseSByZXN1bWUgYmlraW5nIHdoZW4gcmFpbiBzdG9wcy4gT2JzdGFjbGVzIGxpa2Ugd2V0IHN0cmVldHMgYW5kIHdldCBzZWF0cyBpbmZsdWVuY2UgcmlkZXIgY2hvaWNlLiBUaGlzICJoYW5nb3ZlciBlZmZlY3QiIG1vdGl2YXRlZCBjcmVhdGluZyBhIGJpbmFyeSBpbmRpY2F0b3IgZm9yIHdoZXRoZXIgcHJlY2lwaXRhdGlvbiBvY2N1cnJlZCBpbiB0aGUgcHJldmlvdXMgMyBob3Vycy4gVGhlIHZpc3VhbGl6YXRpb24gY29uZmlybXMgdGhhdCByaWRlcnNoaXAgcmVtYWlucyBzdXBwcmVzc2VkIGV2ZW4gYWZ0ZXIgcmFpbiBzdG9wcy4NCg0KKioyLiBEaXN0YW5jZSB0byBOZWFyZXN0IFVuaXZlcnNpdHkqKg0KVGhlIHNwYXRpYWwgZXJyb3IgYW5hbHlzaXMgc2hvd2VkIGVsZXZhdGVkIGVycm9ycyBuZWFyIFVuaXZlcnNpdHkgQ2l0eSwgc3VnZ2VzdGluZyBjYW1wdXMtcmVsYXRlZCBkZW1hbmQgcGF0dGVybnMgdGhlIG1vZGVsIHdhc24ndCBjYXB0dXJpbmcuIFVuaXZlcnNpdGllcyBnZW5lcmF0ZSBkaXN0aW5jdGl2ZSByaWRlcnNoaXA6IGNsYXNzIHNjaGVkdWxlcyBjcmVhdGUgcHJlZGljdGFibGUgaG91cmx5IHBlYWtzLCBhY2FkZW1pYyBjYWxlbmRhcnMgY2FuIHNoaWZ0IGRlbWFuZCBzZWFzb25hbGx5LCBhbmQgc3R1ZGVudCBkZW1vZ3JhcGhpY3MgZGlmZmVyIGZyb20gdHlwaWNhbCBjb21tdXRlcnMuIEkgY2FsY3VsYXRlZCBkaXN0YW5jZSBmcm9tIGVhY2ggc3RhdGlvbiB0byB0aGUgVW5pdmVyc2l0eSBvZiBQZW5uc3lsdmFuaWEsIERyZXhlbCBVbml2ZXJzaXR5LCBhbmQgVGVtcGxlIFVuaXZlcnNpdHksIGZsYWdnaW5nIHN0YXRpb25zIHdpdGhpbiAwLjUgbWlsZXMgYXMgIm5lYXIgdW5pdmVyc2l0eS4iIEh1YiBjYW1wdXNlcyBsaWtlIFRlbXBsZSBVbml2ZXJzaXR5IENlbnRlciBDaXR5IGNhbXB1cyBhbmQgU2FpbnQgSm9lc3BoJ3MgSGF3ayBIaWxsIGNhbXB1cyBpbiBVbml2ZXJzaXR5IENpdHkgYXJlIGV4Y2x1ZGVkLiANCg0KKiozLiBTdGF0aW9uIFR5cGUgQ2x1c3RlcmluZyoqDQpOb3QgYWxsIHN0YXRpb25zIHNlcnZlIHRoZSBzYW1lIHB1cnBvc2UuIEstbWVhbnMgY2x1c3RlcmluZyBvbiBob3VybHkgZGVtYW5kIHByb2ZpbGVzIHJldmVhbGVkIHRocmVlIGRpc3RpbmN0IHN0YXRpb24gdHlwZXM6IENvbW11dGVyIEh1YnMgKHN0cm9uZyBBTS9QTSBwZWFrcyksIFJlc2lkZW50aWFsIHN0YXRpb25zIChtb3JlIGRpc3RyaWJ1dGVkIGRlbWFuZCksIGFuZCBMb3cgQWN0aXZpdHkgc3RhdGlvbnMgKGNvbnNpc3RlbnRseSBsb3cgcmlkZXJzaGlwKS4gVGhpcyBjYXRlZ29yaWNhbCBmZWF0dXJlIGFsbG93cyB0aGUgbW9kZWwgdG8gbGVhcm4gZGlmZmVyZW50IGRlbWFuZCBkeW5hbWljcyBmb3IgZGlmZmVyZW50IHN0YXRpb24gdHlwZXMgcmF0aGVyIHRoYW4gdHJlYXRpbmcgYWxsIHN0YXRpb25zIGlkZW50aWNhbGx5Lg0KDQojIyBGZWF0dXJlIDE6IFByZWNpcGl0YXRpb24gRm9yZWNhc3QNCg0KYGBge3IgcHJlY2lwaXRhdGlvbl9mb3JlY2FzdCwgZWNobyA9IFRSVUUsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZT1GQUxTRSwgY2FjaGUgPSBUUlVFfQ0Kc3R1ZHlfcGFuZWxfY29tcGxldGUgPC0gc3R1ZHlfcGFuZWxfY29tcGxldGUgJT4lDQogIGFycmFuZ2Uoc3RhcnRfc3RhdGlvbiwgaW50ZXJ2YWw2MCkgJT4lDQogIGdyb3VwX2J5KHN0YXJ0X3N0YXRpb24pICU+JQ0KICBtdXRhdGUoDQogICAgIyBSb2xsaW5nIDMtaG91ciBwcmVjaXBpdGF0aW9uIHN1bQ0KICAgIHByZWNpcF9sYXN0XzNocnMgPSBsYWcoUHJlY2lwaXRhdGlvbiwgMSkgKyBsYWcoUHJlY2lwaXRhdGlvbiwgMikgKyBsYWcoUHJlY2lwaXRhdGlvbiwgMyksDQogICAgIyBCaW5hcnk6IGFueSBwcmVjaXBpdGF0aW9uIGluIGxhc3QgMyBob3Vycz8NCiAgICByZWNlbnRfcmFpbiA9IGlmZWxzZShwcmVjaXBfbGFzdF8zaHJzID4gMCwgMSwgMCksDQogICAgcmVjZW50X3JhaW4gPSByZXBsYWNlX25hKHJlY2VudF9yYWluLCAwKQ0KICApICU+JQ0KICB1bmdyb3VwKCkNCg0KDQojIEFkZCB0byB0cmFpbi90ZXN0DQp0cmFpbiA8LSBzdHVkeV9wYW5lbF9jb21wbGV0ZSAlPiUNCiAgZmlsdGVyKHdlZWsgPCAxMCwgc3RhcnRfc3RhdGlvbiAlaW4lIGNvbW1vbl9zdGF0aW9ucykNCg0KdGVzdCA8LSBzdHVkeV9wYW5lbF9jb21wbGV0ZSAlPiUNCiAgZmlsdGVyKHdlZWsgPj0gMTAsIHN0YXJ0X3N0YXRpb24gJWluJSBjb21tb25fc3RhdGlvbnMpDQoNCiMgUmVjcmVhdGUgZG90d19zaW1wbGUgZm9yIGJvdGgNCnRyYWluIDwtIHRyYWluICU+JQ0KICBtdXRhdGUoZG90d19zaW1wbGUgPSBmYWN0b3IoZG90dywgbGV2ZWxzID0gYygiTW9uIiwgIlR1ZSIsICJXZWQiLCAiVGh1IiwgIkZyaSIsICJTYXQiLCAiU3VuIikpKQ0KY29udHJhc3RzKHRyYWluJGRvdHdfc2ltcGxlKSA8LSBjb250ci50cmVhdG1lbnQoNykNCg0KdGVzdCA8LSB0ZXN0ICU+JQ0KICBtdXRhdGUoZG90d19zaW1wbGUgPSBmYWN0b3IoZG90dywgbGV2ZWxzID0gYygiTW9uIiwgIlR1ZSIsICJXZWQiLCAiVGh1IiwgIkZyaSIsICJTYXQiLCAiU3VuIikpKQ0KY29udHJhc3RzKHRlc3QkZG90d19zaW1wbGUpIDwtIGNvbnRyLnRyZWF0bWVudCg3KQ0KDQojIFZpc3VhbGl6ZSB0aGUgaW1wYWN0DQp0cmFpbiAlPiUNCiAgZmlsdGVyKCFpcy5uYShyZWNlbnRfcmFpbikpICU+JQ0KICBncm91cF9ieShob3VyLCByZWNlbnRfcmFpbikgJT4lDQogIHN1bW1hcml6ZShhdmdfdHJpcHMgPSBtZWFuKFRyaXBfQ291bnQpLCAuZ3JvdXBzID0gImRyb3AiKSAlPiUNCiAgZ2dwbG90KGFlcyh4ID0gaG91ciwgeSA9IGF2Z190cmlwcywgY29sb3IgPSBmYWN0b3IocmVjZW50X3JhaW4pKSkgKw0KICBnZW9tX2xpbmUobGluZXdpZHRoID0gMS4yKSArDQogIHNjYWxlX2NvbG9yX21hbnVhbCgNCiAgICB2YWx1ZXMgPSBjKCIwIiA9ICIjMzE4MmJkIiwgIjEiID0gImdyZXk2MCIpLA0KICAgIGxhYmVscyA9IGMoIk5vIFJlY2VudCBSYWluIiwgIlJhaW4gaW4gTGFzdCAzIEhvdXJzIiksDQogICAgbmFtZSA9ICIiDQogICkgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIkltcGFjdCBvZiBSZWNlbnQgUHJlY2lwaXRhdGlvbiBvbiBEZW1hbmQiLA0KICAgIHN1YnRpdGxlID0gIlJpZGVyc2hpcCByZW1haW5zIHN1cHByZXNzZWQgZXZlbiBhZnRlciByYWluIHN0b3BzIiwNCiAgICB4ID0gIkhvdXIgb2YgRGF5IiwNCiAgICB5ID0gIkF2ZXJhZ2UgVHJpcHMgcGVyIFN0YXRpb24iDQogICkgKw0KICBwbG90VGhlbWUgKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIikNCmBgYA0KDQoNCi0tLQ0KDQojIyBGZWF0dXJlIDI6IERpc3RhbmNlIHRvIE5lYXJlc3QgVW5pdmVyc2l0eQ0KDQpgYGB7ciB1bml2ZXJzaXR5X2Rpc3RhbmNlLCBlY2hvID0gVFJVRSwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlPUZBTFNFLCBjYWNoZSA9IFRSVUV9DQojIFBoaWxhZGVscGhpYSB1bml2ZXJzaXR5IGNvb3JkaW5hdGVzDQp1bml2ZXJzaXRpZXMgPC0gZGF0YS5mcmFtZSgNCiAgbmFtZSA9IGMoIlBlbm4iLCAiRHJleGVsIiwgIlRlbXBsZSIpLA0KICBsb24gPSBjKC03NS4xOTMyLCAgLTc1LjE4OTcsICAtNzUuMTQ5MiksDQogIGxhdCA9IGMoMzkuOTUyMiwgICAzOS45NTY2LCAgIDM5Ljk4MTIpDQopJT4lDQogIHN0X2FzX3NmKGNvb3JkcyA9IGMoImxvbiIsICJsYXQiKSwgY3JzID0gNDMyNikNCg0KIyBHZXQgdW5pcXVlIHN0YXRpb25zIGFzIHNmIG9iamVjdA0Kc3RhdGlvbnNfc2ZfZGlzdCA8LSBzdHVkeV9wYW5lbF9jb21wbGV0ZSAlPiUNCiAgZGlzdGluY3Qoc3RhcnRfc3RhdGlvbiwgc3RhcnRfbGF0LngsIHN0YXJ0X2xvbi54KSAlPiUNCiAgZmlsdGVyKCFpcy5uYShzdGFydF9sYXQueCksICFpcy5uYShzdGFydF9sb24ueCkpICU+JQ0KICBzdF9hc19zZihjb29yZHMgPSBjKCJzdGFydF9sb24ueCIsICJzdGFydF9sYXQueCIpLCBjcnMgPSA0MzI2KQ0KDQpkaXN0X3RvX3Blbm4gPC0gc3RfZGlzdGFuY2Uoc3RhdGlvbnNfc2ZfZGlzdCwgdW5pdmVyc2l0aWVzWzEsIF0pIC8gMTYwOS4zNCAgIyBtZXRlcnMgdG8gbWlsZXMNCmRpc3RfdG9fZHJleGVsIDwtIHN0X2Rpc3RhbmNlKHN0YXRpb25zX3NmX2Rpc3QsIHVuaXZlcnNpdGllc1syLCBdKSAvIDE2MDkuMzQNCmRpc3RfdG9fdGVtcGxlIDwtIHN0X2Rpc3RhbmNlKHN0YXRpb25zX3NmX2Rpc3QsIHVuaXZlcnNpdGllc1szLCBdKSAvIDE2MDkuMzQNCg0KIyBBZGQgZGlzdGFuY2VzIHRvIGRhdGFmcmFtZQ0Kc3RhdGlvbl91bmlfZGlzdCA8LSBzdGF0aW9uc19zZl9kaXN0ICU+JQ0KICBzdF9kcm9wX2dlb21ldHJ5KCkgJT4lDQogIGJpbmRfY29scygNCiAgICBkaXN0X3RvX3Blbm4gPSBhcy5udW1lcmljKGRpc3RfdG9fcGVubiksDQogICAgZGlzdF90b19kcmV4ZWwgPSBhcy5udW1lcmljKGRpc3RfdG9fZHJleGVsKSwNCiAgICBkaXN0X3RvX3RlbXBsZSA9IGFzLm51bWVyaWMoZGlzdF90b190ZW1wbGUpDQogICkgJT4lDQogIG11dGF0ZSgNCiAgICBkaXN0X3RvX25lYXJlc3RfdW5pID0gcG1pbihkaXN0X3RvX3Blbm4sIGRpc3RfdG9fZHJleGVsLCBkaXN0X3RvX3RlbXBsZSksDQogICAgbmVhcl91bml2ZXJzaXR5ID0gaWZlbHNlKGRpc3RfdG9fbmVhcmVzdF91bmkgPCAwLjUsIDEsIDApICAjIFdpdGhpbiAwLjUgbWlsZXMNCiAgKQ0KDQojIE5lZWQgdG8gZ2V0IHRoZSBjb29yZGluYXRlcyBiYWNrIGZvciBtYXBwaW5nDQpzdGF0aW9uX3VuaV9kaXN0IDwtIHN0dWR5X3BhbmVsX2NvbXBsZXRlICU+JQ0KICBkaXN0aW5jdChzdGFydF9zdGF0aW9uLCBzdGFydF9sYXQueCwgc3RhcnRfbG9uLngpICU+JQ0KICBmaWx0ZXIoIWlzLm5hKHN0YXJ0X2xhdC54KSwgIWlzLm5hKHN0YXJ0X2xvbi54KSkgJT4lDQogIGxlZnRfam9pbihzdGF0aW9uX3VuaV9kaXN0LCBieSA9ICJzdGFydF9zdGF0aW9uIikNCg0KY2F0KCJTdGF0aW9ucyB3aXRoIGRpc3RhbmNlIGNhbGN1bGF0ZWQ6IiwgbnJvdyhzdGF0aW9uX3VuaV9kaXN0KSwgIlxuXG4iKQ0KDQojIEpvaW4gYmFjayB0byBkYXRhc2V0cw0KdHJhaW4gPC0gdHJhaW4gJT4lDQogIGxlZnRfam9pbihzdGF0aW9uX3VuaV9kaXN0ICU+JSBzZWxlY3Qoc3RhcnRfc3RhdGlvbiwgZGlzdF90b19uZWFyZXN0X3VuaSwgbmVhcl91bml2ZXJzaXR5KSwNCiAgICAgICAgICAgIGJ5ID0gInN0YXJ0X3N0YXRpb24iKQ0KdGVzdCA8LSB0ZXN0ICU+JQ0KICBsZWZ0X2pvaW4oc3RhdGlvbl91bmlfZGlzdCAlPiUgc2VsZWN0KHN0YXJ0X3N0YXRpb24sIGRpc3RfdG9fbmVhcmVzdF91bmksIG5lYXJfdW5pdmVyc2l0eSksDQogICAgICAgICAgICBieSA9ICJzdGFydF9zdGF0aW9uIikNCg0KIyBFeHRyYWN0IHVuaXZlcnNpdHkgY29vcmRpbmF0ZXMgZm9yIHBsb3R0aW5nDQp1bmlfY29vcmRzIDwtIHVuaXZlcnNpdGllcyAlPiUNCiAgc3RfY29vcmRpbmF0ZXMoKSAlPiUNCiAgYXMuZGF0YS5mcmFtZSgpICU+JQ0KICBiaW5kX2NvbHMobmFtZSA9IGMoIlBlbm4iLCAiRHJleGVsIiwgIlRlbXBsZSIpKQ0KDQoNCg0KIyBNYXAgdW5pdmVyc2l0eSBwcm94aW1pdHkNCmdncGxvdCgpICsNCiAgZ2VvbV9zZihkYXRhID0gcGhpbGx5X2NlbnN1cywgZmlsbCA9ICIjZjBmMGYwIiwgY29sb3IgPSAid2hpdGUiLCBsaW5ld2lkdGggPSAwLjMpICsNCiAgIyBVbml2ZXJzaXRpZXMNCiAgZ2VvbV9wb2ludCgNCiAgICBkYXRhID0gdW5pX2Nvb3JkcywNCiAgICBhZXMoeCA9IFgsIHkgPSBZKSwNCiAgICBjb2xvciA9ICIjRTFEQ0Y1Iiwgc2l6ZSA9IDgsIHNoYXBlID0gMTggICMgRGlhbW9uZCBzaGFwZQ0KICApICsNCiAgZ2VvbV90ZXh0KA0KICAgIGRhdGEgPSB1bmlfY29vcmRzLA0KICAgIGFlcyh4ID0gWCwgeSA9IFksIGxhYmVsID0gbmFtZSksDQogICAgdmp1c3QgPSAtMS41LCBzaXplID0gMy41LCBmb250ZmFjZSA9ICJib2xkIg0KICApICsNCiAgIyBTdGF0aW9ucyBjb2xvcmVkIGJ5IHVuaXZlcnNpdHkgcHJveGltaXR5DQogIGdlb21fcG9pbnQoDQogICAgZGF0YSA9IHN0YXRpb25fdW5pX2Rpc3QsDQogICAgYWVzKHggPSBzdGFydF9sb24ueCwgeSA9IHN0YXJ0X2xhdC54LCBjb2xvciA9IGRpc3RfdG9fbmVhcmVzdF91bmksIHNpemUgPSBuZWFyX3VuaXZlcnNpdHkpLA0KICAgIGFscGhhID0gMC43DQogICkgKw0KICBzY2FsZV9jb2xvcl9ncmFkaWVudG4oDQogICAgY29sb3JzID0gcmV2KHBhbGV0dGU1KSwNCiAgICBuYW1lID0gIkRpc3RhbmNlIHRvXG5OZWFyZXN0IFVuaVxuKG1pbGVzKSIsDQogICAgbGltaXRzID0gYygwLCA1KQ0KICApICsNCiAgc2NhbGVfc2l6ZV9jb250aW51b3VzKHJhbmdlID0gYygxLjUsIDQpLCBndWlkZSA9ICJub25lIikgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIkluZGVnbyBTdGF0aW9ucyBieSBVbml2ZXJzaXR5IFByb3hpbWl0eSIsDQogICAgc3VidGl0bGUgPSAiTGFyZ2VyIHBvaW50cyA9IHdpdGhpbiAwLjUgbWlsZXMgb2YgdW5pdmVyc2l0eSINCiAgKSArDQogIGNvb3JkX3NmKHhsaW0gPSBjKC03NS4yNSwgLTc1LjAwKSwgeWxpbSA9IGMoMzkuODgsIDQwLjA4KSkgKw0KICBtYXBUaGVtZQ0KDQoNCmBgYA0KDQoNCmBgYHtyIHVuaXZlcnNpdHlfZGlzdGFuY2UyLCBlY2hvID0gVFJVRSwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlPUZBTFNFLCBjYWNoZSA9IFRSVUV9DQojIENvbXBhcmUgZGVtYW5kIG5lYXIgdnMgZmFyIGZyb20gdW5pdmVyc2l0aWVzDQp0cmFpbiAlPiUNCiAgZ3JvdXBfYnkoaG91ciwgbmVhcl91bml2ZXJzaXR5LCBkb3R3KSAlPiUNCiAgc3VtbWFyaXplKGF2Z190cmlwcyA9IG1lYW4oVHJpcF9Db3VudCksIC5ncm91cHMgPSAiZHJvcCIpICU+JQ0KICBmaWx0ZXIoZG90dyAlaW4lIGMoIk1vbiIsICJUdWUiLCAiV2VkIiwgIlRodSIsICJGcmkiKSkgJT4lDQogIGdncGxvdChhZXMoeCA9IGhvdXIsIHkgPSBhdmdfdHJpcHMsIGNvbG9yID0gZmFjdG9yKG5lYXJfdW5pdmVyc2l0eSkpKSArDQogIGdlb21fbGluZShsaW5ld2lkdGggPSAxLjIpICsNCiAgc2NhbGVfY29sb3JfbWFudWFsKA0KICAgIHZhbHVlcyA9IGMoIjAiID0gImdyZXk2MCIsICIxIiA9ICIjNjg0OUJBIiksDQogICAgbGFiZWxzID0gYygiQXdheSBmcm9tIFVuaXZlcnNpdGllcyIsICJOZWFyIFVuaXZlcnNpdGllcyAoPCAwLjUgbWkpIiksDQogICAgbmFtZSA9ICIiDQogICkgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIlJpZGVyc2hpcCBQYXR0ZXJuczogVW5pdmVyc2l0eSB2cyBOb24tVW5pdmVyc2l0eSBTdGF0aW9ucyIsDQogICAgc3VidGl0bGUgPSAiV2Vla2RheXMgb25seSAtIFVuaXZlcnNpdHkgc3RhdGlvbnMgc2hvdyBkaXN0aW5jdCBwZWFrcyIsDQogICAgeCA9ICJIb3VyIG9mIERheSIsDQogICAgeSA9ICJBdmVyYWdlIFRyaXBzIHBlciBTdGF0aW9uIg0KICApICsNCiAgcGxvdFRoZW1lICsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIpIA0KDQpgYGANCg0KLS0tDQoNCiMjIEZlYXR1cmUgMzogU3RhdGlvbi10eXBlIENsdXN0ZXJpbmcNCg0KYGBge3Igc3RhdGlvbl9jbHVzdGVyaW5nLCBlY2hvID0gVFJVRSwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlPUZBTFNFLCBjYWNoZSA9IFRSVUV9DQojIENyZWF0ZSBob3VybHkgZGVtYW5kIHByb2ZpbGVzIGZvciBlYWNoIHN0YXRpb24NCnN0YXRpb25fcHJvZmlsZXMgPC0gdHJhaW4gJT4lDQogIGdyb3VwX2J5KHN0YXJ0X3N0YXRpb24sIGhvdXIpICU+JQ0KICBzdW1tYXJpemUoDQogICAgYXZnX2RlbWFuZCA9IG1lYW4oVHJpcF9Db3VudCksDQogICAgd2Vla2VuZF9kZW1hbmQgPSBtZWFuKFRyaXBfQ291bnRbd2Vla2VuZCA9PSAxXSksDQogICAgd2Vla2RheV9kZW1hbmQgPSBtZWFuKFRyaXBfQ291bnRbd2Vla2VuZCA9PSAwXSksDQogICAgLmdyb3VwcyA9ICJkcm9wIg0KICApICU+JQ0KICBwaXZvdF93aWRlcigNCiAgICBpZF9jb2xzID0gc3RhcnRfc3RhdGlvbiwNCiAgICBuYW1lc19mcm9tID0gaG91ciwNCiAgICB2YWx1ZXNfZnJvbSA9IGF2Z19kZW1hbmQsDQogICAgbmFtZXNfcHJlZml4ID0gImhvdXJfIg0KICApDQoNCiMgSy1tZWFucyBjbHVzdGVyaW5nICgzIHR5cGVzKQ0Kc2V0LnNlZWQoMTIzKQ0Kc3RhdGlvbl9jbHVzdGVycyA8LSBzdGF0aW9uX3Byb2ZpbGVzICU+JQ0KICBzZWxlY3Qoc3RhcnRzX3dpdGgoImhvdXJfIikpICU+JQ0KICBzY2FsZSgpICU+JQ0KICBrbWVhbnMoY2VudGVycyA9IDMsIG5zdGFydCA9IDI1KQ0KDQojIEFkZCBjbHVzdGVyIGxhYmVscw0Kc3RhdGlvbl9wcm9maWxlcyRzdGF0aW9uX3R5cGUgPC0gc3RhdGlvbl9jbHVzdGVycyRjbHVzdGVyDQoNCiMgSW50ZXJwcmV0IGNsdXN0ZXJzDQpjbHVzdGVyX3N1bW1hcnkgPC0gdHJhaW4gJT4lDQogIGxlZnRfam9pbigNCiAgICBzdGF0aW9uX3Byb2ZpbGVzICU+JSBzZWxlY3Qoc3RhcnRfc3RhdGlvbiwgc3RhdGlvbl90eXBlKSwNCiAgICBieSA9ICJzdGFydF9zdGF0aW9uIg0KICApICU+JQ0KICBncm91cF9ieShzdGF0aW9uX3R5cGUsIGhvdXIpICU+JQ0KICBzdW1tYXJpemUoYXZnX2RlbWFuZCA9IG1lYW4oVHJpcF9Db3VudCksIC5ncm91cHMgPSAiZHJvcCIpDQoNCiAjVmlzdWFsaXplIGNsdXN0ZXIgcGF0dGVybnMNCmdncGxvdChjbHVzdGVyX3N1bW1hcnksIGFlcyh4ID0gaG91ciwgeSA9IGF2Z19kZW1hbmQsIGNvbG9yID0gZmFjdG9yKHN0YXRpb25fdHlwZSkpKSArDQogIGdlb21fbGluZShsaW5ld2lkdGggPSAxLjIpICsNCiAgc2NhbGVfY29sb3JfbWFudWFsKA0KICAgIHZhbHVlcyA9IGMoIjEiID0gcGFsZXR0ZTVbNV0sICIyIiA9IHBhbGV0dGU1WzNdLCAiMyIgPSBwYWxldHRlNVsxXSksDQogICAgbmFtZSA9ICJTdGF0aW9uIFR5cGUiLA0KICAgIGxhYmVscyA9IGMoIlR5cGUgMSIsICJUeXBlIDIiLCAiVHlwZSAzIikNCiAgKSArDQogIGxhYnMoDQogICAgdGl0bGUgPSAiU3RhdGlvbiBUeXBlcyBCYXNlZCBvbiBUZW1wb3JhbCBEZW1hbmQgUGF0dGVybnMiLA0KICAgIHN1YnRpdGxlID0gIkstbWVhbnMgY2x1c3RlcmluZyByZXZlYWxzIGRpc3RpbmN0IHVzYWdlIHByb2ZpbGVzIiwNCiAgICB4ID0gIkhvdXIgb2YgRGF5IiwNCiAgICB5ID0gIkF2ZXJhZ2UgVHJpcHMgcGVyIFN0YXRpb24iDQogICkgKw0KICBwbG90VGhlbWUgKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIikNCmBgYA0KDQpgYGB7ciBzdGF0aW9uX2NsdXN0ZXJpbmcyLCBlY2hvID0gVFJVRSwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlPUZBTFNFLCBjYWNoZSA9IFRSVUV9DQojIExhYmVsIHRoZSBjbHVzdGVycyBiYXNlZCBvbiBwYXR0ZXJucw0Kc3RhdGlvbl9wcm9maWxlcyA8LSBzdGF0aW9uX3Byb2ZpbGVzICU+JQ0KICBtdXRhdGUoDQogICAgc3RhdGlvbl90eXBlX2xhYmVsID0gY2FzZV93aGVuKA0KICAgICAgc3RhdGlvbl90eXBlID09IDEgfiAiQ29tbXV0ZXIgSHViIiwgICAgICAjIEhpZ2ggQU0vUE0gcGVha3MNCiAgICAgIHN0YXRpb25fdHlwZSA9PSAyIH4gIlJlc2lkZW50aWFsIiwgICAgICAgIyBNb2RlcmF0ZSwgc3ByZWFkIG91dA0KICAgICAgc3RhdGlvbl90eXBlID09IDMgfiAiTG93IEFjdGl2aXR5IiAgICAgICAjIENvbnNpc3RlbnRseSBsb3cNCiAgICApDQogICkNCg0KIyBNYXAgc3RhdGlvbiB0eXBlcw0Kc3RhdGlvbl9tYXBfZGF0YSA8LSB0cmFpbiAlPiUNCiAgZGlzdGluY3Qoc3RhcnRfc3RhdGlvbiwgc3RhcnRfbGF0LngsIHN0YXJ0X2xvbi54KSAlPiUNCiAgbGVmdF9qb2luKHN0YXRpb25fcHJvZmlsZXMgJT4lIHNlbGVjdChzdGFydF9zdGF0aW9uLCBzdGF0aW9uX3R5cGVfbGFiZWwpLA0KICAgICAgICAgICAgYnkgPSAic3RhcnRfc3RhdGlvbiIpDQoNCmdncGxvdCgpICsNCiAgZ2VvbV9zZihkYXRhID0gcGhpbGx5X2NlbnN1cywgZmlsbCA9ICIjZjBmMGYwIiwgY29sb3IgPSAid2hpdGUiLCBsaW5ld2lkdGggPSAwLjMpICsNCiAgZ2VvbV9wb2ludCgNCiAgICBkYXRhID0gc3RhdGlvbl9tYXBfZGF0YSwNCiAgICBhZXMoeCA9IHN0YXJ0X2xvbi54LCB5ID0gc3RhcnRfbGF0LngsIGNvbG9yID0gc3RhdGlvbl90eXBlX2xhYmVsKSwNCiAgICBzaXplID0gMywgYWxwaGEgPSAwLjcNCiAgKSArDQogIHNjYWxlX2NvbG9yX21hbnVhbCgNCiAgICB2YWx1ZXMgPSBjKA0KICAgICAgIkNvbW11dGVyIEh1YiIgPSBwYWxldHRlNVs1XSwNCiAgICAgICJSZXNpZGVudGlhbCIgPSBwYWxldHRlNVszXSwNCiAgICAgICJMb3cgQWN0aXZpdHkiID0gcGFsZXR0ZTVbMV0NCiAgICApLA0KICAgIG5hbWUgPSAiU3RhdGlvbiBUeXBlIg0KICApICsNCiAgbGFicygNCiAgICB0aXRsZSA9ICJTdGF0aW9uIFR5cGVzIEFjcm9zcyBQaGlsYWRlbHBoaWEiLA0KICAgIHN1YnRpdGxlID0gIkNsdXN0ZXJlZCBieSB0ZW1wb3JhbCBkZW1hbmQgcGF0dGVybnMiDQogICkgKw0KICBjb29yZF9zZih4bGltID0gYygtNzUuMjUsIC03NS4wMCksIHlsaW0gPSBjKDM5Ljg4LCA0MC4wOCkpICsNCiAgbWFwVGhlbWUNCmBgYA0KDQoNCi0tLQ0KDQojIE1vZGVsIEltcHJvdmVtZW50cw0KDQpOb3cgd2UnbGwgdGVzdCB3aGV0aGVyIG91ciB0aHJlZSBlbmdpbmVlcmVkIGZlYXR1cmVzIGFjdHVhbGx5IGltcHJvdmUgcHJlZGljdGlvbnMuDQoNCiMjIE1vZGVsIDY6IEFkZCBFbmdpbmVlcmVkIEZlYXR1cmVzDQoNCldlIGFkZCBvdXIgdGhyZWUgbmV3IGZlYXR1cmVzIHRvIE1vZGVsIDUgKG91ciBiZXN0IE9MUyBtb2RlbCBzbyBmYXIpOg0KDQoxLiAqKnJlY2VudF9yYWluKio6IEJpbmFyeSBpbmRpY2F0b3IgaWYgcHJlY2lwaXRhdGlvbiBvY2N1cnJlZCBpbiB0aGUgbGFzdCAzIGhvdXJzDQoyLiAqKm5lYXJfdW5pdmVyc2l0eSoqOiBCaW5hcnkgaW5kaWNhdG9yIGlmIHN0YXRpb24gaXMgd2l0aGluIDAuNSBtaWxlcyBvZiBQZW5uLCBEcmV4ZWwsIG9yIFRlbXBsZQ0KMy4gKipzdGF0aW9uX3R5cGVfbGFiZWwqKjogQ2F0ZWdvcmljYWwgdmFyaWFibGUgZnJvbSBrLW1lYW5zIGNsdXN0ZXJpbmcgKENvbW11dGVyIEh1YiwgUmVzaWRlbnRpYWwsIExvdyBBY3Rpdml0eSkNCiAgDQpgYGB7ciBtb2RlbDZfbmV3X2ZlYXR1cmVzLCBlY2hvID0gVFJVRSwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlPUZBTFNFLCBjYWNoZSA9IFRSVUV9DQojIEFkZCB0byB0cmFpbi90ZXN0DQp0cmFpbiA8LSBzdHVkeV9wYW5lbF9jb21wbGV0ZSAlPiUNCiAgZmlsdGVyKHdlZWsgPCAxMCwgc3RhcnRfc3RhdGlvbiAlaW4lIGNvbW1vbl9zdGF0aW9ucykNCg0KdGVzdCA8LSBzdHVkeV9wYW5lbF9jb21wbGV0ZSAlPiUNCiAgZmlsdGVyKHdlZWsgPj0gMTAsIHN0YXJ0X3N0YXRpb24gJWluJSBjb21tb25fc3RhdGlvbnMpDQoNCiMgUmVjcmVhdGUgZG90d19zaW1wbGUgZm9yIGJvdGgNCnRyYWluIDwtIHRyYWluICU+JQ0KICBtdXRhdGUoZG90d19zaW1wbGUgPSBmYWN0b3IoZG90dywgbGV2ZWxzID0gYygiTW9uIiwgIlR1ZSIsICJXZWQiLCAiVGh1IiwgIkZyaSIsICJTYXQiLCAiU3VuIikpKQ0KY29udHJhc3RzKHRyYWluJGRvdHdfc2ltcGxlKSA8LSBjb250ci50cmVhdG1lbnQoNykNCg0KdGVzdCA8LSB0ZXN0ICU+JQ0KICBtdXRhdGUoZG90d19zaW1wbGUgPSBmYWN0b3IoZG90dywgbGV2ZWxzID0gYygiTW9uIiwgIlR1ZSIsICJXZWQiLCAiVGh1IiwgIkZyaSIsICJTYXQiLCAiU3VuIikpKQ0KY29udHJhc3RzKHRlc3QkZG90d19zaW1wbGUpIDwtIGNvbnRyLnRyZWF0bWVudCg3KQ0KDQojIEFERCBUSElTIEhFUkUgLSBSZS1qb2luIHVuaXZlcnNpdHkgZGF0YSBhbmQgc3RhdGlvbiBjbHVzdGVycw0KdHJhaW4gPC0gdHJhaW4gJT4lDQogIGxlZnRfam9pbihzdGF0aW9uX3VuaV9kaXN0ICU+JSBkcGx5cjo6c2VsZWN0KHN0YXJ0X3N0YXRpb24sIGRpc3RfdG9fbmVhcmVzdF91bmksIG5lYXJfdW5pdmVyc2l0eSksDQogICAgICAgICAgICBieSA9ICJzdGFydF9zdGF0aW9uIikgJT4lDQogIGxlZnRfam9pbihzdGF0aW9uX3Byb2ZpbGVzICU+JSBkcGx5cjo6c2VsZWN0KHN0YXJ0X3N0YXRpb24sIHN0YXRpb25fdHlwZV9sYWJlbCksDQogICAgICAgICAgICBieSA9ICJzdGFydF9zdGF0aW9uIikNCg0KdGVzdCA8LSB0ZXN0ICU+JQ0KICBsZWZ0X2pvaW4oc3RhdGlvbl91bmlfZGlzdCAlPiUgZHBseXI6OnNlbGVjdChzdGFydF9zdGF0aW9uLCBkaXN0X3RvX25lYXJlc3RfdW5pLCBuZWFyX3VuaXZlcnNpdHkpLA0KICAgICAgICAgICAgYnkgPSAic3RhcnRfc3RhdGlvbiIpICU+JQ0KICBsZWZ0X2pvaW4oc3RhdGlvbl9wcm9maWxlcyAlPiUgZHBseXI6OnNlbGVjdChzdGFydF9zdGF0aW9uLCBzdGF0aW9uX3R5cGVfbGFiZWwpLA0KICAgICAgICAgICAgYnkgPSAic3RhcnRfc3RhdGlvbiIpDQoNCg0KDQptb2RlbDYgPC0gbG0oDQogIFRyaXBfQ291bnQgfiBhcy5mYWN0b3IoaG91cikgKyBkb3R3X3NpbXBsZSArIFRlbXBlcmF0dXJlICsgUHJlY2lwaXRhdGlvbiArDQogICAgbGFnMUhvdXIgKyBsYWczSG91cnMgKyBsYWcxZGF5ICsgcnVzaF9ob3VyICsgDQogICAgTWVkX0luYy54ICsgUGVyY2VudF9UYWtpbmdfVHJhbnNpdC54ICsgUGVyY2VudF9XaGl0ZS54ICsNCiAgICBhcy5mYWN0b3Ioc3RhcnRfc3RhdGlvbikgKw0KICAgIHJ1c2hfaG91ciAqIHdlZWtlbmQgKw0KICAgIHJlY2VudF9yYWluICsNCiAgICBuZWFyX3VuaXZlcnNpdHkgKw0KICAgIHN0YXRpb25fdHlwZV9sYWJlbCwNCiAgZGF0YSA9IHRyYWluDQopDQoNCg0KDQpjYXQoIk1vZGVsIDYgQWRqIFItc3F1YXJlZDoiLCByb3VuZChzdW1tYXJ5KG1vZGVsNikkYWRqLnIuc3F1YXJlZCwgNCksICJcbiIpDQoNCiMgQ2FsY3VsYXRlIE1BRSBvbiB0ZXN0IHNldA0KdGVzdCRwcmVkNiA8LSBwcmVkaWN0KG1vZGVsNiwgbmV3ZGF0YSA9IHRlc3QpDQptYWVfbW9kZWw2IDwtIG1lYW4oYWJzKHRlc3QkVHJpcF9Db3VudCAtIHRlc3QkcHJlZDYpLCBuYS5ybSA9IFRSVUUpDQpjYXQoIk1vZGVsIDYgTUFFOiIsIHJvdW5kKG1hZV9tb2RlbDYsIDMpLCAiXG4iKQ0KDQojIENvbXBhcmUgdG8gYmVzdCBtb2RlbCAoTW9kZWwgMikNCmNhdCgiXG5Db21wYXJpc29uIHRvIE1vZGVsIDIgKGJlc3QgYmFzZWxpbmUpOlxuIikNCmNhdCgiTW9kZWwgMiBNQUU6IDAuNTc3XG4iKQ0KY2F0KCJNb2RlbCA2IE1BRToiLCByb3VuZChtYWVfbW9kZWw2LCAzKSwgIlxuIikNCmNhdCgiRGlmZmVyZW5jZToiLCByb3VuZChtYWVfbW9kZWw2IC0gMC41NzcsIDMpLCAiKHBvc2l0aXZlID0gd29yc2UpXG4iKQ0KYGBgDQoNCkRlc3BpdGUgdGhlc2UgdGhlb3JldGljYWxseS1tb3RpdmF0ZWQgZmVhdHVyZXMsIE1vZGVsIDYgKE9MUyB3aXRoIG5ldyBmZWF0dXJlcykgYWNoaWV2ZWQgYW4gTUFFIG9mIDAuODMy4oCUYWN0dWFsbHkgd29yc2UgdGhhbiB0aGUgc2ltcGxlIHRlbXBvcmFsIGxhZyBtb2RlbCAoMC41NzcpLiBUaGlzIHN1Z2dlc3RzIG92ZXJmaXR0aW5nOiB0aGUgbmV3IGZlYXR1cmVzIG1heSBjYXB0dXJlIHBhdHRlcm5zIHByZXNlbnQgaW4gdGhlIHRyYWluaW5nIHBlcmlvZCB0aGF0IGRvbid0IGdlbmVyYWxpemUgdG8gdGhlIHRlc3QgcGVyaW9kLg0KDQojIyBNb2RlbCA3OiBQb2lzc29uIA0KYGBge3IgbW9kZWw3X3BvaXNzb24sIGVjaG8gPSBUUlVFLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2U9RkFMU0UsIGNhY2hlID0gVFJVRX0NCiMgUG9pc3NvbiByZWdyZXNzaW9uIC0gYmV0dGVyIGZvciBjb3VudCBkYXRhDQptb2RlbDdfcG9pc3NvbiA8LSBnbG0oDQogIFRyaXBfQ291bnQgfiBhcy5mYWN0b3IoaG91cikgKyBkb3R3X3NpbXBsZSArIFRlbXBlcmF0dXJlICsgUHJlY2lwaXRhdGlvbiArDQogICAgbGFnMUhvdXIgKyBsYWczSG91cnMgKyBsYWcxZGF5ICsgcnVzaF9ob3VyICsgDQogICAgTWVkX0luYy54ICsgUGVyY2VudF9UYWtpbmdfVHJhbnNpdC55ICsgUGVyY2VudF9XaGl0ZS55ICsNCiAgICBhcy5mYWN0b3Ioc3RhcnRfc3RhdGlvbikgKw0KICAgIHJ1c2hfaG91ciAqIHdlZWtlbmQgKw0KICAgIHJlY2VudF9yYWluICsNCiAgICBuZWFyX3VuaXZlcnNpdHkgKw0KICAgIHN0YXRpb25fdHlwZV9sYWJlbCwNCiAgZGF0YSA9IHRyYWluLA0KICBmYW1pbHkgPSBwb2lzc29uKGxpbmsgPSAibG9nIikNCikNCg0KIyBDaGVjayBmb3Igb3ZlcmRpc3BlcnNpb24NCmRpc3BlcnNpb24gPC0gbW9kZWw3X3BvaXNzb24kZGV2aWFuY2UgLyBtb2RlbDdfcG9pc3NvbiRkZi5yZXNpZHVhbA0KY2F0KCJQb2lzc29uIERpc3BlcnNpb24gUmF0aW86Iiwgcm91bmQoZGlzcGVyc2lvbiwgMiksICJcbiIpDQpjYXQoIihTaG91bGQgYmUgfjE7IGlmID4+IDEsIGRhdGEgaXMgb3ZlcmRpc3BlcnNlZClcbiIpDQoNCmBgYA0KDQpUaGUgUG9pc3NvbiByZWdyZXNzaW9uIChNQUUgPSAwLjgyNCkgcGVyZm9ybWVkIGJldHRlciB0aGFuIE1vZGVsIDYgYnV0IHN0aWxsIHdvcnNlIHRoYW4gTW9kZWwgMi4gVGhlIFBvaXNzb24gcmVncmVzc2lvbiB5aWVsZGVkIGEgZGlzcGVyc2lvbiByYXRpbyBvZiAwLjM0LCBpbmRpY2F0aW5nIHRoZSBkYXRhIGlzIHVuZGVyZGlzcGVyc2VkLiBUcmlwIGNvdW50cyBoYXZlIGxlc3MgdmFyaWFiaWxpdHkgdGhhbiBhIFBvaXNzb24gZGlzdHJpYnV0aW9uIHdvdWxkIHByZWRpY3QuIFRoaXMgc3VnZ2VzdHMgYmlrZSBzaGFyZSBkZW1hbmQgaXMgZmFpcmx5IHJlZ3VsYXIgYW5kIHByZWRpY3RhYmxlIGF0IHRoZSBob3VybHkgbGV2ZWwsIGxpa2VseSBiZWNhdXNlIHN0cm9uZyB0ZW1wb3JhbCBwYXR0ZXJucyAoY29tbXV0ZSB0aW1lcywgd2VhdGhlciBlZmZlY3RzKSBjb25zdHJhaW4gdGhlIHZhcmlhbmNlLg0KDQojIyBDb21wYXJpc29uDQpgYGB7ciBmaW5hbF9tYWVfY29tcGFyaXNvbiwgZWNobyA9IFRSVUUsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZT1GQUxTRSwgY2FjaGUgPSBUUlVFfQ0KdGVzdCA8LSB0ZXN0ICU+JQ0KICBtdXRhdGUoZG90d19zaW1wbGUgPSBmYWN0b3IoZG90dywgbGV2ZWxzID0gYygiTW9uIiwgIlR1ZSIsICJXZWQiLCAiVGh1IiwgIkZyaSIsICJTYXQiLCAiU3VuIikpKQ0KDQpjb250cmFzdHModGVzdCRkb3R3X3NpbXBsZSkgPC0gY29udHIudHJlYXRtZW50KDcpDQoNCnRlc3QgPC0gdGVzdCAlPiUNCiAgbXV0YXRlKA0KICAgIHByZWQxID0gcHJlZGljdChtb2RlbDEsIG5ld2RhdGEgPSB0ZXN0KSwNCiAgICBwcmVkMiA9IHByZWRpY3QobW9kZWwyLCBuZXdkYXRhID0gdGVzdCksDQogICAgcHJlZDMgPSBwcmVkaWN0KG1vZGVsMywgbmV3ZGF0YSA9IHRlc3QpLA0KICAgIHByZWQ0ID0gcHJlZGljdChtb2RlbDQsIG5ld2RhdGEgPSB0ZXN0KSwNCiAgICBwcmVkNSA9IHByZWRpY3QobW9kZWw1LCBuZXdkYXRhID0gdGVzdCksDQogICAgcHJlZDYgPSBwcmVkaWN0KG1vZGVsNiwgbmV3ZGF0YSA9IHRlc3QpLA0KICAgIHByZWQ3X3BvaXNzb24gPSBwcmVkaWN0KG1vZGVsN19wb2lzc29uLCBuZXdkYXRhID0gdGVzdCwgdHlwZSA9ICJyZXNwb25zZSIpDQogICkNCg0KbWFlX3Jlc3VsdHMgPC0gZGF0YS5mcmFtZSgNCiAgTW9kZWwgPSBjKA0KICAgICIxLiBUaW1lICsgV2VhdGhlciIsDQogICAgIjIuICsgVGVtcG9yYWwgTGFncyIsDQogICAgIjMuICsgRGVtb2dyYXBoaWNzIiwNCiAgICAiNC4gKyBTdGF0aW9uIEZFIiwNCiAgICAiNS4gKyBSdXNoIEhvdXIgSW50ZXJhY3Rpb24iLA0KICAgICI2LiArIE5ldyBGZWF0dXJlcyAoT0xTKSIsDQogICAgIjcuIFBvaXNzb24iDQogICksDQogIE1BRSA9IGMoDQogICAgbWVhbihhYnModGVzdCRUcmlwX0NvdW50IC0gdGVzdCRwcmVkMSksIG5hLnJtID0gVFJVRSksDQogICAgbWVhbihhYnModGVzdCRUcmlwX0NvdW50IC0gdGVzdCRwcmVkMiksIG5hLnJtID0gVFJVRSksDQogICAgbWVhbihhYnModGVzdCRUcmlwX0NvdW50IC0gdGVzdCRwcmVkMyksIG5hLnJtID0gVFJVRSksDQogICAgbWVhbihhYnModGVzdCRUcmlwX0NvdW50IC0gdGVzdCRwcmVkNCksIG5hLnJtID0gVFJVRSksDQogICAgbWVhbihhYnModGVzdCRUcmlwX0NvdW50IC0gdGVzdCRwcmVkNSksIG5hLnJtID0gVFJVRSksDQogICAgbWVhbihhYnModGVzdCRUcmlwX0NvdW50IC0gdGVzdCRwcmVkNiksIG5hLnJtID0gVFJVRSksDQogICAgbWVhbihhYnModGVzdCRUcmlwX0NvdW50IC0gdGVzdCRwcmVkN19wb2lzc29uKSwgbmEucm0gPSBUUlVFKQ0KICApDQopDQoNCmthYmxlKG1hZV9yZXN1bHRzLCANCiAgICAgIGRpZ2l0cyA9IDMsDQogICAgICBjYXB0aW9uID0gIk1lYW4gQWJzb2x1dGUgRXJyb3IgYnkgTW9kZWwgKFRlc3QgU2V0KSIsDQogICAgICBjb2wubmFtZXMgPSBjKCJNb2RlbCIsICJNQUUgKHRyaXBzKSIpKSAlPiUNCiAga2FibGVfc3R5bGluZyhib290c3RyYXBfb3B0aW9ucyA9IGMoInN0cmlwZWQiLCAiaG92ZXIiKSkNCmBgYA0KDQpEZXNwaXRlIHRoZSBpbmNsdXNpb24gb2YgbmV3IHNwYXRpYWwgZmVhdHVyZXMsIHRoZSBzZWNvbmQgbW9kZWwgaGlnaGxpZ2h0aW5nIHRoZSB0ZW1wb3JhbCBsYWdzIHlpZWxkIHRoZSBsb3dlc3QgTUFFICgwLjU3NyksIG1ha2luZyBpdCB0aGUgYmVzdCBtb2RlbCBvZiB0aGUgYW5hbHlzaXMuIFRoZSBoaWdoIE1BRSBvZiBuZXcgZmVhdHVyZSByZWdyZXNzaW9ucyBjb3VsZCBiZSBpbmRpY2F0aXZlIG9mIG92ZXJmaXR0aW5nLg0KDQojIyBQYXJ0IDQ6IENyaXRpY2FsIFJlZmxlY3Rpb24gDQoNCkxldCdzIHJldmlldyB0aGUgaW1wbGljYXRpb25zIG9mIHRoZXNlIGZpbmRpbmdzLiANCg0KYGBge3IgcGFydDRfZGlhZ25vc3RpY3MsIGVjaG8gPSBUUlVFLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2U9RkFMU0V9DQoNCiMgTWFrZSBzdXJlIHByZWQyIGV4aXN0cyBmb3IgYWxsIGRpYWdub3N0aWNzDQp0ZXN0JHByZWQyIDwtIHByZWRpY3QobW9kZWwyLCBuZXdkYXRhID0gdGVzdCkNCg0KDQojIFdoYXQncyB0aGUgZGlzdHJpYnV0aW9uIG9mIGFjdHVhbCB0cmlwIGNvdW50cz8NCmNhdCgiVHJpcCBDb3VudCBTdW1tYXJ5OlxuIikNCnN1bW1hcnkodGVzdCRUcmlwX0NvdW50KQ0KDQpjYXQoIlxuXG5UcmlwIGNvdW50IGRpc3RyaWJ1dGlvbjpcbiIpDQp0YWJsZShjdXQodGVzdCRUcmlwX0NvdW50LCBicmVha3MgPSBjKC0xLCAwLCAyLCA1LCAxMCwgMTUsIEluZiksDQogICAgICAgICAgbGFiZWxzID0gYygiMCIsICIxLTIiLCAiMy01IiwgIjYtMTAiLCAiMTEtMTUiLCAiMTYrIikpKQ0KDQojIFdoYXQgJSBvZiBzdGF0aW9uLWhvdXJzIGhhdmUgMCB0cmlwcz8NCmNhdCgiXG5cblBlcmNlbnQgb2Ygc3RhdGlvbi1ob3VycyB3aXRoIDAgdHJpcHM6IiwgDQogICAgcm91bmQobWVhbih0ZXN0JFRyaXBfQ291bnQgPT0gMCkgKiAxMDAsIDEpLCAiJVxuIikNCg0KIyBBdmVyYWdlIGRlbWFuZCBhdCBkaWZmZXJlbnQgc3RhdGlvbiB0eXBlcw0KY2F0KCJcblxuQXZlcmFnZSB0cmlwcyBieSBzdGF0aW9uIHR5cGU6XG4iKQ0KdGVzdCAlPiUNCiAgZ3JvdXBfYnkoc3RhdGlvbl90eXBlX2xhYmVsKSAlPiUNCiAgc3VtbWFyaXplKA0KICAgIGF2Z190cmlwcyA9IG1lYW4oVHJpcF9Db3VudCksDQogICAgbWVkaWFuX3RyaXBzID0gbWVkaWFuKFRyaXBfQ291bnQpLA0KICAgIG1heF90cmlwcyA9IG1heChUcmlwX0NvdW50KQ0KICApICU+JQ0KICBwcmludCgpDQoNCiMgUmVsYXRpdmUgZXJyb3IgYXQgaGlnaCB2cyBsb3cgZGVtYW5kIGhvdXJzDQpjYXQoIlxuXG5NQUUgYnkgZGVtYW5kIGxldmVsOlxuIikNCnRlc3QgJT4lDQogIG11dGF0ZSgNCiAgICBkZW1hbmRfbGV2ZWwgPSBjYXNlX3doZW4oDQogICAgICBUcmlwX0NvdW50ID09IDAgfiAiWmVybyB0cmlwcyIsDQogICAgICBUcmlwX0NvdW50IDw9IDIgfiAiTG93ICgxLTIpIiwNCiAgICAgIFRyaXBfQ291bnQgPD0gNSB+ICJNZWRpdW0gKDMtNSkiLA0KICAgICAgVFJVRSB+ICJIaWdoICg2KykiDQogICAgKSwNCiAgICBwcmVkMiA9IHByZWRpY3QobW9kZWwyLCBuZXdkYXRhID0gY3VyX2RhdGEoKSksICAjIHJlY2FsY3VsYXRlIGlubGluZQ0KICAgIGVycm9yID0gYWJzKFRyaXBfQ291bnQgLSBwcmVkMikNCiAgKSAlPiUNCiAgZ3JvdXBfYnkoZGVtYW5kX2xldmVsKSAlPiUNCiAgc3VtbWFyaXplKA0KICAgIG4gPSBuKCksDQogICAgYXZnX2FjdHVhbCA9IG1lYW4oVHJpcF9Db3VudCksDQogICAgTUFFID0gbWVhbihlcnJvciwgbmEucm0gPSBUUlVFKSwNCiAgICByZWxhdGl2ZV9lcnJvcl9wY3QgPSBtZWFuKGVycm9yIC8gcG1heChUcmlwX0NvdW50LCAwLjUpKSAqIDEwMA0KICApDQojIEVycm9yIGR1cmluZyBydXNoIGhvdXIgdnMgbm90DQpjYXQoIlxuXG5NQUUgYnkgdGltZSBwZXJpb2Q6XG4iKQ0KdGVzdCAlPiUNCiAgbXV0YXRlKGVycm9yID0gYWJzKFRyaXBfQ291bnQgLSBwcmVkMikpICU+JQ0KICBncm91cF9ieShydXNoX2hvdXIpICU+JQ0KICBzdW1tYXJpemUoDQogICAgYXZnX3RyaXBzID0gbWVhbihUcmlwX0NvdW50KSwNCiAgICBNQUUgPSBtZWFuKGVycm9yKQ0KICApICU+JQ0KICBwcmludCgpDQpgYGANCg0KKipPcGVyYXRpb25hbCBJbXBsaWNhdGlvbnMqKg0KVGhpcyBtb2RlbCBhY2hpZXZlcyBhIGJlc3QtY2FzZSBNQUUgb2YgMC41NzcgdHJpcHMgcGVyIHN0YXRpb24taG91ciB1c2luZyBvbmx5IHRlbXBvcmFsIGxhZyBmZWF0dXJlcyAoTW9kZWwgMikuIEF0IGZpcnN0IGdsYW5jZSB0aGlzIHNlZW1zIG9wZXJhdGlvbmFsbHkgdXNlZnVsLCBidXQgY29udGV4dCBpcyBjcml0aWNhbDogNjguNiUgb2Ygc3RhdGlvbi1ob3VycyBoYXZlIHplcm8gdHJpcHMsIGFuZCB0aGUgbWVkaWFuIGlzIGFsc28gemVyby4gVGhlIG1lYW4gb2YganVzdCAwLjYgdHJpcHMgcGVyIHN0YXRpb24taG91ciBtZWFucyBtb3N0IHByZWRpY3Rpb25zIGFyZSBmb3IgdmVyeSBsb3ctZGVtYW5kIHNpdHVhdGlvbnMuIFdoZW4gd2UgZXhhbWluZSBNQUUgYnkgZGVtYW5kIGxldmVsLCB0aGUgbW9kZWwgcGVyZm9ybXMgd2VsbCBmb3IgcXVpZXQgcGVyaW9kcyAoTUFFID0gMC4zNSBmb3IgemVyby10cmlwIGhvdXJzKSBidXQgc3RydWdnbGVzIGRyYW1hdGljYWxseSBkdXJpbmcgaGlnaC1kZW1hbmQgcGVyaW9kcyAoTUFFID0gNC45NyBmb3Igc3RhdGlvbnMgd2l0aCA2KyB0cmlwcykuIFRoaXMgImNlaWxpbmcgZWZmZWN0IiBpcyBvcGVyYXRpb25hbGx5IGNvbmNlcm5pbmfigJR0aGUgbW9kZWwgdW5kZXJwcmVkaWN0cyBwcmVjaXNlbHkgd2hlbiBzdG9ja291dCByaXNrIGlzIGhpZ2hlc3QuDQoNCkZvciBJbmRlZ28ncyByZWJhbGFuY2luZyBvcGVyYXRpb25zLCBJIHdvdWxkIHJlY29tbWVuZCBkZXBsb3lpbmcgdGhpcyBtb2RlbCBhcyBhIHNjcmVlbmluZyB0b29sIHJhdGhlciB0aGFuIGFuIGF1dG9tYXRlZCBkZWNpc2lvbi1tYWtlci4gVGhlIG1vZGVsIHJlbGlhYmx5IGlkZW50aWZpZXMgd2hpY2ggc3RhdGlvbnMgd2lsbCByZW1haW4gcXVpZXQsIGZyZWVpbmcgb3BlcmF0aW9ucyBzdGFmZiB0byBmb2N1cyBhdHRlbnRpb24gb24gdGhlIH4zMSUgb2Ygc3RhdGlvbi1ob3VycyB3aXRoIGFjdHVhbCBhY3Rpdml0eS4gSG93ZXZlciwgZm9yIGhpZ2gtdm9sdW1lIHN0YXRpb25zIGR1cmluZyBwZWFrIHBlcmlvZHMsIHByZWRpY3Rpb25zIHNob3VsZCBiZSB0cmVhdGVkIGFzIGxvd2VyIGJvdW5kcyByYXRoZXIgdGhhbiBwb2ludCBlc3RpbWF0ZXMuIEEgcmlzay1hdmVyc2UgZGVwbG95bWVudCBzdHJhdGVneSBtaWdodCBhZGQgYSBidWZmZXIgdG8gcHJlZGljdGlvbnMgYXQgUmVzaWRlbnRpYWwgc3RhdGlvbnMgKHdoaWNoIGF2ZXJhZ2UgMS40OSB0cmlwcy9ob3Vy4oCUbmVhcmx5IGRvdWJsZSBDb21tdXRlciBIdWJzIGF0IDAuODMpIGFuZCBmbGFnIGFueSBzdGF0aW9uIHByZWRpY3RlZCBhYm92ZSAzIHRyaXBzIGFzICJoaWdoIHByaW9yaXR5IiBmb3IgbW9uaXRvcmluZy4NCg0KKipFcXVpdHkgQ29uc2lkZXJhdGlvbnMqKg0KVGhlIGVycm9yIGFuYWx5c2lzIHJldmVhbGVkIHRoYXQgcHJlZGljdGlvbiBlcnJvcnMgZG8gbm90IHN5c3RlbWF0aWNhbGx5IGRpc2FkdmFudGFnZSBsb3dlci1pbmNvbWUgbmVpZ2hib3Job29kcyBvciBjb21tdW5pdGllcyBvZiBjb2xvcuKAlGlmIGFueXRoaW5nLCBlcnJvcnMgYXJlIHNsaWdodGx5IGhpZ2hlciBpbiB3ZWFsdGhpZXIsIHdoaXRlciBhcmVhcyBkdWUgdG8gdGhlaXIgaGlnaGVyIHJpZGVyc2hpcCB2b2x1bWVzLiBUaGUgc3Ryb25nZXN0IGRlbW9ncmFwaGljIHBhdHRlcm4gd2FzIHRyYW5zaXQgdXNhZ2U6IHN0YXRpb25zIGluIHRyYW5zaXQtcmljaCBhcmVhcyAoNjAlKyB0YWtpbmcgdHJhbnNpdCkgaGFkIHN1YnN0YW50aWFsbHkgbG93ZXIgTUFFIHRoYW4gY2FyLWRlcGVuZGVudCBhcmVhcy4gVGhpcyBzdWdnZXN0cyB0aGUgbW9kZWwgd29ya3MgYmVzdCBpbiB0aGUgZGVuc2UgdXJiYW4gY29yZSB3aGVyZSBiaWtlIHNoYXJlIGludGVncmF0ZXMgaW50byBwcmVkaWN0YWJsZSBtdWx0aW1vZGFsIGNvbW11dGluZy4NCg0KRnJvbSBhbiBlcXVpdHkgc3RhbmRwb2ludCwgdGhpcyBwYXR0ZXJuIHJhaXNlcyBjb25jZXJucyBmb3IgcGVyaXBoZXJhbCBuZWlnaGJvcmhvb2RzLiBBcmVhcyB3aXRoIGxvd2VyIHRyYW5zaXQgdXNhZ2XigJR3aGljaCBtYXkgYWxyZWFkeSBoYXZlIGxpbWl0ZWQgdHJhbnNwb3J0YXRpb24gb3B0aW9uc+KAlHJlY2VpdmUgbGVzcyBhY2N1cmF0ZSBwcmVkaWN0aW9ucywgcG90ZW50aWFsbHkgYWZmZWN0aW5nIHNlcnZpY2UgcXVhbGl0eSB3aGVyZSByZWxpYWJsZSBiaWtlIHNoYXJlIGFjY2VzcyBjb3VsZCBwcm92aWRlIHRoZSBtb3N0IGJlbmVmaXQuIFRvIG1pdGlnYXRlIHRoZXNlIHJpc2tzLCBJIHdvdWxkIHJlY29tbWVuZDogKDEpIGVzdGFibGlzaGluZyBtaW5pbXVtIHNlcnZpY2UgbGV2ZWxzIGZvciBhbGwgc3RhdGlvbnMgcmVnYXJkbGVzcyBvZiBwcmVkaWN0ZWQgZGVtYW5kOyAoMikgcmVndWxhcmx5IGF1ZGl0aW5nIG1vZGVsIHBlcmZvcm1hbmNlIGFjcm9zcyBkZW1vZ3JhcGhpYyBncm91cHM7ICgzKSBhcHBseWluZyBjb25zZXJ2YXRpdmUgcHJlZGljdGlvbiBidWZmZXJzIGluIHVuZGVyc2VydmVkIGFyZWFzIHRvIGF2b2lkIHN5c3RlbWF0aWMgdW5kZXItYWxsb2NhdGlvbjsgYW5kICg0KSBzdXBwbGVtZW50aW5nIGFsZ29yaXRobWljIHByZWRpY3Rpb25zIHdpdGggY29tbXVuaXR5IGlucHV0IGFib3V0IHdoZXJlIGJpa2VzIGFyZSBhY3R1YWxseSBuZWVkZWQuDQoNCioqTW9kZWwgTGltaXRhdGlvbnMqKg0KU2V2ZXJhbCBpbXBvcnRhbnQgcGF0dGVybnMgcmVtYWluIG91dHNpZGUgdGhpcyBtb2RlbCdzIHByZWRpY3RpdmUgY2FwYWNpdHkuIFRoZSBzdGF0aW9uIHR5cGUgYW5hbHlzaXMgcmV2ZWFsZWQgdW5leHBlY3RlZCByZXN1bHRzOiAiTG93IEFjdGl2aXR5IiBzdGF0aW9ucyBoYWQgdGhlIGhpZ2hlc3QgbWF4aW11bSB0cmlwcyAoMjgpLCBzdWdnZXN0aW5nIG9jY2FzaW9uYWwgZGVtYW5kIHNwaWtlcyB0aGUgbW9kZWwgY2Fubm90IGFudGljaXBhdGXigJRsaWtlbHkgZnJvbSBzcGVjaWFsIGV2ZW50cyBvciBpcnJlZ3VsYXIgdXNhZ2UgcGF0dGVybnMuIFRoZSBtb2RlbCBhbHNvIGNhbm5vdCBhY2NvdW50IGZvciBQaGlsbGllcyBnYW1lcywgdW5pdmVyc2l0eSBncmFkdWF0aW9ucywgU0VQVEEgZGlzcnVwdGlvbnMsIG9yIGNvbnN0cnVjdGlvbiBkZXRvdXJzIHRoYXQgdGVtcG9yYXJpbHkgc2hpZnQgZGVtYW5kLg0KDQpQZXJoYXBzIG1vc3QgZnVuZGFtZW50YWxseSwgdGhlIG1vZGVsIGFzc3VtZXMgZnV0dXJlIGRlbWFuZCBtaXJyb3JzIGhpc3RvcmljYWwgcGF0dGVybnMuIFdpdGggNjguNiUgb2Ygb2JzZXJ2YXRpb25zIGJlaW5nIHplcm9zLCB0aGUgbW9kZWwgaXMgZXNzZW50aWFsbHkgbGVhcm5pbmcgIm1vc3Qgc3RhdGlvbi1ob3VycyBhcmUgcXVpZXQi4oCUd2hpY2ggaXMgdHJ1ZSBidXQgbm90IG9wZXJhdGlvbmFsbHkgdXNlZnVsLiBUaGUgcmVhbCB2YWx1ZSB3b3VsZCBjb21lIGZyb20gYWNjdXJhdGVseSBwcmVkaWN0aW5nIHRoZSAxLTIlIG9mIHN0YXRpb24taG91cnMgd2l0aCA2KyB0cmlwcywgYnV0IHRoYXQncyBleGFjdGx5IHdoZXJlIE1BRSBpcyB3b3JzdCAoNC45NyB0cmlwcykuIFdpdGggYWRkaXRpb25hbCB0aW1lIGFuZCBkYXRhLCBJIHdvdWxkIHByaW9yaXRpemU6IGJ1aWxkaW5nIHNlcGFyYXRlIG1vZGVscyBmb3IgaGlnaC12b2x1bWUgdnMuIGxvdy12b2x1bWUgc3RhdGlvbnM7IGluY29ycG9yYXRpbmcgZXZlbnQgY2FsZW5kYXJzOyB0ZXN0aW5nIGNsYXNzaWZpY2F0aW9uIGFwcHJvYWNoZXMgKHdpbGwgdGhpcyBzdGF0aW9uLWhvdXIgZXhjZWVkIDUgdHJpcHM/IHllcy9ubykgcmF0aGVyIHRoYW4gcmVncmVzc2lvbjsgYW5kIGFkZGluZyByZWFsLXRpbWUgZG9jayBhdmFpbGFiaWxpdHkgdG8gaWRlbnRpZnkgc3RhdGlvbnMgYWxyZWFkeSBhcHByb2FjaGluZyBjYXBhY2l0eSBjb25zdHJhaW50cy4NCg0KDQo=