Report timestamp: 2020-04-15 09:55:56


Reference Phabricator Ticket: T248308


Notebook: Optimizing WDQS Response Time From SPARQL Queries Analysis

Feedback should be send to goran.milovanovic_ext@wikimedia.de.

Summary

WMDE together with php.cc is trying to find a way to optimize the Wikidata Query Service. This Report describes a Machine Learning (ML) approach to analyze the SPARQL queries that were received by the endpoint in March 2020, with the goal of developing a model able to predict whether a query would be critical for WDQS/Blazegraph or not. By singling out the most important features that are present in the SPARQL queries under analysis we hope to be able to understand better what types of queries typically cause problems. Beyond that, the development of a predictive model for SPARQL query server response time in itself lays out the foundations for the development of an optimization engine for WDQS, a system that would be able to quickly scan a received query and decide whether the WDQS handling of any given query needs to be adjusted in order to avoid critically high server response times.

In the following sections we describe (1) the sampling approach taken to collect a relevant set of SPARQL queries observed at the (WDQS) endpoint in three weeks of March 2020, (2) the feature engineering approach taken to analyze the composition of queries and extract meaningful “atomic elements” of SPARQL that will be used in an attempt to predict the WDQS response time for a query, (3) the definition of a criterion for selecting a critical query for WDQS (i.e. the one that makes too much use of the service’s resources), (4) the ML (optimization) approach, relying on XGBoost, taken to build a predictive model for WDQS server response times starting from the extracted SPARQL features, and (5) the lessons learned - specifically on what constraints will be met in the case of an eventual development of a WDQS optimization engine in the future - as well as suggestions on what further steps need to be made in order to reach an acceptable level of understanding of the problem and design a prototype of a future solution.


0. Sample of SPARQL queries observed at the WDQS SPARQL endpoint

The SPARQL queries that hit the endpoint are registered in the wmf.webrequest table in the WMF Data Lake (our Hadoop Big Data storage). A bucketing approach to sample data from this Hive table was taken, sampling approximately 1% of queries that were observed in the period between 2020/03/01 and 2020/03/20. The sample is approximately uniformly distributed across days and hours of the mentioned period of time. The sample size is slightly higher than 1M SPARQL queries - including duplicates.

The following fields were collected from the wmf.webrequest table for each collected SPARQL query (see: wmf.webrequest documenation for details):

  • uri_query - the URL encoded SPARQL query w. WDQS query parameters
  • dt - the query timestamp, later parsed into year, month, day, hour, and minute
  • http_status - the server http status response for this query
  • cache_status - the cache status for this query (see: https://wikitech.wikimedia.org/wiki/Caching_overview#Headers)
  • time_firstbyte - how many seconds elapsed before the first byte of the server response was sent out to the client
  • response_size - response size
  • agent_type - spider or human
  • content_type - content type (e.g. JSON, text/tsv, xml, etc.)
  • access_method - desktop, mobile, or app.

NOTE. As noted in the Summary, our attempt will be to analyze the composition of the SPARQL queries and try to predict a binary variable critical response time/non-critical response time which we plan to derive from time_firstbyte (taken to be an approximation to the WDQS response time). It is immediately obvious that only some fields from the wmf.webrequest table are candidates for a predictive model of the described kind: uri_query contains the query and we will use it to extract the query features, which can be done before WDQS query processing, dt is the timestamp which can be known before the query is processed, however, response_size is certainly not available before query processing and cannot be used in any imaginable WDQS optimization system. So, the following fields are useful for exploratory data analysis only: http_status, cache_status, response_size, agent_type, content_type (note: we can learn about this variable from the SPARQL query parameters present in the uri_query field), access_method.

#!/usr/bin/env Rscript

### ---------------------------------------------------------------------------
### --- WD_WDQS_Analytics_CollectData.R, v. Beta 0.1
### --- Script: WD_WDQS_Analytics_CollectData.R, v. Beta 0.1
### --- Description: WD_WDQS_Analytics_CollectData.R collects a sample of 
### --- SPARQL queries from wmf.webrequest in the WMF Data Lake,
### --- uri_path = "/sparql" OR uri_path= "/bigdata/namespace/wdq/sparql"
### --- NOTE. "/bigdata/namespace/wdq/sparql" redirects to "/sparql" 
### --- Author: Goran S. Milovanovic, Data Scientist, WMDE
### --- Developed under the contract between Goran Milovanovic PR Data Kolektiv
### --- and WMDE.
### --- Contact: goran.milovanovic_ext@wikimedia.de
### ---------------------------------------------------------------------------
### --- RUN FROM: /home/goransm/Analytics/Wikidata/WD_misc/WD_SPARQL_Endpoint_Analytics
### --- on stat1005
### --- Date: 2020/03/20
### ---------------------------------------------------------------------------

### ---------------------------------------------------------------------------
### --- LICENSE:
### ---------------------------------------------------------------------------
### --- GPL v2
### --- This file is part of the Wikidata SPARQL Endpoint Analytics Project
### ---
### --- Wikidata SPARQL Endpoint Analytics Project is free software: 
### --- you can redistribute it and/or modify
### --- it under the terms of the GNU General Public License as published by
### --- the Free Software Foundation, either version 2 of the License, or
### --- (at your option) any later version.
### ---
### --- Wikidata SPARQL Endpoint Analytics Project is distributed 
### --- in the hope that it will be useful,
### --- but WITHOUT ANY WARRANTY; without even the implied warranty of
### --- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
### --- GNU General Public License for more details.
### ---
### --- You should have received a copy of the GNU General Public License
### --- along with Wikidata SPARQL Endpoint Analytics Project.
### --- If not, see <http://www.gnu.org/licenses/>.
### ---------------------------------------------------------------------------

### ---------------------------------------------------------------------------
### --- Section 1. Collect raw data set from wmf.webrequest
### ---------------------------------------------------------------------------

### --- setup
library(data.table)
library(tidyverse)

### --- directories
### --- Read WDCM paramereters
# - fPath: where the scripts is run from?
fPath <- as.character(commandArgs(trailingOnly = FALSE)[4])
fPath <- gsub("--file=", "", fPath, fixed = T)
fPath <- unlist(strsplit(fPath, split = "/", fixed = T))
fPath <- paste(
  paste(fPath[1:length(fPath) - 1], collapse = "/"),
  "/",
  sep = "")
fPath <- '/home/goransm/Analytics/Wikidata/WD_misc/WD_SPARQL_Endpoint_Analytics/'
dataDir <- paste0(fPath, '_data/')
analyticsDir <- paste0(fPath, '_analytics/')
imgDir <- paste0(fPath, '_img/')

### --- grab a sample of SPARQL queries from wmf.webrequest
### --- w. HiveQL from Beeline
# - query filename
queryFile <- "wdqs_CollectData.hql"
# - Kerberos Init
kerberosPrefix <- 'sudo -u analytics-privatedata kerberos-run-command analytics-privatedata '
# - dateRange
endDate <- '2020/03/20'
startDate <- '2020/03/01'
dateRange <- seq(as.Date(startDate), as.Date(endDate), by = "day")
# - query over dateRange
for (i in 1:length(dateRange)) {
  print(paste0("Started: ", dateRange[i]))
  # - year, month, date
  year = substr(dateRange[i], 1, 4)
  month = substr(dateRange[i], 6, 7)
  if (substr(month, 1, 1) == '0') {month = substr(month, 2, 2)}
  day = substr(dateRange[i], 9, 10)
  if (substr(day, 1, 1) == '0') {day = substr(day, 2, 2)}
  # - compose query
  hiveQLquery <- paste0('SELECT dt, uri_query, http_status, cache_status, time_firstbyte, response_size, agent_type, content_type, access_method
                          FROM wmf.webrequest TABLESAMPLE (BUCKET 1 OUT OF 100 ON rand()) w
                          WHERE (uri_path = "/sparql" OR uri_path= "/bigdata/namespace/wdq/sparql")
                          AND webrequest_source = "text"
                          AND year=', year,
                        ' AND month=', month,
                        ' AND day=', day, ';')
  
  filename <- "wdqs_data_sample_TEST_"
  filename <- paste0(filename, dateRange[i], ".tsv")
  # - write hql
  write(hiveQLquery, paste0(fPath, queryFile))
  # - to Report
  print("Fetching sparql_testData from wmf.webrequest now.")
  # - Run query
  query <- system(command = paste(kerberosPrefix, 
                                  '/usr/local/bin/beeline --incremental=true --silent -f "',
                                  paste0(fPath, queryFile),
                                  '" > ', dataDir, filename,
                                  sep = ""),
                  wait = TRUE)
  print(paste0("Completed: ", dateRange[i]))
  print("--------------------------------------------")
}
# - to Report
print("DONE w. ETL from Hadoop: wmf.webrequest.")

1. Data Cleaning and Feature Engineering

1.1 Data Cleaning

The first step following the sample of SPARQL queries is obtained from wmf.webrequest is to check the data for consistency and urltools::url_decode() decode the queries (R {urltools} package was used). The following chunk of code also parses the dt (timestamp) field and extracts year, month, day, hour, and minute when the query was run. Also, and additional variable is produced from a simple concatenation of year, month, day, hour, and minute: ymdmh. This variable will be subsequently used to calculate the number of queries that were run in a particular minute observed in the sample. This is a potential “environmental” variable (i.e. taken from the WDQS/Blazegraph enviornment) in predictive modeling of server response time. It can characterize the query processing context by estimating how many queries were run against Blazegraph concurrently in the same minute when a particular query under of interest was run. There are possible refinments of this variable that were not explored in this pilot study. Any future WDQS optimization system relying on this variable would have to rely on Blazegraph broadcasting this variable to the predictive modeling API.

NOTE. In this step we eliminate all queries that have resulted in a http_status of 4**: client side errors. Obviously, incorrectly stated SPARQL queries are of no use to us in any attempt to optimize the WDQS processing time. So if a query fails an initial syntax analysis on the endpoint it does not enter our analysis.

#!/usr/bin/env Rscript

### ---------------------------------------------------------------------------
### --- WD_WDQS_Analytics_Clean_Inspect_Data_EDA.R, v. Beta 0.1
### --- Script: WD_WDQS_Analytics_FeatureEngineering.R, v. Beta 0.1
### --- Description: WD_WDQS_Analytics_FeatureEngineering.R performs 
### --- feature engineering of the SPARQL queries data 
### --- collected w. WD_WDQS_Analytics_CollectData.R from the 
### --- wmf.webrequest table in the WMF Data Lake.
### --- Author: Goran S. Milovanovic, Data Scientist, WMDE
### --- Developed under the contract between Goran Milovanovic PR Data Kolektiv
### --- and WMDE.
### --- Contact: goran.milovanovic_ext@wikimedia.de
### ---------------------------------------------------------------------------
### --- RUN FROM: /home/goransm/Analytics/Wikidata/WD_misc/WD_SPARQL_Endpoint_Analytics
### --- on stat1005
### --- Date: 2020/03/20
### ---------------------------------------------------------------------------

### ---------------------------------------------------------------------------
### --- LICENSE:
### ---------------------------------------------------------------------------
### --- GPL v2
### --- This file is part of the Wikidata SPARQL Endpoint Analytics Project
### ---
### --- Wikidata SPARQL Endpoint Analytics Project is free software: 
### --- you can redistribute it and/or modify
### --- it under the terms of the GNU General Public License as published by
### --- the Free Software Foundation, either version 2 of the License, or
### --- (at your option) any later version.
### ---
### --- Wikidata SPARQL Endpoint Analytics Project is distributed 
### --- in the hope that it will be useful,
### --- but WITHOUT ANY WARRANTY; without even the implied warranty of
### --- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
### --- GNU General Public License for more details.
### ---
### --- You should have received a copy of the GNU General Public License
### --- along with Wikidata SPARQL Endpoint Analytics Project.
### --- If not, see <http://www.gnu.org/licenses/>.
### ---------------------------------------------------------------------------

### --- setup
library(data.table)
library(tidyverse)
library(urltools)

### --- directories
### --- Read WDCM paramereters
# - fPath: where the scripts is run from?
fPath <- as.character(commandArgs(trailingOnly = FALSE)[4])
fPath <- gsub("--file=", "", fPath, fixed = T)
fPath <- unlist(strsplit(fPath, split = "/", fixed = T))
fPath <- paste(
  paste(fPath[1:length(fPath) - 1], collapse = "/"),
  "/",
  sep = "")
fPath <- '/home/goransm/Analytics/Wikidata/WD_misc/WD_SPARQL_Endpoint_Analytics/'

dataDir <- paste0(fPath, '_data/')
analyticsDir <- paste0(fPath, '_analytics/')
imgDir <- paste0(fPath, '_img/')

### ---------------------------------------------------------------------------
### --- Section 1. Load, clean, and decode the SPARQL queries data set
### --- NOTE. Mind the private data (!)
### ---------------------------------------------------------------------------

### --- Sys.setlocale()
Sys.setlocale('LC_ALL','C')

### --- load data
lF <- list.files(dataDir)
lF <- lF[grepl("^wdqs_data_sample", lF)]
lF <- lF[!grepl("TEST", lF)]
dataSet <- lapply(paste0(dataDir, lF), fread)
dataSet <- rbindlist(dataSet)
dim(dataSet)

### --- clean data: http_status
table(dataSet$http_status)
# - inspect http_status x time_firstbyte
dataSet %>% 
  dplyr::select(http_status, time_firstbyte) %>% 
  dplyr::group_by(http_status) %>% 
  dplyr::summarise(mean = mean(time_firstbyte, na.rm = T), 
                   median = median(time_firstbyte, na.rm = T), 
                   min = min(time_firstbyte, na.rm = T), 
                   max = max(time_firstbyte, na.rm = T))
# - NOTE. http_status == "-" has min == 0, max == 0, mean == 0, median == 0
# - Remove queries that failed for client errors: http_status == 4**
w <- which(grepl("^4", dataSet$http_status))
if (length(w) > 0) {
  dataSet <- dataSet[-w, ]
}

### --- clean data: cache_status
table(dataSet$cache_status)
# - inspect cache_status x time_firstbyte
dataSet %>% 
  dplyr::select(cache_status, time_firstbyte) %>% 
  dplyr::group_by(cache_status) %>% 
  dplyr::summarise(mean = mean(time_firstbyte, na.rm = T), 
                   median = median(time_firstbyte, na.rm = T), 
                   min = min(time_firstbyte, na.rm = T), 
                   max = max(time_firstbyte, na.rm = T))
# - NOTE. cache_status == "-" has min == 0, max == 0, mean == 0, median == 0

### --- clean data: agent_type
table(dataSet$agent_type)
# - inspect agent_type x time_firstbyte
dataSet %>% 
  dplyr::select(agent_type, time_firstbyte) %>% 
  dplyr::group_by(agent_type) %>% 
  dplyr::summarise(mean = mean(time_firstbyte, na.rm = T), 
                   median = median(time_firstbyte, na.rm = T), 
                   min = min(time_firstbyte, na.rm = T), 
                   max = max(time_firstbyte, na.rm = T))

### --- clean data: content_type
# - recode content_type
dataSet$content_type <- sapply(dataSet$content_type, function(x) {
  d <- gsub("charset=", "", x)
  d <- gsub("tab-separated-values", "tsv", d)
  return(d)
})
table(dataSet$content_type)
# - inspect content_type x time_firstbyte
dataSet %>% 
  dplyr::select(content_type, time_firstbyte) %>% 
  dplyr::group_by(content_type) %>% 
  dplyr::summarise(mean = mean(time_firstbyte, na.rm = T), 
                   median = median(time_firstbyte, na.rm = T), 
                   min = min(time_firstbyte, na.rm = T), 
                   max = max(time_firstbyte, na.rm = T))

### --- clean data: access_method
table(dataSet$access_method)
# - NOTE. Almost always dataSet$access_method == "desktop"

### --- validate dt (timestamp)
w <- which(dataSet$dt == "-")
length(w)
w <- which(!(grepl("\\d\\d\\d\\d-\\d\\d-\\d\\dT\\d\\d:\\d\\d:\\d\\dZ", 
                 dataSet$dt)))
length(w)
### --- extract year, month, day, hour, and minute from dt (timestamp)
dataSet$year <- substr(dataSet$dt, 1, 4)
dataSet$month <- substr(dataSet$dt, 6, 7)
dataSet$day <- substr(dataSet$dt, 9, 10)
dataSet$hour <- substr(dataSet$dt, 12, 13)
dataSet$minute <- substr(dataSet$dt, 15, 16)
dataSet$ymdhm <- paste(dataSet$year, 
                       dataSet$month,
                       dataSet$day,
                       dataSet$hour,
                       dataSet$minute,
                       sep = "-")
dim(dataSet)

### --- clean data: uri_query 
### --- prepare SPARQL queries for parsing
# - decode and clean uri_query
dataSet$uri_query <- urltools::url_decode(dataSet$uri_query)
# - check: dataSet$uri_query == ""
w <- which(dataSet$uri_query == "")
if (length(w) > 0) {
  dataSet <- dataSet[-w, ]
}
dim(dataSet)
# - check: is.na(dataSet$uri_query)
w <- which(is.na(dataSet$uri_query))
if (length(w) > 0) {
  dataSet <- dataSet[-w, ]
}
dim(dataSet)
w <- which(grepl("^\\s+$", dataSet$uri_query))
if (length(w) > 0) {
  dataSet <- dataSet[-w, ]
}
dim(dataSet)

### --- add ID, uniqueQueryID
dataSet$id <- 1:dim(dataSet)[1]
uniqueQueries <- unique(dataSet$uri_query)
uniqueQueries <- data.frame(uri_query = uniqueQueries, 
                            uniqueQueryId = 1:length(uniqueQueries), 
                            stringsAsFactors = F)
dataSet <- dataSet %>% 
  dplyr::left_join(uniqueQueries, 
                   by = "uri_query")
rm(uniqueQueries)

### --- rearrange, store clean data set
colnames(dataSet)
dataSet <- dataSet %>% 
  dplyr::select('id', 'uniqueQueryId', 'dt', 'year', 'month', 'day', 'hour', 'minute',
                       'uri_query', 'http_status', 'cache_status', 'time_firstbyte', 
                       'response_size', 'agent_type', 'content_type', 'access_method')
filename <- paste0(analyticsDir, 
                   'wdqs_data_sample_', 
                   substr(as.character(Sys.time()), 1, 10), 
                   '.csv')
write.csv(dataSet, filename)

1.2 Feaure Engineering

The SPARQL queries in our sample, present in the uri_query field retrieved from the wmf.webrequest table, need to be parsed in order to extract a set of meaningful features to use in predictive modeling of server response time. As in the case of Natural Language Processing, we need to differentiate between three possible levels of analysis here:

  • syntax: the composition of SPARQL keywords, operators, variables, and functions result in a specific description of the data that need to be obtained, and their analysis on behalf of Blazegraph determines the query execution plan (via Abstract Syntax Tree, AST, derived from query analysis); determines query validity and query execution plan;
  • semantics: the exact mapping of query syntax to particular variables that need to be instantiated in order to obtain the desired result from WDQS; theoretically, it determines truth relations; in practice, we can say that it precisely specifies the expected query result;
  • pragmatics: given the SPARQL language conventions are satisfied, any query migth instantiate various variable names, use (more or less efficiently) various syntactic constructions to obtain the same effect, include code comments, etc.: the way users use the SPARQL query language to communicate their goals to the WDQS.

Our approach to feature engineering is to parse each SPARQL query with a set of regular expressions in order to (a) single out the “atomic elements” of the SPARQL language (keywords, functions, IRI mentions, variables, literals, operators, Wikibase parameters, etc.), (b) compute a set of abstract variables that can be used to describe the query, e.g.: the number of different variables instantiated (i.e. how many unique variables there are in a query), the total number of variables mentioned (including repeated uses of the same variable), the number of IRI references in a query, how many variables also request labels, and similar.

With this appproach, which is essentialy the same as what is typically done in Natural Language Processing, query syntax and semantics are in effect fused. A question could be posed: why keep specific variable names, or comments, when it is clear that the later are not even processed while variable names are conventional and WDQS users are free to use them as long as they satisfy the rules of the SPARQL syntax? These aspects of query analysis are important from the viewpoint of query pragmatics and carry useful information indeed. Of course the variable names are conventional, but imagine a community of librarians using WDQS who learn SPARQL from each other and thus frequently use variable names like ?author and/or ?authorLabel? If such “typical” variable names are frequently used in similar queries, they can act as informative features in the prediction of query response time indeed. Comments: many Wikidata bots might use a “signature” comment (and indeed some do) of some form, for example. If such bots run their queries against WDQS frequently, the comments in their code might also act as predictive feaures for the query perfomance.

NOTE. The current regex analysis of SPARQL queries is approximate and, most of time, correct in a sense of being able to extract intuitively meaningful atoms of SPARQL. It is approximate in a sense that the regular expressions use are derived by the author of this report by directly analyzing the typical SPARQL queries with only limited attention payed to the formal definition of SPARQL (e.g. its Backus–Naur forms, or the formal regex definitions of its various syntactic constituents). The feature engineering procedure used in our current analyses can be improved, but from our manual query analysis inspection it can be improved only slightly.

Beyond extracting SPARQL “atoms”, the following features are derived as frequencies (counts) for each query:

  • nchar - the length of the query in characters
  • __vars__ - how many variables are used?
  • __vars_usage__ - how many variable mentions there are in total?
  • __vars_label__ - how many variables request labels also?
  • __vars_label_usage__ - how many times are variable labels mentioned in a query?
  • __paths__ - how many property paths are present in the query?
  • __IRI_REF__ - how many IRI references are used in the query?
  • __LITERAL__ - how many SPARQL literals are used in the query?

So the final description of any SPARQL query, following these feature engineering steps, is a vector of counts for each extracted feature, including: (a) tokens (each IRI mentioned, for example), as well as (b) types (how many __IRI_REFs__ there are in a query in total).

NOTE. Approximating syntax. Obviously, this feature engineering approach does not analyze the structural relations between the language constituents: its syntax. However, some of the extracted features clearly “correlate” with the query syntactic complexity. For example, the count of { tokens, or the count of SELECT keywords, used in a query, clearly speaks about the query depth (i.e. how many sub-queries do we have, if any). So the syntactic information is present in our feature engineering more or less up to a degree dictated by how much useful information is carried by a mention of a particular SPARQL keyword, or function, or operator, etc; the number of variables used, the query “depth”, and similar. More useful syntactic information can be obtained from Blazegraph by running a query in the EXPLAIN mode (similar to what one would in SQL), see: https://www.mediawiki.org/wiki/Wikidata_Query_Service/User_Manual#Explain_Query. However, even if featurus obtained from the query execution plan in this way might prove to be useful, it is unclear at this point whether the SPARQL query execution plan can be obtained from Blazegraph without actually running the query.

#!/usr/bin/env Rscript

### ---------------------------------------------------------------------------
### --- WD_WDQS_Analytics_FeatureEngineering.R, v. Beta 0.1
### --- Script: WD_WDQS_Analytics_FeatureEngineering.R, v. Beta 0.1
### --- Description: WD_WDQS_Analytics_FeatureEngineering.R performs 
### --- feature engineering of the SPARQL queries data 
### --- collected w. WD_WDQS_Analytics_CollectData.R from the 
### --- wmf.webrequest table in the WMF Data Lake.
### --- Author: Goran S. Milovanovic, Data Scientist, WMDE
### --- Developed under the contract between Goran Milovanovic PR Data Kolektiv
### --- and WMDE.
### --- Contact: goran.milovanovic_ext@wikimedia.de
### ---------------------------------------------------------------------------
### --- RUN FROM: 
### --- /home/goransm/Analytics/Wikidata/WD_misc/WD_SPARQL_Endpoint_Analytics
### --- on stat1005
### --- Date: 2020/03/20
### ---------------------------------------------------------------------------

### ---------------------------------------------------------------------------
### --- LICENSE:
### ---------------------------------------------------------------------------
### --- GPL v2
### --- This file is part of the Wikidata SPARQL Endpoint Analytics Project
### ---
### --- Wikidata SPARQL Endpoint Analytics Project is free software: 
### --- you can redistribute it and/or modify
### --- it under the terms of the GNU General Public License as published by
### --- the Free Software Foundation, either version 2 of the License, or
### --- (at your option) any later version.
### ---
### --- Wikidata SPARQL Endpoint Analytics Project is distributed 
### --- in the hope that it will be useful,
### --- but WITHOUT ANY WARRANTY; without even the implied warranty of
### --- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
### --- GNU General Public License for more details.
### ---
### --- You should have received a copy of the GNU General Public License
### --- along with Wikidata SPARQL Endpoint Analytics Project.
### --- If not, see <http://www.gnu.org/licenses/>.
### ---------------------------------------------------------------------------

### --- setup
library(data.table)
library(stringr)
library(dplyr)
library(tidyr)
library(snowfall)

### --- directories
### --- Read WDCM paramereters
# - fPath: where the scripts is run from?
fPath <- as.character(commandArgs(trailingOnly = FALSE)[4])
fPath <- gsub("--file=", "", fPath, fixed = T)
fPath <- unlist(strsplit(fPath, split = "/", fixed = T))
fPath <- paste(
  paste(fPath[1:length(fPath) - 1], collapse = "/"),
  "/",
  sep = "")
fPath <- '/home/goransm/Analytics/Wikidata/WD_misc/WD_SPARQL_Endpoint_Analytics/'
dataDir <- paste0(fPath, '_data/')
analyticsDir <- paste0(fPath, '_analytics/')
imgDir <- paste0(fPath, '_img/')

### ---------------------------------------------------------------------------
### --- Section 1. Load and prepare SPARQL queries
### ---------------------------------------------------------------------------
# - load queries
filename <- 'wdqs_data_sample_TEST_2020-04-12.csv'
dataSet <- fread(paste0(analyticsDir, filename))
dataSet$V1 <- NULL
# - keep only unique queries
w <- which(!duplicated(dataSet$uniqueQueryId))
if (length(w) > 0) {
  dataSet <- dataSet[w]
  }
# - prepare queries for parsing
queries <- dataSet$uri_query
uniqueQueryId <- dataSet$uniqueQueryId
rm(dataSet); gc()

# - parse_SPARQL()
parse_SPARQL <- function(tq) {
  # - get nchar()
  tq_nchar <- nchar(tq)
  # - split by "\\?query=|\\?query ="
  tq <- gsub("query=|query =|\\?query=|\\?query =", "query=__QUERY_STARTS___", tq)
  tq <- unlist(strsplit(tq, split = "query=|query =|\\?query=|\\?query ="))
  w_empty <- which(nchar(tq) == 0)
  if (length(w_empty) > 0) {tq <- tq[-w_empty]}
  # - recognize format, output, results parameters
  wPars <- which(grepl("format=|format =|output=|output =|results=|results =", tq))
  # - store format, output, results parameters in a separate
  # - vector: pars; remove from tq if any parameters are present
  if (length(wPars) > 0) {
    pars <- tq[wPars]
    pars <- gsub("\\?|&", "", pars)
    tq <- tq[-wPars]
  } else {
    pars <- NULL
  }
  tq <- gsub("__QUERY_STARTS___", "", tq, fixed = T)
  # - remove "?" from the beginning of SPARQL if present
  tq <- gsub("^\\?", "", tq)
  # - split by "\n"
  tq <- unlist(lapply(tq, function(x) {
    strsplit(x, split = "\\n")
  }))
  # - remove "\t" if any
  tq <- gsub("\\t+", "", tq)
  # - trim trailing space at the beginning and end, if any
  tq <- str_trim(tq, side = "both")
  # - collect IRI references
  tq_iri <- unlist(str_extract_all(tq, '<([[:alnum:]]|[[:punct:]])+>'))
  # - match IRI references
  tq <- gsub('<([[:alnum:]]|[[:punct:]])+>', " __IRI_REF__ ", tq)
  # - preserve lines that encompasse only comments
  wCom <- which(grepl("^#", tq))
  if (length(wCom) > 0) {
    comment_lines <- tq[wCom]
    tq <- tq[-wCom]
  } else {
    comment_lines <- NULL
  }
  # - space to the left and right of 
  # - brackets, operators, and punctuation
  tq <- gsub("\\{", " \\{ ", tq)
  tq <- gsub("\\}", " \\} ", tq)
  tq <- gsub("\\)", " \\) ", tq)
  tq <- gsub("\\(", " \\( ", tq)
  tq <- gsub("\\]", " \\] ", tq)
  tq <- gsub("\\[", " \\[ ", tq)
  tq <- gsub("([^<]|[^>]|[^!])=", " = ", tq)
  tq <- gsub("!=", " != ", tq)
  tq <- gsub(">=", " >= ", tq)
  tq <- gsub("<=", " <= ", tq)
  tq <- gsub(">", " > ", tq)
  tq <- gsub("<", " < ", tq)
  tq <- gsub("<", " < ", tq)
  tq <- gsub("\\|\\|", " \\|\\| ", tq)
  tq <- gsub("&&", " && ", tq)
  tq <- gsub(",", " , ", tq)
  tq <- gsub("\\.", " \\. ", tq)
  tq <- gsub(";", " ; ", tq)
  tq <- gsub("\\s+", " ", tq)
  # - match literals
  tq <- gsub('(\'|")+([^\'"])+(\'|")+', " __LITERAL__ ", tq)
  tq <- gsub('\\b[[:digit:]]+\\b', " __LITERAL__ ", tq)
  # - "word tokenization": split by "\s"
  tq <- unlist(lapply(tq, function(x) {
    strsplit(x, split = "\\s")
  }))
  # - remove: '"', ".", ","
  tq <- gsub('("|\\.|,)+', '', tq)
  # - remove: "'"
  tq <- gsub("'+", '', tq)
  # - count unique variables
  num_vars <- length(unique(tq[grepl('^\\?|^\\$', tq)]))
  # - count variable use
  vars_usage <- length(tq[grepl('^\\?|^\\$', tq)])
  # - count unique label variables
  num_vars_label <- length(unique(tq[grepl('[^:]label$', tq, ignore.case = T)]))
  # - count label variable use
  label_vars_usage <- length(tq[grepl('[^:]label$', tq, ignore.case = T)])
  # - add variables use and unique variable count to tq
  tq <- c(tq, 
          rep('__vars__', num_vars), 
          rep('__vars_usage__', vars_usage), 
          rep('__vars_label__', num_vars_label),
          rep('__vars_label_usage__', label_vars_usage))
  # - tolower()
  tq <- tolower(tq)
  # - remove tq elements that begin with: "?", "$", ")", ";", "}"
  wClean <- which(grepl('^\\)|^;|^\\}|^\\]', tq))
  if (length(wClean > 0)) {
    tq <-  tq[-wClean]
    }
  # - clean from: "]", "}", ";", ")" if present anywhere
  tq <- gsub("\\]|\\}|\\)|;", "", tq)
  # - count how many "/" path operators were used in a query
  num_paths <- length(tq[grepl('([[:alnum:]]|:)+/([[:alnum:]]|:)+', tq)])
  tq <- c(tq, 
          rep('__paths__', num_paths))
  # - remove empty elements from tq if any:
  wEmpty <- which(grepl("^\\s+$|^$", tq))
  if (length(wEmpty) > 0) {tq <- tq[-wEmpty]}
  # - enter comments
  if (length(comment_lines) > 0) {tq <- c(tq, comment_lines)}
  # - enter pars
  if (!is.null(pars)) {tq <- c(tq, pars)}
  # - enter tq_iri
  if (length(tq_iri)>0) {tq <- c(tq, tq_iri)}
  # - tq feature frequency as data.frame
  tq <- as.data.frame(table(tq))
  # - enter nchar(tq) as feature:
  tq <- rbind(tq, 
              data.frame(tq = 'nchar',
                         Freq = tq_nchar))
  # - ouput: tq
  return(tq)
}

# - Parse SPARQL queries
sfInit(parallel = T, cpus = 36)
t1 <- Sys.time()
sfExport("queries")
sfExport("parse_SPARQL")
sfLibrary(stringr)
# - process
queries_processed <- sfClusterApplyLB(queries, parse_SPARQL)
# - stop cluster
sfStop()
print(paste0("Parsing took: ", 
             round(difftime(Sys.time(), t1, units = "min"), 2)))

# - collect results
names(queries_processed) <- uniqueQueryId
queries_processed <- rbindlist(queries_processed, 
                               idcol = T, 
                               fill = T, 
                               use.names = T)
colnames(queries_processed) <- 
  c('uniqueQueryId', 'feature', 'freq')
write.csv(queries_processed, paste0(analyticsDir, "queries_processed_TEST.csv"))

# - produce vocabulary: feature frequency
vocabulary <- queries_processed %>%
  dplyr::select(feature, freq) %>% 
  dplyr::group_by(feature) %>% 
  dplyr::summarise(freq = sum(freq)) %>% 
  dplyr::arrange(desc(freq))
write.csv(vocabulary, paste0(analyticsDir, "SPARQLvocabulary_TEST.csv"))
head(data.frame(vocabulary), 400)

# - in how many queries is a particular feature used?
head(queries_processed)
query_features <- queries_processed %>% 
  dplyr::select(feature) %>% 
  dplyr::group_by(feature) %>% 
  dplyr::summarise(num_queries = n()) %>% 
  dplyr::arrange(desc(num_queries))
query_features$percent_queries <- 
  query_features$num_queries/length(unique(queries_processed$uniqueQueryId))
write.csv(query_features, paste0(analyticsDir, 'feature_query_percent_TEST.csv'))

Again, The goal is to derive a description of SPARQL queries via feature vectors. For example:

iri_ref:11, literal:1, vars:5, vars_label:1, vars_label_usage:1, vars_usage:20, ?item:15, ?type:1, ?typelabel:1, ?url:2, ?wikipedia:1, {:14, http://standartconta.com.tr/:1, http://standartconta.com.tr:1, http://www.standartconta.com.tr/:2, http://www.standartconta.com.tr:1, http://www.www.standartconta.com.tr/:1, https://standartconta.com.tr/:1, https://standartconta.com.tr:1, https://www.standartconta.com.tr/:1, https://www.standartconta.com.tr:1, https://www.www.standartconta.com.tr/:1, bd:serviceparam:1, distinct:1, optional:1, schema:about:1, select:1, service:1, union:10, wdt:p31:1, wdt:p856:12, where:1, wikibase:label:1, wikibase:language:1, nchar:903

In this example of a query feature vector we see that the query had used 11 IRI references (__iri_ref__:11), one literal (__literal__:1), five variables (__vars__:5) that were mentioned 20 times altogether (__vars_usage__:20), used wikibase:label once (wikibase:label:1), the UNION keyword 10 times (union:10), that the length of the query in characters is nchar:903, etc.

2. A criterion for selecting a critical query for WDQS

Now when each SPARQL query in our sample is described by a feature vector suitable for predictive modeling, we need to select a criterion variable: what do we want to predict? We want to predict the WDQS response time, obviously - the information carried in the time_firstbyte field of the wmf.webrequest table (i.e.how many seconds elapsed before the first byte of the server response was sent out to the client). However, since we already now that the time to process a SPARQL query depends not only upon the features of the query itself, but also upon the WDQS/Blazegraph configuration that presents a black box for us, we might conclude that predicting the exact server response time might present a too ambitious goal. Indeed, our initial attempts to predict the server response time for a query shows that approximately a correlation of R = .72 (on the test data set, approx. half of 1M queries in the sample) between the predictive model based on our features and response time can be achieved - a result not that sound.

What if we can train a model to predict a derived, binary variable that describes each query as critical (i.e. resulting in an excessively long query processing time) or non-critical (i.e. resulting in a “typical” query processing time)? If we pose the problem in this way, than it is easy to derive such a binary criterion from the observed time_firstbyte value for each query:

  • take the extreme outliers on time_firstbyte to be all observations above the variable’s upper outer fence: Q3 + 3*IQR, where Q3 is the .75 percentile of time_firstbyte, while IQR stands for Inter-Quartile Range and presents the difference between Q3 (the .75 percentile of the data) and Q1 (the .25 percentile of the data).

This definition is widely used in Exploratory Data Analysis. We can also select a less extreme criterion by considering anything that is a mild outlier on time_firstbyte as critical:

  • take the mild outliers on time_firstbyte to be all observations above the variable’s upper inner fence: Q3 + 1.5*IQR, where Q3 is the .75 percentile of time_firstbyte, while IQR stands for Inter-Quartile Range and presents the difference between Q3 (the .75 percentile of the data) and Q1 (the .25 percentile of the data).

While we have used both criteria in our attempts at predictive modeling, we will mainly focus on the prediction of extreme outliers on time_firstbyte in what follows.

3. Machine Learning w. XGBoost: can we predict extreme outliers on WDQS response time from SPARQL query features?

Thus far,

  • all SPARQL queries are represented as feature vectors, and
  • a criterion variable has been defined (extreme outliers on WDQS server response times).

Our goal is to select a predictive model that will use the feauture vectors to predict whether the respective queries’ response times are critical (extreme) or not. At the same time, the model will be performing feature selection and informing us about the value of each feature in the prediction. This will allows us to select a small set of SPARQL feauters that can be used to characterize the queries as resulting in typical vs. extreme WDQS response times. And such small feature vocabulary is exactly what we are looking for in order to be able to develop a WDQS optimization engine in the future: a system that would fast can any given SPARQL query and decide wheter it needs to be handled as a special, potentially difficult case to process - or should the processing proceed as usual.

We have used XGBoost via the R {xgboost} package to train many predictive models that attempt to categorize SPARQL queries as critical or not in accordance with the above stated criterion. XGBoost - short for Extreme Gradient Boosting - is a ML method to estimate an ensemble of mathematical models - decision trees in our case - that jointly work to predict the response variable. Its description is really way out of scope of this Report. This technique is as known to be notoriously good in extracting almost every and the last piece of useful information from the training data (feauture vectors describing the SPARQL queries in this case). The XGBoost models involve a large number of parameters that must be fine-tuned in order for it to derive a plausible predictive model for the task at hand. We have experimented with various parameter settings and data sets encompassing a variable number of extracted SPARQL features to reach the following conclusions:

  • the number of features used to build a model does not significantly change the accuracy and precision (both to be explained soon) with which the model can tell the critical SPARQL queries from typical ones; this is most probably entirely due to the fact that XGBoost performs feature selection along the way of its training; this is also very important because it means that we can use only a small fraction of features to train a predictive model - and the model with less features will run faster than the one encompassing a large number of feature;
  • mostly, the performance of the predictive model is not seriously affected by choosing various combinations of parameter settings; we have performed many cross-validations, always additional splitting our 1M sample of SPARQL queries into train and test data (approx. 50% of the sample each), in order to reach this conclusion;
  • the model performance is good but far from perfect, however
  • there are possible improvements that always involve a compromise in respect to how can be we design a future WDQS Optimization Engine.

In order to explain the typical modelling result with XGBoost, we need to introduce some terminology first.

Let’s assume each of the SPARQL queries is coded as 1 if the respective WDQS response time for that query was critically high or 0 if it had fallen in a range of typical processing times. For a sample of SPARQL queries than we have two binary vectors over {0, 1} describing: (1) the observed status of the query (again: 1 - critical, 0 - typical), and (2) the predicted status of the query: what the trained predictive model “thinks” the query would be by looking at the features that describe it.

For example:

sampleFrame <- data.frame(query = paste0("Query_", 1:10),
                          observed = round(runif(10, 0, 1)),
                          predicted = round(runif(10, 0, 1)))
sampleFrame

For each of the 10 queries in the table we know if they really where critical (1) or not (0) and we see that from the observed column, and if the model predicts they would be critical (1) or not (0) from their features: the predicted column. For example, in the first row we have a query that was critical indeed and correctly predicted as critical: that is something we call a Hit (or a True Positive) in ML. In the second row we observe a False Alarm (or a False Positive): the query was not really critical (it has 0 on observed) but the model has incorrectly categorized it as if it would be (1 on predicted). In the eigth row we find a situation known as a Correct Rejection: the query was not critical (0 on observed) and the model figured out it wouldn’t be critical indeed by looking into its features (0 on predicted). Finally, the fourth important situation can be found in the sixth row, where the query was critical (1 on observed) but not recognized as such by the model (0 on predicted), a case known as Miss in the ML terminology. More on this terminology: https://en.wikipedia.org/wiki/Confusion_matrix.

In the table above, we can compute the model accuracy by looking at how many times do the observe and predicted column coincide:

Table 1. Confusion matrix example.

acc <- sum(sampleFrame$observed == sampleFrame$predicted)/dim(sampleFrame)[1]
print(paste0("Model accuracy = ", round(acc, 2), "."))
[1] "Model accuracy = 0.6."

When we look at the Hit rate we see that:

hits <- sum(sampleFrame$observed == 1 & sampleFrame$predicted == 1)/sum(sampleFrame$observed == 1)
print(paste0("Hit rate = ", round(hits, 2), "."))
[1] "Hit rate = 0.43."

Finally, the False alarm rate of the model is:

FAs <- sum(sampleFrame$observed == 0 & sampleFrame$predicted == 1)/sum(sampleFrame$observed == 1)
print(paste0("FA rate = ", round(FAs, 2), "."))
[1] "FA rate = 0."

So, the model would recognize a critical query correctly 71% of time, while 30% of time it would incorrectly predict a non-critical query to be critical. Imagine if we use this model to chose when to start and when not to start some optimization procedure in WDQS - be it as simple as diverging different queries (critical vs. non-critical) to separate processing resources. From the viewpoint of any system optimization, the decision is needed: can we live with these two values, or not? The False Alarm suggests that 30% of time the model would suggest the optimization routine to start in a situation when it should not start and thus spare valuable system resources. Again, 100% - 71% (Hit rate) = 29% of time the model would miss to trigger the optimization routine for a critical query that could engage too many resources.

A good predictive model is the one which attains (a) a high Hit rate (i.e. observed critical and predicted critical), and (b) a low False Alarm rate (i.e. observed non-critical but predicted critical). In other words, we do not want our model to miss critical queries and thus not trigger some optimization procedure to help WDQS better use its resources, but we also do not want our model to trigger any optimization procedures on a query that would not result in a critically high usage of resources.

Our current results:

  • the results vary with the number of features used and specific values of model parameters, but they do not vary significantly:
  • typical model accuracy is around 95%;
  • typical Hit rate is around 48%, and
  • typical False alarm rate is slightly above 2%.

Now we can see how misleading is model accuracy alone.

Tweaks and Compromises

  • Without going into detail about it, since this is a binary classification task, there is a legitimate way to tweak a model and attain a higher hit rate (by manipulating the so called decision boundary), but at the expense of increasing the False Alarm rate at the same time. For example, with the current results, we can achieve a Hit rate as high as 84% but at the expense of increasing the False Alarm rate to almost 40% at the same time.

How do we compromise about this prediction model? First to remind: it is a binary classification model. The model does not directly output its predictions as classes: 1 - critical, 0 - typical, as in the example in Table 1. Rather, the model learns about the queries from their feature vectors and predicts a probability that the query will critical (1). Now, in order to categorize the query as critical or not, we need to set a treshold value (i.e. a decision boundary), .5 for example, and prononuce the model prediction to be 1 each time a predicted probability for a query is above the treshold value. A value of .5 is an intuitive choice for binary classification, however, we might pick another value as well. It is by manipulating the treshold value how we enter a trade-off between True (Hit) and False Positive (False Alarm) rates. In the following chart the treshold value varies smoothly between 0 and 1, the True Positive rate is found on the vertical, and the False Positive rate on the horizontal axis:

Chart 1.

library(ggplot2)
library(ggrepel)
locDir <- '/home/goransm/Work/___DataKolektiv/Projects/WikimediaDEU/_WMDE_Projects/Wikidata/WD_Misc/WD_SPARQL_Endpoint_Analytics/_analytics/'
rocModel <- read.csv(paste0(locDir, 'rocModel.csv'),
                     header = T, 
                     row.names = 1,
                     stringsAsFactors = F)
rocModel$diff <- rocModel$hit - rocModel$FA
wMaxDiff <- which.max(rocModel$diff)
rocModel[wMaxDiff, ]
rocModel$label <- ''
rocModel$label[wMaxDiff] <- paste0('Hit: ', round(rocModel$hit[wMaxDiff], 2), 
                                   "; FA: ", round(rocModel$FA[wMaxDiff], 2))
ggplot(data = rocModel,
       aes(x = FA, y = hit, label = label)) + 
  geom_point(size = .75) + geom_path(size = .25, color = "blue") + 
  ggrepel::geom_text_repel() + 
  xlab('False Positives (False Alarm Rate)') + ylab('True Positives (Hit Rate)') +
  theme_bw()

We can see that the there is a treshold value where we observe a 67% True Positive Rate and a 12% False Positive Rate: the difference between the two is highest when treshold = .2.

In effect this means:

  • imagine, again, that we develop a system to optimize the WDQS by somehow splitting its resources into those that process queries with typical expected processing times from those with critical expected processing times;
  • our current modelling efforts let us know that we can increase the number of True Positives to two-thirds at the expense of observing 12% of typical queries as if they were critical;
  • that means the system would correctly run on critical processing resourcess 2/3 of the time, while it would erroneously run on critical processing resources 12% of time (i.e. 12% of queries should be processed with the typical processing resources but they would incorrectly trigger the critical processing resources);
  • or, to rephrase: the model would think a critical query is non-critical one-third of a time, and decide to pass such a query to a normal, typical WDQS processing, while at the same time it would recognize a non-critical query as non-critical 88% of time and than it would not trigger any optimization action.

The question is: can we afford such an optimizer for WDQS? What is the trade-off that we can accept to live with?

4. Directions for future research + considerations of possible problems in WDQS optimization

Directions for further research (time estimate: until the end of the next week)

  • The sampling approach appears to be robust, but we need to replicate all feature engineering and ML procedures across at least several more samples;
  • The feature engineering procedures are approximate: they could and should be improved;
  • The ML results were discussed and it is uncertain if further significant improvements are possible, but at least the following should be tested:
    • working with corrections for imbalanced designs: we observe a very low number of critical queries compared to a huge number of typical queries in our sample; some corrections to control for this imbalance are already applied, but there are other approaches that might be helpful to potentially train better classifiers;
    • not all XGBoost parameters that might help train a better classifier were entered into our cross-validation procedures; some of them should be given a change;
    • maybe a different model altogether (e.g. Convolutional Deep Learning) should be tested.

Final considerations

  • If we are about to develop some sort of a WDQS Optimization Engine based on this and/or similar models,
  • we need to consider how fast can these class of models run in production, and
  • if they can grab “environmental variables” from the WDQS/Blazegraph: for example, one of the features they use is the total number of queries running against the WDQS in the same minute in which the query under analysis was run - and this cannot be known from SPARQL query analysis alone.
  • Deployment to production: we would probably need to switch from {xgboost} to its implementation in {h2o} where we can deploy models as Java MOJO or POJO objects, or build a REST API for a model predictive run; all these steps imply that some time would be lost in query processing before the optimizer would be able to deliver a prediction, so deploying a model with a minimum runtime latency in a predictive run would be essential.

Goran S. Milovanović

Wikimedia Deutschland, Data Scientist DataKolektiv, Owner

contact: goran.milovanovic_ext@wikimedia.de

LS0tCnRpdGxlOiBXRFFTIEVuZHBvaW50IEFuYWx5dGljcyAtIE9wdGltaXppbmcgU2VydmVyIFJlc3BvbnNlIFRpbWUKYXV0aG9yOgotIG5hbWU6IEdvcmFuIFMuIE1pbG92YW5vdmnEhwogIGFmZmlsaWF0aW9uOiBXaWtpbWVkaWEgRGV1dHNjaGxhbmQsIERhdGEgU2NpZW50aXN0LCBEYXRhS29sZWt0aXYsIE93bmVyCm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgY29kZV9mb2xkaW5nOiBoaWRlCiAgICB0aGVtZTogc3BhY2VsYWIKICAgIHRvYzogeWVzCiAgICB0b2NfZmxvYXQ6IHllcwogICAgdG9jX2RlcHRoOiA1CiAgaHRtbF9kb2N1bWVudDoKICAgIHRvYzogeWVzCiAgICB0b2NfZGVwdGg6IDUKLS0tCioqKgoKKipSZXBvcnQgdGltZXN0YW1wOioqIGByIHtTeXMudGltZSgpfWAKCioqKgoKKipSZWZlcmVuY2UgUGhhYnJpY2F0b3IgVGlja2V0OioqIFtUMjQ4MzA4XShodHRwczovL3BoYWJyaWNhdG9yLndpa2ltZWRpYS5vcmcvVDI0ODMwOCkKCioqKgojIyMgTm90ZWJvb2s6IE9wdGltaXppbmcgV0RRUyBSZXNwb25zZSBUaW1lIEZyb20gU1BBUlFMIFF1ZXJpZXMgQW5hbHlzaXMKKipGZWVkYmFjayoqIHNob3VsZCBiZSBzZW5kIHRvIGBnb3Jhbi5taWxvdmFub3ZpY19leHRAd2lraW1lZGlhLmRlYC4gCgojIyAqKlN1bW1hcnkqKiAKCltXTURFXShodHRwczovL3d3dy53aWtpbWVkaWEuZGUvKSB0b2dldGhlciB3aXRoIFtwaHAuY2NdKGh0dHBzOi8vdGhlcGhwLmNjL3dlbGNvbWUpIGlzIHRyeWluZyB0byBmaW5kIGEgd2F5IHRvIG9wdGltaXplIHRoZSBbV2lraWRhdGEgUXVlcnkgU2VydmljZV0oaHR0cHM6Ly93d3cubWVkaWF3aWtpLm9yZy93aWtpL1dpa2lkYXRhX1F1ZXJ5X1NlcnZpY2UpLiBUaGlzIFJlcG9ydCBkZXNjcmliZXMgYSBNYWNoaW5lIExlYXJuaW5nIChNTCkgYXBwcm9hY2ggdG8gYW5hbHl6ZSB0aGUgU1BBUlFMIHF1ZXJpZXMgdGhhdCB3ZXJlIHJlY2VpdmVkIGJ5IHRoZSBlbmRwb2ludCBpbiBNYXJjaCAyMDIwLCB3aXRoIHRoZSBnb2FsIG9mIGRldmVsb3BpbmcgYSBtb2RlbCBhYmxlIHRvIHByZWRpY3Qgd2hldGhlciBhIHF1ZXJ5IHdvdWxkIGJlICoqY3JpdGljYWwqKiBmb3IgV0RRUy9CbGF6ZWdyYXBoIG9yIG5vdC4gQnkgc2luZ2xpbmcgb3V0IHRoZSBtb3N0IGltcG9ydGFudCBmZWF0dXJlcyB0aGF0IGFyZSBwcmVzZW50IGluIHRoZSBTUEFSUUwgcXVlcmllcyB1bmRlciBhbmFseXNpcyB3ZSBob3BlIHRvIGJlIGFibGUgdG8gdW5kZXJzdGFuZCBiZXR0ZXIgd2hhdCB0eXBlcyBvZiBxdWVyaWVzIHR5cGljYWxseSBjYXVzZSBwcm9ibGVtcy4gQmV5b25kIHRoYXQsIHRoZSBkZXZlbG9wbWVudCBvZiBhIHByZWRpY3RpdmUgbW9kZWwgZm9yIFNQQVJRTCBxdWVyeSBzZXJ2ZXIgcmVzcG9uc2UgdGltZSBpbiBpdHNlbGYgbGF5cyBvdXQgdGhlIGZvdW5kYXRpb25zIGZvciB0aGUgZGV2ZWxvcG1lbnQgb2YgYW4gb3B0aW1pemF0aW9uIGVuZ2luZSBmb3IgV0RRUywgYSBzeXN0ZW0gdGhhdCB3b3VsZCBiZSBhYmxlIHRvIHF1aWNrbHkgc2NhbiBhIHJlY2VpdmVkIHF1ZXJ5IGFuZCBkZWNpZGUgd2hldGhlciB0aGUgV0RRUyBoYW5kbGluZyBvZiBhbnkgZ2l2ZW4gcXVlcnkgbmVlZHMgdG8gYmUgYWRqdXN0ZWQgaW4gb3JkZXIgdG8gYXZvaWQgY3JpdGljYWxseSBoaWdoIHNlcnZlciByZXNwb25zZSB0aW1lcy4gCgpJbiB0aGUgZm9sbG93aW5nIHNlY3Rpb25zIHdlIGRlc2NyaWJlICoqKDEpKiogdGhlICpzYW1wbGluZyBhcHByb2FjaCogdGFrZW4gdG8gY29sbGVjdCBhIHJlbGV2YW50IHNldCBvZiBTUEFSUUwgcXVlcmllcyBvYnNlcnZlZCBhdCB0aGUgKFdEUVMpIGVuZHBvaW50IGluIHRocmVlIHdlZWtzIG9mIE1hcmNoIDIwMjAsICoqKDIpKiogdGhlICpmZWF0dXJlIGVuZ2luZWVyaW5nKiBhcHByb2FjaCB0YWtlbiB0byBhbmFseXplIHRoZSBjb21wb3NpdGlvbiBvZiBxdWVyaWVzIGFuZCBleHRyYWN0IG1lYW5pbmdmdWwgImF0b21pYyBlbGVtZW50cyIgb2YgU1BBUlFMIHRoYXQgd2lsbCBiZSB1c2VkIGluIGFuIGF0dGVtcHQgdG8gcHJlZGljdCB0aGUgV0RRUyByZXNwb25zZSB0aW1lIGZvciBhIHF1ZXJ5LCAqKigzKSoqIHRoZSBkZWZpbml0aW9uIG9mIGEgY3JpdGVyaW9uIGZvciBzZWxlY3RpbmcgYSAqKmNyaXRpY2FsKiogcXVlcnkgZm9yIFdEUVMgKGkuZS4gdGhlIG9uZSB0aGF0IG1ha2VzIHRvbyBtdWNoIHVzZSBvZiB0aGUgc2VydmljZSdzIHJlc291cmNlcyksICoqKDQpKiogdGhlIE1MIChvcHRpbWl6YXRpb24pIGFwcHJvYWNoLCByZWx5aW5nIG9uIFtYR0Jvb3N0XShodHRwczovL3hnYm9vc3QucmVhZHRoZWRvY3MuaW8vZW4vbGF0ZXN0LyksIHRha2VuIHRvIGJ1aWxkIGEgcHJlZGljdGl2ZSBtb2RlbCBmb3IgV0RRUyBzZXJ2ZXIgcmVzcG9uc2UgdGltZXMgc3RhcnRpbmcgZnJvbSB0aGUgZXh0cmFjdGVkIFNQQVJRTCBmZWF0dXJlcywgYW5kICg1KSB0aGUgKmxlc3NvbnMgbGVhcm5lZCogLSBzcGVjaWZpY2FsbHkgb24gd2hhdCAqY29uc3RyYWludHMqIHdpbGwgYmUgbWV0IGluIHRoZSBjYXNlIG9mIGFuIGV2ZW50dWFsIGRldmVsb3BtZW50IG9mIGEgV0RRUyBvcHRpbWl6YXRpb24gZW5naW5lIGluIHRoZSBmdXR1cmUgLSBhcyB3ZWxsIGFzICpzdWdnZXN0aW9ucyogb24gd2hhdCBmdXJ0aGVyIHN0ZXBzIG5lZWQgdG8gYmUgbWFkZSBpbiBvcmRlciB0byByZWFjaCBhbiBhY2NlcHRhYmxlIGxldmVsIG9mIHVuZGVyc3RhbmRpbmcgb2YgdGhlIHByb2JsZW0gYW5kIGRlc2lnbiBhIHByb3RvdHlwZSBvZiBhIGZ1dHVyZSBzb2x1dGlvbi4KCioqKgoKIyMgMC4gU2FtcGxlIG9mIFNQQVJRTCBxdWVyaWVzIG9ic2VydmVkIGF0IHRoZSBXRFFTIFNQQVJRTCBlbmRwb2ludAoKVGhlIFNQQVJRTCBxdWVyaWVzIHRoYXQgaGl0IHRoZSBlbmRwb2ludCBhcmUgcmVnaXN0ZXJlZCBpbiB0aGUgW3dtZi53ZWJyZXF1ZXN0XShodHRwczovL3dpa2l0ZWNoLndpa2ltZWRpYS5vcmcvd2lraS9BbmFseXRpY3MvRGF0YV9MYWtlL1RyYWZmaWMvV2VicmVxdWVzdCkgdGFibGUgaW4gdGhlIFdNRiBEYXRhIExha2UgKG91ciBIYWRvb3AgQmlnIERhdGEgc3RvcmFnZSkuIEEgW2J1Y2tldGluZyBhcHByb2FjaF0oaHR0cHM6Ly9jd2lraS5hcGFjaGUub3JnL2NvbmZsdWVuY2UvZGlzcGxheS9IaXZlL0xhbmd1YWdlTWFudWFsK1NhbXBsaW5nKSB0byBzYW1wbGUgZGF0YSBmcm9tIHRoaXMgSGl2ZSB0YWJsZSB3YXMgdGFrZW4sIHNhbXBsaW5nIGFwcHJveGltYXRlbHkgYDElYCBvZiBxdWVyaWVzIHRoYXQgd2VyZSBvYnNlcnZlZCBpbiB0aGUgcGVyaW9kIGJldHdlZW4gYDIwMjAvMDMvMDFgIGFuZCBgMjAyMC8wMy8yMGAuIFRoZSBzYW1wbGUgaXMgYXBwcm94aW1hdGVseSB1bmlmb3JtbHkgZGlzdHJpYnV0ZWQgYWNyb3NzIGRheXMgYW5kIGhvdXJzIG9mIHRoZSBtZW50aW9uZWQgcGVyaW9kIG9mIHRpbWUuIFRoZSBzYW1wbGUgc2l6ZSBpcyBzbGlnaHRseSBoaWdoZXIgdGhhbiBgMU1gIFNQQVJRTCBxdWVyaWVzIC0gaW5jbHVkaW5nIGR1cGxpY2F0ZXMuCgpUaGUgZm9sbG93aW5nIGZpZWxkcyB3ZXJlIGNvbGxlY3RlZCBmcm9tIHRoZSBgd21mLndlYnJlcXVlc3RgIHRhYmxlIGZvciBlYWNoIGNvbGxlY3RlZCBTUEFSUUwgcXVlcnkgKHNlZTogW3dtZi53ZWJyZXF1ZXN0IGRvY3VtZW5hdGlvbl0oaHR0cHM6Ly93aWtpdGVjaC53aWtpbWVkaWEub3JnL3dpa2kvQW5hbHl0aWNzL0RhdGFfTGFrZS9UcmFmZmljL1dlYnJlcXVlc3QpIGZvciBkZXRhaWxzKToKCi0gYHVyaV9xdWVyeWAgLSB0aGUgVVJMIGVuY29kZWQgU1BBUlFMIHF1ZXJ5IHcuIFdEUVMgcXVlcnkgcGFyYW1ldGVycwotIGBkdGAgLSB0aGUgcXVlcnkgdGltZXN0YW1wLCBsYXRlciBwYXJzZWQgaW50byBgeWVhcmAsIGBtb250aGAsIGBkYXlgLCBgaG91cmAsIGFuZCBgbWludXRlYAotIGBodHRwX3N0YXR1c2AgLSB0aGUgc2VydmVyIGh0dHAgc3RhdHVzIHJlc3BvbnNlIGZvciB0aGlzIHF1ZXJ5Ci0gYGNhY2hlX3N0YXR1c2AgLSB0aGUgY2FjaGUgc3RhdHVzIGZvciB0aGlzIHF1ZXJ5IChzZWU6IGh0dHBzOi8vd2lraXRlY2gud2lraW1lZGlhLm9yZy93aWtpL0NhY2hpbmdfb3ZlcnZpZXcjSGVhZGVycykKLSBgdGltZV9maXJzdGJ5dGVgIC0gaG93IG1hbnkgc2Vjb25kcyBlbGFwc2VkIGJlZm9yZSB0aGUgZmlyc3QgYnl0ZSBvZiB0aGUgc2VydmVyIHJlc3BvbnNlIHdhcyBzZW50IG91dCB0byB0aGUgY2xpZW50Ci0gYHJlc3BvbnNlX3NpemVgIC0gcmVzcG9uc2Ugc2l6ZQotIGBhZ2VudF90eXBlYCAtIHNwaWRlciBvciBodW1hbgotIGBjb250ZW50X3R5cGVgIC0gY29udGVudCB0eXBlIChlLmcuIEpTT04sIHRleHQvdHN2LCB4bWwsIGV0Yy4pCi0gYGFjY2Vzc19tZXRob2RgIC0gZGVza3RvcCwgbW9iaWxlLCBvciBhcHAuCgoqKk5PVEUuKiogQXMgbm90ZWQgaW4gdGhlICoqU3VtbWFyeSoqLCBvdXIgYXR0ZW1wdCB3aWxsIGJlIHRvIGFuYWx5emUgdGhlIGNvbXBvc2l0aW9uIG9mIHRoZSBTUEFSUUwgcXVlcmllcyBhbmQgdHJ5IHRvIHByZWRpY3QgYSBiaW5hcnkgdmFyaWFibGUgKmNyaXRpY2FsIHJlc3BvbnNlIHRpbWUqLypub24tY3JpdGljYWwgcmVzcG9uc2UgdGltZSogd2hpY2ggd2UgcGxhbiB0byBkZXJpdmUgZnJvbSBgdGltZV9maXJzdGJ5dGVgICh0YWtlbiB0byBiZSBhbiBhcHByb3hpbWF0aW9uIHRvIHRoZSBXRFFTIHJlc3BvbnNlIHRpbWUpLiBJdCBpcyBpbW1lZGlhdGVseSBvYnZpb3VzIHRoYXQgb25seSBzb21lIGZpZWxkcyBmcm9tIHRoZSBgd21mLndlYnJlcXVlc3RgIHRhYmxlIGFyZSBjYW5kaWRhdGVzIGZvciBhIHByZWRpY3RpdmUgbW9kZWwgb2YgdGhlIGRlc2NyaWJlZCBraW5kOiBgdXJpX3F1ZXJ5YCBjb250YWlucyB0aGUgcXVlcnkgYW5kIHdlIHdpbGwgdXNlIGl0IHRvIGV4dHJhY3QgdGhlIHF1ZXJ5IGZlYXR1cmVzLCB3aGljaCBjYW4gYmUgZG9uZSBiZWZvcmUgV0RRUyBxdWVyeSBwcm9jZXNzaW5nLCBgZHRgIGlzIHRoZSB0aW1lc3RhbXAgd2hpY2ggY2FuIGJlIGtub3duIGJlZm9yZSB0aGUgcXVlcnkgaXMgcHJvY2Vzc2VkLCBob3dldmVyLCBgcmVzcG9uc2Vfc2l6ZWAgaXMgY2VydGFpbmx5IG5vdCBhdmFpbGFibGUgYmVmb3JlIHF1ZXJ5IHByb2Nlc3NpbmcgYW5kIGNhbm5vdCBiZSB1c2VkIGluIGFueSBpbWFnaW5hYmxlIFdEUVMgb3B0aW1pemF0aW9uIHN5c3RlbS4gU28sIHRoZSBmb2xsb3dpbmcgZmllbGRzIGFyZSB1c2VmdWwgZm9yIGV4cGxvcmF0b3J5IGRhdGEgYW5hbHlzaXMgb25seTogYGh0dHBfc3RhdHVzYCwgYGNhY2hlX3N0YXR1c2AsIGByZXNwb25zZV9zaXplYCwgYGFnZW50X3R5cGVgLCBgY29udGVudF90eXBlYCAobm90ZTogd2UgY2FuIGxlYXJuIGFib3V0IHRoaXMgdmFyaWFibGUgZnJvbSB0aGUgU1BBUlFMIHF1ZXJ5IHBhcmFtZXRlcnMgcHJlc2VudCBpbiB0aGUgYHVyaV9xdWVyeWAgZmllbGQpLCBgYWNjZXNzX21ldGhvZGAuCgoKYGBge3IgZWNobyA9IFQsIGV2YWwgPSBGLCBtZXNzYWdlID0gRn0KIyEvdXNyL2Jpbi9lbnYgUnNjcmlwdAoKIyMjIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQojIyMgLS0tIFdEX1dEUVNfQW5hbHl0aWNzX0NvbGxlY3REYXRhLlIsIHYuIEJldGEgMC4xCiMjIyAtLS0gU2NyaXB0OiBXRF9XRFFTX0FuYWx5dGljc19Db2xsZWN0RGF0YS5SLCB2LiBCZXRhIDAuMQojIyMgLS0tIERlc2NyaXB0aW9uOiBXRF9XRFFTX0FuYWx5dGljc19Db2xsZWN0RGF0YS5SIGNvbGxlY3RzIGEgc2FtcGxlIG9mIAojIyMgLS0tIFNQQVJRTCBxdWVyaWVzIGZyb20gd21mLndlYnJlcXVlc3QgaW4gdGhlIFdNRiBEYXRhIExha2UsCiMjIyAtLS0gdXJpX3BhdGggPSAiL3NwYXJxbCIgT1IgdXJpX3BhdGg9ICIvYmlnZGF0YS9uYW1lc3BhY2Uvd2RxL3NwYXJxbCIKIyMjIC0tLSBOT1RFLiAiL2JpZ2RhdGEvbmFtZXNwYWNlL3dkcS9zcGFycWwiIHJlZGlyZWN0cyB0byAiL3NwYXJxbCIgCiMjIyAtLS0gQXV0aG9yOiBHb3JhbiBTLiBNaWxvdmFub3ZpYywgRGF0YSBTY2llbnRpc3QsIFdNREUKIyMjIC0tLSBEZXZlbG9wZWQgdW5kZXIgdGhlIGNvbnRyYWN0IGJldHdlZW4gR29yYW4gTWlsb3Zhbm92aWMgUFIgRGF0YSBLb2xla3RpdgojIyMgLS0tIGFuZCBXTURFLgojIyMgLS0tIENvbnRhY3Q6IGdvcmFuLm1pbG92YW5vdmljX2V4dEB3aWtpbWVkaWEuZGUKIyMjIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQojIyMgLS0tIFJVTiBGUk9NOiAvaG9tZS9nb3JhbnNtL0FuYWx5dGljcy9XaWtpZGF0YS9XRF9taXNjL1dEX1NQQVJRTF9FbmRwb2ludF9BbmFseXRpY3MKIyMjIC0tLSBvbiBzdGF0MTAwNQojIyMgLS0tIERhdGU6IDIwMjAvMDMvMjAKIyMjIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQoKIyMjIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQojIyMgLS0tIExJQ0VOU0U6CiMjIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KIyMjIC0tLSBHUEwgdjIKIyMjIC0tLSBUaGlzIGZpbGUgaXMgcGFydCBvZiB0aGUgV2lraWRhdGEgU1BBUlFMIEVuZHBvaW50IEFuYWx5dGljcyBQcm9qZWN0CiMjIyAtLS0KIyMjIC0tLSBXaWtpZGF0YSBTUEFSUUwgRW5kcG9pbnQgQW5hbHl0aWNzIFByb2plY3QgaXMgZnJlZSBzb2Z0d2FyZTogCiMjIyAtLS0geW91IGNhbiByZWRpc3RyaWJ1dGUgaXQgYW5kL29yIG1vZGlmeQojIyMgLS0tIGl0IHVuZGVyIHRoZSB0ZXJtcyBvZiB0aGUgR05VIEdlbmVyYWwgUHVibGljIExpY2Vuc2UgYXMgcHVibGlzaGVkIGJ5CiMjIyAtLS0gdGhlIEZyZWUgU29mdHdhcmUgRm91bmRhdGlvbiwgZWl0aGVyIHZlcnNpb24gMiBvZiB0aGUgTGljZW5zZSwgb3IKIyMjIC0tLSAoYXQgeW91ciBvcHRpb24pIGFueSBsYXRlciB2ZXJzaW9uLgojIyMgLS0tCiMjIyAtLS0gV2lraWRhdGEgU1BBUlFMIEVuZHBvaW50IEFuYWx5dGljcyBQcm9qZWN0IGlzIGRpc3RyaWJ1dGVkIAojIyMgLS0tIGluIHRoZSBob3BlIHRoYXQgaXQgd2lsbCBiZSB1c2VmdWwsCiMjIyAtLS0gYnV0IFdJVEhPVVQgQU5ZIFdBUlJBTlRZOyB3aXRob3V0IGV2ZW4gdGhlIGltcGxpZWQgd2FycmFudHkgb2YKIyMjIC0tLSBNRVJDSEFOVEFCSUxJVFkgb3IgRklUTkVTUyBGT1IgQSBQQVJUSUNVTEFSIFBVUlBPU0UuICBTZWUgdGhlCiMjIyAtLS0gR05VIEdlbmVyYWwgUHVibGljIExpY2Vuc2UgZm9yIG1vcmUgZGV0YWlscy4KIyMjIC0tLQojIyMgLS0tIFlvdSBzaG91bGQgaGF2ZSByZWNlaXZlZCBhIGNvcHkgb2YgdGhlIEdOVSBHZW5lcmFsIFB1YmxpYyBMaWNlbnNlCiMjIyAtLS0gYWxvbmcgd2l0aCBXaWtpZGF0YSBTUEFSUUwgRW5kcG9pbnQgQW5hbHl0aWNzIFByb2plY3QuCiMjIyAtLS0gSWYgbm90LCBzZWUgPGh0dHA6Ly93d3cuZ251Lm9yZy9saWNlbnNlcy8+LgojIyMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCgojIyMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCiMjIyAtLS0gU2VjdGlvbiAxLiBDb2xsZWN0IHJhdyBkYXRhIHNldCBmcm9tIHdtZi53ZWJyZXF1ZXN0CiMjIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KCiMjIyAtLS0gc2V0dXAKbGlicmFyeShkYXRhLnRhYmxlKQpsaWJyYXJ5KHRpZHl2ZXJzZSkKCiMjIyAtLS0gZGlyZWN0b3JpZXMKIyMjIC0tLSBSZWFkIFdEQ00gcGFyYW1lcmV0ZXJzCiMgLSBmUGF0aDogd2hlcmUgdGhlIHNjcmlwdHMgaXMgcnVuIGZyb20/CmZQYXRoIDwtIGFzLmNoYXJhY3Rlcihjb21tYW5kQXJncyh0cmFpbGluZ09ubHkgPSBGQUxTRSlbNF0pCmZQYXRoIDwtIGdzdWIoIi0tZmlsZT0iLCAiIiwgZlBhdGgsIGZpeGVkID0gVCkKZlBhdGggPC0gdW5saXN0KHN0cnNwbGl0KGZQYXRoLCBzcGxpdCA9ICIvIiwgZml4ZWQgPSBUKSkKZlBhdGggPC0gcGFzdGUoCiAgcGFzdGUoZlBhdGhbMTpsZW5ndGgoZlBhdGgpIC0gMV0sIGNvbGxhcHNlID0gIi8iKSwKICAiLyIsCiAgc2VwID0gIiIpCmZQYXRoIDwtICcvaG9tZS9nb3JhbnNtL0FuYWx5dGljcy9XaWtpZGF0YS9XRF9taXNjL1dEX1NQQVJRTF9FbmRwb2ludF9BbmFseXRpY3MvJwpkYXRhRGlyIDwtIHBhc3RlMChmUGF0aCwgJ19kYXRhLycpCmFuYWx5dGljc0RpciA8LSBwYXN0ZTAoZlBhdGgsICdfYW5hbHl0aWNzLycpCmltZ0RpciA8LSBwYXN0ZTAoZlBhdGgsICdfaW1nLycpCgojIyMgLS0tIGdyYWIgYSBzYW1wbGUgb2YgU1BBUlFMIHF1ZXJpZXMgZnJvbSB3bWYud2VicmVxdWVzdAojIyMgLS0tIHcuIEhpdmVRTCBmcm9tIEJlZWxpbmUKIyAtIHF1ZXJ5IGZpbGVuYW1lCnF1ZXJ5RmlsZSA8LSAid2Rxc19Db2xsZWN0RGF0YS5ocWwiCiMgLSBLZXJiZXJvcyBJbml0CmtlcmJlcm9zUHJlZml4IDwtICdzdWRvIC11IGFuYWx5dGljcy1wcml2YXRlZGF0YSBrZXJiZXJvcy1ydW4tY29tbWFuZCBhbmFseXRpY3MtcHJpdmF0ZWRhdGEgJwojIC0gZGF0ZVJhbmdlCmVuZERhdGUgPC0gJzIwMjAvMDMvMjAnCnN0YXJ0RGF0ZSA8LSAnMjAyMC8wMy8wMScKZGF0ZVJhbmdlIDwtIHNlcShhcy5EYXRlKHN0YXJ0RGF0ZSksIGFzLkRhdGUoZW5kRGF0ZSksIGJ5ID0gImRheSIpCiMgLSBxdWVyeSBvdmVyIGRhdGVSYW5nZQpmb3IgKGkgaW4gMTpsZW5ndGgoZGF0ZVJhbmdlKSkgewogIHByaW50KHBhc3RlMCgiU3RhcnRlZDogIiwgZGF0ZVJhbmdlW2ldKSkKICAjIC0geWVhciwgbW9udGgsIGRhdGUKICB5ZWFyID0gc3Vic3RyKGRhdGVSYW5nZVtpXSwgMSwgNCkKICBtb250aCA9IHN1YnN0cihkYXRlUmFuZ2VbaV0sIDYsIDcpCiAgaWYgKHN1YnN0cihtb250aCwgMSwgMSkgPT0gJzAnKSB7bW9udGggPSBzdWJzdHIobW9udGgsIDIsIDIpfQogIGRheSA9IHN1YnN0cihkYXRlUmFuZ2VbaV0sIDksIDEwKQogIGlmIChzdWJzdHIoZGF5LCAxLCAxKSA9PSAnMCcpIHtkYXkgPSBzdWJzdHIoZGF5LCAyLCAyKX0KICAjIC0gY29tcG9zZSBxdWVyeQogIGhpdmVRTHF1ZXJ5IDwtIHBhc3RlMCgnU0VMRUNUIGR0LCB1cmlfcXVlcnksIGh0dHBfc3RhdHVzLCBjYWNoZV9zdGF0dXMsIHRpbWVfZmlyc3RieXRlLCByZXNwb25zZV9zaXplLCBhZ2VudF90eXBlLCBjb250ZW50X3R5cGUsIGFjY2Vzc19tZXRob2QKICAgICAgICAgICAgICAgICAgICAgICAgICBGUk9NIHdtZi53ZWJyZXF1ZXN0IFRBQkxFU0FNUExFIChCVUNLRVQgMSBPVVQgT0YgMTAwIE9OIHJhbmQoKSkgdwogICAgICAgICAgICAgICAgICAgICAgICAgIFdIRVJFICh1cmlfcGF0aCA9ICIvc3BhcnFsIiBPUiB1cmlfcGF0aD0gIi9iaWdkYXRhL25hbWVzcGFjZS93ZHEvc3BhcnFsIikKICAgICAgICAgICAgICAgICAgICAgICAgICBBTkQgd2VicmVxdWVzdF9zb3VyY2UgPSAidGV4dCIKICAgICAgICAgICAgICAgICAgICAgICAgICBBTkQgeWVhcj0nLCB5ZWFyLAogICAgICAgICAgICAgICAgICAgICAgICAnIEFORCBtb250aD0nLCBtb250aCwKICAgICAgICAgICAgICAgICAgICAgICAgJyBBTkQgZGF5PScsIGRheSwgJzsnKQogIAogIGZpbGVuYW1lIDwtICJ3ZHFzX2RhdGFfc2FtcGxlX1RFU1RfIgogIGZpbGVuYW1lIDwtIHBhc3RlMChmaWxlbmFtZSwgZGF0ZVJhbmdlW2ldLCAiLnRzdiIpCiAgIyAtIHdyaXRlIGhxbAogIHdyaXRlKGhpdmVRTHF1ZXJ5LCBwYXN0ZTAoZlBhdGgsIHF1ZXJ5RmlsZSkpCiAgIyAtIHRvIFJlcG9ydAogIHByaW50KCJGZXRjaGluZyBzcGFycWxfdGVzdERhdGEgZnJvbSB3bWYud2VicmVxdWVzdCBub3cuIikKICAjIC0gUnVuIHF1ZXJ5CiAgcXVlcnkgPC0gc3lzdGVtKGNvbW1hbmQgPSBwYXN0ZShrZXJiZXJvc1ByZWZpeCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnL3Vzci9sb2NhbC9iaW4vYmVlbGluZSAtLWluY3JlbWVudGFsPXRydWUgLS1zaWxlbnQgLWYgIicsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwYXN0ZTAoZlBhdGgsIHF1ZXJ5RmlsZSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnIiA+ICcsIGRhdGFEaXIsIGZpbGVuYW1lLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2VwID0gIiIpLAogICAgICAgICAgICAgICAgICB3YWl0ID0gVFJVRSkKICBwcmludChwYXN0ZTAoIkNvbXBsZXRlZDogIiwgZGF0ZVJhbmdlW2ldKSkKICBwcmludCgiLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0iKQp9CiMgLSB0byBSZXBvcnQKcHJpbnQoIkRPTkUgdy4gRVRMIGZyb20gSGFkb29wOiB3bWYud2VicmVxdWVzdC4iKQpgYGAKCgojIyAxLiBEYXRhIENsZWFuaW5nIGFuZCBGZWF0dXJlIEVuZ2luZWVyaW5nIAoKIyMjIDEuMSBEYXRhIENsZWFuaW5nCgpUaGUgZmlyc3Qgc3RlcCBmb2xsb3dpbmcgdGhlIHNhbXBsZSBvZiBTUEFSUUwgcXVlcmllcyBpcyBvYnRhaW5lZCBmcm9tIGB3bWYud2VicmVxdWVzdGAgaXMgdG8gY2hlY2sgdGhlIGRhdGEgZm9yIGNvbnNpc3RlbmN5IGFuZCBgdXJsdG9vbHM6OnVybF9kZWNvZGUoKWAgZGVjb2RlIHRoZSBxdWVyaWVzIChSIHt1cmx0b29sc30gcGFja2FnZSB3YXMgdXNlZCkuIFRoZSBmb2xsb3dpbmcgY2h1bmsgb2YgY29kZSBhbHNvIHBhcnNlcyB0aGUgYGR0YCAodGltZXN0YW1wKSBmaWVsZCBhbmQgZXh0cmFjdHMgYHllYXJgLCBgbW9udGhgLCBgZGF5YCwgYGhvdXJgLCBhbmQgYG1pbnV0ZWAgd2hlbiB0aGUgcXVlcnkgd2FzIHJ1bi4gQWxzbywgYW5kIGFkZGl0aW9uYWwgdmFyaWFibGUgaXMgcHJvZHVjZWQgZnJvbSBhIHNpbXBsZSBjb25jYXRlbmF0aW9uIG9mIGB5ZWFyYCwgYG1vbnRoYCwgYGRheWAsIGBob3VyYCwgYW5kIGBtaW51dGVgOiBgeW1kbWhgLiBUaGlzIHZhcmlhYmxlIHdpbGwgYmUgc3Vic2VxdWVudGx5IHVzZWQgdG8gY2FsY3VsYXRlIHRoZSBudW1iZXIgb2YgcXVlcmllcyB0aGF0IHdlcmUgcnVuIGluIGEgcGFydGljdWxhciBtaW51dGUgb2JzZXJ2ZWQgaW4gdGhlIHNhbXBsZS4gVGhpcyBpcyBhIHBvdGVudGlhbCAiZW52aXJvbm1lbnRhbCIgdmFyaWFibGUgKGkuZS4gdGFrZW4gZnJvbSB0aGUgV0RRUy9CbGF6ZWdyYXBoIGVudmlvcm5tZW50KSBpbiBwcmVkaWN0aXZlIG1vZGVsaW5nIG9mIHNlcnZlciByZXNwb25zZSB0aW1lLiBJdCBjYW4gY2hhcmFjdGVyaXplIHRoZSBxdWVyeSBwcm9jZXNzaW5nIGNvbnRleHQgYnkgZXN0aW1hdGluZyBob3cgbWFueSBxdWVyaWVzIHdlcmUgcnVuIGFnYWluc3QgQmxhemVncmFwaCBjb25jdXJyZW50bHkgaW4gdGhlIHNhbWUgbWludXRlIHdoZW4gYSBwYXJ0aWN1bGFyIHF1ZXJ5IHVuZGVyIG9mIGludGVyZXN0IHdhcyBydW4uIFRoZXJlIGFyZSBwb3NzaWJsZSByZWZpbm1lbnRzIG9mIHRoaXMgdmFyaWFibGUgdGhhdCB3ZXJlIG5vdCBleHBsb3JlZCBpbiB0aGlzIHBpbG90IHN0dWR5LiBBbnkgZnV0dXJlIFdEUVMgb3B0aW1pemF0aW9uIHN5c3RlbSByZWx5aW5nIG9uIHRoaXMgdmFyaWFibGUgd291bGQgaGF2ZSB0byByZWx5IG9uIEJsYXplZ3JhcGggYnJvYWRjYXN0aW5nIHRoaXMgdmFyaWFibGUgdG8gdGhlIHByZWRpY3RpdmUgbW9kZWxpbmcgQVBJLgoKKipOT1RFLioqIEluIHRoaXMgc3RlcCB3ZSBlbGltaW5hdGUgYWxsIHF1ZXJpZXMgdGhhdCBoYXZlIHJlc3VsdGVkIGluIGEgYGh0dHBfc3RhdHVzYCBvZiBgNCoqYDogY2xpZW50IHNpZGUgZXJyb3JzLiBPYnZpb3VzbHksIGluY29ycmVjdGx5IHN0YXRlZCBTUEFSUUwgcXVlcmllcyBhcmUgb2Ygbm8gdXNlIHRvIHVzIGluIGFueSBhdHRlbXB0IHRvIG9wdGltaXplIHRoZSBXRFFTIHByb2Nlc3NpbmcgdGltZS4gU28gaWYgYSBxdWVyeSBmYWlscyBhbiBpbml0aWFsIHN5bnRheCBhbmFseXNpcyBvbiB0aGUgZW5kcG9pbnQgaXQgZG9lcyBub3QgZW50ZXIgb3VyIGFuYWx5c2lzLgoKYGBge3IgZWNobyA9IFQsIGV2YWwgPSBGLCBtZXNzYWdlID0gRn0KCiMhL3Vzci9iaW4vZW52IFJzY3JpcHQKCiMjIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KIyMjIC0tLSBXRF9XRFFTX0FuYWx5dGljc19DbGVhbl9JbnNwZWN0X0RhdGFfRURBLlIsIHYuIEJldGEgMC4xCiMjIyAtLS0gU2NyaXB0OiBXRF9XRFFTX0FuYWx5dGljc19GZWF0dXJlRW5naW5lZXJpbmcuUiwgdi4gQmV0YSAwLjEKIyMjIC0tLSBEZXNjcmlwdGlvbjogV0RfV0RRU19BbmFseXRpY3NfRmVhdHVyZUVuZ2luZWVyaW5nLlIgcGVyZm9ybXMgCiMjIyAtLS0gZmVhdHVyZSBlbmdpbmVlcmluZyBvZiB0aGUgU1BBUlFMIHF1ZXJpZXMgZGF0YSAKIyMjIC0tLSBjb2xsZWN0ZWQgdy4gV0RfV0RRU19BbmFseXRpY3NfQ29sbGVjdERhdGEuUiBmcm9tIHRoZSAKIyMjIC0tLSB3bWYud2VicmVxdWVzdCB0YWJsZSBpbiB0aGUgV01GIERhdGEgTGFrZS4KIyMjIC0tLSBBdXRob3I6IEdvcmFuIFMuIE1pbG92YW5vdmljLCBEYXRhIFNjaWVudGlzdCwgV01ERQojIyMgLS0tIERldmVsb3BlZCB1bmRlciB0aGUgY29udHJhY3QgYmV0d2VlbiBHb3JhbiBNaWxvdmFub3ZpYyBQUiBEYXRhIEtvbGVrdGl2CiMjIyAtLS0gYW5kIFdNREUuCiMjIyAtLS0gQ29udGFjdDogZ29yYW4ubWlsb3Zhbm92aWNfZXh0QHdpa2ltZWRpYS5kZQojIyMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCiMjIyAtLS0gUlVOIEZST006IC9ob21lL2dvcmFuc20vQW5hbHl0aWNzL1dpa2lkYXRhL1dEX21pc2MvV0RfU1BBUlFMX0VuZHBvaW50X0FuYWx5dGljcwojIyMgLS0tIG9uIHN0YXQxMDA1CiMjIyAtLS0gRGF0ZTogMjAyMC8wMy8yMAojIyMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCgojIyMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCiMjIyAtLS0gTElDRU5TRToKIyMjIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQojIyMgLS0tIEdQTCB2MgojIyMgLS0tIFRoaXMgZmlsZSBpcyBwYXJ0IG9mIHRoZSBXaWtpZGF0YSBTUEFSUUwgRW5kcG9pbnQgQW5hbHl0aWNzIFByb2plY3QKIyMjIC0tLQojIyMgLS0tIFdpa2lkYXRhIFNQQVJRTCBFbmRwb2ludCBBbmFseXRpY3MgUHJvamVjdCBpcyBmcmVlIHNvZnR3YXJlOiAKIyMjIC0tLSB5b3UgY2FuIHJlZGlzdHJpYnV0ZSBpdCBhbmQvb3IgbW9kaWZ5CiMjIyAtLS0gaXQgdW5kZXIgdGhlIHRlcm1zIG9mIHRoZSBHTlUgR2VuZXJhbCBQdWJsaWMgTGljZW5zZSBhcyBwdWJsaXNoZWQgYnkKIyMjIC0tLSB0aGUgRnJlZSBTb2Z0d2FyZSBGb3VuZGF0aW9uLCBlaXRoZXIgdmVyc2lvbiAyIG9mIHRoZSBMaWNlbnNlLCBvcgojIyMgLS0tIChhdCB5b3VyIG9wdGlvbikgYW55IGxhdGVyIHZlcnNpb24uCiMjIyAtLS0KIyMjIC0tLSBXaWtpZGF0YSBTUEFSUUwgRW5kcG9pbnQgQW5hbHl0aWNzIFByb2plY3QgaXMgZGlzdHJpYnV0ZWQgCiMjIyAtLS0gaW4gdGhlIGhvcGUgdGhhdCBpdCB3aWxsIGJlIHVzZWZ1bCwKIyMjIC0tLSBidXQgV0lUSE9VVCBBTlkgV0FSUkFOVFk7IHdpdGhvdXQgZXZlbiB0aGUgaW1wbGllZCB3YXJyYW50eSBvZgojIyMgLS0tIE1FUkNIQU5UQUJJTElUWSBvciBGSVRORVNTIEZPUiBBIFBBUlRJQ1VMQVIgUFVSUE9TRS4gIFNlZSB0aGUKIyMjIC0tLSBHTlUgR2VuZXJhbCBQdWJsaWMgTGljZW5zZSBmb3IgbW9yZSBkZXRhaWxzLgojIyMgLS0tCiMjIyAtLS0gWW91IHNob3VsZCBoYXZlIHJlY2VpdmVkIGEgY29weSBvZiB0aGUgR05VIEdlbmVyYWwgUHVibGljIExpY2Vuc2UKIyMjIC0tLSBhbG9uZyB3aXRoIFdpa2lkYXRhIFNQQVJRTCBFbmRwb2ludCBBbmFseXRpY3MgUHJvamVjdC4KIyMjIC0tLSBJZiBub3QsIHNlZSA8aHR0cDovL3d3dy5nbnUub3JnL2xpY2Vuc2VzLz4uCiMjIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KCiMjIyAtLS0gc2V0dXAKbGlicmFyeShkYXRhLnRhYmxlKQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeSh1cmx0b29scykKCiMjIyAtLS0gZGlyZWN0b3JpZXMKIyMjIC0tLSBSZWFkIFdEQ00gcGFyYW1lcmV0ZXJzCiMgLSBmUGF0aDogd2hlcmUgdGhlIHNjcmlwdHMgaXMgcnVuIGZyb20/CmZQYXRoIDwtIGFzLmNoYXJhY3Rlcihjb21tYW5kQXJncyh0cmFpbGluZ09ubHkgPSBGQUxTRSlbNF0pCmZQYXRoIDwtIGdzdWIoIi0tZmlsZT0iLCAiIiwgZlBhdGgsIGZpeGVkID0gVCkKZlBhdGggPC0gdW5saXN0KHN0cnNwbGl0KGZQYXRoLCBzcGxpdCA9ICIvIiwgZml4ZWQgPSBUKSkKZlBhdGggPC0gcGFzdGUoCiAgcGFzdGUoZlBhdGhbMTpsZW5ndGgoZlBhdGgpIC0gMV0sIGNvbGxhcHNlID0gIi8iKSwKICAiLyIsCiAgc2VwID0gIiIpCmZQYXRoIDwtICcvaG9tZS9nb3JhbnNtL0FuYWx5dGljcy9XaWtpZGF0YS9XRF9taXNjL1dEX1NQQVJRTF9FbmRwb2ludF9BbmFseXRpY3MvJwoKZGF0YURpciA8LSBwYXN0ZTAoZlBhdGgsICdfZGF0YS8nKQphbmFseXRpY3NEaXIgPC0gcGFzdGUwKGZQYXRoLCAnX2FuYWx5dGljcy8nKQppbWdEaXIgPC0gcGFzdGUwKGZQYXRoLCAnX2ltZy8nKQoKIyMjIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQojIyMgLS0tIFNlY3Rpb24gMS4gTG9hZCwgY2xlYW4sIGFuZCBkZWNvZGUgdGhlIFNQQVJRTCBxdWVyaWVzIGRhdGEgc2V0CiMjIyAtLS0gTk9URS4gTWluZCB0aGUgcHJpdmF0ZSBkYXRhICghKQojIyMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCgojIyMgLS0tIFN5cy5zZXRsb2NhbGUoKQpTeXMuc2V0bG9jYWxlKCdMQ19BTEwnLCdDJykKCiMjIyAtLS0gbG9hZCBkYXRhCmxGIDwtIGxpc3QuZmlsZXMoZGF0YURpcikKbEYgPC0gbEZbZ3JlcGwoIl53ZHFzX2RhdGFfc2FtcGxlIiwgbEYpXQpsRiA8LSBsRlshZ3JlcGwoIlRFU1QiLCBsRildCmRhdGFTZXQgPC0gbGFwcGx5KHBhc3RlMChkYXRhRGlyLCBsRiksIGZyZWFkKQpkYXRhU2V0IDwtIHJiaW5kbGlzdChkYXRhU2V0KQpkaW0oZGF0YVNldCkKCiMjIyAtLS0gY2xlYW4gZGF0YTogaHR0cF9zdGF0dXMKdGFibGUoZGF0YVNldCRodHRwX3N0YXR1cykKIyAtIGluc3BlY3QgaHR0cF9zdGF0dXMgeCB0aW1lX2ZpcnN0Ynl0ZQpkYXRhU2V0ICU+JSAKICBkcGx5cjo6c2VsZWN0KGh0dHBfc3RhdHVzLCB0aW1lX2ZpcnN0Ynl0ZSkgJT4lIAogIGRwbHlyOjpncm91cF9ieShodHRwX3N0YXR1cykgJT4lIAogIGRwbHlyOjpzdW1tYXJpc2UobWVhbiA9IG1lYW4odGltZV9maXJzdGJ5dGUsIG5hLnJtID0gVCksIAogICAgICAgICAgICAgICAgICAgbWVkaWFuID0gbWVkaWFuKHRpbWVfZmlyc3RieXRlLCBuYS5ybSA9IFQpLCAKICAgICAgICAgICAgICAgICAgIG1pbiA9IG1pbih0aW1lX2ZpcnN0Ynl0ZSwgbmEucm0gPSBUKSwgCiAgICAgICAgICAgICAgICAgICBtYXggPSBtYXgodGltZV9maXJzdGJ5dGUsIG5hLnJtID0gVCkpCiMgLSBOT1RFLiBodHRwX3N0YXR1cyA9PSAiLSIgaGFzIG1pbiA9PSAwLCBtYXggPT0gMCwgbWVhbiA9PSAwLCBtZWRpYW4gPT0gMAojIC0gUmVtb3ZlIHF1ZXJpZXMgdGhhdCBmYWlsZWQgZm9yIGNsaWVudCBlcnJvcnM6IGh0dHBfc3RhdHVzID09IDQqKgp3IDwtIHdoaWNoKGdyZXBsKCJeNCIsIGRhdGFTZXQkaHR0cF9zdGF0dXMpKQppZiAobGVuZ3RoKHcpID4gMCkgewogIGRhdGFTZXQgPC0gZGF0YVNldFstdywgXQp9CgojIyMgLS0tIGNsZWFuIGRhdGE6IGNhY2hlX3N0YXR1cwp0YWJsZShkYXRhU2V0JGNhY2hlX3N0YXR1cykKIyAtIGluc3BlY3QgY2FjaGVfc3RhdHVzIHggdGltZV9maXJzdGJ5dGUKZGF0YVNldCAlPiUgCiAgZHBseXI6OnNlbGVjdChjYWNoZV9zdGF0dXMsIHRpbWVfZmlyc3RieXRlKSAlPiUgCiAgZHBseXI6Omdyb3VwX2J5KGNhY2hlX3N0YXR1cykgJT4lIAogIGRwbHlyOjpzdW1tYXJpc2UobWVhbiA9IG1lYW4odGltZV9maXJzdGJ5dGUsIG5hLnJtID0gVCksIAogICAgICAgICAgICAgICAgICAgbWVkaWFuID0gbWVkaWFuKHRpbWVfZmlyc3RieXRlLCBuYS5ybSA9IFQpLCAKICAgICAgICAgICAgICAgICAgIG1pbiA9IG1pbih0aW1lX2ZpcnN0Ynl0ZSwgbmEucm0gPSBUKSwgCiAgICAgICAgICAgICAgICAgICBtYXggPSBtYXgodGltZV9maXJzdGJ5dGUsIG5hLnJtID0gVCkpCiMgLSBOT1RFLiBjYWNoZV9zdGF0dXMgPT0gIi0iIGhhcyBtaW4gPT0gMCwgbWF4ID09IDAsIG1lYW4gPT0gMCwgbWVkaWFuID09IDAKCiMjIyAtLS0gY2xlYW4gZGF0YTogYWdlbnRfdHlwZQp0YWJsZShkYXRhU2V0JGFnZW50X3R5cGUpCiMgLSBpbnNwZWN0IGFnZW50X3R5cGUgeCB0aW1lX2ZpcnN0Ynl0ZQpkYXRhU2V0ICU+JSAKICBkcGx5cjo6c2VsZWN0KGFnZW50X3R5cGUsIHRpbWVfZmlyc3RieXRlKSAlPiUgCiAgZHBseXI6Omdyb3VwX2J5KGFnZW50X3R5cGUpICU+JSAKICBkcGx5cjo6c3VtbWFyaXNlKG1lYW4gPSBtZWFuKHRpbWVfZmlyc3RieXRlLCBuYS5ybSA9IFQpLCAKICAgICAgICAgICAgICAgICAgIG1lZGlhbiA9IG1lZGlhbih0aW1lX2ZpcnN0Ynl0ZSwgbmEucm0gPSBUKSwgCiAgICAgICAgICAgICAgICAgICBtaW4gPSBtaW4odGltZV9maXJzdGJ5dGUsIG5hLnJtID0gVCksIAogICAgICAgICAgICAgICAgICAgbWF4ID0gbWF4KHRpbWVfZmlyc3RieXRlLCBuYS5ybSA9IFQpKQoKIyMjIC0tLSBjbGVhbiBkYXRhOiBjb250ZW50X3R5cGUKIyAtIHJlY29kZSBjb250ZW50X3R5cGUKZGF0YVNldCRjb250ZW50X3R5cGUgPC0gc2FwcGx5KGRhdGFTZXQkY29udGVudF90eXBlLCBmdW5jdGlvbih4KSB7CiAgZCA8LSBnc3ViKCJjaGFyc2V0PSIsICIiLCB4KQogIGQgPC0gZ3N1YigidGFiLXNlcGFyYXRlZC12YWx1ZXMiLCAidHN2IiwgZCkKICByZXR1cm4oZCkKfSkKdGFibGUoZGF0YVNldCRjb250ZW50X3R5cGUpCiMgLSBpbnNwZWN0IGNvbnRlbnRfdHlwZSB4IHRpbWVfZmlyc3RieXRlCmRhdGFTZXQgJT4lIAogIGRwbHlyOjpzZWxlY3QoY29udGVudF90eXBlLCB0aW1lX2ZpcnN0Ynl0ZSkgJT4lIAogIGRwbHlyOjpncm91cF9ieShjb250ZW50X3R5cGUpICU+JSAKICBkcGx5cjo6c3VtbWFyaXNlKG1lYW4gPSBtZWFuKHRpbWVfZmlyc3RieXRlLCBuYS5ybSA9IFQpLCAKICAgICAgICAgICAgICAgICAgIG1lZGlhbiA9IG1lZGlhbih0aW1lX2ZpcnN0Ynl0ZSwgbmEucm0gPSBUKSwgCiAgICAgICAgICAgICAgICAgICBtaW4gPSBtaW4odGltZV9maXJzdGJ5dGUsIG5hLnJtID0gVCksIAogICAgICAgICAgICAgICAgICAgbWF4ID0gbWF4KHRpbWVfZmlyc3RieXRlLCBuYS5ybSA9IFQpKQoKIyMjIC0tLSBjbGVhbiBkYXRhOiBhY2Nlc3NfbWV0aG9kCnRhYmxlKGRhdGFTZXQkYWNjZXNzX21ldGhvZCkKIyAtIE5PVEUuIEFsbW9zdCBhbHdheXMgZGF0YVNldCRhY2Nlc3NfbWV0aG9kID09ICJkZXNrdG9wIgoKIyMjIC0tLSB2YWxpZGF0ZSBkdCAodGltZXN0YW1wKQp3IDwtIHdoaWNoKGRhdGFTZXQkZHQgPT0gIi0iKQpsZW5ndGgodykKdyA8LSB3aGljaCghKGdyZXBsKCJcXGRcXGRcXGRcXGQtXFxkXFxkLVxcZFxcZFRcXGRcXGQ6XFxkXFxkOlxcZFxcZFoiLCAKICAgICAgICAgICAgICAgICBkYXRhU2V0JGR0KSkpCmxlbmd0aCh3KQojIyMgLS0tIGV4dHJhY3QgeWVhciwgbW9udGgsIGRheSwgaG91ciwgYW5kIG1pbnV0ZSBmcm9tIGR0ICh0aW1lc3RhbXApCmRhdGFTZXQkeWVhciA8LSBzdWJzdHIoZGF0YVNldCRkdCwgMSwgNCkKZGF0YVNldCRtb250aCA8LSBzdWJzdHIoZGF0YVNldCRkdCwgNiwgNykKZGF0YVNldCRkYXkgPC0gc3Vic3RyKGRhdGFTZXQkZHQsIDksIDEwKQpkYXRhU2V0JGhvdXIgPC0gc3Vic3RyKGRhdGFTZXQkZHQsIDEyLCAxMykKZGF0YVNldCRtaW51dGUgPC0gc3Vic3RyKGRhdGFTZXQkZHQsIDE1LCAxNikKZGF0YVNldCR5bWRobSA8LSBwYXN0ZShkYXRhU2V0JHllYXIsIAogICAgICAgICAgICAgICAgICAgICAgIGRhdGFTZXQkbW9udGgsCiAgICAgICAgICAgICAgICAgICAgICAgZGF0YVNldCRkYXksCiAgICAgICAgICAgICAgICAgICAgICAgZGF0YVNldCRob3VyLAogICAgICAgICAgICAgICAgICAgICAgIGRhdGFTZXQkbWludXRlLAogICAgICAgICAgICAgICAgICAgICAgIHNlcCA9ICItIikKZGltKGRhdGFTZXQpCgojIyMgLS0tIGNsZWFuIGRhdGE6IHVyaV9xdWVyeSAKIyMjIC0tLSBwcmVwYXJlIFNQQVJRTCBxdWVyaWVzIGZvciBwYXJzaW5nCiMgLSBkZWNvZGUgYW5kIGNsZWFuIHVyaV9xdWVyeQpkYXRhU2V0JHVyaV9xdWVyeSA8LSB1cmx0b29sczo6dXJsX2RlY29kZShkYXRhU2V0JHVyaV9xdWVyeSkKIyAtIGNoZWNrOiBkYXRhU2V0JHVyaV9xdWVyeSA9PSAiIgp3IDwtIHdoaWNoKGRhdGFTZXQkdXJpX3F1ZXJ5ID09ICIiKQppZiAobGVuZ3RoKHcpID4gMCkgewogIGRhdGFTZXQgPC0gZGF0YVNldFstdywgXQp9CmRpbShkYXRhU2V0KQojIC0gY2hlY2s6IGlzLm5hKGRhdGFTZXQkdXJpX3F1ZXJ5KQp3IDwtIHdoaWNoKGlzLm5hKGRhdGFTZXQkdXJpX3F1ZXJ5KSkKaWYgKGxlbmd0aCh3KSA+IDApIHsKICBkYXRhU2V0IDwtIGRhdGFTZXRbLXcsIF0KfQpkaW0oZGF0YVNldCkKdyA8LSB3aGljaChncmVwbCgiXlxccyskIiwgZGF0YVNldCR1cmlfcXVlcnkpKQppZiAobGVuZ3RoKHcpID4gMCkgewogIGRhdGFTZXQgPC0gZGF0YVNldFstdywgXQp9CmRpbShkYXRhU2V0KQoKIyMjIC0tLSBhZGQgSUQsIHVuaXF1ZVF1ZXJ5SUQKZGF0YVNldCRpZCA8LSAxOmRpbShkYXRhU2V0KVsxXQp1bmlxdWVRdWVyaWVzIDwtIHVuaXF1ZShkYXRhU2V0JHVyaV9xdWVyeSkKdW5pcXVlUXVlcmllcyA8LSBkYXRhLmZyYW1lKHVyaV9xdWVyeSA9IHVuaXF1ZVF1ZXJpZXMsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgdW5pcXVlUXVlcnlJZCA9IDE6bGVuZ3RoKHVuaXF1ZVF1ZXJpZXMpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGKQpkYXRhU2V0IDwtIGRhdGFTZXQgJT4lIAogIGRwbHlyOjpsZWZ0X2pvaW4odW5pcXVlUXVlcmllcywgCiAgICAgICAgICAgICAgICAgICBieSA9ICJ1cmlfcXVlcnkiKQpybSh1bmlxdWVRdWVyaWVzKQoKIyMjIC0tLSByZWFycmFuZ2UsIHN0b3JlIGNsZWFuIGRhdGEgc2V0CmNvbG5hbWVzKGRhdGFTZXQpCmRhdGFTZXQgPC0gZGF0YVNldCAlPiUgCiAgZHBseXI6OnNlbGVjdCgnaWQnLCAndW5pcXVlUXVlcnlJZCcsICdkdCcsICd5ZWFyJywgJ21vbnRoJywgJ2RheScsICdob3VyJywgJ21pbnV0ZScsCiAgICAgICAgICAgICAgICAgICAgICAgJ3VyaV9xdWVyeScsICdodHRwX3N0YXR1cycsICdjYWNoZV9zdGF0dXMnLCAndGltZV9maXJzdGJ5dGUnLCAKICAgICAgICAgICAgICAgICAgICAgICAncmVzcG9uc2Vfc2l6ZScsICdhZ2VudF90eXBlJywgJ2NvbnRlbnRfdHlwZScsICdhY2Nlc3NfbWV0aG9kJykKZmlsZW5hbWUgPC0gcGFzdGUwKGFuYWx5dGljc0RpciwgCiAgICAgICAgICAgICAgICAgICAnd2Rxc19kYXRhX3NhbXBsZV8nLCAKICAgICAgICAgICAgICAgICAgIHN1YnN0cihhcy5jaGFyYWN0ZXIoU3lzLnRpbWUoKSksIDEsIDEwKSwgCiAgICAgICAgICAgICAgICAgICAnLmNzdicpCndyaXRlLmNzdihkYXRhU2V0LCBmaWxlbmFtZSkKYGBgCgojIyMgMS4yIEZlYXVyZSBFbmdpbmVlcmluZwoKVGhlIFNQQVJRTCBxdWVyaWVzIGluIG91ciBzYW1wbGUsIHByZXNlbnQgaW4gdGhlIGB1cmlfcXVlcnlgIGZpZWxkIHJldHJpZXZlZCBmcm9tIHRoZSBgd21mLndlYnJlcXVlc3RgIHRhYmxlLCBuZWVkIHRvIGJlIHBhcnNlZCBpbiBvcmRlciB0byBleHRyYWN0IGEgc2V0IG9mIG1lYW5pbmdmdWwgZmVhdHVyZXMgdG8gdXNlIGluIHByZWRpY3RpdmUgbW9kZWxpbmcgb2Ygc2VydmVyIHJlc3BvbnNlIHRpbWUuIEFzIGluIHRoZSBjYXNlIG9mIE5hdHVyYWwgTGFuZ3VhZ2UgUHJvY2Vzc2luZywgd2UgbmVlZCB0byBkaWZmZXJlbnRpYXRlIGJldHdlZW4gdGhyZWUgcG9zc2libGUgbGV2ZWxzIG9mIGFuYWx5c2lzIGhlcmU6CgotICoqc3ludGF4Kio6IHRoZSBjb21wb3NpdGlvbiBvZiBTUEFSUUwga2V5d29yZHMsIG9wZXJhdG9ycywgdmFyaWFibGVzLCBhbmQgZnVuY3Rpb25zIHJlc3VsdCBpbiBhIHNwZWNpZmljIGRlc2NyaXB0aW9uIG9mIHRoZSBkYXRhIHRoYXQgbmVlZCB0byBiZSBvYnRhaW5lZCwgYW5kIHRoZWlyIGFuYWx5c2lzIG9uIGJlaGFsZiBvZiBCbGF6ZWdyYXBoIGRldGVybWluZXMgdGhlIHF1ZXJ5IGV4ZWN1dGlvbiBwbGFuICh2aWEgQWJzdHJhY3QgU3ludGF4IFRyZWUsIEFTVCwgZGVyaXZlZCBmcm9tIHF1ZXJ5IGFuYWx5c2lzKTsgZGV0ZXJtaW5lcyBxdWVyeSB2YWxpZGl0eSBhbmQgcXVlcnkgZXhlY3V0aW9uIHBsYW47Ci0gKipzZW1hbnRpY3MqKjogdGhlIGV4YWN0IG1hcHBpbmcgb2YgcXVlcnkgc3ludGF4IHRvIHBhcnRpY3VsYXIgdmFyaWFibGVzIHRoYXQgbmVlZCB0byBiZSBpbnN0YW50aWF0ZWQgaW4gb3JkZXIgdG8gb2J0YWluIHRoZSBkZXNpcmVkIHJlc3VsdCBmcm9tIFdEUVM7IHRoZW9yZXRpY2FsbHksIGl0IGRldGVybWluZXMgdHJ1dGggcmVsYXRpb25zOyBpbiBwcmFjdGljZSwgd2UgY2FuIHNheSB0aGF0IGl0IHByZWNpc2VseSBzcGVjaWZpZXMgdGhlIGV4cGVjdGVkIHF1ZXJ5IHJlc3VsdDsgCi0gKipwcmFnbWF0aWNzKio6IGdpdmVuIHRoZSBTUEFSUUwgbGFuZ3VhZ2UgY29udmVudGlvbnMgYXJlIHNhdGlzZmllZCwgYW55IHF1ZXJ5IG1pZ3RoIGluc3RhbnRpYXRlIHZhcmlvdXMgdmFyaWFibGUgbmFtZXMsIHVzZSAobW9yZSBvciBsZXNzIGVmZmljaWVudGx5KSB2YXJpb3VzIHN5bnRhY3RpYyBjb25zdHJ1Y3Rpb25zIHRvIG9idGFpbiB0aGUgc2FtZSBlZmZlY3QsIGluY2x1ZGUgY29kZSBjb21tZW50cywgZXRjLjogdGhlIHdheSB1c2VycyAqdXNlKiB0aGUgU1BBUlFMIHF1ZXJ5IGxhbmd1YWdlIHRvIGNvbW11bmljYXRlIHRoZWlyIGdvYWxzIHRvIHRoZSBXRFFTLgoKT3VyIGFwcHJvYWNoIHRvIGZlYXR1cmUgZW5naW5lZXJpbmcgaXMgdG8gcGFyc2UgZWFjaCBTUEFSUUwgcXVlcnkgd2l0aCBhIHNldCBvZiByZWd1bGFyIGV4cHJlc3Npb25zIGluIG9yZGVyIHRvIChhKSBzaW5nbGUgb3V0IHRoZSAiYXRvbWljIGVsZW1lbnRzIiBvZiB0aGUgU1BBUlFMIGxhbmd1YWdlIChrZXl3b3JkcywgZnVuY3Rpb25zLCBJUkkgbWVudGlvbnMsIHZhcmlhYmxlcywgbGl0ZXJhbHMsIG9wZXJhdG9ycywgV2lraWJhc2UgcGFyYW1ldGVycywgZXRjLiksIChiKSBjb21wdXRlIGEgc2V0IG9mIGFic3RyYWN0IHZhcmlhYmxlcyB0aGF0IGNhbiBiZSB1c2VkIHRvIGRlc2NyaWJlIHRoZSBxdWVyeSwgZS5nLjogdGhlIG51bWJlciBvZiBkaWZmZXJlbnQgdmFyaWFibGVzIGluc3RhbnRpYXRlZCAoaS5lLiBob3cgbWFueSB1bmlxdWUgdmFyaWFibGVzIHRoZXJlIGFyZSBpbiBhIHF1ZXJ5KSwgdGhlIHRvdGFsIG51bWJlciBvZiB2YXJpYWJsZXMgbWVudGlvbmVkIChpbmNsdWRpbmcgcmVwZWF0ZWQgdXNlcyBvZiB0aGUgc2FtZSB2YXJpYWJsZSksIHRoZSBudW1iZXIgb2YgSVJJIHJlZmVyZW5jZXMgaW4gYSBxdWVyeSwgaG93IG1hbnkgdmFyaWFibGVzIGFsc28gcmVxdWVzdCBsYWJlbHMsIGFuZCBzaW1pbGFyLgoKV2l0aCB0aGlzIGFwcHByb2FjaCwgd2hpY2ggaXMgZXNzZW50aWFseSB0aGUgc2FtZSBhcyB3aGF0IGlzIHR5cGljYWxseSBkb25lIGluIE5hdHVyYWwgTGFuZ3VhZ2UgUHJvY2Vzc2luZywgcXVlcnkgc3ludGF4IGFuZCBzZW1hbnRpY3MgYXJlIGluIGVmZmVjdCBmdXNlZC4gQSBxdWVzdGlvbiBjb3VsZCBiZSBwb3NlZDogd2h5IGtlZXAgc3BlY2lmaWMgdmFyaWFibGUgbmFtZXMsIG9yIGNvbW1lbnRzLCB3aGVuIGl0IGlzIGNsZWFyIHRoYXQgdGhlIGxhdGVyIGFyZSBub3QgZXZlbiBwcm9jZXNzZWQgd2hpbGUgdmFyaWFibGUgbmFtZXMgYXJlIGNvbnZlbnRpb25hbCBhbmQgV0RRUyB1c2VycyBhcmUgZnJlZSB0byB1c2UgdGhlbSBhcyBsb25nIGFzIHRoZXkgc2F0aXNmeSB0aGUgcnVsZXMgb2YgdGhlIFNQQVJRTCBzeW50YXg/IFRoZXNlIGFzcGVjdHMgb2YgcXVlcnkgYW5hbHlzaXMgYXJlIGltcG9ydGFudCBmcm9tIHRoZSB2aWV3cG9pbnQgb2YgcXVlcnkgKnByYWdtYXRpY3MqIGFuZCBjYXJyeSB1c2VmdWwgaW5mb3JtYXRpb24gaW5kZWVkLiBPZiBjb3Vyc2UgdGhlIHZhcmlhYmxlIG5hbWVzIGFyZSBjb252ZW50aW9uYWwsIGJ1dCBpbWFnaW5lIGEgY29tbXVuaXR5IG9mIGxpYnJhcmlhbnMgdXNpbmcgV0RRUyB3aG8gbGVhcm4gU1BBUlFMIGZyb20gZWFjaCBvdGhlciBhbmQgdGh1cyBmcmVxdWVudGx5IHVzZSB2YXJpYWJsZSBuYW1lcyBsaWtlIGA/YXV0aG9yYCBhbmQvb3IgYD9hdXRob3JMYWJlbGA/IElmIHN1Y2ggInR5cGljYWwiIHZhcmlhYmxlIG5hbWVzIGFyZSBmcmVxdWVudGx5IHVzZWQgaW4gc2ltaWxhciBxdWVyaWVzLCB0aGV5IGNhbiBhY3QgYXMgaW5mb3JtYXRpdmUgZmVhdHVyZXMgaW4gdGhlIHByZWRpY3Rpb24gb2YgcXVlcnkgcmVzcG9uc2UgdGltZSBpbmRlZWQuIENvbW1lbnRzOiBtYW55IFdpa2lkYXRhIGJvdHMgbWlnaHQgdXNlIGEgInNpZ25hdHVyZSIgY29tbWVudCAoYW5kIGluZGVlZCBzb21lIGRvKSBvZiBzb21lIGZvcm0sIGZvciBleGFtcGxlLiBJZiBzdWNoIGJvdHMgcnVuIHRoZWlyIHF1ZXJpZXMgYWdhaW5zdCBXRFFTIGZyZXF1ZW50bHksIHRoZSBjb21tZW50cyBpbiB0aGVpciBjb2RlIG1pZ2h0IGFsc28gYWN0IGFzIHByZWRpY3RpdmUgZmVhdXJlcyBmb3IgdGhlIHF1ZXJ5IHBlcmZvbWFuY2UuIAoKKipOT1RFLioqIFRoZSAqKmN1cnJlbnQgcmVnZXggYW5hbHlzaXMgb2YgU1BBUlFMIHF1ZXJpZXMgaXMgYXBwcm94aW1hdGUqKiBhbmQsIG1vc3Qgb2YgdGltZSwgY29ycmVjdCBpbiBhIHNlbnNlIG9mIGJlaW5nIGFibGUgdG8gZXh0cmFjdCBpbnR1aXRpdmVseSBtZWFuaW5nZnVsIGF0b21zIG9mIFNQQVJRTC4gSXQgaXMgYXBwcm94aW1hdGUgaW4gYSBzZW5zZSB0aGF0IHRoZSByZWd1bGFyIGV4cHJlc3Npb25zIHVzZSBhcmUgZGVyaXZlZCBieSB0aGUgYXV0aG9yIG9mIHRoaXMgcmVwb3J0IGJ5IGRpcmVjdGx5IGFuYWx5emluZyB0aGUgdHlwaWNhbCBTUEFSUUwgcXVlcmllcyB3aXRoIG9ubHkgbGltaXRlZCBhdHRlbnRpb24gcGF5ZWQgdG8gdGhlIGZvcm1hbCBkZWZpbml0aW9uIG9mIFNQQVJRTCAoZS5nLiBpdHMgQmFja3Vz4oCTTmF1ciBmb3Jtcywgb3IgdGhlIGZvcm1hbCByZWdleCBkZWZpbml0aW9ucyBvZiBpdHMgdmFyaW91cyBzeW50YWN0aWMgY29uc3RpdHVlbnRzKS4gVGhlIGZlYXR1cmUgZW5naW5lZXJpbmcgcHJvY2VkdXJlIHVzZWQgaW4gb3VyIGN1cnJlbnQgYW5hbHlzZXMgKipjYW4gYmUgaW1wcm92ZWQqKiwgYnV0IGZyb20gb3VyIG1hbnVhbCBxdWVyeSBhbmFseXNpcyBpbnNwZWN0aW9uIGl0IGNhbiBiZSBpbXByb3ZlZCBvbmx5IHNsaWdodGx5LiAKCkJleW9uZCBleHRyYWN0aW5nIFNQQVJRTCAiYXRvbXMiLCB0aGUgZm9sbG93aW5nIGZlYXR1cmVzIGFyZSBkZXJpdmVkIGFzIGZyZXF1ZW5jaWVzIChjb3VudHMpIGZvciBlYWNoIHF1ZXJ5OgoKLSBgbmNoYXJgIC0gdGhlIGxlbmd0aCBvZiB0aGUgcXVlcnkgaW4gY2hhcmFjdGVycwotIGBfX3ZhcnNfX2AgLSBob3cgbWFueSB2YXJpYWJsZXMgYXJlIHVzZWQ/Ci0gYF9fdmFyc191c2FnZV9fYCAtIGhvdyBtYW55IHZhcmlhYmxlIG1lbnRpb25zIHRoZXJlIGFyZSBpbiB0b3RhbD8gCi0gYF9fdmFyc19sYWJlbF9fYCAtIGhvdyBtYW55IHZhcmlhYmxlcyByZXF1ZXN0IGxhYmVscyBhbHNvPwotIGBfX3ZhcnNfbGFiZWxfdXNhZ2VfX2AgLSBob3cgbWFueSB0aW1lcyBhcmUgdmFyaWFibGUgbGFiZWxzIG1lbnRpb25lZCBpbiBhIHF1ZXJ5PwotIGBfX3BhdGhzX19gIC0gaG93IG1hbnkgcHJvcGVydHkgcGF0aHMgYXJlIHByZXNlbnQgaW4gdGhlIHF1ZXJ5PwotIGBfX0lSSV9SRUZfX2AgLSBob3cgbWFueSBJUkkgcmVmZXJlbmNlcyBhcmUgdXNlZCBpbiB0aGUgcXVlcnk/Ci0gYF9fTElURVJBTF9fYCAtIGhvdyBtYW55IFNQQVJRTCBsaXRlcmFscyBhcmUgdXNlZCBpbiB0aGUgcXVlcnk/CgpTbyB0aGUgZmluYWwgZGVzY3JpcHRpb24gb2YgYW55IFNQQVJRTCBxdWVyeSwgZm9sbG93aW5nIHRoZXNlIGZlYXR1cmUgZW5naW5lZXJpbmcgc3RlcHMsIGlzIGEgdmVjdG9yIG9mIGNvdW50cyBmb3IgZWFjaCBleHRyYWN0ZWQgZmVhdHVyZSwgaW5jbHVkaW5nOiAoYSkgdG9rZW5zIChlYWNoIElSSSBtZW50aW9uZWQsIGZvciBleGFtcGxlKSwgYXMgd2VsbCBhcyAoYikgdHlwZXMgKGhvdyBtYW55IGBfX0lSSV9SRUZzX19gIHRoZXJlIGFyZSBpbiBhIHF1ZXJ5IGluIHRvdGFsKS4KCioqTk9URS4qKiBBcHByb3hpbWF0aW5nIHN5bnRheC4gT2J2aW91c2x5LCB0aGlzIGZlYXR1cmUgZW5naW5lZXJpbmcgYXBwcm9hY2ggZG9lcyBub3QgYW5hbHl6ZSB0aGUgc3RydWN0dXJhbCByZWxhdGlvbnMgYmV0d2VlbiB0aGUgbGFuZ3VhZ2UgY29uc3RpdHVlbnRzOiBpdHMgc3ludGF4LiBIb3dldmVyLCBzb21lIG9mIHRoZSBleHRyYWN0ZWQgZmVhdHVyZXMgY2xlYXJseSAiY29ycmVsYXRlIiB3aXRoIHRoZSBxdWVyeSBzeW50YWN0aWMgY29tcGxleGl0eS4gRm9yIGV4YW1wbGUsIHRoZSBjb3VudCBvZiBge2AgdG9rZW5zLCBvciB0aGUgY291bnQgb2YgYFNFTEVDVGAga2V5d29yZHMsIHVzZWQgaW4gYSBxdWVyeSwgY2xlYXJseSBzcGVha3MgYWJvdXQgdGhlIHF1ZXJ5IGBkZXB0aGAgKGkuZS4gaG93IG1hbnkgc3ViLXF1ZXJpZXMgZG8gd2UgaGF2ZSwgaWYgYW55KS4gU28gdGhlIHN5bnRhY3RpYyBpbmZvcm1hdGlvbiBpcyBwcmVzZW50IGluIG91ciBmZWF0dXJlIGVuZ2luZWVyaW5nIG1vcmUgb3IgbGVzcyB1cCB0byBhIGRlZ3JlZSBkaWN0YXRlZCBieSBob3cgbXVjaCB1c2VmdWwgaW5mb3JtYXRpb24gaXMgY2FycmllZCBieSBhIG1lbnRpb24gb2YgYSBwYXJ0aWN1bGFyIFNQQVJRTCBrZXl3b3JkLCBvciBmdW5jdGlvbiwgb3Igb3BlcmF0b3IsIGV0YzsgdGhlIG51bWJlciBvZiB2YXJpYWJsZXMgdXNlZCwgdGhlIHF1ZXJ5ICJkZXB0aCIsIGFuZCBzaW1pbGFyLiBNb3JlIHVzZWZ1bCBzeW50YWN0aWMgaW5mb3JtYXRpb24gY2FuIGJlIG9idGFpbmVkIGZyb20gQmxhemVncmFwaCBieSBydW5uaW5nIGEgcXVlcnkgaW4gdGhlIEVYUExBSU4gbW9kZSAoc2ltaWxhciB0byB3aGF0IG9uZSB3b3VsZCBpbiBTUUwpLCBzZWU6IGh0dHBzOi8vd3d3Lm1lZGlhd2lraS5vcmcvd2lraS9XaWtpZGF0YV9RdWVyeV9TZXJ2aWNlL1VzZXJfTWFudWFsI0V4cGxhaW5fUXVlcnkuIEhvd2V2ZXIsIGV2ZW4gaWYgZmVhdHVydXMgb2J0YWluZWQgZnJvbSB0aGUgcXVlcnkgZXhlY3V0aW9uIHBsYW4gaW4gdGhpcyB3YXkgbWlnaHQgcHJvdmUgdG8gYmUgdXNlZnVsLCBpdCBpcyB1bmNsZWFyIGF0IHRoaXMgcG9pbnQgd2hldGhlciB0aGUgU1BBUlFMIHF1ZXJ5IGV4ZWN1dGlvbiBwbGFuIGNhbiBiZSBvYnRhaW5lZCBmcm9tIEJsYXplZ3JhcGggKndpdGhvdXQqIGFjdHVhbGx5IHJ1bm5pbmcgdGhlIHF1ZXJ5LgoKCmBgYHtyIGVjaG8gPSBULCBldmFsID0gRiwgbWVzc2FnZSA9IEZ9CgojIS91c3IvYmluL2VudiBSc2NyaXB0CgojIyMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCiMjIyAtLS0gV0RfV0RRU19BbmFseXRpY3NfRmVhdHVyZUVuZ2luZWVyaW5nLlIsIHYuIEJldGEgMC4xCiMjIyAtLS0gU2NyaXB0OiBXRF9XRFFTX0FuYWx5dGljc19GZWF0dXJlRW5naW5lZXJpbmcuUiwgdi4gQmV0YSAwLjEKIyMjIC0tLSBEZXNjcmlwdGlvbjogV0RfV0RRU19BbmFseXRpY3NfRmVhdHVyZUVuZ2luZWVyaW5nLlIgcGVyZm9ybXMgCiMjIyAtLS0gZmVhdHVyZSBlbmdpbmVlcmluZyBvZiB0aGUgU1BBUlFMIHF1ZXJpZXMgZGF0YSAKIyMjIC0tLSBjb2xsZWN0ZWQgdy4gV0RfV0RRU19BbmFseXRpY3NfQ29sbGVjdERhdGEuUiBmcm9tIHRoZSAKIyMjIC0tLSB3bWYud2VicmVxdWVzdCB0YWJsZSBpbiB0aGUgV01GIERhdGEgTGFrZS4KIyMjIC0tLSBBdXRob3I6IEdvcmFuIFMuIE1pbG92YW5vdmljLCBEYXRhIFNjaWVudGlzdCwgV01ERQojIyMgLS0tIERldmVsb3BlZCB1bmRlciB0aGUgY29udHJhY3QgYmV0d2VlbiBHb3JhbiBNaWxvdmFub3ZpYyBQUiBEYXRhIEtvbGVrdGl2CiMjIyAtLS0gYW5kIFdNREUuCiMjIyAtLS0gQ29udGFjdDogZ29yYW4ubWlsb3Zhbm92aWNfZXh0QHdpa2ltZWRpYS5kZQojIyMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCiMjIyAtLS0gUlVOIEZST006IAojIyMgLS0tIC9ob21lL2dvcmFuc20vQW5hbHl0aWNzL1dpa2lkYXRhL1dEX21pc2MvV0RfU1BBUlFMX0VuZHBvaW50X0FuYWx5dGljcwojIyMgLS0tIG9uIHN0YXQxMDA1CiMjIyAtLS0gRGF0ZTogMjAyMC8wMy8yMAojIyMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCgojIyMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCiMjIyAtLS0gTElDRU5TRToKIyMjIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQojIyMgLS0tIEdQTCB2MgojIyMgLS0tIFRoaXMgZmlsZSBpcyBwYXJ0IG9mIHRoZSBXaWtpZGF0YSBTUEFSUUwgRW5kcG9pbnQgQW5hbHl0aWNzIFByb2plY3QKIyMjIC0tLQojIyMgLS0tIFdpa2lkYXRhIFNQQVJRTCBFbmRwb2ludCBBbmFseXRpY3MgUHJvamVjdCBpcyBmcmVlIHNvZnR3YXJlOiAKIyMjIC0tLSB5b3UgY2FuIHJlZGlzdHJpYnV0ZSBpdCBhbmQvb3IgbW9kaWZ5CiMjIyAtLS0gaXQgdW5kZXIgdGhlIHRlcm1zIG9mIHRoZSBHTlUgR2VuZXJhbCBQdWJsaWMgTGljZW5zZSBhcyBwdWJsaXNoZWQgYnkKIyMjIC0tLSB0aGUgRnJlZSBTb2Z0d2FyZSBGb3VuZGF0aW9uLCBlaXRoZXIgdmVyc2lvbiAyIG9mIHRoZSBMaWNlbnNlLCBvcgojIyMgLS0tIChhdCB5b3VyIG9wdGlvbikgYW55IGxhdGVyIHZlcnNpb24uCiMjIyAtLS0KIyMjIC0tLSBXaWtpZGF0YSBTUEFSUUwgRW5kcG9pbnQgQW5hbHl0aWNzIFByb2plY3QgaXMgZGlzdHJpYnV0ZWQgCiMjIyAtLS0gaW4gdGhlIGhvcGUgdGhhdCBpdCB3aWxsIGJlIHVzZWZ1bCwKIyMjIC0tLSBidXQgV0lUSE9VVCBBTlkgV0FSUkFOVFk7IHdpdGhvdXQgZXZlbiB0aGUgaW1wbGllZCB3YXJyYW50eSBvZgojIyMgLS0tIE1FUkNIQU5UQUJJTElUWSBvciBGSVRORVNTIEZPUiBBIFBBUlRJQ1VMQVIgUFVSUE9TRS4gIFNlZSB0aGUKIyMjIC0tLSBHTlUgR2VuZXJhbCBQdWJsaWMgTGljZW5zZSBmb3IgbW9yZSBkZXRhaWxzLgojIyMgLS0tCiMjIyAtLS0gWW91IHNob3VsZCBoYXZlIHJlY2VpdmVkIGEgY29weSBvZiB0aGUgR05VIEdlbmVyYWwgUHVibGljIExpY2Vuc2UKIyMjIC0tLSBhbG9uZyB3aXRoIFdpa2lkYXRhIFNQQVJRTCBFbmRwb2ludCBBbmFseXRpY3MgUHJvamVjdC4KIyMjIC0tLSBJZiBub3QsIHNlZSA8aHR0cDovL3d3dy5nbnUub3JnL2xpY2Vuc2VzLz4uCiMjIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KCiMjIyAtLS0gc2V0dXAKbGlicmFyeShkYXRhLnRhYmxlKQpsaWJyYXJ5KHN0cmluZ3IpCmxpYnJhcnkoZHBseXIpCmxpYnJhcnkodGlkeXIpCmxpYnJhcnkoc25vd2ZhbGwpCgojIyMgLS0tIGRpcmVjdG9yaWVzCiMjIyAtLS0gUmVhZCBXRENNIHBhcmFtZXJldGVycwojIC0gZlBhdGg6IHdoZXJlIHRoZSBzY3JpcHRzIGlzIHJ1biBmcm9tPwpmUGF0aCA8LSBhcy5jaGFyYWN0ZXIoY29tbWFuZEFyZ3ModHJhaWxpbmdPbmx5ID0gRkFMU0UpWzRdKQpmUGF0aCA8LSBnc3ViKCItLWZpbGU9IiwgIiIsIGZQYXRoLCBmaXhlZCA9IFQpCmZQYXRoIDwtIHVubGlzdChzdHJzcGxpdChmUGF0aCwgc3BsaXQgPSAiLyIsIGZpeGVkID0gVCkpCmZQYXRoIDwtIHBhc3RlKAogIHBhc3RlKGZQYXRoWzE6bGVuZ3RoKGZQYXRoKSAtIDFdLCBjb2xsYXBzZSA9ICIvIiksCiAgIi8iLAogIHNlcCA9ICIiKQpmUGF0aCA8LSAnL2hvbWUvZ29yYW5zbS9BbmFseXRpY3MvV2lraWRhdGEvV0RfbWlzYy9XRF9TUEFSUUxfRW5kcG9pbnRfQW5hbHl0aWNzLycKZGF0YURpciA8LSBwYXN0ZTAoZlBhdGgsICdfZGF0YS8nKQphbmFseXRpY3NEaXIgPC0gcGFzdGUwKGZQYXRoLCAnX2FuYWx5dGljcy8nKQppbWdEaXIgPC0gcGFzdGUwKGZQYXRoLCAnX2ltZy8nKQoKIyMjIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQojIyMgLS0tIFNlY3Rpb24gMS4gTG9hZCBhbmQgcHJlcGFyZSBTUEFSUUwgcXVlcmllcwojIyMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCiMgLSBsb2FkIHF1ZXJpZXMKZmlsZW5hbWUgPC0gJ3dkcXNfZGF0YV9zYW1wbGVfVEVTVF8yMDIwLTA0LTEyLmNzdicKZGF0YVNldCA8LSBmcmVhZChwYXN0ZTAoYW5hbHl0aWNzRGlyLCBmaWxlbmFtZSkpCmRhdGFTZXQkVjEgPC0gTlVMTAojIC0ga2VlcCBvbmx5IHVuaXF1ZSBxdWVyaWVzCncgPC0gd2hpY2goIWR1cGxpY2F0ZWQoZGF0YVNldCR1bmlxdWVRdWVyeUlkKSkKaWYgKGxlbmd0aCh3KSA+IDApIHsKICBkYXRhU2V0IDwtIGRhdGFTZXRbd10KICB9CiMgLSBwcmVwYXJlIHF1ZXJpZXMgZm9yIHBhcnNpbmcKcXVlcmllcyA8LSBkYXRhU2V0JHVyaV9xdWVyeQp1bmlxdWVRdWVyeUlkIDwtIGRhdGFTZXQkdW5pcXVlUXVlcnlJZApybShkYXRhU2V0KTsgZ2MoKQoKIyAtIHBhcnNlX1NQQVJRTCgpCnBhcnNlX1NQQVJRTCA8LSBmdW5jdGlvbih0cSkgewogICMgLSBnZXQgbmNoYXIoKQogIHRxX25jaGFyIDwtIG5jaGFyKHRxKQogICMgLSBzcGxpdCBieSAiXFw/cXVlcnk9fFxcP3F1ZXJ5ID0iCiAgdHEgPC0gZ3N1YigicXVlcnk9fHF1ZXJ5ID18XFw/cXVlcnk9fFxcP3F1ZXJ5ID0iLCAicXVlcnk9X19RVUVSWV9TVEFSVFNfX18iLCB0cSkKICB0cSA8LSB1bmxpc3Qoc3Ryc3BsaXQodHEsIHNwbGl0ID0gInF1ZXJ5PXxxdWVyeSA9fFxcP3F1ZXJ5PXxcXD9xdWVyeSA9IikpCiAgd19lbXB0eSA8LSB3aGljaChuY2hhcih0cSkgPT0gMCkKICBpZiAobGVuZ3RoKHdfZW1wdHkpID4gMCkge3RxIDwtIHRxWy13X2VtcHR5XX0KICAjIC0gcmVjb2duaXplIGZvcm1hdCwgb3V0cHV0LCByZXN1bHRzIHBhcmFtZXRlcnMKICB3UGFycyA8LSB3aGljaChncmVwbCgiZm9ybWF0PXxmb3JtYXQgPXxvdXRwdXQ9fG91dHB1dCA9fHJlc3VsdHM9fHJlc3VsdHMgPSIsIHRxKSkKICAjIC0gc3RvcmUgZm9ybWF0LCBvdXRwdXQsIHJlc3VsdHMgcGFyYW1ldGVycyBpbiBhIHNlcGFyYXRlCiAgIyAtIHZlY3RvcjogcGFyczsgcmVtb3ZlIGZyb20gdHEgaWYgYW55IHBhcmFtZXRlcnMgYXJlIHByZXNlbnQKICBpZiAobGVuZ3RoKHdQYXJzKSA+IDApIHsKICAgIHBhcnMgPC0gdHFbd1BhcnNdCiAgICBwYXJzIDwtIGdzdWIoIlxcP3wmIiwgIiIsIHBhcnMpCiAgICB0cSA8LSB0cVstd1BhcnNdCiAgfSBlbHNlIHsKICAgIHBhcnMgPC0gTlVMTAogIH0KICB0cSA8LSBnc3ViKCJfX1FVRVJZX1NUQVJUU19fXyIsICIiLCB0cSwgZml4ZWQgPSBUKQogICMgLSByZW1vdmUgIj8iIGZyb20gdGhlIGJlZ2lubmluZyBvZiBTUEFSUUwgaWYgcHJlc2VudAogIHRxIDwtIGdzdWIoIl5cXD8iLCAiIiwgdHEpCiAgIyAtIHNwbGl0IGJ5ICJcbiIKICB0cSA8LSB1bmxpc3QobGFwcGx5KHRxLCBmdW5jdGlvbih4KSB7CiAgICBzdHJzcGxpdCh4LCBzcGxpdCA9ICJcXG4iKQogIH0pKQogICMgLSByZW1vdmUgIlx0IiBpZiBhbnkKICB0cSA8LSBnc3ViKCJcXHQrIiwgIiIsIHRxKQogICMgLSB0cmltIHRyYWlsaW5nIHNwYWNlIGF0IHRoZSBiZWdpbm5pbmcgYW5kIGVuZCwgaWYgYW55CiAgdHEgPC0gc3RyX3RyaW0odHEsIHNpZGUgPSAiYm90aCIpCiAgIyAtIGNvbGxlY3QgSVJJIHJlZmVyZW5jZXMKICB0cV9pcmkgPC0gdW5saXN0KHN0cl9leHRyYWN0X2FsbCh0cSwgJzwoW1s6YWxudW06XV18W1s6cHVuY3Q6XV0pKz4nKSkKICAjIC0gbWF0Y2ggSVJJIHJlZmVyZW5jZXMKICB0cSA8LSBnc3ViKCc8KFtbOmFsbnVtOl1dfFtbOnB1bmN0Ol1dKSs+JywgIiBfX0lSSV9SRUZfXyAiLCB0cSkKICAjIC0gcHJlc2VydmUgbGluZXMgdGhhdCBlbmNvbXBhc3NlIG9ubHkgY29tbWVudHMKICB3Q29tIDwtIHdoaWNoKGdyZXBsKCJeIyIsIHRxKSkKICBpZiAobGVuZ3RoKHdDb20pID4gMCkgewogICAgY29tbWVudF9saW5lcyA8LSB0cVt3Q29tXQogICAgdHEgPC0gdHFbLXdDb21dCiAgfSBlbHNlIHsKICAgIGNvbW1lbnRfbGluZXMgPC0gTlVMTAogIH0KICAjIC0gc3BhY2UgdG8gdGhlIGxlZnQgYW5kIHJpZ2h0IG9mIAogICMgLSBicmFja2V0cywgb3BlcmF0b3JzLCBhbmQgcHVuY3R1YXRpb24KICB0cSA8LSBnc3ViKCJcXHsiLCAiIFxceyAiLCB0cSkKICB0cSA8LSBnc3ViKCJcXH0iLCAiIFxcfSAiLCB0cSkKICB0cSA8LSBnc3ViKCJcXCkiLCAiIFxcKSAiLCB0cSkKICB0cSA8LSBnc3ViKCJcXCgiLCAiIFxcKCAiLCB0cSkKICB0cSA8LSBnc3ViKCJcXF0iLCAiIFxcXSAiLCB0cSkKICB0cSA8LSBnc3ViKCJcXFsiLCAiIFxcWyAiLCB0cSkKICB0cSA8LSBnc3ViKCIoW148XXxbXj5dfFteIV0pPSIsICIgPSAiLCB0cSkKICB0cSA8LSBnc3ViKCIhPSIsICIgIT0gIiwgdHEpCiAgdHEgPC0gZ3N1YigiPj0iLCAiID49ICIsIHRxKQogIHRxIDwtIGdzdWIoIjw9IiwgIiA8PSAiLCB0cSkKICB0cSA8LSBnc3ViKCI+IiwgIiA+ICIsIHRxKQogIHRxIDwtIGdzdWIoIjwiLCAiIDwgIiwgdHEpCiAgdHEgPC0gZ3N1YigiPCIsICIgPCAiLCB0cSkKICB0cSA8LSBnc3ViKCJcXHxcXHwiLCAiIFxcfFxcfCAiLCB0cSkKICB0cSA8LSBnc3ViKCImJiIsICIgJiYgIiwgdHEpCiAgdHEgPC0gZ3N1YigiLCIsICIgLCAiLCB0cSkKICB0cSA8LSBnc3ViKCJcXC4iLCAiIFxcLiAiLCB0cSkKICB0cSA8LSBnc3ViKCI7IiwgIiA7ICIsIHRxKQogIHRxIDwtIGdzdWIoIlxccysiLCAiICIsIHRxKQogICMgLSBtYXRjaCBsaXRlcmFscwogIHRxIDwtIGdzdWIoJyhcJ3wiKSsoW15cJyJdKSsoXCd8IikrJywgIiBfX0xJVEVSQUxfXyAiLCB0cSkKICB0cSA8LSBnc3ViKCdcXGJbWzpkaWdpdDpdXStcXGInLCAiIF9fTElURVJBTF9fICIsIHRxKQogICMgLSAid29yZCB0b2tlbml6YXRpb24iOiBzcGxpdCBieSAiXHMiCiAgdHEgPC0gdW5saXN0KGxhcHBseSh0cSwgZnVuY3Rpb24oeCkgewogICAgc3Ryc3BsaXQoeCwgc3BsaXQgPSAiXFxzIikKICB9KSkKICAjIC0gcmVtb3ZlOiAnIicsICIuIiwgIiwiCiAgdHEgPC0gZ3N1YignKCJ8XFwufCwpKycsICcnLCB0cSkKICAjIC0gcmVtb3ZlOiAiJyIKICB0cSA8LSBnc3ViKCInKyIsICcnLCB0cSkKICAjIC0gY291bnQgdW5pcXVlIHZhcmlhYmxlcwogIG51bV92YXJzIDwtIGxlbmd0aCh1bmlxdWUodHFbZ3JlcGwoJ15cXD98XlxcJCcsIHRxKV0pKQogICMgLSBjb3VudCB2YXJpYWJsZSB1c2UKICB2YXJzX3VzYWdlIDwtIGxlbmd0aCh0cVtncmVwbCgnXlxcP3xeXFwkJywgdHEpXSkKICAjIC0gY291bnQgdW5pcXVlIGxhYmVsIHZhcmlhYmxlcwogIG51bV92YXJzX2xhYmVsIDwtIGxlbmd0aCh1bmlxdWUodHFbZ3JlcGwoJ1teOl1sYWJlbCQnLCB0cSwgaWdub3JlLmNhc2UgPSBUKV0pKQogICMgLSBjb3VudCBsYWJlbCB2YXJpYWJsZSB1c2UKICBsYWJlbF92YXJzX3VzYWdlIDwtIGxlbmd0aCh0cVtncmVwbCgnW146XWxhYmVsJCcsIHRxLCBpZ25vcmUuY2FzZSA9IFQpXSkKICAjIC0gYWRkIHZhcmlhYmxlcyB1c2UgYW5kIHVuaXF1ZSB2YXJpYWJsZSBjb3VudCB0byB0cQogIHRxIDwtIGModHEsIAogICAgICAgICAgcmVwKCdfX3ZhcnNfXycsIG51bV92YXJzKSwgCiAgICAgICAgICByZXAoJ19fdmFyc191c2FnZV9fJywgdmFyc191c2FnZSksIAogICAgICAgICAgcmVwKCdfX3ZhcnNfbGFiZWxfXycsIG51bV92YXJzX2xhYmVsKSwKICAgICAgICAgIHJlcCgnX192YXJzX2xhYmVsX3VzYWdlX18nLCBsYWJlbF92YXJzX3VzYWdlKSkKICAjIC0gdG9sb3dlcigpCiAgdHEgPC0gdG9sb3dlcih0cSkKICAjIC0gcmVtb3ZlIHRxIGVsZW1lbnRzIHRoYXQgYmVnaW4gd2l0aDogIj8iLCAiJCIsICIpIiwgIjsiLCAifSIKICB3Q2xlYW4gPC0gd2hpY2goZ3JlcGwoJ15cXCl8Xjt8XlxcfXxeXFxdJywgdHEpKQogIGlmIChsZW5ndGgod0NsZWFuID4gMCkpIHsKICAgIHRxIDwtICB0cVstd0NsZWFuXQogICAgfQogICMgLSBjbGVhbiBmcm9tOiAiXSIsICJ9IiwgIjsiLCAiKSIgaWYgcHJlc2VudCBhbnl3aGVyZQogIHRxIDwtIGdzdWIoIlxcXXxcXH18XFwpfDsiLCAiIiwgdHEpCiAgIyAtIGNvdW50IGhvdyBtYW55ICIvIiBwYXRoIG9wZXJhdG9ycyB3ZXJlIHVzZWQgaW4gYSBxdWVyeQogIG51bV9wYXRocyA8LSBsZW5ndGgodHFbZ3JlcGwoJyhbWzphbG51bTpdXXw6KSsvKFtbOmFsbnVtOl1dfDopKycsIHRxKV0pCiAgdHEgPC0gYyh0cSwgCiAgICAgICAgICByZXAoJ19fcGF0aHNfXycsIG51bV9wYXRocykpCiAgIyAtIHJlbW92ZSBlbXB0eSBlbGVtZW50cyBmcm9tIHRxIGlmIGFueToKICB3RW1wdHkgPC0gd2hpY2goZ3JlcGwoIl5cXHMrJHxeJCIsIHRxKSkKICBpZiAobGVuZ3RoKHdFbXB0eSkgPiAwKSB7dHEgPC0gdHFbLXdFbXB0eV19CiAgIyAtIGVudGVyIGNvbW1lbnRzCiAgaWYgKGxlbmd0aChjb21tZW50X2xpbmVzKSA+IDApIHt0cSA8LSBjKHRxLCBjb21tZW50X2xpbmVzKX0KICAjIC0gZW50ZXIgcGFycwogIGlmICghaXMubnVsbChwYXJzKSkge3RxIDwtIGModHEsIHBhcnMpfQogICMgLSBlbnRlciB0cV9pcmkKICBpZiAobGVuZ3RoKHRxX2lyaSk+MCkge3RxIDwtIGModHEsIHRxX2lyaSl9CiAgIyAtIHRxIGZlYXR1cmUgZnJlcXVlbmN5IGFzIGRhdGEuZnJhbWUKICB0cSA8LSBhcy5kYXRhLmZyYW1lKHRhYmxlKHRxKSkKICAjIC0gZW50ZXIgbmNoYXIodHEpIGFzIGZlYXR1cmU6CiAgdHEgPC0gcmJpbmQodHEsIAogICAgICAgICAgICAgIGRhdGEuZnJhbWUodHEgPSAnbmNoYXInLAogICAgICAgICAgICAgICAgICAgICAgICAgRnJlcSA9IHRxX25jaGFyKSkKICAjIC0gb3VwdXQ6IHRxCiAgcmV0dXJuKHRxKQp9CgojIC0gUGFyc2UgU1BBUlFMIHF1ZXJpZXMKc2ZJbml0KHBhcmFsbGVsID0gVCwgY3B1cyA9IDM2KQp0MSA8LSBTeXMudGltZSgpCnNmRXhwb3J0KCJxdWVyaWVzIikKc2ZFeHBvcnQoInBhcnNlX1NQQVJRTCIpCnNmTGlicmFyeShzdHJpbmdyKQojIC0gcHJvY2VzcwpxdWVyaWVzX3Byb2Nlc3NlZCA8LSBzZkNsdXN0ZXJBcHBseUxCKHF1ZXJpZXMsIHBhcnNlX1NQQVJRTCkKIyAtIHN0b3AgY2x1c3RlcgpzZlN0b3AoKQpwcmludChwYXN0ZTAoIlBhcnNpbmcgdG9vazogIiwgCiAgICAgICAgICAgICByb3VuZChkaWZmdGltZShTeXMudGltZSgpLCB0MSwgdW5pdHMgPSAibWluIiksIDIpKSkKCiMgLSBjb2xsZWN0IHJlc3VsdHMKbmFtZXMocXVlcmllc19wcm9jZXNzZWQpIDwtIHVuaXF1ZVF1ZXJ5SWQKcXVlcmllc19wcm9jZXNzZWQgPC0gcmJpbmRsaXN0KHF1ZXJpZXNfcHJvY2Vzc2VkLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlkY29sID0gVCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmaWxsID0gVCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB1c2UubmFtZXMgPSBUKQpjb2xuYW1lcyhxdWVyaWVzX3Byb2Nlc3NlZCkgPC0gCiAgYygndW5pcXVlUXVlcnlJZCcsICdmZWF0dXJlJywgJ2ZyZXEnKQp3cml0ZS5jc3YocXVlcmllc19wcm9jZXNzZWQsIHBhc3RlMChhbmFseXRpY3NEaXIsICJxdWVyaWVzX3Byb2Nlc3NlZF9URVNULmNzdiIpKQoKIyAtIHByb2R1Y2Ugdm9jYWJ1bGFyeTogZmVhdHVyZSBmcmVxdWVuY3kKdm9jYWJ1bGFyeSA8LSBxdWVyaWVzX3Byb2Nlc3NlZCAlPiUKICBkcGx5cjo6c2VsZWN0KGZlYXR1cmUsIGZyZXEpICU+JSAKICBkcGx5cjo6Z3JvdXBfYnkoZmVhdHVyZSkgJT4lIAogIGRwbHlyOjpzdW1tYXJpc2UoZnJlcSA9IHN1bShmcmVxKSkgJT4lIAogIGRwbHlyOjphcnJhbmdlKGRlc2MoZnJlcSkpCndyaXRlLmNzdih2b2NhYnVsYXJ5LCBwYXN0ZTAoYW5hbHl0aWNzRGlyLCAiU1BBUlFMdm9jYWJ1bGFyeV9URVNULmNzdiIpKQpoZWFkKGRhdGEuZnJhbWUodm9jYWJ1bGFyeSksIDQwMCkKCiMgLSBpbiBob3cgbWFueSBxdWVyaWVzIGlzIGEgcGFydGljdWxhciBmZWF0dXJlIHVzZWQ/CmhlYWQocXVlcmllc19wcm9jZXNzZWQpCnF1ZXJ5X2ZlYXR1cmVzIDwtIHF1ZXJpZXNfcHJvY2Vzc2VkICU+JSAKICBkcGx5cjo6c2VsZWN0KGZlYXR1cmUpICU+JSAKICBkcGx5cjo6Z3JvdXBfYnkoZmVhdHVyZSkgJT4lIAogIGRwbHlyOjpzdW1tYXJpc2UobnVtX3F1ZXJpZXMgPSBuKCkpICU+JSAKICBkcGx5cjo6YXJyYW5nZShkZXNjKG51bV9xdWVyaWVzKSkKcXVlcnlfZmVhdHVyZXMkcGVyY2VudF9xdWVyaWVzIDwtIAogIHF1ZXJ5X2ZlYXR1cmVzJG51bV9xdWVyaWVzL2xlbmd0aCh1bmlxdWUocXVlcmllc19wcm9jZXNzZWQkdW5pcXVlUXVlcnlJZCkpCndyaXRlLmNzdihxdWVyeV9mZWF0dXJlcywgcGFzdGUwKGFuYWx5dGljc0RpciwgJ2ZlYXR1cmVfcXVlcnlfcGVyY2VudF9URVNULmNzdicpKQpgYGAKCkFnYWluLCBUaGUgZ29hbCBpcyB0byBkZXJpdmUgYSBkZXNjcmlwdGlvbiBvZiBTUEFSUUwgcXVlcmllcyB2aWEgZmVhdHVyZSB2ZWN0b3JzLiBGb3IgZXhhbXBsZToKCj4gX19pcmlfcmVmX186MTEsIF9fbGl0ZXJhbF9fOjEsIF9fdmFyc19fOjUsIF9fdmFyc19sYWJlbF9fOjEsIF9fdmFyc19sYWJlbF91c2FnZV9fOjEsIF9fdmFyc191c2FnZV9fOjIwLCA/aXRlbToxNSwgP3R5cGU6MSwgP3R5cGVsYWJlbDoxLCA/dXJsOjIsID93aWtpcGVkaWE6MSwgezoxNCwgPGh0dHA6Ly9zdGFuZGFydGNvbnRhLmNvbS50ci8+OjEsIDxodHRwOi8vc3RhbmRhcnRjb250YS5jb20udHI+OjEsIDxodHRwOi8vd3d3LnN0YW5kYXJ0Y29udGEuY29tLnRyLz46MiwgPGh0dHA6Ly93d3cuc3RhbmRhcnRjb250YS5jb20udHI+OjEsIDxodHRwOi8vd3d3Lnd3dy5zdGFuZGFydGNvbnRhLmNvbS50ci8+OjEsIDxodHRwczovL3N0YW5kYXJ0Y29udGEuY29tLnRyLz46MSwgPGh0dHBzOi8vc3RhbmRhcnRjb250YS5jb20udHI+OjEsIDxodHRwczovL3d3dy5zdGFuZGFydGNvbnRhLmNvbS50ci8+OjEsIDxodHRwczovL3d3dy5zdGFuZGFydGNvbnRhLmNvbS50cj46MSwgPGh0dHBzOi8vd3d3Lnd3dy5zdGFuZGFydGNvbnRhLmNvbS50ci8+OjEsIGJkOnNlcnZpY2VwYXJhbToxLCBkaXN0aW5jdDoxLCBvcHRpb25hbDoxLCBzY2hlbWE6YWJvdXQ6MSwgc2VsZWN0OjEsIHNlcnZpY2U6MSwgdW5pb246MTAsIHdkdDpwMzE6MSwgd2R0OnA4NTY6MTIsIHdoZXJlOjEsIHdpa2liYXNlOmxhYmVsOjEsIHdpa2liYXNlOmxhbmd1YWdlOjEsIG5jaGFyOjkwMwoKSW4gdGhpcyBleGFtcGxlIG9mIGEgcXVlcnkgZmVhdHVyZSB2ZWN0b3Igd2Ugc2VlIHRoYXQgdGhlIHF1ZXJ5IGhhZCB1c2VkIDExIElSSSByZWZlcmVuY2VzIChgX19pcmlfcmVmX186MTFgKSwgb25lIGxpdGVyYWwgKGBfX2xpdGVyYWxfXzoxYCksIGZpdmUgdmFyaWFibGVzIChgX192YXJzX186NWApIHRoYXQgd2VyZSBtZW50aW9uZWQgMjAgdGltZXMgYWx0b2dldGhlciAoYF9fdmFyc191c2FnZV9fOjIwYCksIHVzZWQgYHdpa2liYXNlOmxhYmVsYCBvbmNlIChgd2lraWJhc2U6bGFiZWw6MWApLCB0aGUgYFVOSU9OYCBrZXl3b3JkIDEwIHRpbWVzIChgdW5pb246MTBgKSwgdGhhdCB0aGUgbGVuZ3RoIG9mIHRoZSBxdWVyeSBpbiBjaGFyYWN0ZXJzIGlzIGBuY2hhcjo5MDNgLCBldGMuCgojIyAyLiBBIGNyaXRlcmlvbiBmb3Igc2VsZWN0aW5nIGEgY3JpdGljYWwgcXVlcnkgZm9yIFdEUVMKCk5vdyB3aGVuIGVhY2ggU1BBUlFMIHF1ZXJ5IGluIG91ciBzYW1wbGUgaXMgZGVzY3JpYmVkIGJ5IGEgZmVhdHVyZSB2ZWN0b3Igc3VpdGFibGUgZm9yIHByZWRpY3RpdmUgbW9kZWxpbmcsIHdlIG5lZWQgdG8gc2VsZWN0IGEgY3JpdGVyaW9uIHZhcmlhYmxlOiB3aGF0IGRvIHdlIHdhbnQgdG8gcHJlZGljdD8gV2Ugd2FudCB0byBwcmVkaWN0IHRoZSBXRFFTIHJlc3BvbnNlIHRpbWUsIG9idmlvdXNseSAtIHRoZSBpbmZvcm1hdGlvbiBjYXJyaWVkIGluIHRoZSBgdGltZV9maXJzdGJ5dGVgIGZpZWxkIG9mIHRoZSBgd21mLndlYnJlcXVlc3RgIHRhYmxlIChpLmUuaG93IG1hbnkgc2Vjb25kcyBlbGFwc2VkIGJlZm9yZSB0aGUgZmlyc3QgYnl0ZSBvZiB0aGUgc2VydmVyIHJlc3BvbnNlIHdhcyBzZW50IG91dCB0byB0aGUgY2xpZW50KS4gSG93ZXZlciwgc2luY2Ugd2UgYWxyZWFkeSBub3cgdGhhdCB0aGUgdGltZSB0byBwcm9jZXNzIGEgU1BBUlFMIHF1ZXJ5IGRlcGVuZHMgbm90IG9ubHkgdXBvbiB0aGUgZmVhdHVyZXMgb2YgdGhlIHF1ZXJ5IGl0c2VsZiwgYnV0IGFsc28gdXBvbiB0aGUgV0RRUy9CbGF6ZWdyYXBoIGNvbmZpZ3VyYXRpb24gdGhhdCBwcmVzZW50cyBhICpibGFjayBib3gqIGZvciB1cywgd2UgbWlnaHQgY29uY2x1ZGUgdGhhdCBwcmVkaWN0aW5nIHRoZSBleGFjdCBzZXJ2ZXIgcmVzcG9uc2UgdGltZSBtaWdodCBwcmVzZW50IGEgdG9vIGFtYml0aW91cyBnb2FsLiBJbmRlZWQsIG91ciBpbml0aWFsIGF0dGVtcHRzIHRvIHByZWRpY3QgdGhlIHNlcnZlciByZXNwb25zZSB0aW1lIGZvciBhIHF1ZXJ5IHNob3dzIHRoYXQgYXBwcm94aW1hdGVseSBhIGNvcnJlbGF0aW9uIG9mIFIgPSAuNzIgKG9uIHRoZSB0ZXN0IGRhdGEgc2V0LCBhcHByb3guIGhhbGYgb2YgYDFNYCBxdWVyaWVzIGluIHRoZSBzYW1wbGUpIGJldHdlZW4gdGhlIHByZWRpY3RpdmUgbW9kZWwgYmFzZWQgb24gb3VyIGZlYXR1cmVzIGFuZCByZXNwb25zZSB0aW1lIGNhbiBiZSBhY2hpZXZlZCAtIGEgcmVzdWx0IG5vdCB0aGF0IHNvdW5kLiAKCldoYXQgaWYgd2UgY2FuIHRyYWluIGEgbW9kZWwgdG8gcHJlZGljdCBhIGRlcml2ZWQsIGJpbmFyeSB2YXJpYWJsZSB0aGF0IGRlc2NyaWJlcyBlYWNoIHF1ZXJ5IGFzICpjcml0aWNhbCogKGkuZS4gcmVzdWx0aW5nIGluIGFuIGV4Y2Vzc2l2ZWx5IGxvbmcgcXVlcnkgcHJvY2Vzc2luZyB0aW1lKSBvciAqbm9uLWNyaXRpY2FsKiAoaS5lLiByZXN1bHRpbmcgaW4gYSAidHlwaWNhbCIgcXVlcnkgcHJvY2Vzc2luZyB0aW1lKT8gSWYgd2UgcG9zZSB0aGUgcHJvYmxlbSBpbiB0aGlzIHdheSwgdGhhbiBpdCBpcyBlYXN5IHRvIGRlcml2ZSBzdWNoIGEgYmluYXJ5IGNyaXRlcmlvbiBmcm9tIHRoZSBvYnNlcnZlZCBgdGltZV9maXJzdGJ5dGVgIHZhbHVlIGZvciBlYWNoIHF1ZXJ5OgoKLSB0YWtlIHRoZSAqKmV4dHJlbWUgb3V0bGllcnMqKiBvbiBgdGltZV9maXJzdGJ5dGVgIHRvIGJlIGFsbCBvYnNlcnZhdGlvbnMgYWJvdmUgdGhlIHZhcmlhYmxlJ3MgKnVwcGVyIG91dGVyIGZlbmNlKjogUTMgKyAzKklRUiwgd2hlcmUgUTMgaXMgdGhlIGAuNzVgIHBlcmNlbnRpbGUgb2YgYHRpbWVfZmlyc3RieXRlYCwgd2hpbGUgSVFSIHN0YW5kcyBmb3IgSW50ZXItUXVhcnRpbGUgUmFuZ2UgYW5kIHByZXNlbnRzIHRoZSBkaWZmZXJlbmNlIGJldHdlZW4gUTMgKHRoZSBgLjc1YCBwZXJjZW50aWxlIG9mIHRoZSBkYXRhKSBhbmQgUTEgKHRoZSBgLjI1YCBwZXJjZW50aWxlIG9mIHRoZSBkYXRhKS4KClRoaXMgZGVmaW5pdGlvbiBpcyB3aWRlbHkgdXNlZCBpbiBFeHBsb3JhdG9yeSBEYXRhIEFuYWx5c2lzLiBXZSBjYW4gYWxzbyBzZWxlY3QgYSBsZXNzIGV4dHJlbWUgY3JpdGVyaW9uIGJ5IGNvbnNpZGVyaW5nIGFueXRoaW5nIHRoYXQgaXMgYSBtaWxkIG91dGxpZXIgb24gYHRpbWVfZmlyc3RieXRlYCBhcyBjcml0aWNhbDoKCi0gdGFrZSB0aGUgKiptaWxkIG91dGxpZXJzKiogb24gYHRpbWVfZmlyc3RieXRlYCB0byBiZSBhbGwgb2JzZXJ2YXRpb25zIGFib3ZlIHRoZSB2YXJpYWJsZSdzICp1cHBlciBpbm5lciBmZW5jZSo6IFEzICsgMS41KklRUiwgd2hlcmUgUTMgaXMgdGhlIGAuNzVgIHBlcmNlbnRpbGUgb2YgYHRpbWVfZmlyc3RieXRlYCwgd2hpbGUgSVFSIHN0YW5kcyBmb3IgSW50ZXItUXVhcnRpbGUgUmFuZ2UgYW5kIHByZXNlbnRzIHRoZSBkaWZmZXJlbmNlIGJldHdlZW4gUTMgKHRoZSBgLjc1YCBwZXJjZW50aWxlIG9mIHRoZSBkYXRhKSBhbmQgUTEgKHRoZSBgLjI1YCBwZXJjZW50aWxlIG9mIHRoZSBkYXRhKS4KCldoaWxlIHdlIGhhdmUgdXNlZCBib3RoIGNyaXRlcmlhIGluIG91ciBhdHRlbXB0cyBhdCBwcmVkaWN0aXZlIG1vZGVsaW5nLCB3ZSB3aWxsIG1haW5seSBmb2N1cyBvbiB0aGUgcHJlZGljdGlvbiBvZiAqKmV4dHJlbWUqKiBvdXRsaWVycyBvbiBgdGltZV9maXJzdGJ5dGVgICBpbiB3aGF0IGZvbGxvd3MuCgojIyAzLiBNYWNoaW5lIExlYXJuaW5nIHcuIFhHQm9vc3Q6IGNhbiB3ZSBwcmVkaWN0IGV4dHJlbWUgb3V0bGllcnMgb24gV0RRUyByZXNwb25zZSB0aW1lIGZyb20gU1BBUlFMIHF1ZXJ5IGZlYXR1cmVzPwoKVGh1cyBmYXIsCgotIGFsbCBTUEFSUUwgcXVlcmllcyBhcmUgcmVwcmVzZW50ZWQgYXMgZmVhdHVyZSB2ZWN0b3JzLCBhbmQKLSBhIGNyaXRlcmlvbiB2YXJpYWJsZSBoYXMgYmVlbiBkZWZpbmVkIChleHRyZW1lIG91dGxpZXJzIG9uIFdEUVMgc2VydmVyIHJlc3BvbnNlIHRpbWVzKS4KCk91ciBnb2FsIGlzIHRvIHNlbGVjdCBhIHByZWRpY3RpdmUgbW9kZWwgdGhhdCB3aWxsIHVzZSB0aGUgZmVhdXR1cmUgdmVjdG9ycyB0byBwcmVkaWN0IHdoZXRoZXIgdGhlIHJlc3BlY3RpdmUgcXVlcmllcycgcmVzcG9uc2UgdGltZXMgYXJlIGNyaXRpY2FsIChleHRyZW1lKSBvciBub3QuIEF0IHRoZSBzYW1lIHRpbWUsIHRoZSBtb2RlbCB3aWxsIGJlIHBlcmZvcm1pbmcgZmVhdHVyZSBzZWxlY3Rpb24gYW5kIGluZm9ybWluZyB1cyBhYm91dCB0aGUgdmFsdWUgb2YgZWFjaCBmZWF0dXJlIGluIHRoZSBwcmVkaWN0aW9uLiBUaGlzIHdpbGwgYWxsb3dzIHVzIHRvIHNlbGVjdCBhIHNtYWxsIHNldCBvZiBTUEFSUUwgZmVhdXRlcnMgdGhhdCBjYW4gYmUgdXNlZCB0byBjaGFyYWN0ZXJpemUgdGhlIHF1ZXJpZXMgYXMgcmVzdWx0aW5nIGluIHR5cGljYWwgdnMuIGV4dHJlbWUgV0RRUyByZXNwb25zZSB0aW1lcy4gQW5kIHN1Y2ggc21hbGwgZmVhdHVyZSB2b2NhYnVsYXJ5IGlzIGV4YWN0bHkgd2hhdCB3ZSBhcmUgbG9va2luZyBmb3IgaW4gb3JkZXIgdG8gYmUgYWJsZSB0byBkZXZlbG9wIGEgV0RRUyBvcHRpbWl6YXRpb24gZW5naW5lIGluIHRoZSBmdXR1cmU6IGEgc3lzdGVtIHRoYXQgd291bGQgZmFzdCBjYW4gYW55IGdpdmVuIFNQQVJRTCBxdWVyeSBhbmQgZGVjaWRlIHdoZXRlciBpdCBuZWVkcyB0byBiZSBoYW5kbGVkIGFzIGEgc3BlY2lhbCwgcG90ZW50aWFsbHkgZGlmZmljdWx0IGNhc2UgdG8gcHJvY2VzcyAtIG9yIHNob3VsZCB0aGUgcHJvY2Vzc2luZyBwcm9jZWVkIGFzIHVzdWFsLgoKV2UgaGF2ZSB1c2VkIFtYR0Jvb3N0XShodHRwczovL3hnYm9vc3QucmVhZHRoZWRvY3MuaW8vZW4vbGF0ZXN0L2luZGV4Lmh0bWwpIHZpYSB0aGUgUiBbe3hnYm9vc3R9XShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMveGdib29zdC9pbmRleC5odG1sKSBwYWNrYWdlIHRvIHRyYWluIG1hbnkgcHJlZGljdGl2ZSBtb2RlbHMgdGhhdCBhdHRlbXB0IHRvIGNhdGVnb3JpemUgU1BBUlFMIHF1ZXJpZXMgYXMgY3JpdGljYWwgb3Igbm90IGluIGFjY29yZGFuY2Ugd2l0aCB0aGUgYWJvdmUgc3RhdGVkIGNyaXRlcmlvbi4gWEdCb29zdCAtIHNob3J0IGZvciBbRXh0cmVtZSBHcmFkaWVudCBCb29zdGluZ10oaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvR3JhZGllbnRfYm9vc3RpbmcpIC0gaXMgYSBNTCBtZXRob2QgdG8gZXN0aW1hdGUgYW4gZW5zZW1ibGUgb2YgbWF0aGVtYXRpY2FsIG1vZGVscyAtIGRlY2lzaW9uIHRyZWVzIGluIG91ciBjYXNlIC0gdGhhdCBqb2ludGx5IHdvcmsgdG8gcHJlZGljdCB0aGUgcmVzcG9uc2UgdmFyaWFibGUuIEl0cyBkZXNjcmlwdGlvbiBpcyByZWFsbHkgd2F5IG91dCBvZiBzY29wZSBvZiB0aGlzIFJlcG9ydC4gVGhpcyB0ZWNobmlxdWUgaXMgYXMga25vd24gdG8gYmUgbm90b3Jpb3VzbHkgZ29vZCBpbiBleHRyYWN0aW5nIGFsbW9zdCBldmVyeSBhbmQgdGhlIGxhc3QgcGllY2Ugb2YgdXNlZnVsIGluZm9ybWF0aW9uIGZyb20gdGhlIHRyYWluaW5nIGRhdGEgKGZlYXV0dXJlIHZlY3RvcnMgZGVzY3JpYmluZyB0aGUgU1BBUlFMIHF1ZXJpZXMgaW4gdGhpcyBjYXNlKS4gVGhlIFhHQm9vc3QgbW9kZWxzIGludm9sdmUgYSBbbGFyZ2UgbnVtYmVyIG9mIHBhcmFtZXRlcnNdKGh0dHBzOi8veGdib29zdC5yZWFkdGhlZG9jcy5pby9lbi9sYXRlc3QvcGFyYW1ldGVyLmh0bWwpIHRoYXQgbXVzdCBiZSBmaW5lLXR1bmVkIGluIG9yZGVyIGZvciBpdCB0byBkZXJpdmUgYSBwbGF1c2libGUgcHJlZGljdGl2ZSBtb2RlbCBmb3IgdGhlIHRhc2sgYXQgaGFuZC4gV2UgaGF2ZSBleHBlcmltZW50ZWQgd2l0aCB2YXJpb3VzIHBhcmFtZXRlciBzZXR0aW5ncyBhbmQgZGF0YSBzZXRzIGVuY29tcGFzc2luZyBhIHZhcmlhYmxlIG51bWJlciBvZiBleHRyYWN0ZWQgU1BBUlFMIGZlYXR1cmVzIHRvIHJlYWNoIHRoZSBmb2xsb3dpbmcgY29uY2x1c2lvbnM6CgotIHRoZSBudW1iZXIgb2YgZmVhdHVyZXMgdXNlZCB0byBidWlsZCBhIG1vZGVsIGRvZXMgbm90IHNpZ25pZmljYW50bHkgY2hhbmdlIHRoZSAqYWNjdXJhY3kqIGFuZCAqcHJlY2lzaW9uKiAoYm90aCB0byBiZSBleHBsYWluZWQgc29vbikgd2l0aCB3aGljaCB0aGUgbW9kZWwgY2FuIHRlbGwgdGhlIGNyaXRpY2FsIFNQQVJRTCBxdWVyaWVzIGZyb20gdHlwaWNhbCBvbmVzOyB0aGlzIGlzIG1vc3QgcHJvYmFibHkgZW50aXJlbHkgZHVlIHRvIHRoZSBmYWN0IHRoYXQgWEdCb29zdCBwZXJmb3JtcyBmZWF0dXJlIHNlbGVjdGlvbiBhbG9uZyB0aGUgd2F5IG9mIGl0cyB0cmFpbmluZzsgdGhpcyBpcyBhbHNvICp2ZXJ5IGltcG9ydGFudCogYmVjYXVzZSBpdCBtZWFucyB0aGF0IHdlIGNhbiB1c2Ugb25seSBhIHNtYWxsIGZyYWN0aW9uIG9mIGZlYXR1cmVzIHRvIHRyYWluIGEgcHJlZGljdGl2ZSBtb2RlbCAtIGFuZCB0aGUgbW9kZWwgd2l0aCBsZXNzIGZlYXR1cmVzIHdpbGwgcnVuIGZhc3RlciB0aGFuIHRoZSBvbmUgZW5jb21wYXNzaW5nIGEgbGFyZ2UgbnVtYmVyIG9mIGZlYXR1cmU7IAotIG1vc3RseSwgdGhlIHBlcmZvcm1hbmNlIG9mIHRoZSBwcmVkaWN0aXZlIG1vZGVsIGlzIG5vdCBzZXJpb3VzbHkgYWZmZWN0ZWQgYnkgY2hvb3NpbmcgdmFyaW91cyBjb21iaW5hdGlvbnMgb2YgcGFyYW1ldGVyIHNldHRpbmdzOyB3ZSBoYXZlIHBlcmZvcm1lZCBtYW55IGNyb3NzLXZhbGlkYXRpb25zLCBhbHdheXMgYWRkaXRpb25hbCBzcGxpdHRpbmcgb3VyIGAxTWAgc2FtcGxlIG9mIFNQQVJRTCBxdWVyaWVzIGludG8gdHJhaW4gYW5kIHRlc3QgZGF0YSAoYXBwcm94LiA1MCUgb2YgdGhlIHNhbXBsZSBlYWNoKSwgaW4gb3JkZXIgdG8gcmVhY2ggdGhpcyBjb25jbHVzaW9uOwotIHRoZSBtb2RlbCBwZXJmb3JtYW5jZSBpcyBnb29kIGJ1dCBmYXIgZnJvbSBwZXJmZWN0LCBob3dldmVyCi0gdGhlcmUgYXJlIHBvc3NpYmxlIGltcHJvdmVtZW50cyB0aGF0IGFsd2F5cyBpbnZvbHZlIGEgY29tcHJvbWlzZSBpbiByZXNwZWN0IHRvIGhvdyBjYW4gYmUgd2UgZGVzaWduIGEgZnV0dXJlIFdEUVMgT3B0aW1pemF0aW9uIEVuZ2luZS4KCmBgYHtyIGVjaG8gPSBULCBldmFsID0gRiwgbWVzc2FnZSA9IEZ9CgpgYGAKCgpJbiBvcmRlciB0byBleHBsYWluIHRoZSB0eXBpY2FsIG1vZGVsbGluZyByZXN1bHQgd2l0aCBYR0Jvb3N0LCB3ZSBuZWVkIHRvIGludHJvZHVjZSBzb21lIHRlcm1pbm9sb2d5IGZpcnN0LgoKTGV0J3MgYXNzdW1lIGVhY2ggb2YgdGhlIFNQQVJRTCBxdWVyaWVzIGlzIGNvZGVkIGFzIGAxYCBpZiB0aGUgcmVzcGVjdGl2ZSBXRFFTIHJlc3BvbnNlIHRpbWUgZm9yIHRoYXQgcXVlcnkgd2FzIGNyaXRpY2FsbHkgaGlnaCBvciBgMGAgaWYgaXQgaGFkIGZhbGxlbiBpbiBhIHJhbmdlIG9mIHR5cGljYWwgcHJvY2Vzc2luZyB0aW1lcy4gRm9yIGEgc2FtcGxlIG9mIFNQQVJRTCBxdWVyaWVzIHRoYW4gd2UgaGF2ZSB0d28gYmluYXJ5IHZlY3RvcnMgb3ZlciB7MCwgMX0gZGVzY3JpYmluZzogKDEpIHRoZSBvYnNlcnZlZCBzdGF0dXMgb2YgdGhlIHF1ZXJ5IChhZ2FpbjogMSAtIGNyaXRpY2FsLCAwIC0gdHlwaWNhbCksIGFuZCAoMikgdGhlIHByZWRpY3RlZCBzdGF0dXMgb2YgdGhlIHF1ZXJ5OiB3aGF0IHRoZSB0cmFpbmVkIHByZWRpY3RpdmUgbW9kZWwgInRoaW5rcyIgdGhlIHF1ZXJ5IHdvdWxkIGJlIGJ5IGxvb2tpbmcgYXQgdGhlIGZlYXR1cmVzIHRoYXQgZGVzY3JpYmUgaXQuCgpGb3IgZXhhbXBsZToKCmBgYHtyIGVjaG8gPSBUfQpzYW1wbGVGcmFtZSA8LSBkYXRhLmZyYW1lKHF1ZXJ5ID0gcGFzdGUwKCJRdWVyeV8iLCAxOjEwKSwKICAgICAgICAgICAgICAgICAgICAgICAgICBvYnNlcnZlZCA9IHJvdW5kKHJ1bmlmKDEwLCAwLCAxKSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgcHJlZGljdGVkID0gcm91bmQocnVuaWYoMTAsIDAsIDEpKSkKc2FtcGxlRnJhbWUKYGBgCgpGb3IgZWFjaCBvZiB0aGUgMTAgcXVlcmllcyBpbiB0aGUgdGFibGUgd2Uga25vdyBpZiB0aGV5IHJlYWxseSB3aGVyZSBjcml0aWNhbCAoMSkgb3Igbm90ICgwKSBhbmQgd2Ugc2VlIHRoYXQgZnJvbSB0aGUgYG9ic2VydmVkYCBjb2x1bW4sIGFuZCBpZiB0aGUgbW9kZWwgcHJlZGljdHMgdGhleSB3b3VsZCBiZSBjcml0aWNhbCAoMSkgb3Igbm90ICgwKSBmcm9tIHRoZWlyIGZlYXR1cmVzOiB0aGUgYHByZWRpY3RlZGAgY29sdW1uLiBGb3IgZXhhbXBsZSwgaW4gdGhlIGZpcnN0IHJvdyB3ZSBoYXZlIGEgcXVlcnkgdGhhdCB3YXMgY3JpdGljYWwgaW5kZWVkIGFuZCBjb3JyZWN0bHkgcHJlZGljdGVkIGFzIGNyaXRpY2FsOiB0aGF0IGlzIHNvbWV0aGluZyB3ZSBjYWxsIGEgSGl0IChvciBhIFRydWUgUG9zaXRpdmUpIGluIE1MLiBJbiB0aGUgc2Vjb25kIHJvdyB3ZSBvYnNlcnZlIGEgRmFsc2UgQWxhcm0gKG9yIGEgRmFsc2UgUG9zaXRpdmUpOiB0aGUgcXVlcnkgd2FzIG5vdCByZWFsbHkgY3JpdGljYWwgKGl0IGhhcyAwIG9uIGBvYnNlcnZlZGApIGJ1dCB0aGUgbW9kZWwgaGFzIGluY29ycmVjdGx5IGNhdGVnb3JpemVkIGl0IGFzIGlmIGl0IHdvdWxkIGJlICgxIG9uIGBwcmVkaWN0ZWRgKS4gSW4gdGhlIGVpZ3RoIHJvdyB3ZSBmaW5kIGEgc2l0dWF0aW9uIGtub3duIGFzIGEgQ29ycmVjdCBSZWplY3Rpb246IHRoZSBxdWVyeSB3YXMgbm90IGNyaXRpY2FsICgwIG9uIGBvYnNlcnZlZGApIGFuZCB0aGUgbW9kZWwgZmlndXJlZCBvdXQgaXQgd291bGRuJ3QgYmUgY3JpdGljYWwgaW5kZWVkIGJ5IGxvb2tpbmcgaW50byBpdHMgZmVhdHVyZXMgKDAgb24gYHByZWRpY3RlZGApLiBGaW5hbGx5LCB0aGUgZm91cnRoIGltcG9ydGFudCBzaXR1YXRpb24gY2FuIGJlIGZvdW5kIGluIHRoZSBzaXh0aCByb3csIHdoZXJlIHRoZSBxdWVyeSB3YXMgY3JpdGljYWwgKDEgb24gYG9ic2VydmVkYCkgYnV0IG5vdCByZWNvZ25pemVkIGFzIHN1Y2ggYnkgdGhlIG1vZGVsICgwIG9uIGBwcmVkaWN0ZWRgKSwgYSBjYXNlIGtub3duIGFzIE1pc3MgaW4gdGhlIE1MIHRlcm1pbm9sb2d5LiBNb3JlIG9uIHRoaXMgdGVybWlub2xvZ3k6IGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL0NvbmZ1c2lvbl9tYXRyaXguCgpJbiB0aGUgdGFibGUgYWJvdmUsIHdlIGNhbiBjb21wdXRlIHRoZSBtb2RlbCBhY2N1cmFjeSBieSBsb29raW5nIGF0IGhvdyBtYW55IHRpbWVzIGRvIHRoZSBgb2JzZXJ2ZWAgYW5kIGBwcmVkaWN0ZWRgIGNvbHVtbiBjb2luY2lkZToKCioqVGFibGUgMS4qKiBDb25mdXNpb24gbWF0cml4IGV4YW1wbGUuCgpgYGB7ciBlY2hvID0gVH0KYWNjIDwtIHN1bShzYW1wbGVGcmFtZSRvYnNlcnZlZCA9PSBzYW1wbGVGcmFtZSRwcmVkaWN0ZWQpL2RpbShzYW1wbGVGcmFtZSlbMV0KcHJpbnQocGFzdGUwKCJNb2RlbCBhY2N1cmFjeSA9ICIsIHJvdW5kKGFjYywgMiksICIuIikpCmBgYAoKV2hlbiB3ZSBsb29rIGF0IHRoZSBIaXQgcmF0ZSB3ZSBzZWUgdGhhdDoKCmBgYHtyIGVjaG8gPSBUfQpoaXRzIDwtIHN1bShzYW1wbGVGcmFtZSRvYnNlcnZlZCA9PSAxICYgc2FtcGxlRnJhbWUkcHJlZGljdGVkID09IDEpL3N1bShzYW1wbGVGcmFtZSRvYnNlcnZlZCA9PSAxKQpwcmludChwYXN0ZTAoIkhpdCByYXRlID0gIiwgcm91bmQoaGl0cywgMiksICIuIikpCmBgYAoKRmluYWxseSwgdGhlIEZhbHNlIGFsYXJtIHJhdGUgb2YgdGhlIG1vZGVsIGlzOgoKYGBge3IgZWNobyA9IFR9CkZBcyA8LSBzdW0oc2FtcGxlRnJhbWUkb2JzZXJ2ZWQgPT0gMCAmIHNhbXBsZUZyYW1lJHByZWRpY3RlZCA9PSAxKS9zdW0oc2FtcGxlRnJhbWUkb2JzZXJ2ZWQgPT0gMSkKcHJpbnQocGFzdGUwKCJGQSByYXRlID0gIiwgcm91bmQoRkFzLCAyKSwgIi4iKSkKYGBgCgpTbywgdGhlIG1vZGVsIHdvdWxkIHJlY29nbml6ZSBhIGNyaXRpY2FsIHF1ZXJ5IGNvcnJlY3RseSA3MSUgb2YgdGltZSwgd2hpbGUgMzAlIG9mIHRpbWUgaXQgd291bGQgaW5jb3JyZWN0bHkgcHJlZGljdCBhIG5vbi1jcml0aWNhbCBxdWVyeSB0byBiZSBjcml0aWNhbC4gSW1hZ2luZSBpZiB3ZSB1c2UgdGhpcyBtb2RlbCB0byBjaG9zZSB3aGVuIHRvIHN0YXJ0IGFuZCB3aGVuIG5vdCB0byBzdGFydCBzb21lIG9wdGltaXphdGlvbiBwcm9jZWR1cmUgaW4gV0RRUyAtIGJlIGl0IGFzIHNpbXBsZSBhcyBkaXZlcmdpbmcgZGlmZmVyZW50IHF1ZXJpZXMgKGNyaXRpY2FsIHZzLiBub24tY3JpdGljYWwpIHRvIHNlcGFyYXRlIHByb2Nlc3NpbmcgcmVzb3VyY2VzLiBGcm9tIHRoZSB2aWV3cG9pbnQgb2YgYW55IHN5c3RlbSBvcHRpbWl6YXRpb24sIHRoZSBkZWNpc2lvbiBpcyBuZWVkZWQ6IGNhbiB3ZSBsaXZlIHdpdGggdGhlc2UgdHdvIHZhbHVlcywgb3Igbm90PyBUaGUgRmFsc2UgQWxhcm0gc3VnZ2VzdHMgdGhhdCAzMCUgb2YgdGltZSB0aGUgbW9kZWwgd291bGQgc3VnZ2VzdCB0aGUgb3B0aW1pemF0aW9uIHJvdXRpbmUgdG8gc3RhcnQgaW4gYSBzaXR1YXRpb24gd2hlbiBpdCBzaG91bGQgbm90IHN0YXJ0IGFuZCB0aHVzIHNwYXJlIHZhbHVhYmxlIHN5c3RlbSByZXNvdXJjZXMuIEFnYWluLCAxMDAlIC0gNzElIChIaXQgcmF0ZSkgPSAyOSUgb2YgdGltZSB0aGUgbW9kZWwgd291bGQgbWlzcyB0byB0cmlnZ2VyIHRoZSBvcHRpbWl6YXRpb24gcm91dGluZSBmb3IgYSBjcml0aWNhbCBxdWVyeSB0aGF0IGNvdWxkIGVuZ2FnZSB0b28gbWFueSByZXNvdXJjZXMuCgpBIGdvb2QgcHJlZGljdGl2ZSBtb2RlbCBpcyB0aGUgb25lIHdoaWNoIGF0dGFpbnMgKGEpIGEgaGlnaCBIaXQgcmF0ZSAoaS5lLiBvYnNlcnZlZCBjcml0aWNhbCBhbmQgcHJlZGljdGVkIGNyaXRpY2FsKSwgYW5kIChiKSBhIGxvdyBGYWxzZSBBbGFybSByYXRlIChpLmUuIG9ic2VydmVkIG5vbi1jcml0aWNhbCBidXQgcHJlZGljdGVkIGNyaXRpY2FsKS4gSW4gb3RoZXIgd29yZHMsIHdlIGRvIG5vdCB3YW50IG91ciBtb2RlbCB0byBtaXNzIGNyaXRpY2FsIHF1ZXJpZXMgYW5kIHRodXMgbm90IHRyaWdnZXIgc29tZSBvcHRpbWl6YXRpb24gcHJvY2VkdXJlIHRvIGhlbHAgV0RRUyBiZXR0ZXIgdXNlIGl0cyByZXNvdXJjZXMsIGJ1dCB3ZSBhbHNvIGRvIG5vdCB3YW50IG91ciBtb2RlbCB0byB0cmlnZ2VyIGFueSBvcHRpbWl6YXRpb24gcHJvY2VkdXJlcyBvbiBhIHF1ZXJ5IHRoYXQgd291bGQgbm90IHJlc3VsdCBpbiBhIGNyaXRpY2FsbHkgaGlnaCB1c2FnZSBvZiByZXNvdXJjZXMuCgoqKk91ciBjdXJyZW50IHJlc3VsdHMqKjoKCi0gdGhlIHJlc3VsdHMgdmFyeSB3aXRoIHRoZSBudW1iZXIgb2YgZmVhdHVyZXMgdXNlZCBhbmQgc3BlY2lmaWMgdmFsdWVzIG9mIG1vZGVsIHBhcmFtZXRlcnMsIGJ1dCB0aGV5IGRvIG5vdCB2YXJ5IHNpZ25pZmljYW50bHk6Ci0gdHlwaWNhbCBtb2RlbCBhY2N1cmFjeSBpcyBhcm91bmQgOTUlOwotIHR5cGljYWwgSGl0IHJhdGUgaXMgYXJvdW5kIDQ4JSwgYW5kCi0gdHlwaWNhbCBGYWxzZSBhbGFybSByYXRlIGlzIHNsaWdodGx5IGFib3ZlIDIlLgoKTm93IHdlIGNhbiBzZWUgaG93IG1pc2xlYWRpbmcgaXMgbW9kZWwgYWNjdXJhY3kgYWxvbmUuCgoqKlR3ZWFrcyBhbmQgQ29tcHJvbWlzZXMqKgoKLSBXaXRob3V0IGdvaW5nIGludG8gZGV0YWlsIGFib3V0IGl0LCBzaW5jZSB0aGlzIGlzIGEgYmluYXJ5IGNsYXNzaWZpY2F0aW9uIHRhc2ssIHRoZXJlIGlzIGEgbGVnaXRpbWF0ZSB3YXkgdG8gdHdlYWsgYSBtb2RlbCBhbmQgYXR0YWluIGEgaGlnaGVyIGhpdCByYXRlIChieSBtYW5pcHVsYXRpbmcgdGhlIHNvIGNhbGxlZCBkZWNpc2lvbiBib3VuZGFyeSksIGJ1dCBhdCB0aGUgZXhwZW5zZSBvZiBpbmNyZWFzaW5nIHRoZSBGYWxzZSBBbGFybSByYXRlIGF0IHRoZSBzYW1lIHRpbWUuIEZvciBleGFtcGxlLCB3aXRoIHRoZSBjdXJyZW50IHJlc3VsdHMsIHdlIGNhbiBhY2hpZXZlIGEgSGl0IHJhdGUgYXMgaGlnaCBhcyA4NCUgYnV0IGF0IHRoZSBleHBlbnNlIG9mIGluY3JlYXNpbmcgdGhlIEZhbHNlIEFsYXJtIHJhdGUgdG8gYWxtb3N0IDQwJSBhdCB0aGUgc2FtZSB0aW1lLgoKSG93IGRvIHdlIGNvbXByb21pc2UgYWJvdXQgdGhpcyBwcmVkaWN0aW9uIG1vZGVsPyBGaXJzdCB0byByZW1pbmQ6IGl0IGlzIGEgYmluYXJ5IGNsYXNzaWZpY2F0aW9uIG1vZGVsLiBUaGUgbW9kZWwgZG9lcyBub3QgZGlyZWN0bHkgb3V0cHV0IGl0cyBwcmVkaWN0aW9ucyBhcyBjbGFzc2VzOiBgMWAgLSBjcml0aWNhbCwgYDBgIC0gdHlwaWNhbCwgYXMgaW4gdGhlIGV4YW1wbGUgaW4gVGFibGUgMS4gUmF0aGVyLCB0aGUgbW9kZWwgbGVhcm5zIGFib3V0IHRoZSBxdWVyaWVzIGZyb20gdGhlaXIgZmVhdHVyZSB2ZWN0b3JzIGFuZCBwcmVkaWN0cyBhICoqcHJvYmFiaWxpdHkqKiB0aGF0IHRoZSBxdWVyeSB3aWxsIGNyaXRpY2FsIChgMWApLiBOb3csIGluIG9yZGVyIHRvIGNhdGVnb3JpemUgdGhlIHF1ZXJ5IGFzIGNyaXRpY2FsIG9yIG5vdCwgd2UgbmVlZCB0byBzZXQgYSB0cmVzaG9sZCB2YWx1ZSAoaS5lLiBhIGRlY2lzaW9uIGJvdW5kYXJ5KSwgYC41YCBmb3IgZXhhbXBsZSwgYW5kIHByb25vbnVjZSB0aGUgbW9kZWwgcHJlZGljdGlvbiB0byBiZSBgMWAgZWFjaCB0aW1lIGEgcHJlZGljdGVkIHByb2JhYmlsaXR5IGZvciBhIHF1ZXJ5IGlzIGFib3ZlIHRoZSB0cmVzaG9sZCB2YWx1ZS4gQSB2YWx1ZSBvZiBgLjVgIGlzIGFuIGludHVpdGl2ZSBjaG9pY2UgZm9yIGJpbmFyeSBjbGFzc2lmaWNhdGlvbiwgaG93ZXZlciwgd2UgbWlnaHQgcGljayBhbm90aGVyIHZhbHVlIGFzIHdlbGwuIEl0IGlzIGJ5IG1hbmlwdWxhdGluZyB0aGUgdHJlc2hvbGQgdmFsdWUgaG93IHdlIGVudGVyIGEgdHJhZGUtb2ZmIGJldHdlZW4gVHJ1ZSAoSGl0KSBhbmQgRmFsc2UgUG9zaXRpdmUgKEZhbHNlIEFsYXJtKSByYXRlcy4gSW4gdGhlIGZvbGxvd2luZyBjaGFydCB0aGUgdHJlc2hvbGQgdmFsdWUgdmFyaWVzIHNtb290aGx5IGJldHdlZW4gMCBhbmQgMSwgdGhlIFRydWUgUG9zaXRpdmUgcmF0ZSBpcyBmb3VuZCBvbiB0aGUgdmVydGljYWwsIGFuZCB0aGUgRmFsc2UgUG9zaXRpdmUgcmF0ZSBvbiB0aGUgaG9yaXpvbnRhbCBheGlzOgoKKipDaGFydCAxLioqCgpgYGB7ciBlY2hvfQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkoZ2dyZXBlbCkKbG9jRGlyIDwtICcvaG9tZS9nb3JhbnNtL1dvcmsvX19fRGF0YUtvbGVrdGl2L1Byb2plY3RzL1dpa2ltZWRpYURFVS9fV01ERV9Qcm9qZWN0cy9XaWtpZGF0YS9XRF9NaXNjL1dEX1NQQVJRTF9FbmRwb2ludF9BbmFseXRpY3MvX2FuYWx5dGljcy8nCnJvY01vZGVsIDwtIHJlYWQuY3N2KHBhc3RlMChsb2NEaXIsICdyb2NNb2RlbC5jc3YnKSwKICAgICAgICAgICAgICAgICAgICAgaGVhZGVyID0gVCwgCiAgICAgICAgICAgICAgICAgICAgIHJvdy5uYW1lcyA9IDEsCiAgICAgICAgICAgICAgICAgICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGKQpyb2NNb2RlbCRkaWZmIDwtIHJvY01vZGVsJGhpdCAtIHJvY01vZGVsJEZBCndNYXhEaWZmIDwtIHdoaWNoLm1heChyb2NNb2RlbCRkaWZmKQpyb2NNb2RlbFt3TWF4RGlmZiwgXQpyb2NNb2RlbCRsYWJlbCA8LSAnJwpyb2NNb2RlbCRsYWJlbFt3TWF4RGlmZl0gPC0gcGFzdGUwKCdIaXQ6ICcsIHJvdW5kKHJvY01vZGVsJGhpdFt3TWF4RGlmZl0sIDIpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiOyBGQTogIiwgcm91bmQocm9jTW9kZWwkRkFbd01heERpZmZdLCAyKSkKZ2dwbG90KGRhdGEgPSByb2NNb2RlbCwKICAgICAgIGFlcyh4ID0gRkEsIHkgPSBoaXQsIGxhYmVsID0gbGFiZWwpKSArIAogIGdlb21fcG9pbnQoc2l6ZSA9IC43NSkgKyBnZW9tX3BhdGgoc2l6ZSA9IC4yNSwgY29sb3IgPSAiYmx1ZSIpICsgCiAgZ2dyZXBlbDo6Z2VvbV90ZXh0X3JlcGVsKCkgKyAKICB4bGFiKCdGYWxzZSBQb3NpdGl2ZXMgKEZhbHNlIEFsYXJtIFJhdGUpJykgKyB5bGFiKCdUcnVlIFBvc2l0aXZlcyAoSGl0IFJhdGUpJykgKwogIHRoZW1lX2J3KCkKYGBgCgpXZSBjYW4gc2VlIHRoYXQgdGhlIHRoZXJlIGlzIGEgdHJlc2hvbGQgdmFsdWUgd2hlcmUgd2Ugb2JzZXJ2ZSBhIDY3JSBUcnVlIFBvc2l0aXZlIFJhdGUgYW5kIGEgMTIlIEZhbHNlIFBvc2l0aXZlIFJhdGU6IHRoZSBkaWZmZXJlbmNlIGJldHdlZW4gdGhlIHR3byBpcyBoaWdoZXN0IHdoZW4gYHRyZXNob2xkID0gLjJgLgoKSW4gZWZmZWN0IHRoaXMgbWVhbnM6CgotIGltYWdpbmUsIGFnYWluLCB0aGF0IHdlIGRldmVsb3AgYSBzeXN0ZW0gdG8gb3B0aW1pemUgdGhlIFdEUVMgYnkgc29tZWhvdyBzcGxpdHRpbmcgaXRzIHJlc291cmNlcyBpbnRvIHRob3NlIHRoYXQgcHJvY2VzcyBxdWVyaWVzIHdpdGggdHlwaWNhbCBleHBlY3RlZCBwcm9jZXNzaW5nIHRpbWVzIGZyb20gdGhvc2Ugd2l0aCBjcml0aWNhbCBleHBlY3RlZCBwcm9jZXNzaW5nIHRpbWVzOwotIG91ciBjdXJyZW50IG1vZGVsbGluZyBlZmZvcnRzIGxldCB1cyBrbm93IHRoYXQgd2UgY2FuIGluY3JlYXNlIHRoZSBudW1iZXIgb2YgVHJ1ZSBQb3NpdGl2ZXMgdG8gdHdvLXRoaXJkcyBhdCB0aGUgZXhwZW5zZSBvZiBvYnNlcnZpbmcgMTIlIG9mIHR5cGljYWwgcXVlcmllcyBhcyBpZiB0aGV5IHdlcmUgY3JpdGljYWw7Ci0gdGhhdCBtZWFucyB0aGUgc3lzdGVtIHdvdWxkIGNvcnJlY3RseSBydW4gb24gY3JpdGljYWwgcHJvY2Vzc2luZyByZXNvdXJjZXNzIDIvMyBvZiB0aGUgdGltZSwgd2hpbGUgaXQgd291bGQgZXJyb25lb3VzbHkgcnVuIG9uIGNyaXRpY2FsIHByb2Nlc3NpbmcgcmVzb3VyY2VzIDEyJSBvZiB0aW1lIChpLmUuIDEyJSBvZiBxdWVyaWVzIHNob3VsZCBiZSBwcm9jZXNzZWQgd2l0aCB0aGUgdHlwaWNhbCBwcm9jZXNzaW5nIHJlc291cmNlcyBidXQgdGhleSB3b3VsZCBpbmNvcnJlY3RseSB0cmlnZ2VyIHRoZSBjcml0aWNhbCBwcm9jZXNzaW5nIHJlc291cmNlcyk7Ci0gb3IsIHRvIHJlcGhyYXNlOiB0aGUgbW9kZWwgd291bGQgdGhpbmsgYSBjcml0aWNhbCBxdWVyeSBpcyBub24tY3JpdGljYWwgb25lLXRoaXJkIG9mIGEgdGltZSwgYW5kIGRlY2lkZSB0byBwYXNzIHN1Y2ggYSBxdWVyeSB0byBhIG5vcm1hbCwgdHlwaWNhbCBXRFFTIHByb2Nlc3NpbmcsIHdoaWxlIGF0IHRoZSBzYW1lIHRpbWUgaXQgd291bGQgcmVjb2duaXplIGEgbm9uLWNyaXRpY2FsIHF1ZXJ5IGFzIG5vbi1jcml0aWNhbCA4OCUgb2YgdGltZSBhbmQgdGhhbiBpdCB3b3VsZCBub3QgdHJpZ2dlciBhbnkgb3B0aW1pemF0aW9uIGFjdGlvbi4gCgpUaGUgcXVlc3Rpb24gaXM6IGNhbiB3ZSBhZmZvcmQgc3VjaCBhbiBvcHRpbWl6ZXIgZm9yIFdEUVM/IFdoYXQgaXMgdGhlIHRyYWRlLW9mZiB0aGF0IHdlIGNhbiBhY2NlcHQgdG8gbGl2ZSB3aXRoPwoKIyMgNC4gRGlyZWN0aW9ucyBmb3IgZnV0dXJlIHJlc2VhcmNoICsgY29uc2lkZXJhdGlvbnMgb2YgcG9zc2libGUgcHJvYmxlbXMgaW4gV0RRUyBvcHRpbWl6YXRpb24KCioqRGlyZWN0aW9ucyBmb3IgZnVydGhlciByZXNlYXJjaCAodGltZSBlc3RpbWF0ZTogdW50aWwgdGhlIGVuZCBvZiB0aGUgbmV4dCB3ZWVrKSoqCgotIFRoZSAqKnNhbXBsaW5nIGFwcHJvYWNoKiogYXBwZWFycyB0byBiZSByb2J1c3QsIGJ1dCB3ZSBuZWVkIHRvIHJlcGxpY2F0ZSBhbGwgZmVhdHVyZSBlbmdpbmVlcmluZyBhbmQgTUwgcHJvY2VkdXJlcyBhY3Jvc3MgYXQgbGVhc3Qgc2V2ZXJhbCBtb3JlIHNhbXBsZXM7Ci0gVGhlICoqZmVhdHVyZSBlbmdpbmVlcmluZyoqIHByb2NlZHVyZXMgYXJlIGFwcHJveGltYXRlOiB0aGV5IGNvdWxkIGFuZCBzaG91bGQgYmUgaW1wcm92ZWQ7Ci0gVGhlICoqTUwqKiByZXN1bHRzIHdlcmUgZGlzY3Vzc2VkIGFuZCBpdCBpcyB1bmNlcnRhaW4gaWYgZnVydGhlciBzaWduaWZpY2FudCBpbXByb3ZlbWVudHMgYXJlIHBvc3NpYmxlLCBidXQgYXQgbGVhc3QgdGhlIGZvbGxvd2luZyBzaG91bGQgYmUgdGVzdGVkOgogICAgLSB3b3JraW5nIHdpdGggY29ycmVjdGlvbnMgZm9yIGltYmFsYW5jZWQgZGVzaWduczogd2Ugb2JzZXJ2ZSBhIHZlcnkgbG93IG51bWJlciBvZiBjcml0aWNhbCBxdWVyaWVzIGNvbXBhcmVkIHRvIGEgaHVnZSBudW1iZXIgb2YgdHlwaWNhbCBxdWVyaWVzIGluIG91ciBzYW1wbGU7IHNvbWUgY29ycmVjdGlvbnMgdG8gY29udHJvbCBmb3IgdGhpcyBpbWJhbGFuY2UgYXJlIGFscmVhZHkgYXBwbGllZCwgYnV0IHRoZXJlIGFyZSBvdGhlciBhcHByb2FjaGVzIHRoYXQgbWlnaHQgYmUgaGVscGZ1bCB0byBwb3RlbnRpYWxseSB0cmFpbiBiZXR0ZXIgY2xhc3NpZmllcnM7CiAgICAtIG5vdCBhbGwgWEdCb29zdCBwYXJhbWV0ZXJzIHRoYXQgbWlnaHQgaGVscCB0cmFpbiBhIGJldHRlciBjbGFzc2lmaWVyIHdlcmUgZW50ZXJlZCBpbnRvIG91ciBjcm9zcy12YWxpZGF0aW9uIHByb2NlZHVyZXM7IHNvbWUgb2YgdGhlbSBzaG91bGQgYmUgZ2l2ZW4gYSBjaGFuZ2U7CiAgICAtIG1heWJlIGEgZGlmZmVyZW50IG1vZGVsIGFsdG9nZXRoZXIgKGUuZy4gQ29udm9sdXRpb25hbCBEZWVwIExlYXJuaW5nKSBzaG91bGQgYmUgdGVzdGVkLgoKKipGaW5hbCBjb25zaWRlcmF0aW9ucyoqCgotIElmIHdlIGFyZSBhYm91dCB0byBkZXZlbG9wIHNvbWUgc29ydCBvZiBhIFdEUVMgT3B0aW1pemF0aW9uIEVuZ2luZSBiYXNlZCBvbiB0aGlzIGFuZC9vciBzaW1pbGFyIG1vZGVscywKLSB3ZSBuZWVkIHRvIGNvbnNpZGVyIGhvdyBmYXN0IGNhbiB0aGVzZSBjbGFzcyBvZiBtb2RlbHMgcnVuIGluIHByb2R1Y3Rpb24sIGFuZAotIGlmIHRoZXkgY2FuIGdyYWIgImVudmlyb25tZW50YWwgdmFyaWFibGVzIiBmcm9tIHRoZSBXRFFTL0JsYXplZ3JhcGg6IGZvciBleGFtcGxlLCAqKm9uZSBvZiB0aGUgZmVhdHVyZXMgdGhleSB1c2UgaXMgdGhlIHRvdGFsIG51bWJlciBvZiBxdWVyaWVzIHJ1bm5pbmcgYWdhaW5zdCB0aGUgV0RRUyBpbiB0aGUgc2FtZSBtaW51dGUgaW4gd2hpY2ggdGhlIHF1ZXJ5IHVuZGVyIGFuYWx5c2lzIHdhcyBydW4qKiAtIGFuZCB0aGlzIGNhbm5vdCBiZSBrbm93biBmcm9tIFNQQVJRTCBxdWVyeSBhbmFseXNpcyBhbG9uZS4KLSBEZXBsb3ltZW50IHRvIHByb2R1Y3Rpb246IHdlIHdvdWxkIHByb2JhYmx5IG5lZWQgdG8gc3dpdGNoIGZyb20ge3hnYm9vc3R9IHRvIGl0cyBpbXBsZW1lbnRhdGlvbiBpbiB7aDJvfSB3aGVyZSB3ZSBjYW4gZGVwbG95IG1vZGVscyBhcyBKYXZhIE1PSk8gb3IgUE9KTyBvYmplY3RzLCBvciBidWlsZCBhIFJFU1QgQVBJIGZvciBhIG1vZGVsIHByZWRpY3RpdmUgcnVuOyBhbGwgdGhlc2Ugc3RlcHMgaW1wbHkgdGhhdCBzb21lIHRpbWUgd291bGQgYmUgbG9zdCBpbiBxdWVyeSBwcm9jZXNzaW5nIGJlZm9yZSB0aGUgb3B0aW1pemVyIHdvdWxkIGJlIGFibGUgdG8gZGVsaXZlciBhIHByZWRpY3Rpb24sIHNvIGRlcGxveWluZyBhIG1vZGVsIHdpdGggYSBtaW5pbXVtIHJ1bnRpbWUgbGF0ZW5jeSBpbiBhIHByZWRpY3RpdmUgcnVuIHdvdWxkIGJlIGVzc2VudGlhbC4KCioqKgpHb3JhbiBTLiBNaWxvdmFub3ZpxIcKCldpa2ltZWRpYSBEZXV0c2NobGFuZCwgRGF0YSBTY2llbnRpc3QKRGF0YUtvbGVrdGl2LCBPd25lcgoKMjAyMC4KCmNvbnRhY3Q6IGdvcmFuLm1pbG92YW5vdmljX2V4dEB3aWtpbWVkaWEuZGUKCiFbXShfaW1nL0RLX0xvZ29fMTAwLnBuZykKIVtdKF9pbWcvV2lraWRhdGEtbG9nby1lbi5wbmcpCiFbXShfaW1nL1dpa2ltZWRpYV9EZXV0c2NobGFuZF9Mb2dvX3NtYWxsLnBuZykKCgoKCgo=