Prequisites

This lesson presumes that you are familiar with the contents of:

Introduction

Data transformation is a fundamental aspect of data analysis, enabling the conversion of raw data into a structured format suitable for analytics. While there are many ways to transform and prepare data, the dplyr package – an integral part of the Tidyverse – offers a suite of functions designed to simplify this process. In this lesson, we explore key dplyr functions, including filter(), arrange(), select(), mutate(), and summarize().

What is the Tidyverse?

The “Tidyverse” is a collection of R packages designed to work seamlessly together, adhering to a consistent philosophy of “tidy data.” It provides tools for a wide range of data-related tasks, from importing and cleaning data to transforming, visualizing, and modeling it.

The Tidyverse was developed by Hadley Wickham (CTO at R Studio), a prominent statistician and software developer in the R community. Wickham is well-known for creating many influential R packages and shaping the modern landscape of data analysis in R. His work has revolutionized how analysts and researchers interact with data, emphasizing simplicity, consistency, and the concept of “tidy data.”

Key features of the Tidyverse include:

  • Tidy Data Paradigm: Data is organized so that each variable forms a column, each observation forms a row, and each type of observation unit forms a table.
  • Consistency: All packages in the Tidyverse use a consistent syntax and grammar, making them intuitive to learn and use.
  • Ease of Use: The Tidyverse simplifies complex operations, enabling analysts to focus more on insights rather than the mechanics of data handling.

The Tidyverse includes several key packages, including:

  • ggplot2: For data visualization.
  • dplyr: For data manipulation.
  • tidyr: For tidying and reshaping data.
  • readr: For reading data files.
  • tibble: For improved data frame functionality.
  • purrr: For functional programming with lists.

What is dplyr?

dplyr is a core package within the Tidyverse, specifically focused on data manipulation. It provides a grammar of data manipulation, making it easy to perform tasks such as filtering, sorting, summarizing, and transforming data.

Key operations in dplyr, often called the “verbs” of data manipulation, include:

  • filter(): Select rows based on conditions.
  • select(): Choose specific columns.
  • mutate(): Add or modify columns.
  • arrange(): Sort rows.
  • summarize(): Calculate summary statistics.
  • group_by(): Group data for grouped operations.

These functions are designed to be intuitive, readable, and chainable using the %>% operator (pipe), which allows you to express sequences of transformations in a clear and linear fashion. They present an alternative to using SQL for data filtering, summarizing, and selection.

The dplyr Package

dplyr is part of the Tidyverse, meaning it shares the same principles, syntax, and goals as other Tidyverse packages. It serves as the data manipulation backbone for many data analysis workflows in the Tidyverse. In this section, we will explore key functions in detail. To use the dplyr package, you must install it and then load it:

suppressPackageStartupMessages(library(dplyr))

The call to suppressPackageStartupMessages() is necessary to “suppress” all warnings and messages; if not done, those would end up in your knitted notebook.

The dplyr package uses a modified (and simplified) tabular structure called a tibble that replaces dataframes in Tidyverse.

To load all packages of the Tidyverse, you can simply load tidyverse.

library(tidyverse)

tibbles

Tibbles are a modernized re-imagination of data frames in R, introduced by the tibble package, which is part of the Tidyverse. The goal is to address some of the limitations and frustrations associated with base R data frames by providing a more “user-friendly” and consistent experience for data manipulation.

Tibbles provide some key benefits over dataframes, including:

  1. Simplified Printing
    Tibbles display only the first 10 rows and as many columns as fit the screen, avoiding overwhelming output for large datasets. For example:

    library(tibble)
    
    tibble_example <- tibble(
      x = 1:100,
      y = rnorm(100)
    )
    tibble_example
    ## # A tibble: 100 × 2
    ##        x       y
    ##    <int>   <dbl>
    ##  1     1  1.24  
    ##  2     2  0.0969
    ##  3     3 -0.403 
    ##  4     4  0.946 
    ##  5     5  0.323 
    ##  6     6  0.0385
    ##  7     7 -0.582 
    ##  8     8  0.416 
    ##  9     9  0.636 
    ## 10    10 -0.462 
    ## # ℹ 90 more rows

    Output will show:

    # A tibble: 100 × 2
          x       y
      <int>   <dbl>
    1     1  -0.276
    2     2   1.003
    3     3   0.735
    # … with 97 more rows
  2. Preservation of Data Types
    Unlike base R data frames, tibbles never automatically convert strings into factors or perform other unexpected type coercion. For example:

    df <- data.frame(name = "Alice", age = 25)
    class(df$name)  # Output: factor
    ## [1] "character"
    tb <- tibble(name = "Alice", age = 25)
    class(tb$name)  # Output: character
    ## [1] "character"
  3. Column Access by Name
    Tibbles enforce stricter rules for column access:

    • Accessing columns with $ or [[ requires exact matches to column names.
    • Partial matching of names is disallowed, reducing errors.
    tb <- tibble(name = "Alice", age = 25)
    tb$n      # Error: Unknown column 'n'
    ## Warning: Unknown or uninitialised column: `n`.
    ## NULL
  4. Support for Non-Syntactic Names
    Tibbles allow column names that aren’t syntactically valid in R, such as names containing spaces or special characters. These must be referenced using backticks:

    tb <- tibble(`student name` = "Alice", age = 25)
    tb$`student name`  # Access column
    ## [1] "Alice"
  5. Creation Flexibility
    Tibbles can be created using the tibble() function or the tribble() function for row-wise data entry. For example:

    # Using tibble()
    tb <- tibble(name = c("Alice", "Bob"), age = c(25, 30))
    
    # Using tribble()
    tb <- tribble(
      ~name, ~age,
      "Alice", 25,
      "Bob", 30
    )

Tibbles are especially useful when working with the Tidyverse because all Tidyverse packages are designed to work optimally with tibbles. They are recommended whenever you want a more predictable and consistent data structure for data manipulation and analysis.

All of the functions that expect a dataframe also work with tibbles.

Reading CSV Files

To load a CSV file into a tibble rather than a dataframe, use read_csv() instead of read.csv(). Using read_csv() over Base R’s read.csv() has several benefits (aside from returning a tibble).

  1. Better Defaults:
    • Strings are not automatically converted to factors.
    • Column types are automatically guessed and more accurately handled.
  2. Faster Performance:
    • read_csv() is (a bit) faster for large files.
  3. Tidyverse Integration:
    • Returns a tibble, which integrates seamlessly with other Tidyverse packages.
  4. Informative Output:
    • Prints metadata about the data during import (e.g., column types).

The function read_csv() is part of the readr package, so either install and load that package of the full tidyverse package set.

Consider a dataset students containing information on students’ names, majors, and GPA’s (from the CSV file students.csv):

library(readr)
students <- read_csv("students.csv")
## Rows: 7 Columns: 3
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr (2): name, major
## dbl (1): gpa
## 
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.

The function read_csv() displays information about the structure of the CSV. If you wish to “suppress” this information, then use:

library(readr)
students <- read_csv("students.csv", show_col_types = FALSE)

Filtering Rows with filter()

The filter() function extracts rows from a data frame that meet specified logical conditions. To select students with a GPA of 3.5 or better, you can use the following dplyr pipe:

filtered_students <- students %>%
  filter(gpa >= 3.50)

This returns rows where the gpa column is greater than or equal to 3.50. To display the result, you can use the print() function:

print(filtered_students)
## # A tibble: 4 × 3
##   name              major   gpa
##   <chr>             <chr> <dbl>
## 1 Alice Jones       CS     3.82
## 2 Liu Chen          CS     3.9 
## 3 Karl Langenfelder Bus    3.68
## 4 Susan Myers       Arch   3.76

Arranging Rows with arrange()

The arrange() function orders rows based on the values of specified columns. To sort the students dataset by gpa in descending order:

arranged_students <- students %>%
  arrange(desc(gpa))

print(arranged_students)
## # A tibble: 7 × 3
##   name              major   gpa
##   <chr>             <chr> <dbl>
## 1 Liu Chen          CS     3.9 
## 2 Alice Jones       CS     3.82
## 3 Susan Myers       Arch   3.76
## 4 Karl Langenfelder Bus    3.68
## 5 Cameron Wu        IE     3.45
## 6 Sandeep Patel     Econ   3.4 
## 7 Gert Wilder       Math   2.84

This sorts the students from the highest to the lowest grade.

Selecting Columns with select()

The select() function allows for the extraction of specific columns from a data frame. To create a new dataset with only the name and gpa columns:

selected_students <- students %>%
  select(name, gpa)

print(selected_students)
## # A tibble: 7 × 2
##   name                gpa
##   <chr>             <dbl>
## 1 Alice Jones        3.82
## 2 Sandeep Patel      3.4 
## 3 Gert Wilder        2.84
## 4 Liu Chen           3.9 
## 5 Cameron Wu         3.45
## 6 Karl Langenfelder  3.68
## 7 Susan Myers        3.76

This results in a dataset containing only the name and gpa columns. From a relational perspective, this is a “projection” operation.

Adding or Modifying Columns with mutate()

The mutate() function is used to add new columns or modify existing ones. To add a column honors that categorizes students based on their GPA:

mutated_students <- students %>%
  mutate(grade_category = case_when(
    gpa >= 3.9 ~ "Excellent",
    gpa >= 3.5 ~ "Good",
    gpa >= 3.0 ~ "Passing",
    TRUE ~ "Below Passing"
  ))

print(mutated_students)
## # A tibble: 7 × 4
##   name              major   gpa grade_category
##   <chr>             <chr> <dbl> <chr>         
## 1 Alice Jones       CS     3.82 Good          
## 2 Sandeep Patel     Econ   3.4  Passing       
## 3 Gert Wilder       Math   2.84 Below Passing 
## 4 Liu Chen          CS     3.9  Excellent     
## 5 Cameron Wu        IE     3.45 Passing       
## 6 Karl Langenfelder Bus    3.68 Good          
## 7 Susan Myers       Arch   3.76 Good

This adds a honors column with values for various ranges of GPA.

Summarizing Data with summarize()

The summarize() function computes summary statistics for a data frame. For example, to calculate the mean (average) GPA of the students:

average_gpa <- students %>%
  summarize(mean_gpa = mean(gpa))

print(average_gpa)
## # A tibble: 1 × 1
##   mean_gpa
##      <dbl>
## 1     3.55

This computes the mean of the gpa column, providing the average GPA

Grouped Operations with group_by() and summarize()

For more complex analyses, group_by() in conjunction with summarize() enables grouped operations. To calculate the average GPA by major:

avg_by_major <- students %>%
  group_by(major) %>%
  summarize(avg_gpa = mean(gpa))

print(avg_by_major)
## # A tibble: 6 × 2
##   major avg_gpa
##   <chr>   <dbl>
## 1 Arch     3.76
## 2 Bus      3.68
## 3 CS       3.86
## 4 Econ     3.4 
## 5 IE       3.45
## 6 Math     2.84

Whenever there’s a “by” or “per” it means that we want to computer some “fact” for grouped data.

These examples demonstrate the versatility of dplyr in performing data transformations, facilitating efficient data manipulation for complex analyses using an alternative to SQL.

Summary

The Tidyverse and dplyr are integral parts of the modern data analysis ecosystem in R, designed to simplify and streamline data manipulation and visualization tasks.


Files & Resources

All Files for Lesson 6.107

References

  1. Wickham, H., & Grolemund, G. (2017). R for Data Science: Import, tidy, transform, visualize, and model data. O’Reilly Media. Retrieved from https://r4ds.hadley.nz

  2. Wickham, H. (2014). Tidy data. Journal of Statistical Software, 59(10), 1–23. https://doi.org/10.18637/jss.v059.i10

Portions of this lesson were created with the assistance of ChatGPT 4o.

Errata

Let us know.

LS0tCnRpdGxlOiAiRGF0YSBNYW5pcHVsYXRpb24gd2l0aCA8Yj5kcGx5cjwvYj4iCnBhcmFtczoKICB0eXBlIDogbGVzc29uCiAgY2F0ZWdvcnk6IDYKICBudW1iZXI6IDEwNwogIHRpbWU6IDQ1CiAgbGV2ZWw6IGJlZ2lubmVyCiAgdGFnczogInIsZHBseXIsZmlsdGVyaW5nLHN1bW1hcml6aW5nLHNlbGVjdGluZyIKICBkZXNjcmlwdGlvbjogIkRlbW9uc3RyYXRlcyB0aGUgdXNlIG9mIHRoZSA8Yj5kcGx5cjwvYj4gcGFja2FnZSBvZiB0aGUgVGlkeXZlcnNlCiAgICAgICAgICAgICAgICBmb3IgZGF0YSBtYW5pcHVsYXRpb24sIHN1bW1hcml6YXRpb24sIGFuZCBmaWx0ZXJpbmcuIgpkYXRlOiAiPHNtYWxsPmByIFN5cy5EYXRlKClgPC9zbWFsbD4iCmF1dGhvcjogIjxzbWFsbD5NYXJ0aW4gU2NoZWRsYmF1ZXI8L3NtYWxsPiIKZW1haWw6ICJtLnNjaGVkbGJhdWVyQG5ldS5lZHUiCmFmZmlsaXRhdGlvbjogIk5vcnRoZWFzdGVybiBVbml2ZXJzaXR5IgpvdXRwdXQ6IAogIGJvb2tkb3duOjpodG1sX2RvY3VtZW50MjoKICAgIHRvYzogdHJ1ZQogICAgdG9jX2Zsb2F0OiB0cnVlCiAgICBjb2xsYXBzZWQ6IGZhbHNlCiAgICBudW1iZXJfc2VjdGlvbnM6IGZhbHNlCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlCiAgICB0aGVtZTogc3BhY2VsYWIKICAgIGhpZ2hsaWdodDogdGFuZ28KLS0tCgotLS0KdGl0bGU6ICI8c21hbGw+YHIgcGFyYW1zJGNhdGVnb3J5YC5gciBwYXJhbXMkbnVtYmVyYDwvc21hbGw+PGJyLz48c3BhbiBzdHlsZT0nY29sb3I6ICMyRTQwNTM7IGZvbnQtc2l6ZTogMC45ZW0nPmByIHJtYXJrZG93bjo6bWV0YWRhdGEkdGl0bGVgPC9zcGFuPiIKLS0tCgpgYGB7ciBjb2RlPXhmdW46OnJlYWRfdXRmOChwYXN0ZTAoaGVyZTo6aGVyZSgpLCcvUi9faW5zZXJ0MkRCLlInKSksIGluY2x1ZGUgPSBGQUxTRX0KYGBgCgojIyBQcmVxdWlzaXRlcwoKVGhpcyBsZXNzb24gcHJlc3VtZXMgdGhhdCB5b3UgYXJlIGZhbWlsaWFyIHdpdGggdGhlIGNvbnRlbnRzIG9mOgoKLSAgIFs2LjEwNCAtLSBRdWljayBHdWlkZSB0byBSIEZvciBQcm9ncmFtbWVyc10oaHR0cDovL2FydGlmaWNpdW0udXMvbGVzc29ucy8wNi5yL2wtNi0xMDQtcjRwcm9ncy9sLTYtMTA0Lmh0bWwpCgojIyBJbnRyb2R1Y3Rpb24KCkRhdGEgdHJhbnNmb3JtYXRpb24gaXMgYSBmdW5kYW1lbnRhbCBhc3BlY3Qgb2YgZGF0YSBhbmFseXNpcywgZW5hYmxpbmcgdGhlIGNvbnZlcnNpb24gb2YgcmF3IGRhdGEgaW50byBhIHN0cnVjdHVyZWQgZm9ybWF0IHN1aXRhYmxlIGZvciBhbmFseXRpY3MuIFdoaWxlIHRoZXJlIGFyZSBtYW55IHdheXMgdG8gdHJhbnNmb3JtIGFuZCBwcmVwYXJlIGRhdGEsIHRoZSBgZHBseXJgIHBhY2thZ2UgLS0gYW4gaW50ZWdyYWwgcGFydCBvZiB0aGUgVGlkeXZlcnNlIC0tIG9mZmVycyBhIHN1aXRlIG9mIGZ1bmN0aW9ucyBkZXNpZ25lZCB0byBzaW1wbGlmeSB0aGlzIHByb2Nlc3MuIEluIHRoaXMgbGVzc29uLCB3ZSBleHBsb3JlIGtleSBgZHBseXJgIGZ1bmN0aW9ucywgaW5jbHVkaW5nIGBmaWx0ZXIoKWAsIGBhcnJhbmdlKClgLCBgc2VsZWN0KClgLCBgbXV0YXRlKClgLCBhbmQgYHN1bW1hcml6ZSgpYC4KCiMjIyAqKldoYXQgaXMgdGhlIFRpZHl2ZXJzZT8qKgoKVGhlICJUaWR5dmVyc2UiIGlzIGEgY29sbGVjdGlvbiBvZiBSIHBhY2thZ2VzIGRlc2lnbmVkIHRvIHdvcmsgc2VhbWxlc3NseSB0b2dldGhlciwgYWRoZXJpbmcgdG8gYSBjb25zaXN0ZW50IHBoaWxvc29waHkgb2YgInRpZHkgZGF0YS4iIEl0IHByb3ZpZGVzIHRvb2xzIGZvciBhIHdpZGUgcmFuZ2Ugb2YgZGF0YS1yZWxhdGVkIHRhc2tzLCBmcm9tIGltcG9ydGluZyBhbmQgY2xlYW5pbmcgZGF0YSB0byB0cmFuc2Zvcm1pbmcsIHZpc3VhbGl6aW5nLCBhbmQgbW9kZWxpbmcgaXQuCgpUaGUgVGlkeXZlcnNlIHdhcyBkZXZlbG9wZWQgYnkgSGFkbGV5IFdpY2toYW0gKENUTyBhdCBSIFN0dWRpbyksIGEgcHJvbWluZW50IHN0YXRpc3RpY2lhbiBhbmQgc29mdHdhcmUgZGV2ZWxvcGVyIGluIHRoZSBSIGNvbW11bml0eS4gV2lja2hhbSBpcyB3ZWxsLWtub3duIGZvciBjcmVhdGluZyBtYW55IGluZmx1ZW50aWFsIFIgcGFja2FnZXMgYW5kIHNoYXBpbmcgdGhlIG1vZGVybiBsYW5kc2NhcGUgb2YgZGF0YSBhbmFseXNpcyBpbiBSLiBIaXMgd29yayBoYXMgcmV2b2x1dGlvbml6ZWQgaG93IGFuYWx5c3RzIGFuZCByZXNlYXJjaGVycyBpbnRlcmFjdCB3aXRoIGRhdGEsIGVtcGhhc2l6aW5nIHNpbXBsaWNpdHksIGNvbnNpc3RlbmN5LCBhbmQgdGhlIGNvbmNlcHQgb2YgInRpZHkgZGF0YS4iCgpLZXkgZmVhdHVyZXMgb2YgdGhlIFRpZHl2ZXJzZSBpbmNsdWRlOgoKLSAgICoqVGlkeSBEYXRhIFBhcmFkaWdtOioqIERhdGEgaXMgb3JnYW5pemVkIHNvIHRoYXQgZWFjaCB2YXJpYWJsZSBmb3JtcyBhIGNvbHVtbiwgZWFjaCBvYnNlcnZhdGlvbiBmb3JtcyBhIHJvdywgYW5kIGVhY2ggdHlwZSBvZiBvYnNlcnZhdGlvbiB1bml0IGZvcm1zIGEgdGFibGUuCi0gICAqKkNvbnNpc3RlbmN5OioqIEFsbCBwYWNrYWdlcyBpbiB0aGUgVGlkeXZlcnNlIHVzZSBhIGNvbnNpc3RlbnQgc3ludGF4IGFuZCBncmFtbWFyLCBtYWtpbmcgdGhlbSBpbnR1aXRpdmUgdG8gbGVhcm4gYW5kIHVzZS4KLSAgICoqRWFzZSBvZiBVc2U6KiogVGhlIFRpZHl2ZXJzZSBzaW1wbGlmaWVzIGNvbXBsZXggb3BlcmF0aW9ucywgZW5hYmxpbmcgYW5hbHlzdHMgdG8gZm9jdXMgbW9yZSBvbiBpbnNpZ2h0cyByYXRoZXIgdGhhbiB0aGUgbWVjaGFuaWNzIG9mIGRhdGEgaGFuZGxpbmcuCgpUaGUgVGlkeXZlcnNlIGluY2x1ZGVzIHNldmVyYWwga2V5IHBhY2thZ2VzLCBpbmNsdWRpbmc6CgotICAgKipnZ3Bsb3QyOioqIEZvciBkYXRhIHZpc3VhbGl6YXRpb24uCi0gICAqKmRwbHlyOioqIEZvciBkYXRhIG1hbmlwdWxhdGlvbi4KLSAgICoqdGlkeXI6KiogRm9yIHRpZHlpbmcgYW5kIHJlc2hhcGluZyBkYXRhLgotICAgKipyZWFkcjoqKiBGb3IgcmVhZGluZyBkYXRhIGZpbGVzLgotICAgKip0aWJibGU6KiogRm9yIGltcHJvdmVkIGRhdGEgZnJhbWUgZnVuY3Rpb25hbGl0eS4KLSAgICoqcHVycnI6KiogRm9yIGZ1bmN0aW9uYWwgcHJvZ3JhbW1pbmcgd2l0aCBsaXN0cy4KCiMjIyAqKldoYXQgaXMgZHBseXI/KioKCioqZHBseXIqKiBpcyBhIGNvcmUgcGFja2FnZSB3aXRoaW4gdGhlIFRpZHl2ZXJzZSwgc3BlY2lmaWNhbGx5IGZvY3VzZWQgb24gZGF0YSBtYW5pcHVsYXRpb24uIEl0IHByb3ZpZGVzIGEgZ3JhbW1hciBvZiBkYXRhIG1hbmlwdWxhdGlvbiwgbWFraW5nIGl0IGVhc3kgdG8gcGVyZm9ybSB0YXNrcyBzdWNoIGFzIGZpbHRlcmluZywgc29ydGluZywgc3VtbWFyaXppbmcsIGFuZCB0cmFuc2Zvcm1pbmcgZGF0YS4KCktleSBvcGVyYXRpb25zIGluICoqZHBseXIqKiwgb2Z0ZW4gY2FsbGVkIHRoZSAidmVyYnMiIG9mIGRhdGEgbWFuaXB1bGF0aW9uLCBpbmNsdWRlOgoKLSAgICoqYGZpbHRlcigpYCoqOiBTZWxlY3Qgcm93cyBiYXNlZCBvbiBjb25kaXRpb25zLgotICAgKipgc2VsZWN0KClgKio6IENob29zZSBzcGVjaWZpYyBjb2x1bW5zLgotICAgKipgbXV0YXRlKClgKio6IEFkZCBvciBtb2RpZnkgY29sdW1ucy4KLSAgICoqYGFycmFuZ2UoKWAqKjogU29ydCByb3dzLgotICAgKipgc3VtbWFyaXplKClgKio6IENhbGN1bGF0ZSBzdW1tYXJ5IHN0YXRpc3RpY3MuCi0gICAqKmBncm91cF9ieSgpYCoqOiBHcm91cCBkYXRhIGZvciBncm91cGVkIG9wZXJhdGlvbnMuCgpUaGVzZSBmdW5jdGlvbnMgYXJlIGRlc2lnbmVkIHRvIGJlIGludHVpdGl2ZSwgcmVhZGFibGUsIGFuZCBjaGFpbmFibGUgdXNpbmcgdGhlIGAlPiVgIG9wZXJhdG9yIChwaXBlKSwgd2hpY2ggYWxsb3dzIHlvdSB0byBleHByZXNzIHNlcXVlbmNlcyBvZiB0cmFuc2Zvcm1hdGlvbnMgaW4gYSBjbGVhciBhbmQgbGluZWFyIGZhc2hpb24uIFRoZXkgcHJlc2VudCBhbiBhbHRlcm5hdGl2ZSB0byB1c2luZyBTUUwgZm9yIGRhdGEgZmlsdGVyaW5nLCBzdW1tYXJpemluZywgYW5kIHNlbGVjdGlvbi4KCiMjIFRoZSAqKmRwbHlyKiogUGFja2FnZQoKKipkcGx5cioqIGlzIHBhcnQgb2YgdGhlIFRpZHl2ZXJzZSwgbWVhbmluZyBpdCBzaGFyZXMgdGhlIHNhbWUgcHJpbmNpcGxlcywgc3ludGF4LCBhbmQgZ29hbHMgYXMgb3RoZXIgVGlkeXZlcnNlIHBhY2thZ2VzLiBJdCBzZXJ2ZXMgYXMgdGhlICoqZGF0YSBtYW5pcHVsYXRpb24gYmFja2JvbmUqKiBmb3IgbWFueSBkYXRhIGFuYWx5c2lzIHdvcmtmbG93cyBpbiB0aGUgVGlkeXZlcnNlLiBJbiB0aGlzIHNlY3Rpb24sIHdlIHdpbGwgZXhwbG9yZSBrZXkgZnVuY3Rpb25zIGluIGRldGFpbC4gVG8gdXNlIHRoZSAqKmRwbHlyKiogcGFja2FnZSwgeW91IG11c3QgaW5zdGFsbCBpdCBhbmQgdGhlbiBsb2FkIGl0OgoKYGBge3Igd2FybmluZz1GQUxTRX0Kc3VwcHJlc3NQYWNrYWdlU3RhcnR1cE1lc3NhZ2VzKGxpYnJhcnkoZHBseXIpKQpgYGAKClRoZSBjYWxsIHRvIGBzdXBwcmVzc1BhY2thZ2VTdGFydHVwTWVzc2FnZXMoKWAgaXMgbmVjZXNzYXJ5IHRvICJzdXBwcmVzcyIgYWxsIHdhcm5pbmdzIGFuZCBtZXNzYWdlczsgaWYgbm90IGRvbmUsIHRob3NlIHdvdWxkIGVuZCB1cCBpbiB5b3VyIGtuaXR0ZWQgbm90ZWJvb2suCgpUaGUgKipkcGx5cioqIHBhY2thZ2UgdXNlcyBhIG1vZGlmaWVkIChhbmQgc2ltcGxpZmllZCkgdGFidWxhciBzdHJ1Y3R1cmUgY2FsbGVkIGEgKnRpYmJsZSogdGhhdCByZXBsYWNlcyBkYXRhZnJhbWVzIGluIFRpZHl2ZXJzZS4KClRvIGxvYWQgYWxsIHBhY2thZ2VzIG9mIHRoZSBUaWR5dmVyc2UsIHlvdSBjYW4gc2ltcGx5IGxvYWQgKip0aWR5dmVyc2UqKi4KCmBgYHtyIHdhcm5pbmc9RkFMU0UsIGV2YWw9RkFMU0V9CmxpYnJhcnkodGlkeXZlcnNlKQpgYGAKCiMjIyB0aWJibGVzCgoqKlRpYmJsZXMqKiBhcmUgYSBtb2Rlcm5pemVkIHJlLWltYWdpbmF0aW9uIG9mIGRhdGEgZnJhbWVzIGluIFIsIGludHJvZHVjZWQgYnkgdGhlICoqdGliYmxlKiogcGFja2FnZSwgd2hpY2ggaXMgcGFydCBvZiB0aGUgKipUaWR5dmVyc2UqKi4gVGhlIGdvYWwgaXMgdG8gYWRkcmVzcyBzb21lIG9mIHRoZSBsaW1pdGF0aW9ucyBhbmQgZnJ1c3RyYXRpb25zIGFzc29jaWF0ZWQgd2l0aCBiYXNlIFIgZGF0YSBmcmFtZXMgYnkgcHJvdmlkaW5nIGEgbW9yZSAidXNlci1mcmllbmRseSIgYW5kIGNvbnNpc3RlbnQgZXhwZXJpZW5jZSBmb3IgZGF0YSBtYW5pcHVsYXRpb24uCgpUaWJibGVzIHByb3ZpZGUgc29tZSBrZXkgYmVuZWZpdHMgb3ZlciBkYXRhZnJhbWVzLCBpbmNsdWRpbmc6CgoxLiAgKipTaW1wbGlmaWVkIFByaW50aW5nKipcCiAgICBUaWJibGVzIGRpc3BsYXkgb25seSB0aGUgZmlyc3QgMTAgcm93cyBhbmQgYXMgbWFueSBjb2x1bW5zIGFzIGZpdCB0aGUgc2NyZWVuLCBhdm9pZGluZyBvdmVyd2hlbG1pbmcgb3V0cHV0IGZvciBsYXJnZSBkYXRhc2V0cy4gRm9yIGV4YW1wbGU6CgogICAgYGBge3J9CiAgICBsaWJyYXJ5KHRpYmJsZSkKCiAgICB0aWJibGVfZXhhbXBsZSA8LSB0aWJibGUoCiAgICAgIHggPSAxOjEwMCwKICAgICAgeSA9IHJub3JtKDEwMCkKICAgICkKICAgIHRpYmJsZV9leGFtcGxlCiAgICBgYGAKCiAgICBPdXRwdXQgd2lsbCBzaG93OgoKICAgIGBgYCAgICAgICAgIAogICAgIyBBIHRpYmJsZTogMTAwIMOXIDIKICAgICAgICAgIHggICAgICAgeQogICAgICA8aW50PiAgIDxkYmw+CiAgICAxICAgICAxICAtMC4yNzYKICAgIDIgICAgIDIgICAxLjAwMwogICAgMyAgICAgMyAgIDAuNzM1CiAgICAjIOKApiB3aXRoIDk3IG1vcmUgcm93cwogICAgYGBgCgoyLiAgKipQcmVzZXJ2YXRpb24gb2YgRGF0YSBUeXBlcyoqXAogICAgVW5saWtlIGJhc2UgUiBkYXRhIGZyYW1lcywgdGliYmxlcyBuZXZlciBhdXRvbWF0aWNhbGx5IGNvbnZlcnQgc3RyaW5ncyBpbnRvIGZhY3RvcnMgb3IgcGVyZm9ybSBvdGhlciB1bmV4cGVjdGVkIHR5cGUgY29lcmNpb24uIEZvciBleGFtcGxlOgoKICAgIGBgYHtyfQogICAgZGYgPC0gZGF0YS5mcmFtZShuYW1lID0gIkFsaWNlIiwgYWdlID0gMjUpCiAgICBjbGFzcyhkZiRuYW1lKSAgIyBPdXRwdXQ6IGZhY3RvcgoKICAgIHRiIDwtIHRpYmJsZShuYW1lID0gIkFsaWNlIiwgYWdlID0gMjUpCiAgICBjbGFzcyh0YiRuYW1lKSAgIyBPdXRwdXQ6IGNoYXJhY3RlcgogICAgYGBgCgozLiAgKipDb2x1bW4gQWNjZXNzIGJ5IE5hbWUqKlwKICAgIFRpYmJsZXMgZW5mb3JjZSBzdHJpY3RlciBydWxlcyBmb3IgY29sdW1uIGFjY2VzczoKCiAgICAtICAgQWNjZXNzaW5nIGNvbHVtbnMgd2l0aCBgJGAgb3IgYFtbYCByZXF1aXJlcyBleGFjdCBtYXRjaGVzIHRvIGNvbHVtbiBuYW1lcy4KICAgIC0gICBQYXJ0aWFsIG1hdGNoaW5nIG9mIG5hbWVzIGlzIGRpc2FsbG93ZWQsIHJlZHVjaW5nIGVycm9ycy4KCiAgICBgYGB7cn0KICAgIHRiIDwtIHRpYmJsZShuYW1lID0gIkFsaWNlIiwgYWdlID0gMjUpCiAgICB0YiRuICAgICAgIyBFcnJvcjogVW5rbm93biBjb2x1bW4gJ24nCiAgICBgYGAKCjQuICAqKlN1cHBvcnQgZm9yIE5vbi1TeW50YWN0aWMgTmFtZXMqKlwKICAgIFRpYmJsZXMgYWxsb3cgY29sdW1uIG5hbWVzIHRoYXQgYXJlbid0IHN5bnRhY3RpY2FsbHkgdmFsaWQgaW4gUiwgc3VjaCBhcyBuYW1lcyBjb250YWluaW5nIHNwYWNlcyBvciBzcGVjaWFsIGNoYXJhY3RlcnMuIFRoZXNlIG11c3QgYmUgcmVmZXJlbmNlZCB1c2luZyBiYWNrdGlja3M6CgogICAgYGBge3J9CiAgICB0YiA8LSB0aWJibGUoYHN0dWRlbnQgbmFtZWAgPSAiQWxpY2UiLCBhZ2UgPSAyNSkKICAgIHRiJGBzdHVkZW50IG5hbWVgICAjIEFjY2VzcyBjb2x1bW4KICAgIGBgYAoKNS4gICoqQ3JlYXRpb24gRmxleGliaWxpdHkqKlwKICAgIFRpYmJsZXMgY2FuIGJlIGNyZWF0ZWQgdXNpbmcgdGhlIGB0aWJibGUoKWAgZnVuY3Rpb24gb3IgdGhlIGB0cmliYmxlKClgIGZ1bmN0aW9uIGZvciByb3ctd2lzZSBkYXRhIGVudHJ5LiBGb3IgZXhhbXBsZToKCiAgICBgYGB7cn0KICAgICMgVXNpbmcgdGliYmxlKCkKICAgIHRiIDwtIHRpYmJsZShuYW1lID0gYygiQWxpY2UiLCAiQm9iIiksIGFnZSA9IGMoMjUsIDMwKSkKCiAgICAjIFVzaW5nIHRyaWJibGUoKQogICAgdGIgPC0gdHJpYmJsZSgKICAgICAgfm5hbWUsIH5hZ2UsCiAgICAgICJBbGljZSIsIDI1LAogICAgICAiQm9iIiwgMzAKICAgICkKICAgIGBgYAoKVGliYmxlcyBhcmUgZXNwZWNpYWxseSB1c2VmdWwgd2hlbiB3b3JraW5nIHdpdGggdGhlIFRpZHl2ZXJzZSBiZWNhdXNlIGFsbCBUaWR5dmVyc2UgcGFja2FnZXMgYXJlIGRlc2lnbmVkIHRvIHdvcmsgb3B0aW1hbGx5IHdpdGggdGliYmxlcy4gVGhleSBhcmUgcmVjb21tZW5kZWQgd2hlbmV2ZXIgeW91IHdhbnQgYSBtb3JlIHByZWRpY3RhYmxlIGFuZCBjb25zaXN0ZW50IGRhdGEgc3RydWN0dXJlIGZvciBkYXRhIG1hbmlwdWxhdGlvbiBhbmQgYW5hbHlzaXMuCgpBbGwgb2YgdGhlIGZ1bmN0aW9ucyB0aGF0IGV4cGVjdCBhIGRhdGFmcmFtZSBhbHNvIHdvcmsgd2l0aCB0aWJibGVzLgoKIyMjIFJlYWRpbmcgQ1NWIEZpbGVzCgpUbyBsb2FkIGEgQ1NWIGZpbGUgaW50byBhIHRpYmJsZSByYXRoZXIgdGhhbiBhIGRhdGFmcmFtZSwgdXNlIGByZWFkX2NzdigpYCBpbnN0ZWFkIG9mIGByZWFkLmNzdigpYC4gVXNpbmcgYHJlYWRfY3N2KClgIG92ZXIgQmFzZSBSJ3MgYHJlYWQuY3N2KClgIGhhcyBzZXZlcmFsIGJlbmVmaXRzIChhc2lkZSBmcm9tIHJldHVybmluZyBhIHRpYmJsZSkuCgoxLiAgKipCZXR0ZXIgRGVmYXVsdHMqKjoKICAgIC0gICBTdHJpbmdzIGFyZSBub3QgYXV0b21hdGljYWxseSBjb252ZXJ0ZWQgdG8gZmFjdG9ycy4KICAgIC0gICBDb2x1bW4gdHlwZXMgYXJlIGF1dG9tYXRpY2FsbHkgZ3Vlc3NlZCBhbmQgbW9yZSBhY2N1cmF0ZWx5IGhhbmRsZWQuCjIuICAqKkZhc3RlciBQZXJmb3JtYW5jZSoqOgogICAgLSAgIGByZWFkX2NzdigpYCBpcyAoYSBiaXQpIGZhc3RlciBmb3IgbGFyZ2UgZmlsZXMuCjMuICAqKlRpZHl2ZXJzZSBJbnRlZ3JhdGlvbioqOgogICAgLSAgIFJldHVybnMgYSB0aWJibGUsIHdoaWNoIGludGVncmF0ZXMgc2VhbWxlc3NseSB3aXRoIG90aGVyIFRpZHl2ZXJzZSBwYWNrYWdlcy4KNC4gICoqSW5mb3JtYXRpdmUgT3V0cHV0Kio6CiAgICAtICAgUHJpbnRzIG1ldGFkYXRhIGFib3V0IHRoZSBkYXRhIGR1cmluZyBpbXBvcnQgKCplLmcuKiwgY29sdW1uIHR5cGVzKS4KClRoZSBmdW5jdGlvbiBgcmVhZF9jc3YoKWAgaXMgcGFydCBvZiB0aGUgKipyZWFkcioqIHBhY2thZ2UsIHNvIGVpdGhlciBpbnN0YWxsIGFuZCBsb2FkIHRoYXQgcGFja2FnZSBvZiB0aGUgZnVsbCAqKnRpZHl2ZXJzZSoqIHBhY2thZ2Ugc2V0LgoKQ29uc2lkZXIgYSBkYXRhc2V0IGBzdHVkZW50c2AgY29udGFpbmluZyBpbmZvcm1hdGlvbiBvbiBzdHVkZW50cycgbmFtZXMsIG1ham9ycywgYW5kIEdQQSdzIChmcm9tIHRoZSBDU1YgZmlsZSBbc3R1ZGVudHMuY3N2XShzdHVkZW50cy5jc3YpKToKCmBgYHtyfQpsaWJyYXJ5KHJlYWRyKQpzdHVkZW50cyA8LSByZWFkX2Nzdigic3R1ZGVudHMuY3N2IikKYGBgCgpUaGUgZnVuY3Rpb24gYHJlYWRfY3N2KClgIGRpc3BsYXlzIGluZm9ybWF0aW9uIGFib3V0IHRoZSBzdHJ1Y3R1cmUgb2YgdGhlIENTVi4gSWYgeW91IHdpc2ggdG8gInN1cHByZXNzIiB0aGlzIGluZm9ybWF0aW9uLCB0aGVuIHVzZToKCmBgYHtyfQpsaWJyYXJ5KHJlYWRyKQpzdHVkZW50cyA8LSByZWFkX2Nzdigic3R1ZGVudHMuY3N2Iiwgc2hvd19jb2xfdHlwZXMgPSBGQUxTRSkKYGBgCgojIyMgRmlsdGVyaW5nIFJvd3Mgd2l0aCBgZmlsdGVyKClgCgpUaGUgYGZpbHRlcigpYCBmdW5jdGlvbiBleHRyYWN0cyByb3dzIGZyb20gYSBkYXRhIGZyYW1lIHRoYXQgbWVldCBzcGVjaWZpZWQgbG9naWNhbCBjb25kaXRpb25zLiBUbyBzZWxlY3Qgc3R1ZGVudHMgd2l0aCBhIEdQQSBvZiAzLjUgb3IgYmV0dGVyLCB5b3UgY2FuIHVzZSB0aGUgZm9sbG93aW5nICoqZHBseXIqKiBwaXBlOgoKYGBge3J9CmZpbHRlcmVkX3N0dWRlbnRzIDwtIHN0dWRlbnRzICU+JQogIGZpbHRlcihncGEgPj0gMy41MCkKYGBgCgpUaGlzIHJldHVybnMgcm93cyB3aGVyZSB0aGUgYGdwYWAgY29sdW1uIGlzIGdyZWF0ZXIgdGhhbiBvciBlcXVhbCB0byAzLjUwLiBUbyBkaXNwbGF5IHRoZSByZXN1bHQsIHlvdSBjYW4gdXNlIHRoZSBgcHJpbnQoKWAgZnVuY3Rpb246CgpgYGB7cn0KcHJpbnQoZmlsdGVyZWRfc3R1ZGVudHMpCmBgYAoKIyMjIEFycmFuZ2luZyBSb3dzIHdpdGggYGFycmFuZ2UoKWAKClRoZSBgYXJyYW5nZSgpYCBmdW5jdGlvbiBvcmRlcnMgcm93cyBiYXNlZCBvbiB0aGUgdmFsdWVzIG9mIHNwZWNpZmllZCBjb2x1bW5zLiBUbyBzb3J0IHRoZSBgc3R1ZGVudHNgIGRhdGFzZXQgYnkgYGdwYWAgaW4gZGVzY2VuZGluZyBvcmRlcjoKCmBgYHtyfQphcnJhbmdlZF9zdHVkZW50cyA8LSBzdHVkZW50cyAlPiUKICBhcnJhbmdlKGRlc2MoZ3BhKSkKCnByaW50KGFycmFuZ2VkX3N0dWRlbnRzKQpgYGAKClRoaXMgc29ydHMgdGhlIHN0dWRlbnRzIGZyb20gdGhlIGhpZ2hlc3QgdG8gdGhlIGxvd2VzdCBncmFkZS4KCiMjIyBTZWxlY3RpbmcgQ29sdW1ucyB3aXRoIGBzZWxlY3QoKWAKClRoZSBgc2VsZWN0KClgIGZ1bmN0aW9uIGFsbG93cyBmb3IgdGhlIGV4dHJhY3Rpb24gb2Ygc3BlY2lmaWMgY29sdW1ucyBmcm9tIGEgZGF0YSBmcmFtZS4gVG8gY3JlYXRlIGEgbmV3IGRhdGFzZXQgd2l0aCBvbmx5IHRoZSBgbmFtZWAgYW5kIGBncGFgIGNvbHVtbnM6CgpgYGB7cn0Kc2VsZWN0ZWRfc3R1ZGVudHMgPC0gc3R1ZGVudHMgJT4lCiAgc2VsZWN0KG5hbWUsIGdwYSkKCnByaW50KHNlbGVjdGVkX3N0dWRlbnRzKQpgYGAKClRoaXMgcmVzdWx0cyBpbiBhIGRhdGFzZXQgY29udGFpbmluZyBvbmx5IHRoZSBgbmFtZWAgYW5kIGBncGFgIGNvbHVtbnMuIEZyb20gYSByZWxhdGlvbmFsIHBlcnNwZWN0aXZlLCB0aGlzIGlzIGEgInByb2plY3Rpb24iIG9wZXJhdGlvbi4KCiMjIyBBZGRpbmcgb3IgTW9kaWZ5aW5nIENvbHVtbnMgd2l0aCBgbXV0YXRlKClgCgpUaGUgYG11dGF0ZSgpYCBmdW5jdGlvbiBpcyB1c2VkIHRvIGFkZCBuZXcgY29sdW1ucyBvciBtb2RpZnkgZXhpc3Rpbmcgb25lcy4gVG8gYWRkIGEgY29sdW1uIGBob25vcnNgIHRoYXQgY2F0ZWdvcml6ZXMgc3R1ZGVudHMgYmFzZWQgb24gdGhlaXIgR1BBOgoKYGBge3J9Cm11dGF0ZWRfc3R1ZGVudHMgPC0gc3R1ZGVudHMgJT4lCiAgbXV0YXRlKGdyYWRlX2NhdGVnb3J5ID0gY2FzZV93aGVuKAogICAgZ3BhID49IDMuOSB+ICJFeGNlbGxlbnQiLAogICAgZ3BhID49IDMuNSB+ICJHb29kIiwKICAgIGdwYSA+PSAzLjAgfiAiUGFzc2luZyIsCiAgICBUUlVFIH4gIkJlbG93IFBhc3NpbmciCiAgKSkKCnByaW50KG11dGF0ZWRfc3R1ZGVudHMpCmBgYAoKVGhpcyBhZGRzIGEgYGhvbm9yc2AgY29sdW1uIHdpdGggdmFsdWVzIGZvciB2YXJpb3VzIHJhbmdlcyBvZiBHUEEuCgojIyMgU3VtbWFyaXppbmcgRGF0YSB3aXRoIGBzdW1tYXJpemUoKWAKClRoZSBgc3VtbWFyaXplKClgIGZ1bmN0aW9uIGNvbXB1dGVzIHN1bW1hcnkgc3RhdGlzdGljcyBmb3IgYSBkYXRhIGZyYW1lLiBGb3IgZXhhbXBsZSwgdG8gY2FsY3VsYXRlIHRoZSBtZWFuIChhdmVyYWdlKSBHUEEgb2YgdGhlIHN0dWRlbnRzOgoKYGBge3J9CmF2ZXJhZ2VfZ3BhIDwtIHN0dWRlbnRzICU+JQogIHN1bW1hcml6ZShtZWFuX2dwYSA9IG1lYW4oZ3BhKSkKCnByaW50KGF2ZXJhZ2VfZ3BhKQpgYGAKClRoaXMgY29tcHV0ZXMgdGhlIG1lYW4gb2YgdGhlIGBncGFgIGNvbHVtbiwgcHJvdmlkaW5nIHRoZSBhdmVyYWdlIEdQQQoKIyMjIEdyb3VwZWQgT3BlcmF0aW9ucyB3aXRoIGBncm91cF9ieSgpYCBhbmQgYHN1bW1hcml6ZSgpYAoKRm9yIG1vcmUgY29tcGxleCBhbmFseXNlcywgYGdyb3VwX2J5KClgIGluIGNvbmp1bmN0aW9uIHdpdGggYHN1bW1hcml6ZSgpYCBlbmFibGVzIGdyb3VwZWQgb3BlcmF0aW9ucy4gVG8gY2FsY3VsYXRlIHRoZSBhdmVyYWdlIEdQQSBieSBtYWpvcjoKCmBgYHtyfQphdmdfYnlfbWFqb3IgPC0gc3R1ZGVudHMgJT4lCiAgZ3JvdXBfYnkobWFqb3IpICU+JQogIHN1bW1hcml6ZShhdmdfZ3BhID0gbWVhbihncGEpKQoKcHJpbnQoYXZnX2J5X21ham9yKQpgYGAKCldoZW5ldmVyIHRoZXJlJ3MgYSAiYnkiIG9yICJwZXIiIGl0IG1lYW5zIHRoYXQgd2Ugd2FudCB0byBjb21wdXRlciBzb21lICJmYWN0IiBmb3IgZ3JvdXBlZCBkYXRhLgoKVGhlc2UgZXhhbXBsZXMgZGVtb25zdHJhdGUgdGhlIHZlcnNhdGlsaXR5IG9mIGBkcGx5cmAgaW4gcGVyZm9ybWluZyBkYXRhIHRyYW5zZm9ybWF0aW9ucywgZmFjaWxpdGF0aW5nIGVmZmljaWVudCBkYXRhIG1hbmlwdWxhdGlvbiBmb3IgY29tcGxleCBhbmFseXNlcyB1c2luZyBhbiBhbHRlcm5hdGl2ZSB0byBTUUwuCgojIyBTdW1tYXJ5CgpUaGUgKipUaWR5dmVyc2UqKiBhbmQgKipkcGx5cioqIGFyZSBpbnRlZ3JhbCBwYXJ0cyBvZiB0aGUgbW9kZXJuIGRhdGEgYW5hbHlzaXMgZWNvc3lzdGVtIGluIFIsIGRlc2lnbmVkIHRvIHNpbXBsaWZ5IGFuZCBzdHJlYW1saW5lIGRhdGEgbWFuaXB1bGF0aW9uIGFuZCB2aXN1YWxpemF0aW9uIHRhc2tzLgoKLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCgojIyBGaWxlcyAmIFJlc291cmNlcwoKYGBge3IgemlwRmlsZXMsIGVjaG89RkFMU0V9CnppcE5hbWUgPSBzcHJpbnRmKCJMZXNzb25GaWxlcy0lcy0lcy56aXAiLCAKICAgICAgICAgICAgICAgICBwYXJhbXMkY2F0ZWdvcnksCiAgICAgICAgICAgICAgICAgcGFyYW1zJG51bWJlcikKCnRleHRBTGluayA9IHBhc3RlMCgiQWxsIEZpbGVzIGZvciBMZXNzb24gIiwgCiAgICAgICAgICAgICAgIHBhcmFtcyRjYXRlZ29yeSwiLiIscGFyYW1zJG51bWJlcikKCiMgZG93bmxvYWRGaWxlc0xpbmsoKSBpcyBpbmNsdWRlZCBmcm9tIF9pbnNlcnQyREIuUgprbml0cjo6cmF3X2h0bWwoZG93bmxvYWRGaWxlc0xpbmsoIi4iLCB6aXBOYW1lLCB0ZXh0QUxpbmspKQpgYGAKCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQoKIyMgUmVmZXJlbmNlcwoKMS4gIFdpY2toYW0sIEguLCAmIEdyb2xlbXVuZCwgRy4gKDIwMTcpLiAqUiBmb3IgRGF0YSBTY2llbmNlOiBJbXBvcnQsIHRpZHksIHRyYW5zZm9ybSwgdmlzdWFsaXplLCBhbmQgbW9kZWwgZGF0YSouIE8nUmVpbGx5IE1lZGlhLiBSZXRyaWV2ZWQgZnJvbSA8aHR0cHM6Ly9yNGRzLmhhZGxleS5uej4KCjIuICBXaWNraGFtLCBILiAoMjAxNCkuIFRpZHkgZGF0YS4gKkpvdXJuYWwgb2YgU3RhdGlzdGljYWwgU29mdHdhcmUsIDU5KigxMCksIDHigJMyMy4gPGh0dHBzOi8vZG9pLm9yZy8xMC4xODYzNy9qc3MudjA1OS5pMTA+CgpQb3J0aW9ucyBvZiB0aGlzIGxlc3NvbiB3ZXJlIGNyZWF0ZWQgd2l0aCB0aGUgYXNzaXN0YW5jZSBvZiBDaGF0R1BUIDRvLgoKIyMgRXJyYXRhCgpbTGV0IHVzIGtub3ddKGh0dHBzOi8vZm9ybS5qb3Rmb3JtLmNvbS8yMTIxODcwNzI3ODQxNTcpe3RhcmdldD0iX2JsYW5rIn0uCg==