1 Load data

## Read
Data <- rio::import("rezultate_oxitocina_pre_post_O1a_id28corectat.xlsx", sheet = "oxitocina+cortizol")
Data_PsySOP <- readRDS("Date_merged_Psy&SOP.RDS")

2 Processing data

## Clean
Data <-
  Data %>%
    dplyr::filter(rowSums(is.na(.)) < 8) %>%               # filter out rows (no more than 7 NA on row)
    dplyr::select(which(colMeans(is.na(.)) < 0.5))         # filter out columns with Catalina's statistics (no more than 1/2 NA on column values)
## Transform in oder to be consistent with past behavioral data 
oldnames = colnames(Data)
newnames = c("ID_pre","Cortizol_pre", "Oxitocina_pre", "ID_post","Cortizol_post", "Oxitocina_post", "Conditie")
Data <-
  Data %>% 
  dplyr::rename_at(vars(oldnames), ~ newnames) %>%
  dplyr::select(-ID_post) %>%
  dplyr::mutate(ID_pre = stringr::str_remove_all(ID_pre, c(" proba A|/proba A"))) %>%     # small inconsistency "/proba"
  tidyr::separate(ID_pre,  c("ID", "Ziua", "Nr_zi"), "\\s+") %>%                          # split on white space
  dplyr::mutate(Ziua = rep("zi", length.out = n())) %>%
  tidyr::unite("Zi", c("Ziua", "Nr_zi"), sep = "", remove = TRUE) %>%
  mutate(ID = as.numeric(str_extract(ID, "[^/]+"))) %>%                                   # [^/]+ matches 1 or more chars other than /
  mutate(ID = as.character(ID))                                                             # ID Psy&SOP are char, not numeric...for merge
## Melt -- not needed here
Data_melt <-
  Data %>% 
  gather(variable, value, -ID, -Zi, -Conditie) %>%
  tidyr::separate(variable,  c("variable", "PrePost"), "_") %>%
  spread(variable, value) %>%
  mutate(PrePost = factor(PrePost, levels = c("pre","post"))) %>%
  dplyr::arrange(ID)

3 Merge with SOP

## Merge with Psy&SOP data
Data_merged <- left_join(Data_PsySOP, Data, by = c("ID", "Conditie"))             # all good: ID 1,4,36,42 dont have Oxy&Cort 
varnottable <- c("Nume_Prenume", "NA_per_row",
                 "IOS_mama", "IOS_tata", "IOS_iubit", "IOS_prieten", "IOS_personalitate",
                 sprintf("Stais_pre_%01d", seq(1,20)), 
                 sprintf("Stais_post_%01d", seq(1,20)))
Data_merged <- 
  Data_merged %>%
  rename_at(vars(ends_with("Pre")), funs(gsub("Pre", "pre", .))) %>%              # _Pre and _Post -> tolower ... helps for automation
  rename_at(vars(ends_with("Post")), funs(gsub("Post", "post", .))) %>%
  select(-varnottable)

3.1 Table of Merged O.1.A (Psy&SOP&Oxy)

Data_merged %>%                              
    DT::datatable(                                  # excel downloadable  DT table
      extensions = 'Buttons',
      options = list(pageLength = 20,
                     scrollX='500px', 
                     dom = 'Bfrtip', 
                     buttons = c('excel', "csv")))



4 Define Functions

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Define Function for mining correlations
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
## Function for p-value significance -- both for func_ancova_multibox(), Get_Top_Relationships() and Correlations_With_One()
stars_signif <- function(pval) {
  stars = "ns"
  if(pval <= 0.001)
    stars = "***"
  if(pval > 0.001 & pval <= 0.01)
    stars = "**"
  if(pval > 0.01 & pval <= 0.05)
    stars = "*"
  if(pval > 0.05 & pval <= 0.1)
    stars = "."
  stars
}
## Function that returns correlations of all variables in descending order.
# Arg for threshold with default at .3 will keep only correlantions above .3 and below -.3. Also has threshhold for p-value. 
Get_Top_Relationships <- function(data_set, 
                                  correlation_abs_threshold=0.3,
                                  pvalue_threshold=0.05) {
  require(psych)
  require(dplyr)
  feature_names <- names(data_set)
  # strip var names to index for pair-wise identification
  names(data_set) <- seq(1:ncol(data_set))
  # calculate correlation and significance numbers
  cor_data_df <- psych::corr.test(data_set)
  # apply var names to correlation matrix over index
  rownames(cor_data_df$r) <- feature_names
  colnames(cor_data_df$r) <- feature_names
  # top cor and sig
  relationships_set <- cor_data_df$ci[,c('r','p')]
  # apply var names to data over index pairs
  relationships_set$feature_1 <- feature_names[as.numeric(sapply(strsplit(rownames(relationships_set), "-"), `[`, 1))]
  relationships_set$feature_2 <- feature_names[as.numeric(
    sapply(strsplit(rownames(relationships_set), "-"), `[`, 2))]
  relationships_set <- dplyr::select(relationships_set, feature_1, feature_2, r, p) %>% dplyr::rename(correlation = r, p.value = p)
  # return only the most insteresting relationships
  return(relationships_set %>%
         filter(abs(correlation) > correlation_abs_threshold &
                  p.value < pvalue_threshold) %>%
         arrange(p.value) %>%
         mutate(p.signif = sapply(p.value, function(x) stars_signif(x)))) %>%
         mutate(p.value = round(p.value, 3)) 
}
## Function for ploting correlation data frames resulting from Get_Top_Relationships and Correlations_With_One()
func_dotplot_cor <- function(df){                                        # https://www.r-pkg.org/pkg/ggpubr
  dotplotcor_scale_fill <- function(...){                                # Fix colors to signif factor levels even if missing
    ggplot2:::manual_scale(                                   
      'color', 
      values = setNames(
        c("darkgreen", "green3", "lawngreen", "yellow", "red"), 
        c("***", "**", "*", ".", "ns")), 
      ...
    )
  }                                           
  
  dtoplot_theme <- 
    ggpubr::theme_pubr() +
    theme(axis.text.y = element_text(size = 10))
  
  if(!"Variable" %in% colnames(df)){                                             # in oder to work for both Get_Top_Relationships and Correlations_With_One()
  df <- 
    df %>%                                            
      unite(cor_between, c("feature_1", "feature_2"), sep = " X ")               # unite 2 columns to x name from plot
  }else df <- df %>% dplyr::rename(cor_between = Variable)                       # change Variable to x name from plot
  
  df %>%
    ggpubr::ggdotchart(x = "cor_between", y = "correlation",
                       color = "p.signif",                                       # Color by sig
                       #   palette = c("#00AFBB", "#E7B800", "#FC4E07"),         # Custom color palette
                       sorting = "descending",                                   # Sort value in descending order
                       add = "segments",                                         # Add segments from y = 0 to dots
                       add.params = list(color = "lightgray", size = 2),         # Change segment color and size
                       group = "p.signif",                                       # Order by groups
                       dot.size = 8,                                             # Large dot size
                       xlab = "",
                       rotate = TRUE,                                            # Rotate vertically
                       label = round(.$correlation, 1),                          # Add mpg values as dot labels
                       font.label = list(color = "white", size = 9, 
                                         vjust = 0.5),                           # Adjust label parameters
                       ggtheme = dtoplot_theme) +                                # ggplot2 theme
    dotplotcor_scale_fill() +                                            # Fix colors to signif factor levels even if missing
    geom_hline(yintercept = 0, linetype = 2, color = "lightgray")
}
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
## Function for t test and boxplot
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
func_t_box <- function(df, ID, Conditie, pre_var, post_var){
  df_modif <-
    df %>%
    select(ID, Conditie, pre_var, post_var) %>% 
    tidyr::drop_na() %>%
    gather(pre_var, post_var, key = "PrePost", value = "value") %>% 
    mutate_at(vars(c(1, 2)), funs(as.factor)) %>% 
    mutate(PrePost = factor(PrePost, levels = c(pre_var, post_var))) 
  
  stat_comp <-
    df_modif %>% 
    group_by(Conditie) %>% 
    do(tidy(t.test(.$value ~ .$PrePost,
                   paired = TRUE,
                   data=.)))
  
  plot <- 
    ggpubr::ggpaired(df_modif, x = "PrePost", y = "value", id = ID, 
                     color = "PrePost", line.color = "gray", line.size = 0.4,
                     palette = c("#00AFBB", "#FC4E07"), legend = "none") +
    facet_wrap(~Conditie) +
    stat_summary(fun.data = mean_se,  colour = "darkred") +
    ggpubr::stat_compare_means(method = "t.test", paired = TRUE, label.x = as.numeric(df_modif$PrePost) * 0.90, label.y = max(df_modif$value) * 1.15) + 
    ggpubr::stat_compare_means(method = "t.test", paired = TRUE, label = "p.signif", comparisons = list(c(pre_var, post_var)))
  
  cat(paste0("#### ", pre_var, " ", post_var, "\n", "\n"))
  print(stat_comp)
  print(plot)
}
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

5 Analyses

5.1 Pre-Post differences on Oxytocin and Cortisol

func_t_box(Data, "ID", "Conditie", "Cortizol_pre", "Cortizol_post")  

5.1.0.1 Cortizol_pre Cortizol_post

func_t_box(Data, "ID", "Conditie", "Oxitocina_pre", "Oxitocina_post") 

5.1.0.2 Oxitocina_pre Oxitocina_post

5.2 Correlations between Difference Scores

## Automatic Post-Pre Difference Scores
var_prepost <- colnames(Data_merged[, grepl(".*pre|.*post", colnames(Data_merged))])    # find variables that have "pre" and "post"
var_prepost <-  gsub("_pre", "", var_prepost)                                           # delete pre and post from colname
var_prepost <-  gsub("_post", "", var_prepost)
var_prepost <- unique(var_prepost)                                                      # keep unique -- all good         
new_diffvar_name <- vector(mode="character", length = length(var_prepost))              # initialize vector for for()
for(i in seq_along(var_prepost)){
    var_name_pre <- paste0(var_prepost[i], "_pre")
    var_name_post <- paste0(var_prepost[i], "_post")
    new_diffvar_name[i] <- paste0(var_prepost[i], "_Diff")
    Data_merged[, new_diffvar_name[i]] <- Data_merged[, var_name_post] - Data_merged[, var_name_pre]
}
# Select variables
vars_cor <- new_diffvar_name[!new_diffvar_name %in% c("AllButtons_Diff", "AllButtons45_Diff", 
                                                      "Ones_Diff", "Ones45_Diff",
                                                      "Threes_Diff", "Threes45_Diff", 
                                                      "Twos_Diff", "Twos45_Diff")]       # exclude these
vars_cor <- c(vars_cor,
              c("Vas_rel_global", "Vas_rel_arousal", "CRQ_1", "CRQ_2", "CRQ_3", "CRQ_4", "CRQ_5", "CRQ_6"))
## Correlations
Get_Top_Relationships(Data_merged[, vars_cor])
Get_Top_Relationships(Data_merged[, vars_cor]) %>% func_dotplot_cor()


6 Conditioning on VAS global

6.1 Pre-Post differences on Oxytocin and Cortisol

Data_merged %>%
  filter(Vas_rel_global >= 7) %>%
  func_t_box(., "ID", "Conditie", "Cortizol_pre", "Cortizol_post") 

6.1.0.1 Cortizol_pre Cortizol_post

Data_merged %>%
  filter(Vas_rel_global >= 7) %>%
  func_t_box(., "ID", "Conditie", "Oxitocina_pre", "Oxitocina_post") 

6.1.0.2 Oxitocina_pre Oxitocina_post


7 Moderation Analysis

## Define Function
func_moderation <- function(Data, dep, mod, pred){
  moderation <- 
    Data %>%
      medmod::mod(., dep = dep, mod = mod, pred = pred,
                  ci = TRUE, estMethod = 'standard', test = TRUE, simpleSlopeEst = FALSE, simpleSlopePlot = TRUE)
  
  cat(paste("<b> Moderation: ", "Dep = ", dep, "Pred = ", pred, "Mod = ", mod, "</b>"))
  moderation$mod %>% 
    knitr::kable(caption = "Moderation", digits = 3) %>%
    print()
  moderation$simpleSlope$plot %>%
    print()
}
## Apply Function
func_moderation(Data = Data_merged, dep = "Oxitocina_post", mod = "Vas_rel_arousal", pred = "Oxitocina_pre")

Moderation: Dep = Oxitocina_post Pred = Oxitocina_pre Mod = Vas_rel_arousal

term est se lower upper z p
Oxitocina_pre 0.727 0.057 0.615 0.840 12.669 0.000
Vas_rel_arousal 0.000 0.001 -0.003 0.003 -0.171 0.864
Oxitocina_pre:Vas_rel_arousal 0.004 0.002 0.000 0.008 2.038 0.042

func_moderation(Data = Data_merged, dep = "Cortizol_post", mod = "Vas_rel_arousal", pred = "Cortizol_pre")

Moderation: Dep = Cortizol_post Pred = Cortizol_pre Mod = Vas_rel_arousal

term est se lower upper z p
Cortizol_pre 0.483 0.025 0.434 0.533 19.142 0.000
Vas_rel_arousal 0.011 0.005 0.002 0.021 2.382 0.017
Cortizol_pre:Vas_rel_arousal 0.003 0.001 0.000 0.005 2.237 0.025

# bla <-
#   Data_merged %>%
#   mutate(Conditie = as.numeric(as.factor(Conditie)))
# 
# psych::mediate(data = bla, Oxitocina_post ~ Oxitocina_pre + Conditie + Vas_rel_global)   # moderation with covariate and diagram

8 Mediation Analysis




9 Session Info

R version 3.5.2 (2018-12-20)
Platform: x86_64-w64-mingw32/x64 (64-bit)
Running under: Windows >= 8 x64 (build 9200)

Matrix products: default

locale:
[1] LC_COLLATE=Romanian_Romania.1250  LC_CTYPE=Romanian_Romania.1250    LC_MONETARY=Romanian_Romania.1250 LC_NUMERIC=C                     
[5] LC_TIME=Romanian_Romania.1250    

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
 [1] bindrcpp_0.2.2     rio_0.5.16         plyr_1.8.4         summarytools_0.9.3 DT_0.5             ggpubr_0.2         magrittr_1.5      
 [8] broom_0.5.1        papaja_0.1.0.9842  psych_1.8.10       forcats_0.3.0      stringr_1.3.1      dplyr_0.7.8        purrr_0.2.5       
[15] readr_1.3.0        tidyr_0.8.2        tibble_1.4.2       ggplot2_3.1.0      tidyverse_1.2.1    pacman_0.5.0      

loaded via a namespace (and not attached):
 [1] nlme_3.1-137       bitops_1.0-6       matrixStats_0.54.0 lubridate_1.7.4    httr_1.4.0         tools_3.5.2        backports_1.1.3   
 [8] R6_2.4.0           lazyeval_0.2.1     colorspace_1.3-2   withr_2.1.2        tidyselect_0.2.5   mnormt_1.5-5       curl_3.3          
[15] compiler_3.5.2     cli_1.0.1          rvest_0.3.2        xml2_1.2.0         labeling_0.3       scales_1.0.0       checkmate_1.8.5   
[22] pbivnorm_0.6.0     digest_0.6.18      foreign_0.8-71     rmarkdown_1.11     base64enc_0.1-3    pkgconfig_2.0.2    htmltools_0.3.6   
[29] highr_0.7          htmlwidgets_1.3    rlang_0.3.0.1      readxl_1.1.0       rstudioapi_0.8     pryr_0.1.4         jmvcore_0.9.5.2   
[36] shiny_1.2.0        bindr_0.1.1        generics_0.0.2     jsonlite_1.6       crosstalk_1.0.0    zip_1.0.0          RCurl_1.95-4.11   
[43] rapportools_1.0    Rcpp_1.0.1         munsell_0.5.0      stringi_1.2.4      yaml_2.2.0         MASS_7.3-51.1      lavaan_0.6-3      
[50] grid_3.5.2         parallel_3.5.2     promises_1.0.1     crayon_1.3.4       lattice_0.20-38    haven_2.1.0        pander_0.6.3      
[57] hms_0.4.2          magick_2.0         knitr_1.21         pillar_1.3.1       tcltk_3.5.2        rjson_0.2.20       ggsignif_0.4.0    
[64] stats4_3.5.2       codetools_0.2-15   glue_1.3.1         evaluate_0.12      data.table_1.12.2  modelr_0.1.2       httpuv_1.4.5      
[71] cellranger_1.1.0   gtable_0.2.0       assertthat_0.2.1   xfun_0.4           openxlsx_4.1.0     mime_0.6           xtable_1.8-3      
[78] later_0.7.5        medmod_1.0.0      
 

A work by Claudiu Papasteri

claudiu.papasteri@gmail.com

 

LS0tDQp0aXRsZTogIjxicj4gTzFBIFJlcG9ydCIgDQpzdWJ0aXRsZTogIk94eXRvY2luIGFuZCBDb3J0aXNvbCINCmF1dGhvcjogIjxicj4gQ2xhdWRpdSBQYXBhc3RlcmkiDQpkYXRlOiAiYHIgZm9ybWF0KFN5cy50aW1lKCksICclZCAlbSAlWScpYCINCm91dHB1dDogDQogICAgaHRtbF9ub3RlYm9vazoNCiAgICAgICAgICAgIGNvZGVfZm9sZGluZzogaGlkZQ0KICAgICAgICAgICAgdG9jOiB0cnVlDQogICAgICAgICAgICB0b2NfZGVwdGg6IDINCiAgICAgICAgICAgIG51bWJlcl9zZWN0aW9uczogdHJ1ZQ0KICAgICAgICAgICAgdGhlbWU6IHNwYWNlbGFiDQogICAgICAgICAgICBoaWdobGlnaHQ6IHRhbmdvDQogICAgICAgICAgICBmb250LWZhbWlseTogQXJpYWwNCiAgICAgICAgICAgIGZpZ193aWR0aDogMTANCiAgICAgICAgICAgIGZpZ19oZWlnaHQ6IDkNCiAgICAjIHBkZl9kb2N1bWVudDogDQogICAgICAgICAgICAjIHRvYzogdHJ1ZQ0KICAgICAgICAgICAgIyB0b2NfZGVwdGg6IDINCiAgICAgICAgICAgICMgbnVtYmVyX3NlY3Rpb25zOiB0cnVlDQogICAgICAgICAgICAjIGZvbnRzaXplOiAxMXB0DQogICAgICAgICAgICAjIGdlb21ldHJ5OiBtYXJnaW49MWluDQogICAgICAgICAgICAjIGZpZ193aWR0aDogNw0KICAgICAgICAgICAgIyBmaWdfaGVpZ2h0OiA2DQogICAgICAgICAgICAjIGZpZ19jYXB0aW9uOiB0cnVlDQogICAgIyBnaXRodWJfZG9jdW1lbnQ6IA0KICAgICAgICAgICAgIyB0b2M6IHRydWUNCiAgICAgICAgICAgICMgdG9jX2RlcHRoOiAyDQogICAgICAgICAgICAjIGh0bWxfcHJldmlldzogZmFsc2UNCiAgICAgICAgICAgICMgZmlnX3dpZHRoOiA1DQogICAgICAgICAgICAjIGZpZ19oZWlnaHQ6IDUNCiAgICAgICAgICAgICMgZGV2OiBqcGVnDQotLS0NCg0KDQo8IS0tIFNldHVwIC0tPg0KDQoNCmBgYHtyIHNldHVwLCBpbmNsdWRlID0gRkFMU0V9DQojIGtpbnRyIG9wdGlvbnMNCmtuaXRyOjpvcHRzX2NodW5rJHNldCgNCiAgY29tbWVudCA9ICIjIiwNCiAgY29sbGFwc2UgPSBUUlVFLA0KICBlY2hvID0gVFJVRSwgd2FybmluZyA9IFRSVUUsIG1lc3NhZ2UgPSBUUlVFLCBjYWNoZSA9IFRSVUUgICAgICAgIyBlY2hvID0gRmFsc2UgZm9yIGdpdGh1Yl9kb2N1bWVudCwgYnV0IHdpbGwgYmUgZm9sZGVkIGluIGh0bWxfbm90ZWJvb2sNCikNCg0KIyBHZW5lcmFsIFIgb3B0aW9ucyBhbmQgaW5mbw0Kc2V0LnNlZWQoMTExKSAgICAgICAgICAgICAgICMgaW4gY2FzZSB3ZSB1c2UgcmFuZG9taXplZCBwcm9jZWR1cmVzICAgICAgIA0Kb3B0aW9ucyhzY2lwZW4gPSA5OTkpICAgICAgICMgcG9zaXRpdmUgdmFsdWVzIGJpYXMgdG93YXJkcyBmaXhlZCBhbmQgbmVnYXRpdmUgdG93YXJkcyBzY2llbnRpZmljIG5vdGF0aW9uDQoNCiMgTG9hZCBwYWNrYWdlcw0KaWYgKCFyZXF1aXJlKCJwYWNtYW4iKSkgaW5zdGFsbC5wYWNrYWdlcygicGFjbWFuIikNCnBhY2thZ2VzIDwtIGMoDQogICJ0aWR5dmVyc2UiLCAgICAgICMgYmVzdCB0aGluZyB0aGF0IGhhcHBlbmQgdG8gbWUNCiAgInBzeWNoIiwgICAgICAgICAgIyBnZW5lcmFsIHB1cnBvc2UgdG9vbGJveCBmb3IgcGVyc29uYWxpdHksIHBzeWNob21ldHJpYyB0aGVvcnkgYW5kIGV4cGVyaW1lbnRhbCBwc3ljaG9sb2d5DQogICJwYXBhamEiLCAgICAgICAgICMgZm9yIEFQQSBzdHlsZQ0KICAiYnJvb20iLCAgICAgICAgICAjIGZvciB0aWR5IG1vZGVsbGluZw0KICAiZ2dwbG90MiIsICAgICAgICAjIGJlc3QgcGxvdHMNCiAgImdncHViciIsICAgICAgICAgIyBnZ3Bsb3QyIHRvIHB1YmxpY2F0aW9uIHF1YWxpdHkNCiAgIkRUIiwgICAgICAgICAgICAgIyBuaWNlIHNlYXJjaGFibGUgYW5kIGRvd25sb2FkYWJsZSB0YWJsZXMNCiAgInN1bW1hcnl0b29scyIsDQogICJwbHlyIiwgDQogICJyaW8iDQogICMgLCAuLi4NCikNCmlmICghcmVxdWlyZSgicGFjbWFuIikpIGluc3RhbGwucGFja2FnZXMoInBhY21hbiIpDQpwYWNtYW46OnBfbG9hZChjaGFyID0gcGFja2FnZXMpDQoNCg0KIyBUaGVtZXMgZm9yIGdncGxvdDIgcGxvdGluZyAoaGVyZSB1c2VkIEFQQSBzdHlsZSkNCnRoZW1lX3NldCh0aGVtZV9hcGEoKSkNCmBgYA0KDQpgYGB7ciB3b3JraW5nX2RpcmVjdG9yeSwgaW5jbHVkZSA9IEZBTFNFfQ0Kd2QgPC0gIkM6L1VzZXJzL01paGFpL0Rlc2t0b3AvUiBOb3RlYm9va3Mvbm90ZWJvb2tzL28xYS1yZXBvcnQtb3h5Ig0Kc2V0d2Qod2QpDQpgYGANCg0KDQo8IS0tIFJlcG9ydCAtLT4NCg0KDQojIExvYWQgZGF0YQ0KDQpgYGB7ciByYXdfZGF0YSwgcmVzdWx0cyA9ICdoaWRlJ30NCiMjIFJlYWQNCkRhdGEgPC0gcmlvOjppbXBvcnQoInJlenVsdGF0ZV9veGl0b2NpbmFfcHJlX3Bvc3RfTzFhX2lkMjhjb3JlY3RhdC54bHN4Iiwgc2hlZXQgPSAib3hpdG9jaW5hK2NvcnRpem9sIikNCkRhdGFfUHN5U09QIDwtIHJlYWRSRFMoIkRhdGVfbWVyZ2VkX1BzeSZTT1AuUkRTIikNCmBgYA0KDQoNCiMgUHJvY2Vzc2luZyBkYXRhDQoNCmBgYHtyIHByb2Nlc3NlZF9kYXRhLCBjYWNoZSA9IFRSVUUsIGRlcGVuZHNvbiA9ICJyYXdfZGF0YSJ9DQojIyBDbGVhbg0KRGF0YSA8LQ0KICBEYXRhICU+JQ0KICAgIGRwbHlyOjpmaWx0ZXIocm93U3Vtcyhpcy5uYSguKSkgPCA4KSAlPiUgICAgICAgICAgICAgICAjIGZpbHRlciBvdXQgcm93cyAobm8gbW9yZSB0aGFuIDcgTkEgb24gcm93KQ0KICAgIGRwbHlyOjpzZWxlY3Qod2hpY2goY29sTWVhbnMoaXMubmEoLikpIDwgMC41KSkgICAgICAgICAjIGZpbHRlciBvdXQgY29sdW1ucyB3aXRoIENhdGFsaW5hJ3Mgc3RhdGlzdGljcyAobm8gbW9yZSB0aGFuIDEvMiBOQSBvbiBjb2x1bW4gdmFsdWVzKQ0KDQoNCiMjIFRyYW5zZm9ybSBpbiBvZGVyIHRvIGJlIGNvbnNpc3RlbnQgd2l0aCBwYXN0IGJlaGF2aW9yYWwgZGF0YSANCm9sZG5hbWVzID0gY29sbmFtZXMoRGF0YSkNCm5ld25hbWVzID0gYygiSURfcHJlIiwiQ29ydGl6b2xfcHJlIiwgIk94aXRvY2luYV9wcmUiLCAiSURfcG9zdCIsIkNvcnRpem9sX3Bvc3QiLCAiT3hpdG9jaW5hX3Bvc3QiLCAiQ29uZGl0aWUiKQ0KDQpEYXRhIDwtDQogIERhdGEgJT4lIA0KICBkcGx5cjo6cmVuYW1lX2F0KHZhcnMob2xkbmFtZXMpLCB+IG5ld25hbWVzKSAlPiUNCiAgZHBseXI6OnNlbGVjdCgtSURfcG9zdCkgJT4lDQogIGRwbHlyOjptdXRhdGUoSURfcHJlID0gc3RyaW5ncjo6c3RyX3JlbW92ZV9hbGwoSURfcHJlLCBjKCIgcHJvYmEgQXwvcHJvYmEgQSIpKSkgJT4lICAgICAjIHNtYWxsIGluY29uc2lzdGVuY3kgIi9wcm9iYSINCiAgdGlkeXI6OnNlcGFyYXRlKElEX3ByZSwgIGMoIklEIiwgIlppdWEiLCAiTnJfemkiKSwgIlxccysiKSAlPiUgICAgICAgICAgICAgICAgICAgICAgICAgICMgc3BsaXQgb24gd2hpdGUgc3BhY2UNCiAgZHBseXI6Om11dGF0ZShaaXVhID0gcmVwKCJ6aSIsIGxlbmd0aC5vdXQgPSBuKCkpKSAlPiUNCiAgdGlkeXI6OnVuaXRlKCJaaSIsIGMoIlppdWEiLCAiTnJfemkiKSwgc2VwID0gIiIsIHJlbW92ZSA9IFRSVUUpICU+JQ0KICBtdXRhdGUoSUQgPSBhcy5udW1lcmljKHN0cl9leHRyYWN0KElELCAiW14vXSsiKSkpICU+JSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBbXi9dKyBtYXRjaGVzIDEgb3IgbW9yZSBjaGFycyBvdGhlciB0aGFuIC8NCiAgbXV0YXRlKElEID0gYXMuY2hhcmFjdGVyKElEKSkgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBJRCBQc3kmU09QIGFyZSBjaGFyLCBub3QgbnVtZXJpYy4uLmZvciBtZXJnZQ0KDQojIyBNZWx0IC0tIG5vdCBuZWVkZWQgaGVyZQ0KRGF0YV9tZWx0IDwtDQogIERhdGEgJT4lIA0KICBnYXRoZXIodmFyaWFibGUsIHZhbHVlLCAtSUQsIC1aaSwgLUNvbmRpdGllKSAlPiUNCiAgdGlkeXI6OnNlcGFyYXRlKHZhcmlhYmxlLCAgYygidmFyaWFibGUiLCAiUHJlUG9zdCIpLCAiXyIpICU+JQ0KICBzcHJlYWQodmFyaWFibGUsIHZhbHVlKSAlPiUNCiAgbXV0YXRlKFByZVBvc3QgPSBmYWN0b3IoUHJlUG9zdCwgbGV2ZWxzID0gYygicHJlIiwicG9zdCIpKSkgJT4lDQogIGRwbHlyOjphcnJhbmdlKElEKQ0KYGBgDQoNCjwhLS0gSW5zcGVjdCBEYXRhIC0gc3dpdGNoZWQgb2ZmIC0tPg0KYGBge3IgaW5zcGVjdGRhdGEsIGVjaG89RkFMU0UsIHJlc3VsdHM9ImhpZGUifSANCiMgcHJpbnQoc3VtbWFyeXRvb2xzOjpkZlN1bW1hcnkoRGF0ZSwgc3R5bGUgPSAnZ3JpZCcsIHBsYWluLmFzY2lpID0gRkFMU0UsIGdyYXBoLm1hZ25pZiA9IDAuODUpLCAgICAjIHN1cHByZXNzIG91dHB1dA0KIyAgICAgICBtZXRob2QgPSAncmVuZGVyJywgaGVhZGluZ3MgPSBGQUxTRSkNCiMgc3RyKERhdGUsIGxpc3QubGVuPW5jb2woRGF0ZSkpICAjIGRhdGEgdHlwZXMgYXJlIGZpbmUNCmBgYA0KDQoNCiMgTWVyZ2Ugd2l0aCBTT1ANCg0KYGBge3IgbWVyZ2VkX2RhdGEsIGNhY2hlID0gVFJVRSwgZGVwZW5kc29uID0gInByb2Nlc3NlZF9kYXRhIn0NCiMjIE1lcmdlIHdpdGggUHN5JlNPUCBkYXRhDQpEYXRhX21lcmdlZCA8LSBsZWZ0X2pvaW4oRGF0YV9Qc3lTT1AsIERhdGEsIGJ5ID0gYygiSUQiLCAiQ29uZGl0aWUiKSkgICAgICAgICAgICAgIyBhbGwgZ29vZDogSUQgMSw0LDM2LDQyIGRvbnQgaGF2ZSBPeHkmQ29ydCANCg0KdmFybm90dGFibGUgPC0gYygiTnVtZV9QcmVudW1lIiwgIk5BX3Blcl9yb3ciLA0KICAgICAgICAgICAgICAgICAiSU9TX21hbWEiLCAiSU9TX3RhdGEiLCAiSU9TX2l1Yml0IiwgIklPU19wcmlldGVuIiwgIklPU19wZXJzb25hbGl0YXRlIiwNCiAgICAgICAgICAgICAgICAgc3ByaW50ZigiU3RhaXNfcHJlXyUwMWQiLCBzZXEoMSwyMCkpLCANCiAgICAgICAgICAgICAgICAgc3ByaW50ZigiU3RhaXNfcG9zdF8lMDFkIiwgc2VxKDEsMjApKSkNCg0KRGF0YV9tZXJnZWQgPC0gDQogIERhdGFfbWVyZ2VkICU+JQ0KICByZW5hbWVfYXQodmFycyhlbmRzX3dpdGgoIlByZSIpKSwgZnVucyhnc3ViKCJQcmUiLCAicHJlIiwgLikpKSAlPiUgICAgICAgICAgICAgICMgX1ByZSBhbmQgX1Bvc3QgLT4gdG9sb3dlciAuLi4gaGVscHMgZm9yIGF1dG9tYXRpb24NCiAgcmVuYW1lX2F0KHZhcnMoZW5kc193aXRoKCJQb3N0IikpLCBmdW5zKGdzdWIoIlBvc3QiLCAicG9zdCIsIC4pKSkgJT4lDQogIHNlbGVjdCgtdmFybm90dGFibGUpDQpgYGANCg0KDQojIyBUYWJsZSBvZiBNZXJnZWQgTy4xLkEgKFBzeSZTT1AmT3h5KQ0KDQpgYGB7ciB0YWJsZV9tZXJnZWRfZGF0YX0NCkRhdGFfbWVyZ2VkICU+JSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIA0KICAgIERUOjpkYXRhdGFibGUoICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgZXhjZWwgZG93bmxvYWRhYmxlICBEVCB0YWJsZQ0KICAgICAgZXh0ZW5zaW9ucyA9ICdCdXR0b25zJywNCiAgICAgIG9wdGlvbnMgPSBsaXN0KHBhZ2VMZW5ndGggPSAyMCwNCiAgICAgICAgICAgICAgICAgICAgIHNjcm9sbFg9JzUwMHB4JywgDQogICAgICAgICAgICAgICAgICAgICBkb20gPSAnQmZydGlwJywgDQogICAgICAgICAgICAgICAgICAgICBidXR0b25zID0gYygnZXhjZWwnLCAiY3N2IikpKQ0KYGBgDQoNCg0KPGJyPg0KPGJyPg0KDQoNCiMgRGVmaW5lIEZ1bmN0aW9ucw0KDQpgYGB7ciBkZWZfZnVuY30NCiN+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn4NCiMgRGVmaW5lIEZ1bmN0aW9uIGZvciBtaW5pbmcgY29ycmVsYXRpb25zDQojfn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+DQojIyBGdW5jdGlvbiBmb3IgcC12YWx1ZSBzaWduaWZpY2FuY2UgLS0gYm90aCBmb3IgZnVuY19hbmNvdmFfbXVsdGlib3goKSwgR2V0X1RvcF9SZWxhdGlvbnNoaXBzKCkgYW5kIENvcnJlbGF0aW9uc19XaXRoX09uZSgpDQpzdGFyc19zaWduaWYgPC0gZnVuY3Rpb24ocHZhbCkgew0KICBzdGFycyA9ICJucyINCiAgaWYocHZhbCA8PSAwLjAwMSkNCiAgICBzdGFycyA9ICIqKioiDQogIGlmKHB2YWwgPiAwLjAwMSAmIHB2YWwgPD0gMC4wMSkNCiAgICBzdGFycyA9ICIqKiINCiAgaWYocHZhbCA+IDAuMDEgJiBwdmFsIDw9IDAuMDUpDQogICAgc3RhcnMgPSAiKiINCiAgaWYocHZhbCA+IDAuMDUgJiBwdmFsIDw9IDAuMSkNCiAgICBzdGFycyA9ICIuIg0KICBzdGFycw0KfQ0KDQoNCiMjIEZ1bmN0aW9uIHRoYXQgcmV0dXJucyBjb3JyZWxhdGlvbnMgb2YgYWxsIHZhcmlhYmxlcyBpbiBkZXNjZW5kaW5nIG9yZGVyLg0KIyBBcmcgZm9yIHRocmVzaG9sZCB3aXRoIGRlZmF1bHQgYXQgLjMgd2lsbCBrZWVwIG9ubHkgY29ycmVsYW50aW9ucyBhYm92ZSAuMyBhbmQgYmVsb3cgLS4zLiBBbHNvIGhhcyB0aHJlc2hob2xkIGZvciBwLXZhbHVlLiANCkdldF9Ub3BfUmVsYXRpb25zaGlwcyA8LSBmdW5jdGlvbihkYXRhX3NldCwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29ycmVsYXRpb25fYWJzX3RocmVzaG9sZD0wLjMsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcHZhbHVlX3RocmVzaG9sZD0wLjA1KSB7DQogIHJlcXVpcmUocHN5Y2gpDQogIHJlcXVpcmUoZHBseXIpDQogIGZlYXR1cmVfbmFtZXMgPC0gbmFtZXMoZGF0YV9zZXQpDQogICMgc3RyaXAgdmFyIG5hbWVzIHRvIGluZGV4IGZvciBwYWlyLXdpc2UgaWRlbnRpZmljYXRpb24NCiAgbmFtZXMoZGF0YV9zZXQpIDwtIHNlcSgxOm5jb2woZGF0YV9zZXQpKQ0KICAjIGNhbGN1bGF0ZSBjb3JyZWxhdGlvbiBhbmQgc2lnbmlmaWNhbmNlIG51bWJlcnMNCiAgY29yX2RhdGFfZGYgPC0gcHN5Y2g6OmNvcnIudGVzdChkYXRhX3NldCkNCiAgIyBhcHBseSB2YXIgbmFtZXMgdG8gY29ycmVsYXRpb24gbWF0cml4IG92ZXIgaW5kZXgNCiAgcm93bmFtZXMoY29yX2RhdGFfZGYkcikgPC0gZmVhdHVyZV9uYW1lcw0KICBjb2xuYW1lcyhjb3JfZGF0YV9kZiRyKSA8LSBmZWF0dXJlX25hbWVzDQogICMgdG9wIGNvciBhbmQgc2lnDQogIHJlbGF0aW9uc2hpcHNfc2V0IDwtIGNvcl9kYXRhX2RmJGNpWyxjKCdyJywncCcpXQ0KICAjIGFwcGx5IHZhciBuYW1lcyB0byBkYXRhIG92ZXIgaW5kZXggcGFpcnMNCiAgcmVsYXRpb25zaGlwc19zZXQkZmVhdHVyZV8xIDwtIGZlYXR1cmVfbmFtZXNbYXMubnVtZXJpYyhzYXBwbHkoc3Ryc3BsaXQocm93bmFtZXMocmVsYXRpb25zaGlwc19zZXQpLCAiLSIpLCBgW2AsIDEpKV0NCiAgcmVsYXRpb25zaGlwc19zZXQkZmVhdHVyZV8yIDwtIGZlYXR1cmVfbmFtZXNbYXMubnVtZXJpYygNCiAgICBzYXBwbHkoc3Ryc3BsaXQocm93bmFtZXMocmVsYXRpb25zaGlwc19zZXQpLCAiLSIpLCBgW2AsIDIpKV0NCiAgcmVsYXRpb25zaGlwc19zZXQgPC0gZHBseXI6OnNlbGVjdChyZWxhdGlvbnNoaXBzX3NldCwgZmVhdHVyZV8xLCBmZWF0dXJlXzIsIHIsIHApICU+JSBkcGx5cjo6cmVuYW1lKGNvcnJlbGF0aW9uID0gciwgcC52YWx1ZSA9IHApDQogICMgcmV0dXJuIG9ubHkgdGhlIG1vc3QgaW5zdGVyZXN0aW5nIHJlbGF0aW9uc2hpcHMNCiAgcmV0dXJuKHJlbGF0aW9uc2hpcHNfc2V0ICU+JQ0KICAgICAgICAgZmlsdGVyKGFicyhjb3JyZWxhdGlvbikgPiBjb3JyZWxhdGlvbl9hYnNfdGhyZXNob2xkICYNCiAgICAgICAgICAgICAgICAgIHAudmFsdWUgPCBwdmFsdWVfdGhyZXNob2xkKSAlPiUNCiAgICAgICAgIGFycmFuZ2UocC52YWx1ZSkgJT4lDQogICAgICAgICBtdXRhdGUocC5zaWduaWYgPSBzYXBwbHkocC52YWx1ZSwgZnVuY3Rpb24oeCkgc3RhcnNfc2lnbmlmKHgpKSkpICU+JQ0KICAgICAgICAgbXV0YXRlKHAudmFsdWUgPSByb3VuZChwLnZhbHVlLCAzKSkgDQoNCn0NCg0KDQojIyBGdW5jdGlvbiBmb3IgcGxvdGluZyBjb3JyZWxhdGlvbiBkYXRhIGZyYW1lcyByZXN1bHRpbmcgZnJvbSBHZXRfVG9wX1JlbGF0aW9uc2hpcHMgYW5kIENvcnJlbGF0aW9uc19XaXRoX09uZSgpDQpmdW5jX2RvdHBsb3RfY29yIDwtIGZ1bmN0aW9uKGRmKXsgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBodHRwczovL3d3dy5yLXBrZy5vcmcvcGtnL2dncHVicg0KICBkb3RwbG90Y29yX3NjYWxlX2ZpbGwgPC0gZnVuY3Rpb24oLi4uKXsgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgRml4IGNvbG9ycyB0byBzaWduaWYgZmFjdG9yIGxldmVscyBldmVuIGlmIG1pc3NpbmcNCiAgICBnZ3Bsb3QyOjo6bWFudWFsX3NjYWxlKCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgDQogICAgICAnY29sb3InLCANCiAgICAgIHZhbHVlcyA9IHNldE5hbWVzKA0KICAgICAgICBjKCJkYXJrZ3JlZW4iLCAiZ3JlZW4zIiwgImxhd25ncmVlbiIsICJ5ZWxsb3ciLCAicmVkIiksIA0KICAgICAgICBjKCIqKioiLCAiKioiLCAiKiIsICIuIiwgIm5zIikpLCANCiAgICAgIC4uLg0KICAgICkNCiAgfSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICANCiAgDQogIGR0b3Bsb3RfdGhlbWUgPC0gDQogICAgZ2dwdWJyOjp0aGVtZV9wdWJyKCkgKw0KICAgIHRoZW1lKGF4aXMudGV4dC55ID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCkpDQogIA0KICBpZighIlZhcmlhYmxlIiAlaW4lIGNvbG5hbWVzKGRmKSl7ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBpbiBvZGVyIHRvIHdvcmsgZm9yIGJvdGggR2V0X1RvcF9SZWxhdGlvbnNoaXBzIGFuZCBDb3JyZWxhdGlvbnNfV2l0aF9PbmUoKQ0KICBkZiA8LSANCiAgICBkZiAlPiUgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIA0KICAgICAgdW5pdGUoY29yX2JldHdlZW4sIGMoImZlYXR1cmVfMSIsICJmZWF0dXJlXzIiKSwgc2VwID0gIiBYICIpICAgICAgICAgICAgICAgIyB1bml0ZSAyIGNvbHVtbnMgdG8geCBuYW1lIGZyb20gcGxvdA0KICB9ZWxzZSBkZiA8LSBkZiAlPiUgZHBseXI6OnJlbmFtZShjb3JfYmV0d2VlbiA9IFZhcmlhYmxlKSAgICAgICAgICAgICAgICAgICAgICAgIyBjaGFuZ2UgVmFyaWFibGUgdG8geCBuYW1lIGZyb20gcGxvdA0KICANCiAgZGYgJT4lDQogICAgZ2dwdWJyOjpnZ2RvdGNoYXJ0KHggPSAiY29yX2JldHdlZW4iLCB5ID0gImNvcnJlbGF0aW9uIiwNCiAgICAgICAgICAgICAgICAgICAgICAgY29sb3IgPSAicC5zaWduaWYiLCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgQ29sb3IgYnkgc2lnDQogICAgICAgICAgICAgICAgICAgICAgICMgICBwYWxldHRlID0gYygiIzAwQUZCQiIsICIjRTdCODAwIiwgIiNGQzRFMDciKSwgICAgICAgICAjIEN1c3RvbSBjb2xvciBwYWxldHRlDQogICAgICAgICAgICAgICAgICAgICAgIHNvcnRpbmcgPSAiZGVzY2VuZGluZyIsICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIFNvcnQgdmFsdWUgaW4gZGVzY2VuZGluZyBvcmRlcg0KICAgICAgICAgICAgICAgICAgICAgICBhZGQgPSAic2VnbWVudHMiLCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBBZGQgc2VnbWVudHMgZnJvbSB5ID0gMCB0byBkb3RzDQogICAgICAgICAgICAgICAgICAgICAgIGFkZC5wYXJhbXMgPSBsaXN0KGNvbG9yID0gImxpZ2h0Z3JheSIsIHNpemUgPSAyKSwgICAgICAgICAjIENoYW5nZSBzZWdtZW50IGNvbG9yIGFuZCBzaXplDQogICAgICAgICAgICAgICAgICAgICAgIGdyb3VwID0gInAuc2lnbmlmIiwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIE9yZGVyIGJ5IGdyb3Vwcw0KICAgICAgICAgICAgICAgICAgICAgICBkb3Quc2l6ZSA9IDgsICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBMYXJnZSBkb3Qgc2l6ZQ0KICAgICAgICAgICAgICAgICAgICAgICB4bGFiID0gIiIsDQogICAgICAgICAgICAgICAgICAgICAgIHJvdGF0ZSA9IFRSVUUsICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIFJvdGF0ZSB2ZXJ0aWNhbGx5DQogICAgICAgICAgICAgICAgICAgICAgIGxhYmVsID0gcm91bmQoLiRjb3JyZWxhdGlvbiwgMSksICAgICAgICAgICAgICAgICAgICAgICAgICAjIEFkZCBtcGcgdmFsdWVzIGFzIGRvdCBsYWJlbHMNCiAgICAgICAgICAgICAgICAgICAgICAgZm9udC5sYWJlbCA9IGxpc3QoY29sb3IgPSAid2hpdGUiLCBzaXplID0gOSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZqdXN0ID0gMC41KSwgICAgICAgICAgICAgICAgICAgICAgICAgICAjIEFkanVzdCBsYWJlbCBwYXJhbWV0ZXJzDQogICAgICAgICAgICAgICAgICAgICAgIGdndGhlbWUgPSBkdG9wbG90X3RoZW1lKSArICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIGdncGxvdDIgdGhlbWUNCiAgICBkb3RwbG90Y29yX3NjYWxlX2ZpbGwoKSArICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIEZpeCBjb2xvcnMgdG8gc2lnbmlmIGZhY3RvciBsZXZlbHMgZXZlbiBpZiBtaXNzaW5nDQogICAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgbGluZXR5cGUgPSAyLCBjb2xvciA9ICJsaWdodGdyYXkiKQ0KfQ0KDQojfn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fg0KIyMgRnVuY3Rpb24gZm9yIHQgdGVzdCBhbmQgYm94cGxvdA0KI35+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn4NCmZ1bmNfdF9ib3ggPC0gZnVuY3Rpb24oZGYsIElELCBDb25kaXRpZSwgcHJlX3ZhciwgcG9zdF92YXIpew0KICBkZl9tb2RpZiA8LQ0KICAgIGRmICU+JQ0KICAgIHNlbGVjdChJRCwgQ29uZGl0aWUsIHByZV92YXIsIHBvc3RfdmFyKSAlPiUgDQogICAgdGlkeXI6OmRyb3BfbmEoKSAlPiUNCiAgICBnYXRoZXIocHJlX3ZhciwgcG9zdF92YXIsIGtleSA9ICJQcmVQb3N0IiwgdmFsdWUgPSAidmFsdWUiKSAlPiUgDQogICAgbXV0YXRlX2F0KHZhcnMoYygxLCAyKSksIGZ1bnMoYXMuZmFjdG9yKSkgJT4lIA0KICAgIG11dGF0ZShQcmVQb3N0ID0gZmFjdG9yKFByZVBvc3QsIGxldmVscyA9IGMocHJlX3ZhciwgcG9zdF92YXIpKSkgDQogIA0KICBzdGF0X2NvbXAgPC0NCiAgICBkZl9tb2RpZiAlPiUgDQogICAgZ3JvdXBfYnkoQ29uZGl0aWUpICU+JSANCiAgICBkbyh0aWR5KHQudGVzdCguJHZhbHVlIH4gLiRQcmVQb3N0LA0KICAgICAgICAgICAgICAgICAgIHBhaXJlZCA9IFRSVUUsDQogICAgICAgICAgICAgICAgICAgZGF0YT0uKSkpDQogIA0KICBwbG90IDwtIA0KICAgIGdncHVicjo6Z2dwYWlyZWQoZGZfbW9kaWYsIHggPSAiUHJlUG9zdCIsIHkgPSAidmFsdWUiLCBpZCA9IElELCANCiAgICAgICAgICAgICAgICAgICAgIGNvbG9yID0gIlByZVBvc3QiLCBsaW5lLmNvbG9yID0gImdyYXkiLCBsaW5lLnNpemUgPSAwLjQsDQogICAgICAgICAgICAgICAgICAgICBwYWxldHRlID0gYygiIzAwQUZCQiIsICIjRkM0RTA3IiksIGxlZ2VuZCA9ICJub25lIikgKw0KICAgIGZhY2V0X3dyYXAofkNvbmRpdGllKSArDQogICAgc3RhdF9zdW1tYXJ5KGZ1bi5kYXRhID0gbWVhbl9zZSwgIGNvbG91ciA9ICJkYXJrcmVkIikgKw0KICAgIGdncHVicjo6c3RhdF9jb21wYXJlX21lYW5zKG1ldGhvZCA9ICJ0LnRlc3QiLCBwYWlyZWQgPSBUUlVFLCBsYWJlbC54ID0gYXMubnVtZXJpYyhkZl9tb2RpZiRQcmVQb3N0KSAqIDAuOTAsIGxhYmVsLnkgPSBtYXgoZGZfbW9kaWYkdmFsdWUpICogMS4xNSkgKyANCiAgICBnZ3B1YnI6OnN0YXRfY29tcGFyZV9tZWFucyhtZXRob2QgPSAidC50ZXN0IiwgcGFpcmVkID0gVFJVRSwgbGFiZWwgPSAicC5zaWduaWYiLCBjb21wYXJpc29ucyA9IGxpc3QoYyhwcmVfdmFyLCBwb3N0X3ZhcikpKQ0KICANCiAgY2F0KHBhc3RlMCgiIyMjIyAiLCBwcmVfdmFyLCAiICIsIHBvc3RfdmFyLCAiXG4iLCAiXG4iKSkNCiAgcHJpbnQoc3RhdF9jb21wKQ0KICBwcmludChwbG90KQ0KfQ0KI35+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn5+fn4NCmBgYA0KDQoNCiMgQW5hbHlzZXMNCg0KIyMgUHJlLVBvc3QgZGlmZmVyZW5jZXMgb24gT3h5dG9jaW4gYW5kIENvcnRpc29sDQoNCmBgYHtyIHRfb3h5X2NvcnQsIGZpZy53aWR0aD0xMCwgZmlnLmhlaWdodD0xMSwgcmVzdWx0cz0nYXNpcyd9DQpmdW5jX3RfYm94KERhdGEsICJJRCIsICJDb25kaXRpZSIsICJDb3J0aXpvbF9wcmUiLCAiQ29ydGl6b2xfcG9zdCIpICANCmZ1bmNfdF9ib3goRGF0YSwgIklEIiwgIkNvbmRpdGllIiwgIk94aXRvY2luYV9wcmUiLCAiT3hpdG9jaW5hX3Bvc3QiKSANCmBgYA0KDQoNCiMjIENvcnJlbGF0aW9ucyBiZXR3ZWVuIERpZmZlcmVuY2UgU2NvcmVzDQoNCmBgYHtyIGNvcl9kaWZmLCBmaWcud2lkdGg9MTAsIGZpZy5oZWlnaHQ9MTJ9DQojIyBBdXRvbWF0aWMgUG9zdC1QcmUgRGlmZmVyZW5jZSBTY29yZXMNCnZhcl9wcmVwb3N0IDwtIGNvbG5hbWVzKERhdGFfbWVyZ2VkWywgZ3JlcGwoIi4qcHJlfC4qcG9zdCIsIGNvbG5hbWVzKERhdGFfbWVyZ2VkKSldKSAgICAjIGZpbmQgdmFyaWFibGVzIHRoYXQgaGF2ZSAicHJlIiBhbmQgInBvc3QiDQp2YXJfcHJlcG9zdCA8LSAgZ3N1YigiX3ByZSIsICIiLCB2YXJfcHJlcG9zdCkgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBkZWxldGUgcHJlIGFuZCBwb3N0IGZyb20gY29sbmFtZQ0KdmFyX3ByZXBvc3QgPC0gIGdzdWIoIl9wb3N0IiwgIiIsIHZhcl9wcmVwb3N0KQ0KdmFyX3ByZXBvc3QgPC0gdW5pcXVlKHZhcl9wcmVwb3N0KSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMga2VlcCB1bmlxdWUgLS0gYWxsIGdvb2QgICAgICAgICANCm5ld19kaWZmdmFyX25hbWUgPC0gdmVjdG9yKG1vZGU9ImNoYXJhY3RlciIsIGxlbmd0aCA9IGxlbmd0aCh2YXJfcHJlcG9zdCkpICAgICAgICAgICAgICAjIGluaXRpYWxpemUgdmVjdG9yIGZvciBmb3IoKQ0KDQpmb3IoaSBpbiBzZXFfYWxvbmcodmFyX3ByZXBvc3QpKXsNCiAgICB2YXJfbmFtZV9wcmUgPC0gcGFzdGUwKHZhcl9wcmVwb3N0W2ldLCAiX3ByZSIpDQogICAgdmFyX25hbWVfcG9zdCA8LSBwYXN0ZTAodmFyX3ByZXBvc3RbaV0sICJfcG9zdCIpDQogICAgbmV3X2RpZmZ2YXJfbmFtZVtpXSA8LSBwYXN0ZTAodmFyX3ByZXBvc3RbaV0sICJfRGlmZiIpDQogICAgRGF0YV9tZXJnZWRbLCBuZXdfZGlmZnZhcl9uYW1lW2ldXSA8LSBEYXRhX21lcmdlZFssIHZhcl9uYW1lX3Bvc3RdIC0gRGF0YV9tZXJnZWRbLCB2YXJfbmFtZV9wcmVdDQp9DQoNCiMgU2VsZWN0IHZhcmlhYmxlcw0KdmFyc19jb3IgPC0gbmV3X2RpZmZ2YXJfbmFtZVshbmV3X2RpZmZ2YXJfbmFtZSAlaW4lIGMoIkFsbEJ1dHRvbnNfRGlmZiIsICJBbGxCdXR0b25zNDVfRGlmZiIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIk9uZXNfRGlmZiIsICJPbmVzNDVfRGlmZiIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiVGhyZWVzX0RpZmYiLCAiVGhyZWVzNDVfRGlmZiIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIlR3b3NfRGlmZiIsICJUd29zNDVfRGlmZiIpXSAgICAgICAjIGV4Y2x1ZGUgdGhlc2UNCnZhcnNfY29yIDwtIGModmFyc19jb3IsDQogICAgICAgICAgICAgIGMoIlZhc19yZWxfZ2xvYmFsIiwgIlZhc19yZWxfYXJvdXNhbCIsICJDUlFfMSIsICJDUlFfMiIsICJDUlFfMyIsICJDUlFfNCIsICJDUlFfNSIsICJDUlFfNiIpKQ0KDQojIyBDb3JyZWxhdGlvbnMNCkdldF9Ub3BfUmVsYXRpb25zaGlwcyhEYXRhX21lcmdlZFssIHZhcnNfY29yXSkNCkdldF9Ub3BfUmVsYXRpb25zaGlwcyhEYXRhX21lcmdlZFssIHZhcnNfY29yXSkgJT4lIGZ1bmNfZG90cGxvdF9jb3IoKQ0KYGBgDQoNCg0KPGJyPg0KDQojIENvbmRpdGlvbmluZyBvbiBWQVMgZ2xvYmFsDQoNCiMjIFByZS1Qb3N0IGRpZmZlcmVuY2VzIG9uIE94eXRvY2luIGFuZCBDb3J0aXNvbA0KDQpgYGB7ciBjb25kX3Rfb3h5X2NvcnQsIGZpZy53aWR0aD0xMCwgZmlnLmhlaWdodD0xMSwgcmVzdWx0cz0nYXNpcyd9DQpEYXRhX21lcmdlZCAlPiUNCiAgZmlsdGVyKFZhc19yZWxfZ2xvYmFsID49IDcpICU+JQ0KICBmdW5jX3RfYm94KC4sICJJRCIsICJDb25kaXRpZSIsICJDb3J0aXpvbF9wcmUiLCAiQ29ydGl6b2xfcG9zdCIpIA0KDQpEYXRhX21lcmdlZCAlPiUNCiAgZmlsdGVyKFZhc19yZWxfZ2xvYmFsID49IDcpICU+JQ0KICBmdW5jX3RfYm94KC4sICJJRCIsICJDb25kaXRpZSIsICJPeGl0b2NpbmFfcHJlIiwgIk94aXRvY2luYV9wb3N0IikgDQpgYGANCg0KDQo8YnI+DQoNCiMgTW9kZXJhdGlvbiBBbmFseXNpcw0KDQpgYGB7ciBtb2Rfb3h5X2NvcnQsIGZpZy53aWR0aD03LCBmaWcuaGVpZ2h0PTYsIHJlc3VsdHM9J2FzaXMnLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQ0KIyMgRGVmaW5lIEZ1bmN0aW9uDQpmdW5jX21vZGVyYXRpb24gPC0gZnVuY3Rpb24oRGF0YSwgZGVwLCBtb2QsIHByZWQpew0KICBtb2RlcmF0aW9uIDwtIA0KICAgIERhdGEgJT4lDQogICAgICBtZWRtb2Q6Om1vZCguLCBkZXAgPSBkZXAsIG1vZCA9IG1vZCwgcHJlZCA9IHByZWQsDQogICAgICAgICAgICAgICAgICBjaSA9IFRSVUUsIGVzdE1ldGhvZCA9ICdzdGFuZGFyZCcsIHRlc3QgPSBUUlVFLCBzaW1wbGVTbG9wZUVzdCA9IEZBTFNFLCBzaW1wbGVTbG9wZVBsb3QgPSBUUlVFKQ0KICANCiAgY2F0KHBhc3RlKCI8Yj4gTW9kZXJhdGlvbjogIiwgIkRlcCA9ICIsIGRlcCwgIlByZWQgPSAiLCBwcmVkLCAiTW9kID0gIiwgbW9kLCAiPC9iPiIpKQ0KICBtb2RlcmF0aW9uJG1vZCAlPiUgDQogICAga25pdHI6OmthYmxlKGNhcHRpb24gPSAiTW9kZXJhdGlvbiIsIGRpZ2l0cyA9IDMpICU+JQ0KICAgIHByaW50KCkNCiAgbW9kZXJhdGlvbiRzaW1wbGVTbG9wZSRwbG90ICU+JQ0KICAgIHByaW50KCkNCn0NCg0KIyMgQXBwbHkgRnVuY3Rpb24NCmZ1bmNfbW9kZXJhdGlvbihEYXRhID0gRGF0YV9tZXJnZWQsIGRlcCA9ICJPeGl0b2NpbmFfcG9zdCIsIG1vZCA9ICJWYXNfcmVsX2Fyb3VzYWwiLCBwcmVkID0gIk94aXRvY2luYV9wcmUiKQ0KZnVuY19tb2RlcmF0aW9uKERhdGEgPSBEYXRhX21lcmdlZCwgZGVwID0gIkNvcnRpem9sX3Bvc3QiLCBtb2QgPSAiVmFzX3JlbF9hcm91c2FsIiwgcHJlZCA9ICJDb3J0aXpvbF9wcmUiKQ0KDQojIGJsYSA8LQ0KIyAgIERhdGFfbWVyZ2VkICU+JQ0KIyAgIG11dGF0ZShDb25kaXRpZSA9IGFzLm51bWVyaWMoYXMuZmFjdG9yKENvbmRpdGllKSkpDQojIA0KIyBwc3ljaDo6bWVkaWF0ZShkYXRhID0gYmxhLCBPeGl0b2NpbmFfcG9zdCB+IE94aXRvY2luYV9wcmUgKyBDb25kaXRpZSArIFZhc19yZWxfZ2xvYmFsKSAgICMgbW9kZXJhdGlvbiB3aXRoIGNvdmFyaWF0ZSBhbmQgZGlhZ3JhbQ0KYGBgDQoNCg0KIyBNZWRpYXRpb24gQW5hbHlzaXMNCg0KYGBge3IgbWVkX294eV9jb3J0LCBmaWcud2lkdGg9NywgZmlnLmhlaWdodD02LCByZXN1bHRzPSdhc2lzJywgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRSwgICBpbmNsdWRlPUZBTFNFfQ0KIyMgRGVmaW5lIEZ1bmN0aW9uDQpmdW5jX21lZGlhdGlvbiA8LSBmdW5jdGlvbihEYXRhLCBkZXAsIG1lZCwgcHJlZCl7DQogIG1lZGlhdGlvbiA8LSANCiAgICBEYXRhICU+JQ0KICAgICAgbWVkbW9kOjptZWQoLiwgZGVwID0gZGVwLCBtZWQgPSBtZWQsIHByZWQgPSBwcmVkLA0KICAgICAgICAgICAgICAgICAgY2kgPSBUUlVFLCBsYWJlbCA9IFRSVUUsIHBhdGhzID0gVFJVRSwgcG0gPSBUUlVFLCBlc3RQbG90ID0gVFJVRSkNCiAgDQogIGNhdChwYXN0ZSgiPGI+IE1lZGlhdGlvbjogIiwgIkRlcCA9ICIsIGRlcCwgIlByZWQgPSAiLCBwcmVkLCAiTWVkID0gIiwgbWVkLCAiPC9iPiIpKQ0KICBtZWRpYXRpb24kbWVkICU+JSANCiAgICBrbml0cjo6a2FibGUoY2FwdGlvbiA9ICJNb2RlcmF0aW9uIiwgZGlnaXRzID0gMykgJT4lDQogICAgcHJpbnQoKQ0KICBtZWRpYXRpb24kZXN0UGxvdCAlPiUNCiAgICBwcmludCgpDQp9DQoNCiMjIEFwcGx5IEZ1bmN0aW9uDQpmdW5jX21lZGlhdGlvbihEYXRhID0gRGF0YV9tZXJnZWQsIGRlcCA9ICJPeGl0b2NpbmFfcG9zdCIsIG1lZCA9ICJDUlFfNCIsIHByZWQgPSAiT3hpdG9jaW5hX3ByZSIpDQpmdW5jX21lZGlhdGlvbihEYXRhID0gRGF0YV9tZXJnZWQsIGRlcCA9ICJDb3J0aXpvbF9wb3N0IiwgbWVkID0gIkNSUV80IiwgcHJlZCA9ICJDb3J0aXpvbF9wcmUiKQ0KYGBgDQoNCg0KDQo8YnI+DQo8YnI+DQoNCjwhLS0gU2Vzc2lvbiBJbmZvIGFuZCBMaWNlbnNlIC0tPg0KDQo8YnI+DQoNCiMgU2Vzc2lvbiBJbmZvDQpgYGB7ciBzZXNzaW9uX2luZm8sIGVjaG8gPSBGQUxTRSwgcmVzdWx0cyA9ICdtYXJrdXAnfQ0Kc2Vzc2lvbkluZm8oKSAgICANCmBgYA0KDQo8IS0tIEZvb3RlciAtLT4NCiZuYnNwOw0KPGhyIC8+DQo8cCBzdHlsZT0idGV4dC1hbGlnbjogY2VudGVyOyI+QSB3b3JrIGJ5IDxhIGhyZWY9Imh0dHBzOi8vZ2l0aHViLmNvbS9DbGF1ZGl1UGFwYXN0ZXJpLyI+Q2xhdWRpdSBQYXBhc3Rlcmk8L2E+PC9wPg0KPHAgc3R5bGU9InRleHQtYWxpZ246IGNlbnRlcjsiPjxzcGFuIHN0eWxlPSJjb2xvcjogIzgwODA4MDsiPjxlbT5jbGF1ZGl1LnBhcGFzdGVyaUBnbWFpbC5jb208L2VtPjwvc3Bhbj48L3A+DQombmJzcDsNCg==