Disclaimer: The purpose of the Open Case Studies project is to demonstrate the use of various data science methods, tools, and software in the context of messy, real-world data. A given case study does not cover all aspects of the research process, is not claiming to be the most appropriate way to analyze a given data set, and should not be used in the context of making policy decisions without external consultation from scientific experts.

This work is licensed under the Creative Commons Attribution-NonCommercial 3.0 (CC BY-NC 3.0) United States License.

To cite this case study please use:

Wright, Carrie and Meng, Qier and Jager, Leah and Taub, Margaret and Hicks, Stephanie. (2020). https://github.com/opencasestudies/ocs-bp-diet. Exploring global patterns of dietary behaviors associated with health risk (Version v1.0.0).

To access the GitHub Repository with the data for this case study see here: https://github.com/opencasestudies/ocs-bp-diet.

You may also access and download the data using our OCSdata package. To learn more about this package including examples, see this link. Here is how you would install this package:

install.packages("OCSdata")

This case study is part of a series of public health case studies for the Bloomberg American Health Initiative.


The total reading time for this case study is calculated via koRpus and shown below:

Reading Time Method
99 minutes koRpus

Readability Score:

A readability index estimates the reading difficulty level of a particular text. Flesch-Kincaid, FORCAST, and SMOG are three common readability indices that were calculated for this case study via koRpus. These indices provide an estimation of the minimum reading level required to comprehend this case study by grade and age.

Text language: en 
index grade age
Flesch-Kincaid 10 15
FORCAST 10 15
SMOG 12 17

Please help us by filling out our survey.

Motivation


An article recently published in The Lancet evaluated global dietary trends and the relationship of dietary factors with mortality and fertility.

GBD 2017 Diet Collaborators. Health effects of dietary risks in 195 countries, 1990–2017: a systematic analysis for the Global Burden of Disease Study 2017. The Lancet 393, 1958–1972 (2019).

This article evaluated food consumption patterns in 195 countries for 15 different dietary risk factors that have probable associations with non-communicable disease (NCD). For example, over-consumption of sodium is associated with high blood pressure. These consumption levels were then used to estimate levels of mortality and morbidity due to NCD, as well as disability-adjusted life-years (DALYs) attributable to sub-optimal consumption of foods related to these dietary risk factors. The authors found that:

“High intake of sodium …, low intake of whole grains …, and low intake of fruits … were the leading dietary risk factors for deaths and DALYs globally and in many countries.”

This figure from the paper’s supplementary materials shows the ranking of the 15 dietary risk factors based on the estimated number of attributable deaths. Here, the numbers and colors of the little squares imply rankings of the risk factors (rows) by regions (columns). The color red indicates risk factors that are associated with larger number of attributable deaths. The column on the right is the overall global data. As you can see here, the top 3 risk factors are often issues for many different regions of the world.

This case study will evaluate the data reported in this article to explore regional, age, and gender specific differences in dietary consumption patterns around the world in 2017.

Main Questions


Our main questions are:

  1. What are the global trends for potentially harmful diets?
  2. How do males and females compare?
  3. How do different age groups compare for these dietary factors?
  4. How do different countries compare? In particular, how does the US compare to other countries in terms of diet trends?

Learning Objectives


In this case study, we will walk you through importing data from PDF files and CSV files, cleaning data, wrangling data, comparing data, joining data, visualizing data, and comparing two or more groups using well-established and commonly used packages, including stringr, tidyr, dplyr, purrr, and ggplot2. We will especially focus on using packages and functions from the Tidyverse. The tidyverse is a library of packages created by RStudio. While some students may be familiar with previous R programming packages, these packages make data science in R especially legible and intuitive.

The skills, methods, and concepts that students will be familiar with by the end of this case study are:

Data Science Learning Objectives:

  1. Importing/extracting data from PDF (dplyr, stringr)
  2. How to reshape data by pivoting between “long” and “wide” formats (tidyr)
  3. Perform functions on all columns of a tibble (purrr)
  4. Data cleaning with regular expressions (stringr)
  5. Specific data value reassignment
  6. Separate data within a column into multiple columns (tidyr)
  7. Methods to Compare data (dplyr)
  8. Combining data from two sources (dplyr)
  9. Make interactive plots (ggiraph)
  10. Make a zoom facet for plot (ggforce)
  11. Combine plots together (cowplot)

Statistical Learning Objectives:

  1. Understanding of how the t-test and the ANOVA are specialized regressions
  2. Basic understanding of the utility of a regression analysis
  3. How to implement a linear regression analysis in R
  4. How to interpret regression coefficients
  5. Awareness of t-test assumptions
  6. Awareness of linear regression assumptions
  7. How to use Q-Q plots to check for normality
  8. Difference between fixed effects and random effects
  9. How to perform paired t-test
  10. How to perform a linear mixed effects regression


We will begin by loading the packages that we will need:

library(here)
library(readr)
library(dplyr)
library(skimr)
library(pdftools)
library(stringr)
library(magrittr)
library(purrr)
library(tibble)
library(tidyr)
library(ggplot2)
library(ggpubr)
library(forcats)
library(lme4)
library(lmerTest)
library(car)
library(ggiraph)
library(ggforce)
library(viridis)
library(cowplot)
library(OCSdata)
Package Use in this case study
here to easily load and save data
readr to import the CSV file data
dplyr to arrange/filter/select/compare specific subsets of the data
skimr to get an overview of data
pdftools to read a PDF into R
stringr to manipulate the text within the PDF of the data
magrittr to use the %<>% piping operator
purrr to perform functions on all columns of a tibble
tibble to create data objects that we can manipulate with dplyr/stringr/tidyr/purrr
tidyr to separate data within a column into multiple columns
ggplot2 to make visualizations with multiple layers
ggpubr to easily add regression line equations to plots
forcats to change details about factors (categorical variables)
lme4 to fit a linear mixed effects model
lmerTest to perform linear mixed model testing
car to perform Levene’s Test of Homogeneity of Variances
ggiraph to make plots interactive
ggforce to modify facets in plots
viridis to plot in a color palette that is easily interpreted by colorblind individuals
cowplot to allow plots to be combined
OCSdata to access and download OCS data files

The first time we use a function, we will use the :: to indicate which package we are using. Unless we have overlapping function names, this is not necessary, but we will include it here to be informative about where the functions we will use come from.

Context


Here is an excerpt from the article itself about the context of the work:

Many dietary factors have well-established associations with health risk. The authors that generated this data set identified 15 dietary factors that have probable health risk based on literature search.

Here you can see a table of the sources for the health risks associated with the dietary factors. The first column shows the risk factors and the second column shows the health outcomes. This table is part of “Supplemental Table 1. Epidemiological evidence supporting causality between dietary risk factors and disease endpoints” from the paper’s supplementary materials.

In the article the authors found that most of the mortality associated with each factor is related to cardiovascular disease.

Limitations


There are some important limitations regarding the data from this article to keep in mind. The definition of certain dietary factors varied across some of the collection sources. Intakes of certain healthy foods like vegetables and fruits are likely positively correlated with each other and likely negatively correlated with intakes of unhealthy foods. Much of the data was collected with 24 hour recall surveys which are prone to issues due to inaccuracy of memory recall or other biases such as a tendency for some people to report healthier behaviors. The guidelines in the PDF are not specified by gender even though it is known that there are different dietary requirements for optimal health for certain nutrients by gender. The article discusses some limitations about accounting for overall food consumption when calculating consumption of particular foods:

"To remove the effect of energy intake as a potential confounder and address measurement error in dietary assessment tools, most cohorts have adjusted for total energy intake in their statistical models. This energy adjustment means that diet components are defined as risks in terms of the share of diet and not as absolute levels of exposure. In other words, an increase in intake of foods and macronutrients should be compensated by a decrease in intake of other dietary factors to hold total energy intake constant. Thus, the relative risk of change in each component of diet depends on the other components for which it is substituted. However, the relative risks estimated from meta-analyses of cohort studies do not generally specify the type of substitution.

There are also important nuances to keep in mind regarding some of the dietary factors. For example calcium consumption was calculated based on consumption of dairy products, while calcium can be acquired from other sources including plant-based sources. However in these data, the influence of plant-based consumption of calcium was not accounted for, nor was supplementation through vitamin sources.

Also, while gender and sex are not actually binary, the data used in this analysis only contains information for groups of individuals described as male or female.

What are the data?


We will be using data that we requested from the Global Burden of Disease (GBD) of the Institute for Health Metrics and Evaluation (IHME) about dietary intake, as well as the guideline data about optimal consumption amounts for different foods contained within the PDF of the article. We have two CSV files, dietary_risk_exposure_all_ages_2017.csv and dietary_risk_exposure_sep_ages_2017.csv. The first one includes consumption levels at the global level and for different countries for all ages combined.

Looking at the CSV file in excel:

Here you can see that the data contains mean consumption values for both men and women in various countries at the national level in 2017 for various foods that may be problematic for health. The units for the food varies. So for example, the mean column in row that says “Diet low in fiber” indicates the average consumption level per person in that region and of that gender of fiber in grams per day.

The second CSV file has similar data, but consumption levels for different age groups are separated.

The authors of this article obtained the data from a variety of sources including household budget surveys and nutritional surveys regarding 24 hour recall of food consumption and 24 hour urinary sodium analysis. The data was derived from sales data from Euromonitor, estimates about national availability of specific nutrients from the United Nations Food and Agriculture Organization (FAO) and the United States Department of Agriculture’s National Nutrition Database.

Data Import


If you have trouble accessing the GitHub Repository, the data can be downloaded from here and here.

Let’s import our data into R now so that we can explore the data further.

In our case, we downloaded this data and put it within a “data” directory within a subdirectory called “raw” for our project. If you use an RStudio project, then you can use the here() function of the here package to make the path for importing this data simpler. The here package automatically starts looking for files based on where you have a .Rproj file which is created when you start a new RStudio project. We can specify that we want to look for the files within the “docs” directory within a directory where our .Rproj file is located by separating the name of the “data” directory, the “raw” subdirectory, and the file name using commas.


Click here to see more about creating new projects in RStudio.

You can create a project by going to the File menu of RStudio like so:

You can also do so by clicking the project button:

See here to learn more about using RStudio projects.


diet_data <- readr::read_csv(here("data", "raw", 
                       "dietary_risk_exposure_all_ages_2017.csv"))
sep_age_diet_data <- read_csv(here("data", "raw", 
                       "dietary_risk_exposure_sep_ages_2017.csv"))

You may also use the OCSdata package to download the raw data:

# install.packages("OCSdata")
library(OCSdata)
raw_data("ocs-bp-diet", outpath = getwd())
# This will save the raw data files in a "OCSdata/data/raw/" sub-folder 
# in your current working directory

If you used the OCSdata package to download the raw data, you can import the data into R like so:

diet_data <- readr::read_csv(here("OCSdata", "data", "raw", 
                       "dietary_risk_exposure_all_ages_2017.csv"))
sep_age_diet_data <- read_csv(here("OCSdata", "data", "raw", 
                       "dietary_risk_exposure_sep_ages_2017.csv"))

First let’s just get a general sense of our data. We can do that using the glimpse() function of the dplyr package (it is also in the tibble package).

dplyr::glimpse(diet_data)
Rows: 5,880
Columns: 11
$ year_id        <dbl> 2017, 2017, 2017, 2017, 2017, 2017, 2017, 2017, 2017, 2~
$ location_name  <chr> "Global", "Global", "China", "China", "North Korea", "N~
$ rei_id         <dbl> 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, ~
$ rei_name       <chr> "Diet high in processed meat", "Diet high in processed ~
$ age_group_name <chr> "All available ages", "All available ages", "All availa~
$ sex            <chr> "Male", "Female", "Male", "Female", "Male", "Female", "~
$ parameter      <chr> "continuous", "continuous", "continuous", "continuous",~
$ mean           <dbl> 4.2865629, 3.2640990, 2.3176975, 1.7512870, 0.5665229, ~
$ upper          <dbl> 4.4633117, 3.3765360, 2.6944978, 2.0454134, 0.6596296, ~
$ lower          <dbl> 4.1309531, 3.1547299, 1.9933744, 1.5161724, 0.4818201, ~
$ unit           <chr> "g/day", "g/day", "g/day", "g/day", "g/day", "g/day", "~
glimpse(sep_age_diet_data)
Rows: 88,200
Columns: 11
$ year_id        <dbl> 2017, 2017, 2017, 2017, 2017, 2017, 2017, 2017, 2017, 2~
$ location_name  <chr> "Global", "Global", "Global", "Global", "Global", "Glob~
$ rei_name       <chr> "Diet low in calcium", "Diet low in calcium", "Diet low~
$ age_group_id   <dbl> 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 30, 31, 32,~
$ age_group_name <chr> "25 to 29", "30 to 34", "35 to 39", "40 to 44", "45 to ~
$ sex            <chr> "Male", "Male", "Male", "Male", "Male", "Male", "Male",~
$ parameter      <chr> "continuous", "continuous", "continuous", "continuous",~
$ mean           <dbl> 0.4166193, 0.4253721, 0.4352053, 0.4412156, 0.4565795, ~
$ upper          <dbl> 0.4310974, 0.4403903, 0.4481282, 0.4579218, 0.4763366, ~
$ lower          <dbl> 0.4030347, 0.4110026, 0.4222248, 0.4264522, 0.4381799, ~
$ unit           <chr> "g/day", "g/day", "g/day", "g/day", "g/day", "g/day", "~

Here we can tell that the sep_age_diet_data is much larger than the diet_data. The diet_data has only 5,880 rows while the sep_age_diet_data has 88,200 rows!

However, both files appear to have the same column structure with 11 variables each.

The skim() function of the skimr package is also really helpful for getting a general sense of your data.

skim(diet_data)
Data summary
Name diet_data
Number of rows 5880
Number of columns 11
_______________________
Column type frequency:
character 6
numeric 5
________________________
Group variables None

Variable type: character

skim_variable n_missing complete_rate min max empty n_unique whitespace
location_name 0 1 4 32 0 196 0
rei_name 0 1 16 39 0 15 0
age_group_name 0 1 18 18 0 1 0
sex 0 1 4 6 0 2 0
parameter 0 1 10 10 0 1 0
unit 0 1 5 12 0 2 0

Variable type: numeric

skim_variable n_missing complete_rate mean sd p0 p25 p50 p75 p100 hist
year_id 0 1 2017.00 0.00 2017 2017.00 2017.00 2017.00 2017.00 ▁▁▇▁▁
rei_id 0 1 133.67 53.93 111 114.00 118.00 123.00 333.00 ▇▁▁▁▁
mean 0 1 38.27 64.29 0 0.45 8.38 49.12 566.69 ▇▁▁▁▁
upper 0 1 41.81 70.06 0 0.49 9.22 53.18 624.23 ▇▁▁▁▁
lower 0 1 35.02 59.06 0 0.42 7.52 45.18 513.22 ▇▁▁▁▁

Notice how there is a column providing the number of missing observations for each variable. It looks like our data is very complete and we do not have any missing data. We also get a sense about the size of our data.

The n_unqiue column shows us the number of unique values for each of our columns.

Let’s take a look at sep_age_diet_data.

skim(sep_age_diet_data)
Data summary
Name sep_age_diet_data
Number of rows 88200
Number of columns 11
_______________________
Column type frequency:
character 6
numeric 5
________________________
Group variables None

Variable type: character

skim_variable n_missing complete_rate min max empty n_unique whitespace
location_name 0 1 4 32 0 196 0
rei_name 0 1 16 39 0 15 0
age_group_name 0 1 7 8 0 15 0
sex 0 1 4 6 0 2 0
parameter 0 1 10 10 0 1 0
unit 0 1 5 12 0 2 0

Variable type: numeric

skim_variable n_missing complete_rate mean sd p0 p25 p50 p75 p100 hist
year_id 0 1 2017.00 0.00 2017 2017.00 2017.00 2017.00 2017.00 ▁▁▇▁▁
age_group_id 0 1 32.87 54.46 10 13.00 17.00 30.00 235.00 ▇▁▁▁▁
mean 0 1 36.06 61.94 0 0.42 7.90 43.54 604.01 ▇▁▁▁▁
upper 0 1 45.86 78.69 0 0.53 10.43 55.10 806.98 ▇▁▁▁▁
lower 0 1 28.01 48.47 0 0.32 5.72 34.17 494.66 ▇▁▁▁▁

We can see that there are many more rows in this data set.

Let’s change the variable name rei_name to dietary_risk so that it makes more sense. We can use the rename() function from the dplyr package.

diet_data <- dplyr::rename(diet_data, dietary_risk = rei_name)
sep_age_diet_data <- dplyr::rename(sep_age_diet_data, dietary_risk = rei_name)

glimpse(diet_data)
Rows: 5,880
Columns: 11
$ year_id        <dbl> 2017, 2017, 2017, 2017, 2017, 2017, 2017, 2017, 2017, 2~
$ location_name  <chr> "Global", "Global", "China", "China", "North Korea", "N~
$ rei_id         <dbl> 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, ~
$ dietary_risk   <chr> "Diet high in processed meat", "Diet high in processed ~
$ age_group_name <chr> "All available ages", "All available ages", "All availa~
$ sex            <chr> "Male", "Female", "Male", "Female", "Male", "Female", "~
$ parameter      <chr> "continuous", "continuous", "continuous", "continuous",~
$ mean           <dbl> 4.2865629, 3.2640990, 2.3176975, 1.7512870, 0.5665229, ~
$ upper          <dbl> 4.4633117, 3.3765360, 2.6944978, 2.0454134, 0.6596296, ~
$ lower          <dbl> 4.1309531, 3.1547299, 1.9933744, 1.5161724, 0.4818201, ~
$ unit           <chr> "g/day", "g/day", "g/day", "g/day", "g/day", "g/day", "~
glimpse(sep_age_diet_data)
Rows: 88,200
Columns: 11
$ year_id        <dbl> 2017, 2017, 2017, 2017, 2017, 2017, 2017, 2017, 2017, 2~
$ location_name  <chr> "Global", "Global", "Global", "Global", "Global", "Glob~
$ dietary_risk   <chr> "Diet low in calcium", "Diet low in calcium", "Diet low~
$ age_group_id   <dbl> 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 30, 31, 32,~
$ age_group_name <chr> "25 to 29", "30 to 34", "35 to 39", "40 to 44", "45 to ~
$ sex            <chr> "Male", "Male", "Male", "Male", "Male", "Male", "Male",~
$ parameter      <chr> "continuous", "continuous", "continuous", "continuous",~
$ mean           <dbl> 0.4166193, 0.4253721, 0.4352053, 0.4412156, 0.4565795, ~
$ upper          <dbl> 0.4310974, 0.4403903, 0.4481282, 0.4579218, 0.4763366, ~
$ lower          <dbl> 0.4030347, 0.4110026, 0.4222248, 0.4264522, 0.4381799, ~
$ unit           <chr> "g/day", "g/day", "g/day", "g/day", "g/day", "g/day", "~

Looks good!

We will then take a look at the different dietary risk factors considered. To do this we will use the distinct() function of the dplyr package.

This function grabs only the distinct or unique rows from a given variable (dietary_risk, in our case) of a given data frame (diet_data, in our case).

dplyr::distinct(diet_data, dietary_risk)
# A tibble: 15 x 1
   dietary_risk                           
   <chr>                                  
 1 Diet high in processed meat            
 2 Diet high in red meat                  
 3 Diet high in sodium                    
 4 Diet high in sugar-sweetened beverages 
 5 Diet high in trans fatty acids         
 6 Diet low in calcium                    
 7 Diet low in fiber                      
 8 Diet low in fruits                     
 9 Diet low in legumes                    
10 Diet low in milk                       
11 Diet low in nuts and seeds             
12 Diet low in polyunsaturated fatty acids
13 Diet low in seafood omega-3 fatty acids
14 Diet low in vegetables                 
15 Diet low in whole grains               

Both over and under consumption could be a health problem!

We will be using the %>% pipe for sequential steps in our code later on. This will make more sense when we have multiple sequential steps using the same data object.

We could do the same code as above using this notation. For example we first grab the diet_data, then we select the distinct values of the dietary_risk variable.

diet_data %>%
  distinct(dietary_risk)
# A tibble: 15 x 1
   dietary_risk                           
   <chr>                                  
 1 Diet high in processed meat            
 2 Diet high in red meat                  
 3 Diet high in sodium                    
 4 Diet high in sugar-sweetened beverages 
 5 Diet high in trans fatty acids         
 6 Diet low in calcium                    
 7 Diet low in fiber                      
 8 Diet low in fruits                     
 9 Diet low in legumes                    
10 Diet low in milk                       
11 Diet low in nuts and seeds             
12 Diet low in polyunsaturated fatty acids
13 Diet low in seafood omega-3 fatty acids
14 Diet low in vegetables                 
15 Diet low in whole grains               

OK, so that gives us an idea of what dietary factors we can explore, and we can see that there are 15 of them.

Let’s see if the location_name values are the same between both CSV files. To do this we will use the setequal() function of dplyr.

dplyr::setequal(
  distinct(diet_data, location_name),
  distinct(sep_age_diet_data, location_name)
)
[1] TRUE

OK, we got the value of TRUE, so it looks like the same locations are in both files.

Note: In this case were comparing two different objects so using the pipe is not as useful.

Let’s take a look at the locations included in the data.

# scroll through the output!
sep_age_diet_data %>%
  distinct(location_name) %>%
  pull()
  [1] "Global"                           "China"                           
  [3] "North Korea"                      "Taiwan (Province of China)"      
  [5] "Cambodia"                         "Indonesia"                       
  [7] "Laos"                             "Malaysia"                        
  [9] "Maldives"                         "Myanmar"                         
 [11] "Philippines"                      "Sri Lanka"                       
 [13] "Thailand"                         "Timor-Leste"                     
 [15] "Vietnam"                          "Fiji"                            
 [17] "Kiribati"                         "Marshall Islands"                
 [19] "Federated States of Micronesia"   "Papua New Guinea"                
 [21] "Samoa"                            "Solomon Islands"                 
 [23] "Tonga"                            "Vanuatu"                         
 [25] "Armenia"                          "Azerbaijan"                      
 [27] "Georgia"                          "Kazakhstan"                      
 [29] "Kyrgyzstan"                       "Mongolia"                        
 [31] "Tajikistan"                       "Turkmenistan"                    
 [33] "Uzbekistan"                       "Albania"                         
 [35] "Bosnia and Herzegovina"           "Bulgaria"                        
 [37] "Croatia"                          "Czech Republic"                  
 [39] "Hungary"                          "Macedonia"                       
 [41] "Montenegro"                       "Poland"                          
 [43] "Romania"                          "Serbia"                          
 [45] "Slovakia"                         "Slovenia"                        
 [47] "Belarus"                          "Estonia"                         
 [49] "Latvia"                           "Lithuania"                       
 [51] "Moldova"                          "Russian Federation"              
 [53] "Ukraine"                          "Brunei"                          
 [55] "Japan"                            "South Korea"                     
 [57] "Singapore"                        "Australia"                       
 [59] "New Zealand"                      "Andorra"                         
 [61] "Austria"                          "Belgium"                         
 [63] "Cyprus"                           "Denmark"                         
 [65] "Finland"                          "France"                          
 [67] "Germany"                          "Greece"                          
 [69] "Iceland"                          "Ireland"                         
 [71] "Israel"                           "Italy"                           
 [73] "Luxembourg"                       "Malta"                           
 [75] "Netherlands"                      "Norway"                          
 [77] "Portugal"                         "Spain"                           
 [79] "Sweden"                           "Switzerland"                     
 [81] "United Kingdom"                   "Argentina"                       
 [83] "Chile"                            "Uruguay"                         
 [85] "Canada"                           "United States"                   
 [87] "Antigua and Barbuda"              "The Bahamas"                     
 [89] "Barbados"                         "Belize"                          
 [91] "Cuba"                             "Dominica"                        
 [93] "Dominican Republic"               "Grenada"                         
 [95] "Guyana"                           "Haiti"                           
 [97] "Jamaica"                          "Saint Lucia"                     
 [99] "Saint Vincent and the Grenadines" "Suriname"                        
[101] "Trinidad and Tobago"              "Bolivia"                         
[103] "Ecuador"                          "Peru"                            
[105] "Colombia"                         "Costa Rica"                      
[107] "El Salvador"                      "Guatemala"                       
[109] "Honduras"                         "Mexico"                          
[111] "Nicaragua"                        "Panama"                          
[113] "Venezuela"                        "Brazil"                          
[115] "Paraguay"                         "Algeria"                         
[117] "Bahrain"                          "Egypt"                           
[119] "Iran"                             "Iraq"                            
[121] "Jordan"                           "Kuwait"                          
[123] "Lebanon"                          "Libya"                           
[125] "Morocco"                          "Palestine"                       
[127] "Oman"                             "Qatar"                           
[129] "Saudi Arabia"                     "Syria"                           
[131] "Tunisia"                          "Turkey"                          
[133] "United Arab Emirates"             "Yemen"                           
[135] "Afghanistan"                      "Bangladesh"                      
[137] "Bhutan"                           "India"                           
[139] "Nepal"                            "Pakistan"                        
[141] "Angola"                           "Central African Republic"        
[143] "Congo"                            "Democratic Republic of the Congo"
[145] "Equatorial Guinea"                "Gabon"                           
[147] "Burundi"                          "Comoros"                         
[149] "Djibouti"                         "Eritrea"                         
[151] "Ethiopia"                         "Kenya"                           
[153] "Madagascar"                       "Malawi"                          
[155] "Mauritius"                        "Mozambique"                      
[157] "Rwanda"                           "Seychelles"                      
[159] "Somalia"                          "Tanzania"                        
[161] "Uganda"                           "Zambia"                          
[163] "Botswana"                         "Lesotho"                         
[165] "Namibia"                          "South Africa"                    
[167] "Swaziland"                        "Zimbabwe"                        
[169] "Benin"                            "Burkina Faso"                    
[171] "Cameroon"                         "Cape Verde"                      
[173] "Chad"                             "Cote d'Ivoire"                   
[175] "The Gambia"                       "Ghana"                           
[177] "Guinea"                           "Guinea-Bissau"                   
[179] "Liberia"                          "Mali"                            
[181] "Mauritania"                       "Niger"                           
[183] "Nigeria"                          "Sao Tome and Principe"           
[185] "Senegal"                          "Sierra Leone"                    
[187] "Togo"                             "American Samoa"                  
[189] "Bermuda"                          "Greenland"                       
[191] "Guam"                             "Northern Mariana Islands"        
[193] "Puerto Rico"                      "Virgin Islands, U.S."            
[195] "South Sudan"                      "Sudan"                           

OK, so there are global values, as well as values for 195 countries.

Let’s take a look at the data when we order it by the mean consumption rate column. We can do so using the arrange() function of the dplyr package.

diet_data %>%
  dplyr::arrange(mean) %>%
  glimpse()
Rows: 5,880
Columns: 11
$ year_id        <dbl> 2017, 2017, 2017, 2017, 2017, 2017, 2017, 2017, 2017, 2~
$ location_name  <chr> "Lebanon", "Lebanon", "Italy", "Turkey", "Kazakhstan", ~
$ rei_id         <dbl> 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, ~
$ dietary_risk   <chr> "Diet high in trans fatty acids", "Diet high in trans f~
$ age_group_name <chr> "All available ages", "All available ages", "All availa~
$ sex            <chr> "Male", "Female", "Male", "Male", "Male", "Male", "Male~
$ parameter      <chr> "continuous", "continuous", "continuous", "continuous",~
$ mean           <dbl> 0.0007277926, 0.0008198374, 0.0013574727, 0.0014110786,~
$ upper          <dbl> 0.0008927912, 0.0010079978, 0.0014915092, 0.0017274811,~
$ lower          <dbl> 0.0005853401, 0.0006704380, 0.0012335834, 0.0011349718,~
$ unit           <chr> "% energy/day", "% energy/day", "% energy/day", "% ener~

OK, so it looks like people in Lebanon don’t eat very many trans fatty acids.

Let’s also figure out how many values there are in each age group of the data that is separated by age. We will use the count() function of the dplyr package to do this.

sep_age_diet_data %>%
  dplyr::count(age_group_name)
# A tibble: 15 x 2
   age_group_name     n
   <chr>          <int>
 1 25 to 29        5880
 2 30 to 34        5880
 3 35 to 39        5880
 4 40 to 44        5880
 5 45 to 49        5880
 6 50 to 54        5880
 7 55 to 59        5880
 8 60 to 64        5880
 9 65 to 69        5880
10 70 to 74        5880
11 75 to 79        5880
12 80 to 84        5880
13 85 to 89        5880
14 90 to 94        5880
15 95 plus         5880

That’s a lot of values!

Let’s look a bit deeper to try to understand why. We can use the count() function again but get the number of values for each category within sex, age_group_name and location_name of the data.

sep_age_diet_data %>%
  count(sex, age_group_name, location_name)
# A tibble: 5,880 x 4
   sex    age_group_name location_name           n
   <chr>  <chr>          <chr>               <int>
 1 Female 25 to 29       Afghanistan            15
 2 Female 25 to 29       Albania                15
 3 Female 25 to 29       Algeria                15
 4 Female 25 to 29       American Samoa         15
 5 Female 25 to 29       Andorra                15
 6 Female 25 to 29       Angola                 15
 7 Female 25 to 29       Antigua and Barbuda    15
 8 Female 25 to 29       Argentina              15
 9 Female 25 to 29       Armenia                15
10 Female 25 to 29       Australia              15
# ... with 5,870 more rows

OK, so it looks like these are probably the consumption values for each of the different dietary factors (since there were 15 different factors) for each age group and gender combination within each country.

We can confirm this by filtering the data to one of the age groups, for a single gender, and for a single location. To do this we can use the filter() function of the dplyr package. Notice that we need to use two equal signs == to specify what values we would like for each variable.

sep_age_diet_data %>%
  dplyr::filter(
    sex == "Female",
    age_group_name == "25 to 29",
    location_name == "Afghanistan"
  )
# A tibble: 15 x 11
   year_id location_name dietary_risk          age_group_id age_group_name sex  
     <dbl> <chr>         <chr>                        <dbl> <chr>          <chr>
 1    2017 Afghanistan   Diet low in calcium             10 25 to 29       Fema~
 2    2017 Afghanistan   Diet low in fiber               10 25 to 29       Fema~
 3    2017 Afghanistan   Diet low in seafood ~           10 25 to 29       Fema~
 4    2017 Afghanistan   Diet low in fruits              10 25 to 29       Fema~
 5    2017 Afghanistan   Diet low in whole gr~           10 25 to 29       Fema~
 6    2017 Afghanistan   Diet low in legumes             10 25 to 29       Fema~
 7    2017 Afghanistan   Diet low in milk                10 25 to 29       Fema~
 8    2017 Afghanistan   Diet low in nuts and~           10 25 to 29       Fema~
 9    2017 Afghanistan   Diet high in process~           10 25 to 29       Fema~
10    2017 Afghanistan   Diet low in polyunsa~           10 25 to 29       Fema~
11    2017 Afghanistan   Diet high in red meat           10 25 to 29       Fema~
12    2017 Afghanistan   Diet high in sodium             10 25 to 29       Fema~
13    2017 Afghanistan   Diet high in sugar-s~           10 25 to 29       Fema~
14    2017 Afghanistan   Diet high in trans f~           10 25 to 29       Fema~
15    2017 Afghanistan   Diet low in vegetabl~           10 25 to 29       Fema~
# ... with 5 more variables: parameter <chr>, mean <dbl>, upper <dbl>,
#   lower <dbl>, unit <chr>

This confirms that for each of the 15 dietary factors, our unit of observation is a combination of gender, age and country.

However, before we proceed with our analysis, we will want to perform some additional data wrangling. To do this, we will introduce the pdftools package, which will allow us to pull additional data from the manuscript itself.

While all of the mean consumption values are reported in grams, each dietary factor has a different amount that is considered optimal for consuming. To make the consumption values more comparable across factors, let’s also get some data from the PDF of the paper so that we can calculate consumption of these dietary factors as percentages of the daily optimum.

We are interested in this table on page 3:

First let’s import the PDF using the pfd_text() function of the pdftools package.

You can find this file here.

paper <- pdftools::pdf_text(here("data", "raw",
"Afshin_et_al_2019.pdf"))

We can save our imported data as an rda file (stands for R data file) using the save() function.

save(diet_data, sep_age_diet_data, paper, file = here::here("data", "imported", "imported_data.rda"))

Data Wrangling


If you have been following along but stopped, we could load our imported data like so:

load(here::here("data", "imported", "imported_data.rda"))

If you skipped the data import section click here.

First you need to install and load the OCSdata package:

install.packages("OCSdata")
library(OCSdata)

Then, you may load the imported data using the following code:

imported_data("ocs-bp-diet", outpath = getwd())
load(here::here("OCSdata", "data", "imported", "imported_data.rda"))

If the package does not work for you, alternatively, an RDA file (stands for R data) of the data can be found in our GitHub repository or slightly more directly here. Download this file and then place it in your current working directory within a subdirectory called “imported” within a subdirectory called “data” to copy and paste our code. We used an RStudio project and the here package to navigate to the file more easily.

load(here::here("data", "imported", "imported_data.rda"))

Click here to see more about creating new projects in RStudio.

You can create a project by going to the File menu of RStudio like so:

You can also do so by clicking the project button:

See here to learn more about using RStudio projects and here to learn more about the here package.



Let’s take a look at our manuscript data.

We can use the base summary() function to get a sense of what the data looks like. By base we mean that these functions are part of the base package and are loaded automatically on startup of R. Thus, library(base) is not required.

summary(paper)
   Length     Class      Mode 
       15 character character 

We can see that we have 15 different character strings. Each one contains the text on each of the 15 different pages of the PDF.

Again, the table we are interested in is on the third page, so let’s grab just that portion of the PDF. The top of this page looks like:

# Here we will select the 3rd value in the paper object
pdf_table <- paper[3]

summary(pdf_table)
   Length     Class      Mode 
        1 character character 
# specifying nchar.max truncates the output
glimpse(pdf_table, nchar.max = 800)
 chr "                                                                                                                                                                                                  Articles\n\n\n\n\nin systolic blood pressure, and then estimated the                                          Disease-specific deaths and disability-adjusted\nrelationship between change in systolic blood pressure                                      life-years\nand disease outcomes.14                                                                     Data on disease-specific deaths and disability-adjusted\n                                                                                            life-years (DALYs) by age, sex, country, and year were\nOptimal level of intake      "| __truncated__

Here we can see that the pdf_table object now contains the text from the 3rd page as a single large character string. However the text is difficult to read because of the column structure in the PDF. Now let’s try to grab just the text in the table.

One way to approach this is to split the string by some pattern that we notice in the table.

All the rows of interest of the table appear to start with the word "Diet". Moreover, only the capitalized form of the word "Diet" appears to be within the table, and it is not present in the preceding text (although "diet" is).

Let’s use the str_split() function of the stringr package to split the data within the object called pdf_table by the word "Diet". Only lines from page 3 that contain the word "Diet" will be selected (and not "diet" as this function is case-sensitive). Each section of the text that contains "Diet" will be split into individual pieces every time the word "Diet" occurs and the word itself will be removed.

In this case we are also using the magrittr assignment pipe or double pipe that looks like this %<>% of the magrittr package. This allows us use the pdf_table data as input to the later steps but also reassign the output to the same data object name.

pdf_table %<>%
  stringr::str_split(pattern = "Diet")

Using the base::summary() and dplyr::glimpse() function we can see that we created a list of the rows in the table that contained the word "Diet". We can see that we start with the row that contains "low in fruits".

pdf_table %>%
  summary()
     Length Class  Mode     
[1,] 17     -none- character
pdf_table %>%
  glimpse()
List of 1
 $ : chr [1:17] "                                                                                                               "| __truncated__ " low in fruits                Mean daily consumption of fruits (fresh, frozen, cooked, canned, or dried fruits,"| __truncated__ " low in vegetables            Mean daily consumption of vegetables (fresh, frozen, cooked, canned, or dried veg"| __truncated__ " low in legumes               Mean daily consumption of legumes (fresh, frozen, cooked, canned, or dried legume"| __truncated__ ...

In order to extract the values that we want from these character strings, we will use some additional functions from the stringr package. RStudio creates really helpful cheat sheets like this one which shows you all the major functions in the stringr package. You can download others here.

You can see that we could have also used the str_split_fixed() function which would also separate the substrings into different columns of a matrix, however we would need to know the number of substrings or pieces that we would like returned.

For more information about str_split() see here.

Let’s separate the values within the list using the base unlist function, this will allow us to easily select the different substrings within the object called pdf_table.

pdf_table %<>%
  unlist()

It’s important to realize that the first split will split the text before the first occurrence of "Diet" as the first value in the output. (This is why there are 17 elements in pdf_table rather than 15, the number of rows in the table.) We could use the first() function of the dplyr package to look at this value. However, we will suppress the output as this is quite large.

dplyr::first(pdf_table)

Instead we can take a look at the second element of the list. using the nth() function of dplyr.

nth(pdf_table, 2)
[1] " low in fruits                Mean daily consumption of fruits (fresh, frozen, cooked, canned, or dried fruits, excluding                250 g (200–300) per day                           94·9\n                                    fruit juices and salted or pickled fruits)\n  "

Indeed this looks like the first row of interest in our table:

Using the last() and the nth() functions of the dplyr package we can take a look at the last values of the list.

# to see the second to last value we can use nth()
# the -2 specifies that we want the second-to-last value
# -3 would be third-to-last and -1 would be the last value
dplyr::nth(pdf_table, -2)
[1] " high in sodium               24 h urinary sodium measured in g per day                                                                     3 g (1–5) per day*                             26·2\n\n *To reflect the uncertainty in existing evidence on optimal level of intake for sodium, 1–5 g per day was considered as the uncertainty range for the optimal level of sodium where less than 2·3 g per day is the\n intake level of sodium associated with the lowest level of blood pressure in randomised controlled trials and 4–5 g per day is the level of sodium intake associated with the lowest risk of cardiovascular disease in\n observational studies.\n\n Table: "
# to see the very last value we can use last()
dplyr::last(pdf_table)
[1] "ary risk factor exposure definitions, optimal level, and data representativeness index, 1990–2017\n\n\n\nwww.thelancet.com Published online April 3, 2019 http://dx.doi.org/10.1016/S0140-6736(19)30041-8                                                                                                                          3\n"

We don’t need this part of the table or the text before the table if we just want the consumption recommendations.

So we will select the second through the second-to-last of the substrings. Since we have seventeen substrings, we will select the second through the sixteenth. However a better way to do this rather than selecting by index, would be to select phrases that are unique to the text within the table that we want. We will use the str_subset() function of stringr package to select the table rows with consumption guidelines. Most of the rows have the phrase “Mean daily consumption”, however, there are other phrases for some of the rows, including “Mean daily intake” and “24 h sodium”. So we will subset for each of these phrases.

# one could subset the pdf_table like this:
# pdf_table <- pdf_table[2:16]

pdf_table %<>%
  str_subset(pattern = "Mean daily consumption|Mean daily intake|24 h")

Notice that we separate the different patterns to look for using vertical bar character "|" and that all of the patterns are within quotation marks together.

Question opportunity:

  1. What other string patterns could you use to subset the rows of the table that we want?

  2. Why might it be better to subset based on the text rather than the index?

Now the first row is what we want:

first(pdf_table)
[1] " low in fruits                Mean daily consumption of fruits (fresh, frozen, cooked, canned, or dried fruits, excluding                250 g (200–300) per day                           94·9\n                                    fruit juices and salted or pickled fruits)\n  "

And the last row is what we want:

last(pdf_table)
[1] " high in sodium               24 h urinary sodium measured in g per day                                                                     3 g (1–5) per day*                             26·2\n\n *To reflect the uncertainty in existing evidence on optimal level of intake for sodium, 1–5 g per day was considered as the uncertainty range for the optimal level of sodium where less than 2·3 g per day is the\n intake level of sodium associated with the lowest level of blood pressure in randomised controlled trials and 4–5 g per day is the level of sodium intake associated with the lowest risk of cardiovascular disease in\n observational studies.\n\n Table: "

At this point, we have a better look at the current representation of the table data in R, and we might notice something that will need to be fixed. In the string above, the decimal points from the PDF are being recognized as something called an interpunct instead of a period or decimal. An interpunct is a centered dot, as opposed to a period or decimal that is aligned to the bottom of the line.

The interpunct was previously used to separate words in certain languages, like ancient Latin.

[source]

You can produce an interpunct on a Mac like this:

[source]

It is important to replace these for later when we want these values to be converted from character strings to numeric. We will again use the stringr package. This time we will use the str_replace_all() function which replaces all instances of a pattern in an individual string. In this case we want to replace all instances of the interpunct with a decimal point.

pdf_table %<>%
  stringr::str_replace_all(
    pattern = "·",
    replacement = "."
  )
last(pdf_table)
[1] " high in sodium               24 h urinary sodium measured in g per day                                                                     3 g (1–5) per day*                             26.2\n\n *To reflect the uncertainty in existing evidence on optimal level of intake for sodium, 1–5 g per day was considered as the uncertainty range for the optimal level of sodium where less than 2.3 g per day is the\n intake level of sodium associated with the lowest level of blood pressure in randomised controlled trials and 4–5 g per day is the level of sodium intake associated with the lowest risk of cardiovascular disease in\n observational studies.\n\n Table: "

Looks good!

Now we will try to split the strings for each row based on the presence of two spaces to create the columns of the table, as there appears to be more than one space between the columns. The resulting substrings will be separated by quotes.

For additional details, the second page of the stringr cheat sheet has more information about using “Special Characters” in stringr. For example \\s is interpreted as a space as the \\ indicates that the s should be interpreted as a special character and not simply the letter s. The {2,} indicates two or more spaces, while {2} would indicate exactly two spaces.

table_split <- str_split(
  string = pdf_table,
  pattern = "\\s{2,}"
)
glimpse(table_split) # scroll the output!
List of 15
 $ : chr [1:6] " low in fruits" "Mean daily consumption of fruits (fresh, frozen, cooked, canned, or dried fruits, excluding" "250 g (200–300) per day" "94.9" ...
 $ : chr [1:7] " low in vegetables" "Mean daily consumption of vegetables (fresh, frozen, cooked, canned, or dried vegetables," "360 g (290–430) per day" "94.9" ...
 $ : chr [1:5] " low in legumes" "Mean daily consumption of legumes (fresh, frozen, cooked, canned, or dried legumes)" "60 g (50–70) per day" "94.9" ...
 $ : chr [1:7] " low in whole grains" "Mean daily consumption of whole grains (bran, germ, and endosperm in their natural" "125 g (100–150) per day" "94.9" ...
 $ : chr [1:5] " low in nuts and seeds" "Mean daily consumption of nut and seed foods" "21 g (16–25) per day" "94.9" ...
 $ : chr [1:6] " low in milk" "Mean daily consumption of milk including non-fat, low-fat, and full-fat milk, excluding soy" "435 g (350–520) per day" "94.9" ...
 $ : chr [1:6] " high in red meat" "Mean daily consumption of red meat (beef, pork, lamb, and goat, but excluding poultry, fish," "23 g (18–27) per day" "94.9" ...
 $ : chr [1:6] " high in processed meat" "Mean daily consumption of meat preserved by smoking, curing, salting, or addition of" "2 g (0–4) per day" "36.9" ...
 $ : chr [1:6] " high in sugar-sweetened Mean daily consumption of beverages with =50 kcal per 226.8 serving, including carbonated" "3 g (0–5) per day" "36.9" "beverages" ...
 $ : chr [1:6] " low in fibre" "Mean daily intake of fibre from all sources including fruits, vegetables, grains, legumes, and" "24 g (19–28) per day" "94.9" ...
 $ : chr [1:5] " low in calcium" "Mean daily intake of calcium from all sources, including milk, yogurt, and cheese" "1.25 g (1.00–1.50) per day" "94.9" ...
 $ : chr [1:5] " low in seafood omega-3 Mean daily intake of eicosapentaenoic acid and docosahexaenoic acid" "250 mg (200–300) per day" "94.9" "fatty acids" ...
 $ : chr [1:7] " low in polyunsaturated" "Mean daily intake of omega-6 fatty acids from all sources, mainly liquid vegetable oils," "11% (9–13) of total daily energy" "94.9" ...
 $ : chr [1:6] " high in trans fatty acids" "Mean daily intake of trans fat from all sources, mainly partially hydrogenated vegetable oils" "0.5% (0.0–1.0) of total daily energy" "36.9" ...
 $ : chr [1:8] " high in sodium" "24 h urinary sodium measured in g per day" "3 g (1–5) per day*" "26.2" ...

Now we can see that each of our 15 strings has been split into pieces, but unfortunately, it was not completely consistent across dietary factors. Why did this happen? If we look closely, we can see that the sugar-sweetened beverage and the seafood category had only one space between the first and second columns. These are the columns about the dietary category and the one that describes in more detail what the consumption suggestion is about.

The values for these two columns appear to be together still in the same substring for these two categories. We can see this because there are no quotation marks adjacent to the word "Mean".

Here you can see how the next substring should have started with the word "Mean" by the new inclusion of a quotation mark ". The red rectangles indicate the problematic substrings, while the green rectangles show examples where the split worked correctly.

We can add an extra space in front of the word "Mean" for these particular categories and then try splitting again.

Since we originally split based on two or more spaces, we can just add a space in front of the word “Mean” for all the pdf_table strings and then try subsetting again. We can use the str_which() function of the stringr package to find the index of these particular cases.

pdf_table %>%
  str_which(pattern = "seafood|sugar")
[1]  9 12

Here we can use the str_subset() function of the stringr package to see just the strings that match these patterns within pdf_table:

pdf_table %>%
  str_subset(pattern = "seafood|sugar")
[1] " high in sugar-sweetened Mean daily consumption of beverages with =50 kcal per 226.8 serving, including carbonated                          3 g (0–5) per day                              36.9\n  beverages                    beverages, sodas, energy drinks, fruit drinks, but excluding 100% fruit and vegetable juices\n  "
[2] " low in seafood omega-3 Mean daily intake of eicosapentaenoic acid and docosahexaenoic acid                                              250 mg (200–300) per day                          94.9\n  fatty acids\n  "                                                                                                              

This is equivalent to using the str_which() function with []:

pdf_table[str_which(pdf_table, pattern = "seafood|sugar")]

Now we can replace these values within the pdf_table object after adding a space in front of “Mean”:

pdf_table[str_which(pdf_table,
  pattern =
    "seafood|sugar"
)] <- str_replace(
  string = pdf_table[str_which(pdf_table,
    pattern =
      "seafood|sugar"
  )],
  pattern = "Mean",
  replacement = " Mean"
)

And now we can try splitting again by two or more spaces:

table_split <- str_split(pdf_table, pattern = "\\s{2,}")

We could also just add a space in front of all the values of “Mean” in pdf_table since the split was performed based on two or more spaces. Thus the other elements in pdf_table would also be split just as before despite the additional space.

pdf_table <- pdf_table %>%
  stringr::str_replace(
    pattern = "Mean",
    replacement = " Mean"
  )
table_split <- str_split(pdf_table, pattern = "\\s{2,}")

# scroll the output!
glimpse(table_split)
List of 15
 $ : chr [1:6] " low in fruits" "Mean daily consumption of fruits (fresh, frozen, cooked, canned, or dried fruits, excluding" "250 g (200–300) per day" "94.9" ...
 $ : chr [1:7] " low in vegetables" "Mean daily consumption of vegetables (fresh, frozen, cooked, canned, or dried vegetables," "360 g (290–430) per day" "94.9" ...
 $ : chr [1:5] " low in legumes" "Mean daily consumption of legumes (fresh, frozen, cooked, canned, or dried legumes)" "60 g (50–70) per day" "94.9" ...
 $ : chr [1:7] " low in whole grains" "Mean daily consumption of whole grains (bran, germ, and endosperm in their natural" "125 g (100–150) per day" "94.9" ...
 $ : chr [1:5] " low in nuts and seeds" "Mean daily consumption of nut and seed foods" "21 g (16–25) per day" "94.9" ...
 $ : chr [1:6] " low in milk" "Mean daily consumption of milk including non-fat, low-fat, and full-fat milk, excluding soy" "435 g (350–520) per day" "94.9" ...
 $ : chr [1:6] " high in red meat" "Mean daily consumption of red meat (beef, pork, lamb, and goat, but excluding poultry, fish," "23 g (18–27) per day" "94.9" ...
 $ : chr [1:6] " high in processed meat" "Mean daily consumption of meat preserved by smoking, curing, salting, or addition of" "2 g (0–4) per day" "36.9" ...
 $ : chr [1:7] " high in sugar-sweetened" "Mean daily consumption of beverages with =50 kcal per 226.8 serving, including carbonated" "3 g (0–5) per day" "36.9" ...
 $ : chr [1:6] " low in fibre" "Mean daily intake of fibre from all sources including fruits, vegetables, grains, legumes, and" "24 g (19–28) per day" "94.9" ...
 $ : chr [1:5] " low in calcium" "Mean daily intake of calcium from all sources, including milk, yogurt, and cheese" "1.25 g (1.00–1.50) per day" "94.9" ...
 $ : chr [1:6] " low in seafood omega-3" "Mean daily intake of eicosapentaenoic acid and docosahexaenoic acid" "250 mg (200–300) per day" "94.9" ...
 $ : chr [1:7] " low in polyunsaturated" "Mean daily intake of omega-6 fatty acids from all sources, mainly liquid vegetable oils," "11% (9–13) of total daily energy" "94.9" ...
 $ : chr [1:6] " high in trans fatty acids" "Mean daily intake of trans fat from all sources, mainly partially hydrogenated vegetable oils" "0.5% (0.0–1.0) of total daily energy" "36.9" ...
 $ : chr [1:8] " high in sodium" "24 h urinary sodium measured in g per day" "3 g (1–5) per day*" "26.2" ...

Looks better!

We want just the first (the food category) and third column (the optimal consumption amount suggested) for each row in the table. However, the table is currently stored as a list of character vectors, so it is not quite so simple to extract these values.

We can use the map function of the purrr package to accomplish this.

The map function allows us to perform the same action multiple times across each element within an object, in this case, a list.

The following will allow us to select the first or third substring from each element of the pdf_table object.

category <- map(table_split, 1)
amount <- map(table_split, 3)
head(category)
[[1]]
[1] " low in fruits"

[[2]]
[1] " low in vegetables"

[[3]]
[1] " low in legumes"

[[4]]
[1] " low in whole grains"

[[5]]
[1] " low in nuts and seeds"

[[6]]
[1] " low in milk"
head(amount)
[[1]]
[1] "250 g (200–300) per day"

[[2]]
[1] "360 g (290–430) per day"

[[3]]
[1] "60 g (50–70) per day"

[[4]]
[1] "125 g (100–150) per day"

[[5]]
[1] "21 g (16–25) per day"

[[6]]
[1] "435 g (350–520) per day"

Now we will create a tibble using this data. However, currently both category and amount are of class list. To create a tibble we need to unlist the data to create vectors.

class(category)
[1] "list"
category %<>% unlist()
amount %<>% unlist()
class(category)
[1] "character"

category
 [1] " low in fruits"             " low in vegetables"        
 [3] " low in legumes"            " low in whole grains"      
 [5] " low in nuts and seeds"     " low in milk"              
 [7] " high in red meat"          " high in processed meat"   
 [9] " high in sugar-sweetened"   " low in fibre"             
[11] " low in calcium"            " low in seafood omega-3"   
[13] " low in polyunsaturated"    " high in trans fatty acids"
[15] " high in sodium"           
amount
 [1] "250 g (200–300) per day"             
 [2] "360 g (290–430) per day"             
 [3] "60 g (50–70) per day"                
 [4] "125 g (100–150) per day"             
 [5] "21 g (16–25) per day"                
 [6] "435 g (350–520) per day"             
 [7] "23 g (18–27) per day"                
 [8] "2 g (0–4) per day"                   
 [9] "3 g (0–5) per day"                   
[10] "24 g (19–28) per day"                
[11] "1.25 g (1.00–1.50) per day"          
[12] "250 mg (200–300) per day"            
[13] "11% (9–13) of total daily energy"    
[14] "0.5% (0.0–1.0) of total daily energy"
[15] "3 g (1–5) per day*"                  

We could have done all of this at once in one command like this:

category <- unlist(map(table_split, 1))
amount <- unlist(map(table_split, 3))

Now we will create a tibble, which is an important data frame structure in the tidyverse which allows us to use other packages in the tidyverse with our data.

We will name our tibble columns now as we create our tibble using the tibble() function of both the tidyr and the tibble packages, as names are required in tibbles.

guidelines <- tibble::tibble(
  category = category,
  amount = amount
)
guidelines
# A tibble: 15 x 2
   category                     amount                              
   <chr>                        <chr>                               
 1 " low in fruits"             250 g (200–300) per day             
 2 " low in vegetables"         360 g (290–430) per day             
 3 " low in legumes"            60 g (50–70) per day                
 4 " low in whole grains"       125 g (100–150) per day             
 5 " low in nuts and seeds"     21 g (16–25) per day                
 6 " low in milk"               435 g (350–520) per day             
 7 " high in red meat"          23 g (18–27) per day                
 8 " high in processed meat"    2 g (0–4) per day                   
 9 " high in sugar-sweetened"   3 g (0–5) per day                   
10 " low in fibre"              24 g (19–28) per day                
11 " low in calcium"            1.25 g (1.00–1.50) per day          
12 " low in seafood omega-3"    250 mg (200–300) per day            
13 " low in polyunsaturated"    11% (9–13) of total daily energy    
14 " high in trans fatty acids" 0.5% (0.0–1.0) of total daily energy
15 " high in sodium"            3 g (1–5) per day*                  

Looking pretty good!

Separating values within a variable


Recall that the main goal of this data wrangling is to extract the optimal intake level for each dietary factor. So while we have managed to pull and organize the data from the pdf table, we need to further process the results to isolate this numeric value.

Do to this, we want to separate the different numbers within the amount column, to isolate the optimal amount, and the optimal range, and eventually convert them to numeric values.

Recall what the original table looked like:

We can use the tidyr::separate() function to separate the data within the amount column into three new columns based on the optimal level and the optimal range. We can separate the values based on the open parentheses "(" and the long dash "–" characters. Again we will use the bar "|" to indicate that we want to separate by either character.

# The first column will be called optimal
# It will contain the 1st part of the amount column data before the "("
# The 2nd column will be called lower
# It will contain the data after the "("
# The 3rd column will be called upper
# It will contain the 2nd part of the data based on the "–"
# The "\\" are necessary - we will explain very soon

guidelines %<>%
  tidyr::separate(amount,
    c("optimal", "lower", "upper"),
    sep = "\\(|–"
  )

guidelines
# A tibble: 15 x 4
   category                     optimal   lower upper                     
   <chr>                        <chr>     <chr> <chr>                     
 1 " low in fruits"             "250 g "  200   300) per day              
 2 " low in vegetables"         "360 g "  290   430) per day              
 3 " low in legumes"            "60 g "   50    70) per day               
 4 " low in whole grains"       "125 g "  100   150) per day              
 5 " low in nuts and seeds"     "21 g "   16    25) per day               
 6 " low in milk"               "435 g "  350   520) per day              
 7 " high in red meat"          "23 g "   18    27) per day               
 8 " high in processed meat"    "2 g "    0     4) per day                
 9 " high in sugar-sweetened"   "3 g "    0     5) per day                
10 " low in fibre"              "24 g "   19    28) per day               
11 " low in calcium"            "1.25 g " 1.00  1.50) per day             
12 " low in seafood omega-3"    "250 mg " 200   300) per day              
13 " low in polyunsaturated"    "11% "    9     13) of total daily energy 
14 " high in trans fatty acids" "0.5% "   0.0   1.0) of total daily energy
15 " high in sodium"            "3 g "    1     5) per day*               

Let’s also create a new variable/column in our tibble that indicates the direction of over- or under-consumption that can be harmful for each dietary factor.

guidelines %<>%
  separate(category, c("direction", "food"), sep = " in ")
guidelines
# A tibble: 15 x 5
   direction food              optimal   lower upper                     
   <chr>     <chr>             <chr>     <chr> <chr>                     
 1 " low"    fruits            "250 g "  200   300) per day              
 2 " low"    vegetables        "360 g "  290   430) per day              
 3 " low"    legumes           "60 g "   50    70) per day               
 4 " low"    whole grains      "125 g "  100   150) per day              
 5 " low"    nuts and seeds    "21 g "   16    25) per day               
 6 " low"    milk              "435 g "  350   520) per day              
 7 " high"   red meat          "23 g "   18    27) per day               
 8 " high"   processed meat    "2 g "    0     4) per day                
 9 " high"   sugar-sweetened   "3 g "    0     5) per day                
10 " low"    fibre             "24 g "   19    28) per day               
11 " low"    calcium           "1.25 g " 1.00  1.50) per day             
12 " low"    seafood omega-3   "250 mg " 200   300) per day              
13 " low"    polyunsaturated   "11% "    9     13) of total daily energy 
14 " high"   trans fatty acids "0.5% "   0.0   1.0) of total daily energy
15 " high"   sodium            "3 g "    1     5) per day*               

If we wanted to remove the direction variable we could use the modify_at() function of the purrr package:

guidelines %>% purrr::modify_at("direction", ~NULL)

Data cleaning with regular expressions


OK, looking better, but we still need a bit of cleaning to remove symbols and extra words from the columns. Some of the extra symbols include: "%", ")" and the "*".

The "*" and the ")" are what we call metacharacters or regular expressions. These are characters that have special meanings.

Now we need the "\\" to indicate that we want these characters to be matched exactly and not interpreted as the meaning of the symbol. Recall that we used "\\(|–" earlier.

See here for more info about regular expressions in R.


Click here for a simple example of regular expressions using the str_count() function of the stringr package

The str_count() function counts the number of instances of a character string. In this case we will look for individual characters but you could also search for words or phrases.

regextest <- readr::read_file(here("docs", "regEx.txt"))
regextest
[1] "Testing for ts or\ttabs can be tricky.(yes, it really can!*)\r\n"

Count the letter t:

str_count(regextest, "t") # notice this doesn't include the t in the tab
[1] 5

Count tabs:

str_count(regextest, "\\t") # search for tab
[1] 1
# this would not work:
str_count(regextest, "[t]") # searches for the letter t
[1] 5

Count parentheses:

# this would not work because R thinks this is part of the code itself
# str_count(regextest, ")")
# this would not work because R thinks this is part of the code itself
# str_count(regextest, "\)")
str_count(regextest, "\\)") # this works!
[1] 1
# this works! because it is a punctuation character
str_count(regextest, "[)]")
[1] 1

Count the occurrence of the asterisk:

# this also does not work
# str_count(regextest, "*")
# nor does this
# str_count(regextest, "\*")
str_count(regextest, "\\*") # this works!
[1] 1
# this works! because it is a punctuation character
str_count(regextest, "[*]") # this works!
[1] 1

We also want to make a unit variable so that we can make sure that our units are consistent later.

guidelines %>%
  pull(optimal)
 [1] "250 g "  "360 g "  "60 g "   "125 g "  "21 g "   "435 g "  "23 g "  
 [8] "2 g "    "3 g "    "24 g "   "1.25 g " "250 mg " "11% "    "0.5% "  
[15] "3 g "   

Notice that the values that are percentages don’t have spaces between the number and the unit. We can separate the "optimal" values by a space or a percent symbol "%" using "|" to indicate that we want to separate by either. In this case we will lose the “%” and will need to add it back to those values.

guidelines %<>%
  separate(optimal,
    into = c("optimal", "unit"),
    sep = " |%",
    remove = FALSE
  )
guidelines
# A tibble: 15 x 6
   direction food              lower optimal unit  upper                     
   <chr>     <chr>             <chr> <chr>   <chr> <chr>                     
 1 " low"    fruits            200   250     "g"   300) per day              
 2 " low"    vegetables        290   360     "g"   430) per day              
 3 " low"    legumes           50    60      "g"   70) per day               
 4 " low"    whole grains      100   125     "g"   150) per day              
 5 " low"    nuts and seeds    16    21      "g"   25) per day               
 6 " low"    milk              350   435     "g"   520) per day              
 7 " high"   red meat          18    23      "g"   27) per day               
 8 " high"   processed meat    0     2       "g"   4) per day                
 9 " high"   sugar-sweetened   0     3       "g"   5) per day                
10 " low"    fibre             19    24      "g"   28) per day               
11 " low"    calcium           1.00  1.25    "g"   1.50) per day             
12 " low"    seafood omega-3   200   250     "mg"  300) per day              
13 " low"    polyunsaturated   9     11      ""    13) of total daily energy 
14 " high"   trans fatty acids 0.0   0.5     ""    1.0) of total daily energy
15 " high"   sodium            1     3       "g"   5) per day*               

Great, so to now we will add “%” to the unit variable for the "low in polyunsaturated" and "high in trans fatty acids" rows.

First we need to replace the empty values with NA using the na_if() function of the dplyr package.

guidelines %<>%
  na_if("")
guidelines
# A tibble: 15 x 6
   direction food              lower optimal unit  upper                     
   <chr>     <chr>             <chr> <chr>   <chr> <chr>                     
 1 " low"    fruits            200   250     g     300) per day              
 2 " low"    vegetables        290   360     g     430) per day              
 3 " low"    legumes           50    60      g     70) per day               
 4 " low"    whole grains      100   125     g     150) per day              
 5 " low"    nuts and seeds    16    21      g     25) per day               
 6 " low"    milk              350   435     g     520) per day              
 7 " high"   red meat          18    23      g     27) per day               
 8 " high"   processed meat    0     2       g     4) per day                
 9 " high"   sugar-sweetened   0     3       g     5) per day                
10 " low"    fibre             19    24      g     28) per day               
11 " low"    calcium           1.00  1.25    g     1.50) per day             
12 " low"    seafood omega-3   200   250     mg    300) per day              
13 " low"    polyunsaturated   9     11      <NA>  13) of total daily energy 
14 " high"   trans fatty acids 0.0   0.5     <NA>  1.0) of total daily energy
15 " high"   sodium            1     3       g     5) per day*               

Then to replace the NA values, we can use the replace_na() function in the tidyr package and the mutate() function of dplyr to specify which values to replace, in this case the NA values within the variable unit. Essentially this variable gets reassigned with the new values, as we mostly think of the mutate() function as creating new variables.

guidelines %<>%
  dplyr::mutate(unit = replace_na(unit, "%"))

# now just to show these rows
guidelines %>%
  filter(unit == "%")
# A tibble: 2 x 6
  direction food              lower optimal unit  upper                     
  <chr>     <chr>             <chr> <chr>   <chr> <chr>                     
1 " low"    polyunsaturated   9     11      %     13) of total daily energy 
2 " high"   trans fatty acids 0.0   0.5     %     1.0) of total daily energy

Let’s also move unit to be the last column. We can use the relocate() function of the dplyr package to do this. For more information about the relocate() function see here.

guidelines %<>%
  relocate(unit, .after = last_col())

To remove all of the remaining extra characters and words we will again use the stringr package. This time we will use the str_remove_all() function to remove all instances of these characters.

guidelines <- as_tibble(
  map(guidelines, str_remove_all,
    pattern = "\\) per day|\\) of total daily energy|\\*"
  )
)
guidelines
# A tibble: 15 x 6
   direction food              lower optimal upper unit 
   <chr>     <chr>             <chr> <chr>   <chr> <chr>
 1 " low"    fruits            200   250     300   g    
 2 " low"    vegetables        290   360     430   g    
 3 " low"    legumes           50    60      70    g    
 4 " low"    whole grains      100   125     150   g    
 5 " low"    nuts and seeds    16    21      25    g    
 6 " low"    milk              350   435     520   g    
 7 " high"   red meat          18    23      27    g    
 8 " high"   processed meat    0     2       4     g    
 9 " high"   sugar-sweetened   0     3       5     g    
10 " low"    fibre             19    24      28    g    
11 " low"    calcium           1.00  1.25    1.50  g    
12 " low"    seafood omega-3   200   250     300   mg   
13 " low"    polyunsaturated   9     11      13    %    
14 " high"   trans fatty acids 0.0   0.5     1.0   %    
15 " high"   sodium            1     3       5     g    

Nice! That’s pretty clean but we can do a bit more.

Data type conversion


One of the next things to notice about our data is all of our variables are of class character, which is not how we want them to be.

For example, the optimal amounts of consumption are currently of class character, which is indicated by the <chr> just below the column names/variable names of the guidelines tibble:

guidelines
# A tibble: 15 x 6
   direction food              lower optimal upper unit 
   <chr>     <chr>             <chr> <chr>   <chr> <chr>
 1 " low"    fruits            200   250     300   g    
 2 " low"    vegetables        290   360     430   g    
 3 " low"    legumes           50    60      70    g    
 4 " low"    whole grains      100   125     150   g    
 5 " low"    nuts and seeds    16    21      25    g    
 6 " low"    milk              350   435     520   g    
 7 " high"   red meat          18    23      27    g    
 8 " high"   processed meat    0     2       4     g    
 9 " high"   sugar-sweetened   0     3       5     g    
10 " low"    fibre             19    24      28    g    
11 " low"    calcium           1.00  1.25    1.50  g    
12 " low"    seafood omega-3   200   250     300   mg   
13 " low"    polyunsaturated   9     11      13    %    
14 " high"   trans fatty acids 0.0   0.5     1.0   %    
15 " high"   sodium            1     3       5     g    

To convert these values to numeric we use the mutate() and across() functions of the dplyr package.

The across() function has two main arguments: (i) the columns you want to operate on and (ii) the function or list of functions to apply to each column. In this case if we look at the beginning of the guidelines tibble, we can see that optimal, lower and upper should be converted. As these three columns are sequential, we can simply put a : between optimal and upper to indicate that we want all the variables in between these columns to be converted.

guidelines %<>%
  mutate(across(lower:upper, as.numeric))
guidelines
# A tibble: 15 x 6
   direction food              lower optimal upper unit 
   <chr>     <chr>             <dbl>   <dbl> <dbl> <chr>
 1 " low"    fruits              200  250    300   g    
 2 " low"    vegetables          290  360    430   g    
 3 " low"    legumes              50   60     70   g    
 4 " low"    whole grains        100  125    150   g    
 5 " low"    nuts and seeds       16   21     25   g    
 6 " low"    milk                350  435    520   g    
 7 " high"   red meat             18   23     27   g    
 8 " high"   processed meat        0    2      4   g    
 9 " high"   sugar-sweetened       0    3      5   g    
10 " low"    fibre                19   24     28   g    
11 " low"    calcium               1    1.25   1.5 g    
12 " low"    seafood omega-3     200  250    300   mg   
13 " low"    polyunsaturated       9   11     13   %    
14 " high"   trans fatty acids     0    0.5    1   %    
15 " high"   sodium                1    3      5   g    

Great! Now these variables are of class <dbl> (stands for double) which indicates that they are numeric. Here is a link for more information on numeric classes in R.

If we had not replaced the "·" interpunct values to a period, conversion from character to numeric would be problematic and would result in NA values.

Data value reassignments


We seem to have lost the word "beverages" from the "sugar-sweetened beverages" category, as well as "fatty acids" from the "seafood omega 3 fatty acids", and the "polyunsaturated fatty acids" categories as the full category name was listed on two lines within the table. We would like to replace these values with the full name.

To select the food variable we will show you several options. Only a couple will work well with reassigning the data in that particular variable within guidelines without assigning an intermediate data object. We will look using mutate_at(), pull(), select(), and two styles of brackets ["variable name"] and [["variablename"]].

The bracket ["variable name"] option and the select() option will grab a tibble (data frame) version of the food column out of guidelines. However we can’t start commands with select for assignments.

guidelines["food"] # same output as select
# A tibble: 15 x 1
   food             
   <chr>            
 1 fruits           
 2 vegetables       
 3 legumes          
 4 whole grains     
 5 nuts and seeds   
 6 milk             
 7 red meat         
 8 processed meat   
 9 sugar-sweetened  
10 fibre            
11 calcium          
12 seafood omega-3  
13 polyunsaturated  
14 trans fatty acids
15 sodium           
select(guidelines, "food") # same output as brackets
# A tibble: 15 x 1
   food             
   <chr>            
 1 fruits           
 2 vegetables       
 3 legumes          
 4 whole grains     
 5 nuts and seeds   
 6 milk             
 7 red meat         
 8 processed meat   
 9 sugar-sweetened  
10 fibre            
11 calcium          
12 seafood omega-3  
13 polyunsaturated  
14 trans fatty acids
15 sodium           

pull() and the bracket [["variable name"]] option in contrast, will grab the vector version of the food data:

pull(guidelines, "food") # get character vector not a tibble
 [1] "fruits"            "vegetables"        "legumes"          
 [4] "whole grains"      "nuts and seeds"    "milk"             
 [7] "red meat"          "processed meat"    "sugar-sweetened"  
[10] "fibre"             "calcium"           "seafood omega-3"  
[13] "polyunsaturated"   "trans fatty acids" "sodium"           
# bracket option:
guidelines[["food"]] # get character vector not a tibble
 [1] "fruits"            "vegetables"        "legumes"          
 [4] "whole grains"      "nuts and seeds"    "milk"             
 [7] "red meat"          "processed meat"    "sugar-sweetened"  
[10] "fibre"             "calcium"           "seafood omega-3"  
[13] "polyunsaturated"   "trans fatty acids" "sodium"           

The pull() function can be very useful when combined with other functions (for example you typically want to use a vector with the str_replace() function), but just like select, we can’t start assignments with pull().

This is not possible and will result in an error:

select(guidelines, food) <-
  str_replace(
    pull(guidelines, "food"),
    pattern = "sugar-sweetened",
    replacement = "sugar-sweetened beverages"
  )

guidelines %>% select(food) <-
  str_replace(
    pull(guidelines, "food"),
    pattern = "sugar-sweetened",
    replacement = "sugar-sweetened beverages"
  )

This will only print the result, but not reassign the food variable values:

guidelines %>%
  pull(food) %>%
  str_replace(
    pattern = "sugar-sweetened",
    replacement = "sugar-sweetened beverages"
  )
 [1] "fruits"                    "vegetables"               
 [3] "legumes"                   "whole grains"             
 [5] "nuts and seeds"            "milk"                     
 [7] "red meat"                  "processed meat"           
 [9] "sugar-sweetened beverages" "fibre"                    
[11] "calcium"                   "seafood omega-3"          
[13] "polyunsaturated"           "trans fatty acids"        
[15] "sodium"                   

Using select() would work as well to print the result (although the result structure is different):

guidelines %>%
  select(food) %>%
  str_replace(
    pattern = "sugar-sweetened",
    replacement = "sugar-sweetened beverages"
  )
[1] "c(\"fruits\", \"vegetables\", \"legumes\", \"whole grains\", \"nuts and seeds\", \"milk\", \"red meat\", \"processed meat\", \"sugar-sweetened beverages\", \"fibre\", \"calcium\", \"seafood omega-3\", \"polyunsaturated\", \"trans fatty acids\", \"sodium\")"

Question opportunity:

Why do these commands not reassign the food variable values?

The bracket option is great alternative and allows us to reassign the values within guidelines easily. Either of the two styles of brackets: ["variable name"] and [["variablename"]] will work.

# 1st method: `["variable name"]`
# Replacing "sugar-sweetened" with "sugar-sweetened beverages"
guidelines["food"] <-
  str_replace(
    pull(guidelines, "food"),
    pattern = "sugar-sweetened",
    replacement = "sugar-sweetened beverages"
  )

# 2nd method: `[["variablename"]]`
# Replacing "seafood omega-3" with"seafood omega-3 fatty acids"
guidelines[["food"]] <-
  str_replace(
    pull(guidelines, "food"),
    pattern = "seafood omega-3",
    replacement = "seafood omega-3 fatty acids"
  )

guidelines
# A tibble: 15 x 6
   direction food                        lower optimal upper unit 
   <chr>     <chr>                       <dbl>   <dbl> <dbl> <chr>
 1 " low"    fruits                        200  250    300   g    
 2 " low"    vegetables                    290  360    430   g    
 3 " low"    legumes                        50   60     70   g    
 4 " low"    whole grains                  100  125    150   g    
 5 " low"    nuts and seeds                 16   21     25   g    
 6 " low"    milk                          350  435    520   g    
 7 " high"   red meat                       18   23     27   g    
 8 " high"   processed meat                  0    2      4   g    
 9 " high"   sugar-sweetened beverages       0    3      5   g    
10 " low"    fibre                          19   24     28   g    
11 " low"    calcium                         1    1.25   1.5 g    
12 " low"    seafood omega-3 fatty acids   200  250    300   mg   
13 " low"    polyunsaturated                 9   11     13   %    
14 " high"   trans fatty acids               0    0.5    1   %    
15 " high"   sodium                          1    3      5   g    

Finally, the best option is probably the mutate_at() function from dplyr. In this case we need to include ~ in front of the function that we would like to use on the values in our food variables. We also include . as a replacement to reference the data that we want to use within str_replace() (which in this case is the food variable values of guidelines).

Notice we didn’t need this when we previously use mutate_at() with the as.numeric() function. This is because the str_replace() function requires us to specify what data we are using as one of the arguments, while as.numeric() does not.

# Replacing "polyunsaturated" with"polyunsaturated fatty acids"
guidelines %<>%
  mutate_at(
    vars(food),
    ~ str_replace(
      string = .,
      pattern = "polyunsaturated",
      replacement = "polyunsaturated fatty acids"
    )
  )

guidelines
# A tibble: 15 x 6
   direction food                        lower optimal upper unit 
   <chr>     <chr>                       <dbl>   <dbl> <dbl> <chr>
 1 " low"    fruits                        200  250    300   g    
 2 " low"    vegetables                    290  360    430   g    
 3 " low"    legumes                        50   60     70   g    
 4 " low"    whole grains                  100  125    150   g    
 5 " low"    nuts and seeds                 16   21     25   g    
 6 " low"    milk                          350  435    520   g    
 7 " high"   red meat                       18   23     27   g    
 8 " high"   processed meat                  0    2      4   g    
 9 " high"   sugar-sweetened beverages       0    3      5   g    
10 " low"    fibre                          19   24     28   g    
11 " low"    calcium                         1    1.25   1.5 g    
12 " low"    seafood omega-3 fatty acids   200  250    300   mg   
13 " low"    polyunsaturated fatty acids     9   11     13   %    
14 " high"   trans fatty acids               0    0.5    1   %    
15 " high"   sodium                          1    3      5   g    

This might be considered a better option because it is more readable as to where the food data came from that we are replacing values within.

There is one last minor detail… the direction variable has leading spaces still. We can use str_trim() to fix that!

guidelines %<>%
  mutate_at(vars(direction), str_trim)

guidelines
# A tibble: 15 x 6
   direction food                        lower optimal upper unit 
   <chr>     <chr>                       <dbl>   <dbl> <dbl> <chr>
 1 low       fruits                        200  250    300   g    
 2 low       vegetables                    290  360    430   g    
 3 low       legumes                        50   60     70   g    
 4 low       whole grains                  100  125    150   g    
 5 low       nuts and seeds                 16   21     25   g    
 6 low       milk                          350  435    520   g    
 7 high      red meat                       18   23     27   g    
 8 high      processed meat                  0    2      4   g    
 9 high      sugar-sweetened beverages       0    3      5   g    
10 low       fibre                          19   24     28   g    
11 low       calcium                         1    1.25   1.5 g    
12 low       seafood omega-3 fatty acids   200  250    300   mg   
13 low       polyunsaturated fatty acids     9   11     13   %    
14 high      trans fatty acids               0    0.5    1   %    
15 high      sodium                          1    3      5   g    

OK! Now we know how much of each dietary factor we generally need for optimal health according to the guidelines used in this article.

Comparing data


Recall that the main goal of pulling the guideline amounts from the pdf was that we would like to see how the mean consumption rates for the different groups of people compared to the optimal intake guidelines.

One way we could do this is to calculate a consumption percentage of the optimal value.

To calculate this it would be helpful to put the guideline amounts with the average consumption rates into the same tibble, especially because the observed consumption data (diet_data and sep_age_diet_data) are very different dimensions from the guidelines data.

In order to create a tibble with our observed consumption rates with the suggested consumption rates, we will join our data using dplyr. In order to do so it is important that our different data sets have at least one column with the same values that we can use to join them together. So let’s first assess if that is the case.

distinct(diet_data, dietary_risk)
# A tibble: 15 x 1
   dietary_risk                           
   <chr>                                  
 1 Diet high in processed meat            
 2 Diet high in red meat                  
 3 Diet high in sodium                    
 4 Diet high in sugar-sweetened beverages 
 5 Diet high in trans fatty acids         
 6 Diet low in calcium                    
 7 Diet low in fiber                      
 8 Diet low in fruits                     
 9 Diet low in legumes                    
10 Diet low in milk                       
11 Diet low in nuts and seeds             
12 Diet low in polyunsaturated fatty acids
13 Diet low in seafood omega-3 fatty acids
14 Diet low in vegetables                 
15 Diet low in whole grains               
select(guidelines, food)
# A tibble: 15 x 1
   food                       
   <chr>                      
 1 fruits                     
 2 vegetables                 
 3 legumes                    
 4 whole grains               
 5 nuts and seeds             
 6 milk                       
 7 red meat                   
 8 processed meat             
 9 sugar-sweetened beverages  
10 fibre                      
11 calcium                    
12 seafood omega-3 fatty acids
13 polyunsaturated fatty acids
14 trans fatty acids          
15 sodium                     

We are actually pretty close: there are 15 dietary factors in each data set, and the names are nearly the same. To make them match completely, we can see that we need to remove the "Diet low in" and "Diet high in" phrases from the observed consumption data.

diet_data %<>%
  mutate_at(
    vars(dietary_risk),
    ~ str_remove(
      string = .,
      pattern = "Diet low in |Diet high in "
    )
  )

sep_age_diet_data %<>%
  mutate_at(
    vars(dietary_risk),
    ~ str_remove(
      string = .,
      pattern = "Diet low in |Diet high in "
    )
  )

Also let’s double check that the two observed files have the same exact values for dietary factor names.

We can use the setequal() function from dplyr to check that the unique values for dietary_risk are the same for both diet_data and sep_age_diet_data.

setequal(
  distinct(diet_data, dietary_risk),
  distinct(sep_age_diet_data, dietary_risk)
)
[1] TRUE

Great!

Note that the default of the set_equal function ignores the order of values in rows. So we still don’t know if the order is the same.

We can check using the all_equal function of dplyr which reports back clues about what might be different if anything. Importantly we are including ignore_row_order = FALSE as the default is TRUE.

all_equal(distinct(diet_data, dietary_risk),
  distinct(sep_age_diet_data, dietary_risk),
  ignore_row_order = FALSE
)
[1] "Same row values, but different order"

Looks like they are not in the same order.

Note that if any of the values are different, all_equal() will first report this and will not report that the rows are in a different order.


Click here to see a toy example about how the three comparison functions (setequal(), all_equal() (also all.equal() for tbl_df), and setdiff()) work in dplyr.

It’s important to realize that row order is ignored by bothsetequal() and setdiff().

Now let’s compare two tibbles that have different row orders and different values.

Here are our tibbles to compare:

X <- tibble(test = c("A", "B", "AC", "D"))
Y <- tibble(test = c("A", "D", "A", "B"))
X
# A tibble: 4 x 1
  test 
  <chr>
1 A    
2 B    
3 AC   
4 D    
Y
# A tibble: 4 x 1
  test 
  <chr>
1 A    
2 D    
3 A    
4 B    
class(Y)
[1] "tbl_df"     "tbl"        "data.frame"

Since we are using tibbles, which are of class tbl_df we can use either all_equal or all.equal(). Notice that it doesn’t report rows being a different order because it first tells what rows have unique values or rows with a value that has a different number of frequency.

all_equal(X, Y, ignore_row_order = TRUE)
[1] "- Rows in x but not in y: 3\n"
all_equal(X, Y, ignore_row_order = FALSE)
[1] "- Rows in x but not in y: 3\n"
# Doesn't report rows being different order
all.equal(X, Y, ignore_row_order = TRUE)
[1] "Component \"test\": 3 string mismatches"
all.equal(X, Y, ignore_row_order = FALSE)
[1] "Component \"test\": 3 string mismatches"
# Doesn't report rows being different order

setequal() does not provide clues about what is different but TRUE (no differences) or FALSE (indicating at least one difference).

# Reports false indicating at least one difference
setequal(X, Y)
[1] FALSE

setdiff() tells us what is different and is dependent on the order of the objects compared, but prioritizes the values that are unique to each.

# This reports what is unique to X
setdiff(X, Y)
# A tibble: 1 x 1
  test 
  <chr>
1 AC   
# This reports what is unique to Y - nothing in this case
setdiff(Y, X)
# A tibble: 0 x 1
# ... with 1 variable: test <chr>

Now let’s make it so that only the order is different:

Y <- tibble(test = c("A", "D", "AC", "B"))
X
# A tibble: 4 x 1
  test 
  <chr>
1 A    
2 B    
3 AC   
4 D    
Y
# A tibble: 4 x 1
  test 
  <chr>
1 A    
2 D    
3 AC   
4 B    

Now that there are no values that are unique to either X or Y, all_equal() reports that there is a different order.

all_equal(X, Y, ignore_row_order = TRUE)
[1] TRUE
all_equal(X, Y, ignore_row_order = FALSE) # reports diff order
[1] "Same row values, but different order"

Remember setequal() ignores order and gives a value of TRUE for no differences.

# It reports no difference!
setequal(X, Y)
[1] TRUE

setdiff() also ignores order and shows no differences.

setdiff(X, Y)
# A tibble: 0 x 1
# ... with 1 variable: test <chr>

If we have different column/variable names this makes comparisons more challenging. Columns will be identified for having different names.

X <- tibble(colname1 = c("A", "B", "AC", "D"))
Y <- tibble(colname2 = c("A", "D", "AG", "B"))

all_equal() will simply report that col names are different

all_equal(X, Y, ignore_row_order = TRUE)
[1] "not compatible: \n- Cols in y but not x: `colname2`.\n- Cols in x but not y: `colname1`.\n"
all_equal(X, Y, ignore_row_order = FALSE)
[1] "not compatible: \n- Cols in y but not x: `colname2`.\n- Cols in x but not y: `colname1`.\n"

seteqaul() will report TRUE or FALSE to indicate either a difference in columns or rows

setequal(X, Y)
[1] FALSE

setdiff() requires that column names be the same so this will cause an error:

setdiff(X, Y) # This will not work

OK, let’s keep going with our data.

How similar are the guidelines tibble and the observed consumption tibbles?

setequal(
  distinct(diet_data, dietary_risk),
  select(guidelines, food)
)
[1] FALSE

OK, looks like we have some different values.

Let’s use the setdiff function to get more information about what is different between the values.

setdiff(
  distinct(diet_data, dietary_risk),
  select(guidelines, food)
)

:( That wont work. This is because setdiff() requires that the column names are the same in the objects that we are comparing.

We can use the rename() function from dplyr to do this. We list the value that we want to change to first. We find “food” more intuitive now so we are going to change “dietary_risk” to “food” for the diet_data and the sep_age_diet_data:

diet_data %<>%
  dplyr::rename(food = dietary_risk)
sep_age_diet_data %<>%
  dplyr::rename(food = dietary_risk)
setdiff(
  distinct(diet_data, food),
  select(guidelines, food)
)
# A tibble: 1 x 1
  food 
  <chr>
1 fiber

Great, now we know that the fiber value appears to be different between the two.

Checking our original files we can see that the British spelling “fibre” is used in the table from the article (that we used to create guidelines), in contrast to the American spelling “fiber” used in the CSV files.

Let’s stick with the American spelling, so we will replace "fibre" in the guideline tibble.

guidelines %<>%
  mutate_at(
    vars(food),
    ~ str_replace(
      string = .,
      pattern = "fibre",
      replacement = "fiber"
    )
  )

guidelines %>%
  filter(food == "fiber")
# A tibble: 1 x 6
  direction food  lower optimal upper unit 
  <chr>     <chr> <dbl>   <dbl> <dbl> <chr>
1 low       fiber    19      24    28 g    

Now let’s check again to see that our food values match between the guidelines and the observed consumption data tibbles.

setdiff(
  select(guidelines, food),
  distinct(diet_data, food)
)
# A tibble: 0 x 1
# ... with 1 variable: food <chr>
setdiff(
  select(guidelines, food),
  distinct(sep_age_diet_data, food)
)
# A tibble: 0 x 1
# ... with 1 variable: food <chr>

Great! There are no differences :)

Joining data


Now we can put our guideline data together with the diet_data and the sep_age_diet_data.

Remember that the food data in our guidelines tibble is not necessarily in the same order as that of the consumption data tibbles. Thus this could be a problem if we decided to expand the guidelines rows (to repeat for the number of fruit observations etc.) and add them to our observed consumption tibbles by binding them together by column.

[source]

In that case we could use the arrange() function of dplyr to sort the data alphabetically.

However, we will instead use a joining function of dplyr. These functions combine the data together based on common values and don’t require the rows to be in the same order. There are a variety of options.

[source]

In our case we would like to retain all of the values of diet_data and sep_age_diet_data. We would like to add new columns of values to these tibbles that correspond to the guideline information about amounts of consumption for each food type in the guidelines tibble. We shouldn’t have any values of food in guidelines that don’t match, so we will not get any NA values. Therefore, in our case any of the mutating join functions should result in the same output.

It’s important to check if we have any overlapping variable names before we join the data. Otherwise, these columns will either be used to identify which rows to join, or new copies of the columns, with a default name to distinguish the columns of one data set from those of the other, will be created. We can use the base R function names() and the intersect() function of the dplyr package to identify which column names are common to our two data sets.

dplyr::intersect(
  names(diet_data),
  names(guidelines)
)
[1] "food"  "upper" "lower" "unit" 

So it looks like the "upper" , "lower" and "unit" variable names are overlapping. Therefore, to distinguish the names later we will rename the guideline "upper" , "lower" and "unit" variables.

We will again use the rename function from the dplyr package. We can list multiple variables to rename and separate each with a comma. We need to list the new names first.

guidelines %<>%
  rename(
    upper_optimal = upper,
    lower_optimal = lower,
    unit_optimal = unit
  )

guidelines
# A tibble: 15 x 6
   direction food               lower_optimal optimal upper_optimal unit_optimal
   <chr>     <chr>                      <dbl>   <dbl>         <dbl> <chr>       
 1 low       fruits                       200  250            300   g           
 2 low       vegetables                   290  360            430   g           
 3 low       legumes                       50   60             70   g           
 4 low       whole grains                 100  125            150   g           
 5 low       nuts and seeds                16   21             25   g           
 6 low       milk                         350  435            520   g           
 7 high      red meat                      18   23             27   g           
 8 high      processed meat                 0    2              4   g           
 9 high      sugar-sweetened b~             0    3              5   g           
10 low       fiber                         19   24             28   g           
11 low       calcium                        1    1.25           1.5 g           
12 low       seafood omega-3 f~           200  250            300   mg          
13 low       polyunsaturated f~             9   11             13   %           
14 high      trans fatty acids              0    0.5            1   %           
15 high      sodium                         1    3              5   g           

It is also a good idea to check our units to make sure they are the same for both guidelines and the observed consumption tibbles(diet_and_guidelines and all_age_diet_and_guidelines).

Let’s take a look with the count() function of the dplyr package. We will also use the bind_cols() function of dplyr to put the data together so that we can see it easily.

dplyr::bind_cols(
  count(diet_data, unit, food),
  count(sep_age_diet_data, unit, food),
  count(guidelines, unit_optimal, food)
)
# A tibble: 15 x 9
   unit...1   food...2 n...3 unit...4 food...5 n...6 unit_optimal food...8 n...9
   <chr>      <chr>    <int> <chr>    <chr>    <int> <chr>        <chr>    <int>
 1 % energy/~ polyuns~   392 % energ~ polyuns~  5880 %            polyuns~     1
 2 % energy/~ trans f~   392 % energ~ trans f~  5880 %            trans f~     1
 3 g/day      calcium    392 g/day    calcium   5880 g            calcium      1
 4 g/day      fiber      392 g/day    fiber     5880 g            fiber        1
 5 g/day      fruits     392 g/day    fruits    5880 g            fruits       1
 6 g/day      legumes    392 g/day    legumes   5880 g            legumes      1
 7 g/day      milk       392 g/day    milk      5880 g            milk         1
 8 g/day      nuts an~   392 g/day    nuts an~  5880 g            nuts an~     1
 9 g/day      process~   392 g/day    process~  5880 g            process~     1
10 g/day      red meat   392 g/day    red meat  5880 g            red meat     1
11 g/day      seafood~   392 g/day    seafood~  5880 g            sodium       1
12 g/day      sodium     392 g/day    sodium    5880 g            sugar-s~     1
13 g/day      sugar-s~   392 g/day    sugar-s~  5880 g            vegetab~     1
14 g/day      vegetab~   392 g/day    vegetab~  5880 g            whole g~     1
15 g/day      whole g~   392 g/day    whole g~  5880 mg           seafood~     1

We can see that the only potential issue is the seafood omega-3 fatty acids data which is in g/day for the observed data(diet_data and all_age_diet_and_guidelines), but in mg/day in the guidelines data.

We can account for this by dividing the guidelines seafood omega-3 fatty acids data by 1000 to convert it to grams from milligrams.

To do this we will use the if_else() function in the dplyr package. This allows us to specify a condition (in this case if the unit is "mg"), as well as values if this condition is met (true), or if the condition is not met (false).

In the following we mutate the values in each of the guideline numeric columns (lower, optimal and upper) one at a time. When we refer to lower for example we refer to the values in the column/variable. So if the condition is not met, then the original value is retained. We will also replace "mg" with "g" after everything is converted to grams.

# "lower_optimal" variable
guidelines %<>%
  mutate(lower_optimal = dplyr::if_else(
    condition = unit_optimal == "mg",
    true = lower_optimal / 1000,
    false = lower_optimal
  ))
# Explanation for the use of "if_else()" here
# If the "unit_optimal" variable is in "mg", we convert the corresponding "lower_optimal" (currently in mg) variable to grams (g) by dividing by 1,000.
# If not, the corresponding "lower_optimal" (already in g) is not changed

# "optimal" variable
guidelines %<>%
  mutate(optimal = if_else(condition = unit_optimal == "mg",
    true = optimal / 1000,
    false = optimal
  ))

# "upper_optimal" variable
guidelines %<>%
  mutate(upper_optimal = if_else(condition = unit_optimal == "mg",
    true = upper_optimal / 1000,
    false = upper_optimal
  ))

# replace "mg" with "g" in the "unit_optimal" variable
guidelines %<>%
  mutate(unit_optimal = if_else(condition = unit_optimal == "mg",
    true = "g",
    false = unit_optimal
  ))

guidelines
# A tibble: 15 x 6
   direction food               lower_optimal optimal upper_optimal unit_optimal
   <chr>     <chr>                      <dbl>   <dbl>         <dbl> <chr>       
 1 low       fruits                     200    250            300   g           
 2 low       vegetables                 290    360            430   g           
 3 low       legumes                     50     60             70   g           
 4 low       whole grains               100    125            150   g           
 5 low       nuts and seeds              16     21             25   g           
 6 low       milk                       350    435            520   g           
 7 high      red meat                    18     23             27   g           
 8 high      processed meat               0      2              4   g           
 9 high      sugar-sweetened b~           0      3              5   g           
10 low       fiber                       19     24             28   g           
11 low       calcium                      1      1.25           1.5 g           
12 low       seafood omega-3 f~           0.2    0.25           0.3 g           
13 low       polyunsaturated f~           9     11             13   %           
14 high      trans fatty acids            0      0.5            1   %           
15 high      sodium                       1      3              5   g           

Click here to see a couple of other ways to do this:
# Another possible way with dplyr::case_when():
guidelines %<>%
  mutate(lower_optimal = case_when(
    unit_optimal == "mg" ~ lower_optimal / 1000,
    unit_optimal != "mg" ~ lower_optimal
  ))

# Or could use this:
guidelines %<>%
  mutate_at(
    vars(unit_optimal),
    ~ str_replace(
      string = .,
      pattern = "mg",
      replacement = "g"
    )
  )

In contrast we could have changed or mutated the values for lower_optimal, optimal, upper_optimal all at once like this using the funs() argument in mutate_at() of dplyr.

guidelines[str_which(
  string = guidelines[["food"]],
  pattern = "seafood omega-3 fatty acids"
), ] <- guidelines %>%
  filter(food == "seafood omega-3 fatty acids") %>%
  mutate_at(vars(lower_optimal:upper_optimal), funs(. / 1000))

Now we are ready to join the data!

Again, we would like to add new columns of values to diet_data and all_age_diet_and_guidelines that correspond to the guideline information about amounts of consumption for each food type in the guidelines tibble. So we will join the data based on the food variable values. We will use the full_join() function of the dplyr package.

diet_and_guidelines <- diet_data %>%
  dplyr::full_join(guidelines, by = "food")

all_age_diet_and_guidelines <- sep_age_diet_data %>%
  full_join(guidelines, by = "food")

glimpse(diet_and_guidelines)
Rows: 5,880
Columns: 16
$ year_id        <dbl> 2017, 2017, 2017, 2017, 2017, 2017, 2017, 2017, 2017, 2~
$ location_name  <chr> "Global", "Global", "China", "China", "North Korea", "N~
$ rei_id         <dbl> 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, ~
$ food           <chr> "processed meat", "processed meat", "processed meat", "~
$ age_group_name <chr> "All available ages", "All available ages", "All availa~
$ sex            <chr> "Male", "Female", "Male", "Female", "Male", "Female", "~
$ parameter      <chr> "continuous", "continuous", "continuous", "continuous",~
$ mean           <dbl> 4.2865629, 3.2640990, 2.3176975, 1.7512870, 0.5665229, ~
$ upper          <dbl> 4.4633117, 3.3765360, 2.6944978, 2.0454134, 0.6596296, ~
$ lower          <dbl> 4.1309531, 3.1547299, 1.9933744, 1.5161724, 0.4818201, ~
$ unit           <chr> "g/day", "g/day", "g/day", "g/day", "g/day", "g/day", "~
$ direction      <chr> "high", "high", "high", "high", "high", "high", "high",~
$ lower_optimal  <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0~
$ optimal        <dbl> 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2~
$ upper_optimal  <dbl> 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4~
$ unit_optimal   <chr> "g", "g", "g", "g", "g", "g", "g", "g", "g", "g", "g", ~
glimpse(all_age_diet_and_guidelines)
Rows: 88,200
Columns: 16
$ year_id        <dbl> 2017, 2017, 2017, 2017, 2017, 2017, 2017, 2017, 2017, 2~
$ location_name  <chr> "Global", "Global", "Global", "Global", "Global", "Glob~
$ food           <chr> "calcium", "calcium", "calcium", "calcium", "calcium", ~
$ age_group_id   <dbl> 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 30, 31, 32,~
$ age_group_name <chr> "25 to 29", "30 to 34", "35 to 39", "40 to 44", "45 to ~
$ sex            <chr> "Male", "Male", "Male", "Male", "Male", "Male", "Male",~
$ parameter      <chr> "continuous", "continuous", "continuous", "continuous",~
$ mean           <dbl> 0.4166193, 0.4253721, 0.4352053, 0.4412156, 0.4565795, ~
$ upper          <dbl> 0.4310974, 0.4403903, 0.4481282, 0.4579218, 0.4763366, ~
$ lower          <dbl> 0.4030347, 0.4110026, 0.4222248, 0.4264522, 0.4381799, ~
$ unit           <chr> "g/day", "g/day", "g/day", "g/day", "g/day", "g/day", "~
$ direction      <chr> "low", "low", "low", "low", "low", "low", "low", "low",~
$ lower_optimal  <dbl> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1~
$ optimal        <dbl> 1.25, 1.25, 1.25, 1.25, 1.25, 1.25, 1.25, 1.25, 1.25, 1~
$ upper_optimal  <dbl> 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, ~
$ unit_optimal   <chr> "g", "g", "g", "g", "g", "g", "g", "g", "g", "g", "g", ~

It’s always a good idea to check that the values are what you expect after merging.

diet_and_guidelines %>%
  count(food, optimal)
# A tibble: 15 x 3
   food                        optimal     n
   <chr>                         <dbl> <int>
 1 calcium                        1.25   392
 2 fiber                         24      392
 3 fruits                       250      392
 4 legumes                       60      392
 5 milk                         435      392
 6 nuts and seeds                21      392
 7 polyunsaturated fatty acids   11      392
 8 processed meat                 2      392
 9 red meat                      23      392
10 seafood omega-3 fatty acids    0.25   392
11 sodium                         3      392
12 sugar-sweetened beverages      3      392
13 trans fatty acids              0.5    392
14 vegetables                   360      392
15 whole grains                 125      392
all_age_diet_and_guidelines %>%
  count(food, optimal)
# A tibble: 15 x 3
   food                        optimal     n
   <chr>                         <dbl> <int>
 1 calcium                        1.25  5880
 2 fiber                         24     5880
 3 fruits                       250     5880
 4 legumes                       60     5880
 5 milk                         435     5880
 6 nuts and seeds                21     5880
 7 polyunsaturated fatty acids   11     5880
 8 processed meat                 2     5880
 9 red meat                      23     5880
10 seafood omega-3 fatty acids    0.25  5880
11 sodium                         3     5880
12 sugar-sweetened beverages      3     5880
13 trans fatty acids              0.5   5880
14 vegetables                   360     5880
15 whole grains                 125     5880
# For easy comparison we will arrange by food alphabetically
arrange(guidelines, food)
# A tibble: 15 x 6
   direction food               lower_optimal optimal upper_optimal unit_optimal
   <chr>     <chr>                      <dbl>   <dbl>         <dbl> <chr>       
 1 low       calcium                      1      1.25           1.5 g           
 2 low       fiber                       19     24             28   g           
 3 low       fruits                     200    250            300   g           
 4 low       legumes                     50     60             70   g           
 5 low       milk                       350    435            520   g           
 6 low       nuts and seeds              16     21             25   g           
 7 low       polyunsaturated f~           9     11             13   %           
 8 high      processed meat               0      2              4   g           
 9 high      red meat                    18     23             27   g           
10 low       seafood omega-3 f~           0.2    0.25           0.3 g           
11 high      sodium                       1      3              5   g           
12 high      sugar-sweetened b~           0      3              5   g           
13 high      trans fatty acids            0      0.5            1   %           
14 low       vegetables                 290    360            430   g           
15 low       whole grains               100    125            150   g           

Looks good!

Calculating relative consumption


Recall that our aim is to compare the consumption rates of these dietary factors by different groups of people, and ideally, to facilitate cross-factor comparisons, we want to consider consumption rates relative to the optimal guidelines.

To do this, let’s calculate values of consumption that are relative to the suggested guidelines.

There are a few approaches we could take. One is to calculate a "percentage of optimal consumption" using the mean value for each observed factor relative to its optimal value. To do this we will use the mutate() function of the dplyrpackage. This will create a new variable called Relative_Percent that will be equal to the ratio of the mean value and the optimal value, multiplied by 100, to create a percentage relative to the optimal amount suggested.

diet_and_guidelines %<>%
  mutate(Relative_Percent = (mean / optimal) * 100)

all_age_diet_and_guidelines %<>%
  mutate(Relative_Percent = (mean / optimal) * 100)

Another option is to incorporate the range of optimal intakes and the direction that is associated with health risk. If the direction of risk is high and the consumption was greater than the optimal mean value, than the percentage is calculated based on the upper_optimal value, while if the direction of risk is low and the consumption is less than the optimal mean value, then the percentage is calculated based on the lower_optimal value. We will use the case_when() function of the dplyr package to do this. This allows us to specify values (indicated on the right side of the ~symbol) based on specific conditions (indicated on the left side of the ~ symbol). We can specify multiple conditions using the & symbol.

diet_and_guidelines %<>%
  mutate(range_percent = case_when(
    direction == "high" ~ (mean / upper_optimal) * 100,
    direction == "low" ~ (mean / lower_optimal) * 100
  ))

all_age_diet_and_guidelines %<>%
  mutate(range_percent = case_when(
    direction == "high" ~ (mean / upper_optimal) * 100,
    direction == "low" ~ (mean / lower_optimal) * 100
  ))


diet_and_guidelines %<>%
  mutate(percent_over_under = case_when(
    direction == "high" & mean > upper_optimal ~
    ((mean - upper_optimal) / upper_optimal) * 100,
    direction == "high" & mean <= upper_optimal ~ 0,
    direction == "low" & mean >= lower_optimal ~ 0,
    direction == "low" & mean < lower_optimal ~
    ((lower_optimal - mean) / lower_optimal) * -100
  ))


all_age_diet_and_guidelines %<>%
  mutate(percent_over_under = case_when(
    direction == "high" & mean > upper_optimal ~
    ((mean - upper_optimal) / upper_optimal) * 100,
    direction == "high" & mean <= upper_optimal ~ 0,
    direction == "low" & mean >= lower_optimal ~ 0,
    direction == "low" & mean < lower_optimal ~
    ((lower_optimal - mean) / lower_optimal) * -100
  ))

Another option is to create a binary outcome indicating whether optimal consumption was achieved or not.

diet_and_guidelines %<>%
  mutate(opt_achieved = if_else(
    condition = direction == "low" & mean > lower_optimal |
      direction == "high" & mean < upper_optimal,
    true = "Yes",
    false = "No"
  ))

all_age_diet_and_guidelines %<>%
  mutate(opt_achieved = if_else(
    condition = direction == "low" & mean > lower_optimal |
      direction == "high" & mean < upper_optimal,
    true = "Yes",
    false = "No"
  ))

glimpse(diet_and_guidelines)
Rows: 5,880
Columns: 20
$ year_id            <dbl> 2017, 2017, 2017, 2017, 2017, 2017, 2017, 2017, 201~
$ location_name      <chr> "Global", "Global", "China", "China", "North Korea"~
$ rei_id             <dbl> 117, 117, 117, 117, 117, 117, 117, 117, 117, 117, 1~
$ food               <chr> "processed meat", "processed meat", "processed meat~
$ age_group_name     <chr> "All available ages", "All available ages", "All av~
$ sex                <chr> "Male", "Female", "Male", "Female", "Male", "Female~
$ parameter          <chr> "continuous", "continuous", "continuous", "continuo~
$ mean               <dbl> 4.2865629, 3.2640990, 2.3176975, 1.7512870, 0.56652~
$ upper              <dbl> 4.4633117, 3.3765360, 2.6944978, 2.0454134, 0.65962~
$ lower              <dbl> 4.1309531, 3.1547299, 1.9933744, 1.5161724, 0.48182~
$ unit               <chr> "g/day", "g/day", "g/day", "g/day", "g/day", "g/day~
$ direction          <chr> "high", "high", "high", "high", "high", "high", "hi~
$ lower_optimal      <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ~
$ optimal            <dbl> 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, ~
$ upper_optimal      <dbl> 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, ~
$ unit_optimal       <chr> "g", "g", "g", "g", "g", "g", "g", "g", "g", "g", "~
$ Relative_Percent   <dbl> 214.32814, 163.20495, 115.88487, 87.56435, 28.32615~
$ range_percent      <dbl> 107.164072, 81.602474, 57.942437, 43.782175, 14.163~
$ percent_over_under <dbl> 7.164072, 0.000000, 0.000000, 0.000000, 0.000000, 0~
$ opt_achieved       <chr> "No", "Yes", "Yes", "Yes", "Yes", "Yes", "Yes", "Ye~
glimpse(all_age_diet_and_guidelines)
Rows: 88,200
Columns: 20
$ year_id            <dbl> 2017, 2017, 2017, 2017, 2017, 2017, 2017, 2017, 201~
$ location_name      <chr> "Global", "Global", "Global", "Global", "Global", "~
$ food               <chr> "calcium", "calcium", "calcium", "calcium", "calciu~
$ age_group_id       <dbl> 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 30, 31,~
$ age_group_name     <chr> "25 to 29", "30 to 34", "35 to 39", "40 to 44", "45~
$ sex                <chr> "Male", "Male", "Male", "Male", "Male", "Male", "Ma~
$ parameter          <chr> "continuous", "continuous", "continuous", "continuo~
$ mean               <dbl> 0.4166193, 0.4253721, 0.4352053, 0.4412156, 0.45657~
$ upper              <dbl> 0.4310974, 0.4403903, 0.4481282, 0.4579218, 0.47633~
$ lower              <dbl> 0.4030347, 0.4110026, 0.4222248, 0.4264522, 0.43817~
$ unit               <chr> "g/day", "g/day", "g/day", "g/day", "g/day", "g/day~
$ direction          <chr> "low", "low", "low", "low", "low", "low", "low", "l~
$ lower_optimal      <dbl> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, ~
$ optimal            <dbl> 1.25, 1.25, 1.25, 1.25, 1.25, 1.25, 1.25, 1.25, 1.2~
$ upper_optimal      <dbl> 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1.5, 1~
$ unit_optimal       <chr> "g", "g", "g", "g", "g", "g", "g", "g", "g", "g", "~
$ Relative_Percent   <dbl> 33.32954, 34.02977, 34.81642, 35.29725, 36.52636, 3~
$ range_percent      <dbl> 41.66193, 42.53721, 43.52053, 44.12156, 45.65795, 4~
$ percent_over_under <dbl> -58.33807, -57.46279, -56.47947, -55.87844, -54.342~
$ opt_achieved       <chr> "No", "No", "No", "No", "No", "No", "No", "No", "No~

One last thing that can be useful with data wrangling is to reshape the data into what is called the long format. This is very useful for creating visualizations with a powerful and flexible package called ggplot2.

To coerce an object into long format, we create more rows and fewer columns. For more information about this, please see the Data Visualization section of this case study.

We would like to put together the different types of percentages of the optimal intake that we just calculated.

To get our data in long format we can use the pivot_longer() function of the tidyr package. We will list the columns that we want to come together into the longer format using the cols argument. The names_to argument indicates the name of the variable that will include the character information about the values that we are consolidating, i.e., this variable contains the names of the columns that we are bringing together. The values_to is the name of the column that will contain the values of the columns we are consolidating. We can use contains() of the tidyr package to look at the variables with names that contain "percent" .

diet_and_guidelines_long <- diet_and_guidelines %>%
  pivot_longer(
    cols = contains("percent"),
    names_to = "percent_type",
    values_to = "percent"
  )

Click here to see how this would be done with the older version of this function, called gather():

Recall that for pivot_longer(), the cols argument is used. For gather() we would simply list the variables that we wish to consolidate. The names_to and values_to arguments of pivot_longer() are equivalent to the key and value arguments in gather() respectively.

We would get an identical output from the two methods. We can check that with setequal().

diet_and_guidelines_long2 <- diet_and_guidelines %>%
  gather(contains("percent"),
    key = percent_type,
    value = percent
  )

setequal(diet_and_guidelines_long, diet_and_guidelines_long2)
[1] TRUE

Let’s do the same for the age separated data.

all_age_diet_and_guidelines_long <- all_age_diet_and_guidelines %>%
  pivot_longer(
    cols = contains("percent"),
    names_to = "percent_type",
    values_to = "percent"
  )

We now have the main variables and data formats that we need to proceed with the next steps of our analysis, including data exploration and eventually, modeling.

Now we will save our wrangled data. We will save it as an rda file for ourselves and as csv files, as this is often a good option for collaborators. We need a separate csv file for each tibble. We will save these files in a directory called “wrangled” within our “data” directory of our project.

save(all_age_diet_and_guidelines, all_age_diet_and_guidelines_long, diet_and_guidelines, sep_age_diet_data, file = here::here("data", "wrangled", "wrangled_data.rda"))

write_csv(all_age_diet_and_guidelines, file = here::here("data", "wrangled", "all_age_diet_and_guidelines.csv"))
write_csv(all_age_diet_and_guidelines_long, file = here::here("data", "wrangled", "all_age_diet_and_guidelines_long.csv"))
write_csv(diet_and_guidelines, file = here::here("data", "wrangled", "diet_and_guidelines.csv"))
write_csv(sep_age_diet_data, file = here::here("data", "wrangled", "sep_age_diet_data.csv"))

Data Exploration


If you have been following along but stopped you could load the wrangled data like so:

load(here::here("data", "wrangled", "wrangled_data.rda"))

If you skipped the data wrangling section click here.

First you need to install and load the OCSdata package:

install.packages("OCSdata")
library(OCSdata)

Then, you may load the wrangled data using the following code:

wrangled_rda("ocs-bp-diet", outpath = getwd())
load(here::here("OCSdata", "data", "wrangled", "wrangled_data.rda"))

If the package does not work for you, alternatively, an RDA file (stands for R data) of the data can be found here or slightly more directly here. Download this file and then place it in your current working directory within a subdirectory called “wrangled” within a subdirectory called “data” to copy and paste our code. We used an RStudio project and the here package to navigate to the file more easily.

load(here::here("data", "wrangled", "wrangled_data.rda"))

Click here to see more about creating new projects in RStudio.

You can create a project by going to the File menu of RStudio like so:

You can also do so by clicking the project button:

See here to learn more about using RStudio projects and here to learn more about the here package.



Exploring age collapsed data


Let’s start by taking a look at the percent of consumption, across all dietary factors. Again we will use the base R summary() function:

diet_and_guidelines %>%
  select(Relative_Percent) %>%
  summary()
 Relative_Percent  
 Min.   :    0.02  
 1st Qu.:   11.20  
 Median :   34.71  
 Mean   :  303.03  
 3rd Qu.:   73.12  
 Max.   :13724.61  

Wow! Some of the values are nearly zero, suggesting that some people are consuming basically zero percent of what is suggested for optimal health. On the other hand, for some dietary factors people are consuming over 13,000 percent what is suggested!

This is why it is important to look at the direction of consumption that could be harmful. For example if there is a population that consumes large amounts of vegetables this could be a good thing, but if there is a population consuming large amounts of sodium this would be a bad thing.

Let’s take a look to see what dietary factors are at the extremes by arranging the data using the arrange() function of the dplyr package. We can arrange by smallest to largest using the default and we can arrange largest to smallest using the minus sign -.

diet_and_guidelines %>%
  arrange(-Relative_Percent) %>%
  glimpse()
Rows: 5,880
Columns: 20
$ year_id            <dbl> 2017, 2017, 2017, 2017, 2017, 2017, 2017, 2017, 201~
$ location_name      <chr> "Guatemala", "Honduras", "Nicaragua", "Costa Rica",~
$ rei_id             <dbl> 118, 118, 118, 118, 118, 118, 118, 118, 118, 118, 1~
$ food               <chr> "sugar-sweetened beverages", "sugar-sweetened bever~
$ age_group_name     <chr> "All available ages", "All available ages", "All av~
$ sex                <chr> "Male", "Male", "Male", "Male", "Male", "Male", "Ma~
$ parameter          <chr> "continuous", "continuous", "continuous", "continuo~
$ mean               <dbl> 411.7382, 390.2302, 381.4949, 378.7929, 374.6964, 3~
$ upper              <dbl> 444.4002, 423.6780, 411.2766, 407.2788, 402.3668, 3~
$ lower              <dbl> 381.8858, 364.0112, 353.7633, 353.6314, 348.9515, 3~
$ unit               <chr> "g/day", "g/day", "g/day", "g/day", "g/day", "g/day~
$ direction          <chr> "high", "high", "high", "high", "high", "high", "hi~
$ lower_optimal      <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ~
$ optimal            <dbl> 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, ~
$ upper_optimal      <dbl> 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, ~
$ unit_optimal       <chr> "g", "g", "g", "g", "g", "g", "g", "g", "g", "g", "~
$ Relative_Percent   <dbl> 13724.608, 13007.674, 12716.496, 12626.430, 12489.8~
$ range_percent      <dbl> 8234.765, 7804.605, 7629.897, 7575.858, 7493.927, 7~
$ percent_over_under <dbl> 8134.765, 7704.605, 7529.897, 7475.858, 7393.927, 7~
$ opt_achieved       <chr> "No", "No", "No", "No", "No", "No", "No", "No", "No~

OK, so it looks like sugar-sweetened beverages are really over-consumed in some parts of the world!

Recall from the supplementary table from the article that over-consumption of sugar-sweetened beverages is associated with both Diabetes mellitus type 2 and Ischemic heart disease. This article discusses some of the controversy over the potential health risks associated with high consumption of sugar.

It still looks quite bad if we look at the other calculated percentage values.

diet_and_guidelines %>%
  select(contains("percent")) %>%
  summary()
 Relative_Percent   range_percent      percent_over_under
 Min.   :    0.02   Min.   :   0.025   Min.   : -99.97   
 1st Qu.:   11.20   1st Qu.:  13.263   1st Qu.: -77.60   
 Median :   34.71   Median :  38.941   Median : -42.20   
 Mean   :  303.03   Mean   : 196.373   Mean   : 108.59   
 3rd Qu.:   73.12   3rd Qu.:  72.414   3rd Qu.:   0.00   
 Max.   :13724.61   Max.   :8234.765   Max.   :8134.77   

So some places are still consuming 8,000 percent more than the upper range of the suggested optimal intake.

Let’s take a look at global levels:

diet_and_guidelines %>%
  filter(food == "sugar-sweetened beverages" &
    location_name == "Global")
# A tibble: 2 x 20
  year_id location_name rei_id food   age_group_name sex   parameter  mean upper
    <dbl> <chr>          <dbl> <chr>  <chr>          <chr> <chr>     <dbl> <dbl>
1    2017 Global           118 sugar~ All available~ Male  continuo~ 114.  123. 
2    2017 Global           118 sugar~ All available~ Fema~ continuo~  80.2  86.3
# ... with 11 more variables: lower <dbl>, unit <chr>, direction <chr>,
#   lower_optimal <dbl>, optimal <dbl>, upper_optimal <dbl>,
#   unit_optimal <chr>, Relative_Percent <dbl>, range_percent <dbl>,
#   percent_over_under <dbl>, opt_achieved <chr>

For those who are less familiar with the metric system where grams are equivalent to milliliters, it may be useful to realize how many fluid ounces the max amount of consumption per day (~444g for the upper value for Guatemala) actually is.

There are 0.35247 ounces in one gram.

# top amount in ounces
0.35247 * 444.4002
[1] 156.6377

OK, so the top consumers are drinking about 87 fluid ounces per day. Since there are 12 ounces in a single can of soda, this is about 7.25 sodas per day. Globally on average, males are drinking around 1.924 sodas worth of sweetened beverages, while females are drinking about 1.401.

Let’s take a look at what is under-consumed:

diet_and_guidelines %>%
  arrange(Relative_Percent) %>%
  glimpse()
Rows: 5,880
Columns: 20
$ year_id            <dbl> 2017, 2017, 2017, 2017, 2017, 2017, 2017, 2017, 201~
$ location_name      <chr> "Chad", "Chad", "Mali", "Mali", "Burkina Faso", "Bu~
$ rei_id             <dbl> 122, 122, 122, 122, 122, 122, 122, 122, 122, 122, 1~
$ food               <chr> "polyunsaturated fatty acids", "polyunsaturated fat~
$ age_group_name     <chr> "All available ages", "All available ages", "All av~
$ sex                <chr> "Female", "Male", "Female", "Male", "Female", "Male~
$ parameter          <chr> "continuous", "continuous", "continuous", "continuo~
$ mean               <dbl> 0.002227074, 0.002295916, 0.002301266, 0.002373331,~
$ upper              <dbl> 0.002383410, 0.002439931, 0.002461209, 0.002520039,~
$ lower              <dbl> 0.002082132, 0.002161922, 0.002152212, 0.002229945,~
$ unit               <chr> "% energy/day", "% energy/day", "% energy/day", "% ~
$ direction          <chr> "low", "low", "low", "low", "low", "low", "low", "l~
$ lower_optimal      <dbl> 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, ~
$ optimal            <dbl> 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11,~
$ upper_optimal      <dbl> 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13,~
$ unit_optimal       <chr> "%", "%", "%", "%", "%", "%", "%", "%", "%", "%", "~
$ Relative_Percent   <dbl> 0.02024612, 0.02087197, 0.02092060, 0.02157574, 0.0~
$ range_percent      <dbl> 0.02474526, 0.02551018, 0.02556963, 0.02637035, 0.0~
$ percent_over_under <dbl> -99.97525, -99.97449, -99.97443, -99.97363, -99.960~
$ opt_achieved       <chr> "No", "No", "No", "No", "No", "No", "No", "No", "No~

On the other hand, it looks like some places are consuming almost no polyunsaturated fatty acids. These are fats that found in plant-based sources like seeds and nuts. According to an article about polyunsaturated fatty acids and its influence on health:

Coronary heart disease (CHD) is the leading cause of death worldwide … The types of dietary fats consumed play an important role in CHD risk, representing key modifiable risk factors…In particular, higher intakes of trans fat (TFA) and of saturated fat (SFA) replacing ω‐6 (n‐6) polyunsaturated fat (PUFA) are associated with increased CHD… whereas higher intake of PUFA replacing either SFA or carbohydrate is associated with lower risk.

Let’s get an idea about how countries compare in terms of how many of the dietary factors are consumed at the optimal level (the opt_achieved variable).

diet_and_guidelines %>%
  count(opt_achieved)
# A tibble: 2 x 2
  opt_achieved     n
  <chr>        <int>
1 No            4360
2 Yes           1520

Looks like overall, only 34.86% of dietary factors for all tested populations were at optimal levels.

Let’s get an idea about how countries compare on this metric.

diet_and_guidelines %>%
  count(opt_achieved, location_name) %>%
  filter(opt_achieved == "Yes") %>%
  arrange(-n) %>%
  # this allows us to show the full output
  print(n = 1e3)
# A tibble: 196 x 3
    opt_achieved location_name                        n
    <chr>        <chr>                            <int>
  1 Yes          Qatar                               13
  2 Yes          Rwanda                              13
  3 Yes          Turkey                              13
  4 Yes          Dominican Republic                  12
  5 Yes          Egypt                               12
  6 Yes          Malawi                              12
  7 Yes          Benin                               11
  8 Yes          Burkina Faso                        11
  9 Yes          Cameroon                            11
 10 Yes          Cuba                                11
 11 Yes          Ethiopia                            11
 12 Yes          Ghana                               11
 13 Yes          Lesotho                             11
 14 Yes          Mali                                11
 15 Yes          Nigeria                             11
 16 Yes          Senegal                             11
 17 Yes          Togo                                11
 18 Yes          Tunisia                             11
 19 Yes          Angola                              10
 20 Yes          Azerbaijan                          10
 21 Yes          Bahrain                             10
 22 Yes          Barbados                            10
 23 Yes          Bermuda                             10
 24 Yes          Chad                                10
 25 Yes          Comoros                             10
 26 Yes          Cote d'Ivoire                       10
 27 Yes          El Salvador                         10
 28 Yes          Guatemala                           10
 29 Yes          Haiti                               10
 30 Yes          India                               10
 31 Yes          Iran                                10
 32 Yes          Kenya                               10
 33 Yes          Lebanon                             10
 34 Yes          Mauritania                          10
 35 Yes          Mauritius                           10
 36 Yes          Morocco                             10
 37 Yes          Mozambique                          10
 38 Yes          Myanmar                             10
 39 Yes          Nepal                               10
 40 Yes          Nicaragua                           10
 41 Yes          Niger                               10
 42 Yes          North Korea                         10
 43 Yes          Sao Tome and Principe               10
 44 Yes          Sierra Leone                        10
 45 Yes          South Sudan                         10
 46 Yes          Tanzania                            10
 47 Yes          Timor-Leste                         10
 48 Yes          Uganda                              10
 49 Yes          United Arab Emirates                10
 50 Yes          Zambia                              10
 51 Yes          Armenia                              9
 52 Yes          Bosnia and Herzegovina               9
 53 Yes          Burundi                              9
 54 Yes          Costa Rica                           9
 55 Yes          Equatorial Guinea                    9
 56 Yes          Eritrea                              9
 57 Yes          Fiji                                 9
 58 Yes          Honduras                             9
 59 Yes          Iraq                                 9
 60 Yes          Kuwait                               9
 61 Yes          Oman                                 9
 62 Yes          Papua New Guinea                     9
 63 Yes          Solomon Islands                      9
 64 Yes          Tajikistan                           9
 65 Yes          Trinidad and Tobago                  9
 66 Yes          Uzbekistan                           9
 67 Yes          Virgin Islands, U.S.                 9
 68 Yes          Afghanistan                          8
 69 Yes          Albania                              8
 70 Yes          Algeria                              8
 71 Yes          American Samoa                       8
 72 Yes          Antigua and Barbuda                  8
 73 Yes          Bangladesh                           8
 74 Yes          Belize                               8
 75 Yes          Bhutan                               8
 76 Yes          Botswana                             8
 77 Yes          Brazil                               8
 78 Yes          Cambodia                             8
 79 Yes          Cape Verde                           8
 80 Yes          Congo                                8
 81 Yes          Democratic Republic of the Congo     8
 82 Yes          Djibouti                             8
 83 Yes          Dominica                             8
 84 Yes          Federated States of Micronesia       8
 85 Yes          Gabon                                8
 86 Yes          Georgia                              8
 87 Yes          Grenada                              8
 88 Yes          Guam                                 8
 89 Yes          Guinea                               8
 90 Yes          Guinea-Bissau                        8
 91 Yes          Guyana                               8
 92 Yes          Indonesia                            8
 93 Yes          Israel                               8
 94 Yes          Jamaica                              8
 95 Yes          Jordan                               8
 96 Yes          Kazakhstan                           8
 97 Yes          Kiribati                             8
 98 Yes          Kyrgyzstan                           8
 99 Yes          Laos                                 8
100 Yes          Liberia                              8
101 Yes          Libya                                8
102 Yes          Macedonia                            8
103 Yes          Madagascar                           8
104 Yes          Malaysia                             8
105 Yes          Maldives                             8
106 Yes          Marshall Islands                     8
107 Yes          Mexico                               8
108 Yes          Moldova                              8
109 Yes          Namibia                              8
110 Yes          Northern Mariana Islands             8
111 Yes          Pakistan                             8
112 Yes          Palestine                            8
113 Yes          Peru                                 8
114 Yes          Philippines                          8
115 Yes          Saint Lucia                          8
116 Yes          Saint Vincent and the Grenadines     8
117 Yes          Samoa                                8
118 Yes          Saudi Arabia                         8
119 Yes          Seychelles                           8
120 Yes          Somalia                              8
121 Yes          Sri Lanka                            8
122 Yes          Sudan                                8
123 Yes          Suriname                             8
124 Yes          Swaziland                            8
125 Yes          Syria                                8
126 Yes          Taiwan (Province of China)           8
127 Yes          Thailand                             8
128 Yes          The Bahamas                          8
129 Yes          The Gambia                           8
130 Yes          Tonga                                8
131 Yes          Turkmenistan                         8
132 Yes          Vanuatu                              8
133 Yes          Yemen                                8
134 Yes          Zimbabwe                             8
135 Yes          Bolivia                              7
136 Yes          Central African Republic             7
137 Yes          Luxembourg                           7
138 Yes          Montenegro                           7
139 Yes          Panama                               7
140 Yes          Paraguay                             7
141 Yes          Puerto Rico                          7
142 Yes          South Africa                         7
143 Yes          Ukraine                              7
144 Yes          Venezuela                            7
145 Yes          Vietnam                              7
146 Yes          Canada                               6
147 Yes          Colombia                             6
148 Yes          Greece                               6
149 Yes          Japan                                6
150 Yes          Malta                                6
151 Yes          Mongolia                             6
152 Yes          Portugal                             6
153 Yes          Romania                              6
154 Yes          Serbia                               6
155 Yes          Argentina                            5
156 Yes          Belarus                              5
157 Yes          Bulgaria                             5
158 Yes          Croatia                              5
159 Yes          Ecuador                              5
160 Yes          Finland                              5
161 Yes          Global                               5
162 Yes          Iceland                              5
163 Yes          Netherlands                          5
164 Yes          Russian Federation                   5
165 Yes          Sweden                               5
166 Yes          Andorra                              4
167 Yes          Australia                            4
168 Yes          Austria                              4
169 Yes          Belgium                              4
170 Yes          Brunei                               4
171 Yes          Chile                                4
172 Yes          China                                4
173 Yes          Cyprus                               4
174 Yes          Denmark                              4
175 Yes          Estonia                              4
176 Yes          France                               4
177 Yes          Germany                              4
178 Yes          Ireland                              4
179 Yes          Italy                                4
180 Yes          Latvia                               4
181 Yes          Lithuania                            4
182 Yes          New Zealand                          4
183 Yes          Norway                               4
184 Yes          Poland                               4
185 Yes          Singapore                            4
186 Yes          South Korea                          4
187 Yes          Spain                                4
188 Yes          Switzerland                          4
189 Yes          United Kingdom                       4
190 Yes          Uruguay                              4
191 Yes          Czech Republic                       3
192 Yes          Greenland                            3
193 Yes          Hungary                              3
194 Yes          Slovakia                             3
195 Yes          Slovenia                             3
196 Yes          United States                        3

It looks as though on average the populations (both male and female separately) in Qatar, Rwanda, and Turkey consumed the optimal level of intake for the largest number of dietary factors (13 out of 30 (for the 15 dietary factors for males and females)).

In contrast, the Czech Republic, Greenland, Hungary, Slovakia, Slovenia, and the United States had the poorest consumption rates (27 out of 30 were not at optimal levels).

diet_and_guidelines %>%
  count(opt_achieved, location_name) %>%
  filter(opt_achieved == "No") %>%
  arrange(-n) %>%
  # to show full output
  print(n = 1e3)
# A tibble: 196 x 3
    opt_achieved location_name                        n
    <chr>        <chr>                            <int>
  1 No           Czech Republic                      27
  2 No           Greenland                           27
  3 No           Hungary                             27
  4 No           Slovakia                            27
  5 No           Slovenia                            27
  6 No           United States                       27
  7 No           Andorra                             26
  8 No           Australia                           26
  9 No           Austria                             26
 10 No           Belgium                             26
 11 No           Brunei                              26
 12 No           Chile                               26
 13 No           China                               26
 14 No           Cyprus                              26
 15 No           Denmark                             26
 16 No           Estonia                             26
 17 No           France                              26
 18 No           Germany                             26
 19 No           Ireland                             26
 20 No           Italy                               26
 21 No           Latvia                              26
 22 No           Lithuania                           26
 23 No           New Zealand                         26
 24 No           Norway                              26
 25 No           Poland                              26
 26 No           Singapore                           26
 27 No           South Korea                         26
 28 No           Spain                               26
 29 No           Switzerland                         26
 30 No           United Kingdom                      26
 31 No           Uruguay                             26
 32 No           Argentina                           25
 33 No           Belarus                             25
 34 No           Bulgaria                            25
 35 No           Croatia                             25
 36 No           Ecuador                             25
 37 No           Finland                             25
 38 No           Global                              25
 39 No           Iceland                             25
 40 No           Netherlands                         25
 41 No           Russian Federation                  25
 42 No           Sweden                              25
 43 No           Canada                              24
 44 No           Colombia                            24
 45 No           Greece                              24
 46 No           Japan                               24
 47 No           Malta                               24
 48 No           Mongolia                            24
 49 No           Portugal                            24
 50 No           Romania                             24
 51 No           Serbia                              24
 52 No           Bolivia                             23
 53 No           Central African Republic            23
 54 No           Luxembourg                          23
 55 No           Montenegro                          23
 56 No           Panama                              23
 57 No           Paraguay                            23
 58 No           Puerto Rico                         23
 59 No           South Africa                        23
 60 No           Ukraine                             23
 61 No           Venezuela                           23
 62 No           Vietnam                             23
 63 No           Afghanistan                         22
 64 No           Albania                             22
 65 No           Algeria                             22
 66 No           American Samoa                      22
 67 No           Antigua and Barbuda                 22
 68 No           Bangladesh                          22
 69 No           Belize                              22
 70 No           Bhutan                              22
 71 No           Botswana                            22
 72 No           Brazil                              22
 73 No           Cambodia                            22
 74 No           Cape Verde                          22
 75 No           Congo                               22
 76 No           Democratic Republic of the Congo    22
 77 No           Djibouti                            22
 78 No           Dominica                            22
 79 No           Federated States of Micronesia      22
 80 No           Gabon                               22
 81 No           Georgia                             22
 82 No           Grenada                             22
 83 No           Guam                                22
 84 No           Guinea                              22
 85 No           Guinea-Bissau                       22
 86 No           Guyana                              22
 87 No           Indonesia                           22
 88 No           Israel                              22
 89 No           Jamaica                             22
 90 No           Jordan                              22
 91 No           Kazakhstan                          22
 92 No           Kiribati                            22
 93 No           Kyrgyzstan                          22
 94 No           Laos                                22
 95 No           Liberia                             22
 96 No           Libya                               22
 97 No           Macedonia                           22
 98 No           Madagascar                          22
 99 No           Malaysia                            22
100 No           Maldives                            22
101 No           Marshall Islands                    22
102 No           Mexico                              22
103 No           Moldova                             22
104 No           Namibia                             22
105 No           Northern Mariana Islands            22
106 No           Pakistan                            22
107 No           Palestine                           22
108 No           Peru                                22
109 No           Philippines                         22
110 No           Saint Lucia                         22
111 No           Saint Vincent and the Grenadines    22
112 No           Samoa                               22
113 No           Saudi Arabia                        22
114 No           Seychelles                          22
115 No           Somalia                             22
116 No           Sri Lanka                           22
117 No           Sudan                               22
118 No           Suriname                            22
119 No           Swaziland                           22
120 No           Syria                               22
121 No           Taiwan (Province of China)          22
122 No           Thailand                            22
123 No           The Bahamas                         22
124 No           The Gambia                          22
125 No           Tonga                               22
126 No           Turkmenistan                        22
127 No           Vanuatu                             22
128 No           Yemen                               22
129 No           Zimbabwe                            22
130 No           Armenia                             21
131 No           Bosnia and Herzegovina              21
132 No           Burundi                             21
133 No           Costa Rica                          21
134 No           Equatorial Guinea                   21
135 No           Eritrea                             21
136 No           Fiji                                21
137 No           Honduras                            21
138 No           Iraq                                21
139 No           Kuwait                              21
140 No           Oman                                21
141 No           Papua New Guinea                    21
142 No           Solomon Islands                     21
143 No           Tajikistan                          21
144 No           Trinidad and Tobago                 21
145 No           Uzbekistan                          21
146 No           Virgin Islands, U.S.                21
147 No           Angola                              20
148 No           Azerbaijan                          20
149 No           Bahrain                             20
150 No           Barbados                            20
151 No           Bermuda                             20
152 No           Chad                                20
153 No           Comoros                             20
154 No           Cote d'Ivoire                       20
155 No           El Salvador                         20
156 No           Guatemala                           20
157 No           Haiti                               20
158 No           India                               20
159 No           Iran                                20
160 No           Kenya                               20
161 No           Lebanon                             20
162 No           Mauritania                          20
163 No           Mauritius                           20
164 No           Morocco                             20
165 No           Mozambique                          20
166 No           Myanmar                             20
167 No           Nepal                               20
168 No           Nicaragua                           20
169 No           Niger                               20
170 No           North Korea                         20
171 No           Sao Tome and Principe               20
172 No           Sierra Leone                        20
173 No           South Sudan                         20
174 No           Tanzania                            20
175 No           Timor-Leste                         20
176 No           Uganda                              20
177 No           United Arab Emirates                20
178 No           Zambia                              20
179 No           Benin                               19
180 No           Burkina Faso                        19
181 No           Cameroon                            19
182 No           Cuba                                19
183 No           Ethiopia                            19
184 No           Ghana                               19
185 No           Lesotho                             19
186 No           Mali                                19
187 No           Nigeria                             19
188 No           Senegal                             19
189 No           Togo                                19
190 No           Tunisia                             19
191 No           Dominican Republic                  18
192 No           Egypt                               18
193 No           Malawi                              18
194 No           Qatar                               17
195 No           Rwanda                              17
196 No           Turkey                              17

Let’s look at the raw US data:

diet_and_guidelines %>%
  filter(location_name == "United States") %>%
  glimpse()
Rows: 30
Columns: 20
$ year_id            <dbl> 2017, 2017, 2017, 2017, 2017, 2017, 2017, 2017, 201~
$ location_name      <chr> "United States", "United States", "United States", ~
$ rei_id             <dbl> 117, 117, 116, 116, 124, 124, 118, 118, 123, 123, 1~
$ food               <chr> "processed meat", "processed meat", "red meat", "re~
$ age_group_name     <chr> "All available ages", "All available ages", "All av~
$ sex                <chr> "Male", "Female", "Male", "Female", "Male", "Female~
$ parameter          <chr> "continuous", "continuous", "continuous", "continuo~
$ mean               <dbl> 25.31934316, 16.90044942, 62.30037501, 40.30456938,~
$ upper              <dbl> 27.99854665, 18.57300631, 66.61324291, 43.41968400,~
$ lower              <dbl> 22.85404285, 15.32263709, 58.39239430, 37.25710437,~
$ unit               <chr> "g/day", "g/day", "g/day", "g/day", "g/day", "g/day~
$ direction          <chr> "high", "high", "high", "high", "high", "high", "hi~
$ lower_optimal      <dbl> 0.0, 0.0, 18.0, 18.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0,~
$ optimal            <dbl> 2.00, 2.00, 23.00, 23.00, 3.00, 3.00, 3.00, 3.00, 0~
$ upper_optimal      <dbl> 4.0, 4.0, 27.0, 27.0, 5.0, 5.0, 5.0, 5.0, 1.0, 1.0,~
$ unit_optimal       <chr> "g", "g", "g", "g", "g", "g", "g", "g", "%", "%", "~
$ Relative_Percent   <dbl> 1265.967158, 845.022471, 270.871196, 175.237258, 17~
$ range_percent      <dbl> 632.983579, 422.511236, 230.742130, 149.276183, 104~
$ percent_over_under <dbl> 532.983579, 322.511236, 130.742130, 49.276183, 4.04~
$ opt_achieved       <chr> "No", "No", "No", "No", "No", "Yes", "No", "No", "Y~

Let’s see how males and females compare for achieving the optimal intake, across all countries:

count(diet_and_guidelines, sex, opt_achieved)
# A tibble: 4 x 3
  sex    opt_achieved     n
  <chr>  <chr>        <int>
1 Female No            2171
2 Female Yes            769
3 Male   No            2189
4 Male   Yes            751

Looks pretty similar, but it may be a bit better for females. We will evaluate this further below.

In order to assess what we have observed so far in a graphical way, we will make some data visualizations. One way we can do this is with the ggplot2 package. The ggplot2 package creates plots by building the plot components piece by piece, using "layers".

With ggplot2 we select what data we would like to plot using the first function (ggplot()) and then we add on additional layers of complexity (these layers can even involve different data). The aes() argument specifies what aspects of the data will be plotted where. The geom_* function specifies what type of plot to create (e.g. geom_histogram() creates a histogram). Notice in the following code how there is a plus sign between the ggplot() function and the geom_bar() function; this is how we combine different plot layers.

We will see later how we can add many layers to plots with ggplot2. For additional information on using ggplot2, see this case study.

diet_and_guidelines %>%
  ggplot(aes(opt_achieved, fill = sex)) +
  geom_bar(position = "dodge")

Continuing with ggplot2 we will now create a different type of plot. This time we will create a series of box plots. We will use the facet_wrap() function of ggplot2 to allow us to create many different plots simultaneously. In this case we can look at box plots for the different dietary factors colored by sex. The scales argument when set to "free" means that each of the sequential plot created by the facet can have a different scale for the y axis, otherwise, by default they are constrained to the same scale. Since our dietary factors are measured on very different scales, we do not want this constraint here.

# we will create a new variable with food names with new lines
# str_replace() is used here because we are only replacing the first instance of space
# otherwise str_replace_all() should be used
diet_and_guidelines %<>%
  mutate(
    food_to_plot =
      str_replace(
        string = pull(diet_and_guidelines, food),
        pattern = " ",
        replacement = "\n"
      )
  )

diet_and_guidelines %>%
  ggplot(aes(
    y = Relative_Percent,
    x = sex,
    color = sex
  )) +
  geom_boxplot() +
  facet_wrap(~food_to_plot,
    scales = "free",
    # specifies the number of rows of subplots
    nrow = 3,
    # moves the food label to the right
    strip.position = "right"
  ) +
  # this changes the size of the font for the labels
  theme(
    strip.text.y = element_text(size = 8),
    axis.text.x = element_text(
      angle = 70,
      hjust = 1
    )
  )

If we just look at differences by sex for the specific dietary factors, males appear to potentially consume more of many of the factors, including possibly more sodium, fiber, calcium, red meat, and sugar-sweetened beverages than females. Females may consume more fruit.

Exploring the data separated by age


Now we will take a look at the data that is separated by age groups.

First, recall that we have 15 different age groups starting from age 25 to 95 plus.

all_age_diet_and_guidelines %>%
  count(age_group_name)
# A tibble: 15 x 2
   age_group_name     n
   <chr>          <int>
 1 25 to 29        5880
 2 30 to 34        5880
 3 35 to 39        5880
 4 40 to 44        5880
 5 45 to 49        5880
 6 50 to 54        5880
 7 55 to 59        5880
 8 60 to 64        5880
 9 65 to 69        5880
10 70 to 74        5880
11 75 to 79        5880
12 80 to 84        5880
13 85 to 89        5880
14 90 to 94        5880
15 95 plus         5880
sep_age_diet_data %>%
  ggplot(aes(y = mean, x = age_group_name, col = sex)) +
  geom_boxplot() +
  facet_wrap(~food, scales = "free", nrow = 6) +
  theme(
    axis.text.x = element_text(angle = 70, hjust = 1),
    strip.text.x = element_text(size = 8)
  )

We can see from these plots that there appear to be age differences and gender differences for some of the different dietary factors. We will work to create clearer figures later on. However these initial figures have given us a better sense of the data that we are working with.

Data Analysis


If you have been following along but stopped you could load the wrangled data like so:

load(here::here("data", "wrangled", "wrangled_data.rda"))

If you skipped the data wrangling section click here.

First you need to install and load the OCSdata package:

install.packages("OCSdata")
library(OCSdata)

Then, you may load the wrangled data using the following code:

wrangled_rda("ocs-bp-diet", outpath = getwd())
load(here::here("OCSdata", "data", "wrangled", "wrangled_data.rda"))

If the package does not work for you, alternatively, an RDA file (stands for R data) of the data can be found here or slightly more directly here. Download this file and then place it in your current working directory within a subdirectory called “wrangled” within a subdirectory called “data” to copy and paste our code. We used an RStudio project and the here package to navigate to the file more easily.

load(here::here("data", "wrangled", "wrangled_data.rda"))

Click here to see more about creating new projects in RStudio.

You can create a project by going to the File menu of RStudio like so:

You can also do so by clicking the project button:

See here to learn more about using RStudio projects and here to learn more about the here package.



Recall what our main questions were:

Our main questions are:

  1. What are the global trends for potentially harmful diets?
  2. How do males and females compare?
  3. How do different age groups compare for these dietary factors?
  4. How do different countries compare? In particular, how does the US compare to other countries in terms of diet trends?

We have some general sense about global trends for the risk-associated dietary factors, however we want to know more.

We are interested in how much the genders differ, how much the 15 different age groups differ, and how the 195 countries compare.

In order to make inferences about these comparisons, it is helpful to perform statistical tests. These tests can help us to determine the strength of the association between the consumption of the dietary factors (our outcome variable) and sex, age group, and country identity (our predictor variables). One way to look at the strength of association between variables is to use a statistical method called regression.

If we measure consumption using either raw consumption or the percent of optimal consumption, then our outcome variable is what we call continuous, because our values can take on any numeric value within the range of possible values. To look at the strength of association with a continuous outcome, we can use linear regression.

If, instead, we measure consumption by whether or not the optimal level of consumption was achieved (“yes” or “no”), then our outcome would be considered binary, meaning it can take only two possible values. To look at the strength of association with a binary outcome, we can use logistic regression. There are other regression method for different types of outcomes as well; see here for a guide on different types of regression methods.

In this case study, we will focus on the outcome of the percent of optimal consumption (Relative_Percent), so we will focus our analysis on linear regression.

You may have already learned that one can compare a continuous outcome between two groups using a \(t\)-test. For more information on the \(t\)-test see this case study. And perhaps you have heard about ANOVA (ANalysis Of VAriance) for comparing a continuous outcome across more than two groups. It turns out that both the \(t\)-test and ANOVA are specialized types of linear regression. We will use each of these tests to investigate patterns of consumption for dietary factors that contribute to health risk and we will look at how we can obtain equivalent results with regression.

Linear Regression


So what is linear regression? How can we use regression to compare our groups of interest and look at the relationship between group identity and consumption of dietary factors associated with health risk?

The statistical version of the term regression was coined in 1877 in this article about the relationship between hereditary traits and population averages. The author particularly focused on height and kinship or relatedness. The word itself means "to go back to a simpler state". It was noticed that individuals with parents who had an extreme trait, such as exceptional height, tended to have a height more similar to the average of the population than the extreme height of their parents. For example if parents were very tall, their children were likely to be a bit shorter than their parents and therefore closer to the population average. Thus the children regressed towards the mean or in the author’s words the offspring showed:

“a regression towards mediocrity”

See here for more information about this history.

When we think about this from a statistical standpoint, regression allows us to estimate or regress relationships between variables with a “simple” model. We do this by estimating the mean of an outcome, given a value of an input or predictor variable. This can be useful for predicting future values of the outcome based on the approximation of the real relationship between the variables within the model, or just for understanding how different variables are related to one another.

We will start by considering simple linear regression, where we have one continuous predictor variable and one continuous outcome variable, as shown below:

We want to identify a “best fit” line that summarizes the relationship between these two variables. We can so this using the ordinary least squares method, which chooses the line that best fits the data by minimizing the sum of the squared vertical distances between each point and the line. In the above example, this line turns out to be:

Fitting a line to the data like this allows us to create a formula for the line using an intercept and a slope, so that we can then estimate mean values of \(Y\) (dependent/outcome variable) given known values of \(X\) (independent/predictor/covariate/explanatory variable(s)). People will also say that we are “regressing \(Y\) on \(X\)”.

You may have seen the formula for a line written like this:

\[Y = mX + b\]

or

\[Y = aX + b\]

In this case \(m\) or \(a\) is the slope of the line and \(b\) is a constant and represents the y-intercept or the point where the y axis is crossed by the line, when \(x = 0\).

In regression, we usually write this model like this:

\[Y = \beta_{1}X +\beta_{0}\]

Now \(\beta_{1}\), called “beta one”, is our slope and \(\beta_{0}\), called “beta zero” (or “beta naught”), is our intercept. In our example above, the slope of the regression line is \(\beta_{1} = 2.3\) and the intercept is \(\beta_{0} = -6.6\).

Importantly the slope (\(\beta_{1}\)) gives us a quantitative measure of the relationship between the independent variable (\(X\)) on the dependent variable (\(Y\)). In particular, \(\beta_{1}\) tells how the expected difference in the \(Y\) value for a difference of 1 unit in the \(X\) value.

It’s possible that the regression line will perfectly fit the data, and all points will lie on the line with no distance to the line:

In this case, the slope or \(\beta_{1}\) is 1 and the intercept \(\beta_{0}\) is 10 and every observed data point lies exactly on the line, e.g., we can see that when \(X\) is 50, \(Y\) is exactly 60. This is very unusual in statistical analysis however, as often the relationship between variables is more complicated and there is more noise in our data. In these other cases there will be greater distances between the line and the points.

Like this regression:

In this case, because there is some vertical distance between the line and the data points, there is a bit of what is called “error” in the model. The formula for the relationship between \(X\) and \(Y\) does not perfectly describe the data. The vertical distance between the line and each data point is what we call a residual. Our least squares method finds the line with the minimized value of the sum of the squared residual values.

Check out this interactive explanation of how the ordinary least squares method works.

Here is an image of what we are saying about the ordinary least squares regression to fit a line to data:
[source]

This basic concept of simple linear regression an be extended to allow for more than one covariate (the independent variables, or x’s); this is called multivariable linear regression. With more than one independent variable, we can’t visualize these relationships easily with a line on a two-dimensional page, but the mathematical concept remains in some sense the same.

R has it’s own way of representing the regression equation in code. For a guide on how to perform regressions in R see here.

In R we indicate a linear model like this:

y ~ x

Here our response/outcome variable is on the left of the ~ while our covariates/explanatory variables are on the right of the ~.

Before we get started, let’s remove the global values from our data and set them aside, as this is really a composite of all the country values.

global <- diet_and_guidelines %>%
  filter(location_name == "Global")
diet_and_guidelines %<>%
  filter(location_name != "Global")
all_age_diet_and_guidelines %<>%
  filter(location_name != "Global")

\(t\)-test and linear regression


Since we will be covering a lot of different statistical concepts here, we will want to focus are analysis on a single dietary factor. Let’s choose one of the dietary factors that appeared to potentially have a difference between genders based on our figure in our exploratory analysis.

“If we just look at differences by sex for the specific dietary factors, males appear to potentially consume more of many of the factors, including possibly more sodium, fiber, calcium, red meat, and sugar-sweetened beverages than females. Females may consume more fruit.”

Let’s take a look at red meat.

We can compare the relative percent of red meat consumption of males and females around the world using the well known \(t\)-test using the t.test() function and a linear regression model using the lm() function (both are included in stats package that is installed with R by default) and we will get the same results. See here for additional explanation about why that is the case. Here and here are also great sources about how many commonly known statistical tests are specialized forms of regression.

Before we get started, let’s think about the assumptions of both an independent samples \(t\)-test and linear regression.

Independent samples \(t\)-test assumptions:

  1. Normality of the outcome in each group (this is not as much of an issue if the number of observations is relatively large, i.e., total n > 30 - which is indeed the case for us!)
  2. Equal variance between the two groups
  3. Independent observations

Linear regression assumptions:

  1. L (linear) - There is a linear relationship between the outcome variable and each covariate.

  2. I (independent) - The outcome for individual observations are independent from one another, given the covariates in the model.

  3. N (normal) - The residuals (errors) are normally distributed. Note that the variables themselves do not need to be normally distributed.

  4. E (equal variances) - The variance of the residuals is constant across covariate groups. This is called homoscedasticity. In other words the residuals are of similar size along the regression line.

It’s also important that if there are multiple predictor variables, that these are not too highly correlated.

See here for additional information about the assumptions of linear regressions.

Notice that many of the assumptions between \(t\)-tests and linear regression are similar – each has an assumption of normality, equal variance, and independence!

Assessing normality

First we will explore the shape of the distribution of these relative percent of red meat consumption. We can do this by looking at a frequency distribution of the Relative_Percent variable for red meat consumption. We will use the geom_histogram() of the ggplot2package to create a histogram to evaluate the frequency distributions of our data. The facet_wrap() function of the ggplot2 package allows us to look at different parts of our data in separate plots. Here we can compare the distribution for males and females.

diet_and_guidelines %>%
  filter(food == "red meat") %>%
  ggplot(aes(x = Relative_Percent)) +
  geom_histogram() +
  facet_wrap(~sex)

This Relative_Percent variable appears to have a right skew for both male and female individuals. We can also see this by looking at normal Quantile-Quantile (Q-Q) plots of this variable. Remember that in a Q-Q plot, points away from the line indicate one of the distributions is more skewed than the other. In this case, we see that the values in are sample are skewed relative to the theoretical normal distribution. Here is a great reference for interpreting Q-Q plots.

diet_and_guidelines %>%
  filter(food == "red meat") %>%
  ggplot(aes(sample = Relative_Percent)) +
  facet_wrap(~sex) +
  geom_qq() +
  geom_qq_line()

We can consider transforming our data to make it more normally distributed. When data is highly right skewed, a log transformation is often helpful.

Let’s take a look a the log (with base 10) of our Relative_Percent variable:

diet_and_guidelines %>%
  filter(food == "red meat") %>%
  ggplot(aes(x = log10(Relative_Percent))) +
  geom_histogram() +
  facet_wrap(~sex)

diet_and_guidelines %>%
  filter(food == "red meat") %>%
  ggplot(aes(sample = log10(Relative_Percent))) +
  facet_wrap(~sex) +
  geom_qq() +
  geom_qq_line()

OK, so now our histograms look fairly normal. It isn’t perfect, but we have a large number of samples, so this is good for our \(t\)-test assumptions.

Assessing equal variances

The next thing we need to check is if the variance in red meat consumption is similar between the two gender groups. We can use the var.test() of the stats package using the log-normalized data, as this data is fairly normally distributed.

Because we are piping in our data to this test function, we need to indicate that this is the data we intend to use by using . for the data argument. This is a handy tip when piping into a function outside of the tidyverse where the first argument isn’t a data set.

diet_and_guidelines %>%
  filter(food == "red meat") %>%
  var.test(log10(Relative_Percent) ~ sex, data = .)

    F test to compare two variances

data:  log10(Relative_Percent) by sex
F = 0.96369, num df = 194, denom df = 194, p-value = 0.797
alternative hypothesis: true ratio of variances is not equal to 1
95 percent confidence interval:
 0.7266925 1.2779719
sample estimates:
ratio of variances 
          0.963687 

The p value > .05 for this test, thus we can conclude that there is not enough evidence to reject the null hypothesis that there is no difference in the variance of the distributions, so we conclude that variance is roughly equal.

Comparing a \(t\)-test to linear regression

Now let’s compare the consumption of red meat across genders using both a \(t\)-test and a linear regression. First our independent samples \(t\)-test:

diet_and_guidelines %>%
  filter(food == "red meat") %>%
  t.test(log10(Relative_Percent) ~ sex, data = ., var.equal = TRUE)

    Two Sample t-test

data:  log10(Relative_Percent) by sex
t = -5.3187, df = 388, p-value = 1.77e-07
alternative hypothesis: true difference in means between group Female and group Male is not equal to 0
95 percent confidence interval:
 -0.2525474 -0.1162266
sample estimates:
mean in group Female   mean in group Male 
            1.798872             1.983259 

Notice here that sample means for the two groups are 1.80 and 1.98 for males and females, respectively. So that means the difference in sample means is 1.80 - 1.98 = -0.18. We also see a test statistic of \(t\) = -5.32 and a very small \(p\)-value.

Let’s examine the same relationship using linear regression:

diet_and_guidelines %>%
  filter(food == "red meat") %>%
  lm(log10(Relative_Percent) ~ sex, data = .) %>%
  summary()

Call:
lm(formula = log10(Relative_Percent) ~ sex, data = .)

Residuals:
    Min      1Q  Median      3Q     Max 
-1.0238 -0.2484  0.0052  0.3127  0.6170 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)    
(Intercept)  1.79887    0.02451  73.382  < 2e-16 ***
sexMale      0.18439    0.03467   5.319 1.77e-07 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 0.3423 on 388 degrees of freedom
Multiple R-squared:  0.06795,   Adjusted R-squared:  0.06555 
F-statistic: 28.29 on 1 and 388 DF,  p-value: 1.77e-07

Look at the results for the slope of the regression line, indicated by the sexMale row in the output above. Notice how the \(t\)-value and the \(p\)-value match our \(t\)-test! (Well, the signs are switched in each case – the \(t\) value is negative in the t.test() output because the male group is being used as reference group, while the female group is being used as the reference group in lm()). We can fix this using the fct_inorder() function of the forcats package which is all about factors. This function allows us to order the factor by what appears first. In this case “male” appears first, so now our output will match that of the lm() function.

diet_and_guidelines %<>%
  mutate_at(vars(sex), factor)

diet_and_guidelines %>%
  filter(food == "red meat") %>%
  lm(log10(Relative_Percent) ~ sex, data = .) %>%
  summary()

Call:
lm(formula = log10(Relative_Percent) ~ sex, data = .)

Residuals:
    Min      1Q  Median      3Q     Max 
-1.0238 -0.2484  0.0052  0.3127  0.6170 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)    
(Intercept)  1.79887    0.02451  73.382  < 2e-16 ***
sexMale      0.18439    0.03467   5.319 1.77e-07 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 0.3423 on 388 degrees of freedom
Multiple R-squared:  0.06795,   Adjusted R-squared:  0.06555 
F-statistic: 28.29 on 1 and 388 DF,  p-value: 1.77e-07
diet_and_guidelines %>%
  mutate_at(vars(sex), forcats::fct_inorder) %>%
  filter(food == "red meat") %>%
  t.test(log10(Relative_Percent) ~ sex,
    data = .,
    var.equal = TRUE
  )

    Two Sample t-test

data:  log10(Relative_Percent) by sex
t = 5.3187, df = 388, p-value = 1.77e-07
alternative hypothesis: true difference in means between group Male and group Female is not equal to 0
95 percent confidence interval:
 0.1162266 0.2525474
sample estimates:
  mean in group Male mean in group Female 
            1.983259             1.798872 

Now they match. Notice that the degrees of freedom also match, both results show 388 degrees of freedom. We are estimating two parameters for the linear model the two \(\beta\) coefficients, (the slope and intercept), and for the \(t\)-test we are estimating the means of two groups (males and females). Overall we have two samples (male and female) for each of the 195 countries.

Thus, the overall sample number is: \(n = 195*2 = 390\)

\[df = n - # parameters estimating\] Thus the degrees of freedom can be calculated as: \(df = 390 -2 = 388\)

Let’s look more closely at the linear regression output from lm(). Our estimated intercept (\(\beta_{0}\)) is 1.80, which can be interpreted as the mean value when sex is not male (so in this case when sex is female). This matches the sample mean of the female group in the t.test() output.

Our estimated slope (\(\beta_{1}\)) is 0.18, which can be interpreted as the slope of the regression line or the mean change in \(Y\) associated with one-unit increase in \(X\). Since our \(X\) variable is sex, a one-unit change means moving from one group to another. So we can think of the slope as the difference between the means of the two groups, male (\(X\)=1) minus female (\(X\) = 0). If we calculate this difference in means as calculated in the t.test() output, we get the value of \(\beta_{1}\) (the slope or the sexMale estimate) of the lm() output!

Mean of males - Mean of females \(1.983259 - 1.798872 =0.184387\)

Cool! For more information about the output of lm() see here.

After fitting our linear regression model, we can use the base plot() function to get information about our model residuals to help us assess whether any of the assumptions of linear regression are violated. Here we choose to view the first three of these plots with which = 1:3.

diet_and_guidelines %>%
  filter(food == "red meat") %>%
  lm(log10(Relative_Percent) ~ sex, data = .) %>%
  plot(which = 1:3)

The second plot shows us that our residuals are slightly negatively (or left) skewed. We can see also see the spread of the residuals is similar between males and females, as the first and third plot show similar spreads of values in the two lines. This suggests that the assumption of homoscedasticity is met. Here is what these plots would look like if the variance were not the same between the groups:

diet_and_guidelines %>%
  filter(food == "red meat") %>%
  mutate(example_perc = case_when(
    # this will artifically make our female data have different
    # variance from the male data
    sex == "Female" ~ log(Relative_Percent),
    sex == "Male" ~ Relative_Percent
  )) %>%
  lm(log10(example_perc) ~ sex, data = .) %>%
  plot(which = 1:3)

In this case the spread of the points is clearly less for one group compared to the other. If we saw plots like these, we would be concerned the assumption of homoscedasticity was violated.

Assessing independence

We never considered the assumption of independent required by both a \(t\)-test and linear regression. Do we truly have independent samples in this case? No! Since we have female and male values from the same countries, our data is really what we would call “paired”. The male and female diet values from the same country are most likely related to each another because of cultural effects on diet. This means the assumption of independence for the independent samples \(t\)-test is violated, as is the independence assumption for linear regression.

We can address this by doing a paired \(t\)-test instead of an independent \(t\)-test and by accounting for country in our linear model by adding it to our model as what we call a fixed effect.

Paired \(t\)-test and linear model with fixed effects


Now we will perform the paired versions of our analysis. This is very easy to do with the t.test() function, by simply using the paired argument and setting it equal to TRUE.

However, our data needs to be in a slightly different form to do the paired test, since we have to tell R which values need to be paired together. Instead of one long dataset with different rows for males and females, we will need separate columns for the male and female values. So we need to make our dataset wider. We can do that using the pivot_wider() function of the tidyr package. To use this function we specify the values that we want to separate into more variables using the values_from argument and we use the names_from argument to specify how we want to separate these other variables. In this case we will make a male and female version of all the other variables specified.

wide_diet <- diet_and_guidelines %>%
  pivot_wider(
    values_from = c(
      contains("percent"),
      mean,
      upper,
      lower,
      opt_achieved
    ),
    names_from = sex
  )

glimpse(wide_diet)
Rows: 2,925
Columns: 26
$ year_id                   <dbl> 2017, 2017, 2017, 2017, 2017, 2017, 2017, 20~
$ location_name             <chr> "China", "North Korea", "Taiwan (Province of~
$ rei_id                    <dbl> 117, 117, 117, 117, 117, 117, 117, 117, 117,~
$ food                      <chr> "processed meat", "processed meat", "process~
$ age_group_name            <chr> "All available ages", "All available ages", ~
$ parameter                 <chr> "continuous", "continuous", "continuous", "c~
$ unit                      <chr> "g/day", "g/day", "g/day", "g/day", "g/day",~
$ direction                 <chr> "high", "high", "high", "high", "high", "hig~
$ lower_optimal             <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,~
$ optimal                   <dbl> 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,~
$ upper_optimal             <dbl> 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,~
$ unit_optimal              <chr> "g", "g", "g", "g", "g", "g", "g", "g", "g",~
$ Relative_Percent_Male     <dbl> 115.88487, 28.32615, 119.29284, 23.54152, 20~
$ Relative_Percent_Female   <dbl> 87.56435, 20.67010, 90.93536, 17.67477, 15.9~
$ range_percent_Male        <dbl> 57.942437, 14.163074, 59.646422, 11.770760, ~
$ range_percent_Female      <dbl> 43.782175, 10.335051, 45.467681, 8.837384, 7~
$ percent_over_under_Male   <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,~
$ percent_over_under_Female <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,~
$ mean_Male                 <dbl> 2.3176975, 0.5665229, 2.3858569, 0.4708304, ~
$ mean_Female               <dbl> 1.7512870, 0.4134020, 1.8187072, 0.3534954, ~
$ upper_Male                <dbl> 2.6944978, 0.6596296, 2.7830329, 0.5541629, ~
$ upper_Female              <dbl> 2.0454134, 0.4730273, 2.1087411, 0.4132150, ~
$ lower_Male                <dbl> 1.9933744, 0.4818201, 2.0572123, 0.3964639, ~
$ lower_Female              <dbl> 1.5161724, 0.3573467, 1.5705803, 0.3017578, ~
$ opt_achieved_Male         <chr> "Yes", "Yes", "Yes", "Yes", "Yes", "Yes", "Y~
$ opt_achieved_Female       <chr> "Yes", "Yes", "Yes", "Yes", "Yes", "Yes", "Y~

You can see we now have a Relative_Percent_Male variable and a Relative_Percent_Female variable. We can use these two variables in our paired \(t\)-test. Since the paired version of the \(t\)-test doesn’t take a data= argument, we will pull the appropriate variables from our data a little bit differently, using the pull() function.

t.test(log10(pull(
  filter(wide_diet, food == "red meat"),
  Relative_Percent_Male
)),
log10(pull(
  filter(wide_diet, food == "red meat"),
  Relative_Percent_Female
)),
var.equal = TRUE, paired = TRUE
)

    Paired t-test

data:  log10(pull(filter(wide_diet, food == "red meat"), Relative_Percent_Male)) and log10(pull(filter(wide_diet, food == "red meat"), Relative_Percent_Female))
t = 188.16, df = 194, p-value < 2.2e-16
alternative hypothesis: true difference in means is not equal to 0
95 percent confidence interval:
 0.1824542 0.1863197
sample estimates:
mean of the differences 
               0.184387 

Here an estimated mean difference (Males - Females) of 0.18, and that this is considered significantly different than 0 due to a very small \(p\)-value. You can also see that now our degrees of freedom are 194, which makes sense because with paired samples we are only estimating one parameter (the mean difference) based on data on 195 differences for each country. So \(df = n - \# \ parameters = 195 -1 = 194\).

The paired version of the linear model is a bit more complex. In this case we will add another term in our model to evaluate the influence of sex on Relative_Percent consumption while keeping the country identity fixed or constant, or in other words controlling/adjusting for country. We can use the + to add this additional term. Now that we have multiple covariate/explanatory variable terms, we would call this a multivariable linear regression.

So now our model in words will be:

Mean relative consumption of red meat is dependent on sex and country. Or in other words, sex and location influence the consumption of red meat around the world.

Then the coefficient for sex will be different from what we had in our previous lm() model, as it will be calculated while keeping location_name or the country where the consumption value was obtained fixed, or in other words “controlling for location_name.” This will also result in output for each of the countries. The coefficients here represent the average difference in consumption value for each country compared to the reference country of Afghanistan, while accounting for sex.

This now should meet the assumption of independence for a linear regression model, since observations will be independent conditional an the covariates of sex and country.

Let’s fit this model and look at the results.

diet_and_guidelines %>%
  filter(food == "red meat") %>%
  lm(log10(Relative_Percent) ~ sex + location_name, data = .) %>%
  summary()

Call:
lm(formula = log10(Relative_Percent) ~ sex + location_name, data = .)

Residuals:
      Min        1Q    Median        3Q       Max 
-0.031341 -0.003897  0.000000  0.003897  0.031341 

Coefficients:
                                               Estimate Std. Error t value
(Intercept)                                    1.537017   0.006860 224.066
sexMale                                        0.184387   0.000980 188.159
location_nameAlbania                           0.438021   0.009676  45.268
location_nameAlgeria                           0.045488   0.009676   4.701
location_nameAmerican Samoa                    0.467181   0.009676  48.281
location_nameAndorra                           0.748631   0.009676  77.368
location_nameAngola                            0.250455   0.009676  25.884
location_nameAntigua and Barbuda               0.299310   0.009676  30.932
location_nameArgentina                         0.824071   0.009676  85.164
location_nameArmenia                           0.394447   0.009676  40.764
location_nameAustralia                         0.814642   0.009676  84.190
location_nameAustria                           0.870921   0.009676  90.006
location_nameAzerbaijan                        0.293968   0.009676  30.380
location_nameBahrain                           0.289155   0.009676  29.883
location_nameBangladesh                       -0.611390   0.009676 -63.185
location_nameBarbados                          0.310247   0.009676  32.063
location_nameBelarus                           0.687041   0.009676  71.003
location_nameBelgium                           0.676956   0.009676  69.961
location_nameBelize                            0.176578   0.009676  18.249
location_nameBenin                            -0.326705   0.009676 -33.764
location_nameBermuda                           0.424542   0.009676  43.875
location_nameBhutan                           -0.120195   0.009676 -12.422
location_nameBolivia                           0.493009   0.009676  50.950
location_nameBosnia and Herzegovina            0.170649   0.009676  17.636
location_nameBotswana                          0.133756   0.009676  13.823
location_nameBrazil                            0.740470   0.009676  76.525
location_nameBrunei                            0.221789   0.009676  22.921
location_nameBulgaria                          0.680417   0.009676  70.318
location_nameBurkina Faso                      0.092972   0.009676   9.608
location_nameBurundi                          -0.196961   0.009676 -20.355
location_nameCambodia                          0.110614   0.009676  11.432
location_nameCameroon                         -0.055387   0.009676  -5.724
location_nameCanada                            0.667331   0.009676  68.966
location_nameCape Verde                        0.394975   0.009676  40.819
location_nameCentral African Republic          0.369231   0.009676  38.159
location_nameChad                              0.043477   0.009676   4.493
location_nameChile                             0.670126   0.009676  69.255
location_nameChina                             0.634103   0.009676  65.532
location_nameColombia                          0.360716   0.009676  37.279
location_nameComoros                          -0.115892   0.009676 -11.977
location_nameCongo                            -0.257950   0.009676 -26.658
location_nameCosta Rica                        0.379751   0.009676  39.246
location_nameCote d'Ivoire                    -0.387071   0.009676 -40.002
location_nameCroatia                           0.581435   0.009676  60.089
location_nameCuba                              0.347647   0.009676  35.928
location_nameCyprus                            0.600760   0.009676  62.086
location_nameCzech Republic                    0.647519   0.009676  66.918
location_nameDemocratic Republic of the Congo -0.140305   0.009676 -14.500
location_nameDenmark                           0.662793   0.009676  68.497
location_nameDjibouti                          0.204672   0.009676  21.152
location_nameDominica                          0.196880   0.009676  20.347
location_nameDominican Republic                0.279081   0.009676  28.842
location_nameEcuador                           0.516439   0.009676  53.372
location_nameEgypt                             0.138031   0.009676  14.265
location_nameEl Salvador                       0.043217   0.009676   4.466
location_nameEquatorial Guinea                 0.415090   0.009676  42.898
location_nameEritrea                          -0.141927   0.009676 -14.668
location_nameEstonia                           0.541948   0.009676  56.008
location_nameEthiopia                         -0.173354   0.009676 -17.915
location_nameFederated States of Micronesia    0.209379   0.009676  21.638
location_nameFiji                              0.297535   0.009676  30.749
location_nameFinland                           0.625494   0.009676  64.642
location_nameFrance                            0.700810   0.009676  72.426
location_nameGabon                             0.187008   0.009676  19.326
location_nameGeorgia                           0.228131   0.009676  23.576
location_nameGermany                           0.703028   0.009676  72.655
location_nameGhana                            -0.346773   0.009676 -35.838
location_nameGreece                            0.667048   0.009676  68.937
location_nameGreenland                         0.768537   0.009676  79.425
location_nameGrenada                           0.213445   0.009676  22.059
location_nameGuam                              0.577515   0.009676  59.684
location_nameGuatemala                         0.009005   0.009676   0.931
location_nameGuinea                           -0.194870   0.009676 -20.139
location_nameGuinea-Bissau                     0.133387   0.009676  13.785
location_nameGuyana                           -0.216393   0.009676 -22.363
location_nameHaiti                            -0.051033   0.009676  -5.274
location_nameHonduras                          0.117781   0.009676  12.172
location_nameHungary                           0.610661   0.009676  63.109
location_nameIceland                           0.659366   0.009676  68.143
location_nameIndia                            -0.650568   0.009676 -67.234
location_nameIndonesia                        -0.219020   0.009676 -22.635
location_nameIran                             -0.045613   0.009676  -4.714
location_nameIraq                             -0.318520   0.009676 -32.918
location_nameIreland                           0.800964   0.009676  82.776
location_nameIsrael                            0.483992   0.009676  50.019
location_nameItaly                             0.663507   0.009676  68.571
location_nameJamaica                          -0.033539   0.009676  -3.466
location_nameJapan                             0.374311   0.009676  38.683
location_nameJordan                            0.109734   0.009676  11.341
location_nameKazakhstan                        0.657682   0.009676  67.969
location_nameKenya                             0.104881   0.009676  10.839
location_nameKiribati                          0.130670   0.009676  13.504
location_nameKuwait                            0.365667   0.009676  37.790
location_nameKyrgyzstan                        0.424414   0.009676  43.861
location_nameLaos                              0.224672   0.009676  23.219
location_nameLatvia                            0.573087   0.009676  59.226
location_nameLebanon                           0.330736   0.009676  34.180
location_nameLesotho                           0.078852   0.009676   8.149
location_nameLiberia                          -0.380804   0.009676 -39.355
location_nameLibya                             0.101999   0.009676  10.541
location_nameLithuania                         0.653012   0.009676  67.486
location_nameLuxembourg                        0.801064   0.009676  82.787
location_nameMacedonia                         0.315642   0.009676  32.620
location_nameMadagascar                        0.010171   0.009676   1.051
location_nameMalawi                           -0.240867   0.009676 -24.893
location_nameMalaysia                          0.126939   0.009676  13.119
location_nameMaldives                         -0.131439   0.009676 -13.584
location_nameMali                              0.252462   0.009676  26.091
location_nameMalta                             0.654370   0.009676  67.626
location_nameMarshall Islands                  0.242041   0.009676  25.014
location_nameMauritania                        0.253405   0.009676  26.188
location_nameMauritius                         0.084586   0.009676   8.742
location_nameMexico                            0.493796   0.009676  51.032
location_nameMoldova                           0.248523   0.009676  25.684
location_nameMongolia                          0.802313   0.009676  82.916
location_nameMontenegro                        0.695318   0.009676  71.858
location_nameMorocco                           0.046481   0.009676   4.804
location_nameMozambique                       -0.146113   0.009676 -15.100
location_nameMyanmar                           0.161358   0.009676  16.676
location_nameNamibia                           0.271516   0.009676  28.060
location_nameNepal                            -0.003694   0.009676  -0.382
location_nameNetherlands                       0.689104   0.009676  71.216
location_nameNew Zealand                       0.792965   0.009676  81.950
location_nameNicaragua                        -0.108988   0.009676 -11.263
location_nameNiger                             0.191332   0.009676  19.773
location_nameNigeria                          -0.169992   0.009676 -17.568
location_nameNorth Korea                      -0.246185   0.009676 -25.442
location_nameNorthern Mariana Islands          0.435022   0.009676  44.958
location_nameNorway                            0.663406   0.009676  68.560
location_nameOman                              0.377107   0.009676  38.972
location_namePakistan                          0.014624   0.009676   1.511
location_namePalestine                        -0.002820   0.009676  -0.291
location_namePanama                            0.477302   0.009676  49.327
location_namePapua New Guinea                  0.177936   0.009676  18.389
location_nameParaguay                          0.601524   0.009676  62.165
location_namePeru                             -0.037011   0.009676  -3.825
location_namePhilippines                       0.344310   0.009676  35.583
location_namePoland                            0.687177   0.009676  71.017
location_namePortugal                          0.733722   0.009676  75.827
location_namePuerto Rico                       0.412028   0.009676  42.581
location_nameQatar                             0.429955   0.009676  44.434
location_nameRomania                           0.652514   0.009676  67.435
location_nameRussian Federation                0.535982   0.009676  55.392
location_nameRwanda                           -0.277812   0.009676 -28.711
location_nameSaint Lucia                       0.212165   0.009676  21.926
location_nameSaint Vincent and the Grenadines  0.196704   0.009676  20.329
location_nameSamoa                             0.267956   0.009676  27.692
location_nameSao Tome and Principe            -0.047627   0.009676  -4.922
location_nameSaudi Arabia                      0.057527   0.009676   5.945
location_nameSenegal                          -0.072896   0.009676  -7.534
location_nameSerbia                            0.508400   0.009676  52.541
location_nameSeychelles                        0.301701   0.009676  31.180
location_nameSierra Leone                     -0.522458   0.009676 -53.994
location_nameSingapore                         0.582594   0.009676  60.209
location_nameSlovakia                          0.627148   0.009676  64.813
location_nameSlovenia                          0.679024   0.009676  70.174
location_nameSolomon Islands                  -0.010315   0.009676  -1.066
location_nameSomalia                          -0.256388   0.009676 -26.497
location_nameSouth Africa                      0.435449   0.009676  45.002
location_nameSouth Korea                       0.592893   0.009676  61.273
location_nameSouth Sudan                       0.015340   0.009676   1.585
location_nameSpain                             0.678441   0.009676  70.114
location_nameSri Lanka                        -0.749966   0.009676 -77.506
location_nameSudan                            -0.050768   0.009676  -5.247
location_nameSuriname                          0.176747   0.009676  18.266
location_nameSwaziland                         0.301692   0.009676  31.179
location_nameSweden                            0.692026   0.009676  71.518
location_nameSwitzerland                       0.701932   0.009676  72.542
location_nameSyria                            -0.011904   0.009676  -1.230
location_nameTaiwan (Province of China)        0.677543   0.009676  70.021
location_nameTajikistan                        0.015453   0.009676   1.597
location_nameTanzania                         -0.129507   0.009676 -13.384
location_nameThailand                          0.164882   0.009676  17.040
location_nameThe Bahamas                       0.577812   0.009676  59.715
location_nameThe Gambia                       -0.389047   0.009676 -40.206
location_nameTimor-Leste                       0.060600   0.009676   6.263
location_nameTogo                             -0.308629   0.009676 -31.896
location_nameTonga                             0.289031   0.009676  29.870
location_nameTrinidad and Tobago               0.160447   0.009676  16.582
location_nameTunisia                          -0.005090   0.009676  -0.526
location_nameTurkey                            0.050119   0.009676   5.180
location_nameTurkmenistan                      0.722890   0.009676  74.708
location_nameUganda                            0.015166   0.009676   1.567
location_nameUkraine                           0.372418   0.009676  38.488
location_nameUnited Arab Emirates              0.283428   0.009676  29.291
location_nameUnited Kingdom                    0.556010   0.009676  57.461
location_nameUnited States                     0.708984   0.009676  73.271
location_nameUruguay                           0.632295   0.009676  65.345
location_nameUzbekistan                        0.458726   0.009676  47.407
location_nameVanuatu                           0.191061   0.009676  19.745
location_nameVenezuela                         0.439508   0.009676  45.421
location_nameVietnam                           0.494979   0.009676  51.154
location_nameVirgin Islands, U.S.              0.438211   0.009676  45.287
location_nameYemen                            -0.126839   0.009676 -13.108
location_nameZambia                           -0.104306   0.009676 -10.780
location_nameZimbabwe                          0.046567   0.009676   4.812
                                              Pr(>|t|)    
(Intercept)                                    < 2e-16 ***
sexMale                                        < 2e-16 ***
location_nameAlbania                           < 2e-16 ***
location_nameAlgeria                          4.90e-06 ***
location_nameAmerican Samoa                    < 2e-16 ***
location_nameAndorra                           < 2e-16 ***
location_nameAngola                            < 2e-16 ***
location_nameAntigua and Barbuda               < 2e-16 ***
location_nameArgentina                         < 2e-16 ***
location_nameArmenia                           < 2e-16 ***
location_nameAustralia                         < 2e-16 ***
location_nameAustria                           < 2e-16 ***
location_nameAzerbaijan                        < 2e-16 ***
location_nameBahrain                           < 2e-16 ***
location_nameBangladesh                        < 2e-16 ***
location_nameBarbados                          < 2e-16 ***
location_nameBelarus                           < 2e-16 ***
location_nameBelgium                           < 2e-16 ***
location_nameBelize                            < 2e-16 ***
location_nameBenin                             < 2e-16 ***
location_nameBermuda                           < 2e-16 ***
location_nameBhutan                            < 2e-16 ***
location_nameBolivia                           < 2e-16 ***
location_nameBosnia and Herzegovina            < 2e-16 ***
location_nameBotswana                          < 2e-16 ***
location_nameBrazil                            < 2e-16 ***
location_nameBrunei                            < 2e-16 ***
location_nameBulgaria                          < 2e-16 ***
location_nameBurkina Faso                      < 2e-16 ***
location_nameBurundi                           < 2e-16 ***
location_nameCambodia                          < 2e-16 ***
location_nameCameroon                         3.90e-08 ***
location_nameCanada                            < 2e-16 ***
location_nameCape Verde                        < 2e-16 ***
location_nameCentral African Republic          < 2e-16 ***
location_nameChad                             1.20e-05 ***
location_nameChile                             < 2e-16 ***
location_nameChina                             < 2e-16 ***
location_nameColombia                          < 2e-16 ***
location_nameComoros                           < 2e-16 ***
location_nameCongo                             < 2e-16 ***
location_nameCosta Rica                        < 2e-16 ***
location_nameCote d'Ivoire                     < 2e-16 ***
location_nameCroatia                           < 2e-16 ***
location_nameCuba                              < 2e-16 ***
location_nameCyprus                            < 2e-16 ***
location_nameCzech Republic                    < 2e-16 ***
location_nameDemocratic Republic of the Congo  < 2e-16 ***
location_nameDenmark                           < 2e-16 ***
location_nameDjibouti                          < 2e-16 ***
location_nameDominica                          < 2e-16 ***
location_nameDominican Republic                < 2e-16 ***
location_nameEcuador                           < 2e-16 ***
location_nameEgypt                             < 2e-16 ***
location_nameEl Salvador                      1.35e-05 ***
location_nameEquatorial Guinea                 < 2e-16 ***
location_nameEritrea                           < 2e-16 ***
location_nameEstonia                           < 2e-16 ***
location_nameEthiopia                          < 2e-16 ***
location_nameFederated States of Micronesia    < 2e-16 ***
location_nameFiji                              < 2e-16 ***
location_nameFinland                           < 2e-16 ***
location_nameFrance                            < 2e-16 ***
location_nameGabon                             < 2e-16 ***
location_nameGeorgia                           < 2e-16 ***
location_nameGermany                           < 2e-16 ***
location_nameGhana                             < 2e-16 ***
location_nameGreece                            < 2e-16 ***
location_nameGreenland                         < 2e-16 ***
location_nameGrenada                           < 2e-16 ***
location_nameGuam                              < 2e-16 ***
location_nameGuatemala                        0.353203    
location_nameGuinea                            < 2e-16 ***
location_nameGuinea-Bissau                     < 2e-16 ***
location_nameGuyana                            < 2e-16 ***
location_nameHaiti                            3.54e-07 ***
location_nameHonduras                          < 2e-16 ***
location_nameHungary                           < 2e-16 ***
location_nameIceland                           < 2e-16 ***
location_nameIndia                             < 2e-16 ***
location_nameIndonesia                         < 2e-16 ***
location_nameIran                             4.63e-06 ***
location_nameIraq                              < 2e-16 ***
location_nameIreland                           < 2e-16 ***
location_nameIsrael                            < 2e-16 ***
location_nameItaly                             < 2e-16 ***
location_nameJamaica                          0.000650 ***
location_nameJapan                             < 2e-16 ***
location_nameJordan                            < 2e-16 ***
location_nameKazakhstan                        < 2e-16 ***
location_nameKenya                             < 2e-16 ***
location_nameKiribati                          < 2e-16 ***
location_nameKuwait                            < 2e-16 ***
location_nameKyrgyzstan                        < 2e-16 ***
location_nameLaos                              < 2e-16 ***
location_nameLatvia                            < 2e-16 ***
location_nameLebanon                           < 2e-16 ***
location_nameLesotho                          4.43e-14 ***
location_nameLiberia                           < 2e-16 ***
location_nameLibya                             < 2e-16 ***
location_nameLithuania                         < 2e-16 ***
location_nameLuxembourg                        < 2e-16 ***
location_nameMacedonia                         < 2e-16 ***
location_nameMadagascar                       0.294507    
location_nameMalawi                            < 2e-16 ***
location_nameMalaysia                          < 2e-16 ***
location_nameMaldives                          < 2e-16 ***
location_nameMali                              < 2e-16 ***
location_nameMalta                             < 2e-16 ***
location_nameMarshall Islands                  < 2e-16 ***
location_nameMauritania                        < 2e-16 ***
location_nameMauritius                        1.09e-15 ***
location_nameMexico                            < 2e-16 ***
location_nameMoldova                           < 2e-16 ***
location_nameMongolia                          < 2e-16 ***
location_nameMontenegro                        < 2e-16 ***
location_nameMorocco                          3.11e-06 ***
location_nameMozambique                        < 2e-16 ***
location_nameMyanmar                           < 2e-16 ***
location_nameNamibia                           < 2e-16 ***
location_nameNepal                            0.703082    
location_nameNetherlands                       < 2e-16 ***
location_nameNew Zealand                       < 2e-16 ***
location_nameNicaragua                         < 2e-16 ***
location_nameNiger                             < 2e-16 ***
location_nameNigeria                           < 2e-16 ***
location_nameNorth Korea                       < 2e-16 ***
location_nameNorthern Mariana Islands          < 2e-16 ***
location_nameNorway                            < 2e-16 ***
location_nameOman                              < 2e-16 ***
location_namePakistan                         0.132342    
location_namePalestine                        0.771045    
location_namePanama                            < 2e-16 ***
location_namePapua New Guinea                  < 2e-16 ***
location_nameParaguay                          < 2e-16 ***
location_namePeru                             0.000176 ***
location_namePhilippines                       < 2e-16 ***
location_namePoland                            < 2e-16 ***
location_namePortugal                          < 2e-16 ***
location_namePuerto Rico                       < 2e-16 ***
location_nameQatar                             < 2e-16 ***
location_nameRomania                           < 2e-16 ***
location_nameRussian Federation                < 2e-16 ***
location_nameRwanda                            < 2e-16 ***
location_nameSaint Lucia                       < 2e-16 ***
location_nameSaint Vincent and the Grenadines  < 2e-16 ***
location_nameSamoa                             < 2e-16 ***
location_nameSao Tome and Principe            1.83e-06 ***
location_nameSaudi Arabia                     1.26e-08 ***
location_nameSenegal                          1.83e-12 ***
location_nameSerbia                            < 2e-16 ***
location_nameSeychelles                        < 2e-16 ***
location_nameSierra Leone                      < 2e-16 ***
location_nameSingapore                         < 2e-16 ***
location_nameSlovakia                          < 2e-16 ***
location_nameSlovenia                          < 2e-16 ***
location_nameSolomon Islands                  0.287747    
location_nameSomalia                           < 2e-16 ***
location_nameSouth Africa                      < 2e-16 ***
location_nameSouth Korea                       < 2e-16 ***
location_nameSouth Sudan                      0.114522    
location_nameSpain                             < 2e-16 ***
location_nameSri Lanka                         < 2e-16 ***
location_nameSudan                            4.04e-07 ***
location_nameSuriname                          < 2e-16 ***
location_nameSwaziland                         < 2e-16 ***
location_nameSweden                            < 2e-16 ***
location_nameSwitzerland                       < 2e-16 ***
location_nameSyria                            0.220097    
location_nameTaiwan (Province of China)        < 2e-16 ***
location_nameTajikistan                       0.111900    
location_nameTanzania                          < 2e-16 ***
location_nameThailand                          < 2e-16 ***
location_nameThe Bahamas                       < 2e-16 ***
location_nameThe Gambia                        < 2e-16 ***
location_nameTimor-Leste                      2.38e-09 ***
location_nameTogo                              < 2e-16 ***
location_nameTonga                             < 2e-16 ***
location_nameTrinidad and Tobago               < 2e-16 ***
location_nameTunisia                          0.599486    
location_nameTurkey                           5.55e-07 ***
location_nameTurkmenistan                      < 2e-16 ***
location_nameUganda                           0.118652    
location_nameUkraine                           < 2e-16 ***
location_nameUnited Arab Emirates              < 2e-16 ***
location_nameUnited Kingdom                    < 2e-16 ***
location_nameUnited States                     < 2e-16 ***
location_nameUruguay                           < 2e-16 ***
location_nameUzbekistan                        < 2e-16 ***
location_nameVanuatu                           < 2e-16 ***
location_nameVenezuela                         < 2e-16 ***
location_nameVietnam                           < 2e-16 ***
location_nameVirgin Islands, U.S.              < 2e-16 ***
location_nameYemen                             < 2e-16 ***
location_nameZambia                            < 2e-16 ***
location_nameZimbabwe                         2.99e-06 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 0.009676 on 194 degrees of freedom
Multiple R-squared:  0.9996,    Adjusted R-squared:  0.9993 
F-statistic:  2671 on 195 and 194 DF,  p-value: < 2.2e-16

First let’s look at the estimated coefficient for the sexMale variable, which is 0.18. This can be interpreted as the difference in mean log relative percent consumption between males and females, holding country constant. So comparing males to females within the same country. Notice this is the same estimated difference we found from our paired \(t\)-test! The \(p\)-value for this coefficient also matches the \(p\)-value from the paired \(t\)-test.

You can also see from this output that we have a coefficient for every country except Afghanistan, which is our reference country. These coefficients compare the country to that reference. So the estimated coefficient for location_nameAlbania, 0.44, can be interpreted as the difference in mean log relative percent consumption between Albania and Afghanistan, holding sex constant. So comparing Albania to Afghanistan within males or comparing Albania to Afghanistan within females.

Finally, you might notice that the number of residual degrees of freedom for this regression is 194, just as in the paired \(t\)-test. This makes sense since we have to estimate a coefficient for 194 countries (all except Afghanistan) as well as a coefficient for sex and an intercept. So we have:

\[df = n - # parameters estimating = 390 - 194 - 2 = 194\]

We should also check the residual plots for this fixed effects regression model.

diet_and_guidelines %>%
  filter(food == "red meat") %>%
  lm(log10(Relative_Percent) ~ sex + location_name, data = .) %>%
  plot(which = 1:3)

These residual plots look much better than our previous plots. This guide provides more information on how to interpret these residual diagnostic plots.

Based on our Q-Q plot, we appear to have some outliers perhaps at the extreme ends of our tails but overall the residuals look fairly normal. The residual vs fitted plot shows us if the relationship between our outcome variable and our predictors looks linear, if there is unequal error variance between groups, and if there are possible outliers. Ideally this should look like a band of points equally centered around zero. Here are examples of these plots that might show issues of concern.

Overall our plot looks fairly good. The shape of our data looks fairly linear (the residuals don’t appear to have a shape other than a band or line), there does not appear to be any extreme outliers (no data points are especially far away) and the points have the same general range around the line for the various fitted values. There are a few points with wider residuals at the higher fitted values, but overall this looks quite reasonable.

Our scale-location plot also shows us that our variance looks fairly equal across groups as our values show a relatively even spread. A larger bend in the line would indicate more variation in the variance across our independent variable groups also known as heteroscedasticity. There is only a slight bend in the line for our data suggestive of homoscedasticity. So our assumptions look pretty good:

  1. Linear - the relationship appears to be fairly linear
  2. Independence - now that we have taken care of the location structure in our data, our samples are independent
  3. Normality - the residuals appear to be fairly normally distributed and we have a large number of samples to help account for minor violations
  4. Equal variance - the variance in the residuals appear to be fairly equal across the groups of the independent/predictor variables

Paired \(t\)-test and linear model with mixed effects


To “pair” our data using fixed effects cost us an additional 194 variables in our regression model, one for each country except Afghanistan. Alternatively, we can perform a slightly different type of regression that still accounts for the paired structure in the data.

In this case we will use the lmer() function of the lme4 package. This function allows us to fit what is called a linear mixed effects regression model. We will also use the lmerTest package, since this adds test statistics and \(p\)-values to the linear mixed effects model output.

This type of regression is called mixed because it contains both fixed and random effects. There are many different definitions for fixed and random effects and the difference is conceptually complex and context specific.

However in simplistic terms, fixed effects are generally speaking the variables of interest that we have reason to believe explain or predict the outcome or response variable, while random effects are those that may introduce additional variance in the influence of those predictor variables on the outcome variable. For example, they may provide information about group or batch structures within the data.

In our case, we are interested in the influence of sex on the consumption of red meat, however the identity of the country where the male and female consumption values were obtained may influence this relationship and we would like to control for that. We don’t want to model for location_name itself, but just model it’s influence on the relationship of sex on consumption of red meat. In other words, we are interested in getting a sense of how sex influences consumption rates in general and we want to account for the paired structure within our data, the fact that we have corresponding consumption values for the two sexes from different countries. The notation for including a random effect like this is 1 | variable_name. The one indicates a varying-intercept group effect, in other words we expect that the intercept may vary for each value of the variable indicated to the right of the |. So in our case, the intercept (log relative percent consumption when sex is assigned to the zero value - female) may be different for each country.

Let’s fit a mixed effects model that includes a fixed effect for sex and a random intercept for country:

diet_and_guidelines %>%
  filter(food == "red meat") %>%
  lmer(log10(Relative_Percent) ~ sex + (1 | location_name), data = .) %>%
  summary()
Linear mixed model fit by REML. t-tests use Satterthwaite's method [
lmerModLmerTest]
Formula: log10(Relative_Percent) ~ sex + (1 | location_name)
   Data: .

REML criterion at convergence: -969.5

Scaled residuals: 
    Min      1Q  Median      3Q     Max 
-3.2253 -0.3914 -0.0038  0.3993  3.2527 

Random effects:
 Groups        Name        Variance  Std.Dev.
 location_name (Intercept) 1.171e-01 0.342181
 Residual                  9.363e-05 0.009676
Number of obs: 390, groups:  location_name, 195

Fixed effects:
             Estimate Std. Error        df t value Pr(>|t|)    
(Intercept)   1.79887    0.02451 194.15516   73.38   <2e-16 ***
sexMale       0.18439    0.00098 194.00000  188.16   <2e-16 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Correlation of Fixed Effects:
        (Intr)
sexMale -0.020

How would we interpret the results of this model? Again, let’s look at the estimated coefficient for the sexMale variable, which is 0.18. This can be interpreted the same way as in the simple linear regression, as the difference in mean log relative percent consumption between males and females. However, here we haven’t violated the independence assumption because we are accounting for the paired nature of the data through the random effect for country. The \(t\)-statistic and \(p\)-value for this coefficient also match those from the paired \(t\)-test we did before:

t.test(log10(pull(
  filter(wide_diet, food == "red meat"),
  Relative_Percent_Male
)),
log10(pull(
  filter(wide_diet, food == "red meat"),
  Relative_Percent_Female
)),
var.equal = TRUE, paired = TRUE
)

    Paired t-test

data:  log10(pull(filter(wide_diet, food == "red meat"), Relative_Percent_Male)) and log10(pull(filter(wide_diet, food == "red meat"), Relative_Percent_Female))
t = 188.16, df = 194, p-value < 2.2e-16
alternative hypothesis: true difference in means is not equal to 0
95 percent confidence interval:
 0.1824542 0.1863197
sample estimates:
mean of the differences 
               0.184387 

Notice that in the output for the mixed effects model, there are not coefficients for each country, like there were in the fixed effects model. This is because we are not explicitly estimating individual country effects in this model. Instead, the country effect is captured through the intercept in this model. Our estimated intercept is 1.80 and the standard deviation of this intercept is 0.34 (shown in the Random effects table in the output.) We can interpret this as saying that each country has an intercept that comes from a normal distribution with mean of 1.80 and a standard deviation of 0.34. Since the intercept in this model represents the log relative percent consumption for females, this give us an idea of how female consumption varies across countries – average log consumption across countries is 1.80, but there is variability from one country to another. And then the male log consumption is, on average, 0.18 higher than for females.

It is more complicated to calculate the degrees of freedom in the mixed effect model and beyond this case study, but it is based on the Satterthwaite formula and results in the same degrees of freedom.

Finally, lets see what our residual plots look like for this mixed effects model. We can’t use the plot() function with a lmer() model to get all of the plots at once, but we can construct a residual vs. fitted value plot and a Q-Q plot ourselves:

diet_and_guidelines %>%
  filter(food == "red meat") %>%
  lmer(log10(Relative_Percent) ~ sex + (1 | location_name), data = .) %>%
  plot()

diet_and_guidelines %>%
  filter(food == "red meat") %>%
  lmer(log10(Relative_Percent) ~ sex + (1 | location_name), data = .) %>%
  resid() %>%
  qqnorm()

diet_and_guidelines %>%
  filter(food == "red meat") %>%
  lmer(log10(Relative_Percent) ~ sex + (1 | location_name), data = .) %>%
  resid() %>%
  qqline()

Notice that the plots look very similar to what we saw with the fixed effects model.

We see that the paired \(t\)-test, the fixed effects model adjusting for country, and the mixed effects model with a random intercept for country all give the same results in this case. So which test should we use? The decision of which test to perform depends on your question of interest. In this case we were particularly interested in the influence of sex, so setting location_name to a random effect provides the same level of detail about sex without as much information about location_name, so that might be ideal. As we can see, the results, in this case, are the same. The benefit of using regression over a simple paired \(t\)-test would be the ability to add other covariates to our model if we wanted to adjust for other country characteristics.

Overall, though, we can conclude from these tests that we have enough evidence to reject the null hypothesis that there is no difference between the mean consumption of males and females ( or that sex has no association or influence on red meat consumption.) Therefore, it appears that males consume significantly more red meat than females globally.

ANalysis Of VAriance (ANOVA) test


We are also interested in the influence of age group on dietary consumption, but because there are 15 age groups we can’t assess the influence of age group on consumption using the paired \(t\)-test, as this test can only compare 2 groups.

If we wanted to test the hypothesis that there are any age group differences, that at least one of the groups is different from the others; we could use an ANOVA test. This test allows us to compare means of 3 or more groups by evaluating the variance of the data within the groups and among the groups.

Our null hypothesis is that all age groups have equal means: \[ H_0: \mu_{1} = \mu_{2} =\mu_{3}=\mu_{4} = ... \mu_{15} \]

The alternative hypothesis is that at least one age group mean is not equal to the others.

Importantly, if we reject the null, we do not know which group means are different from one another. Subsequent testing is required if we want to know this information. In this case we call this type of non-specific hypothesis an “omnibus” hypothesis.

You could actually perform an ANOVA to compare two means, but in this case you would get an \(F\)-statistic instead of a \(t\)-statistic which would be equivalent to \(t^2\). However it is not conventional to use ANOVA for only 2 means. The \(F\)-statistic is derived form the \(F\)-test is used for a few different type of tests. In the ANOVA the F-test is calculated as:

\[F = \frac{ variability\ between\ the \ groups}{ variablity\ within\ the \ groups}\]

The larger the ratio, the larger the variability between the groups, thus the more likely that the data for each group comes from a different distribution with different means, suggesting that the groups are different.

It turns out that the ANOVA test is also equivalent to linear regression. We will demonstrate this by evaluating how the consumption of red meat varies by age group using an ANOVA and a linear regression.

Thinking about how we want to know if red meat consumption differs between age groups from the linear regression perspective, we could also describe our null hypothesis as:

There is no influence of age group identity on consumption or there is no relationship between age group identity and consumption.

And we could describe our alternative hypothesis as:

Age group identity does influence consumption or explain some of the variation in consumption.

ANOVA assumptions

The ANOVA assumptions are quite similar to the \(t\)-test assumptions:

  1. Normality of the data for all tested groups (less of an issue if the number of observations is relatively large total n > 30)
  2. Equal variance between the groups - aka Homogeneity of Variances assumption (make sure you do the correct test if the data is not normal)
  3. Independent observations

let’s evaluate our assumptions for the groups we are comparing, starting with normality using Q-Q plots. First let’s make age_group_name a factor:

all_age_diet_and_guidelines %<>%
  mutate_at(vars(age_group_name), factor)

Now let’s look at Q-Q plots of both relative percent consumption and the log-transformed version of this variable:

all_age_diet_and_guidelines %>%
  filter(food == "red meat") %>%
  ggplot(aes(sample = Relative_Percent)) +
  facet_wrap(~age_group_name) +
  geom_qq() +
  geom_qq_line()

all_age_diet_and_guidelines %>%
  filter(food == "red meat") %>%
  ggplot(aes(sample = log10(Relative_Percent))) +
  facet_wrap(~age_group_name) +
  geom_qq() +
  geom_qq_line()

After transformation, these Q-Q plots look pretty good.

Now let’s look at the assumption of constant variance. There are different ways to assess this assumption across more than two groups. Bartlett’s test works well if the data appears to be quite normally distributed, while the Fligner-Killeen test is nonparametric and does not assume normality of the data.

We will use another popular test, Levene’s test, which is more robust to violations of normality than the Bartlett’s test, but not as robust as the Fligner-Killeen test. The null hypothesis of this test, as for the other two tests, is that the variances are equal across all of the groups. The alternative hypothesis is that at least one pair of groups has different variances. In symbols we would write this as

\[ H_0: \sigma_1^2 = \sigma_2^2 = \sigma_3^2 ... = \sigma_n^2 \]

and

\[H_a:\sigma_i^2 \neq \sigma_j^2 \] for at least one pair (\(i\),\(j\)).

We will use the leveneTest() function of the car package to performs Levene’s test.

all_age_diet_and_guidelines %>%
  filter(food == "red meat") %>%
  car::leveneTest(log10(Relative_Percent) ~ age_group_name, data = .)
Levene's Test for Homogeneity of Variance (center = median)
        Df F value Pr(>F)
group   14  0.0063      1
      5835               

Our data does not appear to violate the homogeneity of variances assumption as our \(p\)-value was greater than 0.05 and so we would fail to reject the null hypothesis of equal variances.

We already know that our independence assumption is not met, since the data for the different age groups comes from the same countries. We will account for this in later models, but first let’s compare the results between ANOVA and linear regression assuming the independence assumption is met.

ANOVA and linear regression

We can use the aov() function of the stats package to perform an ANOVA test. We will be performing what is called a one-way ANOVA because we only have one independent variable (age group). We will also perform a linear regression for comparison.

all_age_diet_and_guidelines %>%
  filter(food == "red meat") %>%
  aov(log10(Relative_Percent) ~ age_group_name, data = .) %>%
  summary()
                 Df Sum Sq Mean Sq F value Pr(>F)    
age_group_name   14   42.5   3.038   23.37 <2e-16 ***
Residuals      5835  758.7   0.130                   
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
all_age_diet_and_guidelines %>%
  filter(food == "red meat") %>%
  lm(log10(Relative_Percent) ~ age_group_name, data = .) %>%
  summary()

Call:
lm(formula = log10(Relative_Percent) ~ age_group_name, data = .)

Residuals:
     Min       1Q   Median       3Q      Max 
-1.09054 -0.25152  0.00684  0.30072  0.72695 

Coefficients:
                        Estimate Std. Error t value Pr(>|t|)    
(Intercept)             1.921191   0.018259 105.219  < 2e-16 ***
age_group_name30 to 34  0.001325   0.025822   0.051 0.959073    
age_group_name35 to 39  0.001745   0.025822   0.068 0.946133    
age_group_name40 to 44 -0.004447   0.025822  -0.172 0.863283    
age_group_name45 to 49 -0.019088   0.025822  -0.739 0.459801    
age_group_name50 to 54 -0.034767   0.025822  -1.346 0.178223    
age_group_name55 to 59 -0.051470   0.025822  -1.993 0.046279 *  
age_group_name60 to 64 -0.071080   0.025822  -2.753 0.005929 ** 
age_group_name65 to 69 -0.094345   0.025822  -3.654 0.000261 ***
age_group_name70 to 74 -0.119423   0.025822  -4.625 3.83e-06 ***
age_group_name75 to 79 -0.147352   0.025822  -5.706 1.21e-08 ***
age_group_name80 to 84 -0.210007   0.025822  -8.133 5.08e-16 ***
age_group_name85 to 89 -0.215369   0.025822  -8.341  < 2e-16 ***
age_group_name90 to 94 -0.218172   0.025822  -8.449  < 2e-16 ***
age_group_name95 plus  -0.218096   0.025822  -8.446  < 2e-16 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 0.3606 on 5835 degrees of freedom
Multiple R-squared:  0.05309,   Adjusted R-squared:  0.05081 
F-statistic: 23.37 on 14 and 5835 DF,  p-value: < 2.2e-16

We can see that the \(F\)-statistic (\(F\) value in the aov() output and at the bottom of the lm() output) is the same for both models and the \(p\)-value for the \(F\)-statistic is the same!

We also see that the degrees of freedom for the \(F\)-statistic is 14. This makes sense because we have 15 different age groups and degrees of freedom for the \(F\)-statistic are calculated as \(df = n - 1\). So in our case: \(df = 15 -1\).

The difference here is that with the lm() model we also get information about how the individual age groups are associated with the log relative percent consumption of red meat. Notice that if we look at all the age groups in the data

all_age_diet_and_guidelines %>%
  distinct(age_group_name)
# A tibble: 15 x 1
   age_group_name
   <fct>         
 1 25 to 29      
 2 30 to 34      
 3 35 to 39      
 4 40 to 44      
 5 45 to 49      
 6 50 to 54      
 7 55 to 59      
 8 60 to 64      
 9 65 to 69      
10 70 to 74      
11 75 to 79      
12 80 to 84      
13 85 to 89      
14 90 to 94      
15 95 plus       

we see that our lm() results are missing one of the age groups, the 25 to 29 age group. That is because this is the reference group and the coefficients indicate the slope or difference in log relative percent consumption rates for each listed age group compared to this reference group.

ANOVA and linear regression with fixed effects

Now let’s account for the paired location_name structure within our data, since the above models violate the independence assumptions for ANOVA and linear regression. We can do this by adding another fixed effect to both the ANOVA model and the linear regression model. For ANOVA, this means we are now doing a two-way ANOVA, since we have two independent variables (age group and country). For linear regression, we are now adding a fixed effect for country to our model.

all_age_diet_and_guidelines %>%
  filter(food == "red meat") %>%
  aov(log10(Relative_Percent) ~ age_group_name + location_name, data = .) %>%
  summary()
                 Df Sum Sq Mean Sq F value Pr(>F)    
age_group_name   14   42.5   3.038   356.2 <2e-16 ***
location_name   194  710.6   3.663   429.5 <2e-16 ***
Residuals      5641   48.1   0.009                   
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
all_age_diet_and_guidelines %>%
  filter(food == "red meat") %>%
  lm(log10(Relative_Percent) ~ age_group_name + location_name, data = .) %>%
  summary()

Call:
lm(formula = log10(Relative_Percent) ~ age_group_name + location_name, 
    data = .)

Residuals:
     Min       1Q   Median       3Q      Max 
-0.17321 -0.08990  0.04057  0.08969  0.19936 

Coefficients:
                                                Estimate Std. Error t value
(Intercept)                                    1.6473808  0.0174553  94.377
age_group_name30 to 34                         0.0013252  0.0066133   0.200
age_group_name35 to 39                         0.0017447  0.0066133   0.264
age_group_name40 to 44                        -0.0044467  0.0066133  -0.672
age_group_name45 to 49                        -0.0190883  0.0066133  -2.886
age_group_name50 to 54                        -0.0347670  0.0066133  -5.257
age_group_name55 to 59                        -0.0514702  0.0066133  -7.783
age_group_name60 to 64                        -0.0710801  0.0066133 -10.748
age_group_name65 to 69                        -0.0943452  0.0066133 -14.266
age_group_name70 to 74                        -0.1194232  0.0066133 -18.058
age_group_name75 to 79                        -0.1473520  0.0066133 -22.281
age_group_name80 to 84                        -0.2100070  0.0066133 -31.755
age_group_name85 to 89                        -0.2153687  0.0066133 -32.566
age_group_name90 to 94                        -0.2181720  0.0066133 -32.990
age_group_name95 plus                         -0.2180964  0.0066133 -32.979
location_nameAlbania                           0.4696634  0.0238444  19.697
location_nameAlgeria                           0.0524978  0.0238444   2.202
location_nameAmerican Samoa                    0.4781072  0.0238444  20.051
location_nameAndorra                           0.7709817  0.0238444  32.334
location_nameAngola                            0.2502250  0.0238444  10.494
location_nameAntigua and Barbuda               0.3148782  0.0238444  13.206
location_nameArgentina                         0.8391115  0.0238444  35.191
location_nameArmenia                           0.4143613  0.0238444  17.378
location_nameAustralia                         0.8382715  0.0238444  35.156
location_nameAustria                           0.9006351  0.0238444  37.771
location_nameAzerbaijan                        0.3013522  0.0238444  12.638
location_nameBahrain                           0.2892541  0.0238444  12.131
location_nameBangladesh                       -0.6036909  0.0238444 -25.318
location_nameBarbados                          0.3355809  0.0238444  14.074
location_nameBelarus                           0.7101162  0.0238444  29.781
location_nameBelgium                           0.7081735  0.0238444  29.700
location_nameBelize                            0.1840253  0.0238444   7.718
location_nameBenin                            -0.3251738  0.0238444 -13.637
location_nameBermuda                           0.4539095  0.0238444  19.036
location_nameBhutan                           -0.1193613  0.0238444  -5.006
location_nameBolivia                           0.5011897  0.0238444  21.019
location_nameBosnia and Herzegovina            0.1966458  0.0238444   8.247
location_nameBotswana                          0.1360033  0.0238444   5.704
location_nameBrazil                            0.7420931  0.0238444  31.122
location_nameBrunei                            0.2248900  0.0238444   9.432
location_nameBulgaria                          0.7016126  0.0238444  29.425
location_nameBurkina Faso                      0.0952272  0.0238444   3.994
location_nameBurundi                          -0.1975274  0.0238444  -8.284
location_nameCambodia                          0.1162693  0.0238444   4.876
location_nameCameroon                         -0.0547177  0.0238444  -2.295
location_nameCanada                            0.6965652  0.0238444  29.213
location_nameCape Verde                        0.4033481  0.0238444  16.916
location_nameCentral African Republic          0.3695721  0.0238444  15.499
location_nameChad                              0.0445016  0.0238444   1.866
location_nameChile                             0.6899420  0.0238444  28.935
location_nameChina                             0.6542148  0.0238444  27.437
location_nameColombia                          0.3677083  0.0238444  15.421
location_nameComoros                          -0.1092061  0.0238444  -4.580
location_nameCongo                            -0.2562887  0.0238444 -10.748
location_nameCosta Rica                        0.3922246  0.0238444  16.449
location_nameCote d'Ivoire                    -0.3878599  0.0238444 -16.266
location_nameCroatia                           0.6168738  0.0238444  25.871
location_nameCuba                              0.3715729  0.0238444  15.583
location_nameCyprus                            0.6210060  0.0238444  26.044
location_nameCzech Republic                    0.6754221  0.0238444  28.326
location_nameDemocratic Republic of the Congo -0.1381953  0.0238444  -5.796
location_nameDenmark                           0.6940034  0.0238444  29.105
location_nameDjibouti                          0.2040664  0.0238444   8.558
location_nameDominica                          0.2201500  0.0238444   9.233
location_nameDominican Republic                0.2904822  0.0238444  12.182
location_nameEcuador                           0.5001219  0.0238444  20.974
location_nameEgypt                             0.1407597  0.0238444   5.903
location_nameEl Salvador                       0.0564578  0.0238444   2.368
location_nameEquatorial Guinea                 0.4145078  0.0238444  17.384
location_nameEritrea                          -0.1427694  0.0238444  -5.988
location_nameEstonia                           0.5724487  0.0238444  24.008
location_nameEthiopia                         -0.1749948  0.0238444  -7.339
location_nameFederated States of Micronesia    0.2177319  0.0238444   9.131
location_nameFiji                              0.3063835  0.0238444  12.849
location_nameFinland                           0.6616460  0.0238444  27.748
location_nameFrance                            0.7352843  0.0238444  30.837
location_nameGabon                             0.1926532  0.0238444   8.080
location_nameGeorgia                           0.2530224  0.0238444  10.611
location_nameGermany                           0.7386742  0.0238444  30.979
location_nameGhana                            -0.3464979  0.0238444 -14.532
location_nameGreece                            0.7022224  0.0238444  29.450
location_nameGreenland                         0.7880282  0.0238444  33.049
location_nameGrenada                           0.2394215  0.0238444  10.041
location_nameGuam                              0.5937555  0.0238444  24.901
location_nameGuatemala                         0.0139062  0.0238444   0.583
location_nameGuinea                           -0.1905498  0.0238444  -7.991
location_nameGuinea-Bissau                     0.1322312  0.0238444   5.546
location_nameGuyana                           -0.2063983  0.0238444  -8.656
location_nameHaiti                            -0.0491435  0.0238444  -2.061
location_nameHonduras                          0.1244476  0.0238444   5.219
location_nameHungary                           0.6410019  0.0238444  26.883
location_nameIceland                           0.6839722  0.0238444  28.685
location_nameIndia                            -0.6388459  0.0238444 -26.792
location_nameIndonesia                        -0.2110810  0.0238444  -8.852
location_nameIran                             -0.0399115  0.0238444  -1.674
location_nameIraq                             -0.3147627  0.0238444 -13.201
location_nameIreland                           0.8205275  0.0238444  34.412
location_nameIsrael                            0.5031271  0.0238444  21.100
location_nameItaly                             0.7012419  0.0238444  29.409
location_nameJamaica                          -0.0196064  0.0238444  -0.822
location_nameJapan                             0.4163050  0.0238444  17.459
location_nameJordan                            0.1122555  0.0238444   4.708
location_nameKazakhstan                        0.6667740  0.0238444  27.964
location_nameKenya                             0.1062621  0.0238444   4.456
location_nameKiribati                          0.1360152  0.0238444   5.704
location_nameKuwait                            0.3628791  0.0238444  15.219
location_nameKyrgyzstan                        0.4299491  0.0238444  18.031
location_nameLaos                              0.2281184  0.0238444   9.567
location_nameLatvia                            0.6051096  0.0238444  25.377
location_nameLebanon                           0.3358240  0.0238444  14.084
location_nameLesotho                           0.0827870  0.0238444   3.472
location_nameLiberia                          -0.3810751  0.0238444 -15.982
location_nameLibya                             0.1058210  0.0238444   4.438
location_nameLithuania                         0.6853303  0.0238444  28.742
location_nameLuxembourg                        0.8252128  0.0238444  34.608
location_nameMacedonia                         0.3375009  0.0238444  14.154
location_nameMadagascar                        0.0086607  0.0238444   0.363
location_nameMalawi                           -0.2390062  0.0238444 -10.024
location_nameMalaysia                          0.1348008  0.0238444   5.653
location_nameMaldives                         -0.1304736  0.0238444  -5.472
location_nameMali                              0.2545834  0.0238444  10.677
location_nameMalta                             0.6861462  0.0238444  28.776
location_nameMarshall Islands                  0.2453513  0.0238444  10.290
location_nameMauritania                        0.2586036  0.0238444  10.845
location_nameMauritius                         0.1018209  0.0238444   4.270
location_nameMexico                            0.5019364  0.0238444  21.050
location_nameMoldova                           0.2694136  0.0238444  11.299
location_nameMongolia                          0.8050147  0.0238444  33.761
location_nameMontenegro                        0.7197936  0.0238444  30.187
location_nameMorocco                           0.0536665  0.0238444   2.251
location_nameMozambique                       -0.1454548  0.0238444  -6.100
location_nameMyanmar                           0.1701756  0.0238444   7.137
location_nameNamibia                           0.2766865  0.0238444  11.604
location_nameNepal                             0.0040043  0.0238444   0.168
location_nameNetherlands                       0.7182009  0.0238444  30.120
location_nameNew Zealand                       0.8215769  0.0238444  34.456
location_nameNicaragua                        -0.1022029  0.0238444  -4.286
location_nameNiger                             0.1929072  0.0238444   8.090
location_nameNigeria                          -0.1687456  0.0238444  -7.077
location_nameNorth Korea                      -0.2308157  0.0238444  -9.680
location_nameNorthern Mariana Islands          0.4519371  0.0238444  18.954
location_nameNorway                            0.6802116  0.0238444  28.527
location_nameOman                              0.3715916  0.0238444  15.584
location_namePakistan                          0.0163832  0.0238444   0.687
location_namePalestine                        -0.0001115  0.0238444  -0.005
location_namePanama                            0.4910430  0.0238444  20.594
location_namePapua New Guinea                  0.1780454  0.0238444   7.467
location_nameParaguay                          0.6105228  0.0238444  25.604
location_namePeru                             -0.0270767  0.0238444  -1.136
location_namePhilippines                       0.3489508  0.0238444  14.634
location_namePoland                            0.7119822  0.0238444  29.859
location_namePortugal                          0.7651172  0.0238444  32.088
location_namePuerto Rico                       0.4442532  0.0238444  18.631
location_nameQatar                             0.4226739  0.0238444  17.726
location_nameRomania                           0.6732206  0.0238444  28.234
location_nameRussian Federation                0.5601442  0.0238444  23.492
location_nameRwanda                           -0.2771514  0.0238444 -11.623
location_nameSaint Lucia                       0.2295368  0.0238444   9.626
location_nameSaint Vincent and the Grenadines  0.2179043  0.0238444   9.139
location_nameSamoa                             0.2797823  0.0238444  11.734
location_nameSao Tome and Principe            -0.0434018  0.0238444  -1.820
location_nameSaudi Arabia                      0.0524593  0.0238444   2.200
location_nameSenegal                          -0.0681700  0.0238444  -2.859
location_nameSerbia                            0.5359842  0.0238444  22.478
location_nameSeychelles                        0.3146719  0.0238444  13.197
location_nameSierra Leone                     -0.5214405  0.0238444 -21.868
location_nameSingapore                         0.5946425  0.0238444  24.938
location_nameSlovakia                          0.6438632  0.0238444  27.003
location_nameSlovenia                          0.7116904  0.0238444  29.847
location_nameSolomon Islands                  -0.0090348  0.0238444  -0.379
location_nameSomalia                          -0.2564213  0.0238444 -10.754
location_nameSouth Africa                      0.4267033  0.0238444  17.895
location_nameSouth Korea                       0.5949378  0.0238444  24.951
location_nameSouth Sudan                       0.0164738  0.0238444   0.691
location_nameSpain                             0.7138244  0.0238444  29.937
location_nameSri Lanka                        -0.7330116  0.0238444 -30.741
location_nameSudan                            -0.0465153  0.0238444  -1.951
location_nameSuriname                          0.1906880  0.0238444   7.997
location_nameSwaziland                         0.3040096  0.0238444  12.750
location_nameSweden                            0.7247779  0.0238444  30.396
location_nameSwitzerland                       0.7281804  0.0238444  30.539
location_nameSyria                            -0.0024640  0.0238444  -0.103
location_nameTaiwan (Province of China)        0.6900827  0.0238444  28.941
location_nameTajikistan                        0.0176046  0.0238444   0.738
location_nameTanzania                         -0.1278459  0.0238444  -5.362
location_nameThailand                          0.1802762  0.0238444   7.561
location_nameThe Bahamas                       0.5890517  0.0238444  24.704
location_nameThe Gambia                       -0.3863879  0.0238444 -16.205
location_nameTimor-Leste                       0.0705710  0.0238444   2.960
location_nameTogo                             -0.3083612  0.0238444 -12.932
location_nameTonga                             0.3002675  0.0238444  12.593
location_nameTrinidad and Tobago               0.1770619  0.0238444   7.426
location_nameTunisia                           0.0071742  0.0238444   0.301
location_nameTurkey                            0.0631309  0.0238444   2.648
location_nameTurkmenistan                      0.7290156  0.0238444  30.574
location_nameUganda                            0.0146972  0.0238444   0.616
location_nameUkraine                           0.3932282  0.0238444  16.491
location_nameUnited Arab Emirates              0.2716685  0.0238444  11.393
location_nameUnited Kingdom                    0.5932920  0.0238444  24.882
location_nameUnited States                     0.7390080  0.0238444  30.993
location_nameUruguay                           0.6597541  0.0238444  27.669
location_nameUzbekistan                        0.4620568  0.0238444  19.378
location_nameVanuatu                           0.1977037  0.0238444   8.291
location_nameVenezuela                         0.4446078  0.0238444  18.646
location_nameVietnam                           0.5087180  0.0238444  21.335
location_nameVirgin Islands, U.S.              0.4675163  0.0238444  19.607
location_nameYemen                            -0.1277139  0.0238444  -5.356
location_nameZambia                           -0.1062726  0.0238444  -4.457
location_nameZimbabwe                          0.0462498  0.0238444   1.940
                                              Pr(>|t|)    
(Intercept)                                    < 2e-16 ***
age_group_name30 to 34                        0.841191    
age_group_name35 to 39                        0.791927    
age_group_name40 to 44                        0.501365    
age_group_name45 to 49                        0.003912 ** 
age_group_name50 to 54                        1.52e-07 ***
age_group_name55 to 59                        8.37e-15 ***
age_group_name60 to 64                         < 2e-16 ***
age_group_name65 to 69                         < 2e-16 ***
age_group_name70 to 74                         < 2e-16 ***
age_group_name75 to 79                         < 2e-16 ***
age_group_name80 to 84                         < 2e-16 ***
age_group_name85 to 89                         < 2e-16 ***
age_group_name90 to 94                         < 2e-16 ***
age_group_name95 plus                          < 2e-16 ***
location_nameAlbania                           < 2e-16 ***
location_nameAlgeria                          0.027728 *  
location_nameAmerican Samoa                    < 2e-16 ***
location_nameAndorra                           < 2e-16 ***
location_nameAngola                            < 2e-16 ***
location_nameAntigua and Barbuda               < 2e-16 ***
location_nameArgentina                         < 2e-16 ***
location_nameArmenia                           < 2e-16 ***
location_nameAustralia                         < 2e-16 ***
location_nameAustria                           < 2e-16 ***
location_nameAzerbaijan                        < 2e-16 ***
location_nameBahrain                           < 2e-16 ***
location_nameBangladesh                        < 2e-16 ***
location_nameBarbados                          < 2e-16 ***
location_nameBelarus                           < 2e-16 ***
location_nameBelgium                           < 2e-16 ***
location_nameBelize                           1.39e-14 ***
location_nameBenin                             < 2e-16 ***
location_nameBermuda                           < 2e-16 ***
location_nameBhutan                           5.73e-07 ***
location_nameBolivia                           < 2e-16 ***
location_nameBosnia and Herzegovina            < 2e-16 ***
location_nameBotswana                         1.23e-08 ***
location_nameBrazil                            < 2e-16 ***
location_nameBrunei                            < 2e-16 ***
location_nameBulgaria                          < 2e-16 ***
location_nameBurkina Faso                     6.59e-05 ***
location_nameBurundi                           < 2e-16 ***
location_nameCambodia                         1.11e-06 ***
location_nameCameroon                         0.021782 *  
location_nameCanada                            < 2e-16 ***
location_nameCape Verde                        < 2e-16 ***
location_nameCentral African Republic          < 2e-16 ***
location_nameChad                             0.062047 .  
location_nameChile                             < 2e-16 ***
location_nameChina                             < 2e-16 ***
location_nameColombia                          < 2e-16 ***
location_nameComoros                          4.75e-06 ***
location_nameCongo                             < 2e-16 ***
location_nameCosta Rica                        < 2e-16 ***
location_nameCote d'Ivoire                     < 2e-16 ***
location_nameCroatia                           < 2e-16 ***
location_nameCuba                              < 2e-16 ***
location_nameCyprus                            < 2e-16 ***
location_nameCzech Republic                    < 2e-16 ***
location_nameDemocratic Republic of the Congo 7.17e-09 ***
location_nameDenmark                           < 2e-16 ***
location_nameDjibouti                          < 2e-16 ***
location_nameDominica                          < 2e-16 ***
location_nameDominican Republic                < 2e-16 ***
location_nameEcuador                           < 2e-16 ***
location_nameEgypt                            3.77e-09 ***
location_nameEl Salvador                      0.017930 *  
location_nameEquatorial Guinea                 < 2e-16 ***
location_nameEritrea                          2.26e-09 ***
location_nameEstonia                           < 2e-16 ***
location_nameEthiopia                         2.46e-13 ***
location_nameFederated States of Micronesia    < 2e-16 ***
location_nameFiji                              < 2e-16 ***
location_nameFinland                           < 2e-16 ***
location_nameFrance                            < 2e-16 ***
location_nameGabon                            7.88e-16 ***
location_nameGeorgia                           < 2e-16 ***
location_nameGermany                           < 2e-16 ***
location_nameGhana                             < 2e-16 ***
location_nameGreece                            < 2e-16 ***
location_nameGreenland                         < 2e-16 ***
location_nameGrenada                           < 2e-16 ***
location_nameGuam                              < 2e-16 ***
location_nameGuatemala                        0.559777    
location_nameGuinea                           1.61e-15 ***
location_nameGuinea-Bissau                    3.06e-08 ***
location_nameGuyana                            < 2e-16 ***
location_nameHaiti                            0.039348 *  
location_nameHonduras                         1.86e-07 ***
location_nameHungary                           < 2e-16 ***
location_nameIceland                           < 2e-16 ***
location_nameIndia                             < 2e-16 ***
location_nameIndonesia                         < 2e-16 ***
location_nameIran                             0.094219 .  
location_nameIraq                              < 2e-16 ***
location_nameIreland                           < 2e-16 ***
location_nameIsrael                            < 2e-16 ***
location_nameItaly                             < 2e-16 ***
location_nameJamaica                          0.410962    
location_nameJapan                             < 2e-16 ***
location_nameJordan                           2.56e-06 ***
location_nameKazakhstan                        < 2e-16 ***
location_nameKenya                            8.49e-06 ***
location_nameKiribati                         1.23e-08 ***
location_nameKuwait                            < 2e-16 ***
location_nameKyrgyzstan                        < 2e-16 ***
location_nameLaos                              < 2e-16 ***
location_nameLatvia                            < 2e-16 ***
location_nameLebanon                           < 2e-16 ***
location_nameLesotho                          0.000521 ***
location_nameLiberia                           < 2e-16 ***
location_nameLibya                            9.25e-06 ***
location_nameLithuania                         < 2e-16 ***
location_nameLuxembourg                        < 2e-16 ***
location_nameMacedonia                         < 2e-16 ***
location_nameMadagascar                       0.716457    
location_nameMalawi                            < 2e-16 ***
location_nameMalaysia                         1.65e-08 ***
location_nameMaldives                         4.64e-08 ***
location_nameMali                              < 2e-16 ***
location_nameMalta                             < 2e-16 ***
location_nameMarshall Islands                  < 2e-16 ***
location_nameMauritania                        < 2e-16 ***
location_nameMauritius                        1.98e-05 ***
location_nameMexico                            < 2e-16 ***
location_nameMoldova                           < 2e-16 ***
location_nameMongolia                          < 2e-16 ***
location_nameMontenegro                        < 2e-16 ***
location_nameMorocco                          0.024443 *  
location_nameMozambique                       1.13e-09 ***
location_nameMyanmar                          1.07e-12 ***
location_nameNamibia                           < 2e-16 ***
location_nameNepal                            0.866641    
location_nameNetherlands                       < 2e-16 ***
location_nameNew Zealand                       < 2e-16 ***
location_nameNicaragua                        1.85e-05 ***
location_nameNiger                            7.23e-16 ***
location_nameNigeria                          1.65e-12 ***
location_nameNorth Korea                       < 2e-16 ***
location_nameNorthern Mariana Islands          < 2e-16 ***
location_nameNorway                            < 2e-16 ***
location_nameOman                              < 2e-16 ***
location_namePakistan                         0.492057    
location_namePalestine                        0.996268    
location_namePanama                            < 2e-16 ***
location_namePapua New Guinea                 9.46e-14 ***
location_nameParaguay                          < 2e-16 ***
location_namePeru                             0.256190    
location_namePhilippines                       < 2e-16 ***
location_namePoland                            < 2e-16 ***
location_namePortugal                          < 2e-16 ***
location_namePuerto Rico                       < 2e-16 ***
location_nameQatar                             < 2e-16 ***
location_nameRomania                           < 2e-16 ***
location_nameRussian Federation                < 2e-16 ***
location_nameRwanda                            < 2e-16 ***
location_nameSaint Lucia                       < 2e-16 ***
location_nameSaint Vincent and the Grenadines  < 2e-16 ***
location_nameSamoa                             < 2e-16 ***
location_nameSao Tome and Principe            0.068780 .  
location_nameSaudi Arabia                     0.027843 *  
location_nameSenegal                          0.004266 ** 
location_nameSerbia                            < 2e-16 ***
location_nameSeychelles                        < 2e-16 ***
location_nameSierra Leone                      < 2e-16 ***
location_nameSingapore                         < 2e-16 ***
location_nameSlovakia                          < 2e-16 ***
location_nameSlovenia                          < 2e-16 ***
location_nameSolomon Islands                  0.704770    
location_nameSomalia                           < 2e-16 ***
location_nameSouth Africa                      < 2e-16 ***
location_nameSouth Korea                       < 2e-16 ***
location_nameSouth Sudan                      0.489664    
location_nameSpain                             < 2e-16 ***
location_nameSri Lanka                         < 2e-16 ***
location_nameSudan                            0.051132 .  
location_nameSuriname                         1.53e-15 ***
location_nameSwaziland                         < 2e-16 ***
location_nameSweden                            < 2e-16 ***
location_nameSwitzerland                       < 2e-16 ***
location_nameSyria                            0.917699    
location_nameTaiwan (Province of China)        < 2e-16 ***
location_nameTajikistan                       0.460357    
location_nameTanzania                         8.57e-08 ***
location_nameThailand                         4.66e-14 ***
location_nameThe Bahamas                       < 2e-16 ***
location_nameThe Gambia                        < 2e-16 ***
location_nameTimor-Leste                      0.003093 ** 
location_nameTogo                              < 2e-16 ***
location_nameTonga                             < 2e-16 ***
location_nameTrinidad and Tobago              1.29e-13 ***
location_nameTunisia                          0.763519    
location_nameTurkey                           0.008129 ** 
location_nameTurkmenistan                      < 2e-16 ***
location_nameUganda                           0.537669    
location_nameUkraine                           < 2e-16 ***
location_nameUnited Arab Emirates              < 2e-16 ***
location_nameUnited Kingdom                    < 2e-16 ***
location_nameUnited States                     < 2e-16 ***
location_nameUruguay                           < 2e-16 ***
location_nameUzbekistan                        < 2e-16 ***
location_nameVanuatu                           < 2e-16 ***
location_nameVenezuela                         < 2e-16 ***
location_nameVietnam                           < 2e-16 ***
location_nameVirgin Islands, U.S.              < 2e-16 ***
location_nameYemen                            8.84e-08 ***
location_nameZambia                           8.48e-06 ***
location_nameZimbabwe                         0.052472 .  
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 0.09235 on 5641 degrees of freedom
Multiple R-squared:   0.94, Adjusted R-squared:  0.9377 
F-statistic: 424.5 on 208 and 5641 DF,  p-value: < 2.2e-16

It’s hard to see that these results match, since the linear regression output doesn’t print the \(F\)-statistic for the age groups together or the countries together; it only gives results for individual \(t\)-tests of each regression coefficient. We can get these grouped \(F\)-statistics using the anova() function of the stats package. This function does not actually directly perform ANOVA like the aov() function, but instead prints a variance table using a lm() object.

all_age_diet_and_guidelines %>%
  filter(food == "red meat") %>%
  lm(log10(Relative_Percent) ~ age_group_name + location_name, data = .) %>%
  anova()
Analysis of Variance Table

Response: log10(Relative_Percent)
                 Df Sum Sq Mean Sq F value    Pr(>F)    
age_group_name   14  42.53  3.0380  356.23 < 2.2e-16 ***
location_name   194 710.57  3.6627  429.48 < 2.2e-16 ***
Residuals      5641  48.11  0.0085                      
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

We can see that indeed the \(F\)-values and \(p\)-values from linear regression match those from ANOVA. In this case, this analysis suggests that there is a significant relationship between age group and consumption, even when controlling for country. It also suggests that there is a significant relationship between country and consumption, even when controlling for age group. However, only the first relationship is our relationship of interest; the second is only included in the model to account for the dependent nature of the data.

Remember, the ANOVA results indicate that the means are different across these groups, but it does not inform us about which groups are different. However, the original lm() output using the `summary()~ command gives more information about specific group differences. Remember, though, that these are relative to the reference level for the age group and location and that these values are calculated for the effect on consumption while controlling for the other variable in the model.

all_age_diet_and_guidelines %>%
  filter(food == "red meat") %>%
  lm(log10(Relative_Percent) ~ age_group_name + location_name, data = .) %>%
  summary()

Call:
lm(formula = log10(Relative_Percent) ~ age_group_name + location_name, 
    data = .)

Residuals:
     Min       1Q   Median       3Q      Max 
-0.17321 -0.08990  0.04057  0.08969  0.19936 

Coefficients:
                                                Estimate Std. Error t value
(Intercept)                                    1.6473808  0.0174553  94.377
age_group_name30 to 34                         0.0013252  0.0066133   0.200
age_group_name35 to 39                         0.0017447  0.0066133   0.264
age_group_name40 to 44                        -0.0044467  0.0066133  -0.672
age_group_name45 to 49                        -0.0190883  0.0066133  -2.886
age_group_name50 to 54                        -0.0347670  0.0066133  -5.257
age_group_name55 to 59                        -0.0514702  0.0066133  -7.783
age_group_name60 to 64                        -0.0710801  0.0066133 -10.748
age_group_name65 to 69                        -0.0943452  0.0066133 -14.266
age_group_name70 to 74                        -0.1194232  0.0066133 -18.058
age_group_name75 to 79                        -0.1473520  0.0066133 -22.281
age_group_name80 to 84                        -0.2100070  0.0066133 -31.755
age_group_name85 to 89                        -0.2153687  0.0066133 -32.566
age_group_name90 to 94                        -0.2181720  0.0066133 -32.990
age_group_name95 plus                         -0.2180964  0.0066133 -32.979
location_nameAlbania                           0.4696634  0.0238444  19.697
location_nameAlgeria                           0.0524978  0.0238444   2.202
location_nameAmerican Samoa                    0.4781072  0.0238444  20.051
location_nameAndorra                           0.7709817  0.0238444  32.334
location_nameAngola                            0.2502250  0.0238444  10.494
location_nameAntigua and Barbuda               0.3148782  0.0238444  13.206
location_nameArgentina                         0.8391115  0.0238444  35.191
location_nameArmenia                           0.4143613  0.0238444  17.378
location_nameAustralia                         0.8382715  0.0238444  35.156
location_nameAustria                           0.9006351  0.0238444  37.771
location_nameAzerbaijan                        0.3013522  0.0238444  12.638
location_nameBahrain                           0.2892541  0.0238444  12.131
location_nameBangladesh                       -0.6036909  0.0238444 -25.318
location_nameBarbados                          0.3355809  0.0238444  14.074
location_nameBelarus                           0.7101162  0.0238444  29.781
location_nameBelgium                           0.7081735  0.0238444  29.700
location_nameBelize                            0.1840253  0.0238444   7.718
location_nameBenin                            -0.3251738  0.0238444 -13.637
location_nameBermuda                           0.4539095  0.0238444  19.036
location_nameBhutan                           -0.1193613  0.0238444  -5.006
location_nameBolivia                           0.5011897  0.0238444  21.019
location_nameBosnia and Herzegovina            0.1966458  0.0238444   8.247
location_nameBotswana                          0.1360033  0.0238444   5.704
location_nameBrazil                            0.7420931  0.0238444  31.122
location_nameBrunei                            0.2248900  0.0238444   9.432
location_nameBulgaria                          0.7016126  0.0238444  29.425
location_nameBurkina Faso                      0.0952272  0.0238444   3.994
location_nameBurundi                          -0.1975274  0.0238444  -8.284
location_nameCambodia                          0.1162693  0.0238444   4.876
location_nameCameroon                         -0.0547177  0.0238444  -2.295
location_nameCanada                            0.6965652  0.0238444  29.213
location_nameCape Verde                        0.4033481  0.0238444  16.916
location_nameCentral African Republic          0.3695721  0.0238444  15.499
location_nameChad                              0.0445016  0.0238444   1.866
location_nameChile                             0.6899420  0.0238444  28.935
location_nameChina                             0.6542148  0.0238444  27.437
location_nameColombia                          0.3677083  0.0238444  15.421
location_nameComoros                          -0.1092061  0.0238444  -4.580
location_nameCongo                            -0.2562887  0.0238444 -10.748
location_nameCosta Rica                        0.3922246  0.0238444  16.449
location_nameCote d'Ivoire                    -0.3878599  0.0238444 -16.266
location_nameCroatia                           0.6168738  0.0238444  25.871
location_nameCuba                              0.3715729  0.0238444  15.583
location_nameCyprus                            0.6210060  0.0238444  26.044
location_nameCzech Republic                    0.6754221  0.0238444  28.326
location_nameDemocratic Republic of the Congo -0.1381953  0.0238444  -5.796
location_nameDenmark                           0.6940034  0.0238444  29.105
location_nameDjibouti                          0.2040664  0.0238444   8.558
location_nameDominica                          0.2201500  0.0238444   9.233
location_nameDominican Republic                0.2904822  0.0238444  12.182
location_nameEcuador                           0.5001219  0.0238444  20.974
location_nameEgypt                             0.1407597  0.0238444   5.903
location_nameEl Salvador                       0.0564578  0.0238444   2.368
location_nameEquatorial Guinea                 0.4145078  0.0238444  17.384
location_nameEritrea                          -0.1427694  0.0238444  -5.988
location_nameEstonia                           0.5724487  0.0238444  24.008
location_nameEthiopia                         -0.1749948  0.0238444  -7.339
location_nameFederated States of Micronesia    0.2177319  0.0238444   9.131
location_nameFiji                              0.3063835  0.0238444  12.849
location_nameFinland                           0.6616460  0.0238444  27.748
location_nameFrance                            0.7352843  0.0238444  30.837
location_nameGabon                             0.1926532  0.0238444   8.080
location_nameGeorgia                           0.2530224  0.0238444  10.611
location_nameGermany                           0.7386742  0.0238444  30.979
location_nameGhana                            -0.3464979  0.0238444 -14.532
location_nameGreece                            0.7022224  0.0238444  29.450
location_nameGreenland                         0.7880282  0.0238444  33.049
location_nameGrenada                           0.2394215  0.0238444  10.041
location_nameGuam                              0.5937555  0.0238444  24.901
location_nameGuatemala                         0.0139062  0.0238444   0.583
location_nameGuinea                           -0.1905498  0.0238444  -7.991
location_nameGuinea-Bissau                     0.1322312  0.0238444   5.546
location_nameGuyana                           -0.2063983  0.0238444  -8.656
location_nameHaiti                            -0.0491435  0.0238444  -2.061
location_nameHonduras                          0.1244476  0.0238444   5.219
location_nameHungary                           0.6410019  0.0238444  26.883
location_nameIceland                           0.6839722  0.0238444  28.685
location_nameIndia                            -0.6388459  0.0238444 -26.792
location_nameIndonesia                        -0.2110810  0.0238444  -8.852
location_nameIran                             -0.0399115  0.0238444  -1.674
location_nameIraq                             -0.3147627  0.0238444 -13.201
location_nameIreland                           0.8205275  0.0238444  34.412
location_nameIsrael                            0.5031271  0.0238444  21.100
location_nameItaly                             0.7012419  0.0238444  29.409
location_nameJamaica                          -0.0196064  0.0238444  -0.822
location_nameJapan                             0.4163050  0.0238444  17.459
location_nameJordan                            0.1122555  0.0238444   4.708
location_nameKazakhstan                        0.6667740  0.0238444  27.964
location_nameKenya                             0.1062621  0.0238444   4.456
location_nameKiribati                          0.1360152  0.0238444   5.704
location_nameKuwait                            0.3628791  0.0238444  15.219
location_nameKyrgyzstan                        0.4299491  0.0238444  18.031
location_nameLaos                              0.2281184  0.0238444   9.567
location_nameLatvia                            0.6051096  0.0238444  25.377
location_nameLebanon                           0.3358240  0.0238444  14.084
location_nameLesotho                           0.0827870  0.0238444   3.472
location_nameLiberia                          -0.3810751  0.0238444 -15.982
location_nameLibya                             0.1058210  0.0238444   4.438
location_nameLithuania                         0.6853303  0.0238444  28.742
location_nameLuxembourg                        0.8252128  0.0238444  34.608
location_nameMacedonia                         0.3375009  0.0238444  14.154
location_nameMadagascar                        0.0086607  0.0238444   0.363
location_nameMalawi                           -0.2390062  0.0238444 -10.024
location_nameMalaysia                          0.1348008  0.0238444   5.653
location_nameMaldives                         -0.1304736  0.0238444  -5.472
location_nameMali                              0.2545834  0.0238444  10.677
location_nameMalta                             0.6861462  0.0238444  28.776
location_nameMarshall Islands                  0.2453513  0.0238444  10.290
location_nameMauritania                        0.2586036  0.0238444  10.845
location_nameMauritius                         0.1018209  0.0238444   4.270
location_nameMexico                            0.5019364  0.0238444  21.050
location_nameMoldova                           0.2694136  0.0238444  11.299
location_nameMongolia                          0.8050147  0.0238444  33.761
location_nameMontenegro                        0.7197936  0.0238444  30.187
location_nameMorocco                           0.0536665  0.0238444   2.251
location_nameMozambique                       -0.1454548  0.0238444  -6.100
location_nameMyanmar                           0.1701756  0.0238444   7.137
location_nameNamibia                           0.2766865  0.0238444  11.604
location_nameNepal                             0.0040043  0.0238444   0.168
location_nameNetherlands                       0.7182009  0.0238444  30.120
location_nameNew Zealand                       0.8215769  0.0238444  34.456
location_nameNicaragua                        -0.1022029  0.0238444  -4.286
location_nameNiger                             0.1929072  0.0238444   8.090
location_nameNigeria                          -0.1687456  0.0238444  -7.077
location_nameNorth Korea                      -0.2308157  0.0238444  -9.680
location_nameNorthern Mariana Islands          0.4519371  0.0238444  18.954
location_nameNorway                            0.6802116  0.0238444  28.527
location_nameOman                              0.3715916  0.0238444  15.584
location_namePakistan                          0.0163832  0.0238444   0.687
location_namePalestine                        -0.0001115  0.0238444  -0.005
location_namePanama                            0.4910430  0.0238444  20.594
location_namePapua New Guinea                  0.1780454  0.0238444   7.467
location_nameParaguay                          0.6105228  0.0238444  25.604
location_namePeru                             -0.0270767  0.0238444  -1.136
location_namePhilippines                       0.3489508  0.0238444  14.634
location_namePoland                            0.7119822  0.0238444  29.859
location_namePortugal                          0.7651172  0.0238444  32.088
location_namePuerto Rico                       0.4442532  0.0238444  18.631
location_nameQatar                             0.4226739  0.0238444  17.726
location_nameRomania                           0.6732206  0.0238444  28.234
location_nameRussian Federation                0.5601442  0.0238444  23.492
location_nameRwanda                           -0.2771514  0.0238444 -11.623
location_nameSaint Lucia                       0.2295368  0.0238444   9.626
location_nameSaint Vincent and the Grenadines  0.2179043  0.0238444   9.139
location_nameSamoa                             0.2797823  0.0238444  11.734
location_nameSao Tome and Principe            -0.0434018  0.0238444  -1.820
location_nameSaudi Arabia                      0.0524593  0.0238444   2.200
location_nameSenegal                          -0.0681700  0.0238444  -2.859
location_nameSerbia                            0.5359842  0.0238444  22.478
location_nameSeychelles                        0.3146719  0.0238444  13.197
location_nameSierra Leone                     -0.5214405  0.0238444 -21.868
location_nameSingapore                         0.5946425  0.0238444  24.938
location_nameSlovakia                          0.6438632  0.0238444  27.003
location_nameSlovenia                          0.7116904  0.0238444  29.847
location_nameSolomon Islands                  -0.0090348  0.0238444  -0.379
location_nameSomalia                          -0.2564213  0.0238444 -10.754
location_nameSouth Africa                      0.4267033  0.0238444  17.895
location_nameSouth Korea                       0.5949378  0.0238444  24.951
location_nameSouth Sudan                       0.0164738  0.0238444   0.691
location_nameSpain                             0.7138244  0.0238444  29.937
location_nameSri Lanka                        -0.7330116  0.0238444 -30.741
location_nameSudan                            -0.0465153  0.0238444  -1.951
location_nameSuriname                          0.1906880  0.0238444   7.997
location_nameSwaziland                         0.3040096  0.0238444  12.750
location_nameSweden                            0.7247779  0.0238444  30.396
location_nameSwitzerland                       0.7281804  0.0238444  30.539
location_nameSyria                            -0.0024640  0.0238444  -0.103
location_nameTaiwan (Province of China)        0.6900827  0.0238444  28.941
location_nameTajikistan                        0.0176046  0.0238444   0.738
location_nameTanzania                         -0.1278459  0.0238444  -5.362
location_nameThailand                          0.1802762  0.0238444   7.561
location_nameThe Bahamas                       0.5890517  0.0238444  24.704
location_nameThe Gambia                       -0.3863879  0.0238444 -16.205
location_nameTimor-Leste                       0.0705710  0.0238444   2.960
location_nameTogo                             -0.3083612  0.0238444 -12.932
location_nameTonga                             0.3002675  0.0238444  12.593
location_nameTrinidad and Tobago               0.1770619  0.0238444   7.426
location_nameTunisia                           0.0071742  0.0238444   0.301
location_nameTurkey                            0.0631309  0.0238444   2.648
location_nameTurkmenistan                      0.7290156  0.0238444  30.574
location_nameUganda                            0.0146972  0.0238444   0.616
location_nameUkraine                           0.3932282  0.0238444  16.491
location_nameUnited Arab Emirates              0.2716685  0.0238444  11.393
location_nameUnited Kingdom                    0.5932920  0.0238444  24.882
location_nameUnited States                     0.7390080  0.0238444  30.993
location_nameUruguay                           0.6597541  0.0238444  27.669
location_nameUzbekistan                        0.4620568  0.0238444  19.378
location_nameVanuatu                           0.1977037  0.0238444   8.291
location_nameVenezuela                         0.4446078  0.0238444  18.646
location_nameVietnam                           0.5087180  0.0238444  21.335
location_nameVirgin Islands, U.S.              0.4675163  0.0238444  19.607
location_nameYemen                            -0.1277139  0.0238444  -5.356
location_nameZambia                           -0.1062726  0.0238444  -4.457
location_nameZimbabwe                          0.0462498  0.0238444   1.940
                                              Pr(>|t|)    
(Intercept)                                    < 2e-16 ***
age_group_name30 to 34                        0.841191    
age_group_name35 to 39                        0.791927    
age_group_name40 to 44                        0.501365    
age_group_name45 to 49                        0.003912 ** 
age_group_name50 to 54                        1.52e-07 ***
age_group_name55 to 59                        8.37e-15 ***
age_group_name60 to 64                         < 2e-16 ***
age_group_name65 to 69                         < 2e-16 ***
age_group_name70 to 74                         < 2e-16 ***
age_group_name75 to 79                         < 2e-16 ***
age_group_name80 to 84                         < 2e-16 ***
age_group_name85 to 89                         < 2e-16 ***
age_group_name90 to 94                         < 2e-16 ***
age_group_name95 plus                          < 2e-16 ***
location_nameAlbania                           < 2e-16 ***
location_nameAlgeria                          0.027728 *  
location_nameAmerican Samoa                    < 2e-16 ***
location_nameAndorra                           < 2e-16 ***
location_nameAngola                            < 2e-16 ***
location_nameAntigua and Barbuda               < 2e-16 ***
location_nameArgentina                         < 2e-16 ***
location_nameArmenia                           < 2e-16 ***
location_nameAustralia                         < 2e-16 ***
location_nameAustria                           < 2e-16 ***
location_nameAzerbaijan                        < 2e-16 ***
location_nameBahrain                           < 2e-16 ***
location_nameBangladesh                        < 2e-16 ***
location_nameBarbados                          < 2e-16 ***
location_nameBelarus                           < 2e-16 ***
location_nameBelgium                           < 2e-16 ***
location_nameBelize                           1.39e-14 ***
location_nameBenin                             < 2e-16 ***
location_nameBermuda                           < 2e-16 ***
location_nameBhutan                           5.73e-07 ***
location_nameBolivia                           < 2e-16 ***
location_nameBosnia and Herzegovina            < 2e-16 ***
location_nameBotswana                         1.23e-08 ***
location_nameBrazil                            < 2e-16 ***
location_nameBrunei                            < 2e-16 ***
location_nameBulgaria                          < 2e-16 ***
location_nameBurkina Faso                     6.59e-05 ***
location_nameBurundi                           < 2e-16 ***
location_nameCambodia                         1.11e-06 ***
location_nameCameroon                         0.021782 *  
location_nameCanada                            < 2e-16 ***
location_nameCape Verde                        < 2e-16 ***
location_nameCentral African Republic          < 2e-16 ***
location_nameChad                             0.062047 .  
location_nameChile                             < 2e-16 ***
location_nameChina                             < 2e-16 ***
location_nameColombia                          < 2e-16 ***
location_nameComoros                          4.75e-06 ***
location_nameCongo                             < 2e-16 ***
location_nameCosta Rica                        < 2e-16 ***
location_nameCote d'Ivoire                     < 2e-16 ***
location_nameCroatia                           < 2e-16 ***
location_nameCuba                              < 2e-16 ***
location_nameCyprus                            < 2e-16 ***
location_nameCzech Republic                    < 2e-16 ***
location_nameDemocratic Republic of the Congo 7.17e-09 ***
location_nameDenmark                           < 2e-16 ***
location_nameDjibouti                          < 2e-16 ***
location_nameDominica                          < 2e-16 ***
location_nameDominican Republic                < 2e-16 ***
location_nameEcuador                           < 2e-16 ***
location_nameEgypt                            3.77e-09 ***
location_nameEl Salvador                      0.017930 *  
location_nameEquatorial Guinea                 < 2e-16 ***
location_nameEritrea                          2.26e-09 ***
location_nameEstonia                           < 2e-16 ***
location_nameEthiopia                         2.46e-13 ***
location_nameFederated States of Micronesia    < 2e-16 ***
location_nameFiji                              < 2e-16 ***
location_nameFinland                           < 2e-16 ***
location_nameFrance                            < 2e-16 ***
location_nameGabon                            7.88e-16 ***
location_nameGeorgia                           < 2e-16 ***
location_nameGermany                           < 2e-16 ***
location_nameGhana                             < 2e-16 ***
location_nameGreece                            < 2e-16 ***
location_nameGreenland                         < 2e-16 ***
location_nameGrenada                           < 2e-16 ***
location_nameGuam                              < 2e-16 ***
location_nameGuatemala                        0.559777    
location_nameGuinea                           1.61e-15 ***
location_nameGuinea-Bissau                    3.06e-08 ***
location_nameGuyana                            < 2e-16 ***
location_nameHaiti                            0.039348 *  
location_nameHonduras                         1.86e-07 ***
location_nameHungary                           < 2e-16 ***
location_nameIceland                           < 2e-16 ***
location_nameIndia                             < 2e-16 ***
location_nameIndonesia                         < 2e-16 ***
location_nameIran                             0.094219 .  
location_nameIraq                              < 2e-16 ***
location_nameIreland                           < 2e-16 ***
location_nameIsrael                            < 2e-16 ***
location_nameItaly                             < 2e-16 ***
location_nameJamaica                          0.410962    
location_nameJapan                             < 2e-16 ***
location_nameJordan                           2.56e-06 ***
location_nameKazakhstan                        < 2e-16 ***
location_nameKenya                            8.49e-06 ***
location_nameKiribati                         1.23e-08 ***
location_nameKuwait                            < 2e-16 ***
location_nameKyrgyzstan                        < 2e-16 ***
location_nameLaos                              < 2e-16 ***
location_nameLatvia                            < 2e-16 ***
location_nameLebanon                           < 2e-16 ***
location_nameLesotho                          0.000521 ***
location_nameLiberia                           < 2e-16 ***
location_nameLibya                            9.25e-06 ***
location_nameLithuania                         < 2e-16 ***
location_nameLuxembourg                        < 2e-16 ***
location_nameMacedonia                         < 2e-16 ***
location_nameMadagascar                       0.716457    
location_nameMalawi                            < 2e-16 ***
location_nameMalaysia                         1.65e-08 ***
location_nameMaldives                         4.64e-08 ***
location_nameMali                              < 2e-16 ***
location_nameMalta                             < 2e-16 ***
location_nameMarshall Islands                  < 2e-16 ***
location_nameMauritania                        < 2e-16 ***
location_nameMauritius                        1.98e-05 ***
location_nameMexico                            < 2e-16 ***
location_nameMoldova                           < 2e-16 ***
location_nameMongolia                          < 2e-16 ***
location_nameMontenegro                        < 2e-16 ***
location_nameMorocco                          0.024443 *  
location_nameMozambique                       1.13e-09 ***
location_nameMyanmar                          1.07e-12 ***
location_nameNamibia                           < 2e-16 ***
location_nameNepal                            0.866641    
location_nameNetherlands                       < 2e-16 ***
location_nameNew Zealand                       < 2e-16 ***
location_nameNicaragua                        1.85e-05 ***
location_nameNiger                            7.23e-16 ***
location_nameNigeria                          1.65e-12 ***
location_nameNorth Korea                       < 2e-16 ***
location_nameNorthern Mariana Islands          < 2e-16 ***
location_nameNorway                            < 2e-16 ***
location_nameOman                              < 2e-16 ***
location_namePakistan                         0.492057    
location_namePalestine                        0.996268    
location_namePanama                            < 2e-16 ***
location_namePapua New Guinea                 9.46e-14 ***
location_nameParaguay                          < 2e-16 ***
location_namePeru                             0.256190    
location_namePhilippines                       < 2e-16 ***
location_namePoland                            < 2e-16 ***
location_namePortugal                          < 2e-16 ***
location_namePuerto Rico                       < 2e-16 ***
location_nameQatar                             < 2e-16 ***
location_nameRomania                           < 2e-16 ***
location_nameRussian Federation                < 2e-16 ***
location_nameRwanda                            < 2e-16 ***
location_nameSaint Lucia                       < 2e-16 ***
location_nameSaint Vincent and the Grenadines  < 2e-16 ***
location_nameSamoa                             < 2e-16 ***
location_nameSao Tome and Principe            0.068780 .  
location_nameSaudi Arabia                     0.027843 *  
location_nameSenegal                          0.004266 ** 
location_nameSerbia                            < 2e-16 ***
location_nameSeychelles                        < 2e-16 ***
location_nameSierra Leone                      < 2e-16 ***
location_nameSingapore                         < 2e-16 ***
location_nameSlovakia                          < 2e-16 ***
location_nameSlovenia                          < 2e-16 ***
location_nameSolomon Islands                  0.704770    
location_nameSomalia                           < 2e-16 ***
location_nameSouth Africa                      < 2e-16 ***
location_nameSouth Korea                       < 2e-16 ***
location_nameSouth Sudan                      0.489664    
location_nameSpain                             < 2e-16 ***
location_nameSri Lanka                         < 2e-16 ***
location_nameSudan                            0.051132 .  
location_nameSuriname                         1.53e-15 ***
location_nameSwaziland                         < 2e-16 ***
location_nameSweden                            < 2e-16 ***
location_nameSwitzerland                       < 2e-16 ***
location_nameSyria                            0.917699    
location_nameTaiwan (Province of China)        < 2e-16 ***
location_nameTajikistan                       0.460357    
location_nameTanzania                         8.57e-08 ***
location_nameThailand                         4.66e-14 ***
location_nameThe Bahamas                       < 2e-16 ***
location_nameThe Gambia                        < 2e-16 ***
location_nameTimor-Leste                      0.003093 ** 
location_nameTogo                              < 2e-16 ***
location_nameTonga                             < 2e-16 ***
location_nameTrinidad and Tobago              1.29e-13 ***
location_nameTunisia                          0.763519    
location_nameTurkey                           0.008129 ** 
location_nameTurkmenistan                      < 2e-16 ***
location_nameUganda                           0.537669    
location_nameUkraine                           < 2e-16 ***
location_nameUnited Arab Emirates              < 2e-16 ***
location_nameUnited Kingdom                    < 2e-16 ***
location_nameUnited States                     < 2e-16 ***
location_nameUruguay                           < 2e-16 ***
location_nameUzbekistan                        < 2e-16 ***
location_nameVanuatu                           < 2e-16 ***
location_nameVenezuela                         < 2e-16 ***
location_nameVietnam                           < 2e-16 ***
location_nameVirgin Islands, U.S.              < 2e-16 ***
location_nameYemen                            8.84e-08 ***
location_nameZambia                           8.48e-06 ***
location_nameZimbabwe                         0.052472 .  
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 0.09235 on 5641 degrees of freedom
Multiple R-squared:   0.94, Adjusted R-squared:  0.9377 
F-statistic: 424.5 on 208 and 5641 DF,  p-value: < 2.2e-16

ANOVA and linear regression with mixed effects

We could instead perform a similar analysis as we did for the two group analysis where we controlled for the paired data structure using a random effect based on country In particular, we could include a random intercept for country. We could do this within the aov() function using Error() and within the lmer() function with 1 | variable_name.

all_age_diet_and_guidelines %>%
  filter(food == "red meat") %>%
  aov(
    log10(Relative_Percent) ~ age_group_name + Error(location_name),
    data = .
  ) %>%
  summary()

Error: location_name
           Df Sum Sq Mean Sq F value Pr(>F)
Residuals 194  710.6   3.663               

Error: Within
                 Df Sum Sq Mean Sq F value Pr(>F)    
age_group_name   14  42.53  3.0380   356.2 <2e-16 ***
Residuals      5641  48.11  0.0085                   
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
all_age_diet_and_guidelines %>%
  filter(food == "red meat") %>%
  lmer(
    log10(Relative_Percent) ~ age_group_name + (1 | location_name),
    data = .
  ) %>%
  summary()
Linear mixed model fit by REML. t-tests use Satterthwaite's method [
lmerModLmerTest]
Formula: log10(Relative_Percent) ~ age_group_name + (1 | location_name)
   Data: .

REML criterion at convergence: -9975.4

Scaled residuals: 
    Min      1Q  Median      3Q     Max 
-1.8675 -0.9750  0.4451  0.9700  2.1669 

Random effects:
 Groups        Name        Variance Std.Dev.
 location_name (Intercept) 0.121806 0.34901 
 Residual                  0.008528 0.09235 
Number of obs: 5850, groups:  location_name, 195

Fixed effects:
                         Estimate Std. Error         df t value Pr(>|t|)    
(Intercept)             1.921e+00  2.543e-02  2.068e+02  75.558  < 2e-16 ***
age_group_name30 to 34  1.325e-03  6.613e-03  5.641e+03   0.200  0.84119    
age_group_name35 to 39  1.745e-03  6.613e-03  5.641e+03   0.264  0.79193    
age_group_name40 to 44 -4.447e-03  6.613e-03  5.641e+03  -0.672  0.50136    
age_group_name45 to 49 -1.909e-02  6.613e-03  5.641e+03  -2.886  0.00391 ** 
age_group_name50 to 54 -3.477e-02  6.613e-03  5.641e+03  -5.257 1.52e-07 ***
age_group_name55 to 59 -5.147e-02  6.613e-03  5.641e+03  -7.783 8.37e-15 ***
age_group_name60 to 64 -7.108e-02  6.613e-03  5.641e+03 -10.748  < 2e-16 ***
age_group_name65 to 69 -9.434e-02  6.613e-03  5.641e+03 -14.266  < 2e-16 ***
age_group_name70 to 74 -1.194e-01  6.613e-03  5.641e+03 -18.058  < 2e-16 ***
age_group_name75 to 79 -1.474e-01  6.613e-03  5.641e+03 -22.281  < 2e-16 ***
age_group_name80 to 84 -2.100e-01  6.613e-03  5.641e+03 -31.755  < 2e-16 ***
age_group_name85 to 89 -2.154e-01  6.613e-03  5.641e+03 -32.566  < 2e-16 ***
age_group_name90 to 94 -2.182e-01  6.613e-03  5.641e+03 -32.990  < 2e-16 ***
age_group_name95 plus  -2.181e-01  6.613e-03  5.641e+03 -32.979  < 2e-16 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Notice now the results only show for the age group variable, since this is the only fixed effect in the model. However, dependence in the data due to country is still accounted for through the random effect.

If we use anova() instead of summary() for our lmer() model, we can see they give the same results.

all_age_diet_and_guidelines %>%
  filter(food == "red meat") %>%
  aov(
    log10(Relative_Percent) ~ age_group_name + Error(location_name),
    data = .
  ) %>%
  summary()

Error: location_name
           Df Sum Sq Mean Sq F value Pr(>F)
Residuals 194  710.6   3.663               

Error: Within
                 Df Sum Sq Mean Sq F value Pr(>F)    
age_group_name   14  42.53  3.0380   356.2 <2e-16 ***
Residuals      5641  48.11  0.0085                   
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
all_age_diet_and_guidelines %>%
  filter(food == "red meat") %>%
  lmer(
    log10(Relative_Percent) ~ age_group_name + (1 | location_name),
    data = .
  ) %>%
  anova()
Type III Analysis of Variance Table with Satterthwaite's method
               Sum Sq Mean Sq NumDF DenDF F value    Pr(>F)    
age_group_name 42.532   3.038    14  5641  356.23 < 2.2e-16 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Modeling all groups of interest


Now we can extend out model to include include sex, age_group_name and location_name all in the same linear model and get information about how each of these factors influences dietary consumption, while accounting for the other factors. Since we are primarily interested in the effects of sex and age, but want to account for the dependence in the data due to repeated measurements by country, we will include sex and age_group_name as fixed effects and incorporate a random intercept for location_name.

all_age_diet_and_guidelines %>%
  filter(food == "red meat") %>%
  lmer(
    log10(Relative_Percent) ~ sex + age_group_name + (1 | location_name),
    data = .
  ) %>%
  anova()
Type III Analysis of Variance Table with Satterthwaite's method
               Sum Sq Mean Sq NumDF DenDF F value    Pr(>F)    
sex            47.603  47.603     1  5640  530948 < 2.2e-16 ***
age_group_name 42.532   3.038    14  5640   33885 < 2.2e-16 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
all_age_diet_and_guidelines %>%
  filter(food == "red meat") %>%
  lmer(
    log10(Relative_Percent) ~ sex + age_group_name + (1 | location_name),
    data = .
  ) %>%
  summary()
Linear mixed model fit by REML. t-tests use Satterthwaite's method [
lmerModLmerTest]
Formula: log10(Relative_Percent) ~ sex + age_group_name + (1 | location_name)
   Data: .

REML criterion at convergence: -35657.3

Scaled residuals: 
    Min      1Q  Median      3Q     Max 
-8.7650 -0.4242 -0.0168  0.4071 17.2844 

Random effects:
 Groups        Name        Variance  Std.Dev.
 location_name (Intercept) 1.221e-01 0.349411
 Residual                  8.966e-05 0.009469
Number of obs: 5850, groups:  location_name, 195

Fixed effects:
                         Estimate Std. Error         df  t value Pr(>|t|)    
(Intercept)             1.831e+00  2.503e-02  1.941e+02   73.161  < 2e-16 ***
sexMale                 1.804e-01  2.476e-04  5.640e+03  728.662  < 2e-16 ***
age_group_name30 to 34  1.325e-03  6.781e-04  5.640e+03    1.954   0.0507 .  
age_group_name35 to 39  1.745e-03  6.781e-04  5.640e+03    2.573   0.0101 *  
age_group_name40 to 44 -4.447e-03  6.781e-04  5.640e+03   -6.558 5.94e-11 ***
age_group_name45 to 49 -1.909e-02  6.781e-04  5.640e+03  -28.151  < 2e-16 ***
age_group_name50 to 54 -3.477e-02  6.781e-04  5.640e+03  -51.274  < 2e-16 ***
age_group_name55 to 59 -5.147e-02  6.781e-04  5.640e+03  -75.907  < 2e-16 ***
age_group_name60 to 64 -7.108e-02  6.781e-04  5.640e+03 -104.828  < 2e-16 ***
age_group_name65 to 69 -9.435e-02  6.781e-04  5.640e+03 -139.139  < 2e-16 ***
age_group_name70 to 74 -1.194e-01  6.781e-04  5.640e+03 -176.123  < 2e-16 ***
age_group_name75 to 79 -1.474e-01  6.781e-04  5.640e+03 -217.312  < 2e-16 ***
age_group_name80 to 84 -2.100e-01  6.781e-04  5.640e+03 -309.714  < 2e-16 ***
age_group_name85 to 89 -2.154e-01  6.781e-04  5.640e+03 -317.622  < 2e-16 ***
age_group_name90 to 94 -2.182e-01  6.781e-04  5.640e+03 -321.756  < 2e-16 ***
age_group_name95 plus  -2.181e-01  6.781e-04  5.640e+03 -321.644  < 2e-16 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Looking at the anova() output, we can see that sex and age group both have significant associations with the consumption of red meat, when controlling for the other variable. Additionally, by looking at the individual coefficient estimates in the summary() output, we see that males tend to have higher red meat consumption compared to females (positive coefficient for sexMale) and that consumption seems to decrease with increasing age (negative coefficients for all the age group categories that appear to become larger in magnitude with increasing age).

Data Visualization


If you have been following along but stopped you could load the wrangled data like so:

load(here::here("data", "wrangled", "wrangled_data.rda"))

If you are starting the case study at this section click here.

First you need to install and load the OCSdata package:

install.packages("OCSdata")
library(OCSdata)

Then, you may load the wrangled data using the following code:

wrangled_rda("ocs-bp-diet", outpath = getwd())
load(here::here("OCSdata", "data", "wrangled", "wrangled_data.rda"))

If the package does not work for you, alternatively, an RDA file (stands for R data) of the data can be found here or slightly more directly here. Download this file and then place it in your current working directory within a subdirectory called “wrangled” within a subdirectory called “data” to copy and paste our code. We used an RStudio project and the here package to navigate to the file more easily.

load(here::here("data", "wrangled", "wrangled_data.rda"))

Click here to see more about creating new projects in RStudio.

You can create a project by going to the File menu of RStudio like so:

You can also do so by clicking the project button:

See here to learn more about using RStudio projects and here to learn more about the here package.



Now that we have statistically analyzed the consumption of red meat based on the location, sex, and age group of different populations around the world. Let’s make some visualizations to help with our interpretations.

Red Meat


Let’s try to make a plot that shows the relationship of age group, sex, and location on consumption of red meat.

First we will filter our data for only the data associated with red meat, and then we will create a box plot graph with age group as the x axis, but include box plots for each sex for each age group. We can include an additional subplot to just look at the relationship of sex and consumption. Recall that the ggplot2 package is very useful for making figures and uses a layering structure to make plots using the + between layers.

all_age_diet_and_guidelines %>%
  filter(food == "red meat") %>%
  ggplot(aes(
    x = age_group_name,
    y = Relative_Percent,
    col = sex
  )) +
  geom_boxplot() +
  # this adds the individual points for the sex comparison
  geom_jitter(aes(
    x = sex,
    y = Relative_Percent
  ),
  # width specifies how wide the points will be plotted
  width = .2,
  size = 2,
  shape = 21
  ) +
  # this angles the x axis text and removes the legend
  theme(
    axis.text.x = element_text(
      angle = 70,
      hjust = 1
    ),
    legend.position = "none"
  )

OK, this is pretty good, but we can do better.

Let’s try specifically looking at the countries that over-consumed red meat. We can look at these countries by filtering our data where Relative_Percent was greater than 100%. Now we will overlap the jitter points and the box plot using the position_jitterdoge() as the position in geom_pont(). In order to not obscure our box plots, we can use the argument alpha to make our jitter points more transparent.

all_age_diet_and_guidelines %>%
  filter(food == "red meat") %>%
  filter(Relative_Percent > 100) %>%
  ggplot(aes(
    y = Relative_Percent,
    x = age_group_name,
    fill = sex
  )) +
  # this position option will separate the points by sex
  # this is determined by the fill argument in the ggplot() function
  # could also use col argument but it would change the style a bit
  geom_point(
    position = position_jitterdodge(),
    aes(col = sex),
    alpha = 3 / 10
  ) +
  geom_boxplot(outlier.shape = NA) +
  theme(axis.text.x = element_text(
    angle = 70,
    hjust = 1
  ))

What are the countries that have such high consumption rates?

all_age_diet_and_guidelines %>%
  filter(food == "red meat") %>%
  filter(Relative_Percent > 1000)
# A tibble: 0 x 20
# ... with 20 variables: year_id <dbl>, location_name <chr>, food <chr>,
#   age_group_id <dbl>, age_group_name <chr>, sex <chr>, parameter <chr>,
#   mean <dbl>, upper <dbl>, lower <dbl>, unit <chr>, direction <chr>,
#   lower_optimal <dbl>, optimal <dbl>, upper_optimal <dbl>,
#   unit_optimal <chr>, Relative_Percent <dbl>, range_percent <dbl>,
#   percent_over_under <dbl>, opt_achieved <chr>

Looks like the males in Laos and Timor_Leste have the highest consumption.

Now let’s plot just the populations that eat less than the optimal amount by filtering for Relative_Percent < 100%.

all_age_diet_and_guidelines %>%
  filter(food == "red meat") %>%
  filter(Relative_Percent < 100) %>%
  ggplot(aes(
    y = Relative_Percent,
    x = age_group_name,
    fill = sex
  )) +
  geom_point(
    position = position_jitterdodge(),
    aes(col = sex),
    alpha = 3 / 10
  ) +
  geom_boxplot(outlier.shape = NA) +
  theme(axis.text.x = element_text(
    angle = 70,
    hjust = 1
  ))

Nice! It would be nice to be able to know what countries each data point corresponds to. One way to do this is using a package called ggiraph. This package is really helpful for creating interactive graphs.

We will use the geom_point_interactive() function to allow us to hover over points to display the country name. We indicate what label we want with the tooltip argument. This function is similar to the normal geom_point() function. Thus, we will include the same arguments as before. However, we will also split the male and female data using facet_wrap() to make things a bit less overwhelming.

Notice that we are creating a plot object before we use the geom_point_interactive().

We are also rendering the plot with the girafe() function of the ggiraph package.

g <- all_age_diet_and_guidelines %>%
  filter(food == "red meat") %>%
  filter(Relative_Percent < 100) %>%
  ggplot(aes(
    y = Relative_Percent,
    x = age_group_name,
    fill = sex
  )) +
  geom_boxplot(outlier.shape = NA) +
  facet_wrap(~sex) +
  theme(axis.text.x = element_text(
    angle = 70,
    hjust = 1
  ))

g <- g + geom_point_interactive(aes(
  color = sex,
  tooltip = location_name
),
size = 2,
position = position_jitterdodge(),
alpha = 3 / 10
)

girafe(code = print(g))

Cool!

From this plot we can see the countries with populations that do well by not over-consuming red meat, (as over-consumption is associated with health risk). We see that different countries greatly vary, we can see that overall younger populations appear to consume more red meat, and men appear to consume red meat.

Let’s do the same thing for the over-consuming countries. We can also take this one step further to show all the points for the same country when we hover over one data point by using the data_id argument of the geom_point_interactive() function.

We can also add links to Wikipedia pages for these countries using the onclick argument. See this link for more information on using the ggirpah package. We will use the base sprintf() function to format our urls for the Wikipedia links into C style to open a new tab for the link when a user clicks on the figure.

all_age_diet_and_guidelines %<>%
  mutate(link = sprintf(
    "window.open(\"%s%s\")",
    "http://en.wikipedia.org/wiki/",
    as.character(pull(
      all_age_diet_and_guidelines,
      location_name
    ))
  ))

g <- all_age_diet_and_guidelines %>%
  filter(food == "red meat") %>%
  filter(Relative_Percent > 100) %>%
  ggplot(aes(
    y = Relative_Percent,
    x = age_group_name,
    fill = sex
  )) +
  geom_boxplot(outlier.shape = NA) +
  facet_wrap(~sex) +
  theme(
    legend.position = "none",
    axis.text.x = element_text(
      angle = 70,
      hjust = 1
    )
  ) +
  expand_limits(y = 99)


g <- g + geom_point_interactive(aes(
  color = sex,
  tooltip = location_name,
  data_id = location_name,
  onclick = link
),
size = 2,
position = position_jitterdodge(),
alpha = 3 / 10
)

g <- g + geom_point_interactive(
  data =
    all_age_diet_and_guidelines %>%
      filter(food == "red meat") %>%
      filter(Relative_Percent > 100) %>%
      filter(location_name == "United States"),
  aes(
    fill = location_name,
    tooltip = location_name,
    data_id = location_name,
    onclick = link
  ),
  size = 4,
  position = position_jitterdodge(),
  alpha = 5 / 10,
  color = "black"
)

girafe(code = print(g))

United Sates Data


Now let’s take a look at the US data specifically.

diet_and_guidelines %>%
  filter(location_name == "United States") %>%
  count(opt_achieved)
# A tibble: 2 x 2
  opt_achieved     n
  <chr>        <int>
1 No              27
2 Yes              3

OK, it looks like optimal consumption levels were achieved for only 10% of the dietary factors.

Let’s look at males and females separately:

Question Opportunity

Can you come up with the code for how you would do this?


Click here to reveal the code.
diet_and_guidelines %>%
  filter(
    sex == "Male",
    location_name == "United States"
  ) %>%
  count(opt_achieved, food) %>%
  arrange(food)

diet_and_guidelines %>%
  filter(
    sex == "Female",
    location_name == "United States"
  ) %>%
  count(opt_achieved, food) %>%
  arrange(food)

For males:

# A tibble: 15 x 3
   opt_achieved food                            n
   <chr>        <chr>                       <int>
 1 No           calcium                         1
 2 No           fiber                           1
 3 No           fruits                          1
 4 No           legumes                         1
 5 No           milk                            1
 6 No           nuts and seeds                  1
 7 No           polyunsaturated fatty acids     1
 8 No           processed meat                  1
 9 No           red meat                        1
10 No           seafood omega-3 fatty acids     1
11 No           sodium                          1
12 No           sugar-sweetened beverages       1
13 Yes          trans fatty acids               1
14 No           vegetables                      1
15 No           whole grains                    1

For females:

diet_and_guidelines %>%
  filter(
    sex == "Female",
    location_name == "United States"
  ) %>%
  count(opt_achieved, food) %>%
  arrange(food)
# A tibble: 15 x 3
   opt_achieved food                            n
   <chr>        <chr>                       <int>
 1 No           calcium                         1
 2 No           fiber                           1
 3 No           fruits                          1
 4 No           legumes                         1
 5 No           milk                            1
 6 No           nuts and seeds                  1
 7 No           polyunsaturated fatty acids     1
 8 No           processed meat                  1
 9 No           red meat                        1
10 No           seafood omega-3 fatty acids     1
11 Yes          sodium                          1
12 No           sugar-sweetened beverages       1
13 Yes          trans fatty acids               1
14 No           vegetables                      1
15 No           whole grains                    1

So females are a bit better about not over-consuming sodium in the United States relative to males. Both groups are doing well with avoiding trans fatty acids. Let’s look more closely at which dietary components have high and low consumption in the United States:

all_age_diet_and_guidelines %>%
  filter(location_name == "United States") %>%
  ggplot(aes(
    y = Relative_Percent,
    x = food,
    fill = sex
  )) +
  theme(axis.text.x = element_text(
    angle = 70,
    hjust = 1
  )) +
  facet_wrap(~direction, scales = "free") +
  geom_boxplot() +
  geom_point(
    position = position_jitterdodge(),
    alpha = 3 / 10
  )

OK, so we can indeed see that overall consumption of sodium and trans fatty acids is pretty close to optimal. So that’s great. However, Both males and females are over-consuming processed meat, red meat, and sugar-sweetened beverages. On the other hand both genders are not getting adequate intake of all the other dietary factors for optimal health. The population in the United states has especially poor intake of polyunsaturated fats. it also looks like in most cases females are getting less of the dietary factors that pose health risks when under-consumed, with the exception of fruits.

How about if we look at age groups. First let’s look at the dietary components with that were over-consumed in the United States.

Question Opportunity

Can you come up with the code for this on your own?


Click here to reveal the code.

We will also move our legend to the bottom of the plot using the theme() function of the ggplot2 package, like so:

plot_age_groups <- all_age_diet_and_guidelines %>%
  filter(
    location_name == "United States",
    direction == "high"
  ) %>%
  ggplot(aes(
    y = Relative_Percent,
    x = food,
    fill = age_group_name
  )) +
  facet_wrap(~food, scales = "free") +
  geom_boxplot() +
  theme(legend.position = "bottom")

plot_age_groups

OK! It looks like age really influences the consumption of these dietary factors. With the exception of trans fatty acids, the consumption of all of these dietary factors seems to decrease with age. Let’s also use thescale_fill_viridis() function of the viridis package to change the colors of our plot. This package uses palettes of colors that are discernible for individuals who are colorblind.

all_age_diet_and_guidelines %>%
  filter(
    location_name == "United States",
    direction == "high"
  ) %>%
  ggplot(aes(
    y = Relative_Percent,
    x = food,
    fill = age_group_name
  )) +
  facet_wrap(~food, scales = "free") +
  geom_boxplot() +
  # change the colors from rainbow to purple/green/yellow
  scale_fill_viridis(discrete = TRUE) +
  theme_linedraw() +
  theme(
    strip.text = element_text(size = 8, face = "bold"),
    axis.text.x = element_blank(),
    axis.title.x = element_blank(),
    legend.position = "bottom"
  )

Nice!

Now let’s look at the dietary factors that when consumed at low levels increase health risk:

Question Opportunity

Again, see if you come up with the code for this on your own?


Click here to reveal the code.
low_foods_plot <- all_age_diet_and_guidelines %>%
  filter(
    location_name == "United States",
    direction == "low"
  ) %>%
  ggplot(aes(
    y = Relative_Percent,
    x = food,
    fill = age_group_name
  )) +
  facet_wrap(~food, scales = "free") +
  geom_boxplot() +
  # change the colors from rainbow to purple/green/yellow
  scale_fill_viridis(discrete = TRUE) +
  theme_linedraw() +
  theme(
    strip.text = element_text(size = 7, face = "bold"),
    axis.text.x = element_blank(),
    axis.title.x = element_blank(),
    legend.position = "bottom"
  )

low_foods_plot

Interesting, we see that for the foods that are over consumed (processed meat, red meat, sodium, and sugar-sweetened beverages), consumption appears to decrease with age. For the foods that are under consumed, many appear to rise and fall with age.

Summary


Synopsis


We have evaluated average consumption estimates of 15 dietary factors with probably non-communicable disease (NCD) risk from 195 different countries around the world. To do so we imported data from a PDF using the pdftools package, as well as data from two CSV files using readr. We used tidyverse packages such as dplyr, stringr, and tidy to clean and join the data from the PDF with the CSV files.

We learned that regression is a powerful and flexible statistical tool that simplifies or estimates the relationships between variables using a mathematical model. We learned about the utility of regression techniques to compare groups, look for associations between variables, and predict outcomes based on multiple predictor or explanatory variables. We then compared this to other popular tests like the \(t\)-test and the ANOVA. We learned that these tests are actually equivalent to specialized types of regressions.

Our statistical analysis focused on evaluating differences in the consumption of red meat around the world between females and males and across different age groups. First we looked at the assumptions of \(t\)-tests and regressions, and determined that the rate of red meat consumption relative to the optimal guideline-suggested amount was right skewed. We learned that we could transform the data by taking the log of these values to achieve more normally distributed data. To compare males and females we used a \(t\)-test and learned that a \(t\)-test is a specialized form of a linear regression. To compare the 15 different age groups we used an ANOVA and learned that ANOVA is also a specialized form of linear regression. We examined how we obtained the same results using either statistical test. This was also the case if we looked at the effect of gender and controlled for the paired country structure in the data by either including location_name in the model as another term or by using a mixed effects model to control for this structure as a random effect but not specifically test for the influence of location_name on red meat consumption estimates. We learned that fixed effects are those that we wish to evaluate, while random effects are those that may influence the relationships of our variables of interest but that we do not wish to actively evaluate. Using these tests and models, we determined that males consume more red meat than females on average around the world.

Our ANOVA analysis of age determined that indeed there is at least one age group that consumed a significantly different amount of red meat compared to the other age groups, and this was still the case when we controlled for location_name. However, we learned that the ANOVA does not provide information about which age groups are different. We learned how the regression could provide some quantification of the effect of specific age groups relative to the reference age group. Furthermore, our data visualizations allowed us to determine that in general red meat consumption appears to be higher in the younger age groups relative to the older age groups.

Finally, we also looked at differences in red meat consumption between the different countries and saw in our ANOVA analysis and our regression analysis that there were significant differences. We were able to use a regression that included sex, age_group_name, and location_name to evaluate the influence of each of the three demographic factors on consumption while controlling or accounting for the other two. Our results demonstrated that all three influenced or were associated with red meat consumption.

In preforming our statistical analyses we learned about the assumptions of the \(t\)-test, regression, and the ANOVA. We also learned about important methods to tests these assumptions.

Using the ggplot2 package we were able to visualize trends in the data and to compare consumption of these dietary factors in the US with that of the other countries.

We see that the populations in many countries are over-consuming foods that are associated with health risk when over-consumed. In particular processed meat and sugar-sweetened beverages appear to be the most over consumed. Importantly both of these appear to be consumed at higher quantities by males and younger adults. People in the US appear to consume fewer sugar-sweetened beverages than other countries, however, people are still over-consuming. Processed meat however appears to be especially bad in the US. In terms of food that need to be consumed in adequate amounts to overcome health risk, nearly all countries for all factors are not reaching guideline levels. However, there are some countries consuming more than adequate amounts of legumes, vegetables, fruits and fiber. People in the US appear to eat more milk products and consume more omega-3 fatty acids and calcium rich foods than other countries. All countries including the US consume very low levels of polyunsaturated fatty acids. These polyunsaturated fatty acids are abundant in seeds, nuts and avocados, as well as fish. Likely the low level of consumption of nuts and seeds contributes to these low polyunsaturated fatty acid estimates. The supplementary table included in the article suggests that poor consumption of polyunsaturated fatty acids is associated with ischemic heart disease. The article takes this data further to evaluate the association of consumption levels of these foods with mortality.

Analyses like the one in our case study are important for defining which groups could benefit the most from interventions, education, and policy changes when attempting to mitigate public health challenges. You can see in the article however that many additional considerations would be involved to perform a more thorough analysis to adequately understand the data enough to recommend policy changes.

Suggested Homework


Students can evaluate consumption estimates of another dietary factor besides red meat.

Additional Information


Session Info


sessionInfo()
R version 4.1.2 (2021-11-01)
Platform: x86_64-w64-mingw32/x64 (64-bit)
Running under: Windows 10 x64 (build 19044)

Matrix products: default

locale:
[1] LC_COLLATE=English_United States.1252 
[2] LC_CTYPE=English_United States.1252   
[3] LC_MONETARY=English_United States.1252
[4] LC_NUMERIC=C                          
[5] LC_TIME=English_United States.1252    

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
 [1] OCSdata_1.0.2             cowplot_1.1.1            
 [3] viridis_0.6.2             viridisLite_0.4.0        
 [5] ggforce_0.3.3             ggiraph_0.8.2            
 [7] car_3.0-12                carData_3.0-5            
 [9] lmerTest_3.1-3            lme4_1.1-27.1            
[11] Matrix_1.3-4              forcats_0.5.1            
[13] ggpubr_0.4.0              ggplot2_3.3.5            
[15] tidyr_1.1.4               tibble_3.1.6             
[17] purrr_0.3.4               stringr_1.4.0            
[19] pdftools_3.1.1            skimr_2.1.3              
[21] dplyr_1.0.7               readr_2.1.1              
[23] koRpus.lang.en_0.1-4      koRpus_0.13-8            
[25] sylly_0.1-6               read.so_0.1.1            
[27] wordcountaddin_0.3.0.9000 magrittr_2.0.2           
[29] knitr_1.37                here_1.0.1               

loaded via a namespace (and not attached):
 [1] nlme_3.1-153        fs_1.5.2            usethis_2.1.5      
 [4] bit64_4.0.5         httr_1.4.2          rprojroot_2.0.2    
 [7] repr_1.1.4          numDeriv_2016.8-1.1 tools_4.1.2        
[10] backports_1.4.1     bslib_0.3.1         utf8_1.2.2         
[13] R6_2.5.1            mgcv_1.8-38         DBI_1.1.2          
[16] colorspace_2.0-2    withr_2.5.0         gridExtra_2.3      
[19] tidyselect_1.1.2    bit_4.0.4           curl_4.3.2         
[22] compiler_4.1.2      cli_3.2.0           labeling_0.4.2     
[25] sass_0.4.0          scales_1.1.1        askpass_1.1        
[28] systemfonts_1.0.4   digest_0.6.29       minqa_1.2.4        
[31] rmarkdown_2.11      base64enc_0.1-3     pkgconfig_2.0.3    
[34] htmltools_0.5.2     fastmap_1.1.0       highr_0.9          
[37] htmlwidgets_1.5.4   rlang_1.0.1         rstudioapi_0.13    
[40] farver_2.1.0        jquerylib_0.1.4     generics_0.1.1     
[43] jsonlite_1.8.0      vroom_1.5.7         polynom_1.4-0      
[46] Rcpp_1.0.8          munsell_0.5.0       fansi_1.0.2        
[49] abind_1.4-5         lifecycle_1.0.1     stringi_1.7.6      
[52] yaml_2.3.5          MASS_7.3-54         grid_4.1.2         
[55] parallel_4.1.2      crayon_1.5.0        lattice_0.20-45    
[58] splines_4.1.2       hms_1.1.1           pillar_1.7.0       
[61] uuid_1.0-3          boot_1.3-28         ggsignif_0.6.3     
[64] glue_1.6.1          evaluate_0.15       qpdf_1.1           
[67] data.table_1.14.2   remotes_2.4.2       vctrs_0.3.8        
[70] nloptr_1.2.2.3      tzdb_0.2.0          tweenr_1.0.2       
[73] gtable_0.3.0        polyclip_1.10-0     assertthat_0.2.1   
[76] xfun_0.29           sylly.en_0.1-3      broom_0.7.11       
[79] rstatix_0.7.0       ellipsis_0.3.2     

Estimate of RMarkdown Compilation Time:

About 79 - 89 seconds

This compilation time was measured on a PC machine operating on Windows 10. This range should only be used as an estimate as compilation time will vary with different machines and operating systems.

Acknowledgments


We would like to acknowledge Jessica Fanzo for assisting in framing the major direction of the case study, as well as Ashkan Afshin and Erin Mullany for giving us access to the data.

We would like to acknowledge Michael Breshock for his contributions to this case study and developing the OCSdata package.

We would also like to acknowledge the Bloomberg American Health Initiative for funding this work.

LS0tDQp0aXRsZTogIk9wZW4gQ2FzZSBTdHVkaWVzOiBFeHBsb3JpbmcgZ2xvYmFsIHBhdHRlcm5zIG9mIGRpZXRhcnkgYmVoYXZpb3JzIGFzc29jaWF0ZWQgd2l0aCBoZWFsdGggcmlzayAiDQpjc3M6IHN0eWxlLmNzcw0Kb3V0cHV0Og0KICBodG1sX2RvY3VtZW50Og0KICAgIGluY2x1ZGVzOg0KICAgICAgIGluX2hlYWRlcjogR0FfU2NyaXB0LlJodG1sDQogICAgc2VsZl9jb250YWluZWQ6IHllcw0KICAgIGNvZGVfZG93bmxvYWQ6IHllcw0KICAgIGhpZ2hsaWdodDogdGFuZ28NCiAgICBudW1iZXJfc2VjdGlvbnM6IG5vDQogICAgdGhlbWU6IGNvc21vDQogICAgdG9jOiB5ZXMNCiAgICB0b2NfZmxvYXQ6IHllcw0KICBwZGZfZG9jdW1lbnQ6DQogICAgdG9jOiB5ZXMNCiAgd29yZF9kb2N1bWVudDoNCiAgICB0b2M6IHllcw0KDQotLS0NCjxzdHlsZT4NCiNUT0Mgew0KICBiYWNrZ3JvdW5kOiB1cmwoImh0dHBzOi8vb3BlbmNhc2VzdHVkaWVzLmdpdGh1Yi5pby9pbWcvaWNvbi1iYWhpLnBuZyIpOw0KICBiYWNrZ3JvdW5kLXNpemU6IGNvbnRhaW47DQogIHBhZGRpbmctdG9wOiAyNDBweCAhaW1wb3J0YW50Ow0KICBiYWNrZ3JvdW5kLXJlcGVhdDogbm8tcmVwZWF0Ow0KfQ0KPC9zdHlsZT4NCg0KPCEtLSBPcGVuIGFsbCBsaW5rcyBpbiBuZXcgdGFiLS0+ICANCjxiYXNlIHRhcmdldD0iX2JsYW5rIi8+ICANCjxkaXYgaWQ9Imdvb2dsZV90cmFuc2xhdGVfZWxlbWVudCI+PC9kaXY+DQoNCjxzY3JpcHQgdHlwZT0idGV4dC9qYXZhc2NyaXB0IiBzcmM9Jy8vdHJhbnNsYXRlLmdvb2dsZS5jb20vdHJhbnNsYXRlX2EvZWxlbWVudC5qcz9jYj1nb29nbGVUcmFuc2xhdGVFbGVtZW50SW5pdCc+PC9zY3JpcHQ+DQoNCjxzY3JpcHQgdHlwZT0idGV4dC9qYXZhc2NyaXB0Ij4NCmZ1bmN0aW9uIGdvb2dsZVRyYW5zbGF0ZUVsZW1lbnRJbml0KCkgew0KICBuZXcgZ29vZ2xlLnRyYW5zbGF0ZS5UcmFuc2xhdGVFbGVtZW50KHtwYWdlTGFuZ3VhZ2U6ICdlbid9LCAnZ29vZ2xlX3RyYW5zbGF0ZV9lbGVtZW50Jyk7DQp9DQo8L3NjcmlwdD4NCg0KDQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0NCmtuaXRyOjpvcHRzX2NodW5rJHNldCgNCiAgaW5jbHVkZSA9IFRSVUUsIGNvbW1lbnQgPSBOQSwgZWNobyA9IFRSVUUsDQogIG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFLCBjYWNoZSA9IEZBTFNFLA0KICBmaWcuYWxpZ24gPSAiY2VudGVyIiwgb3V0LndpZHRoID0gIjkwJSINCikNCmxpYnJhcnkoaGVyZSkNCmxpYnJhcnkoa25pdHIpDQpsaWJyYXJ5KG1hZ3JpdHRyKQ0KcmVtb3Rlczo6aW5zdGFsbF9naXRodWIoImJlbm1hcndpY2svd29yZGNvdW50YWRkaW4iLCB0eXBlID0gInNvdXJjZSIsIGRlcGVuZGVuY2llcyA9IFRSVUUpDQpyZW1vdGVzOjppbnN0YWxsX2dpdGh1YigiYWxpc3RhaXJlNDcvcmVhZC5zbyIpDQpsaWJyYXJ5KHdvcmRjb3VudGFkZGluKQ0KbGlicmFyeShyZWFkLnNvKQ0KDQpybWFya2Rvd246OjpwZXJmX3RpbWVyX3Jlc2V0X2FsbCgpDQpybWFya2Rvd246OjpwZXJmX3RpbWVyX3N0YXJ0KCJyZW5kZXIiKQ0KYGBgDQoNCg0KDQojIyMjIHsub3V0bGluZSB9DQpgYGB7ciwgZWNobyA9IEZBTFNFLCBvdXQud2lkdGg9IjgwMCBwdHgifQ0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoaGVyZTo6aGVyZSgiaW1nIiwgIm1haW5wbG90LnBuZyIpKQ0KYGBgDQoNCg0KIyMjIyB7LmRpc2NsYWltZXJfYmxvY2t9DQoNCioqRGlzY2xhaW1lcioqOiBUaGUgcHVycG9zZSBvZiB0aGUgW09wZW4gQ2FzZSBTdHVkaWVzXShodHRwczovL3d3dy5vcGVuY2FzZXN0dWRpZXMub3JnKXt0YXJnZXQ9Il9ibGFuayJ9IHByb2plY3QgaXMgKip0byBkZW1vbnN0cmF0ZSB0aGUgdXNlIG9mIHZhcmlvdXMgZGF0YSBzY2llbmNlIG1ldGhvZHMsIHRvb2xzLCBhbmQgc29mdHdhcmUgaW4gdGhlIGNvbnRleHQgb2YgbWVzc3ksIHJlYWwtd29ybGQgZGF0YSoqLiBBIGdpdmVuIGNhc2Ugc3R1ZHkgZG9lcyBub3QgY292ZXIgYWxsIGFzcGVjdHMgb2YgdGhlIHJlc2VhcmNoIHByb2Nlc3MsIGlzIG5vdCBjbGFpbWluZyB0byBiZSB0aGUgbW9zdCBhcHByb3ByaWF0ZSB3YXkgdG8gYW5hbHl6ZSBhIGdpdmVuIGRhdGEgc2V0LCBhbmQgc2hvdWxkIG5vdCBiZSB1c2VkIGluIHRoZSBjb250ZXh0IG9mIG1ha2luZyBwb2xpY3kgZGVjaXNpb25zIHdpdGhvdXQgZXh0ZXJuYWwgY29uc3VsdGF0aW9uIGZyb20gc2NpZW50aWZpYyBleHBlcnRzLiANCg0KIyMjIw0KDQojIyMjIHsubGljZW5zZV9ibG9ja30NCg0KVGhpcyB3b3JrIGlzIGxpY2Vuc2VkIHVuZGVyIHRoZSBDcmVhdGl2ZSBDb21tb25zIEF0dHJpYnV0aW9uLU5vbkNvbW1lcmNpYWwgMy4wIFsoQ0MgQlktTkMgMy4wKV0oaHR0cHM6Ly9jcmVhdGl2ZWNvbW1vbnMub3JnL2xpY2Vuc2VzL2J5LW5jLzMuMC91cy8pe3RhcmdldD0iX2JsYW5rIn0gIFVuaXRlZCBTdGF0ZXMgTGljZW5zZS4NCg0KIyMjIw0KDQojIyMjIHsucmVmZXJlbmNlX2Jsb2NrfQ0KDQpUbyBjaXRlIHRoaXMgY2FzZSBzdHVkeSBwbGVhc2UgdXNlOg0KDQpXcmlnaHQsIENhcnJpZSBhbmQgTWVuZywgUWllciBhbmQgSmFnZXIsIExlYWggYW5kIFRhdWIsIE1hcmdhcmV0IGFuZCBIaWNrcywgU3RlcGhhbmllLiAoMjAyMCkuIFtodHRwczovL2dpdGh1Yi5jb20vb3BlbmNhc2VzdHVkaWVzL29jcy1icC1kaWV0XShodHRwczovL2dpdGh1Yi5jb20vb3BlbmNhc2VzdHVkaWVzL29jcy1icC1kaWV0KS4gRXhwbG9yaW5nIGdsb2JhbCBwYXR0ZXJucyBvZiBkaWV0YXJ5IGJlaGF2aW9ycyBhc3NvY2lhdGVkIHdpdGggaGVhbHRoIHJpc2sgKFZlcnNpb24gdjEuMC4wKS4NCg0KIyMjIw0KDQpUbyBhY2Nlc3MgdGhlIEdpdEh1YiBSZXBvc2l0b3J5IHdpdGggdGhlIGRhdGEgZm9yIHRoaXMgY2FzZSBzdHVkeSBzZWUgaGVyZTogaHR0cHM6Ly9naXRodWIuY29tL29wZW5jYXNlc3R1ZGllcy9vY3MtYnAtZGlldC4NCg0KWW91IG1heSBhbHNvIGFjY2VzcyBhbmQgZG93bmxvYWQgdGhlIGRhdGEgdXNpbmcgb3VyIGBPQ1NkYXRhYCBwYWNrYWdlLiBUbyBsZWFybiBtb3JlIGFib3V0IHRoaXMgcGFja2FnZSBpbmNsdWRpbmcgZXhhbXBsZXMsIHNlZSB0aGlzIFtsaW5rXShodHRwczovL2dpdGh1Yi5jb20vb3BlbmNhc2VzdHVkaWVzL09DU2RhdGEpLiBIZXJlIGlzIGhvdyB5b3Ugd291bGQgaW5zdGFsbCB0aGlzIHBhY2thZ2U6DQoNCmBgYHtyLCBldmFsPUZBTFNFfQ0KaW5zdGFsbC5wYWNrYWdlcygiT0NTZGF0YSIpDQpgYGANCg0KVGhpcyBjYXNlIHN0dWR5IGlzIHBhcnQgb2YgYSBzZXJpZXMgb2YgcHVibGljIGhlYWx0aCBjYXNlIHN0dWRpZXMgZm9yIHRoZSBbQmxvb21iZXJnIEFtZXJpY2FuIEhlYWx0aCBJbml0aWF0aXZlXShodHRwczovL2FtZXJpY2FuaGVhbHRoLmpodS5lZHUvb3Blbi1jYXNlLXN0dWRpZXMpLg0KDQoqKioNCg0KVGhlIHRvdGFsIHJlYWRpbmcgdGltZSBmb3IgdGhpcyBjYXNlIHN0dWR5IGlzIGNhbGN1bGF0ZWQgdmlhIFtrb1JwdXNdKGh0dHBzOi8vZ2l0aHViLmNvbS91bkRvY1VNZWFudEl0L2tvUnB1cykgYW5kIHNob3duIGJlbG93OiANCg0KYGBge3IsIGVjaG89RkFMU0V9DQpyZWFkdGFibGUgPSB0ZXh0X3N0YXRzKCJpbmRleC5SbWQiKSAjIHByb2R1Y2luZyByZWFkaW5nIHRpbWUgbWFya2Rvd24gdGFibGUNCnJlYWR0aW1lID0gcmVhZC5zbzo6cmVhZC5tZChyZWFkdGFibGUpICU+JSBkcGx5cjo6c2VsZWN0KE1ldGhvZCwga29ScHVzKSAlPiUgIyByZWFkaW5nIHRhYmxlIGludG8gZGF0YWZyYW1lLCBzZWxlY3RpbmcgcmVsZXZhbnQgZmFjdG9ycw0KICBkcGx5cjo6ZmlsdGVyKE1ldGhvZCA9PSAiUmVhZGluZyB0aW1lIikgJT4lICMgZHJvcHBpbmcgdW5uZWNlc3Nhcnkgcm93cw0KICBkcGx5cjo6bXV0YXRlKGtvUnB1cyA9IHBhc3RlKHJvdW5kKGFzLm51bWVyaWMoc3RyaW5ncjo6c3RyX3NwbGl0KGtvUnB1cywgIiAiKVtbMV1dWzFdKSksICJtaW51dGVzIikpICU+JSAjIHJvdW5kaW5nIHJlYWRpbmcgdGltZSBlc3RpbWF0ZQ0KICBkcGx5cjo6bXV0YXRlKE1ldGhvZCA9ICJrb1JwdXMiKSAlPiUgZHBseXI6OnJlbG9jYXRlKGtvUnB1cywgLmJlZm9yZSA9IE1ldGhvZCkgJT4lIGRwbHlyOjpyZW5hbWUoYFJlYWRpbmcgVGltZWAgPSBrb1JwdXMpICMgcmVvcmdhbml6aW5nIHRhYmxlDQprbml0cjo6a2FibGUocmVhZHRpbWUsIGZvcm1hdD0ibWFya2Rvd24iKQ0KYGBgDQoNCioqKg0KDQoqKlJlYWRhYmlsaXR5IFNjb3JlOiAqKg0KDQpBIHJlYWRhYmlsaXR5IGluZGV4IGVzdGltYXRlcyB0aGUgcmVhZGluZyBkaWZmaWN1bHR5IGxldmVsIG9mIGEgcGFydGljdWxhciB0ZXh0LiBGbGVzY2gtS2luY2FpZCwgRk9SQ0FTVCwgYW5kIFNNT0cgYXJlIHRocmVlIGNvbW1vbiByZWFkYWJpbGl0eSBpbmRpY2VzIHRoYXQgd2VyZSBjYWxjdWxhdGVkIGZvciB0aGlzIGNhc2Ugc3R1ZHkgdmlhIFtrb1JwdXNdKGh0dHBzOi8vZ2l0aHViLmNvbS91bkRvY1VNZWFudEl0L2tvUnB1cykuIFRoZXNlIGluZGljZXMgcHJvdmlkZSBhbiBlc3RpbWF0aW9uIG9mIHRoZSBtaW5pbXVtIHJlYWRpbmcgbGV2ZWwgcmVxdWlyZWQgdG8gY29tcHJlaGVuZCB0aGlzIGNhc2Ugc3R1ZHkgYnkgZ3JhZGUgYW5kIGFnZS4gDQoNCmBgYHtyLCBlY2hvPUZBTFNFfQ0KcnQgPSB3b3JkY291bnRhZGRpbjo6cmVhZGFiaWxpdHkoImluZGV4LlJtZCIsIHF1aWV0PVRSVUUpICMgcHJvZHVjaW5nIHJlYWRhYmlsaXR5IG1hcmtkb3duIHRhYmxlDQpkZiA9IHJlYWQuc286OnJlYWQubWQocnQpICU+JSBkcGx5cjo6c2VsZWN0KGluZGV4LCBncmFkZSwgYWdlKSAlPiUgICMgcmVhZGluZyB0YWJsZSBpbnRvIGRhdGFmcmFtZSwgc2VsZWN0aW5nIHJlbGV2YW50IGZhY3RvcnMNCiAgdGlkeXI6OmRyb3BfbmEoKSAlPiUgZHBseXI6Om11dGF0ZShncmFkZSA9IHJvdW5kKGFzLm51bWVyaWMoZ3JhZGUpKSwgIyBkcm9wcGluZyByb3dzIHdpdGggbWlzc2luZyB2YWx1ZXMsIHJvdW5kaW5nIGFnZSBhbmQgZ3JhZGUgY29sdW1ucw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFnZSA9IHJvdW5kKGFzLm51bWVyaWMoYWdlKSkNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICApDQprbml0cjo6a2FibGUoZGYsIGZvcm1hdD0ibWFya2Rvd24iKQ0KYGBgDQoNCioqKg0KDQpQbGVhc2UgaGVscCB1cyBieSBmaWxsaW5nIG91dCBvdXIgc3VydmV5Lg0KDQoNCjxkaXYgc3R5bGU9ImRpc3BsYXk6IGZsZXg7IGp1c3RpZnktY29udGVudDogY2VudGVyOyI+PGlmcmFtZSBzcmM9Imh0dHBzOi8vZG9jcy5nb29nbGUuY29tL2Zvcm1zL2QvZS8xRkFJcFFMU2ZwTjRGTjNLRUxxQk5FZ2YyQXRwaTdXeTdOcXkyYmVTa0ZRSU5MN1k1c0FNVjVfdy92aWV3Zm9ybT9lbWJlZGRlZD10cnVlIiB3aWR0aD0iMTIwMCIgaGVpZ2h0PSI3MDAiIGZyYW1lYm9yZGVyPSIwIiBtYXJnaW5oZWlnaHQ9IjAiIG1hcmdpbndpZHRoPSIwIj5Mb2FkaW5n4oCmPC9pZnJhbWU+PC9kaXY+DQoNCg0KIyMgKipNb3RpdmF0aW9uKioNCioqKg0KDQpBbiBbYXJ0aWNsZV0oaHR0cHM6Ly93d3cudGhlbGFuY2V0LmNvbS9hY3Rpb24vc2hvd1BkZj9waWk9UzAxNDAtNjczNiUyODE5JTI5MzAwNDEtOCl7dGFyZ2V0PSJfYmxhbmsifSByZWNlbnRseSBwdWJsaXNoZWQgaW4gVGhlIA0KTGFuY2V0IGV2YWx1YXRlZCBnbG9iYWwgZGlldGFyeSB0cmVuZHMgYW5kIHRoZSByZWxhdGlvbnNoaXAgb2YgZGlldGFyeSBmYWN0b3JzIHdpdGggbW9ydGFsaXR5IGFuZCBmZXJ0aWxpdHkuDQoNCmBgYHtyLCBlY2hvID0gRkFMU0V9DQprbml0cjo6aW5jbHVkZV9ncmFwaGljcyhoZXJlOjpoZXJlKCJpbWciLCAidGhlcGFwZXIucG5nIikpDQpgYGANCg0KIyMjIyB7LnJlZmVyZW5jZV9ibG9ja30NCkdCRCAyMDE3IERpZXQgQ29sbGFib3JhdG9ycy4gSGVhbHRoIGVmZmVjdHMgb2YgZGlldGFyeSByaXNrcyBpbiAxOTUgY291bnRyaWVzLCAxOTkw4oCTMjAxNzogYSBzeXN0ZW1hdGljIGFuYWx5c2lzIGZvciB0aGUgR2xvYmFsIEJ1cmRlbiBvZiBEaXNlYXNlIFN0dWR5IDIwMTcuICpUaGUgTGFuY2V0KiAzOTMsIDE5NTjigJMxOTcyICgyMDE5KS4NCg0KIyMjIw0KDQpUaGlzIGFydGljbGUgZXZhbHVhdGVkIGZvb2QgY29uc3VtcHRpb24gcGF0dGVybnMgaW4gMTk1IGNvdW50cmllcyBmb3IgMTUgZGlmZmVyZW50IGRpZXRhcnkgcmlzayBmYWN0b3JzIHRoYXQgaGF2ZSBwcm9iYWJsZSBhc3NvY2lhdGlvbnMgd2l0aCBub24tY29tbXVuaWNhYmxlIGRpc2Vhc2UgKE5DRCkuIEZvciBleGFtcGxlLCBvdmVyLWNvbnN1bXB0aW9uIG9mIHNvZGl1bSBpcyBhc3NvY2lhdGVkIHdpdGggaGlnaCBibG9vZCBwcmVzc3VyZS4gVGhlc2UgY29uc3VtcHRpb24gbGV2ZWxzIHdlcmUgdGhlbiB1c2VkIHRvIGVzdGltYXRlIGxldmVscyBvZiBtb3J0YWxpdHkgYW5kIG1vcmJpZGl0eSBkdWUgdG8gTkNELCBhcyB3ZWxsIGFzIGRpc2FiaWxpdHktYWRqdXN0ZWQgbGlmZS15ZWFycyAoREFMWXMpIGF0dHJpYnV0YWJsZSB0byBzdWItb3B0aW1hbCBjb25zdW1wdGlvbiBvZiBmb29kcyByZWxhdGVkIHRvIHRoZXNlIGRpZXRhcnkgcmlzayBmYWN0b3JzLiBUaGUgYXV0aG9ycyBmb3VuZCB0aGF0OiANCg0KPiAiSGlnaCBpbnRha2Ugb2Ygc29kaXVtIC4uLiwgbG93IGludGFrZSBvZiB3aG9sZSBncmFpbnMgLi4uLCBhbmQgbG93IGludGFrZSBvZiBmcnVpdHMgLi4uIHdlcmUgdGhlIGxlYWRpbmcgZGlldGFyeSByaXNrIGZhY3RvcnMgZm9yIGRlYXRocyBhbmQgREFMWXMgZ2xvYmFsbHkgYW5kIGluIG1hbnkgY291bnRyaWVzLiIgDQoNClRoaXMgZmlndXJlIGZyb20gdGhlIHBhcGVyJ3MgW3N1cHBsZW1lbnRhcnkgbWF0ZXJpYWxzXShodHRwczovL3d3dy50aGVsYW5jZXQuY29tL2Ntcy8xMC4xMDE2L1MwMTQwLTY3MzYoMTkpMzAwNDEtOC9hdHRhY2htZW50LzNkNGMwMjU4LWMyZWEtNDA1Zi04ZDExLWU5YWU2NWU2Zjk5Ni9tbWMxLnBkZil7dGFyZ2V0PSJfYmxhbmsifSBzaG93cyB0aGUgcmFua2luZyBvZiB0aGUgMTUgZGlldGFyeSByaXNrIGZhY3RvcnMgYmFzZWQgb24gdGhlIGVzdGltYXRlZCBudW1iZXIgb2YgYXR0cmlidXRhYmxlIGRlYXRocy4gSGVyZSwgdGhlIG51bWJlcnMgYW5kIGNvbG9ycyBvZiB0aGUgbGl0dGxlIHNxdWFyZXMgaW1wbHkgcmFua2luZ3Mgb2YgdGhlIHJpc2sgZmFjdG9ycyAocm93cykgYnkgcmVnaW9ucyAoY29sdW1ucykuIFRoZSBjb2xvciByZWQgaW5kaWNhdGVzIHJpc2sgZmFjdG9ycyB0aGF0IGFyZSBhc3NvY2lhdGVkIHdpdGggbGFyZ2VyIG51bWJlciBvZiBhdHRyaWJ1dGFibGUgZGVhdGhzLiBUaGUgY29sdW1uIG9uIHRoZSByaWdodCBpcyB0aGUgb3ZlcmFsbCBnbG9iYWwgZGF0YS4gQXMgeW91IGNhbiBzZWUgaGVyZSwgdGhlIHRvcCAzIHJpc2sgZmFjdG9ycyBhcmUgb2Z0ZW4gaXNzdWVzIGZvciBtYW55IGRpZmZlcmVudCByZWdpb25zIG9mIHRoZSB3b3JsZC4NCg0KYGBge3IsIGVjaG8gPSBGQUxTRSwgb3V0LndpZHRoPSAiNzAwIHB4In0NCmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKGhlcmUoImltZyIsICJkZWF0aHMucG5nIikpDQpgYGANCg0KVGhpcyBjYXNlIHN0dWR5IHdpbGwgZXZhbHVhdGUgdGhlIGRhdGEgcmVwb3J0ZWQgaW4gdGhpcyBhcnRpY2xlIHRvIGV4cGxvcmUgcmVnaW9uYWwsIGFnZSwgYW5kIGdlbmRlciBzcGVjaWZpYyBkaWZmZXJlbmNlcyBpbiBkaWV0YXJ5IGNvbnN1bXB0aW9uIHBhdHRlcm5zIGFyb3VuZCB0aGUgd29ybGQgaW4gMjAxNy4gDQoNCiMjICoqTWFpbiBRdWVzdGlvbnMqKg0KKioqDQoNCiMjIyMgey5tYWluX3F1ZXN0aW9uX2Jsb2NrfQ0KPGI+PHU+IE91ciBtYWluIHF1ZXN0aW9ucyBhcmU6IDwvdT48L2I+DQoNCjEpIFdoYXQgYXJlIHRoZSBnbG9iYWwgdHJlbmRzIGZvciBwb3RlbnRpYWxseSBoYXJtZnVsIGRpZXRzPw0KMikgSG93IGRvIG1hbGVzIGFuZCBmZW1hbGVzIGNvbXBhcmU/DQozKSBIb3cgZG8gZGlmZmVyZW50IGFnZSBncm91cHMgY29tcGFyZSBmb3IgdGhlc2UgZGlldGFyeSBmYWN0b3JzPw0KNCkgSG93IGRvIGRpZmZlcmVudCBjb3VudHJpZXMgY29tcGFyZT8gSW4gcGFydGljdWxhciwgaG93IGRvZXMgdGhlIFVTIGNvbXBhcmUgdG8gb3RoZXIgY291bnRyaWVzIGluIHRlcm1zIG9mIGRpZXQgdHJlbmRzPw0KDQojIyMjDQoNCiMjICoqTGVhcm5pbmcgT2JqZWN0aXZlcyoqDQoqKioNCg0KSW4gdGhpcyBjYXNlIHN0dWR5LCB3ZSB3aWxsIHdhbGsgeW91IHRocm91Z2ggaW1wb3J0aW5nIGRhdGEgZnJvbSBQREYgZmlsZXMgYW5kIENTViBmaWxlcywgY2xlYW5pbmcgZGF0YSwgd3JhbmdsaW5nIGRhdGEsIGNvbXBhcmluZyBkYXRhLCBqb2luaW5nIGRhdGEsIHZpc3VhbGl6aW5nIGRhdGEsIGFuZCA8Yj4gY29tcGFyaW5nIHR3byBvciBtb3JlIGdyb3VwcyA8L2I+IHVzaW5nIHdlbGwtZXN0YWJsaXNoZWQgYW5kIGNvbW1vbmx5IHVzZWQgcGFja2FnZXMsIGluY2x1ZGluZyBgc3RyaW5ncmAsIGB0aWR5cmAsIGBkcGx5cmAsIGBwdXJycmAsIGFuZCBgZ2dwbG90MmAuIFdlIHdpbGwgZXNwZWNpYWxseSBmb2N1cyBvbiB1c2luZyBwYWNrYWdlcyBhbmQgZnVuY3Rpb25zIGZyb20gdGhlIFtUaWR5dmVyc2VdKGh0dHBzOi8vd3d3LnRpZHl2ZXJzZS5vcmcvKXt0YXJnZXQ9Il9ibGFuayJ9LiBUaGUgdGlkeXZlcnNlIGlzIGEgbGlicmFyeSBvZiBwYWNrYWdlcyBjcmVhdGVkIGJ5IFJTdHVkaW8uIFdoaWxlIHNvbWUgc3R1ZGVudHMgbWF5IGJlIGZhbWlsaWFyIHdpdGggcHJldmlvdXMgUiBwcm9ncmFtbWluZyBwYWNrYWdlcywgdGhlc2UgcGFja2FnZXMgbWFrZSBkYXRhIHNjaWVuY2UgaW4gUiBlc3BlY2lhbGx5IGxlZ2libGUgYW5kIGludHVpdGl2ZS4NCg0KVGhlIHNraWxscywgbWV0aG9kcywgYW5kIGNvbmNlcHRzIHRoYXQgc3R1ZGVudHMgd2lsbCBiZSBmYW1pbGlhciB3aXRoIGJ5IHRoZSBlbmQgb2YgdGhpcyBjYXNlIHN0dWR5IGFyZToNCg0KPHU+KipEYXRhIFNjaWVuY2UgTGVhcm5pbmcgT2JqZWN0aXZlczoqKjwvdT4NCg0KMS4gSW1wb3J0aW5nL2V4dHJhY3RpbmcgZGF0YSBmcm9tIFBERiAoYGRwbHlyYCwgYHN0cmluZ3JgKSAgDQoyLiBIb3cgdG8gcmVzaGFwZSBkYXRhIGJ5IHBpdm90aW5nIGJldHdlZW4gImxvbmciIGFuZCAid2lkZSIgZm9ybWF0cyAoYHRpZHlyYCkgICAgDQozLiBQZXJmb3JtIGZ1bmN0aW9ucyBvbiBhbGwgY29sdW1ucyBvZiBhIHRpYmJsZSAoYHB1cnJyYCkgIA0KNC4gRGF0YSBjbGVhbmluZyB3aXRoIHJlZ3VsYXIgZXhwcmVzc2lvbnMgKGBzdHJpbmdyYCkgIA0KNS4gU3BlY2lmaWMgZGF0YSB2YWx1ZSByZWFzc2lnbm1lbnQgIA0KNi4gU2VwYXJhdGUgZGF0YSB3aXRoaW4gYSBjb2x1bW4gaW50byBtdWx0aXBsZSBjb2x1bW5zIChgdGlkeXJgKSANCjcuIE1ldGhvZHMgdG8gQ29tcGFyZSBkYXRhIChgZHBseXJgKSAgDQo4LiBDb21iaW5pbmcgZGF0YSBmcm9tIHR3byBzb3VyY2VzIChgZHBseXJgKSAgDQo5LiBNYWtlIGludGVyYWN0aXZlIHBsb3RzIChgZ2dpcmFwaGApICANCjEwLiBNYWtlIGEgem9vbSBmYWNldCBmb3IgcGxvdCAoYGdnZm9yY2VgKSANCjExLiBDb21iaW5lIHBsb3RzIHRvZ2V0aGVyIChgY293cGxvdGApDQoNCjx1PioqU3RhdGlzdGljYWwgTGVhcm5pbmcgT2JqZWN0aXZlczoqKjwvdT4gDQoNCjEuICBVbmRlcnN0YW5kaW5nIG9mIGhvdyB0aGUgKnQqLXRlc3QgYW5kIHRoZSBBTk9WQSBhcmUgc3BlY2lhbGl6ZWQNCiAgICByZWdyZXNzaW9ucw0KMi4gIEJhc2ljIHVuZGVyc3RhbmRpbmcgb2YgdGhlIHV0aWxpdHkgb2YgYSByZWdyZXNzaW9uIGFuYWx5c2lzDQozLiAgSG93IHRvIGltcGxlbWVudCBhIGxpbmVhciByZWdyZXNzaW9uIGFuYWx5c2lzIGluIFINCjQuICBIb3cgdG8gaW50ZXJwcmV0IHJlZ3Jlc3Npb24gY29lZmZpY2llbnRzDQo1LiAgQXdhcmVuZXNzIG9mICp0Ki10ZXN0IGFzc3VtcHRpb25zDQo2LiAgQXdhcmVuZXNzIG9mIGxpbmVhciByZWdyZXNzaW9uIGFzc3VtcHRpb25zDQo3LiAgSG93IHRvIHVzZSBRLVEgcGxvdHMgdG8gY2hlY2sgZm9yIG5vcm1hbGl0eQ0KOC4gIERpZmZlcmVuY2UgYmV0d2VlbiBmaXhlZCBlZmZlY3RzIGFuZCByYW5kb20gZWZmZWN0cw0KOS4gIEhvdyB0byBwZXJmb3JtIHBhaXJlZCAqdCotdGVzdA0KMTAuIEhvdyB0byBwZXJmb3JtIGEgbGluZWFyIG1peGVkIGVmZmVjdHMgcmVncmVzc2lvbg0KDQoNCmBgYHtyLCBvdXQud2lkdGggPSAiMjAlIiwgZWNobyA9IEZBTFNFLCBmaWcuYWxpZ24gPSAiY2VudGVyIn0NCmluY2x1ZGVfZ3JhcGhpY3MoImh0dHBzOi8vdGlkeXZlcnNlLnRpZHl2ZXJzZS5vcmcvbG9nby5wbmciKQ0KYGBgDQoNCioqKg0KDQpXZSB3aWxsIGJlZ2luIGJ5IGxvYWRpbmcgdGhlIHBhY2thZ2VzIHRoYXQgd2Ugd2lsbCBuZWVkOg0KDQpgYGB7cn0NCmxpYnJhcnkoaGVyZSkNCmxpYnJhcnkocmVhZHIpDQpsaWJyYXJ5KGRwbHlyKQ0KbGlicmFyeShza2ltcikNCmxpYnJhcnkocGRmdG9vbHMpDQpsaWJyYXJ5KHN0cmluZ3IpDQpsaWJyYXJ5KG1hZ3JpdHRyKQ0KbGlicmFyeShwdXJycikNCmxpYnJhcnkodGliYmxlKQ0KbGlicmFyeSh0aWR5cikNCmxpYnJhcnkoZ2dwbG90MikNCmxpYnJhcnkoZ2dwdWJyKQ0KbGlicmFyeShmb3JjYXRzKQ0KbGlicmFyeShsbWU0KQ0KbGlicmFyeShsbWVyVGVzdCkNCmxpYnJhcnkoY2FyKQ0KbGlicmFyeShnZ2lyYXBoKQ0KbGlicmFyeShnZ2ZvcmNlKQ0KbGlicmFyeSh2aXJpZGlzKQ0KbGlicmFyeShjb3dwbG90KQ0KbGlicmFyeShPQ1NkYXRhKQ0KYGBgDQoNCg0KIFBhY2thZ2UgICB8IFVzZSBpbiB0aGlzIGNhc2Ugc3R1ZHkgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICANCi0tLS0tLS0tLS0gfC0tLS0tLS0tLS0tLS0NCltoZXJlXShodHRwczovL2dpdGh1Yi5jb20vamVubnliYy9oZXJlX2hlcmUpe3RhcmdldD0iX2JsYW5rIn0gICAgICAgfCB0byBlYXNpbHkgbG9hZCBhbmQgc2F2ZSBkYXRhDQpbcmVhZHJdKGh0dHBzOi8vcmVhZHIudGlkeXZlcnNlLm9yZy8pe3RhcmdldD0iX2JsYW5rIn0gICAgICB8IHRvIGltcG9ydCB0aGUgQ1NWIGZpbGUgZGF0YQ0KW2RwbHlyXShodHRwczovL2RwbHlyLnRpZHl2ZXJzZS5vcmcvKXt0YXJnZXQ9Il9ibGFuayJ9ICAgICAgfCB0byBhcnJhbmdlL2ZpbHRlci9zZWxlY3QvY29tcGFyZSBzcGVjaWZpYyBzdWJzZXRzIG9mIHRoZSBkYXRhIA0KW3NraW1yXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvc2tpbXIvaW5kZXguaHRtbCl7dGFyZ2V0PSJfYmxhbmsifSAgICAgIHwgdG8gZ2V0IGFuIG92ZXJ2aWV3IG9mIGRhdGENCltwZGZ0b29sc10oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL3BkZnRvb2xzL3BkZnRvb2xzLnBkZil7dGFyZ2V0PSJfYmxhbmsifSAgIHwgdG8gcmVhZCBhIFBERiBpbnRvIFIgDQpbc3RyaW5ncl0oaHR0cHM6Ly9zdHJpbmdyLnRpZHl2ZXJzZS5vcmcvYXJ0aWNsZXMvc3RyaW5nci5odG1sKXt0YXJnZXQ9Il9ibGFuayJ9ICAgIHwgdG8gbWFuaXB1bGF0ZSB0aGUgdGV4dCB3aXRoaW4gdGhlIFBERiBvZiB0aGUgZGF0YQ0KW21hZ3JpdHRyXShodHRwczovL21hZ3JpdHRyLnRpZHl2ZXJzZS5vcmcvYXJ0aWNsZXMvbWFncml0dHIuaHRtbCl7dGFyZ2V0PSJfYmxhbmsifSAgIHwgdG8gdXNlIHRoZSBgJTw+JWAgcGlwaW5nIG9wZXJhdG9yDQpbcHVycnJdKGh0dHBzOi8vcHVycnIudGlkeXZlcnNlLm9yZy8pe3RhcmdldD0iX2JsYW5rIn0gICAgICB8IHRvIHBlcmZvcm0gZnVuY3Rpb25zIG9uIGFsbCBjb2x1bW5zIG9mIGEgdGliYmxlDQpbdGliYmxlXShodHRwczovL3RpYmJsZS50aWR5dmVyc2Uub3JnLyl7dGFyZ2V0PSJfYmxhbmsifSAgICAgfCB0byBjcmVhdGUgZGF0YSBvYmplY3RzIHRoYXQgd2UgY2FuIG1hbmlwdWxhdGUgd2l0aCBkcGx5ci9zdHJpbmdyL3RpZHlyL3B1cnJyDQpbdGlkeXJdKGh0dHBzOi8vdGlkeXIudGlkeXZlcnNlLm9yZy8pe3RhcmdldD0iX2JsYW5rIn0gICAgICB8IHRvIHNlcGFyYXRlIGRhdGEgd2l0aGluIGEgY29sdW1uIGludG8gbXVsdGlwbGUgY29sdW1ucw0KW2dncGxvdDJdKGh0dHBzOi8vZ2dwbG90Mi50aWR5dmVyc2Uub3JnLyl7dGFyZ2V0PSJfYmxhbmsifSAgICB8IHRvIG1ha2UgdmlzdWFsaXphdGlvbnMgd2l0aCBtdWx0aXBsZSBsYXllcnMNCltnZ3B1YnJdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9nZ3B1YnIvaW5kZXguaHRtbCl7dGFyZ2V0PSJfYmxhbmsifSAgICB8IHRvIGVhc2lseSBhZGQgcmVncmVzc2lvbiBsaW5lIGVxdWF0aW9ucyB0byBwbG90cw0KW2ZvcmNhdHNdKGh0dHBzOi8vZm9yY2F0cy50aWR5dmVyc2Uub3JnLyl7dGFyZ2V0PSJfYmxhbmsifSAgICB8IHRvIGNoYW5nZSBkZXRhaWxzIGFib3V0IGZhY3RvcnMgKGNhdGVnb3JpY2FsIHZhcmlhYmxlcykNCltsbWU0XShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvbG1lNC9sbWU0LnBkZil8IHRvIGZpdCBhIGxpbmVhciBtaXhlZCBlZmZlY3RzIG1vZGVsDQpbbG1lclRlc3RdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9sbWVyVGVzdC9sbWVyVGVzdC5wZGYpfCB0byBwZXJmb3JtIGxpbmVhciBtaXhlZCBtb2RlbCB0ZXN0aW5nDQpbY2FyXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvY2FyL2Nhci5wZGYpfCB0byBwZXJmb3JtIExldmVuZSdzIFRlc3Qgb2YgSG9tb2dlbmVpdHkgb2YgVmFyaWFuY2VzDQpbZ2dpcmFwaF0oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL2dnaXJhcGgvaW5kZXguaHRtbCl8IHRvIG1ha2UgcGxvdHMgaW50ZXJhY3RpdmUNCltnZ2ZvcmNlXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvZ2dmb3JjZS9nZ2ZvcmNlLnBkZil8IHRvIG1vZGlmeSBmYWNldHMgaW4gcGxvdHMNClt2aXJpZGlzXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvdmlyaWRpcy92aWduZXR0ZXMvaW50cm8tdG8tdmlyaWRpcy5odG1sKXwgdG8gcGxvdCBpbiBhIGNvbG9yIHBhbGV0dGUgdGhhdCBpcyBlYXNpbHkgaW50ZXJwcmV0ZWQgYnkgY29sb3JibGluZCBpbmRpdmlkdWFscw0KW2Nvd3Bsb3RdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9jb3dwbG90L3ZpZ25ldHRlcy9pbnRyb2R1Y3Rpb24uaHRtbCl7dGFyZ2V0PSJfYmxhbmsifSB8IHRvIGFsbG93IHBsb3RzIHRvIGJlIGNvbWJpbmVkDQpbT0NTZGF0YV0oaHR0cHM6Ly9naXRodWIuY29tL29wZW5jYXNlc3R1ZGllcy9PQ1NkYXRhKXt0YXJnZXQ9Il9ibGFuayJ9IHwgdG8gYWNjZXNzIGFuZCBkb3dubG9hZCBPQ1MgZGF0YSBmaWxlcw0KX19fDQoNCg0KDQpUaGUgZmlyc3QgdGltZSB3ZSB1c2UgYSBmdW5jdGlvbiwgd2Ugd2lsbCB1c2UgdGhlIGA6OmAgdG8gaW5kaWNhdGUgd2hpY2ggcGFja2FnZSB3ZSBhcmUgdXNpbmcuIFVubGVzcyB3ZSBoYXZlIG92ZXJsYXBwaW5nIGZ1bmN0aW9uIG5hbWVzLCB0aGlzIGlzIG5vdCBuZWNlc3NhcnksIGJ1dCB3ZSB3aWxsIGluY2x1ZGUgaXQgaGVyZSB0byBiZSBpbmZvcm1hdGl2ZSBhYm91dCB3aGVyZSB0aGUgZnVuY3Rpb25zIHdlIHdpbGwgdXNlIGNvbWUgZnJvbS4NCg0KDQojIyAqKkNvbnRleHQqKg0KKioqDQoNCkhlcmUgaXMgYW4gZXhjZXJwdCBmcm9tIHRoZSBhcnRpY2xlIGl0c2VsZiBhYm91dCB0aGUgY29udGV4dCBvZiB0aGUgd29yazoNCmBgYHtyLCBlY2hvID0gRkFMU0V9DQprbml0cjo6aW5jbHVkZV9ncmFwaGljcyhoZXJlKCJpbWciLCAiY29udGV4dC5wbmciKSkNCmBgYA0KDQpNYW55IGRpZXRhcnkgZmFjdG9ycyBoYXZlIHdlbGwtZXN0YWJsaXNoZWQgYXNzb2NpYXRpb25zIHdpdGggaGVhbHRoIHJpc2suIFRoZSBhdXRob3JzIHRoYXQgZ2VuZXJhdGVkIHRoaXMgZGF0YSBzZXQgaWRlbnRpZmllZCAxNSBkaWV0YXJ5IGZhY3RvcnMgdGhhdCBoYXZlIHByb2JhYmxlIGhlYWx0aCByaXNrIGJhc2VkIG9uIGxpdGVyYXR1cmUgc2VhcmNoLg0KDQpIZXJlIHlvdSBjYW4gc2VlIGEgdGFibGUgb2YgdGhlIHNvdXJjZXMgZm9yIHRoZSBoZWFsdGggcmlza3MgYXNzb2NpYXRlZCB3aXRoIHRoZSBkaWV0YXJ5IGZhY3RvcnMuIFRoZSBmaXJzdCBjb2x1bW4gc2hvd3MgdGhlIHJpc2sgZmFjdG9ycyBhbmQgdGhlIHNlY29uZCBjb2x1bW4gc2hvd3MgdGhlIGhlYWx0aCBvdXRjb21lcy4gVGhpcyB0YWJsZSBpcyBwYXJ0IG9mICJTdXBwbGVtZW50YWwgVGFibGUgMS4gRXBpZGVtaW9sb2dpY2FsIGV2aWRlbmNlIHN1cHBvcnRpbmcgY2F1c2FsaXR5IGJldHdlZW4gZGlldGFyeSByaXNrIGZhY3RvcnMgYW5kIGRpc2Vhc2UgZW5kcG9pbnRzIiBmcm9tIHRoZSBwYXBlcuKAmXMgW3N1cHBsZW1lbnRhcnkgbWF0ZXJpYWxzXShodHRwczovL3d3dy50aGVsYW5jZXQuY29tL2Ntcy8xMC4xMDE2L1MwMTQwLTY3MzYoMTkpMzAwNDEtOC9hdHRhY2htZW50LzNkNGMwMjU4LWMyZWEtNDA1Zi04ZDExLWU5YWU2NWU2Zjk5Ni9tbWMxLnBkZil7dGFyZ2V0PSJfYmxhbmsifS4NCg0KDQpgYGB7ciwgZWNobyA9IEZBTFNFLCBvdXQud2lkdGg9ICI1MDAgcHgifQ0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoaGVyZSgiaW1nIiwgImRpZXRhcnlyaXNrLnBuZyIpKQ0KYGBgDQoNCg0KSW4gdGhlIGFydGljbGUgdGhlIGF1dGhvcnMgZm91bmQgdGhhdCBtb3N0IG9mIHRoZSBtb3J0YWxpdHkgYXNzb2NpYXRlZCB3aXRoIGVhY2ggZmFjdG9yIGlzIHJlbGF0ZWQgdG8gY2FyZGlvdmFzY3VsYXIgZGlzZWFzZS4NCg0KYGBge3IsIGVjaG8gPSBGQUxTRSwgb3V0LndpZHRoPSAiNTAwIHB4In0NCmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKGhlcmUoImltZyIsICJjYXJkaW9yaXNrLnBuZyIpKQ0KYGBgDQoNCiMjICoqTGltaXRhdGlvbnMqKg0KKioqDQoNClRoZXJlIGFyZSBzb21lIGltcG9ydGFudCBsaW1pdGF0aW9ucyByZWdhcmRpbmcgdGhlIGRhdGEgZnJvbSB0aGlzIGFydGljbGUgdG8ga2VlcCBpbiBtaW5kLiBUaGUgZGVmaW5pdGlvbiBvZiBjZXJ0YWluIGRpZXRhcnkgZmFjdG9ycyB2YXJpZWQgYWNyb3NzIHNvbWUgb2YgdGhlIGNvbGxlY3Rpb24gc291cmNlcy4gSW50YWtlcyBvZiBjZXJ0YWluIGhlYWx0aHkgZm9vZHMgbGlrZSB2ZWdldGFibGVzIGFuZCBmcnVpdHMgYXJlIGxpa2VseSBwb3NpdGl2ZWx5IGNvcnJlbGF0ZWQgd2l0aCBlYWNoIG90aGVyIGFuZCBsaWtlbHkgbmVnYXRpdmVseSBjb3JyZWxhdGVkIHdpdGggaW50YWtlcyBvZiB1bmhlYWx0aHkgZm9vZHMuIE11Y2ggb2YgdGhlIGRhdGEgd2FzIGNvbGxlY3RlZCB3aXRoIDI0IGhvdXIgcmVjYWxsIHN1cnZleXMgd2hpY2ggYXJlIHByb25lIHRvIGlzc3VlcyBkdWUgdG8gaW5hY2N1cmFjeSBvZiBtZW1vcnkgcmVjYWxsIG9yIG90aGVyIGJpYXNlcyBzdWNoIGFzIGEgdGVuZGVuY3kgZm9yIHNvbWUgcGVvcGxlIHRvIHJlcG9ydCBoZWFsdGhpZXIgYmVoYXZpb3JzLiBUaGUgZ3VpZGVsaW5lcyBpbiB0aGUgUERGIGFyZSBub3Qgc3BlY2lmaWVkIGJ5IGdlbmRlciBldmVuIHRob3VnaCBpdCBpcyBrbm93biB0aGF0IHRoZXJlIGFyZSBkaWZmZXJlbnQgZGlldGFyeSByZXF1aXJlbWVudHMgZm9yIG9wdGltYWwgaGVhbHRoIGZvciBjZXJ0YWluIG51dHJpZW50cyBieSBnZW5kZXIuIFRoZSBhcnRpY2xlIGRpc2N1c3NlcyBzb21lIGxpbWl0YXRpb25zIGFib3V0IGFjY291bnRpbmcgZm9yIG92ZXJhbGwgZm9vZCBjb25zdW1wdGlvbiB3aGVuIGNhbGN1bGF0aW5nIGNvbnN1bXB0aW9uIG9mIHBhcnRpY3VsYXIgZm9vZHM6DQoNCj4gIlRvIHJlbW92ZSB0aGUgZWZmZWN0IG9mIGVuZXJneSBpbnRha2UgYXMgYSBwb3RlbnRpYWwgY29uZm91bmRlciBhbmQgYWRkcmVzcyBtZWFzdXJlbWVudCBlcnJvciBpbiBkaWV0YXJ5IGFzc2Vzc21lbnQgdG9vbHMsIG1vc3QgY29ob3J0cyBoYXZlIGFkanVzdGVkIGZvciB0b3RhbCBlbmVyZ3kgaW50YWtlIGluIHRoZWlyIHN0YXRpc3RpY2FsIG1vZGVscy4gVGhpcyBlbmVyZ3kgYWRqdXN0bWVudCBtZWFucyB0aGF0IGRpZXQgY29tcG9uZW50cyBhcmUgZGVmaW5lZCBhcyByaXNrcyBpbiB0ZXJtcyBvZiB0aGUgc2hhcmUgb2YgZGlldCBhbmQgbm90IGFzIGFic29sdXRlIGxldmVscyBvZiBleHBvc3VyZS4gSW4gb3RoZXIgd29yZHMsIGFuIGluY3JlYXNlIGluIGludGFrZSBvZiBmb29kcyBhbmQgbWFjcm9udXRyaWVudHMgc2hvdWxkIGJlIGNvbXBlbnNhdGVkIGJ5IGEgZGVjcmVhc2UgaW4gaW50YWtlIG9mIG90aGVyIGRpZXRhcnkgZmFjdG9ycyB0byBob2xkIHRvdGFsIGVuZXJneSBpbnRha2UgY29uc3RhbnQuIFRodXMsIHRoZSByZWxhdGl2ZSByaXNrIG9mIGNoYW5nZSBpbiBlYWNoIGNvbXBvbmVudCBvZiBkaWV0IGRlcGVuZHMgb24gdGhlIG90aGVyIGNvbXBvbmVudHMgZm9yIHdoaWNoIGl0IGlzIHN1YnN0aXR1dGVkLiBIb3dldmVyLCB0aGUgcmVsYXRpdmUgcmlza3MgZXN0aW1hdGVkIGZyb20gbWV0YS1hbmFseXNlcyBvZiBjb2hvcnQgc3R1ZGllcyBkbyBub3QgZ2VuZXJhbGx5IHNwZWNpZnkgdGhlIHR5cGUgb2Ygc3Vic3RpdHV0aW9uLg0KDQpUaGVyZSBhcmUgYWxzbyBpbXBvcnRhbnQgbnVhbmNlcyB0byBrZWVwIGluIG1pbmQgcmVnYXJkaW5nIHNvbWUgb2YgdGhlIGRpZXRhcnkgZmFjdG9ycy4gRm9yIGV4YW1wbGUgY2FsY2l1bSBjb25zdW1wdGlvbiB3YXMgY2FsY3VsYXRlZCBiYXNlZCBvbiBjb25zdW1wdGlvbiBvZiBkYWlyeSBwcm9kdWN0cywgd2hpbGUgY2FsY2l1bSBjYW4gYmUgYWNxdWlyZWQgZnJvbSBvdGhlciBzb3VyY2VzIGluY2x1ZGluZyBwbGFudC1iYXNlZCBzb3VyY2VzLiBIb3dldmVyIGluIHRoZXNlIGRhdGEsIHRoZSBpbmZsdWVuY2Ugb2YgcGxhbnQtYmFzZWQgY29uc3VtcHRpb24gb2YgY2FsY2l1bSB3YXMgbm90IGFjY291bnRlZCBmb3IsIG5vciB3YXMgc3VwcGxlbWVudGF0aW9uIHRocm91Z2ggdml0YW1pbiBzb3VyY2VzLiANCg0KQWxzbywgd2hpbGUgW2dlbmRlcl0oaHR0cHM6Ly93d3cuZ2VuZGVyc3BlY3RydW0ub3JnL3F1aWNrLWxpbmtzL3VuZGVyc3RhbmRpbmctZ2VuZGVyLyl7dGFyZ2V0PSJfYmxhbmsifSBhbmQgW3NleF0oaHR0cHM6Ly93d3cud2hvLmludC9nZW5vbWljcy9nZW5kZXIvZW4vaW5kZXgxLmh0bWwpe3RhcmdldD0iX2JsYW5rIn0gYXJlIG5vdCBhY3R1YWxseSBiaW5hcnksIHRoZSBkYXRhIHVzZWQgaW4gdGhpcyBhbmFseXNpcyBvbmx5IGNvbnRhaW5zIGluZm9ybWF0aW9uIGZvciBncm91cHMgb2YgaW5kaXZpZHVhbHMgZGVzY3JpYmVkIGFzIG1hbGUgb3IgZmVtYWxlLiANCg0KIyMgKipXaGF0IGFyZSB0aGUgZGF0YT8qKg0KKioqDQoNCldlIHdpbGwgYmUgdXNpbmcgZGF0YSB0aGF0IHdlIHJlcXVlc3RlZCBmcm9tIHRoZSBbR2xvYmFsIEJ1cmRlbiBvZiBEaXNlYXNlIChHQkQpXShodHRwOi8vd3d3LmhlYWx0aGRhdGEub3JnL2diZCl7dGFyZ2V0PSJfYmxhbmsifSBvZiB0aGUgW0luc3RpdHV0ZSBmb3IgSGVhbHRoIE1ldHJpY3MgYW5kIEV2YWx1YXRpb24gKElITUUpXShodHRwOi8vd3d3LmhlYWx0aGRhdGEub3JnL2Fib3V0KSBhYm91dCBkaWV0YXJ5IGludGFrZSwgYXMgd2VsbCBhcyB0aGUgZ3VpZGVsaW5lIGRhdGEgYWJvdXQgb3B0aW1hbCBjb25zdW1wdGlvbiBhbW91bnRzIGZvciBkaWZmZXJlbnQgZm9vZHMgY29udGFpbmVkIHdpdGhpbiB0aGUgUERGIG9mIHRoZSBhcnRpY2xlLiBXZSBoYXZlIHR3byBDU1YgZmlsZXMsIGRpZXRhcnlfcmlza19leHBvc3VyZV9hbGxfYWdlc18yMDE3LmNzdiBhbmQgZGlldGFyeV9yaXNrX2V4cG9zdXJlX3NlcF9hZ2VzXzIwMTcuY3N2LiBUaGUgZmlyc3Qgb25lIGluY2x1ZGVzIGNvbnN1bXB0aW9uIGxldmVscyBhdCB0aGUgZ2xvYmFsIGxldmVsIGFuZCBmb3IgZGlmZmVyZW50IGNvdW50cmllcyBmb3IgYWxsIGFnZXMgY29tYmluZWQuDQoNCkxvb2tpbmcgYXQgdGhlIENTViBmaWxlIGluIGV4Y2VsOg0KDQpgYGB7ciwgZWNobyA9IEZBTFNFLCBvdXQud2lkdGggPSAiODAwcHgifQ0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoaGVyZTo6aGVyZSgiaW1nIiwgImNzdi5wbmciKSkNCmBgYA0KDQpIZXJlIHlvdSBjYW4gc2VlIHRoYXQgdGhlIGRhdGEgY29udGFpbnMgbWVhbiBjb25zdW1wdGlvbiB2YWx1ZXMgZm9yIGJvdGggbWVuIGFuZCB3b21lbiBpbiB2YXJpb3VzIGNvdW50cmllcyBhdCB0aGUgbmF0aW9uYWwgbGV2ZWwgaW4gMjAxNyBmb3IgdmFyaW91cyBmb29kcyB0aGF0IG1heSBiZSBwcm9ibGVtYXRpYyBmb3IgaGVhbHRoLiBUaGUgdW5pdHMgZm9yIHRoZSBmb29kIHZhcmllcy4gU28gZm9yIGV4YW1wbGUsIHRoZSBtZWFuIGNvbHVtbiBpbiByb3cgdGhhdCBzYXlzICJEaWV0IGxvdyBpbiBmaWJlciIgaW5kaWNhdGVzIHRoZSBhdmVyYWdlIGNvbnN1bXB0aW9uIGxldmVsIHBlciBwZXJzb24gaW4gdGhhdCByZWdpb24gYW5kIG9mIHRoYXQgZ2VuZGVyIG9mIGZpYmVyIGluIGdyYW1zIHBlciBkYXkuDQoNClRoZSBzZWNvbmQgQ1NWIGZpbGUgaGFzIHNpbWlsYXIgZGF0YSwgYnV0IGNvbnN1bXB0aW9uIGxldmVscyBmb3IgZGlmZmVyZW50IGFnZSBncm91cHMgYXJlIHNlcGFyYXRlZC4NCg0KYGBge3IsIGVjaG8gPSBGQUxTRSwgb3V0LndpZHRoID0gIjgwMHB4In0NCmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKGhlcmU6OmhlcmUoImltZyIsICJhZ2Vfc2VwMy5wbmciKSkNCmBgYA0KDQpUaGUgYXV0aG9ycyBvZiB0aGlzIGFydGljbGUgb2J0YWluZWQgdGhlIGRhdGEgZnJvbSBhIHZhcmlldHkgb2Ygc291cmNlcyBpbmNsdWRpbmcgaG91c2Vob2xkIGJ1ZGdldCBzdXJ2ZXlzIGFuZCBudXRyaXRpb25hbCBzdXJ2ZXlzIHJlZ2FyZGluZyAyNCBob3VyIHJlY2FsbCBvZiBmb29kIGNvbnN1bXB0aW9uIGFuZCAyNCBob3VyIHVyaW5hcnkgc29kaXVtIGFuYWx5c2lzLiBUaGUgZGF0YSB3YXMgZGVyaXZlZCBmcm9tIHNhbGVzIGRhdGEgZnJvbSBbRXVyb21vbml0b3JdKGh0dHBzOi8vd3d3LmV1cm9tb25pdG9yLmNvbS8pe3RhcmdldD0iX2JsYW5rIn0sIGVzdGltYXRlcyBhYm91dCBuYXRpb25hbCBhdmFpbGFiaWxpdHkgb2Ygc3BlY2lmaWMgbnV0cmllbnRzIGZyb20gdGhlIFtVbml0ZWQgTmF0aW9ucyBGb29kIGFuZCBBZ3JpY3VsdHVyZSBPcmdhbml6YXRpb24gKEZBTyldKGh0dHA6Ly93d3cuZmFvLm9yZy9ob21lL2VuLyl7dGFyZ2V0PSJfYmxhbmsifSBhbmQgdGhlIFtVbml0ZWQgU3RhdGVzIERlcGFydG1lbnQgb2YgQWdyaWN1bHR1cmVdKGh0dHBzOi8vd3d3LnVzZGEuZ292Lyl7dGFyZ2V0PSJfYmxhbmsifSdzIFtOYXRpb25hbCBOdXRyaXRpb24gRGF0YWJhc2VdKGh0dHBzOi8vZGF0YS5uYWwudXNkYS5nb3YvZGF0YXNldC91c2RhLW5hdGlvbmFsLW51dHJpZW50LWRhdGFiYXNlLXN0YW5kYXJkLXJlZmVyZW5jZS1sZWdhY3ktcmVsZWFzZSl7dGFyZ2V0PSJfYmxhbmsifS4NCg0KIyMgKipEYXRhIEltcG9ydCoqDQoqKioNCg0KSWYgeW91IGhhdmUgdHJvdWJsZSBhY2Nlc3NpbmcgdGhlIFtHaXRIdWIgUmVwb3NpdG9yeV0oaHR0cHM6Ly9naXRodWIuY29tL29wZW5jYXNlc3R1ZGllcy9vY3MtYnAtZGlldCksIHRoZSBkYXRhIGNhbiBiZSBkb3dubG9hZGVkIGZyb20gW2hlcmVdKGh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9vcGVuY2FzZXN0dWRpZXMvb2NzLWJwLWRpZXQvbWFzdGVyL2RhdGEvcmF3L2RpZXRhcnlfcmlza19leHBvc3VyZV9hbGxfYWdlc18yMDE3LmNzdikgYW5kIFtoZXJlXShodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vb3BlbmNhc2VzdHVkaWVzL29jcy1icC1kaWV0L21hc3Rlci9kYXRhL3Jhdy9kaWV0YXJ5X3Jpc2tfZXhwb3N1cmVfc2VwX2FnZXNfMjAxNy5jc3YpLg0KDQpMZXQncyBpbXBvcnQgb3VyIGRhdGEgaW50byBSIG5vdyBzbyB0aGF0IHdlIGNhbiBleHBsb3JlIHRoZSBkYXRhIGZ1cnRoZXIuDQoNCkluIG91ciBjYXNlLCB3ZSBkb3dubG9hZGVkIHRoaXMgZGF0YSBhbmQgcHV0IGl0IHdpdGhpbiBhICJkYXRhIiBkaXJlY3Rvcnkgd2l0aGluIGEgc3ViZGlyZWN0b3J5IGNhbGxlZCAicmF3IiBmb3Igb3VyIHByb2plY3QuIElmIHlvdSB1c2UgYW4gUlN0dWRpbyBwcm9qZWN0LCB0aGVuIHlvdSBjYW4gdXNlIHRoZSBgaGVyZSgpYCBmdW5jdGlvbiBvZiB0aGUgYGhlcmVgIHBhY2thZ2UgdG8gbWFrZSB0aGUgcGF0aCBmb3IgaW1wb3J0aW5nIHRoaXMgZGF0YSBzaW1wbGVyLiBUaGUgYGhlcmVgIHBhY2thZ2UgYXV0b21hdGljYWxseSBzdGFydHMgbG9va2luZyBmb3IgZmlsZXMgYmFzZWQgb24gd2hlcmUgeW91IGhhdmUgYSBgLlJwcm9qYCBmaWxlIHdoaWNoIGlzIGNyZWF0ZWQgd2hlbiB5b3Ugc3RhcnQgYSBuZXcgUlN0dWRpbyBwcm9qZWN0LiBXZSBjYW4gc3BlY2lmeSB0aGF0IHdlIHdhbnQgdG8gbG9vayBmb3IgdGhlIGZpbGVzIHdpdGhpbiB0aGUgImRvY3MiIGRpcmVjdG9yeSB3aXRoaW4gYSBkaXJlY3Rvcnkgd2hlcmUgb3VyIGAuUnByb2pgIGZpbGUgaXMgbG9jYXRlZCBieSBzZXBhcmF0aW5nIHRoZSBuYW1lIG9mIHRoZSAiZGF0YSIgZGlyZWN0b3J5LCB0aGUgInJhdyIgc3ViZGlyZWN0b3J5LCBhbmQgdGhlIGZpbGUgbmFtZSB1c2luZyBjb21tYXMuDQoNCioqKg0KPGRldGFpbHM+IDxzdW1tYXJ5PiBDbGljayBoZXJlIHRvIHNlZSBtb3JlIGFib3V0IGNyZWF0aW5nIG5ldyBwcm9qZWN0cyBpbiBSU3R1ZGlvLiA8L3N1bW1hcnk+DQoNCllvdSBjYW4gY3JlYXRlIGEgcHJvamVjdCBieSBnb2luZyB0byB0aGUgRmlsZSBtZW51IG9mIFJTdHVkaW8gbGlrZSBzbzoNCg0KDQpgYGB7ciwgZWNobyA9IEZBTFNFLCBvdXQud2lkdGg9IjYwJSJ9DQprbml0cjo6aW5jbHVkZV9ncmFwaGljcyhoZXJlOjpoZXJlKCJpbWciLCAiTmV3X3Byb2plY3QucG5nIikpDQpgYGANCg0KWW91IGNhbiBhbHNvIGRvIHNvIGJ5IGNsaWNraW5nIHRoZSBwcm9qZWN0IGJ1dHRvbjoNCg0KYGBge3IsIGVjaG8gPSBGQUxTRSwgb3V0LndpZHRoPSI2MCUifQ0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoaGVyZTo6aGVyZSgiaW1nIiwgInByb2plY3RfYnV0dG9uLnBuZyIpKQ0KYGBgDQoNClNlZSBbaGVyZV0oaHR0cHM6Ly9zdXBwb3J0LnJzdHVkaW8uY29tL2hjL2VuLXVzL2FydGljbGVzLzIwMDUyNjIwNy1Vc2luZy1Qcm9qZWN0cykgdG8gbGVhcm4gbW9yZSBhYm91dCB1c2luZyBSU3R1ZGlvIHByb2plY3RzLiANCg0KPC9kZXRhaWxzPg0KDQoqKioNCg0KYGBge3J9DQpkaWV0X2RhdGEgPC0gcmVhZHI6OnJlYWRfY3N2KGhlcmUoImRhdGEiLCAicmF3IiwgDQogICAgICAgICAgICAgICAgICAgICAgICJkaWV0YXJ5X3Jpc2tfZXhwb3N1cmVfYWxsX2FnZXNfMjAxNy5jc3YiKSkNCnNlcF9hZ2VfZGlldF9kYXRhIDwtIHJlYWRfY3N2KGhlcmUoImRhdGEiLCAicmF3IiwgDQogICAgICAgICAgICAgICAgICAgICAgICJkaWV0YXJ5X3Jpc2tfZXhwb3N1cmVfc2VwX2FnZXNfMjAxNy5jc3YiKSkNCmBgYA0KDQpZb3UgbWF5IGFsc28gdXNlIHRoZSBgT0NTZGF0YWAgcGFja2FnZSB0byBkb3dubG9hZCB0aGUgcmF3IGRhdGE6IA0KDQpgYGB7ciwgZXZhbD1GQUxTRX0NCiMgaW5zdGFsbC5wYWNrYWdlcygiT0NTZGF0YSIpDQpsaWJyYXJ5KE9DU2RhdGEpDQpyYXdfZGF0YSgib2NzLWJwLWRpZXQiLCBvdXRwYXRoID0gZ2V0d2QoKSkNCiMgVGhpcyB3aWxsIHNhdmUgdGhlIHJhdyBkYXRhIGZpbGVzIGluIGEgIk9DU2RhdGEvZGF0YS9yYXcvIiBzdWItZm9sZGVyIA0KIyBpbiB5b3VyIGN1cnJlbnQgd29ya2luZyBkaXJlY3RvcnkNCmBgYA0KDQpJZiB5b3UgdXNlZCB0aGUgYE9DU2RhdGFgIHBhY2thZ2UgdG8gZG93bmxvYWQgdGhlIHJhdyBkYXRhLCB5b3UgY2FuIGltcG9ydCB0aGUgZGF0YSBpbnRvIFIgbGlrZSBzbzoNCg0KYGBge3IsIGV2YWw9RkFMU0V9DQpkaWV0X2RhdGEgPC0gcmVhZHI6OnJlYWRfY3N2KGhlcmUoIk9DU2RhdGEiLCAiZGF0YSIsICJyYXciLCANCiAgICAgICAgICAgICAgICAgICAgICAgImRpZXRhcnlfcmlza19leHBvc3VyZV9hbGxfYWdlc18yMDE3LmNzdiIpKQ0Kc2VwX2FnZV9kaWV0X2RhdGEgPC0gcmVhZF9jc3YoaGVyZSgiT0NTZGF0YSIsICJkYXRhIiwgInJhdyIsIA0KICAgICAgICAgICAgICAgICAgICAgICAiZGlldGFyeV9yaXNrX2V4cG9zdXJlX3NlcF9hZ2VzXzIwMTcuY3N2IikpDQpgYGANCg0KRmlyc3QgbGV0J3MganVzdCBnZXQgYSBnZW5lcmFsIHNlbnNlIG9mIG91ciBkYXRhLiBXZSBjYW4gZG8gdGhhdCB1c2luZyB0aGUgYGdsaW1wc2UoKWAgZnVuY3Rpb24gb2YgdGhlIGBkcGx5cmAgcGFja2FnZSAoaXQgaXMgYWxzbyBpbiB0aGUgYHRpYmJsZWAgcGFja2FnZSkuDQoNCmBgYHtyfQ0KZHBseXI6OmdsaW1wc2UoZGlldF9kYXRhKQ0KYGBgDQoNCmBgYHtyfQ0KZ2xpbXBzZShzZXBfYWdlX2RpZXRfZGF0YSkNCmBgYA0KDQpIZXJlIHdlIGNhbiB0ZWxsIHRoYXQgdGhlIGBzZXBfYWdlX2RpZXRfZGF0YWAgaXMgbXVjaCBsYXJnZXIgdGhhbiB0aGUgYGRpZXRfZGF0YWAuIFRoZSBgZGlldF9kYXRhYCBoYXMgb25seSA1LDg4MCByb3dzIHdoaWxlIHRoZSBgc2VwX2FnZV9kaWV0X2RhdGFgIGhhcyA4OCwyMDAgcm93cyENCg0KSG93ZXZlciwgYm90aCBmaWxlcyBhcHBlYXIgdG8gaGF2ZSB0aGUgc2FtZSBjb2x1bW4gc3RydWN0dXJlIHdpdGggMTEgdmFyaWFibGVzIGVhY2guDQoNCg0KVGhlIGBza2ltKClgIGZ1bmN0aW9uIG9mIHRoZSBgc2tpbXJgIHBhY2thZ2UgaXMgYWxzbyByZWFsbHkgaGVscGZ1bCBmb3IgZ2V0dGluZyBhIGdlbmVyYWwgc2Vuc2Ugb2YgeW91ciBkYXRhLg0KDQpgYGB7cn0NCnNraW0oZGlldF9kYXRhKQ0KYGBgDQoNCiAgICAgICAgIA0KTm90aWNlIGhvdyB0aGVyZSBpcyBhIGNvbHVtbiBwcm92aWRpbmcgdGhlIG51bWJlciBvZiBtaXNzaW5nIG9ic2VydmF0aW9ucyBmb3IgZWFjaCB2YXJpYWJsZS4gSXQgbG9va3MgbGlrZSBvdXIgZGF0YSBpcyB2ZXJ5IGNvbXBsZXRlIGFuZCB3ZSBkbyBub3QgaGF2ZSBhbnkgbWlzc2luZyBkYXRhLg0KV2UgYWxzbyBnZXQgYSBzZW5zZSBhYm91dCB0aGUgc2l6ZSBvZiBvdXIgZGF0YS4NCg0KVGhlIGBuX3VucWl1ZWAgY29sdW1uIHNob3dzIHVzIHRoZSBudW1iZXIgb2YgdW5pcXVlIHZhbHVlcyBmb3IgZWFjaCBvZiBvdXIgY29sdW1ucy4NCg0KDQpMZXQncyB0YWtlIGEgbG9vayBhdCBgc2VwX2FnZV9kaWV0X2RhdGFgLg0KDQpgYGB7cn0NCnNraW0oc2VwX2FnZV9kaWV0X2RhdGEpDQpgYGANCg0KV2UgY2FuIHNlZSB0aGF0IHRoZXJlIGFyZSBtYW55IG1vcmUgcm93cyBpbiB0aGlzIGRhdGEgc2V0Lg0KDQpMZXQncyBjaGFuZ2UgdGhlIHZhcmlhYmxlIG5hbWUgYHJlaV9uYW1lYCB0byBgZGlldGFyeV9yaXNrYCBzbyB0aGF0IGl0IG1ha2VzIG1vcmUgc2Vuc2UuIFdlIGNhbiB1c2UgdGhlIGByZW5hbWUoKWAgZnVuY3Rpb24gZnJvbSB0aGUgYGRwbHlyYCBwYWNrYWdlLg0KDQpgYGB7cn0NCmRpZXRfZGF0YSA8LSBkcGx5cjo6cmVuYW1lKGRpZXRfZGF0YSwgZGlldGFyeV9yaXNrID0gcmVpX25hbWUpDQpzZXBfYWdlX2RpZXRfZGF0YSA8LSBkcGx5cjo6cmVuYW1lKHNlcF9hZ2VfZGlldF9kYXRhLCBkaWV0YXJ5X3Jpc2sgPSByZWlfbmFtZSkNCg0KZ2xpbXBzZShkaWV0X2RhdGEpDQpnbGltcHNlKHNlcF9hZ2VfZGlldF9kYXRhKQ0KYGBgDQoNCkxvb2tzIGdvb2QhDQoNCldlIHdpbGwgdGhlbiB0YWtlIGEgbG9vayBhdCB0aGUgZGlmZmVyZW50IGRpZXRhcnkgcmlzayBmYWN0b3JzIGNvbnNpZGVyZWQuDQpUbyBkbyB0aGlzIHdlIHdpbGwgdXNlIHRoZSBgZGlzdGluY3QoKWAgZnVuY3Rpb24gb2YgdGhlIGBkcGx5cmAgcGFja2FnZS4NCg0KVGhpcyBmdW5jdGlvbiBncmFicyBvbmx5IHRoZSBkaXN0aW5jdCBvciB1bmlxdWUgcm93cyBmcm9tIGEgZ2l2ZW4gdmFyaWFibGUgKGBkaWV0YXJ5X3Jpc2tgLCBpbiBvdXIgY2FzZSkgb2YgYSBnaXZlbiBkYXRhIGZyYW1lIChgZGlldF9kYXRhYCwgaW4gb3VyIGNhc2UpLg0KDQpgYGB7cn0NCmRwbHlyOjpkaXN0aW5jdChkaWV0X2RhdGEsIGRpZXRhcnlfcmlzaykNCmBgYA0KDQpCb3RoIG92ZXIgYW5kIHVuZGVyIGNvbnN1bXB0aW9uIGNvdWxkIGJlIGEgaGVhbHRoIHByb2JsZW0hDQoNCldlIHdpbGwgYmUgdXNpbmcgdGhlIGAlPiVgIHBpcGUgZm9yIHNlcXVlbnRpYWwgc3RlcHMgaW4gb3VyIGNvZGUgbGF0ZXIgb24uDQpUaGlzIHdpbGwgbWFrZSBtb3JlIHNlbnNlIHdoZW4gd2UgaGF2ZSBtdWx0aXBsZSBzZXF1ZW50aWFsIHN0ZXBzIHVzaW5nIHRoZSBzYW1lIGRhdGEgb2JqZWN0Lg0KDQoNCldlIGNvdWxkIGRvIHRoZSBzYW1lIGNvZGUgYXMgYWJvdmUgdXNpbmcgdGhpcyBub3RhdGlvbi4gRm9yIGV4YW1wbGUgd2UgZmlyc3QgZ3JhYiB0aGUgYGRpZXRfZGF0YWAsIHRoZW4gd2Ugc2VsZWN0IHRoZSBkaXN0aW5jdCB2YWx1ZXMgb2YgdGhlIGBkaWV0YXJ5X3Jpc2tgIHZhcmlhYmxlLg0KDQpgYGB7cn0NCmRpZXRfZGF0YSAlPiUNCiAgZGlzdGluY3QoZGlldGFyeV9yaXNrKQ0KYGBgDQoNCk9LLCBzbyB0aGF0IGdpdmVzIHVzIGFuIGlkZWEgb2Ygd2hhdCBkaWV0YXJ5IGZhY3RvcnMgd2UgY2FuIGV4cGxvcmUsIGFuZCB3ZSBjYW4gc2VlIHRoYXQgdGhlcmUgYXJlIDE1IG9mIHRoZW0uIA0KDQpMZXQncyBzZWUgaWYgdGhlIGBsb2NhdGlvbl9uYW1lYCB2YWx1ZXMgYXJlIHRoZSBzYW1lIGJldHdlZW4gYm90aCBDU1YgZmlsZXMuIFRvIGRvIHRoaXMgd2Ugd2lsbCB1c2UgdGhlIGBzZXRlcXVhbCgpYCBmdW5jdGlvbiBvZiBgZHBseXJgLg0KYGBge3J9DQpkcGx5cjo6c2V0ZXF1YWwoDQogIGRpc3RpbmN0KGRpZXRfZGF0YSwgbG9jYXRpb25fbmFtZSksDQogIGRpc3RpbmN0KHNlcF9hZ2VfZGlldF9kYXRhLCBsb2NhdGlvbl9uYW1lKQ0KKQ0KYGBgDQoNCk9LLCB3ZSBnb3QgdGhlIHZhbHVlIG9mIFRSVUUsIHNvIGl0IGxvb2tzIGxpa2UgdGhlIHNhbWUgbG9jYXRpb25zIGFyZSBpbiBib3RoIGZpbGVzLg0KDQpOb3RlOiBJbiB0aGlzIGNhc2Ugd2VyZSBjb21wYXJpbmcgdHdvIGRpZmZlcmVudCBvYmplY3RzIHNvIHVzaW5nIHRoZSBwaXBlIGlzIG5vdCBhcyB1c2VmdWwuDQoNCkxldCdzIHRha2UgYSBsb29rIGF0IHRoZSBsb2NhdGlvbnMgaW5jbHVkZWQgaW4gdGhlIGRhdGEuDQoNCiMjIyMgey5zY3JvbGxhYmxlIH0NCmBgYHtyfQ0KIyBzY3JvbGwgdGhyb3VnaCB0aGUgb3V0cHV0IQ0Kc2VwX2FnZV9kaWV0X2RhdGEgJT4lDQogIGRpc3RpbmN0KGxvY2F0aW9uX25hbWUpICU+JQ0KICBwdWxsKCkNCmBgYA0KIyMjIw0KDQoNCk9LLCBzbyB0aGVyZSBhcmUgZ2xvYmFsIHZhbHVlcywgYXMgd2VsbCBhcyB2YWx1ZXMgZm9yIDE5NSBjb3VudHJpZXMuDQoNCg0KTGV0J3MgdGFrZSBhIGxvb2sgYXQgdGhlIGRhdGEgd2hlbiB3ZSBvcmRlciBpdCBieSB0aGUgbWVhbiBjb25zdW1wdGlvbiByYXRlIGNvbHVtbi4gV2UgY2FuIGRvIHNvIHVzaW5nIHRoZSBgYXJyYW5nZSgpYCBmdW5jdGlvbiBvZiB0aGUgYGRwbHlyYCBwYWNrYWdlLg0KDQpgYGB7cn0NCmRpZXRfZGF0YSAlPiUNCiAgZHBseXI6OmFycmFuZ2UobWVhbikgJT4lDQogIGdsaW1wc2UoKQ0KYGBgDQoNCk9LLCBzbyBpdCBsb29rcyBsaWtlIHBlb3BsZSBpbiBMZWJhbm9uIGRvbid0IGVhdCB2ZXJ5IG1hbnkgdHJhbnMgZmF0dHkgYWNpZHMuDQoNCkxldCdzIGFsc28gZmlndXJlIG91dCBob3cgbWFueSB2YWx1ZXMgdGhlcmUgYXJlIGluIGVhY2ggYWdlIGdyb3VwIG9mIHRoZSBkYXRhIHRoYXQgaXMgc2VwYXJhdGVkIGJ5IGFnZS4gV2Ugd2lsbCB1c2UgdGhlIGBjb3VudCgpYCBmdW5jdGlvbiBvZiB0aGUgYGRwbHlyYCBwYWNrYWdlIHRvIGRvIHRoaXMuDQoNCmBgYHtyfQ0Kc2VwX2FnZV9kaWV0X2RhdGEgJT4lDQogIGRwbHlyOjpjb3VudChhZ2VfZ3JvdXBfbmFtZSkNCmBgYA0KVGhhdCdzIGEgbG90IG9mIHZhbHVlcyENCg0KTGV0J3MgbG9vayBhIGJpdCBkZWVwZXIgdG8gdHJ5IHRvIHVuZGVyc3RhbmQgd2h5Lg0KV2UgY2FuIHVzZSB0aGUgYGNvdW50KClgIGZ1bmN0aW9uIGFnYWluIGJ1dCBnZXQgdGhlIG51bWJlciBvZiB2YWx1ZXMgZm9yIGVhY2ggY2F0ZWdvcnkgd2l0aGluIGBzZXhgLCBgYWdlX2dyb3VwX25hbWVgIGFuZCBgbG9jYXRpb25fbmFtZWAgb2YgdGhlIGRhdGEuDQoNCmBgYHtyfQ0Kc2VwX2FnZV9kaWV0X2RhdGEgJT4lDQogIGNvdW50KHNleCwgYWdlX2dyb3VwX25hbWUsIGxvY2F0aW9uX25hbWUpDQpgYGANCg0KT0ssIHNvIGl0IGxvb2tzIGxpa2UgdGhlc2UgYXJlIHByb2JhYmx5IHRoZSBjb25zdW1wdGlvbiB2YWx1ZXMgZm9yIGVhY2ggb2YgdGhlIGRpZmZlcmVudCBkaWV0YXJ5IGZhY3RvcnMgKHNpbmNlIHRoZXJlIHdlcmUgMTUgZGlmZmVyZW50IGZhY3RvcnMpIGZvciBlYWNoIGFnZSBncm91cCBhbmQgZ2VuZGVyIGNvbWJpbmF0aW9uIHdpdGhpbiBlYWNoIGNvdW50cnkuDQoNCldlIGNhbiBjb25maXJtIHRoaXMgYnkgZmlsdGVyaW5nIHRoZSBkYXRhIHRvIG9uZSBvZiB0aGUgYWdlIGdyb3VwcywgZm9yIGEgc2luZ2xlIGdlbmRlciwgYW5kIGZvciBhIHNpbmdsZSBsb2NhdGlvbi4gVG8gZG8gdGhpcyB3ZSBjYW4gdXNlIHRoZSBgZmlsdGVyKClgIGZ1bmN0aW9uIG9mIHRoZSBgZHBseXJgIHBhY2thZ2UuIE5vdGljZSB0aGF0IHdlIG5lZWQgdG8gdXNlIHR3byBlcXVhbCBzaWducyBgPT1gIHRvIHNwZWNpZnkgd2hhdCB2YWx1ZXMgd2Ugd291bGQgbGlrZSBmb3IgZWFjaCB2YXJpYWJsZS4NCg0KYGBge3J9DQpzZXBfYWdlX2RpZXRfZGF0YSAlPiUNCiAgZHBseXI6OmZpbHRlcigNCiAgICBzZXggPT0gIkZlbWFsZSIsDQogICAgYWdlX2dyb3VwX25hbWUgPT0gIjI1IHRvIDI5IiwNCiAgICBsb2NhdGlvbl9uYW1lID09ICJBZmdoYW5pc3RhbiINCiAgKQ0KYGBgDQoNClRoaXMgY29uZmlybXMgdGhhdCBmb3IgZWFjaCBvZiB0aGUgMTUgZGlldGFyeSBmYWN0b3JzLCBvdXIgdW5pdCBvZiBvYnNlcnZhdGlvbiBpcyBhIGNvbWJpbmF0aW9uIG9mIGdlbmRlciwgYWdlIGFuZCBjb3VudHJ5LiANCg0KSG93ZXZlciwgYmVmb3JlIHdlIHByb2NlZWQgd2l0aCBvdXIgYW5hbHlzaXMsIHdlIHdpbGwgd2FudCB0byBwZXJmb3JtIHNvbWUgYWRkaXRpb25hbCBkYXRhIHdyYW5nbGluZy4gVG8gZG8gdGhpcywgd2Ugd2lsbCBpbnRyb2R1Y2UgdGhlIGBwZGZ0b29sc2AgcGFja2FnZSwgd2hpY2ggd2lsbCBhbGxvdyB1cyB0byBwdWxsIGFkZGl0aW9uYWwgZGF0YSBmcm9tIHRoZSBtYW51c2NyaXB0IGl0c2VsZi4NCg0KDQpXaGlsZSBhbGwgb2YgdGhlIG1lYW4gY29uc3VtcHRpb24gdmFsdWVzIGFyZSByZXBvcnRlZCBpbiBncmFtcywgZWFjaCBkaWV0YXJ5IGZhY3RvciBoYXMgYSBkaWZmZXJlbnQgYW1vdW50IHRoYXQgaXMgY29uc2lkZXJlZCBvcHRpbWFsIGZvciBjb25zdW1pbmcuIFRvIG1ha2UgdGhlIGNvbnN1bXB0aW9uIHZhbHVlcyBtb3JlIGNvbXBhcmFibGUgYWNyb3NzIGZhY3RvcnMsIGxldCdzIGFsc28gZ2V0IHNvbWUgZGF0YSBmcm9tIHRoZSBQREYgb2YgdGhlIHBhcGVyIHNvIHRoYXQgd2UgY2FuIGNhbGN1bGF0ZSBjb25zdW1wdGlvbiBvZiB0aGVzZSBkaWV0YXJ5IGZhY3RvcnMgYXMgcGVyY2VudGFnZXMgb2YgdGhlIGRhaWx5IG9wdGltdW0uDQoNCldlIGFyZSBpbnRlcmVzdGVkIGluIHRoaXMgdGFibGUgb24gcGFnZSAzOg0KDQpgYGB7ciwgZWNobyA9IEZBTFNFLCBvdXQud2lkdGggPSAiODAwcHgifQ0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoaGVyZTo6aGVyZSgiaW1nIiwgIlRhYmxlLnBuZyIpKQ0KYGBgDQoNCkZpcnN0IGxldCdzIGltcG9ydCB0aGUgUERGIHVzaW5nIHRoZSBgcGZkX3RleHQoKWAgZnVuY3Rpb24gb2YgdGhlIGBwZGZ0b29sc2AgcGFja2FnZS4NCg0KWW91IGNhbiBmaW5kIHRoaXMgZmlsZSBbaGVyZV0oaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL29wZW5jYXNlc3R1ZGllcy9vY3MtYnAtZGlldC9tYXN0ZXIvZGF0YS9yYXcvQWZzaGluX2V0X2FsXzIwMTkucGRmKS4NCg0KYGBge3J9DQpwYXBlciA8LSBwZGZ0b29sczo6cGRmX3RleHQoaGVyZSgiZGF0YSIsICJyYXciLA0KIkFmc2hpbl9ldF9hbF8yMDE5LnBkZiIpKQ0KYGBgDQoNCldlIGNhbiBzYXZlIG91ciBpbXBvcnRlZCBkYXRhIGFzIGFuIHJkYSBmaWxlIChzdGFuZHMgZm9yIFIgZGF0YSBmaWxlKSB1c2luZyB0aGUgYHNhdmUoKWAgZnVuY3Rpb24uIA0KDQpgYGB7cn0NCnNhdmUoZGlldF9kYXRhLCBzZXBfYWdlX2RpZXRfZGF0YSwgcGFwZXIsIGZpbGUgPSBoZXJlOjpoZXJlKCJkYXRhIiwgImltcG9ydGVkIiwgImltcG9ydGVkX2RhdGEucmRhIikpDQpgYGANCg0KDQojIyAqKkRhdGEgV3JhbmdsaW5nKioNCioqKg0KDQpJZiB5b3UgaGF2ZSBiZWVuIGZvbGxvd2luZyBhbG9uZyBidXQgc3RvcHBlZCwgd2UgY291bGQgbG9hZCBvdXIgaW1wb3J0ZWQgZGF0YSBsaWtlIHNvOg0KDQpgYGB7cn0NCmxvYWQoaGVyZTo6aGVyZSgiZGF0YSIsICJpbXBvcnRlZCIsICJpbXBvcnRlZF9kYXRhLnJkYSIpKQ0KYGBgDQoNCioqKg0KPGRldGFpbHM+IDxzdW1tYXJ5PiBJZiB5b3Ugc2tpcHBlZCB0aGUgZGF0YSBpbXBvcnQgc2VjdGlvbiBjbGljayBoZXJlLiA8L3N1bW1hcnk+DQoNCkZpcnN0IHlvdSBuZWVkIHRvIGluc3RhbGwgYW5kIGxvYWQgdGhlIGBPQ1NkYXRhYCBwYWNrYWdlOg0KDQpgYGB7ciwgZXZhbD1GQUxTRX0NCmluc3RhbGwucGFja2FnZXMoIk9DU2RhdGEiKQ0KbGlicmFyeShPQ1NkYXRhKQ0KYGBgDQoNClRoZW4sIHlvdSBtYXkgbG9hZCB0aGUgaW1wb3J0ZWQgZGF0YSB1c2luZyB0aGUgZm9sbG93aW5nIGNvZGU6DQoNCmBgYHtyLCBldmFsPUZBTFNFfQ0KaW1wb3J0ZWRfZGF0YSgib2NzLWJwLWRpZXQiLCBvdXRwYXRoID0gZ2V0d2QoKSkNCmxvYWQoaGVyZTo6aGVyZSgiT0NTZGF0YSIsICJkYXRhIiwgImltcG9ydGVkIiwgImltcG9ydGVkX2RhdGEucmRhIikpDQpgYGANCg0KSWYgdGhlIHBhY2thZ2UgZG9lcyBub3Qgd29yayBmb3IgeW91LCBhbHRlcm5hdGl2ZWx5LCBhbiBSREEgZmlsZSAoc3RhbmRzIGZvciBSIGRhdGEpIG9mIHRoZSBkYXRhIGNhbiBiZSBmb3VuZCBpbiBvdXIgW0dpdEh1YiByZXBvc2l0b3J5XShodHRwczovL2dpdGh1Yi5jb20vL29wZW5jYXNlc3R1ZGllcy9vY3MtYnAtZGlldC90cmVlL21hc3Rlci9kYXRhL2ltcG9ydGVkKSBvciBzbGlnaHRseSBtb3JlIGRpcmVjdGx5IFtoZXJlXShodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vb3BlbmNhc2VzdHVkaWVzL29jcy1icC1kaWV0L21hc3Rlci9kYXRhL2ltcG9ydGVkL2ltcG9ydGVkX2RhdGEucmRhKS4gRG93bmxvYWQgdGhpcyBmaWxlIGFuZCB0aGVuIHBsYWNlIGl0IGluIHlvdXIgY3VycmVudCB3b3JraW5nIGRpcmVjdG9yeSB3aXRoaW4gYSBzdWJkaXJlY3RvcnkgY2FsbGVkICJpbXBvcnRlZCIgd2l0aGluIGEgc3ViZGlyZWN0b3J5IGNhbGxlZCAiZGF0YSIgdG8gY29weSBhbmQgcGFzdGUgb3VyIGNvZGUuIFdlIHVzZWQgYW4gUlN0dWRpbyBwcm9qZWN0IGFuZCB0aGUgW2BoZXJlYCBwYWNrYWdlXShodHRwczovL2dpdGh1Yi5jb20vamVubnliYy9oZXJlX2hlcmUpIHRvIG5hdmlnYXRlIHRvIHRoZSBmaWxlIG1vcmUgZWFzaWx5LiANCg0KYGBge3J9DQpsb2FkKGhlcmU6OmhlcmUoImRhdGEiLCAiaW1wb3J0ZWQiLCAiaW1wb3J0ZWRfZGF0YS5yZGEiKSkNCmBgYA0KDQoqKioNCjxkZXRhaWxzPiA8c3VtbWFyeT4gQ2xpY2sgaGVyZSB0byBzZWUgbW9yZSBhYm91dCBjcmVhdGluZyBuZXcgcHJvamVjdHMgaW4gUlN0dWRpby4gPC9zdW1tYXJ5Pg0KDQpZb3UgY2FuIGNyZWF0ZSBhIHByb2plY3QgYnkgZ29pbmcgdG8gdGhlIEZpbGUgbWVudSBvZiBSU3R1ZGlvIGxpa2Ugc286DQoNCg0KYGBge3IsIGVjaG8gPSBGQUxTRSwgb3V0LndpZHRoPSI2MCUifQ0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoaGVyZTo6aGVyZSgiaW1nIiwgIk5ld19wcm9qZWN0LnBuZyIpKQ0KYGBgDQoNCllvdSBjYW4gYWxzbyBkbyBzbyBieSBjbGlja2luZyB0aGUgcHJvamVjdCBidXR0b246DQoNCmBgYHtyLCBlY2hvID0gRkFMU0UsIG91dC53aWR0aD0iNjAlIn0NCmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKGhlcmU6OmhlcmUoImltZyIsICJwcm9qZWN0X2J1dHRvbi5wbmciKSkNCmBgYA0KDQpTZWUgW2hlcmVdKGh0dHBzOi8vc3VwcG9ydC5yc3R1ZGlvLmNvbS9oYy9lbi11cy9hcnRpY2xlcy8yMDA1MjYyMDctVXNpbmctUHJvamVjdHMpIHRvIGxlYXJuIG1vcmUgYWJvdXQgdXNpbmcgUlN0dWRpbyBwcm9qZWN0cyBhbmQgW2hlcmVdKGh0dHBzOi8vZ2l0aHViLmNvbS9qZW5ueWJjL2hlcmVfaGVyZSkgdG8gbGVhcm4gbW9yZSBhYm91dCB0aGUgYGhlcmVgIHBhY2thZ2UuDQoNCjwvZGV0YWlscz4NCioqKg0KPC9kZXRhaWxzPg0KDQoqKioNCg0KTGV0J3MgdGFrZSBhIGxvb2sgYXQgb3VyIG1hbnVzY3JpcHQgZGF0YS4NCg0KV2UgY2FuIHVzZSB0aGUgYGJhc2VgIGBzdW1tYXJ5KClgIGZ1bmN0aW9uIHRvIGdldCBhIHNlbnNlIG9mIHdoYXQgdGhlIGRhdGEgbG9va3MgbGlrZS4gQnkgYGJhc2VgIHdlIG1lYW4gdGhhdCB0aGVzZSBmdW5jdGlvbnMgYXJlIHBhcnQgb2YgdGhlIGBiYXNlYCBwYWNrYWdlIGFuZCBhcmUgbG9hZGVkIGF1dG9tYXRpY2FsbHkgb24gc3RhcnR1cCBvZiBSLiBUaHVzLCBgbGlicmFyeShiYXNlKWAgaXMgbm90IHJlcXVpcmVkLg0KDQpgYGB7cn0NCnN1bW1hcnkocGFwZXIpDQpgYGANCg0KV2UgY2FuIHNlZSB0aGF0IHdlIGhhdmUgMTUgZGlmZmVyZW50IGNoYXJhY3RlciBzdHJpbmdzLiBFYWNoIG9uZSBjb250YWlucyB0aGUgdGV4dCBvbiBlYWNoIG9mIHRoZSAxNSBkaWZmZXJlbnQgcGFnZXMgb2YgdGhlIFBERi4NCg0KQWdhaW4sIHRoZSB0YWJsZSB3ZSBhcmUgaW50ZXJlc3RlZCBpbiBpcyBvbiB0aGUgdGhpcmQgcGFnZSwgc28gbGV0J3MgZ3JhYiBqdXN0IHRoYXQgcG9ydGlvbiBvZiB0aGUgUERGLiBUaGUgdG9wIG9mIHRoaXMgcGFnZSBsb29rcyBsaWtlOg0KDQpgYGB7ciwgZWNobyA9IEZBTFNFLCBvdXQud2lkdGggPSAiODAwcHgifQ0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoaGVyZTo6aGVyZSgiaW1nIiwgInBhZ2UzLnBuZyIpKQ0KYGBgDQoNCmBgYHtyfQ0KIyBIZXJlIHdlIHdpbGwgc2VsZWN0IHRoZSAzcmQgdmFsdWUgaW4gdGhlIHBhcGVyIG9iamVjdA0KcGRmX3RhYmxlIDwtIHBhcGVyWzNdDQoNCnN1bW1hcnkocGRmX3RhYmxlKQ0KDQojIHNwZWNpZnlpbmcgbmNoYXIubWF4IHRydW5jYXRlcyB0aGUgb3V0cHV0DQpnbGltcHNlKHBkZl90YWJsZSwgbmNoYXIubWF4ID0gODAwKQ0KYGBgDQoNCkhlcmUgd2UgY2FuIHNlZSB0aGF0IHRoZSBgcGRmX3RhYmxlYCBvYmplY3Qgbm93IGNvbnRhaW5zIHRoZSB0ZXh0IGZyb20gdGhlIDNyZCBwYWdlIGFzIGEgKipzaW5nbGUgbGFyZ2UgY2hhcmFjdGVyIHN0cmluZyoqLiBIb3dldmVyIHRoZSB0ZXh0IGlzIGRpZmZpY3VsdCB0byByZWFkIGJlY2F1c2Ugb2YgdGhlIGNvbHVtbiBzdHJ1Y3R1cmUgaW4gdGhlIFBERi4gTm93IGxldCdzIHRyeSB0byBncmFiIGp1c3QgdGhlIHRleHQgaW4gdGhlIHRhYmxlLg0KDQpPbmUgd2F5IHRvIGFwcHJvYWNoIHRoaXMgaXMgdG8gc3BsaXQgdGhlIHN0cmluZyBieSBzb21lIHBhdHRlcm4gdGhhdCB3ZSBub3RpY2UgaW4gdGhlIHRhYmxlLg0KDQpgYGB7ciwgZWNobyA9IEZBTFNFLCBvdXQud2lkdGggPSAiODAwcHgifQ0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoaGVyZTo6aGVyZSgiaW1nIiwgIlRhYmxlLnBuZyIpKQ0KYGBgDQoNCkFsbCB0aGUgcm93cyBvZiBpbnRlcmVzdCBvZiB0aGUgdGFibGUgYXBwZWFyIHRvIHN0YXJ0IHdpdGggdGhlIHdvcmQgYCJEaWV0ImAuIE1vcmVvdmVyLCBvbmx5IHRoZSBjYXBpdGFsaXplZCBmb3JtIG9mIHRoZSB3b3JkIGAiRGlldCJgIGFwcGVhcnMgdG8gYmUgd2l0aGluIHRoZSB0YWJsZSwgYW5kIGl0IGlzIG5vdCBwcmVzZW50IGluIHRoZSBwcmVjZWRpbmcgdGV4dCAoYWx0aG91Z2ggYCJkaWV0ImAgaXMpLiANCg0KYGBge3IsIGVjaG8gPSBGQUxTRSwgb3V0LndpZHRoID0gIjgwMHB4In0NCmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKGhlcmU6OmhlcmUoImltZyIsICJEaWV0X29uX3BhZ2UzLnBuZyIpKQ0KYGBgDQoNCg0KTGV0J3MgdXNlIHRoZSBgc3RyX3NwbGl0KClgIGZ1bmN0aW9uIG9mIHRoZSBgc3RyaW5ncmAgcGFja2FnZSB0byBzcGxpdCB0aGUgZGF0YSB3aXRoaW4gdGhlIG9iamVjdCBjYWxsZWQgYHBkZl90YWJsZWAgYnkgdGhlIHdvcmQgYCJEaWV0ImAuICBPbmx5IGxpbmVzIGZyb20gcGFnZSAzIHRoYXQgY29udGFpbiB0aGUgd29yZCBgIkRpZXQiYCB3aWxsIGJlIHNlbGVjdGVkIChhbmQgbm90IGAiZGlldCJgIGFzIHRoaXMgZnVuY3Rpb24gaXMgY2FzZS1zZW5zaXRpdmUpLiBFYWNoIHNlY3Rpb24gb2YgdGhlIHRleHQgdGhhdCBjb250YWlucyBgIkRpZXQiYCB3aWxsIGJlIHNwbGl0IGludG8gaW5kaXZpZHVhbCBwaWVjZXMgZXZlcnkgdGltZSB0aGUgd29yZCBgIkRpZXQiYCBvY2N1cnMgYW5kIHRoZSB3b3JkIGl0c2VsZiB3aWxsIGJlIHJlbW92ZWQuDQoNCkluIHRoaXMgY2FzZSB3ZSBhcmUgYWxzbyB1c2luZyB0aGUgbWFncml0dHIgYXNzaWdubWVudCBwaXBlIG9yIGRvdWJsZSBwaXBlIHRoYXQgbG9va3MgbGlrZSB0aGlzIGAlPD4lYCBvZiB0aGUgYG1hZ3JpdHRyYCBwYWNrYWdlLiBUaGlzIGFsbG93cyB1cyB1c2UgdGhlIGBwZGZfdGFibGVgIGRhdGEgYXMgaW5wdXQgdG8gdGhlIGxhdGVyIHN0ZXBzIGJ1dCBhbHNvIHJlYXNzaWduIHRoZSBvdXRwdXQgdG8gdGhlIHNhbWUgZGF0YSBvYmplY3QgbmFtZS4NCg0KYGBge3J9DQpwZGZfdGFibGUgJTw+JQ0KICBzdHJpbmdyOjpzdHJfc3BsaXQocGF0dGVybiA9ICJEaWV0IikNCmBgYA0KDQpVc2luZyB0aGUgYGJhc2U6OnN1bW1hcnkoKWAgYW5kIGBkcGx5cjo6Z2xpbXBzZSgpYCBmdW5jdGlvbiB3ZSBjYW4gc2VlIHRoYXQgd2UgY3JlYXRlZCBhIGxpc3Qgb2YgdGhlIHJvd3MgaW4gdGhlIHRhYmxlIHRoYXQgY29udGFpbmVkIHRoZSB3b3JkIGAiRGlldCJgLiBXZSBjYW4gc2VlIHRoYXQgd2Ugc3RhcnQgd2l0aCB0aGUgcm93IHRoYXQgY29udGFpbnMgYCJsb3cgaW4gZnJ1aXRzImAuIA0KDQpgYGB7cn0NCnBkZl90YWJsZSAlPiUNCiAgc3VtbWFyeSgpDQpgYGANCg0KYGBge3J9DQpwZGZfdGFibGUgJT4lDQogIGdsaW1wc2UoKQ0KYGBgDQoNCkluIG9yZGVyIHRvIGV4dHJhY3QgdGhlIHZhbHVlcyB0aGF0IHdlIHdhbnQgZnJvbSB0aGVzZSBjaGFyYWN0ZXIgc3RyaW5ncywgd2Ugd2lsbCB1c2Ugc29tZSBhZGRpdGlvbmFsIGZ1bmN0aW9ucyBmcm9tIHRoZSBgc3RyaW5ncmAgcGFja2FnZS4gUlN0dWRpbyBjcmVhdGVzIHJlYWxseSBoZWxwZnVsIGNoZWF0IHNoZWV0cyBsaWtlIHRoaXMgb25lIHdoaWNoIHNob3dzIHlvdSBhbGwgdGhlIG1ham9yIGZ1bmN0aW9ucyBpbiB0aGUgYHN0cmluZ3JgIHBhY2thZ2UuIFlvdSBjYW4gZG93bmxvYWQgb3RoZXJzIFtoZXJlXShodHRwczovL3JzdHVkaW8uY29tL3Jlc291cmNlcy9jaGVhdHNoZWV0cy8pe3RhcmdldD0iX2JsYW5rIn0uDQoNCmBgYHtyLCBlY2hvID0gRkFMU0UsIG91dC53aWR0aCA9ICI4MDBweCJ9DQprbml0cjo6aW5jbHVkZV9ncmFwaGljcyhoZXJlOjpoZXJlKCJpbWciLCAic3RyaW5ncy0xX3N0cl9zcGxpdC5wbmciKSkNCmBgYA0KDQpZb3UgY2FuIHNlZSB0aGF0IHdlIGNvdWxkIGhhdmUgYWxzbyB1c2VkIHRoZSBgc3RyX3NwbGl0X2ZpeGVkKClgIGZ1bmN0aW9uIHdoaWNoIHdvdWxkIGFsc28gc2VwYXJhdGUgdGhlIHN1YnN0cmluZ3MgaW50byBkaWZmZXJlbnQgY29sdW1ucyBvZiBhIG1hdHJpeCwgaG93ZXZlciB3ZSB3b3VsZCBuZWVkIHRvIGtub3cgdGhlIG51bWJlciBvZiBzdWJzdHJpbmdzIG9yIHBpZWNlcyB0aGF0IHdlIHdvdWxkIGxpa2UgcmV0dXJuZWQuDQoNCkZvciBtb3JlIGluZm9ybWF0aW9uIGFib3V0IGBzdHJfc3BsaXQoKWAgc2VlIFtoZXJlXShodHRwOi8vcmZ1bmN0aW9uLmNvbS9hcmNoaXZlcy8xNDk5KXt0YXJnZXQ9Il9ibGFuayJ9Lg0KDQpMZXQncyBzZXBhcmF0ZSB0aGUgdmFsdWVzIHdpdGhpbiB0aGUgbGlzdCB1c2luZyB0aGUgYmFzZSBgdW5saXN0YCBmdW5jdGlvbiwgdGhpcyB3aWxsIGFsbG93IHVzIHRvIGVhc2lseSBzZWxlY3QgdGhlIGRpZmZlcmVudCBzdWJzdHJpbmdzIHdpdGhpbiB0aGUgb2JqZWN0IGNhbGxlZCBgcGRmX3RhYmxlYC4NCg0KYGBge3J9DQpwZGZfdGFibGUgJTw+JQ0KICB1bmxpc3QoKQ0KYGBgDQoNCkl0J3MgaW1wb3J0YW50IHRvIHJlYWxpemUgdGhhdCB0aGUgZmlyc3Qgc3BsaXQgd2lsbCBzcGxpdCB0aGUgdGV4dCBiZWZvcmUgdGhlIGZpcnN0IG9jY3VycmVuY2Ugb2YgYCJEaWV0ImAgYXMgdGhlIGZpcnN0IHZhbHVlIGluIHRoZSBvdXRwdXQuIChUaGlzIGlzIHdoeSB0aGVyZSBhcmUgMTcgZWxlbWVudHMgaW4gYHBkZl90YWJsZWAgcmF0aGVyIHRoYW4gMTUsIHRoZSBudW1iZXIgb2Ygcm93cyBpbiB0aGUgdGFibGUuKSBXZSBjb3VsZCB1c2UgdGhlIGBmaXJzdCgpYCBmdW5jdGlvbiBvZiB0aGUgYGRwbHlyYCBwYWNrYWdlIHRvIGxvb2sgYXQgdGhpcyB2YWx1ZS4gSG93ZXZlciwgd2Ugd2lsbCBzdXBwcmVzcyB0aGUgb3V0cHV0IGFzIHRoaXMgaXMgcXVpdGUgbGFyZ2UuDQoNCmBgYHtyLCBldmFsID0gRkFMU0V9DQpkcGx5cjo6Zmlyc3QocGRmX3RhYmxlKQ0KYGBgDQoNCkluc3RlYWQgd2UgY2FuIHRha2UgYSBsb29rIGF0IHRoZSBzZWNvbmQgZWxlbWVudCBvZiB0aGUgbGlzdC4gdXNpbmcgdGhlIGBudGgoKWAgZnVuY3Rpb24gb2YgYGRwbHlyYC4NCg0KYGBge3J9DQpudGgocGRmX3RhYmxlLCAyKQ0KYGBgDQoNCkluZGVlZCB0aGlzIGxvb2tzIGxpa2UgdGhlIGZpcnN0IHJvdyBvZiBpbnRlcmVzdCBpbiBvdXIgdGFibGU6DQoNCmBgYHtyLGVjaG8gPSBGQUxTRSxvdXQud2lkdGg9ICI4MDBweCJ9DQprbml0cjo6aW5jbHVkZV9ncmFwaGljcyhoZXJlKCJpbWciLCAiZmlyc3Ryb3cucG5nIikpDQpgYGANCg0KDQpVc2luZyB0aGUgYGxhc3QoKWAgYW5kIHRoZSBgbnRoKClgIGZ1bmN0aW9ucyBvZiB0aGUgYGRwbHlyYCBwYWNrYWdlIHdlIGNhbiB0YWtlIGEgbG9vayBhdCB0aGUgbGFzdCB2YWx1ZXMgb2YgdGhlIGxpc3QuDQpgYGB7cn0NCiMgdG8gc2VlIHRoZSBzZWNvbmQgdG8gbGFzdCB2YWx1ZSB3ZSBjYW4gdXNlIG50aCgpDQojIHRoZSAtMiBzcGVjaWZpZXMgdGhhdCB3ZSB3YW50IHRoZSBzZWNvbmQtdG8tbGFzdCB2YWx1ZQ0KIyAtMyB3b3VsZCBiZSB0aGlyZC10by1sYXN0IGFuZCAtMSB3b3VsZCBiZSB0aGUgbGFzdCB2YWx1ZQ0KZHBseXI6Om50aChwZGZfdGFibGUsIC0yKQ0KDQojIHRvIHNlZSB0aGUgdmVyeSBsYXN0IHZhbHVlIHdlIGNhbiB1c2UgbGFzdCgpDQpkcGx5cjo6bGFzdChwZGZfdGFibGUpDQpgYGANCg0KYGBge3IsIGVjaG8gPSBGQUxTRSwgb3V0LndpZHRoID0gIjgwMHB4In0NCmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKGhlcmU6OmhlcmUoImltZyIsICJlbmRfb2ZfdGFibGUucG5nIikpDQpgYGANCg0KDQpXZSBkb24ndCBuZWVkIHRoaXMgcGFydCBvZiB0aGUgdGFibGUgb3IgdGhlIHRleHQgYmVmb3JlIHRoZSB0YWJsZSBpZiB3ZSBqdXN0IHdhbnQgdGhlIGNvbnN1bXB0aW9uIHJlY29tbWVuZGF0aW9ucy4gDQoNClNvIHdlIHdpbGwgc2VsZWN0IHRoZSBzZWNvbmQgdGhyb3VnaCB0aGUgc2Vjb25kLXRvLWxhc3Qgb2YgdGhlIHN1YnN0cmluZ3MuIFNpbmNlIHdlIGhhdmUgc2V2ZW50ZWVuIHN1YnN0cmluZ3MsIHdlIHdpbGwgc2VsZWN0IHRoZSBzZWNvbmQgdGhyb3VnaCB0aGUgc2l4dGVlbnRoLiBIb3dldmVyIGEgYmV0dGVyIHdheSB0byBkbyB0aGlzIHJhdGhlciB0aGFuIHNlbGVjdGluZyBieSBpbmRleCwgd291bGQgYmUgdG8gc2VsZWN0IHBocmFzZXMgdGhhdCBhcmUgdW5pcXVlIHRvIHRoZSB0ZXh0IHdpdGhpbiB0aGUgdGFibGUgdGhhdCB3ZSB3YW50LiBXZSB3aWxsIHVzZSB0aGUgYHN0cl9zdWJzZXQoKWAgZnVuY3Rpb24gb2YgYHN0cmluZ3JgIHBhY2thZ2UgdG8gc2VsZWN0IHRoZSB0YWJsZSByb3dzIHdpdGggY29uc3VtcHRpb24gZ3VpZGVsaW5lcy4gIE1vc3Qgb2YgdGhlIHJvd3MgaGF2ZSB0aGUgcGhyYXNlICJNZWFuIGRhaWx5IGNvbnN1bXB0aW9uIiwgaG93ZXZlciwgdGhlcmUgYXJlIG90aGVyIHBocmFzZXMgZm9yIHNvbWUgb2YgdGhlIHJvd3MsIGluY2x1ZGluZyAiTWVhbiBkYWlseSBpbnRha2UiIGFuZCAiMjQgaCBzb2RpdW0iLiBTbyB3ZSB3aWxsIHN1YnNldCBmb3IgZWFjaCBvZiB0aGVzZSBwaHJhc2VzLg0KDQpgYGB7cn0NCiMgb25lIGNvdWxkIHN1YnNldCB0aGUgcGRmX3RhYmxlIGxpa2UgdGhpczoNCiMgcGRmX3RhYmxlIDwtIHBkZl90YWJsZVsyOjE2XQ0KDQpwZGZfdGFibGUgJTw+JQ0KICBzdHJfc3Vic2V0KHBhdHRlcm4gPSAiTWVhbiBkYWlseSBjb25zdW1wdGlvbnxNZWFuIGRhaWx5IGludGFrZXwyNCBoIikNCmBgYA0KDQpOb3RpY2UgdGhhdCB3ZSBzZXBhcmF0ZSB0aGUgZGlmZmVyZW50IHBhdHRlcm5zIHRvIGxvb2sgZm9yIHVzaW5nIHZlcnRpY2FsIGJhciBjaGFyYWN0ZXIgYCJ8ImAgYW5kIHRoYXQgYWxsIG9mIHRoZSBwYXR0ZXJucyBhcmUgd2l0aGluIHF1b3RhdGlvbiBtYXJrcyB0b2dldGhlci4NCg0KIyMjIyB7LnRoaW5rX3F1ZXN0aW9uX2Jsb2NrfQ0KPHU+UXVlc3Rpb24gb3Bwb3J0dW5pdHk6PC91PiANCg0KMSkgV2hhdCBvdGhlciBzdHJpbmcgcGF0dGVybnMgY291bGQgeW91IHVzZSB0byBzdWJzZXQgdGhlIHJvd3Mgb2YgdGhlIHRhYmxlIHRoYXQgd2Ugd2FudD8NCg0KMikgV2h5IG1pZ2h0IGl0IGJlIGJldHRlciB0byBzdWJzZXQgYmFzZWQgb24gdGhlIHRleHQgcmF0aGVyIHRoYW4gdGhlIGluZGV4Pw0KDQojIyMjDQoNCg0KTm93IHRoZSBmaXJzdCByb3cgaXMgd2hhdCB3ZSB3YW50Og0KYGBge3J9DQpmaXJzdChwZGZfdGFibGUpDQpgYGANCg0KQW5kIHRoZSBsYXN0IHJvdyBpcyB3aGF0IHdlIHdhbnQ6DQpgYGB7cn0NCmxhc3QocGRmX3RhYmxlKQ0KYGBgDQoNCkF0IHRoaXMgcG9pbnQsIHdlIGhhdmUgYSBiZXR0ZXIgbG9vayBhdCB0aGUgY3VycmVudCByZXByZXNlbnRhdGlvbiBvZiB0aGUgdGFibGUgZGF0YSBpbiBSLCBhbmQgd2UgbWlnaHQgbm90aWNlIHNvbWV0aGluZyB0aGF0IHdpbGwgbmVlZCB0byBiZSBmaXhlZC4gSW4gdGhlIHN0cmluZyBhYm92ZSwgdGhlIGRlY2ltYWwgcG9pbnRzIGZyb20gdGhlIFBERiBhcmUgYmVpbmcgcmVjb2duaXplZCBhcyBzb21ldGhpbmcgY2FsbGVkIGFuIGludGVycHVuY3QgaW5zdGVhZCBvZiBhIHBlcmlvZCBvciBkZWNpbWFsLiBBbiBpbnRlcnB1bmN0IGlzIGEgY2VudGVyZWQgZG90LCBhcyBvcHBvc2VkIHRvIGEgcGVyaW9kIG9yIGRlY2ltYWwgdGhhdCBpcyBhbGlnbmVkIHRvIHRoZSBib3R0b20gb2YgdGhlIGxpbmUuDQoNClRoZSBpbnRlcnB1bmN0IHdhcyBwcmV2aW91c2x5IHVzZWQgdG8gc2VwYXJhdGUgd29yZHMgaW4gY2VydGFpbiBsYW5ndWFnZXMsIGxpa2UgYW5jaWVudCBMYXRpbi4NCg0KDQo8cCBhbGlnbj0iY2VudGVyIj4NCiAgPGltZyB3aWR0aD0iNDAwIiBzcmM9Imh0dHBzOi8vd3d3LnlvdXJkaWN0aW9uYXJ5LmNvbS9pbWFnZS9hcnRpY2xlcy8zNDE3LkxhdGluLmpwZyI+DQo8L3A+DQoNCiMjIyMjIyBbW3NvdXJjZV0oaHR0cHM6Ly93d3cueW91cmRpY3Rpb25hcnkuY29tL2ltYWdlL2FydGljbGVzLzM0MTcuTGF0aW4uanBnKV0NCg0KWW91IGNhbiBwcm9kdWNlIGFuIGludGVycHVuY3Qgb24gYSBNYWMgbGlrZSB0aGlzOg0KDQoNCjxwIGFsaWduPSJjZW50ZXIiPg0KICA8aW1nIHdpZHRoPSI0MDAiIHNyYz0iaHR0cHM6Ly93d3cuc2hvcnR0dXRvcmlhbHMuY29tL21hYy1vcy1zcGVjaWFsLWNoYXJhY3RlcnMtc2hvcnRjdXRzL2ltYWdlcy9taWRkbGUtZG90LnBuZyI+DQo8L3A+DQoNCiMjIyMjIyBbW3NvdXJjZV0oaHR0cHM6Ly93d3cuc2hvcnR0dXRvcmlhbHMuY29tL21hYy1vcy1zcGVjaWFsLWNoYXJhY3RlcnMtc2hvcnRjdXRzL21pZGRsZS1kb3QuaHRtbCldDQoNCg0KSXQgaXMgaW1wb3J0YW50IHRvIHJlcGxhY2UgdGhlc2UgZm9yIGxhdGVyIHdoZW4gd2Ugd2FudCB0aGVzZSB2YWx1ZXMgdG8gYmUgY29udmVydGVkIGZyb20gY2hhcmFjdGVyIHN0cmluZ3MgdG8gbnVtZXJpYy4gV2Ugd2lsbCBhZ2FpbiB1c2UgdGhlIGBzdHJpbmdyYCBwYWNrYWdlLiBUaGlzIHRpbWUgd2Ugd2lsbCB1c2UgdGhlIGBzdHJfcmVwbGFjZV9hbGwoKWAgZnVuY3Rpb24gd2hpY2ggcmVwbGFjZXMgYWxsIGluc3RhbmNlcyBvZiBhIHBhdHRlcm4gaW4gYW4gaW5kaXZpZHVhbCBzdHJpbmcuIEluIHRoaXMgY2FzZSB3ZSB3YW50IHRvIHJlcGxhY2UgYWxsIGluc3RhbmNlcyBvZiB0aGUgaW50ZXJwdW5jdCB3aXRoIGEgZGVjaW1hbCBwb2ludC4NCg0KDQpgYGB7cix9DQpwZGZfdGFibGUgJTw+JQ0KICBzdHJpbmdyOjpzdHJfcmVwbGFjZV9hbGwoDQogICAgcGF0dGVybiA9ICLCtyIsDQogICAgcmVwbGFjZW1lbnQgPSAiLiINCiAgKQ0KbGFzdChwZGZfdGFibGUpDQpgYGANCg0KTG9va3MgZ29vZCENCg0KTm93IHdlIHdpbGwgdHJ5IHRvIHNwbGl0IHRoZSBzdHJpbmdzIGZvciBlYWNoIHJvdyBiYXNlZCBvbiB0aGUgcHJlc2VuY2Ugb2YgdHdvIHNwYWNlcyB0byBjcmVhdGUgdGhlIGNvbHVtbnMgb2YgdGhlIHRhYmxlLCBhcyB0aGVyZSBhcHBlYXJzIHRvIGJlIG1vcmUgdGhhbiBvbmUgc3BhY2UgYmV0d2VlbiB0aGUgY29sdW1ucy4gVGhlIHJlc3VsdGluZyBzdWJzdHJpbmdzIHdpbGwgYmUgc2VwYXJhdGVkIGJ5IHF1b3Rlcy4NCg0KRm9yIGFkZGl0aW9uYWwgZGV0YWlscywgdGhlIHNlY29uZCBwYWdlIG9mIHRoZSBgc3RyaW5ncmAgY2hlYXQgc2hlZXQgaGFzIG1vcmUgaW5mb3JtYXRpb24gYWJvdXQgdXNpbmcgIlNwZWNpYWwgQ2hhcmFjdGVycyIgaW4gYHN0cmluZ3JgLiBGb3IgZXhhbXBsZSBgXFxzYCBpcyBpbnRlcnByZXRlZCBhcyBhIHNwYWNlIGFzIHRoZSBgXFxgIGluZGljYXRlcyB0aGF0IHRoZSBgc2Agc2hvdWxkIGJlIGludGVycHJldGVkIGFzIGEgc3BlY2lhbCBjaGFyYWN0ZXIgYW5kIG5vdCBzaW1wbHkgdGhlIGxldHRlciBzLiAgVGhlIGB7Mix9YCBpbmRpY2F0ZXMgdHdvIG9yIG1vcmUgc3BhY2VzLCB3aGlsZSBgezJ9YCB3b3VsZCBpbmRpY2F0ZSBleGFjdGx5IHR3byBzcGFjZXMuDQoNCmBgYHtyLCBlY2hvID0gRkFMU0Usb3V0LndpZHRoID0gIjgwMHB4In0NCmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKGhlcmUoImltZyIsICJzdHJpbmdzLTJfaGlnaGxpZ2h0LnBuZyIpKQ0KYGBgDQoNCiMjIyMgey5zY3JvbGxhYmxlIH0NCmBgYHtyfQ0KdGFibGVfc3BsaXQgPC0gc3RyX3NwbGl0KA0KICBzdHJpbmcgPSBwZGZfdGFibGUsDQogIHBhdHRlcm4gPSAiXFxzezIsfSINCikNCmdsaW1wc2UodGFibGVfc3BsaXQpICMgc2Nyb2xsIHRoZSBvdXRwdXQhDQpgYGANCiMjIyMNCg0KTm93IHdlIGNhbiBzZWUgdGhhdCBlYWNoIG9mIG91ciAxNSBzdHJpbmdzIGhhcyBiZWVuIHNwbGl0IGludG8gcGllY2VzLCBidXQgdW5mb3J0dW5hdGVseSwgaXQgd2FzIG5vdCBjb21wbGV0ZWx5IGNvbnNpc3RlbnQgYWNyb3NzIGRpZXRhcnkgZmFjdG9ycy4gV2h5IGRpZCB0aGlzIGhhcHBlbj8gSWYgd2UgbG9vayBjbG9zZWx5LCB3ZSBjYW4gc2VlIHRoYXQgdGhlIHN1Z2FyLXN3ZWV0ZW5lZCBiZXZlcmFnZSBhbmQgdGhlIHNlYWZvb2QgY2F0ZWdvcnkgaGFkIG9ubHkgb25lIHNwYWNlIGJldHdlZW4gdGhlIGZpcnN0IGFuZCBzZWNvbmQgY29sdW1ucy4gVGhlc2UgYXJlIHRoZSBjb2x1bW5zIGFib3V0IHRoZSBkaWV0YXJ5IGNhdGVnb3J5IGFuZCB0aGUgb25lIHRoYXQgZGVzY3JpYmVzIGluIG1vcmUgZGV0YWlsIHdoYXQgdGhlIGNvbnN1bXB0aW9uIHN1Z2dlc3Rpb24gaXMgYWJvdXQuDQoNClRoZSB2YWx1ZXMgZm9yIHRoZXNlIHR3byBjb2x1bW5zIGFwcGVhciB0byBiZSB0b2dldGhlciBzdGlsbCBpbiB0aGUgc2FtZSBzdWJzdHJpbmcgZm9yIHRoZXNlIHR3byBjYXRlZ29yaWVzLiBXZSBjYW4gc2VlIHRoaXMgYmVjYXVzZSB0aGVyZSBhcmUgbm8gcXVvdGF0aW9uIG1hcmtzIGFkamFjZW50IHRvIHRoZSB3b3JkIGAiTWVhbiJgLg0KDQpIZXJlIHlvdSBjYW4gc2VlIGhvdyB0aGUgbmV4dCBzdWJzdHJpbmcgc2hvdWxkIGhhdmUgc3RhcnRlZCB3aXRoIHRoZSB3b3JkIGAiTWVhbiJgIGJ5IHRoZSBuZXcgaW5jbHVzaW9uIG9mIGEgcXVvdGF0aW9uIG1hcmsgYCJgLiBUaGUgcmVkIHJlY3RhbmdsZXMgaW5kaWNhdGUgdGhlIHByb2JsZW1hdGljIHN1YnN0cmluZ3MsIHdoaWxlIHRoZSBncmVlbiByZWN0YW5nbGVzIHNob3cgZXhhbXBsZXMgd2hlcmUgdGhlIHNwbGl0IHdvcmtlZCBjb3JyZWN0bHkuDQoNCmBgYHtyLCBlY2hvID0gRkFMU0UsIG91dC53aWR0aCA9ICI3MDBweCJ9DQprbml0cjo6aW5jbHVkZV9ncmFwaGljcyhoZXJlKCJpbWciLCAic3Vic3RyaW5nX3NlcC5wbmciKSkNCmBgYA0KDQoNCldlIGNhbiBhZGQgYW4gZXh0cmEgc3BhY2UgaW4gZnJvbnQgb2YgdGhlIHdvcmQgYCJNZWFuImAgZm9yIHRoZXNlIHBhcnRpY3VsYXIgY2F0ZWdvcmllcyBhbmQgdGhlbiB0cnkgc3BsaXR0aW5nIGFnYWluLg0KDQpTaW5jZSB3ZSBvcmlnaW5hbGx5IHNwbGl0IGJhc2VkIG9uIHR3byBvciBtb3JlIHNwYWNlcywgd2UgY2FuIGp1c3QgYWRkIGEgc3BhY2UgaW4gZnJvbnQgb2YgdGhlIHdvcmQgIk1lYW4iIGZvciBhbGwgdGhlIGBwZGZfdGFibGVgIHN0cmluZ3MgYW5kIHRoZW4gdHJ5IHN1YnNldHRpbmcgYWdhaW4uIFdlIGNhbiB1c2UgdGhlIGBzdHJfd2hpY2goKWAgZnVuY3Rpb24gb2YgdGhlIGBzdHJpbmdyYCBwYWNrYWdlIHRvIGZpbmQgdGhlIGluZGV4IG9mIHRoZXNlIHBhcnRpY3VsYXIgY2FzZXMuDQoNCmBgYHtyfQ0KcGRmX3RhYmxlICU+JQ0KICBzdHJfd2hpY2gocGF0dGVybiA9ICJzZWFmb29kfHN1Z2FyIikNCmBgYA0KDQpIZXJlIHdlIGNhbiB1c2UgdGhlIGBzdHJfc3Vic2V0KClgIGZ1bmN0aW9uIG9mIHRoZSBgc3RyaW5ncmAgcGFja2FnZSB0byBzZWUganVzdCB0aGUgc3RyaW5ncyB0aGF0IG1hdGNoIHRoZXNlIHBhdHRlcm5zIHdpdGhpbiBgcGRmX3RhYmxlYDoNCmBgYHtyfQ0KcGRmX3RhYmxlICU+JQ0KICBzdHJfc3Vic2V0KHBhdHRlcm4gPSAic2VhZm9vZHxzdWdhciIpDQpgYGANCg0KVGhpcyBpcyBlcXVpdmFsZW50IHRvIHVzaW5nIHRoZSBgc3RyX3doaWNoKClgIGZ1bmN0aW9uIHdpdGggYFtdYDoNCmBgYHtyLCBldmFsID0gRkFMU0V9DQpwZGZfdGFibGVbc3RyX3doaWNoKHBkZl90YWJsZSwgcGF0dGVybiA9ICJzZWFmb29kfHN1Z2FyIildDQpgYGANCg0KTm93IHdlIGNhbiByZXBsYWNlIHRoZXNlIHZhbHVlcyB3aXRoaW4gdGhlIHBkZl90YWJsZSBvYmplY3QgYWZ0ZXIgYWRkaW5nIGEgc3BhY2UgaW4gZnJvbnQgb2YgIk1lYW4iOg0KDQpgYGB7cn0NCnBkZl90YWJsZVtzdHJfd2hpY2gocGRmX3RhYmxlLA0KICBwYXR0ZXJuID0NCiAgICAic2VhZm9vZHxzdWdhciINCildIDwtIHN0cl9yZXBsYWNlKA0KICBzdHJpbmcgPSBwZGZfdGFibGVbc3RyX3doaWNoKHBkZl90YWJsZSwNCiAgICBwYXR0ZXJuID0NCiAgICAgICJzZWFmb29kfHN1Z2FyIg0KICApXSwNCiAgcGF0dGVybiA9ICJNZWFuIiwNCiAgcmVwbGFjZW1lbnQgPSAiIE1lYW4iDQopDQpgYGANCg0KQW5kIG5vdyB3ZSBjYW4gdHJ5IHNwbGl0dGluZyBhZ2FpbiBieSB0d28gb3IgbW9yZSBzcGFjZXM6DQpgYGB7cn0NCnRhYmxlX3NwbGl0IDwtIHN0cl9zcGxpdChwZGZfdGFibGUsIHBhdHRlcm4gPSAiXFxzezIsfSIpDQpgYGANCg0KV2UgY291bGQgYWxzbyBqdXN0IGFkZCBhIHNwYWNlIGluIGZyb250IG9mIGFsbCB0aGUgdmFsdWVzIG9mICJNZWFuIiBpbiBgcGRmX3RhYmxlYCBzaW5jZSB0aGUgc3BsaXQgd2FzIHBlcmZvcm1lZCBiYXNlZCBvbiB0d28gb3IgbW9yZSBzcGFjZXMuIFRodXMgdGhlIG90aGVyIGVsZW1lbnRzIGluIGBwZGZfdGFibGVgIHdvdWxkIGFsc28gYmUgc3BsaXQganVzdCBhcyBiZWZvcmUgZGVzcGl0ZSB0aGUgYWRkaXRpb25hbCBzcGFjZS4NCg0KYGBge3IsIGV2YWwgPSBGQUxTRX0NCnBkZl90YWJsZSA8LSBwZGZfdGFibGUgJT4lDQogIHN0cmluZ3I6OnN0cl9yZXBsYWNlKA0KICAgIHBhdHRlcm4gPSAiTWVhbiIsDQogICAgcmVwbGFjZW1lbnQgPSAiIE1lYW4iDQogICkNCnRhYmxlX3NwbGl0IDwtIHN0cl9zcGxpdChwZGZfdGFibGUsIHBhdHRlcm4gPSAiXFxzezIsfSIpDQpgYGANCg0KIyMjIyB7LnNjcm9sbGFibGUgfQ0KYGBge3J9DQojIHNjcm9sbCB0aGUgb3V0cHV0IQ0KZ2xpbXBzZSh0YWJsZV9zcGxpdCkNCmBgYA0KIyMjIw0KDQpMb29rcyBiZXR0ZXIhDQoNCldlIHdhbnQganVzdCB0aGUgZmlyc3QgKHRoZSBmb29kICoqY2F0ZWdvcnkqKikgYW5kIHRoaXJkIGNvbHVtbiAodGhlIG9wdGltYWwgY29uc3VtcHRpb24gKiphbW91bnQqKiBzdWdnZXN0ZWQpIGZvciBlYWNoIHJvdyBpbiB0aGUgdGFibGUuIEhvd2V2ZXIsIHRoZSB0YWJsZSBpcyBjdXJyZW50bHkgc3RvcmVkIGFzIGEgbGlzdCBvZiBjaGFyYWN0ZXIgdmVjdG9ycywgc28gaXQgaXMgbm90IHF1aXRlIHNvIHNpbXBsZSB0byBleHRyYWN0IHRoZXNlIHZhbHVlcy4NCg0KV2UgY2FuIHVzZSB0aGUgYG1hcGAgZnVuY3Rpb24gb2YgdGhlIGBwdXJycmAgcGFja2FnZSB0byBhY2NvbXBsaXNoIHRoaXMuDQoNClRoZSBgbWFwYCBmdW5jdGlvbiBhbGxvd3MgdXMgdG8gcGVyZm9ybSB0aGUgc2FtZSBhY3Rpb24gbXVsdGlwbGUgdGltZXMgYWNyb3NzIGVhY2ggZWxlbWVudCB3aXRoaW4gYW4gb2JqZWN0LCBpbiB0aGlzIGNhc2UsIGEgbGlzdC4NCg0KVGhlIGZvbGxvd2luZyB3aWxsIGFsbG93IHVzIHRvIHNlbGVjdCB0aGUgZmlyc3Qgb3IgdGhpcmQgc3Vic3RyaW5nIGZyb20gZWFjaCBlbGVtZW50IG9mIHRoZSBgcGRmX3RhYmxlYCBvYmplY3QuDQoNCmBgYHtyfQ0KY2F0ZWdvcnkgPC0gbWFwKHRhYmxlX3NwbGl0LCAxKQ0KYW1vdW50IDwtIG1hcCh0YWJsZV9zcGxpdCwgMykNCmhlYWQoY2F0ZWdvcnkpDQpoZWFkKGFtb3VudCkNCmBgYA0KDQpOb3cgd2Ugd2lsbCBjcmVhdGUgYSBgdGliYmxlYCB1c2luZyB0aGlzIGRhdGEuIEhvd2V2ZXIsIGN1cnJlbnRseSBib3RoIGBjYXRlZ29yeWAgYW5kIGBhbW91bnRgIGFyZSBvZiBjbGFzcyBgbGlzdGAuIFRvIGNyZWF0ZSBhIGB0aWJibGVgIHdlIG5lZWQgdG8gdW5saXN0IHRoZSBkYXRhIHRvIGNyZWF0ZSB2ZWN0b3JzLg0KDQpgYGB7cn0NCmNsYXNzKGNhdGVnb3J5KQ0KY2F0ZWdvcnkgJTw+JSB1bmxpc3QoKQ0KYW1vdW50ICU8PiUgdW5saXN0KCkNCmNsYXNzKGNhdGVnb3J5KQ0KYGBgDQoNCiMjIyMgey5zY3JvbGxhYmxlIH0NCmBgYHtyfQ0KY2F0ZWdvcnkNCmFtb3VudA0KYGBgDQojIyMjDQoNCldlIGNvdWxkIGhhdmUgZG9uZSBhbGwgb2YgdGhpcyBhdCBvbmNlIGluIG9uZSBjb21tYW5kIGxpa2UgdGhpczoNCg0KYGBge3IsIGV2YWwgPSBGQUxTRX0NCmNhdGVnb3J5IDwtIHVubGlzdChtYXAodGFibGVfc3BsaXQsIDEpKQ0KYW1vdW50IDwtIHVubGlzdChtYXAodGFibGVfc3BsaXQsIDMpKQ0KYGBgDQoNCk5vdyB3ZSB3aWxsIGNyZWF0ZSBhIGB0aWJibGVgLCB3aGljaCBpcyBhbiBpbXBvcnRhbnQgZGF0YSBmcmFtZSBzdHJ1Y3R1cmUgaW4gdGhlIHRpZHl2ZXJzZSB3aGljaCBhbGxvd3MgdXMgdG8gdXNlIG90aGVyIHBhY2thZ2VzIGluIHRoZSB0aWR5dmVyc2Ugd2l0aCBvdXIgZGF0YS4NCg0KV2Ugd2lsbCBuYW1lIG91ciBgdGliYmxlYCBjb2x1bW5zIG5vdyBhcyB3ZSBjcmVhdGUgb3VyIGB0aWJibGVgIHVzaW5nIHRoZSBgdGliYmxlKClgIGZ1bmN0aW9uIG9mIGJvdGggdGhlIGB0aWR5cmAgYW5kIHRoZSBgdGliYmxlYCBwYWNrYWdlcywgYXMgbmFtZXMgYXJlIHJlcXVpcmVkIGluIHRpYmJsZXMuDQoNCmBgYHtyfQ0KZ3VpZGVsaW5lcyA8LSB0aWJibGU6OnRpYmJsZSgNCiAgY2F0ZWdvcnkgPSBjYXRlZ29yeSwNCiAgYW1vdW50ID0gYW1vdW50DQopDQpndWlkZWxpbmVzDQpgYGANCg0KTG9va2luZyBwcmV0dHkgZ29vZCENCg0KIyMjICoqU2VwYXJhdGluZyB2YWx1ZXMgd2l0aGluIGEgdmFyaWFibGUqKg0KKioqDQoNClJlY2FsbCB0aGF0IHRoZSBtYWluIGdvYWwgb2YgdGhpcyBkYXRhIHdyYW5nbGluZyBpcyB0byBleHRyYWN0IHRoZSBvcHRpbWFsIGludGFrZSBsZXZlbCBmb3IgZWFjaCBkaWV0YXJ5IGZhY3Rvci4gU28gd2hpbGUgd2UgaGF2ZSBtYW5hZ2VkIHRvIHB1bGwgYW5kIG9yZ2FuaXplIHRoZSBkYXRhIGZyb20gdGhlIHBkZiB0YWJsZSwgd2UgbmVlZCB0byBmdXJ0aGVyIHByb2Nlc3MgdGhlIHJlc3VsdHMgdG8gaXNvbGF0ZSB0aGlzIG51bWVyaWMgdmFsdWUuDQoNCkRvIHRvIHRoaXMsIHdlIHdhbnQgdG8gc2VwYXJhdGUgdGhlIGRpZmZlcmVudCBudW1iZXJzIHdpdGhpbiB0aGUgYGFtb3VudGAgY29sdW1uLCB0byBpc29sYXRlIHRoZSBvcHRpbWFsIGFtb3VudCwgYW5kIHRoZSBvcHRpbWFsIHJhbmdlLCBhbmQgZXZlbnR1YWxseSBjb252ZXJ0IHRoZW0gdG8gbnVtZXJpYyB2YWx1ZXMuDQoNClJlY2FsbCB3aGF0IHRoZSBvcmlnaW5hbCB0YWJsZSBsb29rZWQgbGlrZToNCmBgYHtyLCBlY2hvID0gRkFMU0UsIG91dC53aWR0aCA9ICI4MDBweCJ9DQprbml0cjo6aW5jbHVkZV9ncmFwaGljcyhoZXJlKCJpbWciLCAiZmlyc3Ryb3cucG5nIikpDQpgYGANCg0KV2UgY2FuIHVzZSB0aGUgYHRpZHlyOjpzZXBhcmF0ZSgpYCBmdW5jdGlvbiB0byBzZXBhcmF0ZSB0aGUgZGF0YSB3aXRoaW4gdGhlIGFtb3VudCBjb2x1bW4gaW50byB0aHJlZSBuZXcgY29sdW1ucyBiYXNlZCBvbiB0aGUgb3B0aW1hbCBsZXZlbCBhbmQgdGhlIG9wdGltYWwgcmFuZ2UuIFdlIGNhbiBzZXBhcmF0ZSB0aGUgdmFsdWVzIGJhc2VkIG9uIHRoZSBvcGVuIHBhcmVudGhlc2VzIGAiKCJgIGFuZCB0aGUgbG9uZyBkYXNoIGAi4oCTImAgY2hhcmFjdGVycy4gQWdhaW4gd2Ugd2lsbCB1c2UgdGhlIGJhciBgInwiYCB0byBpbmRpY2F0ZSB0aGF0IHdlIHdhbnQgdG8gc2VwYXJhdGUgYnkgZWl0aGVyIGNoYXJhY3Rlci4NCg0KYGBge3J9DQojIFRoZSBmaXJzdCBjb2x1bW4gd2lsbCBiZSBjYWxsZWQgb3B0aW1hbA0KIyBJdCB3aWxsIGNvbnRhaW4gdGhlIDFzdCBwYXJ0IG9mIHRoZSBhbW91bnQgY29sdW1uIGRhdGEgYmVmb3JlIHRoZSAiKCINCiMgVGhlIDJuZCBjb2x1bW4gd2lsbCBiZSBjYWxsZWQgbG93ZXINCiMgSXQgd2lsbCBjb250YWluIHRoZSBkYXRhIGFmdGVyIHRoZSAiKCINCiMgVGhlIDNyZCBjb2x1bW4gd2lsbCBiZSBjYWxsZWQgdXBwZXINCiMgSXQgd2lsbCBjb250YWluIHRoZSAybmQgcGFydCBvZiB0aGUgZGF0YSBiYXNlZCBvbiB0aGUgIuKAkyINCiMgVGhlICJcXCIgYXJlIG5lY2Vzc2FyeSAtIHdlIHdpbGwgZXhwbGFpbiB2ZXJ5IHNvb24NCg0KZ3VpZGVsaW5lcyAlPD4lDQogIHRpZHlyOjpzZXBhcmF0ZShhbW91bnQsDQogICAgYygib3B0aW1hbCIsICJsb3dlciIsICJ1cHBlciIpLA0KICAgIHNlcCA9ICJcXCh84oCTIg0KICApDQoNCmd1aWRlbGluZXMNCmBgYA0KDQoNCkxldCdzIGFsc28gY3JlYXRlIGEgbmV3IHZhcmlhYmxlL2NvbHVtbiBpbiBvdXIgdGliYmxlIHRoYXQgaW5kaWNhdGVzIHRoZSBkaXJlY3Rpb24gb2Ygb3Zlci0gb3IgdW5kZXItY29uc3VtcHRpb24gdGhhdCBjYW4gYmUgaGFybWZ1bCBmb3IgZWFjaCBkaWV0YXJ5IGZhY3Rvci4NCg0KYGBge3J9DQpndWlkZWxpbmVzICU8PiUNCiAgc2VwYXJhdGUoY2F0ZWdvcnksIGMoImRpcmVjdGlvbiIsICJmb29kIiksIHNlcCA9ICIgaW4gIikNCmd1aWRlbGluZXMNCmBgYA0KDQpJZiB3ZSB3YW50ZWQgdG8gcmVtb3ZlIHRoZSBkaXJlY3Rpb24gdmFyaWFibGUgd2UgY291bGQgdXNlIHRoZSBgbW9kaWZ5X2F0KClgIGZ1bmN0aW9uIG9mIHRoZSBgcHVycnJgIHBhY2thZ2U6DQoNCmBgYHtyLGV2YWwgPSBGQUxTRX0NCmd1aWRlbGluZXMgJT4lIHB1cnJyOjptb2RpZnlfYXQoImRpcmVjdGlvbiIsIH5OVUxMKQ0KYGBgDQoNCg0KIyMjICoqRGF0YSBjbGVhbmluZyB3aXRoIHJlZ3VsYXIgZXhwcmVzc2lvbnMqKg0KKioqDQoNCk9LLCBsb29raW5nIGJldHRlciwgYnV0IHdlIHN0aWxsIG5lZWQgYSBiaXQgb2YgY2xlYW5pbmcgdG8gcmVtb3ZlIHN5bWJvbHMgYW5kIGV4dHJhIHdvcmRzIGZyb20gdGhlIGNvbHVtbnMuIFNvbWUgb2YgdGhlIGV4dHJhIHN5bWJvbHMgaW5jbHVkZTogYCIlImAsIGAiKSJgIGFuZCB0aGUgYCIqImAuDQoNClRoZSBgIioiYCBhbmQgdGhlIGAiKSJgIGFyZSB3aGF0IHdlIGNhbGwgbWV0YWNoYXJhY3RlcnMgb3IgW3JlZ3VsYXIgZXhwcmVzc2lvbnNdKGh0dHBzOi8vd3d3LnItYmxvZ2dlcnMuY29tL3JlZ3VsYXItZXhwcmVzc2lvbnMtZXZlcnktci1wcm9ncmFtbWVyLXNob3VsZC1rbm93Lyl7dGFyZ2V0PSJfYmxhbmsifS4gVGhlc2UgYXJlIGNoYXJhY3RlcnMgdGhhdCBoYXZlIHNwZWNpYWwgbWVhbmluZ3MuDQoNCmBgYHtyLCBlY2hvID0gRkFMU0UsIG91dC53aWR0aCA9ICI4MDBweCJ9DQprbml0cjo6aW5jbHVkZV9ncmFwaGljcyhoZXJlKCJpbWciLCAiUmVnRXhDaGVhdHNoZWV0LnBuZyIpKQ0KYGBgDQoNCk5vdyB3ZSBuZWVkIHRoZSBgIlxcImAgdG8gaW5kaWNhdGUgdGhhdCB3ZSB3YW50IHRoZXNlIGNoYXJhY3RlcnMgdG8gYmUgbWF0Y2hlZCBleGFjdGx5IGFuZCBub3QgaW50ZXJwcmV0ZWQgYXMgdGhlIG1lYW5pbmcgb2YgdGhlIHN5bWJvbC4gUmVjYWxsIHRoYXQgd2UgdXNlZCBgIlxcKHzigJMiYCBlYXJsaWVyLg0KDQpTZWUgW2hlcmVdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9zdHJpbmdyL3ZpZ25ldHRlcy9yZWd1bGFyLWV4cHJlc3Npb25zLmh0bWwpe3RhcmdldD0iX2JsYW5rIn0gZm9yIG1vcmUgaW5mbyBhYm91dCByZWd1bGFyIGV4cHJlc3Npb25zIGluIFIuIA0KDQoqKioNCg0KPGRldGFpbHM+IDxzdW1tYXJ5PiBDbGljayBoZXJlIGZvciBhIHNpbXBsZSBleGFtcGxlIG9mIHJlZ3VsYXIgZXhwcmVzc2lvbnMgdXNpbmcgdGhlIGBzdHJfY291bnQoKWAgZnVuY3Rpb24gb2YgdGhlIGBzdHJpbmdyYCBwYWNrYWdlIDwvc3VtbWFyeT4NCg0KVGhlIGBzdHJfY291bnQoKWAgZnVuY3Rpb24gY291bnRzIHRoZSBudW1iZXIgb2YgaW5zdGFuY2VzIG9mIGEgY2hhcmFjdGVyIHN0cmluZy4gSW4gdGhpcyBjYXNlIHdlIHdpbGwgbG9vayBmb3IgaW5kaXZpZHVhbCBjaGFyYWN0ZXJzIGJ1dCB5b3UgY291bGQgYWxzbyBzZWFyY2ggZm9yIHdvcmRzIG9yIHBocmFzZXMuDQoNCmBgYHtyfQ0KcmVnZXh0ZXN0IDwtIHJlYWRyOjpyZWFkX2ZpbGUoaGVyZSgiZG9jcyIsICJyZWdFeC50eHQiKSkNCnJlZ2V4dGVzdA0KYGBgDQoNCkNvdW50IHRoZSBsZXR0ZXIgdDoNCmBgYHtyfQ0Kc3RyX2NvdW50KHJlZ2V4dGVzdCwgInQiKSAjIG5vdGljZSB0aGlzIGRvZXNuJ3QgaW5jbHVkZSB0aGUgdCBpbiB0aGUgdGFiDQpgYGANCg0KQ291bnQgdGFiczoNCmBgYHtyfQ0Kc3RyX2NvdW50KHJlZ2V4dGVzdCwgIlxcdCIpICMgc2VhcmNoIGZvciB0YWINCiMgdGhpcyB3b3VsZCBub3Qgd29yazoNCnN0cl9jb3VudChyZWdleHRlc3QsICJbdF0iKSAjIHNlYXJjaGVzIGZvciB0aGUgbGV0dGVyIHQNCmBgYA0KDQpDb3VudCBwYXJlbnRoZXNlczoNCmBgYHtyfQ0KIyB0aGlzIHdvdWxkIG5vdCB3b3JrIGJlY2F1c2UgUiB0aGlua3MgdGhpcyBpcyBwYXJ0IG9mIHRoZSBjb2RlIGl0c2VsZg0KIyBzdHJfY291bnQocmVnZXh0ZXN0LCAiKSIpDQojIHRoaXMgd291bGQgbm90IHdvcmsgYmVjYXVzZSBSIHRoaW5rcyB0aGlzIGlzIHBhcnQgb2YgdGhlIGNvZGUgaXRzZWxmDQojIHN0cl9jb3VudChyZWdleHRlc3QsICJcKSIpDQpzdHJfY291bnQocmVnZXh0ZXN0LCAiXFwpIikgIyB0aGlzIHdvcmtzIQ0KIyB0aGlzIHdvcmtzISBiZWNhdXNlIGl0IGlzIGEgcHVuY3R1YXRpb24gY2hhcmFjdGVyDQpzdHJfY291bnQocmVnZXh0ZXN0LCAiWyldIikNCmBgYA0KDQpDb3VudCB0aGUgb2NjdXJyZW5jZSBvZiB0aGUgYXN0ZXJpc2s6DQpgYGB7cn0NCiMgdGhpcyBhbHNvIGRvZXMgbm90IHdvcmsNCiMgc3RyX2NvdW50KHJlZ2V4dGVzdCwgIioiKQ0KIyBub3IgZG9lcyB0aGlzDQojIHN0cl9jb3VudChyZWdleHRlc3QsICJcKiIpDQpzdHJfY291bnQocmVnZXh0ZXN0LCAiXFwqIikgIyB0aGlzIHdvcmtzIQ0KIyB0aGlzIHdvcmtzISBiZWNhdXNlIGl0IGlzIGEgcHVuY3R1YXRpb24gY2hhcmFjdGVyDQpzdHJfY291bnQocmVnZXh0ZXN0LCAiWypdIikgIyB0aGlzIHdvcmtzIQ0KYGBgDQoNCjwvZGV0YWlscz4NCg0KKioqDQoNCldlIGFsc28gd2FudCB0byBtYWtlIGEgdW5pdCB2YXJpYWJsZSBzbyB0aGF0IHdlIGNhbiBtYWtlIHN1cmUgdGhhdCBvdXIgdW5pdHMgYXJlIGNvbnNpc3RlbnQgbGF0ZXIuIA0KDQpgYGB7cn0NCmd1aWRlbGluZXMgJT4lDQogIHB1bGwob3B0aW1hbCkNCmBgYA0KDQpOb3RpY2UgdGhhdCB0aGUgdmFsdWVzIHRoYXQgYXJlIHBlcmNlbnRhZ2VzIGRvbid0IGhhdmUgc3BhY2VzIGJldHdlZW4gdGhlIG51bWJlciBhbmQgdGhlIHVuaXQuDQpXZSBjYW4gc2VwYXJhdGUgdGhlIGAib3B0aW1hbCJgIHZhbHVlcyBieSBhIHNwYWNlIG9yIGEgcGVyY2VudCBzeW1ib2wgYCIlImAgdXNpbmcgYCJ8ImAgdG8gaW5kaWNhdGUgdGhhdCB3ZSB3YW50IHRvIHNlcGFyYXRlIGJ5IGVpdGhlci4gSW4gdGhpcyBjYXNlIHdlIHdpbGwgbG9zZSB0aGUgIiUiIGFuZCB3aWxsIG5lZWQgdG8gYWRkIGl0IGJhY2sgdG8gdGhvc2UgdmFsdWVzLg0KDQpgYGB7cn0NCmd1aWRlbGluZXMgJTw+JQ0KICBzZXBhcmF0ZShvcHRpbWFsLA0KICAgIGludG8gPSBjKCJvcHRpbWFsIiwgInVuaXQiKSwNCiAgICBzZXAgPSAiIHwlIiwNCiAgICByZW1vdmUgPSBGQUxTRQ0KICApDQpndWlkZWxpbmVzDQpgYGANCg0KR3JlYXQsIHNvIHRvIG5vdyB3ZSB3aWxsIGFkZCAiYCVgIiB0byB0aGUgYHVuaXRgIHZhcmlhYmxlIGZvciAgdGhlIGAibG93IGluIHBvbHl1bnNhdHVyYXRlZCJgIGFuZCBgImhpZ2ggaW4gdHJhbnMgZmF0dHkgYWNpZHMiYCByb3dzLg0KDQpGaXJzdCB3ZSBuZWVkIHRvIHJlcGxhY2UgdGhlIGVtcHR5IHZhbHVlcyB3aXRoIGBOQWAgdXNpbmcgdGhlIGBuYV9pZigpYCBmdW5jdGlvbiBvZiB0aGUgYGRwbHlyYCBwYWNrYWdlLg0KDQpgYGB7cn0NCmd1aWRlbGluZXMgJTw+JQ0KICBuYV9pZigiIikNCmd1aWRlbGluZXMNCmBgYA0KDQoNClRoZW4gdG8gcmVwbGFjZSB0aGUgYE5BYCB2YWx1ZXMsIHdlIGNhbiB1c2UgdGhlIGByZXBsYWNlX25hKClgIGZ1bmN0aW9uIGluIHRoZSBgdGlkeXJgIHBhY2thZ2UgYW5kIHRoZSBgbXV0YXRlKClgIGZ1bmN0aW9uIG9mIGBkcGx5cmAgdG8gc3BlY2lmeSB3aGljaCB2YWx1ZXMgdG8gcmVwbGFjZSwgaW4gdGhpcyBjYXNlIHRoZSBgTkFgIHZhbHVlcyB3aXRoaW4gdGhlIHZhcmlhYmxlIGB1bml0YC4gRXNzZW50aWFsbHkgdGhpcyB2YXJpYWJsZSBnZXRzIHJlYXNzaWduZWQgd2l0aCB0aGUgbmV3IHZhbHVlcywgYXMgd2UgbW9zdGx5IHRoaW5rIG9mIHRoZSBgbXV0YXRlKClgIGZ1bmN0aW9uIGFzIGNyZWF0aW5nIG5ldyB2YXJpYWJsZXMuDQoNCmBgYHtyfQ0KZ3VpZGVsaW5lcyAlPD4lDQogIGRwbHlyOjptdXRhdGUodW5pdCA9IHJlcGxhY2VfbmEodW5pdCwgIiUiKSkNCg0KIyBub3cganVzdCB0byBzaG93IHRoZXNlIHJvd3MNCmd1aWRlbGluZXMgJT4lDQogIGZpbHRlcih1bml0ID09ICIlIikNCmBgYA0KDQpMZXQncyBhbHNvIG1vdmUgYHVuaXRgIHRvIGJlIHRoZSBsYXN0IGNvbHVtbi4gV2UgY2FuIHVzZSB0aGUgYHJlbG9jYXRlKClgIGZ1bmN0aW9uIG9mIHRoZSBgZHBseXJgIHBhY2thZ2UgdG8gZG8gdGhpcy4gRm9yIG1vcmUgaW5mb3JtYXRpb24gYWJvdXQgdGhlIGByZWxvY2F0ZSgpYCBmdW5jdGlvbiBzZWUgW2hlcmVdKGh0dHBzOi8vZHBseXIudGlkeXZlcnNlLm9yZy9yZWZlcmVuY2UvcmVsb2NhdGUuaHRtbCl7dGFyZ2V0PSJfYmxhbmsifS4NCg0KYGBge3J9DQpndWlkZWxpbmVzICU8PiUNCiAgcmVsb2NhdGUodW5pdCwgLmFmdGVyID0gbGFzdF9jb2woKSkNCmBgYA0KDQpUbyByZW1vdmUgYWxsIG9mIHRoZSByZW1haW5pbmcgZXh0cmEgY2hhcmFjdGVycyBhbmQgd29yZHMgd2Ugd2lsbCBhZ2FpbiB1c2UgdGhlIGBzdHJpbmdyYCBwYWNrYWdlLiBUaGlzIHRpbWUgd2Ugd2lsbCB1c2UgdGhlIGBzdHJfcmVtb3ZlX2FsbCgpYCBmdW5jdGlvbiB0byByZW1vdmUgYWxsIGluc3RhbmNlcyBvZiB0aGVzZSBjaGFyYWN0ZXJzLg0KDQpgYGB7cn0NCmd1aWRlbGluZXMgPC0gYXNfdGliYmxlKA0KICBtYXAoZ3VpZGVsaW5lcywgc3RyX3JlbW92ZV9hbGwsDQogICAgcGF0dGVybiA9ICJcXCkgcGVyIGRheXxcXCkgb2YgdG90YWwgZGFpbHkgZW5lcmd5fFxcKiINCiAgKQ0KKQ0KZ3VpZGVsaW5lcw0KYGBgDQoNCk5pY2UhIFRoYXQncyBwcmV0dHkgY2xlYW4gYnV0IHdlIGNhbiBkbyBhIGJpdCBtb3JlLg0KDQojIyMgKipEYXRhIHR5cGUgY29udmVyc2lvbioqDQoqKioNCg0KT25lIG9mIHRoZSBuZXh0IHRoaW5ncyB0byBub3RpY2UgYWJvdXQgb3VyIGRhdGEgaXMgYWxsIG9mIG91ciB2YXJpYWJsZXMgYXJlIG9mIGNsYXNzIGNoYXJhY3Rlciwgd2hpY2ggaXMgbm90IGhvdyB3ZSB3YW50IHRoZW0gdG8gYmUuDQoNCkZvciBleGFtcGxlLCB0aGUgb3B0aW1hbCBhbW91bnRzIG9mIGNvbnN1bXB0aW9uIGFyZSBjdXJyZW50bHkgb2YgY2xhc3MgY2hhcmFjdGVyLCB3aGljaCBpcyBpbmRpY2F0ZWQgYnkgdGhlIGA8Y2hyPmAganVzdCBiZWxvdyB0aGUgY29sdW1uIG5hbWVzL3ZhcmlhYmxlIG5hbWVzIG9mIHRoZSBgZ3VpZGVsaW5lc2AgdGliYmxlOg0KDQpgYGB7cn0NCmd1aWRlbGluZXMNCmBgYA0KDQoNClRvIGNvbnZlcnQgdGhlc2UgdmFsdWVzIHRvIG51bWVyaWMgd2UgdXNlIHRoZSBgbXV0YXRlKClgIGFuZCBgYWNyb3NzKClgIGZ1bmN0aW9ucyBvZiB0aGUgYGRwbHlyYCBwYWNrYWdlLg0KDQpUaGUgYGFjcm9zcygpYCBmdW5jdGlvbiBoYXMgdHdvIG1haW4gYXJndW1lbnRzOiAoaSkgdGhlIGNvbHVtbnMgeW91IHdhbnQgdG8gb3BlcmF0ZSBvbiBhbmQgKGlpKSB0aGUgZnVuY3Rpb24gb3IgbGlzdCBvZiBmdW5jdGlvbnMgdG8gYXBwbHkgdG8gZWFjaCBjb2x1bW4uIEluIHRoaXMgY2FzZSBpZiB3ZSBsb29rIGF0IHRoZSBiZWdpbm5pbmcgb2YgdGhlIGBndWlkZWxpbmVzYCB0aWJibGUsIHdlIGNhbiBzZWUgdGhhdCBgb3B0aW1hbGAsIGBsb3dlcmAgYW5kIGB1cHBlcmAgc2hvdWxkIGJlIGNvbnZlcnRlZC4gQXMgdGhlc2UgdGhyZWUgY29sdW1ucyBhcmUgc2VxdWVudGlhbCwgd2UgY2FuIHNpbXBseSBwdXQgYSBgOmAgYmV0d2VlbiBgb3B0aW1hbGAgYW5kIGB1cHBlcmAgdG8gaW5kaWNhdGUgdGhhdCB3ZSB3YW50IGFsbCB0aGUgdmFyaWFibGVzIGluIGJldHdlZW4gdGhlc2UgY29sdW1ucyB0byBiZSBjb252ZXJ0ZWQuIA0KDQpgYGB7cn0NCmd1aWRlbGluZXMgJTw+JQ0KICBtdXRhdGUoYWNyb3NzKGxvd2VyOnVwcGVyLCBhcy5udW1lcmljKSkNCmd1aWRlbGluZXMNCmBgYA0KDQpHcmVhdCEgTm93IHRoZXNlIHZhcmlhYmxlcyBhcmUgb2YgY2xhc3MgYDxkYmw+YCAoc3RhbmRzIGZvciBkb3VibGUpIHdoaWNoIGluZGljYXRlcyB0aGF0IHRoZXkgYXJlIG51bWVyaWMuIEhlcmUgaXMgYSBbbGlua10oaHR0cDovL3VjLXIuZ2l0aHViLmlvL2ludGVnZXJfZG91YmxlLyl7dGFyZ2V0PSJfYmxhbmsifSBmb3IgbW9yZSBpbmZvcm1hdGlvbiBvbiBudW1lcmljIGNsYXNzZXMgaW4gUi4NCg0KSWYgd2UgaGFkIG5vdCByZXBsYWNlZCB0aGUgYCLCtyJgIGludGVycHVuY3QgdmFsdWVzIHRvIGEgcGVyaW9kLCBjb252ZXJzaW9uIGZyb20gY2hhcmFjdGVyIHRvIG51bWVyaWMgd291bGQgYmUgcHJvYmxlbWF0aWMgYW5kIHdvdWxkIHJlc3VsdCBpbiBOQSB2YWx1ZXMuDQoNCiMjIyAqKkRhdGEgdmFsdWUgcmVhc3NpZ25tZW50cyoqDQoqKioNCg0KV2Ugc2VlbSB0byBoYXZlIGxvc3QgdGhlIHdvcmQgYCJiZXZlcmFnZXMiYCBmcm9tIHRoZSBgInN1Z2FyLXN3ZWV0ZW5lZCBiZXZlcmFnZXMiYCBjYXRlZ29yeSwgIGFzIHdlbGwgYXMgYCJmYXR0eSBhY2lkcyJgIGZyb20gdGhlIGAic2VhZm9vZCBvbWVnYSAzIGZhdHR5IGFjaWRzImAsIGFuZCB0aGUgYCJwb2x5dW5zYXR1cmF0ZWQgZmF0dHkgYWNpZHMiYCBjYXRlZ29yaWVzIGFzIHRoZSBmdWxsIGNhdGVnb3J5IG5hbWUgd2FzIGxpc3RlZCBvbiB0d28gbGluZXMgd2l0aGluIHRoZSB0YWJsZS4gV2Ugd291bGQgbGlrZSB0byByZXBsYWNlIHRoZXNlIHZhbHVlcyB3aXRoIHRoZSBmdWxsIG5hbWUuIA0KDQpUbyBzZWxlY3QgdGhlIGBmb29kYCB2YXJpYWJsZSB3ZSB3aWxsIHNob3cgeW91IHNldmVyYWwgb3B0aW9ucy4gT25seSBhIGNvdXBsZSB3aWxsIHdvcmsgd2VsbCB3aXRoIHJlYXNzaWduaW5nIHRoZSBkYXRhIGluIHRoYXQgcGFydGljdWxhciB2YXJpYWJsZSB3aXRoaW4gYGd1aWRlbGluZXNgIHdpdGhvdXQgYXNzaWduaW5nIGFuIGludGVybWVkaWF0ZSBkYXRhIG9iamVjdC4gV2Ugd2lsbCBsb29rIHVzaW5nIGBtdXRhdGVfYXQoKWAsIGBwdWxsKClgLCBgc2VsZWN0KClgLCBhbmQgdHdvIHN0eWxlcyBvZiBicmFja2V0cyBgWyJ2YXJpYWJsZSBuYW1lIl1gIGFuZCBgW1sidmFyaWFibGVuYW1lIl1dYC4NCg0KVGhlIGJyYWNrZXQgYFsidmFyaWFibGUgbmFtZSJdYCBvcHRpb24gYW5kIHRoZSBgc2VsZWN0KClgIG9wdGlvbiB3aWxsIGdyYWIgYSB0aWJibGUgKGRhdGEgZnJhbWUpIHZlcnNpb24gb2YgdGhlIGZvb2QgY29sdW1uIG91dCBvZiBndWlkZWxpbmVzLiBIb3dldmVyIHdlIGNhbid0IHN0YXJ0IGNvbW1hbmRzIHdpdGggc2VsZWN0IGZvciBhc3NpZ25tZW50cy4NCg0KYGBge3J9DQpndWlkZWxpbmVzWyJmb29kIl0gIyBzYW1lIG91dHB1dCBhcyBzZWxlY3QNCnNlbGVjdChndWlkZWxpbmVzLCAiZm9vZCIpICMgc2FtZSBvdXRwdXQgYXMgYnJhY2tldHMNCmBgYA0KDQoNCmBwdWxsKClgIGFuZCB0aGUgYnJhY2tldCBgW1sidmFyaWFibGUgbmFtZSJdXWAgb3B0aW9uIGluIGNvbnRyYXN0LCB3aWxsIGdyYWIgdGhlIHZlY3RvciB2ZXJzaW9uIG9mIHRoZSBmb29kIGRhdGE6DQoNCmBgYHtyfQ0KcHVsbChndWlkZWxpbmVzLCAiZm9vZCIpICMgZ2V0IGNoYXJhY3RlciB2ZWN0b3Igbm90IGEgdGliYmxlDQojIGJyYWNrZXQgb3B0aW9uOg0KZ3VpZGVsaW5lc1tbImZvb2QiXV0gIyBnZXQgY2hhcmFjdGVyIHZlY3RvciBub3QgYSB0aWJibGUNCmBgYA0KDQpUaGUgYHB1bGwoKWAgZnVuY3Rpb24gY2FuIGJlIHZlcnkgdXNlZnVsIHdoZW4gY29tYmluZWQgd2l0aCBvdGhlciBmdW5jdGlvbnMgKGZvciBleGFtcGxlIHlvdSB0eXBpY2FsbHkgd2FudCB0byB1c2UgYSB2ZWN0b3Igd2l0aCB0aGUgYHN0cl9yZXBsYWNlKClgIGZ1bmN0aW9uKSwgYnV0IGp1c3QgbGlrZSBzZWxlY3QsIHdlIGNhbid0IHN0YXJ0IGFzc2lnbm1lbnRzIHdpdGggYHB1bGwoKWAuDQoNCg0KVGhpcyBpcyBub3QgcG9zc2libGUgYW5kIHdpbGwgcmVzdWx0IGluIGFuIGVycm9yOg0KYGBge3IsIGV2YWwgPSBGQUxTRX0NCnNlbGVjdChndWlkZWxpbmVzLCBmb29kKSA8LQ0KICBzdHJfcmVwbGFjZSgNCiAgICBwdWxsKGd1aWRlbGluZXMsICJmb29kIiksDQogICAgcGF0dGVybiA9ICJzdWdhci1zd2VldGVuZWQiLA0KICAgIHJlcGxhY2VtZW50ID0gInN1Z2FyLXN3ZWV0ZW5lZCBiZXZlcmFnZXMiDQogICkNCg0KZ3VpZGVsaW5lcyAlPiUgc2VsZWN0KGZvb2QpIDwtDQogIHN0cl9yZXBsYWNlKA0KICAgIHB1bGwoZ3VpZGVsaW5lcywgImZvb2QiKSwNCiAgICBwYXR0ZXJuID0gInN1Z2FyLXN3ZWV0ZW5lZCIsDQogICAgcmVwbGFjZW1lbnQgPSAic3VnYXItc3dlZXRlbmVkIGJldmVyYWdlcyINCiAgKQ0KYGBgDQoNClRoaXMgd2lsbCBvbmx5IHByaW50IHRoZSByZXN1bHQsIGJ1dCBub3QgcmVhc3NpZ24gdGhlIGZvb2QgdmFyaWFibGUgdmFsdWVzOg0KDQpgYGB7cn0NCmd1aWRlbGluZXMgJT4lDQogIHB1bGwoZm9vZCkgJT4lDQogIHN0cl9yZXBsYWNlKA0KICAgIHBhdHRlcm4gPSAic3VnYXItc3dlZXRlbmVkIiwNCiAgICByZXBsYWNlbWVudCA9ICJzdWdhci1zd2VldGVuZWQgYmV2ZXJhZ2VzIg0KICApDQpgYGAgICANCg0KVXNpbmcgYHNlbGVjdCgpYCB3b3VsZCB3b3JrIGFzIHdlbGwgdG8gcHJpbnQgdGhlIHJlc3VsdCAoYWx0aG91Z2ggdGhlIHJlc3VsdCBzdHJ1Y3R1cmUgaXMgZGlmZmVyZW50KToNCg0KYGBge3J9DQpndWlkZWxpbmVzICU+JQ0KICBzZWxlY3QoZm9vZCkgJT4lDQogIHN0cl9yZXBsYWNlKA0KICAgIHBhdHRlcm4gPSAic3VnYXItc3dlZXRlbmVkIiwNCiAgICByZXBsYWNlbWVudCA9ICJzdWdhci1zd2VldGVuZWQgYmV2ZXJhZ2VzIg0KICApDQpgYGANCg0KIyMjIyB7LnRoaW5rX3F1ZXN0aW9uX2Jsb2NrfQ0KDQo8dT5RdWVzdGlvbiBvcHBvcnR1bml0eTo8L3U+IA0KDQpXaHkgZG8gdGhlc2UgY29tbWFuZHMgbm90IHJlYXNzaWduIHRoZSBmb29kIHZhcmlhYmxlIHZhbHVlcz8NCg0KIyMjIw0KDQpUaGUgYnJhY2tldCBvcHRpb24gaXMgZ3JlYXQgYWx0ZXJuYXRpdmUgYW5kIGFsbG93cyB1cyB0byByZWFzc2lnbiB0aGUgdmFsdWVzIHdpdGhpbiBndWlkZWxpbmVzIGVhc2lseS4gRWl0aGVyIG9mIHRoZSB0d28gc3R5bGVzIG9mIGJyYWNrZXRzOiBgWyJ2YXJpYWJsZSBuYW1lIl1gIGFuZCBgW1sidmFyaWFibGVuYW1lIl1dYCB3aWxsIHdvcmsuDQoNCmBgYHtyfQ0KIyAxc3QgbWV0aG9kOiBgWyJ2YXJpYWJsZSBuYW1lIl1gDQojIFJlcGxhY2luZyAic3VnYXItc3dlZXRlbmVkIiB3aXRoICJzdWdhci1zd2VldGVuZWQgYmV2ZXJhZ2VzIg0KZ3VpZGVsaW5lc1siZm9vZCJdIDwtDQogIHN0cl9yZXBsYWNlKA0KICAgIHB1bGwoZ3VpZGVsaW5lcywgImZvb2QiKSwNCiAgICBwYXR0ZXJuID0gInN1Z2FyLXN3ZWV0ZW5lZCIsDQogICAgcmVwbGFjZW1lbnQgPSAic3VnYXItc3dlZXRlbmVkIGJldmVyYWdlcyINCiAgKQ0KDQojIDJuZCBtZXRob2Q6IGBbWyJ2YXJpYWJsZW5hbWUiXV1gDQojIFJlcGxhY2luZyAic2VhZm9vZCBvbWVnYS0zIiB3aXRoInNlYWZvb2Qgb21lZ2EtMyBmYXR0eSBhY2lkcyINCmd1aWRlbGluZXNbWyJmb29kIl1dIDwtDQogIHN0cl9yZXBsYWNlKA0KICAgIHB1bGwoZ3VpZGVsaW5lcywgImZvb2QiKSwNCiAgICBwYXR0ZXJuID0gInNlYWZvb2Qgb21lZ2EtMyIsDQogICAgcmVwbGFjZW1lbnQgPSAic2VhZm9vZCBvbWVnYS0zIGZhdHR5IGFjaWRzIg0KICApDQoNCmd1aWRlbGluZXMNCmBgYA0KDQoNCkZpbmFsbHksIHRoZSBiZXN0IG9wdGlvbiBpcyBwcm9iYWJseSB0aGUgYG11dGF0ZV9hdCgpYCBmdW5jdGlvbiBmcm9tIGBkcGx5cmAuIEluIHRoaXMgY2FzZSB3ZSBuZWVkIHRvIGluY2x1ZGUgYH5gIGluIGZyb250IG9mIHRoZSBmdW5jdGlvbiB0aGF0IHdlIHdvdWxkIGxpa2UgdG8gdXNlIG9uIHRoZSB2YWx1ZXMgaW4gb3VyIGBmb29kYCB2YXJpYWJsZXMuIFdlIGFsc28gaW5jbHVkZSBgLmAgYXMgYSByZXBsYWNlbWVudCB0byByZWZlcmVuY2UgdGhlIGRhdGEgdGhhdCB3ZSB3YW50IHRvIHVzZSB3aXRoaW4gYHN0cl9yZXBsYWNlKClgICh3aGljaCBpbiB0aGlzIGNhc2UgaXMgdGhlIGBmb29kYCB2YXJpYWJsZSB2YWx1ZXMgb2YgYGd1aWRlbGluZXNgKS4NCg0KTm90aWNlIHdlIGRpZG4ndCBuZWVkIHRoaXMgd2hlbiB3ZSBwcmV2aW91c2x5IHVzZSBgbXV0YXRlX2F0KClgIHdpdGggdGhlIGBhcy5udW1lcmljKClgIGZ1bmN0aW9uLiBUaGlzIGlzIGJlY2F1c2UgdGhlIGBzdHJfcmVwbGFjZSgpYCBmdW5jdGlvbiByZXF1aXJlcyB1cyB0byBzcGVjaWZ5IHdoYXQgZGF0YSB3ZSBhcmUgdXNpbmcgYXMgb25lIG9mIHRoZSBhcmd1bWVudHMsIHdoaWxlIGBhcy5udW1lcmljKClgIGRvZXMgbm90Lg0KDQpgYGB7cn0NCiMgUmVwbGFjaW5nICJwb2x5dW5zYXR1cmF0ZWQiIHdpdGgicG9seXVuc2F0dXJhdGVkIGZhdHR5IGFjaWRzIg0KZ3VpZGVsaW5lcyAlPD4lDQogIG11dGF0ZV9hdCgNCiAgICB2YXJzKGZvb2QpLA0KICAgIH4gc3RyX3JlcGxhY2UoDQogICAgICBzdHJpbmcgPSAuLA0KICAgICAgcGF0dGVybiA9ICJwb2x5dW5zYXR1cmF0ZWQiLA0KICAgICAgcmVwbGFjZW1lbnQgPSAicG9seXVuc2F0dXJhdGVkIGZhdHR5IGFjaWRzIg0KICAgICkNCiAgKQ0KDQpndWlkZWxpbmVzDQpgYGANCg0KVGhpcyBtaWdodCBiZSBjb25zaWRlcmVkIGEgYmV0dGVyIG9wdGlvbiBiZWNhdXNlIGl0IGlzIG1vcmUgcmVhZGFibGUgYXMgdG8gd2hlcmUgdGhlIGBmb29kYCBkYXRhIGNhbWUgZnJvbSB0aGF0IHdlIGFyZSByZXBsYWNpbmcgdmFsdWVzIHdpdGhpbi4NCg0KVGhlcmUgaXMgb25lIGxhc3QgbWlub3IgZGV0YWlsLi4uIHRoZSBgZGlyZWN0aW9uYCB2YXJpYWJsZSBoYXMgbGVhZGluZyBzcGFjZXMgc3RpbGwuIFdlIGNhbiB1c2UgYHN0cl90cmltKClgIHRvIGZpeCB0aGF0IQ0KDQpgYGB7cn0NCmd1aWRlbGluZXMgJTw+JQ0KICBtdXRhdGVfYXQodmFycyhkaXJlY3Rpb24pLCBzdHJfdHJpbSkNCg0KZ3VpZGVsaW5lcw0KYGBgDQoNCk9LISBOb3cgd2Uga25vdyBob3cgbXVjaCBvZiBlYWNoIGRpZXRhcnkgZmFjdG9yIHdlIGdlbmVyYWxseSBuZWVkIGZvciBvcHRpbWFsIGhlYWx0aCBhY2NvcmRpbmcgdG8gdGhlIGd1aWRlbGluZXMgdXNlZCBpbiB0aGlzIGFydGljbGUuDQoNCg0KDQojIyMgKipDb21wYXJpbmcgZGF0YSoqDQoqKioNCg0KUmVjYWxsIHRoYXQgdGhlIG1haW4gZ29hbCBvZiBwdWxsaW5nIHRoZSBndWlkZWxpbmUgYW1vdW50cyBmcm9tIHRoZSBwZGYgd2FzIHRoYXQgd2Ugd291bGQgbGlrZSB0byBzZWUgaG93IHRoZSBtZWFuIGNvbnN1bXB0aW9uIHJhdGVzIGZvciB0aGUgZGlmZmVyZW50IGdyb3VwcyBvZiBwZW9wbGUgY29tcGFyZWQgdG8gdGhlIG9wdGltYWwgaW50YWtlIGd1aWRlbGluZXMuDQoNCk9uZSB3YXkgd2UgY291bGQgZG8gdGhpcyBpcyB0byBjYWxjdWxhdGUgYSBjb25zdW1wdGlvbiBwZXJjZW50YWdlIG9mIHRoZSBvcHRpbWFsIHZhbHVlLg0KDQpUbyBjYWxjdWxhdGUgdGhpcyBpdCB3b3VsZCBiZSBoZWxwZnVsIHRvIHB1dCB0aGUgZ3VpZGVsaW5lIGFtb3VudHMgd2l0aCB0aGUgYXZlcmFnZSBjb25zdW1wdGlvbiByYXRlcyBpbnRvIHRoZSBzYW1lIHRpYmJsZSwgZXNwZWNpYWxseSBiZWNhdXNlIHRoZSBvYnNlcnZlZCBjb25zdW1wdGlvbiBkYXRhIChgZGlldF9kYXRhYCBhbmQgYHNlcF9hZ2VfZGlldF9kYXRhYCkgYXJlIHZlcnkgZGlmZmVyZW50IGRpbWVuc2lvbnMgZnJvbSB0aGUgYGd1aWRlbGluZXNgIGRhdGEuIA0KDQpJbiBvcmRlciB0byBjcmVhdGUgYSB0aWJibGUgd2l0aCBvdXIgb2JzZXJ2ZWQgY29uc3VtcHRpb24gcmF0ZXMgd2l0aCB0aGUgc3VnZ2VzdGVkIGNvbnN1bXB0aW9uIHJhdGVzLCB3ZSB3aWxsIGpvaW4gb3VyIGRhdGEgdXNpbmcgYGRwbHlyYC4gSW4gb3JkZXIgdG8gZG8gc28gaXQgaXMgaW1wb3J0YW50IHRoYXQgb3VyIGRpZmZlcmVudCBkYXRhIHNldHMgaGF2ZSBhdCBsZWFzdCBvbmUgY29sdW1uIHdpdGggdGhlIHNhbWUgdmFsdWVzIHRoYXQgd2UgY2FuIHVzZSB0byBqb2luIHRoZW0gdG9nZXRoZXIuIFNvIGxldCdzIGZpcnN0IGFzc2VzcyBpZiB0aGF0IGlzIHRoZSBjYXNlLg0KDQoNCmBgYHtyfQ0KZGlzdGluY3QoZGlldF9kYXRhLCBkaWV0YXJ5X3Jpc2spDQpzZWxlY3QoZ3VpZGVsaW5lcywgZm9vZCkNCmBgYA0KDQpXZSBhcmUgYWN0dWFsbHkgcHJldHR5IGNsb3NlOiB0aGVyZSBhcmUgMTUgZGlldGFyeSBmYWN0b3JzIGluIGVhY2ggZGF0YSBzZXQsIGFuZCB0aGUgbmFtZXMgYXJlIG5lYXJseSB0aGUgc2FtZS4gVG8gbWFrZSB0aGVtIG1hdGNoIGNvbXBsZXRlbHksIHdlIGNhbiBzZWUgdGhhdCB3ZSBuZWVkIHRvIHJlbW92ZSB0aGUgYCJEaWV0IGxvdyBpbiJgIGFuZCBgIkRpZXQgaGlnaCBpbiJgIHBocmFzZXMgZnJvbSB0aGUgb2JzZXJ2ZWQgY29uc3VtcHRpb24gZGF0YS4NCg0KYGBge3J9DQpkaWV0X2RhdGEgJTw+JQ0KICBtdXRhdGVfYXQoDQogICAgdmFycyhkaWV0YXJ5X3Jpc2spLA0KICAgIH4gc3RyX3JlbW92ZSgNCiAgICAgIHN0cmluZyA9IC4sDQogICAgICBwYXR0ZXJuID0gIkRpZXQgbG93IGluIHxEaWV0IGhpZ2ggaW4gIg0KICAgICkNCiAgKQ0KDQpzZXBfYWdlX2RpZXRfZGF0YSAlPD4lDQogIG11dGF0ZV9hdCgNCiAgICB2YXJzKGRpZXRhcnlfcmlzayksDQogICAgfiBzdHJfcmVtb3ZlKA0KICAgICAgc3RyaW5nID0gLiwNCiAgICAgIHBhdHRlcm4gPSAiRGlldCBsb3cgaW4gfERpZXQgaGlnaCBpbiAiDQogICAgKQ0KICApDQpgYGANCg0KQWxzbyBsZXQncyBkb3VibGUgY2hlY2sgdGhhdCB0aGUgdHdvIG9ic2VydmVkIGZpbGVzIGhhdmUgdGhlIHNhbWUgZXhhY3QgdmFsdWVzIGZvciBkaWV0YXJ5IGZhY3RvciBuYW1lcy4gDQoNCldlIGNhbiB1c2UgdGhlIGBzZXRlcXVhbCgpYCBmdW5jdGlvbiBmcm9tIGBkcGx5cmAgdG8gY2hlY2sgdGhhdCB0aGUgdW5pcXVlIHZhbHVlcyBmb3IgYGRpZXRhcnlfcmlza2AgYXJlIHRoZSBzYW1lIGZvciBib3RoIGBkaWV0X2RhdGFgIGFuZCBgc2VwX2FnZV9kaWV0X2RhdGFgLg0KDQoNCmBgYHtyfQ0Kc2V0ZXF1YWwoDQogIGRpc3RpbmN0KGRpZXRfZGF0YSwgZGlldGFyeV9yaXNrKSwNCiAgZGlzdGluY3Qoc2VwX2FnZV9kaWV0X2RhdGEsIGRpZXRhcnlfcmlzaykNCikNCmBgYA0KR3JlYXQhDQoNCk5vdGUgdGhhdCB0aGUgZGVmYXVsdCBvZiB0aGUgc2V0X2VxdWFsIGZ1bmN0aW9uIGlnbm9yZXMgdGhlIG9yZGVyIG9mIHZhbHVlcyBpbiByb3dzLiBTbyB3ZSBzdGlsbCBkb24ndCBrbm93IGlmIHRoZSBvcmRlciBpcyB0aGUgc2FtZS4NCg0KV2UgY2FuIGNoZWNrIHVzaW5nIHRoZSBgYWxsX2VxdWFsYCBmdW5jdGlvbiBvZiBgZHBseXJgIHdoaWNoIHJlcG9ydHMgYmFjayBjbHVlcyBhYm91dCB3aGF0IG1pZ2h0IGJlIGRpZmZlcmVudCBpZiBhbnl0aGluZy4gSW1wb3J0YW50bHkgd2UgYXJlIGluY2x1ZGluZyBgaWdub3JlX3Jvd19vcmRlciA9IEZBTFNFYCBhcyB0aGUgZGVmYXVsdCBpcyBgVFJVRWAuDQoNCmBgYHtyfQ0KYWxsX2VxdWFsKGRpc3RpbmN0KGRpZXRfZGF0YSwgZGlldGFyeV9yaXNrKSwNCiAgZGlzdGluY3Qoc2VwX2FnZV9kaWV0X2RhdGEsIGRpZXRhcnlfcmlzayksDQogIGlnbm9yZV9yb3dfb3JkZXIgPSBGQUxTRQ0KKQ0KYGBgDQoNCkxvb2tzIGxpa2UgdGhleSBhcmUgbm90IGluIHRoZSBzYW1lIG9yZGVyLiANCg0KTm90ZSB0aGF0IGlmIGFueSBvZiB0aGUgdmFsdWVzIGFyZSBkaWZmZXJlbnQsIGBhbGxfZXF1YWwoKWAgd2lsbCBmaXJzdCByZXBvcnQgdGhpcyBhbmQgd2lsbCBub3QgcmVwb3J0IHRoYXQgdGhlIHJvd3MgYXJlIGluIGEgZGlmZmVyZW50IG9yZGVyLg0KDQoqKioNCg0KPGRldGFpbHM+IDxzdW1tYXJ5PiBDbGljayBoZXJlIHRvIHNlZSBhIHRveSBleGFtcGxlIGFib3V0IGhvdyB0aGUgdGhyZWUgY29tcGFyaXNvbiBmdW5jdGlvbnMgKGBzZXRlcXVhbCgpYCwgYGFsbF9lcXVhbCgpYCAoYWxzbyBgYWxsLmVxdWFsKClgIGZvciBgdGJsX2RmYCksIGFuZCBgc2V0ZGlmZigpYCkgd29yayBpbiBgZHBseXJgLiA8L3N1bW1hcnk+IA0KDQpJdCdzIGltcG9ydGFudCB0byByZWFsaXplIHRoYXQgcm93IG9yZGVyIGlzIGlnbm9yZWQgYnkgYm90aGBzZXRlcXVhbCgpYCBhbmQgYHNldGRpZmYoKWAuIA0KDQpOb3cgbGV0J3MgY29tcGFyZSB0d28gdGliYmxlcyB0aGF0IGhhdmUgZGlmZmVyZW50IHJvdyBvcmRlcnMgYW5kIGRpZmZlcmVudCB2YWx1ZXMuIA0KDQpIZXJlIGFyZSBvdXIgdGliYmxlcyB0byBjb21wYXJlOg0KYGBge3J9DQpYIDwtIHRpYmJsZSh0ZXN0ID0gYygiQSIsICJCIiwgIkFDIiwgIkQiKSkNClkgPC0gdGliYmxlKHRlc3QgPSBjKCJBIiwgIkQiLCAiQSIsICJCIikpDQpYDQpZDQpjbGFzcyhZKQ0KYGBgDQoNClNpbmNlIHdlIGFyZSB1c2luZyB0aWJibGVzLCB3aGljaCBhcmUgb2YgY2xhc3MgYHRibF9kZmAgd2UgY2FuIHVzZSBlaXRoZXIgYGFsbF9lcXVhbGAgb3IgYGFsbC5lcXVhbCgpYC4NCk5vdGljZSB0aGF0IGl0IGRvZXNuJ3QgcmVwb3J0IHJvd3MgYmVpbmcgYSBkaWZmZXJlbnQgb3JkZXIgYmVjYXVzZSBpdCBmaXJzdCB0ZWxscyB3aGF0IHJvd3MgaGF2ZSB1bmlxdWUgdmFsdWVzIG9yIHJvd3Mgd2l0aCBhIHZhbHVlIHRoYXQgaGFzIGEgZGlmZmVyZW50IG51bWJlciBvZiBmcmVxdWVuY3kuDQoNCmBgYHtyfQ0KYWxsX2VxdWFsKFgsIFksIGlnbm9yZV9yb3dfb3JkZXIgPSBUUlVFKQ0KYWxsX2VxdWFsKFgsIFksIGlnbm9yZV9yb3dfb3JkZXIgPSBGQUxTRSkNCiMgRG9lc24ndCByZXBvcnQgcm93cyBiZWluZyBkaWZmZXJlbnQgb3JkZXINCmFsbC5lcXVhbChYLCBZLCBpZ25vcmVfcm93X29yZGVyID0gVFJVRSkNCmFsbC5lcXVhbChYLCBZLCBpZ25vcmVfcm93X29yZGVyID0gRkFMU0UpDQojIERvZXNuJ3QgcmVwb3J0IHJvd3MgYmVpbmcgZGlmZmVyZW50IG9yZGVyDQpgYGANCg0KYHNldGVxdWFsKClgIGRvZXMgbm90IHByb3ZpZGUgY2x1ZXMgYWJvdXQgd2hhdCBpcyBkaWZmZXJlbnQgYnV0IFRSVUUgKG5vIGRpZmZlcmVuY2VzKSBvciBGQUxTRSAoaW5kaWNhdGluZyBhdCBsZWFzdCBvbmUgZGlmZmVyZW5jZSkuDQoNCmBgYHtyfQ0KIyBSZXBvcnRzIGZhbHNlIGluZGljYXRpbmcgYXQgbGVhc3Qgb25lIGRpZmZlcmVuY2UNCnNldGVxdWFsKFgsIFkpDQpgYGANCg0KYHNldGRpZmYoKWAgdGVsbHMgdXMgd2hhdCBpcyBkaWZmZXJlbnQgYW5kIGlzIGRlcGVuZGVudCBvbiB0aGUgb3JkZXIgb2YgdGhlIG9iamVjdHMgY29tcGFyZWQsIGJ1dCBwcmlvcml0aXplcyB0aGUgdmFsdWVzIHRoYXQgYXJlIHVuaXF1ZSB0byBlYWNoLg0KDQpgYGB7cn0NCiMgVGhpcyByZXBvcnRzIHdoYXQgaXMgdW5pcXVlIHRvIFgNCnNldGRpZmYoWCwgWSkNCiMgVGhpcyByZXBvcnRzIHdoYXQgaXMgdW5pcXVlIHRvIFkgLSBub3RoaW5nIGluIHRoaXMgY2FzZQ0Kc2V0ZGlmZihZLCBYKQ0KYGBgDQoNCk5vdyBsZXQncyBtYWtlIGl0IHNvIHRoYXQgb25seSB0aGUgb3JkZXIgaXMgZGlmZmVyZW50Og0KYGBge3J9DQpZIDwtIHRpYmJsZSh0ZXN0ID0gYygiQSIsICJEIiwgIkFDIiwgIkIiKSkNClgNClkNCmBgYA0KDQpOb3cgdGhhdCB0aGVyZSBhcmUgbm8gdmFsdWVzIHRoYXQgYXJlIHVuaXF1ZSB0byBlaXRoZXIgWCBvciBZLCBgYWxsX2VxdWFsKClgIHJlcG9ydHMgdGhhdCB0aGVyZSBpcyBhIGRpZmZlcmVudCBvcmRlci4gDQpgYGB7cn0NCmFsbF9lcXVhbChYLCBZLCBpZ25vcmVfcm93X29yZGVyID0gVFJVRSkNCmFsbF9lcXVhbChYLCBZLCBpZ25vcmVfcm93X29yZGVyID0gRkFMU0UpICMgcmVwb3J0cyBkaWZmIG9yZGVyDQpgYGANCg0KDQpSZW1lbWJlciBgc2V0ZXF1YWwoKWAgaWdub3JlcyBvcmRlciBhbmQgZ2l2ZXMgYSB2YWx1ZSBvZiBUUlVFIGZvciBubyBkaWZmZXJlbmNlcy4NCmBgYHtyfQ0KIyBJdCByZXBvcnRzIG5vIGRpZmZlcmVuY2UhDQpzZXRlcXVhbChYLCBZKQ0KYGBgDQoNCmBzZXRkaWZmKClgIGFsc28gaWdub3JlcyBvcmRlciBhbmQgc2hvd3Mgbm8gZGlmZmVyZW5jZXMuDQpgYGB7cn0NCnNldGRpZmYoWCwgWSkNCmBgYA0KDQpJZiB3ZSBoYXZlIGRpZmZlcmVudCBjb2x1bW4vdmFyaWFibGUgbmFtZXMgdGhpcyBtYWtlcyBjb21wYXJpc29ucyBtb3JlIGNoYWxsZW5naW5nLiBDb2x1bW5zIHdpbGwgYmUgaWRlbnRpZmllZCBmb3IgaGF2aW5nIGRpZmZlcmVudCBuYW1lcy4NCmBgYHtyfQ0KWCA8LSB0aWJibGUoY29sbmFtZTEgPSBjKCJBIiwgIkIiLCAiQUMiLCAiRCIpKQ0KWSA8LSB0aWJibGUoY29sbmFtZTIgPSBjKCJBIiwgIkQiLCAiQUciLCAiQiIpKQ0KYGBgDQoNCmBhbGxfZXF1YWwoKWAgd2lsbCBzaW1wbHkgcmVwb3J0IHRoYXQgY29sIG5hbWVzIGFyZSBkaWZmZXJlbnQNCg0KYGBge3J9DQphbGxfZXF1YWwoWCwgWSwgaWdub3JlX3Jvd19vcmRlciA9IFRSVUUpDQphbGxfZXF1YWwoWCwgWSwgaWdub3JlX3Jvd19vcmRlciA9IEZBTFNFKQ0KYGBgDQoNCmBzZXRlcWF1bCgpYCB3aWxsIHJlcG9ydCBgVFJVRWAgb3IgYEZBTFNFYCB0byBpbmRpY2F0ZSBlaXRoZXIgYSBkaWZmZXJlbmNlIGluIGNvbHVtbnMgb3Igcm93cw0KDQpgYGB7cn0NCnNldGVxdWFsKFgsIFkpDQpgYGANCg0KYHNldGRpZmYoKWAgcmVxdWlyZXMgdGhhdCBjb2x1bW4gbmFtZXMgYmUgdGhlIHNhbWUgc28gdGhpcyB3aWxsIGNhdXNlIGFuIGVycm9yOg0KDQpgYGB7ciwgZXZhbCA9IEZBTFNFfQ0Kc2V0ZGlmZihYLCBZKSAjIFRoaXMgd2lsbCBub3Qgd29yaw0KYGBgDQoNCjwvZGV0YWlscz4gDQoNCioqKg0KDQpPSywgbGV0J3Mga2VlcCBnb2luZyB3aXRoIG91ciBkYXRhLg0KDQpIb3cgc2ltaWxhciBhcmUgdGhlIGd1aWRlbGluZXMgdGliYmxlIGFuZCB0aGUgb2JzZXJ2ZWQgY29uc3VtcHRpb24gdGliYmxlcz8NCg0KYGBge3J9DQpzZXRlcXVhbCgNCiAgZGlzdGluY3QoZGlldF9kYXRhLCBkaWV0YXJ5X3Jpc2spLA0KICBzZWxlY3QoZ3VpZGVsaW5lcywgZm9vZCkNCikNCmBgYA0KDQpPSywgbG9va3MgbGlrZSB3ZSBoYXZlIHNvbWUgZGlmZmVyZW50IHZhbHVlcy4NCg0KTGV0J3MgdXNlIHRoZSBgc2V0ZGlmZmAgZnVuY3Rpb24gdG8gZ2V0IG1vcmUgaW5mb3JtYXRpb24gYWJvdXQgd2hhdCBpcyBkaWZmZXJlbnQgYmV0d2VlbiB0aGUgdmFsdWVzLg0KDQpgYGB7ciwgZXZhbCA9IEZBTFNFfQ0Kc2V0ZGlmZigNCiAgZGlzdGluY3QoZGlldF9kYXRhLCBkaWV0YXJ5X3Jpc2spLA0KICBzZWxlY3QoZ3VpZGVsaW5lcywgZm9vZCkNCikNCmBgYA0KDQo6KCBUaGF0IHdvbnQgd29yay4gVGhpcyBpcyBiZWNhdXNlIGBzZXRkaWZmKClgIHJlcXVpcmVzIHRoYXQgdGhlIGNvbHVtbiBuYW1lcyBhcmUgdGhlIHNhbWUgaW4gdGhlIG9iamVjdHMgdGhhdCB3ZSBhcmUgY29tcGFyaW5nLg0KDQoNCldlIGNhbiB1c2UgdGhlIGByZW5hbWUoKWAgZnVuY3Rpb24gZnJvbSBgZHBseXJgIHRvIGRvIHRoaXMuIFdlIGxpc3QgdGhlIHZhbHVlIHRoYXQgd2Ugd2FudCB0byBjaGFuZ2UgdG8gZmlyc3QuIFdlIGZpbmQgImZvb2QiIG1vcmUgaW50dWl0aXZlIG5vdyBzbyB3ZSBhcmUgZ29pbmcgdG8gY2hhbmdlICJkaWV0YXJ5X3Jpc2siIHRvICJmb29kIiBmb3IgdGhlIGBkaWV0X2RhdGFgIGFuZCB0aGUgYHNlcF9hZ2VfZGlldF9kYXRhYDoNCg0KYGBge3J9DQpkaWV0X2RhdGEgJTw+JQ0KICBkcGx5cjo6cmVuYW1lKGZvb2QgPSBkaWV0YXJ5X3Jpc2spDQpzZXBfYWdlX2RpZXRfZGF0YSAlPD4lDQogIGRwbHlyOjpyZW5hbWUoZm9vZCA9IGRpZXRhcnlfcmlzaykNCmBgYA0KDQoNCmBgYHtyfQ0Kc2V0ZGlmZigNCiAgZGlzdGluY3QoZGlldF9kYXRhLCBmb29kKSwNCiAgc2VsZWN0KGd1aWRlbGluZXMsIGZvb2QpDQopDQpgYGANCg0KR3JlYXQsIG5vdyB3ZSBrbm93IHRoYXQgdGhlIGBmaWJlcmAgdmFsdWUgYXBwZWFycyB0byBiZSBkaWZmZXJlbnQgYmV0d2VlbiB0aGUgdHdvLg0KDQoNCkNoZWNraW5nIG91ciBvcmlnaW5hbCBmaWxlcyB3ZSBjYW4gc2VlIHRoYXQgdGhlIEJyaXRpc2ggc3BlbGxpbmcgImZpYnJlIiBpcyB1c2VkIGluIHRoZSB0YWJsZSBmcm9tIHRoZSBhcnRpY2xlICh0aGF0IHdlIHVzZWQgdG8gY3JlYXRlIGBndWlkZWxpbmVzYCksIGluIGNvbnRyYXN0IHRvIHRoZSBBbWVyaWNhbiBzcGVsbGluZyAiZmliZXIiIHVzZWQgaW4gdGhlIENTViBmaWxlcy4NCg0KTGV0J3Mgc3RpY2sgd2l0aCB0aGUgQW1lcmljYW4gc3BlbGxpbmcsIHNvIHdlIHdpbGwgcmVwbGFjZSBgImZpYnJlImAgaW4gdGhlIGd1aWRlbGluZSB0aWJibGUuDQoNCmBgYHtyfQ0KZ3VpZGVsaW5lcyAlPD4lDQogIG11dGF0ZV9hdCgNCiAgICB2YXJzKGZvb2QpLA0KICAgIH4gc3RyX3JlcGxhY2UoDQogICAgICBzdHJpbmcgPSAuLA0KICAgICAgcGF0dGVybiA9ICJmaWJyZSIsDQogICAgICByZXBsYWNlbWVudCA9ICJmaWJlciINCiAgICApDQogICkNCg0KZ3VpZGVsaW5lcyAlPiUNCiAgZmlsdGVyKGZvb2QgPT0gImZpYmVyIikNCmBgYA0KDQpOb3cgbGV0J3MgY2hlY2sgYWdhaW4gdG8gc2VlIHRoYXQgb3VyIGZvb2QgdmFsdWVzIG1hdGNoIGJldHdlZW4gdGhlIGd1aWRlbGluZXMgYW5kIHRoZSBvYnNlcnZlZCBjb25zdW1wdGlvbiBkYXRhIHRpYmJsZXMuDQoNCmBgYHtyfQ0Kc2V0ZGlmZigNCiAgc2VsZWN0KGd1aWRlbGluZXMsIGZvb2QpLA0KICBkaXN0aW5jdChkaWV0X2RhdGEsIGZvb2QpDQopDQoNCnNldGRpZmYoDQogIHNlbGVjdChndWlkZWxpbmVzLCBmb29kKSwNCiAgZGlzdGluY3Qoc2VwX2FnZV9kaWV0X2RhdGEsIGZvb2QpDQopDQpgYGANCg0KR3JlYXQhICBUaGVyZSBhcmUgbm8gZGlmZmVyZW5jZXMgOikNCg0KIyMjICoqSm9pbmluZyBkYXRhKioNCioqKg0KDQpOb3cgd2UgY2FuIHB1dCBvdXIgZ3VpZGVsaW5lIGRhdGEgdG9nZXRoZXIgd2l0aCB0aGUgYGRpZXRfZGF0YWAgYW5kIHRoZSBgc2VwX2FnZV9kaWV0X2RhdGFgLg0KDQpSZW1lbWJlciB0aGF0IHRoZSBgZm9vZGAgZGF0YSBpbiBvdXIgYGd1aWRlbGluZXNgIHRpYmJsZSBpcyBub3QgbmVjZXNzYXJpbHkgaW4gdGhlIHNhbWUgb3JkZXIgYXMgdGhhdCBvZiB0aGUgY29uc3VtcHRpb24gZGF0YSB0aWJibGVzLiBUaHVzIHRoaXMgY291bGQgYmUgYSBwcm9ibGVtIGlmIHdlIGRlY2lkZWQgdG8gZXhwYW5kIHRoZSBgZ3VpZGVsaW5lc2Agcm93cyAodG8gcmVwZWF0IGZvciB0aGUgbnVtYmVyIG9mIGZydWl0IG9ic2VydmF0aW9ucyBldGMuKSBhbmQgYWRkIHRoZW0gdG8gb3VyIG9ic2VydmVkIGNvbnN1bXB0aW9uIHRpYmJsZXMgYnkgYmluZGluZyB0aGVtIHRvZ2V0aGVyIGJ5IGNvbHVtbi4gDQoNCmBgYHtyLCBlY2hvID0gRkFMU0UsIG91dHdpZHRoID0gIjUwJSIsIGZpZy5hbGlnbj0gImNlbnRlciJ9DQprbml0cjo6aW5jbHVkZV9ncmFwaGljcyhoZXJlKCJpbWciLCAiYmluZC5wbmciKSkNCmBgYA0KDQojIyMjIFtbc291cmNlXV0oaHR0cHM6Ly9yc3R1ZGlvLmNvbS93cC1jb250ZW50L3VwbG9hZHMvMjAxNS8wMi9kYXRhLXdyYW5nbGluZy1jaGVhdHNoZWV0LnBkZikNCg0KSW4gdGhhdCBjYXNlIHdlIGNvdWxkIHVzZSB0aGUgYGFycmFuZ2UoKWAgZnVuY3Rpb24gb2YgYGRwbHlyYCB0byBzb3J0IHRoZSBkYXRhIGFscGhhYmV0aWNhbGx5Lg0KDQpIb3dldmVyLCB3ZSB3aWxsIGluc3RlYWQgdXNlIGEgam9pbmluZyBmdW5jdGlvbiBvZiBgZHBseXJgLiBUaGVzZSBmdW5jdGlvbnMgY29tYmluZSB0aGUgZGF0YSB0b2dldGhlciBiYXNlZCBvbiAqKmNvbW1vbiB2YWx1ZXMqKiBhbmQgZG9uJ3QgcmVxdWlyZSB0aGUgcm93cyB0byBiZSBpbiB0aGUgc2FtZSBvcmRlci4gVGhlcmUgYXJlIGEgdmFyaWV0eSBvZiBvcHRpb25zLg0KDQpgYGB7ciwgZWNobyA9IEZBTFNFLCBvdXR3aWR0aCA9ICI1MCUiLCBmaWcuYWxpZ249ICJjZW50ZXIifQ0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoaGVyZSgiaW1nIiwgImpvaW4ucG5nIikpDQpgYGANCg0KIyMjIyBbW3NvdXJjZV1dKGh0dHBzOi8vcnN0dWRpby5jb20vd3AtY29udGVudC91cGxvYWRzLzIwMTUvMDIvZGF0YS13cmFuZ2xpbmctY2hlYXRzaGVldC5wZGYpDQoNCg0KSW4gb3VyIGNhc2Ugd2Ugd291bGQgbGlrZSB0byByZXRhaW4gYWxsIG9mIHRoZSB2YWx1ZXMgb2YgYGRpZXRfZGF0YWAgYW5kIGBzZXBfYWdlX2RpZXRfZGF0YWAuIFdlIHdvdWxkIGxpa2UgdG8gYWRkIG5ldyBjb2x1bW5zIG9mIHZhbHVlcyB0byB0aGVzZSB0aWJibGVzIHRoYXQgY29ycmVzcG9uZCB0byB0aGUgZ3VpZGVsaW5lIGluZm9ybWF0aW9uIGFib3V0IGFtb3VudHMgb2YgY29uc3VtcHRpb24gZm9yIGVhY2ggZm9vZCB0eXBlIGluIHRoZSBgZ3VpZGVsaW5lc2AgdGliYmxlLiBXZSBzaG91bGRuJ3QgaGF2ZSBhbnkgdmFsdWVzIG9mIGBmb29kYCBpbiBgZ3VpZGVsaW5lc2AgdGhhdCBkb24ndCBtYXRjaCwgc28gd2Ugd2lsbCBub3QgZ2V0IGFueSBgTkFgIHZhbHVlcy4gVGhlcmVmb3JlLCBpbiBvdXIgY2FzZSBhbnkgb2YgdGhlIG11dGF0aW5nIGpvaW4gZnVuY3Rpb25zIHNob3VsZCByZXN1bHQgaW4gdGhlIHNhbWUgb3V0cHV0Lg0KDQpJdCdzIGltcG9ydGFudCB0byBjaGVjayBpZiB3ZSBoYXZlIGFueSBvdmVybGFwcGluZyB2YXJpYWJsZSBuYW1lcyBiZWZvcmUgd2Ugam9pbiB0aGUgZGF0YS4gT3RoZXJ3aXNlLCB0aGVzZSBjb2x1bW5zIHdpbGwgZWl0aGVyIGJlIHVzZWQgdG8gaWRlbnRpZnkgd2hpY2ggcm93cyB0byBqb2luLCBvciBuZXcgY29waWVzIG9mIHRoZSBjb2x1bW5zLCB3aXRoIGEgZGVmYXVsdCBuYW1lIHRvIGRpc3Rpbmd1aXNoIHRoZSBjb2x1bW5zIG9mIG9uZSBkYXRhIHNldCBmcm9tIHRob3NlIG9mIHRoZSBvdGhlciwgd2lsbCBiZSBjcmVhdGVkLiBXZSBjYW4gdXNlIHRoZSBiYXNlIFIgZnVuY3Rpb24gYG5hbWVzKClgICBhbmQgdGhlIGBpbnRlcnNlY3QoKWAgZnVuY3Rpb24gb2YgdGhlIGBkcGx5cmAgcGFja2FnZSB0byBpZGVudGlmeSB3aGljaCBjb2x1bW4gbmFtZXMgYXJlIGNvbW1vbiB0byBvdXIgdHdvIGRhdGEgc2V0cy4NCg0KYGBge3J9DQpkcGx5cjo6aW50ZXJzZWN0KA0KICBuYW1lcyhkaWV0X2RhdGEpLA0KICBuYW1lcyhndWlkZWxpbmVzKQ0KKQ0KYGBgDQoNClNvIGl0IGxvb2tzIGxpa2UgdGhlIGAidXBwZXIiYCAsIGAibG93ZXIiYCBhbmQgYCJ1bml0ImAgdmFyaWFibGUgbmFtZXMgYXJlIG92ZXJsYXBwaW5nLiBUaGVyZWZvcmUsIHRvIGRpc3Rpbmd1aXNoIHRoZSBuYW1lcyBsYXRlciB3ZSB3aWxsIHJlbmFtZSB0aGUgZ3VpZGVsaW5lIGAidXBwZXIiYCAsIGAibG93ZXIiYCBhbmQgYCJ1bml0ImAgdmFyaWFibGVzLg0KDQpXZSB3aWxsIGFnYWluIHVzZSB0aGUgYHJlbmFtZWAgZnVuY3Rpb24gZnJvbSB0aGUgYGRwbHlyYCBwYWNrYWdlLiBXZSBjYW4gbGlzdCBtdWx0aXBsZSB2YXJpYWJsZXMgdG8gcmVuYW1lIGFuZCBzZXBhcmF0ZSBlYWNoIHdpdGggYSBjb21tYS4gV2UgbmVlZCB0byBsaXN0IHRoZSBuZXcgbmFtZXMgZmlyc3QuDQoNCmBgYHtyfQ0KZ3VpZGVsaW5lcyAlPD4lDQogIHJlbmFtZSgNCiAgICB1cHBlcl9vcHRpbWFsID0gdXBwZXIsDQogICAgbG93ZXJfb3B0aW1hbCA9IGxvd2VyLA0KICAgIHVuaXRfb3B0aW1hbCA9IHVuaXQNCiAgKQ0KDQpndWlkZWxpbmVzDQpgYGANCg0KSXQgaXMgYWxzbyBhIGdvb2QgaWRlYSB0byBjaGVjayBvdXIgdW5pdHMgdG8gbWFrZSBzdXJlIHRoZXkgYXJlIHRoZSBzYW1lIGZvciBib3RoIGBndWlkZWxpbmVzYCBhbmQgdGhlIG9ic2VydmVkIGNvbnN1bXB0aW9uIHRpYmJsZXMoYGRpZXRfYW5kX2d1aWRlbGluZXNgIGFuZCBgYWxsX2FnZV9kaWV0X2FuZF9ndWlkZWxpbmVzYCkuDQoNCkxldCdzIHRha2UgYSBsb29rIHdpdGggdGhlIGBjb3VudCgpYCBmdW5jdGlvbiBvZiB0aGUgYGRwbHlyYCBwYWNrYWdlLiBXZSB3aWxsIGFsc28gdXNlIHRoZSBgYmluZF9jb2xzKClgIGZ1bmN0aW9uIG9mIGBkcGx5cmAgdG8gcHV0IHRoZSBkYXRhIHRvZ2V0aGVyIHNvIHRoYXQgd2UgY2FuIHNlZSBpdCBlYXNpbHkuDQoNCmBgYHtyfQ0KZHBseXI6OmJpbmRfY29scygNCiAgY291bnQoZGlldF9kYXRhLCB1bml0LCBmb29kKSwNCiAgY291bnQoc2VwX2FnZV9kaWV0X2RhdGEsIHVuaXQsIGZvb2QpLA0KICBjb3VudChndWlkZWxpbmVzLCB1bml0X29wdGltYWwsIGZvb2QpDQopDQpgYGANCg0KV2UgY2FuIHNlZSB0aGF0IHRoZSBvbmx5IHBvdGVudGlhbCBpc3N1ZSBpcyB0aGUgYHNlYWZvb2Qgb21lZ2EtMyBmYXR0eSBhY2lkc2AgZGF0YSB3aGljaCBpcyBpbiBnL2RheSBmb3IgdGhlIG9ic2VydmVkIGRhdGEoYGRpZXRfZGF0YWAgYW5kIGBhbGxfYWdlX2RpZXRfYW5kX2d1aWRlbGluZXNgKSwgYnV0IGluIG1nL2RheSBpbiB0aGUgYGd1aWRlbGluZXNgIGRhdGEuDQoNCldlIGNhbiBhY2NvdW50IGZvciB0aGlzIGJ5IGRpdmlkaW5nIHRoZSBgZ3VpZGVsaW5lc2AgYHNlYWZvb2Qgb21lZ2EtMyBmYXR0eSBhY2lkcyBkYXRhYCBieSAxMDAwIHRvIGNvbnZlcnQgaXQgdG8gZ3JhbXMgZnJvbSBtaWxsaWdyYW1zLg0KDQpUbyBkbyB0aGlzIHdlIHdpbGwgdXNlIHRoZSBgaWZfZWxzZSgpYCBmdW5jdGlvbiBpbiB0aGUgYGRwbHlyYCBwYWNrYWdlLiBUaGlzIGFsbG93cyB1cyB0byBzcGVjaWZ5IGEgY29uZGl0aW9uIChpbiB0aGlzIGNhc2UgaWYgdGhlIHVuaXQgaXMgYCJtZyJgKSwgYXMgd2VsbCBhcyB2YWx1ZXMgaWYgdGhpcyBjb25kaXRpb24gaXMgbWV0ICh0cnVlKSwgb3IgaWYgdGhlIGNvbmRpdGlvbiBpcyBub3QgbWV0IChmYWxzZSkuIA0KDQpJbiB0aGUgZm9sbG93aW5nIHdlIG11dGF0ZSB0aGUgdmFsdWVzIGluIGVhY2ggb2YgdGhlIGd1aWRlbGluZSBudW1lcmljIGNvbHVtbnMgKGBsb3dlcmAsIGBvcHRpbWFsYCBhbmQgYHVwcGVyYCkgb25lIGF0IGEgdGltZS4gV2hlbiB3ZSByZWZlciB0byBgbG93ZXJgIGZvciBleGFtcGxlIHdlIHJlZmVyIHRvIHRoZSB2YWx1ZXMgaW4gdGhlIGNvbHVtbi92YXJpYWJsZS4gU28gaWYgdGhlIGNvbmRpdGlvbiBpcyBub3QgbWV0LCB0aGVuIHRoZSBvcmlnaW5hbCB2YWx1ZSBpcyByZXRhaW5lZC4gV2Ugd2lsbCBhbHNvIHJlcGxhY2UgYCJtZyJgIHdpdGggYCJnImAgYWZ0ZXIgZXZlcnl0aGluZyBpcyBjb252ZXJ0ZWQgdG8gZ3JhbXMuDQoNCg0KDQpgYGB7cn0NCiMgImxvd2VyX29wdGltYWwiIHZhcmlhYmxlDQpndWlkZWxpbmVzICU8PiUNCiAgbXV0YXRlKGxvd2VyX29wdGltYWwgPSBkcGx5cjo6aWZfZWxzZSgNCiAgICBjb25kaXRpb24gPSB1bml0X29wdGltYWwgPT0gIm1nIiwNCiAgICB0cnVlID0gbG93ZXJfb3B0aW1hbCAvIDEwMDAsDQogICAgZmFsc2UgPSBsb3dlcl9vcHRpbWFsDQogICkpDQojIEV4cGxhbmF0aW9uIGZvciB0aGUgdXNlIG9mICJpZl9lbHNlKCkiIGhlcmUNCiMgSWYgdGhlICJ1bml0X29wdGltYWwiIHZhcmlhYmxlIGlzIGluICJtZyIsIHdlIGNvbnZlcnQgdGhlIGNvcnJlc3BvbmRpbmcgImxvd2VyX29wdGltYWwiIChjdXJyZW50bHkgaW4gbWcpIHZhcmlhYmxlIHRvIGdyYW1zIChnKSBieSBkaXZpZGluZyBieSAxLDAwMC4NCiMgSWYgbm90LCB0aGUgY29ycmVzcG9uZGluZyAibG93ZXJfb3B0aW1hbCIgKGFscmVhZHkgaW4gZykgaXMgbm90IGNoYW5nZWQNCg0KIyAib3B0aW1hbCIgdmFyaWFibGUNCmd1aWRlbGluZXMgJTw+JQ0KICBtdXRhdGUob3B0aW1hbCA9IGlmX2Vsc2UoY29uZGl0aW9uID0gdW5pdF9vcHRpbWFsID09ICJtZyIsDQogICAgdHJ1ZSA9IG9wdGltYWwgLyAxMDAwLA0KICAgIGZhbHNlID0gb3B0aW1hbA0KICApKQ0KDQojICJ1cHBlcl9vcHRpbWFsIiB2YXJpYWJsZQ0KZ3VpZGVsaW5lcyAlPD4lDQogIG11dGF0ZSh1cHBlcl9vcHRpbWFsID0gaWZfZWxzZShjb25kaXRpb24gPSB1bml0X29wdGltYWwgPT0gIm1nIiwNCiAgICB0cnVlID0gdXBwZXJfb3B0aW1hbCAvIDEwMDAsDQogICAgZmFsc2UgPSB1cHBlcl9vcHRpbWFsDQogICkpDQoNCiMgcmVwbGFjZSAibWciIHdpdGggImciIGluIHRoZSAidW5pdF9vcHRpbWFsIiB2YXJpYWJsZQ0KZ3VpZGVsaW5lcyAlPD4lDQogIG11dGF0ZSh1bml0X29wdGltYWwgPSBpZl9lbHNlKGNvbmRpdGlvbiA9IHVuaXRfb3B0aW1hbCA9PSAibWciLA0KICAgIHRydWUgPSAiZyIsDQogICAgZmFsc2UgPSB1bml0X29wdGltYWwNCiAgKSkNCg0KZ3VpZGVsaW5lcw0KYGBgDQoNCg0KKioqDQo8ZGV0YWlscz4gPHN1bW1hcnk+IENsaWNrIGhlcmUgdG8gc2VlIGEgY291cGxlIG9mIG90aGVyIHdheXMgdG8gZG8gdGhpczogPC9zdW1tYXJ5Pg0KDQpgYGB7ciwgZXZhbCA9IEZBTFNFfQ0KIyBBbm90aGVyIHBvc3NpYmxlIHdheSB3aXRoIGRwbHlyOjpjYXNlX3doZW4oKToNCmd1aWRlbGluZXMgJTw+JQ0KICBtdXRhdGUobG93ZXJfb3B0aW1hbCA9IGNhc2Vfd2hlbigNCiAgICB1bml0X29wdGltYWwgPT0gIm1nIiB+IGxvd2VyX29wdGltYWwgLyAxMDAwLA0KICAgIHVuaXRfb3B0aW1hbCAhPSAibWciIH4gbG93ZXJfb3B0aW1hbA0KICApKQ0KDQojIE9yIGNvdWxkIHVzZSB0aGlzOg0KZ3VpZGVsaW5lcyAlPD4lDQogIG11dGF0ZV9hdCgNCiAgICB2YXJzKHVuaXRfb3B0aW1hbCksDQogICAgfiBzdHJfcmVwbGFjZSgNCiAgICAgIHN0cmluZyA9IC4sDQogICAgICBwYXR0ZXJuID0gIm1nIiwNCiAgICAgIHJlcGxhY2VtZW50ID0gImciDQogICAgKQ0KICApDQpgYGANCg0KPC9kZXRhaWxzPg0KDQoqKioNCg0KDQoNCkluIGNvbnRyYXN0IHdlIGNvdWxkIGhhdmUgY2hhbmdlZCBvciBtdXRhdGVkIHRoZSB2YWx1ZXMgZm9yIGBsb3dlcl9vcHRpbWFsYCwgYG9wdGltYWxgLCBgdXBwZXJfb3B0aW1hbGAgYWxsIGF0IG9uY2UgbGlrZSB0aGlzIHVzaW5nIHRoZSBgZnVucygpYCBhcmd1bWVudCBpbiBgbXV0YXRlX2F0KClgIG9mIGBkcGx5cmAuDQoNCmBgYHtyLCBldmFsID0gRkFMU0V9DQpndWlkZWxpbmVzW3N0cl93aGljaCgNCiAgc3RyaW5nID0gZ3VpZGVsaW5lc1tbImZvb2QiXV0sDQogIHBhdHRlcm4gPSAic2VhZm9vZCBvbWVnYS0zIGZhdHR5IGFjaWRzIg0KKSwgXSA8LSBndWlkZWxpbmVzICU+JQ0KICBmaWx0ZXIoZm9vZCA9PSAic2VhZm9vZCBvbWVnYS0zIGZhdHR5IGFjaWRzIikgJT4lDQogIG11dGF0ZV9hdCh2YXJzKGxvd2VyX29wdGltYWw6dXBwZXJfb3B0aW1hbCksIGZ1bnMoLiAvIDEwMDApKQ0KYGBgDQoNCg0KTm93IHdlIGFyZSByZWFkeSB0byBqb2luIHRoZSBkYXRhIQ0KDQpBZ2Fpbiwgd2Ugd291bGQgbGlrZSB0byBhZGQgbmV3IGNvbHVtbnMgb2YgdmFsdWVzIHRvIGBkaWV0X2RhdGFgIGFuZCBgYWxsX2FnZV9kaWV0X2FuZF9ndWlkZWxpbmVzYCB0aGF0IGNvcnJlc3BvbmQgdG8gdGhlIGd1aWRlbGluZSBpbmZvcm1hdGlvbiBhYm91dCBhbW91bnRzIG9mIGNvbnN1bXB0aW9uIGZvciBlYWNoIGZvb2QgdHlwZSBpbiB0aGUgYGd1aWRlbGluZXNgIHRpYmJsZS4gU28gd2Ugd2lsbCBqb2luIHRoZSBkYXRhIGJhc2VkIG9uIHRoZSBgZm9vZGAgdmFyaWFibGUgdmFsdWVzLiBXZSB3aWxsIHVzZSB0aGUgYGZ1bGxfam9pbigpYCBmdW5jdGlvbiBvZiB0aGUgYGRwbHlyYCBwYWNrYWdlLg0KDQpgYGB7cn0NCmRpZXRfYW5kX2d1aWRlbGluZXMgPC0gZGlldF9kYXRhICU+JQ0KICBkcGx5cjo6ZnVsbF9qb2luKGd1aWRlbGluZXMsIGJ5ID0gImZvb2QiKQ0KDQphbGxfYWdlX2RpZXRfYW5kX2d1aWRlbGluZXMgPC0gc2VwX2FnZV9kaWV0X2RhdGEgJT4lDQogIGZ1bGxfam9pbihndWlkZWxpbmVzLCBieSA9ICJmb29kIikNCg0KZ2xpbXBzZShkaWV0X2FuZF9ndWlkZWxpbmVzKQ0KZ2xpbXBzZShhbGxfYWdlX2RpZXRfYW5kX2d1aWRlbGluZXMpDQpgYGANCg0KSXQncyBhbHdheXMgYSBnb29kIGlkZWEgdG8gY2hlY2sgdGhhdCB0aGUgdmFsdWVzIGFyZSB3aGF0IHlvdSBleHBlY3QgYWZ0ZXIgbWVyZ2luZy4gDQoNCmBgYHtyfQ0KZGlldF9hbmRfZ3VpZGVsaW5lcyAlPiUNCiAgY291bnQoZm9vZCwgb3B0aW1hbCkNCg0KYWxsX2FnZV9kaWV0X2FuZF9ndWlkZWxpbmVzICU+JQ0KICBjb3VudChmb29kLCBvcHRpbWFsKQ0KDQojIEZvciBlYXN5IGNvbXBhcmlzb24gd2Ugd2lsbCBhcnJhbmdlIGJ5IGZvb2QgYWxwaGFiZXRpY2FsbHkNCmFycmFuZ2UoZ3VpZGVsaW5lcywgZm9vZCkNCmBgYA0KDQpMb29rcyBnb29kIQ0KIA0KIA0KIyMjICoqQ2FsY3VsYXRpbmcgcmVsYXRpdmUgY29uc3VtcHRpb24qKg0KKioqDQoNClJlY2FsbCB0aGF0IG91ciBhaW0gaXMgdG8gY29tcGFyZSB0aGUgY29uc3VtcHRpb24gcmF0ZXMgb2YgdGhlc2UgZGlldGFyeSBmYWN0b3JzIGJ5IGRpZmZlcmVudCBncm91cHMgb2YgcGVvcGxlLCBhbmQgaWRlYWxseSwgdG8gZmFjaWxpdGF0ZSBjcm9zcy1mYWN0b3IgY29tcGFyaXNvbnMsIHdlIHdhbnQgdG8gY29uc2lkZXIgY29uc3VtcHRpb24gcmF0ZXMgcmVsYXRpdmUgdG8gdGhlIG9wdGltYWwgZ3VpZGVsaW5lcy4NCg0KVG8gZG8gdGhpcywgbGV0J3MgY2FsY3VsYXRlIHZhbHVlcyBvZiBjb25zdW1wdGlvbiB0aGF0IGFyZSByZWxhdGl2ZSB0byB0aGUgc3VnZ2VzdGVkIGd1aWRlbGluZXMuDQoNClRoZXJlIGFyZSBhIGZldyBhcHByb2FjaGVzIHdlIGNvdWxkIHRha2UuIE9uZSBpcyB0byBjYWxjdWxhdGUgYSBgInBlcmNlbnRhZ2Ugb2Ygb3B0aW1hbCBjb25zdW1wdGlvbiJgIHVzaW5nIHRoZSBtZWFuIHZhbHVlIGZvciBlYWNoIG9ic2VydmVkIGZhY3RvciByZWxhdGl2ZSB0byBpdHMgb3B0aW1hbCB2YWx1ZS4gVG8gZG8gdGhpcyB3ZSB3aWxsIHVzZSB0aGUgYG11dGF0ZSgpYCBmdW5jdGlvbiBvZiB0aGUgYGRwbHlyYHBhY2thZ2UuIFRoaXMgd2lsbCBjcmVhdGUgYSBuZXcgdmFyaWFibGUgY2FsbGVkIGBSZWxhdGl2ZV9QZXJjZW50YCB0aGF0IHdpbGwgYmUgZXF1YWwgdG8gdGhlIHJhdGlvIG9mIHRoZSBgbWVhbmAgdmFsdWUgYW5kIHRoZSBgb3B0aW1hbGAgdmFsdWUsIG11bHRpcGxpZWQgYnkgMTAwLCB0byBjcmVhdGUgYSBwZXJjZW50YWdlIHJlbGF0aXZlIHRvIHRoZSBvcHRpbWFsIGFtb3VudCBzdWdnZXN0ZWQuDQoNCmBgYHtyfQ0KZGlldF9hbmRfZ3VpZGVsaW5lcyAlPD4lDQogIG11dGF0ZShSZWxhdGl2ZV9QZXJjZW50ID0gKG1lYW4gLyBvcHRpbWFsKSAqIDEwMCkNCg0KYWxsX2FnZV9kaWV0X2FuZF9ndWlkZWxpbmVzICU8PiUNCiAgbXV0YXRlKFJlbGF0aXZlX1BlcmNlbnQgPSAobWVhbiAvIG9wdGltYWwpICogMTAwKQ0KYGBgDQoNCkFub3RoZXIgb3B0aW9uIGlzIHRvIGluY29ycG9yYXRlIHRoZSByYW5nZSBvZiBvcHRpbWFsIGludGFrZXMgYW5kIHRoZSBkaXJlY3Rpb24gdGhhdCBpcyBhc3NvY2lhdGVkIHdpdGggaGVhbHRoIHJpc2suIElmIHRoZSBkaXJlY3Rpb24gb2YgcmlzayBpcyBgaGlnaGAgYW5kIHRoZSBjb25zdW1wdGlvbiB3YXMgZ3JlYXRlciB0aGFuIHRoZSBgb3B0aW1hbGAgbWVhbiB2YWx1ZSwgdGhhbiB0aGUgcGVyY2VudGFnZSBpcyBjYWxjdWxhdGVkIGJhc2VkIG9uIHRoZSBgdXBwZXJfb3B0aW1hbGAgdmFsdWUsIHdoaWxlIGlmIHRoZSBkaXJlY3Rpb24gb2YgcmlzayBpcyBgbG93YCBhbmQgdGhlIGNvbnN1bXB0aW9uIGlzIGxlc3MgdGhhbiB0aGUgYG9wdGltYWxgIG1lYW4gdmFsdWUsIHRoZW4gdGhlIHBlcmNlbnRhZ2UgaXMgY2FsY3VsYXRlZCBiYXNlZCBvbiB0aGUgYGxvd2VyX29wdGltYWxgIHZhbHVlLiBXZSB3aWxsIHVzZSB0aGUgYGNhc2Vfd2hlbigpYCBmdW5jdGlvbiBvZiB0aGUgYGRwbHlyYCBwYWNrYWdlIHRvIGRvIHRoaXMuIFRoaXMgYWxsb3dzIHVzIHRvIHNwZWNpZnkgdmFsdWVzIChpbmRpY2F0ZWQgb24gdGhlIHJpZ2h0IHNpZGUgb2YgdGhlIGB+YHN5bWJvbCkgYmFzZWQgb24gc3BlY2lmaWMgY29uZGl0aW9ucyAoaW5kaWNhdGVkIG9uIHRoZSBsZWZ0IHNpZGUgb2YgdGhlIGB+YCBzeW1ib2wpLiBXZSBjYW4gc3BlY2lmeSBtdWx0aXBsZSBjb25kaXRpb25zIHVzaW5nIHRoZSBgJmAgc3ltYm9sLg0KDQpgYGB7cn0NCmRpZXRfYW5kX2d1aWRlbGluZXMgJTw+JQ0KICBtdXRhdGUocmFuZ2VfcGVyY2VudCA9IGNhc2Vfd2hlbigNCiAgICBkaXJlY3Rpb24gPT0gImhpZ2giIH4gKG1lYW4gLyB1cHBlcl9vcHRpbWFsKSAqIDEwMCwNCiAgICBkaXJlY3Rpb24gPT0gImxvdyIgfiAobWVhbiAvIGxvd2VyX29wdGltYWwpICogMTAwDQogICkpDQoNCmFsbF9hZ2VfZGlldF9hbmRfZ3VpZGVsaW5lcyAlPD4lDQogIG11dGF0ZShyYW5nZV9wZXJjZW50ID0gY2FzZV93aGVuKA0KICAgIGRpcmVjdGlvbiA9PSAiaGlnaCIgfiAobWVhbiAvIHVwcGVyX29wdGltYWwpICogMTAwLA0KICAgIGRpcmVjdGlvbiA9PSAibG93IiB+IChtZWFuIC8gbG93ZXJfb3B0aW1hbCkgKiAxMDANCiAgKSkNCg0KDQpkaWV0X2FuZF9ndWlkZWxpbmVzICU8PiUNCiAgbXV0YXRlKHBlcmNlbnRfb3Zlcl91bmRlciA9IGNhc2Vfd2hlbigNCiAgICBkaXJlY3Rpb24gPT0gImhpZ2giICYgbWVhbiA+IHVwcGVyX29wdGltYWwgfg0KICAgICgobWVhbiAtIHVwcGVyX29wdGltYWwpIC8gdXBwZXJfb3B0aW1hbCkgKiAxMDAsDQogICAgZGlyZWN0aW9uID09ICJoaWdoIiAmIG1lYW4gPD0gdXBwZXJfb3B0aW1hbCB+IDAsDQogICAgZGlyZWN0aW9uID09ICJsb3ciICYgbWVhbiA+PSBsb3dlcl9vcHRpbWFsIH4gMCwNCiAgICBkaXJlY3Rpb24gPT0gImxvdyIgJiBtZWFuIDwgbG93ZXJfb3B0aW1hbCB+DQogICAgKChsb3dlcl9vcHRpbWFsIC0gbWVhbikgLyBsb3dlcl9vcHRpbWFsKSAqIC0xMDANCiAgKSkNCg0KDQphbGxfYWdlX2RpZXRfYW5kX2d1aWRlbGluZXMgJTw+JQ0KICBtdXRhdGUocGVyY2VudF9vdmVyX3VuZGVyID0gY2FzZV93aGVuKA0KICAgIGRpcmVjdGlvbiA9PSAiaGlnaCIgJiBtZWFuID4gdXBwZXJfb3B0aW1hbCB+DQogICAgKChtZWFuIC0gdXBwZXJfb3B0aW1hbCkgLyB1cHBlcl9vcHRpbWFsKSAqIDEwMCwNCiAgICBkaXJlY3Rpb24gPT0gImhpZ2giICYgbWVhbiA8PSB1cHBlcl9vcHRpbWFsIH4gMCwNCiAgICBkaXJlY3Rpb24gPT0gImxvdyIgJiBtZWFuID49IGxvd2VyX29wdGltYWwgfiAwLA0KICAgIGRpcmVjdGlvbiA9PSAibG93IiAmIG1lYW4gPCBsb3dlcl9vcHRpbWFsIH4NCiAgICAoKGxvd2VyX29wdGltYWwgLSBtZWFuKSAvIGxvd2VyX29wdGltYWwpICogLTEwMA0KICApKQ0KYGBgDQoNCkFub3RoZXIgb3B0aW9uIGlzIHRvIGNyZWF0ZSBhIGJpbmFyeSBvdXRjb21lIGluZGljYXRpbmcgd2hldGhlciBvcHRpbWFsIGNvbnN1bXB0aW9uIHdhcyBhY2hpZXZlZCBvciBub3QuDQoNCmBgYHtyfQ0KDQpkaWV0X2FuZF9ndWlkZWxpbmVzICU8PiUNCiAgbXV0YXRlKG9wdF9hY2hpZXZlZCA9IGlmX2Vsc2UoDQogICAgY29uZGl0aW9uID0gZGlyZWN0aW9uID09ICJsb3ciICYgbWVhbiA+IGxvd2VyX29wdGltYWwgfA0KICAgICAgZGlyZWN0aW9uID09ICJoaWdoIiAmIG1lYW4gPCB1cHBlcl9vcHRpbWFsLA0KICAgIHRydWUgPSAiWWVzIiwNCiAgICBmYWxzZSA9ICJObyINCiAgKSkNCg0KYWxsX2FnZV9kaWV0X2FuZF9ndWlkZWxpbmVzICU8PiUNCiAgbXV0YXRlKG9wdF9hY2hpZXZlZCA9IGlmX2Vsc2UoDQogICAgY29uZGl0aW9uID0gZGlyZWN0aW9uID09ICJsb3ciICYgbWVhbiA+IGxvd2VyX29wdGltYWwgfA0KICAgICAgZGlyZWN0aW9uID09ICJoaWdoIiAmIG1lYW4gPCB1cHBlcl9vcHRpbWFsLA0KICAgIHRydWUgPSAiWWVzIiwNCiAgICBmYWxzZSA9ICJObyINCiAgKSkNCg0KZ2xpbXBzZShkaWV0X2FuZF9ndWlkZWxpbmVzKQ0KZ2xpbXBzZShhbGxfYWdlX2RpZXRfYW5kX2d1aWRlbGluZXMpDQpgYGANCg0KT25lIGxhc3QgdGhpbmcgdGhhdCBjYW4gYmUgdXNlZnVsIHdpdGggZGF0YSB3cmFuZ2xpbmcgaXMgdG8gKipyZXNoYXBlKiogdGhlIGRhdGEgaW50byB3aGF0IGlzIGNhbGxlZCB0aGUgKipsb25nKiogZm9ybWF0LiBUaGlzIGlzIHZlcnkgdXNlZnVsIGZvciBjcmVhdGluZyB2aXN1YWxpemF0aW9ucyB3aXRoIGEgcG93ZXJmdWwgYW5kIGZsZXhpYmxlIHBhY2thZ2UgY2FsbGVkIGBnZ3Bsb3QyYC4NCg0KVG8gY29lcmNlIGFuIG9iamVjdCBpbnRvIGxvbmcgZm9ybWF0LCB3ZSBjcmVhdGUgKiptb3JlIHJvd3MgYW5kIGZld2VyIGNvbHVtbnMqKi4gRm9yIG1vcmUgaW5mb3JtYXRpb24gYWJvdXQgdGhpcywgcGxlYXNlIHNlZSB0aGUgRGF0YSBWaXN1YWxpemF0aW9uIHNlY3Rpb24gb2YgdGhpcyBbY2FzZSBzdHVkeV0oaHR0cHM6Ly93d3cub3BlbmNhc2VzdHVkaWVzLm9yZy9vY3MtYnAtb3Bpb2lkLXJ1cmFsLXVyYmFuLyNSdXJhbF9hbmRfVXJiYW5fYXJlYXMvIzp+OnRleHQ9JTIwdGhlJTIwZGF0YSUyMHdhcyUyMHByZXNlbnRlZCUyMGluJTIwYSUyMGZvcm1hdCUyMHRoYXQlMjBpcyUyMGNhbGxlZCUyMGxvbmclMjBmb3JtYXQuKXt0YXJnZXQ9Il9ibGFuayJ9Lg0KDQpXZSB3b3VsZCBsaWtlIHRvIHB1dCB0b2dldGhlciB0aGUgZGlmZmVyZW50IHR5cGVzIG9mIHBlcmNlbnRhZ2VzIG9mIHRoZSBvcHRpbWFsIGludGFrZSB0aGF0IHdlIGp1c3QgY2FsY3VsYXRlZC4NCg0KVG8gZ2V0IG91ciBkYXRhIGluIGxvbmcgZm9ybWF0IHdlIGNhbiB1c2UgdGhlIGBwaXZvdF9sb25nZXIoKWAgZnVuY3Rpb24gb2YgdGhlIGB0aWR5cmAgcGFja2FnZS4gV2Ugd2lsbCBsaXN0IHRoZSBjb2x1bW5zIHRoYXQgd2Ugd2FudCB0byBjb21lIHRvZ2V0aGVyIGludG8gdGhlIGxvbmdlciBmb3JtYXQgdXNpbmcgdGhlIGBjb2xzYCBhcmd1bWVudC4gVGhlIGBuYW1lc190b2AgYXJndW1lbnQgaW5kaWNhdGVzIHRoZSBuYW1lIG9mIHRoZSB2YXJpYWJsZSB0aGF0IHdpbGwgaW5jbHVkZSB0aGUgY2hhcmFjdGVyIGluZm9ybWF0aW9uIGFib3V0IHRoZSB2YWx1ZXMgdGhhdCB3ZSBhcmUgY29uc29saWRhdGluZywgaS5lLiwgdGhpcyB2YXJpYWJsZSBjb250YWlucyB0aGUgbmFtZXMgb2YgdGhlIGNvbHVtbnMgdGhhdCB3ZSBhcmUgYnJpbmdpbmcgdG9nZXRoZXIuIFRoZSBgdmFsdWVzX3RvYCBpcyB0aGUgbmFtZSBvZiB0aGUgY29sdW1uIHRoYXQgd2lsbCBjb250YWluIHRoZSB2YWx1ZXMgb2YgdGhlIGNvbHVtbnMgd2UgYXJlIGNvbnNvbGlkYXRpbmcuIFdlIGNhbiB1c2UgYGNvbnRhaW5zKClgIG9mIHRoZSBgdGlkeXJgIHBhY2thZ2UgdG8gbG9vayBhdCB0aGUgdmFyaWFibGVzIHdpdGggbmFtZXMgdGhhdCBjb250YWluIGAicGVyY2VudCJgIC4NCg0KYGBge3J9DQpkaWV0X2FuZF9ndWlkZWxpbmVzX2xvbmcgPC0gZGlldF9hbmRfZ3VpZGVsaW5lcyAlPiUNCiAgcGl2b3RfbG9uZ2VyKA0KICAgIGNvbHMgPSBjb250YWlucygicGVyY2VudCIpLA0KICAgIG5hbWVzX3RvID0gInBlcmNlbnRfdHlwZSIsDQogICAgdmFsdWVzX3RvID0gInBlcmNlbnQiDQogICkNCmBgYA0KDQoqKioNCjxkZXRhaWxzPiA8c3VtbWFyeT4gQ2xpY2sgaGVyZSB0byBzZWUgaG93IHRoaXMgd291bGQgYmUgZG9uZSB3aXRoIHRoZSBvbGRlciB2ZXJzaW9uIG9mIHRoaXMgZnVuY3Rpb24sIGNhbGxlZCBgZ2F0aGVyKClgOiA8L3N1bW1hcnk+DQoNClJlY2FsbCB0aGF0IGZvciBgcGl2b3RfbG9uZ2VyKClgLCB0aGUgYGNvbHNgIGFyZ3VtZW50IGlzIHVzZWQuIEZvciBgZ2F0aGVyKClgIHdlIHdvdWxkIHNpbXBseSBsaXN0IHRoZSB2YXJpYWJsZXMgdGhhdCB3ZSB3aXNoIHRvIGNvbnNvbGlkYXRlLiBUaGUgYG5hbWVzX3RvYCBhbmQgYHZhbHVlc190b2AgYXJndW1lbnRzIG9mIGBwaXZvdF9sb25nZXIoKWAgYXJlIGVxdWl2YWxlbnQgdG8gdGhlIGBrZXlgIGFuZCBgdmFsdWVgIGFyZ3VtZW50cyBpbiBgZ2F0aGVyKClgIHJlc3BlY3RpdmVseS4NCg0KV2Ugd291bGQgZ2V0IGFuIGlkZW50aWNhbCBvdXRwdXQgZnJvbSB0aGUgdHdvIG1ldGhvZHMuIFdlIGNhbiBjaGVjayB0aGF0IHdpdGggYHNldGVxdWFsKClgLg0KYGBge3J9DQpkaWV0X2FuZF9ndWlkZWxpbmVzX2xvbmcyIDwtIGRpZXRfYW5kX2d1aWRlbGluZXMgJT4lDQogIGdhdGhlcihjb250YWlucygicGVyY2VudCIpLA0KICAgIGtleSA9IHBlcmNlbnRfdHlwZSwNCiAgICB2YWx1ZSA9IHBlcmNlbnQNCiAgKQ0KDQpzZXRlcXVhbChkaWV0X2FuZF9ndWlkZWxpbmVzX2xvbmcsIGRpZXRfYW5kX2d1aWRlbGluZXNfbG9uZzIpDQpgYGANCg0KPC9kZXRhaWxzPg0KDQoqKioNCg0KTGV0J3MgZG8gdGhlIHNhbWUgZm9yIHRoZSBhZ2Ugc2VwYXJhdGVkIGRhdGEuDQoNCmBgYHtyfQ0KYWxsX2FnZV9kaWV0X2FuZF9ndWlkZWxpbmVzX2xvbmcgPC0gYWxsX2FnZV9kaWV0X2FuZF9ndWlkZWxpbmVzICU+JQ0KICBwaXZvdF9sb25nZXIoDQogICAgY29scyA9IGNvbnRhaW5zKCJwZXJjZW50IiksDQogICAgbmFtZXNfdG8gPSAicGVyY2VudF90eXBlIiwNCiAgICB2YWx1ZXNfdG8gPSAicGVyY2VudCINCiAgKQ0KYGBgDQoNCldlIG5vdyBoYXZlIHRoZSBtYWluIHZhcmlhYmxlcyBhbmQgZGF0YSBmb3JtYXRzIHRoYXQgd2UgbmVlZCB0byBwcm9jZWVkIHdpdGggdGhlIG5leHQgc3RlcHMgb2Ygb3VyIGFuYWx5c2lzLCBpbmNsdWRpbmcgZGF0YSBleHBsb3JhdGlvbiBhbmQgZXZlbnR1YWxseSwgbW9kZWxpbmcuDQoNCk5vdyB3ZSB3aWxsIHNhdmUgb3VyIHdyYW5nbGVkIGRhdGEuIFdlIHdpbGwgc2F2ZSBpdCBhcyBhbiByZGEgZmlsZSBmb3Igb3Vyc2VsdmVzIGFuZCBhcyBjc3YgZmlsZXMsIGFzIHRoaXMgaXMgb2Z0ZW4gYSBnb29kIG9wdGlvbiBmb3IgY29sbGFib3JhdG9ycy4gV2UgbmVlZCBhIHNlcGFyYXRlIGNzdiBmaWxlIGZvciBlYWNoIHRpYmJsZS4gV2Ugd2lsbCBzYXZlIHRoZXNlIGZpbGVzIGluIGEgZGlyZWN0b3J5IGNhbGxlZCAid3JhbmdsZWQiIHdpdGhpbiBvdXIgImRhdGEiIGRpcmVjdG9yeSBvZiBvdXIgcHJvamVjdC4NCg0KYGBge3J9DQpzYXZlKGFsbF9hZ2VfZGlldF9hbmRfZ3VpZGVsaW5lcywgYWxsX2FnZV9kaWV0X2FuZF9ndWlkZWxpbmVzX2xvbmcsIGRpZXRfYW5kX2d1aWRlbGluZXMsIHNlcF9hZ2VfZGlldF9kYXRhLCBmaWxlID0gaGVyZTo6aGVyZSgiZGF0YSIsICJ3cmFuZ2xlZCIsICJ3cmFuZ2xlZF9kYXRhLnJkYSIpKQ0KDQp3cml0ZV9jc3YoYWxsX2FnZV9kaWV0X2FuZF9ndWlkZWxpbmVzLCBmaWxlID0gaGVyZTo6aGVyZSgiZGF0YSIsICJ3cmFuZ2xlZCIsICJhbGxfYWdlX2RpZXRfYW5kX2d1aWRlbGluZXMuY3N2IikpDQp3cml0ZV9jc3YoYWxsX2FnZV9kaWV0X2FuZF9ndWlkZWxpbmVzX2xvbmcsIGZpbGUgPSBoZXJlOjpoZXJlKCJkYXRhIiwgIndyYW5nbGVkIiwgImFsbF9hZ2VfZGlldF9hbmRfZ3VpZGVsaW5lc19sb25nLmNzdiIpKQ0Kd3JpdGVfY3N2KGRpZXRfYW5kX2d1aWRlbGluZXMsIGZpbGUgPSBoZXJlOjpoZXJlKCJkYXRhIiwgIndyYW5nbGVkIiwgImRpZXRfYW5kX2d1aWRlbGluZXMuY3N2IikpDQp3cml0ZV9jc3Yoc2VwX2FnZV9kaWV0X2RhdGEsIGZpbGUgPSBoZXJlOjpoZXJlKCJkYXRhIiwgIndyYW5nbGVkIiwgInNlcF9hZ2VfZGlldF9kYXRhLmNzdiIpKQ0KYGBgDQoNCiMjICoqRGF0YSBFeHBsb3JhdGlvbioqDQoqKioNCg0KSWYgeW91IGhhdmUgYmVlbiBmb2xsb3dpbmcgYWxvbmcgYnV0IHN0b3BwZWQgeW91IGNvdWxkIGxvYWQgdGhlIHdyYW5nbGVkIGRhdGEgbGlrZSBzbzoNCg0KYGBge3J9DQpsb2FkKGhlcmU6OmhlcmUoImRhdGEiLCAid3JhbmdsZWQiLCAid3JhbmdsZWRfZGF0YS5yZGEiKSkNCmBgYA0KDQoqKioNCjxkZXRhaWxzPiA8c3VtbWFyeT4gSWYgeW91IHNraXBwZWQgdGhlIGRhdGEgd3JhbmdsaW5nIHNlY3Rpb24gY2xpY2sgaGVyZS4gPC9zdW1tYXJ5Pg0KDQpGaXJzdCB5b3UgbmVlZCB0byBpbnN0YWxsIGFuZCBsb2FkIHRoZSBgT0NTZGF0YWAgcGFja2FnZToNCg0KYGBge3IsIGV2YWw9RkFMU0V9DQppbnN0YWxsLnBhY2thZ2VzKCJPQ1NkYXRhIikNCmxpYnJhcnkoT0NTZGF0YSkNCmBgYA0KDQpUaGVuLCB5b3UgbWF5IGxvYWQgdGhlIHdyYW5nbGVkIGRhdGEgdXNpbmcgdGhlIGZvbGxvd2luZyBjb2RlOg0KDQpgYGB7ciwgZXZhbD1GQUxTRX0NCndyYW5nbGVkX3JkYSgib2NzLWJwLWRpZXQiLCBvdXRwYXRoID0gZ2V0d2QoKSkNCmxvYWQoaGVyZTo6aGVyZSgiT0NTZGF0YSIsICJkYXRhIiwgIndyYW5nbGVkIiwgIndyYW5nbGVkX2RhdGEucmRhIikpDQpgYGANCg0KSWYgdGhlIHBhY2thZ2UgZG9lcyBub3Qgd29yayBmb3IgeW91LCBhbHRlcm5hdGl2ZWx5LCBhbiBSREEgZmlsZSAoc3RhbmRzIGZvciBSIGRhdGEpIG9mIHRoZSBkYXRhIGNhbiBiZSBmb3VuZCBbaGVyZV0oaHR0cHM6Ly9naXRodWIuY29tLy9vcGVuY2FzZXN0dWRpZXMvb2NzLWJwLWRpZXQvdHJlZS9tYXN0ZXIvZGF0YS93cmFuZ2xlZCkgb3Igc2xpZ2h0bHkgbW9yZSBkaXJlY3RseSBbaGVyZV0oaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL29wZW5jYXNlc3R1ZGllcy9vY3MtYnAtZGlldC9tYXN0ZXIvZGF0YS93cmFuZ2xlZC93cmFuZ2xlZF9kYXRhLnJkYSkuIERvd25sb2FkIHRoaXMgZmlsZSBhbmQgdGhlbiBwbGFjZSBpdCBpbiB5b3VyIGN1cnJlbnQgd29ya2luZyBkaXJlY3Rvcnkgd2l0aGluIGEgc3ViZGlyZWN0b3J5IGNhbGxlZCAid3JhbmdsZWQiIHdpdGhpbiBhIHN1YmRpcmVjdG9yeSBjYWxsZWQgImRhdGEiIHRvIGNvcHkgYW5kIHBhc3RlIG91ciBjb2RlLiBXZSB1c2VkIGFuIFJTdHVkaW8gcHJvamVjdCBhbmQgdGhlIFtgaGVyZWAgcGFja2FnZV0oaHR0cHM6Ly9naXRodWIuY29tL2plbm55YmMvaGVyZV9oZXJlKSB0byBuYXZpZ2F0ZSB0byB0aGUgZmlsZSBtb3JlIGVhc2lseS4gDQoNCmBgYHtyfQ0KbG9hZChoZXJlOjpoZXJlKCJkYXRhIiwgIndyYW5nbGVkIiwgIndyYW5nbGVkX2RhdGEucmRhIikpDQpgYGANCg0KDQoqKioNCjxkZXRhaWxzPiA8c3VtbWFyeT4gQ2xpY2sgaGVyZSB0byBzZWUgbW9yZSBhYm91dCBjcmVhdGluZyBuZXcgcHJvamVjdHMgaW4gUlN0dWRpby4gPC9zdW1tYXJ5Pg0KDQpZb3UgY2FuIGNyZWF0ZSBhIHByb2plY3QgYnkgZ29pbmcgdG8gdGhlIEZpbGUgbWVudSBvZiBSU3R1ZGlvIGxpa2Ugc286DQoNCg0KYGBge3IsIGVjaG8gPSBGQUxTRSwgb3V0LndpZHRoPSI2MCUifQ0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoaGVyZTo6aGVyZSgiaW1nIiwgIk5ld19wcm9qZWN0LnBuZyIpKQ0KYGBgDQoNCllvdSBjYW4gYWxzbyBkbyBzbyBieSBjbGlja2luZyB0aGUgcHJvamVjdCBidXR0b246DQoNCmBgYHtyLCBlY2hvID0gRkFMU0UsIG91dC53aWR0aD0iNjAlIn0NCmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKGhlcmU6OmhlcmUoImltZyIsICJwcm9qZWN0X2J1dHRvbi5wbmciKSkNCmBgYA0KDQpTZWUgW2hlcmVdKGh0dHBzOi8vc3VwcG9ydC5yc3R1ZGlvLmNvbS9oYy9lbi11cy9hcnRpY2xlcy8yMDA1MjYyMDctVXNpbmctUHJvamVjdHMpIHRvIGxlYXJuIG1vcmUgYWJvdXQgdXNpbmcgUlN0dWRpbyBwcm9qZWN0cyBhbmQgW2hlcmVdKGh0dHBzOi8vZ2l0aHViLmNvbS9qZW5ueWJjL2hlcmVfaGVyZSkgdG8gbGVhcm4gbW9yZSBhYm91dCB0aGUgYGhlcmVgIHBhY2thZ2UuDQoNCjwvZGV0YWlscz4NCioqKg0KPC9kZXRhaWxzPg0KKioqDQogDQojIyMgKipFeHBsb3JpbmcgYWdlIGNvbGxhcHNlZCBkYXRhKioNCioqKg0KDQpMZXQncyBzdGFydCBieSB0YWtpbmcgYSBsb29rIGF0IHRoZSAgcGVyY2VudCBvZiBjb25zdW1wdGlvbiwgYWNyb3NzIGFsbCBkaWV0YXJ5IGZhY3RvcnMuIEFnYWluIHdlIHdpbGwgdXNlIHRoZSBiYXNlIFIgYHN1bW1hcnkoKWAgZnVuY3Rpb246DQoNCmBgYHtyfQ0KZGlldF9hbmRfZ3VpZGVsaW5lcyAlPiUNCiAgc2VsZWN0KFJlbGF0aXZlX1BlcmNlbnQpICU+JQ0KICBzdW1tYXJ5KCkNCmBgYA0KDQpXb3chIFNvbWUgb2YgdGhlIHZhbHVlcyBhcmUgbmVhcmx5IHplcm8sIHN1Z2dlc3RpbmcgdGhhdCBzb21lIHBlb3BsZSBhcmUgY29uc3VtaW5nIGJhc2ljYWxseSB6ZXJvIHBlcmNlbnQgb2Ygd2hhdCBpcyBzdWdnZXN0ZWQgZm9yIG9wdGltYWwgaGVhbHRoLiBPbiB0aGUgb3RoZXIgaGFuZCwgZm9yIHNvbWUgZGlldGFyeSBmYWN0b3JzIHBlb3BsZSBhcmUgY29uc3VtaW5nIG92ZXIgMTMsMDAwIHBlcmNlbnQgd2hhdCBpcyBzdWdnZXN0ZWQhIA0KDQpUaGlzIGlzIHdoeSBpdCBpcyBpbXBvcnRhbnQgdG8gbG9vayBhdCB0aGUgZGlyZWN0aW9uIG9mIGNvbnN1bXB0aW9uIHRoYXQgY291bGQgYmUgaGFybWZ1bC4gRm9yIGV4YW1wbGUgaWYgdGhlcmUgaXMgYSBwb3B1bGF0aW9uIHRoYXQgY29uc3VtZXMgbGFyZ2UgYW1vdW50cyBvZiB2ZWdldGFibGVzIHRoaXMgY291bGQgYmUgYSBnb29kIHRoaW5nLCBidXQgaWYgdGhlcmUgaXMgYSBwb3B1bGF0aW9uIGNvbnN1bWluZyBsYXJnZSBhbW91bnRzIG9mIHNvZGl1bSB0aGlzIHdvdWxkIGJlIGEgYmFkIHRoaW5nLiANCg0KTGV0J3MgdGFrZSBhIGxvb2sgdG8gc2VlIHdoYXQgZGlldGFyeSBmYWN0b3JzIGFyZSBhdCB0aGUgZXh0cmVtZXMgYnkgYXJyYW5naW5nIHRoZSBkYXRhIHVzaW5nIHRoZSBgYXJyYW5nZSgpYCBmdW5jdGlvbiBvZiB0aGUgYGRwbHlyYCBwYWNrYWdlLiBXZSBjYW4gYXJyYW5nZSBieSBzbWFsbGVzdCB0byBsYXJnZXN0IHVzaW5nIHRoZSBkZWZhdWx0IGFuZCB3ZSBjYW4gYXJyYW5nZSBsYXJnZXN0IHRvIHNtYWxsZXN0IHVzaW5nIHRoZSBtaW51cyBzaWduIGAtYC4NCg0KYGBge3J9DQpkaWV0X2FuZF9ndWlkZWxpbmVzICU+JQ0KICBhcnJhbmdlKC1SZWxhdGl2ZV9QZXJjZW50KSAlPiUNCiAgZ2xpbXBzZSgpDQpgYGANCg0KT0ssIHNvIGl0IGxvb2tzIGxpa2Ugc3VnYXItc3dlZXRlbmVkIGJldmVyYWdlcyBhcmUgcmVhbGx5IG92ZXItY29uc3VtZWQgaW4gc29tZSBwYXJ0cyBvZiB0aGUgd29ybGQhDQoNClJlY2FsbCBmcm9tIHRoZSBzdXBwbGVtZW50YXJ5IHRhYmxlIGZyb20gdGhlIGFydGljbGUgdGhhdCBvdmVyLWNvbnN1bXB0aW9uIG9mIHN1Z2FyLXN3ZWV0ZW5lZCBiZXZlcmFnZXMgaXMgYXNzb2NpYXRlZCB3aXRoIGJvdGggRGlhYmV0ZXMgbWVsbGl0dXMgdHlwZSAyIGFuZCBJc2NoZW1pYyBoZWFydCBkaXNlYXNlLiBUaGlzIFthcnRpY2xlXShodHRwczovL3d3dy5uY2JpLm5sbS5uaWguZ292L3BtYy9hcnRpY2xlcy9QTUM1MTMzMDg0Lyl7dGFyZ2V0PSJfYmxhbmsifSBkaXNjdXNzZXMgc29tZSBvZiB0aGUgY29udHJvdmVyc3kgb3ZlciB0aGUgcG90ZW50aWFsIGhlYWx0aCByaXNrcyBhc3NvY2lhdGVkIHdpdGggaGlnaCBjb25zdW1wdGlvbiBvZiBzdWdhci4NCg0KSXQgc3RpbGwgbG9va3MgcXVpdGUgYmFkIGlmIHdlIGxvb2sgYXQgdGhlIG90aGVyIGNhbGN1bGF0ZWQgcGVyY2VudGFnZSB2YWx1ZXMuIA0KYGBge3J9DQpkaWV0X2FuZF9ndWlkZWxpbmVzICU+JQ0KICBzZWxlY3QoY29udGFpbnMoInBlcmNlbnQiKSkgJT4lDQogIHN1bW1hcnkoKQ0KYGBgDQpTbyBzb21lIHBsYWNlcyBhcmUgc3RpbGwgY29uc3VtaW5nIDgsMDAwIHBlcmNlbnQgbW9yZSB0aGFuIHRoZSB1cHBlciByYW5nZSBvZiB0aGUgc3VnZ2VzdGVkIG9wdGltYWwgaW50YWtlLg0KDQpMZXQncyB0YWtlIGEgbG9vayBhdCBnbG9iYWwgbGV2ZWxzOg0KYGBge3J9DQpkaWV0X2FuZF9ndWlkZWxpbmVzICU+JQ0KICBmaWx0ZXIoZm9vZCA9PSAic3VnYXItc3dlZXRlbmVkIGJldmVyYWdlcyIgJg0KICAgIGxvY2F0aW9uX25hbWUgPT0gIkdsb2JhbCIpDQpgYGANCg0KRm9yIHRob3NlIHdobyBhcmUgbGVzcyBmYW1pbGlhciB3aXRoIHRoZSBtZXRyaWMgc3lzdGVtIHdoZXJlIGdyYW1zIGFyZSBlcXVpdmFsZW50IHRvIG1pbGxpbGl0ZXJzLCBpdCBtYXkgYmUgdXNlZnVsIHRvIHJlYWxpemUgaG93IG1hbnkgZmx1aWQgb3VuY2VzIHRoZSBtYXggYW1vdW50IG9mIGNvbnN1bXB0aW9uIHBlciBkYXkgKH40NDRnIGZvciB0aGUgYHVwcGVyYCB2YWx1ZSBmb3IgR3VhdGVtYWxhKSBhY3R1YWxseSBpcy4gDQoNClRoZXJlIGFyZSAwLjM1MjQ3IG91bmNlcyBpbiBvbmUgZ3JhbS4NCg0KYGBge3J9DQojIHRvcCBhbW91bnQgaW4gb3VuY2VzDQowLjM1MjQ3ICogNDQ0LjQwMDINCmBgYA0KDQpPSywgc28gdGhlIHRvcCBjb25zdW1lcnMgYXJlIGRyaW5raW5nIGFib3V0IDg3IGZsdWlkIG91bmNlcyBwZXIgZGF5LiBTaW5jZSB0aGVyZSBhcmUgMTIgb3VuY2VzIGluIGEgc2luZ2xlIGNhbiBvZiBzb2RhLCB0aGlzIGlzIGFib3V0IGByIDg3LzEyYCBzb2RhcyBwZXIgZGF5LiBHbG9iYWxseSBvbiBhdmVyYWdlLCBtYWxlcyBhcmUgZHJpbmtpbmcgYXJvdW5kIGByIHJvdW5kKCg2NS41KjAuMzUyNDcpLzEyLCAzKWAgc29kYXMgd29ydGggb2Ygc3dlZXRlbmVkIGJldmVyYWdlcywgd2hpbGUgZmVtYWxlcyBhcmUgZHJpbmtpbmcgYWJvdXQgYHIgcm91bmQoKDQ3LjcqMC4zNTI0NykvMTIsIDMpYC4NCg0KDQpMZXQncyB0YWtlIGEgbG9vayBhdCB3aGF0IGlzIHVuZGVyLWNvbnN1bWVkOg0KDQpgYGB7cn0NCmRpZXRfYW5kX2d1aWRlbGluZXMgJT4lDQogIGFycmFuZ2UoUmVsYXRpdmVfUGVyY2VudCkgJT4lDQogIGdsaW1wc2UoKQ0KYGBgDQoNCk9uIHRoZSBvdGhlciBoYW5kLCBpdCBsb29rcyBsaWtlIHNvbWUgcGxhY2VzIGFyZSBjb25zdW1pbmcgYWxtb3N0IG5vIHBvbHl1bnNhdHVyYXRlZCBmYXR0eSBhY2lkcy4gVGhlc2UgYXJlIGZhdHMgdGhhdCBmb3VuZCBpbiBwbGFudC1iYXNlZCBzb3VyY2VzIGxpa2Ugc2VlZHMgYW5kIG51dHMuIEFjY29yZGluZyB0byBhbiBbYXJ0aWNsZV0oaHR0cHM6Ly93d3cubmNiaS5ubG0ubmloLmdvdi9wbWMvYXJ0aWNsZXMvUE1DNDg1OTQwMS8pe3RhcmdldD0iX2JsYW5rIn0gYWJvdXQgcG9seXVuc2F0dXJhdGVkIGZhdHR5IGFjaWRzIGFuZCBpdHMgaW5mbHVlbmNlIG9uIGhlYWx0aDoNCg0KPiBDb3JvbmFyeSBoZWFydCBkaXNlYXNlIChDSEQpIGlzIHRoZSBsZWFkaW5nIGNhdXNlIG9mIGRlYXRoIHdvcmxkd2lkZSAuLi4gVGhlIHR5cGVzIG9mIGRpZXRhcnkgZmF0cyBjb25zdW1lZCBwbGF5IGFuIGltcG9ydGFudCByb2xlIGluIENIRCByaXNrLCByZXByZXNlbnRpbmcga2V5IG1vZGlmaWFibGUgcmlzayBmYWN0b3JzLi4uSW4gcGFydGljdWxhciwgaGlnaGVyIGludGFrZXMgb2YgdHJhbnMgZmF0IChURkEpIGFuZCBvZiBzYXR1cmF0ZWQgZmF0IChTRkEpIHJlcGxhY2luZyDPieKAkDYgKG7igJA2KSBwb2x5dW5zYXR1cmF0ZWQgZmF0IChQVUZBKSBhcmUgYXNzb2NpYXRlZCB3aXRoIGluY3JlYXNlZCBDSEQuLi4gd2hlcmVhcyBoaWdoZXIgaW50YWtlIG9mIFBVRkEgcmVwbGFjaW5nIGVpdGhlciBTRkEgb3IgY2FyYm9oeWRyYXRlIGlzIGFzc29jaWF0ZWQgd2l0aCBsb3dlciByaXNrLg0KDQoNCkxldCdzIGdldCBhbiBpZGVhIGFib3V0IGhvdyBjb3VudHJpZXMgY29tcGFyZSBpbiB0ZXJtcyBvZiBob3cgbWFueSBvZiB0aGUgZGlldGFyeSBmYWN0b3JzIGFyZSBjb25zdW1lZCBhdCB0aGUgb3B0aW1hbCBsZXZlbCAodGhlIGBvcHRfYWNoaWV2ZWRgIHZhcmlhYmxlKS4NCg0KYGBge3J9DQpkaWV0X2FuZF9ndWlkZWxpbmVzICU+JQ0KICBjb3VudChvcHRfYWNoaWV2ZWQpDQpgYGANCg0KTG9va3MgbGlrZSBvdmVyYWxsLCBvbmx5IGByIHJvdW5kKDE1MjAvNDM2MCoxMDAsIDIpYCUgIG9mIGRpZXRhcnkgZmFjdG9ycyBmb3IgYWxsIHRlc3RlZCBwb3B1bGF0aW9ucyB3ZXJlIGF0IG9wdGltYWwgbGV2ZWxzLg0KDQpMZXQncyBnZXQgYW4gaWRlYSBhYm91dCBob3cgY291bnRyaWVzIGNvbXBhcmUgb24gdGhpcyBtZXRyaWMuDQoNCiMjIyMgey5zY3JvbGxhYmxlIH0NCmBgYHtyfQ0KZGlldF9hbmRfZ3VpZGVsaW5lcyAlPiUNCiAgY291bnQob3B0X2FjaGlldmVkLCBsb2NhdGlvbl9uYW1lKSAlPiUNCiAgZmlsdGVyKG9wdF9hY2hpZXZlZCA9PSAiWWVzIikgJT4lDQogIGFycmFuZ2UoLW4pICU+JQ0KICAjIHRoaXMgYWxsb3dzIHVzIHRvIHNob3cgdGhlIGZ1bGwgb3V0cHV0DQogIHByaW50KG4gPSAxZTMpDQpgYGANCiMjIyMNCg0KSXQgbG9va3MgYXMgdGhvdWdoIG9uIGF2ZXJhZ2UgdGhlIHBvcHVsYXRpb25zIChib3RoIG1hbGUgYW5kIGZlbWFsZSBzZXBhcmF0ZWx5KSBpbiBRYXRhciwgUndhbmRhLCBhbmQgVHVya2V5IGNvbnN1bWVkIHRoZSBvcHRpbWFsIGxldmVsIG9mIGludGFrZSBmb3IgdGhlIGxhcmdlc3QgbnVtYmVyIG9mIGRpZXRhcnkgZmFjdG9ycyAoMTMgb3V0IG9mIDMwIChmb3IgdGhlIDE1IGRpZXRhcnkgZmFjdG9ycyBmb3IgbWFsZXMgYW5kIGZlbWFsZXMpKS4NCg0KSW4gY29udHJhc3QsIHRoZSBDemVjaCBSZXB1YmxpYywgR3JlZW5sYW5kLCBIdW5nYXJ5LCBTbG92YWtpYSwgU2xvdmVuaWEsIGFuZCB0aGUgVW5pdGVkIFN0YXRlcyBoYWQgdGhlIHBvb3Jlc3QgY29uc3VtcHRpb24gcmF0ZXMgKDI3IG91dCBvZiAzMCB3ZXJlIG5vdCBhdCBvcHRpbWFsIGxldmVscykuDQoNCiMjIyMgey5zY3JvbGxhYmxlIH0NCmBgYHtyfQ0KZGlldF9hbmRfZ3VpZGVsaW5lcyAlPiUNCiAgY291bnQob3B0X2FjaGlldmVkLCBsb2NhdGlvbl9uYW1lKSAlPiUNCiAgZmlsdGVyKG9wdF9hY2hpZXZlZCA9PSAiTm8iKSAlPiUNCiAgYXJyYW5nZSgtbikgJT4lDQogICMgdG8gc2hvdyBmdWxsIG91dHB1dA0KICBwcmludChuID0gMWUzKQ0KYGBgDQojIyMjDQoNCkxldCdzIGxvb2sgYXQgdGhlIHJhdyBVUyBkYXRhOg0KYGBge3J9DQpkaWV0X2FuZF9ndWlkZWxpbmVzICU+JQ0KICBmaWx0ZXIobG9jYXRpb25fbmFtZSA9PSAiVW5pdGVkIFN0YXRlcyIpICU+JQ0KICBnbGltcHNlKCkNCmBgYA0KDQpMZXQncyBzZWUgaG93IG1hbGVzIGFuZCBmZW1hbGVzIGNvbXBhcmUgZm9yIGFjaGlldmluZyB0aGUgb3B0aW1hbCBpbnRha2UsIGFjcm9zcyBhbGwgY291bnRyaWVzOg0KDQpgYGB7cn0NCmNvdW50KGRpZXRfYW5kX2d1aWRlbGluZXMsIHNleCwgb3B0X2FjaGlldmVkKQ0KYGBgDQpMb29rcyBwcmV0dHkgc2ltaWxhciwgYnV0IGl0IG1heSBiZSBhIGJpdCBiZXR0ZXIgZm9yIGZlbWFsZXMuIFdlIHdpbGwgZXZhbHVhdGUgdGhpcyBmdXJ0aGVyIGJlbG93Lg0KDQpJbiBvcmRlciB0byBhc3Nlc3Mgd2hhdCB3ZSBoYXZlIG9ic2VydmVkIHNvIGZhciBpbiBhIGdyYXBoaWNhbCB3YXksIHdlIHdpbGwgbWFrZSBzb21lIGRhdGEgdmlzdWFsaXphdGlvbnMuIE9uZSB3YXkgd2UgY2FuIGRvIHRoaXMgaXMgd2l0aCB0aGUgYGdncGxvdDJgIHBhY2thZ2UuDQpUaGUgW2dncGxvdDJdKGh0dHBzOi8vZ2dwbG90Mi50aWR5dmVyc2Uub3JnLyl7dGFyZ2V0PSJfYmxhbmsifSBwYWNrYWdlIGNyZWF0ZXMgcGxvdHMgYnkgYnVpbGRpbmcgdGhlIHBsb3QgY29tcG9uZW50cyBwaWVjZSBieSBwaWVjZSwgdXNpbmcgYCJsYXllcnMiYC4NCg0KV2l0aCBgZ2dwbG90MmAgd2Ugc2VsZWN0IHdoYXQgZGF0YSB3ZSB3b3VsZCBsaWtlIHRvIHBsb3QgdXNpbmcgdGhlIGZpcnN0IGZ1bmN0aW9uIChgZ2dwbG90KClgKSBhbmQgdGhlbiB3ZSBhZGQgb24gYWRkaXRpb25hbCBsYXllcnMgb2YgY29tcGxleGl0eSAodGhlc2UgbGF5ZXJzIGNhbiBldmVuIGludm9sdmUgZGlmZmVyZW50IGRhdGEpLiBUaGUgYGFlcygpYCBhcmd1bWVudCBzcGVjaWZpZXMgd2hhdCBhc3BlY3RzIG9mIHRoZSBkYXRhIHdpbGwgYmUgcGxvdHRlZCB3aGVyZS4gVGhlIGBnZW9tXypgIGZ1bmN0aW9uIHNwZWNpZmllcyB3aGF0IHR5cGUgb2YgcGxvdCB0byBjcmVhdGUgKGUuZy4gYGdlb21faGlzdG9ncmFtKClgIGNyZWF0ZXMgYSBoaXN0b2dyYW0pLiBOb3RpY2UgaW4gdGhlIGZvbGxvd2luZyBjb2RlIGhvdyB0aGVyZSBpcyBhIHBsdXMgc2lnbiBiZXR3ZWVuIHRoZSBgZ2dwbG90KClgIGZ1bmN0aW9uIGFuZCB0aGUgYGdlb21fYmFyKClgIGZ1bmN0aW9uOyB0aGlzIGlzIGhvdyB3ZSBjb21iaW5lIGRpZmZlcmVudCBwbG90IGxheWVycy4gDQoNCldlIHdpbGwgc2VlIGxhdGVyIGhvdyB3ZSBjYW4gYWRkIG1hbnkgbGF5ZXJzIHRvIHBsb3RzIHdpdGggYGdncGxvdDJgLiBGb3IgYWRkaXRpb25hbCBpbmZvcm1hdGlvbiBvbiB1c2luZyBgZ2dwbG90MmAsIHNlZSB0aGlzIFtjYXNlIHN0dWR5XShodHRwczovL29wZW5jYXNlc3R1ZGllcy5naXRodWIuaW8vb2NzLWJwLWNvMi1lbWlzc2lvbnMvKXt0YXJnZXQ9Il9ibGFuayJ9Lg0KDQpgYGB7cn0NCmRpZXRfYW5kX2d1aWRlbGluZXMgJT4lDQogIGdncGxvdChhZXMob3B0X2FjaGlldmVkLCBmaWxsID0gc2V4KSkgKw0KICBnZW9tX2Jhcihwb3NpdGlvbiA9ICJkb2RnZSIpDQpgYGANCg0KQ29udGludWluZyB3aXRoIGBnZ3Bsb3QyYCB3ZSB3aWxsIG5vdyBjcmVhdGUgYSBkaWZmZXJlbnQgdHlwZSBvZiBwbG90LiBUaGlzIHRpbWUgd2Ugd2lsbCBjcmVhdGUgYSBzZXJpZXMgb2YgYm94IHBsb3RzLiBXZSB3aWxsIHVzZSB0aGUgYGZhY2V0X3dyYXAoKWAgZnVuY3Rpb24gb2YgZ2dwbG90MiB0byBhbGxvdyB1cyB0byBjcmVhdGUgbWFueSBkaWZmZXJlbnQgcGxvdHMgc2ltdWx0YW5lb3VzbHkuIEluIHRoaXMgY2FzZSB3ZSBjYW4gbG9vayBhdCBib3ggcGxvdHMgZm9yIHRoZSBkaWZmZXJlbnQgZGlldGFyeSBmYWN0b3JzIGNvbG9yZWQgYnkgc2V4LiBUaGUgYHNjYWxlc2AgYXJndW1lbnQgd2hlbiBzZXQgdG8gYCJmcmVlImAgbWVhbnMgdGhhdCBlYWNoIG9mIHRoZSBzZXF1ZW50aWFsIHBsb3QgY3JlYXRlZCBieSB0aGUgZmFjZXQgY2FuIGhhdmUgYSBkaWZmZXJlbnQgc2NhbGUgZm9yIHRoZSB5IGF4aXMsIG90aGVyd2lzZSwgYnkgZGVmYXVsdCB0aGV5IGFyZSBjb25zdHJhaW5lZCB0byB0aGUgc2FtZSBzY2FsZS4gU2luY2Ugb3VyIGRpZXRhcnkgZmFjdG9ycyBhcmUgbWVhc3VyZWQgb24gdmVyeSBkaWZmZXJlbnQgc2NhbGVzLCB3ZSBkbyBub3Qgd2FudCB0aGlzIGNvbnN0cmFpbnQgaGVyZS4NCg0KDQpgYGB7cn0NCiMgd2Ugd2lsbCBjcmVhdGUgYSBuZXcgdmFyaWFibGUgd2l0aCBmb29kIG5hbWVzIHdpdGggbmV3IGxpbmVzDQojIHN0cl9yZXBsYWNlKCkgaXMgdXNlZCBoZXJlIGJlY2F1c2Ugd2UgYXJlIG9ubHkgcmVwbGFjaW5nIHRoZSBmaXJzdCBpbnN0YW5jZSBvZiBzcGFjZQ0KIyBvdGhlcndpc2Ugc3RyX3JlcGxhY2VfYWxsKCkgc2hvdWxkIGJlIHVzZWQNCmRpZXRfYW5kX2d1aWRlbGluZXMgJTw+JQ0KICBtdXRhdGUoDQogICAgZm9vZF90b19wbG90ID0NCiAgICAgIHN0cl9yZXBsYWNlKA0KICAgICAgICBzdHJpbmcgPSBwdWxsKGRpZXRfYW5kX2d1aWRlbGluZXMsIGZvb2QpLA0KICAgICAgICBwYXR0ZXJuID0gIiAiLA0KICAgICAgICByZXBsYWNlbWVudCA9ICJcbiINCiAgICAgICkNCiAgKQ0KDQpkaWV0X2FuZF9ndWlkZWxpbmVzICU+JQ0KICBnZ3Bsb3QoYWVzKA0KICAgIHkgPSBSZWxhdGl2ZV9QZXJjZW50LA0KICAgIHggPSBzZXgsDQogICAgY29sb3IgPSBzZXgNCiAgKSkgKw0KICBnZW9tX2JveHBsb3QoKSArDQogIGZhY2V0X3dyYXAofmZvb2RfdG9fcGxvdCwNCiAgICBzY2FsZXMgPSAiZnJlZSIsDQogICAgIyBzcGVjaWZpZXMgdGhlIG51bWJlciBvZiByb3dzIG9mIHN1YnBsb3RzDQogICAgbnJvdyA9IDMsDQogICAgIyBtb3ZlcyB0aGUgZm9vZCBsYWJlbCB0byB0aGUgcmlnaHQNCiAgICBzdHJpcC5wb3NpdGlvbiA9ICJyaWdodCINCiAgKSArDQogICMgdGhpcyBjaGFuZ2VzIHRoZSBzaXplIG9mIHRoZSBmb250IGZvciB0aGUgbGFiZWxzDQogIHRoZW1lKA0KICAgIHN0cmlwLnRleHQueSA9IGVsZW1lbnRfdGV4dChzaXplID0gOCksDQogICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoDQogICAgICBhbmdsZSA9IDcwLA0KICAgICAgaGp1c3QgPSAxDQogICAgKQ0KICApDQpgYGANCg0KDQpJZiB3ZSBqdXN0IGxvb2sgYXQgZGlmZmVyZW5jZXMgYnkgc2V4IGZvciB0aGUgc3BlY2lmaWMgZGlldGFyeSBmYWN0b3JzLCAgbWFsZXMgYXBwZWFyIHRvIHBvdGVudGlhbGx5IGNvbnN1bWUgbW9yZSBvZiBtYW55IG9mIHRoZSBmYWN0b3JzLCBpbmNsdWRpbmcgcG9zc2libHkgbW9yZSBzb2RpdW0sIGZpYmVyLCBjYWxjaXVtLCByZWQgbWVhdCwgYW5kIHN1Z2FyLXN3ZWV0ZW5lZCBiZXZlcmFnZXMgdGhhbiBmZW1hbGVzLiBGZW1hbGVzIG1heSBjb25zdW1lIG1vcmUgZnJ1aXQuDQoNCiMjIyAqKkV4cGxvcmluZyB0aGUgZGF0YSBzZXBhcmF0ZWQgYnkgYWdlKioNCioqKg0KDQpOb3cgd2Ugd2lsbCB0YWtlIGEgbG9vayBhdCB0aGUgZGF0YSB0aGF0IGlzIHNlcGFyYXRlZCBieSBhZ2UgZ3JvdXBzLg0KDQpGaXJzdCwgcmVjYWxsIHRoYXQgd2UgaGF2ZSAxNSBkaWZmZXJlbnQgYWdlIGdyb3VwcyBzdGFydGluZyBmcm9tIGFnZSAyNSB0byA5NSBwbHVzLg0KYGBge3J9DQphbGxfYWdlX2RpZXRfYW5kX2d1aWRlbGluZXMgJT4lDQogIGNvdW50KGFnZV9ncm91cF9uYW1lKQ0KYGBgDQoNCg0KDQpgYGB7ciwgZmlnLmhlaWdodD0xNX0NCnNlcF9hZ2VfZGlldF9kYXRhICU+JQ0KICBnZ3Bsb3QoYWVzKHkgPSBtZWFuLCB4ID0gYWdlX2dyb3VwX25hbWUsIGNvbCA9IHNleCkpICsNCiAgZ2VvbV9ib3hwbG90KCkgKw0KICBmYWNldF93cmFwKH5mb29kLCBzY2FsZXMgPSAiZnJlZSIsIG5yb3cgPSA2KSArDQogIHRoZW1lKA0KICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNzAsIGhqdXN0ID0gMSksDQogICAgc3RyaXAudGV4dC54ID0gZWxlbWVudF90ZXh0KHNpemUgPSA4KQ0KICApDQpgYGANCg0KV2UgY2FuIHNlZSBmcm9tIHRoZXNlIHBsb3RzIHRoYXQgdGhlcmUgYXBwZWFyIHRvIGJlIGFnZSBkaWZmZXJlbmNlcyBhbmQgZ2VuZGVyIGRpZmZlcmVuY2VzIGZvciBzb21lIG9mIHRoZSBkaWZmZXJlbnQgZGlldGFyeSBmYWN0b3JzLiBXZSB3aWxsIHdvcmsgdG8gY3JlYXRlIGNsZWFyZXIgZmlndXJlcyBsYXRlciBvbi4gSG93ZXZlciB0aGVzZSBpbml0aWFsIGZpZ3VyZXMgaGF2ZSBnaXZlbiB1cyBhIGJldHRlciBzZW5zZSBvZiB0aGUgZGF0YSB0aGF0IHdlIGFyZSB3b3JraW5nIHdpdGguDQoNCg0KIyMgKipEYXRhIEFuYWx5c2lzKioNCioqKg0KDQpJZiB5b3UgaGF2ZSBiZWVuIGZvbGxvd2luZyBhbG9uZyBidXQgc3RvcHBlZCB5b3UgY291bGQgbG9hZCB0aGUgd3JhbmdsZWQgZGF0YSBsaWtlIHNvOg0KDQpgYGB7cn0NCmxvYWQoaGVyZTo6aGVyZSgiZGF0YSIsICJ3cmFuZ2xlZCIsICJ3cmFuZ2xlZF9kYXRhLnJkYSIpKQ0KYGBgDQoNCioqKg0KPGRldGFpbHM+IDxzdW1tYXJ5PiBJZiB5b3Ugc2tpcHBlZCB0aGUgZGF0YSB3cmFuZ2xpbmcgc2VjdGlvbiBjbGljayBoZXJlLiA8L3N1bW1hcnk+DQoNCkZpcnN0IHlvdSBuZWVkIHRvIGluc3RhbGwgYW5kIGxvYWQgdGhlIGBPQ1NkYXRhYCBwYWNrYWdlOg0KDQpgYGB7ciwgZXZhbD1GQUxTRX0NCmluc3RhbGwucGFja2FnZXMoIk9DU2RhdGEiKQ0KbGlicmFyeShPQ1NkYXRhKQ0KYGBgDQoNClRoZW4sIHlvdSBtYXkgbG9hZCB0aGUgd3JhbmdsZWQgZGF0YSB1c2luZyB0aGUgZm9sbG93aW5nIGNvZGU6DQoNCmBgYHtyLCBldmFsPUZBTFNFfQ0Kd3JhbmdsZWRfcmRhKCJvY3MtYnAtZGlldCIsIG91dHBhdGggPSBnZXR3ZCgpKQ0KbG9hZChoZXJlOjpoZXJlKCJPQ1NkYXRhIiwgImRhdGEiLCAid3JhbmdsZWQiLCAid3JhbmdsZWRfZGF0YS5yZGEiKSkNCmBgYA0KDQpJZiB0aGUgcGFja2FnZSBkb2VzIG5vdCB3b3JrIGZvciB5b3UsIGFsdGVybmF0aXZlbHksIGFuIFJEQSBmaWxlIChzdGFuZHMgZm9yIFIgZGF0YSkgb2YgdGhlIGRhdGEgY2FuIGJlIGZvdW5kIFtoZXJlXShodHRwczovL2dpdGh1Yi5jb20vL29wZW5jYXNlc3R1ZGllcy9vY3MtYnAtZGlldC90cmVlL21hc3Rlci9kYXRhL3dyYW5nbGVkKSBvciBzbGlnaHRseSBtb3JlIGRpcmVjdGx5IFtoZXJlXShodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vb3BlbmNhc2VzdHVkaWVzL29jcy1icC1kaWV0L21hc3Rlci9kYXRhL3dhbmdsZWQvd3JhbmdsZWRfZGF0YS5yZGEpLiBEb3dubG9hZCB0aGlzIGZpbGUgYW5kIHRoZW4gcGxhY2UgaXQgaW4geW91ciBjdXJyZW50IHdvcmtpbmcgZGlyZWN0b3J5IHdpdGhpbiBhIHN1YmRpcmVjdG9yeSBjYWxsZWQgIndyYW5nbGVkIiB3aXRoaW4gYSBzdWJkaXJlY3RvcnkgY2FsbGVkICJkYXRhIiB0byBjb3B5IGFuZCBwYXN0ZSBvdXIgY29kZS4gV2UgdXNlZCBhbiBSU3R1ZGlvIHByb2plY3QgYW5kIHRoZSBbYGhlcmVgIHBhY2thZ2VdKGh0dHBzOi8vZ2l0aHViLmNvbS9qZW5ueWJjL2hlcmVfaGVyZSkgdG8gbmF2aWdhdGUgdG8gdGhlIGZpbGUgbW9yZSBlYXNpbHkuIA0KDQpgYGB7cn0NCmxvYWQoaGVyZTo6aGVyZSgiZGF0YSIsICJ3cmFuZ2xlZCIsICJ3cmFuZ2xlZF9kYXRhLnJkYSIpKQ0KYGBgDQoNCg0KKioqDQo8ZGV0YWlscz4gPHN1bW1hcnk+IENsaWNrIGhlcmUgdG8gc2VlIG1vcmUgYWJvdXQgY3JlYXRpbmcgbmV3IHByb2plY3RzIGluIFJTdHVkaW8uIDwvc3VtbWFyeT4NCg0KWW91IGNhbiBjcmVhdGUgYSBwcm9qZWN0IGJ5IGdvaW5nIHRvIHRoZSBGaWxlIG1lbnUgb2YgUlN0dWRpbyBsaWtlIHNvOg0KDQoNCmBgYHtyLCBlY2hvID0gRkFMU0UsIG91dC53aWR0aD0iNjAlIn0NCmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKGhlcmU6OmhlcmUoImltZyIsICJOZXdfcHJvamVjdC5wbmciKSkNCmBgYA0KDQpZb3UgY2FuIGFsc28gZG8gc28gYnkgY2xpY2tpbmcgdGhlIHByb2plY3QgYnV0dG9uOg0KDQpgYGB7ciwgZWNobyA9IEZBTFNFLCBvdXQud2lkdGg9IjYwJSJ9DQprbml0cjo6aW5jbHVkZV9ncmFwaGljcyhoZXJlOjpoZXJlKCJpbWciLCAicHJvamVjdF9idXR0b24ucG5nIikpDQpgYGANCg0KU2VlIFtoZXJlXShodHRwczovL3N1cHBvcnQucnN0dWRpby5jb20vaGMvZW4tdXMvYXJ0aWNsZXMvMjAwNTI2MjA3LVVzaW5nLVByb2plY3RzKSB0byBsZWFybiBtb3JlIGFib3V0IHVzaW5nIFJTdHVkaW8gcHJvamVjdHMgYW5kIFtoZXJlXShodHRwczovL2dpdGh1Yi5jb20vamVubnliYy9oZXJlX2hlcmUpIHRvIGxlYXJuIG1vcmUgYWJvdXQgdGhlIGBoZXJlYCBwYWNrYWdlLg0KDQo8L2RldGFpbHM+DQoqKioNCjwvZGV0YWlscz4NCioqKg0KDQpSZWNhbGwgd2hhdCBvdXIgbWFpbiBxdWVzdGlvbnMgd2VyZToNCg0KIyMjIyB7Lm1haW5fcXVlc3Rpb25fYmxvY2t9DQo8Yj48dT4gT3VyIG1haW4gcXVlc3Rpb25zIGFyZTogPC91PjwvYj4NCg0KMSkgV2hhdCBhcmUgdGhlIGdsb2JhbCB0cmVuZHMgZm9yIHBvdGVudGlhbGx5IGhhcm1mdWwgZGlldHM/DQoyKSBIb3cgZG8gbWFsZXMgYW5kIGZlbWFsZXMgY29tcGFyZT8NCjMpIEhvdyBkbyBkaWZmZXJlbnQgYWdlIGdyb3VwcyBjb21wYXJlIGZvciB0aGVzZSBkaWV0YXJ5IGZhY3RvcnM/DQo0KSBIb3cgZG8gZGlmZmVyZW50IGNvdW50cmllcyBjb21wYXJlPyBJbiBwYXJ0aWN1bGFyLCBob3cgZG9lcyB0aGUgVVMgY29tcGFyZSB0byBvdGhlciBjb3VudHJpZXMgaW4gdGVybXMgb2YgZGlldCB0cmVuZHM/DQoNCiMjIyMNCg0KV2UgaGF2ZSBzb21lIGdlbmVyYWwgc2Vuc2UgYWJvdXQgZ2xvYmFsIHRyZW5kcyBmb3IgdGhlIHJpc2stYXNzb2NpYXRlZCBkaWV0YXJ5IGZhY3RvcnMsIGhvd2V2ZXIgd2Ugd2FudCB0byBrbm93IG1vcmUuDQoNCldlIGFyZSBpbnRlcmVzdGVkIGluIGhvdyBtdWNoIHRoZSBnZW5kZXJzIGRpZmZlciwgaG93IG11Y2ggdGhlIDE1IGRpZmZlcmVudCBhZ2UgZ3JvdXBzIGRpZmZlciwgYW5kIGhvdyB0aGUgMTk1IGNvdW50cmllcyBjb21wYXJlLiANCg0KSW4gb3JkZXIgdG8gbWFrZSBbaW5mZXJlbmNlc10oaHR0cHM6Ly93d3cuYnJpdGFubmljYS5jb20vc2NpZW5jZS9pbmZlcmVuY2Utc3RhdGlzdGljcykgYWJvdXQgdGhlc2UgY29tcGFyaXNvbnMsIGl0IGlzIGhlbHBmdWwgdG8gcGVyZm9ybSBzdGF0aXN0aWNhbCB0ZXN0cy4gVGhlc2UgdGVzdHMgY2FuIGhlbHAgdXMgdG8gZGV0ZXJtaW5lIHRoZSBzdHJlbmd0aCBvZiB0aGUgYXNzb2NpYXRpb24gYmV0d2VlbiB0aGUgY29uc3VtcHRpb24gb2YgdGhlIGRpZXRhcnkgZmFjdG9ycyAob3VyIG91dGNvbWUgdmFyaWFibGUpIGFuZCBzZXgsIGFnZSBncm91cCwgYW5kIGNvdW50cnkgaWRlbnRpdHkgKG91ciBwcmVkaWN0b3IgdmFyaWFibGVzKS4gT25lIHdheSB0byBsb29rIGF0IHRoZSBzdHJlbmd0aCBvZiBhc3NvY2lhdGlvbiBiZXR3ZWVuIHZhcmlhYmxlcyBpcyB0byB1c2UgYSBzdGF0aXN0aWNhbCBtZXRob2QgY2FsbGVkICoqcmVncmVzc2lvbioqLg0KDQpJZiB3ZSBtZWFzdXJlIGNvbnN1bXB0aW9uIHVzaW5nIGVpdGhlciByYXcgY29uc3VtcHRpb24gb3IgdGhlIHBlcmNlbnQgb2Ygb3B0aW1hbCBjb25zdW1wdGlvbiwgdGhlbiBvdXIgb3V0Y29tZSB2YXJpYWJsZSBpcyB3aGF0IHdlIGNhbGwgKipjb250aW51b3VzKiosIGJlY2F1c2Ugb3VyIHZhbHVlcyBjYW4gdGFrZSBvbiBhbnkgbnVtZXJpYyB2YWx1ZSB3aXRoaW4gdGhlIHJhbmdlIG9mIHBvc3NpYmxlIHZhbHVlcy4gIFRvIGxvb2sgYXQgdGhlIHN0cmVuZ3RoIG9mIGFzc29jaWF0aW9uIHdpdGggYSBjb250aW51b3VzIG91dGNvbWUsIHdlIGNhbiB1c2UgKipsaW5lYXIgcmVncmVzc2lvbioqLg0KDQpJZiwgaW5zdGVhZCwgd2UgbWVhc3VyZSBjb25zdW1wdGlvbiBieSB3aGV0aGVyIG9yIG5vdCB0aGUgb3B0aW1hbCBsZXZlbCBvZiBjb25zdW1wdGlvbiB3YXMgYWNoaWV2ZWQgKCJ5ZXMiIG9yICJubyIpLCB0aGVuIG91ciBvdXRjb21lIHdvdWxkIGJlIGNvbnNpZGVyZWQgKipiaW5hcnkqKiwgbWVhbmluZyBpdCBjYW4gdGFrZSBvbmx5IHR3byBwb3NzaWJsZSB2YWx1ZXMuICBUbyBsb29rIGF0IHRoZSBzdHJlbmd0aCBvZiBhc3NvY2lhdGlvbiB3aXRoIGEgYmluYXJ5IG91dGNvbWUsIHdlIGNhbiB1c2UgKipsb2dpc3RpYyByZWdyZXNzaW9uLioqICBUaGVyZSBhcmUgb3RoZXIgcmVncmVzc2lvbiBtZXRob2QgZm9yIGRpZmZlcmVudCB0eXBlcyBvZiBvdXRjb21lcyBhcyB3ZWxsOyBzZWUgW2hlcmVdKGh0dHBzOi8vd3d3LmFuYWx5dGljc3ZpZGh5YS5jb20vYmxvZy8yMDE1LzA4L2NvbXByZWhlbnNpdmUtZ3VpZGUtcmVncmVzc2lvbi8pe3RhcmdldD0iX2JsYW5rIn0gZm9yIGEgZ3VpZGUgb24gZGlmZmVyZW50IHR5cGVzIG9mIHJlZ3Jlc3Npb24gbWV0aG9kcy4NCg0KSW4gdGhpcyBjYXNlIHN0dWR5LCB3ZSB3aWxsIGZvY3VzIG9uIHRoZSBvdXRjb21lIG9mIHRoZSBwZXJjZW50IG9mIG9wdGltYWwgY29uc3VtcHRpb24gKGBSZWxhdGl2ZV9QZXJjZW50YCksIHNvIHdlIHdpbGwgZm9jdXMgb3VyIGFuYWx5c2lzIG9uIGxpbmVhciByZWdyZXNzaW9uLiAgDQoNCllvdSBtYXkgaGF2ZSBhbHJlYWR5IGxlYXJuZWQgdGhhdCBvbmUgY2FuIGNvbXBhcmUgYSBjb250aW51b3VzIG91dGNvbWUgYmV0d2VlbiB0d28gZ3JvdXBzIHVzaW5nIGEgJHQkLXRlc3QuIEZvciBtb3JlIGluZm9ybWF0aW9uIG9uIHRoZSAkdCQtdGVzdCBzZWUgdGhpcyBbY2FzZSBzdHVkeV0oaHR0cHM6Ly9vcGVuY2FzZXN0dWRpZXMuZ2l0aHViLmlvL29jcy1icC1ydXJhbC1hbmQtdXJiYW4tb2Jlc2l0eS8pe3RhcmdldD0iX2JsYW5rIn0uICBBbmQgcGVyaGFwcyB5b3UgaGF2ZSBoZWFyZCBhYm91dCBBTk9WQSAoQU5hbHlzaXMgT2YgVkFyaWFuY2UpIGZvciBjb21wYXJpbmcgYSBjb250aW51b3VzIG91dGNvbWUgYWNyb3NzIG1vcmUgdGhhbiB0d28gZ3JvdXBzLiAgSXQgdHVybnMgb3V0IHRoYXQgYm90aCB0aGUgJHQkLXRlc3QgYW5kIEFOT1ZBIGFyZSBzcGVjaWFsaXplZCB0eXBlcyBvZiBbbGluZWFyIHJlZ3Jlc3Npb25dKGh0dHBzOi8vbGluZGVsb2V2LmdpdGh1Yi5pby90ZXN0cy1hcy1saW5lYXIvKXt0YXJnZXQ9Il9ibGFuayJ9LiBXZSB3aWxsIHVzZSBlYWNoIG9mIHRoZXNlIHRlc3RzIHRvIGludmVzdGlnYXRlIHBhdHRlcm5zIG9mIGNvbnN1bXB0aW9uIGZvciBkaWV0YXJ5IGZhY3RvcnMgdGhhdCBjb250cmlidXRlIHRvIGhlYWx0aCByaXNrIGFuZCB3ZSB3aWxsIGxvb2sgYXQgaG93IHdlIGNhbiBvYnRhaW4gZXF1aXZhbGVudCByZXN1bHRzIHdpdGggcmVncmVzc2lvbi4NCg0KIyMjICoqTGluZWFyIFJlZ3Jlc3Npb24qKg0KKioqDQoNClNvIHdoYXQgaXMgbGluZWFyIHJlZ3Jlc3Npb24/IEhvdyBjYW4gd2UgdXNlIHJlZ3Jlc3Npb24gdG8gY29tcGFyZSBvdXIgZ3JvdXBzIG9mIGludGVyZXN0IGFuZCBsb29rIGF0IHRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiBncm91cCBpZGVudGl0eSBhbmQgY29uc3VtcHRpb24gb2YgZGlldGFyeSBmYWN0b3JzIGFzc29jaWF0ZWQgd2l0aCBoZWFsdGggcmlzaz8NCg0KVGhlIHN0YXRpc3RpY2FsIHZlcnNpb24gb2YgdGhlIHRlcm0gcmVncmVzc2lvbiB3YXMgY29pbmVkIGluIDE4NzcgaW4gdGhpcyBbYXJ0aWNsZV0oaHR0cDovL2dhbHRvbi5vcmcvZXNzYXlzLzE4NzAtMTg3OS9nYWx0b24tMTg3Ny10eXBpY2FsLWxhd3MtaGVyZWRpdHkucGRmICl7dGFyZ2V0PSJfYmxhbmsifSBhYm91dCB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gaGVyZWRpdGFyeSB0cmFpdHMgYW5kIHBvcHVsYXRpb24gYXZlcmFnZXMuIFRoZSBhdXRob3IgcGFydGljdWxhcmx5IGZvY3VzZWQgb24gW2hlaWdodF0oaHR0cHM6Ly96ZW5vZG8ub3JnL3JlY29yZC8xNDQ5NTQ4Iy5YbGZfOWhOS2loYyl7dGFyZ2V0PSJfYmxhbmsifSBhbmQga2luc2hpcCBvciByZWxhdGVkbmVzcy4gVGhlIHdvcmQgaXRzZWxmIG1lYW5zIGAidG8gZ28gYmFjayB0byBhIHNpbXBsZXIgc3RhdGUiYC4gSXQgd2FzIG5vdGljZWQgdGhhdCBpbmRpdmlkdWFscyB3aXRoIHBhcmVudHMgd2hvIGhhZCBhbiBleHRyZW1lIHRyYWl0LCBzdWNoIGFzIGV4Y2VwdGlvbmFsIGhlaWdodCwgdGVuZGVkIHRvIGhhdmUgYSBoZWlnaHQgbW9yZSBzaW1pbGFyIHRvIHRoZSBhdmVyYWdlIG9mIHRoZSBwb3B1bGF0aW9uIHRoYW4gdGhlIGV4dHJlbWUgaGVpZ2h0IG9mIHRoZWlyIHBhcmVudHMuIEZvciBleGFtcGxlIGlmIHBhcmVudHMgd2VyZSB2ZXJ5IHRhbGwsIHRoZWlyIGNoaWxkcmVuIHdlcmUgbGlrZWx5IHRvIGJlIGEgYml0IHNob3J0ZXIgdGhhbiB0aGVpciBwYXJlbnRzIGFuZCB0aGVyZWZvcmUgY2xvc2VyIHRvIHRoZSBwb3B1bGF0aW9uIGF2ZXJhZ2UuIFRodXMgdGhlIGNoaWxkcmVuIHJlZ3Jlc3NlZCB0b3dhcmRzIHRoZSBtZWFuIG9yIGluIHRoZSBhdXRob3IncyB3b3JkcyB0aGUgb2Zmc3ByaW5nIHNob3dlZDoNCg0KPiAiYSAqcmVncmVzc2lvbiogdG93YXJkcyBtZWRpb2NyaXR5Ig0KDQpTZWUgW2hlcmVdKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL1JlZ3Jlc3Npb25fdG93YXJkX3RoZV9tZWFuKXt0YXJnZXQ9Il9ibGFuayJ9IGZvciBtb3JlIGluZm9ybWF0aW9uIGFib3V0IHRoaXMgaGlzdG9yeS4NCg0KV2hlbiB3ZSB0aGluayBhYm91dCB0aGlzIGZyb20gYSBzdGF0aXN0aWNhbCBzdGFuZHBvaW50LCByZWdyZXNzaW9uIGFsbG93cyB1cyB0byBlc3RpbWF0ZSBvciAqKnJlZ3Jlc3MqKiByZWxhdGlvbnNoaXBzIGJldHdlZW4gdmFyaWFibGVzIHdpdGggYSAic2ltcGxlIiBtb2RlbC4gV2UgZG8gdGhpcyBieSAqKmVzdGltYXRpbmcgdGhlIG1lYW4qKiBvZiBhbiBvdXRjb21lLCBnaXZlbiBhIHZhbHVlIG9mIGFuIGlucHV0IG9yIHByZWRpY3RvciB2YXJpYWJsZS4gVGhpcyBjYW4gYmUgdXNlZnVsIGZvciAqKnByZWRpY3RpbmcgZnV0dXJlIHZhbHVlcyoqIG9mIHRoZSBvdXRjb21lIGJhc2VkIG9uIHRoZSBhcHByb3hpbWF0aW9uIG9mIHRoZSByZWFsIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIHRoZSB2YXJpYWJsZXMgd2l0aGluIHRoZSBtb2RlbCwgb3IganVzdCBmb3IgdW5kZXJzdGFuZGluZyBob3cgZGlmZmVyZW50IHZhcmlhYmxlcyBhcmUgcmVsYXRlZCB0byBvbmUgYW5vdGhlci4NCg0KV2Ugd2lsbCBzdGFydCBieSBjb25zaWRlcmluZyAqKnNpbXBsZSBsaW5lYXIgcmVncmVzc2lvbioqLCB3aGVyZSB3ZSBoYXZlIG9uZSBjb250aW51b3VzIHByZWRpY3RvciB2YXJpYWJsZSBhbmQgb25lIGNvbnRpbnVvdXMgb3V0Y29tZSB2YXJpYWJsZSwgYXMgc2hvd24gYmVsb3c6DQpgYGB7ciwgZWNobyA9IEZBTFNFLCBvdXQud2lkdGggPSAiNDAwcHR4In0NCnNldC5zZWVkKDE1KQ0KZGF0YV94IDwtIHNhbXBsZSgxOjEwLCAxMCwgcmVwbGFjZSA9IFRSVUUpDQpkYXRhX3kgPC0gZGF0YV94ICsgcm5vcm0oMTAsIDAsIDEwKQ0KdGhlZGF0YSA8LSBiaW5kX2NvbHMoeCA9IGRhdGFfeCwgeSA9IGRhdGFfeSkNCg0KZ2dwbG90KGRhdGEgPSB0aGVkYXRhLCBhZXMoeCA9IHgsIHkgPSB5KSkgKw0KICBnZW9tX3BvaW50KCkgKw0KICB0aGVtZSgNCiAgICBheGlzLnRleHQueSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTUpLA0KICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxNSkNCiAgKQ0KYGBgDQoNCldlIHdhbnQgdG8gaWRlbnRpZnkgYSAiYmVzdCBmaXQiIGxpbmUgdGhhdCBzdW1tYXJpemVzIHRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiB0aGVzZSB0d28gdmFyaWFibGVzLiAgV2UgY2FuIHNvIHRoaXMgdXNpbmcgdGhlIG9yZGluYXJ5IGxlYXN0IHNxdWFyZXMgbWV0aG9kLCB3aGljaCBjaG9vc2VzIHRoZSBsaW5lIHRoYXQgYmVzdCBmaXRzIHRoZSBkYXRhIGJ5IG1pbmltaXppbmcgdGhlIHN1bSBvZiB0aGUgc3F1YXJlZCB2ZXJ0aWNhbCBkaXN0YW5jZXMgYmV0d2VlbiBlYWNoIHBvaW50IGFuZCB0aGUgbGluZS4gSW4gdGhlIGFib3ZlIGV4YW1wbGUsIHRoaXMgbGluZSB0dXJucyBvdXQgdG8gYmU6DQpgYGB7ciwgZWNobyA9IEZBTFNFLCBvdXQud2lkdGggPSAiNDAwcHR4In0NCnNldC5zZWVkKDE1KQ0KZGF0YV94IDwtIHNhbXBsZSgxOjEwLCAxMCwgcmVwbGFjZSA9IFRSVUUpDQpkYXRhX3kgPC0gZGF0YV94ICsgcm5vcm0oMTAsIDAsIDEwKQ0KdGhlZGF0YSA8LSBiaW5kX2NvbHMoeCA9IGRhdGFfeCwgeSA9IGRhdGFfeSkNCg0KZ2dwbG90KGRhdGEgPSB0aGVkYXRhLCBhZXMoeCA9IHgsIHkgPSB5KSkgKw0KICBnZW9tX3BvaW50KCkgKw0KICBnZW9tX3Ntb290aCgNCiAgICBtZXRob2QgPSAibG0iLA0KICAgIHNlID0gRkFMU0UsDQogICAgY29sb3IgPSAiYmxhY2siLA0KICAgIGZvcm11bGEgPSB5IH4geA0KICApICsNCiAgc3RhdF9yZWdsaW5lX2VxdWF0aW9uKHNpemUgPSA2KSArDQogIHRoZW1lKA0KICAgIGF4aXMudGV4dC55ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxNSksDQogICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoc2l6ZSA9IDE1KQ0KICApDQpgYGANCg0KRml0dGluZyBhIGxpbmUgdG8gdGhlIGRhdGEgbGlrZSB0aGlzIGFsbG93cyB1cyB0byBjcmVhdGUgYSBmb3JtdWxhIGZvciB0aGUgbGluZSB1c2luZyBhbiAqKmludGVyY2VwdCoqIGFuZCBhICoqc2xvcGUqKiwgc28gdGhhdCB3ZSBjYW4gdGhlbiBlc3RpbWF0ZSAqKm1lYW4qKiB2YWx1ZXMgb2YgJFkkIChkZXBlbmRlbnQvb3V0Y29tZSB2YXJpYWJsZSkgZ2l2ZW4ga25vd24gdmFsdWVzIG9mICRYJCAoaW5kZXBlbmRlbnQvcHJlZGljdG9yL2NvdmFyaWF0ZS9leHBsYW5hdG9yeSB2YXJpYWJsZShzKSkuIFBlb3BsZSB3aWxsIGFsc28gc2F5IHRoYXQgd2UgYXJlICJyZWdyZXNzaW5nICRZJCBvbiAkWCQiLg0KIA0KWW91IG1heSBoYXZlIHNlZW4gdGhlIGZvcm11bGEgZm9yIGEgbGluZSB3cml0dGVuIGxpa2UgdGhpczoNCg0KJCRZID0gbVggKyBiJCQgDQoNCjxjZW50ZXI+IG9yIDwvY2VudGVyPg0KJCRZID0gYVggKyBiJCQNCg0KSW4gdGhpcyBjYXNlICRtJCBvciAkYSQgaXMgdGhlIHNsb3BlIG9mIHRoZSBsaW5lIGFuZCAkYiQgaXMgYSBjb25zdGFudCBhbmQgcmVwcmVzZW50cyB0aGUgeS1pbnRlcmNlcHQgb3IgdGhlIHBvaW50IHdoZXJlIHRoZSB5IGF4aXMgaXMgY3Jvc3NlZCBieSB0aGUgbGluZSwgd2hlbiAkeCA9IDAkLg0KDQpJbiByZWdyZXNzaW9uLCB3ZSB1c3VhbGx5IHdyaXRlIHRoaXMgbW9kZWwgbGlrZSB0aGlzOg0KDQokJFkgPSBcYmV0YV97MX1YICtcYmV0YV97MH0kJA0KDQpOb3cgJFxiZXRhX3sxfSQsIGNhbGxlZCAiYmV0YSBvbmUiLCBpcyBvdXIgc2xvcGUgYW5kICRcYmV0YV97MH0kLCBjYWxsZWQgImJldGEgemVybyIgKG9yICJiZXRhIG5hdWdodCIpLCBpcyBvdXIgaW50ZXJjZXB0LiAgSW4gb3VyIGV4YW1wbGUgYWJvdmUsIHRoZSBzbG9wZSBvZiB0aGUgcmVncmVzc2lvbiBsaW5lIGlzICRcYmV0YV97MX0gPSAyLjMkIGFuZCB0aGUgaW50ZXJjZXB0IGlzICRcYmV0YV97MH0gPSAtNi42JC4NCg0KSW1wb3J0YW50bHkgdGhlIHNsb3BlICgkXGJldGFfezF9JCkgZ2l2ZXMgdXMgYSBxdWFudGl0YXRpdmUgbWVhc3VyZSBvZiB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gdGhlIGluZGVwZW5kZW50IHZhcmlhYmxlICgkWCQpIG9uIHRoZSBkZXBlbmRlbnQgdmFyaWFibGUgKCRZJCkuICBJbiBwYXJ0aWN1bGFyLCAkXGJldGFfezF9JCB0ZWxscyBob3cgdGhlIGV4cGVjdGVkIGRpZmZlcmVuY2UgaW4gdGhlICRZJCB2YWx1ZSBmb3IgYSBkaWZmZXJlbmNlIG9mIDEgdW5pdCBpbiB0aGUgJFgkIHZhbHVlLg0KDQpJdCdzIHBvc3NpYmxlIHRoYXQgdGhlIHJlZ3Jlc3Npb24gbGluZSB3aWxsIHBlcmZlY3RseSBmaXQgdGhlIGRhdGEsIGFuZCBhbGwgcG9pbnRzIHdpbGwgbGllIG9uIHRoZSBsaW5lIHdpdGggbm8gZGlzdGFuY2UgdG8gdGhlIGxpbmU6DQoNCmBgYHtyLCBlY2hvID0gRkFMU0UsIG91dC53aWR0aCA9ICI0MDBwdHgifQ0KDQpkYXRhX3ggPC0gc2FtcGxlKDE6MTAwLCAyMCwgcmVwbGFjZSA9IFRSVUUpDQpkYXRhX3kgPC0gZGF0YV94ICsgMTANCnRoZWRhdGEgPC0gYmluZF9jb2xzKHggPSBkYXRhX3gsIHkgPSBkYXRhX3kpDQoNCmdncGxvdChkYXRhID0gdGhlZGF0YSwgYWVzKHggPSB4LCB5ID0geSkpICsNCiAgZ2VvbV9wb2ludCgpICsNCiAgZ2VvbV9zbW9vdGgoDQogICAgbWV0aG9kID0gImxtIiwNCiAgICBzZSA9IEZBTFNFLA0KICAgIGNvbG9yID0gImJsYWNrIiwNCiAgICBmb3JtdWxhID0geSB+IHgNCiAgKSArDQogIHN0YXRfcmVnbGluZV9lcXVhdGlvbihzaXplID0gNikgKw0KICB0aGVtZSgNCiAgICBheGlzLnRleHQueSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTUpLA0KICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxNSkNCiAgKQ0KYGBgDQoNCkluIHRoaXMgY2FzZSwgdGhlIHNsb3BlIG9yICRcYmV0YV97MX0kIGlzIDEgYW5kIHRoZSBpbnRlcmNlcHQgJFxiZXRhX3swfSQgaXMgMTAgYW5kIGV2ZXJ5IG9ic2VydmVkIGRhdGEgcG9pbnQgbGllcyBleGFjdGx5IG9uIHRoZSBsaW5lLCBlLmcuLCB3ZSBjYW4gc2VlIHRoYXQgd2hlbiAkWCQgaXMgNTAsICRZJCBpcyBleGFjdGx5IDYwLiBUaGlzIGlzIHZlcnkgdW51c3VhbCBpbiBzdGF0aXN0aWNhbCBhbmFseXNpcyBob3dldmVyLCBhcyBvZnRlbiB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gdmFyaWFibGVzIGlzIG1vcmUgY29tcGxpY2F0ZWQgYW5kIHRoZXJlIGlzIG1vcmUgbm9pc2UgaW4gb3VyIGRhdGEuIEluIHRoZXNlIG90aGVyIGNhc2VzIHRoZXJlIHdpbGwgYmUgZ3JlYXRlciBkaXN0YW5jZXMgYmV0d2VlbiB0aGUgbGluZSBhbmQgdGhlIHBvaW50cy4gDQoNCkxpa2UgdGhpcyByZWdyZXNzaW9uOg0KYGBge3IsIGVjaG8gPSBGQUxTRSwgb3V0LndpZHRoPSI0MDBwdHgifQ0Kc2V0LnNlZWQoMTMpDQp0aGVkYXRhICU8PiUgbXV0YXRlKHkyID0gcm5vcm0oMjAsIHNkID0gNDApKQ0KDQpnZ3Bsb3QoZGF0YSA9IHRoZWRhdGEsIGFlcyh4ID0geCwgeSA9IHkyKSkgKw0KICBnZW9tX3Ntb290aCgNCiAgICBtZXRob2QgPSAibG0iLA0KICAgIHNlID0gRkFMU0UsDQogICAgY29sb3IgPSAiYmxhY2siLA0KICAgIGZvcm11bGEgPSB5IH4geA0KICApICsNCiAgZ2VvbV9wb2ludCgpICsNCiAgc3RhdF9yZWdsaW5lX2VxdWF0aW9uKHNpemUgPSA2KSArDQogIHRoZW1lKA0KICAgIGF4aXMudGV4dC55ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxNSksDQogICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoc2l6ZSA9IDE1KQ0KICApDQpgYGANCg0KSW4gdGhpcyBjYXNlLCBiZWNhdXNlIHRoZXJlIGlzIHNvbWUgdmVydGljYWwgZGlzdGFuY2UgYmV0d2VlbiB0aGUgbGluZSBhbmQgdGhlIGRhdGEgcG9pbnRzLCB0aGVyZSBpcyBhIGJpdCBvZiB3aGF0IGlzIGNhbGxlZCAiZXJyb3IiIGluIHRoZSBtb2RlbC4gVGhlIGZvcm11bGEgZm9yIHRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiAkWCQgYW5kICRZJCBkb2VzIG5vdCBwZXJmZWN0bHkgZGVzY3JpYmUgdGhlIGRhdGEuIFRoZSB2ZXJ0aWNhbCBkaXN0YW5jZSBiZXR3ZWVuIHRoZSBsaW5lIGFuZCBlYWNoIGRhdGEgcG9pbnQgaXMgd2hhdCB3ZSBjYWxsIGEgW3Jlc2lkdWFsXShodHRwczovL3d3dy5zdGF0aXN0aWNzaG93dG8uZGF0YXNjaWVuY2VjZW50cmFsLmNvbS9yZXNpZHVhbC8pe3RhcmdldD0iX2JsYW5rIn0uIE91ciBsZWFzdCBzcXVhcmVzIG1ldGhvZCBmaW5kcyB0aGUgbGluZSB3aXRoIHRoZSBtaW5pbWl6ZWQgdmFsdWUgb2YgdGhlIHN1bSBvZiB0aGUgc3F1YXJlZCByZXNpZHVhbCB2YWx1ZXMuDQoNCkNoZWNrIG91dCB0aGlzIFtpbnRlcmFjdGl2ZSBleHBsYW5hdGlvbl0oaHR0cDovL3NldG9zYS5pby9ldi9vcmRpbmFyeS1sZWFzdC1zcXVhcmVzLXJlZ3Jlc3Npb24vKXt0YXJnZXQ9Il9ibGFuayJ9IG9mIGhvdyB0aGUgb3JkaW5hcnkgbGVhc3Qgc3F1YXJlcyBtZXRob2Qgd29ya3MuDQoNCkhlcmUgaXMgYW4gaW1hZ2Ugb2Ygd2hhdCB3ZSBhcmUgc2F5aW5nIGFib3V0IHRoZSBvcmRpbmFyeSBsZWFzdCBzcXVhcmVzIHJlZ3Jlc3Npb24gdG8gZml0IGEgbGluZSB0byBkYXRhOg0KPGNlbnRlcj4hW10oaHR0cHM6Ly9xcGguZnMucXVvcmFjZG4ubmV0L21haW4tcWltZy0zYjBkNzY1NWFjNzZlZGYxMjQxZjk3MDE1ZWU3NTViNCk8L2NlbnRlcj4NCg0KIyMjIyMjIFtbc291cmNlXShodHRwczovL3FwaC5mcy5xdW9yYWNkbi5uZXQvbWFpbi1xaW1nLTNiMGQ3NjU1YWM3NmVkZjEyNDFmOTcwMTVlZTc1NWI0KV0NCg0KVGhpcyBiYXNpYyBjb25jZXB0IG9mIHNpbXBsZSBsaW5lYXIgcmVncmVzc2lvbiBhbiBiZSBleHRlbmRlZCB0byBhbGxvdyBmb3IgbW9yZSB0aGFuIG9uZSBjb3ZhcmlhdGUgKHRoZSBpbmRlcGVuZGVudCB2YXJpYWJsZXMsIG9yIHgncyk7IHRoaXMgaXMgY2FsbGVkICoqbXVsdGl2YXJpYWJsZSoqIGxpbmVhciByZWdyZXNzaW9uLiAgIFdpdGggbW9yZSB0aGFuIG9uZSBpbmRlcGVuZGVudCB2YXJpYWJsZSwgd2UgY2FuJ3QgdmlzdWFsaXplIHRoZXNlIHJlbGF0aW9uc2hpcHMgZWFzaWx5IHdpdGggYSBsaW5lIG9uIGEgdHdvLWRpbWVuc2lvbmFsIHBhZ2UsIGJ1dCB0aGUgbWF0aGVtYXRpY2FsIGNvbmNlcHQgcmVtYWlucyBpbiBzb21lIHNlbnNlIHRoZSBzYW1lLg0KDQpSIGhhcyBpdCdzIG93biB3YXkgb2YgcmVwcmVzZW50aW5nIHRoZSByZWdyZXNzaW9uIGVxdWF0aW9uIGluIGNvZGUuIEZvciBhIGd1aWRlIG9uIGhvdyB0byBwZXJmb3JtIHJlZ3Jlc3Npb25zIGluIFIgc2VlIFtoZXJlXShodHRwOi8vd3d3Lm1vbnRlZmlvcmUudWxnLmFjLmJlL35rdmFuc3RlZW4vR0JJTzAwMDktMS9hYzIwMDkyMDEwL0NsYXNzOC9Vc2luZyUyMFIlMjBmb3IlMjBsaW5lYXIlMjByZWdyZXNzaW9uLnBkZil7dGFyZ2V0PSJfYmxhbmsifS4NCg0KSW4gUiB3ZSBpbmRpY2F0ZSBhIGxpbmVhciBtb2RlbCBsaWtlIHRoaXM6DQpgYGB7ciwgZXZhbCA9IEZBTFNFfQ0KeSB+IHgNCmBgYA0KSGVyZSBvdXIgcmVzcG9uc2Uvb3V0Y29tZSB2YXJpYWJsZSBpcyBvbiB0aGUgbGVmdCBvZiB0aGUgYH5gIHdoaWxlIG91ciBjb3ZhcmlhdGVzL2V4cGxhbmF0b3J5IHZhcmlhYmxlcyBhcmUgb24gdGhlIHJpZ2h0IG9mIHRoZSBgfmAuDQoNCg0KQmVmb3JlIHdlIGdldCBzdGFydGVkLCBsZXQncyByZW1vdmUgdGhlIGdsb2JhbCB2YWx1ZXMgZnJvbSBvdXIgZGF0YSBhbmQgc2V0IHRoZW0gYXNpZGUsIGFzIHRoaXMgaXMgcmVhbGx5IGEgY29tcG9zaXRlIG9mIGFsbCB0aGUgY291bnRyeSB2YWx1ZXMuDQoNCmBgYHtyfQ0KZ2xvYmFsIDwtIGRpZXRfYW5kX2d1aWRlbGluZXMgJT4lDQogIGZpbHRlcihsb2NhdGlvbl9uYW1lID09ICJHbG9iYWwiKQ0KZGlldF9hbmRfZ3VpZGVsaW5lcyAlPD4lDQogIGZpbHRlcihsb2NhdGlvbl9uYW1lICE9ICJHbG9iYWwiKQ0KYWxsX2FnZV9kaWV0X2FuZF9ndWlkZWxpbmVzICU8PiUNCiAgZmlsdGVyKGxvY2F0aW9uX25hbWUgIT0gIkdsb2JhbCIpDQpgYGANCg0KIyMjICoqJHQkLXRlc3QgYW5kIGxpbmVhciByZWdyZXNzaW9uKioNCioqKg0KDQpTaW5jZSB3ZSB3aWxsIGJlIGNvdmVyaW5nIGEgbG90IG9mIGRpZmZlcmVudCBzdGF0aXN0aWNhbCBjb25jZXB0cyBoZXJlLCB3ZSB3aWxsIHdhbnQgdG8gZm9jdXMgYXJlIGFuYWx5c2lzIG9uIGEgc2luZ2xlIGRpZXRhcnkgZmFjdG9yLiBMZXQncyBjaG9vc2Ugb25lIG9mIHRoZSBkaWV0YXJ5IGZhY3RvcnMgdGhhdCBhcHBlYXJlZCB0byBwb3RlbnRpYWxseSBoYXZlIGEgZGlmZmVyZW5jZSBiZXR3ZWVuIGdlbmRlcnMgYmFzZWQgb24gb3VyIGZpZ3VyZSBpbiBvdXIgZXhwbG9yYXRvcnkgYW5hbHlzaXMuDQoNCj4gIklmIHdlIGp1c3QgbG9vayBhdCBkaWZmZXJlbmNlcyBieSBzZXggZm9yIHRoZSBzcGVjaWZpYyBkaWV0YXJ5IGZhY3RvcnMsICBtYWxlcyBhcHBlYXIgdG8gcG90ZW50aWFsbHkgY29uc3VtZSBtb3JlIG9mIG1hbnkgb2YgdGhlIGZhY3RvcnMsIGluY2x1ZGluZyBwb3NzaWJseSBtb3JlIHNvZGl1bSwgZmliZXIsIGNhbGNpdW0sIHJlZCBtZWF0LCBhbmQgc3VnYXItc3dlZXRlbmVkIGJldmVyYWdlcyB0aGFuIGZlbWFsZXMuIEZlbWFsZXMgbWF5IGNvbnN1bWUgbW9yZSBmcnVpdC4iDQoNCkxldCdzIHRha2UgYSBsb29rIGF0IHJlZCBtZWF0Lg0KDQpXZSBjYW4gY29tcGFyZSB0aGUgcmVsYXRpdmUgcGVyY2VudCBvZiByZWQgbWVhdCBjb25zdW1wdGlvbiBvZiBtYWxlcyBhbmQgZmVtYWxlcyBhcm91bmQgdGhlIHdvcmxkIHVzaW5nIHRoZSB3ZWxsIGtub3duICR0JC10ZXN0IHVzaW5nIHRoZSBgdC50ZXN0KClgIGZ1bmN0aW9uIGFuZCBhIGxpbmVhciByZWdyZXNzaW9uIG1vZGVsIHVzaW5nIHRoZSBgbG0oKWAgZnVuY3Rpb24gKGJvdGggYXJlIGluY2x1ZGVkIGluIGBzdGF0c2AgcGFja2FnZSB0aGF0IGlzIGluc3RhbGxlZCB3aXRoIFIgYnkgZGVmYXVsdCkgYW5kIHdlIHdpbGwgZ2V0IHRoZSAqKnNhbWUgcmVzdWx0cyoqLiBTZWUgW2hlcmVdKGh0dHBzOi8vc2NpZW50aWZpY2FsbHlzb3VuZC5vcmcvMjAxNy8wNi8wOC8kdCQtdGVzdC1hcy1saW5lYXItbW9kZWxzLXIvKXt0YXJnZXQ9Il9ibGFuayJ9IGZvciBhZGRpdGlvbmFsIGV4cGxhbmF0aW9uIGFib3V0IHdoeSB0aGF0IGlzIHRoZSBjYXNlLiBbSGVyZV0oaHR0cHM6Ly90b3dhcmRzZGF0YXNjaWVuY2UuY29tL2V2ZXJ5dGhpbmctaXMtanVzdC1hLXJlZ3Jlc3Npb24tNWEzYmYyMmM0NTljKXt0YXJnZXQ9Il9ibGFuayJ9IGFuZCBbaGVyZV0oaHR0cHM6Ly9saW5kZWxvZXYuZ2l0aHViLmlvL3Rlc3RzLWFzLWxpbmVhci8pe3RhcmdldD0iX2JsYW5rIn0gYXJlIGFsc28gZ3JlYXQgc291cmNlcyBhYm91dCBob3cgbWFueSBjb21tb25seSBrbm93biBzdGF0aXN0aWNhbCB0ZXN0cyBhcmUgc3BlY2lhbGl6ZWQgZm9ybXMgb2YgcmVncmVzc2lvbi4NCg0KQmVmb3JlIHdlIGdldCBzdGFydGVkLCBsZXQncyB0aGluayBhYm91dCB0aGUgYXNzdW1wdGlvbnMgb2YgYm90aCBhbiBpbmRlcGVuZGVudCBzYW1wbGVzICR0JC10ZXN0IGFuZCBsaW5lYXIgcmVncmVzc2lvbi4NCg0KDQojIyMjIEluZGVwZW5kZW50IHNhbXBsZXMgJHQkLXRlc3QgYXNzdW1wdGlvbnM6DQoNCjEpIE5vcm1hbGl0eSBvZiB0aGUgb3V0Y29tZSBpbiBlYWNoIGdyb3VwICh0aGlzIGlzIG5vdCBhcyBtdWNoIG9mIGFuIGlzc3VlIGlmIHRoZSBudW1iZXIgb2Ygb2JzZXJ2YXRpb25zIGlzIHJlbGF0aXZlbHkgbGFyZ2UsIGkuZS4sIHRvdGFsIG4gPiAzMCAtIHdoaWNoIGlzIGluZGVlZCB0aGUgY2FzZSBmb3IgdXMhKQ0KMikgRXF1YWwgdmFyaWFuY2UgYmV0d2VlbiB0aGUgdHdvIGdyb3Vwcw0KMykgSW5kZXBlbmRlbnQgb2JzZXJ2YXRpb25zDQoNCiMjIyMgTGluZWFyIHJlZ3Jlc3Npb24gYXNzdW1wdGlvbnM6DQoNCjEpICoqTCoqIChsaW5lYXIpIC0gVGhlcmUgaXMgYSBsaW5lYXIgcmVsYXRpb25zaGlwIGJldHdlZW4gdGhlIG91dGNvbWUgdmFyaWFibGUgYW5kIGVhY2ggY292YXJpYXRlLg0KDQoyKSAqKkkqKiAoaW5kZXBlbmRlbnQpIC0gVGhlIG91dGNvbWUgZm9yIGluZGl2aWR1YWwgb2JzZXJ2YXRpb25zIGFyZSBpbmRlcGVuZGVudCBmcm9tIG9uZSBhbm90aGVyLCBnaXZlbiB0aGUgY292YXJpYXRlcyBpbiB0aGUgbW9kZWwuDQoNCjMpICoqTioqIChub3JtYWwpIC0gVGhlIHJlc2lkdWFscyAoZXJyb3JzKSBhcmUgbm9ybWFsbHkgZGlzdHJpYnV0ZWQuIE5vdGUgdGhhdCB0aGUgdmFyaWFibGVzIHRoZW1zZWx2ZXMgZG8gbm90IG5lZWQgdG8gYmUgbm9ybWFsbHkgZGlzdHJpYnV0ZWQuDQoNCjQpICoqRSoqIChlcXVhbCB2YXJpYW5jZXMpIC0gVGhlIHZhcmlhbmNlIG9mIHRoZSByZXNpZHVhbHMgaXMgY29uc3RhbnQgYWNyb3NzIGNvdmFyaWF0ZSBncm91cHMuICBUaGlzIGlzIGNhbGxlZCBbaG9tb3NjZWRhc3RpY2l0eV0oaHR0cHM6Ly93d3cuc3RhdGlzdGljc3NvbHV0aW9ucy5jb20vaG9tb3NjZWRhc3RpY2l0eS8pe3RhcmdldD0iX2JsYW5rIn0uIEluIG90aGVyIHdvcmRzIHRoZSByZXNpZHVhbHMgYXJlIG9mIHNpbWlsYXIgc2l6ZSBhbG9uZyB0aGUgcmVncmVzc2lvbiBsaW5lLg0KDQpJdCdzIGFsc28gaW1wb3J0YW50IHRoYXQgaWYgdGhlcmUgYXJlIG11bHRpcGxlIHByZWRpY3RvciB2YXJpYWJsZXMsIHRoYXQgdGhlc2UgYXJlIG5vdCB0b28gaGlnaGx5IGNvcnJlbGF0ZWQuDQoNClNlZSBbaGVyZV0oaHR0cHM6Ly93d3cuam1wLmNvbS9lbl91cy9zdGF0aXN0aWNzLWtub3dsZWRnZS1wb3J0YWwvd2hhdC1pcy1yZWdyZXNzaW9uL3NpbXBsZS1saW5lYXItcmVncmVzc2lvbi1hc3N1bXB0aW9ucy5odG1sKXt0YXJnZXQ9Il9ibGFuayJ9IGZvciBhZGRpdGlvbmFsIGluZm9ybWF0aW9uIGFib3V0IHRoZSBhc3N1bXB0aW9ucyBvZiBsaW5lYXIgcmVncmVzc2lvbnMuDQoNCk5vdGljZSB0aGF0IG1hbnkgb2YgdGhlIGFzc3VtcHRpb25zIGJldHdlZW4gJHQkLXRlc3RzIGFuZCBsaW5lYXIgcmVncmVzc2lvbiBhcmUgc2ltaWxhciAtLSBlYWNoIGhhcyBhbiBhc3N1bXB0aW9uIG9mIG5vcm1hbGl0eSwgZXF1YWwgdmFyaWFuY2UsIGFuZCBpbmRlcGVuZGVuY2UhDQoNCiMjIyMgQXNzZXNzaW5nIG5vcm1hbGl0eQ0KDQpGaXJzdCB3ZSB3aWxsIGV4cGxvcmUgdGhlIHNoYXBlIG9mIHRoZSBkaXN0cmlidXRpb24gb2YgdGhlc2UgcmVsYXRpdmUgcGVyY2VudCBvZiByZWQgbWVhdCBjb25zdW1wdGlvbi4gIFdlIGNhbiBkbyB0aGlzIGJ5IGxvb2tpbmcgYXQgYSBmcmVxdWVuY3kgZGlzdHJpYnV0aW9uIG9mIHRoZSBgUmVsYXRpdmVfUGVyY2VudGAgdmFyaWFibGUgZm9yIHJlZCBtZWF0IGNvbnN1bXB0aW9uLiAgV2Ugd2lsbCB1c2UgdGhlIGBnZW9tX2hpc3RvZ3JhbSgpYCBvZiB0aGUgYGdncGxvdDJgcGFja2FnZSB0byBjcmVhdGUgYSBoaXN0b2dyYW0gdG8gZXZhbHVhdGUgdGhlIGZyZXF1ZW5jeSBkaXN0cmlidXRpb25zIG9mIG91ciBkYXRhLiBUaGUgYGZhY2V0X3dyYXAoKWAgZnVuY3Rpb24gb2YgdGhlIGBnZ3Bsb3QyYCBwYWNrYWdlIGFsbG93cyB1cyB0byBsb29rIGF0IGRpZmZlcmVudCBwYXJ0cyBvZiBvdXIgZGF0YSBpbiBzZXBhcmF0ZSBwbG90cy4gIEhlcmUgd2UgY2FuIGNvbXBhcmUgdGhlIGRpc3RyaWJ1dGlvbiBmb3IgbWFsZXMgYW5kIGZlbWFsZXMuDQoNCmBgYHtyfQ0KZGlldF9hbmRfZ3VpZGVsaW5lcyAlPiUNCiAgZmlsdGVyKGZvb2QgPT0gInJlZCBtZWF0IikgJT4lDQogIGdncGxvdChhZXMoeCA9IFJlbGF0aXZlX1BlcmNlbnQpKSArDQogIGdlb21faGlzdG9ncmFtKCkgKw0KICBmYWNldF93cmFwKH5zZXgpDQpgYGANCg0KVGhpcyBgUmVsYXRpdmVfUGVyY2VudGAgdmFyaWFibGUgYXBwZWFycyB0byBoYXZlIGEgcmlnaHQgc2tldyBmb3IgYm90aCBtYWxlIGFuZCBmZW1hbGUgaW5kaXZpZHVhbHMuICBXZSBjYW4gYWxzbyBzZWUgdGhpcyBieSBsb29raW5nIGF0IG5vcm1hbCBRdWFudGlsZS1RdWFudGlsZSAoUS1RKSBwbG90cyBvZiB0aGlzIHZhcmlhYmxlLiAgUmVtZW1iZXIgdGhhdCBpbiBhIFEtUSBwbG90LCBwb2ludHMgYXdheSBmcm9tIHRoZSBsaW5lIGluZGljYXRlIG9uZSBvZiB0aGUgZGlzdHJpYnV0aW9ucyBpcyBtb3JlIHNrZXdlZCB0aGFuIHRoZSBvdGhlci4gIEluIHRoaXMgY2FzZSwgd2Ugc2VlIHRoYXQgdGhlIHZhbHVlcyBpbiBhcmUgc2FtcGxlIGFyZSBza2V3ZWQgcmVsYXRpdmUgdG8gdGhlIHRoZW9yZXRpY2FsIG5vcm1hbCBkaXN0cmlidXRpb24uIFtIZXJlXShodHRwOi8vd3d3LnVjZC5pZS9lY29tb2RlbC9SZXNvdXJjZXMvUVFwbG90c19XZWJWZXJzaW9uLmh0bWwpe3RhcmdldD0iX2JsYW5rIn0gaXMgYSBncmVhdCByZWZlcmVuY2UgZm9yIGludGVycHJldGluZyBRLVEgcGxvdHMuDQoNCmBgYHtyfQ0KZGlldF9hbmRfZ3VpZGVsaW5lcyAlPiUNCiAgZmlsdGVyKGZvb2QgPT0gInJlZCBtZWF0IikgJT4lDQogIGdncGxvdChhZXMoc2FtcGxlID0gUmVsYXRpdmVfUGVyY2VudCkpICsNCiAgZmFjZXRfd3JhcCh+c2V4KSArDQogIGdlb21fcXEoKSArDQogIGdlb21fcXFfbGluZSgpDQpgYGANCg0KV2UgY2FuIGNvbnNpZGVyIHRyYW5zZm9ybWluZyBvdXIgZGF0YSB0byBtYWtlIGl0IG1vcmUgbm9ybWFsbHkgZGlzdHJpYnV0ZWQuIFdoZW4gZGF0YSBpcyBoaWdobHkgcmlnaHQgc2tld2VkLCBhIGxvZyB0cmFuc2Zvcm1hdGlvbiBpcyBvZnRlbiBoZWxwZnVsLg0KDQpMZXQncyB0YWtlIGEgbG9vayBhIHRoZSBsb2cgKHdpdGggYmFzZSAxMCkgb2Ygb3VyIGBSZWxhdGl2ZV9QZXJjZW50YCB2YXJpYWJsZToNCg0KYGBge3J9DQpkaWV0X2FuZF9ndWlkZWxpbmVzICU+JQ0KICBmaWx0ZXIoZm9vZCA9PSAicmVkIG1lYXQiKSAlPiUNCiAgZ2dwbG90KGFlcyh4ID0gbG9nMTAoUmVsYXRpdmVfUGVyY2VudCkpKSArDQogIGdlb21faGlzdG9ncmFtKCkgKw0KICBmYWNldF93cmFwKH5zZXgpDQoNCmRpZXRfYW5kX2d1aWRlbGluZXMgJT4lDQogIGZpbHRlcihmb29kID09ICJyZWQgbWVhdCIpICU+JQ0KICBnZ3Bsb3QoYWVzKHNhbXBsZSA9IGxvZzEwKFJlbGF0aXZlX1BlcmNlbnQpKSkgKw0KICBmYWNldF93cmFwKH5zZXgpICsNCiAgZ2VvbV9xcSgpICsNCiAgZ2VvbV9xcV9saW5lKCkNCmBgYA0KDQpPSywgc28gbm93IG91ciBoaXN0b2dyYW1zIGxvb2sgZmFpcmx5IG5vcm1hbC4gSXQgaXNuJ3QgcGVyZmVjdCwgYnV0IHdlIGhhdmUgYSBsYXJnZSBudW1iZXIgb2Ygc2FtcGxlcywgc28gdGhpcyBpcyBnb29kIGZvciBvdXIgJHQkLXRlc3QgYXNzdW1wdGlvbnMuIA0KDQojIyMjIEFzc2Vzc2luZyBlcXVhbCB2YXJpYW5jZXMNCg0KVGhlIG5leHQgdGhpbmcgd2UgbmVlZCB0byBjaGVjayBpcyBpZiB0aGUgdmFyaWFuY2UgaW4gcmVkIG1lYXQgY29uc3VtcHRpb24gaXMgc2ltaWxhciBiZXR3ZWVuIHRoZSB0d28gZ2VuZGVyIGdyb3Vwcy4gV2UgY2FuIHVzZSB0aGUgYHZhci50ZXN0KClgICBvZiB0aGUgYHN0YXRzYCBwYWNrYWdlIHVzaW5nIHRoZSBsb2ctbm9ybWFsaXplZCBkYXRhLCBhcyB0aGlzIGRhdGEgaXMgZmFpcmx5IG5vcm1hbGx5IGRpc3RyaWJ1dGVkLg0KDQpCZWNhdXNlIHdlIGFyZSBwaXBpbmcgaW4gb3VyIGRhdGEgdG8gdGhpcyB0ZXN0IGZ1bmN0aW9uLCB3ZSBuZWVkIHRvIGluZGljYXRlIHRoYXQgdGhpcyBpcyB0aGUgZGF0YSB3ZSBpbnRlbmQgdG8gdXNlIGJ5IHVzaW5nIGAuYCBmb3IgdGhlIGBkYXRhYCBhcmd1bWVudC4gIFRoaXMgaXMgYSBoYW5keSB0aXAgd2hlbiBwaXBpbmcgaW50byBhIGZ1bmN0aW9uIG91dHNpZGUgb2YgdGhlIGB0aWR5dmVyc2VgIHdoZXJlIHRoZSBmaXJzdCBhcmd1bWVudCBpc24ndCBhIGRhdGEgc2V0Lg0KDQpgYGB7cn0NCmRpZXRfYW5kX2d1aWRlbGluZXMgJT4lDQogIGZpbHRlcihmb29kID09ICJyZWQgbWVhdCIpICU+JQ0KICB2YXIudGVzdChsb2cxMChSZWxhdGl2ZV9QZXJjZW50KSB+IHNleCwgZGF0YSA9IC4pDQpgYGANCg0KVGhlIHAgdmFsdWUgPiAuMDUgZm9yIHRoaXMgdGVzdCwgdGh1cyB3ZSBjYW4gY29uY2x1ZGUgdGhhdCB0aGVyZSBpcyBub3QgZW5vdWdoIGV2aWRlbmNlIHRvIHJlamVjdCB0aGUgbnVsbCBoeXBvdGhlc2lzIHRoYXQgdGhlcmUgaXMgbm8gZGlmZmVyZW5jZSBpbiB0aGUgdmFyaWFuY2Ugb2YgdGhlIGRpc3RyaWJ1dGlvbnMsIHNvIHdlIGNvbmNsdWRlIHRoYXQgdmFyaWFuY2UgaXMgcm91Z2hseSBlcXVhbC4NCg0KIyMjIyBDb21wYXJpbmcgYSAkdCQtdGVzdCB0byBsaW5lYXIgcmVncmVzc2lvbg0KDQpOb3cgbGV0J3MgY29tcGFyZSB0aGUgY29uc3VtcHRpb24gb2YgcmVkIG1lYXQgYWNyb3NzIGdlbmRlcnMgdXNpbmcgYm90aCBhICR0JC10ZXN0IGFuZCBhIGxpbmVhciByZWdyZXNzaW9uLiBGaXJzdCBvdXIgaW5kZXBlbmRlbnQgc2FtcGxlcyAkdCQtdGVzdDoNCmBgYHtyfQ0KZGlldF9hbmRfZ3VpZGVsaW5lcyAlPiUNCiAgZmlsdGVyKGZvb2QgPT0gInJlZCBtZWF0IikgJT4lDQogIHQudGVzdChsb2cxMChSZWxhdGl2ZV9QZXJjZW50KSB+IHNleCwgZGF0YSA9IC4sIHZhci5lcXVhbCA9IFRSVUUpDQpgYGANCg0KTm90aWNlIGhlcmUgdGhhdCBzYW1wbGUgbWVhbnMgZm9yIHRoZSB0d28gZ3JvdXBzIGFyZSAxLjgwIGFuZCAxLjk4IGZvciBtYWxlcyBhbmQgZmVtYWxlcywgcmVzcGVjdGl2ZWx5LiAgU28gdGhhdCBtZWFucyB0aGUgZGlmZmVyZW5jZSBpbiBzYW1wbGUgbWVhbnMgaXMgMS44MCAtIDEuOTggPSAtMC4xOC4gIFdlIGFsc28gc2VlIGEgdGVzdCBzdGF0aXN0aWMgb2YgJHQkID0gLTUuMzIgYW5kIGEgdmVyeSBzbWFsbCAkcCQtdmFsdWUuICANCg0KTGV0J3MgZXhhbWluZSB0aGUgc2FtZSByZWxhdGlvbnNoaXAgdXNpbmcgbGluZWFyIHJlZ3Jlc3Npb246DQpgYGB7cn0NCmRpZXRfYW5kX2d1aWRlbGluZXMgJT4lDQogIGZpbHRlcihmb29kID09ICJyZWQgbWVhdCIpICU+JQ0KICBsbShsb2cxMChSZWxhdGl2ZV9QZXJjZW50KSB+IHNleCwgZGF0YSA9IC4pICU+JQ0KICBzdW1tYXJ5KCkNCmBgYA0KDQpMb29rIGF0IHRoZSByZXN1bHRzIGZvciB0aGUgc2xvcGUgb2YgdGhlIHJlZ3Jlc3Npb24gbGluZSwgaW5kaWNhdGVkIGJ5IHRoZSBgc2V4TWFsZWAgcm93IGluIHRoZSBvdXRwdXQgYWJvdmUuICBOb3RpY2UgaG93IHRoZSAkdCQtdmFsdWUgYW5kIHRoZSAkcCQtdmFsdWUgbWF0Y2ggb3VyICR0JC10ZXN0ISAgKFdlbGwsIHRoZSBzaWducyBhcmUgc3dpdGNoZWQgaW4gZWFjaCBjYXNlIC0tIHRoZSAkdCQgdmFsdWUgaXMgbmVnYXRpdmUgaW4gdGhlIGB0LnRlc3QoKWAgb3V0cHV0IGJlY2F1c2UgdGhlIG1hbGUgZ3JvdXAgaXMgYmVpbmcgdXNlZCBhcyByZWZlcmVuY2UgZ3JvdXAsIHdoaWxlIHRoZSBmZW1hbGUgZ3JvdXAgaXMgYmVpbmcgdXNlZCBhcyB0aGUgcmVmZXJlbmNlIGdyb3VwIGluIGBsbSgpYCkuIFdlIGNhbiBmaXggdGhpcyB1c2luZyB0aGUgYGZjdF9pbm9yZGVyKClgIGZ1bmN0aW9uIG9mIHRoZSBgZm9yY2F0c2AgcGFja2FnZSB3aGljaCBpcyBhbGwgYWJvdXQgZmFjdG9ycy4gVGhpcyBmdW5jdGlvbiBhbGxvd3MgdXMgdG8gb3JkZXIgdGhlIGZhY3RvciBieSB3aGF0IGFwcGVhcnMgZmlyc3QuIEluIHRoaXMgY2FzZSAibWFsZSIgYXBwZWFycyBmaXJzdCwgc28gbm93IG91ciBvdXRwdXQgd2lsbCBtYXRjaCB0aGF0IG9mIHRoZSBgbG0oKWAgZnVuY3Rpb24uDQoNCg0KYGBge3J9DQpkaWV0X2FuZF9ndWlkZWxpbmVzICU8PiUNCiAgbXV0YXRlX2F0KHZhcnMoc2V4KSwgZmFjdG9yKQ0KDQpkaWV0X2FuZF9ndWlkZWxpbmVzICU+JQ0KICBmaWx0ZXIoZm9vZCA9PSAicmVkIG1lYXQiKSAlPiUNCiAgbG0obG9nMTAoUmVsYXRpdmVfUGVyY2VudCkgfiBzZXgsIGRhdGEgPSAuKSAlPiUNCiAgc3VtbWFyeSgpDQoNCmRpZXRfYW5kX2d1aWRlbGluZXMgJT4lDQogIG11dGF0ZV9hdCh2YXJzKHNleCksIGZvcmNhdHM6OmZjdF9pbm9yZGVyKSAlPiUNCiAgZmlsdGVyKGZvb2QgPT0gInJlZCBtZWF0IikgJT4lDQogIHQudGVzdChsb2cxMChSZWxhdGl2ZV9QZXJjZW50KSB+IHNleCwNCiAgICBkYXRhID0gLiwNCiAgICB2YXIuZXF1YWwgPSBUUlVFDQogICkNCmBgYA0KDQpOb3cgdGhleSBtYXRjaC4gTm90aWNlIHRoYXQgdGhlIGRlZ3JlZXMgb2YgZnJlZWRvbSBhbHNvIG1hdGNoLCBib3RoIHJlc3VsdHMgc2hvdyAzODggZGVncmVlcyBvZiBmcmVlZG9tLiBXZSBhcmUgZXN0aW1hdGluZyB0d28gcGFyYW1ldGVycyBmb3IgdGhlIGxpbmVhciBtb2RlbCB0aGUgdHdvICRcYmV0YSQgY29lZmZpY2llbnRzLCAodGhlIHNsb3BlIGFuZCBpbnRlcmNlcHQpLCBhbmQgZm9yIHRoZSAkdCQtdGVzdCB3ZSBhcmUgZXN0aW1hdGluZyB0aGUgbWVhbnMgb2YgdHdvIGdyb3VwcyAobWFsZXMgYW5kIGZlbWFsZXMpLiBPdmVyYWxsIHdlIGhhdmUgdHdvIHNhbXBsZXMgKG1hbGUgYW5kIGZlbWFsZSkgZm9yIGVhY2ggb2YgdGhlIDE5NSBjb3VudHJpZXMuIA0KDQpUaHVzLCB0aGUgb3ZlcmFsbCBzYW1wbGUgbnVtYmVyIGlzOiAgJG4gPSAxOTUqMiA9IDM5MCQNCg0KJCRkZiA9IG4gLSAjIHBhcmFtZXRlcnMgZXN0aW1hdGluZyQkIA0KVGh1cyB0aGUgZGVncmVlcyBvZiBmcmVlZG9tIGNhbiBiZSBjYWxjdWxhdGVkIGFzOiAgJGRmID0gMzkwIC0yID0gMzg4JA0KDQpMZXQncyBsb29rIG1vcmUgY2xvc2VseSBhdCB0aGUgbGluZWFyIHJlZ3Jlc3Npb24gb3V0cHV0IGZyb20gYGxtKClgLiAgT3VyIGVzdGltYXRlZCBpbnRlcmNlcHQgKCRcYmV0YV97MH0kKSBpcyAxLjgwLCB3aGljaCBjYW4gYmUgaW50ZXJwcmV0ZWQgYXMgdGhlIG1lYW4gdmFsdWUgd2hlbiBzZXggaXMgbm90IG1hbGUgKHNvIGluIHRoaXMgY2FzZSB3aGVuIHNleCBpcyBmZW1hbGUpLiAgVGhpcyBtYXRjaGVzIHRoZSBzYW1wbGUgbWVhbiBvZiB0aGUgZmVtYWxlIGdyb3VwIGluIHRoZSBgdC50ZXN0KClgIG91dHB1dC4gDQoNCk91ciBlc3RpbWF0ZWQgc2xvcGUgKCRcYmV0YV97MX0kKSBpcyAwLjE4LCB3aGljaCBjYW4gYmUgaW50ZXJwcmV0ZWQgYXMgdGhlIHNsb3BlIG9mIHRoZSByZWdyZXNzaW9uIGxpbmUgb3IgKip0aGUgbWVhbiBjaGFuZ2UgaW4gJFkkIGFzc29jaWF0ZWQgd2l0aCBvbmUtdW5pdCBpbmNyZWFzZSBpbiAkWCQqKi4gIFNpbmNlIG91ciAkWCQgdmFyaWFibGUgaXMgc2V4LCBhIG9uZS11bml0IGNoYW5nZSBtZWFucyBtb3ZpbmcgZnJvbSBvbmUgZ3JvdXAgdG8gYW5vdGhlci4gIFNvIHdlIGNhbiB0aGluayBvZiB0aGUgc2xvcGUgYXMgdGhlIGRpZmZlcmVuY2UgYmV0d2VlbiB0aGUgbWVhbnMgb2YgdGhlIHR3byBncm91cHMsIG1hbGUgKCRYJD0xKSBtaW51cyBmZW1hbGUgKCRYJCA9IDApLiAgSWYgd2UgY2FsY3VsYXRlIHRoaXMgZGlmZmVyZW5jZSBpbiBtZWFucyBhcyBjYWxjdWxhdGVkIGluIHRoZSBgdC50ZXN0KClgIG91dHB1dCwgd2UgZ2V0IHRoZSB2YWx1ZSBvZiAkXGJldGFfezF9JCAodGhlIHNsb3BlIG9yIHRoZSBgc2V4TWFsZSBlc3RpbWF0ZWApIG9mIHRoZSBgbG0oKWAgb3V0cHV0IQ0KDQpNZWFuIG9mIG1hbGVzIC0gTWVhbiBvZiBmZW1hbGVzDQokMS45ODMyNTkgLSAxLjc5ODg3MiA9MC4xODQzODckDQoNCkNvb2whICBGb3IgbW9yZSBpbmZvcm1hdGlvbiBhYm91dCB0aGUgb3V0cHV0IG9mIGBsbSgpYCBzZWUgW2hlcmVdKGh0dHBzOi8vZmVsaXBlcmVnby5naXRodWIuaW8vYmxvZy8yMDE1LzEwLzIzL0ludGVycHJldGluZy1Nb2RlbC1PdXRwdXQtSW4tUil7dGFyZ2V0PSJfYmxhbmsifS4NCg0KQWZ0ZXIgZml0dGluZyBvdXIgbGluZWFyIHJlZ3Jlc3Npb24gbW9kZWwsIHdlIGNhbiB1c2UgdGhlIGJhc2UgYHBsb3QoKWAgZnVuY3Rpb24gdG8gZ2V0IGluZm9ybWF0aW9uIGFib3V0IG91ciBtb2RlbCByZXNpZHVhbHMgdG8gaGVscCB1cyBhc3Nlc3Mgd2hldGhlciBhbnkgb2YgdGhlIGFzc3VtcHRpb25zIG9mIGxpbmVhciByZWdyZXNzaW9uIGFyZSB2aW9sYXRlZC4gSGVyZSB3ZSBjaG9vc2UgdG8gdmlldyB0aGUgZmlyc3QgdGhyZWUgb2YgdGhlc2UgcGxvdHMgd2l0aCBgd2hpY2ggPSAxOjNgLg0KDQpgYGB7cn0NCmRpZXRfYW5kX2d1aWRlbGluZXMgJT4lDQogIGZpbHRlcihmb29kID09ICJyZWQgbWVhdCIpICU+JQ0KICBsbShsb2cxMChSZWxhdGl2ZV9QZXJjZW50KSB+IHNleCwgZGF0YSA9IC4pICU+JQ0KICBwbG90KHdoaWNoID0gMTozKQ0KYGBgDQoNClRoZSBzZWNvbmQgcGxvdCBzaG93cyB1cyB0aGF0IG91ciByZXNpZHVhbHMgYXJlIHNsaWdodGx5IG5lZ2F0aXZlbHkgKG9yIGxlZnQpIHNrZXdlZC4gIFdlIGNhbiBzZWUgYWxzbyBzZWUgdGhlIHNwcmVhZCBvZiB0aGUgcmVzaWR1YWxzIGlzIHNpbWlsYXIgYmV0d2VlbiBtYWxlcyBhbmQgZmVtYWxlcywgYXMgdGhlIGZpcnN0IGFuZCB0aGlyZCBwbG90IHNob3cgc2ltaWxhciBzcHJlYWRzIG9mIHZhbHVlcyBpbiB0aGUgdHdvIGxpbmVzLiBUaGlzIHN1Z2dlc3RzIHRoYXQgdGhlIGFzc3VtcHRpb24gb2YgaG9tb3NjZWRhc3RpY2l0eSBpcyBtZXQuICBIZXJlIGlzIHdoYXQgdGhlc2UgcGxvdHMgd291bGQgbG9vayBsaWtlIGlmIHRoZSB2YXJpYW5jZSB3ZXJlIG5vdCB0aGUgc2FtZSBiZXR3ZWVuIHRoZSBncm91cHM6DQoNCmBgYHtyfQ0KZGlldF9hbmRfZ3VpZGVsaW5lcyAlPiUNCiAgZmlsdGVyKGZvb2QgPT0gInJlZCBtZWF0IikgJT4lDQogIG11dGF0ZShleGFtcGxlX3BlcmMgPSBjYXNlX3doZW4oDQogICAgIyB0aGlzIHdpbGwgYXJ0aWZpY2FsbHkgbWFrZSBvdXIgZmVtYWxlIGRhdGEgaGF2ZSBkaWZmZXJlbnQNCiAgICAjIHZhcmlhbmNlIGZyb20gdGhlIG1hbGUgZGF0YQ0KICAgIHNleCA9PSAiRmVtYWxlIiB+IGxvZyhSZWxhdGl2ZV9QZXJjZW50KSwNCiAgICBzZXggPT0gIk1hbGUiIH4gUmVsYXRpdmVfUGVyY2VudA0KICApKSAlPiUNCiAgbG0obG9nMTAoZXhhbXBsZV9wZXJjKSB+IHNleCwgZGF0YSA9IC4pICU+JQ0KICBwbG90KHdoaWNoID0gMTozKQ0KYGBgDQoNCkluIHRoaXMgY2FzZSB0aGUgc3ByZWFkIG9mIHRoZSBwb2ludHMgaXMgY2xlYXJseSBsZXNzIGZvciBvbmUgZ3JvdXAgY29tcGFyZWQgdG8gdGhlIG90aGVyLiAgSWYgd2Ugc2F3IHBsb3RzIGxpa2UgdGhlc2UsIHdlIHdvdWxkIGJlIGNvbmNlcm5lZCB0aGUgYXNzdW1wdGlvbiBvZiBob21vc2NlZGFzdGljaXR5IHdhcyB2aW9sYXRlZC4NCg0KIyMjIyBBc3Nlc3NpbmcgaW5kZXBlbmRlbmNlDQoNCldlIG5ldmVyIGNvbnNpZGVyZWQgdGhlIGFzc3VtcHRpb24gb2YgaW5kZXBlbmRlbnQgcmVxdWlyZWQgYnkgYm90aCBhICR0JC10ZXN0IGFuZCBsaW5lYXIgcmVncmVzc2lvbi4gIERvIHdlIHRydWx5IGhhdmUgaW5kZXBlbmRlbnQgc2FtcGxlcyBpbiB0aGlzIGNhc2U/ICBObyEgIFNpbmNlIHdlIGhhdmUgZmVtYWxlIGFuZCBtYWxlIHZhbHVlcyBmcm9tIHRoZSBzYW1lIGNvdW50cmllcywgb3VyIGRhdGEgaXMgcmVhbGx5IHdoYXQgd2Ugd291bGQgY2FsbCAicGFpcmVkIi4gVGhlIG1hbGUgYW5kIGZlbWFsZSBkaWV0IHZhbHVlcyBmcm9tIHRoZSBzYW1lIGNvdW50cnkgYXJlIG1vc3QgbGlrZWx5IHJlbGF0ZWQgdG8gZWFjaCBhbm90aGVyIGJlY2F1c2Ugb2YgY3VsdHVyYWwgZWZmZWN0cyBvbiBkaWV0LiAgVGhpcyBtZWFucyB0aGUgYXNzdW1wdGlvbiBvZiBpbmRlcGVuZGVuY2UgZm9yIHRoZSBpbmRlcGVuZGVudCBzYW1wbGVzICR0JC10ZXN0IGlzIHZpb2xhdGVkLCBhcyBpcyB0aGUgaW5kZXBlbmRlbmNlIGFzc3VtcHRpb24gZm9yIGxpbmVhciByZWdyZXNzaW9uLiAgDQoNCldlIGNhbiBhZGRyZXNzIHRoaXMgYnkgZG9pbmcgYSAqcGFpcmVkKiAkdCQtdGVzdCBpbnN0ZWFkIG9mIGFuIGluZGVwZW5kZW50ICR0JC10ZXN0IGFuZCBieSBhY2NvdW50aW5nIGZvciBjb3VudHJ5IGluIG91ciBsaW5lYXIgbW9kZWwgYnkgYWRkaW5nIGl0IHRvIG91ciBtb2RlbCBhcyB3aGF0IHdlIGNhbGwgYSAqZml4ZWQgZWZmZWN0Ki4gICANCg0KIyMjICoqUGFpcmVkICR0JC10ZXN0IGFuZCBsaW5lYXIgbW9kZWwgd2l0aCBmaXhlZCBlZmZlY3RzKioNCioqKg0KDQpOb3cgd2Ugd2lsbCBwZXJmb3JtIHRoZSBwYWlyZWQgdmVyc2lvbnMgb2Ygb3VyIGFuYWx5c2lzLiBUaGlzIGlzIHZlcnkgZWFzeSB0byBkbyB3aXRoIHRoZSBgdC50ZXN0KClgIGZ1bmN0aW9uLCBieSBzaW1wbHkgdXNpbmcgdGhlIGBwYWlyZWRgIGFyZ3VtZW50IGFuZCBzZXR0aW5nIGl0IGVxdWFsIHRvIGBUUlVFYC4NCg0KSG93ZXZlciwgb3VyIGRhdGEgbmVlZHMgdG8gYmUgaW4gYSBzbGlnaHRseSBkaWZmZXJlbnQgZm9ybSB0byBkbyB0aGUgcGFpcmVkIHRlc3QsIHNpbmNlIHdlIGhhdmUgdG8gdGVsbCBgUmAgd2hpY2ggdmFsdWVzIG5lZWQgdG8gYmUgcGFpcmVkIHRvZ2V0aGVyLiAgSW5zdGVhZCBvZiBvbmUgbG9uZyBkYXRhc2V0IHdpdGggZGlmZmVyZW50IHJvd3MgZm9yIG1hbGVzIGFuZCBmZW1hbGVzLCB3ZSB3aWxsIG5lZWQgc2VwYXJhdGUgY29sdW1ucyBmb3IgdGhlIG1hbGUgYW5kIGZlbWFsZSB2YWx1ZXMuICBTbyB3ZSBuZWVkIHRvIG1ha2Ugb3VyIGRhdGFzZXQgKndpZGVyKi4gIFdlIGNhbiBkbyB0aGF0IHVzaW5nIHRoZSBgcGl2b3Rfd2lkZXIoKWAgZnVuY3Rpb24gb2YgIHRoZSBgdGlkeXJgIHBhY2thZ2UuIFRvIHVzZSB0aGlzIGZ1bmN0aW9uIHdlIHNwZWNpZnkgdGhlIHZhbHVlcyB0aGF0IHdlIHdhbnQgdG8gc2VwYXJhdGUgaW50byBtb3JlIHZhcmlhYmxlcyB1c2luZyB0aGUgYHZhbHVlc19mcm9tYCBhcmd1bWVudCBhbmQgd2UgdXNlIHRoZSBgbmFtZXNfZnJvbWAgYXJndW1lbnQgdG8gc3BlY2lmeSBob3cgd2Ugd2FudCB0byBzZXBhcmF0ZSB0aGVzZSBvdGhlciB2YXJpYWJsZXMuIEluIHRoaXMgY2FzZSB3ZSB3aWxsIG1ha2UgYSBtYWxlIGFuZCBmZW1hbGUgdmVyc2lvbiBvZiBhbGwgdGhlIG90aGVyIHZhcmlhYmxlcyBzcGVjaWZpZWQuDQoNCmBgYHtyfQ0Kd2lkZV9kaWV0IDwtIGRpZXRfYW5kX2d1aWRlbGluZXMgJT4lDQogIHBpdm90X3dpZGVyKA0KICAgIHZhbHVlc19mcm9tID0gYygNCiAgICAgIGNvbnRhaW5zKCJwZXJjZW50IiksDQogICAgICBtZWFuLA0KICAgICAgdXBwZXIsDQogICAgICBsb3dlciwNCiAgICAgIG9wdF9hY2hpZXZlZA0KICAgICksDQogICAgbmFtZXNfZnJvbSA9IHNleA0KICApDQoNCmdsaW1wc2Uod2lkZV9kaWV0KQ0KYGBgDQoNCllvdSBjYW4gc2VlIHdlIG5vdyBoYXZlIGEgYFJlbGF0aXZlX1BlcmNlbnRfTWFsZWAgdmFyaWFibGUgYW5kIGEgYFJlbGF0aXZlX1BlcmNlbnRfRmVtYWxlYCB2YXJpYWJsZS4gIFdlIGNhbiB1c2UgdGhlc2UgdHdvIHZhcmlhYmxlcyBpbiBvdXIgcGFpcmVkICR0JC10ZXN0LiAgU2luY2UgdGhlIHBhaXJlZCB2ZXJzaW9uIG9mIHRoZSAkdCQtdGVzdCBkb2Vzbid0IHRha2UgYSBgZGF0YT1gIGFyZ3VtZW50LCB3ZSB3aWxsIHB1bGwgdGhlIGFwcHJvcHJpYXRlIHZhcmlhYmxlcyBmcm9tIG91ciBkYXRhIGEgbGl0dGxlIGJpdCBkaWZmZXJlbnRseSwgdXNpbmcgdGhlIGBwdWxsKClgIGZ1bmN0aW9uLg0KYGBge3J9DQp0LnRlc3QobG9nMTAocHVsbCgNCiAgZmlsdGVyKHdpZGVfZGlldCwgZm9vZCA9PSAicmVkIG1lYXQiKSwNCiAgUmVsYXRpdmVfUGVyY2VudF9NYWxlDQopKSwNCmxvZzEwKHB1bGwoDQogIGZpbHRlcih3aWRlX2RpZXQsIGZvb2QgPT0gInJlZCBtZWF0IiksDQogIFJlbGF0aXZlX1BlcmNlbnRfRmVtYWxlDQopKSwNCnZhci5lcXVhbCA9IFRSVUUsIHBhaXJlZCA9IFRSVUUNCikNCmBgYA0KDQpIZXJlIGFuIGVzdGltYXRlZCBtZWFuIGRpZmZlcmVuY2UgKE1hbGVzIC0gRmVtYWxlcykgb2YgMC4xOCwgYW5kIHRoYXQgdGhpcyBpcyBjb25zaWRlcmVkIHNpZ25pZmljYW50bHkgZGlmZmVyZW50IHRoYW4gMCBkdWUgdG8gYSB2ZXJ5IHNtYWxsICRwJC12YWx1ZS4gIFlvdSBjYW4gYWxzbyBzZWUgdGhhdCBub3cgb3VyIGRlZ3JlZXMgb2YgZnJlZWRvbSBhcmUgMTk0LCB3aGljaCBtYWtlcyBzZW5zZSBiZWNhdXNlIHdpdGggcGFpcmVkIHNhbXBsZXMgd2UgYXJlIG9ubHkgZXN0aW1hdGluZyBvbmUgcGFyYW1ldGVyICh0aGUgbWVhbiBkaWZmZXJlbmNlKSBiYXNlZCBvbiBkYXRhIG9uIDE5NSBkaWZmZXJlbmNlcyBmb3IgZWFjaCBjb3VudHJ5LiBTbyAkZGYgPSBuIC0gXCMgXCBwYXJhbWV0ZXJzID0gMTk1IC0xID0gMTk0JC4NCg0KVGhlIHBhaXJlZCB2ZXJzaW9uIG9mIHRoZSBsaW5lYXIgbW9kZWwgaXMgYSBiaXQgbW9yZSBjb21wbGV4LiBJbiB0aGlzIGNhc2Ugd2Ugd2lsbCBhZGQgYW5vdGhlciB0ZXJtIGluIG91ciBtb2RlbCB0byBldmFsdWF0ZSB0aGUgaW5mbHVlbmNlIG9mIGBzZXhgIG9uIGBSZWxhdGl2ZV9QZXJjZW50YCBjb25zdW1wdGlvbiB3aGlsZSBrZWVwaW5nIHRoZSBjb3VudHJ5IGlkZW50aXR5IGZpeGVkIG9yIGNvbnN0YW50LCBvciBpbiBvdGhlciB3b3JkcyBjb250cm9sbGluZy9hZGp1c3RpbmcgZm9yIGNvdW50cnkuIFdlIGNhbiB1c2UgdGhlICBgK2AgdG8gYWRkIHRoaXMgYWRkaXRpb25hbCB0ZXJtLiBOb3cgdGhhdCB3ZSBoYXZlIG11bHRpcGxlIGNvdmFyaWF0ZS9leHBsYW5hdG9yeSB2YXJpYWJsZSB0ZXJtcywgd2Ugd291bGQgY2FsbCB0aGlzIGEgKiptdWx0aXZhcmlhYmxlIGxpbmVhciByZWdyZXNzaW9uKiouDQoNClNvIG5vdyBvdXIgbW9kZWwgaW4gd29yZHMgd2lsbCBiZTogDQoNCk1lYW4gcmVsYXRpdmUgY29uc3VtcHRpb24gb2YgcmVkIG1lYXQgaXMgZGVwZW5kZW50IG9uIHNleCBhbmQgY291bnRyeS4gT3IgaW4gb3RoZXIgd29yZHMsIHNleCBhbmQgbG9jYXRpb24gaW5mbHVlbmNlIHRoZSBjb25zdW1wdGlvbiBvZiByZWQgbWVhdCBhcm91bmQgdGhlIHdvcmxkLg0KDQpUaGVuIHRoZSBjb2VmZmljaWVudCBmb3IgYHNleGAgd2lsbCBiZSBkaWZmZXJlbnQgZnJvbSB3aGF0IHdlIGhhZCBpbiBvdXIgcHJldmlvdXMgYGxtKClgIG1vZGVsLCBhcyBpdCB3aWxsIGJlIGNhbGN1bGF0ZWQgd2hpbGUga2VlcGluZyBgbG9jYXRpb25fbmFtZWAgb3IgdGhlIGNvdW50cnkgd2hlcmUgdGhlIGNvbnN1bXB0aW9uIHZhbHVlIHdhcyBvYnRhaW5lZCBmaXhlZCwgb3IgaW4gb3RoZXIgd29yZHMgImNvbnRyb2xsaW5nIGZvciBgbG9jYXRpb25fbmFtZWAuIiBUaGlzIHdpbGwgYWxzbyByZXN1bHQgaW4gb3V0cHV0IGZvciBlYWNoIG9mIHRoZSBjb3VudHJpZXMuIFRoZSBbY29lZmZpY2llbnRzXShodHRwczovL3d3dy50aGVhbmFseXNpc2ZhY3Rvci5jb20vaW50ZXJwcmV0aW5nLXJlZ3Jlc3Npb24tY29lZmZpY2llbnRzLyl7dGFyZ2V0PSJfYmxhbmsifSBoZXJlIHJlcHJlc2VudCB0aGUgYXZlcmFnZSBkaWZmZXJlbmNlIGluIGNvbnN1bXB0aW9uIHZhbHVlIGZvciBlYWNoIGNvdW50cnkgY29tcGFyZWQgdG8gdGhlIHJlZmVyZW5jZSBjb3VudHJ5IG9mIEFmZ2hhbmlzdGFuLCB3aGlsZSBhY2NvdW50aW5nIGZvciBzZXguDQoNClRoaXMgbm93IHNob3VsZCBtZWV0IHRoZSBhc3N1bXB0aW9uIG9mIGluZGVwZW5kZW5jZSBmb3IgYSBsaW5lYXIgcmVncmVzc2lvbiBtb2RlbCwgc2luY2Ugb2JzZXJ2YXRpb25zIHdpbGwgYmUgaW5kZXBlbmRlbnQgY29uZGl0aW9uYWwgYW4gdGhlIGNvdmFyaWF0ZXMgb2Ygc2V4IGFuZCBjb3VudHJ5Lg0KDQpMZXQncyBmaXQgdGhpcyBtb2RlbCBhbmQgbG9vayBhdCB0aGUgcmVzdWx0cy4NCg0KIyMjIyB7LnNjcm9sbGFibGUgfQ0KYGBge3J9DQpkaWV0X2FuZF9ndWlkZWxpbmVzICU+JQ0KICBmaWx0ZXIoZm9vZCA9PSAicmVkIG1lYXQiKSAlPiUNCiAgbG0obG9nMTAoUmVsYXRpdmVfUGVyY2VudCkgfiBzZXggKyBsb2NhdGlvbl9uYW1lLCBkYXRhID0gLikgJT4lDQogIHN1bW1hcnkoKQ0KYGBgDQojIyMjDQoNCkZpcnN0IGxldCdzIGxvb2sgYXQgdGhlIGVzdGltYXRlZCBjb2VmZmljaWVudCBmb3IgdGhlIGBzZXhNYWxlYCB2YXJpYWJsZSwgd2hpY2ggaXMgMC4xOC4gIFRoaXMgY2FuIGJlIGludGVycHJldGVkIGFzIHRoZSBkaWZmZXJlbmNlIGluIG1lYW4gbG9nIHJlbGF0aXZlIHBlcmNlbnQgY29uc3VtcHRpb24gYmV0d2VlbiBtYWxlcyBhbmQgZmVtYWxlcywgaG9sZGluZyBjb3VudHJ5IGNvbnN0YW50LiAgU28gY29tcGFyaW5nIG1hbGVzIHRvIGZlbWFsZXMgd2l0aGluIHRoZSBzYW1lIGNvdW50cnkuICBOb3RpY2UgdGhpcyBpcyB0aGUgc2FtZSBlc3RpbWF0ZWQgZGlmZmVyZW5jZSB3ZSBmb3VuZCBmcm9tIG91ciBwYWlyZWQgJHQkLXRlc3QhICBUaGUgJHAkLXZhbHVlIGZvciB0aGlzIGNvZWZmaWNpZW50IGFsc28gbWF0Y2hlcyB0aGUgJHAkLXZhbHVlIGZyb20gdGhlIHBhaXJlZCAkdCQtdGVzdC4NCg0KWW91IGNhbiBhbHNvIHNlZSBmcm9tIHRoaXMgb3V0cHV0IHRoYXQgd2UgaGF2ZSBhIGNvZWZmaWNpZW50IGZvciBldmVyeSBjb3VudHJ5IGV4Y2VwdCBBZmdoYW5pc3Rhbiwgd2hpY2ggaXMgb3VyIHJlZmVyZW5jZSBjb3VudHJ5LiAgVGhlc2UgY29lZmZpY2llbnRzIGNvbXBhcmUgdGhlIGNvdW50cnkgdG8gdGhhdCByZWZlcmVuY2UuICBTbyB0aGUgZXN0aW1hdGVkIGNvZWZmaWNpZW50IGZvciBgbG9jYXRpb25fbmFtZUFsYmFuaWFgLCAwLjQ0LCBjYW4gYmUgaW50ZXJwcmV0ZWQgYXMgdGhlIGRpZmZlcmVuY2UgaW4gbWVhbiBsb2cgcmVsYXRpdmUgcGVyY2VudCBjb25zdW1wdGlvbiBiZXR3ZWVuIEFsYmFuaWEgYW5kIEFmZ2hhbmlzdGFuLCBob2xkaW5nIHNleCBjb25zdGFudC4gIFNvIGNvbXBhcmluZyBBbGJhbmlhIHRvIEFmZ2hhbmlzdGFuIHdpdGhpbiBtYWxlcyBvciBjb21wYXJpbmcgQWxiYW5pYSB0byBBZmdoYW5pc3RhbiB3aXRoaW4gZmVtYWxlcy4NCg0KRmluYWxseSwgeW91IG1pZ2h0IG5vdGljZSB0aGF0IHRoZSBudW1iZXIgb2YgcmVzaWR1YWwgZGVncmVlcyBvZiBmcmVlZG9tIGZvciB0aGlzIHJlZ3Jlc3Npb24gaXMgMTk0LCBqdXN0IGFzIGluIHRoZSBwYWlyZWQgJHQkLXRlc3QuICBUaGlzIG1ha2VzIHNlbnNlIHNpbmNlIHdlIGhhdmUgdG8gZXN0aW1hdGUgYSBjb2VmZmljaWVudCBmb3IgMTk0IGNvdW50cmllcyAoYWxsIGV4Y2VwdCBBZmdoYW5pc3RhbikgYXMgd2VsbCBhcyBhIGNvZWZmaWNpZW50IGZvciBzZXggYW5kIGFuIGludGVyY2VwdC4gIFNvIHdlIGhhdmU6DQoNCiQkZGYgPSBuIC0gIyBwYXJhbWV0ZXJzIGVzdGltYXRpbmcgPSAzOTAgLSAxOTQgLSAyID0gMTk0JCQgDQoNCldlIHNob3VsZCBhbHNvIGNoZWNrIHRoZSByZXNpZHVhbCBwbG90cyBmb3IgdGhpcyBmaXhlZCBlZmZlY3RzIHJlZ3Jlc3Npb24gbW9kZWwuDQoNCmBgYHtyfQ0KZGlldF9hbmRfZ3VpZGVsaW5lcyAlPiUNCiAgZmlsdGVyKGZvb2QgPT0gInJlZCBtZWF0IikgJT4lDQogIGxtKGxvZzEwKFJlbGF0aXZlX1BlcmNlbnQpIH4gc2V4ICsgbG9jYXRpb25fbmFtZSwgZGF0YSA9IC4pICU+JQ0KICBwbG90KHdoaWNoID0gMTozKQ0KYGBgDQoNClRoZXNlIHJlc2lkdWFsIHBsb3RzIGxvb2sgbXVjaCBiZXR0ZXIgdGhhbiBvdXIgcHJldmlvdXMgcGxvdHMuICBUaGlzIFtndWlkZV0oaHR0cHM6Ly9kYXRhLmxpYnJhcnkudmlyZ2luaWEuZWR1L2RpYWdub3N0aWMtcGxvdHMvKSBwcm92aWRlcyBtb3JlIGluZm9ybWF0aW9uIG9uIGhvdyB0byBpbnRlcnByZXQgdGhlc2UgcmVzaWR1YWwgZGlhZ25vc3RpYyBwbG90cy4NCg0KQmFzZWQgb24gb3VyIFtRLVEgcGxvdF0oaHR0cDovL29ubGluZXN0YXRib29rLmNvbS8yL2FkdmFuY2VkX2dyYXBocy9xLXFfcGxvdHMuaHRtbCl7dGFyZ2V0PSJfYmxhbmsifSwgd2UgYXBwZWFyIHRvIGhhdmUgc29tZSBvdXRsaWVycyBwZXJoYXBzIGF0IHRoZSBleHRyZW1lIGVuZHMgb2Ygb3VyIHRhaWxzIGJ1dCBvdmVyYWxsIHRoZSByZXNpZHVhbHMgbG9vayBmYWlybHkgbm9ybWFsLiBUaGUgW3Jlc2lkdWFsIHZzIGZpdHRlZCBwbG90XShodHRwczovL29ubGluZS5zdGF0LnBzdS5lZHUvc3RhdDQ2Mi9ub2RlLzExOC8pe3RhcmdldD0iX2JsYW5rIn0gc2hvd3MgdXMgaWYgdGhlIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIG91ciBvdXRjb21lIHZhcmlhYmxlIGFuZCBvdXIgcHJlZGljdG9ycyBsb29rcyBsaW5lYXIsIGlmIHRoZXJlIGlzIHVuZXF1YWwgZXJyb3IgdmFyaWFuY2UgYmV0d2VlbiBncm91cHMsIGFuZCBpZiB0aGVyZSBhcmUgcG9zc2libGUgb3V0bGllcnMuIElkZWFsbHkgdGhpcyBzaG91bGQgbG9vayBsaWtlIGEgYmFuZCBvZiBwb2ludHMgZXF1YWxseSBjZW50ZXJlZCBhcm91bmQgemVyby4gSGVyZSBhcmUgW2V4YW1wbGVzXShodHRwOi8vZG9jcy5zdGF0d2luZy5jb20vaW50ZXJwcmV0aW5nLXJlc2lkdWFsLXBsb3RzLXRvLWltcHJvdmUteW91ci1yZWdyZXNzaW9uLyl7dGFyZ2V0PSJfYmxhbmsifSBvZiB0aGVzZSBwbG90cyB0aGF0IG1pZ2h0IHNob3cgaXNzdWVzIG9mIGNvbmNlcm4uIA0KDQpPdmVyYWxsIG91ciBwbG90IGxvb2tzIGZhaXJseSBnb29kLiBUaGUgc2hhcGUgb2Ygb3VyIGRhdGEgbG9va3MgZmFpcmx5IGxpbmVhciAodGhlIHJlc2lkdWFscyBkb24ndCBhcHBlYXIgdG8gaGF2ZSBhIHNoYXBlIG90aGVyIHRoYW4gYSBiYW5kIG9yIGxpbmUpLCB0aGVyZSBkb2VzIG5vdCBhcHBlYXIgdG8gYmUgYW55IGV4dHJlbWUgb3V0bGllcnMgKG5vIGRhdGEgcG9pbnRzIGFyZSBlc3BlY2lhbGx5IGZhciBhd2F5KSBhbmQgdGhlIHBvaW50cyBoYXZlIHRoZSBzYW1lIGdlbmVyYWwgcmFuZ2UgYXJvdW5kIHRoZSBsaW5lIGZvciB0aGUgdmFyaW91cyBmaXR0ZWQgdmFsdWVzLiBUaGVyZSBhcmUgYSBmZXcgcG9pbnRzIHdpdGggd2lkZXIgcmVzaWR1YWxzIGF0IHRoZSBoaWdoZXIgZml0dGVkIHZhbHVlcywgYnV0IG92ZXJhbGwgdGhpcyBsb29rcyBxdWl0ZSByZWFzb25hYmxlLiANCg0KT3VyIFtzY2FsZS1sb2NhdGlvbiBwbG90XShodHRwczovL2Jvb3N0ZWRtbC5jb20vMjAxOS8wMy9saW5lYXItcmVncmVzc2lvbi1wbG90cy1zY2FsZS1sb2NhdGlvbi1wbG90Lmh0bWwpe3RhcmdldD0iX2JsYW5rIn0gYWxzbyBzaG93cyB1cyB0aGF0IG91ciB2YXJpYW5jZSBsb29rcyBmYWlybHkgZXF1YWwgYWNyb3NzIGdyb3VwcyBhcyBvdXIgdmFsdWVzIHNob3cgYSByZWxhdGl2ZWx5IGV2ZW4gc3ByZWFkLiBBIGxhcmdlciAgYmVuZCBpbiB0aGUgbGluZSB3b3VsZCBpbmRpY2F0ZSBtb3JlIHZhcmlhdGlvbiBpbiB0aGUgdmFyaWFuY2UgYWNyb3NzIG91ciBpbmRlcGVuZGVudCB2YXJpYWJsZSBncm91cHMgYWxzbyBrbm93biBhcyBbaGV0ZXJvc2NlZGFzdGljaXR5XShodHRwczovL3N0YXRpc3RpY3NieWppbS5jb20vcmVncmVzc2lvbi9oZXRlcm9zY2VkYXN0aWNpdHktcmVncmVzc2lvbi8pe3RhcmdldD0iX2JsYW5rIn0uIFRoZXJlIGlzIG9ubHkgYSBzbGlnaHQgYmVuZCBpbiB0aGUgbGluZSBmb3Igb3VyIGRhdGEgc3VnZ2VzdGl2ZSBvZiBbaG9tb3NjZWRhc3RpY2l0eSBdKGh0dHBzOi8vd3d3LnN0YXRpc3RpY3Nzb2x1dGlvbnMuY29tL2hvbW9zY2VkYXN0aWNpdHkvKXt0YXJnZXQ9Il9ibGFuayJ9LiBTbyBvdXIgYXNzdW1wdGlvbnMgbG9vayBwcmV0dHkgZ29vZDoNCg0KMSkgTGluZWFyIC0gdGhlIHJlbGF0aW9uc2hpcCBhcHBlYXJzIHRvIGJlIGZhaXJseSBsaW5lYXIgDQoyKSBJbmRlcGVuZGVuY2UgLSBub3cgdGhhdCB3ZSBoYXZlIHRha2VuIGNhcmUgb2YgdGhlIGxvY2F0aW9uIHN0cnVjdHVyZSBpbiBvdXIgZGF0YSwgb3VyIHNhbXBsZXMgYXJlIGluZGVwZW5kZW50DQozKSBOb3JtYWxpdHkgLSB0aGUgcmVzaWR1YWxzIGFwcGVhciB0byBiZSBmYWlybHkgbm9ybWFsbHkgZGlzdHJpYnV0ZWQgYW5kIHdlIGhhdmUgYSBsYXJnZSBudW1iZXIgb2Ygc2FtcGxlcyB0byBoZWxwIGFjY291bnQgZm9yIG1pbm9yIHZpb2xhdGlvbnMNCjQpIEVxdWFsIHZhcmlhbmNlIC0gdGhlIHZhcmlhbmNlIGluIHRoZSByZXNpZHVhbHMgYXBwZWFyIHRvIGJlIGZhaXJseSBlcXVhbCBhY3Jvc3MgdGhlIGdyb3VwcyBvZiB0aGUgaW5kZXBlbmRlbnQvcHJlZGljdG9yIHZhcmlhYmxlcw0KDQoNCiMjIyAqKlBhaXJlZCAkdCQtdGVzdCBhbmQgbGluZWFyIG1vZGVsIHdpdGggbWl4ZWQgZWZmZWN0cyoqDQoqKioNCg0KDQpUbyAicGFpciIgb3VyIGRhdGEgdXNpbmcgZml4ZWQgZWZmZWN0cyBjb3N0IHVzIGFuIGFkZGl0aW9uYWwgMTk0IHZhcmlhYmxlcyBpbiBvdXIgcmVncmVzc2lvbiBtb2RlbCwgb25lIGZvciBlYWNoIGNvdW50cnkgZXhjZXB0IEFmZ2hhbmlzdGFuLiAgQWx0ZXJuYXRpdmVseSwgd2UgY2FuIHBlcmZvcm0gYSBzbGlnaHRseSBkaWZmZXJlbnQgdHlwZSBvZiByZWdyZXNzaW9uIHRoYXQgc3RpbGwgYWNjb3VudHMgZm9yIHRoZSBwYWlyZWQgc3RydWN0dXJlIGluIHRoZSBkYXRhLg0KDQpJbiB0aGlzIGNhc2Ugd2Ugd2lsbCB1c2UgdGhlIGBsbWVyKClgIGZ1bmN0aW9uIG9mIHRoZSBgbG1lNGAgcGFja2FnZS4gIFRoaXMgZnVuY3Rpb24gYWxsb3dzIHVzIHRvIGZpdCB3aGF0IGlzIGNhbGxlZCBhIFtsaW5lYXIgbWl4ZWQgZWZmZWN0cyByZWdyZXNzaW9uIG1vZGVsXShodHRwczovL291cmNvZGluZ2NsdWIuZ2l0aHViLmlvL3R1dG9yaWFscy9taXhlZC1tb2RlbHMvKXt0YXJnZXQ9Il9ibGFuayJ9LiBXZSB3aWxsIGFsc28gdXNlIHRoZSBgbG1lclRlc3RgIHBhY2thZ2UsIHNpbmNlIHRoaXMgYWRkcyB0ZXN0IHN0YXRpc3RpY3MgYW5kICRwJC12YWx1ZXMgdG8gdGhlIGxpbmVhciBtaXhlZCBlZmZlY3RzIG1vZGVsIG91dHB1dC4gIA0KDQpUaGlzIHR5cGUgb2YgcmVncmVzc2lvbiBpcyBjYWxsZWQgKiptaXhlZCoqIGJlY2F1c2UgaXQgY29udGFpbnMgYm90aCAqKmZpeGVkKiogYW5kICoqcmFuZG9tKiogZWZmZWN0cy4gIFRoZXJlIGFyZSBtYW55IGRpZmZlcmVudCBkZWZpbml0aW9ucyBmb3IgKipmaXhlZCoqIGFuZCAqKnJhbmRvbSoqIGVmZmVjdHMgYW5kIHRoZSBkaWZmZXJlbmNlIGlzIGNvbmNlcHR1YWxseSBjb21wbGV4IGFuZCBjb250ZXh0IHNwZWNpZmljLiANCg0KSG93ZXZlciBpbiBzaW1wbGlzdGljIHRlcm1zLCAqKmZpeGVkIGVmZmVjdHMqKiBhcmUgZ2VuZXJhbGx5IHNwZWFraW5nIHRoZSB2YXJpYWJsZXMgb2YgaW50ZXJlc3QgdGhhdCB3ZSBoYXZlIHJlYXNvbiB0byBiZWxpZXZlIGV4cGxhaW4gb3IgcHJlZGljdCB0aGUgb3V0Y29tZSBvciByZXNwb25zZSB2YXJpYWJsZSwgd2hpbGUgcmFuZG9tIGVmZmVjdHMgYXJlIHRob3NlIHRoYXQgbWF5IGludHJvZHVjZSBhZGRpdGlvbmFsIHZhcmlhbmNlIGluIHRoZSBpbmZsdWVuY2Ugb2YgdGhvc2UgcHJlZGljdG9yIHZhcmlhYmxlcyBvbiB0aGUgb3V0Y29tZSB2YXJpYWJsZS4gRm9yIGV4YW1wbGUsIHRoZXkgbWF5IHByb3ZpZGUgaW5mb3JtYXRpb24gYWJvdXQgKipncm91cCBvciBiYXRjaCBzdHJ1Y3R1cmVzKiogd2l0aGluIHRoZSBkYXRhLiAgDQoNCkluIG91ciBjYXNlLCB3ZSBhcmUgaW50ZXJlc3RlZCBpbiB0aGUgaW5mbHVlbmNlIG9mIGBzZXhgIG9uIHRoZSBjb25zdW1wdGlvbiBvZiByZWQgbWVhdCwgaG93ZXZlciB0aGUgaWRlbnRpdHkgb2YgdGhlIGNvdW50cnkgd2hlcmUgdGhlIG1hbGUgYW5kIGZlbWFsZSBjb25zdW1wdGlvbiB2YWx1ZXMgd2VyZSBvYnRhaW5lZCBtYXkgaW5mbHVlbmNlIHRoaXMgcmVsYXRpb25zaGlwIGFuZCB3ZSB3b3VsZCBsaWtlIHRvIGNvbnRyb2wgZm9yIHRoYXQuIFdlIGRvbid0IHdhbnQgdG8gbW9kZWwgZm9yIGBsb2NhdGlvbl9uYW1lYCBpdHNlbGYsIGJ1dCBqdXN0IG1vZGVsIGl0J3MgaW5mbHVlbmNlIG9uIHRoZSByZWxhdGlvbnNoaXAgb2YgYHNleGAgb24gY29uc3VtcHRpb24gb2YgcmVkIG1lYXQuIEluIG90aGVyIHdvcmRzLCB3ZSBhcmUgaW50ZXJlc3RlZCBpbiBnZXR0aW5nIGEgc2Vuc2Ugb2YgaG93IHNleCBpbmZsdWVuY2VzIGNvbnN1bXB0aW9uIHJhdGVzIGluIGdlbmVyYWwgYW5kIHdlIHdhbnQgdG8gYWNjb3VudCBmb3IgdGhlIHBhaXJlZCBzdHJ1Y3R1cmUgd2l0aGluIG91ciBkYXRhLCB0aGUgZmFjdCB0aGF0IHdlIGhhdmUgY29ycmVzcG9uZGluZyBjb25zdW1wdGlvbiB2YWx1ZXMgZm9yIHRoZSB0d28gc2V4ZXMgZnJvbSBkaWZmZXJlbnQgY291bnRyaWVzLiBUaGUgbm90YXRpb24gZm9yIGluY2x1ZGluZyBhIHJhbmRvbSBlZmZlY3QgbGlrZSB0aGlzIGlzICBgMSB8IHZhcmlhYmxlX25hbWVgLiBUaGUgb25lIGluZGljYXRlcyBhIHZhcnlpbmctaW50ZXJjZXB0IGdyb3VwIGVmZmVjdCwgaW4gb3RoZXIgd29yZHMgd2UgZXhwZWN0IHRoYXQgdGhlIGludGVyY2VwdCBtYXkgdmFyeSBmb3IgZWFjaCB2YWx1ZSBvZiB0aGUgdmFyaWFibGUgaW5kaWNhdGVkIHRvIHRoZSByaWdodCBvZiB0aGUgYHxgLiBTbyBpbiBvdXIgY2FzZSwgdGhlIGludGVyY2VwdCAgKGxvZyByZWxhdGl2ZSBwZXJjZW50IGNvbnN1bXB0aW9uIHdoZW4gc2V4IGlzIGFzc2lnbmVkIHRvIHRoZSB6ZXJvIHZhbHVlIC0gZmVtYWxlKSBtYXkgYmUgZGlmZmVyZW50IGZvciBlYWNoIGNvdW50cnkuDQoNCkxldCdzIGZpdCBhIG1peGVkIGVmZmVjdHMgbW9kZWwgdGhhdCBpbmNsdWRlcyBhIGZpeGVkIGVmZmVjdCBmb3Igc2V4IGFuZCBhIHJhbmRvbSBpbnRlcmNlcHQgZm9yIGNvdW50cnk6DQoNCmBgYHtyfQ0KZGlldF9hbmRfZ3VpZGVsaW5lcyAlPiUNCiAgZmlsdGVyKGZvb2QgPT0gInJlZCBtZWF0IikgJT4lDQogIGxtZXIobG9nMTAoUmVsYXRpdmVfUGVyY2VudCkgfiBzZXggKyAoMSB8IGxvY2F0aW9uX25hbWUpLCBkYXRhID0gLikgJT4lDQogIHN1bW1hcnkoKQ0KYGBgDQoNCkhvdyB3b3VsZCB3ZSBpbnRlcnByZXQgdGhlIHJlc3VsdHMgb2YgdGhpcyBtb2RlbD8gQWdhaW4sIGxldCdzIGxvb2sgYXQgdGhlIGVzdGltYXRlZCBjb2VmZmljaWVudCBmb3IgdGhlIGBzZXhNYWxlYCB2YXJpYWJsZSwgd2hpY2ggaXMgMC4xOC4gIFRoaXMgY2FuIGJlIGludGVycHJldGVkIHRoZSBzYW1lIHdheSBhcyBpbiB0aGUgc2ltcGxlIGxpbmVhciByZWdyZXNzaW9uLCBhcyB0aGUgZGlmZmVyZW5jZSBpbiBtZWFuIGxvZyByZWxhdGl2ZSBwZXJjZW50IGNvbnN1bXB0aW9uIGJldHdlZW4gbWFsZXMgYW5kIGZlbWFsZXMuICBIb3dldmVyLCBoZXJlIHdlIGhhdmVuJ3QgdmlvbGF0ZWQgdGhlIGluZGVwZW5kZW5jZSBhc3N1bXB0aW9uIGJlY2F1c2Ugd2UgYXJlIGFjY291bnRpbmcgZm9yIHRoZSBwYWlyZWQgbmF0dXJlIG9mIHRoZSBkYXRhIHRocm91Z2ggdGhlIHJhbmRvbSBlZmZlY3QgZm9yIGNvdW50cnkuICBUaGUgJHQkLXN0YXRpc3RpYyBhbmQgJHAkLXZhbHVlIGZvciB0aGlzIGNvZWZmaWNpZW50IGFsc28gbWF0Y2ggdGhvc2UgZnJvbSB0aGUgcGFpcmVkICR0JC10ZXN0IHdlIGRpZCBiZWZvcmU6DQpgYGB7cn0NCnQudGVzdChsb2cxMChwdWxsKA0KICBmaWx0ZXIod2lkZV9kaWV0LCBmb29kID09ICJyZWQgbWVhdCIpLA0KICBSZWxhdGl2ZV9QZXJjZW50X01hbGUNCikpLA0KbG9nMTAocHVsbCgNCiAgZmlsdGVyKHdpZGVfZGlldCwgZm9vZCA9PSAicmVkIG1lYXQiKSwNCiAgUmVsYXRpdmVfUGVyY2VudF9GZW1hbGUNCikpLA0KdmFyLmVxdWFsID0gVFJVRSwgcGFpcmVkID0gVFJVRQ0KKQ0KYGBgDQoNCk5vdGljZSB0aGF0IGluIHRoZSBvdXRwdXQgZm9yIHRoZSBtaXhlZCBlZmZlY3RzIG1vZGVsLCB0aGVyZSBhcmUgKipub3QqKiBjb2VmZmljaWVudHMgZm9yIGVhY2ggY291bnRyeSwgbGlrZSB0aGVyZSB3ZXJlIGluIHRoZSBmaXhlZCBlZmZlY3RzIG1vZGVsLiAgVGhpcyBpcyBiZWNhdXNlIHdlIGFyZSBub3QgZXhwbGljaXRseSBlc3RpbWF0aW5nIGluZGl2aWR1YWwgY291bnRyeSBlZmZlY3RzIGluIHRoaXMgbW9kZWwuICBJbnN0ZWFkLCB0aGUgY291bnRyeSBlZmZlY3QgaXMgY2FwdHVyZWQgdGhyb3VnaCB0aGUgaW50ZXJjZXB0IGluIHRoaXMgbW9kZWwuICBPdXIgZXN0aW1hdGVkIGludGVyY2VwdCBpcyAxLjgwIGFuZCB0aGUgc3RhbmRhcmQgZGV2aWF0aW9uIG9mIHRoaXMgaW50ZXJjZXB0IGlzIDAuMzQgKHNob3duIGluIHRoZSBgUmFuZG9tIGVmZmVjdHNgIHRhYmxlIGluIHRoZSBvdXRwdXQuKSAgV2UgY2FuIGludGVycHJldCB0aGlzIGFzIHNheWluZyB0aGF0IGVhY2ggY291bnRyeSBoYXMgYW4gaW50ZXJjZXB0IHRoYXQgY29tZXMgZnJvbSBhIG5vcm1hbCBkaXN0cmlidXRpb24gd2l0aCBtZWFuIG9mIDEuODAgYW5kIGEgc3RhbmRhcmQgZGV2aWF0aW9uIG9mIDAuMzQuICBTaW5jZSB0aGUgaW50ZXJjZXB0IGluIHRoaXMgbW9kZWwgcmVwcmVzZW50cyB0aGUgbG9nIHJlbGF0aXZlIHBlcmNlbnQgY29uc3VtcHRpb24gZm9yIGZlbWFsZXMsIHRoaXMgZ2l2ZSB1cyBhbiBpZGVhIG9mIGhvdyBmZW1hbGUgY29uc3VtcHRpb24gdmFyaWVzIGFjcm9zcyBjb3VudHJpZXMgLS0gYXZlcmFnZSBsb2cgY29uc3VtcHRpb24gYWNyb3NzIGNvdW50cmllcyBpcyAxLjgwLCBidXQgdGhlcmUgaXMgdmFyaWFiaWxpdHkgZnJvbSBvbmUgY291bnRyeSB0byBhbm90aGVyLiAgQW5kIHRoZW4gdGhlIG1hbGUgbG9nIGNvbnN1bXB0aW9uIGlzLCBvbiBhdmVyYWdlLCAwLjE4IGhpZ2hlciB0aGFuIGZvciBmZW1hbGVzLg0KDQpJdCBpcyBtb3JlIGNvbXBsaWNhdGVkIHRvIGNhbGN1bGF0ZSB0aGUgZGVncmVlcyBvZiBmcmVlZG9tIGluIHRoZSBtaXhlZCBlZmZlY3QgbW9kZWwgYW5kIGJleW9uZCB0aGlzIGNhc2Ugc3R1ZHksIGJ1dCBpdCBpcyBiYXNlZCBvbiB0aGUgW1NhdHRlcnRod2FpdGUgZm9ybXVsYV0oaHR0cHM6Ly93d3cuc3RhdGlzdGljc2hvd3RvLmRhdGFzY2llbmNlY2VudHJhbC5jb20vc2F0dGVydGh3YWl0ZS1mb3JtdWxhLyl7dGFyZ2V0PSJfYmxhbmsifSBhbmQgcmVzdWx0cyBpbiB0aGUgc2FtZSBkZWdyZWVzIG9mIGZyZWVkb20uDQoNCkZpbmFsbHksIGxldHMgc2VlIHdoYXQgb3VyIHJlc2lkdWFsIHBsb3RzIGxvb2sgbGlrZSBmb3IgdGhpcyBtaXhlZCBlZmZlY3RzIG1vZGVsLiAgV2UgY2FuJ3QgdXNlIHRoZSBgcGxvdCgpYCBmdW5jdGlvbiB3aXRoIGEgYGxtZXIoKWAgbW9kZWwgdG8gZ2V0IGFsbCBvZiB0aGUgcGxvdHMgYXQgb25jZSwgYnV0IHdlIGNhbiBjb25zdHJ1Y3QgYSByZXNpZHVhbCB2cy4gZml0dGVkIHZhbHVlIHBsb3QgYW5kIGEgUS1RIHBsb3Qgb3Vyc2VsdmVzOg0KDQpgYGB7cn0NCmRpZXRfYW5kX2d1aWRlbGluZXMgJT4lDQogIGZpbHRlcihmb29kID09ICJyZWQgbWVhdCIpICU+JQ0KICBsbWVyKGxvZzEwKFJlbGF0aXZlX1BlcmNlbnQpIH4gc2V4ICsgKDEgfCBsb2NhdGlvbl9uYW1lKSwgZGF0YSA9IC4pICU+JQ0KICBwbG90KCkNCg0KZGlldF9hbmRfZ3VpZGVsaW5lcyAlPiUNCiAgZmlsdGVyKGZvb2QgPT0gInJlZCBtZWF0IikgJT4lDQogIGxtZXIobG9nMTAoUmVsYXRpdmVfUGVyY2VudCkgfiBzZXggKyAoMSB8IGxvY2F0aW9uX25hbWUpLCBkYXRhID0gLikgJT4lDQogIHJlc2lkKCkgJT4lDQogIHFxbm9ybSgpDQoNCmRpZXRfYW5kX2d1aWRlbGluZXMgJT4lDQogIGZpbHRlcihmb29kID09ICJyZWQgbWVhdCIpICU+JQ0KICBsbWVyKGxvZzEwKFJlbGF0aXZlX1BlcmNlbnQpIH4gc2V4ICsgKDEgfCBsb2NhdGlvbl9uYW1lKSwgZGF0YSA9IC4pICU+JQ0KICByZXNpZCgpICU+JQ0KICBxcWxpbmUoKQ0KYGBgDQoNCk5vdGljZSB0aGF0IHRoZSBwbG90cyBsb29rIHZlcnkgc2ltaWxhciB0byB3aGF0IHdlIHNhdyB3aXRoIHRoZSBmaXhlZCBlZmZlY3RzIG1vZGVsLg0KDQpXZSBzZWUgdGhhdCB0aGUgcGFpcmVkICR0JC10ZXN0LCB0aGUgZml4ZWQgZWZmZWN0cyBtb2RlbCBhZGp1c3RpbmcgZm9yIGNvdW50cnksIGFuZCB0aGUgbWl4ZWQgZWZmZWN0cyBtb2RlbCB3aXRoIGEgcmFuZG9tIGludGVyY2VwdCBmb3IgY291bnRyeSBhbGwgZ2l2ZSB0aGUgc2FtZSByZXN1bHRzIGluIHRoaXMgY2FzZS4gIFNvIHdoaWNoIHRlc3Qgc2hvdWxkIHdlIHVzZT8gIFRoZSBkZWNpc2lvbiBvZiB3aGljaCB0ZXN0IHRvIHBlcmZvcm0gZGVwZW5kcyBvbiB5b3VyIHF1ZXN0aW9uIG9mIGludGVyZXN0LiBJbiB0aGlzIGNhc2Ugd2Ugd2VyZSBwYXJ0aWN1bGFybHkgaW50ZXJlc3RlZCBpbiB0aGUgaW5mbHVlbmNlIG9mIGBzZXhgLCBzbyBzZXR0aW5nIGBsb2NhdGlvbl9uYW1lYCB0byBhIHJhbmRvbSBlZmZlY3QgcHJvdmlkZXMgdGhlIHNhbWUgbGV2ZWwgb2YgZGV0YWlsIGFib3V0IHNleCB3aXRob3V0IGFzIG11Y2ggaW5mb3JtYXRpb24gYWJvdXQgYGxvY2F0aW9uX25hbWVgLCBzbyB0aGF0IG1pZ2h0IGJlIGlkZWFsLiBBcyB3ZSBjYW4gc2VlLCB0aGUgcmVzdWx0cywgaW4gdGhpcyBjYXNlLCBhcmUgdGhlIHNhbWUuICBUaGUgYmVuZWZpdCBvZiB1c2luZyByZWdyZXNzaW9uIG92ZXIgYSBzaW1wbGUgcGFpcmVkICR0JC10ZXN0IHdvdWxkIGJlIHRoZSBhYmlsaXR5IHRvIGFkZCBvdGhlciBjb3ZhcmlhdGVzIHRvIG91ciBtb2RlbCBpZiB3ZSB3YW50ZWQgdG8gYWRqdXN0IGZvciBvdGhlciBjb3VudHJ5IGNoYXJhY3RlcmlzdGljcy4NCg0KT3ZlcmFsbCwgdGhvdWdoLCB3ZSBjYW4gY29uY2x1ZGUgZnJvbSB0aGVzZSB0ZXN0cyB0aGF0IHdlIGhhdmUgZW5vdWdoIGV2aWRlbmNlIHRvIHJlamVjdCB0aGUgbnVsbCBoeXBvdGhlc2lzIHRoYXQgdGhlcmUgaXMgbm8gZGlmZmVyZW5jZSBiZXR3ZWVuIHRoZSBtZWFuIGNvbnN1bXB0aW9uIG9mIG1hbGVzIGFuZCBmZW1hbGVzICggb3IgdGhhdCBgc2V4YCBoYXMgbm8gYXNzb2NpYXRpb24gb3IgaW5mbHVlbmNlIG9uIHJlZCBtZWF0IGNvbnN1bXB0aW9uLikgKipUaGVyZWZvcmUsIGl0IGFwcGVhcnMgdGhhdCBtYWxlcyBjb25zdW1lIHNpZ25pZmljYW50bHkgbW9yZSByZWQgbWVhdCB0aGFuIGZlbWFsZXMgZ2xvYmFsbHkuKiogDQoNCg0KIyMjICoqQU5hbHlzaXMgT2YgVkFyaWFuY2UgKEFOT1ZBKSB0ZXN0KioNCioqKg0KDQpXZSBhcmUgYWxzbyBpbnRlcmVzdGVkIGluIHRoZSBpbmZsdWVuY2Ugb2YgYWdlIGdyb3VwIG9uIGRpZXRhcnkgY29uc3VtcHRpb24sIGJ1dCBiZWNhdXNlIHRoZXJlIGFyZSAxNSBhZ2UgZ3JvdXBzIHdlIGNhbid0IGFzc2VzcyB0aGUgaW5mbHVlbmNlIG9mIGFnZSBncm91cCBvbiBjb25zdW1wdGlvbiB1c2luZyB0aGUgcGFpcmVkICR0JC10ZXN0LCBhcyB0aGlzIHRlc3QgY2FuIG9ubHkgY29tcGFyZSAyIGdyb3Vwcy4gDQoNCklmIHdlIHdhbnRlZCB0byB0ZXN0IHRoZSBoeXBvdGhlc2lzIHRoYXQgdGhlcmUgYXJlIGFueSBhZ2UgZ3JvdXAgZGlmZmVyZW5jZXMsIHRoYXQgYXQgbGVhc3Qgb25lIG9mIHRoZSBncm91cHMgaXMgZGlmZmVyZW50IGZyb20gdGhlIG90aGVyczsgd2UgY291bGQgdXNlIGFuIFtBTk9WQSB0ZXN0XShodHRwOi8vb25saW5lc3RhdGJvb2suY29tLzIvYW5hbHlzaXNfb2ZfdmFyaWFuY2UvaW50cm8uaHRtbCl7dGFyZ2V0PSJfYmxhbmsifS4gVGhpcyB0ZXN0IGFsbG93cyB1cyB0byBjb21wYXJlIG1lYW5zIG9mIDMgb3IgbW9yZSBncm91cHMgYnkgZXZhbHVhdGluZyB0aGUgdmFyaWFuY2Ugb2YgdGhlIGRhdGEgd2l0aGluIHRoZSBncm91cHMgYW5kIGFtb25nIHRoZSBncm91cHMuIA0KDQpPdXIgbnVsbCBoeXBvdGhlc2lzIGlzIHRoYXQgYWxsIGFnZSBncm91cHMgaGF2ZSBlcXVhbCBtZWFuczoNCiQkIEhfMDogXG11X3sxfSA9IFxtdV97Mn0gPVxtdV97M309XG11X3s0fSA9IC4uLiBcbXVfezE1fSAkJA0KDQpUaGUgYWx0ZXJuYXRpdmUgaHlwb3RoZXNpcyBpcyB0aGF0IGF0IGxlYXN0IG9uZSBhZ2UgZ3JvdXAgbWVhbiBpcyBub3QgZXF1YWwgdG8gdGhlIG90aGVycy4NCg0KKipJbXBvcnRhbnRseSoqLCBpZiB3ZSByZWplY3QgdGhlIG51bGwsIHdlICpkbyBub3Qga25vdyB3aGljaCBncm91cCBtZWFucyBhcmUgZGlmZmVyZW50IGZyb20gb25lIGFub3RoZXIqLiBTdWJzZXF1ZW50IHRlc3RpbmcgaXMgcmVxdWlyZWQgaWYgd2Ugd2FudCB0byBrbm93IHRoaXMgaW5mb3JtYXRpb24uIEluIHRoaXMgY2FzZSB3ZSBjYWxsIHRoaXMgdHlwZSBvZiBub24tc3BlY2lmaWMgaHlwb3RoZXNpcyBhbiAib21uaWJ1cyIgaHlwb3RoZXNpcy4NCg0KWW91IGNvdWxkIGFjdHVhbGx5IHBlcmZvcm0gYW4gQU5PVkEgdG8gY29tcGFyZSB0d28gbWVhbnMsIGJ1dCBpbiB0aGlzIGNhc2UgeW91IHdvdWxkIGdldCBhbiAkRiQtc3RhdGlzdGljIGluc3RlYWQgb2YgYSAkdCQtc3RhdGlzdGljIHdoaWNoIHdvdWxkIGJlIGVxdWl2YWxlbnQgdG8gJHReMiQuIEhvd2V2ZXIgaXQgaXMgbm90IGNvbnZlbnRpb25hbCB0byB1c2UgQU5PVkEgZm9yIG9ubHkgMiBtZWFucy4gVGhlICRGJC1zdGF0aXN0aWMgaXMgZGVyaXZlZCBmb3JtIHRoZSAkRiQtdGVzdCBpcyB1c2VkIGZvciBhIGZldyBkaWZmZXJlbnQgdHlwZSBvZiB0ZXN0cy4gSW4gdGhlIEFOT1ZBIHRoZSBGLXRlc3QgaXMgY2FsY3VsYXRlZCBhczoNCg0KJCRGID0gXGZyYWN7IA0KdmFyaWFiaWxpdHlcIGJldHdlZW5cICB0aGUgXCBncm91cHN9eyANCnZhcmlhYmxpdHlcIHdpdGhpblwgdGhlIFwgZ3JvdXBzfSQkDQoNClRoZSBsYXJnZXIgdGhlIHJhdGlvLCB0aGUgbGFyZ2VyIHRoZSB2YXJpYWJpbGl0eSBiZXR3ZWVuIHRoZSBncm91cHMsIHRodXMgdGhlIG1vcmUgbGlrZWx5IHRoYXQgdGhlIGRhdGEgZm9yIGVhY2ggZ3JvdXAgY29tZXMgZnJvbSBhIGRpZmZlcmVudCBkaXN0cmlidXRpb24gd2l0aCBkaWZmZXJlbnQgbWVhbnMsIHN1Z2dlc3RpbmcgdGhhdCB0aGUgZ3JvdXBzIGFyZSBkaWZmZXJlbnQuDQoNCkl0IHR1cm5zIG91dCB0aGF0IHRoZSBBTk9WQSB0ZXN0IGlzIGFsc28gZXF1aXZhbGVudCB0byBsaW5lYXIgcmVncmVzc2lvbi4gIFdlIHdpbGwgZGVtb25zdHJhdGUgdGhpcyBieSBldmFsdWF0aW5nIGhvdyB0aGUgY29uc3VtcHRpb24gb2YgcmVkIG1lYXQgdmFyaWVzIGJ5IGFnZSBncm91cCB1c2luZyBhbiBBTk9WQSBhbmQgYSBsaW5lYXIgcmVncmVzc2lvbi4NCg0KVGhpbmtpbmcgYWJvdXQgaG93IHdlIHdhbnQgdG8ga25vdyBpZiByZWQgbWVhdCBjb25zdW1wdGlvbiBkaWZmZXJzIGJldHdlZW4gYWdlIGdyb3VwcyBmcm9tIHRoZSBsaW5lYXIgcmVncmVzc2lvbiBwZXJzcGVjdGl2ZSwgd2UgY291bGQgYWxzbyBkZXNjcmliZSBvdXIgbnVsbCBoeXBvdGhlc2lzIGFzOg0KDQpUaGVyZSBpcyBubyBpbmZsdWVuY2Ugb2YgYWdlIGdyb3VwIGlkZW50aXR5IG9uIGNvbnN1bXB0aW9uIG9yIHRoZXJlIGlzIG5vIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIGFnZSBncm91cCBpZGVudGl0eSBhbmQgY29uc3VtcHRpb24uDQoNCkFuZCB3ZSBjb3VsZCBkZXNjcmliZSBvdXIgYWx0ZXJuYXRpdmUgaHlwb3RoZXNpcyBhczoNCg0KQWdlIGdyb3VwIGlkZW50aXR5IGRvZXMgaW5mbHVlbmNlIGNvbnN1bXB0aW9uIG9yIGV4cGxhaW4gc29tZSBvZiB0aGUgdmFyaWF0aW9uIGluIGNvbnN1bXB0aW9uLg0KDQojIyMjIEFOT1ZBIGFzc3VtcHRpb25zDQoNClRoZSBBTk9WQSBhc3N1bXB0aW9ucyBhcmUgcXVpdGUgc2ltaWxhciB0byB0aGUgJHQkLXRlc3QgYXNzdW1wdGlvbnM6DQoNCjEpIE5vcm1hbGl0eSBvZiB0aGUgZGF0YSBmb3IgYWxsIHRlc3RlZCBncm91cHMgKGxlc3Mgb2YgYW4gaXNzdWUgaWYgdGhlIG51bWJlciBvZiBvYnNlcnZhdGlvbnMgaXMgcmVsYXRpdmVseSBsYXJnZSB0b3RhbCBuID4gMzApDQoyKSBFcXVhbCB2YXJpYW5jZSBiZXR3ZWVuIHRoZSBncm91cHMgLSBha2EgW0hvbW9nZW5laXR5IG9mIFZhcmlhbmNlcyBhc3N1bXB0aW9uXShodHRwczovL3VjLXIuZ2l0aHViLmlvL2Fzc3VtcHRpb25zX2hvbW9nZW5laXR5KXt0YXJnZXQ9Il9ibGFuayJ9IChtYWtlIHN1cmUgeW91IGRvIHRoZSBjb3JyZWN0IHRlc3QgaWYgdGhlIGRhdGEgaXMgbm90IG5vcm1hbCkNCjMpIEluZGVwZW5kZW50IG9ic2VydmF0aW9ucw0KDQpsZXQncyBldmFsdWF0ZSBvdXIgYXNzdW1wdGlvbnMgZm9yIHRoZSBncm91cHMgd2UgYXJlIGNvbXBhcmluZywgc3RhcnRpbmcgd2l0aCBub3JtYWxpdHkgdXNpbmcgUS1RIHBsb3RzLiAgRmlyc3QgbGV0J3MgbWFrZSBgYWdlX2dyb3VwX25hbWVgIGEgZmFjdG9yOg0KDQpgYGB7cn0NCmFsbF9hZ2VfZGlldF9hbmRfZ3VpZGVsaW5lcyAlPD4lDQogIG11dGF0ZV9hdCh2YXJzKGFnZV9ncm91cF9uYW1lKSwgZmFjdG9yKQ0KYGBgDQoNCk5vdyBsZXQncyBsb29rIGF0IFEtUSBwbG90cyBvZiBib3RoIHJlbGF0aXZlIHBlcmNlbnQgY29uc3VtcHRpb24gYW5kIHRoZSBsb2ctdHJhbnNmb3JtZWQgdmVyc2lvbiBvZiB0aGlzIHZhcmlhYmxlOg0KYGBge3J9DQphbGxfYWdlX2RpZXRfYW5kX2d1aWRlbGluZXMgJT4lDQogIGZpbHRlcihmb29kID09ICJyZWQgbWVhdCIpICU+JQ0KICBnZ3Bsb3QoYWVzKHNhbXBsZSA9IFJlbGF0aXZlX1BlcmNlbnQpKSArDQogIGZhY2V0X3dyYXAofmFnZV9ncm91cF9uYW1lKSArDQogIGdlb21fcXEoKSArDQogIGdlb21fcXFfbGluZSgpDQoNCmFsbF9hZ2VfZGlldF9hbmRfZ3VpZGVsaW5lcyAlPiUNCiAgZmlsdGVyKGZvb2QgPT0gInJlZCBtZWF0IikgJT4lDQogIGdncGxvdChhZXMoc2FtcGxlID0gbG9nMTAoUmVsYXRpdmVfUGVyY2VudCkpKSArDQogIGZhY2V0X3dyYXAofmFnZV9ncm91cF9uYW1lKSArDQogIGdlb21fcXEoKSArDQogIGdlb21fcXFfbGluZSgpDQpgYGANCg0KQWZ0ZXIgdHJhbnNmb3JtYXRpb24sIHRoZXNlIFEtUSBwbG90cyBsb29rIHByZXR0eSBnb29kLg0KDQpOb3cgbGV0J3MgbG9vayBhdCB0aGUgYXNzdW1wdGlvbiBvZiBjb25zdGFudCB2YXJpYW5jZS4gVGhlcmUgYXJlIGRpZmZlcmVudCB3YXlzIHRvIGFzc2VzcyB0aGlzIGFzc3VtcHRpb24gYWNyb3NzIG1vcmUgdGhhbiB0d28gZ3JvdXBzLiAgW0JhcnRsZXR0J3MgdGVzdF0oaHR0cHM6Ly93d3cuaXRsLm5pc3QuZ292L2Rpdjg5OC9oYW5kYm9vay9lZGEvc2VjdGlvbjMvZWRhMzU3Lmh0bSl7dGFyZ2V0PSJfYmxhbmsifSB3b3JrcyB3ZWxsIGlmIHRoZSBkYXRhIGFwcGVhcnMgdG8gYmUgcXVpdGUgbm9ybWFsbHkgZGlzdHJpYnV0ZWQsIHdoaWxlIHRoZSBbRmxpZ25lci1LaWxsZWVuXShodHRwOi8vd2lraS5zdGF0LnVjbGEuZWR1L3NvY3IvaW5kZXgucGhwL0FQX1N0YXRpc3RpY3NfQ3VycmljdWx1bV8yMDA3X05vblBhcmFtX1ZhckluZGVwKXt0YXJnZXQ9Il9ibGFuayJ9IHRlc3QgaXMgbm9ucGFyYW1ldHJpYyBhbmQgZG9lcyBub3QgYXNzdW1lIG5vcm1hbGl0eSBvZiB0aGUgZGF0YS4NCg0KV2Ugd2lsbCB1c2UgYW5vdGhlciBwb3B1bGFyIHRlc3QsIFtMZXZlbmUncyB0ZXN0XShodHRwczovL3d3dy5pdGwubmlzdC5nb3YvZGl2ODk4L2hhbmRib29rL2VkYS9zZWN0aW9uMy9lZGEzNWEuaHRtKXt0YXJnZXQ9Il9ibGFuayJ9LCB3aGljaCBpcyBtb3JlIHJvYnVzdCB0byB2aW9sYXRpb25zIG9mIG5vcm1hbGl0eSB0aGFuIHRoZSBCYXJ0bGV0dCdzIHRlc3QsIGJ1dCBub3QgYXMgcm9idXN0IGFzIHRoZSBGbGlnbmVyLUtpbGxlZW4gdGVzdC4gIFRoZSBudWxsIGh5cG90aGVzaXMgb2YgdGhpcyB0ZXN0LCBhcyBmb3IgdGhlIG90aGVyIHR3byB0ZXN0cywgaXMgdGhhdCB0aGUgdmFyaWFuY2VzIGFyZSBlcXVhbCBhY3Jvc3MgYWxsIG9mIHRoZSBncm91cHMuICBUaGUgYWx0ZXJuYXRpdmUgaHlwb3RoZXNpcyBpcyB0aGF0IGF0IGxlYXN0IG9uZSBwYWlyIG9mIGdyb3VwcyBoYXMgZGlmZmVyZW50IHZhcmlhbmNlcy4gIEluIHN5bWJvbHMgd2Ugd291bGQgd3JpdGUgdGhpcyBhcw0KDQokJCBIXzA6IFxzaWdtYV8xXjIgPSBcc2lnbWFfMl4yID0gXHNpZ21hXzNeMiAuLi4gPSBcc2lnbWFfbl4yICQkDQoNCmFuZA0KDQokJEhfYTpcc2lnbWFfaV4yIFxuZXEgXHNpZ21hX2peMiAgICQkDQpmb3IgYXQgbGVhc3QgIG9uZSAgcGFpciAoJGkkLCRqJCkuDQoNCg0KV2Ugd2lsbCB1c2UgdGhlIGBsZXZlbmVUZXN0KClgIGZ1bmN0aW9uIG9mIHRoZSBgY2FyYCBwYWNrYWdlIHRvIHBlcmZvcm1zIExldmVuZSdzIHRlc3QuDQoNCmBgYHtyfQ0KYWxsX2FnZV9kaWV0X2FuZF9ndWlkZWxpbmVzICU+JQ0KICBmaWx0ZXIoZm9vZCA9PSAicmVkIG1lYXQiKSAlPiUNCiAgY2FyOjpsZXZlbmVUZXN0KGxvZzEwKFJlbGF0aXZlX1BlcmNlbnQpIH4gYWdlX2dyb3VwX25hbWUsIGRhdGEgPSAuKQ0KYGBgDQoNCk91ciBkYXRhIGRvZXMgbm90IGFwcGVhciB0byB2aW9sYXRlIHRoZSBob21vZ2VuZWl0eSBvZiB2YXJpYW5jZXMgYXNzdW1wdGlvbiBhcyBvdXIgJHAkLXZhbHVlIHdhcyBncmVhdGVyIHRoYW4gMC4wNSBhbmQgc28gd2Ugd291bGQgZmFpbCB0byByZWplY3QgdGhlIG51bGwgaHlwb3RoZXNpcyBvZiBlcXVhbCB2YXJpYW5jZXMuDQoNCldlIGFscmVhZHkga25vdyB0aGF0IG91ciBpbmRlcGVuZGVuY2UgYXNzdW1wdGlvbiBpcyBub3QgbWV0LCBzaW5jZSB0aGUgZGF0YSBmb3IgdGhlIGRpZmZlcmVudCBhZ2UgZ3JvdXBzIGNvbWVzIGZyb20gdGhlIHNhbWUgY291bnRyaWVzLiAgV2Ugd2lsbCBhY2NvdW50IGZvciB0aGlzIGluIGxhdGVyIG1vZGVscywgYnV0IGZpcnN0IGxldCdzIGNvbXBhcmUgdGhlIHJlc3VsdHMgYmV0d2VlbiBBTk9WQSBhbmQgbGluZWFyIHJlZ3Jlc3Npb24gYXNzdW1pbmcgdGhlIGluZGVwZW5kZW5jZSBhc3N1bXB0aW9uIGlzIG1ldC4NCg0KIyMjIyBBTk9WQSBhbmQgbGluZWFyIHJlZ3Jlc3Npb24NCg0KV2UgY2FuIHVzZSB0aGUgYGFvdigpYCBmdW5jdGlvbiBvZiB0aGUgYHN0YXRzYCBwYWNrYWdlIHRvIHBlcmZvcm0gYW4gQU5PVkEgdGVzdC4gV2Ugd2lsbCBiZSBwZXJmb3JtaW5nIHdoYXQgaXMgY2FsbGVkIGEgW29uZS13YXkgQU5PVkFdKGh0dHA6Ly9vbmxpbmVzdGF0Ym9vay5jb20vMi9hbmFseXNpc19vZl92YXJpYW5jZS9vbmUtd2F5Lmh0bWwpe3RhcmdldD0iX2JsYW5rIn0gYmVjYXVzZSB3ZSBvbmx5IGhhdmUgb25lIGluZGVwZW5kZW50IHZhcmlhYmxlIChhZ2UgZ3JvdXApLiBXZSB3aWxsIGFsc28gcGVyZm9ybSBhIGxpbmVhciByZWdyZXNzaW9uIGZvciBjb21wYXJpc29uLg0KDQojIyMjIHsuc2Nyb2xsYWJsZSB9DQpgYGB7cn0NCmFsbF9hZ2VfZGlldF9hbmRfZ3VpZGVsaW5lcyAlPiUNCiAgZmlsdGVyKGZvb2QgPT0gInJlZCBtZWF0IikgJT4lDQogIGFvdihsb2cxMChSZWxhdGl2ZV9QZXJjZW50KSB+IGFnZV9ncm91cF9uYW1lLCBkYXRhID0gLikgJT4lDQogIHN1bW1hcnkoKQ0KDQphbGxfYWdlX2RpZXRfYW5kX2d1aWRlbGluZXMgJT4lDQogIGZpbHRlcihmb29kID09ICJyZWQgbWVhdCIpICU+JQ0KICBsbShsb2cxMChSZWxhdGl2ZV9QZXJjZW50KSB+IGFnZV9ncm91cF9uYW1lLCBkYXRhID0gLikgJT4lDQogIHN1bW1hcnkoKQ0KYGBgDQojIyMjDQoNCg0KV2UgY2FuIHNlZSB0aGF0IHRoZSAkRiQtc3RhdGlzdGljICgkRiQgdmFsdWUgaW4gdGhlIGBhb3YoKWAgb3V0cHV0IGFuZCBhdCB0aGUgYm90dG9tIG9mIHRoZSBgbG0oKWAgb3V0cHV0KSBpcyB0aGUgc2FtZSBmb3IgYm90aCBtb2RlbHMgYW5kIHRoZSAkcCQtdmFsdWUgZm9yIHRoZSAkRiQtc3RhdGlzdGljIGlzIHRoZSBzYW1lIQ0KDQpXZSBhbHNvIHNlZSB0aGF0IHRoZSBkZWdyZWVzIG9mIGZyZWVkb20gZm9yIHRoZSAkRiQtc3RhdGlzdGljIGlzIDE0LiBUaGlzIG1ha2VzIHNlbnNlIGJlY2F1c2Ugd2UgaGF2ZSAxNSBkaWZmZXJlbnQgYWdlIGdyb3VwcyBhbmQgZGVncmVlcyBvZiBmcmVlZG9tIGZvciB0aGUgJEYkLXN0YXRpc3RpYyBhcmUgY2FsY3VsYXRlZCBhcyAkZGYgPSBuIC0gMSQuIFNvIGluIG91ciBjYXNlOiAkZGYgPSAxNSAtMSQuDQoNCg0KVGhlIGRpZmZlcmVuY2UgaGVyZSBpcyB0aGF0IHdpdGggdGhlIGBsbSgpYCBtb2RlbCB3ZSBhbHNvIGdldCBpbmZvcm1hdGlvbiBhYm91dCBob3cgdGhlIGluZGl2aWR1YWwgYWdlIGdyb3VwcyBhcmUgYXNzb2NpYXRlZCB3aXRoIHRoZSBsb2cgcmVsYXRpdmUgcGVyY2VudCBjb25zdW1wdGlvbiBvZiByZWQgbWVhdC4gTm90aWNlIHRoYXQgaWYgd2UgbG9vayBhdCBhbGwgdGhlIGFnZSBncm91cHMgaW4gdGhlIGRhdGENCg0KYGBge3J9DQphbGxfYWdlX2RpZXRfYW5kX2d1aWRlbGluZXMgJT4lDQogIGRpc3RpbmN0KGFnZV9ncm91cF9uYW1lKQ0KYGBgDQoNCndlIHNlZSB0aGF0IG91ciBgbG0oKWAgcmVzdWx0cyBhcmUgbWlzc2luZyBvbmUgb2YgdGhlIGFnZSBncm91cHMsIHRoZSBgMjUgdG8gMjlgIGFnZSBncm91cC4gVGhhdCBpcyBiZWNhdXNlIHRoaXMgaXMgdGhlICoqcmVmZXJlbmNlIGdyb3VwKiogYW5kIHRoZSBjb2VmZmljaWVudHMgaW5kaWNhdGUgdGhlIHNsb3BlIG9yIGRpZmZlcmVuY2UgaW4gbG9nIHJlbGF0aXZlIHBlcmNlbnQgY29uc3VtcHRpb24gcmF0ZXMgZm9yIGVhY2ggbGlzdGVkIGFnZSBncm91cCAqY29tcGFyZWQqIHRvIHRoaXMgcmVmZXJlbmNlIGdyb3VwLiANCg0KIyMjIyBBTk9WQSBhbmQgbGluZWFyIHJlZ3Jlc3Npb24gd2l0aCBmaXhlZCBlZmZlY3RzDQoNCk5vdyBsZXQncyBhY2NvdW50IGZvciB0aGUgcGFpcmVkIGBsb2NhdGlvbl9uYW1lYCBzdHJ1Y3R1cmUgd2l0aGluIG91ciBkYXRhLCBzaW5jZSB0aGUgYWJvdmUgbW9kZWxzIHZpb2xhdGUgdGhlIGluZGVwZW5kZW5jZSBhc3N1bXB0aW9ucyBmb3IgQU5PVkEgYW5kIGxpbmVhciByZWdyZXNzaW9uLiBXZSBjYW4gZG8gdGhpcyBieSBhZGRpbmcgYW5vdGhlciBmaXhlZCBlZmZlY3QgdG8gYm90aCB0aGUgQU5PVkEgbW9kZWwgYW5kIHRoZSBsaW5lYXIgcmVncmVzc2lvbiBtb2RlbC4gIEZvciBBTk9WQSwgdGhpcyBtZWFucyB3ZSBhcmUgbm93IGRvaW5nIGEgdHdvLXdheSBBTk9WQSwgc2luY2Ugd2UgaGF2ZSB0d28gaW5kZXBlbmRlbnQgdmFyaWFibGVzIChhZ2UgZ3JvdXAgYW5kIGNvdW50cnkpLiAgRm9yIGxpbmVhciByZWdyZXNzaW9uLCB3ZSBhcmUgbm93IGFkZGluZyBhIGZpeGVkIGVmZmVjdCBmb3IgY291bnRyeSB0byBvdXIgbW9kZWwuICANCg0KIyMjIyB7LnNjcm9sbGFibGUgfQ0KDQpgYGB7cn0NCmFsbF9hZ2VfZGlldF9hbmRfZ3VpZGVsaW5lcyAlPiUNCiAgZmlsdGVyKGZvb2QgPT0gInJlZCBtZWF0IikgJT4lDQogIGFvdihsb2cxMChSZWxhdGl2ZV9QZXJjZW50KSB+IGFnZV9ncm91cF9uYW1lICsgbG9jYXRpb25fbmFtZSwgZGF0YSA9IC4pICU+JQ0KICBzdW1tYXJ5KCkNCg0KYWxsX2FnZV9kaWV0X2FuZF9ndWlkZWxpbmVzICU+JQ0KICBmaWx0ZXIoZm9vZCA9PSAicmVkIG1lYXQiKSAlPiUNCiAgbG0obG9nMTAoUmVsYXRpdmVfUGVyY2VudCkgfiBhZ2VfZ3JvdXBfbmFtZSArIGxvY2F0aW9uX25hbWUsIGRhdGEgPSAuKSAlPiUNCiAgc3VtbWFyeSgpDQpgYGANCiMjIyMgDQoNCkl0J3MgaGFyZCB0byBzZWUgdGhhdCB0aGVzZSByZXN1bHRzIG1hdGNoLCBzaW5jZSB0aGUgbGluZWFyIHJlZ3Jlc3Npb24gb3V0cHV0IGRvZXNuJ3QgcHJpbnQgdGhlICRGJC1zdGF0aXN0aWMgZm9yIHRoZSBhZ2UgZ3JvdXBzIHRvZ2V0aGVyIG9yIHRoZSBjb3VudHJpZXMgdG9nZXRoZXI7IGl0IG9ubHkgZ2l2ZXMgcmVzdWx0cyBmb3IgaW5kaXZpZHVhbCAkdCQtdGVzdHMgb2YgZWFjaCByZWdyZXNzaW9uIGNvZWZmaWNpZW50LiAgV2UgY2FuIGdldCB0aGVzZSBncm91cGVkICRGJC1zdGF0aXN0aWNzIHVzaW5nIHRoZSBgYW5vdmEoKWAgZnVuY3Rpb24gb2YgdGhlIGBzdGF0c2AgcGFja2FnZS4gVGhpcyBmdW5jdGlvbiBkb2VzIG5vdCBhY3R1YWxseSBkaXJlY3RseSBwZXJmb3JtIEFOT1ZBIGxpa2UgdGhlIGBhb3YoKWAgZnVuY3Rpb24sIGJ1dCBpbnN0ZWFkIHByaW50cyBhIHZhcmlhbmNlIHRhYmxlIHVzaW5nIGEgYGxtKClgIG9iamVjdC4NCg0KYGBge3J9DQphbGxfYWdlX2RpZXRfYW5kX2d1aWRlbGluZXMgJT4lDQogIGZpbHRlcihmb29kID09ICJyZWQgbWVhdCIpICU+JQ0KICBsbShsb2cxMChSZWxhdGl2ZV9QZXJjZW50KSB+IGFnZV9ncm91cF9uYW1lICsgbG9jYXRpb25fbmFtZSwgZGF0YSA9IC4pICU+JQ0KICBhbm92YSgpDQpgYGANCiMjIyMgDQoNCldlIGNhbiBzZWUgdGhhdCBpbmRlZWQgdGhlICRGJC12YWx1ZXMgYW5kICRwJC12YWx1ZXMgZnJvbSBsaW5lYXIgcmVncmVzc2lvbiBtYXRjaCB0aG9zZSBmcm9tIEFOT1ZBLiBJbiB0aGlzIGNhc2UsIHRoaXMgYW5hbHlzaXMgc3VnZ2VzdHMgdGhhdCB0aGVyZSBpcyBhIHNpZ25pZmljYW50IHJlbGF0aW9uc2hpcCBiZXR3ZWVuIGFnZSBncm91cCBhbmQgY29uc3VtcHRpb24sIGV2ZW4gd2hlbiBjb250cm9sbGluZyBmb3IgY291bnRyeS4gIEl0IGFsc28gc3VnZ2VzdHMgdGhhdCB0aGVyZSBpcyBhIHNpZ25pZmljYW50IHJlbGF0aW9uc2hpcCBiZXR3ZWVuIGNvdW50cnkgYW5kIGNvbnN1bXB0aW9uLCBldmVuIHdoZW4gY29udHJvbGxpbmcgZm9yIGFnZSBncm91cC4gIEhvd2V2ZXIsIG9ubHkgdGhlIGZpcnN0IHJlbGF0aW9uc2hpcCBpcyBvdXIgcmVsYXRpb25zaGlwIG9mIGludGVyZXN0OyB0aGUgc2Vjb25kIGlzIG9ubHkgaW5jbHVkZWQgaW4gdGhlIG1vZGVsIHRvIGFjY291bnQgZm9yIHRoZSBkZXBlbmRlbnQgbmF0dXJlIG9mIHRoZSBkYXRhLg0KDQpSZW1lbWJlciwgdGhlIEFOT1ZBIHJlc3VsdHMgaW5kaWNhdGUgdGhhdCB0aGUgbWVhbnMgYXJlIGRpZmZlcmVudCBhY3Jvc3MgdGhlc2UgZ3JvdXBzLCBidXQgaXQgKipkb2VzIG5vdCoqIGluZm9ybSB1cyBhYm91dCB3aGljaCBncm91cHMgYXJlIGRpZmZlcmVudC4gSG93ZXZlciwgdGhlIG9yaWdpbmFsIGBsbSgpYCBvdXRwdXQgdXNpbmcgdGhlIGBzdW1tYXJ5KCl+IGNvbW1hbmQgZ2l2ZXMgbW9yZSBpbmZvcm1hdGlvbiBhYm91dCBzcGVjaWZpYyBncm91cCBkaWZmZXJlbmNlcy4gUmVtZW1iZXIsIHRob3VnaCwgdGhhdCB0aGVzZSBhcmUgKipyZWxhdGl2ZSB0byB0aGUgcmVmZXJlbmNlKiogbGV2ZWwgZm9yIHRoZSBhZ2UgZ3JvdXAgYW5kIGxvY2F0aW9uIGFuZCB0aGF0IHRoZXNlIHZhbHVlcyBhcmUgY2FsY3VsYXRlZCBmb3IgdGhlIGVmZmVjdCBvbiBjb25zdW1wdGlvbiB3aGlsZSBjb250cm9sbGluZyBmb3IgdGhlIG90aGVyIHZhcmlhYmxlIGluIHRoZSBtb2RlbC4NCg0KIyMjIyB7LnNjcm9sbGFibGUgfQ0KYGBge3J9DQphbGxfYWdlX2RpZXRfYW5kX2d1aWRlbGluZXMgJT4lDQogIGZpbHRlcihmb29kID09ICJyZWQgbWVhdCIpICU+JQ0KICBsbShsb2cxMChSZWxhdGl2ZV9QZXJjZW50KSB+IGFnZV9ncm91cF9uYW1lICsgbG9jYXRpb25fbmFtZSwgZGF0YSA9IC4pICU+JQ0KICBzdW1tYXJ5KCkNCmBgYA0KIyMjIw0KDQojIyMjIEFOT1ZBIGFuZCBsaW5lYXIgcmVncmVzc2lvbiB3aXRoIG1peGVkIGVmZmVjdHMNCg0KV2UgY291bGQgaW5zdGVhZCBwZXJmb3JtIGEgc2ltaWxhciBhbmFseXNpcyBhcyB3ZSBkaWQgZm9yIHRoZSB0d28gZ3JvdXAgYW5hbHlzaXMgd2hlcmUgd2UgY29udHJvbGxlZCBmb3IgdGhlIHBhaXJlZCBkYXRhIHN0cnVjdHVyZSB1c2luZyBhIHJhbmRvbSBlZmZlY3QgYmFzZWQgb24gY291bnRyeSBJbiBwYXJ0aWN1bGFyLCB3ZSBjb3VsZCBpbmNsdWRlIGEgcmFuZG9tIGludGVyY2VwdCBmb3IgY291bnRyeS4gIFdlIGNvdWxkIGRvIHRoaXMgd2l0aGluIHRoZSBgYW92KClgIGZ1bmN0aW9uIHVzaW5nIGBFcnJvcigpYCBhbmQgd2l0aGluIHRoZSBgbG1lcigpYCBmdW5jdGlvbiB3aXRoIGAxIHwgdmFyaWFibGVfbmFtZWAuDQoNCiMjIyMgey5zY3JvbGxhYmxlIH0NCmBgYHtyfQ0KYWxsX2FnZV9kaWV0X2FuZF9ndWlkZWxpbmVzICU+JQ0KICBmaWx0ZXIoZm9vZCA9PSAicmVkIG1lYXQiKSAlPiUNCiAgYW92KA0KICAgIGxvZzEwKFJlbGF0aXZlX1BlcmNlbnQpIH4gYWdlX2dyb3VwX25hbWUgKyBFcnJvcihsb2NhdGlvbl9uYW1lKSwNCiAgICBkYXRhID0gLg0KICApICU+JQ0KICBzdW1tYXJ5KCkNCg0KYWxsX2FnZV9kaWV0X2FuZF9ndWlkZWxpbmVzICU+JQ0KICBmaWx0ZXIoZm9vZCA9PSAicmVkIG1lYXQiKSAlPiUNCiAgbG1lcigNCiAgICBsb2cxMChSZWxhdGl2ZV9QZXJjZW50KSB+IGFnZV9ncm91cF9uYW1lICsgKDEgfCBsb2NhdGlvbl9uYW1lKSwNCiAgICBkYXRhID0gLg0KICApICU+JQ0KICBzdW1tYXJ5KCkNCmBgYA0KDQpOb3RpY2Ugbm93IHRoZSByZXN1bHRzIG9ubHkgc2hvdyBmb3IgdGhlIGFnZSBncm91cCB2YXJpYWJsZSwgc2luY2UgdGhpcyBpcyB0aGUgb25seSBmaXhlZCBlZmZlY3QgaW4gdGhlIG1vZGVsLiAgSG93ZXZlciwgZGVwZW5kZW5jZSBpbiB0aGUgZGF0YSBkdWUgdG8gY291bnRyeSBpcyBzdGlsbCBhY2NvdW50ZWQgZm9yIHRocm91Z2ggdGhlIHJhbmRvbSBlZmZlY3QuDQoNCklmIHdlIHVzZSBgYW5vdmEoKWAgaW5zdGVhZCBvZiBgc3VtbWFyeSgpYCBmb3Igb3VyIGBsbWVyKClgIG1vZGVsLCB3ZSBjYW4gc2VlIHRoZXkgZ2l2ZSB0aGUgc2FtZSByZXN1bHRzLg0KYGBge3J9DQphbGxfYWdlX2RpZXRfYW5kX2d1aWRlbGluZXMgJT4lDQogIGZpbHRlcihmb29kID09ICJyZWQgbWVhdCIpICU+JQ0KICBhb3YoDQogICAgbG9nMTAoUmVsYXRpdmVfUGVyY2VudCkgfiBhZ2VfZ3JvdXBfbmFtZSArIEVycm9yKGxvY2F0aW9uX25hbWUpLA0KICAgIGRhdGEgPSAuDQogICkgJT4lDQogIHN1bW1hcnkoKQ0KDQphbGxfYWdlX2RpZXRfYW5kX2d1aWRlbGluZXMgJT4lDQogIGZpbHRlcihmb29kID09ICJyZWQgbWVhdCIpICU+JQ0KICBsbWVyKA0KICAgIGxvZzEwKFJlbGF0aXZlX1BlcmNlbnQpIH4gYWdlX2dyb3VwX25hbWUgKyAoMSB8IGxvY2F0aW9uX25hbWUpLA0KICAgIGRhdGEgPSAuDQogICkgJT4lDQogIGFub3ZhKCkNCmBgYA0KDQoNCiMjIyAqKk1vZGVsaW5nIGFsbCBncm91cHMgb2YgaW50ZXJlc3QqKg0KKioqDQoNCk5vdyB3ZSBjYW4gZXh0ZW5kIG91dCBtb2RlbCB0byBpbmNsdWRlIGluY2x1ZGUgYHNleGAsIGBhZ2VfZ3JvdXBfbmFtZWAgYW5kIGBsb2NhdGlvbl9uYW1lYCBhbGwgaW4gdGhlIHNhbWUgbGluZWFyIG1vZGVsIGFuZCBnZXQgaW5mb3JtYXRpb24gYWJvdXQgaG93IGVhY2ggb2YgdGhlc2UgZmFjdG9ycyBpbmZsdWVuY2VzIGRpZXRhcnkgY29uc3VtcHRpb24sIHdoaWxlIGFjY291bnRpbmcgZm9yIHRoZSBvdGhlciBmYWN0b3JzLiBTaW5jZSB3ZSBhcmUgcHJpbWFyaWx5IGludGVyZXN0ZWQgaW4gdGhlIGVmZmVjdHMgb2Ygc2V4IGFuZCBhZ2UsIGJ1dCB3YW50IHRvIGFjY291bnQgZm9yIHRoZSBkZXBlbmRlbmNlIGluIHRoZSBkYXRhIGR1ZSB0byByZXBlYXRlZCBtZWFzdXJlbWVudHMgYnkgY291bnRyeSwgd2Ugd2lsbCBpbmNsdWRlIGBzZXhgIGFuZCBgYWdlX2dyb3VwX25hbWVgIGFzIGZpeGVkIGVmZmVjdHMgYW5kIGluY29ycG9yYXRlIGEgcmFuZG9tIGludGVyY2VwdCBmb3IgYGxvY2F0aW9uX25hbWVgLiAgDQoNCmBgYHtyfQ0KYWxsX2FnZV9kaWV0X2FuZF9ndWlkZWxpbmVzICU+JQ0KICBmaWx0ZXIoZm9vZCA9PSAicmVkIG1lYXQiKSAlPiUNCiAgbG1lcigNCiAgICBsb2cxMChSZWxhdGl2ZV9QZXJjZW50KSB+IHNleCArIGFnZV9ncm91cF9uYW1lICsgKDEgfCBsb2NhdGlvbl9uYW1lKSwNCiAgICBkYXRhID0gLg0KICApICU+JQ0KICBhbm92YSgpDQoNCmFsbF9hZ2VfZGlldF9hbmRfZ3VpZGVsaW5lcyAlPiUNCiAgZmlsdGVyKGZvb2QgPT0gInJlZCBtZWF0IikgJT4lDQogIGxtZXIoDQogICAgbG9nMTAoUmVsYXRpdmVfUGVyY2VudCkgfiBzZXggKyBhZ2VfZ3JvdXBfbmFtZSArICgxIHwgbG9jYXRpb25fbmFtZSksDQogICAgZGF0YSA9IC4NCiAgKSAlPiUNCiAgc3VtbWFyeSgpDQpgYGANCg0KTG9va2luZyBhdCB0aGUgYGFub3ZhKClgIG91dHB1dCwgd2UgY2FuIHNlZSB0aGF0IHNleCBhbmQgYWdlIGdyb3VwIGJvdGggaGF2ZSBzaWduaWZpY2FudCBhc3NvY2lhdGlvbnMgd2l0aCB0aGUgY29uc3VtcHRpb24gb2YgcmVkIG1lYXQsIHdoZW4gY29udHJvbGxpbmcgZm9yIHRoZSBvdGhlciB2YXJpYWJsZS4gIEFkZGl0aW9uYWxseSwgYnkgbG9va2luZyBhdCB0aGUgaW5kaXZpZHVhbCBjb2VmZmljaWVudCBlc3RpbWF0ZXMgaW4gdGhlIGBzdW1tYXJ5KClgIG91dHB1dCwgd2Ugc2VlIHRoYXQgbWFsZXMgdGVuZCB0byBoYXZlIGhpZ2hlciByZWQgbWVhdCBjb25zdW1wdGlvbiBjb21wYXJlZCB0byBmZW1hbGVzIChwb3NpdGl2ZSBjb2VmZmljaWVudCBmb3IgYHNleE1hbGVgKSBhbmQgdGhhdCBjb25zdW1wdGlvbiBzZWVtcyB0byBkZWNyZWFzZSB3aXRoIGluY3JlYXNpbmcgYWdlIChuZWdhdGl2ZSBjb2VmZmljaWVudHMgZm9yIGFsbCB0aGUgYWdlIGdyb3VwIGNhdGVnb3JpZXMgdGhhdCBhcHBlYXIgdG8gYmVjb21lIGxhcmdlciBpbiBtYWduaXR1ZGUgd2l0aCBpbmNyZWFzaW5nIGFnZSkuDQoNCiMjICoqRGF0YSBWaXN1YWxpemF0aW9uKioNCioqKg0KDQpJZiB5b3UgaGF2ZSBiZWVuIGZvbGxvd2luZyBhbG9uZyBidXQgc3RvcHBlZCB5b3UgY291bGQgbG9hZCB0aGUgd3JhbmdsZWQgZGF0YSBsaWtlIHNvOg0KDQpgYGB7cn0NCmxvYWQoaGVyZTo6aGVyZSgiZGF0YSIsICJ3cmFuZ2xlZCIsICJ3cmFuZ2xlZF9kYXRhLnJkYSIpKQ0KYGBgDQoNCioqKg0KPGRldGFpbHM+IDxzdW1tYXJ5PiBJZiB5b3UgYXJlIHN0YXJ0aW5nIHRoZSBjYXNlIHN0dWR5IGF0IHRoaXMgc2VjdGlvbiBjbGljayBoZXJlLiA8L3N1bW1hcnk+DQoNCkZpcnN0IHlvdSBuZWVkIHRvIGluc3RhbGwgYW5kIGxvYWQgdGhlIGBPQ1NkYXRhYCBwYWNrYWdlOg0KDQpgYGB7ciwgZXZhbD1GQUxTRX0NCmluc3RhbGwucGFja2FnZXMoIk9DU2RhdGEiKQ0KbGlicmFyeShPQ1NkYXRhKQ0KYGBgDQoNClRoZW4sIHlvdSBtYXkgbG9hZCB0aGUgd3JhbmdsZWQgZGF0YSB1c2luZyB0aGUgZm9sbG93aW5nIGNvZGU6DQoNCmBgYHtyLCBldmFsPUZBTFNFfQ0Kd3JhbmdsZWRfcmRhKCJvY3MtYnAtZGlldCIsIG91dHBhdGggPSBnZXR3ZCgpKQ0KbG9hZChoZXJlOjpoZXJlKCJPQ1NkYXRhIiwgImRhdGEiLCAid3JhbmdsZWQiLCAid3JhbmdsZWRfZGF0YS5yZGEiKSkNCmBgYA0KDQpJZiB0aGUgcGFja2FnZSBkb2VzIG5vdCB3b3JrIGZvciB5b3UsIGFsdGVybmF0aXZlbHksIGFuIFJEQSBmaWxlIChzdGFuZHMgZm9yIFIgZGF0YSkgb2YgdGhlIGRhdGEgY2FuIGJlIGZvdW5kIFtoZXJlXShodHRwczovL2dpdGh1Yi5jb20vL29wZW5jYXNlc3R1ZGllcy9vY3MtYnAtZGlldC90cmVlL21hc3Rlci9kYXRhL3dyYW5nbGVkKSBvciBzbGlnaHRseSBtb3JlIGRpcmVjdGx5IFtoZXJlXShodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vb3BlbmNhc2VzdHVkaWVzL29jcy1icC1kaWV0L21hc3Rlci9kYXRhL3dyYW5nbGVkL3dyYW5nbGVkX2RhdGEucmRhKS4gRG93bmxvYWQgdGhpcyBmaWxlIGFuZCB0aGVuIHBsYWNlIGl0IGluIHlvdXIgY3VycmVudCB3b3JraW5nIGRpcmVjdG9yeSB3aXRoaW4gYSBzdWJkaXJlY3RvcnkgY2FsbGVkICJ3cmFuZ2xlZCIgd2l0aGluIGEgc3ViZGlyZWN0b3J5IGNhbGxlZCAiZGF0YSIgdG8gY29weSBhbmQgcGFzdGUgb3VyIGNvZGUuIFdlIHVzZWQgYW4gUlN0dWRpbyBwcm9qZWN0IGFuZCB0aGUgW2BoZXJlYCBwYWNrYWdlXShodHRwczovL2dpdGh1Yi5jb20vamVubnliYy9oZXJlX2hlcmUpIHRvIG5hdmlnYXRlIHRvIHRoZSBmaWxlIG1vcmUgZWFzaWx5LiANCg0KYGBge3J9DQpsb2FkKGhlcmU6OmhlcmUoImRhdGEiLCAid3JhbmdsZWQiLCAid3JhbmdsZWRfZGF0YS5yZGEiKSkNCmBgYA0KDQoNCioqKg0KPGRldGFpbHM+IDxzdW1tYXJ5PiBDbGljayBoZXJlIHRvIHNlZSBtb3JlIGFib3V0IGNyZWF0aW5nIG5ldyBwcm9qZWN0cyBpbiBSU3R1ZGlvLiA8L3N1bW1hcnk+DQoNCllvdSBjYW4gY3JlYXRlIGEgcHJvamVjdCBieSBnb2luZyB0byB0aGUgRmlsZSBtZW51IG9mIFJTdHVkaW8gbGlrZSBzbzoNCg0KDQpgYGB7ciwgZWNobyA9IEZBTFNFLCBvdXQud2lkdGg9IjYwJSJ9DQprbml0cjo6aW5jbHVkZV9ncmFwaGljcyhoZXJlOjpoZXJlKCJpbWciLCAiTmV3X3Byb2plY3QucG5nIikpDQpgYGANCg0KWW91IGNhbiBhbHNvIGRvIHNvIGJ5IGNsaWNraW5nIHRoZSBwcm9qZWN0IGJ1dHRvbjoNCg0KYGBge3IsIGVjaG8gPSBGQUxTRSwgb3V0LndpZHRoPSI2MCUifQ0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoaGVyZTo6aGVyZSgiaW1nIiwgInByb2plY3RfYnV0dG9uLnBuZyIpKQ0KYGBgDQoNClNlZSBbaGVyZV0oaHR0cHM6Ly9zdXBwb3J0LnJzdHVkaW8uY29tL2hjL2VuLXVzL2FydGljbGVzLzIwMDUyNjIwNy1Vc2luZy1Qcm9qZWN0cykgdG8gbGVhcm4gbW9yZSBhYm91dCB1c2luZyBSU3R1ZGlvIHByb2plY3RzIGFuZCBbaGVyZV0oaHR0cHM6Ly9naXRodWIuY29tL2plbm55YmMvaGVyZV9oZXJlKSB0byBsZWFybiBtb3JlIGFib3V0IHRoZSBgaGVyZWAgcGFja2FnZS4NCg0KPC9kZXRhaWxzPg0KKioqDQo8L2RldGFpbHM+DQoqKioNCg0KTm93IHRoYXQgd2UgaGF2ZSBzdGF0aXN0aWNhbGx5IGFuYWx5emVkIHRoZSBjb25zdW1wdGlvbiBvZiByZWQgbWVhdCBiYXNlZCBvbiB0aGUgbG9jYXRpb24sIHNleCwgYW5kIGFnZSBncm91cCBvZiBkaWZmZXJlbnQgcG9wdWxhdGlvbnMgYXJvdW5kIHRoZSB3b3JsZC4gTGV0J3MgbWFrZSBzb21lIHZpc3VhbGl6YXRpb25zIHRvIGhlbHAgd2l0aCBvdXIgaW50ZXJwcmV0YXRpb25zLg0KDQojIyMgKipSZWQgTWVhdCoqDQoqKioNCg0KTGV0J3MgdHJ5IHRvIG1ha2UgYSBwbG90IHRoYXQgc2hvd3MgdGhlIHJlbGF0aW9uc2hpcCBvZiBhZ2UgZ3JvdXAsIHNleCwgYW5kIGxvY2F0aW9uIG9uIGNvbnN1bXB0aW9uIG9mIHJlZCBtZWF0Lg0KDQpGaXJzdCB3ZSB3aWxsIGZpbHRlciBvdXIgZGF0YSBmb3Igb25seSB0aGUgZGF0YSBhc3NvY2lhdGVkIHdpdGggcmVkIG1lYXQsIGFuZCB0aGVuIHdlIHdpbGwgY3JlYXRlIGEgYm94IHBsb3QgZ3JhcGggd2l0aCBhZ2UgZ3JvdXAgYXMgdGhlIHggYXhpcywgYnV0IGluY2x1ZGUgYm94IHBsb3RzIGZvciBlYWNoIHNleCBmb3IgZWFjaCBhZ2UgZ3JvdXAuIFdlIGNhbiBpbmNsdWRlIGFuIGFkZGl0aW9uYWwgc3VicGxvdCB0byBqdXN0IGxvb2sgYXQgdGhlIHJlbGF0aW9uc2hpcCBvZiBzZXggYW5kIGNvbnN1bXB0aW9uLiBSZWNhbGwgdGhhdCB0aGUgYGdncGxvdDJgIHBhY2thZ2UgaXMgdmVyeSB1c2VmdWwgZm9yIG1ha2luZyBmaWd1cmVzIGFuZCB1c2VzIGEgbGF5ZXJpbmcgc3RydWN0dXJlIHRvIG1ha2UgcGxvdHMgdXNpbmcgdGhlIGArYCBiZXR3ZWVuIGxheWVycy4NCg0KYGBge3J9DQphbGxfYWdlX2RpZXRfYW5kX2d1aWRlbGluZXMgJT4lDQogIGZpbHRlcihmb29kID09ICJyZWQgbWVhdCIpICU+JQ0KICBnZ3Bsb3QoYWVzKA0KICAgIHggPSBhZ2VfZ3JvdXBfbmFtZSwNCiAgICB5ID0gUmVsYXRpdmVfUGVyY2VudCwNCiAgICBjb2wgPSBzZXgNCiAgKSkgKw0KICBnZW9tX2JveHBsb3QoKSArDQogICMgdGhpcyBhZGRzIHRoZSBpbmRpdmlkdWFsIHBvaW50cyBmb3IgdGhlIHNleCBjb21wYXJpc29uDQogIGdlb21faml0dGVyKGFlcygNCiAgICB4ID0gc2V4LA0KICAgIHkgPSBSZWxhdGl2ZV9QZXJjZW50DQogICksDQogICMgd2lkdGggc3BlY2lmaWVzIGhvdyB3aWRlIHRoZSBwb2ludHMgd2lsbCBiZSBwbG90dGVkDQogIHdpZHRoID0gLjIsDQogIHNpemUgPSAyLA0KICBzaGFwZSA9IDIxDQogICkgKw0KICAjIHRoaXMgYW5nbGVzIHRoZSB4IGF4aXMgdGV4dCBhbmQgcmVtb3ZlcyB0aGUgbGVnZW5kDQogIHRoZW1lKA0KICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KA0KICAgICAgYW5nbGUgPSA3MCwNCiAgICAgIGhqdXN0ID0gMQ0KICAgICksDQogICAgbGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiDQogICkNCmBgYA0KDQpPSywgdGhpcyBpcyBwcmV0dHkgZ29vZCwgYnV0IHdlIGNhbiBkbyBiZXR0ZXIuDQoNCkxldCdzIHRyeSBzcGVjaWZpY2FsbHkgbG9va2luZyBhdCB0aGUgY291bnRyaWVzIHRoYXQgb3Zlci1jb25zdW1lZCByZWQgbWVhdC4gV2UgY2FuIGxvb2sgYXQgdGhlc2UgY291bnRyaWVzIGJ5IGZpbHRlcmluZyBvdXIgZGF0YSB3aGVyZSBgUmVsYXRpdmVfUGVyY2VudGAgd2FzIGdyZWF0ZXIgdGhhbiAxMDAlLiBOb3cgd2Ugd2lsbCBvdmVybGFwIHRoZSBqaXR0ZXIgcG9pbnRzIGFuZCB0aGUgYm94IHBsb3QgdXNpbmcgdGhlIGBwb3NpdGlvbl9qaXR0ZXJkb2dlKClgIGFzIHRoZSBwb3NpdGlvbiBpbiBgZ2VvbV9wb250KClgLiBJbiBvcmRlciB0byBub3Qgb2JzY3VyZSBvdXIgYm94IHBsb3RzLCB3ZSBjYW4gdXNlIHRoZSBhcmd1bWVudCBgYWxwaGFgIHRvIG1ha2Ugb3VyIGppdHRlciBwb2ludHMgbW9yZSB0cmFuc3BhcmVudC4NCg0KYGBge3J9DQphbGxfYWdlX2RpZXRfYW5kX2d1aWRlbGluZXMgJT4lDQogIGZpbHRlcihmb29kID09ICJyZWQgbWVhdCIpICU+JQ0KICBmaWx0ZXIoUmVsYXRpdmVfUGVyY2VudCA+IDEwMCkgJT4lDQogIGdncGxvdChhZXMoDQogICAgeSA9IFJlbGF0aXZlX1BlcmNlbnQsDQogICAgeCA9IGFnZV9ncm91cF9uYW1lLA0KICAgIGZpbGwgPSBzZXgNCiAgKSkgKw0KICAjIHRoaXMgcG9zaXRpb24gb3B0aW9uIHdpbGwgc2VwYXJhdGUgdGhlIHBvaW50cyBieSBzZXgNCiAgIyB0aGlzIGlzIGRldGVybWluZWQgYnkgdGhlIGZpbGwgYXJndW1lbnQgaW4gdGhlIGdncGxvdCgpIGZ1bmN0aW9uDQogICMgY291bGQgYWxzbyB1c2UgY29sIGFyZ3VtZW50IGJ1dCBpdCB3b3VsZCBjaGFuZ2UgdGhlIHN0eWxlIGEgYml0DQogIGdlb21fcG9pbnQoDQogICAgcG9zaXRpb24gPSBwb3NpdGlvbl9qaXR0ZXJkb2RnZSgpLA0KICAgIGFlcyhjb2wgPSBzZXgpLA0KICAgIGFscGhhID0gMyAvIDEwDQogICkgKw0KICBnZW9tX2JveHBsb3Qob3V0bGllci5zaGFwZSA9IE5BKSArDQogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KA0KICAgIGFuZ2xlID0gNzAsDQogICAgaGp1c3QgPSAxDQogICkpDQpgYGANCg0KV2hhdCBhcmUgdGhlIGNvdW50cmllcyB0aGF0IGhhdmUgc3VjaCBoaWdoIGNvbnN1bXB0aW9uIHJhdGVzPw0KDQpgYGB7cn0NCmFsbF9hZ2VfZGlldF9hbmRfZ3VpZGVsaW5lcyAlPiUNCiAgZmlsdGVyKGZvb2QgPT0gInJlZCBtZWF0IikgJT4lDQogIGZpbHRlcihSZWxhdGl2ZV9QZXJjZW50ID4gMTAwMCkNCmBgYA0KIA0KIExvb2tzIGxpa2UgdGhlIG1hbGVzIGluIExhb3MgYW5kIFRpbW9yX0xlc3RlIGhhdmUgdGhlIGhpZ2hlc3QgY29uc3VtcHRpb24uDQoNCiANCk5vdyBsZXQncyBwbG90IGp1c3QgdGhlIHBvcHVsYXRpb25zIHRoYXQgZWF0IGxlc3MgdGhhbiB0aGUgb3B0aW1hbCBhbW91bnQgYnkgZmlsdGVyaW5nIGZvciBgUmVsYXRpdmVfUGVyY2VudGAgPCAxMDAlLg0KIA0KYGBge3J9DQphbGxfYWdlX2RpZXRfYW5kX2d1aWRlbGluZXMgJT4lDQogIGZpbHRlcihmb29kID09ICJyZWQgbWVhdCIpICU+JQ0KICBmaWx0ZXIoUmVsYXRpdmVfUGVyY2VudCA8IDEwMCkgJT4lDQogIGdncGxvdChhZXMoDQogICAgeSA9IFJlbGF0aXZlX1BlcmNlbnQsDQogICAgeCA9IGFnZV9ncm91cF9uYW1lLA0KICAgIGZpbGwgPSBzZXgNCiAgKSkgKw0KICBnZW9tX3BvaW50KA0KICAgIHBvc2l0aW9uID0gcG9zaXRpb25faml0dGVyZG9kZ2UoKSwNCiAgICBhZXMoY29sID0gc2V4KSwNCiAgICBhbHBoYSA9IDMgLyAxMA0KICApICsNCiAgZ2VvbV9ib3hwbG90KG91dGxpZXIuc2hhcGUgPSBOQSkgKw0KICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dCgNCiAgICBhbmdsZSA9IDcwLA0KICAgIGhqdXN0ID0gMQ0KICApKQ0KYGBgDQoNCk5pY2UhIEl0IHdvdWxkIGJlIG5pY2UgdG8gYmUgYWJsZSB0byBrbm93IHdoYXQgY291bnRyaWVzIGVhY2ggZGF0YSBwb2ludCBjb3JyZXNwb25kcyB0by4gT25lIHdheSB0byBkbyB0aGlzIGlzIHVzaW5nIGEgcGFja2FnZSBjYWxsZWQgYGdnaXJhcGhgLiBUaGlzIHBhY2thZ2UgaXMgcmVhbGx5IGhlbHBmdWwgZm9yIGNyZWF0aW5nIGludGVyYWN0aXZlIGdyYXBocy4gDQoNCldlIHdpbGwgdXNlIHRoZSBgZ2VvbV9wb2ludF9pbnRlcmFjdGl2ZSgpYCBmdW5jdGlvbiB0byBhbGxvdyB1cyB0byBob3ZlciBvdmVyIHBvaW50cyB0byBkaXNwbGF5IHRoZSBjb3VudHJ5IG5hbWUuIFdlIGluZGljYXRlIHdoYXQgbGFiZWwgd2Ugd2FudCB3aXRoIHRoZSBgdG9vbHRpcGAgYXJndW1lbnQuDQpUaGlzIGZ1bmN0aW9uIGlzIHNpbWlsYXIgdG8gdGhlIG5vcm1hbCBgZ2VvbV9wb2ludCgpYCBmdW5jdGlvbi4gVGh1cywgd2Ugd2lsbCBpbmNsdWRlIHRoZSBzYW1lIGFyZ3VtZW50cyBhcyBiZWZvcmUuIEhvd2V2ZXIsIHdlIHdpbGwgYWxzbyBzcGxpdCB0aGUgbWFsZSBhbmQgZmVtYWxlIGRhdGEgdXNpbmcgYGZhY2V0X3dyYXAoKWAgdG8gbWFrZSB0aGluZ3MgYSBiaXQgbGVzcyBvdmVyd2hlbG1pbmcuDQoNCk5vdGljZSB0aGF0IHdlIGFyZSBjcmVhdGluZyBhIHBsb3Qgb2JqZWN0IGJlZm9yZSB3ZSB1c2UgdGhlIGBnZW9tX3BvaW50X2ludGVyYWN0aXZlKClgLg0KDQpXZSBhcmUgYWxzbyByZW5kZXJpbmcgdGhlIHBsb3Qgd2l0aCB0aGUgYGdpcmFmZSgpYCBmdW5jdGlvbiBvZiB0aGUgYGdnaXJhcGhgIHBhY2thZ2UuDQoNCg0KYGBge3IsIGV2YWwgPSBUUlVFfQ0KZyA8LSBhbGxfYWdlX2RpZXRfYW5kX2d1aWRlbGluZXMgJT4lDQogIGZpbHRlcihmb29kID09ICJyZWQgbWVhdCIpICU+JQ0KICBmaWx0ZXIoUmVsYXRpdmVfUGVyY2VudCA8IDEwMCkgJT4lDQogIGdncGxvdChhZXMoDQogICAgeSA9IFJlbGF0aXZlX1BlcmNlbnQsDQogICAgeCA9IGFnZV9ncm91cF9uYW1lLA0KICAgIGZpbGwgPSBzZXgNCiAgKSkgKw0KICBnZW9tX2JveHBsb3Qob3V0bGllci5zaGFwZSA9IE5BKSArDQogIGZhY2V0X3dyYXAofnNleCkgKw0KICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dCgNCiAgICBhbmdsZSA9IDcwLA0KICAgIGhqdXN0ID0gMQ0KICApKQ0KDQpnIDwtIGcgKyBnZW9tX3BvaW50X2ludGVyYWN0aXZlKGFlcygNCiAgY29sb3IgPSBzZXgsDQogIHRvb2x0aXAgPSBsb2NhdGlvbl9uYW1lDQopLA0Kc2l6ZSA9IDIsDQpwb3NpdGlvbiA9IHBvc2l0aW9uX2ppdHRlcmRvZGdlKCksDQphbHBoYSA9IDMgLyAxMA0KKQ0KDQpnaXJhZmUoY29kZSA9IHByaW50KGcpKQ0KYGBgDQoNCg0KQ29vbCEgDQoNCkZyb20gdGhpcyBwbG90IHdlIGNhbiBzZWUgdGhlIGNvdW50cmllcyB3aXRoIHBvcHVsYXRpb25zIHRoYXQgZG8gd2VsbCBieSBub3Qgb3Zlci1jb25zdW1pbmcgcmVkIG1lYXQsIChhcyBvdmVyLWNvbnN1bXB0aW9uIGlzIGFzc29jaWF0ZWQgd2l0aCBoZWFsdGggcmlzaykuIFdlIHNlZSB0aGF0IGRpZmZlcmVudCBjb3VudHJpZXMgZ3JlYXRseSB2YXJ5LCB3ZSBjYW4gc2VlIHRoYXQgb3ZlcmFsbCB5b3VuZ2VyIHBvcHVsYXRpb25zIGFwcGVhciB0byBjb25zdW1lIG1vcmUgcmVkIG1lYXQsIGFuZCBtZW4gYXBwZWFyIHRvIGNvbnN1bWUgcmVkIG1lYXQuDQoNCkxldCdzIGRvIHRoZSBzYW1lIHRoaW5nIGZvciB0aGUgb3Zlci1jb25zdW1pbmcgY291bnRyaWVzLiBXZSBjYW4gYWxzbyB0YWtlIHRoaXMgb25lIHN0ZXAgZnVydGhlciB0byBzaG93IGFsbCB0aGUgcG9pbnRzIGZvciB0aGUgc2FtZSBjb3VudHJ5IHdoZW4gd2UgaG92ZXIgb3ZlciBvbmUgZGF0YSBwb2ludCBieSB1c2luZyB0aGUgYGRhdGFfaWRgIGFyZ3VtZW50IG9mIHRoZSBgZ2VvbV9wb2ludF9pbnRlcmFjdGl2ZSgpYCBmdW5jdGlvbi4gDQoNCg0KV2UgY2FuIGFsc28gYWRkIGxpbmtzIHRvIFdpa2lwZWRpYSBwYWdlcyBmb3IgdGhlc2UgY291bnRyaWVzIHVzaW5nIHRoZSBgb25jbGlja2AgYXJndW1lbnQuIFNlZSB0aGlzIFtsaW5rXShodHRwczovL2RhdmlkZ29oZWwuZ2l0aHViLmlvL2dnaXJhcGgvYXJ0aWNsZXMvb2ZmY3Jhbi91c2luZ19nZ2lyYXBoLmh0bWwpe3RhcmdldD0iX2JsYW5rIn0gZm9yIG1vcmUgaW5mb3JtYXRpb24gb24gdXNpbmcgdGhlIGBnZ2lycGFoYCBwYWNrYWdlLiBXZSB3aWxsIHVzZSB0aGUgYmFzZSBgc3ByaW50ZigpYCBmdW5jdGlvbiB0byBmb3JtYXQgb3VyIHVybHMgZm9yIHRoZSBXaWtpcGVkaWEgbGlua3MgaW50byBDIHN0eWxlIHRvIG9wZW4gYSBuZXcgdGFiIGZvciB0aGUgbGluayB3aGVuIGEgdXNlciBjbGlja3Mgb24gdGhlIGZpZ3VyZS4NCg0KYGBge3IsIGV2YWwgPSBUUlVFfQ0KYWxsX2FnZV9kaWV0X2FuZF9ndWlkZWxpbmVzICU8PiUNCiAgbXV0YXRlKGxpbmsgPSBzcHJpbnRmKA0KICAgICJ3aW5kb3cub3BlbihcIiVzJXNcIikiLA0KICAgICJodHRwOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpLyIsDQogICAgYXMuY2hhcmFjdGVyKHB1bGwoDQogICAgICBhbGxfYWdlX2RpZXRfYW5kX2d1aWRlbGluZXMsDQogICAgICBsb2NhdGlvbl9uYW1lDQogICAgKSkNCiAgKSkNCg0KZyA8LSBhbGxfYWdlX2RpZXRfYW5kX2d1aWRlbGluZXMgJT4lDQogIGZpbHRlcihmb29kID09ICJyZWQgbWVhdCIpICU+JQ0KICBmaWx0ZXIoUmVsYXRpdmVfUGVyY2VudCA+IDEwMCkgJT4lDQogIGdncGxvdChhZXMoDQogICAgeSA9IFJlbGF0aXZlX1BlcmNlbnQsDQogICAgeCA9IGFnZV9ncm91cF9uYW1lLA0KICAgIGZpbGwgPSBzZXgNCiAgKSkgKw0KICBnZW9tX2JveHBsb3Qob3V0bGllci5zaGFwZSA9IE5BKSArDQogIGZhY2V0X3dyYXAofnNleCkgKw0KICB0aGVtZSgNCiAgICBsZWdlbmQucG9zaXRpb24gPSAibm9uZSIsDQogICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoDQogICAgICBhbmdsZSA9IDcwLA0KICAgICAgaGp1c3QgPSAxDQogICAgKQ0KICApICsNCiAgZXhwYW5kX2xpbWl0cyh5ID0gOTkpDQoNCg0KZyA8LSBnICsgZ2VvbV9wb2ludF9pbnRlcmFjdGl2ZShhZXMoDQogIGNvbG9yID0gc2V4LA0KICB0b29sdGlwID0gbG9jYXRpb25fbmFtZSwNCiAgZGF0YV9pZCA9IGxvY2F0aW9uX25hbWUsDQogIG9uY2xpY2sgPSBsaW5rDQopLA0Kc2l6ZSA9IDIsDQpwb3NpdGlvbiA9IHBvc2l0aW9uX2ppdHRlcmRvZGdlKCksDQphbHBoYSA9IDMgLyAxMA0KKQ0KDQpnIDwtIGcgKyBnZW9tX3BvaW50X2ludGVyYWN0aXZlKA0KICBkYXRhID0NCiAgICBhbGxfYWdlX2RpZXRfYW5kX2d1aWRlbGluZXMgJT4lDQogICAgICBmaWx0ZXIoZm9vZCA9PSAicmVkIG1lYXQiKSAlPiUNCiAgICAgIGZpbHRlcihSZWxhdGl2ZV9QZXJjZW50ID4gMTAwKSAlPiUNCiAgICAgIGZpbHRlcihsb2NhdGlvbl9uYW1lID09ICJVbml0ZWQgU3RhdGVzIiksDQogIGFlcygNCiAgICBmaWxsID0gbG9jYXRpb25fbmFtZSwNCiAgICB0b29sdGlwID0gbG9jYXRpb25fbmFtZSwNCiAgICBkYXRhX2lkID0gbG9jYXRpb25fbmFtZSwNCiAgICBvbmNsaWNrID0gbGluaw0KICApLA0KICBzaXplID0gNCwNCiAgcG9zaXRpb24gPSBwb3NpdGlvbl9qaXR0ZXJkb2RnZSgpLA0KICBhbHBoYSA9IDUgLyAxMCwNCiAgY29sb3IgPSAiYmxhY2siDQopDQoNCmdpcmFmZShjb2RlID0gcHJpbnQoZykpDQpgYGANCg0KIyMjICoqVW5pdGVkIFNhdGVzIERhdGEqKg0KKioqDQoNCk5vdyBsZXQncyB0YWtlIGEgbG9vayBhdCB0aGUgVVMgZGF0YSBzcGVjaWZpY2FsbHkuDQoNCmBgYHtyfQ0KZGlldF9hbmRfZ3VpZGVsaW5lcyAlPiUNCiAgZmlsdGVyKGxvY2F0aW9uX25hbWUgPT0gIlVuaXRlZCBTdGF0ZXMiKSAlPiUNCiAgY291bnQob3B0X2FjaGlldmVkKQ0KYGBgDQoNCk9LLCBpdCBsb29rcyBsaWtlIG9wdGltYWwgY29uc3VtcHRpb24gbGV2ZWxzIHdlcmUgYWNoaWV2ZWQgZm9yIG9ubHkgMTAlIG9mIHRoZSBkaWV0YXJ5IGZhY3RvcnMuDQoNCkxldCdzIGxvb2sgYXQgbWFsZXMgYW5kIGZlbWFsZXMgc2VwYXJhdGVseToNCg0KIyMjIyB7LnJlY2FsbF9jb2RlX3F1ZXN0aW9uX2Jsb2NrfQ0KPGI+PHU+IFF1ZXN0aW9uIE9wcG9ydHVuaXR5IDwvdT48L2I+DQoNCkNhbiB5b3UgY29tZSB1cCB3aXRoIHRoZSBjb2RlIGZvciBob3cgeW91IHdvdWxkIGRvIHRoaXM/DQoNCiMjIyMNCg0KKioqDQo8ZGV0YWlscz4gPHN1bW1hcnk+IENsaWNrIGhlcmUgdG8gcmV2ZWFsIHRoZSBjb2RlLiA8L3N1bW1hcnk+DQoNCmBgYHtyLCBldmFsID0gRkFMU0V9DQpkaWV0X2FuZF9ndWlkZWxpbmVzICU+JQ0KICBmaWx0ZXIoDQogICAgc2V4ID09ICJNYWxlIiwNCiAgICBsb2NhdGlvbl9uYW1lID09ICJVbml0ZWQgU3RhdGVzIg0KICApICU+JQ0KICBjb3VudChvcHRfYWNoaWV2ZWQsIGZvb2QpICU+JQ0KICBhcnJhbmdlKGZvb2QpDQoNCmRpZXRfYW5kX2d1aWRlbGluZXMgJT4lDQogIGZpbHRlcigNCiAgICBzZXggPT0gIkZlbWFsZSIsDQogICAgbG9jYXRpb25fbmFtZSA9PSAiVW5pdGVkIFN0YXRlcyINCiAgKSAlPiUNCiAgY291bnQob3B0X2FjaGlldmVkLCBmb29kKSAlPiUNCiAgYXJyYW5nZShmb29kKQ0KYGBgDQo8L2RldGFpbHM+DQoqKioNCg0KRm9yIG1hbGVzOg0KYGBge3IsIGVjaG8gPSBGQUxTRX0NCmRpZXRfYW5kX2d1aWRlbGluZXMgJT4lDQogIGZpbHRlcigNCiAgICBzZXggPT0gIk1hbGUiLA0KICAgIGxvY2F0aW9uX25hbWUgPT0gIlVuaXRlZCBTdGF0ZXMiDQogICkgJT4lDQogIGNvdW50KG9wdF9hY2hpZXZlZCwgZm9vZCkgJT4lDQogIGFycmFuZ2UoZm9vZCkNCmBgYA0KDQpGb3IgZmVtYWxlczoNCmBgYHtyfQ0KZGlldF9hbmRfZ3VpZGVsaW5lcyAlPiUNCiAgZmlsdGVyKA0KICAgIHNleCA9PSAiRmVtYWxlIiwNCiAgICBsb2NhdGlvbl9uYW1lID09ICJVbml0ZWQgU3RhdGVzIg0KICApICU+JQ0KICBjb3VudChvcHRfYWNoaWV2ZWQsIGZvb2QpICU+JQ0KICBhcnJhbmdlKGZvb2QpDQpgYGANCg0KDQpTbyBmZW1hbGVzIGFyZSBhIGJpdCBiZXR0ZXIgYWJvdXQgbm90IG92ZXItY29uc3VtaW5nIHNvZGl1bSBpbiB0aGUgVW5pdGVkIFN0YXRlcyByZWxhdGl2ZSB0byBtYWxlcy4gIEJvdGggZ3JvdXBzIGFyZSBkb2luZyB3ZWxsIHdpdGggYXZvaWRpbmcgdHJhbnMgZmF0dHkgYWNpZHMuIExldCdzIGxvb2sgbW9yZSBjbG9zZWx5IGF0IHdoaWNoIGRpZXRhcnkgY29tcG9uZW50cyBoYXZlIGhpZ2ggYW5kIGxvdyBjb25zdW1wdGlvbiBpbiB0aGUgVW5pdGVkIFN0YXRlczoNCg0KYGBge3J9DQphbGxfYWdlX2RpZXRfYW5kX2d1aWRlbGluZXMgJT4lDQogIGZpbHRlcihsb2NhdGlvbl9uYW1lID09ICJVbml0ZWQgU3RhdGVzIikgJT4lDQogIGdncGxvdChhZXMoDQogICAgeSA9IFJlbGF0aXZlX1BlcmNlbnQsDQogICAgeCA9IGZvb2QsDQogICAgZmlsbCA9IHNleA0KICApKSArDQogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KA0KICAgIGFuZ2xlID0gNzAsDQogICAgaGp1c3QgPSAxDQogICkpICsNCiAgZmFjZXRfd3JhcCh+ZGlyZWN0aW9uLCBzY2FsZXMgPSAiZnJlZSIpICsNCiAgZ2VvbV9ib3hwbG90KCkgKw0KICBnZW9tX3BvaW50KA0KICAgIHBvc2l0aW9uID0gcG9zaXRpb25faml0dGVyZG9kZ2UoKSwNCiAgICBhbHBoYSA9IDMgLyAxMA0KICApDQpgYGANCg0KT0ssIHNvIHdlIGNhbiAgaW5kZWVkIHNlZSB0aGF0IG92ZXJhbGwgY29uc3VtcHRpb24gb2Ygc29kaXVtIGFuZCB0cmFucyBmYXR0eSBhY2lkcyBpcyBwcmV0dHkgY2xvc2UgdG8gb3B0aW1hbC4gU28gdGhhdCdzIGdyZWF0LiBIb3dldmVyLCBCb3RoIG1hbGVzIGFuZCBmZW1hbGVzIGFyZSBvdmVyLWNvbnN1bWluZyBwcm9jZXNzZWQgbWVhdCwgcmVkIG1lYXQsIGFuZCBzdWdhci1zd2VldGVuZWQgYmV2ZXJhZ2VzLiBPbiB0aGUgb3RoZXIgaGFuZCBib3RoIGdlbmRlcnMgYXJlIG5vdCBnZXR0aW5nIGFkZXF1YXRlIGludGFrZSBvZiBhbGwgdGhlIG90aGVyIGRpZXRhcnkgZmFjdG9ycyBmb3Igb3B0aW1hbCBoZWFsdGguIFRoZSBwb3B1bGF0aW9uIGluIHRoZSBVbml0ZWQgc3RhdGVzIGhhcyBlc3BlY2lhbGx5IHBvb3IgaW50YWtlIG9mIHBvbHl1bnNhdHVyYXRlZCBmYXRzLiBpdCBhbHNvIGxvb2tzIGxpa2UgaW4gbW9zdCBjYXNlcyBmZW1hbGVzIGFyZSBnZXR0aW5nIGxlc3Mgb2YgdGhlIGRpZXRhcnkgZmFjdG9ycyB0aGF0IHBvc2UgaGVhbHRoIHJpc2tzIHdoZW4gdW5kZXItY29uc3VtZWQsIHdpdGggdGhlIGV4Y2VwdGlvbiBvZiBmcnVpdHMuDQoNCg0KSG93IGFib3V0IGlmIHdlIGxvb2sgYXQgYWdlIGdyb3Vwcy4gRmlyc3QgbGV0J3MgbG9vayBhdCB0aGUgZGlldGFyeSBjb21wb25lbnRzIHdpdGggdGhhdCB3ZXJlIG92ZXItY29uc3VtZWQgaW4gdGhlIFVuaXRlZCBTdGF0ZXMuDQoNCiMjIyMgey5yZWNhbGxfY29kZV9xdWVzdGlvbl9ibG9ja30NCjxiPjx1PiBRdWVzdGlvbiBPcHBvcnR1bml0eSA8L3U+PC9iPg0KDQpDYW4geW91IGNvbWUgdXAgd2l0aCB0aGUgY29kZSBmb3IgdGhpcyBvbiB5b3VyIG93bj8NCg0KIyMjIw0KDQoqKioNCjxkZXRhaWxzPiA8c3VtbWFyeT4gQ2xpY2sgaGVyZSB0byByZXZlYWwgdGhlIGNvZGUuIDwvc3VtbWFyeT4NCg0KIFdlIHdpbGwgYWxzbyBtb3ZlIG91ciBsZWdlbmQgdG8gdGhlIGJvdHRvbSBvZiB0aGUgcGxvdCB1c2luZyB0aGUgYHRoZW1lKClgIGZ1bmN0aW9uIG9mIHRoZSBgZ2dwbG90MmAgcGFja2FnZSwgbGlrZSBzbzoNCg0KYGBge3J9DQpwbG90X2FnZV9ncm91cHMgPC0gYWxsX2FnZV9kaWV0X2FuZF9ndWlkZWxpbmVzICU+JQ0KICBmaWx0ZXIoDQogICAgbG9jYXRpb25fbmFtZSA9PSAiVW5pdGVkIFN0YXRlcyIsDQogICAgZGlyZWN0aW9uID09ICJoaWdoIg0KICApICU+JQ0KICBnZ3Bsb3QoYWVzKA0KICAgIHkgPSBSZWxhdGl2ZV9QZXJjZW50LA0KICAgIHggPSBmb29kLA0KICAgIGZpbGwgPSBhZ2VfZ3JvdXBfbmFtZQ0KICApKSArDQogIGZhY2V0X3dyYXAofmZvb2QsIHNjYWxlcyA9ICJmcmVlIikgKw0KICBnZW9tX2JveHBsb3QoKSArDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iKQ0KYGBgDQo8L2RldGFpbHM+DQoNCioqKg0KDQpgYGB7cn0NCnBsb3RfYWdlX2dyb3Vwcw0KYGBgDQoNCk9LISBJdCBsb29rcyBsaWtlIGFnZSByZWFsbHkgaW5mbHVlbmNlcyB0aGUgY29uc3VtcHRpb24gb2YgdGhlc2UgZGlldGFyeSBmYWN0b3JzLiBXaXRoIHRoZSBleGNlcHRpb24gb2YgdHJhbnMgZmF0dHkgYWNpZHMsIHRoZSBjb25zdW1wdGlvbiBvZiBhbGwgb2YgdGhlc2UgZGlldGFyeSBmYWN0b3JzIHNlZW1zIHRvIGRlY3JlYXNlIHdpdGggYWdlLiAgTGV0J3MgYWxzbyB1c2UgdGhlYHNjYWxlX2ZpbGxfdmlyaWRpcygpYCBmdW5jdGlvbiBvZiB0aGUgYHZpcmlkaXNgIHBhY2thZ2UgdG8gY2hhbmdlIHRoZSBjb2xvcnMgb2Ygb3VyIHBsb3QuIFRoaXMgcGFja2FnZSB1c2VzIHBhbGV0dGVzIG9mIGNvbG9ycyB0aGF0IGFyZSBkaXNjZXJuaWJsZSBmb3IgaW5kaXZpZHVhbHMgd2hvIGFyZSBjb2xvcmJsaW5kLiANCmBgYHtyfQ0KYWxsX2FnZV9kaWV0X2FuZF9ndWlkZWxpbmVzICU+JQ0KICBmaWx0ZXIoDQogICAgbG9jYXRpb25fbmFtZSA9PSAiVW5pdGVkIFN0YXRlcyIsDQogICAgZGlyZWN0aW9uID09ICJoaWdoIg0KICApICU+JQ0KICBnZ3Bsb3QoYWVzKA0KICAgIHkgPSBSZWxhdGl2ZV9QZXJjZW50LA0KICAgIHggPSBmb29kLA0KICAgIGZpbGwgPSBhZ2VfZ3JvdXBfbmFtZQ0KICApKSArDQogIGZhY2V0X3dyYXAofmZvb2QsIHNjYWxlcyA9ICJmcmVlIikgKw0KICBnZW9tX2JveHBsb3QoKSArDQogICMgY2hhbmdlIHRoZSBjb2xvcnMgZnJvbSByYWluYm93IHRvIHB1cnBsZS9ncmVlbi95ZWxsb3cNCiAgc2NhbGVfZmlsbF92aXJpZGlzKGRpc2NyZXRlID0gVFJVRSkgKw0KICB0aGVtZV9saW5lZHJhdygpICsNCiAgdGhlbWUoDQogICAgc3RyaXAudGV4dCA9IGVsZW1lbnRfdGV4dChzaXplID0gOCwgZmFjZSA9ICJib2xkIiksDQogICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCksDQogICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF9ibGFuaygpLA0KICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iDQogICkNCmBgYA0KDQpOaWNlIQ0KDQoNCk5vdyBsZXQncyBsb29rIGF0IHRoZSBkaWV0YXJ5IGZhY3RvcnMgdGhhdCB3aGVuIGNvbnN1bWVkIGF0IGxvdyBsZXZlbHMgaW5jcmVhc2UgaGVhbHRoIHJpc2s6DQoNCg0KIyMjIyB7LnJlY2FsbF9jb2RlX3F1ZXN0aW9uX2Jsb2NrfQ0KPGI+PHU+IFF1ZXN0aW9uIE9wcG9ydHVuaXR5IDwvdT48L2I+DQoNCkFnYWluLCBzZWUgaWYgeW91IGNvbWUgdXAgd2l0aCB0aGUgY29kZSBmb3IgdGhpcyBvbiB5b3VyIG93bj8NCg0KIyMjIw0KDQoqKioNCjxkZXRhaWxzPiA8c3VtbWFyeT4gQ2xpY2sgaGVyZSB0byByZXZlYWwgdGhlIGNvZGUuIDwvc3VtbWFyeT4NCg0KYGBge3J9DQpsb3dfZm9vZHNfcGxvdCA8LSBhbGxfYWdlX2RpZXRfYW5kX2d1aWRlbGluZXMgJT4lDQogIGZpbHRlcigNCiAgICBsb2NhdGlvbl9uYW1lID09ICJVbml0ZWQgU3RhdGVzIiwNCiAgICBkaXJlY3Rpb24gPT0gImxvdyINCiAgKSAlPiUNCiAgZ2dwbG90KGFlcygNCiAgICB5ID0gUmVsYXRpdmVfUGVyY2VudCwNCiAgICB4ID0gZm9vZCwNCiAgICBmaWxsID0gYWdlX2dyb3VwX25hbWUNCiAgKSkgKw0KICBmYWNldF93cmFwKH5mb29kLCBzY2FsZXMgPSAiZnJlZSIpICsNCiAgZ2VvbV9ib3hwbG90KCkgKw0KICAjIGNoYW5nZSB0aGUgY29sb3JzIGZyb20gcmFpbmJvdyB0byBwdXJwbGUvZ3JlZW4veWVsbG93DQogIHNjYWxlX2ZpbGxfdmlyaWRpcyhkaXNjcmV0ZSA9IFRSVUUpICsNCiAgdGhlbWVfbGluZWRyYXcoKSArDQogIHRoZW1lKA0KICAgIHN0cmlwLnRleHQgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDcsIGZhY2UgPSAiYm9sZCIpLA0KICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpLA0KICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfYmxhbmsoKSwNCiAgICBsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIg0KICApDQpgYGANCg0KPC9kZXRhaWxzPiANCioqKg0KDQoNCmBgYHtyfQ0KbG93X2Zvb2RzX3Bsb3QNCmBgYA0KDQoNCg0KSW50ZXJlc3RpbmcsIHdlIHNlZSB0aGF0IGZvciB0aGUgZm9vZHMgdGhhdCBhcmUgb3ZlciBjb25zdW1lZCAocHJvY2Vzc2VkIG1lYXQsIHJlZCBtZWF0LCBzb2RpdW0sIGFuZCBzdWdhci1zd2VldGVuZWQgYmV2ZXJhZ2VzKSwgY29uc3VtcHRpb24gYXBwZWFycyB0byBkZWNyZWFzZSB3aXRoIGFnZS4gRm9yIHRoZSBmb29kcyB0aGF0IGFyZSB1bmRlciBjb25zdW1lZCwgbWFueSBhcHBlYXIgdG8gcmlzZSBhbmQgZmFsbCB3aXRoIGFnZS4NCg0KIyMjICoqT3ZlcmFsbCB0cmVuZHMqKg0KKioqDQoNCkZpbmFsbHksIHdlIHdvdWxkIGxpa2UgdG8gZ2V0IGEgZ2VuZXJhbCBzZW5zZSBvZiBob3cgY29uc3VtcHRpb24gb2YgdGhlc2UgZGlldGFyeSBmYWN0b3JzIGRpZmZlcnMgYXJvdW5kIHRoZSB3b3JsZCBhbmQgd2Ugd291bGQgbGlrZSB0byBrbm93IGhvdyB0aGUgVVMgY29tcGFyZXMgdG8gb3RoZXIgY291bnRyaWVzLiANCg0KQmVmb3JlIHdlIGRvIHRoaXMgbGV0J3MgY2hhbmdlIHRoZSBsYWJlbHMgb2YgdGhlIGZvb2RzIGJ5IGFkZGluZyBuZXcgbGluZSBicmVha3MgKGAiXG4iYCkgc28gdGhhdCB0aGV5IHdpbGwgZml0IG1vcmUgZWFzaWx5IG9uIG91ciBncmFwaHMuDQoNCiMjIyMgey5yZWNhbGxfY29kZV9xdWVzdGlvbl9ibG9ja30NCjxiPjx1PiBRdWVzdGlvbiBPcHBvcnR1bml0eSA8L3U+PC9iPg0KDQpTZWUgaWYgeW91IGNvbWUgdXAgd2l0aCB0aGUgY29kZSBmb3IgdGhpcyBvbiB5b3VyIG93bj8NCg0KIyMjIw0KDQoqKioNCjxkZXRhaWxzPiA8c3VtbWFyeT4gQ2xpY2sgaGVyZSB0byByZXZlYWwgdGhlIGNvZGUgZm9yIHRoZSBgZGlldF9hbmRfZ3VpZGVsaW5lc2AgYW5kIHRoZSBgYWxsX2FnZV9kaWV0X2FuZF9ndWlkZWxpbmVzYCBkYXRhLiA8L3N1bW1hcnk+DQoNCmBgYHtyfQ0KZGlldF9hbmRfZ3VpZGVsaW5lc1tbImZvb2QiXV0gPC0gc3RyX3JlcGxhY2VfYWxsKA0KICBkaWV0X2FuZF9ndWlkZWxpbmVzW1siZm9vZCJdXSwNCiAgInN1Z2FyLXN3ZWV0ZW5lZCBiZXZlcmFnZXMiLA0KICAic3VnYXItc3dlZXRlbmVkXG5iZXZlcmFnZXMiDQopDQpkaWV0X2FuZF9ndWlkZWxpbmVzW1siZm9vZCJdXSA8LSBzdHJfcmVwbGFjZV9hbGwoDQogIGRpZXRfYW5kX2d1aWRlbGluZXNbWyJmb29kIl1dLA0KICAic2VhZm9vZCBvbWVnYS0zIGZhdHR5IGFjaWRzIiwNCiAgInNlYWZvb2Qgb21lZ2EtM1xuZmF0dHkgYWNpZHMiDQopDQpkaWV0X2FuZF9ndWlkZWxpbmVzW1siZm9vZCJdXSA8LSBzdHJfcmVwbGFjZV9hbGwoDQogIGRpZXRfYW5kX2d1aWRlbGluZXNbWyJmb29kIl1dLA0KICAicG9seXVuc2F0dXJhdGVkIGZhdHR5IGFjaWRzIiwNCiAgInBvbHl1bnNhdHVyYXRlZFxuZmF0dHkgYWNpZHMiDQopDQoNCmFsbF9hZ2VfZGlldF9hbmRfZ3VpZGVsaW5lc1tbImZvb2QiXV0gPC0gc3RyX3JlcGxhY2VfYWxsKA0KICBhbGxfYWdlX2RpZXRfYW5kX2d1aWRlbGluZXNbWyJmb29kIl1dLA0KICAic3VnYXItc3dlZXRlbmVkIGJldmVyYWdlcyIsDQogICJzdWdhci1zd2VldGVuZWRcbmJldmVyYWdlcyINCikNCg0KYWxsX2FnZV9kaWV0X2FuZF9ndWlkZWxpbmVzW1siZm9vZCJdXSA8LSBzdHJfcmVwbGFjZV9hbGwoDQogIGFsbF9hZ2VfZGlldF9hbmRfZ3VpZGVsaW5lc1tbImZvb2QiXV0sDQogICJzZWFmb29kIG9tZWdhLTMgZmF0dHkgYWNpZHMiLA0KICAic2VhZm9vZCBvbWVnYS0zXG5mYXR0eSBhY2lkcyINCikNCg0KYWxsX2FnZV9kaWV0X2FuZF9ndWlkZWxpbmVzW1siZm9vZCJdXSA8LSBzdHJfcmVwbGFjZV9hbGwoDQogIGFsbF9hZ2VfZGlldF9hbmRfZ3VpZGVsaW5lc1tbImZvb2QiXV0sDQogICJwb2x5dW5zYXR1cmF0ZWQgZmF0dHkgYWNpZHMiLA0KICAicG9seXVuc2F0dXJhdGVkXG5mYXR0eSBhY2lkcyINCikNCmBgYA0KDQo8L2RldGFpbHM+DQoqKioNCg0KYGBge3J9DQpkaWV0X2FuZF9ndWlkZWxpbmVzICU+JQ0KICBzZWxlY3QoZm9vZCkgJT4lDQogIGRpc3RpbmN0KCkNCmBgYA0KDQpgYGB7cn0NCmFsbF9hZ2VfZGlldF9hbmRfZ3VpZGVsaW5lcyAlPiUNCiAgc2VsZWN0KGZvb2QpICU+JQ0KICBkaXN0aW5jdCgpDQpgYGANCg0KTmljZSENCg0KIyMjIyBVbmRlci1jb25zdW1lZCBmb29kcw0KDQpUbyBjaG9vc2UgdGhlIGNvbG9ycyBmb3Igb3VyIHBsb3Qgd2UgY2FuIHVzZSB0aGUgYHNob3dfY29sKClgIGZ1bmN0aW9uIG9mIHRoZSBgc2NhbGVzYCBwYWNrYWdlICh3aGljaCBpcyBpbnN0YWxsZWQgd2l0aCBgZ2dwbG90MmAgcGFja2FnZSkgdG8gcHJldmlldyBjb2xvciBvcHRpb25zIGZyb20gdGhlIHZpcmlkaXMgcGFsZXR0ZSBvZiB0aGUgYHZpcmlkaXNgIHBhY2thZ2UuDQoNCmBgYHtyfQ0Kc2NhbGVzOjpzaG93X2NvbCh2aXJpZGlzX3BhbCgpKDMpKQ0KYGBgDQoNCldlIHdpbGwgdXNlIHRoZSBgcG9zaXRpb25faml0dGVyZG9kZ2UoKWAgaW4gb3VyIGBwb3NpdGlvbmAgYXJndW1lbnQgb2YgYGdlb21fcG9pbnQoKWAgdG8gaW5kaWNhdGUgaG93IHRoZSBwb2ludHMgc2hvdWxkIGJlIGdyb3VwZWQgYnkgc2V4Lg0KDQpgYGB7ciwgZmlnLndpZHRoID0gMTcsIGZpZy5oZWlnaHQ9MTR9DQojIGZpcnN0IGZpbHRlciB0aGUgdmFsdWVzDQpVbmRlciA8LSBkaWV0X2FuZF9ndWlkZWxpbmVzICU+JQ0KICBmaWx0ZXIoZGlyZWN0aW9uID09ICJsb3ciKSAlPiUNCiAgZmlsdGVyKFJlbGF0aXZlX1BlcmNlbnQgPCAxMTApICU+JQ0KICBnZ3Bsb3QoYWVzKA0KICAgIHkgPSBSZWxhdGl2ZV9QZXJjZW50LA0KICAgIHggPSBmb29kLA0KICAgIGNvbG9yID0gc2V4DQogICkpICsNCiAgIyBhZGRzIGdyZXkgcG9pbnRzIGZvciBlYWNoIGNvdW50cnkNCiAgZ2VvbV9wb2ludCgNCiAgICBwb3NpdGlvbiA9IHBvc2l0aW9uX2ppdHRlcmRvZGdlKCksDQogICAgY29sb3IgPSAiZGFyayBncmV5IiwNCiAgICBhbHBoYSA9IDcgLyAxMCwNCiAgICBzaXplID0gNywNCiAgICAjIHRoaXMgc3BlY2lmaWVzIGhvdyB0byBzZXBhcmF0ZSB0aGUgcG9pbnRzDQogICAgYWVzKGZpbGwgPSBzZXgpDQogICkgKw0KICAjIGFkZCBib3hwbG90cw0KICBnZW9tX2JveHBsb3QoDQogICAgb3V0bGllci5zaGFwZSA9IE5BLA0KICAgIGNvbG9yID0gImJsYWNrIiwNCiAgICBsd2QgPSAyLjUsDQogICAgYWVzKGZpbGwgPSBzZXgpDQogICkgKw0KICAjIHRoaXMgYWxsb3dzIHVzIHRvIHVzZSBzcGVjaWZpYyBjb2xvcnMNCiAgc2NhbGVfZmlsbF9tYW51YWwoDQogICAgdmFsdWVzID0NCiAgICAgIGMoDQogICAgICAgICIjNDgxNTY3RkYiLA0KICAgICAgICAiIzFGOTY4QkZGIg0KICAgICAgKQ0KICApICsNCiAgIyBhZGRzIGxpbmUgZm9yIG9wdGltYWwgYW1vdW50DQogIGdlb21faGxpbmUoDQogICAgeWludGVyY2VwdCA9IDEwMCwNCiAgICBsaW5ldHlwZSA9ICJkYXNoZWQiLA0KICAgIGNvbG9yID0gInJlZCIsDQogICAgc2l6ZSA9IDMNCiAgKSArDQogICMgbWFudWFsbHkgY2hhbmdlcyB5IGF4aXMgYnJlYWtzDQogIHNjYWxlX3lfY29udGludW91cygNCiAgICBicmVha3MgPSBjKDAsIDI1LCA1MCwgNzUsIDEwMCksDQogICAgbGFiZWxzID0gYygNCiAgICAgIDAsIDI1LCA1MCwgNzUsDQogICAgICAiMTAwJSBcbiBvcHRpbWFsIGFtb3VudCINCiAgICApDQogICkNCiMgY3JlYXRlcyBhIGxhcmdlciBibGFjayBwb2ludCBmb3IgdGhlIFVTIGRhdGENClVuZGVyIDwtIFVuZGVyICsNCiAgZ2VvbV9wb2ludCgNCiAgICBkYXRhID0gZGlldF9hbmRfZ3VpZGVsaW5lcyAlPiUNCiAgICAgIGZpbHRlcigNCiAgICAgICAgZGlyZWN0aW9uID09ICJsb3ciLA0KICAgICAgICBsb2NhdGlvbl9uYW1lID09ICJVbml0ZWQgU3RhdGVzIg0KICAgICAgKSwNCiAgICBwb3NpdGlvbiA9IHBvc2l0aW9uX2ppdHRlcmRvZGdlKA0KICAgICAgaml0dGVyLndpZHRoID0gMC4wMSwNCiAgICAgIGRvZGdlLndpZHRoID0gMC43DQogICAgKSwNCiAgICBjb2xvciA9ICJibGFjayIsDQogICAgc2l6ZSA9IDExLA0KICAgIGFlcygNCiAgICAgIGZpbGwgPSBzZXgsDQogICAgICBzaGFwZSA9IGxvY2F0aW9uX25hbWUNCiAgICApDQogICkNCg0KIyBjcmVhdGVzIGEgc21hbGxlciB5ZWxsb3cgcG9pbnQgb24gdG9wIGZvciB0aGUgVVMgZGF0YQ0KVW5kZXIgPC0gVW5kZXIgKw0KICBnZW9tX3BvaW50KA0KICAgIGRhdGEgPSBkaWV0X2FuZF9ndWlkZWxpbmVzICU+JQ0KICAgICAgZmlsdGVyKA0KICAgICAgICBkaXJlY3Rpb24gPT0gImxvdyIsDQogICAgICAgIGxvY2F0aW9uX25hbWUgPT0gIlVuaXRlZCBTdGF0ZXMiDQogICAgICApLA0KICAgIHBvc2l0aW9uID0gcG9zaXRpb25faml0dGVyZG9kZ2UoDQogICAgICBqaXR0ZXIud2lkdGggPSAwLjAxLA0KICAgICAgZG9kZ2Uud2lkdGggPSAwLjcNCiAgICApLA0KICAgIGNvbG9yID0gIiNGRkRGMDAiLA0KICAgIHNpemUgPSA3LA0KICAgIGFlcygNCiAgICAgIGZpbGwgPSBzZXgsDQogICAgICBzaGFwZSA9IGxvY2F0aW9uX25hbWUNCiAgICApDQogICkgKw0KICAjIG1ha2UgdGhlIHBsb3QgbG9vayBuaWNlDQogIHRoZW1lX2xpbmVkcmF3KCkgKw0KICB0aGVtZSgNCiAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KA0KICAgICAgc2l6ZSA9IDQwLA0KICAgICAgaGp1c3QgPSAwLjUsDQogICAgICBmYWNlID0gImJvbGQiDQogICAgKSwNCiAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dCgNCiAgICAgIGFuZ2xlID0gNzAsDQogICAgICBoanVzdCA9IDEsDQogICAgICBzaXplID0gMzINCiAgICApLA0KICAgICMgdGhpcyBpcyB1c2VmdWwgZm9yIHJlbW92aW5nIHRoZSBsZWdlbmQNCiAgICBsZWdlbmQucG9zaXRpb24gPSAibm9uZSIsDQogICAgYXhpcy50ZXh0LnkgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDM1KSwNCiAgICBheGlzLnRpdGxlLnkgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDM1KSwNCiAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X3RleHQoc2l6ZSA9IDI1KSwNCiAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9yZWN0KA0KICAgICAgY29sb3VyID0gImJsYWNrIiwNCiAgICAgIHNpemUgPSAxLjUNCiAgICApDQogICkgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIlxuIEdsb2JhbCBjb25zdW1wdGlvbiBvZiBmb29kcyBhc3NvY2lhdGVkIHdpdGhcbmhlYWx0aCByaXNrIHdoZW4gdW5kZXItY29uc3VtZWQiLA0KICAgIHggPSAiIiwNCiAgICB5ID0gIlBlcmNlbnQgY29uc3VtcHRpb24gcmVsYXRpdmUgXG4gdG8gZ3VpZGVsaW5lcyINCiAgKQ0KDQpVbmRlcg0KYGBgDQoNCiMjIyMgT3Zlci1jb25zdW1lZCBmb29kcw0KDQpXZSB3aWxsIHVzZSB0aGUgYGZhY2V0X3pvb20oKWAgZnVuY3Rpb24gb2YgdGhlIGBnZ2ZvcmNlYCBwYWNrYWdlIHRvIGNyZWF0ZSBhIHBsb3Qgd2l0aCBhIHpvb21lZCBpbiBwb3J0aW9uLg0KDQpgYGB7ciwgZmlnLndpZHRoPTIwLCBmaWcuaGVpZ2h0PTE2fQ0KT3ZlciA8LSBkaWV0X2FuZF9ndWlkZWxpbmVzICU+JQ0KICBmaWx0ZXIoZGlyZWN0aW9uID09ICJoaWdoIikgJT4lDQogIGdncGxvdChhZXMoDQogICAgeSA9IFJlbGF0aXZlX1BlcmNlbnQsDQogICAgeCA9IGZvb2QNCiAgKSkgKw0KICAjIHRoaXMgYWRkcyBwb2ludHMgZm9yIGVhY2ggY291bnRyeQ0KICBnZW9tX3BvaW50KA0KICAgIHBvc2l0aW9uID0gcG9zaXRpb25faml0dGVyZG9kZ2UoKSwNCiAgICBjb2xvciA9ICJkYXJrIGdyZXkiLA0KICAgIGFscGhhID0gNyAvIDEwLA0KICAgICMgdGhpcyBzcGVjaWZpZXMgaG93IHRvIHNlcGFyYXRlIHRoZSBwb2ludHMgZm9yIG1hbGUgYW5kIGZlbWFsZQ0KICAgIGFlcyhmaWxsID0gc2V4KSwNCiAgICBzaXplID0gNw0KICApICsNCiAgIyB0aGlzIGFkZHMgYm94cGxvdHMNCiAgZ2VvbV9ib3hwbG90KA0KICAgIG91dGxpZXIuc2hhcGUgPSBOQSwNCiAgICBjb2xvciA9ICJibGFjayIsDQogICAgbHdkID0gMi41LA0KICAgICMga2V5X2dseXBoID0gZHJhd19rZXlfcmVjdCksDQogICAgYWVzKGZpbGwgPSBzZXgpDQogICkgKw0KICAjIHRoaXMgbWFudWFsbHkgY2hhbmdlcyBib3hwbG90IGNvbG9ycw0KICBzY2FsZV9maWxsX21hbnVhbCgNCiAgICB2YWx1ZXMgPQ0KICAgICAgYygNCiAgICAgICAgIiM0ODE1NjdGRiIsDQogICAgICAgICIjMUY5NjhCRkYiDQogICAgICApDQogICkgKw0KICAjIHRoaXMgYWRkcyBvcHRpbWFsIHJlZCBsaW5lDQogIGdlb21faGxpbmUoDQogICAgeWludGVyY2VwdCA9IDEwMCwNCiAgICBsaW5ldHlwZSA9ICJkYXNoZWQiLA0KICAgIGNvbG9yID0gInJlZCIsDQogICAgc2l6ZSA9IDMNCiAgKSArDQogICMgdGhpcyBjaGFuZ2VzIHkgYXhpcyBicmVha3MNCiAgc2NhbGVfeV9jb250aW51b3VzKA0KICAgIGJyZWFrcyA9IGMoMTAwLCA1MDAsIDEwMDAsIDIwMDApLA0KICAgIGxhYmVscyA9IGMoIjEwMCUgdGhyZXNoLiIsIDUwMCwgMTAwMCwgMjAwMCkNCiAgKSArDQogICMgdGhpcyB6b29tcyBpbiB0byBwYXJ0IG9mIHRoZSBwbG90DQogIGdnZm9yY2U6OmZhY2V0X3pvb20oeWxpbSA9IGMoMCwgMTMwMCkpICsNCiAgIyB0aGlzIGNoYW5nZXMgdGhlIGxlZ2VuZCBkaXJlY3Rpb24NCiAgZ3VpZGVzKGd1aWRlX2xlZ2VuZCgNCiAgICBkaXJlY3Rpb24gPSAiaG9yaXpvbnRhbCIsDQogICAgbGFiZWwudmp1c3QgPSAuNQ0KICApKQ0KDQojIGNyZWF0ZXMgYSBsYXJnZXIgYmxhY2sgcG9pbnQgZm9yIHRoZSBVUyBkYXRhDQpPdmVyIDwtIE92ZXIgKw0KICBnZW9tX3BvaW50KA0KICAgIGRhdGEgPSBkaWV0X2FuZF9ndWlkZWxpbmVzICU+JQ0KICAgICAgZmlsdGVyKA0KICAgICAgICBkaXJlY3Rpb24gPT0gImhpZ2giLA0KICAgICAgICBsb2NhdGlvbl9uYW1lID09ICJVbml0ZWQgU3RhdGVzIg0KICAgICAgKSwNCiAgICBwb3NpdGlvbiA9IHBvc2l0aW9uX2ppdHRlcmRvZGdlKA0KICAgICAgaml0dGVyLndpZHRoID0gMC4wMSwNCiAgICAgIGRvZGdlLndpZHRoID0gMC43DQogICAgKSwNCiAgICBjb2xvciA9ICJibGFjayIsDQogICAgc2l6ZSA9IDExLA0KICAgIGFlcygNCiAgICAgIGZpbGwgPSBzZXgsDQogICAgICBzaGFwZSA9IGxvY2F0aW9uX25hbWUNCiAgICApDQogICkNCg0KIyBjcmVhdGVzIGEgc21hbGxlciB5ZWxsb3cgcG9pbnQgb24gdG9wIGZvciB0aGUgVVMgZGF0YQ0KT3ZlciA8LSBPdmVyICsNCiAgZ2VvbV9wb2ludCgNCiAgICBkYXRhID0gZGlldF9hbmRfZ3VpZGVsaW5lcyAlPiUNCiAgICAgIGZpbHRlcigNCiAgICAgICAgZGlyZWN0aW9uID09ICJoaWdoIiwNCiAgICAgICAgbG9jYXRpb25fbmFtZSA9PSAiVW5pdGVkIFN0YXRlcyINCiAgICAgICksDQogICAgcG9zaXRpb24gPSBwb3NpdGlvbl9qaXR0ZXJkb2RnZSgNCiAgICAgIGppdHRlci53aWR0aCA9IDAuMDEsDQogICAgICBkb2RnZS53aWR0aCA9IDAuNw0KICAgICksDQogICAgY29sb3IgPSAiI0ZGREYwMCIsDQogICAgc2l6ZSA9IDcsDQogICAgYWVzKA0KICAgICAgZmlsbCA9IHNleCwNCiAgICAgIHNoYXBlID0gbG9jYXRpb25fbmFtZQ0KICAgICkNCiAgKSArDQogIHNjYWxlX2NvbG9yX2Rpc2NyZXRlKGJyZWFrcyA9ICJVbml0ZWQgU3RhdGVzIikgKw0KICAjIHRoaXMgbWFrZXMgdGhlIHBsb3QgbG9vayBuaWNlDQogIHRoZW1lX2xpbmVkcmF3KCkgKw0KICB0aGVtZSgNCiAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KA0KICAgICAgc2l6ZSA9IDQwLA0KICAgICAgaGp1c3QgPSAwLjUsDQogICAgICBmYWNlID0gImJvbGQiDQogICAgKSwNCiAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dCgNCiAgICAgIGFuZ2xlID0gNjAsDQogICAgICBoanVzdCA9IDEsDQogICAgICBzaXplID0gMzINCiAgICApLA0KICAgIGxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSwNCiAgICBsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIiwNCiAgICAjIHRoaXMgaXMgZm9yIGNoYW5naW5nIHRoZSB6b29tIHRyaWFuZ2xlIGNvbG9yDQogICAgc3RyaXAuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdCgNCiAgICAgIGZpbGwgPSAiZ3JleTg2IiwNCiAgICAgIGNvbG91ciA9ICJncmV5ODYiDQogICAgKSwNCiAgICBheGlzLnRleHQueSA9IGVsZW1lbnRfdGV4dChzaXplID0gMzUpLA0KICAgIGF4aXMudGl0bGUueSA9IGVsZW1lbnRfdGV4dChzaXplID0gMzUpLA0KICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfdGV4dChzaXplID0gMjUpLA0KICAgIGxlZ2VuZC50ZXh0ID0gZWxlbWVudF90ZXh0KA0KICAgICAgc2l6ZSA9IDMwLA0KICAgICAgdmp1c3QgPSAuMDENCiAgICApLA0KICAgICMgdGhpcyBpcyBmb3IgY2hhbmdpbmcgdGhlIGxlZ2VuZCBzeW1ib2wgc2l6ZQ0KICAgIGxlZ2VuZC5rZXkuaGVpZ2h0ID0gdW5pdCgyLCAiY20iKSwNCiAgICBsZWdlbmQua2V5LndpZHRoID0gdW5pdCgyLCAiY20iKSwNCiAgICAjIHRoaXMgaXMgZm9yIGNoYW5naW5nIHRoZSBmaWd1cmUgb3V0bGluZQ0KICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoDQogICAgICBjb2xvdXIgPSAiYmxhY2siLA0KICAgICAgc2l6ZSA9IDEuNQ0KICAgICkNCiAgKSArDQogIGV4cGFuZF9saW1pdHMoeSA9IC0xMCkgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIiBHbG9iYWwgY29uc3VtcHRpb24gb2YgZm9vZHMgYXNzb2NpYXRlZCB3aXRoXG4gaGVhbHRoIHJpc2sgd2hlbiBvdmVyLWNvbnN1bWVkIiwNCiAgICB4ID0gIiIsDQogICAgeSA9ICJQZXJjZW50IGNvbnN1bXB0aW9uIHJlbGF0aXZlIFxuIHRvIGd1aWRlbGluZXMiDQogICkNCg0KT3Zlcg0KYGBgDQoNCkxldCdzIHB1dCB0aGUgcGxvdHMgdG9nZXRoZXI6DQoNCmBgYHtyLCBmaWcuaGVpZ2h0ID0gMjYsIGZpZy53aWR0aCA9IDE4fQ0KY293cGxvdDo6cGxvdF9ncmlkKE92ZXIsIFVuZGVyLCBuY29sID0gMSkNCmBgYA0KTmljZSENCg0KDQojIyMjIE92ZXItY29uc3VtZWQgYnkgQWdlIEdsb2JhbCB0cmVuZHMNCg0KTm93IHdlIHdpbGwgbG9vayBhdCBHbG9iYWwgdHJlbmRzIHdpdGggYWdlLiANCg0KVG8gbWFrZSB0aGlzIHBsb3Qgd2Ugd2lsbCB1c2UgdGhlIGBmYWNldF93cmFwX3BhZ2luYXRlKClgIGZ1bmN0aW9uIG9mIHRoZSBgZ2dmb3JjZWAgcGFja2FnZSB3aGljaCBhbGxvd3MgeW91IHRvIHNwZWNpZnkgdGhlIG51bWJlciBvZiBjb2x1bW5zIG9yIHJvd3MgZm9yIHRoZSBmYWNldHMuDQoNCmBgYHtyLCBmaWcud2lkdGggPSAxNiwgZmlnLmhlaWdodCA9IDE4fQ0KDQpPdmVyX2FnZSA8LSBhbGxfYWdlX2RpZXRfYW5kX2d1aWRlbGluZXMgJT4lDQogIGZpbHRlcihkaXJlY3Rpb24gPT0gImhpZ2giKSAlPiUNCiAgZmlsdGVyKFJlbGF0aXZlX1BlcmNlbnQgPCA0MDAwKSAlPiUNCiAgZ2dwbG90KGFlcygNCiAgICB5ID0gUmVsYXRpdmVfUGVyY2VudCwNCiAgICB4ID0gYWdlX2dyb3VwX25hbWUsDQogICAgZmlsbCA9IGFnZV9ncm91cF9uYW1lDQogICkpICsNCiAgZ2VvbV9ib3hwbG90KA0KICAgIG91dGxpZXIuc2hhcGUgPSBOQSwNCiAgICBjb2xvciA9ICJibGFjayIsDQogICAgYWVzKGZpbGwgPSBhZ2VfZ3JvdXBfbmFtZSkNCiAgKSArDQogIGdlb21faGxpbmUoDQogICAgeWludGVyY2VwdCA9IDEwMCwNCiAgICBsaW5ldHlwZSA9ICJkYXNoZWQiLA0KICAgIGNvbG9yID0gInJlZCIsDQogICAgc2l6ZSA9IDINCiAgKSArDQogIHRoZW1lX2xpbmVkcmF3KCkgKw0KICB0aGVtZSgNCiAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KA0KICAgICAgc2l6ZSA9IDMwLA0KICAgICAgaGp1c3QgPSAwLjUsDQogICAgICBmYWNlID0gImJvbGQiDQogICAgKSwNCiAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfYmxhbmsoKSwNCiAgICBsZWdlbmQudGl0bGUgPSBlbGVtZW50X2JsYW5rKCksDQogICAgbGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIsDQogICAgYXhpcy50ZXh0LnkgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDI4KSwNCiAgICBheGlzLnRpdGxlLnkgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDMwKSwNCiAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X3RleHQoc2l6ZSA9IDMwKSwNCiAgICBzdHJpcC50ZXh0LnggPSBlbGVtZW50X3RleHQoDQogICAgICBzaXplID0gMjUsDQogICAgICBmYWNlID0gImJvbGQiDQogICAgKSwNCiAgICBsZWdlbmQudGV4dCA9IGVsZW1lbnRfdGV4dChzaXplID0gMjUpLA0KICAgIGxlZ2VuZC5rZXkuaGVpZ2h0ID0gdW5pdCgxLjIsICJjbSIpLA0KICAgIGxlZ2VuZC5rZXkud2lkdGggPSB1bml0KDEuMiwgImNtIiksDQogICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdCgNCiAgICAgIGNvbG91ciA9ICJibGFjayIsDQogICAgICBzaXplID0gMS41DQogICAgKQ0KICApICsNCiAgbGFicygNCiAgICB0aXRsZSA9ICIgR2xvYmFsIGNvbnN1bXB0aW9uIGFjcm9zcyBhZ2VcbiAgb2YgZm9vZHMgYXNzb2NpYXRlZFxuIHdpdGggaGVhbHRoIHJpc2sgd2hlbiBvdmVyLWNvbnN1bWVkIiwNCiAgICB4ID0gIkFnZSIsDQogICAgeSA9ICJcblBlcmNlbnQgY29uc3VtcHRpb24gcmVsYXRpdmUgXG4gdG8gZ3VpZGVsaW5lcyINCiAgKSArDQogIGdnZm9yY2U6OmZhY2V0X3dyYXBfcGFnaW5hdGUofmZvb2QsDQogICAgbmNvbCA9IDIsDQogICAgc2NhbGVzID0gImZyZWUiDQogICkgKw0KICBzY2FsZV9maWxsX3ZpcmlkaXMoZGlzY3JldGUgPSBUUlVFKQ0KDQpPdmVyX2FnZQ0KYGBgDQoNCg0KSG1tbSB0aGVzZSBhcmUgYSBiaXQgZGlmZmljdWx0IHRvIHNlZSBmb3Igc29tZSBvZiB0aGUgZGlldGFyeSBmYWN0b3JzIGxpa2UgcmVkIG1lYXQgYmVjYXVzZSB0aGUgb3V0bGllcnMgYXJlIG1ha2luZyB0aGUgcmFuZ2UgbXVjaCBsYXJnZXIgdGhhbiB0aGUgYm94IHBsb3QgdGhlbXNlbHZlcy4gV2UgY2FuIGNhbGN1bGF0ZSB0aGUgdmFsdWVzIGZvciB0aGUgYm94IHBsb3RzIG91cnNlbHZlcyB0byBkZWFsIHdpdGggdGhpczoNCg0KYGBge3J9DQpjYWxjX3N0YXQgPC0gZnVuY3Rpb24oeCkgew0KICBjb2VmIDwtIDEuNQ0KICBuIDwtIHN1bSghaXMubmEoeCkpDQogICMgY2FsY3VsYXRlIHF1YW50aWxlcw0KICBzdGF0cyA8LSBxdWFudGlsZSh4LCBwcm9icyA9IGMoMC4xLCAwLjI1LCAwLjUsIDAuNzUsIDAuOSkpDQogIG5hbWVzKHN0YXRzKSA8LSBjKCJ5bWluIiwgImxvd2VyIiwgIm1pZGRsZSIsICJ1cHBlciIsICJ5bWF4IikNCiAgcmV0dXJuKHN0YXRzKQ0KfQ0KYGBgDQoNClRoZW4gd2UgY2FuIHVzZSBpdCBpbiBvdXIgcGxvdCBjb2RlOg0KDQpUaGlzIGlzIHRoYW5rcyB0byB0aGlzOg0KaHR0cHM6Ly9zdGFja292ZXJmbG93LmNvbS9xdWVzdGlvbnMvMjUxMjQ4OTUvbm8tb3V0bGllcnMtaW4tZ2dwbG90LWJveHBsb3Qtd2l0aC1mYWNldC13cmFwDQoNCmBgYHtyLCBmaWcud2lkdGggPSAxNiwgZmlnLmhlaWdodCA9IDE4fQ0KIyBtYXBfZGYoYWxsX2FnZV9kaWV0X2FuZF9ndWlkZWxpbmVzICU+JSBzZWxlY3QoUmVsYXRpdmVfUGVyY2VudCksIGNhbGNfc3RhdCkNCg0KT3Zlcl9hZ2UgPC0gYWxsX2FnZV9kaWV0X2FuZF9ndWlkZWxpbmVzICU+JQ0KICBmaWx0ZXIoZGlyZWN0aW9uID09ICJoaWdoIikgJT4lDQogIGdncGxvdChhZXMoDQogICAgeSA9IFJlbGF0aXZlX1BlcmNlbnQsDQogICAgeCA9IGFnZV9ncm91cF9uYW1lLA0KICAgIGZpbGwgPSBhZ2VfZ3JvdXBfbmFtZQ0KICApKSArDQogICMgaGVyZSB3ZSB3aWxsIHJlcGxhY2UgZ2VvbV9ib3hwbG90KCkgeWV0IHN0aWxsIGNyZWF0ZSBib3hwbG90cw0KICBzdGF0X3N1bW1hcnkoDQogICAgZnVuLmRhdGEgPSBjYWxjX3N0YXQsDQogICAgZ2VvbSA9ICJib3hwbG90IiwNCiAgICBvdXRsaWVyLnNoYXBlID0gTkEsDQogICAgY29sb3IgPSAiYmxhY2siLA0KICAgIGx3ZCA9IDEuMSwNCiAgICBhZXMoZmlsbCA9IGFnZV9ncm91cF9uYW1lKQ0KICApICsNCiAgZ2VvbV9obGluZSgNCiAgICB5aW50ZXJjZXB0ID0gMTAwLA0KICAgIGxpbmV0eXBlID0gImRhc2hlZCIsDQogICAgY29sb3IgPSAicmVkIiwNCiAgICBzaXplID0gMg0KICApICsNCiAgdGhlbWVfbGluZWRyYXcoKSArDQogIHRoZW1lKA0KICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KA0KICAgICAgYW5nbGUgPSA2MCwNCiAgICAgIGhqdXN0ID0gMQ0KICAgICksDQogICAgbGVnZW5kLnRpdGxlID0gZWxlbWVudF9ibGFuaygpDQogICkgKw0KICB0aGVtZV9saW5lZHJhdygpICsNCiAgdGhlbWUoDQogICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dCgNCiAgICAgIHNpemUgPSAzMCwNCiAgICAgIGhqdXN0ID0gMC41LA0KICAgICAgZmFjZSA9ICJib2xkIg0KICAgICksDQogICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X2JsYW5rKCksDQogICAgbGVnZW5kLnRpdGxlID0gZWxlbWVudF9ibGFuaygpLA0KICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iLA0KICAgIGF4aXMudGV4dC55ID0gZWxlbWVudF90ZXh0KHNpemUgPSAyOCksDQogICAgYXhpcy50aXRsZS55ID0gZWxlbWVudF90ZXh0KHNpemUgPSAzMCksDQogICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF90ZXh0KHNpemUgPSAzMCksDQogICAgc3RyaXAudGV4dC54ID0gZWxlbWVudF90ZXh0KA0KICAgICAgc2l6ZSA9IDI1LA0KICAgICAgZmFjZSA9ICJib2xkIg0KICAgICksDQogICAgbGVnZW5kLnRleHQgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDI1KSwNCiAgICBsZWdlbmQua2V5LmhlaWdodCA9IHVuaXQoMS4yLCAiY20iKSwNCiAgICBsZWdlbmQua2V5LndpZHRoID0gdW5pdCgxLjIsICJjbSIpLA0KICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoDQogICAgICBjb2xvdXIgPSAiYmxhY2siLA0KICAgICAgc2l6ZSA9IDEuNQ0KICAgICkNCiAgKSArDQogIGxhYnMoDQogICAgdGl0bGUgPSAiIEdsb2JhbCBjb25zdW1wdGlvbiBhY3Jvc3MgYWdlXG4gIG9mIGZvb2RzIGFzc29jaWF0ZWRcbiB3aXRoIGhlYWx0aCByaXNrIHdoZW4gb3Zlci1jb25zdW1lZCIsDQogICAgeCA9ICJBZ2UiLA0KICAgIHkgPSAiXG5QZXJjZW50IGNvbnN1bXB0aW9uIHJlbGF0aXZlIFxuIHRvIGd1aWRlbGluZXMiDQogICkgKw0KICBndWlkZXMoZmlsbCA9IGd1aWRlX2xlZ2VuZCgNCiAgICBucm93ID0gNSwNCiAgICBieXJvdyA9IFRSVUUNCiAgKSkgKw0KICBmYWNldF93cmFwX3BhZ2luYXRlKH5mb29kLA0KICAgIG5jb2wgPSAyLA0KICAgIHNjYWxlcyA9ICJmcmVlIg0KICApICsNCiAgc2NhbGVfZmlsbF92aXJpZGlzKGRpc2NyZXRlID0gVFJVRSkNCg0KT3Zlcl9hZ2UNCmBgYA0KDQojIyMjIFVuZGVyLWNvbnN1bWVkIGJ5IEFnZSBHbG9iYWwgVHJlbmRzDQoNCmBgYHtyLCBmaWcud2lkdGg9MTYsIGZpZy5oZWlnaHQ9MTh9DQpVbmRlcl9hZ2UgPC0gYWxsX2FnZV9kaWV0X2FuZF9ndWlkZWxpbmVzICU+JQ0KICBmaWx0ZXIoZGlyZWN0aW9uID09ICJsb3ciKSAlPiUNCiAgZ2dwbG90KGFlcygNCiAgICB5ID0gUmVsYXRpdmVfUGVyY2VudCwNCiAgICB4ID0gYWdlX2dyb3VwX25hbWUsDQogICAgZmlsbCA9IGFnZV9ncm91cF9uYW1lDQogICkpICsNCiAgIyBoZXJlIHdlIHdpbGwgcmVwbGFjZSBnZW9tX2JveHBsb3QoKQ0KICBzdGF0X3N1bW1hcnkoDQogICAgZnVuLmRhdGEgPSBjYWxjX3N0YXQsDQogICAgZ2VvbSA9ICJib3hwbG90IiwNCiAgICBvdXRsaWVyLnNoYXBlID0gTkEsDQogICAgY29sb3IgPSAiYmxhY2siLA0KICAgIGx3ZCA9IDEuMSwNCiAgICBhZXMoZmlsbCA9IGFnZV9ncm91cF9uYW1lKQ0KICApICsNCiAgZ2VvbV9obGluZSgNCiAgICB5aW50ZXJjZXB0ID0gMTAwLA0KICAgIGxpbmV0eXBlID0gImRhc2hlZCIsDQogICAgY29sb3IgPSAicmVkIiwNCiAgICBzaXplID0gMg0KICApICsNCiAgdGhlbWVfbGluZWRyYXcoKSArDQogIHRoZW1lKA0KICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KA0KICAgICAgYW5nbGUgPSA2MCwNCiAgICAgIGhqdXN0ID0gMQ0KICAgICksDQogICAgbGVnZW5kLnRpdGxlID0gZWxlbWVudF9ibGFuaygpLA0KICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iDQogICkgKw0KICBmYWNldF93cmFwX3BhZ2luYXRlKH5mb29kLCBucm93ID0gNSwgc2NhbGVzID0gImZyZWUiKSArDQogIHRoZW1lX2xpbmVkcmF3KCkgKw0KICB0aGVtZSgNCiAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KA0KICAgICAgc2l6ZSA9IDMwLA0KICAgICAgaGp1c3QgPSAwLjUsDQogICAgICBmYWNlID0gImJvbGQiDQogICAgKSwNCiAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfYmxhbmsoKSwNCiAgICBsZWdlbmQudGl0bGUgPSBlbGVtZW50X2JsYW5rKCksDQogICAgbGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiLA0KICAgIGF4aXMudGV4dC55ID0gZWxlbWVudF90ZXh0KHNpemUgPSAyOCksDQogICAgYXhpcy50aXRsZS55ID0gZWxlbWVudF90ZXh0KHNpemUgPSAzMCksDQogICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF90ZXh0KHNpemUgPSAyNSksDQogICAgc3RyaXAudGV4dC54ID0gZWxlbWVudF90ZXh0KA0KICAgICAgc2l6ZSA9IDI1LA0KICAgICAgZmFjZSA9ICJib2xkIg0KICAgICksDQogICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdCgNCiAgICAgIGNvbG91ciA9ICJibGFjayIsDQogICAgICBzaXplID0gMS41DQogICAgKSwNCiAgICBheGlzLnRpY2tzLnggPSBlbGVtZW50X2JsYW5rKCkNCiAgKSArDQogIGxhYnMoDQogICAgdGl0bGUgPSAiXG4gR2xvYmFsIGNvbnN1bXB0aW9uIGFjcm9zcyBhZ2VcbiAgb2YgZm9vZHMgYXNzb2NpYXRlZFxuIHdpdGggaGVhbHRoIHJpc2sgd2hlbiB1bmRlci1jb25zdW1lZCIsDQogICAgeCA9ICIiLA0KICAgIHkgPSAiXG5QZXJjZW50IGNvbnN1bXB0aW9uIHJlbGF0aXZlIFxuIHRvIGd1aWRlbGluZXMiDQogICkgKw0KICBzY2FsZV9maWxsX3ZpcmlkaXMoZGlzY3JldGUgPSBUUlVFKQ0KDQpVbmRlcl9hZ2UNCmBgYA0KDQoNCg0KDQpOb3cgbGV0J3MgcHV0IGFsbCB0aGUgcGxvdHMgdG9nZXRoZXI6DQoNCmBgYHtyLCBmaWcuaGVpZ2h0PTMzLCBmaWcud2lkdGggPSAzNH0NCmNvd3Bsb3Q6OnBsb3RfZ3JpZChPdmVyLA0KICBPdmVyX2FnZSwNCiAgVW5kZXIsDQogIFVuZGVyX2FnZSwNCiAgbmNvbCA9IDIsDQogIHJlbF93aWR0aHMgPSBjKDIuNSwgMSkNCikNCmBgYA0KDQpgYGB7ciwgaW5jbHVkZSA9IEZBTFNFLCBlY2hvID0gRkFMU0V9DQpwbmcoDQogIGZpbGVuYW1lID0gaGVyZTo6aGVyZSgiaW1nIiwgIm1haW5wbG90LnBuZyIpLA0KICByZXMgPSAzMDAsIHdpZHRoID0gMzQsIGhlaWdodCA9IDMwLCB1bml0cyA9ICJpbiINCikNCmNvd3Bsb3Q6OnBsb3RfZ3JpZChPdmVyLA0KICBPdmVyX2FnZSwNCiAgVW5kZXIsDQogIFVuZGVyX2FnZSwNCiAgbmNvbCA9IDIsDQogIHJlbF93aWR0aHMgPSBjKDIuNSwgMSkNCikgKyB0aGVtZShwbG90Lm1hcmdpbiA9IHVuaXQoYygxLCAxLCAxLCAxKSwgImNtIikpDQpkZXYub2ZmKCkNCmBgYA0KDQoNCiMjICoqU3VtbWFyeSoqDQoqKioNCg0KIyMjICoqU3lub3BzaXMqKg0KKioqDQoNCldlIGhhdmUgZXZhbHVhdGVkIGF2ZXJhZ2UgY29uc3VtcHRpb24gZXN0aW1hdGVzIG9mIDE1IGRpZXRhcnkgZmFjdG9ycyB3aXRoIHByb2JhYmx5IG5vbi1jb21tdW5pY2FibGUgZGlzZWFzZSAoTkNEKSByaXNrIGZyb20gMTk1IGRpZmZlcmVudCBjb3VudHJpZXMgYXJvdW5kIHRoZSB3b3JsZC4gVG8gZG8gc28gd2UgaW1wb3J0ZWQgZGF0YSBmcm9tIGEgUERGIHVzaW5nIHRoZSBgcGRmdG9vbHNgIHBhY2thZ2UsIGFzIHdlbGwgYXMgZGF0YSBmcm9tIHR3byBDU1YgZmlsZXMgdXNpbmcgYHJlYWRyYC4gV2UgdXNlZCBgdGlkeXZlcnNlYCBwYWNrYWdlcyBzdWNoIGFzIGBkcGx5cmAsIGBzdHJpbmdyYCwgYW5kIGB0aWR5YCB0byBjbGVhbiBhbmQgam9pbiB0aGUgZGF0YSBmcm9tIHRoZSBQREYgd2l0aCB0aGUgQ1NWIGZpbGVzLiANCg0KV2UgbGVhcm5lZCB0aGF0IHJlZ3Jlc3Npb24gaXMgYSBwb3dlcmZ1bCBhbmQgZmxleGlibGUgc3RhdGlzdGljYWwgdG9vbCB0aGF0IHNpbXBsaWZpZXMgb3IgZXN0aW1hdGVzIHRoZSByZWxhdGlvbnNoaXBzIGJldHdlZW4gdmFyaWFibGVzIHVzaW5nIGEgbWF0aGVtYXRpY2FsIG1vZGVsLiBXZSBsZWFybmVkIGFib3V0IHRoZSB1dGlsaXR5IG9mIHJlZ3Jlc3Npb24gdGVjaG5pcXVlcyB0byBjb21wYXJlIGdyb3VwcywgbG9vayBmb3IgYXNzb2NpYXRpb25zIGJldHdlZW4gdmFyaWFibGVzLCBhbmQgcHJlZGljdCBvdXRjb21lcyBiYXNlZCBvbiBtdWx0aXBsZSBwcmVkaWN0b3Igb3IgZXhwbGFuYXRvcnkgdmFyaWFibGVzLiBXZSB0aGVuIGNvbXBhcmVkIHRoaXMgdG8gb3RoZXIgcG9wdWxhciB0ZXN0cyBsaWtlIHRoZSAkdCQtdGVzdCBhbmQgdGhlIEFOT1ZBLiBXZSBsZWFybmVkIHRoYXQgdGhlc2UgdGVzdHMgYXJlIGFjdHVhbGx5IGVxdWl2YWxlbnQgdG8gc3BlY2lhbGl6ZWQgdHlwZXMgb2YgcmVncmVzc2lvbnMuDQoNCk91ciBzdGF0aXN0aWNhbCBhbmFseXNpcyBmb2N1c2VkIG9uIGV2YWx1YXRpbmcgZGlmZmVyZW5jZXMgaW4gdGhlIGNvbnN1bXB0aW9uIG9mIHJlZCBtZWF0IGFyb3VuZCB0aGUgd29ybGQgYmV0d2VlbiBmZW1hbGVzIGFuZCBtYWxlcyBhbmQgYWNyb3NzIGRpZmZlcmVudCBhZ2UgZ3JvdXBzLiBGaXJzdCB3ZSBsb29rZWQgYXQgdGhlIGFzc3VtcHRpb25zIG9mIFskdCQtdGVzdHNdKGh0dHBzOi8vc3RhdHRyZWsuY29tL3N0YXRpc3RpY3MvZGljdGlvbmFyeS5hc3B4P2RlZmluaXRpb249dHdvLXNhbXBsZSUyMCR0JC10ZXN0KXt0YXJnZXQ9Il9ibGFuayJ9IGFuZCByZWdyZXNzaW9ucywgYW5kIGRldGVybWluZWQgdGhhdCB0aGUgcmF0ZSBvZiByZWQgbWVhdCBjb25zdW1wdGlvbiByZWxhdGl2ZSB0byB0aGUgb3B0aW1hbCBndWlkZWxpbmUtc3VnZ2VzdGVkIGFtb3VudCB3YXMgcmlnaHQgc2tld2VkLiBXZSBsZWFybmVkIHRoYXQgd2UgY291bGQgdHJhbnNmb3JtIHRoZSBkYXRhIGJ5IHRha2luZyB0aGUgbG9nIG9mIHRoZXNlIHZhbHVlcyB0byBhY2hpZXZlIG1vcmUgbm9ybWFsbHkgZGlzdHJpYnV0ZWQgZGF0YS4gVG8gY29tcGFyZSBtYWxlcyBhbmQgZmVtYWxlcyB3ZSB1c2VkIGEgJHQkLXRlc3QgYW5kIGxlYXJuZWQgdGhhdCBhICR0JC10ZXN0IGlzIGEgc3BlY2lhbGl6ZWQgZm9ybSBvZiBhIGxpbmVhciByZWdyZXNzaW9uLiBUbyBjb21wYXJlIHRoZSAxNSBkaWZmZXJlbnQgYWdlIGdyb3VwcyB3ZSB1c2VkIGFuIEFOT1ZBIGFuZCBsZWFybmVkIHRoYXQgQU5PVkEgaXMgYWxzbyBhIHNwZWNpYWxpemVkIGZvcm0gb2YgbGluZWFyIHJlZ3Jlc3Npb24uIFdlIGV4YW1pbmVkIGhvdyB3ZSBvYnRhaW5lZCB0aGUgc2FtZSByZXN1bHRzIHVzaW5nIGVpdGhlciBzdGF0aXN0aWNhbCB0ZXN0LiBUaGlzIHdhcyBhbHNvIHRoZSBjYXNlIGlmIHdlIGxvb2tlZCBhdCB0aGUgZWZmZWN0IG9mIGdlbmRlciBhbmQgY29udHJvbGxlZCBmb3IgdGhlIHBhaXJlZCBjb3VudHJ5IHN0cnVjdHVyZSBpbiB0aGUgZGF0YSBieSBlaXRoZXIgaW5jbHVkaW5nIGBsb2NhdGlvbl9uYW1lYCBpbiB0aGUgbW9kZWwgYXMgYW5vdGhlciB0ZXJtIG9yIGJ5IHVzaW5nIGEgbWl4ZWQgZWZmZWN0cyBtb2RlbCB0byBjb250cm9sIGZvciB0aGlzIHN0cnVjdHVyZSBhcyBhIHJhbmRvbSBlZmZlY3QgYnV0IG5vdCBzcGVjaWZpY2FsbHkgdGVzdCBmb3IgdGhlIGluZmx1ZW5jZSBvZiBgbG9jYXRpb25fbmFtZWAgb24gcmVkIG1lYXQgY29uc3VtcHRpb24gZXN0aW1hdGVzLiBXZSBsZWFybmVkIHRoYXQgZml4ZWQgZWZmZWN0cyBhcmUgdGhvc2UgdGhhdCB3ZSB3aXNoIHRvIGV2YWx1YXRlLCB3aGlsZSByYW5kb20gZWZmZWN0cyBhcmUgdGhvc2UgdGhhdCBtYXkgaW5mbHVlbmNlIHRoZSByZWxhdGlvbnNoaXBzIG9mIG91ciB2YXJpYWJsZXMgb2YgaW50ZXJlc3QgYnV0IHRoYXQgd2UgZG8gbm90IHdpc2ggdG8gYWN0aXZlbHkgZXZhbHVhdGUuIFVzaW5nIHRoZXNlIHRlc3RzIGFuZCBtb2RlbHMsIHdlIGRldGVybWluZWQgdGhhdCBtYWxlcyBjb25zdW1lIG1vcmUgcmVkIG1lYXQgdGhhbiBmZW1hbGVzIG9uIGF2ZXJhZ2UgYXJvdW5kIHRoZSB3b3JsZC4gDQoNCk91ciBBTk9WQSBhbmFseXNpcyBvZiBhZ2UgZGV0ZXJtaW5lZCB0aGF0IGluZGVlZCB0aGVyZSBpcyBhdCBsZWFzdCBvbmUgYWdlIGdyb3VwIHRoYXQgY29uc3VtZWQgYSBzaWduaWZpY2FudGx5IGRpZmZlcmVudCBhbW91bnQgb2YgcmVkIG1lYXQgY29tcGFyZWQgdG8gdGhlIG90aGVyIGFnZSBncm91cHMsIGFuZCB0aGlzIHdhcyBzdGlsbCB0aGUgY2FzZSB3aGVuIHdlIGNvbnRyb2xsZWQgZm9yIGBsb2NhdGlvbl9uYW1lYC4gSG93ZXZlciwgd2UgbGVhcm5lZCB0aGF0IHRoZSBBTk9WQSBkb2VzIG5vdCBwcm92aWRlIGluZm9ybWF0aW9uIGFib3V0IHdoaWNoIGFnZSBncm91cHMgYXJlIGRpZmZlcmVudC4gV2UgbGVhcm5lZCBob3cgdGhlIHJlZ3Jlc3Npb24gY291bGQgcHJvdmlkZSBzb21lIHF1YW50aWZpY2F0aW9uIG9mIHRoZSBlZmZlY3Qgb2Ygc3BlY2lmaWMgYWdlIGdyb3VwcyByZWxhdGl2ZSB0byB0aGUgcmVmZXJlbmNlIGFnZSBncm91cC4gRnVydGhlcm1vcmUsIG91ciBkYXRhIHZpc3VhbGl6YXRpb25zIGFsbG93ZWQgdXMgdG8gZGV0ZXJtaW5lIHRoYXQgaW4gZ2VuZXJhbCByZWQgbWVhdCBjb25zdW1wdGlvbiBhcHBlYXJzIHRvIGJlIGhpZ2hlciBpbiB0aGUgeW91bmdlciBhZ2UgZ3JvdXBzIHJlbGF0aXZlIHRvIHRoZSBvbGRlciBhZ2UgZ3JvdXBzLiANCg0KRmluYWxseSwgd2UgYWxzbyBsb29rZWQgYXQgZGlmZmVyZW5jZXMgaW4gcmVkIG1lYXQgY29uc3VtcHRpb24gYmV0d2VlbiB0aGUgZGlmZmVyZW50IGNvdW50cmllcyBhbmQgc2F3IGluIG91ciBBTk9WQSBhbmFseXNpcyBhbmQgb3VyIHJlZ3Jlc3Npb24gYW5hbHlzaXMgdGhhdCB0aGVyZSB3ZXJlIHNpZ25pZmljYW50IGRpZmZlcmVuY2VzLiBXZSB3ZXJlIGFibGUgdG8gdXNlIGEgcmVncmVzc2lvbiB0aGF0IGluY2x1ZGVkIGBzZXhgLCBgYWdlX2dyb3VwX25hbWVgLCBhbmQgYGxvY2F0aW9uX25hbWVgIHRvIGV2YWx1YXRlIHRoZSBpbmZsdWVuY2Ugb2YgZWFjaCBvZiB0aGUgdGhyZWUgZGVtb2dyYXBoaWMgZmFjdG9ycyBvbiBjb25zdW1wdGlvbiB3aGlsZSBjb250cm9sbGluZyBvciBhY2NvdW50aW5nIGZvciB0aGUgb3RoZXIgdHdvLiBPdXIgcmVzdWx0cyBkZW1vbnN0cmF0ZWQgdGhhdCBhbGwgdGhyZWUgaW5mbHVlbmNlZCBvciB3ZXJlIGFzc29jaWF0ZWQgd2l0aCByZWQgbWVhdCBjb25zdW1wdGlvbi4NCg0KSW4gcHJlZm9ybWluZyBvdXIgc3RhdGlzdGljYWwgYW5hbHlzZXMgd2UgbGVhcm5lZCBhYm91dCB0aGUgYXNzdW1wdGlvbnMgb2YgdGhlICR0JC10ZXN0LCByZWdyZXNzaW9uLCBhbmQgdGhlIEFOT1ZBLiBXZSBhbHNvIGxlYXJuZWQgYWJvdXQgaW1wb3J0YW50IG1ldGhvZHMgdG8gdGVzdHMgdGhlc2UgYXNzdW1wdGlvbnMuDQoNClVzaW5nIHRoZSBgZ2dwbG90MmAgcGFja2FnZSB3ZSB3ZXJlIGFibGUgdG8gdmlzdWFsaXplIHRyZW5kcyBpbiB0aGUgZGF0YSBhbmQgdG8gY29tcGFyZSBjb25zdW1wdGlvbiBvZiB0aGVzZSBkaWV0YXJ5IGZhY3RvcnMgaW4gdGhlIFVTIHdpdGggdGhhdCBvZiB0aGUgb3RoZXIgY291bnRyaWVzLg0KDQpXZSBzZWUgdGhhdCB0aGUgcG9wdWxhdGlvbnMgaW4gbWFueSBjb3VudHJpZXMgYXJlIG92ZXItY29uc3VtaW5nIGZvb2RzIHRoYXQgYXJlIGFzc29jaWF0ZWQgd2l0aCBoZWFsdGggcmlzayB3aGVuIG92ZXItY29uc3VtZWQuIEluIHBhcnRpY3VsYXIgcHJvY2Vzc2VkIG1lYXQgYW5kIHN1Z2FyLXN3ZWV0ZW5lZCBiZXZlcmFnZXMgYXBwZWFyIHRvIGJlIHRoZSBtb3N0IG92ZXIgY29uc3VtZWQuIEltcG9ydGFudGx5IGJvdGggb2YgdGhlc2UgYXBwZWFyIHRvIGJlIGNvbnN1bWVkIGF0IGhpZ2hlciBxdWFudGl0aWVzIGJ5IG1hbGVzIGFuZCB5b3VuZ2VyIGFkdWx0cy4gUGVvcGxlIGluIHRoZSBVUyAgYXBwZWFyIHRvIGNvbnN1bWUgZmV3ZXIgc3VnYXItc3dlZXRlbmVkIGJldmVyYWdlcyB0aGFuIG90aGVyIGNvdW50cmllcywgaG93ZXZlciwgcGVvcGxlIGFyZSBzdGlsbCBvdmVyLWNvbnN1bWluZy4gUHJvY2Vzc2VkIG1lYXQgaG93ZXZlciBhcHBlYXJzIHRvIGJlIGVzcGVjaWFsbHkgYmFkIGluIHRoZSBVUy4gSW4gdGVybXMgb2YgZm9vZCB0aGF0IG5lZWQgdG8gYmUgY29uc3VtZWQgaW4gYWRlcXVhdGUgYW1vdW50cyB0byBvdmVyY29tZSBoZWFsdGggcmlzaywgbmVhcmx5IGFsbCBjb3VudHJpZXMgZm9yIGFsbCBmYWN0b3JzIGFyZSBub3QgcmVhY2hpbmcgZ3VpZGVsaW5lIGxldmVscy4gSG93ZXZlciwgdGhlcmUgYXJlIHNvbWUgY291bnRyaWVzIGNvbnN1bWluZyBtb3JlIHRoYW4gYWRlcXVhdGUgYW1vdW50cyBvZiBsZWd1bWVzLCB2ZWdldGFibGVzLCBmcnVpdHMgYW5kIGZpYmVyLiBQZW9wbGUgaW4gdGhlIFVTIGFwcGVhciB0byBlYXQgbW9yZSBtaWxrIHByb2R1Y3RzIGFuZCBjb25zdW1lIG1vcmUgb21lZ2EtMyBmYXR0eSBhY2lkcyBhbmQgY2FsY2l1bSByaWNoIGZvb2RzIHRoYW4gb3RoZXIgY291bnRyaWVzLiBBbGwgY291bnRyaWVzIGluY2x1ZGluZyB0aGUgVVMgY29uc3VtZSB2ZXJ5IGxvdyBsZXZlbHMgb2YgcG9seXVuc2F0dXJhdGVkIGZhdHR5IGFjaWRzLiBUaGVzZSBbcG9seXVuc2F0dXJhdGVkIGZhdHR5IGFjaWRzXShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9Qb2x5dW5zYXR1cmF0ZWRfZmF0KSBhcmUgYWJ1bmRhbnQgaW4gc2VlZHMsIG51dHMgYW5kIGF2b2NhZG9zLCBhcyB3ZWxsIGFzIGZpc2guIExpa2VseSB0aGUgbG93IGxldmVsIG9mIGNvbnN1bXB0aW9uIG9mIG51dHMgYW5kIHNlZWRzIGNvbnRyaWJ1dGVzIHRvIHRoZXNlIGxvdyBwb2x5dW5zYXR1cmF0ZWQgZmF0dHkgYWNpZCBlc3RpbWF0ZXMuIFRoZSBzdXBwbGVtZW50YXJ5IHRhYmxlIGluY2x1ZGVkIGluIHRoZSBhcnRpY2xlIHN1Z2dlc3RzIHRoYXQgcG9vciBjb25zdW1wdGlvbiBvZiBwb2x5dW5zYXR1cmF0ZWQgZmF0dHkgYWNpZHMgaXMgYXNzb2NpYXRlZCB3aXRoIGlzY2hlbWljIGhlYXJ0IGRpc2Vhc2UuIFRoZSBhcnRpY2xlIHRha2VzIHRoaXMgZGF0YSBmdXJ0aGVyIHRvIGV2YWx1YXRlIHRoZSBhc3NvY2lhdGlvbiBvZiBjb25zdW1wdGlvbiBsZXZlbHMgb2YgdGhlc2UgZm9vZHMgd2l0aCBtb3J0YWxpdHkuDQoNCkFuYWx5c2VzIGxpa2UgdGhlIG9uZSBpbiBvdXIgY2FzZSBzdHVkeSBhcmUgaW1wb3J0YW50IGZvciBkZWZpbmluZyB3aGljaCBncm91cHMgY291bGQgYmVuZWZpdCB0aGUgbW9zdCBmcm9tIGludGVydmVudGlvbnMsIGVkdWNhdGlvbiwgYW5kIHBvbGljeSBjaGFuZ2VzIHdoZW4gYXR0ZW1wdGluZyB0byBtaXRpZ2F0ZSBwdWJsaWMgaGVhbHRoIGNoYWxsZW5nZXMuIFlvdSBjYW4gc2VlIGluIHRoZSBbYXJ0aWNsZV0oaHR0cHM6Ly93d3cudGhlbGFuY2V0LmNvbS9hY3Rpb24vc2hvd1BkZj9waWk9UzAxNDAtNjczNiUyODE5JTI5MzAwNDEtOCl7dGFyZ2V0PSJfYmxhbmsifSBob3dldmVyIHRoYXQgbWFueSBhZGRpdGlvbmFsIGNvbnNpZGVyYXRpb25zIHdvdWxkIGJlIGludm9sdmVkIHRvIHBlcmZvcm0gYSBtb3JlIHRob3JvdWdoIGFuYWx5c2lzIHRvIGFkZXF1YXRlbHkgdW5kZXJzdGFuZCB0aGUgZGF0YSBlbm91Z2ggdG8gcmVjb21tZW5kIHBvbGljeSBjaGFuZ2VzLg0KDQoNCiMjICoqU3VnZ2VzdGVkIEhvbWV3b3JrKioNCioqKg0KDQpTdHVkZW50cyBjYW4gZXZhbHVhdGUgY29uc3VtcHRpb24gZXN0aW1hdGVzIG9mIGFub3RoZXIgZGlldGFyeSBmYWN0b3IgYmVzaWRlcyByZWQgbWVhdC4NCg0KDQojIyAqKkFkZGl0aW9uYWwgSW5mb3JtYXRpb24qKg0KKioqIA0KDQojIyMgKipIZWxwZnVsIExpbmtzKioNCioqKg0KDQo8dT5UZXJtcyBhbmQgY29uY2VwdHMgY292ZXJlZDo8L3U+ICANCg0KW1RpZHl2ZXJzZV0oaHR0cHM6Ly93d3cudGlkeXZlcnNlLm9yZy8pe3RhcmdldD0iX2JsYW5rIn0gIA0KW1JTdHVkaW8gY2hlYXRzaGVldHNdKGh0dHBzOi8vcnN0dWRpby5jb20vcmVzb3VyY2VzL2NoZWF0c2hlZXRzLyl7dGFyZ2V0PSJfYmxhbmsifSAgDQpbSW50ZXJwdW5jdF0oaHR0cHM6Ly93d3cuc2hvcnR0dXRvcmlhbHMuY29tL21hYy1vcy1zcGVjaWFsLWNoYXJhY3RlcnMtc2hvcnRjdXRzL21pZGRsZS1kb3QuaHRtbCl7dGFyZ2V0PSJfYmxhbmsifSAgDQpbUmVndWxhciBleHByZXNzaW9uc10oaHR0cHM6Ly93d3cuci1ibG9nZ2Vycy5jb20vcmVndWxhci1leHByZXNzaW9ucy1ldmVyeS1yLXByb2dyYW1tZXItc2hvdWxkLWtub3cvKXt0YXJnZXQ9Il9ibGFuayJ9ICANCltJbmZlcmVuY2VdKGh0dHBzOi8vd3d3LmJyaXRhbm5pY2EuY29tL3NjaWVuY2UvaW5mZXJlbmNlLXN0YXRpc3RpY3Mpe3RhcmdldD0iX2JsYW5rIn0gIA0KW1JlZ3Jlc3Npb25dKGh0dHBzOi8vbGluZGVsb2V2LmdpdGh1Yi5pby90ZXN0cy1hcy1saW5lYXIvKXt0YXJnZXQ9Il9ibGFuayJ9ICANCltEaWZmZXJlbnQgdHlwZXMgb2YgcmVncmVzc2lvbl0oaHR0cHM6Ly93d3cuYW5hbHl0aWNzdmlkaHlhLmNvbS9ibG9nLzIwMTUvMDgvY29tcHJlaGVuc2l2ZS1ndWlkZS1yZWdyZXNzaW9uLyl7dGFyZ2V0PSJfYmxhbmsifSAgDQpbT3JkaW5hcnkgbGVhc3Qgc3F1YXJlcyBtZXRob2RdKGh0dHA6Ly9zZXRvc2EuaW8vZXYvb3JkaW5hcnktbGVhc3Qtc3F1YXJlcy1yZWdyZXNzaW9uLyl7dGFyZ2V0PSJfYmxhbmsifSAgDQpbUmVzaWR1YWxdKGh0dHBzOi8vd3d3LnN0YXRpc3RpY3Nob3d0by5kYXRhc2NpZW5jZWNlbnRyYWwuY29tL3Jlc2lkdWFsLyl7dGFyZ2V0PSJfYmxhbmsifSAgDQpbJHQkLXRlc3RzXShodHRwczovL3N0YXR0cmVrLmNvbS9zdGF0aXN0aWNzL2RpY3Rpb25hcnkuYXNweD9kZWZpbml0aW9uPXR3by1zYW1wbGUlMjAkdCQtdGVzdCl7dGFyZ2V0PSJfYmxhbmsifSAgDQpbQU5PVkFdKGh0dHA6Ly9vbmxpbmVzdGF0Ym9vay5jb20vMi9hbmFseXNpc19vZl92YXJpYW5jZS9pbnRyby5odG1sKXt0YXJnZXQ9Il9ibGFuayJ9ICANClskdCQtdGVzdHMgYW5kIEFOT1ZBIGFyZSBlcXVpdmFsZW50IHRvIHJlZ3Jlc3Npb25dKGh0dHBzOi8vc2NpZW50aWZpY2FsbHlzb3VuZC5vcmcvMjAxNy8wNi8wOC8kdCQtdGVzdC1hcy1saW5lYXItbW9kZWxzLXIvKXt0YXJnZXQ9Il9ibGFuayJ9IGFsc28gc2VlIFtoZXJlXShodHRwczovL3Rvd2FyZHNkYXRhc2NpZW5jZS5jb20vZXZlcnl0aGluZy1pcy1qdXN0LWEtcmVncmVzc2lvbi01YTNiZjIyYzQ1OWMpe3RhcmdldD0iX2JsYW5rIn0gYW5kIFtoZXJlXShodHRwczovL2xpbmRlbG9ldi5naXRodWIuaW8vdGVzdHMtYXMtbGluZWFyLyl7dGFyZ2V0PSJfYmxhbmsifSBhYm91dCBob3cgbWFueSBjb21tb25seSBrbm93biBzdGF0aXN0aWNhbCB0ZXN0cyBhcmUgc3BlY2lhbGl6ZWQgZm9ybXMgb2YgcmVncmVzc2lvbiAgDQpbTm9ybWFsIERpc3RyaWJ1dGlvbl0oaHR0cHM6Ly93d3cucGh5c2lvbG9neS5vcmcvZG9pL2Z1bGwvMTAuMTE1Mi9hZHZhbi4wMDA2NC4yMDE3KXt0YXJnZXQ9Il9ibGFuayJ9ICANCltRLVEgcGxvdF0oaHR0cDovL29ubGluZXN0YXRib29rLmNvbS8yL2FkdmFuY2VkX2dyYXBocy9xLXFfcGxvdHMuaHRtbCl7dGFyZ2V0PSJfYmxhbmsifSAgDQpbR3VpZGUgdG8gcmVzaWR1YWwgZGlhZ25vc3RpYyBwbG90c10oaHR0cHM6Ly9kYXRhLmxpYnJhcnkudmlyZ2luaWEuZWR1L2RpYWdub3N0aWMtcGxvdHMvKSBhbmQgW0V4YW1wbGVzXShodHRwOi8vZG9jcy5zdGF0d2luZy5jb20vaW50ZXJwcmV0aW5nLXJlc2lkdWFsLXBsb3RzLXRvLWltcHJvdmUteW91ci1yZWdyZXNzaW9uLyl7dGFyZ2V0PSJfYmxhbmsifSAgDQpbUmVzaWR1YWwgdnMgZml0dGVkIHBsb3RdKGh0dHBzOi8vb25saW5lLnN0YXQucHN1LmVkdS9zdGF0NDYyL25vZGUvMTE4Lyl7dGFyZ2V0PSJfYmxhbmsifSAgDQpbU2NhbGUtbG9jYXRpb24gcGxvdF0oaHR0cHM6Ly9ib29zdGVkbWwuY29tLzIwMTkvMDMvbGluZWFyLXJlZ3Jlc3Npb24tcGxvdHMtc2NhbGUtbG9jYXRpb24tcGxvdC5odG1sKXt0YXJnZXQ9Il9ibGFuayJ9ICANCltIb21vc2NlZGFzdGljaXR5IF0oaHR0cHM6Ly93d3cuc3RhdGlzdGljc3NvbHV0aW9ucy5jb20vaG9tb3NjZWRhc3RpY2l0eS8pe3RhcmdldD0iX2JsYW5rIn0gIA0KW0hldGVyb3NjZWRhc3RpY2l0eV0oaHR0cHM6Ly9zdGF0aXN0aWNzYnlqaW0uY29tL3JlZ3Jlc3Npb24vaGV0ZXJvc2NlZGFzdGljaXR5LXJlZ3Jlc3Npb24vKXt0YXJnZXQ9Il9ibGFuayJ9ICANCltJbnRlcnByZXRpbmcgYGxtKClgIG91dHB1dF0oaHR0cHM6Ly9mZWxpcGVyZWdvLmdpdGh1Yi5pby9ibG9nLzIwMTUvMTAvMjMvSW50ZXJwcmV0aW5nLU1vZGVsLU91dHB1dC1Jbi1SKXt0YXJnZXQ9Il9ibGFuayJ9ICANCltDb2VmZmljaWVudHNdKGh0dHBzOi8vd3d3LnRoZWFuYWx5c2lzZmFjdG9yLmNvbS9pbnRlcnByZXRpbmctcmVncmVzc2lvbi1jb2VmZmljaWVudHMvKXt0YXJnZXQ9Il9ibGFuayJ9ICANCltMaW5lYXIgbWl4ZWQgZWZmZWN0cyByZWdyZXNzaW9uXShodHRwczovL291cmNvZGluZ2NsdWIuZ2l0aHViLmlvL3R1dG9yaWFscy9taXhlZC1tb2RlbHMvKXt0YXJnZXQ9Il9ibGFuayJ9ICANCltTYXR0ZXJ0aHdhaXRlIGZvcm11bGFdKGh0dHBzOi8vd3d3LnN0YXRpc3RpY3Nob3d0by5kYXRhc2NpZW5jZWNlbnRyYWwuY29tL3NhdHRlcnRod2FpdGUtZm9ybXVsYS8pe3RhcmdldD0iX2JsYW5rIn0gIA0KW01vb2QncyBUd28tU2FtcGxlIFNjYWxlIFRlc3RdKGh0dHBzOi8vZmlsZXMuZXJpYy5lZC5nb3YvZnVsbHRleHQvRUQwNjU1NTkucGRmKXt0YXJnZXQ9Il9ibGFuayJ9ICAgDQpbU3RhbmRhcmQgZGV2aWF0aW9uXShodHRwczovL3d3dy5zdGF0c2RpcmVjdC5jb20vaGVscC9iYXNpY19kZXNjcmlwdGl2ZV9zdGF0aXN0aWNzL3N0YW5kYXJkX2RldmlhdGlvbi5odG0pe3RhcmdldD0iX2JsYW5rIn0gIA0KW0hvbW9nZW5laXR5IG9mIFZhcmlhbmNlcyBhc3N1bXB0aW9uXShodHRwczovL3VjLXIuZ2l0aHViLmlvL2Fzc3VtcHRpb25zX2hvbW9nZW5laXR5KXt0YXJnZXQ9Il9ibGFuayJ9ICAgDQpbcG9seXVuc2F0dXJhdGVkIGZhdHR5IGFjaWRzXShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9Qb2x5dW5zYXR1cmF0ZWRfZmF0KXt0YXJnZXQ9Il9ibGFuayJ9IA0KDQoNCjx1PlRlc3RzIG9mIEhvbW9nZW5laXR5IG9mIFZhcmlhbmNlIGZvciAzIG9yIG1vcmUgZ3JvdXBzOjwvdT4NCg0KW0JhcnRsZXR0J3MgdGVzdF0oaHR0cHM6Ly93d3cuaXRsLm5pc3QuZ292L2Rpdjg5OC9oYW5kYm9vay9lZGEvc2VjdGlvbjMvZWRhMzU3Lmh0bSl7dGFyZ2V0PSJfYmxhbmsifSAgDQpbRmxpZ25lci1LaWxsZWVuXShodHRwOi8vd2lraS5zdGF0LnVjbGEuZWR1L3NvY3IvaW5kZXgucGhwL0FQX1N0YXRpc3RpY3NfQ3VycmljdWx1bV8yMDA3X05vblBhcmFtX1ZhckluZGVwKXt0YXJnZXQ9Il9ibGFuayJ9ICANCltMZXZlbmUncyB0ZXN0XShodHRwczovL3d3dy5pdGwubmlzdC5nb3YvZGl2ODk4L2hhbmRib29rL2VkYS9zZWN0aW9uMy9lZGEzNWEuaHRtKXt0YXJnZXQ9Il9ibGFuayJ9ICANCiANCg0KPHU+T3RoZXIgaGVscGZ1bCBsaW5rczo8L3U+DQoNCltMb25nIGFuZCBXaWRlIERhdGEgRm9ybWF0c10oaHR0cHM6Ly9vcGVuY2FzZXN0dWRpZXMuZ2l0aHViLmlvL29jcy1oZWFsdGhleHBlbmRpdHVyZS9vY3MtaGVhbHRoZXhwZW5kaXR1cmUuaHRtbCl7dGFyZ2V0PSJfYmxhbmsifSAgICANCltEaXN0cmlidXRpb25zXShodHRwOi8vb25saW5lc3RhdGJvb2suY29tLzIvaW50cm9kdWN0aW9uL2Rpc3RyaWJ1dGlvbnMuaHRtbCl7dGFyZ2V0PSJfYmxhbmsifSANCltTa2V3ZWQgRGlzdHJpYnV0aW9uc10oaHR0cDovL29ubGluZXN0YXRib29rLmNvbS8yL2dsb3NzYXJ5L3NrZXcuaHRtbCl7dGFyZ2V0PSJfYmxhbmsifSANCltCaW1vZGFsIERpc3RyaWJ1dGlvbl0oaHR0cDovL29ubGluZXN0YXRib29rLmNvbS8yL2ludHJvZHVjdGlvbi9kaXN0cmlidXRpb25zLmh0bWwpe3RhcmdldD0iX2JsYW5rIn0gDQpbZ2dwbG90Ml0oaHR0cHM6Ly9vcGVuY2FzZXN0dWRpZXMuZ2l0aHViLmlvL29jcy1oZWFsdGhleHBlbmRpdHVyZS9vY3MtaGVhbHRoZXhwZW5kaXR1cmUuaHRtbCl7dGFyZ2V0PSJfYmxhbmsifSAgICANCltTaGFwaXJvLVdpbGsgVGVzdF0oaHR0cDovL3d3dy5zdGF0aXN0aWNzNHUuaW5mby9mdW5kc3RhdF9lbmcvZWVfc2hhcGlyb193aWxrX3Rlc3QuaHRtbCl7dGFyZ2V0PSJfYmxhbmsifSAgIA0KW1BhaXJlZCBEYXRhXShodHRwczovL3d3dy5uY2JpLm5sbS5uaWguZ292L3BtYy9hcnRpY2xlcy9QTUM1NTc5NDY1Lyl7dGFyZ2V0PSJfYmxhbmsifSAgDQpbV2VsY2gncyAkdCQtdGVzdF0oaHR0cHM6Ly93d3cuc3RhdGlzdGljc2hvd3RvLmRhdGFzY2llbmNlY2VudHJhbC5jb20vd2VsY2hzLXRlc3QtZm9yLXVuZXF1YWwtdmFyaWFuY2VzLyl7dGFyZ2V0PSJfYmxhbmsifSAgICANCltQYXJhbWV0cmljIGFuZCBOb25wYXJhbWV0cmljIE1ldGhvZHNdKGh0dHBzOi8vd3d3Lm1heW8uZWR1L3Jlc2VhcmNoL2RvY3VtZW50cy9wYXJhbWV0cmljLWFuZC1ub25wYXJhbWV0cmljLWRlbXlzdGlmeWluZy10aGUtdGVybXMvZG9jLTIwNDA4OTYwKXt0YXJnZXQ9Il9ibGFuayJ9ICAgDQpbVmFyaWFuY2VdKGh0dHBzOi8vc3RhdHRyZWsuY29tL3N0YXRpc3RpY3MvZGljdGlvbmFyeS5hc3B4P2RlZmluaXRpb249dmFyaWFuY2Upe3RhcmdldD0iX2JsYW5rIn0gIA0KW0JhbGFuY2VkIFN0dWR5IERlc2lnbl0oaHR0cHM6Ly93d3cuc3RhdGlzdGljc2hvd3RvLmRhdGFzY2llbmNlY2VudHJhbC5jb20vYmFsYW5jZWQtYW5kLXVuYmFsYW5jZWQtZGVzaWducy8pe3RhcmdldD0iX2JsYW5rIn0gIA0KW0luZGVwZW5kZW50IE9ic2VydmF0aW9uc10oaHR0cHM6Ly93d3cuc3RhdC5jbXUuZWR1L35jc2hhbGl6aS8zNi0yMjAvbGVjdHVyZS01LnBkZil7dGFyZ2V0PSJfYmxhbmsifSAgDQpbVHJhbnNmb3JtYXRpb25dKGh0dHBzOi8vd3d3LnN0YXRpc3RpY3Nob3d0by5kYXRhc2NpZW5jZWNlbnRyYWwuY29tL3RyYW5zZm9ybWF0aW9uLXN0YXRpc3RpY3MvKXt0YXJnZXQ9Il9ibGFuayJ9ICANCltQZXJtdXRhdGlvbi9SZXNhbXBsaW5nIE1ldGhvZHNdKGh0dHBzOi8vamh1LWFkdmRhdGFzY2kuZ2l0aHViLmlvLzIwMTkvbGVjdHVyZXMvMjEtcmVzYW1wbGluZy10ZWNobmlxdWVzLmh0bWwpe3RhcmdldD0iX2JsYW5rIn0gICANCltDZW50cmFsIExpbWl0IFRoZW9yZW1dKGh0dHBzOi8vd3d3LmFuYWx5dGljc3ZpZGh5YS5jb20vYmxvZy8yMDE5LzA1L3N0YXRpc3RpY3MtMTAxLWludHJvZHVjdGlvbi1jZW50cmFsLWxpbWl0LXRoZW9yZW0vKXt0YXJnZXQ9Il9ibGFuayJ9IA0KW1dpbGNveG9uIFNpZ25lZCBSYW5rIFRlc3RdKGh0dHA6Ly93d3cuYmlvc3RhdGhhbmRib29rLmNvbS93aWxjb3hvbnNpZ25lZHJhbmsuaHRtbCkgICANCltXaWxjb3hvbiBSYW5rIFN1bSBUZXN0XShodHRwOi8vc3Bod2ViLmJ1bWMuYnUuZWR1L290bHQvbXBoLW1vZHVsZXMvYnMvYnM3MDRfbm9ucGFyYW1ldHJpYy9CUzcwNF9Ob25wYXJhbWV0cmljNC5odG1sKXt0YXJnZXQ9Il9ibGFuayJ9ICANCltUd28tc2FtcGxlIEtvbG1vZ29yb3YtU21pcm5vdiBUZXN0XShodHRwczovL3d3dy5pdGwubmlzdC5nb3YvZGl2ODk4L3NvZnR3YXJlL2RhdGFwbG90L3JlZm1hbjEvYXV4aWxsYXIva3Myc2FtcC5odG0pe3RhcmdldD0iX2JsYW5rIn0gIA0KW1R5cGUgMSBFcnJvcl0oaHR0cHM6Ly93ZWIubWEudXRleGFzLmVkdS91c2Vycy9ta3Mvc3RhdG1pc3Rha2VzL2Vycm9ydHlwZXMuaHRtbCl7dGFyZ2V0PSJfYmxhbmsifSAgDQpbcC12YWx1ZV0oaHR0cHM6Ly90b3dhcmRzZGF0YXNjaWVuY2UuY29tL3AtdmFsdWVzLWV4cGxhaW5lZC1ieS1kYXRhLXNjaWVudGlzdC1mNDBhNzQ2Y2ZjOCl7dGFyZ2V0PSJfYmxhbmsifSAgDQpbTXVsdGlwbGUgVGVzdGluZ10oaHR0cHM6Ly93d3cuZ3Mud2FzaGluZ3Rvbi5lZHUvYWNhZGVtaWNzL2NvdXJzZXMvYWtleS81NjAwOC9sZWN0dXJlL2xlY3R1cmUxMC5wZGYpe3RhcmdldD0iX2JsYW5rIn0gICAgDQpbQm9uZmVycm9uaSBNZXRob2Qgb2YgTXVsdGlwbGUgVGVzdGluZyBDb3JyZWN0aW9uXShodHRwOi8vbWF0aHdvcmxkLndvbGZyYW0uY29tL0JvbmZlcnJvbmlDb3JyZWN0aW9uLmh0bWwpe3RhcmdldD0iX2JsYW5rIn0NCg0KPHU+UGFja2FnZXMgdXNlZCBpbiB0aGlzIGNhc2Ugc3R1ZHk6IDwvdT4NCg0KIFBhY2thZ2UgICB8IFVzZSBpbiB0aGlzIGNhc2Ugc3R1ZHkgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICANCi0tLS0tLS0tLS0gfC0tLS0tLS0tLS0tLS0NCltoZXJlXShodHRwczovL2dpdGh1Yi5jb20vamVubnliYy9oZXJlX2hlcmUpe3RhcmdldD0iX2JsYW5rIn0gICAgICAgfCB0byBlYXNpbHkgbG9hZCBhbmQgc2F2ZSBkYXRhICANCltyZWFkcl0oaHR0cHM6Ly9yZWFkci50aWR5dmVyc2Uub3JnLyl7dGFyZ2V0PSJfYmxhbmsifSAgICAgIHwgdG8gaW1wb3J0IHRoZSBDU1YgZmlsZSBkYXRhICANCltkcGx5cl0oaHR0cHM6Ly9kcGx5ci50aWR5dmVyc2Uub3JnLyl7dGFyZ2V0PSJfYmxhbmsifSAgICAgIHwgdG8gYXJyYW5nZS9maWx0ZXIvc2VsZWN0L2NvbXBhcmUgc3BlY2lmaWMgc3Vic2V0cyBvZiB0aGUgZGF0YSAgDQpbc2tpbXJdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9za2ltci9pbmRleC5odG1sKXt0YXJnZXQ9Il9ibGFuayJ9ICAgICAgfCB0byBnZXQgYW4gb3ZlcnZpZXcgb2YgZGF0YSAgICANCltwZGZ0b29sc10oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL3BkZnRvb2xzL3BkZnRvb2xzLnBkZil7dGFyZ2V0PSJfYmxhbmsifSAgIHwgdG8gcmVhZCBhIFBERiBpbnRvIFIgICANCltzdHJpbmdyXShodHRwczovL3N0cmluZ3IudGlkeXZlcnNlLm9yZy9hcnRpY2xlcy9zdHJpbmdyLmh0bWwpe3RhcmdldD0iX2JsYW5rIn0gICAgfCB0byBtYW5pcHVsYXRlIHRoZSB0ZXh0IHdpdGhpbiB0aGUgUERGIG9mIHRoZSBkYXRhICAgDQpbbWFncml0dHJdKGh0dHBzOi8vbWFncml0dHIudGlkeXZlcnNlLm9yZy9hcnRpY2xlcy9tYWdyaXR0ci5odG1sKXt0YXJnZXQ9Il9ibGFuayJ9ICAgfCB0byB1c2UgdGhlIGAlPD4lYCBwaXBpbmcgb3BlcmF0b3IgIA0KW3B1cnJyXShodHRwczovL3B1cnJyLnRpZHl2ZXJzZS5vcmcvKXt0YXJnZXQ9Il9ibGFuayJ9ICAgICAgfCB0byBwZXJmb3JtIGZ1bmN0aW9ucyBvbiBhbGwgY29sdW1ucyBvZiBhIHRpYmJsZSAgIA0KW3RpYmJsZV0oaHR0cHM6Ly90aWJibGUudGlkeXZlcnNlLm9yZy8pe3RhcmdldD0iX2JsYW5rIn0gICAgIHwgdG8gY3JlYXRlIGRhdGEgb2JqZWN0cyB0aGF0IHdlIGNhbiBtYW5pcHVsYXRlIHdpdGggIGRwbHlyL3N0cmluZ3IvdGlkeXIvcHVycnIgIA0KW3RpZHlyXShodHRwczovL3RpZHlyLnRpZHl2ZXJzZS5vcmcvKXt0YXJnZXQ9Il9ibGFuayJ9ICAgICAgfCB0byBzZXBhcmF0ZSBkYXRhIHdpdGhpbiBhIGNvbHVtbiBpbnRvIG11bHRpcGxlIGNvbHVtbnMgDQpbZ2dwbG90Ml0oaHR0cHM6Ly9nZ3Bsb3QyLnRpZHl2ZXJzZS5vcmcvKXt0YXJnZXQ9Il9ibGFuayJ9ICAgIHwgdG8gbWFrZSB2aXN1YWxpemF0aW9ucyB3aXRoIG11bHRpcGxlIGxheWVycyAgDQpbZ2dwdWJyXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvZ2dwdWJyL2luZGV4Lmh0bWwpe3RhcmdldD0iX2JsYW5rIn0gICAgfCB0byBlYXNpbHkgYWRkIHJlZ3Jlc3Npb24gbGluZSBlcXVhdGlvbnMgdG8gcGxvdHMgIA0KW2ZvcmNhdHNdKGh0dHBzOi8vZm9yY2F0cy50aWR5dmVyc2Uub3JnLyl7dGFyZ2V0PSJfYmxhbmsifSAgICB8IHRvIGNoYW5nZSBkZXRhaWxzIGFib3V0IGZhY3RvcnMgKGNhdGVnb3JpY2FsIHZhcmlhYmxlcykgIA0KW2xtZXJUZXN0XShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvbG1lclRlc3QvbG1lclRlc3QucGRmKXwgdG8gcGVyZm9ybSBsaW5lYXIgbWl4ZWQgbW9kZWwgdGVzdGluZyAgIA0KW2Nhcl0oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL2Nhci9jYXIucGRmKXwgdG8gcGVyZm9ybSBMZXZlbmUncyBUZXN0IG9mIEhvbW9nZW5laXR5IG9mIFZhcmlhbmNlcyAgIA0KW2dnaXJhcGhdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9nZ2lyYXBoL2luZGV4Lmh0bWwpfCB0byBtYWtlIHBsb3RzIGludGVyYWN0aXZlICAgDQpbZ2dmb3JjZV0oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL2dnZm9yY2UvZ2dmb3JjZS5wZGYpfCB0byBtb2RpZnkgZmFjZXRzIGluIHBsb3RzICANClt2aXJpZGlzXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvdmlyaWRpcy92aWduZXR0ZXMvaW50cm8tdG8tdmlyaWRpcy5odG1sKXwgdG8gcGxvdCBpbiBjb2xvciBwYWxldHRlICAgIA0KW2Nvd3Bsb3RdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9jb3dwbG90L3ZpZ25ldHRlcy9pbnRyb2R1Y3Rpb24uaHRtbCl7dGFyZ2V0PSJfYmxhbmsifSB8IHRvIGFsbG93IHBsb3RzIHRvIGJlIGNvbWJpbmVkICAgDQoNCiMjIyAqKlNlc3Npb24gSW5mbyoqDQoqKioNCg0KYGBge3J9DQpzZXNzaW9uSW5mbygpDQpgYGANCg0KKipFc3RpbWF0ZSBvZiBSTWFya2Rvd24gQ29tcGlsYXRpb24gVGltZTogKioNCg0KYGBge3IsIGVjaG89RkFMU0V9DQpybWFya2Rvd246OjpwZXJmX3RpbWVyX3N0b3AoInJlbmRlciIpDQpwdHMgPSBybWFya2Rvd246OjpwZXJmX3RpbWVyX3N1bW1hcnkoKQ0KY2F0KCJBYm91dCIsIHJvdW5kKHB0cyR0aW1lWzFdLzEwMDAgKyA1KSwgIi0iLCByb3VuZChwdHMkdGltZVsxXS8xMDAwICsgMTUpLCJzZWNvbmRzIikNCmBgYA0KDQpUaGlzIGNvbXBpbGF0aW9uIHRpbWUgd2FzIG1lYXN1cmVkIG9uIGEgUEMgbWFjaGluZSBvcGVyYXRpbmcgb24gV2luZG93cyAxMC4gVGhpcyByYW5nZSBzaG91bGQgb25seSBiZSB1c2VkIGFzIGFuIGVzdGltYXRlIGFzIGNvbXBpbGF0aW9uIHRpbWUgd2lsbCB2YXJ5IHdpdGggZGlmZmVyZW50IG1hY2hpbmVzIGFuZCBvcGVyYXRpbmcgc3lzdGVtcy4NCg0KIyMjICoqQWNrbm93bGVkZ21lbnRzKioNCioqKg0KDQpXZSB3b3VsZCBsaWtlIHRvIGFja25vd2xlZGdlIFtKZXNzaWNhDQpGYW56b10oaHR0cHM6Ly9iaW9ldGhpY3Muamh1LmVkdS9wZW9wbGUvcHJvZmlsZS9qZXNzaWNhLWZhbnpvLykgZm9yDQphc3Npc3RpbmcgaW4gZnJhbWluZyB0aGUgbWFqb3IgZGlyZWN0aW9uIG9mIHRoZSBjYXNlIHN0dWR5LCBhcyB3ZWxsIGFzIFtBc2hrYW4gQWZzaGluXShodHRwczovL2dsb2JhbGhlYWx0aC53YXNoaW5ndG9uLmVkdS9mYWN1bHR5L2FzaGthbi1hZnNoaW4pIGFuZCBbRXJpbiBNdWxsYW55XShodHRwOi8vd3d3LmhlYWx0aGRhdGEub3JnL2Fib3V0L2VyaW4tbXVsbGFueSkgZm9yIGdpdmluZyB1cyBhY2Nlc3MgdG8gdGhlIGRhdGEuDQoNCldlIHdvdWxkIGxpa2UgdG8gYWNrbm93bGVkZ2UgW01pY2hhZWwgQnJlc2hvY2tdKGh0dHBzOi8vbWJyZXNob2NrLmdpdGh1Yi5pby8pIGZvciBoaXMgY29udHJpYnV0aW9ucyB0byB0aGlzIGNhc2Ugc3R1ZHkgYW5kIGRldmVsb3BpbmcgdGhlIGBPQ1NkYXRhYCBwYWNrYWdlLg0KDQpXZSB3b3VsZCBhbHNvIGxpa2UgdG8gYWNrbm93bGVkZ2UgdGhlIFtCbG9vbWJlcmcgQW1lcmljYW4gSGVhbHRoDQpJbml0aWF0aXZlXShodHRwczovL2FtZXJpY2FuaGVhbHRoLmpodS5lZHUvKSBmb3IgZnVuZGluZyB0aGlzIHdvcmsuDQo=