The R markdown is available from the pulldown menu for Code at the upper-right, choose “Download Rmd”, or download the Rmd from GitHub.


In these exercises, we will use the stringApp for Cytoscape to retrieve molecular networks from the STRING and STITCH databases. The exercises will teach you how to:

  • retrieve networks for proteins or small-molecule compounds of interest
  • retrieve networks for a disease or arbitrary topics in PubMed
  • layout and visually style the resulting networks
  • import external data and map them onto a network
  • perform enrichment analyses and visualize the results
  • merge and compare networks
  • select proteins by attributes
  • identify functional modules through network clustering

The original version of this tutorial was developed by Lars Juhl Jensen of the Novo Nordisk Center for Protein Research at the University of Copenhagen. We thank professor Jensen for his gracious willingness to allow us to repackage the content for delivery as a Cytoscape tutorial.


Installation

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

if(!"RCy3" %in% installed.packages())
  BiocManager::install("RCy3")
library(RCy3)

if(!"RColorBrewer" %in% installed.packages()){
    install.packages("RColorBrewer")
}
library(RColorBrewer)

Getting started

First, launch Cytoscape and keep it running whenever using RCy3. Confirm that you have everything installed and running:

cytoscapePing()
cytoscapeVersionInfo()

The exercises require you to have certain Cytoscape apps and R packages installed.

installApp('stringApp')
installApp('enhancedGraphics')
installApp('clusterMaker2')

If you are not already familiar with the STRING database, we highly recommend that you go through the short STRING exercises provided by the Jensen lab to learn about the underlying data before working with them in these exercises.

Exercise 1

In this exercise, we will perform some simple queries to retrieve molecular networks based on a protein, a small molecule compound, a disease, and a topic in PubMed.

Protein queries

You can query STRING as protein data source (in the following query the protein name is SORCS2). You can select the appropriate organism by setting the species option (e.g. Homo sapiens). (And the following chunk also imports the result into Cytoscape.)

string.cmd = 'string protein query query="SORCS2" species="Homo sapiens"'
commandsRun(string.cmd)

The limit (Maximum number of interactors) option determines how many interaction partners of your protein(s) of interest will be added to the network. By default, if you enter only one protein name, the resulting network will contain 10 additional interactors. If you enter more than one protein name, the network will contain only the interactions among these proteins, unless you explicitly ask for additional proteins.

string.cmd = 'string protein query query="SORCS2" species="Homo sapiens" limit=100'
commandsRun(string.cmd)

How many nodes are in the resulting network? How does this compare to the maximum number of interactors you specified? What types of information does the Node Table provide?

Compound queries

You can query STITCH as protein/compound data source (in the following query the compound name is imatinib). You can select the organism and number of additional interactors just like for the protein query above.

string.cmd = 'string compound query query="imatinib" species="Homo sapiens"'
commandsRun(string.cmd)

How is this network different from the protein-only network with respect to node types and the information provided in the Node Table?

Disease queries

You can query STRING as disease data source (in the following query the disease term is Alzheimer). The stringApp will retrieve a STRING network for the top-N proteins (by default 100) associated with the disease.

string.cmd = 'string disease query disease="Alzheimer"'
commandsRun(string.cmd)

Which additional attribute column do you get in the Node Table for a disease query compared to a protein query? (Hint: check the last column.)

PubMed queries

You can query STRING as PubMed data source (in the following query the query representing a topic of interest is jet-lag). You can use any query that would work on the PubMed website, but it should obviously a topic with related genes or proteins. The stringApp will query PubMed for the abstracts, find the top-N proteins (by default 100) associated with these abstracts, and retrieve a STRING network for them.

string.cmd = 'string pubmed query pubmed="jet-lag"'
commandsRun(string.cmd)

Which attribute column do you get in the Node Table for a PubMed query compared to a disease query? (Hint: check the last columns.)

Exercise 2

In this exercise, we are going to use the stringApp to query the DISEASES database for proteins associated with ovarian cancer, retrieve a STRING network for them, and explore the resulting network.

Close the current session in Cytoscape:

closeSession(save.before.closing=FALSE)

Retrieve disease network

First, we will run a disease query for ovarian cancer:

string.cmd = 'string disease query disease="ovarian cancer" limit=250'
commandsRun(string.cmd)

The retrieved network contains a lot of additional information associated with the nodes and edges, such as the protein sequence, tissue expression data, subcellular localization, disease score (Node Table) as well as the confidence scores for the different interaction evidences (Edge Table). In the next few steps, we will explore these data.

Create a dataframe containing the disease score and sort it descending by values to see the highest disease scores:

disease.score <- getTableColumns('node', "stringdb::disease score")
disease.score.sorted <- disease.score[order(-disease.score$`stringdb::disease score`),,drop=FALSE]
head(disease.score.sorted)

You can highlight the top nodes by selecting the corresponding rows in the table. Here we are selecting the top few nodes based on disease score:

selectNodes(rownames(head(disease.score.sorted)))

Continuous color mapping

The stringApp automatically retrieves information about which compartments the proteins are located from the COMPARTMENTS database. Cytoscape allows you to map attributes of the nodes and edges to visual properties such as node color and edge width. Here, we will map the subcellular localization data for nucleus to the node color.

First, let’s remove the String style:

deleteStyleMapping(style.name = 'STRING style v1.5 - ovarian cancer', visual.prop = 'NODE_CUSTOMGRAPHICS_1')

For each cellular compartment, a score is defined for each node. Next, let’s define a dataframe corresponding to the score for nucleus:

nucleus.nodes <- getTableColumns('node', 'compartment::nucleus')

Define the bounds of the values in the compartment::nucleus column:

nucleus.min <- min(nucleus.nodes, na.rm = T)
nucleus.max <- max(nucleus.nodes, na.rm = T)
nucleus.center <- nucleus.min + (nucleus.max - nucleus.min)/2
data.values = c(nucleus.min, nucleus.center, nucleus.max)
node.colors <- c(brewer.pal(length(data.values), "YlOrRd"))
setNodeColorMapping('compartment::nucleus', data.values, node.colors, style.name = "STRING style v1.5 - ovarian cancer")

Because many proteins are located in the nucleus, we will identify the proteins with highest confidence of 5 and create a subnetwork.

top.nodes <- row.names(nucleus.nodes)[which(nucleus.nodes[,1]>=5)]
createSubnetwork(top.nodes,subnetwork.name ='nucleus score 5')

Exercise 3

In this exercise, we will work with a list of 541 proteins associated with epithelial ovarian cancer (EOC) as identified by phosphoproteomics in the study by Francavilla et al. An adapted table with the data from this study is available here.

Protein network retrieval

Here, we run a query with the first column (UniProt IDs) in the table:

eoc.df <- read.table("https://raw.githubusercontent.com/cytoscape/cytoscape-automation/master/for-scripters/R/notebooks/stringApp/Francavilla2017CellRep.tsv", header = TRUE, sep = "\t", quote="\"", stringsAsFactors = FALSE, check.names = FALSE)
string.cmd = paste('string protein query query="', paste(eoc.df$UniProt, collapse = '\n'), '"', sep = "")
commandsRun(string.cmd)

How many nodes and edges are there in the resulting network? Do the proteins all form a connected network? Why?

Since we will need to refer to this network later, let’s give it a more specific name:

renameNetwork("EOC - data")

Cytoscape provides several network layout options. For example, you can try the Degree Sorted Circle Layout

layoutNetwork('degree-circle')

and the Prefuse Force Directed Layout with score as edge weight

layoutNetwork('force-directed edgeAttribute="score"')

Can you find a layout that allows you to easily recognize patterns in the network? Try the Edge-weighted Spring Embedded (Kamada-Kawai) Layout with the attribute ‘score’, which is the combined STRING interaction score.

layoutNetwork('kamada-kawai edgeAttribute="score"')

Note that yFiles Layout Algorithms App does not support any automation.

Discrete color mapping

Cytoscape allows you to map attributes of the nodes and edges to visual properties such as node color and edge width. Here, we will map the target family data to the node color.

Lets change the node Fill Color with target family column in the node table. We can also remove the String styling for nodes to better see our data:

deleteStyleMapping(style.name = 'STRING style v1.5', visual.prop = 'NODE_CUSTOMGRAPHICS_1')
column <- "target::family"
values <- c('Kinase', 'GPCR')
colors <- c('#FF0000', '#0000FF')
setNodeColorDefault('#CCCCCC', style.name = "STRING style v1.5")
setNodeColorMapping(column, values, colors, mapping.type = "d", style.name = "STRING style v1.5")

How many of the proteins in the network are kinases?

Note that the retrieved network contains a lot of additional information associated with the nodes and edges, such as the protein sequence, tissue expression data (Node Table) as well as the confidence scores for the different interaction evidences (Edge Table). In the following steps, we will explore these data using Cytoscape.

Data import

Network nodes and edges can have additional information associated with them that we can load into Cytoscape and use for visualization. We already imported the data from an Excel spreadsheet derived from data provided in the paper mentioned above. Here we check it again with:

head(eoc.df)

Now we need to map unique identifiers between the entries in the data and the nodes in the network. The key point of this is to identify which nodes in the network are equivalent to which entries in the table. This enables mapping of data values into visual properties like Fill Color and Shape. This kind of mapping is typically done by comparing the unique Identifier attribute value for each node (Key Column for Network) with the unique Identifier value for each data value (key symbol).

The Key Column for Network allows you to set the node attribute column that is to be used as key to map to. In this case it is query term because this attribute contains the UniProt accession numbers you entered when retrieving the network.

To import the node attributes file into Cytoscape, run

loadTableData(as.data.frame(eoc.df), data.key.column = "UniProt", table = "node", table.key.column = "query term")

If there is a match between the value of a Key in the dataset and the value the Key Column for Network field in the network, all attribute–value pairs associated with the element in the dataset are assigned to the matching node in the network. You will find the imported columns at the end of the Node Table.

Continuous color mapping

Now, we want to color the nodes according to the quantitative phosphorylation data (log ratio) between disease and healthy tissues for the most significant site for each protein:

logratio.score.table <- getTableColumns('node', "EOC vs FTE&OSE")

Since this is a numeric value, we will use the Continuous Mapping as the Mapping Type, and set a color gradient for how abundant each protein is.

logratio.min <- min(logratio.score.table, na.rm = T)
logratio.max <- max(logratio.score.table, na.rm = T)
logratio.data.values = c(logratio.min, 0, logratio.max)
logratio.node.colors <- c(brewer.pal(length(logratio.data.values), "RdBu"))
setNodeColorMapping('EOC vs FTE&OSE', logratio.data.values, logratio.node.colors, style.name = "STRING style v1.5")

The color gradient blue-white-red gives a visualization of the negative-to-positive abundance ratio.

Are the up-regulated nodes grouped together?

Functional enrichment

Next, we will retrieve functional enrichment for the proteins in our network.

The STRING app has built-in enrichment analysis functionality, which includes enrichment for GO Process, GO Component, GO Function, InterPro, KEGG Pathways, and PFAM.

First, we will run the enrichment on the whole network, against the genome:

string.cmd = 'string retrieve enrichment allNetSpecies="Homo sapiens", background=genome  selectedNodesOnly="false"'
commandsRun(string.cmd)
string.cmd = 'string show enrichment'
commandsRun(string.cmd)

A new STRING Enrichment tab will appear in the Table Panel on the bottom. It contains a table of enriched terms and corresponding information for each enrichment category.

Which are the three most statistically significant terms?

To explore only specific types of terms, e.g. GO terms, and to remove redundant terms from the table, we are going to filter the table to only show GO Process:

string.cmd = 'string filter enrichment categories="GO Process", overlapCutoff = "0.5", removeOverlapping = "true"'
commandsRun(string.cmd)

In this way, you will see only the statistically significant GO terms that do not represent largely the same set of proteins within the network.

Do the functional terms assigned to a protein correlate with whether it is up- or down-regulated?

Next, we will visualize the top-3 enriched terms in the network by using split charts.

string.cmd = 'string show charts'
commandsRun(string.cmd)

Do these terms give good coverage of the proteins in network?

Finally, you can save the list of enriched terms and associated p-values as a text file locally. Note that this will export a cvs file to your current working directory.

commandsPOST(paste0('table export table="STRING Enrichment: All" options="CSV" outputFile="',paste(getwd(),"string-enrichment-all.csv",sep = "/"),'"'))

Network overlap

Cytoscape provides functionality to merge two or more networks, building either their union, intersection or difference. We will now merge the “ovarian cancner” network we have from the DISEASES query with the one we have from the data, so that we can identify the overlap between them.

mergeNetworks(c("String Network", "String Network - ovarian cancer"), "Merged Network", operation="intersection")

How many nodes are in the intersection?

Now we will make the union of the intersection network, which contains the disease scores, and the experimental network. Make sure that the new merged network has the same number of nodes and edges as ‘String Network’, and that some nodes have a disease score.

mergeNetworks(c("Merged Network", "String Network"), "Union")

Now, we can change the visualization of the merged network to be able to identify high disease score proteins. Specifically, we will change the size and color of the nodes to reflect their disease score.

disease.score.table <- getTableColumns('node', "stringdb::disease score")

min <- min(disease.score.table, na.rm = T)
max <- max(disease.score.table, na.rm = T)
mid <- min + (max - min)/2

node.data.values = c(min, max)
node.sizes = c(35,50)
setNodeSizeMapping("stringdb::disease score", node.data.values, node.sizes, default.size="30", style.name = "default")

color.data.values = c(min, mid, max)
node.colors <- c(brewer.pal(length(color.data.values), "Blues"))
setNodeColorMapping("stringdb::disease score", color.data.values, node.colors, style.name = "default")

Let’s focus on only the connected nodes:

createSubnetwork(edges='all', subnetwork.name='Union sub')
LS0tCnRpdGxlOiAiQ3l0b3NjYXBlIFN0cmluZ0FwcCIKYXV0aG9yOiAiS296byBOaXNoaWRhLCBLcmlzdGluYSBIYW5zcGVycyBhbmQgQWxleCBQaWNvIgpkYXRlOiAiYHIgU3lzLkRhdGUoKWAiCm91dHB1dDoKICBCaW9jU3R5bGU6Omh0bWxfZG9jdW1lbnQ6CiAgICB0b2M6IHRydWUKICAgIHRvY19kZXB0aDogNAogICAgZGZfcHJpbnQ6IHBhZ2VkCi0tLQpgYGB7ciwgZWNobyA9IEZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoCiAgZXZhbD1GQUxTRQopCmBgYAoKKlRoZSBSIG1hcmtkb3duIGlzIGF2YWlsYWJsZSBmcm9tIHRoZSBwdWxsZG93biBtZW51IGZvciogQ29kZSAqYXQgdGhlIHVwcGVyLXJpZ2h0LCBjaG9vc2UgIkRvd25sb2FkIFJtZCIsIG9yIFtkb3dubG9hZCB0aGUgUm1kIGZyb20gR2l0SHViXShodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vY3l0b3NjYXBlL2N5dG9zY2FwZS1hdXRvbWF0aW9uL21hc3Rlci9mb3Itc2NyaXB0ZXJzL1Ivbm90ZWJvb2tzL3N0cmluZ0FwcC5SbWQpLioKCjxociAvPgoKSW4gdGhlc2UgZXhlcmNpc2VzLCB3ZSB3aWxsIHVzZSB0aGUgW3N0cmluZ0FwcF0oaHR0cDovL2FwcHMuY3l0b3NjYXBlLm9yZy9hcHBzL3N0cmluZ0FwcCkgZm9yIFtDeXRvc2NhcGVdKGh0dHA6Ly9jeXRvc2NhcGUub3JnLykgdG8gcmV0cmlldmUgbW9sZWN1bGFyIG5ldHdvcmtzIGZyb20gdGhlIFtTVFJJTkddKGh0dHBzOi8vc3RyaW5nLWRiLm9yZy8pIGFuZCBbU1RJVENIXShodHRwOi8vc3RpdGNoLWRiLm9yZy8pIGRhdGFiYXNlcy4gVGhlIGV4ZXJjaXNlcyB3aWxsIHRlYWNoIHlvdSBob3cgdG86CgotIHJldHJpZXZlIG5ldHdvcmtzIGZvciBwcm90ZWlucyBvciBzbWFsbC1tb2xlY3VsZSBjb21wb3VuZHMgb2YgaW50ZXJlc3QKLSByZXRyaWV2ZSBuZXR3b3JrcyBmb3IgYSBkaXNlYXNlIG9yIGFyYml0cmFyeSB0b3BpY3MgaW4gUHViTWVkCi0gbGF5b3V0IGFuZCB2aXN1YWxseSBzdHlsZSB0aGUgcmVzdWx0aW5nIG5ldHdvcmtzCi0gaW1wb3J0IGV4dGVybmFsIGRhdGEgYW5kIG1hcCB0aGVtIG9udG8gYSBuZXR3b3JrCi0gcGVyZm9ybSBlbnJpY2htZW50IGFuYWx5c2VzIGFuZCB2aXN1YWxpemUgdGhlIHJlc3VsdHMKLSBtZXJnZSBhbmQgY29tcGFyZSBuZXR3b3JrcwotIHNlbGVjdCBwcm90ZWlucyBieSBhdHRyaWJ1dGVzCi0gaWRlbnRpZnkgZnVuY3Rpb25hbCBtb2R1bGVzIHRocm91Z2ggbmV0d29yayBjbHVzdGVyaW5nCgpUaGUgb3JpZ2luYWwgdmVyc2lvbiBvZiB0aGlzIHR1dG9yaWFsIHdhcyBkZXZlbG9wZWQgYnkgTGFycyBKdWhsIEplbnNlbiBvZiB0aGUgTm92byBOb3JkaXNrIENlbnRlciBmb3IgUHJvdGVpbiBSZXNlYXJjaCBhdCB0aGUgVW5pdmVyc2l0eSBvZiBDb3BlbmhhZ2VuLiBXZSB0aGFuayBwcm9mZXNzb3IgSmVuc2VuIGZvciBoaXMgZ3JhY2lvdXMgd2lsbGluZ25lc3MgdG8gYWxsb3cgdXMgdG8gcmVwYWNrYWdlIHRoZSBjb250ZW50IGZvciBkZWxpdmVyeSBhcyBhIEN5dG9zY2FwZSB0dXRvcmlhbC4KCjxociAvPgoKIyBJbnN0YWxsYXRpb24KYGBge3IsIGV2YWwgPSBGQUxTRX0KaWYgKCFyZXF1aXJlTmFtZXNwYWNlKCJCaW9jTWFuYWdlciIsIHF1aWV0bHkgPSBUUlVFKSkKICBpbnN0YWxsLnBhY2thZ2VzKCJCaW9jTWFuYWdlciIpCgppZighIlJDeTMiICVpbiUgaW5zdGFsbGVkLnBhY2thZ2VzKCkpCiAgQmlvY01hbmFnZXI6Omluc3RhbGwoIlJDeTMiKQpsaWJyYXJ5KFJDeTMpCgppZighIlJDb2xvckJyZXdlciIgJWluJSBpbnN0YWxsZWQucGFja2FnZXMoKSl7CiAgICBpbnN0YWxsLnBhY2thZ2VzKCJSQ29sb3JCcmV3ZXIiKQp9CmxpYnJhcnkoUkNvbG9yQnJld2VyKQpgYGAKCiMgR2V0dGluZyBzdGFydGVkCkZpcnN0LCBsYXVuY2ggQ3l0b3NjYXBlIGFuZCBrZWVwIGl0IHJ1bm5pbmcgd2hlbmV2ZXIgdXNpbmcgUkN5My4gQ29uZmlybSB0aGF0IHlvdSBoYXZlIGV2ZXJ5dGhpbmcgaW5zdGFsbGVkIGFuZCBydW5uaW5nOgpgYGB7cn0KY3l0b3NjYXBlUGluZygpCmN5dG9zY2FwZVZlcnNpb25JbmZvKCkKYGBgCgpUaGUgZXhlcmNpc2VzIHJlcXVpcmUgeW91IHRvIGhhdmUgY2VydGFpbiBDeXRvc2NhcGUgYXBwcyBhbmQgUiBwYWNrYWdlcyBpbnN0YWxsZWQuCgpgYGB7cn0KaW5zdGFsbEFwcCgnc3RyaW5nQXBwJykKaW5zdGFsbEFwcCgnZW5oYW5jZWRHcmFwaGljcycpCmluc3RhbGxBcHAoJ2NsdXN0ZXJNYWtlcjInKQpgYGAKCklmIHlvdSBhcmUgbm90IGFscmVhZHkgZmFtaWxpYXIgd2l0aCB0aGUgU1RSSU5HIGRhdGFiYXNlLCB3ZSBoaWdobHkgcmVjb21tZW5kIHRoYXQgeW91IGdvIHRocm91Z2ggdGhlIHNob3J0IFtTVFJJTkcgZXhlcmNpc2VzXShodHRwczovL2plbnNlbmxhYi5vcmcvdHJhaW5pbmcvc3RyaW5nLykgcHJvdmlkZWQgYnkgdGhlIFtKZW5zZW4gbGFiXShodHRwczovL2plbnNlbmxhYi5vcmcvKSB0byBsZWFybiBhYm91dCB0aGUgdW5kZXJseWluZyBkYXRhIGJlZm9yZSB3b3JraW5nIHdpdGggdGhlbSBpbiB0aGVzZSBleGVyY2lzZXMuCgojIEV4ZXJjaXNlIDEKCkluIHRoaXMgZXhlcmNpc2UsIHdlIHdpbGwgcGVyZm9ybSBzb21lIHNpbXBsZSBxdWVyaWVzIHRvIHJldHJpZXZlIG1vbGVjdWxhciBuZXR3b3JrcyBiYXNlZCBvbiBhIHByb3RlaW4sIGEgc21hbGwgbW9sZWN1bGUgY29tcG91bmQsIGEgZGlzZWFzZSwgYW5kIGEgdG9waWMgaW4gUHViTWVkLgoKIyMgUHJvdGVpbiBxdWVyaWVzCgpZb3UgY2FuIHF1ZXJ5ICoqU1RSSU5HKiogYXMgcHJvdGVpbiBkYXRhIHNvdXJjZSAoaW4gdGhlIGZvbGxvd2luZyBxdWVyeSB0aGUgKipwcm90ZWluIG5hbWUqKiBpcyAqKlNPUkNTMioqKS4KWW91IGNhbiBzZWxlY3QgdGhlIGFwcHJvcHJpYXRlIG9yZ2FuaXNtIGJ5IHNldHRpbmcgdGhlICoqc3BlY2llcyoqIG9wdGlvbiAoZS5nLiAqKkhvbW8gc2FwaWVucyoqKS4KKEFuZCB0aGUgZm9sbG93aW5nIGNodW5rIGFsc28gaW1wb3J0cyB0aGUgcmVzdWx0IGludG8gQ3l0b3NjYXBlLikKCmBgYHtyfQpzdHJpbmcuY21kID0gJ3N0cmluZyBwcm90ZWluIHF1ZXJ5IHF1ZXJ5PSJTT1JDUzIiIHNwZWNpZXM9IkhvbW8gc2FwaWVucyInCmNvbW1hbmRzUnVuKHN0cmluZy5jbWQpCmBgYAoKVGhlIGxpbWl0IChNYXhpbXVtIG51bWJlciBvZiBpbnRlcmFjdG9ycykgb3B0aW9uIGRldGVybWluZXMgaG93IG1hbnkgaW50ZXJhY3Rpb24gcGFydG5lcnMgb2YgeW91ciBwcm90ZWluKHMpIG9mIGludGVyZXN0IHdpbGwgYmUgYWRkZWQgdG8gdGhlIG5ldHdvcmsuCkJ5IGRlZmF1bHQsIGlmIHlvdSBlbnRlciBvbmx5IG9uZSBwcm90ZWluIG5hbWUsIHRoZSByZXN1bHRpbmcgbmV0d29yayB3aWxsIGNvbnRhaW4gMTAgYWRkaXRpb25hbCBpbnRlcmFjdG9ycy4gSWYgeW91IGVudGVyIG1vcmUgdGhhbiBvbmUgcHJvdGVpbiBuYW1lLCB0aGUgbmV0d29yayB3aWxsIGNvbnRhaW4gb25seSB0aGUgaW50ZXJhY3Rpb25zIGFtb25nIHRoZXNlIHByb3RlaW5zLCB1bmxlc3MgeW91IGV4cGxpY2l0bHkgYXNrIGZvciBhZGRpdGlvbmFsIHByb3RlaW5zLgoKYGBge3J9CnN0cmluZy5jbWQgPSAnc3RyaW5nIHByb3RlaW4gcXVlcnkgcXVlcnk9IlNPUkNTMiIgc3BlY2llcz0iSG9tbyBzYXBpZW5zIiBsaW1pdD0xMDAnCmNvbW1hbmRzUnVuKHN0cmluZy5jbWQpCmBgYAoKPGNlbnRlcj4KIVtdKGh0dHBzOi8vY3l0b3NjYXBlLmdpdGh1Yi5pby9jeXRvc2NhcGUtYXV0b21hdGlvbi9mb3Itc2NyaXB0ZXJzL1Ivbm90ZWJvb2tzL2RhdGEvaW1nL3N0cmluZ0FwcF9TT1JDUzIucG5nKXt3aWR0aD02MCV9CjwvY2VudGVyPgoKSG93IG1hbnkgbm9kZXMgYXJlIGluIHRoZSByZXN1bHRpbmcgbmV0d29yaz8KSG93IGRvZXMgdGhpcyBjb21wYXJlIHRvIHRoZSBtYXhpbXVtIG51bWJlciBvZiBpbnRlcmFjdG9ycyB5b3Ugc3BlY2lmaWVkPwpXaGF0IHR5cGVzIG9mIGluZm9ybWF0aW9uIGRvZXMgdGhlICoqTm9kZSBUYWJsZSoqIHByb3ZpZGU/CgojIyBDb21wb3VuZCBxdWVyaWVzCgpZb3UgY2FuIHF1ZXJ5ICoqU1RJVENIKiogYXMgcHJvdGVpbi9jb21wb3VuZCBkYXRhIHNvdXJjZSAoaW4gdGhlIGZvbGxvd2luZyBxdWVyeSB0aGUgKipjb21wb3VuZCBuYW1lKiogaXMgKippbWF0aW5pYioqKS4KWW91IGNhbiBzZWxlY3QgdGhlIG9yZ2FuaXNtIGFuZCBudW1iZXIgb2YgYWRkaXRpb25hbCBpbnRlcmFjdG9ycyBqdXN0IGxpa2UgZm9yIHRoZSBwcm90ZWluIHF1ZXJ5IGFib3ZlLgoKYGBge3J9CnN0cmluZy5jbWQgPSAnc3RyaW5nIGNvbXBvdW5kIHF1ZXJ5IHF1ZXJ5PSJpbWF0aW5pYiIgc3BlY2llcz0iSG9tbyBzYXBpZW5zIicKY29tbWFuZHNSdW4oc3RyaW5nLmNtZCkKYGBgCgo8Y2VudGVyPgohW10oaHR0cHM6Ly9jeXRvc2NhcGUuZ2l0aHViLmlvL2N5dG9zY2FwZS1hdXRvbWF0aW9uL2Zvci1zY3JpcHRlcnMvUi9ub3RlYm9va3MvZGF0YS9pbWcvc3RyaW5nQXBwX2ltYXRpbmliLnBuZyl7d2lkdGg9NjAlfQo8L2NlbnRlcj4KCkhvdyBpcyB0aGlzIG5ldHdvcmsgZGlmZmVyZW50IGZyb20gdGhlIHByb3RlaW4tb25seSBuZXR3b3JrIHdpdGggcmVzcGVjdCB0byBub2RlIHR5cGVzIGFuZCB0aGUgaW5mb3JtYXRpb24gcHJvdmlkZWQgaW4gdGhlIE5vZGUgVGFibGU/CgojIyBEaXNlYXNlIHF1ZXJpZXMKCllvdSBjYW4gcXVlcnkgKipTVFJJTkcqKiBhcyBkaXNlYXNlIGRhdGEgc291cmNlIChpbiB0aGUgZm9sbG93aW5nIHF1ZXJ5IHRoZSAqKmRpc2Vhc2UgdGVybSoqIGlzICoqQWx6aGVpbWVyKiopLgpUaGUgc3RyaW5nQXBwIHdpbGwgcmV0cmlldmUgYSBTVFJJTkcgbmV0d29yayBmb3IgdGhlIHRvcC1OIHByb3RlaW5zIChieSBkZWZhdWx0IDEwMCkgYXNzb2NpYXRlZCB3aXRoIHRoZSBkaXNlYXNlLgoKYGBge3J9CnN0cmluZy5jbWQgPSAnc3RyaW5nIGRpc2Vhc2UgcXVlcnkgZGlzZWFzZT0iQWx6aGVpbWVyIicKY29tbWFuZHNSdW4oc3RyaW5nLmNtZCkKYGBgCgo8Y2VudGVyPgohW10oaHR0cHM6Ly9jeXRvc2NhcGUuZ2l0aHViLmlvL2N5dG9zY2FwZS1hdXRvbWF0aW9uL2Zvci1zY3JpcHRlcnMvUi9ub3RlYm9va3MvZGF0YS9pbWcvc3RyaW5nQXBwX2FsemhlaW1lci5wbmcpe3dpZHRoPTYwJX0KPC9jZW50ZXI+CgpXaGljaCBhZGRpdGlvbmFsIGF0dHJpYnV0ZSBjb2x1bW4gZG8geW91IGdldCBpbiB0aGUgTm9kZSBUYWJsZSBmb3IgYSBkaXNlYXNlIHF1ZXJ5IGNvbXBhcmVkIHRvIGEgcHJvdGVpbiBxdWVyeT8gKEhpbnQ6IGNoZWNrIHRoZSBsYXN0IGNvbHVtbi4pCgojIyBQdWJNZWQgcXVlcmllcwoKWW91IGNhbiBxdWVyeSAqKlNUUklORyoqIGFzICoqUHViTWVkKiogZGF0YSBzb3VyY2UgKGluIHRoZSBmb2xsb3dpbmcgcXVlcnkgdGhlIHF1ZXJ5IHJlcHJlc2VudGluZyBhIHRvcGljIG9mIGludGVyZXN0IGlzICoqamV0LWxhZyoqKS4KWW91IGNhbiB1c2UgYW55IHF1ZXJ5IHRoYXQgd291bGQgd29yayBvbiB0aGUgUHViTWVkIHdlYnNpdGUsIGJ1dCBpdCBzaG91bGQgb2J2aW91c2x5IGEgdG9waWMgd2l0aCByZWxhdGVkIGdlbmVzIG9yIHByb3RlaW5zLiBUaGUgc3RyaW5nQXBwIHdpbGwgcXVlcnkgUHViTWVkIGZvciB0aGUgYWJzdHJhY3RzLCBmaW5kIHRoZSB0b3AtTiBwcm90ZWlucyAoYnkgZGVmYXVsdCAxMDApIGFzc29jaWF0ZWQgd2l0aCB0aGVzZSBhYnN0cmFjdHMsIGFuZCByZXRyaWV2ZSBhIFNUUklORyBuZXR3b3JrIGZvciB0aGVtLgoKYGBge3J9CnN0cmluZy5jbWQgPSAnc3RyaW5nIHB1Ym1lZCBxdWVyeSBwdWJtZWQ9ImpldC1sYWciJwpjb21tYW5kc1J1bihzdHJpbmcuY21kKQpgYGAKPGNlbnRlcj4KIVtdKGh0dHBzOi8vY3l0b3NjYXBlLmdpdGh1Yi5pby9jeXRvc2NhcGUtYXV0b21hdGlvbi9mb3Itc2NyaXB0ZXJzL1Ivbm90ZWJvb2tzL2RhdGEvaW1nL3N0cmluZ0FwcF9qZXRsYWcucG5nKXt3aWR0aD02MCV9CjwvY2VudGVyPgoKV2hpY2ggYXR0cmlidXRlIGNvbHVtbiBkbyB5b3UgZ2V0IGluIHRoZSBOb2RlIFRhYmxlIGZvciBhIFB1Yk1lZCBxdWVyeSBjb21wYXJlZCB0byBhIGRpc2Vhc2UgcXVlcnk/IChIaW50OiBjaGVjayB0aGUgbGFzdCBjb2x1bW5zLikKCiMgRXhlcmNpc2UgMgpJbiB0aGlzIGV4ZXJjaXNlLCB3ZSBhcmUgZ29pbmcgdG8gdXNlIHRoZSBzdHJpbmdBcHAgdG8gcXVlcnkgdGhlIERJU0VBU0VTIGRhdGFiYXNlIGZvciBwcm90ZWlucyBhc3NvY2lhdGVkIHdpdGggb3ZhcmlhbiBjYW5jZXIsIHJldHJpZXZlIGEgU1RSSU5HIG5ldHdvcmsgZm9yIHRoZW0sIGFuZCBleHBsb3JlIHRoZSByZXN1bHRpbmcgbmV0d29yay4KCkNsb3NlIHRoZSBjdXJyZW50IHNlc3Npb24gaW4gQ3l0b3NjYXBlOgpgYGB7cn0KY2xvc2VTZXNzaW9uKHNhdmUuYmVmb3JlLmNsb3Npbmc9RkFMU0UpCmBgYAoKIyMgUmV0cmlldmUgZGlzZWFzZSBuZXR3b3JrCkZpcnN0LCB3ZSB3aWxsIHJ1biBhIGRpc2Vhc2UgcXVlcnkgZm9yICpvdmFyaWFuIGNhbmNlcio6CgpgYGB7cn0Kc3RyaW5nLmNtZCA9ICdzdHJpbmcgZGlzZWFzZSBxdWVyeSBkaXNlYXNlPSJvdmFyaWFuIGNhbmNlciIgbGltaXQ9MjUwJwpjb21tYW5kc1J1bihzdHJpbmcuY21kKQpgYGAKClRoZSByZXRyaWV2ZWQgbmV0d29yayBjb250YWlucyBhIGxvdCBvZiBhZGRpdGlvbmFsIGluZm9ybWF0aW9uIGFzc29jaWF0ZWQgd2l0aCB0aGUgbm9kZXMgYW5kIGVkZ2VzLCBzdWNoIGFzIHRoZSBwcm90ZWluIHNlcXVlbmNlLCB0aXNzdWUgZXhwcmVzc2lvbiBkYXRhLCBzdWJjZWxsdWxhciBsb2NhbGl6YXRpb24sIGRpc2Vhc2Ugc2NvcmUgKE5vZGUgVGFibGUpIGFzIHdlbGwgYXMgdGhlIGNvbmZpZGVuY2Ugc2NvcmVzIGZvciB0aGUgZGlmZmVyZW50IGludGVyYWN0aW9uIGV2aWRlbmNlcyAoRWRnZSBUYWJsZSkuIEluIHRoZSBuZXh0IGZldyBzdGVwcywgd2Ugd2lsbCBleHBsb3JlIHRoZXNlIGRhdGEuCgpDcmVhdGUgYSBkYXRhZnJhbWUgY29udGFpbmluZyB0aGUgKmRpc2Vhc2Ugc2NvcmUqIGFuZCBzb3J0IGl0IGRlc2NlbmRpbmcgYnkgdmFsdWVzIHRvIHNlZSB0aGUgaGlnaGVzdCBkaXNlYXNlIHNjb3JlczoKCmBgYHtyfQpkaXNlYXNlLnNjb3JlIDwtIGdldFRhYmxlQ29sdW1ucygnbm9kZScsICJzdHJpbmdkYjo6ZGlzZWFzZSBzY29yZSIpCmRpc2Vhc2Uuc2NvcmUuc29ydGVkIDwtIGRpc2Vhc2Uuc2NvcmVbb3JkZXIoLWRpc2Vhc2Uuc2NvcmUkYHN0cmluZ2RiOjpkaXNlYXNlIHNjb3JlYCksLGRyb3A9RkFMU0VdCmhlYWQoZGlzZWFzZS5zY29yZS5zb3J0ZWQpCmBgYAoKWW91IGNhbiBoaWdobGlnaHQgdGhlIHRvcCBub2RlcyBieSBzZWxlY3RpbmcgdGhlIGNvcnJlc3BvbmRpbmcgcm93cyBpbiB0aGUgdGFibGUuIEhlcmUgd2UgYXJlIHNlbGVjdGluZyB0aGUgdG9wIGZldyBub2RlcyBiYXNlZCBvbiBkaXNlYXNlIHNjb3JlOgoKYGBge3J9CnNlbGVjdE5vZGVzKHJvd25hbWVzKGhlYWQoZGlzZWFzZS5zY29yZS5zb3J0ZWQpKSkKYGBgCgojIyBDb250aW51b3VzIGNvbG9yIG1hcHBpbmcKVGhlIHN0cmluZ0FwcCBhdXRvbWF0aWNhbGx5IHJldHJpZXZlcyBpbmZvcm1hdGlvbiBhYm91dCB3aGljaCBjb21wYXJ0bWVudHMgdGhlIHByb3RlaW5zIGFyZSBsb2NhdGVkIGZyb20gdGhlIENPTVBBUlRNRU5UUyBkYXRhYmFzZS4gQ3l0b3NjYXBlIGFsbG93cyB5b3UgdG8gbWFwIGF0dHJpYnV0ZXMgb2YgdGhlIG5vZGVzIGFuZCBlZGdlcyB0byB2aXN1YWwgcHJvcGVydGllcyBzdWNoIGFzIG5vZGUgY29sb3IgYW5kIGVkZ2Ugd2lkdGguIEhlcmUsIHdlIHdpbGwgbWFwIHRoZSBzdWJjZWxsdWxhciBsb2NhbGl6YXRpb24gZGF0YSBmb3IgbnVjbGV1cyB0byB0aGUgbm9kZSBjb2xvci4KCkZpcnN0LCBsZXQncyByZW1vdmUgdGhlIFN0cmluZyBzdHlsZToKYGBge3J9CmRlbGV0ZVN0eWxlTWFwcGluZyhzdHlsZS5uYW1lID0gJ1NUUklORyBzdHlsZSB2MS41IC0gb3ZhcmlhbiBjYW5jZXInLCB2aXN1YWwucHJvcCA9ICdOT0RFX0NVU1RPTUdSQVBISUNTXzEnKQpgYGAKCkZvciBlYWNoIGNlbGx1bGFyIGNvbXBhcnRtZW50LCBhIHNjb3JlIGlzIGRlZmluZWQgZm9yIGVhY2ggbm9kZS4gTmV4dCwgbGV0J3MgZGVmaW5lIGEgZGF0YWZyYW1lIGNvcnJlc3BvbmRpbmcgdG8gdGhlIHNjb3JlIGZvciBudWNsZXVzOgpgYGB7cn0KbnVjbGV1cy5ub2RlcyA8LSBnZXRUYWJsZUNvbHVtbnMoJ25vZGUnLCAnY29tcGFydG1lbnQ6Om51Y2xldXMnKQpgYGAKCkRlZmluZSB0aGUgYm91bmRzIG9mIHRoZSB2YWx1ZXMgaW4gdGhlICpjb21wYXJ0bWVudDo6bnVjbGV1cyogY29sdW1uOgpgYGB7cn0KbnVjbGV1cy5taW4gPC0gbWluKG51Y2xldXMubm9kZXMsIG5hLnJtID0gVCkKbnVjbGV1cy5tYXggPC0gbWF4KG51Y2xldXMubm9kZXMsIG5hLnJtID0gVCkKbnVjbGV1cy5jZW50ZXIgPC0gbnVjbGV1cy5taW4gKyAobnVjbGV1cy5tYXggLSBudWNsZXVzLm1pbikvMgpgYGAKCmBgYHtyfQpkYXRhLnZhbHVlcyA9IGMobnVjbGV1cy5taW4sIG51Y2xldXMuY2VudGVyLCBudWNsZXVzLm1heCkKbm9kZS5jb2xvcnMgPC0gYyhicmV3ZXIucGFsKGxlbmd0aChkYXRhLnZhbHVlcyksICJZbE9yUmQiKSkKc2V0Tm9kZUNvbG9yTWFwcGluZygnY29tcGFydG1lbnQ6Om51Y2xldXMnLCBkYXRhLnZhbHVlcywgbm9kZS5jb2xvcnMsIHN0eWxlLm5hbWUgPSAiU1RSSU5HIHN0eWxlIHYxLjUgLSBvdmFyaWFuIGNhbmNlciIpCmBgYAoKQmVjYXVzZSBtYW55IHByb3RlaW5zIGFyZSBsb2NhdGVkIGluIHRoZSBudWNsZXVzLCB3ZSB3aWxsIGlkZW50aWZ5IHRoZSBwcm90ZWlucyB3aXRoIGhpZ2hlc3QgY29uZmlkZW5jZSBvZiA1IGFuZCBjcmVhdGUgYSBzdWJuZXR3b3JrLgoKYGBge3J9CnRvcC5ub2RlcyA8LSByb3cubmFtZXMobnVjbGV1cy5ub2Rlcylbd2hpY2gobnVjbGV1cy5ub2Rlc1ssMV0+PTUpXQpjcmVhdGVTdWJuZXR3b3JrKHRvcC5ub2RlcyxzdWJuZXR3b3JrLm5hbWUgPSdudWNsZXVzIHNjb3JlIDUnKQpgYGAKCjxjZW50ZXI+CiFbXShodHRwczovL2N5dG9zY2FwZS5naXRodWIuaW8vY3l0b3NjYXBlLWF1dG9tYXRpb24vZm9yLXNjcmlwdGVycy9SL25vdGVib29rcy9kYXRhL2ltZy9zdHJpbmdBcHBfbnVjbGV1cy1zdWIucG5nKXt3aWR0aD02MCV9CjwvY2VudGVyPgoKIyBFeGVyY2lzZSAzCkluIHRoaXMgZXhlcmNpc2UsIHdlIHdpbGwgd29yayB3aXRoIGEgbGlzdCBvZiA1NDEgcHJvdGVpbnMgYXNzb2NpYXRlZCB3aXRoIGVwaXRoZWxpYWwgb3ZhcmlhbiBjYW5jZXIgKEVPQykgYXMgaWRlbnRpZmllZCBieSBwaG9zcGhvcHJvdGVvbWljcyBpbiB0aGUgc3R1ZHkgYnkgW0ZyYW5jYXZpbGxhIGV0IGFsXShodHRwczovL2RvaS5vcmcvMTAuMTAxNi9qLmNlbHJlcC4yMDE3LjAzLjAxNSkuIEFuIGFkYXB0ZWQgdGFibGUgd2l0aCB0aGUgZGF0YSBmcm9tIHRoaXMgc3R1ZHkgaXMgYXZhaWxhYmxlIFtoZXJlXShodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vY3l0b3NjYXBlL2N5dG9zY2FwZS1hdXRvbWF0aW9uL21hc3Rlci9mb3Itc2NyaXB0ZXJzL1Ivbm90ZWJvb2tzL3N0cmluZ0FwcC9GcmFuY2F2aWxsYTIwMTdDZWxsUmVwLnRzdikuCgojIyBQcm90ZWluIG5ldHdvcmsgcmV0cmlldmFsCgpIZXJlLCB3ZSBydW4gYSBxdWVyeSB3aXRoIHRoZSBmaXJzdCBjb2x1bW4gKFVuaVByb3QgSURzKSBpbiB0aGUgdGFibGU6CgpgYGB7cn0KZW9jLmRmIDwtIHJlYWQudGFibGUoImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9jeXRvc2NhcGUvY3l0b3NjYXBlLWF1dG9tYXRpb24vbWFzdGVyL2Zvci1zY3JpcHRlcnMvUi9ub3RlYm9va3Mvc3RyaW5nQXBwL0ZyYW5jYXZpbGxhMjAxN0NlbGxSZXAudHN2IiwgaGVhZGVyID0gVFJVRSwgc2VwID0gIlx0IiwgcXVvdGU9IlwiIiwgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFLCBjaGVjay5uYW1lcyA9IEZBTFNFKQpgYGAKCmBgYHtyfQpzdHJpbmcuY21kID0gcGFzdGUoJ3N0cmluZyBwcm90ZWluIHF1ZXJ5IHF1ZXJ5PSInLCBwYXN0ZShlb2MuZGYkVW5pUHJvdCwgY29sbGFwc2UgPSAnXG4nKSwgJyInLCBzZXAgPSAiIikKY29tbWFuZHNSdW4oc3RyaW5nLmNtZCkKYGBgCgo8Y2VudGVyPgohW10oaHR0cHM6Ly9jeXRvc2NhcGUuZ2l0aHViLmlvL2N5dG9zY2FwZS1hdXRvbWF0aW9uL2Zvci1zY3JpcHRlcnMvUi9ub3RlYm9va3MvZGF0YS9pbWcvc3RyaW5nQXBwX2VvYy5wbmcpe3dpZHRoPTYwJX0KPC9jZW50ZXI+CgpIb3cgbWFueSBub2RlcyBhbmQgZWRnZXMgYXJlIHRoZXJlIGluIHRoZSByZXN1bHRpbmcgbmV0d29yaz8gRG8gdGhlIHByb3RlaW5zIGFsbCBmb3JtIGEgY29ubmVjdGVkIG5ldHdvcms/IFdoeT8KClNpbmNlIHdlIHdpbGwgbmVlZCB0byByZWZlciB0byB0aGlzIG5ldHdvcmsgbGF0ZXIsIGxldCdzIGdpdmUgaXQgYSBtb3JlIHNwZWNpZmljIG5hbWU6CmBgYHtyfQpyZW5hbWVOZXR3b3JrKCJFT0MgLSBkYXRhIikKYGBgCgpDeXRvc2NhcGUgcHJvdmlkZXMgc2V2ZXJhbCBuZXR3b3JrIGxheW91dCBvcHRpb25zLgpGb3IgZXhhbXBsZSwgeW91IGNhbiB0cnkgdGhlICoqRGVncmVlIFNvcnRlZCBDaXJjbGUgTGF5b3V0KioKCmBgYHtyfQpsYXlvdXROZXR3b3JrKCdkZWdyZWUtY2lyY2xlJykKYGBgCgphbmQgdGhlICoqUHJlZnVzZSBGb3JjZSBEaXJlY3RlZCBMYXlvdXQqKiB3aXRoICoqc2NvcmUqKiBhcyBlZGdlIHdlaWdodAoKYGBge3J9CmxheW91dE5ldHdvcmsoJ2ZvcmNlLWRpcmVjdGVkIGVkZ2VBdHRyaWJ1dGU9InNjb3JlIicpCmBgYAoKKkNhbiB5b3UgZmluZCBhIGxheW91dCB0aGF0IGFsbG93cyB5b3UgdG8gZWFzaWx5IHJlY29nbml6ZSBwYXR0ZXJucyBpbiB0aGUgbmV0d29yaz8gVHJ5IHRoZSBFZGdlLXdlaWdodGVkIFNwcmluZyBFbWJlZGRlZCAoS2FtYWRhLUthd2FpKSBMYXlvdXQgd2l0aCB0aGUgYXR0cmlidXRlIOKAmHNjb3Jl4oCZLCB3aGljaCBpcyB0aGUgY29tYmluZWQgU1RSSU5HIGludGVyYWN0aW9uIHNjb3JlLioKCmBgYHtyfQpsYXlvdXROZXR3b3JrKCdrYW1hZGEta2F3YWkgZWRnZUF0dHJpYnV0ZT0ic2NvcmUiJykKYGBgCgo8Y2VudGVyPgohW10oaHR0cHM6Ly9jeXRvc2NhcGUuZ2l0aHViLmlvL2N5dG9zY2FwZS1hdXRvbWF0aW9uL2Zvci1zY3JpcHRlcnMvUi9ub3RlYm9va3MvZGF0YS9pbWcvc3RyaW5nQXBwX2VvYy1rYW1hZGEta2F3YWkucG5nKXt3aWR0aD02MCV9CjwvY2VudGVyPgoKKipOb3RlIHRoYXQgW3lGaWxlcyBMYXlvdXQgQWxnb3JpdGhtcyBBcHBdKGh0dHA6Ly9hcHBzLmN5dG9zY2FwZS5vcmcvYXBwcy95ZmlsZXNsYXlvdXRhbGdvcml0aG1zKSBkb2VzIG5vdCBzdXBwb3J0IGFueSBhdXRvbWF0aW9uLioqCgojIyBEaXNjcmV0ZSBjb2xvciBtYXBwaW5nCgpDeXRvc2NhcGUgYWxsb3dzIHlvdSB0byBtYXAgYXR0cmlidXRlcyBvZiB0aGUgbm9kZXMgYW5kIGVkZ2VzIHRvIHZpc3VhbCBwcm9wZXJ0aWVzIHN1Y2ggYXMgbm9kZSBjb2xvciBhbmQgZWRnZSB3aWR0aC4KSGVyZSwgd2Ugd2lsbCBtYXAgdGhlIHRhcmdldCBmYW1pbHkgZGF0YSB0byB0aGUgbm9kZSBjb2xvci4KCkxldHMgY2hhbmdlIHRoZSBub2RlICoqRmlsbCBDb2xvcioqIHdpdGggKip0YXJnZXQgZmFtaWx5KiogY29sdW1uIGluIHRoZSBub2RlIHRhYmxlLiBXZSBjYW4gYWxzbyByZW1vdmUgdGhlIFN0cmluZyBzdHlsaW5nIGZvciBub2RlcyB0byBiZXR0ZXIgc2VlIG91ciBkYXRhOgoKYGBge3J9CmRlbGV0ZVN0eWxlTWFwcGluZyhzdHlsZS5uYW1lID0gJ1NUUklORyBzdHlsZSB2MS41JywgdmlzdWFsLnByb3AgPSAnTk9ERV9DVVNUT01HUkFQSElDU18xJykKY29sdW1uIDwtICJ0YXJnZXQ6OmZhbWlseSIKdmFsdWVzIDwtIGMoJ0tpbmFzZScsICdHUENSJykKY29sb3JzIDwtIGMoJyNGRjAwMDAnLCAnIzAwMDBGRicpCnNldE5vZGVDb2xvckRlZmF1bHQoJyNDQ0NDQ0MnLCBzdHlsZS5uYW1lID0gIlNUUklORyBzdHlsZSB2MS41IikKc2V0Tm9kZUNvbG9yTWFwcGluZyhjb2x1bW4sIHZhbHVlcywgY29sb3JzLCBtYXBwaW5nLnR5cGUgPSAiZCIsIHN0eWxlLm5hbWUgPSAiU1RSSU5HIHN0eWxlIHYxLjUiKQpgYGAKCjxjZW50ZXI+CiFbXShodHRwczovL2N5dG9zY2FwZS5naXRodWIuaW8vY3l0b3NjYXBlLWF1dG9tYXRpb24vZm9yLXNjcmlwdGVycy9SL25vdGVib29rcy9kYXRhL2ltZy9zdHJpbmdBcHBfc3R5bGUucG5nKXt3aWR0aD02MCV9CjwvY2VudGVyPgoqSG93IG1hbnkgb2YgdGhlIHByb3RlaW5zIGluIHRoZSBuZXR3b3JrIGFyZSBraW5hc2VzPyoKCk5vdGUgdGhhdCB0aGUgcmV0cmlldmVkIG5ldHdvcmsgY29udGFpbnMgYSBsb3Qgb2YgYWRkaXRpb25hbCBpbmZvcm1hdGlvbiBhc3NvY2lhdGVkIHdpdGggdGhlIG5vZGVzIGFuZCBlZGdlcywgc3VjaCBhcyB0aGUgcHJvdGVpbiBzZXF1ZW5jZSwgdGlzc3VlIGV4cHJlc3Npb24gZGF0YSAoTm9kZSBUYWJsZSkgYXMgd2VsbCBhcyB0aGUgY29uZmlkZW5jZSBzY29yZXMgZm9yIHRoZSBkaWZmZXJlbnQgaW50ZXJhY3Rpb24gZXZpZGVuY2VzIChFZGdlIFRhYmxlKS4gSW4gdGhlIGZvbGxvd2luZyBzdGVwcywgd2Ugd2lsbCBleHBsb3JlIHRoZXNlIGRhdGEgdXNpbmcgQ3l0b3NjYXBlLgoKIyMgRGF0YSBpbXBvcnQKCk5ldHdvcmsgbm9kZXMgYW5kIGVkZ2VzIGNhbiBoYXZlIGFkZGl0aW9uYWwgaW5mb3JtYXRpb24gYXNzb2NpYXRlZCB3aXRoIHRoZW0gdGhhdCB3ZSBjYW4gbG9hZCBpbnRvIEN5dG9zY2FwZSBhbmQgdXNlIGZvciB2aXN1YWxpemF0aW9uLgpXZSBhbHJlYWR5IGltcG9ydGVkIHRoZSBkYXRhIGZyb20gYW4gRXhjZWwgc3ByZWFkc2hlZXQgZGVyaXZlZCBmcm9tIGRhdGEgcHJvdmlkZWQgaW4gdGhlIHBhcGVyIG1lbnRpb25lZCBhYm92ZS4gSGVyZSB3ZSBjaGVjayBpdCBhZ2FpbiB3aXRoOgoKYGBge3J9CmhlYWQoZW9jLmRmKQpgYGAKCk5vdyB3ZSBuZWVkIHRvIG1hcCB1bmlxdWUgaWRlbnRpZmllcnMgYmV0d2VlbiB0aGUgZW50cmllcyBpbiB0aGUgZGF0YSBhbmQgdGhlIG5vZGVzIGluIHRoZSBuZXR3b3JrLiBUaGUga2V5IHBvaW50IG9mIHRoaXMgaXMgdG8gaWRlbnRpZnkgd2hpY2ggbm9kZXMgaW4gdGhlIG5ldHdvcmsgYXJlIGVxdWl2YWxlbnQgdG8gd2hpY2ggZW50cmllcyBpbiB0aGUgdGFibGUuIFRoaXMgZW5hYmxlcyBtYXBwaW5nIG9mIGRhdGEgdmFsdWVzIGludG8gdmlzdWFsIHByb3BlcnRpZXMgbGlrZSBGaWxsIENvbG9yIGFuZCBTaGFwZS4gVGhpcyBraW5kIG9mIG1hcHBpbmcgaXMgdHlwaWNhbGx5IGRvbmUgYnkgY29tcGFyaW5nIHRoZSB1bmlxdWUgSWRlbnRpZmllciBhdHRyaWJ1dGUgdmFsdWUgZm9yIGVhY2ggbm9kZSAoS2V5IENvbHVtbiBmb3IgTmV0d29yaykgd2l0aCB0aGUgdW5pcXVlIElkZW50aWZpZXIgdmFsdWUgZm9yIGVhY2ggZGF0YSB2YWx1ZSAoa2V5IHN5bWJvbCkuCgpUaGUgKipLZXkgQ29sdW1uKiogZm9yIE5ldHdvcmsgYWxsb3dzIHlvdSB0byBzZXQgdGhlIG5vZGUgYXR0cmlidXRlIGNvbHVtbiB0aGF0IGlzIHRvIGJlIHVzZWQgYXMga2V5IHRvIG1hcCB0by4KSW4gdGhpcyBjYXNlIGl0IGlzICoqcXVlcnkgdGVybSoqIGJlY2F1c2UgdGhpcyBhdHRyaWJ1dGUgY29udGFpbnMgdGhlIFVuaVByb3QgYWNjZXNzaW9uIG51bWJlcnMgeW91IGVudGVyZWQgd2hlbiByZXRyaWV2aW5nIHRoZSBuZXR3b3JrLgoKVG8gaW1wb3J0IHRoZSBub2RlIGF0dHJpYnV0ZXMgZmlsZSBpbnRvIEN5dG9zY2FwZSwgcnVuCgpgYGB7cn0KbG9hZFRhYmxlRGF0YShhcy5kYXRhLmZyYW1lKGVvYy5kZiksIGRhdGEua2V5LmNvbHVtbiA9ICJVbmlQcm90IiwgdGFibGUgPSAibm9kZSIsIHRhYmxlLmtleS5jb2x1bW4gPSAicXVlcnkgdGVybSIpCmBgYAoKSWYgdGhlcmUgaXMgYSBtYXRjaCBiZXR3ZWVuIHRoZSB2YWx1ZSBvZiBhIEtleSBpbiB0aGUgZGF0YXNldCBhbmQgdGhlIHZhbHVlIHRoZSBLZXkgQ29sdW1uIGZvciBOZXR3b3JrIGZpZWxkIGluIHRoZSBuZXR3b3JrLCBhbGwgYXR0cmlidXRl4oCTdmFsdWUgcGFpcnMgYXNzb2NpYXRlZCB3aXRoIHRoZSBlbGVtZW50IGluIHRoZSBkYXRhc2V0IGFyZSBhc3NpZ25lZCB0byB0aGUgbWF0Y2hpbmcgbm9kZSBpbiB0aGUgbmV0d29yay4gCllvdSB3aWxsIGZpbmQgdGhlIGltcG9ydGVkIGNvbHVtbnMgYXQgdGhlIGVuZCBvZiB0aGUgTm9kZSBUYWJsZS4KCiMjIENvbnRpbnVvdXMgY29sb3IgbWFwcGluZwoKTm93LCB3ZSB3YW50IHRvIGNvbG9yIHRoZSBub2RlcyBhY2NvcmRpbmcgdG8gdGhlIHF1YW50aXRhdGl2ZSBwaG9zcGhvcnlsYXRpb24gZGF0YSAobG9nIHJhdGlvKSBiZXR3ZWVuIGRpc2Vhc2UgYW5kIGhlYWx0aHkgdGlzc3VlcyBmb3IgdGhlIG1vc3Qgc2lnbmlmaWNhbnQgc2l0ZSBmb3IgZWFjaCBwcm90ZWluOgoKYGBge3J9CmxvZ3JhdGlvLnNjb3JlLnRhYmxlIDwtIGdldFRhYmxlQ29sdW1ucygnbm9kZScsICJFT0MgdnMgRlRFJk9TRSIpCmBgYAoKU2luY2UgdGhpcyBpcyBhIG51bWVyaWMgdmFsdWUsIHdlIHdpbGwgdXNlIHRoZSBDb250aW51b3VzIE1hcHBpbmcgYXMgdGhlIE1hcHBpbmcgVHlwZSwgYW5kIHNldCBhIGNvbG9yIGdyYWRpZW50IGZvciBob3cgYWJ1bmRhbnQgZWFjaCBwcm90ZWluIGlzLgoKYGBge3J9CmxvZ3JhdGlvLm1pbiA8LSBtaW4obG9ncmF0aW8uc2NvcmUudGFibGUsIG5hLnJtID0gVCkKbG9ncmF0aW8ubWF4IDwtIG1heChsb2dyYXRpby5zY29yZS50YWJsZSwgbmEucm0gPSBUKQpgYGAKCmBgYHtyfQpsb2dyYXRpby5kYXRhLnZhbHVlcyA9IGMobG9ncmF0aW8ubWluLCAwLCBsb2dyYXRpby5tYXgpCmxvZ3JhdGlvLm5vZGUuY29sb3JzIDwtIGMoYnJld2VyLnBhbChsZW5ndGgobG9ncmF0aW8uZGF0YS52YWx1ZXMpLCAiUmRCdSIpKQpgYGAKCmBgYHtyfQpzZXROb2RlQ29sb3JNYXBwaW5nKCdFT0MgdnMgRlRFJk9TRScsIGxvZ3JhdGlvLmRhdGEudmFsdWVzLCBsb2dyYXRpby5ub2RlLmNvbG9ycywgc3R5bGUubmFtZSA9ICJTVFJJTkcgc3R5bGUgdjEuNSIpCmBgYApUaGUgY29sb3IgZ3JhZGllbnQgYmx1ZS13aGl0ZS1yZWQgZ2l2ZXMgYSB2aXN1YWxpemF0aW9uIG9mIHRoZSBuZWdhdGl2ZS10by1wb3NpdGl2ZSBhYnVuZGFuY2UgcmF0aW8uCgo8Y2VudGVyPgohW10oaHR0cHM6Ly9jeXRvc2NhcGUuZ2l0aHViLmlvL2N5dG9zY2FwZS1hdXRvbWF0aW9uL2Zvci1zY3JpcHRlcnMvUi9ub3RlYm9va3MvZGF0YS9pbWcvc3RyaW5nQXBwX2VvYy1maW5hbC5wbmcpe3dpZHRoPTYwJX0KPC9jZW50ZXI+CgoqQXJlIHRoZSB1cC1yZWd1bGF0ZWQgbm9kZXMgZ3JvdXBlZCB0b2dldGhlcj8qCgojIyBGdW5jdGlvbmFsIGVucmljaG1lbnQKCk5leHQsIHdlIHdpbGwgcmV0cmlldmUgZnVuY3Rpb25hbCBlbnJpY2htZW50IGZvciB0aGUgcHJvdGVpbnMgaW4gb3VyIG5ldHdvcmsuCgpUaGUgU1RSSU5HIGFwcCBoYXMgYnVpbHQtaW4gZW5yaWNobWVudCBhbmFseXNpcyBmdW5jdGlvbmFsaXR5LCB3aGljaCBpbmNsdWRlcyBlbnJpY2htZW50IGZvciBHTyBQcm9jZXNzLCBHTyBDb21wb25lbnQsIEdPIEZ1bmN0aW9uLCBJbnRlclBybywgS0VHRyBQYXRod2F5cywgYW5kIFBGQU0uCgpGaXJzdCwgd2Ugd2lsbCBydW4gdGhlIGVucmljaG1lbnQgb24gdGhlIHdob2xlIG5ldHdvcmssIGFnYWluc3QgdGhlIGdlbm9tZTogCgpgYGB7cn0Kc3RyaW5nLmNtZCA9ICdzdHJpbmcgcmV0cmlldmUgZW5yaWNobWVudCBhbGxOZXRTcGVjaWVzPSJIb21vIHNhcGllbnMiLCBiYWNrZ3JvdW5kPWdlbm9tZSAgc2VsZWN0ZWROb2Rlc09ubHk9ImZhbHNlIicKY29tbWFuZHNSdW4oc3RyaW5nLmNtZCkKc3RyaW5nLmNtZCA9ICdzdHJpbmcgc2hvdyBlbnJpY2htZW50Jwpjb21tYW5kc1J1bihzdHJpbmcuY21kKQpgYGAKCkEgbmV3IFNUUklORyBFbnJpY2htZW50IHRhYiB3aWxsIGFwcGVhciBpbiB0aGUgVGFibGUgUGFuZWwgb24gdGhlIGJvdHRvbS4gCkl0IGNvbnRhaW5zIGEgdGFibGUgb2YgZW5yaWNoZWQgdGVybXMgYW5kIGNvcnJlc3BvbmRpbmcgaW5mb3JtYXRpb24gZm9yIGVhY2ggZW5yaWNobWVudCBjYXRlZ29yeS4KCipXaGljaCBhcmUgdGhlIHRocmVlIG1vc3Qgc3RhdGlzdGljYWxseSBzaWduaWZpY2FudCB0ZXJtcz8qCgpUbyBleHBsb3JlIG9ubHkgc3BlY2lmaWMgdHlwZXMgb2YgdGVybXMsIGUuZy4gR08gdGVybXMsIGFuZCB0byByZW1vdmUgcmVkdW5kYW50IHRlcm1zIGZyb20gdGhlIHRhYmxlLCB3ZSBhcmUgZ29pbmcgdG8gZmlsdGVyIHRoZSB0YWJsZSB0byBvbmx5IHNob3cgR08gUHJvY2VzczoKCmBgYHtyfQpzdHJpbmcuY21kID0gJ3N0cmluZyBmaWx0ZXIgZW5yaWNobWVudCBjYXRlZ29yaWVzPSJHTyBQcm9jZXNzIiwgb3ZlcmxhcEN1dG9mZiA9ICIwLjUiLCByZW1vdmVPdmVybGFwcGluZyA9ICJ0cnVlIicKY29tbWFuZHNSdW4oc3RyaW5nLmNtZCkKYGBgCgpJbiB0aGlzIHdheSwgeW91IHdpbGwgc2VlIG9ubHkgdGhlIHN0YXRpc3RpY2FsbHkgc2lnbmlmaWNhbnQgR08gdGVybXMgdGhhdCBkbyBub3QgcmVwcmVzZW50IGxhcmdlbHkgdGhlIHNhbWUgc2V0IG9mIHByb3RlaW5zIHdpdGhpbiB0aGUgbmV0d29yay4KCipEbyB0aGUgZnVuY3Rpb25hbCB0ZXJtcyBhc3NpZ25lZCB0byBhIHByb3RlaW4gY29ycmVsYXRlIHdpdGggd2hldGhlciBpdCBpcyB1cC0gb3IgZG93bi1yZWd1bGF0ZWQ/KgoKTmV4dCwgd2Ugd2lsbCB2aXN1YWxpemUgdGhlIHRvcC0zIGVucmljaGVkIHRlcm1zIGluIHRoZSBuZXR3b3JrIGJ5IHVzaW5nIHNwbGl0IGNoYXJ0cy4KCmBgYHtyfQpzdHJpbmcuY21kID0gJ3N0cmluZyBzaG93IGNoYXJ0cycKY29tbWFuZHNSdW4oc3RyaW5nLmNtZCkKYGBgCgo8Y2VudGVyPgohW10oaHR0cHM6Ly9jeXRvc2NhcGUuZ2l0aHViLmlvL2N5dG9zY2FwZS1hdXRvbWF0aW9uL2Zvci1zY3JpcHRlcnMvUi9ub3RlYm9va3MvZGF0YS9pbWcvc3RyaW5nQXBwX2VvYy1lbnJpY2gucG5nKXt3aWR0aD02MCV9CjwvY2VudGVyPgoKKkRvIHRoZXNlIHRlcm1zIGdpdmUgZ29vZCBjb3ZlcmFnZSBvZiB0aGUgcHJvdGVpbnMgaW4gbmV0d29yaz8qCgpGaW5hbGx5LCB5b3UgY2FuIHNhdmUgdGhlIGxpc3Qgb2YgZW5yaWNoZWQgdGVybXMgYW5kIGFzc29jaWF0ZWQgcC12YWx1ZXMgYXMgYSB0ZXh0IGZpbGUgbG9jYWxseS4gTm90ZSB0aGF0IHRoaXMgd2lsbCBleHBvcnQgYSBjdnMgZmlsZSB0byB5b3VyIGN1cnJlbnQgd29ya2luZyBkaXJlY3RvcnkuCgpgYGB7cn0KY29tbWFuZHNQT1NUKHBhc3RlMCgndGFibGUgZXhwb3J0IHRhYmxlPSJTVFJJTkcgRW5yaWNobWVudDogQWxsIiBvcHRpb25zPSJDU1YiIG91dHB1dEZpbGU9IicscGFzdGUoZ2V0d2QoKSwic3RyaW5nLWVucmljaG1lbnQtYWxsLmNzdiIsc2VwID0gIi8iKSwnIicpKQpgYGAKCiMjIE5ldHdvcmsgb3ZlcmxhcAoKQ3l0b3NjYXBlIHByb3ZpZGVzIGZ1bmN0aW9uYWxpdHkgdG8gbWVyZ2UgdHdvIG9yIG1vcmUgbmV0d29ya3MsIGJ1aWxkaW5nIGVpdGhlciB0aGVpciB1bmlvbiwgaW50ZXJzZWN0aW9uIG9yIGRpZmZlcmVuY2UuIFdlIHdpbGwgbm93IG1lcmdlIHRoZSAib3ZhcmlhbiBjYW5jbmVyIiBuZXR3b3JrIHdlIGhhdmUgZnJvbSB0aGUgRElTRUFTRVMgcXVlcnkgd2l0aCB0aGUgb25lIHdlIGhhdmUgZnJvbSB0aGUgZGF0YSwgc28gdGhhdCB3ZSBjYW4gaWRlbnRpZnkgdGhlIG92ZXJsYXAgYmV0d2VlbiB0aGVtLiAKCmBgYHtyfQptZXJnZU5ldHdvcmtzKGMoIlN0cmluZyBOZXR3b3JrIiwgIlN0cmluZyBOZXR3b3JrIC0gb3ZhcmlhbiBjYW5jZXIiKSwgIk1lcmdlZCBOZXR3b3JrIiwgb3BlcmF0aW9uPSJpbnRlcnNlY3Rpb24iKQpgYGAKCkhvdyBtYW55IG5vZGVzIGFyZSBpbiB0aGUgaW50ZXJzZWN0aW9uPwoKTm93IHdlIHdpbGwgbWFrZSB0aGUgdW5pb24gb2YgdGhlIGludGVyc2VjdGlvbiBuZXR3b3JrLCB3aGljaCBjb250YWlucyB0aGUgZGlzZWFzZSBzY29yZXMsIGFuZCB0aGUgZXhwZXJpbWVudGFsIG5ldHdvcmsuIE1ha2Ugc3VyZSB0aGF0IHRoZSBuZXcgbWVyZ2VkIG5ldHdvcmsgaGFzIHRoZSBzYW1lIG51bWJlciBvZiBub2RlcyBhbmQgZWRnZXMgYXMg4oCYU3RyaW5nIE5ldHdvcmvigJksIGFuZCB0aGF0IHNvbWUgbm9kZXMgaGF2ZSBhIGRpc2Vhc2Ugc2NvcmUuCmBgYHtyfQptZXJnZU5ldHdvcmtzKGMoIk1lcmdlZCBOZXR3b3JrIiwgIlN0cmluZyBOZXR3b3JrIiksICJVbmlvbiIpCmBgYApOb3csIHdlIGNhbiBjaGFuZ2UgdGhlIHZpc3VhbGl6YXRpb24gb2YgdGhlIG1lcmdlZCBuZXR3b3JrIHRvIGJlIGFibGUgdG8gaWRlbnRpZnkgaGlnaCBkaXNlYXNlIHNjb3JlIHByb3RlaW5zLiBTcGVjaWZpY2FsbHksIHdlIHdpbGwgY2hhbmdlIHRoZSBzaXplIGFuZCBjb2xvciBvZiB0aGUgbm9kZXMgdG8gcmVmbGVjdCB0aGVpciBkaXNlYXNlIHNjb3JlLiAKCmBgYHtyfQoKZGlzZWFzZS5zY29yZS50YWJsZSA8LSBnZXRUYWJsZUNvbHVtbnMoJ25vZGUnLCAic3RyaW5nZGI6OmRpc2Vhc2Ugc2NvcmUiKQoKbWluIDwtIG1pbihkaXNlYXNlLnNjb3JlLnRhYmxlLCBuYS5ybSA9IFQpCm1heCA8LSBtYXgoZGlzZWFzZS5zY29yZS50YWJsZSwgbmEucm0gPSBUKQptaWQgPC0gbWluICsgKG1heCAtIG1pbikvMgoKbm9kZS5kYXRhLnZhbHVlcyA9IGMobWluLCBtYXgpCm5vZGUuc2l6ZXMgPSBjKDM1LDUwKQpzZXROb2RlU2l6ZU1hcHBpbmcoInN0cmluZ2RiOjpkaXNlYXNlIHNjb3JlIiwgbm9kZS5kYXRhLnZhbHVlcywgbm9kZS5zaXplcywgZGVmYXVsdC5zaXplPSIzMCIsIHN0eWxlLm5hbWUgPSAiZGVmYXVsdCIpCgpjb2xvci5kYXRhLnZhbHVlcyA9IGMobWluLCBtaWQsIG1heCkKbm9kZS5jb2xvcnMgPC0gYyhicmV3ZXIucGFsKGxlbmd0aChjb2xvci5kYXRhLnZhbHVlcyksICJCbHVlcyIpKQpzZXROb2RlQ29sb3JNYXBwaW5nKCJzdHJpbmdkYjo6ZGlzZWFzZSBzY29yZSIsIGNvbG9yLmRhdGEudmFsdWVzLCBub2RlLmNvbG9ycywgc3R5bGUubmFtZSA9ICJkZWZhdWx0IikKYGBgCgpMZXQncyBmb2N1cyBvbiBvbmx5IHRoZSBjb25uZWN0ZWQgbm9kZXM6CgpgYGB7cn0KY3JlYXRlU3VibmV0d29yayhlZGdlcz0nYWxsJywgc3VibmV0d29yay5uYW1lPSdVbmlvbiBzdWInKQpgYGAKCjxjZW50ZXI+CiFbXShodHRwczovL2N5dG9zY2FwZS5naXRodWIuaW8vY3l0b3NjYXBlLWF1dG9tYXRpb24vZm9yLXNjcmlwdGVycy9SL25vdGVib29rcy9kYXRhL2ltZy9zdHJpbmdBcHBfdW5pb24ucG5nKXt3aWR0aD04MCV9CjwvY2VudGVyPgoKPCEtLSAjIEV4ZXJjaXNlIDQgLS0+CjwhLS0gSW4gdGhpcyBleGVyY2lzZSwgd2Ugd2lsbCByZXRyaWV2ZSB2aXJ1cy1ob3N0IG5ldHdvcmtzIGZvciB0d28gY2xvc2VseSByZWxhdGVkIHZpcnVzZXMsIG1lcmdlIHRoZW0gaW50byBhIHNpbmdsZSBuZXR3b3JrLCBhbmQgdGhlbiB3aWxsIHJldHJpZXZlIHRoZSBmdW5jdGlvbmFsIGVucmljaG1lbnQgZm9yIHRoZSBob3N0IHByb3RlaW5zIGluIHRoaXMgbmV0d29yay4gLS0+Cg==