A couple of days ago, I read about this news that said Cicago motorists will get a longer grace period before getting hit with a $100 red light camera ticket. Chicago extend the “enforcement threshold” when cars are given tickets from 0.1 seconds to 0.3 seconds after the light turns red. It’s believed the change will cut the number of tickets issued by about 29 percent and result in an expected revenue loss of $17 million this year.

This is great news, I guess, to many drivers in the city. To people who are interested in data science this is also a great opportunity to explore the impact of a policy change, by studying the public available data. Luckily, city of Chicago publishes Red Light Camera Violations dataset at the City of Chicago Data Portal

In this first part of the series, we will explore the dataset and try to answer the following questions,

  1. Where are the cameras?
  2. Which intersections have the most or the fewest violations?
  3. Can we build a model to predict the number of violations?

In the next post, we will use an updated dataset after the new policy kicks in for a few weeks along with our model to examine if the new policy brings down ticket issuance.

The whole analysis will be done in R notebook. We start by loading all necessary packages.

library(plyr)
library(dplyr)
library(tibble)
library(ggplot2)
library(ggmap)
library(ggthemes)
library(viridis)
library(lubridate)
library(forecast)
library(xts)
library(prophet)

The dataset was downloaded as of March 22. We read in the data saved as a CSV file and show the last six rows of the file. The rows are sorted by violaion date. We can see that the latest violation records are from 03/08/2017. Remember that the new rule does not take effect until March 20. So the data in the file records those violations 0.1 second from the light turns red. Another quick look at the summary reveals that there’re NAs in the camera ID column, coordinates columns and locations columns. The City of Chicago publishes the list of current Red Light Camera intersections. By looking at the list we know that at each section, usually there are multiple cameras, facing different directions. We take a simplified approach here and aggregate the violations of such cameras at the same intersections. We care about the violations at the intersection level and won’t distinguish if east-west traffic or north-south traffic is more violation-prone.

rlcv <- read.csv("Red_Light_Camera_Violations.csv") %>% tbl_df()
rlcv %>% tail() %>% print()
rlcv %>% summary()
                  INTERSECTION      CAMERA.ID   
 CALIFORNIA AND DIVERSEY:  2836   Min.   :1002  
 KOSTNER AND NORTH      :  2491   1st Qu.:1444  
 BELMONT AND KEDZIE     :  2383   Median :1851  
 STONEY ISLAND AND 79TH :  2119   Mean   :1860  
 WENTWORTH AND GARFIELD :  1947   3rd Qu.:2294  
 ROOSEVELT AND HALSTED  :  1944   Max.   :8313  
 (Other)                :251278   NA's   :305   
                  ADDRESS          VIOLATION.DATE     VIOLATIONS     
 800 W ROOSEVELT ROAD :  1944   09/26/2014:   315   Min.   :  1.000  
 1000 W HOLLYWOOD AVE :  1943   02/27/2015:   314   1st Qu.:  2.000  
 2400 N ASHLAND AVENUE:  1940   09/13/2014:   313   Median :  4.000  
 2000 W DIVISION      :  1928   08/16/2014:   308   Mean   :  5.615  
 6800 W GRAND AVENUE  :  1925   09/17/2014:   308   3rd Qu.:  7.000  
 3600 N CICERO AVENUE :  1908   09/27/2014:   308   Max.   :186.000  
 (Other)              :253410   (Other)   :263132                    
  X.COORDINATE      Y.COORDINATE        LATITUDE       LONGITUDE     
 Min.   :1125717   Min.   :1825911   Min.   :41.68   Min.   :-87.81  
 1st Qu.:1147201   1st Qu.:1869738   1st Qu.:41.80   1st Qu.:-87.73  
 Median :1158152   Median :1907723   Median :41.90   Median :-87.69  
 Mean   :1157552   Mean   :1898415   Mean   :41.88   Mean   :-87.70  
 3rd Qu.:1167172   3rd Qu.:1921821   3rd Qu.:41.94   3rd Qu.:-87.66  
 Max.   :1191188   Max.   :1947754   Max.   :42.01   Max.   :-87.58  
 NA's   :15826     NA's   :15826     NA's   :15826   NA's   :15826   
                                     LOCATION     
                                         : 15826  
 (41.86726309836901, -87.64697268601648) :  1944  
 (41.98564979859328, -87.65533580655139) :  1943  
 (41.92519388981048, -87.66829436339881) :  1940  
 (41.903298792681944, -87.67738324739636):  1928  
 (41.92370819286044, -87.79530078123082) :  1925  
 (Other)                                 :239492  

As we see below, the missing camera IDs composite a small portion of records in the dataset. And when aggregating the violations at the intersection level, the missing IDs shouldn’t affect our analysis.

rlcv %>% group_by(INTERSECTION, CAMERA.ID) %>% 
  #select(INTERSECTION, CAMERA.ID, VIOLATION.DATE, VIOLATIONS) %>% 
  dplyr::summarize(Sum.Of.Violations = sum(VIOLATIONS))

We want to map the red lights onto a Chicago map. Missing location data could be a challenge to us. Let’s see how we can fix that. We notice that every record with missing locations has a valid intersection value. Since we analyze at the intersection level, as long as we have the location for that intersection, we will use that to fill in the blanks. We still have three intersections without location data. We will ignore them in visualization for the moment.

intersection_geo <- rlcv %>% group_by(INTERSECTION) %>% 
  dplyr::summarize(intersection_lat = round(mean(LATITUDE, na.rm = T), 3),
                   intersection_lon = round(mean(LONGITUDE, na.rm = T), 3))
intersection_geo

Let’s first see where these cameras are. The network of the red light cameras is pretty much covering the core neighborhoods of Chicago. But there aren’t many cameras in the Loop or South Loop area.

qmplot(intersection_lon, intersection_lat, data = intersection_geo,
       zoom = 11, size = I(1.25), color = I("red"),
       maptype = "toner-lite",
       main = "Intersections with Red Light Cameras") +
  theme_minimal() + xlab("") + ylab("")

It would be interesting to see which intersections have seen the most violations since the installation of the cameras. LAKE SHORE DR AND BELMONT has seen the most violations. CICERO AND I55 and VAN BUREN AND WESTERN are not far away.

rlcv %>% group_by(INTERSECTION) %>% 
  dplyr::summarize(sum_violations = sum(VIOLATIONS),
            med_violations = median(VIOLATIONS),
            lat2 = round(mean(LATITUDE, na.rm = T), 4),
            lon2 = round(mean(LONGITUDE, na.rm = T), 4)) %>% 
  arrange(-sum_violations) -> intersection_violations
intersection_violations
qmplot(lon2, lat2, data = intersection_violations,
       zoom = 11, size = sum_violations,
       maptype = "toner-lite", color = sum_violations,
       main = "Sum of violations at each intersection") +
  viridis::scale_color_viridis(option = "magma", direction = -1) +
  theme_minimal() + xlab("") + ylab("") +
  guides(color=guide_legend(title = ""),
         size = guide_legend(title = ""))

But by looking at the total of the violations since the start of the data may not be an accurate description. It’s possible that some red light cameras are newly installed and hasn’t had accumulated many violations. However these new cameras could send out many tickets on a daily basis. To accomodate that thought, we look at the median of the violations at the intersections, which would give us a view of what a typical day looks like. And we take a snap shot of February 2017 data. This should give us a most recent view of which cameras have seen the most violations in a month.

qmplot(lon2, lat2, data = intersection_violations,
       zoom = 11, size = med_violations,
       maptype = "toner-lite", color = med_violations,
       main = "Median of violations at each intersection") +
  viridis::scale_color_viridis(option = "magma", direction = -1) +
  theme_minimal() + xlab("") + ylab("") +
  guides(color=guide_legend(title = ""),
         size = guide_legend(title = ""))

rlcv %>%
  dplyr::mutate(VIOLATION.DATE = mdy(VIOLATION.DATE)) %>% 
  filter(VIOLATION.DATE >= "2017-02-01" &
           VIOLATION.DATE < "2017-03-01") -> feb.violation
feb.violation %>% group_by(INTERSECTION) %>% 
  dplyr::summarize(sum_violations = sum(VIOLATIONS),
            n = n(),
            lat2 = round(mean(LATITUDE, na.rm = T), 4),
            lon2 = round(mean(LONGITUDE, na.rm = T), 4)) -> feb.vio.sum
qmplot(lon2, lat2, data = feb.vio.sum,
       zoom = 11, size = sum_violations,
       maptype = "toner-lite", color = sum_violations,
       main = "Sum of violations at each intersection in February 2017") +
  viridis::scale_color_viridis(option = "magma", direction = -1) +
  theme_minimal() + xlab("") + ylab("") +
  guides(color=guide_legend(title = ""),
         size = guide_legend(title = ""))

The charts provides different angles to the number of violations. But it’s very obvious from all three charts that the intersection of CICERO AND I55 is probably the most notorious red camera intersection in the Chicagoland.

Let’s try to build a model to see if we can predict daily violations at the intersection of our interest.

rlcv %>% filter(INTERSECTION == "CICERO AND I55") %>% 
  dplyr::mutate(VIOLATION.DATE = mdy(VIOLATION.DATE)) %>% 
  group_by(VIOLATION.DATE) %>% 
  dplyr::summarize(sum.violations = sum(VIOLATIONS)) %>% 
  arrange(VIOLATION.DATE) -> cicero.55
rlcv %>% filter(INTERSECTION == "LAKE SHORE DR AND BELMONT") %>% 
  dplyr::mutate(VIOLATION.DATE = mdy(VIOLATION.DATE)) %>% 
  group_by(VIOLATION.DATE) %>% 
  dplyr::summarize(sum.violations = sum(VIOLATIONS)) %>% 
  arrange(VIOLATION.DATE) -> lakeshore.belmont
cicero.55.xts <- xts(cicero.55[,-1], order.by=cicero.55$VIOLATION.DATE)
xts::plot.xts(cicero.55.xts)

lakeshore.belmont.xts <- xts(lakeshore.belmont[,-1],
                             order.by=lakeshore.belmont$VIOLATION.DATE)
xts::plot.xts(lakeshore.belmont.xts)

Now we realize the intersection CICERO AND I55 misses data from April to October in 2015. We decide to use data from Nov 2015 to end of 2016 to build a model and test it on 2017 data. There are many time series modeling approach out there. This time we will try a new method, namely prophet, published by Facebook recently.

cicero.55 %>%
  filter(VIOLATION.DATE > "2015/11/1",
         VIOLATION.DATE < "2017/1/1") %>%
  dplyr::rename(y = sum.violations, ds = VIOLATION.DATE) -> cicero.55.data
cicero.55 %>%
  filter(VIOLATION.DATE >= "2017/1/1") %>%
  dplyr::rename(y = sum.violations, ds = VIOLATION.DATE) -> cicero.55.test
fit.prophet <- prophet(cicero.55.data)
STAN OPTIMIZATION COMMAND (LBFGS)
init = user
save_iterations = 1
init_alpha = 0.001
tol_obj = 1e-12
tol_grad = 1e-08
tol_param = 1e-08
tol_rel_obj = 10000
tol_rel_grad = 1e+07
history_size = 5
seed = 159829875
initial log joint probability = -12.5338
Optimization terminated normally: 
  Convergence detected: relative gradient magnitude is below tolerance
#summary(fit.prophet)
future <- make_future_dataframe(fit.prophet, periods = 68)
fcst <- predict(fit.prophet, future)
plot(fit.prophet, fcst) +
  geom_line(data = cicero.55.data, aes(x = ds, y = y), alpha = 0.25) +
  geom_point(data = cicero.55.test, aes(x = ds, y = y), color = "orange") +
  xlab("Date")

prophet_plot_components(fit.prophet, fcst)

fcst %>% tbl_df() %>% 
  filter(ds >= "2017/1/1") %>% 
  select(ds, yhat) -> fcst.test
inner_join(cicero.55.test, fcst.test, by = "ds") -> cicero.55.test.acc
ggplot(cicero.55.test.acc, aes(x = ds)) +
  geom_point(aes(y = y)) +
  geom_line(aes(y = yhat), color = "blue") +
  xlab("Date") + ylab("Out of sample actuals and predicted")

(cicero.55.test.acc$y - cicero.55.test.acc$yhat) %>%
  abs() %>% mean()
[1] 21.23464

We can see that the model recognizes a general trend but fails to capture the volatility of the violations from day to day. The mean absolute error in 2017 is about 21. We try the model on another intersection with a longer violation history.

lakeshore.belmont %>%
  filter(VIOLATION.DATE < "2017/1/1") %>%
  dplyr::rename(y = sum.violations, ds = VIOLATION.DATE) -> lakeshore.belmont.data
lakeshore.belmont %>%
  filter(VIOLATION.DATE >= "2017/1/1") %>%
  dplyr::rename(y = sum.violations, ds = VIOLATION.DATE) -> lakeshore.belmont.test
fit.prophet <- prophet(lakeshore.belmont.data)
STAN OPTIMIZATION COMMAND (LBFGS)
init = user
save_iterations = 1
init_alpha = 0.001
tol_obj = 1e-12
tol_grad = 1e-08
tol_param = 1e-08
tol_rel_obj = 10000
tol_rel_grad = 1e+07
history_size = 5
seed = 548628329
initial log joint probability = -27.6485
Optimization terminated normally: 
  Convergence detected: relative gradient magnitude is below tolerance
future <- make_future_dataframe(fit.prophet, periods = 68)
fcst <- predict(fit.prophet, future)
plot(fit.prophet, fcst) +
  geom_line(data = lakeshore.belmont.data, aes(x = ds, y = y), alpha = 0.25) +
  geom_point(data = lakeshore.belmont.test, aes(x = ds, y = y), color = "orange") +
  xlab("Date") + ylab("Violations")

prophet_plot_components(fit.prophet, fcst)

fcst %>% tbl_df() %>% 
  filter(ds >= "2017/1/1") %>% 
  select(ds, yhat) -> fcst.test
inner_join(lakeshore.belmont.test, fcst.test, by = "ds") -> lakeshore.belmont.test.acc
ggplot(lakeshore.belmont.test.acc, aes(x = ds)) +
  geom_point(aes(y = y)) +
  geom_line(aes(y = yhat), color = "blue") +
  xlab("Date") + ylab("Out of sample actuals and predicted")

This time the model seems to be able to fit well in sample. But it shows a upward bias in the 2017 out of sample predictions. There’s no doubt it’s no easy task to accurately predict the violations into the future. We will explore this topic in the next post.

In summary, what we have achieved in this post is to gather the data from City of Chicago data portal and draw the violations on a map. We also entertained the idea of building a predictive model to forecast violations into the future. Hope you find some of the content interesting or useful. Let us know if you have any thoughts.

LS0tCnRpdGxlOiAiRXhwbG9yaW5nIENoaWNhZ28gUmVkIExpZ2h0IENhbWVyYSBWaW9sYXRpb25zIgpvdXRwdXQ6IAogIGh0bWxfbm90ZWJvb2s6IAogICAgdGhlbWU6IHJlYWRhYmxlCmF1dGhvcjogWFlaCi0tLQoKQSBjb3VwbGUgb2YgZGF5cyBhZ28sIEkgcmVhZCBhYm91dCBbdGhpcyBuZXdzXShodHRwOi8vd3d3LmNoaWNhZ290cmlidW5lLmNvbS9uZXdzL3dhdGNoZG9nL3JlZGxpZ2h0L2N0LXJlZC1saWdodC1zdHVkeS0wMzIxLTIwMTcwMzIxLXN0b3J5Lmh0bWwpIHRoYXQgc2FpZCBDaWNhZ28gbW90b3Jpc3RzIHdpbGwgZ2V0IGEgbG9uZ2VyIGdyYWNlIHBlcmlvZCBiZWZvcmUgZ2V0dGluZyBoaXQgd2l0aCBhIFwkMTAwIHJlZCBsaWdodCBjYW1lcmEgdGlja2V0LiBDaGljYWdvIGV4dGVuZCB0aGUgImVuZm9yY2VtZW50IHRocmVzaG9sZCIgd2hlbiBjYXJzIGFyZSBnaXZlbiB0aWNrZXRzIGZyb20gMC4xIHNlY29uZHMgdG8gMC4zIHNlY29uZHMgYWZ0ZXIgdGhlIGxpZ2h0IHR1cm5zIHJlZC4gSXQncyBiZWxpZXZlZCB0aGUgY2hhbmdlIHdpbGwgY3V0IHRoZSBudW1iZXIgb2YgdGlja2V0cyBpc3N1ZWQgYnkgYWJvdXQgMjkgcGVyY2VudCBhbmQgcmVzdWx0IGluIGFuIGV4cGVjdGVkIHJldmVudWUgbG9zcyBvZiBcJDE3IG1pbGxpb24gdGhpcyB5ZWFyLgoKVGhpcyBpcyBncmVhdCBuZXdzLCBJIGd1ZXNzLCB0byBtYW55IGRyaXZlcnMgaW4gdGhlIGNpdHkuIFRvIHBlb3BsZSB3aG8gYXJlIGludGVyZXN0ZWQgaW4gZGF0YSBzY2llbmNlIHRoaXMgaXMgYWxzbyBhIGdyZWF0IG9wcG9ydHVuaXR5IHRvIGV4cGxvcmUgdGhlIGltcGFjdCBvZiBhIHBvbGljeSBjaGFuZ2UsIGJ5IHN0dWR5aW5nIHRoZSBwdWJsaWMgYXZhaWxhYmxlIGRhdGEuIEx1Y2tpbHksIGNpdHkgb2YgQ2hpY2FnbyBwdWJsaXNoZXMgKlJlZCBMaWdodCBDYW1lcmEgVmlvbGF0aW9ucyogZGF0YXNldCBhdCB0aGUgW0NpdHkgb2YgQ2hpY2FnbyBEYXRhIFBvcnRhbF0oaHR0cHM6Ly9kYXRhLmNpdHlvZmNoaWNhZ28ub3JnL1RyYW5zcG9ydGF0aW9uL1JlZC1MaWdodC1DYW1lcmEtVmlvbGF0aW9ucy9zcHF4LWpzMzcvZGF0YSkKCkluIHRoaXMgZmlyc3QgcGFydCBvZiB0aGUgc2VyaWVzLCB3ZSB3aWxsIGV4cGxvcmUgdGhlIGRhdGFzZXQgYW5kIHRyeSB0byBhbnN3ZXIgdGhlIGZvbGxvd2luZyBxdWVzdGlvbnMsCgoxLiBXaGVyZSBhcmUgdGhlIGNhbWVyYXM/CjIuIFdoaWNoIGludGVyc2VjdGlvbnMgaGF2ZSB0aGUgbW9zdCBvciB0aGUgZmV3ZXN0IHZpb2xhdGlvbnM/CjMuIENhbiB3ZSBidWlsZCBhIG1vZGVsIHRvIHByZWRpY3QgdGhlIG51bWJlciBvZiB2aW9sYXRpb25zPwoKSW4gdGhlIG5leHQgcG9zdCwgd2Ugd2lsbCB1c2UgYW4gdXBkYXRlZCBkYXRhc2V0IGFmdGVyIHRoZSBuZXcgcG9saWN5IGtpY2tzIGluIGZvciBhIGZldyB3ZWVrcyBhbG9uZyB3aXRoIG91ciBtb2RlbCB0byBleGFtaW5lIGlmIHRoZSBuZXcgcG9saWN5IGJyaW5ncyBkb3duIHRpY2tldCBpc3N1YW5jZS4KClRoZSB3aG9sZSBhbmFseXNpcyB3aWxsIGJlIGRvbmUgaW4gUiBub3RlYm9vay4gV2Ugc3RhcnQgYnkgbG9hZGluZyBhbGwgbmVjZXNzYXJ5IHBhY2thZ2VzLgoKYGBge3IgTG9hZCBQYWNrYWdlcywgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KbGlicmFyeShwbHlyKQpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KHRpYmJsZSkKbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KGdnbWFwKQpsaWJyYXJ5KGdndGhlbWVzKQpsaWJyYXJ5KHZpcmlkaXMpCmxpYnJhcnkobHVicmlkYXRlKQpsaWJyYXJ5KGZvcmVjYXN0KQpsaWJyYXJ5KHh0cykKbGlicmFyeShwcm9waGV0KQpgYGAKClRoZSBkYXRhc2V0IHdhcyBkb3dubG9hZGVkIGFzIG9mIE1hcmNoIDIyLiBXZSByZWFkIGluIHRoZSBkYXRhIHNhdmVkIGFzIGEgQ1NWIGZpbGUgYW5kIHNob3cgdGhlIGxhc3Qgc2l4IHJvd3Mgb2YgdGhlIGZpbGUuIFRoZSByb3dzIGFyZSBzb3J0ZWQgYnkgdmlvbGFpb24gZGF0ZS4gV2UgY2FuIHNlZSB0aGF0IHRoZSBsYXRlc3QgdmlvbGF0aW9uIHJlY29yZHMgYXJlIGZyb20gMDMvMDgvMjAxNy4gUmVtZW1iZXIgdGhhdCB0aGUgbmV3IHJ1bGUgZG9lcyBub3QgdGFrZSBlZmZlY3QgdW50aWwgTWFyY2ggMjAuIFNvIHRoZSBkYXRhIGluIHRoZSBmaWxlIHJlY29yZHMgdGhvc2UgdmlvbGF0aW9ucyAwLjEgc2Vjb25kIGZyb20gdGhlIGxpZ2h0IHR1cm5zIHJlZC4gQW5vdGhlciBxdWljayBsb29rIGF0IHRoZSBzdW1tYXJ5IHJldmVhbHMgdGhhdCB0aGVyZSdyZSBOQXMgaW4gdGhlIGNhbWVyYSBJRCBjb2x1bW4sIGNvb3JkaW5hdGVzIGNvbHVtbnMgYW5kIGxvY2F0aW9ucyBjb2x1bW5zLiBUaGUgQ2l0eSBvZiBDaGljYWdvIHB1Ymxpc2hlcyB0aGUgW2xpc3Qgb2YgY3VycmVudCBSZWQgTGlnaHQgQ2FtZXJhIGludGVyc2VjdGlvbnNdKGh0dHBzOi8vd3d3LmNpdHlvZmNoaWNhZ28ub3JnL2NvbnRlbnQvZGFtL2NpdHkvZGVwdHMvY2RvdC9SZWQlMjBMaWdodCUyMENhbWVyYXMvQWN0aXZlX1JMQ19JbnRlcnNlY3Rpb25zLnBkZikuIEJ5IGxvb2tpbmcgYXQgdGhlIGxpc3Qgd2Uga25vdyB0aGF0IGF0IGVhY2ggc2VjdGlvbiwgdXN1YWxseSB0aGVyZSBhcmUgbXVsdGlwbGUgY2FtZXJhcywgZmFjaW5nIGRpZmZlcmVudCBkaXJlY3Rpb25zLiBXZSB0YWtlIGEgc2ltcGxpZmllZCBhcHByb2FjaCBoZXJlIGFuZCBhZ2dyZWdhdGUgdGhlIHZpb2xhdGlvbnMgb2Ygc3VjaCBjYW1lcmFzIGF0IHRoZSBzYW1lIGludGVyc2VjdGlvbnMuIFdlIGNhcmUgYWJvdXQgdGhlIHZpb2xhdGlvbnMgYXQgdGhlIGludGVyc2VjdGlvbiBsZXZlbCBhbmQgd29uJ3QgZGlzdGluZ3Vpc2ggaWYgZWFzdC13ZXN0IHRyYWZmaWMgb3Igbm9ydGgtc291dGggdHJhZmZpYyBpcyBtb3JlIHZpb2xhdGlvbi1wcm9uZS4KCmBgYHtyIFJlYWQgdGhlIERhdGFzZXR9CnJsY3YgPC0gcmVhZC5jc3YoIlJlZF9MaWdodF9DYW1lcmFfVmlvbGF0aW9ucy5jc3YiKSAlPiUgdGJsX2RmKCkKYGBgCgpgYGB7ciBTdW1tYXJ5IFN0YXRpc3RpY3N9CnJsY3YgJT4lIHRhaWwoKSAlPiUgcHJpbnQoKQpybGN2ICU+JSBzdW1tYXJ5KCkKYGBgCgpBcyB3ZSBzZWUgYmVsb3csIHRoZSBtaXNzaW5nIGNhbWVyYSBJRHMgY29tcG9zaXRlIGEgc21hbGwgcG9ydGlvbiBvZiByZWNvcmRzIGluIHRoZSBkYXRhc2V0LiBBbmQgd2hlbiBhZ2dyZWdhdGluZyB0aGUgdmlvbGF0aW9ucyBhdCB0aGUgaW50ZXJzZWN0aW9uIGxldmVsLCB0aGUgbWlzc2luZyBJRHMgc2hvdWxkbid0IGFmZmVjdCBvdXIgYW5hbHlzaXMuCgpgYGB7ciBNaXNzaW5nIElEfQpybGN2ICU+JSBncm91cF9ieShJTlRFUlNFQ1RJT04sIENBTUVSQS5JRCkgJT4lIAogICNzZWxlY3QoSU5URVJTRUNUSU9OLCBDQU1FUkEuSUQsIFZJT0xBVElPTi5EQVRFLCBWSU9MQVRJT05TKSAlPiUgCiAgZHBseXI6OnN1bW1hcml6ZShTdW0uT2YuVmlvbGF0aW9ucyA9IHN1bShWSU9MQVRJT05TKSkKYGBgCgpXZSB3YW50IHRvIG1hcCB0aGUgcmVkIGxpZ2h0cyBvbnRvIGEgQ2hpY2FnbyBtYXAuIE1pc3NpbmcgbG9jYXRpb24gZGF0YSBjb3VsZCBiZSBhIGNoYWxsZW5nZSB0byB1cy4gTGV0J3Mgc2VlIGhvdyB3ZSBjYW4gZml4IHRoYXQuIFdlIG5vdGljZSB0aGF0IGV2ZXJ5IHJlY29yZCB3aXRoIG1pc3NpbmcgbG9jYXRpb25zIGhhcyBhIHZhbGlkIGludGVyc2VjdGlvbiB2YWx1ZS4gU2luY2Ugd2UgYW5hbHl6ZSBhdCB0aGUgaW50ZXJzZWN0aW9uIGxldmVsLCBhcyBsb25nIGFzIHdlIGhhdmUgdGhlIGxvY2F0aW9uIGZvciB0aGF0IGludGVyc2VjdGlvbiwgd2Ugd2lsbCB1c2UgdGhhdCB0byBmaWxsIGluIHRoZSBibGFua3MuIFdlIHN0aWxsIGhhdmUgdGhyZWUgaW50ZXJzZWN0aW9ucyB3aXRob3V0IGxvY2F0aW9uIGRhdGEuIFdlIHdpbGwgaWdub3JlIHRoZW0gaW4gdmlzdWFsaXphdGlvbiBmb3IgdGhlIG1vbWVudC4KCmBgYHtyfQppbnRlcnNlY3Rpb25fZ2VvIDwtIHJsY3YgJT4lIGdyb3VwX2J5KElOVEVSU0VDVElPTikgJT4lIAogIGRwbHlyOjpzdW1tYXJpemUoaW50ZXJzZWN0aW9uX2xhdCA9IHJvdW5kKG1lYW4oTEFUSVRVREUsIG5hLnJtID0gVCksIDMpLAogICAgICAgICAgICAgICAgICAgaW50ZXJzZWN0aW9uX2xvbiA9IHJvdW5kKG1lYW4oTE9OR0lUVURFLCBuYS5ybSA9IFQpLCAzKSkKaW50ZXJzZWN0aW9uX2dlbwpgYGAKCkxldCdzIGZpcnN0IHNlZSB3aGVyZSB0aGVzZSBjYW1lcmFzIGFyZS4gVGhlIG5ldHdvcmsgb2YgdGhlIHJlZCBsaWdodCBjYW1lcmFzIGlzIHByZXR0eSBtdWNoIGNvdmVyaW5nIHRoZSBjb3JlIG5laWdoYm9yaG9vZHMgb2YgQ2hpY2Fnby4gQnV0IHRoZXJlIGFyZW4ndCBtYW55IGNhbWVyYXMgaW4gdGhlIExvb3Agb3IgU291dGggTG9vcCBhcmVhLiAKCmBgYHtyLCBmaWcuaGVpZ2h0PTgsIGZpZy53aWR0aD04LCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpxbXBsb3QoaW50ZXJzZWN0aW9uX2xvbiwgaW50ZXJzZWN0aW9uX2xhdCwgZGF0YSA9IGludGVyc2VjdGlvbl9nZW8sCiAgICAgICB6b29tID0gMTEsIHNpemUgPSBJKDEuMjUpLCBjb2xvciA9IEkoInJlZCIpLAogICAgICAgbWFwdHlwZSA9ICJ0b25lci1saXRlIiwKICAgICAgIG1haW4gPSAiSW50ZXJzZWN0aW9ucyB3aXRoIFJlZCBMaWdodCBDYW1lcmFzIikgKwogIHRoZW1lX21pbmltYWwoKSArIHhsYWIoIiIpICsgeWxhYigiIikKYGBgCgpJdCB3b3VsZCBiZSBpbnRlcmVzdGluZyB0byBzZWUgd2hpY2ggaW50ZXJzZWN0aW9ucyBoYXZlIHNlZW4gdGhlIG1vc3QgdmlvbGF0aW9ucyBzaW5jZSB0aGUgaW5zdGFsbGF0aW9uIG9mIHRoZSBjYW1lcmFzLiBMQUtFIFNIT1JFIERSIEFORCBCRUxNT05UIGhhcyBzZWVuIHRoZSBtb3N0IHZpb2xhdGlvbnMuIENJQ0VSTyBBTkQgSTU1IGFuZCBWQU4gQlVSRU4gQU5EIFdFU1RFUk4gYXJlIG5vdCBmYXIgYXdheS4KCmBgYHtyLCB9CnJsY3YgJT4lIGdyb3VwX2J5KElOVEVSU0VDVElPTikgJT4lIAogIGRwbHlyOjpzdW1tYXJpemUoc3VtX3Zpb2xhdGlvbnMgPSBzdW0oVklPTEFUSU9OUyksCiAgICAgICAgICAgIG1lZF92aW9sYXRpb25zID0gbWVkaWFuKFZJT0xBVElPTlMpLAogICAgICAgICAgICBsYXQyID0gcm91bmQobWVhbihMQVRJVFVERSwgbmEucm0gPSBUKSwgNCksCiAgICAgICAgICAgIGxvbjIgPSByb3VuZChtZWFuKExPTkdJVFVERSwgbmEucm0gPSBUKSwgNCkpICU+JSAKICBhcnJhbmdlKC1zdW1fdmlvbGF0aW9ucykgLT4gaW50ZXJzZWN0aW9uX3Zpb2xhdGlvbnMKCmludGVyc2VjdGlvbl92aW9sYXRpb25zCmBgYAoKYGBge3IgU3VtIG9mIFZpb2xhdGlvbiBieSBJbnRlcnNlY3Rpb24sIGZpZy5oZWlnaHQ9OCwgZmlnLndpZHRoPTgsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CnFtcGxvdChsb24yLCBsYXQyLCBkYXRhID0gaW50ZXJzZWN0aW9uX3Zpb2xhdGlvbnMsCiAgICAgICB6b29tID0gMTEsIHNpemUgPSBzdW1fdmlvbGF0aW9ucywKICAgICAgIG1hcHR5cGUgPSAidG9uZXItbGl0ZSIsIGNvbG9yID0gc3VtX3Zpb2xhdGlvbnMsCiAgICAgICBtYWluID0gIlN1bSBvZiB2aW9sYXRpb25zIGF0IGVhY2ggaW50ZXJzZWN0aW9uIikgKwogIHZpcmlkaXM6OnNjYWxlX2NvbG9yX3ZpcmlkaXMob3B0aW9uID0gIm1hZ21hIiwgZGlyZWN0aW9uID0gLTEpICsKICB0aGVtZV9taW5pbWFsKCkgKyB4bGFiKCIiKSArIHlsYWIoIiIpICsKICBndWlkZXMoY29sb3I9Z3VpZGVfbGVnZW5kKHRpdGxlID0gIiIpLAogICAgICAgICBzaXplID0gZ3VpZGVfbGVnZW5kKHRpdGxlID0gIiIpKQpgYGAKCkJ1dCBieSBsb29raW5nIGF0IHRoZSB0b3RhbCBvZiB0aGUgdmlvbGF0aW9ucyBzaW5jZSB0aGUgc3RhcnQgb2YgdGhlIGRhdGEgbWF5IG5vdCBiZSBhbiBhY2N1cmF0ZSBkZXNjcmlwdGlvbi4gSXQncyBwb3NzaWJsZSB0aGF0IHNvbWUgcmVkIGxpZ2h0IGNhbWVyYXMgYXJlIG5ld2x5IGluc3RhbGxlZCBhbmQgaGFzbid0IGhhZCBhY2N1bXVsYXRlZCBtYW55IHZpb2xhdGlvbnMuIEhvd2V2ZXIgdGhlc2UgbmV3IGNhbWVyYXMgY291bGQgc2VuZCBvdXQgbWFueSB0aWNrZXRzIG9uIGEgZGFpbHkgYmFzaXMuIFRvIGFjY29tb2RhdGUgdGhhdCB0aG91Z2h0LCB3ZSBsb29rIGF0IHRoZSBtZWRpYW4gb2YgdGhlIHZpb2xhdGlvbnMgYXQgdGhlIGludGVyc2VjdGlvbnMsIHdoaWNoIHdvdWxkIGdpdmUgdXMgYSB2aWV3IG9mIHdoYXQgYSB0eXBpY2FsIGRheSBsb29rcyBsaWtlLiBBbmQgd2UgdGFrZSBhIHNuYXAgc2hvdCBvZiBGZWJydWFyeSAyMDE3IGRhdGEuIFRoaXMgc2hvdWxkIGdpdmUgdXMgYSBtb3N0IHJlY2VudCB2aWV3IG9mIHdoaWNoIGNhbWVyYXMgaGF2ZSBzZWVuIHRoZSBtb3N0IHZpb2xhdGlvbnMgaW4gYSBtb250aC4KCmBgYHtyIE1lZGlhbiBWaW9sYXRpb24gcGVyIGludGVyc2VjdGlvbiwgZmlnLmhlaWdodD04LCBmaWcud2lkdGg9OCwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KcW1wbG90KGxvbjIsIGxhdDIsIGRhdGEgPSBpbnRlcnNlY3Rpb25fdmlvbGF0aW9ucywKICAgICAgIHpvb20gPSAxMSwgc2l6ZSA9IG1lZF92aW9sYXRpb25zLAogICAgICAgbWFwdHlwZSA9ICJ0b25lci1saXRlIiwgY29sb3IgPSBtZWRfdmlvbGF0aW9ucywKICAgICAgIG1haW4gPSAiTWVkaWFuIG9mIHZpb2xhdGlvbnMgYXQgZWFjaCBpbnRlcnNlY3Rpb24iKSArCiAgdmlyaWRpczo6c2NhbGVfY29sb3JfdmlyaWRpcyhvcHRpb24gPSAibWFnbWEiLCBkaXJlY3Rpb24gPSAtMSkgKwogIHRoZW1lX21pbmltYWwoKSArIHhsYWIoIiIpICsgeWxhYigiIikgKwogIGd1aWRlcyhjb2xvcj1ndWlkZV9sZWdlbmQodGl0bGUgPSAiIiksCiAgICAgICAgIHNpemUgPSBndWlkZV9sZWdlbmQodGl0bGUgPSAiIikpCmBgYAoKYGBge3IgRmVicnVhcnkgVmlvbGF0aW9uIHBlciBpbnRlcnNlY3Rpb24sIGZpZy5oZWlnaHQ9OCwgZmlnLndpZHRoPTgsIG1lc3NhZ2U9VFJVRSwgd2FybmluZz1UUlVFfQpybGN2ICU+JQogIGRwbHlyOjptdXRhdGUoVklPTEFUSU9OLkRBVEUgPSBtZHkoVklPTEFUSU9OLkRBVEUpKSAlPiUgCiAgZmlsdGVyKFZJT0xBVElPTi5EQVRFID49ICIyMDE3LTAyLTAxIiAmCiAgICAgICAgICAgVklPTEFUSU9OLkRBVEUgPCAiMjAxNy0wMy0wMSIpIC0+IGZlYi52aW9sYXRpb24KYGBgCgpgYGB7ciwgZmlnLmhlaWdodD04LCBmaWcud2lkdGg9OCwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KZmViLnZpb2xhdGlvbiAlPiUgZ3JvdXBfYnkoSU5URVJTRUNUSU9OKSAlPiUgCiAgZHBseXI6OnN1bW1hcml6ZShzdW1fdmlvbGF0aW9ucyA9IHN1bShWSU9MQVRJT05TKSwKICAgICAgICAgICAgbiA9IG4oKSwKICAgICAgICAgICAgbGF0MiA9IHJvdW5kKG1lYW4oTEFUSVRVREUsIG5hLnJtID0gVCksIDQpLAogICAgICAgICAgICBsb24yID0gcm91bmQobWVhbihMT05HSVRVREUsIG5hLnJtID0gVCksIDQpKSAtPiBmZWIudmlvLnN1bQoKcW1wbG90KGxvbjIsIGxhdDIsIGRhdGEgPSBmZWIudmlvLnN1bSwKICAgICAgIHpvb20gPSAxMSwgc2l6ZSA9IHN1bV92aW9sYXRpb25zLAogICAgICAgbWFwdHlwZSA9ICJ0b25lci1saXRlIiwgY29sb3IgPSBzdW1fdmlvbGF0aW9ucywKICAgICAgIG1haW4gPSAiU3VtIG9mIHZpb2xhdGlvbnMgYXQgZWFjaCBpbnRlcnNlY3Rpb24gaW4gRmVicnVhcnkgMjAxNyIpICsKICB2aXJpZGlzOjpzY2FsZV9jb2xvcl92aXJpZGlzKG9wdGlvbiA9ICJtYWdtYSIsIGRpcmVjdGlvbiA9IC0xKSArCiAgdGhlbWVfbWluaW1hbCgpICsgeGxhYigiIikgKyB5bGFiKCIiKSArCiAgZ3VpZGVzKGNvbG9yPWd1aWRlX2xlZ2VuZCh0aXRsZSA9ICIiKSwKICAgICAgICAgc2l6ZSA9IGd1aWRlX2xlZ2VuZCh0aXRsZSA9ICIiKSkKYGBgCgpUaGUgY2hhcnRzIHByb3ZpZGVzIGRpZmZlcmVudCBhbmdsZXMgdG8gdGhlIG51bWJlciBvZiB2aW9sYXRpb25zLiBCdXQgaXQncyB2ZXJ5IG9idmlvdXMgZnJvbSBhbGwgdGhyZWUgY2hhcnRzIHRoYXQgdGhlIGludGVyc2VjdGlvbiBvZiBDSUNFUk8gQU5EIEk1NSBpcyBwcm9iYWJseSB0aGUgbW9zdCBub3RvcmlvdXMgcmVkIGNhbWVyYSBpbnRlcnNlY3Rpb24gaW4gdGhlIENoaWNhZ29sYW5kLgoKTGV0J3MgdHJ5IHRvIGJ1aWxkIGEgbW9kZWwgdG8gc2VlIGlmIHdlIGNhbiBwcmVkaWN0IGRhaWx5IHZpb2xhdGlvbnMgYXQgdGhlIGludGVyc2VjdGlvbiBvZiBvdXIgaW50ZXJlc3QuCgpgYGB7cn0KcmxjdiAlPiUgZmlsdGVyKElOVEVSU0VDVElPTiA9PSAiQ0lDRVJPIEFORCBJNTUiKSAlPiUgCiAgZHBseXI6Om11dGF0ZShWSU9MQVRJT04uREFURSA9IG1keShWSU9MQVRJT04uREFURSkpICU+JSAKICBncm91cF9ieShWSU9MQVRJT04uREFURSkgJT4lIAogIGRwbHlyOjpzdW1tYXJpemUoc3VtLnZpb2xhdGlvbnMgPSBzdW0oVklPTEFUSU9OUykpICU+JSAKICBhcnJhbmdlKFZJT0xBVElPTi5EQVRFKSAtPiBjaWNlcm8uNTUKCnJsY3YgJT4lIGZpbHRlcihJTlRFUlNFQ1RJT04gPT0gIkxBS0UgU0hPUkUgRFIgQU5EIEJFTE1PTlQiKSAlPiUgCiAgZHBseXI6Om11dGF0ZShWSU9MQVRJT04uREFURSA9IG1keShWSU9MQVRJT04uREFURSkpICU+JSAKICBncm91cF9ieShWSU9MQVRJT04uREFURSkgJT4lIAogIGRwbHlyOjpzdW1tYXJpemUoc3VtLnZpb2xhdGlvbnMgPSBzdW0oVklPTEFUSU9OUykpICU+JSAKICBhcnJhbmdlKFZJT0xBVElPTi5EQVRFKSAtPiBsYWtlc2hvcmUuYmVsbW9udApgYGAKCmBgYHtyfQpjaWNlcm8uNTUueHRzIDwtIHh0cyhjaWNlcm8uNTVbLC0xXSwgb3JkZXIuYnk9Y2ljZXJvLjU1JFZJT0xBVElPTi5EQVRFKQp4dHM6OnBsb3QueHRzKGNpY2Vyby41NS54dHMpCgpsYWtlc2hvcmUuYmVsbW9udC54dHMgPC0geHRzKGxha2VzaG9yZS5iZWxtb250WywtMV0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgb3JkZXIuYnk9bGFrZXNob3JlLmJlbG1vbnQkVklPTEFUSU9OLkRBVEUpCnh0czo6cGxvdC54dHMobGFrZXNob3JlLmJlbG1vbnQueHRzKQpgYGAKCk5vdyB3ZSByZWFsaXplIHRoZSBpbnRlcnNlY3Rpb24gQ0lDRVJPIEFORCBJNTUgbWlzc2VzIGRhdGEgZnJvbSBBcHJpbCB0byBPY3RvYmVyIGluIDIwMTUuIFdlIGRlY2lkZSB0byB1c2UgZGF0YSBmcm9tIE5vdiAyMDE1IHRvIGVuZCBvZiAyMDE2IHRvIGJ1aWxkIGEgbW9kZWwgYW5kIHRlc3QgaXQgb24gMjAxNyBkYXRhLiBUaGVyZSBhcmUgbWFueSB0aW1lIHNlcmllcyBtb2RlbGluZyBhcHByb2FjaCBvdXQgdGhlcmUuIFRoaXMgdGltZSB3ZSB3aWxsIHRyeSBhIG5ldyBtZXRob2QsIG5hbWVseSBwcm9waGV0LCBwdWJsaXNoZWQgYnkgRmFjZWJvb2sgcmVjZW50bHkuCgpgYGB7ciBDaWNlcm8gNTUgbW9kZWwsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmNpY2Vyby41NSAlPiUKICBmaWx0ZXIoVklPTEFUSU9OLkRBVEUgPiAiMjAxNS8xMS8xIiwKICAgICAgICAgVklPTEFUSU9OLkRBVEUgPCAiMjAxNy8xLzEiKSAlPiUKICBkcGx5cjo6cmVuYW1lKHkgPSBzdW0udmlvbGF0aW9ucywgZHMgPSBWSU9MQVRJT04uREFURSkgLT4gY2ljZXJvLjU1LmRhdGEKCmNpY2Vyby41NSAlPiUKICBmaWx0ZXIoVklPTEFUSU9OLkRBVEUgPj0gIjIwMTcvMS8xIikgJT4lCiAgZHBseXI6OnJlbmFtZSh5ID0gc3VtLnZpb2xhdGlvbnMsIGRzID0gVklPTEFUSU9OLkRBVEUpIC0+IGNpY2Vyby41NS50ZXN0CgpmaXQucHJvcGhldCA8LSBwcm9waGV0KGNpY2Vyby41NS5kYXRhKQojc3VtbWFyeShmaXQucHJvcGhldCkKCmZ1dHVyZSA8LSBtYWtlX2Z1dHVyZV9kYXRhZnJhbWUoZml0LnByb3BoZXQsIHBlcmlvZHMgPSA2OCkKZmNzdCA8LSBwcmVkaWN0KGZpdC5wcm9waGV0LCBmdXR1cmUpCnBsb3QoZml0LnByb3BoZXQsIGZjc3QpICsKICBnZW9tX2xpbmUoZGF0YSA9IGNpY2Vyby41NS5kYXRhLCBhZXMoeCA9IGRzLCB5ID0geSksIGFscGhhID0gMC4yNSkgKwogIGdlb21fcG9pbnQoZGF0YSA9IGNpY2Vyby41NS50ZXN0LCBhZXMoeCA9IGRzLCB5ID0geSksIGNvbG9yID0gIm9yYW5nZSIpICsKICB4bGFiKCJEYXRlIikKcHJvcGhldF9wbG90X2NvbXBvbmVudHMoZml0LnByb3BoZXQsIGZjc3QpCgpmY3N0ICU+JSB0YmxfZGYoKSAlPiUgCiAgZmlsdGVyKGRzID49ICIyMDE3LzEvMSIpICU+JSAKICBzZWxlY3QoZHMsIHloYXQpIC0+IGZjc3QudGVzdAoKaW5uZXJfam9pbihjaWNlcm8uNTUudGVzdCwgZmNzdC50ZXN0LCBieSA9ICJkcyIpIC0+IGNpY2Vyby41NS50ZXN0LmFjYwpnZ3Bsb3QoY2ljZXJvLjU1LnRlc3QuYWNjLCBhZXMoeCA9IGRzKSkgKwogIGdlb21fcG9pbnQoYWVzKHkgPSB5KSkgKwogIGdlb21fbGluZShhZXMoeSA9IHloYXQpLCBjb2xvciA9ICJibHVlIikgKwogIHhsYWIoIkRhdGUiKSArIHlsYWIoIk91dCBvZiBzYW1wbGUgYWN0dWFscyBhbmQgcHJlZGljdGVkIikKCihjaWNlcm8uNTUudGVzdC5hY2MkeSAtIGNpY2Vyby41NS50ZXN0LmFjYyR5aGF0KSAlPiUKICBhYnMoKSAlPiUgbWVhbigpCmBgYAoKV2UgY2FuIHNlZSB0aGF0IHRoZSBtb2RlbCByZWNvZ25pemVzIGEgZ2VuZXJhbCB0cmVuZCBidXQgZmFpbHMgdG8gY2FwdHVyZSB0aGUgdm9sYXRpbGl0eSBvZiB0aGUgdmlvbGF0aW9ucyBmcm9tIGRheSB0byBkYXkuIFRoZSBtZWFuIGFic29sdXRlIGVycm9yIGluIDIwMTcgaXMgYWJvdXQgMjEuIFdlIHRyeSB0aGUgbW9kZWwgb24gYW5vdGhlciBpbnRlcnNlY3Rpb24gd2l0aCBhIGxvbmdlciB2aW9sYXRpb24gaGlzdG9yeS4KCmBgYHtyfQpsYWtlc2hvcmUuYmVsbW9udCAlPiUKICBmaWx0ZXIoVklPTEFUSU9OLkRBVEUgPCAiMjAxNy8xLzEiKSAlPiUKICBkcGx5cjo6cmVuYW1lKHkgPSBzdW0udmlvbGF0aW9ucywgZHMgPSBWSU9MQVRJT04uREFURSkgLT4gbGFrZXNob3JlLmJlbG1vbnQuZGF0YQoKbGFrZXNob3JlLmJlbG1vbnQgJT4lCiAgZmlsdGVyKFZJT0xBVElPTi5EQVRFID49ICIyMDE3LzEvMSIpICU+JQogIGRwbHlyOjpyZW5hbWUoeSA9IHN1bS52aW9sYXRpb25zLCBkcyA9IFZJT0xBVElPTi5EQVRFKSAtPiBsYWtlc2hvcmUuYmVsbW9udC50ZXN0CgpmaXQucHJvcGhldCA8LSBwcm9waGV0KGxha2VzaG9yZS5iZWxtb250LmRhdGEpCgpmdXR1cmUgPC0gbWFrZV9mdXR1cmVfZGF0YWZyYW1lKGZpdC5wcm9waGV0LCBwZXJpb2RzID0gNjgpCmZjc3QgPC0gcHJlZGljdChmaXQucHJvcGhldCwgZnV0dXJlKQpwbG90KGZpdC5wcm9waGV0LCBmY3N0KSArCiAgZ2VvbV9saW5lKGRhdGEgPSBsYWtlc2hvcmUuYmVsbW9udC5kYXRhLCBhZXMoeCA9IGRzLCB5ID0geSksIGFscGhhID0gMC4yNSkgKwogIGdlb21fcG9pbnQoZGF0YSA9IGxha2VzaG9yZS5iZWxtb250LnRlc3QsIGFlcyh4ID0gZHMsIHkgPSB5KSwgY29sb3IgPSAib3JhbmdlIikgKwogIHhsYWIoIkRhdGUiKSArIHlsYWIoIlZpb2xhdGlvbnMiKQpwcm9waGV0X3Bsb3RfY29tcG9uZW50cyhmaXQucHJvcGhldCwgZmNzdCkKCmZjc3QgJT4lIHRibF9kZigpICU+JSAKICBmaWx0ZXIoZHMgPj0gIjIwMTcvMS8xIikgJT4lIAogIHNlbGVjdChkcywgeWhhdCkgLT4gZmNzdC50ZXN0Cgppbm5lcl9qb2luKGxha2VzaG9yZS5iZWxtb250LnRlc3QsIGZjc3QudGVzdCwgYnkgPSAiZHMiKSAtPiBsYWtlc2hvcmUuYmVsbW9udC50ZXN0LmFjYwpnZ3Bsb3QobGFrZXNob3JlLmJlbG1vbnQudGVzdC5hY2MsIGFlcyh4ID0gZHMpKSArCiAgZ2VvbV9wb2ludChhZXMoeSA9IHkpKSArCiAgZ2VvbV9saW5lKGFlcyh5ID0geWhhdCksIGNvbG9yID0gImJsdWUiKSArCiAgeGxhYigiRGF0ZSIpICsgeWxhYigiT3V0IG9mIHNhbXBsZSBhY3R1YWxzIGFuZCBwcmVkaWN0ZWQiKQoKYGBgCgpUaGlzIHRpbWUgdGhlIG1vZGVsIHNlZW1zIHRvIGJlIGFibGUgdG8gZml0IHdlbGwgaW4gc2FtcGxlLiBCdXQgaXQgc2hvd3MgYSB1cHdhcmQgYmlhcyBpbiB0aGUgMjAxNyBvdXQgb2Ygc2FtcGxlIHByZWRpY3Rpb25zLiBUaGVyZSdzIG5vIGRvdWJ0IGl0J3Mgbm8gZWFzeSB0YXNrIHRvIGFjY3VyYXRlbHkgcHJlZGljdCB0aGUgdmlvbGF0aW9ucyBpbnRvIHRoZSBmdXR1cmUuIFdlIHdpbGwgZXhwbG9yZSB0aGlzIHRvcGljIGluIHRoZSBuZXh0IHBvc3QuCgpJbiBzdW1tYXJ5LCB3aGF0IHdlIGhhdmUgYWNoaWV2ZWQgaW4gdGhpcyBwb3N0IGlzIHRvIGdhdGhlciB0aGUgZGF0YSBmcm9tIENpdHkgb2YgQ2hpY2FnbyBkYXRhIHBvcnRhbCBhbmQgZHJhdyB0aGUgdmlvbGF0aW9ucyBvbiBhIG1hcC4gV2UgYWxzbyBlbnRlcnRhaW5lZCB0aGUgaWRlYSBvZiBidWlsZGluZyBhIHByZWRpY3RpdmUgbW9kZWwgdG8gZm9yZWNhc3QgdmlvbGF0aW9ucyBpbnRvIHRoZSBmdXR1cmUuIEhvcGUgeW91IGZpbmQgc29tZSBvZiB0aGUgY29udGVudCBpbnRlcmVzdGluZyBvciB1c2VmdWwuIExldCB1cyBrbm93IGlmIHlvdSBoYXZlIGFueSB0aG91Z2h0cy4K