Operation

In order to work with RCy3 you must have Cytoscape v3.7 or later installed and running. Cytoscape can be installed from cytoscape.org. The RCy3 package can be installed from Bioconductor:

if (!requireNamespace("BiocManager", quietly = TRUE))
  install.packages("BiocManager")
BiocManager::install("RCy3")
library(RCy3)

Launch Cytoscape and keep it running whenever using RCy3. Confirm that you have everything installed and that RCy3 is communicating with Cytoscape via CyREST:

cytoscapePing ()
#[1] "You are connected to Cytoscape!"

As with any R package, one can access the documentation and browse over a dozen vignettes included in the RCy3 package:

help(package=RCy3)
browseVignettes("RCy3")

Use Cases

The following sections demonstrate a variety of common and advanced network biology use cases as runnable R code snippets. The first set focuses on fundamental Cytoscape operations that are common to most use cases:

  • Loading networks (from R objects, Cytoscape files and public databases)
  • Visualizing network data
  • Filtering by node degree or data
  • Saving and exporting networks

Additionally, there are examples that demonstrate analytical workflows, relying not only on Cytoscape, but also on Cytoscape apps and other R packages:

  • Building maps of enrichment analysis results using EnrichmentMap and AutoAnnotate
  • Visualizing integrated network analysis using BioNet
  • Performing advanced graph analytics using RBGL

Loading Networks

Networks come in all shapes and sizes in multiple formats from multiple sources. Here are just a few of the myriad ways to load networks into Cytoscape using RCy3.

From R Objects. . .

# From graph objects (graphNEL)  
g <- makeSimpleGraph()
createNetworkFromGraph(g)
## And round-trip back from Cytoscape to graph 
g2 <- createGraphFromNetwork()

# From igraph objects
library(igraph)
ig <- make_graph("Zachary")
createNetworkFromIgraph(ig)
## And round-trip back from Cytoscape to igraph
ig2 <- createIgraphFromNetwork()
## Note that the Cytoscape model infers directionality

# From dataframes
nodes <- data.frame(id=c("node 0","node 1","node 2","node 3"),
                    group=c("A","A","B","B"), #categorical strings
                    score=as.integer(c(20,10,15,5)), #integers
                    stringsAsFactors=FALSE)
edges <- data.frame(source=c("node 0","node 0","node 0","node 2"),
                    target=c("node 1","node 2","node 3","node 3"),
                    interaction=c("inhibits","interacts", 
                                  "activates","interacts"),  #optional
                    weight=c(5.1,3.0,5.2,9.9), #numerics
                    stringsAsFactors=FALSE)
createNetworkFromDataFrames(nodes, edges)

From Cytoscape-supported File Formats. . .

# From Cytoscape session files
## Will erase and replace all data from current session!
openSession() # default file = galFiltered.cys

# From local network files
importNetworkFromFile() # default file = galFiltered.sif
## Supported file formats: SIF, GML, xGMML, graphML, CX, plus

# From NDEx, the network database
importNetworkFromNDEx("5be85817-1e5f-11e8-b939-0ac135e8bacf")
## Account information or accessKey are required arguments only
## when accessing private content

From Public Databases via Cytoscape Apps. . .

# From STRING, starting with a list of genes/proteins
installApp("stringApp")
gene.list <- c("T53","AKT1","CDKN1A")
gene.str <- paste(gene.list, collapse = ",")
string.cmd <- paste("string protein query cutoff=0.99 limit=40 query",
                    gene.str, sep = "=")
commandsRun(string.cmd)

# From WikiPathways, starting with a keyword
library(rWikiPathways) # install from Bioconductor
installApp("WikiPathways")
keyword <- "glioblastoma"
gbm.pathways <- findPathwaysByText(keyword)
gbm.wpid <- gbm.pathways[[1]]$id # let’s just take the first one
wikipathways.cmd <- paste("wikipathways import-as-pathway id",
                          gbm.wpid, sep = "=")
commandsRun(wikipathways.cmd)

Visualizing Data on Networks

Cytoscape excels at generating publication-quality network visualization with data overlays. This vignette demonstrates just one of the hundreds of visual style mapping options using RCy3.

# Load sample network
closeSession(FALSE) # clears all session data wihtout saving
importNetworkFromFile() # default file = galFiltered.sif

# Load sample data
csv <- system.file("extdata","galExpData.csv", package="RCy3")
data <- read.csv(csv, stringsAsFactors = FALSE)
loadTableData(data,data.key.column="name")

# Prepare data-mapping points
gal80Rexp.min <- min(data$gal80Rexp, na.rm = TRUE)
gal80Rexp.max <- max(data$gal80Rexp, na.rm = TRUE)
## For a balanced color gradient, pick the largest absolute value
gal80Rexp.max.abs <- max(abs(gal80Rexp.min), abs(gal80Rexp.max)) 

# Set node color gradient from blue to white to red
setNodeColorMapping('gal80Rexp', c(-gal80Rexp.max.abs, 0, gal80Rexp.max.abs), 
                    c('#5577FF','#FFFFFF','#FF7755'), default.color = '#999999')

Filtering Networks by Degree and by Data

Network topology and associated node or edge data can be used to make selections in Cytoscape that enable filtering and subnetworking. The filters are added to the Select tab in the Control Panel of Cytoscape’s GUI and saved in session files.

# Load demo Cytoscape session file
openSession() # default file = galFiltered.cys
net.suid <- getNetworkSuid() # get SUID for future reference

# Filter for neighbors of high degree nodes
createDegreeFilter(filter.name = "degree filter",
                   criterion = c(0,9),
                   predicate = "IS_NOT_BETWEEN")
selectFirstNeighbors() # expand selection to first neighbors
createSubnetwork(subnetwork.name = "first neighbors of high degree nodes")

# Filter for high edge betweenness
createColumnFilter(filter.name = "edge betweenness",
                   type = "edges",
                   column = "EdgeBetweenness",
                   4000,
                   "GREATER_THAN",
                   network = net.suid)
createSubnetwork(subnetwork.name = "high edge betweenness")

Saving and Exporting Networks

There are local and cloud-hosted options for saving and sharing network models and images. The Cytoscape session file (CYS) will include all networks, collections, tables and styles. It should retain every aspect of your session, including the size of the application window. Network and image exports include only the currently active network. Export to NDEx requires account information you can obtain from ndexbio.org.

# Saving sessions
saveSession("MySession") #.cys
## Leave filename blank to update previously saved session file

# Exporting images and networks
exportNetwork() #.sif
## Optionally specify filename, default is network name
## Optionally specify type: SIF(default), CX, cyjs, graphML, NNF, SIF, xGMML
exportImage(type='png') #.png
## Optionally specify filename, default is network name
## Optionally specify type: PNG (default), JPEG, PDF, PostScript, SVG 

# Exporting to NDEx, a.k.a. “Dropbox” for networks
exportNetworkToNDEx(username, password, TRUE)
## Account information (username and password) is required to upload
## Use updateNetworkInNDEx if the network has previously been uploaded

Building Maps of Enrichment Analysis Results

This workflow illustrates how to plot an annotated map of enrichment results using the EnrichmentMap Pipeline Collection of apps in Cytoscape. An enrichment map is a network visualization of related genesets in which nodes are gene sets (or pathways) and edge weight indicates the overlap in member genes. Following the construction of the enrichment map, AutoAnnotate clusters redundant gene sets and uses WordCloud to label the resulting cluster. The code uses the Commands interface to invoke EnrichmentMap and AutoAnnotate apps. After installing apps, run commandsAPI() to open the live Swagger documentation to browse and execute command-line syntax.

installApp("EnrichmentMap Pipeline Collection") # installs 4 apps
# Download sample gmt file of human pathways
gmt.file <- "rcy3_enrichmentmap.gmt"
download.file(file.path("http://download.baderlab.org/EM_Genesets",
                        "September_01_2019/Human/symbol/Pathways",
                        "Human_WikiPathways_September_01_2019_symbol.gmt"),
              gmt.file)
# Run EnrichmentMap build command
em_command <- paste('enrichmentmap build analysisType="generic"',
                    "gmtFile=", paste(getwd(), gmt.file, sep="/"),
                    "pvalue=", 1,
                    "qvalue=", 1,
                    "similaritycutoff=",0.25,
                    "coefficients=","JACCARD")
print(em_command)
commandsGET(em_command)
# Run the AutoAnnotate command
aa_command <- paste("autoannotate annotate-clusterBoosted",
                    "clusterAlgorithm=MCL",
                    "labelColumn=EnrichmentMap::GS_DESCR",
                    "maxWords=3")
print(aa_command)
commandsGET(aa_command)
# Annotate a subnetwork
createSubnetwork(c(1:4),"__mclCluster")
commandsGET(aa_command)

Visualizing Integrated Network Analysis Using BioNet

The BioNet package implements analytical methods to perform integrated network analysis, e.g., of gene expression data and clinical survival data in the context of protein-protein interaction networks. Partnered with RCy3, the analytical results from BioNet can be visualized in Cytoscape with vastly more options for customization. Starting with the “Quick Start” tutorial from BioNet, we pass the results directly to Cytoscape for visualization:

library(BioNet) # install from Bioconductor
library(DLBCL) # install from Bioconductor
data(dataLym)
data(interactome)
## The following steps are from BioNet's Quick Start tutorial:
pvals <- cbind(t = dataLym$t.pval, s = dataLym$s.pval)
rownames(pvals) <- dataLym$label
pval <- aggrPvals(pvals, order = 2, plot = FALSE)
subnet <- subNetwork(dataLym$label, interactome)
subnet <- rmSelfLoops(subnet)
fb <- fitBumModel(pval, plot = FALSE)
scores <- scoreNodes(subnet, fb, fdr = 0.001)
module <- runFastHeinz(subnet, scores)
logFC <- dataLym$diff
names(logFC) <- dataLym$label
plotModule(module, scores = scores, diff.expr = logFC)

# Using RCy3 we can generate a custom visualization of the same output
## Create network from graphNEL object and load data calculated above
createNetworkFromGraph(module, "module", "BioNet")
loadTableData(as.data.frame(scores))
loadTableData(as.data.frame(logFC))
## Set styles
setNodeLabelMapping("geneSymbol")
setNodeFontSizeDefault(18)
setNodeBorderWidthDefault(3.0)
logFC.max.abs <- max(abs(min(logFC)), abs(max(logFC))) 
setNodeColorMapping('logFC', c(-logFC.max.abs, 0, logFC.max.abs), 
                    c('#5577FF','#FFFFFF','#FF7755'), default.color = '#999999')
createColumnFilter("Positive scores", "scores",c(0,max(scores)),"BETWEEN")
setNodeShapeBypass(getSelectedNodes(), "ELLIPSE")
clearSelection()

##Performing Advanced Graph Analytics Using RBGL As an interface to the BOOST library, the RBGL Bioconductor package offers an impressive array of analytical functions for graphs. Here we will start with a network in Cytoscape, load it into R as a graph object, perform shortest path calculation using RBGL and then visualize the results back in Cytoscape.

library(RBGL) # install from Bioconductor
# Convert a sample Cytoscape network to a graph object
openSession()
g <- createGraphFromNetwork()
# Identify start and finish nodes (styling is optional)
start <- "YNL216W"
finish <- "YER040W"
setNodeBorderWidthBypass(c(start, finish), 20)
setNodeBorderColorBypass(start, "#00CC33")
setNodeBorderColorBypass(finish, "#CC00CC")
# Use RBGL to perform shortest path calculation
shortest <- sp.between(g, start, finish)
shortest$`YNL216W:YER040W`$length
#[1] 6
shortest.path <- shortest$`YNL216W:YER040W`$path_detail
# Visualize results in Cytoscape
selectNodes(shortest.path, "name")
setNodeBorderWidthBypass(shortest.path, 20)
createSubnetwork()
LS0tCnRpdGxlOiAiUkN5MyBVc2UgQ2FzZXMiCmF1dGhvcjogIkFsZXggUGljbywgSnVsaWEgR3VzdGF2c2VuLCBTaHJhZGRoYSBQYWksIFJ1dGggSXNzZXJsaW4sIEJhcnJ5IERlbWNoYWsiCmRhdGU6ICJgciBTeXMuRGF0ZSgpYCIKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6CiAgICB0b2NfZmxvYXQ6IHRydWUKICAgIGNvZGVfZm9sZGluZzogIm5vbmUiCiMgIHBkZl9kb2N1bWVudDoKIyAgICB0b2M6IHRydWUgIAotLS0KYGBge3IsIGVjaG8gPSBGQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KAogIGV2YWw9RkFMU0UKKQpgYGAKCiMgT3BlcmF0aW9uCkluIG9yZGVyIHRvIHdvcmsgd2l0aCBSQ3kzIHlvdSBtdXN0IGhhdmUgQ3l0b3NjYXBlIHYzLjcgb3IgbGF0ZXIgaW5zdGFsbGVkIGFuZCBydW5uaW5nLiAgQ3l0b3NjYXBlIGNhbiBiZSBpbnN0YWxsZWQgZnJvbSBbY3l0b3NjYXBlLm9yZ10oaHR0cHM6Ly9jeXRvc2NhcGUub3JnKS4gVGhlIFJDeTMgcGFja2FnZSBjYW4gYmUgaW5zdGFsbGVkIGZyb20gW0Jpb2NvbmR1Y3Rvcl0oaHR0cHM6Ly9iaW9jb25kdWN0b3Iub3JnL3BhY2thZ2VzL3JlbGVhc2UvYmlvYy9odG1sL1JDeTMuaHRtbH17QmlvY29uZHVjdG9yKToKYGBge3IsIGV2YWw9RkFMU0V9CmlmICghcmVxdWlyZU5hbWVzcGFjZSgiQmlvY01hbmFnZXIiLCBxdWlldGx5ID0gVFJVRSkpCiAgaW5zdGFsbC5wYWNrYWdlcygiQmlvY01hbmFnZXIiKQpCaW9jTWFuYWdlcjo6aW5zdGFsbCgiUkN5MyIpCmxpYnJhcnkoUkN5MykKYGBgCgpMYXVuY2ggQ3l0b3NjYXBlIGFuZCBrZWVwIGl0IHJ1bm5pbmcgd2hlbmV2ZXIgdXNpbmcgUkN5My4gQ29uZmlybSB0aGF0IHlvdSBoYXZlIGV2ZXJ5dGhpbmcgaW5zdGFsbGVkIGFuZCB0aGF0IFJDeTMgaXMgY29tbXVuaWNhdGluZyB3aXRoIEN5dG9zY2FwZSB2aWEgQ3lSRVNUOgpgYGB7cn0KY3l0b3NjYXBlUGluZyAoKQojWzFdICJZb3UgYXJlIGNvbm5lY3RlZCB0byBDeXRvc2NhcGUhIgpgYGAKCkFzIHdpdGggYW55IFIgcGFja2FnZSwgb25lIGNhbiBhY2Nlc3MgdGhlIGRvY3VtZW50YXRpb24gYW5kIGJyb3dzZSBvdmVyIGEgZG96ZW4gdmlnbmV0dGVzIGluY2x1ZGVkIGluIHRoZSBSQ3kzIHBhY2thZ2U6CmBgYHtyfQpoZWxwKHBhY2thZ2U9UkN5MykKYnJvd3NlVmlnbmV0dGVzKCJSQ3kzIikKYGBgCgojIFVzZSBDYXNlcwpUaGUgZm9sbG93aW5nIHNlY3Rpb25zIGRlbW9uc3RyYXRlIGEgdmFyaWV0eSBvZiBjb21tb24gYW5kIGFkdmFuY2VkIG5ldHdvcmsgYmlvbG9neSB1c2UgY2FzZXMgYXMgcnVubmFibGUgUiBjb2RlIHNuaXBwZXRzLiBUaGUgZmlyc3Qgc2V0IGZvY3VzZXMgb24gZnVuZGFtZW50YWwgQ3l0b3NjYXBlIG9wZXJhdGlvbnMgdGhhdCBhcmUgY29tbW9uIHRvIG1vc3QgdXNlIGNhc2VzOgoKKiBMb2FkaW5nIG5ldHdvcmtzIChmcm9tIFIgb2JqZWN0cywgQ3l0b3NjYXBlIGZpbGVzIGFuZCBwdWJsaWMgZGF0YWJhc2VzKQoqIFZpc3VhbGl6aW5nIG5ldHdvcmsgZGF0YQoqIEZpbHRlcmluZyBieSBub2RlIGRlZ3JlZSBvciBkYXRhCiogU2F2aW5nIGFuZCBleHBvcnRpbmcgbmV0d29ya3MKCkFkZGl0aW9uYWxseSwgdGhlcmUgYXJlIGV4YW1wbGVzIHRoYXQgZGVtb25zdHJhdGUgYW5hbHl0aWNhbCB3b3JrZmxvd3MsIHJlbHlpbmcgbm90IG9ubHkgb24gQ3l0b3NjYXBlLCBidXQgYWxzbyBvbiBDeXRvc2NhcGUgYXBwcyBhbmQgb3RoZXIgUiBwYWNrYWdlczoKCiogQnVpbGRpbmcgbWFwcyBvZiBlbnJpY2htZW50IGFuYWx5c2lzIHJlc3VsdHMgdXNpbmcgRW5yaWNobWVudE1hcCBhbmQgQXV0b0Fubm90YXRlCiogVmlzdWFsaXppbmcgaW50ZWdyYXRlZCBuZXR3b3JrIGFuYWx5c2lzIHVzaW5nIEJpb05ldAoqIFBlcmZvcm1pbmcgYWR2YW5jZWQgZ3JhcGggYW5hbHl0aWNzIHVzaW5nIFJCR0wKCiMjIExvYWRpbmcgTmV0d29ya3MKTmV0d29ya3MgY29tZSBpbiBhbGwgc2hhcGVzIGFuZCBzaXplcyBpbiBtdWx0aXBsZSBmb3JtYXRzIGZyb20gbXVsdGlwbGUgc291cmNlcy4gSGVyZSBhcmUganVzdCBhIGZldyBvZiB0aGUgbXlyaWFkIHdheXMgdG8gbG9hZCBuZXR3b3JrcyBpbnRvIEN5dG9zY2FwZSB1c2luZyBSQ3kzLgoKRnJvbSBSIE9iamVjdHMuIC4gLgpgYGB7cn0KIyBGcm9tIGdyYXBoIG9iamVjdHMgKGdyYXBoTkVMKSAgCmcgPC0gbWFrZVNpbXBsZUdyYXBoKCkKY3JlYXRlTmV0d29ya0Zyb21HcmFwaChnKQojIyBBbmQgcm91bmQtdHJpcCBiYWNrIGZyb20gQ3l0b3NjYXBlIHRvIGdyYXBoIApnMiA8LSBjcmVhdGVHcmFwaEZyb21OZXR3b3JrKCkKCiMgRnJvbSBpZ3JhcGggb2JqZWN0cwpsaWJyYXJ5KGlncmFwaCkKaWcgPC0gbWFrZV9ncmFwaCgiWmFjaGFyeSIpCmNyZWF0ZU5ldHdvcmtGcm9tSWdyYXBoKGlnKQojIyBBbmQgcm91bmQtdHJpcCBiYWNrIGZyb20gQ3l0b3NjYXBlIHRvIGlncmFwaAppZzIgPC0gY3JlYXRlSWdyYXBoRnJvbU5ldHdvcmsoKQojIyBOb3RlIHRoYXQgdGhlIEN5dG9zY2FwZSBtb2RlbCBpbmZlcnMgZGlyZWN0aW9uYWxpdHkKCiMgRnJvbSBkYXRhZnJhbWVzCm5vZGVzIDwtIGRhdGEuZnJhbWUoaWQ9Yygibm9kZSAwIiwibm9kZSAxIiwibm9kZSAyIiwibm9kZSAzIiksCiAgICAgICAgICAgICAgICAgICAgZ3JvdXA9YygiQSIsIkEiLCJCIiwiQiIpLCAjY2F0ZWdvcmljYWwgc3RyaW5ncwogICAgICAgICAgICAgICAgICAgIHNjb3JlPWFzLmludGVnZXIoYygyMCwxMCwxNSw1KSksICNpbnRlZ2VycwogICAgICAgICAgICAgICAgICAgIHN0cmluZ3NBc0ZhY3RvcnM9RkFMU0UpCmVkZ2VzIDwtIGRhdGEuZnJhbWUoc291cmNlPWMoIm5vZGUgMCIsIm5vZGUgMCIsIm5vZGUgMCIsIm5vZGUgMiIpLAogICAgICAgICAgICAgICAgICAgIHRhcmdldD1jKCJub2RlIDEiLCJub2RlIDIiLCJub2RlIDMiLCJub2RlIDMiKSwKICAgICAgICAgICAgICAgICAgICBpbnRlcmFjdGlvbj1jKCJpbmhpYml0cyIsImludGVyYWN0cyIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImFjdGl2YXRlcyIsImludGVyYWN0cyIpLCAgI29wdGlvbmFsCiAgICAgICAgICAgICAgICAgICAgd2VpZ2h0PWMoNS4xLDMuMCw1LjIsOS45KSwgI251bWVyaWNzCiAgICAgICAgICAgICAgICAgICAgc3RyaW5nc0FzRmFjdG9ycz1GQUxTRSkKY3JlYXRlTmV0d29ya0Zyb21EYXRhRnJhbWVzKG5vZGVzLCBlZGdlcykKYGBgCgpGcm9tIEN5dG9zY2FwZS1zdXBwb3J0ZWQgRmlsZSBGb3JtYXRzLiAuIC4KYGBge3J9CiMgRnJvbSBDeXRvc2NhcGUgc2Vzc2lvbiBmaWxlcwojIyBXaWxsIGVyYXNlIGFuZCByZXBsYWNlIGFsbCBkYXRhIGZyb20gY3VycmVudCBzZXNzaW9uIQpvcGVuU2Vzc2lvbigpICMgZGVmYXVsdCBmaWxlID0gZ2FsRmlsdGVyZWQuY3lzCgojIEZyb20gbG9jYWwgbmV0d29yayBmaWxlcwppbXBvcnROZXR3b3JrRnJvbUZpbGUoKSAjIGRlZmF1bHQgZmlsZSA9IGdhbEZpbHRlcmVkLnNpZgojIyBTdXBwb3J0ZWQgZmlsZSBmb3JtYXRzOiBTSUYsIEdNTCwgeEdNTUwsIGdyYXBoTUwsIENYLCBwbHVzCgojIEZyb20gTkRFeCwgdGhlIG5ldHdvcmsgZGF0YWJhc2UKaW1wb3J0TmV0d29ya0Zyb21OREV4KCI1YmU4NTgxNy0xZTVmLTExZTgtYjkzOS0wYWMxMzVlOGJhY2YiKQojIyBBY2NvdW50IGluZm9ybWF0aW9uIG9yIGFjY2Vzc0tleSBhcmUgcmVxdWlyZWQgYXJndW1lbnRzIG9ubHkKIyMgd2hlbiBhY2Nlc3NpbmcgcHJpdmF0ZSBjb250ZW50CmBgYAoKRnJvbSBQdWJsaWMgRGF0YWJhc2VzIHZpYSBDeXRvc2NhcGUgQXBwcy4gLiAuCmBgYHtyfQojIEZyb20gU1RSSU5HLCBzdGFydGluZyB3aXRoIGEgbGlzdCBvZiBnZW5lcy9wcm90ZWlucwppbnN0YWxsQXBwKCJzdHJpbmdBcHAiKQpnZW5lLmxpc3QgPC0gYygiVDUzIiwiQUtUMSIsIkNES04xQSIpCmdlbmUuc3RyIDwtIHBhc3RlKGdlbmUubGlzdCwgY29sbGFwc2UgPSAiLCIpCnN0cmluZy5jbWQgPC0gcGFzdGUoInN0cmluZyBwcm90ZWluIHF1ZXJ5IGN1dG9mZj0wLjk5IGxpbWl0PTQwIHF1ZXJ5IiwKICAgICAgICAgICAgICAgICAgICBnZW5lLnN0ciwgc2VwID0gIj0iKQpjb21tYW5kc1J1bihzdHJpbmcuY21kKQoKIyBGcm9tIFdpa2lQYXRod2F5cywgc3RhcnRpbmcgd2l0aCBhIGtleXdvcmQKbGlicmFyeShyV2lraVBhdGh3YXlzKSAjIGluc3RhbGwgZnJvbSBCaW9jb25kdWN0b3IKaW5zdGFsbEFwcCgiV2lraVBhdGh3YXlzIikKa2V5d29yZCA8LSAiZ2xpb2JsYXN0b21hIgpnYm0ucGF0aHdheXMgPC0gZmluZFBhdGh3YXlzQnlUZXh0KGtleXdvcmQpCmdibS53cGlkIDwtIGdibS5wYXRod2F5c1tbMV1dJGlkICMgbGV04oCZcyBqdXN0IHRha2UgdGhlIGZpcnN0IG9uZQp3aWtpcGF0aHdheXMuY21kIDwtIHBhc3RlKCJ3aWtpcGF0aHdheXMgaW1wb3J0LWFzLXBhdGh3YXkgaWQiLAogICAgICAgICAgICAgICAgICAgICAgICAgIGdibS53cGlkLCBzZXAgPSAiPSIpCmNvbW1hbmRzUnVuKHdpa2lwYXRod2F5cy5jbWQpCmBgYAoKIyMgVmlzdWFsaXppbmcgRGF0YSBvbiBOZXR3b3JrcwpDeXRvc2NhcGUgZXhjZWxzIGF0IGdlbmVyYXRpbmcgcHVibGljYXRpb24tcXVhbGl0eSBuZXR3b3JrIHZpc3VhbGl6YXRpb24gd2l0aCBkYXRhIG92ZXJsYXlzLiBUaGlzIHZpZ25ldHRlIGRlbW9uc3RyYXRlcyBqdXN0IG9uZSBvZiB0aGUgaHVuZHJlZHMgb2YgdmlzdWFsIHN0eWxlIG1hcHBpbmcgb3B0aW9ucyB1c2luZyBSQ3kzLgpgYGB7cn0KIyBMb2FkIHNhbXBsZSBuZXR3b3JrCmNsb3NlU2Vzc2lvbihGQUxTRSkgIyBjbGVhcnMgYWxsIHNlc3Npb24gZGF0YSB3aWh0b3V0IHNhdmluZwppbXBvcnROZXR3b3JrRnJvbUZpbGUoKSAjIGRlZmF1bHQgZmlsZSA9IGdhbEZpbHRlcmVkLnNpZgoKIyBMb2FkIHNhbXBsZSBkYXRhCmNzdiA8LSBzeXN0ZW0uZmlsZSgiZXh0ZGF0YSIsImdhbEV4cERhdGEuY3N2IiwgcGFja2FnZT0iUkN5MyIpCmRhdGEgPC0gcmVhZC5jc3YoY3N2LCBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UpCmxvYWRUYWJsZURhdGEoZGF0YSxkYXRhLmtleS5jb2x1bW49Im5hbWUiKQoKIyBQcmVwYXJlIGRhdGEtbWFwcGluZyBwb2ludHMKZ2FsODBSZXhwLm1pbiA8LSBtaW4oZGF0YSRnYWw4MFJleHAsIG5hLnJtID0gVFJVRSkKZ2FsODBSZXhwLm1heCA8LSBtYXgoZGF0YSRnYWw4MFJleHAsIG5hLnJtID0gVFJVRSkKIyMgRm9yIGEgYmFsYW5jZWQgY29sb3IgZ3JhZGllbnQsIHBpY2sgdGhlIGxhcmdlc3QgYWJzb2x1dGUgdmFsdWUKZ2FsODBSZXhwLm1heC5hYnMgPC0gbWF4KGFicyhnYWw4MFJleHAubWluKSwgYWJzKGdhbDgwUmV4cC5tYXgpKSAKCiMgU2V0IG5vZGUgY29sb3IgZ3JhZGllbnQgZnJvbSBibHVlIHRvIHdoaXRlIHRvIHJlZApzZXROb2RlQ29sb3JNYXBwaW5nKCdnYWw4MFJleHAnLCBjKC1nYWw4MFJleHAubWF4LmFicywgMCwgZ2FsODBSZXhwLm1heC5hYnMpLCAKICAgICAgICAgICAgICAgICAgICBjKCcjNTU3N0ZGJywnI0ZGRkZGRicsJyNGRjc3NTUnKSwgZGVmYXVsdC5jb2xvciA9ICcjOTk5OTk5JykKCmBgYAoKIyMgRmlsdGVyaW5nIE5ldHdvcmtzIGJ5IERlZ3JlZSBhbmQgYnkgRGF0YQpOZXR3b3JrIHRvcG9sb2d5IGFuZCBhc3NvY2lhdGVkIG5vZGUgb3IgZWRnZSBkYXRhIGNhbiBiZSB1c2VkIHRvIG1ha2Ugc2VsZWN0aW9ucyBpbiBDeXRvc2NhcGUgdGhhdCBlbmFibGUgZmlsdGVyaW5nIGFuZCBzdWJuZXR3b3JraW5nLiBUaGUgZmlsdGVycyBhcmUgYWRkZWQgdG8gdGhlIFNlbGVjdCB0YWIgaW4gdGhlIENvbnRyb2wgUGFuZWwgb2YgQ3l0b3NjYXBl4oCZcyBHVUkgYW5kIHNhdmVkIGluIHNlc3Npb24gZmlsZXMuCmBgYHtyfQojIExvYWQgZGVtbyBDeXRvc2NhcGUgc2Vzc2lvbiBmaWxlCm9wZW5TZXNzaW9uKCkgIyBkZWZhdWx0IGZpbGUgPSBnYWxGaWx0ZXJlZC5jeXMKbmV0LnN1aWQgPC0gZ2V0TmV0d29ya1N1aWQoKSAjIGdldCBTVUlEIGZvciBmdXR1cmUgcmVmZXJlbmNlCgojIEZpbHRlciBmb3IgbmVpZ2hib3JzIG9mIGhpZ2ggZGVncmVlIG5vZGVzCmNyZWF0ZURlZ3JlZUZpbHRlcihmaWx0ZXIubmFtZSA9ICJkZWdyZWUgZmlsdGVyIiwKICAgICAgICAgICAgICAgICAgIGNyaXRlcmlvbiA9IGMoMCw5KSwKICAgICAgICAgICAgICAgICAgIHByZWRpY2F0ZSA9ICJJU19OT1RfQkVUV0VFTiIpCnNlbGVjdEZpcnN0TmVpZ2hib3JzKCkgIyBleHBhbmQgc2VsZWN0aW9uIHRvIGZpcnN0IG5laWdoYm9ycwpjcmVhdGVTdWJuZXR3b3JrKHN1Ym5ldHdvcmsubmFtZSA9ICJmaXJzdCBuZWlnaGJvcnMgb2YgaGlnaCBkZWdyZWUgbm9kZXMiKQoKIyBGaWx0ZXIgZm9yIGhpZ2ggZWRnZSBiZXR3ZWVubmVzcwpjcmVhdGVDb2x1bW5GaWx0ZXIoZmlsdGVyLm5hbWUgPSAiZWRnZSBiZXR3ZWVubmVzcyIsCiAgICAgICAgICAgICAgICAgICB0eXBlID0gImVkZ2VzIiwKICAgICAgICAgICAgICAgICAgIGNvbHVtbiA9ICJFZGdlQmV0d2Vlbm5lc3MiLAogICAgICAgICAgICAgICAgICAgNDAwMCwKICAgICAgICAgICAgICAgICAgICJHUkVBVEVSX1RIQU4iLAogICAgICAgICAgICAgICAgICAgbmV0d29yayA9IG5ldC5zdWlkKQpjcmVhdGVTdWJuZXR3b3JrKHN1Ym5ldHdvcmsubmFtZSA9ICJoaWdoIGVkZ2UgYmV0d2Vlbm5lc3MiKQpgYGAKCiMjIFNhdmluZyBhbmQgRXhwb3J0aW5nIE5ldHdvcmtzClRoZXJlIGFyZSBsb2NhbCBhbmQgY2xvdWQtaG9zdGVkIG9wdGlvbnMgZm9yIHNhdmluZyBhbmQgc2hhcmluZyBuZXR3b3JrIG1vZGVscyBhbmQgaW1hZ2VzLiBUaGUgQ3l0b3NjYXBlIHNlc3Npb24gZmlsZSAoQ1lTKSB3aWxsIGluY2x1ZGUgYWxsIG5ldHdvcmtzLCBjb2xsZWN0aW9ucywgdGFibGVzIGFuZCBzdHlsZXMuIEl0IHNob3VsZCByZXRhaW4gZXZlcnkgYXNwZWN0IG9mIHlvdXIgc2Vzc2lvbiwgaW5jbHVkaW5nIHRoZSBzaXplIG9mIHRoZSBhcHBsaWNhdGlvbiB3aW5kb3cuIE5ldHdvcmsgYW5kIGltYWdlIGV4cG9ydHMgaW5jbHVkZSBvbmx5IHRoZSBjdXJyZW50bHkgYWN0aXZlIG5ldHdvcmsuIEV4cG9ydCB0byBOREV4IHJlcXVpcmVzIGFjY291bnQgaW5mb3JtYXRpb24geW91IGNhbiBvYnRhaW4gZnJvbSBbbmRleGJpby5vcmddKGh0dHBzOi8vbmRleGJpby5vcmcpLgpgYGB7cn0KIyBTYXZpbmcgc2Vzc2lvbnMKc2F2ZVNlc3Npb24oIk15U2Vzc2lvbiIpICMuY3lzCiMjIExlYXZlIGZpbGVuYW1lIGJsYW5rIHRvIHVwZGF0ZSBwcmV2aW91c2x5IHNhdmVkIHNlc3Npb24gZmlsZQoKIyBFeHBvcnRpbmcgaW1hZ2VzIGFuZCBuZXR3b3JrcwpleHBvcnROZXR3b3JrKCkgIy5zaWYKIyMgT3B0aW9uYWxseSBzcGVjaWZ5IGZpbGVuYW1lLCBkZWZhdWx0IGlzIG5ldHdvcmsgbmFtZQojIyBPcHRpb25hbGx5IHNwZWNpZnkgdHlwZTogU0lGKGRlZmF1bHQpLCBDWCwgY3lqcywgZ3JhcGhNTCwgTk5GLCBTSUYsIHhHTU1MCmV4cG9ydEltYWdlKHR5cGU9J3BuZycpICMucG5nCiMjIE9wdGlvbmFsbHkgc3BlY2lmeSBmaWxlbmFtZSwgZGVmYXVsdCBpcyBuZXR3b3JrIG5hbWUKIyMgT3B0aW9uYWxseSBzcGVjaWZ5IHR5cGU6IFBORyAoZGVmYXVsdCksIEpQRUcsIFBERiwgUG9zdFNjcmlwdCwgU1ZHIAoKIyBFeHBvcnRpbmcgdG8gTkRFeCwgYS5rLmEuIOKAnERyb3Bib3jigJ0gZm9yIG5ldHdvcmtzCmV4cG9ydE5ldHdvcmtUb05ERXgodXNlcm5hbWUsIHBhc3N3b3JkLCBUUlVFKQojIyBBY2NvdW50IGluZm9ybWF0aW9uICh1c2VybmFtZSBhbmQgcGFzc3dvcmQpIGlzIHJlcXVpcmVkIHRvIHVwbG9hZAojIyBVc2UgdXBkYXRlTmV0d29ya0luTkRFeCBpZiB0aGUgbmV0d29yayBoYXMgcHJldmlvdXNseSBiZWVuIHVwbG9hZGVkCmBgYAoKIyMgQnVpbGRpbmcgTWFwcyBvZiBFbnJpY2htZW50IEFuYWx5c2lzIFJlc3VsdHMKVGhpcyB3b3JrZmxvdyBpbGx1c3RyYXRlcyBob3cgdG8gcGxvdCBhbiBhbm5vdGF0ZWQgbWFwIG9mIGVucmljaG1lbnQgcmVzdWx0cyB1c2luZyB0aGUgIFtFbnJpY2htZW50TWFwIFBpcGVsaW5lIENvbGxlY3Rpb24gb2YgYXBwc10oaHR0cDovL2FwcHMuY3l0b3NjYXBlLm9yZy9hcHBzL2VucmljaG1lbnRtYXBwaXBlbGluZWNvbGxlY3Rpb24pIGluIEN5dG9zY2FwZS4gIEFuIGVucmljaG1lbnQgbWFwIGlzIGEgbmV0d29yayB2aXN1YWxpemF0aW9uIG9mIHJlbGF0ZWQgZ2VuZXNldHMgaW4gd2hpY2ggbm9kZXMgYXJlIGdlbmUgc2V0cyAob3IgcGF0aHdheXMpIGFuZCBlZGdlIHdlaWdodCBpbmRpY2F0ZXMgdGhlIG92ZXJsYXAgaW4gbWVtYmVyIGdlbmVzLiBGb2xsb3dpbmcgdGhlIGNvbnN0cnVjdGlvbiBvZiB0aGUgZW5yaWNobWVudCBtYXAsIEF1dG9Bbm5vdGF0ZSBjbHVzdGVycyByZWR1bmRhbnQgZ2VuZSBzZXRzIGFuZCB1c2VzIFdvcmRDbG91ZCB0byBsYWJlbCB0aGUgcmVzdWx0aW5nIGNsdXN0ZXIuIFRoZSBjb2RlIHVzZXMgdGhlIENvbW1hbmRzIGludGVyZmFjZSB0byBpbnZva2UgRW5yaWNobWVudE1hcCBhbmQgQXV0b0Fubm90YXRlIGFwcHMuIEFmdGVyIGluc3RhbGxpbmcgYXBwcywgcnVuIGNvbW1hbmRzQVBJKCkgdG8gb3BlbiB0aGUgbGl2ZSBTd2FnZ2VyIGRvY3VtZW50YXRpb24gdG8gYnJvd3NlIGFuZCBleGVjdXRlIGNvbW1hbmQtbGluZSBzeW50YXguCmBgYHtyfQppbnN0YWxsQXBwKCJFbnJpY2htZW50TWFwIFBpcGVsaW5lIENvbGxlY3Rpb24iKSAjIGluc3RhbGxzIDQgYXBwcwojIERvd25sb2FkIHNhbXBsZSBnbXQgZmlsZSBvZiBodW1hbiBwYXRod2F5cwpnbXQuZmlsZSA8LSAicmN5M19lbnJpY2htZW50bWFwLmdtdCIKZG93bmxvYWQuZmlsZShmaWxlLnBhdGgoImh0dHA6Ly9kb3dubG9hZC5iYWRlcmxhYi5vcmcvRU1fR2VuZXNldHMiLAogICAgICAgICAgICAgICAgICAgICAgICAiU2VwdGVtYmVyXzAxXzIwMTkvSHVtYW4vc3ltYm9sL1BhdGh3YXlzIiwKICAgICAgICAgICAgICAgICAgICAgICAgIkh1bWFuX1dpa2lQYXRod2F5c19TZXB0ZW1iZXJfMDFfMjAxOV9zeW1ib2wuZ210IiksCiAgICAgICAgICAgICAgZ210LmZpbGUpCiMgUnVuIEVucmljaG1lbnRNYXAgYnVpbGQgY29tbWFuZAplbV9jb21tYW5kIDwtIHBhc3RlKCdlbnJpY2htZW50bWFwIGJ1aWxkIGFuYWx5c2lzVHlwZT0iZ2VuZXJpYyInLAogICAgICAgICAgICAgICAgICAgICJnbXRGaWxlPSIsIHBhc3RlKGdldHdkKCksIGdtdC5maWxlLCBzZXA9Ii8iKSwKICAgICAgICAgICAgICAgICAgICAicHZhbHVlPSIsIDEsCiAgICAgICAgICAgICAgICAgICAgInF2YWx1ZT0iLCAxLAogICAgICAgICAgICAgICAgICAgICJzaW1pbGFyaXR5Y3V0b2ZmPSIsMC4yNSwKICAgICAgICAgICAgICAgICAgICAiY29lZmZpY2llbnRzPSIsIkpBQ0NBUkQiKQpwcmludChlbV9jb21tYW5kKQpjb21tYW5kc0dFVChlbV9jb21tYW5kKQojIFJ1biB0aGUgQXV0b0Fubm90YXRlIGNvbW1hbmQKYWFfY29tbWFuZCA8LSBwYXN0ZSgiYXV0b2Fubm90YXRlIGFubm90YXRlLWNsdXN0ZXJCb29zdGVkIiwKICAgICAgICAgICAgICAgICAgICAiY2x1c3RlckFsZ29yaXRobT1NQ0wiLAogICAgICAgICAgICAgICAgICAgICJsYWJlbENvbHVtbj1FbnJpY2htZW50TWFwOjpHU19ERVNDUiIsCiAgICAgICAgICAgICAgICAgICAgIm1heFdvcmRzPTMiKQpwcmludChhYV9jb21tYW5kKQpjb21tYW5kc0dFVChhYV9jb21tYW5kKQojIEFubm90YXRlIGEgc3VibmV0d29yawpjcmVhdGVTdWJuZXR3b3JrKGMoMTo0KSwiX19tY2xDbHVzdGVyIikKY29tbWFuZHNHRVQoYWFfY29tbWFuZCkKYGBgCgojIyBWaXN1YWxpemluZyBJbnRlZ3JhdGVkIE5ldHdvcmsgQW5hbHlzaXMgVXNpbmcgQmlvTmV0IApUaGUgW0Jpb05ldF0oaHR0cHM6Ly9iaW9jb25kdWN0b3Iub3JnL3BhY2thZ2VzL3JlbGVhc2UvYmlvYy9odG1sL0Jpb05ldC5odG1sKSBwYWNrYWdlIGltcGxlbWVudHMgYW5hbHl0aWNhbCBtZXRob2RzIHRvIHBlcmZvcm0gaW50ZWdyYXRlZCBuZXR3b3JrIGFuYWx5c2lzLCBlLmcuLCBvZiBnZW5lIGV4cHJlc3Npb24gZGF0YSBhbmQgY2xpbmljYWwgc3Vydml2YWwgZGF0YSBpbiB0aGUgY29udGV4dCBvZiBwcm90ZWluLXByb3RlaW4gaW50ZXJhY3Rpb24gbmV0d29ya3MuIFBhcnRuZXJlZCB3aXRoIFJDeTMsIHRoZSBhbmFseXRpY2FsIHJlc3VsdHMgZnJvbSBCaW9OZXQgY2FuIGJlIHZpc3VhbGl6ZWQgaW4gQ3l0b3NjYXBlIHdpdGggdmFzdGx5IG1vcmUgb3B0aW9ucyBmb3IgY3VzdG9taXphdGlvbi4gU3RhcnRpbmcgd2l0aCB0aGUgWyJRdWljayBTdGFydCIgdHV0b3JpYWxdKGh0dHBzOi8vYmlvY29uZHVjdG9yLm9yZy9wYWNrYWdlcy9yZWxlYXNlL2Jpb2MvdmlnbmV0dGVzL0Jpb05ldC9pbnN0L2RvYy9UdXRvcmlhbC5wZGYpIGZyb20gQmlvTmV0LCB3ZSBwYXNzIHRoZSByZXN1bHRzIGRpcmVjdGx5IHRvIEN5dG9zY2FwZSBmb3IgdmlzdWFsaXphdGlvbjoKCmBgYHtyfQpsaWJyYXJ5KEJpb05ldCkgIyBpbnN0YWxsIGZyb20gQmlvY29uZHVjdG9yCmxpYnJhcnkoRExCQ0wpICMgaW5zdGFsbCBmcm9tIEJpb2NvbmR1Y3RvcgpkYXRhKGRhdGFMeW0pCmRhdGEoaW50ZXJhY3RvbWUpCiMjIFRoZSBmb2xsb3dpbmcgc3RlcHMgYXJlIGZyb20gQmlvTmV0J3MgUXVpY2sgU3RhcnQgdHV0b3JpYWw6CnB2YWxzIDwtIGNiaW5kKHQgPSBkYXRhTHltJHQucHZhbCwgcyA9IGRhdGFMeW0kcy5wdmFsKQpyb3duYW1lcyhwdmFscykgPC0gZGF0YUx5bSRsYWJlbApwdmFsIDwtIGFnZ3JQdmFscyhwdmFscywgb3JkZXIgPSAyLCBwbG90ID0gRkFMU0UpCnN1Ym5ldCA8LSBzdWJOZXR3b3JrKGRhdGFMeW0kbGFiZWwsIGludGVyYWN0b21lKQpzdWJuZXQgPC0gcm1TZWxmTG9vcHMoc3VibmV0KQpmYiA8LSBmaXRCdW1Nb2RlbChwdmFsLCBwbG90ID0gRkFMU0UpCnNjb3JlcyA8LSBzY29yZU5vZGVzKHN1Ym5ldCwgZmIsIGZkciA9IDAuMDAxKQptb2R1bGUgPC0gcnVuRmFzdEhlaW56KHN1Ym5ldCwgc2NvcmVzKQpsb2dGQyA8LSBkYXRhTHltJGRpZmYKbmFtZXMobG9nRkMpIDwtIGRhdGFMeW0kbGFiZWwKcGxvdE1vZHVsZShtb2R1bGUsIHNjb3JlcyA9IHNjb3JlcywgZGlmZi5leHByID0gbG9nRkMpCgojIFVzaW5nIFJDeTMgd2UgY2FuIGdlbmVyYXRlIGEgY3VzdG9tIHZpc3VhbGl6YXRpb24gb2YgdGhlIHNhbWUgb3V0cHV0CiMjIENyZWF0ZSBuZXR3b3JrIGZyb20gZ3JhcGhORUwgb2JqZWN0IGFuZCBsb2FkIGRhdGEgY2FsY3VsYXRlZCBhYm92ZQpjcmVhdGVOZXR3b3JrRnJvbUdyYXBoKG1vZHVsZSwgIm1vZHVsZSIsICJCaW9OZXQiKQpsb2FkVGFibGVEYXRhKGFzLmRhdGEuZnJhbWUoc2NvcmVzKSkKbG9hZFRhYmxlRGF0YShhcy5kYXRhLmZyYW1lKGxvZ0ZDKSkKIyMgU2V0IHN0eWxlcwpzZXROb2RlTGFiZWxNYXBwaW5nKCJnZW5lU3ltYm9sIikKc2V0Tm9kZUZvbnRTaXplRGVmYXVsdCgxOCkKc2V0Tm9kZUJvcmRlcldpZHRoRGVmYXVsdCgzLjApCmxvZ0ZDLm1heC5hYnMgPC0gbWF4KGFicyhtaW4obG9nRkMpKSwgYWJzKG1heChsb2dGQykpKSAKc2V0Tm9kZUNvbG9yTWFwcGluZygnbG9nRkMnLCBjKC1sb2dGQy5tYXguYWJzLCAwLCBsb2dGQy5tYXguYWJzKSwgCiAgICAgICAgICAgICAgICAgICAgYygnIzU1NzdGRicsJyNGRkZGRkYnLCcjRkY3NzU1JyksIGRlZmF1bHQuY29sb3IgPSAnIzk5OTk5OScpCmNyZWF0ZUNvbHVtbkZpbHRlcigiUG9zaXRpdmUgc2NvcmVzIiwgInNjb3JlcyIsYygwLG1heChzY29yZXMpKSwiQkVUV0VFTiIpCnNldE5vZGVTaGFwZUJ5cGFzcyhnZXRTZWxlY3RlZE5vZGVzKCksICJFTExJUFNFIikKY2xlYXJTZWxlY3Rpb24oKQpgYGAKCiMjUGVyZm9ybWluZyBBZHZhbmNlZCBHcmFwaCBBbmFseXRpY3MgVXNpbmcgUkJHTApBcyBhbiBpbnRlcmZhY2UgdG8gdGhlIEJPT1NUIGxpYnJhcnksIHRoZSBbUkJHTF0oaHR0cHM6Ly9iaW9jb25kdWN0b3Iub3JnL3BhY2thZ2VzL3JlbGVhc2UvYmlvYy9odG1sL1JCR0wuaHRtbCkgQmlvY29uZHVjdG9yIHBhY2thZ2Ugb2ZmZXJzIGFuIGltcHJlc3NpdmUgYXJyYXkgb2YgYW5hbHl0aWNhbCBmdW5jdGlvbnMgZm9yIGdyYXBocy4gSGVyZSB3ZSB3aWxsIHN0YXJ0IHdpdGggYSBuZXR3b3JrIGluIEN5dG9zY2FwZSwgbG9hZCBpdCBpbnRvIFIgYXMgYSBncmFwaCBvYmplY3QsIHBlcmZvcm0gc2hvcnRlc3QgcGF0aCBjYWxjdWxhdGlvbiB1c2luZyBSQkdMIGFuZCB0aGVuIHZpc3VhbGl6ZSB0aGUgcmVzdWx0cyBiYWNrIGluIEN5dG9zY2FwZS4KYGBge3J9CmxpYnJhcnkoUkJHTCkgIyBpbnN0YWxsIGZyb20gQmlvY29uZHVjdG9yCiMgQ29udmVydCBhIHNhbXBsZSBDeXRvc2NhcGUgbmV0d29yayB0byBhIGdyYXBoIG9iamVjdApvcGVuU2Vzc2lvbigpCmcgPC0gY3JlYXRlR3JhcGhGcm9tTmV0d29yaygpCiMgSWRlbnRpZnkgc3RhcnQgYW5kIGZpbmlzaCBub2RlcyAoc3R5bGluZyBpcyBvcHRpb25hbCkKc3RhcnQgPC0gIllOTDIxNlciCmZpbmlzaCA8LSAiWUVSMDQwVyIKc2V0Tm9kZUJvcmRlcldpZHRoQnlwYXNzKGMoc3RhcnQsIGZpbmlzaCksIDIwKQpzZXROb2RlQm9yZGVyQ29sb3JCeXBhc3Moc3RhcnQsICIjMDBDQzMzIikKc2V0Tm9kZUJvcmRlckNvbG9yQnlwYXNzKGZpbmlzaCwgIiNDQzAwQ0MiKQojIFVzZSBSQkdMIHRvIHBlcmZvcm0gc2hvcnRlc3QgcGF0aCBjYWxjdWxhdGlvbgpzaG9ydGVzdCA8LSBzcC5iZXR3ZWVuKGcsIHN0YXJ0LCBmaW5pc2gpCnNob3J0ZXN0JGBZTkwyMTZXOllFUjA0MFdgJGxlbmd0aAojWzFdIDYKc2hvcnRlc3QucGF0aCA8LSBzaG9ydGVzdCRgWU5MMjE2VzpZRVIwNDBXYCRwYXRoX2RldGFpbAojIFZpc3VhbGl6ZSByZXN1bHRzIGluIEN5dG9zY2FwZQpzZWxlY3ROb2RlcyhzaG9ydGVzdC5wYXRoLCAibmFtZSIpCnNldE5vZGVCb3JkZXJXaWR0aEJ5cGFzcyhzaG9ydGVzdC5wYXRoLCAyMCkKY3JlYXRlU3VibmV0d29yaygpCmBgYAoK