Presentation Video

Introduction

Project option 7: Forecasting parking demand

Use Case & Motivation

X-Parks-the-Spot is an app that finds the perfect parking spot for the user. The perfect parking spot, to put it bluntly, is the that’s available closest to the user’s destination. Currently, the app only operates in the pilot city of San Francisco, due to the city’s availability of data.

The current version of the X-Parks-the-Spot app provides the user with the block of parking spots that has the highest probability of having an open spot, considering the user’s:

  1. destination neighborhood,

  2. day of the week, and

  3. time period of the day (e.g. 9am to noon).

The development team formed a prediction model based on 2019 parking meter transactions in the City of San Francisco. Specifically, this is a binomial logistic model that predicts if the block’s occupancy rate – e.g. the aggregated occupation time of all the block’s parking meters over the aggregated possible time – will be lower or higher than 50%. 50% Parking Meter Occupancy in a time period is a safe number to assume that there will be openings.

This approach is helpful as it gives the app user a safe estimate of what street park to park on. Our app’s funding is based on both public & private investment; as a result, it is in our best interest to not waste an app user’s time by checking multiple blocks for parking.

Method

The general method is to

  1. prepare data that gives us the occupancy rates for park of meters by time of day & block,

  2. find variables that can predict the occupancy rate,

  3. fit a model based on the occupancy rates

  4. evaluate the fit of the model

The goal is to find blocks that have the lowest probability of being below 50% occupancy for a time period.

Since there is a wide range of neighborhoods and time periods, we aim for the model to both be accurate and generalizable. If the model is generalizable enough, it could potentially be applied to future cities.

Data

Describe the data you used.

The observations are parking meter transactions from the City of San Francisco for the year 2019. First, the meter transactions are aggregated by the meter, day, and time period (e.g. 9am to noon) to find (1) the amount of time occupied, and (2) the total possible meter time. Secondly, those grouped meter observations are then aggregated by the street block – giving the block’s total time occupied.

The dataset is fairly large, raw, and aggregated from parking meter vendors which requires a methodical cleaning process. Our focus only looks at meter transactions and time periods that are:

  1. meters for general use (e.g. no motorcycle or commercial parking),

  2. transactions that are not used for testing or administration,

  3. time periods that are not charging for parking (e.g. not on a holiday or shutdown for construction).

Besides cleaning the dependent variables, we will (1) import external predictors (e.g. census data) and (2) further engineer the parking transactions (e.g. lag time).

Import

Parking

As foreshadowed, the bulk of this project was in cleaning up the parking transactions to be measured by both time periods and their blocks. The size of this cleaning required pre-processing to be performed in python scripts.

Before grouping by districts, the meter transactions ranged in sizes from 3-10 million rows. The City of San Francisco has about 18,000 parking meters – so measuring for all of 2019 makes it exponentially larger.

The complicated cleaning process isn’t the scale but organizing the transaction’s start & end times. To bin by time periods, the start & end times have to first be cropped by the periods. Then the periods have to account for potentially overlapping transactions for the same meter. Before accounting for the overlaps, many meters would have time periods of over 100% occupation.

The python scripts cleaned the transactions in this order:

  1. remove likely administrative meters;

  2. split each transaction into a start & stop row;

  3. aggregate these transactions by the meter & day;

  4. sort the start & stops so they can be (A) cleaned of overlaps, (B) split by time periods, (C) re-paired, & (D) measured in hours;

  5. the measured transaction rows are then grouped by meter, time period, day, and, finally, by street block;

  6. the script also adds bins if there weren’t any transactions but there was parking allowed.

In python, the scripts primarily use the pandas and dask dataframe packages to manage the data. Originally the scripts processed in 1-5 hours (arguably the lowest point of my semester was when they ran and I couldn’t use my laptop), but were cut down to 5 minutes.


path =  'C:/Users/nelms/Documents/Penn/MUSA-508/MUSA508_FINAL_SFPARK/data/block_count_occ.parquet'
park.import = read_parquet(path)

park.import = park.import %>% 
  transmute(
    id.block = block_id,
    id.bdb = block_day_bin,
    id.dist = dist_id,
    bin = bin,
    meters.list = meter_ids,
    meters.count = meter_count,
    day = day,
    day.week = `day of week`,
    date = date(date),
    datetime = datetime,
    t.hrs.occ = occ_hours,
    t.hrs.tot = tot_hours,
    t.pct.occ = occ_perc
  ) %>%
  mutate(
    week = week(date),
    month = month(date)
  )

park.last = 
  park.import %>%
  group_by(id.block) %>%
  filter(datetime == max(datetime)) %>%
  pull(id.bdb)
park.first = 
  park.import %>%
  group_by(id.block) %>%
  filter(datetime == min(datetime)) %>%
  pull(id.bdb)

park.import = 
  park.import %>%
    mutate(
      first.bin = ifelse(
        id.bdb %in% park.first,
        TRUE, FALSE),
      last.bin = ifelse(
        id.bdb %in% park.last,
        TRUE, FALSE),
      )

park.last = 
  park.import %>%
  group_by(id.block) %>%
  filter(date == max(date)) %>%
  pull(id.bdb)

park.first = 
  park.import %>%
  group_by(id.block) %>%
  filter(date == min(date)) %>%
  pull(id.bdb)

park.import = 
  park.import %>%
    mutate(
      first.date = ifelse(
        id.bdb %in% park.first,
        TRUE, FALSE),
      last.date = ifelse(
        id.bdb %in% park.last,
        TRUE, FALSE),
      ) %>%
  dplyr::select(-meters.list)

rm(park.first)
rm(park.last)

Block Geometry

For spatially visualizing the dataset, we pull in the street blocks geometries.


path = "C:/Users/nelms/Documents/Penn/MUSA-508/MUSA508_FINAL_SFPARK/data/Blockfaces.geojson"

park.blocks = 
  st_read(path) %>% 
  st_transform(sf_crs) %>%
  #dplyr::select(sfpark_id, geometry) %>%
  filter(
    !is.na(sfpark_id) & 
    sfpark_id %in% park.import$id.block
    ) %>%
  rename(
    id.block = sfpark_id
  )


path = "C:/Users/nelms/Documents/Penn/MUSA-508/MUSA508_FINAL_SFPARK/data/Planning Department Neighborhood Quadrants.geojson"

SF.quad = st_read(path) %>% 
  st_transform(sf_crs) %>%
  dplyr::select(quad, geometry)

park.blocks = 
  st_join(
    park.blocks %>%
      dplyr::select(id.block, geometry),
    SF.quad,
    join = st_intersects,
    left = TRUE,
    largest = T
  )

park.import =
  merge(
    park.import,
    park.blocks %>%
      st_drop_geometry(),
    by='id.block'
  )

Census

To understand the demographics of different areas, we pull in a variety of census data.


og_vars = c("B01003_001", "B19013_001", 
                        "B02001_002", "B08013_001",
                        "B08012_001", "B08301_001", 
                        "B08301_010", "B01002_001"
                        )

added_vars = c("B25026_001E","B02001_002E","B15001_050E",
              "B15001_009E","B19013_001E","B25058_001E",
              "B06012_002E", "B08301_003E", "B08301_010E", 
              "B08301_016E", "B08301_018E", "B08301_021E", 
              "B08007_001E", "B11016_001E", "B25044_003E", 
              "B15003_022E", "B01003_001E", "B08301_019E", 
              "B08301_017E", "B08301_020E", "B08301_004E", 
              "B08135_001E", "B08303_001E", "B08303_002E", 
              "B08303_003E", "B08303_004E", "B08303_005E", 
              "B08303_006E", "B08303_007E", "B08303_008E", 
              "B08303_009E", "B08303_010E", "B08303_011E", 
              "B08303_012E", "B08303_013E", "B08301_018E", 
              "B15003_022E")
# 
# SF.census =
#   get_acs(
#     geography = "tract",
#     variables = unique(c(og_vars,added_vars)),
#     year = 2019,
#     state = "CA",
#     geometry = TRUE,
#     county=c("San Francisco"),
#     output = "wide"
#           ) %>%
#   rename(
#     Total_Pop =  B01003_001E,
#     Med_Inc = B19013_001E,
#     Med_Age = B01002_001E,
#     White_Pop = B02001_002E,
#     Travel_Time = B08013_001E,
#     Num_Commuters = B08012_001E,
#     Means_of_Transport = B08301_001E,
#     Total_Public_Trans = B08301_010E,
#     workforce_16 = B08007_001E,
#     Num_Vehicles = B06012_002E,
# 
#     drove_to_work = B08301_003E,
#     House_holds_no_vehicles = B25044_003E,
#     total_households = B11016_001E
#     ) %>%
#   mutate(
#     workers_commute_30_90min = B08303_008E + B08303_009E + B08303_010E + B08303_011E + B08303_012E + B08303_013E,
#     commute_30_90min_pct = (workers_commute_30_90min/workforce_16),
#     households_car_pct = (Num_Vehicles/total_households),
#     households_NOcar_pct = (House_holds_no_vehicles/total_households),
#     Drive_Work_pct = (drove_to_work/workforce_16),
#     PublicTransport_work_pct = (Total_Public_Trans/workforce_16)
#          ) %>%
#   dplyr::select(Total_Pop, Med_Inc, White_Pop, Travel_Time,
#          Means_of_Transport, Total_Public_Trans, Med_Age,
#          workforce_16, Num_Vehicles, households_NOcar_pct, households_car_pct,
#          commute_30_90min_pct, Drive_Work_pct, PublicTransport_work_pct,
#          GEOID, geometry) %>%
#   mutate(Percent_White = White_Pop / Total_Pop,
#          Mean_Commute_Time = Travel_Time / Total_Public_Trans,
#          Percent_Taking_Public_Trans = Total_Public_Trans / Means_of_Transport)

# web_srid = 'EPSG:3857'
# path = 'C:/Users/nelms/Documents/Penn/MUSA-508/MUSA508_FINAL_SFPARK/data/sf_2019census.geojson'
# st_write(st_transform(SF.census, web_srid), path, delete_dsn=T)

path = 'C:/Users/nelms/Documents/Penn/MUSA-508/MUSA508_FINAL_SFPARK/data/sf_2019census.geojson'
SF.census = st_read(path) %>%
  st_transform(., sf_crs)

Reading layer sf_2019census' from data sourceC:_FINAL_SFPARK_2019census.geojson’ using driver `GeoJSON’ Simple feature collection with 197 features and 18 fields (with 1 geometry empty) Geometry type: MULTIPOLYGON Dimension: XY Bounding box: xmin: -13693850 ymin: 4536110 xmax: -13617440 ymax: 4560139 Projected CRS: WGS 84 / Pseudo-Mercator


SF.tracts = 
  SF.census %>%
  as.data.frame() %>%
  distinct(GEOID, .keep_all = TRUE) %>%
  dplyr::select(GEOID, geometry) %>% 
  st_sf

park.census = 
  st_join(
    park.blocks %>%
      dplyr::select(id.block,geometry),
    SF.tracts %>%
      st_transform(crs=sf_crs),
    join=st_intersects,
    left = TRUE) %>% 
  st_drop_geometry() %>%
  filter(id.block %in% park.import$id.block) %>%
  left_join(
    .,
    SF.census %>%
      st_drop_geometry(),
    on='GEOID'
    ) %>%
  dplyr::select(-GEOID) %>%
  left_join(
    park.import %>%
      dplyr::select(id.bdb, id.block, t.pct.occ),
    .,
    on='id.block'
  )
## Joining, by = "GEOID"
## Joining, by = "id.block"

rm(SF.tracts)
rm(SF.census)

correlation_table(
    data=park.census %>%
      dplyr::select(-id.bdb,-id.block) %>%
      na.omit(.), 
    target="t.pct.occ"
  ) %>%
  arrange(Variable) %>%
  filter(Variable!='t.pct.occ') %>%
  rename(
    Correlation = t.pct.occ
  ) %>%
  kable() %>%
  kable_styling()
Variable Correlation
commute_30_90min_pct -0.07
Drive_Work_pct -0.08
households_car_pct -0.11
households_NOcar_pct -0.04
Mean_Commute_Time 0.00
Means_of_Transport 0.02
Med_Age -0.08
Med_Inc 0.14
Num_Vehicles -0.07
Percent_Taking_Public_Trans -0.02
Percent_White 0.21
PublicTransport_work_pct -0.02
Total_Pop -0.01
Total_Public_Trans 0.02
Travel_Time 0.01
White_Pop 0.08
workforce_16 0.02

park.census = 
  park.census %>%
  dplyr::select(
    id.block,
    households_car_pct,
    Med_Inc,
    Percent_White
  ) %>% 
  distinct() %>%
    arrange(id.block) %>%
    data.table(., key='id.block')

Looking at this correlation plot to the Meter’s Occupation Rate, a majority of the census demographics have little linear connection with the rate. The strongest variables (that we will actually use) are (1) % that Drive to Work, Median Income, & % of the population that’s White.

The stronger variables could form a similar ‘gentrification’ narrative similar to a previous San Francisco Bay study. Many of the high density, high demand neighborhoods are being primarily the home of new market-rate, car-less housing for predominantly white, higher-income residents. Although this narrative is outside the scope of the project and app, the neighborhoods that have higher demand for street parking align with those new developments.

Weather Info

Another external predictor is the temperature, wind, and precipitation by hour or day. The hope was that these predictors could help describe some parking patterns.



# for some reason the SFO weather station isn't working

path = 'C:/Users/nelms/Documents/Penn/MUSA-508/MUSA508_FINAL_SFPARK/data/sf_weather_2019.parquet'

# library(riem)
# oak.weather =
#   riem_measures(station = "OAK", date_start = "2019-01-02", date_end = "2020-01-01")
# write_arrow(oak.weather, path)

oak.weather = 
  read_arrow(path) %>%
    dplyr::select(valid, tmpf, p01i, sknt) %>%
    replace(is.na(.), 0) %>%
    mutate(interval60 = ymd_h(substr(valid,1,13))) %>%
    dplyr::group_by(interval60) %>%
    dplyr::summarize(
      Temperature = max(tmpf),
      Wind_Speed = max(sknt)
      ) %>%
  filter(interval60 %in% park.import$datetime) %>%
  mutate(Temperature = ifelse(Temperature == 0, 42, Temperature)) 

oak.rain = 
  read_arrow(path) %>%
    dplyr::select(valid, tmpf, p01i, sknt) %>%
    replace(is.na(.), 0) %>%
    mutate(interval60 = ymd_h(substr(valid,1,13))) %>%
    mutate(date = date(interval60)) %>%
    dplyr::group_by(date) %>%
    dplyr::summarize(Precipitation = sum(p01i)) %>%
  filter(date %in% park.import$date)



park.weather =
  park.import %>% 
  dplyr::select(
    id.bdb, id.block, 
    datetime, date,
    t.pct.occ) %>%
  left_join(
    .,
    oak.weather %>%
      rename(datetime = interval60),
    on='datetime'
  ) %>%
  left_join(
    .,
    oak.rain,
    on='date'
  )

rm(oak.rain)
rm(oak.weather)


correlation_table(
    data=park.weather %>%
      dplyr::select(t.pct.occ, Temperature, Wind_Speed, Precipitation) %>%
      na.omit(.), 
    target="t.pct.occ"
  ) %>%
  arrange(Variable) %>%
  filter(Variable!='t.pct.occ') %>%
  rename(
    Correlation = t.pct.occ
  ) %>%
  kable() %>%
  kable_styling()
Variable Correlation
Precipitation 0.00
Temperature -0.03
Wind_Speed 0.01

rm(park.weather)

As seen in the correlation chart (and weather plot below), the weather variables are very insignificant to the meter’s occupation rate. As a result, they won’t be used.

Engineer Data

The primary predictors to be engineered are ones that describe the meter’s place in space and time.

Average Times

To start, the meter’s occupancy rate is going to be averaged by different time periods (e.g. hours, day, week, month) and geographic locations (e.g. block, district, city quadrant).


# one line group_by, summarize, ungroup
mutate_by = function(.data, group, ...) {
  group_by_at(
    .data, 
    dplyr::vars( !! rlang::enquo(group) )) %>%
    mutate(...) %>%
    ungroup()
  }

park.avg =
  park.import %>%
  # BY BLOCK ID
  ## DAY
  mutate_by(
    group=c('id.block', 'day'),
    avg.blk.day = mean(t.pct.occ)
  ) %>%
  ## DAY OF WEEK
  mutate_by(
    group=c('id.block', 'day.week'),
    avg.blk.weekday = mean(t.pct.occ)
  ) %>%
  mutate_by(
    group=c('id.block', 'month'),
    avg.blk.month = mean(t.pct.occ)
  ) %>%
  
  # BY TIME BIN
  ## DAY
  mutate_by(
    group=c('bin', 'day'),
    avg.bin.day = mean(t.pct.occ)
  ) %>%
  ## DAY OF WEEK
  mutate_by(
    group=c('bin', 'day.week'),
    avg.bin.weekday = mean(t.pct.occ)
  ) %>%
  mutate_by(
    group=c('bin', 'month'),
    avg.bin.month = mean(t.pct.occ)
  ) %>%
  
  # BY BLOCK & BIN 
  ## DAY OF WEEK
  mutate_by(
    group=c('id.block', 'bin', 'day.week'),
    avg.blkbin.weekday = mean(t.pct.occ)
  ) %>%
  mutate_by(
    group=c('id.block', 'bin', 'month'),
    avg.blkbin.month = mean(t.pct.occ)
  ) %>%
  
  # BY District
  ## 
  mutate_by(
    group=c('id.dist', 'bin', 'day'),
    avg.distbin.day = mean(t.pct.occ)
  ) %>%
  
  ## DAY OF WEEK
  mutate_by(
    group=c('id.dist', 'bin', 'day.week'),
    avg.distbin.weekday = mean(t.pct.occ)
  ) %>%
  mutate_by(
    group=c('id.block', 'bin', 'month'),
    avg.distbin.month = mean(t.pct.occ)
  ) %>%
  dplyr::select(c(starts_with("avg."), "t.pct.occ", "id.bdb"))

The variables initially are a range of combinations of time periods and geographies. The initial correlation table below suggests that most of the variables have a high correlation.


park.avg.cor =
  correlation_table(
    data=park.avg %>%
      dplyr::select(c(starts_with("avg."), "t.pct.occ")), 
    target="t.pct.occ") %>%
  arrange(Variable) %>%
  filter(Variable!='t.pct.occ')

park.avg.cor.col = 
  str_split_fixed(park.avg.cor$Variable, "[.]", 3) %>%
  as.tibble() %>%
  dplyr::select(2,3)
colnames(park.avg.cor.col) = c("By", "Time Period")

cbind(
  park.avg.cor.col, 
  park.avg.cor %>%
    dplyr::select(-Variable)
) %>%
  kable() %>%
  kable_styling()
By Time Period t.pct.occ
bin day 0.15
bin month 0.12
bin weekday 0.14
blk day 0.68
blk month 0.48
blk weekday 0.49
blkbin month 0.61
blkbin weekday 0.62
distbin day 0.39
distbin month 0.61
distbin weekday 0.35

park.avg = 
  park.avg %>%
  dplyr::select(
    -avg.bin.day, -avg.bin.month, -avg.bin.weekday,
    )

rm(park.avg.cor)
rm(park.avg.cor.col)

Because of their potency, the predictors will be selected after determining their multicollinearity at the end of the EDA section.

Lagged Time

The averages simply look at different scales of time and space. Lagged time aims to look at periods before the current period.


park.bdb.occ = park.import %>%
  dplyr::select(id.bdb, t.pct.occ) %>%
  arrange(id.bdb)

lag_lead = function(data_col, last_col, first_col, n=1){
  c(ifelse(
    last_col==TRUE,
    dplyr::lag(data_col, n=n)[1],
    NA
  ),
  ifelse(
    first_col==TRUE,
    dplyr::lead(data_col, n=n)[1],
    NA
  )) %>% paste(.,collapse=" ")
}

park.lag = 
  park.import %>% 
    arrange(id.block, datetime) %>% 
    mutate(
      lag.blkbin.1bin = ifelse(
        first.bin==T,NA,
        dplyr::lag(t.pct.occ,1)
      ),
      # lead.bin.1 = ifelse(
      #   last.bin==T,NA,
      #   dplyr::lead(t.pct.occ,1)
      # ),
      lag.blkbin.1day = ifelse(
        lag(bin,3)!=bin,
        NA,
        dplyr::lag(t.pct.occ,3)
      ),
      # lead.bin.3 = ifelse(
      #   lead(bin,3)!=bin,NA,
      #   dplyr::lead(t.pct.occ,3)
      # ),
      lag.blkid.week = ifelse((yday(date)-8)>0,
        paste(
          id.block,
          str_pad(yday(date)-8, 3, pad = "0"),
          bin, sep='_'),
        NA
      )
      # lead.date.7 = ifelse((yday(date)+8)<366,
      #   paste(
      #     id.block,
      #     str_pad(yday(date)+8, 3, pad = "0"),
      #     bin, sep='_'),
      #   NA
      # )
      )
  # left_join(
  #   .,
  #   park.blk.occ %>% 
  #     rename(lead.occ.7 = t.pct.occ, lead.date.7=id.bdb),
  #   on='lead.date.7', 
  #   na_matches = "never"
  # ) %>%

park.lag = 
  left_join(
    park.lag,
    park.bdb.occ %>% 
      rename(
        lag.blkbin.week = t.pct.occ, 
        lag.blkid.week=id.bdb),
    on='lag.blkid.week',
    na_matches = "never"
  ) %>%
  dplyr::select(-lag.blkid.week)  %>%
  dplyr::select(c(starts_with("lag."), "t.pct.occ", "id.bdb"))
## Joining, by = "lag.blkid.week"

rm(park.bdb.occ)

The lag time has to work around the meter blocks which are grouped by time period bins of a few hours. The lags then look at three periods (1) 1 bin beforehand, 1 day beforehand (same time bin), and 1 week beforehand.

So if our current time bin is Tuesday December 17th, 2019 between 12pm to 3pm, we will look at the same meter’s bin (1) that day from 9am to 12pm, (2) one day beforehand on Mon Dec 16th from 12-3, then (3) one week beforehand on Tue Dec 10th at 12-3.


correlation_table(
  data=park.lag %>%
    dplyr::select(c(starts_with("lag."), "t.pct.occ")), 
  target="t.pct.occ") %>%
  arrange(Variable) %>%
  filter(Variable!='t.pct.occ') %>%
  mutate(
    `Lag Period` = c('1 Bin', '1 Day', '1 Week'),
    Correlation = t.pct.occ
  ) %>% 
  dplyr::select(-Variable,-t.pct.occ) %>%
  kable() %>%
  kable_styling()
Lag Period Correlation
1 Bin 0.22
1 Day 0.35
1 Week 0.32

As a side note: When aggregating, I lean towards performing the mean of all the groouped occupancy rates – rather than median or recalculating the rate & mean by summing the group’s occupied time & possible time.

Join All

Now we simply join the previous variables into one dataset.



park.import = 
  park.import %>%
    dplyr::select(
      -first.bin, -last.bin, -first.date, -last.date
      #-week, -month, -day
    ) %>% 
    arrange(id.block) %>%
    data.table(.)

park.import = 
  park.import %>%
  left_join(
    .,
    park.census %>%
      distinct(),
    by = 'id.block'
  )
rm(park.census)

park.import = 
  merge(
    park.import %>%
      data.table(., key='id.bdb'), #, key='id.block'),
    park.avg %>%
      dplyr::select(-t.pct.occ) %>%
      data.table(., key='id.bdb'),
    on='id.bdb'
  ) %>%
  merge(
    .,
    park.lag %>%
      dplyr::select(-t.pct.occ) %>%
      data.table(., key='id.bdb'),
    on='id.bdb'
    )
rm(park.avg)
rm(park.lag)

EDA

The varying time and locations in this large dataset provide an amazing opportunity to understand parking dynamics in the relative vacuum of San Francisco. With the features mostly imported and engineered, we will perform Exploratory Data Analysis (EDA) to evaluate the variables themselves as well as their relationship to each other. The section is split between analysis of the dependent variable and then analysis of the predictor variables.

Meter Occupancy Rate

To clarify, the dependent variable is whether or not a Block’s Meters in a time period have an Occupancy Rate above 50%. So a ‘TRUE’/1 suggests that at least half of the the block is filled up in a time period.

For example, lets say the 1000 Valencia Block has 3 meters active between the time period of 9am to noon. With each meter having the ability to be operational for 3 hour, the block has an aggregate of 9 possible parking hours that people can park infront of this Ritual Coffee. If each meter is occupied for 2 hours during that period, then the block aggregates 6 occupied hours in total. Leading to that block and time period having a parking occupation rate of 66.6% (6hrs / 9hrs). So there will likely be an open parking spot that I have to awkwardly stand in because the line is too long to my favorite coffee shop.

Even though the statistic doesn’t describe how long at least one single parking meter is unoccupied, the rate still suggests how frequently used the parking is. This will still be helpful for drivers who want to find a parking meter close to a bougie, $6-a-cup coffee shop.

Table

The first table highlights the distribution of the Occupation rate across the day. With the highest rate of parking being ain the middle of the day. This largely could be the result of both work-related and recreational trips being active at the same time. With the city being a regional and tourist destination, many out-of-town travelers heavily lean on parking.



park.import$occ50 = FALSE
park.import[park.import$t.pct.occ >= .5, 'occ50'] = TRUE




tab = 
  describeBy(
    park.import  %>% 
      dplyr::select(t.pct.occ, bin),
    group='bin', mat = TRUE,
    na.rm = TRUE
  ) %>%
  as.data.frame() %>%
  #filter(item>3) %>%
  arrange(desc(item)) %>%
  dplyr::select(-item, -vars, -n) %>%
  tail(., -3) %>%
  arrange(fct_relevel(group1, c('9a to 12p','12p to 3p','3p to 6p')))

rownames(tab) = NULL

tab %>%
  transmute(
    `Time Period` = group1,
    Mean = mean %>% percent_formatter(., n=1),
    Median = median  %>% percent_formatter(., n=1),
    SD = sd %>% percent_formatter(.),
    Min = min %>% percent_formatter(.),
    Max = max %>% percent_formatter(.)
  ) %>% 
  kable(title = 'Parking Meter Occupation Rate') %>%
  kable_styling() %>%
  add_header_above(c(" " = 1, "Parking Occupation Rate" = 5))
Parking Occupation Rate
Time Period Mean Median SD Min Max
9a to 12p 40.9% 41.1% 22% 0% 100%
12p to 3p 43.8% 44.8% 20% 0% 100%
3p to 6p 37.6% 38.6% 20% 0% 100%

The following table highlights the proportion of the blocks that are above 50% parking occupation. It is odd that a majority of blocks are only half full of parking. This is an uneasy trend as personal experience and SFMTA’s report suggest that blocks are typicaly more than half full.




park.len = nrow(park.import)

park.import %>%
  mutate(`Parking\nAbove 50%\nOccupied` = occ50 %>% as.character()) %>%
  group_by(`Parking\nAbove 50%\nOccupied`) %>% 
  dplyr::summarize(
    `Percent\nof Total` = (n()/park.len) %>% percent_formatter(.),
    Count = n() %>% format(big.mark = ',')
      ) %>%
  kable() %>%
  kable_styling()
Parking Above 50% Occupied Percent of Total Count
FALSE 66% 927,523
TRUE 34% 468,002

Histogram

The histogram better describes the occupation rate distribution. The blocks provide a bell curve around the mean of 41% occupied. The bin at 0% could be naturally empty blocks, or that there are blocks and bins that aren’t allowed to have parking.


title = "Parking Occupation Rate Histogram"
subtitle = 'San Francisco, 2019'

ggplot(park.import, aes(x = t.pct.occ)) + 
  geom_histogram(aes(y = ..density..),
                 colour = 1, fill = "grey70", 
                 position="identity", closed = "left",
                 binwidth = .05, boundary = 0
                 ) +
  geom_density(aes(y=..density..), lwd = 1.2,
               linetype = 2,
               colour = 2) +
  scale_x_continuous(
    labels = function(num) num %>% percent_formatter(),
    name="Meter Occupation Rate") +
  labs(
    title=title,
    subtitle=subtitle) +
  theme_minimal()

Time Series

This time series chart provides the occupancy rates over the course of the year. Each chart averages the blocks by the day, week, and month. The top charts highlight that the average occupancy rate doesn’t vary much throughout the year – aside from a small 1% dip in summer and winter. The bottom date chart highlights the weekend peaks of parking. As indicated earlier, these peaks could be the result of recreational travel.


ybreaks = c(.4,.45,.5)
ysmolbreaks = c(.425,.475)

map_park = function(
  time='date', color='black', 
  size=1, ylab='')
 ggplot(
  park.import %>%
    group_by(.data[[time]]) %>%
    dplyr::summarize(
      t.pct.occ =
        sum(t.hrs.occ)/sum(t.hrs.tot)))+
  geom_line(
    aes(
      x = .data[[time]], 
      y = t.pct.occ), 
    color=color, size=size)+
  labs(
    title=glue("by {toupper(time)}"),
    x="Date",
    y=ylab) +
    xlim(
      min(park.import[[time]]),
      max(park.import[[time]])) +
    scale_y_continuous(
      breaks = ybreaks,
      minor_breaks = ysmolbreaks,
      labels = percent_formatter,
      limits = c(.4,.5)
      ) +
  plotTheme()

date = map_park('date', 'darkgreen', size=.5)
week = map_park('week', 'green', size=1,
                ylab = "Occupied / Operational Hours")
month = map_park('month', 'lightgreen', size=1.5)

remove_x = theme(
  axis.text.x = element_blank(),
  axis.ticks.x = element_blank(),
  axis.title.x = element_blank()
)

p <- list(
  month + remove_x,
  week + remove_x,
  date
)
title = "Average Occupation Rate of Parking Meters"
subtitle = 'San Francisco, 2019'
wrap_plots(p, nrow = 3) + 
  plot_layout(guides = "collect") + 
  plot_annotation(
    title = title,
    subtitle = subtitle) + plotTheme()

Weekday Time Series

This table builds off the previously discussed weekday trend. Saturdays as a whole have a higher occupation rate than other days. The 12-3 time period still remains very high on weekdays.


title = "Average Percent of Time Meter is Occupied"

park.import$bin = factor(park.import$bin, levels = c("9a to 12p", "12p to 3p", "3p to 6p"))
park.import$day.week = factor(
  park.import$day.week,
  levels = 
    c("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"))

park.weekday = 
  park.import %>%
    group_by(day.week,bin) %>%
    summarise(
      Count = sum(meters.count),
      Mean = mean(t.pct.occ,rm.na=T) #,
      #Median = mean(t.pct.occ,rm.na=T)
      )
## `summarise()` has grouped output by 'day.week'. You can override using the `.groups` argument.
rownames(park.weekday) = NULL

park.weekday %>%
  ungroup() %>%
  transmute(
    `Week Day` = day.week %>% as.character(),
    `Time Period` = bin %>% as.character(),
    Count = Count %>% format(big.mark=','),
    Mean = Mean %>% percent_formatter(., n=1) #,
    #Median = Median %>% percent_formatter(., n=1)
      ) %>%
  kable(title=title) %>%
  kable_styling() %>%
  collapse_rows(., columns = 1:2)
Week Day Time Period Count Mean
Monday 9a to 12p 420,687 40.1%
12p to 3p 420,687 42.6%
3p to 6p 420,687 35.8%
Tuesday 9a to 12p 472,564 40.9%
12p to 3p 472,564 42.9%
3p to 6p 472,564 36.6%
Wednesday 9a to 12p 472,077 41.4%
12p to 3p 472,077 42.9%
3p to 6p 472,077 36.9%
Thursday 9a to 12p 465,505 41.0%
12p to 3p 465,505 43.2%
3p to 6p 465,505 36.9%
Friday 9a to 12p 480,707 41.5%
12p to 3p 480,707 43.7%
3p to 6p 480,707 37.3%
Saturday 9a to 12p 479,967 40.7%
12p to 3p 479,967 47.3%
3p to 6p 479,967 41.7%

# cbind(
#   park.import %>%
#     group_by(day.week) %>%
#     dplyr::summarize(
#       `Occupation Rate` = mean(t.pct.occ, na.rm = T) %>% percent_formatter(n=1),
#       `Occupied Hours` = sum(t.hrs.occ) %>% round(0) %>% format(big.mark=','),
#       Count = n()
#     ),
#   park.import %>%
#     filter(occ50==TRUE) %>%
#     group_by(day.week) %>%
#     dplyr::summarize(
#       `Above 50% Occupied` = n()
#     ) %>%
#     ungroup() %>%
#     dplyr::select(-day.week)
#   ) %>%
#   rename(`Week Day` = day.week) %>%
#   mutate(`Above 50% Occupied` = (`Above 50% Occupied`/Count) %>% percent_formatter()) %>%
#   dplyr::select(-Count) %>%
#   kable() %>%
#   kable_styling()

This time series plot helps visualize the weekend trends while splitting by the cities quadrants. The NE-quadrant is San Francisco’s higher density recreation & employment area – including the Downtown Business District, the touristy Fisherman’s Wharf, and the high traffic Civic Center/Tenderloin. The SE-quadrant also is a higher-traffic area – which includes the Mission neighborhood and the sports stadiums. The SW & NW quadrants are largely residential.

The plot slightly complicates this narrative as the NW-quadrant has the consistently highest occupation rate of +40%. This is likely due to the parking meters (29% of total) being located on the busy driving corridors of Geary boulevard, the Marina, and Hayes Valley. The NE- & SE-quadrants of Downtown & the Mission have high peaks but drop off at night – due to the day-driven employment centers. The SW-quadrant has few meters (16%) spread across the largest, low-density area with abundant free on-street parking.


library(forcats)

park.weekday = 
  park.import %>%
    group_by(day.week,bin,quad) %>%
    summarise(
      Count = sum(meters.count),
      Mean = mean(t.pct.occ,rm.na=T) #,
      #Median = mean(t.pct.occ,rm.na=T)
      ) %>%
  mutate(
    id = row_number(),
    Quadrant = quad
    
    )
## `summarise()` has grouped output by 'day.week', 'bin'. You can override using the `.groups` argument.

park.weekday$week_bin = fct_cross(park.weekday$bin, park.weekday$day.week, sep = ' - ')

title = "Average Occupation Rate of Parking Meters"
subtitle = 'by Weekday, Time Period, & Quadrant of San Francisco'


ybreaks = c(.275,.30,.325,.35,.375,.4,.425,.45,.475,.5)


period_formatter = function(brk){
  bstr = brk %>% as.character() %>%
    str_replace(., 'a ', ' ') %>% str_replace(., 'p', '') %>% 
    str_replace(., '3p', '3') %>% str_replace(., '6p', '6') %>%
    str_replace(., ' - ', ' — ') %>% str_replace(., ' to ', '-') 
  splt = c(strsplit(bstr, '—', fixed=T))%>% unlist()
  fst = splt[[1]]
  sec = ifelse(fst=='9-12 ', splt[[2]], '')
  fin = paste(sec,fst, collapse = "   ") %>% str_trim()
  return(fin)
}


brks = unique(park.weekday$week_bin) %>% as.character() %>% 
  lapply(., function(wk) period_formatter(wk))


ggplot(
  park.weekday %>% ungroup()) + 
    annotate("rect", 
             xmin=1-.5, xmax=4-.5, ymin=-Inf, ymax=Inf, 
             alpha=0.2, fill="grey40") +
    annotate("rect", 
             xmin=1+6-.5, xmax=4+6-.5, ymin=-Inf, ymax=Inf, 
             alpha=0.2, fill="grey40") +
    annotate("rect", 
             xmin=1+6*2-.5, xmax=4+6*2-.5, ymin=-Inf, ymax=Inf, 
             alpha=0.2, fill="grey40") +
  geom_line(
    aes(
      x = week_bin, 
      y = Mean, 
      group = id,
      color=Quadrant
      ),
    alpha=0.90,
    size=1.5
    ) + 
  labs(
    title=title,
    subtitle=subtitle,
    x="Time Period",
    y= "Occupied Meter Rate") +
    # xlim(
    #   min(park.import[[time]]),
    #   max(park.import[[time]])) +
    scale_y_continuous(
      breaks = ybreaks,
      labels = percent_formatter,
      #limits = c(.4,.5)
      ) +
    # scale_x_continuous(
    #   ,
    #   labels = period_formatter
    #   )
    scale_x_discrete(labels= brks) + 
  theme(axis.text.x = element_text(
    angle = 45, vjust = 1, hjust=1)) + 
  plotTheme()

### Time Period Map

The Time Period Map by Quadrant highlights the trends previously discussed. Starting with the Downtown NE-quadrant, there is a heavy variation in time and location. The north coast is largely the touristy Fisherman’s Wharf while the core has more business activity. This will be discussed in more length in the next map.

The SE-quadrant has a heavy variation in the far north-eastern cornea, in the SOMA (‘South of Market’) & Mission Bay area, where there is a heavy concentration of tech companies (i.e. Twitter, Salesforce) and a regional rail station. As a result, the morning has a heavy amount of parking occupancy but dramatically drops in the evening. The area is newly developed and has room to fleshed out its recreational activities past the Giants and Warriors stadiums. Combined with more off-street garages, there is less parking demand.

As mentioned, the NW-quadrant has pockets of consistently occupied corridors. The SE-quadrant is very large with few commercial centers to attract more paid parking.


SF.quad.labels = sf_to_labels(SF.quad, 'quad')

park.map = 
  #rbind(
    park.import %>%
      dplyr::group_by(id.block, bin) %>%
      dplyr::summarize(
        t.pct.occ = mean(t.pct.occ, rm.na=T),
        meters.count = sum(meters.count)
      ) %>% #,
    # park.import %>%
    #   dplyr::group_by(id.block) %>%
    #   dplyr::summarize(
    #     t.pct.occ = mean(t.pct.occ, rm.na=T),
    #     meters.count = sum(meters.count)
    #   ) %>%
    #   mutate(bin = 'All')
    #)%>%
  merge(
    .,
    park.blocks,
    on='id.block'
  ) %>%
  st_sf() %>%
  st_buffer(250)
## `summarise()` has grouped output by 'id.block'. You can override using the `.groups` argument.
  
park.map$bin = factor(park.map$bin, levels = c(
  #"All",
  "9a to 12p", "12p to 3p", "3p to 6p"))
# park.map$day.week = factor(
#   park.map$day.week,
#   levels = 
#     c("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"))

ggplot()+
  geom_sf(
    data = SF.quad, fill='grey90'
  ) +
  geom_sf(data = park.map,
          aes(fill = t.pct.occ), #, size=meters.count), 
          color = "transparent", alpha = 0.8
          ) +
  geom_text_sf(
    SF.quad.labels, label.size = .5,
    vjust = 'bottom'
    ) + 
  scale_fill_gradientn(
    colors = rev(RColorBrewer::brewer.pal(5, "RdYlGn")),
    values = c(0,.25,.55,.75,1),
    labels = function(tstr) percent_formatter(tstr),
    name='% of Time\nMeter is\nOccupied',
    guide = "colorbar"
    #direction = -1, discrete = FALSE, option = "D"
    ) +
  geom_sf(data = SF.quad, color='black',
          fill = "transparent", #alpha = 0.8,
          size=.01
          #linetype = 'dashed'
          ) +
  facet_wrap(~bin, ncol = 3)+
  labs(
    title='Parking Meter Occupation Rate - by Time Period',
    subtitle = 'San Francisco, 2019'
    ) + 
  mapTheme+
  guides(
    fill = guide_colourbar(
      barwidth = 16,
      barheight = 1,
      direction="horizontal",
      #label.position = "bottom"
      )
    ) + 
  theme(legend.position="bottom")

Downtown Map

With Downtown NE-quadrant having a larger share of meters (30.5%) and an interesting level of variety of uses, our team will focus one of our models just on the area. The map highlights the previously discussed trends of the touristy coast of Fisherman’s Wharf. The Civic Center area’s large amount of government, theater, and business buildings makes it a hot spot for parking. The Tenderloin and Chinatown have lower occupation rates as they don’t have as many specific destinations or car-owners.


path = "C:/Users/nelms/Documents/Penn/MUSA-508/MUSA508_FINAL_SFPARK/data/Parking_Management_Districts.csv"
SF.dist = read.csv(path)

SF.dist$geometry = st_as_sfc(SF.dist$shape, crs = 4326)

SF.dist = SF.dist %>% 
  st_sf() %>% st_transform(sf_crs) %>%
  dplyr::select(PM_DISTRICT_ID, PM_DISTRICT_NAME, geometry) %>%
  st_join(., SF.quad, largest=TRUE)

#SF.quad.labels = sf_to_labels(SF.quad, 'quad') %>% dplyr::filter(label=='NE')
SF.dist.labels = sf_to_labels(
  SF.dist %>% arrange(quad) %>% 
    dplyr::filter(quad=='NE') %>%
    dplyr::filter(!PM_DISTRICT_NAME  %in% c(
      'Polk', 'Telegraph Hill'
    )), 'PM_DISTRICT_NAME') %>%
  mutate(label = 
    ifelse(
      label == 'N.Beach-Chinatown',
      'N.Beach\n\nChinatown',
    ifelse(
      label == "Fisherman's Wharf",
      "\nFisherman's Wharf",
           label)
      ) #%>% gsub(' ','\n', ., fixed = TRUE)
    )

park.map = 
  park.import %>% 
    dplyr::filter(quad=='NE') %>%
    dplyr::group_by(id.block, bin) %>%
    dplyr::summarize(
      t.pct.occ = mean(t.pct.occ, rm.na=T)
    ) %>%
  merge(
    .,
    park.blocks,
    on='id.block'
  ) %>%
  st_sf() %>%
  st_buffer(50)
## `summarise()` has grouped output by 'id.block'. You can override using the `.groups` argument.

#park.map$bin = factor(park.map$bin, levels = c("All","9a to 12p", "12p to 3p", "3p to 6p"))
# park.map$day.week = factor(
#   park.map$day.week,
#   levels = 
#     c("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"))
sep = 10000
ggplot()+
  geom_sf(
    data = SF.quad %>% 
      dplyr::filter(quad=='NE'), color = 'transparent', fill='grey90'
  ) +
  geom_sf(data = park.map,
          aes(fill = t.pct.occ), #, size=meters.count), 
          color = "transparent", #alpha = 0.95
          ) +
  geom_text(
        data=SF.dist.labels, check_overlap=TRUE,
        fontface='bold', color='black',
        size=3,
        aes(x=lon,y=lat, label=label)) + 
  # geom_text_sf(
  #   SF.dist.labels, check_overlap = TRUE,
  #   size=1
  #   #hjust = "top"#, vjust = "outward"
  #   ) + 
  #xlim(min(SF.dist.labels$lon)-sep, max(SF.dist.labels$lon)+sep) + 
  scale_fill_gradientn(
    colors = rev(RColorBrewer::brewer.pal(5, "RdYlGn")),
    values = c(0,.25,.60,.80,1),
    labels = function(tstr) percent_formatter(tstr)
    #direction = -1, discrete = FALSE, option = "D"
    )+
  guides(fill = guide_colourbar(barwidth = 2)) + 
  facet_wrap(~bin, ncol = 3)+
  labs(
    title='Parking Meter Occupation Rate - Downtown Area',
    subtitle = 'San Francisco, 2019'
    )+
  mapTheme+
  guides(
    fill = guide_colourbar(
      barwidth = 16,
      barheight = 1,
      direction="horizontal",
      #label.position = "bottom"
      )
    ) + 
  theme(legend.position="bottom")

With this map, it will be important to incorporate predictor variables that highlight the neighorhood and parking district specific spatial and time patterns.

Predictor Variables


table_title = glue("Summary Statistics")

vars = c(
  "t.pct.occ",
  "avg.blk.day", "avg.blk.weekday", "avg.blk.month", 
  "avg.blkbin.weekday", "avg.blkbin.month", "avg.distbin.day",
  "avg.distbin.weekday", "avg.distbin.month"
  # "lag.blkbin.1bin", "lag.blkbin.1day","lag.blkbin.week"
  )

Category = c(
  'Occupation\nRate',
  'Average\nBlock','Average\nBlock','Average\nBlock',
  'Average\nDistrict & Bin','Average\nDistrict & Bin',
  'Average\nBlock & Bin',  'Average\nBlock & Bin',  'Average\nBlock & Bin'
  # 'Time Lag','Time Lag','Time Lag'
)
park.tab =
  psych::describe(
    park.import %>% 
      dplyr::select(vars),
    na.rm = TRUE
    ) %>% 
    as.data.frame() %>%
    transmute(
      Mean = mean %>% percent_formatter(., n=2),
      SD = sd %>% percent_formatter(.),
      Min = min %>% percent_formatter(.),
      Max = max %>% percent_formatter(.)
    ) 
## Note: Using an external vector in selections is ambiguous.
## i Use `all_of(vars)` instead of `vars` to silence this message.
## i See <https://tidyselect.r-lib.org/reference/faq-external-vector.html>.
## This message is displayed once per session.
indx = rownames(park.tab)
rownames(park.tab) = NULL
  cbind(
    Category = Category,
    Variables = indx,
    park.tab
  ) %>%
  kable() %>%
  kable_styling() %>%
  collapse_rows(., columns = 1)
Category Variables Mean SD Min Max
Occupation Rate t.pct.occ 40.77% 21% 0% 100%
Average Block avg.blk.day 40.77% 14% 0% 99%
avg.blk.weekday 40.76% 10% 4% 67%
avg.blk.month 40.78% 10% 0% 91%
Average District & Bin avg.blkbin.weekday 40.76% 13% 0% 100%
avg.blkbin.month 40.78% 13% 0% 100%
Average Block & Bin avg.distbin.day 40.92% 8% 0% 100%
avg.distbin.weekday 40.91% 7% 11% 69%
avg.distbin.month 40.78% 13% 0% 100%

table_title = glue("Summary Statistics")

vars = c(
  "t.pct.occ",
  # "avg.blk.day", "avg.blk.weekday", "avg.blk.month", 
  # "avg.blkbin.weekday", "avg.blkbin.month", "avg.distbin.day",
  # "avg.distbin.weekday", "avg.distbin.month", 
  "lag.blkbin.1bin", "lag.blkbin.1day","lag.blkbin.week"
  )

Category = c(
  'Occupation\nRate',
  # 'Average\nBlock','Average\nBlock','Average\nBlock',
  # 'Average\nDistrict & Bin','Average\nDistrict & Bin',
  # 'Average\nBlock & Bin',  'Average\nBlock & Bin',  'Average\nBlock & Bin',
  'Time Lag','Time Lag','Time Lag'
)

park.tab =
  psych::describe(
    park.import %>% 
      dplyr::select(vars),
    na.rm = TRUE
    ) %>% 
    as.data.frame() %>%
    transmute(
      Mean = mean %>% percent_formatter(., n=2),
      SD = sd %>% percent_formatter(.),
      Min = min %>% percent_formatter(.),
      Max = max %>% percent_formatter(.)
    ) 
indx = rownames(park.tab)
rownames(park.tab) = NULL
  cbind(
    Category = Category,
    Variables = indx,
    park.tab
  ) %>%
  kable() %>%
  kable_styling() %>%
  collapse_rows(., columns = 1)
Category Variables Mean SD Min Max
Occupation Rate t.pct.occ 40.77% 21% 0% 100%
Time Lag lag.blkbin.1bin 40.78% 21% 0% 100%
lag.blkbin.1day 40.95% 21% 0% 100%
lag.blkbin.week 41.07% 20% 0% 100%

Weather Plot

As the earlier correlation table showed, the weather patterns don’t line up with any of the changes in parking occupation. As a result, the weather variables will be left out

remove_x = theme(
  axis.text.x = element_blank(),
  axis.ticks.x = element_blank(),
  axis.title.x = element_blank()
)

path = 'C:/Users/nelms/Documents/Penn/MUSA-508/MUSA508_FINAL_SFPARK/data/sf_weather_2019.parquet'

oak.weather.daily = read_arrow(path) %>%
  dplyr::select(valid, tmpf, p01i, sknt) %>%
  replace(is.na(.), 0) %>%
    mutate(interval60 = ymd_h(substr(valid,1,13))) %>%
    mutate(week = week(interval60),
           #dotw = wday(interval60, label=TRUE),
           date = date(interval60)) %>%
    group_by(date) %>%
    dplyr::summarize(Temperature = max(tmpf),
              Precipitation = sum(p01i),
              Wind_Speed = max(sknt)) %>%
    mutate(Temperature = ifelse(Temperature == 0, 42, Temperature))

rain = ggplot(oak.weather.daily, aes(date,Precipitation)) + 
  geom_line(colour = "blue") + 
  labs(title="Percipitation", x="Day", y="Perecipitation") + plotTheme()

wind = ggplot(oak.weather.daily, aes(date,Wind_Speed)) + 
  geom_line(colour = "gold") + 
    labs(title="Wind Speed", x="Day", y="Wind Speed") + plotTheme()

temp = ggplot(oak.weather.daily, aes(date,Temperature)) + 
  geom_line(colour = "red") + 
    labs(title="Temperature", x="Day", y="Temperature") + plotTheme()

p = list(
  week = map_park('week', 'green', size=1,
                ylab = "Occupied %") + remove_x,
  rain + remove_x,
  wind + remove_x,
  temp
)
wrap_plots(p, nrow = 4) + plot_layout(guides = "collect") + 
  plot_annotation(title = "Weather Data - Oakland OAK - Daily 2019") + plotTheme()


rm(oak.weather.daily)

Correlation Plot

The correlation plot highlights the variables that have a higher linear relationship to the dependent variable (‘t.pct.occ’ is just percent occupation) and each other.


vars = c(  "t.pct.occ", 'avg.blk.day', 'avg.blk.weekday', 'avg.distbin.day', 'avg.distbin.weekday', 'avg.blkbin.weekday', 'households_car_pct', 'Med_Inc', 'Percent_White', 'lag.blkbin.1bin', 'lag.blkbin.1day', 'lag.blkbin.week')

# c("t.pct.occ","avg.blk.day", "avg.blk.weekday", "avg.blk.month", 
     #   "avg.blkbin.weekday", "avg.blkbin.month", "avg.distbin.day",
     #   "avg.distbin.weekday", "avg.distbin.month", "lag.blkbin.1bin",
     #   "lag.blkbin.1day","lag.blkbin.week", 'households_car_pct', 'Med_Inc', 'Percent_White')

park.corr = 
  park.import %>%
  dplyr::select(vars) %>% 
  na.omit(.)

corrmethod = 'pearson'

ggcorrplot::ggcorrplot(
  cor(park.corr, method=corrmethod) %>%
    round(., 2), # %>% sub("^0+", "", .),
    colors = c("#6D9EC1", "white", "#E46726"), 
    type="lower", ggtheme = plotTheme,lab_size=3.25,
    lab=TRUE
  ) +
  labs(
      title = "Pearson Correlation",
      subtitle = "SF Park Variables"
      )

rm(park.corr)

The largest takeaways are that the averages by block and either day or weekday are the strongest. However, there are interdependence issues with a few of the variables. As a result, I am taking out “avg.blkbin.month”, “avg.blk.month”, and “avg.distbin.month”.

Model

Describe your modeling approach and show how you arrived at your final model.

To partition the dataset, we split the model by 75% for training and 25% for testing purposes. The partition is at the beginning of Quarter 2 (April 1st).


path = "C:/Users/nelms/Documents/Penn/MUSA-508/MUSA508_FINAL_SFPARK/data/sfpark_clean_data.parquet"
# write_parquet(
#   park.import,
#   path
# )

park.import = read_parquet(path)

vars = c( 
  "t.hrs.occ", "t.hrs.tot", "t.pct.occ", "bin",
  "avg.blk.day", 
  "avg.blk.weekday", #"avg.blk.month", 
  "avg.blkbin.weekday", 
  #"avg.blkbin.month", 
  "avg.distbin.day", "avg.distbin.weekday", #"avg.distbin.month", 
  "lag.blkbin.1bin", "lag.blkbin.1day", "lag.blkbin.week", 
  "quad", "households_car_pct", "Med_Inc", "Percent_White"
  )

vars = c(
  "occ50", 'id.block', 'quad', 'bin', 'day.week', 'avg.blk.day', 'avg.blk.weekday', 'avg.distbin.day', 'avg.distbin.weekday', 'avg.blkbin.weekday', 'households_car_pct', 'Med_Inc', 'Percent_White', 'lag.blkbin.1bin', 'lag.blkbin.1day', 'lag.blkbin.week', 'id.dist'
         )

partition_day = ymd('2019-04-01')
park.import$dataset = 'Train'
park.import[park.import$date < partition_day, 'dataset'] = 'Test'

park.train = park.import %>% filter(dataset == 'Train') %>% dplyr::select(vars)
park.test = park.import %>% filter(dataset == 'Test') %>% dplyr::select(vars)

Surprisingly, based on the table below, the Testing dataset of Q2-Q4 is exactly 75% of the data and the occupation rate appears to not be too different.



tab = 
  describeBy(
    park.import  %>% 
      dplyr::select(t.pct.occ, dataset),
    group='dataset', mat = TRUE,
    na.rm = TRUE
  ) %>%
  as.data.frame() %>%
  #filter(item>3) %>%
  arrange(desc(item)) %>%
  dplyr::select(-item, -vars) %>%
  tail(., -2) #%>%
  # arrange(fct_relevel(group1, c('9a to 12p','12p to 3p','3p to 6p')))

rownames(tab) = NULL

tab %>%
  transmute(
    `Dataset` = group1,
    `Time Covered` = c('Apr - Dec','Jan - Mar'),
    Count = c(nrow(park.train), nrow(park.test)) %>% format(., big.mark=','),
    `% of All` = percent_formatter(c(nrow(park.train)/ nrow(park.import), nrow(park.test)/ nrow(park.import)) , n=0),
    Mean = mean %>% percent_formatter(., n=1),
    Median = median  %>% percent_formatter(., n=1),
    SD = sd %>% percent_formatter(.),
    Min = min %>% percent_formatter(.),
    Max = max %>% percent_formatter(.)
  ) %>% 
  kable(title = 'Parking Meter Occupation Rate') %>%
  kable_styling()
Dataset Time Covered Count % of All Mean Median SD Min Max
Train Apr - Dec 1,047,069 75% 40.7% 41.5% 21% 0% 100%
Test Jan - Mar 348,456 25% 41.0% 42.0% 21% 0% 100%

rm(park.import)

Model 1 – All Blocks

For the first model, we are only looking at 4 basic predictor variables for the entire city. This is largely due to the dataset size. But it will also give an opportunity to focus.

# # TIME

lm.all.time_location =
  glm(
    occ50 ~ bin + day.week + quad + avg.blkbin.weekday,
    data=park.train,
    family="binomial" (link="logit")
    )
gc()

One of the main reasons our team uses a binomial logistic regression is to evaluate the odds of a highly demanded parking block. So this linear model summary includes the odds ratio, which suggests the following options:

  1. OR < 1 - suggests that the variable is associate with lower odds of high demand parking (+50%)

  2. OR > 1 - suggests that the variable is associate with higher odds of high demand parking (+50%)

  3. OR = 1 - suggests that the variable doesn’t affect the odds


dv = 'occ50'

col_mae = function(column) column %>% abs(.) %>% mean(., na.rm=T)
col_mape = function(column.res, column.actual) abs(column.res/column.actual) %>% mean(., na.rm=T)
library(generics)
#aug_lm = augment(lm.all.time_location)

park.train =
  park.train %>%
  na.omit() %>%
  mutate(
    occ50.predict   = predict(lm.all.time_location, .))

Category = c(
  '',
  'Time Period',
  'Time Period',
  'Day of\nthe Week',
  'Day of\nthe Week',
  'Day of\nthe Week',
  'Day of\nthe Week',
  'City Quadrant',
  'City Quadrant',
  'City Quadrant',
  'Average\nBlock & Bin'
)

path = "C:/Users/nelms/Documents/Penn/MUSA-508/MUSA508_FINAL_SFPARK/data/park_all_summary.parquet"
park.all.summ.CI = read_arrow(path) %>%
  cbind(
    Category = Category,
    .
  )

# park.all.summ =
#   lm.all.time_location %>%
#   tidy() %>%
#   filter(!grepl("id.dist",term)) %>%
#   mutate(
#     Variable = term,
#     Estimate = round_thresh(estimate, thresh = .0001, digits=4),
#     std.error = round_thresh(std.error, thresh = .0001, digits=4),
#     t.value = round_str(statistic, 2),
#     p.value = p.value %>% round_thresh()
#   ) %>%
#   dplyr::select(Variable, Estimate, std.error, t.value, p.value)
# 
# park.all.CI =
#   exp(
#     cbind(
#       OR = coef(lm.all.time_location),
#       confint(lm.all.time_location)
#       )
#     ) %>%
#   round(3) %>%
#   as.data.frame()
# park.all.CI$Variable = rownames(park.all.CI)
# 
# park.all.summ.CI =
#   park.all.summ %>%
#   merge(
#     .,
#     park.all.CI,
#     on='Variable',
#     sort = FALSE
#   )
# 
# path = "C:/Users/nelms/Documents/Penn/MUSA-508/MUSA508_FINAL_SFPARK/data/park_all_summary.parquet"
# park.all.summ.CI %>% write_arrow(path)

# rm(park.all.CI)
# rm(park.all.summ)



cols = colnames(park.all.summ.CI)
#park.all.summ.CI = 
  park.all.summ.CI %>%
  mutate(
    Estimate = Estimate %>% as.numeric() %>% round(2),
    std.error = std.error %>% as.numeric() %>% round(2),
    OR = OR %>% 
      round_thresh(., int_check = F, commas = T),
    `2.5 %`=`2.5 %` %>% 
      round_thresh(., int_check = F, commas = T),
    `97.5 %`=`97.5 %` %>% 
      round_thresh(., int_check = F, commas = T)
  )  %>%
  flextable(.) %>%
  theme_vanilla(.) %>%
  align(., 
        align = "center", 
        part = "header") %>%
  align(., j=3:length(cols), 
        align = "right", 
        part = "body") %>%
  set_table_properties(
    ., layout='autofit') %>%
  add_footer_row(
    ., 
    values=c("2.5% & 97.5% Confidence Intervals"), 
    colwidths = c(length(cols))) %>%
  add_footer_row(
    ., 
    values=c("OR = Odds Ratio"), 
    colwidths = c(length(cols))) %>%
  merge_v(j = ~Category)

SO this summary table tells us that the city quadrants tell us the most about whether parking is not in high demand (occupation < 50%). The Days of the Week and time periods oddly are very close to 1 – suggesting that they alone cannot predict any changes. The extremely high OR of the Average Weekend Occupancy is potent because it is directly providing occupation numbers – when the others generalized the region or timeframe.

The variable’s weakness suggests that this model is too generalized and needs more accuracy.

Model 2 – Downtown Blocks

The next model takes a more in-depth look into the San Francisco Downtown (Quadrant 4). The reason to refocus is largely due to the size of the data with added variables.

With a smaller focus area, the model can now use more variables. Specifically the lag, census, district ids, and averages. This will provide a more accurate prediction.


dwtn_vars = c(
  "occ50", 'id.dist', #'id.block', 
  'bin', 'day.week', 
  'avg.blk.day', 'avg.blk.weekday', 'avg.distbin.day', 'avg.distbin.weekday', 'avg.blkbin.weekday', 
  'households_car_pct', 'Med_Inc', 'Percent_White', 
  'lag.blkbin.1bin', 'lag.blkbin.1day', 'lag.blkbin.week'
         )

park.train.dwtn = park.train %>% 
      filter(quad == 'NE') %>%
      dplyr::select(dwtn_vars)
## Note: Using an external vector in selections is ambiguous.
## i Use `all_of(dwtn_vars)` instead of `dwtn_vars` to silence this message.
## i See <https://tidyselect.r-lib.org/reference/faq-external-vector.html>.
## This message is displayed once per session.

lm.dwtn.all =
  glm(
    occ50 ~ .,
    data=park.train.dwtn,
    family="binomial" (link="logit")
    )

park.train.dwtn =
  park.train %>%
  filter(quad == 'NE') %>%
  na.omit() %>%
  mutate(
    occ50.predict   = predict(lm.dwtn.all, .))

# lm.dwtn.all %>%
#   tidy() %>%
#   transmute(
#     Variable = term, 
#     Estimate = percent_formatter(estimate, n = 1),
#     std.error = percent_formatter(std.error, n=1) ,
#     t.value = format_nums(statistic),
#     p.value = p.value %>% round_thresh()
#   ) %>%
#   kable(
#     label = NA,
#     caption = glue('Training Model Summary'),
#     align = 'lrrrr') %>%
#   kable_styling()

gc()
        used  (Mb) gc trigger   (Mb) limit (Mb)  max used   (Mb)

Ncells 4575380 244.4 9934951 530.6 NA 9934951 530.6 Vcells 118880979 907.0 243453243 1857.5 102400 243443862 1857.4

# path = "C:/Users/nelms/Documents/Penn/MUSA-508/MUSA508_FINAL_SFPARK/data/lm_dwtn_id.parquet"
# lm.dwtn.all %>%
#   tidy() %>% 
#   write_arrow(., path)

Category = c(
  '',
  'Time Period',
  'Time Period',
  'Day of\nthe Week',
  'Day of\nthe Week',
  'Day of\nthe Week',
  'Day of\nthe Week',
  'Average\nBlock',
  'Average\nBlock',
  'Average\nDistrict & Bin',
  'Average\nDistrict & Bin',
  'Average\nBlock & Bin',
  'Census',
  'Census',
  'Census',
  'Time Lag',
  'Time Lag',
  'Time Lag'
)

path = "C:/Users/nelms/Documents/Penn/MUSA-508/MUSA508_FINAL_SFPARK/data/park_dwtn_summary.parquet"
park.dwtn.summ.CI = read_arrow(path) %>%
  mutate(
    OR = OR %>% 
      round_thresh(., int_check = F, commas = T),
    `2.5 %`=`2.5 %` %>% 
      round_thresh(., int_check = F, commas = T),
    `97.5 %`=`97.5 %` %>% 
      round_thresh(., int_check = F, commas = T)
  ) %>%
  cbind(
    Category = Category,
    .
  )

# park.dwtn.summ =
#   lm.dwtn.all %>%
#   tidy() %>%
#   filter(!grepl("id.dist",term)) %>%
#   mutate(
#     Variable = term,
#     Estimate = round_thresh(estimate, thresh = .0001, digits=4),
#     std.error = round_thresh(std.error, thresh = .0001, digits=4),
#     t.value = round_str(statistic, 2),
#     p.value = p.value %>% round_thresh()
#   ) %>%
#   dplyr::select(Variable, Estimate, std.error, t.value, p.value)
# 
# park.dwtn.CI =
#   exp(
#     cbind(
#       OR = coef(lm.dwtn.all),
#       confint(lm.dwtn.all)
#       )
#     ) %>%
#   round(3) %>%
#   as.data.frame()
# #park.dwtn.CI$Variable = rownames(park.dwtn.CI)
# 
# park.dwtn.CI$Variable = rownames(park.dwtn.CI)
# park.dwtn.CI =
#   park.dwtn.CI %>%
#   filter(!grepl("id.dist",Variable))
# 
# 
# park.dwtn.summ.CI =
#   merge(
#     park.dwtn.summ,
#     park.dwtn.CI,
#     on='Variable',
#     sort = FALSE
#   )

# # park.dwtn.summ.CI %>%
# #   write_arrow(path)

cols = colnames(park.dwtn.summ.CI)
#park.dwtn.summ.flex = 
  park.dwtn.summ.CI %>%
  mutate(
    Estimate = ifelse(Variable=='Med_Inc',.01,Estimate %>% as.numeric() %>% round(2)),
    std.error = ifelse(Variable=='Med_Inc',.01,std.error %>% as.numeric() %>% round(2)),
    t.value = t.value %>% as.numeric() %>% round(0),
  ) %>%
  flextable(.) %>%
  theme_vanilla(.) %>%
  align(., 
        align = "center", 
        part = "header") %>%
  align(., j=3:length(cols), 
        align = "right", 
        part = "body") %>%
  set_table_properties(
    ., layout='autofit') %>%
  add_footer_row(
    ., 
    values=c("2.5% & 97.5% Confidence Intervals"), 
    colwidths = c(length(cols))) %>%
  add_footer_row(
    ., 
    values=c("OR = Odds Ratio"), 
    colwidths = c(length(cols))) %>%
  merge_v(j = ~Category)

The summary table for the second model suggests that it is more accurate. Some averages provide heavier predictions – such as the Average Block Occupancies. But many sit just around an OR of 1.

Compare Models

A higher degrees of freedom suggests a larger number of parameters used by the fitting procedure. Combined with the larger number of variables used, the Downtown Model is more complex.



park.all.glance = 
  lm.all.time_location %>% glance()

park.dwtn.glance = 
  lm.dwtn.all %>% glance()


park.BOTH.glance = 
  rbind(
    park.all.glance,
    park.dwtn.glance
  )

park.AIC = AIC(lm.all.time_location, lm.dwtn.all)

cols = colnames(park.AIC)
#park.AIC.flex = 
  park.AIC %>%
  transmute(
    `Training\nModel` = c('All Blocks', 'Downtown Blocks'),
    Variables = c(4,14),
    `Observations` = c(nrow(park.train), nrow(park.train.dwtn)) %>% format(big.mark=','),
    AIC = AIC %>% round(0) %>% format(big.mark=',', nsmall = 0, justify='right'),
    `Degrees of\nFreedom` = df
  ) %>%
  flextable(.) %>%
  theme_vanilla(.) %>%
  align(., 
        align = "center", 
        part = "header") %>%
  align(., j=2:5, 
        align = "right", 
        part = "body") %>%
  set_table_properties(
    ., layout='autofit') %>%
  add_footer_row(
    ., 
    values=c("Akaike Information Criterion"), 
    colwidths = c(length(cols))+3)

Validation / Goodness of Fit

The AIC statistic only suggests that the Downtown model has comparatively less predictive error than the original All Blocks model. To determine the true predictive nature of our binomial logistic model, we will:

  1. Determine the Cut-Off threshold of how to label the outcomes of our predicted dependent variables,

  2. Evaluate the Specificity, Sensitivity, & Misclassification rates,

  3. Use a ROC Curve to determine the quality of our fit, then

  4. Cross-Validate the model.

Because the Downtown Model outperformed the first All Blocks model, the following goodness of fit metrics will primarily evaluate the Downtown Model


get_cut_off_values = function(
  focus_dv = 'occ50',
  focus_df = park.train.dwtn,
  focus_glm = lm.dwtn.all,
  cut_off = 0.05
){
  fit = focus_glm$fitted.values
  
  #a is a matrix combining the vectors containing y and y-hat in matrix a; first variable is
  #DRINKING_D, which is y; second variable is fit, which is y-hat
  a = cbind(
    focus_df[[focus_dv]],
    fit
    )
  
  #b is matrix a, just sorted by the variable fit
  b = a[order(a[,2]),]
  
  #Calculating variable c which is 1 if y-hat (second column of matrix b) is greater
  #than or equal to 0.05 and 0 otherwise.
  
  #Other cut-offs can be used here!
  
  cut_offs = c(cut_off)
  
  c = (b[,2] >= cut_offs[1])
  c_colnames = glue("Prob.Above{str_remove(cut_offs[1], '^0+')}")
  
  if (length(cut_offs)>1){
    for (
      current_cut_off in cut_offs[2:length(cut_offs)]
      ){
      c = cbind(
        c,
        (b[,2] >= current_cut_off)
        )
      current_cut_off = current_cut_off %>% str_remove(., "^0+")
      c_colnames = 
        c(c_colnames, glue("Prob.Above{current_cut_off}"))
      }}
  
  #Creating matrix d which merges matrixes b and c
  d = cbind(b,c)
  
  #Let's label the columns of matrix d for easier reading
  colnames(d) = c(
    glue("Observed.{focus_dv}"),
    glue("Probability.{focus_dv}"),
    c_colnames
    )
  
  #Converting matrix to data frame
  e = as.data.frame(d)
  
  return(e)
}

get_sens_spec_miss = function(
  cut_off = cut_off_list[1],
  focus_dv = 'Observed.occ50',
  focus_df = cut_off_values
){
  str_cut_off = cut_off %>% str_remove(., "^0+")
  focus_cut_off = glue("Prob.Above{str_cut_off}")
  focus_formula = as.formula(glue("~ {focus_cut_off} + {focus_dv}"))
  focus_dv_cut_off = xtabs(formula=focus_formula, data=focus_df) %>% as.matrix()
  a = focus_dv_cut_off[1]
  b = focus_dv_cut_off[3]
  c = focus_dv_cut_off[2]
  d = focus_dv_cut_off[4]
  
  Sensitivity = d/(b+d)
  Specificity = a/(a+c)
  Misclassification = (b+c)/(a+b+c+d)
  
  return(data.frame(
    Cut_Off = cut_off,
    Sensitivity = Sensitivity,
    Specificity = Specificity,
    Misclassification = Misclassification
  ))
}

Determine Cut-Off Threshold

Our testing model only gave us the probability of predicting that the parking meter occupations are above 50%. That probability, needs a cut-off threshold that labels its binomial outcome. The threshold will never perfectly divide the predictions into their observed outcomes.

To determine this misclassification rate, we first run the testing model on the training dataset to find predictive values. With those predictions of Meters above 50% and observations, we can find misclassification rates with hypothetical cut-offs.

The table below shows proposed cut-off thresholds along with their Sensitivity (True Positive Rate), Specificity (True Negative Rate), and Misclassification rate (combined False Positive & Negative Rate). Initially it seems best to pick the threshold with the least misclassification (50%), but it would be best to pick a threshold that produces higher correct predictions – highest possible Sensitivity and Specificity.

Using a function that finds where the Sensitivity and Specificity rates intersect, we find that the optimum cut-off threshold is 40.4%.


# lm.dwtn.all$labels
# 
library(ROCR)

focus_dv = 'occ50'
focus_df = park.train.dwtn
focus_glm = lm.dwtn.all

focus.ROC = cbind(focus_df[[focus_dv]], focus_glm$fitted.values) %>%
  as.data.frame()
colnames(focus.ROC) = c("labels","predictions")


pred = prediction(focus.ROC$predictions, focus.ROC$labels)

ROC.perf = performance(pred, measure = "tpr", x.measure="fpr")

####

opt.cut = function(perf, pred){
  cut.ind = mapply(FUN=function(x, y, p){
    d = (x - 0)^2 + (y-1)^2
    ind = which(d == min(d))
    c(sensitivity = y[[ind]], 
      specificity = 1-x[[ind]], 
      cutoff = p[[ind]])
  }, perf@x.values, perf@y.values, pred@cutoffs)
}

cut_off_opt_df = opt.cut(ROC.perf, pred) %>% 
  t() %>% as.data.frame()
cut_off_opt = cut_off_opt_df$cutoff


cut_off_list = c(0.01, 0.10, 0.25, 0.50, .75, .90)
cut_off_list = c(cut_off_list,cut_off_opt)
cut_off_values = get_cut_off_values(
  cut_off = cut_off_list)


park.dwtn.cutoff = get_sens_spec_miss(
  focus_df = cut_off_values,
  cut_off=cut_off_list[1])

for (curr_cut_off in cut_off_list[2:length(cut_off_list)]){
      park.dwtn.cutoff = rbind(
        park.dwtn.cutoff,
        get_sens_spec_miss(
          focus_df = cut_off_values,
          cut_off = curr_cut_off)
      ) %>% as.data.frame()
      row.names(park.dwtn.cutoff) = NULL}
park.dwtn.cutoff = 
  park.dwtn.cutoff %>% 
  round(3) %>% arrange(Cut_Off) %>%
  mutate(
    Cut_Off = sub("0+$", "", as.character(Cut_Off)) %>%
      str_remove(., "^0+")
  )

cols = colnames(park.dwtn.cutoff)
new_cols = setNames(
  replace(cols, cols=='Cut_Off', 'Cut-Off Value'), 
  cols)

#park.dwtn.cutoff.flex = 
  park.dwtn.cutoff %>%
  mutate(Cut_Off = 
           ifelse(Cut_Off=='.404',
                  '40.4%*',
                  Cut_Off %>% 
                    as.numeric() %>%
                    percent_formatter()
                  ),
         Sensitivity = Sensitivity %>% 
                    as.numeric() %>%
                    percent_formatter(n=1),
         Specificity = Specificity %>% 
                    as.numeric() %>%
                    percent_formatter(n=1),
         Misclassification = Misclassification %>% 
                    as.numeric() %>%
                    percent_formatter(n=1)
         ) %>%
  flextable(., col_keys = cols) %>%
  set_header_labels(
    ., 
    values = new_cols) %>%
  theme_vanilla(.) %>%
  align(., 
        align = "center", 
        part = "header") %>%
  align(., j=2:length(cols), 
        align = "right", 
        part = "body") %>%
  align(., j=1, 
        align = "center", 
        part = "body") %>%
  set_table_properties(
    ., layout='autofit') %>%
  bg(., i = ~ Cut_Off == '40.4%*', bg = "wheat", part = "body") %>%
  add_footer_row(
    ., 
    values=c("* Optimium Cut-Off"), 
    colwidths = c(length(cols)))

The confusion matrix for the threshold of .404 (below) highlights the True/False Negative/Positive rates that form the previously discussed rates. Our True Negatives (a) and Positives (b) are fairly high – which is a good sign of our model’s accuracy.



park.test.dwtn = data.frame(
  id.block = park.test %>% 
    filter(quad=='NE') %>% na.omit() %>% 
    pull(id.block) %>% as.factor(.),
  bin = park.test %>% 
    filter(quad=='NE') %>% na.omit() %>% 
    pull(bin) %>% as.factor(.),
  occ50 = park.test %>% 
    filter(quad=='NE') %>% na.omit() %>% 
    pull(occ50) %>% as.factor(.),
  occ50.predict.pct = predict(
    lm.dwtn.all, 
    park.test %>% 
    filter(quad=='NE') %>% na.omit(), 
    type= "response")
  ) %>%
  mutate(
    occ50.predict  = 
      as.factor(
        ifelse(
          occ50.predict.pct > cut_off_opt,
          TRUE, 
          FALSE
          )))
testlen = nrow(park.test.dwtn)
park.text.dwtn.mx = caret::confusionMatrix(
  park.test.dwtn$occ50.predict, 
  park.test.dwtn$occ50) %>% 
  as.matrix(., what = "xtabs") %>%
  as.data.frame() %>%
  transmute(
    Results = c('Predicted\nOutcome'),
    Outcome = c("Below 50%\n(FALSE)","Above 50%\n(TRUE)"),
    `Below 50%\n(FALSE)` = (`FALSE`/testlen) %>% percent_formatter() %>%
      paste(c('(a)','(c)'), ., sep=' '),
    `Above 50%\n(TRUE)` = (`TRUE`/testlen) %>% percent_formatter() %>%
      paste(c('(b)','(d)'), ., sep=' ')
  )
rownames(park.text.dwtn.mx) = NULL

rename_cols = setNames(
  c("", "", "Below 50%\n(FALSE)", "Above 50%\n(TRUE)" ), 
  colnames(park.text.dwtn.mx))


park.text.dwtn.mx %>%
  flextable(.) %>%
  theme_vanilla(.) %>%
  align(., 
        align = "center", 
        part = "header") %>%
  align(., j=2:4, 
        align = "center", 
        part = "body") %>%
  align(., j=1, 
        align = "center", 
        part = "body") %>%
  set_table_properties(
    ., layout='autofit') %>%
  merge_v(j = ~Results) %>%
  set_header_labels(
    ., 
    values = rename_cols) %>%
  add_header_row(
    ., 
    values = c('', 'Observed Outcome'), 
    colwidths = c(2,2)) %>%
  bold(., i = 1:2, j = 1:2, bold = TRUE, part = "body") %>%
  add_footer_row(
    ., 
    values=c("Specificity =  a/(a+c)\nSensitivity =  d/(d+b)\nMisclassification Rate =  (b+c)/(a+b+c+d)"), 
    colwidths = c(4))
# False Negative True Negative ## Ref No Click Ref Click ## Pred No Click Pred No Click # False Positive True Positive ## Ref No Click Ref Click ## Pred Click Pred Click

The Predicted Probabilities graph below provides a visual of the confusion matrix. The True Negatives (left side of the bottom, blue curve) are accurately predicting occupancy rates (forming 50% of total observations) – which can be seen in the curve peaking at a prediction rate of 10%. The True/False Positives (top, red curve) are more concerning as there isn’t a distinct peak to the right of the threshold. As a result, there are more False Positives (12% of total) which suggests our model does not have enough predictor variables that account for peaks of high meter occupancy.


auc.perf = performance(pred, measure ="auc")
AUC = auc.perf@y.values[[1]] %>% round(4)

palette2 = c("#981FAC","#FF006A")

ggplot() + 
  # geom_density(
  #   data=focus.ROC,
  #   aes(x = predictions, group=labels), 
  #   fill='grey90', color='black', lwd=1.5
  #   ) +
  geom_density(
    data=focus.ROC %>%
      mutate(
        labels = ifelse(
          labels==1,
          'Above 50% Occupancy',
          'Below 50% Occupancy'
      )),
    aes(x = predictions, group=labels, fill=labels, color=labels),
    fill='grey90', lwd=1.25 #, color='orange'
    # linetype='dashed'
    ) +
  facet_grid(labels ~ .) +
  scale_fill_manual(values = palette2) +
  labs(
    x = "Probability", 
    y = "Density of Probabilities",
    title = "Predicted Probabilities - Downtown Model",
    subtitle = glue("Optimal Cut-Off Rate = {cut_off_opt %>% percent_formatter(n=1)}  (dashed line)")
    ) + 
  scale_x_continuous(
    labels = function(num) num %>% percent_formatter(),
    name="Probability") + 
  geom_vline(xintercept=cut_off_opt,
             linetype='dashed') + 
  theme(strip.text.x = element_text(size = 18),
        legend.position = "none") + 
  plotTheme()

#dev.off()

ROC Curve

The ROC curve is a goodness-of-fit plot of the true positive rate (i.e., sensitivity) against false positive rate (i.e., specificity). A curve at a 45 degree angle (i.e. just a diagonal line) suggests the model is inaccurate. A curve with a perfect 90 degree angle would be very predictive but is likely the result of over fitting. In the chart below, our curve’s bow shape is a good sign that our model is accurately predicting without over-fitting.

The area under our model’s ROC curve is 0.88. An AUC value that falls in the 0.60-0.70 range is considered poor. This tells us that our model is fairly accurate.


#library(rROC)

library(plotROC)

ggplot() +
  geom_roc(
    data = focus.ROC, 
    aes(d = labels, m = predictions),
    n.cuts = 50, labels = FALSE, colour = "#000000") +
  # geom_roc(
  #   data = test.predict, 
  #   aes(d = as.numeric(test.predict$outcome), m = predict.2),
  #   n.cuts = 50, labels = FALSE, colour = "#FE9900") +
  style_roc(theme = theme_grey) +
  geom_abline(slope = 1, intercept = 0, size = 1.5, color = 'grey') +
  labs(
    title = "ROC Curve - Downtown Model",
    subtitle = glue("Area Under Curve = {AUC}")
    )

CV

library(caret)

park.train.dwtn.prep = 
  park.train.dwtn %>%
      dplyr::select(dwtn_vars) %>%
      mutate(occ50 = ifelse(occ50==TRUE, "ABOVE50", "BELOW50"))  %>%
  na.omit()
park.train.dwtn.prep$occ50 = park.train.dwtn.prep$occ50 %>% as.factor() 

# control CV parameters
ctrl = trainControl(
  method = "cv", 
  number = 100, 
  classProbs=TRUE,
  summaryFunction=twoClassSummary #(data=park.train.dwtn.prep, lev=c(1,0))
  )

form = colnames(park.train.dwtn.prep)
form = form[form!='occ50'&form!='id.dist']
#form = paste(form, collapse=' + ') %>% paste('occ50', ., sep=' ~ ') %>% as.formula()
library(pROC)
## Type 'citation("pROC")' for a citation.
## 
## Attaching package: 'pROC'
## The following object is masked from 'package:plotROC':
## 
##     ggroc
## The following objects are masked from 'package:stats':
## 
##     cov, smooth, var
# cv LM
park.cv.dwtn = caret::train(
  x = park.train.dwtn.prep %>% dplyr::select(form),
  y = park.train.dwtn.prep$occ50,
  method="glm", 
  family="binomial",
  metric="ROC", 
  trControl = ctrl
  )
## Note: Using an external vector in selections is ambiguous.
## i Use `all_of(form)` instead of `form` to silence this message.
## i See <https://tidyselect.r-lib.org/reference/faq-external-vector.html>.
## This message is displayed once per session.

dplyr::select(park.cv.dwtn$resample, -Resample) %>%
  gather(metric, value) %>%
  left_join(gather(park.cv.dwtn$results[2:4], metric, mean)) %>%
  ggplot(aes(value)) + 
    geom_histogram(bins=35, fill = "#FF006A") +
    facet_wrap(~metric) +
    geom_vline(aes(xintercept = mean), colour = "#981FAC", linetype = 3, size = 1.5) +
    #scale_x_continuous(limits = c(0, 1)) +
    labs(x="Goodness of Fit", y="Count", title="Cross Validation -- Measure Goodness of Fit",
         subtitle = "Comparing Observed Training Results to k-fold permutations")
## Joining, by = "metric"

Map

This map displays the misclassification rate of the Downtown model. It helps the team evaluate which geographic areas are being misclassified. It appears that blocks with heavier occupations also have higher missclassifications. This aligns with the Test model summary and ROC results that we are predicting better for lower occupancies and not higher occupancies.

#SF.quad.labels = sf_to_labels(SF.quad, 'quad') %>% dplyr::filter(label=='NE')
SF.dist.labels = sf_to_labels(
  SF.dist %>% arrange(quad) %>% 
    dplyr::filter(quad=='NE') %>%
    dplyr::filter(!PM_DISTRICT_NAME  %in% c(
      'Polk', 'Telegraph Hill'
    )), 'PM_DISTRICT_NAME') %>%
  mutate(label = 
    ifelse(
      label == 'N.Beach-Chinatown',
      'N.Beach\n\nChinatown',
    ifelse(
      label == "Fisherman's Wharf",
      "\nFisherman's Wharf",
           label)
      ) #%>% gsub(' ','\n', ., fixed = TRUE)
    )

park.map = 
  park.test.dwtn %>% 
    dplyr::group_by(id.block, occ50, occ50.predict) %>%
    dplyr::summarize(
      count = n()
    ) %>%
  mutate(
    Outcome = 
      case_when(
        occ50 == TRUE & occ50.predict == TRUE ~ 'True Positive',
        occ50 == TRUE & occ50.predict == FALSE ~ 'False Negative',
        occ50 == FALSE & occ50.predict == TRUE ~ 'False Positive',
        occ50 == FALSE & occ50.predict == FALSE ~ 'True Negative'
      )
  ) %>% ungroup() %>%
  dplyr::select(id.block,Outcome,count) %>% 
  dcast(., id.block~Outcome, fill=0) %>%
  mutate(
    Misclassification = (`False Negative` + `False Positive`) / 
      (`False Negative` + `False Positive` + `True Negative` + `True Positive`)
  ) %>% 
  merge(
    .,
    park.blocks,
    on='id.block'
  ) %>%
  st_sf() %>%
  st_buffer(50)
## `summarise()` has grouped output by 'id.block', 'occ50'. You can override using the `.groups` argument.
## Using 'count' as value column. Use 'value.var' to override

#park.map$bin = factor(park.map$bin, levels = c("All","9a to 12p", "12p to 3p", "3p to 6p"))
# park.map$day.week = factor(
#   park.map$day.week,
#   levels = 
#     c("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"))
sep = 10000
ggplot()+
  geom_sf(
    data = SF.quad %>% 
      dplyr::filter(quad=='NE'), color = 'transparent', fill='grey90'
  ) +
  geom_sf(data = park.map,
          aes(fill = Misclassification), #, size=meters.count), 
          color = "transparent", alpha = 0.95
          ) +
  geom_text_sf(
    SF.dist.labels, check_overlap = TRUE,
    
    #hjust = "top"#, vjust = "outward"
    ) + 
  xlim(min(SF.dist.labels$lon)-sep, max(SF.dist.labels$lon)+sep) + 
  scale_fill_gradientn(
    colors = RColorBrewer::brewer.pal(5, "YlOrRd"),
    values = c(0,.45,.65,.85,1),
    labels = function(tstr) percent_formatter(tstr)
    #direction = -1, discrete = FALSE, option = "D"
    )+
  guides(fill = guide_colourbar(barwidth = 2)) + 
  #facet_wrap(~bin, ncol = 2)+
  labs(
    title='Testing Set Misclassification Rate',
    subtitle = 'San Francisco, 2019'
    )+
  mapTheme

geom_text()

Conclusion

Use Case Results

Like any good Silicon Valley app, our app is promising in theory but a devil’s errand in reality. The app’s ability to predict open parking spots hinges on the a single city’s uncleaned dataset. Regardless, the second, downtown model appeared to provide a rough estimation of which blocks are more occupied in a given area or time. Our final application would simply take in the list of blocks and their predicted odds.

Improvements

Before the app goes to market, I believe there are large gaps for improvement, specifically for the:

  1. Dependent Variable It would be better to determine the probability that a single parking spot is open. A block could have a low average occupation rate but only have 2 parking spots – that could get filled up. This would require a heavier amount of cleaning and a different model.

At the same time, the meter data only provides parking space times that have metered transactions. The city still has a large amount of illegal parking and free neighborhood parking.

  1. Model Type The app’s focus on finding a single parking spot suggests it would be better to look at the probability of 1 or 2 spots being open. A Temple-based spatial statistics professor suggested to the team that a ordinal logistic regression model could find the probability of 0, 1, or 2+ spots being open. The model’s added complexity is, however, outside the confines of this app development team’s current timeline and pay grade.

  2. Improved Data Cleaning The cleaned parking meter transactions provide an average occupancy rate (~42%) that feels tremendously lower than anecdotal evidence or SFMTA’s pricing rates (which suggest 60-80% occupancy). Overall the parking dataset is large and murky. Many transaction appear to be administrative (e.g. parking transaction from midnight to 10am) but there isn’t a column describing this transaction. If the app garners public support, SFMTA could provide the team with a cleaner dataset.

  3. Improved Predictor Variables The model had a lower AIC score and a promising ROC Curve, but the relatively low True Positive Rate suggests that the model is more ambiguous at predicting higher occupancy parking meters. Some helpful predictor variables that could predict high demand are: event times & locations, commercial center locations, and San Francisco landmarks. Another set of predictors are the location of free on-street parking spaces and paid off-street parking spots.

  4. Enlarging Study Area & City The final model only looked at Downtown San Francisco and at metered spots. IF we had the data for non-metered parking spots, the app user would benefit in not having to spend money.

LS0tDQp0aXRsZTogIk1VU0E1MDhfRklOQUxfU0ZQQVJLIg0KYXV0aG9yOiAiQWxleGFuZGVyIE5lbG1zICYgR2lhbmx1Y2EgTWFuZ2lhcGFuZSINCmRhdGU6ICJEZWNlbWJlciAxNywgMjAyMSINCmVkaXRvcl9vcHRpb25zOiANCiAgY2h1bmtfb3V0cHV0X3R5cGU6IGlubGluZQ0Kb3V0cHV0OiANCiAgaHRtbF9kb2N1bWVudDoNCiAgICB0b2M6IHRydWUNCiAgICB0b2NfZmxvYXQ6IHRydWUNCiAgICBjb2RlX2ZvbGRpbmc6ICJoaWRlIg0KICAgIGNvZGVfZG93bmxvYWQ6IHRydWUNCi0tLQ0KDQpbUHJlc2VudGF0aW9uIFZpZGVvXShodHRwczovL3d3dy55b3V0dWJlLmNvbS93YXRjaD92PTh3NHhzWElvQ3dvKQ0KDQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0NCmxpYnJhcnkoYm9va2Rvd24pDQprbml0cjo6b3B0c19jaHVuayRzZXQoDQogIGVjaG89VFJVRSwNCiAgaW5jbHVkZT1UUlVFLA0KICB3YXJuaW5nPUZBTFNFLA0KICBtZXNzYWdlcz1GQUxTRSwNCiAgZmlnLndpZHRoID0gOCwNCiAgZmlnLmtlZXAgPSAnYWxsJywNCiAgY29sbGFwc2U9VFJVRSwNCiAgY2FjaGU9VFJVRSwgDQogIGF1dG9kZXA9VFJVRSwNCiAgcmVzdWx0cz1GQUxTRQ0KICApDQpvcHRpb25zKHRpZ3Jpc19jbGFzcyA9ICJzZiIpDQpvcHRpb25zKHNjaXBlbiA9IDk5OSkNCmBgYA0KDQojIEludHJvZHVjdGlvbg0KKipQcm9qZWN0IG9wdGlvbiA3OiBGb3JlY2FzdGluZyBwYXJraW5nIGRlbWFuZCoqDQoNCmBgYCB7ciBpbnRybywgaW5jbHVkZT1GQUxTRX0NCg0KbGlicmFyeSh0aWR5dmVyc2UpDQpsaWJyYXJ5KHNmKQ0KbGlicmFyeShsdWJyaWRhdGUpDQpsaWJyYXJ5KHRpZ3JpcykNCmxpYnJhcnkodGlkeWNlbnN1cykNCmxpYnJhcnkodmlyaWRpcykNCmxpYnJhcnkocmllbSkNCmxpYnJhcnkoZ3JpZEV4dHJhKQ0KbGlicmFyeShrbml0cikNCmxpYnJhcnkoa2FibGVFeHRyYSkNCmxpYnJhcnkoUlNvY3JhdGEpDQpsaWJyYXJ5KGFycm93KQ0KbGlicmFyeShnbHVlKQ0KbGlicmFyeShmdW5Nb2RlbGluZykNCmxpYnJhcnkocGF0Y2h3b3JrKQ0KbGlicmFyeShwc3ljaCkNCmxpYnJhcnkoZGF0YS50YWJsZSkNCmxpYnJhcnkoYnJvb20pDQpsaWJyYXJ5KGZsZXh0YWJsZSkNCmxpYnJhcnkoc3BlZWRnbG0pDQpsaWJyYXJ5KGNhcmV0KQ0KDQpzZl9jcnMgPSBzdF9jcnMoJ0VQU0c6NzEzMicpDQoNCnBlcmNlbnRfZm9ybWF0dGVyID0gZnVuY3Rpb24oc3RyLCBuPTApIChzdHIgKiAxMDApICU+JSByb3VuZChuKSAlPiUgZm9ybWF0KC4sIGJpZy5tYXJrID0gIiwiKSAlPiUgcGFzdGUwKC4sICIlIikNCg0KZm9ybWF0X251bXMgPSBmdW5jdGlvbihudW1faW5wdXQsIGRpZ2l0cyA9IDIpIGlmZWxzZShhYnMobnVtX2lucHV0KT45OTksDQogICAgICAgICAgICAgICAgICAgICAgY291bnRfZm9ybWF0KG51bV9pbnB1dCksDQogICAgICAgICAgICAgICAgICAgICAgcm91bmRfdGhyZXNoKG51bV9pbnB1dCwgZGlnaXRzID0gZGlnaXRzLGludF9jaGVjayA9IFRSVUUpKQ0KDQoNCg0KcGxvdFRoZW1lID0gdGhlbWUoDQogIHBsb3QudGl0bGUgPWVsZW1lbnRfdGV4dChzaXplPTEyKSwNCiAgcGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplPTgpLA0KICBwbG90LmNhcHRpb24gPSBlbGVtZW50X3RleHQoc2l6ZSA9IDYpLA0KICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTAsIGFuZ2xlID0gNDUsIGhqdXN0ID0gMSksDQogIGF4aXMudGV4dC55ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCksDQogIGF4aXMudGl0bGUueSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApLA0KICAjIFNldCB0aGUgZW50aXJlIGNoYXJ0IHJlZ2lvbiB0byBibGFuaw0KICBwYW5lbC5iYWNrZ3JvdW5kPWVsZW1lbnRfYmxhbmsoKSwNCiAgcGxvdC5iYWNrZ3JvdW5kPWVsZW1lbnRfYmxhbmsoKSwNCiAgI3BhbmVsLmJvcmRlcj1lbGVtZW50X3JlY3QoY29sb3VyPSIjRjBGMEYwIiksDQogICMgRm9ybWF0IHRoZSBncmlkDQogIHBhbmVsLmdyaWQubWFqb3I9ZWxlbWVudF9saW5lKGNvbG91cj0iI0QwRDBEMCIsc2l6ZT0uMiksDQogIGF4aXMudGlja3M9ZWxlbWVudF9ibGFuaygpKQ0KDQoNCnJvdW5kX3N0ciA9IGZ1bmN0aW9uKHN0cl9udW0sIGRpZ2l0cz0zKSBzdHJfbnVtICU+JSANCiAgICAgICAgYXMubnVtZXJpYygpICU+JQ0KICAgICAgICByb3VuZChkaWdpdHMpICU+JSANCiAgICAgICAgYXMuY2hhcmFjdGVyKCkgJT4lIA0KICAgICAgICBzdHJfcmVwbGFjZSguLCAiXjBcXC4iLCAiLiIpICU+JQ0KICAgICAgICBzdHJfcmVtb3ZlKC4sICIwKyQiKQ0KDQpyb3VuZF90aHJlc2ggPSBmdW5jdGlvbigNCiAgZmllbGQsDQogIHRocmVzaCA9IC4wMDEsDQogIGRpZ2l0cyA9IDMsDQogIGludF9jaGVjayA9IEZBTFNFLA0KICBjb21tYXMgPSBGQUxTRQ0KICApew0KICANCiAgaWYoaXMubnVsbChmaWVsZCkpe3JldHVybignTlVMTCcpfQ0KICBpZihpcy5uYShmaWVsZCkpe3JldHVybignTkEnKX0NCiAgDQogIHRocmVzaF9zdHIgPSANCiAgICBwYXN0ZSgnPCcsIHJvdW5kX3N0cih0aHJlc2gsIGRpZ2l0cz1kaWdpdHMpLCBzZXA9JyAnKQ0KICANCiAgZmllbGRfbnVtID0gZmllbGQgJT4lIGFzLm51bWVyaWMoKQ0KICANCiAgZmllbGRfc3RyID0gDQogICAgaWZlbHNlKA0KICAgICAgYWJzKGZpZWxkX251bSkgPCB0aHJlc2gsDQogICAgICB0aHJlc2hfc3RyLA0KICAgICAgaWZlbHNlKA0KICAgICAgICAoaW50X2NoZWNrID09IFRSVUUgJiBhYnMoZmllbGRfbnVtKSA+PSAxKSwNCiAgICAgICAgZmllbGRfbnVtICU+JSByb3VuZF9zdHIoZGlnaXRzPTApLA0KICAgICAgICBmaWVsZF9udW0gJT4lIHJvdW5kX3N0cihkaWdpdHM9ZGlnaXRzKQ0KICAgICAgICApKQ0KICANCiAgcmV0dXJuKGZpZWxkX3N0cikNCn0NCg0KDQptYXBUaGVtZSA9IHRoZW1lKHBsb3QudGl0bGUgPWVsZW1lbnRfdGV4dChzaXplPTEyKSwNCiAgICAgICAgICAgICAgICAgIHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZT04KSwNCiAgICAgICAgICAgICAgICAgIHBsb3QuY2FwdGlvbiA9IGVsZW1lbnRfdGV4dChzaXplID0gNiksDQogICAgICAgICAgICAgICAgICBheGlzLmxpbmU9ZWxlbWVudF9ibGFuaygpLA0KICAgICAgICAgICAgICAgICAgYXhpcy50ZXh0Lng9ZWxlbWVudF9ibGFuaygpLA0KICAgICAgICAgICAgICAgICAgYXhpcy50ZXh0Lnk9ZWxlbWVudF9ibGFuaygpLA0KICAgICAgICAgICAgICAgICAgYXhpcy50aWNrcz1lbGVtZW50X2JsYW5rKCksDQogICAgICAgICAgICAgICAgICBheGlzLnRpdGxlLng9ZWxlbWVudF9ibGFuaygpLA0KICAgICAgICAgICAgICAgICAgYXhpcy50aXRsZS55PWVsZW1lbnRfYmxhbmsoKSwNCiAgICAgICAgICAgICAgICAgIHBhbmVsLmJhY2tncm91bmQ9ZWxlbWVudF9ibGFuaygpLA0KICAgICAgICAgICAgICAgICAgcGFuZWwuYm9yZGVyPWVsZW1lbnRfYmxhbmsoKSwNCiAgICAgICAgICAgICAgICAgIHBhbmVsLmdyaWQubWFqb3I9ZWxlbWVudF9saW5lKGNvbG91ciA9ICd0cmFuc3BhcmVudCcpLA0KICAgICAgICAgICAgICAgICAgcGFuZWwuZ3JpZC5taW5vcj1lbGVtZW50X2JsYW5rKCksDQogICAgICAgICAgICAgICAgICBsZWdlbmQuZGlyZWN0aW9uID0gInZlcnRpY2FsIiwgDQogICAgICAgICAgICAgICAgICBsZWdlbmQucG9zaXRpb24gPSAicmlnaHQiLA0KICAgICAgICAgICAgICAgICAgcGxvdC5tYXJnaW4gPSBtYXJnaW4oMSwgMSwgMSwgMSwgJ2NtJyksDQogICAgICAgICAgICAgICAgICBsZWdlbmQua2V5LmhlaWdodCA9IHVuaXQoMSwgImNtIiksIGxlZ2VuZC5rZXkud2lkdGggPSB1bml0KDAuMiwgImNtIikpDQoNCnBsb3RUaGVtZSA9IGZ1bmN0aW9uKGJhc2Vfc2l6ZSA9IDEyKSB7DQogIHRoZW1lKA0KICAgIHRleHQgPSBlbGVtZW50X3RleHQoIGNvbG9yID0gImJsYWNrIiksDQogICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTYsY29sb3VyID0gImJsYWNrIiksDQogICAgcGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChmYWNlPSJpdGFsaWMiKSwNCiAgICBwbG90LmNhcHRpb24gPSBlbGVtZW50X3RleHQoaGp1c3Q9MCksDQogICAgYXhpcy50aWNrcyA9IGVsZW1lbnRfYmxhbmsoKSwNCiAgICANCiAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLA0KICAgIHBhbmVsLmdyaWQubWFqb3IgPSBlbGVtZW50X2xpbmUoImdyZXk4MCIsIHNpemUgPSAwLjEpLA0KICAgIHBhbmVsLmdyaWQubWlub3IgPSBlbGVtZW50X2JsYW5rKCksDQogICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9yZWN0KGNvbG91ciA9ICJibGFjayIsIGZpbGw9TkEsIHNpemU9MiksDQogICAgc3RyaXAuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gImdyZXk4MCIsIGNvbG9yID0gIndoaXRlIiksDQogICAgc3RyaXAudGV4dCA9IGVsZW1lbnRfdGV4dChzaXplPTEyKSwNCiAgICANCiAgICBheGlzLnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemU9MTIpLA0KICAgIGF4aXMudGV4dCA9IGVsZW1lbnRfdGV4dChzaXplPTEwKSwNCiAgICANCiAgICANCiAgICBwbG90LmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksDQogICAgDQogICAgbGVnZW5kLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksDQogICAgbGVnZW5kLnRpdGxlID0gZWxlbWVudF90ZXh0KGNvbG91ciA9ICJibGFjayIsIGZhY2UgPSAiaXRhbGljIiksDQogICAgbGVnZW5kLnRleHQgPSBlbGVtZW50X3RleHQoY29sb3VyID0gImJsYWNrIiwgZmFjZSA9ICJpdGFsaWMiKSwNCiAgICANCiAgICBzdHJpcC50ZXh0LnggPSBlbGVtZW50X3RleHQoc2l6ZSA9IDE0KQ0KICApDQp9DQoNCg0Kc2ZfdG9fbGFiZWxzID0gZnVuY3Rpb24oDQogIHNmX2ZvY3VzLCBsYWJlbF9maWVsZA0KKXsNCiAgc2YuZm9jdXMubGFiZWxzID0gDQogICAgc2ZfZm9jdXMgJT4lDQogICAgc3RfY2VudHJvaWQoLiwgb2ZfbGFyZ2VzdF9wb2x5Z29uID0gVFJVRSkgJT4lIA0KICAgIG11dGF0ZSgNCiAgICAgIGxvbiA9IA0KICAgICAgICBtYXBfZGJsKA0KICAgICAgICAgIGdlb21ldHJ5LA0KICAgICAgICAgIH5zdF9jZW50cm9pZCgueCwgDQogICAgICAgICAgICAgICAgICAgICAgIG9mX2xhcmdlc3RfcG9seWdvbiA9IFRSVUUsDQogICAgICAgICAgICAgICAgICAgICAgIHF1aWV0ID0gVFJVRSwpW1sxXV0pLA0KICAgICAgbGF0ID0gDQogICAgICAgIG1hcF9kYmwoDQogICAgICAgICAgZ2VvbWV0cnksIA0KICAgICAgICAgIH5zdF9jZW50cm9pZCgueCwgDQogICAgICAgICAgICAgICAgICAgICAgIG9mX2xhcmdlc3RfcG9seWdvbiA9IFRSVUUsIA0KICAgICAgICAgICAgICAgICAgICAgICBxdWlldCA9IFRSVUUsKVtbMl1dKQ0KICAgICkNCiAgc2YuZm9jdXMubGFiZWxzJGxhYmVsID0gc2YuZm9jdXMubGFiZWxzW1tsYWJlbF9maWVsZF1dDQogIHJldHVybihzZi5mb2N1cy5sYWJlbHMgJT4lIGRwbHlyOjpzZWxlY3QobGFiZWwsIGxvbiwgbGF0KSkNCn0NCmdlb21fdGV4dF9zZiA9IGZ1bmN0aW9uKA0KICBzZi5mb2N1cy5sYWJlbHMsDQogIGZvbnRmYWNlPSdib2xkJywgY29sb3I9J2JsYWNrJywNCiAgY2hlY2tfb3ZlcmxhcCA9IFRSVUUsIC4uLg0KKXsNCiAgbGFiZWxzID0gDQogICAgZ2VvbV90ZXh0KA0KICAgICAgICBkYXRhPXNmLmZvY3VzLmxhYmVscywgY2hlY2tfb3ZlcmxhcD1UUlVFLA0KICAgICAgICBmb250ZmFjZT1mb250ZmFjZSwgY29sb3I9Y29sb3IsDQogICAgICAgIGFlcyh4PWxvbix5PWxhdCwgbGFiZWw9bGFiZWwsLi4uKSkNCiAgcmV0dXJuKGxhYmVscyl9DQoNCg0KcGFsZXR0ZTUgPSBjKCIjZWZmM2ZmIiwiI2JkZDdlNyIsIiM2YmFlZDYiLCIjMzE4MmJkIiwiIzA4NTE5YyIpDQpwYWxldHRlNCA9IGMoIiNEMkZCRDQiLCIjOTJCQ0FCIiwiIzUyN0Q4MiIsIiMxMjNGNUEiKQ0KcGFsZXR0ZTIgPSBjKCIjNmJhZWQ2IiwiIzA4NTE5YyIpDQoNCmBgYA0KIA0KIyMgVXNlIENhc2UgJiBNb3RpdmF0aW9uDQoNClgtUGFya3MtdGhlLVNwb3QgaXMgYW4gYXBwIHRoYXQgZmluZHMgdGhlIHBlcmZlY3QgcGFya2luZyBzcG90IGZvciB0aGUgdXNlci4gVGhlIHBlcmZlY3QgcGFya2luZyBzcG90LCB0byBwdXQgaXQgYmx1bnRseSwgaXMgdGhlIHRoYXQncyBhdmFpbGFibGUgY2xvc2VzdCB0byB0aGUgdXNlcidzIGRlc3RpbmF0aW9uLiBDdXJyZW50bHksIHRoZSBhcHAgb25seSBvcGVyYXRlcyBpbiB0aGUgcGlsb3QgY2l0eSBvZiBTYW4gRnJhbmNpc2NvLCBkdWUgdG8gdGhlIGNpdHkncyBhdmFpbGFiaWxpdHkgb2YgZGF0YS4gDQoNClRoZSBjdXJyZW50IHZlcnNpb24gb2YgdGhlIFgtUGFya3MtdGhlLVNwb3QgYXBwIHByb3ZpZGVzIHRoZSB1c2VyIHdpdGggdGhlIGJsb2NrIG9mIHBhcmtpbmcgc3BvdHMgdGhhdCBoYXMgdGhlIGhpZ2hlc3QgcHJvYmFiaWxpdHkgb2YgaGF2aW5nIGFuIG9wZW4gc3BvdCwgY29uc2lkZXJpbmcgdGhlIHVzZXInczoNCg0KMS4gZGVzdGluYXRpb24gbmVpZ2hib3Job29kLCANCg0KMi4gZGF5IG9mIHRoZSB3ZWVrLCBhbmQgDQoNCjMuIHRpbWUgcGVyaW9kIG9mIHRoZSBkYXkgKGUuZy4gOWFtIHRvIG5vb24pLg0KDQpUaGUgZGV2ZWxvcG1lbnQgdGVhbSBmb3JtZWQgYSBwcmVkaWN0aW9uIG1vZGVsIGJhc2VkIG9uIDIwMTkgcGFya2luZyBtZXRlciB0cmFuc2FjdGlvbnMgaW4gdGhlIENpdHkgb2YgU2FuIEZyYW5jaXNjby4gU3BlY2lmaWNhbGx5LCB0aGlzIGlzIGEgYmlub21pYWwgbG9naXN0aWMgbW9kZWwgdGhhdCBwcmVkaWN0cyBpZiB0aGUgYmxvY2sncyBvY2N1cGFuY3kgcmF0ZSAtLSBlLmcuIHRoZSBhZ2dyZWdhdGVkIG9jY3VwYXRpb24gdGltZSBvZiBhbGwgdGhlIGJsb2NrJ3MgcGFya2luZyBtZXRlcnMgb3ZlciB0aGUgYWdncmVnYXRlZCBwb3NzaWJsZSB0aW1lIC0tIHdpbGwgYmUgbG93ZXIgb3IgaGlnaGVyIHRoYW4gNTAlLiA1MCUgUGFya2luZyBNZXRlciBPY2N1cGFuY3kgaW4gYSB0aW1lIHBlcmlvZCBpcyBhIHNhZmUgbnVtYmVyIHRvIGFzc3VtZSB0aGF0IHRoZXJlIHdpbGwgYmUgb3BlbmluZ3MuIA0KDQpUaGlzIGFwcHJvYWNoIGlzIGhlbHBmdWwgYXMgaXQgZ2l2ZXMgdGhlIGFwcCB1c2VyIGEgc2FmZSBlc3RpbWF0ZSBvZiB3aGF0IHN0cmVldCBwYXJrIHRvIHBhcmsgb24uIE91ciBhcHAncyBmdW5kaW5nIGlzIGJhc2VkIG9uIGJvdGggcHVibGljICYgcHJpdmF0ZSBpbnZlc3RtZW50OyBhcyBhIHJlc3VsdCwgaXQgaXMgaW4gb3VyIGJlc3QgaW50ZXJlc3QgdG8gbm90IHdhc3RlIGFuIGFwcCB1c2VyJ3MgdGltZSBieSBjaGVja2luZyBtdWx0aXBsZSBibG9ja3MgZm9yIHBhcmtpbmcuDQoNCiMjIE1ldGhvZA0KDQpUaGUgZ2VuZXJhbCBtZXRob2QgaXMgdG8gDQoNCjEuIHByZXBhcmUgZGF0YSB0aGF0IGdpdmVzIHVzIHRoZSBvY2N1cGFuY3kgcmF0ZXMgZm9yIHBhcmsgb2YgbWV0ZXJzIGJ5IHRpbWUgb2YgZGF5ICYgYmxvY2ssIA0KDQoyLiBmaW5kIHZhcmlhYmxlcyB0aGF0IGNhbiBwcmVkaWN0IHRoZSBvY2N1cGFuY3kgcmF0ZSwNCg0KMy4gZml0IGEgbW9kZWwgYmFzZWQgb24gdGhlIG9jY3VwYW5jeSByYXRlcw0KDQo0LiBldmFsdWF0ZSB0aGUgZml0IG9mIHRoZSBtb2RlbA0KDQpUaGUgZ29hbCBpcyB0byBmaW5kIGJsb2NrcyB0aGF0IGhhdmUgdGhlIGxvd2VzdCBwcm9iYWJpbGl0eSBvZiBiZWluZyBiZWxvdyA1MCUgb2NjdXBhbmN5IGZvciBhIHRpbWUgcGVyaW9kLiANCg0KU2luY2UgdGhlcmUgaXMgYSB3aWRlIHJhbmdlIG9mIG5laWdoYm9yaG9vZHMgYW5kIHRpbWUgcGVyaW9kcywgd2UgYWltIGZvciB0aGUgbW9kZWwgdG8gYm90aCBiZSBhY2N1cmF0ZSBhbmQgZ2VuZXJhbGl6YWJsZS4gSWYgdGhlIG1vZGVsIGlzIGdlbmVyYWxpemFibGUgZW5vdWdoLCBpdCBjb3VsZCBwb3RlbnRpYWxseSBiZSBhcHBsaWVkIHRvIGZ1dHVyZSBjaXRpZXMuICAgDQoNCiMgRGF0YQ0KKkRlc2NyaWJlIHRoZSBkYXRhIHlvdSB1c2VkLioNCg0KVGhlIG9ic2VydmF0aW9ucyBhcmUgcGFya2luZyBtZXRlciB0cmFuc2FjdGlvbnMgZnJvbSB0aGUgQ2l0eSBvZiBTYW4gRnJhbmNpc2NvIGZvciB0aGUgeWVhciAyMDE5LiBGaXJzdCwgdGhlIG1ldGVyIHRyYW5zYWN0aW9ucyBhcmUgYWdncmVnYXRlZCBieSB0aGUgbWV0ZXIsIGRheSwgYW5kIHRpbWUgcGVyaW9kIChlLmcuIDlhbSB0byBub29uKSB0byBmaW5kICgxKSB0aGUgYW1vdW50IG9mIHRpbWUgb2NjdXBpZWQsIGFuZCAoMikgdGhlIHRvdGFsIHBvc3NpYmxlIG1ldGVyIHRpbWUuIFNlY29uZGx5LCB0aG9zZSBncm91cGVkIG1ldGVyIG9ic2VydmF0aW9ucyBhcmUgdGhlbiBhZ2dyZWdhdGVkIGJ5IHRoZSBzdHJlZXQgYmxvY2sgLS0gZ2l2aW5nIHRoZSBibG9jaydzIHRvdGFsIHRpbWUgb2NjdXBpZWQuIA0KDQpUaGUgZGF0YXNldCBpcyBmYWlybHkgbGFyZ2UsIHJhdywgYW5kIGFnZ3JlZ2F0ZWQgZnJvbSBwYXJraW5nIG1ldGVyIHZlbmRvcnMgd2hpY2ggcmVxdWlyZXMgYSBtZXRob2RpY2FsIGNsZWFuaW5nIHByb2Nlc3MuIE91ciBmb2N1cyBvbmx5IGxvb2tzIGF0IG1ldGVyIHRyYW5zYWN0aW9ucyBhbmQgdGltZSBwZXJpb2RzIHRoYXQgYXJlOg0KDQoxLiBtZXRlcnMgZm9yIGdlbmVyYWwgdXNlIChlLmcuIG5vIG1vdG9yY3ljbGUgb3IgY29tbWVyY2lhbCBwYXJraW5nKSwNCg0KMi4gdHJhbnNhY3Rpb25zIHRoYXQgYXJlIG5vdCB1c2VkIGZvciB0ZXN0aW5nIG9yIGFkbWluaXN0cmF0aW9uLCANCg0KMy4gdGltZSBwZXJpb2RzIHRoYXQgYXJlIG5vdCBjaGFyZ2luZyBmb3IgcGFya2luZyAoZS5nLiBub3Qgb24gYSBob2xpZGF5IG9yIHNodXRkb3duIGZvciBjb25zdHJ1Y3Rpb24pLg0KDQpCZXNpZGVzIGNsZWFuaW5nIHRoZSBkZXBlbmRlbnQgdmFyaWFibGVzLCB3ZSB3aWxsICgxKSBpbXBvcnQgZXh0ZXJuYWwgcHJlZGljdG9ycyAoZS5nLiBjZW5zdXMgZGF0YSkgYW5kICgyKSBmdXJ0aGVyIGVuZ2luZWVyIHRoZSBwYXJraW5nIHRyYW5zYWN0aW9ucyAoZS5nLiBsYWcgdGltZSkuDQoNCiMjIEltcG9ydA0KIyMjIFBhcmtpbmcNCg0KQXMgZm9yZXNoYWRvd2VkLCB0aGUgYnVsayBvZiB0aGlzIHByb2plY3Qgd2FzIGluIGNsZWFuaW5nIHVwIHRoZSBwYXJraW5nIHRyYW5zYWN0aW9ucyB0byBiZSBtZWFzdXJlZCBieSBib3RoIHRpbWUgcGVyaW9kcyBhbmQgdGhlaXIgYmxvY2tzLiBUaGUgc2l6ZSBvZiB0aGlzIGNsZWFuaW5nIHJlcXVpcmVkIHByZS1wcm9jZXNzaW5nIHRvIGJlIHBlcmZvcm1lZCBpbiBweXRob24gc2NyaXB0cy4NCg0KQmVmb3JlIGdyb3VwaW5nIGJ5IGRpc3RyaWN0cywgdGhlIG1ldGVyIHRyYW5zYWN0aW9ucyByYW5nZWQgaW4gc2l6ZXMgZnJvbSAzLTEwIG1pbGxpb24gcm93cy4gVGhlIENpdHkgb2YgU2FuIEZyYW5jaXNjbyBoYXMgYWJvdXQgMTgsMDAwIHBhcmtpbmcgbWV0ZXJzIC0tIHNvIG1lYXN1cmluZyBmb3IgYWxsIG9mIDIwMTkgbWFrZXMgaXQgZXhwb25lbnRpYWxseSBsYXJnZXIuIA0KDQpUaGUgY29tcGxpY2F0ZWQgY2xlYW5pbmcgcHJvY2VzcyBpc24ndCB0aGUgc2NhbGUgYnV0IG9yZ2FuaXppbmcgdGhlIHRyYW5zYWN0aW9uJ3Mgc3RhcnQgJiBlbmQgdGltZXMuIFRvIGJpbiBieSB0aW1lIHBlcmlvZHMsIHRoZSBzdGFydCAmIGVuZCB0aW1lcyBoYXZlIHRvIGZpcnN0IGJlIGNyb3BwZWQgYnkgdGhlIHBlcmlvZHMuIFRoZW4gdGhlIHBlcmlvZHMgaGF2ZSB0byBhY2NvdW50IGZvciBwb3RlbnRpYWxseSBvdmVybGFwcGluZyB0cmFuc2FjdGlvbnMgZm9yIHRoZSBzYW1lIG1ldGVyLiBCZWZvcmUgYWNjb3VudGluZyBmb3IgdGhlIG92ZXJsYXBzLCBtYW55IG1ldGVycyB3b3VsZCBoYXZlIHRpbWUgcGVyaW9kcyBvZiBvdmVyIDEwMCUgb2NjdXBhdGlvbi4gDQoNClRoZSBweXRob24gc2NyaXB0cyBjbGVhbmVkIHRoZSB0cmFuc2FjdGlvbnMgaW4gdGhpcyBvcmRlcjoNCg0KMS4gcmVtb3ZlIGxpa2VseSBhZG1pbmlzdHJhdGl2ZSBtZXRlcnM7DQoNCjIuIHNwbGl0IGVhY2ggdHJhbnNhY3Rpb24gaW50byBhIHN0YXJ0ICYgc3RvcCByb3c7DQoNCjMuIGFnZ3JlZ2F0ZSB0aGVzZSB0cmFuc2FjdGlvbnMgYnkgdGhlIG1ldGVyICYgZGF5Ow0KDQo0LiBzb3J0IHRoZSBzdGFydCAmIHN0b3BzIHNvIHRoZXkgY2FuIGJlIChBKSBjbGVhbmVkIG9mIG92ZXJsYXBzLCAoQikgc3BsaXQgYnkgdGltZSBwZXJpb2RzLCAoQykgcmUtcGFpcmVkLCAmIChEKSBtZWFzdXJlZCBpbiBob3VyczsNCg0KNS4gdGhlIG1lYXN1cmVkIHRyYW5zYWN0aW9uIHJvd3MgYXJlIHRoZW4gZ3JvdXBlZCBieSBtZXRlciwgdGltZSBwZXJpb2QsIGRheSwgYW5kLCAqZmluYWxseSosIGJ5IHN0cmVldCBibG9jazsNCg0KNi4gdGhlIHNjcmlwdCBhbHNvIGFkZHMgYmlucyBpZiB0aGVyZSB3ZXJlbid0IGFueSB0cmFuc2FjdGlvbnMgYnV0IHRoZXJlIHdhcyBwYXJraW5nIGFsbG93ZWQuDQoNCkluIHB5dGhvbiwgdGhlIHNjcmlwdHMgcHJpbWFyaWx5IHVzZSB0aGUgcGFuZGFzIGFuZCBkYXNrIGRhdGFmcmFtZSBwYWNrYWdlcyB0byBtYW5hZ2UgdGhlIGRhdGEuIE9yaWdpbmFsbHkgdGhlIHNjcmlwdHMgcHJvY2Vzc2VkIGluIDEtNSBob3VycyAqKGFyZ3VhYmx5IHRoZSBsb3dlc3QgcG9pbnQgb2YgbXkgc2VtZXN0ZXIgd2FzIHdoZW4gdGhleSByYW4gYW5kIEkgY291bGRuJ3QgdXNlIG15IGxhcHRvcCkqLCBidXQgd2VyZSBjdXQgZG93biB0byA1IG1pbnV0ZXMuIA0KDQpgYGAge3IgZGF0YV9ibG9ja19ncm91cH0NCg0KcGF0aCA9ICAnQzovVXNlcnMvbmVsbXMvRG9jdW1lbnRzL1Blbm4vTVVTQS01MDgvTVVTQTUwOF9GSU5BTF9TRlBBUksvZGF0YS9ibG9ja19jb3VudF9vY2MucGFycXVldCcNCnBhcmsuaW1wb3J0ID0gcmVhZF9wYXJxdWV0KHBhdGgpDQoNCnBhcmsuaW1wb3J0ID0gcGFyay5pbXBvcnQgJT4lIA0KICB0cmFuc211dGUoDQogICAgaWQuYmxvY2sgPSBibG9ja19pZCwNCiAgICBpZC5iZGIgPSBibG9ja19kYXlfYmluLA0KICAgIGlkLmRpc3QgPSBkaXN0X2lkLA0KICAgIGJpbiA9IGJpbiwNCiAgICBtZXRlcnMubGlzdCA9IG1ldGVyX2lkcywNCiAgICBtZXRlcnMuY291bnQgPSBtZXRlcl9jb3VudCwNCiAgICBkYXkgPSBkYXksDQogICAgZGF5LndlZWsgPSBgZGF5IG9mIHdlZWtgLA0KICAgIGRhdGUgPSBkYXRlKGRhdGUpLA0KICAgIGRhdGV0aW1lID0gZGF0ZXRpbWUsDQogICAgdC5ocnMub2NjID0gb2NjX2hvdXJzLA0KICAgIHQuaHJzLnRvdCA9IHRvdF9ob3VycywNCiAgICB0LnBjdC5vY2MgPSBvY2NfcGVyYw0KICApICU+JQ0KICBtdXRhdGUoDQogICAgd2VlayA9IHdlZWsoZGF0ZSksDQogICAgbW9udGggPSBtb250aChkYXRlKQ0KICApDQoNCnBhcmsubGFzdCA9IA0KICBwYXJrLmltcG9ydCAlPiUNCiAgZ3JvdXBfYnkoaWQuYmxvY2spICU+JQ0KICBmaWx0ZXIoZGF0ZXRpbWUgPT0gbWF4KGRhdGV0aW1lKSkgJT4lDQogIHB1bGwoaWQuYmRiKQ0KcGFyay5maXJzdCA9IA0KICBwYXJrLmltcG9ydCAlPiUNCiAgZ3JvdXBfYnkoaWQuYmxvY2spICU+JQ0KICBmaWx0ZXIoZGF0ZXRpbWUgPT0gbWluKGRhdGV0aW1lKSkgJT4lDQogIHB1bGwoaWQuYmRiKQ0KDQpwYXJrLmltcG9ydCA9IA0KICBwYXJrLmltcG9ydCAlPiUNCiAgICBtdXRhdGUoDQogICAgICBmaXJzdC5iaW4gPSBpZmVsc2UoDQogICAgICAgIGlkLmJkYiAlaW4lIHBhcmsuZmlyc3QsDQogICAgICAgIFRSVUUsIEZBTFNFKSwNCiAgICAgIGxhc3QuYmluID0gaWZlbHNlKA0KICAgICAgICBpZC5iZGIgJWluJSBwYXJrLmxhc3QsDQogICAgICAgIFRSVUUsIEZBTFNFKSwNCiAgICAgICkNCg0KcGFyay5sYXN0ID0gDQogIHBhcmsuaW1wb3J0ICU+JQ0KICBncm91cF9ieShpZC5ibG9jaykgJT4lDQogIGZpbHRlcihkYXRlID09IG1heChkYXRlKSkgJT4lDQogIHB1bGwoaWQuYmRiKQ0KDQpwYXJrLmZpcnN0ID0gDQogIHBhcmsuaW1wb3J0ICU+JQ0KICBncm91cF9ieShpZC5ibG9jaykgJT4lDQogIGZpbHRlcihkYXRlID09IG1pbihkYXRlKSkgJT4lDQogIHB1bGwoaWQuYmRiKQ0KDQpwYXJrLmltcG9ydCA9IA0KICBwYXJrLmltcG9ydCAlPiUNCiAgICBtdXRhdGUoDQogICAgICBmaXJzdC5kYXRlID0gaWZlbHNlKA0KICAgICAgICBpZC5iZGIgJWluJSBwYXJrLmZpcnN0LA0KICAgICAgICBUUlVFLCBGQUxTRSksDQogICAgICBsYXN0LmRhdGUgPSBpZmVsc2UoDQogICAgICAgIGlkLmJkYiAlaW4lIHBhcmsubGFzdCwNCiAgICAgICAgVFJVRSwgRkFMU0UpLA0KICAgICAgKSAlPiUNCiAgZHBseXI6OnNlbGVjdCgtbWV0ZXJzLmxpc3QpDQoNCnJtKHBhcmsuZmlyc3QpDQpybShwYXJrLmxhc3QpDQoNCmBgYA0KDQojIyMgQmxvY2sgR2VvbWV0cnkgDQoNCkZvciBzcGF0aWFsbHkgdmlzdWFsaXppbmcgdGhlIGRhdGFzZXQsIHdlIHB1bGwgaW4gdGhlIHN0cmVldCBibG9ja3MgZ2VvbWV0cmllcy4NCg0KYGBgIHtyIGRhdGFfbWV0ZXJ9DQoNCnBhdGggPSAiQzovVXNlcnMvbmVsbXMvRG9jdW1lbnRzL1Blbm4vTVVTQS01MDgvTVVTQTUwOF9GSU5BTF9TRlBBUksvZGF0YS9CbG9ja2ZhY2VzLmdlb2pzb24iDQoNCnBhcmsuYmxvY2tzID0gDQogIHN0X3JlYWQocGF0aCkgJT4lIA0KICBzdF90cmFuc2Zvcm0oc2ZfY3JzKSAlPiUNCiAgI2RwbHlyOjpzZWxlY3Qoc2ZwYXJrX2lkLCBnZW9tZXRyeSkgJT4lDQogIGZpbHRlcigNCiAgICAhaXMubmEoc2ZwYXJrX2lkKSAmIA0KICAgIHNmcGFya19pZCAlaW4lIHBhcmsuaW1wb3J0JGlkLmJsb2NrDQogICAgKSAlPiUNCiAgcmVuYW1lKA0KICAgIGlkLmJsb2NrID0gc2ZwYXJrX2lkDQogICkNCg0KDQpwYXRoID0gIkM6L1VzZXJzL25lbG1zL0RvY3VtZW50cy9QZW5uL01VU0EtNTA4L01VU0E1MDhfRklOQUxfU0ZQQVJLL2RhdGEvUGxhbm5pbmcgRGVwYXJ0bWVudCBOZWlnaGJvcmhvb2QgUXVhZHJhbnRzLmdlb2pzb24iDQoNClNGLnF1YWQgPSBzdF9yZWFkKHBhdGgpICU+JSANCiAgc3RfdHJhbnNmb3JtKHNmX2NycykgJT4lDQogIGRwbHlyOjpzZWxlY3QocXVhZCwgZ2VvbWV0cnkpDQoNCnBhcmsuYmxvY2tzID0gDQogIHN0X2pvaW4oDQogICAgcGFyay5ibG9ja3MgJT4lDQogICAgICBkcGx5cjo6c2VsZWN0KGlkLmJsb2NrLCBnZW9tZXRyeSksDQogICAgU0YucXVhZCwNCiAgICBqb2luID0gc3RfaW50ZXJzZWN0cywNCiAgICBsZWZ0ID0gVFJVRSwNCiAgICBsYXJnZXN0ID0gVA0KICApDQoNCnBhcmsuaW1wb3J0ID0NCiAgbWVyZ2UoDQogICAgcGFyay5pbXBvcnQsDQogICAgcGFyay5ibG9ja3MgJT4lDQogICAgICBzdF9kcm9wX2dlb21ldHJ5KCksDQogICAgYnk9J2lkLmJsb2NrJw0KICApDQoNCmBgYA0KIyMjIENlbnN1cw0KDQpUbyB1bmRlcnN0YW5kIHRoZSBkZW1vZ3JhcGhpY3Mgb2YgZGlmZmVyZW50IGFyZWFzLCB3ZSBwdWxsIGluIGEgdmFyaWV0eSBvZiBjZW5zdXMgZGF0YS4gDQoNCmBgYCB7ciBkYXRhX2NlbnN1cywgcmVzdWx0cz0nYXNpcyd9DQoNCm9nX3ZhcnMgPSBjKCJCMDEwMDNfMDAxIiwgIkIxOTAxM18wMDEiLCANCiAgICAgICAgICAgICAgICAgICAgICAgICJCMDIwMDFfMDAyIiwgIkIwODAxM18wMDEiLA0KICAgICAgICAgICAgICAgICAgICAgICAgIkIwODAxMl8wMDEiLCAiQjA4MzAxXzAwMSIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgIkIwODMwMV8wMTAiLCAiQjAxMDAyXzAwMSINCiAgICAgICAgICAgICAgICAgICAgICAgICkNCg0KYWRkZWRfdmFycyA9IGMoIkIyNTAyNl8wMDFFIiwiQjAyMDAxXzAwMkUiLCJCMTUwMDFfMDUwRSIsDQogICAgICAgICAgICAgICJCMTUwMDFfMDA5RSIsIkIxOTAxM18wMDFFIiwiQjI1MDU4XzAwMUUiLA0KICAgICAgICAgICAgICAiQjA2MDEyXzAwMkUiLCAiQjA4MzAxXzAwM0UiLCAiQjA4MzAxXzAxMEUiLCANCiAgICAgICAgICAgICAgIkIwODMwMV8wMTZFIiwgIkIwODMwMV8wMThFIiwgIkIwODMwMV8wMjFFIiwgDQogICAgICAgICAgICAgICJCMDgwMDdfMDAxRSIsICJCMTEwMTZfMDAxRSIsICJCMjUwNDRfMDAzRSIsIA0KICAgICAgICAgICAgICAiQjE1MDAzXzAyMkUiLCAiQjAxMDAzXzAwMUUiLCAiQjA4MzAxXzAxOUUiLCANCiAgICAgICAgICAgICAgIkIwODMwMV8wMTdFIiwgIkIwODMwMV8wMjBFIiwgIkIwODMwMV8wMDRFIiwgDQogICAgICAgICAgICAgICJCMDgxMzVfMDAxRSIsICJCMDgzMDNfMDAxRSIsICJCMDgzMDNfMDAyRSIsIA0KICAgICAgICAgICAgICAiQjA4MzAzXzAwM0UiLCAiQjA4MzAzXzAwNEUiLCAiQjA4MzAzXzAwNUUiLCANCiAgICAgICAgICAgICAgIkIwODMwM18wMDZFIiwgIkIwODMwM18wMDdFIiwgIkIwODMwM18wMDhFIiwgDQogICAgICAgICAgICAgICJCMDgzMDNfMDA5RSIsICJCMDgzMDNfMDEwRSIsICJCMDgzMDNfMDExRSIsIA0KICAgICAgICAgICAgICAiQjA4MzAzXzAxMkUiLCAiQjA4MzAzXzAxM0UiLCAiQjA4MzAxXzAxOEUiLCANCiAgICAgICAgICAgICAgIkIxNTAwM18wMjJFIikNCiMgDQojIFNGLmNlbnN1cyA9DQojICAgZ2V0X2FjcygNCiMgICAgIGdlb2dyYXBoeSA9ICJ0cmFjdCIsDQojICAgICB2YXJpYWJsZXMgPSB1bmlxdWUoYyhvZ192YXJzLGFkZGVkX3ZhcnMpKSwNCiMgICAgIHllYXIgPSAyMDE5LA0KIyAgICAgc3RhdGUgPSAiQ0EiLA0KIyAgICAgZ2VvbWV0cnkgPSBUUlVFLA0KIyAgICAgY291bnR5PWMoIlNhbiBGcmFuY2lzY28iKSwNCiMgICAgIG91dHB1dCA9ICJ3aWRlIg0KIyAgICAgICAgICAgKSAlPiUNCiMgICByZW5hbWUoDQojICAgICBUb3RhbF9Qb3AgPSAgQjAxMDAzXzAwMUUsDQojICAgICBNZWRfSW5jID0gQjE5MDEzXzAwMUUsDQojICAgICBNZWRfQWdlID0gQjAxMDAyXzAwMUUsDQojICAgICBXaGl0ZV9Qb3AgPSBCMDIwMDFfMDAyRSwNCiMgICAgIFRyYXZlbF9UaW1lID0gQjA4MDEzXzAwMUUsDQojICAgICBOdW1fQ29tbXV0ZXJzID0gQjA4MDEyXzAwMUUsDQojICAgICBNZWFuc19vZl9UcmFuc3BvcnQgPSBCMDgzMDFfMDAxRSwNCiMgICAgIFRvdGFsX1B1YmxpY19UcmFucyA9IEIwODMwMV8wMTBFLA0KIyAgICAgd29ya2ZvcmNlXzE2ID0gQjA4MDA3XzAwMUUsDQojICAgICBOdW1fVmVoaWNsZXMgPSBCMDYwMTJfMDAyRSwNCiMgDQojICAgICBkcm92ZV90b193b3JrID0gQjA4MzAxXzAwM0UsDQojICAgICBIb3VzZV9ob2xkc19ub192ZWhpY2xlcyA9IEIyNTA0NF8wMDNFLA0KIyAgICAgdG90YWxfaG91c2Vob2xkcyA9IEIxMTAxNl8wMDFFDQojICAgICApICU+JQ0KIyAgIG11dGF0ZSgNCiMgICAgIHdvcmtlcnNfY29tbXV0ZV8zMF85MG1pbiA9IEIwODMwM18wMDhFICsgQjA4MzAzXzAwOUUgKyBCMDgzMDNfMDEwRSArIEIwODMwM18wMTFFICsgQjA4MzAzXzAxMkUgKyBCMDgzMDNfMDEzRSwNCiMgICAgIGNvbW11dGVfMzBfOTBtaW5fcGN0ID0gKHdvcmtlcnNfY29tbXV0ZV8zMF85MG1pbi93b3JrZm9yY2VfMTYpLA0KIyAgICAgaG91c2Vob2xkc19jYXJfcGN0ID0gKE51bV9WZWhpY2xlcy90b3RhbF9ob3VzZWhvbGRzKSwNCiMgICAgIGhvdXNlaG9sZHNfTk9jYXJfcGN0ID0gKEhvdXNlX2hvbGRzX25vX3ZlaGljbGVzL3RvdGFsX2hvdXNlaG9sZHMpLA0KIyAgICAgRHJpdmVfV29ya19wY3QgPSAoZHJvdmVfdG9fd29yay93b3JrZm9yY2VfMTYpLA0KIyAgICAgUHVibGljVHJhbnNwb3J0X3dvcmtfcGN0ID0gKFRvdGFsX1B1YmxpY19UcmFucy93b3JrZm9yY2VfMTYpDQojICAgICAgICAgICkgJT4lDQojICAgZHBseXI6OnNlbGVjdChUb3RhbF9Qb3AsIE1lZF9JbmMsIFdoaXRlX1BvcCwgVHJhdmVsX1RpbWUsDQojICAgICAgICAgIE1lYW5zX29mX1RyYW5zcG9ydCwgVG90YWxfUHVibGljX1RyYW5zLCBNZWRfQWdlLA0KIyAgICAgICAgICB3b3JrZm9yY2VfMTYsIE51bV9WZWhpY2xlcywgaG91c2Vob2xkc19OT2Nhcl9wY3QsIGhvdXNlaG9sZHNfY2FyX3BjdCwNCiMgICAgICAgICAgY29tbXV0ZV8zMF85MG1pbl9wY3QsIERyaXZlX1dvcmtfcGN0LCBQdWJsaWNUcmFuc3BvcnRfd29ya19wY3QsDQojICAgICAgICAgIEdFT0lELCBnZW9tZXRyeSkgJT4lDQojICAgbXV0YXRlKFBlcmNlbnRfV2hpdGUgPSBXaGl0ZV9Qb3AgLyBUb3RhbF9Qb3AsDQojICAgICAgICAgIE1lYW5fQ29tbXV0ZV9UaW1lID0gVHJhdmVsX1RpbWUgLyBUb3RhbF9QdWJsaWNfVHJhbnMsDQojICAgICAgICAgIFBlcmNlbnRfVGFraW5nX1B1YmxpY19UcmFucyA9IFRvdGFsX1B1YmxpY19UcmFucyAvIE1lYW5zX29mX1RyYW5zcG9ydCkNCg0KIyB3ZWJfc3JpZCA9ICdFUFNHOjM4NTcnDQojIHBhdGggPSAnQzovVXNlcnMvbmVsbXMvRG9jdW1lbnRzL1Blbm4vTVVTQS01MDgvTVVTQTUwOF9GSU5BTF9TRlBBUksvZGF0YS9zZl8yMDE5Y2Vuc3VzLmdlb2pzb24nDQojIHN0X3dyaXRlKHN0X3RyYW5zZm9ybShTRi5jZW5zdXMsIHdlYl9zcmlkKSwgcGF0aCwgZGVsZXRlX2Rzbj1UKQ0KDQpwYXRoID0gJ0M6L1VzZXJzL25lbG1zL0RvY3VtZW50cy9QZW5uL01VU0EtNTA4L01VU0E1MDhfRklOQUxfU0ZQQVJLL2RhdGEvc2ZfMjAxOWNlbnN1cy5nZW9qc29uJw0KU0YuY2Vuc3VzID0gc3RfcmVhZChwYXRoKSAlPiUNCiAgc3RfdHJhbnNmb3JtKC4sIHNmX2NycykNCg0KU0YudHJhY3RzID0gDQogIFNGLmNlbnN1cyAlPiUNCiAgYXMuZGF0YS5mcmFtZSgpICU+JQ0KICBkaXN0aW5jdChHRU9JRCwgLmtlZXBfYWxsID0gVFJVRSkgJT4lDQogIGRwbHlyOjpzZWxlY3QoR0VPSUQsIGdlb21ldHJ5KSAlPiUgDQogIHN0X3NmDQoNCnBhcmsuY2Vuc3VzID0gDQogIHN0X2pvaW4oDQogICAgcGFyay5ibG9ja3MgJT4lDQogICAgICBkcGx5cjo6c2VsZWN0KGlkLmJsb2NrLGdlb21ldHJ5KSwNCiAgICBTRi50cmFjdHMgJT4lDQogICAgICBzdF90cmFuc2Zvcm0oY3JzPXNmX2NycyksDQogICAgam9pbj1zdF9pbnRlcnNlY3RzLA0KICAgIGxlZnQgPSBUUlVFKSAlPiUgDQogIHN0X2Ryb3BfZ2VvbWV0cnkoKSAlPiUNCiAgZmlsdGVyKGlkLmJsb2NrICVpbiUgcGFyay5pbXBvcnQkaWQuYmxvY2spICU+JQ0KICBsZWZ0X2pvaW4oDQogICAgLiwNCiAgICBTRi5jZW5zdXMgJT4lDQogICAgICBzdF9kcm9wX2dlb21ldHJ5KCksDQogICAgb249J0dFT0lEJw0KICAgICkgJT4lDQogIGRwbHlyOjpzZWxlY3QoLUdFT0lEKSAlPiUNCiAgbGVmdF9qb2luKA0KICAgIHBhcmsuaW1wb3J0ICU+JQ0KICAgICAgZHBseXI6OnNlbGVjdChpZC5iZGIsIGlkLmJsb2NrLCB0LnBjdC5vY2MpLA0KICAgIC4sDQogICAgb249J2lkLmJsb2NrJw0KICApDQoNCnJtKFNGLnRyYWN0cykNCnJtKFNGLmNlbnN1cykNCg0KY29ycmVsYXRpb25fdGFibGUoDQogICAgZGF0YT1wYXJrLmNlbnN1cyAlPiUNCiAgICAgIGRwbHlyOjpzZWxlY3QoLWlkLmJkYiwtaWQuYmxvY2spICU+JQ0KICAgICAgbmEub21pdCguKSwgDQogICAgdGFyZ2V0PSJ0LnBjdC5vY2MiDQogICkgJT4lDQogIGFycmFuZ2UoVmFyaWFibGUpICU+JQ0KICBmaWx0ZXIoVmFyaWFibGUhPSd0LnBjdC5vY2MnKSAlPiUNCiAgcmVuYW1lKA0KICAgIENvcnJlbGF0aW9uID0gdC5wY3Qub2NjDQogICkgJT4lDQogIGthYmxlKCkgJT4lDQogIGthYmxlX3N0eWxpbmcoKQ0KDQpwYXJrLmNlbnN1cyA9IA0KICBwYXJrLmNlbnN1cyAlPiUNCiAgZHBseXI6OnNlbGVjdCgNCiAgICBpZC5ibG9jaywNCiAgICBob3VzZWhvbGRzX2Nhcl9wY3QsDQogICAgTWVkX0luYywNCiAgICBQZXJjZW50X1doaXRlDQogICkgJT4lIA0KICBkaXN0aW5jdCgpICU+JQ0KICAgIGFycmFuZ2UoaWQuYmxvY2spICU+JQ0KICAgIGRhdGEudGFibGUoLiwga2V5PSdpZC5ibG9jaycpDQoNCg0KDQpgYGANCg0KTG9va2luZyBhdCB0aGlzIGNvcnJlbGF0aW9uIHBsb3QgdG8gdGhlIE1ldGVyJ3MgT2NjdXBhdGlvbiBSYXRlLCBhIG1ham9yaXR5IG9mIHRoZSBjZW5zdXMgZGVtb2dyYXBoaWNzIGhhdmUgbGl0dGxlIGxpbmVhciBjb25uZWN0aW9uIHdpdGggdGhlIHJhdGUuIFRoZSBzdHJvbmdlc3QgdmFyaWFibGVzICh0aGF0IHdlIHdpbGwgYWN0dWFsbHkgdXNlKSBhcmUgKDEpICUgdGhhdCBEcml2ZSB0byBXb3JrLCBNZWRpYW4gSW5jb21lLCAmICUgb2YgdGhlIHBvcHVsYXRpb24gdGhhdCdzIFdoaXRlLg0KDQpUaGUgc3Ryb25nZXIgdmFyaWFibGVzIGNvdWxkIGZvcm0gYSBzaW1pbGFyICdnZW50cmlmaWNhdGlvbicgbmFycmF0aXZlIHNpbWlsYXIgdG8gYSBwcmV2aW91cyBTYW4gRnJhbmNpc2NvIEJheSBzdHVkeS4gTWFueSBvZiB0aGUgaGlnaCBkZW5zaXR5LCBoaWdoIGRlbWFuZCBuZWlnaGJvcmhvb2RzIGFyZSBiZWluZyBwcmltYXJpbHkgdGhlIGhvbWUgb2YgbmV3IG1hcmtldC1yYXRlLCBjYXItbGVzcyBob3VzaW5nIGZvciBwcmVkb21pbmFudGx5IHdoaXRlLCBoaWdoZXItaW5jb21lIHJlc2lkZW50cy4gQWx0aG91Z2ggdGhpcyBuYXJyYXRpdmUgaXMgb3V0c2lkZSB0aGUgc2NvcGUgb2YgdGhlIHByb2plY3QgYW5kIGFwcCwgdGhlIG5laWdoYm9yaG9vZHMgdGhhdCBoYXZlIGhpZ2hlciBkZW1hbmQgZm9yIHN0cmVldCBwYXJraW5nIGFsaWduIHdpdGggdGhvc2UgbmV3IGRldmVsb3BtZW50cy4NCg0KIyMjIFdlYXRoZXIgSW5mbw0KDQpBbm90aGVyIGV4dGVybmFsIHByZWRpY3RvciBpcyB0aGUgdGVtcGVyYXR1cmUsIHdpbmQsIGFuZCBwcmVjaXBpdGF0aW9uIGJ5IGhvdXIgb3IgZGF5LiBUaGUgaG9wZSB3YXMgdGhhdCB0aGVzZSBwcmVkaWN0b3JzIGNvdWxkIGhlbHAgZGVzY3JpYmUgc29tZSBwYXJraW5nIHBhdHRlcm5zLg0KDQpgYGB7ciBpbXBvcnRfd2VhdGhlciwgbWVzc2FnZSA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0UsIGNhY2hlID0gVFJVRSwgcmVzdWx0cz0nYXNpcyd9DQoNCg0KIyBmb3Igc29tZSByZWFzb24gdGhlIFNGTyB3ZWF0aGVyIHN0YXRpb24gaXNuJ3Qgd29ya2luZw0KDQpwYXRoID0gJ0M6L1VzZXJzL25lbG1zL0RvY3VtZW50cy9QZW5uL01VU0EtNTA4L01VU0E1MDhfRklOQUxfU0ZQQVJLL2RhdGEvc2Zfd2VhdGhlcl8yMDE5LnBhcnF1ZXQnDQoNCiMgbGlicmFyeShyaWVtKQ0KIyBvYWsud2VhdGhlciA9DQojICAgcmllbV9tZWFzdXJlcyhzdGF0aW9uID0gIk9BSyIsIGRhdGVfc3RhcnQgPSAiMjAxOS0wMS0wMiIsIGRhdGVfZW5kID0gIjIwMjAtMDEtMDEiKQ0KIyB3cml0ZV9hcnJvdyhvYWsud2VhdGhlciwgcGF0aCkNCg0Kb2FrLndlYXRoZXIgPSANCiAgcmVhZF9hcnJvdyhwYXRoKSAlPiUNCiAgICBkcGx5cjo6c2VsZWN0KHZhbGlkLCB0bXBmLCBwMDFpLCBza250KSAlPiUNCiAgICByZXBsYWNlKGlzLm5hKC4pLCAwKSAlPiUNCiAgICBtdXRhdGUoaW50ZXJ2YWw2MCA9IHltZF9oKHN1YnN0cih2YWxpZCwxLDEzKSkpICU+JQ0KICAgIGRwbHlyOjpncm91cF9ieShpbnRlcnZhbDYwKSAlPiUNCiAgICBkcGx5cjo6c3VtbWFyaXplKA0KICAgICAgVGVtcGVyYXR1cmUgPSBtYXgodG1wZiksDQogICAgICBXaW5kX1NwZWVkID0gbWF4KHNrbnQpDQogICAgICApICU+JQ0KICBmaWx0ZXIoaW50ZXJ2YWw2MCAlaW4lIHBhcmsuaW1wb3J0JGRhdGV0aW1lKSAlPiUNCiAgbXV0YXRlKFRlbXBlcmF0dXJlID0gaWZlbHNlKFRlbXBlcmF0dXJlID09IDAsIDQyLCBUZW1wZXJhdHVyZSkpIA0KDQpvYWsucmFpbiA9IA0KICByZWFkX2Fycm93KHBhdGgpICU+JQ0KICAgIGRwbHlyOjpzZWxlY3QodmFsaWQsIHRtcGYsIHAwMWksIHNrbnQpICU+JQ0KICAgIHJlcGxhY2UoaXMubmEoLiksIDApICU+JQ0KICAgIG11dGF0ZShpbnRlcnZhbDYwID0geW1kX2goc3Vic3RyKHZhbGlkLDEsMTMpKSkgJT4lDQogICAgbXV0YXRlKGRhdGUgPSBkYXRlKGludGVydmFsNjApKSAlPiUNCiAgICBkcGx5cjo6Z3JvdXBfYnkoZGF0ZSkgJT4lDQogICAgZHBseXI6OnN1bW1hcml6ZShQcmVjaXBpdGF0aW9uID0gc3VtKHAwMWkpKSAlPiUNCiAgZmlsdGVyKGRhdGUgJWluJSBwYXJrLmltcG9ydCRkYXRlKQ0KDQoNCg0KcGFyay53ZWF0aGVyID0NCiAgcGFyay5pbXBvcnQgJT4lIA0KICBkcGx5cjo6c2VsZWN0KA0KICAgIGlkLmJkYiwgaWQuYmxvY2ssIA0KICAgIGRhdGV0aW1lLCBkYXRlLA0KICAgIHQucGN0Lm9jYykgJT4lDQogIGxlZnRfam9pbigNCiAgICAuLA0KICAgIG9hay53ZWF0aGVyICU+JQ0KICAgICAgcmVuYW1lKGRhdGV0aW1lID0gaW50ZXJ2YWw2MCksDQogICAgb249J2RhdGV0aW1lJw0KICApICU+JQ0KICBsZWZ0X2pvaW4oDQogICAgLiwNCiAgICBvYWsucmFpbiwNCiAgICBvbj0nZGF0ZScNCiAgKQ0KDQpybShvYWsucmFpbikNCnJtKG9hay53ZWF0aGVyKQ0KDQoNCmNvcnJlbGF0aW9uX3RhYmxlKA0KICAgIGRhdGE9cGFyay53ZWF0aGVyICU+JQ0KICAgICAgZHBseXI6OnNlbGVjdCh0LnBjdC5vY2MsIFRlbXBlcmF0dXJlLCBXaW5kX1NwZWVkLCBQcmVjaXBpdGF0aW9uKSAlPiUNCiAgICAgIG5hLm9taXQoLiksIA0KICAgIHRhcmdldD0idC5wY3Qub2NjIg0KICApICU+JQ0KICBhcnJhbmdlKFZhcmlhYmxlKSAlPiUNCiAgZmlsdGVyKFZhcmlhYmxlIT0ndC5wY3Qub2NjJykgJT4lDQogIHJlbmFtZSgNCiAgICBDb3JyZWxhdGlvbiA9IHQucGN0Lm9jYw0KICApICU+JQ0KICBrYWJsZSgpICU+JQ0KICBrYWJsZV9zdHlsaW5nKCkNCg0Kcm0ocGFyay53ZWF0aGVyKQ0KYGBgDQpBcyBzZWVuIGluIHRoZSBjb3JyZWxhdGlvbiBjaGFydCAoYW5kIHdlYXRoZXIgcGxvdCBiZWxvdyksIHRoZSB3ZWF0aGVyIHZhcmlhYmxlcyBhcmUgdmVyeSBpbnNpZ25pZmljYW50IHRvIHRoZSBtZXRlcidzIG9jY3VwYXRpb24gcmF0ZS4gQXMgYSByZXN1bHQsIHRoZXkgd29uJ3QgYmUgdXNlZC4NCg0KIyMgRW5naW5lZXIgRGF0YQ0KDQpUaGUgcHJpbWFyeSBwcmVkaWN0b3JzIHRvIGJlIGVuZ2luZWVyZWQgYXJlIG9uZXMgdGhhdCBkZXNjcmliZSB0aGUgbWV0ZXIncyBwbGFjZSBpbiBzcGFjZSBhbmQgdGltZS4gDQoNCiMjIyBBdmVyYWdlIFRpbWVzDQoNClRvIHN0YXJ0LCB0aGUgbWV0ZXIncyBvY2N1cGFuY3kgcmF0ZSBpcyBnb2luZyB0byBiZSBhdmVyYWdlZCBieSBkaWZmZXJlbnQgdGltZSBwZXJpb2RzIChlLmcuIGhvdXJzLCBkYXksIHdlZWssIG1vbnRoKSBhbmQgZ2VvZ3JhcGhpYyBsb2NhdGlvbnMgKGUuZy4gYmxvY2ssIGRpc3RyaWN0LCBjaXR5IHF1YWRyYW50KS4gDQpgYGAge3IgZGF0YV9wYXJrLCByZXN1bHRzPSdhc2lzJ30NCg0KIyBvbmUgbGluZSBncm91cF9ieSwgc3VtbWFyaXplLCB1bmdyb3VwDQptdXRhdGVfYnkgPSBmdW5jdGlvbiguZGF0YSwgZ3JvdXAsIC4uLikgew0KICBncm91cF9ieV9hdCgNCiAgICAuZGF0YSwgDQogICAgZHBseXI6OnZhcnMoICEhIHJsYW5nOjplbnF1byhncm91cCkgKSkgJT4lDQogICAgbXV0YXRlKC4uLikgJT4lDQogICAgdW5ncm91cCgpDQogIH0NCg0KcGFyay5hdmcgPQ0KICBwYXJrLmltcG9ydCAlPiUNCiAgIyBCWSBCTE9DSyBJRA0KICAjIyBEQVkNCiAgbXV0YXRlX2J5KA0KICAgIGdyb3VwPWMoJ2lkLmJsb2NrJywgJ2RheScpLA0KICAgIGF2Zy5ibGsuZGF5ID0gbWVhbih0LnBjdC5vY2MpDQogICkgJT4lDQogICMjIERBWSBPRiBXRUVLDQogIG11dGF0ZV9ieSgNCiAgICBncm91cD1jKCdpZC5ibG9jaycsICdkYXkud2VlaycpLA0KICAgIGF2Zy5ibGsud2Vla2RheSA9IG1lYW4odC5wY3Qub2NjKQ0KICApICU+JQ0KICBtdXRhdGVfYnkoDQogICAgZ3JvdXA9YygnaWQuYmxvY2snLCAnbW9udGgnKSwNCiAgICBhdmcuYmxrLm1vbnRoID0gbWVhbih0LnBjdC5vY2MpDQogICkgJT4lDQogIA0KICAjIEJZIFRJTUUgQklODQogICMjIERBWQ0KICBtdXRhdGVfYnkoDQogICAgZ3JvdXA9YygnYmluJywgJ2RheScpLA0KICAgIGF2Zy5iaW4uZGF5ID0gbWVhbih0LnBjdC5vY2MpDQogICkgJT4lDQogICMjIERBWSBPRiBXRUVLDQogIG11dGF0ZV9ieSgNCiAgICBncm91cD1jKCdiaW4nLCAnZGF5LndlZWsnKSwNCiAgICBhdmcuYmluLndlZWtkYXkgPSBtZWFuKHQucGN0Lm9jYykNCiAgKSAlPiUNCiAgbXV0YXRlX2J5KA0KICAgIGdyb3VwPWMoJ2JpbicsICdtb250aCcpLA0KICAgIGF2Zy5iaW4ubW9udGggPSBtZWFuKHQucGN0Lm9jYykNCiAgKSAlPiUNCiAgDQogICMgQlkgQkxPQ0sgJiBCSU4gDQogICMjIERBWSBPRiBXRUVLDQogIG11dGF0ZV9ieSgNCiAgICBncm91cD1jKCdpZC5ibG9jaycsICdiaW4nLCAnZGF5LndlZWsnKSwNCiAgICBhdmcuYmxrYmluLndlZWtkYXkgPSBtZWFuKHQucGN0Lm9jYykNCiAgKSAlPiUNCiAgbXV0YXRlX2J5KA0KICAgIGdyb3VwPWMoJ2lkLmJsb2NrJywgJ2JpbicsICdtb250aCcpLA0KICAgIGF2Zy5ibGtiaW4ubW9udGggPSBtZWFuKHQucGN0Lm9jYykNCiAgKSAlPiUNCiAgDQogICMgQlkgRGlzdHJpY3QNCiAgIyMgDQogIG11dGF0ZV9ieSgNCiAgICBncm91cD1jKCdpZC5kaXN0JywgJ2JpbicsICdkYXknKSwNCiAgICBhdmcuZGlzdGJpbi5kYXkgPSBtZWFuKHQucGN0Lm9jYykNCiAgKSAlPiUNCiAgDQogICMjIERBWSBPRiBXRUVLDQogIG11dGF0ZV9ieSgNCiAgICBncm91cD1jKCdpZC5kaXN0JywgJ2JpbicsICdkYXkud2VlaycpLA0KICAgIGF2Zy5kaXN0YmluLndlZWtkYXkgPSBtZWFuKHQucGN0Lm9jYykNCiAgKSAlPiUNCiAgbXV0YXRlX2J5KA0KICAgIGdyb3VwPWMoJ2lkLmJsb2NrJywgJ2JpbicsICdtb250aCcpLA0KICAgIGF2Zy5kaXN0YmluLm1vbnRoID0gbWVhbih0LnBjdC5vY2MpDQogICkgJT4lDQogIGRwbHlyOjpzZWxlY3QoYyhzdGFydHNfd2l0aCgiYXZnLiIpLCAidC5wY3Qub2NjIiwgImlkLmJkYiIpKQ0KDQpgYGANCg0KVGhlIHZhcmlhYmxlcyBpbml0aWFsbHkgYXJlIGEgcmFuZ2Ugb2YgY29tYmluYXRpb25zIG9mIHRpbWUgcGVyaW9kcyBhbmQgZ2VvZ3JhcGhpZXMuIFRoZSBpbml0aWFsIGNvcnJlbGF0aW9uIHRhYmxlIGJlbG93IHN1Z2dlc3RzIHRoYXQgbW9zdCBvZiB0aGUgdmFyaWFibGVzIGhhdmUgYSBoaWdoIGNvcnJlbGF0aW9uLg0KDQoNCmBgYCB7ciBFREFfY29ycl9hdmcsIHJlc3VsdHM9J2FzaXMnfQ0KDQpwYXJrLmF2Zy5jb3IgPQ0KICBjb3JyZWxhdGlvbl90YWJsZSgNCiAgICBkYXRhPXBhcmsuYXZnICU+JQ0KICAgICAgZHBseXI6OnNlbGVjdChjKHN0YXJ0c193aXRoKCJhdmcuIiksICJ0LnBjdC5vY2MiKSksIA0KICAgIHRhcmdldD0idC5wY3Qub2NjIikgJT4lDQogIGFycmFuZ2UoVmFyaWFibGUpICU+JQ0KICBmaWx0ZXIoVmFyaWFibGUhPSd0LnBjdC5vY2MnKQ0KDQpwYXJrLmF2Zy5jb3IuY29sID0gDQogIHN0cl9zcGxpdF9maXhlZChwYXJrLmF2Zy5jb3IkVmFyaWFibGUsICJbLl0iLCAzKSAlPiUNCiAgYXMudGliYmxlKCkgJT4lDQogIGRwbHlyOjpzZWxlY3QoMiwzKQ0KY29sbmFtZXMocGFyay5hdmcuY29yLmNvbCkgPSBjKCJCeSIsICJUaW1lIFBlcmlvZCIpDQoNCmNiaW5kKA0KICBwYXJrLmF2Zy5jb3IuY29sLCANCiAgcGFyay5hdmcuY29yICU+JQ0KICAgIGRwbHlyOjpzZWxlY3QoLVZhcmlhYmxlKQ0KKSAlPiUNCiAga2FibGUoKSAlPiUNCiAga2FibGVfc3R5bGluZygpDQoNCnBhcmsuYXZnID0gDQogIHBhcmsuYXZnICU+JQ0KICBkcGx5cjo6c2VsZWN0KA0KICAgIC1hdmcuYmluLmRheSwgLWF2Zy5iaW4ubW9udGgsIC1hdmcuYmluLndlZWtkYXksDQogICAgKQ0KDQpybShwYXJrLmF2Zy5jb3IpDQpybShwYXJrLmF2Zy5jb3IuY29sKQ0KDQpgYGANCkJlY2F1c2Ugb2YgdGhlaXIgcG90ZW5jeSwgdGhlIHByZWRpY3RvcnMgd2lsbCBiZSBzZWxlY3RlZCBhZnRlciBkZXRlcm1pbmluZyB0aGVpciBtdWx0aWNvbGxpbmVhcml0eSBhdCB0aGUgZW5kIG9mIHRoZSBFREEgc2VjdGlvbi4NCg0KIyMjIExhZ2dlZCBUaW1lDQoNClRoZSBhdmVyYWdlcyBzaW1wbHkgbG9vayBhdCBkaWZmZXJlbnQgc2NhbGVzIG9mIHRpbWUgYW5kIHNwYWNlLiBMYWdnZWQgdGltZSBhaW1zIHRvIGxvb2sgYXQgcGVyaW9kcyBiZWZvcmUgdGhlIGN1cnJlbnQgcGVyaW9kLiAgDQoNCmBgYCB7ciBkYXRhX3RpbWVfbGFnfQ0KDQpwYXJrLmJkYi5vY2MgPSBwYXJrLmltcG9ydCAlPiUNCiAgZHBseXI6OnNlbGVjdChpZC5iZGIsIHQucGN0Lm9jYykgJT4lDQogIGFycmFuZ2UoaWQuYmRiKQ0KDQpsYWdfbGVhZCA9IGZ1bmN0aW9uKGRhdGFfY29sLCBsYXN0X2NvbCwgZmlyc3RfY29sLCBuPTEpew0KICBjKGlmZWxzZSgNCiAgICBsYXN0X2NvbD09VFJVRSwNCiAgICBkcGx5cjo6bGFnKGRhdGFfY29sLCBuPW4pWzFdLA0KICAgIE5BDQogICksDQogIGlmZWxzZSgNCiAgICBmaXJzdF9jb2w9PVRSVUUsDQogICAgZHBseXI6OmxlYWQoZGF0YV9jb2wsIG49bilbMV0sDQogICAgTkENCiAgKSkgJT4lIHBhc3RlKC4sY29sbGFwc2U9IiAiKQ0KfQ0KDQpwYXJrLmxhZyA9IA0KICBwYXJrLmltcG9ydCAlPiUgDQogICAgYXJyYW5nZShpZC5ibG9jaywgZGF0ZXRpbWUpICU+JSANCiAgICBtdXRhdGUoDQogICAgICBsYWcuYmxrYmluLjFiaW4gPSBpZmVsc2UoDQogICAgICAgIGZpcnN0LmJpbj09VCxOQSwNCiAgICAgICAgZHBseXI6OmxhZyh0LnBjdC5vY2MsMSkNCiAgICAgICksDQogICAgICAjIGxlYWQuYmluLjEgPSBpZmVsc2UoDQogICAgICAjICAgbGFzdC5iaW49PVQsTkEsDQogICAgICAjICAgZHBseXI6OmxlYWQodC5wY3Qub2NjLDEpDQogICAgICAjICksDQogICAgICBsYWcuYmxrYmluLjFkYXkgPSBpZmVsc2UoDQogICAgICAgIGxhZyhiaW4sMykhPWJpbiwNCiAgICAgICAgTkEsDQogICAgICAgIGRwbHlyOjpsYWcodC5wY3Qub2NjLDMpDQogICAgICApLA0KICAgICAgIyBsZWFkLmJpbi4zID0gaWZlbHNlKA0KICAgICAgIyAgIGxlYWQoYmluLDMpIT1iaW4sTkEsDQogICAgICAjICAgZHBseXI6OmxlYWQodC5wY3Qub2NjLDMpDQogICAgICAjICksDQogICAgICBsYWcuYmxraWQud2VlayA9IGlmZWxzZSgoeWRheShkYXRlKS04KT4wLA0KICAgICAgICBwYXN0ZSgNCiAgICAgICAgICBpZC5ibG9jaywNCiAgICAgICAgICBzdHJfcGFkKHlkYXkoZGF0ZSktOCwgMywgcGFkID0gIjAiKSwNCiAgICAgICAgICBiaW4sIHNlcD0nXycpLA0KICAgICAgICBOQQ0KICAgICAgKQ0KICAgICAgIyBsZWFkLmRhdGUuNyA9IGlmZWxzZSgoeWRheShkYXRlKSs4KTwzNjYsDQogICAgICAjICAgcGFzdGUoDQogICAgICAjICAgICBpZC5ibG9jaywNCiAgICAgICMgICAgIHN0cl9wYWQoeWRheShkYXRlKSs4LCAzLCBwYWQgPSAiMCIpLA0KICAgICAgIyAgICAgYmluLCBzZXA9J18nKSwNCiAgICAgICMgICBOQQ0KICAgICAgIyApDQogICAgICApDQogICMgbGVmdF9qb2luKA0KICAjICAgLiwNCiAgIyAgIHBhcmsuYmxrLm9jYyAlPiUgDQogICMgICAgIHJlbmFtZShsZWFkLm9jYy43ID0gdC5wY3Qub2NjLCBsZWFkLmRhdGUuNz1pZC5iZGIpLA0KICAjICAgb249J2xlYWQuZGF0ZS43JywgDQogICMgICBuYV9tYXRjaGVzID0gIm5ldmVyIg0KICAjICkgJT4lDQoNCnBhcmsubGFnID0gDQogIGxlZnRfam9pbigNCiAgICBwYXJrLmxhZywNCiAgICBwYXJrLmJkYi5vY2MgJT4lIA0KICAgICAgcmVuYW1lKA0KICAgICAgICBsYWcuYmxrYmluLndlZWsgPSB0LnBjdC5vY2MsIA0KICAgICAgICBsYWcuYmxraWQud2Vlaz1pZC5iZGIpLA0KICAgIG9uPSdsYWcuYmxraWQud2VlaycsDQogICAgbmFfbWF0Y2hlcyA9ICJuZXZlciINCiAgKSAlPiUNCiAgZHBseXI6OnNlbGVjdCgtbGFnLmJsa2lkLndlZWspICAlPiUNCiAgZHBseXI6OnNlbGVjdChjKHN0YXJ0c193aXRoKCJsYWcuIiksICJ0LnBjdC5vY2MiLCAiaWQuYmRiIikpDQoNCnJtKHBhcmsuYmRiLm9jYykNCg0KYGBgDQpUaGUgbGFnIHRpbWUgaGFzIHRvIHdvcmsgYXJvdW5kIHRoZSBtZXRlciBibG9ja3Mgd2hpY2ggYXJlIGdyb3VwZWQgYnkgdGltZSBwZXJpb2QgYmlucyBvZiBhIGZldyBob3Vycy4gVGhlIGxhZ3MgdGhlbiBsb29rIGF0IHRocmVlIHBlcmlvZHMgKDEpIDEgYmluIGJlZm9yZWhhbmQsIDEgZGF5IGJlZm9yZWhhbmQgKihzYW1lIHRpbWUgYmluKSosIGFuZCAxIHdlZWsgYmVmb3JlaGFuZC4gDQoNClNvIGlmIG91ciBjdXJyZW50IHRpbWUgYmluIGlzIFR1ZXNkYXkgRGVjZW1iZXIgMTd0aCwgMjAxOSBiZXR3ZWVuIDEycG0gdG8gM3BtLCB3ZSB3aWxsIGxvb2sgYXQgdGhlIHNhbWUgbWV0ZXIncyBiaW4gKDEpIHRoYXQgZGF5IGZyb20gOWFtIHRvIDEycG0sICgyKSBvbmUgZGF5IGJlZm9yZWhhbmQgb24gTW9uIERlYyAxNnRoIGZyb20gMTItMywgdGhlbiAoMykgb25lIHdlZWsgYmVmb3JlaGFuZCBvbiBUdWUgRGVjIDEwdGggYXQgMTItMy4gDQoNCmBgYCB7ciBlZGFfbGFnLCByZXN1bHRzPSdhc2lzJ30NCg0KY29ycmVsYXRpb25fdGFibGUoDQogIGRhdGE9cGFyay5sYWcgJT4lDQogICAgZHBseXI6OnNlbGVjdChjKHN0YXJ0c193aXRoKCJsYWcuIiksICJ0LnBjdC5vY2MiKSksIA0KICB0YXJnZXQ9InQucGN0Lm9jYyIpICU+JQ0KICBhcnJhbmdlKFZhcmlhYmxlKSAlPiUNCiAgZmlsdGVyKFZhcmlhYmxlIT0ndC5wY3Qub2NjJykgJT4lDQogIG11dGF0ZSgNCiAgICBgTGFnIFBlcmlvZGAgPSBjKCcxIEJpbicsICcxIERheScsICcxIFdlZWsnKSwNCiAgICBDb3JyZWxhdGlvbiA9IHQucGN0Lm9jYw0KICApICU+JSANCiAgZHBseXI6OnNlbGVjdCgtVmFyaWFibGUsLXQucGN0Lm9jYykgJT4lDQogIGthYmxlKCkgJT4lDQogIGthYmxlX3N0eWxpbmcoKQ0KDQpgYGANCg0KKkFzIGEgc2lkZSBub3RlOiogV2hlbiBhZ2dyZWdhdGluZywgSSBsZWFuIHRvd2FyZHMgcGVyZm9ybWluZyB0aGUgbWVhbiBvZiBhbGwgdGhlIGdyb291cGVkIG9jY3VwYW5jeSByYXRlcyAtLSByYXRoZXIgdGhhbiBtZWRpYW4gb3IgcmVjYWxjdWxhdGluZyB0aGUgcmF0ZSAmIG1lYW4gYnkgc3VtbWluZyB0aGUgZ3JvdXAncyBvY2N1cGllZCB0aW1lICYgcG9zc2libGUgdGltZS4gDQoNCiMjIyBKb2luIEFsbA0KDQpOb3cgd2Ugc2ltcGx5IGpvaW4gdGhlIHByZXZpb3VzIHZhcmlhYmxlcyBpbnRvIG9uZSBkYXRhc2V0LiANCg0KYGBgIHtyIGRhdGFfd2VhdGhlcl9qb2lufQ0KDQoNCnBhcmsuaW1wb3J0ID0gDQogIHBhcmsuaW1wb3J0ICU+JQ0KICAgIGRwbHlyOjpzZWxlY3QoDQogICAgICAtZmlyc3QuYmluLCAtbGFzdC5iaW4sIC1maXJzdC5kYXRlLCAtbGFzdC5kYXRlDQogICAgICAjLXdlZWssIC1tb250aCwgLWRheQ0KICAgICkgJT4lIA0KICAgIGFycmFuZ2UoaWQuYmxvY2spICU+JQ0KICAgIGRhdGEudGFibGUoLikNCg0KcGFyay5pbXBvcnQgPSANCiAgcGFyay5pbXBvcnQgJT4lDQogIGxlZnRfam9pbigNCiAgICAuLA0KICAgIHBhcmsuY2Vuc3VzICU+JQ0KICAgICAgZGlzdGluY3QoKSwNCiAgICBieSA9ICdpZC5ibG9jaycNCiAgKQ0Kcm0ocGFyay5jZW5zdXMpDQoNCnBhcmsuaW1wb3J0ID0gDQogIG1lcmdlKA0KICAgIHBhcmsuaW1wb3J0ICU+JQ0KICAgICAgZGF0YS50YWJsZSguLCBrZXk9J2lkLmJkYicpLCAjLCBrZXk9J2lkLmJsb2NrJyksDQogICAgcGFyay5hdmcgJT4lDQogICAgICBkcGx5cjo6c2VsZWN0KC10LnBjdC5vY2MpICU+JQ0KICAgICAgZGF0YS50YWJsZSguLCBrZXk9J2lkLmJkYicpLA0KICAgIG9uPSdpZC5iZGInDQogICkgJT4lDQogIG1lcmdlKA0KICAgIC4sDQogICAgcGFyay5sYWcgJT4lDQogICAgICBkcGx5cjo6c2VsZWN0KC10LnBjdC5vY2MpICU+JQ0KICAgICAgZGF0YS50YWJsZSguLCBrZXk9J2lkLmJkYicpLA0KICAgIG9uPSdpZC5iZGInDQogICAgKQ0Kcm0ocGFyay5hdmcpDQpybShwYXJrLmxhZykNCg0KDQpgYGANCg0KIyBFREENCg0KVGhlIHZhcnlpbmcgdGltZSBhbmQgbG9jYXRpb25zIGluIHRoaXMgbGFyZ2UgZGF0YXNldCBwcm92aWRlIGFuIGFtYXppbmcgb3Bwb3J0dW5pdHkgdG8gdW5kZXJzdGFuZCBwYXJraW5nIGR5bmFtaWNzIGluIHRoZSByZWxhdGl2ZSB2YWN1dW0gb2YgU2FuIEZyYW5jaXNjby4gV2l0aCB0aGUgZmVhdHVyZXMgbW9zdGx5IGltcG9ydGVkIGFuZCBlbmdpbmVlcmVkLCB3ZSB3aWxsIHBlcmZvcm0gKipFeHBsb3JhdG9yeSBEYXRhIEFuYWx5c2lzIChFREEpKiogdG8gZXZhbHVhdGUgdGhlIHZhcmlhYmxlcyB0aGVtc2VsdmVzIGFzIHdlbGwgYXMgdGhlaXIgcmVsYXRpb25zaGlwIHRvIGVhY2ggb3RoZXIuIFRoZSBzZWN0aW9uIGlzIHNwbGl0IGJldHdlZW4gYW5hbHlzaXMgb2YgdGhlIGRlcGVuZGVudCB2YXJpYWJsZSBhbmQgdGhlbiBhbmFseXNpcyBvZiB0aGUgcHJlZGljdG9yIHZhcmlhYmxlcy4gDQoNCiMjIE1ldGVyIE9jY3VwYW5jeSBSYXRlDQoNClRvIGNsYXJpZnksIHRoZSBkZXBlbmRlbnQgdmFyaWFibGUgaXMgd2hldGhlciBvciBub3QgYSBCbG9jaydzIE1ldGVycyBpbiBhIHRpbWUgcGVyaW9kIGhhdmUgYW4gT2NjdXBhbmN5IFJhdGUgYWJvdmUgNTAlLiBTbyBhICdUUlVFJy8xIHN1Z2dlc3RzIHRoYXQgYXQgbGVhc3QgaGFsZiBvZiB0aGUgdGhlIGJsb2NrIGlzIGZpbGxlZCB1cCBpbiBhIHRpbWUgcGVyaW9kLiANCg0KRm9yIGV4YW1wbGUsIGxldHMgc2F5IHRoZSAxMDAwIFZhbGVuY2lhIEJsb2NrIGhhcyAzIG1ldGVycyBhY3RpdmUgYmV0d2VlbiB0aGUgdGltZSBwZXJpb2Qgb2YgOWFtIHRvIG5vb24uIFdpdGggZWFjaCBtZXRlciBoYXZpbmcgdGhlIGFiaWxpdHkgdG8gYmUgb3BlcmF0aW9uYWwgZm9yIDMgaG91ciwgdGhlIGJsb2NrIGhhcyBhbiBhZ2dyZWdhdGUgb2YgOSBwb3NzaWJsZSBwYXJraW5nIGhvdXJzIHRoYXQgcGVvcGxlIGNhbiBwYXJrIGluZnJvbnQgb2YgdGhpcyBbUml0dWFsIENvZmZlZV0oaHR0cHM6Ly9nb28uZ2wvbWFwcy9ub1U2SlR3eHRwa2ZLNlF5NikuIElmIGVhY2ggbWV0ZXIgaXMgb2NjdXBpZWQgZm9yIDIgaG91cnMgZHVyaW5nIHRoYXQgcGVyaW9kLCB0aGVuIHRoZSBibG9jayBhZ2dyZWdhdGVzIDYgb2NjdXBpZWQgaG91cnMgaW4gdG90YWwuIExlYWRpbmcgdG8gdGhhdCBibG9jayBhbmQgdGltZSBwZXJpb2QgaGF2aW5nIGEgcGFya2luZyBvY2N1cGF0aW9uIHJhdGUgb2YgNjYuNiUgKDZocnMgLyA5aHJzKS4gU28gdGhlcmUgd2lsbCBsaWtlbHkgYmUgYW4gb3BlbiBwYXJraW5nIHNwb3QgdGhhdCBJIGhhdmUgdG8gYXdrd2FyZGx5IHN0YW5kIGluIGJlY2F1c2UgdGhlIGxpbmUgaXMgdG9vIGxvbmcgdG8gbXkgZmF2b3JpdGUgY29mZmVlIHNob3AuIA0KDQpFdmVuIHRob3VnaCB0aGUgc3RhdGlzdGljIGRvZXNuJ3QgZGVzY3JpYmUgaG93IGxvbmcgYXQgbGVhc3Qgb25lIHNpbmdsZSBwYXJraW5nIG1ldGVyIGlzIHVub2NjdXBpZWQsIHRoZSByYXRlIHN0aWxsIHN1Z2dlc3RzIGhvdyBmcmVxdWVudGx5IHVzZWQgdGhlIHBhcmtpbmcgaXMuIFRoaXMgd2lsbCBzdGlsbCBiZSBoZWxwZnVsIGZvciBkcml2ZXJzIHdobyB3YW50IHRvIGZpbmQgYSBwYXJraW5nIG1ldGVyIGNsb3NlIHRvIGEgYm91Z2llLCAkNi1hLWN1cCBjb2ZmZWUgc2hvcC4gDQoNCiMjIyBUYWJsZQ0KDQpUaGUgZmlyc3QgdGFibGUgaGlnaGxpZ2h0cyB0aGUgZGlzdHJpYnV0aW9uIG9mIHRoZSBPY2N1cGF0aW9uIHJhdGUgYWNyb3NzIHRoZSBkYXkuIFdpdGggdGhlIGhpZ2hlc3QgcmF0ZSBvZiBwYXJraW5nIGJlaW5nIGFpbiB0aGUgbWlkZGxlIG9mIHRoZSBkYXkuIFRoaXMgbGFyZ2VseSBjb3VsZCBiZSB0aGUgcmVzdWx0IG9mIGJvdGggd29yay1yZWxhdGVkIGFuZCByZWNyZWF0aW9uYWwgdHJpcHMgYmVpbmcgYWN0aXZlIGF0IHRoZSBzYW1lIHRpbWUuIFdpdGggdGhlIGNpdHkgYmVpbmcgYSByZWdpb25hbCBhbmQgdG91cmlzdCBkZXN0aW5hdGlvbiwgbWFueSBvdXQtb2YtdG93biB0cmF2ZWxlcnMgaGVhdmlseSBsZWFuIG9uIHBhcmtpbmcuIA0KDQpgYGAge3IgRURBX3RhYmxlX2IsIHJlc3VsdHM9J2FzaXMnfQ0KDQoNCnBhcmsuaW1wb3J0JG9jYzUwID0gRkFMU0UNCnBhcmsuaW1wb3J0W3BhcmsuaW1wb3J0JHQucGN0Lm9jYyA+PSAuNSwgJ29jYzUwJ10gPSBUUlVFDQoNCg0KDQoNCnRhYiA9IA0KICBkZXNjcmliZUJ5KA0KICAgIHBhcmsuaW1wb3J0ICAlPiUgDQogICAgICBkcGx5cjo6c2VsZWN0KHQucGN0Lm9jYywgYmluKSwNCiAgICBncm91cD0nYmluJywgbWF0ID0gVFJVRSwNCiAgICBuYS5ybSA9IFRSVUUNCiAgKSAlPiUNCiAgYXMuZGF0YS5mcmFtZSgpICU+JQ0KICAjZmlsdGVyKGl0ZW0+MykgJT4lDQogIGFycmFuZ2UoZGVzYyhpdGVtKSkgJT4lDQogIGRwbHlyOjpzZWxlY3QoLWl0ZW0sIC12YXJzLCAtbikgJT4lDQogIHRhaWwoLiwgLTMpICU+JQ0KICBhcnJhbmdlKGZjdF9yZWxldmVsKGdyb3VwMSwgYygnOWEgdG8gMTJwJywnMTJwIHRvIDNwJywnM3AgdG8gNnAnKSkpDQoNCnJvd25hbWVzKHRhYikgPSBOVUxMDQoNCnRhYiAlPiUNCiAgdHJhbnNtdXRlKA0KICAgIGBUaW1lIFBlcmlvZGAgPSBncm91cDEsDQogICAgTWVhbiA9IG1lYW4gJT4lIHBlcmNlbnRfZm9ybWF0dGVyKC4sIG49MSksDQogICAgTWVkaWFuID0gbWVkaWFuICAlPiUgcGVyY2VudF9mb3JtYXR0ZXIoLiwgbj0xKSwNCiAgICBTRCA9IHNkICU+JSBwZXJjZW50X2Zvcm1hdHRlciguKSwNCiAgICBNaW4gPSBtaW4gJT4lIHBlcmNlbnRfZm9ybWF0dGVyKC4pLA0KICAgIE1heCA9IG1heCAlPiUgcGVyY2VudF9mb3JtYXR0ZXIoLikNCiAgKSAlPiUgDQogIGthYmxlKHRpdGxlID0gJ1BhcmtpbmcgTWV0ZXIgT2NjdXBhdGlvbiBSYXRlJykgJT4lDQogIGthYmxlX3N0eWxpbmcoKSAlPiUNCiAgYWRkX2hlYWRlcl9hYm92ZShjKCIgIiA9IDEsICJQYXJraW5nIE9jY3VwYXRpb24gUmF0ZSIgPSA1KSkNCg0KDQpgYGANCg0KVGhlIGZvbGxvd2luZyB0YWJsZSBoaWdobGlnaHRzIHRoZSBwcm9wb3J0aW9uIG9mIHRoZSBibG9ja3MgdGhhdCBhcmUgYWJvdmUgNTAlIHBhcmtpbmcgb2NjdXBhdGlvbi4gSXQgaXMgb2RkIHRoYXQgYSBtYWpvcml0eSBvZiBibG9ja3MgYXJlIG9ubHkgaGFsZiBmdWxsIG9mIHBhcmtpbmcuIFRoaXMgaXMgYW4gdW5lYXN5IHRyZW5kIGFzIHBlcnNvbmFsIGV4cGVyaWVuY2UgYW5kIFNGTVRBJ3MgcmVwb3J0IHN1Z2dlc3QgdGhhdCBibG9ja3MgYXJlIHR5cGljYWx5IG1vcmUgdGhhbiBoYWxmIGZ1bGwuIA0KDQpgYGAge3IgRURBX3RhYmxlLCByZXN1bHRzPSdhc2lzJ30NCg0KDQoNCnBhcmsubGVuID0gbnJvdyhwYXJrLmltcG9ydCkNCg0KcGFyay5pbXBvcnQgJT4lDQogIG11dGF0ZShgUGFya2luZ1xuQWJvdmUgNTAlXG5PY2N1cGllZGAgPSBvY2M1MCAlPiUgYXMuY2hhcmFjdGVyKCkpICU+JQ0KICBncm91cF9ieShgUGFya2luZ1xuQWJvdmUgNTAlXG5PY2N1cGllZGApICU+JSANCiAgZHBseXI6OnN1bW1hcml6ZSgNCiAgICBgUGVyY2VudFxub2YgVG90YWxgID0gKG4oKS9wYXJrLmxlbikgJT4lIHBlcmNlbnRfZm9ybWF0dGVyKC4pLA0KICAgIENvdW50ID0gbigpICU+JSBmb3JtYXQoYmlnLm1hcmsgPSAnLCcpDQogICAgICApICU+JQ0KICBrYWJsZSgpICU+JQ0KICBrYWJsZV9zdHlsaW5nKCkNCg0KYGBgDQoNCg0KIyMjIEhpc3RvZ3JhbQ0KDQpUaGUgaGlzdG9ncmFtIGJldHRlciBkZXNjcmliZXMgdGhlIG9jY3VwYXRpb24gcmF0ZSBkaXN0cmlidXRpb24uIFRoZSBibG9ja3MgcHJvdmlkZSBhIGJlbGwgY3VydmUgYXJvdW5kIHRoZSBtZWFuIG9mIDQxJSBvY2N1cGllZC4gVGhlIGJpbiBhdCAwJSBjb3VsZCBiZSBuYXR1cmFsbHkgZW1wdHkgYmxvY2tzLCBvciB0aGF0IHRoZXJlIGFyZSBibG9ja3MgYW5kIGJpbnMgdGhhdCBhcmVuJ3QgYWxsb3dlZCB0byBoYXZlIHBhcmtpbmcuIA0KDQpgYGAge3IgRURBX2hpc3R9DQoNCnRpdGxlID0gIlBhcmtpbmcgT2NjdXBhdGlvbiBSYXRlIEhpc3RvZ3JhbSINCnN1YnRpdGxlID0gJ1NhbiBGcmFuY2lzY28sIDIwMTknDQoNCmdncGxvdChwYXJrLmltcG9ydCwgYWVzKHggPSB0LnBjdC5vY2MpKSArIA0KICBnZW9tX2hpc3RvZ3JhbShhZXMoeSA9IC4uZGVuc2l0eS4uKSwNCiAgICAgICAgICAgICAgICAgY29sb3VyID0gMSwgZmlsbCA9ICJncmV5NzAiLCANCiAgICAgICAgICAgICAgICAgcG9zaXRpb249ImlkZW50aXR5IiwgY2xvc2VkID0gImxlZnQiLA0KICAgICAgICAgICAgICAgICBiaW53aWR0aCA9IC4wNSwgYm91bmRhcnkgPSAwDQogICAgICAgICAgICAgICAgICkgKw0KICBnZW9tX2RlbnNpdHkoYWVzKHk9Li5kZW5zaXR5Li4pLCBsd2QgPSAxLjIsDQogICAgICAgICAgICAgICBsaW5ldHlwZSA9IDIsDQogICAgICAgICAgICAgICBjb2xvdXIgPSAyKSArDQogIHNjYWxlX3hfY29udGludW91cygNCiAgICBsYWJlbHMgPSBmdW5jdGlvbihudW0pIG51bSAlPiUgcGVyY2VudF9mb3JtYXR0ZXIoKSwNCiAgICBuYW1lPSJNZXRlciBPY2N1cGF0aW9uIFJhdGUiKSArDQogIGxhYnMoDQogICAgdGl0bGU9dGl0bGUsDQogICAgc3VidGl0bGU9c3VidGl0bGUpICsNCiAgdGhlbWVfbWluaW1hbCgpDQoNCmBgYA0KDQojIyMgVGltZSBTZXJpZXMNCg0KVGhpcyB0aW1lIHNlcmllcyBjaGFydCBwcm92aWRlcyB0aGUgb2NjdXBhbmN5IHJhdGVzIG92ZXIgdGhlIGNvdXJzZSBvZiB0aGUgeWVhci4gRWFjaCBjaGFydCBhdmVyYWdlcyB0aGUgYmxvY2tzIGJ5IHRoZSBkYXksIHdlZWssIGFuZCBtb250aC4gVGhlIHRvcCBjaGFydHMgaGlnaGxpZ2h0IHRoYXQgdGhlIGF2ZXJhZ2Ugb2NjdXBhbmN5IHJhdGUgZG9lc24ndCB2YXJ5IG11Y2ggdGhyb3VnaG91dCB0aGUgeWVhciAtLSBhc2lkZSBmcm9tIGEgc21hbGwgMSUgZGlwIGluIHN1bW1lciBhbmQgd2ludGVyLiBUaGUgYm90dG9tIGRhdGUgY2hhcnQgaGlnaGxpZ2h0cyB0aGUgd2Vla2VuZCBwZWFrcyBvZiBwYXJraW5nLiBBcyBpbmRpY2F0ZWQgZWFybGllciwgdGhlc2UgcGVha3MgY291bGQgYmUgdGhlIHJlc3VsdCBvZiByZWNyZWF0aW9uYWwgdHJhdmVsLiANCg0KYGBge3IgRURBX3RpbWVzZXJpZXMsIGNhY2hlID0gVFJVRX0NCg0KeWJyZWFrcyA9IGMoLjQsLjQ1LC41KQ0KeXNtb2xicmVha3MgPSBjKC40MjUsLjQ3NSkNCg0KbWFwX3BhcmsgPSBmdW5jdGlvbigNCiAgdGltZT0nZGF0ZScsIGNvbG9yPSdibGFjaycsIA0KICBzaXplPTEsIHlsYWI9JycpDQogZ2dwbG90KA0KICBwYXJrLmltcG9ydCAlPiUNCiAgICBncm91cF9ieSguZGF0YVtbdGltZV1dKSAlPiUNCiAgICBkcGx5cjo6c3VtbWFyaXplKA0KICAgICAgdC5wY3Qub2NjID0NCiAgICAgICAgc3VtKHQuaHJzLm9jYykvc3VtKHQuaHJzLnRvdCkpKSsNCiAgZ2VvbV9saW5lKA0KICAgIGFlcygNCiAgICAgIHggPSAuZGF0YVtbdGltZV1dLCANCiAgICAgIHkgPSB0LnBjdC5vY2MpLCANCiAgICBjb2xvcj1jb2xvciwgc2l6ZT1zaXplKSsNCiAgbGFicygNCiAgICB0aXRsZT1nbHVlKCJieSB7dG91cHBlcih0aW1lKX0iKSwNCiAgICB4PSJEYXRlIiwNCiAgICB5PXlsYWIpICsNCiAgICB4bGltKA0KICAgICAgbWluKHBhcmsuaW1wb3J0W1t0aW1lXV0pLA0KICAgICAgbWF4KHBhcmsuaW1wb3J0W1t0aW1lXV0pKSArDQogICAgc2NhbGVfeV9jb250aW51b3VzKA0KICAgICAgYnJlYWtzID0geWJyZWFrcywNCiAgICAgIG1pbm9yX2JyZWFrcyA9IHlzbW9sYnJlYWtzLA0KICAgICAgbGFiZWxzID0gcGVyY2VudF9mb3JtYXR0ZXIsDQogICAgICBsaW1pdHMgPSBjKC40LC41KQ0KICAgICAgKSArDQogIHBsb3RUaGVtZSgpDQoNCmRhdGUgPSBtYXBfcGFyaygnZGF0ZScsICdkYXJrZ3JlZW4nLCBzaXplPS41KQ0Kd2VlayA9IG1hcF9wYXJrKCd3ZWVrJywgJ2dyZWVuJywgc2l6ZT0xLA0KICAgICAgICAgICAgICAgIHlsYWIgPSAiT2NjdXBpZWQgLyBPcGVyYXRpb25hbCBIb3VycyIpDQptb250aCA9IG1hcF9wYXJrKCdtb250aCcsICdsaWdodGdyZWVuJywgc2l6ZT0xLjUpDQoNCnJlbW92ZV94ID0gdGhlbWUoDQogIGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpLA0KICBheGlzLnRpY2tzLnggPSBlbGVtZW50X2JsYW5rKCksDQogIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfYmxhbmsoKQ0KKQ0KDQpwIDwtIGxpc3QoDQogIG1vbnRoICsgcmVtb3ZlX3gsDQogIHdlZWsgKyByZW1vdmVfeCwNCiAgZGF0ZQ0KKQ0KdGl0bGUgPSAiQXZlcmFnZSBPY2N1cGF0aW9uIFJhdGUgb2YgUGFya2luZyBNZXRlcnMiDQpzdWJ0aXRsZSA9ICdTYW4gRnJhbmNpc2NvLCAyMDE5Jw0Kd3JhcF9wbG90cyhwLCBucm93ID0gMykgKyANCiAgcGxvdF9sYXlvdXQoZ3VpZGVzID0gImNvbGxlY3QiKSArIA0KICBwbG90X2Fubm90YXRpb24oDQogICAgdGl0bGUgPSB0aXRsZSwNCiAgICBzdWJ0aXRsZSA9IHN1YnRpdGxlKSArIHBsb3RUaGVtZSgpDQoNCmBgYA0KDQojIyMgV2Vla2RheSBUaW1lIFNlcmllcw0KDQpUaGlzIHRhYmxlIGJ1aWxkcyBvZmYgdGhlIHByZXZpb3VzbHkgZGlzY3Vzc2VkIHdlZWtkYXkgdHJlbmQuIFNhdHVyZGF5cyBhcyBhIHdob2xlIGhhdmUgYSBoaWdoZXIgb2NjdXBhdGlvbiByYXRlIHRoYW4gb3RoZXIgZGF5cy4gVGhlIDEyLTMgdGltZSBwZXJpb2Qgc3RpbGwgcmVtYWlucyB2ZXJ5IGhpZ2ggb24gd2Vla2RheXMuDQoNCmBgYCB7ciBFREFfdGFibGVfd2Vla2RheSwgcmVzdWx0cz0nYXNpcyd9DQoNCnRpdGxlID0gIkF2ZXJhZ2UgUGVyY2VudCBvZiBUaW1lIE1ldGVyIGlzIE9jY3VwaWVkIg0KDQpwYXJrLmltcG9ydCRiaW4gPSBmYWN0b3IocGFyay5pbXBvcnQkYmluLCBsZXZlbHMgPSBjKCI5YSB0byAxMnAiLCAiMTJwIHRvIDNwIiwgIjNwIHRvIDZwIikpDQpwYXJrLmltcG9ydCRkYXkud2VlayA9IGZhY3RvcigNCiAgcGFyay5pbXBvcnQkZGF5LndlZWssDQogIGxldmVscyA9IA0KICAgIGMoIk1vbmRheSIsICJUdWVzZGF5IiwgIldlZG5lc2RheSIsICJUaHVyc2RheSIsICJGcmlkYXkiLCAiU2F0dXJkYXkiLCAiU3VuZGF5IikpDQoNCnBhcmsud2Vla2RheSA9IA0KICBwYXJrLmltcG9ydCAlPiUNCiAgICBncm91cF9ieShkYXkud2VlayxiaW4pICU+JQ0KICAgIHN1bW1hcmlzZSgNCiAgICAgIENvdW50ID0gc3VtKG1ldGVycy5jb3VudCksDQogICAgICBNZWFuID0gbWVhbih0LnBjdC5vY2Mscm0ubmE9VCkgIywNCiAgICAgICNNZWRpYW4gPSBtZWFuKHQucGN0Lm9jYyxybS5uYT1UKQ0KICAgICAgKQ0Kcm93bmFtZXMocGFyay53ZWVrZGF5KSA9IE5VTEwNCg0KcGFyay53ZWVrZGF5ICU+JQ0KICB1bmdyb3VwKCkgJT4lDQogIHRyYW5zbXV0ZSgNCiAgICBgV2VlayBEYXlgID0gZGF5LndlZWsgJT4lIGFzLmNoYXJhY3RlcigpLA0KICAgIGBUaW1lIFBlcmlvZGAgPSBiaW4gJT4lIGFzLmNoYXJhY3RlcigpLA0KICAgIENvdW50ID0gQ291bnQgJT4lIGZvcm1hdChiaWcubWFyaz0nLCcpLA0KICAgIE1lYW4gPSBNZWFuICU+JSBwZXJjZW50X2Zvcm1hdHRlciguLCBuPTEpICMsDQogICAgI01lZGlhbiA9IE1lZGlhbiAlPiUgcGVyY2VudF9mb3JtYXR0ZXIoLiwgbj0xKQ0KICAgICAgKSAlPiUNCiAga2FibGUodGl0bGU9dGl0bGUpICU+JQ0KICBrYWJsZV9zdHlsaW5nKCkgJT4lDQogIGNvbGxhcHNlX3Jvd3MoLiwgY29sdW1ucyA9IDE6MikNCg0KIyBjYmluZCgNCiMgICBwYXJrLmltcG9ydCAlPiUNCiMgICAgIGdyb3VwX2J5KGRheS53ZWVrKSAlPiUNCiMgICAgIGRwbHlyOjpzdW1tYXJpemUoDQojICAgICAgIGBPY2N1cGF0aW9uIFJhdGVgID0gbWVhbih0LnBjdC5vY2MsIG5hLnJtID0gVCkgJT4lIHBlcmNlbnRfZm9ybWF0dGVyKG49MSksDQojICAgICAgIGBPY2N1cGllZCBIb3Vyc2AgPSBzdW0odC5ocnMub2NjKSAlPiUgcm91bmQoMCkgJT4lIGZvcm1hdChiaWcubWFyaz0nLCcpLA0KIyAgICAgICBDb3VudCA9IG4oKQ0KIyAgICAgKSwNCiMgICBwYXJrLmltcG9ydCAlPiUNCiMgICAgIGZpbHRlcihvY2M1MD09VFJVRSkgJT4lDQojICAgICBncm91cF9ieShkYXkud2VlaykgJT4lDQojICAgICBkcGx5cjo6c3VtbWFyaXplKA0KIyAgICAgICBgQWJvdmUgNTAlIE9jY3VwaWVkYCA9IG4oKQ0KIyAgICAgKSAlPiUNCiMgICAgIHVuZ3JvdXAoKSAlPiUNCiMgICAgIGRwbHlyOjpzZWxlY3QoLWRheS53ZWVrKQ0KIyAgICkgJT4lDQojICAgcmVuYW1lKGBXZWVrIERheWAgPSBkYXkud2VlaykgJT4lDQojICAgbXV0YXRlKGBBYm92ZSA1MCUgT2NjdXBpZWRgID0gKGBBYm92ZSA1MCUgT2NjdXBpZWRgL0NvdW50KSAlPiUgcGVyY2VudF9mb3JtYXR0ZXIoKSkgJT4lDQojICAgZHBseXI6OnNlbGVjdCgtQ291bnQpICU+JQ0KIyAgIGthYmxlKCkgJT4lDQojICAga2FibGVfc3R5bGluZygpDQoNCmBgYA0KVGhpcyB0aW1lIHNlcmllcyBwbG90IGhlbHBzIHZpc3VhbGl6ZSB0aGUgd2Vla2VuZCB0cmVuZHMgd2hpbGUgc3BsaXR0aW5nIGJ5IHRoZSBjaXRpZXMgcXVhZHJhbnRzLiBUaGUgTkUtcXVhZHJhbnQgaXMgU2FuIEZyYW5jaXNjbydzIGhpZ2hlciBkZW5zaXR5IHJlY3JlYXRpb24gJiBlbXBsb3ltZW50IGFyZWEgLS0gaW5jbHVkaW5nIHRoZSBEb3dudG93biBCdXNpbmVzcyBEaXN0cmljdCwgdGhlIHRvdXJpc3R5IEZpc2hlcm1hbidzIFdoYXJmLCBhbmQgdGhlIGhpZ2ggdHJhZmZpYyBDaXZpYyBDZW50ZXIvVGVuZGVybG9pbi4gVGhlIFNFLXF1YWRyYW50IGFsc28gaXMgYSBoaWdoZXItdHJhZmZpYyBhcmVhIC0tIHdoaWNoIGluY2x1ZGVzIHRoZSBNaXNzaW9uIG5laWdoYm9yaG9vZCBhbmQgdGhlIHNwb3J0cyBzdGFkaXVtcy4gVGhlIFNXICYgTlcgcXVhZHJhbnRzIGFyZSBsYXJnZWx5IHJlc2lkZW50aWFsLg0KDQpUaGUgcGxvdCBzbGlnaHRseSBjb21wbGljYXRlcyB0aGlzIG5hcnJhdGl2ZSBhcyB0aGUgTlctcXVhZHJhbnQgaGFzIHRoZSBjb25zaXN0ZW50bHkgaGlnaGVzdCBvY2N1cGF0aW9uIHJhdGUgb2YgKzQwJS4gVGhpcyBpcyBsaWtlbHkgZHVlIHRvIHRoZSBwYXJraW5nIG1ldGVycyAoMjklIG9mIHRvdGFsKSBiZWluZyBsb2NhdGVkIG9uIHRoZSBidXN5IGRyaXZpbmcgY29ycmlkb3JzIG9mIEdlYXJ5IGJvdWxldmFyZCwgdGhlIE1hcmluYSwgYW5kIEhheWVzIFZhbGxleS4gVGhlIE5FLSAmIFNFLXF1YWRyYW50cyBvZiBEb3dudG93biAmIHRoZSBNaXNzaW9uIGhhdmUgaGlnaCBwZWFrcyBidXQgZHJvcCBvZmYgYXQgbmlnaHQgLS0gZHVlIHRvIHRoZSBkYXktZHJpdmVuIGVtcGxveW1lbnQgY2VudGVycy4gVGhlIFNXLXF1YWRyYW50IGhhcyBmZXcgbWV0ZXJzICgxNiUpIHNwcmVhZCBhY3Jvc3MgdGhlIGxhcmdlc3QsIGxvdy1kZW5zaXR5IGFyZWEgd2l0aCBhYnVuZGFudCBmcmVlIG9uLXN0cmVldCBwYXJraW5nLiANCg0KYGBgIHtyIHBsb3RfdGltZV93ZWVrZGF5fQ0KDQpsaWJyYXJ5KGZvcmNhdHMpDQoNCnBhcmsud2Vla2RheSA9IA0KICBwYXJrLmltcG9ydCAlPiUNCiAgICBncm91cF9ieShkYXkud2VlayxiaW4scXVhZCkgJT4lDQogICAgc3VtbWFyaXNlKA0KICAgICAgQ291bnQgPSBzdW0obWV0ZXJzLmNvdW50KSwNCiAgICAgIE1lYW4gPSBtZWFuKHQucGN0Lm9jYyxybS5uYT1UKSAjLA0KICAgICAgI01lZGlhbiA9IG1lYW4odC5wY3Qub2NjLHJtLm5hPVQpDQogICAgICApICU+JQ0KICBtdXRhdGUoDQogICAgaWQgPSByb3dfbnVtYmVyKCksDQogICAgUXVhZHJhbnQgPSBxdWFkDQogICAgDQogICAgKQ0KDQpwYXJrLndlZWtkYXkkd2Vla19iaW4gPSBmY3RfY3Jvc3MocGFyay53ZWVrZGF5JGJpbiwgcGFyay53ZWVrZGF5JGRheS53ZWVrLCBzZXAgPSAnIC0gJykNCg0KdGl0bGUgPSAiQXZlcmFnZSBPY2N1cGF0aW9uIFJhdGUgb2YgUGFya2luZyBNZXRlcnMiDQpzdWJ0aXRsZSA9ICdieSBXZWVrZGF5LCBUaW1lIFBlcmlvZCwgJiBRdWFkcmFudCBvZiBTYW4gRnJhbmNpc2NvJw0KDQoNCnlicmVha3MgPSBjKC4yNzUsLjMwLC4zMjUsLjM1LC4zNzUsLjQsLjQyNSwuNDUsLjQ3NSwuNSkNCg0KDQpwZXJpb2RfZm9ybWF0dGVyID0gZnVuY3Rpb24oYnJrKXsNCiAgYnN0ciA9IGJyayAlPiUgYXMuY2hhcmFjdGVyKCkgJT4lDQogICAgc3RyX3JlcGxhY2UoLiwgJ2EgJywgJyAnKSAlPiUgc3RyX3JlcGxhY2UoLiwgJ3AnLCAnJykgJT4lIA0KICAgIHN0cl9yZXBsYWNlKC4sICczcCcsICczJykgJT4lIHN0cl9yZXBsYWNlKC4sICc2cCcsICc2JykgJT4lDQogICAgc3RyX3JlcGxhY2UoLiwgJyAtICcsICcg4oCUICcpICU+JSBzdHJfcmVwbGFjZSguLCAnIHRvICcsICctJykgDQogIHNwbHQgPSBjKHN0cnNwbGl0KGJzdHIsICfigJQnLCBmaXhlZD1UKSklPiUgdW5saXN0KCkNCiAgZnN0ID0gc3BsdFtbMV1dDQogIHNlYyA9IGlmZWxzZShmc3Q9PSc5LTEyICcsIHNwbHRbWzJdXSwgJycpDQogIGZpbiA9IHBhc3RlKHNlYyxmc3QsIGNvbGxhcHNlID0gIiAgICIpICU+JSBzdHJfdHJpbSgpDQogIHJldHVybihmaW4pDQp9DQoNCg0KYnJrcyA9IHVuaXF1ZShwYXJrLndlZWtkYXkkd2Vla19iaW4pICU+JSBhcy5jaGFyYWN0ZXIoKSAlPiUgDQogIGxhcHBseSguLCBmdW5jdGlvbih3aykgcGVyaW9kX2Zvcm1hdHRlcih3aykpDQoNCg0KZ2dwbG90KA0KICBwYXJrLndlZWtkYXkgJT4lIHVuZ3JvdXAoKSkgKyANCiAgICBhbm5vdGF0ZSgicmVjdCIsIA0KICAgICAgICAgICAgIHhtaW49MS0uNSwgeG1heD00LS41LCB5bWluPS1JbmYsIHltYXg9SW5mLCANCiAgICAgICAgICAgICBhbHBoYT0wLjIsIGZpbGw9ImdyZXk0MCIpICsNCiAgICBhbm5vdGF0ZSgicmVjdCIsIA0KICAgICAgICAgICAgIHhtaW49MSs2LS41LCB4bWF4PTQrNi0uNSwgeW1pbj0tSW5mLCB5bWF4PUluZiwgDQogICAgICAgICAgICAgYWxwaGE9MC4yLCBmaWxsPSJncmV5NDAiKSArDQogICAgYW5ub3RhdGUoInJlY3QiLCANCiAgICAgICAgICAgICB4bWluPTErNioyLS41LCB4bWF4PTQrNioyLS41LCB5bWluPS1JbmYsIHltYXg9SW5mLCANCiAgICAgICAgICAgICBhbHBoYT0wLjIsIGZpbGw9ImdyZXk0MCIpICsNCiAgZ2VvbV9saW5lKA0KICAgIGFlcygNCiAgICAgIHggPSB3ZWVrX2JpbiwgDQogICAgICB5ID0gTWVhbiwgDQogICAgICBncm91cCA9IGlkLA0KICAgICAgY29sb3I9UXVhZHJhbnQNCiAgICAgICksDQogICAgYWxwaGE9MC45MCwNCiAgICBzaXplPTEuNQ0KICAgICkgKyANCiAgbGFicygNCiAgICB0aXRsZT10aXRsZSwNCiAgICBzdWJ0aXRsZT1zdWJ0aXRsZSwNCiAgICB4PSJUaW1lIFBlcmlvZCIsDQogICAgeT0gIk9jY3VwaWVkIE1ldGVyIFJhdGUiKSArDQogICAgIyB4bGltKA0KICAgICMgICBtaW4ocGFyay5pbXBvcnRbW3RpbWVdXSksDQogICAgIyAgIG1heChwYXJrLmltcG9ydFtbdGltZV1dKSkgKw0KICAgIHNjYWxlX3lfY29udGludW91cygNCiAgICAgIGJyZWFrcyA9IHlicmVha3MsDQogICAgICBsYWJlbHMgPSBwZXJjZW50X2Zvcm1hdHRlciwNCiAgICAgICNsaW1pdHMgPSBjKC40LC41KQ0KICAgICAgKSArDQogICAgIyBzY2FsZV94X2NvbnRpbnVvdXMoDQogICAgIyAgICwNCiAgICAjICAgbGFiZWxzID0gcGVyaW9kX2Zvcm1hdHRlcg0KICAgICMgICApDQogICAgc2NhbGVfeF9kaXNjcmV0ZShsYWJlbHM9IGJya3MpICsgDQogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KA0KICAgIGFuZ2xlID0gNDUsIHZqdXN0ID0gMSwgaGp1c3Q9MSkpICsgDQogIHBsb3RUaGVtZSgpDQoNCg0KYGBgDQojIyMgVGltZSBQZXJpb2QgTWFwDQoNClRoZSBUaW1lIFBlcmlvZCBNYXAgYnkgUXVhZHJhbnQgaGlnaGxpZ2h0cyB0aGUgdHJlbmRzIHByZXZpb3VzbHkgZGlzY3Vzc2VkLiBTdGFydGluZyB3aXRoIHRoZSBEb3dudG93biBORS1xdWFkcmFudCwgdGhlcmUgaXMgYSBoZWF2eSB2YXJpYXRpb24gaW4gdGltZSBhbmQgbG9jYXRpb24uIFRoZSBub3J0aCBjb2FzdCBpcyBsYXJnZWx5IHRoZSB0b3VyaXN0eSBGaXNoZXJtYW4ncyBXaGFyZiB3aGlsZSB0aGUgY29yZSBoYXMgbW9yZSBidXNpbmVzcyBhY3Rpdml0eS4gVGhpcyB3aWxsIGJlIGRpc2N1c3NlZCBpbiBtb3JlIGxlbmd0aCBpbiB0aGUgbmV4dCBtYXAuDQoNClRoZSBTRS1xdWFkcmFudCBoYXMgYSBoZWF2eSB2YXJpYXRpb24gaW4gdGhlIGZhciBub3J0aC1lYXN0ZXJuIGNvcm5lYSwgaW4gdGhlIFNPTUEgKignU291dGggb2YgTWFya2V0JykqICYgTWlzc2lvbiBCYXkgYXJlYSwgd2hlcmUgdGhlcmUgaXMgYSBoZWF2eSBjb25jZW50cmF0aW9uIG9mIHRlY2ggY29tcGFuaWVzIChpLmUuIFR3aXR0ZXIsIFNhbGVzZm9yY2UpIGFuZCBhIHJlZ2lvbmFsIHJhaWwgc3RhdGlvbi4gQXMgYSByZXN1bHQsIHRoZSBtb3JuaW5nIGhhcyBhIGhlYXZ5IGFtb3VudCBvZiBwYXJraW5nIG9jY3VwYW5jeSBidXQgZHJhbWF0aWNhbGx5IGRyb3BzIGluIHRoZSBldmVuaW5nLiBUaGUgYXJlYSBpcyBuZXdseSBkZXZlbG9wZWQgYW5kIGhhcyByb29tIHRvIGZsZXNoZWQgb3V0IGl0cyByZWNyZWF0aW9uYWwgYWN0aXZpdGllcyBwYXN0IHRoZSBHaWFudHMgYW5kIFdhcnJpb3JzIHN0YWRpdW1zLiBDb21iaW5lZCB3aXRoIG1vcmUgb2ZmLXN0cmVldCBnYXJhZ2VzLCB0aGVyZSBpcyBsZXNzIHBhcmtpbmcgZGVtYW5kLg0KDQpBcyBtZW50aW9uZWQsIHRoZSBOVy1xdWFkcmFudCBoYXMgcG9ja2V0cyBvZiBjb25zaXN0ZW50bHkgb2NjdXBpZWQgY29ycmlkb3JzLiBUaGUgU0UtcXVhZHJhbnQgaXMgdmVyeSBsYXJnZSB3aXRoIGZldyBjb21tZXJjaWFsIGNlbnRlcnMgdG8gYXR0cmFjdCBtb3JlIHBhaWQgcGFya2luZy4gDQoNCmBgYCB7ciBFREFfbWFwfQ0KDQpTRi5xdWFkLmxhYmVscyA9IHNmX3RvX2xhYmVscyhTRi5xdWFkLCAncXVhZCcpDQoNCnBhcmsubWFwID0gDQogICNyYmluZCgNCiAgICBwYXJrLmltcG9ydCAlPiUNCiAgICAgIGRwbHlyOjpncm91cF9ieShpZC5ibG9jaywgYmluKSAlPiUNCiAgICAgIGRwbHlyOjpzdW1tYXJpemUoDQogICAgICAgIHQucGN0Lm9jYyA9IG1lYW4odC5wY3Qub2NjLCBybS5uYT1UKSwNCiAgICAgICAgbWV0ZXJzLmNvdW50ID0gc3VtKG1ldGVycy5jb3VudCkNCiAgICAgICkgJT4lICMsDQogICAgIyBwYXJrLmltcG9ydCAlPiUNCiAgICAjICAgZHBseXI6Omdyb3VwX2J5KGlkLmJsb2NrKSAlPiUNCiAgICAjICAgZHBseXI6OnN1bW1hcml6ZSgNCiAgICAjICAgICB0LnBjdC5vY2MgPSBtZWFuKHQucGN0Lm9jYywgcm0ubmE9VCksDQogICAgIyAgICAgbWV0ZXJzLmNvdW50ID0gc3VtKG1ldGVycy5jb3VudCkNCiAgICAjICAgKSAlPiUNCiAgICAjICAgbXV0YXRlKGJpbiA9ICdBbGwnKQ0KICAgICMpJT4lDQogIG1lcmdlKA0KICAgIC4sDQogICAgcGFyay5ibG9ja3MsDQogICAgb249J2lkLmJsb2NrJw0KICApICU+JQ0KICBzdF9zZigpICU+JQ0KICBzdF9idWZmZXIoMjUwKQ0KICANCnBhcmsubWFwJGJpbiA9IGZhY3RvcihwYXJrLm1hcCRiaW4sIGxldmVscyA9IGMoDQogICMiQWxsIiwNCiAgIjlhIHRvIDEycCIsICIxMnAgdG8gM3AiLCAiM3AgdG8gNnAiKSkNCiMgcGFyay5tYXAkZGF5LndlZWsgPSBmYWN0b3IoDQojICAgcGFyay5tYXAkZGF5LndlZWssDQojICAgbGV2ZWxzID0gDQojICAgICBjKCJNb25kYXkiLCAiVHVlc2RheSIsICJXZWRuZXNkYXkiLCAiVGh1cnNkYXkiLCAiRnJpZGF5IiwgIlNhdHVyZGF5IiwgIlN1bmRheSIpKQ0KDQpnZ3Bsb3QoKSsNCiAgZ2VvbV9zZigNCiAgICBkYXRhID0gU0YucXVhZCwgZmlsbD0nZ3JleTkwJw0KICApICsNCiAgZ2VvbV9zZihkYXRhID0gcGFyay5tYXAsDQogICAgICAgICAgYWVzKGZpbGwgPSB0LnBjdC5vY2MpLCAjLCBzaXplPW1ldGVycy5jb3VudCksIA0KICAgICAgICAgIGNvbG9yID0gInRyYW5zcGFyZW50IiwgYWxwaGEgPSAwLjgNCiAgICAgICAgICApICsNCiAgZ2VvbV90ZXh0X3NmKA0KICAgIFNGLnF1YWQubGFiZWxzLCBsYWJlbC5zaXplID0gLjUsDQogICAgdmp1c3QgPSAnYm90dG9tJw0KICAgICkgKyANCiAgc2NhbGVfZmlsbF9ncmFkaWVudG4oDQogICAgY29sb3JzID0gcmV2KFJDb2xvckJyZXdlcjo6YnJld2VyLnBhbCg1LCAiUmRZbEduIikpLA0KICAgIHZhbHVlcyA9IGMoMCwuMjUsLjU1LC43NSwxKSwNCiAgICBsYWJlbHMgPSBmdW5jdGlvbih0c3RyKSBwZXJjZW50X2Zvcm1hdHRlcih0c3RyKSwNCiAgICBuYW1lPSclIG9mIFRpbWVcbk1ldGVyIGlzXG5PY2N1cGllZCcsDQogICAgZ3VpZGUgPSAiY29sb3JiYXIiDQogICAgI2RpcmVjdGlvbiA9IC0xLCBkaXNjcmV0ZSA9IEZBTFNFLCBvcHRpb24gPSAiRCINCiAgICApICsNCiAgZ2VvbV9zZihkYXRhID0gU0YucXVhZCwgY29sb3I9J2JsYWNrJywNCiAgICAgICAgICBmaWxsID0gInRyYW5zcGFyZW50IiwgI2FscGhhID0gMC44LA0KICAgICAgICAgIHNpemU9LjAxDQogICAgICAgICAgI2xpbmV0eXBlID0gJ2Rhc2hlZCcNCiAgICAgICAgICApICsNCiAgZmFjZXRfd3JhcCh+YmluLCBuY29sID0gMykrDQogIGxhYnMoDQogICAgdGl0bGU9J1BhcmtpbmcgTWV0ZXIgT2NjdXBhdGlvbiBSYXRlIC0gYnkgVGltZSBQZXJpb2QnLA0KICAgIHN1YnRpdGxlID0gJ1NhbiBGcmFuY2lzY28sIDIwMTknDQogICAgKSArIA0KICBtYXBUaGVtZSsNCiAgZ3VpZGVzKA0KICAgIGZpbGwgPSBndWlkZV9jb2xvdXJiYXIoDQogICAgICBiYXJ3aWR0aCA9IDE2LA0KICAgICAgYmFyaGVpZ2h0ID0gMSwNCiAgICAgIGRpcmVjdGlvbj0iaG9yaXpvbnRhbCIsDQogICAgICAjbGFiZWwucG9zaXRpb24gPSAiYm90dG9tIg0KICAgICAgKQ0KICAgICkgKyANCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJib3R0b20iKQ0KDQpgYGANCg0KDQojIyMgRG93bnRvd24gTWFwDQoNCldpdGggRG93bnRvd24gTkUtcXVhZHJhbnQgaGF2aW5nIGEgbGFyZ2VyIHNoYXJlIG9mIG1ldGVycyAoMzAuNSUpIGFuZCBhbiBpbnRlcmVzdGluZyBsZXZlbCBvZiB2YXJpZXR5IG9mIHVzZXMsIG91ciB0ZWFtIHdpbGwgZm9jdXMgb25lIG9mIG91ciBtb2RlbHMganVzdCBvbiB0aGUgYXJlYS4gVGhlIG1hcCBoaWdobGlnaHRzIHRoZSBwcmV2aW91c2x5IGRpc2N1c3NlZCB0cmVuZHMgb2YgdGhlIHRvdXJpc3R5IGNvYXN0IG9mIEZpc2hlcm1hbidzIFdoYXJmLiBUaGUgQ2l2aWMgQ2VudGVyIGFyZWEncyBsYXJnZSBhbW91bnQgb2YgZ292ZXJubWVudCwgdGhlYXRlciwgYW5kIGJ1c2luZXNzIGJ1aWxkaW5ncyBtYWtlcyBpdCBhIGhvdCBzcG90IGZvciBwYXJraW5nLiBUaGUgVGVuZGVybG9pbiBhbmQgQ2hpbmF0b3duIGhhdmUgbG93ZXIgb2NjdXBhdGlvbiByYXRlcyBhcyB0aGV5IGRvbid0IGhhdmUgYXMgbWFueSBzcGVjaWZpYyBkZXN0aW5hdGlvbnMgb3IgY2FyLW93bmVycy4gDQoNCmBgYCB7ciBFREFfbWFwX2R3dG5fMSwgZmlnLndpZHRoPTh9DQoNCnBhdGggPSAiQzovVXNlcnMvbmVsbXMvRG9jdW1lbnRzL1Blbm4vTVVTQS01MDgvTVVTQTUwOF9GSU5BTF9TRlBBUksvZGF0YS9QYXJraW5nX01hbmFnZW1lbnRfRGlzdHJpY3RzLmNzdiINClNGLmRpc3QgPSByZWFkLmNzdihwYXRoKQ0KDQpTRi5kaXN0JGdlb21ldHJ5ID0gc3RfYXNfc2ZjKFNGLmRpc3Qkc2hhcGUsIGNycyA9IDQzMjYpDQoNClNGLmRpc3QgPSBTRi5kaXN0ICU+JSANCiAgc3Rfc2YoKSAlPiUgc3RfdHJhbnNmb3JtKHNmX2NycykgJT4lDQogIGRwbHlyOjpzZWxlY3QoUE1fRElTVFJJQ1RfSUQsIFBNX0RJU1RSSUNUX05BTUUsIGdlb21ldHJ5KSAlPiUNCiAgc3Rfam9pbiguLCBTRi5xdWFkLCBsYXJnZXN0PVRSVUUpDQoNCiNTRi5xdWFkLmxhYmVscyA9IHNmX3RvX2xhYmVscyhTRi5xdWFkLCAncXVhZCcpICU+JSBkcGx5cjo6ZmlsdGVyKGxhYmVsPT0nTkUnKQ0KU0YuZGlzdC5sYWJlbHMgPSBzZl90b19sYWJlbHMoDQogIFNGLmRpc3QgJT4lIGFycmFuZ2UocXVhZCkgJT4lIA0KICAgIGRwbHlyOjpmaWx0ZXIocXVhZD09J05FJykgJT4lDQogICAgZHBseXI6OmZpbHRlcighUE1fRElTVFJJQ1RfTkFNRSAgJWluJSBjKA0KICAgICAgJ1BvbGsnLCAnVGVsZWdyYXBoIEhpbGwnDQogICAgKSksICdQTV9ESVNUUklDVF9OQU1FJykgJT4lDQogIG11dGF0ZShsYWJlbCA9IA0KICAgIGlmZWxzZSgNCiAgICAgIGxhYmVsID09ICdOLkJlYWNoLUNoaW5hdG93bicsDQogICAgICAnTi5CZWFjaFxuXG5DaGluYXRvd24nLA0KICAgIGlmZWxzZSgNCiAgICAgIGxhYmVsID09ICJGaXNoZXJtYW4ncyBXaGFyZiIsDQogICAgICAiXG5GaXNoZXJtYW4ncyBXaGFyZiIsDQogICAgICAgICAgIGxhYmVsKQ0KICAgICAgKSAjJT4lIGdzdWIoJyAnLCdcbicsIC4sIGZpeGVkID0gVFJVRSkNCiAgICApDQoNCnBhcmsubWFwID0gDQogIHBhcmsuaW1wb3J0ICU+JSANCiAgICBkcGx5cjo6ZmlsdGVyKHF1YWQ9PSdORScpICU+JQ0KICAgIGRwbHlyOjpncm91cF9ieShpZC5ibG9jaywgYmluKSAlPiUNCiAgICBkcGx5cjo6c3VtbWFyaXplKA0KICAgICAgdC5wY3Qub2NjID0gbWVhbih0LnBjdC5vY2MsIHJtLm5hPVQpDQogICAgKSAlPiUNCiAgbWVyZ2UoDQogICAgLiwNCiAgICBwYXJrLmJsb2NrcywNCiAgICBvbj0naWQuYmxvY2snDQogICkgJT4lDQogIHN0X3NmKCkgJT4lDQogIHN0X2J1ZmZlcig1MCkNCg0KI3BhcmsubWFwJGJpbiA9IGZhY3RvcihwYXJrLm1hcCRiaW4sIGxldmVscyA9IGMoIkFsbCIsIjlhIHRvIDEycCIsICIxMnAgdG8gM3AiLCAiM3AgdG8gNnAiKSkNCiMgcGFyay5tYXAkZGF5LndlZWsgPSBmYWN0b3IoDQojICAgcGFyay5tYXAkZGF5LndlZWssDQojICAgbGV2ZWxzID0gDQojICAgICBjKCJNb25kYXkiLCAiVHVlc2RheSIsICJXZWRuZXNkYXkiLCAiVGh1cnNkYXkiLCAiRnJpZGF5IiwgIlNhdHVyZGF5IiwgIlN1bmRheSIpKQ0Kc2VwID0gMTAwMDANCmdncGxvdCgpKw0KICBnZW9tX3NmKA0KICAgIGRhdGEgPSBTRi5xdWFkICU+JSANCiAgICAgIGRwbHlyOjpmaWx0ZXIocXVhZD09J05FJyksIGNvbG9yID0gJ3RyYW5zcGFyZW50JywgZmlsbD0nZ3JleTkwJw0KICApICsNCiAgZ2VvbV9zZihkYXRhID0gcGFyay5tYXAsDQogICAgICAgICAgYWVzKGZpbGwgPSB0LnBjdC5vY2MpLCAjLCBzaXplPW1ldGVycy5jb3VudCksIA0KICAgICAgICAgIGNvbG9yID0gInRyYW5zcGFyZW50IiwgI2FscGhhID0gMC45NQ0KICAgICAgICAgICkgKw0KICBnZW9tX3RleHQoDQogICAgICAgIGRhdGE9U0YuZGlzdC5sYWJlbHMsIGNoZWNrX292ZXJsYXA9VFJVRSwNCiAgICAgICAgZm9udGZhY2U9J2JvbGQnLCBjb2xvcj0nYmxhY2snLA0KICAgICAgICBzaXplPTMsDQogICAgICAgIGFlcyh4PWxvbix5PWxhdCwgbGFiZWw9bGFiZWwpKSArIA0KICAjIGdlb21fdGV4dF9zZigNCiAgIyAgIFNGLmRpc3QubGFiZWxzLCBjaGVja19vdmVybGFwID0gVFJVRSwNCiAgIyAgIHNpemU9MQ0KICAjICAgI2hqdXN0ID0gInRvcCIjLCB2anVzdCA9ICJvdXR3YXJkIg0KICAjICAgKSArIA0KICAjeGxpbShtaW4oU0YuZGlzdC5sYWJlbHMkbG9uKS1zZXAsIG1heChTRi5kaXN0LmxhYmVscyRsb24pK3NlcCkgKyANCiAgc2NhbGVfZmlsbF9ncmFkaWVudG4oDQogICAgY29sb3JzID0gcmV2KFJDb2xvckJyZXdlcjo6YnJld2VyLnBhbCg1LCAiUmRZbEduIikpLA0KICAgIHZhbHVlcyA9IGMoMCwuMjUsLjYwLC44MCwxKSwNCiAgICBsYWJlbHMgPSBmdW5jdGlvbih0c3RyKSBwZXJjZW50X2Zvcm1hdHRlcih0c3RyKQ0KICAgICNkaXJlY3Rpb24gPSAtMSwgZGlzY3JldGUgPSBGQUxTRSwgb3B0aW9uID0gIkQiDQogICAgKSsNCiAgZ3VpZGVzKGZpbGwgPSBndWlkZV9jb2xvdXJiYXIoYmFyd2lkdGggPSAyKSkgKyANCiAgZmFjZXRfd3JhcCh+YmluLCBuY29sID0gMykrDQogIGxhYnMoDQogICAgdGl0bGU9J1BhcmtpbmcgTWV0ZXIgT2NjdXBhdGlvbiBSYXRlIC0gRG93bnRvd24gQXJlYScsDQogICAgc3VidGl0bGUgPSAnU2FuIEZyYW5jaXNjbywgMjAxOScNCiAgICApKw0KICBtYXBUaGVtZSsNCiAgZ3VpZGVzKA0KICAgIGZpbGwgPSBndWlkZV9jb2xvdXJiYXIoDQogICAgICBiYXJ3aWR0aCA9IDE2LA0KICAgICAgYmFyaGVpZ2h0ID0gMSwNCiAgICAgIGRpcmVjdGlvbj0iaG9yaXpvbnRhbCIsDQogICAgICAjbGFiZWwucG9zaXRpb24gPSAiYm90dG9tIg0KICAgICAgKQ0KICAgICkgKyANCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJib3R0b20iKQ0KDQpgYGANCg0KV2l0aCB0aGlzIG1hcCwgaXQgd2lsbCBiZSBpbXBvcnRhbnQgdG8gaW5jb3Jwb3JhdGUgcHJlZGljdG9yIHZhcmlhYmxlcyB0aGF0IGhpZ2hsaWdodCB0aGUgbmVpZ2hvcmhvb2QgYW5kIHBhcmtpbmcgZGlzdHJpY3Qgc3BlY2lmaWMgc3BhdGlhbCBhbmQgdGltZSBwYXR0ZXJucy4NCg0KIyMgUHJlZGljdG9yIFZhcmlhYmxlcw0KDQoNCmBgYCB7ciBFREFfYXZnX3RhYnMsIHJlc3VsdHM9J2FzaXMnfQ0KDQp0YWJsZV90aXRsZSA9IGdsdWUoIlN1bW1hcnkgU3RhdGlzdGljcyIpDQoNCnZhcnMgPSBjKA0KICAidC5wY3Qub2NjIiwNCiAgImF2Zy5ibGsuZGF5IiwgImF2Zy5ibGsud2Vla2RheSIsICJhdmcuYmxrLm1vbnRoIiwgDQogICJhdmcuYmxrYmluLndlZWtkYXkiLCAiYXZnLmJsa2Jpbi5tb250aCIsICJhdmcuZGlzdGJpbi5kYXkiLA0KICAiYXZnLmRpc3RiaW4ud2Vla2RheSIsICJhdmcuZGlzdGJpbi5tb250aCINCiAgIyAibGFnLmJsa2Jpbi4xYmluIiwgImxhZy5ibGtiaW4uMWRheSIsImxhZy5ibGtiaW4ud2VlayINCiAgKQ0KDQpDYXRlZ29yeSA9IGMoDQogICdPY2N1cGF0aW9uXG5SYXRlJywNCiAgJ0F2ZXJhZ2VcbkJsb2NrJywnQXZlcmFnZVxuQmxvY2snLCdBdmVyYWdlXG5CbG9jaycsDQogICdBdmVyYWdlXG5EaXN0cmljdCAmIEJpbicsJ0F2ZXJhZ2VcbkRpc3RyaWN0ICYgQmluJywNCiAgJ0F2ZXJhZ2VcbkJsb2NrICYgQmluJywgICdBdmVyYWdlXG5CbG9jayAmIEJpbicsICAnQXZlcmFnZVxuQmxvY2sgJiBCaW4nDQogICMgJ1RpbWUgTGFnJywnVGltZSBMYWcnLCdUaW1lIExhZycNCikNCnBhcmsudGFiID0NCiAgcHN5Y2g6OmRlc2NyaWJlKA0KICAgIHBhcmsuaW1wb3J0ICU+JSANCiAgICAgIGRwbHlyOjpzZWxlY3QodmFycyksDQogICAgbmEucm0gPSBUUlVFDQogICAgKSAlPiUgDQogICAgYXMuZGF0YS5mcmFtZSgpICU+JQ0KICAgIHRyYW5zbXV0ZSgNCiAgICAgIE1lYW4gPSBtZWFuICU+JSBwZXJjZW50X2Zvcm1hdHRlciguLCBuPTIpLA0KICAgICAgU0QgPSBzZCAlPiUgcGVyY2VudF9mb3JtYXR0ZXIoLiksDQogICAgICBNaW4gPSBtaW4gJT4lIHBlcmNlbnRfZm9ybWF0dGVyKC4pLA0KICAgICAgTWF4ID0gbWF4ICU+JSBwZXJjZW50X2Zvcm1hdHRlciguKQ0KICAgICkgDQppbmR4ID0gcm93bmFtZXMocGFyay50YWIpDQpyb3duYW1lcyhwYXJrLnRhYikgPSBOVUxMDQogIGNiaW5kKA0KICAgIENhdGVnb3J5ID0gQ2F0ZWdvcnksDQogICAgVmFyaWFibGVzID0gaW5keCwNCiAgICBwYXJrLnRhYg0KICApICU+JQ0KICBrYWJsZSgpICU+JQ0KICBrYWJsZV9zdHlsaW5nKCkgJT4lDQogIGNvbGxhcHNlX3Jvd3MoLiwgY29sdW1ucyA9IDEpDQoNCmBgYA0KDQoNCmBgYCB7ciBFREFfYXZnX2xhZywgcmVzdWx0cz0nYXNpcyd9DQoNCnRhYmxlX3RpdGxlID0gZ2x1ZSgiU3VtbWFyeSBTdGF0aXN0aWNzIikNCg0KdmFycyA9IGMoDQogICJ0LnBjdC5vY2MiLA0KICAjICJhdmcuYmxrLmRheSIsICJhdmcuYmxrLndlZWtkYXkiLCAiYXZnLmJsay5tb250aCIsIA0KICAjICJhdmcuYmxrYmluLndlZWtkYXkiLCAiYXZnLmJsa2Jpbi5tb250aCIsICJhdmcuZGlzdGJpbi5kYXkiLA0KICAjICJhdmcuZGlzdGJpbi53ZWVrZGF5IiwgImF2Zy5kaXN0YmluLm1vbnRoIiwgDQogICJsYWcuYmxrYmluLjFiaW4iLCAibGFnLmJsa2Jpbi4xZGF5IiwibGFnLmJsa2Jpbi53ZWVrIg0KICApDQoNCkNhdGVnb3J5ID0gYygNCiAgJ09jY3VwYXRpb25cblJhdGUnLA0KICAjICdBdmVyYWdlXG5CbG9jaycsJ0F2ZXJhZ2VcbkJsb2NrJywnQXZlcmFnZVxuQmxvY2snLA0KICAjICdBdmVyYWdlXG5EaXN0cmljdCAmIEJpbicsJ0F2ZXJhZ2VcbkRpc3RyaWN0ICYgQmluJywNCiAgIyAnQXZlcmFnZVxuQmxvY2sgJiBCaW4nLCAgJ0F2ZXJhZ2VcbkJsb2NrICYgQmluJywgICdBdmVyYWdlXG5CbG9jayAmIEJpbicsDQogICdUaW1lIExhZycsJ1RpbWUgTGFnJywnVGltZSBMYWcnDQopDQoNCnBhcmsudGFiID0NCiAgcHN5Y2g6OmRlc2NyaWJlKA0KICAgIHBhcmsuaW1wb3J0ICU+JSANCiAgICAgIGRwbHlyOjpzZWxlY3QodmFycyksDQogICAgbmEucm0gPSBUUlVFDQogICAgKSAlPiUgDQogICAgYXMuZGF0YS5mcmFtZSgpICU+JQ0KICAgIHRyYW5zbXV0ZSgNCiAgICAgIE1lYW4gPSBtZWFuICU+JSBwZXJjZW50X2Zvcm1hdHRlciguLCBuPTIpLA0KICAgICAgU0QgPSBzZCAlPiUgcGVyY2VudF9mb3JtYXR0ZXIoLiksDQogICAgICBNaW4gPSBtaW4gJT4lIHBlcmNlbnRfZm9ybWF0dGVyKC4pLA0KICAgICAgTWF4ID0gbWF4ICU+JSBwZXJjZW50X2Zvcm1hdHRlciguKQ0KICAgICkgDQppbmR4ID0gcm93bmFtZXMocGFyay50YWIpDQpyb3duYW1lcyhwYXJrLnRhYikgPSBOVUxMDQogIGNiaW5kKA0KICAgIENhdGVnb3J5ID0gQ2F0ZWdvcnksDQogICAgVmFyaWFibGVzID0gaW5keCwNCiAgICBwYXJrLnRhYg0KICApICU+JQ0KICBrYWJsZSgpICU+JQ0KICBrYWJsZV9zdHlsaW5nKCkgJT4lDQogIGNvbGxhcHNlX3Jvd3MoLiwgY29sdW1ucyA9IDEpDQoNCmBgYA0KDQoNCiMjIyBXZWF0aGVyIFBsb3QNCg0KQXMgdGhlIGVhcmxpZXIgY29ycmVsYXRpb24gdGFibGUgc2hvd2VkLCB0aGUgd2VhdGhlciBwYXR0ZXJucyBkb24ndCBsaW5lIHVwIHdpdGggYW55IG9mIHRoZSBjaGFuZ2VzIGluIHBhcmtpbmcgb2NjdXBhdGlvbi4gQXMgYSByZXN1bHQsIHRoZSB3ZWF0aGVyIHZhcmlhYmxlcyB3aWxsIGJlIGxlZnQgb3V0DQoNCmBgYHtyIHBsb3Rfd2VhdGhlciwgY2F0Y2hlID0gVFJVRSwgd2FybmluZz1GQUxTRX0NCnJlbW92ZV94ID0gdGhlbWUoDQogIGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpLA0KICBheGlzLnRpY2tzLnggPSBlbGVtZW50X2JsYW5rKCksDQogIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfYmxhbmsoKQ0KKQ0KDQpwYXRoID0gJ0M6L1VzZXJzL25lbG1zL0RvY3VtZW50cy9QZW5uL01VU0EtNTA4L01VU0E1MDhfRklOQUxfU0ZQQVJLL2RhdGEvc2Zfd2VhdGhlcl8yMDE5LnBhcnF1ZXQnDQoNCm9hay53ZWF0aGVyLmRhaWx5ID0gcmVhZF9hcnJvdyhwYXRoKSAlPiUNCiAgZHBseXI6OnNlbGVjdCh2YWxpZCwgdG1wZiwgcDAxaSwgc2tudCkgJT4lDQogIHJlcGxhY2UoaXMubmEoLiksIDApICU+JQ0KICAgIG11dGF0ZShpbnRlcnZhbDYwID0geW1kX2goc3Vic3RyKHZhbGlkLDEsMTMpKSkgJT4lDQogICAgbXV0YXRlKHdlZWsgPSB3ZWVrKGludGVydmFsNjApLA0KICAgICAgICAgICAjZG90dyA9IHdkYXkoaW50ZXJ2YWw2MCwgbGFiZWw9VFJVRSksDQogICAgICAgICAgIGRhdGUgPSBkYXRlKGludGVydmFsNjApKSAlPiUNCiAgICBncm91cF9ieShkYXRlKSAlPiUNCiAgICBkcGx5cjo6c3VtbWFyaXplKFRlbXBlcmF0dXJlID0gbWF4KHRtcGYpLA0KICAgICAgICAgICAgICBQcmVjaXBpdGF0aW9uID0gc3VtKHAwMWkpLA0KICAgICAgICAgICAgICBXaW5kX1NwZWVkID0gbWF4KHNrbnQpKSAlPiUNCiAgICBtdXRhdGUoVGVtcGVyYXR1cmUgPSBpZmVsc2UoVGVtcGVyYXR1cmUgPT0gMCwgNDIsIFRlbXBlcmF0dXJlKSkNCg0KcmFpbiA9IGdncGxvdChvYWsud2VhdGhlci5kYWlseSwgYWVzKGRhdGUsUHJlY2lwaXRhdGlvbikpICsgDQogIGdlb21fbGluZShjb2xvdXIgPSAiYmx1ZSIpICsgDQogIGxhYnModGl0bGU9IlBlcmNpcGl0YXRpb24iLCB4PSJEYXkiLCB5PSJQZXJlY2lwaXRhdGlvbiIpICsgcGxvdFRoZW1lKCkNCg0Kd2luZCA9IGdncGxvdChvYWsud2VhdGhlci5kYWlseSwgYWVzKGRhdGUsV2luZF9TcGVlZCkpICsgDQogIGdlb21fbGluZShjb2xvdXIgPSAiZ29sZCIpICsgDQogICAgbGFicyh0aXRsZT0iV2luZCBTcGVlZCIsIHg9IkRheSIsIHk9IldpbmQgU3BlZWQiKSArIHBsb3RUaGVtZSgpDQoNCnRlbXAgPSBnZ3Bsb3Qob2FrLndlYXRoZXIuZGFpbHksIGFlcyhkYXRlLFRlbXBlcmF0dXJlKSkgKyANCiAgZ2VvbV9saW5lKGNvbG91ciA9ICJyZWQiKSArIA0KICAgIGxhYnModGl0bGU9IlRlbXBlcmF0dXJlIiwgeD0iRGF5IiwgeT0iVGVtcGVyYXR1cmUiKSArIHBsb3RUaGVtZSgpDQoNCnAgPSBsaXN0KA0KICB3ZWVrID0gbWFwX3BhcmsoJ3dlZWsnLCAnZ3JlZW4nLCBzaXplPTEsDQogICAgICAgICAgICAgICAgeWxhYiA9ICJPY2N1cGllZCAlIikgKyByZW1vdmVfeCwNCiAgcmFpbiArIHJlbW92ZV94LA0KICB3aW5kICsgcmVtb3ZlX3gsDQogIHRlbXANCikNCndyYXBfcGxvdHMocCwgbnJvdyA9IDQpICsgcGxvdF9sYXlvdXQoZ3VpZGVzID0gImNvbGxlY3QiKSArIA0KICBwbG90X2Fubm90YXRpb24odGl0bGUgPSAiV2VhdGhlciBEYXRhIC0gT2FrbGFuZCBPQUsgLSBEYWlseSAyMDE5IikgKyBwbG90VGhlbWUoKQ0KDQpybShvYWsud2VhdGhlci5kYWlseSkNCmBgYA0KDQojIyMgQ29ycmVsYXRpb24gUGxvdA0KDQpUaGUgY29ycmVsYXRpb24gcGxvdCBoaWdobGlnaHRzIHRoZSB2YXJpYWJsZXMgdGhhdCBoYXZlIGEgaGlnaGVyIGxpbmVhciByZWxhdGlvbnNoaXAgdG8gdGhlIGRlcGVuZGVudCB2YXJpYWJsZSAoJ3QucGN0Lm9jYycgaXMganVzdCBwZXJjZW50IG9jY3VwYXRpb24pIGFuZCBlYWNoIG90aGVyLiAgDQoNCmBgYCB7ciBjb3JyX3Bsb3QsIGZpZy5oZWlnaHQ9OH0NCg0KdmFycyA9IGMoICAidC5wY3Qub2NjIiwgJ2F2Zy5ibGsuZGF5JywgJ2F2Zy5ibGsud2Vla2RheScsICdhdmcuZGlzdGJpbi5kYXknLCAnYXZnLmRpc3RiaW4ud2Vla2RheScsICdhdmcuYmxrYmluLndlZWtkYXknLCAnaG91c2Vob2xkc19jYXJfcGN0JywgJ01lZF9JbmMnLCAnUGVyY2VudF9XaGl0ZScsICdsYWcuYmxrYmluLjFiaW4nLCAnbGFnLmJsa2Jpbi4xZGF5JywgJ2xhZy5ibGtiaW4ud2VlaycpDQoNCiMgYygidC5wY3Qub2NjIiwiYXZnLmJsay5kYXkiLCAiYXZnLmJsay53ZWVrZGF5IiwgImF2Zy5ibGsubW9udGgiLCANCiAgICAgIyAgICJhdmcuYmxrYmluLndlZWtkYXkiLCAiYXZnLmJsa2Jpbi5tb250aCIsICJhdmcuZGlzdGJpbi5kYXkiLA0KICAgICAjICAgImF2Zy5kaXN0YmluLndlZWtkYXkiLCAiYXZnLmRpc3RiaW4ubW9udGgiLCAibGFnLmJsa2Jpbi4xYmluIiwNCiAgICAgIyAgICJsYWcuYmxrYmluLjFkYXkiLCJsYWcuYmxrYmluLndlZWsiLCAnaG91c2Vob2xkc19jYXJfcGN0JywgJ01lZF9JbmMnLCAnUGVyY2VudF9XaGl0ZScpDQoNCnBhcmsuY29yciA9IA0KICBwYXJrLmltcG9ydCAlPiUNCiAgZHBseXI6OnNlbGVjdCh2YXJzKSAlPiUgDQogIG5hLm9taXQoLikNCg0KY29ycm1ldGhvZCA9ICdwZWFyc29uJw0KDQpnZ2NvcnJwbG90OjpnZ2NvcnJwbG90KA0KICBjb3IocGFyay5jb3JyLCBtZXRob2Q9Y29ycm1ldGhvZCkgJT4lDQogICAgcm91bmQoLiwgMiksICMgJT4lIHN1YigiXjArIiwgIiIsIC4pLA0KICAgIGNvbG9ycyA9IGMoIiM2RDlFQzEiLCAid2hpdGUiLCAiI0U0NjcyNiIpLCANCiAgICB0eXBlPSJsb3dlciIsIGdndGhlbWUgPSBwbG90VGhlbWUsbGFiX3NpemU9My4yNSwNCiAgICBsYWI9VFJVRQ0KICApICsNCiAgbGFicygNCiAgICAgIHRpdGxlID0gIlBlYXJzb24gQ29ycmVsYXRpb24iLA0KICAgICAgc3VidGl0bGUgPSAiU0YgUGFyayBWYXJpYWJsZXMiDQogICAgICApDQpybShwYXJrLmNvcnIpDQoNCmBgYA0KVGhlIGxhcmdlc3QgdGFrZWF3YXlzIGFyZSB0aGF0IHRoZSBhdmVyYWdlcyBieSBibG9jayBhbmQgZWl0aGVyIGRheSBvciB3ZWVrZGF5IGFyZSB0aGUgc3Ryb25nZXN0LiBIb3dldmVyLCB0aGVyZSBhcmUgaW50ZXJkZXBlbmRlbmNlIGlzc3VlcyB3aXRoIGEgZmV3IG9mIHRoZSB2YXJpYWJsZXMuIEFzIGEgcmVzdWx0LCBJIGFtIHRha2luZyBvdXQgImF2Zy5ibGtiaW4ubW9udGgiLCAiYXZnLmJsay5tb250aCIsICBhbmQgImF2Zy5kaXN0YmluLm1vbnRoIi4gDQoNCiMgIE1vZGVsDQoqRGVzY3JpYmUgeW91ciBtb2RlbGluZyBhcHByb2FjaCBhbmQgc2hvdyBob3cgeW91IGFycml2ZWQgYXQgeW91ciBmaW5hbA0KbW9kZWwuKg0KDQpUbyAqKnBhcnRpdGlvbioqIHRoZSBkYXRhc2V0LCB3ZSBzcGxpdCB0aGUgbW9kZWwgYnkgNzUlIGZvciB0cmFpbmluZyBhbmQgMjUlIGZvciB0ZXN0aW5nIHB1cnBvc2VzLiBUaGUgcGFydGl0aW9uIGlzIGF0IHRoZSBiZWdpbm5pbmcgb2YgUXVhcnRlciAyIChBcHJpbCAxc3QpLiANCg0KDQpgYGAge3IgcGFydGl0aW9ufQ0KDQpwYXRoID0gIkM6L1VzZXJzL25lbG1zL0RvY3VtZW50cy9QZW5uL01VU0EtNTA4L01VU0E1MDhfRklOQUxfU0ZQQVJLL2RhdGEvc2ZwYXJrX2NsZWFuX2RhdGEucGFycXVldCINCiMgd3JpdGVfcGFycXVldCgNCiMgICBwYXJrLmltcG9ydCwNCiMgICBwYXRoDQojICkNCg0KcGFyay5pbXBvcnQgPSByZWFkX3BhcnF1ZXQocGF0aCkNCg0KdmFycyA9IGMoIA0KICAidC5ocnMub2NjIiwgInQuaHJzLnRvdCIsICJ0LnBjdC5vY2MiLCAiYmluIiwNCiAgImF2Zy5ibGsuZGF5IiwgDQogICJhdmcuYmxrLndlZWtkYXkiLCAjImF2Zy5ibGsubW9udGgiLCANCiAgImF2Zy5ibGtiaW4ud2Vla2RheSIsIA0KICAjImF2Zy5ibGtiaW4ubW9udGgiLCANCiAgImF2Zy5kaXN0YmluLmRheSIsICJhdmcuZGlzdGJpbi53ZWVrZGF5IiwgIyJhdmcuZGlzdGJpbi5tb250aCIsIA0KICAibGFnLmJsa2Jpbi4xYmluIiwgImxhZy5ibGtiaW4uMWRheSIsICJsYWcuYmxrYmluLndlZWsiLCANCiAgInF1YWQiLCAiaG91c2Vob2xkc19jYXJfcGN0IiwgIk1lZF9JbmMiLCAiUGVyY2VudF9XaGl0ZSINCiAgKQ0KDQp2YXJzID0gYygNCiAgIm9jYzUwIiwgJ2lkLmJsb2NrJywgJ3F1YWQnLCAnYmluJywgJ2RheS53ZWVrJywgJ2F2Zy5ibGsuZGF5JywgJ2F2Zy5ibGsud2Vla2RheScsICdhdmcuZGlzdGJpbi5kYXknLCAnYXZnLmRpc3RiaW4ud2Vla2RheScsICdhdmcuYmxrYmluLndlZWtkYXknLCAnaG91c2Vob2xkc19jYXJfcGN0JywgJ01lZF9JbmMnLCAnUGVyY2VudF9XaGl0ZScsICdsYWcuYmxrYmluLjFiaW4nLCAnbGFnLmJsa2Jpbi4xZGF5JywgJ2xhZy5ibGtiaW4ud2VlaycsICdpZC5kaXN0Jw0KICAgICAgICAgKQ0KDQpwYXJ0aXRpb25fZGF5ID0geW1kKCcyMDE5LTA0LTAxJykNCnBhcmsuaW1wb3J0JGRhdGFzZXQgPSAnVHJhaW4nDQpwYXJrLmltcG9ydFtwYXJrLmltcG9ydCRkYXRlIDwgcGFydGl0aW9uX2RheSwgJ2RhdGFzZXQnXSA9ICdUZXN0Jw0KDQpwYXJrLnRyYWluID0gcGFyay5pbXBvcnQgJT4lIGZpbHRlcihkYXRhc2V0ID09ICdUcmFpbicpICU+JSBkcGx5cjo6c2VsZWN0KHZhcnMpDQpwYXJrLnRlc3QgPSBwYXJrLmltcG9ydCAlPiUgZmlsdGVyKGRhdGFzZXQgPT0gJ1Rlc3QnKSAlPiUgZHBseXI6OnNlbGVjdCh2YXJzKQ0KDQoNCmBgYA0KDQpTdXJwcmlzaW5nbHksIGJhc2VkIG9uIHRoZSB0YWJsZSBiZWxvdywgdGhlIFRlc3RpbmcgZGF0YXNldCBvZiBRMi1RNCBpcyBleGFjdGx5IDc1JSBvZiB0aGUgZGF0YSBhbmQgdGhlIG9jY3VwYXRpb24gcmF0ZSBhcHBlYXJzIHRvIG5vdCBiZSB0b28gZGlmZmVyZW50LiAgDQoNCmBgYCB7ciBjb21wX3RhYiwgcmVzdWx0cz0nYXNpcyd9DQoNCg0KdGFiID0gDQogIGRlc2NyaWJlQnkoDQogICAgcGFyay5pbXBvcnQgICU+JSANCiAgICAgIGRwbHlyOjpzZWxlY3QodC5wY3Qub2NjLCBkYXRhc2V0KSwNCiAgICBncm91cD0nZGF0YXNldCcsIG1hdCA9IFRSVUUsDQogICAgbmEucm0gPSBUUlVFDQogICkgJT4lDQogIGFzLmRhdGEuZnJhbWUoKSAlPiUNCiAgI2ZpbHRlcihpdGVtPjMpICU+JQ0KICBhcnJhbmdlKGRlc2MoaXRlbSkpICU+JQ0KICBkcGx5cjo6c2VsZWN0KC1pdGVtLCAtdmFycykgJT4lDQogIHRhaWwoLiwgLTIpICMlPiUNCiAgIyBhcnJhbmdlKGZjdF9yZWxldmVsKGdyb3VwMSwgYygnOWEgdG8gMTJwJywnMTJwIHRvIDNwJywnM3AgdG8gNnAnKSkpDQoNCnJvd25hbWVzKHRhYikgPSBOVUxMDQoNCnRhYiAlPiUNCiAgdHJhbnNtdXRlKA0KICAgIGBEYXRhc2V0YCA9IGdyb3VwMSwNCiAgICBgVGltZSBDb3ZlcmVkYCA9IGMoJ0FwciAtIERlYycsJ0phbiAtIE1hcicpLA0KICAgIENvdW50ID0gYyhucm93KHBhcmsudHJhaW4pLCBucm93KHBhcmsudGVzdCkpICU+JSBmb3JtYXQoLiwgYmlnLm1hcms9JywnKSwNCiAgICBgJSBvZiBBbGxgID0gcGVyY2VudF9mb3JtYXR0ZXIoYyhucm93KHBhcmsudHJhaW4pLyBucm93KHBhcmsuaW1wb3J0KSwgbnJvdyhwYXJrLnRlc3QpLyBucm93KHBhcmsuaW1wb3J0KSkgLCBuPTApLA0KICAgIE1lYW4gPSBtZWFuICU+JSBwZXJjZW50X2Zvcm1hdHRlciguLCBuPTEpLA0KICAgIE1lZGlhbiA9IG1lZGlhbiAgJT4lIHBlcmNlbnRfZm9ybWF0dGVyKC4sIG49MSksDQogICAgU0QgPSBzZCAlPiUgcGVyY2VudF9mb3JtYXR0ZXIoLiksDQogICAgTWluID0gbWluICU+JSBwZXJjZW50X2Zvcm1hdHRlciguKSwNCiAgICBNYXggPSBtYXggJT4lIHBlcmNlbnRfZm9ybWF0dGVyKC4pDQogICkgJT4lIA0KICBrYWJsZSh0aXRsZSA9ICdQYXJraW5nIE1ldGVyIE9jY3VwYXRpb24gUmF0ZScpICU+JQ0KICBrYWJsZV9zdHlsaW5nKCkNCg0Kcm0ocGFyay5pbXBvcnQpDQoNCmBgYA0KDQojIyMgTW9kZWwgMSAtLSBBbGwgQmxvY2tzDQoNCkZvciB0aGUgZmlyc3QgbW9kZWwsIHdlIGFyZSBvbmx5IGxvb2tpbmcgYXQgNCBiYXNpYyBwcmVkaWN0b3IgdmFyaWFibGVzIGZvciB0aGUgZW50aXJlIGNpdHkuIFRoaXMgaXMgbGFyZ2VseSBkdWUgdG8gdGhlIGRhdGFzZXQgc2l6ZS4gQnV0IGl0IHdpbGwgYWxzbyBnaXZlIGFuIG9wcG9ydHVuaXR5IHRvIGZvY3VzLiANCg0KYGBge3IgbG1fYWxsfQ0KIyAjIFRJTUUNCg0KbG0uYWxsLnRpbWVfbG9jYXRpb24gPQ0KICBnbG0oDQogICAgb2NjNTAgfiBiaW4gKyBkYXkud2VlayArIHF1YWQgKyBhdmcuYmxrYmluLndlZWtkYXksDQogICAgZGF0YT1wYXJrLnRyYWluLA0KICAgIGZhbWlseT0iYmlub21pYWwiIChsaW5rPSJsb2dpdCIpDQogICAgKQ0KZ2MoKQ0KDQpgYGANCk9uZSBvZiB0aGUgbWFpbiByZWFzb25zIG91ciB0ZWFtIHVzZXMgYSBiaW5vbWlhbCBsb2dpc3RpYyByZWdyZXNzaW9uIGlzIHRvIGV2YWx1YXRlIHRoZSBvZGRzIG9mIGEgaGlnaGx5IGRlbWFuZGVkIHBhcmtpbmcgYmxvY2suIFNvIHRoaXMgbGluZWFyIG1vZGVsIHN1bW1hcnkgaW5jbHVkZXMgdGhlIG9kZHMgcmF0aW8sIHdoaWNoIHN1Z2dlc3RzIHRoZSBmb2xsb3dpbmcgb3B0aW9uczoNCg0KMS4gT1IgPCAxIC0gc3VnZ2VzdHMgdGhhdCB0aGUgdmFyaWFibGUgaXMgYXNzb2NpYXRlIHdpdGggbG93ZXIgb2RkcyBvZiBoaWdoIGRlbWFuZCBwYXJraW5nICgrNTAlKQ0KDQoyLiBPUiA+IDEgLSBzdWdnZXN0cyB0aGF0IHRoZSB2YXJpYWJsZSBpcyBhc3NvY2lhdGUgd2l0aCBoaWdoZXIgb2RkcyBvZiBoaWdoIGRlbWFuZCBwYXJraW5nICgrNTAlKQ0KDQozLiBPUiA9IDEgLSBzdWdnZXN0cyB0aGF0IHRoZSB2YXJpYWJsZSBkb2Vzbid0IGFmZmVjdCB0aGUgb2Rkcw0KDQoNCmBgYCB7ciBzdW1tX3RhYmxlLCByZXN1bHRzPSdhc2lzJ30NCg0KZHYgPSAnb2NjNTAnDQoNCmNvbF9tYWUgPSBmdW5jdGlvbihjb2x1bW4pIGNvbHVtbiAlPiUgYWJzKC4pICU+JSBtZWFuKC4sIG5hLnJtPVQpDQpjb2xfbWFwZSA9IGZ1bmN0aW9uKGNvbHVtbi5yZXMsIGNvbHVtbi5hY3R1YWwpIGFicyhjb2x1bW4ucmVzL2NvbHVtbi5hY3R1YWwpICU+JSBtZWFuKC4sIG5hLnJtPVQpDQpsaWJyYXJ5KGdlbmVyaWNzKQ0KI2F1Z19sbSA9IGF1Z21lbnQobG0uYWxsLnRpbWVfbG9jYXRpb24pDQoNCnBhcmsudHJhaW4gPQ0KICBwYXJrLnRyYWluICU+JQ0KICBuYS5vbWl0KCkgJT4lDQogIG11dGF0ZSgNCiAgICBvY2M1MC5wcmVkaWN0ICAgPSBwcmVkaWN0KGxtLmFsbC50aW1lX2xvY2F0aW9uLCAuKSkNCg0KQ2F0ZWdvcnkgPSBjKA0KICAnJywNCiAgJ1RpbWUgUGVyaW9kJywNCiAgJ1RpbWUgUGVyaW9kJywNCiAgJ0RheSBvZlxudGhlIFdlZWsnLA0KICAnRGF5IG9mXG50aGUgV2VlaycsDQogICdEYXkgb2ZcbnRoZSBXZWVrJywNCiAgJ0RheSBvZlxudGhlIFdlZWsnLA0KICAnQ2l0eSBRdWFkcmFudCcsDQogICdDaXR5IFF1YWRyYW50JywNCiAgJ0NpdHkgUXVhZHJhbnQnLA0KICAnQXZlcmFnZVxuQmxvY2sgJiBCaW4nDQopDQoNCnBhdGggPSAiQzovVXNlcnMvbmVsbXMvRG9jdW1lbnRzL1Blbm4vTVVTQS01MDgvTVVTQTUwOF9GSU5BTF9TRlBBUksvZGF0YS9wYXJrX2FsbF9zdW1tYXJ5LnBhcnF1ZXQiDQpwYXJrLmFsbC5zdW1tLkNJID0gcmVhZF9hcnJvdyhwYXRoKSAlPiUNCiAgY2JpbmQoDQogICAgQ2F0ZWdvcnkgPSBDYXRlZ29yeSwNCiAgICAuDQogICkNCg0KIyBwYXJrLmFsbC5zdW1tID0NCiMgICBsbS5hbGwudGltZV9sb2NhdGlvbiAlPiUNCiMgICB0aWR5KCkgJT4lDQojICAgZmlsdGVyKCFncmVwbCgiaWQuZGlzdCIsdGVybSkpICU+JQ0KIyAgIG11dGF0ZSgNCiMgICAgIFZhcmlhYmxlID0gdGVybSwNCiMgICAgIEVzdGltYXRlID0gcm91bmRfdGhyZXNoKGVzdGltYXRlLCB0aHJlc2ggPSAuMDAwMSwgZGlnaXRzPTQpLA0KIyAgICAgc3RkLmVycm9yID0gcm91bmRfdGhyZXNoKHN0ZC5lcnJvciwgdGhyZXNoID0gLjAwMDEsIGRpZ2l0cz00KSwNCiMgICAgIHQudmFsdWUgPSByb3VuZF9zdHIoc3RhdGlzdGljLCAyKSwNCiMgICAgIHAudmFsdWUgPSBwLnZhbHVlICU+JSByb3VuZF90aHJlc2goKQ0KIyAgICkgJT4lDQojICAgZHBseXI6OnNlbGVjdChWYXJpYWJsZSwgRXN0aW1hdGUsIHN0ZC5lcnJvciwgdC52YWx1ZSwgcC52YWx1ZSkNCiMgDQojIHBhcmsuYWxsLkNJID0NCiMgICBleHAoDQojICAgICBjYmluZCgNCiMgICAgICAgT1IgPSBjb2VmKGxtLmFsbC50aW1lX2xvY2F0aW9uKSwNCiMgICAgICAgY29uZmludChsbS5hbGwudGltZV9sb2NhdGlvbikNCiMgICAgICAgKQ0KIyAgICAgKSAlPiUNCiMgICByb3VuZCgzKSAlPiUNCiMgICBhcy5kYXRhLmZyYW1lKCkNCiMgcGFyay5hbGwuQ0kkVmFyaWFibGUgPSByb3duYW1lcyhwYXJrLmFsbC5DSSkNCiMgDQojIHBhcmsuYWxsLnN1bW0uQ0kgPQ0KIyAgIHBhcmsuYWxsLnN1bW0gJT4lDQojICAgbWVyZ2UoDQojICAgICAuLA0KIyAgICAgcGFyay5hbGwuQ0ksDQojICAgICBvbj0nVmFyaWFibGUnLA0KIyAgICAgc29ydCA9IEZBTFNFDQojICAgKQ0KIyANCiMgcGF0aCA9ICJDOi9Vc2Vycy9uZWxtcy9Eb2N1bWVudHMvUGVubi9NVVNBLTUwOC9NVVNBNTA4X0ZJTkFMX1NGUEFSSy9kYXRhL3BhcmtfYWxsX3N1bW1hcnkucGFycXVldCINCiMgcGFyay5hbGwuc3VtbS5DSSAlPiUgd3JpdGVfYXJyb3cocGF0aCkNCg0KIyBybShwYXJrLmFsbC5DSSkNCiMgcm0ocGFyay5hbGwuc3VtbSkNCg0KDQoNCmNvbHMgPSBjb2xuYW1lcyhwYXJrLmFsbC5zdW1tLkNJKQ0KI3BhcmsuYWxsLnN1bW0uQ0kgPSANCiAgcGFyay5hbGwuc3VtbS5DSSAlPiUNCiAgbXV0YXRlKA0KICAgIEVzdGltYXRlID0gRXN0aW1hdGUgJT4lIGFzLm51bWVyaWMoKSAlPiUgcm91bmQoMiksDQogICAgc3RkLmVycm9yID0gc3RkLmVycm9yICU+JSBhcy5udW1lcmljKCkgJT4lIHJvdW5kKDIpLA0KICAgIE9SID0gT1IgJT4lIA0KICAgICAgcm91bmRfdGhyZXNoKC4sIGludF9jaGVjayA9IEYsIGNvbW1hcyA9IFQpLA0KICAgIGAyLjUgJWA9YDIuNSAlYCAlPiUgDQogICAgICByb3VuZF90aHJlc2goLiwgaW50X2NoZWNrID0gRiwgY29tbWFzID0gVCksDQogICAgYDk3LjUgJWA9YDk3LjUgJWAgJT4lIA0KICAgICAgcm91bmRfdGhyZXNoKC4sIGludF9jaGVjayA9IEYsIGNvbW1hcyA9IFQpDQogICkgICU+JQ0KICBmbGV4dGFibGUoLikgJT4lDQogIHRoZW1lX3ZhbmlsbGEoLikgJT4lDQogIGFsaWduKC4sIA0KICAgICAgICBhbGlnbiA9ICJjZW50ZXIiLCANCiAgICAgICAgcGFydCA9ICJoZWFkZXIiKSAlPiUNCiAgYWxpZ24oLiwgaj0zOmxlbmd0aChjb2xzKSwgDQogICAgICAgIGFsaWduID0gInJpZ2h0IiwgDQogICAgICAgIHBhcnQgPSAiYm9keSIpICU+JQ0KICBzZXRfdGFibGVfcHJvcGVydGllcygNCiAgICAuLCBsYXlvdXQ9J2F1dG9maXQnKSAlPiUNCiAgYWRkX2Zvb3Rlcl9yb3coDQogICAgLiwgDQogICAgdmFsdWVzPWMoIjIuNSUgJiA5Ny41JSBDb25maWRlbmNlIEludGVydmFscyIpLCANCiAgICBjb2x3aWR0aHMgPSBjKGxlbmd0aChjb2xzKSkpICU+JQ0KICBhZGRfZm9vdGVyX3JvdygNCiAgICAuLCANCiAgICB2YWx1ZXM9YygiT1IgPSBPZGRzIFJhdGlvIiksIA0KICAgIGNvbHdpZHRocyA9IGMobGVuZ3RoKGNvbHMpKSkgJT4lDQogIG1lcmdlX3YoaiA9IH5DYXRlZ29yeSkNCg0KDQpgYGANCg0KU08gdGhpcyBzdW1tYXJ5IHRhYmxlIHRlbGxzIHVzIHRoYXQgdGhlIGNpdHkgcXVhZHJhbnRzIHRlbGwgdXMgdGhlIG1vc3QgYWJvdXQgd2hldGhlciBwYXJraW5nIGlzIG5vdCBpbiBoaWdoIGRlbWFuZCAob2NjdXBhdGlvbiA8IDUwJSkuIFRoZSBEYXlzIG9mIHRoZSBXZWVrIGFuZCB0aW1lIHBlcmlvZHMgb2RkbHkgYXJlIHZlcnkgY2xvc2UgdG8gMSAtLSBzdWdnZXN0aW5nIHRoYXQgdGhleSBhbG9uZSBjYW5ub3QgcHJlZGljdCBhbnkgY2hhbmdlcy4gVGhlIGV4dHJlbWVseSBoaWdoIE9SIG9mIHRoZSBBdmVyYWdlIFdlZWtlbmQgT2NjdXBhbmN5IGlzIHBvdGVudCBiZWNhdXNlIGl0IGlzIGRpcmVjdGx5IHByb3ZpZGluZyBvY2N1cGF0aW9uIG51bWJlcnMgLS0gd2hlbiB0aGUgb3RoZXJzIGdlbmVyYWxpemVkIHRoZSByZWdpb24gb3IgdGltZWZyYW1lLg0KDQpUaGUgdmFyaWFibGUncyB3ZWFrbmVzcyBzdWdnZXN0cyB0aGF0IHRoaXMgbW9kZWwgaXMgdG9vIGdlbmVyYWxpemVkIGFuZCBuZWVkcyBtb3JlIGFjY3VyYWN5Lg0KDQoNCiMjIyBNb2RlbCAyIC0tIERvd250b3duIEJsb2Nrcw0KDQpUaGUgbmV4dCBtb2RlbCB0YWtlcyBhIG1vcmUgaW4tZGVwdGggbG9vayBpbnRvIHRoZSBTYW4gRnJhbmNpc2NvIERvd250b3duIChRdWFkcmFudCA0KS4gVGhlIHJlYXNvbiB0byByZWZvY3VzIGlzIGxhcmdlbHkgZHVlIHRvIHRoZSBzaXplIG9mIHRoZSBkYXRhIHdpdGggYWRkZWQgdmFyaWFibGVzLg0KDQpXaXRoIGEgc21hbGxlciBmb2N1cyBhcmVhLCB0aGUgbW9kZWwgY2FuIG5vdyB1c2UgbW9yZSB2YXJpYWJsZXMuIFNwZWNpZmljYWxseSB0aGUgbGFnLCBjZW5zdXMsIGRpc3RyaWN0IGlkcywgYW5kIGF2ZXJhZ2VzLiBUaGlzIHdpbGwgcHJvdmlkZSBhIG1vcmUgYWNjdXJhdGUgcHJlZGljdGlvbi4gDQoNCmBgYCB7ciBsbV9kd3RufQ0KDQpkd3RuX3ZhcnMgPSBjKA0KICAib2NjNTAiLCAnaWQuZGlzdCcsICMnaWQuYmxvY2snLCANCiAgJ2JpbicsICdkYXkud2VlaycsIA0KICAnYXZnLmJsay5kYXknLCAnYXZnLmJsay53ZWVrZGF5JywgJ2F2Zy5kaXN0YmluLmRheScsICdhdmcuZGlzdGJpbi53ZWVrZGF5JywgJ2F2Zy5ibGtiaW4ud2Vla2RheScsIA0KICAnaG91c2Vob2xkc19jYXJfcGN0JywgJ01lZF9JbmMnLCAnUGVyY2VudF9XaGl0ZScsIA0KICAnbGFnLmJsa2Jpbi4xYmluJywgJ2xhZy5ibGtiaW4uMWRheScsICdsYWcuYmxrYmluLndlZWsnDQogICAgICAgICApDQoNCnBhcmsudHJhaW4uZHd0biA9IHBhcmsudHJhaW4gJT4lIA0KICAgICAgZmlsdGVyKHF1YWQgPT0gJ05FJykgJT4lDQogICAgICBkcGx5cjo6c2VsZWN0KGR3dG5fdmFycykNCg0KbG0uZHd0bi5hbGwgPQ0KICBnbG0oDQogICAgb2NjNTAgfiAuLA0KICAgIGRhdGE9cGFyay50cmFpbi5kd3RuLA0KICAgIGZhbWlseT0iYmlub21pYWwiIChsaW5rPSJsb2dpdCIpDQogICAgKQ0KDQpgYGANCg0KYGBgIHtyIGxtX19kLCByZXN1bHRzPSdhc2lzJ30NCg0KcGFyay50cmFpbi5kd3RuID0NCiAgcGFyay50cmFpbiAlPiUNCiAgZmlsdGVyKHF1YWQgPT0gJ05FJykgJT4lDQogIG5hLm9taXQoKSAlPiUNCiAgbXV0YXRlKA0KICAgIG9jYzUwLnByZWRpY3QgICA9IHByZWRpY3QobG0uZHd0bi5hbGwsIC4pKQ0KDQojIGxtLmR3dG4uYWxsICU+JQ0KIyAgIHRpZHkoKSAlPiUNCiMgICB0cmFuc211dGUoDQojICAgICBWYXJpYWJsZSA9IHRlcm0sIA0KIyAgICAgRXN0aW1hdGUgPSBwZXJjZW50X2Zvcm1hdHRlcihlc3RpbWF0ZSwgbiA9IDEpLA0KIyAgICAgc3RkLmVycm9yID0gcGVyY2VudF9mb3JtYXR0ZXIoc3RkLmVycm9yLCBuPTEpICwNCiMgICAgIHQudmFsdWUgPSBmb3JtYXRfbnVtcyhzdGF0aXN0aWMpLA0KIyAgICAgcC52YWx1ZSA9IHAudmFsdWUgJT4lIHJvdW5kX3RocmVzaCgpDQojICAgKSAlPiUNCiMgICBrYWJsZSgNCiMgICAgIGxhYmVsID0gTkEsDQojICAgICBjYXB0aW9uID0gZ2x1ZSgnVHJhaW5pbmcgTW9kZWwgU3VtbWFyeScpLA0KIyAgICAgYWxpZ24gPSAnbHJycnInKSAlPiUNCiMgICBrYWJsZV9zdHlsaW5nKCkNCg0KZ2MoKQ0KIyBwYXRoID0gIkM6L1VzZXJzL25lbG1zL0RvY3VtZW50cy9QZW5uL01VU0EtNTA4L01VU0E1MDhfRklOQUxfU0ZQQVJLL2RhdGEvbG1fZHd0bl9pZC5wYXJxdWV0Ig0KIyBsbS5kd3RuLmFsbCAlPiUNCiMgICB0aWR5KCkgJT4lIA0KIyAgIHdyaXRlX2Fycm93KC4sIHBhdGgpDQoNCkNhdGVnb3J5ID0gYygNCiAgJycsDQogICdUaW1lIFBlcmlvZCcsDQogICdUaW1lIFBlcmlvZCcsDQogICdEYXkgb2ZcbnRoZSBXZWVrJywNCiAgJ0RheSBvZlxudGhlIFdlZWsnLA0KICAnRGF5IG9mXG50aGUgV2VlaycsDQogICdEYXkgb2ZcbnRoZSBXZWVrJywNCiAgJ0F2ZXJhZ2VcbkJsb2NrJywNCiAgJ0F2ZXJhZ2VcbkJsb2NrJywNCiAgJ0F2ZXJhZ2VcbkRpc3RyaWN0ICYgQmluJywNCiAgJ0F2ZXJhZ2VcbkRpc3RyaWN0ICYgQmluJywNCiAgJ0F2ZXJhZ2VcbkJsb2NrICYgQmluJywNCiAgJ0NlbnN1cycsDQogICdDZW5zdXMnLA0KICAnQ2Vuc3VzJywNCiAgJ1RpbWUgTGFnJywNCiAgJ1RpbWUgTGFnJywNCiAgJ1RpbWUgTGFnJw0KKQ0KDQpwYXRoID0gIkM6L1VzZXJzL25lbG1zL0RvY3VtZW50cy9QZW5uL01VU0EtNTA4L01VU0E1MDhfRklOQUxfU0ZQQVJLL2RhdGEvcGFya19kd3RuX3N1bW1hcnkucGFycXVldCINCnBhcmsuZHd0bi5zdW1tLkNJID0gcmVhZF9hcnJvdyhwYXRoKSAlPiUNCiAgbXV0YXRlKA0KICAgIE9SID0gT1IgJT4lIA0KICAgICAgcm91bmRfdGhyZXNoKC4sIGludF9jaGVjayA9IEYsIGNvbW1hcyA9IFQpLA0KICAgIGAyLjUgJWA9YDIuNSAlYCAlPiUgDQogICAgICByb3VuZF90aHJlc2goLiwgaW50X2NoZWNrID0gRiwgY29tbWFzID0gVCksDQogICAgYDk3LjUgJWA9YDk3LjUgJWAgJT4lIA0KICAgICAgcm91bmRfdGhyZXNoKC4sIGludF9jaGVjayA9IEYsIGNvbW1hcyA9IFQpDQogICkgJT4lDQogIGNiaW5kKA0KICAgIENhdGVnb3J5ID0gQ2F0ZWdvcnksDQogICAgLg0KICApDQoNCiMgcGFyay5kd3RuLnN1bW0gPQ0KIyAgIGxtLmR3dG4uYWxsICU+JQ0KIyAgIHRpZHkoKSAlPiUNCiMgICBmaWx0ZXIoIWdyZXBsKCJpZC5kaXN0Iix0ZXJtKSkgJT4lDQojICAgbXV0YXRlKA0KIyAgICAgVmFyaWFibGUgPSB0ZXJtLA0KIyAgICAgRXN0aW1hdGUgPSByb3VuZF90aHJlc2goZXN0aW1hdGUsIHRocmVzaCA9IC4wMDAxLCBkaWdpdHM9NCksDQojICAgICBzdGQuZXJyb3IgPSByb3VuZF90aHJlc2goc3RkLmVycm9yLCB0aHJlc2ggPSAuMDAwMSwgZGlnaXRzPTQpLA0KIyAgICAgdC52YWx1ZSA9IHJvdW5kX3N0cihzdGF0aXN0aWMsIDIpLA0KIyAgICAgcC52YWx1ZSA9IHAudmFsdWUgJT4lIHJvdW5kX3RocmVzaCgpDQojICAgKSAlPiUNCiMgICBkcGx5cjo6c2VsZWN0KFZhcmlhYmxlLCBFc3RpbWF0ZSwgc3RkLmVycm9yLCB0LnZhbHVlLCBwLnZhbHVlKQ0KIyANCiMgcGFyay5kd3RuLkNJID0NCiMgICBleHAoDQojICAgICBjYmluZCgNCiMgICAgICAgT1IgPSBjb2VmKGxtLmR3dG4uYWxsKSwNCiMgICAgICAgY29uZmludChsbS5kd3RuLmFsbCkNCiMgICAgICAgKQ0KIyAgICAgKSAlPiUNCiMgICByb3VuZCgzKSAlPiUNCiMgICBhcy5kYXRhLmZyYW1lKCkNCiMgI3BhcmsuZHd0bi5DSSRWYXJpYWJsZSA9IHJvd25hbWVzKHBhcmsuZHd0bi5DSSkNCiMgDQojIHBhcmsuZHd0bi5DSSRWYXJpYWJsZSA9IHJvd25hbWVzKHBhcmsuZHd0bi5DSSkNCiMgcGFyay5kd3RuLkNJID0NCiMgICBwYXJrLmR3dG4uQ0kgJT4lDQojICAgZmlsdGVyKCFncmVwbCgiaWQuZGlzdCIsVmFyaWFibGUpKQ0KIyANCiMgDQojIHBhcmsuZHd0bi5zdW1tLkNJID0NCiMgICBtZXJnZSgNCiMgICAgIHBhcmsuZHd0bi5zdW1tLA0KIyAgICAgcGFyay5kd3RuLkNJLA0KIyAgICAgb249J1ZhcmlhYmxlJywNCiMgICAgIHNvcnQgPSBGQUxTRQ0KIyAgICkNCg0KIyAjIHBhcmsuZHd0bi5zdW1tLkNJICU+JQ0KIyAjICAgd3JpdGVfYXJyb3cocGF0aCkNCg0KY29scyA9IGNvbG5hbWVzKHBhcmsuZHd0bi5zdW1tLkNJKQ0KI3BhcmsuZHd0bi5zdW1tLmZsZXggPSANCiAgcGFyay5kd3RuLnN1bW0uQ0kgJT4lDQogIG11dGF0ZSgNCiAgICBFc3RpbWF0ZSA9IGlmZWxzZShWYXJpYWJsZT09J01lZF9JbmMnLC4wMSxFc3RpbWF0ZSAlPiUgYXMubnVtZXJpYygpICU+JSByb3VuZCgyKSksDQogICAgc3RkLmVycm9yID0gaWZlbHNlKFZhcmlhYmxlPT0nTWVkX0luYycsLjAxLHN0ZC5lcnJvciAlPiUgYXMubnVtZXJpYygpICU+JSByb3VuZCgyKSksDQogICAgdC52YWx1ZSA9IHQudmFsdWUgJT4lIGFzLm51bWVyaWMoKSAlPiUgcm91bmQoMCksDQogICkgJT4lDQogIGZsZXh0YWJsZSguKSAlPiUNCiAgdGhlbWVfdmFuaWxsYSguKSAlPiUNCiAgYWxpZ24oLiwgDQogICAgICAgIGFsaWduID0gImNlbnRlciIsIA0KICAgICAgICBwYXJ0ID0gImhlYWRlciIpICU+JQ0KICBhbGlnbiguLCBqPTM6bGVuZ3RoKGNvbHMpLCANCiAgICAgICAgYWxpZ24gPSAicmlnaHQiLCANCiAgICAgICAgcGFydCA9ICJib2R5IikgJT4lDQogIHNldF90YWJsZV9wcm9wZXJ0aWVzKA0KICAgIC4sIGxheW91dD0nYXV0b2ZpdCcpICU+JQ0KICBhZGRfZm9vdGVyX3JvdygNCiAgICAuLCANCiAgICB2YWx1ZXM9YygiMi41JSAmIDk3LjUlIENvbmZpZGVuY2UgSW50ZXJ2YWxzIiksIA0KICAgIGNvbHdpZHRocyA9IGMobGVuZ3RoKGNvbHMpKSkgJT4lDQogIGFkZF9mb290ZXJfcm93KA0KICAgIC4sIA0KICAgIHZhbHVlcz1jKCJPUiA9IE9kZHMgUmF0aW8iKSwgDQogICAgY29sd2lkdGhzID0gYyhsZW5ndGgoY29scykpKSAlPiUNCiAgbWVyZ2VfdihqID0gfkNhdGVnb3J5KQ0KDQpgYGANCg0KVGhlIHN1bW1hcnkgdGFibGUgZm9yIHRoZSBzZWNvbmQgbW9kZWwgc3VnZ2VzdHMgdGhhdCBpdCBpcyBtb3JlIGFjY3VyYXRlLiBTb21lIGF2ZXJhZ2VzIHByb3ZpZGUgaGVhdmllciBwcmVkaWN0aW9ucyAtLSBzdWNoIGFzIHRoZSBBdmVyYWdlIEJsb2NrIE9jY3VwYW5jaWVzLiBCdXQgbWFueSBzaXQganVzdCBhcm91bmQgYW4gT1Igb2YgMS4gDQoNCiMjIyBDb21wYXJlIE1vZGVscw0KDQpBIGhpZ2hlciBkZWdyZWVzIG9mIGZyZWVkb20gc3VnZ2VzdHMgYSBsYXJnZXIgbnVtYmVyIG9mIHBhcmFtZXRlcnMgdXNlZCBieSB0aGUgZml0dGluZyBwcm9jZWR1cmUuIENvbWJpbmVkIHdpdGggdGhlIGxhcmdlciBudW1iZXIgb2YgdmFyaWFibGVzIHVzZWQsIHRoZSBEb3dudG93biBNb2RlbCBpcyBtb3JlIGNvbXBsZXguDQoNCmBgYCB7ciBjb21wX3RhYmxlLCByZXN1bHRzPSdhc2lzJ30NCg0KDQpwYXJrLmFsbC5nbGFuY2UgPSANCiAgbG0uYWxsLnRpbWVfbG9jYXRpb24gJT4lIGdsYW5jZSgpDQoNCnBhcmsuZHd0bi5nbGFuY2UgPSANCiAgbG0uZHd0bi5hbGwgJT4lIGdsYW5jZSgpDQoNCg0KcGFyay5CT1RILmdsYW5jZSA9IA0KICByYmluZCgNCiAgICBwYXJrLmFsbC5nbGFuY2UsDQogICAgcGFyay5kd3RuLmdsYW5jZQ0KICApDQoNCnBhcmsuQUlDID0gQUlDKGxtLmFsbC50aW1lX2xvY2F0aW9uLCBsbS5kd3RuLmFsbCkNCg0KY29scyA9IGNvbG5hbWVzKHBhcmsuQUlDKQ0KI3BhcmsuQUlDLmZsZXggPSANCiAgcGFyay5BSUMgJT4lDQogIHRyYW5zbXV0ZSgNCiAgICBgVHJhaW5pbmdcbk1vZGVsYCA9IGMoJ0FsbCBCbG9ja3MnLCAnRG93bnRvd24gQmxvY2tzJyksDQogICAgVmFyaWFibGVzID0gYyg0LDE0KSwNCiAgICBgT2JzZXJ2YXRpb25zYCA9IGMobnJvdyhwYXJrLnRyYWluKSwgbnJvdyhwYXJrLnRyYWluLmR3dG4pKSAlPiUgZm9ybWF0KGJpZy5tYXJrPScsJyksDQogICAgQUlDID0gQUlDICU+JSByb3VuZCgwKSAlPiUgZm9ybWF0KGJpZy5tYXJrPScsJywgbnNtYWxsID0gMCwganVzdGlmeT0ncmlnaHQnKSwNCiAgICBgRGVncmVlcyBvZlxuRnJlZWRvbWAgPSBkZg0KICApICU+JQ0KICBmbGV4dGFibGUoLikgJT4lDQogIHRoZW1lX3ZhbmlsbGEoLikgJT4lDQogIGFsaWduKC4sIA0KICAgICAgICBhbGlnbiA9ICJjZW50ZXIiLCANCiAgICAgICAgcGFydCA9ICJoZWFkZXIiKSAlPiUNCiAgYWxpZ24oLiwgaj0yOjUsIA0KICAgICAgICBhbGlnbiA9ICJyaWdodCIsIA0KICAgICAgICBwYXJ0ID0gImJvZHkiKSAlPiUNCiAgc2V0X3RhYmxlX3Byb3BlcnRpZXMoDQogICAgLiwgbGF5b3V0PSdhdXRvZml0JykgJT4lDQogIGFkZF9mb290ZXJfcm93KA0KICAgIC4sIA0KICAgIHZhbHVlcz1jKCJBa2Fpa2UgSW5mb3JtYXRpb24gQ3JpdGVyaW9uIiksIA0KICAgIGNvbHdpZHRocyA9IGMobGVuZ3RoKGNvbHMpKSszKQ0KDQpgYGANCg0KIyBWYWxpZGF0aW9uIC8gR29vZG5lc3Mgb2YgRml0DQoNClRoZSBBSUMgc3RhdGlzdGljIG9ubHkgc3VnZ2VzdHMgdGhhdCB0aGUgRG93bnRvd24gbW9kZWwgaGFzIGNvbXBhcmF0aXZlbHkgbGVzcyBwcmVkaWN0aXZlIGVycm9yIHRoYW4gdGhlIG9yaWdpbmFsIEFsbCBCbG9ja3MgbW9kZWwuIFRvIGRldGVybWluZSB0aGUgdHJ1ZSBwcmVkaWN0aXZlIG5hdHVyZSBvZiBvdXIgYmlub21pYWwgbG9naXN0aWMgbW9kZWwsIHdlIHdpbGw6DQoNCjEuIERldGVybWluZSB0aGUgKipDdXQtT2ZmKiogdGhyZXNob2xkIG9mIGhvdyB0byBsYWJlbCB0aGUgb3V0Y29tZXMgb2Ygb3VyIHByZWRpY3RlZCBkZXBlbmRlbnQgdmFyaWFibGVzLA0KDQoyLiBFdmFsdWF0ZSB0aGUgKipTcGVjaWZpY2l0eSwgU2Vuc2l0aXZpdHksICYgTWlzY2xhc3NpZmljYXRpb24qKiByYXRlcywNCg0KMy4gVXNlIGEgKipST0MgQ3VydmUqKiB0byBkZXRlcm1pbmUgdGhlIHF1YWxpdHkgb2Ygb3VyIGZpdCwgdGhlbg0KDQo0LiAqKkNyb3NzLVZhbGlkYXRlKiogdGhlIG1vZGVsLg0KDQpCZWNhdXNlIHRoZSBEb3dudG93biBNb2RlbCBvdXRwZXJmb3JtZWQgdGhlIGZpcnN0IEFsbCBCbG9ja3MgbW9kZWwsIHRoZSBmb2xsb3dpbmcgZ29vZG5lc3Mgb2YgZml0IG1ldHJpY3Mgd2lsbCBwcmltYXJpbHkgZXZhbHVhdGUgdGhlIERvd250b3duIE1vZGVsDQoNCmBgYCB7ciBmdW5jdGlvbl99DQoNCmdldF9jdXRfb2ZmX3ZhbHVlcyA9IGZ1bmN0aW9uKA0KICBmb2N1c19kdiA9ICdvY2M1MCcsDQogIGZvY3VzX2RmID0gcGFyay50cmFpbi5kd3RuLA0KICBmb2N1c19nbG0gPSBsbS5kd3RuLmFsbCwNCiAgY3V0X29mZiA9IDAuMDUNCil7DQogIGZpdCA9IGZvY3VzX2dsbSRmaXR0ZWQudmFsdWVzDQogIA0KICAjYSBpcyBhIG1hdHJpeCBjb21iaW5pbmcgdGhlIHZlY3RvcnMgY29udGFpbmluZyB5IGFuZCB5LWhhdCBpbiBtYXRyaXggYTsgZmlyc3QgdmFyaWFibGUgaXMNCiAgI0RSSU5LSU5HX0QsIHdoaWNoIGlzIHk7IHNlY29uZCB2YXJpYWJsZSBpcyBmaXQsIHdoaWNoIGlzIHktaGF0DQogIGEgPSBjYmluZCgNCiAgICBmb2N1c19kZltbZm9jdXNfZHZdXSwNCiAgICBmaXQNCiAgICApDQogIA0KICAjYiBpcyBtYXRyaXggYSwganVzdCBzb3J0ZWQgYnkgdGhlIHZhcmlhYmxlIGZpdA0KICBiID0gYVtvcmRlcihhWywyXSksXQ0KICANCiAgI0NhbGN1bGF0aW5nIHZhcmlhYmxlIGMgd2hpY2ggaXMgMSBpZiB5LWhhdCAoc2Vjb25kIGNvbHVtbiBvZiBtYXRyaXggYikgaXMgZ3JlYXRlcg0KICAjdGhhbiBvciBlcXVhbCB0byAwLjA1IGFuZCAwIG90aGVyd2lzZS4NCiAgDQogICNPdGhlciBjdXQtb2ZmcyBjYW4gYmUgdXNlZCBoZXJlIQ0KICANCiAgY3V0X29mZnMgPSBjKGN1dF9vZmYpDQogIA0KICBjID0gKGJbLDJdID49IGN1dF9vZmZzWzFdKQ0KICBjX2NvbG5hbWVzID0gZ2x1ZSgiUHJvYi5BYm92ZXtzdHJfcmVtb3ZlKGN1dF9vZmZzWzFdLCAnXjArJyl9IikNCiAgDQogIGlmIChsZW5ndGgoY3V0X29mZnMpPjEpew0KICAgIGZvciAoDQogICAgICBjdXJyZW50X2N1dF9vZmYgaW4gY3V0X29mZnNbMjpsZW5ndGgoY3V0X29mZnMpXQ0KICAgICAgKXsNCiAgICAgIGMgPSBjYmluZCgNCiAgICAgICAgYywNCiAgICAgICAgKGJbLDJdID49IGN1cnJlbnRfY3V0X29mZikNCiAgICAgICAgKQ0KICAgICAgY3VycmVudF9jdXRfb2ZmID0gY3VycmVudF9jdXRfb2ZmICU+JSBzdHJfcmVtb3ZlKC4sICJeMCsiKQ0KICAgICAgY19jb2xuYW1lcyA9IA0KICAgICAgICBjKGNfY29sbmFtZXMsIGdsdWUoIlByb2IuQWJvdmV7Y3VycmVudF9jdXRfb2ZmfSIpKQ0KICAgICAgfX0NCiAgDQogICNDcmVhdGluZyBtYXRyaXggZCB3aGljaCBtZXJnZXMgbWF0cml4ZXMgYiBhbmQgYw0KICBkID0gY2JpbmQoYixjKQ0KICANCiAgI0xldCdzIGxhYmVsIHRoZSBjb2x1bW5zIG9mIG1hdHJpeCBkIGZvciBlYXNpZXIgcmVhZGluZw0KICBjb2xuYW1lcyhkKSA9IGMoDQogICAgZ2x1ZSgiT2JzZXJ2ZWQue2ZvY3VzX2R2fSIpLA0KICAgIGdsdWUoIlByb2JhYmlsaXR5Lntmb2N1c19kdn0iKSwNCiAgICBjX2NvbG5hbWVzDQogICAgKQ0KICANCiAgI0NvbnZlcnRpbmcgbWF0cml4IHRvIGRhdGEgZnJhbWUNCiAgZSA9IGFzLmRhdGEuZnJhbWUoZCkNCiAgDQogIHJldHVybihlKQ0KfQ0KDQpnZXRfc2Vuc19zcGVjX21pc3MgPSBmdW5jdGlvbigNCiAgY3V0X29mZiA9IGN1dF9vZmZfbGlzdFsxXSwNCiAgZm9jdXNfZHYgPSAnT2JzZXJ2ZWQub2NjNTAnLA0KICBmb2N1c19kZiA9IGN1dF9vZmZfdmFsdWVzDQopew0KICBzdHJfY3V0X29mZiA9IGN1dF9vZmYgJT4lIHN0cl9yZW1vdmUoLiwgIl4wKyIpDQogIGZvY3VzX2N1dF9vZmYgPSBnbHVlKCJQcm9iLkFib3Zle3N0cl9jdXRfb2ZmfSIpDQogIGZvY3VzX2Zvcm11bGEgPSBhcy5mb3JtdWxhKGdsdWUoIn4ge2ZvY3VzX2N1dF9vZmZ9ICsge2ZvY3VzX2R2fSIpKQ0KICBmb2N1c19kdl9jdXRfb2ZmID0geHRhYnMoZm9ybXVsYT1mb2N1c19mb3JtdWxhLCBkYXRhPWZvY3VzX2RmKSAlPiUgYXMubWF0cml4KCkNCiAgYSA9IGZvY3VzX2R2X2N1dF9vZmZbMV0NCiAgYiA9IGZvY3VzX2R2X2N1dF9vZmZbM10NCiAgYyA9IGZvY3VzX2R2X2N1dF9vZmZbMl0NCiAgZCA9IGZvY3VzX2R2X2N1dF9vZmZbNF0NCiAgDQogIFNlbnNpdGl2aXR5ID0gZC8oYitkKQ0KICBTcGVjaWZpY2l0eSA9IGEvKGErYykNCiAgTWlzY2xhc3NpZmljYXRpb24gPSAoYitjKS8oYStiK2MrZCkNCiAgDQogIHJldHVybihkYXRhLmZyYW1lKA0KICAgIEN1dF9PZmYgPSBjdXRfb2ZmLA0KICAgIFNlbnNpdGl2aXR5ID0gU2Vuc2l0aXZpdHksDQogICAgU3BlY2lmaWNpdHkgPSBTcGVjaWZpY2l0eSwNCiAgICBNaXNjbGFzc2lmaWNhdGlvbiA9IE1pc2NsYXNzaWZpY2F0aW9uDQogICkpDQp9DQoNCg0KYGBgDQoNCiMjIERldGVybWluZSBDdXQtT2ZmIFRocmVzaG9sZA0KDQpPdXIgdGVzdGluZyBtb2RlbCBvbmx5IGdhdmUgdXMgdGhlIHByb2JhYmlsaXR5IG9mIHByZWRpY3RpbmcgdGhhdCB0aGUgcGFya2luZyBtZXRlciBvY2N1cGF0aW9ucyBhcmUgYWJvdmUgNTAlLiBUaGF0IHByb2JhYmlsaXR5LCBuZWVkcyBhICpjdXQtb2ZmIHRocmVzaG9sZCogdGhhdCBsYWJlbHMgaXRzIGJpbm9taWFsIG91dGNvbWUuIFRoZSB0aHJlc2hvbGQgd2lsbCBuZXZlciBwZXJmZWN0bHkgZGl2aWRlIHRoZSBwcmVkaWN0aW9ucyBpbnRvIHRoZWlyIG9ic2VydmVkIG91dGNvbWVzLiANCg0KVG8gZGV0ZXJtaW5lIHRoaXMgbWlzY2xhc3NpZmljYXRpb24gcmF0ZSwgd2UgZmlyc3QgcnVuIHRoZSB0ZXN0aW5nIG1vZGVsIG9uIHRoZSB0cmFpbmluZyBkYXRhc2V0IHRvIGZpbmQgcHJlZGljdGl2ZSB2YWx1ZXMuIFdpdGggdGhvc2UgcHJlZGljdGlvbnMgb2YgTWV0ZXJzIGFib3ZlIDUwJSAqYW5kKiBvYnNlcnZhdGlvbnMsIHdlIGNhbiBmaW5kIG1pc2NsYXNzaWZpY2F0aW9uIHJhdGVzIHdpdGggaHlwb3RoZXRpY2FsIGN1dC1vZmZzLiANCg0KVGhlIHRhYmxlIGJlbG93IHNob3dzIHByb3Bvc2VkIGN1dC1vZmYgdGhyZXNob2xkcyBhbG9uZyB3aXRoIHRoZWlyIFNlbnNpdGl2aXR5IChUcnVlIFBvc2l0aXZlIFJhdGUpLCBTcGVjaWZpY2l0eSAoVHJ1ZSBOZWdhdGl2ZSBSYXRlKSwgYW5kIE1pc2NsYXNzaWZpY2F0aW9uIHJhdGUgKGNvbWJpbmVkIEZhbHNlIFBvc2l0aXZlICYgTmVnYXRpdmUgUmF0ZSkuIEluaXRpYWxseSBpdCBzZWVtcyBiZXN0IHRvIHBpY2sgdGhlIHRocmVzaG9sZCB3aXRoIHRoZSBsZWFzdCBtaXNjbGFzc2lmaWNhdGlvbiAoNTAlKSwgYnV0IGl0IHdvdWxkIGJlIGJlc3QgdG8gcGljayBhIHRocmVzaG9sZCB0aGF0IHByb2R1Y2VzIGhpZ2hlciBjb3JyZWN0IHByZWRpY3Rpb25zIC0tIGhpZ2hlc3QgcG9zc2libGUgU2Vuc2l0aXZpdHkgKmFuZCogU3BlY2lmaWNpdHkuDQoNClVzaW5nIGEgZnVuY3Rpb24gdGhhdCBmaW5kcyB3aGVyZSB0aGUgU2Vuc2l0aXZpdHkgYW5kIFNwZWNpZmljaXR5IHJhdGVzIGludGVyc2VjdCwgd2UgZmluZCB0aGF0IHRoZSBvcHRpbXVtIGN1dC1vZmYgdGhyZXNob2xkIGlzIDQwLjQlLiANCg0KYGBgIHtyIG1vZGVfY29tcCwgcmVzdWx0cz0nYXNpcyd9DQoNCiMgbG0uZHd0bi5hbGwkbGFiZWxzDQojIA0KbGlicmFyeShST0NSKQ0KDQpmb2N1c19kdiA9ICdvY2M1MCcNCmZvY3VzX2RmID0gcGFyay50cmFpbi5kd3RuDQpmb2N1c19nbG0gPSBsbS5kd3RuLmFsbA0KDQpmb2N1cy5ST0MgPSBjYmluZChmb2N1c19kZltbZm9jdXNfZHZdXSwgZm9jdXNfZ2xtJGZpdHRlZC52YWx1ZXMpICU+JQ0KICBhcy5kYXRhLmZyYW1lKCkNCmNvbG5hbWVzKGZvY3VzLlJPQykgPSBjKCJsYWJlbHMiLCJwcmVkaWN0aW9ucyIpDQoNCg0KcHJlZCA9IHByZWRpY3Rpb24oZm9jdXMuUk9DJHByZWRpY3Rpb25zLCBmb2N1cy5ST0MkbGFiZWxzKQ0KDQpST0MucGVyZiA9IHBlcmZvcm1hbmNlKHByZWQsIG1lYXN1cmUgPSAidHByIiwgeC5tZWFzdXJlPSJmcHIiKQ0KDQojIyMjDQoNCm9wdC5jdXQgPSBmdW5jdGlvbihwZXJmLCBwcmVkKXsNCiAgY3V0LmluZCA9IG1hcHBseShGVU49ZnVuY3Rpb24oeCwgeSwgcCl7DQogICAgZCA9ICh4IC0gMCleMiArICh5LTEpXjINCiAgICBpbmQgPSB3aGljaChkID09IG1pbihkKSkNCiAgICBjKHNlbnNpdGl2aXR5ID0geVtbaW5kXV0sIA0KICAgICAgc3BlY2lmaWNpdHkgPSAxLXhbW2luZF1dLCANCiAgICAgIGN1dG9mZiA9IHBbW2luZF1dKQ0KICB9LCBwZXJmQHgudmFsdWVzLCBwZXJmQHkudmFsdWVzLCBwcmVkQGN1dG9mZnMpDQp9DQoNCmN1dF9vZmZfb3B0X2RmID0gb3B0LmN1dChST0MucGVyZiwgcHJlZCkgJT4lIA0KICB0KCkgJT4lIGFzLmRhdGEuZnJhbWUoKQ0KY3V0X29mZl9vcHQgPSBjdXRfb2ZmX29wdF9kZiRjdXRvZmYNCg0KDQpjdXRfb2ZmX2xpc3QgPSBjKDAuMDEsIDAuMTAsIDAuMjUsIDAuNTAsIC43NSwgLjkwKQ0KY3V0X29mZl9saXN0ID0gYyhjdXRfb2ZmX2xpc3QsY3V0X29mZl9vcHQpDQpjdXRfb2ZmX3ZhbHVlcyA9IGdldF9jdXRfb2ZmX3ZhbHVlcygNCiAgY3V0X29mZiA9IGN1dF9vZmZfbGlzdCkNCg0KDQpwYXJrLmR3dG4uY3V0b2ZmID0gZ2V0X3NlbnNfc3BlY19taXNzKA0KICBmb2N1c19kZiA9IGN1dF9vZmZfdmFsdWVzLA0KICBjdXRfb2ZmPWN1dF9vZmZfbGlzdFsxXSkNCg0KZm9yIChjdXJyX2N1dF9vZmYgaW4gY3V0X29mZl9saXN0WzI6bGVuZ3RoKGN1dF9vZmZfbGlzdCldKXsNCiAgICAgIHBhcmsuZHd0bi5jdXRvZmYgPSByYmluZCgNCiAgICAgICAgcGFyay5kd3RuLmN1dG9mZiwNCiAgICAgICAgZ2V0X3NlbnNfc3BlY19taXNzKA0KICAgICAgICAgIGZvY3VzX2RmID0gY3V0X29mZl92YWx1ZXMsDQogICAgICAgICAgY3V0X29mZiA9IGN1cnJfY3V0X29mZikNCiAgICAgICkgJT4lIGFzLmRhdGEuZnJhbWUoKQ0KICAgICAgcm93Lm5hbWVzKHBhcmsuZHd0bi5jdXRvZmYpID0gTlVMTH0NCnBhcmsuZHd0bi5jdXRvZmYgPSANCiAgcGFyay5kd3RuLmN1dG9mZiAlPiUgDQogIHJvdW5kKDMpICU+JSBhcnJhbmdlKEN1dF9PZmYpICU+JQ0KICBtdXRhdGUoDQogICAgQ3V0X09mZiA9IHN1YigiMCskIiwgIiIsIGFzLmNoYXJhY3RlcihDdXRfT2ZmKSkgJT4lDQogICAgICBzdHJfcmVtb3ZlKC4sICJeMCsiKQ0KICApDQoNCmNvbHMgPSBjb2xuYW1lcyhwYXJrLmR3dG4uY3V0b2ZmKQ0KbmV3X2NvbHMgPSBzZXROYW1lcygNCiAgcmVwbGFjZShjb2xzLCBjb2xzPT0nQ3V0X09mZicsICdDdXQtT2ZmIFZhbHVlJyksIA0KICBjb2xzKQ0KDQojcGFyay5kd3RuLmN1dG9mZi5mbGV4ID0gDQogIHBhcmsuZHd0bi5jdXRvZmYgJT4lDQogIG11dGF0ZShDdXRfT2ZmID0gDQogICAgICAgICAgIGlmZWxzZShDdXRfT2ZmPT0nLjQwNCcsDQogICAgICAgICAgICAgICAgICAnNDAuNCUqJywNCiAgICAgICAgICAgICAgICAgIEN1dF9PZmYgJT4lIA0KICAgICAgICAgICAgICAgICAgICBhcy5udW1lcmljKCkgJT4lDQogICAgICAgICAgICAgICAgICAgIHBlcmNlbnRfZm9ybWF0dGVyKCkNCiAgICAgICAgICAgICAgICAgICksDQogICAgICAgICBTZW5zaXRpdml0eSA9IFNlbnNpdGl2aXR5ICU+JSANCiAgICAgICAgICAgICAgICAgICAgYXMubnVtZXJpYygpICU+JQ0KICAgICAgICAgICAgICAgICAgICBwZXJjZW50X2Zvcm1hdHRlcihuPTEpLA0KICAgICAgICAgU3BlY2lmaWNpdHkgPSBTcGVjaWZpY2l0eSAlPiUgDQogICAgICAgICAgICAgICAgICAgIGFzLm51bWVyaWMoKSAlPiUNCiAgICAgICAgICAgICAgICAgICAgcGVyY2VudF9mb3JtYXR0ZXIobj0xKSwNCiAgICAgICAgIE1pc2NsYXNzaWZpY2F0aW9uID0gTWlzY2xhc3NpZmljYXRpb24gJT4lIA0KICAgICAgICAgICAgICAgICAgICBhcy5udW1lcmljKCkgJT4lDQogICAgICAgICAgICAgICAgICAgIHBlcmNlbnRfZm9ybWF0dGVyKG49MSkNCiAgICAgICAgICkgJT4lDQogIGZsZXh0YWJsZSguLCBjb2xfa2V5cyA9IGNvbHMpICU+JQ0KICBzZXRfaGVhZGVyX2xhYmVscygNCiAgICAuLCANCiAgICB2YWx1ZXMgPSBuZXdfY29scykgJT4lDQogIHRoZW1lX3ZhbmlsbGEoLikgJT4lDQogIGFsaWduKC4sIA0KICAgICAgICBhbGlnbiA9ICJjZW50ZXIiLCANCiAgICAgICAgcGFydCA9ICJoZWFkZXIiKSAlPiUNCiAgYWxpZ24oLiwgaj0yOmxlbmd0aChjb2xzKSwgDQogICAgICAgIGFsaWduID0gInJpZ2h0IiwgDQogICAgICAgIHBhcnQgPSAiYm9keSIpICU+JQ0KICBhbGlnbiguLCBqPTEsIA0KICAgICAgICBhbGlnbiA9ICJjZW50ZXIiLCANCiAgICAgICAgcGFydCA9ICJib2R5IikgJT4lDQogIHNldF90YWJsZV9wcm9wZXJ0aWVzKA0KICAgIC4sIGxheW91dD0nYXV0b2ZpdCcpICU+JQ0KICBiZyguLCBpID0gfiBDdXRfT2ZmID09ICc0MC40JSonLCBiZyA9ICJ3aGVhdCIsIHBhcnQgPSAiYm9keSIpICU+JQ0KICBhZGRfZm9vdGVyX3JvdygNCiAgICAuLCANCiAgICB2YWx1ZXM9YygiKiBPcHRpbWl1bSBDdXQtT2ZmIiksIA0KICAgIGNvbHdpZHRocyA9IGMobGVuZ3RoKGNvbHMpKSkNCg0KDQpgYGANCg0KDQoNClRoZSAqY29uZnVzaW9uIG1hdHJpeCogZm9yIHRoZSB0aHJlc2hvbGQgb2YgLjQwNCAoYmVsb3cpIGhpZ2hsaWdodHMgdGhlIFRydWUvRmFsc2UgTmVnYXRpdmUvUG9zaXRpdmUgcmF0ZXMgdGhhdCBmb3JtIHRoZSBwcmV2aW91c2x5IGRpc2N1c3NlZCByYXRlcy4gT3VyIFRydWUgTmVnYXRpdmVzIChhKSBhbmQgUG9zaXRpdmVzIChiKSBhcmUgZmFpcmx5IGhpZ2ggLS0gd2hpY2ggaXMgYSBnb29kIHNpZ24gb2Ygb3VyIG1vZGVsJ3MgYWNjdXJhY3kuIA0KDQpgYGB7ciBjb25mdXNpb25fbWF0cml4LCByZXN1bHRzPSdhc2lzJ30NCg0KDQpwYXJrLnRlc3QuZHd0biA9IGRhdGEuZnJhbWUoDQogIGlkLmJsb2NrID0gcGFyay50ZXN0ICU+JSANCiAgICBmaWx0ZXIocXVhZD09J05FJykgJT4lIG5hLm9taXQoKSAlPiUgDQogICAgcHVsbChpZC5ibG9jaykgJT4lIGFzLmZhY3RvciguKSwNCiAgYmluID0gcGFyay50ZXN0ICU+JSANCiAgICBmaWx0ZXIocXVhZD09J05FJykgJT4lIG5hLm9taXQoKSAlPiUgDQogICAgcHVsbChiaW4pICU+JSBhcy5mYWN0b3IoLiksDQogIG9jYzUwID0gcGFyay50ZXN0ICU+JSANCiAgICBmaWx0ZXIocXVhZD09J05FJykgJT4lIG5hLm9taXQoKSAlPiUgDQogICAgcHVsbChvY2M1MCkgJT4lIGFzLmZhY3RvciguKSwNCiAgb2NjNTAucHJlZGljdC5wY3QgPSBwcmVkaWN0KA0KICAgIGxtLmR3dG4uYWxsLCANCiAgICBwYXJrLnRlc3QgJT4lIA0KICAgIGZpbHRlcihxdWFkPT0nTkUnKSAlPiUgbmEub21pdCgpLCANCiAgICB0eXBlPSAicmVzcG9uc2UiKQ0KICApICU+JQ0KICBtdXRhdGUoDQogICAgb2NjNTAucHJlZGljdCAgPSANCiAgICAgIGFzLmZhY3RvcigNCiAgICAgICAgaWZlbHNlKA0KICAgICAgICAgIG9jYzUwLnByZWRpY3QucGN0ID4gY3V0X29mZl9vcHQsDQogICAgICAgICAgVFJVRSwgDQogICAgICAgICAgRkFMU0UNCiAgICAgICAgICApKSkNCnRlc3RsZW4gPSBucm93KHBhcmsudGVzdC5kd3RuKQ0KcGFyay50ZXh0LmR3dG4ubXggPSBjYXJldDo6Y29uZnVzaW9uTWF0cml4KA0KICBwYXJrLnRlc3QuZHd0biRvY2M1MC5wcmVkaWN0LCANCiAgcGFyay50ZXN0LmR3dG4kb2NjNTApICU+JSANCiAgYXMubWF0cml4KC4sIHdoYXQgPSAieHRhYnMiKSAlPiUNCiAgYXMuZGF0YS5mcmFtZSgpICU+JQ0KICB0cmFuc211dGUoDQogICAgUmVzdWx0cyA9IGMoJ1ByZWRpY3RlZFxuT3V0Y29tZScpLA0KICAgIE91dGNvbWUgPSBjKCJCZWxvdyA1MCVcbihGQUxTRSkiLCJBYm92ZSA1MCVcbihUUlVFKSIpLA0KICAgIGBCZWxvdyA1MCVcbihGQUxTRSlgID0gKGBGQUxTRWAvdGVzdGxlbikgJT4lIHBlcmNlbnRfZm9ybWF0dGVyKCkgJT4lDQogICAgICBwYXN0ZShjKCcoYSknLCcoYyknKSwgLiwgc2VwPScgJyksDQogICAgYEFib3ZlIDUwJVxuKFRSVUUpYCA9IChgVFJVRWAvdGVzdGxlbikgJT4lIHBlcmNlbnRfZm9ybWF0dGVyKCkgJT4lDQogICAgICBwYXN0ZShjKCcoYiknLCcoZCknKSwgLiwgc2VwPScgJykNCiAgKQ0Kcm93bmFtZXMocGFyay50ZXh0LmR3dG4ubXgpID0gTlVMTA0KDQpyZW5hbWVfY29scyA9IHNldE5hbWVzKA0KICBjKCIiLCAiIiwgIkJlbG93IDUwJVxuKEZBTFNFKSIsICJBYm92ZSA1MCVcbihUUlVFKSIgKSwgDQogIGNvbG5hbWVzKHBhcmsudGV4dC5kd3RuLm14KSkNCg0KDQpwYXJrLnRleHQuZHd0bi5teCAlPiUNCiAgZmxleHRhYmxlKC4pICU+JQ0KICB0aGVtZV92YW5pbGxhKC4pICU+JQ0KICBhbGlnbiguLCANCiAgICAgICAgYWxpZ24gPSAiY2VudGVyIiwgDQogICAgICAgIHBhcnQgPSAiaGVhZGVyIikgJT4lDQogIGFsaWduKC4sIGo9Mjo0LCANCiAgICAgICAgYWxpZ24gPSAiY2VudGVyIiwgDQogICAgICAgIHBhcnQgPSAiYm9keSIpICU+JQ0KICBhbGlnbiguLCBqPTEsIA0KICAgICAgICBhbGlnbiA9ICJjZW50ZXIiLCANCiAgICAgICAgcGFydCA9ICJib2R5IikgJT4lDQogIHNldF90YWJsZV9wcm9wZXJ0aWVzKA0KICAgIC4sIGxheW91dD0nYXV0b2ZpdCcpICU+JQ0KICBtZXJnZV92KGogPSB+UmVzdWx0cykgJT4lDQogIHNldF9oZWFkZXJfbGFiZWxzKA0KICAgIC4sIA0KICAgIHZhbHVlcyA9IHJlbmFtZV9jb2xzKSAlPiUNCiAgYWRkX2hlYWRlcl9yb3coDQogICAgLiwgDQogICAgdmFsdWVzID0gYygnJywgJ09ic2VydmVkIE91dGNvbWUnKSwgDQogICAgY29sd2lkdGhzID0gYygyLDIpKSAlPiUNCiAgYm9sZCguLCBpID0gMToyLCBqID0gMToyLCBib2xkID0gVFJVRSwgcGFydCA9ICJib2R5IikgJT4lDQogIGFkZF9mb290ZXJfcm93KA0KICAgIC4sIA0KICAgIHZhbHVlcz1jKCJTcGVjaWZpY2l0eSA9ICBhLyhhK2MpXG5TZW5zaXRpdml0eSA9ICBkLyhkK2IpXG5NaXNjbGFzc2lmaWNhdGlvbiBSYXRlID0gIChiK2MpLyhhK2IrYytkKSIpLCANCiAgICBjb2x3aWR0aHMgPSBjKDQpKQ0KDQoNCiMgICBGYWxzZSBOZWdhdGl2ZSAgICAgIFRydWUgTmVnYXRpdmUNCiMjIFJlZiAgTm8gQ2xpY2sgICAgICBSZWYgICBDbGljaw0KIyMgUHJlZCBObyBDbGljayAgICAgIFByZWQgIE5vIENsaWNrDQoNCiMgICBGYWxzZSBQb3NpdGl2ZSAgICAgIFRydWUgUG9zaXRpdmUNCiMjIFJlZiAgTm8gQ2xpY2sgICAgICBSZWYgICBDbGljaw0KIyMgUHJlZCBDbGljayAgICAgICAgIFByZWQgIENsaWNrDQoNCmBgYA0KVGhlICoqUHJlZGljdGVkIFByb2JhYmlsaXRpZXMgZ3JhcGgqKiBiZWxvdyBwcm92aWRlcyBhIHZpc3VhbCBvZiB0aGUgY29uZnVzaW9uIG1hdHJpeC4gVGhlIFRydWUgTmVnYXRpdmVzIChsZWZ0IHNpZGUgb2YgdGhlIGJvdHRvbSwgYmx1ZSBjdXJ2ZSkgYXJlIGFjY3VyYXRlbHkgcHJlZGljdGluZyBvY2N1cGFuY3kgcmF0ZXMgKGZvcm1pbmcgNTAlIG9mIHRvdGFsIG9ic2VydmF0aW9ucykgLS0gd2hpY2ggY2FuIGJlIHNlZW4gaW4gdGhlIGN1cnZlIHBlYWtpbmcgYXQgYSBwcmVkaWN0aW9uIHJhdGUgb2YgMTAlLiBUaGUgVHJ1ZS9GYWxzZSBQb3NpdGl2ZXMgKHRvcCwgcmVkIGN1cnZlKSBhcmUgbW9yZSBjb25jZXJuaW5nIGFzIHRoZXJlIGlzbid0IGEgZGlzdGluY3QgcGVhayB0byB0aGUgcmlnaHQgb2YgdGhlIHRocmVzaG9sZC4gQXMgYSByZXN1bHQsIHRoZXJlIGFyZSBtb3JlIEZhbHNlIFBvc2l0aXZlcyAoMTIlIG9mIHRvdGFsKSB3aGljaCBzdWdnZXN0cyBvdXIgbW9kZWwgZG9lcyBub3QgaGF2ZSBlbm91Z2ggcHJlZGljdG9yIHZhcmlhYmxlcyB0aGF0IGFjY291bnQgZm9yIHBlYWtzIG9mIGhpZ2ggbWV0ZXIgb2NjdXBhbmN5Lg0KDQpgYGAge3IgcGxvdHB9DQoNCmF1Yy5wZXJmID0gcGVyZm9ybWFuY2UocHJlZCwgbWVhc3VyZSA9ImF1YyIpDQpBVUMgPSBhdWMucGVyZkB5LnZhbHVlc1tbMV1dICU+JSByb3VuZCg0KQ0KDQpwYWxldHRlMiA9IGMoIiM5ODFGQUMiLCIjRkYwMDZBIikNCg0KZ2dwbG90KCkgKyANCiAgIyBnZW9tX2RlbnNpdHkoDQogICMgICBkYXRhPWZvY3VzLlJPQywNCiAgIyAgIGFlcyh4ID0gcHJlZGljdGlvbnMsIGdyb3VwPWxhYmVscyksIA0KICAjICAgZmlsbD0nZ3JleTkwJywgY29sb3I9J2JsYWNrJywgbHdkPTEuNQ0KICAjICAgKSArDQogIGdlb21fZGVuc2l0eSgNCiAgICBkYXRhPWZvY3VzLlJPQyAlPiUNCiAgICAgIG11dGF0ZSgNCiAgICAgICAgbGFiZWxzID0gaWZlbHNlKA0KICAgICAgICAgIGxhYmVscz09MSwNCiAgICAgICAgICAnQWJvdmUgNTAlIE9jY3VwYW5jeScsDQogICAgICAgICAgJ0JlbG93IDUwJSBPY2N1cGFuY3knDQogICAgICApKSwNCiAgICBhZXMoeCA9IHByZWRpY3Rpb25zLCBncm91cD1sYWJlbHMsIGZpbGw9bGFiZWxzLCBjb2xvcj1sYWJlbHMpLA0KICAgIGZpbGw9J2dyZXk5MCcsIGx3ZD0xLjI1ICMsIGNvbG9yPSdvcmFuZ2UnDQogICAgIyBsaW5ldHlwZT0nZGFzaGVkJw0KICAgICkgKw0KICBmYWNldF9ncmlkKGxhYmVscyB+IC4pICsNCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gcGFsZXR0ZTIpICsNCiAgbGFicygNCiAgICB4ID0gIlByb2JhYmlsaXR5IiwgDQogICAgeSA9ICJEZW5zaXR5IG9mIFByb2JhYmlsaXRpZXMiLA0KICAgIHRpdGxlID0gIlByZWRpY3RlZCBQcm9iYWJpbGl0aWVzIC0gRG93bnRvd24gTW9kZWwiLA0KICAgIHN1YnRpdGxlID0gZ2x1ZSgiT3B0aW1hbCBDdXQtT2ZmIFJhdGUgPSB7Y3V0X29mZl9vcHQgJT4lIHBlcmNlbnRfZm9ybWF0dGVyKG49MSl9ICAoZGFzaGVkIGxpbmUpIikNCiAgICApICsgDQogIHNjYWxlX3hfY29udGludW91cygNCiAgICBsYWJlbHMgPSBmdW5jdGlvbihudW0pIG51bSAlPiUgcGVyY2VudF9mb3JtYXR0ZXIoKSwNCiAgICBuYW1lPSJQcm9iYWJpbGl0eSIpICsgDQogIGdlb21fdmxpbmUoeGludGVyY2VwdD1jdXRfb2ZmX29wdCwNCiAgICAgICAgICAgICBsaW5ldHlwZT0nZGFzaGVkJykgKyANCiAgdGhlbWUoc3RyaXAudGV4dC54ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxOCksDQogICAgICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikgKyANCiAgcGxvdFRoZW1lKCkNCiNkZXYub2ZmKCkNCg0KYGBgDQoNCg0KIyMgUk9DIEN1cnZlDQoNClRoZSBST0MgY3VydmUgaXMgYSBnb29kbmVzcy1vZi1maXQgcGxvdCBvZiB0aGUgdHJ1ZSBwb3NpdGl2ZSByYXRlIChpLmUuLCBzZW5zaXRpdml0eSkgYWdhaW5zdCBmYWxzZSBwb3NpdGl2ZSByYXRlIChpLmUuLCBzcGVjaWZpY2l0eSkuIEEgY3VydmUgYXQgYSA0NSBkZWdyZWUgYW5nbGUgKGkuZS4ganVzdCBhIGRpYWdvbmFsIGxpbmUpIHN1Z2dlc3RzIHRoZSBtb2RlbCBpcyBpbmFjY3VyYXRlLiBBIGN1cnZlIHdpdGggYSBwZXJmZWN0IDkwIGRlZ3JlZSBhbmdsZSB3b3VsZCBiZSB2ZXJ5IHByZWRpY3RpdmUgYnV0IGlzIGxpa2VseSB0aGUgcmVzdWx0IG9mIG92ZXIgZml0dGluZy4gSW4gdGhlIGNoYXJ0IGJlbG93LCBvdXIgY3VydmUncyBib3cgc2hhcGUgaXMgYSBnb29kIHNpZ24gdGhhdCBvdXIgbW9kZWwgaXMgYWNjdXJhdGVseSBwcmVkaWN0aW5nIHdpdGhvdXQgb3Zlci1maXR0aW5nLiANCg0KVGhlIGFyZWEgdW5kZXIgb3VyIG1vZGVs4oCZcyBST0MgY3VydmUgaXMgMC44OC4gQW4gQVVDIHZhbHVlIHRoYXQgZmFsbHMgaW4gdGhlIDAuNjAtMC43MCByYW5nZSBpcyBjb25zaWRlcmVkIHBvb3IuIFRoaXMgdGVsbHMgdXMgdGhhdCBvdXIgbW9kZWwgaXMgZmFpcmx5IGFjY3VyYXRlLiANCg0KDQpgYGAge3IgZml0fQ0KDQojbGlicmFyeShyUk9DKQ0KDQpsaWJyYXJ5KHBsb3RST0MpDQoNCmdncGxvdCgpICsNCiAgZ2VvbV9yb2MoDQogICAgZGF0YSA9IGZvY3VzLlJPQywgDQogICAgYWVzKGQgPSBsYWJlbHMsIG0gPSBwcmVkaWN0aW9ucyksDQogICAgbi5jdXRzID0gNTAsIGxhYmVscyA9IEZBTFNFLCBjb2xvdXIgPSAiIzAwMDAwMCIpICsNCiAgIyBnZW9tX3JvYygNCiAgIyAgIGRhdGEgPSB0ZXN0LnByZWRpY3QsIA0KICAjICAgYWVzKGQgPSBhcy5udW1lcmljKHRlc3QucHJlZGljdCRvdXRjb21lKSwgbSA9IHByZWRpY3QuMiksDQogICMgICBuLmN1dHMgPSA1MCwgbGFiZWxzID0gRkFMU0UsIGNvbG91ciA9ICIjRkU5OTAwIikgKw0KICBzdHlsZV9yb2ModGhlbWUgPSB0aGVtZV9ncmV5KSArDQogIGdlb21fYWJsaW5lKHNsb3BlID0gMSwgaW50ZXJjZXB0ID0gMCwgc2l6ZSA9IDEuNSwgY29sb3IgPSAnZ3JleScpICsNCiAgbGFicygNCiAgICB0aXRsZSA9ICJST0MgQ3VydmUgLSBEb3dudG93biBNb2RlbCIsDQogICAgc3VidGl0bGUgPSBnbHVlKCJBcmVhIFVuZGVyIEN1cnZlID0ge0FVQ30iKQ0KICAgICkNCg0KYGBgDQoNCiMjIENWDQoNCg0KYGBge3IgY3Z9DQpsaWJyYXJ5KGNhcmV0KQ0KDQpwYXJrLnRyYWluLmR3dG4ucHJlcCA9IA0KICBwYXJrLnRyYWluLmR3dG4gJT4lDQogICAgICBkcGx5cjo6c2VsZWN0KGR3dG5fdmFycykgJT4lDQogICAgICBtdXRhdGUob2NjNTAgPSBpZmVsc2Uob2NjNTA9PVRSVUUsICJBQk9WRTUwIiwgIkJFTE9XNTAiKSkgICU+JQ0KICBuYS5vbWl0KCkNCnBhcmsudHJhaW4uZHd0bi5wcmVwJG9jYzUwID0gcGFyay50cmFpbi5kd3RuLnByZXAkb2NjNTAgJT4lIGFzLmZhY3RvcigpIA0KDQojIGNvbnRyb2wgQ1YgcGFyYW1ldGVycw0KY3RybCA9IHRyYWluQ29udHJvbCgNCiAgbWV0aG9kID0gImN2IiwgDQogIG51bWJlciA9IDEwMCwgDQogIGNsYXNzUHJvYnM9VFJVRSwNCiAgc3VtbWFyeUZ1bmN0aW9uPXR3b0NsYXNzU3VtbWFyeSAjKGRhdGE9cGFyay50cmFpbi5kd3RuLnByZXAsIGxldj1jKDEsMCkpDQogICkNCg0KZm9ybSA9IGNvbG5hbWVzKHBhcmsudHJhaW4uZHd0bi5wcmVwKQ0KZm9ybSA9IGZvcm1bZm9ybSE9J29jYzUwJyZmb3JtIT0naWQuZGlzdCddDQojZm9ybSA9IHBhc3RlKGZvcm0sIGNvbGxhcHNlPScgKyAnKSAlPiUgcGFzdGUoJ29jYzUwJywgLiwgc2VwPScgfiAnKSAlPiUgYXMuZm9ybXVsYSgpDQpsaWJyYXJ5KHBST0MpDQojIGN2IExNDQpwYXJrLmN2LmR3dG4gPSBjYXJldDo6dHJhaW4oDQogIHggPSBwYXJrLnRyYWluLmR3dG4ucHJlcCAlPiUgZHBseXI6OnNlbGVjdChmb3JtKSwNCiAgeSA9IHBhcmsudHJhaW4uZHd0bi5wcmVwJG9jYzUwLA0KICBtZXRob2Q9ImdsbSIsIA0KICBmYW1pbHk9ImJpbm9taWFsIiwNCiAgbWV0cmljPSJST0MiLCANCiAgdHJDb250cm9sID0gY3RybA0KICApDQoNCmBgYA0KDQoNCg0KYGBgIHtyIGN2X2hpc3R9DQoNCmRwbHlyOjpzZWxlY3QocGFyay5jdi5kd3RuJHJlc2FtcGxlLCAtUmVzYW1wbGUpICU+JQ0KICBnYXRoZXIobWV0cmljLCB2YWx1ZSkgJT4lDQogIGxlZnRfam9pbihnYXRoZXIocGFyay5jdi5kd3RuJHJlc3VsdHNbMjo0XSwgbWV0cmljLCBtZWFuKSkgJT4lDQogIGdncGxvdChhZXModmFsdWUpKSArIA0KICAgIGdlb21faGlzdG9ncmFtKGJpbnM9MzUsIGZpbGwgPSAiI0ZGMDA2QSIpICsNCiAgICBmYWNldF93cmFwKH5tZXRyaWMpICsNCiAgICBnZW9tX3ZsaW5lKGFlcyh4aW50ZXJjZXB0ID0gbWVhbiksIGNvbG91ciA9ICIjOTgxRkFDIiwgbGluZXR5cGUgPSAzLCBzaXplID0gMS41KSArDQogICAgI3NjYWxlX3hfY29udGludW91cyhsaW1pdHMgPSBjKDAsIDEpKSArDQogICAgbGFicyh4PSJHb29kbmVzcyBvZiBGaXQiLCB5PSJDb3VudCIsIHRpdGxlPSJDcm9zcyBWYWxpZGF0aW9uIC0tIE1lYXN1cmUgR29vZG5lc3Mgb2YgRml0IiwNCiAgICAgICAgIHN1YnRpdGxlID0gIkNvbXBhcmluZyBPYnNlcnZlZCBUcmFpbmluZyBSZXN1bHRzIHRvIGstZm9sZCBwZXJtdXRhdGlvbnMiKQ0KDQpgYGANCg0KIyMgTWFwDQoNClRoaXMgbWFwIGRpc3BsYXlzIHRoZSBtaXNjbGFzc2lmaWNhdGlvbiByYXRlIG9mIHRoZSBEb3dudG93biBtb2RlbC4gSXQgaGVscHMgdGhlIHRlYW0gZXZhbHVhdGUgd2hpY2ggZ2VvZ3JhcGhpYyBhcmVhcyBhcmUgYmVpbmcgbWlzY2xhc3NpZmllZC4gSXQgYXBwZWFycyB0aGF0IGJsb2NrcyB3aXRoIGhlYXZpZXIgb2NjdXBhdGlvbnMgYWxzbyBoYXZlIGhpZ2hlciBtaXNzY2xhc3NpZmljYXRpb25zLiBUaGlzIGFsaWducyB3aXRoIHRoZSBUZXN0IG1vZGVsIHN1bW1hcnkgYW5kIFJPQyByZXN1bHRzIHRoYXQgd2UgYXJlIHByZWRpY3RpbmcgYmV0dGVyIGZvciBsb3dlciBvY2N1cGFuY2llcyBhbmQgbm90IGhpZ2hlciBvY2N1cGFuY2llcy4gDQoNCmBgYCB7ciBFREFfbWFwX2R3bnRufQ0KI1NGLnF1YWQubGFiZWxzID0gc2ZfdG9fbGFiZWxzKFNGLnF1YWQsICdxdWFkJykgJT4lIGRwbHlyOjpmaWx0ZXIobGFiZWw9PSdORScpDQpTRi5kaXN0LmxhYmVscyA9IHNmX3RvX2xhYmVscygNCiAgU0YuZGlzdCAlPiUgYXJyYW5nZShxdWFkKSAlPiUgDQogICAgZHBseXI6OmZpbHRlcihxdWFkPT0nTkUnKSAlPiUNCiAgICBkcGx5cjo6ZmlsdGVyKCFQTV9ESVNUUklDVF9OQU1FICAlaW4lIGMoDQogICAgICAnUG9saycsICdUZWxlZ3JhcGggSGlsbCcNCiAgICApKSwgJ1BNX0RJU1RSSUNUX05BTUUnKSAlPiUNCiAgbXV0YXRlKGxhYmVsID0gDQogICAgaWZlbHNlKA0KICAgICAgbGFiZWwgPT0gJ04uQmVhY2gtQ2hpbmF0b3duJywNCiAgICAgICdOLkJlYWNoXG5cbkNoaW5hdG93bicsDQogICAgaWZlbHNlKA0KICAgICAgbGFiZWwgPT0gIkZpc2hlcm1hbidzIFdoYXJmIiwNCiAgICAgICJcbkZpc2hlcm1hbidzIFdoYXJmIiwNCiAgICAgICAgICAgbGFiZWwpDQogICAgICApICMlPiUgZ3N1YignICcsJ1xuJywgLiwgZml4ZWQgPSBUUlVFKQ0KICAgICkNCg0KcGFyay5tYXAgPSANCiAgcGFyay50ZXN0LmR3dG4gJT4lIA0KICAgIGRwbHlyOjpncm91cF9ieShpZC5ibG9jaywgb2NjNTAsIG9jYzUwLnByZWRpY3QpICU+JQ0KICAgIGRwbHlyOjpzdW1tYXJpemUoDQogICAgICBjb3VudCA9IG4oKQ0KICAgICkgJT4lDQogIG11dGF0ZSgNCiAgICBPdXRjb21lID0gDQogICAgICBjYXNlX3doZW4oDQogICAgICAgIG9jYzUwID09IFRSVUUgJiBvY2M1MC5wcmVkaWN0ID09IFRSVUUgfiAnVHJ1ZSBQb3NpdGl2ZScsDQogICAgICAgIG9jYzUwID09IFRSVUUgJiBvY2M1MC5wcmVkaWN0ID09IEZBTFNFIH4gJ0ZhbHNlIE5lZ2F0aXZlJywNCiAgICAgICAgb2NjNTAgPT0gRkFMU0UgJiBvY2M1MC5wcmVkaWN0ID09IFRSVUUgfiAnRmFsc2UgUG9zaXRpdmUnLA0KICAgICAgICBvY2M1MCA9PSBGQUxTRSAmIG9jYzUwLnByZWRpY3QgPT0gRkFMU0UgfiAnVHJ1ZSBOZWdhdGl2ZScNCiAgICAgICkNCiAgKSAlPiUgdW5ncm91cCgpICU+JQ0KICBkcGx5cjo6c2VsZWN0KGlkLmJsb2NrLE91dGNvbWUsY291bnQpICU+JSANCiAgZGNhc3QoLiwgaWQuYmxvY2t+T3V0Y29tZSwgZmlsbD0wKSAlPiUNCiAgbXV0YXRlKA0KICAgIE1pc2NsYXNzaWZpY2F0aW9uID0gKGBGYWxzZSBOZWdhdGl2ZWAgKyBgRmFsc2UgUG9zaXRpdmVgKSAvIA0KICAgICAgKGBGYWxzZSBOZWdhdGl2ZWAgKyBgRmFsc2UgUG9zaXRpdmVgICsgYFRydWUgTmVnYXRpdmVgICsgYFRydWUgUG9zaXRpdmVgKQ0KICApICU+JSANCiAgbWVyZ2UoDQogICAgLiwNCiAgICBwYXJrLmJsb2NrcywNCiAgICBvbj0naWQuYmxvY2snDQogICkgJT4lDQogIHN0X3NmKCkgJT4lDQogIHN0X2J1ZmZlcig1MCkNCg0KI3BhcmsubWFwJGJpbiA9IGZhY3RvcihwYXJrLm1hcCRiaW4sIGxldmVscyA9IGMoIkFsbCIsIjlhIHRvIDEycCIsICIxMnAgdG8gM3AiLCAiM3AgdG8gNnAiKSkNCiMgcGFyay5tYXAkZGF5LndlZWsgPSBmYWN0b3IoDQojICAgcGFyay5tYXAkZGF5LndlZWssDQojICAgbGV2ZWxzID0gDQojICAgICBjKCJNb25kYXkiLCAiVHVlc2RheSIsICJXZWRuZXNkYXkiLCAiVGh1cnNkYXkiLCAiRnJpZGF5IiwgIlNhdHVyZGF5IiwgIlN1bmRheSIpKQ0Kc2VwID0gMTAwMDANCmdncGxvdCgpKw0KICBnZW9tX3NmKA0KICAgIGRhdGEgPSBTRi5xdWFkICU+JSANCiAgICAgIGRwbHlyOjpmaWx0ZXIocXVhZD09J05FJyksIGNvbG9yID0gJ3RyYW5zcGFyZW50JywgZmlsbD0nZ3JleTkwJw0KICApICsNCiAgZ2VvbV9zZihkYXRhID0gcGFyay5tYXAsDQogICAgICAgICAgYWVzKGZpbGwgPSBNaXNjbGFzc2lmaWNhdGlvbiksICMsIHNpemU9bWV0ZXJzLmNvdW50KSwgDQogICAgICAgICAgY29sb3IgPSAidHJhbnNwYXJlbnQiLCBhbHBoYSA9IDAuOTUNCiAgICAgICAgICApICsNCiAgZ2VvbV90ZXh0X3NmKA0KICAgIFNGLmRpc3QubGFiZWxzLCBjaGVja19vdmVybGFwID0gVFJVRSwNCiAgICANCiAgICAjaGp1c3QgPSAidG9wIiMsIHZqdXN0ID0gIm91dHdhcmQiDQogICAgKSArIA0KICB4bGltKG1pbihTRi5kaXN0LmxhYmVscyRsb24pLXNlcCwgbWF4KFNGLmRpc3QubGFiZWxzJGxvbikrc2VwKSArIA0KICBzY2FsZV9maWxsX2dyYWRpZW50bigNCiAgICBjb2xvcnMgPSBSQ29sb3JCcmV3ZXI6OmJyZXdlci5wYWwoNSwgIllsT3JSZCIpLA0KICAgIHZhbHVlcyA9IGMoMCwuNDUsLjY1LC44NSwxKSwNCiAgICBsYWJlbHMgPSBmdW5jdGlvbih0c3RyKSBwZXJjZW50X2Zvcm1hdHRlcih0c3RyKQ0KICAgICNkaXJlY3Rpb24gPSAtMSwgZGlzY3JldGUgPSBGQUxTRSwgb3B0aW9uID0gIkQiDQogICAgKSsNCiAgZ3VpZGVzKGZpbGwgPSBndWlkZV9jb2xvdXJiYXIoYmFyd2lkdGggPSAyKSkgKyANCiAgI2ZhY2V0X3dyYXAofmJpbiwgbmNvbCA9IDIpKw0KICBsYWJzKA0KICAgIHRpdGxlPSdUZXN0aW5nIFNldCBNaXNjbGFzc2lmaWNhdGlvbiBSYXRlJywNCiAgICBzdWJ0aXRsZSA9ICdTYW4gRnJhbmNpc2NvLCAyMDE5Jw0KICAgICkrDQogIG1hcFRoZW1lDQpnZW9tX3RleHQoKQ0KDQpgYGANCg0KDQoNCg0KIyBDb25jbHVzaW9uDQojIyBVc2UgQ2FzZSBSZXN1bHRzDQoNCipMaWtlIGFueSBnb29kIFNpbGljb24gVmFsbGV5IGFwcCwqIG91ciBhcHAgaXMgcHJvbWlzaW5nIGluIHRoZW9yeSBidXQgYSBkZXZpbCdzIGVycmFuZCBpbiByZWFsaXR5LiBUaGUgYXBwJ3MgYWJpbGl0eSB0byBwcmVkaWN0IG9wZW4gcGFya2luZyBzcG90cyBoaW5nZXMgb24gdGhlIGEgc2luZ2xlIGNpdHkncyB1bmNsZWFuZWQgZGF0YXNldC4gUmVnYXJkbGVzcywgdGhlIHNlY29uZCwgZG93bnRvd24gbW9kZWwgYXBwZWFyZWQgdG8gcHJvdmlkZSBhIHJvdWdoIGVzdGltYXRpb24gb2Ygd2hpY2ggYmxvY2tzIGFyZSBtb3JlIG9jY3VwaWVkIGluIGEgZ2l2ZW4gYXJlYSBvciB0aW1lLiBPdXIgZmluYWwgYXBwbGljYXRpb24gd291bGQgc2ltcGx5IHRha2UgaW4gdGhlIGxpc3Qgb2YgYmxvY2tzIGFuZCB0aGVpciBwcmVkaWN0ZWQgb2Rkcy4gDQoNCiMjIEltcHJvdmVtZW50cw0KDQpCZWZvcmUgdGhlIGFwcCBnb2VzIHRvIG1hcmtldCwgSSBiZWxpZXZlIHRoZXJlIGFyZSBsYXJnZSBnYXBzIGZvciBpbXByb3ZlbWVudCwgc3BlY2lmaWNhbGx5IGZvciB0aGU6DQoNCjEuICoqRGVwZW5kZW50IFZhcmlhYmxlKioNCkl0IHdvdWxkIGJlIGJldHRlciB0byBkZXRlcm1pbmUgdGhlICpwcm9iYWJpbGl0eSB0aGF0IGEgc2luZ2xlIHBhcmtpbmcgc3BvdCBpcyBvcGVuKi4gQSBibG9jayBjb3VsZCBoYXZlIGEgbG93IGF2ZXJhZ2Ugb2NjdXBhdGlvbiByYXRlIGJ1dCBvbmx5IGhhdmUgMiBwYXJraW5nIHNwb3RzIC0tIHRoYXQgY291bGQgZ2V0IGZpbGxlZCB1cC4gVGhpcyB3b3VsZCByZXF1aXJlIGEgaGVhdmllciBhbW91bnQgb2YgY2xlYW5pbmcgYW5kIGEgZGlmZmVyZW50IG1vZGVsLg0KDQpBdCB0aGUgc2FtZSB0aW1lLCB0aGUgbWV0ZXIgZGF0YSBvbmx5IHByb3ZpZGVzIHBhcmtpbmcgc3BhY2UgdGltZXMgdGhhdCBoYXZlIG1ldGVyZWQgdHJhbnNhY3Rpb25zLiBUaGUgY2l0eSBzdGlsbCBoYXMgYSBsYXJnZSBhbW91bnQgb2YgaWxsZWdhbCBwYXJraW5nIGFuZCBmcmVlIG5laWdoYm9yaG9vZCBwYXJraW5nLiANCg0KMi4gKipNb2RlbCBUeXBlKioNClRoZSBhcHAncyBmb2N1cyBvbiBmaW5kaW5nIGEgc2luZ2xlIHBhcmtpbmcgc3BvdCBzdWdnZXN0cyBpdCB3b3VsZCBiZSBiZXR0ZXIgdG8gbG9vayBhdCB0aGUgcHJvYmFiaWxpdHkgb2YgMSBvciAyIHNwb3RzIGJlaW5nIG9wZW4uIEEgVGVtcGxlLWJhc2VkIHNwYXRpYWwgc3RhdGlzdGljcyBwcm9mZXNzb3Igc3VnZ2VzdGVkIHRvIHRoZSB0ZWFtIHRoYXQgYSAqb3JkaW5hbCBsb2dpc3RpYyByZWdyZXNzaW9uIG1vZGVsKiBjb3VsZCBmaW5kIHRoZSBwcm9iYWJpbGl0eSBvZiAwLCAxLCBvciAyKyBzcG90cyBiZWluZyBvcGVuLiBUaGUgbW9kZWwncyBhZGRlZCBjb21wbGV4aXR5IGlzLCBob3dldmVyLCBvdXRzaWRlIHRoZSBjb25maW5lcyBvZiB0aGlzIGFwcCBkZXZlbG9wbWVudCB0ZWFtJ3MgY3VycmVudCB0aW1lbGluZSBhbmQgcGF5IGdyYWRlLiANCg0KMy4gKipJbXByb3ZlZCBEYXRhIENsZWFuaW5nKioNClRoZSBjbGVhbmVkIHBhcmtpbmcgbWV0ZXIgdHJhbnNhY3Rpb25zIHByb3ZpZGUgYW4gYXZlcmFnZSBvY2N1cGFuY3kgcmF0ZSAofjQyJSkgdGhhdCBmZWVscyB0cmVtZW5kb3VzbHkgbG93ZXIgdGhhbiBhbmVjZG90YWwgZXZpZGVuY2Ugb3IgU0ZNVEEncyBwcmljaW5nIHJhdGVzICh3aGljaCBzdWdnZXN0IDYwLTgwJSBvY2N1cGFuY3kpLiBPdmVyYWxsIHRoZSBwYXJraW5nIGRhdGFzZXQgaXMgbGFyZ2UgYW5kIG11cmt5LiBNYW55IHRyYW5zYWN0aW9uIGFwcGVhciB0byBiZSBhZG1pbmlzdHJhdGl2ZSAoZS5nLiBwYXJraW5nIHRyYW5zYWN0aW9uIGZyb20gbWlkbmlnaHQgdG8gMTBhbSkgYnV0IHRoZXJlIGlzbid0IGEgY29sdW1uIGRlc2NyaWJpbmcgdGhpcyB0cmFuc2FjdGlvbi4gSWYgdGhlIGFwcCBnYXJuZXJzIHB1YmxpYyBzdXBwb3J0LCBTRk1UQSBjb3VsZCBwcm92aWRlIHRoZSB0ZWFtIHdpdGggYSBjbGVhbmVyIGRhdGFzZXQuIA0KDQo0LiAqKkltcHJvdmVkIFByZWRpY3RvciBWYXJpYWJsZXMqKg0KVGhlIG1vZGVsIGhhZCBhIGxvd2VyIEFJQyBzY29yZSBhbmQgYSBwcm9taXNpbmcgUk9DIEN1cnZlLCBidXQgdGhlIHJlbGF0aXZlbHkgbG93IFRydWUgUG9zaXRpdmUgUmF0ZSBzdWdnZXN0cyB0aGF0IHRoZSBtb2RlbCBpcyBtb3JlIGFtYmlndW91cyBhdCBwcmVkaWN0aW5nIGhpZ2hlciBvY2N1cGFuY3kgcGFya2luZyBtZXRlcnMuIFNvbWUgaGVscGZ1bCBwcmVkaWN0b3IgdmFyaWFibGVzIHRoYXQgY291bGQgcHJlZGljdCBoaWdoIGRlbWFuZCBhcmU6IGV2ZW50IHRpbWVzICYgbG9jYXRpb25zLCBjb21tZXJjaWFsIGNlbnRlciBsb2NhdGlvbnMsIGFuZCBTYW4gRnJhbmNpc2NvIGxhbmRtYXJrcy4gQW5vdGhlciBzZXQgb2YgcHJlZGljdG9ycyBhcmUgdGhlIGxvY2F0aW9uIG9mIGZyZWUgb24tc3RyZWV0IHBhcmtpbmcgc3BhY2VzIGFuZCBwYWlkIG9mZi1zdHJlZXQgcGFya2luZyBzcG90cy4NCg0KNS4gKipFbmxhcmdpbmcgU3R1ZHkgQXJlYSAmIENpdHkqKg0KVGhlIGZpbmFsIG1vZGVsIG9ubHkgbG9va2VkIGF0IERvd250b3duIFNhbiBGcmFuY2lzY28gYW5kIGF0IG1ldGVyZWQgc3BvdHMuIElGIHdlIGhhZCB0aGUgZGF0YSBmb3Igbm9uLW1ldGVyZWQgcGFya2luZyBzcG90cywgdGhlIGFwcCB1c2VyIHdvdWxkIGJlbmVmaXQgaW4gbm90IGhhdmluZyB0byBzcGVuZCBtb25leS4gDQoNCg0KDQoNCg0K