Overview

This report analyses 9,001 Fonterra social media posts from six platforms (Facebook, Instagram, LinkedIn, TikTok, Twitter/X and YouTube), published between 2010 and 2026.

The analysis has three parts:

Install and load packages and import and preview data

# Set consistent options for tables and figures
knitr::opts_chunk$set(fig.width = 8, fig.height = 5, echo = TRUE)

# Install and load packages
if(!require("pacman"))install.packages("pacman")
## Loading required package: pacman
pacman::p_load(readr, dplyr, tidyr, stringr, lubridate, purrr, ggplot2, gridExtra, GGally, corrplot,forcats,rstatix,DescTools, skimr, finalfit)
pacman::p_load(tidytext, textstem, wordcloud, textdata, topicmodels, reshape2, 
               knitr)
# Import the Fonterra social media data
raw_post_data <- read_csv(
  "fonterra.csv",
  na = c("", "NA"),
  show_col_types = FALSE
)

# Preview the structure and first rows of the data
glimpse(raw_post_data)
## Rows: 9,001
## Columns: 9
## $ platform       <chr> "Twitter/X", "Twitter/X", "Twitter/X", "Twitter/X", "Tw…
## $ url            <chr> "https://x.com/Fonterra/status/1891573780557988062", "h…
## $ createdAt      <chr> "2025/02/17 19:41:47", "2024/12/05 22:23:33", "2024/12/…
## $ text           <chr> "Today we announced new incentives to help farms reduce…
## $ media          <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
## $ commentCount   <dbl> 66, 1, 1, 303, 8, 2, 2, 0, 2, 0, 1, 1, 1, 0, 0, 1, 0, 0…
## $ shareCount     <dbl> 2, 0, 0, 102, 3, 0, 0, 0, 2, 0, 2, 0, 0, 0, 0, 1, 0, 0,…
## $ likeCount      <dbl> 15, 0, 0, 704, 16, 1, 6, 0, 19, 0, 16, 1, 3, 18, 2, 14,…
## $ videoplayCount <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
head(raw_post_data)
## # A tibble: 6 × 9
##   platform  url          createdAt text  media commentCount shareCount likeCount
##   <chr>     <chr>        <chr>     <chr> <chr>        <dbl>      <dbl>     <dbl>
## 1 Twitter/X https://x.c… 2025/02/… "Tod… <NA>            66          2        15
## 2 Twitter/X https://x.c… 2024/12/… "@ah… <NA>             1          0         0
## 3 Twitter/X https://x.c… 2024/12/… "@ah… <NA>             1          0         0
## 4 Twitter/X https://x.c… 2024/12/… "At … <NA>           303        102       704
## 5 Twitter/X https://x.c… 2024/11/… "We’… <NA>             8          3        16
## 6 Twitter/X https://x.c… 2024/10/… "Tod… <NA>             2          0         1
## # ℹ 1 more variable: videoplayCount <dbl>

Data preparation

Non-text data

There are some criteria taken into account during the non-text data cleaning:

  • createdAt variable was changed to UTC date-time, since according to the instruction, all the timestamps were collected in UTC time zone.
  • There are some timestamps with 00:00:00, which can be interpreted as date only without time information. Therefore, time will not be considered, only date-related features (year, month, weekday).
  • Likes and “digs” on TikTok are regarded as similar. Comments (except Twitter/X) and replies (Twitter/X) are also considered to be similar.
  • media feature contains different capitalisation (video, Video), so it should be standardised (Image, Video, Sidecar). Empty value was assigned Not recorded, rather than “no media”, since media type cannot be determined.
  • High levels of engagement are maintained, since they probably are real examples of viral posts rather than outliers; rather than deleting them, medians and non-parametric tests are employed later on.
  • Posts without text are maintained during engagement analysis but deleted only when text analysis begins, since there will be nothing to tokenize then.
  • The missing engagements remain missing as opposed to using 0s since certain sites such as Instagram and YouTube do not provide the sharing metrics.
  • There are no duplicates in the form of identical rows or URLs. However, there is one instance where two posts have the same date and text for Facebook and Instagram (January 11, 2019, 06:00:24). This is not an error but cross-posting.
# confirm all expected columns are present before cleaning
necessary_cols <- c("platform", "url", "createdAt", "text", "media",
                  "commentCount", "shareCount", "likeCount", "videoplayCount")
stopifnot(all(necessary_cols %in% names(raw_post_data)))
# trim whitespace, parse the timestamp, and standardise the media labels
posts <- raw_post_data |>
  mutate(
    platform   = str_squish(platform),
    url        = str_squish(url),
    createdAt  = ymd_hms(createdAt, tz = "UTC", quiet = TRUE),
    post_media = case_when(
      is.na(media)                             ~ "Not recorded",
      str_to_lower(media) %in% c("photo", "image") ~ "Image",
      str_to_lower(media) == "video"            ~ "Video",
      str_to_lower(media) == "sidecar"          ~ "Sidecar",
      TRUE ~ str_to_title(media)
    )
  )

# confirm every timestamp parsed successfully
stopifnot(sum(is.na(posts$createdAt)) == 0)
# check for exact duplicate rows and duplicate URLs
tibble(
  check  = c("Exact duplicate rows", "Duplicate URLs"),
  number = c(sum(duplicated(posts)), sum(duplicated(posts$url)))
) |>
  kable(caption = "Duplicate checks")
Duplicate checks
check number
Exact duplicate rows 0
Duplicate URLs 0
# check for identical text posted at the exact same time, and which platforms
posts |>
  filter(!is.na(text)) |>
  group_by(createdAt, text) |>
  filter(n() > 1) |>
  summarise(platforms = paste(platform, collapse = " & "), records = n(), .groups = "drop") |>
  select(createdAt, platforms, records) |>
  kable(caption = "Same-time, same-text records (kept as likely cross-posting)")
Same-time, same-text records (kept as likely cross-posting)
createdAt platforms records
2019-01-11 06:00:24 Facebook & Instagram 2
# missing values by column
posts |>
  summarise(across(everything(), ~ sum(is.na(.x)))) |>
  pivot_longer(everything(), names_to = "column", values_to = "missing") |>
  mutate(pct_missing = round(100 * missing / nrow(posts), 1)) |>
  kable(caption = "Missing values by column")
Missing values by column
column missing pct_missing
platform 0 0.0
url 0 0.0
createdAt 0 0.0
text 82 0.9
media 6994 77.7
commentCount 293 3.3
shareCount 951 10.6
likeCount 0 0.0
videoplayCount 8115 90.2
post_media 0 0.0
# check whether missing engagement counts are platform-specific
posts |>
  group_by(platform) |>
  summarise(
    missing_comments = sum(is.na(commentCount)),
    missing_shares    = sum(is.na(shareCount)),
    missing_plays     = sum(is.na(videoplayCount)),
    .groups = "drop"
  ) |>
  kable(caption = "Missing engagement counts by platform")
Missing engagement counts by platform
platform missing_comments missing_shares missing_plays
Facebook 269 0 1115
Instagram 0 519 454
LinkedIn 24 37 326
TikTok 0 0 0
Twitter/X 0 0 6220
YouTube 0 395 0
# check for impossible negative counts
posts |>
  summarise(across(c(commentCount, shareCount, likeCount, videoplayCount),
                    ~ sum(.x < 0, na.rm = TRUE))) |>
  pivot_longer(everything(), names_to = "column", values_to = "negative_values") |>
  kable(caption = "Any impossible negative counts?")
Any impossible negative counts?
column negative_values
commentCount 0
shareCount 0
likeCount 0
videoplayCount 0

New variables created

Why each new variable is useful:

  • post_id gives each post a unique identifier which is required for the text analysis and topic model.
  • post_date, post_year, post_month, post_yearmonth and post_weekday break the timestamp into components, so posting patterns can be examined without altering the original createdAt.
  • post_media is a tidy content-format label that can be compared against engagement.
  • word_count and char_count measure caption length, so length can be tested against engagement.
  • total_reaction sums whichever of likes/comments/shares a post has data for. It is a useful descriptive number but not the main cross-platform outcome, since shares are structurally missing on some platforms.
  • has_play_data flags the subset of posts where a plays-vs-likes comparison is possible.
  • has_hashtag flags whether a post used a # - a two-level variable used for a t-test/Mann-Whitney comparison later.
#new_variables
posts <- posts |>
  mutate(
    post_id        = row_number(),
    post_date      = as.Date(createdAt),
    post_year      = year(createdAt),
    post_month     = month(createdAt, label = TRUE, abbr = TRUE),
    post_yearmonth = floor_date(createdAt, unit = "month"),
    post_weekday   = wday(createdAt, label = TRUE, abbr = FALSE, week_start = 1),
    word_count     = str_count(coalesce(text, ""), boundary("word")),
    char_count     = str_length(coalesce(text, "")),
    total_reaction = likeCount + coalesce(commentCount, 0) + coalesce(shareCount, 0),
    has_play_data  = !is.na(videoplayCount),
    has_hashtag    = str_detect(coalesce(text, ""), "#\\w+")
  )

# preview the new variables
posts |>
  select(post_id, platform, createdAt, post_year, post_month, post_weekday,
         post_media, word_count, total_reaction, has_play_data) |>
  head(10) |>
  kable(caption = "New variables - first 10 rows")
New variables - first 10 rows
post_id platform createdAt post_year post_month post_weekday post_media word_count total_reaction has_play_data
1 Twitter/X 2025-02-17 19:41:47 2025 Feb Monday Not recorded 54 83 FALSE
2 Twitter/X 2024-12-05 22:23:33 2024 Dec Thursday Not recorded 23 1 FALSE
3 Twitter/X 2024-12-05 21:05:34 2024 Dec Thursday Not recorded 27 1 FALSE
4 Twitter/X 2024-12-01 23:04:24 2024 Dec Sunday Not recorded 24 1109 FALSE
5 Twitter/X 2024-11-10 19:39:51 2024 Nov Sunday Not recorded 44 27 FALSE
6 Twitter/X 2024-10-10 19:36:23 2024 Oct Thursday Not recorded 46 3 FALSE
7 Twitter/X 2024-10-09 19:38:11 2024 Oct Wednesday Not recorded 51 8 FALSE
8 Twitter/X 2024-10-09 19:22:00 2024 Oct Wednesday Not recorded 54 0 FALSE
9 Twitter/X 2024-09-29 19:43:38 2024 Sep Sunday Not recorded 39 23 FALSE
10 Twitter/X 2024-09-28 04:00:18 2024 Sep Saturday Not recorded 63 0 FALSE

The posts run from 08 November 2010 to 28 July 2026. Because likes/comments/shares are cumulative counts rather than a rate over a fixed window, older posts have had more time to accumulate engagement than newer ones.

Text data

Posts with no text content are excluded, as there is no data to tokenize. Links, @mentions, HTML artifacts, and the # symbol are stripped before tokenization takes place, although the text within the # symbol is usually kept, as it relates to the topic. Stop words, along with a few special dataset filler words, are excluded.Curly apostrophes have been converted into straight apostrophes to ensure that contractions like we’re are excluded from the list of stop words. No lemmatisation was performed; therefore, different forms of a word are considered independently, for example, award and awards.

# custom stop words: generic filler that adds no subject-matter meaning
additional_stopwords <- tibble(
  word = c("fonterra", "amp", "https", "http", "www", "com", "co",
           "nz", "new", "zealand", "uh", "um", "yeah", "really",
           "just", "know", "like", "good", "great", "thanks", "thank",
           "hi", "hey", "cheers", "today", "year", "years", "day",
           "time", "going", "make", "right", "week", "look", "think",
           "got", "need", "want", "lot", "share", "come", "way",
           "doing", "sure", "things", "bit", "read", "link")
)
# remove links, @mentions and HTML artefacts, lower-case, then tidy whitespace
posts_with_text <- posts |>
  filter(!is.na(text), str_squish(text) != "") |>
  mutate(
    text_clean = text |>
      str_to_lower() |>
      str_replace_all("[’‘]", "'") |>
      str_replace_all("https?://\\S+|www\\.\\S+", " ") |>
      str_replace_all("@\\w+", " ") |>
      str_replace_all("&amp;", " and ") |>
      str_replace_all("#", " ") |>
      str_squish()
  ) |>
  select(post_id, platform, createdAt, text_clean)
# tokenise into one word per row (unnest_tokens() lower-cases and strips
# punctuation automatically)
post_words_raw <- posts_with_text |>
  unnest_tokens(output = word, input = text_clean, token = "words")

# remove standard stop words, the custom list above, and any token with a digit
post_words <- post_words_raw |>
  filter(!word %in% stop_words$word,
         !word %in% additional_stopwords$word,
         !str_detect(word, "[0-9]"),
         str_length(word) >= 3)

# how many posts were available for text analysis
tibble(
  measure = c("All posts", "Posts with usable text", "Posts dropped (no text)"),
  value = c(nrow(posts), n_distinct(posts_with_text$post_id),
            nrow(posts) - n_distinct(posts_with_text$post_id))
) |>
  kable(caption = "Text preparation summary")
Text preparation summary
measure value
All posts 9001
Posts with usable text 8919
Posts dropped (no text) 82

Data-informed insights and recommendations

Topics content creators have been posting about

Term frequency shows which individual words appear most often; the LDA topic model groups words that tend to occur together into broader context. The two are complementary.

# count each word, then keep the 20 most frequent
word_counts <- post_words |> count(word, sort = TRUE, name = "frequency")
top_words <- word_counts |> slice_max(n = 20, order_by = frequency)

kable(top_words, caption = "Top 20 words in Fonterra's social media content")
Top 20 words in Fonterra’s social media content
word frequency
milk 2127
dairy 1512
farmers 1492
farm 1213
team 1147
people 767
food 661
world 604
business 575
support 537
products 518
water 502
cheese 490
site 485
price 450
learn 411
global 404
tanker 378
china 376
quality 370
# bar chart of the top 20 words, shaded by frequency
top_words |>
  mutate(word = fct_reorder(word, frequency)) |>
  ggplot(aes(x = word, y = frequency, fill = frequency)) +
  geom_col() +
  coord_flip() +
  scale_fill_gradient(low = "#d9f2d0", high = "#1b4332", guide = "none") +
  labs(title = "Top 20 words across Fonterra's social media content",
       subtitle = "How often each word shows up across every platform",
       x = NULL, y = "Number of times used") +
  theme_minimal(base_size = 12)

The most common words in this word list include milk, dairy, farmers, and farm; then come team, people, food, world, business, support, products, and water. Some other common words in this list are cheese, price, China, global, and tanker. The contents of this list are not confined only to marketing of products but include many other topics.

# drop words that are too rare (fewer than 5 posts) or too common (over half
# of all posts) - both extremes make topics harder to interpret
word_doc_counts <- post_words |>
  distinct(post_id, word) |>
  count(word, name = "n_docs")

n_text_posts <- n_distinct(post_words$post_id)

common_closer <- word_doc_counts |>
  filter(n_docs >= 5, n_docs <= 0.50 * n_text_posts)

input_topic_words <- post_words |>
  inner_join(common_closer, by = "word")

# build the document-term matrix that LDA() requires
topic_dtm <- input_topic_words |>
  count(post_id, word, name = "n") |>
  cast_dtm(document = post_id, term = word, value = n)
# fit a six-topic model (k = 6, as suggested in the brief); the seed in
# control= makes it reproducible
topic_model <- LDA(topic_dtm, k = 6, control = list(seed = 1234))

# quick list of the top words per topic (no probabilities attached)
get_terms(topic_model, 10)
##       Topic 1    Topic 2     Topic 3       Topic 4           Topic 5      
##  [1,] "milk"     "milk"      "support"     "dairy"           "farm"       
##  [2,] "farmers"  "dairy"     "school"      "tanker"          "farmers"    
##  [3,] "price"    "world"     "community"   "awards"          "water"      
##  [4,] "business" "food"      "kids"        "farmers"         "cows"       
##  [5,] "john"     "products"  "farmers"     "award"           "music"      
##  [6,] "million"  "anchor"    "farm"        "industry"        "farming"    
##  [7,] "china"    "cream"     "milk"        "congratulations" "dairy"      
##  [8,] "strong"   "nutrition" "team"        "agchatnz"        "environment"
##  [9,] "forecast" "cheese"    "people"      "team"            "people"     
## [10,] "food"     "protein"   "communities" "māori"           "quality"    
##       Topic 6    
##  [1,] "team"     
##  [2,] "site"     
##  [3,] "cheese"   
##  [4,] "hope"     
##  [5,] "check"    
##  [6,] "event"    
##  [7,] "christmas"
##  [8,] "gdt"      
##  [9,] "listen"   
## [10,] "global"
# word-topic probabilities, needed for the chart below
topic_words <- tidy(topic_model, matrix = "beta") |>
  group_by(topic) |>
  slice_max(order_by = beta, n = 10) |>
  ungroup()

topic_words |>
  arrange(topic, desc(beta)) |>
  group_by(topic) |>
  summarise(top_words = paste(term, collapse = ", "), .groups = "drop") |>
  kable(caption = "Top words for each of the six topics")
Top words for each of the six topics
topic top_words
1 milk, farmers, price, business, john, million, china, strong, forecast, food
2 milk, dairy, world, food, products, anchor, cream, nutrition, cheese, protein
3 support, school, community, kids, farmers, farm, milk, team, people, communities
4 dairy, tanker, awards, farmers, award, industry, congratulations, agchatnz, team, māori
5 farm, farmers, water, cows, music, farming, dairy, environment, people, quality
6 team, site, cheese, hope, check, event, christmas, gdt, listen, global
# bar chart of the top words per topic, one colour per topic
topic_words |>
  ggplot(aes(x = beta, y = reorder_within(x = term, by = beta, within = topic),
             fill = factor(topic))) +
  geom_col(show.legend = FALSE) +
  facet_wrap(~ topic, scales = "free") +
  scale_y_reordered() +
  scale_fill_brewer(palette = "Set2") +
  labs(subtitle = "Top 10 highest-probability words per LDA topic",
       x = "Word-topic probability (beta)", y = NULL) +
  theme_minimal(base_size = 10)

# ---- Word cloud of the 80 most frequent words ----
set.seed(2026)
wordcloud(words = word_counts$word, freq = word_counts$frequency, max.words = 80, random.order = FALSE,
          scale = c(3.5, 0.6), colors = RColorBrewer::brewer.pal(8, "Dark2"))

# assign each post its single most likely topic, with the model's confidence
post_topics <- tidy(topic_model, matrix = "gamma") |>
  group_by(document) |>
  slice_max(gamma, n = 1, with_ties = FALSE) |>
  ungroup() |>
  transmute(post_id = as.integer(as.character(document)), topic, topic_confidence = gamma)

# share of posts falling into each topic
post_topics |>
  count(topic, name = "posts") |>
  mutate(pct = round(100 * posts / sum(posts), 1)) |>
  kable(caption = "Share of posts falling into each topic")
Share of posts falling into each topic
topic posts pct
1 1087 12.4
2 1471 16.8
3 1484 16.9
4 1535 17.5
5 1387 15.8
6 1803 20.6

Commentary on topic insights

The following six themes have been developed based on the most frequent words of each topic (numbers are random, hence description needs to be confirmed using the table “Top words for each of the six topics” on each knit). The first topic deals with business and pricing (price, million, China, strong, forecast); the second topic deals with products and nutrition (products, cream, nutrition, cheese, protein); the third topic deals with community and schools (support, school, community, kids, communities); the fourth topic deals with awards and industry recognition (awards, congratulations, industry, agchatnz) and captures milk tanker posts; the fifth topic deals with farming and environment (farm, water, cows, farming, environment); the sixth topic deals with team, events and site activity (team, site, event, Christmas, GDT, global) and is the biggest topic capturing 20.6% of the posts. The sixth topic is rather indistinctive, and words like “music” (topic 5) and “john” (topic 1) are ambiguous. Topic sizes are fairly balanced (12.4% to 20.6%). In this dataset, the most prominent topics extend beyond direct product-related content and include business, community, industry recognition, farming and events.

Level of user engagement and factors that influence user engagement

The likeCount can be applied to every single post, and therefore, it is considered a main dependent variable for cross-platform comparison. The comments, shares, and videos played are counted where possible; however, they cannot be considered a directly comparable variable due to lack of data on some platforms.

# Summarise engagement statistics by platform
safe_median <- function(x) if (all(is.na(x))) NA_real_ else median(x, na.rm = TRUE)

platform_engagement <- posts |>
  group_by(platform) |>
  summarise(
    posts           = n(),
    median_likes    = median(likeCount, na.rm = TRUE),
    iqr_likes       = IQR(likeCount, na.rm = TRUE),
    median_comments = safe_median(commentCount),
    median_shares   = safe_median(shareCount),
    median_plays    = safe_median(videoplayCount),
    .groups = "drop"
  )

platform_engagement |>
  kable(digits = 1, caption = "Engagement by platform - descriptive statistics")
Engagement by platform - descriptive statistics
platform posts median_likes iqr_likes median_comments median_shares median_plays
Facebook 1433 61 134.0 7 4 10172.5
Instagram 519 132 117.5 2 NA 3690.0
LinkedIn 326 141 141.2 5 4 NA
TikTok 108 7 8.2 0 2 2969.5
Twitter/X 6220 1 5.0 0 0 NA
YouTube 395 0 4.0 0 NA 505.0
# distribution of likes per post (log scale) - shows the heavy right skew
posts |>
  ggplot(aes(x = likeCount + 1)) +
  geom_histogram(bins = 40, fill = "#225ea8") +
  scale_x_log10() +
  labs(title = "Distribution of likes per post",
       subtitle = "Log scale; most posts receive few likes",
       x = "Likes + 1 (log scale)", y = "Number of posts") +
  theme_minimal(base_size = 11)

Most posts receive very few likes, while a small number receive many, so the distribution is strongly right-skewed. This is why medians and non-parametric tests are used.

# posting volume over time by platform
posts |>
  count(post_year, platform) |>
  ggplot(aes(x = post_year, y = n, fill = platform)) +
  geom_col() +
  scale_fill_brewer(palette = "Set2") +
  labs(title = "Number of posts per year by platform",
       x = "Year", y = "Posts", fill = "Platform") +
  theme_minimal(base_size = 10)

Twitter/X accounts for most of the posts in the dataset (6,220 of 9,001), so overall posting volume is largely driven by this platform.

#plot_engagement_by_platform
# boxplot of engagement by platform; log10(1 + likes) keeps a handful of
# viral posts from squashing the rest of the distribution
posts |>
  ggplot(aes(x = fct_reorder(platform, likeCount, .fun = median), y = log10(likeCount + 1),
             fill = platform)) +
  geom_boxplot(outlier.alpha = 0.25, show.legend = FALSE) +
  coord_flip() +
  scale_fill_brewer(palette = "Blues") +
  labs(title = "Engagement by platform",
       subtitle = "log10(1 + likes) per post, so a few viral posts don't hide everything else",
       x = NULL, y = "log10(1 + likes)") +
  theme_minimal(base_size = 11)

# boxplot of engagement by media type - only Facebook, Instagram and
# LinkedIn vary their media type in this dataset
posts |>
  filter(platform %in% c("Facebook", "Instagram", "LinkedIn")) |>
  ggplot(aes(x = post_media, y = log10(likeCount + 1), fill = post_media)) +
  geom_boxplot(outlier.alpha = 0.25) +
  facet_wrap(~ platform) +
  scale_fill_brewer(palette = "Dark2") +
  labs(title = "Engagement by media type",
       subtitle = "Not recorded vs. image vs. video vs. sidecar, log10(1 + likes)",
       x = NULL, y = "log10(1 + likes)", fill = "Media type") +
  theme_minimal(base_size = 11) +
  theme(axis.text.x = element_text(angle = 20, hjust = 1))

#plot_length_vs_engagement
# scatter plot of caption length vs. likes; both axes logged since both
# variables are right-skewed
posts |>
  ggplot(aes(x = word_count + 1, y = likeCount + 1, colour = platform)) +
  geom_point(alpha = 0.35) +
  scale_x_log10() +
  scale_y_log10() +
  scale_colour_brewer(palette = "Set1") +
  labs(title = "Post length vs. engagement",
       subtitle = "Caption word count against likes (log-log scale), by platform",
       x = "Words in the post (log scale)", y = "Likes (log scale)", colour = "Platform") +
  theme_minimal(base_size = 11)

# boxplot of engagement by topic, linking Part 3(a)'s topics to Part 3(b)'s
# engagement question
posts |>
  left_join(post_topics, by = "post_id") |>
  filter(!is.na(topic)) |>
  ggplot(aes(x = factor(topic), y = log10(likeCount + 1), fill = factor(topic))) +
  geom_boxplot(outlier.alpha = 0.25, show.legend = FALSE) +
  scale_fill_brewer(palette = "Blues") +
  labs(title = "Engagement by topic",
       subtitle = "Does what a post is about relate to how well it does? (log10(1 + likes))",
       x = "Topic", y = "log10(1 + likes)") +
  theme_minimal(base_size = 11)

Prior to selecting any tests, the assumptions of normality (using Shapiro-Wilk test on each platform, but reducing sample size to 5,000 for those platforms which have more data since shapiro.test() can handle maximum 5,000 observations) and equality of variance (using rstatix::levene_test()) have been verified for the platform test while the assumption of Levene’s test has been verified for the hashtag test.

#bivariate_setup
# posts joined to their topic, and posts with video-play data - both reused below
posts_with_topic  <- posts |> left_join(post_topics, by = "post_id") |> filter(!is.na(topic)) |>
  mutate(topic = factor(topic))
videos_with_plays <- posts |> filter(!is.na(videoplayCount))

# log(1 + likes) column used for the normality/variance checks and the ANOVA
posts_log <- posts |> mutate(log_likes = log10(likeCount + 1))
# Shapiro-Wilk normality check, per platform (sampled to 5,000 rows where
# needed, since Twitter/X alone has over 6,000 posts)
posts_log |>
  group_by(platform) |>
  group_map(~ {
    vals <- .x$log_likes
    if (length(vals) > 5000) vals <- sample(vals, 5000)
    test <- shapiro.test(vals)
    tibble(platform = unique(.y$platform), n = length(.x$log_likes),
           W = unname(test$statistic), p_value = test$p.value)
  }) |>
  bind_rows() |>
  kable(digits = 3, caption = "Shapiro-Wilk normality check, log(1 + likes) by platform")
Shapiro-Wilk normality check, log(1 + likes) by platform
platform n W p_value
Facebook 1433 0.998 0.066
Instagram 519 0.974 0.000
LinkedIn 326 0.992 0.065
TikTok 108 0.880 0.000
Twitter/X 6220 0.868 0.000
YouTube 395 0.753 0.000
# homogeneity-of-variance check
posts_log |> mutate(platform = factor(platform)) |> levene_test(log_likes ~ platform)
## # A tibble: 1 × 4
##     df1   df2 statistic        p
##   <int> <int>     <dbl>    <dbl>
## 1     5  8995      71.5 1.25e-73

Facebook and LinkedIn approach normality (p > .05), whereas Instagram, TikTok, Twitter/X, and YouTube deviate from normal (p < .001), with significant differences between variances for the different platforms (Levene’s test, p < .001). Hence, the Kruskal-Wallis outcome is considered the primary finding, with ANOVA included as well.

# one-way ANOVA (parametric) with Tukey's HSD post-hoc
anova_platform <- aov(log_likes ~ platform, data = posts_log)
summary(anova_platform)
##               Df Sum Sq Mean Sq F value Pr(>F)    
## platform       5   3754   750.9    3257 <2e-16 ***
## Residuals   8995   2074     0.2                   
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
as.data.frame(TukeyHSD(anova_platform)$platform) |>
  round(3) |>
  kable(caption = "Tukey's HSD - pairwise platform differences")
Tukey’s HSD - pairwise platform differences
diff lwr upr p adj
Instagram-Facebook 0.278 0.208 0.348 0.000
LinkedIn-Facebook 0.346 0.262 0.430 0.000
TikTok-Facebook -0.862 -0.999 -0.725 0.000
Twitter/X-Facebook -1.362 -1.403 -1.322 0.000
YouTube-Facebook -1.409 -1.486 -1.331 0.000
LinkedIn-Instagram 0.068 -0.029 0.164 0.344
TikTok-Instagram -1.140 -1.285 -0.995 0.000
Twitter/X-Instagram -1.641 -1.703 -1.578 0.000
YouTube-Instagram -1.687 -1.778 -1.595 0.000
TikTok-LinkedIn -1.208 -1.360 -1.056 0.000
Twitter/X-LinkedIn -1.708 -1.786 -1.631 0.000
YouTube-LinkedIn -1.755 -1.857 -1.652 0.000
Twitter/X-TikTok -0.500 -0.633 -0.368 0.000
YouTube-TikTok -0.547 -0.695 -0.398 0.000
YouTube-Twitter/X -0.046 -0.117 0.025 0.430
# Kruskal-Wallis (non-parametric equivalent) with Dunn's test as post-hoc
platform_test <- kruskal.test(likeCount ~ platform, data = posts)
platform_test
## 
##  Kruskal-Wallis rank sum test
## 
## data:  likeCount by platform
## Kruskal-Wallis chi-squared = 4728.4, df = 5, p-value < 2.2e-16
dunn_test(posts, likeCount ~ platform, p.adjust.method = "bonferroni") |>
  select(group1, group2, statistic, p.adj, p.adj.signif)
## # A tibble: 15 × 5
##    group1    group2    statistic     p.adj p.adj.signif
##    <chr>     <chr>         <dbl>     <dbl> <chr>       
##  1 Facebook  Instagram     4.85  1.84e-  5 ****        
##  2 Facebook  LinkedIn      4.55  7.92e-  5 ****        
##  3 Facebook  TikTok       -7.87  5.44e- 14 ****        
##  4 Facebook  Twitter/X   -53.1   0         ****        
##  5 Facebook  YouTube     -29.9   3.64e-195 ****        
##  6 Instagram LinkedIn      0.437 1   e+  0 ns          
##  7 Instagram TikTok       -9.77  2.22e- 21 ****        
##  8 Instagram Twitter/X   -39.5   0         ****        
##  9 Instagram YouTube     -29.2   8.40e-186 ****        
## 10 LinkedIn  TikTok       -9.59  1.36e- 20 ****        
## 11 LinkedIn  Twitter/X   -32.3   1.53e-227 ****        
## 12 LinkedIn  YouTube     -26.4   7.79e-153 ****        
## 13 TikTok    Twitter/X    -7.93  3.17e- 14 ****        
## 14 TikTok    YouTube      -8.41  5.90e- 16 ****        
## 15 Twitter/X YouTube      -2.77  8.46e-  2 ns
# chi-squared test: is media type linked to being above/below the platform's
# own median engagement? Restricted to Facebook, Instagram and LinkedIn,
# the only platforms where media type varies
posts_media <- posts |>
  filter(platform %in% c("Facebook", "Instagram", "LinkedIn")) |>
  group_by(platform) |>
  mutate(high_engagement = likeCount > median(likeCount)) |>
  ungroup()

chi_table1 <- table(posts_media$post_media, posts_media$high_engagement)
chi_table1
##               
##                FALSE TRUE
##   Image          434  496
##   Not recorded   456  318
##   Sidecar         46   49
##   Video          210  269
chisq.test(chi_table1)$expected   # check: all expected counts should be at least 5
##               
##                    FALSE      TRUE
##   Image        467.85777 462.14223
##   Not recorded 389.37840 384.62160
##   Sidecar       47.79192  47.20808
##   Video        240.97191 238.02809
chisq.test(chi_table1)
## 
##  Pearson's Chi-squared test
## 
## data:  chi_table1
## X-squared = 36.015, df = 3, p-value = 7.433e-08
chisq.test(chi_table1)$stdres
##               
##                     FALSE       TRUE
##   Image        -2.8866025  2.8866025
##   Not recorded  5.8943471 -5.8943471
##   Sidecar      -0.3756174  0.3756174
##   Video        -3.1849282  3.1849282
# hashtag use and median likes, by platform
posts |>
  group_by(platform) |>
  summarise(pct_hashtag = round(100 * mean(has_hashtag), 1),
            median_with = median(likeCount[has_hashtag]),
            median_without = median(likeCount[!has_hashtag]),
            .groups = "drop") |>
  kable(caption = "Hashtag use and median likes by platform")
Hashtag use and median likes by platform
platform pct_hashtag median_with median_without
Facebook 36.2 76 54.0
Instagram 55.5 154 95.0
LinkedIn 30.4 121 149.0
TikTok 85.2 7 11.5
Twitter/X 26.4 2 1.0
YouTube 0.0 NA 0.0
# does hashtag use relate to engagement? two groups, so a t-test /
# Mann-Whitney U comparison
posts_log |> mutate(has_hashtag = factor(has_hashtag)) |> levene_test(log_likes ~ has_hashtag)
## # A tibble: 1 × 4
##     df1   df2 statistic        p
##   <int> <int>     <dbl>    <dbl>
## 1     1  8999      70.7 4.74e-17
t.test(log_likes ~ has_hashtag, data = posts_log)        # Welch's t-test (parametric)
## 
##  Welch Two Sample t-test
## 
## data:  log_likes by has_hashtag
## t = -20.169, df = 4566.4, p-value < 2.2e-16
## alternative hypothesis: true difference in means between group FALSE and group TRUE is not equal to 0
## 95 percent confidence interval:
##  -0.4176917 -0.3436825
## sample estimates:
## mean in group FALSE  mean in group TRUE 
##           0.7013558           1.0820429
wilcox.test(log_likes ~ has_hashtag, data = posts_log)   # Mann-Whitney U (non-parametric)
## 
##  Wilcoxon rank sum test with continuity correction
## 
## data:  log_likes by has_hashtag
## W = 6043194, p-value < 2.2e-16
## alternative hypothesis: true location shift is not equal to 0
# median likes per topic, to show direction
posts_with_topic |>
  group_by(topic) |>
  summarise(posts = n(), median_likes = median(likeCount), .groups = "drop") |>
  kable(caption = "Median likes by topic")
Median likes by topic
topic posts median_likes
1 1087 1
2 1471 5
3 1484 8
4 1535 7
5 1387 4
6 1803 1
# likes across the six topics - Kruskal-Wallis with Dunn's test as post-hoc
topic_test <- kruskal.test(likeCount ~ topic, data = posts_with_topic)
topic_test
## 
##  Kruskal-Wallis rank sum test
## 
## data:  likeCount by topic
## Kruskal-Wallis chi-squared = 664.65, df = 5, p-value < 2.2e-16
dunn_test(posts_with_topic, likeCount ~ topic, p.adjust.method = "bonferroni") |>
  filter(p.adj < 0.05) |>
  select(group1, group2, statistic, p.adj, p.adj.signif)
## # A tibble: 13 × 5
##    group1 group2 statistic    p.adj p.adj.signif
##    <chr>  <chr>      <dbl>    <dbl> <chr>       
##  1 1      2          13.2  1.45e-38 ****        
##  2 1      3          19.0  3.96e-79 ****        
##  3 1      4          17.4  8.22e-67 ****        
##  4 1      5          12.2  8.41e-33 ****        
##  5 1      6           2.94 4.94e- 2 *           
##  6 2      3           6.25 6.17e- 9 ****        
##  7 2      4           4.47 1.19e- 4 ***         
##  8 2      6         -11.8  5.43e-31 ****        
##  9 3      5          -7.11 1.80e-11 ****        
## 10 3      6         -18.4  2.15e-74 ****        
## 11 4      5          -5.36 1.27e- 6 ****        
## 12 4      6         -16.6  5.54e-61 ****        
## 13 5      6         -10.6  3.51e-25 ****
# post length vs likes within each platform
posts |>
  group_by(platform) |>
  summarise(rho = round(cor(word_count, likeCount, method = "spearman"), 2),
            .groups = "drop") |>
  kable(caption = "Spearman rho: word count vs likes, by platform")
Spearman rho: word count vs likes, by platform
platform rho
Facebook 0.10
Instagram -0.18
LinkedIn 0.20
TikTok 0.10
Twitter/X 0.38
YouTube 0.15
# correlation matrix across the engagement metrics and post length,
# visualised as a correlogram
posts |>
  select(likeCount, commentCount, shareCount, videoplayCount, word_count) |>
  cor(method = "spearman", use = "pairwise.complete.obs") |>
  corrplot(type = "upper", tl.col ="black", addCoef.col ="black", number.cex = 0.9,
           col= colorRampPalette(c("#d73027", "white", "#1a9850")) (200),
           title = "Spearman correlations: engagement metrics and post length", mar = c (0,0,2,9))

# video plays vs. likes specifically
plays_likes_test <- cor.test(videos_with_plays$videoplayCount, videos_with_plays$likeCount,
                              method = "spearman", exact = FALSE)
plays_likes_test
## 
##  Spearman's rank correlation rho
## 
## data:  videos_with_plays$videoplayCount and videos_with_plays$likeCount
## S = 31148559, p-value < 2.2e-16
## alternative hypothesis: true rho is not equal to 0
## sample estimates:
##       rho 
## 0.7312871
#bivariate_summary
# summary table of the headline result from each test
tibble(
  analysis = c("Likes by platform (Kruskal-Wallis)",
               "Likes by media type, high vs. low engagement (chi-squared)",
               "Likes by hashtag use (Mann-Whitney U)",
               "Likes by topic (Kruskal-Wallis)",
               "Post length vs. likes (Spearman)",
               "Video plays vs. likes (Spearman)"),
  statistic = c(paste0("H = ", round(unname(platform_test$statistic), 2)),
                paste0("X-squared = ", round(unname(chisq.test(chi_table1)$statistic), 2)),
                paste0("W = ", format(unname(wilcox.test(log_likes ~ has_hashtag, data = posts_log)$statistic), big.mark = ",")),
                paste0("H = ", round(unname(topic_test$statistic), 2)),
                paste0("rho = ", round(unname(cor.test(posts$word_count, posts$likeCount, method = "spearman", exact = FALSE)$estimate), 3)),
                paste0("rho = ", round(unname(plays_likes_test$estimate), 3))),
  p_value = c(platform_test$p.value,
              chisq.test(chi_table1)$p.value,
              wilcox.test(log_likes ~ has_hashtag, data = posts_log)$p.value,
              topic_test$p.value,
              cor.test(posts$word_count, posts$likeCount, method = "spearman", exact = FALSE)$p.value,
              plays_likes_test$p.value)
) |>
  mutate(p_value = format.pval(p_value, digits = 3, eps = 0.001)) |>
  kable(caption = "Bivariate test results - summary")
Bivariate test results - summary
analysis statistic p_value
Likes by platform (Kruskal-Wallis) H = 4728.39 <0.001
Likes by media type, high vs. low engagement (chi-squared) X-squared = 36.02 <0.001
Likes by hashtag use (Mann-Whitney U) W = 6,043,194 <0.001
Likes by topic (Kruskal-Wallis) H = 664.65 <0.001
Post length vs. likes (Spearman) rho = 0.464 <0.001
Video plays vs. likes (Spearman) rho = 0.731 <0.001

Commentary on engagement insights and recommendations

There are many more outliers than one would like - some posts have huge engagement compared to the rest, which means that medians will be used to describe typical engagement. Also, whenever the results from the Kruskal-Wallis test and Mann-Whitney test differ from the results of the parametric test, they are treated as primary.

Platform. The platforms with the highest median number of likes are LinkedIn (median = 141) and Instagram (median = 132), then Facebook (median = 61). According to both ANOVA (F = 3257, p < .001) and the Kruskal-Wallis test (H = 4728.4, p < .001), there are significant differences between the platforms. Post-hoc tests (Tukey and Dunn) show every pair differs significantly except Instagram vs LinkedIn and Twitter/X vs YouTube. However, it should be kept in mind that it is not an engagement-rate ranking - there are no measures of followers/following, reach, paid promotion and time lived in the data.

Media type. Image and video posts on Facebook, Instagram and LinkedIn were found to be more often than expected above the median number of likes for that platform compared to posts with not recorded media type (chi-square = 36.0, df = 3, p < .001; standardized residuals: Image +2.9, Video +3.2, Not recorded – 5.9). Sidecar posts had no significant difference. “Not recorded” refers to the media type being unknown rather than a text post, thus it gives rise to further experiments on image and video posts.

Hashtags. For all platforms combined, there are more likes for posts with hashtags compared to those without (Mann-Whitney U, W = 6,043,194, p < .001). However, hashtags vary across platforms, ranging from 0% on YouTube and 26–36% on Twitter/X, LinkedIn, and Facebook, to 56% on Instagram and 85% on TikTok, therefore some of the difference observed above might be attributable to the platform rather than hashtags. Looking at individual platforms, the trend is inconsistent: posts with hashtags get more likes on Facebook (median: 76 vs. 54), Instagram (154 vs. 95) and almost on Twitter/X (2 vs. 1), while less on LinkedIn (121 vs. 149) and TikTok (7 vs. 11.5). Medians above are descriptive only and have not been tested; therefore, hashtags cannot be considered an absolute engagement predictor.

Post length. When we consider all the platforms together, we observe a moderate positive correlation between caption length and likes (Spearman rho = 0.46, p < .001). In each platform separately, the correlation varies from -0.18 (Instagram) to 0.38 (Twitter/X): it is low but positive for Facebook (0.10), TikTok (0.10), YouTube (0.15), and LinkedIn (0.20), while it is negative for Instagram. There is variability in the strength and nature of the relationship, which indicates that platform context may be significant to understand the relationship between caption length and likes.

Video plays. Of 886 posts with plays information, there is a strong correlation between video plays and likes (Spearman rho = 0.73, p < .001). Again, correlation doesn’t imply causation here.

Topic. Likes vary greatly based on the topic (H for Kruskal-Wallis = 664.65, p < .001). The topic that has the greatest median likes is Topic 3 (community and schools) with a median value of 8, followed by Topic 4 (7, awards and recognition), Topic 2 (5) and Topic 5 (4). The topics having the fewest median likes are Topic 1 (business and pricing) and Topic 6 (team and events) at 1. In Dunn’s test (Bonferroni), most topic pairs showed significant differences; the non-significant pairwise differences were between Topics 2 and 5 and Topics 3 and 4. This analysis did not take into account the platforms and could also include specific platforms, so the next step would be comparing the topics in one platform over time.

Recommendations. In future data collection, Fonterra should include reach/impressions, follower count at the time of posting, paid/organic status and more complete share and view counts so that engagement rates can be calculated and platforms can be compared more fairly. Based on the current data, the results suggest several areas for further testing: (1) develop platform-specific strategies rather than relying on one approach, because median likes differ substantially across platforms; (2) test image and video content on Facebook, Instagram and LinkedIn, where these media types were more frequently associated with above-median likes; (3) test hashtag use separately by platform, particularly on Facebook and Instagram, while recognising that the observed differences do not establish that hashtags cause higher engagement; (4) avoid relying on caption length alone as an engagement strategy because the relationship varies substantially across platforms; (5) investigate community/school and awards/recognition content further, as these topics had higher median likes in this dataset; and (6) use rate-based engagement measures once reach and follower data are available.

Declaration for Generative AI use

LS0tCnRpdGxlOiAiRm9udGVycmEgU29jaWFsIE1lZGlhIEFuYWx5c2lzIgpvdXRwdXQ6IAogIGh0bWxfZG9jdW1lbnQ6CiAgICBjb2RlX2Rvd25sb2FkOiBUUlVFCmVkaXRvcl9vcHRpb25zOiAKICBjaHVua19vdXRwdXRfdHlwZTogY29uc29sZQotLS0KCiMjIE92ZXJ2aWV3CgpUaGlzIHJlcG9ydCBhbmFseXNlcyA5LDAwMSBGb250ZXJyYSBzb2NpYWwgbWVkaWEgcG9zdHMgZnJvbSBzaXggcGxhdGZvcm1zIChGYWNlYm9vaywgSW5zdGFncmFtLCBMaW5rZWRJbiwgVGlrVG9rLCBUd2l0dGVyL1ggYW5kIFlvdVR1YmUpLCBwdWJsaXNoZWQgYmV0d2VlbiAyMDEwIGFuZCAyMDI2LgoKVGhlIGFuYWx5c2lzIGhhcyB0aHJlZSBwYXJ0czoKCi0gKipEYXRhIHByZXBhcmF0aW9uOioqIGNsZWFuaW5nIHRoZSB0aW1lc3RhbXBzLCBtZWRpYSBsYWJlbHMgYW5kIG1pc3NpbmcgZW5nYWdlbWVudCBjb3VudHMsIGFuZCBwcmVwYXJpbmcgdGhlIHBvc3QgdGV4dCBmb3IgYW5hbHlzaXMuCi0gKipUZXh0IGFuYWx5c2lzOioqIGZpbmRpbmcgdGhlIG1vc3QgZnJlcXVlbnQgd29yZHMgYW5kIHVzaW5nIHRvcGljIG1vZGVsbGluZyAoTERBLCA2IHRvcGljcykgdG8gc2VlIHdoYXQgRm9udGVycmEgcG9zdHMgYWJvdXQuCi0gKipFbmdhZ2VtZW50IGFuYWx5c2lzOioqIGNvbXBhcmluZyBsaWtlcywgY29tbWVudHMgYW5kIHNoYXJlcyBhY3Jvc3MgcGxhdGZvcm1zLCBtZWRpYSB0eXBlcyBhbmQgcG9zdGluZyBwYXR0ZXJucy4KCiMgSW5zdGFsbCBhbmQgbG9hZCBwYWNrYWdlcyBhbmQgaW1wb3J0IGFuZCBwcmV2aWV3IGRhdGEKCmBgYHtyIGluaXRpYX0KIyBTZXQgY29uc2lzdGVudCBvcHRpb25zIGZvciB0YWJsZXMgYW5kIGZpZ3VyZXMKa25pdHI6Om9wdHNfY2h1bmskc2V0KGZpZy53aWR0aCA9IDgsIGZpZy5oZWlnaHQgPSA1LCBlY2hvID0gVFJVRSkKCiMgSW5zdGFsbCBhbmQgbG9hZCBwYWNrYWdlcwppZighcmVxdWlyZSgicGFjbWFuIikpaW5zdGFsbC5wYWNrYWdlcygicGFjbWFuIikKcGFjbWFuOjpwX2xvYWQocmVhZHIsIGRwbHlyLCB0aWR5ciwgc3RyaW5nciwgbHVicmlkYXRlLCBwdXJyciwgZ2dwbG90MiwgZ3JpZEV4dHJhLCBHR2FsbHksIGNvcnJwbG90LGZvcmNhdHMscnN0YXRpeCxEZXNjVG9vbHMsIHNraW1yLCBmaW5hbGZpdCkKcGFjbWFuOjpwX2xvYWQodGlkeXRleHQsIHRleHRzdGVtLCB3b3JkY2xvdWQsIHRleHRkYXRhLCB0b3BpY21vZGVscywgcmVzaGFwZTIsIAogICAgICAgICAgICAgICBrbml0cikKICAgICAgICAgIApgYGAKCmBgYHtyIHJlcV8yYV8xfQojIEltcG9ydCB0aGUgRm9udGVycmEgc29jaWFsIG1lZGlhIGRhdGEKcmF3X3Bvc3RfZGF0YSA8LSByZWFkX2NzdigKICAiZm9udGVycmEuY3N2IiwKICBuYSA9IGMoIiIsICJOQSIpLAogIHNob3dfY29sX3R5cGVzID0gRkFMU0UKKQoKIyBQcmV2aWV3IHRoZSBzdHJ1Y3R1cmUgYW5kIGZpcnN0IHJvd3Mgb2YgdGhlIGRhdGEKZ2xpbXBzZShyYXdfcG9zdF9kYXRhKQpoZWFkKHJhd19wb3N0X2RhdGEpCmBgYAoKIyBEYXRhIHByZXBhcmF0aW9uCgojIyBOb24tdGV4dCBkYXRhCgpUaGVyZSBhcmUgc29tZSBjcml0ZXJpYSB0YWtlbiBpbnRvIGFjY291bnQgZHVyaW5nIHRoZSBub24tdGV4dCBkYXRhIGNsZWFuaW5nOgoKLSBgY3JlYXRlZEF0YCB2YXJpYWJsZSB3YXMgY2hhbmdlZCB0byBVVEMgZGF0ZS10aW1lLCBzaW5jZSBhY2NvcmRpbmcgdG8gdGhlIGluc3RydWN0aW9uLCBhbGwgdGhlIHRpbWVzdGFtcHMgd2VyZSBjb2xsZWN0ZWQgaW4gVVRDIHRpbWUgem9uZS4KLSBUaGVyZSBhcmUgc29tZSB0aW1lc3RhbXBzIHdpdGggMDA6MDA6MDAsIHdoaWNoIGNhbiBiZSBpbnRlcnByZXRlZCBhcyBkYXRlIG9ubHkgd2l0aG91dCB0aW1lIGluZm9ybWF0aW9uLiBUaGVyZWZvcmUsIHRpbWUgd2lsbCBub3QgYmUgY29uc2lkZXJlZCwgb25seSBkYXRlLXJlbGF0ZWQgZmVhdHVyZXMgKHllYXIsIG1vbnRoLCB3ZWVrZGF5KS4KLSBMaWtlcyBhbmQgImRpZ3MiIG9uIFRpa1RvayBhcmUgcmVnYXJkZWQgYXMgc2ltaWxhci4gQ29tbWVudHMgKGV4Y2VwdCBUd2l0dGVyL1gpIGFuZCByZXBsaWVzIChUd2l0dGVyL1gpIGFyZSBhbHNvIGNvbnNpZGVyZWQgdG8gYmUgc2ltaWxhci4KLSBgbWVkaWFgIGZlYXR1cmUgY29udGFpbnMgZGlmZmVyZW50IGNhcGl0YWxpc2F0aW9uICh2aWRlbywgVmlkZW8pLCBzbyBpdCBzaG91bGQgYmUgc3RhbmRhcmRpc2VkIChJbWFnZSwgVmlkZW8sIFNpZGVjYXIpLiBFbXB0eSB2YWx1ZSB3YXMgYXNzaWduZWQgTm90IHJlY29yZGVkLCByYXRoZXIgdGhhbiAibm8gbWVkaWEiLCBzaW5jZSBtZWRpYSB0eXBlIGNhbm5vdCBiZSBkZXRlcm1pbmVkLgotIEhpZ2ggbGV2ZWxzIG9mIGVuZ2FnZW1lbnQgYXJlIG1haW50YWluZWQsIHNpbmNlIHRoZXkgcHJvYmFibHkgYXJlIHJlYWwgZXhhbXBsZXMgb2YgdmlyYWwgcG9zdHMgcmF0aGVyIHRoYW4gb3V0bGllcnM7IHJhdGhlciB0aGFuIGRlbGV0aW5nIHRoZW0sIG1lZGlhbnMgYW5kIG5vbi1wYXJhbWV0cmljIHRlc3RzIGFyZSBlbXBsb3llZCBsYXRlciBvbi4KLSBQb3N0cyB3aXRob3V0IHRleHQgYXJlIG1haW50YWluZWQgZHVyaW5nIGVuZ2FnZW1lbnQgYW5hbHlzaXMgYnV0IGRlbGV0ZWQgb25seSB3aGVuIHRleHQgYW5hbHlzaXMgYmVnaW5zLCBzaW5jZSB0aGVyZSB3aWxsIGJlIG5vdGhpbmcgdG8gdG9rZW5pemUgdGhlbi4KLSBUaGUgbWlzc2luZyBlbmdhZ2VtZW50cyByZW1haW4gbWlzc2luZyBhcyBvcHBvc2VkIHRvIHVzaW5nIDBzIHNpbmNlIGNlcnRhaW4gc2l0ZXMgc3VjaCBhcyBJbnN0YWdyYW0gYW5kIFlvdVR1YmUgZG8gbm90IHByb3ZpZGUgdGhlIHNoYXJpbmcgbWV0cmljcy4KLSBUaGVyZSBhcmUgbm8gZHVwbGljYXRlcyBpbiB0aGUgZm9ybSBvZiBpZGVudGljYWwgcm93cyBvciBVUkxzLiBIb3dldmVyLCB0aGVyZSBpcyBvbmUgaW5zdGFuY2Ugd2hlcmUgdHdvIHBvc3RzIGhhdmUgdGhlIHNhbWUgZGF0ZSBhbmQgdGV4dCBmb3IgRmFjZWJvb2sgYW5kIEluc3RhZ3JhbSAoSmFudWFyeSAxMSwgMjAxOSwgMDY6MDA6MjQpLiBUaGlzIGlzIG5vdCBhbiBlcnJvciBidXQgY3Jvc3MtcG9zdGluZy4KCmBgYHtyIHJlcV8yYX0KIyBjb25maXJtIGFsbCBleHBlY3RlZCBjb2x1bW5zIGFyZSBwcmVzZW50IGJlZm9yZSBjbGVhbmluZwpuZWNlc3NhcnlfY29scyA8LSBjKCJwbGF0Zm9ybSIsICJ1cmwiLCAiY3JlYXRlZEF0IiwgInRleHQiLCAibWVkaWEiLAogICAgICAgICAgICAgICAgICAiY29tbWVudENvdW50IiwgInNoYXJlQ291bnQiLCAibGlrZUNvdW50IiwgInZpZGVvcGxheUNvdW50IikKc3RvcGlmbm90KGFsbChuZWNlc3NhcnlfY29scyAlaW4lIG5hbWVzKHJhd19wb3N0X2RhdGEpKSkKYGBgCgpgYGB7ciByZXFfMmJ9CiMgdHJpbSB3aGl0ZXNwYWNlLCBwYXJzZSB0aGUgdGltZXN0YW1wLCBhbmQgc3RhbmRhcmRpc2UgdGhlIG1lZGlhIGxhYmVscwpwb3N0cyA8LSByYXdfcG9zdF9kYXRhIHw+CiAgbXV0YXRlKAogICAgcGxhdGZvcm0gICA9IHN0cl9zcXVpc2gocGxhdGZvcm0pLAogICAgdXJsICAgICAgICA9IHN0cl9zcXVpc2godXJsKSwKICAgIGNyZWF0ZWRBdCAgPSB5bWRfaG1zKGNyZWF0ZWRBdCwgdHogPSAiVVRDIiwgcXVpZXQgPSBUUlVFKSwKICAgIHBvc3RfbWVkaWEgPSBjYXNlX3doZW4oCiAgICAgIGlzLm5hKG1lZGlhKSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfiAiTm90IHJlY29yZGVkIiwKICAgICAgc3RyX3RvX2xvd2VyKG1lZGlhKSAlaW4lIGMoInBob3RvIiwgImltYWdlIikgfiAiSW1hZ2UiLAogICAgICBzdHJfdG9fbG93ZXIobWVkaWEpID09ICJ2aWRlbyIgICAgICAgICAgICB+ICJWaWRlbyIsCiAgICAgIHN0cl90b19sb3dlcihtZWRpYSkgPT0gInNpZGVjYXIiICAgICAgICAgIH4gIlNpZGVjYXIiLAogICAgICBUUlVFIH4gc3RyX3RvX3RpdGxlKG1lZGlhKQogICAgKQogICkKCiMgY29uZmlybSBldmVyeSB0aW1lc3RhbXAgcGFyc2VkIHN1Y2Nlc3NmdWxseQpzdG9waWZub3Qoc3VtKGlzLm5hKHBvc3RzJGNyZWF0ZWRBdCkpID09IDApCmBgYAoKYGBge3IgcmVxXzJjfQojIGNoZWNrIGZvciBleGFjdCBkdXBsaWNhdGUgcm93cyBhbmQgZHVwbGljYXRlIFVSTHMKdGliYmxlKAogIGNoZWNrICA9IGMoIkV4YWN0IGR1cGxpY2F0ZSByb3dzIiwgIkR1cGxpY2F0ZSBVUkxzIiksCiAgbnVtYmVyID0gYyhzdW0oZHVwbGljYXRlZChwb3N0cykpLCBzdW0oZHVwbGljYXRlZChwb3N0cyR1cmwpKSkKKSB8PgogIGthYmxlKGNhcHRpb24gPSAiRHVwbGljYXRlIGNoZWNrcyIpCmBgYAoKYGBge3IgcmVxXzJkfQojIGNoZWNrIGZvciBpZGVudGljYWwgdGV4dCBwb3N0ZWQgYXQgdGhlIGV4YWN0IHNhbWUgdGltZSwgYW5kIHdoaWNoIHBsYXRmb3Jtcwpwb3N0cyB8PgogIGZpbHRlcighaXMubmEodGV4dCkpIHw+CiAgZ3JvdXBfYnkoY3JlYXRlZEF0LCB0ZXh0KSB8PgogIGZpbHRlcihuKCkgPiAxKSB8PgogIHN1bW1hcmlzZShwbGF0Zm9ybXMgPSBwYXN0ZShwbGF0Zm9ybSwgY29sbGFwc2UgPSAiICYgIiksIHJlY29yZHMgPSBuKCksIC5ncm91cHMgPSAiZHJvcCIpIHw+CiAgc2VsZWN0KGNyZWF0ZWRBdCwgcGxhdGZvcm1zLCByZWNvcmRzKSB8PgogIGthYmxlKGNhcHRpb24gPSAiU2FtZS10aW1lLCBzYW1lLXRleHQgcmVjb3JkcyAoa2VwdCBhcyBsaWtlbHkgY3Jvc3MtcG9zdGluZykiKQpgYGAKCmBgYHtyIHJlcV8yZX0KIyBtaXNzaW5nIHZhbHVlcyBieSBjb2x1bW4KcG9zdHMgfD4KICBzdW1tYXJpc2UoYWNyb3NzKGV2ZXJ5dGhpbmcoKSwgfiBzdW0oaXMubmEoLngpKSkpIHw+CiAgcGl2b3RfbG9uZ2VyKGV2ZXJ5dGhpbmcoKSwgbmFtZXNfdG8gPSAiY29sdW1uIiwgdmFsdWVzX3RvID0gIm1pc3NpbmciKSB8PgogIG11dGF0ZShwY3RfbWlzc2luZyA9IHJvdW5kKDEwMCAqIG1pc3NpbmcgLyBucm93KHBvc3RzKSwgMSkpIHw+CiAga2FibGUoY2FwdGlvbiA9ICJNaXNzaW5nIHZhbHVlcyBieSBjb2x1bW4iKQpgYGAKCmBgYHtyIHJlcV8yZn0KIyBjaGVjayB3aGV0aGVyIG1pc3NpbmcgZW5nYWdlbWVudCBjb3VudHMgYXJlIHBsYXRmb3JtLXNwZWNpZmljCnBvc3RzIHw+CiAgZ3JvdXBfYnkocGxhdGZvcm0pIHw+CiAgc3VtbWFyaXNlKAogICAgbWlzc2luZ19jb21tZW50cyA9IHN1bShpcy5uYShjb21tZW50Q291bnQpKSwKICAgIG1pc3Npbmdfc2hhcmVzICAgID0gc3VtKGlzLm5hKHNoYXJlQ291bnQpKSwKICAgIG1pc3NpbmdfcGxheXMgICAgID0gc3VtKGlzLm5hKHZpZGVvcGxheUNvdW50KSksCiAgICAuZ3JvdXBzID0gImRyb3AiCiAgKSB8PgogIGthYmxlKGNhcHRpb24gPSAiTWlzc2luZyBlbmdhZ2VtZW50IGNvdW50cyBieSBwbGF0Zm9ybSIpCmBgYAoKYGBge3IgcmVxXzJnfQojIGNoZWNrIGZvciBpbXBvc3NpYmxlIG5lZ2F0aXZlIGNvdW50cwpwb3N0cyB8PgogIHN1bW1hcmlzZShhY3Jvc3MoYyhjb21tZW50Q291bnQsIHNoYXJlQ291bnQsIGxpa2VDb3VudCwgdmlkZW9wbGF5Q291bnQpLAogICAgICAgICAgICAgICAgICAgIH4gc3VtKC54IDwgMCwgbmEucm0gPSBUUlVFKSkpIHw+CiAgcGl2b3RfbG9uZ2VyKGV2ZXJ5dGhpbmcoKSwgbmFtZXNfdG8gPSAiY29sdW1uIiwgdmFsdWVzX3RvID0gIm5lZ2F0aXZlX3ZhbHVlcyIpIHw+CiAga2FibGUoY2FwdGlvbiA9ICJBbnkgaW1wb3NzaWJsZSBuZWdhdGl2ZSBjb3VudHM/IikKYGBgCgojIyBOZXcgdmFyaWFibGVzIGNyZWF0ZWQKCldoeSBlYWNoIG5ldyB2YXJpYWJsZSBpcyB1c2VmdWw6CgotIGBwb3N0X2lkYCBnaXZlcyBlYWNoIHBvc3QgYSB1bmlxdWUgaWRlbnRpZmllciB3aGljaCBpcyByZXF1aXJlZCBmb3IgdGhlIHRleHQgYW5hbHlzaXMgYW5kIHRvcGljIG1vZGVsLgotIGBwb3N0X2RhdGVgLCBgcG9zdF95ZWFyYCwgYHBvc3RfbW9udGhgLCBgcG9zdF95ZWFybW9udGhgIGFuZCBgcG9zdF93ZWVrZGF5YCBicmVhayB0aGUgdGltZXN0YW1wIGludG8gY29tcG9uZW50cywgc28gcG9zdGluZyBwYXR0ZXJucyBjYW4gYmUgZXhhbWluZWQgd2l0aG91dCBhbHRlcmluZyB0aGUgb3JpZ2luYWwgYGNyZWF0ZWRBdGAuCi0gYHBvc3RfbWVkaWFgIGlzIGEgdGlkeSBjb250ZW50LWZvcm1hdCBsYWJlbCB0aGF0IGNhbiBiZSBjb21wYXJlZCBhZ2FpbnN0IGVuZ2FnZW1lbnQuCi0gYHdvcmRfY291bnRgIGFuZCBgY2hhcl9jb3VudGAgbWVhc3VyZSBjYXB0aW9uIGxlbmd0aCwgc28gbGVuZ3RoIGNhbiBiZSB0ZXN0ZWQgYWdhaW5zdCBlbmdhZ2VtZW50LgotIGB0b3RhbF9yZWFjdGlvbmAgc3VtcyB3aGljaGV2ZXIgb2YgbGlrZXMvY29tbWVudHMvc2hhcmVzIGEgcG9zdCBoYXMgZGF0YSBmb3IuIEl0IGlzIGEgdXNlZnVsIGRlc2NyaXB0aXZlIG51bWJlciBidXQgbm90IHRoZSBtYWluIGNyb3NzLXBsYXRmb3JtIG91dGNvbWUsIHNpbmNlIHNoYXJlcyBhcmUgc3RydWN0dXJhbGx5IG1pc3Npbmcgb24gc29tZSBwbGF0Zm9ybXMuCi0gYGhhc19wbGF5X2RhdGFgIGZsYWdzIHRoZSBzdWJzZXQgb2YgcG9zdHMgd2hlcmUgYSBwbGF5cy12cy1saWtlcyBjb21wYXJpc29uIGlzIHBvc3NpYmxlLgotIGBoYXNfaGFzaHRhZ2AgZmxhZ3Mgd2hldGhlciBhIHBvc3QgdXNlZCBhIGAjYCAtIGEgdHdvLWxldmVsIHZhcmlhYmxlIHVzZWQgZm9yIGEgdC10ZXN0L01hbm4tV2hpdG5leSBjb21wYXJpc29uIGxhdGVyLgoKYGBge3IgcmVxXzNhaX0KI25ld192YXJpYWJsZXMKcG9zdHMgPC0gcG9zdHMgfD4KICBtdXRhdGUoCiAgICBwb3N0X2lkICAgICAgICA9IHJvd19udW1iZXIoKSwKICAgIHBvc3RfZGF0ZSAgICAgID0gYXMuRGF0ZShjcmVhdGVkQXQpLAogICAgcG9zdF95ZWFyICAgICAgPSB5ZWFyKGNyZWF0ZWRBdCksCiAgICBwb3N0X21vbnRoICAgICA9IG1vbnRoKGNyZWF0ZWRBdCwgbGFiZWwgPSBUUlVFLCBhYmJyID0gVFJVRSksCiAgICBwb3N0X3llYXJtb250aCA9IGZsb29yX2RhdGUoY3JlYXRlZEF0LCB1bml0ID0gIm1vbnRoIiksCiAgICBwb3N0X3dlZWtkYXkgICA9IHdkYXkoY3JlYXRlZEF0LCBsYWJlbCA9IFRSVUUsIGFiYnIgPSBGQUxTRSwgd2Vla19zdGFydCA9IDEpLAogICAgd29yZF9jb3VudCAgICAgPSBzdHJfY291bnQoY29hbGVzY2UodGV4dCwgIiIpLCBib3VuZGFyeSgid29yZCIpKSwKICAgIGNoYXJfY291bnQgICAgID0gc3RyX2xlbmd0aChjb2FsZXNjZSh0ZXh0LCAiIikpLAogICAgdG90YWxfcmVhY3Rpb24gPSBsaWtlQ291bnQgKyBjb2FsZXNjZShjb21tZW50Q291bnQsIDApICsgY29hbGVzY2Uoc2hhcmVDb3VudCwgMCksCiAgICBoYXNfcGxheV9kYXRhICA9ICFpcy5uYSh2aWRlb3BsYXlDb3VudCksCiAgICBoYXNfaGFzaHRhZyAgICA9IHN0cl9kZXRlY3QoY29hbGVzY2UodGV4dCwgIiIpLCAiI1xcdysiKQogICkKCiMgcHJldmlldyB0aGUgbmV3IHZhcmlhYmxlcwpwb3N0cyB8PgogIHNlbGVjdChwb3N0X2lkLCBwbGF0Zm9ybSwgY3JlYXRlZEF0LCBwb3N0X3llYXIsIHBvc3RfbW9udGgsIHBvc3Rfd2Vla2RheSwKICAgICAgICAgcG9zdF9tZWRpYSwgd29yZF9jb3VudCwgdG90YWxfcmVhY3Rpb24sIGhhc19wbGF5X2RhdGEpIHw+CiAgaGVhZCgxMCkgfD4KICBrYWJsZShjYXB0aW9uID0gIk5ldyB2YXJpYWJsZXMgLSBmaXJzdCAxMCByb3dzIikKYGBgCgpUaGUgcG9zdHMgcnVuIGZyb20gYHIgZm9ybWF0KG1pbihwb3N0cyRwb3N0X2RhdGUpLCAiJWQgJUIgJVkiKWAgdG8gYHIgZm9ybWF0KG1heChwb3N0cyRwb3N0X2RhdGUpLCAiJWQgJUIgJVkiKWAuIEJlY2F1c2UgbGlrZXMvY29tbWVudHMvc2hhcmVzIGFyZSBjdW11bGF0aXZlIGNvdW50cyByYXRoZXIgdGhhbiBhIHJhdGUgb3ZlciBhIGZpeGVkIHdpbmRvdywgb2xkZXIgcG9zdHMgaGF2ZSBoYWQgbW9yZSB0aW1lIHRvIGFjY3VtdWxhdGUgZW5nYWdlbWVudCB0aGFuIG5ld2VyIG9uZXMuCgojIyBUZXh0IGRhdGEKClBvc3RzIHdpdGggbm8gdGV4dCBjb250ZW50IGFyZSBleGNsdWRlZCwgYXMgdGhlcmUgaXMgbm8gZGF0YSB0byB0b2tlbml6ZS4gTGlua3MsIEBtZW50aW9ucywgSFRNTCBhcnRpZmFjdHMsIGFuZCB0aGUgXCMgc3ltYm9sIGFyZSBzdHJpcHBlZCBiZWZvcmUgdG9rZW5pemF0aW9uIHRha2VzIHBsYWNlLCBhbHRob3VnaCB0aGUgdGV4dCB3aXRoaW4gdGhlIFwjIHN5bWJvbCBpcyB1c3VhbGx5IGtlcHQsIGFzIGl0IHJlbGF0ZXMgdG8gdGhlIHRvcGljLiBTdG9wIHdvcmRzLCBhbG9uZyB3aXRoIGEgZmV3IHNwZWNpYWwgZGF0YXNldCBmaWxsZXIgd29yZHMsIGFyZSBleGNsdWRlZC5DdXJseSBhcG9zdHJvcGhlcyBoYXZlIGJlZW4gY29udmVydGVkIGludG8gc3RyYWlnaHQgYXBvc3Ryb3BoZXMgdG8gZW5zdXJlIHRoYXQgY29udHJhY3Rpb25zIGxpa2Ugd2XigJlyZSBhcmUgZXhjbHVkZWQgZnJvbSB0aGUgbGlzdCBvZiBzdG9wIHdvcmRzLiBObyBsZW1tYXRpc2F0aW9uIHdhcyBwZXJmb3JtZWQ7IHRoZXJlZm9yZSwgZGlmZmVyZW50IGZvcm1zIG9mIGEgd29yZCBhcmUgY29uc2lkZXJlZCBpbmRlcGVuZGVudGx5LCBmb3IgZXhhbXBsZSwgYXdhcmQgYW5kIGF3YXJkcy4KCmBgYHtyIHJlcV8zYWlpfQojIGN1c3RvbSBzdG9wIHdvcmRzOiBnZW5lcmljIGZpbGxlciB0aGF0IGFkZHMgbm8gc3ViamVjdC1tYXR0ZXIgbWVhbmluZwphZGRpdGlvbmFsX3N0b3B3b3JkcyA8LSB0aWJibGUoCiAgd29yZCA9IGMoImZvbnRlcnJhIiwgImFtcCIsICJodHRwcyIsICJodHRwIiwgInd3dyIsICJjb20iLCAiY28iLAogICAgICAgICAgICJueiIsICJuZXciLCAiemVhbGFuZCIsICJ1aCIsICJ1bSIsICJ5ZWFoIiwgInJlYWxseSIsCiAgICAgICAgICAgImp1c3QiLCAia25vdyIsICJsaWtlIiwgImdvb2QiLCAiZ3JlYXQiLCAidGhhbmtzIiwgInRoYW5rIiwKICAgICAgICAgICAiaGkiLCAiaGV5IiwgImNoZWVycyIsICJ0b2RheSIsICJ5ZWFyIiwgInllYXJzIiwgImRheSIsCiAgICAgICAgICAgInRpbWUiLCAiZ29pbmciLCAibWFrZSIsICJyaWdodCIsICJ3ZWVrIiwgImxvb2siLCAidGhpbmsiLAogICAgICAgICAgICJnb3QiLCAibmVlZCIsICJ3YW50IiwgImxvdCIsICJzaGFyZSIsICJjb21lIiwgIndheSIsCiAgICAgICAgICAgImRvaW5nIiwgInN1cmUiLCAidGhpbmdzIiwgImJpdCIsICJyZWFkIiwgImxpbmsiKQopCmBgYAoKYGBge3IgcmVxXzNifQojIHJlbW92ZSBsaW5rcywgQG1lbnRpb25zIGFuZCBIVE1MIGFydGVmYWN0cywgbG93ZXItY2FzZSwgdGhlbiB0aWR5IHdoaXRlc3BhY2UKcG9zdHNfd2l0aF90ZXh0IDwtIHBvc3RzIHw+CiAgZmlsdGVyKCFpcy5uYSh0ZXh0KSwgc3RyX3NxdWlzaCh0ZXh0KSAhPSAiIikgfD4KICBtdXRhdGUoCiAgICB0ZXh0X2NsZWFuID0gdGV4dCB8PgogICAgICBzdHJfdG9fbG93ZXIoKSB8PgogICAgICBzdHJfcmVwbGFjZV9hbGwoIlvigJnigJhdIiwgIiciKSB8PgogICAgICBzdHJfcmVwbGFjZV9hbGwoImh0dHBzPzovL1xcUyt8d3d3XFwuXFxTKyIsICIgIikgfD4KICAgICAgc3RyX3JlcGxhY2VfYWxsKCJAXFx3KyIsICIgIikgfD4KICAgICAgc3RyX3JlcGxhY2VfYWxsKCImYW1wOyIsICIgYW5kICIpIHw+CiAgICAgIHN0cl9yZXBsYWNlX2FsbCgiIyIsICIgIikgfD4KICAgICAgc3RyX3NxdWlzaCgpCiAgKSB8PgogIHNlbGVjdChwb3N0X2lkLCBwbGF0Zm9ybSwgY3JlYXRlZEF0LCB0ZXh0X2NsZWFuKQpgYGAKCmBgYHtyIHJlcV8zYml9CiMgdG9rZW5pc2UgaW50byBvbmUgd29yZCBwZXIgcm93ICh1bm5lc3RfdG9rZW5zKCkgbG93ZXItY2FzZXMgYW5kIHN0cmlwcwojIHB1bmN0dWF0aW9uIGF1dG9tYXRpY2FsbHkpCnBvc3Rfd29yZHNfcmF3IDwtIHBvc3RzX3dpdGhfdGV4dCB8PgogIHVubmVzdF90b2tlbnMob3V0cHV0ID0gd29yZCwgaW5wdXQgPSB0ZXh0X2NsZWFuLCB0b2tlbiA9ICJ3b3JkcyIpCgojIHJlbW92ZSBzdGFuZGFyZCBzdG9wIHdvcmRzLCB0aGUgY3VzdG9tIGxpc3QgYWJvdmUsIGFuZCBhbnkgdG9rZW4gd2l0aCBhIGRpZ2l0CnBvc3Rfd29yZHMgPC0gcG9zdF93b3Jkc19yYXcgfD4KICBmaWx0ZXIoIXdvcmQgJWluJSBzdG9wX3dvcmRzJHdvcmQsCiAgICAgICAgICF3b3JkICVpbiUgYWRkaXRpb25hbF9zdG9wd29yZHMkd29yZCwKICAgICAgICAgIXN0cl9kZXRlY3Qod29yZCwgIlswLTldIiksCiAgICAgICAgIHN0cl9sZW5ndGgod29yZCkgPj0gMykKCiMgaG93IG1hbnkgcG9zdHMgd2VyZSBhdmFpbGFibGUgZm9yIHRleHQgYW5hbHlzaXMKdGliYmxlKAogIG1lYXN1cmUgPSBjKCJBbGwgcG9zdHMiLCAiUG9zdHMgd2l0aCB1c2FibGUgdGV4dCIsICJQb3N0cyBkcm9wcGVkIChubyB0ZXh0KSIpLAogIHZhbHVlID0gYyhucm93KHBvc3RzKSwgbl9kaXN0aW5jdChwb3N0c193aXRoX3RleHQkcG9zdF9pZCksCiAgICAgICAgICAgIG5yb3cocG9zdHMpIC0gbl9kaXN0aW5jdChwb3N0c193aXRoX3RleHQkcG9zdF9pZCkpCikgfD4KICBrYWJsZShjYXB0aW9uID0gIlRleHQgcHJlcGFyYXRpb24gc3VtbWFyeSIpCgpgYGAKCiMgRGF0YS1pbmZvcm1lZCBpbnNpZ2h0cyBhbmQgcmVjb21tZW5kYXRpb25zCgojIyBUb3BpY3MgY29udGVudCBjcmVhdG9ycyBoYXZlIGJlZW4gcG9zdGluZyBhYm91dAoKVGVybSBmcmVxdWVuY3kgc2hvd3Mgd2hpY2ggaW5kaXZpZHVhbCB3b3JkcyBhcHBlYXIgbW9zdCBvZnRlbjsgdGhlIExEQSB0b3BpYyBtb2RlbCBncm91cHMgd29yZHMgdGhhdCB0ZW5kIHRvIG9jY3VyIHRvZ2V0aGVyIGludG8gYnJvYWRlciBjb250ZXh0LiBUaGUgdHdvIGFyZSBjb21wbGVtZW50YXJ5LgoKYGBge3IgcmVxXzNiaWl9CiMgY291bnQgZWFjaCB3b3JkLCB0aGVuIGtlZXAgdGhlIDIwIG1vc3QgZnJlcXVlbnQKd29yZF9jb3VudHMgPC0gcG9zdF93b3JkcyB8PiBjb3VudCh3b3JkLCBzb3J0ID0gVFJVRSwgbmFtZSA9ICJmcmVxdWVuY3kiKQp0b3Bfd29yZHMgPC0gd29yZF9jb3VudHMgfD4gc2xpY2VfbWF4KG4gPSAyMCwgb3JkZXJfYnkgPSBmcmVxdWVuY3kpCgprYWJsZSh0b3Bfd29yZHMsIGNhcHRpb24gPSAiVG9wIDIwIHdvcmRzIGluIEZvbnRlcnJhJ3Mgc29jaWFsIG1lZGlhIGNvbnRlbnQiKQoKIyBiYXIgY2hhcnQgb2YgdGhlIHRvcCAyMCB3b3Jkcywgc2hhZGVkIGJ5IGZyZXF1ZW5jeQp0b3Bfd29yZHMgfD4KICBtdXRhdGUod29yZCA9IGZjdF9yZW9yZGVyKHdvcmQsIGZyZXF1ZW5jeSkpIHw+CiAgZ2dwbG90KGFlcyh4ID0gd29yZCwgeSA9IGZyZXF1ZW5jeSwgZmlsbCA9IGZyZXF1ZW5jeSkpICsKICBnZW9tX2NvbCgpICsKICBjb29yZF9mbGlwKCkgKwogIHNjYWxlX2ZpbGxfZ3JhZGllbnQobG93ID0gIiNkOWYyZDAiLCBoaWdoID0gIiMxYjQzMzIiLCBndWlkZSA9ICJub25lIikgKwogIGxhYnModGl0bGUgPSAiVG9wIDIwIHdvcmRzIGFjcm9zcyBGb250ZXJyYSdzIHNvY2lhbCBtZWRpYSBjb250ZW50IiwKICAgICAgIHN1YnRpdGxlID0gIkhvdyBvZnRlbiBlYWNoIHdvcmQgc2hvd3MgdXAgYWNyb3NzIGV2ZXJ5IHBsYXRmb3JtIiwKICAgICAgIHggPSBOVUxMLCB5ID0gIk51bWJlciBvZiB0aW1lcyB1c2VkIikgKwogIHRoZW1lX21pbmltYWwoYmFzZV9zaXplID0gMTIpCmBgYAoKVGhlIG1vc3QgY29tbW9uIHdvcmRzIGluIHRoaXMgd29yZCBsaXN0IGluY2x1ZGUgbWlsaywgZGFpcnksIGZhcm1lcnMsIGFuZCBmYXJtOyB0aGVuIGNvbWUgdGVhbSwgcGVvcGxlLCBmb29kLCB3b3JsZCwgYnVzaW5lc3MsIHN1cHBvcnQsIHByb2R1Y3RzLCBhbmQgd2F0ZXIuIFNvbWUgb3RoZXIgY29tbW9uIHdvcmRzIGluIHRoaXMgbGlzdCBhcmUgY2hlZXNlLCBwcmljZSwgQ2hpbmEsIGdsb2JhbCwgYW5kIHRhbmtlci4gVGhlIGNvbnRlbnRzIG9mIHRoaXMgbGlzdCBhcmUgbm90IGNvbmZpbmVkIG9ubHkgdG8gbWFya2V0aW5nIG9mIHByb2R1Y3RzIGJ1dCBpbmNsdWRlIG1hbnkgb3RoZXIgdG9waWNzLgoKYGBge3IgcmVxXzNjfQojIGRyb3Agd29yZHMgdGhhdCBhcmUgdG9vIHJhcmUgKGZld2VyIHRoYW4gNSBwb3N0cykgb3IgdG9vIGNvbW1vbiAob3ZlciBoYWxmCiMgb2YgYWxsIHBvc3RzKSAtIGJvdGggZXh0cmVtZXMgbWFrZSB0b3BpY3MgaGFyZGVyIHRvIGludGVycHJldAp3b3JkX2RvY19jb3VudHMgPC0gcG9zdF93b3JkcyB8PgogIGRpc3RpbmN0KHBvc3RfaWQsIHdvcmQpIHw+CiAgY291bnQod29yZCwgbmFtZSA9ICJuX2RvY3MiKQoKbl90ZXh0X3Bvc3RzIDwtIG5fZGlzdGluY3QocG9zdF93b3JkcyRwb3N0X2lkKQoKY29tbW9uX2Nsb3NlciA8LSB3b3JkX2RvY19jb3VudHMgfD4KICBmaWx0ZXIobl9kb2NzID49IDUsIG5fZG9jcyA8PSAwLjUwICogbl90ZXh0X3Bvc3RzKQoKaW5wdXRfdG9waWNfd29yZHMgPC0gcG9zdF93b3JkcyB8PgogIGlubmVyX2pvaW4oY29tbW9uX2Nsb3NlciwgYnkgPSAid29yZCIpCgojIGJ1aWxkIHRoZSBkb2N1bWVudC10ZXJtIG1hdHJpeCB0aGF0IExEQSgpIHJlcXVpcmVzCnRvcGljX2R0bSA8LSBpbnB1dF90b3BpY193b3JkcyB8PgogIGNvdW50KHBvc3RfaWQsIHdvcmQsIG5hbWUgPSAibiIpIHw+CiAgY2FzdF9kdG0oZG9jdW1lbnQgPSBwb3N0X2lkLCB0ZXJtID0gd29yZCwgdmFsdWUgPSBuKQpgYGAKCmBgYHtyIHJlcV8zY2l9CiMgZml0IGEgc2l4LXRvcGljIG1vZGVsIChrID0gNiwgYXMgc3VnZ2VzdGVkIGluIHRoZSBicmllZik7IHRoZSBzZWVkIGluCiMgY29udHJvbD0gbWFrZXMgaXQgcmVwcm9kdWNpYmxlCnRvcGljX21vZGVsIDwtIExEQSh0b3BpY19kdG0sIGsgPSA2LCBjb250cm9sID0gbGlzdChzZWVkID0gMTIzNCkpCgojIHF1aWNrIGxpc3Qgb2YgdGhlIHRvcCB3b3JkcyBwZXIgdG9waWMgKG5vIHByb2JhYmlsaXRpZXMgYXR0YWNoZWQpCmdldF90ZXJtcyh0b3BpY19tb2RlbCwgMTApCgojIHdvcmQtdG9waWMgcHJvYmFiaWxpdGllcywgbmVlZGVkIGZvciB0aGUgY2hhcnQgYmVsb3cKdG9waWNfd29yZHMgPC0gdGlkeSh0b3BpY19tb2RlbCwgbWF0cml4ID0gImJldGEiKSB8PgogIGdyb3VwX2J5KHRvcGljKSB8PgogIHNsaWNlX21heChvcmRlcl9ieSA9IGJldGEsIG4gPSAxMCkgfD4KICB1bmdyb3VwKCkKCnRvcGljX3dvcmRzIHw+CiAgYXJyYW5nZSh0b3BpYywgZGVzYyhiZXRhKSkgfD4KICBncm91cF9ieSh0b3BpYykgfD4KICBzdW1tYXJpc2UodG9wX3dvcmRzID0gcGFzdGUodGVybSwgY29sbGFwc2UgPSAiLCAiKSwgLmdyb3VwcyA9ICJkcm9wIikgfD4KICBrYWJsZShjYXB0aW9uID0gIlRvcCB3b3JkcyBmb3IgZWFjaCBvZiB0aGUgc2l4IHRvcGljcyIpCmBgYAoKYGBge3IgcmVxXzNjaWl9CiMgYmFyIGNoYXJ0IG9mIHRoZSB0b3Agd29yZHMgcGVyIHRvcGljLCBvbmUgY29sb3VyIHBlciB0b3BpYwp0b3BpY193b3JkcyB8PgogIGdncGxvdChhZXMoeCA9IGJldGEsIHkgPSByZW9yZGVyX3dpdGhpbih4ID0gdGVybSwgYnkgPSBiZXRhLCB3aXRoaW4gPSB0b3BpYyksCiAgICAgICAgICAgICBmaWxsID0gZmFjdG9yKHRvcGljKSkpICsKICBnZW9tX2NvbChzaG93LmxlZ2VuZCA9IEZBTFNFKSArCiAgZmFjZXRfd3JhcCh+IHRvcGljLCBzY2FsZXMgPSAiZnJlZSIpICsKICBzY2FsZV95X3Jlb3JkZXJlZCgpICsKICBzY2FsZV9maWxsX2JyZXdlcihwYWxldHRlID0gIlNldDIiKSArCiAgbGFicyhzdWJ0aXRsZSA9ICJUb3AgMTAgaGlnaGVzdC1wcm9iYWJpbGl0eSB3b3JkcyBwZXIgTERBIHRvcGljIiwKICAgICAgIHggPSAiV29yZC10b3BpYyBwcm9iYWJpbGl0eSAoYmV0YSkiLCB5ID0gTlVMTCkgKwogIHRoZW1lX21pbmltYWwoYmFzZV9zaXplID0gMTApCgojIC0tLS0gV29yZCBjbG91ZCBvZiB0aGUgODAgbW9zdCBmcmVxdWVudCB3b3JkcyAtLS0tCnNldC5zZWVkKDIwMjYpCndvcmRjbG91ZCh3b3JkcyA9IHdvcmRfY291bnRzJHdvcmQsIGZyZXEgPSB3b3JkX2NvdW50cyRmcmVxdWVuY3ksIG1heC53b3JkcyA9IDgwLCByYW5kb20ub3JkZXIgPSBGQUxTRSwKICAgICAgICAgIHNjYWxlID0gYygzLjUsIDAuNiksIGNvbG9ycyA9IFJDb2xvckJyZXdlcjo6YnJld2VyLnBhbCg4LCAiRGFyazIiKSkKCmBgYAoKYGBge3IgcmVxXzNkfQojIGFzc2lnbiBlYWNoIHBvc3QgaXRzIHNpbmdsZSBtb3N0IGxpa2VseSB0b3BpYywgd2l0aCB0aGUgbW9kZWwncyBjb25maWRlbmNlCnBvc3RfdG9waWNzIDwtIHRpZHkodG9waWNfbW9kZWwsIG1hdHJpeCA9ICJnYW1tYSIpIHw+CiAgZ3JvdXBfYnkoZG9jdW1lbnQpIHw+CiAgc2xpY2VfbWF4KGdhbW1hLCBuID0gMSwgd2l0aF90aWVzID0gRkFMU0UpIHw+CiAgdW5ncm91cCgpIHw+CiAgdHJhbnNtdXRlKHBvc3RfaWQgPSBhcy5pbnRlZ2VyKGFzLmNoYXJhY3Rlcihkb2N1bWVudCkpLCB0b3BpYywgdG9waWNfY29uZmlkZW5jZSA9IGdhbW1hKQoKIyBzaGFyZSBvZiBwb3N0cyBmYWxsaW5nIGludG8gZWFjaCB0b3BpYwpwb3N0X3RvcGljcyB8PgogIGNvdW50KHRvcGljLCBuYW1lID0gInBvc3RzIikgfD4KICBtdXRhdGUocGN0ID0gcm91bmQoMTAwICogcG9zdHMgLyBzdW0ocG9zdHMpLCAxKSkgfD4KICBrYWJsZShjYXB0aW9uID0gIlNoYXJlIG9mIHBvc3RzIGZhbGxpbmcgaW50byBlYWNoIHRvcGljIikKYGBgCgojIyMgQ29tbWVudGFyeSBvbiB0b3BpYyBpbnNpZ2h0cwoKVGhlIGZvbGxvd2luZyBzaXggdGhlbWVzIGhhdmUgYmVlbiBkZXZlbG9wZWQgYmFzZWQgb24gdGhlIG1vc3QgZnJlcXVlbnQgd29yZHMgb2YgZWFjaCB0b3BpYyAobnVtYmVycyBhcmUgcmFuZG9tLCBoZW5jZSBkZXNjcmlwdGlvbiBuZWVkcyB0byBiZSBjb25maXJtZWQgdXNpbmcgdGhlIHRhYmxlIOKAnFRvcCB3b3JkcyBmb3IgZWFjaCBvZiB0aGUgc2l4IHRvcGljc+KAnSBvbiBlYWNoIGtuaXQpLiBUaGUgZmlyc3QgdG9waWMgZGVhbHMgd2l0aCBidXNpbmVzcyBhbmQgcHJpY2luZyAocHJpY2UsIG1pbGxpb24sIENoaW5hLCBzdHJvbmcsIGZvcmVjYXN0KTsgdGhlIHNlY29uZCB0b3BpYyBkZWFscyB3aXRoIHByb2R1Y3RzIGFuZCBudXRyaXRpb24gKHByb2R1Y3RzLCBjcmVhbSwgbnV0cml0aW9uLCBjaGVlc2UsIHByb3RlaW4pOyB0aGUgdGhpcmQgdG9waWMgZGVhbHMgd2l0aCBjb21tdW5pdHkgYW5kIHNjaG9vbHMgKHN1cHBvcnQsIHNjaG9vbCwgY29tbXVuaXR5LCBraWRzLCBjb21tdW5pdGllcyk7IHRoZSBmb3VydGggdG9waWMgZGVhbHMgd2l0aCBhd2FyZHMgYW5kIGluZHVzdHJ5IHJlY29nbml0aW9uIChhd2FyZHMsIGNvbmdyYXR1bGF0aW9ucywgaW5kdXN0cnksIGFnY2hhdG56KSBhbmQgY2FwdHVyZXMgbWlsayB0YW5rZXIgcG9zdHM7IHRoZSBmaWZ0aCB0b3BpYyBkZWFscyB3aXRoIGZhcm1pbmcgYW5kIGVudmlyb25tZW50IChmYXJtLCB3YXRlciwgY293cywgZmFybWluZywgZW52aXJvbm1lbnQpOyB0aGUgc2l4dGggdG9waWMgZGVhbHMgd2l0aCB0ZWFtLCBldmVudHMgYW5kIHNpdGUgYWN0aXZpdHkgKHRlYW0sIHNpdGUsIGV2ZW50LCBDaHJpc3RtYXMsIEdEVCwgZ2xvYmFsKSBhbmQgaXMgdGhlIGJpZ2dlc3QgdG9waWMgY2FwdHVyaW5nIDIwLjYlIG9mIHRoZSBwb3N0cy4gVGhlIHNpeHRoIHRvcGljIGlzIHJhdGhlciBpbmRpc3RpbmN0aXZlLCBhbmQgd29yZHMgbGlrZSDigJxtdXNpY+KAnSAodG9waWMgNSkgYW5kIOKAnGpvaG7igJ0gKHRvcGljIDEpIGFyZSBhbWJpZ3VvdXMuIFRvcGljIHNpemVzIGFyZSBmYWlybHkgYmFsYW5jZWQgKDEyLjQlIHRvIDIwLjYlKS4gSW4gdGhpcyBkYXRhc2V0LCB0aGUgbW9zdCBwcm9taW5lbnQgdG9waWNzIGV4dGVuZCBiZXlvbmQgZGlyZWN0IHByb2R1Y3QtcmVsYXRlZCBjb250ZW50IGFuZCBpbmNsdWRlIGJ1c2luZXNzLCBjb21tdW5pdHksIGluZHVzdHJ5IHJlY29nbml0aW9uLCBmYXJtaW5nIGFuZCBldmVudHMuCgojIyBMZXZlbCBvZiB1c2VyIGVuZ2FnZW1lbnQgYW5kIGZhY3RvcnMgdGhhdCBpbmZsdWVuY2UgdXNlciBlbmdhZ2VtZW50CgpUaGUgYGxpa2VDb3VudGAgY2FuIGJlIGFwcGxpZWQgdG8gZXZlcnkgc2luZ2xlIHBvc3QsIGFuZCB0aGVyZWZvcmUsIGl0IGlzIGNvbnNpZGVyZWQgYSBtYWluIGRlcGVuZGVudCB2YXJpYWJsZSBmb3IgY3Jvc3MtcGxhdGZvcm0gY29tcGFyaXNvbi4gVGhlIGNvbW1lbnRzLCBzaGFyZXMsIGFuZCB2aWRlb3MgcGxheWVkIGFyZSBjb3VudGVkIHdoZXJlIHBvc3NpYmxlOyBob3dldmVyLCB0aGV5IGNhbm5vdCBiZSBjb25zaWRlcmVkIGEgZGlyZWN0bHkgY29tcGFyYWJsZSB2YXJpYWJsZSBkdWUgdG8gbGFjayBvZiBkYXRhIG9uIHNvbWUgcGxhdGZvcm1zLgoKYGBge3IgcmVxXzNkaX0KIyBTdW1tYXJpc2UgZW5nYWdlbWVudCBzdGF0aXN0aWNzIGJ5IHBsYXRmb3JtCnNhZmVfbWVkaWFuIDwtIGZ1bmN0aW9uKHgpIGlmIChhbGwoaXMubmEoeCkpKSBOQV9yZWFsXyBlbHNlIG1lZGlhbih4LCBuYS5ybSA9IFRSVUUpCgpwbGF0Zm9ybV9lbmdhZ2VtZW50IDwtIHBvc3RzIHw+CiAgZ3JvdXBfYnkocGxhdGZvcm0pIHw+CiAgc3VtbWFyaXNlKAogICAgcG9zdHMgICAgICAgICAgID0gbigpLAogICAgbWVkaWFuX2xpa2VzICAgID0gbWVkaWFuKGxpa2VDb3VudCwgbmEucm0gPSBUUlVFKSwKICAgIGlxcl9saWtlcyAgICAgICA9IElRUihsaWtlQ291bnQsIG5hLnJtID0gVFJVRSksCiAgICBtZWRpYW5fY29tbWVudHMgPSBzYWZlX21lZGlhbihjb21tZW50Q291bnQpLAogICAgbWVkaWFuX3NoYXJlcyAgID0gc2FmZV9tZWRpYW4oc2hhcmVDb3VudCksCiAgICBtZWRpYW5fcGxheXMgICAgPSBzYWZlX21lZGlhbih2aWRlb3BsYXlDb3VudCksCiAgICAuZ3JvdXBzID0gImRyb3AiCiAgKQoKcGxhdGZvcm1fZW5nYWdlbWVudCB8PgogIGthYmxlKGRpZ2l0cyA9IDEsIGNhcHRpb24gPSAiRW5nYWdlbWVudCBieSBwbGF0Zm9ybSAtIGRlc2NyaXB0aXZlIHN0YXRpc3RpY3MiKQpgYGAKCmBgYHtyIHJlcV8zZGlpfQojIGRpc3RyaWJ1dGlvbiBvZiBsaWtlcyBwZXIgcG9zdCAobG9nIHNjYWxlKSAtIHNob3dzIHRoZSBoZWF2eSByaWdodCBza2V3CnBvc3RzIHw+CiAgZ2dwbG90KGFlcyh4ID0gbGlrZUNvdW50ICsgMSkpICsKICBnZW9tX2hpc3RvZ3JhbShiaW5zID0gNDAsIGZpbGwgPSAiIzIyNWVhOCIpICsKICBzY2FsZV94X2xvZzEwKCkgKwogIGxhYnModGl0bGUgPSAiRGlzdHJpYnV0aW9uIG9mIGxpa2VzIHBlciBwb3N0IiwKICAgICAgIHN1YnRpdGxlID0gIkxvZyBzY2FsZTsgbW9zdCBwb3N0cyByZWNlaXZlIGZldyBsaWtlcyIsCiAgICAgICB4ID0gIkxpa2VzICsgMSAobG9nIHNjYWxlKSIsIHkgPSAiTnVtYmVyIG9mIHBvc3RzIikgKwogIHRoZW1lX21pbmltYWwoYmFzZV9zaXplID0gMTEpCmBgYAoKTW9zdCBwb3N0cyByZWNlaXZlIHZlcnkgZmV3IGxpa2VzLCB3aGlsZSBhIHNtYWxsIG51bWJlciByZWNlaXZlIG1hbnksIHNvIHRoZSBkaXN0cmlidXRpb24gaXMgc3Ryb25nbHkgcmlnaHQtc2tld2VkLiBUaGlzIGlzIHdoeSBtZWRpYW5zIGFuZCBub24tcGFyYW1ldHJpYyB0ZXN0cyBhcmUgdXNlZC4KCmBgYHtyIHJlcV8zZX0KIyBwb3N0aW5nIHZvbHVtZSBvdmVyIHRpbWUgYnkgcGxhdGZvcm0KcG9zdHMgfD4KICBjb3VudChwb3N0X3llYXIsIHBsYXRmb3JtKSB8PgogIGdncGxvdChhZXMoeCA9IHBvc3RfeWVhciwgeSA9IG4sIGZpbGwgPSBwbGF0Zm9ybSkpICsKICBnZW9tX2NvbCgpICsKICBzY2FsZV9maWxsX2JyZXdlcihwYWxldHRlID0gIlNldDIiKSArCiAgbGFicyh0aXRsZSA9ICJOdW1iZXIgb2YgcG9zdHMgcGVyIHllYXIgYnkgcGxhdGZvcm0iLAogICAgICAgeCA9ICJZZWFyIiwgeSA9ICJQb3N0cyIsIGZpbGwgPSAiUGxhdGZvcm0iKSArCiAgdGhlbWVfbWluaW1hbChiYXNlX3NpemUgPSAxMCkKYGBgCgpUd2l0dGVyL1ggYWNjb3VudHMgZm9yIG1vc3Qgb2YgdGhlIHBvc3RzIGluIHRoZSBkYXRhc2V0ICg2LDIyMCBvZiA5LDAwMSksIHNvIG92ZXJhbGwgcG9zdGluZyB2b2x1bWUgaXMgbGFyZ2VseSBkcml2ZW4gYnkgdGhpcyBwbGF0Zm9ybS4KCmBgYHtyIHJlcV8zZWl9CiNwbG90X2VuZ2FnZW1lbnRfYnlfcGxhdGZvcm0KIyBib3hwbG90IG9mIGVuZ2FnZW1lbnQgYnkgcGxhdGZvcm07IGxvZzEwKDEgKyBsaWtlcykga2VlcHMgYSBoYW5kZnVsIG9mCiMgdmlyYWwgcG9zdHMgZnJvbSBzcXVhc2hpbmcgdGhlIHJlc3Qgb2YgdGhlIGRpc3RyaWJ1dGlvbgpwb3N0cyB8PgogIGdncGxvdChhZXMoeCA9IGZjdF9yZW9yZGVyKHBsYXRmb3JtLCBsaWtlQ291bnQsIC5mdW4gPSBtZWRpYW4pLCB5ID0gbG9nMTAobGlrZUNvdW50ICsgMSksCiAgICAgICAgICAgICBmaWxsID0gcGxhdGZvcm0pKSArCiAgZ2VvbV9ib3hwbG90KG91dGxpZXIuYWxwaGEgPSAwLjI1LCBzaG93LmxlZ2VuZCA9IEZBTFNFKSArCiAgY29vcmRfZmxpcCgpICsKICBzY2FsZV9maWxsX2JyZXdlcihwYWxldHRlID0gIkJsdWVzIikgKwogIGxhYnModGl0bGUgPSAiRW5nYWdlbWVudCBieSBwbGF0Zm9ybSIsCiAgICAgICBzdWJ0aXRsZSA9ICJsb2cxMCgxICsgbGlrZXMpIHBlciBwb3N0LCBzbyBhIGZldyB2aXJhbCBwb3N0cyBkb24ndCBoaWRlIGV2ZXJ5dGhpbmcgZWxzZSIsCiAgICAgICB4ID0gTlVMTCwgeSA9ICJsb2cxMCgxICsgbGlrZXMpIikgKwogIHRoZW1lX21pbmltYWwoYmFzZV9zaXplID0gMTEpCmBgYAoKYGBge3IgcmVxXzNlaWl9CiMgYm94cGxvdCBvZiBlbmdhZ2VtZW50IGJ5IG1lZGlhIHR5cGUgLSBvbmx5IEZhY2Vib29rLCBJbnN0YWdyYW0gYW5kCiMgTGlua2VkSW4gdmFyeSB0aGVpciBtZWRpYSB0eXBlIGluIHRoaXMgZGF0YXNldApwb3N0cyB8PgogIGZpbHRlcihwbGF0Zm9ybSAlaW4lIGMoIkZhY2Vib29rIiwgIkluc3RhZ3JhbSIsICJMaW5rZWRJbiIpKSB8PgogIGdncGxvdChhZXMoeCA9IHBvc3RfbWVkaWEsIHkgPSBsb2cxMChsaWtlQ291bnQgKyAxKSwgZmlsbCA9IHBvc3RfbWVkaWEpKSArCiAgZ2VvbV9ib3hwbG90KG91dGxpZXIuYWxwaGEgPSAwLjI1KSArCiAgZmFjZXRfd3JhcCh+IHBsYXRmb3JtKSArCiAgc2NhbGVfZmlsbF9icmV3ZXIocGFsZXR0ZSA9ICJEYXJrMiIpICsKICBsYWJzKHRpdGxlID0gIkVuZ2FnZW1lbnQgYnkgbWVkaWEgdHlwZSIsCiAgICAgICBzdWJ0aXRsZSA9ICJOb3QgcmVjb3JkZWQgdnMuIGltYWdlIHZzLiB2aWRlbyB2cy4gc2lkZWNhciwgbG9nMTAoMSArIGxpa2VzKSIsCiAgICAgICB4ID0gTlVMTCwgeSA9ICJsb2cxMCgxICsgbGlrZXMpIiwgZmlsbCA9ICJNZWRpYSB0eXBlIikgKwogIHRoZW1lX21pbmltYWwoYmFzZV9zaXplID0gMTEpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDIwLCBoanVzdCA9IDEpKQpgYGAKCmBgYHtyIHJlcV8zX2F9CiNwbG90X2xlbmd0aF92c19lbmdhZ2VtZW50CiMgc2NhdHRlciBwbG90IG9mIGNhcHRpb24gbGVuZ3RoIHZzLiBsaWtlczsgYm90aCBheGVzIGxvZ2dlZCBzaW5jZSBib3RoCiMgdmFyaWFibGVzIGFyZSByaWdodC1za2V3ZWQKcG9zdHMgfD4KICBnZ3Bsb3QoYWVzKHggPSB3b3JkX2NvdW50ICsgMSwgeSA9IGxpa2VDb3VudCArIDEsIGNvbG91ciA9IHBsYXRmb3JtKSkgKwogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjM1KSArCiAgc2NhbGVfeF9sb2cxMCgpICsKICBzY2FsZV95X2xvZzEwKCkgKwogIHNjYWxlX2NvbG91cl9icmV3ZXIocGFsZXR0ZSA9ICJTZXQxIikgKwogIGxhYnModGl0bGUgPSAiUG9zdCBsZW5ndGggdnMuIGVuZ2FnZW1lbnQiLAogICAgICAgc3VidGl0bGUgPSAiQ2FwdGlvbiB3b3JkIGNvdW50IGFnYWluc3QgbGlrZXMgKGxvZy1sb2cgc2NhbGUpLCBieSBwbGF0Zm9ybSIsCiAgICAgICB4ID0gIldvcmRzIGluIHRoZSBwb3N0IChsb2cgc2NhbGUpIiwgeSA9ICJMaWtlcyAobG9nIHNjYWxlKSIsIGNvbG91ciA9ICJQbGF0Zm9ybSIpICsKICB0aGVtZV9taW5pbWFsKGJhc2Vfc2l6ZSA9IDExKQpgYGAKCmBgYHtyIHJlcV8zX2J9CiMgYm94cGxvdCBvZiBlbmdhZ2VtZW50IGJ5IHRvcGljLCBsaW5raW5nIFBhcnQgMyhhKSdzIHRvcGljcyB0byBQYXJ0IDMoYikncwojIGVuZ2FnZW1lbnQgcXVlc3Rpb24KcG9zdHMgfD4KICBsZWZ0X2pvaW4ocG9zdF90b3BpY3MsIGJ5ID0gInBvc3RfaWQiKSB8PgogIGZpbHRlcighaXMubmEodG9waWMpKSB8PgogIGdncGxvdChhZXMoeCA9IGZhY3Rvcih0b3BpYyksIHkgPSBsb2cxMChsaWtlQ291bnQgKyAxKSwgZmlsbCA9IGZhY3Rvcih0b3BpYykpKSArCiAgZ2VvbV9ib3hwbG90KG91dGxpZXIuYWxwaGEgPSAwLjI1LCBzaG93LmxlZ2VuZCA9IEZBTFNFKSArCiAgc2NhbGVfZmlsbF9icmV3ZXIocGFsZXR0ZSA9ICJCbHVlcyIpICsKICBsYWJzKHRpdGxlID0gIkVuZ2FnZW1lbnQgYnkgdG9waWMiLAogICAgICAgc3VidGl0bGUgPSAiRG9lcyB3aGF0IGEgcG9zdCBpcyBhYm91dCByZWxhdGUgdG8gaG93IHdlbGwgaXQgZG9lcz8gKGxvZzEwKDEgKyBsaWtlcykpIiwKICAgICAgIHggPSAiVG9waWMiLCB5ID0gImxvZzEwKDEgKyBsaWtlcykiKSArCiAgdGhlbWVfbWluaW1hbChiYXNlX3NpemUgPSAxMSkKYGBgCgpQcmlvciB0byBzZWxlY3RpbmcgYW55IHRlc3RzLCB0aGUgYXNzdW1wdGlvbnMgb2Ygbm9ybWFsaXR5ICh1c2luZyBTaGFwaXJvLVdpbGsgdGVzdCBvbiBlYWNoIHBsYXRmb3JtLCBidXQgcmVkdWNpbmcgc2FtcGxlIHNpemUgdG8gNSwwMDAgZm9yIHRob3NlIHBsYXRmb3JtcyB3aGljaCBoYXZlIG1vcmUgZGF0YSBzaW5jZSBzaGFwaXJvLnRlc3QoKSBjYW4gaGFuZGxlIG1heGltdW0gNSwwMDAgb2JzZXJ2YXRpb25zKSBhbmQgZXF1YWxpdHkgb2YgdmFyaWFuY2UgKHVzaW5nIHJzdGF0aXg6OmxldmVuZV90ZXN0KCkpIGhhdmUgYmVlbiB2ZXJpZmllZCBmb3IgdGhlIHBsYXRmb3JtIHRlc3Qgd2hpbGUgdGhlIGFzc3VtcHRpb24gb2YgTGV2ZW5lJ3MgdGVzdCBoYXMgYmVlbiB2ZXJpZmllZCBmb3IgdGhlIGhhc2h0YWcgdGVzdC4KCmBgYHtyIHJlcV8zX2N9CiNiaXZhcmlhdGVfc2V0dXAKIyBwb3N0cyBqb2luZWQgdG8gdGhlaXIgdG9waWMsIGFuZCBwb3N0cyB3aXRoIHZpZGVvLXBsYXkgZGF0YSAtIGJvdGggcmV1c2VkIGJlbG93CnBvc3RzX3dpdGhfdG9waWMgIDwtIHBvc3RzIHw+IGxlZnRfam9pbihwb3N0X3RvcGljcywgYnkgPSAicG9zdF9pZCIpIHw+IGZpbHRlcighaXMubmEodG9waWMpKSB8PgogIG11dGF0ZSh0b3BpYyA9IGZhY3Rvcih0b3BpYykpCnZpZGVvc193aXRoX3BsYXlzIDwtIHBvc3RzIHw+IGZpbHRlcighaXMubmEodmlkZW9wbGF5Q291bnQpKQoKIyBsb2coMSArIGxpa2VzKSBjb2x1bW4gdXNlZCBmb3IgdGhlIG5vcm1hbGl0eS92YXJpYW5jZSBjaGVja3MgYW5kIHRoZSBBTk9WQQpwb3N0c19sb2cgPC0gcG9zdHMgfD4gbXV0YXRlKGxvZ19saWtlcyA9IGxvZzEwKGxpa2VDb3VudCArIDEpKQpgYGAKCmBgYHtyIHJlcV8zX2R9CiMgU2hhcGlyby1XaWxrIG5vcm1hbGl0eSBjaGVjaywgcGVyIHBsYXRmb3JtIChzYW1wbGVkIHRvIDUsMDAwIHJvd3Mgd2hlcmUKIyBuZWVkZWQsIHNpbmNlIFR3aXR0ZXIvWCBhbG9uZSBoYXMgb3ZlciA2LDAwMCBwb3N0cykKcG9zdHNfbG9nIHw+CiAgZ3JvdXBfYnkocGxhdGZvcm0pIHw+CiAgZ3JvdXBfbWFwKH4gewogICAgdmFscyA8LSAueCRsb2dfbGlrZXMKICAgIGlmIChsZW5ndGgodmFscykgPiA1MDAwKSB2YWxzIDwtIHNhbXBsZSh2YWxzLCA1MDAwKQogICAgdGVzdCA8LSBzaGFwaXJvLnRlc3QodmFscykKICAgIHRpYmJsZShwbGF0Zm9ybSA9IHVuaXF1ZSgueSRwbGF0Zm9ybSksIG4gPSBsZW5ndGgoLngkbG9nX2xpa2VzKSwKICAgICAgICAgICBXID0gdW5uYW1lKHRlc3Qkc3RhdGlzdGljKSwgcF92YWx1ZSA9IHRlc3QkcC52YWx1ZSkKICB9KSB8PgogIGJpbmRfcm93cygpIHw+CiAga2FibGUoZGlnaXRzID0gMywgY2FwdGlvbiA9ICJTaGFwaXJvLVdpbGsgbm9ybWFsaXR5IGNoZWNrLCBsb2coMSArIGxpa2VzKSBieSBwbGF0Zm9ybSIpCgojIGhvbW9nZW5laXR5LW9mLXZhcmlhbmNlIGNoZWNrCnBvc3RzX2xvZyB8PiBtdXRhdGUocGxhdGZvcm0gPSBmYWN0b3IocGxhdGZvcm0pKSB8PiBsZXZlbmVfdGVzdChsb2dfbGlrZXMgfiBwbGF0Zm9ybSkKYGBgCgpGYWNlYm9vayBhbmQgTGlua2VkSW4gYXBwcm9hY2ggbm9ybWFsaXR5IChwIFw+IC4wNSksIHdoZXJlYXMgSW5zdGFncmFtLCBUaWtUb2ssIFR3aXR0ZXIvWCwgYW5kIFlvdVR1YmUgZGV2aWF0ZSBmcm9tIG5vcm1hbCAocCBcPCAuMDAxKSwgd2l0aCBzaWduaWZpY2FudCBkaWZmZXJlbmNlcyBiZXR3ZWVuIHZhcmlhbmNlcyBmb3IgdGhlIGRpZmZlcmVudCBwbGF0Zm9ybXMgKExldmVuZSdzIHRlc3QsIHAgXDwgLjAwMSkuIEhlbmNlLCB0aGUgS3J1c2thbC1XYWxsaXMgb3V0Y29tZSBpcyBjb25zaWRlcmVkIHRoZSBwcmltYXJ5IGZpbmRpbmcsIHdpdGggQU5PVkEgaW5jbHVkZWQgYXMgd2VsbC4KCmBgYHtyIHJlcV8zX2V9CiMgb25lLXdheSBBTk9WQSAocGFyYW1ldHJpYykgd2l0aCBUdWtleSdzIEhTRCBwb3N0LWhvYwphbm92YV9wbGF0Zm9ybSA8LSBhb3YobG9nX2xpa2VzIH4gcGxhdGZvcm0sIGRhdGEgPSBwb3N0c19sb2cpCnN1bW1hcnkoYW5vdmFfcGxhdGZvcm0pCgphcy5kYXRhLmZyYW1lKFR1a2V5SFNEKGFub3ZhX3BsYXRmb3JtKSRwbGF0Zm9ybSkgfD4KICByb3VuZCgzKSB8PgogIGthYmxlKGNhcHRpb24gPSAiVHVrZXkncyBIU0QgLSBwYWlyd2lzZSBwbGF0Zm9ybSBkaWZmZXJlbmNlcyIpCgojIEtydXNrYWwtV2FsbGlzIChub24tcGFyYW1ldHJpYyBlcXVpdmFsZW50KSB3aXRoIER1bm4ncyB0ZXN0IGFzIHBvc3QtaG9jCnBsYXRmb3JtX3Rlc3QgPC0ga3J1c2thbC50ZXN0KGxpa2VDb3VudCB+IHBsYXRmb3JtLCBkYXRhID0gcG9zdHMpCnBsYXRmb3JtX3Rlc3QKCmR1bm5fdGVzdChwb3N0cywgbGlrZUNvdW50IH4gcGxhdGZvcm0sIHAuYWRqdXN0Lm1ldGhvZCA9ICJib25mZXJyb25pIikgfD4KICBzZWxlY3QoZ3JvdXAxLCBncm91cDIsIHN0YXRpc3RpYywgcC5hZGosIHAuYWRqLnNpZ25pZikKYGBgCgpgYGB7ciByZXFfM19mfQojIGNoaS1zcXVhcmVkIHRlc3Q6IGlzIG1lZGlhIHR5cGUgbGlua2VkIHRvIGJlaW5nIGFib3ZlL2JlbG93IHRoZSBwbGF0Zm9ybSdzCiMgb3duIG1lZGlhbiBlbmdhZ2VtZW50PyBSZXN0cmljdGVkIHRvIEZhY2Vib29rLCBJbnN0YWdyYW0gYW5kIExpbmtlZEluLAojIHRoZSBvbmx5IHBsYXRmb3JtcyB3aGVyZSBtZWRpYSB0eXBlIHZhcmllcwpwb3N0c19tZWRpYSA8LSBwb3N0cyB8PgogIGZpbHRlcihwbGF0Zm9ybSAlaW4lIGMoIkZhY2Vib29rIiwgIkluc3RhZ3JhbSIsICJMaW5rZWRJbiIpKSB8PgogIGdyb3VwX2J5KHBsYXRmb3JtKSB8PgogIG11dGF0ZShoaWdoX2VuZ2FnZW1lbnQgPSBsaWtlQ291bnQgPiBtZWRpYW4obGlrZUNvdW50KSkgfD4KICB1bmdyb3VwKCkKCmNoaV90YWJsZTEgPC0gdGFibGUocG9zdHNfbWVkaWEkcG9zdF9tZWRpYSwgcG9zdHNfbWVkaWEkaGlnaF9lbmdhZ2VtZW50KQpjaGlfdGFibGUxCgpjaGlzcS50ZXN0KGNoaV90YWJsZTEpJGV4cGVjdGVkICAgIyBjaGVjazogYWxsIGV4cGVjdGVkIGNvdW50cyBzaG91bGQgYmUgYXQgbGVhc3QgNQpjaGlzcS50ZXN0KGNoaV90YWJsZTEpCmNoaXNxLnRlc3QoY2hpX3RhYmxlMSkkc3RkcmVzCgpgYGAKCmBgYHtyIHJlcV8zX2d9CiMgaGFzaHRhZyB1c2UgYW5kIG1lZGlhbiBsaWtlcywgYnkgcGxhdGZvcm0KcG9zdHMgfD4KICBncm91cF9ieShwbGF0Zm9ybSkgfD4KICBzdW1tYXJpc2UocGN0X2hhc2h0YWcgPSByb3VuZCgxMDAgKiBtZWFuKGhhc19oYXNodGFnKSwgMSksCiAgICAgICAgICAgIG1lZGlhbl93aXRoID0gbWVkaWFuKGxpa2VDb3VudFtoYXNfaGFzaHRhZ10pLAogICAgICAgICAgICBtZWRpYW5fd2l0aG91dCA9IG1lZGlhbihsaWtlQ291bnRbIWhhc19oYXNodGFnXSksCiAgICAgICAgICAgIC5ncm91cHMgPSAiZHJvcCIpIHw+CiAga2FibGUoY2FwdGlvbiA9ICJIYXNodGFnIHVzZSBhbmQgbWVkaWFuIGxpa2VzIGJ5IHBsYXRmb3JtIikKIyBkb2VzIGhhc2h0YWcgdXNlIHJlbGF0ZSB0byBlbmdhZ2VtZW50PyB0d28gZ3JvdXBzLCBzbyBhIHQtdGVzdCAvCiMgTWFubi1XaGl0bmV5IFUgY29tcGFyaXNvbgpwb3N0c19sb2cgfD4gbXV0YXRlKGhhc19oYXNodGFnID0gZmFjdG9yKGhhc19oYXNodGFnKSkgfD4gbGV2ZW5lX3Rlc3QobG9nX2xpa2VzIH4gaGFzX2hhc2h0YWcpCgp0LnRlc3QobG9nX2xpa2VzIH4gaGFzX2hhc2h0YWcsIGRhdGEgPSBwb3N0c19sb2cpICAgICAgICAjIFdlbGNoJ3MgdC10ZXN0IChwYXJhbWV0cmljKQp3aWxjb3gudGVzdChsb2dfbGlrZXMgfiBoYXNfaGFzaHRhZywgZGF0YSA9IHBvc3RzX2xvZykgICAjIE1hbm4tV2hpdG5leSBVIChub24tcGFyYW1ldHJpYykKYGBgCgpgYGB7ciByZXFfM19ofQojIG1lZGlhbiBsaWtlcyBwZXIgdG9waWMsIHRvIHNob3cgZGlyZWN0aW9uCnBvc3RzX3dpdGhfdG9waWMgfD4KICBncm91cF9ieSh0b3BpYykgfD4KICBzdW1tYXJpc2UocG9zdHMgPSBuKCksIG1lZGlhbl9saWtlcyA9IG1lZGlhbihsaWtlQ291bnQpLCAuZ3JvdXBzID0gImRyb3AiKSB8PgogIGthYmxlKGNhcHRpb24gPSAiTWVkaWFuIGxpa2VzIGJ5IHRvcGljIikKIyBsaWtlcyBhY3Jvc3MgdGhlIHNpeCB0b3BpY3MgLSBLcnVza2FsLVdhbGxpcyB3aXRoIER1bm4ncyB0ZXN0IGFzIHBvc3QtaG9jCnRvcGljX3Rlc3QgPC0ga3J1c2thbC50ZXN0KGxpa2VDb3VudCB+IHRvcGljLCBkYXRhID0gcG9zdHNfd2l0aF90b3BpYykKdG9waWNfdGVzdAoKZHVubl90ZXN0KHBvc3RzX3dpdGhfdG9waWMsIGxpa2VDb3VudCB+IHRvcGljLCBwLmFkanVzdC5tZXRob2QgPSAiYm9uZmVycm9uaSIpIHw+CiAgZmlsdGVyKHAuYWRqIDwgMC4wNSkgfD4KICBzZWxlY3QoZ3JvdXAxLCBncm91cDIsIHN0YXRpc3RpYywgcC5hZGosIHAuYWRqLnNpZ25pZikKYGBgCgpgYGB7ciByZXFfNF9hfQojIHBvc3QgbGVuZ3RoIHZzIGxpa2VzIHdpdGhpbiBlYWNoIHBsYXRmb3JtCnBvc3RzIHw+CiAgZ3JvdXBfYnkocGxhdGZvcm0pIHw+CiAgc3VtbWFyaXNlKHJobyA9IHJvdW5kKGNvcih3b3JkX2NvdW50LCBsaWtlQ291bnQsIG1ldGhvZCA9ICJzcGVhcm1hbiIpLCAyKSwKICAgICAgICAgICAgLmdyb3VwcyA9ICJkcm9wIikgfD4KICBrYWJsZShjYXB0aW9uID0gIlNwZWFybWFuIHJobzogd29yZCBjb3VudCB2cyBsaWtlcywgYnkgcGxhdGZvcm0iKQoKIyBjb3JyZWxhdGlvbiBtYXRyaXggYWNyb3NzIHRoZSBlbmdhZ2VtZW50IG1ldHJpY3MgYW5kIHBvc3QgbGVuZ3RoLAojIHZpc3VhbGlzZWQgYXMgYSBjb3JyZWxvZ3JhbQpwb3N0cyB8PgogIHNlbGVjdChsaWtlQ291bnQsIGNvbW1lbnRDb3VudCwgc2hhcmVDb3VudCwgdmlkZW9wbGF5Q291bnQsIHdvcmRfY291bnQpIHw+CiAgY29yKG1ldGhvZCA9ICJzcGVhcm1hbiIsIHVzZSA9ICJwYWlyd2lzZS5jb21wbGV0ZS5vYnMiKSB8PgogIGNvcnJwbG90KHR5cGUgPSAidXBwZXIiLCB0bC5jb2wgPSJibGFjayIsIGFkZENvZWYuY29sID0iYmxhY2siLCBudW1iZXIuY2V4ID0gMC45LAogICAgICAgICAgIGNvbD0gY29sb3JSYW1wUGFsZXR0ZShjKCIjZDczMDI3IiwgIndoaXRlIiwgIiMxYTk4NTAiKSkgKDIwMCksCiAgICAgICAgICAgdGl0bGUgPSAiU3BlYXJtYW4gY29ycmVsYXRpb25zOiBlbmdhZ2VtZW50IG1ldHJpY3MgYW5kIHBvc3QgbGVuZ3RoIiwgbWFyID0gYyAoMCwwLDIsOSkpCgojIHZpZGVvIHBsYXlzIHZzLiBsaWtlcyBzcGVjaWZpY2FsbHkKcGxheXNfbGlrZXNfdGVzdCA8LSBjb3IudGVzdCh2aWRlb3Nfd2l0aF9wbGF5cyR2aWRlb3BsYXlDb3VudCwgdmlkZW9zX3dpdGhfcGxheXMkbGlrZUNvdW50LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtZXRob2QgPSAic3BlYXJtYW4iLCBleGFjdCA9IEZBTFNFKQpwbGF5c19saWtlc190ZXN0CmBgYAoKYGBge3IgcmVxXzRfYn0KI2JpdmFyaWF0ZV9zdW1tYXJ5CiMgc3VtbWFyeSB0YWJsZSBvZiB0aGUgaGVhZGxpbmUgcmVzdWx0IGZyb20gZWFjaCB0ZXN0CnRpYmJsZSgKICBhbmFseXNpcyA9IGMoIkxpa2VzIGJ5IHBsYXRmb3JtIChLcnVza2FsLVdhbGxpcykiLAogICAgICAgICAgICAgICAiTGlrZXMgYnkgbWVkaWEgdHlwZSwgaGlnaCB2cy4gbG93IGVuZ2FnZW1lbnQgKGNoaS1zcXVhcmVkKSIsCiAgICAgICAgICAgICAgICJMaWtlcyBieSBoYXNodGFnIHVzZSAoTWFubi1XaGl0bmV5IFUpIiwKICAgICAgICAgICAgICAgIkxpa2VzIGJ5IHRvcGljIChLcnVza2FsLVdhbGxpcykiLAogICAgICAgICAgICAgICAiUG9zdCBsZW5ndGggdnMuIGxpa2VzIChTcGVhcm1hbikiLAogICAgICAgICAgICAgICAiVmlkZW8gcGxheXMgdnMuIGxpa2VzIChTcGVhcm1hbikiKSwKICBzdGF0aXN0aWMgPSBjKHBhc3RlMCgiSCA9ICIsIHJvdW5kKHVubmFtZShwbGF0Zm9ybV90ZXN0JHN0YXRpc3RpYyksIDIpKSwKICAgICAgICAgICAgICAgIHBhc3RlMCgiWC1zcXVhcmVkID0gIiwgcm91bmQodW5uYW1lKGNoaXNxLnRlc3QoY2hpX3RhYmxlMSkkc3RhdGlzdGljKSwgMikpLAogICAgICAgICAgICAgICAgcGFzdGUwKCJXID0gIiwgZm9ybWF0KHVubmFtZSh3aWxjb3gudGVzdChsb2dfbGlrZXMgfiBoYXNfaGFzaHRhZywgZGF0YSA9IHBvc3RzX2xvZykkc3RhdGlzdGljKSwgYmlnLm1hcmsgPSAiLCIpKSwKICAgICAgICAgICAgICAgIHBhc3RlMCgiSCA9ICIsIHJvdW5kKHVubmFtZSh0b3BpY190ZXN0JHN0YXRpc3RpYyksIDIpKSwKICAgICAgICAgICAgICAgIHBhc3RlMCgicmhvID0gIiwgcm91bmQodW5uYW1lKGNvci50ZXN0KHBvc3RzJHdvcmRfY291bnQsIHBvc3RzJGxpa2VDb3VudCwgbWV0aG9kID0gInNwZWFybWFuIiwgZXhhY3QgPSBGQUxTRSkkZXN0aW1hdGUpLCAzKSksCiAgICAgICAgICAgICAgICBwYXN0ZTAoInJobyA9ICIsIHJvdW5kKHVubmFtZShwbGF5c19saWtlc190ZXN0JGVzdGltYXRlKSwgMykpKSwKICBwX3ZhbHVlID0gYyhwbGF0Zm9ybV90ZXN0JHAudmFsdWUsCiAgICAgICAgICAgICAgY2hpc3EudGVzdChjaGlfdGFibGUxKSRwLnZhbHVlLAogICAgICAgICAgICAgIHdpbGNveC50ZXN0KGxvZ19saWtlcyB+IGhhc19oYXNodGFnLCBkYXRhID0gcG9zdHNfbG9nKSRwLnZhbHVlLAogICAgICAgICAgICAgIHRvcGljX3Rlc3QkcC52YWx1ZSwKICAgICAgICAgICAgICBjb3IudGVzdChwb3N0cyR3b3JkX2NvdW50LCBwb3N0cyRsaWtlQ291bnQsIG1ldGhvZCA9ICJzcGVhcm1hbiIsIGV4YWN0ID0gRkFMU0UpJHAudmFsdWUsCiAgICAgICAgICAgICAgcGxheXNfbGlrZXNfdGVzdCRwLnZhbHVlKQopIHw+CiAgbXV0YXRlKHBfdmFsdWUgPSBmb3JtYXQucHZhbChwX3ZhbHVlLCBkaWdpdHMgPSAzLCBlcHMgPSAwLjAwMSkpIHw+CiAga2FibGUoY2FwdGlvbiA9ICJCaXZhcmlhdGUgdGVzdCByZXN1bHRzIC0gc3VtbWFyeSIpCmBgYAoKIyMjIENvbW1lbnRhcnkgb24gZW5nYWdlbWVudCBpbnNpZ2h0cyBhbmQgcmVjb21tZW5kYXRpb25zCgpUaGVyZSBhcmUgbWFueSBtb3JlIG91dGxpZXJzIHRoYW4gb25lIHdvdWxkIGxpa2UgLSBzb21lIHBvc3RzIGhhdmUgaHVnZSBlbmdhZ2VtZW50IGNvbXBhcmVkIHRvIHRoZSByZXN0LCB3aGljaCBtZWFucyB0aGF0IG1lZGlhbnMgd2lsbCBiZSB1c2VkIHRvIGRlc2NyaWJlIHR5cGljYWwgZW5nYWdlbWVudC4gQWxzbywgd2hlbmV2ZXIgdGhlIHJlc3VsdHMgZnJvbSB0aGUgS3J1c2thbC1XYWxsaXMgdGVzdCBhbmQgTWFubi1XaGl0bmV5IHRlc3QgZGlmZmVyIGZyb20gdGhlIHJlc3VsdHMgb2YgdGhlIHBhcmFtZXRyaWMgdGVzdCwgdGhleSBhcmUgdHJlYXRlZCBhcyBwcmltYXJ5LgoKKipQbGF0Zm9ybS4qKiBUaGUgcGxhdGZvcm1zIHdpdGggdGhlIGhpZ2hlc3QgbWVkaWFuIG51bWJlciBvZiBsaWtlcyBhcmUgTGlua2VkSW4gKG1lZGlhbiA9IDE0MSkgYW5kIEluc3RhZ3JhbSAobWVkaWFuID0gMTMyKSwgdGhlbiBGYWNlYm9vayAobWVkaWFuID0gNjEpLiBBY2NvcmRpbmcgdG8gYm90aCBBTk9WQSAoRiA9IDMyNTcsIHAgXDwgLjAwMSkgYW5kIHRoZSBLcnVza2FsLVdhbGxpcyB0ZXN0IChIID0gNDcyOC40LCBwIFw8IC4wMDEpLCB0aGVyZSBhcmUgc2lnbmlmaWNhbnQgZGlmZmVyZW5jZXMgYmV0d2VlbiB0aGUgcGxhdGZvcm1zLiBQb3N0LWhvYyB0ZXN0cyAoVHVrZXkgYW5kIER1bm4pIHNob3cgZXZlcnkgcGFpciBkaWZmZXJzIHNpZ25pZmljYW50bHkgZXhjZXB0IEluc3RhZ3JhbSB2cyBMaW5rZWRJbiBhbmQgVHdpdHRlci9YIHZzIFlvdVR1YmUuIEhvd2V2ZXIsIGl0IHNob3VsZCBiZSBrZXB0IGluIG1pbmQgdGhhdCBpdCBpcyBub3QgYW4gZW5nYWdlbWVudC0qcmF0ZSogcmFua2luZyAtIHRoZXJlIGFyZSBubyBtZWFzdXJlcyBvZiBmb2xsb3dlcnMvZm9sbG93aW5nLCByZWFjaCwgcGFpZCBwcm9tb3Rpb24gYW5kIHRpbWUgbGl2ZWQgaW4gdGhlIGRhdGEuCgoqKk1lZGlhIHR5cGUuKiogSW1hZ2UgYW5kIHZpZGVvIHBvc3RzIG9uIEZhY2Vib29rLCBJbnN0YWdyYW0gYW5kIExpbmtlZEluIHdlcmUgZm91bmQgdG8gYmUgbW9yZSBvZnRlbiB0aGFuIGV4cGVjdGVkIGFib3ZlIHRoZSBtZWRpYW4gbnVtYmVyIG9mIGxpa2VzIGZvciB0aGF0IHBsYXRmb3JtIGNvbXBhcmVkIHRvIHBvc3RzIHdpdGggbm90IHJlY29yZGVkIG1lZGlhIHR5cGUgKGNoaS1zcXVhcmUgPSAzNi4wLCBkZiA9IDMsIHAgXDwgLjAwMTsgc3RhbmRhcmRpemVkIHJlc2lkdWFsczogSW1hZ2UgKzIuOSwgVmlkZW8gKzMuMiwgTm90IHJlY29yZGVkIOKAkyA1LjkpLiBTaWRlY2FyIHBvc3RzIGhhZCBubyBzaWduaWZpY2FudCBkaWZmZXJlbmNlLiAiTm90IHJlY29yZGVkIiByZWZlcnMgdG8gdGhlIG1lZGlhIHR5cGUgYmVpbmcgdW5rbm93biByYXRoZXIgdGhhbiBhIHRleHQgcG9zdCwgdGh1cyBpdCBnaXZlcyByaXNlIHRvIGZ1cnRoZXIgZXhwZXJpbWVudHMgb24gaW1hZ2UgYW5kIHZpZGVvIHBvc3RzLgoKKipIYXNodGFncy4qKiBGb3IgYWxsIHBsYXRmb3JtcyBjb21iaW5lZCwgdGhlcmUgYXJlIG1vcmUgbGlrZXMgZm9yIHBvc3RzIHdpdGggaGFzaHRhZ3MgY29tcGFyZWQgdG8gdGhvc2Ugd2l0aG91dCAoTWFubi1XaGl0bmV5IFUsIFcgPSA2LDA0MywxOTQsIHAgXDwgLjAwMSkuIEhvd2V2ZXIsIGhhc2h0YWdzIHZhcnkgYWNyb3NzIHBsYXRmb3JtcywgcmFuZ2luZyBmcm9tIDAlIG9uIFlvdVR1YmUgYW5kIDI24oCTMzYlIG9uIFR3aXR0ZXIvWCwgTGlua2VkSW4sIGFuZCBGYWNlYm9vaywgdG8gNTYlIG9uIEluc3RhZ3JhbSBhbmQgODUlIG9uIFRpa1RvaywgdGhlcmVmb3JlIHNvbWUgb2YgdGhlIGRpZmZlcmVuY2Ugb2JzZXJ2ZWQgYWJvdmUgbWlnaHQgYmUgYXR0cmlidXRhYmxlIHRvIHRoZSBwbGF0Zm9ybSByYXRoZXIgdGhhbiBoYXNodGFncy4gTG9va2luZyBhdCBpbmRpdmlkdWFsIHBsYXRmb3JtcywgdGhlIHRyZW5kIGlzIGluY29uc2lzdGVudDogcG9zdHMgd2l0aCBoYXNodGFncyBnZXQgbW9yZSBsaWtlcyBvbiBGYWNlYm9vayAobWVkaWFuOiA3NiB2cy4gNTQpLCBJbnN0YWdyYW0gKDE1NCB2cy4gOTUpIGFuZCBhbG1vc3Qgb24gVHdpdHRlci9YICgyIHZzLiAxKSwgd2hpbGUgbGVzcyBvbiBMaW5rZWRJbiAoMTIxIHZzLiAxNDkpIGFuZCBUaWtUb2sgKDcgdnMuIDExLjUpLiBNZWRpYW5zIGFib3ZlIGFyZSBkZXNjcmlwdGl2ZSBvbmx5IGFuZCBoYXZlIG5vdCBiZWVuIHRlc3RlZDsgdGhlcmVmb3JlLCBoYXNodGFncyBjYW5ub3QgYmUgY29uc2lkZXJlZCBhbiBhYnNvbHV0ZSBlbmdhZ2VtZW50IHByZWRpY3Rvci4KCioqUG9zdCBsZW5ndGguKiogV2hlbiB3ZSBjb25zaWRlciBhbGwgdGhlIHBsYXRmb3JtcyB0b2dldGhlciwgd2Ugb2JzZXJ2ZSBhIG1vZGVyYXRlIHBvc2l0aXZlIGNvcnJlbGF0aW9uIGJldHdlZW4gY2FwdGlvbiBsZW5ndGggYW5kIGxpa2VzIChTcGVhcm1hbiByaG8gPSAwLjQ2LCBwIFw8IC4wMDEpLiBJbiBlYWNoIHBsYXRmb3JtIHNlcGFyYXRlbHksIHRoZSBjb3JyZWxhdGlvbiB2YXJpZXMgZnJvbSAtMC4xOCAoSW5zdGFncmFtKSB0byAwLjM4IChUd2l0dGVyL1gpOiBpdCBpcyBsb3cgYnV0IHBvc2l0aXZlIGZvciBGYWNlYm9vayAoMC4xMCksIFRpa1RvayAoMC4xMCksIFlvdVR1YmUgKDAuMTUpLCBhbmQgTGlua2VkSW4gKDAuMjApLCB3aGlsZSBpdCBpcyBuZWdhdGl2ZSBmb3IgSW5zdGFncmFtLiBUaGVyZSBpcyB2YXJpYWJpbGl0eSBpbiB0aGUgc3RyZW5ndGggYW5kIG5hdHVyZSBvZiB0aGUgcmVsYXRpb25zaGlwLCB3aGljaCBpbmRpY2F0ZXMgdGhhdCBwbGF0Zm9ybSBjb250ZXh0IG1heSBiZSBzaWduaWZpY2FudCB0byB1bmRlcnN0YW5kIHRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiBjYXB0aW9uIGxlbmd0aCBhbmQgbGlrZXMuCgoqKlZpZGVvIHBsYXlzLioqIE9mIDg4NiBwb3N0cyB3aXRoIHBsYXlzIGluZm9ybWF0aW9uLCB0aGVyZSBpcyBhIHN0cm9uZyBjb3JyZWxhdGlvbiBiZXR3ZWVuIHZpZGVvIHBsYXlzIGFuZCBsaWtlcyAoU3BlYXJtYW4gcmhvID0gMC43MywgcCBcPCAuMDAxKS4gQWdhaW4sIGNvcnJlbGF0aW9uIGRvZXNuJ3QgaW1wbHkgY2F1c2F0aW9uIGhlcmUuCgoqKlRvcGljLioqIExpa2VzIHZhcnkgZ3JlYXRseSBiYXNlZCBvbiB0aGUgdG9waWMgKEggZm9yIEtydXNrYWwtV2FsbGlzID0gNjY0LjY1LCBwIFw8IC4wMDEpLiBUaGUgdG9waWMgdGhhdCBoYXMgdGhlIGdyZWF0ZXN0IG1lZGlhbiBsaWtlcyBpcyBUb3BpYyAzIChjb21tdW5pdHkgYW5kIHNjaG9vbHMpIHdpdGggYSBtZWRpYW4gdmFsdWUgb2YgOCwgZm9sbG93ZWQgYnkgVG9waWMgNCAoNywgYXdhcmRzIGFuZCByZWNvZ25pdGlvbiksIFRvcGljIDIgKDUpIGFuZCBUb3BpYyA1ICg0KS4gVGhlIHRvcGljcyBoYXZpbmcgdGhlIGZld2VzdCBtZWRpYW4gbGlrZXMgYXJlIFRvcGljIDEgKGJ1c2luZXNzIGFuZCBwcmljaW5nKSBhbmQgVG9waWMgNiAodGVhbSBhbmQgZXZlbnRzKSBhdCAxLiBJbiBEdW5uJ3MgdGVzdCAoQm9uZmVycm9uaSksIG1vc3QgdG9waWMgcGFpcnMgc2hvd2VkIHNpZ25pZmljYW50IGRpZmZlcmVuY2VzOyB0aGUgbm9uLXNpZ25pZmljYW50IHBhaXJ3aXNlIGRpZmZlcmVuY2VzIHdlcmUgYmV0d2VlbiBUb3BpY3MgMiBhbmQgNSBhbmQgVG9waWNzIDMgYW5kIDQuIFRoaXMgYW5hbHlzaXMgZGlkIG5vdCB0YWtlIGludG8gYWNjb3VudCB0aGUgcGxhdGZvcm1zIGFuZCBjb3VsZCBhbHNvIGluY2x1ZGUgc3BlY2lmaWMgcGxhdGZvcm1zLCBzbyB0aGUgbmV4dCBzdGVwIHdvdWxkIGJlIGNvbXBhcmluZyB0aGUgdG9waWNzIGluIG9uZSBwbGF0Zm9ybSBvdmVyIHRpbWUuCgoqKlJlY29tbWVuZGF0aW9ucy4qKiBJbiBmdXR1cmUgZGF0YSBjb2xsZWN0aW9uLCBGb250ZXJyYSBzaG91bGQgaW5jbHVkZSByZWFjaC9pbXByZXNzaW9ucywgZm9sbG93ZXIgY291bnQgYXQgdGhlIHRpbWUgb2YgcG9zdGluZywgcGFpZC9vcmdhbmljIHN0YXR1cyBhbmQgbW9yZSBjb21wbGV0ZSBzaGFyZSBhbmQgdmlldyBjb3VudHMgc28gdGhhdCBlbmdhZ2VtZW50IHJhdGVzIGNhbiBiZSBjYWxjdWxhdGVkIGFuZCBwbGF0Zm9ybXMgY2FuIGJlIGNvbXBhcmVkIG1vcmUgZmFpcmx5LiBCYXNlZCBvbiB0aGUgY3VycmVudCBkYXRhLCB0aGUgcmVzdWx0cyBzdWdnZXN0IHNldmVyYWwgYXJlYXMgZm9yIGZ1cnRoZXIgdGVzdGluZzogKDEpIGRldmVsb3AgcGxhdGZvcm0tc3BlY2lmaWMgc3RyYXRlZ2llcyByYXRoZXIgdGhhbiByZWx5aW5nIG9uIG9uZSBhcHByb2FjaCwgYmVjYXVzZSBtZWRpYW4gbGlrZXMgZGlmZmVyIHN1YnN0YW50aWFsbHkgYWNyb3NzIHBsYXRmb3JtczsgKDIpIHRlc3QgaW1hZ2UgYW5kIHZpZGVvIGNvbnRlbnQgb24gRmFjZWJvb2ssIEluc3RhZ3JhbSBhbmQgTGlua2VkSW4sIHdoZXJlIHRoZXNlIG1lZGlhIHR5cGVzIHdlcmUgbW9yZSBmcmVxdWVudGx5IGFzc29jaWF0ZWQgd2l0aCBhYm92ZS1tZWRpYW4gbGlrZXM7ICgzKSB0ZXN0IGhhc2h0YWcgdXNlIHNlcGFyYXRlbHkgYnkgcGxhdGZvcm0sIHBhcnRpY3VsYXJseSBvbiBGYWNlYm9vayBhbmQgSW5zdGFncmFtLCB3aGlsZSByZWNvZ25pc2luZyB0aGF0IHRoZSBvYnNlcnZlZCBkaWZmZXJlbmNlcyBkbyBub3QgZXN0YWJsaXNoIHRoYXQgaGFzaHRhZ3MgY2F1c2UgaGlnaGVyIGVuZ2FnZW1lbnQ7ICg0KSBhdm9pZCByZWx5aW5nIG9uIGNhcHRpb24gbGVuZ3RoIGFsb25lIGFzIGFuIGVuZ2FnZW1lbnQgc3RyYXRlZ3kgYmVjYXVzZSB0aGUgcmVsYXRpb25zaGlwIHZhcmllcyBzdWJzdGFudGlhbGx5IGFjcm9zcyBwbGF0Zm9ybXM7ICg1KSBpbnZlc3RpZ2F0ZSBjb21tdW5pdHkvc2Nob29sIGFuZCBhd2FyZHMvcmVjb2duaXRpb24gY29udGVudCBmdXJ0aGVyLCBhcyB0aGVzZSB0b3BpY3MgaGFkIGhpZ2hlciBtZWRpYW4gbGlrZXMgaW4gdGhpcyBkYXRhc2V0OyBhbmQgKDYpIHVzZSByYXRlLWJhc2VkIGVuZ2FnZW1lbnQgbWVhc3VyZXMgb25jZSByZWFjaCBhbmQgZm9sbG93ZXIgZGF0YSBhcmUgYXZhaWxhYmxlLgoKIyBEZWNsYXJhdGlvbiBmb3IgR2VuZXJhdGl2ZSBBSSB1c2UKCi0gKipBSSB0b29sOioqIChDb3BpbG90KS4KLSAqKlB1cnBvc2Ugb2YgdXNpbmcgdGhlIHRvb2w6KiogSSB1c2VkIEFJIHRvIG91dGxpbmUgdGhlIGFuYWx5c2lzLCB0byB3cml0ZSBhbmQgZGVidWcgUiBjb2RlLCBhbmQgdG8gZW5zdXJlIG15IGNvbW1lbnRhcnkgY29ycmVzcG9uZGVkIHRvIHRoZSBrbml0dGVkIHJlc3VsdHMuCi0gKipBbW91bnQgb2YgQUkgdXNhZ2U6KiogQ29waWxvdCBoZWxwZWQgbWUgdG8gZGVjaWRlIG9uIHdoaWNoIGRlY2lzaW9uIGlzIGltcG9ydGFudCBmb3IgdGhlIGFuYWx5c2lzIGFuZCBnYXZlIG1lIHRoZSBjb2RlIGFzIHBlciBteSBpbnN0cnVjdGlvbiBhbmQgZHJhZnQgY29tbWVudGFyeS4gSG93ZXZlciwgYWxsIHRoZSBjb21tZW50YXJ5IGFuZCBmaW5hbCBkZWNpc2lvbiB3ZXJlIG1pbmUuIEkgcmFuLCByZXZpZXdlZCwgZWRpdGVkIGFuZCBtYWRlIHRoZSBmaW5hbCBhbmFseXRpY2FsIGRlY2lzaW9ucyBteXNlbGYuCi0gKipNZXRob2RzIHVzZWQgdG8gZW5zdXJlIGFjY3VyYWN5OioqSSBrbml0dGVkIHRoZSBkb2N1bWVudCB0byBtYWtlIHN1cmUgaXQgd29ya3Mgd2l0aG91dCBhbnkgZXJyb3JzLCBjb21wYXJlZCBhbGwgbnVtYmVycyBpbiB0aGUgY29tbWVudGFyeSBhZ2FpbnN0IHRoZSBrbml0dGVkIHJlc3VsdHMsIHJlYWQgc2FtcGxlcyBvZiBwb3N0cyBvbiBlYWNoIHRvcGljIHRvIHZlcmlmeSB0aGF0IHRoZSB0b3BpYyBuYW1lcyB3ZXJlIGNvcnJlY3QsIGFuZCB1bmRlcnN0b29kIHdoeSBlYWNoIHN0YXRpc3RpY2FsIHRlc3Qgd2FzIHVzZWQuCg==