We are going apply some of the techniques we learned in Exploratory Data Analysis to study air pollution data, specifically particulate matter (we’ll call it pm25 sometimes), collected by the U.S. Environmental Protection Agency. This website https://www.health.ny.gov/environmental/indoors/air/pmq_a.htm from New York State offers some basic information on this topic if you’re interested.

Particulate matter (less than 2.5 microns in diameter) is a fancy name for dust, and breathing in dust might pose health hazards to the population. We’ll study data from two years, 1999 (when monitoring of particulate matter started) and 2012. Our goal is to see if there’s been a noticeable decline in this type of air pollution between these two years.

Reading in the data

There are 2 large files to read in using the R command read_delim. We’ll store the 1999 data in the array pm0 and let R print the tibble to see its dimensions.

library(tidyverse)
package 㤼㸱tidyverse㤼㸲 was built under R version 3.6.3Registered S3 method overwritten by 'dplyr':
  method           from
  print.rowwise_df     
Registered S3 methods overwritten by 'dbplyr':
  method         from
  print.tbl_lazy     
  print.tbl_sql      
-- Attaching packages --------------------------------------- tidyverse 1.3.0 --
v ggplot2 3.2.1     v purrr   0.3.3
v tibble  2.1.3     v dplyr   0.8.3
v tidyr   1.0.0     v stringr 1.4.0
v readr   1.3.1     v forcats 0.4.0
-- Conflicts ------------------------------------------ tidyverse_conflicts() --
x dplyr::filter() masks stats::filter()
x dplyr::lag()    masks stats::lag()
pm0 <- read_delim("RD_501_88101_1999-0.txt", comment = "#", col_names = FALSE, delim = "|", na = "")
Parsed with column specification:
cols(
  .default = col_logical(),
  X1 = col_character(),
  X2 = col_character(),
  X3 = col_character(),
  X4 = col_character(),
  X5 = col_character(),
  X6 = col_double(),
  X7 = col_double(),
  X8 = col_double(),
  X9 = col_double(),
  X10 = col_double(),
  X11 = col_double(),
  X12 = col_time(format = ""),
  X13 = col_double(),
  X14 = col_character(),
  X15 = col_double(),
  X17 = col_character()
)
See spec(...) for full column specifications.
pm0

We see that pm0 has over 117,000 lines, each containing 5 columns. In the original file, at the EPA website, each row had 28 columns, but since we’re only using a few of these, we’ve created and read in a somewhat smaller file.

We can also see there’s some missing data, but we won’t worry about that now. The column names, X1, X2, etc., are not informative. However, we know that the first line of the original file (a comment) explained what information the columns contained.

We’ll use read_lines to read in the first line of the original file and use the functions in stringr to parse out the 28 column names of the original file.

nm<-read_lines("RD_501_88101_1999-0.txt",n_max=1)
cnm<-str_split(nm,"\\|",simplify = FALSE)[[1]]
cnm<-str_replace_all(cnm," - ","")
cnm<-str_replace_all(cnm,"# ","")
cnm<-str_replace_all(cnm," ","_")
cnm<-str_replace_all(cnm,"\\(","")
cnm<-str_replace_all(cnm,"\\)","")
cnm
 [1] "RD"                                "Action_Code"                      
 [3] "State_Code"                        "County_Code"                      
 [5] "Site_ID"                           "Parameter"                        
 [7] "POC"                               "Sample_Duration"                  
 [9] "Unit"                              "Method"                           
[11] "Date"                              "Start_Time"                       
[13] "Sample_Value"                      "Null_Data_Code"                   
[15] "Sampling_Frequency"                "Monitor_Protocol_MP_ID"           
[17] "Qualifier1"                        "Qualifier2"                       
[19] "Qualifier3"                        "Qualifier4"                       
[21] "Qualifier5"                        "Qualifier6"                       
[23] "Qualifier7"                        "Qualifier8"                       
[25] "Qualifier9"                        "Qualifier10"                      
[27] "Alternate_Method_Detectable_Limit" "Uncertainty"                      

The 28 column names are all jumbled together even though they were separated by | characters. We fixed this by reassigning the column names to the output of a call to str_split (string split) with 3 arguments. The first is cnm, the pipe symbol ‘|’ is the second (use the quotation marks), and the third is the argument simplify set to FALSE.

The variable cnm now holds a list of the column headings which is nice, but we don’t need all these. We really only need State_Code, County_Code,Site_ID, Date, and Sample_Value which are X3, X4, X5, X11, and X13, respectivly. We’ll set the names of the data set to the values of our names vector, cnm, and select those variables for our the pm0 data set.

names(pm0)<-cnm
pm0 <- select(pm0,State_Code,County_Code,Site_ID,Date,Sample_Value)
pm0

Now it’s much more clear what information each column of pm0 holds. The measurements of particulate matter (pm25) are in the column named Sample_Value.

Sample_Value itself can be treated as a numeric vector (of length 117000+) with at least the first 3 values missing. Exactly what percentage of values are missing in this vector? We’ll se the R function mean with is.na() as an argument to see what percentage of values are missing (NA).

summarize(pm0,missing=mean(is.na(Sample_Value)))

So a little over 11% of the 1.1710^{5}+ are missing. We’ll keep that in mind. Now let’s start processing the 2012 data which we stored for you in the array pm1.

We’ll repeat what we did for pm0, except a little more efficiently. First read in the file and assign column names. Then we’ll print the tibble to check the dimensions.

pm1 <- read_delim("RD_501_88101_2012-0.txt", comment = "#", col_names = FALSE, delim = "|", na = "",n_max=1000000)
Parsed with column specification:
cols(
  .default = col_logical(),
  X1 = col_character(),
  X2 = col_character(),
  X3 = col_character(),
  X4 = col_character(),
  X5 = col_character(),
  X6 = col_double(),
  X7 = col_double(),
  X8 = col_double(),
  X9 = col_double(),
  X10 = col_double(),
  X11 = col_double(),
  X12 = col_time(format = ""),
  X13 = col_double(),
  X14 = col_character(),
  X15 = col_double(),
  X17 = col_character()
)
See spec(...) for full column specifications.
20268 parsing failures.
   row col           expected actual                      file
  1082 X18 1/0/T/F/TRUE/FALSE      5 'RD_501_88101_2012-0.txt'
  1091 X18 1/0/T/F/TRUE/FALSE      5 'RD_501_88101_2012-0.txt'
353841 X27 1/0/T/F/TRUE/FALSE      2 'RD_501_88101_2012-0.txt'
353842 X27 1/0/T/F/TRUE/FALSE      2 'RD_501_88101_2012-0.txt'
353843 X27 1/0/T/F/TRUE/FALSE      2 'RD_501_88101_2012-0.txt'
...... ... .................. ...... .........................
See problems(...) for more details.
names(pm1)<-cnm
pm1 <- select(pm1,State_Code,County_Code,Site_ID,Date,Sample_Value)
pm1

Wow! nrow(pm1) rows! Particulate matter was first collected in 1999 so perhaps there weren’t as many sensors collecting data then as in 2012 when the program was more mature.

Now let’s see what percentage of values are missing in measurements of particulate matter (pm25) for 2012. As before, use the R function mean with is.na() as an argument to find out.

summarize(pm1,missing=mean(is.na(Sample_Value)))

So only `round(mean(is.na(pm1$Sample_Value)*100,1)% of the particulate matter measurements are missing. That’s about half the percentage as in 1999.

Now let’s look at summaries (using the summary command) for both datasets.

summary(pm0$Sample_Value)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max.    NA's 
   0.00    7.20   11.50   13.74   17.90  157.10   13217 
summary(pm1$Sample_Value)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max.    NA's 
 -10.00    4.20    7.90    9.25   12.00  893.70   52608 

The numbers in the vectors pm0$Sample_Value and x1pm1$Sample_Value` represent measurements taken in micrograms per cubic meter.

We see that both the median and the mean of measured particulate matter have declined from 1999 to 2012. In fact, all of the measurements, except for the maximum and missing values (Max and NA’s), have decreased. Even the Min has gone down from NA to NA! We’ll address what a negative measurment might mean a little later. Note that the Max has increased from NA in 1999 to NA in 2012. This is quite high and might reflect an error in the table or malfunctions in some monitors.

Making some exploratory plots

To make this data a bit easier to work with, I’m going to put them together. I’ll create a factor variable called Year to distinguish the measurements in 1999 from 2012. Then I’ll use the bind_rows function to sandwhich these two data frames together.

pm25<-bind_rows(pm0,pm1)
pm25<-mutate(pm25,Year=as_factor(str_sub(Date,1,4)))
pm25

Let’s make a boxplot function with 2 arguments, x0 and x1.

library(plotly)
package 㤼㸱plotly㤼㸲 was built under R version 3.6.3Registered S3 method overwritten by 'data.table':
  method           from
  print.data.table     
Registered S3 methods overwritten by 'htmltools':
  method               from         
  print.html           tools:rstudio
  print.shiny.tag      tools:rstudio
  print.shiny.tag.list tools:rstudio
Registered S3 method overwritten by 'htmlwidgets':
  method           from         
  print.htmlwidget tools:rstudio

Attaching package: 㤼㸱plotly㤼㸲

The following object is masked from 㤼㸱package:ggplot2㤼㸲:

    last_plot

The following object is masked from 㤼㸱package:stats㤼㸲:

    filter

The following object is masked from 㤼㸱package:graphics㤼㸲:

    layout
p<-ggplot(pm25, aes(x = Year, y = Sample_Value)) + 
  geom_boxplot(notch = TRUE) +
  stat_summary(fun.y=mean,geom="point",shape=23,size=4) # add mean points
ggplotly(p)
Removed 65825 rows containing non-finite values (stat_boxplot).Removed 65825 rows containing non-finite values (stat_summary).

Huh? Did somebody step on the boxes? It’s hard to see what’s going on here. There are so many values outside the boxes and the range of particulate matter (pm25) for 2012 is so big that the boxes are flattened. It might be more informative to call boxplot on the logs (base 2).

p<-ggplot(pm25, aes(x = Year, y = log2(Sample_Value))) + 
  geom_boxplot(notch = TRUE) +
  stat_summary(fun.y=mean,geom="point",shape=23,size=4) # add mean points
ggplotly(p)
NaNs producedNaNs producedNaNs producedRemoved 109290 rows containing non-finite values (stat_boxplot).Removed 109290 rows containing non-finite values (stat_summary).

A bonus! Not only do we get a better looking boxplot we also get some warnings from R in Red. These let us know that some values in particulate matter (pm25) for both years were “unloggable”, no doubt the 0 (Min) we saw in the summary of particulate matter (pm25) in 1999 and the negative values we saw in the Min of the summary of particulate matter (pm25) for 2012.

From the boxplot, what can you say about the data? 1: The boxes are too small to interpret 2: The range of particulate matter (pm25) in 1999 is greater than the range of particulate matter (pm25) in 2012 3: The median of particulate matter (pm25) in 2012 is less than the median of particulate matter (pm25) in 1999 4: The mean of particulate matter (pm25) in 2012 is less than the mean of particulate matter (pm25) in 1999

Let’s return to the question of the negative values in particulate matter (pm25) for 2012. Let’s count how many negative values there are. We’ll do this in a few steps.

First, group the data by year. Then, form the vector negative by assigning to it the boolean Sample_Value<0. Now run the R command summarize. We can use the sum function and set the na.rm argument equal to TRUE. This tells sum to ignore the missing values in our logic.

pmyr<-group_by(pm25,Year)
summarize(pmyr,count_neg=sum(Sample_Value<0,na.rm=TRUE))

So there are over 210^{4} negative values. Sounds like a lot. Is it? Let’s run the same R command using mean to calculate a percentage missing.

summarize(pmyr,pneg=mean(Sample_Value<0,na.rm=TRUE))

We see that just 2.2% of the particulate matter (pm25) for 2012 values are negative. Perhaps that’s a small enough percentage that we can ignore them. Before we ignore them, though, let’s see if they occur during certain times of the year.

But Date is a very long set of integers, and the format of the entries is hard to read. There’s no separation between the year, month, and day. Let’s reassign the output of a call to ymd.

library(lubridate)

Attaching package: 㤼㸱lubridate㤼㸲

The following object is masked from 㤼㸱package:base㤼㸲:

    date
pm25<-mutate(pm25,Date=ymd(Date))
pm25

Now Date is in a nicer format.

Let’s plot the months when the particulate matter measurements are negative.

pm_2k12_neg<-filter(pm25,Year=='2012',Sample_Value<0)
p<-ggplot(pm_2k12_neg, aes(x=as_factor(month(Date)))) +
  geom_bar(fill="#bcbddc") +
  xlab("Month")
ggplotly(p)

We see the bulk of the negative measurements were taken in the winter months, with a spike in May. Not many of these negative measurements occurred in summer months. We can take a guess that because particulate measures tend to be low in winter and high in summer, coupled with the fact that higher densities are easier to measure, that measurement errors occurred when the values were low. For now we’ll attribute these negative measurements to errors. Also, since they account for only 2% of the 2012 data, we’ll ignore them.

New York State

Now we’ll change focus a bit and instead of looking at all the monitors throughout the country and the data they recorded, we’ll try to find one monitor that was taking measurements in both 1999 and 2012. This will allow us to control for different geographical and environmental variables that might have affected air quality in different areas. We’ll narrow our search and look just at monitors in New York State.

First we’ll subset off the New York monitor identification data for 1999 and 2012 into 2 vectors, site0 and site1. Look at the structure of site0 now with the R command str.

pm25<-mutate(pm25,Site=as_factor(str_c(County_Code,Site_ID)))
site1999<-filter(pm25,State_Code=='36',Year=='1999')
site2012<-filter(pm25,State_Code=='36',Year=='2012')
site1999
site2012

We see that the IDs of monitors in New York State in 1999 is a set of strings, each of which has the form CCCSSSS. We created these from the county codes (the C portion of the string) and the monitor IDs (the S portion). We can use the intersect command with to examine which sites have measurements in both years.

both <- dplyr::intersect(site1999$Site, site2012$Site)
both
 [1] "0010005" "0010012" "0050080" "0130011" "0290005" "0310003" "0632008" "0671015"
 [9] "0850055" "1010003"

We see that 10 monitors in New York State were active in both 1999 and 2012.

Now let’s see how many measurements each of the 10 New York monitors that were active in both 1999 and 2012 took in those years. We’ll create 2 subsets (one for each year), one of pm0 and the other of pm1.

The subsets will filter for 2 characteristics. The first is State.Code equal to 36 (the code for New York), and the second is that the county.site (the component we added) is in the vector both.

First create the variable cnt0 by assigning to it the output of the R command subset, called with 2 arguments. The first is pm0, and the second is a boolean with the 2 conditions we just mentioned. Recall that the testing for equality in a boolean requires ==, intersection of 2 boolean conditions is denoted by & and membership by %in%.

Take a look at both now.

site<-bind_rows(site1999, site2012)
site <- filter(site, Site %in% both)
site

Now run the split and map trick we saw in the Iteration lecture. This will split Site into several data frames according to county.site (that is, monitor IDs) and tell us how many measurements each monitor recorded.

site %>%
  group_by(Year,Site) %>%
  summarize(count=n(),
            avg_pm25=mean(Sample_Value,na.rm=TRUE))

From the output above, which monitor is the only one whose number of measurements increased from 1999 to 2012? 1: 0632008 2: 1010003 3: 0290005 4: 0850055 5: None

We want to examine a monitor with a reasonable number of measurements so let’s look at the monitor with ID 0632008. Create a variable pm0sub which is the subset of cnt0 (this contains just New York data) which has County.Code equal to 63 and Site.ID 2008.

site.sub <- filter(site, Site=='0632008')
site.sub

From the output of the 2 calls to sapply, how many rows will site.sub have for 1999? 1: 183 2: 29 3: 122 4: 30

Now we’d like to compare the pm25 measurements of this particular monitor (0632008) for the 2 years. First, create the vector x0sub by assigning to it the Sample.Value component of pm0sub.

g<-g<-ggplot(site.sub,aes(Year,Sample_Value)) +
  geom_violin() +
  stat_summary(fun.y=median, geom="crossbar", size=2, color="#a6bddb")
ggplotly(g)
Removed 35 rows containing non-finite values (stat_ydensity).Removed 35 rows containing non-finite values (stat_summary).

Which median is larger - the one for 1999 or the one for 2012? 1: 2012 2: 1999

We’ve created a violin plot showing the PM 2.5 values side by side with the same range of values on the y axis. The improvement in the medians between 1999 and 2012 is now clear. Also notice that in 2012 there are no big values (above 15). This shows that not only is there a chronic improvement in air quality, but also there are fewer days with severe pollution.

The last avenue of this data we’ll explore (and we’ll do it quickly) concerns a comparison of all the states’ mean pollution levels. This is important because the states are responsible for implementing the regulations set at the federal level by the EPA.

Let’s first gather the mean (average measurement) for each state in both years.

by_state<-pm25 %>%
  group_by(State_Code,Year) %>%
  summarise(avgpm25=mean(Sample_Value,na.rm=TRUE)) %>%
  pivot_wider(names_from = Year, values_from = avgpm25) %>%
  mutate(increase=`1999`<`2012`)
by_state

Each row of mrg has 4 entries - a state identified by number, a state mean for 1999, a state mean for 2012, and an indicator for whether or not the state saw an increase in PM 2.5 levels between 1999 and 2012.

Now we’ll plot the data to see how the state means changed between the 2 years. I’m going to use a parallel coordinate plot using ggally

library(GGally)
package 㤼㸱GGally㤼㸲 was built under R version 3.6.3Registered S3 method overwritten by 'GGally':
  method from   
  +.gg   ggplot2

Attaching package: 㤼㸱GGally㤼㸲

The following object is masked from 㤼㸱package:dplyr㤼㸲:

    nasa
p <- ggparcoord(data = by_state, columns = 2:3,groupColumn =4,showPoints = TRUE,title ="4 States had increased PM 2.5 levels between 1999 and 2012",scale="globalminmax")+
  scale_colour_manual(values = c('#bdbdbd','#de2d26')) + 
  theme(legend.position = "none") +
  ylab("PM 2.5 concentration") +
  xlab("Year")
ggplotly(p)

We see a column of points at 1999 which represent the 1999 state means. We see a shorter column of points at 2012. We see from the plot that the vast majority of states have indeed improved their particulate matter counts so the general trend is downward. There are a few exceptions. (The topmost point in the 1999 column is actually two points that had very close measurements.)

For fun, let’s see which states had higher means in 2012 than in 1999. Just use the mrg[mrg\(mean.x < mrg\)mean.y, ] notation to find the rows of mrg with this particulate property.

filter(by_state,increase)

Only 4 states had worse pollution averages, and 2 of these had means that were very close. If you want to see which states (15, 31, 35, and 40) these are, you can check out this website https://www.epa.gov/enviro/state-fips-code-listing to decode the state codes.

This concludes our comparison of air pollution data from two years in different ways. First, we looked at measures of the entire set of monitors, then we compared the two measures from a particular monitor, and finally, we looked at the mean measures of the individual states.

Congratulations! We hope you enjoyed this particulate lesson.

LS0tDQp0aXRsZTogIkVQQSBDYXNlIFN0dWR5Ig0Kb3V0cHV0OiANCiAgaHRtbF9ub3RlYm9vazoNCiAgICB0b2M6IHRydWUNCiAgICB0b2NfZmxvYXQ6IHRydWUNCi0tLQ0KDQpXZSBhcmUgZ29pbmcgYXBwbHkgc29tZSBvZiB0aGUgdGVjaG5pcXVlcyB3ZSBsZWFybmVkIGluIEV4cGxvcmF0b3J5IERhdGEgQW5hbHlzaXMgdG8gc3R1ZHkgYWlyIHBvbGx1dGlvbiBkYXRhLCBzcGVjaWZpY2FsbHkgcGFydGljdWxhdGUgbWF0dGVyICh3ZSdsbCBjYWxsIGl0IHBtMjUgc29tZXRpbWVzKSwgY29sbGVjdGVkIGJ5IHRoZSBVLlMuIEVudmlyb25tZW50YWwgUHJvdGVjdGlvbiBBZ2VuY3kuIFRoaXMgd2Vic2l0ZSA8aHR0cHM6Ly93d3cuaGVhbHRoLm55Lmdvdi9lbnZpcm9ubWVudGFsL2luZG9vcnMvYWlyL3BtcV9hLmh0bT4gZnJvbSBOZXcgWW9yayBTdGF0ZSBvZmZlcnMgc29tZSBiYXNpYyBpbmZvcm1hdGlvbiBvbiB0aGlzIHRvcGljIGlmIHlvdSdyZSBpbnRlcmVzdGVkLg0KDQpQYXJ0aWN1bGF0ZSBtYXR0ZXIgKGxlc3MgdGhhbiAyLjUgbWljcm9ucyBpbiBkaWFtZXRlcikgaXMgYSBmYW5jeSBuYW1lIGZvciBkdXN0LCBhbmQgYnJlYXRoaW5nIGluIGR1c3QgbWlnaHQgcG9zZSBoZWFsdGggaGF6YXJkcyB0byB0aGUgcG9wdWxhdGlvbi4gV2UnbGwgc3R1ZHkgZGF0YSBmcm9tIHR3byB5ZWFycywgMTk5OSAod2hlbiBtb25pdG9yaW5nIG9mIHBhcnRpY3VsYXRlIG1hdHRlciBzdGFydGVkKSBhbmQgMjAxMi4gT3VyIGdvYWwgaXMgdG8gc2VlIGlmIHRoZXJlJ3MgYmVlbiBhIG5vdGljZWFibGUgZGVjbGluZSBpbiB0aGlzIHR5cGUgb2YgYWlyIHBvbGx1dGlvbiBiZXR3ZWVuIHRoZXNlIHR3byB5ZWFycy4NCg0KIyMgUmVhZGluZyBpbiB0aGUgZGF0YQ0KVGhlcmUgYXJlIDIgbGFyZ2UgZmlsZXMgdG8gcmVhZCBpbiB1c2luZyB0aGUgYFJgIGNvbW1hbmQgYHJlYWRfZGVsaW1gLiAgV2UnbGwgc3RvcmUgdGhlIDE5OTkgZGF0YSBpbiB0aGUgYXJyYXkgYHBtMGAgYW5kIGxldCBgUmAgcHJpbnQgdGhlIHRpYmJsZSB0byBzZWUgaXRzIGRpbWVuc2lvbnMuDQoNCmBgYHtyfQ0KbGlicmFyeSh0aWR5dmVyc2UpDQpwbTAgPC0gcmVhZF9kZWxpbSgiUkRfNTAxXzg4MTAxXzE5OTktMC50eHQiLCBjb21tZW50ID0gIiMiLCBjb2xfbmFtZXMgPSBGQUxTRSwgZGVsaW0gPSAifCIsIG5hID0gIiIpDQpwbTANCmBgYA0KDQpXZSBzZWUgdGhhdCBgcG0wYCBoYXMgb3ZlciAxMTcsMDAwIGxpbmVzLCBlYWNoIGNvbnRhaW5pbmcgNSBjb2x1bW5zLiBJbiB0aGUgb3JpZ2luYWwgZmlsZSwgYXQgdGhlIEVQQSB3ZWJzaXRlLCBlYWNoIHJvdyBoYWQgMjggY29sdW1ucywgYnV0IHNpbmNlIHdlJ3JlIG9ubHkgdXNpbmcgYSBmZXcgb2YgdGhlc2UsIHdlJ3ZlIGNyZWF0ZWQgYW5kIHJlYWQgaW4gYSBzb21ld2hhdCBzbWFsbGVyIGZpbGUuIA0KDQpXZSBjYW4gYWxzbyBzZWUgdGhlcmUncyBzb21lIG1pc3NpbmcgZGF0YSwgYnV0IHdlIHdvbid0IHdvcnJ5IGFib3V0IHRoYXQgbm93LiBUaGUgY29sdW1uIG5hbWVzLCBYMSwgWDIsIGV0Yy4sIGFyZSBub3QgaW5mb3JtYXRpdmUuIEhvd2V2ZXIsIHdlIGtub3cgdGhhdCB0aGUgZmlyc3QgbGluZSBvZiB0aGUgb3JpZ2luYWwgZmlsZSAoYSBjb21tZW50KSBleHBsYWluZWQgd2hhdCBpbmZvcm1hdGlvbiB0aGUgY29sdW1ucyBjb250YWluZWQuIA0KDQpXZSdsbCB1c2UgYHJlYWRfbGluZXNgIHRvIHJlYWQgaW4gdGhlIGZpcnN0IGxpbmUgb2YgdGhlIG9yaWdpbmFsIGZpbGUgYW5kIHVzZSB0aGUgZnVuY3Rpb25zIGluIGBzdHJpbmdyYCB0byBwYXJzZSBvdXQgdGhlIDI4IGNvbHVtbiBuYW1lcyBvZiB0aGUgb3JpZ2luYWwgZmlsZS4NCg0KYGBge3J9DQpubTwtcmVhZF9saW5lcygiUkRfNTAxXzg4MTAxXzE5OTktMC50eHQiLG5fbWF4PTEpDQpjbm08LXN0cl9zcGxpdChubSwiXFx8IixzaW1wbGlmeSA9IEZBTFNFKVtbMV1dDQpjbm08LXN0cl9yZXBsYWNlX2FsbChjbm0sIiAtICIsIiIpDQpjbm08LXN0cl9yZXBsYWNlX2FsbChjbm0sIiMgIiwiIikNCmNubTwtc3RyX3JlcGxhY2VfYWxsKGNubSwiICIsIl8iKQ0KY25tPC1zdHJfcmVwbGFjZV9hbGwoY25tLCJcXCgiLCIiKQ0KY25tPC1zdHJfcmVwbGFjZV9hbGwoY25tLCJcXCkiLCIiKQ0KY25tDQpgYGANCg0KVGhlIDI4IGNvbHVtbiBuYW1lcyBhcmUgYWxsIGp1bWJsZWQgdG9nZXRoZXIgZXZlbiB0aG91Z2ggdGhleSB3ZXJlIHNlcGFyYXRlZCBieSBgfGAgY2hhcmFjdGVycy4gV2UgZml4ZWQgdGhpcyBieSByZWFzc2lnbmluZyB0aGUgY29sdW1uIG5hbWVzIHRvIHRoZSBvdXRwdXQgb2YgYSBjYWxsIHRvIGBzdHJfc3BsaXRgIChzdHJpbmcgc3BsaXQpIHdpdGggMyBhcmd1bWVudHMuIFRoZSBmaXJzdCBpcyBjbm0sIHRoZSBwaXBlIHN5bWJvbCAnfCcgaXMgdGhlIHNlY29uZCAodXNlIHRoZSBxdW90YXRpb24gbWFya3MpLCBhbmQgdGhlIHRoaXJkIGlzIHRoZSBhcmd1bWVudCBgc2ltcGxpZnlgIHNldCB0byBgRkFMU0VgLiANCg0KVGhlIHZhcmlhYmxlIGBjbm1gIG5vdyBob2xkcyBhIGxpc3Qgb2YgdGhlIGNvbHVtbiBoZWFkaW5ncyB3aGljaCBpcyBuaWNlLCBidXQgd2UgZG9uJ3QgbmVlZCBhbGwgdGhlc2UuIFdlIHJlYWxseSBvbmx5IG5lZWQgYFN0YXRlX0NvZGVgLCBgQ291bnR5X0NvZGVgLGBTaXRlX0lEYCwgYERhdGVgLCBhbmQgYFNhbXBsZV9WYWx1ZWAgd2hpY2ggYXJlICBgWDNgLCBgWDRgLCBgWDVgLCBgWDExYCwgYW5kIGBYMTNgLCByZXNwZWN0aXZseS4gV2UnbGwgc2V0IHRoZSBuYW1lcyBvZiB0aGUgZGF0YSBzZXQgdG8gdGhlIHZhbHVlcyBvZiBvdXIgbmFtZXMgdmVjdG9yLCBgY25tYCwgYW5kIGBzZWxlY3RgIHRob3NlIHZhcmlhYmxlcyBmb3Igb3VyIHRoZSBgcG0wYCBkYXRhIHNldC4gDQoNCmBgYHtyfQ0KbmFtZXMocG0wKTwtY25tDQpwbTAgPC0gc2VsZWN0KHBtMCxTdGF0ZV9Db2RlLENvdW50eV9Db2RlLFNpdGVfSUQsRGF0ZSxTYW1wbGVfVmFsdWUpDQpwbTANCmBgYA0KDQpOb3cgaXQncyBtdWNoIG1vcmUgY2xlYXIgd2hhdCBpbmZvcm1hdGlvbiBlYWNoIGNvbHVtbiBvZiBwbTAgaG9sZHMuIFRoZSBtZWFzdXJlbWVudHMgb2YgcGFydGljdWxhdGUgbWF0dGVyIChwbTI1KSBhcmUgaW4gdGhlIGNvbHVtbiBuYW1lZCBgU2FtcGxlX1ZhbHVlYC4gDQoNCmBTYW1wbGVfVmFsdWVgIGl0c2VsZiBjYW4gYmUgdHJlYXRlZCBhcyBhIG51bWVyaWMgdmVjdG9yIChvZiBsZW5ndGggMTE3MDAwKykgd2l0aCBhdCBsZWFzdCB0aGUgZmlyc3QgMyB2YWx1ZXMgbWlzc2luZy4gIEV4YWN0bHkgd2hhdCBwZXJjZW50YWdlIG9mIHZhbHVlcyBhcmUgbWlzc2luZyBpbiB0aGlzIHZlY3Rvcj8gV2UnbGwgc2UgdGhlIGBSYCBmdW5jdGlvbiBgbWVhbmAgd2l0aCBgaXMubmEoKWAgYXMgYW4gYXJndW1lbnQgdG8gc2VlIHdoYXQgcGVyY2VudGFnZSBvZiB2YWx1ZXMgYXJlIG1pc3NpbmcgKGBOQWApLg0KDQpgYGB7cn0NCnN1bW1hcml6ZShwbTAsbWlzc2luZz1tZWFuKGlzLm5hKFNhbXBsZV9WYWx1ZSkpKQ0KYGBgDQoNClNvIGEgbGl0dGxlIG92ZXIgYHIgcm91bmQobWVhbihpcy5uYShwbTAkU2FtcGxlX1ZhbHVlKSkqMTAwLDApYCUgb2YgdGhlIGByIHNpZ25pZihucm93KHBtMCksMylgKyBhcmUgbWlzc2luZy4gV2UnbGwga2VlcCB0aGF0IGluIG1pbmQuIE5vdyBsZXQncyBzdGFydCBwcm9jZXNzaW5nIHRoZSAyMDEyIGRhdGEgd2hpY2ggd2Ugc3RvcmVkIGZvciB5b3UgaW4gdGhlIGFycmF5IHBtMS4NCg0KV2UnbGwgcmVwZWF0IHdoYXQgd2UgZGlkIGZvciBgcG0wYCwgZXhjZXB0IGEgbGl0dGxlIG1vcmUgZWZmaWNpZW50bHkuIEZpcnN0IHJlYWQgaW4gdGhlIGZpbGUgYW5kIGFzc2lnbiBjb2x1bW4gbmFtZXMuIFRoZW4gd2UnbGwgcHJpbnQgdGhlIHRpYmJsZSB0byBjaGVjayB0aGUgZGltZW5zaW9ucy4NCg0KYGBge3J9DQpwbTEgPC0gcmVhZF9kZWxpbSgiUkRfNTAxXzg4MTAxXzIwMTItMC50eHQiLCBjb21tZW50ID0gIiMiLCBjb2xfbmFtZXMgPSBGQUxTRSwgZGVsaW0gPSAifCIsIG5hID0gIiIsbl9tYXg9MTAwMDAwMCkNCm5hbWVzKHBtMSk8LWNubQ0KcG0xIDwtIHNlbGVjdChwbTEsU3RhdGVfQ29kZSxDb3VudHlfQ29kZSxTaXRlX0lELERhdGUsU2FtcGxlX1ZhbHVlKQ0KcG0xDQpgYGANCg0KV293ISBgbnJvdyhwbTEpYCByb3dzISBQYXJ0aWN1bGF0ZSBtYXR0ZXIgd2FzIGZpcnN0IGNvbGxlY3RlZCBpbiAxOTk5IHNvIHBlcmhhcHMgdGhlcmUgd2VyZW4ndCBhcyBtYW55IHNlbnNvcnMgY29sbGVjdGluZyBkYXRhIHRoZW4gYXMgaW4gMjAxMiB3aGVuIHRoZSBwcm9ncmFtIHdhcyBtb3JlIG1hdHVyZS4NCg0KTm93IGxldCdzIHNlZSB3aGF0IHBlcmNlbnRhZ2Ugb2YgdmFsdWVzIGFyZSBtaXNzaW5nIGluIG1lYXN1cmVtZW50cyBvZiBwYXJ0aWN1bGF0ZSBtYXR0ZXIgKHBtMjUpIGZvciAyMDEyLiBBcyBiZWZvcmUsIHVzZSB0aGUgYFJgIGZ1bmN0aW9uIGBtZWFuYCB3aXRoIGBpcy5uYSgpYCBhcyBhbiBhcmd1bWVudCB0byBmaW5kIG91dC4NCg0KYGBge3J9DQpzdW1tYXJpemUocG0xLG1pc3Npbmc9bWVhbihpcy5uYShTYW1wbGVfVmFsdWUpKSkNCmBgYA0KDQpTbyBvbmx5IGByb3VuZChtZWFuKGlzLm5hKHBtMSRTYW1wbGVfVmFsdWUpKjEwMCwxKSUgb2YgdGhlIHBhcnRpY3VsYXRlIG1hdHRlciBtZWFzdXJlbWVudHMgYXJlIG1pc3NpbmcuIFRoYXQncyBhYm91dCBoYWxmIHRoZSBwZXJjZW50YWdlIGFzIGluIDE5OTkuDQoNCk5vdyBsZXQncyBsb29rIGF0IHN1bW1hcmllcyAodXNpbmcgdGhlIHN1bW1hcnkgY29tbWFuZCkgZm9yIGJvdGggZGF0YXNldHMuDQoNCmBgYHtyfQ0Kc3VtbWFyeShwbTAkU2FtcGxlX1ZhbHVlKQ0Kc3VtbWFyeShwbTEkU2FtcGxlX1ZhbHVlKQ0KYGBgDQoNClRoZSBudW1iZXJzIGluIHRoZSB2ZWN0b3JzIGBwbTAkU2FtcGxlX1ZhbHVlYCBhbmQgYHgxYHBtMSRTYW1wbGVfVmFsdWVgIHJlcHJlc2VudCBtZWFzdXJlbWVudHMgdGFrZW4gaW4gbWljcm9ncmFtcyBwZXIgY3ViaWMgbWV0ZXIuIA0KDQpXZSBzZWUgdGhhdCBib3RoIHRoZSBtZWRpYW4gYW5kIHRoZSBtZWFuIG9mIG1lYXN1cmVkIHBhcnRpY3VsYXRlIG1hdHRlciBoYXZlIGRlY2xpbmVkIGZyb20gMTk5OSB0byAyMDEyLiBJbiBmYWN0LCBhbGwgb2YgdGhlIG1lYXN1cmVtZW50cywgZXhjZXB0IGZvciB0aGUgbWF4aW11bSBhbmQgbWlzc2luZyB2YWx1ZXMgKE1heCBhbmQgTkEncyksIGhhdmUgZGVjcmVhc2VkLiBFdmVuIHRoZSBNaW4gaGFzIGdvbmUgZG93biBmcm9tIGByIG1pbihwbTAkU2FtcGxlX1ZhbHVlKWAgdG8gYHIgbWluKHBtMSRTYW1wbGVfVmFsdWUpYCEgV2UnbGwgYWRkcmVzcyB3aGF0IGEgbmVnYXRpdmUgbWVhc3VybWVudCBtaWdodCBtZWFuIGEgbGl0dGxlIGxhdGVyLiBOb3RlIHRoYXQgdGhlIE1heCBoYXMgaW5jcmVhc2VkIGZyb20gYHIgbWF4KHBtMCRTYW1wbGVfVmFsdWUpYCBpbiAxOTk5IHRvIGByIG1heChwbTEkU2FtcGxlX1ZhbHVlKWAgaW4gMjAxMi4gVGhpcyBpcyBxdWl0ZSBoaWdoIGFuZCBtaWdodCByZWZsZWN0IGFuIGVycm9yIGluIHRoZSB0YWJsZSBvciBtYWxmdW5jdGlvbnMgaW4gc29tZSBtb25pdG9ycy4NCg0KIyMgTWFraW5nIHNvbWUgZXhwbG9yYXRvcnkgcGxvdHMNClRvIG1ha2UgdGhpcyBkYXRhIGEgYml0IGVhc2llciB0byB3b3JrIHdpdGgsIEknbSBnb2luZyB0byBwdXQgdGhlbSB0b2dldGhlci4gSSdsbCBjcmVhdGUgYSBmYWN0b3IgdmFyaWFibGUgY2FsbGVkIFllYXIgdG8gZGlzdGluZ3Vpc2ggdGhlIG1lYXN1cmVtZW50cyBpbiAxOTk5IGZyb20gMjAxMi4gVGhlbiBJJ2xsIHVzZSB0aGUgYmluZF9yb3dzIGZ1bmN0aW9uIHRvIHNhbmR3aGljaCB0aGVzZSB0d28gZGF0YSBmcmFtZXMgdG9nZXRoZXIuDQoNCmBgYHtyfQ0KcG0yNTwtYmluZF9yb3dzKHBtMCxwbTEpDQpwbTI1PC1tdXRhdGUocG0yNSxZZWFyPWFzX2ZhY3RvcihzdHJfc3ViKERhdGUsMSw0KSkpDQpwbTI1DQpgYGANCg0KTGV0J3MgbWFrZSBhIGJveHBsb3QgZnVuY3Rpb24gd2l0aCAyIGFyZ3VtZW50cywgeDAgYW5kIHgxLg0KDQpgYGB7cn0NCmxpYnJhcnkocGxvdGx5KQ0KcDwtZ2dwbG90KHBtMjUsIGFlcyh4ID0gWWVhciwgeSA9IFNhbXBsZV9WYWx1ZSkpICsgDQogIGdlb21fYm94cGxvdChub3RjaCA9IFRSVUUpICsNCiAgc3RhdF9zdW1tYXJ5KGZ1bi55PW1lYW4sZ2VvbT0icG9pbnQiLHNoYXBlPTIzLHNpemU9NCkgIyBhZGQgbWVhbiBwb2ludHMNCmdncGxvdGx5KHApDQpgYGANCg0KSHVoPyBEaWQgc29tZWJvZHkgc3RlcCBvbiB0aGUgYm94ZXM/IEl0J3MgaGFyZCB0byBzZWUgd2hhdCdzIGdvaW5nIG9uIGhlcmUuIFRoZXJlIGFyZSBzbw0KbWFueSB2YWx1ZXMgb3V0c2lkZSB0aGUgYm94ZXMgYW5kIHRoZSByYW5nZSBvZiBwYXJ0aWN1bGF0ZSBtYXR0ZXIgKHBtMjUpIGZvciAyMDEyIGlzIHNvIGJpZyB0aGF0IHRoZSBib3hlcyBhcmUgZmxhdHRlbmVkLiBJdCBtaWdodCBiZSBtb3JlIGluZm9ybWF0aXZlIHRvIGNhbGwgYm94cGxvdCBvbiB0aGUgbG9ncyAoYmFzZSAyKS4gDQoNCmBgYHtyfQ0KcDwtZ2dwbG90KHBtMjUsIGFlcyh4ID0gWWVhciwgeSA9IGxvZzIoU2FtcGxlX1ZhbHVlKSkpICsgDQogIGdlb21fYm94cGxvdChub3RjaCA9IFRSVUUpICsNCiAgc3RhdF9zdW1tYXJ5KGZ1bi55PW1lYW4sZ2VvbT0icG9pbnQiLHNoYXBlPTIzLHNpemU9NCkgIyBhZGQgbWVhbiBwb2ludHMNCmdncGxvdGx5KHApDQpgYGANCg0KQSBib251cyEgTm90IG9ubHkgZG8gd2UgZ2V0IGEgYmV0dGVyIGxvb2tpbmcgYm94cGxvdCB3ZSBhbHNvIGdldCBzb21lIHdhcm5pbmdzIGZyb20gUiBpbiBSZWQuIFRoZXNlIGxldCB1cyBrbm93IHRoYXQgc29tZSB2YWx1ZXMgaW4gcGFydGljdWxhdGUgbWF0dGVyIChwbTI1KSBmb3IgYm90aCB5ZWFycyB3ZXJlICJ1bmxvZ2dhYmxlIiwgbm8gZG91YnQgdGhlIDAgKE1pbikgd2Ugc2F3IGluIHRoZSBzdW1tYXJ5IG9mIHBhcnRpY3VsYXRlIG1hdHRlciAocG0yNSkgaW4gMTk5OSBhbmQgdGhlIG5lZ2F0aXZlIHZhbHVlcyB3ZSBzYXcgaW4gdGhlIE1pbiBvZiB0aGUgc3VtbWFyeSBvZiBwYXJ0aWN1bGF0ZSBtYXR0ZXIgKHBtMjUpIGZvciAyMDEyLg0KDQpGcm9tIHRoZSBib3hwbG90LCB3aGF0IGNhbiB5b3Ugc2F5IGFib3V0IHRoZSBkYXRhPw0KMTogVGhlIGJveGVzIGFyZSB0b28gc21hbGwgdG8gaW50ZXJwcmV0DQoyOiBUaGUgcmFuZ2Ugb2YgcGFydGljdWxhdGUgbWF0dGVyIChwbTI1KSBpbiAxOTk5IGlzIGdyZWF0ZXIgdGhhbiB0aGUgcmFuZ2Ugb2YgcGFydGljdWxhdGUgbWF0dGVyIChwbTI1KSBpbiAyMDEyDQozOiBUaGUgbWVkaWFuIG9mIHBhcnRpY3VsYXRlIG1hdHRlciAocG0yNSkgaW4gMjAxMiBpcyBsZXNzIHRoYW4gdGhlIG1lZGlhbiBvZiBwYXJ0aWN1bGF0ZSBtYXR0ZXIgKHBtMjUpIGluIDE5OTkNCjQ6IFRoZSBtZWFuIG9mIHBhcnRpY3VsYXRlIG1hdHRlciAocG0yNSkgaW4gMjAxMiBpcyBsZXNzIHRoYW4gdGhlIG1lYW4gb2YgcGFydGljdWxhdGUgbWF0dGVyIChwbTI1KSBpbiAxOTk5DQoNCmBgYHtyLGluY2x1ZGU9RkFMU0V9DQojIEFuczogMw0KYGBgDQoNCkxldCdzIHJldHVybiB0byB0aGUgcXVlc3Rpb24gb2YgdGhlIG5lZ2F0aXZlIHZhbHVlcyBpbiBwYXJ0aWN1bGF0ZSBtYXR0ZXIgKHBtMjUpIGZvciAyMDEyLiBMZXQncyBjb3VudCBob3cgbWFueSBuZWdhdGl2ZSB2YWx1ZXMgdGhlcmUgYXJlLiBXZSdsbCBkbyB0aGlzIGluIGEgZmV3IHN0ZXBzLg0KDQpGaXJzdCwgZ3JvdXAgdGhlIGRhdGEgYnkgeWVhci4gVGhlbiwgZm9ybSB0aGUgdmVjdG9yIG5lZ2F0aXZlIGJ5IGFzc2lnbmluZyB0byBpdCB0aGUgYm9vbGVhbiBgU2FtcGxlX1ZhbHVlPDBgLiBOb3cgcnVuIHRoZSBgUmAgY29tbWFuZCBgc3VtbWFyaXplYC4gV2UgY2FuIHVzZSB0aGUgYHN1bWAgZnVuY3Rpb24gYW5kIHNldCB0aGUgYG5hLnJtYCBhcmd1bWVudCBlcXVhbCB0byBgVFJVRWAuIFRoaXMgdGVsbHMgYHN1bWAgdG8gaWdub3JlIHRoZSBtaXNzaW5nIHZhbHVlcyBpbiBvdXIgbG9naWMuDQoNCmBgYHtyfQ0KcG15cjwtZ3JvdXBfYnkocG0yNSxZZWFyKQ0Kc3VtbWFyaXplKHBteXIsY291bnRfbmVnPXN1bShTYW1wbGVfVmFsdWU8MCxuYS5ybT1UUlVFKSkNCmBgYA0KDQpTbyB0aGVyZSBhcmUgb3ZlciBgciByb3VuZChzdW0ocG0xJFNhbXBsZV9WYWx1ZTwwLG5hLnJtPVRSVUUpLC0zKWAgbmVnYXRpdmUgdmFsdWVzLiBTb3VuZHMgbGlrZSBhIGxvdC4gSXMgaXQ/IExldCdzIHJ1biB0aGUgc2FtZSBgUmAgY29tbWFuZCB1c2luZyBgbWVhbmAgdG8gY2FsY3VsYXRlIGEgcGVyY2VudGFnZSBtaXNzaW5nLg0KDQpgYGB7cn0NCnN1bW1hcml6ZShwbXlyLHBuZWc9bWVhbihTYW1wbGVfVmFsdWU8MCxuYS5ybT1UUlVFKSkNCmBgYA0KDQpXZSBzZWUgdGhhdCBqdXN0IGByIHJvdW5kKG1lYW4ocG0xJFNhbXBsZV9WYWx1ZTwwLG5hLnJtPVRSVUUpKjEwMCwxKWAlIG9mIHRoZSBwYXJ0aWN1bGF0ZSBtYXR0ZXIgKHBtMjUpIGZvciAyMDEyIHZhbHVlcyBhcmUgbmVnYXRpdmUuIFBlcmhhcHMgdGhhdCdzIGEgc21hbGwgZW5vdWdoIHBlcmNlbnRhZ2UgdGhhdCB3ZSBjYW4gaWdub3JlIHRoZW0uIEJlZm9yZSB3ZSBpZ25vcmUgdGhlbSwgdGhvdWdoLCBsZXQncyBzZWUgaWYgdGhleSBvY2N1ciBkdXJpbmcgY2VydGFpbiB0aW1lcyBvZiB0aGUgeWVhci4NCg0KQnV0IGBEYXRlYCBpcyBhIHZlcnkgbG9uZyBzZXQgb2YgaW50ZWdlcnMsIGFuZCB0aGUgZm9ybWF0IG9mIHRoZSBlbnRyaWVzIGlzIGhhcmQgdG8gcmVhZC4gVGhlcmUncyBubyBzZXBhcmF0aW9uIGJldHdlZW4gdGhlIHllYXIsIG1vbnRoLCBhbmQgZGF5LiBMZXQncyByZWFzc2lnbiB0aGUgb3V0cHV0IG9mIGEgY2FsbCB0byBgeW1kYC4NCg0KYGBge3J9DQpsaWJyYXJ5KGx1YnJpZGF0ZSkNCnBtMjU8LW11dGF0ZShwbTI1LERhdGU9eW1kKERhdGUpKQ0KcG0yNQ0KYGBgDQoNCk5vdyBgRGF0ZWAgaXMgaW4gYSBuaWNlciBmb3JtYXQuIA0KDQpMZXQncyBwbG90IHRoZSBtb250aHMgd2hlbiB0aGUgcGFydGljdWxhdGUgbWF0dGVyIG1lYXN1cmVtZW50cyBhcmUgbmVnYXRpdmUuDQoNCmBgYHtyfQ0KcG1fMmsxMl9uZWc8LWZpbHRlcihwbTI1LFllYXI9PScyMDEyJyxTYW1wbGVfVmFsdWU8MCkNCnA8LWdncGxvdChwbV8yazEyX25lZywgYWVzKHg9YXNfZmFjdG9yKG1vbnRoKERhdGUpKSkpICsNCiAgZ2VvbV9iYXIoZmlsbD0iI2JjYmRkYyIpICsNCiAgeGxhYigiTW9udGgiKQ0KZ2dwbG90bHkocCkNCmBgYA0KDQpXZSBzZWUgdGhlIGJ1bGsgb2YgdGhlIG5lZ2F0aXZlIG1lYXN1cmVtZW50cyB3ZXJlIHRha2VuIGluIHRoZSB3aW50ZXIgbW9udGhzLCB3aXRoIGEgc3Bpa2UgaW4gTWF5LiBOb3QgbWFueSBvZiB0aGVzZSBuZWdhdGl2ZSBtZWFzdXJlbWVudHMgb2NjdXJyZWQgaW4gc3VtbWVyIG1vbnRocy4gV2UgY2FuIHRha2UgYSBndWVzcyB0aGF0IGJlY2F1c2UgcGFydGljdWxhdGUgbWVhc3VyZXMgdGVuZCB0byBiZSBsb3cgaW4gd2ludGVyIGFuZCBoaWdoIGluIHN1bW1lciwgY291cGxlZCB3aXRoIHRoZSBmYWN0IHRoYXQgaGlnaGVyIGRlbnNpdGllcyBhcmUgZWFzaWVyIHRvIG1lYXN1cmUsIHRoYXQgbWVhc3VyZW1lbnQgZXJyb3JzIG9jY3VycmVkIHdoZW4gdGhlIHZhbHVlcyB3ZXJlIGxvdy4gRm9yIG5vdyB3ZSdsbCBhdHRyaWJ1dGUgdGhlc2UgbmVnYXRpdmUgbWVhc3VyZW1lbnRzIHRvIGVycm9ycy4gQWxzbywgc2luY2UgdGhleSBhY2NvdW50IGZvciBvbmx5IDIlIG9mIHRoZSAyMDEyIGRhdGEsIHdlJ2xsIGlnbm9yZSB0aGVtLg0KDQojIyMgTmV3IFlvcmsgU3RhdGUNCk5vdyB3ZSdsbCBjaGFuZ2UgZm9jdXMgYSBiaXQgYW5kIGluc3RlYWQgb2YgbG9va2luZyBhdCBhbGwgdGhlIG1vbml0b3JzIHRocm91Z2hvdXQgdGhlIGNvdW50cnkgYW5kIHRoZSBkYXRhIHRoZXkgcmVjb3JkZWQsIHdlJ2xsIHRyeSB0byBmaW5kIG9uZSBtb25pdG9yIHRoYXQgd2FzIHRha2luZyBtZWFzdXJlbWVudHMgaW4gYm90aCAxOTk5IGFuZCAyMDEyLiBUaGlzIHdpbGwgYWxsb3cgdXMgdG8gY29udHJvbCBmb3IgZGlmZmVyZW50IGdlb2dyYXBoaWNhbCBhbmQgZW52aXJvbm1lbnRhbCB2YXJpYWJsZXMgdGhhdCBtaWdodCBoYXZlIGFmZmVjdGVkIGFpciBxdWFsaXR5IGluIGRpZmZlcmVudCBhcmVhcy4gV2UnbGwgbmFycm93IG91ciBzZWFyY2ggYW5kIGxvb2sganVzdCBhdCBtb25pdG9ycyBpbiBOZXcgWW9yayBTdGF0ZS4NCg0KRmlyc3Qgd2UnbGwgc3Vic2V0IG9mZiB0aGUgTmV3IFlvcmsgbW9uaXRvciBpZGVudGlmaWNhdGlvbiBkYXRhIGZvciAxOTk5IGFuZCAyMDEyIGludG8gMiB2ZWN0b3JzLCBzaXRlMCBhbmQgc2l0ZTEuIExvb2sgYXQgdGhlIHN0cnVjdHVyZSBvZiBzaXRlMCBub3cgd2l0aCB0aGUgUiBjb21tYW5kIHN0ci4NCg0KYGBge3J9DQpwbTI1PC1tdXRhdGUocG0yNSxTaXRlPWFzX2ZhY3RvcihzdHJfYyhDb3VudHlfQ29kZSxTaXRlX0lEKSkpDQpzaXRlMTk5OTwtZmlsdGVyKHBtMjUsU3RhdGVfQ29kZT09JzM2JyxZZWFyPT0nMTk5OScpDQpzaXRlMjAxMjwtZmlsdGVyKHBtMjUsU3RhdGVfQ29kZT09JzM2JyxZZWFyPT0nMjAxMicpDQpzaXRlMTk5OQ0Kc2l0ZTIwMTINCmBgYA0KDQpXZSBzZWUgdGhhdCB0aGUgSURzIG9mIG1vbml0b3JzIGluIE5ldyBZb3JrIFN0YXRlIGluIDE5OTkgaXMgYSBzZXQgb2Ygc3RyaW5ncywgZWFjaCBvZiB3aGljaCBoYXMgdGhlIGZvcm0gYENDQ1NTU1NgLiBXZSBjcmVhdGVkIHRoZXNlIGZyb20gdGhlIGNvdW50eSBjb2RlcyAodGhlICpDKiBwb3J0aW9uIG9mIHRoZSBzdHJpbmcpIGFuZCB0aGUgbW9uaXRvciBJRHMgKHRoZSAqUyogcG9ydGlvbikuIFdlIGNhbiB1c2UgdGhlIGludGVyc2VjdCBjb21tYW5kIHdpdGggdG8gZXhhbWluZSB3aGljaCBzaXRlcyBoYXZlIG1lYXN1cmVtZW50cyBpbiBib3RoIHllYXJzLg0KDQpgYGB7cn0NCmJvdGggPC0gZHBseXI6OmludGVyc2VjdChzaXRlMTk5OSRTaXRlLCBzaXRlMjAxMiRTaXRlKQ0KYm90aA0KYGBgDQoNCldlIHNlZSB0aGF0IGByIGxlbmd0aChib3RoKWAgbW9uaXRvcnMgaW4gTmV3IFlvcmsgU3RhdGUgd2VyZSBhY3RpdmUgaW4gYm90aCAxOTk5IGFuZCAyMDEyLg0KDQpOb3cgbGV0J3Mgc2VlIGhvdyBtYW55IG1lYXN1cmVtZW50cyBlYWNoIG9mIHRoZSBgciBsZW5ndGgoYm90aClgIE5ldyBZb3JrIG1vbml0b3JzIHRoYXQgd2VyZSBhY3RpdmUgaW4gYm90aCAxOTk5IGFuZCAyMDEyIHRvb2sgaW4gdGhvc2UgeWVhcnMuIFdlJ2xsIGNyZWF0ZSAyIHN1YnNldHMgKG9uZSBmb3IgZWFjaCB5ZWFyKSwgb25lIG9mIHBtMCBhbmQgdGhlIG90aGVyIG9mIHBtMS4NCg0KVGhlIHN1YnNldHMgd2lsbCBmaWx0ZXIgZm9yIDIgY2hhcmFjdGVyaXN0aWNzLiBUaGUgZmlyc3QgaXMgU3RhdGUuQ29kZSBlcXVhbCB0byAzNiAodGhlIGNvZGUgZm9yIE5ldyBZb3JrKSwgYW5kIHRoZSBzZWNvbmQgaXMgdGhhdCB0aGUgY291bnR5LnNpdGUgKHRoZSBjb21wb25lbnQgd2UgYWRkZWQpIGlzIGluIHRoZSB2ZWN0b3IgYm90aC4NCg0KRmlyc3QgY3JlYXRlIHRoZSB2YXJpYWJsZSBjbnQwIGJ5IGFzc2lnbmluZyB0byBpdCB0aGUgb3V0cHV0IG9mIHRoZSBSIGNvbW1hbmQgc3Vic2V0LCBjYWxsZWQgd2l0aCAyIGFyZ3VtZW50cy4gVGhlIGZpcnN0IGlzIHBtMCwgYW5kIHRoZSBzZWNvbmQgaXMgYSBib29sZWFuIHdpdGggdGhlIDIgY29uZGl0aW9ucyB3ZSBqdXN0IG1lbnRpb25lZC4gUmVjYWxsIHRoYXQgdGhlIHRlc3RpbmcgZm9yIGVxdWFsaXR5IGluIGEgYm9vbGVhbiByZXF1aXJlcyA9PSwgaW50ZXJzZWN0aW9uIG9mIDIgYm9vbGVhbiBjb25kaXRpb25zIGlzIGRlbm90ZWQgYnkgJiBhbmQgbWVtYmVyc2hpcCBieSAlaW4lLg0KDQpUYWtlIGEgbG9vayBhdCBib3RoIG5vdy4NCg0KYGBge3J9DQpzaXRlPC1iaW5kX3Jvd3Moc2l0ZTE5OTksIHNpdGUyMDEyKQ0Kc2l0ZSA8LSBmaWx0ZXIoc2l0ZSwgU2l0ZSAlaW4lIGJvdGgpDQpzaXRlDQpgYGANCg0KTm93IHJ1biB0aGUgYHNwbGl0YCBhbmQgYG1hcGAgdHJpY2sgd2Ugc2F3IGluIHRoZSBJdGVyYXRpb24gbGVjdHVyZS4gVGhpcyB3aWxsIHNwbGl0IGBTaXRlYCBpbnRvIHNldmVyYWwgZGF0YSBmcmFtZXMgYWNjb3JkaW5nIHRvIGNvdW50eS5zaXRlICh0aGF0IGlzLCBtb25pdG9yIElEcykgYW5kIHRlbGwgdXMgaG93IG1hbnkgbWVhc3VyZW1lbnRzIGVhY2ggbW9uaXRvciByZWNvcmRlZC4NCg0KYGBge3J9DQpzaXRlICU+JQ0KICBncm91cF9ieShZZWFyLFNpdGUpICU+JQ0KICBzdW1tYXJpemUoY291bnQ9bigpLA0KICAgICAgICAgICAgYXZnX3BtMjU9bWVhbihTYW1wbGVfVmFsdWUsbmEucm09VFJVRSkpDQpgYGANCg0KRnJvbSB0aGUgb3V0cHV0IGFib3ZlLCB3aGljaCBtb25pdG9yIGlzIHRoZSBvbmx5IG9uZSB3aG9zZSBudW1iZXIgb2YgbWVhc3VyZW1lbnRzIGluY3JlYXNlZCBmcm9tIDE5OTkgdG8gMjAxMj8NCjE6IDA2MzIwMDgNCjI6IDEwMTAwMDMNCjM6IDAyOTAwMDUNCjQ6IDA4NTAwNTUNCjU6IE5vbmUNCg0KYGBge3IsaW5jbHVkZT1GQUxTRX0NCiNBbnM6IDUgb3IgND8NCmBgYA0KDQpXZSB3YW50IHRvIGV4YW1pbmUgYSBtb25pdG9yIHdpdGggYSByZWFzb25hYmxlIG51bWJlciBvZiBtZWFzdXJlbWVudHMgc28gbGV0J3MgbG9vayBhdCB0aGUgbW9uaXRvciB3aXRoIElEIDA2MzIwMDguIENyZWF0ZSBhIHZhcmlhYmxlIHBtMHN1YiB3aGljaCBpcyB0aGUgc3Vic2V0IG9mIGNudDAgKHRoaXMNCmNvbnRhaW5zIGp1c3QgTmV3IFlvcmsgZGF0YSkgd2hpY2ggaGFzIENvdW50eS5Db2RlIGVxdWFsIHRvIDYzIGFuZCBTaXRlLklEIDIwMDguDQoNCmBgYHtyfQ0Kc2l0ZS5zdWIgPC0gZmlsdGVyKHNpdGUsIFNpdGU9PScwNjMyMDA4JykNCnNpdGUuc3ViDQpgYGANCg0KRnJvbSB0aGUgb3V0cHV0IG9mIHRoZSAyIGNhbGxzIHRvIHNhcHBseSwgaG93IG1hbnkgcm93cyB3aWxsIHNpdGUuc3ViIGhhdmUgZm9yIDE5OTk/DQoxOiAxODMNCjI6IDI5DQozOiAxMjINCjQ6IDMwDQoNCmBgYHtyLGluY2x1ZGUgPSBGQUxTRX0NCiNBbnM6IDMNCmBgYA0KDQpOb3cgd2UnZCBsaWtlIHRvIGNvbXBhcmUgdGhlIHBtMjUgbWVhc3VyZW1lbnRzIG9mIHRoaXMgcGFydGljdWxhciBtb25pdG9yICgwNjMyMDA4KSBmb3IgdGhlIDIgeWVhcnMuIEZpcnN0LCBjcmVhdGUgdGhlIHZlY3RvciB4MHN1YiBieSBhc3NpZ25pbmcgdG8gaXQgdGhlIFNhbXBsZS5WYWx1ZSBjb21wb25lbnQgb2YgcG0wc3ViLg0KDQpgYGB7cn0NCmc8LWc8LWdncGxvdChzaXRlLnN1YixhZXMoWWVhcixTYW1wbGVfVmFsdWUpKSArDQogIGdlb21fdmlvbGluKCkgKw0KICBzdGF0X3N1bW1hcnkoZnVuLnk9bWVkaWFuLCBnZW9tPSJjcm9zc2JhciIsIHNpemU9MiwgY29sb3I9IiNhNmJkZGIiKQ0KZ2dwbG90bHkoZykNCmBgYA0KDQoNCldoaWNoIG1lZGlhbiBpcyBsYXJnZXIgLSB0aGUgb25lIGZvciAxOTk5IG9yIHRoZSBvbmUgZm9yIDIwMTI/DQoxOiAyMDEyDQoyOiAxOTk5DQoNCmBgYHtyLGluY2x1ZGU9RkFMU0V9DQojQW5zOiAxOTk5DQpgYGANCg0KV2UndmUgY3JlYXRlZCBhIHZpb2xpbiBwbG90IHNob3dpbmcgdGhlIFBNIDIuNSB2YWx1ZXMgc2lkZSBieSBzaWRlIHdpdGggdGhlIHNhbWUgcmFuZ2Ugb2YgdmFsdWVzIG9uIHRoZSB5IGF4aXMuIFRoZSBpbXByb3ZlbWVudCBpbiB0aGUgbWVkaWFucyBiZXR3ZWVuIDE5OTkgYW5kIDIwMTIgaXMgbm93IGNsZWFyLiBBbHNvIG5vdGljZSB0aGF0IGluIDIwMTIgdGhlcmUgYXJlIG5vIGJpZyB2YWx1ZXMgKGFib3ZlIDE1KS4gVGhpcyBzaG93cyB0aGF0IG5vdCBvbmx5IGlzIHRoZXJlIGEgY2hyb25pYyBpbXByb3ZlbWVudCBpbiBhaXIgcXVhbGl0eSwgYnV0IGFsc28gdGhlcmUgYXJlIGZld2VyIGRheXMgd2l0aCBzZXZlcmUgcG9sbHV0aW9uLg0KDQpUaGUgbGFzdCBhdmVudWUgb2YgdGhpcyBkYXRhIHdlJ2xsIGV4cGxvcmUgKGFuZCB3ZSdsbCBkbyBpdCBxdWlja2x5KSBjb25jZXJucyBhIGNvbXBhcmlzb24gb2YgYWxsIHRoZSBzdGF0ZXMnIG1lYW4gcG9sbHV0aW9uIGxldmVscy4gVGhpcyBpcyBpbXBvcnRhbnQgYmVjYXVzZSB0aGUgc3RhdGVzIGFyZSByZXNwb25zaWJsZSBmb3IgaW1wbGVtZW50aW5nIHRoZSByZWd1bGF0aW9ucyBzZXQgYXQgdGhlIGZlZGVyYWwgbGV2ZWwgYnkgdGhlIEVQQS4NCg0KTGV0J3MgZmlyc3QgZ2F0aGVyIHRoZSBtZWFuIChhdmVyYWdlIG1lYXN1cmVtZW50KSBmb3IgZWFjaCBzdGF0ZSBpbiBib3RoIHllYXJzLiANCg0KYGBge3J9DQpieV9zdGF0ZTwtcG0yNSAlPiUNCiAgZ3JvdXBfYnkoU3RhdGVfQ29kZSxZZWFyKSAlPiUNCiAgc3VtbWFyaXNlKGF2Z3BtMjU9bWVhbihTYW1wbGVfVmFsdWUsbmEucm09VFJVRSkpICU+JQ0KICBwaXZvdF93aWRlcihuYW1lc19mcm9tID0gWWVhciwgdmFsdWVzX2Zyb20gPSBhdmdwbTI1KSAlPiUNCiAgbXV0YXRlKGluY3JlYXNlPWAxOTk5YDxgMjAxMmApDQpieV9zdGF0ZQ0KYGBgDQoNCg0KRWFjaCByb3cgb2YgbXJnIGhhcyA0IGVudHJpZXMgLSBhIHN0YXRlIGlkZW50aWZpZWQgYnkgbnVtYmVyLCBhIHN0YXRlIG1lYW4gZm9yIDE5OTksIGEgc3RhdGUgbWVhbiBmb3IgMjAxMiwgYW5kIGFuIGluZGljYXRvciBmb3Igd2hldGhlciBvciBub3QgdGhlIHN0YXRlIHNhdyBhbiBpbmNyZWFzZSBpbiBQTSAyLjUgbGV2ZWxzIGJldHdlZW4gMTk5OSBhbmQgMjAxMi4gDQoNCk5vdyB3ZSdsbCBwbG90IHRoZSBkYXRhIHRvIHNlZSBob3cgdGhlIHN0YXRlIG1lYW5zIGNoYW5nZWQgYmV0d2VlbiB0aGUgMiB5ZWFycy4gSSdtIGdvaW5nIHRvIHVzZSBhIHBhcmFsbGVsIGNvb3JkaW5hdGUgcGxvdCB1c2luZyBbZ2dhbGx5XShodHRwczovL3d3dy5yLWdyYXBoLWdhbGxlcnkuY29tL3BhcmFsbGVsLXBsb3QtZ2dhbGx5Lmh0bWwpIA0KDQpgYGB7cn0NCmxpYnJhcnkoR0dhbGx5KQ0KcCA8LSBnZ3BhcmNvb3JkKGRhdGEgPSBieV9zdGF0ZSwgY29sdW1ucyA9IDI6Myxncm91cENvbHVtbiA9NCxzaG93UG9pbnRzID0gVFJVRSx0aXRsZSA9IjQgU3RhdGVzIGhhZCBpbmNyZWFzZWQgUE0gMi41IGxldmVscyBiZXR3ZWVuIDE5OTkgYW5kIDIwMTIiLHNjYWxlPSJnbG9iYWxtaW5tYXgiKSsNCiAgc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXMgPSBjKCcjYmRiZGJkJywnI2RlMmQyNicpKSArIA0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpICsNCiAgeWxhYigiUE0gMi41IGNvbmNlbnRyYXRpb24iKSArDQogIHhsYWIoIlllYXIiKQ0KZ2dwbG90bHkocCkNCmBgYA0KDQpXZSBzZWUgYSBjb2x1bW4gb2YgcG9pbnRzIGF0IDE5OTkgd2hpY2ggcmVwcmVzZW50IHRoZSAxOTk5IHN0YXRlIG1lYW5zLiBXZSBzZWUgYSBzaG9ydGVyIGNvbHVtbiBvZiBwb2ludHMgYXQgMjAxMi4gV2Ugc2VlIGZyb20gdGhlIHBsb3QgdGhhdCB0aGUgdmFzdCBtYWpvcml0eSBvZiBzdGF0ZXMgaGF2ZSBpbmRlZWQgaW1wcm92ZWQgdGhlaXIgcGFydGljdWxhdGUgbWF0dGVyIGNvdW50cyBzbyB0aGUgZ2VuZXJhbCB0cmVuZCBpcyBkb3dud2FyZC4gVGhlcmUgYXJlIGEgZmV3IGV4Y2VwdGlvbnMuIChUaGUgdG9wbW9zdCBwb2ludCBpbiB0aGUgMTk5OSBjb2x1bW4gaXMgYWN0dWFsbHkgdHdvIHBvaW50cyB0aGF0IGhhZCB2ZXJ5IGNsb3NlIG1lYXN1cmVtZW50cy4pDQoNCkZvciBmdW4sIGxldCdzIHNlZSB3aGljaCBzdGF0ZXMgaGFkIGhpZ2hlciBtZWFucyBpbiAyMDEyIHRoYW4gaW4gMTk5OS4gSnVzdCB1c2UgdGhlIG1yZ1ttcmckbWVhbi54IDwgbXJnJG1lYW4ueSwgXSBub3RhdGlvbiB0byBmaW5kIHRoZSByb3dzIG9mIG1yZyB3aXRoIHRoaXMgcGFydGljdWxhdGUgcHJvcGVydHkuDQoNCg0KYGBge3J9DQpmaWx0ZXIoYnlfc3RhdGUsaW5jcmVhc2UpDQpgYGANCg0KT25seSA0IHN0YXRlcyBoYWQgd29yc2UgcG9sbHV0aW9uIGF2ZXJhZ2VzLCBhbmQgMiBvZiB0aGVzZSBoYWQgbWVhbnMgdGhhdCB3ZXJlIHZlcnkgY2xvc2UuIElmIHlvdSB3YW50IHRvIHNlZSB3aGljaCBzdGF0ZXMgKDE1LCAzMSwgMzUsIGFuZCA0MCkgdGhlc2UgYXJlLCB5b3UgY2FuIGNoZWNrIG91dCB0aGlzIHdlYnNpdGUgPGh0dHBzOi8vd3d3LmVwYS5nb3YvZW52aXJvL3N0YXRlLWZpcHMtY29kZS1saXN0aW5nPiB0byBkZWNvZGUgdGhlIHN0YXRlIGNvZGVzLg0KDQpUaGlzIGNvbmNsdWRlcyBvdXIgY29tcGFyaXNvbiBvZiBhaXIgcG9sbHV0aW9uIGRhdGEgZnJvbSB0d28geWVhcnMgaW4gZGlmZmVyZW50IHdheXMuIEZpcnN0LCB3ZSBsb29rZWQgYXQgbWVhc3VyZXMgb2YgdGhlIGVudGlyZSBzZXQgb2YgbW9uaXRvcnMsIHRoZW4gd2UgY29tcGFyZWQgdGhlIHR3byBtZWFzdXJlcyBmcm9tIGEgcGFydGljdWxhciBtb25pdG9yLCBhbmQgZmluYWxseSwgd2UgbG9va2VkIGF0IHRoZSBtZWFuIG1lYXN1cmVzIG9mIHRoZSBpbmRpdmlkdWFsIHN0YXRlcy4NCg0KQ29uZ3JhdHVsYXRpb25zISBXZSBob3BlIHlvdSBlbmpveWVkIHRoaXMgcGFydGljdWxhdGUgbGVzc29uLg0KDQoNCg0KDQoNCg==