1 Loading Libraries

library(raster)
library(soilP)
library(dplyr)
library(tidyr)
library(Hmisc)
library(MASS)
library(ggplot2)
library(ade4)

2 Reading ISRIC 2011 data

2.1 Starting with the base raster file

First we load the raster.

data_dir    <- system.file("data",    package = "soilP", mustWork = TRUE)
extdata_dir <- system.file("extdata", package = "soilP", mustWork = TRUE)
FAO74_tif <- file.path(extdata_dir,"ISRIC2011", "FAO74.tif")
# Initializing ISRIC2011 list with the base map
ISRIC2011 <- list()
ISRIC2011$FAO74 <- raster::raster(FAO74_tif)

This raster has a single layer and its value corresponds to the soil map unit identifier (ID) for the Soil Map of the World, FAO74. The same identifier can be found in more recent versions of the FAO soil map and in the harmonized soil database.

2.2 Plotting FAO Soil Map Unit Raster

nb_plot(ISRIC2011$FAO74, main = "Soil Map of the World, FAO74")

The MUIDs have the lowest values in Africa and then ascend through Asia, the Americas, Oceania and attain the highest values in Europe. Contrary to what the file name seems to imply these ids do not directly represent the phosphorus retention potential. Batjes used the ids and a series of SQL queries to assign phosphorus retention potential classes to each map unit, which then are easily projected over each pixel in the map.

These main classes can be extracted from the map unit raster, and the tables in the mdb files. These tables function as raster attribute tables.

2.3 Extracting Raster Attribute Table from Microsoft Access database (mdb file).

For this I need Hmisc and the ODBC drivers. Mac installation is from brew.

The MS Access database has the tables for mapping the soil unit identifiers to soil properties, i.e. phosphorus retention as soil unit composition percentage, or physicochemical variables.

mdb_file <- file.path(
  extdata_dir,
  "ISRIC2011",
  "ISRIC_Phosphorus_Retention_Potential.mdb")

2.4 Adding Raster Attribute Table, including Map Unit P Retention Potential

levels<- S3 method is internal to raster and it could not be exported to soilP, which prevented me from making a working package function out of the following snippet:

### Get Raster Attribute Tables
ISRIC_AT <- list()
ISRIC_AT <- read_ISRIC_AT(mdb_file)
### Adding ID Raster Attribute Table to FAO74
ISRIC2011$FAO74 <- raster::raster(FAO74_tif)
ISRIC2011$FAO74 <- raster::ratify(ISRIC2011$FAO74)
levels(ISRIC2011$FAO74) <- ISRIC_AT$mapunit
the number of rows in the raster attributes (factors) data.frame is unexpectedlonger object length is not a multiple of shorter object lengththe values in the "ID" column in the raster attributes (factors) data.frame have changed

3 Soil Map Phosphorus Retention Potential as Percentages of Map Units

A phosphorus retention potential class was assigned to each of the FAO74 soil units (Low, Moderate, High and very High). Each map unit is composed of up to 8 soil units in different area percentages. Depending on the composition of the map unit and each component soil unit phosphorus retention potential class a map unit class was assigned. In total 260 different phosphorus retention potential classes were assigned to the 4923 map units. These were further grouped into 16 main phosphorus retention potential classes.

pct_vars <- c("Lo", "Mo", "Hi", "VH", "MISC") 
ISRIC2011 <- c(ISRIC2011, layers_from(ISRIC2011$FAO74,cols = pct_vars))
for (varname in pct_vars) {
  out_tif <- file.path( extdata_dir, "ISRIC2011", paste0(varname,".tif"))
    
  writeRaster(ISRIC2011[[varname]],
              file = out_tif,
              datatype = 'INT1U',
              overwrite = TRUE)
    
  nb_plot(ISRIC2011[[varname]], main =  varname)
}

4 Rebuilding the arcgis render appeareance

We will use a manually curated raster attribute table stored in th soilclass dataframe.

See the manual for details.

?soilclass

4.1 Adding Raster Attribute Table to ISRIC2011$FAO74

First we need to merge soilclass to the Soil Map Unit id number, then we add this as the RAT to the FAO74 raster.

### Adding ID Raster Attribute Table to FAO74
ISRIC_AT$mu_soilclass <- ISRIC_AT$mapunit %>% 
    dplyr::left_join(soilclass, by = c("mainclass" = "main"))
Column `mainclass`/`main` joining factors with different levels, coercing to character vector
levels(ISRIC2011$FAO74) <- ISRIC_AT$mu_soilclass

4.2 Reclassifying the FAO74 soil map into ascending Phosphorus Retention Classes

I manually assigned integers in ascending order to the ascending column in the soilclass dataframe as follows: For the Low map unit main class, higher percentage of Low soil unit P retention corresponded to lower integers. For the Moderate class, a multivariate analysis reveled that Mo2 has a higher content of Low souil units,snd simirlaly Mo3 has a greater content of Hi soil units tan Mo1, which suggests a Mo2, Mo1, Mo3, ascending order. For the High, and very High map unit main classes, higher integers were assigned to higher percentage of corresponding soil unit P retention. This means the higher the Low soil unit P retention percentage the lower the integer, and complentarily the higher the High, and Very High soil unit P retention percentage the higher the integer, except in the Moderate class that behaves unexpectedly.

ISRIC2011$main <- layers_from(ISRIC2011$FAO74, cols = "ascending")[[1]]
raster::writeRaster(
  ISRIC2011$main,
  file.path(extdata_dir,"ISRIC2011","main.tif"),
  datatype = 'INT1U',
  overwrite = TRUE)

4.3 Plotting Soil Phosphorus Retention Potential Raster

nb_plot(ISRIC2011$main,
        axis.args = list(breaks=0:15, at = 0:15, cex.axis = 2),
        main =  "Phosphorus Retention Potential, ISRIC2011")

Now the numbers do represent the phosphorus retention potential!!!

However, the raster plot legend above assumes a continuous scale from 0 to 15, while the data is explicitly a categorical variable (although derived from continuous percentages, see above).

# Raster Attribute Table
ISRIC2011$main <- raster::ratify(ISRIC2011$main)
# Assign RAT to main class raster
levels(ISRIC2011$main) <- rat(ISRIC2011$main) %>% 
    dplyr::inner_join(soilclass, by = c("ID" = "ascending"))

4.4 Adding arcgis render color and saving the map as full resolution geotif

main_color <- layers_from(ISRIC2011$FAO74, cols = c("r","g","b"))
plotRGB(stack(main_color))

writeRaster(stack(main_color),
  filename  = file.path(extdata_dir, "ISRIC2011","main_color.tif"),
  datatype  = "INT1U",
  options   = "TFW=YES",
  format    = "GTiff",
  overwrite = TRUE)

4.5 Using rasterVis to appropiately label categories and add legend

Remember that we assigned to the to the phosphorus retention potential main classes an ad hoc order so we can interpret a direction of ascending retention potential (see above) in the plot legend.

In order to show the right labels in this ascending scale we use the levelplot function from RasterVis that uses the raster attribute table to transform the numerical value of the raster to discrete categories.

rasterVis::levelplot(
  ISRIC2011$main, att = "main",
  col.regions = soilclass$color_hex[1:16],
  maxpixels = ncell(ISRIC2011$main),
  scales = list(draw = FALSE),
  xlab = NULL, ylab = NULL,
  main = "Generalized Phosphorus Retention Potential Map")

5 Multivariate analysis of Soil retention Potential

We should establish a data based order of map unit soil phosphorus retention classes instead of postulating an ad hoc order. This can be done through selecting the first discriminant dimension from a Discriminant Analysis of the percentage of soil unit phosphorus retention classes per mapping unit. Furthermore, we can use that discriminant dimention as a continuous variable instead of the discrete classes for downstream analyses

5.1 Map Unit Soil Composition Matrix and entropy

Each soil unit percentage can be used as a map unit descriptor variable for multivariate analysis of the phosphorus retention potential per map unit.

ISRIC_AT <- within(ISRIC_AT, {
  su_share <- soil_composition(mapunit,FAO74)
  su_share <- as.data.frame(
    cbind( ID = mapunit$ID,
           su_share,
           soilS = row_entropy(su_share/100)))
})

However this results in sparse matrix with essentially orthogonal columns.

5.2 Phosphorus Retention, Soil Unit Composition Join table

phychem_vars <- c("pH","SAND","SILT","CLAY","CECLAY")
su_types <- colnames(ISRIC_AT$su_share)
ret_vars <- c("ID",
              "mainclass",
              phychem_vars,
              pct_vars,
              su_types)
levels <- soilclass %>%
            dplyr::arrange(ascending) %>%
            dplyr::select(main)
ISRIC_AT <- within(ISRIC_AT, {
  ret_share <- mapunit %>%
    dplyr::left_join(soilunit, by = c("SOIL1" = "key")) %>%
    dplyr::left_join(su_share, by = "ID") %>%
    dplyr::select(!!!ret_vars) 
  ret_share$mainclass <- factor(ret_share$mainclass, levels = levels$main[1:16])
})
Column `SOIL1`/`key` joining factors with different levels, coercing to character vector

5.3 Soil and Phosphorus Retention Class Composition Entropy

ISRIC_AT <- within(ISRIC_AT, {
  ret_share$pS <- row_entropy(ret_share[,pct_vars] / 100)
})
with(ISRIC_AT,{
  boxplot(soilS ~ mainclass, data = ret_share, las = 2, 
          main = "Map Unit Soil Composition Entropy")
  boxplot(pS ~ mainclass, data = ret_share, las = 2,
          main = "Phosphorus Retention Class Composition Entropy")
})

5.4 Exploratory Discriminant Analysis using ade4

Soil Retention Potential Space is essentially a tetrahedron with Moderate retention at its center.Only three degrees of freedom, Lo-VH, Mo-Hi, Lo-Hi.

# create dudi object for discriminant analysis
soil_idx <- with(ISRIC_AT$ret_share,{
    which(is_soil(mainclass))
})
soil_data <- ISRIC_AT$ret_share[soil_idx,-1]
soil_data$mainclass   <- with(ISRIC_AT$ret_share,{
    factor(mainclass[soil_idx], levels = levels$main[5:16])
})
pca <- dudi.pca(soil_data[,-1], scannf = FALSE, nf = 139)
  
dis <- discrimin(pca, fac = soil_data$mainclass, scannf = FALSE, nf = 10)
palette <- soilclass$color_hex[5:16]
color <- palette[soil_data$mainclass]
# Select most important variables
disva <- as.data.frame(dis$va)
disva$idx <- (1:nrow(dis$va)) / 1000
major_vars <- 1000 * disva %>% 
  dplyr::filter_all(any_vars(abs(.) > 0.5)) %>%
  dplyr::select(idx)
# Plot
par(mfrow = c(2, 2))
s.class(dis$li, xax = 2, yax = 1, fac = soil_data$mainclass, col = palette)
s.class(dis$li, xax = 3, yax = 1, fac = soil_data$mainclass, col = palette)
s.class(dis$li, xax = 3, yax = 2, fac = soil_data$mainclass, col = palette)
s.corcircle(dis$va[major_vars$idx,])
par(mfrow = c(1, 1))

6 Linearization of Phosphorus Retention Potential with MASS LDA

The MASS LDA wiill use all info by default.

fit <- MASS::lda(mainclass ~ ., data = soil_data)
variables are collinear
plda <- predict(object = fit, # predictions
                 newdata = ISRIC_AT$ret_share[,-c(1,2)])

From categorical variable to continous “linear” scale. Linear combination of map unit soil content.

ret_scale<- NA
ISRIC_AT <- within(ISRIC_AT,{
  ret_scale <- ret_share[,c("ID",pct_vars)]
  ret_scale$ret <- ret_share$mainclass
  ret_scale[!is_soil(ret_scale$ret), pct_vars] <- NA
})
ISRIC_AT$ret_scale <- within(ISRIC_AT$ret_scale,{
    # Weighted scale, ad hoc weights!!!!!!    
    weighted <- as.double((Lo + 2 * Mo + 3 * Hi + 4 * VH) / 400)
    # Linear Discrimant scale, post hoc weights/coefficients
    LD1 <-  plda$x[,1]
    LD2 <-  -plda$x[,2] # sign manually adjusted
    LD3 <-  -plda$x[,3] # sign manually adjusted
    LD1[!is_soil(ret)] <- NA
    LD2[!is_soil(ret)] <- NA
    LD3[!is_soil(ret)] <- NA
})
with(ISRIC_AT,{
  plot_P_scales(ret_scale[soil_idx,], palette = palette,
              scale_x = "LD2",scale_y = "weighted")
})

ld_vars <- c("weighted", "LD1", "LD2", "LD3")
ISRIC_AT <- within(ISRIC_AT,{
  raster_scale <- mu_soilclass[,c("ID",pct_vars)] %>% 
    dplyr::left_join(ret_scale[,c("ID",ld_vars)],
                     by = c("ID" = "ID")) %>%
    dplyr::mutate_if(colnames(.) %in% ld_vars, scale256)
})

6.1 Linear Discriminant Maps

levels(ISRIC2011$FAO74) <- ISRIC_AT$raster_scale
ISRIC2011 <- c(ISRIC2011, layers_from(ISRIC2011$FAO74, cols = ld_vars))
for(varname in ld_vars) {
  out_tif <- file.path( extdata_dir, "ISRIC2011", paste0(varname,".tif"))
    
  writeRaster(ISRIC2011[[varname]],
              file = out_tif,
              datatype = 'INT1U',
              overwrite = TRUE)
    
  nb_plot(ISRIC2011[[varname]], main =  varname)
}

# finally save ISRIC2011 in .RData format
save(ISRIC_AT, file = file.path(data_dir, "ISRIC_AT.RData"))
save(ISRIC2011, file = file.path(data_dir, "ISRIC2011.RData"))

6.2 False Color Combination of Linear Discriminants

LD_composite <- stack(ISRIC2011[c("LD2","LD1","LD3")])
op <- par()
par(mar = c(0, 0, 0, 0), oma = c(0, 0, 0, 0))
plotRGB(LD_composite)
par(op)

out_tif <- file.path(extdata_dir,"ISRIC2011","LD_composite.tif")
writeRaster(LD_composite, filename = out_tif,
            datatype  = "INT1U",
            options   = "TFW=YES",
            format    = "GTiff",
            overwrite = TRUE)
LS0tCnRpdGxlOiAiUmVhZGluZyBJU1JJQyBQaG9zcGhvcnVzIFJldGVudGlvbiBEYXRhIgpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazoKICAgIGhpZ2hsaWdodDogdGFuZ28KICAgIG51bWJlcl9zZWN0aW9uczogdHJ1ZQogICAgdGhlbWU6IHNwYWNlbGFiCiAgICB0b2M6IHRydWUKICAgIHRvY19mbG9hdDogdHJ1ZQotLS0KCiMgTG9hZGluZyBMaWJyYXJpZXMKCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCByZXN1bHRzPSJoaWRlIn0KbGlicmFyeShyYXN0ZXIpCmxpYnJhcnkoc29pbFApCmxpYnJhcnkoZHBseXIpCmxpYnJhcnkodGlkeXIpCmxpYnJhcnkoSG1pc2MpCmxpYnJhcnkoTUFTUykKbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KGFkZTQpCmBgYAojIFJlYWRpbmcgSVNSSUMgMjAxMSBkYXRhCiMjIFN0YXJ0aW5nIHdpdGggdGhlIGJhc2UgcmFzdGVyIGZpbGUKRmlyc3Qgd2UgbG9hZCB0aGUgcmFzdGVyLiAKYGBge3J9CmRhdGFfZGlyICAgIDwtIHN5c3RlbS5maWxlKCJkYXRhIiwgICAgcGFja2FnZSA9ICJzb2lsUCIsIG11c3RXb3JrID0gVFJVRSkKZXh0ZGF0YV9kaXIgPC0gc3lzdGVtLmZpbGUoImV4dGRhdGEiLCBwYWNrYWdlID0gInNvaWxQIiwgbXVzdFdvcmsgPSBUUlVFKQoKRkFPNzRfdGlmIDwtIGZpbGUucGF0aChleHRkYXRhX2RpciwiSVNSSUMyMDExIiwgIkZBTzc0LnRpZiIpCgojIEluaXRpYWxpemluZyBJU1JJQzIwMTEgbGlzdCB3aXRoIHRoZSBiYXNlIG1hcApJU1JJQzIwMTEgPC0gbGlzdCgpCklTUklDMjAxMSRGQU83NCA8LSByYXN0ZXI6OnJhc3RlcihGQU83NF90aWYpCgpgYGAKClRoaXMgcmFzdGVyIGhhcyBhIHNpbmdsZSBsYXllciBhbmQgaXRzICB2YWx1ZSBjb3JyZXNwb25kcyB0byB0aGUgc29pbCBtYXAgdW5pdCAKaWRlbnRpZmllciAoSUQpIGZvciB0aGUgU29pbCBNYXAgb2YgdGhlIFdvcmxkLCBGQU83NC4gVGhlIHNhbWUgaWRlbnRpZmllciBjYW4KYmUgZm91bmQgaW4gbW9yZSByZWNlbnQgdmVyc2lvbnMgb2YgdGhlIEZBTyBzb2lsIG1hcCBhbmQgaW4gdGhlIGhhcm1vbml6ZWQgc29pbCAKZGF0YWJhc2UuCgojIyBQbG90dGluZyBGQU8gU29pbCBNYXAgVW5pdCBSYXN0ZXIKYGBge3IsIGZpZy5oZWlnaHQ9NCwgZmlnLndpZHRoPTEwLCB3YXJuaW5nPUZBTFNFfQpuYl9wbG90KElTUklDMjAxMSRGQU83NCwgbWFpbiA9ICJTb2lsIE1hcCBvZiB0aGUgV29ybGQsIEZBTzc0IikKYGBgCgpUaGUgTVVJRHMgaGF2ZSB0aGUgbG93ZXN0IHZhbHVlcyBpbiBBZnJpY2EgYW5kIHRoZW4gYXNjZW5kIHRocm91Z2ggQXNpYSwKdGhlIEFtZXJpY2FzLCBPY2VhbmlhIGFuZCBhdHRhaW4gdGhlIGhpZ2hlc3QgdmFsdWVzIGluIEV1cm9wZS4KQ29udHJhcnkgdG8gd2hhdCB0aGUgZmlsZSBuYW1lIHNlZW1zIHRvIGltcGx5IHRoZXNlIGlkcyBkbyBub3QgZGlyZWN0bHkKcmVwcmVzZW50ICB0aGUgcGhvc3Bob3J1cyByZXRlbnRpb24gcG90ZW50aWFsLiBCYXRqZXMgdXNlZCB0aGUgaWRzIGFuZCAKYSBzZXJpZXMgb2YgU1FMIHF1ZXJpZXMgdG8gYXNzaWduIHBob3NwaG9ydXMgcmV0ZW50aW9uIHBvdGVudGlhbCBjbGFzc2VzCnRvIGVhY2ggbWFwIHVuaXQsIHdoaWNoIHRoZW4gYXJlIGVhc2lseSBwcm9qZWN0ZWQgb3ZlciBlYWNoIHBpeGVsIGluIHRoZQptYXAuCgpUaGVzZSBtYWluIGNsYXNzZXMgY2FuIGJlIGV4dHJhY3RlZCBmcm9tIHRoZSBtYXAgdW5pdCByYXN0ZXIsIGFuZCB0aGUgdGFibGVzIGluCnRoZSBtZGIgZmlsZXMuIFRoZXNlIHRhYmxlcyBmdW5jdGlvbiBhcyByYXN0ZXIgYXR0cmlidXRlIHRhYmxlcy4KCiMjIEV4dHJhY3RpbmcgUmFzdGVyIEF0dHJpYnV0ZSBUYWJsZSBmcm9tIE1pY3Jvc29mdCBBY2Nlc3MgZGF0YWJhc2UgKGBtZGJgIGZpbGUpLgoKRm9yIHRoaXMgSSBuZWVkIEhtaXNjIGFuZCB0aGUgT0RCQyBkcml2ZXJzLiBNYWMgaW5zdGFsbGF0aW9uIGlzIGZyb20gYnJldy4KClRoZSBNUyBBY2Nlc3MgZGF0YWJhc2UgaGFzIHRoZSB0YWJsZXMgZm9yIG1hcHBpbmcgdGhlIHNvaWwgdW5pdCBpZGVudGlmaWVycyB0bwpzb2lsIHByb3BlcnRpZXMsIGkuZS4gcGhvc3Bob3J1cyByZXRlbnRpb24gYXMgc29pbCB1bml0IGNvbXBvc2l0aW9uIHBlcmNlbnRhZ2UsIApvciBwaHlzaWNvY2hlbWljYWwgdmFyaWFibGVzLgoKYGBge3J9CgptZGJfZmlsZSA8LSBmaWxlLnBhdGgoCiAgZXh0ZGF0YV9kaXIsCiAgIklTUklDMjAxMSIsCiAgIklTUklDX1Bob3NwaG9ydXNfUmV0ZW50aW9uX1BvdGVudGlhbC5tZGIiKQpgYGAKCiMjIEFkZGluZyBSYXN0ZXIgQXR0cmlidXRlIFRhYmxlLCBpbmNsdWRpbmcgTWFwIFVuaXQgUCBSZXRlbnRpb24gUG90ZW50aWFsIAoKYGxldmVsczwtYCBTMyBtZXRob2QgaXMgaW50ZXJuYWwgdG8gYHJhc3RlcmAgYW5kIGl0IGNvdWxkIG5vdCBiZQpleHBvcnRlZCAgdG8gYHNvaWxQYCwgd2hpY2ggcHJldmVudGVkIG1lIGZyb20gbWFraW5nIGEgd29ya2luZyBwYWNrYWdlIGZ1bmN0aW9uCm91dCBvZiB0aGUgZm9sbG93aW5nIHNuaXBwZXQ6CgoKYGBge3J9CgojIyMgR2V0IFJhc3RlciBBdHRyaWJ1dGUgVGFibGVzCklTUklDX0FUIDwtIGxpc3QoKQoKSVNSSUNfQVQgPC0gcmVhZF9JU1JJQ19BVChtZGJfZmlsZSkKCiMjIyBBZGRpbmcgSUQgUmFzdGVyIEF0dHJpYnV0ZSBUYWJsZSB0byBGQU83NApJU1JJQzIwMTEkRkFPNzQgPC0gcmFzdGVyOjpyYXN0ZXIoRkFPNzRfdGlmKQpJU1JJQzIwMTEkRkFPNzQgPC0gcmFzdGVyOjpyYXRpZnkoSVNSSUMyMDExJEZBTzc0KQoKbGV2ZWxzKElTUklDMjAxMSRGQU83NCkgPC0gSVNSSUNfQVQkbWFwdW5pdAoKYGBgCiMgU29pbCBNYXAgUGhvc3Bob3J1cyBSZXRlbnRpb24gUG90ZW50aWFsIGFzIFBlcmNlbnRhZ2VzIG9mIE1hcCBVbml0cwoKQSBwaG9zcGhvcnVzIHJldGVudGlvbiBwb3RlbnRpYWwgY2xhc3Mgd2FzIGFzc2lnbmVkIHRvIGVhY2ggb2YgdGhlIEZBTzc0CnNvaWwgdW5pdHMgKExvdywgTW9kZXJhdGUsIEhpZ2ggYW5kIHZlcnkgSGlnaCkuIApFYWNoIG1hcCB1bml0IGlzIGNvbXBvc2VkIG9mIHVwIHRvIDggc29pbCB1bml0cyBpbiBkaWZmZXJlbnQgYXJlYSBwZXJjZW50YWdlcy4KRGVwZW5kaW5nIG9uIHRoZSBjb21wb3NpdGlvbiBvZiB0aGUgbWFwIHVuaXQgYW5kIGVhY2ggY29tcG9uZW50IHNvaWwgdW5pdCAKcGhvc3Bob3J1cyByZXRlbnRpb24gcG90ZW50aWFsIGNsYXNzIGEgIG1hcCB1bml0IGNsYXNzIHdhcyBhc3NpZ25lZC4KSW4gdG90YWwgMjYwIGRpZmZlcmVudCBwaG9zcGhvcnVzIHJldGVudGlvbiBwb3RlbnRpYWwgY2xhc3NlcyB3ZXJlIGFzc2lnbmVkIHRvCnRoZSA0OTIzIG1hcCB1bml0cy4gVGhlc2Ugd2VyZSBmdXJ0aGVyIGdyb3VwZWQgaW50byAxNiBtYWluIHBob3NwaG9ydXMgcmV0ZW50aW9uCnBvdGVudGlhbCBjbGFzc2VzLgoKYGBge3IsIGZpZy5oZWlnaHQ9NCwgZmlnLndpZHRoPTEwLCB3YXJuaW5nPUZBTFNFfQoKcGN0X3ZhcnMgPC0gYygiTG8iLCAiTW8iLCAiSGkiLCAiVkgiLCAiTUlTQyIpIAoKSVNSSUMyMDExIDwtIGMoSVNSSUMyMDExLCBsYXllcnNfZnJvbShJU1JJQzIwMTEkRkFPNzQsY29scyA9IHBjdF92YXJzKSkKCmZvciAodmFybmFtZSBpbiBwY3RfdmFycykgewogIG91dF90aWYgPC0gZmlsZS5wYXRoKCBleHRkYXRhX2RpciwgIklTUklDMjAxMSIsIHBhc3RlMCh2YXJuYW1lLCIudGlmIikpCiAgICAKICB3cml0ZVJhc3RlcihJU1JJQzIwMTFbW3Zhcm5hbWVdXSwKICAgICAgICAgICAgICBmaWxlID0gb3V0X3RpZiwKICAgICAgICAgICAgICBkYXRhdHlwZSA9ICdJTlQxVScsCiAgICAgICAgICAgICAgb3ZlcndyaXRlID0gVFJVRSkKICAgIAogIG5iX3Bsb3QoSVNSSUMyMDExW1t2YXJuYW1lXV0sIG1haW4gPSAgdmFybmFtZSkKfQoKCmBgYAoKIyBSZWJ1aWxkaW5nIHRoZSBhcmNnaXMgcmVuZGVyIGFwcGVhcmVhbmNlIAoKV2Ugd2lsbCB1c2UgYSBtYW51YWxseSBjdXJhdGVkIHJhc3RlciBhdHRyaWJ1dGUgdGFibGUgc3RvcmVkIGluIHRoIGBzb2lsY2xhc3NgCmRhdGFmcmFtZS4KClNlZSB0aGUgbWFudWFsIGZvciBkZXRhaWxzLgpgYGB7cn0KP3NvaWxjbGFzcwpgYGAKCiMjIEFkZGluZyBSYXN0ZXIgQXR0cmlidXRlIFRhYmxlIHRvICBgSVNSSUMyMDExJEZBTzc0YAoKRmlyc3Qgd2UgbmVlZCB0byAgbWVyZ2UgYHNvaWxjbGFzc2AgdG8gdGhlIFNvaWwgTWFwIFVuaXQgaWQgbnVtYmVyLCB0aGVuIHdlIGFkZCB0aGlzIGFzIHRoZSBSQVQgdG8gdGhlIEZBTzc0IHJhc3Rlci4KCgpgYGB7cn0KCiMjIyBBZGRpbmcgSUQgUmFzdGVyIEF0dHJpYnV0ZSBUYWJsZSB0byBGQU83NAoKSVNSSUNfQVQkbXVfc29pbGNsYXNzIDwtIElTUklDX0FUJG1hcHVuaXQgJT4lIAogICAgZHBseXI6OmxlZnRfam9pbihzb2lsY2xhc3MsIGJ5ID0gYygibWFpbmNsYXNzIiA9ICJtYWluIikpCgpsZXZlbHMoSVNSSUMyMDExJEZBTzc0KSA8LSBJU1JJQ19BVCRtdV9zb2lsY2xhc3MKYGBgCgojIyAgUmVjbGFzc2lmeWluZyB0aGUgRkFPNzQgc29pbCBtYXAgaW50byBhc2NlbmRpbmcgUGhvc3Bob3J1cyBSZXRlbnRpb24gQ2xhc3NlcwoKSSBtYW51YWxseSBhc3NpZ25lZCBpbnRlZ2VycyBpbiBhc2NlbmRpbmcgb3JkZXIgdG8gdGhlCmBhc2NlbmRpbmdgIGNvbHVtbiBpbiB0aGUgYHNvaWxjbGFzc2AgZGF0YWZyYW1lIGFzIGZvbGxvd3M6IEZvciB0aGUgTG93IG1hcCB1bml0Cm1haW4gY2xhc3MsIGhpZ2hlciBwZXJjZW50YWdlIG9mIExvdyBzb2lsIHVuaXQgUCByZXRlbnRpb24gY29ycmVzcG9uZGVkIHRvIGxvd2VyCmludGVnZXJzLiBGb3IgdGhlIE1vZGVyYXRlIGNsYXNzLCBhIG11bHRpdmFyaWF0ZSBhbmFseXNpcyByZXZlbGVkIHRoYXQgTW8yIGhhcyBhCmhpZ2hlciBjb250ZW50IG9mIExvdyBzb3VpbCB1bml0cyxzbmQgIHNpbWlybGFseSBNbzMgaGFzIGEgZ3JlYXRlciBjb250ZW50IG9mIEhpCnNvaWwgdW5pdHMgdGFuIE1vMSwgd2hpY2ggc3VnZ2VzdHMgYSBNbzIsIE1vMSwgTW8zLCBhc2NlbmRpbmcgb3JkZXIuCkZvciB0aGUgSGlnaCwgYW5kIHZlcnkgSGlnaCBtYXAgdW5pdCBtYWluIGNsYXNzZXMsIGhpZ2hlciBpbnRlZ2VycyB3ZXJlIGFzc2lnbmVkCnRvIGhpZ2hlciBwZXJjZW50YWdlIG9mIGNvcnJlc3BvbmRpbmcgc29pbCB1bml0IFAgcmV0ZW50aW9uLiAKVGhpcyBtZWFucyB0aGUgaGlnaGVyIHRoZSBMb3cgc29pbCB1bml0IFAgcmV0ZW50aW9uIHBlcmNlbnRhZ2UgdGhlIGxvd2VyIAp0aGUgaW50ZWdlciwgYW5kIGNvbXBsZW50YXJpbHkgdGhlIGhpZ2hlciB0aGUgSGlnaCwgYW5kIFZlcnkgCkhpZ2ggc29pbCB1bml0IFAgcmV0ZW50aW9uIHBlcmNlbnRhZ2UgdGhlIGhpZ2hlciB0aGUgaW50ZWdlciwgZXhjZXB0IGluIHRoZQpNb2RlcmF0ZSBjbGFzcyB0aGF0IGJlaGF2ZXMgdW5leHBlY3RlZGx5LgoKCmBgYHtyfQoKSVNSSUMyMDExJG1haW4gPC0gbGF5ZXJzX2Zyb20oSVNSSUMyMDExJEZBTzc0LCBjb2xzID0gImFzY2VuZGluZyIpW1sxXV0KCnJhc3Rlcjo6d3JpdGVSYXN0ZXIoCiAgSVNSSUMyMDExJG1haW4sCiAgZmlsZS5wYXRoKGV4dGRhdGFfZGlyLCJJU1JJQzIwMTEiLCJtYWluLnRpZiIpLAogIGRhdGF0eXBlID0gJ0lOVDFVJywKICBvdmVyd3JpdGUgPSBUUlVFKQoKYGBgCgojIyBQbG90dGluZyBTb2lsIFBob3NwaG9ydXMgUmV0ZW50aW9uIFBvdGVudGlhbCBSYXN0ZXIKCmBgYHtyLCBmaWcuaGVpZ2h0PTQsIGZpZy53aWR0aD0xMCwgd2FybmluZz1GQUxTRX0KCm5iX3Bsb3QoSVNSSUMyMDExJG1haW4sCiAgICAgICAgYXhpcy5hcmdzID0gbGlzdChicmVha3M9MDoxNSwgYXQgPSAwOjE1LCBjZXguYXhpcyA9IDIpLAogICAgICAgIG1haW4gPSAgIlBob3NwaG9ydXMgUmV0ZW50aW9uIFBvdGVudGlhbCwgSVNSSUMyMDExIikKCmBgYAoKTm93IHRoZSBudW1iZXJzIGRvIHJlcHJlc2VudCB0aGUgcGhvc3Bob3J1cyByZXRlbnRpb24gcG90ZW50aWFsISEhCgpIb3dldmVyLCB0aGUgYHJhc3RlcmAgcGxvdCBsZWdlbmQgYWJvdmUgYXNzdW1lcyBhIGNvbnRpbnVvdXMgc2NhbGUgZnJvbSAwIHRvIDE1LAp3aGlsZSB0aGUgZGF0YSBpcyBleHBsaWNpdGx5IGEgY2F0ZWdvcmljYWwgdmFyaWFibGUgKGFsdGhvdWdoIGRlcml2ZWQgZnJvbQpjb250aW51b3VzIHBlcmNlbnRhZ2VzLCBzZWUgYWJvdmUpLiAKCgpgYGB7cn0KIyBSYXN0ZXIgQXR0cmlidXRlIFRhYmxlCklTUklDMjAxMSRtYWluIDwtIHJhc3Rlcjo6cmF0aWZ5KElTUklDMjAxMSRtYWluKQojIEFzc2lnbiBSQVQgdG8gbWFpbiBjbGFzcyByYXN0ZXIKbGV2ZWxzKElTUklDMjAxMSRtYWluKSA8LSByYXQoSVNSSUMyMDExJG1haW4pICU+JSAKICAgIGRwbHlyOjppbm5lcl9qb2luKHNvaWxjbGFzcywgYnkgPSBjKCJJRCIgPSAiYXNjZW5kaW5nIikpCmBgYAoKIyMgQWRkaW5nIGFyY2dpcyByZW5kZXIgY29sb3IgYW5kIHNhdmluZyB0aGUgbWFwIGFzIGZ1bGwgcmVzb2x1dGlvbiBnZW90aWYKCmBgYHtyLCBmaWcuaGVpZ2h0PTQsIGZpZy53aWR0aD0xMCwgd2FybmluZz1GQUxTRX0KCm1haW5fY29sb3IgPC0gbGF5ZXJzX2Zyb20oSVNSSUMyMDExJEZBTzc0LCBjb2xzID0gYygiciIsImciLCJiIikpCgpwbG90UkdCKHN0YWNrKG1haW5fY29sb3IpKQoKd3JpdGVSYXN0ZXIoc3RhY2sobWFpbl9jb2xvciksCiAgZmlsZW5hbWUgID0gZmlsZS5wYXRoKGV4dGRhdGFfZGlyLCAiSVNSSUMyMDExIiwibWFpbl9jb2xvci50aWYiKSwKICBkYXRhdHlwZSAgPSAiSU5UMVUiLAogIG9wdGlvbnMgICA9ICJURlc9WUVTIiwKICBmb3JtYXQgICAgPSAiR1RpZmYiLAogIG92ZXJ3cml0ZSA9IFRSVUUpCmBgYAoKIyMgVXNpbmcgcmFzdGVyVmlzIHRvIGFwcHJvcGlhdGVseSBsYWJlbCBjYXRlZ29yaWVzIGFuZCBhZGQgbGVnZW5kIApSZW1lbWJlciB0aGF0IHdlIGFzc2lnbmVkIHRvIHRoZSB0byB0aGUgcGhvc3Bob3J1cyByZXRlbnRpb24gcG90ZW50aWFsIG1haW4KY2xhc3NlcyBhbiAqYWQgaG9jKiBvcmRlciBzbyB3ZSBjYW4gaW50ZXJwcmV0IGEgZGlyZWN0aW9uIG9mIGFzY2VuZGluZyByZXRlbnRpb24gCnBvdGVudGlhbCAoc2VlIGFib3ZlKSBpbiB0aGUgcGxvdCBsZWdlbmQuCgpJbiBvcmRlciB0byBzaG93IHRoZSByaWdodCBsYWJlbHMgaW4gdGhpcyBhc2NlbmRpbmcgc2NhbGUgd2UgdXNlIHRoZSBgbGV2ZWxwbG90YApmdW5jdGlvbiBmcm9tIGBSYXN0ZXJWaXNgIHRoYXQgdXNlcyB0aGUgcmFzdGVyIGF0dHJpYnV0ZSB0YWJsZSB0byB0cmFuc2Zvcm0gdGhlIApudW1lcmljYWwgdmFsdWUgb2YgdGhlIHJhc3RlciB0byBkaXNjcmV0ZSBjYXRlZ29yaWVzLgoKCmBgYHtyfQpyYXN0ZXJWaXM6OmxldmVscGxvdCgKICBJU1JJQzIwMTEkbWFpbiwgYXR0ID0gIm1haW4iLAogIGNvbC5yZWdpb25zID0gc29pbGNsYXNzJGNvbG9yX2hleFsxOjE2XSwKICBtYXhwaXhlbHMgPSBuY2VsbChJU1JJQzIwMTEkbWFpbiksCiAgc2NhbGVzID0gbGlzdChkcmF3ID0gRkFMU0UpLAogIHhsYWIgPSBOVUxMLCB5bGFiID0gTlVMTCwKICBtYWluID0gIkdlbmVyYWxpemVkIFBob3NwaG9ydXMgUmV0ZW50aW9uIFBvdGVudGlhbCBNYXAiKQoKYGBgCgoKCiMgTXVsdGl2YXJpYXRlIGFuYWx5c2lzIG9mIFNvaWwgcmV0ZW50aW9uIFBvdGVudGlhbAoKV2Ugc2hvdWxkIGVzdGFibGlzaCBhIGRhdGEgYmFzZWQgb3JkZXIgb2YgbWFwIHVuaXQgc29pbCBwaG9zcGhvcnVzIApyZXRlbnRpb24gY2xhc3NlcyBpbnN0ZWFkIG9mIHBvc3R1bGF0aW5nIGFuIGFkIGhvYyBvcmRlci4gVGhpcyBjYW4gYmUgZG9uZQp0aHJvdWdoIHNlbGVjdGluZyB0aGUgZmlyc3QgZGlzY3JpbWluYW50IGRpbWVuc2lvbiBmcm9tIGEgRGlzY3JpbWluYW50CkFuYWx5c2lzIG9mIHRoZSBwZXJjZW50YWdlIG9mIHNvaWwgdW5pdCBwaG9zcGhvcnVzIHJldGVudGlvbiBjbGFzc2VzIHBlcgptYXBwaW5nIHVuaXQuIEZ1cnRoZXJtb3JlLCB3ZSBjYW4gdXNlIHRoYXQgZGlzY3JpbWluYW50IGRpbWVudGlvbiBhcyBhCmNvbnRpbnVvdXMgdmFyaWFibGUgaW5zdGVhZCBvZiB0aGUgZGlzY3JldGUgY2xhc3NlcyBmb3IgZG93bnN0cmVhbQphbmFseXNlcwogICAgICAKIyMgTWFwIFVuaXQgU29pbCBDb21wb3NpdGlvbiBNYXRyaXggYW5kIGVudHJvcHkKCkVhY2ggc29pbCB1bml0IHBlcmNlbnRhZ2UgY2FuIGJlIHVzZWQgYXMgYSBtYXAgdW5pdCBkZXNjcmlwdG9yIHZhcmlhYmxlIGZvciAKbXVsdGl2YXJpYXRlIGFuYWx5c2lzIG9mIHRoZSBwaG9zcGhvcnVzIHJldGVudGlvbiBwb3RlbnRpYWwgcGVyIG1hcCB1bml0LgoKYGBge3J9CklTUklDX0FUIDwtIHdpdGhpbihJU1JJQ19BVCwgewogIHN1X3NoYXJlIDwtIHNvaWxfY29tcG9zaXRpb24obWFwdW5pdCxGQU83NCkKICBzdV9zaGFyZSA8LSBhcy5kYXRhLmZyYW1lKAogICAgY2JpbmQoIElEID0gbWFwdW5pdCRJRCwKICAgICAgICAgICBzdV9zaGFyZSwKICAgICAgICAgICBzb2lsUyA9IHJvd19lbnRyb3B5KHN1X3NoYXJlLzEwMCkpKQp9KQoKYGBgCkhvd2V2ZXIgdGhpcyByZXN1bHRzIGluIHNwYXJzZSBtYXRyaXggd2l0aCBlc3NlbnRpYWxseSBvcnRob2dvbmFsIGNvbHVtbnMuCgojIyBQaG9zcGhvcnVzIFJldGVudGlvbiwgU29pbCBVbml0IENvbXBvc2l0aW9uICBKb2luIHRhYmxlCgpgYGB7cn0KcGh5Y2hlbV92YXJzIDwtIGMoInBIIiwiU0FORCIsIlNJTFQiLCJDTEFZIiwiQ0VDTEFZIikKc3VfdHlwZXMgPC0gY29sbmFtZXMoSVNSSUNfQVQkc3Vfc2hhcmUpCnJldF92YXJzIDwtIGMoIklEIiwKICAgICAgICAgICAgICAibWFpbmNsYXNzIiwKICAgICAgICAgICAgICBwaHljaGVtX3ZhcnMsCiAgICAgICAgICAgICAgcGN0X3ZhcnMsCiAgICAgICAgICAgICAgc3VfdHlwZXMpCgpsZXZlbHMgPC0gc29pbGNsYXNzICU+JQogICAgICAgICAgICBkcGx5cjo6YXJyYW5nZShhc2NlbmRpbmcpICU+JQogICAgICAgICAgICBkcGx5cjo6c2VsZWN0KG1haW4pCgpJU1JJQ19BVCA8LSB3aXRoaW4oSVNSSUNfQVQsIHsKICByZXRfc2hhcmUgPC0gbWFwdW5pdCAlPiUKICAgIGRwbHlyOjpsZWZ0X2pvaW4oc29pbHVuaXQsIGJ5ID0gYygiU09JTDEiID0gImtleSIpKSAlPiUKICAgIGRwbHlyOjpsZWZ0X2pvaW4oc3Vfc2hhcmUsIGJ5ID0gIklEIikgJT4lCiAgICBkcGx5cjo6c2VsZWN0KCEhIXJldF92YXJzKSAKICByZXRfc2hhcmUkbWFpbmNsYXNzIDwtIGZhY3RvcihyZXRfc2hhcmUkbWFpbmNsYXNzLCBsZXZlbHMgPSBsZXZlbHMkbWFpblsxOjE2XSkKfSkKCgpgYGAKCiMjIFNvaWwgYW5kIFBob3NwaG9ydXMgUmV0ZW50aW9uIENsYXNzIENvbXBvc2l0aW9uIEVudHJvcHkKYGBge3J9CklTUklDX0FUIDwtIHdpdGhpbihJU1JJQ19BVCwgewogIHJldF9zaGFyZSRwUyA8LSByb3dfZW50cm9weShyZXRfc2hhcmVbLHBjdF92YXJzXSAvIDEwMCkKfSkKCndpdGgoSVNSSUNfQVQsewogIGJveHBsb3Qoc29pbFMgfiBtYWluY2xhc3MsIGRhdGEgPSByZXRfc2hhcmUsIGxhcyA9IDIsIAogICAgICAgICAgbWFpbiA9ICJNYXAgVW5pdCBTb2lsIENvbXBvc2l0aW9uIEVudHJvcHkiKQogIGJveHBsb3QocFMgfiBtYWluY2xhc3MsIGRhdGEgPSByZXRfc2hhcmUsIGxhcyA9IDIsCiAgICAgICAgICBtYWluID0gIlBob3NwaG9ydXMgUmV0ZW50aW9uIENsYXNzIENvbXBvc2l0aW9uIEVudHJvcHkiKQp9KQpgYGAKCiMjIEV4cGxvcmF0b3J5IERpc2NyaW1pbmFudCBBbmFseXNpcyB1c2luZyBgYWRlNGAKClNvaWwgUmV0ZW50aW9uIFBvdGVudGlhbCBTcGFjZSBpcyBlc3NlbnRpYWxseSBhIHRldHJhaGVkcm9uIHdpdGggTW9kZXJhdGUKcmV0ZW50aW9uIGF0IGl0cyBjZW50ZXIuT25seSB0aHJlZSBkZWdyZWVzIG9mIGZyZWVkb20sIExvLVZILCBNby1IaSwgTG8tSGkuCgoKYGBge3J9CiMgY3JlYXRlIGR1ZGkgb2JqZWN0IGZvciBkaXNjcmltaW5hbnQgYW5hbHlzaXMKCnNvaWxfaWR4IDwtIHdpdGgoSVNSSUNfQVQkcmV0X3NoYXJlLHsKICAgIHdoaWNoKGlzX3NvaWwobWFpbmNsYXNzKSkKfSkKCnNvaWxfZGF0YSA8LSBJU1JJQ19BVCRyZXRfc2hhcmVbc29pbF9pZHgsLTFdCgpzb2lsX2RhdGEkbWFpbmNsYXNzICAgPC0gd2l0aChJU1JJQ19BVCRyZXRfc2hhcmUsewogICAgZmFjdG9yKG1haW5jbGFzc1tzb2lsX2lkeF0sIGxldmVscyA9IGxldmVscyRtYWluWzU6MTZdKQp9KQoKCgpwY2EgPC0gZHVkaS5wY2Eoc29pbF9kYXRhWywtMV0sIHNjYW5uZiA9IEZBTFNFLCBuZiA9IDEzOSkKICAKZGlzIDwtIGRpc2NyaW1pbihwY2EsIGZhYyA9IHNvaWxfZGF0YSRtYWluY2xhc3MsIHNjYW5uZiA9IEZBTFNFLCBuZiA9IDEwKQpwYWxldHRlIDwtIHNvaWxjbGFzcyRjb2xvcl9oZXhbNToxNl0KY29sb3IgPC0gcGFsZXR0ZVtzb2lsX2RhdGEkbWFpbmNsYXNzXQoKIyBTZWxlY3QgbW9zdCBpbXBvcnRhbnQgdmFyaWFibGVzCmRpc3ZhIDwtIGFzLmRhdGEuZnJhbWUoZGlzJHZhKQpkaXN2YSRpZHggPC0gKDE6bnJvdyhkaXMkdmEpKSAvIDEwMDAKCm1ham9yX3ZhcnMgPC0gMTAwMCAqIGRpc3ZhICU+JSAKICBkcGx5cjo6ZmlsdGVyX2FsbChhbnlfdmFycyhhYnMoLikgPiAwLjUpKSAlPiUKICBkcGx5cjo6c2VsZWN0KGlkeCkKCiMgUGxvdApwYXIobWZyb3cgPSBjKDIsIDIpKQpzLmNsYXNzKGRpcyRsaSwgeGF4ID0gMiwgeWF4ID0gMSwgZmFjID0gc29pbF9kYXRhJG1haW5jbGFzcywgY29sID0gcGFsZXR0ZSkKcy5jbGFzcyhkaXMkbGksIHhheCA9IDMsIHlheCA9IDEsIGZhYyA9IHNvaWxfZGF0YSRtYWluY2xhc3MsIGNvbCA9IHBhbGV0dGUpCnMuY2xhc3MoZGlzJGxpLCB4YXggPSAzLCB5YXggPSAyLCBmYWMgPSBzb2lsX2RhdGEkbWFpbmNsYXNzLCBjb2wgPSBwYWxldHRlKQpzLmNvcmNpcmNsZShkaXMkdmFbbWFqb3JfdmFycyRpZHgsXSkKcGFyKG1mcm93ID0gYygxLCAxKSkKYGBgCgoKIyBMaW5lYXJpemF0aW9uIG9mIFBob3NwaG9ydXMgUmV0ZW50aW9uIFBvdGVudGlhbCB3aXRoIGBNQVNTYCBMREEKClRoZSBgTUFTU2AgTERBIHdpaWxsIHVzZSBhbGwgaW5mbyBieSBkZWZhdWx0LgoKYGBge3J9CgpmaXQgPC0gTUFTUzo6bGRhKG1haW5jbGFzcyB+IC4sIGRhdGEgPSBzb2lsX2RhdGEpCgpwbGRhIDwtIHByZWRpY3Qob2JqZWN0ID0gZml0LCAjIHByZWRpY3Rpb25zCiAgICAgICAgICAgICAgICAgbmV3ZGF0YSA9IElTUklDX0FUJHJldF9zaGFyZVssLWMoMSwyKV0pCgpgYGAKCkZyb20gY2F0ZWdvcmljYWwgdmFyaWFibGUgdG8gY29udGlub3VzICJsaW5lYXIiIHNjYWxlLgpMaW5lYXIgY29tYmluYXRpb24gb2YgbWFwIHVuaXQgc29pbCBjb250ZW50LgoKYGBge3J9CnJldF9zY2FsZTwtIE5BCklTUklDX0FUIDwtIHdpdGhpbihJU1JJQ19BVCx7CiAgcmV0X3NjYWxlIDwtIHJldF9zaGFyZVssYygiSUQiLHBjdF92YXJzKV0KICByZXRfc2NhbGUkcmV0IDwtIHJldF9zaGFyZSRtYWluY2xhc3MKICByZXRfc2NhbGVbIWlzX3NvaWwocmV0X3NjYWxlJHJldCksIHBjdF92YXJzXSA8LSBOQQp9KQoKCklTUklDX0FUJHJldF9zY2FsZSA8LSB3aXRoaW4oSVNSSUNfQVQkcmV0X3NjYWxlLHsKICAgICMgV2VpZ2h0ZWQgc2NhbGUsIGFkIGhvYyB3ZWlnaHRzISEhISEhICAgIAogICAgd2VpZ2h0ZWQgPC0gYXMuZG91YmxlKChMbyArIDIgKiBNbyArIDMgKiBIaSArIDQgKiBWSCkgLyA0MDApCiAgICAjIExpbmVhciBEaXNjcmltYW50IHNjYWxlLCBwb3N0IGhvYyB3ZWlnaHRzL2NvZWZmaWNpZW50cwogICAgTEQxIDwtICBwbGRhJHhbLDFdCiAgICBMRDIgPC0gIC1wbGRhJHhbLDJdICMgc2lnbiBtYW51YWxseSBhZGp1c3RlZAogICAgTEQzIDwtICAtcGxkYSR4WywzXSAjIHNpZ24gbWFudWFsbHkgYWRqdXN0ZWQKICAgIExEMVshaXNfc29pbChyZXQpXSA8LSBOQQogICAgTEQyWyFpc19zb2lsKHJldCldIDwtIE5BCiAgICBMRDNbIWlzX3NvaWwocmV0KV0gPC0gTkEKfSkKCmBgYAoKYGBge3IsIGZpZy5hc3AgPSAxfQp3aXRoKElTUklDX0FULHsKICBwbG90X1Bfc2NhbGVzKHJldF9zY2FsZVtzb2lsX2lkeCxdLCBwYWxldHRlID0gcGFsZXR0ZSwKICAgICAgICAgICAgICBzY2FsZV94ID0gIkxEMiIsc2NhbGVfeSA9ICJ3ZWlnaHRlZCIpCn0pCgpgYGAKCmBgYHtyfQpsZF92YXJzIDwtIGMoIndlaWdodGVkIiwgIkxEMSIsICJMRDIiLCAiTEQzIikKCklTUklDX0FUIDwtIHdpdGhpbihJU1JJQ19BVCx7CiAgcmFzdGVyX3NjYWxlIDwtIG11X3NvaWxjbGFzc1ssYygiSUQiLHBjdF92YXJzKV0gJT4lIAogICAgZHBseXI6OmxlZnRfam9pbihyZXRfc2NhbGVbLGMoIklEIixsZF92YXJzKV0sCiAgICAgICAgICAgICAgICAgICAgIGJ5ID0gYygiSUQiID0gIklEIikpICU+JQogICAgZHBseXI6Om11dGF0ZV9pZihjb2xuYW1lcyguKSAlaW4lIGxkX3ZhcnMsIHNjYWxlMjU2KQp9KQpgYGAKCiMjIExpbmVhciBEaXNjcmltaW5hbnQgTWFwcwpgYGB7ciwgZmlnLmhlaWdodD00LCBmaWcud2lkdGg9MTAsIHdhcm5pbmc9RkFMU0V9CgpsZXZlbHMoSVNSSUMyMDExJEZBTzc0KSA8LSBJU1JJQ19BVCRyYXN0ZXJfc2NhbGUKCklTUklDMjAxMSA8LSBjKElTUklDMjAxMSwgbGF5ZXJzX2Zyb20oSVNSSUMyMDExJEZBTzc0LCBjb2xzID0gbGRfdmFycykpCgpmb3IodmFybmFtZSBpbiBsZF92YXJzKSB7CgogIG91dF90aWYgPC0gZmlsZS5wYXRoKCBleHRkYXRhX2RpciwgIklTUklDMjAxMSIsIHBhc3RlMCh2YXJuYW1lLCIudGlmIikpCiAgICAKICB3cml0ZVJhc3RlcihJU1JJQzIwMTFbW3Zhcm5hbWVdXSwKICAgICAgICAgICAgICBmaWxlID0gb3V0X3RpZiwKICAgICAgICAgICAgICBkYXRhdHlwZSA9ICdJTlQxVScsCiAgICAgICAgICAgICAgb3ZlcndyaXRlID0gVFJVRSkKICAgIAogIG5iX3Bsb3QoSVNSSUMyMDExW1t2YXJuYW1lXV0sIG1haW4gPSAgdmFybmFtZSkKfQoKIyBmaW5hbGx5IHNhdmUgSVNSSUMyMDExIGluIC5SRGF0YSBmb3JtYXQKc2F2ZShJU1JJQ19BVCwgZmlsZSA9IGZpbGUucGF0aChkYXRhX2RpciwgIklTUklDX0FULlJEYXRhIikpCnNhdmUoSVNSSUMyMDExLCBmaWxlID0gZmlsZS5wYXRoKGRhdGFfZGlyLCAiSVNSSUMyMDExLlJEYXRhIikpCmBgYAoKIyMgRmFsc2UgQ29sb3IgQ29tYmluYXRpb24gb2YgTGluZWFyIERpc2NyaW1pbmFudHMKYGBge3IsIGZpZy5oZWlnaHQ9NCwgZmlnLndpZHRoPTEwLCB3YXJuaW5nPUZBTFNFfQoKTERfY29tcG9zaXRlIDwtIHN0YWNrKElTUklDMjAxMVtjKCJMRDIiLCJMRDEiLCJMRDMiKV0pCgpvcCA8LSBwYXIoKQpwYXIobWFyID0gYygwLCAwLCAwLCAwKSwgb21hID0gYygwLCAwLCAwLCAwKSkKCnBsb3RSR0IoTERfY29tcG9zaXRlKQoKcGFyKG9wKQoKb3V0X3RpZiA8LSBmaWxlLnBhdGgoZXh0ZGF0YV9kaXIsIklTUklDMjAxMSIsIkxEX2NvbXBvc2l0ZS50aWYiKQp3cml0ZVJhc3RlcihMRF9jb21wb3NpdGUsIGZpbGVuYW1lID0gb3V0X3RpZiwKICAgICAgICAgICAgZGF0YXR5cGUgID0gIklOVDFVIiwKICAgICAgICAgICAgb3B0aW9ucyAgID0gIlRGVz1ZRVMiLAogICAgICAgICAgICBmb3JtYXQgICAgPSAiR1RpZmYiLAogICAgICAgICAgICBvdmVyd3JpdGUgPSBUUlVFKQpgYGAK