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


Cytoscape (www.cytoscape.org) is one of the most popular applications for network analysis and visualization. In this tutorial, we will demonstrate new capabilities to integrate Cytoscape into programmatic workflows and pipelines using R. We will look at two use cases; the first exploring how top scoring genes are related and how to overlay data; the second looking at genes with similar expression and their functional enrichment.

Installation

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

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

Required Software

The whole point of RCy3 is to connect with Cytoscape. You will need to install and launch Cytoscape:

Make sure that Cytoscape is running

cytoscapePing ()
cytoscapeVersionInfo ()

To see all the functions available in the RCy3 package:

help(package=RCy3)

Also install additional Cytoscape apps that will be used in this tutorial:

#available in Cytoscape 3.7.0 and above
installApp('STRINGapp')  
installApp('aMatReader')
installApp('clusterMaker2')

Example Data Set

We downloaded gene expression data from the Ovarian Serous Cystadenocarcinoma project of The Cancer Genome Atlas (TCGA), http://cancergenome.nih.gov via the Genomic Data Commons (GDC) portal on 2017-06-14 using TCGABiolinks R package. The data includes 300 samples available as RNA-seq data, with reads mapped to a reference genome using MapSplice and read counts per transcript determined using the RSEM method. RNA-seq data are labeled as ‘RNA-Seq V2’, see details at: https://wiki.nci.nih.gov/display/TCGA/RNASeq+Version+2). The RNA-SeqV2 data consists of raw counts similar to regular RNA-seq but RSEM (RNA-Seq by Expectation Maximization) data can be used with the edgeR method. The expression dataset of 300 tumours, with 79 classified as Immunoreactive, 72 classified as Mesenchymal, 69 classified as Differentiated, and 80 classified as Proliferative samples (class definitions were obtained from Verhaak et al. Supplementary Table 1, third column). RNA-seq read counts were converted to CPM values and genes with CPM > 1 in at least 50 of the samples are retained for further study (50 is the minimal sample size in the classes). The data was normalized and differential expression was calculated for each cancer class relative to the rest of the samples.

There are two data files: 1. Expression matrix - containing the normalized expression for each gene across all 300 samples. 1. Gene ranks - containing the p-values, FDR and foldchange values for the 4 comparisons (mesenchymal vs rest, differential vs rest, proliferative vs rest and immunoreactive vs rest)

The following script will download and export files to the same directory as this copy of the Rmd file:

getwd()
#load data files
RNASeq_expression_matrix <- read.table("https://raw.githubusercontent.com/cytoscape/cytoscape-tutorials/gh-pages/presentations/modules/RCy3_ExampleData/data/TCGA_OV_RNAseq_expression.txt", header = TRUE, sep = "\t", quote="\"", stringsAsFactors = FALSE)

RNASeq_gene_scores <- read.table("https://raw.githubusercontent.com/cytoscape/cytoscape-tutorials/gh-pages/presentations/modules/RCy3_ExampleData/data/TCGA_OV_RNAseq_All_edgeR_scores.txt", header = TRUE, sep = "\t", quote="\"", stringsAsFactors = FALSE)

Use Case 2 - Which genes have similar expression?

Instead of querying existing resources look for correlations in your own dataset to find out which genes have similar expression. There are many tools that can analyze your data for correlation. A popular tool is Weighted Gene Correlation Network Analysis (WGCNA) which takes expression data and calculates functional modules. As a simple example we can transform our expression dataset into a correlation matrix.

Create correlation matrix

Using the Cytoscape App, aMatReader, we transform our adjacency matrix into an interaction network. First we filter the correlation matrix to contain only the strongest connections (for example, only correlations greater than 0.9).

RNASeq_expression <- RNASeq_expression_matrix[,3:ncol(RNASeq_expression_matrix)]

rownames(RNASeq_expression) <- RNASeq_expression_matrix$Name
RNAseq_correlation_matrix <- cor(t(RNASeq_expression), method="pearson") #Note: this takes a while

#set the diagonal of matrix to zero - eliminate self-correlation
RNAseq_correlation_matrix[ 
  row(RNAseq_correlation_matrix) == col(RNAseq_correlation_matrix) ] <- 0

# set all correlations that are less than 0.9 to zero
RNAseq_correlation_matrix[which(RNAseq_correlation_matrix<0.90)] <- 0

#get rid of rows and columns that have no correlations with the above thresholds
RNAseq_correlation_matrix <- RNAseq_correlation_matrix[which(rowSums(RNAseq_correlation_matrix) != 0),
                          which(colSums(RNAseq_correlation_matrix) !=0)]

#write out the correlation file
correlation_filename <- file.path(getwd(), "TCGA_OV_RNAseq_expression_correlation_matrix.txt") 
write.table(RNAseq_correlation_matrix,  file = correlation_filename, col.names  = TRUE, row.names = FALSE, sep = "\t", quote=FALSE)

Create network from correlation matrix

Use the CyRest call to access the aMatReader functionality:

amat_url <- "aMatReader/v1/import"
amat_params = list(files = list(correlation_filename),
                   delimiter = "TAB",
                   undirected = TRUE,
                   ignoreZeros = TRUE,
                   interactionName = "correlated with",
                   rowNames = FALSE
                   )
 
response <- cyrestPOST(operation = amat_url, body = amat_params, base.url = "http://localhost:1234")

current_network_id <- response$data["suid"]
#relayout network
layoutNetwork('cose',
              network = as.numeric(current_network_id))
renameNetwork(title ="Coexpression_network_pear0_95",
              network = as.numeric(current_network_id))

Modify the visual style

Modify the visualization to see where each genes is predominantly expressed. Look at the 4 different p-values associated with each gene and color the nodes with the type associated with the lowest FDR.

Load in the scoring data. Specify the cancer type where the genes has the lowest FDR value:

nodes_in_network <- rownames(RNAseq_correlation_matrix)

#add an additional column to the gene scores table to indicate in which samples
# the gene is significant
node_class <- vector(length = length(nodes_in_network),mode = "character")
for(i in 1:length(nodes_in_network)){
  current_row <- which(RNASeq_gene_scores$Name == nodes_in_network[i])
  min_pvalue <- min(RNASeq_gene_scores[current_row,
                                       grep(colnames(RNASeq_gene_scores), pattern = "FDR")])
  if(RNASeq_gene_scores$FDR.mesen[current_row] <=min_pvalue){
    node_class[i] <- paste(node_class[i],"mesen",sep = " ")
  }
  if(RNASeq_gene_scores$FDR.diff[current_row] <=min_pvalue){
    node_class[i] <- paste(node_class[i],"diff",sep = " ")
  }
  if(RNASeq_gene_scores$FDR.prolif[current_row] <=min_pvalue){
    node_class[i] <- paste(node_class[i],"prolif",sep = " ")
  }
  if(RNASeq_gene_scores$FDR.immuno[current_row] <=min_pvalue){
    node_class[i] <- paste(node_class[i],"immuno",sep = " ")
  }
}
node_class <- trimws(node_class)
node_class_df <-data.frame(name=nodes_in_network, node_class,stringsAsFactors = FALSE)

head(node_class_df)

Map the new node attribute and the all the gene scores to the network.

loadTableData(RNASeq_gene_scores,table.key.column = "name",data.key.column = "Name")  #default data.frame key is row.names

loadTableData(node_class_df,table.key.column = "name",data.key.column = "name")  #default data.frame key is row.names

Create a color mapping for the different cancer types:

#create a new mapping with the different types
unique_types <- sort(unique(node_class))

coul = brewer.pal(4, "Set1") 
 
# I can add more tones to this palette :
coul = colorRampPalette(coul)(length(unique_types))

setNodeColorMapping(table.column = "node_class",table.column.values = unique_types,
                    colors = coul,mapping.type = "d")

Cluster the Network

#make sure it is set to the right network
  setCurrentNetwork(network = getNetworkName(suid=as.numeric(current_network_id)))

  #cluster the network
  clustermaker_url <- paste("cluster mcl network=SUID:",current_network_id, sep="")
  commandsGET(clustermaker_url)
  
  #get the clustering results
  default_node_table <- getTableColumns(table= "node",network = as.numeric(current_network_id))
 
  head(default_node_table)

Perform functional enrichment

We can use the STRINGapp to perform a quick-and-easy functional enrichment analysis. This will provide functional labels (e.g., GO terms and pathways) to the bulk of genes in a given cluster.

Focusing on cluster 1 as an example:

current_cluster <- "1"
#select all the nodes in cluster 1
selectednodes <- selectNodes(current_cluster, by.col="__mclCluster")
  
#create a subnetwork with cluster 1
subnetwork_suid <- createSubnetwork(nodes="selected")

Let’s “stringify” the network so that the STRINGapp can recognize its contents:

commandsRun('string stringify column="name" species="Homo sapiens"')

You may want to reapply the cluster-based style and readjust layout:

setVisualStyle("default")
layoutNetwork('force-directed defaultSpringCoefficient=0.000005 defaultSpringLength=60')

Then, it’s just a few commands to perform enrichment analysis and display the results as color-coded node borders:

commandsPOST('string retrieve enrichment')
commandsPOST('string show enrichment') #toggles to 'hide' after running
commandsPOST('string show charts')

Export image of resulting cluster with enrichment results.

cluster1enr_png_file_name <- "cluster1em.png"
if(file.exists(cluster1enr_png_file_name)){
  #cytoscape hangs waiting for user response if file already exists.  Remove it first
  file.remove(cluster1enr_png_file_name)
  } 

#export the network
exportImage(cluster1enr_png_file_name, type = "png")
LS0tCnRpdGxlOiAiVG9wIGdlbmVzIGFuZCBjb2V4cHJlc3Npb24iCmF1dGhvcjogImJ5IFJ1dGggSXNzZXJsaW4sIEtyaXN0aW5hIEhhbnNwZXJzIgpkYXRlOiAiYHIgU3lzLkRhdGUoKWAiCm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgY29kZV9mb2xkaW5nOiBub25lCiAgICB0b2NfZmxvYXQ6IHllcwogIGh0bWxfZG9jdW1lbnQ6CiAgICBkZl9wcmludDogcGFnZWQKcGFja2FnZTogUkN5MwotLS0KYGBge3IsIGVjaG8gPSBGQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KAogIGV2YWw9RkFMU0UKKQpgYGAKKlRoZSBSIG1hcmtkb3duIGlzIGF2YWlsYWJsZSBmcm9tIHRoZSBwdWxsZG93biBtZW51IGZvciogQ29kZSAqYXQgdGhlIHVwcGVyLXJpZ2h0LCBjaG9vc2UgIkRvd25sb2FkIFJtZCIsIG9yIFtkb3dubG9hZCB0aGUgUm1kIGZyb20gR2l0SHViXShodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vY3l0b3NjYXBlL2N5dG9zY2FwZS1hdXRvbWF0aW9uL21hc3Rlci9mb3Itc2NyaXB0ZXJzL1Ivbm90ZWJvb2tzL1RvcC1nZW5lcy1hbmQtY29leHByZXNzaW9uLlJtZCkuKgoKPGhyIC8+CkN5dG9zY2FwZSAod3d3LmN5dG9zY2FwZS5vcmcpIGlzIG9uZSBvZiB0aGUgbW9zdCBwb3B1bGFyIGFwcGxpY2F0aW9ucyBmb3IgbmV0d29yayBhbmFseXNpcyBhbmQgdmlzdWFsaXphdGlvbi4gSW4gdGhpcyB0dXRvcmlhbCwgd2Ugd2lsbCBkZW1vbnN0cmF0ZSBuZXcgY2FwYWJpbGl0aWVzIHRvIGludGVncmF0ZSBDeXRvc2NhcGUgaW50byBwcm9ncmFtbWF0aWMgd29ya2Zsb3dzIGFuZCBwaXBlbGluZXMgdXNpbmcgUi4gV2Ugd2lsbCBsb29rIGF0IHR3byB1c2UgY2FzZXM7IHRoZSBmaXJzdCBleHBsb3JpbmcgaG93IHRvcCBzY29yaW5nIGdlbmVzIGFyZSByZWxhdGVkIGFuZCBob3cgdG8gb3ZlcmxheSBkYXRhOyB0aGUgc2Vjb25kIGxvb2tpbmcgYXQgZ2VuZXMgd2l0aCBzaW1pbGFyIGV4cHJlc3Npb24gYW5kIHRoZWlyIGZ1bmN0aW9uYWwgZW5yaWNobWVudC4gCgojIEluc3RhbGxhdGlvbgpgYGB7cn0KaWYoISJSQ3kzIiAlaW4lIGluc3RhbGxlZC5wYWNrYWdlcygpKXsKICAgIGluc3RhbGwucGFja2FnZXMoIkJpb2NNYW5hZ2VyIikKICAgIEJpb2NNYW5hZ2VyOjppbnN0YWxsKCJSQ3kzIikKfQpsaWJyYXJ5KFJDeTMpCgppZighIlJDb2xvckJyZXdlciIgJWluJSBpbnN0YWxsZWQucGFja2FnZXMoKSl7CiAgICBpbnN0YWxsLnBhY2thZ2VzKCJSQ29sb3JCcmV3ZXIiKQp9CmxpYnJhcnkoUkNvbG9yQnJld2VyKQpgYGAKCiMgUmVxdWlyZWQgU29mdHdhcmUKVGhlIHdob2xlIHBvaW50IG9mIFJDeTMgaXMgdG8gY29ubmVjdCB3aXRoIEN5dG9zY2FwZS4gWW91IHdpbGwgbmVlZCB0byBpbnN0YWxsIGFuZCBsYXVuY2ggQ3l0b3NjYXBlOiAKICAgIAoqIERvd25sb2FkIHRoZSBsYXRlc3QgQ3l0b3NjYXBlIGZyb20gaHR0cDovL3d3dy5jeXRvc2NhcGUub3JnL2Rvd25sb2FkLnBocAoqIENvbXBsZXRlIGluc3RhbGxhdGlvbiB3aXphcmQKKiBMYXVuY2ggQ3l0b3NjYXBlIAoKKipNYWtlIHN1cmUgdGhhdCBDeXRvc2NhcGUgaXMgcnVubmluZyoqCmBgYHtyIGV2YWw9RkFMU0V9CmN5dG9zY2FwZVBpbmcgKCkKYGBgCgpgYGB7ciBldmFsPUZBTFNFfQpjeXRvc2NhcGVWZXJzaW9uSW5mbyAoKQpgYGAKClRvIHNlZSBhbGwgdGhlIGZ1bmN0aW9ucyBhdmFpbGFibGUgaW4gdGhlIFJDeTMgcGFja2FnZTogCmBgYHtyfQpoZWxwKHBhY2thZ2U9UkN5MykKYGBgCgpBbHNvIGluc3RhbGwgYWRkaXRpb25hbCBDeXRvc2NhcGUgYXBwcyB0aGF0IHdpbGwgYmUgdXNlZCBpbiB0aGlzIHR1dG9yaWFsOgpgYGB7cn0KI2F2YWlsYWJsZSBpbiBDeXRvc2NhcGUgMy43LjAgYW5kIGFib3ZlCmluc3RhbGxBcHAoJ1NUUklOR2FwcCcpICAKaW5zdGFsbEFwcCgnYU1hdFJlYWRlcicpCmluc3RhbGxBcHAoJ2NsdXN0ZXJNYWtlcjInKQpgYGAKCiMgRXhhbXBsZSBEYXRhIFNldApXZSBkb3dubG9hZGVkIGdlbmUgZXhwcmVzc2lvbiBkYXRhIGZyb20gdGhlIE92YXJpYW4gU2Vyb3VzIEN5c3RhZGVub2NhcmNpbm9tYSBwcm9qZWN0IG9mIFRoZSBDYW5jZXIgR2Vub21lIEF0bGFzIChUQ0dBKSwgaHR0cDovL2NhbmNlcmdlbm9tZS5uaWguZ292IHZpYSB0aGUgR2Vub21pYyBEYXRhIENvbW1vbnMgKEdEQykgcG9ydGFsIG9uIDIwMTctMDYtMTQgdXNpbmcgVENHQUJpb2xpbmtzIFIgcGFja2FnZS4gVGhlIGRhdGEgaW5jbHVkZXMgMzAwIHNhbXBsZXMgYXZhaWxhYmxlIGFzIFJOQS1zZXEgZGF0YSwgd2l0aCByZWFkcyBtYXBwZWQgdG8gYSByZWZlcmVuY2UgZ2Vub21lIHVzaW5nIE1hcFNwbGljZSBhbmQgcmVhZCBjb3VudHMgcGVyIHRyYW5zY3JpcHQgZGV0ZXJtaW5lZCB1c2luZyB0aGUgUlNFTSBtZXRob2QuIFJOQS1zZXEgZGF0YSBhcmUgbGFiZWxlZCBhcyDigJhSTkEtU2VxIFYy4oCZLCBzZWUgZGV0YWlscyBhdDogaHR0cHM6Ly93aWtpLm5jaS5uaWguZ292L2Rpc3BsYXkvVENHQS9STkFTZXErVmVyc2lvbisyKS4gVGhlIFJOQS1TZXFWMiBkYXRhIGNvbnNpc3RzIG9mIHJhdyBjb3VudHMgc2ltaWxhciB0byByZWd1bGFyIFJOQS1zZXEgYnV0IFJTRU0gKFJOQS1TZXEgYnkgRXhwZWN0YXRpb24gTWF4aW1pemF0aW9uKSBkYXRhIGNhbiBiZSB1c2VkIHdpdGggdGhlIGVkZ2VSIG1ldGhvZC4gVGhlIGV4cHJlc3Npb24gZGF0YXNldCBvZiAzMDAgdHVtb3Vycywgd2l0aCA3OSBjbGFzc2lmaWVkIGFzIEltbXVub3JlYWN0aXZlLCA3MiBjbGFzc2lmaWVkIGFzIE1lc2VuY2h5bWFsLCA2OSBjbGFzc2lmaWVkIGFzIERpZmZlcmVudGlhdGVkLCBhbmQgODAgY2xhc3NpZmllZCBhcyBQcm9saWZlcmF0aXZlIHNhbXBsZXMgKGNsYXNzIGRlZmluaXRpb25zIHdlcmUgb2J0YWluZWQgZnJvbSBWZXJoYWFrIGV0IGFsLiBTdXBwbGVtZW50YXJ5IFRhYmxlIDEsIHRoaXJkIGNvbHVtbikuIFJOQS1zZXEgcmVhZCBjb3VudHMgd2VyZSBjb252ZXJ0ZWQgdG8gQ1BNIHZhbHVlcyBhbmQgZ2VuZXMgd2l0aCBDUE0gPiAxIGluIGF0IGxlYXN0IDUwIG9mIHRoZSBzYW1wbGVzIGFyZSByZXRhaW5lZCBmb3IgZnVydGhlciBzdHVkeSAoNTAgaXMgdGhlIG1pbmltYWwgc2FtcGxlIHNpemUgaW4gdGhlIGNsYXNzZXMpLiBUaGUgZGF0YSB3YXMgbm9ybWFsaXplZCBhbmQgZGlmZmVyZW50aWFsIGV4cHJlc3Npb24gd2FzIGNhbGN1bGF0ZWQgZm9yIGVhY2ggY2FuY2VyIGNsYXNzIHJlbGF0aXZlIHRvIHRoZSByZXN0IG9mIHRoZSBzYW1wbGVzLiAKClRoZXJlIGFyZSB0d28gZGF0YSBmaWxlczoKIDEuIEV4cHJlc3Npb24gbWF0cml4IC0gY29udGFpbmluZyB0aGUgbm9ybWFsaXplZCBleHByZXNzaW9uIGZvciBlYWNoIGdlbmUgYWNyb3NzIGFsbCAzMDAgc2FtcGxlcy4KIDEuIEdlbmUgcmFua3MgLSBjb250YWluaW5nIHRoZSBwLXZhbHVlcywgRkRSIGFuZCBmb2xkY2hhbmdlIHZhbHVlcyBmb3IgdGhlIDQgY29tcGFyaXNvbnMgKG1lc2VuY2h5bWFsIHZzIHJlc3QsIGRpZmZlcmVudGlhbCB2cyByZXN0LCBwcm9saWZlcmF0aXZlIHZzIHJlc3QgYW5kIGltbXVub3JlYWN0aXZlIHZzIHJlc3QpCgpUaGUgZm9sbG93aW5nIHNjcmlwdCB3aWxsIGRvd25sb2FkIGFuZCBleHBvcnQgZmlsZXMgdG8gdGhlIHNhbWUgZGlyZWN0b3J5IGFzIHRoaXMgY29weSBvZiB0aGUgUm1kIGZpbGU6CmBgYHtyIGV2YWw9RkFMU0V9CmdldHdkKCkKYGBgCgpgYGB7cn0KI2xvYWQgZGF0YSBmaWxlcwpSTkFTZXFfZXhwcmVzc2lvbl9tYXRyaXggPC0gcmVhZC50YWJsZSgiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL2N5dG9zY2FwZS9jeXRvc2NhcGUtdHV0b3JpYWxzL2doLXBhZ2VzL3ByZXNlbnRhdGlvbnMvbW9kdWxlcy9SQ3kzX0V4YW1wbGVEYXRhL2RhdGEvVENHQV9PVl9STkFzZXFfZXhwcmVzc2lvbi50eHQiLCBoZWFkZXIgPSBUUlVFLCBzZXAgPSAiXHQiLCBxdW90ZT0iXCIiLCBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UpCgpSTkFTZXFfZ2VuZV9zY29yZXMgPC0gcmVhZC50YWJsZSgiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL2N5dG9zY2FwZS9jeXRvc2NhcGUtdHV0b3JpYWxzL2doLXBhZ2VzL3ByZXNlbnRhdGlvbnMvbW9kdWxlcy9SQ3kzX0V4YW1wbGVEYXRhL2RhdGEvVENHQV9PVl9STkFzZXFfQWxsX2VkZ2VSX3Njb3Jlcy50eHQiLCBoZWFkZXIgPSBUUlVFLCBzZXAgPSAiXHQiLCBxdW90ZT0iXCIiLCBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UpCmBgYAoKIyBVc2UgQ2FzZSAxIC0gSG93IGFyZSBteSB0b3AgZ2VuZXMgcmVsYXRlZD8KCiMjIyBHZXQgdG9wLXNjb3JpbmcgZ2VuZSBnZW5lcyBmcm9tIHRoZSBkYXRhCkdldCBhIHN1YnNldCBvZiBzaWduaWZpY2FudCwgdG9wLXNjb3JpbmcgZ2VuZXMgZnJvbSBvdXIgZGF0YToKYGBge3J9CnRvcF9tZXNlbmNoeW1hbF9nZW5lcyA8LSBSTkFTZXFfZ2VuZV9zY29yZXNbd2hpY2goUk5BU2VxX2dlbmVfc2NvcmVzJEZEUi5tZXNlbiA8IDAuMDUgJiBSTkFTZXFfZ2VuZV9zY29yZXMkbG9nRkMubWVzZW4gPiAyKSxdCmhlYWQodG9wX21lc2VuY2h5bWFsX2dlbmVzKQpgYGAKCiMjIyBRdWVyeSBTVFJJTkcgZGF0YWJhc2UgZm9yIHRvcC1zY29yaW5nIGdlbmVzCldlIGFyZSBnb2luZyB0byBxdWVyeSB0aGUgU1RSSU5HIERhdGFiYXNlIHRvIGdldCBhbGwgaW50ZXJhY3Rpb25zIGZvdW5kIGZvciBvdXIgc2V0IG9mIHRvcCBNZXNlbmNoeW1hbCBnZW5lcy4KClJlbWluZGVyOiB0byBzZWUgdGhlIHBhcmFtZXRlcnMgcmVxdWlyZWQgYnkgdGhlIHN0cmluZyBmdW5jdGlvbiBvciB0byBmaW5kIHRoZSByaWdodCBzdHJpbmcgZnVuY3Rpb24geW91IGNhbiB1c2UgY29tbWFuZHNIZWxwLgpgYGB7ciBldmFsPUZBTFNFfQpjb21tYW5kc0hlbHAoImhlbHAgc3RyaW5nIikKYGBgCgpgYGB7ciBldmFsPUZBTFNFfQpjb21tYW5kc0hlbHAoImhlbHAgc3RyaW5nIHByb3RlaW4gcXVlcnkiKQpgYGAKCmBgYHtyIGV2YWw9RkFMU0V9Cm1lc2VuX3N0cmluZ19pbnRlcmFjdGlvbl9jbWQgPC0gcGFzdGUoJ3N0cmluZyBwcm90ZWluIHF1ZXJ5IHRheG9uSUQ9OTYwNiBsaW1pdD0xNTAgY3V0b2ZmPTAuOSBxdWVyeT0iJyxwYXN0ZSh0b3BfbWVzZW5jaHltYWxfZ2VuZXMkTmFtZSwgY29sbGFwc2U9IiwiKSwnIicsc2VwPSIiKQpjb21tYW5kc1BPU1QobWVzZW5fc3RyaW5nX2ludGVyYWN0aW9uX2NtZCkKYGBgCgpMYXlvdXQgdGhlIG5ldHdvcms6CmBgYHtyIGV2YWw9RkFMU0V9CmxheW91dE5ldHdvcmsoJ2ZvcmNlLWRpcmVjdGVkJykKYGBgCgpDaGVjayB3aGF0IG90aGVyIGxheW91dCBhbGdvcml0aG1zIGFyZSBhdmFpbGFibGUgdG8gdHJ5IG91dDoKYGBge3IgZXZhbD1GQUxTRX0KZ2V0TGF5b3V0TmFtZXMoKQpgYGAKCkdldCB0aGUgcGFyYW1ldGVycyBmb3IgYSBzcGVjaWZpYyBsYXlvdXQ6CmBgYHtyIGV2YWw9RkFMU0V9CmdldExheW91dFByb3BlcnR5TmFtZXMobGF5b3V0Lm5hbWU9J2ZvcmNlLWRpcmVjdGVkJykKYGBgCgpSZS1sYXlvdXQgdGhlIG5ldHdvcmsgdXNpbmcgdGhlIGZvcmNlIGRpcmVjdGVkIGxheW91dCBidXQgc3BlY2lmeSBzb21lIG9mIHRoZSBwYXJhbWV0ZXJzOgpgYGB7ciBldmFsPUZBTFNFfQpsYXlvdXROZXR3b3JrKCdmb3JjZS1kaXJlY3RlZCBkZWZhdWx0U3ByaW5nQ29lZmZpY2llbnQ9MC4wMDAwMDEgZGVmYXVsdFNwcmluZ0xlbmd0aD01MCcpCmBgYAoKIyMjIE92ZXJsYXkgZXhwcmVzc2lvbiBkYXRhCk5vdyB3ZSBjYW4gb3ZlcmxheSBvdXIgZXhwcmVzc2lvbiBkYXRhIG9uIHRoZSBTdHJpbmcgbmV0d29yay4gVG8gZG8gdGhpcyB3ZSB3aWxsIGJlIHVzaW5nIHRoZSBsb2FkVGFibGVEYXRhIGZ1bmN0aW9uIGZyb20gUkN5My4gSXQgaXMgaW1wb3J0YW50IHRvIG1ha2Ugc3VyZSB0aGF0ICB0aGF0IHlvdXIgaWRlbnRpZmllcnMgdHlwZXMgbWF0Y2ggdXAuIFlvdSBjYW4gY2hlY2sgd2hhdCBpcyB1c2VkIGJ5IFN0cmluZyBieSBwdWxsaW5nIGluIHRoZSBjb2x1bW4gbmFtZXMgb2YgdGhlIG5vZGUgYXR0cmlidXRlIHRhYmxlLgpgYGB7ciBldmFsPUZBTFNFfQpnZXRUYWJsZUNvbHVtbk5hbWVzKCdub2RlJykKYGBgCgpJZiB5b3UgYXJlIHVuc3VyZSBvZiB3aGF0IGVhY2ggY29sdW1uIGlzIGFuZCB3YW50IHRvIGZ1cnRoZXIgdmVyaWZ5IHRoZSBjb2x1bW4gdG8gdXNlIHlvdSBjYW4gYWxzbyBwdWxsIGluIHRoZSBlbnRpcmUgbm9kZSBhdHRyaWJ1dGUgdGFibGU6CmBgYHtyIGV2YWw9RkFMU0V9Cm5vZGVfYXR0cmlidXRlX3RhYmxlX3RvcG1lc2VuIDwtIGdldFRhYmxlQ29sdW1ucyh0YWJsZT0ibm9kZSIpCmhlYWQobm9kZV9hdHRyaWJ1dGVfdGFibGVfdG9wbWVzZW5bLDM6N10pCmBgYAoKVGhlIGNvbHVtbiAiZGlzcGxheSBuYW1lIiBjb250YWlucyBIR05DIGdlbmUgbmFtZXMgd2hpY2ggYXJlIGFsc28gZm91bmQgaW4gb3VyIE92YXJpYW4gQ2FuY2VyIGRhdGFzZXQuCgpUbyBpbXBvcnQgb3VyIGV4cHJlc3Npb24gZGF0YSB3ZSB3aWxsIG1hdGNoIG91ciBkYXRhc2V0IHRvIHRoZSAiZGlzcGxheSBuYW1lIiBub2RlIGF0dHJpYnV0ZToKYGBge3IgZXZhbD1GQUxTRX0KP2xvYWRUYWJsZURhdGEKCmxvYWRUYWJsZURhdGEoUk5BU2VxX2dlbmVfc2NvcmVzLHRhYmxlLmtleS5jb2x1bW4gPSAiZGlzcGxheSBuYW1lIixkYXRhLmtleS5jb2x1bW4gPSAiTmFtZSIpICAjZGVmYXVsdCBkYXRhLmZyYW1lIGtleSBpcyByb3cubmFtZXMKYGBgCgojIyMgTW9kaWZ5IHRoZSB2aXN1YWwgc3R5bGUKQ3JlYXRlIHlvdXIgb3duIHZpc3VhbCBzdHlsZSB0byB2aXN1YWxpemUgeW91ciBleHByZXNzaW9uIGRhdGEgb24gdGhlIFN0cmluZyBuZXR3b3JrLiAKClN0YXJ0IHdpdGggYSBkZWZhdWx0IHN0eWxlOgpgYGB7ciBldmFsPUZBTFNFfQpzdHlsZS5uYW1lID0gIk1lc2VuY2h5bWFsU3R5bGUiCmRlZmF1bHRzLmxpc3QgPC0gbGlzdChOT0RFX1NIQVBFPSJlbGxpcHNlIiwKICAgICAgICAgICAgICAgICBOT0RFX1NJWkU9NjAsCiAgICAgICAgICAgICAgICAgTk9ERV9GSUxMX0NPTE9SPSIjQUFBQUFBIiwKICAgICAgICAgICAgICAgICBFREdFX1RSQU5TUEFSRU5DWT0xMjApCm5vZGUubGFiZWwubWFwIDwtIG1hcFZpc3VhbFByb3BlcnR5KCdub2RlIGxhYmVsJywnZGlzcGxheSBuYW1lJywncCcpICMgcCBmb3IgcGFzc3Rocm91Z2g7IG5vdGhpbmcgZWxzZSBuZWVkZWQKY3JlYXRlVmlzdWFsU3R5bGUoc3R5bGUubmFtZSwgZGVmYXVsdHMubGlzdCwgbGlzdChub2RlLmxhYmVsLm1hcCkpCnNldFZpc3VhbFN0eWxlKHN0eWxlLm5hbWU9c3R5bGUubmFtZSkKYGBgCgpVcGRhdGUgeW91ciBjcmVhdGVkIHN0eWxlIHdpdGggYSBtYXBwaW5nIGZvciB0aGUgTWVzZW5jaHltYWwgbG9nRkMgZXhwcmVzc2lvbi4gVGhlIGZpcnN0IHN0ZXAgaXMgdG8gZ3JhYiB0aGUgY29sdW1uIGRhdGEgZnJvbSBDeXRvc2NhcGUgKHdlIGNhbiByZXVzZSB0aGUgbm9kZV9hdHRyaWJ1dGUgdGFibGUgY29uY2VwdCBmcm9tIGFib3ZlIGJ1dCB3ZSBoYXZlIHRvIGNhbGwgdGhlIGZ1bmN0aW9uIGFnYWluIGFzIHdlIGhhdmUgc2luY2UgYWRkZWQgb3VyIGV4cHJlc3Npb24gZGF0YSkgYW5kIHB1bGwgb3V0IHRoZSBtaW4gYW5kIG1heCB0byBkZWZpbmUgb3VyIGRhdGEgbWFwcGluZyByYW5nZSBvZiB2YWx1ZXMuCgoqKk5vdGUqKjogeW91IGNvdWxkIGRlZmluZSB0aGUgbWluIGFuZCBtYXggYmFzZWQgb24gdGhlIGVudGlyZSBkYXRhc2V0IG9yIGp1c3QgdGhlIHN1YnNldCB0aGF0IGlzIHJlcHJlc2VudGVkIGluIEN5dG9zY2FwZSBjdXJyZW50bHkuIFRoZSB0d28gbWV0aG9kcyB3aWxsIGdpdmUgeW91IGRpZmZlcmVudCByZXN1bHRzLiBJZiB5b3UgaW50ZW5kIG9uIGNvbXBhcmluZyBkaWZmZXJlbnQgbmV0d29ya3MgY3JlYXRlZCB3aXRoIHRoZSBzYW1lIGRhdGFzZXQgdGhlbiBpdCBpcyBiZXN0IHRvIGNhbGN1bGF0ZSB0aGUgbWluIGFuZCBtYXggZnJvbSB0aGUgZW50aXJlIGRhdGFzZXQgYXMgb3Bwb3NlZCB0byBhIHN1YnNldC4gCmBgYHtyfQptaW4ubWVzZW4ubG9nZmMgPSBtaW4oUk5BU2VxX2dlbmVfc2NvcmVzJGxvZ0ZDLm1lc2VuLG5hLnJtPVRSVUUpCm1heC5tZXNlbi5sb2dmYyA9IG1heChSTkFTZXFfZ2VuZV9zY29yZXMkbG9nRkMubWVzZW4sbmEucm09VFJVRSkKZGF0YS52YWx1ZXMgPSBjKG1pbi5tZXNlbi5sb2dmYywwLG1heC5tZXNlbi5sb2dmYykKYGBgCgpOZXh0LCB3ZSB1c2UgdGhlIFJDb2xvckJyZXdlciBwYWNrYWdlIHRvIGhlbHAgdXMgcGljayBnb29kIGNvbG9ycyB0byBwYWlyIHdpdGggb3VyIGRhdGEgdmFsdWVzOgpgYGB7cn0KbGlicmFyeShSQ29sb3JCcmV3ZXIpCmRpc3BsYXkuYnJld2VyLmFsbChsZW5ndGgoZGF0YS52YWx1ZXMpLCBjb2xvcmJsaW5kRnJpZW5kbHk9VFJVRSwgdHlwZT0iZGl2IikgIyBkaXYscXVhbCxzZXEsYWxsCm5vZGUuY29sb3JzIDwtIGMocmV2KGJyZXdlci5wYWwobGVuZ3RoKGRhdGEudmFsdWVzKSwgIlJkQnUiKSkpCmBgYAoKTWFwIHRoZSBjb2xvcnMgdG8gb3VyIGRhdGEgdmFsdWUgYW5kIHVwZGF0ZSBvdXIgdmlzdWFsIHN0eWxlOgpgYGB7ciBldmFsPUZBTFNFfSAKc2V0Tm9kZUNvbG9yTWFwcGluZygibG9nRkMubWVzZW4iLCBkYXRhLnZhbHVlcywgbm9kZS5jb2xvcnMsIHN0eWxlLm5hbWU9c3R5bGUubmFtZSkKYGBgCgpSZW1lbWJlciwgU3RyaW5nIGluY2x1ZGVzIHlvdXIgcXVlcnkgcHJvdGVpbnMgYXMgd2VsbCBhcyBvdGhlciBwcm90ZWlucyB0aGF0IGFzc29jaWF0ZSB3aXRoIHlvdXIgcXVlcnkgcHJvdGVpbnMgKGluY2x1ZGluZyB0aGUgc3Ryb25nZXN0IGNvbm5lY3Rpb24gZmlyc3QpLiBOb3QgYWxsIG9mIHRoZSBwcm90ZWlucyBpbiB0aGlzIG5ldHdvcmsgYXJlIHlvdXIgdG9wIGhpdHMuIEhvdyBjYW4gd2UgdmlzdWFsaXplIHdoaWNoIHByb3RlaW5zIGFyZSBvdXIgdG9wIE1lc2VuY2h5bWFsIGhpdHM/CgpDaGFuZ2UgdGhlIG5vZGUgc2hhcGUgZm9yIG91ciB0b3AgaGl0czoKYGBge3IgZXZhbD1GQUxTRX0KZ2V0Tm9kZVNoYXBlcygpCgojIHNlbGVjdCBub2RlcyBieSAiZGlzcGxheSBuYW1lIiBjb2x1bW4Kc2VsZWN0Tm9kZXModG9wX21lc2VuY2h5bWFsX2dlbmVzJE5hbWUsICJkaXNwbGF5IG5hbWUiKQpzZXROb2RlU2hhcGVCeXBhc3Mobm9kZS5uYW1lcyA9IGdldFNlbGVjdGVkTm9kZXMoKSwgbmV3LnNoYXBlcyA9ICJUUklBTkdMRSIpCmNsZWFyU2VsZWN0aW9uKCkKYGBgCgpDaGFuZ2UgdGhlIHNpemUgb2YgdGhlIG5vZGUgdG8gYmUgY29ycmVsYXRlZCB3aXRoIHRoZSBNZXNlbmNoeW1hbCBwLXZhbHVlOgpgYGB7ciBldmFsPUZBTFNFfQpzZXROb2RlU2l6ZU1hcHBpbmcodGFibGUuY29sdW1uID0gJ0xSLm1lc2VuJywgCiAgICAgICAgICAgICAgICAgICB0YWJsZS5jb2x1bW4udmFsdWVzID0gYyhtaW4oUk5BU2VxX2dlbmVfc2NvcmVzJExSLm1lc2VuKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtZWFuKFJOQVNlcV9nZW5lX3Njb3JlcyRMUi5tZXNlbiksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWF4KFJOQVNlcV9nZW5lX3Njb3JlcyRMUi5tZXNlbikpLCAKICAgICAgICAgICAgICAgICAgIHNpemVzID0gYygzMCwgNjAsIDE1MCksbWFwcGluZy50eXBlID0gImMiLCBzdHlsZS5uYW1lID0gc3R5bGUubmFtZSkKYGBgCgpHZXQgYSBzY3JlZW5zaG90IG9mIHRoZSByZXN1bHRpbmcgbmV0d29yazoKYGBge3IgbWVzZW5fc3RyaW5nX25ldHdvcmtfc2NyZWVuc2hvdCwgaW5jbHVkZT1UUlVFfQptZXNlbl9zdHJpbmdfbmV0d29ya19wbmdfZmlsZV9uYW1lIDwtICJtZXNlbl9zdHJpbmdfbmV0d29yay5wbmciCmBgYAoKYGBge3IgZXZhbD1GQUxTRX0KaWYoZmlsZS5leGlzdHMobWVzZW5fc3RyaW5nX25ldHdvcmtfcG5nX2ZpbGVfbmFtZSkpewogICNjeXRvc2NhcGUgaGFuZ3Mgd2FpdGluZyBmb3IgdXNlciByZXNwb25zZSBpZiBmaWxlIGFscmVhZHkgZXhpc3RzLiAgUmVtb3ZlIGl0IGZpcnN0CiAgcmVzcG9uc2U8LSBmaWxlLnJlbW92ZShtZXNlbl9zdHJpbmdfbmV0d29ya19wbmdfZmlsZV9uYW1lKQogIH0gCnJlc3BvbnNlIDwtIGV4cG9ydEltYWdlKG1lc2VuX3N0cmluZ19uZXR3b3JrX3BuZ19maWxlX25hbWUsIHR5cGUgPSAicG5nIikKYGBgCgojIFVzZSBDYXNlIDIgLSBXaGljaCBnZW5lcyBoYXZlIHNpbWlsYXIgZXhwcmVzc2lvbj8KCkluc3RlYWQgb2YgcXVlcnlpbmcgZXhpc3RpbmcgcmVzb3VyY2VzIGxvb2sgZm9yIGNvcnJlbGF0aW9ucyBpbiB5b3VyIG93biBkYXRhc2V0IHRvIGZpbmQgb3V0IHdoaWNoIGdlbmVzIGhhdmUgc2ltaWxhciBleHByZXNzaW9uLiBUaGVyZSBhcmUgbWFueSB0b29scyB0aGF0IGNhbiBhbmFseXplIHlvdXIgZGF0YSBmb3IgY29ycmVsYXRpb24uIEEgcG9wdWxhciB0b29sIGlzIFdlaWdodGVkIEdlbmUgQ29ycmVsYXRpb24gTmV0d29yayBBbmFseXNpcyAoV0dDTkEpIHdoaWNoIHRha2VzIGV4cHJlc3Npb24gZGF0YSBhbmQgY2FsY3VsYXRlcyBmdW5jdGlvbmFsIG1vZHVsZXMuIEFzIGEgc2ltcGxlIGV4YW1wbGUgd2UgY2FuIHRyYW5zZm9ybSBvdXIgZXhwcmVzc2lvbiBkYXRhc2V0IGludG8gYSBjb3JyZWxhdGlvbiBtYXRyaXguICAKCiMjIyBDcmVhdGUgY29ycmVsYXRpb24gbWF0cml4ClVzaW5nIHRoZSBDeXRvc2NhcGUgQXBwLCBhTWF0UmVhZGVyLCB3ZSB0cmFuc2Zvcm0gb3VyIGFkamFjZW5jeSBtYXRyaXggaW50byBhbiBpbnRlcmFjdGlvbiBuZXR3b3JrLiBGaXJzdCB3ZSBmaWx0ZXIgdGhlIGNvcnJlbGF0aW9uIG1hdHJpeCB0byBjb250YWluIG9ubHkgdGhlIHN0cm9uZ2VzdCBjb25uZWN0aW9ucyAoZm9yIGV4YW1wbGUsIG9ubHkgY29ycmVsYXRpb25zIGdyZWF0ZXIgdGhhbiAwLjkpLiAKYGBge3J9ClJOQVNlcV9leHByZXNzaW9uIDwtIFJOQVNlcV9leHByZXNzaW9uX21hdHJpeFssMzpuY29sKFJOQVNlcV9leHByZXNzaW9uX21hdHJpeCldCgpyb3duYW1lcyhSTkFTZXFfZXhwcmVzc2lvbikgPC0gUk5BU2VxX2V4cHJlc3Npb25fbWF0cml4JE5hbWUKUk5Bc2VxX2NvcnJlbGF0aW9uX21hdHJpeCA8LSBjb3IodChSTkFTZXFfZXhwcmVzc2lvbiksIG1ldGhvZD0icGVhcnNvbiIpICNOb3RlOiB0aGlzIHRha2VzIGEgd2hpbGUKCiNzZXQgdGhlIGRpYWdvbmFsIG9mIG1hdHJpeCB0byB6ZXJvIC0gZWxpbWluYXRlIHNlbGYtY29ycmVsYXRpb24KUk5Bc2VxX2NvcnJlbGF0aW9uX21hdHJpeFsgCiAgcm93KFJOQXNlcV9jb3JyZWxhdGlvbl9tYXRyaXgpID09IGNvbChSTkFzZXFfY29ycmVsYXRpb25fbWF0cml4KSBdIDwtIDAKCiMgc2V0IGFsbCBjb3JyZWxhdGlvbnMgdGhhdCBhcmUgbGVzcyB0aGFuIDAuOSB0byB6ZXJvClJOQXNlcV9jb3JyZWxhdGlvbl9tYXRyaXhbd2hpY2goUk5Bc2VxX2NvcnJlbGF0aW9uX21hdHJpeDwwLjkwKV0gPC0gMAoKI2dldCByaWQgb2Ygcm93cyBhbmQgY29sdW1ucyB0aGF0IGhhdmUgbm8gY29ycmVsYXRpb25zIHdpdGggdGhlIGFib3ZlIHRocmVzaG9sZHMKUk5Bc2VxX2NvcnJlbGF0aW9uX21hdHJpeCA8LSBSTkFzZXFfY29ycmVsYXRpb25fbWF0cml4W3doaWNoKHJvd1N1bXMoUk5Bc2VxX2NvcnJlbGF0aW9uX21hdHJpeCkgIT0gMCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgd2hpY2goY29sU3VtcyhSTkFzZXFfY29ycmVsYXRpb25fbWF0cml4KSAhPTApXQoKI3dyaXRlIG91dCB0aGUgY29ycmVsYXRpb24gZmlsZQpjb3JyZWxhdGlvbl9maWxlbmFtZSA8LSBmaWxlLnBhdGgoZ2V0d2QoKSwgIlRDR0FfT1ZfUk5Bc2VxX2V4cHJlc3Npb25fY29ycmVsYXRpb25fbWF0cml4LnR4dCIpIAp3cml0ZS50YWJsZShSTkFzZXFfY29ycmVsYXRpb25fbWF0cml4LCAgZmlsZSA9IGNvcnJlbGF0aW9uX2ZpbGVuYW1lLCBjb2wubmFtZXMgID0gVFJVRSwgcm93Lm5hbWVzID0gRkFMU0UsIHNlcCA9ICJcdCIsIHF1b3RlPUZBTFNFKQoKYGBgCgojIyMgQ3JlYXRlIG5ldHdvcmsgZnJvbSBjb3JyZWxhdGlvbiBtYXRyaXgKVXNlIHRoZSBDeVJlc3QgY2FsbCB0byBhY2Nlc3MgdGhlIGFNYXRSZWFkZXIgZnVuY3Rpb25hbGl0eToKYGBge3IgZXZhbD1GQUxTRX0KYW1hdF91cmwgPC0gImFNYXRSZWFkZXIvdjEvaW1wb3J0IgphbWF0X3BhcmFtcyA9IGxpc3QoZmlsZXMgPSBsaXN0KGNvcnJlbGF0aW9uX2ZpbGVuYW1lKSwKICAgICAgICAgICAgICAgICAgIGRlbGltaXRlciA9ICJUQUIiLAogICAgICAgICAgICAgICAgICAgdW5kaXJlY3RlZCA9IFRSVUUsCiAgICAgICAgICAgICAgICAgICBpZ25vcmVaZXJvcyA9IFRSVUUsCiAgICAgICAgICAgICAgICAgICBpbnRlcmFjdGlvbk5hbWUgPSAiY29ycmVsYXRlZCB3aXRoIiwKICAgICAgICAgICAgICAgICAgIHJvd05hbWVzID0gRkFMU0UKICAgICAgICAgICAgICAgICAgICkKIApyZXNwb25zZSA8LSBjeXJlc3RQT1NUKG9wZXJhdGlvbiA9IGFtYXRfdXJsLCBib2R5ID0gYW1hdF9wYXJhbXMsIGJhc2UudXJsID0gImh0dHA6Ly9sb2NhbGhvc3Q6MTIzNCIpCgpjdXJyZW50X25ldHdvcmtfaWQgPC0gcmVzcG9uc2UkZGF0YVsic3VpZCJdCmBgYAoKYGBge3IgZXZhbD1GQUxTRX0KI3JlbGF5b3V0IG5ldHdvcmsKbGF5b3V0TmV0d29yaygnY29zZScsCiAgICAgICAgICAgICAgbmV0d29yayA9IGFzLm51bWVyaWMoY3VycmVudF9uZXR3b3JrX2lkKSkKYGBgCgpgYGB7ciBldmFsPUZBTFNFfQpyZW5hbWVOZXR3b3JrKHRpdGxlID0iQ29leHByZXNzaW9uX25ldHdvcmtfcGVhcjBfOTUiLAogICAgICAgICAgICAgIG5ldHdvcmsgPSBhcy5udW1lcmljKGN1cnJlbnRfbmV0d29ya19pZCkpCmBgYAoKIyMjIE1vZGlmeSB0aGUgdmlzdWFsIHN0eWxlCk1vZGlmeSB0aGUgdmlzdWFsaXphdGlvbiB0byBzZWUgd2hlcmUgZWFjaCBnZW5lcyBpcyBwcmVkb21pbmFudGx5IGV4cHJlc3NlZC4gTG9vayBhdCB0aGUgNCBkaWZmZXJlbnQgcC12YWx1ZXMgYXNzb2NpYXRlZCB3aXRoIGVhY2ggZ2VuZSBhbmQgY29sb3IgdGhlIG5vZGVzIHdpdGggdGhlIHR5cGUgYXNzb2NpYXRlZCB3aXRoIHRoZSBsb3dlc3QgRkRSLgoKTG9hZCBpbiB0aGUgc2NvcmluZyBkYXRhLiBTcGVjaWZ5IHRoZSBjYW5jZXIgdHlwZSB3aGVyZSB0aGUgZ2VuZXMgaGFzIHRoZSBsb3dlc3QgRkRSIHZhbHVlOgpgYGB7cn0Kbm9kZXNfaW5fbmV0d29yayA8LSByb3duYW1lcyhSTkFzZXFfY29ycmVsYXRpb25fbWF0cml4KQoKI2FkZCBhbiBhZGRpdGlvbmFsIGNvbHVtbiB0byB0aGUgZ2VuZSBzY29yZXMgdGFibGUgdG8gaW5kaWNhdGUgaW4gd2hpY2ggc2FtcGxlcwojIHRoZSBnZW5lIGlzIHNpZ25pZmljYW50Cm5vZGVfY2xhc3MgPC0gdmVjdG9yKGxlbmd0aCA9IGxlbmd0aChub2Rlc19pbl9uZXR3b3JrKSxtb2RlID0gImNoYXJhY3RlciIpCmZvcihpIGluIDE6bGVuZ3RoKG5vZGVzX2luX25ldHdvcmspKXsKICBjdXJyZW50X3JvdyA8LSB3aGljaChSTkFTZXFfZ2VuZV9zY29yZXMkTmFtZSA9PSBub2Rlc19pbl9uZXR3b3JrW2ldKQogIG1pbl9wdmFsdWUgPC0gbWluKFJOQVNlcV9nZW5lX3Njb3Jlc1tjdXJyZW50X3JvdywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZ3JlcChjb2xuYW1lcyhSTkFTZXFfZ2VuZV9zY29yZXMpLCBwYXR0ZXJuID0gIkZEUiIpXSkKICBpZihSTkFTZXFfZ2VuZV9zY29yZXMkRkRSLm1lc2VuW2N1cnJlbnRfcm93XSA8PW1pbl9wdmFsdWUpewogICAgbm9kZV9jbGFzc1tpXSA8LSBwYXN0ZShub2RlX2NsYXNzW2ldLCJtZXNlbiIsc2VwID0gIiAiKQogIH0KICBpZihSTkFTZXFfZ2VuZV9zY29yZXMkRkRSLmRpZmZbY3VycmVudF9yb3ddIDw9bWluX3B2YWx1ZSl7CiAgICBub2RlX2NsYXNzW2ldIDwtIHBhc3RlKG5vZGVfY2xhc3NbaV0sImRpZmYiLHNlcCA9ICIgIikKICB9CiAgaWYoUk5BU2VxX2dlbmVfc2NvcmVzJEZEUi5wcm9saWZbY3VycmVudF9yb3ddIDw9bWluX3B2YWx1ZSl7CiAgICBub2RlX2NsYXNzW2ldIDwtIHBhc3RlKG5vZGVfY2xhc3NbaV0sInByb2xpZiIsc2VwID0gIiAiKQogIH0KICBpZihSTkFTZXFfZ2VuZV9zY29yZXMkRkRSLmltbXVub1tjdXJyZW50X3Jvd10gPD1taW5fcHZhbHVlKXsKICAgIG5vZGVfY2xhc3NbaV0gPC0gcGFzdGUobm9kZV9jbGFzc1tpXSwiaW1tdW5vIixzZXAgPSAiICIpCiAgfQp9Cm5vZGVfY2xhc3MgPC0gdHJpbXdzKG5vZGVfY2xhc3MpCm5vZGVfY2xhc3NfZGYgPC1kYXRhLmZyYW1lKG5hbWU9bm9kZXNfaW5fbmV0d29yaywgbm9kZV9jbGFzcyxzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UpCgpoZWFkKG5vZGVfY2xhc3NfZGYpCmBgYAoKTWFwIHRoZSBuZXcgbm9kZSBhdHRyaWJ1dGUgYW5kIHRoZSBhbGwgdGhlIGdlbmUgc2NvcmVzIHRvIHRoZSBuZXR3b3JrLgpgYGB7ciBldmFsPUZBTFNFfQpsb2FkVGFibGVEYXRhKFJOQVNlcV9nZW5lX3Njb3Jlcyx0YWJsZS5rZXkuY29sdW1uID0gIm5hbWUiLGRhdGEua2V5LmNvbHVtbiA9ICJOYW1lIikgICNkZWZhdWx0IGRhdGEuZnJhbWUga2V5IGlzIHJvdy5uYW1lcwoKbG9hZFRhYmxlRGF0YShub2RlX2NsYXNzX2RmLHRhYmxlLmtleS5jb2x1bW4gPSAibmFtZSIsZGF0YS5rZXkuY29sdW1uID0gIm5hbWUiKSAgI2RlZmF1bHQgZGF0YS5mcmFtZSBrZXkgaXMgcm93Lm5hbWVzCmBgYAoKQ3JlYXRlIGEgY29sb3IgbWFwcGluZyBmb3IgdGhlIGRpZmZlcmVudCBjYW5jZXIgdHlwZXM6CmBgYHtyIGV2YWw9RkFMU0V9CiNjcmVhdGUgYSBuZXcgbWFwcGluZyB3aXRoIHRoZSBkaWZmZXJlbnQgdHlwZXMKdW5pcXVlX3R5cGVzIDwtIHNvcnQodW5pcXVlKG5vZGVfY2xhc3MpKQoKY291bCA9IGJyZXdlci5wYWwoNCwgIlNldDEiKSAKIAojIEkgY2FuIGFkZCBtb3JlIHRvbmVzIHRvIHRoaXMgcGFsZXR0ZSA6CmNvdWwgPSBjb2xvclJhbXBQYWxldHRlKGNvdWwpKGxlbmd0aCh1bmlxdWVfdHlwZXMpKQoKc2V0Tm9kZUNvbG9yTWFwcGluZyh0YWJsZS5jb2x1bW4gPSAibm9kZV9jbGFzcyIsdGFibGUuY29sdW1uLnZhbHVlcyA9IHVuaXF1ZV90eXBlcywKICAgICAgICAgICAgICAgICAgICBjb2xvcnMgPSBjb3VsLG1hcHBpbmcudHlwZSA9ICJkIikKYGBgCgojIyMgQ2x1c3RlciB0aGUgTmV0d29yawoKYGBge3IgZXZhbD1GQUxTRX0KI21ha2Ugc3VyZSBpdCBpcyBzZXQgdG8gdGhlIHJpZ2h0IG5ldHdvcmsKICBzZXRDdXJyZW50TmV0d29yayhuZXR3b3JrID0gZ2V0TmV0d29ya05hbWUoc3VpZD1hcy5udW1lcmljKGN1cnJlbnRfbmV0d29ya19pZCkpKQoKICAjY2x1c3RlciB0aGUgbmV0d29yawogIGNsdXN0ZXJtYWtlcl91cmwgPC0gcGFzdGUoImNsdXN0ZXIgbWNsIG5ldHdvcms9U1VJRDoiLGN1cnJlbnRfbmV0d29ya19pZCwgc2VwPSIiKQogIGNvbW1hbmRzR0VUKGNsdXN0ZXJtYWtlcl91cmwpCiAgCiAgI2dldCB0aGUgY2x1c3RlcmluZyByZXN1bHRzCiAgZGVmYXVsdF9ub2RlX3RhYmxlIDwtIGdldFRhYmxlQ29sdW1ucyh0YWJsZT0gIm5vZGUiLG5ldHdvcmsgPSBhcy5udW1lcmljKGN1cnJlbnRfbmV0d29ya19pZCkpCiAKICBoZWFkKGRlZmF1bHRfbm9kZV90YWJsZSkKYGBgCgojIyMgUGVyZm9ybSBmdW5jdGlvbmFsIGVucmljaG1lbnQKV2UgY2FuIHVzZSB0aGUgU1RSSU5HYXBwIHRvIHBlcmZvcm0gYSBxdWljay1hbmQtZWFzeSBmdW5jdGlvbmFsIGVucmljaG1lbnQgYW5hbHlzaXMuIFRoaXMgd2lsbCBwcm92aWRlIGZ1bmN0aW9uYWwgbGFiZWxzIChlLmcuLCBHTyB0ZXJtcyBhbmQgcGF0aHdheXMpIHRvIHRoZSBidWxrIG9mIGdlbmVzIGluIGEgZ2l2ZW4gY2x1c3Rlci4KCkZvY3VzaW5nIG9uIGNsdXN0ZXIgMSBhcyBhbiBleGFtcGxlOgpgYGB7cn0KY3VycmVudF9jbHVzdGVyIDwtICIxIgojc2VsZWN0IGFsbCB0aGUgbm9kZXMgaW4gY2x1c3RlciAxCnNlbGVjdGVkbm9kZXMgPC0gc2VsZWN0Tm9kZXMoY3VycmVudF9jbHVzdGVyLCBieS5jb2w9Il9fbWNsQ2x1c3RlciIpCiAgCiNjcmVhdGUgYSBzdWJuZXR3b3JrIHdpdGggY2x1c3RlciAxCnN1Ym5ldHdvcmtfc3VpZCA8LSBjcmVhdGVTdWJuZXR3b3JrKG5vZGVzPSJzZWxlY3RlZCIpCgpgYGAKCkxldCdzICJzdHJpbmdpZnkiIHRoZSBuZXR3b3JrIHNvIHRoYXQgdGhlIFNUUklOR2FwcCBjYW4gcmVjb2duaXplIGl0cyBjb250ZW50czoKYGBge3J9CmNvbW1hbmRzUnVuKCdzdHJpbmcgc3RyaW5naWZ5IGNvbHVtbj0ibmFtZSIgc3BlY2llcz0iSG9tbyBzYXBpZW5zIicpCmBgYAoKWW91IG1heSB3YW50IHRvIHJlYXBwbHkgdGhlIGNsdXN0ZXItYmFzZWQgc3R5bGUgYW5kIHJlYWRqdXN0IGxheW91dDoKYGBge3J9CnNldFZpc3VhbFN0eWxlKCJkZWZhdWx0IikKbGF5b3V0TmV0d29yaygnZm9yY2UtZGlyZWN0ZWQgZGVmYXVsdFNwcmluZ0NvZWZmaWNpZW50PTAuMDAwMDA1IGRlZmF1bHRTcHJpbmdMZW5ndGg9NjAnKQpgYGAKClRoZW4sIGl0J3MganVzdCBhIGZldyBjb21tYW5kcyB0byBwZXJmb3JtIGVucmljaG1lbnQgYW5hbHlzaXMgYW5kIGRpc3BsYXkgdGhlIHJlc3VsdHMgYXMgY29sb3ItY29kZWQgbm9kZSBib3JkZXJzOgpgYGB7cn0KY29tbWFuZHNQT1NUKCdzdHJpbmcgcmV0cmlldmUgZW5yaWNobWVudCcpCmNvbW1hbmRzUE9TVCgnc3RyaW5nIHNob3cgZW5yaWNobWVudCcpICN0b2dnbGVzIHRvICdoaWRlJyBhZnRlciBydW5uaW5nCmNvbW1hbmRzUE9TVCgnc3RyaW5nIHNob3cgY2hhcnRzJykKYGBgCgpFeHBvcnQgaW1hZ2Ugb2YgcmVzdWx0aW5nIGNsdXN0ZXIgd2l0aCBlbnJpY2htZW50IHJlc3VsdHMuCmBgYHtyIGNsdXN0ZXIxZW0sIGluY2x1ZGU9VFJVRX0KY2x1c3RlcjFlbnJfcG5nX2ZpbGVfbmFtZSA8LSAiY2x1c3RlcjFlbS5wbmciCgpgYGAKCmBgYHtyIGV2YWw9RkFMU0V9CmlmKGZpbGUuZXhpc3RzKGNsdXN0ZXIxZW5yX3BuZ19maWxlX25hbWUpKXsKICAjY3l0b3NjYXBlIGhhbmdzIHdhaXRpbmcgZm9yIHVzZXIgcmVzcG9uc2UgaWYgZmlsZSBhbHJlYWR5IGV4aXN0cy4gIFJlbW92ZSBpdCBmaXJzdAogIGZpbGUucmVtb3ZlKGNsdXN0ZXIxZW5yX3BuZ19maWxlX25hbWUpCiAgfSAKCiNleHBvcnQgdGhlIG5ldHdvcmsKZXhwb3J0SW1hZ2UoY2x1c3RlcjFlbnJfcG5nX2ZpbGVfbmFtZSwgdHlwZSA9ICJwbmciKQpgYGA=