Takeaways from the book

Using AlienVault IP reputation Database

# make sure the packages for this chapter
# are installed, install if necessary
pkg <- c("ggplot2", "scales", "maptools",
"sp", "maps", "grid", "car" )
new.pkg <- pkg[!(pkg %in% installed.packages())]
if (length(new.pkg)) {
install.packages(new.pkg)
}

Data from: http://labs.alienvault.com/labs/index.php/projects/open-source-ip-reputation-portal/download-ip-reputation-database/

AlienVault provides this data in numerous formats free of charge. The version you work with is the OSSIM Format (http://reputation.alienvault.com/ reputation.data) since it provides the richest information of all the available formats

# URL for the AlienVault IP Reputation Database (OSSIM format)
# storing the URL in a variable makes it easier to modify later
# if it changes. NOTE: we are using a specific version of the data
# in these examples, so we are pulling it from an alternate
# book-specific location.
  avURL <-
"http://datadrivensecurity.info/book/ch03/data/reputation.data"
# use relative path for the downloaded data
  avRep <- "data/reputation.data"
# using an if{}-wrapped test with download.file() vs read.xxx()
# directly avoids having to re-download a 16MB file every time
# we run the script
  if (file.access(avRep)) {
download.file(avURL, avRep)
}
# read in the IP reputation db into a data frame
# this data file has no header, so set header=FALSE
  av <- read.csv(avRep,sep="#", header=FALSE, stringsAsFactors = FALSE)
# assign more readable column names since we didn't pick
# any up from the header
  colnames(av) <- c("IP", "Reliability", "Risk", "Type",
"Country", "Locale", "Coords", "x")
  str(av) 
'data.frame':   258626 obs. of  8 variables:
 $ IP         : chr  "222.76.212.189" "222.76.212.185" "222.76.212.186" "5.34.246.67" ...
 $ Reliability: int  4 4 4 6 4 4 4 4 4 6 ...
 $ Risk       : int  2 2 2 3 5 2 2 2 2 3 ...
 $ Type       : chr  "Scanning Host" "Scanning Host" "Scanning Host" "Spamming" ...
 $ Country    : chr  "CN" "CN" "CN" "US" ...
 $ Locale     : chr  "Xiamen" "Xiamen" "Xiamen" "" ...
 $ Coords     : chr  "24.4797992706,118.08190155" "24.4797992706,118.08190155" "24.4797992706,118.08190155" "38.0,-97.0" ...
 $ x          : chr  "11" "11" "11" "12" ...
# get an overview of the data frame
# Risk calculation is ((asset * priority * reliability)/25)
# since x is treated as char we need to convert it back to numbers
  av$x <-  as.numeric(av$x)
NAs introduced by coercion
# take a quick look
  head(av)
# exploratory data analysis
  library(psych)
package <U+393C><U+3E31>psych<U+393C><U+3E32> was built under R version 3.3.3
Attaching package: <U+393C><U+3E31>psych<U+393C><U+3E32>

The following object is masked from <U+393C><U+3E31>package:randomForest<U+393C><U+3E32>:

    outlier

The following objects are masked from <U+393C><U+3E31>package:ggplot2<U+393C><U+3E32>:

    %+%, alpha
  describe(av$Reliability)
   vars      n mean   sd median trimmed mad min max range skew kurtosis se
X1    1 258626  2.8 1.13      2     2.7   0   1  10     9 1.23      2.8  0
  describe(av$Risk)
   vars      n mean   sd median trimmed mad min max range skew kurtosis se
X1    1 258626 2.22 0.53      2    2.09   0   1   7     6 2.62     7.25  0
  
table(av$Country)

         A1    A2    AE    AL    AM    AN    AO    AR    AT    AU    AW    AX    AZ 
10055   267     2  1827     4     6     3   256  3046    51   155   257     1     7 
   BA    BD    BE    BF    BG    BH    BJ    BM    BO    BR    BS    BY    BZ    CA 
   15   535   834     1   871     1     3     1    10  3811     2    35     8  3051 
   CD    CH    CI    CL    CM    CN    CO    CR    CY    CZ    DE    DJ    DK    DO 
    1   333     3  1896     1 68583    33     1   295   928  9953     1    54   259 
   DZ    EC    EE    EG    ES    EU    FI    FJ    FR    GA    GB    GE    GH    GR 
    4   278   274  1452  1929   129   286     1  5449     1  6293    34     3   557 
   GT    HK    HN    HR    HU    ID    IE    IL    IM    IN    IQ    IR    IS    IT 
  261  2361     2    25  1636  1378   201   854     1  5480     2   866   516  2448 
   JM    JO    JP    KE    KG    KH    KR    KW    KY    KZ    LA    LB    LC    LK 
    2    16  1811     4     2   261  3101   269     1   313     1   517     1     5 
   LT    LU    LV    LY    MA    MC    MD    ME    MK    MN    MO    MQ    MR    MT 
   65   283  1056     2    13     3   788     3    14     7     1     5     1     3 
   MU    MX    MY    NG    NI    NL    NO    NP    NZ    PA    PE    PH    PK    PL 
    5  3039   664     5   256  7931   958     2   272   554   295   552  1309  1610 
   PR    PS    PT    PY    QA    RO    RS    RU    RW    SA    SB    SC    SD    SE 
    7    23   847   259     3  3274  1323  6346     2   582     1     1     3   130 
   SG    SI    SK    SM    SN    SZ    TG    TH    TJ    TN    TR    TT    TW    TZ 
  868    20    31     3     1     1     1  2572     2    10 13958     2  4399     1 
   UA    UG    US    UY    VC    VE    VG    VI    VN    YE    ZA    ZM    ZW 
 3443     1 50387   516     1  1589    59     1  1203     2   573     1     3 
table(av$Type)

                 APT;Malware Domain                                 C&C 
                                  1                                 610 
                 C&C;Malware Domain                      C&C;Malware IP 
                                 31                                  20 
                  C&C;Scanning Host                      Malicious Host 
                                  7                                3770 
      Malicious Host;Malware Domain           Malicious Host;Malware IP 
                                  4                                   2 
       Malicious Host;Scanning Host                Malware distribution 
                                163                                   1 
Malware distribution;Malicious Host     Malware distribution;Malware IP 
                                  1                                   4 
                     Malware Domain                  Malware Domain;C&C 
                               9274                                  25 
      Malware Domain;Malicious Host           Malware Domain;Malware IP 
                                  4                                 173 
       Malware Domain;Scanning Host             Malware Domain;Spamming 
                                 39                                   2 
                         Malware IP                      Malware IP;C&C 
                               6470                                   2 
          Malware IP;Malicious Host           Malware IP;Malware Domain 
                                  1                                  57 
           Malware IP;Scanning Host                 Malware IP;Spamming 
                                  8                                   7 
                      Scanning Host                   Scanning Host;C&C 
                             234180                                   2 
       Scanning Host;Malicious Host        Scanning Host;Malware Domain 
                                215                                  19 
           Scanning Host;Malware IP              Scanning Host;Spamming 
                                  7                                   7 
                           Spamming             Spamming;Malware Domain 
                               3487                                   5 
                Spamming;Malware IP              Spamming;Scanning Host 
                                  4                                  24 
# require object: av (3-4)
# We need to load the ggplot2 library to make the graphs
# See corresponding output in Figure 3-2
# NOTE: Graphing the data shows there are a number of entries without
# a corresponding country code, hence the blank entry
  library(ggplot2)
  library(dplyr)
# Bar graph of counts (sorted) by Country (top 20)
# get the top 20 countries' names
  # country.top20 <- as.data.frame(names(summary(av$Country))[1:20])
  
  countrytop20 <-  av %>% group_by(Country) %>% summarize(tcount = n())
  countrytop20 <-  arrange(countrytop20, desc(tcount))
  countrytop20 <-  countrytop20[1:20,]
# give ggplot a subset of our data (the top 20 countries)
# map the x value to a sorted count of country
  gg <- ggplot(data = countrytop20 ,  aes(x = reorder(Country, tcount), y = tcount))
# tell ggplot we want a bar chart
  gg <- gg + geom_bar(fill = "#000099", stat = "identity")
# ensure we have decent labels
  gg <- gg + labs(title = "Country Counts", x = "Country", y = "Count")
# rotate the chart to make this one more readable
  gg <- gg + coord_flip()
  gg# remove "chart junk"

  gg <- gg + theme(panel.grid = element_blank(),
  panel.background = element_blank())
# display the image
  gg

  gg <- ggplot(data=av, aes(x = Risk))
  gg <- gg + geom_bar(fill = "#000099")
# force an X scale to be just the limits of the data
# and to be discrete vs continuous
  gg <- gg + scale_x_discrete(limits=seq(max(av$Risk)))
  gg <- gg + labs(title="'Risk' Counts", x="Risk Score", y="Count")
  gg <- gg + theme(panel.grid=element_blank(),
  panel.background=element_blank())
  print(gg)

# requires packages: ggplot2
# require object: av (3-4)
# See corresponding output in Figure 3-4
# Bar graph of counts by Reliability
  gg <- ggplot(data=av, aes(x = Reliability))
  gg <- gg + geom_bar(fill ="#000099")
  gg <- gg + scale_x_discrete(limits = seq(max(av$Reliability)))
  gg <- gg + labs(title =  "'Reliabiity' Counts",  x = "Reliability Score",
y ="Count")
  gg <- gg + theme(panel.grid = element_blank(),
  panel.background = element_blank())
  print(gg)

# require object: av (3-4)
countrytop20$pcnt <- countrytop20$tcount/nrow(av)
# and print it
print(countrytop20[,c(1,3)])

HeatMaps

# require object: av (3-4)
# print table
# graphical view of levelplot
# need to use levelplot function from lattice package
  library(lattice)
# cast the table into a data frame
  rr.df = data.frame(table(av$Risk, av$Reliability))
# set the column names since table uses "Var1" and "Var2"
  colnames(rr.df) <- c("Risk", "Reliability", "Freq")
# now create a level plot with readable labels
  levelplot(Freq~Risk*Reliability, data = rr.df, main = "Risk ~ Reliabilty",
  ylab = "Reliability", xlab = "Risk", shrink = c(0.5, 1),
  col.regions = colorRampPalette(c("#F5F5F5", "#01665E"))(20))

# require object: av (3-4), lattice (3-19)
# See corresponding output in Figure 3-11
# Create a new varible called "simpletype"
# replacing mutiple categories with label of "Multiples"
  av$simpletype <- as.character(av$Type)
# Group all nodes with mutiple categories into a new category
  av$simpletype[grep(';', av$simpletype)] <- "Multiples"
# Turn it into a factor again
  av$simpletype <- factor(av$simpletype)
  rrt.df = data.frame(table(av$Risk, av$Reliability, av$simpletype))
  colnames(rrt.df) <- c("Risk", "Reliability", "simpletype", "Freq")
  levelplot(Freq ~ Reliability*Risk|simpletype, data = rrt.df,
  main = "Risk ~ Reliabilty | Type", ylab = "Risk",
  xlab = "Reliability", shrink = c(0.5, 1),
  col.regions = colorRampPalette(c("#F5F5F5","#01665E"))(20))

# if we exclude Scanning host
  rrt.df <- subset(rrt.df, simpletype != "Scanning Host")
  levelplot(Freq ~ Reliability*Risk|simpletype, data = rrt.df,
  main = "Risk ~ Reliabilty | Type", ylab = "Risk",
  xlab = "Reliability", shrink = c(0.5, 1),
  col.regions = colorRampPalette(c("#F5F5F5","#01665E"))(20))

# Listing 4-1
# requires packages: bitops
  library(bitops) 
# load the bitops functions
# Define functions for converting IP addresses to/from integers
# take an IP address string in dotted octets (e.g.
#"192.168.0.1")
# take an IP address string in dotted octets (e.g.
#"192.168.0.1")
# and convert it to a 32-bit long integer (e.g. 3232235521)
  ip2long <- function(ip) {
# convert string into vector of characters
  ips <- unlist(strsplit(ip, '.', fixed = TRUE))
# set up a function to bit-shift, then "OR" the octets
  octet <- function(x,y) bitOr(bitShiftL(x, 8), y)
# Reduce applys a function cumulatively left to right
  Reduce(octet, as.integer(ips))
  }
# take an 32-bit integer IP address (e.g. 3232235521)
# and convert it to a (e.g. "192.168.0.1").
 long2ip <- function(longip) {
# set up reversing bit manipulation
  octet <- function(nbits) bitAnd(bitShiftR(longip, nbits),0xFF)
# Map applys a function to each element of the argument
# paste converts arguments to character and concatenates them
  paste(Map(octet, c(24,16,8,0)), sep="", collapse=".")
}
#Test the functions 
  ip2long("192.168.0.1")
[1] 3232235521
  long2ip(3232235521)
[1] "192.168.0.1"
# Listing 4-2
# requires packages: bitops
# requires all objects from 4-1
# Define function to test for IP CIDR membership
# take an IP address (string) and a CIDR (string) and
# return whether the given IP address is in the CIDR range
  ip.is.in.cidr <- function(ip, cidr) {
    long.ip <- ip2long(ip)
    cidr.parts <- unlist(strsplit(cidr, "/"))
    cidr.range <- ip2long(cidr.parts[1])
    cidr.mask <- bitShiftL(bitFlip(0),
    (32-as.integer(cidr.parts[2])))
    return(bitAnd(long.ip, cidr.mask) == bitAnd(cidr.range,
    cidr.mask))
  }
ip.is.in.cidr("10.0.1.15","10.0.1.3/24")
[1] TRUE
ip.is.in.cidr("10.0.1.15","10.0.2.255/24")
[1] FALSE

Converting Geo Coordinate string in AV to lat long

# Listing 4-3
# R code to extract longitude/latitude pairs from AlienVault data
# read in the AlienVault reputation data (see Chapter 3)
  avRep <- "data/reputation.data"
  av.df <- read.csv(avRep, sep = "#", header = FALSE)
  colnames(av.df) <- c("IP", "Reliability", "Risk", "Type",
      "Country", "Locale", "Coords", "x")
# create a vector of lat/long data by splitting on ","
  av.coords.vec <- unlist(strsplit(as.character(av.df$Coords), ","))
# convert the vector in a 2-column matrix
  av.coords.mat <- matrix(av.coords.vec, ncol = 2, byrow = TRUE)
# project into a data frame
  av.coords.df <- as.data.frame(av.coords.mat)
# name the columns
  colnames(av.coords.df) <- c("lat","long")
# convert the characters to numeric values
  av.df$long <- as.double(as.character(av.coords.df$long))
  av.df$lat <- as.double(as.character(av.coords.df$lat))

Then visualize these

# Listing 4-4
# requires packages: ggplot2, maps, RColorBrewer
# requires object: av.coords.df (4-3)
# generates Figure 4-2
# R code to extract longitude/latitude pairs from AlienVault data
# need plotting and mapping functions
  library(ggplot2)
  library(maps)
package <U+393C><U+3E31>maps<U+393C><U+3E32> was built under R version 3.3.3
Attaching package: <U+393C><U+3E31>maps<U+393C><U+3E32>

The following object is masked from <U+393C><U+3E31>package:purrr<U+393C><U+3E32>:

    map
  library(RColorBrewer)
  library(scales)

Attaching package: <U+393C><U+3E31>scales<U+393C><U+3E32>

The following objects are masked from <U+393C><U+3E31>package:psych<U+393C><U+3E32>:

    alpha, rescale

The following object is masked from <U+393C><U+3E31>package:purrr<U+393C><U+3E32>:

    discard

The following object is masked from <U+393C><U+3E31>package:readr<U+393C><U+3E32>:

    col_factor
# extract a color pallete from the RColorBrewer package
  set2 <- brewer.pal(8,"Set2")
# extract the polygon information for the world map, minus Antarctica
  world <- map_data('world')
  world <- subset(world, region != "Antarctica")
# plot the map with the points marking lat/lon of the geocoded entries
# Chapter 5 examples explain mapping in greater detail
  gg <- ggplot(height = 600, width = 1200)
  gg <- gg + geom_polygon(data = world, aes(long, lat, group = group),
      fill = "white")
  gg <- gg + geom_point(data = av.df, aes(x = long, y = lat, 
              size = Risk), color = set2[2], alpha = 0.1) +
              scale_color_brewer(palette = "Spectral")
  gg <- gg + labs(x = "", y = "")
  gg <- gg + theme(panel.background = element_rect(fill = alpha(set2[3],0.2),
      colour = 'white'))
  gg

  # ggsave('data/test.pdf', units = "in", width = 20, height = 30)
  # dev.off()

Demonstration of Graph Theory Visualization

# Listing 4-11
# Retrieve and read ZeuS blocklist data into R
  zeusURL <- "https://zeustracker.abuse.ch/blocklist.php?download=ipblocklist"
  zeusData <- "data/zeus.csv"
  if (file.access(zeusData)) {
    # need to change download method for universal "https" compatibility
      download.file(zeusURL, zeusData, method = "curl")
  }
# read in the ZeuS table; skip junk; no header; assign colnames
  zeus <- read.table(zeusData, skip = 5, header = FALSE, col.names = c("IP"))
# Listing 4-15
# requires objects: BulkOrigin() & BulkPeer() from book's web site
# require package: igraph (4-11)
# create connected network of ZeuS IPs, ASNs, and ASN peers
# generates Figure 4-9
library(igraph)

Attaching package: <U+393C><U+3E31>igraph<U+393C><U+3E32>

The following objects are masked from <U+393C><U+3E31>package:lubridate<U+393C><U+3E32>:

    %--%, union

The following objects are masked from <U+393C><U+3E31>package:plotly<U+393C><U+3E32>:

    %>%, groups

The following object is masked from <U+393C><U+3E31>package:DT<U+393C><U+3E32>:

    %>%

The following object is masked from <U+393C><U+3E31>package:leaflet<U+393C><U+3E32>:

    %>%

The following objects are masked from <U+393C><U+3E31>package:purrr<U+393C><U+3E32>:

    %>%, compose, simplify

The following objects are masked from <U+393C><U+3E31>package:tidyr<U+393C><U+3E32>:

    %>%, crossing

The following object is masked from <U+393C><U+3E31>package:tibble<U+393C><U+3E32>:

    as_data_frame

The following objects are masked from <U+393C><U+3E31>package:dplyr<U+393C><U+3E32>:

    %>%, as_data_frame, groups, union

The following object is masked from <U+393C><U+3E31>package:stringr<U+393C><U+3E32>:

    %>%

The following object is masked from <U+393C><U+3E31>package:urltools<U+393C><U+3E32>:

    path

The following objects are masked from <U+393C><U+3E31>package:stats<U+393C><U+3E32>:

    decompose, spectrum

The following object is masked from <U+393C><U+3E31>package:base<U+393C><U+3E32>:

    union
library(plyr)
--------------------------------------------------------------------------------------
You have loaded plyr after dplyr - this is likely to cause problems.
If you need functions from both plyr and dplyr, please load plyr first, then dplyr:
library(plyr); library(dplyr)
--------------------------------------------------------------------------------------

Attaching package: <U+393C><U+3E31>plyr<U+393C><U+3E32>

The following object is masked from <U+393C><U+3E31>package:maps<U+393C><U+3E32>:

    ozone

The following object is masked from <U+393C><U+3E31>package:lubridate<U+393C><U+3E32>:

    here

The following objects are masked from <U+393C><U+3E31>package:plotly<U+393C><U+3E32>:

    arrange, mutate, rename, summarise

The following object is masked from <U+393C><U+3E31>package:purrr<U+393C><U+3E32>:

    compact

The following objects are masked from <U+393C><U+3E31>package:dplyr<U+393C><U+3E32>:

    arrange, count, desc, failwith, id, mutate, rename, summarise, summarize
library(colorspace)
# Open the file
  zeus <- read.table("data/zeus-book.csv", skip = 5, header = FALSE,
  col.names = c("IP"))
  ips <- as.character(zeus$IP)
  
# HELPER FUNCTION MENTIONED IN THE BOOK
# BUT NOT IN THE PRINTED LISTINGS
  trim <- function(x) gsub("^\\s+|\\s+$", "", x)
# HELPER FUNCTION MENTIONED IN THE BOOK
# BUT NOT IN THE PRINTED LISTINGS
  BulkOrigin <- function(ip.list,host = "v4.whois.cymru.com", port = 43) {
 
  # setup query
  cmd <- "begin\nverbose\n" 
  ips <- paste(unlist(ip.list), collapse = "\n")
  cmd <- sprintf("%s%s\nend\n",cmd,ips)
  
  # setup connection and post query 
  con <- socketConnection(host = host, port = port, blocking = TRUE,open = "r+")  
  cat(cmd,file = con)
  response <- readLines(con)
  close(con)
  
  # trim header, split fields and convert results
  response <- response[2:length(response)]
  response <- laply(response,.fun = function(n) {
    sapply(strsplit(n,"|",fixed = TRUE),trim)
    })
  response <- adply(response,c(1))
  response <- subset(response, select = -c(X1) )
  names(response) = c("AS","IP","BGP.Prefix","CC",
                      "Registry","Allocated","AS.Name")
  
  return(response)
  
}
# HELPER FUNCTION MENTIONED IN THE BOOK
# BUT NOT IN THE PRINTED LISTINGS
  BulkPeer <- function(ip.list,host = "v4-peer.whois.cymru.com", port = 43) {
   
  # setup query
  cmd <- "begin\nverbose\n" 
  ips <- paste(unlist(ip.list), collapse = "\n")
  cmd <- sprintf("%s%s\nend\n",cmd,ips)
  
  # setup connection and post query
  con <- socketConnection(host = host,port = port,blocking = TRUE, open = "r+")  
  cat(cmd,file = con)
  response <- readLines(con)
  close(con)
  
  # trim header, split fields and convert results
  response <- response[2:length(response)]
  response <- laply(response,function(n) {
    sapply(strsplit(n,"|",fixed = TRUE),trim)
  })  
  response <- adply(response,c(1))
  response <- subset(response, select = -c(X1) )
  names(response) <- c("Peer.AS","IP","BGP.Prefix","CC",
                       "Registry","Allocated","Peer.AS.Name")
  return(response)
  
}
# HELPER FUNCTION MENTIONED IN THE BOOK
# BUT NOT IN THE PRINTED LISTINGS
  BulkOriginASN <- function(asn.list,host="v4.whois.cymru.com", port = 43) {
  
  # setup query
  cmd <- "begin\nverbose\n" 
  ips <- paste(unlist(asn.list), collapse = "\n")
  cmd <- sprintf("%s%s\nend\n",cmd,ips)
  
  # setup connection and post query
  con <- socketConnection(host = host,port = port,blocking = TRUE,open = "r+")  
  cat(cmd,file = con)
  response <- readLines(con)
  close(con)
  
  # trim header, split fields and convert results
  
  response <- response[2:length(response)]
  response <- laply(response,.fun = function(n) {
    sapply(strsplit(n,"|",fixed = TRUE),trim)
  })
  
  response <- adply(response,c(1))
  response <- subset(response, select = -c(X1) )
  names(response) <- c("AS","CC","Registry","Allocated","AS.Name")
  
  return(response)
  
  }
  g <- graph.empty()
  g <- g + vertices(ips, size = 3, color = set2[4], group = 1)
  origin <- BulkOrigin(ips)
  peers <- BulkPeer(ips)
# add ASN origin & peer vertices
  g <- g + vertices(unique(c(peers$Peer.AS, origin$AS)),
  size = 3, color = set2[2], group = 2)
# build IP->BGP edge list
  ip.edges <- lapply(ips, function(x) {
  iAS <- origin[origin$IP == x, ]$AS
  lapply(iAS,function(y){
  c(x, y)
  })
  })
  bgp.edges <- lapply(
  grep("NA",unique(origin$BGP.Prefix),value = TRUE,invert = TRUE),
  function(x) {
  startAS <- unique(origin[origin$BGP.Prefix == x,]$AS)
    lapply(startAS,function(z) {
    pAS <- peers[peers$BGP.Prefix == x,]$Peer.AS
    lapply(pAS,function(y) {
    c(z,y)
    })
  })
})
  
  g <- g + edges(unlist(ip.edges))
  g <- g + edges(unlist(bgp.edges))
  g <- delete.vertices(g, which(degree(g) < 1))
  g <- simplify(g, edge.attr.comb = list(weight = "sum"))
  E(g)$arrow.size <- 0
  V(g)[grep("\\.", V(g)$name)]$name = ""
  L <- layout.fruchterman.reingold(g, niter = 10000, area = 30*vcount(g)^2)
Argument `area' is deprecated and has no effect
  par(bg = 'white')
  plot(g, margin = 0, layout = L, vertex.label.dist = 0.5,
  vertex.label = NA,
  main = "ZeuS botnet ASN+Peer Network")

LS0tDQp0aXRsZTogIkRhdGEgRHJpdmVuIFNlY3VyaXR5ICINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KLS0tDQoNCiMgVGFrZWF3YXlzIGZyb20gdGhlIGJvb2sNCiBVc2luZyBBbGllblZhdWx0IElQIHJlcHV0YXRpb24gRGF0YWJhc2UNCiANCmBgYHtyfQ0KIyBtYWtlIHN1cmUgdGhlIHBhY2thZ2VzIGZvciB0aGlzIGNoYXB0ZXINCiMgYXJlIGluc3RhbGxlZCwgaW5zdGFsbCBpZiBuZWNlc3NhcnkNCnBrZyA8LSBjKCJnZ3Bsb3QyIiwgInNjYWxlcyIsICJtYXB0b29scyIsDQoic3AiLCAibWFwcyIsICJncmlkIiwgImNhciIgKQ0KbmV3LnBrZyA8LSBwa2dbIShwa2cgJWluJSBpbnN0YWxsZWQucGFja2FnZXMoKSldDQppZiAobGVuZ3RoKG5ldy5wa2cpKSB7DQppbnN0YWxsLnBhY2thZ2VzKG5ldy5wa2cpDQp9DQoNCmBgYA0KDQpEYXRhIGZyb206DQpodHRwOi8vbGFicy5hbGllbnZhdWx0LmNvbS9sYWJzL2luZGV4LnBocC9wcm9qZWN0cy9vcGVuLXNvdXJjZS1pcC1yZXB1dGF0aW9uLXBvcnRhbC9kb3dubG9hZC1pcC1yZXB1dGF0aW9uLWRhdGFiYXNlLw0KDQpBbGllblZhdWx0IHByb3ZpZGVzIHRoaXMgZGF0YSBpbiBudW1lcm91cyBmb3JtYXRzIGZyZWUgb2YNCmNoYXJnZS4gVGhlIHZlcnNpb24geW91IHdvcmsgd2l0aCBpcyB0aGUgT1NTSU0gRm9ybWF0IChodHRwOi8vcmVwdXRhdGlvbi5hbGllbnZhdWx0LmNvbS8NCnJlcHV0YXRpb24uZGF0YSkgc2luY2UgaXQgcHJvdmlkZXMgdGhlIHJpY2hlc3QgaW5mb3JtYXRpb24gb2YgYWxsIHRoZSBhdmFpbGFibGUgZm9ybWF0cw0KDQpgYGB7cn0NCg0KIyBVUkwgZm9yIHRoZSBBbGllblZhdWx0IElQIFJlcHV0YXRpb24gRGF0YWJhc2UgKE9TU0lNIGZvcm1hdCkNCiMgc3RvcmluZyB0aGUgVVJMIGluIGEgdmFyaWFibGUgbWFrZXMgaXQgZWFzaWVyIHRvIG1vZGlmeSBsYXRlcg0KIyBpZiBpdCBjaGFuZ2VzLiBOT1RFOiB3ZSBhcmUgdXNpbmcgYSBzcGVjaWZpYyB2ZXJzaW9uIG9mIHRoZSBkYXRhDQojIGluIHRoZXNlIGV4YW1wbGVzLCBzbyB3ZSBhcmUgcHVsbGluZyBpdCBmcm9tIGFuIGFsdGVybmF0ZQ0KIyBib29rLXNwZWNpZmljIGxvY2F0aW9uLg0KICBhdlVSTCA8LQ0KImh0dHA6Ly9kYXRhZHJpdmVuc2VjdXJpdHkuaW5mby9ib29rL2NoMDMvZGF0YS9yZXB1dGF0aW9uLmRhdGEiDQojIHVzZSByZWxhdGl2ZSBwYXRoIGZvciB0aGUgZG93bmxvYWRlZCBkYXRhDQogIGF2UmVwIDwtICJkYXRhL3JlcHV0YXRpb24uZGF0YSINCiMgdXNpbmcgYW4gaWZ7fS13cmFwcGVkIHRlc3Qgd2l0aCBkb3dubG9hZC5maWxlKCkgdnMgcmVhZC54eHgoKQ0KIyBkaXJlY3RseSBhdm9pZHMgaGF2aW5nIHRvIHJlLWRvd25sb2FkIGEgMTZNQiBmaWxlIGV2ZXJ5IHRpbWUNCiMgd2UgcnVuIHRoZSBzY3JpcHQNCiAgaWYgKGZpbGUuYWNjZXNzKGF2UmVwKSkgew0KZG93bmxvYWQuZmlsZShhdlVSTCwgYXZSZXApDQp9DQpgYGANCg0KYGBge3J9DQojIHJlYWQgaW4gdGhlIElQIHJlcHV0YXRpb24gZGIgaW50byBhIGRhdGEgZnJhbWUNCiMgdGhpcyBkYXRhIGZpbGUgaGFzIG5vIGhlYWRlciwgc28gc2V0IGhlYWRlcj1GQUxTRQ0KICBhdiA8LSByZWFkLmNzdihhdlJlcCxzZXA9IiMiLCBoZWFkZXI9RkFMU0UsIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRSkNCiMgYXNzaWduIG1vcmUgcmVhZGFibGUgY29sdW1uIG5hbWVzIHNpbmNlIHdlIGRpZG4ndCBwaWNrDQojIGFueSB1cCBmcm9tIHRoZSBoZWFkZXINCiAgY29sbmFtZXMoYXYpIDwtIGMoIklQIiwgIlJlbGlhYmlsaXR5IiwgIlJpc2siLCAiVHlwZSIsDQoiQ291bnRyeSIsICJMb2NhbGUiLCAiQ29vcmRzIiwgIngiKQ0KICBzdHIoYXYpIA0KIyBnZXQgYW4gb3ZlcnZpZXcgb2YgdGhlIGRhdGEgZnJhbWUNCiMgUmlzayBjYWxjdWxhdGlvbiBpcyAoKGFzc2V0ICogcHJpb3JpdHkgKiByZWxpYWJpbGl0eSkvMjUpDQpgYGANCg0KYGBge3J9DQoNCiMgc2luY2UgeCBpcyB0cmVhdGVkIGFzIGNoYXIgd2UgbmVlZCB0byBjb252ZXJ0IGl0IGJhY2sgdG8gbnVtYmVycw0KICBhdiR4IDwtICBhcy5udW1lcmljKGF2JHgpDQojIHRha2UgYSBxdWljayBsb29rDQogIGhlYWQoYXYpDQpgYGANCg0KYGBge3J9DQojIGV4cGxvcmF0b3J5IGRhdGEgYW5hbHlzaXMNCiAgbGlicmFyeShwc3ljaCkNCiAgZGVzY3JpYmUoYXYkUmVsaWFiaWxpdHkpDQogIGRlc2NyaWJlKGF2JFJpc2spDQoNCiAgDQpgYGANCg0KYGBge3J9DQp0YWJsZShhdiRDb3VudHJ5KQ0KdGFibGUoYXYkVHlwZSkNCmBgYA0KDQpgYGB7cn0NCiMgcmVxdWlyZSBvYmplY3Q6IGF2ICgzLTQpDQojIFdlIG5lZWQgdG8gbG9hZCB0aGUgZ2dwbG90MiBsaWJyYXJ5IHRvIG1ha2UgdGhlIGdyYXBocw0KIyBTZWUgY29ycmVzcG9uZGluZyBvdXRwdXQgaW4gRmlndXJlIDMtMg0KIyBOT1RFOiBHcmFwaGluZyB0aGUgZGF0YSBzaG93cyB0aGVyZSBhcmUgYSBudW1iZXIgb2YgZW50cmllcyB3aXRob3V0DQojIGEgY29ycmVzcG9uZGluZyBjb3VudHJ5IGNvZGUsIGhlbmNlIHRoZSBibGFuayBlbnRyeQ0KICBsaWJyYXJ5KGdncGxvdDIpDQogIGxpYnJhcnkoZHBseXIpDQojIEJhciBncmFwaCBvZiBjb3VudHMgKHNvcnRlZCkgYnkgQ291bnRyeSAodG9wIDIwKQ0KIyBnZXQgdGhlIHRvcCAyMCBjb3VudHJpZXMnIG5hbWVzDQogICMgY291bnRyeS50b3AyMCA8LSBhcy5kYXRhLmZyYW1lKG5hbWVzKHN1bW1hcnkoYXYkQ291bnRyeSkpWzE6MjBdKQ0KICANCiAgY291bnRyeXRvcDIwIDwtICBhdiAlPiUgZ3JvdXBfYnkoQ291bnRyeSkgJT4lIHN1bW1hcml6ZSh0Y291bnQgPSBuKCkpDQogIGNvdW50cnl0b3AyMCA8LSAgYXJyYW5nZShjb3VudHJ5dG9wMjAsIGRlc2ModGNvdW50KSkNCiAgY291bnRyeXRvcDIwIDwtICBjb3VudHJ5dG9wMjBbMToyMCxdDQojIGdpdmUgZ2dwbG90IGEgc3Vic2V0IG9mIG91ciBkYXRhICh0aGUgdG9wIDIwIGNvdW50cmllcykNCiMgbWFwIHRoZSB4IHZhbHVlIHRvIGEgc29ydGVkIGNvdW50IG9mIGNvdW50cnkNCiAgZ2cgPC0gZ2dwbG90KGRhdGEgPSBjb3VudHJ5dG9wMjAgLCAgYWVzKHggPSByZW9yZGVyKENvdW50cnksIHRjb3VudCksIHkgPSB0Y291bnQpKQ0KDQojIHRlbGwgZ2dwbG90IHdlIHdhbnQgYSBiYXIgY2hhcnQNCiAgZ2cgPC0gZ2cgKyBnZW9tX2JhcihmaWxsID0gIiMwMDAwOTkiLCBzdGF0ID0gImlkZW50aXR5IikNCg0KIyBlbnN1cmUgd2UgaGF2ZSBkZWNlbnQgbGFiZWxzDQogIGdnIDwtIGdnICsgbGFicyh0aXRsZSA9ICJDb3VudHJ5IENvdW50cyIsIHggPSAiQ291bnRyeSIsIHkgPSAiQ291bnQiKQ0KIyByb3RhdGUgdGhlIGNoYXJ0IHRvIG1ha2UgdGhpcyBvbmUgbW9yZSByZWFkYWJsZQ0KICBnZyA8LSBnZyArIGNvb3JkX2ZsaXAoKQ0KICBnZyMgcmVtb3ZlICJjaGFydCBqdW5rIg0KICBnZyA8LSBnZyArIHRoZW1lKHBhbmVsLmdyaWQgPSBlbGVtZW50X2JsYW5rKCksDQogIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCkpDQojIGRpc3BsYXkgdGhlIGltYWdlDQogIGdnDQoNCmBgYA0KYGBge3J9DQogIGdnIDwtIGdncGxvdChkYXRhPWF2LCBhZXMoeCA9IFJpc2spKQ0KICBnZyA8LSBnZyArIGdlb21fYmFyKGZpbGwgPSAiIzAwMDA5OSIpDQojIGZvcmNlIGFuIFggc2NhbGUgdG8gYmUganVzdCB0aGUgbGltaXRzIG9mIHRoZSBkYXRhDQojIGFuZCB0byBiZSBkaXNjcmV0ZSB2cyBjb250aW51b3VzDQogIGdnIDwtIGdnICsgc2NhbGVfeF9kaXNjcmV0ZShsaW1pdHM9c2VxKG1heChhdiRSaXNrKSkpDQogIGdnIDwtIGdnICsgbGFicyh0aXRsZT0iJ1Jpc2snIENvdW50cyIsIHg9IlJpc2sgU2NvcmUiLCB5PSJDb3VudCIpDQogIGdnIDwtIGdnICsgdGhlbWUocGFuZWwuZ3JpZD1lbGVtZW50X2JsYW5rKCksDQogIHBhbmVsLmJhY2tncm91bmQ9ZWxlbWVudF9ibGFuaygpKQ0KICBwcmludChnZykNCmBgYA0KDQpgYGB7cn0NCiMgcmVxdWlyZXMgcGFja2FnZXM6IGdncGxvdDINCiMgcmVxdWlyZSBvYmplY3Q6IGF2ICgzLTQpDQojIFNlZSBjb3JyZXNwb25kaW5nIG91dHB1dCBpbiBGaWd1cmUgMy00DQojIEJhciBncmFwaCBvZiBjb3VudHMgYnkgUmVsaWFiaWxpdHkNCiAgZ2cgPC0gZ2dwbG90KGRhdGE9YXYsIGFlcyh4ID0gUmVsaWFiaWxpdHkpKQ0KICBnZyA8LSBnZyArIGdlb21fYmFyKGZpbGwgPSIjMDAwMDk5IikNCiAgZ2cgPC0gZ2cgKyBzY2FsZV94X2Rpc2NyZXRlKGxpbWl0cyA9IHNlcShtYXgoYXYkUmVsaWFiaWxpdHkpKSkNCiAgZ2cgPC0gZ2cgKyBsYWJzKHRpdGxlID0gICInUmVsaWFiaWl0eScgQ291bnRzIiwgIHggPSAiUmVsaWFiaWxpdHkgU2NvcmUiLA0KeSA9IkNvdW50IikNCiAgZ2cgPC0gZ2cgKyB0aGVtZShwYW5lbC5ncmlkID0gZWxlbWVudF9ibGFuaygpLA0KICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpKQ0KICBwcmludChnZykNCg0KYGBgDQoNCmBgYHtyfQ0KIyByZXF1aXJlIG9iamVjdDogYXYgKDMtNCkNCmNvdW50cnl0b3AyMCRwY250IDwtIGNvdW50cnl0b3AyMCR0Y291bnQvbnJvdyhhdikNCiMgYW5kIHByaW50IGl0DQpwcmludChjb3VudHJ5dG9wMjBbLGMoMSwzKV0pDQpgYGANCg0KSGVhdE1hcHMNCg0KYGBge3J9DQojIHJlcXVpcmUgb2JqZWN0OiBhdiAoMy00KQ0KIyBwcmludCB0YWJsZQ0KIyBncmFwaGljYWwgdmlldyBvZiBsZXZlbHBsb3QNCiMgbmVlZCB0byB1c2UgbGV2ZWxwbG90IGZ1bmN0aW9uIGZyb20gbGF0dGljZSBwYWNrYWdlDQogIGxpYnJhcnkobGF0dGljZSkNCiMgY2FzdCB0aGUgdGFibGUgaW50byBhIGRhdGEgZnJhbWUNCiAgcnIuZGYgPSBkYXRhLmZyYW1lKHRhYmxlKGF2JFJpc2ssIGF2JFJlbGlhYmlsaXR5KSkNCiMgc2V0IHRoZSBjb2x1bW4gbmFtZXMgc2luY2UgdGFibGUgdXNlcyAiVmFyMSIgYW5kICJWYXIyIg0KICBjb2xuYW1lcyhyci5kZikgPC0gYygiUmlzayIsICJSZWxpYWJpbGl0eSIsICJGcmVxIikNCiMgbm93IGNyZWF0ZSBhIGxldmVsIHBsb3Qgd2l0aCByZWFkYWJsZSBsYWJlbHMNCiAgbGV2ZWxwbG90KEZyZXF+UmlzaypSZWxpYWJpbGl0eSwgZGF0YSA9IHJyLmRmLCBtYWluID0gIlJpc2sgfiBSZWxpYWJpbHR5IiwNCiAgeWxhYiA9ICJSZWxpYWJpbGl0eSIsIHhsYWIgPSAiUmlzayIsIHNocmluayA9IGMoMC41LCAxKSwNCiAgY29sLnJlZ2lvbnMgPSBjb2xvclJhbXBQYWxldHRlKGMoIiNGNUY1RjUiLCAiIzAxNjY1RSIpKSgyMCkpDQoNCmBgYA0KDQpgYGB7cn0NCiMgcmVxdWlyZSBvYmplY3Q6IGF2ICgzLTQpLCBsYXR0aWNlICgzLTE5KQ0KIyBTZWUgY29ycmVzcG9uZGluZyBvdXRwdXQgaW4gRmlndXJlIDMtMTENCiMgQ3JlYXRlIGEgbmV3IHZhcmlibGUgY2FsbGVkICJzaW1wbGV0eXBlIg0KIyByZXBsYWNpbmcgbXV0aXBsZSBjYXRlZ29yaWVzIHdpdGggbGFiZWwgb2YgIk11bHRpcGxlcyINCiAgYXYkc2ltcGxldHlwZSA8LSBhcy5jaGFyYWN0ZXIoYXYkVHlwZSkNCiMgR3JvdXAgYWxsIG5vZGVzIHdpdGggbXV0aXBsZSBjYXRlZ29yaWVzIGludG8gYSBuZXcgY2F0ZWdvcnkNCiAgYXYkc2ltcGxldHlwZVtncmVwKCc7JywgYXYkc2ltcGxldHlwZSldIDwtICJNdWx0aXBsZXMiDQojIFR1cm4gaXQgaW50byBhIGZhY3RvciBhZ2Fpbg0KICBhdiRzaW1wbGV0eXBlIDwtIGZhY3RvcihhdiRzaW1wbGV0eXBlKQ0KICBycnQuZGYgPSBkYXRhLmZyYW1lKHRhYmxlKGF2JFJpc2ssIGF2JFJlbGlhYmlsaXR5LCBhdiRzaW1wbGV0eXBlKSkNCiAgY29sbmFtZXMocnJ0LmRmKSA8LSBjKCJSaXNrIiwgIlJlbGlhYmlsaXR5IiwgInNpbXBsZXR5cGUiLCAiRnJlcSIpDQogIGxldmVscGxvdChGcmVxIH4gUmVsaWFiaWxpdHkqUmlza3xzaW1wbGV0eXBlLCBkYXRhID0gcnJ0LmRmLA0KICBtYWluID0gIlJpc2sgfiBSZWxpYWJpbHR5IHwgVHlwZSIsIHlsYWIgPSAiUmlzayIsDQogIHhsYWIgPSAiUmVsaWFiaWxpdHkiLCBzaHJpbmsgPSBjKDAuNSwgMSksDQogIGNvbC5yZWdpb25zID0gY29sb3JSYW1wUGFsZXR0ZShjKCIjRjVGNUY1IiwiIzAxNjY1RSIpKSgyMCkpDQpgYGANCg0KYGBge3J9DQojIGlmIHdlIGV4Y2x1ZGUgU2Nhbm5pbmcgaG9zdA0KICBycnQuZGYgPC0gc3Vic2V0KHJydC5kZiwgc2ltcGxldHlwZSAhPSAiU2Nhbm5pbmcgSG9zdCIpDQogIGxldmVscGxvdChGcmVxIH4gUmVsaWFiaWxpdHkqUmlza3xzaW1wbGV0eXBlLCBkYXRhID0gcnJ0LmRmLA0KICBtYWluID0gIlJpc2sgfiBSZWxpYWJpbHR5IHwgVHlwZSIsIHlsYWIgPSAiUmlzayIsDQogIHhsYWIgPSAiUmVsaWFiaWxpdHkiLCBzaHJpbmsgPSBjKDAuNSwgMSksDQogIGNvbC5yZWdpb25zID0gY29sb3JSYW1wUGFsZXR0ZShjKCIjRjVGNUY1IiwiIzAxNjY1RSIpKSgyMCkpDQpgYGANCg0KYGBge3J9DQojIExpc3RpbmcgNC0xDQojIHJlcXVpcmVzIHBhY2thZ2VzOiBiaXRvcHMNCiAgbGlicmFyeShiaXRvcHMpIA0KIyBsb2FkIHRoZSBiaXRvcHMgZnVuY3Rpb25zDQojIERlZmluZSBmdW5jdGlvbnMgZm9yIGNvbnZlcnRpbmcgSVAgYWRkcmVzc2VzIHRvL2Zyb20gaW50ZWdlcnMNCiMgdGFrZSBhbiBJUCBhZGRyZXNzIHN0cmluZyBpbiBkb3R0ZWQgb2N0ZXRzIChlLmcuDQojIjE5Mi4xNjguMC4xIikNCiMgdGFrZSBhbiBJUCBhZGRyZXNzIHN0cmluZyBpbiBkb3R0ZWQgb2N0ZXRzIChlLmcuDQojIjE5Mi4xNjguMC4xIikNCiMgYW5kIGNvbnZlcnQgaXQgdG8gYSAzMi1iaXQgbG9uZyBpbnRlZ2VyIChlLmcuIDMyMzIyMzU1MjEpDQogIGlwMmxvbmcgPC0gZnVuY3Rpb24oaXApIHsNCiMgY29udmVydCBzdHJpbmcgaW50byB2ZWN0b3Igb2YgY2hhcmFjdGVycw0KICBpcHMgPC0gdW5saXN0KHN0cnNwbGl0KGlwLCAnLicsIGZpeGVkID0gVFJVRSkpDQojIHNldCB1cCBhIGZ1bmN0aW9uIHRvIGJpdC1zaGlmdCwgdGhlbiAiT1IiIHRoZSBvY3RldHMNCiAgb2N0ZXQgPC0gZnVuY3Rpb24oeCx5KSBiaXRPcihiaXRTaGlmdEwoeCwgOCksIHkpDQojIFJlZHVjZSBhcHBseXMgYSBmdW5jdGlvbiBjdW11bGF0aXZlbHkgbGVmdCB0byByaWdodA0KICBSZWR1Y2Uob2N0ZXQsIGFzLmludGVnZXIoaXBzKSkNCiAgfQ0KIyB0YWtlIGFuIDMyLWJpdCBpbnRlZ2VyIElQIGFkZHJlc3MgKGUuZy4gMzIzMjIzNTUyMSkNCiMgYW5kIGNvbnZlcnQgaXQgdG8gYSAoZS5nLiAiMTkyLjE2OC4wLjEiKS4NCiBsb25nMmlwIDwtIGZ1bmN0aW9uKGxvbmdpcCkgew0KIyBzZXQgdXAgcmV2ZXJzaW5nIGJpdCBtYW5pcHVsYXRpb24NCiAgb2N0ZXQgPC0gZnVuY3Rpb24obmJpdHMpIGJpdEFuZChiaXRTaGlmdFIobG9uZ2lwLCBuYml0cyksMHhGRikNCiMgTWFwIGFwcGx5cyBhIGZ1bmN0aW9uIHRvIGVhY2ggZWxlbWVudCBvZiB0aGUgYXJndW1lbnQNCiMgcGFzdGUgY29udmVydHMgYXJndW1lbnRzIHRvIGNoYXJhY3RlciBhbmQgY29uY2F0ZW5hdGVzIHRoZW0NCiAgcGFzdGUoTWFwKG9jdGV0LCBjKDI0LDE2LDgsMCkpLCBzZXA9IiIsIGNvbGxhcHNlPSIuIikNCn0NCmBgYA0KDQpgYGB7cn0NCiNUZXN0IHRoZSBmdW5jdGlvbnMgDQogIGlwMmxvbmcoIjE5Mi4xNjguMC4xIikNCiAgbG9uZzJpcCgzMjMyMjM1NTIxKQ0KYGBgDQoNCmBgYHtyfQ0KIyBMaXN0aW5nIDQtMg0KIyByZXF1aXJlcyBwYWNrYWdlczogYml0b3BzDQojIHJlcXVpcmVzIGFsbCBvYmplY3RzIGZyb20gNC0xDQojIERlZmluZSBmdW5jdGlvbiB0byB0ZXN0IGZvciBJUCBDSURSIG1lbWJlcnNoaXANCiMgdGFrZSBhbiBJUCBhZGRyZXNzIChzdHJpbmcpIGFuZCBhIENJRFIgKHN0cmluZykgYW5kDQojIHJldHVybiB3aGV0aGVyIHRoZSBnaXZlbiBJUCBhZGRyZXNzIGlzIGluIHRoZSBDSURSIHJhbmdlDQogIGlwLmlzLmluLmNpZHIgPC0gZnVuY3Rpb24oaXAsIGNpZHIpIHsNCiAgICBsb25nLmlwIDwtIGlwMmxvbmcoaXApDQogICAgY2lkci5wYXJ0cyA8LSB1bmxpc3Qoc3Ryc3BsaXQoY2lkciwgIi8iKSkNCiAgICBjaWRyLnJhbmdlIDwtIGlwMmxvbmcoY2lkci5wYXJ0c1sxXSkNCiAgICBjaWRyLm1hc2sgPC0gYml0U2hpZnRMKGJpdEZsaXAoMCksDQogICAgKDMyLWFzLmludGVnZXIoY2lkci5wYXJ0c1syXSkpKQ0KICAgIHJldHVybihiaXRBbmQobG9uZy5pcCwgY2lkci5tYXNrKSA9PSBiaXRBbmQoY2lkci5yYW5nZSwNCiAgICBjaWRyLm1hc2spKQ0KICB9DQpgYGANCg0KYGBge3J9DQppcC5pcy5pbi5jaWRyKCIxMC4wLjEuMTUiLCIxMC4wLjEuMy8yNCIpDQppcC5pcy5pbi5jaWRyKCIxMC4wLjEuMTUiLCIxMC4wLjIuMjU1LzI0IikNCmBgYA0KIyBDb252ZXJ0aW5nIEdlbyBDb29yZGluYXRlIHN0cmluZyBpbiBBViB0byBsYXQgbG9uZw0KYGBge3J9DQojIExpc3RpbmcgNC0zDQojIFIgY29kZSB0byBleHRyYWN0IGxvbmdpdHVkZS9sYXRpdHVkZSBwYWlycyBmcm9tIEFsaWVuVmF1bHQgZGF0YQ0KIyByZWFkIGluIHRoZSBBbGllblZhdWx0IHJlcHV0YXRpb24gZGF0YSAoc2VlIENoYXB0ZXIgMykNCiAgYXZSZXAgPC0gImRhdGEvcmVwdXRhdGlvbi5kYXRhIg0KICBhdi5kZiA8LSByZWFkLmNzdihhdlJlcCwgc2VwID0gIiMiLCBoZWFkZXIgPSBGQUxTRSkNCiAgY29sbmFtZXMoYXYuZGYpIDwtIGMoIklQIiwgIlJlbGlhYmlsaXR5IiwgIlJpc2siLCAiVHlwZSIsDQogICAgICAiQ291bnRyeSIsICJMb2NhbGUiLCAiQ29vcmRzIiwgIngiKQ0KIyBjcmVhdGUgYSB2ZWN0b3Igb2YgbGF0L2xvbmcgZGF0YSBieSBzcGxpdHRpbmcgb24gIiwiDQogIGF2LmNvb3Jkcy52ZWMgPC0gdW5saXN0KHN0cnNwbGl0KGFzLmNoYXJhY3Rlcihhdi5kZiRDb29yZHMpLCAiLCIpKQ0KIyBjb252ZXJ0IHRoZSB2ZWN0b3IgaW4gYSAyLWNvbHVtbiBtYXRyaXgNCiAgYXYuY29vcmRzLm1hdCA8LSBtYXRyaXgoYXYuY29vcmRzLnZlYywgbmNvbCA9IDIsIGJ5cm93ID0gVFJVRSkNCiMgcHJvamVjdCBpbnRvIGEgZGF0YSBmcmFtZQ0KICBhdi5jb29yZHMuZGYgPC0gYXMuZGF0YS5mcmFtZShhdi5jb29yZHMubWF0KQ0KIyBuYW1lIHRoZSBjb2x1bW5zDQogIGNvbG5hbWVzKGF2LmNvb3Jkcy5kZikgPC0gYygibGF0IiwibG9uZyIpDQojIGNvbnZlcnQgdGhlIGNoYXJhY3RlcnMgdG8gbnVtZXJpYyB2YWx1ZXMNCiAgYXYuZGYkbG9uZyA8LSBhcy5kb3VibGUoYXMuY2hhcmFjdGVyKGF2LmNvb3Jkcy5kZiRsb25nKSkNCiAgYXYuZGYkbGF0IDwtIGFzLmRvdWJsZShhcy5jaGFyYWN0ZXIoYXYuY29vcmRzLmRmJGxhdCkpDQpgYGANCg0KVGhlbiB2aXN1YWxpemUgdGhlc2UNCg0KYGBge3J9DQojIExpc3RpbmcgNC00DQojIHJlcXVpcmVzIHBhY2thZ2VzOiBnZ3Bsb3QyLCBtYXBzLCBSQ29sb3JCcmV3ZXINCiMgcmVxdWlyZXMgb2JqZWN0OiBhdi5jb29yZHMuZGYgKDQtMykNCiMgZ2VuZXJhdGVzIEZpZ3VyZSA0LTINCiMgUiBjb2RlIHRvIGV4dHJhY3QgbG9uZ2l0dWRlL2xhdGl0dWRlIHBhaXJzIGZyb20gQWxpZW5WYXVsdCBkYXRhDQojIG5lZWQgcGxvdHRpbmcgYW5kIG1hcHBpbmcgZnVuY3Rpb25zDQogIGxpYnJhcnkoZ2dwbG90MikNCiAgbGlicmFyeShtYXBzKQ0KICBsaWJyYXJ5KFJDb2xvckJyZXdlcikNCiAgbGlicmFyeShzY2FsZXMpDQojIGV4dHJhY3QgYSBjb2xvciBwYWxsZXRlIGZyb20gdGhlIFJDb2xvckJyZXdlciBwYWNrYWdlDQogIHNldDIgPC0gYnJld2VyLnBhbCg4LCJTZXQyIikNCiMgZXh0cmFjdCB0aGUgcG9seWdvbiBpbmZvcm1hdGlvbiBmb3IgdGhlIHdvcmxkIG1hcCwgbWludXMgQW50YXJjdGljYQ0KICB3b3JsZCA8LSBtYXBfZGF0YSgnd29ybGQnKQ0KICB3b3JsZCA8LSBzdWJzZXQod29ybGQsIHJlZ2lvbiAhPSAiQW50YXJjdGljYSIpDQojIHBsb3QgdGhlIG1hcCB3aXRoIHRoZSBwb2ludHMgbWFya2luZyBsYXQvbG9uIG9mIHRoZSBnZW9jb2RlZCBlbnRyaWVzDQojIENoYXB0ZXIgNSBleGFtcGxlcyBleHBsYWluIG1hcHBpbmcgaW4gZ3JlYXRlciBkZXRhaWwNCiAgZ2cgPC0gZ2dwbG90KGhlaWdodCA9IDYwMCwgd2lkdGggPSAxMjAwKQ0KICBnZyA8LSBnZyArIGdlb21fcG9seWdvbihkYXRhID0gd29ybGQsIGFlcyhsb25nLCBsYXQsIGdyb3VwID0gZ3JvdXApLA0KICAgICAgZmlsbCA9ICJ3aGl0ZSIpDQogIGdnIDwtIGdnICsgZ2VvbV9wb2ludChkYXRhID0gYXYuZGYsIGFlcyh4ID0gbG9uZywgeSA9IGxhdCwgDQogICAgICAgICAgICAgIHNpemUgPSBSaXNrKSwgY29sb3IgPSBzZXQyWzJdLCBhbHBoYSA9IDAuMSkgKw0KICAgICAgICAgICAgICBzY2FsZV9jb2xvcl9icmV3ZXIocGFsZXR0ZSA9ICJTcGVjdHJhbCIpDQogIGdnIDwtIGdnICsgbGFicyh4ID0gIiIsIHkgPSAiIikNCiAgZ2cgPC0gZ2cgKyB0aGVtZShwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9yZWN0KGZpbGwgPSBhbHBoYShzZXQyWzNdLDAuMiksDQogICAgICBjb2xvdXIgPSAnd2hpdGUnKSkNCiAgZ2cNCiAgIyBnZ3NhdmUoJ2RhdGEvdGVzdC5wZGYnLCB1bml0cyA9ICJpbiIsIHdpZHRoID0gMjAsIGhlaWdodCA9IDMwKQ0KICAjIGRldi5vZmYoKQ0KDQpgYGANCg0KRGVtb25zdHJhdGlvbiBvZiBHcmFwaCBUaGVvcnkgVmlzdWFsaXphdGlvbg0KDQpgYGB7cn0NCiMgTGlzdGluZyA0LTExDQojIFJldHJpZXZlIGFuZCByZWFkIFpldVMgYmxvY2tsaXN0IGRhdGEgaW50byBSDQogIHpldXNVUkwgPC0gImh0dHBzOi8vemV1c3RyYWNrZXIuYWJ1c2UuY2gvYmxvY2tsaXN0LnBocD9kb3dubG9hZD1pcGJsb2NrbGlzdCINCiAgemV1c0RhdGEgPC0gImRhdGEvemV1cy5jc3YiDQogIGlmIChmaWxlLmFjY2Vzcyh6ZXVzRGF0YSkpIHsNCiAgICAjIG5lZWQgdG8gY2hhbmdlIGRvd25sb2FkIG1ldGhvZCBmb3IgdW5pdmVyc2FsICJodHRwcyIgY29tcGF0aWJpbGl0eQ0KICAgICAgZG93bmxvYWQuZmlsZSh6ZXVzVVJMLCB6ZXVzRGF0YSwgbWV0aG9kID0gImN1cmwiKQ0KICB9DQojIHJlYWQgaW4gdGhlIFpldVMgdGFibGU7IHNraXAganVuazsgbm8gaGVhZGVyOyBhc3NpZ24gY29sbmFtZXMNCiAgemV1cyA8LSByZWFkLnRhYmxlKHpldXNEYXRhLCBza2lwID0gNSwgaGVhZGVyID0gRkFMU0UsIGNvbC5uYW1lcyA9IGMoIklQIikpDQpgYGANCg0KDQpgYGB7cn0NCiMgTGlzdGluZyA0LTE1DQojIHJlcXVpcmVzIG9iamVjdHM6IEJ1bGtPcmlnaW4oKSAmIEJ1bGtQZWVyKCkgZnJvbSBib29rJ3Mgd2ViIHNpdGUNCiMgcmVxdWlyZSBwYWNrYWdlOiBpZ3JhcGggKDQtMTEpDQojIGNyZWF0ZSBjb25uZWN0ZWQgbmV0d29yayBvZiBaZXVTIElQcywgQVNOcywgYW5kIEFTTiBwZWVycw0KIyBnZW5lcmF0ZXMgRmlndXJlIDQtOQ0KbGlicmFyeShpZ3JhcGgpDQpsaWJyYXJ5KHBseXIpDQpsaWJyYXJ5KGNvbG9yc3BhY2UpDQoNCg0KIyBPcGVuIHRoZSBmaWxlDQogIHpldXMgPC0gcmVhZC50YWJsZSgiZGF0YS96ZXVzLWJvb2suY3N2Iiwgc2tpcCA9IDUsIGhlYWRlciA9IEZBTFNFLA0KICBjb2wubmFtZXMgPSBjKCJJUCIpKQ0KICBpcHMgPC0gYXMuY2hhcmFjdGVyKHpldXMkSVApDQogIA0KIyBIRUxQRVIgRlVOQ1RJT04gTUVOVElPTkVEIElOIFRIRSBCT09LDQojIEJVVCBOT1QgSU4gVEhFIFBSSU5URUQgTElTVElOR1MNCg0KICB0cmltIDwtIGZ1bmN0aW9uKHgpIGdzdWIoIl5cXHMrfFxccyskIiwgIiIsIHgpDQoNCiMgSEVMUEVSIEZVTkNUSU9OIE1FTlRJT05FRCBJTiBUSEUgQk9PSw0KIyBCVVQgTk9UIElOIFRIRSBQUklOVEVEIExJU1RJTkdTDQoNCiAgQnVsa09yaWdpbiA8LSBmdW5jdGlvbihpcC5saXN0LGhvc3QgPSAidjQud2hvaXMuY3ltcnUuY29tIiwgcG9ydCA9IDQzKSB7DQogDQogICMgc2V0dXAgcXVlcnkNCiAgY21kIDwtICJiZWdpblxudmVyYm9zZVxuIiANCiAgaXBzIDwtIHBhc3RlKHVubGlzdChpcC5saXN0KSwgY29sbGFwc2UgPSAiXG4iKQ0KICBjbWQgPC0gc3ByaW50ZigiJXMlc1xuZW5kXG4iLGNtZCxpcHMpDQogIA0KICAjIHNldHVwIGNvbm5lY3Rpb24gYW5kIHBvc3QgcXVlcnkgDQogIGNvbiA8LSBzb2NrZXRDb25uZWN0aW9uKGhvc3QgPSBob3N0LCBwb3J0ID0gcG9ydCwgYmxvY2tpbmcgPSBUUlVFLG9wZW4gPSAicisiKSAgDQogIGNhdChjbWQsZmlsZSA9IGNvbikNCiAgcmVzcG9uc2UgPC0gcmVhZExpbmVzKGNvbikNCiAgY2xvc2UoY29uKQ0KICANCiAgIyB0cmltIGhlYWRlciwgc3BsaXQgZmllbGRzIGFuZCBjb252ZXJ0IHJlc3VsdHMNCiAgcmVzcG9uc2UgPC0gcmVzcG9uc2VbMjpsZW5ndGgocmVzcG9uc2UpXQ0KICByZXNwb25zZSA8LSBsYXBseShyZXNwb25zZSwuZnVuID0gZnVuY3Rpb24obikgew0KICAgIHNhcHBseShzdHJzcGxpdChuLCJ8IixmaXhlZCA9IFRSVUUpLHRyaW0pDQogICAgfSkNCiAgcmVzcG9uc2UgPC0gYWRwbHkocmVzcG9uc2UsYygxKSkNCiAgcmVzcG9uc2UgPC0gc3Vic2V0KHJlc3BvbnNlLCBzZWxlY3QgPSAtYyhYMSkgKQ0KICBuYW1lcyhyZXNwb25zZSkgPSBjKCJBUyIsIklQIiwiQkdQLlByZWZpeCIsIkNDIiwNCiAgICAgICAgICAgICAgICAgICAgICAiUmVnaXN0cnkiLCJBbGxvY2F0ZWQiLCJBUy5OYW1lIikNCiAgDQogIHJldHVybihyZXNwb25zZSkNCiAgDQp9DQoNCiMgSEVMUEVSIEZVTkNUSU9OIE1FTlRJT05FRCBJTiBUSEUgQk9PSw0KIyBCVVQgTk9UIElOIFRIRSBQUklOVEVEIExJU1RJTkdTDQoNCiAgQnVsa1BlZXIgPC0gZnVuY3Rpb24oaXAubGlzdCxob3N0ID0gInY0LXBlZXIud2hvaXMuY3ltcnUuY29tIiwgcG9ydCA9IDQzKSB7DQogICANCiAgIyBzZXR1cCBxdWVyeQ0KICBjbWQgPC0gImJlZ2luXG52ZXJib3NlXG4iIA0KICBpcHMgPC0gcGFzdGUodW5saXN0KGlwLmxpc3QpLCBjb2xsYXBzZSA9ICJcbiIpDQogIGNtZCA8LSBzcHJpbnRmKCIlcyVzXG5lbmRcbiIsY21kLGlwcykNCiAgDQogICMgc2V0dXAgY29ubmVjdGlvbiBhbmQgcG9zdCBxdWVyeQ0KICBjb24gPC0gc29ja2V0Q29ubmVjdGlvbihob3N0ID0gaG9zdCxwb3J0ID0gcG9ydCxibG9ja2luZyA9IFRSVUUsIG9wZW4gPSAicisiKSAgDQogIGNhdChjbWQsZmlsZSA9IGNvbikNCiAgcmVzcG9uc2UgPC0gcmVhZExpbmVzKGNvbikNCiAgY2xvc2UoY29uKQ0KICANCiAgIyB0cmltIGhlYWRlciwgc3BsaXQgZmllbGRzIGFuZCBjb252ZXJ0IHJlc3VsdHMNCiAgcmVzcG9uc2UgPC0gcmVzcG9uc2VbMjpsZW5ndGgocmVzcG9uc2UpXQ0KICByZXNwb25zZSA8LSBsYXBseShyZXNwb25zZSxmdW5jdGlvbihuKSB7DQogICAgc2FwcGx5KHN0cnNwbGl0KG4sInwiLGZpeGVkID0gVFJVRSksdHJpbSkNCiAgfSkgIA0KICByZXNwb25zZSA8LSBhZHBseShyZXNwb25zZSxjKDEpKQ0KICByZXNwb25zZSA8LSBzdWJzZXQocmVzcG9uc2UsIHNlbGVjdCA9IC1jKFgxKSApDQogIG5hbWVzKHJlc3BvbnNlKSA8LSBjKCJQZWVyLkFTIiwiSVAiLCJCR1AuUHJlZml4IiwiQ0MiLA0KICAgICAgICAgICAgICAgICAgICAgICAiUmVnaXN0cnkiLCJBbGxvY2F0ZWQiLCJQZWVyLkFTLk5hbWUiKQ0KICByZXR1cm4ocmVzcG9uc2UpDQogIA0KfQ0KDQojIEhFTFBFUiBGVU5DVElPTiBNRU5USU9ORUQgSU4gVEhFIEJPT0sNCiMgQlVUIE5PVCBJTiBUSEUgUFJJTlRFRCBMSVNUSU5HUw0KDQogIEJ1bGtPcmlnaW5BU04gPC0gZnVuY3Rpb24oYXNuLmxpc3QsaG9zdD0idjQud2hvaXMuY3ltcnUuY29tIiwgcG9ydCA9IDQzKSB7DQogIA0KICAjIHNldHVwIHF1ZXJ5DQogIGNtZCA8LSAiYmVnaW5cbnZlcmJvc2VcbiIgDQogIGlwcyA8LSBwYXN0ZSh1bmxpc3QoYXNuLmxpc3QpLCBjb2xsYXBzZSA9ICJcbiIpDQogIGNtZCA8LSBzcHJpbnRmKCIlcyVzXG5lbmRcbiIsY21kLGlwcykNCiAgDQogICMgc2V0dXAgY29ubmVjdGlvbiBhbmQgcG9zdCBxdWVyeQ0KICBjb24gPC0gc29ja2V0Q29ubmVjdGlvbihob3N0ID0gaG9zdCxwb3J0ID0gcG9ydCxibG9ja2luZyA9IFRSVUUsb3BlbiA9ICJyKyIpICANCiAgY2F0KGNtZCxmaWxlID0gY29uKQ0KICByZXNwb25zZSA8LSByZWFkTGluZXMoY29uKQ0KICBjbG9zZShjb24pDQogIA0KICAjIHRyaW0gaGVhZGVyLCBzcGxpdCBmaWVsZHMgYW5kIGNvbnZlcnQgcmVzdWx0cw0KICANCiAgcmVzcG9uc2UgPC0gcmVzcG9uc2VbMjpsZW5ndGgocmVzcG9uc2UpXQ0KICByZXNwb25zZSA8LSBsYXBseShyZXNwb25zZSwuZnVuID0gZnVuY3Rpb24obikgew0KICAgIHNhcHBseShzdHJzcGxpdChuLCJ8IixmaXhlZCA9IFRSVUUpLHRyaW0pDQogIH0pDQogIA0KICByZXNwb25zZSA8LSBhZHBseShyZXNwb25zZSxjKDEpKQ0KICByZXNwb25zZSA8LSBzdWJzZXQocmVzcG9uc2UsIHNlbGVjdCA9IC1jKFgxKSApDQogIG5hbWVzKHJlc3BvbnNlKSA8LSBjKCJBUyIsIkNDIiwiUmVnaXN0cnkiLCJBbGxvY2F0ZWQiLCJBUy5OYW1lIikNCiAgDQogIHJldHVybihyZXNwb25zZSkNCiAgDQogIH0NCg0KICBnIDwtIGdyYXBoLmVtcHR5KCkNCiAgZyA8LSBnICsgdmVydGljZXMoaXBzLCBzaXplID0gMywgY29sb3IgPSBzZXQyWzRdLCBncm91cCA9IDEpDQogIG9yaWdpbiA8LSBCdWxrT3JpZ2luKGlwcykNCiAgcGVlcnMgPC0gQnVsa1BlZXIoaXBzKQ0KIyBhZGQgQVNOIG9yaWdpbiAmIHBlZXIgdmVydGljZXMNCiAgZyA8LSBnICsgdmVydGljZXModW5pcXVlKGMocGVlcnMkUGVlci5BUywgb3JpZ2luJEFTKSksDQogIHNpemUgPSAzLCBjb2xvciA9IHNldDJbMl0sIGdyb3VwID0gMikNCiMgYnVpbGQgSVAtPkJHUCBlZGdlIGxpc3QNCiAgaXAuZWRnZXMgPC0gbGFwcGx5KGlwcywgZnVuY3Rpb24oeCkgew0KICBpQVMgPC0gb3JpZ2luW29yaWdpbiRJUCA9PSB4LCBdJEFTDQogIGxhcHBseShpQVMsZnVuY3Rpb24oeSl7DQogIGMoeCwgeSkNCiAgfSkNCiAgfSkNCiAgYmdwLmVkZ2VzIDwtIGxhcHBseSgNCiAgZ3JlcCgiTkEiLHVuaXF1ZShvcmlnaW4kQkdQLlByZWZpeCksdmFsdWUgPSBUUlVFLGludmVydCA9IFRSVUUpLA0KICBmdW5jdGlvbih4KSB7DQogIHN0YXJ0QVMgPC0gdW5pcXVlKG9yaWdpbltvcmlnaW4kQkdQLlByZWZpeCA9PSB4LF0kQVMpDQogICAgbGFwcGx5KHN0YXJ0QVMsZnVuY3Rpb24oeikgew0KICAgIHBBUyA8LSBwZWVyc1twZWVycyRCR1AuUHJlZml4ID09IHgsXSRQZWVyLkFTDQogICAgbGFwcGx5KHBBUyxmdW5jdGlvbih5KSB7DQogICAgYyh6LHkpDQogICAgfSkNCiAgfSkNCn0pDQogIA0KICBnIDwtIGcgKyBlZGdlcyh1bmxpc3QoaXAuZWRnZXMpKQ0KICBnIDwtIGcgKyBlZGdlcyh1bmxpc3QoYmdwLmVkZ2VzKSkNCiAgZyA8LSBkZWxldGUudmVydGljZXMoZywgd2hpY2goZGVncmVlKGcpIDwgMSkpDQogIGcgPC0gc2ltcGxpZnkoZywgZWRnZS5hdHRyLmNvbWIgPSBsaXN0KHdlaWdodCA9ICJzdW0iKSkNCiAgRShnKSRhcnJvdy5zaXplIDwtIDANCiAgVihnKVtncmVwKCJcXC4iLCBWKGcpJG5hbWUpXSRuYW1lID0gIiINCiAgTCA8LSBsYXlvdXQuZnJ1Y2h0ZXJtYW4ucmVpbmdvbGQoZywgbml0ZXIgPSAxMDAwMCwgYXJlYSA9IDMwKnZjb3VudChnKV4yKQ0KICBwYXIoYmcgPSAnd2hpdGUnKQ0KICBwbG90KGcsIG1hcmdpbiA9IDAsIGxheW91dCA9IEwsIHZlcnRleC5sYWJlbC5kaXN0ID0gMC41LA0KICB2ZXJ0ZXgubGFiZWwgPSBOQSwNCiAgbWFpbiA9ICJaZXVTIGJvdG5ldCBBU04rUGVlciBOZXR3b3JrIikNCmBgYA0KDQo=