1 Get Started with Shiny

1.1 Introduction to Shiny

1.1.1 Client vs. Server

Most web applications have a client or user-interface that users interact with and a server (or backend) that carries out computations based on the user interactions.

1.1.2 When to build a web-app?

It can be beneficial for a data scientist to turn their analyses into a web application, especially when interactive exploration of the results are useful. It is important to be able to recognize when building an app is an appropriate solution and when it might not be.

1.2 Build a “Hello, world” Shiny app

Though this app doesn’t actually do anything other than display the text “Hello, world!!!”, you should get used to loading shiny and using the appropriate functions to create the UI, server, and actually run the app.

For this example, we make sure to create the UI before the server. We can do them in any order when building our own apps later, but for this course we’ll write them in that order.

library(shiny)

ui <- fluidPage( 
"Hello, world!!!"
)

server <- function(input, output, session) { 
}

shinyApp(ui = ui, server = server)

Listening on http://127.0.0.1:3371
NA

1.2.1 “Hello, World” app input (UI)

Extend the app and have it wish a hello to a specific person. A user will enter a name and the app will update to wish that name “Hello”.

For users to enter text, you’ll have to use a text-specific shiny input function to capture it. Recall that shiny makes available a number of input functions depending on what kind of input you’d like to capture from your users.

ui <- fluidPage(
    # CODE BELOW: Add a text input "name"
    textInput("name", "Enter a name:")
  
)

server <- function(input, output) {
  
}

shinyApp(ui = ui, server = server)

Listening on http://127.0.0.1:3371
NA

textInput() is the way to capture what it says - text input from your user - but there’s a lot of other kinds of input functions provided in shiny that will let you capture other types.

1.2.2 “Hello, World” app output (UI/Server)

To finish up your “Hello, world” app, we’ll have to actually display the text that’s input.

# Render output y using input x
output$y <- renderText({
  input$x
})

If we get an error message resembling “Parsing error in script.R:4:3: unexpected symbol”, it is very likely that you have forgotten to use a comma to separate the arguments to one of the functions.

ui <- fluidPage(
    textInput("name", "What is your name?"),
    # CODE BELOW: Display the text output, greeting
    # Make sure to add a comma after textInput()
  textOutput("greeting")
)

server <- function(input, output) {
    # CODE BELOW: Render a text output, greeting
  output$greeting <- renderText({
  paste("hello",
  input$name)
  })
  
  
}

shinyApp(ui = ui, server = server)

Listening on http://127.0.0.1:3371
NA

1.3 Build a babynames explorer Shiny app

1.3.1 Add input (UI)

This app will allow users to enter a baby name and visualize the popularity of that name over time.

The first step is to add a text input to the UI that will allow a user to enter their (or any other) name. Try using the optional default argument this time around.

library(babynames)
library(dplyr)
library(ggplot2)

babynames <- babynames %>% 
  select(year, sex, name, n, prop)
ui <- fluidPage(
  # CODE BELOW: Add a text input "name"
  textInput("name", "Enter your Name", "David")
)
server <- function(input, output, session) {

}
shinyApp(ui = ui, server = server)

1.3.2 Add output (UI/Server)

The next step in building your app is to add an empty plot as a placeholder. Recall that in order to add a plot p assigned to an object named x to a Shiny app, you need to:

  1. Render the plot object using renderPlot({p}).
  2. Assign the rendered plot to output$x.
  3. Display the plot in the UI using plotOutput(“x”).
ui <- fluidPage(
  textInput('name', 'Enter Name', 'David'),
  # CODE BELOW: Display the plot output named 'trend'
  plotOutput("trend")
)
server <- function(input, output, session) {
  # CODE BELOW: Render an empty plot and assign to output named 'trend'
  output$trend <- renderPlot({
    ggplot()
  })
  
  
}
shinyApp(ui = ui, server = server)

1.3.3 Update layout (UI)

You can use layout functions provided by Shiny to arrange the UI elements. In this case, we want to use a sidebarLayout(), where the input is placed inside a sidebarPanel() and the output is placed inside the mainPanel(). You can use this template to update the layout of your app.

sidebarLayout(
  sidebarPanel(p("This goes into the sidebar on the left")),
  mainPanel(p("This goes into the panel on the right"))
)
ui <- fluidPage(
  titlePanel("Baby Name Explorer"),
  # CODE BELOW: Add a sidebarLayout, sidebarPanel, and mainPanel
  sidebarLayout(
    sidebarPanel(
  textInput('name', 'Enter Name', 'David')
  ),
  mainPanel(
  plotOutput('trend')
  )
  )
)

server <- function(input, output, session) {
  output$trend <- renderPlot({
    ggplot()
  })
}
shinyApp(ui = ui, server = server)

1.3.4 Update output (server)

The final step is to update the plot output to display a line plot of prop vs. year, colored by sex, for the name that was input by the user. You can use this plot template to create your plot:

ggplot(subset(babynames, name == "David")) +
  geom_line(aes(x = year, y = prop, color = sex))

Recall that a user input named foo can be accessed as input$foo in the server.

ui <- fluidPage(
  titlePanel("Baby Name Explorer"),
  sidebarLayout(
    sidebarPanel(textInput('name', 'Enter Name', 'David')),
    mainPanel(plotOutput('trend'))
  )
)
server <- function(input, output, session) {
  output$trend <- renderPlot({
    # CODE BELOW: Update to display a line plot of the input name
    ggplot(subset(babynames, name == input$name)) +
    geom_line(aes(x = year, y = prop, color = sex))
    
  })
}
shinyApp(ui = ui, server = server)

Listening on http://127.0.0.1:3371

This is now a complete app, and is much more informative than the app that only had the name-entering functionality.

2 Inputs, Outputs, and Layouts

2.1 Inputs

2.1.1 Selecting an input

Shiny provides a wide variety of inputs that allows users to provide text (textInput, selectInput), numbers (numericInput, sliderInput), booleans (checkBoxInput, radioInput), and dates (dateInput, dateRangeInput).

2.1.2 Add a select input

Adding an input to a shiny app is a two step process, where you first add an ___Input(“x”) function to the UI and then access its value in the server using input$x.

For example, if we want users to choose an animal from a list, we can use a selectInput, and refer to the chosen value as input$animal:

selectInput(
  'animal', 
  'Select Animal', 
  selected = 'Cat', 
  choices = c('Dog', 'Cat')
)

We will build a Shiny app that lets users visualize the top 10 most popular names by sex by adding an input to let them choose the sex.

ui <- fluidPage(
  titlePanel("What's in a Name?"),
  # CODE BELOW: Add select input named "sex" to choose between "M" and "F"
  selectInput('sex', 'Select Sex', choices = c("F", "M")),
  # Add plot output to display top 10 most popular names
  plotOutput('plot_top_10_names')
)

server <- function(input, output, session){
  # Render plot of top 10 most popular names
  output$plot_top_10_names <- renderPlot({
    # Get top 10 names by sex and year
    top_10_names <- babynames %>% 
      # MODIFY CODE BELOW: Filter for the selected sex
      filter(sex == input$sex) %>% 
      filter(year == 1900) %>% 
      top_n(10, prop)
    # Plot top 10 names by sex and year
    ggplot(top_10_names, aes(x = name, y = prop)) +
      geom_col(fill = "#263e63")
  })
}

shinyApp(ui = ui, server = server)

– Many of the provided Shiny inputs, like this one, are named very aptly (selectInput() will hopefully help you remember you have to select one choice) and are easy to use to adjust your outputs in the server.

2.1.3 Add a slider input to select year

Slider inputs are great for numeric inputs, both when you’d like users to choose from a range of values and also when they should choose a static value from a set of options, but you want to be more creative than using a selectInput().

ui <- fluidPage(
  titlePanel("What's in a Name?"),
  # Add select input named "sex" to choose between "M" and "F"
  selectInput('sex', 'Select Sex', choices = c("F", "M")),
  # CODE BELOW: Add slider input named 'year' to select years  (1900 - 2010)
  sliderInput("year", "Select year", value = 1900, min = 1900, max = 2010),
  # Add plot output to display top 10 most popular names
  plotOutput('plot_top_10_names')
)

server <- function(input, output, session){
  # Render plot of top 10 most popular names
  output$plot_top_10_names <- renderPlot({
    # Get top 10 names by sex and year
    top_10_names <- babynames %>% 
      filter(sex == input$sex) %>% 
    # MODIFY CODE BELOW: Filter for the selected year
      filter(year == input$year) %>% 
      top_n(10, prop)
    # Plot top 10 names by sex and year
      ggplot(top_10_names, aes(x = name, y = prop)) +
        geom_col(fill = "#263e63")
  })
}

shinyApp(ui = ui, server = server)

Listening on http://127.0.0.1:3371
NA

Having users select a specific year with the sliderInput() is much cooler than selecting years from a drop down (and in this case, 100 years in a drop down would be a lot for them to scroll through.)

2.2 Outputs

2.2.1 Add a table output

In order to add any output to a Shiny app, you need to:

  1. Create the output (plot, table, text, etc.).
  2. Render the output object using the appropriate render___ function.
  3. Assign the rendered object to output$x.
  4. Add the output to the UI using the appropriate ___Output function.

We will add a table output to the baby names explorer app ereated earlier. The code inside a render___ function needs to be wrapped inside curly braces (e.g. renderPlot({…})).

ui <- fluidPage(
  titlePanel("What's in a Name?"),
  # Add select input named "sex" to choose between "M" and "F"
  selectInput('sex', 'Select Sex', choices = c("F", "M")),
  # Add slider input named "year" to select year between 1900 and 2010
  sliderInput('year', 'Select Year', min = 1900, max = 2010, value = 1900),
  # CODE BELOW: Add table output named "table_top_10_names"
  tableOutput("table_top_10_names")
)
server <- function(input, output, session){
  # Function to create a data frame of top 10 names by sex and year 
  top_10_names <- function(){
    top_10_names <- babynames %>% 
      filter(sex == input$sex) %>% 
      filter(year == input$year) %>% 
      top_n(10, prop)
  }
  # CODE BELOW: Render a table output named "table_top_10_names"
  output$table_top_10_names <- renderTable({
    top_10_names()
  })
  
}
shinyApp(ui = ui, server = server)

Listening on http://127.0.0.1:7993
NA

This static table output creates a new way of looking at our top babynames, and you are now starting to get the hang of the process of building an output in the server using a render function and then displaying it in the UI with an output function.

2.2.2 Add an interactive table output

There are multiple htmlwidgets packages like DT, leaflet, plotly, etc. that provide highly interactive outputs and can be easily integrated into Shiny apps using almost the same pattern. For example, you can turn a static table in a Shiny app into an interactive table using the DT package:

  1. Create an interactive table using DT::datatable().
  2. Render it using DT::renderDT().
  3. Display it using DT::DTOutput().

We will update the app created previously, replacing the static table with an interactive table.

ui <- fluidPage(
  titlePanel("What's in a Name?"),
  # Add select input named "sex" to choose between "M" and "F"
  selectInput('sex', 'Select Sex', choices = c("M", "F")),
  # Add slider input named "year" to select year between 1900 and 2010
  sliderInput('year', 'Select Year', min = 1900, max = 2010, value = 1900),
  # MODIFY CODE BELOW: Add a DT output named "table_top_10_names"
  DT::DTOutput('table_top_10_names')
)
Registered S3 method overwritten by 'htmlwidgets':
  method           from         
  print.htmlwidget tools:rstudio
server <- function(input, output, session){
  top_10_names <- function(){
    babynames %>% 
      filter(sex == input$sex) %>% 
      filter(year == input$year) %>% 
      top_n(10, prop)
  }
  # MODIFY CODE BELOW: Render a DT output named "table_top_10_names"
  output$table_top_10_names <- DT::renderDT({
    DT::datatable(top_10_names())
  })
}
shinyApp(ui = ui, server = server)

Listening on http://127.0.0.1:7993
NA

Just by adjusting the functions you used to render and display the table, you made it interactive. Your users can now filter and adjust the data and gain even more insights into the top baby names.

2.2.3 Add interactive plot output

Similar to creating interactive tables, you can easily turn a static plot created using ggplot2 into an interactive plot using the plotly package. To render an interactive plot, use plotly::renderPlotly(), and display it using plotly::plotlyOutput().

top_trendy_names <- readRDS("top_trendy_names.rds")
ui <- fluidPage(
  selectInput('name', 'Select Name', top_trendy_names$name),
  # CODE BELOW: Add a plotly output named 'plot_trendy_names'
  plotly::plotlyOutput("plot_trendy_names")
)
server <- function(input, output, session){
  # Function to plot trends in a name
  plot_trends <- function(){
     babynames %>% 
      filter(name == input$name) %>% 
      ggplot(aes(x = year, y = n)) +
      geom_col()
  }
  # CODE BELOW: Render a plotly output named 'plot_trendy_names'
  output$plot_trendy_names <- plotly::renderPlotly({
    plot_trends()
  })
  
  
}
shinyApp(ui = ui, server = server)

Listening on http://127.0.0.1:7993
NA

You can zoom in on certain areas, zoom back out, and hover over the bars to see the values. This makes plots in your app far more interesting, and allows users to gain insights without having to see any code or data.

2.3 Layouts and themes

2.3.2 Tab layouts

Displaying several tables and plots on the same page can lead to visual clutter and distract users of the app. In such cases, the tab layout comes in handy, as it allows different outputs to be displayed as tabs.

We will start with the Shiny app using the sidebar layout from the last example and modify it to use tabs. This example should also make it very clear that Shiny makes it really easy to switch app layouts with only a few modifications to the code.

ui <- fluidPage(
  sidebarLayout(
    sidebarPanel(
      selectInput('name', 'Select Name', top_trendy_names$name)
    ),
    mainPanel(
      # MODIFY CODE BLOCK BELOW: Wrap in a tabsetPanel
      tabsetPanel(
        # MODIFY CODE BELOW: Wrap in a tabPanel providing an appropriate label
        tabPanel("Plot",
        plotly::plotlyOutput('plot_trendy_names')),
        # MODIFY CODE BELOW: Wrap in a tabPanel providing an appropriate label
        tabPanel("Table", DT::DTOutput('table_trendy_names'))
      )
    )
  )
)
server <- function(input, output, session){
  # Function to plot trends in a name
  plot_trends <- function(){
     babynames %>% 
      filter(name == input$name) %>% 
      ggplot(aes(x = year, y = n)) +
      geom_col()
  }
  output$plot_trendy_names <- plotly::renderPlotly({
    plot_trends()
  })
  
  output$table_trendy_names <- DT::renderDT({
    babynames %>% 
      filter(name == input$name)
  })
}
shinyApp(ui = ui, server = server)

Listening on http://127.0.0.1:7993
NA

As you can see, a tab panel is a much cleaner way to extend a sidebar layout and display multiple pieces of information in one app. Tab layouts become especially helpful if you begin to build dashboards in Shiny.

2.3.3 Themes

Shiny makes it easy to customize the theme of an app. The UI functions in Shiny make use of Twitter Bootstrap, a popular framework for building web applications. Bootswatch extends Bootstrap by making it really easy to skin an application with minimal code changes.

We will add a title panel to your app, use the theme selector to explore different themes, and apply then a theme of your choice.

ui <- fluidPage(
  # CODE BELOW: Add a titlePanel with an appropriate title
  
  # REPLACE CODE BELOW: with theme = shinythemes::shinytheme("<your theme>")
  shinythemes::themeSelector(),
  sidebarLayout(
    sidebarPanel(
      selectInput('name', 'Select Name', top_trendy_names$name)
    ),
    mainPanel(
      tabsetPanel(
        tabPanel('Plot', plotly::plotlyOutput('plot_trendy_names')),
        tabPanel('Table', DT::DTOutput('table_trendy_names'))
      )
    )
  )
)
server <- function(input, output, session){
  # Function to plot trends in a name
  plot_trends <- function(){
     babynames %>% 
      filter(name == input$name) %>% 
      ggplot(aes(x = year, y = n)) +
      geom_col()
  }
  output$plot_trendy_names <- plotly::renderPlotly({
    plot_trends()
  })
  
  output$table_trendy_names <- DT::renderDT({
    babynames %>% 
      filter(name == input$name)
  })
}
shinyApp(ui = ui, server = server)

Listening on http://127.0.0.1:7993
NA
ui <- fluidPage(
  # CODE BELOW: Add a titlePanel with an appropriate title
  titlePanel("Trendy Names"),
  # REPLACE CODE BELOW: with theme = shinythemes::shinytheme("<your theme>")
  theme = shinythemes::shinytheme("spacelab"),
  sidebarLayout(
    sidebarPanel(
      selectInput('name', 'Select Name', top_trendy_names$name)
    ),
    mainPanel(
      tabsetPanel(
        tabPanel('Plot', plotly::plotlyOutput('plot_trendy_names')),
        tabPanel('Table', DT::DTOutput('table_trendy_names'))
      )
    )
  )
)
server <- function(input, output, session){
  # Function to plot trends in a name
  plot_trends <- function(){
     babynames %>% 
      filter(name == input$name) %>% 
      ggplot(aes(x = year, y = n)) +
      geom_col()
  }
  output$plot_trendy_names <- plotly::renderPlotly({
    plot_trends()
  })
  
  output$table_trendy_names <- DT::renderDT({
    babynames %>% 
      filter(name == input$name)
  })
}
shinyApp(ui = ui, server = server)

Listening on http://127.0.0.1:7993
NA

There are a lot of cool built-in themes in shinythemes, and if none of them suit your fancy, you can learn how to further customize your app with custom CSS.

2.4 Building apps

2.4.1 App 1: Multilingual Greeting

The best way to learn Shiny is by deconstructing an existing app and rebuilding it from scratch.

We are going to build a Shiny app that allows you to enter your name and select a greeting (Hello/Bonjour), and returns “Hello, Kaelen”, when the user is Kaelen. Admittedly, it is a really simple app, but the challenge is we are going to have to code it from scratch!

Four steps to building a Shiny app are:

  1. Add inputs
  2. Add outputs
  3. Update layout
  4. Update outputs
ui <- fluidPage(
  selectInput("select", "Select greeting", choices = c("Hello", "Bonjour")),
  textInput("name", "Enter your name"),
  
  textOutput("greeting")
)

server <- function(input, output, session) {
  output$greeting <- renderText({
    paste(input$select, input$name)
  })
}

shinyApp(ui = ui, server = server)

Listening on http://127.0.0.1:4667
NA

3 Reactive Programming

3.1 Reactivity 101

3.1.1 Source vs. Conductor vs. Endpoint

The magic behind Shiny is driven by reactivity. As you learned in this lesson, there are three types of reactive components in a Shiny app.

  1. Reactive source: User input that comes through a browser interface, typically.

  2. Reactive conductor: Reactive component between a source and an endpoint, typically used to encapsulate slow computations.

  3. Reactive endpoint: Something that appears in the user’s browser window, such as a plot or a table of values.

    ui <- fluidPage( titlePanel(‘BMI Calculator’), theme = shinythemes::shinytheme(‘cosmo’), sidebarLayout( sidebarPanel( numericInput(‘height’, ‘Enter your height in meters’, 1.5, 1, 2), numeriInput(‘weight’, ‘Enter your weight in Kilograms’, 60, 45, 120) ), mainPanel( textOutput(“bmi”), textOutput(“bmi_range”) ) ) ) server <- function(input, output, session) { rval_bmi <- reactive({ input\(weight/(input\)height^2) }) output\(bmi <- renderText({ bmi <- rval_bmi() paste("Your BMI is", round(bmi, 1)) }) output\)bmi_range <- renderText({ bmi <- rval_bmi() health_status <- cut(bmi, breaks = c(0, 18.5, 24.9, 29.9, 40), labels = c(‘underweight’, ‘healthy’, ‘overweight’, ‘obese’) ) paste(“You are”, health_status) }) } shinyApp(ui, server)

3.1.2 Add a reactive expression

A reactive expression is an R expression that uses widget input and returns a value. The reactive expression will update this value whenever the original widget changes. Reactive expressions are lazy and cached.

In this exercise, you will encapsulate a repeated computation as a reactive expression.

server <- function(input, output, session) {
  # CODE BELOW: Add a reactive expression rval_bmi to calculate BMI
  rval_bmi <- reactive({
    input$weight/(input$height^2)
  })
  
  output$bmi <- renderText({
    # MODIFY CODE BELOW: Replace right-hand-side with reactive expression
    bmi <- rval_bmi()
    paste("Your BMI is", round(bmi, 1))
  })
  output$bmi_range <- renderText({
    # MODIFY CODE BELOW: Replace right-hand-side with reactive expression
    bmi <- rval_bmi()
    bmi_status <- cut(bmi, 
      breaks = c(0, 18.5, 24.9, 29.9, 40),
      labels = c('underweight', 'healthy', 'overweight', 'obese')
    )
    paste("You are", bmi_status)
  })
}
ui <- fluidPage(
  titlePanel('BMI Calculator'),
  sidebarLayout(
    sidebarPanel(
      numericInput('height', 'Enter your height in meters', 1.5, 1, 2),
      numericInput('weight', 'Enter your weight in Kilograms', 60, 45, 120)
    ),
    mainPanel(
      textOutput("bmi"),
      textOutput("bmi_range")
    )
  )
)

shinyApp(ui = ui, server = server)

Listening on http://127.0.0.1:6934
NA

Encapsulating computations as reactive expressions is key to building modular and performant Shiny apps.

3.1.3 Understanding reactive expressions

One of the central tenets of reactivity is that reactive expressions are executed lazily, and their values are cached.

  1. Lazy: Evaluated only when it is called, typically by a reactive endpoint.
  2. Cached: Evaluated only when the value of one of its underlying dependencies changes.

3.2 Observers vs. reactives

3.2.1 Add another reactive expression

A reactive expression can call other reactive expressions. This allows you to modularize computations and ensure that they are NOT executed repeatedly. Mastering the use of reactive expressions is key to building performant Shiny applications.

In this exercise, you will use a reactive expression to calculate the health status based on the BMI.

server <- function(input, output, session) {
  rval_bmi <- reactive({
    input$weight/(input$height^2)
  })
  # CODE BELOW: Add a reactive expression rval_bmi_status to 
  # return health status as underweight etc. based on inputs
  rval_bmi_status <- reactive({
    cut(rval_bmi(), 
      breaks = c(0, 18.5, 24.9, 29.9, 40),
      labels = c('underweight', 'healthy', 'overweight', 'obese')
    )
  })
  
  
  
  output$bmi <- renderText({
    bmi <- rval_bmi()
    paste("Your BMI is", round(bmi, 1))
  })
  output$bmi_status <- renderText({
    # MODIFY CODE BELOW: Replace right-hand-side with 
    # reactive expression rval_bmi_status
    bmi_status <- rval_bmi_status()
    paste("You are", bmi_status)
  })
}
ui <- fluidPage(
  titlePanel('BMI Calculator'),
  sidebarLayout(
    sidebarPanel(
      numericInput('height', 'Enter your height in meters', 1.5, 1, 2),
      numericInput('weight', 'Enter your weight in Kilograms', 60, 45, 120)
    ),
    mainPanel(
      textOutput("bmi"),
      textOutput("bmi_status")
    )
  )
)

shinyApp(ui = ui, server = server)

Listening on http://127.0.0.1:6934
NA

Modularity is really important while building complex, performant Shiny apps. Reactive expressions enable you to achieve this modularity.

3.2.2 Add an observer to display notifications

Recall that an observer is used for side effects, like displaying a plot, table, or text in the browser. By default an observer triggers an action, whenever one of its underlying dependencies change.

In this exercise, you will use an observer to display a notification in the browser, using observe() and showNotification(). As we are triggering an action using an observer, we do NOT need to use a render***() function or assign the results to an output.

ui <- fluidPage(
  textInput('name', 'Enter your name')
)

server <- function(input, output, session) {
  # CODE BELOW: Add an observer to display a notification
  # 'You have entered the name xxxx' where xxxx is the name
  observe({
    showNotification(
      paste('You have entered the name', input$name)
    )
  })
}

shinyApp(ui = ui, server = server)

3.3 Stop - delay - trigger

3.3.1 Stop reactions with isolate()

Ordinarily, the simple act of reading a reactive value is sufficient to set up a relationship, where a change to the reactive value will cause the calling expression to re-execute. The isolate() function allows an expression to read a reactive value without triggering re-execution when its value changes.

In this exercise, you will use the isolate() function to stop reactive flow.

server <- function(input, output, session) {
  rval_bmi <- reactive({
    input$weight/(input$height^2)
  })
  output$bmi <- renderText({
    bmi <- rval_bmi()
    # MODIFY CODE BELOW: 
    # Use isolate to stop output from updating when name changes.
    paste("Hi", isolate({input$name}), ". Your BMI is", round(bmi, 1))
  })
}
ui <- fluidPage(
  titlePanel('BMI Calculator'),
  sidebarLayout(
    sidebarPanel(
      textInput('name', 'Enter your name'),
      numericInput('height', 'Enter your height (in m)', 1.5, 1, 2, step = 0.1),
      numericInput('weight', 'Enter your weight (in Kg)', 60, 45, 120)
    ),
    mainPanel(
      textOutput("bmi")
    )
  )
)

shinyApp(ui = ui, server = server)

Listening on http://127.0.0.1:5271
NA

There are situations where you don’t want Shiny’s reactive framework to automatically trigger an update. The isolate() function will be very handy in these scenarios.

3.3.2 Delay reactions with eventReactive()

Shiny’s reactive programming framework is designed such that any changes to inputs automatically updates the outputs that depend on it. In some situations, we might want more explicitly control the trigger that causes the update.

The function eventReactive() is used to compute a reactive value that only updates in response to a specific event.

rval_x <- eventReactive(input$event, {
 # calculations
})
server <- function(input, output, session) {
  # MODIFY CODE BELOW: Use eventReactive to delay the execution of the
  # calculation until the user clicks on the show_bmi button (Show BMI)
  rval_bmi <- eventReactive(input$muestra_bmi,{
    input$weight/(input$height^2)
  })
  output$bmi <- renderText({
    bmi <- rval_bmi()
    paste("Hi", input$name, ". Your BMI is", round(bmi, 1))
  })
}
ui <- fluidPage(
  titlePanel('BMI Calculator'),
  sidebarLayout(
    sidebarPanel(
      textInput('name', 'Enter your name'),
      numericInput('height', 'Enter height (in m)', 1.5, 1, 2, step = 0.1),
      numericInput('weight', 'Enter weight (in Kg)', 60, 45, 120),
      actionButton("show_bmi", "Show BMI")
    ),
    mainPanel(
      textOutput("bmi")
    )
  )
)


shinyApp(ui = ui, server = server)

Listening on http://127.0.0.1:5271
NA

There are situations where you want to delay the computation of a reactive expression until a specific event is triggered. The eventReactive() function will prove very handy in these scenarios.

3.3.3 Trigger reactions with observeEvent()

There are times when you want to perform an action in response to an event. For example, you might want to let the app user download a table as a CSV file, when they click on a “Download” button. Or, you might want to display a notification or modal dialog, in response to a click.

The observeEvent() function allows you to achieve this. It accepts two arguments:

  1. The event you want to respond to.
  2. The function that should be called whenever the event occurs.

In this exercise, you will use observeEvent() to display a modal dialog with help text, when the user clicks on a button labelled “Help”. The help text has already been assigned to the variable bmi_help_text.

bmi_help_text <- "Body Mass Index is a simple calculation using a person's height and weight. The formula is BMI = kg/m2 where kg is a person's weight in kilograms and m2 is their height in metres squared. A BMI of 25.0 or more is overweight, while the healthy range is 18.5 to 24.9."
server <- function(input, output, session) {
  # MODIFY CODE BELOW: Wrap in observeEvent() so the help text 
  # is displayed when a user clicks on the Help button.
  
     # Display a modal dialog with bmi_help_text
     # MODIFY CODE BELOW: Uncomment code
     observeEvent(input$show_help,{
     showModal(modalDialog(bmi_help_text))
     })
  rv_bmi <- eventReactive(input$show_bmi, {
    input$weight/(input$height^2)
  })
  output$bmi <- renderText({
    bmi <- rv_bmi()
    paste("Hi", input$name, ". Your BMI is", round(bmi, 1))
  })
}

ui <- fluidPage(
  titlePanel('BMI Calculator'),
  sidebarLayout(
    sidebarPanel(
      textInput('name', 'Enter your name'),
      numericInput('height', 'Enter your height in meters', 1.5, 1, 2),
      numericInput('weight', 'Enter your weight in Kilograms', 60, 45, 120),
      actionButton("show_bmi", "Show BMI"),
      # CODE BELOW: Add an action button named "show_help"
      actionButton("show_help", "Help")
    ),
    mainPanel(
      textOutput("bmi")
    )
  )
)

shinyApp(ui = ui, server = server)

Listening on http://127.0.0.1:5271
NA

The observeEvent() function is very useful when you want to explicitly trigger an action in response to an event in the user-interface.

3.4 Applying reactivity concepts

3.4.1 Convert height from inches to centimeters

Earlier in the chapter, we practiced stopping, delaying, and triggering apps. This is a very common pattern of programming in Shiny that enables your apps to be optimized for speed (and only re-run when something is updated and your user would like to re-run the app.)

In this exercise, you’ll practice some of those concepts again, just to make sure you truly understand them. Instead of calculating BMI, this app converts height in inches to centimeters.

server <- function(input, output, session) {
  # MODIFY CODE BELOW: Delay the height calculation until
  # the show button is pressed
  rval_height_cm <- eventReactive(input$show_height_cm,{
    input$height * 2.54
  })
  
  output$height_cm <- renderText({
    height_cm <- rval_height_cm()
        paste("Your height in centimeters is", height_cm, "cm")
    })
}

ui <- fluidPage(
  titlePanel("Inches to Centimeters Conversion"),
  sidebarLayout(
    sidebarPanel(
      numericInput("height", "Height (in)", 60),
      actionButton("show_height_cm", "Show height in cm")
    ),
    mainPanel(
      textOutput("height_cm")
    )
  )
)

shinyApp(ui = ui, server = server)

Listening on http://127.0.0.1:5271
NA

Using eventReactive(), you recalculate the height only when the ‘Show height in cm’ button is pressed, which makes sense. Remember that your app doesn’t need to do everything all the time!

4 Build Shiny Apps

4.1 Build an Alien Sightings Dashboard

library(readr)
usa_ufo_sightings <- read_csv("usa_ufo_sightings.csv")
Parsed with column specification:
cols(
  date_sighted = col_date(format = ""),
  latitude = col_double(),
  longitude = col_double(),
  city = col_character(),
  state = col_character(),
  shape = col_character(),
  duration_sec = col_double(),
  comments = col_character()
)

4.1.1 Alien sightings: add inputs

The National UFO Reporting Center (NUFORC) has collected sightings data throughout the last century. This app is going to allow users to select a U.S. state and a time period in which the sightings occurred.

ui <- fluidPage(
  # CODE BELOW: Add a title
   titlePanel("UFO Sightings"),
  sidebarLayout(
    sidebarPanel(
      # CODE BELOW: One input to select a U.S. state
      # And one input to select a range of dates
      selectInput("state", "Choose a U.S. state:", choices = unique(usa_ufo_sightings$state)),
      
      dateRangeInput("date", "Choose a date range:", start = "1920-01-01", end = "2020-01-01")
      
      

    
    
    ),
    mainPanel()
  )
)

server <- function(input, output) {

}

shinyApp(ui, server)

Listening on http://127.0.0.1:4934
NA

These two inputs are a great start to the app, but if we don’t build anything in the server, they don’t do anything. Let’s get our outputs built.

4.1.2 Alien sightings: add outputs

Now that the dashboard has inputs, you should build your outputs to actually see information about the reported UFO sightings.

Recall there will be two, a plot and a table. The plot should show the number sighted, by shape, for the selected state and time period. The table should show, for the selected state and time period, the number sighted, plus the average, median, minimum, and maximum duration (duration_sec) of the sightings. This will require using dplyr, or a method of your choosing, to manipulate the usa_ufo_sightings data.

library(dplyr)
library(ggplot2)
server <- function(input, output) {
  # CODE BELOW: Create a plot output name 'shapes', of sightings by shape,
  # For the selected inputs
  output$shapes <- renderPlot({
    usa_ufo_sightings %>%
      filter(state == input$state,
             date_sighted >= input$dates[1],
             date_sighted <= input$dates[2]) %>%
      ggplot(aes(shape)) +
      geom_bar() +
      labs(x = "Shape", y = "# Sighted")
  })
  # CODE BELOW: Create a table output named 'duration_table', by shape, 
  # of # sighted, plus mean, median, max, and min duration of sightings
  # for the selected inputs
  output$duration_table <- renderTable({
    usa_ufo_sightings %>%
      filter(
        state == input$state,
        date_sighted >= input$dates[1],
        date_sighted <= input$dates[2]
      ) %>%
      group_by(shape) %>%
      summarize(
        nb_sighted = n(),
        avg_duration = mean(duration_sec),
        median_duration = median(duration_sec),
        min_duration = min(duration_sec),
        max_duration = max(duration_sec)
      )
  })
}

ui <- fluidPage(
  titlePanel("UFO Sightings"),
  sidebarLayout(
    sidebarPanel(
      selectInput("state", "Choose a U.S. state:", choices = unique(usa_ufo_sightings$state)),
      dateRangeInput("dates", "Choose a date range:",
                     start = "1920-01-01",
                     end = "1950-01-01")
    ),
    mainPanel(
      # Add plot output named 'shapes'
      plotOutput("shapes"),
      # Add table output named 'duration_table'
      tableOutput("duration_table")
    )
  )
)

shinyApp(ui, server)

Listening on http://127.0.0.1:4565
NA

This app is sort of cluttered, given that the plot and table output are just sitting on top of one another, but you’ve started to get some concrete information about the aliens sighted. Let’s clean it up by adding the tab layout.

4.1.3 Alien sightings: tab layout

As-is, the app is sort of busy with the graph on top of the table. Given that this is a dashboard, it might be nice to instead separate the two outputs.

The last step in building your dashboard is to take the plotOutput() and tableOutput() you’ve built and add the tab layout.

ui <- fluidPage(
  titlePanel("UFO Sightings"),
  sidebarLayout(
  sidebarPanel(
    selectInput("state", "Choose a U.S. state:", choices = unique(usa_ufo_sightings$state)),
    dateRangeInput("dates", "Choose a date range:",
      start = "1920-01-01",
      end = "1950-01-01"
    )
  ),
  # MODIFY CODE BELOW: Create a tab layout for the dashboard
  mainPanel(
    tabsetPanel(
      tabPanel("Plot",
      plotOutput("shapes")),
      tabPanel("Table",
      tableOutput("duration_table"))
      )
    )
  )
)

server <- function(input, output) {
  output$shapes <- renderPlot({
    usa_ufo_sightings %>%
      filter(
        state == input$state,
        date_sighted >= input$dates[1],
        date_sighted <= input$dates[2]
      ) %>%
      ggplot(aes(shape)) +
      geom_bar() +
      labs(
        x = "Shape",
        y = "# Sighted"
      )
  })

  output$duration_table <- renderTable({
    usa_ufo_sightings %>%
      filter(
        state == input$state,
        date_sighted >= input$dates[1],
        date_sighted <= input$dates[2]
      ) %>%
      group_by(shape) %>%
      summarize(
        nb_sighted = n(),
        avg_duration_min = mean(duration_sec) / 60,
        median_duration_min = median(duration_sec) / 60,
        min_duration_min = min(duration_sec) / 60,
        max_duration_min = max(duration_sec) / 60
      )
  })
}

shinyApp(ui, server)

You could add a theme, write a custom CSS stylesheet to add pictures of aliens, and use the data to add even more information about alien sightings the world over.

4.2 Exploring the 2014 Mental Health in Tech Survey

4.2.2 Explore the Mental Health in Tech 2014 Survey

Don’t be intimidated, but in this exercise, you’re going to build the entirety of this app (minus the custom error message) in one go!

For this app, you’ll be using the questions “Do you think that discussing a mental health issue with your employer would have negative consequences?” (the mental_health_consequence variable) and “Do you feel that your employer takes mental health as seriously as physical health?” (mental_vs_physical) as multi-selector inputs, then displaying a histogram of the Age of respondents. To see the choices for these variables, count() them in the console.

mental_health_survey <- read_csv("mental_health_survey_edited.csv")
Parsed with column specification:
cols(
  .default = col_character(),
  Timestamp = col_datetime(format = ""),
  Age = col_double()
)
See spec(...) for full column specifications.
ui <- fluidPage(
  # CODE BELOW: Add an appropriate title
  titlePanel("2014 Mental Health in Tech Survey"),
  sidebarPanel(
    # CODE BELOW: Add a checkboxGroupInput
    checkboxGroupInput(
      inputId = "mental_health_consequence",
      label = "Do you think that discussing a mental health issue with your employer would have negative consequences?",
      choices = unique(mental_health_survey$mental_health_consequence),
      selected = "Maybe"
    ),
    # CODE BELOW: Add a pickerInput
    pickerInput(
      inputId = "mental_vs_physical",
      label = "Do you feel that your employer takes mental health as seriously as physical health?",
      choices = unique(mental_health_survey$mental_vs_physical),
      multiple = TRUE
    )
  ),
  mainPanel(
    # CODE BELOW: Display the output
    plotOutput("age")
  )
)

server <- function(input, output, session) {
  # CODE BELOW: Build a histogram of the age of respondents
  # Filtered by the two inputs
  output$age <- renderPlot({
    mental_health_survey %>%
      filter(
        mental_health_consequence %in% input$mental_health_consequence,
        mental_vs_physical %in% input$mental_vs_physical
      ) %>%
      ggplot(aes(Age)) +
      geom_histogram()
  })
}

shinyApp(ui, server)

Listening on http://127.0.0.1:6496
NA

This was a real challenge, building an app in one go, but you’ve learned so much in the course and hopefully it wasn’t too bad. Now, let’s get rid of the blank plot and throw a custom error message to users.

4.2.3 Validate that a user made a selection

It is often good practice to select a default value for your selector inputs, if one should be excluded, you can throw a custom error message to your users that clues them in on what they need to do for the app to run successfully.

We saw in the last exercise that, without a default value for the pickerInput(), the plot is simply blank. Instead of a blank plot, in this exercise you’ll show users a custom error message telling them to make the correct selection needed to get the app working.

server <- function(input, output, session) {
  output$age <- renderPlot({
    # MODIFY CODE BELOW: Add validation that user selected a 3rd input
    validate(
      need(input$mental_vs_physical != "", "Be sure to select an option")
    )
    
    
    
    
    

    mental_health_survey %>%
      filter(
        work_interfere == input$work_interfere,
        mental_health_consequence %in% input$mental_health_consequence,
        mental_vs_physical %in% input$mental_vs_physical
      ) %>%
      ggplot(aes(Age)) +
      geom_histogram()
  })
}

ui <- fluidPage(
  titlePanel("2014 Mental Health in Tech Survey"),
  sidebarPanel(
    sliderTextInput(
      inputId = "work_interfere",
      label = "If you have a mental health condition, do you feel that it interferes with your work?", 
      grid = TRUE,
      force_edges = TRUE,
      choices = c("Never", "Rarely", "Sometimes", "Often")
    ),
    checkboxGroupInput(
      inputId = "mental_health_consequence",
      label = "Do you think that discussing a mental health issue with your employer would have negative consequences?", 
      choices = c("Maybe", "Yes", "No"),
      selected = "Maybe"
    ),
    pickerInput(
      inputId = "mental_vs_physical",
      label = "Do you feel that your employer takes mental health as seriously as physical health?", 
      choices = c("Don't Know", "No", "Yes"),
      multiple = TRUE
    )    
  ),
  mainPanel(
    plotOutput("age")  
  )
)

shinyApp(ui, server)

Listening on http://127.0.0.1:6496
NA

Though this could have been avoided by setting a default value for our pickerInput(), this was a great demonstration of how to throw a custom error message for your users should you need it in future apps.

4.3 Explore cuisines

4.3.1 Explore cuisines: top ingredients

Food has universal appeal, and the amazing array of dishes one can concoct with the multitude of ingredients leads to near infinite variety! In this exercise, you will use a dataset named recipes that contains recipes, the cuisine it belongs to, and the ingredients it uses, to build a Shiny app that lets the user explore the most used ingredients by cuisine.

recipes <- read_rds("recipes.rds")

Here is a handy snippet of code that gets you the top 10 ingredients used in Greek cuisine. You will find this useful to create the interactive data table in the app based on the cuisine and number of ingredients selected by the user.

library(tidyr)
recipes <- recipes %>% 
  unnest(ingredients)
recipes %>% 
  filter(cuisine == 'greek') %>% 
  count(ingredients, name = 'nb_recipes') %>% 
  arrange(desc(nb_recipes)) %>% 
  head(10)
ui <- fluidPage(
  titlePanel('Explore Cuisines'),
  sidebarLayout(
    sidebarPanel(
      # CODE BELOW: Add an input named "cuisine" to select a cuisine
      selectInput("cuisine",
      label = "Select Cuisine",
      choices = unique(recipes$cuisine)),
      # CODE BELOW: Add an input named "nb_ingredients" to select # of ingredients
     sliderInput("nb_ingredients",
     "Select No. of Ingredients",
     value = 5,
     min = 1,
     max = 100)
    ),
    mainPanel(
      # CODE BELOW: Add a DT output named "dt_top_ingredients"
     DT::DTOutput("dt_top_ingredients")
    )
 )
)
server <- function(input, output, session) {
  # CODE BELOW: Render the top ingredients in a chosen cuisine as 
  # an interactive data table and assign it to output object `dt_top_ingredients`
  output$dt_top_ingredients <- DT::renderDT({
  recipes %>%
    filter(cuisine == input$cuisine) %>%
    count(ingredients, name = "nb_recipes") %>%
    arrange(desc(nb_recipes)) %>%
    head(input$nb_ingredients)
  })

}
shinyApp(ui, server)

Listening on http://127.0.0.1:4447
NA

Interactive data tables are a great way to showcase raw data in your apps to let your users explore. The datatable package makes it easy to create highly interactive data tables, and is definitely worth exploring in detail.

4.3.2 Explore cuisines: top ingredients redux

Each cuisine is distinct because of a small set of distinct ingredients. We can’t surface these by looking at the most popular ingredients, since they’re the bread-and-butter ingredients of cooking like salt or sugar.

Another metric that can aid us in this quest is the term frequency–inverse document frequency (TFIDF), a numerical statistic that is intended to reflect how important a word (ingredient) is to a document (cuisine) in a collection or corpus (recipes).

library(tidytext)
recipes_enriched <- recipes %>%
  count(cuisine, ingredients, name ='nb_recipes') %>%
  tidytext::bind_tf_idf(ingredients, cuisine, nb_recipes)

We already precomputed the tf_idf for you and created an enriched dataset named recipes_enriched. Your goal is to create a Shiny app that displays a horizontal bar plot of the top distinctive ingredients in a cuisine, as measured by tf_idf.

You will use a reactive expression to encapsulate the computations and let the plotting code focus only on creating the plot. This is good programming practice and helps create modular and performant Shiny apps.

We have loaded the packages shiny, dplyr, ggplot2 and plotly. Here are two handy snippets to filter for the top recipes by cuisine and create a horizontal bar plot. You can modify it appropriately.

top_ingredients <- recipes_enriched %>% 
  filter(cuisine == 'greek') %>% 
  arrange(desc(tf_idf)) %>% 
  head(5) 

ggplot(top_ingredients, aes(x = ingredient, y = tf_idf)) +
  geom_col() +
  coord_flip()
  
library(forcats)
ui <- fluidPage(
  titlePanel('Explore Cuisines'),
  sidebarLayout(
    sidebarPanel(
      selectInput('cuisine', 'Select Cuisine', unique(recipes$cuisine)),
      sliderInput('nb_ingredients', 'Select No. of Ingredients', 5, 100, 10),
    ),
    mainPanel(
      tabsetPanel(
        # CODE BELOW: Add a plotly output named "plot_top_ingredients"
        tabPanel("Plot", plotly::plotlyOutput("plot_top_ingredients")), 
        tabPanel('Table', DT::DTOutput('dt_top_ingredients'))
      )
    )
  )
)
server <- function(input, output, session) {
  # CODE BELOW: Add a reactive expression named `rval_top_ingredients` that
  # filters `recipes_enriched` for the selected cuisine and top ingredients
  # based on the tf_idf value.
 rval_top_ingredients <- reactive({
   recipes_enriched %>%
   filter(cuisine == input$cuisine) %>%
   arrange(desc(tf_idf)) %>%
   head(input$nb_ingredients) %>%
   mutate(ingredients = forcats::fct_reorder(ingredients, tf_idf))
 })
  
  # CODE BELOW: Render a horizontal bar plot of top ingredients and 
  # the tf_idf of recipes they get used in, and assign it to an output named 
  # `plot_top_ingredients` 
  output$plot_top_ingredients <- plotly::renderPlotly({
    rval_top_ingredients() %>% 
      ggplot( aes(x = ingredients, y = tf_idf)) +
      geom_col() +
      coord_flip()
  })
  
  
  
  
  output$dt_top_ingredients <- DT::renderDT({
    recipes %>% 
      filter(cuisine == input$cuisine) %>% 
      count(ingredients, name = 'nb_recipes') %>% 
      arrange(desc(nb_recipes)) %>% 
      head(input$nb_ingredients)
  })
}
shinyApp(ui, server)

Listening on http://127.0.0.1:4447
NA

4.3.3 Explore cuisines: wordclouds

A handy way to visualize a lot of data is wordclouds. In this exercise, you will extend the Shiny app we built previously and add a new tab that displays the top distinctive ingredients as an interactive wordcloud.

Here is a handy snippet to create a wordcloud.

d3wordcloud(
  words = c('hello', 'world', 'good'), 
  freqs = c(20, 40, 30),
  tooltip = TRUE
)
library(wordcloud)
library(RColorBrewer)
ui <- fluidPage(
  titlePanel('Explore Cuisines'),
  sidebarLayout(
    sidebarPanel(
      selectInput('cuisine', 'Select Cuisine', unique(recipes$cuisine)),
      sliderInput('nb_ingredients', 'Select No. of Ingredients', 5, 100, 20),
    ),
    mainPanel(
      tabsetPanel(
        # CODE BELOW: Add `d3wordcloudOutput` named `wc_ingredients` in a `tabPanel`
        tabPanel('Word Cloud', plotOutput('wc_ingredients')),
        tabPanel('Plot', plotly::plotlyOutput('plot_top_ingredients')),
        tabPanel('Table', DT::DTOutput('dt_top_ingredients'))
      )
    )
  )
)
server <- function(input, output, session){
  # CODE BELOW: Render an interactive wordcloud of top distinctive ingredients 
  # and the number of recipes they get used in, using 
  # `d3wordcloud::renderD3wordcloud`, and assign it to an output named
  # `wc_ingredients`
  wordcloud_rep <- repeatable(wordcloud)
  output$wc_ingredients <- renderPlot({
     ingredients_df <- rval_top_ingredients()
     wordcloud_rep(words = ingredients_df$ingredients,
                   freq = ingredients_df$nb_recipes,
                   scale = c(4, 0.5),
                   colors=brewer.pal(8, "Dark2"))
  })
  rval_top_ingredients <- reactive({
    recipes_enriched %>% 
      filter(cuisine == input$cuisine) %>% 
      arrange(desc(tf_idf)) %>% 
      head(input$nb_ingredients) %>% 
      mutate(ingredients = forcats::fct_reorder(ingredients, tf_idf))
  })
  output$plot_top_ingredients <- plotly::renderPlotly({
    rval_top_ingredients() %>%
      ggplot(aes(x = ingredients, y = tf_idf)) +
      geom_col() +
      coord_flip()
  })
  output$dt_top_ingredients <- DT::renderDT({
    recipes %>% 
      filter(cuisine == input$cuisine) %>% 
      count(ingredients, name = 'nb_recipes') %>% 
      arrange(desc(nb_recipes)) %>% 
      head(input$nb_ingredients)
  })
}
shinyApp(ui = ui, server= server)

Listening on http://127.0.0.1:4447

Below not working in R 4:

ui <- fluidPage(
  titlePanel('Explore Cuisines'),
  sidebarLayout(
    sidebarPanel(
      selectInput('cuisine', 'Select Cuisine', unique(recipes$cuisine)),
      sliderInput('nb_ingredients', 'Select No. of Ingredients', 5, 100, 20),
    ),
    mainPanel(
      tabsetPanel(
        # CODE BELOW: Add `d3wordcloudOutput` named `wc_ingredients` in a `tabPanel`
        tabPanel('Word Cloud', d3wordcloud::d3wordcloudOutput('wc_ingredients', height = '400')),
        tabPanel('Plot', plotly::plotlyOutput('plot_top_ingredients')),
        tabPanel('Table', DT::DTOutput('dt_top_ingredients'))
      )
    )
  )
)
server <- function(input, output, session){
  # CODE BELOW: Render an interactive wordcloud of top distinctive ingredients 
  # and the number of recipes they get used in, using 
  # `d3wordcloud::renderD3wordcloud`, and assign it to an output named
  # `wc_ingredients`.
  output$wc_ingredients <- d3wordcloud::renderD3wordcloud({
     ingredients_df <- rval_top_ingredients()
     d3wordcloud(ingredients_df$ingredient, ingredients_df$nb_recipes, tooltip = TRUE)
  })
  rval_top_ingredients <- reactive({
    recipes_enriched %>% 
      filter(cuisine == input$cuisine) %>% 
      arrange(desc(tf_idf)) %>% 
      head(input$nb_ingredients) %>% 
      mutate(ingredient = forcats::fct_reorder(ingredient, tf_idf))
  })
  output$plot_top_ingredients <- plotly::renderPlotly({
    rval_top_ingredients() %>%
      ggplot(aes(x = ingredient, y = tf_idf)) +
      geom_col() +
      coord_flip()
  })
  output$dt_top_ingredients <- DT::renderDT({
    recipes %>% 
      filter(cuisine == input$cuisine) %>% 
      count(ingredient, name = 'nb_recipes') %>% 
      arrange(desc(nb_recipes)) %>% 
      head(input$nb_ingredients)
  })
}
shinyApp(ui = ui, server= server)

There is a rich ecosystem of interactive widgets like d3wordcloud, that make it easy to add interactivity to your Shiny app. Look up the gallery of htmlwidgets at http://gallery.htmlwidgets.org/.

4.4 Mass shootings

4.4.1 Mass shootings: add inputs

Mass Shootings have been a topic of intense discussion in the United States. A public database of mass shootings since 1982 has been made available by the Mother Jones, a non-profit organization. Over the next three exercises, you will build a Shiny app to explore these shootings on an interactive map.

In this exercise, you will add a slider input to filter on fatalities and a date range input to filter on a range of dates.

library(shiny)
library(dplyr)
library(leaflet)
library(readr)
library(lubridate)
mass_shootings <- read_csv("mass-shootings.csv")
Duplicated column names deduplicated: 'location' => 'location_1' [8]Parsed with column specification:
cols(
  .default = col_character(),
  fatalities = col_double(),
  injured = col_double(),
  total_victims = col_double(),
  age_of_shooter = col_double(),
  latitude = col_double(),
  longitude = col_double(),
  year = col_double()
)
See spec(...) for full column specifications.
)
Error: unexpected ')' in ")"
mass_shootings <- mass_shootings %>% 
  mutate(date = parse_date_time(mass_shootings$date, "mdy"))
ui <- bootstrapPage(
  theme = shinythemes::shinytheme('simplex'),
  leaflet::leafletOutput('map', height = '100%', width = '100%'),
  absolutePanel(top = 10, right = 10, id = 'controls',
    # CODE BELOW: Add slider input named nb_fatalities
    sliderInput("nb_fatalities", "Minimum Fatalities", value = 10, min = 1, max = 40),
    # CODE BELOW: Add date range input named date_range
    dateRangeInput("date_range", "Select Date", start = "1982-01-01", end = 2020-01-01)
  ),
  tags$style(type = "text/css", "
    html, body {width:100%;height:100%}     
    #controls{background-color:white;padding:20px;}
  ")
)
Couldn't coerce the `end` argument to a date string with format yyyy-mm-dd
server <- function(input, output, session) {
  output$map <- leaflet::renderLeaflet({
    leaflet() %>% 
      addTiles() %>%
      setView( -98.58, 39.82, zoom = 5) %>% 
      addTiles()
  })
}

shinyApp(ui, server)

Listening on http://127.0.0.1:4906

Note how we made use of an alternate layout to display a full screen interactive map, and a sticky input panel on the top right. Shiny has many such layouts that you should definitely explore.

4.4.2 Mass shootings: modify output

Wou will extend the Shiny app you built previously so that red circles sized based on the number of fatalities appear on the interactive map, along with a summary of the case when the circle is clicked.

server <- function(input, output, session) {
  rval_mass_shootings <- reactive({
    # MODIFY CODE BELOW: Filter mass_shootings on nb_fatalities and 
    # selected date_range.
    mass_shootings %>% 
      filter(
        date >= input$date_range[1],
        date <= input$date_range[2],
        fatalities >= input$nb_fatalities
      )
  })
  output$map <- leaflet::renderLeaflet({
    rval_mass_shootings() %>%
      leaflet() %>% 
      addTiles() %>%
      setView( -98.58, 39.82, zoom = 5) %>% 
      addTiles() %>% 
      addCircleMarkers(
        # CODE BELOW: Add parameters popup and radius and map them
        # to the summary and fatalities columns
        popup = ~ summary, radius = ~ fatalities,
        fillColor = 'red', color = 'red', weight = 1
      )
  })
}
ui <- bootstrapPage(
  theme = shinythemes::shinytheme('simplex'),
  leaflet::leafletOutput('map', height = '100%', width = '100%'),
  absolutePanel(top = 10, right = 10, id = 'controls',
    sliderInput('nb_fatalities', 'Minimum Fatalities', 1, 40, 10),
    dateRangeInput('date_range', 'Select Date', "2010-01-01", "2019-12-01")
  ),
  tags$style(type = "text/css", "
    html, body {width:100%;height:100%}     
    #controls{background-color:white;padding:20px;}
  ")
)

shinyApp(ui, server)

Listening on http://127.0.0.1:5879
Assuming "longitude" and "latitude" are longitude and latitude, respectively
Assuming "longitude" and "latitude" are longitude and latitude, respectively
Assuming "longitude" and "latitude" are longitude and latitude, respectively
NA

Use reactive expressions generously in your app. They are executed only when required, and their values are cached, leading to highly performant apps. It also leads to more modular code that is easier to maintain.

4.4.3 Mass shootings: display help

It is always useful to provide users with more context about your app. One way to do this is by adding an About button to the app and display the context as a modal dialog.

This is exactly what we will be doing in this exercise.

text_about <- "This data was compiled by Mother Jones, nonprofit founded in 1976. Originally covering cases from 1982-2012, this database has since been expanded numerous times to remain current."
ui <- bootstrapPage(
  theme = shinythemes::shinytheme('simplex'),
  leaflet::leafletOutput('map', width = '100%', height = '100%'),
  absolutePanel(top = 10, right = 10, id = 'controls',
    sliderInput('nb_fatalities', 'Minimum Fatalities', 1, 40, 10),
    dateRangeInput(
      'date_range', 'Select Date', "2010-01-01", "2019-12-01"
    ),
    # CODE BELOW: Add an action button named show_about
    actionButton('show_about', 'About')
  ),
  tags$style(type = "text/css", "
    html, body {width:100%;height:100%}     
    #controls{background-color:white;padding:20px;}
  ")
)
server <- function(input, output, session) {
  # CODE BELOW: Use observeEvent to display a modal dialog
  # with the help text stored in text_about.
  observeEvent(input$show_about, {
  showModal(modalDialog(text_about, title = 'About'))
  })
  
  output$map <- leaflet::renderLeaflet({
    mass_shootings %>% 
      filter(
        date >= input$date_range[1],
        date <= input$date_range[2],
        fatalities >= input$nb_fatalities
      ) %>% 
      leaflet() %>% 
      setView( -98.58, 39.82, zoom = 5) %>% 
      addTiles() %>% 
      addCircleMarkers(
        popup = ~ summary, radius = ~ sqrt(fatalities)*3,
        fillColor = 'red', color = 'red', weight = 1
      )
  })
}

shinyApp(ui, server)

Listening on http://127.0.0.1:5879
Assuming "longitude" and "latitude" are longitude and latitude, respectively
NA

Modal dialogs are a great way to provide users with more context and information about your app, without cluttering the user interface.

LS0tCnRpdGxlOiAiQnVpbGRpbmcgV2ViIEFwcGxpY2F0aW9ucyB3aXRoIFNoaW55IGluIFIiCm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgdG9jOiB0cnVlCiAgICB0b2NfZmxvYXQ6IHRydWUKICAgIHRvY19jb2xsYXBzZWQ6IGZhbHNlCiAgICBudW1iZXJfc2VjdGlvbnM6IHRydWUKICAgIAp0b2NfZGVwdGg6IDMKLS0tCgojIEdldCBTdGFydGVkIHdpdGggU2hpbnkKCiMjIEludHJvZHVjdGlvbiB0byBTaGlueQoKIyMjIENsaWVudCB2cy4gU2VydmVyCgpNb3N0IHdlYiBhcHBsaWNhdGlvbnMgaGF2ZSBhIGNsaWVudCBvciB1c2VyLWludGVyZmFjZSB0aGF0IHVzZXJzIGludGVyYWN0IHdpdGggYW5kIGEgc2VydmVyIChvciBiYWNrZW5kKSB0aGF0IGNhcnJpZXMgb3V0IGNvbXB1dGF0aW9ucyBiYXNlZCBvbiB0aGUgdXNlciBpbnRlcmFjdGlvbnMuCgojIyMgV2hlbiB0byBidWlsZCBhIHdlYi1hcHA/CgpJdCBjYW4gYmUgYmVuZWZpY2lhbCBmb3IgYSBkYXRhIHNjaWVudGlzdCB0byB0dXJuIHRoZWlyIGFuYWx5c2VzIGludG8gYSB3ZWIgYXBwbGljYXRpb24sIGVzcGVjaWFsbHkgd2hlbiBpbnRlcmFjdGl2ZSBleHBsb3JhdGlvbiBvZiB0aGUgcmVzdWx0cyBhcmUgdXNlZnVsLiBJdCBpcyBpbXBvcnRhbnQgdG8gYmUgYWJsZSB0byByZWNvZ25pemUgd2hlbiBidWlsZGluZyBhbiBhcHAgaXMgYW4gYXBwcm9wcmlhdGUgc29sdXRpb24gYW5kIHdoZW4gaXQgbWlnaHQgbm90IGJlLgoKIyMgQnVpbGQgYSAiSGVsbG8sIHdvcmxkIiBTaGlueSBhcHAKClRob3VnaCB0aGlzIGFwcCBkb2Vzbid0IGFjdHVhbGx5IGRvIGFueXRoaW5nIG90aGVyIHRoYW4gZGlzcGxheSB0aGUgdGV4dCAiSGVsbG8sIHdvcmxkISEhIiwgeW91IHNob3VsZCBnZXQgdXNlZCB0byBsb2FkaW5nIHNoaW55IGFuZCB1c2luZyB0aGUgYXBwcm9wcmlhdGUgZnVuY3Rpb25zIHRvIGNyZWF0ZSB0aGUgVUksIHNlcnZlciwgYW5kIGFjdHVhbGx5IHJ1biB0aGUgYXBwLgoKRm9yIHRoaXMgZXhhbXBsZSwgd2UgbWFrZSBzdXJlIHRvIGNyZWF0ZSB0aGUgVUkgYmVmb3JlIHRoZSBzZXJ2ZXIuIFdlIGNhbiBkbyB0aGVtIGluIGFueSBvcmRlciB3aGVuIGJ1aWxkaW5nIG91ciBvd24gYXBwcyBsYXRlciwgYnV0IGZvciB0aGlzIGNvdXJzZSB3ZSdsbCB3cml0ZSB0aGVtIGluIHRoYXQgb3JkZXIuCmBgYHtyfQpsaWJyYXJ5KHNoaW55KQoKdWkgPC0gZmx1aWRQYWdlKCAKIkhlbGxvLCB3b3JsZCEhISIKKQoKc2VydmVyIDwtIGZ1bmN0aW9uKGlucHV0LCBvdXRwdXQsIHNlc3Npb24pIHsgCn0KCnNoaW55QXBwKHVpID0gdWksIHNlcnZlciA9IHNlcnZlcikKYGBgCiMjIyAiSGVsbG8sIFdvcmxkIiBhcHAgaW5wdXQgKFVJKQoKRXh0ZW5kIHRoZSBhcHAgYW5kIGhhdmUgaXQgd2lzaCBhIGhlbGxvIHRvIGEgc3BlY2lmaWMgcGVyc29uLiBBIHVzZXIgd2lsbCBlbnRlciBhIG5hbWUgYW5kIHRoZSBhcHAgd2lsbCB1cGRhdGUgdG8gd2lzaCB0aGF0IG5hbWUgIkhlbGxvIi4KCkZvciB1c2VycyB0byBlbnRlciB0ZXh0LCB5b3UnbGwgaGF2ZSB0byB1c2UgYSB0ZXh0LXNwZWNpZmljIHNoaW55IGlucHV0IGZ1bmN0aW9uIHRvIGNhcHR1cmUgaXQuIFJlY2FsbCB0aGF0IHNoaW55IG1ha2VzIGF2YWlsYWJsZSBhIG51bWJlciBvZiBpbnB1dCBmdW5jdGlvbnMgZGVwZW5kaW5nIG9uIHdoYXQga2luZCBvZiBpbnB1dCB5b3UnZCBsaWtlIHRvIGNhcHR1cmUgZnJvbSB5b3VyIHVzZXJzLgpgYGB7cn0KdWkgPC0gZmx1aWRQYWdlKAoJIyBDT0RFIEJFTE9XOiBBZGQgYSB0ZXh0IGlucHV0ICJuYW1lIgoJdGV4dElucHV0KCJuYW1lIiwgIkVudGVyIGEgbmFtZToiKQogIAopCgpzZXJ2ZXIgPC0gZnVuY3Rpb24oaW5wdXQsIG91dHB1dCkgewogIAp9CgpzaGlueUFwcCh1aSA9IHVpLCBzZXJ2ZXIgPSBzZXJ2ZXIpCmBgYAp0ZXh0SW5wdXQoKSBpcyB0aGUgd2F5IHRvIGNhcHR1cmUgd2hhdCBpdCBzYXlzIC0gdGV4dCBpbnB1dCBmcm9tIHlvdXIgdXNlciAtIGJ1dCB0aGVyZSdzIGEgbG90IG9mIG90aGVyIGtpbmRzIG9mIGlucHV0IGZ1bmN0aW9ucyBwcm92aWRlZCBpbiBzaGlueSB0aGF0IHdpbGwgbGV0IHlvdSBjYXB0dXJlIG90aGVyIHR5cGVzLgoKIyMjICJIZWxsbywgV29ybGQiIGFwcCBvdXRwdXQgKFVJL1NlcnZlcikKClRvIGZpbmlzaCB1cCB5b3VyICJIZWxsbywgd29ybGQiIGFwcCwgd2UnbGwgaGF2ZSB0byBhY3R1YWxseSBkaXNwbGF5IHRoZSB0ZXh0IHRoYXQncyBpbnB1dC4KCiAgICAjIFJlbmRlciBvdXRwdXQgeSB1c2luZyBpbnB1dCB4CiAgICBvdXRwdXQkeSA8LSByZW5kZXJUZXh0KHsKICAgICAgaW5wdXQkeAogICAgfSkKCklmIHdlIGdldCBhbiBlcnJvciBtZXNzYWdlIHJlc2VtYmxpbmcg4oCcUGFyc2luZyBlcnJvciBpbiBzY3JpcHQuUjo0OjM6IHVuZXhwZWN0ZWQgc3ltYm9s4oCdLCBpdCBpcyB2ZXJ5IGxpa2VseSB0aGF0IHlvdSBoYXZlIGZvcmdvdHRlbiB0byB1c2UgYSBjb21tYSB0byBzZXBhcmF0ZSB0aGUgYXJndW1lbnRzIHRvIG9uZSBvZiB0aGUgZnVuY3Rpb25zLgpgYGB7cn0KdWkgPC0gZmx1aWRQYWdlKAoJdGV4dElucHV0KCJuYW1lIiwgIldoYXQgaXMgeW91ciBuYW1lPyIpLAoJIyBDT0RFIEJFTE9XOiBEaXNwbGF5IHRoZSB0ZXh0IG91dHB1dCwgZ3JlZXRpbmcKICAgICMgTWFrZSBzdXJlIHRvIGFkZCBhIGNvbW1hIGFmdGVyIHRleHRJbnB1dCgpCiAgdGV4dE91dHB1dCgiZ3JlZXRpbmciKQopCgpzZXJ2ZXIgPC0gZnVuY3Rpb24oaW5wdXQsIG91dHB1dCkgewoJIyBDT0RFIEJFTE9XOiBSZW5kZXIgYSB0ZXh0IG91dHB1dCwgZ3JlZXRpbmcKICBvdXRwdXQkZ3JlZXRpbmcgPC0gcmVuZGVyVGV4dCh7CiAgcGFzdGUoImhlbGxvIiwKICBpbnB1dCRuYW1lKQogIH0pCiAgCiAgCn0KCnNoaW55QXBwKHVpID0gdWksIHNlcnZlciA9IHNlcnZlcikKYGBgCiMjIEJ1aWxkIGEgYmFieW5hbWVzIGV4cGxvcmVyIFNoaW55IGFwcAoKIyMjIEFkZCBpbnB1dCAoVUkpCgpUaGlzIGFwcCB3aWxsIGFsbG93IHVzZXJzIHRvIGVudGVyIGEgYmFieSBuYW1lIGFuZCB2aXN1YWxpemUgdGhlIHBvcHVsYXJpdHkgb2YgdGhhdCBuYW1lIG92ZXIgdGltZS4KClRoZSBmaXJzdCBzdGVwIGlzIHRvIGFkZCBhIHRleHQgaW5wdXQgdG8gdGhlIFVJIHRoYXQgd2lsbCBhbGxvdyBhIHVzZXIgdG8gZW50ZXIgdGhlaXIgKG9yIGFueSBvdGhlcikgbmFtZS4gVHJ5IHVzaW5nIHRoZSBvcHRpb25hbCBkZWZhdWx0IGFyZ3VtZW50IHRoaXMgdGltZSBhcm91bmQuCmBgYHtyfQpsaWJyYXJ5KGJhYnluYW1lcykKbGlicmFyeShkcGx5cikKbGlicmFyeShnZ3Bsb3QyKQoKYmFieW5hbWVzIDwtIGJhYnluYW1lcyAlPiUgCiAgc2VsZWN0KHllYXIsIHNleCwgbmFtZSwgbiwgcHJvcCkKYGBgCgoKYGBge3J9CnVpIDwtIGZsdWlkUGFnZSgKICAjIENPREUgQkVMT1c6IEFkZCBhIHRleHQgaW5wdXQgIm5hbWUiCiAgdGV4dElucHV0KCJuYW1lIiwgIkVudGVyIHlvdXIgTmFtZSIsICJEYXZpZCIpCikKc2VydmVyIDwtIGZ1bmN0aW9uKGlucHV0LCBvdXRwdXQsIHNlc3Npb24pIHsKCn0Kc2hpbnlBcHAodWkgPSB1aSwgc2VydmVyID0gc2VydmVyKQpgYGAKIyMjIEFkZCBvdXRwdXQgKFVJL1NlcnZlcikKVGhlIG5leHQgc3RlcCBpbiBidWlsZGluZyB5b3VyIGFwcCBpcyB0byBhZGQgYW4gZW1wdHkgcGxvdCBhcyBhIHBsYWNlaG9sZGVyLiBSZWNhbGwgdGhhdCBpbiBvcmRlciB0byBhZGQgYSBwbG90IHAgYXNzaWduZWQgdG8gYW4gb2JqZWN0IG5hbWVkIHggdG8gYSBTaGlueSBhcHAsIHlvdSBuZWVkIHRvOgoKICAxLiBSZW5kZXIgdGhlIHBsb3Qgb2JqZWN0IHVzaW5nIHJlbmRlclBsb3Qoe3B9KS4KICAyLiBBc3NpZ24gdGhlIHJlbmRlcmVkIHBsb3QgdG8gb3V0cHV0JHguCiAgMy4gRGlzcGxheSB0aGUgcGxvdCBpbiB0aGUgVUkgdXNpbmcgcGxvdE91dHB1dCgieCIpLgoKYGBge3J9CnVpIDwtIGZsdWlkUGFnZSgKICB0ZXh0SW5wdXQoJ25hbWUnLCAnRW50ZXIgTmFtZScsICdEYXZpZCcpLAogICMgQ09ERSBCRUxPVzogRGlzcGxheSB0aGUgcGxvdCBvdXRwdXQgbmFtZWQgJ3RyZW5kJwogIHBsb3RPdXRwdXQoInRyZW5kIikKKQpzZXJ2ZXIgPC0gZnVuY3Rpb24oaW5wdXQsIG91dHB1dCwgc2Vzc2lvbikgewogICMgQ09ERSBCRUxPVzogUmVuZGVyIGFuIGVtcHR5IHBsb3QgYW5kIGFzc2lnbiB0byBvdXRwdXQgbmFtZWQgJ3RyZW5kJwogIG91dHB1dCR0cmVuZCA8LSByZW5kZXJQbG90KHsKICAgIGdncGxvdCgpCiAgfSkKICAKICAKfQpzaGlueUFwcCh1aSA9IHVpLCBzZXJ2ZXIgPSBzZXJ2ZXIpCmBgYAojIyMgVXBkYXRlIGxheW91dCAoVUkpCgpZb3UgY2FuIHVzZSBsYXlvdXQgZnVuY3Rpb25zIHByb3ZpZGVkIGJ5IFNoaW55IHRvIGFycmFuZ2UgdGhlIFVJIGVsZW1lbnRzLiBJbiB0aGlzIGNhc2UsIHdlIHdhbnQgdG8gdXNlIGEgc2lkZWJhckxheW91dCgpLCB3aGVyZSB0aGUgaW5wdXQgaXMgcGxhY2VkIGluc2lkZSBhIHNpZGViYXJQYW5lbCgpIGFuZCB0aGUgb3V0cHV0IGlzIHBsYWNlZCBpbnNpZGUgdGhlIG1haW5QYW5lbCgpLiBZb3UgY2FuIHVzZSB0aGlzIHRlbXBsYXRlIHRvIHVwZGF0ZSB0aGUgbGF5b3V0IG9mIHlvdXIgYXBwLgoKICAgIHNpZGViYXJMYXlvdXQoCiAgICAgIHNpZGViYXJQYW5lbChwKCJUaGlzIGdvZXMgaW50byB0aGUgc2lkZWJhciBvbiB0aGUgbGVmdCIpKSwKICAgICAgbWFpblBhbmVsKHAoIlRoaXMgZ29lcyBpbnRvIHRoZSBwYW5lbCBvbiB0aGUgcmlnaHQiKSkKICAgICkKCmBgYHtyfQp1aSA8LSBmbHVpZFBhZ2UoCiAgdGl0bGVQYW5lbCgiQmFieSBOYW1lIEV4cGxvcmVyIiksCiAgIyBDT0RFIEJFTE9XOiBBZGQgYSBzaWRlYmFyTGF5b3V0LCBzaWRlYmFyUGFuZWwsIGFuZCBtYWluUGFuZWwKICBzaWRlYmFyTGF5b3V0KAogICAgc2lkZWJhclBhbmVsKAogIHRleHRJbnB1dCgnbmFtZScsICdFbnRlciBOYW1lJywgJ0RhdmlkJykKICApLAogIG1haW5QYW5lbCgKICBwbG90T3V0cHV0KCd0cmVuZCcpCiAgKQogICkKKQoKc2VydmVyIDwtIGZ1bmN0aW9uKGlucHV0LCBvdXRwdXQsIHNlc3Npb24pIHsKICBvdXRwdXQkdHJlbmQgPC0gcmVuZGVyUGxvdCh7CiAgICBnZ3Bsb3QoKQogIH0pCn0Kc2hpbnlBcHAodWkgPSB1aSwgc2VydmVyID0gc2VydmVyKQpgYGAKIyMjIFVwZGF0ZSBvdXRwdXQgKHNlcnZlcikKClRoZSBmaW5hbCBzdGVwIGlzIHRvIHVwZGF0ZSB0aGUgcGxvdCBvdXRwdXQgdG8gZGlzcGxheSBhIGxpbmUgcGxvdCBvZiBwcm9wIHZzLiB5ZWFyLCBjb2xvcmVkIGJ5IHNleCwgZm9yIHRoZSBuYW1lIHRoYXQgd2FzIGlucHV0IGJ5IHRoZSB1c2VyLiBZb3UgY2FuIHVzZSB0aGlzIHBsb3QgdGVtcGxhdGUgdG8gY3JlYXRlIHlvdXIgcGxvdDoKCiAgICBnZ3Bsb3Qoc3Vic2V0KGJhYnluYW1lcywgbmFtZSA9PSAiRGF2aWQiKSkgKwogICAgICBnZW9tX2xpbmUoYWVzKHggPSB5ZWFyLCB5ID0gcHJvcCwgY29sb3IgPSBzZXgpKQoKUmVjYWxsIHRoYXQgYSB1c2VyIGlucHV0IG5hbWVkIGZvbyBjYW4gYmUgYWNjZXNzZWQgYXMgaW5wdXQkZm9vIGluIHRoZSBzZXJ2ZXIuCgpgYGB7cn0KdWkgPC0gZmx1aWRQYWdlKAogIHRpdGxlUGFuZWwoIkJhYnkgTmFtZSBFeHBsb3JlciIpLAogIHNpZGViYXJMYXlvdXQoCiAgICBzaWRlYmFyUGFuZWwodGV4dElucHV0KCduYW1lJywgJ0VudGVyIE5hbWUnLCAnRGF2aWQnKSksCiAgICBtYWluUGFuZWwocGxvdE91dHB1dCgndHJlbmQnKSkKICApCikKc2VydmVyIDwtIGZ1bmN0aW9uKGlucHV0LCBvdXRwdXQsIHNlc3Npb24pIHsKICBvdXRwdXQkdHJlbmQgPC0gcmVuZGVyUGxvdCh7CiAgICAjIENPREUgQkVMT1c6IFVwZGF0ZSB0byBkaXNwbGF5IGEgbGluZSBwbG90IG9mIHRoZSBpbnB1dCBuYW1lCiAgICBnZ3Bsb3Qoc3Vic2V0KGJhYnluYW1lcywgbmFtZSA9PSBpbnB1dCRuYW1lKSkgKwogICAgZ2VvbV9saW5lKGFlcyh4ID0geWVhciwgeSA9IHByb3AsIGNvbG9yID0gc2V4KSkKICAgIAogIH0pCn0Kc2hpbnlBcHAodWkgPSB1aSwgc2VydmVyID0gc2VydmVyKQpgYGAKVGhpcyBpcyBub3cgYSBjb21wbGV0ZSBhcHAsIGFuZCBpcyBtdWNoIG1vcmUgaW5mb3JtYXRpdmUgdGhhbiB0aGUgYXBwIHRoYXQgb25seSBoYWQgdGhlIG5hbWUtZW50ZXJpbmcgZnVuY3Rpb25hbGl0eS4KCiMgSW5wdXRzLCBPdXRwdXRzLCBhbmQgTGF5b3V0cwoKIyMgSW5wdXRzCgojIyMgU2VsZWN0aW5nIGFuIGlucHV0CgpTaGlueSBwcm92aWRlcyBhIHdpZGUgdmFyaWV0eSBvZiBpbnB1dHMgdGhhdCBhbGxvd3MgdXNlcnMgdG8gcHJvdmlkZSB0ZXh0ICh0ZXh0SW5wdXQsIHNlbGVjdElucHV0KSwgbnVtYmVycyAobnVtZXJpY0lucHV0LCBzbGlkZXJJbnB1dCksIGJvb2xlYW5zIChjaGVja0JveElucHV0LCByYWRpb0lucHV0KSwgYW5kIGRhdGVzIChkYXRlSW5wdXQsIGRhdGVSYW5nZUlucHV0KS4KCiMjIyBBZGQgYSBzZWxlY3QgaW5wdXQKCkFkZGluZyBhbiBpbnB1dCB0byBhIHNoaW55IGFwcCBpcyBhIHR3byBzdGVwIHByb2Nlc3MsIHdoZXJlIHlvdSBmaXJzdCBhZGQgYW4gX19fSW5wdXQo4oCceOKAnSkgZnVuY3Rpb24gdG8gdGhlIFVJIGFuZCB0aGVuIGFjY2VzcyBpdHMgdmFsdWUgaW4gdGhlIHNlcnZlciB1c2luZyBpbnB1dCR4LgoKRm9yIGV4YW1wbGUsIGlmIHdlIHdhbnQgdXNlcnMgdG8gY2hvb3NlIGFuIGFuaW1hbCBmcm9tIGEgbGlzdCwgd2UgY2FuIHVzZSBhIHNlbGVjdElucHV0LCBhbmQgcmVmZXIgdG8gdGhlIGNob3NlbiB2YWx1ZSBhcyBpbnB1dCRhbmltYWw6CgogICAgc2VsZWN0SW5wdXQoCiAgICAgICdhbmltYWwnLCAKICAgICAgJ1NlbGVjdCBBbmltYWwnLCAKICAgICAgc2VsZWN0ZWQgPSAnQ2F0JywgCiAgICAgIGNob2ljZXMgPSBjKCdEb2cnLCAnQ2F0JykKICAgICkKCldlIHdpbGwgYnVpbGQgYSBTaGlueSBhcHAgdGhhdCBsZXRzIHVzZXJzIHZpc3VhbGl6ZSB0aGUgdG9wIDEwIG1vc3QgcG9wdWxhciBuYW1lcyBieSBzZXggYnkgYWRkaW5nIGFuIGlucHV0IHRvIGxldCB0aGVtIGNob29zZSB0aGUgc2V4LgpgYGB7cn0KdWkgPC0gZmx1aWRQYWdlKAogIHRpdGxlUGFuZWwoIldoYXQncyBpbiBhIE5hbWU/IiksCiAgIyBDT0RFIEJFTE9XOiBBZGQgc2VsZWN0IGlucHV0IG5hbWVkICJzZXgiIHRvIGNob29zZSBiZXR3ZWVuICJNIiBhbmQgIkYiCiAgc2VsZWN0SW5wdXQoJ3NleCcsICdTZWxlY3QgU2V4JywgY2hvaWNlcyA9IGMoIkYiLCAiTSIpKSwKICAjIEFkZCBwbG90IG91dHB1dCB0byBkaXNwbGF5IHRvcCAxMCBtb3N0IHBvcHVsYXIgbmFtZXMKICBwbG90T3V0cHV0KCdwbG90X3RvcF8xMF9uYW1lcycpCikKCnNlcnZlciA8LSBmdW5jdGlvbihpbnB1dCwgb3V0cHV0LCBzZXNzaW9uKXsKICAjIFJlbmRlciBwbG90IG9mIHRvcCAxMCBtb3N0IHBvcHVsYXIgbmFtZXMKICBvdXRwdXQkcGxvdF90b3BfMTBfbmFtZXMgPC0gcmVuZGVyUGxvdCh7CiAgICAjIEdldCB0b3AgMTAgbmFtZXMgYnkgc2V4IGFuZCB5ZWFyCiAgICB0b3BfMTBfbmFtZXMgPC0gYmFieW5hbWVzICU+JSAKICAgICAgIyBNT0RJRlkgQ09ERSBCRUxPVzogRmlsdGVyIGZvciB0aGUgc2VsZWN0ZWQgc2V4CiAgICAgIGZpbHRlcihzZXggPT0gaW5wdXQkc2V4KSAlPiUgCiAgICAgIGZpbHRlcih5ZWFyID09IDE5MDApICU+JSAKICAgICAgdG9wX24oMTAsIHByb3ApCiAgICAjIFBsb3QgdG9wIDEwIG5hbWVzIGJ5IHNleCBhbmQgeWVhcgogICAgZ2dwbG90KHRvcF8xMF9uYW1lcywgYWVzKHggPSBuYW1lLCB5ID0gcHJvcCkpICsKICAgICAgZ2VvbV9jb2woZmlsbCA9ICIjMjYzZTYzIikKICB9KQp9CgpzaGlueUFwcCh1aSA9IHVpLCBzZXJ2ZXIgPSBzZXJ2ZXIpCmBgYAotLQpNYW55IG9mIHRoZSBwcm92aWRlZCBTaGlueSBpbnB1dHMsIGxpa2UgdGhpcyBvbmUsIGFyZSBuYW1lZCB2ZXJ5IGFwdGx5IChzZWxlY3RJbnB1dCgpIHdpbGwgaG9wZWZ1bGx5IGhlbHAgeW91IHJlbWVtYmVyIHlvdSBoYXZlIHRvIHNlbGVjdCBvbmUgY2hvaWNlKSBhbmQgYXJlIGVhc3kgdG8gdXNlIHRvIGFkanVzdCB5b3VyIG91dHB1dHMgaW4gdGhlIHNlcnZlci4KCiMjIyBBZGQgYSBzbGlkZXIgaW5wdXQgdG8gc2VsZWN0IHllYXIKClNsaWRlciBpbnB1dHMgYXJlIGdyZWF0IGZvciBudW1lcmljIGlucHV0cywgYm90aCB3aGVuIHlvdSdkIGxpa2UgdXNlcnMgdG8gY2hvb3NlIGZyb20gYSByYW5nZSBvZiB2YWx1ZXMgYW5kIGFsc28gd2hlbiB0aGV5IHNob3VsZCBjaG9vc2UgYSBzdGF0aWMgdmFsdWUgZnJvbSBhIHNldCBvZiBvcHRpb25zLCBidXQgeW91IHdhbnQgdG8gYmUgbW9yZSBjcmVhdGl2ZSB0aGFuIHVzaW5nIGEgc2VsZWN0SW5wdXQoKS4KYGBge3J9CnVpIDwtIGZsdWlkUGFnZSgKICB0aXRsZVBhbmVsKCJXaGF0J3MgaW4gYSBOYW1lPyIpLAogICMgQWRkIHNlbGVjdCBpbnB1dCBuYW1lZCAic2V4IiB0byBjaG9vc2UgYmV0d2VlbiAiTSIgYW5kICJGIgogIHNlbGVjdElucHV0KCdzZXgnLCAnU2VsZWN0IFNleCcsIGNob2ljZXMgPSBjKCJGIiwgIk0iKSksCiAgIyBDT0RFIEJFTE9XOiBBZGQgc2xpZGVyIGlucHV0IG5hbWVkICd5ZWFyJyB0byBzZWxlY3QgeWVhcnMgICgxOTAwIC0gMjAxMCkKICBzbGlkZXJJbnB1dCgieWVhciIsICJTZWxlY3QgeWVhciIsIHZhbHVlID0gMTkwMCwgbWluID0gMTkwMCwgbWF4ID0gMjAxMCksCiAgIyBBZGQgcGxvdCBvdXRwdXQgdG8gZGlzcGxheSB0b3AgMTAgbW9zdCBwb3B1bGFyIG5hbWVzCiAgcGxvdE91dHB1dCgncGxvdF90b3BfMTBfbmFtZXMnKQopCgpzZXJ2ZXIgPC0gZnVuY3Rpb24oaW5wdXQsIG91dHB1dCwgc2Vzc2lvbil7CiAgIyBSZW5kZXIgcGxvdCBvZiB0b3AgMTAgbW9zdCBwb3B1bGFyIG5hbWVzCiAgb3V0cHV0JHBsb3RfdG9wXzEwX25hbWVzIDwtIHJlbmRlclBsb3QoewogICAgIyBHZXQgdG9wIDEwIG5hbWVzIGJ5IHNleCBhbmQgeWVhcgogICAgdG9wXzEwX25hbWVzIDwtIGJhYnluYW1lcyAlPiUgCiAgICAgIGZpbHRlcihzZXggPT0gaW5wdXQkc2V4KSAlPiUgCiAgICAjIE1PRElGWSBDT0RFIEJFTE9XOiBGaWx0ZXIgZm9yIHRoZSBzZWxlY3RlZCB5ZWFyCiAgICAgIGZpbHRlcih5ZWFyID09IGlucHV0JHllYXIpICU+JSAKICAgICAgdG9wX24oMTAsIHByb3ApCiAgICAjIFBsb3QgdG9wIDEwIG5hbWVzIGJ5IHNleCBhbmQgeWVhcgogICAgICBnZ3Bsb3QodG9wXzEwX25hbWVzLCBhZXMoeCA9IG5hbWUsIHkgPSBwcm9wKSkgKwogICAgICAgIGdlb21fY29sKGZpbGwgPSAiIzI2M2U2MyIpCiAgfSkKfQoKc2hpbnlBcHAodWkgPSB1aSwgc2VydmVyID0gc2VydmVyKQpgYGAKSGF2aW5nIHVzZXJzIHNlbGVjdCBhIHNwZWNpZmljIHllYXIgd2l0aCB0aGUgc2xpZGVySW5wdXQoKSBpcyBtdWNoIGNvb2xlciB0aGFuIHNlbGVjdGluZyB5ZWFycyBmcm9tIGEgZHJvcCBkb3duIChhbmQgaW4gdGhpcyBjYXNlLCAxMDAgeWVhcnMgaW4gYSBkcm9wIGRvd24gd291bGQgYmUgYSBsb3QgZm9yIHRoZW0gdG8gc2Nyb2xsIHRocm91Z2guKQoKIyMgT3V0cHV0cwoKIyMjIEFkZCBhIHRhYmxlIG91dHB1dAoKSW4gb3JkZXIgdG8gYWRkIGFueSBvdXRwdXQgdG8gYSBTaGlueSBhcHAsIHlvdSBuZWVkIHRvOgoKIDEuIENyZWF0ZSB0aGUgb3V0cHV0IChwbG90LCB0YWJsZSwgdGV4dCwgZXRjLikuCiAyLiBSZW5kZXIgdGhlIG91dHB1dCBvYmplY3QgdXNpbmcgdGhlIGFwcHJvcHJpYXRlIHJlbmRlcl9fXyBmdW5jdGlvbi4KIDMuIEFzc2lnbiB0aGUgcmVuZGVyZWQgb2JqZWN0IHRvIG91dHB1dCR4LgogNC4gQWRkIHRoZSBvdXRwdXQgdG8gdGhlIFVJIHVzaW5nIHRoZSBhcHByb3ByaWF0ZSBfX19PdXRwdXQgZnVuY3Rpb24uCgpXZSB3aWxsIGFkZCBhIHRhYmxlIG91dHB1dCB0byB0aGUgYmFieSBuYW1lcyBleHBsb3JlciBhcHAgZXJlYXRlZCBlYXJsaWVyLiBUaGUgY29kZSBpbnNpZGUgYSByZW5kZXJfX18gZnVuY3Rpb24gbmVlZHMgdG8gYmUgd3JhcHBlZCBpbnNpZGUgY3VybHkgYnJhY2VzIChlLmcuIHJlbmRlclBsb3Qoey4uLn0pKS4KYGBge3J9CnVpIDwtIGZsdWlkUGFnZSgKICB0aXRsZVBhbmVsKCJXaGF0J3MgaW4gYSBOYW1lPyIpLAogICMgQWRkIHNlbGVjdCBpbnB1dCBuYW1lZCAic2V4IiB0byBjaG9vc2UgYmV0d2VlbiAiTSIgYW5kICJGIgogIHNlbGVjdElucHV0KCdzZXgnLCAnU2VsZWN0IFNleCcsIGNob2ljZXMgPSBjKCJGIiwgIk0iKSksCiAgIyBBZGQgc2xpZGVyIGlucHV0IG5hbWVkICJ5ZWFyIiB0byBzZWxlY3QgeWVhciBiZXR3ZWVuIDE5MDAgYW5kIDIwMTAKICBzbGlkZXJJbnB1dCgneWVhcicsICdTZWxlY3QgWWVhcicsIG1pbiA9IDE5MDAsIG1heCA9IDIwMTAsIHZhbHVlID0gMTkwMCksCiAgIyBDT0RFIEJFTE9XOiBBZGQgdGFibGUgb3V0cHV0IG5hbWVkICJ0YWJsZV90b3BfMTBfbmFtZXMiCiAgdGFibGVPdXRwdXQoInRhYmxlX3RvcF8xMF9uYW1lcyIpCikKc2VydmVyIDwtIGZ1bmN0aW9uKGlucHV0LCBvdXRwdXQsIHNlc3Npb24pewogICMgRnVuY3Rpb24gdG8gY3JlYXRlIGEgZGF0YSBmcmFtZSBvZiB0b3AgMTAgbmFtZXMgYnkgc2V4IGFuZCB5ZWFyIAogIHRvcF8xMF9uYW1lcyA8LSBmdW5jdGlvbigpewogICAgdG9wXzEwX25hbWVzIDwtIGJhYnluYW1lcyAlPiUgCiAgICAgIGZpbHRlcihzZXggPT0gaW5wdXQkc2V4KSAlPiUgCiAgICAgIGZpbHRlcih5ZWFyID09IGlucHV0JHllYXIpICU+JSAKICAgICAgdG9wX24oMTAsIHByb3ApCiAgfQogICMgQ09ERSBCRUxPVzogUmVuZGVyIGEgdGFibGUgb3V0cHV0IG5hbWVkICJ0YWJsZV90b3BfMTBfbmFtZXMiCiAgb3V0cHV0JHRhYmxlX3RvcF8xMF9uYW1lcyA8LSByZW5kZXJUYWJsZSh7CiAgICB0b3BfMTBfbmFtZXMoKQogIH0pCiAgCn0Kc2hpbnlBcHAodWkgPSB1aSwgc2VydmVyID0gc2VydmVyKQpgYGAKVGhpcyBzdGF0aWMgdGFibGUgb3V0cHV0IGNyZWF0ZXMgYSBuZXcgd2F5IG9mIGxvb2tpbmcgYXQgb3VyIHRvcCBiYWJ5bmFtZXMsIGFuZCB5b3UgYXJlIG5vdyBzdGFydGluZyB0byBnZXQgdGhlIGhhbmcgb2YgdGhlIHByb2Nlc3Mgb2YgYnVpbGRpbmcgYW4gb3V0cHV0IGluIHRoZSBzZXJ2ZXIgdXNpbmcgYSByZW5kZXIgZnVuY3Rpb24gYW5kIHRoZW4gZGlzcGxheWluZyBpdCBpbiB0aGUgVUkgd2l0aCBhbiBvdXRwdXQgZnVuY3Rpb24uCgojIyMgQWRkIGFuIGludGVyYWN0aXZlIHRhYmxlIG91dHB1dAoKVGhlcmUgYXJlIG11bHRpcGxlIGh0bWx3aWRnZXRzIHBhY2thZ2VzIGxpa2UgRFQsIGxlYWZsZXQsIHBsb3RseSwgZXRjLiB0aGF0IHByb3ZpZGUgaGlnaGx5IGludGVyYWN0aXZlIG91dHB1dHMgYW5kIGNhbiBiZSBlYXNpbHkgaW50ZWdyYXRlZCBpbnRvIFNoaW55IGFwcHMgdXNpbmcgYWxtb3N0IHRoZSBzYW1lIHBhdHRlcm4uIEZvciBleGFtcGxlLCB5b3UgY2FuIHR1cm4gYSBzdGF0aWMgdGFibGUgaW4gYSBTaGlueSBhcHAgaW50byBhbiBpbnRlcmFjdGl2ZSB0YWJsZSB1c2luZyB0aGUgRFQgcGFja2FnZToKCiAxLiBDcmVhdGUgYW4gaW50ZXJhY3RpdmUgdGFibGUgdXNpbmcgRFQ6OmRhdGF0YWJsZSgpLgogMi4gUmVuZGVyIGl0IHVzaW5nIERUOjpyZW5kZXJEVCgpLgogMy4gRGlzcGxheSBpdCB1c2luZyBEVDo6RFRPdXRwdXQoKS4KIApXZSB3aWxsIHVwZGF0ZSB0aGUgYXBwIGNyZWF0ZWQgcHJldmlvdXNseSwgcmVwbGFjaW5nIHRoZSBzdGF0aWMgdGFibGUgd2l0aCBhbiBpbnRlcmFjdGl2ZSB0YWJsZS4KCmBgYHtyfQp1aSA8LSBmbHVpZFBhZ2UoCiAgdGl0bGVQYW5lbCgiV2hhdCdzIGluIGEgTmFtZT8iKSwKICAjIEFkZCBzZWxlY3QgaW5wdXQgbmFtZWQgInNleCIgdG8gY2hvb3NlIGJldHdlZW4gIk0iIGFuZCAiRiIKICBzZWxlY3RJbnB1dCgnc2V4JywgJ1NlbGVjdCBTZXgnLCBjaG9pY2VzID0gYygiTSIsICJGIikpLAogICMgQWRkIHNsaWRlciBpbnB1dCBuYW1lZCAieWVhciIgdG8gc2VsZWN0IHllYXIgYmV0d2VlbiAxOTAwIGFuZCAyMDEwCiAgc2xpZGVySW5wdXQoJ3llYXInLCAnU2VsZWN0IFllYXInLCBtaW4gPSAxOTAwLCBtYXggPSAyMDEwLCB2YWx1ZSA9IDE5MDApLAogICMgTU9ESUZZIENPREUgQkVMT1c6IEFkZCBhIERUIG91dHB1dCBuYW1lZCAidGFibGVfdG9wXzEwX25hbWVzIgogIERUOjpEVE91dHB1dCgndGFibGVfdG9wXzEwX25hbWVzJykKKQpzZXJ2ZXIgPC0gZnVuY3Rpb24oaW5wdXQsIG91dHB1dCwgc2Vzc2lvbil7CiAgdG9wXzEwX25hbWVzIDwtIGZ1bmN0aW9uKCl7CiAgICBiYWJ5bmFtZXMgJT4lIAogICAgICBmaWx0ZXIoc2V4ID09IGlucHV0JHNleCkgJT4lIAogICAgICBmaWx0ZXIoeWVhciA9PSBpbnB1dCR5ZWFyKSAlPiUgCiAgICAgIHRvcF9uKDEwLCBwcm9wKQogIH0KICAjIE1PRElGWSBDT0RFIEJFTE9XOiBSZW5kZXIgYSBEVCBvdXRwdXQgbmFtZWQgInRhYmxlX3RvcF8xMF9uYW1lcyIKICBvdXRwdXQkdGFibGVfdG9wXzEwX25hbWVzIDwtIERUOjpyZW5kZXJEVCh7CiAgICBEVDo6ZGF0YXRhYmxlKHRvcF8xMF9uYW1lcygpKQogIH0pCn0Kc2hpbnlBcHAodWkgPSB1aSwgc2VydmVyID0gc2VydmVyKQpgYGAKSnVzdCBieSBhZGp1c3RpbmcgdGhlIGZ1bmN0aW9ucyB5b3UgdXNlZCB0byByZW5kZXIgYW5kIGRpc3BsYXkgdGhlIHRhYmxlLCB5b3UgbWFkZSBpdCBpbnRlcmFjdGl2ZS4gWW91ciB1c2VycyBjYW4gbm93IGZpbHRlciBhbmQgYWRqdXN0IHRoZSBkYXRhIGFuZCBnYWluIGV2ZW4gbW9yZSBpbnNpZ2h0cyBpbnRvIHRoZSB0b3AgYmFieSBuYW1lcy4KCiMjIyBBZGQgaW50ZXJhY3RpdmUgcGxvdCBvdXRwdXQKClNpbWlsYXIgdG8gY3JlYXRpbmcgaW50ZXJhY3RpdmUgdGFibGVzLCB5b3UgY2FuIGVhc2lseSB0dXJuIGEgc3RhdGljIHBsb3QgY3JlYXRlZCB1c2luZyBnZ3Bsb3QyIGludG8gYW4gaW50ZXJhY3RpdmUgcGxvdCB1c2luZyB0aGUgcGxvdGx5IHBhY2thZ2UuIFRvIHJlbmRlciBhbiBpbnRlcmFjdGl2ZSBwbG90LCB1c2UgcGxvdGx5OjpyZW5kZXJQbG90bHkoKSwgYW5kIGRpc3BsYXkgaXQgdXNpbmcgcGxvdGx5OjpwbG90bHlPdXRwdXQoKS4KYGBge3J9CnRvcF90cmVuZHlfbmFtZXMgPC0gcmVhZFJEUygidG9wX3RyZW5keV9uYW1lcy5yZHMiKQpgYGAKCmBgYHtyfQp1aSA8LSBmbHVpZFBhZ2UoCiAgc2VsZWN0SW5wdXQoJ25hbWUnLCAnU2VsZWN0IE5hbWUnLCB0b3BfdHJlbmR5X25hbWVzJG5hbWUpLAogICMgQ09ERSBCRUxPVzogQWRkIGEgcGxvdGx5IG91dHB1dCBuYW1lZCAncGxvdF90cmVuZHlfbmFtZXMnCiAgcGxvdGx5OjpwbG90bHlPdXRwdXQoInBsb3RfdHJlbmR5X25hbWVzIikKKQpzZXJ2ZXIgPC0gZnVuY3Rpb24oaW5wdXQsIG91dHB1dCwgc2Vzc2lvbil7CiAgIyBGdW5jdGlvbiB0byBwbG90IHRyZW5kcyBpbiBhIG5hbWUKICBwbG90X3RyZW5kcyA8LSBmdW5jdGlvbigpewogICAgIGJhYnluYW1lcyAlPiUgCiAgICAgIGZpbHRlcihuYW1lID09IGlucHV0JG5hbWUpICU+JSAKICAgICAgZ2dwbG90KGFlcyh4ID0geWVhciwgeSA9IG4pKSArCiAgICAgIGdlb21fY29sKCkKICB9CiAgIyBDT0RFIEJFTE9XOiBSZW5kZXIgYSBwbG90bHkgb3V0cHV0IG5hbWVkICdwbG90X3RyZW5keV9uYW1lcycKICBvdXRwdXQkcGxvdF90cmVuZHlfbmFtZXMgPC0gcGxvdGx5OjpyZW5kZXJQbG90bHkoewogICAgcGxvdF90cmVuZHMoKQogIH0pCiAgCiAgCn0Kc2hpbnlBcHAodWkgPSB1aSwgc2VydmVyID0gc2VydmVyKQpgYGAKWW91IGNhbiB6b29tIGluIG9uIGNlcnRhaW4gYXJlYXMsIHpvb20gYmFjayBvdXQsIGFuZCBob3ZlciBvdmVyIHRoZSBiYXJzIHRvIHNlZSB0aGUgdmFsdWVzLiBUaGlzIG1ha2VzIHBsb3RzIGluIHlvdXIgYXBwIGZhciBtb3JlIGludGVyZXN0aW5nLCBhbmQgYWxsb3dzIHVzZXJzIHRvIGdhaW4gaW5zaWdodHMgd2l0aG91dCBoYXZpbmcgdG8gc2VlIGFueSBjb2RlIG9yIGRhdGEuCgojIyBMYXlvdXRzIGFuZCB0aGVtZXMKCiMjIyBTaWRlYmFyIGxheW91dHMKCkxheW91dCBmdW5jdGlvbnMgYWxsb3cgaW5wdXRzIGFuZCBvdXRwdXRzIHRvIGJlIHZpc3VhbGx5IGFycmFuZ2VkIGluIHRoZSBVSS4gQSB3ZWxsLWNob3NlbiBsYXlvdXQgbWFrZXMgYSBTaGlueSBhcHAgYWVzdGhldGljYWxseSBtb3JlIGFwcGVhbGluZywgYW5kIGFsc28gaW1wcm92ZXMgdGhlIHVzZXIgZXhwZXJpZW5jZS4KCldlIHdpbGwgbW9kaWZ5IHRoZSBsYXlvdXQgb2YgYSBTaGlueSBhcHAgdGhhdCBsZXRzIHVzZXJzIGV4cGxvcmUgdGhlIHBvcHVsYXJpdHkgb2YgdHJlbmR5IG5hbWVzLgpgYGB7cn0KdWkgPC0gZmx1aWRQYWdlKAogICMgTU9ESUZZIENPREUgQkVMT1c6IFdyYXAgaW4gYSBzaWRlYmFyTGF5b3V0CiAgICAjIE1PRElGWSBDT0RFIEJFTE9XOiBXcmFwIGluIGEgc2lkZWJhclBhbmVsCiAgICBzaWRlYmFyTGF5b3V0KAogICAgc2lkZWJhclBhbmVsKHNlbGVjdElucHV0KCduYW1lJywgJ1NlbGVjdCBOYW1lJywgdG9wX3RyZW5keV9uYW1lcyRuYW1lKSksCiAgICAjIE1PRElGWSBDT0RFIEJFTE9XOiBXcmFwIGluIGEgbWFpblBhbmVsCiAgICBtYWluUGFuZWwoCiAgICBwbG90bHk6OnBsb3RseU91dHB1dCgncGxvdF90cmVuZHlfbmFtZXMnKSwKICAgIERUOjpEVE91dHB1dCgndGFibGVfdHJlbmR5X25hbWVzJykpCiApCikKIyBETyBOT1QgTU9ESUZZCnNlcnZlciA8LSBmdW5jdGlvbihpbnB1dCwgb3V0cHV0LCBzZXNzaW9uKXsKICAjIEZ1bmN0aW9uIHRvIHBsb3QgdHJlbmRzIGluIGEgbmFtZQogIHBsb3RfdHJlbmRzIDwtIGZ1bmN0aW9uKCl7CiAgICAgYmFieW5hbWVzICU+JSAKICAgICAgZmlsdGVyKG5hbWUgPT0gaW5wdXQkbmFtZSkgJT4lIAogICAgICBnZ3Bsb3QoYWVzKHggPSB5ZWFyLCB5ID0gbikpICsKICAgICAgZ2VvbV9jb2woKQogIH0KICBvdXRwdXQkcGxvdF90cmVuZHlfbmFtZXMgPC0gcGxvdGx5OjpyZW5kZXJQbG90bHkoewogICAgcGxvdF90cmVuZHMoKQogIH0pCiAgCiAgb3V0cHV0JHRhYmxlX3RyZW5keV9uYW1lcyA8LSBEVDo6cmVuZGVyRFQoewogICAgYmFieW5hbWVzICU+JSAKICAgICAgZmlsdGVyKG5hbWUgPT0gaW5wdXQkbmFtZSkKICB9KQp9CnNoaW55QXBwKHVpID0gdWksIHNlcnZlciA9IHNlcnZlcikKYGBgClRoaXMgYXBwIGlzIGZhciBjbGVhbmVyIGxvb2tpbmcgd2l0aCB0aGUgc2VsZWN0SW5wdXQoKSBvbiB0aGUgbGVmdCBhbmQgdGhlIHBsb3QgaW4gdGhlIG1haW4gcGFuZWwgb24gdGhlIHJpZ2h0LgoKIyMjIFRhYiBsYXlvdXRzCgpEaXNwbGF5aW5nIHNldmVyYWwgdGFibGVzIGFuZCBwbG90cyBvbiB0aGUgc2FtZSBwYWdlIGNhbiBsZWFkIHRvIHZpc3VhbCBjbHV0dGVyIGFuZCBkaXN0cmFjdCB1c2VycyBvZiB0aGUgYXBwLiBJbiBzdWNoIGNhc2VzLCB0aGUgdGFiIGxheW91dCBjb21lcyBpbiBoYW5keSwgYXMgaXQgYWxsb3dzIGRpZmZlcmVudCBvdXRwdXRzIHRvIGJlIGRpc3BsYXllZCBhcyB0YWJzLgoKV2Ugd2lsbCBzdGFydCB3aXRoIHRoZSBTaGlueSBhcHAgdXNpbmcgdGhlIHNpZGViYXIgbGF5b3V0IGZyb20gdGhlIGxhc3QgZXhhbXBsZSBhbmQgbW9kaWZ5IGl0IHRvIHVzZSB0YWJzLiBUaGlzIGV4YW1wbGUgc2hvdWxkIGFsc28gbWFrZSBpdCB2ZXJ5IGNsZWFyIHRoYXQgU2hpbnkgbWFrZXMgaXQgcmVhbGx5IGVhc3kgdG8gc3dpdGNoIGFwcCBsYXlvdXRzIHdpdGggb25seSBhIGZldyBtb2RpZmljYXRpb25zIHRvIHRoZSBjb2RlLgoKYGBge3J9CnVpIDwtIGZsdWlkUGFnZSgKICBzaWRlYmFyTGF5b3V0KAogICAgc2lkZWJhclBhbmVsKAogICAgICBzZWxlY3RJbnB1dCgnbmFtZScsICdTZWxlY3QgTmFtZScsIHRvcF90cmVuZHlfbmFtZXMkbmFtZSkKICAgICksCiAgICBtYWluUGFuZWwoCiAgICAgICMgTU9ESUZZIENPREUgQkxPQ0sgQkVMT1c6IFdyYXAgaW4gYSB0YWJzZXRQYW5lbAogICAgICB0YWJzZXRQYW5lbCgKICAgICAgICAjIE1PRElGWSBDT0RFIEJFTE9XOiBXcmFwIGluIGEgdGFiUGFuZWwgcHJvdmlkaW5nIGFuIGFwcHJvcHJpYXRlIGxhYmVsCiAgICAgICAgdGFiUGFuZWwoIlBsb3QiLAogICAgICAgIHBsb3RseTo6cGxvdGx5T3V0cHV0KCdwbG90X3RyZW5keV9uYW1lcycpKSwKICAgICAgICAjIE1PRElGWSBDT0RFIEJFTE9XOiBXcmFwIGluIGEgdGFiUGFuZWwgcHJvdmlkaW5nIGFuIGFwcHJvcHJpYXRlIGxhYmVsCiAgICAgICAgdGFiUGFuZWwoIlRhYmxlIiwgRFQ6OkRUT3V0cHV0KCd0YWJsZV90cmVuZHlfbmFtZXMnKSkKICAgICAgKQogICAgKQogICkKKQpzZXJ2ZXIgPC0gZnVuY3Rpb24oaW5wdXQsIG91dHB1dCwgc2Vzc2lvbil7CiAgIyBGdW5jdGlvbiB0byBwbG90IHRyZW5kcyBpbiBhIG5hbWUKICBwbG90X3RyZW5kcyA8LSBmdW5jdGlvbigpewogICAgIGJhYnluYW1lcyAlPiUgCiAgICAgIGZpbHRlcihuYW1lID09IGlucHV0JG5hbWUpICU+JSAKICAgICAgZ2dwbG90KGFlcyh4ID0geWVhciwgeSA9IG4pKSArCiAgICAgIGdlb21fY29sKCkKICB9CiAgb3V0cHV0JHBsb3RfdHJlbmR5X25hbWVzIDwtIHBsb3RseTo6cmVuZGVyUGxvdGx5KHsKICAgIHBsb3RfdHJlbmRzKCkKICB9KQogIAogIG91dHB1dCR0YWJsZV90cmVuZHlfbmFtZXMgPC0gRFQ6OnJlbmRlckRUKHsKICAgIGJhYnluYW1lcyAlPiUgCiAgICAgIGZpbHRlcihuYW1lID09IGlucHV0JG5hbWUpCiAgfSkKfQpzaGlueUFwcCh1aSA9IHVpLCBzZXJ2ZXIgPSBzZXJ2ZXIpCmBgYApBcyB5b3UgY2FuIHNlZSwgYSB0YWIgcGFuZWwgaXMgYSBtdWNoIGNsZWFuZXIgd2F5IHRvIGV4dGVuZCBhIHNpZGViYXIgbGF5b3V0IGFuZCBkaXNwbGF5IG11bHRpcGxlIHBpZWNlcyBvZiBpbmZvcm1hdGlvbiBpbiBvbmUgYXBwLiBUYWIgbGF5b3V0cyBiZWNvbWUgZXNwZWNpYWxseSBoZWxwZnVsIGlmIHlvdSBiZWdpbiB0byBidWlsZCBkYXNoYm9hcmRzIGluIFNoaW55LgoKIyMjIFRoZW1lcwoKU2hpbnkgbWFrZXMgaXQgZWFzeSB0byBjdXN0b21pemUgdGhlIHRoZW1lIG9mIGFuIGFwcC4gVGhlIFVJIGZ1bmN0aW9ucyBpbiBTaGlueSBtYWtlIHVzZSBvZiBbVHdpdHRlciBCb290c3RyYXBdKGh0dHBzOi8vZ2V0Ym9vdHN0cmFwLmNvbS9kb2NzLzMuNC8pLCBhIHBvcHVsYXIgZnJhbWV3b3JrIGZvciBidWlsZGluZyB3ZWIgYXBwbGljYXRpb25zLiBbQm9vdHN3YXRjaF0oaHR0cHM6Ly9ib290c3dhdGNoLmNvbS8pIGV4dGVuZHMgQm9vdHN0cmFwIGJ5IG1ha2luZyBpdCByZWFsbHkgZWFzeSB0byBza2luIGFuIGFwcGxpY2F0aW9uIHdpdGggbWluaW1hbCBjb2RlIGNoYW5nZXMuCgpXZSB3aWxsIGFkZCBhIHRpdGxlIHBhbmVsIHRvIHlvdXIgYXBwLCB1c2UgdGhlIHRoZW1lIHNlbGVjdG9yIHRvIGV4cGxvcmUgZGlmZmVyZW50IHRoZW1lcywgYW5kIGFwcGx5IHRoZW4gYSB0aGVtZSBvZiB5b3VyIGNob2ljZS4KCmBgYHtyfQp1aSA8LSBmbHVpZFBhZ2UoCiAgIyBDT0RFIEJFTE9XOiBBZGQgYSB0aXRsZVBhbmVsIHdpdGggYW4gYXBwcm9wcmlhdGUgdGl0bGUKICAKICAjIFJFUExBQ0UgQ09ERSBCRUxPVzogd2l0aCB0aGVtZSA9IHNoaW55dGhlbWVzOjpzaGlueXRoZW1lKCI8eW91ciB0aGVtZT4iKQogIHNoaW55dGhlbWVzOjp0aGVtZVNlbGVjdG9yKCksCiAgc2lkZWJhckxheW91dCgKICAgIHNpZGViYXJQYW5lbCgKICAgICAgc2VsZWN0SW5wdXQoJ25hbWUnLCAnU2VsZWN0IE5hbWUnLCB0b3BfdHJlbmR5X25hbWVzJG5hbWUpCiAgICApLAogICAgbWFpblBhbmVsKAogICAgICB0YWJzZXRQYW5lbCgKICAgICAgICB0YWJQYW5lbCgnUGxvdCcsIHBsb3RseTo6cGxvdGx5T3V0cHV0KCdwbG90X3RyZW5keV9uYW1lcycpKSwKICAgICAgICB0YWJQYW5lbCgnVGFibGUnLCBEVDo6RFRPdXRwdXQoJ3RhYmxlX3RyZW5keV9uYW1lcycpKQogICAgICApCiAgICApCiAgKQopCnNlcnZlciA8LSBmdW5jdGlvbihpbnB1dCwgb3V0cHV0LCBzZXNzaW9uKXsKICAjIEZ1bmN0aW9uIHRvIHBsb3QgdHJlbmRzIGluIGEgbmFtZQogIHBsb3RfdHJlbmRzIDwtIGZ1bmN0aW9uKCl7CiAgICAgYmFieW5hbWVzICU+JSAKICAgICAgZmlsdGVyKG5hbWUgPT0gaW5wdXQkbmFtZSkgJT4lIAogICAgICBnZ3Bsb3QoYWVzKHggPSB5ZWFyLCB5ID0gbikpICsKICAgICAgZ2VvbV9jb2woKQogIH0KICBvdXRwdXQkcGxvdF90cmVuZHlfbmFtZXMgPC0gcGxvdGx5OjpyZW5kZXJQbG90bHkoewogICAgcGxvdF90cmVuZHMoKQogIH0pCiAgCiAgb3V0cHV0JHRhYmxlX3RyZW5keV9uYW1lcyA8LSBEVDo6cmVuZGVyRFQoewogICAgYmFieW5hbWVzICU+JSAKICAgICAgZmlsdGVyKG5hbWUgPT0gaW5wdXQkbmFtZSkKICB9KQp9CnNoaW55QXBwKHVpID0gdWksIHNlcnZlciA9IHNlcnZlcikKYGBgCmBgYHtyfQp1aSA8LSBmbHVpZFBhZ2UoCiAgIyBDT0RFIEJFTE9XOiBBZGQgYSB0aXRsZVBhbmVsIHdpdGggYW4gYXBwcm9wcmlhdGUgdGl0bGUKICB0aXRsZVBhbmVsKCJUcmVuZHkgTmFtZXMiKSwKICAjIFJFUExBQ0UgQ09ERSBCRUxPVzogd2l0aCB0aGVtZSA9IHNoaW55dGhlbWVzOjpzaGlueXRoZW1lKCI8eW91ciB0aGVtZT4iKQogIHRoZW1lID0gc2hpbnl0aGVtZXM6OnNoaW55dGhlbWUoInNwYWNlbGFiIiksCiAgc2lkZWJhckxheW91dCgKICAgIHNpZGViYXJQYW5lbCgKICAgICAgc2VsZWN0SW5wdXQoJ25hbWUnLCAnU2VsZWN0IE5hbWUnLCB0b3BfdHJlbmR5X25hbWVzJG5hbWUpCiAgICApLAogICAgbWFpblBhbmVsKAogICAgICB0YWJzZXRQYW5lbCgKICAgICAgICB0YWJQYW5lbCgnUGxvdCcsIHBsb3RseTo6cGxvdGx5T3V0cHV0KCdwbG90X3RyZW5keV9uYW1lcycpKSwKICAgICAgICB0YWJQYW5lbCgnVGFibGUnLCBEVDo6RFRPdXRwdXQoJ3RhYmxlX3RyZW5keV9uYW1lcycpKQogICAgICApCiAgICApCiAgKQopCnNlcnZlciA8LSBmdW5jdGlvbihpbnB1dCwgb3V0cHV0LCBzZXNzaW9uKXsKICAjIEZ1bmN0aW9uIHRvIHBsb3QgdHJlbmRzIGluIGEgbmFtZQogIHBsb3RfdHJlbmRzIDwtIGZ1bmN0aW9uKCl7CiAgICAgYmFieW5hbWVzICU+JSAKICAgICAgZmlsdGVyKG5hbWUgPT0gaW5wdXQkbmFtZSkgJT4lIAogICAgICBnZ3Bsb3QoYWVzKHggPSB5ZWFyLCB5ID0gbikpICsKICAgICAgZ2VvbV9jb2woKQogIH0KICBvdXRwdXQkcGxvdF90cmVuZHlfbmFtZXMgPC0gcGxvdGx5OjpyZW5kZXJQbG90bHkoewogICAgcGxvdF90cmVuZHMoKQogIH0pCiAgCiAgb3V0cHV0JHRhYmxlX3RyZW5keV9uYW1lcyA8LSBEVDo6cmVuZGVyRFQoewogICAgYmFieW5hbWVzICU+JSAKICAgICAgZmlsdGVyKG5hbWUgPT0gaW5wdXQkbmFtZSkKICB9KQp9CnNoaW55QXBwKHVpID0gdWksIHNlcnZlciA9IHNlcnZlcikKYGBgCiBUaGVyZSBhcmUgYSBsb3Qgb2YgY29vbCBidWlsdC1pbiB0aGVtZXMgaW4gc2hpbnl0aGVtZXMsIGFuZCBpZiBub25lIG9mIHRoZW0gc3VpdCB5b3VyIGZhbmN5LCB5b3UgY2FuIGxlYXJuIGhvdyB0byBmdXJ0aGVyIGN1c3RvbWl6ZSB5b3VyIGFwcCB3aXRoIGN1c3RvbSBDU1MuCiAKIyMgQnVpbGRpbmcgYXBwcwoKIyMjIEFwcCAxOiBNdWx0aWxpbmd1YWwgR3JlZXRpbmcKClRoZSBiZXN0IHdheSB0byBsZWFybiBTaGlueSBpcyBieSBkZWNvbnN0cnVjdGluZyBhbiBleGlzdGluZyBhcHAgYW5kIHJlYnVpbGRpbmcgaXQgZnJvbSBzY3JhdGNoLgoKV2UgYXJlIGdvaW5nIHRvIGJ1aWxkIGEgU2hpbnkgYXBwIHRoYXQgYWxsb3dzIHlvdSB0byBlbnRlciB5b3VyIG5hbWUgYW5kIHNlbGVjdCBhIGdyZWV0aW5nIChIZWxsby9Cb25qb3VyKSwgYW5kIHJldHVybnMgIkhlbGxvLCBLYWVsZW4iLCB3aGVuIHRoZSB1c2VyIGlzIEthZWxlbi4gQWRtaXR0ZWRseSwgaXQgaXMgYSByZWFsbHkgc2ltcGxlIGFwcCwgYnV0IHRoZSBjaGFsbGVuZ2UgaXMgd2UgYXJlIGdvaW5nIHRvIGhhdmUgdG8gY29kZSBpdCBmcm9tIHNjcmF0Y2ghCgpGb3VyIHN0ZXBzIHRvIGJ1aWxkaW5nIGEgU2hpbnkgYXBwIGFyZToKCiAxLiBBZGQgaW5wdXRzCiAyLiBBZGQgb3V0cHV0cwogMy4gVXBkYXRlIGxheW91dAogNC4gVXBkYXRlIG91dHB1dHMKCmBgYHtyfQp1aSA8LSBmbHVpZFBhZ2UoCiAgc2VsZWN0SW5wdXQoInNlbGVjdCIsICJTZWxlY3QgZ3JlZXRpbmciLCBjaG9pY2VzID0gYygiSGVsbG8iLCAiQm9uam91ciIpKSwKICB0ZXh0SW5wdXQoIm5hbWUiLCAiRW50ZXIgeW91ciBuYW1lIiksCiAgCiAgdGV4dE91dHB1dCgiZ3JlZXRpbmciKQopCgpzZXJ2ZXIgPC0gZnVuY3Rpb24oaW5wdXQsIG91dHB1dCwgc2Vzc2lvbikgewogIG91dHB1dCRncmVldGluZyA8LSByZW5kZXJUZXh0KHsKICAgIHBhc3RlKGlucHV0JHNlbGVjdCwgaW5wdXQkbmFtZSkKICB9KQp9CgpzaGlueUFwcCh1aSA9IHVpLCBzZXJ2ZXIgPSBzZXJ2ZXIpCmBgYAojIyMgQXBwIDI6IFBvcHVsYXIgQmFieSBOYW1lcwoKQnVpbGRpbmcgYSBTaGlueSBhcHAgaXMgYSBtb2R1bGFyIHByb2Nlc3MuIFlvdSBzdGFydCB3aXRoIHRoZSBVSSwgdGhlbiB5b3Ugd29yayBvbiB0aGUgc2VydmVyIGNvZGUsIGJ1aWxkaW5nIG91dHB1dHMgYmFzZWQgb24gdGhlIHVzZXIgaW5wdXRzLiBUaGUgbW9yZSB5b3UgcHJhY3RpY2UgdGhpcyBhcHByb2FjaCBkZWxpYmVyYXRlbHksIHRoZSBlYXNpZXIgaXQgd2lsbCBiZWNvbWUgdG8gYnVpbGQgZ29vZCBhcHBzLgoKWW91IHdpbGwgbm93IGJ1aWxkIGEgU2hpbnkgYXBwIHRoYXQgbGV0cyBhIHVzZXIgY2hvb3NlIHNleCBhbmQgeWVhciwgYW5kIHdpbGwgZGlzcGxheSB0aGUgdG9wIDEwIG1vc3QgcG9wdWxhciBuYW1lcyBpbiB0aGF0IHllYXIgYXMgYSBjb2x1bW4gcGxvdCBvZiBwcm9wb3J0aW9uIG9mIGJpcnRocyAocHJvcCkgYnkgbmFtZSAobmFtZSkuCgpXZSBoYXZlIHByb3ZpZGVkIGEgZnVuY3Rpb24gZ2V0X3RvcF9uYW1lcygpIHRvIGV4dHJhY3QgdGhlIHRvcCAxMCBuYW1lcyBmb3IgYSBnaXZlbiB5ZWFyIGFuZCBzZXguIEZvciBleGFtcGxlLCB5b3UgY2FuIGdldCB0aGUgdG9wIDEwIG1hbGUgbmFtZXMgZm9yIHRoZSB5ZWFyIDIwMDAgdXNpbmcgZ2V0X3RvcF9uYW1lcygyMDAwLCAiTSIpLgpgYGB7cn0KZ2V0X3RvcF9uYW1lcyA8LSBmdW5jdGlvbigueWVhciwgLnNleCkgewogIGJhYnluYW1lcyAlPiUgCiAgICBmaWx0ZXIoeWVhciA9PSAueWVhcikgJT4lIAogICAgZmlsdGVyKHNleCA9PSAuc2V4KSAlPiUgCiAgICB0b3BfbigxMCkgJT4lIAogICAgbXV0YXRlKG5hbWUgPSBmb3JjYXRzOjpmY3RfaW5vcmRlcihuYW1lKSkKfQpgYGAKV2UgY2FuIGNyZWF0ZSBhIGNvbHVtbiBwbG90IGZyb20gYSBkYXRhIGZyYW1lIGQgd2l0aCBjb2x1bW5zIHggYW5kIHkgdXNpbmc6CgogICAgZ2dwbG90KGQsIGFlcyh4ID0geCwgeSA9IHkpKSArCiAgICAgIGdlb21fY29sKCkKCmBgYHtyfQp1aSA8LSBmbHVpZFBhZ2UoCiAgdGl0bGVQYW5lbCgiTW9zdCBQb3B1bGFyIE5hbWVzIiksCiAgc2lkZWJhckxheW91dCgKICBzaWRlYmFyUGFuZWwoCiAgc2VsZWN0SW5wdXQoInNleCIsICJTZWxlY3QgU2V4IiwgY2hvaWNlcyA9IGMoIk0iLCAiRiIpKSwKICBzbGlkZXJJbnB1dCgieWVhciIsICJTZWxlY3QgWWVhciIsIG1pbiA9IDE4ODAsIG1heCA9IDIwMTcsIHZhbHVlID0gMTkwMCkpLAogIG1haW5QYW5lbCgKICBwbG90T3V0cHV0KCJwbG90X3BvcHVsYXJfbmFtZXMiKQogICAgKQogICkKKQoKc2VydmVyIDwtIGZ1bmN0aW9uKGlucHV0LCBvdXRwdXQsIHNlc3Npb24pIHsKICBvdXRwdXQkcGxvdF9wb3B1bGFyX25hbWVzIDwtIHJlbmRlclBsb3QoewogICAgZ2dwbG90KGdldF90b3BfbmFtZXMoaW5wdXQkeWVhciwgaW5wdXQkc2V4KSwgYWVzKHggPSBuYW1lLCB5ID0gcHJvcCkpICsKICAgIGdlb21fY29sKCkKICB9KQp9CnNoaW55QXBwKHVpID0gdWksIHNlcnZlciA9IHNlcnZlcikKYGBgCiMjIyBBcHAgMzogUG9wdWxhciBCYWJ5IE5hbWVzIFJlZHV4CgpMZXQgdXMgd3JhcCB0aGlzIGNoYXB0ZXIgdXAgYnkgZW5oYW5jaW5nIHRoZSBhcHAgd2UgYnVpbHQgZWFybGllciBieSBhZGRpbmcgYSB0YWJsZSBzaG93aW5nIHRoZSB0b3AgMTAgYmFieSBuYW1lcyBhcyBhIHRhYi4KYGBge3J9CnVpIDwtIGZsdWlkUGFnZSgKICB0aXRsZVBhbmVsKCJNb3N0IFBvcHVsYXIgTmFtZXMiKSwKICBzaWRlYmFyTGF5b3V0KAogICAgc2lkZWJhclBhbmVsKAogICAgICBzZWxlY3RJbnB1dCgic2V4IiwgIlNlbGVjdCBTZXgiLCBjaG9pY2VzID0gYygiTSIsICJGIikpLAogICAgICBzbGlkZXJJbnB1dCgieWVhciIsICJTZWxlY3QgWWVhciIsIG1pbiA9IDE4ODAsIG1heCA9IDIwMTcsIHZhbHVlID0gMTkwMCkKICAgICksCiAgICBtYWluUGFuZWwoCiAgICAgIHRhYnNldFBhbmVsKAogICAgICAgIHRhYlBhbmVsKCJQbG90IiwgcGxvdE91dHB1dCgicGxvdF9wb3B1bGFyX25hbWVzIikpLAogICAgICAgIHRhYlBhbmVsKCJUYWJsZSIsIHRhYmxlT3V0cHV0KCJ0YWJsZV9wb3BsYXJfbmFtZXMiKSkKICAgICAgKQogICAgKQogICkKKQoKc2VydmVyIDwtIGZ1bmN0aW9uKGlucHV0LCBvdXRwdXQsIHNlc3Npb24pIHsKICAgIG91dHB1dCRwbG90X3BvcHVsYXJfbmFtZXMgPC0gcmVuZGVyUGxvdCh7CiAgICBnZ3Bsb3QoZ2V0X3RvcF9uYW1lcyhpbnB1dCR5ZWFyLCBpbnB1dCRzZXgpLCBhZXMoeCA9IG5hbWUsIHkgPSBwcm9wKSkgKwogICAgZ2VvbV9jb2woKQogIH0pCiAgICBvdXRwdXQkdGFibGVfcG9wbGFyX25hbWVzIDwtIHJlbmRlclRhYmxlKHsKICAgICAgZ2V0X3RvcF9uYW1lcyhpbnB1dCR5ZWFyLCBpbnB1dCRzZXgpCiAgICB9KQp9CnNoaW55QXBwKHVpID0gdWksIHNlcnZlciA9IHNlcnZlcikKYGBgCgoKCiMgUmVhY3RpdmUgUHJvZ3JhbW1pbmcKCiMjIFJlYWN0aXZpdHkgMTAxCgojIyMgU291cmNlIHZzLiBDb25kdWN0b3IgdnMuIEVuZHBvaW50CgpUaGUgbWFnaWMgYmVoaW5kIFNoaW55IGlzIGRyaXZlbiBieSByZWFjdGl2aXR5LiBBcyB5b3UgbGVhcm5lZCBpbiB0aGlzIGxlc3NvbiwgdGhlcmUgYXJlIHRocmVlIHR5cGVzIG9mIHJlYWN0aXZlIGNvbXBvbmVudHMgaW4gYSBTaGlueSBhcHAuCgoxLiBSZWFjdGl2ZSBzb3VyY2U6IFVzZXIgaW5wdXQgdGhhdCBjb21lcyB0aHJvdWdoIGEgYnJvd3NlciBpbnRlcmZhY2UsIHR5cGljYWxseS4KMi4gUmVhY3RpdmUgY29uZHVjdG9yOiBSZWFjdGl2ZSBjb21wb25lbnQgYmV0d2VlbiBhIHNvdXJjZSBhbmQgYW4gZW5kcG9pbnQsIHR5cGljYWxseSB1c2VkIHRvIGVuY2Fwc3VsYXRlIHNsb3cgY29tcHV0YXRpb25zLgozLiBSZWFjdGl2ZSBlbmRwb2ludDogU29tZXRoaW5nIHRoYXQgYXBwZWFycyBpbiB0aGUgdXNlcidzIGJyb3dzZXIgd2luZG93LCBzdWNoIGFzIGEgcGxvdCBvciBhIHRhYmxlIG9mIHZhbHVlcy4KCiAgICB1aSA8LSBmbHVpZFBhZ2UoCiAgICAgIHRpdGxlUGFuZWwoJ0JNSSBDYWxjdWxhdG9yJyksCiAgICAgIHRoZW1lID0gc2hpbnl0aGVtZXM6OnNoaW55dGhlbWUoJ2Nvc21vJyksCiAgICAgIHNpZGViYXJMYXlvdXQoCiAgICAgICAgc2lkZWJhclBhbmVsKAogICAgICAgICAgbnVtZXJpY0lucHV0KCdoZWlnaHQnLCAnRW50ZXIgeW91ciBoZWlnaHQgaW4gbWV0ZXJzJywgMS41LCAxLCAyKSwKICAgICAgICAgIG51bWVyaUlucHV0KCd3ZWlnaHQnLCAnRW50ZXIgeW91ciB3ZWlnaHQgaW4gS2lsb2dyYW1zJywgNjAsIDQ1LCAxMjApCiAgICAgICAgKSwKICAgICAgICBtYWluUGFuZWwoCiAgICAgICAgICB0ZXh0T3V0cHV0KCJibWkiKSwKICAgICAgICAgIHRleHRPdXRwdXQoImJtaV9yYW5nZSIpCiAgICAgICAgKQogICAgICApCiAgICApCiAgICBzZXJ2ZXIgPC0gZnVuY3Rpb24oaW5wdXQsIG91dHB1dCwgc2Vzc2lvbikgewogICAgICBydmFsX2JtaSA8LSByZWFjdGl2ZSh7CiAgICAgICAgaW5wdXQkd2VpZ2h0LyhpbnB1dCRoZWlnaHReMikKICAgICAgfSkKICAgICAgb3V0cHV0JGJtaSA8LSByZW5kZXJUZXh0KHsKICAgICAgICBibWkgPC0gcnZhbF9ibWkoKQogICAgICAgIHBhc3RlKCJZb3VyIEJNSSBpcyIsIHJvdW5kKGJtaSwgMSkpCiAgICAgIH0pCiAgICAgIG91dHB1dCRibWlfcmFuZ2UgPC0gcmVuZGVyVGV4dCh7CiAgICAgICAgYm1pIDwtIHJ2YWxfYm1pKCkKICAgICAgICBoZWFsdGhfc3RhdHVzIDwtIGN1dChibWksIAogICAgICAgICAgYnJlYWtzID0gYygwLCAxOC41LCAyNC45LCAyOS45LCA0MCksCiAgICAgICAgICBsYWJlbHMgPSBjKCd1bmRlcndlaWdodCcsICdoZWFsdGh5JywgJ292ZXJ3ZWlnaHQnLCAnb2Jlc2UnKQogICAgICAgICkKICAgICAgICBwYXN0ZSgiWW91IGFyZSIsIGhlYWx0aF9zdGF0dXMpCiAgICAgIH0pCiAgICB9CiAgICBzaGlueUFwcCh1aSwgc2VydmVyKQoKIyMjIEFkZCBhIHJlYWN0aXZlIGV4cHJlc3Npb24KCkEgcmVhY3RpdmUgZXhwcmVzc2lvbiBpcyBhbiBSIGV4cHJlc3Npb24gdGhhdCB1c2VzIHdpZGdldCBpbnB1dCBhbmQgcmV0dXJucyBhIHZhbHVlLiBUaGUgcmVhY3RpdmUgZXhwcmVzc2lvbiB3aWxsIHVwZGF0ZSB0aGlzIHZhbHVlIHdoZW5ldmVyIHRoZSBvcmlnaW5hbCB3aWRnZXQgY2hhbmdlcy4gUmVhY3RpdmUgZXhwcmVzc2lvbnMgYXJlIGxhenkgYW5kIGNhY2hlZC4KCkluIHRoaXMgZXhlcmNpc2UsIHlvdSB3aWxsIGVuY2Fwc3VsYXRlIGEgcmVwZWF0ZWQgY29tcHV0YXRpb24gYXMgYSByZWFjdGl2ZSBleHByZXNzaW9uLgpgYGB7cn0Kc2VydmVyIDwtIGZ1bmN0aW9uKGlucHV0LCBvdXRwdXQsIHNlc3Npb24pIHsKICAjIENPREUgQkVMT1c6IEFkZCBhIHJlYWN0aXZlIGV4cHJlc3Npb24gcnZhbF9ibWkgdG8gY2FsY3VsYXRlIEJNSQogIHJ2YWxfYm1pIDwtIHJlYWN0aXZlKHsKICAgIGlucHV0JHdlaWdodC8oaW5wdXQkaGVpZ2h0XjIpCiAgfSkKICAKICBvdXRwdXQkYm1pIDwtIHJlbmRlclRleHQoewogICAgIyBNT0RJRlkgQ09ERSBCRUxPVzogUmVwbGFjZSByaWdodC1oYW5kLXNpZGUgd2l0aCByZWFjdGl2ZSBleHByZXNzaW9uCiAgICBibWkgPC0gcnZhbF9ibWkoKQogICAgcGFzdGUoIllvdXIgQk1JIGlzIiwgcm91bmQoYm1pLCAxKSkKICB9KQogIG91dHB1dCRibWlfcmFuZ2UgPC0gcmVuZGVyVGV4dCh7CiAgICAjIE1PRElGWSBDT0RFIEJFTE9XOiBSZXBsYWNlIHJpZ2h0LWhhbmQtc2lkZSB3aXRoIHJlYWN0aXZlIGV4cHJlc3Npb24KICAgIGJtaSA8LSBydmFsX2JtaSgpCiAgICBibWlfc3RhdHVzIDwtIGN1dChibWksIAogICAgICBicmVha3MgPSBjKDAsIDE4LjUsIDI0LjksIDI5LjksIDQwKSwKICAgICAgbGFiZWxzID0gYygndW5kZXJ3ZWlnaHQnLCAnaGVhbHRoeScsICdvdmVyd2VpZ2h0JywgJ29iZXNlJykKICAgICkKICAgIHBhc3RlKCJZb3UgYXJlIiwgYm1pX3N0YXR1cykKICB9KQp9CnVpIDwtIGZsdWlkUGFnZSgKICB0aXRsZVBhbmVsKCdCTUkgQ2FsY3VsYXRvcicpLAogIHNpZGViYXJMYXlvdXQoCiAgICBzaWRlYmFyUGFuZWwoCiAgICAgIG51bWVyaWNJbnB1dCgnaGVpZ2h0JywgJ0VudGVyIHlvdXIgaGVpZ2h0IGluIG1ldGVycycsIDEuNSwgMSwgMiksCiAgICAgIG51bWVyaWNJbnB1dCgnd2VpZ2h0JywgJ0VudGVyIHlvdXIgd2VpZ2h0IGluIEtpbG9ncmFtcycsIDYwLCA0NSwgMTIwKQogICAgKSwKICAgIG1haW5QYW5lbCgKICAgICAgdGV4dE91dHB1dCgiYm1pIiksCiAgICAgIHRleHRPdXRwdXQoImJtaV9yYW5nZSIpCiAgICApCiAgKQopCgpzaGlueUFwcCh1aSA9IHVpLCBzZXJ2ZXIgPSBzZXJ2ZXIpCmBgYApFbmNhcHN1bGF0aW5nIGNvbXB1dGF0aW9ucyBhcyByZWFjdGl2ZSBleHByZXNzaW9ucyBpcyBrZXkgdG8gYnVpbGRpbmcgbW9kdWxhciBhbmQgcGVyZm9ybWFudCBTaGlueSBhcHBzLgoKIyMjIFVuZGVyc3RhbmRpbmcgcmVhY3RpdmUgZXhwcmVzc2lvbnMKCk9uZSBvZiB0aGUgY2VudHJhbCB0ZW5ldHMgb2YgcmVhY3Rpdml0eSBpcyB0aGF0IHJlYWN0aXZlIGV4cHJlc3Npb25zIGFyZSBleGVjdXRlZCBsYXppbHksIGFuZCB0aGVpciB2YWx1ZXMgYXJlIGNhY2hlZC4KCjEuIExhenk6IEV2YWx1YXRlZCBvbmx5IHdoZW4gaXQgaXMgY2FsbGVkLCB0eXBpY2FsbHkgYnkgYSByZWFjdGl2ZSBlbmRwb2ludC4KMi4gQ2FjaGVkOiBFdmFsdWF0ZWQgb25seSB3aGVuIHRoZSB2YWx1ZSBvZiBvbmUgb2YgaXRzIHVuZGVybHlpbmcgZGVwZW5kZW5jaWVzIGNoYW5nZXMuCgojIyBPYnNlcnZlcnMgdnMuIHJlYWN0aXZlcwoKIyMjIEFkZCBhbm90aGVyIHJlYWN0aXZlIGV4cHJlc3Npb24KCkEgcmVhY3RpdmUgZXhwcmVzc2lvbiBjYW4gY2FsbCBvdGhlciByZWFjdGl2ZSBleHByZXNzaW9ucy4gVGhpcyBhbGxvd3MgeW91IHRvIG1vZHVsYXJpemUgY29tcHV0YXRpb25zIGFuZCBlbnN1cmUgdGhhdCB0aGV5IGFyZSBOT1QgZXhlY3V0ZWQgcmVwZWF0ZWRseS4gTWFzdGVyaW5nIHRoZSB1c2Ugb2YgcmVhY3RpdmUgZXhwcmVzc2lvbnMgaXMga2V5IHRvIGJ1aWxkaW5nIHBlcmZvcm1hbnQgU2hpbnkgYXBwbGljYXRpb25zLgoKSW4gdGhpcyBleGVyY2lzZSwgeW91IHdpbGwgdXNlIGEgcmVhY3RpdmUgZXhwcmVzc2lvbiB0byBjYWxjdWxhdGUgdGhlIGhlYWx0aCBzdGF0dXMgYmFzZWQgb24gdGhlIEJNSS4KYGBge3J9CnNlcnZlciA8LSBmdW5jdGlvbihpbnB1dCwgb3V0cHV0LCBzZXNzaW9uKSB7CiAgcnZhbF9ibWkgPC0gcmVhY3RpdmUoewogICAgaW5wdXQkd2VpZ2h0LyhpbnB1dCRoZWlnaHReMikKICB9KQogICMgQ09ERSBCRUxPVzogQWRkIGEgcmVhY3RpdmUgZXhwcmVzc2lvbiBydmFsX2JtaV9zdGF0dXMgdG8gCiAgIyByZXR1cm4gaGVhbHRoIHN0YXR1cyBhcyB1bmRlcndlaWdodCBldGMuIGJhc2VkIG9uIGlucHV0cwogIHJ2YWxfYm1pX3N0YXR1cyA8LSByZWFjdGl2ZSh7CiAgICBjdXQocnZhbF9ibWkoKSwgCiAgICAgIGJyZWFrcyA9IGMoMCwgMTguNSwgMjQuOSwgMjkuOSwgNDApLAogICAgICBsYWJlbHMgPSBjKCd1bmRlcndlaWdodCcsICdoZWFsdGh5JywgJ292ZXJ3ZWlnaHQnLCAnb2Jlc2UnKQogICAgKQogIH0pCiAgCiAgCiAgCiAgb3V0cHV0JGJtaSA8LSByZW5kZXJUZXh0KHsKICAgIGJtaSA8LSBydmFsX2JtaSgpCiAgICBwYXN0ZSgiWW91ciBCTUkgaXMiLCByb3VuZChibWksIDEpKQogIH0pCiAgb3V0cHV0JGJtaV9zdGF0dXMgPC0gcmVuZGVyVGV4dCh7CiAgICAjIE1PRElGWSBDT0RFIEJFTE9XOiBSZXBsYWNlIHJpZ2h0LWhhbmQtc2lkZSB3aXRoIAogICAgIyByZWFjdGl2ZSBleHByZXNzaW9uIHJ2YWxfYm1pX3N0YXR1cwogICAgYm1pX3N0YXR1cyA8LSBydmFsX2JtaV9zdGF0dXMoKQogICAgcGFzdGUoIllvdSBhcmUiLCBibWlfc3RhdHVzKQogIH0pCn0KdWkgPC0gZmx1aWRQYWdlKAogIHRpdGxlUGFuZWwoJ0JNSSBDYWxjdWxhdG9yJyksCiAgc2lkZWJhckxheW91dCgKICAgIHNpZGViYXJQYW5lbCgKICAgICAgbnVtZXJpY0lucHV0KCdoZWlnaHQnLCAnRW50ZXIgeW91ciBoZWlnaHQgaW4gbWV0ZXJzJywgMS41LCAxLCAyKSwKICAgICAgbnVtZXJpY0lucHV0KCd3ZWlnaHQnLCAnRW50ZXIgeW91ciB3ZWlnaHQgaW4gS2lsb2dyYW1zJywgNjAsIDQ1LCAxMjApCiAgICApLAogICAgbWFpblBhbmVsKAogICAgICB0ZXh0T3V0cHV0KCJibWkiKSwKICAgICAgdGV4dE91dHB1dCgiYm1pX3N0YXR1cyIpCiAgICApCiAgKQopCgpzaGlueUFwcCh1aSA9IHVpLCBzZXJ2ZXIgPSBzZXJ2ZXIpCmBgYApNb2R1bGFyaXR5IGlzIHJlYWxseSBpbXBvcnRhbnQgd2hpbGUgYnVpbGRpbmcgY29tcGxleCwgcGVyZm9ybWFudCBTaGlueSBhcHBzLiBSZWFjdGl2ZSBleHByZXNzaW9ucyBlbmFibGUgeW91IHRvIGFjaGlldmUgdGhpcyBtb2R1bGFyaXR5LgoKIyMjIEFkZCBhbiBvYnNlcnZlciB0byBkaXNwbGF5IG5vdGlmaWNhdGlvbnMKClJlY2FsbCB0aGF0IGFuIG9ic2VydmVyIGlzIHVzZWQgZm9yIHNpZGUgZWZmZWN0cywgbGlrZSBkaXNwbGF5aW5nIGEgcGxvdCwgdGFibGUsIG9yIHRleHQgaW4gdGhlIGJyb3dzZXIuIEJ5IGRlZmF1bHQgYW4gb2JzZXJ2ZXIgdHJpZ2dlcnMgYW4gYWN0aW9uLCB3aGVuZXZlciBvbmUgb2YgaXRzIHVuZGVybHlpbmcgZGVwZW5kZW5jaWVzIGNoYW5nZS4KCkluIHRoaXMgZXhlcmNpc2UsIHlvdSB3aWxsIHVzZSBhbiBvYnNlcnZlciB0byBkaXNwbGF5IGEgbm90aWZpY2F0aW9uIGluIHRoZSBicm93c2VyLCB1c2luZyBvYnNlcnZlKCkgYW5kIHNob3dOb3RpZmljYXRpb24oKS4gQXMgd2UgYXJlIHRyaWdnZXJpbmcgYW4gYWN0aW9uIHVzaW5nIGFuIG9ic2VydmVyLCB3ZSBkbyBOT1QgbmVlZCB0byB1c2UgYSByZW5kZXIqKiooKSBmdW5jdGlvbiBvciBhc3NpZ24gdGhlIHJlc3VsdHMgdG8gYW4gb3V0cHV0LgoKYGBge3J9CnVpIDwtIGZsdWlkUGFnZSgKICB0ZXh0SW5wdXQoJ25hbWUnLCAnRW50ZXIgeW91ciBuYW1lJykKKQoKc2VydmVyIDwtIGZ1bmN0aW9uKGlucHV0LCBvdXRwdXQsIHNlc3Npb24pIHsKICAjIENPREUgQkVMT1c6IEFkZCBhbiBvYnNlcnZlciB0byBkaXNwbGF5IGEgbm90aWZpY2F0aW9uCiAgIyAnWW91IGhhdmUgZW50ZXJlZCB0aGUgbmFtZSB4eHh4JyB3aGVyZSB4eHh4IGlzIHRoZSBuYW1lCiAgb2JzZXJ2ZSh7CiAgICBzaG93Tm90aWZpY2F0aW9uKAogICAgICBwYXN0ZSgnWW91IGhhdmUgZW50ZXJlZCB0aGUgbmFtZScsIGlucHV0JG5hbWUpCiAgICApCiAgfSkKfQoKc2hpbnlBcHAodWkgPSB1aSwgc2VydmVyID0gc2VydmVyKQpgYGAKCiMjIFN0b3AgLSBkZWxheSAtIHRyaWdnZXIKCiMjIyBTdG9wIHJlYWN0aW9ucyB3aXRoIGlzb2xhdGUoKQoKT3JkaW5hcmlseSwgdGhlIHNpbXBsZSBhY3Qgb2YgcmVhZGluZyBhIHJlYWN0aXZlIHZhbHVlIGlzIHN1ZmZpY2llbnQgdG8gc2V0IHVwIGEgcmVsYXRpb25zaGlwLCB3aGVyZSBhIGNoYW5nZSB0byB0aGUgcmVhY3RpdmUgdmFsdWUgd2lsbCBjYXVzZSB0aGUgY2FsbGluZyBleHByZXNzaW9uIHRvIHJlLWV4ZWN1dGUuIFRoZSBpc29sYXRlKCkgZnVuY3Rpb24gYWxsb3dzIGFuIGV4cHJlc3Npb24gdG8gcmVhZCBhIHJlYWN0aXZlIHZhbHVlIHdpdGhvdXQgdHJpZ2dlcmluZyByZS1leGVjdXRpb24gd2hlbiBpdHMgdmFsdWUgY2hhbmdlcy4KCkluIHRoaXMgZXhlcmNpc2UsIHlvdSB3aWxsIHVzZSB0aGUgaXNvbGF0ZSgpIGZ1bmN0aW9uIHRvIHN0b3AgcmVhY3RpdmUgZmxvdy4KCmBgYHtyfQpzZXJ2ZXIgPC0gZnVuY3Rpb24oaW5wdXQsIG91dHB1dCwgc2Vzc2lvbikgewogIHJ2YWxfYm1pIDwtIHJlYWN0aXZlKHsKICAgIGlucHV0JHdlaWdodC8oaW5wdXQkaGVpZ2h0XjIpCiAgfSkKICBvdXRwdXQkYm1pIDwtIHJlbmRlclRleHQoewogICAgYm1pIDwtIHJ2YWxfYm1pKCkKICAgICMgTU9ESUZZIENPREUgQkVMT1c6IAogICAgIyBVc2UgaXNvbGF0ZSB0byBzdG9wIG91dHB1dCBmcm9tIHVwZGF0aW5nIHdoZW4gbmFtZSBjaGFuZ2VzLgogICAgcGFzdGUoIkhpIiwgaXNvbGF0ZSh7aW5wdXQkbmFtZX0pLCAiLiBZb3VyIEJNSSBpcyIsIHJvdW5kKGJtaSwgMSkpCiAgfSkKfQp1aSA8LSBmbHVpZFBhZ2UoCiAgdGl0bGVQYW5lbCgnQk1JIENhbGN1bGF0b3InKSwKICBzaWRlYmFyTGF5b3V0KAogICAgc2lkZWJhclBhbmVsKAogICAgICB0ZXh0SW5wdXQoJ25hbWUnLCAnRW50ZXIgeW91ciBuYW1lJyksCiAgICAgIG51bWVyaWNJbnB1dCgnaGVpZ2h0JywgJ0VudGVyIHlvdXIgaGVpZ2h0IChpbiBtKScsIDEuNSwgMSwgMiwgc3RlcCA9IDAuMSksCiAgICAgIG51bWVyaWNJbnB1dCgnd2VpZ2h0JywgJ0VudGVyIHlvdXIgd2VpZ2h0IChpbiBLZyknLCA2MCwgNDUsIDEyMCkKICAgICksCiAgICBtYWluUGFuZWwoCiAgICAgIHRleHRPdXRwdXQoImJtaSIpCiAgICApCiAgKQopCgpzaGlueUFwcCh1aSA9IHVpLCBzZXJ2ZXIgPSBzZXJ2ZXIpCmBgYApUaGVyZSBhcmUgc2l0dWF0aW9ucyB3aGVyZSB5b3UgZG9uJ3Qgd2FudCBTaGlueSdzIHJlYWN0aXZlIGZyYW1ld29yayB0byBhdXRvbWF0aWNhbGx5IHRyaWdnZXIgYW4gdXBkYXRlLiBUaGUgaXNvbGF0ZSgpIGZ1bmN0aW9uIHdpbGwgYmUgdmVyeSBoYW5keSBpbiB0aGVzZSBzY2VuYXJpb3MuCgojIyMgRGVsYXkgcmVhY3Rpb25zIHdpdGggZXZlbnRSZWFjdGl2ZSgpCgpTaGlueSdzIHJlYWN0aXZlIHByb2dyYW1taW5nIGZyYW1ld29yayBpcyBkZXNpZ25lZCBzdWNoIHRoYXQgYW55IGNoYW5nZXMgdG8gaW5wdXRzIGF1dG9tYXRpY2FsbHkgdXBkYXRlcyB0aGUgb3V0cHV0cyB0aGF0IGRlcGVuZCBvbiBpdC4gSW4gc29tZSBzaXR1YXRpb25zLCB3ZSBtaWdodCB3YW50IG1vcmUgZXhwbGljaXRseSBjb250cm9sIHRoZSB0cmlnZ2VyIHRoYXQgY2F1c2VzIHRoZSB1cGRhdGUuCgpUaGUgZnVuY3Rpb24gZXZlbnRSZWFjdGl2ZSgpIGlzIHVzZWQgdG8gY29tcHV0ZSBhIHJlYWN0aXZlIHZhbHVlIHRoYXQgb25seSB1cGRhdGVzIGluIHJlc3BvbnNlIHRvIGEgc3BlY2lmaWMgZXZlbnQuCgoKICAgIHJ2YWxfeCA8LSBldmVudFJlYWN0aXZlKGlucHV0JGV2ZW50LCB7CiAgICAgIyBjYWxjdWxhdGlvbnMKICAgIH0pCgpgYGB7cn0Kc2VydmVyIDwtIGZ1bmN0aW9uKGlucHV0LCBvdXRwdXQsIHNlc3Npb24pIHsKICAjIE1PRElGWSBDT0RFIEJFTE9XOiBVc2UgZXZlbnRSZWFjdGl2ZSB0byBkZWxheSB0aGUgZXhlY3V0aW9uIG9mIHRoZQogICMgY2FsY3VsYXRpb24gdW50aWwgdGhlIHVzZXIgY2xpY2tzIG9uIHRoZSBzaG93X2JtaSBidXR0b24gKFNob3cgQk1JKQogIHJ2YWxfYm1pIDwtIGV2ZW50UmVhY3RpdmUoaW5wdXQkc2hvd19ibWksewogICAgaW5wdXQkd2VpZ2h0LyhpbnB1dCRoZWlnaHReMikKICB9KQogIG91dHB1dCRibWkgPC0gcmVuZGVyVGV4dCh7CiAgICBibWkgPC0gcnZhbF9ibWkoKQogICAgcGFzdGUoIkhpIiwgaW5wdXQkbmFtZSwgIi4gWW91ciBCTUkgaXMiLCByb3VuZChibWksIDEpKQogIH0pCn0KdWkgPC0gZmx1aWRQYWdlKAogIHRpdGxlUGFuZWwoJ0JNSSBDYWxjdWxhdG9yJyksCiAgc2lkZWJhckxheW91dCgKICAgIHNpZGViYXJQYW5lbCgKICAgICAgdGV4dElucHV0KCduYW1lJywgJ0VudGVyIHlvdXIgbmFtZScpLAogICAgICBudW1lcmljSW5wdXQoJ2hlaWdodCcsICdFbnRlciBoZWlnaHQgKGluIG0pJywgMS41LCAxLCAyLCBzdGVwID0gMC4xKSwKICAgICAgbnVtZXJpY0lucHV0KCd3ZWlnaHQnLCAnRW50ZXIgd2VpZ2h0IChpbiBLZyknLCA2MCwgNDUsIDEyMCksCiAgICAgIGFjdGlvbkJ1dHRvbigic2hvd19ibWkiLCAiU2hvdyBCTUkiKQogICAgKSwKICAgIG1haW5QYW5lbCgKICAgICAgdGV4dE91dHB1dCgiYm1pIikKICAgICkKICApCikKCgpzaGlueUFwcCh1aSA9IHVpLCBzZXJ2ZXIgPSBzZXJ2ZXIpCmBgYApUaGVyZSBhcmUgc2l0dWF0aW9ucyB3aGVyZSB5b3Ugd2FudCB0byBkZWxheSB0aGUgY29tcHV0YXRpb24gb2YgYSByZWFjdGl2ZSBleHByZXNzaW9uIHVudGlsIGEgc3BlY2lmaWMgZXZlbnQgaXMgdHJpZ2dlcmVkLiBUaGUgZXZlbnRSZWFjdGl2ZSgpIGZ1bmN0aW9uIHdpbGwgcHJvdmUgdmVyeSBoYW5keSBpbiB0aGVzZSBzY2VuYXJpb3MuCgojIyMgVHJpZ2dlciByZWFjdGlvbnMgd2l0aCBvYnNlcnZlRXZlbnQoKQoKVGhlcmUgYXJlIHRpbWVzIHdoZW4geW91IHdhbnQgdG8gcGVyZm9ybSBhbiBhY3Rpb24gaW4gcmVzcG9uc2UgdG8gYW4gZXZlbnQuIEZvciBleGFtcGxlLCB5b3UgbWlnaHQgd2FudCB0byBsZXQgdGhlIGFwcCB1c2VyIGRvd25sb2FkIGEgdGFibGUgYXMgYSBDU1YgZmlsZSwgd2hlbiB0aGV5IGNsaWNrIG9uIGEgIkRvd25sb2FkIiBidXR0b24uIE9yLCB5b3UgbWlnaHQgd2FudCB0byBkaXNwbGF5IGEgbm90aWZpY2F0aW9uIG9yIG1vZGFsIGRpYWxvZywgaW4gcmVzcG9uc2UgdG8gYSBjbGljay4KClRoZSBvYnNlcnZlRXZlbnQoKSBmdW5jdGlvbiBhbGxvd3MgeW91IHRvIGFjaGlldmUgdGhpcy4gSXQgYWNjZXB0cyB0d28gYXJndW1lbnRzOgoKMS4gVGhlIGV2ZW50IHlvdSB3YW50IHRvIHJlc3BvbmQgdG8uCjIuIFRoZSBmdW5jdGlvbiB0aGF0IHNob3VsZCBiZSBjYWxsZWQgd2hlbmV2ZXIgdGhlIGV2ZW50IG9jY3Vycy4KCkluIHRoaXMgZXhlcmNpc2UsIHlvdSB3aWxsIHVzZSBvYnNlcnZlRXZlbnQoKSB0byBkaXNwbGF5IGEgbW9kYWwgZGlhbG9nIHdpdGggaGVscCB0ZXh0LCB3aGVuIHRoZSB1c2VyIGNsaWNrcyBvbiBhIGJ1dHRvbiBsYWJlbGxlZCAiSGVscCIuIFRoZSBoZWxwIHRleHQgaGFzIGFscmVhZHkgYmVlbiBhc3NpZ25lZCB0byB0aGUgdmFyaWFibGUgYm1pX2hlbHBfdGV4dC4KYGBge3J9CmJtaV9oZWxwX3RleHQgPC0gIkJvZHkgTWFzcyBJbmRleCBpcyBhIHNpbXBsZSBjYWxjdWxhdGlvbiB1c2luZyBhIHBlcnNvbidzIGhlaWdodCBhbmQgd2VpZ2h0LiBUaGUgZm9ybXVsYSBpcyBCTUkgPSBrZy9tMiB3aGVyZSBrZyBpcyBhIHBlcnNvbidzIHdlaWdodCBpbiBraWxvZ3JhbXMgYW5kIG0yIGlzIHRoZWlyIGhlaWdodCBpbiBtZXRyZXMgc3F1YXJlZC4gQSBCTUkgb2YgMjUuMCBvciBtb3JlIGlzIG92ZXJ3ZWlnaHQsIHdoaWxlIHRoZSBoZWFsdGh5IHJhbmdlIGlzIDE4LjUgdG8gMjQuOS4iCmBgYAoKYGBge3J9CnNlcnZlciA8LSBmdW5jdGlvbihpbnB1dCwgb3V0cHV0LCBzZXNzaW9uKSB7CiAgIyBNT0RJRlkgQ09ERSBCRUxPVzogV3JhcCBpbiBvYnNlcnZlRXZlbnQoKSBzbyB0aGUgaGVscCB0ZXh0IAogICMgaXMgZGlzcGxheWVkIHdoZW4gYSB1c2VyIGNsaWNrcyBvbiB0aGUgSGVscCBidXR0b24uCiAgCiAgICAgIyBEaXNwbGF5IGEgbW9kYWwgZGlhbG9nIHdpdGggYm1pX2hlbHBfdGV4dAogICAgICMgTU9ESUZZIENPREUgQkVMT1c6IFVuY29tbWVudCBjb2RlCiAgICAgb2JzZXJ2ZUV2ZW50KGlucHV0JHNob3dfaGVscCx7CiAgICAgc2hvd01vZGFsKG1vZGFsRGlhbG9nKGJtaV9oZWxwX3RleHQpKQogICAgIH0pCiAgcnZfYm1pIDwtIGV2ZW50UmVhY3RpdmUoaW5wdXQkc2hvd19ibWksIHsKICAgIGlucHV0JHdlaWdodC8oaW5wdXQkaGVpZ2h0XjIpCiAgfSkKICBvdXRwdXQkYm1pIDwtIHJlbmRlclRleHQoewogICAgYm1pIDwtIHJ2X2JtaSgpCiAgICBwYXN0ZSgiSGkiLCBpbnB1dCRuYW1lLCAiLiBZb3VyIEJNSSBpcyIsIHJvdW5kKGJtaSwgMSkpCiAgfSkKfQoKdWkgPC0gZmx1aWRQYWdlKAogIHRpdGxlUGFuZWwoJ0JNSSBDYWxjdWxhdG9yJyksCiAgc2lkZWJhckxheW91dCgKICAgIHNpZGViYXJQYW5lbCgKICAgICAgdGV4dElucHV0KCduYW1lJywgJ0VudGVyIHlvdXIgbmFtZScpLAogICAgICBudW1lcmljSW5wdXQoJ2hlaWdodCcsICdFbnRlciB5b3VyIGhlaWdodCBpbiBtZXRlcnMnLCAxLjUsIDEsIDIpLAogICAgICBudW1lcmljSW5wdXQoJ3dlaWdodCcsICdFbnRlciB5b3VyIHdlaWdodCBpbiBLaWxvZ3JhbXMnLCA2MCwgNDUsIDEyMCksCiAgICAgIGFjdGlvbkJ1dHRvbigic2hvd19ibWkiLCAiU2hvdyBCTUkiKSwKICAgICAgIyBDT0RFIEJFTE9XOiBBZGQgYW4gYWN0aW9uIGJ1dHRvbiBuYW1lZCAic2hvd19oZWxwIgogICAgICBhY3Rpb25CdXR0b24oInNob3dfaGVscCIsICJIZWxwIikKICAgICksCiAgICBtYWluUGFuZWwoCiAgICAgIHRleHRPdXRwdXQoImJtaSIpCiAgICApCiAgKQopCgpzaGlueUFwcCh1aSA9IHVpLCBzZXJ2ZXIgPSBzZXJ2ZXIpCmBgYApUaGUgb2JzZXJ2ZUV2ZW50KCkgZnVuY3Rpb24gaXMgdmVyeSB1c2VmdWwgd2hlbiB5b3Ugd2FudCB0byBleHBsaWNpdGx5IHRyaWdnZXIgYW4gYWN0aW9uIGluIHJlc3BvbnNlIHRvIGFuIGV2ZW50IGluIHRoZSB1c2VyLWludGVyZmFjZS4KCiMjIEFwcGx5aW5nIHJlYWN0aXZpdHkgY29uY2VwdHMKCiMjIyBDb252ZXJ0IGhlaWdodCBmcm9tIGluY2hlcyB0byBjZW50aW1ldGVycwoKRWFybGllciBpbiB0aGUgY2hhcHRlciwgd2UgcHJhY3RpY2VkIHN0b3BwaW5nLCBkZWxheWluZywgYW5kIHRyaWdnZXJpbmcgYXBwcy4gVGhpcyBpcyBhIHZlcnkgY29tbW9uIHBhdHRlcm4gb2YgcHJvZ3JhbW1pbmcgaW4gU2hpbnkgdGhhdCBlbmFibGVzIHlvdXIgYXBwcyB0byBiZSBvcHRpbWl6ZWQgZm9yIHNwZWVkIChhbmQgb25seSByZS1ydW4gd2hlbiBzb21ldGhpbmcgaXMgdXBkYXRlZCBhbmQgeW91ciB1c2VyIHdvdWxkIGxpa2UgdG8gcmUtcnVuIHRoZSBhcHAuKQoKSW4gdGhpcyBleGVyY2lzZSwgeW91J2xsIHByYWN0aWNlIHNvbWUgb2YgdGhvc2UgY29uY2VwdHMgYWdhaW4sIGp1c3QgdG8gbWFrZSBzdXJlIHlvdSB0cnVseSB1bmRlcnN0YW5kIHRoZW0uIEluc3RlYWQgb2YgY2FsY3VsYXRpbmcgQk1JLCB0aGlzIGFwcCBjb252ZXJ0cyBoZWlnaHQgaW4gaW5jaGVzIHRvIGNlbnRpbWV0ZXJzLgpgYGB7cn0Kc2VydmVyIDwtIGZ1bmN0aW9uKGlucHV0LCBvdXRwdXQsIHNlc3Npb24pIHsKICAjIE1PRElGWSBDT0RFIEJFTE9XOiBEZWxheSB0aGUgaGVpZ2h0IGNhbGN1bGF0aW9uIHVudGlsCiAgIyB0aGUgc2hvdyBidXR0b24gaXMgcHJlc3NlZAogIHJ2YWxfaGVpZ2h0X2NtIDwtIGV2ZW50UmVhY3RpdmUoaW5wdXQkc2hvd19oZWlnaHRfY20sewogICAgaW5wdXQkaGVpZ2h0ICogMi41NAogIH0pCiAgCiAgb3V0cHV0JGhlaWdodF9jbSA8LSByZW5kZXJUZXh0KHsKICAgIGhlaWdodF9jbSA8LSBydmFsX2hlaWdodF9jbSgpCiAgICAJcGFzdGUoIllvdXIgaGVpZ2h0IGluIGNlbnRpbWV0ZXJzIGlzIiwgaGVpZ2h0X2NtLCAiY20iKQogICAgfSkKfQoKdWkgPC0gZmx1aWRQYWdlKAogIHRpdGxlUGFuZWwoIkluY2hlcyB0byBDZW50aW1ldGVycyBDb252ZXJzaW9uIiksCiAgc2lkZWJhckxheW91dCgKICAgIHNpZGViYXJQYW5lbCgKICAgICAgbnVtZXJpY0lucHV0KCJoZWlnaHQiLCAiSGVpZ2h0IChpbikiLCA2MCksCiAgICAgIGFjdGlvbkJ1dHRvbigic2hvd19oZWlnaHRfY20iLCAiU2hvdyBoZWlnaHQgaW4gY20iKQogICAgKSwKICAgIG1haW5QYW5lbCgKICAgICAgdGV4dE91dHB1dCgiaGVpZ2h0X2NtIikKICAgICkKICApCikKCnNoaW55QXBwKHVpID0gdWksIHNlcnZlciA9IHNlcnZlcikKYGBgClVzaW5nIGV2ZW50UmVhY3RpdmUoKSwgeW91IHJlY2FsY3VsYXRlIHRoZSBoZWlnaHQgb25seSB3aGVuIHRoZSAnU2hvdyBoZWlnaHQgaW4gY20nIGJ1dHRvbiBpcyBwcmVzc2VkLCB3aGljaCBtYWtlcyBzZW5zZS4gUmVtZW1iZXIgdGhhdCB5b3VyIGFwcCBkb2Vzbid0IG5lZWQgdG8gZG8gZXZlcnl0aGluZyBhbGwgdGhlIHRpbWUhCgojIEJ1aWxkIFNoaW55IEFwcHMKCiMjIEJ1aWxkIGFuIEFsaWVuIFNpZ2h0aW5ncyBEYXNoYm9hcmQKCmBgYHtyfQpsaWJyYXJ5KHJlYWRyKQp1c2FfdWZvX3NpZ2h0aW5ncyA8LSByZWFkX2NzdigidXNhX3Vmb19zaWdodGluZ3MuY3N2IikKYGBgCiMjIyBBbGllbiBzaWdodGluZ3M6IGFkZCBpbnB1dHMKClRoZSBOYXRpb25hbCBVRk8gUmVwb3J0aW5nIENlbnRlciAoTlVGT1JDKSBoYXMgY29sbGVjdGVkIHNpZ2h0aW5ncyBkYXRhIHRocm91Z2hvdXQgdGhlIGxhc3QgY2VudHVyeS4gVGhpcyBhcHAgaXMgZ29pbmcgdG8gYWxsb3cgdXNlcnMgdG8gc2VsZWN0IGEgVS5TLiBzdGF0ZSBhbmQgYSB0aW1lIHBlcmlvZCBpbiB3aGljaCB0aGUgc2lnaHRpbmdzIG9jY3VycmVkLgpgYGB7cn0KdWkgPC0gZmx1aWRQYWdlKAogICMgQ09ERSBCRUxPVzogQWRkIGEgdGl0bGUKICAgdGl0bGVQYW5lbCgiVUZPIFNpZ2h0aW5ncyIpLAogIHNpZGViYXJMYXlvdXQoCiAgICBzaWRlYmFyUGFuZWwoCiAgICAgICMgQ09ERSBCRUxPVzogT25lIGlucHV0IHRvIHNlbGVjdCBhIFUuUy4gc3RhdGUKICAgICAgIyBBbmQgb25lIGlucHV0IHRvIHNlbGVjdCBhIHJhbmdlIG9mIGRhdGVzCiAgICAgIHNlbGVjdElucHV0KCJzdGF0ZSIsICJDaG9vc2UgYSBVLlMuIHN0YXRlOiIsIGNob2ljZXMgPSB1bmlxdWUodXNhX3Vmb19zaWdodGluZ3Mkc3RhdGUpKSwKICAgICAgCiAgICAgIGRhdGVSYW5nZUlucHV0KCJkYXRlIiwgIkNob29zZSBhIGRhdGUgcmFuZ2U6Iiwgc3RhcnQgPSAiMTkyMC0wMS0wMSIsIGVuZCA9ICIyMDIwLTAxLTAxIikKICAgICAgCiAgICAgIAoKICAgIAogICAgCiAgICApLAogIAltYWluUGFuZWwoKQogICkKKQoKc2VydmVyIDwtIGZ1bmN0aW9uKGlucHV0LCBvdXRwdXQpIHsKCn0KCnNoaW55QXBwKHVpLCBzZXJ2ZXIpCmBgYApUaGVzZSB0d28gaW5wdXRzIGFyZSBhIGdyZWF0IHN0YXJ0IHRvIHRoZSBhcHAsIGJ1dCBpZiB3ZSBkb24ndCBidWlsZCBhbnl0aGluZyBpbiB0aGUgc2VydmVyLCB0aGV5IGRvbid0IGRvIGFueXRoaW5nLiBMZXQncyBnZXQgb3VyIG91dHB1dHMgYnVpbHQuCgojIyMgQWxpZW4gc2lnaHRpbmdzOiBhZGQgb3V0cHV0cwoKTm93IHRoYXQgdGhlIGRhc2hib2FyZCBoYXMgaW5wdXRzLCB5b3Ugc2hvdWxkIGJ1aWxkIHlvdXIgb3V0cHV0cyB0byBhY3R1YWxseSBzZWUgaW5mb3JtYXRpb24gYWJvdXQgdGhlIHJlcG9ydGVkIFVGTyBzaWdodGluZ3MuCgpSZWNhbGwgdGhlcmUgd2lsbCBiZSB0d28sIGEgcGxvdCBhbmQgYSB0YWJsZS4gVGhlIHBsb3Qgc2hvdWxkIHNob3cgdGhlIG51bWJlciBzaWdodGVkLCBieSBzaGFwZSwgZm9yIHRoZSBzZWxlY3RlZCBzdGF0ZSBhbmQgdGltZSBwZXJpb2QuIFRoZSB0YWJsZSBzaG91bGQgc2hvdywgZm9yIHRoZSBzZWxlY3RlZCBzdGF0ZSBhbmQgdGltZSBwZXJpb2QsIHRoZSBudW1iZXIgc2lnaHRlZCwgcGx1cyB0aGUgYXZlcmFnZSwgbWVkaWFuLCBtaW5pbXVtLCBhbmQgbWF4aW11bSBkdXJhdGlvbiAoZHVyYXRpb25fc2VjKSBvZiB0aGUgc2lnaHRpbmdzLiBUaGlzIHdpbGwgcmVxdWlyZSB1c2luZyBkcGx5ciwgb3IgYSBtZXRob2Qgb2YgeW91ciBjaG9vc2luZywgdG8gbWFuaXB1bGF0ZSB0aGUgdXNhX3Vmb19zaWdodGluZ3MgZGF0YS4KYGBge3J9CmxpYnJhcnkoZHBseXIpCmxpYnJhcnkoZ2dwbG90MikKYGBgCmBgYHtyfQpzZXJ2ZXIgPC0gZnVuY3Rpb24oaW5wdXQsIG91dHB1dCkgewogICMgQ09ERSBCRUxPVzogQ3JlYXRlIGEgcGxvdCBvdXRwdXQgbmFtZSAnc2hhcGVzJywgb2Ygc2lnaHRpbmdzIGJ5IHNoYXBlLAogICMgRm9yIHRoZSBzZWxlY3RlZCBpbnB1dHMKICBvdXRwdXQkc2hhcGVzIDwtIHJlbmRlclBsb3QoewogICAgdXNhX3Vmb19zaWdodGluZ3MgJT4lCiAgICAgIGZpbHRlcihzdGF0ZSA9PSBpbnB1dCRzdGF0ZSwKICAgICAgICAgICAgIGRhdGVfc2lnaHRlZCA+PSBpbnB1dCRkYXRlc1sxXSwKICAgICAgICAgICAgIGRhdGVfc2lnaHRlZCA8PSBpbnB1dCRkYXRlc1syXSkgJT4lCiAgICAgIGdncGxvdChhZXMoc2hhcGUpKSArCiAgICAgIGdlb21fYmFyKCkgKwogICAgICBsYWJzKHggPSAiU2hhcGUiLCB5ID0gIiMgU2lnaHRlZCIpCiAgfSkKICAjIENPREUgQkVMT1c6IENyZWF0ZSBhIHRhYmxlIG91dHB1dCBuYW1lZCAnZHVyYXRpb25fdGFibGUnLCBieSBzaGFwZSwgCiAgIyBvZiAjIHNpZ2h0ZWQsIHBsdXMgbWVhbiwgbWVkaWFuLCBtYXgsIGFuZCBtaW4gZHVyYXRpb24gb2Ygc2lnaHRpbmdzCiAgIyBmb3IgdGhlIHNlbGVjdGVkIGlucHV0cwogIG91dHB1dCRkdXJhdGlvbl90YWJsZSA8LSByZW5kZXJUYWJsZSh7CiAgICB1c2FfdWZvX3NpZ2h0aW5ncyAlPiUKICAgICAgZmlsdGVyKAogICAgICAgIHN0YXRlID09IGlucHV0JHN0YXRlLAogICAgICAgIGRhdGVfc2lnaHRlZCA+PSBpbnB1dCRkYXRlc1sxXSwKICAgICAgICBkYXRlX3NpZ2h0ZWQgPD0gaW5wdXQkZGF0ZXNbMl0KICAgICAgKSAlPiUKICAgICAgZ3JvdXBfYnkoc2hhcGUpICU+JQogICAgICBzdW1tYXJpemUoCiAgICAgICAgbmJfc2lnaHRlZCA9IG4oKSwKICAgICAgICBhdmdfZHVyYXRpb24gPSBtZWFuKGR1cmF0aW9uX3NlYyksCiAgICAgICAgbWVkaWFuX2R1cmF0aW9uID0gbWVkaWFuKGR1cmF0aW9uX3NlYyksCiAgICAgICAgbWluX2R1cmF0aW9uID0gbWluKGR1cmF0aW9uX3NlYyksCiAgICAgICAgbWF4X2R1cmF0aW9uID0gbWF4KGR1cmF0aW9uX3NlYykKICAgICAgKQogIH0pCn0KCnVpIDwtIGZsdWlkUGFnZSgKICB0aXRsZVBhbmVsKCJVRk8gU2lnaHRpbmdzIiksCiAgc2lkZWJhckxheW91dCgKICAgIHNpZGViYXJQYW5lbCgKICAgICAgc2VsZWN0SW5wdXQoInN0YXRlIiwgIkNob29zZSBhIFUuUy4gc3RhdGU6IiwgY2hvaWNlcyA9IHVuaXF1ZSh1c2FfdWZvX3NpZ2h0aW5ncyRzdGF0ZSkpLAogICAgICBkYXRlUmFuZ2VJbnB1dCgiZGF0ZXMiLCAiQ2hvb3NlIGEgZGF0ZSByYW5nZToiLAogICAgICAgICAgICAgICAgICAgICBzdGFydCA9ICIxOTIwLTAxLTAxIiwKICAgICAgICAgICAgICAgICAgICAgZW5kID0gIjE5NTAtMDEtMDEiKQogICAgKSwKICAgIG1haW5QYW5lbCgKICAgICAgIyBBZGQgcGxvdCBvdXRwdXQgbmFtZWQgJ3NoYXBlcycKICAgICAgcGxvdE91dHB1dCgic2hhcGVzIiksCiAgICAgICMgQWRkIHRhYmxlIG91dHB1dCBuYW1lZCAnZHVyYXRpb25fdGFibGUnCiAgICAgIHRhYmxlT3V0cHV0KCJkdXJhdGlvbl90YWJsZSIpCiAgICApCiAgKQopCgpzaGlueUFwcCh1aSwgc2VydmVyKQpgYGAKVGhpcyBhcHAgaXMgc29ydCBvZiBjbHV0dGVyZWQsIGdpdmVuIHRoYXQgdGhlIHBsb3QgYW5kIHRhYmxlIG91dHB1dCBhcmUganVzdCBzaXR0aW5nIG9uIHRvcCBvZiBvbmUgYW5vdGhlciwgYnV0IHlvdSd2ZSBzdGFydGVkIHRvIGdldCBzb21lIGNvbmNyZXRlIGluZm9ybWF0aW9uIGFib3V0IHRoZSBhbGllbnMgc2lnaHRlZC4gTGV0J3MgY2xlYW4gaXQgdXAgYnkgYWRkaW5nIHRoZSB0YWIgbGF5b3V0LgoKIyMjIEFsaWVuIHNpZ2h0aW5nczogdGFiIGxheW91dAoKQXMtaXMsIHRoZSBhcHAgaXMgc29ydCBvZiBidXN5IHdpdGggdGhlIGdyYXBoIG9uIHRvcCBvZiB0aGUgdGFibGUuIEdpdmVuIHRoYXQgdGhpcyBpcyBhIGRhc2hib2FyZCwgaXQgbWlnaHQgYmUgbmljZSB0byBpbnN0ZWFkIHNlcGFyYXRlIHRoZSB0d28gb3V0cHV0cy4KClRoZSBsYXN0IHN0ZXAgaW4gYnVpbGRpbmcgeW91ciBkYXNoYm9hcmQgaXMgdG8gdGFrZSB0aGUgcGxvdE91dHB1dCgpIGFuZCB0YWJsZU91dHB1dCgpIHlvdSd2ZSBidWlsdCBhbmQgYWRkIHRoZSB0YWIgbGF5b3V0LgoKYGBge3J9CnVpIDwtIGZsdWlkUGFnZSgKICB0aXRsZVBhbmVsKCJVRk8gU2lnaHRpbmdzIiksCiAgc2lkZWJhckxheW91dCgKICBzaWRlYmFyUGFuZWwoCiAgICBzZWxlY3RJbnB1dCgic3RhdGUiLCAiQ2hvb3NlIGEgVS5TLiBzdGF0ZToiLCBjaG9pY2VzID0gdW5pcXVlKHVzYV91Zm9fc2lnaHRpbmdzJHN0YXRlKSksCiAgICBkYXRlUmFuZ2VJbnB1dCgiZGF0ZXMiLCAiQ2hvb3NlIGEgZGF0ZSByYW5nZToiLAogICAgICBzdGFydCA9ICIxOTIwLTAxLTAxIiwKICAgICAgZW5kID0gIjE5NTAtMDEtMDEiCiAgICApCiAgKSwKICAjIE1PRElGWSBDT0RFIEJFTE9XOiBDcmVhdGUgYSB0YWIgbGF5b3V0IGZvciB0aGUgZGFzaGJvYXJkCiAgbWFpblBhbmVsKAogICAgdGFic2V0UGFuZWwoCiAgICAgIHRhYlBhbmVsKCJQbG90IiwKICAgICAgcGxvdE91dHB1dCgic2hhcGVzIikpLAogICAgICB0YWJQYW5lbCgiVGFibGUiLAogICAgICB0YWJsZU91dHB1dCgiZHVyYXRpb25fdGFibGUiKSkKICAgICAgKQogICAgKQogICkKKQoKc2VydmVyIDwtIGZ1bmN0aW9uKGlucHV0LCBvdXRwdXQpIHsKICBvdXRwdXQkc2hhcGVzIDwtIHJlbmRlclBsb3QoewogICAgdXNhX3Vmb19zaWdodGluZ3MgJT4lCiAgICAgIGZpbHRlcigKICAgICAgICBzdGF0ZSA9PSBpbnB1dCRzdGF0ZSwKICAgICAgICBkYXRlX3NpZ2h0ZWQgPj0gaW5wdXQkZGF0ZXNbMV0sCiAgICAgICAgZGF0ZV9zaWdodGVkIDw9IGlucHV0JGRhdGVzWzJdCiAgICAgICkgJT4lCiAgICAgIGdncGxvdChhZXMoc2hhcGUpKSArCiAgICAgIGdlb21fYmFyKCkgKwogICAgICBsYWJzKAogICAgICAgIHggPSAiU2hhcGUiLAogICAgICAgIHkgPSAiIyBTaWdodGVkIgogICAgICApCiAgfSkKCiAgb3V0cHV0JGR1cmF0aW9uX3RhYmxlIDwtIHJlbmRlclRhYmxlKHsKICAgIHVzYV91Zm9fc2lnaHRpbmdzICU+JQogICAgICBmaWx0ZXIoCiAgICAgICAgc3RhdGUgPT0gaW5wdXQkc3RhdGUsCiAgICAgICAgZGF0ZV9zaWdodGVkID49IGlucHV0JGRhdGVzWzFdLAogICAgICAgIGRhdGVfc2lnaHRlZCA8PSBpbnB1dCRkYXRlc1syXQogICAgICApICU+JQogICAgICBncm91cF9ieShzaGFwZSkgJT4lCiAgICAgIHN1bW1hcml6ZSgKICAgICAgICBuYl9zaWdodGVkID0gbigpLAogICAgICAgIGF2Z19kdXJhdGlvbl9taW4gPSBtZWFuKGR1cmF0aW9uX3NlYykgLyA2MCwKICAgICAgICBtZWRpYW5fZHVyYXRpb25fbWluID0gbWVkaWFuKGR1cmF0aW9uX3NlYykgLyA2MCwKICAgICAgICBtaW5fZHVyYXRpb25fbWluID0gbWluKGR1cmF0aW9uX3NlYykgLyA2MCwKICAgICAgICBtYXhfZHVyYXRpb25fbWluID0gbWF4KGR1cmF0aW9uX3NlYykgLyA2MAogICAgICApCiAgfSkKfQoKc2hpbnlBcHAodWksIHNlcnZlcikKYGBgCllvdSBjb3VsZCBhZGQgYSB0aGVtZSwgd3JpdGUgYSBjdXN0b20gQ1NTIHN0eWxlc2hlZXQgdG8gYWRkIHBpY3R1cmVzIG9mIGFsaWVucywgYW5kIHVzZSB0aGUgZGF0YSB0byBhZGQgZXZlbiBtb3JlIGluZm9ybWF0aW9uIGFib3V0IGFsaWVuIHNpZ2h0aW5ncyB0aGUgd29ybGQgb3Zlci4KCiMjIEV4cGxvcmluZyB0aGUgMjAxNCBNZW50YWwgSGVhbHRoIGluIFRlY2ggU3VydmV5CgojIyMgVGhlIHNoaW55V2lkZ2V0cyBnYWxsZXJ5CgpMZXQncyBsb29rIGF0IGFsbCB0aGUgZGlmZmVyZW50IGlucHV0IG9wdGlvbnMgdGhhdCBhcmUgYWxyZWFkeSBidWlsdCBmb3IgeW91IGJ5IGV4cGxvcmluZyB0aGUgc2hpbnlXaWRnZXRzIHBhY2thZ2UuIEl0IGNvbWVzIHdpdGggYSBuZWF0IGJ1aWx0LWluIGZ1bmN0aW9uLCBzaGlueVdpZGdldHNHYWxsZXJ5KCkgdGhhdCBvcGVucyBhIHByZS1idWlsdCBTaGlueSBhcHAgdGhhdCBhbGxvd3MgeW91IHRvIGV4cGxvcmUgdGhlc2UgcHJlLWJ1aWx0IGlucHV0cyBhbmQgZ2l2ZXMgeW91IHRoZSBjb2RlIGZvciBpbXBsZW1lbnRpbmcgdGhlbS4KCk5hdmlnYXRlIHRvIHRoZSByYWRpb0J1dHRvbnMgc2VjdGlvbi4gV2hhdCBhcmd1bWVudCBkbyB5b3UgdXNlIGluIHJhZGlvR3JvdXBCdXR0b25zKCkgdG8gZGlzcGxheSB0aGUgYnV0dG9ucyB2ZXJ0aWNhbGx5IGluc3RlYWQgb2YgaG9yaXpvbnRhbGx5PwoKZGlyZWN0aW9uID0gInZlcnRpY2FsIgoKIyMjIEV4cGxvcmUgdGhlIE1lbnRhbCBIZWFsdGggaW4gVGVjaCAyMDE0IFN1cnZleQoKRG9uJ3QgYmUgaW50aW1pZGF0ZWQsIGJ1dCBpbiB0aGlzIGV4ZXJjaXNlLCB5b3UncmUgZ29pbmcgdG8gYnVpbGQgdGhlIGVudGlyZXR5IG9mIHRoaXMgYXBwIChtaW51cyB0aGUgY3VzdG9tIGVycm9yIG1lc3NhZ2UpIGluIG9uZSBnbyEKCkZvciB0aGlzIGFwcCwgeW91J2xsIGJlIHVzaW5nIHRoZSBxdWVzdGlvbnMgIkRvIHlvdSB0aGluayB0aGF0IGRpc2N1c3NpbmcgYSBtZW50YWwgaGVhbHRoIGlzc3VlIHdpdGggeW91ciBlbXBsb3llciB3b3VsZCBoYXZlIG5lZ2F0aXZlIGNvbnNlcXVlbmNlcz8iICh0aGUgbWVudGFsX2hlYWx0aF9jb25zZXF1ZW5jZSB2YXJpYWJsZSkgYW5kICJEbyB5b3UgZmVlbCB0aGF0IHlvdXIgZW1wbG95ZXIgdGFrZXMgbWVudGFsIGhlYWx0aCBhcyBzZXJpb3VzbHkgYXMgcGh5c2ljYWwgaGVhbHRoPyIgKG1lbnRhbF92c19waHlzaWNhbCkgYXMgbXVsdGktc2VsZWN0b3IgaW5wdXRzLCB0aGVuIGRpc3BsYXlpbmcgYSBoaXN0b2dyYW0gb2YgdGhlIEFnZSBvZiByZXNwb25kZW50cy4gVG8gc2VlIHRoZSBjaG9pY2VzIGZvciB0aGVzZSB2YXJpYWJsZXMsIGNvdW50KCkgdGhlbSBpbiB0aGUgY29uc29sZS4KYGBge3J9Cm1lbnRhbF9oZWFsdGhfc3VydmV5IDwtIHJlYWRfY3N2KCJtZW50YWxfaGVhbHRoX3N1cnZleV9lZGl0ZWQuY3N2IikKYGBgCmBgYHtyfQp1aSA8LSBmbHVpZFBhZ2UoCiAgIyBDT0RFIEJFTE9XOiBBZGQgYW4gYXBwcm9wcmlhdGUgdGl0bGUKICB0aXRsZVBhbmVsKCIyMDE0IE1lbnRhbCBIZWFsdGggaW4gVGVjaCBTdXJ2ZXkiKSwKICBzaWRlYmFyUGFuZWwoCiAgICAjIENPREUgQkVMT1c6IEFkZCBhIGNoZWNrYm94R3JvdXBJbnB1dAogICAgY2hlY2tib3hHcm91cElucHV0KAogICAgICBpbnB1dElkID0gIm1lbnRhbF9oZWFsdGhfY29uc2VxdWVuY2UiLAogICAgICBsYWJlbCA9ICJEbyB5b3UgdGhpbmsgdGhhdCBkaXNjdXNzaW5nIGEgbWVudGFsIGhlYWx0aCBpc3N1ZSB3aXRoIHlvdXIgZW1wbG95ZXIgd291bGQgaGF2ZSBuZWdhdGl2ZSBjb25zZXF1ZW5jZXM/IiwKICAgICAgY2hvaWNlcyA9IHVuaXF1ZShtZW50YWxfaGVhbHRoX3N1cnZleSRtZW50YWxfaGVhbHRoX2NvbnNlcXVlbmNlKSwKICAgICAgc2VsZWN0ZWQgPSAiTWF5YmUiCiAgICApLAogICAgIyBDT0RFIEJFTE9XOiBBZGQgYSBwaWNrZXJJbnB1dAogICAgcGlja2VySW5wdXQoCiAgICAgIGlucHV0SWQgPSAibWVudGFsX3ZzX3BoeXNpY2FsIiwKICAgICAgbGFiZWwgPSAiRG8geW91IGZlZWwgdGhhdCB5b3VyIGVtcGxveWVyIHRha2VzIG1lbnRhbCBoZWFsdGggYXMgc2VyaW91c2x5IGFzIHBoeXNpY2FsIGhlYWx0aD8iLAogICAgICBjaG9pY2VzID0gdW5pcXVlKG1lbnRhbF9oZWFsdGhfc3VydmV5JG1lbnRhbF92c19waHlzaWNhbCksCiAgICAgIG11bHRpcGxlID0gVFJVRQogICAgKQogICksCiAgbWFpblBhbmVsKAogICAgIyBDT0RFIEJFTE9XOiBEaXNwbGF5IHRoZSBvdXRwdXQKICAgIHBsb3RPdXRwdXQoImFnZSIpCiAgKQopCgpzZXJ2ZXIgPC0gZnVuY3Rpb24oaW5wdXQsIG91dHB1dCwgc2Vzc2lvbikgewogICMgQ09ERSBCRUxPVzogQnVpbGQgYSBoaXN0b2dyYW0gb2YgdGhlIGFnZSBvZiByZXNwb25kZW50cwogICMgRmlsdGVyZWQgYnkgdGhlIHR3byBpbnB1dHMKICBvdXRwdXQkYWdlIDwtIHJlbmRlclBsb3QoewogICAgbWVudGFsX2hlYWx0aF9zdXJ2ZXkgJT4lCiAgICAgIGZpbHRlcigKICAgICAgICBtZW50YWxfaGVhbHRoX2NvbnNlcXVlbmNlICVpbiUgaW5wdXQkbWVudGFsX2hlYWx0aF9jb25zZXF1ZW5jZSwKICAgICAgICBtZW50YWxfdnNfcGh5c2ljYWwgJWluJSBpbnB1dCRtZW50YWxfdnNfcGh5c2ljYWwKICAgICAgKSAlPiUKICAgICAgZ2dwbG90KGFlcyhBZ2UpKSArCiAgICAgIGdlb21faGlzdG9ncmFtKCkKICB9KQp9CgpzaGlueUFwcCh1aSwgc2VydmVyKQpgYGAKVGhpcyB3YXMgYSByZWFsIGNoYWxsZW5nZSwgYnVpbGRpbmcgYW4gYXBwIGluIG9uZSBnbywgYnV0IHlvdSd2ZSBsZWFybmVkIHNvIG11Y2ggaW4gdGhlIGNvdXJzZSBhbmQgaG9wZWZ1bGx5IGl0IHdhc24ndCB0b28gYmFkLiBOb3csIGxldCdzIGdldCByaWQgb2YgdGhlIGJsYW5rIHBsb3QgYW5kIHRocm93IGEgY3VzdG9tIGVycm9yIG1lc3NhZ2UgdG8gdXNlcnMuCgojIyMgVmFsaWRhdGUgdGhhdCBhIHVzZXIgbWFkZSBhIHNlbGVjdGlvbgoKSXQgaXMgb2Z0ZW4gZ29vZCBwcmFjdGljZSB0byBzZWxlY3QgYSBkZWZhdWx0IHZhbHVlIGZvciB5b3VyIHNlbGVjdG9yIGlucHV0cywgaWYgb25lIHNob3VsZCBiZSBleGNsdWRlZCwgeW91IGNhbiB0aHJvdyBhIGN1c3RvbSBlcnJvciBtZXNzYWdlIHRvIHlvdXIgdXNlcnMgdGhhdCBjbHVlcyB0aGVtIGluIG9uIHdoYXQgdGhleSBuZWVkIHRvIGRvIGZvciB0aGUgYXBwIHRvIHJ1biBzdWNjZXNzZnVsbHkuCgpXZSBzYXcgaW4gdGhlIGxhc3QgZXhlcmNpc2UgdGhhdCwgd2l0aG91dCBhIGRlZmF1bHQgdmFsdWUgZm9yIHRoZSBwaWNrZXJJbnB1dCgpLCB0aGUgcGxvdCBpcyBzaW1wbHkgYmxhbmsuIEluc3RlYWQgb2YgYSBibGFuayBwbG90LCBpbiB0aGlzIGV4ZXJjaXNlIHlvdSdsbCBzaG93IHVzZXJzIGEgY3VzdG9tIGVycm9yIG1lc3NhZ2UgdGVsbGluZyB0aGVtIHRvIG1ha2UgdGhlIGNvcnJlY3Qgc2VsZWN0aW9uIG5lZWRlZCB0byBnZXQgdGhlIGFwcCB3b3JraW5nLgpgYGB7cn0Kc2VydmVyIDwtIGZ1bmN0aW9uKGlucHV0LCBvdXRwdXQsIHNlc3Npb24pIHsKICBvdXRwdXQkYWdlIDwtIHJlbmRlclBsb3QoewogICAgIyBNT0RJRlkgQ09ERSBCRUxPVzogQWRkIHZhbGlkYXRpb24gdGhhdCB1c2VyIHNlbGVjdGVkIGEgM3JkIGlucHV0CiAgICB2YWxpZGF0ZSgKICAgICAgbmVlZChpbnB1dCRtZW50YWxfdnNfcGh5c2ljYWwgIT0gIiIsICJCZSBzdXJlIHRvIHNlbGVjdCBhbiBvcHRpb24iKQogICAgKQoKICAgIG1lbnRhbF9oZWFsdGhfc3VydmV5ICU+JQogICAgICBmaWx0ZXIoCiAgICAgICAgd29ya19pbnRlcmZlcmUgPT0gaW5wdXQkd29ya19pbnRlcmZlcmUsCiAgICAgICAgbWVudGFsX2hlYWx0aF9jb25zZXF1ZW5jZSAlaW4lIGlucHV0JG1lbnRhbF9oZWFsdGhfY29uc2VxdWVuY2UsCiAgICAgICAgbWVudGFsX3ZzX3BoeXNpY2FsICVpbiUgaW5wdXQkbWVudGFsX3ZzX3BoeXNpY2FsCiAgICAgICkgJT4lCiAgICAgIGdncGxvdChhZXMoQWdlKSkgKwogICAgICBnZW9tX2hpc3RvZ3JhbSgpCiAgfSkKfQoKdWkgPC0gZmx1aWRQYWdlKAogIHRpdGxlUGFuZWwoIjIwMTQgTWVudGFsIEhlYWx0aCBpbiBUZWNoIFN1cnZleSIpLAogIHNpZGViYXJQYW5lbCgKICAgIHNsaWRlclRleHRJbnB1dCgKICAgICAgaW5wdXRJZCA9ICJ3b3JrX2ludGVyZmVyZSIsCiAgICAgIGxhYmVsID0gIklmIHlvdSBoYXZlIGEgbWVudGFsIGhlYWx0aCBjb25kaXRpb24sIGRvIHlvdSBmZWVsIHRoYXQgaXQgaW50ZXJmZXJlcyB3aXRoIHlvdXIgd29yaz8iLCAKICAgICAgZ3JpZCA9IFRSVUUsCiAgICAgIGZvcmNlX2VkZ2VzID0gVFJVRSwKICAgICAgY2hvaWNlcyA9IGMoIk5ldmVyIiwgIlJhcmVseSIsICJTb21ldGltZXMiLCAiT2Z0ZW4iKQogICAgKSwKICAgIGNoZWNrYm94R3JvdXBJbnB1dCgKICAgICAgaW5wdXRJZCA9ICJtZW50YWxfaGVhbHRoX2NvbnNlcXVlbmNlIiwKICAgICAgbGFiZWwgPSAiRG8geW91IHRoaW5rIHRoYXQgZGlzY3Vzc2luZyBhIG1lbnRhbCBoZWFsdGggaXNzdWUgd2l0aCB5b3VyIGVtcGxveWVyIHdvdWxkIGhhdmUgbmVnYXRpdmUgY29uc2VxdWVuY2VzPyIsIAogICAgICBjaG9pY2VzID0gYygiTWF5YmUiLCAiWWVzIiwgIk5vIiksCiAgICAgIHNlbGVjdGVkID0gIk1heWJlIgogICAgKSwKICAgIHBpY2tlcklucHV0KAogICAgICBpbnB1dElkID0gIm1lbnRhbF92c19waHlzaWNhbCIsCiAgICAgIGxhYmVsID0gIkRvIHlvdSBmZWVsIHRoYXQgeW91ciBlbXBsb3llciB0YWtlcyBtZW50YWwgaGVhbHRoIGFzIHNlcmlvdXNseSBhcyBwaHlzaWNhbCBoZWFsdGg/IiwgCiAgICAgIGNob2ljZXMgPSBjKCJEb24ndCBLbm93IiwgIk5vIiwgIlllcyIpLAogICAgICBtdWx0aXBsZSA9IFRSVUUKICAgICkgICAgCiAgKSwKICBtYWluUGFuZWwoCiAgICBwbG90T3V0cHV0KCJhZ2UiKSAgCiAgKQopCgpzaGlueUFwcCh1aSwgc2VydmVyKQpgYGAKVGhvdWdoIHRoaXMgY291bGQgaGF2ZSBiZWVuIGF2b2lkZWQgYnkgc2V0dGluZyBhIGRlZmF1bHQgdmFsdWUgZm9yIG91ciBwaWNrZXJJbnB1dCgpLCB0aGlzIHdhcyBhIGdyZWF0IGRlbW9uc3RyYXRpb24gb2YgaG93IHRvIHRocm93IGEgY3VzdG9tIGVycm9yIG1lc3NhZ2UgZm9yIHlvdXIgdXNlcnMgc2hvdWxkIHlvdSBuZWVkIGl0IGluIGZ1dHVyZSBhcHBzLgoKIyMgRXhwbG9yZSBjdWlzaW5lcwoKIyMjIEV4cGxvcmUgY3Vpc2luZXM6IHRvcCBpbmdyZWRpZW50cwoKRm9vZCBoYXMgdW5pdmVyc2FsIGFwcGVhbCwgYW5kIHRoZSBhbWF6aW5nIGFycmF5IG9mIGRpc2hlcyBvbmUgY2FuIGNvbmNvY3Qgd2l0aCB0aGUgbXVsdGl0dWRlIG9mIGluZ3JlZGllbnRzIGxlYWRzIHRvIG5lYXIgaW5maW5pdGUgdmFyaWV0eSEgSW4gdGhpcyBleGVyY2lzZSwgeW91IHdpbGwgdXNlIGEgZGF0YXNldCBuYW1lZCByZWNpcGVzIHRoYXQgY29udGFpbnMgcmVjaXBlcywgdGhlIGN1aXNpbmUgaXQgYmVsb25ncyB0bywgYW5kIHRoZSBpbmdyZWRpZW50cyBpdCB1c2VzLCB0byBidWlsZCBhIFNoaW55IGFwcCB0aGF0IGxldHMgdGhlIHVzZXIgZXhwbG9yZSB0aGUgbW9zdCB1c2VkIGluZ3JlZGllbnRzIGJ5IGN1aXNpbmUuCmBgYHtyfQpyZWNpcGVzIDwtIHJlYWRfcmRzKCJyZWNpcGVzLnJkcyIpCmBgYAoKSGVyZSBpcyBhIGhhbmR5IHNuaXBwZXQgb2YgY29kZSB0aGF0IGdldHMgeW91IHRoZSB0b3AgMTAgaW5ncmVkaWVudHMgdXNlZCBpbiBHcmVlayBjdWlzaW5lLiBZb3Ugd2lsbCBmaW5kIHRoaXMgdXNlZnVsIHRvIGNyZWF0ZSB0aGUgaW50ZXJhY3RpdmUgZGF0YSB0YWJsZSBpbiB0aGUgYXBwIGJhc2VkIG9uIHRoZSBjdWlzaW5lIGFuZCBudW1iZXIgb2YgaW5ncmVkaWVudHMgc2VsZWN0ZWQgYnkgdGhlIHVzZXIuCmBgYHtyfQpsaWJyYXJ5KHRpZHlyKQpyZWNpcGVzIDwtIHJlY2lwZXMgJT4lIAogIHVubmVzdChpbmdyZWRpZW50cykKYGBgCgoKYGBge3J9CnJlY2lwZXMgJT4lIAogIGZpbHRlcihjdWlzaW5lID09ICdncmVlaycpICU+JSAKICBjb3VudChpbmdyZWRpZW50cywgbmFtZSA9ICduYl9yZWNpcGVzJykgJT4lIAogIGFycmFuZ2UoZGVzYyhuYl9yZWNpcGVzKSkgJT4lIAogIGhlYWQoMTApCmBgYApgYGB7cn0KdWkgPC0gZmx1aWRQYWdlKAogIHRpdGxlUGFuZWwoJ0V4cGxvcmUgQ3Vpc2luZXMnKSwKICBzaWRlYmFyTGF5b3V0KAogICAgc2lkZWJhclBhbmVsKAogICAgICAjIENPREUgQkVMT1c6IEFkZCBhbiBpbnB1dCBuYW1lZCAiY3Vpc2luZSIgdG8gc2VsZWN0IGEgY3Vpc2luZQogICAgICBzZWxlY3RJbnB1dCgiY3Vpc2luZSIsCiAgICAgIGxhYmVsID0gIlNlbGVjdCBDdWlzaW5lIiwKICAgICAgY2hvaWNlcyA9IHVuaXF1ZShyZWNpcGVzJGN1aXNpbmUpKSwKICAgICAgIyBDT0RFIEJFTE9XOiBBZGQgYW4gaW5wdXQgbmFtZWQgIm5iX2luZ3JlZGllbnRzIiB0byBzZWxlY3QgIyBvZiBpbmdyZWRpZW50cwogICAgIHNsaWRlcklucHV0KCJuYl9pbmdyZWRpZW50cyIsCiAgICAgIlNlbGVjdCBOby4gb2YgSW5ncmVkaWVudHMiLAogICAgIHZhbHVlID0gNSwKICAgICBtaW4gPSAxLAogICAgIG1heCA9IDEwMCkKICAgICksCiAgICBtYWluUGFuZWwoCiAgICAgICMgQ09ERSBCRUxPVzogQWRkIGEgRFQgb3V0cHV0IG5hbWVkICJkdF90b3BfaW5ncmVkaWVudHMiCiAgICAgRFQ6OkRUT3V0cHV0KCJkdF90b3BfaW5ncmVkaWVudHMiKQogICAgKQogKQopCnNlcnZlciA8LSBmdW5jdGlvbihpbnB1dCwgb3V0cHV0LCBzZXNzaW9uKSB7CiAgIyBDT0RFIEJFTE9XOiBSZW5kZXIgdGhlIHRvcCBpbmdyZWRpZW50cyBpbiBhIGNob3NlbiBjdWlzaW5lIGFzIAogICMgYW4gaW50ZXJhY3RpdmUgZGF0YSB0YWJsZSBhbmQgYXNzaWduIGl0IHRvIG91dHB1dCBvYmplY3QgYGR0X3RvcF9pbmdyZWRpZW50c2AKICBvdXRwdXQkZHRfdG9wX2luZ3JlZGllbnRzIDwtIERUOjpyZW5kZXJEVCh7CiAgcmVjaXBlcyAlPiUKICAgIGZpbHRlcihjdWlzaW5lID09IGlucHV0JGN1aXNpbmUpICU+JQogICAgY291bnQoaW5ncmVkaWVudHMsIG5hbWUgPSAibmJfcmVjaXBlcyIpICU+JQogICAgYXJyYW5nZShkZXNjKG5iX3JlY2lwZXMpKSAlPiUKICAgIGhlYWQoaW5wdXQkbmJfaW5ncmVkaWVudHMpCiAgfSkKCn0Kc2hpbnlBcHAodWksIHNlcnZlcikKYGBgCkludGVyYWN0aXZlIGRhdGEgdGFibGVzIGFyZSBhIGdyZWF0IHdheSB0byBzaG93Y2FzZSByYXcgZGF0YSBpbiB5b3VyIGFwcHMgdG8gbGV0IHlvdXIgdXNlcnMgZXhwbG9yZS4gVGhlIGRhdGF0YWJsZSBwYWNrYWdlIG1ha2VzIGl0IGVhc3kgdG8gY3JlYXRlIGhpZ2hseSBpbnRlcmFjdGl2ZSBkYXRhIHRhYmxlcywgYW5kIGlzIGRlZmluaXRlbHkgd29ydGggZXhwbG9yaW5nIGluIGRldGFpbC4KCiMjIyBFeHBsb3JlIGN1aXNpbmVzOiB0b3AgaW5ncmVkaWVudHMgcmVkdXgKCkVhY2ggY3Vpc2luZSBpcyBkaXN0aW5jdCBiZWNhdXNlIG9mIGEgc21hbGwgc2V0IG9mIGRpc3RpbmN0IGluZ3JlZGllbnRzLiBXZSBjYW4ndCBzdXJmYWNlIHRoZXNlIGJ5IGxvb2tpbmcgYXQgdGhlIG1vc3QgcG9wdWxhciBpbmdyZWRpZW50cywgc2luY2UgdGhleSdyZSB0aGUgYnJlYWQtYW5kLWJ1dHRlciBpbmdyZWRpZW50cyBvZiBjb29raW5nIGxpa2Ugc2FsdCBvciBzdWdhci4KCkFub3RoZXIgbWV0cmljIHRoYXQgY2FuIGFpZCB1cyBpbiB0aGlzIHF1ZXN0IGlzIHRoZSB0ZXJtIGZyZXF1ZW5jeeKAk2ludmVyc2UgZG9jdW1lbnQgZnJlcXVlbmN5IChURklERiksIGEgbnVtZXJpY2FsIHN0YXRpc3RpYyB0aGF0IGlzIGludGVuZGVkIHRvIHJlZmxlY3QgaG93IGltcG9ydGFudCBhIHdvcmQgKGluZ3JlZGllbnQpIGlzIHRvIGEgZG9jdW1lbnQgKGN1aXNpbmUpIGluIGEgY29sbGVjdGlvbiBvciBjb3JwdXMgKHJlY2lwZXMpLgpgYGB7cn0KbGlicmFyeSh0aWR5dGV4dCkKcmVjaXBlc19lbnJpY2hlZCA8LSByZWNpcGVzICU+JQogIGNvdW50KGN1aXNpbmUsIGluZ3JlZGllbnRzLCBuYW1lID0nbmJfcmVjaXBlcycpICU+JQogIHRpZHl0ZXh0OjpiaW5kX3RmX2lkZihpbmdyZWRpZW50cywgY3Vpc2luZSwgbmJfcmVjaXBlcykKYGBgCldlIGFscmVhZHkgcHJlY29tcHV0ZWQgdGhlIHRmX2lkZiBmb3IgeW91IGFuZCBjcmVhdGVkIGFuIGVucmljaGVkIGRhdGFzZXQgbmFtZWQgcmVjaXBlc19lbnJpY2hlZC4gWW91ciBnb2FsIGlzIHRvIGNyZWF0ZSBhIFNoaW55IGFwcCB0aGF0IGRpc3BsYXlzIGEgaG9yaXpvbnRhbCBiYXIgcGxvdCBvZiB0aGUgdG9wIGRpc3RpbmN0aXZlIGluZ3JlZGllbnRzIGluIGEgY3Vpc2luZSwgYXMgbWVhc3VyZWQgYnkgdGZfaWRmLgoKWW91IHdpbGwgdXNlIGEgcmVhY3RpdmUgZXhwcmVzc2lvbiB0byBlbmNhcHN1bGF0ZSB0aGUgY29tcHV0YXRpb25zIGFuZCBsZXQgdGhlIHBsb3R0aW5nIGNvZGUgZm9jdXMgb25seSBvbiBjcmVhdGluZyB0aGUgcGxvdC4gVGhpcyBpcyBnb29kIHByb2dyYW1taW5nIHByYWN0aWNlIGFuZCBoZWxwcyBjcmVhdGUgbW9kdWxhciBhbmQgcGVyZm9ybWFudCBTaGlueSBhcHBzLgoKV2UgaGF2ZSBsb2FkZWQgdGhlIHBhY2thZ2VzIHNoaW55LCBkcGx5ciwgZ2dwbG90MiBhbmQgcGxvdGx5LiBIZXJlIGFyZSB0d28gaGFuZHkgc25pcHBldHMgdG8gZmlsdGVyIGZvciB0aGUgdG9wIHJlY2lwZXMgYnkgY3Vpc2luZSBhbmQgY3JlYXRlIGEgaG9yaXpvbnRhbCBiYXIgcGxvdC4gWW91IGNhbiBtb2RpZnkgaXQgYXBwcm9wcmlhdGVseS4KCiAgICB0b3BfaW5ncmVkaWVudHMgPC0gcmVjaXBlc19lbnJpY2hlZCAlPiUgCiAgICAgIGZpbHRlcihjdWlzaW5lID09ICdncmVlaycpICU+JSAKICAgICAgYXJyYW5nZShkZXNjKHRmX2lkZikpICU+JSAKICAgICAgaGVhZCg1KSAKICAgIAogICAgZ2dwbG90KHRvcF9pbmdyZWRpZW50cywgYWVzKHggPSBpbmdyZWRpZW50LCB5ID0gdGZfaWRmKSkgKwogICAgICBnZW9tX2NvbCgpICsKICAgICAgY29vcmRfZmxpcCgpCiAgICAgIApgYGB7cn0KbGlicmFyeShmb3JjYXRzKQpgYGAKCmBgYHtyfQp1aSA8LSBmbHVpZFBhZ2UoCiAgdGl0bGVQYW5lbCgnRXhwbG9yZSBDdWlzaW5lcycpLAogIHNpZGViYXJMYXlvdXQoCiAgICBzaWRlYmFyUGFuZWwoCiAgICAgIHNlbGVjdElucHV0KCdjdWlzaW5lJywgJ1NlbGVjdCBDdWlzaW5lJywgdW5pcXVlKHJlY2lwZXMkY3Vpc2luZSkpLAogICAgICBzbGlkZXJJbnB1dCgnbmJfaW5ncmVkaWVudHMnLCAnU2VsZWN0IE5vLiBvZiBJbmdyZWRpZW50cycsIDUsIDEwMCwgMTApLAogICAgKSwKICAgIG1haW5QYW5lbCgKICAgICAgdGFic2V0UGFuZWwoCiAgICAgICAgIyBDT0RFIEJFTE9XOiBBZGQgYSBwbG90bHkgb3V0cHV0IG5hbWVkICJwbG90X3RvcF9pbmdyZWRpZW50cyIKICAgICAgICB0YWJQYW5lbCgiUGxvdCIsIHBsb3RseTo6cGxvdGx5T3V0cHV0KCJwbG90X3RvcF9pbmdyZWRpZW50cyIpKSwgCiAgICAgICAgdGFiUGFuZWwoJ1RhYmxlJywgRFQ6OkRUT3V0cHV0KCdkdF90b3BfaW5ncmVkaWVudHMnKSkKICAgICAgKQogICAgKQogICkKKQpzZXJ2ZXIgPC0gZnVuY3Rpb24oaW5wdXQsIG91dHB1dCwgc2Vzc2lvbikgewogICMgQ09ERSBCRUxPVzogQWRkIGEgcmVhY3RpdmUgZXhwcmVzc2lvbiBuYW1lZCBgcnZhbF90b3BfaW5ncmVkaWVudHNgIHRoYXQKICAjIGZpbHRlcnMgYHJlY2lwZXNfZW5yaWNoZWRgIGZvciB0aGUgc2VsZWN0ZWQgY3Vpc2luZSBhbmQgdG9wIGluZ3JlZGllbnRzCiAgIyBiYXNlZCBvbiB0aGUgdGZfaWRmIHZhbHVlLgogcnZhbF90b3BfaW5ncmVkaWVudHMgPC0gcmVhY3RpdmUoewogICByZWNpcGVzX2VucmljaGVkICU+JQogICBmaWx0ZXIoY3Vpc2luZSA9PSBpbnB1dCRjdWlzaW5lKSAlPiUKICAgYXJyYW5nZShkZXNjKHRmX2lkZikpICU+JQogICBoZWFkKGlucHV0JG5iX2luZ3JlZGllbnRzKSAlPiUKICAgbXV0YXRlKGluZ3JlZGllbnRzID0gZm9yY2F0czo6ZmN0X3Jlb3JkZXIoaW5ncmVkaWVudHMsIHRmX2lkZikpCiB9KQogIAogICMgQ09ERSBCRUxPVzogUmVuZGVyIGEgaG9yaXpvbnRhbCBiYXIgcGxvdCBvZiB0b3AgaW5ncmVkaWVudHMgYW5kIAogICMgdGhlIHRmX2lkZiBvZiByZWNpcGVzIHRoZXkgZ2V0IHVzZWQgaW4sIGFuZCBhc3NpZ24gaXQgdG8gYW4gb3V0cHV0IG5hbWVkIAogICMgYHBsb3RfdG9wX2luZ3JlZGllbnRzYCAKICBvdXRwdXQkcGxvdF90b3BfaW5ncmVkaWVudHMgPC0gcGxvdGx5OjpyZW5kZXJQbG90bHkoewogICAgcnZhbF90b3BfaW5ncmVkaWVudHMoKSAlPiUgCiAgICAgIGdncGxvdCggYWVzKHggPSBpbmdyZWRpZW50cywgeSA9IHRmX2lkZikpICsKICAgICAgZ2VvbV9jb2woKSArCiAgICAgIGNvb3JkX2ZsaXAoKQogIH0pCiAgCiAgb3V0cHV0JGR0X3RvcF9pbmdyZWRpZW50cyA8LSBEVDo6cmVuZGVyRFQoewogICAgcmVjaXBlcyAlPiUgCiAgICAgIGZpbHRlcihjdWlzaW5lID09IGlucHV0JGN1aXNpbmUpICU+JSAKICAgICAgY291bnQoaW5ncmVkaWVudHMsIG5hbWUgPSAnbmJfcmVjaXBlcycpICU+JSAKICAgICAgYXJyYW5nZShkZXNjKG5iX3JlY2lwZXMpKSAlPiUgCiAgICAgIGhlYWQoaW5wdXQkbmJfaW5ncmVkaWVudHMpCiAgfSkKfQpzaGlueUFwcCh1aSwgc2VydmVyKQpgYGAKIyMjIEV4cGxvcmUgY3Vpc2luZXM6IHdvcmRjbG91ZHMKCkEgaGFuZHkgd2F5IHRvIHZpc3VhbGl6ZSBhIGxvdCBvZiBkYXRhIGlzIHdvcmRjbG91ZHMuIEluIHRoaXMgZXhlcmNpc2UsIHlvdSB3aWxsIGV4dGVuZCB0aGUgU2hpbnkgYXBwIHdlIGJ1aWx0IHByZXZpb3VzbHkgYW5kIGFkZCBhIG5ldyB0YWIgdGhhdCBkaXNwbGF5cyB0aGUgdG9wIGRpc3RpbmN0aXZlIGluZ3JlZGllbnRzIGFzIGFuIGludGVyYWN0aXZlIHdvcmRjbG91ZC4KCgpIZXJlIGlzIGEgaGFuZHkgc25pcHBldCB0byBjcmVhdGUgYSB3b3JkY2xvdWQuCgogICAgZDN3b3JkY2xvdWQoCiAgICAgIHdvcmRzID0gYygnaGVsbG8nLCAnd29ybGQnLCAnZ29vZCcpLCAKICAgICAgZnJlcXMgPSBjKDIwLCA0MCwgMzApLAogICAgICB0b29sdGlwID0gVFJVRQogICAgKQpgYGB7cn0KbGlicmFyeSh3b3JkY2xvdWQpCmxpYnJhcnkoUkNvbG9yQnJld2VyKQpgYGAKCmBgYHtyfQp1aSA8LSBmbHVpZFBhZ2UoCiAgdGl0bGVQYW5lbCgnRXhwbG9yZSBDdWlzaW5lcycpLAogIHNpZGViYXJMYXlvdXQoCiAgICBzaWRlYmFyUGFuZWwoCiAgICAgIHNlbGVjdElucHV0KCdjdWlzaW5lJywgJ1NlbGVjdCBDdWlzaW5lJywgdW5pcXVlKHJlY2lwZXMkY3Vpc2luZSkpLAogICAgICBzbGlkZXJJbnB1dCgnbmJfaW5ncmVkaWVudHMnLCAnU2VsZWN0IE5vLiBvZiBJbmdyZWRpZW50cycsIDUsIDEwMCwgMjApLAogICAgKSwKICAgIG1haW5QYW5lbCgKICAgICAgdGFic2V0UGFuZWwoCiAgICAgICAgIyBDT0RFIEJFTE9XOiBBZGQgYGQzd29yZGNsb3VkT3V0cHV0YCBuYW1lZCBgd2NfaW5ncmVkaWVudHNgIGluIGEgYHRhYlBhbmVsYAogICAgICAgIHRhYlBhbmVsKCdXb3JkIENsb3VkJywgcGxvdE91dHB1dCgnd2NfaW5ncmVkaWVudHMnKSksCiAgICAgICAgdGFiUGFuZWwoJ1Bsb3QnLCBwbG90bHk6OnBsb3RseU91dHB1dCgncGxvdF90b3BfaW5ncmVkaWVudHMnKSksCiAgICAgICAgdGFiUGFuZWwoJ1RhYmxlJywgRFQ6OkRUT3V0cHV0KCdkdF90b3BfaW5ncmVkaWVudHMnKSkKICAgICAgKQogICAgKQogICkKKQpzZXJ2ZXIgPC0gZnVuY3Rpb24oaW5wdXQsIG91dHB1dCwgc2Vzc2lvbil7CiAgIyBDT0RFIEJFTE9XOiBSZW5kZXIgYW4gaW50ZXJhY3RpdmUgd29yZGNsb3VkIG9mIHRvcCBkaXN0aW5jdGl2ZSBpbmdyZWRpZW50cyAKICAjIGFuZCB0aGUgbnVtYmVyIG9mIHJlY2lwZXMgdGhleSBnZXQgdXNlZCBpbiwgdXNpbmcgCiAgIyBgZDN3b3JkY2xvdWQ6OnJlbmRlckQzd29yZGNsb3VkYCwgYW5kIGFzc2lnbiBpdCB0byBhbiBvdXRwdXQgbmFtZWQKICAjIGB3Y19pbmdyZWRpZW50c2AKICB3b3JkY2xvdWRfcmVwIDwtIHJlcGVhdGFibGUod29yZGNsb3VkKQogIG91dHB1dCR3Y19pbmdyZWRpZW50cyA8LSByZW5kZXJQbG90KHsKICAgICBpbmdyZWRpZW50c19kZiA8LSBydmFsX3RvcF9pbmdyZWRpZW50cygpCiAgICAgd29yZGNsb3VkX3JlcCh3b3JkcyA9IGluZ3JlZGllbnRzX2RmJGluZ3JlZGllbnRzLAogICAgICAgICAgICAgICAgICAgZnJlcSA9IGluZ3JlZGllbnRzX2RmJG5iX3JlY2lwZXMsCiAgICAgICAgICAgICAgICAgICBzY2FsZSA9IGMoNCwgMC41KSwKICAgICAgICAgICAgICAgICAgIGNvbG9ycz1icmV3ZXIucGFsKDgsICJEYXJrMiIpKQogIH0pCiAgcnZhbF90b3BfaW5ncmVkaWVudHMgPC0gcmVhY3RpdmUoewogICAgcmVjaXBlc19lbnJpY2hlZCAlPiUgCiAgICAgIGZpbHRlcihjdWlzaW5lID09IGlucHV0JGN1aXNpbmUpICU+JSAKICAgICAgYXJyYW5nZShkZXNjKHRmX2lkZikpICU+JSAKICAgICAgaGVhZChpbnB1dCRuYl9pbmdyZWRpZW50cykgJT4lIAogICAgICBtdXRhdGUoaW5ncmVkaWVudHMgPSBmb3JjYXRzOjpmY3RfcmVvcmRlcihpbmdyZWRpZW50cywgdGZfaWRmKSkKICB9KQogIG91dHB1dCRwbG90X3RvcF9pbmdyZWRpZW50cyA8LSBwbG90bHk6OnJlbmRlclBsb3RseSh7CiAgICBydmFsX3RvcF9pbmdyZWRpZW50cygpICU+JQogICAgICBnZ3Bsb3QoYWVzKHggPSBpbmdyZWRpZW50cywgeSA9IHRmX2lkZikpICsKICAgICAgZ2VvbV9jb2woKSArCiAgICAgIGNvb3JkX2ZsaXAoKQogIH0pCiAgb3V0cHV0JGR0X3RvcF9pbmdyZWRpZW50cyA8LSBEVDo6cmVuZGVyRFQoewogICAgcmVjaXBlcyAlPiUgCiAgICAgIGZpbHRlcihjdWlzaW5lID09IGlucHV0JGN1aXNpbmUpICU+JSAKICAgICAgY291bnQoaW5ncmVkaWVudHMsIG5hbWUgPSAnbmJfcmVjaXBlcycpICU+JSAKICAgICAgYXJyYW5nZShkZXNjKG5iX3JlY2lwZXMpKSAlPiUgCiAgICAgIGhlYWQoaW5wdXQkbmJfaW5ncmVkaWVudHMpCiAgfSkKfQpzaGlueUFwcCh1aSA9IHVpLCBzZXJ2ZXI9IHNlcnZlcikKYGBgCkJlbG93IG5vdCB3b3JraW5nIGluIFIgNDoKYGBge3J9CnVpIDwtIGZsdWlkUGFnZSgKICB0aXRsZVBhbmVsKCdFeHBsb3JlIEN1aXNpbmVzJyksCiAgc2lkZWJhckxheW91dCgKICAgIHNpZGViYXJQYW5lbCgKICAgICAgc2VsZWN0SW5wdXQoJ2N1aXNpbmUnLCAnU2VsZWN0IEN1aXNpbmUnLCB1bmlxdWUocmVjaXBlcyRjdWlzaW5lKSksCiAgICAgIHNsaWRlcklucHV0KCduYl9pbmdyZWRpZW50cycsICdTZWxlY3QgTm8uIG9mIEluZ3JlZGllbnRzJywgNSwgMTAwLCAyMCksCiAgICApLAogICAgbWFpblBhbmVsKAogICAgICB0YWJzZXRQYW5lbCgKICAgICAgICAjIENPREUgQkVMT1c6IEFkZCBgZDN3b3JkY2xvdWRPdXRwdXRgIG5hbWVkIGB3Y19pbmdyZWRpZW50c2AgaW4gYSBgdGFiUGFuZWxgCiAgICAgICAgdGFiUGFuZWwoJ1dvcmQgQ2xvdWQnLCBkM3dvcmRjbG91ZDo6ZDN3b3JkY2xvdWRPdXRwdXQoJ3djX2luZ3JlZGllbnRzJywgaGVpZ2h0ID0gJzQwMCcpKSwKICAgICAgICB0YWJQYW5lbCgnUGxvdCcsIHBsb3RseTo6cGxvdGx5T3V0cHV0KCdwbG90X3RvcF9pbmdyZWRpZW50cycpKSwKICAgICAgICB0YWJQYW5lbCgnVGFibGUnLCBEVDo6RFRPdXRwdXQoJ2R0X3RvcF9pbmdyZWRpZW50cycpKQogICAgICApCiAgICApCiAgKQopCnNlcnZlciA8LSBmdW5jdGlvbihpbnB1dCwgb3V0cHV0LCBzZXNzaW9uKXsKICAjIENPREUgQkVMT1c6IFJlbmRlciBhbiBpbnRlcmFjdGl2ZSB3b3JkY2xvdWQgb2YgdG9wIGRpc3RpbmN0aXZlIGluZ3JlZGllbnRzIAogICMgYW5kIHRoZSBudW1iZXIgb2YgcmVjaXBlcyB0aGV5IGdldCB1c2VkIGluLCB1c2luZyAKICAjIGBkM3dvcmRjbG91ZDo6cmVuZGVyRDN3b3JkY2xvdWRgLCBhbmQgYXNzaWduIGl0IHRvIGFuIG91dHB1dCBuYW1lZAogICMgYHdjX2luZ3JlZGllbnRzYC4KICBvdXRwdXQkd2NfaW5ncmVkaWVudHMgPC0gZDN3b3JkY2xvdWQ6OnJlbmRlckQzd29yZGNsb3VkKHsKICAgICBpbmdyZWRpZW50c19kZiA8LSBydmFsX3RvcF9pbmdyZWRpZW50cygpCiAgICAgZDN3b3JkY2xvdWQoaW5ncmVkaWVudHNfZGYkaW5ncmVkaWVudCwgaW5ncmVkaWVudHNfZGYkbmJfcmVjaXBlcywgdG9vbHRpcCA9IFRSVUUpCiAgfSkKICBydmFsX3RvcF9pbmdyZWRpZW50cyA8LSByZWFjdGl2ZSh7CiAgICByZWNpcGVzX2VucmljaGVkICU+JSAKICAgICAgZmlsdGVyKGN1aXNpbmUgPT0gaW5wdXQkY3Vpc2luZSkgJT4lIAogICAgICBhcnJhbmdlKGRlc2ModGZfaWRmKSkgJT4lIAogICAgICBoZWFkKGlucHV0JG5iX2luZ3JlZGllbnRzKSAlPiUgCiAgICAgIG11dGF0ZShpbmdyZWRpZW50ID0gZm9yY2F0czo6ZmN0X3Jlb3JkZXIoaW5ncmVkaWVudCwgdGZfaWRmKSkKICB9KQogIG91dHB1dCRwbG90X3RvcF9pbmdyZWRpZW50cyA8LSBwbG90bHk6OnJlbmRlclBsb3RseSh7CiAgICBydmFsX3RvcF9pbmdyZWRpZW50cygpICU+JQogICAgICBnZ3Bsb3QoYWVzKHggPSBpbmdyZWRpZW50LCB5ID0gdGZfaWRmKSkgKwogICAgICBnZW9tX2NvbCgpICsKICAgICAgY29vcmRfZmxpcCgpCiAgfSkKICBvdXRwdXQkZHRfdG9wX2luZ3JlZGllbnRzIDwtIERUOjpyZW5kZXJEVCh7CiAgICByZWNpcGVzICU+JSAKICAgICAgZmlsdGVyKGN1aXNpbmUgPT0gaW5wdXQkY3Vpc2luZSkgJT4lIAogICAgICBjb3VudChpbmdyZWRpZW50LCBuYW1lID0gJ25iX3JlY2lwZXMnKSAlPiUgCiAgICAgIGFycmFuZ2UoZGVzYyhuYl9yZWNpcGVzKSkgJT4lIAogICAgICBoZWFkKGlucHV0JG5iX2luZ3JlZGllbnRzKQogIH0pCn0Kc2hpbnlBcHAodWkgPSB1aSwgc2VydmVyPSBzZXJ2ZXIpCmBgYApUaGVyZSBpcyBhIHJpY2ggZWNvc3lzdGVtIG9mIGludGVyYWN0aXZlIHdpZGdldHMgbGlrZSBkM3dvcmRjbG91ZCwgdGhhdCBtYWtlIGl0IGVhc3kgdG8gYWRkIGludGVyYWN0aXZpdHkgdG8geW91ciBTaGlueSBhcHAuIExvb2sgdXAgdGhlIGdhbGxlcnkgb2YgaHRtbHdpZGdldHMgYXQgaHR0cDovL2dhbGxlcnkuaHRtbHdpZGdldHMub3JnLy4KCiMjIE1hc3Mgc2hvb3RpbmdzCgojIyMgTWFzcyBzaG9vdGluZ3M6IGFkZCBpbnB1dHMKCk1hc3MgU2hvb3RpbmdzIGhhdmUgYmVlbiBhIHRvcGljIG9mIGludGVuc2UgZGlzY3Vzc2lvbiBpbiB0aGUgVW5pdGVkIFN0YXRlcy4gQSBwdWJsaWMgZGF0YWJhc2Ugb2YgbWFzcyBzaG9vdGluZ3Mgc2luY2UgMTk4MiBoYXMgYmVlbiBtYWRlIGF2YWlsYWJsZSBieSB0aGUgW01vdGhlciBKb25lc10oaHR0cHM6Ly93d3cubW90aGVyam9uZXMuY29tL3BvbGl0aWNzLzIwMTIvMTIvbWFzcy1zaG9vdGluZ3MtbW90aGVyLWpvbmVzLWZ1bGwtZGF0YS8pLCBhIG5vbi1wcm9maXQgb3JnYW5pemF0aW9uLiBPdmVyIHRoZSBuZXh0IHRocmVlIGV4ZXJjaXNlcywgeW91IHdpbGwgYnVpbGQgYSBTaGlueSBhcHAgdG8gZXhwbG9yZSB0aGVzZSBzaG9vdGluZ3Mgb24gYW4gaW50ZXJhY3RpdmUgbWFwLgoKSW4gdGhpcyBleGVyY2lzZSwgeW91IHdpbGwgYWRkIGEgc2xpZGVyIGlucHV0IHRvIGZpbHRlciBvbiBmYXRhbGl0aWVzIGFuZCBhIGRhdGUgcmFuZ2UgaW5wdXQgdG8gZmlsdGVyIG9uIGEgcmFuZ2Ugb2YgZGF0ZXMuIAoKYGBge3J9CmxpYnJhcnkoc2hpbnkpCmxpYnJhcnkoZHBseXIpCmxpYnJhcnkobGVhZmxldCkKbGlicmFyeShyZWFkcikKbGlicmFyeShsdWJyaWRhdGUpCm1hc3Nfc2hvb3RpbmdzIDwtIHJlYWRfY3N2KCJtYXNzLXNob290aW5ncy5jc3YiKQpgYGAKYGBge3J9Cm1hc3Nfc2hvb3RpbmdzIDwtIG1hc3Nfc2hvb3RpbmdzICU+JSAKICBtdXRhdGUoZGF0ZSA9IHBhcnNlX2RhdGVfdGltZShtYXNzX3Nob290aW5ncyRkYXRlLCAibWR5IikpCmBgYAoKYGBge3J9CnVpIDwtIGJvb3RzdHJhcFBhZ2UoCiAgdGhlbWUgPSBzaGlueXRoZW1lczo6c2hpbnl0aGVtZSgnc2ltcGxleCcpLAogIGxlYWZsZXQ6OmxlYWZsZXRPdXRwdXQoJ21hcCcsIGhlaWdodCA9ICcxMDAlJywgd2lkdGggPSAnMTAwJScpLAogIGFic29sdXRlUGFuZWwodG9wID0gMTAsIHJpZ2h0ID0gMTAsIGlkID0gJ2NvbnRyb2xzJywKICAgICMgQ09ERSBCRUxPVzogQWRkIHNsaWRlciBpbnB1dCBuYW1lZCBuYl9mYXRhbGl0aWVzCiAgICBzbGlkZXJJbnB1dCgibmJfZmF0YWxpdGllcyIsICJNaW5pbXVtIEZhdGFsaXRpZXMiLCB2YWx1ZSA9IDEwLCBtaW4gPSAxLCBtYXggPSA0MCksCiAgICAjIENPREUgQkVMT1c6IEFkZCBkYXRlIHJhbmdlIGlucHV0IG5hbWVkIGRhdGVfcmFuZ2UKICAgIGRhdGVSYW5nZUlucHV0KCJkYXRlX3JhbmdlIiwgIlNlbGVjdCBEYXRlIiwgc3RhcnQgPSAiMTk4Mi0wMS0wMSIsIGVuZCA9ICIyMDIwLTAxLTAxIikKICApLAogIHRhZ3Mkc3R5bGUodHlwZSA9ICJ0ZXh0L2NzcyIsICIKICAgIGh0bWwsIGJvZHkge3dpZHRoOjEwMCU7aGVpZ2h0OjEwMCV9ICAgICAKICAgICNjb250cm9sc3tiYWNrZ3JvdW5kLWNvbG9yOndoaXRlO3BhZGRpbmc6MjBweDt9CiAgIikKKQpzZXJ2ZXIgPC0gZnVuY3Rpb24oaW5wdXQsIG91dHB1dCwgc2Vzc2lvbikgewogIG91dHB1dCRtYXAgPC0gbGVhZmxldDo6cmVuZGVyTGVhZmxldCh7CiAgICBsZWFmbGV0KCkgJT4lIAogICAgICBhZGRUaWxlcygpICU+JQogICAgICBzZXRWaWV3KCAtOTguNTgsIDM5LjgyLCB6b29tID0gNSkgJT4lIAogICAgICBhZGRUaWxlcygpCiAgfSkKfQoKc2hpbnlBcHAodWksIHNlcnZlcikKYGBgCk5vdGUgaG93IHdlIG1hZGUgdXNlIG9mIGFuIGFsdGVybmF0ZSBsYXlvdXQgdG8gZGlzcGxheSBhIGZ1bGwgc2NyZWVuIGludGVyYWN0aXZlIG1hcCwgYW5kIGEgc3RpY2t5IGlucHV0IHBhbmVsIG9uIHRoZSB0b3AgcmlnaHQuIFNoaW55IGhhcyBtYW55IHN1Y2ggbGF5b3V0cyB0aGF0IHlvdSBzaG91bGQgZGVmaW5pdGVseSBleHBsb3JlLgoKIyMjIE1hc3Mgc2hvb3RpbmdzOiBtb2RpZnkgb3V0cHV0CgpXb3Ugd2lsbCBleHRlbmQgdGhlIFNoaW55IGFwcCB5b3UgYnVpbHQgcHJldmlvdXNseSBzbyB0aGF0IHJlZCBjaXJjbGVzIHNpemVkIGJhc2VkIG9uIHRoZSBudW1iZXIgb2YgZmF0YWxpdGllcyBhcHBlYXIgb24gdGhlIGludGVyYWN0aXZlIG1hcCwgYWxvbmcgd2l0aCBhIHN1bW1hcnkgb2YgdGhlIGNhc2Ugd2hlbiB0aGUgY2lyY2xlIGlzIGNsaWNrZWQuIAoKYGBge3J9CnNlcnZlciA8LSBmdW5jdGlvbihpbnB1dCwgb3V0cHV0LCBzZXNzaW9uKSB7CiAgcnZhbF9tYXNzX3Nob290aW5ncyA8LSByZWFjdGl2ZSh7CiAgICAjIE1PRElGWSBDT0RFIEJFTE9XOiBGaWx0ZXIgbWFzc19zaG9vdGluZ3Mgb24gbmJfZmF0YWxpdGllcyBhbmQgCiAgICAjIHNlbGVjdGVkIGRhdGVfcmFuZ2UuCiAgICBtYXNzX3Nob290aW5ncyAlPiUgCiAgICAgIGZpbHRlcigKICAgICAgICBkYXRlID49IGlucHV0JGRhdGVfcmFuZ2VbMV0sCiAgICAgICAgZGF0ZSA8PSBpbnB1dCRkYXRlX3JhbmdlWzJdLAogICAgICAgIGZhdGFsaXRpZXMgPj0gaW5wdXQkbmJfZmF0YWxpdGllcwogICAgICApCiAgfSkKICBvdXRwdXQkbWFwIDwtIGxlYWZsZXQ6OnJlbmRlckxlYWZsZXQoewogICAgcnZhbF9tYXNzX3Nob290aW5ncygpICU+JQogICAgICBsZWFmbGV0KCkgJT4lIAogICAgICBhZGRUaWxlcygpICU+JQogICAgICBzZXRWaWV3KCAtOTguNTgsIDM5LjgyLCB6b29tID0gNSkgJT4lIAogICAgICBhZGRUaWxlcygpICU+JSAKICAgICAgYWRkQ2lyY2xlTWFya2VycygKICAgICAgICAjIENPREUgQkVMT1c6IEFkZCBwYXJhbWV0ZXJzIHBvcHVwIGFuZCByYWRpdXMgYW5kIG1hcCB0aGVtCiAgICAgICAgIyB0byB0aGUgc3VtbWFyeSBhbmQgZmF0YWxpdGllcyBjb2x1bW5zCiAgICAgICAgcG9wdXAgPSB+IHN1bW1hcnksIHJhZGl1cyA9IH4gZmF0YWxpdGllcywKICAgICAgICBmaWxsQ29sb3IgPSAncmVkJywgY29sb3IgPSAncmVkJywgd2VpZ2h0ID0gMQogICAgICApCiAgfSkKfQp1aSA8LSBib290c3RyYXBQYWdlKAogIHRoZW1lID0gc2hpbnl0aGVtZXM6OnNoaW55dGhlbWUoJ3NpbXBsZXgnKSwKICBsZWFmbGV0OjpsZWFmbGV0T3V0cHV0KCdtYXAnLCBoZWlnaHQgPSAnMTAwJScsIHdpZHRoID0gJzEwMCUnKSwKICBhYnNvbHV0ZVBhbmVsKHRvcCA9IDEwLCByaWdodCA9IDEwLCBpZCA9ICdjb250cm9scycsCiAgICBzbGlkZXJJbnB1dCgnbmJfZmF0YWxpdGllcycsICdNaW5pbXVtIEZhdGFsaXRpZXMnLCAxLCA0MCwgMTApLAogICAgZGF0ZVJhbmdlSW5wdXQoJ2RhdGVfcmFuZ2UnLCAnU2VsZWN0IERhdGUnLCAiMjAxMC0wMS0wMSIsICIyMDE5LTEyLTAxIikKICApLAogIHRhZ3Mkc3R5bGUodHlwZSA9ICJ0ZXh0L2NzcyIsICIKICAgIGh0bWwsIGJvZHkge3dpZHRoOjEwMCU7aGVpZ2h0OjEwMCV9ICAgICAKICAgICNjb250cm9sc3tiYWNrZ3JvdW5kLWNvbG9yOndoaXRlO3BhZGRpbmc6MjBweDt9CiAgIikKKQoKc2hpbnlBcHAodWksIHNlcnZlcikKYGBgClVzZSByZWFjdGl2ZSBleHByZXNzaW9ucyBnZW5lcm91c2x5IGluIHlvdXIgYXBwLiBUaGV5IGFyZSBleGVjdXRlZCBvbmx5IHdoZW4gcmVxdWlyZWQsIGFuZCB0aGVpciB2YWx1ZXMgYXJlIGNhY2hlZCwgbGVhZGluZyB0byBoaWdobHkgcGVyZm9ybWFudCBhcHBzLiBJdCBhbHNvIGxlYWRzIHRvIG1vcmUgbW9kdWxhciBjb2RlIHRoYXQgaXMgZWFzaWVyIHRvIG1haW50YWluLgoKIyMjIE1hc3Mgc2hvb3RpbmdzOiBkaXNwbGF5IGhlbHAKCkl0IGlzIGFsd2F5cyB1c2VmdWwgdG8gcHJvdmlkZSB1c2VycyB3aXRoIG1vcmUgY29udGV4dCBhYm91dCB5b3VyIGFwcC4gT25lIHdheSB0byBkbyB0aGlzIGlzIGJ5IGFkZGluZyBhbiBBYm91dCBidXR0b24gdG8gdGhlIGFwcCBhbmQgZGlzcGxheSB0aGUgY29udGV4dCBhcyBhIG1vZGFsIGRpYWxvZy4KClRoaXMgaXMgZXhhY3RseSB3aGF0IHdlIHdpbGwgYmUgZG9pbmcgaW4gdGhpcyBleGVyY2lzZS4gCmBgYHtyfQp0ZXh0X2Fib3V0IDwtICJUaGlzIGRhdGEgd2FzIGNvbXBpbGVkIGJ5IE1vdGhlciBKb25lcywgbm9ucHJvZml0IGZvdW5kZWQgaW4gMTk3Ni4gT3JpZ2luYWxseSBjb3ZlcmluZyBjYXNlcyBmcm9tIDE5ODItMjAxMiwgdGhpcyBkYXRhYmFzZSBoYXMgc2luY2UgYmVlbiBleHBhbmRlZCBudW1lcm91cyB0aW1lcyB0byByZW1haW4gY3VycmVudC4iCmBgYApgYGB7cn0KdWkgPC0gYm9vdHN0cmFwUGFnZSgKICB0aGVtZSA9IHNoaW55dGhlbWVzOjpzaGlueXRoZW1lKCdzaW1wbGV4JyksCiAgbGVhZmxldDo6bGVhZmxldE91dHB1dCgnbWFwJywgd2lkdGggPSAnMTAwJScsIGhlaWdodCA9ICcxMDAlJyksCiAgYWJzb2x1dGVQYW5lbCh0b3AgPSAxMCwgcmlnaHQgPSAxMCwgaWQgPSAnY29udHJvbHMnLAogICAgc2xpZGVySW5wdXQoJ25iX2ZhdGFsaXRpZXMnLCAnTWluaW11bSBGYXRhbGl0aWVzJywgMSwgNDAsIDEwKSwKICAgIGRhdGVSYW5nZUlucHV0KAogICAgICAnZGF0ZV9yYW5nZScsICdTZWxlY3QgRGF0ZScsICIyMDEwLTAxLTAxIiwgIjIwMTktMTItMDEiCiAgICApLAogICAgIyBDT0RFIEJFTE9XOiBBZGQgYW4gYWN0aW9uIGJ1dHRvbiBuYW1lZCBzaG93X2Fib3V0CiAgICBhY3Rpb25CdXR0b24oJ3Nob3dfYWJvdXQnLCAnQWJvdXQnKQogICksCiAgdGFncyRzdHlsZSh0eXBlID0gInRleHQvY3NzIiwgIgogICAgaHRtbCwgYm9keSB7d2lkdGg6MTAwJTtoZWlnaHQ6MTAwJX0gICAgIAogICAgI2NvbnRyb2xze2JhY2tncm91bmQtY29sb3I6d2hpdGU7cGFkZGluZzoyMHB4O30KICAiKQopCnNlcnZlciA8LSBmdW5jdGlvbihpbnB1dCwgb3V0cHV0LCBzZXNzaW9uKSB7CiAgIyBDT0RFIEJFTE9XOiBVc2Ugb2JzZXJ2ZUV2ZW50IHRvIGRpc3BsYXkgYSBtb2RhbCBkaWFsb2cKICAjIHdpdGggdGhlIGhlbHAgdGV4dCBzdG9yZWQgaW4gdGV4dF9hYm91dC4KICBvYnNlcnZlRXZlbnQoaW5wdXQkc2hvd19hYm91dCwgewogIHNob3dNb2RhbChtb2RhbERpYWxvZyh0ZXh0X2Fib3V0LCB0aXRsZSA9ICdBYm91dCcpKQogIH0pCiAgCiAgb3V0cHV0JG1hcCA8LSBsZWFmbGV0OjpyZW5kZXJMZWFmbGV0KHsKICAgIG1hc3Nfc2hvb3RpbmdzICU+JSAKICAgICAgZmlsdGVyKAogICAgICAgIGRhdGUgPj0gaW5wdXQkZGF0ZV9yYW5nZVsxXSwKICAgICAgICBkYXRlIDw9IGlucHV0JGRhdGVfcmFuZ2VbMl0sCiAgICAgICAgZmF0YWxpdGllcyA+PSBpbnB1dCRuYl9mYXRhbGl0aWVzCiAgICAgICkgJT4lIAogICAgICBsZWFmbGV0KCkgJT4lIAogICAgICBzZXRWaWV3KCAtOTguNTgsIDM5LjgyLCB6b29tID0gNSkgJT4lIAogICAgICBhZGRUaWxlcygpICU+JSAKICAgICAgYWRkQ2lyY2xlTWFya2VycygKICAgICAgICBwb3B1cCA9IH4gc3VtbWFyeSwgcmFkaXVzID0gfiBzcXJ0KGZhdGFsaXRpZXMpKjMsCiAgICAgICAgZmlsbENvbG9yID0gJ3JlZCcsIGNvbG9yID0gJ3JlZCcsIHdlaWdodCA9IDEKICAgICAgKQogIH0pCn0KCnNoaW55QXBwKHVpLCBzZXJ2ZXIpCmBgYApNb2RhbCBkaWFsb2dzIGFyZSBhIGdyZWF0IHdheSB0byBwcm92aWRlIHVzZXJzIHdpdGggbW9yZSBjb250ZXh0IGFuZCBpbmZvcm1hdGlvbiBhYm91dCB5b3VyIGFwcCwgd2l0aG91dCBjbHV0dGVyaW5nIHRoZSB1c2VyIGludGVyZmFjZS4K