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.
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:
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==