In today’s post we will be doing some basic EDA (Exploratory Data Analysis) with the synthetic COVD data set provided by https://synthea.mitre.org/downloads. Additional information about the data can be found at https://github.com/synthetichealth/synthea/wiki/CSV-File-Data-Dictionary.

As always, let’s begin with the packages, functions and a little supplemental data…

library(tidyverse)
library(data.table)
library(plotly)
library(leaflet)
library(sf)
library(colorspace)

# Massachusetts County Population Data #https://worldpopulationreview.com/us-counties/states/ma
pop<- data.frame(
  stringsAsFactors = FALSE,
  COUNTY = c("Middlesex County",
             "Worcester County","Suffolk County",
             "Essex County","Norfolk County","Bristol County",
             "Plymouth County","Hampden County","Barnstable County",
             "Hampshire County","Berkshire County",
             "Franklin County","Dukes County","Nantucket County"),
  Population = c(1614710,830839,
                 807252,790638,705388,564022,518132,470406,
                 213413,161355,126348,70963,17352,11327))


death_rate_table<- function(thevar, titleText){
  # Plotting function that creates a barplot specifically to compare death rates by a given factor
  PID$thevar<- thevar
  t0<- PID %>% group_by(thevar,death_status) %>% tally() %>% spread(death_status,n) #%>% print()
  t1<- PID %>% group_by(thevar) %>% summarise(Total= n()) %>% left_join(t0) #%>% print()
  t1<- t1 %>% mutate(Death_rate= Died/Total, label= paste0(round(Death_rate*100,1),"%"))
  ymid <- mean(range(t1$Death_rate))
  t1$Ordered<- fct_reorder(t1$thevar, t1$Death_rate)
  g<- ggplot(t1, aes(x= Death_rate, y= Ordered, fill= Death_rate)) +
    geom_col(color= "gray50", size= 0.25) +
    geom_text(aes(label=label,hjust = ifelse(Death_rate < ymid, -0.5, 1.5)), vjust= 0.5, fontface="bold") +
    scale_fill_gradient(low=  "#7a5195",high= "#f95d6a") +
    scale_x_continuous( expand = c(0, 0)) +
    theme_bw() +
    theme(legend.position = 'none',
          plot.margin = margin(0.5, 0.5, 0.5, 0.5, "cm"),
          panel.grid.major.y = element_blank(),
          panel.border= element_blank(),
          axis.ticks.y= element_blank(),
          axis.text.y = element_text(size= 10)) +
    labs(title= paste("Death Rate by",titleText), x= "\nDeath Rate", y= NULL)
  print(g)
  
  return(t1)
}

# Hospitals (A data set of hospitals in Massachusetts - derived from the main set)
hospitals<- readRDS("hospitals.rds")

# Some utility functions
putZeros<- function(OB) {
  # Convert NAs in a matrix to zeros
  OB<- OB %>% mutate_at(vars(-group_cols()),~replace(.,is.na(.),0))
  return(OB)
}

label_cols<- function(sett,label,before="before") {
  # Add labels to all but the first column of a data.frame
  P<- ncol(sett)
  if (before!= "before") {
    names(sett)[2:P]<- paste0(names(sett)[2:P],label)
  } else {
    names(sett)[2:P]<- paste0(label,names(sett)[2:P])
  }
  return(sett)
}

howmany<- function(thevar) {
  # Count how many unique values in a vector
  res<- length(unique(thevar))
  return(res)
}

Today we are picking up our data set that we cleansed in a previous session. Now, the purpose of EDA is really just to get an overall feel for what the data is doing. In particular, we might ask questions like:

  1. Where are COVID cases occurring?
  2. Where are COVID deaths occurring?
  3. What do the timing patterns of infections look like?
  4. What are the average costs of COVID treatment?
  5. How do death patterns vary across different parts of the State?
IP<- readRDS("Dev Data 0/IP_0.rds") # 1766943

# Create a patient summary set

PID<- IP %>%  select(PID, Status, age, sex, ethnicity_fac, allergy, CITY, COUNTY, ZIP, LAT, LON, PATIENT) %>% distinct() # 9040

# Some enrichment
PID$death_status<- ifelse(PID$Status==0, "Alive", "Died")
PID$sex_fac<-      factor(PID$sex, labels= c("Male", "Female"))
PID$age_group<-    floor(PID$age/10)*10

# Make some tidy labels for age group
age_labels<- names(table(PID$age_group))
age_labels<- paste(age_labels,c(age_labels[-1],"+")) 
age_labels<- ifelse(age_labels %like% "\\+", gsub(" ","",age_labels), gsub(" ","-",age_labels)) %>% print()
 [1] "0-10"   "10-20"  "20-30"  "30-40"  "40-50"  "50-60"  "60-70"  "70-80"  "80-90"  "90-100" "100+"  
PID$age_fac<- ordered(factor(PID$age_group), labels= age_labels)
table(PID$age_fac)

  0-10  10-20  20-30  30-40  40-50  50-60  60-70  70-80  80-90 90-100   100+ 
   950   1128   1239   1145   1190   1357    981    598    269     98     85 
# Super basic initial plot of patient locations
ggplot(PID, aes(x= LON, y=LAT, color= death_status))+
  geom_point(alpha= 0.3, size= 1) +
  theme_void() +
  labs(title= "Patient locations before any styling")

We can pull patient encounters from the active period, and look at the date at which each patient is first diagnosed. We can then use the cumsum function to derive the cumulative infection curve.

# Pull out records of patient encounters across the active period

encounters<- IP %>% filter(!type %like% "history") %>% left_join(hospitals)

encounters<- encounters %>% mutate(death_status= ifelse(Status==0,"Alive", "Died"),
                                   COST= ifelse(is.na(TOTALCOST), BASE_COST, TOTALCOST),
                                   age_group= floor(age/10)*10,
                                   age_group= factor(age_group, labels= age_labels),
                                   description= str_to_lower(DESCRIPTION))

encounters<- encounters %>% mutate(COVID= ifelse(description == "covid-19", 1, 0))

# Infection Rates

diags<- encounters %>% filter(COVID==1) %>% group_by(PID) %>% summarise(etime= min(etime))
t0<- diags %>% count(etime) %>% mutate(cumulative= cumsum(n)) 
plot(t0$etime,t0$cumulative, type="l", main= "Overall Infections", xlab= "Day of the active period",
     ylab= "Cumulative Infections")

This curve is not very informative, lets split the infections by County…

# Infection Rates by County

diags<-   encounters %>% filter(COVID==1) %>% group_by(PID,COUNTY) %>% summarise(etime= min(etime)) %>% ungroup()
t0<-      diags %>% count(etime,COUNTY) %>% spread(COUNTY,n) %>% putZeros()
cummat<-  sapply(t0[,2:15], cumsum) %>% data.frame() %>%  mutate(etime= t0$etime)
clong<-   cummat %>% gather(key, value, -etime) %>% mutate(key= gsub("\\."," ", key))
clong<-   left_join(clong, pop, by=c("key"="COUNTY"))
clong<-   clong %>% mutate(per_100k = value/Population * 100000,
                           County=    word(key, 1))

g<- ggplot(clong, aes(x= etime, y= per_100k, color= County)) +
  geom_line() +
  labs(x= "Day of the active period", y= "Infections per 100k population",
               title= "Infection Rate by County") +
  theme_bw()

ggplotly(g)

Next we explore the total pathway cost (across the one-year active period) for each patient, broken down by age group, and whether or not the patient ultimately died.

# Summarise patient pathways
t0<- encounters %>% group_by(PID, COUNTY, age_group, death_status) %>% 
                    summarise(COST= sum(COST,na.rm = T), length= max(etime, na.rm=T))

# Average cost by age group
t1<- t0 %>% group_by(age_group, death_status) %>% summarise(Average= mean(COST)) %>%
            mutate(Label= paste0(round(Average/1000),"k"))
ggplot(t1, aes(x= Average, y= factor(age_group), fill= death_status)) +
     geom_col(position= "dodge") +
     geom_text(aes(label= Label), position= position_dodge(width= 1), hjust= 1.2, fontface= "bold", color="white") +
     labs(title= "Total Cost by Age Group and Death Outcome", 
        subtitle= "Average Total Cost across the 1-year active period per patient",
        x= "Average Overall Cost", y= "Age Group", fill= "Death Outcome") +
     scale_fill_manual(values=c("#7a5195","#f95d6a")) +
     theme_bw()

Next we try out our generic death rate function across various factors that exist in the data. It seems that Men have a slightly higher risk of death from COVID than women, and that patients who are African American might be at higher risk than White or Asian patients.

# Visual EDA of Death Rate by various factors
death_rate_table(PID$sex_fac, "Sex")


death_rate_table(PID$ethnicity_fac, "Ethnicity")


death_rate_table(PID$age_fac, "Age Group")


death_rate_table(PID$COUNTY, "County")

Because latitude and longitude data has been provided for each patient in the data set, it is an excellent opportunity to try out some geo-spatial tools. Please note that because the data has been synthesized from real data, sometimes the randomness inserted into the data makes the points lay outside the State or outside the land!

I also found a handy shapefile online (link below) with the county boundaries of Massachusetts. We also have a set of Hospitals, who coordinates are also approximate. Originally these plots were done with leaflet, however I was not able to get those to upload to RPubs from my mac.

# Download a shapefile
# http://maps-massgis.opendata.arcgis.com/datasets/aca1bbc8e91e4970b24bc4db8311e6f6_0

csh<- st_read("~/Documents/Synthea/Massachusetts_Counties-shp/Massachusetts_Counties.shp")
Reading layer `Massachusetts_Counties' from data source `/Users/celeste/Documents/Synthea/Massachusetts_Counties-shp/Massachusetts_Counties.shp' using driver `ESRI Shapefile'
Simple feature collection with 14 features and 3 fields
geometry type:  MULTIPOLYGON
dimension:      XY
bbox:           xmin: -73.50821 ymin: 41.23876 xmax: -69.92809 ymax: 42.88679
CRS:            4326
csh %>%
  leaflet() %>% 
  addTiles() %>% 
  # set color to green and create Wealth Zipcodes group
  addPolygons(weight = 1, fillOpacity = .3, color = "green",
              label = ~COUNTY,
              highlightOptions = highlightOptions(weight = 5, color = "white", bringToFront = FALSE)) %>%
  addCircleMarkers(data= hospitals, radius= 2, color= "yellow",
                 popup = hospitals$HOS_KEY)

Now let’s add the patient points in layers so that we can explore each set separately…


pal <- colorFactor(palette = c("blue", "red"), 
                   levels = c("Alive", "Died"))

massCentral<- c(-71.801299, 42.196323)

died<-  PID %>% filter(death_status=="Died")
alive<- PID %>% filter(death_status== "Alive")

g<- leaflet() %>% 
  addTiles() %>% 
  addCircleMarkers(data= alive, radius= 1, opacity= 0.3, color= ~pal(death_status), group= "Alive") %>%
  addCircleMarkers(data= died, radius=1, opacity= 0.5, color= ~pal(death_status), group= "Died") %>%
  addCircleMarkers(data= hospitals, radius= 2, opacity= 1, color= "yellow", 
                   popup = hospitals$HOS_KEY, group= "Hospitals") %>%
  addLayersControl(overlayGroups = c("Alive", "Died", "Hospitals"))

g

Clearly the population is unevenly spread across the state, so it would be interesting to explore various metrics and how they vary by county.

# Death metrics by county

# First get population size order for later plotting
bySize <-            pop %>% arrange(Population) %>% pull(COUNTY)
encounters$COUNTY <- factor(encounters$COUNTY, levels = bySize)

# Summarise statistics from patient pathways (by patient)
t0<- encounters %>% filter(!is.na(COUNTY)) %>%
       group_by(PID, CITY, COUNTY, age, age_group, death_status, LAT, LON) %>% 
       summarise(COST= sum(COST,na.rm = T)) %>% ungroup()

# Summarise across patients
t1<- t0  %>% group_by(COUNTY) %>% summarise(Patients= howmany(PID),
                                             Average_age= mean(age),
                                             Average_cost= mean(COST))
# Death Rate by COUNTY
t2<- t0 %>% group_by(COUNTY,death_status) %>% summarise(N= howmany(PID)) %>%
             left_join(t1) %>%
             mutate(Percent= N/Patients) %>% 
             select(COUNTY,death_status, Percent) %>%
             spread(death_status, Percent) %>% putZeros() %>% label_cols("Percent_")

tv<- left_join(t1, t2 %>% select(COUNTY, Percent_Died))   
tv<- left_join(tv, pop)

# Calculate some metrics
tv$Patients_per_100k= tv$Patients/tv$Population * 100000
tv$Raw_Deaths<-       tv$Percent_Died * tv$Patients
tv$Deaths_per_100k<-  tv$Raw_Deaths/tv$Population * 100000
tv$COUNTY<-           word(tv$COUNTY,1)
sizes<-               sort(table(encounters$COUNTY))
tv$COUNTY<-           fct_reorder(tv$COUNTY, names(sizes)) %>% str_to_upper(tv$COUNTY) %>% trimws()
argument `locale` should be a single character string; only the first element is used
# Re-shape for plotting
tlong<- tv %>% gather(key, value, -COUNTY) %>% 
                 mutate(Label= value,
                        COUNTY= factor(COUNTY, levels= str_to_upper(word(bySize, 1)))) 
tmid<-  tlong %>% group_by(key) %>% mutate(mid= mean(value))
tlong<- left_join(tlong, tmid)

# Tidy up the labels for plotting
tlong$key<- factor(tlong$key, levels= c("Population","Average_age","Average_cost","Patients",
                                       "Patients_per_100k","Raw_Deaths","Percent_Died","Deaths_per_100k"))
tlong$Label<- with(tlong, ifelse(key=="Average_cost", paste0("$",round(value/1000),"k"), round(value)))
tlong$Label<- with(tlong, ifelse(key=="Population", paste0(round(value/1000),"k"), Label))
tlong$Label<- with(tlong, ifelse(key=="Percent_Died", paste0(round(value*100,1),"%"), Label))
tlong$Label<- with(tlong, ifelse(key=="Deaths_per_100k", round(value,1), Label))

ggplot(tlong, aes(x= value, y= COUNTY, fill= key)) +
  geom_col() +
  geom_text(aes(label=Label,hjust = ifelse(value < mid, -0.3, 1.3)), vjust= 0.5, size= 2.5) +
  facet_wrap(~key, scales= "free_x", ncol= 4, nrow=2) +
  theme_bw() +
  theme(legend.position = "none") +
  ggtitle("Various Metrics by County")

The sf package allows data to be held alongside spatial geometries, which allows us to join various calculated fields into spatial objects. Here is the most basic plotting function provided via sf.

# Native shapefile plotting via sf
csht<- left_join(csh, tv)
Joining, by = "COUNTY"
plot(csht[,c("Deaths_per_100k","Patients_per_100k","Population","Average_age")])

Alternatively we can combine the functionality of sf with our trusty ggplot by making use of geom_sf. We have plotted the county names in the polygon centres via the st_centroid function. Here we have plotted Age, but you could substitute field that can be created at county granularity.

# Combining shapefile + data plotting with ggplot and geom_sf
pp<- st_centroid(csh) %>% st_coordinates()
st_centroid assumes attributes are constant over geometries of xst_centroid does not give correct centroids for longitude/latitude data
counties<- csht$COUNTY

ggplot() +
  geom_sf(data= csht, aes(fill= Average_age)) +
  geom_text(aes(x= pp[,1], y=pp[,2], label= counties), fontface="bold", color = "white")+
  theme_void() +
  theme(panel.background = element_rect(fill= "grey60"))+
  scale_fill_continuous_sequential(palette = "Plasma", rev= FALSE)

This has just been a quick breeze through of some alternatives for basic EDA, making some additional use of geo-spatial elements.

..

..

..

LS0tCnRpdGxlOiAiU3ludGhlYTogQ09WSUQgRXhwbG9yYXRvcnkgQW5hbHlzaXMiCm91dHB1dDogaHRtbF9ub3RlYm9vawphdXRob3I6IENlbCBNY0NyYWNrZW4KZGF0ZTogIjIwMjAtMDctMTIiCi0tLQohW10oL1VzZXJzL0NlbGVzdGUvRGVza3RvcC9TeW50aGVhX0VEQS5wbmcpCgpJbiB0b2RheSdzIHBvc3Qgd2Ugd2lsbCBiZSBkb2luZyBzb21lIGJhc2ljIEVEQSAoRXhwbG9yYXRvcnkgRGF0YSBBbmFseXNpcykgd2l0aCB0aGUgc3ludGhldGljIENPVkQgZGF0YSBzZXQgcHJvdmlkZWQgYnkgW2h0dHBzOi8vc3ludGhlYS5taXRyZS5vcmcvZG93bmxvYWRzXShodHRwczovL3N5bnRoZWEubWl0cmUub3JnL2Rvd25sb2FkcykuICBBZGRpdGlvbmFsIGluZm9ybWF0aW9uIGFib3V0IHRoZSBkYXRhIGNhbiBiZSBmb3VuZCBhdCBbaHR0cHM6Ly9naXRodWIuY29tL3N5bnRoZXRpY2hlYWx0aC9zeW50aGVhL3dpa2kvQ1NWLUZpbGUtRGF0YS1EaWN0aW9uYXJ5XShodHRwczovL2dpdGh1Yi5jb20vc3ludGhldGljaGVhbHRoL3N5bnRoZWEvd2lraS9DU1YtRmlsZS1EYXRhLURpY3Rpb25hcnkpLgoKQXMgYWx3YXlzLCBsZXQncyBiZWdpbiB3aXRoIHRoZSBwYWNrYWdlcywgZnVuY3Rpb25zIGFuZCBhIGxpdHRsZSBzdXBwbGVtZW50YWwgZGF0YS4uLgoKYGBge3IgLG1lc3NhZ2U9IEZBTFNFLCB3YXJuaW5nPSBGQUxTRX0KbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkoZGF0YS50YWJsZSkKbGlicmFyeShwbG90bHkpCmxpYnJhcnkobGVhZmxldCkKbGlicmFyeShzZikKbGlicmFyeShjb2xvcnNwYWNlKQoKIyBNYXNzYWNodXNldHRzIENvdW50eSBQb3B1bGF0aW9uIERhdGEgI2h0dHBzOi8vd29ybGRwb3B1bGF0aW9ucmV2aWV3LmNvbS91cy1jb3VudGllcy9zdGF0ZXMvbWEKcG9wPC0gZGF0YS5mcmFtZSgKICBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UsCiAgQ09VTlRZID0gYygiTWlkZGxlc2V4IENvdW50eSIsCiAgICAgICAgICAgICAiV29yY2VzdGVyIENvdW50eSIsIlN1ZmZvbGsgQ291bnR5IiwKICAgICAgICAgICAgICJFc3NleCBDb3VudHkiLCJOb3Jmb2xrIENvdW50eSIsIkJyaXN0b2wgQ291bnR5IiwKICAgICAgICAgICAgICJQbHltb3V0aCBDb3VudHkiLCJIYW1wZGVuIENvdW50eSIsIkJhcm5zdGFibGUgQ291bnR5IiwKICAgICAgICAgICAgICJIYW1wc2hpcmUgQ291bnR5IiwiQmVya3NoaXJlIENvdW50eSIsCiAgICAgICAgICAgICAiRnJhbmtsaW4gQ291bnR5IiwiRHVrZXMgQ291bnR5IiwiTmFudHVja2V0IENvdW50eSIpLAogIFBvcHVsYXRpb24gPSBjKDE2MTQ3MTAsODMwODM5LAogICAgICAgICAgICAgICAgIDgwNzI1Miw3OTA2MzgsNzA1Mzg4LDU2NDAyMiw1MTgxMzIsNDcwNDA2LAogICAgICAgICAgICAgICAgIDIxMzQxMywxNjEzNTUsMTI2MzQ4LDcwOTYzLDE3MzUyLDExMzI3KSkKCgpkZWF0aF9yYXRlX3RhYmxlPC0gZnVuY3Rpb24odGhldmFyLCB0aXRsZVRleHQpewogICMgUGxvdHRpbmcgZnVuY3Rpb24gdGhhdCBjcmVhdGVzIGEgYmFycGxvdCBzcGVjaWZpY2FsbHkgdG8gY29tcGFyZSBkZWF0aCByYXRlcyBieSBhIGdpdmVuIGZhY3RvcgogIFBJRCR0aGV2YXI8LSB0aGV2YXIKICB0MDwtIFBJRCAlPiUgZ3JvdXBfYnkodGhldmFyLGRlYXRoX3N0YXR1cykgJT4lIHRhbGx5KCkgJT4lIHNwcmVhZChkZWF0aF9zdGF0dXMsbikgIyU+JSBwcmludCgpCiAgdDE8LSBQSUQgJT4lIGdyb3VwX2J5KHRoZXZhcikgJT4lIHN1bW1hcmlzZShUb3RhbD0gbigpKSAlPiUgbGVmdF9qb2luKHQwKSAjJT4lIHByaW50KCkKICB0MTwtIHQxICU+JSBtdXRhdGUoRGVhdGhfcmF0ZT0gRGllZC9Ub3RhbCwgbGFiZWw9IHBhc3RlMChyb3VuZChEZWF0aF9yYXRlKjEwMCwxKSwiJSIpKQogIHltaWQgPC0gbWVhbihyYW5nZSh0MSREZWF0aF9yYXRlKSkKICB0MSRPcmRlcmVkPC0gZmN0X3Jlb3JkZXIodDEkdGhldmFyLCB0MSREZWF0aF9yYXRlKQogIGc8LSBnZ3Bsb3QodDEsIGFlcyh4PSBEZWF0aF9yYXRlLCB5PSBPcmRlcmVkLCBmaWxsPSBEZWF0aF9yYXRlKSkgKwogICAgZ2VvbV9jb2woY29sb3I9ICJncmF5NTAiLCBzaXplPSAwLjI1KSArCiAgICBnZW9tX3RleHQoYWVzKGxhYmVsPWxhYmVsLGhqdXN0ID0gaWZlbHNlKERlYXRoX3JhdGUgPCB5bWlkLCAtMC41LCAxLjUpKSwgdmp1c3Q9IDAuNSwgZm9udGZhY2U9ImJvbGQiKSArCiAgICBzY2FsZV9maWxsX2dyYWRpZW50KGxvdz0gICIjN2E1MTk1IixoaWdoPSAiI2Y5NWQ2YSIpICsKICAgIHNjYWxlX3hfY29udGludW91cyggZXhwYW5kID0gYygwLCAwKSkgKwogICAgdGhlbWVfYncoKSArCiAgICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAnbm9uZScsCiAgICAgICAgICBwbG90Lm1hcmdpbiA9IG1hcmdpbigwLjUsIDAuNSwgMC41LCAwLjUsICJjbSIpLAogICAgICAgICAgcGFuZWwuZ3JpZC5tYWpvci55ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgcGFuZWwuYm9yZGVyPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICBheGlzLnRpY2tzLnk9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgIGF4aXMudGV4dC55ID0gZWxlbWVudF90ZXh0KHNpemU9IDEwKSkgKwogICAgbGFicyh0aXRsZT0gcGFzdGUoIkRlYXRoIFJhdGUgYnkiLHRpdGxlVGV4dCksIHg9ICJcbkRlYXRoIFJhdGUiLCB5PSBOVUxMKQogIHByaW50KGcpCiAgCiAgcmV0dXJuKHQxKQp9CgojIEhvc3BpdGFscyAoQSBkYXRhIHNldCBvZiBob3NwaXRhbHMgaW4gTWFzc2FjaHVzZXR0cyAtIGRlcml2ZWQgZnJvbSB0aGUgbWFpbiBzZXQpCmhvc3BpdGFsczwtIHJlYWRSRFMoImhvc3BpdGFscy5yZHMiKQoKIyBTb21lIHV0aWxpdHkgZnVuY3Rpb25zCnB1dFplcm9zPC0gZnVuY3Rpb24oT0IpIHsKICAjIENvbnZlcnQgTkFzIGluIGEgbWF0cml4IHRvIHplcm9zCiAgT0I8LSBPQiAlPiUgbXV0YXRlX2F0KHZhcnMoLWdyb3VwX2NvbHMoKSksfnJlcGxhY2UoLixpcy5uYSguKSwwKSkKICByZXR1cm4oT0IpCn0KCmxhYmVsX2NvbHM8LSBmdW5jdGlvbihzZXR0LGxhYmVsLGJlZm9yZT0iYmVmb3JlIikgewogICMgQWRkIGxhYmVscyB0byBhbGwgYnV0IHRoZSBmaXJzdCBjb2x1bW4gb2YgYSBkYXRhLmZyYW1lCiAgUDwtIG5jb2woc2V0dCkKICBpZiAoYmVmb3JlIT0gImJlZm9yZSIpIHsKICAgIG5hbWVzKHNldHQpWzI6UF08LSBwYXN0ZTAobmFtZXMoc2V0dClbMjpQXSxsYWJlbCkKICB9IGVsc2UgewogICAgbmFtZXMoc2V0dClbMjpQXTwtIHBhc3RlMChsYWJlbCxuYW1lcyhzZXR0KVsyOlBdKQogIH0KICByZXR1cm4oc2V0dCkKfQoKaG93bWFueTwtIGZ1bmN0aW9uKHRoZXZhcikgewogICMgQ291bnQgaG93IG1hbnkgdW5pcXVlIHZhbHVlcyBpbiBhIHZlY3RvcgogIHJlczwtIGxlbmd0aCh1bmlxdWUodGhldmFyKSkKICByZXR1cm4ocmVzKQp9CmBgYAoKClRvZGF5IHdlIGFyZSBwaWNraW5nIHVwIG91ciBkYXRhIHNldCB0aGF0IHdlIGNsZWFuc2VkIGluIGEgcHJldmlvdXMgc2Vzc2lvbi4gTm93LCB0aGUgcHVycG9zZSBvZiBFREEgaXMgcmVhbGx5IGp1c3QgdG8gZ2V0IGFuIG92ZXJhbGwgZmVlbCBmb3Igd2hhdCB0aGUgZGF0YSBpcyBkb2luZy4gSW4gcGFydGljdWxhciwgd2UgbWlnaHQgYXNrIHF1ZXN0aW9ucyBsaWtlOgoKMS4gV2hlcmUgYXJlIENPVklEIGNhc2VzIG9jY3VycmluZz8KMi4gV2hlcmUgYXJlIENPVklEIGRlYXRocyBvY2N1cnJpbmc/CjMuIFdoYXQgZG8gdGhlIHRpbWluZyBwYXR0ZXJucyBvZiBpbmZlY3Rpb25zIGxvb2sgbGlrZT8KNC4gV2hhdCBhcmUgdGhlIGF2ZXJhZ2UgY29zdHMgb2YgQ09WSUQgdHJlYXRtZW50Pwo1LiBIb3cgZG8gZGVhdGggcGF0dGVybnMgdmFyeSBhY3Jvc3MgZGlmZmVyZW50IHBhcnRzIG9mIHRoZSBTdGF0ZT8KYGBge3J9CklQPC0gcmVhZFJEUygiRGV2IERhdGEgMC9JUF8wLnJkcyIpICMgMTc2Njk0MwoKIyBDcmVhdGUgYSBwYXRpZW50IHN1bW1hcnkgc2V0CgpQSUQ8LSBJUCAlPiUgIHNlbGVjdChQSUQsIFN0YXR1cywgYWdlLCBzZXgsIGV0aG5pY2l0eV9mYWMsIGFsbGVyZ3ksIENJVFksIENPVU5UWSwgWklQLCBMQVQsIExPTiwgUEFUSUVOVCkgJT4lIGRpc3RpbmN0KCkgIyA5MDQwCgojIFNvbWUgZW5yaWNobWVudApQSUQkZGVhdGhfc3RhdHVzPC0gaWZlbHNlKFBJRCRTdGF0dXM9PTAsICJBbGl2ZSIsICJEaWVkIikKUElEJHNleF9mYWM8LSAgICAgIGZhY3RvcihQSUQkc2V4LCBsYWJlbHM9IGMoIk1hbGUiLCAiRmVtYWxlIikpClBJRCRhZ2VfZ3JvdXA8LSAgICBmbG9vcihQSUQkYWdlLzEwKSoxMAoKIyBNYWtlIHNvbWUgdGlkeSBsYWJlbHMgZm9yIGFnZSBncm91cAphZ2VfbGFiZWxzPC0gbmFtZXModGFibGUoUElEJGFnZV9ncm91cCkpCmFnZV9sYWJlbHM8LSBwYXN0ZShhZ2VfbGFiZWxzLGMoYWdlX2xhYmVsc1stMV0sIisiKSkgCmFnZV9sYWJlbHM8LSBpZmVsc2UoYWdlX2xhYmVscyAlbGlrZSUgIlxcKyIsIGdzdWIoIiAiLCIiLGFnZV9sYWJlbHMpLCBnc3ViKCIgIiwiLSIsYWdlX2xhYmVscykpICU+JSBwcmludCgpCgpQSUQkYWdlX2ZhYzwtIG9yZGVyZWQoZmFjdG9yKFBJRCRhZ2VfZ3JvdXApLCBsYWJlbHM9IGFnZV9sYWJlbHMpCnRhYmxlKFBJRCRhZ2VfZmFjKQoKIyBTdXBlciBiYXNpYyBpbml0aWFsIHBsb3Qgb2YgcGF0aWVudCBsb2NhdGlvbnMKZ2dwbG90KFBJRCwgYWVzKHg9IExPTiwgeT1MQVQsIGNvbG9yPSBkZWF0aF9zdGF0dXMpKSsKICBnZW9tX3BvaW50KGFscGhhPSAwLjMsIHNpemU9IDEpICsKICB0aGVtZV92b2lkKCkgKwogIGxhYnModGl0bGU9ICJQYXRpZW50IGxvY2F0aW9ucyBiZWZvcmUgYW55IHN0eWxpbmciKQpgYGAKV2UgY2FuIHB1bGwgcGF0aWVudCBlbmNvdW50ZXJzIGZyb20gdGhlIGFjdGl2ZSBwZXJpb2QsIGFuZCBsb29rIGF0IHRoZSBkYXRlIGF0IHdoaWNoIGVhY2ggcGF0aWVudCBpcyBmaXJzdCBkaWFnbm9zZWQuIFdlIGNhbiB0aGVuIHVzZSB0aGUgYGN1bXN1bWAgZnVuY3Rpb24gdG8gZGVyaXZlIHRoZSBjdW11bGF0aXZlIGluZmVjdGlvbiBjdXJ2ZS4KYGBge3IsIG1lc3NhZ2U9IEZBTFNFfQojIFB1bGwgb3V0IHJlY29yZHMgb2YgcGF0aWVudCBlbmNvdW50ZXJzIGFjcm9zcyB0aGUgYWN0aXZlIHBlcmlvZAoKZW5jb3VudGVyczwtIElQICU+JSBmaWx0ZXIoIXR5cGUgJWxpa2UlICJoaXN0b3J5IikgJT4lIGxlZnRfam9pbihob3NwaXRhbHMpCgplbmNvdW50ZXJzPC0gZW5jb3VudGVycyAlPiUgbXV0YXRlKGRlYXRoX3N0YXR1cz0gaWZlbHNlKFN0YXR1cz09MCwiQWxpdmUiLCAiRGllZCIpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIENPU1Q9IGlmZWxzZShpcy5uYShUT1RBTENPU1QpLCBCQVNFX0NPU1QsIFRPVEFMQ09TVCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYWdlX2dyb3VwPSBmbG9vcihhZ2UvMTApKjEwLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFnZV9ncm91cD0gZmFjdG9yKGFnZV9ncm91cCwgbGFiZWxzPSBhZ2VfbGFiZWxzKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkZXNjcmlwdGlvbj0gc3RyX3RvX2xvd2VyKERFU0NSSVBUSU9OKSkKCmVuY291bnRlcnM8LSBlbmNvdW50ZXJzICU+JSBtdXRhdGUoQ09WSUQ9IGlmZWxzZShkZXNjcmlwdGlvbiA9PSAiY292aWQtMTkiLCAxLCAwKSkKCiMgSW5mZWN0aW9uIFJhdGVzCgpkaWFnczwtIGVuY291bnRlcnMgJT4lIGZpbHRlcihDT1ZJRD09MSkgJT4lIGdyb3VwX2J5KFBJRCkgJT4lIHN1bW1hcmlzZShldGltZT0gbWluKGV0aW1lKSkKdDA8LSBkaWFncyAlPiUgY291bnQoZXRpbWUpICU+JSBtdXRhdGUoY3VtdWxhdGl2ZT0gY3Vtc3VtKG4pKSAKcGxvdCh0MCRldGltZSx0MCRjdW11bGF0aXZlLCB0eXBlPSJsIiwgbWFpbj0gIk92ZXJhbGwgSW5mZWN0aW9ucyIsIHhsYWI9ICJEYXkgb2YgdGhlIGFjdGl2ZSBwZXJpb2QiLAogICAgIHlsYWI9ICJDdW11bGF0aXZlIEluZmVjdGlvbnMiKQoKYGBgClRoaXMgY3VydmUgaXMgbm90IHZlcnkgaW5mb3JtYXRpdmUsIGxldHMgc3BsaXQgdGhlIGluZmVjdGlvbnMgYnkgQ291bnR5Li4uCmBgYHtyLCBtZXNzYWdlPSBGQUxTRX0KIyBJbmZlY3Rpb24gUmF0ZXMgYnkgQ291bnR5CgpkaWFnczwtICAgZW5jb3VudGVycyAlPiUgZmlsdGVyKENPVklEPT0xKSAlPiUgZ3JvdXBfYnkoUElELENPVU5UWSkgJT4lIHN1bW1hcmlzZShldGltZT0gbWluKGV0aW1lKSkgJT4lIHVuZ3JvdXAoKQp0MDwtICAgICAgZGlhZ3MgJT4lIGNvdW50KGV0aW1lLENPVU5UWSkgJT4lIHNwcmVhZChDT1VOVFksbikgJT4lIHB1dFplcm9zKCkKY3VtbWF0PC0gIHNhcHBseSh0MFssMjoxNV0sIGN1bXN1bSkgJT4lIGRhdGEuZnJhbWUoKSAlPiUgIG11dGF0ZShldGltZT0gdDAkZXRpbWUpCmNsb25nPC0gICBjdW1tYXQgJT4lIGdhdGhlcihrZXksIHZhbHVlLCAtZXRpbWUpICU+JSBtdXRhdGUoa2V5PSBnc3ViKCJcXC4iLCIgIiwga2V5KSkKY2xvbmc8LSAgIGxlZnRfam9pbihjbG9uZywgcG9wLCBieT1jKCJrZXkiPSJDT1VOVFkiKSkKY2xvbmc8LSAgIGNsb25nICU+JSBtdXRhdGUocGVyXzEwMGsgPSB2YWx1ZS9Qb3B1bGF0aW9uICogMTAwMDAwLAogICAgICAgICAgICAgICAgICAgICAgICAgICBDb3VudHk9ICAgIHdvcmQoa2V5LCAxKSkKCmc8LSBnZ3Bsb3QoY2xvbmcsIGFlcyh4PSBldGltZSwgeT0gcGVyXzEwMGssIGNvbG9yPSBDb3VudHkpKSArCiAgZ2VvbV9saW5lKCkgKwogIGxhYnMoeD0gIkRheSBvZiB0aGUgYWN0aXZlIHBlcmlvZCIsIHk9ICJJbmZlY3Rpb25zIHBlciAxMDBrIHBvcHVsYXRpb24iLAogICAgICAgICAgICAgICB0aXRsZT0gIkluZmVjdGlvbiBSYXRlIGJ5IENvdW50eSIpICsKICB0aGVtZV9idygpCgpnZ3Bsb3RseShnKQpgYGAKTmV4dCB3ZSBleHBsb3JlIHRoZSB0b3RhbCBwYXRod2F5IGNvc3QgKGFjcm9zcyB0aGUgb25lLXllYXIgYWN0aXZlIHBlcmlvZCkgZm9yIGVhY2ggcGF0aWVudCwgYnJva2VuIGRvd24gYnkgYWdlIGdyb3VwLCBhbmQgd2hldGhlciBvciBub3QgdGhlIHBhdGllbnQgdWx0aW1hdGVseSBkaWVkLgpgYGB7ciwgbWVzc2FnZT0gRkFMU0V9CiMgU3VtbWFyaXNlIHBhdGllbnQgcGF0aHdheXMKdDA8LSBlbmNvdW50ZXJzICU+JSBncm91cF9ieShQSUQsIENPVU5UWSwgYWdlX2dyb3VwLCBkZWF0aF9zdGF0dXMpICU+JSAKICAgICAgICAgICAgICAgICAgICBzdW1tYXJpc2UoQ09TVD0gc3VtKENPU1QsbmEucm0gPSBUKSwgbGVuZ3RoPSBtYXgoZXRpbWUsIG5hLnJtPVQpKQoKIyBBdmVyYWdlIGNvc3QgYnkgYWdlIGdyb3VwCnQxPC0gdDAgJT4lIGdyb3VwX2J5KGFnZV9ncm91cCwgZGVhdGhfc3RhdHVzKSAlPiUgc3VtbWFyaXNlKEF2ZXJhZ2U9IG1lYW4oQ09TVCkpICU+JQogICAgICAgICAgICBtdXRhdGUoTGFiZWw9IHBhc3RlMChyb3VuZChBdmVyYWdlLzEwMDApLCJrIikpCmdncGxvdCh0MSwgYWVzKHg9IEF2ZXJhZ2UsIHk9IGZhY3RvcihhZ2VfZ3JvdXApLCBmaWxsPSBkZWF0aF9zdGF0dXMpKSArCiAgICAgZ2VvbV9jb2wocG9zaXRpb249ICJkb2RnZSIpICsKICAgICBnZW9tX3RleHQoYWVzKGxhYmVsPSBMYWJlbCksIHBvc2l0aW9uPSBwb3NpdGlvbl9kb2RnZSh3aWR0aD0gMSksIGhqdXN0PSAxLjIsIGZvbnRmYWNlPSAiYm9sZCIsIGNvbG9yPSJ3aGl0ZSIpICsKICAgICBsYWJzKHRpdGxlPSAiVG90YWwgQ29zdCBieSBBZ2UgR3JvdXAgYW5kIERlYXRoIE91dGNvbWUiLCAKICAgICAgICBzdWJ0aXRsZT0gIkF2ZXJhZ2UgVG90YWwgQ29zdCBhY3Jvc3MgdGhlIDEteWVhciBhY3RpdmUgcGVyaW9kIHBlciBwYXRpZW50IiwKICAgICAgICB4PSAiQXZlcmFnZSBPdmVyYWxsIENvc3QiLCB5PSAiQWdlIEdyb3VwIiwgZmlsbD0gIkRlYXRoIE91dGNvbWUiKSArCiAgICAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzPWMoIiM3YTUxOTUiLCIjZjk1ZDZhIikpICsKICAgICB0aGVtZV9idygpCmBgYApOZXh0IHdlIHRyeSBvdXQgb3VyIGdlbmVyaWMgZGVhdGggcmF0ZSBmdW5jdGlvbiBhY3Jvc3MgdmFyaW91cyBmYWN0b3JzIHRoYXQgZXhpc3QgaW4gdGhlIGRhdGEuIEl0IHNlZW1zIHRoYXQgTWVuIGhhdmUgYSBzbGlnaHRseSBoaWdoZXIgcmlzayBvZiBkZWF0aCBmcm9tIENPVklEIHRoYW4gd29tZW4sIGFuZCB0aGF0IHBhdGllbnRzIHdobyBhcmUgQWZyaWNhbiBBbWVyaWNhbiBtaWdodCBiZSBhdCBoaWdoZXIgcmlzayB0aGFuIFdoaXRlIG9yIEFzaWFuIHBhdGllbnRzLgpgYGB7ciwgbWVzc2FnZT0gRkFMU0V9CiMgVmlzdWFsIEVEQSBvZiBEZWF0aCBSYXRlIGJ5IHZhcmlvdXMgZmFjdG9ycwpkZWF0aF9yYXRlX3RhYmxlKFBJRCRzZXhfZmFjLCAiU2V4IikKCmRlYXRoX3JhdGVfdGFibGUoUElEJGV0aG5pY2l0eV9mYWMsICJFdGhuaWNpdHkiKQoKZGVhdGhfcmF0ZV90YWJsZShQSUQkYWdlX2ZhYywgIkFnZSBHcm91cCIpCgpkZWF0aF9yYXRlX3RhYmxlKFBJRCRDT1VOVFksICJDb3VudHkiKQpgYGAKQmVjYXVzZSBsYXRpdHVkZSBhbmQgbG9uZ2l0dWRlIGRhdGEgaGFzIGJlZW4gcHJvdmlkZWQgZm9yIGVhY2ggcGF0aWVudCBpbiB0aGUgZGF0YSBzZXQsIGl0IGlzIGFuIGV4Y2VsbGVudCBvcHBvcnR1bml0eSB0byB0cnkgb3V0IHNvbWUgZ2VvLXNwYXRpYWwgdG9vbHMuIFBsZWFzZSBub3RlIHRoYXQgYmVjYXVzZSB0aGUgZGF0YSBoYXMgYmVlbiBzeW50aGVzaXplZCBmcm9tIHJlYWwgZGF0YSwgc29tZXRpbWVzIHRoZSByYW5kb21uZXNzIGluc2VydGVkIGludG8gdGhlIGRhdGEgbWFrZXMgdGhlIHBvaW50cyBsYXkgb3V0c2lkZSB0aGUgU3RhdGUgb3Igb3V0c2lkZSB0aGUgbGFuZCEKCkkgYWxzbyBmb3VuZCBhIGhhbmR5IHNoYXBlZmlsZSBvbmxpbmUgKGxpbmsgYmVsb3cpIHdpdGggdGhlIGNvdW50eSBib3VuZGFyaWVzIG9mIE1hc3NhY2h1c2V0dHMuIFdlIGFsc28gaGF2ZSBhIHNldCBvZiBIb3NwaXRhbHMsIHdobyBjb29yZGluYXRlcyBhcmUgYWxzbyBhcHByb3hpbWF0ZS4gIE9yaWdpbmFsbHkgdGhlc2UgcGxvdHMgd2VyZSBkb25lIHdpdGggbGVhZmxldCwgaG93ZXZlciBJIHdhcyBub3QgYWJsZSB0byBnZXQgdGhvc2UgdG8gdXBsb2FkIHRvIFJQdWJzIGZyb20gbXkgbWFjLgpgYGB7ciwgbWVzc2FnZT0gRkFMU0V9CiMgRG93bmxvYWQgYSBzaGFwZWZpbGUKIyBodHRwOi8vbWFwcy1tYXNzZ2lzLm9wZW5kYXRhLmFyY2dpcy5jb20vZGF0YXNldHMvYWNhMWJiYzhlOTFlNDk3MGIyNGJjNGRiODMxMWU2ZjZfMAoKY3NoPC0gc3RfcmVhZCgifi9Eb2N1bWVudHMvU3ludGhlYS9NYXNzYWNodXNldHRzX0NvdW50aWVzLXNocC9NYXNzYWNodXNldHRzX0NvdW50aWVzLnNocCIpCgpjc2ggJT4lCiAgbGVhZmxldCgpICU+JSAKICBhZGRUaWxlcygpICU+JSAKICAjIHNldCBjb2xvciB0byBncmVlbiBhbmQgY3JlYXRlIFdlYWx0aCBaaXBjb2RlcyBncm91cAogIGFkZFBvbHlnb25zKHdlaWdodCA9IDEsIGZpbGxPcGFjaXR5ID0gLjMsIGNvbG9yID0gImdyZWVuIiwKICAgICAgICAgICAgICBsYWJlbCA9IH5DT1VOVFksCiAgICAgICAgICAgICAgaGlnaGxpZ2h0T3B0aW9ucyA9IGhpZ2hsaWdodE9wdGlvbnMod2VpZ2h0ID0gNSwgY29sb3IgPSAid2hpdGUiLCBicmluZ1RvRnJvbnQgPSBGQUxTRSkpICU+JQogIGFkZENpcmNsZU1hcmtlcnMoZGF0YT0gaG9zcGl0YWxzLCByYWRpdXM9IDIsIGNvbG9yPSAieWVsbG93IiwKICAgICAgICAgICAgICAgICBwb3B1cCA9IGhvc3BpdGFscyRIT1NfS0VZKQpgYGAKTm93IGxldCdzIGFkZCB0aGUgcGF0aWVudCBwb2ludHMgaW4gbGF5ZXJzIHNvIHRoYXQgd2UgY2FuIGV4cGxvcmUgZWFjaCBzZXQgc2VwYXJhdGVseS4uLgpgYGB7ciwgbWVzc2FnZT0gRkFMU0V9CgpwYWwgPC0gY29sb3JGYWN0b3IocGFsZXR0ZSA9IGMoImJsdWUiLCAicmVkIiksIAogICAgICAgICAgICAgICAgICAgbGV2ZWxzID0gYygiQWxpdmUiLCAiRGllZCIpKQoKbWFzc0NlbnRyYWw8LSBjKC03MS44MDEyOTksIDQyLjE5NjMyMykKCmRpZWQ8LSAgUElEICU+JSBmaWx0ZXIoZGVhdGhfc3RhdHVzPT0iRGllZCIpCmFsaXZlPC0gUElEICU+JSBmaWx0ZXIoZGVhdGhfc3RhdHVzPT0gIkFsaXZlIikKCmc8LSBsZWFmbGV0KCkgJT4lIAogIGFkZFRpbGVzKCkgJT4lIAogIGFkZENpcmNsZU1hcmtlcnMoZGF0YT0gYWxpdmUsIHJhZGl1cz0gMSwgb3BhY2l0eT0gMC4zLCBjb2xvcj0gfnBhbChkZWF0aF9zdGF0dXMpLCBncm91cD0gIkFsaXZlIikgJT4lCiAgYWRkQ2lyY2xlTWFya2VycyhkYXRhPSBkaWVkLCByYWRpdXM9MSwgb3BhY2l0eT0gMC41LCBjb2xvcj0gfnBhbChkZWF0aF9zdGF0dXMpLCBncm91cD0gIkRpZWQiKSAlPiUKICBhZGRDaXJjbGVNYXJrZXJzKGRhdGE9IGhvc3BpdGFscywgcmFkaXVzPSAyLCBvcGFjaXR5PSAxLCBjb2xvcj0gInllbGxvdyIsIAogICAgICAgICAgICAgICAgICAgcG9wdXAgPSBob3NwaXRhbHMkSE9TX0tFWSwgZ3JvdXA9ICJIb3NwaXRhbHMiKSAlPiUKICBhZGRMYXllcnNDb250cm9sKG92ZXJsYXlHcm91cHMgPSBjKCJBbGl2ZSIsICJEaWVkIiwgIkhvc3BpdGFscyIpKQoKZwpgYGAKQ2xlYXJseSB0aGUgcG9wdWxhdGlvbiBpcyB1bmV2ZW5seSBzcHJlYWQgYWNyb3NzIHRoZSBzdGF0ZSwgc28gaXQgd291bGQgYmUgaW50ZXJlc3RpbmcgdG8gZXhwbG9yZSB2YXJpb3VzIG1ldHJpY3MgYW5kIGhvdyB0aGV5IHZhcnkgYnkgY291bnR5LgpgYGB7ciwgbWVzc2FnZT0gRkFMU0V9CiMgRGVhdGggbWV0cmljcyBieSBjb3VudHkKCiMgRmlyc3QgZ2V0IHBvcHVsYXRpb24gc2l6ZSBvcmRlciBmb3IgbGF0ZXIgcGxvdHRpbmcKYnlTaXplIDwtICAgICAgICAgICAgcG9wICU+JSBhcnJhbmdlKFBvcHVsYXRpb24pICU+JSBwdWxsKENPVU5UWSkKZW5jb3VudGVycyRDT1VOVFkgPC0gZmFjdG9yKGVuY291bnRlcnMkQ09VTlRZLCBsZXZlbHMgPSBieVNpemUpCgojIFN1bW1hcmlzZSBzdGF0aXN0aWNzIGZyb20gcGF0aWVudCBwYXRod2F5cyAoYnkgcGF0aWVudCkKdDA8LSBlbmNvdW50ZXJzICU+JSBmaWx0ZXIoIWlzLm5hKENPVU5UWSkpICU+JQogICAgICAgZ3JvdXBfYnkoUElELCBDSVRZLCBDT1VOVFksIGFnZSwgYWdlX2dyb3VwLCBkZWF0aF9zdGF0dXMsIExBVCwgTE9OKSAlPiUgCiAgICAgICBzdW1tYXJpc2UoQ09TVD0gc3VtKENPU1QsbmEucm0gPSBUKSkgJT4lIHVuZ3JvdXAoKQoKIyBTdW1tYXJpc2UgYWNyb3NzIHBhdGllbnRzCnQxPC0gdDAgICU+JSBncm91cF9ieShDT1VOVFkpICU+JSBzdW1tYXJpc2UoUGF0aWVudHM9IGhvd21hbnkoUElEKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgQXZlcmFnZV9hZ2U9IG1lYW4oYWdlKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgQXZlcmFnZV9jb3N0PSBtZWFuKENPU1QpKQojIERlYXRoIFJhdGUgYnkgQ09VTlRZCnQyPC0gdDAgJT4lIGdyb3VwX2J5KENPVU5UWSxkZWF0aF9zdGF0dXMpICU+JSBzdW1tYXJpc2UoTj0gaG93bWFueShQSUQpKSAlPiUKICAgICAgICAgICAgIGxlZnRfam9pbih0MSkgJT4lCiAgICAgICAgICAgICBtdXRhdGUoUGVyY2VudD0gTi9QYXRpZW50cykgJT4lIAogICAgICAgICAgICAgc2VsZWN0KENPVU5UWSxkZWF0aF9zdGF0dXMsIFBlcmNlbnQpICU+JQogICAgICAgICAgICAgc3ByZWFkKGRlYXRoX3N0YXR1cywgUGVyY2VudCkgJT4lIHB1dFplcm9zKCkgJT4lIGxhYmVsX2NvbHMoIlBlcmNlbnRfIikKCnR2PC0gbGVmdF9qb2luKHQxLCB0MiAlPiUgc2VsZWN0KENPVU5UWSwgUGVyY2VudF9EaWVkKSkgICAKdHY8LSBsZWZ0X2pvaW4odHYsIHBvcCkKCiMgQ2FsY3VsYXRlIHNvbWUgbWV0cmljcwp0diRQYXRpZW50c19wZXJfMTAwaz0gdHYkUGF0aWVudHMvdHYkUG9wdWxhdGlvbiAqIDEwMDAwMAp0diRSYXdfRGVhdGhzPC0gICAgICAgdHYkUGVyY2VudF9EaWVkICogdHYkUGF0aWVudHMKdHYkRGVhdGhzX3Blcl8xMDBrPC0gIHR2JFJhd19EZWF0aHMvdHYkUG9wdWxhdGlvbiAqIDEwMDAwMAp0diRDT1VOVFk8LSAgICAgICAgICAgd29yZCh0diRDT1VOVFksMSkKc2l6ZXM8LSAgICAgICAgICAgICAgIHNvcnQodGFibGUoZW5jb3VudGVycyRDT1VOVFkpKQp0diRDT1VOVFk8LSAgICAgICAgICAgZmN0X3Jlb3JkZXIodHYkQ09VTlRZLCBuYW1lcyhzaXplcykpICU+JSBzdHJfdG9fdXBwZXIodHYkQ09VTlRZKSAlPiUgdHJpbXdzKCkKCiMgUmUtc2hhcGUgZm9yIHBsb3R0aW5nCnRsb25nPC0gdHYgJT4lIGdhdGhlcihrZXksIHZhbHVlLCAtQ09VTlRZKSAlPiUgCiAgICAgICAgICAgICAgICAgbXV0YXRlKExhYmVsPSB2YWx1ZSwKICAgICAgICAgICAgICAgICAgICAgICAgQ09VTlRZPSBmYWN0b3IoQ09VTlRZLCBsZXZlbHM9IHN0cl90b191cHBlcih3b3JkKGJ5U2l6ZSwgMSkpKSkgCnRtaWQ8LSAgdGxvbmcgJT4lIGdyb3VwX2J5KGtleSkgJT4lIG11dGF0ZShtaWQ9IG1lYW4odmFsdWUpKQp0bG9uZzwtIGxlZnRfam9pbih0bG9uZywgdG1pZCkKCiMgVGlkeSB1cCB0aGUgbGFiZWxzIGZvciBwbG90dGluZwp0bG9uZyRrZXk8LSBmYWN0b3IodGxvbmcka2V5LCBsZXZlbHM9IGMoIlBvcHVsYXRpb24iLCJBdmVyYWdlX2FnZSIsIkF2ZXJhZ2VfY29zdCIsIlBhdGllbnRzIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIlBhdGllbnRzX3Blcl8xMDBrIiwiUmF3X0RlYXRocyIsIlBlcmNlbnRfRGllZCIsIkRlYXRoc19wZXJfMTAwayIpKQp0bG9uZyRMYWJlbDwtIHdpdGgodGxvbmcsIGlmZWxzZShrZXk9PSJBdmVyYWdlX2Nvc3QiLCBwYXN0ZTAoIiQiLHJvdW5kKHZhbHVlLzEwMDApLCJrIiksIHJvdW5kKHZhbHVlKSkpCnRsb25nJExhYmVsPC0gd2l0aCh0bG9uZywgaWZlbHNlKGtleT09IlBvcHVsYXRpb24iLCBwYXN0ZTAocm91bmQodmFsdWUvMTAwMCksImsiKSwgTGFiZWwpKQp0bG9uZyRMYWJlbDwtIHdpdGgodGxvbmcsIGlmZWxzZShrZXk9PSJQZXJjZW50X0RpZWQiLCBwYXN0ZTAocm91bmQodmFsdWUqMTAwLDEpLCIlIiksIExhYmVsKSkKdGxvbmckTGFiZWw8LSB3aXRoKHRsb25nLCBpZmVsc2Uoa2V5PT0iRGVhdGhzX3Blcl8xMDBrIiwgcm91bmQodmFsdWUsMSksIExhYmVsKSkKCmdncGxvdCh0bG9uZywgYWVzKHg9IHZhbHVlLCB5PSBDT1VOVFksIGZpbGw9IGtleSkpICsKICBnZW9tX2NvbCgpICsKICBnZW9tX3RleHQoYWVzKGxhYmVsPUxhYmVsLGhqdXN0ID0gaWZlbHNlKHZhbHVlIDwgbWlkLCAtMC4zLCAxLjMpKSwgdmp1c3Q9IDAuNSwgc2l6ZT0gMi41KSArCiAgZmFjZXRfd3JhcCh+a2V5LCBzY2FsZXM9ICJmcmVlX3giLCBuY29sPSA0LCBucm93PTIpICsKICB0aGVtZV9idygpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpICsKICBnZ3RpdGxlKCJWYXJpb3VzIE1ldHJpY3MgYnkgQ291bnR5IikKYGBgClRoZSBgc2ZgIHBhY2thZ2UgYWxsb3dzIGRhdGEgdG8gYmUgaGVsZCBhbG9uZ3NpZGUgc3BhdGlhbCBnZW9tZXRyaWVzLCB3aGljaCBhbGxvd3MgdXMgdG8gam9pbiB2YXJpb3VzIGNhbGN1bGF0ZWQgZmllbGRzIGludG8gc3BhdGlhbCBvYmplY3RzLiAgSGVyZSBpcyB0aGUgbW9zdCBiYXNpYyBwbG90dGluZyBmdW5jdGlvbiBwcm92aWRlZCB2aWEgYHNmYC4KYGBge3J9CiMgTmF0aXZlIHNoYXBlZmlsZSBwbG90dGluZyB2aWEgc2YKY3NodDwtIGxlZnRfam9pbihjc2gsIHR2KQpwbG90KGNzaHRbLGMoIkRlYXRoc19wZXJfMTAwayIsIlBhdGllbnRzX3Blcl8xMDBrIiwiUG9wdWxhdGlvbiIsIkF2ZXJhZ2VfYWdlIildKQpgYGAKQWx0ZXJuYXRpdmVseSB3ZSBjYW4gY29tYmluZSB0aGUgZnVuY3Rpb25hbGl0eSBvZiBgc2ZgIHdpdGggb3VyIHRydXN0eSBgZ2dwbG90YCBieSBtYWtpbmcgdXNlIG9mIGBnZW9tX3NmYC4gV2UgaGF2ZSBwbG90dGVkIHRoZSBjb3VudHkgbmFtZXMgaW4gdGhlIHBvbHlnb24gY2VudHJlcyB2aWEgdGhlIGBzdF9jZW50cm9pZGAgZnVuY3Rpb24uIEhlcmUgd2UgaGF2ZSBwbG90dGVkIEFnZSwgYnV0IHlvdSBjb3VsZCBzdWJzdGl0dXRlIGZpZWxkIHRoYXQgY2FuIGJlIGNyZWF0ZWQgYXQgY291bnR5IGdyYW51bGFyaXR5LgpgYGB7ciwgbWVzc2FnZT0gRkFMU0V9CiMgQ29tYmluaW5nIHNoYXBlZmlsZSArIGRhdGEgcGxvdHRpbmcgd2l0aCBnZ3Bsb3QgYW5kIGdlb21fc2YKcHA8LSBzdF9jZW50cm9pZChjc2gpICU+JSBzdF9jb29yZGluYXRlcygpCmNvdW50aWVzPC0gY3NodCRDT1VOVFkKCmdncGxvdCgpICsKICBnZW9tX3NmKGRhdGE9IGNzaHQsIGFlcyhmaWxsPSBBdmVyYWdlX2FnZSkpICsKICBnZW9tX3RleHQoYWVzKHg9IHBwWywxXSwgeT1wcFssMl0sIGxhYmVsPSBjb3VudGllcyksIGZvbnRmYWNlPSJib2xkIiwgY29sb3IgPSAid2hpdGUiKSsKICB0aGVtZV92b2lkKCkgKwogIHRoZW1lKHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoZmlsbD0gImdyZXk2MCIpKSsKICBzY2FsZV9maWxsX2NvbnRpbnVvdXNfc2VxdWVudGlhbChwYWxldHRlID0gIlBsYXNtYSIsIHJldj0gRkFMU0UpCmBgYApUaGlzIGhhcyBqdXN0IGJlZW4gYSBxdWljayBicmVlemUgdGhyb3VnaCBvZiBzb21lIGFsdGVybmF0aXZlcyBmb3IgYmFzaWMgRURBLCBtYWtpbmcgc29tZSBhZGRpdGlvbmFsIHVzZSBvZiBnZW8tc3BhdGlhbCBlbGVtZW50cy4KCi4uCgoKLi4KCi4uCg==