Table of Contents

1) Program Overview

The purpose of this program is to append new temperature files to existing temperature time series files. The program is designed to handle a variety of different file naming conventions, file types, column/row formats, data types, and column names.

1.1) R Project File Contents

Inputs Folder where the program will create and store copies of the new data files and combined data files that you select with the dialogue box. Working with copies of the new and combined data safeguards against corrupting or loosing older versions of those data archived in the Data Base.

Module Compendiums — Folder where compilations of other scripts that contain functions used in the MAIN.R script are housed. The compendium used by the MAIN.R script is listed at the start of the script. Previous compendiums were used in older versions of MAIN.R.

Old Scripts — Folder that’s just an archive of old versions of the MAIN.R and module scripts. These scripts aren’t used for anything.

Operational Files — Folder containing any files that are used in by the MAIN.R script or any of the modules. These include (but not limited to) the .xlsx files containing all the metadata about each logger in the PGR network.

Outputs — A folder that can (optionally) be used by the user to output the new combined files so they can be checked for accuracy before putting them into the Data Base.

MAIN.R — The main R script that appends the new files to the old combined data files.

Figure 1.
Figure 1.


2) Guide Book

This section presents a step-by-step guide for users to use the program.

2.1) What file formats can be used?

Table 1 below shows the file logger/file type combinations that are supported by the program. The special pre-formatting requirements are any formatting that the user should double check before running the script. However, they usually are already set this way by default. This table may not cover all of the possible formatings that come off of the stations. If there are any settings that are missing let Casey know so he can update the script.

Table 1. Types of files, by logger type, that can be read by the program.
Logger Type File Type Special Pre-Formatting Requirements
HOBO
  • .xlsx
  • There must be a single DateTime column, not divided into separate Date and Time columns.
HOBO bluetooth
  • .csv
Pendant
  • .xlsx

  • .csv

  • There must be a single DateTime column, not divided into separate Date and Time columns.
LOGR
  • .xlsx

  • .csv

  • Of the 4 files that the LOGR outputs, you should use the one that contains “temp” in the name.
  • The columns containing ground temperature data need to have “CH” somewhere in their column names (this seems to be a default).
  • The datetimes should be text or numeric unix values.
    • If the datetimes values are text, they should be in one of the following formats:

      • mdY HM

      • Ymd HMS

Met Station
  • .dat


2.2) Where should the new and combined files be before running the script?

The new data and combined data files should already be inside their appropriate Site folder in the Data Base (Fig 2). The program will make copies of the select new and combined data and place them in the Inputs folder of the R Project folder (Fig. 1).

Figure X.X.
Figure X.X.


2.3) How should my new data and combined data files be named before running the program?

The new files can keep the original names that the logger gave them. Table 2 summarizes the possible formats for the combined file names:

Table 2. Combined file name formats.
Data Type Format Example
Air aircombined_{borehole}_{monthYear} aircombined_BH11a_Sept2015
Internal Surface surfaceIntcombined_{borehole}_{monthYear} surfaceIntcombined_BH24_Apr2019
External Surface surfaceExtcombined_{borehole}_{monthYear} surfaceExtcombined_BH05_Jun2021
Ground groundcombined_{borehole}_{monthYear} groundcombined_BH13_Sept2020

The month and year at the end of the combined file names are only to help us as users know when the last time that file was updated, and is not required for the functioning of the program.

2.4) What if the site is brand new and does not have any combined data yet?

The current version of the program does not create new combined files. You will need to make new empty combined files from scratch and also fill them in with the data the first time. The next version will either prompt the user whether to create a new combined file for a new site (see Section 4) Upcoming Updates for more details). For now, following the instructions below to prepare the files for a new site so they can be used with the script in the future:

Preparing a New Site for Program Use

  1. Create new .xlsx combined files for the site.

    Check what kinds of data you have (air, internal surface, external surface, ground). For each data type, make a new .xlsx file with the appropriate combined data name (see table in Section 2.3 table). The column names of each of these new combined files are shown in Fig 3. You don’t need all of them, only the ones relevant to the data coming off the logger:

    Figure 3.
    Figure 3.


    1. Update the appropriate Logger_Information.xlsx file

      The Logger_Information.xlsx files contain all of the meta data about each of the loggers for a particular region. They are found in the R project subdirectory (Fig 4):

      Data Base > R > Temperature Combining Program > Operational Files

      Figure 4.
      Figure 4.


      Make a new row in the appropriate .xlsx file for each new logger. Table 3 below summarizes what to enter in to each column in a logger information sheet:

      Table 3. Information to fill into a logger information sheet.
      Column To Fill In
      Raw_File_Name
      • The name of the file that comes off of the logger.

      • WITHOUT the file extension (e.g. .xlsx)

      Logger
      • The type of logger. One of:

        • HOBO

        • HOBObluetooth

        • LOGR

        • pendant

        • Met

      • (Met basically implies CR1000x)

      Borehole
      • The simple borehole designator
      Type
      • Type of data output by the logger

      • One of:

        • air

        • surfaceInt

        • surfaceExt

        • ground

      • If there are multiple types, separate the types by a comma and NO spaces

      Combined_File_Name
      • The name of the excel file that contains the compiled time series of data for the logger

      • WITHOUT the file extension (e.g. .xlsx)

      • See Table 2 for combined file name formats

      Tandem_Loggers
      • Tandem loggers are loggers that are part of the same borehole or station that are recording the same data type (e.g. two HOBO loggers recording ground temperature at different sets of depth)

      • If a borehole/station has tandem loggers, ENTER THE DATA TYPE (air, surfaceInt, surfaceExt, or ground)

      • ELSE, LEAVE BLANK

      Column_Names
      • A comma-delineated list of the names of the columns.

      • Column names are separated by a comma and NO SPACES

      • See Figure 3 for column name formats

3) MAIN.R Script

3.1) Package Installation and Importing

Imports external packages of code used by the program.

```r
```r
```r
library(pacman)
pacman::p_load(dplyr,writexl,clock,ggplot2,plotly,reshape2,reshape,stats,tidyverse,ggsci,
               openxlsx,tidyselect,stringr,readxl,lubridate,DT,tibble,haven,DescTools,
               imputeTS,berryFunctions,clock,gdata,extrafont,ggpmisc,ggsci,ggh4x,viridis,rlist,
               zoom,stringi,ggrepel,fabR,devtools,tcltk)

<!-- rnb-source-end -->

<!-- rnb-chunk-end -->


<!-- rnb-text-begin -->


## 3.2) Module Importing

Runs the scripts within the separate module files in the **Module Compendiums** folder.


<!-- rnb-text-end -->


<!-- rnb-chunk-begin -->


<!-- rnb-source-begin eyJkYXRhIjoiYGBgclxuYGBgclxuYGBgclxuIyBHZXRzIG1hdGNoZXMgYmV0d2VlbiBuZXdfZmlsZW5hbWVzX3RydW5jIGFuZCB0aGUgUmF3IEZpbGUgTmFtZXMgaW4gdGhlIExvZ2dlcl9JbmZvcm1hdGlvbiBmaWxlLlxubmV3X2ZpbGVfaW5mbyA8LSBuZXdfZmlsZV9pbmZvIHw+IFxuICBtdXRhdGUoZmlsZW5hbWVNYXRjaGVzID0gaWZlbHNlKG5ld19maWxlbmFtZXNfdHJ1bmMgJWluJSBsb2dnZXJJbmZvJFJhd19GaWxlX05hbWUsIFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5ld19maWxlbmFtZXNfdHJ1bmMsIFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE5BKSlcblxuIyBHZXQgbm9uLW1hdGNoZXNcbm5ld19maWxlX2luZm8gPC0gbmV3X2ZpbGVfaW5mbyB8PiBcbiAgbXV0YXRlKGZpbGVuYW1lTm9NYXRjaCA9IGlmZWxzZSghKG5ld19maWxlbmFtZXNfdHJ1bmMgJWluJSBsb2dnZXJJbmZvJFJhd19GaWxlX05hbWUpLFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5ld19maWxlbmFtZXNfdHJ1bmMsXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgTkEpKVxuXG5cbiMgSWYgbm9uLW1hdGNoZXMgKFxcbmV3X2ZpbGVuYW1lc190cnVuY1xcIHRoYXQgZG9uJ3QgaGF2ZSBhIGNvcnJlc3BvbmRpbmcgXFxSYXdfRmlsZV9OYW1lXFwgaW4gXFxsb2dnZXJJbmZvXFwpIFxuIyB3ZXJlIGZvdW5kLCBub3RpZnkgdXNlciBhbmQgYXNrIHdoZXRoZXIgdG8gY29udGludWUgb3IgcXVpdC5cbiMgSWYgdGhlIHVzZXIgc2VsZWN0cyBcXHlcXCwgbm9uLW1hdGNoZXMgYXJlIHJlbW92ZWQgZnJvbSBib3RoIG5ld19maWxlbmFtZXMgYW5kIG5ld19maWxlbmFtZXNfdHJ1bmNcbmlmKGxlbmd0aChuZXdfZmlsZV9pbmZvJGZpbGVuYW1lTm9NYXRjaCkgPiAwKSB7XG4gIFxuICAjIFRlbGwgdGhlIHVzZXIgdGhlcmUgYXJlIG5vIG1hdGNoZXMgYmV0d2VlbiB0aGUgbmV3IGRhdGEgZmlsZSBuYW1lcyBhbmQgdGhlIFJhd19GaWxlX05hbWUgY29sdW1uIGluIHRoZSBsb2dnZXIgaW5mb3JtYXRpb24gc2hlZXRcbiAgbWVzc2FnZShwYXN0ZShcXFRoZXJlIGFyZVxcLGxlbmd0aChuZXdfZmlsZV9pbmZvJGZpbGVuYW1lTm9NYXRjaCksXG4gICAgICAgICAgICAgICAgXFxuZXcgZGF0YSBmaWxlcyB3aG8ncyBuYW1lcyBkaWQgbm90IG1hdGNoIGFueSBvZiB0aG9zZSBsaXN0ZWQgaW4gdGhlICdSYXdfRmlsZV9OYW1lJyBjb2x1bW4gaW4gdGhlICdMb2dnZXJfSW5mb3JtYXRpb24ueGxzeCc6XFwpKVxuICBwcmludChkYXRhLmZyYW1lKE5vX01hdGNoZXMgPSBuZXdfZmlsZV9pbmZvJGZpbGVuYW1lTm9NYXRjaCkgfD4gZmlsdGVyKCFpcy5uYShOb19NYXRjaGVzKSkpXG4gIFxuICBcbiAgIyBBc2sgdGhlIHVzZXIgaWYgdGhleSB3b3VsZCBsaWtlIHRvIGNvbnRpbnVlIHdpdGggdGhlIHByb2dyYW0gZXZlbiB0aG91Z2ggdGhlcmUgd2VyZSBpbnN0YW5jZXMgb2Ygbm8gZmlsZSBuYW1lIG1hdGNoZXNcbiAgYW5zd2VyX25vbWF0Y2ggPC0gcmVhZGxpbmUoXFxXb3VsZCB5b3UgbGlrZSB0byBjb250aW51ZT9beS9uXVxcKVxuICBcbiAgXG4gICMgSWYgdGhlIHVzZXIncyBhbnN3ZXIgKGFuc3dlcl9ub21hdGNoKSBpcyBcXG5cXCwgc3RvcHMgdGhlIHByb2dyYW1cbiAgaWYoYW5zd2VyX25vbWF0Y2ggPT0gXFxuXFwpIHtcbiAgICBcbiAgICBcbiAgICBzdG9wKFxcUGxlYXNlIG1ha2UgYXBwcm9wcmlhdGUgZmlsZSBuYW1lIGNoYW5nZXMgdG8gdGhlIG5ldyBmaWxlcyBvciBhZGQgbmV3IHJvd3MgdG8gdGhlICdMb2dnZXJfSW5mb3JtYXRpb24ueGxzeCcgc2hlZXQgYmVmb3JlIHJldHJ5aW5nLlxcKVxuICAgIFxuICAgIFxuICAjIElmIHRoZSB1c2VyJ3MgYW5zd2VyIChhbnN3ZXJfbm9tYXRjaCkgaXMgXFx5XFwuLi5cbiAgfSBlbHNlIGlmIChhbnN3ZXJfbm9tYXRjaCA9PSBcXHlcXCkge1xuICAgIFxuICAgIFxuICAgIG1lc3NhZ2UoXFxcXG5cXG5Qcm9ncmFtIHdpbGwgYXR0ZW1wdCB0byByZW5hbWUgbmV3IGZpbGVzIHRvIHNvbWV0aGluZyB0aGF0IGNhbiBiZSB1c2VkLlxcbklmIGl0IGNhbm5vdFxuYGBgXG5gYGAifQ== -->

```r
```r
```r
# Gets matches between new_filenames_trunc and the Raw File Names in the Logger_Information file.
new_file_info <- new_file_info |> 
  mutate(filenameMatches = ifelse(new_filenames_trunc %in% loggerInfo$Raw_File_Name, 
                                  new_filenames_trunc, 
                                  NA))

# Get non-matches
new_file_info <- new_file_info |> 
  mutate(filenameNoMatch = ifelse(!(new_filenames_trunc %in% loggerInfo$Raw_File_Name),
                                  new_filenames_trunc,
                                  NA))


# If non-matches (\new_filenames_trunc\ that don't have a corresponding \Raw_File_Name\ in \loggerInfo\) 
# were found, notify user and ask whether to continue or quit.
# If the user selects \y\, non-matches are removed from both new_filenames and new_filenames_trunc
if(length(new_file_info$filenameNoMatch) > 0) {
  
  # Tell the user there are no matches between the new data file names and the Raw_File_Name column in the logger information sheet
  message(paste(\There are\,length(new_file_info$filenameNoMatch),
                \new data files who's names did not match any of those listed in the 'Raw_File_Name' column in the 'Logger_Information.xlsx':\))
  print(data.frame(No_Matches = new_file_info$filenameNoMatch) |> filter(!is.na(No_Matches)))
  
  
  # Ask the user if they would like to continue with the program even though there were instances of no file name matches
  answer_nomatch <- readline(\Would you like to continue?[y/n]\)
  
  
  # If the user's answer (answer_nomatch) is \n\, stops the program
  if(answer_nomatch == \n\) {
    
    
    stop(\Please make appropriate file name changes to the new files or add new rows to the 'Logger_Information.xlsx' sheet before retrying.\)
    
    
  # If the user's answer (answer_nomatch) is \y\...
  } else if (answer_nomatch == \y\) {
    
    
    message(\\n\nProgram will attempt to rename new files to something that can be used.\nIf it cannot

<!-- rnb-source-end -->

<!-- rnb-chunk-end -->


<!-- rnb-text-begin -->


## 3.3) Create Session Info File

Creates an table containing meta data about the current R session. This includes the directories of the last combined and new data files that were selected by the user, so when they run the script again, the dialogue windows open to those directories rather than a remote, high level folder somewhere on the computer (basically saves the user a bit of time).


<!-- rnb-text-end -->


<!-- rnb-chunk-begin -->


<!-- rnb-source-begin eyJkYXRhIjoiYGBgclxuYGBgclxuYGBgclxuIyBDcmVhdGUgYSBzdWJzZXQgb2YgdGhlIGxvZ2dlckluZm8gdGliYmxlIHRoYXQgb25seSBjb250YWlucyByb3dzIGNvcnJlc3BvbmRpbmcgdG8gZmlsZSBuYW1lIG1hdGNoZXNcbiMgYW5kIGNyZWF0ZSBhIG5ldyBcXEltcG9ydF9QYXRod2F5c1xcIGNvbHVtbi5cbmluZm9ybWF0aW9uIDwtIGxvZ2dlckluZm8gfD4gXG4gIGZpbHRlcihSYXdfRmlsZV9OYW1lICVpbiUgbmV3X2ZpbGVfaW5mbyRuZXdfZmlsZW5hbWVzX3RydW5jKSB8PiBcbiAgbXV0YXRlKEltcG9ydF9QYXRod2F5cyA9IGltcG9ydF9wYXRod2F5cywgLmFmdGVyID0gUmF3X0ZpbGVfTmFtZSlcblxuXG4jSW1wb3J0IGFsbCBjb21iaW5lZCBkYXRhIGFzIHRpYmJsZXMgYW5kIG9yZ2FuaXplIHRoZW0gaW50byBhIGxpc3Rcbm5ld19kYXRhIDwtIGxhcHBseShpbmZvcm1hdGlvbiRJbXBvcnRfUGF0aHdheXMsIGZ1bmN0aW9uKGZpbGVwYXRoKSBSRUFEX05FV19URU1QRVJfRklMRShmaWxlcGF0aCkgKVxubmFtZXMobmV3X2RhdGEpIDwtIG5ld19maWxlX2luZm8kbmV3X2ZpbGVuYW1lc190cnVuY1xuXG5cbiNJbXBvcnQgYWxsIGNvbWJpbmVkIGRhdGEgYXMgdGliYmxlcyBhbmQgb3JnYW5pemUgdGhlbSBpbnRvIGEgbGlzdFxuY29tYl9kYXRhIDwtIGxhcHBseShjb21iX2ZpbGVwYXRocywgZnVuY3Rpb24oeCkgUkVBRF9URU1QRVJfRklMRSh4KSApXG5uYW1lcyhjb21iX2RhdGEpIDwtIGNvbWJfZmlsZW5hbWVzXG5cblxuXG5tZXNzYWdlKFxcXFxuXFxuUHJvZ3Jlc3M6ICA3KSBJbXBvcnRhdGlvbiBjb21wbGV0ZS5cXClcblxuXG5cblxuIyBSZWFycmFuZ2UgdGhlIHJvd3MgaW4gXFxpbmZvcm1hdGlvblxcIHRvIG1hdGNoIHRoZSBvcmRlciBvZiBkYXRhIGZpbGVzIGluIFxcbmV3X2RhdGFcXCBcbiMgKGJlY2F1c2UgZm9yIHNvbWUgcmVhc29uIGl0IGRpZG4ndCBkbyB0aGF0IGF1dG9tYXRpY2FsbHkgZHVyaW5nIGltcG9ydGF0aW9uKVxueSA9IE5VTExcbmlmKGxlbmd0aChuZXdfZGF0YSkgPiAxKSB7XG4gIGZvcihpIGluIG5ld19maWxlX2luZm8kbmV3X2ZpbGVuYW1lc190cnVuYykge1xuICAgIHggPC0gaW5mb3JtYXRpb24gfD4gZmlsdGVyKFJhd19GaWxlX05hbWUgPT0gaSlcbiAgICB5IDwtIGJpbmRfcm93cyh5LHgpXG4gIH1cbiAgaW5mb3JtYXRpb24gPC0geVxuICBybSh4LHkpXG59XG5gYGBcbmBgYFxuYGBgIn0= -->

```r
```r
```r
# Create a subset of the loggerInfo tibble that only contains rows corresponding to file name matches
# and create a new \Import_Pathways\ column.
information <- loggerInfo |> 
  filter(Raw_File_Name %in% new_file_info$new_filenames_trunc) |> 
  mutate(Import_Pathways = import_pathways, .after = Raw_File_Name)


#Import all combined data as tibbles and organize them into a list
new_data <- lapply(information$Import_Pathways, function(filepath) READ_NEW_TEMPER_FILE(filepath) )
names(new_data) <- new_file_info$new_filenames_trunc


#Import all combined data as tibbles and organize them into a list
comb_data <- lapply(comb_filepaths, function(x) READ_TEMPER_FILE(x) )
names(comb_data) <- comb_filenames



message(\\n\nProgress:  7) Importation complete.\)




# Rearrange the rows in \information\ to match the order of data files in \new_data\ 
# (because for some reason it didn't do that automatically during importation)
y = NULL
if(length(new_data) > 1) {
  for(i in new_file_info$new_filenames_trunc) {
    x <- information |> filter(Raw_File_Name == i)
    y <- bind_rows(y,x)
  }
  information <- y
  rm(x,y)
}

3.4) Import Logger Information .xlsx

The user selects the region that the new logger data has come from. This imports the relevant logger information excel file that contains meta data about the loggers. The rest of the program uses this meta data to direct how new data is reformatted and how new and old data is combined.

```r
# Gets matches between new_filenames_trunc and the Raw File Names in the Logger_Information file.
new_file_info <- new_file_info |> 
  mutate(filenameMatches = ifelse(new_filenames_trunc %in% loggerInfo$Raw_File_Name, 
                                  new_filenames_trunc, 
                                  NA))

# Get non-matches
new_file_info <- new_file_info |> 
  mutate(filenameNoMatch = ifelse(!(new_filenames_trunc %in% loggerInfo$Raw_File_Name),
                                  new_filenames_trunc,
                                  NA))


# If non-matches (\new_filenames_trunc\ that don't have a corresponding \Raw_File_Name\ in \loggerInfo\) 
# were found, notify user and ask whether to continue or quit.
# If the user selects \y\, non-matches are removed from both new_filenames and new_filenames_trunc
if(length(new_file_info$filenameNoMatch) > 0) {
  
  # Tell the user there are no matches between the new data file names and the Raw_File_Name column in the logger information sheet
  message(paste(\There are\,length(new_file_info$filenameNoMatch),
                \new data files who's names did not match any of those listed in the 'Raw_File_Name' column in the 'Logger_Information.xlsx':\))
  print(data.frame(No_Matches = new_file_info$filenameNoMatch) |> filter(!is.na(No_Matches)))
  
  
  # Ask the user if they would like to continue with the program even though there were instances of no file name matches
  answer_nomatch <- readline(\Would you like to continue?[y/n]\)
  
  
  # If the user's answer (answer_nomatch) is \n\, stops the program
  if(answer_nomatch == \n\) {
    
    
    stop(\Please make appropriate file name changes to the new files or add new rows to the 'Logger_Information.xlsx' sheet before retrying.\)
    
    
  # If the user's answer (answer_nomatch) is \y\...
  } else if (answer_nomatch == \y\) {
    
    
    message(\\n\nProgram will attempt to rename new files to something that can be used.\nIf it cannot

3.5) Choose New Data Files

The section pops up file explorer windows where the user navigates to and selects the new data and compiled data files they want to combine. The program creates copies of these data files and places them in the Inputs/New Data and Inputs/Combined Data subdirectories.

Note The dialogue window might pop up behind all the other open windows on your computer!

# Open a windows explorer window to choose the new data files
new_filepaths <- tk_choose.files(last(sesh_info$lastNewDirectory), caption = "Select New Files")


# If there aren't any character strings in "new_filepaths", stops the script and
# tells the user they need to select new data files.
if(length(new_filepaths) == 0) {
  message("No new data files were selected. Exiting...")
  break
}

# A tibble containing information about the file pathways, file names, extensions,
# roots, whether or not file name matches were found in the logger info sheet, and
# what name, if any, can replace those file names that don't have matches.
new_file_info <- tibble(
  
  new_filepaths = new_filepaths,
  
  new_filepath_roots = sapply(new_filepaths, function(x) {
    str_split(x,"/") |> 
      unlist() |> 
      head(-1) |> 
      paste0(collapse = "/")
  }) |> paste0("/"),
  
  new_filenames = new_filepaths |> basename(),
  
)

# Add a "new_filename_extensions" column to "new_file_info"
new_file_info <- new_file_info |> 
  mutate(
    new_filename_extensions = sapply(new_filenames, function(x) {
      str_split(x, "\\.") |> 
        unlist() |> 
        last() 
    }),
    .after = "new_filepath_roots"
  )


# Add a "new_filenames_trunc" column to "new_file_info" that is just the "new_filenames"
# column without the file extensions
new_file_info <- new_file_info |> 
  mutate(new_filenames_trunc = new_filenames |> 
           str_replace_all(pattern = ".xlsx|.csv", replacement = ""),
         .after = "new_filename_extensions"
         )
  


message("\n\nProgress:  5.1) Progress:  New data files chosen.")



## 5.2) SELECTING COMBINED DATA FILES----



# comb_filepaths <- tk_choose.files(paste0(getwd(),"/Outputs"), caption = "Select Combined Files")
comb_filepaths <- tk_choose.files(paste0(ifelse(is.na(last(sesh_info$lastCombDirectory)), getwd(), last(sesh_info$lastCombDirectory)),"/Outputs"), caption = "Select Combined Files")


# If there aren't any character strings in "comb_filepaths", stops the script and
# tells the user they need to select combined data files.
if(length(comb_filepaths) == 0) {
  message("No combined data files were selected. Exiting...")
  break
}


# Get just the name of the file
comb_filenames <- comb_filepaths |> basename()


# Removes the month and year at the end of the combined file names and renames
# the actual file names to these altered versions.
# (These dates are only for our benefit when we are manually looking through the data base,
# but actually make writing this script a bit more difficult.)
for(i in 1:length(comb_filenames)) {
  
  filename <- comb_filenames[i]
  filename_dateremoved <- gsub("_[A-Za-z]+\\d{4}(?=\\.xlsx)", "", filename, perl = TRUE)
  comb_filenames[i] <- filename_dateremoved
  
}
rm(filename,filename_dateremoved,i)



message("\n\nProgress:  5.2) Progress:  Combined data files chosen.")



## 5.3) SELECTING OUTPUT DIRECTORY----


# Open windows explorer window to choose the output file
chosen_output_directory <- tk_choose.dir(last(sesh_info$lastOutputDirectory), caption = "Select Output Directory")
if(length(chosen_output_directory) == 0) {
  message("No output directory was selected. Exiting...")
  break
}


# Update session info
sesh_info <- sesh_info |> add_row(lastNewDirectory = new_filepaths[1] |> 
                                    str_split("/") |> 
                                    unlist() |> 
                                    head(-1) |> 
                                    paste0(collapse = "/"),
                                  lastCombDirectory = comb_filepaths[1] |> 
                                    str_split("/") |> 
                                    unlist() |> 
                                    head(-1) |> 
                                    paste0(collapse = "/"),
                                  lastOutputDirectory = chosen_output_directory)




message("\n\nProgress:  5.3) Progress:  Output directory chosen.")

3.6) Discover New File Name Matches

This section confirms to the user whether or not there matches found between the new data files that were selected and corresponding rows of meta data in the imported Logger_Information.xlsx file. If a match wasn’t found, the user is prompted to answer whether or not to continue processing the files for which matches were found (new data files without matches won’t be used for anything). If a match wasn’t found, the program won’t be able to properly manipulate the data.

# Gets matches between new_filenames_trunc and the Raw File Names in the Logger_Information file.
new_file_info <- new_file_info |> 
  mutate(filenameMatches = ifelse(new_filenames_trunc %in% loggerInfo$Raw_File_Name, 
                                  new_filenames_trunc, 
                                  NA))

# Get non-matches
new_file_info <- new_file_info |> 
  mutate(filenameNoMatch = ifelse(!(new_filenames_trunc %in% loggerInfo$Raw_File_Name),
                                  new_filenames_trunc,
                                  NA))


# If non-matches ("new_filenames_trunc" that don't have a corresponding "Raw_File_Name" in "loggerInfo") 
# were found, notify user and ask whether to continue or quit.
# If the user selects "y", non-matches are removed from both new_filenames and new_filenames_trunc
if(length(new_file_info$filenameNoMatch) > 0) {
  
  # Tell the user there are no matches between the new data file names and the Raw_File_Name column in the logger information sheet
  message(paste("There are",length(new_file_info$filenameNoMatch),
                "new data files who's names did not match any of those listed in the 'Raw_File_Name' column in the 'Logger_Information.xlsx':"))
  print(data.frame(No_Matches = new_file_info$filenameNoMatch) |> filter(!is.na(No_Matches)))
  
  
  # Ask the user if they would like to continue with the program even though there were instances of no file name matches
  answer_nomatch <- readline("Would you like to continue?[y/n]")
  
  
  # If the user's answer (answer_nomatch) is "n", stops the program
  if(answer_nomatch == "n") {
    
    
    stop("Please make appropriate file name changes to the new files or add new rows to the 'Logger_Information.xlsx' sheet before retrying.")
    
    
  # If the user's answer (answer_nomatch) is "y"...
  } else if (answer_nomatch == "y") {
    
    
    message("\n\nProgram will attempt to rename new files to something that can be used.\nIf it cannot, the file names of new data without matches will not be used for processing\n\n.")
    Sys.sleep(5)
    
    
    new_file_info <- new_file_info |> 
      mutate(replaced_names = TREAT_VAGRANT_LOGGER_NAMES(filenameNoMatch))
    
    
    
    # Replace the relevant "Raw_File_Name" values in "loggerInfo" to their replacement names
    y = sapply(filter(new_file_info, !is.na(replaced_names)) |> pull(replaced_names) , str_which, pattern = loggerInfo$Raw_File_Name)
    loggerInfo[y, "Raw_File_Name"] <- new_file_info |> filter(!is.na(replaced_names)) |> pull(new_filenames_trunc)
    rm(y)
    
    # Create new vector of pathways for use during importing. This includes any "new_pathways"
    # associated with existing "filenameMatches" and with "replaced_names".
    import_pathways <<- new_file_info |> 
      filter(!is.na(filenameMatches) | !is.na(replaced_names)) |> 
      pull(new_filepaths)
    
    
    message("Non-matched filenames and filenames that couldn't be renamed have been removed from new filenames list...")
    Sys.sleep(3)
    
    
  } else {
    
    stop("Please provide a 'y' or 'n' answer.")
    
  }
}



message("\n\nProgress:  6) Parsing filename matches complete.")

3.7) Import New and Combined Data

Imports the data that the user selected.

# Create a subset of the loggerInfo tibble that only contains rows corresponding to file name matches
# and create a new "Import_Pathways" column.
information <- loggerInfo |> 
  filter(Raw_File_Name %in% new_file_info$new_filenames_trunc) |> 
  mutate(Import_Pathways = import_pathways, .after = Raw_File_Name)


#Import all combined data as tibbles and organize them into a list
new_data <- lapply(information$Import_Pathways, function(filepath) READ_NEW_TEMPER_FILE(filepath) )
names(new_data) <- new_file_info$new_filenames_trunc


#Import all combined data as tibbles and organize them into a list
comb_data <- lapply(comb_filepaths, function(x) READ_TEMPER_FILE(x) )
names(comb_data) <- comb_filenames



message("\n\nProgress:  7) Importation complete.")




# Rearrange the rows in "information" to match the order of data files in "new_data" 
# (because for some reason it didn't do that automatically during importation)
y = NULL
if(length(new_data) > 1) {
  for(i in new_file_info$new_filenames_trunc) {
    x <- information |> filter(Raw_File_Name == i)
    y <- bind_rows(y,x)
  }
  information <- y
  rm(x,y)
}

3.8) Set User-Controlled Parameters

Prompts the user to enter in the maximum time gap to fill with interpolated time and temperature values.

crit_gap_hr <- readline("Set time gap threshold for imputation (in hours):  ") |> as.numeric()

3.9) Data Cleaning

Removes and outlier points in the time series. Because automated processes to identify outliers is very complex, this version of the program currently only removes values > 50ºC and < -50ºC, which are too low or high to be considered real.

# For each column with temperature data (air,surface,or ground), replaces temperatures
# that are greater than 50C or less than -50C with NA.


for(i in 1:length(new_data)) {

 workDat <- new_data[[i]]

 # Subsets the names of colums in workDat containing temperature data.
 temperCols <- colnames(workDat) |> PPVO(patterns = c("Air","Surface_Ext","Surface_Int","Depth"))
 temperCols <- colnames(workDat)[temperCols]

 # For each column in "workDat" containing temperature data...
 for(ii in temperCols) {

   z <- workDat[[ii]]
   z[z > 50 | z < -50] <- NA
   workDat[[ii]] <- z

 }
 
 print(paste0("CLEANING COMPLETE:  ",names(new_data)[i]))

}
rm(i)

3.10) Impute Time Gaps

Fills in gaps in the DateTime and temperature data columns that were either in the data set already or were produced by the cleaning process.

# HEADS UP!... the datetime columns in the "new_data" tibbles are now in UTC again.
  
for(i in 1:length(new_data)) {
  workDat_name <- names(new_data)[i]
  workDat <- new_data[[i]] |>
    OPEN_ROWGAPS() |> 
    DATETIME_IMPUTE() |> 
    select(!c(hr_diff,Index))
  new_data[[workDat_name]] <- workDat
  
  print(paste0("IMPUTATION COMPLETE:  ", workDat_name))
  
} 
rm(workDat, workDat_name)

3.11) Append New Data to Combined Data Files

Appends new data tables available for each site to the relevant combined temperature data tables. For a site or borehole that has tandem (x2) ground temperature loggers, the script combines the new tandem ground temperature data together first, and then appends this combined new data to the old compiled data.

Warning! —- The treatment of tandem loggers is currently only valid for tandem GROUND TEMPERATURE loggers!!

# Creates a copy of the list "comb_data" that new versions of the combined data tibbles
# will be assigned to.
new_comb_data <- comb_data



# Get the levels of the "Borehole" column in "information"
boreholes <- information$Borehole |> 
  as.factor() |> 
  levels() |> 
  as.character()


# For each borehole in "information"...
for(b in boreholes) {
  
  
  # Isolate rows in "information" pertaining to the site
  workInfoBorehole <- information |> filter(Borehole == b)
  
  
  # If the borehole doesn't have tandem ground temperature loggers...
  if (sum(!is.na(workInfoBorehole$Tandem_Loggers)) == 0) {
    
    
    message(paste0("No tandem ground temperature data for ", b, ". Using nominal appending method."))
    
    
    # For each logger at the borehole...
    for(rawname in workInfoBorehole$Raw_File_Name) {
      
      
      # Isolate the data types collected by that logger
      workTypes <- workInfoBorehole |> 
        filter(Raw_File_Name == rawname) |> 
        select(Type) |> 
        unlist() |> 
        lapply(function(x) strsplit(x, ",")) |> 
        unlist() |> 
        unique()
      
      
      # Isolate the working new data tibble
      workNew <- new_data[[rawname]]
      
      
      # For each working data type...
      for(t in workTypes) {
        
        
        
        
        # Subset the new data tibble with respect to the relevant columns to the data type
        if(t == "air") {
          workNew_subset <- workNew |> select(DateTime, Air)
        } else if(t == "surfaceInt") {
          workNew_subset <- workNew |> select(DateTime, Surface_Int)
        } else if(t == "surfaceExt") {
          workNew_subset <- workNew |> select(DateTime, Surface_Ext)
        } else if(t == "ground") {
          workNew_subset <- workNew |> select(DateTime, contains("Depth"))
        }
        
        
       
        
        # Gets the working combined file name
        workComb_filename <- workInfoBorehole |> 
          filter(Raw_File_Name == rawname) |> 
          select(Combined_File_Name) |>
          as.character() |> 
          lapply(function(x) strsplit(x, ",") |> unlist()) |> 
          unlist() |> 
          str_subset(t) |> 
          paste0(".xlsx")
          
        
        
        
        # Isolates the working combined data tibble
        workComb <- comb_data[[workComb_filename]]
        
        
        
        
        # Join the subset new data tibble to the isolated combined tibble
        comb_data[[workComb_filename]] <- workComb |> full_join(workNew_subset)
        comb_data[[workComb_filename]] <- comb_data[[workComb_filename]] |>
          distinct(DateTime, .keep_all = TRUE)
        
        
        
      }
      
      
    }
    
    
    
    
    
    
  } else if(sum(!is.na(workInfoBorehole$Tandem_Loggers)) > 0) {
    
    
    message(paste0("Tandem ground temperature data appending method initiated for ", b, "."))
    
    
    # The raw file names for loggers containing tandem data
    tandem_raw_filenames <- workInfoBorehole |> 
      filter(!is.na(Tandem_Loggers)) |> 
      select(Raw_File_Name) |> 
      unlist()
    
    if(length(tandem_raw_filenames) < 2) {
      stop(paste0("Only one tandem file was detected for ", b,". Two tandem files are needed to
      finish appending. Please manually append these data instead."))
    }
    
    
    
    # Create a new temporary tibble by adding the columns of both tandem files together
    to_append <- full_join(new_data[[ tandem_raw_filenames[1] ]], new_data[[ tandem_raw_filenames[2] ]])
    
    
    
    # Append the temporary tibble "to_append" to the combined ground file
    
    for(i in comb_data) {
      if(nrow(i) == 0) {
        i[,"DateTime"] = as.POSIXct(i[,"DateTime"])
      }
    }
    
    appended_ground <- comb_data[[paste0("groundcombined_", b, ".xlsx")]] |>
      full_join(y = to_append) |> 
      select(starts_with("DateTime") | starts_with("Depth"))
    comb_data[[paste0("groundcombined_", b, ".xlsx")]] <- appended_ground
    comb_data[[paste0("groundcombined_", b, ".xlsx")]] <- comb_data[[paste0("groundcombined_", b, ".xlsx")]] |> 
      distinct(DateTime, .keep_all = TRUE)
    
    # If a "aircombined" file exists, append the temporary tibble "to_append" to 
    # the "aircombined" data
    if(sum(str_starts(names(comb_data), "air")) > 0) {
      appended_air <- comb_data[[paste0("aircombined_", b, ".xlsx")]] |> 
        full_join(y = to_append) |> 
        select(starts_with("DateTime") | starts_with("Air")) 
      comb_data[[paste0("aircombined_", b, ".xlsx")]] <- appended_air
      comb_data[[paste0("groundcombined_", b, ".xlsx")]] <- comb_data[[paste0("groundcombined_", b, ".xlsx")]] |> 
        distinct(DateTime, .keep_all = TRUE)
    }
    
    # If a "surfaceInt" file exists, append the temporary tibble "to_append" to 
    # the "surfaceInt" data
    if(sum(str_starts(names(comb_data), "surfaceInt")) > 0) {
      appended_surfaceInt <- comb_data[[paste0("surfaceIntcombined_", b, ".xlsx")]] |> 
        full_join(y = to_append) |> 
        select(starts_with("DateTime") | starts_with("Surface_Int"))
      comb_data[[paste0("surfaceIntcombined_", b, ".xlsx")]] <- appended_surfaceInt
      comb_data[[paste0("groundcombined_", b, ".xlsx")]] <- comb_data[[paste0("groundcombined_", b, ".xlsx")]] |> 
        distinct(DateTime, .keep_all = TRUE)
    }
    
    # If a "surfaceExt" file exists, append the temporary tibble "to_append" to 
    # the "surfaceExt" data
    if(sum(str_starts(names(comb_data), "surfaceExt")) > 0) {
      appended_surfaceExt <- comb_data[[paste0("surfaceExtcombined_", b, ".xlsx")]] |> 
        full_join(y = to_append) |> 
        select(starts_with("DateTime") | starts_with("Surface_Ext"))
      comb_data[[paste0("surfaceExtcombined_", b, ".xlsx")]] <- appended_surfaceExt
      comb_data[[paste0("groundcombined_", b, ".xlsx")]] <- comb_data[[paste0("groundcombined_", b, ".xlsx")]] |> 
        distinct(DateTime, .keep_all = TRUE)
    }
    
    
    
    
    
    
  }
  
  print(paste0("APPENDING COMPLETE:  ", b))
  
}

3.12) Data Exporting

Exports the data to the directory selected by the user using the dialogue window. The name of the output combined data file DOES NOT include the month and year of the update and needs to be added manually by the user. Doing this automatically is an aim for a future update.

for(i in 1:length(comb_data)) {
  
  forexport <- comb_data[[i]]
  name <- names(comb_data)[i] |> 
    strsplit("\\.") |> 
    unlist() %>%
    .[1]
  write_xlsx(forexport, paste0(chosen_output_directory,"/", name, ".xlsx"))
  
  
  print(paste0("EXPORTATION COMPLETE:  ", name))

}

4) Upcoming Updates

LS0tDQp0aXRsZTogIlRFTVBFUkFUVVJFIERBVEEgQ09NQklOSU5HIFBST0dSQU0iDQpvdXRwdXQ6DQogIGh0bWxfbm90ZWJvb2s6IGRlZmF1bHQNCiAgd29yZF9kb2N1bWVudDogZGVmYXVsdA0KLS0tDQoNCiMgVGFibGUgb2YgQ29udGVudHMNCg0KPiAtICAgWzEpIFByb2dyYW0gT3ZlcnZpZXddDQo+ICAgICAtICAgWzEuMSkgUiBQcm9qZWN0IEZpbGUgQ29udGVudHNdDQo+IC0gICBbMikgR3VpZGUgQm9va10NCj4gICAgIC0gICBbMi4xKSBXaGF0IGZpbGUgZm9ybWF0cyBjYW4gYmUgdXNlZD9dDQo+ICAgICAtICAgWzIuMikgV2hlcmUgc2hvdWxkIHRoZSBuZXcgYW5kIGNvbWJpbmVkIGZpbGVzIGJlIGJlZm9yZSBydW5uaW5nIHRoZSBzY3JpcHQ/XQ0KPiAgICAgLSAgIFsyLjMpIEhvdyBzaG91bGQgbXkgbmV3IGRhdGEgYW5kIGNvbWJpbmVkIGRhdGEgZmlsZXMgYmUgbmFtZWQgYmVmb3JlIHJ1bm5pbmcgdGhlIHByb2dyYW0/XQ0KPiAgICAgLSAgIFsyLjQpIFdoYXQgaWYgdGhlIHNpdGUgaXMgYnJhbmQgbmV3IGFuZCBkb2VzIG5vdCBoYXZlIGFueSBjb21iaW5lZCBkYXRhIHlldD9dDQo+ICAgICAgICAgLSAgIFtQcmVwYXJpbmcgYSBOZXcgU2l0ZSBmb3IgUHJvZ3JhbSBVc2VdDQo+IC0gICBbMykgTUFJTi5SIFNjcmlwdF0NCj4gICAgIC0gICBbMy4xKSBQYWNrYWdlIEluc3RhbGxhdGlvbiBhbmQgSW1wb3J0aW5nXQ0KPg0KPiAgICAgLSAgIFszLjIpIE1vZHVsZSBJbXBvcnRpbmddDQo+DQo+ICAgICAtICAgWzMuMykgQ3JlYXRlIFNlc3Npb24gSW5mbyBGaWxlXQ0KPg0KPiAgICAgLSAgIFszLjQpIEltcG9ydCBMb2dnZXIgSW5mb3JtYXRpb24gLnhsc3hdDQo+DQo+ICAgICAtICAgWzMuNSkgQ2hvb3NlIE5ldyBEYXRhIEZpbGVzXQ0KPg0KPiAgICAgLSAgIFszLjYpIERpc2NvdmVyIE5ldyBGaWxlIE5hbWUgTWF0Y2hlc10NCj4NCj4gICAgIC0gICBbMy43KSBJbXBvcnQgTmV3IGFuZCBDb21iaW5lZCBEYXRhXQ0KPg0KPiAgICAgLSAgIFszLjgpIFNldCBVc2VyLUNvbnRyb2xsZWQgUGFyYW1ldGVyc10NCj4NCj4gICAgIC0gICBbMy45KSBEYXRhIENsZWFuaW5nXQ0KPg0KPiAgICAgLSAgIFszLjEwKSBJbXB1dGUgVGltZSBHYXBzXQ0KPg0KPiAgICAgLSAgIFszLjExKSBBcHBlbmQgTmV3IERhdGEgdG8gQ29tYmluZWQgRGF0YSBGaWxlc10NCj4NCj4gICAgIC0gICBbMy4xMikgRGF0YSBFeHBvcnRpbmddDQo+IC0gICBbNCkgVXBjb21pbmcgVXBkYXRlc10NCg0KIyAxKSBQcm9ncmFtIE92ZXJ2aWV3DQoNClRoZSBwdXJwb3NlIG9mIHRoaXMgcHJvZ3JhbSBpcyB0byBhcHBlbmQgbmV3IHRlbXBlcmF0dXJlIGZpbGVzIHRvIGV4aXN0aW5nIHRlbXBlcmF0dXJlIHRpbWUgc2VyaWVzIGZpbGVzLiBUaGUgcHJvZ3JhbSBpcyBkZXNpZ25lZCB0byBoYW5kbGUgYSB2YXJpZXR5IG9mIGRpZmZlcmVudCBmaWxlIG5hbWluZyBjb252ZW50aW9ucywgZmlsZSB0eXBlcywgY29sdW1uL3JvdyBmb3JtYXRzLCBkYXRhIHR5cGVzLCBhbmQgY29sdW1uIG5hbWVzLg0KDQojIyAxLjEpIFIgUHJvamVjdCBGaWxlIENvbnRlbnRzDQoNClsqKipJbnB1dHMqKipdey51bmRlcmxpbmV9ICoqKuKAlCoqKiBGb2xkZXIgd2hlcmUgdGhlIHByb2dyYW0gd2lsbCBjcmVhdGUgYW5kIHN0b3JlIGNvcGllcyBvZiB0aGUgbmV3IGRhdGEgZmlsZXMgYW5kIGNvbWJpbmVkIGRhdGEgZmlsZXMgdGhhdCB5b3Ugc2VsZWN0IHdpdGggdGhlIGRpYWxvZ3VlIGJveC4gV29ya2luZyB3aXRoIGNvcGllcyBvZiB0aGUgbmV3IGFuZCBjb21iaW5lZCBkYXRhIHNhZmVndWFyZHMgYWdhaW5zdCBjb3JydXB0aW5nIG9yIGxvb3Npbmcgb2xkZXIgdmVyc2lvbnMgb2YgdGhvc2UgZGF0YSBhcmNoaXZlZCBpbiB0aGUgRGF0YSBCYXNlLg0KDQpbKioqTW9kdWxlIENvbXBlbmRpdW1zKioqXXsudW5kZXJsaW5lfSDigJQgRm9sZGVyIHdoZXJlIGNvbXBpbGF0aW9ucyBvZiBvdGhlciBzY3JpcHRzIHRoYXQgY29udGFpbiBmdW5jdGlvbnMgdXNlZCBpbiB0aGUgTUFJTi5SIHNjcmlwdCBhcmUgaG91c2VkLiBUaGUgY29tcGVuZGl1bSB1c2VkIGJ5IHRoZSBNQUlOLlIgc2NyaXB0IGlzIGxpc3RlZCBhdCB0aGUgc3RhcnQgb2YgdGhlIHNjcmlwdC4gUHJldmlvdXMgY29tcGVuZGl1bXMgd2VyZSB1c2VkIGluIG9sZGVyIHZlcnNpb25zIG9mIE1BSU4uUi4NCg0KWyoqKk9sZCBTY3JpcHRzKioqXXsudW5kZXJsaW5lfSDigJQgRm9sZGVyIHRoYXQncyBqdXN0IGFuIGFyY2hpdmUgb2Ygb2xkIHZlcnNpb25zIG9mIHRoZSBNQUlOLlIgYW5kIG1vZHVsZSBzY3JpcHRzLiBUaGVzZSBzY3JpcHRzIGFyZW4ndCB1c2VkIGZvciBhbnl0aGluZy4NCg0KWyoqKk9wZXJhdGlvbmFsIEZpbGVzKioqXXsudW5kZXJsaW5lfSDigJQgRm9sZGVyIGNvbnRhaW5pbmcgYW55IGZpbGVzIHRoYXQgYXJlIHVzZWQgaW4gYnkgdGhlIE1BSU4uUiBzY3JpcHQgb3IgYW55IG9mIHRoZSBtb2R1bGVzLiBUaGVzZSBpbmNsdWRlIChidXQgbm90IGxpbWl0ZWQgdG8pIHRoZSAueGxzeCBmaWxlcyBjb250YWluaW5nIGFsbCB0aGUgbWV0YWRhdGEgYWJvdXQgZWFjaCBsb2dnZXIgaW4gdGhlIFBHUiBuZXR3b3JrLg0KDQpbKioqT3V0cHV0cyoqKl17LnVuZGVybGluZX0g4oCUIEEgZm9sZGVyIHRoYXQgY2FuIChvcHRpb25hbGx5KSBiZSB1c2VkIGJ5IHRoZSB1c2VyIHRvIG91dHB1dCB0aGUgbmV3IGNvbWJpbmVkIGZpbGVzIHNvIHRoZXkgY2FuIGJlIGNoZWNrZWQgZm9yIGFjY3VyYWN5IGJlZm9yZSBwdXR0aW5nIHRoZW0gaW50byB0aGUgRGF0YSBCYXNlLg0KDQpbKioqTUFJTi5SKioqXXsudW5kZXJsaW5lfSDigJQgVGhlIG1haW4gUiBzY3JpcHQgdGhhdCBhcHBlbmRzIHRoZSBuZXcgZmlsZXMgdG8gdGhlIG9sZCBjb21iaW5lZCBkYXRhIGZpbGVzLg0KDQohW0ZpZ3VyZSAxLl0oT3BlcmF0aW9uYWwlMjBGaWxlcy9STUQlMjBEb2N1bWVudCUyMEltYWdlcy9Qcm9ncmFtJTIwT3ZlcnZpZXcvUiUyMFByb2plY3QlMjBGb2xkZXIucG5nKQ0KDQo8YnI+DQoNCiMgMikgR3VpZGUgQm9vaw0KDQpUaGlzIHNlY3Rpb24gcHJlc2VudHMgYSBzdGVwLWJ5LXN0ZXAgZ3VpZGUgZm9yIHVzZXJzIHRvIHVzZSB0aGUgcHJvZ3JhbS4NCg0KIyMgMi4xKSBXaGF0IGZpbGUgZm9ybWF0cyBjYW4gYmUgdXNlZD8NCg0KVGFibGUgMSBiZWxvdyBzaG93cyB0aGUgZmlsZSBsb2dnZXIvZmlsZSB0eXBlIGNvbWJpbmF0aW9ucyB0aGF0IGFyZSBzdXBwb3J0ZWQgYnkgdGhlIHByb2dyYW0uIFRoZSBzcGVjaWFsIHByZS1mb3JtYXR0aW5nIHJlcXVpcmVtZW50cyBhcmUgYW55IGZvcm1hdHRpbmcgdGhhdCB0aGUgdXNlciBzaG91bGQgZG91YmxlIGNoZWNrIGJlZm9yZSBydW5uaW5nIHRoZSBzY3JpcHQuIEhvd2V2ZXIsIHRoZXkgdXN1YWxseSBhcmUgYWxyZWFkeSBzZXQgdGhpcyB3YXkgYnkgZGVmYXVsdC4gVGhpcyB0YWJsZSBtYXkgbm90IGNvdmVyIGFsbCBvZiB0aGUgcG9zc2libGUgZm9ybWF0aW5ncyB0aGF0IGNvbWUgb2ZmIG9mIHRoZSBzdGF0aW9ucy4gSWYgdGhlcmUgYXJlIGFueSBzZXR0aW5ncyB0aGF0IGFyZSBtaXNzaW5nIGxldCBDYXNleSBrbm93IHNvIGhlIGNhbiB1cGRhdGUgdGhlIHNjcmlwdC4NCg0KKy0tLS0tLS0tLS0tLS0tLS0rLS0tLS0tLS0tLS0rLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tKw0KfCBMb2dnZXIgVHlwZSAgICB8IEZpbGUgVHlwZSB8IFNwZWNpYWwgUHJlLUZvcm1hdHRpbmcgUmVxdWlyZW1lbnRzICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfA0KKz09PT09PT09PT09PT09PT0rPT09PT09PT09PT0rPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09Kw0KfCBIT0JPICAgICAgICAgICB8IC0gICAueGxzeCB8IC0gICBUaGVyZSBtdXN0IGJlIGEgc2luZ2xlIERhdGVUaW1lIGNvbHVtbiwgbm90IGRpdmlkZWQgaW50byBzZXBhcmF0ZSBEYXRlIGFuZCBUaW1lIGNvbHVtbnMuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfA0KKy0tLS0tLS0tLS0tLS0tLS0rLS0tLS0tLS0tLS0rLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tKw0KfCBIT0JPIGJsdWV0b290aCB8IC0gICAuY3N2ICB8ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfA0KKy0tLS0tLS0tLS0tLS0tLS0rLS0tLS0tLS0tLS0rLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tKw0KfCBQZW5kYW50ICAgICAgICB8IC0gICAueGxzeCB8IC0gICBUaGVyZSBtdXN0IGJlIGEgc2luZ2xlIERhdGVUaW1lIGNvbHVtbiwgbm90IGRpdmlkZWQgaW50byBzZXBhcmF0ZSBEYXRlIGFuZCBUaW1lIGNvbHVtbnMuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfA0KfCAgICAgICAgICAgICAgICB8ICAgICAgICAgICB8ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfA0KfCAgICAgICAgICAgICAgICB8IC0gICAuY3N2ICB8ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfA0KKy0tLS0tLS0tLS0tLS0tLS0rLS0tLS0tLS0tLS0rLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tKw0KfCBMT0dSICAgICAgICAgICB8IC0gICAueGxzeCB8IC0gICBPZiB0aGUgNCBmaWxlcyB0aGF0IHRoZSBMT0dSIG91dHB1dHMsIHlvdSBzaG91bGQgdXNlIHRoZSBvbmUgdGhhdCBjb250YWlucyAidGVtcCIgaW4gdGhlIG5hbWUuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfA0KfCAgICAgICAgICAgICAgICB8ICAgICAgICAgICB8IC0gICBUaGUgY29sdW1ucyBjb250YWluaW5nIGdyb3VuZCB0ZW1wZXJhdHVyZSBkYXRhIG5lZWQgdG8gaGF2ZSAiQ0giIHNvbWV3aGVyZSBpbiB0aGVpciBjb2x1bW4gbmFtZXMgKHRoaXMgc2VlbXMgdG8gYmUgYSBkZWZhdWx0KS4gfA0KfCAgICAgICAgICAgICAgICB8IC0gICAuY3N2ICB8IC0gICBUaGUgZGF0ZXRpbWVzIHNob3VsZCBiZSB0ZXh0IG9yIG51bWVyaWMgdW5peCB2YWx1ZXMuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfA0KfCAgICAgICAgICAgICAgICB8ICAgICAgICAgICB8ICAgICAtICAgSWYgdGhlIGRhdGV0aW1lcyB2YWx1ZXMgYXJlIHRleHQsIHRoZXkgc2hvdWxkIGJlIGluIG9uZSBvZiB0aGUgZm9sbG93aW5nIGZvcm1hdHM6ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfA0KfCAgICAgICAgICAgICAgICB8ICAgICAgICAgICB8ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfA0KfCAgICAgICAgICAgICAgICB8ICAgICAgICAgICB8ICAgICAgICAgLSAgIG1kWSBITSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfA0KfCAgICAgICAgICAgICAgICB8ICAgICAgICAgICB8ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfA0KfCAgICAgICAgICAgICAgICB8ICAgICAgICAgICB8ICAgICAgICAgLSAgIFltZCBITVMgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfA0KKy0tLS0tLS0tLS0tLS0tLS0rLS0tLS0tLS0tLS0rLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tKw0KfCBNZXQgU3RhdGlvbiAgICB8IC0gICAuZGF0ICB8ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfA0KKy0tLS0tLS0tLS0tLS0tLS0rLS0tLS0tLS0tLS0rLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tKw0KDQo6IFRhYmxlIDEuIFR5cGVzIG9mIGZpbGVzLCBieSBsb2dnZXIgdHlwZSwgdGhhdCBjYW4gYmUgcmVhZCBieSB0aGUgcHJvZ3JhbS4NCg0KPGJyPg0KDQojIyAyLjIpIFdoZXJlIHNob3VsZCB0aGUgbmV3IGFuZCBjb21iaW5lZCBmaWxlcyBiZSBiZWZvcmUgcnVubmluZyB0aGUgc2NyaXB0Pw0KDQpUaGUgbmV3IGRhdGEgYW5kIGNvbWJpbmVkIGRhdGEgZmlsZXMgc2hvdWxkIGFscmVhZHkgYmUgaW5zaWRlIHRoZWlyIGFwcHJvcHJpYXRlIFNpdGUgZm9sZGVyIGluIHRoZSBEYXRhIEJhc2UgKEZpZyAyKS4gVGhlIHByb2dyYW0gd2lsbCBtYWtlIGNvcGllcyBvZiB0aGUgc2VsZWN0IG5ldyBhbmQgY29tYmluZWQgZGF0YSBhbmQgcGxhY2UgdGhlbSBpbiB0aGUgSW5wdXRzIGZvbGRlciBvZiB0aGUgUiBQcm9qZWN0IGZvbGRlciAoRmlnLiAxKS4NCg0KIVtGaWd1cmUgWC5YLl0oT3BlcmF0aW9uYWwlMjBGaWxlcy9STUQlMjBEb2N1bWVudCUyMEltYWdlcy9HdWlkZSUyMEJvb2svRGF0YSUyMEJhc2UlMjBPcmdhbml6YXRpb24ucG5nKQ0KDQo8YnI+DQoNCiMjIDIuMykgSG93IHNob3VsZCBteSBuZXcgZGF0YSBhbmQgY29tYmluZWQgZGF0YSBmaWxlcyBiZSBuYW1lZCBiZWZvcmUgcnVubmluZyB0aGUgcHJvZ3JhbT8NCg0KVGhlIG5ldyBmaWxlcyBjYW4ga2VlcCB0aGUgb3JpZ2luYWwgbmFtZXMgdGhhdCB0aGUgbG9nZ2VyIGdhdmUgdGhlbS4gVGFibGUgMiBzdW1tYXJpemVzIHRoZSBwb3NzaWJsZSBmb3JtYXRzIGZvciB0aGUgY29tYmluZWQgZmlsZSBuYW1lczoNCg0KfCBEYXRhIFR5cGUgfCBGb3JtYXQgfCBFeGFtcGxlIHwNCnwtLS0tfC0tLS18LS0tLXwNCnwgQWlyIHwgYWlyY29tYmluZWRcX3tib3JlaG9sZX1cX3ttb250aFllYXJ9IHwgYWlyY29tYmluZWRfQkgxMWFfU2VwdDIwMTUgfA0KfCBJbnRlcm5hbCBTdXJmYWNlIHwgc3VyZmFjZUludGNvbWJpbmVkXF97Ym9yZWhvbGV9XF97bW9udGhZZWFyfSB8IHN1cmZhY2VJbnRjb21iaW5lZF9CSDI0X0FwcjIwMTkgfA0KfCBFeHRlcm5hbCBTdXJmYWNlIHwgc3VyZmFjZUV4dGNvbWJpbmVkXF97Ym9yZWhvbGV9XF97bW9udGhZZWFyfSB8IHN1cmZhY2VFeHRjb21iaW5lZF9CSDA1X0p1bjIwMjEgfA0KfCBHcm91bmQgfCBncm91bmRjb21iaW5lZFxfe2JvcmVob2xlfVxfe21vbnRoWWVhcn0gfCBncm91bmRjb21iaW5lZF9CSDEzX1NlcHQyMDIwIHwNCg0KOiBUYWJsZSAyLiBDb21iaW5lZCBmaWxlIG5hbWUgZm9ybWF0cy4NCg0KVGhlIG1vbnRoIGFuZCB5ZWFyIGF0IHRoZSBlbmQgb2YgdGhlIGNvbWJpbmVkIGZpbGUgbmFtZXMgYXJlIG9ubHkgdG8gaGVscCB1cyBhcyB1c2VycyBrbm93IHdoZW4gdGhlIGxhc3QgdGltZSB0aGF0IGZpbGUgd2FzIHVwZGF0ZWQsIGFuZCBpcyBub3QgcmVxdWlyZWQgZm9yIHRoZSBmdW5jdGlvbmluZyBvZiB0aGUgcHJvZ3JhbS4NCg0KIyMgMi40KSBXaGF0IGlmIHRoZSBzaXRlIGlzIGJyYW5kIG5ldyBhbmQgZG9lcyBub3QgaGF2ZSBhbnkgY29tYmluZWQgZGF0YSB5ZXQ/DQoNClRoZSBjdXJyZW50IHZlcnNpb24gb2YgdGhlIHByb2dyYW0gZG9lcyBub3QgY3JlYXRlIG5ldyBjb21iaW5lZCBmaWxlcy4gWW91IHdpbGwgbmVlZCB0byBtYWtlIG5ldyBlbXB0eSBjb21iaW5lZCBmaWxlcyBmcm9tIHNjcmF0Y2ggYW5kIGFsc28gZmlsbCB0aGVtIGluIHdpdGggdGhlIGRhdGEgdGhlIGZpcnN0IHRpbWUuIFRoZSBuZXh0IHZlcnNpb24gd2lsbCBlaXRoZXIgcHJvbXB0IHRoZSB1c2VyIHdoZXRoZXIgdG8gY3JlYXRlIGEgbmV3IGNvbWJpbmVkIGZpbGUgZm9yIGEgbmV3IHNpdGUgKHNlZSAqU2VjdGlvbiA0KSBVcGNvbWluZyBVcGRhdGVzKiBmb3IgbW9yZSBkZXRhaWxzKS4gRm9yIG5vdywgZm9sbG93aW5nIHRoZSBpbnN0cnVjdGlvbnMgYmVsb3cgdG8gcHJlcGFyZSB0aGUgZmlsZXMgZm9yIGEgbmV3IHNpdGUgc28gdGhleSBjYW4gYmUgdXNlZCB3aXRoIHRoZSBzY3JpcHQgaW4gdGhlIGZ1dHVyZToNCg0KPiAjIyMgUHJlcGFyaW5nIGEgTmV3IFNpdGUgZm9yIFByb2dyYW0gVXNlDQo+DQo+IDEuICAqKipDcmVhdGUgbmV3IC54bHN4IGNvbWJpbmVkIGZpbGVzIGZvciB0aGUgc2l0ZS4qKioNCj4NCj4gICAgIENoZWNrIHdoYXQga2luZHMgb2YgZGF0YSB5b3UgaGF2ZSAoYWlyLCBpbnRlcm5hbCBzdXJmYWNlLCBleHRlcm5hbCBzdXJmYWNlLCBncm91bmQpLiBGb3IgZWFjaCBkYXRhIHR5cGUsIG1ha2UgYSBuZXcgLnhsc3ggZmlsZSB3aXRoIHRoZSBhcHByb3ByaWF0ZSBjb21iaW5lZCBkYXRhIG5hbWUgKHNlZSB0YWJsZSBpbiBTZWN0aW9uIDIuMyB0YWJsZSkuIFRoZSBjb2x1bW4gbmFtZXMgb2YgZWFjaCBvZiB0aGVzZSBuZXcgY29tYmluZWQgZmlsZXMgYXJlIHNob3duIGluIEZpZyAzLiBZb3UgZG9uJ3QgbmVlZCBhbGwgb2YgdGhlbSwgb25seSB0aGUgb25lcyByZWxldmFudCB0byB0aGUgZGF0YSBjb21pbmcgb2ZmIHRoZSBsb2dnZXI6DQo+DQo+ICAgICAhW0ZpZ3VyZSAzLl0oT3BlcmF0aW9uYWwlMjBGaWxlcy9STUQlMjBEb2N1bWVudCUyMEltYWdlcy9HdWlkZSUyMEJvb2svY29tYmluZWQlMjBmaWxlJTIwdGFibGUlMjBmb3JtYXRzLnBuZykNCj4NCj4gICAgIDxicj4NCj4NCj4gICAgIDEuICAqKipVcGRhdGUgdGhlIGFwcHJvcHJpYXRlIExvZ2dlcl9JbmZvcm1hdGlvbi54bHN4IGZpbGUqKioNCj4NCj4gICAgICAgICBUaGUgTG9nZ2VyX0luZm9ybWF0aW9uLnhsc3ggZmlsZXMgY29udGFpbiBhbGwgb2YgdGhlIG1ldGEgZGF0YSBhYm91dCBlYWNoIG9mIHRoZSBsb2dnZXJzIGZvciBhIHBhcnRpY3VsYXIgcmVnaW9uLiBUaGV5IGFyZSBmb3VuZCBpbiB0aGUgUiBwcm9qZWN0IHN1YmRpcmVjdG9yeSAoRmlnIDQpOg0KPg0KPiAgICAgICAgICoqRGF0YSBCYXNlIFw+IFIgXD4gVGVtcGVyYXR1cmUgQ29tYmluaW5nIFByb2dyYW0gXD4gT3BlcmF0aW9uYWwgRmlsZXMqKg0KPg0KPiAgICAgICAgICFbRmlndXJlIDQuXShPcGVyYXRpb25hbCUyMEZpbGVzL1JNRCUyMERvY3VtZW50JTIwSW1hZ2VzL0d1aWRlJTIwQm9vay9VcGRhdGluZyUyMExvZ2dlcl9JbmZvJTIwKDMpLnBuZykNCj4NCj4gICAgICAgICA8YnI+DQo+DQo+ICAgICAgICAgTWFrZSBhIG5ldyByb3cgaW4gdGhlIGFwcHJvcHJpYXRlIC54bHN4IGZpbGUgZm9yIGVhY2ggbmV3IGxvZ2dlci4gVGFibGUgMyBiZWxvdyBzdW1tYXJpemVzIHdoYXQgdG8gZW50ZXIgaW4gdG8gZWFjaCBjb2x1bW4gaW4gYSBsb2dnZXIgaW5mb3JtYXRpb24gc2hlZXQ6DQo+DQo+ICAgICAgICAgKy0tLS0tLS0tLS0tLS0tLS0tLS0tKy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0rDQo+ICAgICAgICAgfCBDb2x1bW4gICAgICAgICAgICAgfCBUbyBGaWxsIEluICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8DQo+ICAgICAgICAgKz09PT09PT09PT09PT09PT09PT09Kz09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0rDQo+ICAgICAgICAgfCBSYXdfRmlsZV9OYW1lICAgICAgfCAtICAgVGhlIG5hbWUgb2YgdGhlIGZpbGUgdGhhdCBjb21lcyBvZmYgb2YgdGhlIGxvZ2dlci4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8DQo+ICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8DQo+ICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgfCAtICAgV0lUSE9VVCB0aGUgZmlsZSBleHRlbnNpb24gKGUuZy4gLnhsc3gpICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8DQo+ICAgICAgICAgKy0tLS0tLS0tLS0tLS0tLS0tLS0tKy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0rDQo+ICAgICAgICAgfCBMb2dnZXIgICAgICAgICAgICAgfCAtICAgVGhlIHR5cGUgb2YgbG9nZ2VyLiBPbmUgb2Y6ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8DQo+ICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8DQo+ICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgfCAgICAgLSAgIEhPQk8gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8DQo+ICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8DQo+ICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgfCAgICAgLSAgIEhPQk9ibHVldG9vdGggICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8DQo+ICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8DQo+ICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgfCAgICAgLSAgIExPR1IgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8DQo+ICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8DQo+ICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgfCAgICAgLSAgIHBlbmRhbnQgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8DQo+ICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8DQo+ICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgfCAgICAgLSAgIE1ldCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8DQo+ICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8DQo+ICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgfCAtICAgKE1ldCBiYXNpY2FsbHkgaW1wbGllcyBDUjEwMDB4KSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8DQo+ICAgICAgICAgKy0tLS0tLS0tLS0tLS0tLS0tLS0tKy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0rDQo+ICAgICAgICAgfCBCb3JlaG9sZSAgICAgICAgICAgfCAtICAgVGhlIHNpbXBsZSBib3JlaG9sZSBkZXNpZ25hdG9yICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8DQo+ICAgICAgICAgKy0tLS0tLS0tLS0tLS0tLS0tLS0tKy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0rDQo+ICAgICAgICAgfCBUeXBlICAgICAgICAgICAgICAgfCAtICAgVHlwZSBvZiBkYXRhIG91dHB1dCBieSB0aGUgbG9nZ2VyICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8DQo+ICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8DQo+ICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgfCAtICAgT25lIG9mOiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8DQo+ICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8DQo+ICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgfCAgICAgLSAgIGFpciAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8DQo+ICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8DQo+ICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgfCAgICAgLSAgIHN1cmZhY2VJbnQgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8DQo+ICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8DQo+ICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgfCAgICAgLSAgIHN1cmZhY2VFeHQgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8DQo+ICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8DQo+ICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgfCAgICAgLSAgIGdyb3VuZCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8DQo+ICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8DQo+ICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgfCAtICAgSWYgdGhlcmUgYXJlIG11bHRpcGxlIHR5cGVzLCBzZXBhcmF0ZSB0aGUgdHlwZXMgYnkgYSBjb21tYSBhbmQgTk8gc3BhY2VzICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8DQo+ICAgICAgICAgKy0tLS0tLS0tLS0tLS0tLS0tLS0tKy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0rDQo+ICAgICAgICAgfCBDb21iaW5lZF9GaWxlX05hbWUgfCAtICAgVGhlIG5hbWUgb2YgdGhlIGV4Y2VsIGZpbGUgdGhhdCBjb250YWlucyB0aGUgY29tcGlsZWQgdGltZSBzZXJpZXMgb2YgZGF0YSBmb3IgdGhlIGxvZ2dlciAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8DQo+ICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8DQo+ICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgfCAtICAgV0lUSE9VVCB0aGUgZmlsZSBleHRlbnNpb24gKGUuZy4gLnhsc3gpICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8DQo+ICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8DQo+ICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgfCAtICAgKlNlZSAqKlRhYmxlIDIqKiBmb3IgY29tYmluZWQgZmlsZSBuYW1lIGZvcm1hdHMqICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8DQo+ICAgICAgICAgKy0tLS0tLS0tLS0tLS0tLS0tLS0tKy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0rDQo+ICAgICAgICAgfCBUYW5kZW1fTG9nZ2VycyAgICAgfCAtICAgVGFuZGVtIGxvZ2dlcnMgYXJlIGxvZ2dlcnMgdGhhdCBhcmUgcGFydCBvZiB0aGUgc2FtZSBib3JlaG9sZSBvciBzdGF0aW9uIHRoYXQgYXJlIHJlY29yZGluZyB0aGUgc2FtZSBkYXRhIHR5cGUgKGUuZy4gdHdvIEhPQk8gbG9nZ2VycyByZWNvcmRpbmcgZ3JvdW5kIHRlbXBlcmF0dXJlIGF0IGRpZmZlcmVudCBzZXRzIG9mIGRlcHRoKSB8DQo+ICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8DQo+ICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgfCAtICAgSWYgYSBib3JlaG9sZS9zdGF0aW9uIGhhcyB0YW5kZW0gbG9nZ2VycywgRU5URVIgVEhFIERBVEEgVFlQRSAoYWlyLCBzdXJmYWNlSW50LCBzdXJmYWNlRXh0LCBvciBncm91bmQpICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8DQo+ICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8DQo+ICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgfCAtICAgRUxTRSwgTEVBVkUgQkxBTksgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8DQo+ICAgICAgICAgKy0tLS0tLS0tLS0tLS0tLS0tLS0tKy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0rDQo+ICAgICAgICAgfCBDb2x1bW5fTmFtZXMgICAgICAgfCAtICAgQSBjb21tYS1kZWxpbmVhdGVkIGxpc3Qgb2YgdGhlIG5hbWVzIG9mIHRoZSBjb2x1bW5zLiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8DQo+ICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8DQo+ICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgfCAtICAgQ29sdW1uIG5hbWVzIGFyZSBzZXBhcmF0ZWQgYnkgYSBjb21tYSBhbmQgTk8gU1BBQ0VTICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8DQo+ICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8DQo+ICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgfCAtICAgKlNlZSBGaWd1cmUgMyBmb3IgY29sdW1uIG5hbWUgZm9ybWF0cyogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8DQo+ICAgICAgICAgKy0tLS0tLS0tLS0tLS0tLS0tLS0tKy0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0rDQo+DQo+ICAgICAgICAgOiBUYWJsZSAzLiBJbmZvcm1hdGlvbiB0byBmaWxsIGludG8gYSBsb2dnZXIgaW5mb3JtYXRpb24gc2hlZXQuDQoNCiMgMykgTUFJTi5SIFNjcmlwdA0KDQojIyAzLjEpIFBhY2thZ2UgSW5zdGFsbGF0aW9uIGFuZCBJbXBvcnRpbmcNCg0KSW1wb3J0cyBleHRlcm5hbCBwYWNrYWdlcyBvZiBjb2RlIHVzZWQgYnkgdGhlIHByb2dyYW0uDQoNCmBgYHtyfQ0KbGlicmFyeShwYWNtYW4pDQpwYWNtYW46OnBfbG9hZChkcGx5cix3cml0ZXhsLGNsb2NrLGdncGxvdDIscGxvdGx5LHJlc2hhcGUyLHJlc2hhcGUsc3RhdHMsdGlkeXZlcnNlLGdnc2NpLA0KICAgICAgICAgICAgICAgb3Blbnhsc3gsdGlkeXNlbGVjdCxzdHJpbmdyLHJlYWR4bCxsdWJyaWRhdGUsRFQsdGliYmxlLGhhdmVuLERlc2NUb29scywNCiAgICAgICAgICAgICAgIGltcHV0ZVRTLGJlcnJ5RnVuY3Rpb25zLGNsb2NrLGdkYXRhLGV4dHJhZm9udCxnZ3BtaXNjLGdnc2NpLGdnaDR4LHZpcmlkaXMscmxpc3QsDQogICAgICAgICAgICAgICB6b29tLHN0cmluZ2ksZ2dyZXBlbCxmYWJSLGRldnRvb2xzLHRjbHRrKQ0KYGBgDQoNCiMjIDMuMikgTW9kdWxlIEltcG9ydGluZw0KDQpSdW5zIHRoZSBzY3JpcHRzIHdpdGhpbiB0aGUgc2VwYXJhdGUgbW9kdWxlIGZpbGVzIGluIHRoZSAqKk1vZHVsZSBDb21wZW5kaXVtcyoqIGZvbGRlci4NCg0KYGBge3J9DQpzb3VyY2UoIk1vZHVsZSBDb21wZW5kaXVtcy9Db21wZW5kaXVtIDIvQ1VTVE9NIEZVTkNUSU9OUy5SIikNCnNvdXJjZSgiTW9kdWxlIENvbXBlbmRpdW1zL0NvbXBlbmRpdW0gMi9SRUFEX05FV19URU1QRVJfRklMRS5SIikNCmBgYA0KDQojIyAzLjMpIENyZWF0ZSBTZXNzaW9uIEluZm8gRmlsZQ0KDQpDcmVhdGVzIGFuIHRhYmxlIGNvbnRhaW5pbmcgbWV0YSBkYXRhIGFib3V0IHRoZSBjdXJyZW50IFIgc2Vzc2lvbi4gVGhpcyBpbmNsdWRlcyB0aGUgZGlyZWN0b3JpZXMgb2YgdGhlIGxhc3QgY29tYmluZWQgYW5kIG5ldyBkYXRhIGZpbGVzIHRoYXQgd2VyZSBzZWxlY3RlZCBieSB0aGUgdXNlciwgc28gd2hlbiB0aGV5IHJ1biB0aGUgc2NyaXB0IGFnYWluLCB0aGUgZGlhbG9ndWUgd2luZG93cyBvcGVuIHRvIHRob3NlIGRpcmVjdG9yaWVzIHJhdGhlciB0aGFuIGEgcmVtb3RlLCBoaWdoIGxldmVsIGZvbGRlciBzb21ld2hlcmUgb24gdGhlIGNvbXB1dGVyIChiYXNpY2FsbHkgc2F2ZXMgdGhlIHVzZXIgYSBiaXQgb2YgdGltZSkuDQoNCmBgYHtyfQ0KaWYoIWV4aXN0cygic2VzaF9pbmZvIikpIHsNCiAgc2VzaF9pbmZvIDwtIHRpYmJsZShsYXN0TmV3RGlyZWN0b3J5ID0gZ2V0d2QoKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICBsYXN0Q29tYkRpcmVjdG9yeSA9IE5BLA0KICAgICAgICAgICAgICAgICAgICAgICAgIGxhc3RPdXRwdXREaXJlY3RvcnkgPSBOQSkNCn0NCmBgYA0KDQojIyAzLjQpIEltcG9ydCBMb2dnZXIgSW5mb3JtYXRpb24gLnhsc3gNCg0KVGhlIHVzZXIgc2VsZWN0cyB0aGUgcmVnaW9uIHRoYXQgdGhlIG5ldyBsb2dnZXIgZGF0YSBoYXMgY29tZSBmcm9tLiBUaGlzIGltcG9ydHMgdGhlIHJlbGV2YW50IGxvZ2dlciBpbmZvcm1hdGlvbiBleGNlbCBmaWxlIHRoYXQgY29udGFpbnMgbWV0YSBkYXRhIGFib3V0IHRoZSBsb2dnZXJzLiBUaGUgcmVzdCBvZiB0aGUgcHJvZ3JhbSB1c2VzIHRoaXMgbWV0YSBkYXRhIHRvIGRpcmVjdCBob3cgbmV3IGRhdGEgaXMgcmVmb3JtYXR0ZWQgYW5kIGhvdyBuZXcgYW5kIG9sZCBkYXRhIGlzIGNvbWJpbmVkLg0KDQpgYGB7cn0NCiMgVXNlciBzZWxlY3QgcmVnaW9uDQpyZWdpb25zIDwtIGMoIkFsYXNrYSBIaWdod2F5IiwgIkRlbXBzdGVyIEhpZ2h3YXkiLCAiS2xvbmRpa2UgSGlnaHdheSIsIlJlZ2lvbiBOb3QgTGlzdGVkIikNCnJlZ2lvbnNfdGliIDwtIHRpYmJsZShJbmRleCA9IDE6bGVuZ3RoKHJlZ2lvbnMpLCBSZWdpb24gPSBjKCJBbGFza2EgSGlnaHdheSIsICJEZW1wc3RlciBIaWdod2F5IiwgIktsb25kaWtlIEhpZ2h3YXkiLCJSZWdpb24gTm90IExpc3RlZCIpKQ0KcHJpbnQocmVnaW9uc190aWIpDQpzZWxlY3RlZFJlZ2lvbiA8LSByZWFkbGluZSgiU2VsZWN0aW9uIHJlZ2lvbiAoY2hvb3NlIGluZGV4IG51bWJlcik6ICAiKSB8PiBhcy5udW1lcmljKCkNCnNlbGVjdGVkUmVnaW9uIDwtIHJlZ2lvbnNfdGliW1tzZWxlY3RlZFJlZ2lvbiwgMl1dDQoNCiNJbXBvcnQgc2Vuc29yIGRlcHRocyAueGxzeCBzaGVldA0KaWYoc2VsZWN0ZWRSZWdpb24gPT0gIkFsYXNrYSBIaWdod2F5Iikgew0KICBsb2dnZXJJbmZvIDwtIHJlYWRfeGxzeCgiT3BlcmF0aW9uYWwgRmlsZXMvQUhfTG9nZ2VyX0luZm9ybWF0aW9uLnhsc3giKQ0KfSBlbHNlIGlmIChzZWxlY3RlZFJlZ2lvbiA9PSAiRGVtcHN0ZXIgSGlnaHdheSIpIHsNCiAgbG9nZ2VySW5mbyA8LSByZWFkX3hsc3goIk9wZXJhdGlvbmFsIEZpbGVzL0RIX0xvZ2dlcl9JbmZvcm1hdGlvbi54bHN4IikNCn0gZWxzZSBpZiAoc2VsZWN0ZWRSZWdpb24gPT0gIktsb25kaWtlIEhpZ2h3YXkiKSB7DQogIGxvZ2dlckluZm8gPC0gcmVhZF94bHN4KCJPcGVyYXRpb25hbCBGaWxlcy9LSF9Mb2dnZXJfSW5mb3JtYXRpb24ueGxzeCIpDQp9DQpgYGANCg0KIyMgMy41KSBDaG9vc2UgTmV3IERhdGEgRmlsZXMNCg0KVGhlIHNlY3Rpb24gcG9wcyB1cCBmaWxlIGV4cGxvcmVyIHdpbmRvd3Mgd2hlcmUgdGhlIHVzZXIgbmF2aWdhdGVzIHRvIGFuZCBzZWxlY3RzIHRoZSBuZXcgZGF0YSBhbmQgY29tcGlsZWQgZGF0YSBmaWxlcyB0aGV5IHdhbnQgdG8gY29tYmluZS4gVGhlIHByb2dyYW0gY3JlYXRlcyBjb3BpZXMgb2YgdGhlc2UgZGF0YSBmaWxlcyBhbmQgcGxhY2VzIHRoZW0gaW4gdGhlICoqSW5wdXRzL05ldyBEYXRhKiogYW5kICoqSW5wdXRzL0NvbWJpbmVkIERhdGEqKiBzdWJkaXJlY3Rvcmllcy4NCg0KWyoqTm90ZSoqXXsudW5kZXJsaW5lfSAqKuKAlCoqIFRoZSBkaWFsb2d1ZSB3aW5kb3cgbWlnaHQgcG9wIHVwIGJlaGluZCBhbGwgdGhlIG90aGVyIG9wZW4gd2luZG93cyBvbiB5b3VyIGNvbXB1dGVyIQ0KDQpgYGB7cn0NCiMgT3BlbiBhIHdpbmRvd3MgZXhwbG9yZXIgd2luZG93IHRvIGNob29zZSB0aGUgbmV3IGRhdGEgZmlsZXMNCm5ld19maWxlcGF0aHMgPC0gdGtfY2hvb3NlLmZpbGVzKGxhc3Qoc2VzaF9pbmZvJGxhc3ROZXdEaXJlY3RvcnkpLCBjYXB0aW9uID0gIlNlbGVjdCBOZXcgRmlsZXMiKQ0KDQoNCiMgSWYgdGhlcmUgYXJlbid0IGFueSBjaGFyYWN0ZXIgc3RyaW5ncyBpbiAibmV3X2ZpbGVwYXRocyIsIHN0b3BzIHRoZSBzY3JpcHQgYW5kDQojIHRlbGxzIHRoZSB1c2VyIHRoZXkgbmVlZCB0byBzZWxlY3QgbmV3IGRhdGEgZmlsZXMuDQppZihsZW5ndGgobmV3X2ZpbGVwYXRocykgPT0gMCkgew0KICBtZXNzYWdlKCJObyBuZXcgZGF0YSBmaWxlcyB3ZXJlIHNlbGVjdGVkLiBFeGl0aW5nLi4uIikNCiAgYnJlYWsNCn0NCg0KIyBBIHRpYmJsZSBjb250YWluaW5nIGluZm9ybWF0aW9uIGFib3V0IHRoZSBmaWxlIHBhdGh3YXlzLCBmaWxlIG5hbWVzLCBleHRlbnNpb25zLA0KIyByb290cywgd2hldGhlciBvciBub3QgZmlsZSBuYW1lIG1hdGNoZXMgd2VyZSBmb3VuZCBpbiB0aGUgbG9nZ2VyIGluZm8gc2hlZXQsIGFuZA0KIyB3aGF0IG5hbWUsIGlmIGFueSwgY2FuIHJlcGxhY2UgdGhvc2UgZmlsZSBuYW1lcyB0aGF0IGRvbid0IGhhdmUgbWF0Y2hlcy4NCm5ld19maWxlX2luZm8gPC0gdGliYmxlKA0KICANCiAgbmV3X2ZpbGVwYXRocyA9IG5ld19maWxlcGF0aHMsDQogIA0KICBuZXdfZmlsZXBhdGhfcm9vdHMgPSBzYXBwbHkobmV3X2ZpbGVwYXRocywgZnVuY3Rpb24oeCkgew0KICAgIHN0cl9zcGxpdCh4LCIvIikgfD4gDQogICAgICB1bmxpc3QoKSB8PiANCiAgICAgIGhlYWQoLTEpIHw+IA0KICAgICAgcGFzdGUwKGNvbGxhcHNlID0gIi8iKQ0KICB9KSB8PiBwYXN0ZTAoIi8iKSwNCiAgDQogIG5ld19maWxlbmFtZXMgPSBuZXdfZmlsZXBhdGhzIHw+IGJhc2VuYW1lKCksDQogIA0KKQ0KDQojIEFkZCBhICJuZXdfZmlsZW5hbWVfZXh0ZW5zaW9ucyIgY29sdW1uIHRvICJuZXdfZmlsZV9pbmZvIg0KbmV3X2ZpbGVfaW5mbyA8LSBuZXdfZmlsZV9pbmZvIHw+IA0KICBtdXRhdGUoDQogICAgbmV3X2ZpbGVuYW1lX2V4dGVuc2lvbnMgPSBzYXBwbHkobmV3X2ZpbGVuYW1lcywgZnVuY3Rpb24oeCkgew0KICAgICAgc3RyX3NwbGl0KHgsICJcXC4iKSB8PiANCiAgICAgICAgdW5saXN0KCkgfD4gDQogICAgICAgIGxhc3QoKSANCiAgICB9KSwNCiAgICAuYWZ0ZXIgPSAibmV3X2ZpbGVwYXRoX3Jvb3RzIg0KICApDQoNCg0KIyBBZGQgYSAibmV3X2ZpbGVuYW1lc190cnVuYyIgY29sdW1uIHRvICJuZXdfZmlsZV9pbmZvIiB0aGF0IGlzIGp1c3QgdGhlICJuZXdfZmlsZW5hbWVzIg0KIyBjb2x1bW4gd2l0aG91dCB0aGUgZmlsZSBleHRlbnNpb25zDQpuZXdfZmlsZV9pbmZvIDwtIG5ld19maWxlX2luZm8gfD4gDQogIG11dGF0ZShuZXdfZmlsZW5hbWVzX3RydW5jID0gbmV3X2ZpbGVuYW1lcyB8PiANCiAgICAgICAgICAgc3RyX3JlcGxhY2VfYWxsKHBhdHRlcm4gPSAiLnhsc3h8LmNzdiIsIHJlcGxhY2VtZW50ID0gIiIpLA0KICAgICAgICAgLmFmdGVyID0gIm5ld19maWxlbmFtZV9leHRlbnNpb25zIg0KICAgICAgICAgKQ0KICANCg0KDQptZXNzYWdlKCJcblxuUHJvZ3Jlc3M6ICA1LjEpIFByb2dyZXNzOiAgTmV3IGRhdGEgZmlsZXMgY2hvc2VuLiIpDQoNCg0KDQojIyA1LjIpIFNFTEVDVElORyBDT01CSU5FRCBEQVRBIEZJTEVTLS0tLQ0KDQoNCg0KIyBjb21iX2ZpbGVwYXRocyA8LSB0a19jaG9vc2UuZmlsZXMocGFzdGUwKGdldHdkKCksIi9PdXRwdXRzIiksIGNhcHRpb24gPSAiU2VsZWN0IENvbWJpbmVkIEZpbGVzIikNCmNvbWJfZmlsZXBhdGhzIDwtIHRrX2Nob29zZS5maWxlcyhwYXN0ZTAoaWZlbHNlKGlzLm5hKGxhc3Qoc2VzaF9pbmZvJGxhc3RDb21iRGlyZWN0b3J5KSksIGdldHdkKCksIGxhc3Qoc2VzaF9pbmZvJGxhc3RDb21iRGlyZWN0b3J5KSksIi9PdXRwdXRzIiksIGNhcHRpb24gPSAiU2VsZWN0IENvbWJpbmVkIEZpbGVzIikNCg0KDQojIElmIHRoZXJlIGFyZW4ndCBhbnkgY2hhcmFjdGVyIHN0cmluZ3MgaW4gImNvbWJfZmlsZXBhdGhzIiwgc3RvcHMgdGhlIHNjcmlwdCBhbmQNCiMgdGVsbHMgdGhlIHVzZXIgdGhleSBuZWVkIHRvIHNlbGVjdCBjb21iaW5lZCBkYXRhIGZpbGVzLg0KaWYobGVuZ3RoKGNvbWJfZmlsZXBhdGhzKSA9PSAwKSB7DQogIG1lc3NhZ2UoIk5vIGNvbWJpbmVkIGRhdGEgZmlsZXMgd2VyZSBzZWxlY3RlZC4gRXhpdGluZy4uLiIpDQogIGJyZWFrDQp9DQoNCg0KIyBHZXQganVzdCB0aGUgbmFtZSBvZiB0aGUgZmlsZQ0KY29tYl9maWxlbmFtZXMgPC0gY29tYl9maWxlcGF0aHMgfD4gYmFzZW5hbWUoKQ0KDQoNCiMgUmVtb3ZlcyB0aGUgbW9udGggYW5kIHllYXIgYXQgdGhlIGVuZCBvZiB0aGUgY29tYmluZWQgZmlsZSBuYW1lcyBhbmQgcmVuYW1lcw0KIyB0aGUgYWN0dWFsIGZpbGUgbmFtZXMgdG8gdGhlc2UgYWx0ZXJlZCB2ZXJzaW9ucy4NCiMgKFRoZXNlIGRhdGVzIGFyZSBvbmx5IGZvciBvdXIgYmVuZWZpdCB3aGVuIHdlIGFyZSBtYW51YWxseSBsb29raW5nIHRocm91Z2ggdGhlIGRhdGEgYmFzZSwNCiMgYnV0IGFjdHVhbGx5IG1ha2Ugd3JpdGluZyB0aGlzIHNjcmlwdCBhIGJpdCBtb3JlIGRpZmZpY3VsdC4pDQpmb3IoaSBpbiAxOmxlbmd0aChjb21iX2ZpbGVuYW1lcykpIHsNCiAgDQogIGZpbGVuYW1lIDwtIGNvbWJfZmlsZW5hbWVzW2ldDQogIGZpbGVuYW1lX2RhdGVyZW1vdmVkIDwtIGdzdWIoIl9bQS1aYS16XStcXGR7NH0oPz1cXC54bHN4KSIsICIiLCBmaWxlbmFtZSwgcGVybCA9IFRSVUUpDQogIGNvbWJfZmlsZW5hbWVzW2ldIDwtIGZpbGVuYW1lX2RhdGVyZW1vdmVkDQogIA0KfQ0Kcm0oZmlsZW5hbWUsZmlsZW5hbWVfZGF0ZXJlbW92ZWQsaSkNCg0KDQoNCm1lc3NhZ2UoIlxuXG5Qcm9ncmVzczogIDUuMikgUHJvZ3Jlc3M6ICBDb21iaW5lZCBkYXRhIGZpbGVzIGNob3Nlbi4iKQ0KDQoNCg0KIyMgNS4zKSBTRUxFQ1RJTkcgT1VUUFVUIERJUkVDVE9SWS0tLS0NCg0KDQojIE9wZW4gd2luZG93cyBleHBsb3JlciB3aW5kb3cgdG8gY2hvb3NlIHRoZSBvdXRwdXQgZmlsZQ0KY2hvc2VuX291dHB1dF9kaXJlY3RvcnkgPC0gdGtfY2hvb3NlLmRpcihsYXN0KHNlc2hfaW5mbyRsYXN0T3V0cHV0RGlyZWN0b3J5KSwgY2FwdGlvbiA9ICJTZWxlY3QgT3V0cHV0IERpcmVjdG9yeSIpDQppZihsZW5ndGgoY2hvc2VuX291dHB1dF9kaXJlY3RvcnkpID09IDApIHsNCiAgbWVzc2FnZSgiTm8gb3V0cHV0IGRpcmVjdG9yeSB3YXMgc2VsZWN0ZWQuIEV4aXRpbmcuLi4iKQ0KICBicmVhaw0KfQ0KDQoNCiMgVXBkYXRlIHNlc3Npb24gaW5mbw0Kc2VzaF9pbmZvIDwtIHNlc2hfaW5mbyB8PiBhZGRfcm93KGxhc3ROZXdEaXJlY3RvcnkgPSBuZXdfZmlsZXBhdGhzWzFdIHw+IA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RyX3NwbGl0KCIvIikgfD4gDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB1bmxpc3QoKSB8PiANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhlYWQoLTEpIHw+IA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGFzdGUwKGNvbGxhcHNlID0gIi8iKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsYXN0Q29tYkRpcmVjdG9yeSA9IGNvbWJfZmlsZXBhdGhzWzFdIHw+IA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RyX3NwbGl0KCIvIikgfD4gDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB1bmxpc3QoKSB8PiANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhlYWQoLTEpIHw+IA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGFzdGUwKGNvbGxhcHNlID0gIi8iKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsYXN0T3V0cHV0RGlyZWN0b3J5ID0gY2hvc2VuX291dHB1dF9kaXJlY3RvcnkpDQoNCg0KDQoNCm1lc3NhZ2UoIlxuXG5Qcm9ncmVzczogIDUuMykgUHJvZ3Jlc3M6ICBPdXRwdXQgZGlyZWN0b3J5IGNob3Nlbi4iKQ0KYGBgDQoNCiMjIDMuNikgRGlzY292ZXIgTmV3IEZpbGUgTmFtZSBNYXRjaGVzDQoNClRoaXMgc2VjdGlvbiBjb25maXJtcyB0byB0aGUgdXNlciB3aGV0aGVyIG9yIG5vdCB0aGVyZSBtYXRjaGVzIGZvdW5kIGJldHdlZW4gdGhlIG5ldyBkYXRhIGZpbGVzIHRoYXQgd2VyZSBzZWxlY3RlZCBhbmQgY29ycmVzcG9uZGluZyByb3dzIG9mIG1ldGEgZGF0YSBpbiB0aGUgaW1wb3J0ZWQgTG9nZ2VyX0luZm9ybWF0aW9uLnhsc3ggZmlsZS4gSWYgYSBtYXRjaCB3YXNuJ3QgZm91bmQsIHRoZSB1c2VyIGlzIHByb21wdGVkIHRvIGFuc3dlciB3aGV0aGVyIG9yIG5vdCB0byBjb250aW51ZSBwcm9jZXNzaW5nIHRoZSBmaWxlcyBmb3Igd2hpY2ggbWF0Y2hlcyB3ZXJlIGZvdW5kIChuZXcgZGF0YSBmaWxlcyB3aXRob3V0IG1hdGNoZXMgd29uJ3QgYmUgdXNlZCBmb3IgYW55dGhpbmcpLiBJZiBhIG1hdGNoIHdhc24ndCBmb3VuZCwgdGhlIHByb2dyYW0gd29uJ3QgYmUgYWJsZSB0byBwcm9wZXJseSBtYW5pcHVsYXRlIHRoZSBkYXRhLg0KDQpgYGB7cn0NCiMgR2V0cyBtYXRjaGVzIGJldHdlZW4gbmV3X2ZpbGVuYW1lc190cnVuYyBhbmQgdGhlIFJhdyBGaWxlIE5hbWVzIGluIHRoZSBMb2dnZXJfSW5mb3JtYXRpb24gZmlsZS4NCm5ld19maWxlX2luZm8gPC0gbmV3X2ZpbGVfaW5mbyB8PiANCiAgbXV0YXRlKGZpbGVuYW1lTWF0Y2hlcyA9IGlmZWxzZShuZXdfZmlsZW5hbWVzX3RydW5jICVpbiUgbG9nZ2VySW5mbyRSYXdfRmlsZV9OYW1lLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuZXdfZmlsZW5hbWVzX3RydW5jLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBOQSkpDQoNCiMgR2V0IG5vbi1tYXRjaGVzDQpuZXdfZmlsZV9pbmZvIDwtIG5ld19maWxlX2luZm8gfD4gDQogIG11dGF0ZShmaWxlbmFtZU5vTWF0Y2ggPSBpZmVsc2UoIShuZXdfZmlsZW5hbWVzX3RydW5jICVpbiUgbG9nZ2VySW5mbyRSYXdfRmlsZV9OYW1lKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuZXdfZmlsZW5hbWVzX3RydW5jLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE5BKSkNCg0KDQojIElmIG5vbi1tYXRjaGVzICgibmV3X2ZpbGVuYW1lc190cnVuYyIgdGhhdCBkb24ndCBoYXZlIGEgY29ycmVzcG9uZGluZyAiUmF3X0ZpbGVfTmFtZSIgaW4gImxvZ2dlckluZm8iKSANCiMgd2VyZSBmb3VuZCwgbm90aWZ5IHVzZXIgYW5kIGFzayB3aGV0aGVyIHRvIGNvbnRpbnVlIG9yIHF1aXQuDQojIElmIHRoZSB1c2VyIHNlbGVjdHMgInkiLCBub24tbWF0Y2hlcyBhcmUgcmVtb3ZlZCBmcm9tIGJvdGggbmV3X2ZpbGVuYW1lcyBhbmQgbmV3X2ZpbGVuYW1lc190cnVuYw0KaWYobGVuZ3RoKG5ld19maWxlX2luZm8kZmlsZW5hbWVOb01hdGNoKSA+IDApIHsNCiAgDQogICMgVGVsbCB0aGUgdXNlciB0aGVyZSBhcmUgbm8gbWF0Y2hlcyBiZXR3ZWVuIHRoZSBuZXcgZGF0YSBmaWxlIG5hbWVzIGFuZCB0aGUgUmF3X0ZpbGVfTmFtZSBjb2x1bW4gaW4gdGhlIGxvZ2dlciBpbmZvcm1hdGlvbiBzaGVldA0KICBtZXNzYWdlKHBhc3RlKCJUaGVyZSBhcmUiLGxlbmd0aChuZXdfZmlsZV9pbmZvJGZpbGVuYW1lTm9NYXRjaCksDQogICAgICAgICAgICAgICAgIm5ldyBkYXRhIGZpbGVzIHdobydzIG5hbWVzIGRpZCBub3QgbWF0Y2ggYW55IG9mIHRob3NlIGxpc3RlZCBpbiB0aGUgJ1Jhd19GaWxlX05hbWUnIGNvbHVtbiBpbiB0aGUgJ0xvZ2dlcl9JbmZvcm1hdGlvbi54bHN4JzoiKSkNCiAgcHJpbnQoZGF0YS5mcmFtZShOb19NYXRjaGVzID0gbmV3X2ZpbGVfaW5mbyRmaWxlbmFtZU5vTWF0Y2gpIHw+IGZpbHRlcighaXMubmEoTm9fTWF0Y2hlcykpKQ0KICANCiAgDQogICMgQXNrIHRoZSB1c2VyIGlmIHRoZXkgd291bGQgbGlrZSB0byBjb250aW51ZSB3aXRoIHRoZSBwcm9ncmFtIGV2ZW4gdGhvdWdoIHRoZXJlIHdlcmUgaW5zdGFuY2VzIG9mIG5vIGZpbGUgbmFtZSBtYXRjaGVzDQogIGFuc3dlcl9ub21hdGNoIDwtIHJlYWRsaW5lKCJXb3VsZCB5b3UgbGlrZSB0byBjb250aW51ZT9beS9uXSIpDQogIA0KICANCiAgIyBJZiB0aGUgdXNlcidzIGFuc3dlciAoYW5zd2VyX25vbWF0Y2gpIGlzICJuIiwgc3RvcHMgdGhlIHByb2dyYW0NCiAgaWYoYW5zd2VyX25vbWF0Y2ggPT0gIm4iKSB7DQogICAgDQogICAgDQogICAgc3RvcCgiUGxlYXNlIG1ha2UgYXBwcm9wcmlhdGUgZmlsZSBuYW1lIGNoYW5nZXMgdG8gdGhlIG5ldyBmaWxlcyBvciBhZGQgbmV3IHJvd3MgdG8gdGhlICdMb2dnZXJfSW5mb3JtYXRpb24ueGxzeCcgc2hlZXQgYmVmb3JlIHJldHJ5aW5nLiIpDQogICAgDQogICAgDQogICMgSWYgdGhlIHVzZXIncyBhbnN3ZXIgKGFuc3dlcl9ub21hdGNoKSBpcyAieSIuLi4NCiAgfSBlbHNlIGlmIChhbnN3ZXJfbm9tYXRjaCA9PSAieSIpIHsNCiAgICANCiAgICANCiAgICBtZXNzYWdlKCJcblxuUHJvZ3JhbSB3aWxsIGF0dGVtcHQgdG8gcmVuYW1lIG5ldyBmaWxlcyB0byBzb21ldGhpbmcgdGhhdCBjYW4gYmUgdXNlZC5cbklmIGl0IGNhbm5vdCwgdGhlIGZpbGUgbmFtZXMgb2YgbmV3IGRhdGEgd2l0aG91dCBtYXRjaGVzIHdpbGwgbm90IGJlIHVzZWQgZm9yIHByb2Nlc3NpbmdcblxuLiIpDQogICAgU3lzLnNsZWVwKDUpDQogICAgDQogICAgDQogICAgbmV3X2ZpbGVfaW5mbyA8LSBuZXdfZmlsZV9pbmZvIHw+IA0KICAgICAgbXV0YXRlKHJlcGxhY2VkX25hbWVzID0gVFJFQVRfVkFHUkFOVF9MT0dHRVJfTkFNRVMoZmlsZW5hbWVOb01hdGNoKSkNCiAgICANCiAgICANCiAgICANCiAgICAjIFJlcGxhY2UgdGhlIHJlbGV2YW50ICJSYXdfRmlsZV9OYW1lIiB2YWx1ZXMgaW4gImxvZ2dlckluZm8iIHRvIHRoZWlyIHJlcGxhY2VtZW50IG5hbWVzDQogICAgeSA9IHNhcHBseShmaWx0ZXIobmV3X2ZpbGVfaW5mbywgIWlzLm5hKHJlcGxhY2VkX25hbWVzKSkgfD4gcHVsbChyZXBsYWNlZF9uYW1lcykgLCBzdHJfd2hpY2gsIHBhdHRlcm4gPSBsb2dnZXJJbmZvJFJhd19GaWxlX05hbWUpDQogICAgbG9nZ2VySW5mb1t5LCAiUmF3X0ZpbGVfTmFtZSJdIDwtIG5ld19maWxlX2luZm8gfD4gZmlsdGVyKCFpcy5uYShyZXBsYWNlZF9uYW1lcykpIHw+IHB1bGwobmV3X2ZpbGVuYW1lc190cnVuYykNCiAgICBybSh5KQ0KICAgIA0KICAgICMgQ3JlYXRlIG5ldyB2ZWN0b3Igb2YgcGF0aHdheXMgZm9yIHVzZSBkdXJpbmcgaW1wb3J0aW5nLiBUaGlzIGluY2x1ZGVzIGFueSAibmV3X3BhdGh3YXlzIg0KICAgICMgYXNzb2NpYXRlZCB3aXRoIGV4aXN0aW5nICJmaWxlbmFtZU1hdGNoZXMiIGFuZCB3aXRoICJyZXBsYWNlZF9uYW1lcyIuDQogICAgaW1wb3J0X3BhdGh3YXlzIDw8LSBuZXdfZmlsZV9pbmZvIHw+IA0KICAgICAgZmlsdGVyKCFpcy5uYShmaWxlbmFtZU1hdGNoZXMpIHwgIWlzLm5hKHJlcGxhY2VkX25hbWVzKSkgfD4gDQogICAgICBwdWxsKG5ld19maWxlcGF0aHMpDQogICAgDQogICAgDQogICAgbWVzc2FnZSgiTm9uLW1hdGNoZWQgZmlsZW5hbWVzIGFuZCBmaWxlbmFtZXMgdGhhdCBjb3VsZG4ndCBiZSByZW5hbWVkIGhhdmUgYmVlbiByZW1vdmVkIGZyb20gbmV3IGZpbGVuYW1lcyBsaXN0Li4uIikNCiAgICBTeXMuc2xlZXAoMykNCiAgICANCiAgICANCiAgfSBlbHNlIHsNCiAgICANCiAgICBzdG9wKCJQbGVhc2UgcHJvdmlkZSBhICd5JyBvciAnbicgYW5zd2VyLiIpDQogICAgDQogIH0NCn0NCg0KDQoNCm1lc3NhZ2UoIlxuXG5Qcm9ncmVzczogIDYpIFBhcnNpbmcgZmlsZW5hbWUgbWF0Y2hlcyBjb21wbGV0ZS4iKQ0KYGBgDQoNCiMjIDMuNykgSW1wb3J0IE5ldyBhbmQgQ29tYmluZWQgRGF0YQ0KDQpJbXBvcnRzIHRoZSBkYXRhIHRoYXQgdGhlIHVzZXIgc2VsZWN0ZWQuDQoNCmBgYHtyfQ0KIyBDcmVhdGUgYSBzdWJzZXQgb2YgdGhlIGxvZ2dlckluZm8gdGliYmxlIHRoYXQgb25seSBjb250YWlucyByb3dzIGNvcnJlc3BvbmRpbmcgdG8gZmlsZSBuYW1lIG1hdGNoZXMNCiMgYW5kIGNyZWF0ZSBhIG5ldyAiSW1wb3J0X1BhdGh3YXlzIiBjb2x1bW4uDQppbmZvcm1hdGlvbiA8LSBsb2dnZXJJbmZvIHw+IA0KICBmaWx0ZXIoUmF3X0ZpbGVfTmFtZSAlaW4lIG5ld19maWxlX2luZm8kbmV3X2ZpbGVuYW1lc190cnVuYykgfD4gDQogIG11dGF0ZShJbXBvcnRfUGF0aHdheXMgPSBpbXBvcnRfcGF0aHdheXMsIC5hZnRlciA9IFJhd19GaWxlX05hbWUpDQoNCg0KI0ltcG9ydCBhbGwgY29tYmluZWQgZGF0YSBhcyB0aWJibGVzIGFuZCBvcmdhbml6ZSB0aGVtIGludG8gYSBsaXN0DQpuZXdfZGF0YSA8LSBsYXBwbHkoaW5mb3JtYXRpb24kSW1wb3J0X1BhdGh3YXlzLCBmdW5jdGlvbihmaWxlcGF0aCkgUkVBRF9ORVdfVEVNUEVSX0ZJTEUoZmlsZXBhdGgpICkNCm5hbWVzKG5ld19kYXRhKSA8LSBuZXdfZmlsZV9pbmZvJG5ld19maWxlbmFtZXNfdHJ1bmMNCg0KDQojSW1wb3J0IGFsbCBjb21iaW5lZCBkYXRhIGFzIHRpYmJsZXMgYW5kIG9yZ2FuaXplIHRoZW0gaW50byBhIGxpc3QNCmNvbWJfZGF0YSA8LSBsYXBwbHkoY29tYl9maWxlcGF0aHMsIGZ1bmN0aW9uKHgpIFJFQURfVEVNUEVSX0ZJTEUoeCkgKQ0KbmFtZXMoY29tYl9kYXRhKSA8LSBjb21iX2ZpbGVuYW1lcw0KDQoNCg0KbWVzc2FnZSgiXG5cblByb2dyZXNzOiAgNykgSW1wb3J0YXRpb24gY29tcGxldGUuIikNCg0KDQoNCg0KIyBSZWFycmFuZ2UgdGhlIHJvd3MgaW4gImluZm9ybWF0aW9uIiB0byBtYXRjaCB0aGUgb3JkZXIgb2YgZGF0YSBmaWxlcyBpbiAibmV3X2RhdGEiIA0KIyAoYmVjYXVzZSBmb3Igc29tZSByZWFzb24gaXQgZGlkbid0IGRvIHRoYXQgYXV0b21hdGljYWxseSBkdXJpbmcgaW1wb3J0YXRpb24pDQp5ID0gTlVMTA0KaWYobGVuZ3RoKG5ld19kYXRhKSA+IDEpIHsNCiAgZm9yKGkgaW4gbmV3X2ZpbGVfaW5mbyRuZXdfZmlsZW5hbWVzX3RydW5jKSB7DQogICAgeCA8LSBpbmZvcm1hdGlvbiB8PiBmaWx0ZXIoUmF3X0ZpbGVfTmFtZSA9PSBpKQ0KICAgIHkgPC0gYmluZF9yb3dzKHkseCkNCiAgfQ0KICBpbmZvcm1hdGlvbiA8LSB5DQogIHJtKHgseSkNCn0NCmBgYA0KDQojIyAzLjgpIFNldCBVc2VyLUNvbnRyb2xsZWQgUGFyYW1ldGVycw0KDQpQcm9tcHRzIHRoZSB1c2VyIHRvIGVudGVyIGluIHRoZSBtYXhpbXVtIHRpbWUgZ2FwIHRvIGZpbGwgd2l0aCBpbnRlcnBvbGF0ZWQgdGltZSBhbmQgdGVtcGVyYXR1cmUgdmFsdWVzLg0KDQpgYGB7cn0NCmNyaXRfZ2FwX2hyIDwtIHJlYWRsaW5lKCJTZXQgdGltZSBnYXAgdGhyZXNob2xkIGZvciBpbXB1dGF0aW9uIChpbiBob3Vycyk6ICAiKSB8PiBhcy5udW1lcmljKCkNCmBgYA0KDQojIyAzLjkpIERhdGEgQ2xlYW5pbmcNCg0KUmVtb3ZlcyBhbmQgb3V0bGllciBwb2ludHMgaW4gdGhlIHRpbWUgc2VyaWVzLiBCZWNhdXNlIGF1dG9tYXRlZCBwcm9jZXNzZXMgdG8gaWRlbnRpZnkgb3V0bGllcnMgaXMgdmVyeSBjb21wbGV4LCB0aGlzIHZlcnNpb24gb2YgdGhlIHByb2dyYW0gY3VycmVudGx5IG9ubHkgcmVtb3ZlcyB2YWx1ZXMgXD4gNTDCukMgYW5kIFw8IC01MMK6Qywgd2hpY2ggYXJlIHRvbyBsb3cgb3IgaGlnaCB0byBiZSBjb25zaWRlcmVkIHJlYWwuDQoNCmBgYHtyfQ0KIyBGb3IgZWFjaCBjb2x1bW4gd2l0aCB0ZW1wZXJhdHVyZSBkYXRhIChhaXIsc3VyZmFjZSxvciBncm91bmQpLCByZXBsYWNlcyB0ZW1wZXJhdHVyZXMNCiMgdGhhdCBhcmUgZ3JlYXRlciB0aGFuIDUwQyBvciBsZXNzIHRoYW4gLTUwQyB3aXRoIE5BLg0KDQoNCmZvcihpIGluIDE6bGVuZ3RoKG5ld19kYXRhKSkgew0KDQogd29ya0RhdCA8LSBuZXdfZGF0YVtbaV1dDQoNCiAjIFN1YnNldHMgdGhlIG5hbWVzIG9mIGNvbHVtcyBpbiB3b3JrRGF0IGNvbnRhaW5pbmcgdGVtcGVyYXR1cmUgZGF0YS4NCiB0ZW1wZXJDb2xzIDwtIGNvbG5hbWVzKHdvcmtEYXQpIHw+IFBQVk8ocGF0dGVybnMgPSBjKCJBaXIiLCJTdXJmYWNlX0V4dCIsIlN1cmZhY2VfSW50IiwiRGVwdGgiKSkNCiB0ZW1wZXJDb2xzIDwtIGNvbG5hbWVzKHdvcmtEYXQpW3RlbXBlckNvbHNdDQoNCiAjIEZvciBlYWNoIGNvbHVtbiBpbiAid29ya0RhdCIgY29udGFpbmluZyB0ZW1wZXJhdHVyZSBkYXRhLi4uDQogZm9yKGlpIGluIHRlbXBlckNvbHMpIHsNCg0KICAgeiA8LSB3b3JrRGF0W1tpaV1dDQogICB6W3ogPiA1MCB8IHogPCAtNTBdIDwtIE5BDQogICB3b3JrRGF0W1tpaV1dIDwtIHoNCg0KIH0NCiANCiBwcmludChwYXN0ZTAoIkNMRUFOSU5HIENPTVBMRVRFOiAgIixuYW1lcyhuZXdfZGF0YSlbaV0pKQ0KDQp9DQpybShpKQ0KYGBgDQoNCiMjIDMuMTApIEltcHV0ZSBUaW1lIEdhcHMNCg0KRmlsbHMgaW4gZ2FwcyBpbiB0aGUgRGF0ZVRpbWUgYW5kIHRlbXBlcmF0dXJlIGRhdGEgY29sdW1ucyB0aGF0IHdlcmUgZWl0aGVyIGluIHRoZSBkYXRhIHNldCBhbHJlYWR5IG9yIHdlcmUgcHJvZHVjZWQgYnkgdGhlIGNsZWFuaW5nIHByb2Nlc3MuDQoNCmBgYHtyfQ0KIyBIRUFEUyBVUCEuLi4gdGhlIGRhdGV0aW1lIGNvbHVtbnMgaW4gdGhlICJuZXdfZGF0YSIgdGliYmxlcyBhcmUgbm93IGluIFVUQyBhZ2Fpbi4NCiAgDQpmb3IoaSBpbiAxOmxlbmd0aChuZXdfZGF0YSkpIHsNCiAgd29ya0RhdF9uYW1lIDwtIG5hbWVzKG5ld19kYXRhKVtpXQ0KICB3b3JrRGF0IDwtIG5ld19kYXRhW1tpXV0gfD4NCiAgICBPUEVOX1JPV0dBUFMoKSB8PiANCiAgICBEQVRFVElNRV9JTVBVVEUoKSB8PiANCiAgICBzZWxlY3QoIWMoaHJfZGlmZixJbmRleCkpDQogIG5ld19kYXRhW1t3b3JrRGF0X25hbWVdXSA8LSB3b3JrRGF0DQogIA0KICBwcmludChwYXN0ZTAoIklNUFVUQVRJT04gQ09NUExFVEU6ICAiLCB3b3JrRGF0X25hbWUpKQ0KICANCn0gDQpybSh3b3JrRGF0LCB3b3JrRGF0X25hbWUpDQpgYGANCg0KIyMgMy4xMSkgQXBwZW5kIE5ldyBEYXRhIHRvIENvbWJpbmVkIERhdGEgRmlsZXMNCg0KQXBwZW5kcyBuZXcgZGF0YSB0YWJsZXMgYXZhaWxhYmxlIGZvciBlYWNoIHNpdGUgdG8gdGhlIHJlbGV2YW50IGNvbWJpbmVkIHRlbXBlcmF0dXJlIGRhdGEgdGFibGVzLiBGb3IgYSBzaXRlIG9yIGJvcmVob2xlIHRoYXQgaGFzIHRhbmRlbSAoeDIpIGdyb3VuZCB0ZW1wZXJhdHVyZSBsb2dnZXJzLCB0aGUgc2NyaXB0IGNvbWJpbmVzIHRoZSBuZXcgdGFuZGVtIGdyb3VuZCB0ZW1wZXJhdHVyZSBkYXRhIHRvZ2V0aGVyIGZpcnN0LCBhbmQgdGhlbiBhcHBlbmRzIHRoaXMgY29tYmluZWQgbmV3IGRhdGEgdG8gdGhlIG9sZCBjb21waWxlZCBkYXRhLg0KDQpbKipXYXJuaW5nISoqXXsudW5kZXJsaW5lfSAqKuKAlC0qKiBUaGUgdHJlYXRtZW50IG9mIHRhbmRlbSBsb2dnZXJzIGlzIGN1cnJlbnRseSBvbmx5IHZhbGlkIGZvciB0YW5kZW0gR1JPVU5EIFRFTVBFUkFUVVJFIGxvZ2dlcnMhIQ0KDQpgYGB7cn0NCiMgQ3JlYXRlcyBhIGNvcHkgb2YgdGhlIGxpc3QgImNvbWJfZGF0YSIgdGhhdCBuZXcgdmVyc2lvbnMgb2YgdGhlIGNvbWJpbmVkIGRhdGEgdGliYmxlcw0KIyB3aWxsIGJlIGFzc2lnbmVkIHRvLg0KbmV3X2NvbWJfZGF0YSA8LSBjb21iX2RhdGENCg0KDQoNCiMgR2V0IHRoZSBsZXZlbHMgb2YgdGhlICJCb3JlaG9sZSIgY29sdW1uIGluICJpbmZvcm1hdGlvbiINCmJvcmVob2xlcyA8LSBpbmZvcm1hdGlvbiRCb3JlaG9sZSB8PiANCiAgYXMuZmFjdG9yKCkgfD4gDQogIGxldmVscygpIHw+IA0KICBhcy5jaGFyYWN0ZXIoKQ0KDQoNCiMgRm9yIGVhY2ggYm9yZWhvbGUgaW4gImluZm9ybWF0aW9uIi4uLg0KZm9yKGIgaW4gYm9yZWhvbGVzKSB7DQogIA0KICANCiAgIyBJc29sYXRlIHJvd3MgaW4gImluZm9ybWF0aW9uIiBwZXJ0YWluaW5nIHRvIHRoZSBzaXRlDQogIHdvcmtJbmZvQm9yZWhvbGUgPC0gaW5mb3JtYXRpb24gfD4gZmlsdGVyKEJvcmVob2xlID09IGIpDQogIA0KICANCiAgIyBJZiB0aGUgYm9yZWhvbGUgZG9lc24ndCBoYXZlIHRhbmRlbSBncm91bmQgdGVtcGVyYXR1cmUgbG9nZ2Vycy4uLg0KICBpZiAoc3VtKCFpcy5uYSh3b3JrSW5mb0JvcmVob2xlJFRhbmRlbV9Mb2dnZXJzKSkgPT0gMCkgew0KICAgIA0KICAgIA0KICAgIG1lc3NhZ2UocGFzdGUwKCJObyB0YW5kZW0gZ3JvdW5kIHRlbXBlcmF0dXJlIGRhdGEgZm9yICIsIGIsICIuIFVzaW5nIG5vbWluYWwgYXBwZW5kaW5nIG1ldGhvZC4iKSkNCiAgICANCiAgICANCiAgICAjIEZvciBlYWNoIGxvZ2dlciBhdCB0aGUgYm9yZWhvbGUuLi4NCiAgICBmb3IocmF3bmFtZSBpbiB3b3JrSW5mb0JvcmVob2xlJFJhd19GaWxlX05hbWUpIHsNCiAgICAgIA0KICAgICAgDQogICAgICAjIElzb2xhdGUgdGhlIGRhdGEgdHlwZXMgY29sbGVjdGVkIGJ5IHRoYXQgbG9nZ2VyDQogICAgICB3b3JrVHlwZXMgPC0gd29ya0luZm9Cb3JlaG9sZSB8PiANCiAgICAgICAgZmlsdGVyKFJhd19GaWxlX05hbWUgPT0gcmF3bmFtZSkgfD4gDQogICAgICAgIHNlbGVjdChUeXBlKSB8PiANCiAgICAgICAgdW5saXN0KCkgfD4gDQogICAgICAgIGxhcHBseShmdW5jdGlvbih4KSBzdHJzcGxpdCh4LCAiLCIpKSB8PiANCiAgICAgICAgdW5saXN0KCkgfD4gDQogICAgICAgIHVuaXF1ZSgpDQogICAgICANCiAgICAgIA0KICAgICAgIyBJc29sYXRlIHRoZSB3b3JraW5nIG5ldyBkYXRhIHRpYmJsZQ0KICAgICAgd29ya05ldyA8LSBuZXdfZGF0YVtbcmF3bmFtZV1dDQogICAgICANCiAgICAgIA0KICAgICAgIyBGb3IgZWFjaCB3b3JraW5nIGRhdGEgdHlwZS4uLg0KICAgICAgZm9yKHQgaW4gd29ya1R5cGVzKSB7DQogICAgICAgIA0KICAgICAgICANCiAgICAgICAgDQogICAgICAgIA0KICAgICAgICAjIFN1YnNldCB0aGUgbmV3IGRhdGEgdGliYmxlIHdpdGggcmVzcGVjdCB0byB0aGUgcmVsZXZhbnQgY29sdW1ucyB0byB0aGUgZGF0YSB0eXBlDQogICAgICAgIGlmKHQgPT0gImFpciIpIHsNCiAgICAgICAgICB3b3JrTmV3X3N1YnNldCA8LSB3b3JrTmV3IHw+IHNlbGVjdChEYXRlVGltZSwgQWlyKQ0KICAgICAgICB9IGVsc2UgaWYodCA9PSAic3VyZmFjZUludCIpIHsNCiAgICAgICAgICB3b3JrTmV3X3N1YnNldCA8LSB3b3JrTmV3IHw+IHNlbGVjdChEYXRlVGltZSwgU3VyZmFjZV9JbnQpDQogICAgICAgIH0gZWxzZSBpZih0ID09ICJzdXJmYWNlRXh0Iikgew0KICAgICAgICAgIHdvcmtOZXdfc3Vic2V0IDwtIHdvcmtOZXcgfD4gc2VsZWN0KERhdGVUaW1lLCBTdXJmYWNlX0V4dCkNCiAgICAgICAgfSBlbHNlIGlmKHQgPT0gImdyb3VuZCIpIHsNCiAgICAgICAgICB3b3JrTmV3X3N1YnNldCA8LSB3b3JrTmV3IHw+IHNlbGVjdChEYXRlVGltZSwgY29udGFpbnMoIkRlcHRoIikpDQogICAgICAgIH0NCiAgICAgICAgDQogICAgICAgIA0KICAgICAgIA0KICAgICAgICANCiAgICAgICAgIyBHZXRzIHRoZSB3b3JraW5nIGNvbWJpbmVkIGZpbGUgbmFtZQ0KICAgICAgICB3b3JrQ29tYl9maWxlbmFtZSA8LSB3b3JrSW5mb0JvcmVob2xlIHw+IA0KICAgICAgICAgIGZpbHRlcihSYXdfRmlsZV9OYW1lID09IHJhd25hbWUpIHw+IA0KICAgICAgICAgIHNlbGVjdChDb21iaW5lZF9GaWxlX05hbWUpIHw+DQogICAgICAgICAgYXMuY2hhcmFjdGVyKCkgfD4gDQogICAgICAgICAgbGFwcGx5KGZ1bmN0aW9uKHgpIHN0cnNwbGl0KHgsICIsIikgfD4gdW5saXN0KCkpIHw+IA0KICAgICAgICAgIHVubGlzdCgpIHw+IA0KICAgICAgICAgIHN0cl9zdWJzZXQodCkgfD4gDQogICAgICAgICAgcGFzdGUwKCIueGxzeCIpDQogICAgICAgICAgDQogICAgICAgIA0KICAgICAgICANCiAgICAgICAgDQogICAgICAgICMgSXNvbGF0ZXMgdGhlIHdvcmtpbmcgY29tYmluZWQgZGF0YSB0aWJibGUNCiAgICAgICAgd29ya0NvbWIgPC0gY29tYl9kYXRhW1t3b3JrQ29tYl9maWxlbmFtZV1dDQogICAgICAgIA0KICAgICAgICANCiAgICAgICAgDQogICAgICAgIA0KICAgICAgICAjIEpvaW4gdGhlIHN1YnNldCBuZXcgZGF0YSB0aWJibGUgdG8gdGhlIGlzb2xhdGVkIGNvbWJpbmVkIHRpYmJsZQ0KICAgICAgICBjb21iX2RhdGFbW3dvcmtDb21iX2ZpbGVuYW1lXV0gPC0gd29ya0NvbWIgfD4gZnVsbF9qb2luKHdvcmtOZXdfc3Vic2V0KQ0KICAgICAgICBjb21iX2RhdGFbW3dvcmtDb21iX2ZpbGVuYW1lXV0gPC0gY29tYl9kYXRhW1t3b3JrQ29tYl9maWxlbmFtZV1dIHw+DQogICAgICAgICAgZGlzdGluY3QoRGF0ZVRpbWUsIC5rZWVwX2FsbCA9IFRSVUUpDQogICAgICAgIA0KICAgICAgICANCiAgICAgICAgDQogICAgICB9DQogICAgICANCiAgICAgIA0KICAgIH0NCiAgICANCiAgICANCiAgICANCiAgICANCiAgICANCiAgICANCiAgfSBlbHNlIGlmKHN1bSghaXMubmEod29ya0luZm9Cb3JlaG9sZSRUYW5kZW1fTG9nZ2VycykpID4gMCkgew0KICAgIA0KICAgIA0KICAgIG1lc3NhZ2UocGFzdGUwKCJUYW5kZW0gZ3JvdW5kIHRlbXBlcmF0dXJlIGRhdGEgYXBwZW5kaW5nIG1ldGhvZCBpbml0aWF0ZWQgZm9yICIsIGIsICIuIikpDQogICAgDQogICAgDQogICAgIyBUaGUgcmF3IGZpbGUgbmFtZXMgZm9yIGxvZ2dlcnMgY29udGFpbmluZyB0YW5kZW0gZGF0YQ0KICAgIHRhbmRlbV9yYXdfZmlsZW5hbWVzIDwtIHdvcmtJbmZvQm9yZWhvbGUgfD4gDQogICAgICBmaWx0ZXIoIWlzLm5hKFRhbmRlbV9Mb2dnZXJzKSkgfD4gDQogICAgICBzZWxlY3QoUmF3X0ZpbGVfTmFtZSkgfD4gDQogICAgICB1bmxpc3QoKQ0KICAgIA0KICAgIGlmKGxlbmd0aCh0YW5kZW1fcmF3X2ZpbGVuYW1lcykgPCAyKSB7DQogICAgICBzdG9wKHBhc3RlMCgiT25seSBvbmUgdGFuZGVtIGZpbGUgd2FzIGRldGVjdGVkIGZvciAiLCBiLCIuIFR3byB0YW5kZW0gZmlsZXMgYXJlIG5lZWRlZCB0bw0KICAgICAgZmluaXNoIGFwcGVuZGluZy4gUGxlYXNlIG1hbnVhbGx5IGFwcGVuZCB0aGVzZSBkYXRhIGluc3RlYWQuIikpDQogICAgfQ0KICAgIA0KICAgIA0KICAgIA0KICAgICMgQ3JlYXRlIGEgbmV3IHRlbXBvcmFyeSB0aWJibGUgYnkgYWRkaW5nIHRoZSBjb2x1bW5zIG9mIGJvdGggdGFuZGVtIGZpbGVzIHRvZ2V0aGVyDQogICAgdG9fYXBwZW5kIDwtIGZ1bGxfam9pbihuZXdfZGF0YVtbIHRhbmRlbV9yYXdfZmlsZW5hbWVzWzFdIF1dLCBuZXdfZGF0YVtbIHRhbmRlbV9yYXdfZmlsZW5hbWVzWzJdIF1dKQ0KICAgIA0KICAgIA0KICAgIA0KICAgICMgQXBwZW5kIHRoZSB0ZW1wb3JhcnkgdGliYmxlICJ0b19hcHBlbmQiIHRvIHRoZSBjb21iaW5lZCBncm91bmQgZmlsZQ0KICAgIA0KICAgIGZvcihpIGluIGNvbWJfZGF0YSkgew0KICAgICAgaWYobnJvdyhpKSA9PSAwKSB7DQogICAgICAgIGlbLCJEYXRlVGltZSJdID0gYXMuUE9TSVhjdChpWywiRGF0ZVRpbWUiXSkNCiAgICAgIH0NCiAgICB9DQogICAgDQogICAgYXBwZW5kZWRfZ3JvdW5kIDwtIGNvbWJfZGF0YVtbcGFzdGUwKCJncm91bmRjb21iaW5lZF8iLCBiLCAiLnhsc3giKV1dIHw+DQogICAgICBmdWxsX2pvaW4oeSA9IHRvX2FwcGVuZCkgfD4gDQogICAgICBzZWxlY3Qoc3RhcnRzX3dpdGgoIkRhdGVUaW1lIikgfCBzdGFydHNfd2l0aCgiRGVwdGgiKSkNCiAgICBjb21iX2RhdGFbW3Bhc3RlMCgiZ3JvdW5kY29tYmluZWRfIiwgYiwgIi54bHN4IildXSA8LSBhcHBlbmRlZF9ncm91bmQNCiAgICBjb21iX2RhdGFbW3Bhc3RlMCgiZ3JvdW5kY29tYmluZWRfIiwgYiwgIi54bHN4IildXSA8LSBjb21iX2RhdGFbW3Bhc3RlMCgiZ3JvdW5kY29tYmluZWRfIiwgYiwgIi54bHN4IildXSB8PiANCiAgICAgIGRpc3RpbmN0KERhdGVUaW1lLCAua2VlcF9hbGwgPSBUUlVFKQ0KICAgIA0KICAgICMgSWYgYSAiYWlyY29tYmluZWQiIGZpbGUgZXhpc3RzLCBhcHBlbmQgdGhlIHRlbXBvcmFyeSB0aWJibGUgInRvX2FwcGVuZCIgdG8gDQogICAgIyB0aGUgImFpcmNvbWJpbmVkIiBkYXRhDQogICAgaWYoc3VtKHN0cl9zdGFydHMobmFtZXMoY29tYl9kYXRhKSwgImFpciIpKSA+IDApIHsNCiAgICAgIGFwcGVuZGVkX2FpciA8LSBjb21iX2RhdGFbW3Bhc3RlMCgiYWlyY29tYmluZWRfIiwgYiwgIi54bHN4IildXSB8PiANCiAgICAgICAgZnVsbF9qb2luKHkgPSB0b19hcHBlbmQpIHw+IA0KICAgICAgICBzZWxlY3Qoc3RhcnRzX3dpdGgoIkRhdGVUaW1lIikgfCBzdGFydHNfd2l0aCgiQWlyIikpIA0KICAgICAgY29tYl9kYXRhW1twYXN0ZTAoImFpcmNvbWJpbmVkXyIsIGIsICIueGxzeCIpXV0gPC0gYXBwZW5kZWRfYWlyDQogICAgICBjb21iX2RhdGFbW3Bhc3RlMCgiZ3JvdW5kY29tYmluZWRfIiwgYiwgIi54bHN4IildXSA8LSBjb21iX2RhdGFbW3Bhc3RlMCgiZ3JvdW5kY29tYmluZWRfIiwgYiwgIi54bHN4IildXSB8PiANCiAgICAgICAgZGlzdGluY3QoRGF0ZVRpbWUsIC5rZWVwX2FsbCA9IFRSVUUpDQogICAgfQ0KICAgIA0KICAgICMgSWYgYSAic3VyZmFjZUludCIgZmlsZSBleGlzdHMsIGFwcGVuZCB0aGUgdGVtcG9yYXJ5IHRpYmJsZSAidG9fYXBwZW5kIiB0byANCiAgICAjIHRoZSAic3VyZmFjZUludCIgZGF0YQ0KICAgIGlmKHN1bShzdHJfc3RhcnRzKG5hbWVzKGNvbWJfZGF0YSksICJzdXJmYWNlSW50IikpID4gMCkgew0KICAgICAgYXBwZW5kZWRfc3VyZmFjZUludCA8LSBjb21iX2RhdGFbW3Bhc3RlMCgic3VyZmFjZUludGNvbWJpbmVkXyIsIGIsICIueGxzeCIpXV0gfD4gDQogICAgICAgIGZ1bGxfam9pbih5ID0gdG9fYXBwZW5kKSB8PiANCiAgICAgICAgc2VsZWN0KHN0YXJ0c193aXRoKCJEYXRlVGltZSIpIHwgc3RhcnRzX3dpdGgoIlN1cmZhY2VfSW50IikpDQogICAgICBjb21iX2RhdGFbW3Bhc3RlMCgic3VyZmFjZUludGNvbWJpbmVkXyIsIGIsICIueGxzeCIpXV0gPC0gYXBwZW5kZWRfc3VyZmFjZUludA0KICAgICAgY29tYl9kYXRhW1twYXN0ZTAoImdyb3VuZGNvbWJpbmVkXyIsIGIsICIueGxzeCIpXV0gPC0gY29tYl9kYXRhW1twYXN0ZTAoImdyb3VuZGNvbWJpbmVkXyIsIGIsICIueGxzeCIpXV0gfD4gDQogICAgICAgIGRpc3RpbmN0KERhdGVUaW1lLCAua2VlcF9hbGwgPSBUUlVFKQ0KICAgIH0NCiAgICANCiAgICAjIElmIGEgInN1cmZhY2VFeHQiIGZpbGUgZXhpc3RzLCBhcHBlbmQgdGhlIHRlbXBvcmFyeSB0aWJibGUgInRvX2FwcGVuZCIgdG8gDQogICAgIyB0aGUgInN1cmZhY2VFeHQiIGRhdGENCiAgICBpZihzdW0oc3RyX3N0YXJ0cyhuYW1lcyhjb21iX2RhdGEpLCAic3VyZmFjZUV4dCIpKSA+IDApIHsNCiAgICAgIGFwcGVuZGVkX3N1cmZhY2VFeHQgPC0gY29tYl9kYXRhW1twYXN0ZTAoInN1cmZhY2VFeHRjb21iaW5lZF8iLCBiLCAiLnhsc3giKV1dIHw+IA0KICAgICAgICBmdWxsX2pvaW4oeSA9IHRvX2FwcGVuZCkgfD4gDQogICAgICAgIHNlbGVjdChzdGFydHNfd2l0aCgiRGF0ZVRpbWUiKSB8IHN0YXJ0c193aXRoKCJTdXJmYWNlX0V4dCIpKQ0KICAgICAgY29tYl9kYXRhW1twYXN0ZTAoInN1cmZhY2VFeHRjb21iaW5lZF8iLCBiLCAiLnhsc3giKV1dIDwtIGFwcGVuZGVkX3N1cmZhY2VFeHQNCiAgICAgIGNvbWJfZGF0YVtbcGFzdGUwKCJncm91bmRjb21iaW5lZF8iLCBiLCAiLnhsc3giKV1dIDwtIGNvbWJfZGF0YVtbcGFzdGUwKCJncm91bmRjb21iaW5lZF8iLCBiLCAiLnhsc3giKV1dIHw+IA0KICAgICAgICBkaXN0aW5jdChEYXRlVGltZSwgLmtlZXBfYWxsID0gVFJVRSkNCiAgICB9DQogICAgDQogICAgDQogICAgDQogICAgDQogICAgDQogICAgDQogIH0NCiAgDQogIHByaW50KHBhc3RlMCgiQVBQRU5ESU5HIENPTVBMRVRFOiAgIiwgYikpDQogIA0KfQ0KYGBgDQoNCiMjIDMuMTIpIERhdGEgRXhwb3J0aW5nDQoNCkV4cG9ydHMgdGhlIGRhdGEgdG8gdGhlIGRpcmVjdG9yeSBzZWxlY3RlZCBieSB0aGUgdXNlciB1c2luZyB0aGUgZGlhbG9ndWUgd2luZG93LiBUaGUgbmFtZSBvZiB0aGUgb3V0cHV0IGNvbWJpbmVkIGRhdGEgZmlsZSBET0VTIE5PVCBpbmNsdWRlIHRoZSBtb250aCBhbmQgeWVhciBvZiB0aGUgdXBkYXRlIGFuZCBuZWVkcyB0byBiZSBhZGRlZCBtYW51YWxseSBieSB0aGUgdXNlci4gRG9pbmcgdGhpcyBhdXRvbWF0aWNhbGx5IGlzIGFuIGFpbSBmb3IgYSBmdXR1cmUgdXBkYXRlLg0KDQpgYGB7cn0NCmZvcihpIGluIDE6bGVuZ3RoKGNvbWJfZGF0YSkpIHsNCiAgDQogIGZvcmV4cG9ydCA8LSBjb21iX2RhdGFbW2ldXQ0KICBuYW1lIDwtIG5hbWVzKGNvbWJfZGF0YSlbaV0gfD4gDQogICAgc3Ryc3BsaXQoIlxcLiIpIHw+IA0KICAgIHVubGlzdCgpICU+JQ0KICAgIC5bMV0NCiAgd3JpdGVfeGxzeChmb3JleHBvcnQsIHBhc3RlMChjaG9zZW5fb3V0cHV0X2RpcmVjdG9yeSwiLyIsIG5hbWUsICIueGxzeCIpKQ0KICANCiAgDQogIHByaW50KHBhc3RlMCgiRVhQT1JUQVRJT04gQ09NUExFVEU6ICAiLCBuYW1lKSkNCg0KfQ0KYGBgDQoNCiMgNCkgVXBjb21pbmcgVXBkYXRlcw0K