A Two Agent Model of Forward Electricity Prices in Brazil with Generalized Extended CVaR Preferences

Authors: Felipe Van de Sande Araujo, Cristina Pimenta de Mello Spineti Luz, Leonardo Lima Gomes, Luiz Eduardo Teixeira Brandão

Abstract: Despite its continental size and integrated electrical system, Brazil does not have an exchange for trading forward and futures contracts for electricity. Thus, price information for long-term contracts is often obtained through market research and expert opinions. This article proposes a simple yet efficient approach to estimate the forward price of electricity in the Brazilian energy market. The model is based on the equilibrium between two representative agents negotiating bilateral contracts where the agents’ risk aversion is derived from the utility functions related to the Generalized Extended Conditional Value-at-Risk Preference. This model is comprehensive and can be applied to all agents participating in the electricity futures transaction independent of whether they are directly involved in the production chain or simply carry speculative positions. Our results indicate that the model’s forecasted prices, which are based on the participants’ expected behavior, can be used as an indicator for the forward price of electricity, providing more transparency and security for the participants in this market.

This work presents the calculations done for the article referenced above. The following calculations show an optimization of the parameters of an ECP_G risk measure for two agents transacting bilateral contracts in the power market. All the code was run in RStudio using the version of the software below.

Software version

R.version
               _                           
platform       x86_64-w64-mingw32          
arch           x86_64                      
os             mingw32                     
system         x86_64, mingw32             
status                                     
major          4                           
minor          0.3                         
year           2020                        
month          10                          
day            10                          
svn rev        79318                       
language       R                           
version.string R version 4.0.3 (2020-10-10)
nickname       Bunny-Wunnies Freak Out     

Setting of the environment

# Set the random seed
set.seed(2020) # The year of the start of this project

Local functions definition

The first function is the determination of the ECPG phi which is the certainty equivalent of the agent when it is directly exposed to the spot prices.

ECPG.phi <- function(parameters, dataset, auxList, buyer=TRUE) {
  if (any(parameters < 0) || any(parameters > 1)) return(NA)
  sumLambda = parameters['Lambda1'] + parameters['Lambda2']
  if (sumLambda > 1) return(NA)
  Lambda0 = 1 - sumLambda
  if (Lambda0 < 0) return(NA)
  
  if (buyer) {
    mean.pld = auxList$meanPLD
    VaR0 = auxList$VaR0
    VaR1 = auxList$VaR1
  } else {
    dataset = -dataset
    mean.pld = -auxList$meanPLD
    VaR0 = auxList$VaR1
    VaR1 = auxList$VaR0
  }
  
  VaRAlpha1 = apply(dataset, 1, quantile, (1-parameters['Alpha1']))
  VaRAlpha2 = apply(dataset, 1, quantile, (1-parameters['Alpha2']))
  
  CVaR1 = CVaR2 = NA
  
  CVaR1.data = dataset
  CVaR1.data[dataset > VaRAlpha1] <- NA
  
  CVaR2.data = dataset
  CVaR2.data[dataset > VaRAlpha2] <- NA
  
  CVaR1 = apply(CVaR1.data, 1, mean, na.rm=TRUE)
  CVaR2 = apply(CVaR2.data, 1, mean, na.rm=TRUE)
  
  Sum1 = parameters['Lambda1']*VaRAlpha1 + parameters['Lambda2']*VaRAlpha2
  Sum2 = parameters['Lambda1']*(CVaR1 - VaRAlpha1) + parameters['Lambda2']*(CVaR2 - VaRAlpha2)
  
  Limit1 = Lambda0*VaR0 + Sum1
  Limit2 = Lambda0*VaRAlpha1 + Sum1
  Limit3 = Lambda0*VaRAlpha2 + Sum1
  Limit4 = Lambda0*VaR1 + Sum1
  
  ECPG = Lambda0*mean.pld + parameters['Lambda1']*CVaR1 + parameters['Lambda2']*CVaR2
  
  nIter = nrow(dataset)
  Lambda1 = rep(parameters['Lambda1'], nIter)
  Lambda2 = rep(parameters['Lambda2'], nIter)
  
  if (any(ECPG>=Limit2)) Lambda1[ECPG>=Limit2] = 0
  if (any(ECPG>=Limit3)) Lambda2[ECPG>=Limit3] = 0
  
  Q = Lambda0 + Lambda1/(1-parameters['Alpha1']) + Lambda2/(1-parameters['Alpha2'])
  
  A = Sum2 + Lambda0*mean.pld + 
    Lambda1*VaRAlpha1/(1-parameters['Alpha1']) +
    Lambda2*VaRAlpha2/(1-parameters['Alpha2'])
  
  Eq = A/Q
  
  if (any(is.na(Eq))) return(NA)
  return(Eq)
}

We will use a list with all the parameters and data which will be called ECPG_Object, in order to facilitate the next function calls. The next function will create the list.

createECPGObject <- function(dataset, discountVector, forwardPrice, buyerParam, sellerParam) {
  VaR0 = apply(dataset, 1, quantile, 1)
  VaR1 = apply(dataset, 1, quantile, 0)
  meanPLD = apply(dataset, 1, mean)
  
  ECPGObject = list(
    dataset=dataset,
    discountVector=discountVector,
    forwardPrice=forwardPrice,
    buyerParam=buyerParam,
    sellerParam=sellerParam,
    VaR0=VaR0,
    VaR1=VaR1,
    meanPLD=meanPLD
  )
  return(ECPGObject)
}

Then we will define a function to obtain the equilibrium price using the calculated phi for each agent. The distinction between the agents must be made because the signal of the price data changes between them, because of the revenue equations.

ECPG.equilibrium <- function (ECPGobject) {
  auxList = list(
    VaR0 = ECPGobject$VaR0,
    VaR1 = ECPGobject$VaR1,
    meanPLD = ECPGobject$meanPLD
  )
  
  buyer.phi = ECPG.phi(ECPGobject$buyerParam, ECPGobject$dataset, auxList)
  seller.phi = -ECPG.phi(ECPGobject$sellerParam, ECPGobject$dataset, auxList, buyer=FALSE)
  
  equilibrium.prices = (buyer.phi + seller.phi)/2
  
  adjusted.prices = equilibrium.prices/ECPGobject$discountVector
  
  averagePrice = yearly.mean(adjusted.prices)
  if (length(averagePrice) != length(ECPGobject$forwardPrice)) stop("Error in ECPG.equilibrium function: Length of fowardPrice vector is different from the length of data divided by 12!")
  
  ECPGobject$buyerPhi = buyer.phi
  ECPGobject$sellerPhi = seller.phi
  ECPGobject$equilibriumPrices = equilibrium.prices
  ECPGobject$adjustedPrices=adjusted.prices
  ECPGobject$averagePrice=averagePrice
  
  return(ECPGobject)
}

The next function will be used for the optimization with R command optim.

optim.foo <- function(parameters, ECPGobject) {
  param_b = parameters[1:4]
  param_s = parameters[5:8]
  names(param_b) = names(param_s) = c('Lambda1', 'Lambda2', 'Alpha1', 'Alpha2')
  
  ECPGobject$buyerParam = param_b
  ECPGobject$sellerParam = param_s
  
  averagePrice = ECPG.equilibrium(ECPGobject)$averagePrice
  
  return(mean((averagePrice - ECPGobject$forwardPrice)^2))
}

The next function is used to calculate the discount vector based on the risk-free rates for each data point.

getDiscountVector <- function(index, modifier, anualRate) {
  yearAdj = (11+modifier*12)
  monthlyRate = (1+anualRate)^(1/12)-1
  completeDiscountVector <- rep(NA, yearAdj)
  for (i in 1:yearAdj){
    completeDiscountVector[i] <- (1+monthlyRate)^(i-1)
  }
  
  getDiscountBounds <- function(index, modifier, completeDiscountVector) {
    lower.limit = 1+12*modifier-index
    upper.limit = 12+12*modifier-index
    
    return(completeDiscountVector[lower.limit:upper.limit])
  }
  
  discountVector = as.numeric(sapply(index, getDiscountBounds, modifier=modifier, completeDiscountVector=completeDiscountVector))
  return(discountVector)
}

We define an acessory function to calculate the yearly mean using the discount vector.

yearly.mean <- function(x) {
  len = length(x)
  if (len/12 > len %/%12) stop("Error in yearly.mean function: Length of data is not divisible by 12!")
  
  mean.vec=NULL
  
  iter = len/12
  for (i in 1:iter) {
    mean.vec[i] = mean(x[(1+(i-1)*12):(i*12)])
  }
  return(mean.vec)
}

Local variables definition

Set the initial optimization parameters. They must provide the optimization function with a non-NA return, which is a requirement from optim.

buyer.initial.param = c(Lambda1 = 0, Lambda2 = 0, Alpha1 = 0.5, Alpha2 = 0.95) # Initial Parameters
seller.initial.param = c(Lambda1 = 0, Lambda2 = 0, Alpha1 = 0.5, Alpha2 = 0.95) # Initial Parameter

Set the annual discount rate (the economy risk-free rate). A sensitivity analysis for this rate is provided in the sensitivity analysis sector (link).

anualRate = 0.05

Set the data points, defined by month and year, to be loaded for model training.

data.index = rbind(
  c(7, 2019),
  c(8, 2019),
  c(9, 2019),
  c(10, 2019),
  c(11, 2019),
  c(12, 2019),
  c(1, 2020),
  c(2, 2020)
)

# Define the number of years ahead that the forward price is applied to.
seriesA = 1

Loading the data

Load the forward price for electricity obtained from DCide Energia.

forwardPrice.data = read.csv(file='./Data/ForwardPrices.csv')

Loading future spot price (PLD) data.

data.abbrev = paste(month.abb[data.index[, 1]], data.index[,2], sep="_")

price.data = list()
for (n in 1:8) {
  price.data[[data.abbrev[n]]] <- t(read.csv(file=paste('./Data/Data', data.abbrev[n], paste0('A', seriesA, '.csv'), sep="_"), col.names=paste(month.abb, data.index[n,2]+seriesA, sep="-")))
}

Allocate and verify data

training.discountVector = getDiscountVector(data.index[,1], seriesA, anualRate)

len = length(training.discountVector)
if (len/12 > len %/%12) stop("Error in discountVector allocation: Length of data is not divisible by 12!")

training.forwardPrice = as.numeric(forwardPrice.data[seriesA, 2:9])
if(len/12 != length(training.forwardPrice)) stop("Error in forwardPrice: Length of data is not compatible with discountVector!")

training.data = Reduce(rbind, price.data)
if (nrow(training.data) != len) stop("Error in trainingDataA2: the number of rows is not compatible with the discountVector!")

training.ECPG_object = createECPGObject(dataset = training.data, buyerParam = buyer.initial.param, sellerParam = seller.initial.param, discountVector = training.discountVector, forwardPrice = training.forwardPrice)

Inputting the data object to the equilibrium function will instantaneously provide the equilibrium price given the selected initial parameters.

training.initialResults = ECPG.equilibrium(training.ECPG_object)
print(training.initialResults$adjustedPrices)
 Jan.2020  Feb.2020  Mar.2020  Apr.2020  May.2020  Jun.2020  Jul.2020  Aug.2020  Sep.2020  Oct.2020 
130.31658 113.69551 100.80909  98.40887  93.20563  97.33694 103.22188 101.35799 100.25743  97.07982 
 Nov.2020  Dec.2020  Jan.2020  Feb.2020  Mar.2020  Apr.2020  May.2020  Jun.2020  Jul.2020  Aug.2020 
 92.90089  80.19736 172.92066 154.94408 135.66051 119.61474 116.73274 124.16783 124.92827 123.44964 
 Sep.2020  Oct.2020  Nov.2020  Dec.2020  Jan.2020  Feb.2020  Mar.2020  Apr.2020  May.2020  Jun.2020 
119.94780 115.01348 109.66032 102.89738 168.36718 148.11347 135.61932 115.12322 104.33096 112.05194 
 Jul.2020  Aug.2020  Sep.2020  Oct.2020  Nov.2020  Dec.2020  Jan.2020  Feb.2020  Mar.2020  Apr.2020 
119.01736 113.08721 111.84146 105.98660 101.98258  96.38066 154.02275 154.64323 142.27034 126.64928 
 May.2020  Jun.2020  Jul.2020  Aug.2020  Sep.2020  Oct.2020  Nov.2020  Dec.2020  Jan.2020  Feb.2020 
111.28805 114.83834 120.96265 119.45808 113.87291 111.42977 105.26910  97.62996 189.40988 169.72126 
 Mar.2020  Apr.2020  May.2020  Jun.2020  Jul.2020  Aug.2020  Sep.2020  Oct.2020  Nov.2020  Dec.2020 
170.38496 147.99881 131.35556 131.85121 136.38243 137.53981 134.24549 123.21282 117.93440 105.24825 
 Jan.2020  Feb.2020  Mar.2020  Apr.2020  May.2020  Jun.2020  Jul.2020  Aug.2020  Sep.2020  Oct.2020 
219.86712 189.78008 175.68983 164.38756 144.76853 149.16315 151.72033 148.53520 147.37294 141.21168 
 Nov.2020  Dec.2020  Jan.2021  Feb.2021  Mar.2021  Apr.2021  May.2021  Jun.2021  Jul.2021  Aug.2021 
129.41579 117.97894  82.29963  74.83584  71.34283  67.47625  66.05609  73.48056  78.88422  77.43999 
 Sep.2021  Oct.2021  Nov.2021  Dec.2021  Jan.2021  Feb.2021  Mar.2021  Apr.2021  May.2021  Jun.2021 
 75.64360  73.43770  70.61789  68.79611  98.54072  88.91851  83.04969  76.25934  73.06132  80.35147 
 Jul.2021  Aug.2021  Sep.2021  Oct.2021  Nov.2021  Dec.2021 
 84.68324  84.70222  83.97916  79.65823  76.24243  71.92401 

The object carries all the provided parameters, so it is easy to verify after the execution.

Optimization of parameters

Now the optimization will start. It can take up to 10 minutes running in a single core. Parallelism could be also used if need arise.

## Run the optimization - uses proc.time to measure the execution time
ptm = proc.time()

optim.results = optim(c(buyer.initial.param, seller.initial.param), optim.foo, ECPGobject=training.ECPG_object, method="SANN")

timeSpent = proc.time() - ptm
print(timeSpent)
   user  system elapsed 
 870.14    9.66  882.42 

The optimized parameters are extracted from the optimization results.

## Get ECPG parameters from optimization results
training.ECPG_object$buyerParam = optim.results$par[1:4]
training.ECPG_object$sellerParam = optim.results$par[5:8]

Results

The optimal parameters are used to calculate the equilibrium prices. The original object is overwritten because there is no data loss, only new information is added.

## Calculate results from optimized parameters
training.ECPG_object = ECPG.equilibrium(training.ECPG_object)
print(training.ECPG_object$adjustedPrices)
Jan.2020 Feb.2020 Mar.2020 Apr.2020 May.2020 Jun.2020 Jul.2020 Aug.2020 Sep.2020 Oct.2020 Nov.2020 
164.1603 265.1970 216.7372 189.5391 185.1429 197.4354 215.3346 217.5299 229.9580 255.0911 260.1054 
Dec.2020 Jan.2020 Feb.2020 Mar.2020 Apr.2020 May.2020 Jun.2020 Jul.2020 Aug.2020 Sep.2020 Oct.2020 
243.2866 200.5650 187.7903 169.9147 253.8775 259.8568 271.9785 288.2995 149.2170 149.5756 139.5760 
Nov.2020 Dec.2020 Jan.2020 Feb.2020 Mar.2020 Apr.2020 May.2020 Jun.2020 Jul.2020 Aug.2020 Sep.2020 
132.7791 127.7754 190.6412 175.4352 164.9706 143.6773 216.9722 225.1108 147.4742 140.3391 140.3123 
Oct.2020 Nov.2020 Dec.2020 Jan.2020 Feb.2020 Mar.2020 Apr.2020 May.2020 Jun.2020 Jul.2020 Aug.2020 
130.8242 127.3668 261.6383 178.3329 181.3986 169.6619 154.7897 139.5671 143.7310 150.3345 262.1866 
Sep.2020 Oct.2020 Nov.2020 Dec.2020 Jan.2020 Feb.2020 Mar.2020 Apr.2020 May.2020 Jun.2020 Jul.2020 
266.0690 137.0331 126.6714 118.7016 220.6390 199.6275 202.9962 176.7106 163.2699 160.8085 165.6458 
Aug.2020 Sep.2020 Oct.2020 Nov.2020 Dec.2020 Jan.2020 Feb.2020 Mar.2020 Apr.2020 May.2020 Jun.2020 
164.3916 159.6192 150.2491 145.5895 268.1365 251.0878 221.8259 209.8095 198.0010 178.6772 181.2455 
Jul.2020 Aug.2020 Sep.2020 Oct.2020 Nov.2020 Dec.2020 Jan.2021 Feb.2021 Mar.2021 Apr.2021 May.2021 
184.2241 179.1611 180.6341 172.0481 160.1863 144.3433 225.3101 200.1615 183.8775 179.3444 171.4650 
Jun.2021 Jul.2021 Aug.2021 Sep.2021 Oct.2021 Nov.2021 Dec.2021 Jan.2021 Feb.2021 Mar.2021 Apr.2021 
185.8318 198.3489 213.6070 202.8853 221.1814 220.0302 202.9710 278.8835 110.5687 257.5283 195.2552 
May.2021 Jun.2021 Jul.2021 Aug.2021 Sep.2021 Oct.2021 Nov.2021 Dec.2021 
191.8043 206.1048 225.9838 199.7817 236.0811 223.8446 241.5765 238.2001 

Finally the results can be printed onscreen.

training.ECPG_object$buyerParam
   Lambda1    Lambda2     Alpha1     Alpha2 
0.25159927 0.09390924 0.32334330 0.69293115 
training.ECPG_object$sellerParam
  Lambda1   Lambda2    Alpha1    Alpha2 
0.7466865 0.1712721 0.9912755 0.7641934 

See also:

Model Validation (link)

Sensitivity Analysis (link)

LS0tDQp0aXRsZTogIk1vZGVsIFRyYWluaW5nIg0Kb3V0cHV0OiBodG1sX25vdGVib29rDQotLS0NCg0KIyMgIEEgVHdvIEFnZW50IE1vZGVsIG9mIEZvcndhcmQgRWxlY3RyaWNpdHkgUHJpY2VzIGluIEJyYXppbCB3aXRoIEdlbmVyYWxpemVkIEV4dGVuZGVkIENWYVIgUHJlZmVyZW5jZXMNCg0KQXV0aG9yczogRmVsaXBlIFZhbiBkZSBTYW5kZSBBcmF1am8sIENyaXN0aW5hIFBpbWVudGEgZGUgTWVsbG8gU3BpbmV0aSBMdXosIExlb25hcmRvIExpbWEgR29tZXMsIEx1aXogRWR1YXJkbyBUZWl4ZWlyYSBCcmFuZMOjbw0KDQpBYnN0cmFjdDogRGVzcGl0ZSBpdHMgY29udGluZW50YWwgc2l6ZSBhbmQgaW50ZWdyYXRlZCBlbGVjdHJpY2FsIHN5c3RlbSwgQnJhemlsIGRvZXMgbm90IGhhdmUgYW4gZXhjaGFuZ2UgZm9yIHRyYWRpbmcgZm9yd2FyZCBhbmQgZnV0dXJlcyBjb250cmFjdHMgZm9yIGVsZWN0cmljaXR5LiBUaHVzLCBwcmljZSBpbmZvcm1hdGlvbiBmb3IgbG9uZy10ZXJtIGNvbnRyYWN0cyBpcyBvZnRlbiBvYnRhaW5lZCB0aHJvdWdoIG1hcmtldCByZXNlYXJjaCBhbmQgZXhwZXJ0IG9waW5pb25zLiBUaGlzIGFydGljbGUgcHJvcG9zZXMgYSBzaW1wbGUgeWV0IGVmZmljaWVudCBhcHByb2FjaCB0byBlc3RpbWF0ZSB0aGUgZm9yd2FyZCBwcmljZSBvZiBlbGVjdHJpY2l0eSBpbiB0aGUgQnJhemlsaWFuIGVuZXJneSBtYXJrZXQuIFRoZSBtb2RlbCBpcyBiYXNlZCBvbiB0aGUgZXF1aWxpYnJpdW0gYmV0d2VlbiB0d28gcmVwcmVzZW50YXRpdmUgYWdlbnRzIG5lZ290aWF0aW5nIGJpbGF0ZXJhbCBjb250cmFjdHMgd2hlcmUgdGhlIGFnZW50c+KAmSByaXNrIGF2ZXJzaW9uIGlzIGRlcml2ZWQgZnJvbSB0aGUgdXRpbGl0eSBmdW5jdGlvbnMgcmVsYXRlZCB0byB0aGUgR2VuZXJhbGl6ZWQgRXh0ZW5kZWQgQ29uZGl0aW9uYWwgVmFsdWUtYXQtUmlzayBQcmVmZXJlbmNlLiBUaGlzIG1vZGVsIGlzIGNvbXByZWhlbnNpdmUgYW5kIGNhbiBiZSBhcHBsaWVkIHRvIGFsbCBhZ2VudHMgcGFydGljaXBhdGluZyBpbiB0aGUgZWxlY3RyaWNpdHkgZnV0dXJlcyB0cmFuc2FjdGlvbiBpbmRlcGVuZGVudCBvZiB3aGV0aGVyIHRoZXkgYXJlIGRpcmVjdGx5IGludm9sdmVkIGluIHRoZSBwcm9kdWN0aW9uIGNoYWluIG9yIHNpbXBseSBjYXJyeSBzcGVjdWxhdGl2ZSBwb3NpdGlvbnMuIE91ciByZXN1bHRzIGluZGljYXRlIHRoYXQgdGhlIG1vZGVs4oCZcyBmb3JlY2FzdGVkIHByaWNlcywgd2hpY2ggYXJlIGJhc2VkIG9uIHRoZSBwYXJ0aWNpcGFudHMnIGV4cGVjdGVkIGJlaGF2aW9yLCBjYW4gYmUgdXNlZCBhcyBhbiBpbmRpY2F0b3IgZm9yIHRoZSBmb3J3YXJkIHByaWNlIG9mIGVsZWN0cmljaXR5LCBwcm92aWRpbmcgbW9yZSB0cmFuc3BhcmVuY3kgYW5kIHNlY3VyaXR5IGZvciB0aGUgcGFydGljaXBhbnRzIGluIHRoaXMgbWFya2V0Lg0KDQpUaGlzIHdvcmsgcHJlc2VudHMgdGhlIGNhbGN1bGF0aW9ucyBkb25lIGZvciB0aGUgYXJ0aWNsZSByZWZlcmVuY2VkIGFib3ZlLiBUaGUgZm9sbG93aW5nIGNhbGN1bGF0aW9ucyBzaG93IGFuIG9wdGltaXphdGlvbiBvZiB0aGUgcGFyYW1ldGVycyBvZiBhbiBFQ1BfRyByaXNrIG1lYXN1cmUgZm9yIHR3byBhZ2VudHMgdHJhbnNhY3RpbmcgYmlsYXRlcmFsIGNvbnRyYWN0cyBpbiB0aGUgcG93ZXIgbWFya2V0LiBBbGwgdGhlIGNvZGUgd2FzIHJ1biBpbiBSU3R1ZGlvIHVzaW5nIHRoZSB2ZXJzaW9uIG9mIHRoZSBzb2Z0d2FyZSBiZWxvdy4NCg0KIyMjIFNvZnR3YXJlIHZlcnNpb24NCg0KYGBge3J9DQpSLnZlcnNpb24NCmBgYA0KDQojIyMgU2V0dGluZyBvZiB0aGUgZW52aXJvbm1lbnQNCg0KYGBge3J9DQojIFNldCB0aGUgcmFuZG9tIHNlZWQNCnNldC5zZWVkKDIwMjApICMgVGhlIHllYXIgb2YgdGhlIHN0YXJ0IG9mIHRoaXMgcHJvamVjdA0KYGBgDQoNCiMjIyBMb2NhbCBmdW5jdGlvbnMgZGVmaW5pdGlvbg0KDQpUaGUgZmlyc3QgZnVuY3Rpb24gaXMgdGhlIGRldGVybWluYXRpb24gb2YgdGhlIEVDUEcgcGhpIHdoaWNoIGlzIHRoZSBjZXJ0YWludHkgZXF1aXZhbGVudCBvZiB0aGUgYWdlbnQgd2hlbiBpdCBpcyBkaXJlY3RseSBleHBvc2VkIHRvIHRoZSBzcG90IHByaWNlcy4NCg0KYGBge3J9DQpFQ1BHLnBoaSA8LSBmdW5jdGlvbihwYXJhbWV0ZXJzLCBkYXRhc2V0LCBhdXhMaXN0LCBidXllcj1UUlVFKSB7DQogIGlmIChhbnkocGFyYW1ldGVycyA8IDApIHx8IGFueShwYXJhbWV0ZXJzID4gMSkpIHJldHVybihOQSkNCiAgc3VtTGFtYmRhID0gcGFyYW1ldGVyc1snTGFtYmRhMSddICsgcGFyYW1ldGVyc1snTGFtYmRhMiddDQogIGlmIChzdW1MYW1iZGEgPiAxKSByZXR1cm4oTkEpDQogIExhbWJkYTAgPSAxIC0gc3VtTGFtYmRhDQogIGlmIChMYW1iZGEwIDwgMCkgcmV0dXJuKE5BKQ0KICANCiAgaWYgKGJ1eWVyKSB7DQogICAgbWVhbi5wbGQgPSBhdXhMaXN0JG1lYW5QTEQNCiAgICBWYVIwID0gYXV4TGlzdCRWYVIwDQogICAgVmFSMSA9IGF1eExpc3QkVmFSMQ0KICB9IGVsc2Ugew0KICAgIGRhdGFzZXQgPSAtZGF0YXNldA0KICAgIG1lYW4ucGxkID0gLWF1eExpc3QkbWVhblBMRA0KICAgIFZhUjAgPSBhdXhMaXN0JFZhUjENCiAgICBWYVIxID0gYXV4TGlzdCRWYVIwDQogIH0NCiAgDQogIFZhUkFscGhhMSA9IGFwcGx5KGRhdGFzZXQsIDEsIHF1YW50aWxlLCAoMS1wYXJhbWV0ZXJzWydBbHBoYTEnXSkpDQogIFZhUkFscGhhMiA9IGFwcGx5KGRhdGFzZXQsIDEsIHF1YW50aWxlLCAoMS1wYXJhbWV0ZXJzWydBbHBoYTInXSkpDQogIA0KICBDVmFSMSA9IENWYVIyID0gTkENCiAgDQogIENWYVIxLmRhdGEgPSBkYXRhc2V0DQogIENWYVIxLmRhdGFbZGF0YXNldCA+IFZhUkFscGhhMV0gPC0gTkENCiAgDQogIENWYVIyLmRhdGEgPSBkYXRhc2V0DQogIENWYVIyLmRhdGFbZGF0YXNldCA+IFZhUkFscGhhMl0gPC0gTkENCiAgDQogIENWYVIxID0gYXBwbHkoQ1ZhUjEuZGF0YSwgMSwgbWVhbiwgbmEucm09VFJVRSkNCiAgQ1ZhUjIgPSBhcHBseShDVmFSMi5kYXRhLCAxLCBtZWFuLCBuYS5ybT1UUlVFKQ0KICANCiAgU3VtMSA9IHBhcmFtZXRlcnNbJ0xhbWJkYTEnXSpWYVJBbHBoYTEgKyBwYXJhbWV0ZXJzWydMYW1iZGEyJ10qVmFSQWxwaGEyDQogIFN1bTIgPSBwYXJhbWV0ZXJzWydMYW1iZGExJ10qKENWYVIxIC0gVmFSQWxwaGExKSArIHBhcmFtZXRlcnNbJ0xhbWJkYTInXSooQ1ZhUjIgLSBWYVJBbHBoYTIpDQogIA0KICBMaW1pdDEgPSBMYW1iZGEwKlZhUjAgKyBTdW0xDQogIExpbWl0MiA9IExhbWJkYTAqVmFSQWxwaGExICsgU3VtMQ0KICBMaW1pdDMgPSBMYW1iZGEwKlZhUkFscGhhMiArIFN1bTENCiAgTGltaXQ0ID0gTGFtYmRhMCpWYVIxICsgU3VtMQ0KICANCiAgRUNQRyA9IExhbWJkYTAqbWVhbi5wbGQgKyBwYXJhbWV0ZXJzWydMYW1iZGExJ10qQ1ZhUjEgKyBwYXJhbWV0ZXJzWydMYW1iZGEyJ10qQ1ZhUjINCiAgDQogIG5JdGVyID0gbnJvdyhkYXRhc2V0KQ0KICBMYW1iZGExID0gcmVwKHBhcmFtZXRlcnNbJ0xhbWJkYTEnXSwgbkl0ZXIpDQogIExhbWJkYTIgPSByZXAocGFyYW1ldGVyc1snTGFtYmRhMiddLCBuSXRlcikNCiAgDQogIGlmIChhbnkoRUNQRz49TGltaXQyKSkgTGFtYmRhMVtFQ1BHPj1MaW1pdDJdID0gMA0KICBpZiAoYW55KEVDUEc+PUxpbWl0MykpIExhbWJkYTJbRUNQRz49TGltaXQzXSA9IDANCiAgDQogIFEgPSBMYW1iZGEwICsgTGFtYmRhMS8oMS1wYXJhbWV0ZXJzWydBbHBoYTEnXSkgKyBMYW1iZGEyLygxLXBhcmFtZXRlcnNbJ0FscGhhMiddKQ0KICANCiAgQSA9IFN1bTIgKyBMYW1iZGEwKm1lYW4ucGxkICsgDQogICAgTGFtYmRhMSpWYVJBbHBoYTEvKDEtcGFyYW1ldGVyc1snQWxwaGExJ10pICsNCiAgICBMYW1iZGEyKlZhUkFscGhhMi8oMS1wYXJhbWV0ZXJzWydBbHBoYTInXSkNCiAgDQogIEVxID0gQS9RDQogIA0KICBpZiAoYW55KGlzLm5hKEVxKSkpIHJldHVybihOQSkNCiAgcmV0dXJuKEVxKQ0KfQ0KDQpgYGANCg0KV2Ugd2lsbCB1c2UgYSBsaXN0IHdpdGggYWxsIHRoZSBwYXJhbWV0ZXJzIGFuZCBkYXRhIHdoaWNoIHdpbGwgYmUgY2FsbGVkIEVDUEdfT2JqZWN0LCBpbiBvcmRlciB0byBmYWNpbGl0YXRlIHRoZSBuZXh0IGZ1bmN0aW9uIGNhbGxzLiBUaGUgbmV4dCBmdW5jdGlvbiB3aWxsIGNyZWF0ZSB0aGUgbGlzdC4NCg0KYGBge3J9DQpjcmVhdGVFQ1BHT2JqZWN0IDwtIGZ1bmN0aW9uKGRhdGFzZXQsIGRpc2NvdW50VmVjdG9yLCBmb3J3YXJkUHJpY2UsIGJ1eWVyUGFyYW0sIHNlbGxlclBhcmFtKSB7DQogIFZhUjAgPSBhcHBseShkYXRhc2V0LCAxLCBxdWFudGlsZSwgMSkNCiAgVmFSMSA9IGFwcGx5KGRhdGFzZXQsIDEsIHF1YW50aWxlLCAwKQ0KICBtZWFuUExEID0gYXBwbHkoZGF0YXNldCwgMSwgbWVhbikNCiAgDQogIEVDUEdPYmplY3QgPSBsaXN0KA0KICAgIGRhdGFzZXQ9ZGF0YXNldCwNCiAgICBkaXNjb3VudFZlY3Rvcj1kaXNjb3VudFZlY3RvciwNCiAgICBmb3J3YXJkUHJpY2U9Zm9yd2FyZFByaWNlLA0KICAgIGJ1eWVyUGFyYW09YnV5ZXJQYXJhbSwNCiAgICBzZWxsZXJQYXJhbT1zZWxsZXJQYXJhbSwNCiAgICBWYVIwPVZhUjAsDQogICAgVmFSMT1WYVIxLA0KICAgIG1lYW5QTEQ9bWVhblBMRA0KICApDQogIHJldHVybihFQ1BHT2JqZWN0KQ0KfQ0KYGBgDQoNClRoZW4gd2Ugd2lsbCBkZWZpbmUgYSBmdW5jdGlvbiB0byBvYnRhaW4gdGhlIGVxdWlsaWJyaXVtIHByaWNlIHVzaW5nIHRoZSBjYWxjdWxhdGVkIHBoaSBmb3IgZWFjaCBhZ2VudC4gVGhlIGRpc3RpbmN0aW9uIGJldHdlZW4gdGhlIGFnZW50cyBtdXN0IGJlIG1hZGUgYmVjYXVzZSB0aGUgc2lnbmFsIG9mIHRoZSBwcmljZSBkYXRhIGNoYW5nZXMgYmV0d2VlbiB0aGVtLCBiZWNhdXNlIG9mIHRoZSByZXZlbnVlIGVxdWF0aW9ucy4gDQoNCmBgYHtyfQ0KRUNQRy5lcXVpbGlicml1bSA8LSBmdW5jdGlvbiAoRUNQR29iamVjdCkgew0KICBhdXhMaXN0ID0gbGlzdCgNCiAgICBWYVIwID0gRUNQR29iamVjdCRWYVIwLA0KICAgIFZhUjEgPSBFQ1BHb2JqZWN0JFZhUjEsDQogICAgbWVhblBMRCA9IEVDUEdvYmplY3QkbWVhblBMRA0KICApDQogIA0KICBidXllci5waGkgPSBFQ1BHLnBoaShFQ1BHb2JqZWN0JGJ1eWVyUGFyYW0sIEVDUEdvYmplY3QkZGF0YXNldCwgYXV4TGlzdCkNCiAgc2VsbGVyLnBoaSA9IC1FQ1BHLnBoaShFQ1BHb2JqZWN0JHNlbGxlclBhcmFtLCBFQ1BHb2JqZWN0JGRhdGFzZXQsIGF1eExpc3QsIGJ1eWVyPUZBTFNFKQ0KICANCiAgZXF1aWxpYnJpdW0ucHJpY2VzID0gKGJ1eWVyLnBoaSArIHNlbGxlci5waGkpLzINCiAgDQogIGFkanVzdGVkLnByaWNlcyA9IGVxdWlsaWJyaXVtLnByaWNlcy9FQ1BHb2JqZWN0JGRpc2NvdW50VmVjdG9yDQogIA0KICBhdmVyYWdlUHJpY2UgPSB5ZWFybHkubWVhbihhZGp1c3RlZC5wcmljZXMpDQogIGlmIChsZW5ndGgoYXZlcmFnZVByaWNlKSAhPSBsZW5ndGgoRUNQR29iamVjdCRmb3J3YXJkUHJpY2UpKSBzdG9wKCJFcnJvciBpbiBFQ1BHLmVxdWlsaWJyaXVtIGZ1bmN0aW9uOiBMZW5ndGggb2YgZm93YXJkUHJpY2UgdmVjdG9yIGlzIGRpZmZlcmVudCBmcm9tIHRoZSBsZW5ndGggb2YgZGF0YSBkaXZpZGVkIGJ5IDEyISIpDQogIA0KICBFQ1BHb2JqZWN0JGJ1eWVyUGhpID0gYnV5ZXIucGhpDQogIEVDUEdvYmplY3Qkc2VsbGVyUGhpID0gc2VsbGVyLnBoaQ0KICBFQ1BHb2JqZWN0JGVxdWlsaWJyaXVtUHJpY2VzID0gZXF1aWxpYnJpdW0ucHJpY2VzDQogIEVDUEdvYmplY3QkYWRqdXN0ZWRQcmljZXM9YWRqdXN0ZWQucHJpY2VzDQogIEVDUEdvYmplY3QkYXZlcmFnZVByaWNlPWF2ZXJhZ2VQcmljZQ0KICANCiAgcmV0dXJuKEVDUEdvYmplY3QpDQp9DQoNCmBgYA0KDQpUaGUgbmV4dCBmdW5jdGlvbiB3aWxsIGJlIHVzZWQgZm9yIHRoZSBvcHRpbWl6YXRpb24gd2l0aCBSIGNvbW1hbmQgKm9wdGltKi4NCg0KYGBge3J9DQpvcHRpbS5mb28gPC0gZnVuY3Rpb24ocGFyYW1ldGVycywgRUNQR29iamVjdCkgew0KICBwYXJhbV9iID0gcGFyYW1ldGVyc1sxOjRdDQogIHBhcmFtX3MgPSBwYXJhbWV0ZXJzWzU6OF0NCiAgbmFtZXMocGFyYW1fYikgPSBuYW1lcyhwYXJhbV9zKSA9IGMoJ0xhbWJkYTEnLCAnTGFtYmRhMicsICdBbHBoYTEnLCAnQWxwaGEyJykNCiAgDQogIEVDUEdvYmplY3QkYnV5ZXJQYXJhbSA9IHBhcmFtX2INCiAgRUNQR29iamVjdCRzZWxsZXJQYXJhbSA9IHBhcmFtX3MNCiAgDQogIGF2ZXJhZ2VQcmljZSA9IEVDUEcuZXF1aWxpYnJpdW0oRUNQR29iamVjdCkkYXZlcmFnZVByaWNlDQogIA0KICByZXR1cm4obWVhbigoYXZlcmFnZVByaWNlIC0gRUNQR29iamVjdCRmb3J3YXJkUHJpY2UpXjIpKQ0KfQ0KYGBgDQoNClRoZSBuZXh0IGZ1bmN0aW9uIGlzIHVzZWQgdG8gY2FsY3VsYXRlIHRoZSBkaXNjb3VudCB2ZWN0b3IgYmFzZWQgb24gdGhlIHJpc2stZnJlZSByYXRlcyBmb3IgZWFjaCBkYXRhIHBvaW50Lg0KDQpgYGB7cn0NCmdldERpc2NvdW50VmVjdG9yIDwtIGZ1bmN0aW9uKGluZGV4LCBtb2RpZmllciwgYW51YWxSYXRlKSB7DQogIHllYXJBZGogPSAoMTErbW9kaWZpZXIqMTIpDQogIG1vbnRobHlSYXRlID0gKDErYW51YWxSYXRlKV4oMS8xMiktMQ0KICBjb21wbGV0ZURpc2NvdW50VmVjdG9yIDwtIHJlcChOQSwgeWVhckFkaikNCiAgZm9yIChpIGluIDE6eWVhckFkail7DQogICAgY29tcGxldGVEaXNjb3VudFZlY3RvcltpXSA8LSAoMSttb250aGx5UmF0ZSleKGktMSkNCiAgfQ0KICANCiAgZ2V0RGlzY291bnRCb3VuZHMgPC0gZnVuY3Rpb24oaW5kZXgsIG1vZGlmaWVyLCBjb21wbGV0ZURpc2NvdW50VmVjdG9yKSB7DQogICAgbG93ZXIubGltaXQgPSAxKzEyKm1vZGlmaWVyLWluZGV4DQogICAgdXBwZXIubGltaXQgPSAxMisxMiptb2RpZmllci1pbmRleA0KICAgIA0KICAgIHJldHVybihjb21wbGV0ZURpc2NvdW50VmVjdG9yW2xvd2VyLmxpbWl0OnVwcGVyLmxpbWl0XSkNCiAgfQ0KICANCiAgZGlzY291bnRWZWN0b3IgPSBhcy5udW1lcmljKHNhcHBseShpbmRleCwgZ2V0RGlzY291bnRCb3VuZHMsIG1vZGlmaWVyPW1vZGlmaWVyLCBjb21wbGV0ZURpc2NvdW50VmVjdG9yPWNvbXBsZXRlRGlzY291bnRWZWN0b3IpKQ0KICByZXR1cm4oZGlzY291bnRWZWN0b3IpDQp9DQpgYGANCg0KV2UgZGVmaW5lIGFuIGFjZXNzb3J5IGZ1bmN0aW9uIHRvIGNhbGN1bGF0ZSB0aGUgeWVhcmx5IG1lYW4gdXNpbmcgdGhlIGRpc2NvdW50IHZlY3Rvci4NCg0KYGBge3J9DQp5ZWFybHkubWVhbiA8LSBmdW5jdGlvbih4KSB7DQogIGxlbiA9IGxlbmd0aCh4KQ0KICBpZiAobGVuLzEyID4gbGVuICUvJTEyKSBzdG9wKCJFcnJvciBpbiB5ZWFybHkubWVhbiBmdW5jdGlvbjogTGVuZ3RoIG9mIGRhdGEgaXMgbm90IGRpdmlzaWJsZSBieSAxMiEiKQ0KICANCiAgbWVhbi52ZWM9TlVMTA0KICANCiAgaXRlciA9IGxlbi8xMg0KICBmb3IgKGkgaW4gMTppdGVyKSB7DQogICAgbWVhbi52ZWNbaV0gPSBtZWFuKHhbKDErKGktMSkqMTIpOihpKjEyKV0pDQogIH0NCiAgcmV0dXJuKG1lYW4udmVjKQ0KfQ0KYGBgDQoNCiMjIyBMb2NhbCB2YXJpYWJsZXMgZGVmaW5pdGlvbg0KDQpTZXQgdGhlIGluaXRpYWwgb3B0aW1pemF0aW9uIHBhcmFtZXRlcnMuIFRoZXkgbXVzdCBwcm92aWRlIHRoZSBvcHRpbWl6YXRpb24gZnVuY3Rpb24gd2l0aCBhIG5vbi1OQSByZXR1cm4sIHdoaWNoIGlzIGEgcmVxdWlyZW1lbnQgZnJvbSAqb3B0aW0qLg0KDQpgYGB7cn0NCmJ1eWVyLmluaXRpYWwucGFyYW0gPSBjKExhbWJkYTEgPSAwLCBMYW1iZGEyID0gMCwgQWxwaGExID0gMC41LCBBbHBoYTIgPSAwLjk1KSAjIEluaXRpYWwgUGFyYW1ldGVycw0Kc2VsbGVyLmluaXRpYWwucGFyYW0gPSBjKExhbWJkYTEgPSAwLCBMYW1iZGEyID0gMCwgQWxwaGExID0gMC41LCBBbHBoYTIgPSAwLjk1KSAjIEluaXRpYWwgUGFyYW1ldGVyDQpgYGANCg0KU2V0IHRoZSBhbm51YWwgZGlzY291bnQgcmF0ZSAodGhlIGVjb25vbXkgcmlzay1mcmVlIHJhdGUpLiBBIHNlbnNpdGl2aXR5IGFuYWx5c2lzIGZvciB0aGlzIHJhdGUgaXMgcHJvdmlkZWQgaW4gdGhlIHNlbnNpdGl2aXR5IGFuYWx5c2lzIHNlY3RvciAoW2xpbmtdKC4vU2Vuc2l0aXZpdHlBbmFseXNpc05vdGVib29rLm5iLmh0bWwpKS4NCg0KYGBge3J9DQphbnVhbFJhdGUgPSAwLjA1DQpgYGANCg0KU2V0IHRoZSBkYXRhIHBvaW50cywgZGVmaW5lZCBieSBtb250aCBhbmQgeWVhciwgdG8gYmUgbG9hZGVkIGZvciBtb2RlbCB0cmFpbmluZy4NCg0KYGBge3J9DQpkYXRhLmluZGV4ID0gcmJpbmQoDQogIGMoNywgMjAxOSksDQogIGMoOCwgMjAxOSksDQogIGMoOSwgMjAxOSksDQogIGMoMTAsIDIwMTkpLA0KICBjKDExLCAyMDE5KSwNCiAgYygxMiwgMjAxOSksDQogIGMoMSwgMjAyMCksDQogIGMoMiwgMjAyMCkNCikNCg0KIyBEZWZpbmUgdGhlIG51bWJlciBvZiB5ZWFycyBhaGVhZCB0aGF0IHRoZSBmb3J3YXJkIHByaWNlIGlzIGFwcGxpZWQgdG8uDQpzZXJpZXNBID0gMQ0KYGBgDQoNCiMjIyBMb2FkaW5nIHRoZSBkYXRhDQoNCkxvYWQgdGhlIGZvcndhcmQgcHJpY2UgZm9yIGVsZWN0cmljaXR5IG9idGFpbmVkIGZyb20gRENpZGUgRW5lcmdpYS4NCg0KYGBge3J9DQpmb3J3YXJkUHJpY2UuZGF0YSA9IHJlYWQuY3N2KGZpbGU9Jy4vRGF0YS9Gb3J3YXJkUHJpY2VzLmNzdicpDQpgYGANCg0KTG9hZGluZyBmdXR1cmUgc3BvdCBwcmljZSAoUExEKSBkYXRhLg0KDQpgYGB7cn0NCmRhdGEuYWJicmV2ID0gcGFzdGUobW9udGguYWJiW2RhdGEuaW5kZXhbLCAxXV0sIGRhdGEuaW5kZXhbLDJdLCBzZXA9Il8iKQ0KDQpwcmljZS5kYXRhID0gbGlzdCgpDQpmb3IgKG4gaW4gMTo4KSB7DQogIHByaWNlLmRhdGFbW2RhdGEuYWJicmV2W25dXV0gPC0gdChyZWFkLmNzdihmaWxlPXBhc3RlKCcuL0RhdGEvRGF0YScsIGRhdGEuYWJicmV2W25dLCBwYXN0ZTAoJ0EnLCBzZXJpZXNBLCAnLmNzdicpLCBzZXA9Il8iKSwgY29sLm5hbWVzPXBhc3RlKG1vbnRoLmFiYiwgZGF0YS5pbmRleFtuLDJdK3Nlcmllc0EsIHNlcD0iLSIpKSkNCn0NCmBgYA0KDQojIyMgQWxsb2NhdGUgYW5kIHZlcmlmeSBkYXRhDQoNCmBgYHtyfQ0KdHJhaW5pbmcuZGlzY291bnRWZWN0b3IgPSBnZXREaXNjb3VudFZlY3RvcihkYXRhLmluZGV4WywxXSwgc2VyaWVzQSwgYW51YWxSYXRlKQ0KDQpsZW4gPSBsZW5ndGgodHJhaW5pbmcuZGlzY291bnRWZWN0b3IpDQppZiAobGVuLzEyID4gbGVuICUvJTEyKSBzdG9wKCJFcnJvciBpbiBkaXNjb3VudFZlY3RvciBhbGxvY2F0aW9uOiBMZW5ndGggb2YgZGF0YSBpcyBub3QgZGl2aXNpYmxlIGJ5IDEyISIpDQoNCnRyYWluaW5nLmZvcndhcmRQcmljZSA9IGFzLm51bWVyaWMoZm9yd2FyZFByaWNlLmRhdGFbc2VyaWVzQSwgMjo5XSkNCmlmKGxlbi8xMiAhPSBsZW5ndGgodHJhaW5pbmcuZm9yd2FyZFByaWNlKSkgc3RvcCgiRXJyb3IgaW4gZm9yd2FyZFByaWNlOiBMZW5ndGggb2YgZGF0YSBpcyBub3QgY29tcGF0aWJsZSB3aXRoIGRpc2NvdW50VmVjdG9yISIpDQoNCnRyYWluaW5nLmRhdGEgPSBSZWR1Y2UocmJpbmQsIHByaWNlLmRhdGEpDQppZiAobnJvdyh0cmFpbmluZy5kYXRhKSAhPSBsZW4pIHN0b3AoIkVycm9yIGluIHRyYWluaW5nRGF0YUEyOiB0aGUgbnVtYmVyIG9mIHJvd3MgaXMgbm90IGNvbXBhdGlibGUgd2l0aCB0aGUgZGlzY291bnRWZWN0b3IhIikNCg0KdHJhaW5pbmcuRUNQR19vYmplY3QgPSBjcmVhdGVFQ1BHT2JqZWN0KGRhdGFzZXQgPSB0cmFpbmluZy5kYXRhLCBidXllclBhcmFtID0gYnV5ZXIuaW5pdGlhbC5wYXJhbSwgc2VsbGVyUGFyYW0gPSBzZWxsZXIuaW5pdGlhbC5wYXJhbSwgZGlzY291bnRWZWN0b3IgPSB0cmFpbmluZy5kaXNjb3VudFZlY3RvciwgZm9yd2FyZFByaWNlID0gdHJhaW5pbmcuZm9yd2FyZFByaWNlKQ0KDQpgYGANCg0KSW5wdXR0aW5nIHRoZSBkYXRhIG9iamVjdCB0byB0aGUgZXF1aWxpYnJpdW0gZnVuY3Rpb24gd2lsbCBpbnN0YW50YW5lb3VzbHkgcHJvdmlkZSB0aGUgZXF1aWxpYnJpdW0gcHJpY2UgZ2l2ZW4gdGhlIHNlbGVjdGVkIGluaXRpYWwgcGFyYW1ldGVycy4NCg0KYGBge3J9DQp0cmFpbmluZy5pbml0aWFsUmVzdWx0cyA9IEVDUEcuZXF1aWxpYnJpdW0odHJhaW5pbmcuRUNQR19vYmplY3QpDQpwcmludCh0cmFpbmluZy5pbml0aWFsUmVzdWx0cyRhZGp1c3RlZFByaWNlcykNCmBgYA0KVGhlIG9iamVjdCBjYXJyaWVzIGFsbCB0aGUgcHJvdmlkZWQgcGFyYW1ldGVycywgc28gaXQgaXMgZWFzeSB0byB2ZXJpZnkgYWZ0ZXIgdGhlIGV4ZWN1dGlvbi4NCg0KIyMjIE9wdGltaXphdGlvbiBvZiBwYXJhbWV0ZXJzDQoNCk5vdyB0aGUgb3B0aW1pemF0aW9uIHdpbGwgc3RhcnQuIEl0IGNhbiB0YWtlIHVwIHRvIDEwIG1pbnV0ZXMgcnVubmluZyBpbiBhIHNpbmdsZSBjb3JlLiBQYXJhbGxlbGlzbSBjb3VsZCBiZSBhbHNvIHVzZWQgaWYgbmVlZCBhcmlzZS4NCg0KYGBge3J9DQojIyBSdW4gdGhlIG9wdGltaXphdGlvbiAtIHVzZXMgcHJvYy50aW1lIHRvIG1lYXN1cmUgdGhlIGV4ZWN1dGlvbiB0aW1lDQpwdG0gPSBwcm9jLnRpbWUoKQ0KDQpvcHRpbS5yZXN1bHRzID0gb3B0aW0oYyhidXllci5pbml0aWFsLnBhcmFtLCBzZWxsZXIuaW5pdGlhbC5wYXJhbSksIG9wdGltLmZvbywgRUNQR29iamVjdD10cmFpbmluZy5FQ1BHX29iamVjdCwgbWV0aG9kPSJTQU5OIikNCg0KdGltZVNwZW50ID0gcHJvYy50aW1lKCkgLSBwdG0NCnByaW50KHRpbWVTcGVudCkNCg0KYGBgDQoNClRoZSBvcHRpbWl6ZWQgcGFyYW1ldGVycyBhcmUgZXh0cmFjdGVkIGZyb20gdGhlIG9wdGltaXphdGlvbiByZXN1bHRzLg0KDQpgYGB7cn0NCiMjIEdldCBFQ1BHIHBhcmFtZXRlcnMgZnJvbSBvcHRpbWl6YXRpb24gcmVzdWx0cw0KdHJhaW5pbmcuRUNQR19vYmplY3QkYnV5ZXJQYXJhbSA9IG9wdGltLnJlc3VsdHMkcGFyWzE6NF0NCnRyYWluaW5nLkVDUEdfb2JqZWN0JHNlbGxlclBhcmFtID0gb3B0aW0ucmVzdWx0cyRwYXJbNTo4XQ0KYGBgDQoNCiMjIyBSZXN1bHRzDQoNClRoZSBvcHRpbWFsIHBhcmFtZXRlcnMgYXJlIHVzZWQgdG8gY2FsY3VsYXRlIHRoZSBlcXVpbGlicml1bSBwcmljZXMuIFRoZSBvcmlnaW5hbCBvYmplY3QgaXMgb3ZlcndyaXR0ZW4gYmVjYXVzZSB0aGVyZSBpcyBubyBkYXRhIGxvc3MsIG9ubHkgbmV3IGluZm9ybWF0aW9uIGlzIGFkZGVkLg0KDQpgYGB7cn0NCiMjIENhbGN1bGF0ZSByZXN1bHRzIGZyb20gb3B0aW1pemVkIHBhcmFtZXRlcnMNCnRyYWluaW5nLkVDUEdfb2JqZWN0ID0gRUNQRy5lcXVpbGlicml1bSh0cmFpbmluZy5FQ1BHX29iamVjdCkNCnByaW50KHRyYWluaW5nLkVDUEdfb2JqZWN0JGFkanVzdGVkUHJpY2VzKQ0KYGBgDQoNCkZpbmFsbHkgdGhlIHJlc3VsdHMgY2FuIGJlIHByaW50ZWQgb25zY3JlZW4uDQoNCmBgYHtyfQ0KdHJhaW5pbmcuRUNQR19vYmplY3QkYnV5ZXJQYXJhbQ0KdHJhaW5pbmcuRUNQR19vYmplY3Qkc2VsbGVyUGFyYW0NCmBgYA0KDQojIyMgU2VlIGFsc286DQoNCk1vZGVsIFZhbGlkYXRpb24gKFtsaW5rXSguL01vZGVsVmFsaWRhdGlvbk5vdGVib29rLm5iLmh0bWwpKQ0KDQpTZW5zaXRpdml0eSBBbmFseXNpcyAoW2xpbmtdKC4vU2Vuc2l0aXZpdHlBbmFseXNpc05vdGVib29rLm5iLmh0bWwpKQ0K