### Load standardpackages
library(tidyverse) # Collection of all the good stuff like dplyr, ggplot2 ect.
library(magrittr) # For extra-piping operators (eg. %<>%)
library(tidygraph)
library(igraph)
library(ggraph)
This session
Welcome to your second part of the introduction to network analysis. In this session you will learn:
- xxx
Introduction
- The main concern in designing a network visualization is the purpose it has to serve.
- What are the structural properties that we want to highlight? What are the key concerns we want to address?
- Network maps are far from the only visualization available for graphs - other network representation formats, and even simple charts of key characteristics, may be more appropriate in some cases.
- In network maps, as in other visualization formats, we have several key elements that control the outcome. The major ones are color, size, shape, and position.
Visualization Basics
# We load the highschool network nd make up som characteristics
set.seed(1337)
g <- as_tbl_graph(highschool, directed = TRUE) %E>%
mutate(weight = sample(1:5, n(), replace = TRUE),
year = year %>% as.factor()) %N>%
mutate(class = sample(LETTERS[1:3], n(), replace = TRUE),
gender = rbinom(n = n(), size = 1, prob = 0.5) %>% as.logical(),
label = randomNames::randomNames(gender = gender, name.order = "first.last"))
set.seed(1337)
g <- g %N>%
mutate(community = group_edge_betweenness(weights = weight, directed = TRUE) %>% as.factor()) %N>%
filter(!node_is_isolated())
g <- g %N>%
mutate(popular = case_when(
centrality_degree(mode = 'in') < 5 ~ 'unpopular',
centrality_degree(mode = 'in') >= 15 ~ 'popular',
TRUE ~ 'medium') %>% factor()
)
g %N>%
as_tibble() %>%
head()
Node Visualization
- Nodes in a network are the entities that are connected. Sometimes these are also referred to as vertices.
- While the nodes in a graph are the abstract concepts of entities, and the layout is their physical placement, the node geoms are the visual manifestation of the entities.
Node positions
- Conceptually one can simply think of it in terms of a scatter plot — the layout provides the x and y coordinates, and these can be used to draw nodes in different ways in the plotting window.
- Actually, due to the design of ggraph the standard scatterplot-like geoms from ggplot2 can be used directly for plotting nodes:
set.seed(1337)
g %>%
ggraph(layout = "nicely") +
geom_point(aes(x = x, y = y))
- The reason this works is that layouts (about which we talk in a moment) return a
data.frame
of node positions and metadata and this is used as the default plot data:
set.seed(1337)
g_layout <- g %>% create_layout(layout = "nicely") %>% select(x,y)
g_layout %>% head()
- While usage of the default
ggplot2
is theoreticlly fine, ggraph
practically comes with its own set of node geoms (geom_node_*()
).
- They by default already inherit the layout x and y coordinates, and come with extra features for network visualization.
ggraph
also comes with an own plotting theme (theme_graph()
), which optimizes for graph visualization, and we might want to use.
g %>% ggraph(layout = g_layout) +
geom_node_point() +
theme_graph()
- Usually (but not always) when visualizing a network, we are interested in the connectivity structure as expressed by the interplay between nodes and edges.
- So, lets also plot the edges (the geometries from the
geom_edge_*
family, about which we talk in a moment)
g %>% ggraph(layout = g_layout) +
geom_node_point() +
geom_edge_link(alpha = 0.25) +
theme_graph()
Size
- Size is the first obvious choice to highlight important (eg. central) nodes on a contineous scale.
g %>% ggraph(layout = g_layout) +
geom_edge_link(alpha = 0.25) +
geom_node_point(aes(size = centrality_degree())) +
theme_graph()
Color
- Color can also be used to visualize importance in a second continuous dimension, or to highlight categorical features
g %>% ggraph(layout = g_layout) +
geom_edge_link(alpha = 0.25) +
geom_node_point(aes(color = community)) +
theme_graph()
Alpha
g %>% ggraph(layout = g_layout) +
geom_edge_link(alpha = 0.25) +
geom_node_point(aes(alpha = centrality_degree())) +
theme_graph()
Shapes
- In case we want to express even more categorical characteristics, we can also (just like in the visualiation of tabular data) use node shapes.
shapes()
[1] "circle" "crectangle" "csquare" "none" "pie" "raster" "rectangle"
[8] "sphere" "square" "vrectangle"
g %>% ggraph(layout = g_layout) +
geom_edge_link(alpha = 0.25) +
geom_node_point(aes(shape = gender)) +
theme_graph()
Labels
- With the
geom_node_text
geometry, we can also ad labels to the node. They are subject to common aestetics.
g %>% ggraph(layout = g_layout) +
geom_edge_link(alpha = 0.25) +
geom_node_text(aes(label = label)) +
theme_graph()
In large graphs, plotting labels can appear messy, so it might make sense to only focus on important nodes to label
g %>% ggraph(layout = g_layout) +
geom_edge_link(alpha = 0.25) +
geom_node_point() +
geom_node_text(aes(label = label), repel = TRUE) +
theme_graph()
- Still looks like too much. If we want to highlight only certain important nodes with label, we can also only plot them.
- Note that (very practical) all
ggraph
geoms have a filter
aestetic we can use for that
g %>% ggraph(layout = g_layout) +
geom_edge_link(alpha = 0.25) +
geom_node_point() +
geom_node_text(aes(label = label,
filter = centrality_degree() >= centrality_degree() %>% quantile(0.9)),
repel = TRUE) +
theme_graph()
Edge Visualization
- So, now that we captured nodes, lets see how we can highlight aspects of edges, which are visualized with the geometries of the
geom_edge_*
family.
Weight
- Obviously, the edge weight (=thickness)
g %>% ggraph(layout = g_layout) +
geom_edge_link(aes(width = weight), alpha = 0.25) +
geom_node_point() +
theme_graph()
- Unfortunately, I wind the default to thick. We can also scale it.
g %>% ggraph(layout = g_layout) +
geom_edge_link(aes(width = weight), alpha = 0.25) +
scale_edge_width(range = c(0.1, 2)) +
geom_node_point() +
theme_graph()
Color
- Color can also be used to highlight edge significance (continuous)
- However, color is more often used to highlight different edge categories.
- Notice, since we want to represent the colors of potentially multiple edges between a node pair, I now use the
geom_edge_fan
geometry.
g %>% ggraph(layout = g_layout) +
geom_edge_fan(aes(size = weight,
color = year), alpha = 0.25) +
geom_node_point() +
theme_graph()
Density
- Density plots can also be used to highlight densely connected regions.
g %>% ggraph(layout = g_layout) +
geom_edge_link(aes(col = year), alpha = 0.1) +
geom_edge_density(aes(fill = year)) +
geom_node_point() +
theme_graph()
Directionality
- The easiest way to express directionality is by defining the
arrow()
, which comes with own aestetics.
g %>% ggraph(layout = g_layout) +
geom_edge_fan(aes(color = year),
arrow = arrow(),
alpha = 0.5) +
geom_node_point() +
theme_graph()
- The default open arrow and other settings are a bit ugly, so we use some adittional aestetics
g %>% ggraph(layout = g_layout) +
geom_edge_fan(aes(color = year),
arrow = arrow(type = "closed", length = unit(2, "mm")),
start_cap = circle(1, "mm"),
end_cap = circle(1, "mm"),
alpha = 0.5) +
geom_node_point() +
theme_graph()
- Another nice trick is to work with alphas or colors, which change between start and end node.
g %>%
ggraph(layout = g_layout) +
geom_edge_fan(aes(color = year,
alpha = stat(index)) # Notice that
) +
geom_node_point() +
theme_graph() +
scale_edge_alpha("Edge direction", guide = "edge_direction")
- It can also be really practical to change edge characteristics by the characteristics of their adjacent nodes.
- Remember, with
.N()
, we can access them to do so.
set.seed(1337)
g %>%
ggraph(layout = 'nicely') +
geom_edge_fan(aes(color = .N()$community[to]), # Notice that
alpha = 0.5,
show.legend = FALSE) +
geom_node_point(aes(color = community),
show.legend = FALSE) +
theme_graph()
Layouts
- The graph layout refers to the node position on the reference system.
Ordinary graph style
- Graphs can be represented in simple geometries such as squares, circles, lines, or randomly.
- Or, specialized algorithms can be used to position nodes according to the properties of their connectivity.
- They are usually designed to highlight different aspects of the network.
- Lets inspect some standard layouts
set.seed(1337)
library(ggpubr)
layout_list <- c("randomly", "linear", "circle",
"grid", "fr", "kk",
"graphopt", "stress", 'mds',
'dh', 'drl', 'lgl')
g_list <- list(NULL)
for(i in 1:length(layout_list)){
g_list[[i]] <-g %>%
ggraph(layout = layout_list[i]) +
geom_edge_fan(aes(color = year,
width = weight,
alpha = weight),
arrow = arrow(type = "closed", length = unit(2, "mm")),
start_cap = circle(1, "mm"),
end_cap = circle(1, "mm"),
show.legend = FALSE) +
scale_edge_width(range = c(0.1, 0.5)) +
geom_node_point(aes(size = centrality_degree(mode = 'in'),
color = community,
shape = gender),
show.legend = FALSE) +
theme_graph() +
labs(title = paste("Layout:", layout_list[i], sep = " "))
}
ggarrange(plotlist = g_list, nrow = 4, ncol = 3)
Arcs and circles
# An arc diagram
g %>% ggraph(layout = 'linear') +
geom_edge_arc() +
geom_node_point(aes(size = centrality_degree(),
color = community),
show.legend = FALSE) +
theme_graph()
# An arc diagram
g %>% ggraph(layout = 'linear', circular = TRUE) +
geom_edge_arc(aes(color = .N()$community[to])) +
geom_node_point(aes(size = centrality_degree(),
color = community),
show.legend = FALSE) +
theme_graph() +
coord_fixed()
Hive plots
- A hive plot, while still technically a node-edge diagram, is a bit different from the rest as it uses information pertaining to the nodes, rather than the connection information in the graph.
- This means that hive plots, to a certain extent are more interpretable as well as less vulnerable to small changes in the graph structure.
- They are less common though, so use will often require some additional explanation.
g %>%
ggraph(layout = 'hive', axis = popular, sort.by = centrality_degree(mode = 'in')) +
geom_edge_hive(aes(colour = year, alpha = ..index..), show.legend = FALSE) +
geom_axis_hive(aes(colour = popular), size = 3, label = FALSE) +
coord_fixed() +
theme_graph() +
theme(legend.position = 'bottom')
Social Fabric
g %>% ggraph(layout = 'fabric', sort.by = community) +
geom_node_range(aes(colour = community), alpha = 0.3) +
geom_edge_span(aes(col = .N()$community[to]), end_shape = 'circle', alpha = 0.5) +
coord_fixed() +
theme_graph() +
theme(legend.position = 'none')
Visualizing Hirarchical networks
If the network is by definition hierarchical, edges can only exist between nodes of higher to lower dept (eg. tree structures).
This offers us possibility for quite some adittional ways of representing it which are geared towards hirarchical (=nested) structures
Here an example of the dependency structures of the flare
package
edges <- flare$edges
vertices <- flare$vertices %>% arrange(name) %>% mutate(name=factor(name, name))
connections <- flare$imports
vertices %>% head()
edges %>% head()
connections %>% head()
g_hir <- tbl_graph(vertices, edges)
g_hir
Tree structures
g_hir %>% ggraph('tree') +
geom_edge_diagonal() +
theme_graph()
g_hir %>% ggraph( 'dendrogram') +
geom_edge_elbow() +
theme_graph()
g_hir %>% ggraph('dendrogram', circular = TRUE) +
geom_edge_elbow() +
coord_fixed() +
theme_graph()
# The connection object must refer to the ids of the leaves:
from = match(connections$from, vertices$name)
to = match(connections$to, vertices$name)
g_hir %>% ggraph(layout = 'dendrogram', circular = TRUE) +
geom_conn_bundle(data = get_con(from = from, to = to), alpha = 0.1) +
geom_edge_diagonal0() +
#geom_node_text(aes(filter = leaf, angle = node_angle(x, y), label = shortName),
# hjust = 'outward', size = 2) +
coord_fixed() +
theme_graph()
Non-edge-based
# An icicle plot
g_hir %>% ggraph('partition') +
geom_node_tile(aes(fill = depth), size = 0.25) +
theme_graph()
# A sunburst plot
g_hir %>% ggraph('partition', circular = TRUE) +
geom_node_arc_bar(aes(fill = depth), size = 0.25) +
coord_fixed() +
theme_graph()
g_hir %>% ggraph('circlepack') + # , weight = size
geom_node_circle(aes(fill = depth), size = 0.25, n = 50) +
coord_fixed() +
theme_graph()
g_hir %>% ggraph('treemap') +
geom_node_tile(aes(fill = depth), size = 0.25) +
theme_graph()
Geospatial networks
Defining a map
library(maps)
map_us <- map_data("usa")
map_us %>%
head()
Getting some network data
library(anyflights)
us_airports <- get_airports() %>%
filter(lat >= 24 & lat <= 49 & lon >= -124 & lon <= -66) %>%
rename(name_full = name,
name = faa)
us_airports %>% head()
flights <- get_flights(station = us_airports %>% pull(name), year = 2015, month = 5)
flights %>% head()
edges <- flights %>% count(origin, dest, sort = TRUE) %>%
rename(from = origin, to = dest, weight = n) %>%
semi_join(us_airports, by = c('from' = 'name')) %>%
semi_join(us_airports, by = c('to' = 'name')) %>%
filter(percent_rank(weight) >= 0.25)
g_geo <- tbl_graph(nodes = us_airports, edges = edges, directed = TRUE) %N>%
filter(!node_is_isolated())
Constructing a graph
coords <- g_geo %N>%
as_tibble() %>%
select(lat, lon) %>%
rename(x = lon, y = lat)
g_geo %>%
ggraph(layout = coords) +
geom_polygon(data = map_us, aes(x=long, y = lat, group = group), fill = "#CECECE", color = "#515151") +
geom_edge_arc(aes(width = weight,
alpha = weight,
filter = percent_rank(weight) >= 0.75,
circular = FALSE),
strength = 0.33,
color = 'chocolate2') +
geom_node_point(aes(size = centrality_degree(),
col = centrality_degree())) +
scale_edge_width_continuous(range = c(0.1, 1)) +
coord_fixed(1.3) +
theme_graph() +
theme(legend.position = 'none')
Interactive networks
- There are numerous ways to to interactive network visualizations in R
- For the sake of time, I just show you what I find the easiest and most consistent implementation (plotly unfortunately does by now not support ggraph)
library(ggiraph)
g_plot_int <- g %>%
ggraph(layout = layout_list[i]) +
geom_edge_fan(aes(color = year,
width = weight,
alpha = weight),
arrow = arrow(type = "closed", length = unit(2, "mm")),
start_cap = circle(1, "mm"),
end_cap = circle(1, "mm"),
show.legend = FALSE) +
scale_edge_width(range = c(0.1, 0.5)) +
geom_node_point(aes(size = centrality_degree(mode = 'in'),
color = community,
shape = gender),
show.legend = FALSE) +
geom_point_interactive(aes(x, y, # Notice this extra layer
tooltip = label, data_id = name,
size = centrality_degree(mode = 'in')), alpha = 0.01) +
theme_graph() +
theme(legend.position = 'none')
girafe(ggobj = g_plot_int, width_svg = 10, height_svg = 10) %>%
girafe_options(opts_zoom(max = 10), opts_tooltip(opacity = 0.7) )
Your turn
Please do Exercise 1 in the corresponding section on Github
. This time you are about to do your own bibliographic analysis!
Endnotes
Session info
sessionInfo()
R version 4.0.2 (2020-06-22)
Platform: x86_64-apple-darwin17.0 (64-bit)
Running under: macOS Catalina 10.15.7
Matrix products: default
BLAS: /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libBLAS.dylib
LAPACK: /Library/Frameworks/R.framework/Versions/4.0/Resources/lib/libRlapack.dylib
locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8
attached base packages:
[1] stats graphics grDevices utils datasets methods base
other attached packages:
[1] gdtools_0.2.2 ggiraph_0.7.8 anyflights_0.3.0 maps_3.3.0 ggpubr_0.4.0
[6] ggraph_2.0.3 igraph_1.2.5 tidygraph_1.2.0 magrittr_1.5 forcats_0.5.0
[11] stringr_1.4.0 dplyr_1.0.2 purrr_0.3.4 readr_1.3.1 tidyr_1.1.2
[16] tibble_3.0.3 ggplot2_3.3.2 tidyverse_1.3.0 knitr_1.30
loaded via a namespace (and not attached):
[1] fs_1.5.0 bit64_4.0.5 lubridate_1.7.9 progress_1.2.2
[5] httr_1.4.2 tools_4.0.2 backports_1.1.10 utf8_1.1.4
[9] R6_2.4.1 DBI_1.1.0 colorspace_1.4-1 withr_2.3.0
[13] prettyunits_1.1.1 tidyselect_1.1.0 gridExtra_2.3 bit_4.0.4
[17] curl_4.3 compiler_4.0.2 cli_2.0.2 rvest_0.3.6
[21] pacman_0.5.1 randomNames_1.4-0.0 xml2_1.3.2 labeling_0.3
[25] scales_1.1.1 systemfonts_0.3.2 digest_0.6.25 foreign_0.8-80
[29] rmarkdown_2.4 rio_0.5.16 pkgconfig_2.0.3 htmltools_0.5.0
[33] dbplyr_1.4.4 htmlwidgets_1.5.1 rlang_0.4.7 readxl_1.3.1
[37] rstudioapi_0.11 farver_2.0.3 generics_0.0.2 jsonlite_1.7.1
[41] vroom_1.3.2 zip_2.1.1 car_3.0-10 Rcpp_1.0.5
[45] munsell_0.5.0 fansi_0.4.1 abind_1.4-5 viridis_0.5.1
[49] lifecycle_0.2.0 stringi_1.5.3 yaml_2.2.1 carData_3.0-4
[53] MASS_7.3-53 grid_4.0.2 blob_1.2.1 parallel_4.0.2
[57] ggrepel_0.8.2 crayon_1.3.4 cowplot_1.1.0 graphlayouts_0.7.0
[61] haven_2.3.1 hms_0.5.3 pillar_1.4.6 uuid_0.1-4
[65] ggsignif_0.6.0 reprex_0.3.0 glue_1.4.2 evaluate_0.14
[69] data.table_1.13.0 modelr_0.1.8 vctrs_0.3.4 tweenr_1.0.1
[73] testthat_2.3.2 cellranger_1.1.0 gtable_0.3.0 polyclip_1.10-0
[77] assertthat_0.2.1 xfun_0.18 ggforce_0.3.2 openxlsx_4.2.2
[81] broom_0.7.1 rstatix_0.6.0 rsconnect_0.8.16 viridisLite_0.3.0
[85] toOrdinal_1.1-0.0 ellipsis_0.3.1
LS0tCnRpdGxlOiAnSW50ZXJtZWRpYXRlIE5ldHdvcmsgQW5hbHlzaXM6IE5ldHdvcmsgVml6dWFsaXphdGlvbjogQXBwbGljYXRpb24gKFIpJwphdXRob3I6ICJEYW5pZWwgUy4gSGFpbiAoZHNoQGJ1c2luZXNzLmFhdS5kaykiCmRhdGU6ICJVcGRhdGVkIGByIGZvcm1hdChTeXMudGltZSgpLCAnJUIgJWQsICVZJylgIgpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazoKICAgIGNvZGVfZm9sZGluZzogc2hvdwogICAgZGZfcHJpbnQ6IHBhZ2VkCiAgICB0b2M6IHRydWUKICAgIHRvY19kZXB0aDogMgogICAgdG9jX2Zsb2F0OgogICAgICBjb2xsYXBzZWQ6IGZhbHNlCiAgICB0aGVtZTogZmxhdGx5Ci0tLQoKYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9CiMjIyBHZW5lcmljIHByZWFtYmxlCnJtKGxpc3Q9bHMoKSkKU3lzLnNldGVudihMQU5HID0gImVuIikgIyBGb3IgZW5nbGlzaCBsYW5ndWFnZQpvcHRpb25zKHNjaXBlbiA9IDUpICMgVG8gZGVhY3RpdmF0ZSBhbm5veWluZyBzY2llbnRpZmljIG51bWJlciBub3RhdGlvbgoKIyMjIEtuaXRyIG9wdGlvbnMKbGlicmFyeShrbml0cikgIyBGb3IgZGlzcGxheSBvZiB0aGUgbWFya2Rvd24Ka25pdHI6Om9wdHNfY2h1bmskc2V0KHdhcm5pbmc9RkFMU0UsCiAgICAgICAgICAgICAgICAgICAgIG1lc3NhZ2U9RkFMU0UsCiAgICAgICAgICAgICAgICAgICAgIGNvbW1lbnQ9RkFMU0UsIAogICAgICAgICAgICAgICAgICAgICBmaWcuYWxpZ249ImNlbnRlciIKICAgICAgICAgICAgICAgICAgICAgKQpgYGAKCmBgYHtyfQojIyMgTG9hZCBzdGFuZGFyZHBhY2thZ2VzCmxpYnJhcnkodGlkeXZlcnNlKSAjIENvbGxlY3Rpb24gb2YgYWxsIHRoZSBnb29kIHN0dWZmIGxpa2UgZHBseXIsIGdncGxvdDIgZWN0LgpsaWJyYXJ5KG1hZ3JpdHRyKSAjIEZvciBleHRyYS1waXBpbmcgb3BlcmF0b3JzIChlZy4gJTw+JSkKCmxpYnJhcnkodGlkeWdyYXBoKQpsaWJyYXJ5KGlncmFwaCkKbGlicmFyeShnZ3JhcGgpCmBgYAoKIyMjIFRoaXMgc2Vzc2lvbgoKV2VsY29tZSB0byB5b3VyIHNlY29uZCBwYXJ0IG9mIHRoZSBpbnRyb2R1Y3Rpb24gdG8gbmV0d29yayBhbmFseXNpcy4gSW4gdGhpcyBzZXNzaW9uIHlvdSB3aWxsIGxlYXJuOgoKMS4geHh4CgoKCiMgSW50cm9kdWN0aW9uCgoqIFRoZSBtYWluIGNvbmNlcm4gaW4gZGVzaWduaW5nIGEgbmV0d29yayB2aXN1YWxpemF0aW9uIGlzIHRoZSBwdXJwb3NlIGl0IGhhcyB0byBzZXJ2ZS4gCiogV2hhdCBhcmUgdGhlIHN0cnVjdHVyYWwgcHJvcGVydGllcyB0aGF0IHdlIHdhbnQgdG8gaGlnaGxpZ2h0PyBXaGF0IGFyZSB0aGUga2V5IGNvbmNlcm5zIHdlIHdhbnQgdG8gYWRkcmVzcz8KCiFbXShodHRwczovL3Nkcy1hYXUuZ2l0aHViLmlvL1NEUy1tYXN0ZXIvMDBfbWVkaWEvbmV0d29ya3Nfdml6X2dvYWwucG5nKXt3aWR0aD01MDBweH0KCiogTmV0d29yayBtYXBzIGFyZSBmYXIgZnJvbSB0aGUgb25seSB2aXN1YWxpemF0aW9uIGF2YWlsYWJsZSBmb3IgZ3JhcGhzIC0gb3RoZXIgbmV0d29yayByZXByZXNlbnRhdGlvbiBmb3JtYXRzLCBhbmQgZXZlbiBzaW1wbGUgY2hhcnRzIG9mIGtleSBjaGFyYWN0ZXJpc3RpY3MsIG1heSBiZSBtb3JlIGFwcHJvcHJpYXRlIGluIHNvbWUgY2FzZXMuCgohW10oaHR0cHM6Ly9zZHMtYWF1LmdpdGh1Yi5pby9TRFMtbWFzdGVyLzAwX21lZGlhL25ldHdvcmtzX3Zpel90eXBlLnBuZyl7d2lkdGg9NTAwcHh9CgoqIEluIG5ldHdvcmsgbWFwcywgYXMgaW4gb3RoZXIgdmlzdWFsaXphdGlvbiBmb3JtYXRzLCB3ZSBoYXZlIHNldmVyYWwga2V5IGVsZW1lbnRzIHRoYXQgY29udHJvbCB0aGUgb3V0Y29tZS4gVGhlIG1ham9yIG9uZXMgYXJlIGNvbG9yLCBzaXplLCBzaGFwZSwgYW5kIHBvc2l0aW9uLgoKIVtdKGh0dHBzOi8vc2RzLWFhdS5naXRodWIuaW8vU0RTLW1hc3Rlci8wMF9tZWRpYS9uZXR3b3Jrc192aXpfY29udHJvbHMucG5nKXt3aWR0aD01MDBweH0KCiMgVmlzdWFsaXphdGlvbiBCYXNpY3MKCmBgYHtyfQojIFdlIGxvYWQgdGhlIGhpZ2hzY2hvb2wgbmV0d29yayBuZCBtYWtlIHVwIHNvbSBjaGFyYWN0ZXJpc3RpY3MKc2V0LnNlZWQoMTMzNykKZyA8LSBhc190YmxfZ3JhcGgoaGlnaHNjaG9vbCwgZGlyZWN0ZWQgPSBUUlVFKSAlRT4lCiAgbXV0YXRlKHdlaWdodCA9IHNhbXBsZSgxOjUsIG4oKSwgcmVwbGFjZSA9IFRSVUUpLAogICAgICAgICB5ZWFyID0geWVhciAlPiUgYXMuZmFjdG9yKCkpICVOPiUKICBtdXRhdGUoY2xhc3MgPSBzYW1wbGUoTEVUVEVSU1sxOjNdLCBuKCksIHJlcGxhY2UgPSBUUlVFKSwKICAgICAgICAgZ2VuZGVyID0gcmJpbm9tKG4gPSBuKCksIHNpemUgPSAxLCBwcm9iID0gMC41KSAlPiUgYXMubG9naWNhbCgpLAogICAgICAgICBsYWJlbCA9IHJhbmRvbU5hbWVzOjpyYW5kb21OYW1lcyhnZW5kZXIgPSBnZW5kZXIsIG5hbWUub3JkZXIgPSAiZmlyc3QubGFzdCIpKQpgYGAKCmBgYHtyLHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0UsIGVycm9yPUZBTFNFLCBjb21tZW50PUZBTFNFfQpzZXQuc2VlZCgxMzM3KQpnIDwtIGcgJU4+JQogIG11dGF0ZShjb21tdW5pdHkgPSBncm91cF9lZGdlX2JldHdlZW5uZXNzKHdlaWdodHMgPSB3ZWlnaHQsIGRpcmVjdGVkID0gVFJVRSkgJT4lIGFzLmZhY3RvcigpKSAlTj4lCiAgZmlsdGVyKCFub2RlX2lzX2lzb2xhdGVkKCkpIApgYGAKCmBgYHtyfQpnIDwtIGcgJU4+JQogIG11dGF0ZShwb3B1bGFyID0gY2FzZV93aGVuKAogICAgY2VudHJhbGl0eV9kZWdyZWUobW9kZSA9ICdpbicpIDwgNSB+ICd1bnBvcHVsYXInLAogICAgY2VudHJhbGl0eV9kZWdyZWUobW9kZSA9ICdpbicpID49IDE1IH4gJ3BvcHVsYXInLAogICAgVFJVRSAgfiAnbWVkaXVtJykgJT4lIGZhY3RvcigpCiAgICApCmBgYAoKYGBge3J9CmcgJU4+JQogIGFzX3RpYmJsZSgpICU+JQogIGhlYWQoKQpgYGAKCiMjIE5vZGUgVmlzdWFsaXphdGlvbgoKKiBOb2RlcyBpbiBhIG5ldHdvcmsgYXJlIHRoZSBlbnRpdGllcyB0aGF0IGFyZSBjb25uZWN0ZWQuIFNvbWV0aW1lcyB0aGVzZSBhcmUgYWxzbyByZWZlcnJlZCB0byBhcyB2ZXJ0aWNlcy4gCiogV2hpbGUgdGhlIG5vZGVzIGluIGEgZ3JhcGggYXJlIHRoZSBhYnN0cmFjdCBjb25jZXB0cyBvZiBlbnRpdGllcywgYW5kIHRoZSBsYXlvdXQgaXMgdGhlaXIgcGh5c2ljYWwgcGxhY2VtZW50LCB0aGUgbm9kZSBnZW9tcyBhcmUgdGhlIHZpc3VhbCBtYW5pZmVzdGF0aW9uIG9mIHRoZSBlbnRpdGllcy4gCgojIyMgTm9kZSBwb3NpdGlvbnMKCiogQ29uY2VwdHVhbGx5IG9uZSBjYW4gc2ltcGx5IHRoaW5rIG9mIGl0IGluIHRlcm1zIG9mIGEgc2NhdHRlciBwbG90IOKAlCB0aGUgbGF5b3V0IHByb3ZpZGVzIHRoZSB4IGFuZCB5IGNvb3JkaW5hdGVzLCBhbmQgdGhlc2UgY2FuIGJlIHVzZWQgdG8gZHJhdyBub2RlcyBpbiBkaWZmZXJlbnQgd2F5cyBpbiB0aGUgcGxvdHRpbmcgd2luZG93LiAKKiBBY3R1YWxseSwgZHVlIHRvIHRoZSBkZXNpZ24gb2YgZ2dyYXBoIHRoZSBzdGFuZGFyZCBzY2F0dGVycGxvdC1saWtlIGdlb21zIGZyb20gZ2dwbG90MiBjYW4gYmUgdXNlZCBkaXJlY3RseSBmb3IgcGxvdHRpbmcgbm9kZXM6CgpgYGB7cn0Kc2V0LnNlZWQoMTMzNykKZyAlPiUKICBnZ3JhcGgobGF5b3V0ID0gIm5pY2VseSIpICsgCiAgICBnZW9tX3BvaW50KGFlcyh4ID0geCwgeSA9IHkpKQpgYGAKCiogVGhlIHJlYXNvbiB0aGlzIHdvcmtzIGlzIHRoYXQgbGF5b3V0cyAoYWJvdXQgd2hpY2ggd2UgdGFsayBpbiBhIG1vbWVudCkgcmV0dXJuIGEgYGRhdGEuZnJhbWVgIG9mIG5vZGUgcG9zaXRpb25zIGFuZCBtZXRhZGF0YSBhbmQgdGhpcyBpcyB1c2VkIGFzIHRoZSBkZWZhdWx0IHBsb3QgZGF0YToKCmBgYHtyfQpzZXQuc2VlZCgxMzM3KQpnX2xheW91dCA8LSBnICU+JSBjcmVhdGVfbGF5b3V0KGxheW91dCA9ICJuaWNlbHkiKSAlPiUgc2VsZWN0KHgseSkgCmBgYAoKYGBge3J9CmdfbGF5b3V0ICU+JSBoZWFkKCkKYGBgCiogV2hpbGUgdXNhZ2Ugb2YgdGhlIGRlZmF1bHQgYGdncGxvdDJgIGlzIHRoZW9yZXRpY2xseSBmaW5lLCBgZ2dyYXBoYCBwcmFjdGljYWxseSBjb21lcyB3aXRoIGl0cyBvd24gc2V0IG9mIG5vZGUgZ2VvbXMgKGBnZW9tX25vZGVfKigpYCkuIAoqIFRoZXkgYnkgZGVmYXVsdCBhbHJlYWR5IGluaGVyaXQgdGhlIGxheW91dCB4IGFuZCB5IGNvb3JkaW5hdGVzLCBhbmQgY29tZSB3aXRoIGV4dHJhIGZlYXR1cmVzIGZvciBuZXR3b3JrIHZpc3VhbGl6YXRpb24uCiogYGdncmFwaGAgYWxzbyBjb21lcyB3aXRoIGFuIG93biBwbG90dGluZyB0aGVtZSAoYHRoZW1lX2dyYXBoKClgKSwgd2hpY2ggb3B0aW1pemVzIGZvciBncmFwaCB2aXN1YWxpemF0aW9uLCBhbmQgd2UgbWlnaHQgd2FudCB0byB1c2UuCgpgYGB7cn0KZyAlPiUgZ2dyYXBoKGxheW91dCA9IGdfbGF5b3V0KSArIAogIGdlb21fbm9kZV9wb2ludCgpICsKICB0aGVtZV9ncmFwaCgpCmBgYAoKKiBVc3VhbGx5IChidXQgbm90IGFsd2F5cykgd2hlbiB2aXN1YWxpemluZyBhIG5ldHdvcmssIHdlIGFyZSBpbnRlcmVzdGVkIGluIHRoZSBjb25uZWN0aXZpdHkgc3RydWN0dXJlIGFzIGV4cHJlc3NlZCBieSB0aGUgaW50ZXJwbGF5IGJldHdlZW4gbm9kZXMgYW5kIGVkZ2VzLiAKKiBTbywgbGV0cyBhbHNvIHBsb3QgdGhlIGVkZ2VzICh0aGUgZ2VvbWV0cmllcyBmcm9tIHRoZSBgZ2VvbV9lZGdlXypgIGZhbWlseSwgYWJvdXQgd2hpY2ggd2UgdGFsayBpbiBhIG1vbWVudCkKCmBgYHtyfQpnICU+JSBnZ3JhcGgobGF5b3V0ID0gZ19sYXlvdXQpICsgCiAgZ2VvbV9ub2RlX3BvaW50KCkgKyAKICBnZW9tX2VkZ2VfbGluayhhbHBoYSA9IDAuMjUpICsKICB0aGVtZV9ncmFwaCgpCmBgYAoKIyMjIFNpemUKCiogU2l6ZSBpcyB0aGUgZmlyc3Qgb2J2aW91cyBjaG9pY2UgdG8gaGlnaGxpZ2h0IGltcG9ydGFudCAoZWcuIGNlbnRyYWwpIG5vZGVzIG9uIGEgY29udGluZW91cyBzY2FsZS4KCmBgYHtyfQpnICU+JSBnZ3JhcGgobGF5b3V0ID0gZ19sYXlvdXQpICsgCiAgZ2VvbV9lZGdlX2xpbmsoYWxwaGEgPSAwLjI1KSArCiAgZ2VvbV9ub2RlX3BvaW50KGFlcyhzaXplID0gY2VudHJhbGl0eV9kZWdyZWUoKSkpICsKICB0aGVtZV9ncmFwaCgpCmBgYAoKIyMjIENvbG9yCgoqIENvbG9yIGNhbiBhbHNvIGJlIHVzZWQgdG8gdmlzdWFsaXplIGltcG9ydGFuY2UgaW4gYSBzZWNvbmQgY29udGludW91cyBkaW1lbnNpb24sIG9yIHRvIGhpZ2hsaWdodCBjYXRlZ29yaWNhbCBmZWF0dXJlcwoKYGBge3J9CmcgJT4lIGdncmFwaChsYXlvdXQgPSBnX2xheW91dCkgKyAKICBnZW9tX2VkZ2VfbGluayhhbHBoYSA9IDAuMjUpICsKICBnZW9tX25vZGVfcG9pbnQoYWVzKGNvbG9yID0gY29tbXVuaXR5KSkgKwogIHRoZW1lX2dyYXBoKCkKYGBgCgojIyMgQWxwaGEKCmBgYHtyfQpnICU+JSBnZ3JhcGgobGF5b3V0ID0gZ19sYXlvdXQpICsgCiAgZ2VvbV9lZGdlX2xpbmsoYWxwaGEgPSAwLjI1KSArCiAgZ2VvbV9ub2RlX3BvaW50KGFlcyhhbHBoYSA9IGNlbnRyYWxpdHlfZGVncmVlKCkpKSArCiAgdGhlbWVfZ3JhcGgoKQpgYGAKCiMjIyBTaGFwZXMKCiogSW4gY2FzZSB3ZSB3YW50IHRvIGV4cHJlc3MgZXZlbiBtb3JlIGNhdGVnb3JpY2FsIGNoYXJhY3RlcmlzdGljcywgd2UgY2FuIGFsc28gKGp1c3QgbGlrZSBpbiB0aGUgdmlzdWFsaWF0aW9uIG9mIHRhYnVsYXIgZGF0YSkgdXNlIG5vZGUgc2hhcGVzLgoKYGBge3J9CnNoYXBlcygpIApgYGAKCmBgYHtyfQpnICU+JSBnZ3JhcGgobGF5b3V0ID0gZ19sYXlvdXQpICsgCiAgZ2VvbV9lZGdlX2xpbmsoYWxwaGEgPSAwLjI1KSArCiAgZ2VvbV9ub2RlX3BvaW50KGFlcyhzaGFwZSA9IGdlbmRlcikpICsKICB0aGVtZV9ncmFwaCgpIApgYGAKCiMjIyBMYWJlbHMKCiogV2l0aCB0aGUgYGdlb21fbm9kZV90ZXh0YCBnZW9tZXRyeSwgd2UgY2FuIGFsc28gYWQgbGFiZWxzIHRvIHRoZSBub2RlLiBUaGV5IGFyZSBzdWJqZWN0IHRvIGNvbW1vbiBhZXN0ZXRpY3MuCgpgYGB7cn0KZyAlPiUgZ2dyYXBoKGxheW91dCA9IGdfbGF5b3V0KSArIAogIGdlb21fZWRnZV9saW5rKGFscGhhID0gMC4yNSkgKwogIGdlb21fbm9kZV90ZXh0KGFlcyhsYWJlbCA9IGxhYmVsKSkgKwogIHRoZW1lX2dyYXBoKCkgCmBgYAoKSW4gbGFyZ2UgZ3JhcGhzLCBwbG90dGluZyBsYWJlbHMgY2FuIGFwcGVhciBtZXNzeSwgc28gaXQgbWlnaHQgbWFrZSBzZW5zZSB0byBvbmx5IGZvY3VzIG9uIGltcG9ydGFudCBub2RlcyB0byBsYWJlbAoKYGBge3J9CmcgJT4lIGdncmFwaChsYXlvdXQgPSBnX2xheW91dCkgKyAKICBnZW9tX2VkZ2VfbGluayhhbHBoYSA9IDAuMjUpICsKICBnZW9tX25vZGVfcG9pbnQoKSArCiAgZ2VvbV9ub2RlX3RleHQoYWVzKGxhYmVsID0gbGFiZWwpLCByZXBlbCA9IFRSVUUpICsKICB0aGVtZV9ncmFwaCgpIApgYGAKCiogU3RpbGwgbG9va3MgbGlrZSB0b28gbXVjaC4gSWYgd2Ugd2FudCB0byBoaWdobGlnaHQgb25seSBjZXJ0YWluIGltcG9ydGFudCBub2RlcyB3aXRoIGxhYmVsLCB3ZSBjYW4gYWxzbyBvbmx5IHBsb3QgdGhlbS4KKiBOb3RlIHRoYXQgKHZlcnkgcHJhY3RpY2FsKSBhbGwgYGdncmFwaGAgZ2VvbXMgaGF2ZSBhIGBmaWx0ZXJgIGFlc3RldGljIHdlIGNhbiB1c2UgZm9yIHRoYXQKCgpgYGB7cn0KZyAlPiUgZ2dyYXBoKGxheW91dCA9IGdfbGF5b3V0KSArIAogIGdlb21fZWRnZV9saW5rKGFscGhhID0gMC4yNSkgKwogIGdlb21fbm9kZV9wb2ludCgpICsKICBnZW9tX25vZGVfdGV4dChhZXMobGFiZWwgPSBsYWJlbCwgCiAgICAgICAgICAgICAgICAgICAgIGZpbHRlciA9IGNlbnRyYWxpdHlfZGVncmVlKCkgPj0gY2VudHJhbGl0eV9kZWdyZWUoKSAgJT4lIHF1YW50aWxlKDAuOSkpLCAKICAgICAgICAgICAgICAgICByZXBlbCA9IFRSVUUpICsKICB0aGVtZV9ncmFwaCgpIApgYGAKCiMjIyBDb21iaW5lZCBub2RlIHZpc3VhbGl6YXRpb24gdG9vbHMKCmBgYHtyfQpnICU+JSBnZ3JhcGgobGF5b3V0ID0gZ19sYXlvdXQpICsgCiAgZ2VvbV9lZGdlX2xpbmsoYWxwaGEgPSAwLjI1KSArCiAgZ2VvbV9ub2RlX3BvaW50KGFlcyhzaXplID0gY2VudHJhbGl0eV9laWdlbigpLCAKICAgICAgICAgICAgICAgICAgICAgIGNvbG9yID0gY29tbXVuaXR5LAogICAgICAgICAgICAgICAgICAgICAgc2hhcGUgPSBnZW5kZXIpKSArCiAgZ2VvbV9ub2RlX3RleHQoYWVzKGxhYmVsID0gbGFiZWwsIAogICAgICAgICAgICAgICAgICAgICBmaWx0ZXIgPSBjZW50cmFsaXR5X2VpZ2VuKCkgPj0gY2VudHJhbGl0eV9laWdlbigpICU+JSBxdWFudGlsZSgwLjkwKSksIAogICAgICAgICAgICAgICAgIHJlcGVsID0gVFJVRSkgKwogIHRoZW1lX2dyYXBoKCkgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICdub25lJykKYGBgCgoKIyMgRWRnZSBWaXN1YWxpemF0aW9uCgoqIFNvLCBub3cgdGhhdCB3ZSBjYXB0dXJlZCBub2RlcywgbGV0cyBzZWUgaG93IHdlIGNhbiBoaWdobGlnaHQgYXNwZWN0cyBvZiBlZGdlcywgd2hpY2ggYXJlIHZpc3VhbGl6ZWQgd2l0aCB0aGUgZ2VvbWV0cmllcyBvZiB0aGUgYGdlb21fZWRnZV8qYCBmYW1pbHkuCgojIyMgV2VpZ2h0CgoqIE9idmlvdXNseSwgdGhlIGVkZ2Ugd2VpZ2h0ICg9dGhpY2tuZXNzKQoKYGBge3J9CmcgJT4lIGdncmFwaChsYXlvdXQgPSBnX2xheW91dCkgKyAKICBnZW9tX2VkZ2VfbGluayhhZXMod2lkdGggPSB3ZWlnaHQpLCBhbHBoYSA9IDAuMjUpICsKICBnZW9tX25vZGVfcG9pbnQoKSArCiAgdGhlbWVfZ3JhcGgoKSAKYGBgCgoqIFVuZm9ydHVuYXRlbHksIEkgd2luZCB0aGUgZGVmYXVsdCB0byB0aGljay4gV2UgY2FuIGFsc28gc2NhbGUgaXQuCgpgYGB7cn0KZyAlPiUgZ2dyYXBoKGxheW91dCA9IGdfbGF5b3V0KSArIAogIGdlb21fZWRnZV9saW5rKGFlcyh3aWR0aCA9IHdlaWdodCksIGFscGhhID0gMC4yNSkgKwogIHNjYWxlX2VkZ2Vfd2lkdGgocmFuZ2UgPSBjKDAuMSwgMikpICsgCiAgZ2VvbV9ub2RlX3BvaW50KCkgKwogIHRoZW1lX2dyYXBoKCkgCmBgYAoKIyMjIENvbG9yCgoqIENvbG9yIGNhbiBhbHNvIGJlIHVzZWQgdG8gaGlnaGxpZ2h0IGVkZ2Ugc2lnbmlmaWNhbmNlIChjb250aW51b3VzKQoqIEhvd2V2ZXIsIGNvbG9yIGlzIG1vcmUgb2Z0ZW4gdXNlZCB0byBoaWdobGlnaHQgZGlmZmVyZW50IGVkZ2UgY2F0ZWdvcmllcy4KKiBOb3RpY2UsIHNpbmNlIHdlIHdhbnQgdG8gcmVwcmVzZW50IHRoZSBjb2xvcnMgb2YgcG90ZW50aWFsbHkgbXVsdGlwbGUgZWRnZXMgYmV0d2VlbiBhIG5vZGUgcGFpciwgSSBub3cgdXNlIHRoZSBgZ2VvbV9lZGdlX2ZhbmAgZ2VvbWV0cnkuCgpgYGB7cn0KZyAlPiUgZ2dyYXBoKGxheW91dCA9IGdfbGF5b3V0KSArIAogIGdlb21fZWRnZV9mYW4oYWVzKHNpemUgPSB3ZWlnaHQsCiAgICAgICAgICAgICAgICAgICAgIGNvbG9yID0geWVhciksIGFscGhhID0gMC4yNSkgKwogIGdlb21fbm9kZV9wb2ludCgpICsKICB0aGVtZV9ncmFwaCgpIApgYGAKCiMjIyBEZW5zaXR5CgoqIERlbnNpdHkgcGxvdHMgY2FuIGFsc28gYmUgdXNlZCB0byBoaWdobGlnaHQgZGVuc2VseSBjb25uZWN0ZWQgcmVnaW9ucy4KCmBgYHtyfQpnICU+JSBnZ3JhcGgobGF5b3V0ID0gZ19sYXlvdXQpICsgCiAgZ2VvbV9lZGdlX2xpbmsoYWVzKGNvbCA9IHllYXIpLCBhbHBoYSA9IDAuMSkgKwogIGdlb21fZWRnZV9kZW5zaXR5KGFlcyhmaWxsID0geWVhcikpICsKICBnZW9tX25vZGVfcG9pbnQoKSArCiAgdGhlbWVfZ3JhcGgoKSAKYGBgCgojIyMgRGlyZWN0aW9uYWxpdHkKCiogVGhlIGVhc2llc3Qgd2F5IHRvIGV4cHJlc3MgZGlyZWN0aW9uYWxpdHkgaXMgYnkgZGVmaW5pbmcgdGhlIGBhcnJvdygpYCwgd2hpY2ggY29tZXMgd2l0aCBvd24gYWVzdGV0aWNzLgoKYGBge3J9CmcgJT4lIGdncmFwaChsYXlvdXQgPSBnX2xheW91dCkgKyAKICBnZW9tX2VkZ2VfZmFuKGFlcyhjb2xvciA9IHllYXIpLCAKICAgICAgICAgICAgICAgIGFycm93ID0gYXJyb3coKSwKICAgICAgICAgICAgICAgIGFscGhhID0gMC41KSArCiAgZ2VvbV9ub2RlX3BvaW50KCkgKwogIHRoZW1lX2dyYXBoKCkgCmBgYAoKKiBUaGUgZGVmYXVsdCBvcGVuIGFycm93IGFuZCBvdGhlciBzZXR0aW5ncyBhcmUgYSBiaXQgdWdseSwgc28gd2UgdXNlIHNvbWUgYWRpdHRpb25hbCBhZXN0ZXRpY3MKCmBgYHtyfQpnICU+JSBnZ3JhcGgobGF5b3V0ID0gZ19sYXlvdXQpICsgCiAgZ2VvbV9lZGdlX2ZhbihhZXMoY29sb3IgPSB5ZWFyKSwgCiAgICAgICAgICAgICAgICBhcnJvdyA9IGFycm93KHR5cGUgPSAiY2xvc2VkIiwgbGVuZ3RoID0gdW5pdCgyLCAibW0iKSksCiAgICAgICAgICAgICAgICBzdGFydF9jYXAgPSBjaXJjbGUoMSwgIm1tIiksCiAgICAgICAgICAgICAgICBlbmRfY2FwID0gY2lyY2xlKDEsICJtbSIpLAogICAgICAgICAgICAgICAgYWxwaGEgPSAwLjUpICsKICBnZW9tX25vZGVfcG9pbnQoKSArCiAgdGhlbWVfZ3JhcGgoKSAKYGBgCgoqIEFub3RoZXIgbmljZSB0cmljayBpcyB0byB3b3JrIHdpdGggYWxwaGFzIG9yIGNvbG9ycywgd2hpY2ggY2hhbmdlIGJldHdlZW4gc3RhcnQgYW5kIGVuZCBub2RlLgoKYGBge3J9CmcgJT4lCiAgZ2dyYXBoKGxheW91dCA9IGdfbGF5b3V0KSArIAogIGdlb21fZWRnZV9mYW4oYWVzKGNvbG9yID0geWVhciwKICAgICAgICAgICAgICAgICAgICBhbHBoYSA9IHN0YXQoaW5kZXgpKSAjIE5vdGljZSB0aGF0CiAgICAgICAgICAgICAgICApICsKICBnZW9tX25vZGVfcG9pbnQoKSArCiAgdGhlbWVfZ3JhcGgoKSArIAogIHNjYWxlX2VkZ2VfYWxwaGEoIkVkZ2UgZGlyZWN0aW9uIiwgZ3VpZGUgPSAiZWRnZV9kaXJlY3Rpb24iKQpgYGAKCiogSXQgY2FuIGFsc28gYmUgcmVhbGx5IHByYWN0aWNhbCB0byBjaGFuZ2UgZWRnZSBjaGFyYWN0ZXJpc3RpY3MgYnkgdGhlIGNoYXJhY3RlcmlzdGljcyBvZiB0aGVpciBhZGphY2VudCBub2Rlcy4gCiogUmVtZW1iZXIsIHdpdGggYC5OKClgLCB3ZSBjYW4gYWNjZXNzIHRoZW0gdG8gZG8gc28uCgpgYGB7cn0Kc2V0LnNlZWQoMTMzNykKZyAlPiUKICBnZ3JhcGgobGF5b3V0ID0gJ25pY2VseScpICsgCiAgZ2VvbV9lZGdlX2ZhbihhZXMoY29sb3IgPSAuTigpJGNvbW11bml0eVt0b10pLCAjIE5vdGljZSB0aGF0CiAgICAgICAgICAgICAgICBhbHBoYSA9IDAuNSwKICAgICAgICAgICAgICAgIHNob3cubGVnZW5kID0gRkFMU0UpICsKICBnZW9tX25vZGVfcG9pbnQoYWVzKGNvbG9yID0gY29tbXVuaXR5KSwKICAgICAgICAgICAgICAgICAgc2hvdy5sZWdlbmQgPSBGQUxTRSkgKwogIHRoZW1lX2dyYXBoKCkgCmBgYAoKCiMjIExheW91dHMKCiogVGhlIGdyYXBoIGxheW91dCByZWZlcnMgdG8gdGhlIG5vZGUgcG9zaXRpb24gb24gdGhlIHJlZmVyZW5jZSBzeXN0ZW0uCgojIyMgT3JkaW5hcnkgZ3JhcGggc3R5bGUKCiogR3JhcGhzIGNhbiBiZSByZXByZXNlbnRlZCBpbiBzaW1wbGUgZ2VvbWV0cmllcyBzdWNoIGFzIHNxdWFyZXMsIGNpcmNsZXMsIGxpbmVzLCBvciByYW5kb21seS4KKiBPciwgc3BlY2lhbGl6ZWQgYWxnb3JpdGhtcyBjYW4gYmUgdXNlZCB0byBwb3NpdGlvbiBub2RlcyBhY2NvcmRpbmcgdG8gdGhlIHByb3BlcnRpZXMgb2YgdGhlaXIgY29ubmVjdGl2aXR5LgoqIFRoZXkgYXJlIHVzdWFsbHkgZGVzaWduZWQgdG8gaGlnaGxpZ2h0IGRpZmZlcmVudCBhc3BlY3RzIG9mIHRoZSBuZXR3b3JrLgoqIExldHMgaW5zcGVjdCBzb21lIHN0YW5kYXJkIGxheW91dHMKCmBgYHtyLCBmaWcuaGVpZ2h0PTMwLCBmaWcud2lkdGg9IDIwfQpzZXQuc2VlZCgxMzM3KQpsaWJyYXJ5KGdncHVicikKbGF5b3V0X2xpc3QgPC0gYygicmFuZG9tbHkiLCAibGluZWFyIiwgImNpcmNsZSIsIAogICAgICAgICAgICAgICAgICJncmlkIiwgImZyIiwgImtrIiwgCiAgICAgICAgICAgICAgICAgImdyYXBob3B0IiwgInN0cmVzcyIsICdtZHMnLCAKICAgICAgICAgICAgICAgICAnZGgnLCAnZHJsJywgJ2xnbCcpCgpnX2xpc3QgPC0gbGlzdChOVUxMKQpmb3IoaSBpbiAxOmxlbmd0aChsYXlvdXRfbGlzdCkpewogIGdfbGlzdFtbaV1dIDwtZyAlPiUgCiAgICBnZ3JhcGgobGF5b3V0ID0gbGF5b3V0X2xpc3RbaV0pICsgCiAgZ2VvbV9lZGdlX2ZhbihhZXMoY29sb3IgPSB5ZWFyLAogICAgICAgICAgICAgICAgICAgIHdpZHRoID0gd2VpZ2h0LAogICAgICAgICAgICAgICAgICAgIGFscGhhID0gd2VpZ2h0KSwgCiAgICAgICAgICAgICAgICBhcnJvdyA9IGFycm93KHR5cGUgPSAiY2xvc2VkIiwgbGVuZ3RoID0gdW5pdCgyLCAibW0iKSksCiAgICAgICAgICAgICAgICBzdGFydF9jYXAgPSBjaXJjbGUoMSwgIm1tIiksCiAgICAgICAgICAgICAgICBlbmRfY2FwID0gY2lyY2xlKDEsICJtbSIpLAogICAgICAgICAgICAgICAgc2hvdy5sZWdlbmQgPSBGQUxTRSkgKwogICAgc2NhbGVfZWRnZV93aWR0aChyYW5nZSA9IGMoMC4xLCAwLjUpKSArIAogICAgZ2VvbV9ub2RlX3BvaW50KGFlcyhzaXplID0gY2VudHJhbGl0eV9kZWdyZWUobW9kZSA9ICdpbicpLCAKICAgICAgICAgICAgICAgICAgICAgICAgY29sb3IgPSBjb21tdW5pdHksCiAgICAgICAgICAgICAgICAgICAgICAgIHNoYXBlID0gZ2VuZGVyKSwKICAgICAgICAgICAgICAgICAgICBzaG93LmxlZ2VuZCA9IEZBTFNFKSArCiAgICB0aGVtZV9ncmFwaCgpICsKICAgIGxhYnModGl0bGUgPSBwYXN0ZSgiTGF5b3V0OiIsIGxheW91dF9saXN0W2ldLCBzZXAgPSAiICIpKQp9CgpnZ2FycmFuZ2UocGxvdGxpc3QgPSBnX2xpc3QsIG5yb3cgPSA0LCBuY29sID0gMykKYGBgCgoKIyMjIEFyY3MgYW5kIGNpcmNsZXMKCmBgYHtyfQojIEFuIGFyYyBkaWFncmFtCmcgJT4lIGdncmFwaChsYXlvdXQgPSAnbGluZWFyJykgKyAKICBnZW9tX2VkZ2VfYXJjKCkgKwogIGdlb21fbm9kZV9wb2ludChhZXMoc2l6ZSA9IGNlbnRyYWxpdHlfZGVncmVlKCksIAogICAgICAgICAgICAgICAgICAgICAgY29sb3IgPSBjb21tdW5pdHkpLAogICAgICAgICAgICAgICAgICBzaG93LmxlZ2VuZCA9IEZBTFNFKSArCiAgICB0aGVtZV9ncmFwaCgpIApgYGAKCmBgYHtyfQojIEFuIGFyYyBkaWFncmFtCmcgJT4lIGdncmFwaChsYXlvdXQgPSAnbGluZWFyJywgY2lyY3VsYXIgPSBUUlVFKSArIAogIGdlb21fZWRnZV9hcmMoYWVzKGNvbG9yID0gLk4oKSRjb21tdW5pdHlbdG9dKSkgKwogIGdlb21fbm9kZV9wb2ludChhZXMoc2l6ZSA9IGNlbnRyYWxpdHlfZGVncmVlKCksIAogICAgICAgICAgICAgICAgICAgICAgY29sb3IgPSBjb21tdW5pdHkpLAogICAgICAgICAgICAgICAgICBzaG93LmxlZ2VuZCA9IEZBTFNFKSArCiAgICB0aGVtZV9ncmFwaCgpICsKICBjb29yZF9maXhlZCgpCmBgYAoKIyMjIEhpdmUgcGxvdHMKCiogQSBoaXZlIHBsb3QsIHdoaWxlIHN0aWxsIHRlY2huaWNhbGx5IGEgbm9kZS1lZGdlIGRpYWdyYW0sIGlzIGEgYml0IGRpZmZlcmVudCBmcm9tIHRoZSByZXN0IGFzIGl0IHVzZXMgaW5mb3JtYXRpb24gcGVydGFpbmluZyB0byB0aGUgbm9kZXMsIHJhdGhlciB0aGFuIHRoZSBjb25uZWN0aW9uIGluZm9ybWF0aW9uIGluIHRoZSBncmFwaC4gCiogVGhpcyBtZWFucyB0aGF0IGhpdmUgcGxvdHMsIHRvIGEgY2VydGFpbiBleHRlbnQgYXJlIG1vcmUgaW50ZXJwcmV0YWJsZSBhcyB3ZWxsIGFzIGxlc3MgdnVsbmVyYWJsZSB0byBzbWFsbCBjaGFuZ2VzIGluIHRoZSBncmFwaCBzdHJ1Y3R1cmUuIAoqIFRoZXkgYXJlIGxlc3MgY29tbW9uIHRob3VnaCwgc28gdXNlIHdpbGwgb2Z0ZW4gcmVxdWlyZSBzb21lIGFkZGl0aW9uYWwgZXhwbGFuYXRpb24uCgpgYGB7cn0KZyAlPiUKICBnZ3JhcGgobGF5b3V0ID0gJ2hpdmUnLCBheGlzID0gcG9wdWxhciwgc29ydC5ieSA9IGNlbnRyYWxpdHlfZGVncmVlKG1vZGUgPSAnaW4nKSkgKyAKICAgIGdlb21fZWRnZV9oaXZlKGFlcyhjb2xvdXIgPSB5ZWFyLCBhbHBoYSA9IC4uaW5kZXguLiksIHNob3cubGVnZW5kID0gRkFMU0UpICsgCiAgICBnZW9tX2F4aXNfaGl2ZShhZXMoY29sb3VyID0gcG9wdWxhciksIHNpemUgPSAzLCBsYWJlbCA9IEZBTFNFKSArIAogICAgY29vcmRfZml4ZWQoKSArIAogIHRoZW1lX2dyYXBoKCkgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICdib3R0b20nKQpgYGAKCiMjIyBTb2NpYWwgRmFicmljCgpgYGB7cn0KZyAlPiUgZ2dyYXBoKGxheW91dCA9ICdmYWJyaWMnLCBzb3J0LmJ5ID0gY29tbXVuaXR5KSArIAogIGdlb21fbm9kZV9yYW5nZShhZXMoY29sb3VyID0gY29tbXVuaXR5KSwgYWxwaGEgPSAwLjMpICsgCiAgZ2VvbV9lZGdlX3NwYW4oYWVzKGNvbCA9IC5OKCkkY29tbXVuaXR5W3RvXSksIGVuZF9zaGFwZSA9ICdjaXJjbGUnLCBhbHBoYSA9IDAuNSkgKyAKICBjb29yZF9maXhlZCgpICsgCiAgdGhlbWVfZ3JhcGgoKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gJ25vbmUnKQpgYGAKCgojIyBWaXN1YWxpemluZyBIaXJhcmNoaWNhbCBuZXR3b3JrcwoKKiBJZiB0aGUgbmV0d29yayBpcyBieSBkZWZpbml0aW9uIGhpZXJhcmNoaWNhbCwgZWRnZXMgY2FuIG9ubHkgZXhpc3QgYmV0d2VlbiBub2RlcyBvZiBoaWdoZXIgdG8gbG93ZXIgZGVwdCAoZWcuIHRyZWUgc3RydWN0dXJlcykuCiogVGhpcyBvZmZlcnMgdXMgcG9zc2liaWxpdHkgZm9yIHF1aXRlIHNvbWUgYWRpdHRpb25hbCB3YXlzIG9mIHJlcHJlc2VudGluZyBpdCB3aGljaCBhcmUgZ2VhcmVkIHRvd2FyZHMgaGlyYXJjaGljYWwgKD1uZXN0ZWQpIHN0cnVjdHVyZXMKCiogSGVyZSBhbiBleGFtcGxlIG9mIHRoZSBkZXBlbmRlbmN5IHN0cnVjdHVyZXMgb2YgdGhlIGBmbGFyZWAgcGFja2FnZQoKYGBge3J9CmVkZ2VzIDwtIGZsYXJlJGVkZ2VzCnZlcnRpY2VzIDwtIGZsYXJlJHZlcnRpY2VzICU+JSBhcnJhbmdlKG5hbWUpICU+JSBtdXRhdGUobmFtZT1mYWN0b3IobmFtZSwgbmFtZSkpCmNvbm5lY3Rpb25zIDwtIGZsYXJlJGltcG9ydHMKYGBgCgpgYGB7cn0KdmVydGljZXMgJT4lIGhlYWQoKQpgYGAKCmBgYHtyfQplZGdlcyAlPiUgaGVhZCgpCmBgYAoKYGBge3J9CmNvbm5lY3Rpb25zICU+JSBoZWFkKCkKYGBgCgpgYGB7cn0KZ19oaXIgPC0gdGJsX2dyYXBoKHZlcnRpY2VzLCBlZGdlcykKYGBgCgpgYGB7cn0KZ19oaXIKYGBgCgojIyMgVHJlZSBzdHJ1Y3R1cmVzCgpgYGB7cn0KZ19oaXIgJT4lIGdncmFwaCgndHJlZScpICsgCiAgZ2VvbV9lZGdlX2RpYWdvbmFsKCkgKwogIHRoZW1lX2dyYXBoKCkKYGBgCgoKYGBge3J9CmdfaGlyICU+JSBnZ3JhcGgoICdkZW5kcm9ncmFtJykgKyAKICAgIGdlb21fZWRnZV9lbGJvdygpICsKICB0aGVtZV9ncmFwaCgpCmBgYAoKYGBge3J9CmdfaGlyICU+JSBnZ3JhcGgoJ2RlbmRyb2dyYW0nLCBjaXJjdWxhciA9IFRSVUUpICsgCiAgICBnZW9tX2VkZ2VfZWxib3coKSArIAogICAgY29vcmRfZml4ZWQoKSArCiAgdGhlbWVfZ3JhcGgoKQpgYGAKCmBgYHtyfQojIFRoZSBjb25uZWN0aW9uIG9iamVjdCBtdXN0IHJlZmVyIHRvIHRoZSBpZHMgb2YgdGhlIGxlYXZlczoKZnJvbSA9IG1hdGNoKGNvbm5lY3Rpb25zJGZyb20sIHZlcnRpY2VzJG5hbWUpCnRvID0gbWF0Y2goY29ubmVjdGlvbnMkdG8sIHZlcnRpY2VzJG5hbWUpCmBgYAoKCmBgYHtyfQpnX2hpciAlPiUgZ2dyYXBoKGxheW91dCA9ICdkZW5kcm9ncmFtJywgY2lyY3VsYXIgPSBUUlVFKSArIAogIGdlb21fY29ubl9idW5kbGUoZGF0YSA9IGdldF9jb24oZnJvbSA9IGZyb20sIHRvID0gdG8pLCBhbHBoYSA9IDAuMSkgKyAKICBnZW9tX2VkZ2VfZGlhZ29uYWwwKCkgKwogICNnZW9tX25vZGVfdGV4dChhZXMoZmlsdGVyID0gbGVhZiwgYW5nbGUgPSBub2RlX2FuZ2xlKHgsIHkpLCBsYWJlbCA9IHNob3J0TmFtZSksCiAgIyBoanVzdCA9ICdvdXR3YXJkJywgc2l6ZSA9IDIpICsKICBjb29yZF9maXhlZCgpICsKICB0aGVtZV9ncmFwaCgpCmBgYAoKIyMjIE5vbi1lZGdlLWJhc2VkIAoKYGBge3J9CiMgQW4gaWNpY2xlIHBsb3QKZ19oaXIgJT4lIGdncmFwaCgncGFydGl0aW9uJykgKyAKICBnZW9tX25vZGVfdGlsZShhZXMoZmlsbCA9IGRlcHRoKSwgc2l6ZSA9IDAuMjUpICsKICB0aGVtZV9ncmFwaCgpCmBgYAoKYGBge3J9CiMgQSBzdW5idXJzdCBwbG90CmdfaGlyICU+JSBnZ3JhcGgoJ3BhcnRpdGlvbicsIGNpcmN1bGFyID0gVFJVRSkgKyAKICBnZW9tX25vZGVfYXJjX2JhcihhZXMoZmlsbCA9IGRlcHRoKSwgc2l6ZSA9IDAuMjUpICsgCiAgY29vcmRfZml4ZWQoKSArCiAgdGhlbWVfZ3JhcGgoKQpgYGAKCmBgYHtyfQpnX2hpciAlPiUgZ2dyYXBoKCdjaXJjbGVwYWNrJykgKyAjICwgd2VpZ2h0ID0gc2l6ZQogIGdlb21fbm9kZV9jaXJjbGUoYWVzKGZpbGwgPSBkZXB0aCksIHNpemUgPSAwLjI1LCBuID0gNTApICsgCiAgY29vcmRfZml4ZWQoKSArCiAgdGhlbWVfZ3JhcGgoKQpgYGAKCgoKCmBgYHtyfQpnX2hpciAlPiUgZ2dyYXBoKCd0cmVlbWFwJykgKyAKICBnZW9tX25vZGVfdGlsZShhZXMoZmlsbCA9IGRlcHRoKSwgc2l6ZSA9IDAuMjUpICsKICB0aGVtZV9ncmFwaCgpCmBgYAoKIyMgR2Vvc3BhdGlhbCBuZXR3b3JrcwoKIyMjIERlZmluaW5nIGEgbWFwCgpgYGB7cn0KbGlicmFyeShtYXBzKQpgYGAKCmBgYHtyfQptYXBfdXMgPC0gbWFwX2RhdGEoInVzYSIpCmBgYAoKYGBge3J9Cm1hcF91cyAlPiUKICBoZWFkKCkKYGBgCgojIyMgR2V0dGluZyBzb21lIG5ldHdvcmsgZGF0YQoKYGBge3J9CmxpYnJhcnkoYW55ZmxpZ2h0cykKYGBgCgpgYGB7cn0KdXNfYWlycG9ydHMgPC0gZ2V0X2FpcnBvcnRzKCkgJT4lCiAgZmlsdGVyKGxhdCA+PSAyNCAmIGxhdCA8PSA0OSAmIGxvbiA+PSAtMTI0ICYgbG9uIDw9IC02NikgICU+JQogIHJlbmFtZShuYW1lX2Z1bGwgPSBuYW1lLAogICAgICAgICBuYW1lID0gZmFhKQpgYGAKCmBgYHtyfQp1c19haXJwb3J0cyAlPiUgaGVhZCgpCmBgYAoKYGBge3J9CmZsaWdodHMgPC0gZ2V0X2ZsaWdodHMoc3RhdGlvbiA9IHVzX2FpcnBvcnRzICU+JSBwdWxsKG5hbWUpLCB5ZWFyID0gMjAxNSwgbW9udGggPSA1KQpgYGAKCmBgYHtyfQpmbGlnaHRzICU+JSBoZWFkKCkKYGBgCgoKYGBge3J9CmVkZ2VzIDwtIGZsaWdodHMgJT4lIGNvdW50KG9yaWdpbiwgZGVzdCwgc29ydCA9IFRSVUUpICU+JQogIHJlbmFtZShmcm9tID0gb3JpZ2luLCB0byA9IGRlc3QsIHdlaWdodCA9IG4pICU+JQogIHNlbWlfam9pbih1c19haXJwb3J0cywgYnkgPSBjKCdmcm9tJyA9ICduYW1lJykpICU+JQogIHNlbWlfam9pbih1c19haXJwb3J0cywgYnkgPSBjKCd0bycgPSAnbmFtZScpKSAlPiUgCiAgZmlsdGVyKHBlcmNlbnRfcmFuayh3ZWlnaHQpID49IDAuMjUpCmBgYAoKYGBge3J9CmdfZ2VvIDwtIHRibF9ncmFwaChub2RlcyA9IHVzX2FpcnBvcnRzLCBlZGdlcyA9IGVkZ2VzLCBkaXJlY3RlZCA9IFRSVUUpICVOPiUKICBmaWx0ZXIoIW5vZGVfaXNfaXNvbGF0ZWQoKSkKYGBgCgoKIyMjIENvbnN0cnVjdGluZyBhIGdyYXBoCgpgYGB7cn0KY29vcmRzIDwtIGdfZ2VvICVOPiUgCiAgYXNfdGliYmxlKCkgJT4lIAogIHNlbGVjdChsYXQsIGxvbikgJT4lCiAgcmVuYW1lKHggPSBsb24sIHkgPSBsYXQpCmBgYAoKYGBge3J9CmdfZ2VvICU+JQogIGdncmFwaChsYXlvdXQgPSBjb29yZHMpICsKICBnZW9tX3BvbHlnb24oZGF0YSA9IG1hcF91cywgYWVzKHg9bG9uZywgeSA9IGxhdCwgZ3JvdXAgPSBncm91cCksIGZpbGwgPSAiI0NFQ0VDRSIsIGNvbG9yID0gIiM1MTUxNTEiKSArIAogIGdlb21fZWRnZV9hcmMoYWVzKHdpZHRoID0gd2VpZ2h0LCAgIAogICAgICAgICAgICAgICAgICAgIGFscGhhID0gd2VpZ2h0LAogICAgICAgICAgICAgICAgICAgIGZpbHRlciA9IHBlcmNlbnRfcmFuayh3ZWlnaHQpID49IDAuNzUsCiAgICAgICAgICAgICAgICAgICAgY2lyY3VsYXIgPSBGQUxTRSksCiAgICAgICAgICAgICAgICBzdHJlbmd0aCA9IDAuMzMsCiAgICAgICAgICAgICAgICBjb2xvciA9ICdjaG9jb2xhdGUyJykgKwogIGdlb21fbm9kZV9wb2ludChhZXMoc2l6ZSA9IGNlbnRyYWxpdHlfZGVncmVlKCksCiAgICAgICAgICAgICAgICAgICAgICBjb2wgPSBjZW50cmFsaXR5X2RlZ3JlZSgpKSkgKyAKICBzY2FsZV9lZGdlX3dpZHRoX2NvbnRpbnVvdXMocmFuZ2UgPSBjKDAuMSwgMSkpICsgCiAgY29vcmRfZml4ZWQoMS4zKSArIAogIHRoZW1lX2dyYXBoKCkgKyAKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAnbm9uZScpCmBgYAoKIyMgSW50ZXJhY3RpdmUgbmV0d29ya3MKCiogVGhlcmUgYXJlIG51bWVyb3VzIHdheXMgdG8gdG8gaW50ZXJhY3RpdmUgbmV0d29yayB2aXN1YWxpemF0aW9ucyBpbiBSCiogRm9yIHRoZSBzYWtlIG9mIHRpbWUsIEkganVzdCBzaG93IHlvdSB3aGF0IEkgZmluZCB0aGUgZWFzaWVzdCBhbmQgbW9zdCBjb25zaXN0ZW50IGltcGxlbWVudGF0aW9uIChwbG90bHkgdW5mb3J0dW5hdGVseSBkb2VzIGJ5IG5vdyBub3Qgc3VwcG9ydCBnZ3JhcGgpCgpgYGB7cn0KbGlicmFyeShnZ2lyYXBoKQpgYGAKCgpgYGB7cn0KZ19wbG90X2ludCA8LSBnICU+JSAKICBnZ3JhcGgobGF5b3V0ID0gbGF5b3V0X2xpc3RbaV0pICsgCiAgZ2VvbV9lZGdlX2ZhbihhZXMoY29sb3IgPSB5ZWFyLAogICAgICAgICAgICAgICAgICAgIHdpZHRoID0gd2VpZ2h0LAogICAgICAgICAgICAgICAgICAgIGFscGhhID0gd2VpZ2h0KSwgCiAgICAgICAgICAgICAgICBhcnJvdyA9IGFycm93KHR5cGUgPSAiY2xvc2VkIiwgbGVuZ3RoID0gdW5pdCgyLCAibW0iKSksCiAgICAgICAgICAgICAgICBzdGFydF9jYXAgPSBjaXJjbGUoMSwgIm1tIiksCiAgICAgICAgICAgICAgICBlbmRfY2FwID0gY2lyY2xlKDEsICJtbSIpLAogICAgICAgICAgICAgICAgc2hvdy5sZWdlbmQgPSBGQUxTRSkgKwogICAgc2NhbGVfZWRnZV93aWR0aChyYW5nZSA9IGMoMC4xLCAwLjUpKSArIAogICAgZ2VvbV9ub2RlX3BvaW50KGFlcyhzaXplID0gY2VudHJhbGl0eV9kZWdyZWUobW9kZSA9ICdpbicpLCAKICAgICAgICAgICAgICAgICAgICAgICAgY29sb3IgPSBjb21tdW5pdHksCiAgICAgICAgICAgICAgICAgICAgICAgIHNoYXBlID0gZ2VuZGVyKSwKICAgICAgICAgICAgICAgICAgICBzaG93LmxlZ2VuZCA9IEZBTFNFKSArCiAgZ2VvbV9wb2ludF9pbnRlcmFjdGl2ZShhZXMoeCwgeSwgIyBOb3RpY2UgdGhpcyBleHRyYSBsYXllcgogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRvb2x0aXAgPSBsYWJlbCwgZGF0YV9pZCA9IG5hbWUsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNpemUgPSBjZW50cmFsaXR5X2RlZ3JlZShtb2RlID0gJ2luJykpLCBhbHBoYSA9IDAuMDEpICsgIAogICAgdGhlbWVfZ3JhcGgoKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gJ25vbmUnKQpgYGAKCmBgYHtyLCBmaWcuaGVpZ2h0PTEwLCBmaWcud2lkdGg9MTB9CmdpcmFmZShnZ29iaiA9IGdfcGxvdF9pbnQsIHdpZHRoX3N2ZyA9IDEwLCBoZWlnaHRfc3ZnID0gMTApICU+JSAKICAgIGdpcmFmZV9vcHRpb25zKG9wdHNfem9vbShtYXggPSAxMCksIG9wdHNfdG9vbHRpcChvcGFjaXR5ID0gMC43KSApCmBgYAoKCiMgWW91ciB0dXJuClBsZWFzZSBkbyAqKkV4ZXJjaXNlIDEqKiBpbiB0aGUgY29ycmVzcG9uZGluZyBzZWN0aW9uIG9uIGBHaXRodWJgLiBUaGlzIHRpbWUgeW91IGFyZSBhYm91dCB0byBkbyB5b3VyIG93biBiaWJsaW9ncmFwaGljIGFuYWx5c2lzIQoKIyBFbmRub3RlcwoKIyMjIE1vcmUgaW5mbwoKIyMjIyBQYWNrYWdlcyAmIEVjb3N5c3RlbQoKKiBgdGlkeWdyYXBoYCBbaGVyZV0oaHR0cHM6Ly90aWR5Z3JhcGguZGF0YS1pbWFnaW5pc3QuY29tLykKKiBgZ2dyYXBoYCBbaGVyZV0oaHR0cHM6Ly9nZ3JhcGguZGF0YS1pbWFnaW5pc3QuY29tLykKKiBgZ2dpcmFwaGAgW2hlcmVdKGh0dHBzOi8vZGF2aWRnb2hlbC5naXRodWIuaW8vZ2dpcmFwaC8pCgojIyMjIE90aGVyIHNvdWNlcwoKKiBbSW50cm86IE5ldHdvcmsgVmlzdWFsaXphdGlvbnMgaW4gUiB1c2luZyBnZ3JhcGggYW5kIGdyYXBobGF5b3V0c10oaHR0cDovL21yLnNjaG9jaGFzdGljcy5uZXQvbmV0Vml6Ui5odG1sKTogR29vZCBpbnRyb3MgdG8gZ2dyYXBoIGZ1bmN0aW9uYWxpdHkgJiBmaW5ldHVuaW5nLgoqIFtLYXRoZXJpbmUgT2dueWFub3ZhJ3MgQmxvZyldKGh0dHBzOi8va2F0ZXRvLm5ldC8pOiBIZXIgYmxvZyBpcyBmdWxsIG9mIHNvbWUgb2YgdGhlIG1vc3QgY29tcGxldGUgaW50cm9kdWN0aW9ucyB0byBuZXR3b3JrIHZpc3VhbGl6YXRpb24uIERvZXMgdXNlIGlncmFwaCwgYnV0IHRoZXJlIGFyZSBmb3Igc3VyZSAxIG9yIHRyaWNrcyB5b3UgY2FuIGxlYXJuIGZyb20gaGVyLCBwYXJ0aWN1bGFybHkgd2hlbiBpdCdzIGFib3V0IGludGVyYWN0aXZlIG5ldHdvcmsgdmlvc3VhbGl6YXRpb24uCiogW0dvb2Qgc2xpZGVkZWNrIG9uIHN0YXRpYyAvIGludGVyYWN0aXZlIG5ldHdvcmsgdml6IGluIFJdKGh0dHA6Ly9jdXJsZXlsYWIucHN5Y2guY29sdW1iaWEuZWR1L25ldHZpei9uZXR2aXoxLmh0bWwjLyk6IFdvcnRoIHZpc2l0aW5nCgoKIyMjIFNlc3Npb24gaW5mbwpgYGB7cn0Kc2Vzc2lvbkluZm8oKQpgYGAKCg==
Social Fabric