In this tutorial, we show how to build a Shiny web application to
upload and visualize spatio-temporal data (Chang
et al. 2021). The app allows to upload a shapefile with a map of a
region, and a CSV file with the number of disease cases and population
in each of the areas in which the region is divided. The app includes a
variety of elements for interactive data visualization such as a map
built with leaflet (Cheng,
Karambelkar, and Xie 2021), a table built with DT
(Xie,
Cheng, and Tan 2021), and a time plot built with
dygraphs (Vanderkam
et al. 2018). The app also allows interactivity by giving the user
the possibility to select specific information to be shown. To build the
app, we use data of the number of lung cancer cases and population in
the 88 counties of Ohio, USA, from 1968 to 1988 (Figure 1).
Shiny
Shiny is a web application framework for R that enables to build
interactive web applications. A Shiny app can be built by creating a
directory (called, for example, appdir
) that contains an R
file (called, for example, app.R
) with three
components:
a user interface object (ui
) which controls the
layout and appearance of the app,
a server()
function with the instructions to build
the objects displayed in the ui
, and
a call to shinyApp()
that creates the app from the ui
/server
pair.
Shiny apps contain input and output objects. Inputs permit users
interact with the app by modifying their values. Outputs are objects
that are shown in the app. Outputs are reactive if they are built using
input values. The following code shows the content of a generic
app.R
file.
# load the shiny package
library(shiny)
# define user interface object
ui <- fluidPage(
*Input(inputId = myinput, label = mylabel, ...)
*Output(outputId = myoutput, ...)
)
# define server() function
server <- function(input, output){
output$myoutput <- render*({
# code to build the output.
# If it uses an input value (input$myinput),
# the output will be rebuilt whenever
# the input value changes
})}
# call to shinyApp() which returns the Shiny app
shinyApp(ui = ui, server = server)
The app.R
file is saved inside a directory called, for
example, appdir
. Then, the app can be launched by typing
runApp("appdir_path")
where appdir_path
is the
path of the directory that contains app.R
, or by clicking
the Run button of RStudio.
Setup
To build the Shiny app of this example, we need to download the
folder appdir
from the book webpage
and save it in our computer. This folder contains the following
subfolders:
data
which contains a file called
data.csv
with the data of lung cancer in Ohio, and a folder
called fe_2007_39_county
with the shapefile of Ohio,
and
www
with an image of a Shiny logo called
imageShiny.png
.
Structure of app.R
We start creating the Shiny app by writing a file called
app.R
with the minimum code needed to create a Shiny
app:
library(shiny)
# ui object
ui <- fluidPage( )
# server()
server <- function(input, output){ }
# shinyApp()
shinyApp(ui = ui, server = server)
We save this file with the name app.R
inside a directory
called appdir
. Then, we can launch the app by clicking the
Run App button at the top of the RStudio editor or by executing
runApp("appdir_path")
where appdir_path
is the
path of the directory that contains the app.R
file. The
Shiny app created has a blank user interface. In the following sections,
we include the elements and functionality we wish to have in the Shiny
app.
Layout
We build a user interface with a sidebar layout. This layout includes
a title panel, a sidebar panel for inputs on the left, and a main panel
for outputs on the right. The elements of the user interface are placed
within the fluidPage()
function and this permits the app to adjust automatically to the
dimensions of the browser window. The title of the app is added with titlePanel()
.
Then we write sidebarLayout()
to create a sidebar layout with input and output definitions. sidebarLayout()
takes the arguments sidebarPanel()
and mainPanel()
.
sidebarPanel()
creates a a sidebar panel for inputs on the left. mainPanel()
creates a main panel for displaying outputs on the right.
We can add content to the app by passing it as an argument to titlePanel()
,
sidebarPanel()
,
and mainPanel()
.
Here we have added texts with the description of the panels. Note that
to include multiple elements in the same panel, we need to separate them
with commas.
ui <- fluidPage(
titlePanel("title"),
sidebarLayout(
sidebarPanel("sidebar panel for inputs"),
mainPanel("main panel for outputs")
)
)
We can add content to the app by passing it as an argument to titlePanel()
,
sidebarPanel()
,
and mainPanel()
.
Here we have added texts with the description of the panels. Note that
to include multiple elements in the same panel, we need to separate them
with commas.
HTML content
Here we add a title, an image and a website link to the app. First we
add the title “Spatial app” to titlePanel()
.
We want to show this title in blue so we use p()
to create a paragraph with text and set the style to the #3474A7
color.
titlePanel(p("Spatial app", style = "color:#3474A7")),
Then we add an image with the img()
function. The images that we wish to include in the app must be in a
folder named www
in the same directory as the
app.R
file. We use the image called
imageShiny.png
and put it in the sidebarPanel()
by using the following instruction.
sidebarPanel(img(src = "imageShiny.png",
width = "70px", height = "70px")),
Here src
denotes the source of the image, and
height
and width
are the image height and
width in pixels, respectively. We also add text with a link referencing
the Shiny website.
p("Made with", a("Shiny",
href = "http://shiny.rstudio.com"), "."),
Note that in sidebarPanel()
we need to write the function to generate the website link and the
function to include the image separated with a comma.
sidebarPanel(
p("Made with", a("Shiny",
href = "http://shiny.rstudio.com"), "."),
img(src = "imageShiny.png",
width = "70px", height = "70px")),
Below is the content of app.R
we have until now. A
snapshot of the Shiny app is shown in Figure 2.
library(shiny)
# ui object
ui <- fluidPage(
titlePanel(p("Spatial app", style = "color:#3474A7")),
sidebarLayout(
sidebarPanel(
p("Made with", a("Shiny",
href = "http://shiny.rstudio.com"
), "."),
img(
src = "imageShiny.png",
width = "70px", height = "70px"
)
),
mainPanel("main panel for outputs")
)
)
# server()
server <- function(input, output) { }
# shinyApp()
shinyApp(ui = ui, server = server)
Read Data
Now we import the data we want to show in the app. The data is in the
folder called data
in the appdir
directory. To
read the CSV file data.csv
, we use the read.csv()
function, and to read the shapefile of Ohio that is in the folder
fe_2007_39_county
, we use the readOGR()
function of the rgdal package.
library(rgdal)
data <- read.csv("data/data.csv")
map <- readOGR("data/fe_2007_39_county/fe_2007_39_county.shp")
OGR data source with driver: ESRI Shapefile
Source: "/Users/angelazhang/Documents/R Shiny tutorials/data/fe_2007_39_county/fe_2007_39_county.shp", layer: "fe_2007_39_county"
with 88 features
It has 11 fields
We only need to read the data once so we write this code at the
beginning of app.R
outside the server()
function. By doing this, the code is not unnecessarily run more than
once and the performance of the app is not decreased.
Adding Outputs
Now we show the data in the Shiny app by including several outputs
for interactive visualization. Specifically, we include HTML widgets
created with JavaScript libraries and embedded in Shiny by using the
htmlwidgets package (Vaidyanathan
et al. 2021). The outputs are created using the following
packages:
DT to display the data in an interactive
table,
dygraphs to display a time plot with the data,
and
leaflet to create an interactive map.
Outputs are added in the app by including in ui
an
*Output()
function for the output, and adding in
server()
a render*()
function to the
output
that specifies how to build the output. For example,
to add a plot, we write in the ui
plotOutput()
and in server()
renderPlot()
.
Table using DT
We show the data in data
with an interactive table using
the DT package. In ui
we use DTOutput()
,
and in server()
we use renderDT()
.
library(DT)
# in ui
DTOutput(outputId = "table")
# in server()
output$table <- renderDT(data)
Time plot using dygrapghs
We show a time plot with the data with the dygraphs
package. In ui
we use dygraphOutput()
,
and in server()
we use renderDygraph()
.
dygraphs plots an extensible time series object
xts
. We can create this type of object using the xts()
function of the xts package (Ryan
and Ulrich 2020) specifying the values and the dates. The dates in
data
are the years of column year
. For now we
choose to plot the values of the variable cases
of
data
.
We need to construct a xts
object for each county and
then put them together in an object called dataxts
. For
each of the counties, we filter the data of the county and assign it to
datacounty
. Then we construct a xts
object
with values datacounty$cases
and dates
as.Date(paste0(data$year, "-01-01"))
. Then we assign the
name of the counties to each xts
(colnames(dataxts) <- counties
) so county names can be
shown in the legend.
dataxts <- NULL
counties <- unique(data$county)
for (l in 1:length(counties)) {
datacounty <- data[data$county == counties[l], ]
dd <- xts(
datacounty[, "cases"],
as.Date(paste0(datacounty$year, "-01-01"))
)
dataxts <- cbind(dataxts, dd)
}
colnames(dataxts) <- counties
Finally, we plot dataxts
with dygraph()
,
and use dyHighlight()
to allow mouse-over highlighting.
dygraph(dataxts) %>%
dyHighlight(highlightSeriesBackgroundAlpha = 0.2)
We customize the legend so that only the name of the highlighted
series is shown. To do this, one option is to write a css file with the
instructions and pass the css file to the dyCSS()
function. Alternatively, we can set the css directly in the code as
follows:
dygraph(dataxts) %>%
dyHighlight(highlightSeriesBackgroundAlpha = 0.2) -> d1
d1$x$css <- "
.dygraph-legend > span {display:none;}
.dygraph-legend > span.highlight { display: inline; }
"
d1
The complete code to build the dygraphs
object is the
following:
library(xts)
# in ui
dygraphOutput(outputId = "timetrend")
# in server()
output$timetrend <- renderDygraph({
dataxts <- NULL
counties <- unique(data$county)
for (l in 1:length(counties)) {
datacounty <- data[data$county == counties[l], ]
dd <- xts(
datacounty[, "cases"],
as.Date(paste0(datacounty$year, "-01-01"))
)
dataxts <- cbind(dataxts, dd)
}
colnames(dataxts) <- counties
dygraph(dataxts) %>%
dyHighlight(highlightSeriesBackgroundAlpha = 0.2) -> d1
d1$x$css <- "
.dygraph-legend > span {display:none;}
.dygraph-legend > span.highlight { display: inline; }
"
d1
})
Map using leaflet
We use the leaflet package to build an interactive
map. In ui
we use leafletOutput()
,
and in server()
we use renderLeaflet()
.
Inside renderLeaflet()
we write the instructions to return a leaflet map. First, we need to add
the data to the shapefile so the values can be plotted in a map. For now
we choose to plot the values of the variable in 1980. We create a
dataset called datafiltered
with the data corresponding to
that year. Then we add datafiltered
to
map@data
in an order such that the counties in the data
match the counties in the map.
datafiltered <- data[which(data$year == 1980), ]
# this returns positions of map@data$NAME in datafiltered$county
ordercounties <- match(map@data$NAME, datafiltered$county)
map@data <- datafiltered[ordercounties, ]
We create the leaflet map with the leaflet()
function, create a color palette with colorBin()
,
and add a legend with addLegend()
.
For now we choose to plot the values of variable cases
. We
also add labels with the area names and values that are displayed when
the mouse is over the map.
library(leaflet)
# in ui
leafletOutput(outputId = "map")
# in server()
output$map <- renderLeaflet({
# add data to map
datafiltered <- data[which(data$year == 1980), ]
ordercounties <- match(map@data$NAME, datafiltered$county)
map@data <- datafiltered[ordercounties, ]
# create leaflet
pal <- colorBin("YlOrRd", domain = map$cases, bins = 7)
labels <- sprintf("%s: %g", map$county, map$cases) %>%
lapply(htmltools::HTML)
l <- leaflet(map) %>%
addTiles() %>%
addPolygons(
fillColor = ~ pal(cases),
color = "white",
dashArray = "3",
fillOpacity = 0.7,
label = labels
) %>%
leaflet::addLegend(
pal = pal, values = ~cases,
opacity = 0.7, title = NULL
)
})
Below is the content of app.R
we have until now. A
snapshot of the Shiny app is shown in Figure 3.
library(shiny)
library(rgdal)
library(DT)
library(dygraphs)
library(xts)
library(leaflet)
data <- read.csv("data/data.csv")
map <- readOGR("data/fe_2007_39_county/fe_2007_39_county.shp")
# ui object
ui <- fluidPage(
titlePanel(p("Spatial app", style = "color:#3474A7")),
sidebarLayout(
sidebarPanel(
p("Made with", a("Shiny",
href = "http://shiny.rstudio.com"
), "."),
img(
src = "imageShiny.png",
width = "70px", height = "70px"
)
),
mainPanel(
leafletOutput(outputId = "map"),
dygraphOutput(outputId = "timetrend"),
DTOutput(outputId = "table")
)
)
)
# server()
server <- function(input, output) {
output$table <- renderDT(data)
output$timetrend <- renderDygraph({
dataxts <- NULL
counties <- unique(data$county)
for (l in 1:length(counties)) {
datacounty <- data[data$county == counties[l], ]
dd <- xts(
datacounty[, "cases"],
as.Date(paste0(datacounty$year, "-01-01"))
)
dataxts <- cbind(dataxts, dd)
}
colnames(dataxts) <- counties
dygraph(dataxts) %>%
dyHighlight(highlightSeriesBackgroundAlpha = 0.2) -> d1
d1$x$css <- "
.dygraph-legend > span {display:none;}
.dygraph-legend > span.highlight { display: inline; }
"
d1
})
output$map <- renderLeaflet({
# Add data to map
datafiltered <- data[which(data$year == 1980), ]
ordercounties <- match(map@data$NAME, datafiltered$county)
map@data <- datafiltered[ordercounties, ]
# Create leaflet
pal <- colorBin("YlOrRd", domain = map$cases, bins = 7)
labels <- sprintf("%s: %g", map$county, map$cases) %>%
lapply(htmltools::HTML)
l <- leaflet(map) %>%
addTiles() %>%
addPolygons(
fillColor = ~ pal(cases),
color = "white",
dashArray = "3",
fillOpacity = 0.7,
label = labels
) %>%
leaflet::addLegend(
pal = pal, values = ~cases,
opacity = 0.7, title = NULL
)
})
}
# shinyApp()
shinyApp(ui = ui, server = server)
LS0tCnRpdGxlOiAiSW50cm9kdWN0aW9uIHRvIFIgU2hpbnkgcGFydDEiCm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KCkluIHRoaXMgdHV0b3JpYWwsIHdlIHNob3cgaG93IHRvIGJ1aWxkIGEgU2hpbnkgd2ViIGFwcGxpY2F0aW9uIHRvIHVwbG9hZCBhbmQgdmlzdWFsaXplIHNwYXRpby10ZW1wb3JhbCBkYXRhIChbQ2hhbmcgZXQgYWwuIDIwMjFdKGh0dHBzOi8vd3d3LnBhdWxhbW9yYWdhLmNvbS9ib29rLWdlb3NwYXRpYWwvcmVmZXJlbmNlcy5odG1sI3JlZi1SLXNoaW55KSkuIFRoZSBhcHAgYWxsb3dzIHRvIHVwbG9hZCBhIHNoYXBlZmlsZSB3aXRoIGEgbWFwIG9mIGEgcmVnaW9uLCBhbmQgYSBDU1YgZmlsZSB3aXRoIHRoZSBudW1iZXIgb2YgZGlzZWFzZSBjYXNlcyBhbmQgcG9wdWxhdGlvbiBpbiBlYWNoIG9mIHRoZSBhcmVhcyBpbiB3aGljaCB0aGUgcmVnaW9uIGlzIGRpdmlkZWQuIFRoZSBhcHAgaW5jbHVkZXMgYSB2YXJpZXR5IG9mIGVsZW1lbnRzIGZvciBpbnRlcmFjdGl2ZSBkYXRhIHZpc3VhbGl6YXRpb24gc3VjaCBhcyBhIG1hcCBidWlsdCB3aXRoICoqbGVhZmxldCoqIChbQ2hlbmcsIEthcmFtYmVsa2FyLCBhbmQgWGllIDIwMjFdKGh0dHBzOi8vd3d3LnBhdWxhbW9yYWdhLmNvbS9ib29rLWdlb3NwYXRpYWwvcmVmZXJlbmNlcy5odG1sI3JlZi1SLWxlYWZsZXQpKSwgYSB0YWJsZSBidWlsdCB3aXRoICoqRFQqKiAoW1hpZSwgQ2hlbmcsIGFuZCBUYW4gMjAyMV0oaHR0cHM6Ly93d3cucGF1bGFtb3JhZ2EuY29tL2Jvb2stZ2Vvc3BhdGlhbC9yZWZlcmVuY2VzLmh0bWwjcmVmLVItRFQpKSwgYW5kIGEgdGltZSBwbG90IGJ1aWx0IHdpdGggKipkeWdyYXBocyoqIChbVmFuZGVya2FtIGV0IGFsLiAyMDE4XShodHRwczovL3d3dy5wYXVsYW1vcmFnYS5jb20vYm9vay1nZW9zcGF0aWFsL3JlZmVyZW5jZXMuaHRtbCNyZWYtUi1keWdyYXBocykpLiBUaGUgYXBwIGFsc28gYWxsb3dzIGludGVyYWN0aXZpdHkgYnkgZ2l2aW5nIHRoZSB1c2VyIHRoZSBwb3NzaWJpbGl0eSB0byBzZWxlY3Qgc3BlY2lmaWMgaW5mb3JtYXRpb24gdG8gYmUgc2hvd24uIFRvIGJ1aWxkIHRoZSBhcHAsIHdlIHVzZSBkYXRhIG9mIHRoZSBudW1iZXIgb2YgbHVuZyBjYW5jZXIgY2FzZXMgYW5kIHBvcHVsYXRpb24gaW4gdGhlIDg4IGNvdW50aWVzIG9mIE9oaW8sIFVTQSwgZnJvbSAxOTY4IHRvIDE5ODggKEZpZ3VyZSAxKS4KCiFbRmlndXJlMS4gU25hcHNob3Qgb2YgdGhlIFNoaW55IGFwcCB0byB1cGxvYWQgYW5kIHZpc3VhbGl6ZSBzcGF0aW8tdGVtcG9yYWwgZGF0YS5dKGltYWdlcy9GaWd1cmUxLnBuZyl7I2ZpZzF9CgojIyBTaGlueQoKU2hpbnkgaXMgYSB3ZWIgYXBwbGljYXRpb24gZnJhbWV3b3JrIGZvciBSIHRoYXQgZW5hYmxlcyB0byBidWlsZCBpbnRlcmFjdGl2ZSB3ZWIgYXBwbGljYXRpb25zLiBBIFNoaW55IGFwcCBjYW4gYmUgYnVpbHQgYnkgY3JlYXRpbmcgYSBkaXJlY3RvcnkgKGNhbGxlZCwgZm9yIGV4YW1wbGUsIGBhcHBkaXJgKSB0aGF0IGNvbnRhaW5zIGFuIFIgZmlsZSAoY2FsbGVkLCBmb3IgZXhhbXBsZSwgYGFwcC5SYCkgd2l0aCB0aHJlZSBjb21wb25lbnRzOgoKLSAgIGEgdXNlciBpbnRlcmZhY2Ugb2JqZWN0IChgdWlgKSB3aGljaCBjb250cm9scyB0aGUgbGF5b3V0IGFuZCBhcHBlYXJhbmNlIG9mIHRoZSBhcHAsCgotICAgYSBgc2VydmVyKClgIGZ1bmN0aW9uIHdpdGggdGhlIGluc3RydWN0aW9ucyB0byBidWlsZCB0aGUgb2JqZWN0cyBkaXNwbGF5ZWQgaW4gdGhlIGB1aWAsIGFuZAoKLSAgIGEgY2FsbCB0byBbYHNoaW55QXBwKClgXShodHRwczovL3JkcnIuaW8vcGtnL3NoaW55L21hbi9zaGlueUFwcC5odG1sKSB0aGF0IGNyZWF0ZXMgdGhlIGFwcCBmcm9tIHRoZSBgdWlgL2BzZXJ2ZXJgIHBhaXIuCgpTaGlueSBhcHBzIGNvbnRhaW4gaW5wdXQgYW5kIG91dHB1dCBvYmplY3RzLiBJbnB1dHMgcGVybWl0IHVzZXJzIGludGVyYWN0IHdpdGggdGhlIGFwcCBieSBtb2RpZnlpbmcgdGhlaXIgdmFsdWVzLiBPdXRwdXRzIGFyZSBvYmplY3RzIHRoYXQgYXJlIHNob3duIGluIHRoZSBhcHAuIE91dHB1dHMgYXJlIHJlYWN0aXZlIGlmIHRoZXkgYXJlIGJ1aWx0IHVzaW5nIGlucHV0IHZhbHVlcy4gVGhlIGZvbGxvd2luZyBjb2RlIHNob3dzIHRoZSBjb250ZW50IG9mIGEgZ2VuZXJpYyBgYXBwLlJgIGZpbGUuCgogICAgIyBsb2FkIHRoZSBzaGlueSBwYWNrYWdlCiAgICBsaWJyYXJ5KHNoaW55KQoKICAgICMgZGVmaW5lIHVzZXIgaW50ZXJmYWNlIG9iamVjdAogICAgdWkgPC0gZmx1aWRQYWdlKAogICAgICAqSW5wdXQoaW5wdXRJZCA9IG15aW5wdXQsIGxhYmVsID0gbXlsYWJlbCwgLi4uKQogICAgICAqT3V0cHV0KG91dHB1dElkID0gbXlvdXRwdXQsIC4uLikKICAgICkKCiAgICAjIGRlZmluZSBzZXJ2ZXIoKSBmdW5jdGlvbgogICAgc2VydmVyIDwtIGZ1bmN0aW9uKGlucHV0LCBvdXRwdXQpewogICAgICBvdXRwdXQkbXlvdXRwdXQgPC0gcmVuZGVyKih7CiAgICAgICAgIyBjb2RlIHRvIGJ1aWxkIHRoZSBvdXRwdXQuCiAgICAgICAgIyBJZiBpdCB1c2VzIGFuIGlucHV0IHZhbHVlIChpbnB1dCRteWlucHV0KSwKICAgICAgICAjIHRoZSBvdXRwdXQgd2lsbCBiZSByZWJ1aWx0IHdoZW5ldmVyCiAgICAgICAgIyB0aGUgaW5wdXQgdmFsdWUgY2hhbmdlcwogICAgICB9KX0KCiAgICAjIGNhbGwgdG8gc2hpbnlBcHAoKSB3aGljaCByZXR1cm5zIHRoZSBTaGlueSBhcHAKICAgIHNoaW55QXBwKHVpID0gdWksIHNlcnZlciA9IHNlcnZlcikKClRoZSBgYXBwLlJgIGZpbGUgaXMgc2F2ZWQgaW5zaWRlIGEgZGlyZWN0b3J5IGNhbGxlZCwgZm9yIGV4YW1wbGUsIGBhcHBkaXJgLiBUaGVuLCB0aGUgYXBwIGNhbiBiZSBsYXVuY2hlZCBieSB0eXBpbmcgYHJ1bkFwcCgiYXBwZGlyX3BhdGgiKWAgd2hlcmUgYGFwcGRpcl9wYXRoYCBpcyB0aGUgcGF0aCBvZiB0aGUgZGlyZWN0b3J5IHRoYXQgY29udGFpbnMgYGFwcC5SYCwgb3IgYnkgY2xpY2tpbmcgdGhlIFJ1biBidXR0b24gb2YgUlN0dWRpby4KCiMjICoqU2V0dXAqKgoKVG8gYnVpbGQgdGhlIFNoaW55IGFwcCBvZiB0aGlzIGV4YW1wbGUsIHdlIG5lZWQgdG8gZG93bmxvYWQgdGhlIGZvbGRlciBgYXBwZGlyYCBmcm9tIHRoZSBib29rIFt3ZWJwYWdlXShodHRwczovL3BhdWxhLW1vcmFnYS5naXRodWIuaW8vYm9vay1nZW9zcGF0aWFsLWluZm8pIGFuZCBzYXZlIGl0IGluIG91ciBjb21wdXRlci4gVGhpcyBmb2xkZXIgY29udGFpbnMgdGhlIGZvbGxvd2luZyBzdWJmb2xkZXJzOgoKLSAgIGBkYXRhYCB3aGljaCBjb250YWlucyBhIGZpbGUgY2FsbGVkIGBkYXRhLmNzdmAgd2l0aCB0aGUgZGF0YSBvZiBsdW5nIGNhbmNlciBpbiBPaGlvLCBhbmQgYSBmb2xkZXIgY2FsbGVkIGBmZV8yMDA3XzM5X2NvdW50eWAgd2l0aCB0aGUgc2hhcGVmaWxlIG9mIE9oaW8sIGFuZAoKLSAgIGB3d3dgIHdpdGggYW4gaW1hZ2Ugb2YgYSBTaGlueSBsb2dvIGNhbGxlZCBgaW1hZ2VTaGlueS5wbmdgLgoKIyMgKipTdHJ1Y3R1cmUgb2YgYGFwcC5SYCoqCgpXZSBzdGFydCBjcmVhdGluZyB0aGUgU2hpbnkgYXBwIGJ5IHdyaXRpbmcgYSBmaWxlIGNhbGxlZCBgYXBwLlJgIHdpdGggdGhlIG1pbmltdW0gY29kZSBuZWVkZWQgdG8gY3JlYXRlIGEgU2hpbnkgYXBwOgoKICAgIGxpYnJhcnkoc2hpbnkpCgogICAgIyB1aSBvYmplY3QKICAgIHVpIDwtIGZsdWlkUGFnZSggKQoKICAgICMgc2VydmVyKCkKICAgIHNlcnZlciA8LSBmdW5jdGlvbihpbnB1dCwgb3V0cHV0KXsgfQoKICAgICMgc2hpbnlBcHAoKQogICAgc2hpbnlBcHAodWkgPSB1aSwgc2VydmVyID0gc2VydmVyKQoKV2Ugc2F2ZSB0aGlzIGZpbGUgd2l0aCB0aGUgbmFtZSBgYXBwLlJgIGluc2lkZSBhIGRpcmVjdG9yeSBjYWxsZWQgYGFwcGRpcmAuIFRoZW4sIHdlIGNhbiBsYXVuY2ggdGhlIGFwcCBieSBjbGlja2luZyB0aGUgUnVuIEFwcCBidXR0b24gYXQgdGhlIHRvcCBvZiB0aGUgUlN0dWRpbyBlZGl0b3Igb3IgYnkgZXhlY3V0aW5nIGBydW5BcHAoImFwcGRpcl9wYXRoIilgIHdoZXJlIGBhcHBkaXJfcGF0aGAgaXMgdGhlIHBhdGggb2YgdGhlIGRpcmVjdG9yeSB0aGF0IGNvbnRhaW5zIHRoZSBgYXBwLlJgIGZpbGUuIFRoZSBTaGlueSBhcHAgY3JlYXRlZCBoYXMgYSBibGFuayB1c2VyIGludGVyZmFjZS4gSW4gdGhlIGZvbGxvd2luZyBzZWN0aW9ucywgd2UgaW5jbHVkZSB0aGUgZWxlbWVudHMgYW5kIGZ1bmN0aW9uYWxpdHkgd2Ugd2lzaCB0byBoYXZlIGluIHRoZSBTaGlueSBhcHAuCgojIyBMYXlvdXQKCldlIGJ1aWxkIGEgdXNlciBpbnRlcmZhY2Ugd2l0aCBhIHNpZGViYXIgbGF5b3V0LiBUaGlzIGxheW91dCBpbmNsdWRlcyBhIHRpdGxlIHBhbmVsLCBhIHNpZGViYXIgcGFuZWwgZm9yIGlucHV0cyBvbiB0aGUgbGVmdCwgYW5kIGEgbWFpbiBwYW5lbCBmb3Igb3V0cHV0cyBvbiB0aGUgcmlnaHQuIFRoZSBlbGVtZW50cyBvZiB0aGUgdXNlciBpbnRlcmZhY2UgYXJlIHBsYWNlZCB3aXRoaW4gdGhlIFtgZmx1aWRQYWdlKClgXShodHRwczovL3JkcnIuaW8vcGtnL3NoaW55L21hbi9mbHVpZFBhZ2UuaHRtbCkgZnVuY3Rpb24gYW5kIHRoaXMgcGVybWl0cyB0aGUgYXBwIHRvIGFkanVzdCBhdXRvbWF0aWNhbGx5IHRvIHRoZSBkaW1lbnNpb25zIG9mIHRoZSBicm93c2VyIHdpbmRvdy4gVGhlIHRpdGxlIG9mIHRoZSBhcHAgaXMgYWRkZWQgd2l0aCBbYHRpdGxlUGFuZWwoKWBdKGh0dHBzOi8vcmRyci5pby9wa2cvc2hpbnkvbWFuL3RpdGxlUGFuZWwuaHRtbCkuIFRoZW4gd2Ugd3JpdGUgW2BzaWRlYmFyTGF5b3V0KClgXShodHRwczovL3JkcnIuaW8vcGtnL3NoaW55L21hbi9zaWRlYmFyTGF5b3V0Lmh0bWwpIHRvIGNyZWF0ZSBhIHNpZGViYXIgbGF5b3V0IHdpdGggaW5wdXQgYW5kIG91dHB1dCBkZWZpbml0aW9ucy4gW2BzaWRlYmFyTGF5b3V0KClgXShodHRwczovL3JkcnIuaW8vcGtnL3NoaW55L21hbi9zaWRlYmFyTGF5b3V0Lmh0bWwpIHRha2VzIHRoZSBhcmd1bWVudHMgW2BzaWRlYmFyUGFuZWwoKWBdKGh0dHBzOi8vcmRyci5pby9wa2cvc2hpbnkvbWFuL3NpZGViYXJMYXlvdXQuaHRtbCkgYW5kIFtgbWFpblBhbmVsKClgXShodHRwczovL3JkcnIuaW8vcGtnL3NoaW55L21hbi9zaWRlYmFyTGF5b3V0Lmh0bWwpLiBbYHNpZGViYXJQYW5lbCgpYF0oaHR0cHM6Ly9yZHJyLmlvL3BrZy9zaGlueS9tYW4vc2lkZWJhckxheW91dC5odG1sKSBjcmVhdGVzIGEgYSBzaWRlYmFyIHBhbmVsIGZvciBpbnB1dHMgb24gdGhlIGxlZnQuIFtgbWFpblBhbmVsKClgXShodHRwczovL3JkcnIuaW8vcGtnL3NoaW55L21hbi9zaWRlYmFyTGF5b3V0Lmh0bWwpIGNyZWF0ZXMgYSBtYWluIHBhbmVsIGZvciBkaXNwbGF5aW5nIG91dHB1dHMgb24gdGhlIHJpZ2h0LlwKV2UgY2FuIGFkZCBjb250ZW50IHRvIHRoZSBhcHAgYnkgcGFzc2luZyBpdCBhcyBhbiBhcmd1bWVudCB0byBbYHRpdGxlUGFuZWwoKWBdKGh0dHBzOi8vcmRyci5pby9wa2cvc2hpbnkvbWFuL3RpdGxlUGFuZWwuaHRtbCksIFtgc2lkZWJhclBhbmVsKClgXShodHRwczovL3JkcnIuaW8vcGtnL3NoaW55L21hbi9zaWRlYmFyTGF5b3V0Lmh0bWwpLCBhbmQgW2BtYWluUGFuZWwoKWBdKGh0dHBzOi8vcmRyci5pby9wa2cvc2hpbnkvbWFuL3NpZGViYXJMYXlvdXQuaHRtbCkuIEhlcmUgd2UgaGF2ZSBhZGRlZCB0ZXh0cyB3aXRoIHRoZSBkZXNjcmlwdGlvbiBvZiB0aGUgcGFuZWxzLiBOb3RlIHRoYXQgdG8gaW5jbHVkZSBtdWx0aXBsZSBlbGVtZW50cyBpbiB0aGUgc2FtZSBwYW5lbCwgd2UgbmVlZCB0byBzZXBhcmF0ZSB0aGVtIHdpdGggY29tbWFzLgoKICAgIHVpIDwtIGZsdWlkUGFnZSgKICAgICAgdGl0bGVQYW5lbCgidGl0bGUiKSwKICAgICAgc2lkZWJhckxheW91dCgKICAgICAgICBzaWRlYmFyUGFuZWwoInNpZGViYXIgcGFuZWwgZm9yIGlucHV0cyIpLAogICAgICAgIG1haW5QYW5lbCgibWFpbiBwYW5lbCBmb3Igb3V0cHV0cyIpCiAgICAgICkKICAgICkKCldlIGNhbiBhZGQgY29udGVudCB0byB0aGUgYXBwIGJ5IHBhc3NpbmcgaXQgYXMgYW4gYXJndW1lbnQgdG8gW2B0aXRsZVBhbmVsKClgXShodHRwczovL3JkcnIuaW8vcGtnL3NoaW55L21hbi90aXRsZVBhbmVsLmh0bWwpLCBbYHNpZGViYXJQYW5lbCgpYF0oaHR0cHM6Ly9yZHJyLmlvL3BrZy9zaGlueS9tYW4vc2lkZWJhckxheW91dC5odG1sKSwgYW5kIFtgbWFpblBhbmVsKClgXShodHRwczovL3JkcnIuaW8vcGtnL3NoaW55L21hbi9zaWRlYmFyTGF5b3V0Lmh0bWwpLiBIZXJlIHdlIGhhdmUgYWRkZWQgdGV4dHMgd2l0aCB0aGUgZGVzY3JpcHRpb24gb2YgdGhlIHBhbmVscy4gTm90ZSB0aGF0IHRvIGluY2x1ZGUgbXVsdGlwbGUgZWxlbWVudHMgaW4gdGhlIHNhbWUgcGFuZWwsIHdlIG5lZWQgdG8gc2VwYXJhdGUgdGhlbSB3aXRoIGNvbW1hcy4KCiMjIEhUTUwgY29udGVudAoKSGVyZSB3ZSBhZGQgYSB0aXRsZSwgYW4gaW1hZ2UgYW5kIGEgd2Vic2l0ZSBsaW5rIHRvIHRoZSBhcHAuIEZpcnN0IHdlIGFkZCB0aGUgdGl0bGUgIlNwYXRpYWwgYXBwIiB0byBbYHRpdGxlUGFuZWwoKWBdKGh0dHBzOi8vcmRyci5pby9wa2cvc2hpbnkvbWFuL3RpdGxlUGFuZWwuaHRtbCkuIFdlIHdhbnQgdG8gc2hvdyB0aGlzIHRpdGxlIGluIGJsdWUgc28gd2UgdXNlIFtgcCgpYF0oaHR0cHM6Ly9yZHJyLmlvL3BrZy9odG1sdG9vbHMvbWFuL2J1aWxkZXIuaHRtbCkgdG8gY3JlYXRlIGEgcGFyYWdyYXBoIHdpdGggdGV4dCBhbmQgc2V0IHRoZSBzdHlsZSB0byB0aGUgIzM0NzRBNyBjb2xvci4KCiAgICB0aXRsZVBhbmVsKHAoIlNwYXRpYWwgYXBwIiwgc3R5bGUgPSAiY29sb3I6IzM0NzRBNyIpKSwKClRoZW4gd2UgYWRkIGFuIGltYWdlIHdpdGggdGhlIFtgaW1nKClgXShodHRwczovL3JkcnIuaW8vcGtnL2h0bWx0b29scy9tYW4vYnVpbGRlci5odG1sKSBmdW5jdGlvbi4gVGhlIGltYWdlcyB0aGF0IHdlIHdpc2ggdG8gaW5jbHVkZSBpbiB0aGUgYXBwIG11c3QgYmUgaW4gYSBmb2xkZXIgbmFtZWQgYHd3d2AgaW4gdGhlIHNhbWUgZGlyZWN0b3J5IGFzIHRoZSBgYXBwLlJgIGZpbGUuIFdlIHVzZSB0aGUgaW1hZ2UgY2FsbGVkIGBpbWFnZVNoaW55LnBuZ2AgYW5kIHB1dCBpdCBpbiB0aGUgW2BzaWRlYmFyUGFuZWwoKWBdKGh0dHBzOi8vcmRyci5pby9wa2cvc2hpbnkvbWFuL3NpZGViYXJMYXlvdXQuaHRtbCkgYnkgdXNpbmcgdGhlIGZvbGxvd2luZyBpbnN0cnVjdGlvbi4KCiAgICBzaWRlYmFyUGFuZWwoaW1nKHNyYyA9ICJpbWFnZVNoaW55LnBuZyIsCiAgICAgICAgICAgICAgICAgICAgIHdpZHRoID0gIjcwcHgiLCBoZWlnaHQgPSAiNzBweCIpKSwKCkhlcmUgYHNyY2AgZGVub3RlcyB0aGUgc291cmNlIG9mIHRoZSBpbWFnZSwgYW5kIGBoZWlnaHRgIGFuZCBgd2lkdGhgIGFyZSB0aGUgaW1hZ2UgaGVpZ2h0IGFuZCB3aWR0aCBpbiBwaXhlbHMsIHJlc3BlY3RpdmVseS4gV2UgYWxzbyBhZGQgdGV4dCB3aXRoIGEgbGluayByZWZlcmVuY2luZyB0aGUgU2hpbnkgd2Vic2l0ZS4KCiAgICBwKCJNYWRlIHdpdGgiLCBhKCJTaGlueSIsCiAgICAgICAgICAgICAgICAgICAgIGhyZWYgPSAiaHR0cDovL3NoaW55LnJzdHVkaW8uY29tIiksICIuIiksCgpOb3RlIHRoYXQgaW4gW2BzaWRlYmFyUGFuZWwoKWBdKGh0dHBzOi8vcmRyci5pby9wa2cvc2hpbnkvbWFuL3NpZGViYXJMYXlvdXQuaHRtbCkgd2UgbmVlZCB0byB3cml0ZSB0aGUgZnVuY3Rpb24gdG8gZ2VuZXJhdGUgdGhlIHdlYnNpdGUgbGluayBhbmQgdGhlIGZ1bmN0aW9uIHRvIGluY2x1ZGUgdGhlIGltYWdlIHNlcGFyYXRlZCB3aXRoIGEgY29tbWEuCgogICAgc2lkZWJhclBhbmVsKAogICAgcCgiTWFkZSB3aXRoIiwgYSgiU2hpbnkiLAogICAgICAgICAgICAgICAgICAgICBocmVmID0gImh0dHA6Ly9zaGlueS5yc3R1ZGlvLmNvbSIpLCAiLiIpLAogICAgaW1nKHNyYyA9ICJpbWFnZVNoaW55LnBuZyIsCiAgICAgICAgd2lkdGggPSAiNzBweCIsIGhlaWdodCA9ICI3MHB4IikpLAoKQmVsb3cgaXMgdGhlIGNvbnRlbnQgb2YgYGFwcC5SYCB3ZSBoYXZlIHVudGlsIG5vdy4gQSBzbmFwc2hvdCBvZiB0aGUgU2hpbnkgYXBwIGlzIHNob3duIGluIEZpZ3VyZSAyLgoKYGBge3J9CmxpYnJhcnkoc2hpbnkpCgojIHVpIG9iamVjdAp1aSA8LSBmbHVpZFBhZ2UoCiAgdGl0bGVQYW5lbChwKCJTcGF0aWFsIGFwcCIsIHN0eWxlID0gImNvbG9yOiMzNDc0QTciKSksCiAgc2lkZWJhckxheW91dCgKICAgIHNpZGViYXJQYW5lbCgKICAgICAgcCgiTWFkZSB3aXRoIiwgYSgiU2hpbnkiLAogICAgICAgIGhyZWYgPSAiaHR0cDovL3NoaW55LnJzdHVkaW8uY29tIgogICAgICApLCAiLiIpLAogICAgICBpbWcoCiAgICAgICAgc3JjID0gImltYWdlU2hpbnkucG5nIiwKICAgICAgICB3aWR0aCA9ICI3MHB4IiwgaGVpZ2h0ID0gIjcwcHgiCiAgICAgICkKICAgICksCiAgICBtYWluUGFuZWwoIm1haW4gcGFuZWwgZm9yIG91dHB1dHMiKQogICkKKQoKIyBzZXJ2ZXIoKQpzZXJ2ZXIgPC0gZnVuY3Rpb24oaW5wdXQsIG91dHB1dCkgeyB9CgojIHNoaW55QXBwKCkKc2hpbnlBcHAodWkgPSB1aSwgc2VydmVyID0gc2VydmVyKQoKYGBgCgohW0ZpZ3VyZSAyOiBTbmFwc2hvdCBvZiB0aGUgU2hpbnkgYXBwIGFmdGVyIGluY2x1ZGluZyBhIHRpdGxlLCBhbiBpbWFnZSBhbmQgYSB3ZWJzaXRlIGxpbmtdKGltYWdlcy9GaWd1cmUyLnBuZyl7I2ZpaWcyfQoKIyMgUmVhZCBEYXRhCgpOb3cgd2UgaW1wb3J0IHRoZSBkYXRhIHdlIHdhbnQgdG8gc2hvdyBpbiB0aGUgYXBwLiBUaGUgZGF0YSBpcyBpbiB0aGUgZm9sZGVyIGNhbGxlZCBgZGF0YWAgaW4gdGhlIGBhcHBkaXJgIGRpcmVjdG9yeS4gVG8gcmVhZCB0aGUgQ1NWIGZpbGUgYGRhdGEuY3N2YCwgd2UgdXNlIHRoZSBbYHJlYWQuY3N2KClgXShodHRwczovL3JkcnIuaW8vci91dGlscy9yZWFkLnRhYmxlLmh0bWwpIGZ1bmN0aW9uLCBhbmQgdG8gcmVhZCB0aGUgc2hhcGVmaWxlIG9mIE9oaW8gdGhhdCBpcyBpbiB0aGUgZm9sZGVyIGBmZV8yMDA3XzM5X2NvdW50eWAsIHdlIHVzZSB0aGUgW2ByZWFkT0dSKClgXShodHRwOi8vcmdkYWwuci1mb3JnZS5yLXByb2plY3Qub3JnL3JlZmVyZW5jZS9yZWFkT0dSLmh0bWwpIGZ1bmN0aW9uIG9mIHRoZSAqKnJnZGFsKiogcGFja2FnZS4KCmBgYHtyfQpsaWJyYXJ5KHJnZGFsKQpkYXRhIDwtIHJlYWQuY3N2KCJkYXRhL2RhdGEuY3N2IikKbWFwIDwtIHJlYWRPR1IoImRhdGEvZmVfMjAwN18zOV9jb3VudHkvZmVfMjAwN18zOV9jb3VudHkuc2hwIikKYGBgCgpXZSBvbmx5IG5lZWQgdG8gcmVhZCB0aGUgZGF0YSBvbmNlIHNvIHdlIHdyaXRlIHRoaXMgY29kZSBhdCB0aGUgYmVnaW5uaW5nIG9mIGBhcHAuUmAgb3V0c2lkZSB0aGUgYHNlcnZlcigpYCBmdW5jdGlvbi4gQnkgZG9pbmcgdGhpcywgdGhlIGNvZGUgaXMgbm90IHVubmVjZXNzYXJpbHkgcnVuIG1vcmUgdGhhbiBvbmNlIGFuZCB0aGUgcGVyZm9ybWFuY2Ugb2YgdGhlIGFwcCBpcyBub3QgZGVjcmVhc2VkLgoKIyMgQWRkaW5nIE91dHB1dHMKCk5vdyB3ZSBzaG93IHRoZSBkYXRhIGluIHRoZSBTaGlueSBhcHAgYnkgaW5jbHVkaW5nIHNldmVyYWwgb3V0cHV0cyBmb3IgaW50ZXJhY3RpdmUgdmlzdWFsaXphdGlvbi4gU3BlY2lmaWNhbGx5LCB3ZSBpbmNsdWRlIEhUTUwgd2lkZ2V0cyBjcmVhdGVkIHdpdGggSmF2YVNjcmlwdCBsaWJyYXJpZXMgYW5kIGVtYmVkZGVkIGluIFNoaW55IGJ5IHVzaW5nIHRoZSAqKmh0bWx3aWRnZXRzKiogcGFja2FnZSAoW1ZhaWR5YW5hdGhhbiBldCBhbC4gMjAyMV0oaHR0cHM6Ly93d3cucGF1bGFtb3JhZ2EuY29tL2Jvb2stZ2Vvc3BhdGlhbC9yZWZlcmVuY2VzLmh0bWwjcmVmLVItaHRtbHdpZGdldHMpKS4gVGhlIG91dHB1dHMgYXJlIGNyZWF0ZWQgdXNpbmcgdGhlIGZvbGxvd2luZyBwYWNrYWdlczoKCi0gICAqKkRUKiogdG8gZGlzcGxheSB0aGUgZGF0YSBpbiBhbiBpbnRlcmFjdGl2ZSB0YWJsZSwKCi0gICAqKmR5Z3JhcGhzKiogdG8gZGlzcGxheSBhIHRpbWUgcGxvdCB3aXRoIHRoZSBkYXRhLCBhbmQKCi0gICAqKmxlYWZsZXQqKiB0byBjcmVhdGUgYW4gaW50ZXJhY3RpdmUgbWFwLgoKT3V0cHV0cyBhcmUgYWRkZWQgaW4gdGhlIGFwcCBieSBpbmNsdWRpbmcgaW4gYHVpYCBhbiBgKk91dHB1dCgpYCBmdW5jdGlvbiBmb3IgdGhlIG91dHB1dCwgYW5kIGFkZGluZyBpbiBgc2VydmVyKClgIGEgYHJlbmRlciooKWAgZnVuY3Rpb24gdG8gdGhlIGBvdXRwdXRgIHRoYXQgc3BlY2lmaWVzIGhvdyB0byBidWlsZCB0aGUgb3V0cHV0LiBGb3IgZXhhbXBsZSwgdG8gYWRkIGEgcGxvdCwgd2Ugd3JpdGUgaW4gdGhlIGB1aWAgW2BwbG90T3V0cHV0KClgXShodHRwczovL3JkcnIuaW8vcGtnL3NoaW55L21hbi9wbG90T3V0cHV0Lmh0bWwpIGFuZCBpbiBgc2VydmVyKClgIFtgcmVuZGVyUGxvdCgpYF0oaHR0cHM6Ly9yZHJyLmlvL3BrZy9zaGlueS9tYW4vcmVuZGVyUGxvdC5odG1sKS4KCiMjIyBUYWJsZSB1c2luZyBEVAoKV2Ugc2hvdyB0aGUgZGF0YSBpbiBgZGF0YWAgd2l0aCBhbiBpbnRlcmFjdGl2ZSB0YWJsZSB1c2luZyB0aGUgKipEVCoqIHBhY2thZ2UuIEluIGB1aWAgd2UgdXNlIFtgRFRPdXRwdXQoKWBdKGh0dHBzOi8vcmRyci5pby9wa2cvRFQvbWFuL2RhdGFUYWJsZU91dHB1dC5odG1sKSwgYW5kIGluIGBzZXJ2ZXIoKWAgd2UgdXNlIFtgcmVuZGVyRFQoKWBdKGh0dHBzOi8vcmRyci5pby9wa2cvRFQvbWFuL2RhdGFUYWJsZU91dHB1dC5odG1sKS4KCiAgICBsaWJyYXJ5KERUKQoKICAgICMgaW4gdWkKICAgIERUT3V0cHV0KG91dHB1dElkID0gInRhYmxlIikKCiAgICAjIGluIHNlcnZlcigpCiAgICBvdXRwdXQkdGFibGUgPC0gcmVuZGVyRFQoZGF0YSkKCiMjIyBUaW1lIHBsb3QgdXNpbmcgZHlncmFwZ2hzCgpXZSBzaG93IGEgdGltZSBwbG90IHdpdGggdGhlIGRhdGEgd2l0aCB0aGUgKipkeWdyYXBocyoqIHBhY2thZ2UuIEluIGB1aWAgd2UgdXNlIFtgZHlncmFwaE91dHB1dCgpYF0oaHR0cHM6Ly9yZHJyLmlvL3BrZy9keWdyYXBocy9tYW4vZHlncmFwaC1zaGlueS5odG1sKSwgYW5kIGluIGBzZXJ2ZXIoKWAgd2UgdXNlIFtgcmVuZGVyRHlncmFwaCgpYF0oaHR0cHM6Ly9yZHJyLmlvL3BrZy9keWdyYXBocy9tYW4vZHlncmFwaC1zaGlueS5odG1sKS4gKipkeWdyYXBocyoqIHBsb3RzIGFuIGV4dGVuc2libGUgdGltZSBzZXJpZXMgb2JqZWN0IGB4dHNgLiBXZSBjYW4gY3JlYXRlIHRoaXMgdHlwZSBvZiBvYmplY3QgdXNpbmcgdGhlIFtgeHRzKClgXShodHRwczovL3JkcnIuaW8vcGtnL3h0cy9tYW4veHRzLmh0bWwpIGZ1bmN0aW9uIG9mIHRoZSAqKnh0cyoqIHBhY2thZ2UgKFtSeWFuIGFuZCBVbHJpY2ggMjAyMF0oaHR0cHM6Ly93d3cucGF1bGFtb3JhZ2EuY29tL2Jvb2stZ2Vvc3BhdGlhbC9yZWZlcmVuY2VzLmh0bWwjcmVmLVIteHRzKSkgc3BlY2lmeWluZyB0aGUgdmFsdWVzIGFuZCB0aGUgZGF0ZXMuIFRoZSBkYXRlcyBpbiBgZGF0YWAgYXJlIHRoZSB5ZWFycyBvZiBjb2x1bW4gYHllYXJgLiBGb3Igbm93IHdlIGNob29zZSB0byBwbG90IHRoZSB2YWx1ZXMgb2YgdGhlIHZhcmlhYmxlIGBjYXNlc2Agb2YgYGRhdGFgLgoKV2UgbmVlZCB0byBjb25zdHJ1Y3QgYSBgeHRzYCBvYmplY3QgZm9yIGVhY2ggY291bnR5IGFuZCB0aGVuIHB1dCB0aGVtIHRvZ2V0aGVyIGluIGFuIG9iamVjdCBjYWxsZWQgYGRhdGF4dHNgLiBGb3IgZWFjaCBvZiB0aGUgY291bnRpZXMsIHdlIGZpbHRlciB0aGUgZGF0YSBvZiB0aGUgY291bnR5IGFuZCBhc3NpZ24gaXQgdG8gYGRhdGFjb3VudHlgLiBUaGVuIHdlIGNvbnN0cnVjdCBhIGB4dHNgIG9iamVjdCB3aXRoIHZhbHVlcyBgZGF0YWNvdW50eSRjYXNlc2AgYW5kIGRhdGVzIGBhcy5EYXRlKHBhc3RlMChkYXRhJHllYXIsICItMDEtMDEiKSlgLiBUaGVuIHdlIGFzc2lnbiB0aGUgbmFtZSBvZiB0aGUgY291bnRpZXMgdG8gZWFjaCBgeHRzYCAoYGNvbG5hbWVzKGRhdGF4dHMpIDwtIGNvdW50aWVzYCkgc28gY291bnR5IG5hbWVzIGNhbiBiZSBzaG93biBpbiB0aGUgbGVnZW5kLgoKICAgIGRhdGF4dHMgPC0gTlVMTAogICAgY291bnRpZXMgPC0gdW5pcXVlKGRhdGEkY291bnR5KQogICAgZm9yIChsIGluIDE6bGVuZ3RoKGNvdW50aWVzKSkgewogICAgICBkYXRhY291bnR5IDwtIGRhdGFbZGF0YSRjb3VudHkgPT0gY291bnRpZXNbbF0sIF0KICAgICAgZGQgPC0geHRzKAogICAgICAgIGRhdGFjb3VudHlbLCAiY2FzZXMiXSwKICAgICAgICBhcy5EYXRlKHBhc3RlMChkYXRhY291bnR5JHllYXIsICItMDEtMDEiKSkKICAgICAgKQogICAgICBkYXRheHRzIDwtIGNiaW5kKGRhdGF4dHMsIGRkKQogICAgfQogICAgY29sbmFtZXMoZGF0YXh0cykgPC0gY291bnRpZXMKCkZpbmFsbHksIHdlIHBsb3QgYGRhdGF4dHNgIHdpdGggW2BkeWdyYXBoKClgXShodHRwczovL3JkcnIuaW8vcGtnL2R5Z3JhcGhzL21hbi9keWdyYXBoLmh0bWwpLCBhbmQgdXNlIFtgZHlIaWdobGlnaHQoKWBdKGh0dHBzOi8vcmRyci5pby9wa2cvZHlncmFwaHMvbWFuL2R5SGlnaGxpZ2h0Lmh0bWwpIHRvIGFsbG93IG1vdXNlLW92ZXIgaGlnaGxpZ2h0aW5nLgoKICAgIGR5Z3JhcGgoZGF0YXh0cykgJT4lCiAgICAgIGR5SGlnaGxpZ2h0KGhpZ2hsaWdodFNlcmllc0JhY2tncm91bmRBbHBoYSA9IDAuMikKCldlIGN1c3RvbWl6ZSB0aGUgbGVnZW5kIHNvIHRoYXQgb25seSB0aGUgbmFtZSBvZiB0aGUgaGlnaGxpZ2h0ZWQgc2VyaWVzIGlzIHNob3duLiBUbyBkbyB0aGlzLCBvbmUgb3B0aW9uIGlzIHRvIHdyaXRlIGEgY3NzIGZpbGUgd2l0aCB0aGUgaW5zdHJ1Y3Rpb25zIGFuZCBwYXNzIHRoZSBjc3MgZmlsZSB0byB0aGUgW2BkeUNTUygpYF0oaHR0cHM6Ly9yZHJyLmlvL3BrZy9keWdyYXBocy9tYW4vZHlDU1MuaHRtbCkgZnVuY3Rpb24uIEFsdGVybmF0aXZlbHksIHdlIGNhbiBzZXQgdGhlIGNzcyBkaXJlY3RseSBpbiB0aGUgY29kZSBhcyBmb2xsb3dzOgoKICAgIGR5Z3JhcGgoZGF0YXh0cykgJT4lCiAgICAgIGR5SGlnaGxpZ2h0KGhpZ2hsaWdodFNlcmllc0JhY2tncm91bmRBbHBoYSA9IDAuMikgLT4gZDEKCiAgICBkMSR4JGNzcyA8LSAiCiAgICAuZHlncmFwaC1sZWdlbmQgPiBzcGFuIHtkaXNwbGF5Om5vbmU7fQogICAgLmR5Z3JhcGgtbGVnZW5kID4gc3Bhbi5oaWdobGlnaHQgeyBkaXNwbGF5OiBpbmxpbmU7IH0KICAgICIKCiAgICBkMQoKVGhlIGNvbXBsZXRlIGNvZGUgdG8gYnVpbGQgdGhlIGBkeWdyYXBoc2Agb2JqZWN0IGlzIHRoZSBmb2xsb3dpbmc6CgpgYGAge3J9bGlicmFyeShkeWdyYXBocyl9CmxpYnJhcnkoeHRzKQoKIyBpbiB1aQpkeWdyYXBoT3V0cHV0KG91dHB1dElkID0gInRpbWV0cmVuZCIpCgojIGluIHNlcnZlcigpCm91dHB1dCR0aW1ldHJlbmQgPC0gcmVuZGVyRHlncmFwaCh7CiAgZGF0YXh0cyA8LSBOVUxMCiAgY291bnRpZXMgPC0gdW5pcXVlKGRhdGEkY291bnR5KQogIGZvciAobCBpbiAxOmxlbmd0aChjb3VudGllcykpIHsKICAgIGRhdGFjb3VudHkgPC0gZGF0YVtkYXRhJGNvdW50eSA9PSBjb3VudGllc1tsXSwgXQogICAgZGQgPC0geHRzKAogICAgICBkYXRhY291bnR5WywgImNhc2VzIl0sCiAgICAgIGFzLkRhdGUocGFzdGUwKGRhdGFjb3VudHkkeWVhciwgIi0wMS0wMSIpKQogICAgKQogICAgZGF0YXh0cyA8LSBjYmluZChkYXRheHRzLCBkZCkKICB9CiAgY29sbmFtZXMoZGF0YXh0cykgPC0gY291bnRpZXMKCiAgZHlncmFwaChkYXRheHRzKSAlPiUKICAgIGR5SGlnaGxpZ2h0KGhpZ2hsaWdodFNlcmllc0JhY2tncm91bmRBbHBoYSA9IDAuMikgLT4gZDEKCiAgZDEkeCRjc3MgPC0gIgogLmR5Z3JhcGgtbGVnZW5kID4gc3BhbiB7ZGlzcGxheTpub25lO30KIC5keWdyYXBoLWxlZ2VuZCA+IHNwYW4uaGlnaGxpZ2h0IHsgZGlzcGxheTogaW5saW5lOyB9CiAiCiAgZDEKfSkKYGBgCgojIyMgTWFwIHVzaW5nIGxlYWZsZXQKCldlIHVzZSB0aGUgKipsZWFmbGV0KiogcGFja2FnZSB0byBidWlsZCBhbiBpbnRlcmFjdGl2ZSBtYXAuIEluIGB1aWAgd2UgdXNlIFtgbGVhZmxldE91dHB1dCgpYF0oaHR0cHM6Ly9yZHJyLmlvL3BrZy9sZWFmbGV0L21hbi9tYXAtc2hpbnkuaHRtbCksIGFuZCBpbiBgc2VydmVyKClgIHdlIHVzZSBbYHJlbmRlckxlYWZsZXQoKWBdKGh0dHBzOi8vcmRyci5pby9wa2cvbGVhZmxldC9tYW4vbWFwLXNoaW55Lmh0bWwpLiBJbnNpZGUgW2ByZW5kZXJMZWFmbGV0KClgXShodHRwczovL3JkcnIuaW8vcGtnL2xlYWZsZXQvbWFuL21hcC1zaGlueS5odG1sKSB3ZSB3cml0ZSB0aGUgaW5zdHJ1Y3Rpb25zIHRvIHJldHVybiBhIGxlYWZsZXQgbWFwLiBGaXJzdCwgd2UgbmVlZCB0byBhZGQgdGhlIGRhdGEgdG8gdGhlIHNoYXBlZmlsZSBzbyB0aGUgdmFsdWVzIGNhbiBiZSBwbG90dGVkIGluIGEgbWFwLiBGb3Igbm93IHdlIGNob29zZSB0byBwbG90IHRoZSB2YWx1ZXMgb2YgdGhlIHZhcmlhYmxlIGluIDE5ODAuIFdlIGNyZWF0ZSBhIGRhdGFzZXQgY2FsbGVkIGBkYXRhZmlsdGVyZWRgIHdpdGggdGhlIGRhdGEgY29ycmVzcG9uZGluZyB0byB0aGF0IHllYXIuIFRoZW4gd2UgYWRkIGBkYXRhZmlsdGVyZWRgIHRvIGBtYXBAZGF0YWAgaW4gYW4gb3JkZXIgc3VjaCB0aGF0IHRoZSBjb3VudGllcyBpbiB0aGUgZGF0YSBtYXRjaCB0aGUgY291bnRpZXMgaW4gdGhlIG1hcC4KCiAgICBkYXRhZmlsdGVyZWQgPC0gZGF0YVt3aGljaChkYXRhJHllYXIgPT0gMTk4MCksIF0KICAgICMgdGhpcyByZXR1cm5zIHBvc2l0aW9ucyBvZiBtYXBAZGF0YSROQU1FIGluIGRhdGFmaWx0ZXJlZCRjb3VudHkKICAgIG9yZGVyY291bnRpZXMgPC0gbWF0Y2gobWFwQGRhdGEkTkFNRSwgZGF0YWZpbHRlcmVkJGNvdW50eSkKICAgIG1hcEBkYXRhIDwtIGRhdGFmaWx0ZXJlZFtvcmRlcmNvdW50aWVzLCBdCgpXZSBjcmVhdGUgdGhlIGxlYWZsZXQgbWFwIHdpdGggdGhlIFtgbGVhZmxldCgpYF0oaHR0cHM6Ly9yZHJyLmlvL3BrZy9sZWFmbGV0L21hbi9sZWFmbGV0Lmh0bWwpIGZ1bmN0aW9uLCBjcmVhdGUgYSBjb2xvciBwYWxldHRlIHdpdGggW2Bjb2xvckJpbigpYF0oaHR0cHM6Ly9yZHJyLmlvL3BrZy9sZWFmbGV0L21hbi9jb2xvck51bWVyaWMuaHRtbCksIGFuZCBhZGQgYSBsZWdlbmQgd2l0aCBbYGFkZExlZ2VuZCgpYF0oaHR0cHM6Ly9yZHJyLmlvL3BrZy94dHMvbWFuL2FkZExlZ2VuZC5odG1sKS4gRm9yIG5vdyB3ZSBjaG9vc2UgdG8gcGxvdCB0aGUgdmFsdWVzIG9mIHZhcmlhYmxlIGBjYXNlc2AuIFdlIGFsc28gYWRkIGxhYmVscyB3aXRoIHRoZSBhcmVhIG5hbWVzIGFuZCB2YWx1ZXMgdGhhdCBhcmUgZGlzcGxheWVkIHdoZW4gdGhlIG1vdXNlIGlzIG92ZXIgdGhlIG1hcC4KCiAgICBsaWJyYXJ5KGxlYWZsZXQpCgogICAgIyBpbiB1aQogICAgbGVhZmxldE91dHB1dChvdXRwdXRJZCA9ICJtYXAiKQoKICAgICMgaW4gc2VydmVyKCkKICAgIG91dHB1dCRtYXAgPC0gcmVuZGVyTGVhZmxldCh7CgogICAgICAjIGFkZCBkYXRhIHRvIG1hcAogICAgICBkYXRhZmlsdGVyZWQgPC0gZGF0YVt3aGljaChkYXRhJHllYXIgPT0gMTk4MCksIF0KICAgICAgb3JkZXJjb3VudGllcyA8LSBtYXRjaChtYXBAZGF0YSROQU1FLCBkYXRhZmlsdGVyZWQkY291bnR5KQogICAgICBtYXBAZGF0YSA8LSBkYXRhZmlsdGVyZWRbb3JkZXJjb3VudGllcywgXQoKICAgICAgIyBjcmVhdGUgbGVhZmxldAogICAgICBwYWwgPC0gY29sb3JCaW4oIllsT3JSZCIsIGRvbWFpbiA9IG1hcCRjYXNlcywgYmlucyA9IDcpCgogICAgICBsYWJlbHMgPC0gc3ByaW50ZigiJXM6ICVnIiwgbWFwJGNvdW50eSwgbWFwJGNhc2VzKSAlPiUKICAgICAgICBsYXBwbHkoaHRtbHRvb2xzOjpIVE1MKQoKICAgICAgbCA8LSBsZWFmbGV0KG1hcCkgJT4lCiAgICAgICAgYWRkVGlsZXMoKSAlPiUKICAgICAgICBhZGRQb2x5Z29ucygKICAgICAgICAgIGZpbGxDb2xvciA9IH4gcGFsKGNhc2VzKSwKICAgICAgICAgIGNvbG9yID0gIndoaXRlIiwKICAgICAgICAgIGRhc2hBcnJheSA9ICIzIiwKICAgICAgICAgIGZpbGxPcGFjaXR5ID0gMC43LAogICAgICAgICAgbGFiZWwgPSBsYWJlbHMKICAgICAgICApICU+JQogICAgICAgIGxlYWZsZXQ6OmFkZExlZ2VuZCgKICAgICAgICAgIHBhbCA9IHBhbCwgdmFsdWVzID0gfmNhc2VzLAogICAgICAgICAgb3BhY2l0eSA9IDAuNywgdGl0bGUgPSBOVUxMCiAgICAgICAgKQogICAgfSkKCkJlbG93IGlzIHRoZSBjb250ZW50IG9mIGBhcHAuUmAgd2UgaGF2ZSB1bnRpbCBub3cuIEEgc25hcHNob3Qgb2YgdGhlIFNoaW55IGFwcCBpcyBzaG93biBpbiBGaWd1cmUgMy4KCmBgYHtyfQpsaWJyYXJ5KHNoaW55KQpsaWJyYXJ5KHJnZGFsKQpsaWJyYXJ5KERUKQpsaWJyYXJ5KGR5Z3JhcGhzKQpsaWJyYXJ5KHh0cykKbGlicmFyeShsZWFmbGV0KQoKZGF0YSA8LSByZWFkLmNzdigiZGF0YS9kYXRhLmNzdiIpCm1hcCA8LSByZWFkT0dSKCJkYXRhL2ZlXzIwMDdfMzlfY291bnR5L2ZlXzIwMDdfMzlfY291bnR5LnNocCIpCgojIHVpIG9iamVjdAp1aSA8LSBmbHVpZFBhZ2UoCiAgdGl0bGVQYW5lbChwKCJTcGF0aWFsIGFwcCIsIHN0eWxlID0gImNvbG9yOiMzNDc0QTciKSksCiAgc2lkZWJhckxheW91dCgKICAgIHNpZGViYXJQYW5lbCgKICAgICAgcCgiTWFkZSB3aXRoIiwgYSgiU2hpbnkiLAogICAgICAgIGhyZWYgPSAiaHR0cDovL3NoaW55LnJzdHVkaW8uY29tIgogICAgICApLCAiLiIpLAogICAgICBpbWcoCiAgICAgICAgc3JjID0gImltYWdlU2hpbnkucG5nIiwKICAgICAgICB3aWR0aCA9ICI3MHB4IiwgaGVpZ2h0ID0gIjcwcHgiCiAgICAgICkKICAgICksCiAgICBtYWluUGFuZWwoCiAgICAgIGxlYWZsZXRPdXRwdXQob3V0cHV0SWQgPSAibWFwIiksCiAgICAgIGR5Z3JhcGhPdXRwdXQob3V0cHV0SWQgPSAidGltZXRyZW5kIiksCiAgICAgIERUT3V0cHV0KG91dHB1dElkID0gInRhYmxlIikKICAgICkKICApCikKCiMgc2VydmVyKCkKc2VydmVyIDwtIGZ1bmN0aW9uKGlucHV0LCBvdXRwdXQpIHsKICBvdXRwdXQkdGFibGUgPC0gcmVuZGVyRFQoZGF0YSkKCiAgb3V0cHV0JHRpbWV0cmVuZCA8LSByZW5kZXJEeWdyYXBoKHsKICAgIGRhdGF4dHMgPC0gTlVMTAogICAgY291bnRpZXMgPC0gdW5pcXVlKGRhdGEkY291bnR5KQogICAgZm9yIChsIGluIDE6bGVuZ3RoKGNvdW50aWVzKSkgewogICAgICBkYXRhY291bnR5IDwtIGRhdGFbZGF0YSRjb3VudHkgPT0gY291bnRpZXNbbF0sIF0KICAgICAgZGQgPC0geHRzKAogICAgICAgIGRhdGFjb3VudHlbLCAiY2FzZXMiXSwKICAgICAgICBhcy5EYXRlKHBhc3RlMChkYXRhY291bnR5JHllYXIsICItMDEtMDEiKSkKICAgICAgKQogICAgICBkYXRheHRzIDwtIGNiaW5kKGRhdGF4dHMsIGRkKQogICAgfQogICAgY29sbmFtZXMoZGF0YXh0cykgPC0gY291bnRpZXMKCiAgICBkeWdyYXBoKGRhdGF4dHMpICU+JQogICAgICBkeUhpZ2hsaWdodChoaWdobGlnaHRTZXJpZXNCYWNrZ3JvdW5kQWxwaGEgPSAwLjIpIC0+IGQxCgogICAgZDEkeCRjc3MgPC0gIgogLmR5Z3JhcGgtbGVnZW5kID4gc3BhbiB7ZGlzcGxheTpub25lO30KIC5keWdyYXBoLWxlZ2VuZCA+IHNwYW4uaGlnaGxpZ2h0IHsgZGlzcGxheTogaW5saW5lOyB9CiAiCiAgICBkMQogIH0pCgogIG91dHB1dCRtYXAgPC0gcmVuZGVyTGVhZmxldCh7CgogICAgIyBBZGQgZGF0YSB0byBtYXAKICAgIGRhdGFmaWx0ZXJlZCA8LSBkYXRhW3doaWNoKGRhdGEkeWVhciA9PSAxOTgwKSwgXQogICAgb3JkZXJjb3VudGllcyA8LSBtYXRjaChtYXBAZGF0YSROQU1FLCBkYXRhZmlsdGVyZWQkY291bnR5KQogICAgbWFwQGRhdGEgPC0gZGF0YWZpbHRlcmVkW29yZGVyY291bnRpZXMsIF0KCiAgICAjIENyZWF0ZSBsZWFmbGV0CiAgICBwYWwgPC0gY29sb3JCaW4oIllsT3JSZCIsIGRvbWFpbiA9IG1hcCRjYXNlcywgYmlucyA9IDcpCgogICAgbGFiZWxzIDwtIHNwcmludGYoIiVzOiAlZyIsIG1hcCRjb3VudHksIG1hcCRjYXNlcykgJT4lCiAgICAgIGxhcHBseShodG1sdG9vbHM6OkhUTUwpCgogICAgbCA8LSBsZWFmbGV0KG1hcCkgJT4lCiAgICAgIGFkZFRpbGVzKCkgJT4lCiAgICAgIGFkZFBvbHlnb25zKAogICAgICAgIGZpbGxDb2xvciA9IH4gcGFsKGNhc2VzKSwKICAgICAgICBjb2xvciA9ICJ3aGl0ZSIsCiAgICAgICAgZGFzaEFycmF5ID0gIjMiLAogICAgICAgIGZpbGxPcGFjaXR5ID0gMC43LAogICAgICAgIGxhYmVsID0gbGFiZWxzCiAgICAgICkgJT4lCiAgICAgIGxlYWZsZXQ6OmFkZExlZ2VuZCgKICAgICAgICBwYWwgPSBwYWwsIHZhbHVlcyA9IH5jYXNlcywKICAgICAgICBvcGFjaXR5ID0gMC43LCB0aXRsZSA9IE5VTEwKICAgICAgKQogIH0pCn0KCiMgc2hpbnlBcHAoKQpzaGlueUFwcCh1aSA9IHVpLCBzZXJ2ZXIgPSBzZXJ2ZXIpCmBgYAoKIVtGaWd1cmUgMzogU25hcHNob3Qgb2YgdGhlIFNoaW55IGFwcCBhZnRlciBpbmNsdWRpbmcgdGhlIG1hcCwgdGhlIHRpbWUgcGxvdCwgYW5kIHRoZSB0YWJsZS5dKGltYWdlcy9GaWd1cmUzLnBuZyl7I2ZpZzN9Cg==