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
packages = c("RSiena")
fpackage.check(packages)
We will:
- Read in our R-SIENA object list
- Inspect our data
- Define our effects
- Define our algorithm
- 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:
- structural network effects
- network selection effects
- covariate effects on network and behavior
dynamics
- 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