Introduction

Analysis of data is a process of inspecting, cleaning, transforming, and modeling data with the goal of highlighting useful information, suggesting conclusions and supporting decision making.

Many times in the beginning we spend hours on handling problems with missing values, logical inconsistencies or outliers in our datasets. In this tutorial we will go through the most popular techniques in data cleansing.

We will be working with the messy dataset iris. Originally published at UCI Machine Learning Repository: Iris Data Set, this small dataset from 1936 is often used for testing out machine learning algorithms and visualizations. Each row of the table represents an iris flower, including its species and dimensions of its botanical parts, sepal and petal, in centimeters.

Take a look at this dataset here:

mydata
##     Sepal.Length Sepal.Width Petal.Length Petal.Width    Species
## 1            6.4         3.2        4.500         1.5 versicolor
## 2            6.3         3.3        6.000         2.5  virginica
## 3            6.2          NA        5.400         2.3  virginica
## 4            5.0         3.4        1.600         0.4     setosa
## 5            5.7         2.6        3.500         1.0 versicolor
## 6            5.3          NA           NA         0.2     setosa
## 7            6.4         2.7        5.300          NA  virginica
## 8            5.9         3.0        5.100         1.8  virginica
## 9            5.8         2.7        4.100         1.0 versicolor
## 10           4.8         3.1        1.600         0.2     setosa
## 11           5.0         3.5        1.600         0.6     setosa
## 12           6.0         2.7        5.100         1.6 versicolor
## 13           6.0         3.0        4.800          NA  virginica
## 14           6.8         2.8        4.800         1.4 versicolor
## 15            NA         3.9        1.700         0.4     setosa
## 16           5.0        -3.0        3.500         1.0 versicolor
## 17           5.5          NA        4.000         1.3 versicolor
## 18           4.7         3.2        1.300         0.2     setosa
## 19            NA         4.0           NA         0.2     setosa
## 20           5.6          NA        4.200         1.3 versicolor
## 21           4.9         3.6           NA         0.1     setosa
## 22           5.4          NA        4.500         1.5 versicolor
## 23           6.2         2.8           NA         1.8  virginica
## 24           6.7         3.3        5.700         2.5  virginica
## 25            NA         3.0        5.900         2.1  virginica
## 26           4.6         3.2        1.400         0.2     setosa
## 27           4.9         3.1        1.500         0.1     setosa
## 28          73.0        29.0       63.000          NA  virginica
## 29           6.5         3.2        5.100         2.0  virginica
## 30            NA         2.8        0.820         1.3 versicolor
## 31           4.4         3.2           NA         0.2     setosa
## 32           5.9         3.2        4.800          NA versicolor
## 33           5.7         2.8        4.500         1.3 versicolor
## 34           6.2         2.9           NA         1.3 versicolor
## 35           6.6         2.9       23.000         1.3 versicolor
## 36           4.8         3.0        1.400         0.1     setosa
## 37           6.5         3.0        5.500         1.8  virginica
## 38           6.2         2.2        4.500         1.5 versicolor
## 39           6.7         2.5        5.800         1.8  virginica
## 40           5.0         3.0        1.600         0.2     setosa
## 41           5.0          NA        1.200         0.2     setosa
## 42           5.8         2.7        3.900         1.2 versicolor
## 43           0.0          NA        1.300         0.4     setosa
## 44           5.8         2.7        5.100         1.9  virginica
## 45           5.5         4.2        1.400         0.2     setosa
## 46           7.7         2.8        6.700         2.0  virginica
## 47           5.7          NA           NA         0.4     setosa
## 48           7.0         3.2        4.700         1.4 versicolor
## 49           6.5         3.0        5.800         2.2  virginica
## 50           6.0         3.4        4.500         1.6 versicolor
## 51           5.5         2.6        4.400         1.2 versicolor
## 52           4.9         3.1           NA         0.2     setosa
## 53           5.2         2.7        3.900         1.4 versicolor
## 54           4.8         3.4        1.600         0.2     setosa
## 55           6.3         3.3        4.700         1.6 versicolor
## 56           7.7         3.8        6.700         2.2  virginica
## 57           5.1         3.8        1.500         0.3     setosa
## 58            NA         2.9        4.500         1.5 versicolor
## 59           6.4         2.8        5.600          NA  virginica
## 60           6.4         2.8        5.600         2.1  virginica
## 61           5.0         2.3        3.300          NA versicolor
## 62           7.4         2.8        6.100         1.9  virginica
## 63           4.3         3.0        1.100         0.1     setosa
## 64           5.0         3.3        1.400         0.2     setosa
## 65           7.2         3.0        5.800         1.6  virginica
## 66           6.3         2.5        4.900         1.5 versicolor
## 67           5.1         2.5           NA         1.1 versicolor
## 68            NA         3.2        5.700         2.3  virginica
## 69           5.1         3.5           NA          NA     setosa
## 70           5.0         3.5        1.300         0.3     setosa
## 71           6.1         3.0        4.600         1.4 versicolor
## 72           6.9         3.1        5.100         2.3  virginica
## 73           5.1         3.5        1.400         0.3     setosa
## 74           6.5          NA        4.600         1.5 versicolor
## 75           5.6         2.8        4.900         2.0  virginica
## 76           4.9         2.5        4.500          NA  virginica
## 77           5.5         3.5        1.300         0.2     setosa
## 78           7.6         3.0        6.600         2.1  virginica
## 79           5.1         3.8        0.000         0.2     setosa
## 80           7.9         3.8        6.400         2.0  virginica
## 81           6.1         2.6        5.600         1.4  virginica
## 82           5.4         3.4        1.700         0.2     setosa
## 83           6.1         2.9        4.700         1.4 versicolor
## 84           5.4         3.7        1.500         0.2     setosa
## 85           6.7         3.0        5.200         2.3  virginica
## 86           5.1         3.8        1.900         Inf     setosa
## 87           6.4         2.9        4.300         1.3 versicolor
## 88           5.7         2.9        4.200         1.3 versicolor
## 89           4.4         2.9        1.400         0.2     setosa
## 90           6.3         2.5        5.000         1.9  virginica
## 91           7.2         3.2        6.000         1.8  virginica
## 92           4.9          NA        3.300         1.0 versicolor
## 93           5.2         3.4        1.400         0.2     setosa
## 94           5.8         2.7        5.100         1.9  virginica
## 95           6.0         2.2        5.000         1.5  virginica
## 96           6.9         3.1           NA         1.5 versicolor
## 97           5.5         2.3        4.000         1.3 versicolor
## 98           6.7          NA        5.000         1.7 versicolor
## 99           5.7         3.0        4.200         1.2 versicolor
## 100          6.3         2.8        5.100         1.5  virginica
## 101          5.4         3.4        1.500         0.4     setosa
## 102          7.2         3.6           NA         2.5  virginica
## 103          6.3         2.7        4.900          NA  virginica
## 104          5.6         3.0        4.100         1.3 versicolor
## 105          5.1         3.7           NA         0.4     setosa
## 106          5.5          NA        0.925         1.0 versicolor
## 107          6.5         3.0        5.200         2.0  virginica
## 108          4.8         3.0        1.400          NA     setosa
## 109          6.1         2.8           NA         1.3 versicolor
## 110          4.6         3.4        1.400         0.3     setosa
## 111          6.3         3.4           NA         2.4  virginica
## 112          5.0         3.4        1.500         0.2     setosa
## 113          5.1         3.4        1.500         0.2     setosa
## 114           NA         3.3        5.700         2.1  virginica
## 115          6.7         3.1        4.700         1.5 versicolor
## 116          7.7         2.6        6.900         2.3  virginica
## 117          6.3          NA        4.400         1.3 versicolor
## 118          4.6         3.1        1.500         0.2     setosa
## 119           NA         3.0        5.500         2.1  virginica
## 120           NA         2.8        4.700         1.2 versicolor
## 121          5.9         3.0           NA         1.5 versicolor
## 122          4.5         2.3        1.300         0.3     setosa
## 123          6.4         3.2        5.300         2.3  virginica
## 124          5.2         4.1        1.500         0.1     setosa
## 125         49.0        30.0       14.000         2.0     setosa
## 126          5.6         2.9        3.600         1.3 versicolor
## 127          6.8         3.2        5.900         2.3  virginica
## 128          5.8          NA        5.100         2.4  virginica
## 129          4.6         3.6           NA         0.2     setosa
## 130          5.7         0.0        1.700         0.3     setosa
## 131          5.6         2.5        3.900         1.1 versicolor
## 132          6.7         3.1        4.400         1.4 versicolor
## 133          4.8          NA        1.900         0.2     setosa
## 134          5.1         3.3        1.700         0.5     setosa
## 135          4.4         3.0        1.300          NA     setosa
## 136          7.7         3.0           NA         2.3  virginica
## 137          4.7         3.2        1.600         0.2     setosa
## 138           NA         3.0        4.900         1.8  virginica
## 139          6.9         3.1        5.400         2.1  virginica
## 140          6.0         2.2        4.000         1.0 versicolor
## 141          5.0          NA        1.400         0.2     setosa
## 142          5.5          NA        3.800         1.1 versicolor
## 143          6.6         3.0        4.400         1.4 versicolor
## 144          6.3         2.9        5.600         1.8  virginica
## 145          5.7         2.5        5.000         2.0  virginica
## 146          6.7         3.1        5.600         2.4  virginica
## 147          5.6         3.0        4.500         1.5 versicolor
## 148          5.2         3.5        1.500         0.2     setosa
## 149          6.4         3.1           NA         1.8  virginica
## 150          5.8         2.6        4.000          NA versicolor

Dealing with NA

In all of our data analyses so far we implicitly assumed that we don’t have any missing values in our data. In practice, that is often not the case. While some statistical and machine learning methods work with missing data, many commonly used methods can’t, so it is important to learn how to deal with missing values.

The severity of NA

If we judge by the imputation or data removing methods that are most commonly used in practice, we might conclude that missing data is a relatively simple problem that is secondary to the inference, predictive modelling, etc. that are the primary goal of our analysis. Unfortunately, that is not the case. Dealing with missing values is very challenging, often in itself a modelling problem.

Three classes of NA’s

The choice of an appropriate method is inseparable from our understanding of or assumptions about the process that generated the missing values (the missingness mechanism). Based on the characteristics of this process we typically characterize the missing data problem as one of these three cases:

  1. MCAR (Missing Completely At Random): Whether or not a value is missing is independent of both the observed values and the missing (unobserved) values. For example, if we had temperature measuring devices at different locations and they occassionally and random intervals stopped working. Or, in surveys, where respondents don’t respond with a certain probability, independent of the characteristics that we are surveying.

  2. MAR (Missing At Random): Whether or not a value is missing is independent of the missing (unobserved) values but depends on the observed values. That is, there is a pattern to how the values are missing, but we could fully explain that pattern given only the observed data. For example, if our temperature measuring devices stopped working more often in certain locations than in others. Or, in surveys, if women are less likely to report their weight than men.

  3. MNAR (Missing Not At Random): Whether or not a value is missing also depends on the missing (unobserved) values in a way that can’t be explained by the observed values. That is, there is a pattern to how the values are missing, but we wouldn’t be able to fully explain it without observing the values that are missing. For example, if our temperature measuring device had a tendency to stop working when the temperature is very low. Or, in surveys, if a person was less likely to report their salary if their salary was high.

The NA’s mechanism

Every variable in our data might have a different missingness mechanism. So, how do we determine whether it is MCAR, MAR, or MNAR?

Showing with a reasonable degree of certainty that the mechanism is not MCAR is equivalent to showing that the missingness (whether or not a value is missing) can be predicted from observed values. That is, it is a prediction problem and it is sufficient to show one way that missingness can be predicted. On the other hand, it is infeasible to show that the mechanism is MCAR, because that would require us to show that there is no way of predicting missingness from observed values. We can, however, rule out certain kinds of dependency (for example, linear dependency).

For MNAR, the situation is even worse. In general, it is impossible to determine from the data the relationship between missingness and the value that is missing, because we don’t know what is missing. That is, unless we are able to somehow measure the values that are missing, we won’t be able to determine whether or not the missingness regime is MNAR. Getting our hands on the missing values, however, is in most cases impossible or infeasible.

To summarize, we’ll often be able to show that our missingness regime is not MCAR and never that it is MCAR. Subsequently, we’ll often know that the missingness regime is at least MAR, but we’ll rarely be able to determine whether it is MAR or MNAR, unless we can get our hands on the missing data. Therefore, it becomes very important to utilize not only data but also domain-specific background knowledge, when applicable. In particular, known relationships between the variables in our data and what caused the values to be missing.

Causes for NA’s

Understanding the cause for missing data can often help us identify the missingness mechanism and avoid introducing a bias. In general, we can split the causes into two classes: intentional or unintentional.

Intentionally or systematically missing data are missing by design. For example:

  • patients that did not experience pain were not asked to rate their pain,
  • a patient that has not been discharged from the hospital doesn’t have a ‘days spent in hospital care’ data point (although we can infer a lower bound from the day of arrival) and
  • a particular prediction algorithm’s performance was measured on datasets with fewer than 100 variables, due to its time complexity and
  • some measurements were not made because it was too costly to make all of them.

Unintentionally missing data were not planned. For example:

  • data missing due to measurement error,
  • a subject skipping a survey question,
  • a patient prematurely dropping out of a study

and other reasons not planned by and outside the control of the data collector.

There is no general rule and further conclusions can only be made on a case-by-case basis, using domain specific knowledge. Measurement error can range from completly random to completely not-at-random, such a temperature sensor breaking down at high temperatures. Subjects typically do not drop out of studies at random, but that is also a possiblity. When not all measurements are made to reduce cost, they are often omitted completely at random, but sometimes a different design is used.

Detecting NA

A missing value, represented by NA in R, is a placeholder for a datum of which the type is known but its value isn’t. Therefore, it is impossible to perform statistical analysis on data where one or more values in the data are missing. One may choose to either omit elements from a dataset that contain missing values or to impute a value, but missingness is something to be dealt with prior to any analysis.

Can you see that many values in our dataset have status NA = Not Available? Count (or plot), how many (%) of all 150 rows is complete.

sum(complete.cases(mydata))
## [1] 96
nrow(mydata[complete.cases(mydata), ])/nrow(mydata)*100
## [1] 64

Does the data contain other special values? If it does, replace them with NA.

is.special <- function(x){
  if (is.numeric(x)) !is.finite(x) else is.na(x)
}

sapply(mydata, is.special)
##        Sepal.Length Sepal.Width Petal.Length Petal.Width Species
##   [1,]        FALSE       FALSE        FALSE       FALSE   FALSE
##   [2,]        FALSE       FALSE        FALSE       FALSE   FALSE
##   [3,]        FALSE        TRUE        FALSE       FALSE   FALSE
##   [4,]        FALSE       FALSE        FALSE       FALSE   FALSE
##   [5,]        FALSE       FALSE        FALSE       FALSE   FALSE
##   [6,]        FALSE        TRUE         TRUE       FALSE   FALSE
##   [7,]        FALSE       FALSE        FALSE        TRUE   FALSE
##   [8,]        FALSE       FALSE        FALSE       FALSE   FALSE
##   [9,]        FALSE       FALSE        FALSE       FALSE   FALSE
##  [10,]        FALSE       FALSE        FALSE       FALSE   FALSE
##  [11,]        FALSE       FALSE        FALSE       FALSE   FALSE
##  [12,]        FALSE       FALSE        FALSE       FALSE   FALSE
##  [13,]        FALSE       FALSE        FALSE        TRUE   FALSE
##  [14,]        FALSE       FALSE        FALSE       FALSE   FALSE
##  [15,]         TRUE       FALSE        FALSE       FALSE   FALSE
##  [16,]        FALSE       FALSE        FALSE       FALSE   FALSE
##  [17,]        FALSE        TRUE        FALSE       FALSE   FALSE
##  [18,]        FALSE       FALSE        FALSE       FALSE   FALSE
##  [19,]         TRUE       FALSE         TRUE       FALSE   FALSE
##  [20,]        FALSE        TRUE        FALSE       FALSE   FALSE
##  [21,]        FALSE       FALSE         TRUE       FALSE   FALSE
##  [22,]        FALSE        TRUE        FALSE       FALSE   FALSE
##  [23,]        FALSE       FALSE         TRUE       FALSE   FALSE
##  [24,]        FALSE       FALSE        FALSE       FALSE   FALSE
##  [25,]         TRUE       FALSE        FALSE       FALSE   FALSE
##  [26,]        FALSE       FALSE        FALSE       FALSE   FALSE
##  [27,]        FALSE       FALSE        FALSE       FALSE   FALSE
##  [28,]        FALSE       FALSE        FALSE        TRUE   FALSE
##  [29,]        FALSE       FALSE        FALSE       FALSE   FALSE
##  [30,]         TRUE       FALSE        FALSE       FALSE   FALSE
##  [31,]        FALSE       FALSE         TRUE       FALSE   FALSE
##  [32,]        FALSE       FALSE        FALSE        TRUE   FALSE
##  [33,]        FALSE       FALSE        FALSE       FALSE   FALSE
##  [34,]        FALSE       FALSE         TRUE       FALSE   FALSE
##  [35,]        FALSE       FALSE        FALSE       FALSE   FALSE
##  [36,]        FALSE       FALSE        FALSE       FALSE   FALSE
##  [37,]        FALSE       FALSE        FALSE       FALSE   FALSE
##  [38,]        FALSE       FALSE        FALSE       FALSE   FALSE
##  [39,]        FALSE       FALSE        FALSE       FALSE   FALSE
##  [40,]        FALSE       FALSE        FALSE       FALSE   FALSE
##  [41,]        FALSE        TRUE        FALSE       FALSE   FALSE
##  [42,]        FALSE       FALSE        FALSE       FALSE   FALSE
##  [43,]        FALSE        TRUE        FALSE       FALSE   FALSE
##  [44,]        FALSE       FALSE        FALSE       FALSE   FALSE
##  [45,]        FALSE       FALSE        FALSE       FALSE   FALSE
##  [46,]        FALSE       FALSE        FALSE       FALSE   FALSE
##  [47,]        FALSE        TRUE         TRUE       FALSE   FALSE
##  [48,]        FALSE       FALSE        FALSE       FALSE   FALSE
##  [49,]        FALSE       FALSE        FALSE       FALSE   FALSE
##  [50,]        FALSE       FALSE        FALSE       FALSE   FALSE
##  [51,]        FALSE       FALSE        FALSE       FALSE   FALSE
##  [52,]        FALSE       FALSE         TRUE       FALSE   FALSE
##  [53,]        FALSE       FALSE        FALSE       FALSE   FALSE
##  [54,]        FALSE       FALSE        FALSE       FALSE   FALSE
##  [55,]        FALSE       FALSE        FALSE       FALSE   FALSE
##  [56,]        FALSE       FALSE        FALSE       FALSE   FALSE
##  [57,]        FALSE       FALSE        FALSE       FALSE   FALSE
##  [58,]         TRUE       FALSE        FALSE       FALSE   FALSE
##  [59,]        FALSE       FALSE        FALSE        TRUE   FALSE
##  [60,]        FALSE       FALSE        FALSE       FALSE   FALSE
##  [61,]        FALSE       FALSE        FALSE        TRUE   FALSE
##  [62,]        FALSE       FALSE        FALSE       FALSE   FALSE
##  [63,]        FALSE       FALSE        FALSE       FALSE   FALSE
##  [64,]        FALSE       FALSE        FALSE       FALSE   FALSE
##  [65,]        FALSE       FALSE        FALSE       FALSE   FALSE
##  [66,]        FALSE       FALSE        FALSE       FALSE   FALSE
##  [67,]        FALSE       FALSE         TRUE       FALSE   FALSE
##  [68,]         TRUE       FALSE        FALSE       FALSE   FALSE
##  [69,]        FALSE       FALSE         TRUE        TRUE   FALSE
##  [70,]        FALSE       FALSE        FALSE       FALSE   FALSE
##  [71,]        FALSE       FALSE        FALSE       FALSE   FALSE
##  [72,]        FALSE       FALSE        FALSE       FALSE   FALSE
##  [73,]        FALSE       FALSE        FALSE       FALSE   FALSE
##  [74,]        FALSE        TRUE        FALSE       FALSE   FALSE
##  [75,]        FALSE       FALSE        FALSE       FALSE   FALSE
##  [76,]        FALSE       FALSE        FALSE        TRUE   FALSE
##  [77,]        FALSE       FALSE        FALSE       FALSE   FALSE
##  [78,]        FALSE       FALSE        FALSE       FALSE   FALSE
##  [79,]        FALSE       FALSE        FALSE       FALSE   FALSE
##  [80,]        FALSE       FALSE        FALSE       FALSE   FALSE
##  [81,]        FALSE       FALSE        FALSE       FALSE   FALSE
##  [82,]        FALSE       FALSE        FALSE       FALSE   FALSE
##  [83,]        FALSE       FALSE        FALSE       FALSE   FALSE
##  [84,]        FALSE       FALSE        FALSE       FALSE   FALSE
##  [85,]        FALSE       FALSE        FALSE       FALSE   FALSE
##  [86,]        FALSE       FALSE        FALSE        TRUE   FALSE
##  [87,]        FALSE       FALSE        FALSE       FALSE   FALSE
##  [88,]        FALSE       FALSE        FALSE       FALSE   FALSE
##  [89,]        FALSE       FALSE        FALSE       FALSE   FALSE
##  [90,]        FALSE       FALSE        FALSE       FALSE   FALSE
##  [91,]        FALSE       FALSE        FALSE       FALSE   FALSE
##  [92,]        FALSE        TRUE        FALSE       FALSE   FALSE
##  [93,]        FALSE       FALSE        FALSE       FALSE   FALSE
##  [94,]        FALSE       FALSE        FALSE       FALSE   FALSE
##  [95,]        FALSE       FALSE        FALSE       FALSE   FALSE
##  [96,]        FALSE       FALSE         TRUE       FALSE   FALSE
##  [97,]        FALSE       FALSE        FALSE       FALSE   FALSE
##  [98,]        FALSE        TRUE        FALSE       FALSE   FALSE
##  [99,]        FALSE       FALSE        FALSE       FALSE   FALSE
## [100,]        FALSE       FALSE        FALSE       FALSE   FALSE
## [101,]        FALSE       FALSE        FALSE       FALSE   FALSE
## [102,]        FALSE       FALSE         TRUE       FALSE   FALSE
## [103,]        FALSE       FALSE        FALSE        TRUE   FALSE
## [104,]        FALSE       FALSE        FALSE       FALSE   FALSE
## [105,]        FALSE       FALSE         TRUE       FALSE   FALSE
## [106,]        FALSE        TRUE        FALSE       FALSE   FALSE
## [107,]        FALSE       FALSE        FALSE       FALSE   FALSE
## [108,]        FALSE       FALSE        FALSE        TRUE   FALSE
## [109,]        FALSE       FALSE         TRUE       FALSE   FALSE
## [110,]        FALSE       FALSE        FALSE       FALSE   FALSE
## [111,]        FALSE       FALSE         TRUE       FALSE   FALSE
## [112,]        FALSE       FALSE        FALSE       FALSE   FALSE
## [113,]        FALSE       FALSE        FALSE       FALSE   FALSE
## [114,]         TRUE       FALSE        FALSE       FALSE   FALSE
## [115,]        FALSE       FALSE        FALSE       FALSE   FALSE
## [116,]        FALSE       FALSE        FALSE       FALSE   FALSE
## [117,]        FALSE        TRUE        FALSE       FALSE   FALSE
## [118,]        FALSE       FALSE        FALSE       FALSE   FALSE
## [119,]         TRUE       FALSE        FALSE       FALSE   FALSE
## [120,]         TRUE       FALSE        FALSE       FALSE   FALSE
## [121,]        FALSE       FALSE         TRUE       FALSE   FALSE
## [122,]        FALSE       FALSE        FALSE       FALSE   FALSE
## [123,]        FALSE       FALSE        FALSE       FALSE   FALSE
## [124,]        FALSE       FALSE        FALSE       FALSE   FALSE
## [125,]        FALSE       FALSE        FALSE       FALSE   FALSE
## [126,]        FALSE       FALSE        FALSE       FALSE   FALSE
## [127,]        FALSE       FALSE        FALSE       FALSE   FALSE
## [128,]        FALSE        TRUE        FALSE       FALSE   FALSE
## [129,]        FALSE       FALSE         TRUE       FALSE   FALSE
## [130,]        FALSE       FALSE        FALSE       FALSE   FALSE
## [131,]        FALSE       FALSE        FALSE       FALSE   FALSE
## [132,]        FALSE       FALSE        FALSE       FALSE   FALSE
## [133,]        FALSE        TRUE        FALSE       FALSE   FALSE
## [134,]        FALSE       FALSE        FALSE       FALSE   FALSE
## [135,]        FALSE       FALSE        FALSE        TRUE   FALSE
## [136,]        FALSE       FALSE         TRUE       FALSE   FALSE
## [137,]        FALSE       FALSE        FALSE       FALSE   FALSE
## [138,]         TRUE       FALSE        FALSE       FALSE   FALSE
## [139,]        FALSE       FALSE        FALSE       FALSE   FALSE
## [140,]        FALSE       FALSE        FALSE       FALSE   FALSE
## [141,]        FALSE        TRUE        FALSE       FALSE   FALSE
## [142,]        FALSE        TRUE        FALSE       FALSE   FALSE
## [143,]        FALSE       FALSE        FALSE       FALSE   FALSE
## [144,]        FALSE       FALSE        FALSE       FALSE   FALSE
## [145,]        FALSE       FALSE        FALSE       FALSE   FALSE
## [146,]        FALSE       FALSE        FALSE       FALSE   FALSE
## [147,]        FALSE       FALSE        FALSE       FALSE   FALSE
## [148,]        FALSE       FALSE        FALSE       FALSE   FALSE
## [149,]        FALSE       FALSE         TRUE       FALSE   FALSE
## [150,]        FALSE       FALSE        FALSE        TRUE   FALSE
for (n in colnames(mydata)){
  is.na(mydata[[n]]) <- is.special(mydata[[n]])
}
summary(mydata)
##   Sepal.Length     Sepal.Width      Petal.Length    Petal.Width   
##  Min.   : 0.000   Min.   :-3.000   Min.   : 0.00   Min.   :0.100  
##  1st Qu.: 5.100   1st Qu.: 2.800   1st Qu.: 1.60   1st Qu.:0.300  
##  Median : 5.750   Median : 3.000   Median : 4.50   Median :1.300  
##  Mean   : 6.559   Mean   : 3.391   Mean   : 4.45   Mean   :1.207  
##  3rd Qu.: 6.400   3rd Qu.: 3.300   3rd Qu.: 5.10   3rd Qu.:1.800  
##  Max.   :73.000   Max.   :30.000   Max.   :63.00   Max.   :2.500  
##  NA's   :10       NA's   :17       NA's   :19      NA's   :13     
##    Species         
##  Length:150        
##  Class :character  
##  Mode  :character  
##                    
##                    
##                    
## 

NaniaR

OMG! So hard :-) It’s better to use visualizations, tests… or automatically detect NA’s with some function…

There are a variety of different plots to explore missing data available in the naniar package. If you would like to know more about the philosophy of the naniar package, you should read the vignette Getting Started with naniar.

A key point to remember with the visualisation tools in naniar is that there is a way to get the data from the plot out from the visualisation.

Exploring NA’s

One of the first plots that I recommend you start with when you are first exploring your missing data, is the vis_miss() plot, which is re-exported from visdat.

vis_miss(airquality)

This plot provides a specific visualiation of the amount of missing data, showing in black the location of missing values, and also providing information on the overall percentage of missing values overall (in the legend), and in each variable.

Patterns

An upset plot from the UpSetR package can be used to visualise the patterns of missingness, or rather the combinations of missingness across cases. To see combinations of missingness and intersections of missingness amongst variables, use the gg_miss_upset function:

gg_miss_upset(airquality)

We can explore this with more complex data, such as riskfactors:

gg_miss_upset(riskfactors)

NA’s Mechanisms

There are a few different ways to explore different missing data mechanisms and relationships. One way incorporates the method of shifting missing values so that they can be visualised on the same axes as the regular values, and then colours the missing and not missing points. This is implemented with geom_miss_point().

# using regular geom_point()
ggplot(airquality,
       aes(x = Ozone,
           y = Solar.R)) +
geom_miss_point()

NA’s in vars

This plot shows the number of missing values in each variable in a dataset. It is powered by the miss_var_summary() function.

gg_miss_var(airquality)

gg_miss_var(airquality) + labs(y = "Look at all the missing ones")

NA’s in cases

This plot shows the number of missing values in each case. It is powered by the miss_case_summary() function.

gg_miss_case(airquality) + labs(x = "Number of Cases")

NA’s across factors

This plot shows the number of missings in each column, broken down by a categorical variable from the dataset. It is powered by a dplyr::group_by statement followed by miss_var_summary().

gg_miss_fct(x = riskfactors, fct = marital)

Summaries for NA’s

naniar provides numerical summaries of missing data, that follow a consistent rule that uses a syntax begining with miss_.

miss_var_summary(airquality)
## # A tibble: 6 × 3
##   variable n_miss pct_miss
##   <chr>     <int>    <dbl>
## 1 Ozone        37    24.2 
## 2 Solar.R       7     4.58
## 3 Wind          0     0   
## 4 Temp          0     0   
## 5 Month         0     0   
## 6 Day           0     0

You could also group_by() to work out the number of missings in each variable across the levels within it.

airquality %>%
  group_by(Month) %>%
  miss_var_summary()
## # A tibble: 25 × 4
## # Groups:   Month [5]
##    Month variable n_miss pct_miss
##    <int> <chr>     <int>    <dbl>
##  1     5 Ozone         5     16.1
##  2     5 Solar.R       4     12.9
##  3     5 Wind          0      0  
##  4     5 Temp          0      0  
##  5     5 Day           0      0  
##  6     6 Ozone        21     70  
##  7     6 Solar.R       0      0  
##  8     6 Wind          0      0  
##  9     6 Temp          0      0  
## 10     6 Day           0      0  
## # ℹ 15 more rows

Identify outliers

Thanks to rstatix library we can easily detect outliers using boxplot methods. Boxplots are a popular and an easy method for identifying outliers.

# Convert the variable dose from a numeric to a factor variable
tooth <- as.data.frame(ToothGrowth)
tooth$dose <- as.factor(tooth$dose)
# Change box plot line colors by groups
p<-ggplot(tooth, aes(x=dose, y=len, color=dose)) +
  geom_boxplot()
p

There are two categories of outlier: (1) outliers and (2) extreme points.

Values above Q3 + 1.5xIQR or below Q1 - 1.5xIQR are considered as outliers. Values above Q3 + 3xIQR or below Q1 - 3xIQR are considered as extreme points (or extreme outliers).

Q1 and Q3 are the first and third quartile, respectively. IQR is the interquartile range (IQR = Q3 - Q1).

Generally speaking, data points that are labelled outliers in boxplots are not considered as troublesome as those considered extreme points and might even be ignored. Note that, any NA and NaN are automatically removed before the quantiles are computed.

Example

tooth %>%
  group_by(dose) %>%
  identify_outliers(len)
## # A tibble: 1 × 5
##   dose    len supp  is.outlier is.extreme
##   <fct> <dbl> <fct> <lgl>      <lgl>     
## 1 0.5    21.5 OJ    TRUE       FALSE

See? one outlier! (that was visible on the boxplot)

Checking consistency

Consistent data are technically correct data that are fit for statistical analysis. They are data in which missing values, special values, (obvious) errors and outliers are either removed, corrected or imputed. The data are consistent with constraints based on real-world knowledge about the subject that the data describe.

We have the following background knowledge:

  • Species should be one of the following values: setosa, versicolor or virginica.

  • All measured numerical properties of an iris should be positive.

  • The petal length of an iris is at least 2 times its petal width.

  • The sepal length of an iris cannot exceed 30 cm.

  • The sepals of an iris are longer than its petals.

Define these rules in a separate object ‘RULE’ and read them into R using editset (package ‘editrules’). Print the resulting constraint object.

RULE <- editset(c("Sepal.Length <= 30","Species %in% c('setosa','versicolor','virginica')"
               , "Sepal.Length > 0", "Sepal.Width > 0", "Petal.Length > 0", "Petal.Width > 0",
"Petal.Length >= 2 * Petal.Width", "Sepal.Length>Petal.Length"))
RULE
## 
## Data model:
## dat1 : Species %in% c('setosa', 'versicolor', 'virginica') 
## 
## Edit set:
## num1 : Sepal.Length <= 30
## num2 : 0 < Sepal.Length
## num3 : 0 < Sepal.Width
## num4 : 0 < Petal.Length
## num5 : 0 < Petal.Width
## num6 : 2*Petal.Width <= Petal.Length
## num7 : Petal.Length < Sepal.Length

Now we are ready to determine how often each rule is broken (violatedEdits). Also we can summarize and plot the result.

summary(violatedEdits(RULE, mydata))
## Edit violations, 150 observations, 0 completely missing (0%):
## 
##  editname freq  rel
##      num6    3   2%
##      num1    2 1.3%
##      num3    2 1.3%
##      num7    2 1.3%
##      num2    1 0.7%
##      num4    1 0.7%
## 
## Edit violations per record:
## 
##  errors freq   rel
##       0   90   60%
##       1   17 11.3%
##       2   13  8.7%
##       3   25 16.7%
##       4    4  2.7%
##       5    1  0.7%

What percentage of the data has no errors?

violated <- violatedEdits(RULE, mydata)
summary(violated)
## Edit violations, 150 observations, 0 completely missing (0%):
## 
##  editname freq  rel
##      num6    3   2%
##      num1    2 1.3%
##      num3    2 1.3%
##      num7    2 1.3%
##      num2    1 0.7%
##      num4    1 0.7%
## 
## Edit violations per record:
## 
##  errors freq   rel
##       0   90   60%
##       1   17 11.3%
##       2   13  8.7%
##       3   25 16.7%
##       4    4  2.7%
##       5    1  0.7%
plot(violated)

Exercise 1.

Find out which observations have too long sepals using the result of violatedEdits.

# solution for the exercise 1 here ;-)

Exercise 2.

Find outliers in sepal length using boxplot and boxplot.stats. Retrieve the corresponding observations and look at the other values. Any ideas what might have happened? Set the outliers to NA (or a value that you find more appropiate)

boxplot(mydata$Sepal.Length)

outliers <- boxplot.stats(mydata$Sepal.Length)$out
outliers_idx <- which(mydata$Sepal.Length %in% outliers)
mydata[outliers_idx,]
##     Sepal.Length Sepal.Width Petal.Length Petal.Width   Species
## 28            73          29         63.0          NA virginica
## 43             0          NA          1.3         0.4    setosa
## 125           49          30         14.0         2.0    setosa
# they all seem to be too big... may they were measured in mm i.o cm?
mydata[outliers_idx,1:4] <- mydata[outliers_idx,1:4]/10
summary(mydata)
##   Sepal.Length    Sepal.Width      Petal.Length     Petal.Width   
##  Min.   :0.000   Min.   :-3.000   Min.   : 0.000   Min.   :0.040  
##  1st Qu.:5.100   1st Qu.: 2.800   1st Qu.: 1.600   1st Qu.:0.300  
##  Median :5.700   Median : 3.000   Median : 4.400   Median :1.300  
##  Mean   :5.775   Mean   : 2.992   Mean   : 3.912   Mean   :1.192  
##  3rd Qu.:6.400   3rd Qu.: 3.300   3rd Qu.: 5.100   3rd Qu.:1.800  
##  Max.   :7.900   Max.   : 4.200   Max.   :23.000   Max.   :2.500  
##  NA's   :10      NA's   :17       NA's   :19       NA's   :13     
##    Species         
##  Length:150        
##  Class :character  
##  Mode  :character  
##                    
##                    
##                    
## 

Note that simple boxplot shows an extra outlier!

boxplot(Sepal.Length ~ Species, data=mydata)

Corrections

Replace non positive values from Sepal.Width with NA using correctWithRules from the library ‘deducorrect’.

cr <- correctionRules(expression(
  if (!is.na(Sepal.Width) && Sepal.Width <=0 ) Sepal.Width = NA
  ))
correctWithRules(cr, mydata)
## $corrected
##     Sepal.Length Sepal.Width Petal.Length Petal.Width    Species
## 1            6.4         3.2        4.500        1.50 versicolor
## 2            6.3         3.3        6.000        2.50  virginica
## 3            6.2          NA        5.400        2.30  virginica
## 4            5.0         3.4        1.600        0.40     setosa
## 5            5.7         2.6        3.500        1.00 versicolor
## 6            5.3          NA           NA        0.20     setosa
## 7            6.4         2.7        5.300          NA  virginica
## 8            5.9         3.0        5.100        1.80  virginica
## 9            5.8         2.7        4.100        1.00 versicolor
## 10           4.8         3.1        1.600        0.20     setosa
## 11           5.0         3.5        1.600        0.60     setosa
## 12           6.0         2.7        5.100        1.60 versicolor
## 13           6.0         3.0        4.800          NA  virginica
## 14           6.8         2.8        4.800        1.40 versicolor
## 15            NA         3.9        1.700        0.40     setosa
## 16           5.0          NA        3.500        1.00 versicolor
## 17           5.5          NA        4.000        1.30 versicolor
## 18           4.7         3.2        1.300        0.20     setosa
## 19            NA         4.0           NA        0.20     setosa
## 20           5.6          NA        4.200        1.30 versicolor
## 21           4.9         3.6           NA        0.10     setosa
## 22           5.4          NA        4.500        1.50 versicolor
## 23           6.2         2.8           NA        1.80  virginica
## 24           6.7         3.3        5.700        2.50  virginica
## 25            NA         3.0        5.900        2.10  virginica
## 26           4.6         3.2        1.400        0.20     setosa
## 27           4.9         3.1        1.500        0.10     setosa
## 28           7.3         2.9        6.300          NA  virginica
## 29           6.5         3.2        5.100        2.00  virginica
## 30            NA         2.8        0.820        1.30 versicolor
## 31           4.4         3.2           NA        0.20     setosa
## 32           5.9         3.2        4.800          NA versicolor
## 33           5.7         2.8        4.500        1.30 versicolor
## 34           6.2         2.9           NA        1.30 versicolor
## 35           6.6         2.9       23.000        1.30 versicolor
## 36           4.8         3.0        1.400        0.10     setosa
## 37           6.5         3.0        5.500        1.80  virginica
## 38           6.2         2.2        4.500        1.50 versicolor
## 39           6.7         2.5        5.800        1.80  virginica
## 40           5.0         3.0        1.600        0.20     setosa
## 41           5.0          NA        1.200        0.20     setosa
## 42           5.8         2.7        3.900        1.20 versicolor
## 43           0.0          NA        0.130        0.04     setosa
## 44           5.8         2.7        5.100        1.90  virginica
## 45           5.5         4.2        1.400        0.20     setosa
## 46           7.7         2.8        6.700        2.00  virginica
## 47           5.7          NA           NA        0.40     setosa
## 48           7.0         3.2        4.700        1.40 versicolor
## 49           6.5         3.0        5.800        2.20  virginica
## 50           6.0         3.4        4.500        1.60 versicolor
## 51           5.5         2.6        4.400        1.20 versicolor
## 52           4.9         3.1           NA        0.20     setosa
## 53           5.2         2.7        3.900        1.40 versicolor
## 54           4.8         3.4        1.600        0.20     setosa
## 55           6.3         3.3        4.700        1.60 versicolor
## 56           7.7         3.8        6.700        2.20  virginica
## 57           5.1         3.8        1.500        0.30     setosa
## 58            NA         2.9        4.500        1.50 versicolor
## 59           6.4         2.8        5.600          NA  virginica
## 60           6.4         2.8        5.600        2.10  virginica
## 61           5.0         2.3        3.300          NA versicolor
## 62           7.4         2.8        6.100        1.90  virginica
## 63           4.3         3.0        1.100        0.10     setosa
## 64           5.0         3.3        1.400        0.20     setosa
## 65           7.2         3.0        5.800        1.60  virginica
## 66           6.3         2.5        4.900        1.50 versicolor
## 67           5.1         2.5           NA        1.10 versicolor
## 68            NA         3.2        5.700        2.30  virginica
## 69           5.1         3.5           NA          NA     setosa
## 70           5.0         3.5        1.300        0.30     setosa
## 71           6.1         3.0        4.600        1.40 versicolor
## 72           6.9         3.1        5.100        2.30  virginica
## 73           5.1         3.5        1.400        0.30     setosa
## 74           6.5          NA        4.600        1.50 versicolor
## 75           5.6         2.8        4.900        2.00  virginica
## 76           4.9         2.5        4.500          NA  virginica
## 77           5.5         3.5        1.300        0.20     setosa
## 78           7.6         3.0        6.600        2.10  virginica
## 79           5.1         3.8        0.000        0.20     setosa
## 80           7.9         3.8        6.400        2.00  virginica
## 81           6.1         2.6        5.600        1.40  virginica
## 82           5.4         3.4        1.700        0.20     setosa
## 83           6.1         2.9        4.700        1.40 versicolor
## 84           5.4         3.7        1.500        0.20     setosa
## 85           6.7         3.0        5.200        2.30  virginica
## 86           5.1         3.8        1.900          NA     setosa
## 87           6.4         2.9        4.300        1.30 versicolor
## 88           5.7         2.9        4.200        1.30 versicolor
## 89           4.4         2.9        1.400        0.20     setosa
## 90           6.3         2.5        5.000        1.90  virginica
## 91           7.2         3.2        6.000        1.80  virginica
## 92           4.9          NA        3.300        1.00 versicolor
## 93           5.2         3.4        1.400        0.20     setosa
## 94           5.8         2.7        5.100        1.90  virginica
## 95           6.0         2.2        5.000        1.50  virginica
## 96           6.9         3.1           NA        1.50 versicolor
## 97           5.5         2.3        4.000        1.30 versicolor
## 98           6.7          NA        5.000        1.70 versicolor
## 99           5.7         3.0        4.200        1.20 versicolor
## 100          6.3         2.8        5.100        1.50  virginica
## 101          5.4         3.4        1.500        0.40     setosa
## 102          7.2         3.6           NA        2.50  virginica
## 103          6.3         2.7        4.900          NA  virginica
## 104          5.6         3.0        4.100        1.30 versicolor
## 105          5.1         3.7           NA        0.40     setosa
## 106          5.5          NA        0.925        1.00 versicolor
## 107          6.5         3.0        5.200        2.00  virginica
## 108          4.8         3.0        1.400          NA     setosa
## 109          6.1         2.8           NA        1.30 versicolor
## 110          4.6         3.4        1.400        0.30     setosa
## 111          6.3         3.4           NA        2.40  virginica
## 112          5.0         3.4        1.500        0.20     setosa
## 113          5.1         3.4        1.500        0.20     setosa
## 114           NA         3.3        5.700        2.10  virginica
## 115          6.7         3.1        4.700        1.50 versicolor
## 116          7.7         2.6        6.900        2.30  virginica
## 117          6.3          NA        4.400        1.30 versicolor
## 118          4.6         3.1        1.500        0.20     setosa
## 119           NA         3.0        5.500        2.10  virginica
## 120           NA         2.8        4.700        1.20 versicolor
## 121          5.9         3.0           NA        1.50 versicolor
## 122          4.5         2.3        1.300        0.30     setosa
## 123          6.4         3.2        5.300        2.30  virginica
## 124          5.2         4.1        1.500        0.10     setosa
## 125          4.9         3.0        1.400        0.20     setosa
## 126          5.6         2.9        3.600        1.30 versicolor
## 127          6.8         3.2        5.900        2.30  virginica
## 128          5.8          NA        5.100        2.40  virginica
## 129          4.6         3.6           NA        0.20     setosa
## 130          5.7          NA        1.700        0.30     setosa
## 131          5.6         2.5        3.900        1.10 versicolor
## 132          6.7         3.1        4.400        1.40 versicolor
## 133          4.8          NA        1.900        0.20     setosa
## 134          5.1         3.3        1.700        0.50     setosa
## 135          4.4         3.0        1.300          NA     setosa
## 136          7.7         3.0           NA        2.30  virginica
## 137          4.7         3.2        1.600        0.20     setosa
## 138           NA         3.0        4.900        1.80  virginica
## 139          6.9         3.1        5.400        2.10  virginica
## 140          6.0         2.2        4.000        1.00 versicolor
## 141          5.0          NA        1.400        0.20     setosa
## 142          5.5          NA        3.800        1.10 versicolor
## 143          6.6         3.0        4.400        1.40 versicolor
## 144          6.3         2.9        5.600        1.80  virginica
## 145          5.7         2.5        5.000        2.00  virginica
## 146          6.7         3.1        5.600        2.40  virginica
## 147          5.6         3.0        4.500        1.50 versicolor
## 148          5.2         3.5        1.500        0.20     setosa
## 149          6.4         3.1           NA        1.80  virginica
## 150          5.8         2.6        4.000          NA versicolor
## 
## $corrections
##   row    variable old new
## 1  16 Sepal.Width  -3  NA
## 2 130 Sepal.Width   0  NA
##                                                             how
## 1 if (!is.na(Sepal.Width) && Sepal.Width <= 0) Sepal.Width = NA
## 2 if (!is.na(Sepal.Width) && Sepal.Width <= 0) Sepal.Width = NA

Replace all erroneous values with NA using (the result of) localizeErrors:

mydata[localizeErrors(RULE, mydata)$adapt] <- NA
# anything violated?
any(violatedEdits(RULE,mydata), na.rm=TRUE)
## [1] FALSE

Well done! No errors!

dlookr package

After you have acquired the data, you should do the following:

  • Diagnose data quality.
    • If there is a problem with data quality,
    • The data must be corrected or re-acquired.
  • Explore data to understand the data and find scenarios for performing the analysis.
  • Derive new variables or perform variable transformations.

The dlookr package makes these steps fast and easy:

  • Performs an data diagnosis or automatically generates a data diagnosis report.
  • Discover data in a variety of ways, and automatically generate EDA(exploratory data analysis) report.
  • Impute missing values and outliers, resolve skewed data, and categorize continuous variables into categorical variables. And generates an automated report to support it.

Here we will introduce data transformation methods provided by the dlookr package. You will learn how to transform of tbl_df data that inherits from data.frame and data.frame with functions provided by dlookr.

dlookr increases synergy with dplyr. Particularly in data transformation and data wrangle, it increases the efficiency of the tidyverse package group.

To illustrate the basic use of data transformation in the dlookr package, I use a Carseats dataset. Carseats in the ISLR package is simulation dataset that sells children’s car seats at 400 stores. This data is a data.frame created for the purpose of predicting sales volume.

str(Carseats)
## 'data.frame':    400 obs. of  11 variables:
##  $ Sales      : num  9.5 11.22 10.06 7.4 4.15 ...
##  $ CompPrice  : num  138 111 113 117 141 124 115 136 132 132 ...
##  $ Income     : num  73 48 35 100 64 113 105 81 110 113 ...
##  $ Advertising: num  11 16 10 4 3 13 0 15 0 0 ...
##  $ Population : num  276 260 269 466 340 501 45 425 108 131 ...
##  $ Price      : num  120 83 80 97 128 72 108 120 124 124 ...
##  $ ShelveLoc  : Factor w/ 3 levels "Bad","Good","Medium": 1 2 3 3 1 1 3 2 3 3 ...
##  $ Age        : num  42 65 59 55 38 78 71 67 76 76 ...
##  $ Education  : num  17 10 12 14 13 16 15 10 10 17 ...
##  $ Urban      : Factor w/ 2 levels "No","Yes": 2 2 2 2 2 1 2 2 1 1 ...
##  $ US         : Factor w/ 2 levels "No","Yes": 2 2 2 2 1 2 1 2 1 2 ...

The contents of individual variables are as follows. (Refer to ISLR::Carseats Man page)

  • Sales
    • Unit sales (in thousands) at each location
  • CompPrice
    • Price charged by competitor at each location
  • Income
    • Community income level (in thousands of dollars)
  • Advertising
    • Local advertising budget for company at each location (in thousands of dollars)
  • Population
    • Population size in region (in thousands)
  • Price
    • Price company charges for car seats at each site
  • ShelveLoc
    • A factor with levels Bad, Good and Medium indicating the quality of the shelving location for the car seats at each site
  • Age
    • Average age of the local population
  • Education
    • Education level at each location
  • Urban
    • A factor with levels No and Yes to indicate whether the store is in an urban or rural location
  • US
    • A factor with levels No and Yes to indicate whether the store is in the US or not

When data analysis is performed, data containing missing values is often encountered. However, Carseats is complete data without missing. Therefore, the missing values are generated as follows. And I created a data.frame object named carseats.

carseats <- ISLR::Carseats
suppressWarnings(RNGversion("3.5.0"))
set.seed(123)
carseats[sample(seq(NROW(carseats)), 20), "Income"] <- NA
suppressWarnings(RNGversion("3.5.0"))
set.seed(456)
carseats[sample(seq(NROW(carseats)), 10), "Urban"] <- NA

Functions

dlookr imputes missing values and outliers and resolves skewed data. It also provides the ability to bin continuous variables as categorical variables.

Here is a list of the data conversion functions and functions provided by dlookr:

  • find_na() finds a variable that contains the missing values variable, and imputate_na() imputes the missing values.
  • find_outliers() finds a variable that contains the outliers, and imputate_outlier() imputes the outlier.
  • summary.imputation() and plot.imputation() provide information and visualization of the imputed variables.
  • find_skewness() finds the variables of the skewed data, and transform() performs the resolving of the skewed data.
  • transform() also performs standardization of numeric variables.
  • summary.transform() and plot.transform() provide information and visualization of transformed variables.
  • binning() and binning_by() convert binational data into categorical data.
  • print.bins() and summary.bins() show and summarize the binning results.
  • plot.bins() and plot.optimal_bins() provide visualization of the binning result.
  • transformation_report() performs the data transform and reports the result.

Imputations of NA’s

imputate_na() imputes the missing value contained in the variable. The predictor with missing values support both numeric and categorical variables, and supports the following method.

Imputation is the process of estimating or deriving values for fields where data is missing. There is a vast body of literature on imputation methods and it goes beyond the scope of this tutorial to discuss all of them.

There is no one single best imputation method that works in all cases. The imputation model of choice depends on what auxiliary information is available and whether there are (multivariate) edit restrictions on the data to be imputed. The availability of R software for imputation under edit restrictions is, to our best knowledge, limited. However, a viable strategy for imputing numerical data is to first impute missing values without restrictions, and then minimally adjust the imputed values so that the restrictions are obeyed. Separately, these methods are available in R.

  • predictor is numerical variable
    • “mean” : arithmetic mean
    • “median” : median
    • “mode” : mode
    • “knn” : K-nearest neighbors
      • target variable must be specified
    • “rpart” : Recursive Partitioning and Regression Trees
      • target variable must be specified
    • “mice” : Multivariate Imputation by Chained Equations
      • target variable must be specified
      • random seed must be set
  • predictor is categorical variable
    • “mode” : mode
    • “rpart” : Recursive Partitioning and Regression Trees
      • target variable must be specified
    • “mice” : Multivariate Imputation by Chained Equations
      • target variable must be specified
      • random seed must be set

Example

In the following example, imputate_na() imputes the missing value of Income, a numeric variable of carseats, using the “rpart” method. summary() summarizes missing value imputation information, and plot() visualizes missing information.

income <- imputate_na(carseats, Income, US, method = "rpart")
  # summary of imputation
  summary(income)
## * Impute missing values based on Recursive Partitioning and Regression Trees
##  - method : rpart
## 
## * Information of Imputation (before vs after)
##                     Original     Imputation  
## described_variables "value"      "value"     
## n                   "380"        "400"       
## na                  "20"         " 0"        
## mean                "68.86053"   "69.05073"  
## sd                  "28.09161"   "27.57382"  
## se_mean             "1.441069"   "1.378691"  
## IQR                 "48.25"      "46.00"     
## skewness            "0.04490600" "0.02935732"
## kurtosis            "-1.089201"  "-1.035086" 
## p00                 "21"         "21"        
## p01                 "21.79"      "21.99"     
## p05                 "26"         "26"        
## p10                 "30.0"       "30.9"      
## p20                 "39"         "40"        
## p25                 "42.75"      "44.00"     
## p30                 "48.00000"   "51.58333"  
## p40                 "62"         "63"        
## p50                 "69"         "69"        
## p60                 "78.0"       "77.4"      
## p70                 "86.3"       "84.3"      
## p75                 "91"         "90"        
## p80                 "96.2"       "96.0"      
## p90                 "108.1"      "106.1"     
## p95                 "115.05"     "115.00"    
## p99                 "119.21"     "119.01"    
## p100                "120"        "120"
  # viz of imputation
  plot(income)

Example

The following imputes the categorical variable urban by the “mice” method.

library(mice)
## 
## Dołączanie pakietu: 'mice'
## Następujący obiekt został zakryty z 'package:stats':
## 
##     filter
## Następujące obiekty zostały zakryte z 'package:base':
## 
##     cbind, rbind
urban <- imputate_na(carseats, Urban, US, method = "mice")
## 
##  iter imp variable
##   1   1  Income  Urban
##   1   2  Income  Urban
##   1   3  Income  Urban
##   1   4  Income  Urban
##   1   5  Income  Urban
##   2   1  Income  Urban
##   2   2  Income  Urban
##   2   3  Income  Urban
##   2   4  Income  Urban
##   2   5  Income  Urban
##   3   1  Income  Urban
##   3   2  Income  Urban
##   3   3  Income  Urban
##   3   4  Income  Urban
##   3   5  Income  Urban
##   4   1  Income  Urban
##   4   2  Income  Urban
##   4   3  Income  Urban
##   4   4  Income  Urban
##   4   5  Income  Urban
##   5   1  Income  Urban
##   5   2  Income  Urban
##   5   3  Income  Urban
##   5   4  Income  Urban
##   5   5  Income  Urban
# result of imputation
urban
##   [1] Yes Yes Yes Yes Yes No  Yes Yes No  No  No  Yes Yes Yes Yes No  Yes Yes
##  [19] No  Yes Yes No  Yes Yes Yes No  No  Yes Yes Yes Yes Yes Yes Yes Yes No 
##  [37] No  Yes Yes No  No  Yes Yes Yes Yes Yes No  Yes Yes Yes Yes Yes Yes Yes
##  [55] No  Yes Yes Yes Yes Yes Yes No  Yes Yes No  No  Yes Yes Yes Yes Yes No 
##  [73] Yes No  No  No  Yes No  Yes Yes Yes Yes Yes Yes No  No  Yes No  Yes No 
##  [91] No  Yes Yes No  Yes Yes No  Yes No  No  No  Yes No  Yes Yes Yes No  Yes
## [109] Yes No  Yes Yes Yes Yes Yes Yes No  Yes Yes Yes Yes Yes Yes No  Yes No 
## [127] Yes Yes Yes No  Yes Yes Yes Yes Yes No  No  Yes Yes No  Yes Yes Yes Yes
## [145] No  Yes Yes No  No  Yes Yes No  No  No  No  Yes Yes No  No  No  No  No 
## [163] Yes No  No  Yes Yes Yes Yes Yes Yes Yes Yes Yes No  Yes No  Yes No  Yes
## [181] Yes Yes Yes Yes No  Yes No  Yes Yes No  No  Yes No  Yes Yes Yes Yes Yes
## [199] Yes Yes No  Yes No  Yes Yes Yes Yes No  Yes No  No  Yes Yes Yes Yes Yes
## [217] Yes No  Yes Yes Yes Yes Yes Yes No  Yes Yes Yes No  No  No  No  Yes No 
## [235] No  Yes Yes Yes Yes Yes Yes Yes No  Yes Yes No  Yes Yes Yes Yes Yes Yes
## [253] Yes No  Yes Yes Yes Yes No  No  Yes Yes Yes Yes Yes Yes No  No  Yes Yes
## [271] Yes Yes Yes Yes Yes Yes Yes Yes No  Yes Yes No  Yes No  No  Yes No  Yes
## [289] No  Yes No  No  Yes Yes Yes No  Yes Yes Yes No  Yes Yes Yes Yes Yes Yes
## [307] Yes Yes Yes Yes Yes Yes Yes Yes Yes Yes Yes No  No  No  Yes Yes Yes Yes
## [325] Yes Yes Yes Yes Yes Yes No  Yes Yes Yes Yes Yes Yes Yes No  Yes Yes No 
## [343] No  Yes No  Yes No  No  Yes No  No  No  Yes No  Yes Yes Yes Yes Yes Yes
## [361] No  No  Yes Yes Yes No  No  Yes No  Yes Yes Yes No  Yes Yes Yes Yes No 
## [379] Yes Yes Yes Yes Yes Yes Yes Yes Yes No  Yes Yes Yes Yes Yes No  Yes Yes
## [397] No  Yes Yes Yes
## attr(,"var_type")
## [1] categorical
## attr(,"method")
## [1] mice
## attr(,"na_pos")
##  [1]  33  36  84  94 113 132 151 292 313 339
## attr(,"seed")
## [1] 24283
## attr(,"type")
## [1] missing values
## attr(,"message")
## [1] complete imputation
## attr(,"success")
## [1] TRUE
## Levels: No Yes
# summary of imputation
summary(urban)
## * Impute missing values based on Multivariate Imputation by Chained Equations
##  - method : mice
##  - random seed : 24283
## 
## * Information of Imputation (before vs after)
##      original imputation original_percent imputation_percent
## No        115        119            28.75              29.75
## Yes       275        281            68.75              70.25
## <NA>       10          0             2.50               0.00
# viz of imputation
plot(urban)

In the following exercise try to impute the missing value of the Income variable, and then calculates the arithmetic mean for each level of US. In this case, dplyr should be used, and it is easily interpreted logically using pipes!

Exercise 3.

# The mean before and after the imputation of the Income variable

Imputation of outliers

imputate_outlier() imputes the outliers value. The predictor with outliers supports only numeric variables and supports the following methods.

  • predictor is numerical variable
    • “mean” : arithmetic mean
    • “median” : median
    • “mode” : mode
    • “capping” : Impute the upper outliers with 95 percentile, and Impute the bottom outliers with 5 percentile.

Exercise 4.

Use the function imputate_outlier() to impute the outliers of the numeric variable Price as the “capping” method. Hint: summary() summarizes outliers imputation information, and plot() visualizes imputation information.

# solution for the exercise 4 here ;-)

The following example imputes the outliers of the Price variable, and then calculates the arithmetic mean for each level of US. In this case, dplyr is used, and it is easily interpreted logically using pipes.

Example

# The mean before and after the imputation of the Price variable
carseats %>%
  mutate(Price_imp = imputate_outlier(carseats, Price, method = "capping")) %>%
  group_by(US) %>%
  summarise(orig = mean(Price, na.rm = TRUE),
    imputation = mean(Price_imp, na.rm = TRUE))
## # A tibble: 2 × 3
##   US     orig imputation
##   <fct> <dbl>      <dbl>
## 1 No     114.       114.
## 2 Yes    117.       117.

Transformations

Finally, we sometimes encounter the situation where we have problems with skewed distributions or we just want to transform, recode or perform discretization. Let’s review some of the most popular transformation methods.

First, standardization (also known as normalization):

  • Z-score approach - standardization procedure, using the formula: \(z=\frac{x-\mu}{\sigma}\) where \(\mu\) = mean and \(\sigma\) = standard deviation. Z-scores are also known as standardized scores; they are scores (or data values) that have been given a common standard. This standard is a mean of zero and a standard deviation of 1.

  • minmax approach - An alternative approach to Z-score normalization (or standardization) is the so-called MinMax scaling (often also simply called “normalization” - a common cause for ambiguities). In this approach, the data is scaled to a fixed range - usually 0 to 1. The cost of having this bounded range - in contrast to standardization - is that we will end up with smaller standard deviations, which can suppress the effect of outliers. If you would like to perform MinMax scaling - simply substract minimum value and divide it by range:\((x-min)/(max-min)\)

In order to solve problems with very skewed distributions we can also use several types of simple transformations:

+ "log" : log transformation. log(x)
+ "log+1" : log transformation. log(x + 1). Used for values that contain 0.
+ "sqrt" : square root transformation.
+ "1/x" : 1 / x transformation
+ "x^2" : x square transformation
+ "x^3" : x^3 square transformation

Standardization

Use the methods “zscore” and “minmax” to perform standardization.

carseats %>% 
  mutate(Income_minmax = transform(carseats$Income, method = "minmax"),
    Sales_minmax = transform(carseats$Sales, method = "minmax")) %>% 
  select(Income_minmax, Sales_minmax) %>% 
  boxplot()

Binning

Sometimes we just would like to perform so called ‘binning’ procedure to be able to analyze our categorical data, to compare several categorical variables, to construct statistical models etc. Thanks to the ‘binning’ function we can transform quantitative variables into categorical using several methods:

binning() transforms a numeric variable into a categorical variable by binning it. The following types of binning are supported.

  • “quantile” : categorize using quantile to include the same frequencies
  • “equal” : categorize to have equal length segments
  • “pretty” : categorized into moderately good segments
  • “kmeans” : categorization using K-means clustering
  • “bclust” : categorization using bagged clustering technique

Here are some examples of how to bin Income using binning().:

Example:

# Binning the carat variable. default type argument is "quantile"
bin <- binning(carseats$Income)
# Print bins class object
bin
## binned type: quantile
## number of bins: 10
## x
##         [21,30]         (30,39]         (39,48]         (48,62]         (62,69] 
##              40              37              38              40              42 
##         (69,78]   (78,86.56667] (86.56667,96.6] (96.6,108.6333]  (108.6333,120] 
##              33              36              38              38              38 
##            <NA> 
##              20
# Summarize bins class object
summary(bin)
##             levels freq   rate
## 1          [21,30]   40 0.1000
## 2          (30,39]   37 0.0925
## 3          (39,48]   38 0.0950
## 4          (48,62]   40 0.1000
## 5          (62,69]   42 0.1050
## 6          (69,78]   33 0.0825
## 7    (78,86.56667]   36 0.0900
## 8  (86.56667,96.6]   38 0.0950
## 9  (96.6,108.6333]   38 0.0950
## 10  (108.6333,120]   38 0.0950
## 11            <NA>   20 0.0500
# Plot bins class object
plot(bin)

Using pipes & dplyr:

 carseats %>%
 mutate(Income_bin = binning(carseats$Income) %>% 
                     extract()) %>%
 group_by(ShelveLoc, Income_bin) %>%
 summarise(freq = n()) %>%
 arrange(desc(freq)) %>%
 head(10)
## `summarise()` has grouped output by 'ShelveLoc'. You can override using the
## `.groups` argument.
## # A tibble: 10 × 3
## # Groups:   ShelveLoc [1]
##    ShelveLoc Income_bin       freq
##    <fct>     <ord>           <int>
##  1 Medium    [21,30]            25
##  2 Medium    (62,69]            24
##  3 Medium    (48,62]            23
##  4 Medium    (39,48]            21
##  5 Medium    (30,39]            20
##  6 Medium    (86.56667,96.6]    20
##  7 Medium    (108.6333,120]     20
##  8 Medium    (69,78]            18
##  9 Medium    (96.6,108.6333]    18
## 10 Medium    (78,86.56667]      17

Example: Recode the original distribution of incomes using fixed length of intervals and assign them labels.

income_fixed<- binning(carseats$Income, nbins = 4,
                   labels = c("low", "average", "high", "very high"))
summary(income_fixed)
##      levels freq   rate
## 1       low   95 0.2375
## 2   average  102 0.2550
## 3      high   89 0.2225
## 4 very high   94 0.2350
## 5      <NA>   20 0.0500
plot(income_fixed)

In case of statistical modeling (i.e. credit scoring purposes) - we need to be aware of the fact, that the optimal discretization of the original distribution must be achieved. The ‘binning_by’ function comes with some help here.

Example: Perform discretization of the variable ‘Advertising’ using optimal binning.

bin <- binning_by(carseats, "US", "Advertising")
## Warning in binning_by(carseats, "US", "Advertising"): The factor y has been changed to a numeric vector consisting of 0 and 1.
## 'Yes' changed to 1 (positive) and 'No' changed to 0 (negative).
summary(bin)
## ── Binning Table ──────────────────────── Several Metrics ── 
##      Bin CntRec CntPos CntNeg RatePos RateNeg    Odds      WoE      IV     JSD
## 1 [-1,0]    144     19    125 0.07364 0.88028  0.1520 -2.48101 2.00128 0.20093
## 2  (0,6]     69     54     15 0.20930 0.10563  3.6000  0.68380 0.07089 0.00869
## 3 (6,29]    187    185      2 0.71705 0.01408 92.5000  3.93008 2.76272 0.21861
## 4  Total    400    258    142 1.00000 1.00000  1.8169       NA 4.83489 0.42823
##       AUC
## 1 0.03241
## 2 0.01883
## 3 0.00903
## 4 0.06028
## 
## ── General Metrics ───────────────────────────────────────── 
## • Gini index                       :  -0.87944
## • IV (Jeffrey)                     :  4.83489
## • JS (Jensen-Shannon) Divergence   :  0.42823
## • Kolmogorov-Smirnov Statistics    :  0.80664
## • HHI (Herfindahl-Hirschman Index) :  0.37791
## • HHI (normalized)                 :  0.06687
## • Cramer's V                       :  0.81863 
## 
## ── Significance Tests ──────────────────── Chisquare Test ── 
##    Bin A  Bin B statistics      p_value
## 1 [-1,0]  (0,6]   87.67064 7.731349e-21
## 2  (0,6] (6,29]   34.73349 3.780706e-09
plot(bin)

Please take a look once again at the final report of our binning procedure. Try to interpret information criterion (Jeffrey’s or K-S) and plots.

We can finally print some summary reports after the cleansing procedures are all done - in the PDF format (TEX installation is required first) or HTML format. Please use this chunk in your own RStudio in order to produce some reports.

carseats %>%
  transformation_report(target = US, output_format = "html", 
                        output_file = "transformation_carseats.html")

Exercise 5.

Use quantile approach to perform binning of the variable ‘Income’ (4 equal categories).

# solution for the exercise 5 here ;-)

Automated report

dlookr provides two automated data transformation reports:

  • Web page-based dynamic reports can perform in-depth analysis through visualization and statistical tables.
  • Static reports generated as pdf files or html files can be archived as output of data analysis.

Dynamic report

transformation_web_report() creates dynamic report for object inherited from data.frame(tbl_df, tbl, etc) or data.frame.

The contents of the report are as follows.:

  • Overview
    • Data Structures
    • Data Types
    • Job Informations
  • Imputation
    • Missing Values
    • Outliers
  • Resolving Skewness
  • Binning
  • Optimal Binning

The following script creates a data transformation report for the tbl_df class object, heartfailure.

heartfailure %>% transformation_web_report(target = “death_event”, subtitle = “heartfailure”,output_dir = “./”, output_file = “transformation.html”, theme = “blue”)

  • The dynamic contents of the report is shown in the following figure.:

Static report

transformation_paged_report() create static report for object inherited from data.frame(tbl_df, tbl, etc) or data.frame.

The contents of the report are as follows.:

  • Overview
    • Data Structures
    • Job Informations
  • Imputation
    • Missing Values
    • Outliers
  • Resolving Skewness
  • Binning
  • Optimal Binning

The following script creates a data transformation report for the data.frame class object, heartfailure.

heartfailure %>% transformation_paged_report(target = “death_event”, subtitle = “heartfailure”, output_dir = “./”, output_file = “transformation.pdf”, theme = “blue”)

  • The cover of the report is shown in the following figure.:

Diagnostic report

diagnose_paged_report() creates static report for object inherited from data.frame(tbl_df, tbl, etc) or data.frame.

The following script creates a quality diagnosis report for the tbl_df class object, flights.

flights %>% diagnose_paged_report(subtitle = “flights”, output_dir = “./”, output_file = “Diagn.pdf”, theme = “blue”)

The cover of the report is shown in the following figure.:

EDA Report

dlookr provides two automated EDA reports:

  • Web page-based dynamic reports can perform in-depth analysis through visualization and statistical tables.

  • Static reports generated as pdf files or html files can be archived as output of data analysis.

eda_web_report() creates dynamic report for object inherited from data.frame(tbl_df, tbl, etc) or data.frame.

The following script creates a EDA report for the data.frame class object, heartfailure:

heartfailure %>% eda_web_report(target = “death_event”, subtitle = “heartfailure”, output_dir = “./”, output_file = “EDA.html”, theme = “blue”)

The dynamic contents of the report is shown in the following figure.:

Transformation Report

dlookr provides two automated data transformation reports:

  • Web page-based dynamic reports can perform in-depth analysis through visualization and statistical tables.
  • Static reports generated as pdf files or html files can be archived as output of data analysis.

transformation_web_report() creates dynamic report for object inherited from data.frame(tbl_df, tbl, etc) or data.frame.

The following script creates a data transformation report for the tbl_df class object, heartfailure:

heartfailure %>% transformation_web_report(target = “death_event”, subtitle = “heartfailure”, output_dir = “./”, output_file = “transformation.html”, theme = “blue”)

The dynamic contents of the report is shown in the following figure.:

Summary

If we are to take away one thing from this TIDY WEEK it should be that dealing with missing data is not an easy problem. Data will rarely be missing in a nice (completely random) way, so if we want to resort to more simple removal or imputation techniques, we must put reasonable effort into determining whether or not the dependencies between observed data and missingness are a cause for concern. If strong dependencies exist and/or there is a lot of data missing, the missing data problem becomes a prediction problem (or a series of prediction problems). We should also be aware of the possibility that missingness depends on the missing value in a way that can’t be explained by observed variables. This can also cause bias in our analyses and we will not be able to detect it unless we get our hands on some of the missing values.

Note that we focused only on standard cross-sectional data. There are many other types of data, such as time-series data, spatial data, images, sound, graphs, etc. The basic principles remain unchanged. We must be aware of the missingness mechanism and introducing bias. We can deal with missing values either by removing observations/variables or by imputing them. However, different, sometimes additional models and techniques will be appropriate. For example, temporal and spatial data lend themselves to interpolation of missing values.

Further reading

  • A gentle introduction from a practitioners perspective: Blankers, M., Koeter, M. W., & Schippers, G. M. (2010). Missing data approaches in eHealth research: simulation study and a tutorial for nonmathematically inclined researchers. Journal of medical Internet research, 12(5), e54.

  • A great book on basic and some advance techniques: Allison, P. D. (2001). Missing data (Vol. 136). Sage publications.

  • Another great book with many examples and case-studies: Van Buuren, S. (2018). Flexible imputation of missing data. Chapman and Hall/CRC.

  • Understanding multiple imputation with chained equations (in R): Buuren, S. V., & Groothuis-Oudshoorn, K. (2010). mice: Multivariate imputation by chained equations in R. Journal of statistical software, 1-68.

  • When doing missing data value handling and prediction separately, we should be aware that certain types of prediction models might work better with certain methods for handling missing values. A paper that illustrate this: Yadav, M. L., & Roychoudhury, B. (2018). Handling missing values: A study of popular imputation packages in R. Knowledge-Based Systems, 160, 104-118.

Learning outcomes

Data science students should work towards obtaining the knowledge and the skills that enable them to:

  • Reproduce the techniques demonstrated in this chapter using their language/tool of choice.
  • Analyze the severity of the missing data problem.
  • Recognize when a technique is appropriate and what are its limitations.
LS0tDQp0aXRsZTogIlRpZHkgV2VlayAyOiBEYXRhIENsZWFuc2luZyINCmF1dGhvcjogIllvdXIgTmFtZSINCm91dHB1dDoNCiAgaHRtbF9kb2N1bWVudDogDQogICAgdGhlbWU6IGNlcnVsZWFuDQogICAgaGlnaGxpZ2h0OiB0ZXh0bWF0ZQ0KICAgIGZvbnRzaXplOiA4cHQNCiAgICB0b2M6IHllcw0KICAgIGNvZGVfZG93bmxvYWQ6IHllcw0KICAgIHRvY19mbG9hdDoNCiAgICAgIGNvbGxhcHNlZDogbm8NCiAgICBkZl9wcmludDogZGVmYXVsdA0KICAgIHRvY19kZXB0aDogNQ0KZWRpdG9yX29wdGlvbnM6IA0KICBtYXJrZG93bjogDQogICAgd3JhcDogNzINCi0tLQ0KDQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0NCmxpYnJhcnkoZGxvb2tyKQ0KbGlicmFyeShlZGl0cnVsZXMpDQpsaWJyYXJ5KGRlZHVjb3JyZWN0KQ0KbGlicmFyeShJU0xSKSANCmxpYnJhcnkocmFuZG9tRm9yZXN0KQ0KbGlicmFyeShkcGx5cikNCmxpYnJhcnkoVklNKQ0KbGlicmFyeShnZ3Bsb3QyKQ0KbGlicmFyeShyc3RhdGl4KQ0KbGlicmFyeShuYW5pYXIpDQpteWRhdGEgPC0gcmVhZC5jc3YoImh0dHBzOi8vcmF3LmdpdGh1Yi5jb20vZWR3aW5kai9kYXRhY2xlYW5pbmcvbWFzdGVyL2RhdGEvZGlydHlfaXJpcy5jc3YiLCBoZWFkZXI9VFJVRSwgc2VwPSIsIikNCg0KY2Fyc2VhdHMgPC0gSVNMUjo6Q2Fyc2VhdHMgIA0Kc3VwcHJlc3NXYXJuaW5ncyhSTkd2ZXJzaW9uKCIzLjUuMCIpKSANCnNldC5zZWVkKDEyMykgDQpjYXJzZWF0c1tzYW1wbGUoc2VxKE5ST1coY2Fyc2VhdHMpKSwgMjApLCAiSW5jb21lIl0gPC0gTkEgDQpzdXBwcmVzc1dhcm5pbmdzKFJOR3ZlcnNpb24oIjMuNS4wIikpIA0Kc2V0LnNlZWQoNDU2KSANCmNhcnNlYXRzW3NhbXBsZShzZXEoTlJPVyhjYXJzZWF0cykpLCAxMCksICJVcmJhbiJdIDwtIE5BDQpgYGANCg0KIyMgSW50cm9kdWN0aW9uDQoNCkFuYWx5c2lzIG9mIGRhdGEgaXMgYSBwcm9jZXNzIG9mIGluc3BlY3RpbmcsIGNsZWFuaW5nLCB0cmFuc2Zvcm1pbmcsIGFuZCBtb2RlbGluZyBkYXRhIHdpdGggdGhlIGdvYWwgb2YgaGlnaGxpZ2h0aW5nIHVzZWZ1bCBpbmZvcm1hdGlvbiwgc3VnZ2VzdGluZyBjb25jbHVzaW9ucyBhbmQgc3VwcG9ydGluZyBkZWNpc2lvbiBtYWtpbmcuDQoNCmBgYHtyIGZpZzEsIGVjaG8gPSBGQUxTRSwgb3V0LndpZHRoID0gIjYwJSIsIGZpZy5hbGlnbiA9ICdjZW50ZXInfQ0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9rZmxpc2lrb3dza2kvZHMvbWFzdGVyL2RzLnBuZyIpDQpgYGANCg0KTWFueSB0aW1lcyBpbiB0aGUgYmVnaW5uaW5nIHdlIHNwZW5kIGhvdXJzIG9uIGhhbmRsaW5nIHByb2JsZW1zIHdpdGggbWlzc2luZyB2YWx1ZXMsIGxvZ2ljYWwgaW5jb25zaXN0ZW5jaWVzIG9yIG91dGxpZXJzIGluIG91ciBkYXRhc2V0cy4gSW4gdGhpcyB0dXRvcmlhbCB3ZSB3aWxsIGdvIHRocm91Z2ggdGhlIG1vc3QgcG9wdWxhciB0ZWNobmlxdWVzIGluIGRhdGEgY2xlYW5zaW5nLg0KDQpXZSB3aWxsIGJlIHdvcmtpbmcgd2l0aCB0aGUgbWVzc3kgZGF0YXNldCBgaXJpc2AuIE9yaWdpbmFsbHkgcHVibGlzaGVkIGF0IFVDSSBNYWNoaW5lIExlYXJuaW5nIFJlcG9zaXRvcnk6IElyaXMgRGF0YSBTZXQsIHRoaXMgc21hbGwgZGF0YXNldCBmcm9tIDE5MzYgaXMgb2Z0ZW4gdXNlZCBmb3IgdGVzdGluZyBvdXQgbWFjaGluZSBsZWFybmluZyBhbGdvcml0aG1zIGFuZCB2aXN1YWxpemF0aW9ucy4gRWFjaCByb3cgb2YgdGhlIHRhYmxlIHJlcHJlc2VudHMgYW4gaXJpcyBmbG93ZXIsIGluY2x1ZGluZyBpdHMgc3BlY2llcyBhbmQgZGltZW5zaW9ucyBvZiBpdHMgYm90YW5pY2FsIHBhcnRzLCBzZXBhbCBhbmQgcGV0YWwsIGluIGNlbnRpbWV0ZXJzLg0KDQpUYWtlIGEgbG9vayBhdCB0aGlzIGRhdGFzZXQgaGVyZToNCg0KYGBge3IgZWNobyA9IFRSVUV9DQpteWRhdGENCmBgYA0KDQoNCiMjIERlYWxpbmcgd2l0aCBOQSANCg0KSW4gYWxsIG9mIG91ciBkYXRhIGFuYWx5c2VzIHNvIGZhciB3ZSBpbXBsaWNpdGx5IGFzc3VtZWQgdGhhdCB3ZSBkb24ndCBoYXZlIGFueSBtaXNzaW5nIHZhbHVlcyBpbiBvdXIgZGF0YS4gSW4gcHJhY3RpY2UsIHRoYXQgaXMgb2Z0ZW4gbm90IHRoZSBjYXNlLiBXaGlsZSBzb21lIHN0YXRpc3RpY2FsIGFuZCBtYWNoaW5lIGxlYXJuaW5nIG1ldGhvZHMgd29yayB3aXRoIG1pc3NpbmcgZGF0YSwgbWFueSBjb21tb25seSB1c2VkIG1ldGhvZHMgY2FuJ3QsIHNvIGl0IGlzIGltcG9ydGFudCB0byBsZWFybiBob3cgdG8gZGVhbCB3aXRoIG1pc3NpbmcgdmFsdWVzLiANCg0KIyMjIFRoZSBzZXZlcml0eSBvZiBOQQ0KDQpJZiB3ZSBqdWRnZSBieSB0aGUgaW1wdXRhdGlvbiBvciBkYXRhIHJlbW92aW5nIG1ldGhvZHMgdGhhdCBhcmUgbW9zdCBjb21tb25seSB1c2VkIGluIHByYWN0aWNlLCB3ZSBtaWdodCBjb25jbHVkZSB0aGF0IG1pc3NpbmcgZGF0YSBpcyBhIHJlbGF0aXZlbHkgc2ltcGxlIHByb2JsZW0gdGhhdCBpcyBzZWNvbmRhcnkgdG8gdGhlIGluZmVyZW5jZSwgcHJlZGljdGl2ZSBtb2RlbGxpbmcsIGV0Yy4gdGhhdCBhcmUgdGhlIHByaW1hcnkgZ29hbCBvZiBvdXIgYW5hbHlzaXMuIFVuZm9ydHVuYXRlbHksIHRoYXQgaXMgbm90IHRoZSBjYXNlLiBEZWFsaW5nIHdpdGggbWlzc2luZyB2YWx1ZXMgaXMgdmVyeSBjaGFsbGVuZ2luZywgb2Z0ZW4gaW4gaXRzZWxmIGEgbW9kZWxsaW5nIHByb2JsZW0uDQoNCiMjIyBUaHJlZSBjbGFzc2VzIG9mIE5BJ3MNCg0KVGhlIGNob2ljZSBvZiBhbiBhcHByb3ByaWF0ZSBtZXRob2QgaXMgaW5zZXBhcmFibGUgZnJvbSBvdXIgdW5kZXJzdGFuZGluZyBvZiBvciBhc3N1bXB0aW9ucyBhYm91dCB0aGUgcHJvY2VzcyB0aGF0IGdlbmVyYXRlZCB0aGUgbWlzc2luZyB2YWx1ZXMgKHRoZSAqbWlzc2luZ25lc3MgbWVjaGFuaXNtKikuIEJhc2VkIG9uIHRoZSBjaGFyYWN0ZXJpc3RpY3Mgb2YgdGhpcyBwcm9jZXNzIHdlIHR5cGljYWxseSBjaGFyYWN0ZXJpemUgdGhlIG1pc3NpbmcgZGF0YSBwcm9ibGVtIGFzIG9uZSBvZiB0aGVzZSB0aHJlZSBjYXNlczogIA0KDQphLiAqKk1DQVIqKiAoTWlzc2luZyBDb21wbGV0ZWx5IEF0IFJhbmRvbSk6IFdoZXRoZXIgb3Igbm90IGEgdmFsdWUgaXMgbWlzc2luZyBpcyBpbmRlcGVuZGVudCBvZiBib3RoIHRoZSBvYnNlcnZlZCB2YWx1ZXMgYW5kIHRoZSBtaXNzaW5nICh1bm9ic2VydmVkKSB2YWx1ZXMuIEZvciBleGFtcGxlLCBpZiB3ZSBoYWQgdGVtcGVyYXR1cmUgbWVhc3VyaW5nIGRldmljZXMgYXQgZGlmZmVyZW50IGxvY2F0aW9ucyBhbmQgdGhleSBvY2Nhc3Npb25hbGx5IGFuZCByYW5kb20gaW50ZXJ2YWxzIHN0b3BwZWQgd29ya2luZy4gT3IsIGluIHN1cnZleXMsIHdoZXJlIHJlc3BvbmRlbnRzIGRvbid0IHJlc3BvbmQgd2l0aCBhIGNlcnRhaW4gcHJvYmFiaWxpdHksIGluZGVwZW5kZW50IG9mIHRoZSBjaGFyYWN0ZXJpc3RpY3MgdGhhdCB3ZSBhcmUgc3VydmV5aW5nLg0KDQpiLiAqKk1BUioqIChNaXNzaW5nIEF0IFJhbmRvbSk6IFdoZXRoZXIgb3Igbm90IGEgdmFsdWUgaXMgbWlzc2luZyBpcyBpbmRlcGVuZGVudCBvZiB0aGUgbWlzc2luZyAodW5vYnNlcnZlZCkgdmFsdWVzIGJ1dCBkZXBlbmRzIG9uIHRoZSBvYnNlcnZlZCB2YWx1ZXMuIFRoYXQgaXMsIHRoZXJlIGlzIGEgcGF0dGVybiB0byBob3cgdGhlIHZhbHVlcyBhcmUgbWlzc2luZywgYnV0IHdlIGNvdWxkIGZ1bGx5IGV4cGxhaW4gdGhhdCBwYXR0ZXJuIGdpdmVuIG9ubHkgdGhlIG9ic2VydmVkIGRhdGEuIEZvciBleGFtcGxlLCBpZiBvdXIgdGVtcGVyYXR1cmUgbWVhc3VyaW5nIGRldmljZXMgc3RvcHBlZCB3b3JraW5nIG1vcmUgb2Z0ZW4gaW4gY2VydGFpbiBsb2NhdGlvbnMgdGhhbiBpbiBvdGhlcnMuIE9yLCBpbiBzdXJ2ZXlzLCBpZiB3b21lbiBhcmUgbGVzcyBsaWtlbHkgdG8gcmVwb3J0IHRoZWlyIHdlaWdodCB0aGFuIG1lbi4NCg0KYy4gKipNTkFSKiogKE1pc3NpbmcgTm90IEF0IFJhbmRvbSk6IFdoZXRoZXIgb3Igbm90IGEgdmFsdWUgaXMgbWlzc2luZyBhbHNvIGRlcGVuZHMgb24gdGhlIG1pc3NpbmcgKHVub2JzZXJ2ZWQpIHZhbHVlcyBpbiBhIHdheSB0aGF0IGNhbid0IGJlIGV4cGxhaW5lZCBieSB0aGUgb2JzZXJ2ZWQgdmFsdWVzLiBUaGF0IGlzLCB0aGVyZSBpcyBhIHBhdHRlcm4gdG8gaG93IHRoZSB2YWx1ZXMgYXJlIG1pc3NpbmcsIGJ1dCB3ZSB3b3VsZG4ndCBiZSBhYmxlIHRvIGZ1bGx5IGV4cGxhaW4gaXQgd2l0aG91dCBvYnNlcnZpbmcgdGhlIHZhbHVlcyB0aGF0IGFyZSBtaXNzaW5nLiBGb3IgZXhhbXBsZSwgaWYgb3VyIHRlbXBlcmF0dXJlIG1lYXN1cmluZyBkZXZpY2UgaGFkIGEgdGVuZGVuY3kgdG8gc3RvcCB3b3JraW5nIHdoZW4gdGhlIHRlbXBlcmF0dXJlIGlzIHZlcnkgbG93LiBPciwgaW4gc3VydmV5cywgaWYgYSBwZXJzb24gd2FzIGxlc3MgbGlrZWx5IHRvIHJlcG9ydCB0aGVpciBzYWxhcnkgaWYgdGhlaXIgc2FsYXJ5IHdhcyBoaWdoLg0KDQojIyMgVGhlIE5BJ3MgbWVjaGFuaXNtDQoNCkV2ZXJ5IHZhcmlhYmxlIGluIG91ciBkYXRhIG1pZ2h0IGhhdmUgYSBkaWZmZXJlbnQgbWlzc2luZ25lc3MgbWVjaGFuaXNtLiBTbywgaG93IGRvIHdlIGRldGVybWluZSB3aGV0aGVyIGl0IGlzIE1DQVIsIE1BUiwgb3IgTU5BUj8NCg0KU2hvd2luZyB3aXRoIGEgcmVhc29uYWJsZSBkZWdyZWUgb2YgY2VydGFpbnR5IHRoYXQgdGhlIG1lY2hhbmlzbSBpcyBub3QgTUNBUiBpcyBlcXVpdmFsZW50IHRvIHNob3dpbmcgdGhhdCB0aGUgbWlzc2luZ25lc3MgKHdoZXRoZXIgb3Igbm90IGEgdmFsdWUgaXMgbWlzc2luZykgY2FuIGJlIHByZWRpY3RlZCBmcm9tIG9ic2VydmVkIHZhbHVlcy4gVGhhdCBpcywgaXQgaXMgYSBwcmVkaWN0aW9uIHByb2JsZW0gYW5kIGl0IGlzIHN1ZmZpY2llbnQgdG8gc2hvdyAqb25lIHdheSogdGhhdCBtaXNzaW5nbmVzcyBjYW4gYmUgcHJlZGljdGVkLiBPbiB0aGUgb3RoZXIgaGFuZCwgaXQgaXMgaW5mZWFzaWJsZSB0byBzaG93IHRoYXQgdGhlIG1lY2hhbmlzbSBpcyBNQ0FSLCBiZWNhdXNlIHRoYXQgd291bGQgcmVxdWlyZSB1cyB0byBzaG93IHRoYXQgdGhlcmUgaXMgKm5vIHdheSogb2YgcHJlZGljdGluZyBtaXNzaW5nbmVzcyBmcm9tIG9ic2VydmVkIHZhbHVlcy4gV2UgY2FuLCBob3dldmVyLCBydWxlIG91dCBjZXJ0YWluIGtpbmRzIG9mIGRlcGVuZGVuY3kgKGZvciBleGFtcGxlLCBsaW5lYXIgZGVwZW5kZW5jeSkuDQoNCkZvciBNTkFSLCB0aGUgc2l0dWF0aW9uIGlzIGV2ZW4gd29yc2UuIEluIGdlbmVyYWwsIGl0IGlzIGltcG9zc2libGUgdG8gZGV0ZXJtaW5lIGZyb20gdGhlIGRhdGEgdGhlIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIG1pc3NpbmduZXNzIGFuZCB0aGUgdmFsdWUgdGhhdCBpcyBtaXNzaW5nLCBiZWNhdXNlIHdlIGRvbid0IGtub3cgd2hhdCBpcyBtaXNzaW5nLiBUaGF0IGlzLCB1bmxlc3Mgd2UgYXJlIGFibGUgdG8gc29tZWhvdyBtZWFzdXJlIHRoZSB2YWx1ZXMgdGhhdCBhcmUgbWlzc2luZywgd2Ugd29uJ3QgYmUgYWJsZSB0byBkZXRlcm1pbmUgd2hldGhlciBvciBub3QgdGhlIG1pc3NpbmduZXNzIHJlZ2ltZSBpcyBNTkFSLiBHZXR0aW5nIG91ciBoYW5kcyBvbiB0aGUgbWlzc2luZyB2YWx1ZXMsIGhvd2V2ZXIsIGlzIGluIG1vc3QgY2FzZXMgaW1wb3NzaWJsZSBvciBpbmZlYXNpYmxlLg0KDQpUbyBzdW1tYXJpemUsIHdlJ2xsIG9mdGVuIGJlIGFibGUgdG8gc2hvdyB0aGF0IG91ciBtaXNzaW5nbmVzcyByZWdpbWUgaXMgbm90IE1DQVIgYW5kIG5ldmVyIHRoYXQgaXQgaXMgTUNBUi4gU3Vic2VxdWVudGx5LCB3ZSdsbCBvZnRlbiBrbm93IHRoYXQgdGhlIG1pc3NpbmduZXNzIHJlZ2ltZSBpcyBhdCBsZWFzdCBNQVIsIGJ1dCB3ZSdsbCByYXJlbHkgYmUgYWJsZSB0byBkZXRlcm1pbmUgd2hldGhlciBpdCBpcyBNQVIgb3IgTU5BUiwgdW5sZXNzIHdlIGNhbiBnZXQgb3VyIGhhbmRzIG9uIHRoZSBtaXNzaW5nIGRhdGEuIFRoZXJlZm9yZSwgaXQgYmVjb21lcyB2ZXJ5IGltcG9ydGFudCB0byB1dGlsaXplIG5vdCBvbmx5IGRhdGEgYnV0IGFsc28gZG9tYWluLXNwZWNpZmljIGJhY2tncm91bmQga25vd2xlZGdlLCB3aGVuIGFwcGxpY2FibGUuIEluIHBhcnRpY3VsYXIsIGtub3duIHJlbGF0aW9uc2hpcHMgYmV0d2VlbiB0aGUgdmFyaWFibGVzIGluIG91ciBkYXRhIGFuZCB3aGF0IGNhdXNlZCB0aGUgdmFsdWVzIHRvIGJlIG1pc3NpbmcuDQoNCiMjIyBDYXVzZXMgZm9yIE5BJ3MNCg0KVW5kZXJzdGFuZGluZyB0aGUgY2F1c2UgZm9yIG1pc3NpbmcgZGF0YSBjYW4gb2Z0ZW4gaGVscCB1cyBpZGVudGlmeSB0aGUgbWlzc2luZ25lc3MgbWVjaGFuaXNtIGFuZCBhdm9pZCBpbnRyb2R1Y2luZyBhIGJpYXMuIEluIGdlbmVyYWwsIHdlIGNhbiBzcGxpdCB0aGUgY2F1c2VzIGludG8gdHdvIGNsYXNzZXM6ICppbnRlbnRpb25hbCogb3IgKnVuaW50ZW50aW9uYWwqLiANCg0KSW50ZW50aW9uYWxseSBvciBzeXN0ZW1hdGljYWxseSBtaXNzaW5nIGRhdGEgYXJlIG1pc3NpbmcgYnkgZGVzaWduLiBGb3IgZXhhbXBsZToNCg0KKiBwYXRpZW50cyB0aGF0IGRpZCBub3QgZXhwZXJpZW5jZSBwYWluIHdlcmUgbm90IGFza2VkIHRvIHJhdGUgdGhlaXIgcGFpbiwNCiogYSBwYXRpZW50IHRoYXQgaGFzIG5vdCBiZWVuIGRpc2NoYXJnZWQgZnJvbSB0aGUgaG9zcGl0YWwgZG9lc24ndCBoYXZlIGEgJ2RheXMgc3BlbnQgaW4gaG9zcGl0YWwgY2FyZScgZGF0YSBwb2ludCAoYWx0aG91Z2ggd2UgY2FuIGluZmVyIGEgbG93ZXIgYm91bmQgZnJvbSB0aGUgZGF5IG9mIGFycml2YWwpIGFuZA0KKiBhIHBhcnRpY3VsYXIgcHJlZGljdGlvbiBhbGdvcml0aG0ncyBwZXJmb3JtYW5jZSB3YXMgbWVhc3VyZWQgb24gZGF0YXNldHMgd2l0aCBmZXdlciB0aGFuIDEwMCB2YXJpYWJsZXMsIGR1ZSB0byBpdHMgdGltZSBjb21wbGV4aXR5IGFuZA0KKiBzb21lIG1lYXN1cmVtZW50cyB3ZXJlIG5vdCBtYWRlIGJlY2F1c2UgaXQgd2FzIHRvbyBjb3N0bHkgdG8gbWFrZSBhbGwgb2YgdGhlbS4NCg0KVW5pbnRlbnRpb25hbGx5IG1pc3NpbmcgZGF0YSB3ZXJlIG5vdCBwbGFubmVkLiBGb3IgZXhhbXBsZToNCg0KKiBkYXRhIG1pc3NpbmcgZHVlIHRvIG1lYXN1cmVtZW50IGVycm9yLA0KKiBhIHN1YmplY3Qgc2tpcHBpbmcgYSBzdXJ2ZXkgcXVlc3Rpb24sIA0KKiBhIHBhdGllbnQgcHJlbWF0dXJlbHkgZHJvcHBpbmcgb3V0IG9mIGEgc3R1ZHkNCg0KYW5kIG90aGVyIHJlYXNvbnMgbm90IHBsYW5uZWQgYnkgYW5kIG91dHNpZGUgdGhlIGNvbnRyb2wgb2YgdGhlIGRhdGEgY29sbGVjdG9yLg0KDQpUaGVyZSBpcyBubyBnZW5lcmFsIHJ1bGUgYW5kIGZ1cnRoZXIgY29uY2x1c2lvbnMgY2FuIG9ubHkgYmUgbWFkZSBvbiBhIGNhc2UtYnktY2FzZSBiYXNpcywgdXNpbmcgZG9tYWluIHNwZWNpZmljIGtub3dsZWRnZS4gTWVhc3VyZW1lbnQgZXJyb3IgY2FuIHJhbmdlIGZyb20gY29tcGxldGx5IHJhbmRvbSB0byBjb21wbGV0ZWx5IG5vdC1hdC1yYW5kb20sIHN1Y2ggYSB0ZW1wZXJhdHVyZSBzZW5zb3IgYnJlYWtpbmcgZG93biBhdCBoaWdoIHRlbXBlcmF0dXJlcy4gU3ViamVjdHMgdHlwaWNhbGx5IGRvIG5vdCBkcm9wIG91dCBvZiBzdHVkaWVzIGF0IHJhbmRvbSwgYnV0IHRoYXQgaXMgYWxzbyBhIHBvc3NpYmxpdHkuIFdoZW4gbm90IGFsbCBtZWFzdXJlbWVudHMgYXJlIG1hZGUgdG8gcmVkdWNlIGNvc3QsIHRoZXkgYXJlIG9mdGVuIG9taXR0ZWQgY29tcGxldGVseSBhdCByYW5kb20sIGJ1dCBzb21ldGltZXMgYSBkaWZmZXJlbnQgZGVzaWduIGlzIHVzZWQuDQoNCg0KIyMjIERldGVjdGluZyBOQQ0KDQpBIG1pc3NpbmcgdmFsdWUsIHJlcHJlc2VudGVkIGJ5IE5BIGluIFIsIGlzIGEgcGxhY2Vob2xkZXIgZm9yIGEgZGF0dW0gb2Ygd2hpY2ggdGhlIHR5cGUgaXMga25vd24gYnV0IGl0cyB2YWx1ZSBpc24ndC4gVGhlcmVmb3JlLCBpdCBpcyBpbXBvc3NpYmxlIHRvIHBlcmZvcm0gc3RhdGlzdGljYWwgYW5hbHlzaXMgb24gZGF0YSB3aGVyZSBvbmUgb3IgbW9yZSB2YWx1ZXMgaW4gdGhlIGRhdGEgYXJlIG1pc3NpbmcuIE9uZSBtYXkgY2hvb3NlIHRvIGVpdGhlciBvbWl0IGVsZW1lbnRzIGZyb20gYSBkYXRhc2V0IHRoYXQgY29udGFpbiBtaXNzaW5nIHZhbHVlcyBvciB0byBpbXB1dGUgYSB2YWx1ZSwgYnV0IG1pc3NpbmduZXNzIGlzIHNvbWV0aGluZyB0byBiZSBkZWFsdCB3aXRoIHByaW9yIHRvIGFueSBhbmFseXNpcy4NCg0KYGBge3IgZmlnMiwgZWNobyA9IEZBTFNFLCBvdXQud2lkdGggPSAiNjAlIiwgZmlnLmFsaWduID0gJ2NlbnRlcid9DQprbml0cjo6aW5jbHVkZV9ncmFwaGljcygiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL2tmbGlzaWtvd3NraS9kcy9tYXN0ZXIvZGF0YV9jb3dib3kucG5nIikNCmBgYA0KDQpDYW4geW91IHNlZSB0aGF0IG1hbnkgdmFsdWVzIGluIG91ciBkYXRhc2V0IGhhdmUgc3RhdHVzIE5BID0gTm90IEF2YWlsYWJsZT8gQ291bnQgKG9yIHBsb3QpLCBob3cgbWFueSAoJSkgb2YgYWxsIDE1MCByb3dzIGlzIGNvbXBsZXRlLg0KDQpgYGB7ciBjb3VudC1kZW1vLCBlY2hvPVRSVUV9DQpzdW0oY29tcGxldGUuY2FzZXMobXlkYXRhKSkNCm5yb3cobXlkYXRhW2NvbXBsZXRlLmNhc2VzKG15ZGF0YSksIF0pL25yb3cobXlkYXRhKSoxMDANCmBgYA0KDQpEb2VzIHRoZSBkYXRhIGNvbnRhaW4gb3RoZXIgc3BlY2lhbCB2YWx1ZXM/IElmIGl0IGRvZXMsIHJlcGxhY2UgdGhlbSB3aXRoIE5BLg0KDQpgYGB7ciBjb3VudC1kZW1vMiwgZWNobz1UUlVFfQ0KaXMuc3BlY2lhbCA8LSBmdW5jdGlvbih4KXsNCiAgaWYgKGlzLm51bWVyaWMoeCkpICFpcy5maW5pdGUoeCkgZWxzZSBpcy5uYSh4KQ0KfQ0KDQpzYXBwbHkobXlkYXRhLCBpcy5zcGVjaWFsKQ0KZm9yIChuIGluIGNvbG5hbWVzKG15ZGF0YSkpew0KICBpcy5uYShteWRhdGFbW25dXSkgPC0gaXMuc3BlY2lhbChteWRhdGFbW25dXSkNCn0NCnN1bW1hcnkobXlkYXRhKQ0KYGBgDQoNCg0KIyMjIE5hbmlhUiANCg0KT01HISBTbyBoYXJkIDotKSBJdCdzIGJldHRlciB0byB1c2UgdmlzdWFsaXphdGlvbnMsIHRlc3RzLi4uIG9yIGF1dG9tYXRpY2FsbHkgZGV0ZWN0IE5BJ3Mgd2l0aCBzb21lIGZ1bmN0aW9uLi4uDQoNCmBgYHtyIG5hbmlhciwgZWNobyA9IEZBTFNFLCBvdXQud2lkdGggPSAiNjAlIiwgZmlnLmFsaWduID0gJ2NlbnRlcid9DQprbml0cjo6aW5jbHVkZV9ncmFwaGljcygiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL2tmbGlzaWtvd3NraS9kcy9tYXN0ZXIvbmFuaWFyLmpwZyIpDQpgYGANCg0KVGhlcmUgYXJlIGEgdmFyaWV0eSBvZiBkaWZmZXJlbnQgcGxvdHMgdG8gZXhwbG9yZSBtaXNzaW5nIGRhdGEgYXZhaWxhYmxlIGluIHRoZSBuYW5pYXIgcGFja2FnZS4gSWYgeW91IHdvdWxkIGxpa2UgdG8ga25vdyBtb3JlIGFib3V0IHRoZSBwaGlsb3NvcGh5IG9mIHRoZSBgbmFuaWFyYCBwYWNrYWdlLCB5b3Ugc2hvdWxkIHJlYWQgdGhlIHZpZ25ldHRlIFtHZXR0aW5nIFN0YXJ0ZWQgd2l0aCBuYW5pYXJdKGh0dHA6Ly9uYW5pYXIubmp0aWVybmV5LmNvbS9hcnRpY2xlcy9nZXR0aW5nLXN0YXJ0ZWQtdy1uYW5pYXIuaHRtbCkuDQoNCkEga2V5IHBvaW50IHRvIHJlbWVtYmVyIHdpdGggdGhlIHZpc3VhbGlzYXRpb24gdG9vbHMgaW4gYG5hbmlhcmAgaXMgdGhhdCB0aGVyZSBpcyBhIHdheSB0byBnZXQgdGhlIGRhdGEgZnJvbSB0aGUgcGxvdCBvdXQgZnJvbSB0aGUgdmlzdWFsaXNhdGlvbi4NCg0KIyMjIyBFeHBsb3JpbmcgTkEncw0KDQpPbmUgb2YgdGhlIGZpcnN0IHBsb3RzIHRoYXQgSSByZWNvbW1lbmQgeW91IHN0YXJ0IHdpdGggd2hlbiB5b3UgYXJlIGZpcnN0IGV4cGxvcmluZyB5b3VyIG1pc3NpbmcgZGF0YSwgaXMgdGhlIGB2aXNfbWlzcygpYCBwbG90LCB3aGljaCBpcyByZS1leHBvcnRlZCBmcm9tIFtgdmlzZGF0YF0oaHR0cHM6Ly9naXRodWIuY29tL3JvcGVuc2NpL3Zpc2RhdCkuDQoNCmBgYHtyIHZpcy1taXNzfQ0KdmlzX21pc3MoYWlycXVhbGl0eSkNCmBgYA0KVGhpcyBwbG90IHByb3ZpZGVzIGEgc3BlY2lmaWMgdmlzdWFsaWF0aW9uIG9mIHRoZSBhbW91bnQgb2YgbWlzc2luZyBkYXRhLCBzaG93aW5nIGluIGJsYWNrIHRoZSBsb2NhdGlvbiBvZiBtaXNzaW5nIHZhbHVlcywgYW5kIGFsc28gcHJvdmlkaW5nIGluZm9ybWF0aW9uIG9uIHRoZSBvdmVyYWxsIHBlcmNlbnRhZ2Ugb2YgbWlzc2luZyB2YWx1ZXMgb3ZlcmFsbCAoaW4gdGhlIGxlZ2VuZCksIGFuZCBpbiBlYWNoIHZhcmlhYmxlLg0KDQoNCiMjIyMgUGF0dGVybnMNCg0KDQpBbiB1cHNldCBwbG90IGZyb20gdGhlIGBVcFNldFJgIHBhY2thZ2UgY2FuIGJlIHVzZWQgdG8gdmlzdWFsaXNlIHRoZSBwYXR0ZXJucyBvZiBtaXNzaW5nbmVzcywgb3IgcmF0aGVyIHRoZSBjb21iaW5hdGlvbnMgb2YgbWlzc2luZ25lc3MgYWNyb3NzIGNhc2VzLiBUbyBzZWUgY29tYmluYXRpb25zIG9mIG1pc3NpbmduZXNzIGFuZCBpbnRlcnNlY3Rpb25zIG9mIG1pc3NpbmduZXNzIGFtb25nc3QgdmFyaWFibGVzLCB1c2UgdGhlIGBnZ19taXNzX3Vwc2V0YCBmdW5jdGlvbjoNCg0KYGBge3IgZ2ctbWlzcy11cHNldH0NCmdnX21pc3NfdXBzZXQoYWlycXVhbGl0eSkNCmBgYA0KDQpXZSBjYW4gZXhwbG9yZSB0aGlzIHdpdGggbW9yZSBjb21wbGV4IGRhdGEsIHN1Y2ggYXMgcmlza2ZhY3RvcnM6DQoNCmBgYHtyfQ0KZ2dfbWlzc191cHNldChyaXNrZmFjdG9ycykNCmBgYA0KDQojIyMjIE5BJ3MgTWVjaGFuaXNtcw0KDQpUaGVyZSBhcmUgYSBmZXcgZGlmZmVyZW50IHdheXMgdG8gZXhwbG9yZSBkaWZmZXJlbnQgbWlzc2luZyBkYXRhIG1lY2hhbmlzbXMgYW5kIHJlbGF0aW9uc2hpcHMuIE9uZSB3YXkgaW5jb3Jwb3JhdGVzIHRoZSBtZXRob2Qgb2Ygc2hpZnRpbmcgbWlzc2luZyB2YWx1ZXMgc28gdGhhdCB0aGV5IGNhbiBiZSB2aXN1YWxpc2VkIG9uIHRoZSBzYW1lIGF4ZXMgYXMgdGhlIHJlZ3VsYXIgdmFsdWVzLCBhbmQgdGhlbiBjb2xvdXJzIHRoZSBtaXNzaW5nIGFuZCBub3QgbWlzc2luZyBwb2ludHMuIFRoaXMgaXMgaW1wbGVtZW50ZWQgd2l0aCBgZ2VvbV9taXNzX3BvaW50KClgLg0KDQpgYGB7ciBnZ3Bsb3QtZ2VvbS1taXNzLXBvaW50LCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KIyB1c2luZyByZWd1bGFyIGdlb21fcG9pbnQoKQ0KZ2dwbG90KGFpcnF1YWxpdHksDQogICAgICAgYWVzKHggPSBPem9uZSwNCiAgICAgICAgICAgeSA9IFNvbGFyLlIpKSArDQpnZW9tX21pc3NfcG9pbnQoKQ0KYGBgDQoNCiMjIyMgTkEncyBpbiB2YXJzDQoNClRoaXMgcGxvdCBzaG93cyB0aGUgbnVtYmVyIG9mIG1pc3NpbmcgdmFsdWVzIGluIGVhY2ggdmFyaWFibGUgaW4gYSBkYXRhc2V0LiBJdCBpcyBwb3dlcmVkIGJ5IHRoZSBgbWlzc192YXJfc3VtbWFyeSgpYCBmdW5jdGlvbi4NCg0KYGBge3IgZ2ctbWlzcy12YXJ9DQpnZ19taXNzX3ZhcihhaXJxdWFsaXR5KQ0KZ2dfbWlzc192YXIoYWlycXVhbGl0eSkgKyBsYWJzKHkgPSAiTG9vayBhdCBhbGwgdGhlIG1pc3Npbmcgb25lcyIpDQpgYGANCg0KIyMjIyBOQSdzIGluIGNhc2VzDQoNClRoaXMgcGxvdCBzaG93cyB0aGUgbnVtYmVyIG9mIG1pc3NpbmcgdmFsdWVzIGluIGVhY2ggY2FzZS4gSXQgaXMgcG93ZXJlZCBieSB0aGUgYG1pc3NfY2FzZV9zdW1tYXJ5KClgIGZ1bmN0aW9uLg0KDQpgYGB7ciBnZy1taXNzLWNhc2V9DQpnZ19taXNzX2Nhc2UoYWlycXVhbGl0eSkgKyBsYWJzKHggPSAiTnVtYmVyIG9mIENhc2VzIikNCmBgYA0KDQojIyMjIE5BJ3MgYWNyb3NzIGZhY3RvcnMNCg0KVGhpcyBwbG90IHNob3dzIHRoZSBudW1iZXIgb2YgbWlzc2luZ3MgaW4gZWFjaCBjb2x1bW4sIGJyb2tlbiBkb3duIGJ5IGEgY2F0ZWdvcmljYWwgdmFyaWFibGUgZnJvbSB0aGUgZGF0YXNldC4gSXQgaXMgcG93ZXJlZCBieSBhIGBkcGx5cjo6Z3JvdXBfYnlgIHN0YXRlbWVudCBmb2xsb3dlZCBieSBgbWlzc192YXJfc3VtbWFyeSgpYC4gDQoNCmBgYHtyIGdnLW1pc3MtZmN0LCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KZ2dfbWlzc19mY3QoeCA9IHJpc2tmYWN0b3JzLCBmY3QgPSBtYXJpdGFsKQ0KYGBgDQoNCiMjIyMgU3VtbWFyaWVzIGZvciBOQSdzDQoNCm5hbmlhciBwcm92aWRlcyBudW1lcmljYWwgc3VtbWFyaWVzIG9mIG1pc3NpbmcgZGF0YSwgdGhhdCBmb2xsb3cgYSBjb25zaXN0ZW50IHJ1bGUgdGhhdCB1c2VzIGEgc3ludGF4IGJlZ2luaW5nIHdpdGggbWlzc18uIA0KDQpgYGB7cn0NCm1pc3NfdmFyX3N1bW1hcnkoYWlycXVhbGl0eSkNCmBgYA0KDQpZb3UgY291bGQgYWxzbyBncm91cF9ieSgpIHRvIHdvcmsgb3V0IHRoZSBudW1iZXIgb2YgbWlzc2luZ3MgaW4gZWFjaCB2YXJpYWJsZSBhY3Jvc3MgdGhlIGxldmVscyB3aXRoaW4gaXQuDQoNCmBgYHtyfQ0KYWlycXVhbGl0eSAlPiUNCiAgZ3JvdXBfYnkoTW9udGgpICU+JQ0KICBtaXNzX3Zhcl9zdW1tYXJ5KCkNCmBgYA0KDQoNCiMjIElkZW50aWZ5IG91dGxpZXJzDQoNClRoYW5rcyB0byAqcnN0YXRpeCogbGlicmFyeSB3ZSBjYW4gZWFzaWx5IGRldGVjdCBvdXRsaWVycyB1c2luZyBib3hwbG90IG1ldGhvZHMuIEJveHBsb3RzIGFyZSBhIHBvcHVsYXIgYW5kIGFuIGVhc3kgbWV0aG9kIGZvciBpZGVudGlmeWluZyBvdXRsaWVycy4gDQoNCmBgYHtyfQ0KIyBDb252ZXJ0IHRoZSB2YXJpYWJsZSBkb3NlIGZyb20gYSBudW1lcmljIHRvIGEgZmFjdG9yIHZhcmlhYmxlDQp0b290aCA8LSBhcy5kYXRhLmZyYW1lKFRvb3RoR3Jvd3RoKQ0KdG9vdGgkZG9zZSA8LSBhcy5mYWN0b3IodG9vdGgkZG9zZSkNCiMgQ2hhbmdlIGJveCBwbG90IGxpbmUgY29sb3JzIGJ5IGdyb3Vwcw0KcDwtZ2dwbG90KHRvb3RoLCBhZXMoeD1kb3NlLCB5PWxlbiwgY29sb3I9ZG9zZSkpICsNCiAgZ2VvbV9ib3hwbG90KCkNCnANCmBgYA0KDQpUaGVyZSBhcmUgdHdvIGNhdGVnb3JpZXMgb2Ygb3V0bGllcjogKDEpIG91dGxpZXJzIGFuZCAoMikgZXh0cmVtZSBwb2ludHMuDQoNClZhbHVlcyBhYm92ZSBRMyArIDEuNXhJUVIgb3IgYmVsb3cgUTEgLSAxLjV4SVFSIGFyZSBjb25zaWRlcmVkIGFzIG91dGxpZXJzLiBWYWx1ZXMgYWJvdmUgUTMgKyAzeElRUiBvciBiZWxvdyBRMSAtIDN4SVFSIGFyZSBjb25zaWRlcmVkIGFzIGV4dHJlbWUgcG9pbnRzIChvciBleHRyZW1lIG91dGxpZXJzKS4NCg0KUTEgYW5kIFEzIGFyZSB0aGUgZmlyc3QgYW5kIHRoaXJkIHF1YXJ0aWxlLCByZXNwZWN0aXZlbHkuIElRUiBpcyB0aGUgaW50ZXJxdWFydGlsZSByYW5nZSAoSVFSID0gUTMgLSBRMSkuDQoNCkdlbmVyYWxseSBzcGVha2luZywgZGF0YSBwb2ludHMgdGhhdCBhcmUgbGFiZWxsZWQgb3V0bGllcnMgaW4gYm94cGxvdHMgYXJlIG5vdCBjb25zaWRlcmVkIGFzIHRyb3VibGVzb21lIGFzIHRob3NlIGNvbnNpZGVyZWQgZXh0cmVtZSBwb2ludHMgYW5kIG1pZ2h0IGV2ZW4gYmUgaWdub3JlZC4gTm90ZSB0aGF0LCBhbnkgTkEgYW5kIE5hTiBhcmUgYXV0b21hdGljYWxseSByZW1vdmVkIGJlZm9yZSB0aGUgcXVhbnRpbGVzIGFyZSBjb21wdXRlZC4NCg0KKipFeGFtcGxlKioNCg0KYGBge3J9DQp0b290aCAlPiUNCiAgZ3JvdXBfYnkoZG9zZSkgJT4lDQogIGlkZW50aWZ5X291dGxpZXJzKGxlbikNCmBgYA0KDQpTZWU/IG9uZSBvdXRsaWVyISAodGhhdCB3YXMgdmlzaWJsZSBvbiB0aGUgYm94cGxvdCkNCg0KDQojIyBDaGVja2luZyBjb25zaXN0ZW5jeQ0KDQpDb25zaXN0ZW50IGRhdGEgYXJlIHRlY2huaWNhbGx5IGNvcnJlY3QgZGF0YSB0aGF0IGFyZSBmaXQgZm9yIHN0YXRpc3RpY2FsIGFuYWx5c2lzLiBUaGV5IGFyZSBkYXRhIGluIHdoaWNoIG1pc3NpbmcgdmFsdWVzLCBzcGVjaWFsIHZhbHVlcywgKG9idmlvdXMpIGVycm9ycyBhbmQgb3V0bGllcnMgYXJlIGVpdGhlciByZW1vdmVkLCBjb3JyZWN0ZWQgb3IgaW1wdXRlZC4gVGhlIGRhdGEgYXJlIGNvbnNpc3RlbnQgd2l0aCBjb25zdHJhaW50cyBiYXNlZCBvbiByZWFsLXdvcmxkIGtub3dsZWRnZSBhYm91dCB0aGUgc3ViamVjdCB0aGF0IHRoZSBkYXRhIGRlc2NyaWJlLg0KDQpgYGB7ciBmaWczLCBlY2hvID0gRkFMU0UsIG91dC53aWR0aCA9ICI2MCUiLCBmaWcuYWxpZ24gPSAnY2VudGVyJ30NCmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKCJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20va2ZsaXNpa293c2tpL2RzL21hc3Rlci9pcmlzLnBuZyIpDQpgYGANCg0KV2UgaGF2ZSB0aGUgZm9sbG93aW5nIGJhY2tncm91bmQga25vd2xlZGdlOg0KDQotICAgU3BlY2llcyBzaG91bGQgYmUgb25lIG9mIHRoZSBmb2xsb3dpbmcgdmFsdWVzOiBzZXRvc2EsIHZlcnNpY29sb3Igb3IgdmlyZ2luaWNhLg0KDQotICAgQWxsIG1lYXN1cmVkIG51bWVyaWNhbCBwcm9wZXJ0aWVzIG9mIGFuIGlyaXMgc2hvdWxkIGJlIHBvc2l0aXZlLg0KDQotICAgVGhlIHBldGFsIGxlbmd0aCBvZiBhbiBpcmlzIGlzIGF0IGxlYXN0IDIgdGltZXMgaXRzIHBldGFsIHdpZHRoLg0KDQotICAgVGhlIHNlcGFsIGxlbmd0aCBvZiBhbiBpcmlzIGNhbm5vdCBleGNlZWQgMzAgY20uDQoNCi0gICBUaGUgc2VwYWxzIG9mIGFuIGlyaXMgYXJlIGxvbmdlciB0aGFuIGl0cyBwZXRhbHMuDQoNCkRlZmluZSB0aGVzZSBydWxlcyBpbiBhIHNlcGFyYXRlIG9iamVjdCAnUlVMRScgYW5kIHJlYWQgdGhlbSBpbnRvIFIgdXNpbmcgZWRpdHNldCAocGFja2FnZSAnZWRpdHJ1bGVzJykuIFByaW50IHRoZSByZXN1bHRpbmcgY29uc3RyYWludCBvYmplY3QuDQoNCmBgYHtyIGVjaG8gPSBUUlVFfQ0KUlVMRSA8LSBlZGl0c2V0KGMoIlNlcGFsLkxlbmd0aCA8PSAzMCIsIlNwZWNpZXMgJWluJSBjKCdzZXRvc2EnLCd2ZXJzaWNvbG9yJywndmlyZ2luaWNhJykiDQogICAgICAgICAgICAgICAsICJTZXBhbC5MZW5ndGggPiAwIiwgIlNlcGFsLldpZHRoID4gMCIsICJQZXRhbC5MZW5ndGggPiAwIiwgIlBldGFsLldpZHRoID4gMCIsDQoiUGV0YWwuTGVuZ3RoID49IDIgKiBQZXRhbC5XaWR0aCIsICJTZXBhbC5MZW5ndGg+UGV0YWwuTGVuZ3RoIikpDQpSVUxFDQpgYGANCg0KTm93IHdlIGFyZSByZWFkeSB0byBkZXRlcm1pbmUgaG93IG9mdGVuIGVhY2ggcnVsZSBpcyBicm9rZW4gKHZpb2xhdGVkRWRpdHMpLiBBbHNvIHdlIGNhbiBzdW1tYXJpemUgYW5kIHBsb3QgdGhlIHJlc3VsdC4NCg0KYGBge3IgZWNobyA9IFRSVUV9DQpzdW1tYXJ5KHZpb2xhdGVkRWRpdHMoUlVMRSwgbXlkYXRhKSkNCmBgYA0KDQpXaGF0IHBlcmNlbnRhZ2Ugb2YgdGhlIGRhdGEgaGFzIG5vIGVycm9ycz8NCg0KYGBge3Igbm8tZXJyb3JzLCBlY2hvPVRSVUV9DQp2aW9sYXRlZCA8LSB2aW9sYXRlZEVkaXRzKFJVTEUsIG15ZGF0YSkNCnN1bW1hcnkodmlvbGF0ZWQpDQpwbG90KHZpb2xhdGVkKQ0KYGBgDQoNCiMjIyBFeGVyY2lzZSAxLg0KDQpGaW5kIG91dCB3aGljaCBvYnNlcnZhdGlvbnMgaGF2ZSB0b28gbG9uZyBzZXBhbHMgdXNpbmcgdGhlIHJlc3VsdCBvZiB2aW9sYXRlZEVkaXRzLg0KDQpgYGB7ciBleGVyY2lzZTF9DQojIHNvbHV0aW9uIGZvciB0aGUgZXhlcmNpc2UgMSBoZXJlIDstKQ0KYGBgDQoNCiMjIyBFeGVyY2lzZSAyLg0KDQpGaW5kIG91dGxpZXJzIGluIHNlcGFsIGxlbmd0aCB1c2luZyBib3hwbG90IGFuZCBib3hwbG90LnN0YXRzLiBSZXRyaWV2ZSB0aGUgY29ycmVzcG9uZGluZyBvYnNlcnZhdGlvbnMgYW5kIGxvb2sgYXQgdGhlIG90aGVyIHZhbHVlcy4gQW55IGlkZWFzIHdoYXQgbWlnaHQgaGF2ZSBoYXBwZW5lZD8gU2V0IHRoZSBvdXRsaWVycyB0byBOQSAob3IgYSB2YWx1ZSB0aGF0IHlvdSBmaW5kIG1vcmUgYXBwcm9waWF0ZSkNCg0KYGBge3Igb3V0bGllcjEsIGVjaG89VFJVRX0NCmJveHBsb3QobXlkYXRhJFNlcGFsLkxlbmd0aCkNCm91dGxpZXJzIDwtIGJveHBsb3Quc3RhdHMobXlkYXRhJFNlcGFsLkxlbmd0aCkkb3V0DQpvdXRsaWVyc19pZHggPC0gd2hpY2gobXlkYXRhJFNlcGFsLkxlbmd0aCAlaW4lIG91dGxpZXJzKQ0KbXlkYXRhW291dGxpZXJzX2lkeCxdDQojIHRoZXkgYWxsIHNlZW0gdG8gYmUgdG9vIGJpZy4uLiBtYXkgdGhleSB3ZXJlIG1lYXN1cmVkIGluIG1tIGkubyBjbT8NCm15ZGF0YVtvdXRsaWVyc19pZHgsMTo0XSA8LSBteWRhdGFbb3V0bGllcnNfaWR4LDE6NF0vMTANCnN1bW1hcnkobXlkYXRhKQ0KYGBgDQoNCk5vdGUgdGhhdCBzaW1wbGUgYm94cGxvdCBzaG93cyBhbiBleHRyYSBvdXRsaWVyIQ0KDQpgYGB7ciBib3hwbG90LCBlY2hvPVRSVUV9DQpib3hwbG90KFNlcGFsLkxlbmd0aCB+IFNwZWNpZXMsIGRhdGE9bXlkYXRhKQ0KYGBgDQoNCiMjIENvcnJlY3Rpb25zDQoNClJlcGxhY2Ugbm9uIHBvc2l0aXZlIHZhbHVlcyBmcm9tIFNlcGFsLldpZHRoIHdpdGggTkEgdXNpbmcgY29ycmVjdFdpdGhSdWxlcyBmcm9tIHRoZSBsaWJyYXJ5ICdkZWR1Y29ycmVjdCcuDQoNCmBgYHtyIGNvcnJlY3Qtc2ltcGxlLCBlY2hvID0gVFJVRX0NCg0KY3IgPC0gY29ycmVjdGlvblJ1bGVzKGV4cHJlc3Npb24oDQogIGlmICghaXMubmEoU2VwYWwuV2lkdGgpICYmIFNlcGFsLldpZHRoIDw9MCApIFNlcGFsLldpZHRoID0gTkENCiAgKSkNCmNvcnJlY3RXaXRoUnVsZXMoY3IsIG15ZGF0YSkNCmBgYA0KDQpSZXBsYWNlIGFsbCBlcnJvbmVvdXMgdmFsdWVzIHdpdGggTkEgdXNpbmcgKHRoZSByZXN1bHQgb2YpIGxvY2FsaXplRXJyb3JzOg0KDQpgYGB7ciBjb3JyZWN0LXNpbXBsZTIsIGVjaG8gPSBUUlVFfQ0KbXlkYXRhW2xvY2FsaXplRXJyb3JzKFJVTEUsIG15ZGF0YSkkYWRhcHRdIDwtIE5BDQojIGFueXRoaW5nIHZpb2xhdGVkPw0KYW55KHZpb2xhdGVkRWRpdHMoUlVMRSxteWRhdGEpLCBuYS5ybT1UUlVFKQ0KYGBgDQoNCldlbGwgZG9uZSEgTm8gZXJyb3JzIQ0KDQojIyBkbG9va3IgcGFja2FnZQ0KDQpgYGB7ciBmaWc0LCBlY2hvID0gRkFMU0UsIG91dC53aWR0aCA9ICI4MCUiLCBmaWcuYWxpZ24gPSAnY2VudGVyJ30NCmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKCJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20va2ZsaXNpa293c2tpL2RzL21hc3Rlci90aWR5ZGF0YS5wbmciKQ0KYGBgDQoNCkFmdGVyIHlvdSBoYXZlIGFjcXVpcmVkIHRoZSBkYXRhLCB5b3Ugc2hvdWxkIGRvIHRoZSBmb2xsb3dpbmc6DQoNCiogRGlhZ25vc2UgZGF0YSBxdWFsaXR5Lg0KICAgICsgSWYgdGhlcmUgaXMgYSBwcm9ibGVtIHdpdGggZGF0YSBxdWFsaXR5LA0KICAgICsgVGhlIGRhdGEgbXVzdCBiZSBjb3JyZWN0ZWQgb3IgcmUtYWNxdWlyZWQuDQoqIEV4cGxvcmUgZGF0YSB0byB1bmRlcnN0YW5kIHRoZSBkYXRhIGFuZCBmaW5kIHNjZW5hcmlvcyBmb3IgcGVyZm9ybWluZyB0aGUgYW5hbHlzaXMuDQoqICoqRGVyaXZlIG5ldyB2YXJpYWJsZXMgb3IgcGVyZm9ybSB2YXJpYWJsZSB0cmFuc2Zvcm1hdGlvbnMuKioNCg0KVGhlIGRsb29rciBwYWNrYWdlIG1ha2VzIHRoZXNlIHN0ZXBzIGZhc3QgYW5kIGVhc3k6DQoNCiogUGVyZm9ybXMgYW4gZGF0YSBkaWFnbm9zaXMgb3IgYXV0b21hdGljYWxseSBnZW5lcmF0ZXMgYSBkYXRhIGRpYWdub3NpcyByZXBvcnQuDQoqIERpc2NvdmVyIGRhdGEgaW4gYSB2YXJpZXR5IG9mIHdheXMsIGFuZCBhdXRvbWF0aWNhbGx5IGdlbmVyYXRlIEVEQShleHBsb3JhdG9yeSBkYXRhIGFuYWx5c2lzKSByZXBvcnQuDQoqICoqSW1wdXRlIG1pc3NpbmcgdmFsdWVzIGFuZCBvdXRsaWVycywgcmVzb2x2ZSBza2V3ZWQgZGF0YSwgYW5kIGNhdGVnb3JpemUgY29udGludW91cyB2YXJpYWJsZXMgaW50byBjYXRlZ29yaWNhbCB2YXJpYWJsZXMuIEFuZCBnZW5lcmF0ZXMgYW4gYXV0b21hdGVkIHJlcG9ydCB0byBzdXBwb3J0IGl0LioqDQoNCkhlcmUgd2Ugd2lsbCBpbnRyb2R1Y2UgKipkYXRhIHRyYW5zZm9ybWF0aW9uKiogbWV0aG9kcyBwcm92aWRlZCBieSB0aGUgZGxvb2tyIHBhY2thZ2UuIFlvdSB3aWxsIGxlYXJuIGhvdyB0byB0cmFuc2Zvcm0gb2YgYHRibF9kZmAgZGF0YSB0aGF0IGluaGVyaXRzIGZyb20gZGF0YS5mcmFtZSBhbmQgYGRhdGEuZnJhbWVgIHdpdGggZnVuY3Rpb25zIHByb3ZpZGVkIGJ5IGRsb29rci4NCg0KZGxvb2tyIGluY3JlYXNlcyBzeW5lcmd5IHdpdGggYGRwbHlyYC4gUGFydGljdWxhcmx5IGluIGRhdGEgdHJhbnNmb3JtYXRpb24gYW5kIGRhdGEgd3JhbmdsZSwgaXQgaW5jcmVhc2VzIHRoZSBlZmZpY2llbmN5IG9mIHRoZSBgdGlkeXZlcnNlYCBwYWNrYWdlIGdyb3VwLg0KDQpUbyBpbGx1c3RyYXRlIHRoZSBiYXNpYyB1c2Ugb2YgZGF0YSB0cmFuc2Zvcm1hdGlvbiBpbiB0aGUgZGxvb2tyIHBhY2thZ2UsIEkgdXNlIGEgYENhcnNlYXRzYCBkYXRhc2V0Lg0KYENhcnNlYXRzYCBpbiB0aGUgYElTTFJgIHBhY2thZ2UgaXMgc2ltdWxhdGlvbiBkYXRhc2V0IHRoYXQgc2VsbHMgY2hpbGRyZW4ncyBjYXIgc2VhdHMgYXQgNDAwIHN0b3Jlcy4gVGhpcyBkYXRhIGlzIGEgZGF0YS5mcmFtZSBjcmVhdGVkIGZvciB0aGUgcHVycG9zZSBvZiBwcmVkaWN0aW5nIHNhbGVzIHZvbHVtZS4NCg0KYGBge3IgaW1wb3J0X2RhdGF9DQpzdHIoQ2Fyc2VhdHMpDQpgYGANCg0KVGhlIGNvbnRlbnRzIG9mIGluZGl2aWR1YWwgdmFyaWFibGVzIGFyZSBhcyBmb2xsb3dzLiAoUmVmZXIgdG8gSVNMUjo6Q2Fyc2VhdHMgTWFuIHBhZ2UpDQoNCiogU2FsZXMNCiAgICArIFVuaXQgc2FsZXMgKGluIHRob3VzYW5kcykgYXQgZWFjaCBsb2NhdGlvbg0KKiBDb21wUHJpY2UNCiAgICArIFByaWNlIGNoYXJnZWQgYnkgY29tcGV0aXRvciBhdCBlYWNoIGxvY2F0aW9uDQoqIEluY29tZQ0KICAgICsgQ29tbXVuaXR5IGluY29tZSBsZXZlbCAoaW4gdGhvdXNhbmRzIG9mIGRvbGxhcnMpDQoqIEFkdmVydGlzaW5nDQogICAgKyBMb2NhbCBhZHZlcnRpc2luZyBidWRnZXQgZm9yIGNvbXBhbnkgYXQgZWFjaCBsb2NhdGlvbiAoaW4gdGhvdXNhbmRzIG9mIGRvbGxhcnMpDQoqIFBvcHVsYXRpb24NCiAgICArIFBvcHVsYXRpb24gc2l6ZSBpbiByZWdpb24gKGluIHRob3VzYW5kcykNCiogUHJpY2UNCiAgICArIFByaWNlIGNvbXBhbnkgY2hhcmdlcyBmb3IgY2FyIHNlYXRzIGF0IGVhY2ggc2l0ZQ0KKiBTaGVsdmVMb2MNCiAgICArIEEgZmFjdG9yIHdpdGggbGV2ZWxzIEJhZCwgR29vZCBhbmQgTWVkaXVtIGluZGljYXRpbmcgdGhlIHF1YWxpdHkgb2YgdGhlIHNoZWx2aW5nIGxvY2F0aW9uIGZvciB0aGUgY2FyIHNlYXRzIGF0IGVhY2ggc2l0ZQ0KKiBBZ2UNCiAgICArIEF2ZXJhZ2UgYWdlIG9mIHRoZSBsb2NhbCBwb3B1bGF0aW9uDQoqIEVkdWNhdGlvbg0KICAgICsgRWR1Y2F0aW9uIGxldmVsIGF0IGVhY2ggbG9jYXRpb24NCiogVXJiYW4NCiAgICArIEEgZmFjdG9yIHdpdGggbGV2ZWxzIE5vIGFuZCBZZXMgdG8gaW5kaWNhdGUgd2hldGhlciB0aGUgc3RvcmUgaXMgaW4gYW4gdXJiYW4gb3IgcnVyYWwgbG9jYXRpb24NCiogVVMNCiAgICArIEEgZmFjdG9yIHdpdGggbGV2ZWxzIE5vIGFuZCBZZXMgdG8gaW5kaWNhdGUgd2hldGhlciB0aGUgc3RvcmUgaXMgaW4gdGhlIFVTIG9yIG5vdA0KDQpXaGVuIGRhdGEgYW5hbHlzaXMgaXMgcGVyZm9ybWVkLCBkYXRhIGNvbnRhaW5pbmcgbWlzc2luZyB2YWx1ZXMgaXMgb2Z0ZW4gZW5jb3VudGVyZWQuIEhvd2V2ZXIsIGBDYXJzZWF0c2AgaXMgY29tcGxldGUgZGF0YSB3aXRob3V0IG1pc3NpbmcuIFRoZXJlZm9yZSwgdGhlIG1pc3NpbmcgdmFsdWVzIGFyZSBnZW5lcmF0ZWQgYXMgZm9sbG93cy4gQW5kIEkgY3JlYXRlZCBhIGRhdGEuZnJhbWUgb2JqZWN0IG5hbWVkIGNhcnNlYXRzLg0KDQpgYGB7ciBtaXNzaW5nfQ0KY2Fyc2VhdHMgPC0gSVNMUjo6Q2Fyc2VhdHMNCnN1cHByZXNzV2FybmluZ3MoUk5HdmVyc2lvbigiMy41LjAiKSkNCnNldC5zZWVkKDEyMykNCmNhcnNlYXRzW3NhbXBsZShzZXEoTlJPVyhjYXJzZWF0cykpLCAyMCksICJJbmNvbWUiXSA8LSBOQQ0Kc3VwcHJlc3NXYXJuaW5ncyhSTkd2ZXJzaW9uKCIzLjUuMCIpKQ0Kc2V0LnNlZWQoNDU2KQ0KY2Fyc2VhdHNbc2FtcGxlKHNlcShOUk9XKGNhcnNlYXRzKSksIDEwKSwgIlVyYmFuIl0gPC0gTkENCmBgYA0KDQoNCiMjIyBGdW5jdGlvbnMNCg0KZGxvb2tyIGltcHV0ZXMgbWlzc2luZyB2YWx1ZXMgYW5kIG91dGxpZXJzIGFuZCByZXNvbHZlcyBza2V3ZWQgZGF0YS4gSXQgYWxzbyBwcm92aWRlcyB0aGUgYWJpbGl0eSB0byBiaW4gY29udGludW91cyB2YXJpYWJsZXMgYXMgY2F0ZWdvcmljYWwgdmFyaWFibGVzLg0KDQpIZXJlIGlzIGEgbGlzdCBvZiB0aGUgZGF0YSBjb252ZXJzaW9uIGZ1bmN0aW9ucyBhbmQgZnVuY3Rpb25zIHByb3ZpZGVkIGJ5IGRsb29rcjoNCg0KKiBgZmluZF9uYSgpYCBmaW5kcyBhIHZhcmlhYmxlIHRoYXQgY29udGFpbnMgdGhlIG1pc3NpbmcgdmFsdWVzIHZhcmlhYmxlLCBhbmQgYGltcHV0YXRlX25hKClgIGltcHV0ZXMgdGhlIG1pc3NpbmcgdmFsdWVzLg0KKiBgZmluZF9vdXRsaWVycygpYCBmaW5kcyBhIHZhcmlhYmxlIHRoYXQgY29udGFpbnMgdGhlIG91dGxpZXJzLCBhbmQgYGltcHV0YXRlX291dGxpZXIoKWAgaW1wdXRlcyB0aGUgb3V0bGllci4NCiogYHN1bW1hcnkuaW1wdXRhdGlvbigpYCBhbmQgYHBsb3QuaW1wdXRhdGlvbigpYCBwcm92aWRlIGluZm9ybWF0aW9uIGFuZCB2aXN1YWxpemF0aW9uIG9mIHRoZSBpbXB1dGVkIHZhcmlhYmxlcy4NCiogYGZpbmRfc2tld25lc3MoKWAgZmluZHMgdGhlIHZhcmlhYmxlcyBvZiB0aGUgc2tld2VkIGRhdGEsIGFuZCBgdHJhbnNmb3JtKClgIHBlcmZvcm1zIHRoZSByZXNvbHZpbmcgb2YgdGhlIHNrZXdlZCBkYXRhLg0KKiBgdHJhbnNmb3JtKClgIGFsc28gcGVyZm9ybXMgc3RhbmRhcmRpemF0aW9uIG9mIG51bWVyaWMgdmFyaWFibGVzLg0KKiBgc3VtbWFyeS50cmFuc2Zvcm0oKWAgYW5kIGBwbG90LnRyYW5zZm9ybSgpYCBwcm92aWRlIGluZm9ybWF0aW9uIGFuZCB2aXN1YWxpemF0aW9uIG9mIHRyYW5zZm9ybWVkIHZhcmlhYmxlcy4NCiogYGJpbm5pbmcoKWAgYW5kIGBiaW5uaW5nX2J5KClgIGNvbnZlcnQgYmluYXRpb25hbCBkYXRhIGludG8gY2F0ZWdvcmljYWwgZGF0YS4NCiogYHByaW50LmJpbnMoKWAgYW5kIGBzdW1tYXJ5LmJpbnMoKWAgc2hvdyBhbmQgc3VtbWFyaXplIHRoZSBiaW5uaW5nIHJlc3VsdHMuDQoqIGBwbG90LmJpbnMoKWAgYW5kIGBwbG90Lm9wdGltYWxfYmlucygpYCBwcm92aWRlIHZpc3VhbGl6YXRpb24gb2YgdGhlIGJpbm5pbmcgcmVzdWx0Lg0KKiBgdHJhbnNmb3JtYXRpb25fcmVwb3J0KClgIHBlcmZvcm1zIHRoZSBkYXRhIHRyYW5zZm9ybSBhbmQgcmVwb3J0cyB0aGUgcmVzdWx0Lg0KDQojIyMgSW1wdXRhdGlvbnMgb2YgTkEncw0KDQpgaW1wdXRhdGVfbmEoKWAgaW1wdXRlcyB0aGUgbWlzc2luZyB2YWx1ZSBjb250YWluZWQgaW4gdGhlIHZhcmlhYmxlLiBUaGUgcHJlZGljdG9yIHdpdGggbWlzc2luZyB2YWx1ZXMgc3VwcG9ydCBib3RoIG51bWVyaWMgYW5kIGNhdGVnb3JpY2FsIHZhcmlhYmxlcywgYW5kIHN1cHBvcnRzIHRoZSBmb2xsb3dpbmcgYG1ldGhvZGAuDQoNCkltcHV0YXRpb24gaXMgdGhlIHByb2Nlc3Mgb2YgZXN0aW1hdGluZyBvciBkZXJpdmluZyB2YWx1ZXMgZm9yIGZpZWxkcyB3aGVyZSBkYXRhIGlzIG1pc3NpbmcuIFRoZXJlIGlzIGEgdmFzdCBib2R5IG9mIGxpdGVyYXR1cmUgb24gaW1wdXRhdGlvbiBtZXRob2RzIGFuZCBpdCBnb2VzIGJleW9uZCB0aGUgc2NvcGUgb2YgdGhpcyB0dXRvcmlhbCB0byBkaXNjdXNzIGFsbCBvZiB0aGVtLg0KDQpUaGVyZSBpcyBubyBvbmUgc2luZ2xlIGJlc3QgaW1wdXRhdGlvbiBtZXRob2QgdGhhdCB3b3JrcyBpbiBhbGwgY2FzZXMuIFRoZSBpbXB1dGF0aW9uIG1vZGVsIG9mIGNob2ljZSBkZXBlbmRzIG9uIHdoYXQgYXV4aWxpYXJ5IGluZm9ybWF0aW9uIGlzIGF2YWlsYWJsZSBhbmQgd2hldGhlciB0aGVyZSBhcmUgKG11bHRpdmFyaWF0ZSkgZWRpdCByZXN0cmljdGlvbnMgb24gdGhlIGRhdGEgdG8gYmUgaW1wdXRlZC4gVGhlIGF2YWlsYWJpbGl0eSBvZiBSIHNvZnR3YXJlIGZvciBpbXB1dGF0aW9uIHVuZGVyIGVkaXQgcmVzdHJpY3Rpb25zIGlzLCB0byBvdXIgYmVzdCBrbm93bGVkZ2UsIGxpbWl0ZWQuIEhvd2V2ZXIsIGEgdmlhYmxlIHN0cmF0ZWd5IGZvciBpbXB1dGluZyBudW1lcmljYWwgZGF0YSBpcyB0byBmaXJzdCBpbXB1dGUgbWlzc2luZyB2YWx1ZXMgd2l0aG91dCByZXN0cmljdGlvbnMsIGFuZCB0aGVuIG1pbmltYWxseSBhZGp1c3QgdGhlIGltcHV0ZWQgdmFsdWVzIHNvIHRoYXQgdGhlIHJlc3RyaWN0aW9ucyBhcmUgb2JleWVkLiBTZXBhcmF0ZWx5LCB0aGVzZSBtZXRob2RzIGFyZSBhdmFpbGFibGUgaW4gUi4NCg0KKiBwcmVkaWN0b3IgaXMgbnVtZXJpY2FsIHZhcmlhYmxlDQogICAgKyAibWVhbiIgOiBhcml0aG1ldGljIG1lYW4NCiAgICArICJtZWRpYW4iIDogbWVkaWFuDQogICAgKyAibW9kZSIgOiBtb2RlDQogICAgKyAia25uIiA6IEstbmVhcmVzdCBuZWlnaGJvcnMNCiAgICAgICAgKyB0YXJnZXQgdmFyaWFibGUgbXVzdCBiZSBzcGVjaWZpZWQNCiAgICArICJycGFydCIgOiBSZWN1cnNpdmUgUGFydGl0aW9uaW5nIGFuZCBSZWdyZXNzaW9uIFRyZWVzDQogICAgICAgICsgdGFyZ2V0IHZhcmlhYmxlIG11c3QgYmUgc3BlY2lmaWVkICAgIA0KICAgICsgIm1pY2UiIDogTXVsdGl2YXJpYXRlIEltcHV0YXRpb24gYnkgQ2hhaW5lZCBFcXVhdGlvbnMNCiAgICAgICAgKyB0YXJnZXQgdmFyaWFibGUgbXVzdCBiZSBzcGVjaWZpZWQgIA0KICAgICAgICArIHJhbmRvbSBzZWVkIG11c3QgYmUgc2V0DQoqIHByZWRpY3RvciBpcyBjYXRlZ29yaWNhbCB2YXJpYWJsZQ0KICAgICsgIm1vZGUiIDogbW9kZQ0KICAgICsgInJwYXJ0IiA6IFJlY3Vyc2l2ZSBQYXJ0aXRpb25pbmcgYW5kIFJlZ3Jlc3Npb24gVHJlZXMNCiAgICAgICAgKyB0YXJnZXQgdmFyaWFibGUgbXVzdCBiZSBzcGVjaWZpZWQgICAgDQogICAgKyAibWljZSIgOiBNdWx0aXZhcmlhdGUgSW1wdXRhdGlvbiBieSBDaGFpbmVkIEVxdWF0aW9ucw0KICAgICAgICArIHRhcmdldCB2YXJpYWJsZSBtdXN0IGJlIHNwZWNpZmllZCAgDQogICAgICAgICsgcmFuZG9tIHNlZWQgbXVzdCBiZSBzZXQNCg0KKipFeGFtcGxlKiogDQoNCkluIHRoZSBmb2xsb3dpbmcgZXhhbXBsZSwgYGltcHV0YXRlX25hKClgIGltcHV0ZXMgdGhlIG1pc3NpbmcgdmFsdWUgb2YgYEluY29tZWAsIGEgbnVtZXJpYyB2YXJpYWJsZSBvZiBjYXJzZWF0cywgdXNpbmcgdGhlICJycGFydCIgbWV0aG9kLiBgc3VtbWFyeSgpYCBzdW1tYXJpemVzIG1pc3NpbmcgdmFsdWUgaW1wdXRhdGlvbiBpbmZvcm1hdGlvbiwgYW5kIGBwbG90KClgIHZpc3VhbGl6ZXMgbWlzc2luZyBpbmZvcm1hdGlvbi4NCg0KYGBge3IgaW1wdXRhdGVfbmEsIGZpZy5hbGlnbj0nY2VudGVyJywgZmlnLndpZHRoID0gNywgZmlnLmhlaWdodCA9IDV9DQppbmNvbWUgPC0gaW1wdXRhdGVfbmEoY2Fyc2VhdHMsIEluY29tZSwgVVMsIG1ldGhvZCA9ICJycGFydCIpDQogICMgc3VtbWFyeSBvZiBpbXB1dGF0aW9uDQogIHN1bW1hcnkoaW5jb21lKQ0KICAjIHZpeiBvZiBpbXB1dGF0aW9uDQogIHBsb3QoaW5jb21lKQ0KYGBgDQoNCioqRXhhbXBsZSoqDQoNClRoZSBmb2xsb3dpbmcgaW1wdXRlcyB0aGUgY2F0ZWdvcmljYWwgdmFyaWFibGUgYHVyYmFuYCBieSB0aGUgIm1pY2UiIG1ldGhvZC4NCg0KYGBge3IgaW1wdXRhdGVfbmEyLCBmaWcuYWxpZ249J2NlbnRlcicsIGZpZy53aWR0aCA9IDcsIGZpZy5oZWlnaHQgPSA1fQ0KbGlicmFyeShtaWNlKQ0KdXJiYW4gPC0gaW1wdXRhdGVfbmEoY2Fyc2VhdHMsIFVyYmFuLCBVUywgbWV0aG9kID0gIm1pY2UiKQ0KIyByZXN1bHQgb2YgaW1wdXRhdGlvbg0KdXJiYW4NCiMgc3VtbWFyeSBvZiBpbXB1dGF0aW9uDQpzdW1tYXJ5KHVyYmFuKQ0KIyB2aXogb2YgaW1wdXRhdGlvbg0KcGxvdCh1cmJhbikNCmBgYA0KDQpJbiB0aGUgZm9sbG93aW5nIGV4ZXJjaXNlIHRyeSB0byBpbXB1dGUgdGhlIG1pc3NpbmcgdmFsdWUgb2YgdGhlIGBJbmNvbWVgIHZhcmlhYmxlLCBhbmQgdGhlbiBjYWxjdWxhdGVzIHRoZSBhcml0aG1ldGljIG1lYW4gZm9yIGVhY2ggbGV2ZWwgb2YgYFVTYC4gSW4gdGhpcyBjYXNlLCBgZHBseXJgIHNob3VsZCBiZSB1c2VkLCBhbmQgaXQgaXMgZWFzaWx5IGludGVycHJldGVkIGxvZ2ljYWxseSB1c2luZyBwaXBlcyENCg0KIyMjIEV4ZXJjaXNlIDMuIA0KDQpgYGB7ciBleDN9DQojIFRoZSBtZWFuIGJlZm9yZSBhbmQgYWZ0ZXIgdGhlIGltcHV0YXRpb24gb2YgdGhlIEluY29tZSB2YXJpYWJsZQ0KDQpgYGANCg0KIyMjIEltcHV0YXRpb24gb2Ygb3V0bGllcnMNCg0KYGltcHV0YXRlX291dGxpZXIoKWAgaW1wdXRlcyB0aGUgb3V0bGllcnMgdmFsdWUuIFRoZSBwcmVkaWN0b3Igd2l0aCBvdXRsaWVycyBzdXBwb3J0cyBvbmx5IG51bWVyaWMgdmFyaWFibGVzIGFuZCBzdXBwb3J0cyB0aGUgZm9sbG93aW5nIG1ldGhvZHMuDQoNCiogcHJlZGljdG9yIGlzIG51bWVyaWNhbCB2YXJpYWJsZQ0KICAgICsgIm1lYW4iIDogYXJpdGhtZXRpYyBtZWFuDQogICAgKyAibWVkaWFuIiA6IG1lZGlhbg0KICAgICsgIm1vZGUiIDogbW9kZQ0KICAgICsgImNhcHBpbmciIDogSW1wdXRlIHRoZSB1cHBlciBvdXRsaWVycyB3aXRoIDk1IHBlcmNlbnRpbGUsIGFuZCBJbXB1dGUgdGhlIGJvdHRvbSBvdXRsaWVycyB3aXRoIDUgcGVyY2VudGlsZS4NCg0KIyMjIEV4ZXJjaXNlIDQuDQoNClVzZSB0aGUgZnVuY3Rpb24gYGltcHV0YXRlX291dGxpZXIoKWAgdG8gaW1wdXRlIHRoZSBvdXRsaWVycyBvZiB0aGUgbnVtZXJpYyB2YXJpYWJsZSBgUHJpY2VgIGFzIHRoZSAiY2FwcGluZyIgbWV0aG9kLiBIaW50OiBgc3VtbWFyeSgpYCBzdW1tYXJpemVzIG91dGxpZXJzIGltcHV0YXRpb24gaW5mb3JtYXRpb24sIGFuZCBgcGxvdCgpYCB2aXN1YWxpemVzIGltcHV0YXRpb24gaW5mb3JtYXRpb24uDQoNCmBgYHtyIGV4NH0NCiMgc29sdXRpb24gZm9yIHRoZSBleGVyY2lzZSA0IGhlcmUgOy0pDQpgYGANCg0KVGhlIGZvbGxvd2luZyBleGFtcGxlIGltcHV0ZXMgdGhlIG91dGxpZXJzIG9mIHRoZSBgUHJpY2VgIHZhcmlhYmxlLCBhbmQgdGhlbiBjYWxjdWxhdGVzIHRoZSBhcml0aG1ldGljIG1lYW4gZm9yIGVhY2ggbGV2ZWwgb2YgYFVTYC4gSW4gdGhpcyBjYXNlLCBgZHBseXJgIGlzIHVzZWQsIGFuZCBpdCBpcyBlYXNpbHkgaW50ZXJwcmV0ZWQgbG9naWNhbGx5IHVzaW5nIHBpcGVzLg0KDQoqKkV4YW1wbGUqKg0KDQpgYGB7ciBpbXB1dGF0ZV9vdXRsaWVyMn0NCiMgVGhlIG1lYW4gYmVmb3JlIGFuZCBhZnRlciB0aGUgaW1wdXRhdGlvbiBvZiB0aGUgUHJpY2UgdmFyaWFibGUNCmNhcnNlYXRzICU+JQ0KICBtdXRhdGUoUHJpY2VfaW1wID0gaW1wdXRhdGVfb3V0bGllcihjYXJzZWF0cywgUHJpY2UsIG1ldGhvZCA9ICJjYXBwaW5nIikpICU+JQ0KICBncm91cF9ieShVUykgJT4lDQogIHN1bW1hcmlzZShvcmlnID0gbWVhbihQcmljZSwgbmEucm0gPSBUUlVFKSwNCiAgICBpbXB1dGF0aW9uID0gbWVhbihQcmljZV9pbXAsIG5hLnJtID0gVFJVRSkpDQpgYGANCg0KDQojIyBUcmFuc2Zvcm1hdGlvbnMNCg0KRmluYWxseSwgd2Ugc29tZXRpbWVzIGVuY291bnRlciB0aGUgc2l0dWF0aW9uIHdoZXJlIHdlIGhhdmUgcHJvYmxlbXMgd2l0aCBza2V3ZWQgZGlzdHJpYnV0aW9ucyBvciB3ZSBqdXN0IHdhbnQgdG8gW3RyYW5zZm9ybV17LnVsfSwgW3JlY29kZV17LnVsfSBvciBwZXJmb3JtIFtkaXNjcmV0aXphdGlvbl17LnVsfS4gTGV0J3MgcmV2aWV3IHNvbWUgb2YgdGhlIG1vc3QgcG9wdWxhciB0cmFuc2Zvcm1hdGlvbiBtZXRob2RzLg0KDQpGaXJzdCwgc3RhbmRhcmRpemF0aW9uIChhbHNvIGtub3duIGFzIG5vcm1hbGl6YXRpb24pOg0KDQotICAgKipaLXNjb3JlKiogYXBwcm9hY2ggLSBzdGFuZGFyZGl6YXRpb24gcHJvY2VkdXJlLCB1c2luZyB0aGUgZm9ybXVsYTogJHo9XGZyYWN7eC1cbXV9e1xzaWdtYX0kIHdoZXJlICRcbXUkID0gbWVhbiBhbmQgJFxzaWdtYSQgPSBzdGFuZGFyZCBkZXZpYXRpb24uIFotc2NvcmVzIGFyZSBhbHNvIGtub3duIGFzIHN0YW5kYXJkaXplZCBzY29yZXM7IHRoZXkgYXJlIHNjb3JlcyAob3IgZGF0YSB2YWx1ZXMpIHRoYXQgaGF2ZSBiZWVuIGdpdmVuIGEgY29tbW9uICpzdGFuZGFyZCouIFRoaXMgc3RhbmRhcmQgaXMgYSBtZWFuIG9mIHplcm8gYW5kIGEgc3RhbmRhcmQgZGV2aWF0aW9uIG9mIDEuDQoNCi0gICAqKm1pbm1heCoqIGFwcHJvYWNoIC0gQW4gYWx0ZXJuYXRpdmUgYXBwcm9hY2ggdG8gWi1zY29yZSBub3JtYWxpemF0aW9uIChvciBzdGFuZGFyZGl6YXRpb24pIGlzIHRoZSBzby1jYWxsZWQgTWluTWF4IHNjYWxpbmcgKG9mdGVuIGFsc28gc2ltcGx5IGNhbGxlZCAibm9ybWFsaXphdGlvbiIgLSBhIGNvbW1vbiBjYXVzZSBmb3IgYW1iaWd1aXRpZXMpLiBJbiB0aGlzIGFwcHJvYWNoLCB0aGUgZGF0YSBpcyBzY2FsZWQgdG8gYSBmaXhlZCByYW5nZSAtIHVzdWFsbHkgMCB0byAxLiBUaGUgY29zdCBvZiBoYXZpbmcgdGhpcyBib3VuZGVkIHJhbmdlIC0gaW4gY29udHJhc3QgdG8gc3RhbmRhcmRpemF0aW9uIC0gaXMgdGhhdCB3ZSB3aWxsIGVuZCB1cCB3aXRoIHNtYWxsZXIgc3RhbmRhcmQgZGV2aWF0aW9ucywgd2hpY2ggY2FuIHN1cHByZXNzIHRoZSBlZmZlY3Qgb2Ygb3V0bGllcnMuIElmIHlvdSB3b3VsZCBsaWtlIHRvIHBlcmZvcm0gTWluTWF4IHNjYWxpbmcgLSBzaW1wbHkgc3Vic3RyYWN0IG1pbmltdW0gdmFsdWUgYW5kIGRpdmlkZSBpdCBieSByYW5nZTokKHgtbWluKS8obWF4LW1pbikkDQoNCkluIG9yZGVyIHRvIHNvbHZlIHByb2JsZW1zIHdpdGggdmVyeSBza2V3ZWQgZGlzdHJpYnV0aW9ucyB3ZSBjYW4gYWxzbyB1c2Ugc2V2ZXJhbCB0eXBlcyBvZiBzaW1wbGUgdHJhbnNmb3JtYXRpb25zOg0KDQogICAgKyAibG9nIiA6IGxvZyB0cmFuc2Zvcm1hdGlvbi4gbG9nKHgpDQogICAgKyAibG9nKzEiIDogbG9nIHRyYW5zZm9ybWF0aW9uLiBsb2coeCArIDEpLiBVc2VkIGZvciB2YWx1ZXMgdGhhdCBjb250YWluIDAuDQogICAgKyAic3FydCIgOiBzcXVhcmUgcm9vdCB0cmFuc2Zvcm1hdGlvbi4NCiAgICArICIxL3giIDogMSAvIHggdHJhbnNmb3JtYXRpb24NCiAgICArICJ4XjIiIDogeCBzcXVhcmUgdHJhbnNmb3JtYXRpb24NCiAgICArICJ4XjMiIDogeF4zIHNxdWFyZSB0cmFuc2Zvcm1hdGlvbg0KDQojIyMgU3RhbmRhcmRpemF0aW9uDQoNClVzZSB0aGUgbWV0aG9kcyAienNjb3JlIiBhbmQgIm1pbm1heCIgdG8gcGVyZm9ybSBzdGFuZGFyZGl6YXRpb24uDQoNCmBgYHtyIHN0YW5kYXJkaXphdGlvbiwgZmlnLmFsaWduPSdjZW50ZXInLCBmaWcud2lkdGggPSA3LCBmaWcuaGVpZ2h0ID0gNX0NCmNhcnNlYXRzICU+JSANCiAgbXV0YXRlKEluY29tZV9taW5tYXggPSB0cmFuc2Zvcm0oY2Fyc2VhdHMkSW5jb21lLCBtZXRob2QgPSAibWlubWF4IiksDQogICAgU2FsZXNfbWlubWF4ID0gdHJhbnNmb3JtKGNhcnNlYXRzJFNhbGVzLCBtZXRob2QgPSAibWlubWF4IikpICU+JSANCiAgc2VsZWN0KEluY29tZV9taW5tYXgsIFNhbGVzX21pbm1heCkgJT4lIA0KICBib3hwbG90KCkNCmBgYA0KDQoNCiMjIyBCaW5uaW5nDQoNClNvbWV0aW1lcyB3ZSBqdXN0IHdvdWxkIGxpa2UgdG8gcGVyZm9ybSBzbyBjYWxsZWQgJ2Jpbm5pbmcnIHByb2NlZHVyZSB0byBiZSBhYmxlIHRvIGFuYWx5emUgb3VyIGNhdGVnb3JpY2FsIGRhdGEsIHRvIGNvbXBhcmUgc2V2ZXJhbCBjYXRlZ29yaWNhbCB2YXJpYWJsZXMsIHRvIGNvbnN0cnVjdCBzdGF0aXN0aWNhbCBtb2RlbHMgZXRjLiBUaGFua3MgdG8gdGhlICdiaW5uaW5nJyBmdW5jdGlvbiB3ZSBjYW4gdHJhbnNmb3JtIHF1YW50aXRhdGl2ZSB2YXJpYWJsZXMgaW50byBjYXRlZ29yaWNhbCB1c2luZyBzZXZlcmFsIG1ldGhvZHM6DQoNCmBiaW5uaW5nKClgIHRyYW5zZm9ybXMgYSBudW1lcmljIHZhcmlhYmxlIGludG8gYSBjYXRlZ29yaWNhbCB2YXJpYWJsZSBieSBiaW5uaW5nIGl0LiBUaGUgZm9sbG93aW5nIHR5cGVzIG9mIGJpbm5pbmcgYXJlIHN1cHBvcnRlZC4NCg0KKiAicXVhbnRpbGUiIDogY2F0ZWdvcml6ZSB1c2luZyBxdWFudGlsZSB0byBpbmNsdWRlIHRoZSBzYW1lIGZyZXF1ZW5jaWVzDQoqICJlcXVhbCIgOiBjYXRlZ29yaXplIHRvIGhhdmUgZXF1YWwgbGVuZ3RoIHNlZ21lbnRzDQoqICJwcmV0dHkiIDogY2F0ZWdvcml6ZWQgaW50byBtb2RlcmF0ZWx5IGdvb2Qgc2VnbWVudHMNCiogImttZWFucyIgOiBjYXRlZ29yaXphdGlvbiB1c2luZyBLLW1lYW5zIGNsdXN0ZXJpbmcNCiogImJjbHVzdCIgOiBjYXRlZ29yaXphdGlvbiB1c2luZyBiYWdnZWQgY2x1c3RlcmluZyB0ZWNobmlxdWUNCg0KSGVyZSBhcmUgc29tZSBleGFtcGxlcyBvZiBob3cgdG8gYmluIGBJbmNvbWVgIHVzaW5nIGBiaW5uaW5nKClgLjoNCg0KKipFeGFtcGxlOioqIA0KDQpgYGB7ciBiaW4xfQ0KIyBCaW5uaW5nIHRoZSBjYXJhdCB2YXJpYWJsZS4gZGVmYXVsdCB0eXBlIGFyZ3VtZW50IGlzICJxdWFudGlsZSINCmJpbiA8LSBiaW5uaW5nKGNhcnNlYXRzJEluY29tZSkNCiMgUHJpbnQgYmlucyBjbGFzcyBvYmplY3QNCmJpbg0KIyBTdW1tYXJpemUgYmlucyBjbGFzcyBvYmplY3QNCnN1bW1hcnkoYmluKQ0KIyBQbG90IGJpbnMgY2xhc3Mgb2JqZWN0DQpwbG90KGJpbikNCmBgYA0KDQoNClVzaW5nIHBpcGVzICYgZHBseXI6DQoNCg0KYGBge3IgYmlubmluZywgZWNobyA9IFRSVUV9DQogY2Fyc2VhdHMgJT4lDQogbXV0YXRlKEluY29tZV9iaW4gPSBiaW5uaW5nKGNhcnNlYXRzJEluY29tZSkgJT4lIA0KICAgICAgICAgICAgICAgICAgICAgZXh0cmFjdCgpKSAlPiUNCiBncm91cF9ieShTaGVsdmVMb2MsIEluY29tZV9iaW4pICU+JQ0KIHN1bW1hcmlzZShmcmVxID0gbigpKSAlPiUNCiBhcnJhbmdlKGRlc2MoZnJlcSkpICU+JQ0KIGhlYWQoMTApDQpgYGANCg0KDQoqKkV4YW1wbGU6KiogUmVjb2RlIHRoZSBvcmlnaW5hbCBkaXN0cmlidXRpb24gb2YgaW5jb21lcyB1c2luZyBmaXhlZCBsZW5ndGggb2YgaW50ZXJ2YWxzIGFuZCBhc3NpZ24gdGhlbSBsYWJlbHMuDQoNCmBgYHtyIGV4YW1wbGU3LCBlY2hvID0gVFJVRX0NCmluY29tZV9maXhlZDwtIGJpbm5pbmcoY2Fyc2VhdHMkSW5jb21lLCBuYmlucyA9IDQsDQogICAgICAgICAgICAgICAgICAgbGFiZWxzID0gYygibG93IiwgImF2ZXJhZ2UiLCAiaGlnaCIsICJ2ZXJ5IGhpZ2giKSkNCnN1bW1hcnkoaW5jb21lX2ZpeGVkKQ0KcGxvdChpbmNvbWVfZml4ZWQpDQpgYGANCg0KSW4gY2FzZSBvZiBzdGF0aXN0aWNhbCBtb2RlbGluZyAoaS5lLiBjcmVkaXQgc2NvcmluZyBwdXJwb3NlcykgLSB3ZSBuZWVkIHRvIGJlIGF3YXJlIG9mIHRoZSBmYWN0LCB0aGF0IHRoZSBbKioqb3B0aW1hbCoqKl17LnVsfSBkaXNjcmV0aXphdGlvbiBvZiB0aGUgb3JpZ2luYWwgZGlzdHJpYnV0aW9uIG11c3QgYmUgYWNoaWV2ZWQuIFRoZSAnKmJpbm5pbmdfYnkqJyBmdW5jdGlvbiBjb21lcyB3aXRoIHNvbWUgaGVscCBoZXJlLg0KDQoqKkV4YW1wbGU6KiogUGVyZm9ybSBkaXNjcmV0aXphdGlvbiBvZiB0aGUgdmFyaWFibGUgJ0FkdmVydGlzaW5nJyB1c2luZyBvcHRpbWFsIGJpbm5pbmcuDQoNCmBgYHtyIGV4YW1wbGU4LCBlY2hvID0gVFJVRX0NCmJpbiA8LSBiaW5uaW5nX2J5KGNhcnNlYXRzLCAiVVMiLCAiQWR2ZXJ0aXNpbmciKQ0Kc3VtbWFyeShiaW4pDQpwbG90KGJpbikNCmBgYA0KDQpQbGVhc2UgdGFrZSBhIGxvb2sgb25jZSBhZ2FpbiBhdCB0aGUgZmluYWwgcmVwb3J0IG9mIG91ciBiaW5uaW5nIHByb2NlZHVyZS4gVHJ5IHRvIGludGVycHJldCBpbmZvcm1hdGlvbiBjcml0ZXJpb24gKEplZmZyZXkncyBvciBLLVMpIGFuZCBwbG90cy4NCg0KV2UgY2FuIGZpbmFsbHkgcHJpbnQgc29tZSBzdW1tYXJ5IHJlcG9ydHMgYWZ0ZXIgdGhlIGNsZWFuc2luZyBwcm9jZWR1cmVzIGFyZSBhbGwgZG9uZSAtIGluIHRoZSBQREYgZm9ybWF0IChURVggaW5zdGFsbGF0aW9uIGlzIHJlcXVpcmVkIGZpcnN0KSBvciBIVE1MIGZvcm1hdC4gUGxlYXNlIHVzZSB0aGlzIGNodW5rIGluIHlvdXIgb3duIFJTdHVkaW8gaW4gb3JkZXIgdG8gcHJvZHVjZSBzb21lIHJlcG9ydHMuDQoNCiAgICBjYXJzZWF0cyAlPiUNCiAgICAgIHRyYW5zZm9ybWF0aW9uX3JlcG9ydCh0YXJnZXQgPSBVUywgb3V0cHV0X2Zvcm1hdCA9ICJodG1sIiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgb3V0cHV0X2ZpbGUgPSAidHJhbnNmb3JtYXRpb25fY2Fyc2VhdHMuaHRtbCIpDQoNCg0KIyMjIEV4ZXJjaXNlIDUuDQoNClVzZSBxdWFudGlsZSBhcHByb2FjaCB0byBwZXJmb3JtIGJpbm5pbmcgb2YgdGhlIHZhcmlhYmxlICdJbmNvbWUnICg0IGVxdWFsIGNhdGVnb3JpZXMpLg0KDQpgYGB7ciBleDV9DQojIHNvbHV0aW9uIGZvciB0aGUgZXhlcmNpc2UgNSBoZXJlIDstKQ0KYGBgDQoNCg0KDQojIyBBdXRvbWF0ZWQgcmVwb3J0DQoNCmRsb29rciBwcm92aWRlcyB0d28gYXV0b21hdGVkIGRhdGEgdHJhbnNmb3JtYXRpb24gcmVwb3J0czoNCg0KKiBXZWIgcGFnZS1iYXNlZCBkeW5hbWljIHJlcG9ydHMgY2FuIHBlcmZvcm0gaW4tZGVwdGggYW5hbHlzaXMgdGhyb3VnaCB2aXN1YWxpemF0aW9uIGFuZCBzdGF0aXN0aWNhbCB0YWJsZXMuDQoqIFN0YXRpYyByZXBvcnRzIGdlbmVyYXRlZCBhcyBwZGYgZmlsZXMgb3IgaHRtbCBmaWxlcyBjYW4gYmUgYXJjaGl2ZWQgYXMgb3V0cHV0IG9mIGRhdGEgYW5hbHlzaXMuDQoNCiMjIyBEeW5hbWljIHJlcG9ydCANCg0KYHRyYW5zZm9ybWF0aW9uX3dlYl9yZXBvcnQoKWAgY3JlYXRlcyBkeW5hbWljIHJlcG9ydCBmb3Igb2JqZWN0IGluaGVyaXRlZCBmcm9tIGRhdGEuZnJhbWUoYHRibF9kZmAsIGB0YmxgLCBldGMpIG9yIGRhdGEuZnJhbWUuDQoNClRoZSBjb250ZW50cyBvZiB0aGUgcmVwb3J0IGFyZSBhcyBmb2xsb3dzLjoNCg0KKiBPdmVydmlldw0KICAgICsgRGF0YSBTdHJ1Y3R1cmVzDQogICAgKyBEYXRhIFR5cGVzDQogICAgKyBKb2IgSW5mb3JtYXRpb25zDQoqIEltcHV0YXRpb24NCiAgICArIE1pc3NpbmcgVmFsdWVzDQogICAgKyBPdXRsaWVycw0KKiBSZXNvbHZpbmcgU2tld25lc3MNCiogQmlubmluZw0KKiBPcHRpbWFsIEJpbm5pbmcNCg0KVGhlIGZvbGxvd2luZyBzY3JpcHQgY3JlYXRlcyBhIGRhdGEgdHJhbnNmb3JtYXRpb24gcmVwb3J0IGZvciB0aGUgYHRibF9kZmAgY2xhc3Mgb2JqZWN0LCBgaGVhcnRmYWlsdXJlYC4NCg0KDQpoZWFydGZhaWx1cmUgJT4lDQogIHRyYW5zZm9ybWF0aW9uX3dlYl9yZXBvcnQodGFyZ2V0ID0gImRlYXRoX2V2ZW50Iiwgc3VidGl0bGUgPSAiaGVhcnRmYWlsdXJlIixvdXRwdXRfZGlyID0gIi4vIiwgb3V0cHV0X2ZpbGUgPSAidHJhbnNmb3JtYXRpb24uaHRtbCIsIHRoZW1lID0gImJsdWUiKQ0KDQoqIFRoZSBkeW5hbWljIGNvbnRlbnRzIG9mIHRoZSByZXBvcnQgaXMgc2hvd24gaW4gdGhlIGZvbGxvd2luZyBmaWd1cmUuOg0KDQpgYGB7ciByZXBvcnQsIGVjaG8gPSBGQUxTRSwgb3V0LndpZHRoID0gIjYwJSIsIGZpZy5hbGlnbiA9ICdjZW50ZXInfQ0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9rZmxpc2lrb3dza2kvZHMvbWFzdGVyL3JlcDEuanBnIikNCmBgYA0KDQoNCiMjIyBTdGF0aWMgcmVwb3J0DQoNCmB0cmFuc2Zvcm1hdGlvbl9wYWdlZF9yZXBvcnQoKWAgY3JlYXRlIHN0YXRpYyByZXBvcnQgZm9yIG9iamVjdCBpbmhlcml0ZWQgZnJvbSBkYXRhLmZyYW1lKGB0YmxfZGZgLCBgdGJsYCwgZXRjKSBvciBkYXRhLmZyYW1lLg0KDQpUaGUgY29udGVudHMgb2YgdGhlIHJlcG9ydCBhcmUgYXMgZm9sbG93cy46DQoNCiogT3ZlcnZpZXcNCiAgICArIERhdGEgU3RydWN0dXJlcw0KICAgICsgSm9iIEluZm9ybWF0aW9ucw0KKiBJbXB1dGF0aW9uDQogICAgKyBNaXNzaW5nIFZhbHVlcw0KICAgICsgT3V0bGllcnMNCiogUmVzb2x2aW5nIFNrZXduZXNzDQoqIEJpbm5pbmcNCiogT3B0aW1hbCBCaW5uaW5nDQoNClRoZSBmb2xsb3dpbmcgc2NyaXB0IGNyZWF0ZXMgYSBkYXRhIHRyYW5zZm9ybWF0aW9uIHJlcG9ydCBmb3IgdGhlIGBkYXRhLmZyYW1lYCBjbGFzcyBvYmplY3QsIGBoZWFydGZhaWx1cmVgLg0KDQpoZWFydGZhaWx1cmUgJT4lDQogIHRyYW5zZm9ybWF0aW9uX3BhZ2VkX3JlcG9ydCh0YXJnZXQgPSAiZGVhdGhfZXZlbnQiLCBzdWJ0aXRsZSA9ICJoZWFydGZhaWx1cmUiLCBvdXRwdXRfZGlyID0gIi4vIiwgb3V0cHV0X2ZpbGUgPSAidHJhbnNmb3JtYXRpb24ucGRmIiwgdGhlbWUgPSAiYmx1ZSIpDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICANCiogVGhlIGNvdmVyIG9mIHRoZSByZXBvcnQgaXMgc2hvd24gaW4gdGhlIGZvbGxvd2luZyBmaWd1cmUuOg0KDQpgYGB7ciByZXBvcnQyLCBlY2hvID0gRkFMU0UsIG91dC53aWR0aCA9ICI2MCUiLCBmaWcuYWxpZ24gPSAnY2VudGVyJ30NCmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKCJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20va2ZsaXNpa293c2tpL2RzL21hc3Rlci9yZXAyLmpwZyIpDQpgYGANCg0KIyMjIERpYWdub3N0aWMgcmVwb3J0IA0KDQoqZGlhZ25vc2VfcGFnZWRfcmVwb3J0KCkqIGNyZWF0ZXMgc3RhdGljIHJlcG9ydCBmb3Igb2JqZWN0IGluaGVyaXRlZCBmcm9tIGRhdGEuZnJhbWUodGJsX2RmLCB0YmwsIGV0Yykgb3IgZGF0YS5mcmFtZS4NCg0KVGhlIGZvbGxvd2luZyBzY3JpcHQgY3JlYXRlcyBhIHF1YWxpdHkgZGlhZ25vc2lzIHJlcG9ydCBmb3IgdGhlIHRibF9kZiBjbGFzcyBvYmplY3QsIGZsaWdodHMuDQoNCmZsaWdodHMgJT4lDQogIGRpYWdub3NlX3BhZ2VkX3JlcG9ydChzdWJ0aXRsZSA9ICJmbGlnaHRzIiwgb3V0cHV0X2RpciA9ICIuLyIsIG91dHB1dF9maWxlID0gIkRpYWduLnBkZiIsIHRoZW1lID0gImJsdWUiKQ0KDQpUaGUgY292ZXIgb2YgdGhlIHJlcG9ydCBpcyBzaG93biBpbiB0aGUgZm9sbG93aW5nIGZpZ3VyZS46DQoNCmBgYHtyIHJlcG9ydDMsIGVjaG8gPSBGQUxTRSwgb3V0LndpZHRoID0gIjYwJSIsIGZpZy5hbGlnbiA9ICdjZW50ZXInfQ0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9rZmxpc2lrb3dza2kvZHMvbWFzdGVyL3JlcDMuanBnIikNCmBgYA0KDQoNCiMjIyBFREEgUmVwb3J0DQoNCmRsb29rciBwcm92aWRlcyB0d28gYXV0b21hdGVkIEVEQSByZXBvcnRzOg0KDQotIFdlYiBwYWdlLWJhc2VkIGR5bmFtaWMgcmVwb3J0cyBjYW4gcGVyZm9ybSBpbi1kZXB0aCBhbmFseXNpcyB0aHJvdWdoIHZpc3VhbGl6YXRpb24gYW5kIHN0YXRpc3RpY2FsIHRhYmxlcy4NCg0KLSBTdGF0aWMgcmVwb3J0cyBnZW5lcmF0ZWQgYXMgcGRmIGZpbGVzIG9yIGh0bWwgZmlsZXMgY2FuIGJlIGFyY2hpdmVkIGFzIG91dHB1dCBvZiBkYXRhIGFuYWx5c2lzLg0KDQplZGFfd2ViX3JlcG9ydCgpIGNyZWF0ZXMgZHluYW1pYyByZXBvcnQgZm9yIG9iamVjdCBpbmhlcml0ZWQgZnJvbSBkYXRhLmZyYW1lKHRibF9kZiwgdGJsLCBldGMpIG9yIGRhdGEuZnJhbWUuDQoNClRoZSBmb2xsb3dpbmcgc2NyaXB0IGNyZWF0ZXMgYSBFREEgcmVwb3J0IGZvciB0aGUgZGF0YS5mcmFtZSBjbGFzcyBvYmplY3QsIGhlYXJ0ZmFpbHVyZToNCg0KaGVhcnRmYWlsdXJlICU+JQ0KICBlZGFfd2ViX3JlcG9ydCh0YXJnZXQgPSAiZGVhdGhfZXZlbnQiLCBzdWJ0aXRsZSA9ICJoZWFydGZhaWx1cmUiLCBvdXRwdXRfZGlyID0gIi4vIiwgb3V0cHV0X2ZpbGUgPSAiRURBLmh0bWwiLCB0aGVtZSA9ICJibHVlIikNCg0KVGhlIGR5bmFtaWMgY29udGVudHMgb2YgdGhlIHJlcG9ydCBpcyBzaG93biBpbiB0aGUgZm9sbG93aW5nIGZpZ3VyZS46DQoNCmBgYHtyIHJlcG9ydDQsIGVjaG8gPSBGQUxTRSwgb3V0LndpZHRoID0gIjYwJSIsIGZpZy5hbGlnbiA9ICdjZW50ZXInfQ0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9rZmxpc2lrb3dza2kvZHMvbWFzdGVyL3JlcDQuanBnIikNCmBgYA0KDQoNCiMjIyBUcmFuc2Zvcm1hdGlvbiBSZXBvcnQNCg0KZGxvb2tyIHByb3ZpZGVzIHR3byBhdXRvbWF0ZWQgZGF0YSB0cmFuc2Zvcm1hdGlvbiByZXBvcnRzOg0KDQotIFdlYiBwYWdlLWJhc2VkIGR5bmFtaWMgcmVwb3J0cyBjYW4gcGVyZm9ybSBpbi1kZXB0aCBhbmFseXNpcyB0aHJvdWdoIHZpc3VhbGl6YXRpb24gYW5kIHN0YXRpc3RpY2FsIHRhYmxlcy4NCi0gU3RhdGljIHJlcG9ydHMgZ2VuZXJhdGVkIGFzIHBkZiBmaWxlcyBvciBodG1sIGZpbGVzIGNhbiBiZSBhcmNoaXZlZCBhcyBvdXRwdXQgb2YgZGF0YSBhbmFseXNpcy4NCg0KdHJhbnNmb3JtYXRpb25fd2ViX3JlcG9ydCgpIGNyZWF0ZXMgZHluYW1pYyByZXBvcnQgZm9yIG9iamVjdCBpbmhlcml0ZWQgZnJvbSBkYXRhLmZyYW1lKHRibF9kZiwgdGJsLCBldGMpIG9yIGRhdGEuZnJhbWUuDQoNClRoZSBmb2xsb3dpbmcgc2NyaXB0IGNyZWF0ZXMgYSBkYXRhIHRyYW5zZm9ybWF0aW9uIHJlcG9ydCBmb3IgdGhlIHRibF9kZiBjbGFzcyBvYmplY3QsIGhlYXJ0ZmFpbHVyZToNCg0KaGVhcnRmYWlsdXJlICU+JQ0KICB0cmFuc2Zvcm1hdGlvbl93ZWJfcmVwb3J0KHRhcmdldCA9ICJkZWF0aF9ldmVudCIsIHN1YnRpdGxlID0gImhlYXJ0ZmFpbHVyZSIsICBvdXRwdXRfZGlyID0gIi4vIiwgb3V0cHV0X2ZpbGUgPSAidHJhbnNmb3JtYXRpb24uaHRtbCIsIHRoZW1lID0gImJsdWUiKQ0KDQpUaGUgZHluYW1pYyBjb250ZW50cyBvZiB0aGUgcmVwb3J0IGlzIHNob3duIGluIHRoZSBmb2xsb3dpbmcgZmlndXJlLjoNCg0KYGBge3IgcmVwb3J0NSwgZWNobyA9IEZBTFNFLCBvdXQud2lkdGggPSAiNjAlIiwgZmlnLmFsaWduID0gJ2NlbnRlcid9DQprbml0cjo6aW5jbHVkZV9ncmFwaGljcygiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL2tmbGlzaWtvd3NraS9kcy9tYXN0ZXIvcmVwNS5qcGciKQ0KYGBgDQoNCg0KIyMgU3VtbWFyeQ0KDQpJZiB3ZSBhcmUgdG8gdGFrZSBhd2F5IG9uZSB0aGluZyBmcm9tIHRoaXMgVElEWSBXRUVLIGl0IHNob3VsZCBiZSB0aGF0IGRlYWxpbmcgd2l0aCBtaXNzaW5nIGRhdGEgaXMgbm90IGFuIGVhc3kgcHJvYmxlbS4gRGF0YSB3aWxsIHJhcmVseSBiZSBtaXNzaW5nIGluIGEgKm5pY2UqIChjb21wbGV0ZWx5IHJhbmRvbSkgd2F5LCBzbyBpZiB3ZSB3YW50IHRvIHJlc29ydCB0byBtb3JlIHNpbXBsZSByZW1vdmFsIG9yIGltcHV0YXRpb24gdGVjaG5pcXVlcywgd2UgbXVzdCBwdXQgcmVhc29uYWJsZSBlZmZvcnQgaW50byBkZXRlcm1pbmluZyB3aGV0aGVyIG9yIG5vdCB0aGUgZGVwZW5kZW5jaWVzIGJldHdlZW4gb2JzZXJ2ZWQgZGF0YSBhbmQgbWlzc2luZ25lc3MgYXJlIGEgY2F1c2UgZm9yIGNvbmNlcm4uIElmIHN0cm9uZyBkZXBlbmRlbmNpZXMgZXhpc3QgYW5kL29yIHRoZXJlIGlzIGEgbG90IG9mIGRhdGEgbWlzc2luZywgdGhlIG1pc3NpbmcgZGF0YSBwcm9ibGVtIGJlY29tZXMgYSBwcmVkaWN0aW9uIHByb2JsZW0gKG9yIGEgc2VyaWVzIG9mIHByZWRpY3Rpb24gcHJvYmxlbXMpLiBXZSBzaG91bGQgYWxzbyBiZSBhd2FyZSBvZiB0aGUgcG9zc2liaWxpdHkgdGhhdCBtaXNzaW5nbmVzcyBkZXBlbmRzIG9uIHRoZSBtaXNzaW5nIHZhbHVlIGluIGEgd2F5IHRoYXQgY2FuJ3QgYmUgZXhwbGFpbmVkIGJ5IG9ic2VydmVkIHZhcmlhYmxlcy4gVGhpcyBjYW4gYWxzbyBjYXVzZSBiaWFzIGluIG91ciBhbmFseXNlcyBhbmQgd2Ugd2lsbCBub3QgYmUgYWJsZSB0byBkZXRlY3QgaXQgdW5sZXNzIHdlIGdldCBvdXIgaGFuZHMgb24gc29tZSBvZiB0aGUgbWlzc2luZyB2YWx1ZXMuDQoNCk5vdGUgdGhhdCB3ZSBmb2N1c2VkIG9ubHkgb24gc3RhbmRhcmQgY3Jvc3Mtc2VjdGlvbmFsIGRhdGEuIFRoZXJlIGFyZSBtYW55IG90aGVyIHR5cGVzIG9mIGRhdGEsIHN1Y2ggYXMgdGltZS1zZXJpZXMgZGF0YSwgc3BhdGlhbCBkYXRhLCBpbWFnZXMsIHNvdW5kLCBncmFwaHMsIGV0Yy4gVGhlIGJhc2ljIHByaW5jaXBsZXMgcmVtYWluIHVuY2hhbmdlZC4gV2UgbXVzdCBiZSBhd2FyZSBvZiB0aGUgbWlzc2luZ25lc3MgbWVjaGFuaXNtIGFuZCBpbnRyb2R1Y2luZyBiaWFzLiBXZSBjYW4gZGVhbCB3aXRoIG1pc3NpbmcgdmFsdWVzIGVpdGhlciBieSByZW1vdmluZyBvYnNlcnZhdGlvbnMvdmFyaWFibGVzIG9yIGJ5IGltcHV0aW5nIHRoZW0uIEhvd2V2ZXIsIGRpZmZlcmVudCwgc29tZXRpbWVzIGFkZGl0aW9uYWwgbW9kZWxzIGFuZCB0ZWNobmlxdWVzIHdpbGwgYmUgYXBwcm9wcmlhdGUuIEZvciBleGFtcGxlLCB0ZW1wb3JhbCBhbmQgc3BhdGlhbCBkYXRhIGxlbmQgdGhlbXNlbHZlcyB0byBpbnRlcnBvbGF0aW9uIG9mIG1pc3NpbmcgdmFsdWVzLg0KDQoNCiMjIEZ1cnRoZXIgcmVhZGluZw0KDQoqIEEgZ2VudGxlIGludHJvZHVjdGlvbiBmcm9tIGEgcHJhY3RpdGlvbmVycyBwZXJzcGVjdGl2ZTogQmxhbmtlcnMsIE0uLCBLb2V0ZXIsIE0uIFcuLCAmIFNjaGlwcGVycywgRy4gTS4gKDIwMTApLiBNaXNzaW5nIGRhdGEgYXBwcm9hY2hlcyBpbiBlSGVhbHRoIHJlc2VhcmNoOiBzaW11bGF0aW9uIHN0dWR5IGFuZCBhIHR1dG9yaWFsIGZvciBub25tYXRoZW1hdGljYWxseSBpbmNsaW5lZCByZXNlYXJjaGVycy4gSm91cm5hbCBvZiBtZWRpY2FsIEludGVybmV0IHJlc2VhcmNoLCAxMig1KSwgZTU0Lg0KDQoqIEEgZ3JlYXQgYm9vayBvbiBiYXNpYyBhbmQgc29tZSBhZHZhbmNlIHRlY2huaXF1ZXM6ICpBbGxpc29uLCBQLiBELiAoMjAwMSkuIE1pc3NpbmcgZGF0YSAoVm9sLiAxMzYpLiBTYWdlIHB1YmxpY2F0aW9ucy4qDQoNCiogQW5vdGhlciBncmVhdCBib29rIHdpdGggbWFueSBleGFtcGxlcyBhbmQgY2FzZS1zdHVkaWVzOiAqVmFuIEJ1dXJlbiwgUy4gKDIwMTgpLiBGbGV4aWJsZSBpbXB1dGF0aW9uIG9mIG1pc3NpbmcgZGF0YS4gQ2hhcG1hbiBhbmQgSGFsbC9DUkMuKg0KDQoqIFVuZGVyc3RhbmRpbmcgbXVsdGlwbGUgaW1wdXRhdGlvbiB3aXRoIGNoYWluZWQgZXF1YXRpb25zIChpbiBSKTogQnV1cmVuLCBTLiBWLiwgJiBHcm9vdGh1aXMtT3Vkc2hvb3JuLCBLLiAoMjAxMCkuIG1pY2U6IE11bHRpdmFyaWF0ZSBpbXB1dGF0aW9uIGJ5IGNoYWluZWQgZXF1YXRpb25zIGluIFIuIEpvdXJuYWwgb2Ygc3RhdGlzdGljYWwgc29mdHdhcmUsIDEtNjguDQoNCiogV2hlbiBkb2luZyBtaXNzaW5nIGRhdGEgdmFsdWUgaGFuZGxpbmcgYW5kIHByZWRpY3Rpb24gc2VwYXJhdGVseSwgd2Ugc2hvdWxkIGJlIGF3YXJlIHRoYXQgY2VydGFpbiB0eXBlcyBvZiBwcmVkaWN0aW9uIG1vZGVscyBtaWdodCB3b3JrIGJldHRlciB3aXRoIGNlcnRhaW4gbWV0aG9kcyBmb3IgaGFuZGxpbmcgbWlzc2luZyB2YWx1ZXMuIEEgcGFwZXIgdGhhdCBpbGx1c3RyYXRlIHRoaXM6ICpZYWRhdiwgTS4gTC4sICYgUm95Y2hvdWRodXJ5LCBCLiAoMjAxOCkuIEhhbmRsaW5nIG1pc3NpbmcgdmFsdWVzOiBBIHN0dWR5IG9mIHBvcHVsYXIgaW1wdXRhdGlvbiBwYWNrYWdlcyBpbiBSLiBLbm93bGVkZ2UtQmFzZWQgU3lzdGVtcywgMTYwLCAxMDQtMTE4LioNCg0KIyMgTGVhcm5pbmcgb3V0Y29tZXMNCg0KYGBge3IgZmlnNSwgZWNobyA9IEZBTFNFLCBvdXQud2lkdGggPSAiNjAlIiwgZmlnLmFsaWduID0gJ2NlbnRlcid9DQprbml0cjo6aW5jbHVkZV9ncmFwaGljcygiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL2tmbGlzaWtvd3NraS9kcy9tYXN0ZXIvbW9uc3Rlcl9zdXBwb3J0LnBuZyIpDQpgYGANCg0KDQpEYXRhIHNjaWVuY2Ugc3R1ZGVudHMgc2hvdWxkIHdvcmsgdG93YXJkcyBvYnRhaW5pbmcgdGhlIGtub3dsZWRnZSBhbmQgdGhlIHNraWxscyB0aGF0IGVuYWJsZSB0aGVtIHRvOg0KDQoqIFJlcHJvZHVjZSB0aGUgdGVjaG5pcXVlcyBkZW1vbnN0cmF0ZWQgaW4gdGhpcyBjaGFwdGVyIHVzaW5nIHRoZWlyIGxhbmd1YWdlL3Rvb2wgb2YgY2hvaWNlLg0KKiBBbmFseXplIHRoZSBzZXZlcml0eSBvZiB0aGUgbWlzc2luZyBkYXRhIHByb2JsZW0uDQoqIFJlY29nbml6ZSB3aGVuIGEgdGVjaG5pcXVlIGlzIGFwcHJvcHJpYXRlIGFuZCB3aGF0IGFyZSBpdHMgbGltaXRhdGlvbnMuDQo=