Let’s estimate the Stochastic Actor-Oriented Model (SAOM) implemented in R as the Simulation Investigation for Empirical Network Analysis (R-SIENA), developed by Snijders, Van de Bunt, and Steglich (2010).



Getting started

clean up

rm (list = ls( ))


custom functions

  • fpackage.check: Check if packages are installed (and install if not) in R (source)
fpackage.check <- function(packages) {
    lapply(packages, FUN = function(x) {
        if (!require(x, character.only = TRUE)) {
            install.packages(x, dependencies = TRUE)
            library(x, character.only = TRUE)
        }
    })
}


necessary packages

  • RSiena: RSiena models
packages = c("RSiena")
fpackage.check(packages)


We will:

  1. Read in our R-SIENA object list
  2. Inspect our data
  3. Define our effects
  4. Define our algorithm
  5. And estimate the SAOM

Below, we will follow these steps for club 1 (N=27). Here, we focus on running frequency. We did the same procedure for the other clubs.



Step 1: Data

We read in the R-SIENA objects list (clubdata_rsiena_freq.RData) and we grab club 1 (N=27). We take as our network variable the kudos-network in which awarding/receiving at least 1 kudos constitutes an i,j tie.

  • Our (dependent) behavioral variable is running frequency (in times per week; ranging from 0 to 7+ times per week).

  • We included activity (frequency) in other sports (e.g., cycling and swimming) as a time-varying covariate.

  • And we also included gender (men vs. women and others) as constant covariate.

load("clubdata_rsiena_freq.Rdata") # load rsiena object list
mydata <- clubdata_rsiena_freq[[1]] # grab club 1



Step 2: Inspect data

We inspect the R-SIENA object

print01Report(mydata, modelname="files/test")

A text file is printed in the working directory.



Step 3: Define effects

We are going to define our myeff object containing the model parameters. A list of all available effects for the given object can be displayed in browser by requesting effectsDocumentation(myeff). See Ripley et al. (2021) for a substantial and mathematical description of all effects.

We build increasingly complex models.

We include:

  1. structural network effects
  2. network selection effects
  3. covariate effects on network and behavior dynamics
  4. network influence effects

We fix these effects to 0 and test them with the score-type test Schweinberger (2012) (we test the hypothesis that the parameter estimates are not 0, other than the model assumes).

myeff <- getEffects(mydata)
#effectsDocumentation(myeff)


Structural network effects

First, we are going to include structural network effects, guided by recommendations of Snijders (2020): outdegree, reciprocity, and transitivity (GWESP).

We also add degree-related effects: indegree-popularity and outdegree-activity (square-root versions).

We tested the out-Isolate effect (leading to not awarding kudos) and this effect was not different from 0.

myeff1 <- includeEffects(myeff, gwespFF, name = "kudonet") 
myeff1 <- includeEffects(myeff1, outActSqrt, inPopSqrt, name = "kudonet") 
myeff1 <- setEffect( myeff1, outIso, name = "kudonet", fix = TRUE, test = FALSE, initialValue = 0)


Selection effects

Second, we include selection effects with respect to behavior: egoX, altX and simX.

In addition, we use the higher-effect to control for aspirational tie-preferences (indicated by a negative parameter estimate).

myeff2 <- includeEffects(myeff1, egoX, altX, simX, higher, name = "kudonet", interaction1 = "freq_run")


Covariate effects

We include effects on tie changes of gender (monadic and dyadic).

myeff2 <- includeEffects(myeff2, egoX, altX, sameX, name="kudonet", interaction1 = "gender" )


We have selected a rather simple model to simulate kudos tie-formation dynamics. Let’s estimate the model and assess the model’s GOF to additional effects that were not directly modeled: the in- and outdegree distribution and the geodesic distance distribution. We use ‘returnDeps=TRUE’ for keeping the simulated data (networks and behavior), for subsequent GOF assesment. We save the GOF-diagnostics in a list.

myalgorithm <- sienaAlgorithmCreate(projname = "test", nsub=5, n3=5000 )
 # first, set the SAOM algorithm 

ansM1 <- siena07(myalgorithm, data = mydata, effects = myeff2, # estimate the SAOM
                 batch = FALSE, verbose = FALSE, returnDeps = TRUE)

# calculate GOF diagnostics
gofi <- sienaGOF(ansM1, 
                 IndegreeDistribution, 
                 verbose = TRUE,
                 join = TRUE, 
                 varName = "kudonet")

gofo <- sienaGOF(ansM1, 
                 OutdegreeDistribution, 
                 verbose = TRUE,
                 join = TRUE, 
                 varName = "kudonet")

GeodesicDistribution <- function (i, data, sims, period, groupName,
   varName, levls=c(1:5, Inf), cumulative=TRUE, ...) {
     x <- networkExtraction(i, data, sims, period, groupName, varName)
     require(sna)
     a <- sna::geodist(symmetrize(x))$gdist
     if (cumulative)
     {
       gdi <- sapply(levls, function(i){ sum(a<=i) })
     }
     else
     {
       gdi <- sapply(levls, function(i){ sum(a==i) })
     }
     names(gdi) <- as.character(levls)
     gdi
}

gofgeo <- sienaGOF(ansM1, 
                 GeodesicDistribution, 
                 verbose = TRUE,
                 join = TRUE, 
                 varName = "kudonet")

goflist <- list(gofi, gofo, gofgeo)
save(goflist, file= paste("files", "/", "test club 1", "/", "gof.RData", sep=""))

Indegree distribution

load("files/test club 1/gof.RData")
plot(goflist[[1]])

Outdegree distribution

plot(goflist[[2]])

Geodesic distance distribution

plot(goflist[[3]])
#> Note: some statistics are not plotted because their variance is 0.
#> This holds for the statistic: Inf.

GOF is acceptable!


For subsequent meta-analysis, we need to ensure that the model specification for all our club-networks is identical. Some effects were rather important in other clubs. We fix these to 0:

myeff2 <- setEffect( myeff2, outPopSqrt, name = "kudonet", fix = TRUE, test = FALSE, initialValue = 0)
myeff2 <- setEffect( myeff2, reciAct, name = "kudonet", fix = TRUE, test = FALSE, initialValue = 0)
myeff2 <- includeInteraction(myeff2, recip, gwespFF, parameter = 69, name = "kudonet")
eff1 <- myeff2[myeff2$include,]$effect1[27]
eff2 <- myeff2[myeff2$include,]$effect2[27]
myeff2 <- setEffect(myeff2, unspInt, fix=TRUE, test=FALSE, effect1=eff1, effect2=eff2)



We have modeled the dynamics of kudos tie formation. Now let’s model dynamics in running behaviors.

Covariate effects

We start with effects on behavior changes of other variables.

  • the interdependence between running frequency and other sports frequency.
  • gender on behavior
myeff3 <- includeEffects(myeff2, effFrom, name = "freq_run", interaction1 = "freq_other")
myeff3 <- includeEffects(myeff3, effFrom, name = "freq_run", interaction1 = "gender")



Influence effects

Last, we include effects of network position (indegree) and alter behaviors (average alter/similarity, etc.) on behavior change. We make, for each club, 6 model specifications. We save these effect objects in a list.

  • Model 1: base model + indegree effect on running
  • Model 2: Model 1 + average alter effect
  • Model 3: Model 1 + average attraction higher
  • Model 4: Model 1 + average attraction lower
  • Model 5: Model 1 + average attraction higher + lower
  • Model 6: Model 1 + average similarity effect

We also fixed-and-tested the effect of outdegree on behavior, to rule out possible confounding of the outdegree effect. Score-type test indicated that outdegree-effects on behavior were 0 (not shown).

myeff0 <- myeff3                                                                            # model 0: base
myeff1 <- includeEffects(myeff0, indeg, name = "freq_run", interaction1 = "kudonet")        # model 1: indegree
myeff2 <- includeEffects(myeff1, avAlt, name = "freq_run", interaction1 = "kudonet")        # model 2: avAlt
myeff3 <- includeEffects(myeff1, avAttHigher, name = "freq_run", interaction1 = "kudonet")  # model 3: avAttHigher
myeff4 <- includeEffects(myeff1, avAttLower, name = "freq_run", interaction1 = "kudonet")   # model 4: avAttLower
myeff5 <- includeEffects(myeff3, avAttLower, name = "freq_run", interaction1 = "kudonet")   # model 5: avAttHigher+Lower
myeff6 <- includeEffects(myeff1, avSim, name = "freq_run", interaction1 = "kudonet")        # model 6: avSim

myeff <- list(myeff1, myeff2, myeff3, myeff4, myeff5, myeff6)



Step 4: Estimate the model

Let’s estimate these models. We rerun the models until adequate convergence is reached. We store the sienaFit objects of these models in a list, which we save later on. We use ‘returnDeps=TRUE’ for keeping the simulated data (networks and behavior).

m=6 # models to estimate (indeg, avAlt, avAttHigher, avAttLower, avAttHigher+Lower, avSim)

# tweak the algorithm
myalgorithm <- sienaAlgorithmCreate(projname = "test", nsub=5, n3=3 )

# siena07( myalgorithm, data = mydata, effects = myeff[[j]], prevAns= sienaFit[[j]], returnDeps=TRUE, useCluster=TRUE, nbrNodes=10, initC=TRUE, batch=TRUE)

# we make a list for storing the RSiena fit objects
sienaFit <- list()

# for club i (here, 1) we run models j in 1:m
i = 1
for (j in 1:m) {
 
# we estimate the model
try <- 1
print(paste("Estimating model ", j, " for club 1", sep=""))
sienaFit[[j]] <- siena07(myalgorithm, data = mydata, effects = myeff[[j]], returnDeps=TRUE,
                             useCluster=TRUE, nbrNodes=10, initC=TRUE, batch=TRUE) # store it in the list
    
    # re-run until we reach adequate convergence 
    while (TRUE){
      if(sienaFit[[j]]$tconv.max >= .25){
        try <- try + 1
        if (try>30) {
          print(paste("Now it lasted to long!") 
          break      
        }
        print(paste("Model did not converge adequately (", sienaFit[[j]]$tconv.max, "); ", "Repeat the estimation (", "try ", try, ")", sep = ""))
        sienaFit[[j]] <- siena07( myalgorithm, data = mydata, effects = myeff[[j]], prevAns= sienaFit[[j]], returnDeps=TRUE, useCluster=TRUE, nbrNodes=10, initC=TRUE, batch=TRUE)
      }else{
        print(paste("Reached overall maximum convergence ratio of: ", sienaFit[[j]]$tconv.max, sep = ""))
        print("")
        break
      }
    }
    
  }
  # and save the list with RSiena fit objects
  save(sienaFit, file=paste("test", "/", "sienaFit", "/", "sienaFit_club", i, ".RData", sep = ""))
  print(paste("All models are estimated for club ", i, ". Model results are stored in sienaFit_club", i, ".RData", sep=""))

}

sienaFit_clubL <- list()

for (i in 1:5) {
  temp.space <- new.env()
  bar <- load(paste("test/sienaFit/sienaFit_club", i, ".RData", sep=""), temp.space)
  sienaFit_clubL[[i]] <- get(bar, temp.space)
  rm(temp.space)
}


lapply(sienaFit_clubL, '[[', 5)
map(sienaFit_clubL, 6)


Because we are now modeling the evolution of both the network and the attribute (running freq.), we will add an additional indicator to evaluate GOF; namely, does the model capture the distribution of actors’ attribute levels over time?

gofbeh <- sienaGOF(sienaFit[[5]],
                   BehaviorDistribution, levls = 0:7,
                   verbose=TRUE, join=TRUE,
                   varName="freq_run")
save(gofbeh, file= paste("files", "/", "test club 1", "/", "gofbeh.RData", sep=""))
load("files/test club 1/gofbeh.RData")
plot(gofbeh)
#> Note: some statistics are not plotted because their variance is 0.
#> This holds for the statistic: 7.

GOF is adequate for the distribution of running frequency values.


Next up

We will check whether this model configuration also converges for the other clubs. To summarize the results over our clubs, we will perform a meta-analysis using a Fisher-type combination of one-tailed p-values.


References

Ripley, R. M., T. A. B. Snijders, Z. Boda, A. Vörös, and P. & Preciado. 2021. “Manual for RSIENA.” University of Oxford, Department of Statistics, Nuffield College - (-): –. http://www.stats.ox.ac.uk/~snijders/siena/RSiena_Manual.pdf.
Schweinberger, Michael. 2012. “Statistical Modelling of Network Panel Data: Goodness of Fit.” British Journal of Mathematical and Statistical Psychology 65 (2): 263–81. https://doi.org/10.1111/j.2044-8317.2011.02022.x.
Snijders, T. A. B. 2020. “Statistical Methods for Social Network Dynamics. A: Networks.” http://www.stats.ox.ac.uk/~snijders/siena/Siena_ModelSpec_s.pdf.
Snijders, T. A. B., G. G. Van de Bunt, and C. E. G. Steglich. 2010. “Introduction to Stochastic Actor-Based Models for Network Dynamics.” Social Networks 32 (1): 44–60. http://www.sciencedirect.com/science/article/pii/S0378873309000069.
LS0tDQp0aXRsZTogIlNBT006IE1vZGVsIHNlbGVjdGlvbiINCmRhdGU6ICJMYXN0IGNvbXBpbGVkIG9uIGByIGZvcm1hdChTeXMudGltZSgpLCAnJUIsICVZJylgIg0KYmlibGlvZ3JhcGh5OiByZWZlcmVuY2VzLmJpYg0Kb3V0cHV0Og0KICBodG1sX2RvY3VtZW50Og0KICAgIGNzczogdHdlYWtzLmNzcw0KICAgIHRvYzogdHJ1ZQ0KICAgIHRvY19mbG9hdDogdHJ1ZQ0KICAgIGNvbGxhcHNlZDogZmFsc2UNCiAgICBudW1iZXJfc2VjdGlvbnM6IGZhbHNlDQogICAgdG9jX2RlcHRoOiAxDQogICAgY29kZV9mb2xkaW5nOiBzaG93DQogICAgY29kZV9kb3dubG9hZDogeWVzDQotLS0NCiAgDQpgYGB7ciwgZ2xvYmFsc2V0dGluZ3MsIGVjaG89RkFMU0UsIHdhcm5pbmc9RkFMU0UsIHJlc3VsdHM9J2hpZGUnfQ0KDQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUpDQpvcHRzX2NodW5rJHNldCh0aWR5Lm9wdHM9bGlzdCh3aWR0aC5jdXRvZmY9MTAwKSx0aWR5PVRSVUUsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFLGNvbW1lbnQgPSAiIz4iLCBjYWNoZT1UUlVFLCBjbGFzcy5zb3VyY2U9YygidGVzdCIpLCBjbGFzcy5vdXRwdXQ9YygidGVzdDIiKSkNCm9wdGlvbnMod2lkdGggPSAxMDApDQpyZ2w6OnNldHVwS25pdHIoKQ0KDQpjb2xvcml6ZSA8LSBmdW5jdGlvbih4LCBjb2xvcikge3NwcmludGYoIjxzcGFuIHN0eWxlPSdjb2xvcjogJXM7Jz4lczwvc3Bhbj4iLCBjb2xvciwgeCkgfQ0KDQpgYGANCg0KYGBge3Iga2xpcHB5LCBlY2hvPUZBTFNFLCBpbmNsdWRlPVRSVUV9DQprbGlwcHk6OmtsaXBweShwb3NpdGlvbiA9IGMoJ3RvcCcsICdyaWdodCcpKQ0KI2tsaXBweTo6a2xpcHB5KGNvbG9yID0gJ2RhcmtyZWQnKQ0KI2tsaXBweTo6a2xpcHB5KHRvb2x0aXBfbWVzc2FnZSA9ICdDbGljayB0byBjb3B5JywgdG9vbHRpcF9zdWNjZXNzID0gJ0RvbmUnKQ0KYGBgDQoNCg0KDQotLS0NCiAgDQpMZXQncyBlc3RpbWF0ZSB0aGUgU3RvY2hhc3RpYyBBY3Rvci1PcmllbnRlZCBNb2RlbCAoU0FPTSkgaW1wbGVtZW50ZWQgaW4gUiBhcyB0aGUgU2ltdWxhdGlvbiBJbnZlc3RpZ2F0aW9uIGZvciBFbXBpcmljYWwgTmV0d29yayBBbmFseXNpcyAoUi1TSUVOQSksIGRldmVsb3BlZCBieSBAc25pamRlcnMyMDEwLg0KDQotLS0tDQoNCjxicj4NCg0KDQojIEdldHRpbmcgc3RhcnRlZA0KDQojIyBjbGVhbiB1cA0KDQpgYGB7ciwgYXR0ci5vdXRwdXQ9J3N0eWxlPSJtYXgtaGVpZ2h0OiAyMDBweDsiJ30NCnJtIChsaXN0ID0gbHMoICkpDQpgYGANCg0KPGJyPiANCg0KIyMgY3VzdG9tIGZ1bmN0aW9ucw0KDQotIGBmcGFja2FnZS5jaGVja2A6IENoZWNrIGlmIHBhY2thZ2VzIGFyZSBpbnN0YWxsZWQgKGFuZCBpbnN0YWxsIGlmIG5vdCkgaW4gUiAoW3NvdXJjZV0oaHR0cHM6Ly92YmFsaWdhLmdpdGh1Yi5pby92ZXJpZnktdGhhdC1yLXBhY2thZ2VzLWFyZS1pbnN0YWxsZWQtYW5kLWxvYWRlZC8pKQ0KDQpgYGB7ciwgcmVzdWx0cz0naGlkZSd9DQoNCmZwYWNrYWdlLmNoZWNrIDwtIGZ1bmN0aW9uKHBhY2thZ2VzKSB7DQogICAgbGFwcGx5KHBhY2thZ2VzLCBGVU4gPSBmdW5jdGlvbih4KSB7DQogICAgICAgIGlmICghcmVxdWlyZSh4LCBjaGFyYWN0ZXIub25seSA9IFRSVUUpKSB7DQogICAgICAgICAgICBpbnN0YWxsLnBhY2thZ2VzKHgsIGRlcGVuZGVuY2llcyA9IFRSVUUpDQogICAgICAgICAgICBsaWJyYXJ5KHgsIGNoYXJhY3Rlci5vbmx5ID0gVFJVRSkNCiAgICAgICAgfQ0KICAgIH0pDQp9DQpgYGANCg0KPGJyPg0KDQoNCiMjIG5lY2Vzc2FyeSBwYWNrYWdlcw0KDQotIGBSU2llbmFgOiBSU2llbmEgbW9kZWxzDQoNCmBgYHtyIHBhY2thZ2VzLCByZXN1bHRzPSdoaWRlJ30NCnBhY2thZ2VzID0gYygiUlNpZW5hIikNCmZwYWNrYWdlLmNoZWNrKHBhY2thZ2VzKQ0KYGBgDQo8YnI+IA0KDQpXZSB3aWxsOg0KDQoxLiBSZWFkIGluIG91ciBSLVNJRU5BIG9iamVjdCBsaXN0DQoyLiBJbnNwZWN0IG91ciBkYXRhDQozLiBEZWZpbmUgb3VyIGVmZmVjdHMNCjQuIERlZmluZSBvdXIgYWxnb3JpdGhtDQo1LiBBbmQgZXN0aW1hdGUgdGhlIFNBT00NCg0KQmVsb3csIHdlIHdpbGwgZm9sbG93IHRoZXNlIHN0ZXBzIGZvciBjbHViIDEgKE49MjcpLiBIZXJlLCB3ZSBmb2N1cyBvbiBydW5uaW5nIGZyZXF1ZW5jeS4gV2UgZGlkIHRoZSBzYW1lIHByb2NlZHVyZSBmb3IgdGhlIG90aGVyIGNsdWJzLg0KDQo8YnI+DQoNCi0tLS0NCg0KDQojIFN0ZXAgMTogRGF0YQ0KDQpXZSByZWFkIGluIHRoZSBSLVNJRU5BIG9iamVjdHMgbGlzdCAoKmNsdWJkYXRhX3JzaWVuYV9mcmVxLlJEYXRhKikgYW5kIHdlIGdyYWIgY2x1YiAxIChOPTI3KS4gV2UgdGFrZSBhcyBvdXIgbmV0d29yayB2YXJpYWJsZSB0aGUga3Vkb3MtbmV0d29yayBpbiB3aGljaCBhd2FyZGluZy9yZWNlaXZpbmcgKmF0IGxlYXN0KiAxIGt1ZG9zIGNvbnN0aXR1dGVzIGFuICppLGoqIHRpZS4gDQoNCi0gT3VyIChkZXBlbmRlbnQpIGJlaGF2aW9yYWwgdmFyaWFibGUgaXMgcnVubmluZyBmcmVxdWVuY3kgKGluIHRpbWVzIHBlciB3ZWVrOyByYW5naW5nIGZyb20gMCB0byA3KyB0aW1lcyBwZXIgd2VlaykuDQoNCi0gV2UgaW5jbHVkZWQgYWN0aXZpdHkgKGZyZXF1ZW5jeSkgaW4gb3RoZXIgc3BvcnRzIChlLmcuLCBjeWNsaW5nIGFuZCBzd2ltbWluZykgYXMgYSB0aW1lLXZhcnlpbmcgY292YXJpYXRlLg0KDQotIEFuZCB3ZSBhbHNvIGluY2x1ZGVkIGdlbmRlciAobWVuIHZzLiB3b21lbiBhbmQgb3RoZXJzKSBhcyBjb25zdGFudCBjb3ZhcmlhdGUuDQoNCg0KYGBge3J9DQpsb2FkKCJjbHViZGF0YV9yc2llbmFfZnJlcS5SZGF0YSIpICMgbG9hZCByc2llbmEgb2JqZWN0IGxpc3QNCm15ZGF0YSA8LSBjbHViZGF0YV9yc2llbmFfZnJlcVtbMV1dICMgZ3JhYiBjbHViIDENCmBgYA0KDQo8YnI+DQoNCi0tLS0NCg0KDQojIFN0ZXAgMjogSW5zcGVjdCBkYXRhDQoNCldlIGluc3BlY3QgdGhlIFItU0lFTkEgb2JqZWN0DQoNCmBgYHtyIGV2YWw9Rn0NCnByaW50MDFSZXBvcnQobXlkYXRhLCBtb2RlbG5hbWU9ImZpbGVzL3Rlc3QiKQ0KYGBgDQoNCkEgdGV4dCBmaWxlIGlzIHByaW50ZWQgaW4gdGhlIHdvcmtpbmcgZGlyZWN0b3J5Lg0KDQohW10oZmlsZXMvdGVzdC50eHQpeyNpZCAuY2xhc3Mgd2lkdGg9MTAwJSBoZWlnaHQ9MjAwcHh9DQoNCi0tLS0NCg0KPGJyPg0KDQojIFN0ZXAgMzogRGVmaW5lIGVmZmVjdHMNCldlIGFyZSBnb2luZyB0byBkZWZpbmUgb3VyIGBteWVmZmAgb2JqZWN0IGNvbnRhaW5pbmcgdGhlIG1vZGVsIHBhcmFtZXRlcnMuIEEgbGlzdCBvZiBhbGwgYXZhaWxhYmxlIGVmZmVjdHMgZm9yIHRoZSBnaXZlbiBvYmplY3QgY2FuIGJlIGRpc3BsYXllZCBpbiBicm93c2VyIGJ5IHJlcXVlc3RpbmcgYGVmZmVjdHNEb2N1bWVudGF0aW9uKG15ZWZmKWAuIFNlZSBAcnNpZW5hbWFudWFsIGZvciBhIHN1YnN0YW50aWFsIGFuZCBtYXRoZW1hdGljYWwgZGVzY3JpcHRpb24gb2YgYWxsIGVmZmVjdHMuDQoNCldlIGJ1aWxkIGluY3JlYXNpbmdseSBjb21wbGV4IG1vZGVscy4NCg0KV2UgaW5jbHVkZToNCg0KMS4gW3N0cnVjdHVyYWwgbmV0d29yayBlZmZlY3RzXSgjc3RyKQ0KMi4gW25ldHdvcmsgc2VsZWN0aW9uIGVmZmVjdHNdKCNzZWwpDQozLiBbY292YXJpYXRlIGVmZmVjdHNdKCNjbykgb24gbmV0d29yayBhbmQgYmVoYXZpb3IgZHluYW1pY3MNCjQuIFtuZXR3b3JrIGluZmx1ZW5jZSBlZmZlY3RzXSgjaW5mKQ0KDQpXZSBmaXggdGhlc2UgZWZmZWN0cyB0byAwIGFuZCB0ZXN0IHRoZW0gd2l0aCB0aGUgc2NvcmUtdHlwZSB0ZXN0IEBTY2h3ZWluYmVyZ2VyMjAxMiAod2UgdGVzdCB0aGUgaHlwb3RoZXNpcyB0aGF0IHRoZSBwYXJhbWV0ZXIgZXN0aW1hdGVzIGFyZSBub3QgMCwgb3RoZXIgdGhhbiB0aGUgbW9kZWwgYXNzdW1lcykuDQoNCg0KYGBge3IgZWNobz1ULCByZXN1bHRzPSdoaWRlJ30NCm15ZWZmIDwtIGdldEVmZmVjdHMobXlkYXRhKQ0KI2VmZmVjdHNEb2N1bWVudGF0aW9uKG15ZWZmKQ0KYGBgDQoNCg0KPGJyPg0KDQojIyBTdHJ1Y3R1cmFsIG5ldHdvcmsgZWZmZWN0cyB7I3N0cn0NCkZpcnN0LCB3ZSBhcmUgZ29pbmcgdG8gaW5jbHVkZSBzdHJ1Y3R1cmFsIG5ldHdvcmsgZWZmZWN0cywgZ3VpZGVkIGJ5IHJlY29tbWVuZGF0aW9ucyBvZiBAc25pamRlcnNwcmVzOiBvdXRkZWdyZWUsIHJlY2lwcm9jaXR5LCBhbmQgdHJhbnNpdGl2aXR5IChHV0VTUCkuDQoNCldlIGFsc28gYWRkIGRlZ3JlZS1yZWxhdGVkIGVmZmVjdHM6IGluZGVncmVlLXBvcHVsYXJpdHkgYW5kIG91dGRlZ3JlZS1hY3Rpdml0eSAoc3F1YXJlLXJvb3QgdmVyc2lvbnMpLg0KDQpXZSB0ZXN0ZWQgdGhlIG91dC1Jc29sYXRlIGVmZmVjdCAobGVhZGluZyB0byBub3QgYXdhcmRpbmcga3Vkb3MpIGFuZCB0aGlzIGVmZmVjdCB3YXMgbm90IGRpZmZlcmVudCBmcm9tIDAuIA0KDQpgYGB7ciBlY2hvPVQsIHJlc3VsdHM9J2hpZGUnfQ0KbXllZmYxIDwtIGluY2x1ZGVFZmZlY3RzKG15ZWZmLCBnd2VzcEZGLCBuYW1lID0gImt1ZG9uZXQiKSANCm15ZWZmMSA8LSBpbmNsdWRlRWZmZWN0cyhteWVmZjEsIG91dEFjdFNxcnQsIGluUG9wU3FydCwgbmFtZSA9ICJrdWRvbmV0IikgDQpteWVmZjEgPC0gc2V0RWZmZWN0KCBteWVmZjEsIG91dElzbywgbmFtZSA9ICJrdWRvbmV0IiwgZml4ID0gVFJVRSwgdGVzdCA9IEZBTFNFLCBpbml0aWFsVmFsdWUgPSAwKQ0KYGBgDQoNCg0KPGJyPg0KDQojIyBTZWxlY3Rpb24gZWZmZWN0cyB7I3NlbH0NClNlY29uZCwgd2UgaW5jbHVkZSBzZWxlY3Rpb24gZWZmZWN0cyB3aXRoIHJlc3BlY3QgdG8gYmVoYXZpb3I6IGVnb1gsIGFsdFggYW5kIHNpbVguDQoNCkluIGFkZGl0aW9uLCB3ZSB1c2UgdGhlIGhpZ2hlci1lZmZlY3QgdG8gY29udHJvbCBmb3IgYXNwaXJhdGlvbmFsIHRpZS1wcmVmZXJlbmNlcyAoaW5kaWNhdGVkIGJ5IGEgbmVnYXRpdmUgcGFyYW1ldGVyIGVzdGltYXRlKS4NCg0KYGBge3IgZWNobz1ULCByZXN1bHRzPSdoaWRlJ30NCm15ZWZmMiA8LSBpbmNsdWRlRWZmZWN0cyhteWVmZjEsIGVnb1gsIGFsdFgsIHNpbVgsIGhpZ2hlciwgbmFtZSA9ICJrdWRvbmV0IiwgaW50ZXJhY3Rpb24xID0gImZyZXFfcnVuIikNCmBgYA0KDQoNCjxicj4NCiAgDQojIyBDb3ZhcmlhdGUgZWZmZWN0cyB7I2NvfQ0KICANCldlIGluY2x1ZGUgZWZmZWN0cyBvbiB0aWUgY2hhbmdlcyBvZiBnZW5kZXIgKG1vbmFkaWMgYW5kIGR5YWRpYykuDQoNCmBgYHtyIGVjaG89VCwgcmVzdWx0cz0naGlkZSd9DQpteWVmZjIgPC0gaW5jbHVkZUVmZmVjdHMobXllZmYyLCBlZ29YLCBhbHRYLCBzYW1lWCwgbmFtZT0ia3Vkb25ldCIsIGludGVyYWN0aW9uMSA9ICJnZW5kZXIiICkNCmBgYA0KDQogIA0KPGJyPiANCiAgDQpXZSBoYXZlIHNlbGVjdGVkIGEgcmF0aGVyIHNpbXBsZSBtb2RlbCB0byBzaW11bGF0ZSBrdWRvcyB0aWUtZm9ybWF0aW9uIGR5bmFtaWNzLiBMZXQncyBlc3RpbWF0ZSB0aGUgbW9kZWwgYW5kIGFzc2VzcyB0aGUgbW9kZWwncyBHT0YgdG8gYWRkaXRpb25hbCBlZmZlY3RzIHRoYXQgd2VyZSBub3QgZGlyZWN0bHkgbW9kZWxlZDogdGhlIGluLSBhbmQgb3V0ZGVncmVlIGRpc3RyaWJ1dGlvbiBhbmQgdGhlIGdlb2Rlc2ljIGRpc3RhbmNlIGRpc3RyaWJ1dGlvbi4gV2UgdXNlICdyZXR1cm5EZXBzPVRSVUUnIGZvciBrZWVwaW5nIHRoZSBzaW11bGF0ZWQgZGF0YSAobmV0d29ya3MgYW5kIGJlaGF2aW9yKSwgZm9yIHN1YnNlcXVlbnQgR09GIGFzc2VzbWVudC4gV2Ugc2F2ZSB0aGUgR09GLWRpYWdub3N0aWNzIGluIGEgbGlzdC4NCg0KYGBge3IgZXZhbD1GLCByZXN1bHRzPSdoaWRlJ30NCm15YWxnb3JpdGhtIDwtIHNpZW5hQWxnb3JpdGhtQ3JlYXRlKHByb2puYW1lID0gInRlc3QiLCBuc3ViPTUsIG4zPTUwMDAgKQ0KICMgZmlyc3QsIHNldCB0aGUgU0FPTSBhbGdvcml0aG0gDQoNCmFuc00xIDwtIHNpZW5hMDcobXlhbGdvcml0aG0sIGRhdGEgPSBteWRhdGEsIGVmZmVjdHMgPSBteWVmZjIsICMgZXN0aW1hdGUgdGhlIFNBT00NCiAgICAgICAgICAgICAgICAgYmF0Y2ggPSBGQUxTRSwgdmVyYm9zZSA9IEZBTFNFLCByZXR1cm5EZXBzID0gVFJVRSkNCg0KIyBjYWxjdWxhdGUgR09GIGRpYWdub3N0aWNzDQpnb2ZpIDwtIHNpZW5hR09GKGFuc00xLCANCiAgICAgICAgICAgICAgICAgSW5kZWdyZWVEaXN0cmlidXRpb24sIA0KICAgICAgICAgICAgICAgICB2ZXJib3NlID0gVFJVRSwNCiAgICAgICAgICAgICAgICAgam9pbiA9IFRSVUUsIA0KICAgICAgICAgICAgICAgICB2YXJOYW1lID0gImt1ZG9uZXQiKQ0KDQpnb2ZvIDwtIHNpZW5hR09GKGFuc00xLCANCiAgICAgICAgICAgICAgICAgT3V0ZGVncmVlRGlzdHJpYnV0aW9uLCANCiAgICAgICAgICAgICAgICAgdmVyYm9zZSA9IFRSVUUsDQogICAgICAgICAgICAgICAgIGpvaW4gPSBUUlVFLCANCiAgICAgICAgICAgICAgICAgdmFyTmFtZSA9ICJrdWRvbmV0IikNCg0KR2VvZGVzaWNEaXN0cmlidXRpb24gPC0gZnVuY3Rpb24gKGksIGRhdGEsIHNpbXMsIHBlcmlvZCwgZ3JvdXBOYW1lLA0KICAgdmFyTmFtZSwgbGV2bHM9YygxOjUsIEluZiksIGN1bXVsYXRpdmU9VFJVRSwgLi4uKSB7DQogICAgIHggPC0gbmV0d29ya0V4dHJhY3Rpb24oaSwgZGF0YSwgc2ltcywgcGVyaW9kLCBncm91cE5hbWUsIHZhck5hbWUpDQogICAgIHJlcXVpcmUoc25hKQ0KICAgICBhIDwtIHNuYTo6Z2VvZGlzdChzeW1tZXRyaXplKHgpKSRnZGlzdA0KICAgICBpZiAoY3VtdWxhdGl2ZSkNCiAgICAgew0KICAgICAgIGdkaSA8LSBzYXBwbHkobGV2bHMsIGZ1bmN0aW9uKGkpeyBzdW0oYTw9aSkgfSkNCiAgICAgfQ0KICAgICBlbHNlDQogICAgIHsNCiAgICAgICBnZGkgPC0gc2FwcGx5KGxldmxzLCBmdW5jdGlvbihpKXsgc3VtKGE9PWkpIH0pDQogICAgIH0NCiAgICAgbmFtZXMoZ2RpKSA8LSBhcy5jaGFyYWN0ZXIobGV2bHMpDQogICAgIGdkaQ0KfQ0KDQpnb2ZnZW8gPC0gc2llbmFHT0YoYW5zTTEsIA0KICAgICAgICAgICAgICAgICBHZW9kZXNpY0Rpc3RyaWJ1dGlvbiwgDQogICAgICAgICAgICAgICAgIHZlcmJvc2UgPSBUUlVFLA0KICAgICAgICAgICAgICAgICBqb2luID0gVFJVRSwgDQogICAgICAgICAgICAgICAgIHZhck5hbWUgPSAia3Vkb25ldCIpDQoNCmdvZmxpc3QgPC0gbGlzdChnb2ZpLCBnb2ZvLCBnb2ZnZW8pDQpzYXZlKGdvZmxpc3QsIGZpbGU9IHBhc3RlKCJmaWxlcyIsICIvIiwgInRlc3QgY2x1YiAxIiwgIi8iLCAiZ29mLlJEYXRhIiwgc2VwPSIiKSkNCmBgYA0KDQojIyMgey50YWJzZXQgLnRhYnNldC1mYWRlfQ0KDQojIyMjIEluZGVncmVlIGRpc3RyaWJ1dGlvbg0KYGBge3IgY2xhc3Muc291cmNlID0gJ2ZvbGQtaGlkZSd9DQpsb2FkKCJmaWxlcy90ZXN0IGNsdWIgMS9nb2YuUkRhdGEiKQ0KcGxvdChnb2ZsaXN0W1sxXV0pDQpgYGANCg0KIyMjIyBPdXRkZWdyZWUgZGlzdHJpYnV0aW9uDQpgYGB7ciBjbGFzcy5zb3VyY2UgPSAnZm9sZC1oaWRlJ30NCnBsb3QoZ29mbGlzdFtbMl1dKQ0KYGBgDQoNCiMjIyMgR2VvZGVzaWMgZGlzdGFuY2UgZGlzdHJpYnV0aW9uDQpgYGB7ciBjbGFzcy5zb3VyY2UgPSAnZm9sZC1oaWRlJ30NCnBsb3QoZ29mbGlzdFtbM11dKQ0KYGBgDQoNCg0KIyMjIHstfQ0KDQpHT0YgaXMgYWNjZXB0YWJsZSENCg0KPGJyPg0KDQpGb3Igc3Vic2VxdWVudCBtZXRhLWFuYWx5c2lzLCB3ZSBuZWVkIHRvIGVuc3VyZSB0aGF0IHRoZSBtb2RlbCBzcGVjaWZpY2F0aW9uIGZvciBhbGwgb3VyIGNsdWItbmV0d29ya3MgaXMgaWRlbnRpY2FsLiBTb21lIGVmZmVjdHMgd2VyZSByYXRoZXIgaW1wb3J0YW50IGluIG90aGVyIGNsdWJzLiBXZSBmaXggdGhlc2UgdG8gMDoNCg0KYGBge3IgZXZhbD1GLCByZXN1bHRzPSdoaWRlJ30NCm15ZWZmMiA8LSBzZXRFZmZlY3QoIG15ZWZmMiwgb3V0UG9wU3FydCwgbmFtZSA9ICJrdWRvbmV0IiwgZml4ID0gVFJVRSwgdGVzdCA9IEZBTFNFLCBpbml0aWFsVmFsdWUgPSAwKQ0KbXllZmYyIDwtIHNldEVmZmVjdCggbXllZmYyLCByZWNpQWN0LCBuYW1lID0gImt1ZG9uZXQiLCBmaXggPSBUUlVFLCB0ZXN0ID0gRkFMU0UsIGluaXRpYWxWYWx1ZSA9IDApDQpteWVmZjIgPC0gaW5jbHVkZUludGVyYWN0aW9uKG15ZWZmMiwgcmVjaXAsIGd3ZXNwRkYsIHBhcmFtZXRlciA9IDY5LCBuYW1lID0gImt1ZG9uZXQiKQ0KZWZmMSA8LSBteWVmZjJbbXllZmYyJGluY2x1ZGUsXSRlZmZlY3QxWzI3XQ0KZWZmMiA8LSBteWVmZjJbbXllZmYyJGluY2x1ZGUsXSRlZmZlY3QyWzI3XQ0KbXllZmYyIDwtIHNldEVmZmVjdChteWVmZjIsIHVuc3BJbnQsIGZpeD1UUlVFLCB0ZXN0PUZBTFNFLCBlZmZlY3QxPWVmZjEsIGVmZmVjdDI9ZWZmMikNCmBgYA0KDQo8YnI+DQoNCi0tLS0NCg0KV2UgaGF2ZSBtb2RlbGVkIHRoZSBkeW5hbWljcyBvZiBrdWRvcyB0aWUgZm9ybWF0aW9uLiBOb3cgbGV0J3MgbW9kZWwgZHluYW1pY3MgaW4gcnVubmluZyBiZWhhdmlvcnMuDQoNCiMjIENvdmFyaWF0ZSBlZmZlY3RzDQpXZSBzdGFydCB3aXRoIGVmZmVjdHMgb24gYmVoYXZpb3IgY2hhbmdlcyBvZiBvdGhlciB2YXJpYWJsZXMuDQoNCi0gdGhlIGludGVyZGVwZW5kZW5jZSBiZXR3ZWVuIHJ1bm5pbmcgZnJlcXVlbmN5IGFuZCBvdGhlciBzcG9ydHMgZnJlcXVlbmN5Lg0KLSBnZW5kZXIgb24gYmVoYXZpb3INCiAgDQpgYGB7ciBlY2hvPVQsIHJlc3VsdHM9J2hpZGUnfQ0KbXllZmYzIDwtIGluY2x1ZGVFZmZlY3RzKG15ZWZmMiwgZWZmRnJvbSwgbmFtZSA9ICJmcmVxX3J1biIsIGludGVyYWN0aW9uMSA9ICJmcmVxX290aGVyIikNCm15ZWZmMyA8LSBpbmNsdWRlRWZmZWN0cyhteWVmZjMsIGVmZkZyb20sIG5hbWUgPSAiZnJlcV9ydW4iLCBpbnRlcmFjdGlvbjEgPSAiZ2VuZGVyIikNCmBgYA0KICANCjxicj4NCg0KLS0tLQ0KICANCiMjIEluZmx1ZW5jZSBlZmZlY3RzIHsjaW5mfQ0KTGFzdCwgd2UgaW5jbHVkZSBlZmZlY3RzIG9mIG5ldHdvcmsgcG9zaXRpb24gKGluZGVncmVlKSBhbmQgYWx0ZXIgYmVoYXZpb3JzIChhdmVyYWdlIGFsdGVyL3NpbWlsYXJpdHksIGV0Yy4pIG9uIGJlaGF2aW9yIGNoYW5nZS4NCldlIG1ha2UsIGZvciBlYWNoIGNsdWIsIDYgbW9kZWwgc3BlY2lmaWNhdGlvbnMuIFdlIHNhdmUgdGhlc2UgZWZmZWN0IG9iamVjdHMgaW4gYSBsaXN0Lg0KDQotIE1vZGVsIDE6IGJhc2UgbW9kZWwgKyBpbmRlZ3JlZSBlZmZlY3Qgb24gcnVubmluZw0KLSBNb2RlbCAyOiBNb2RlbCAxICsgYXZlcmFnZSBhbHRlciBlZmZlY3QNCi0gTW9kZWwgMzogTW9kZWwgMSArIGF2ZXJhZ2UgYXR0cmFjdGlvbiBoaWdoZXINCi0gTW9kZWwgNDogTW9kZWwgMSArIGF2ZXJhZ2UgYXR0cmFjdGlvbiBsb3dlcg0KLSBNb2RlbCA1OiBNb2RlbCAxICsgYXZlcmFnZSBhdHRyYWN0aW9uIGhpZ2hlciArIGxvd2VyDQotIE1vZGVsIDY6IE1vZGVsIDEgKyBhdmVyYWdlIHNpbWlsYXJpdHkgZWZmZWN0DQoNCldlIGFsc28gZml4ZWQtYW5kLXRlc3RlZCB0aGUgZWZmZWN0IG9mIG91dGRlZ3JlZSBvbiBiZWhhdmlvciwgdG8gcnVsZSBvdXQgcG9zc2libGUgY29uZm91bmRpbmcgb2YgdGhlIG91dGRlZ3JlZSBlZmZlY3QuIFNjb3JlLXR5cGUgdGVzdCBpbmRpY2F0ZWQgdGhhdCBvdXRkZWdyZWUtZWZmZWN0cyBvbiBiZWhhdmlvciB3ZXJlIDAgKG5vdCBzaG93bikuIA0KDQoNCmBgYHtyIGVjaG89VCwgcmVzdWx0cz0naGlkZSd9DQpteWVmZjAgPC0gbXllZmYzICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgbW9kZWwgMDogYmFzZQ0KbXllZmYxIDwtIGluY2x1ZGVFZmZlY3RzKG15ZWZmMCwgaW5kZWcsIG5hbWUgPSAiZnJlcV9ydW4iLCBpbnRlcmFjdGlvbjEgPSAia3Vkb25ldCIpICAgICAgICAjIG1vZGVsIDE6IGluZGVncmVlDQpteWVmZjIgPC0gaW5jbHVkZUVmZmVjdHMobXllZmYxLCBhdkFsdCwgbmFtZSA9ICJmcmVxX3J1biIsIGludGVyYWN0aW9uMSA9ICJrdWRvbmV0IikgICAgICAgICMgbW9kZWwgMjogYXZBbHQNCm15ZWZmMyA8LSBpbmNsdWRlRWZmZWN0cyhteWVmZjEsIGF2QXR0SGlnaGVyLCBuYW1lID0gImZyZXFfcnVuIiwgaW50ZXJhY3Rpb24xID0gImt1ZG9uZXQiKSAgIyBtb2RlbCAzOiBhdkF0dEhpZ2hlcg0KbXllZmY0IDwtIGluY2x1ZGVFZmZlY3RzKG15ZWZmMSwgYXZBdHRMb3dlciwgbmFtZSA9ICJmcmVxX3J1biIsIGludGVyYWN0aW9uMSA9ICJrdWRvbmV0IikgICAjIG1vZGVsIDQ6IGF2QXR0TG93ZXINCm15ZWZmNSA8LSBpbmNsdWRlRWZmZWN0cyhteWVmZjMsIGF2QXR0TG93ZXIsIG5hbWUgPSAiZnJlcV9ydW4iLCBpbnRlcmFjdGlvbjEgPSAia3Vkb25ldCIpICAgIyBtb2RlbCA1OiBhdkF0dEhpZ2hlcitMb3dlcg0KbXllZmY2IDwtIGluY2x1ZGVFZmZlY3RzKG15ZWZmMSwgYXZTaW0sIG5hbWUgPSAiZnJlcV9ydW4iLCBpbnRlcmFjdGlvbjEgPSAia3Vkb25ldCIpICAgICAgICAjIG1vZGVsIDY6IGF2U2ltDQoNCm15ZWZmIDwtIGxpc3QobXllZmYxLCBteWVmZjIsIG15ZWZmMywgbXllZmY0LCBteWVmZjUsIG15ZWZmNikNCmBgYA0KDQogIA0KPGJyPg0KICANCi0tLS0NCiAgDQojIFN0ZXAgNDogRXN0aW1hdGUgdGhlIG1vZGVsDQogIA0KTGV0J3MgZXN0aW1hdGUgdGhlc2UgbW9kZWxzLiBXZSByZXJ1biB0aGUgbW9kZWxzIHVudGlsIGFkZXF1YXRlIGNvbnZlcmdlbmNlIGlzIHJlYWNoZWQuDQpXZSBzdG9yZSB0aGUgc2llbmFGaXQgb2JqZWN0cyBvZiB0aGVzZSBtb2RlbHMgaW4gYSBsaXN0LCB3aGljaCB3ZSBzYXZlIGxhdGVyIG9uLiBXZSB1c2UgJ3JldHVybkRlcHM9VFJVRScgZm9yIGtlZXBpbmcgdGhlIHNpbXVsYXRlZCBkYXRhIChuZXR3b3JrcyBhbmQgYmVoYXZpb3IpLg0KDQpgYGB7ciBldmFsPSBGIH0NCm09NiAjIG1vZGVscyB0byBlc3RpbWF0ZSAoaW5kZWcsIGF2QWx0LCBhdkF0dEhpZ2hlciwgYXZBdHRMb3dlciwgYXZBdHRIaWdoZXIrTG93ZXIsIGF2U2ltKQ0KDQojIHR3ZWFrIHRoZSBhbGdvcml0aG0NCm15YWxnb3JpdGhtIDwtIHNpZW5hQWxnb3JpdGhtQ3JlYXRlKHByb2puYW1lID0gInRlc3QiLCBuc3ViPTUsIG4zPTMgKQ0KDQojIHNpZW5hMDcoIG15YWxnb3JpdGhtLCBkYXRhID0gbXlkYXRhLCBlZmZlY3RzID0gbXllZmZbW2pdXSwgcHJldkFucz0gc2llbmFGaXRbW2pdXSwgcmV0dXJuRGVwcz1UUlVFLCB1c2VDbHVzdGVyPVRSVUUsIG5ick5vZGVzPTEwLCBpbml0Qz1UUlVFLCBiYXRjaD1UUlVFKQ0KDQojIHdlIG1ha2UgYSBsaXN0IGZvciBzdG9yaW5nIHRoZSBSU2llbmEgZml0IG9iamVjdHMNCnNpZW5hRml0IDwtIGxpc3QoKQ0KDQojIGZvciBjbHViIGkgKGhlcmUsIDEpIHdlIHJ1biBtb2RlbHMgaiBpbiAxOm0NCmkgPSAxDQpmb3IgKGogaW4gMTptKSB7DQogDQojIHdlIGVzdGltYXRlIHRoZSBtb2RlbA0KdHJ5IDwtIDENCnByaW50KHBhc3RlKCJFc3RpbWF0aW5nIG1vZGVsICIsIGosICIgZm9yIGNsdWIgMSIsIHNlcD0iIikpDQpzaWVuYUZpdFtbal1dIDwtIHNpZW5hMDcobXlhbGdvcml0aG0sIGRhdGEgPSBteWRhdGEsIGVmZmVjdHMgPSBteWVmZltbal1dLCByZXR1cm5EZXBzPVRSVUUsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHVzZUNsdXN0ZXI9VFJVRSwgbmJyTm9kZXM9MTAsIGluaXRDPVRSVUUsIGJhdGNoPVRSVUUpICMgc3RvcmUgaXQgaW4gdGhlIGxpc3QNCiAgICANCiAgICAjIHJlLXJ1biB1bnRpbCB3ZSByZWFjaCBhZGVxdWF0ZSBjb252ZXJnZW5jZSANCiAgICB3aGlsZSAoVFJVRSl7DQogICAgICBpZihzaWVuYUZpdFtbal1dJHRjb252Lm1heCA+PSAuMjUpew0KICAgICAgICB0cnkgPC0gdHJ5ICsgMQ0KICAgICAgICBpZiAodHJ5PjMwKSB7DQogICAgICAgICAgcHJpbnQocGFzdGUoIk5vdyBpdCBsYXN0ZWQgdG8gbG9uZyEiKSANCiAgICAgICAgICBicmVhayAgICAgIA0KICAgICAgICB9DQogICAgICAgIHByaW50KHBhc3RlKCJNb2RlbCBkaWQgbm90IGNvbnZlcmdlIGFkZXF1YXRlbHkgKCIsIHNpZW5hRml0W1tqXV0kdGNvbnYubWF4LCAiKTsgIiwgIlJlcGVhdCB0aGUgZXN0aW1hdGlvbiAoIiwgInRyeSAiLCB0cnksICIpIiwgc2VwID0gIiIpKQ0KICAgICAgICBzaWVuYUZpdFtbal1dIDwtIHNpZW5hMDcoIG15YWxnb3JpdGhtLCBkYXRhID0gbXlkYXRhLCBlZmZlY3RzID0gbXllZmZbW2pdXSwgcHJldkFucz0gc2llbmFGaXRbW2pdXSwgcmV0dXJuRGVwcz1UUlVFLCB1c2VDbHVzdGVyPVRSVUUsIG5ick5vZGVzPTEwLCBpbml0Qz1UUlVFLCBiYXRjaD1UUlVFKQ0KICAgICAgfWVsc2V7DQogICAgICAgIHByaW50KHBhc3RlKCJSZWFjaGVkIG92ZXJhbGwgbWF4aW11bSBjb252ZXJnZW5jZSByYXRpbyBvZjogIiwgc2llbmFGaXRbW2pdXSR0Y29udi5tYXgsIHNlcCA9ICIiKSkNCiAgICAgICAgcHJpbnQoIiIpDQogICAgICAgIGJyZWFrDQogICAgICB9DQogICAgfQ0KICAgIA0KICB9DQogICMgYW5kIHNhdmUgdGhlIGxpc3Qgd2l0aCBSU2llbmEgZml0IG9iamVjdHMNCiAgc2F2ZShzaWVuYUZpdCwgZmlsZT1wYXN0ZSgidGVzdCIsICIvIiwgInNpZW5hRml0IiwgIi8iLCAic2llbmFGaXRfY2x1YiIsIGksICIuUkRhdGEiLCBzZXAgPSAiIikpDQogIHByaW50KHBhc3RlKCJBbGwgbW9kZWxzIGFyZSBlc3RpbWF0ZWQgZm9yIGNsdWIgIiwgaSwgIi4gTW9kZWwgcmVzdWx0cyBhcmUgc3RvcmVkIGluIHNpZW5hRml0X2NsdWIiLCBpLCAiLlJEYXRhIiwgc2VwPSIiKSkNCg0KfQ0KDQpzaWVuYUZpdF9jbHViTCA8LSBsaXN0KCkNCg0KZm9yIChpIGluIDE6NSkgew0KICB0ZW1wLnNwYWNlIDwtIG5ldy5lbnYoKQ0KICBiYXIgPC0gbG9hZChwYXN0ZSgidGVzdC9zaWVuYUZpdC9zaWVuYUZpdF9jbHViIiwgaSwgIi5SRGF0YSIsIHNlcD0iIiksIHRlbXAuc3BhY2UpDQogIHNpZW5hRml0X2NsdWJMW1tpXV0gPC0gZ2V0KGJhciwgdGVtcC5zcGFjZSkNCiAgcm0odGVtcC5zcGFjZSkNCn0NCg0KDQpsYXBwbHkoc2llbmFGaXRfY2x1YkwsICdbWycsIDUpDQptYXAoc2llbmFGaXRfY2x1YkwsIDYpDQoNCg0KYGBgDQoNCg0KDQo8YnI+IA0KDQpCZWNhdXNlIHdlIGFyZSBub3cgbW9kZWxpbmcgdGhlIGV2b2x1dGlvbiBvZiBib3RoIHRoZSBuZXR3b3JrIGFuZCB0aGUgYXR0cmlidXRlIChydW5uaW5nIGZyZXEuKSwgd2Ugd2lsbCBhZGQgYW4gYWRkaXRpb25hbCBpbmRpY2F0b3IgdG8gZXZhbHVhdGUgR09GOyBuYW1lbHksIGRvZXMgdGhlIG1vZGVsIGNhcHR1cmUgdGhlIGRpc3RyaWJ1dGlvbiBvZiBhY3RvcnPigJkgYXR0cmlidXRlIGxldmVscyBvdmVyIHRpbWU/DQoNCg0KYGBge3IgZXZhbD1GLCByZXN1bHRzPSdoaWRlJ30NCmdvZmJlaCA8LSBzaWVuYUdPRihzaWVuYUZpdFtbNV1dLA0KICAgICAgICAgICAgICAgICAgIEJlaGF2aW9yRGlzdHJpYnV0aW9uLCBsZXZscyA9IDA6NywNCiAgICAgICAgICAgICAgICAgICB2ZXJib3NlPVRSVUUsIGpvaW49VFJVRSwNCiAgICAgICAgICAgICAgICAgICB2YXJOYW1lPSJmcmVxX3J1biIpDQpzYXZlKGdvZmJlaCwgZmlsZT0gcGFzdGUoImZpbGVzIiwgIi8iLCAidGVzdCBjbHViIDEiLCAiLyIsICJnb2ZiZWguUkRhdGEiLCBzZXA9IiIpKQ0KYGBgDQoNCmBgYHtyIGNsYXNzLnNvdXJjZSA9ICdmb2xkLWhpZGUnfQ0KbG9hZCgiZmlsZXMvdGVzdCBjbHViIDEvZ29mYmVoLlJEYXRhIikNCnBsb3QoZ29mYmVoKQ0KYGBgDQoNCg0KR09GIGlzIGFkZXF1YXRlIGZvciB0aGUgZGlzdHJpYnV0aW9uIG9mIHJ1bm5pbmcgZnJlcXVlbmN5IHZhbHVlcy4NCg0KLS0tLQ0KDQojIyBOZXh0IHVwDQoNCldlIHdpbGwgY2hlY2sgd2hldGhlciB0aGlzIG1vZGVsIGNvbmZpZ3VyYXRpb24gYWxzbyBjb252ZXJnZXMgZm9yIHRoZSBbb3RoZXIgY2x1YnNdKGh0dHBzOi8vcm9iZnJhbmtlbi5naXRodWIuaW8vU3RyYXZhL290aGVyLmh0bWwpLiBUbyBzdW1tYXJpemUgdGhlIHJlc3VsdHMgb3ZlciBvdXIgY2x1YnMsIHdlIHdpbGwgcGVyZm9ybSBhIFttZXRhLWFuYWx5c2lzXShodHRwczovL3JvYmZyYW5rZW4uZ2l0aHViLmlvL1N0cmF2YS9tZXRhLmh0bWwpIHVzaW5nIGEgRmlzaGVyLXR5cGUgY29tYmluYXRpb24gb2Ygb25lLXRhaWxlZCBwLXZhbHVlcy4NCg0KDQotLS0tDQoNCg0KIyMjIFJlZmVyZW5jZXMNCg==


Copyright © 2021 Rob Franken